summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:49:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:49:04 +0000
commit16f504a9dca3fe3b70568f67b7d41241ae485288 (patch)
treec60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Main
parentInitial commit. (diff)
downloadvirtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.tar.xz
virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.zip
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Main')
-rw-r--r--src/VBox/Main/.scm-settings92
-rw-r--r--src/VBox/Main/Certificates/Makefile.kup0
-rw-r--r--src/VBox/Main/Certificates/MicCorKEKCA2011_2011-06-24.crtbin0 -> 1516 bytes
-rw-r--r--src/VBox/Main/Certificates/MicCorUEFCA2011_2011-06-27.crtbin0 -> 1556 bytes
-rw-r--r--src/VBox/Main/Certificates/MicWinProPCA2011_2011-10-19.crtbin0 -> 1499 bytes
-rw-r--r--src/VBox/Main/Certificates/OrclUefiDefPk2021_2021-09-29.crtbin0 -> 991 bytes
-rw-r--r--src/VBox/Main/Config.kmk55
-rw-r--r--src/VBox/Main/Docs-Intro.cpp101
-rw-r--r--src/VBox/Main/Doxyfile.Main2469
-rw-r--r--src/VBox/Main/Makefile.kmk1925
-rw-r--r--src/VBox/Main/UnattendedTemplates/Makefile.kmk74
-rwxr-xr-xsrc/VBox/Main/UnattendedTemplates/debian_postinstall.sh331
-rw-r--r--src/VBox/Main/UnattendedTemplates/debian_preseed.cfg86
-rw-r--r--src/VBox/Main/UnattendedTemplates/fedora_ks.cfg90
-rw-r--r--src/VBox/Main/UnattendedTemplates/freebsd_installer.cfg30
-rwxr-xr-xsrc/VBox/Main/UnattendedTemplates/freebsd_postinstall.sh29
-rw-r--r--src/VBox/Main/UnattendedTemplates/lgw_ks.cfg91
-rwxr-xr-xsrc/VBox/Main/UnattendedTemplates/lgw_postinstall.sh524
-rw-r--r--src/VBox/Main/UnattendedTemplates/ol_ks.cfg108
-rwxr-xr-xsrc/VBox/Main/UnattendedTemplates/ol_postinstall.sh340
-rw-r--r--src/VBox/Main/UnattendedTemplates/os2_cid_install.cmd498
-rw-r--r--src/VBox/Main/UnattendedTemplates/os2_response_files.rsp325
-rw-r--r--src/VBox/Main/UnattendedTemplates/os2_util.exebin0 -> 7854 bytes
-rw-r--r--src/VBox/Main/UnattendedTemplates/redhat67_ks.cfg108
-rwxr-xr-xsrc/VBox/Main/UnattendedTemplates/redhat_postinstall.sh328
-rw-r--r--src/VBox/Main/UnattendedTemplates/rhel3_ks.cfg141
-rw-r--r--src/VBox/Main/UnattendedTemplates/rhel4_ks.cfg120
-rw-r--r--src/VBox/Main/UnattendedTemplates/rhel5_ks.cfg119
-rw-r--r--src/VBox/Main/UnattendedTemplates/suse_autoinstall.xml93
-rw-r--r--src/VBox/Main/UnattendedTemplates/ubuntu_preseed.cfg101
-rw-r--r--src/VBox/Main/UnattendedTemplates/win_nt5_unattended.sif60
-rw-r--r--src/VBox/Main/UnattendedTemplates/win_nt6_unattended.xml298
-rw-r--r--src/VBox/Main/UnattendedTemplates/win_postinstall.cmd164
-rw-r--r--src/VBox/Main/cbinding/Makefile.kmk158
-rw-r--r--src/VBox/Main/cbinding/VBoxCAPI.cpp1026
-rw-r--r--src/VBox/Main/cbinding/VBoxCAPI.rc61
-rw-r--r--src/VBox/Main/cbinding/VBoxCAPIGlue.c345
-rw-r--r--src/VBox/Main/cbinding/VBoxCAPIGlue.h.in68
-rw-r--r--src/VBox/Main/cbinding/capiidl.xsl2736
-rw-r--r--src/VBox/Main/cbinding/makefile.tstCAPIGlue63
-rw-r--r--src/VBox/Main/cbinding/tstCAPIGlue.c1145
-rw-r--r--src/VBox/Main/glue/AutoLock.cpp795
-rw-r--r--src/VBox/Main/glue/ErrorInfo.cpp374
-rw-r--r--src/VBox/Main/glue/EventQueue.cpp257
-rw-r--r--src/VBox/Main/glue/GetVBoxUserHomeDirectory.cpp142
-rw-r--r--src/VBox/Main/glue/Makefile.kup0
-rw-r--r--src/VBox/Main/glue/NativeEventQueue.cpp676
-rw-r--r--src/VBox/Main/glue/VBoxLogRelCreate.cpp214
-rw-r--r--src/VBox/Main/glue/com.cpp187
-rwxr-xr-xsrc/VBox/Main/glue/constants-python.xsl198
-rw-r--r--src/VBox/Main/glue/errorprint.cpp223
-rw-r--r--src/VBox/Main/glue/glue-java.xsl5259
-rw-r--r--src/VBox/Main/glue/initterm.cpp863
-rw-r--r--src/VBox/Main/glue/string-base64.cpp61
-rw-r--r--src/VBox/Main/glue/string.cpp1037
-rw-r--r--src/VBox/Main/glue/tests/Makefile81
-rw-r--r--src/VBox/Main/glue/tests/TestVBox.java310
-rw-r--r--src/VBox/Main/glue/tests/TestVBoxNATEngine.java206
-rw-r--r--src/VBox/Main/glue/vbox-err-consts.sed59
-rwxr-xr-xsrc/VBox/Main/glue/vboxapi.py1294
-rw-r--r--src/VBox/Main/glue/xpcom/Makefile.kup0
-rw-r--r--src/VBox/Main/glue/xpcom/helpers.cpp204
-rw-r--r--src/VBox/Main/idl/Makefile.kup0
-rw-r--r--src/VBox/Main/idl/VirtualBox.dtd159
-rw-r--r--src/VBox/Main/idl/VirtualBox.xidl30710
-rw-r--r--src/VBox/Main/idl/apiwrap-server-filelist.xsl192
-rw-r--r--src/VBox/Main/idl/apiwrap-server.xsl2592
-rw-r--r--src/VBox/Main/idl/comimpl.xsl1182
-rw-r--r--src/VBox/Main/idl/docstrip.xsl51
-rw-r--r--src/VBox/Main/idl/doxygen.xsl703
-rw-r--r--src/VBox/Main/idl/midl.xsl924
-rw-r--r--src/VBox/Main/idl/stringify-enums.xsl251
-rw-r--r--src/VBox/Main/idl/typemap-shared.inc.xsl571
-rw-r--r--src/VBox/Main/idl/xpidl.xsl1124
-rw-r--r--src/VBox/Main/idl/xpidl_iid.xsl172
-rw-r--r--src/VBox/Main/include/AdditionsFacilityImpl.h110
-rw-r--r--src/VBox/Main/include/ApplianceImpl.h367
-rw-r--r--src/VBox/Main/include/ApplianceImplPrivate.h572
-rw-r--r--src/VBox/Main/include/AudioAdapterImpl.h94
-rw-r--r--src/VBox/Main/include/AudioDriver.h144
-rw-r--r--src/VBox/Main/include/AudioSettingsImpl.h84
-rw-r--r--src/VBox/Main/include/AuthLibrary.h54
-rw-r--r--src/VBox/Main/include/AutoCaller.h535
-rw-r--r--src/VBox/Main/include/AutoStateDep.h214
-rw-r--r--src/VBox/Main/include/AutostartDb.h109
-rw-r--r--src/VBox/Main/include/BIOSSettingsImpl.h102
-rw-r--r--src/VBox/Main/include/BandwidthControlImpl.h111
-rw-r--r--src/VBox/Main/include/BandwidthGroupImpl.h115
-rw-r--r--src/VBox/Main/include/BusAssignmentManager.h90
-rw-r--r--src/VBox/Main/include/CPUProfileImpl.h75
-rw-r--r--src/VBox/Main/include/CertificateImpl.h112
-rw-r--r--src/VBox/Main/include/ClientToken.h118
-rw-r--r--src/VBox/Main/include/ClientTokenHolder.h112
-rw-r--r--src/VBox/Main/include/ClientWatcher.h146
-rw-r--r--src/VBox/Main/include/CloudGateway.h103
-rw-r--r--src/VBox/Main/include/CloudNetworkImpl.h79
-rw-r--r--src/VBox/Main/include/CloudProviderManagerImpl.h82
-rw-r--r--src/VBox/Main/include/ConsoleImpl.h1192
-rw-r--r--src/VBox/Main/include/ConsoleVRDPServer.h428
-rw-r--r--src/VBox/Main/include/CryptoUtils.h141
-rw-r--r--src/VBox/Main/include/DHCPConfigImpl.h529
-rw-r--r--src/VBox/Main/include/DHCPServerImpl.h130
-rw-r--r--src/VBox/Main/include/DataStreamImpl.h78
-rw-r--r--src/VBox/Main/include/DisplayImpl.h562
-rw-r--r--src/VBox/Main/include/DisplayUtils.h57
-rw-r--r--src/VBox/Main/include/DrvAudioRec.h79
-rw-r--r--src/VBox/Main/include/DrvAudioVRDE.h87
-rw-r--r--src/VBox/Main/include/EBMLWriter.h135
-rw-r--r--src/VBox/Main/include/EBML_MKV.h106
-rw-r--r--src/VBox/Main/include/EmulatedUSBImpl.h103
-rw-r--r--src/VBox/Main/include/EventImpl.h188
-rw-r--r--src/VBox/Main/include/ExtPackManagerImpl.h322
-rw-r--r--src/VBox/Main/include/ExtPackUtil.h158
-rw-r--r--src/VBox/Main/include/Global.h255
-rw-r--r--src/VBox/Main/include/GraphicsAdapterImpl.h88
-rw-r--r--src/VBox/Main/include/GuestCtrlImplPrivate.h1422
-rw-r--r--src/VBox/Main/include/GuestDebugControlImpl.h82
-rw-r--r--src/VBox/Main/include/GuestDirectoryImpl.h108
-rw-r--r--src/VBox/Main/include/GuestDnDPrivate.h1108
-rw-r--r--src/VBox/Main/include/GuestDnDSourceImpl.h131
-rw-r--r--src/VBox/Main/include/GuestDnDTargetImpl.h128
-rw-r--r--src/VBox/Main/include/GuestFileImpl.h161
-rw-r--r--src/VBox/Main/include/GuestFsObjInfoImpl.h86
-rw-r--r--src/VBox/Main/include/GuestImpl.h282
-rw-r--r--src/VBox/Main/include/GuestOSTypeImpl.h131
-rw-r--r--src/VBox/Main/include/GuestProcessImpl.h310
-rw-r--r--src/VBox/Main/include/GuestSessionImpl.h461
-rw-r--r--src/VBox/Main/include/GuestSessionImplTasks.h435
-rw-r--r--src/VBox/Main/include/HGCM.h69
-rw-r--r--src/VBox/Main/include/HGCMObjects.h138
-rw-r--r--src/VBox/Main/include/HGCMThread.h238
-rw-r--r--src/VBox/Main/include/HashedPw.h40
-rw-r--r--src/VBox/Main/include/HostAudioDeviceImpl.h58
-rw-r--r--src/VBox/Main/include/HostDriveImpl.h86
-rw-r--r--src/VBox/Main/include/HostDrivePartitionImpl.h126
-rw-r--r--src/VBox/Main/include/HostHardwareLinux.h209
-rw-r--r--src/VBox/Main/include/HostImpl.h227
-rw-r--r--src/VBox/Main/include/HostNetworkInterfaceImpl.h145
-rw-r--r--src/VBox/Main/include/HostOnlyNetworkImpl.h80
-rw-r--r--src/VBox/Main/include/HostPower.h136
-rw-r--r--src/VBox/Main/include/HostUSBDeviceImpl.h327
-rw-r--r--src/VBox/Main/include/HostVideoInputDeviceImpl.h82
-rw-r--r--src/VBox/Main/include/KeyboardImpl.h110
-rw-r--r--src/VBox/Main/include/LoggingNew.h60
-rw-r--r--src/VBox/Main/include/MachineDebuggerImpl.h176
-rw-r--r--src/VBox/Main/include/MachineImpl.h1691
-rw-r--r--src/VBox/Main/include/MachineImplCloneVM.h63
-rw-r--r--src/VBox/Main/include/MachineImplMoveVM.h146
-rw-r--r--src/VBox/Main/include/MachineLaunchVMCommonWorker.h49
-rw-r--r--src/VBox/Main/include/Makefile.kup0
-rw-r--r--src/VBox/Main/include/Matching.h540
-rw-r--r--src/VBox/Main/include/MediumAttachmentImpl.h146
-rw-r--r--src/VBox/Main/include/MediumFormatImpl.h125
-rw-r--r--src/VBox/Main/include/MediumIOImpl.h92
-rw-r--r--src/VBox/Main/include/MediumImpl.h473
-rw-r--r--src/VBox/Main/include/MediumLock.h351
-rw-r--r--src/VBox/Main/include/MouseImpl.h175
-rw-r--r--src/VBox/Main/include/NATEngineImpl.h124
-rw-r--r--src/VBox/Main/include/NATNetworkImpl.h143
-rw-r--r--src/VBox/Main/include/NetworkAdapterImpl.h141
-rw-r--r--src/VBox/Main/include/NetworkServiceRunner.h87
-rw-r--r--src/VBox/Main/include/NvramStoreImpl.h156
-rw-r--r--src/VBox/Main/include/ObjectState.h150
-rw-r--r--src/VBox/Main/include/PCIDeviceAttachmentImpl.h79
-rw-r--r--src/VBox/Main/include/PCIRawDevImpl.h64
-rw-r--r--src/VBox/Main/include/ParallelPortImpl.h87
-rw-r--r--src/VBox/Main/include/Performance.h915
-rw-r--r--src/VBox/Main/include/PerformanceImpl.h206
-rw-r--r--src/VBox/Main/include/ProgressImpl.h259
-rw-r--r--src/VBox/Main/include/ProgressProxyImpl.h130
-rw-r--r--src/VBox/Main/include/QMTranslator.h79
-rw-r--r--src/VBox/Main/include/Recording.h174
-rw-r--r--src/VBox/Main/include/RecordingInternals.h504
-rw-r--r--src/VBox/Main/include/RecordingScreenSettingsImpl.h141
-rw-r--r--src/VBox/Main/include/RecordingSettingsImpl.h102
-rw-r--r--src/VBox/Main/include/RecordingStream.h234
-rw-r--r--src/VBox/Main/include/RecordingUtils.h217
-rw-r--r--src/VBox/Main/include/RemoteUSBBackend.h153
-rw-r--r--src/VBox/Main/include/RemoteUSBDeviceImpl.h136
-rw-r--r--src/VBox/Main/include/SecretKeyStore.h216
-rw-r--r--src/VBox/Main/include/SerialPortImpl.h102
-rw-r--r--src/VBox/Main/include/SessionImpl.h188
-rw-r--r--src/VBox/Main/include/SharedFolderImpl.h129
-rw-r--r--src/VBox/Main/include/SnapshotImpl.h142
-rw-r--r--src/VBox/Main/include/StorageControllerImpl.h107
-rw-r--r--src/VBox/Main/include/SystemPropertiesImpl.h220
-rw-r--r--src/VBox/Main/include/TextScript.h251
-rw-r--r--src/VBox/Main/include/ThreadTask.h78
-rw-r--r--src/VBox/Main/include/TokenImpl.h118
-rw-r--r--src/VBox/Main/include/TrustAnchorsAndCerts.h53
-rw-r--r--src/VBox/Main/include/TrustedPlatformModuleImpl.h84
-rw-r--r--src/VBox/Main/include/USBControllerImpl.h88
-rw-r--r--src/VBox/Main/include/USBDeviceFilterImpl.h240
-rw-r--r--src/VBox/Main/include/USBDeviceFiltersImpl.h97
-rw-r--r--src/VBox/Main/include/USBDeviceImpl.h118
-rw-r--r--src/VBox/Main/include/USBGetDevices.h114
-rw-r--r--src/VBox/Main/include/USBIdDatabase.h226
-rw-r--r--src/VBox/Main/include/USBProxyBackend.h448
-rw-r--r--src/VBox/Main/include/USBProxyService.h147
-rw-r--r--src/VBox/Main/include/UefiVariableStoreImpl.h104
-rw-r--r--src/VBox/Main/include/UnattendedImpl.h318
-rw-r--r--src/VBox/Main/include/UnattendedInstaller.h891
-rw-r--r--src/VBox/Main/include/UnattendedScript.h193
-rw-r--r--src/VBox/Main/include/UpdateAgentImpl.h220
-rw-r--r--src/VBox/Main/include/UsbCardReader.h87
-rw-r--r--src/VBox/Main/include/UsbWebcamInterface.h79
-rw-r--r--src/VBox/Main/include/VBoxNls.h65
-rw-r--r--src/VBox/Main/include/VFSExplorerImpl.h101
-rw-r--r--src/VBox/Main/include/VMMDev.h115
-rw-r--r--src/VBox/Main/include/VRDEServerImpl.h98
-rw-r--r--src/VBox/Main/include/VirtualBoxBase.h1118
-rw-r--r--src/VBox/Main/include/VirtualBoxClientImpl.h144
-rw-r--r--src/VBox/Main/include/VirtualBoxErrorInfoImpl.h175
-rw-r--r--src/VBox/Main/include/VirtualBoxImpl.h502
-rw-r--r--src/VBox/Main/include/VirtualBoxSDSImpl.h161
-rw-r--r--src/VBox/Main/include/VirtualBoxTranslator.h165
-rw-r--r--src/VBox/Main/include/WebMWriter.h579
-rw-r--r--src/VBox/Main/include/Wrapper.h504
-rw-r--r--src/VBox/Main/include/netif.h145
-rw-r--r--src/VBox/Main/include/objectslist.h224
-rw-r--r--src/VBox/Main/include/ovfreader.h732
-rw-r--r--src/VBox/Main/include/vbox-libhal.h80
-rw-r--r--src/VBox/Main/include/vector.h368
-rw-r--r--src/VBox/Main/include/win/resource.h43
-rw-r--r--src/VBox/Main/nls/ApprovedLanguages.kmk31
-rw-r--r--src/VBox/Main/nls/VirtualBoxAPI_ru.ts9628
-rw-r--r--src/VBox/Main/nls/VirtualBoxAPI_xx_YY.ts9493
-rw-r--r--src/VBox/Main/nls/dummy.c32
-rw-r--r--src/VBox/Main/src-all/AuthLibrary.cpp267
-rw-r--r--src/VBox/Main/src-all/AutoCaller.cpp585
-rw-r--r--src/VBox/Main/src-all/CryptoUtils.cpp344
-rw-r--r--src/VBox/Main/src-all/DisplayPNGUtil.cpp233
-rw-r--r--src/VBox/Main/src-all/DisplayResampleImage.cpp159
-rw-r--r--src/VBox/Main/src-all/DisplayUtils.cpp225
-rw-r--r--src/VBox/Main/src-all/EventImpl.cpp1690
-rw-r--r--src/VBox/Main/src-all/ExtPackManagerImpl.cpp3839
-rw-r--r--src/VBox/Main/src-all/ExtPackUtil.cpp1474
-rw-r--r--src/VBox/Main/src-all/Global.cpp787
-rw-r--r--src/VBox/Main/src-all/GlobalStatusConversion.cpp148
-rw-r--r--src/VBox/Main/src-all/HashedPw.cpp114
-rw-r--r--src/VBox/Main/src-all/Logging.cpp33
-rw-r--r--src/VBox/Main/src-all/MachineLaunchVMCommonWorker.cpp289
-rw-r--r--src/VBox/Main/src-all/Makefile.kup0
-rw-r--r--src/VBox/Main/src-all/NvramStoreImpl.cpp1645
-rw-r--r--src/VBox/Main/src-all/PCIDeviceAttachmentImpl.cpp160
-rw-r--r--src/VBox/Main/src-all/ProgressImpl.cpp1215
-rw-r--r--src/VBox/Main/src-all/QMTranslatorImpl.cpp671
-rw-r--r--src/VBox/Main/src-all/SecretKeyStore.cpp248
-rw-r--r--src/VBox/Main/src-all/SharedFolderImpl.cpp465
-rw-r--r--src/VBox/Main/src-all/TextScript.cpp388
-rw-r--r--src/VBox/Main/src-all/ThreadTask.cpp136
-rw-r--r--src/VBox/Main/src-all/VBoxAPI-end-alternative.d3
-rw-r--r--src/VBox/Main/src-all/VBoxAPI-end.d9
-rw-r--r--src/VBox/Main/src-all/VBoxAPI-start-alternative.d39
-rw-r--r--src/VBox/Main/src-all/VBoxAPI-start.d33
-rw-r--r--src/VBox/Main/src-all/VBoxLibSsh.def35
-rw-r--r--src/VBox/Main/src-all/VirtualBoxBase.cpp884
-rw-r--r--src/VBox/Main/src-all/VirtualBoxErrorInfoImpl.cpp317
-rw-r--r--src/VBox/Main/src-all/VirtualBoxTranslator.cpp593
-rw-r--r--src/VBox/Main/src-all/win/Makefile.kup0
-rw-r--r--src/VBox/Main/src-all/win/VBoxAPIWrap-precomp_vcc.h50
-rw-r--r--src/VBox/Main/src-all/win/VBoxProxyStub-x86.rc34
-rw-r--r--src/VBox/Main/src-all/win/VBoxProxyStub.c2599
-rw-r--r--src/VBox/Main/src-all/win/VBoxProxyStub.def36
-rw-r--r--src/VBox/Main/src-all/win/VBoxProxyStub.rc34
-rw-r--r--src/VBox/Main/src-all/win/VBoxProxyStubLegacy.rc34
-rw-r--r--src/VBox/Main/src-all/win/VirtualBox_rgs.xsl196
-rw-r--r--src/VBox/Main/src-all/win/comregister.cmd212
-rw-r--r--src/VBox/Main/src-all/xpcom/VBoxAPIWrap-precomp_gcc.h52
-rw-r--r--src/VBox/Main/src-client/AdditionsFacilityImpl.cpp231
-rw-r--r--src/VBox/Main/src-client/AudioDriver.cpp337
-rw-r--r--src/VBox/Main/src-client/BusAssignmentManager.cpp711
-rw-r--r--src/VBox/Main/src-client/ClientTokenHolder.cpp347
-rw-r--r--src/VBox/Main/src-client/CloudGateway.cpp309
-rw-r--r--src/VBox/Main/src-client/ConsoleImpl.cpp11964
-rw-r--r--src/VBox/Main/src-client/ConsoleImpl2.cpp6915
-rw-r--r--src/VBox/Main/src-client/ConsoleImplTeleporter.cpp1483
-rw-r--r--src/VBox/Main/src-client/ConsoleVRDPServer.cpp4059
-rw-r--r--src/VBox/Main/src-client/DisplayImpl.cpp3872
-rw-r--r--src/VBox/Main/src-client/DisplayImplLegacy.cpp1018
-rw-r--r--src/VBox/Main/src-client/DisplaySourceBitmapImpl.cpp196
-rw-r--r--src/VBox/Main/src-client/DrvAudioRec.cpp972
-rw-r--r--src/VBox/Main/src-client/DrvAudioVRDE.cpp823
-rw-r--r--src/VBox/Main/src-client/EBMLWriter.cpp275
-rw-r--r--src/VBox/Main/src-client/EmulatedUSBImpl.cpp678
-rw-r--r--src/VBox/Main/src-client/GuestCtrlImpl.cpp726
-rw-r--r--src/VBox/Main/src-client/GuestCtrlPrivate.cpp1900
-rw-r--r--src/VBox/Main/src-client/GuestDirectoryImpl.cpp501
-rw-r--r--src/VBox/Main/src-client/GuestDnDPrivate.cpp1642
-rw-r--r--src/VBox/Main/src-client/GuestDnDSourceImpl.cpp1733
-rw-r--r--src/VBox/Main/src-client/GuestDnDTargetImpl.cpp1789
-rw-r--r--src/VBox/Main/src-client/GuestFileImpl.cpp1902
-rw-r--r--src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp237
-rw-r--r--src/VBox/Main/src-client/GuestImpl.cpp1201
-rw-r--r--src/VBox/Main/src-client/GuestProcessImpl.cpp3031
-rw-r--r--src/VBox/Main/src-client/GuestSessionImpl.cpp4821
-rw-r--r--src/VBox/Main/src-client/GuestSessionImplTasks.cpp3307
-rw-r--r--src/VBox/Main/src-client/HGCM.cpp3040
-rw-r--r--src/VBox/Main/src-client/HGCMObjects.cpp286
-rw-r--r--src/VBox/Main/src-client/HGCMThread.cpp790
-rw-r--r--src/VBox/Main/src-client/KeyboardImpl.cpp548
-rw-r--r--src/VBox/Main/src-client/MachineDebuggerImpl.cpp1560
-rw-r--r--src/VBox/Main/src-client/Makefile.kup0
-rw-r--r--src/VBox/Main/src-client/MouseImpl.cpp1404
-rw-r--r--src/VBox/Main/src-client/PCIRawDevImpl.cpp230
-rw-r--r--src/VBox/Main/src-client/README.testing16
-rw-r--r--src/VBox/Main/src-client/Recording.cpp945
-rw-r--r--src/VBox/Main/src-client/RecordingCodec.cpp894
-rw-r--r--src/VBox/Main/src-client/RecordingInternals.cpp158
-rw-r--r--src/VBox/Main/src-client/RecordingStream.cpp1039
-rw-r--r--src/VBox/Main/src-client/RecordingUtils.cpp290
-rw-r--r--src/VBox/Main/src-client/RemoteUSBBackend.cpp1414
-rw-r--r--src/VBox/Main/src-client/RemoteUSBDeviceImpl.cpp314
-rw-r--r--src/VBox/Main/src-client/SessionImpl.cpp1339
-rw-r--r--src/VBox/Main/src-client/USBDeviceImpl.cpp351
-rw-r--r--src/VBox/Main/src-client/UsbCardReader.cpp1986
-rw-r--r--src/VBox/Main/src-client/UsbWebcamInterface.cpp492
-rw-r--r--src/VBox/Main/src-client/VBoxDriversRegister.cpp124
-rw-r--r--src/VBox/Main/src-client/VMMDevInterface.cpp1265
-rw-r--r--src/VBox/Main/src-client/VirtualBoxClientImpl.cpp833
-rw-r--r--src/VBox/Main/src-client/WebMWriter.cpp881
-rw-r--r--src/VBox/Main/src-client/win/Makefile.kup0
-rw-r--r--src/VBox/Main/src-client/win/VBoxC.def37
-rw-r--r--src/VBox/Main/src-client/win/VBoxC.rc72
-rw-r--r--src/VBox/Main/src-client/win/VBoxClient-x86.def36
-rw-r--r--src/VBox/Main/src-client/win/VBoxClient-x86.rc73
-rw-r--r--src/VBox/Main/src-client/win/dllmain.cpp176
-rw-r--r--src/VBox/Main/src-client/win/precomp_vcc.h49
-rw-r--r--src/VBox/Main/src-client/xpcom/Makefile.kup0
-rw-r--r--src/VBox/Main/src-client/xpcom/module.cpp159
-rw-r--r--src/VBox/Main/src-client/xpcom/precomp_gcc.h53
-rw-r--r--src/VBox/Main/src-global/Makefile.kup0
-rw-r--r--src/VBox/Main/src-global/win/Makefile.kup0
-rw-r--r--src/VBox/Main/src-global/win/VBoxSDS.cpp1047
-rw-r--r--src/VBox/Main/src-global/win/VBoxSDS.rc88
-rw-r--r--src/VBox/Main/src-global/win/VirtualBoxSDSImpl.cpp1371
-rw-r--r--src/VBox/Main/src-helper-apps/Makefile.kup0
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk107
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTest.cpp113
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestApp.cpp526
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestDarwin.cpp183
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/VBoxFBOverlayCommon.h131
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/VBoxGLSupportInfo.cpp727
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/VBoxTestOGL.rc61
-rw-r--r--src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp2017
-rw-r--r--src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.rc61
-rw-r--r--src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp107
-rw-r--r--src/VBox/Main/src-helper-apps/os2/Makefile.kmk66
-rw-r--r--src/VBox/Main/src-helper-apps/os2/os2_util.c1031
-rw-r--r--src/VBox/Main/src-helper-apps/os2/os2_utilA.asm54
-rw-r--r--src/VBox/Main/src-server/ApplianceImpl.cpp1943
-rw-r--r--src/VBox/Main/src-server/ApplianceImplExport.cpp2872
-rw-r--r--src/VBox/Main/src-server/ApplianceImplImport.cpp6170
-rw-r--r--src/VBox/Main/src-server/AudioAdapterImpl.cpp690
-rw-r--r--src/VBox/Main/src-server/AudioSettingsImpl.cpp434
-rw-r--r--src/VBox/Main/src-server/BIOSSettingsImpl.cpp621
-rw-r--r--src/VBox/Main/src-server/BandwidthControlImpl.cpp596
-rw-r--r--src/VBox/Main/src-server/BandwidthGroupImpl.cpp355
-rw-r--r--src/VBox/Main/src-server/CPUProfileImpl.cpp146
-rw-r--r--src/VBox/Main/src-server/CertificateImpl.cpp590
-rw-r--r--src/VBox/Main/src-server/ClientToken.cpp350
-rw-r--r--src/VBox/Main/src-server/ClientWatcher.cpp1055
-rw-r--r--src/VBox/Main/src-server/CloudNetworkImpl.cpp262
-rw-r--r--src/VBox/Main/src-server/CloudProviderManagerImpl.cpp321
-rw-r--r--src/VBox/Main/src-server/DHCPConfigImpl.cpp1509
-rw-r--r--src/VBox/Main/src-server/DHCPServerImpl.cpp1276
-rw-r--r--src/VBox/Main/src-server/DataStreamImpl.cpp297
-rw-r--r--src/VBox/Main/src-server/GraphicsAdapterImpl.cpp445
-rw-r--r--src/VBox/Main/src-server/GuestDebugControlImpl.cpp457
-rw-r--r--src/VBox/Main/src-server/GuestOSTypeImpl.cpp470
-rw-r--r--src/VBox/Main/src-server/HostAudioDeviceImpl.cpp99
-rw-r--r--src/VBox/Main/src-server/HostDnsService.cpp440
-rw-r--r--src/VBox/Main/src-server/HostDnsService.h302
-rw-r--r--src/VBox/Main/src-server/HostDnsServiceResolvConf.cpp131
-rw-r--r--src/VBox/Main/src-server/HostDriveImpl.cpp273
-rw-r--r--src/VBox/Main/src-server/HostDrivePartitionImpl.cpp381
-rw-r--r--src/VBox/Main/src-server/HostImpl.cpp4162
-rw-r--r--src/VBox/Main/src-server/HostNetworkInterfaceImpl.cpp812
-rw-r--r--src/VBox/Main/src-server/HostOnlyNetworkImpl.cpp287
-rw-r--r--src/VBox/Main/src-server/HostPower.cpp208
-rw-r--r--src/VBox/Main/src-server/HostUSBDeviceImpl.cpp2596
-rw-r--r--src/VBox/Main/src-server/HostVideoInputDeviceImpl.cpp256
-rw-r--r--src/VBox/Main/src-server/MachineImpl.cpp17130
-rw-r--r--src/VBox/Main/src-server/MachineImplCloneVM.cpp1698
-rw-r--r--src/VBox/Main/src-server/MachineImplMoveVM.cpp1702
-rw-r--r--src/VBox/Main/src-server/Makefile.kup0
-rw-r--r--src/VBox/Main/src-server/Matching.cpp212
-rw-r--r--src/VBox/Main/src-server/MediumAttachmentImpl.cpp644
-rw-r--r--src/VBox/Main/src-server/MediumFormatImpl.cpp279
-rw-r--r--src/VBox/Main/src-server/MediumIOImpl.cpp905
-rw-r--r--src/VBox/Main/src-server/MediumImpl.cpp11233
-rw-r--r--src/VBox/Main/src-server/MediumLock.cpp401
-rw-r--r--src/VBox/Main/src-server/NATEngineImpl.cpp627
-rw-r--r--src/VBox/Main/src-server/NATNetworkImpl.cpp1239
-rw-r--r--src/VBox/Main/src-server/NetworkAdapterImpl.cpp1616
-rw-r--r--src/VBox/Main/src-server/NetworkServiceRunner.cpp307
-rw-r--r--src/VBox/Main/src-server/ParallelPortImpl.cpp572
-rw-r--r--src/VBox/Main/src-server/Performance.cpp1511
-rw-r--r--src/VBox/Main/src-server/PerformanceImpl.cpp884
-rw-r--r--src/VBox/Main/src-server/ProgressProxyImpl.cpp709
-rw-r--r--src/VBox/Main/src-server/RecordingScreenSettingsImpl.cpp1250
-rw-r--r--src/VBox/Main/src-server/RecordingSettingsImpl.cpp866
-rw-r--r--src/VBox/Main/src-server/SerialPortImpl.cpp787
-rw-r--r--src/VBox/Main/src-server/SnapshotImpl.cpp4330
-rw-r--r--src/VBox/Main/src-server/StorageControllerImpl.cpp848
-rw-r--r--src/VBox/Main/src-server/SystemPropertiesImpl.cpp2402
-rw-r--r--src/VBox/Main/src-server/TokenImpl.cpp227
-rw-r--r--src/VBox/Main/src-server/TrustedPlatformModuleImpl.cpp367
-rw-r--r--src/VBox/Main/src-server/USBControllerImpl.cpp459
-rw-r--r--src/VBox/Main/src-server/USBDeviceFilterImpl.cpp1286
-rw-r--r--src/VBox/Main/src-server/USBDeviceFiltersImpl.cpp1091
-rw-r--r--src/VBox/Main/src-server/USBIdDatabaseGenerator.cpp488
-rw-r--r--src/VBox/Main/src-server/USBIdDatabaseStub.cpp39
-rw-r--r--src/VBox/Main/src-server/USBProxyBackend.cpp759
-rw-r--r--src/VBox/Main/src-server/USBProxyService.cpp971
-rw-r--r--src/VBox/Main/src-server/UefiVariableStoreImpl.cpp960
-rw-r--r--src/VBox/Main/src-server/UnattendedImpl.cpp4292
-rw-r--r--src/VBox/Main/src-server/UnattendedInstaller.cpp1590
-rw-r--r--src/VBox/Main/src-server/UnattendedOs2Installer.cpp1228
-rw-r--r--src/VBox/Main/src-server/UnattendedScript.cpp957
-rw-r--r--src/VBox/Main/src-server/UpdateAgentImpl.cpp1176
-rw-r--r--src/VBox/Main/src-server/VFSExplorerImpl.cpp547
-rw-r--r--src/VBox/Main/src-server/VRDEServerImpl.cpp975
-rw-r--r--src/VBox/Main/src-server/VirtualBoxImpl.cpp6616
-rw-r--r--src/VBox/Main/src-server/custom.ids10
-rw-r--r--src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp281
-rw-r--r--src/VBox/Main/src-server/darwin/HostPowerDarwin.cpp254
-rw-r--r--src/VBox/Main/src-server/darwin/Makefile.kup0
-rw-r--r--src/VBox/Main/src-server/darwin/NetIf-darwin.cpp558
-rw-r--r--src/VBox/Main/src-server/darwin/PerformanceDarwin.cpp191
-rw-r--r--src/VBox/Main/src-server/darwin/USBProxyBackendDarwin.cpp195
-rw-r--r--src/VBox/Main/src-server/darwin/iokit.cpp1992
-rw-r--r--src/VBox/Main/src-server/darwin/iokit.h117
-rw-r--r--src/VBox/Main/src-server/freebsd/HostHardwareFreeBSD.cpp560
-rw-r--r--src/VBox/Main/src-server/freebsd/Makefile.kup0
-rw-r--r--src/VBox/Main/src-server/freebsd/NetIf-freebsd.cpp460
-rw-r--r--src/VBox/Main/src-server/freebsd/PerformanceFreeBSD.cpp128
-rw-r--r--src/VBox/Main/src-server/freebsd/USBProxyBackendFreeBSD.cpp353
-rw-r--r--src/VBox/Main/src-server/generic/AutostartDb-generic.cpp272
-rw-r--r--src/VBox/Main/src-server/generic/Makefile.kup0
-rw-r--r--src/VBox/Main/src-server/generic/NetIf-generic.cpp432
-rw-r--r--src/VBox/Main/src-server/generic/USBProxyBackendUsbIp.cpp1088
-rw-r--r--src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp252
-rw-r--r--src/VBox/Main/src-server/linux/HostHardwareLinux.cpp1369
-rw-r--r--src/VBox/Main/src-server/linux/HostPowerLinux.cpp199
-rw-r--r--src/VBox/Main/src-server/linux/Makefile.kup0
-rw-r--r--src/VBox/Main/src-server/linux/NetIf-linux.cpp330
-rw-r--r--src/VBox/Main/src-server/linux/PerformanceLinux.cpp610
-rw-r--r--src/VBox/Main/src-server/linux/USBGetDevices.cpp1800
-rw-r--r--src/VBox/Main/src-server/linux/USBProxyBackendLinux.cpp399
-rw-r--r--src/VBox/Main/src-server/linux/vbox-libhal.cpp105
-rw-r--r--src/VBox/Main/src-server/os2/Makefile.kup0
-rw-r--r--src/VBox/Main/src-server/os2/NetIf-os2.cpp66
-rw-r--r--src/VBox/Main/src-server/os2/PerformanceOs2.cpp75
-rw-r--r--src/VBox/Main/src-server/os2/USBProxyBackendOs2.cpp291
-rw-r--r--src/VBox/Main/src-server/solaris/DynLoadLibSolaris.cpp85
-rw-r--r--src/VBox/Main/src-server/solaris/DynLoadLibSolaris.h50
-rw-r--r--src/VBox/Main/src-server/solaris/Makefile.kup0
-rw-r--r--src/VBox/Main/src-server/solaris/NetIf-solaris.cpp559
-rw-r--r--src/VBox/Main/src-server/solaris/PerformanceSolaris.cpp743
-rw-r--r--src/VBox/Main/src-server/solaris/USBProxyBackendSolaris.cpp496
-rw-r--r--src/VBox/Main/src-server/usb.ids20663
-rw-r--r--src/VBox/Main/src-server/win/HostDnsServiceWin.cpp488
-rw-r--r--src/VBox/Main/src-server/win/HostPowerWin.cpp238
-rw-r--r--src/VBox/Main/src-server/win/Makefile.kup0
-rw-r--r--src/VBox/Main/src-server/win/NetIf-win.cpp2017
-rw-r--r--src/VBox/Main/src-server/win/PerformanceWin.cpp357
-rw-r--r--src/VBox/Main/src-server/win/USBProxyBackendWindows.cpp274
-rw-r--r--src/VBox/Main/src-server/win/VBoxSVC.rc78
-rw-r--r--src/VBox/Main/src-server/win/precomp_vcc.h48
-rw-r--r--src/VBox/Main/src-server/win/svchlp.cpp308
-rw-r--r--src/VBox/Main/src-server/win/svchlp.h107
-rw-r--r--src/VBox/Main/src-server/win/svcmain.cpp1212
-rw-r--r--src/VBox/Main/src-server/xpcom/Makefile.kup0
-rw-r--r--src/VBox/Main/src-server/xpcom/precomp_gcc.h52
-rw-r--r--src/VBox/Main/src-server/xpcom/server.cpp988
-rw-r--r--src/VBox/Main/src-server/xpcom/server.h50
-rw-r--r--src/VBox/Main/src-server/xpcom/server_module.cpp383
-rw-r--r--src/VBox/Main/testcase/Makefile.kmk343
-rw-r--r--src/VBox/Main/testcase/VBoxVBTest/TestForm.frm137
-rw-r--r--src/VBox/Main/testcase/VBoxVBTest/TestForm.frxbin0 -> 155 bytes
-rw-r--r--src/VBox/Main/testcase/VBoxVBTest/VBoxVBTest.vbp34
-rw-r--r--src/VBox/Main/testcase/makefile.tstVBoxAPIWin100
-rw-r--r--src/VBox/Main/testcase/makefile.tstVBoxAPIXPCOM65
-rw-r--r--src/VBox/Main/testcase/msiDarwinDescriptorDecoder.cpp81
-rw-r--r--src/VBox/Main/testcase/ovf-joomla-0.9/joomla-1.1.4-ovf.ovf83
-rw-r--r--src/VBox/Main/testcase/ovf-winhost-audio-nodisks/WinXP.ovf267
-rw-r--r--src/VBox/Main/testcase/ovf-winxp-vbox-sharedfolders/winxp.ovf315
-rw-r--r--src/VBox/Main/testcase/tstAPI.cpp1706
-rw-r--r--src/VBox/Main/testcase/tstBstr.cpp295
-rw-r--r--src/VBox/Main/testcase/tstCollector.cpp588
-rw-r--r--src/VBox/Main/testcase/tstGuestCtrlContextID.cpp125
-rw-r--r--src/VBox/Main/testcase/tstGuestCtrlParseBuffer.cpp297
-rw-r--r--src/VBox/Main/testcase/tstGuestCtrlPaths.cpp167
-rw-r--r--src/VBox/Main/testcase/tstGuid.cpp110
-rw-r--r--src/VBox/Main/testcase/tstMediumLock.cpp309
-rw-r--r--src/VBox/Main/testcase/tstOVF.cpp431
-rw-r--r--src/VBox/Main/testcase/tstUSBLinux.h79
-rw-r--r--src/VBox/Main/testcase/tstUSBProxyLinux.cpp195
-rw-r--r--src/VBox/Main/testcase/tstUnattendedScript-1.expected384
-rw-r--r--src/VBox/Main/testcase/tstUnattendedScript-1.template384
-rw-r--r--src/VBox/Main/testcase/tstUnattendedScript.cpp731
-rw-r--r--src/VBox/Main/testcase/tstVBoxAPI.cpp417
-rw-r--r--src/VBox/Main/testcase/tstVBoxAPIPerf.cpp257
-rw-r--r--src/VBox/Main/testcase/tstVBoxAPIWin.cpp305
-rw-r--r--src/VBox/Main/testcase/tstVBoxAPIXPCOM.cpp684
-rw-r--r--src/VBox/Main/testcase/tstVBoxCrypto.cpp431
-rw-r--r--src/VBox/Main/testcase/tstVBoxMultipleVM.cpp617
-rw-r--r--src/VBox/Main/webservice/MANIFEST.MF.in28
-rw-r--r--src/VBox/Main/webservice/Makefile.kmk960
-rw-r--r--src/VBox/Main/webservice/Makefile.webtest94
-rw-r--r--src/VBox/Main/webservice/VBoxWebSrv.rc61
-rw-r--r--src/VBox/Main/webservice/platform-xidl.xsl96
-rw-r--r--src/VBox/Main/webservice/samples/java/axis/clienttest.java310
-rw-r--r--src/VBox/Main/webservice/samples/java/jax-ws/Makefile83
-rw-r--r--src/VBox/Main/webservice/samples/java/jax-ws/Makefile.glue72
-rw-r--r--src/VBox/Main/webservice/samples/java/jax-ws/clienttest.java339
-rw-r--r--src/VBox/Main/webservice/samples/java/jax-ws/metrictest.java231
-rwxr-xr-xsrc/VBox/Main/webservice/samples/perl/clienttest.pl232
-rw-r--r--src/VBox/Main/webservice/samples/php/clienttest.php108
-rw-r--r--src/VBox/Main/webservice/samples/python/Makefile39
-rw-r--r--src/VBox/Main/webservice/samples/python/Makefile.glue35
-rwxr-xr-xsrc/VBox/Main/webservice/samples/python/clienttest.py132
-rw-r--r--src/VBox/Main/webservice/soap-header-strip-inline.sed35
-rw-r--r--src/VBox/Main/webservice/soap-header-to-inline-source-file.sed38
-rw-r--r--src/VBox/Main/webservice/split-soapC.cpp236
-rw-r--r--src/VBox/Main/webservice/stdsoap2.sed31
-rw-r--r--src/VBox/Main/webservice/types.txt30
-rw-r--r--src/VBox/Main/webservice/vboxweb.cpp2518
-rw-r--r--src/VBox/Main/webservice/vboxweb.h376
-rw-r--r--src/VBox/Main/webservice/websrv-cpp.xsl1507
-rw-r--r--src/VBox/Main/webservice/websrv-nsmap.xsl148
-rw-r--r--src/VBox/Main/webservice/websrv-php.xsl647
-rw-r--r--src/VBox/Main/webservice/websrv-python.xsl923
-rw-r--r--src/VBox/Main/webservice/websrv-typemap.xsl165
-rw-r--r--src/VBox/Main/webservice/websrv-wsdl-service.xsl204
-rw-r--r--src/VBox/Main/webservice/websrv-wsdl.xsl1308
-rw-r--r--src/VBox/Main/webservice/websrv-wsdl2gsoapH.xsl308
-rw-r--r--src/VBox/Main/webservice/webtest.cpp572
-rw-r--r--src/VBox/Main/xml/Makefile.kup0
-rw-r--r--src/VBox/Main/xml/SchemaDefs.xsl246
-rw-r--r--src/VBox/Main/xml/Settings.cpp9719
-rw-r--r--src/VBox/Main/xml/VirtualBox-settings.xsd1514
-rw-r--r--src/VBox/Main/xml/ovfreader.cpp1078
-rw-r--r--src/VBox/Main/xml/samples/VirtualBox-global.xml62
-rw-r--r--src/VBox/Main/xml/samples/VirtualBox-machine-linux.xml65
-rw-r--r--src/VBox/Main/xml/samples/VirtualBox-machine-windows.xml203
547 files changed, 407442 insertions, 0 deletions
diff --git a/src/VBox/Main/.scm-settings b/src/VBox/Main/.scm-settings
new file mode 100644
index 00000000..29671b01
--- /dev/null
+++ b/src/VBox/Main/.scm-settings
@@ -0,0 +1,92 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for the Main API.
+#
+
+#
+# Copyright (C) 2010-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# cbindings have different licences.
+/cbinding/*.h: --license-lgpl
+/cbinding/VBoxCAPIGlue.c: --license-mit
+/cbinding/VBoxCAPIGlue.h.in: --license-mit --treat-as .h
+/cbinding/makefile.tstCAPIGlue: --license-mit --treat-as Makefile
+
+# The Python bindings is licensed under dual GPL and CDDL.
+/glue/vboxapi.py: --license-ose-dual
+
+# Source dirs.
+--filter-out-files /src-all/VBoxAPI-end.d
+--filter-out-files /src-all/VBoxAPI-end-alternative.d
+--filter-out-files /src-client/README.testing
+--filter-out-files /src-server/custom.ids
+--filter-out-files /src-server/usb.ids
+/src-all/win/VBoxAPIWrap-precomp_vcc.h: --no-fix-header-guards
+/src-all/xpcom/VBoxAPIWrap-precomp_gcc.h: --no-fix-header-guards
+/src-client/win/precomp_vcc.h: --no-fix-header-guards
+/src-client/xpcom/precomp_gcc.h: --no-fix-header-guards
+/src-server/win/precomp_vcc.h: --no-fix-header-guards
+/src-server/xpcom/precomp_gcc.h: --no-fix-header-guards
+/*.h: --guard-relative-to-dir . --guard-prefix MAIN_INCLUDED_SRC_
+/include/*.h: --guard-relative-to-dir include --guard-prefix MAIN_INCLUDED_
+/include/*.h: --guard-relative-to-dir include --guard-prefix MAIN_INCLUDED_
+/cbinding/VBoxCAPI*.h: --guard-relative-to-dir ''
+/cbinding/VBoxCAPI*.h: --guard-relative-to-dir ''
+/cbinding/VBoxCAPIGlue.h.in: --guard-relative-to-dir ''
+
+# IDL and XML
+/idl/VirtualBox.xidl: --treat-as .xml
+/idl/VirtualBox.dtd: --treat-as .xml
+
+/xml/VirtualBox-settings.xsd: --treat-as .xml
+--filter-out-files /xml/samples/*.xml
+
+# We don't have any copyright or license in the .cfg and .sif files.
+# Exploint --external-copyright to check that this remains the case.
+# This does not imply anything wrt to the copyright status of the files.
+/UnattendedTemplates/*.cfg: --external-copyright --treat-as .sh
+/UnattendedTemplates/*.sif: --external-copyright
+/UnattendedTemplates/*.xml: --dont-set-svn-eol --no-convert-eol
+
+# The webservice is fun.
+/webservice/Makefile.webtest: --treat-as Makefile
+--filter-out-files /webservice/types.txt
+--filter-out-files /webservice/MANIFEST.MF.in
+--filter-out-dirs /webservice/jaxlibs/.
+
+# The webservice samples are MIT and need some hacks to handle unusual file types.
+/webservice/samples/*: --license-mit
+/webservice/samples/*/Makefile.glue: --treat-as Makefile
+/webservice/samples/php/clienttest.php: --treat-as .c
+
+
+# Testcases.
+/testcase/makefile.tstVBoxAPIWin: --treat-as Makefile
+/testcase/makefile.tstVBoxAPIXPCOM: --treat-as Makefile
+--filter-out-files /testcase/ovf-dummy.vmdk
+--filter-out-files /testcase/ovf-joomla-0.9/joomla-1.1.4-ovf.ovf
+--filter-out-files /testcase/ovf-winhost-audio-nodisks/WinXP.ovf
+--filter-out-files /testcase/ovf-winxp-vbox-sharedfolders/winxp.ovf
+--filter-out-files /testcase/VBoxVBTest/TestForm.frm
+--filter-out-files /testcase/VBoxVBTest/TestForm.frx
+--filter-out-files /testcase/VBoxVBTest/VBoxVBTest.vbp
+
diff --git a/src/VBox/Main/Certificates/Makefile.kup b/src/VBox/Main/Certificates/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/Certificates/Makefile.kup
diff --git a/src/VBox/Main/Certificates/MicCorKEKCA2011_2011-06-24.crt b/src/VBox/Main/Certificates/MicCorKEKCA2011_2011-06-24.crt
new file mode 100644
index 00000000..2787083e
--- /dev/null
+++ b/src/VBox/Main/Certificates/MicCorKEKCA2011_2011-06-24.crt
Binary files differ
diff --git a/src/VBox/Main/Certificates/MicCorUEFCA2011_2011-06-27.crt b/src/VBox/Main/Certificates/MicCorUEFCA2011_2011-06-27.crt
new file mode 100644
index 00000000..9aa6ac6c
--- /dev/null
+++ b/src/VBox/Main/Certificates/MicCorUEFCA2011_2011-06-27.crt
Binary files differ
diff --git a/src/VBox/Main/Certificates/MicWinProPCA2011_2011-10-19.crt b/src/VBox/Main/Certificates/MicWinProPCA2011_2011-10-19.crt
new file mode 100644
index 00000000..a6d001c2
--- /dev/null
+++ b/src/VBox/Main/Certificates/MicWinProPCA2011_2011-10-19.crt
Binary files differ
diff --git a/src/VBox/Main/Certificates/OrclUefiDefPk2021_2021-09-29.crt b/src/VBox/Main/Certificates/OrclUefiDefPk2021_2021-09-29.crt
new file mode 100644
index 00000000..753e0f2e
--- /dev/null
+++ b/src/VBox/Main/Certificates/OrclUefiDefPk2021_2021-09-29.crt
Binary files differ
diff --git a/src/VBox/Main/Config.kmk b/src/VBox/Main/Config.kmk
new file mode 100644
index 00000000..5e43b717
--- /dev/null
+++ b/src/VBox/Main/Config.kmk
@@ -0,0 +1,55 @@
+# $Id: Config.kmk $
+## @file
+# kBuild Configuration file for the Main API.
+#
+
+#
+# Copyright (C) 2013-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+VBOX_MAIN_CONFIG_KMK_INCLUDED = 1
+
+# Include the top-level configure file.
+ifndef VBOX_ROOT_CONFIG_KMK_INCLUDED
+ include $(PATH_ROOT)/Config.kmk
+endif
+
+
+#
+# Globals.
+#
+VBOX_MAIN_APIWRAPPER_DIR := $(abspath $(call TARGET_PATH,VBoxAPIWrap))
+VBOX_MAIN_APIWRAPPER_INCS := $(VBOX_MAIN_APIWRAPPER_DIR)
+
+
+# The header is required by a testcase, that why these defines are here.
+VBOX_XML_SCHEMADEFS_H = $(PATH_TARGET)/Main/SchemaDefs.h
+VBOX_XML_SCHEMADEFS_CPP = $(PATH_TARGET)/Main/SchemaDefs.cpp
+VBOX_XML_SCHEMADEFS_XSL = $(VBOX_PATH_MAIN_SRC)/xml/SchemaDefs.xsl
+
+# These files are also required by a testcase.
+VBOX_AUTOGEN_EVENT_H = $(PATH_TARGET)/Main/VBoxEvents.h
+VBOX_AUTOGEN_EVENT_CPP = $(PATH_TARGET)/Main/VBoxEvents.cpp
+
+# Enum stringify functions.
+VBOX_AUTOGEN_STRINGIFY_ENUMS_H = $(PATH_TARGET)/Main/StringifyEnums.h
+VBOX_AUTOGEN_STRINGIFY_ENUMS_CPP = $(PATH_TARGET)/Main/StringifyEnums.cpp
+
diff --git a/src/VBox/Main/Docs-Intro.cpp b/src/VBox/Main/Docs-Intro.cpp
new file mode 100644
index 00000000..7cc68ff7
--- /dev/null
+++ b/src/VBox/Main/Docs-Intro.cpp
@@ -0,0 +1,101 @@
+/* $Id: Docs-Intro.cpp $ */
+/** @file
+ * This file contains the introduction to Main for developers.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @page pg_main Main API
+ *
+ * First of all, check out the "Technical background" chapter in the manual, pay
+ * attention to the "VirtualBox executables and components" chapter. It lists
+ * three processes, (1) VBoxSVC, (2) VirtualBox in manager mode and (3)
+ * VirtualBox in VM mode. This will be referred to as (1) server, (2) client
+ * and (3) VM process, respectively.
+ *
+ *
+ * @section sec_main_walk_thru_suspend IConsole::Pause Walkthru
+ *
+ * The instigator can be a client (VirtualBox in manager mode, VBoxManage
+ * controlvm, web services, ++) or the VM process it self (i.e. you select
+ * pause via the menu or the host key short cut).
+ *
+ * We will not cover the case where the guest triggers a suspend.
+ *
+ * Approximate sequence of events:
+ * - Client calls IConsole::Pause.
+ * - The COM/XPCOM routes this to the VM process, invoking Console::Pause() in
+ * ConsoleImpl.cpp. (The IConsole::Pause method in the client process is a
+ * COM/XPCOM stub method which does marshalling+IPC.)
+ * - Console::Pause validates the Console object state, the VM state and the VM
+ * handle.
+ * - Console::Pause calls VMR3Suspend to do the actual suspending.
+ * - VMR3Suspend() in VMM/VMMR3/VM.cpp calls VMMR3EmtRendezvous() to change the
+ * VM state synchronously on all EMTs (threads performing as virtual CPUs).
+ * - VMMR3EmtRendezvous() will first detect that the caller isn't an EMT and
+ * use VMR3ReqCallWait() to forward the call to an EMT.
+ * - When VMMR3EmtRendezvous() is called again on an EMT, it will signal the
+ * other EMTs by raising a force action flag (VM_FF_EMT_RENDEZVOUS) and then
+ * poke them via VMR3NotifyGlobalFFU(). Then wait for them all to arrive.
+ * - The other EMTs will call VMMR3EmtRendezvousFF as soon as they can.
+ * - When all EMTs are there, the calling back of vmR3Suspend() on each CPU in
+ * decending order will start.
+ * - When the CPU with the higest ID calls vmR3Suspend() the VM state is
+ * changed to VMSTATE_SUSPENDING or VMSTATE_SUSPENDING_EXT_LS.
+ * - When the CPU with ID 0 calls vmR3Suspend() the virtual device emulations
+ * and drivers get notified via PDMR3Suspend().
+ * - PDMR3Suspend() in VMM/VMMR3/PDM.cpp will iterate thru all device
+ * emulations and notify them that the VM is suspending by calling their
+ * PDMDEVREG::pfnSuspend / PDMUSBREG::pfnSuspend entry point (can be NULL).
+ * For each device it will iterate the chains of drivers and call their
+ * PDMDRVREG::pfnSuspend entry point as well.
+ * - Should a worker thread in a PDM device or PDM driver be busy and need some
+ * extra time to finish up / notice the pending suspend, the device or driver
+ * will ask for more time via PDMDevHlpSetAsyncNotification(),
+ * PDMDrvHlpSetAsyncNotification() or PDMUsbHlpSetAsyncNotification().
+ * PDMR3Suspend will then poll these devices and drivers frequently until all
+ * are done.
+ * - PDMR3Suspend() will return to vmR3Suspend() once all PDM devices and PDM
+ * drivers has responded to the pfnSuspend callback.
+ * - The virtual CPU with ID 0 returns from vmR3Suspend() to the rendezvous
+ * code and the EMTs are released.
+ * - The inner VMMR3EmtRendezvous() call returns and this in turn triggers the
+ * VMR3ReqCallWait() call to return (with the status code of the inner call).
+ * - The outer VMMR3EmtRendezvous() returns to VMR3Suspend().
+ * - VMR3Suspend() returns to Console::Pause().
+ * - Console::Pause() checks the result and flags provides error details on
+ * failure.
+ * - Console::Pause() returns to the COM/XPCOM marshalling/IPC stuff.
+ * - Switch back to client process.
+ * - The IConsole::Pause() call returns. The end.
+ *
+ * Summary of above: Client process calls into the VM process, VM process does a
+ * bunch of inter thread calls with all the EMT, EMT0 suspends the PDM devices
+ * and drivers.
+ *
+ * The EMTs will return to the outer execution loop, vmR3EmulationThreadWithId()
+ * in VMM/VMMR3/VMEmt.cpp, where they will mostly do sleep. They will not
+ * execute any guest code until VMR3Resume() is called.
+ *
+ */
+
diff --git a/src/VBox/Main/Doxyfile.Main b/src/VBox/Main/Doxyfile.Main
new file mode 100644
index 00000000..6547bd6d
--- /dev/null
+++ b/src/VBox/Main/Doxyfile.Main
@@ -0,0 +1,2469 @@
+# Doxyfile 1.8.11
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = "VirtualBox Main API"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF =
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = $(DOCDIR)
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name interface" \
+ "is" \
+ "provides" \
+ "represents" \
+ "a" \
+ "an" \
+ "the"
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = NO
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = YES
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS = YES
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES = NO
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = NO
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE = $(PATH_TARGET)/Main.err
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = $(PATH_TARGET)/VirtualBox.idl
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl,
+# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js.
+
+FILE_PATTERNS =
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
+# cost of reduced performance. This can be particularly helpful with template
+# rich C++ code for which doxygen's built-in parser lacks the necessary type
+# information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse-libclang=ON option for CMake.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX = NO
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = NO
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = NO
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sf.net) file that captures the
+# structure of the code including all documentation. Note that this feature is
+# still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = YES
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH = include
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED = DOXYGEN_RUNNING \
+ RT_C_DECLS_END \
+ RT_C_DECLS_BEGIN
+
+# Compile assertion hacks.
+PREDEFINED += \
+ "AssertCompileNS(expr)=static_assert(expr)" \
+ "AssertCompile(expr)=static_assert(expr)" \
+ "AssertCompileSize(type, size)=static_assert(true)" \
+ "AssertCompileSizeAlignment(type, align)=static_assert(true)" \
+ "AssertCompileMemberAlignment(type, member, align)=static_assert(true)" \
+ "AssertCompileMemberOffset(type, member, off)=static_assert(true)" \
+ "AssertCompile2MemberOffsets(type, member1, member2)=static_assert(true)" \
+ "AssertCompileAdjacentMembers(type, member1, member2)=static_assert(true)" \
+ "AssertCompileMembersAtSameOffset(type1, member1, type2, member2)=static_assert(true)" \
+ "AssertCompileMemberSize(type, member, size)=static_assert(true)" \
+ "AssertCompileMemberSizeAlignment(type, member, align)=static_assert(true)" \
+ "AssertCompileMembersSameSize(type1, member1, type2, member2)=static_assert(true)" \
+ "AssertCompileMembersSameSizeAndOffset(type1, member1, type2, member2)=static_assert(true)"
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED = DECLEXPORT \
+ DECLIMPORT \
+ DECLASM \
+ DECLASMTYPE \
+ DECLCALLBACK \
+ VMMR3DECL \
+ VMMR0DECL \
+ VMMRCDECL \
+ VMMDECL \
+ DBGDECL \
+ DISDECL \
+ SUPR3DECL \
+ SUPR0DECL \
+ SUPDECL \
+ RTR3DECL \
+ RTR0DECL \
+ RTRCDECL \
+ RTDECL
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: YES.
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+#DOT_FONTNAME = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = YES
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS = YES
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = YES
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
+# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
+# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT = YES
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP = YES
diff --git a/src/VBox/Main/Makefile.kmk b/src/VBox/Main/Makefile.kmk
new file mode 100644
index 00000000..f8e9998f
--- /dev/null
+++ b/src/VBox/Main/Makefile.kmk
@@ -0,0 +1,1925 @@
+# $Id: Makefile.kmk $
+## @file
+# Makefile for the VBox Main module.
+#
+
+#
+# Copyright (C) 2004-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Make sure our Config.kmk is included.
+#
+ifndef VBOX_MAIN_CONFIG_KMK_INCLUDED
+ include $(PATH_SUB_CURRENT)/Config.kmk
+endif
+
+#
+# Include sub-makefile(s).
+#
+ifndef VBOX_ONLY_EXTPACKS
+ ifdef VBOX_WITH_WEBSERVICES
+ include $(PATH_SUB_CURRENT)/webservice/Makefile.kmk
+ endif
+ include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk
+ include $(PATH_SUB_CURRENT)/cbinding/Makefile.kmk
+ include $(PATH_SUB_CURRENT)/UnattendedTemplates/Makefile.kmk
+ ifndef VBOX_ONLY_SDK
+ include $(PATH_SUB_CURRENT)/src-helper-apps/OpenGLTest/Makefile.kmk
+ endif
+ if defined(VBOX_WITH_OPEN_WATCOM) && defined(VBOX_WITH_UNATTENDED)
+ include $(PATH_SUB_CURRENT)/src-helper-apps/os2/Makefile.kmk
+ endif
+endif
+
+
+#
+# Targets and globals (bit of a mess...)
+#
+VBOX_PATH_MAIN_SRC := $(PATH_SUB_CURRENT)
+
+ifdef VBOX_ONLY_SDK
+ # HACK ALERT! VBoxCOM is skipped, but its output dir is referenced a lot. So, define it.
+ VBoxCOM_0_OUTDIR := $(call TARGET_PATH,VBoxCOM)
+ BLDDIRS += $(VBoxCOM_0_OUTDIR) $(PATH_STAGE_BIN)/components
+ ifndef TOOL_$(VBOX_VCC_TOOL)
+ include $(KBUILD_PATH)/tools/$(VBOX_VCC_TOOL).kmk # For the idl /cpp statement.
+ endif
+ ifndef SDK_$(VBOX_WINPSDK)
+ include $(KBUILD_PATH)/sdks/$(VBOX_WINPSDK).kmk
+ endif
+endif # !VBOX_ONLY_SDK
+
+
+## VBOX_MAIN_DEFS is used by VBoxC, VBoxClient-x86, VBoxSVC and VBoxSVCM, making
+# sure they all have the same set of features enabled.
+VBOX_MAIN_DEFS :=
+ifneq ($(KBUILD_TARGET),win)
+ ifndef VBOX_WITH_XPCOM
+$(error "VBox: VBOX_WITH_XPCOM isn't defined")
+ endif
+VBOX_MAIN_DEFS += VBOX_WITH_GENERIC_SESSION_WATCHER
+endif
+ifdef VBOX_WITH_AUDIO_ALSA
+VBOX_MAIN_DEFS += VBOX_WITH_AUDIO_ALSA
+endif
+ifdef VBOX_WITH_AUDIO_DEBUG
+VBOX_MAIN_DEFS += VBOX_WITH_AUDIO_DEBUG
+endif
+ifdef VBOX_WITH_AUDIO_OSS
+VBOX_MAIN_DEFS += VBOX_WITH_AUDIO_OSS
+endif
+ifdef VBOX_WITH_AUDIO_PULSE
+VBOX_MAIN_DEFS += VBOX_WITH_AUDIO_PULSE
+endif
+ifdef VBOX_WITH_AUDIO_RECORDING
+VBOX_MAIN_DEFS += VBOX_WITH_AUDIO_RECORDING
+endif
+ifdef VBOX_WITH_AUDIO_VALIDATIONKIT
+VBOX_MAIN_DEFS += VBOX_WITH_AUDIO_VALIDATIONKIT
+endif
+ifdef VBOX_WITH_AUDIO_VRDE
+VBOX_MAIN_DEFS += VBOX_WITH_AUDIO_VRDE
+endif
+ifdef VBOX_WITH_COPYTOGUEST
+VBOX_MAIN_DEFS += VBOX_WITH_COPYTOGUEST
+endif
+ifdef VBOX_WITH_E1000
+VBOX_MAIN_DEFS += VBOX_WITH_E1000
+endif
+ifdef VBOX_WITH_EXTPACK
+VBOX_MAIN_DEFS += VBOX_WITH_EXTPACK
+endif
+ifdef VBOX_WITH_GUEST_CONTROL
+VBOX_MAIN_DEFS += VBOX_WITH_GUEST_CONTROL
+endif
+ifdef VBOX_WITH_GUEST_PROPS
+VBOX_MAIN_DEFS += VBOX_WITH_GUEST_PROPS
+ ifdef VBOX_WITH_GUEST_PROPS_RDONLY_GUEST
+VBOX_MAIN_DEFS += VBOX_WITH_GUEST_PROPS_RDONLY_GUEST
+ endif
+endif
+ifdef VBOX_WITH_HGCM
+VBOX_MAIN_DEFS += VBOX_WITH_HGCM
+endif
+ifdef VBOX_WITH_HOSTNETIF_API
+VBOX_MAIN_DEFS += VBOX_WITH_HOSTNETIF_API
+endif
+ifdef VBOX_WITH_DHCPD
+VBOX_MAIN_DEFS += VBOX_WITH_DHCPD
+endif
+ifdef VBOX_WITH_LIVE_MIGRATION
+VBOX_MAIN_DEFS += VBOX_WITH_LIVE_MIGRATION
+endif
+ifdef VBOX_WITH_MIDL_PROXY_STUB
+VBOX_MAIN_DEFS += VBOX_WITH_MIDL_PROXY_STUB
+endif
+ifdef VBOX_WITH_NETFLT
+VBOX_MAIN_DEFS += VBOX_WITH_NETFLT
+endif
+if defined(VBOX_WITH_VMNET) && "$(KBUILD_TARGET)" == "darwin"
+VBOX_MAIN_DEFS += VBOX_WITH_VMNET
+endif
+ifdef VBOX_WITH_CLOUD_NET
+VBOX_MAIN_DEFS += VBOX_WITH_CLOUD_NET
+endif
+ifdef VBOX_WITH_PCI_PASSTHROUGH
+VBOX_MAIN_DEFS += VBOX_WITH_PCI_PASSTHROUGH
+endif
+ifdef VBOX_WITH_PDM_ASYNC_COMPLETION
+VBOX_MAIN_DEFS += VBOX_WITH_PDM_ASYNC_COMPLETION
+endif
+ifdef VBOX_WITH_RECORDING
+VBOX_MAIN_DEFS += VBOX_WITH_RECORDING
+endif
+ifdef VBOX_WITH_SDS
+VBOX_MAIN_DEFS += VBOX_WITH_SDS
+endif
+ifdef VBOX_WITH_SHARED_CLIPBOARD
+VBOX_MAIN_DEFS += VBOX_WITH_SHARED_CLIPBOARD
+ ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ VBOX_MAIN_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ endif
+endif
+if defined(VBOX_WITH_LIBCURL) && defined(VBOX_WITH_UPDATE_AGENT)
+VBOX_MAIN_DEFS += VBOX_WITH_UPDATE_AGENT
+endif
+ifdef VBOX_WITH_USB_CARDREADER
+VBOX_MAIN_DEFS += VBOX_WITH_USB_CARDREADER
+endif
+ifdef VBOX_WITH_USB_CARDREADER_TEST
+VBOX_MAIN_DEFS += VBOX_WITH_USB_CARDREADER_TEST
+endif
+ifdef VBOX_WITH_VIRTIO
+VBOX_MAIN_DEFS += VBOX_WITH_VIRTIO
+endif
+ifdef VBOX_WITH_VIRTIO_1_0
+VBOX_MAIN_DEFS += VBOX_WITH_VIRTIO_1_0
+endif
+ifdef VBOX_WITH_VIRTIO_NET_1_0
+VBOX_MAIN_DEFS += VBOX_WITH_VIRTIO_NET_1_0
+endif
+ifdef VBOX_WITH_VMSVGA
+VBOX_MAIN_DEFS += VBOX_WITH_VMSVGA
+endif
+ifdef VBOX_WITH_VMSVGA3D
+VBOX_MAIN_DEFS += VBOX_WITH_VMSVGA3D
+endif
+ifdef VBOX_WITH_VUSB
+VBOX_MAIN_DEFS += VBOX_WITH_VUSB
+endif
+ifdef VBOX_WITH_EHCI
+ ifdef VBOX_WITH_USB
+ VBOX_MAIN_DEFS += VBOX_WITH_EHCI
+ endif
+endif
+ifdef VBOX_WITH_EFI_IN_DD2
+ VBOX_MAIN_DEFS += VBOX_WITH_EFI_IN_DD2
+endif
+ifdef VBOX_WITH_MAIN_NLS
+ VBOX_MAIN_DEFS += VBOX_WITH_MAIN_NLS
+endif
+# Unconditionally enable the new semaphore key generation code
+VBOX_MAIN_DEFS += VBOX_WITH_NEW_SYS_V_KEYGEN
+
+
+VBOX_IDL_FILE.MSCOM = $(VBOX_PATH_SDK)/bindings/mscom/idl/VirtualBox.idl
+VBOX_IDL_FILE.XPCOM = $(VBOX_PATH_SDK)/bindings/xpcom/idl/VirtualBox_XPCOM.idl
+
+VBOX_IDL_TYPELIB.XPCOM = $(PATH_STAGE_BIN)/components/VirtualBox_XPCOM.xpt
+VBOX_IDL_HEADER.XPCOM = $(VBOX_PATH_SDK)/bindings/xpcom/include/VirtualBox_XPCOM.h
+
+# The MS COM specific stuff.
+if defined(VBOX_ONLY_SDK) || "$(KBUILD_TARGET)" == "win"
+ OTHERS += \
+ $(VBOX_IDL_FILE.MSCOM)
+ OTHER_CLEAN += \
+ $(VBOX_IDL_FILE.MSCOM)
+
+ INSTALLS += VBox-mscom-bindings-include VBox-mscom-bindings-lib
+ VBox-mscom-bindings-include_INST = $(INST_SDK)bindings/mscom/include/
+ VBox-mscom-bindings-include_MODE = a+r,u+w
+ VBox-mscom-bindings-include_SOURCES = $(VBoxCOM_0_OUTDIR)/VirtualBox.h
+ VBox-mscom-bindings-include_CLEAN = $(VBoxCOM_0_OUTDIR)/VirtualBox.h
+ VBox-mscom-bindings-lib_INST = $(INST_SDK)bindings/mscom/lib/
+ VBox-mscom-bindings-lib_MODE = a+r,u+w
+ VBox-mscom-bindings-lib_SOURCES = $(VBoxCOM_0_OUTDIR)/VirtualBox_i.c \
+ $(VBoxCOM_0_OUTDIR)/VirtualBox.tlb
+ VBox-mscom-bindings-lib_CLEAN = $(VBoxCOM_0_OUTDIR)/VirtualBox_i.c \
+ $(VBoxCOM_0_OUTDIR)/VirtualBox.tlb
+
+ BLDDIRS += $(VBOX_PATH_SDK)/bindings/mscom/idl
+endif
+
+# The XPCOM specific stuff.
+# if defined(VBOX_ONLY_SDK) || "$(KBUILD_TARGET)" != "win" -- does not build on Windows
+if "$(KBUILD_TARGET)" != "win"
+ OTHERS += $(VBOX_IDL_FILE.XPCOM) $(VBOX_IDL_TYPELIB.XPCOM) $(VBOX_IDL_HEADER.XPCOM)
+ OTHER_CLEAN += \
+ $(VBOX_IDL_FILE.XPCOM) \
+ $(VBOX_IDL_HEADER.XPCOM) \
+ $(VBOX_IDL_TYPELIB.XPCOM)
+
+ INSTALLS += VBox-xpcom-bindings-lib
+ VBox-xpcom-bindings-lib_INST = $(INST_SDK)bindings/xpcom/lib/
+ VBox-xpcom-bindings-lib_MODE = a+r,u+w
+ VBox-xpcom-bindings-lib_SOURCES = $(VBoxCOM_0_OUTDIR)/VirtualBox_XPCOM_i.c=>VirtualBox_i.c
+ VBox-xpcom-bindings-lib_CLEAN = $(VBoxCOM_0_OUTDIR)/VirtualBox_XPCOM_i.c
+
+ VBOX_MAIN_PREREQS += $(VBOX_IDL_TYPELIB.XPCOM) $(VBOX_IDL_HEADER.XPCOM)
+ BLDDIRS += \
+ $(VBOX_PATH_SDK)/bindings/xpcom/idl \
+ $(VBOX_PATH_SDK)/bindings/xpcom/include
+ # disable VBoxSDS for XPCOM
+ override VBOX_WITH_SDS =
+endif # xpcom
+
+
+#
+# Strip documentation from source XIDL so that we don't get a full
+# recompile every time a comma in the documentation is changed.
+#
+$(call KB_FN_AUTO_CMD_DEPS,$(VBOX_XIDL_FILE).ts)
+$(VBOX_XIDL_FILE).ts +| $(VBOX_XIDL_FILE): \
+ $(VBOX_XIDL_FILE_SRC) \
+ $(VBOX_PATH_MAIN_SRC)/idl/docstrip.xsl \
+ | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(QUIET)$(VBOX_XSLTPROC) -o $(VBOX_XIDL_FILE).ts-tmp $(VBOX_PATH_MAIN_SRC)/idl/docstrip.xsl $(VBOX_XIDL_FILE_SRC)
+ $(QUIET)$(SED) -e 's/ *$(DOLLAR)//g' -e '/^$(DOLLAR)/d' --output $(VBOX_XIDL_FILE).ts $(VBOX_XIDL_FILE).ts-tmp
+ $(QUIET)$(RM) -- $(VBOX_XIDL_FILE).ts-tmp
+ $(QUIET)$(CP) --changed -fv -- $(VBOX_XIDL_FILE).ts $(VBOX_XIDL_FILE)
+
+OTHER_CLEAN += $(VBOX_XIDL_FILE) $(VBOX_XIDL_FILE).ts
+
+
+#
+# The Main API documentation
+#
+VBOX_MAIN_DOC_DIR = $(VBOX_PATH_SDK)/docs
+BLDDIRS += $(VBOX_MAIN_DOC_DIR)
+
+$(PATH_TARGET)/docs.Main: \
+ $(VBOX_PATH_MAIN_SRC)/Doxyfile.Main \
+ $(VBOX_PATH_MAIN_SRC)/idl/doxygen.xsl \
+ $(VBOX_XIDL_FILE_SRC) \
+ | $(PATH_TARGET)/ \
+ $(VBOX_MAIN_DOC_DIR)/
+ $(call MSG_TOOL,xsltproc,doxygen input,$<,$@)
+ $(QUIET)$(RM) -f $(wildcard $(VBOX_MAIN_DOC_DIR)/html/*) $(PATH_TARGET)/docs.Main
+ $(QUIET)$(VBOX_XSLTPROC) --path ".:$(PATH_ROOT)/doc/manual" -o $(PATH_TARGET)/VirtualBox.idl $(VBOX_PATH_MAIN_SRC)/idl/doxygen.xsl $(VBOX_XIDL_FILE_SRC)
+ $(QUIET)$(REDIRECT) -E 'DOCDIR=$(VBOX_MAIN_DOC_DIR)' -E 'PATH_TARGET=$(PATH_TARGET)' \
+ -- doxygen $(VBOX_PATH_MAIN_SRC)/Doxyfile.Main
+ $(QUIET)$(TEST) -s "$(PATH_TARGET)/Main.err" -- $(ECHO_EXT) ">>>>>>>>>>>>>>>>>>>> Main.err: >>>>>>>>>>>>>>>>>>>>>>>>"
+ $(QUIET)$(TEST) -s "$(PATH_TARGET)/Main.err" -- $(CAT) "$(PATH_TARGET)/Main.err"
+ $(QUIET)$(TEST) -s "$(PATH_TARGET)/Main.err" -- $(ECHO_EXT) "<<<<<<<<<<<<<<<<<<<< Main.err <<<<<<<<<<<<<<<<<<<<<<<<<"
+ $(QUIET)$(TEST) -s "$(PATH_TARGET)/Main.err" -- $(ECHO_EXT) "===> **************************************************"
+ $(QUIET)$(TEST) -s "$(PATH_TARGET)/Main.err" -- $(ECHO_EXT) "===> Please fix above doxygen errors/warnings listed in"
+ $(QUIET)$(TEST) -s "$(PATH_TARGET)/Main.err" -- $(ECHO_EXT) "===> $(PATH_TARGET)/Main.err"
+ $(QUIET)$(TEST) -s "$(PATH_TARGET)/Main.err" -- $(ECHO_EXT) "===> **************************************************"
+ $(APPEND) $(PATH_TARGET)/docs.Main
+# aliases
+docs.main docs.Main: $(PATH_TARGET)/docs.Main
+if !defined(VBOX_ONLY_DOCS) && defined(VBOX_WITH_ALL_DOXYGEN_TARGETS)
+docs: $(PATH_TARGET)/docs.Main
+else
+docs:
+endif
+
+
+ifndef VBOX_ONLY_SDK
+ #
+ # Generate library with API class wrappers from the XIDL file.
+ #
+ LIBRARIES += VBoxAPIWrap
+ VBoxAPIWrap_TEMPLATE = VBoxMainLib
+ ifeq ($(KBUILD_TARGET),win)
+ VBoxAPIWrap_USES = vccprecomp
+ VBoxAPIWrap_PCH_HDR := $(PATH_SUB_CURRENT)/src-all/win/VBoxAPIWrap-precomp_vcc.h
+ else if $(KBUILD_KMK_REVISION) >= 3256 && "$(KBUILD_TARGET)" != "darwin" && !defined(VBOX_WITHOUT_PRECOMPILED_HEADERS)
+ if $(VBOX_GCC_VERSION_CXX) >= 50000 # GCC 4.x runs into trouble compiling the header.
+VBoxAPIWrap_USES = gccprecomp
+VBoxAPIWrap_PCH_HDR := $(PATH_SUB_CURRENT)/src-all/xpcom/VBoxAPIWrap-precomp_gcc.h
+ endif
+ endif
+ VBoxAPIWrap_SOURCES = \
+ $(VBoxAPIWrap_0_OUTDIR)/VBoxAPI.d \
+ $(if-expr defined(VBOX_ONLY_EXTPACKS) \
+ , $(addprefix $(VBoxAPIWrap_0_OUTDIR)/,$(filter \
+ Cloud%\
+ DataStreamWrap.cpp\
+ %FormValueWrap.cpp\
+ %FormWrap.cpp\
+ StringArrayWrap.cpp\
+ , $(notdir $(VBOX_MAIN_APIWRAPPER_GEN_SRCS)))) \
+ , $(VBOX_MAIN_APIWRAPPER_GEN_SRCS))
+ VBoxAPIWrap_INTERMEDIATES = $(VBOX_MAIN_APIWRAPPER_GEN_HDRS)
+ VBoxAPIWrap_DEFS.win.x86 += _WIN32_WINNT=0x0500
+ VBoxAPIWrap_DEFS.win.amd64 += _WIN32_WINNT=0x0510
+ VBoxAPIWrap_INCS += \
+ include
+ if ("$(KBUILD_TARGET)" == "solaris" && $(VBOX_GCC_VERSION_CXX) >= 70000)
+ # Avoid too long symbol names with Solaris 11.4 "dtrace: failed to link script .../VBoxAPI.d: failed to allocate space for probe"
+ VBoxAPIWrap_CXXFLAGS.solaris += -freorder-blocks-algorithm=simple -fno-reorder-blocks-and-partition -fno-reorder-functions
+ endif
+ VBoxAPIWrap_CLEAN = \
+ $(wildcard $(VBoxAPIWrap_0_OUTDIR)/*.cpp) \
+ $(wildcard $(VBoxAPIWrap_0_OUTDIR)/*.h) \
+ $(VBoxAPIWrap_0_OUTDIR)/VBoxAPI.d.ts \
+ $(VBoxAPIWrap_0_OUTDIR)/VBoxAPI.d \
+ $(VBoxAPIWrap_0_OUTDIR)/apiwrappers-headers \
+ $(VBoxAPIWrap_0_OUTDIR)/apiwrappers-sources-even \
+ $(VBoxAPIWrap_0_OUTDIR)/apiwrappers-sources-odd
+
+ # Two VBox variables used here and/or in the rules generating wrappers.
+ VBoxAPIWrap_VBOX_XSLT = \
+ $(VBOX_PATH_MAIN_SRC)/idl/apiwrap-server.xsl
+
+
+ # This include file (generated at the bottom of this file), defines
+ # VBOX_MAIN_APIWRAPPER_GEN_SRCS and VBOX_MAIN_APIWRAPPER_GEN_HDRS.
+ VBoxAPIWrap_VBOX_KMK_FILE = $(PATH_OUT)/vboxapiwrap.kmk
+ include $(VBoxAPIWrap_VBOX_KMK_FILE)
+ OTHER_CLEAN += $(VBoxAPIWrap_VBOX_KMK_FILE) $(VBoxAPIWrap_VBOX_KMK_FILE).ts
+
+ ifdef VBOX_WITH_32_ON_64_MAIN_API
+ LIBRARIES += VBoxAPIWrap-x86
+ VBoxAPIWrap-x86_TEMPLATE = VBoxMainLib-x86
+ VBoxAPIWrap-x86_EXTENDS = VBoxAPIWrap
+ VBoxAPIWrap-x86_DTRACE_HDR_FLAGS = -32 # ugly, should be fixed in the tool in Config.kmk
+ ifeq ($(KBUILD_TARGET),win) ## @todo fix inheritance (fixed in r3253)
+ VBoxAPIWrap-x86_USES = vccprecomp
+ VBoxAPIWrap-x86_PCH_HDR := $(PATH_SUB_CURRENT)/src-all/win/VBoxAPIWrap-precomp_vcc.h
+ endif
+ endif
+
+endif # !VBOX_ONLY_SDK
+
+#
+# Some SDK bit.
+#
+INSTALLS += VBox-main-xidl
+VBox-main-xidl_INST = $(INST_SDK)bindings/
+VBox-main-xidl_MODE = a+r,u+w
+VBox-main-xidl_SOURCES = $(VBOX_XIDL_FILE_SRC)
+
+if defined(VBOX_WITH_PYTHON) && !defined(VBOX_ONLY_EXTPACKS)
+ INSTALLS += VBox-python-glue
+ VBox-python-glue_INST = $(INST_SDK)installer/vboxapi/
+ VBox-python-glue_MODE = a+r,u+w
+ VBox-python-glue_SOURCES = \
+ glue/vboxapi.py=>__init__.py \
+ $(VBox-python-glue_0_OUTDIR)/VirtualBox_constants.py
+ VBox-python-glue_CLEAN = \
+ $(VBox-python-glue_0_OUTDIR)/VirtualBox_constants.py \
+ $(VBox-python-glue_0_OUTDIR)/VirtualBox_constants_err_h_1.txt
+ $(call KB_FN_DO_PASS0_ON_TARGET,VBox-python-glue)
+ $(call KB_FN_AUTO_CMD_DEPS,$(VBox-python-glue_0_OUTDIR)/VirtualBox_constants.py)
+ $(VBox-python-glue_0_OUTDIR)/VirtualBox_constants.py \
+ + $(VBox-python-glue_0_OUTDIR)/VirtualBox_constants_err_h_1.txt: \
+ $(VBOX_PATH_MAIN_SRC)/glue/constants-python.xsl \
+ $(VBOX_PATH_MAIN_SRC)/glue/vbox-err-consts.sed \
+ $(PATH_ROOT)/include/iprt/err.h \
+ $(PATH_ROOT)/include/VBox/err.h \
+ $(VBOX_XIDL_FILE) \
+ | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(call MSG_TOOL,xsltproc,Python constants,$<,$@)
+ $(SED) -f $(VBOX_PATH_MAIN_SRC)/glue/vbox-err-consts.sed \
+ --output $(VBox-python-glue_0_OUTDIR)/VirtualBox_constants_err_h_1.txt \
+ $(PATH_ROOT)/include/iprt/err.h \
+ $(PATH_ROOT)/include/VBox/err.h
+ $(QUIET)$(VBOX_XSLTPROC) -o $@ \
+ --stringparam "g_sErrHFile" "$(VBox-python-glue_0_OUTDIR)/VirtualBox_constants_err_h_1.txt" \
+ $(VBOX_PATH_MAIN_SRC)/glue/constants-python.xsl $(VBOX_XIDL_FILE)
+endif # VBOX_WITH_PYTHON && !VBOX_ONLY_EXTPACKS
+
+if !defined(VBOX_ONLY_SDK) && !defined(VBOX_ONLY_EXTPACKS) # Note this goes on for *very* long
+
+#
+# Generate SchemaDefs.h and SchemaDefs.cpp from XML Schema
+# These two files are used by both VBoxC and VBoxSVC.
+#
+BLDDIRS += $(PATH_TARGET)/Main
+
+testschemadefs: $(VBOX_XML_SCHEMADEFS_H) $(VBOX_XML_SCHEMADEFS_CPP)
+
+
+#
+# Trust anchors and certificates -> .cpp
+#
+VBOX_SVC_CERTS_FILE = $(VBoxSVC_0_OUTDIR)/TrustAnchorsAndCerts.cpp
+VBOX_SVC_CERTS := \
+ UefiMicrosoftKek=MicCorKEKCA2011_2011-06-24.crt \
+ UefiMicrosoftCa=MicCorUEFCA2011_2011-06-27.crt \
+ UefiMicrosoftProPca=MicWinProPCA2011_2011-10-19.crt \
+ UefiOracleDefPk=OrclUefiDefPk2021_2021-09-29.crt
+
+VBOX_SVC_CERT_NAMES := $(foreach cert,$(VBOX_SVC_CERTS),$(firstword $(subst =,$(SPACE) ,$(cert))))
+VBOX_SVC_PATH_CERTIFICATES := $(PATH_SUB_CURRENT)/Certificates
+
+$$(VBOX_SVC_CERTS_FILE): $(MAKEFILE_CURRENT) \
+ $(foreach cert,$(VBOX_SVC_CERTS),$(VBOX_SVC_PATH_CERTIFICATES)/$(lastword $(subst =,$(SPACE) ,$(cert)))) \
+ $(VBOX_BIN2C) \
+ | $$(dir $$@)
+ $(QUIET)$(RM) -f -- $@
+ $(QUIET)$(APPEND) -n "$@" \
+ '' \
+ '#include "TrustAnchorsAndCerts.h"' \
+ ''
+ $(foreach cert,$(VBOX_SVC_CERTS), $(NLTAB)$(VBOX_BIN2C) -ascii --append \
+ "$(firstword $(subst =,$(SP) ,$(cert)))" \
+ "$(VBOX_SVC_PATH_CERTIFICATES)/$(lastword $(subst =,$(SP) ,$(cert)))" \
+ "$@")
+
+OTHER_CLEAN += $(VBOX_SVC_CERTS_FILE)
+
+tst-main-certificates: $(VBOX_SVC_CERTS_FILE)
+
+
+#
+# VBoxSDS executable
+#
+if defined(VBOX_WITH_SDS) && "$(KBUILD_TARGET)" == "win"
+PROGRAMS.win += VBoxSDS
+VBoxSDS_TEMPLATE = VBOXMAINEXE
+VBoxSDS_DEFS += VBOX_COM_OUTOFPROC_MODULE _WIN32_WINNT=0x0600
+ ifdef VBOX_WITH_VBOXSDL
+VBoxSDS_DEFS += VBOX_WITH_VBOXSDL
+ endif
+ ifdef VBOX_WITH_HEADLESS
+VBoxSDS_DEFS += VBOX_WITH_HEADLESS
+ endif
+ ifdef VBOX_WITH_QTGUI
+VBoxSDS_DEFS += VBOX_WITH_QTGUI
+ endif
+ ifdef VBOX_WITH_VBOXSVC_SESSION_0
+VBoxSDS_DEFS += VBOX_WITH_VBOXSVC_SESSION_0
+ endif
+VBoxSDS_INCS = \
+ include \
+ $(VBoxSDS_0_OUTDIR) \
+ $(VBoxCOM_0_OUTDIR)
+VBoxSDS_INTERMEDIATES = \
+ $(VBOX_MAIN_PREREQS)
+VBoxSDS_SOURCES = \
+ src-global/win/VBoxSDS.cpp \
+ src-global/win/VirtualBoxSDSImpl.cpp \
+ src-global/win/VBoxSDS.rc \
+ src-all/MachineLaunchVMCommonWorker.cpp
+$(call KB_FN_DO_PASS0_ON_TARGET,VBoxSDS) # Sets VBoxSDS_0_OUTDIR
+
+src-global/win/VBoxSDS.rc_INCS = $(VBoxSDS_0_OUTDIR)
+src-global/win/VBoxSDS.rc_DEPS = $(VBoxSDS_0_OUTDIR)/VBoxSDS-icon.rc #$(VBoxSDS_0_OUTDIR)/VBoxSDS.rgs
+VBoxSDS_CLEAN = $(VBoxSDS_0_OUTDIR)/VBoxSDS-icon.rc #$(VBoxSDS_0_OUTDIR)/VBoxSDS.rgs
+
+$(call KB_FN_AUTO_CMD_DEPS,$(VBoxSDS_0_OUTDIR)/VBoxSDS-icon.rc)
+$(VBoxSDS_0_OUTDIR)/VBoxSDS-icon.rc: $(VBOX_WINDOWS_ICON_FILE) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(APPEND) -t $@ '1 ICON DISCARDABLE "$(subst /,\\,$(VBOX_WINDOWS_ICON_FILE))"'
+
+# $(call KB_FN_AUTO_CMD_DEPS,$(VBoxSDS_0_OUTDIR)/VBoxSDS.rgs)
+# $(VBoxSDS_0_OUTDIR)/VBoxSDS.rgs: $(VBOX_PATH_MAIN_SRC)/src-all/win/VirtualBox_rgs.xsl $(VBOX_XIDL_FILE) | $$(dir $$@)
+# $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+# $(VBOX_XSLTPROC) \
+# --stringparam Application "VirtualBox System" \
+# --stringparam Module VBoxSDS \
+# -o $@ \
+# $(VBOX_PATH_MAIN_SRC)/src-all/win/VirtualBox_rgs.xsl \
+# $(VBOX_XIDL_FILE)
+#
+endif #if defined(VBOX_WITH_SDS) && "$(KBUILD_TARGET)" == "win"
+
+#
+# VBoxSVC executable
+#
+PROGRAMS += VBoxSVC
+VBoxSVC_TEMPLATE = VBOXMAINEXE
+if defined(VBOX_WITH_PRECOMPILED_HEADERS) && "$(KBUILD_TARGET)" == "win"
+ VBoxSVC_USES += vccprecomp
+ VBoxSVC_PCH_HDR := $(PATH_SUB_CURRENT)/src-server/win/precomp_vcc.h
+else if $(KBUILD_KMK_REVISION) >= 3256 && "$(KBUILD_TARGET)" != "darwin" && !defined(VBOX_WITHOUT_PRECOMPILED_HEADERS)
+ if $(VBOX_GCC_VERSION_CXX) >= 50000 # GCC 4.x runs into trouble compiling the header.
+VBoxSVC_USES += gccprecomp
+VBoxSVC_PCH_HDR := $(PATH_SUB_CURRENT)/src-server/xpcom/precomp_gcc.h
+ endif
+endif
+VBoxSVC_DEFS = \
+ IN_VBOXSVC \
+ VBOX_MAIN_SETTINGS_ADDONS \
+ IN_VMM_STATIC \
+ $(VBOX_MAIN_DEFS) \
+ $(if $(VBOX_WITH_VBOXSDL),VBOX_WITH_VBOXSDL,) \
+ $(if $(VBOX_WITH_HEADLESS),VBOX_WITH_HEADLESS,) \
+ $(if $(VBOX_WITH_QTGUI),VBOX_WITH_QTGUI,) \
+ $(if $(VBOX_WITH_AHCI),VBOX_WITH_AHCI,) \
+ $(if $(VBOX_WITH_LSILOGIC),VBOX_WITH_LSILOGIC,) \
+ $(if $(VBOX_WITH_LINUX_COMPILER_H),VBOX_WITH_LINUX_COMPILER_H,) \
+ $(if $(VBOX_WITH_RESOURCE_USAGE_API),VBOX_WITH_RESOURCE_USAGE_API,) \
+ $(if $(VBOX_WITH_UNATTENDED),VBOX_WITH_UNATTENDED,) \
+ $(if $(VBOX_WITH_DBUS),VBOX_WITH_DBUS,) \
+ $(if $(VBOX_WITH_DBUS),$(if $(VBOX_USB_WITH_DBUS),VBOX_USB_WITH_DBUS,),) \
+ $(if $(VBOX_USB_WITH_SYSFS),VBOX_USB_WITH_SYSFS,) \
+ $(if $(VBOX_USB_WITH_INOTIFY),VBOX_USB_WITH_INOTIFY,) \
+ $(if $(VBOX_WITH_NAT_SERVICE),VBOX_WITH_NAT_SERVICE,) \
+ $(if $(VBOX_WITH_IOMMU_AMD),VBOX_WITH_IOMMU_AMD,) \
+ $(if $(VBOX_WITH_IOMMU_INTEL),VBOX_WITH_IOMMU_INTEL,) \
+ $(if $(VBOX_WITH_TPM),VBOX_WITH_TPM,) \
+ $(if $(VBOX_WITH_FULL_VM_ENCRYPTION),VBOX_WITH_FULL_VM_ENCRYPTION,) \
+ $(if-expr defined(VBOX_WITH_SDS),VBOX_WITH_SDS,)
+ifdef VBOX_WITH_USB
+ VBoxSVC_DEFS += \
+ VBOX_WITH_USB \
+ $(if $(VBOX_WITH_EHCI),VBOX_WITH_EHCI,) \
+ $(if $(VBOX_WITH_NEW_USB_CODE_ON_DARWIN),VBOX_WITH_NEW_USB_CODE_ON_DARWIN,)
+endif
+VBoxSVC_DEFS.win += VBOX_COM_OUTOFPROC_MODULE _WIN32_WINNT=0x0600
+# Try to load and use libhal at runtime for probing removable media
+# VBoxSVC_DEFS.linux += VBOX_USE_LIBHAL
+VBoxSVC_DEFS.solaris += VBOX_USE_LIBHAL
+
+if $(KBUILD_TARGET) != "win" && $(VBOX_GCC_VERSION_CXX) >= 40900 # gcc 4.9.x (4.8.x is causing endless RT_ELEMENT trouble)
+ VBoxSVC_CXXFLAGS = -Wunused -Wconversion
+endif
+
+VBoxSVC_INCS = \
+ include \
+ $(VBoxSVC_0_OUTDIR) \
+ $(dir $(VBOX_XML_SCHEMADEFS_H)) \
+ $(VBOX_MAIN_APIWRAPPER_INCS) \
+ . \
+ $(VBOX_GRAPHICS_INCS)
+VBoxSVC_INCS.win = \
+ $(VBoxCOM_0_OUTDIR)
+ifdef VBOX_WITH_USB
+ VBoxSVC_INCS.os2 = \
+ $(PATH_ROOT)/src/VBox/HostDrivers/VBoxUSB/os2
+endif
+
+VBoxSVC_LIBS += \
+ $(PATH_STAGE_LIB)/VBoxAPIWrap$(VBOX_SUFF_LIB) \
+ $(PATH_STAGE_LIB)/SSMStandalone$(VBOX_SUFF_LIB) \
+ $(LIB_DDU)
+
+VBoxSVC_SDKS = VBOX_LIBPNG VBOX_ZLIB
+VBoxSVC_LIBS.solaris = \
+ adm \
+ nsl \
+ devinfo \
+ socket
+
+VBoxSVC_LIBS.win += \
+ $(PATH_SDK_$(VBOX_WINPSDK)_LIB)/dnsapi.lib
+
+VBoxSVC_INTERMEDIATES = \
+ $(VBOX_MAIN_PREREQS) \
+ $(VBOX_XML_SCHEMADEFS_H) \
+ $(VBOX_AUTOGEN_EVENT_H) \
+ $(VBOX_AUTOGEN_STRINGIFY_ENUMS_H) \
+ $(VBOX_MAIN_APIWRAPPER_GEN_HDRS)
+
+VBoxSVC_SOURCES = \
+ $(VBOX_SVC_CERTS_FILE) \
+ $(VBoxAPIWrap_0_OUTDIR)/VBoxAPI.d \
+ src-all/AuthLibrary.cpp \
+ src-all/CryptoUtils.cpp \
+ src-all/DisplayPNGUtil.cpp \
+ src-all/DisplayResampleImage.cpp \
+ src-all/DisplayUtils.cpp \
+ src-all/EventImpl.cpp \
+ src-all/Global.cpp \
+ src-all/GlobalStatusConversion.cpp \
+ src-all/HashedPw.cpp \
+ src-all/Logging.cpp \
+ src-all/NvramStoreImpl.cpp \
+ src-all/PCIDeviceAttachmentImpl.cpp \
+ src-all/ProgressImpl.cpp \
+ src-all/SecretKeyStore.cpp \
+ src-all/SharedFolderImpl.cpp \
+ src-all/AutoCaller.cpp \
+ src-all/ThreadTask.cpp \
+ src-all/VirtualBoxBase.cpp \
+ src-all/VirtualBoxErrorInfoImpl.cpp \
+ $(if $(VBOX_WITH_EXTPACK),src-all/ExtPackManagerImpl.cpp src-all/ExtPackUtil.cpp,) \
+ src-server/CertificateImpl.cpp \
+ src-server/ApplianceImpl.cpp \
+ src-server/ApplianceImplExport.cpp \
+ src-server/ApplianceImplImport.cpp \
+ src-server/AudioAdapterImpl.cpp \
+ src-server/AudioSettingsImpl.cpp \
+ src-server/BandwidthControlImpl.cpp \
+ src-server/BandwidthGroupImpl.cpp \
+ src-server/BIOSSettingsImpl.cpp \
+ src-server/RecordingSettingsImpl.cpp \
+ src-server/RecordingScreenSettingsImpl.cpp \
+ src-server/GraphicsAdapterImpl.cpp \
+ src-server/ClientWatcher.cpp \
+ src-server/ClientToken.cpp \
+ src-server/CloudProviderManagerImpl.cpp \
+ src-server/CPUProfileImpl.cpp \
+ src-server/DataStreamImpl.cpp \
+ src-server/DHCPServerImpl.cpp \
+ src-server/DHCPConfigImpl.cpp \
+ ../NetworkServices/Dhcpd/DhcpOptions.cpp \
+ src-server/NetworkServiceRunner.cpp \
+ src-server/NATNetworkImpl.cpp \
+ $(if $(VBOX_WITH_CLOUD_NET), \
+ src-server/CloudNetworkImpl.cpp \
+ ,) \
+ src-server/GuestDebugControlImpl.cpp \
+ src-server/GuestOSTypeImpl.cpp \
+ src-server/HostDnsService.cpp \
+ src-server/HostImpl.cpp \
+ src-server/HostAudioDeviceImpl.cpp \
+ src-server/HostNetworkInterfaceImpl.cpp \
+ src-server/HostPower.cpp \
+ $(if $(VBOX_WITH_LIBCURL), \
+ $(if $(VBOX_WITH_UPDATE_AGENT), \
+ src-server/UpdateAgentImpl.cpp,), \
+ ) \
+ src-server/HostVideoInputDeviceImpl.cpp \
+ src-server/HostDrivePartitionImpl.cpp \
+ src-server/HostDriveImpl.cpp \
+ src-server/MachineImpl.cpp \
+ src-all/MachineLaunchVMCommonWorker.cpp \
+ src-server/MachineImplCloneVM.cpp \
+ src-server/MachineImplMoveVM.cpp \
+ src-server/Matching.cpp \
+ src-server/MediumAttachmentImpl.cpp \
+ src-server/MediumFormatImpl.cpp \
+ src-server/MediumImpl.cpp \
+ src-server/MediumLock.cpp \
+ src-server/MediumIOImpl.cpp \
+ src-server/NATEngineImpl.cpp \
+ src-server/NetworkAdapterImpl.cpp \
+ src-server/ParallelPortImpl.cpp \
+ src-server/ProgressProxyImpl.cpp \
+ src-server/SerialPortImpl.cpp \
+ src-server/SnapshotImpl.cpp \
+ src-server/StorageControllerImpl.cpp \
+ src-server/SystemPropertiesImpl.cpp \
+ src-server/TokenImpl.cpp \
+ src-server/TrustedPlatformModuleImpl.cpp \
+ src-server/UefiVariableStoreImpl.cpp \
+ $(if $(VBOX_WITH_UNATTENDED), \
+ src-server/UnattendedImpl.cpp \
+ src-server/UnattendedInstaller.cpp \
+ src-server/UnattendedOs2Installer.cpp \
+ src-server/UnattendedScript.cpp \
+ src-all/TextScript.cpp \
+ ,) \
+ src-server/USBControllerImpl.cpp \
+ src-server/USBDeviceFiltersImpl.cpp \
+ src-server/VFSExplorerImpl.cpp \
+ src-server/VirtualBoxImpl.cpp \
+ src-server/VRDEServerImpl.cpp \
+ src-server/generic/AutostartDb-generic.cpp \
+ xml/ovfreader.cpp \
+ xml/Settings.cpp \
+ $(VBOX_XML_SCHEMADEFS_CPP) \
+ $(VBOX_AUTOGEN_EVENT_CPP) \
+ $(VBOX_AUTOGEN_STRINGIFY_ENUMS_CPP) \
+ $(if $(VBOX_WITH_XPCOM),src-server/xpcom/server.cpp,)
+
+ifdef VBOX_WITH_MAIN_NLS
+VBoxSVC_SOURCES += \
+ src-all/VirtualBoxTranslator.cpp \
+ src-all/QMTranslatorImpl.cpp
+endif
+
+ifn1of ($(KBUILD_TARGET), win darwin)
+VBoxSVC_SOURCES += $(PATH_ROOT)/src/VBox/Devices/Network/slirp/resolv_conf_parser.c
+#$(PATH_ROOT)/src/VBox/Devices/Network/slirp/resolv_conf_parser.c_DEFS += LOG_GROUP=LOG_GROUP_MAIN
+endif
+
+VBoxSVC_SOURCES.darwin = \
+ src-server/darwin/iokit.cpp \
+ src-server/darwin/HostPowerDarwin.cpp \
+ src-server/darwin/HostDnsServiceDarwin.cpp
+ifdef VBOX_WITH_VMNET
+ VBoxSVC_SOURCES.darwin += \
+ src-server/HostOnlyNetworkImpl.cpp
+endif
+
+VBoxSVC_SOURCES.win = \
+ src-server/win/svcmain.cpp \
+ src-server/win/svchlp.cpp \
+ src-server/win/HostPowerWin.cpp \
+ src-server/win/VBoxSVC.rc \
+ src-server/win/HostDnsServiceWin.cpp
+
+VBoxSVC_SOURCES.linux = \
+ src-server/linux/HostHardwareLinux.cpp \
+ src-server/linux/HostDnsServiceLinux.cpp \
+ $(if $(VBOX_WITH_DBUS),src-server/linux/HostPowerLinux.cpp) \
+ src-server/HostDnsServiceResolvConf.cpp
+
+VBoxSVC_SOURCES.solaris = \
+ src-server/linux/vbox-libhal.cpp \
+ src-server/solaris/DynLoadLibSolaris.cpp \
+ src-server/HostDnsServiceResolvConf.cpp
+
+VBoxSVC_SOURCES.os2 = \
+ src-server/HostDnsServiceResolvConf.cpp
+
+VBoxSVC_SOURCES.freebsd = \
+ src-server/freebsd/HostHardwareFreeBSD.cpp \
+ src-server/HostDnsServiceResolvConf.cpp
+
+VBoxSVC_LDFLAGS.freebsd += -lcam
+
+
+ifdef VBOX_WITH_USB
+ ifdef VBOX_WITH_SYSFS_BY_DEFAULT
+ src-server/linux/USBProxyBackendLinux.cpp_DEFS += VBOX_WITH_SYSFS_BY_DEFAULT
+ endif
+ VBoxSVC_SOURCES += \
+ src-server/USBDeviceFilterImpl.cpp \
+ src-server/USBProxyService.cpp \
+ src-server/USBProxyBackend.cpp \
+ src-server/generic/USBProxyBackendUsbIp.cpp \
+ src-server/HostUSBDeviceImpl.cpp
+ VBoxSVC_SOURCES.darwin += src-server/darwin/USBProxyBackendDarwin.cpp
+ VBoxSVC_SOURCES.linux += src-server/linux/USBProxyBackendLinux.cpp
+ VBoxSVC_SOURCES.linux += src-server/linux/USBGetDevices.cpp
+ VBoxSVC_SOURCES.os2 += src-server/os2/USBProxyBackendOs2.cpp
+ VBoxSVC_SOURCES.solaris += src-server/solaris/USBProxyBackendSolaris.cpp
+ VBoxSVC_SOURCES.win += src-server/win/USBProxyBackendWindows.cpp
+ VBoxSVC_SOURCES.freebsd += src-server/freebsd/USBProxyBackendFreeBSD.cpp
+endif
+
+ifdef VBOX_WITH_NETFLT
+ VBoxSVC_SDKS.win += VBOX_WIN_NEWDEV
+ VBoxSVC_LIBS.win += \
+ $(PATH_STAGE_LIB)/WinNetConfigDll$(VBOX_SUFF_LIB) \
+ $(PATH_STAGE_LIB)/VBoxDrvCfgDll$(VBOX_SUFF_LIB) \
+ $(PATH_TOOL_$(VBOX_VCC_TOOL)_LIB)/comsupp.lib \
+ $(PATH_SDK_$(VBOX_WINPSDK)_LIB)/WbemUuid.Lib
+endif
+VBoxSVC_LDFLAGS.darwin = -framework IOKit -framework SystemConfiguration
+
+ifdef VBOX_WITH_3D_ACCELERATION
+ VBoxSVC_DEFS += VBOX_WITH_3D_ACCELERATION
+ VBoxSVC_LIBS += $(PATH_STAGE_LIB)/VBoxOGLTest$(VBOX_SUFF_LIB)
+ VBoxSVC_LDFLAGS.darwin += -framework OpenGL
+endif
+
+ifeq ($(KBUILD_TYPE),debug)
+ VBoxSVC_LDFLAGS.linux += -rdynamic # for backtrace_symbols()
+endif
+
+ifdef VBOX_WITH_RESOURCE_USAGE_API
+ VBoxSVC_SOURCES += \
+ src-server/PerformanceImpl.cpp \
+ src-server/Performance.cpp
+ VBoxSVC_SOURCES.darwin += src-server/darwin/PerformanceDarwin.cpp
+ VBoxSVC_SOURCES.freebsd += src-server/freebsd/PerformanceFreeBSD.cpp
+ VBoxSVC_SOURCES.linux += src-server/linux/PerformanceLinux.cpp
+ VBoxSVC_SOURCES.os2 += src-server/os2/PerformanceOs2.cpp
+ VBoxSVC_SOURCES.solaris += src-server/solaris/PerformanceSolaris.cpp
+ VBoxSVC_SOURCES.win += src-server/win/PerformanceWin.cpp
+ VBoxSVC_LDFLAGS.darwin += -lproc
+ VBoxSVC_LDFLAGS.solaris += -lkstat -lnvpair
+ VBoxSVC_LDFLAGS.win += psapi.lib powrprof.lib
+endif
+
+ifdef VBOX_WITH_HOSTNETIF_API
+ VBoxSVC_SOURCES.win += src-server/win/NetIf-win.cpp
+ VBoxSVC_SOURCES.linux += src-server/linux/NetIf-linux.cpp
+ VBoxSVC_SOURCES.os2 += src-server/os2/NetIf-os2.cpp
+ VBoxSVC_SOURCES.darwin += src-server/darwin/NetIf-darwin.cpp
+ VBoxSVC_SOURCES.solaris += src-server/solaris/NetIf-solaris.cpp
+ VBoxSVC_SOURCES.freebsd += src-server/freebsd/NetIf-freebsd.cpp
+ VBoxSVC_DEFS += VBOX_WITH_HOSTNETIF_API
+ if1of ($(KBUILD_TARGET), linux darwin solaris freebsd)
+ VBoxSVC_SOURCES += src-server/generic/NetIf-generic.cpp
+ endif
+endif
+
+$(call KB_FN_DO_PASS0_ON_TARGET,VBoxSVC) # Sets VBoxSVC_0_OUTDIR
+
+ifdef VBOX_WITH_MAIN_USB_ID_DATABASE # Generate a database of USB vendor IDs and device IDs.
+ VBoxSVC_DEFS += VBOX_WITH_MAIN_USB_ID_DATABASE
+ VBoxSVC_SOURCES += $(VBoxSVC_0_OUTDIR)/USBIdDatabase.cpp
+ VBoxSVC_CLEAN += $(VBoxSVC_0_OUTDIR)/USBIdDatabase.cpp
+
+ $(call KB_FN_AUTO_CMD_DEPS,$(VBoxSVC_0_OUTDIR)/USBIdDatabase.cpp)
+ $(VBoxSVC_0_OUTDIR)/USBIdDatabase.cpp: $$(USBIdDatabaseGenerator_1_TARGET) \
+ $(PATH_SUB_CURRENT)/src-server/usb.ids $(PATH_SUB_CURRENT)/src-server/custom.ids | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(call MSG_GENERATE,USBIdDatabase,$@,$(USBIdDatabaseGenerator_1_TARGET))
+ $(QUIET)$(USBIdDatabaseGenerator_1_TARGET) -o "$@" $(filter %.ids,$^)
+
+ BLDPROGS += USBIdDatabaseGenerator
+ USBIdDatabaseGenerator_TEMPLATE = VBoxAdvBldProg
+ USBIdDatabaseGenerator_SOURCES = src-server/USBIdDatabaseGenerator.cpp
+else
+ VBoxSVC_SOURCES += src-server/USBIdDatabaseStub.cpp
+endif
+
+src-server/win/VBoxSVC.rc_INCS = $(VBoxSVC_0_OUTDIR)
+src-server/win/VBoxSVC.rc_DEPS = $(VBoxSVC_0_OUTDIR)/VBoxSVC-icon.rc
+VBoxSVC_CLEAN += $(VBoxSVC_0_OUTDIR)/VBoxSVC-icon.rc
+$(call KB_FN_AUTO_CMD_DEPS,$(VBoxSVC_0_OUTDIR)/VBoxSVC-icon.rc)
+$(VBoxSVC_0_OUTDIR)/VBoxSVC-icon.rc: $(VBOX_WINDOWS_ICON_FILE) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(APPEND) -t $@ '1 ICON DISCARDABLE "$(subst /,\\,$(VBOX_WINDOWS_ICON_FILE))"'
+
+ifndef VBOX_WITH_MIDL_PROXY_STUB
+ src-server/win/VBoxSVC.rc_DEPS += $(VBoxSVC_0_OUTDIR)/VBoxSVC.rgs
+ VBoxSVC_CLEAN += $(VBoxSVC_0_OUTDIR)/VBoxSVC.rgs
+ $(call KB_FN_AUTO_CMD_DEPS,$(VBoxSVC_0_OUTDIR)/VBoxSVC.rgs)
+ $(VBoxSVC_0_OUTDIR)/VBoxSVC.rgs: $(VBOX_PATH_MAIN_SRC)/src-all/win/VirtualBox_rgs.xsl $(VBOX_XIDL_FILE) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(VBOX_XSLTPROC) \
+ --stringparam Application VirtualBox \
+ --stringparam Module VBoxSVC \
+ -o $@ \
+ $(VBOX_PATH_MAIN_SRC)/src-all/win/VirtualBox_rgs.xsl \
+ $(VBOX_XIDL_FILE)
+endif
+
+## @todo r=klaus unfortunately we don't have xmllint everywhere, would be
+# good to check the validity for every VirtualBox.xidl change.
+#$(VBOX_XIDL_FILE).validated.ts: $(VBOX_XIDL_FILE_SRC)
+# xmllint --dtdvalid $(VBOX_PATH_MAIN_SRC)/idl/VirtualBox.dtd --noout $<
+# $(QUIET)$(CP) --changed -fv $< $(VBOX_XIDL_FILE).validated.ts
+#
+#OTHERS += $(VBOX_XIDL_FILE).validated.ts
+#OTHER_CLEAN += $(VBOX_XIDL_FILE).validated.ts
+
+testvalidxidl: $(VBOX_XIDL_FILE_SRC)
+ xmllint --dtdvalid $(VBOX_PATH_MAIN_SRC)/idl/VirtualBox.dtd --noout $<
+
+
+#
+# Embed some constraints from XML Schema file into VBoxSVC
+#
+VBOX_XML_SCHEMA = $(VBOX_PATH_MAIN_SRC)/xml/VirtualBox-settings.xsd
+
+$(VBOX_XML_SCHEMADEFS_H): $(VBOX_XML_SCHEMADEFS_XSL) $(VBOX_XML_SCHEMA) | $$(dir $$@)
+ $(call MSG_GENERATE,SchemaDefs,$@,$<)
+ $(QUIET)$(VBOX_XSLTPROC) --stringparam mode declare -o $@ $(VBOX_XML_SCHEMADEFS_XSL) $(VBOX_XML_SCHEMA)
+
+$(VBOX_XML_SCHEMADEFS_CPP): $(VBOX_XML_SCHEMADEFS_XSL) $(VBOX_XML_SCHEMA) | $$(dir $$@)
+ $(call MSG_GENERATE,SchemaDefs,$@,$<)
+ $(QUIET)$(VBOX_XSLTPROC) --stringparam mode define -o $@ $(VBOX_XML_SCHEMADEFS_XSL) $(VBOX_XML_SCHEMA)
+
+testvalidsettings: $(VBOX_XML_SCHEMA)
+ xmllint --schema $< --noout $(HOME)/.VirtualBox/VirtualBox.xml
+ xmllint --schema $< --noout $(HOME)/.VirtualBox/Machines/*/*.xml
+ xmllint --schema $< --noout $(HOME)/.VirtualBox/Machines/*/*.vbox
+ xmllint --schema $< --noout $(HOME)/VirtualBox\ VMs/*/*.vbox
+
+OTHER_CLEAN += $(VBOX_XML_SCHEMADEFS_H) $(VBOX_XML_SCHEMADEFS_CPP)
+
+
+#
+# Generate some event stuff for VBoxSVC and VBoxC.
+#
+
+# Events
+$(call KB_FN_AUTO_CMD_DEPS,$(VBOX_AUTOGEN_EVENT_H).ts)
+$(VBOX_AUTOGEN_EVENT_H).ts +| $(VBOX_AUTOGEN_EVENT_H): $(VBOX_PATH_MAIN_SRC)/idl/comimpl.xsl $(VBOX_XIDL_FILE) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(call MSG_TOOL,xsltproc,autogen events,$<,$@)
+ $(QUIET)$(VBOX_XSLTPROC) --stringparam G_kind VBoxEventHeader -o "$@" "$(VBOX_PATH_MAIN_SRC)/idl/comimpl.xsl" "$(VBOX_XIDL_FILE)"
+ $(QUIET)$(CP) --changed -fv -- "$@" "$(VBOX_AUTOGEN_EVENT_H)"
+
+$(call KB_FN_AUTO_CMD_DEPS,$(VBOX_AUTOGEN_EVENT_CPP).ts)
+$(VBOX_AUTOGEN_EVENT_CPP).ts +| $(VBOX_AUTOGEN_EVENT_CPP): $(VBOX_PATH_MAIN_SRC)/idl/comimpl.xsl $(VBOX_XIDL_FILE) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(call MSG_TOOL,xsltproc,autogen events,$<,$@)
+ $(QUIET)$(VBOX_XSLTPROC) --stringparam G_kind VBoxEvent -o "$@" "$(VBOX_PATH_MAIN_SRC)/idl/comimpl.xsl" "$(VBOX_XIDL_FILE)"
+ $(QUIET)$(CP) --changed -fv -- "$@" "$(VBOX_AUTOGEN_EVENT_CPP)"
+
+OTHER_CLEAN += $(VBOX_AUTOGEN_EVENT_H).ts $(VBOX_AUTOGEN_EVENT_H) $(VBOX_AUTOGEN_EVENT_CPP).ts $(VBOX_AUTOGEN_EVENT_CPP)
+
+# Enum stringifiers
+$(call KB_FN_AUTO_CMD_DEPS,$(VBOX_AUTOGEN_STRINGIFY_ENUMS_H).ts)
+$(VBOX_AUTOGEN_STRINGIFY_ENUMS_H).ts +| $(VBOX_AUTOGEN_STRINGIFY_ENUMS_H): $(VBOX_PATH_MAIN_SRC)/idl/stringify-enums.xsl $(VBOX_XIDL_FILE) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(call MSG_TOOL,xsltproc,autogen enum stringifiers,$<,$@)
+ $(QUIET)$(VBOX_XSLTPROC) --stringparam G_kind header -o "$@" "$(VBOX_PATH_MAIN_SRC)/idl/stringify-enums.xsl" "$(VBOX_XIDL_FILE)"
+ $(QUIET)$(CP) --changed -fv -- "$@" "$(VBOX_AUTOGEN_STRINGIFY_ENUMS_H)"
+
+$(call KB_FN_AUTO_CMD_DEPS,$(VBOX_AUTOGEN_STRINGIFY_ENUMS_CPP).ts)
+$(VBOX_AUTOGEN_STRINGIFY_ENUMS_CPP).ts +| $(VBOX_AUTOGEN_STRINGIFY_ENUMS_CPP): $(VBOX_PATH_MAIN_SRC)/idl/stringify-enums.xsl $(VBOX_XIDL_FILE) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(call MSG_TOOL,xsltproc,autogen enum stringifiers,$<,$@)
+ $(QUIET)$(VBOX_XSLTPROC) --stringparam G_kind source -o "$@" "$(VBOX_PATH_MAIN_SRC)/idl/stringify-enums.xsl" "$(VBOX_XIDL_FILE)"
+ $(QUIET)$(CP) --changed -fv -- "$@" "$(VBOX_AUTOGEN_STRINGIFY_ENUMS_CPP)"
+
+OTHER_CLEAN += $(VBOX_AUTOGEN_STRINGIFY_ENUMS_H).ts $(VBOX_AUTOGEN_STRINGIFY_ENUMS_H) $(VBOX_AUTOGEN_STRINGIFY_ENUMS_CPP).ts $(VBOX_AUTOGEN_STRINGIFY_ENUMS_CPP)
+
+ifdef VBOX_WITH_XPCOM
+#
+# VBoxSVCM - VBoxSVC wrapper module
+#
+DLLS += VBoxSVCM
+VBoxSVCM_TEMPLATE = VBOXMAINCOMP
+VBoxSVCM_DEFS = IN_RING3 $(VBOX_MAIN_DEFS)
+ ifdef VBOX_WITH_HARDENING
+VBoxSVCM_DEFS += VBOX_WITH_HARDENING
+ endif
+VBoxSVCM_INCS = \
+ include \
+ $(VBoxC_0_OUTDIR) \
+ .
+VBoxSVCM_INTERMEDIATES = $(VBOX_MAIN_PREREQS)
+VBoxSVCM_SOURCES = \
+ src-server/xpcom/server_module.cpp
+VBoxSVCM_LDFLAGS.darwin = \
+ -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/components/VBoxSVCM.dylib \
+ -exported_symbols_list $(VBoxSVCM_0_OUTDIR)/VBoxSVCM.def
+ ifeq ($(KBUILD_TARGET),darwin)
+VBoxSVCM_ORDERDEPS += $(VBoxSVCM_0_OUTDIR)/VBoxSVCM.def
+VBoxSVCM_CLEAN += $(VBoxSVCM_0_OUTDIR)/VBoxSVCM.def
+$$(VBoxSVCM_0_OUTDIR)/VBoxSVCM.def:
+ $(RM) -f $@
+ $(APPEND) $@ _NSGetModule
+ endif
+VBoxSVCM_INTERMEDIATES += $(VBOX_IDL_HEADER.XPCOM)
+
+ # 32-bit version of the module.
+ ifdef VBOX_WITH_32_ON_64_MAIN_API
+DLLS += VBoxSVCM-x86
+VBoxSVCM-x86_TEMPLATE = VBoxMainComp-x86
+VBoxSVCM-x86_EXTENDS = VBoxSVCM
+ endif
+
+endif # VBOX_WITH_XPCOM
+
+
+#
+# VBoxC module
+#
+DLLS += VBoxC
+VBoxC_TEMPLATE = VBOXMAINCOMP
+if defined(VBOX_WITH_PRECOMPILED_HEADERS) && "$(KBUILD_TARGET)" == "win"
+ VBoxC_USES = vccprecomp
+ VBoxC_PCH_HDR := $(PATH_SUB_CURRENT)/src-client/win/precomp_vcc.h
+else if $(KBUILD_KMK_REVISION) >= 3256 && "$(KBUILD_TARGET)" != "darwin" && !defined(VBOX_WITHOUT_PRECOMPILED_HEADERS)
+ if $(VBOX_GCC_VERSION_CXX) >= 50000 # GCC 4.x runs into trouble compiling the header.
+VBoxC_USES += gccprecomp
+VBoxC_PCH_HDR := $(PATH_SUB_CURRENT)/src-client/xpcom/precomp_gcc.h
+ endif
+endif
+VBoxC_DEFS = \
+ IN_RING3 \
+ $(VBOX_MAIN_DEFS) \
+ VBOX_COM_INPROC \
+ $(if $(VBOX_WITH_EFI),VBOX_WITH_EFI,) \
+ $(if $(VBOX_WITH_HGSMI),VBOX_WITH_HGSMI,) \
+ $(if $(VBOX_WITH_VIDEOHWACCEL),VBOX_WITH_VIDEOHWACCEL,) \
+ $(if $(VBOX_WITH_DRAG_AND_DROP),VBOX_WITH_DRAG_AND_DROP,) \
+ $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,) \
+ $(if $(VBOX_WITH_USB),VBOX_WITH_USB,) \
+ $(if $(VBOX_WITH_VRDEAUTH_IN_VBOXSVC),VBOX_WITH_VRDEAUTH_IN_VBOXSVC,) \
+ $(if $(VBOX_WITH_IOMMU_AMD),VBOX_WITH_IOMMU_AMD,) \
+ $(if $(VBOX_WITH_IOMMU_INTEL),VBOX_WITH_IOMMU_INTEL,) \
+ $(if $(VBOX_WITH_TPM),VBOX_WITH_TPM,) \
+ $(if $(VBOX_WITH_FULL_VM_ENCRYPTION),VBOX_WITH_FULL_VM_ENCRYPTION,)
+ifdef VBOX_WITH_NETSHAPER
+ VBoxC_DEFS += VBOX_WITH_NETSHAPER
+endif
+VBoxC_DEFS.darwin.x86 = VBOX_WITH_2X_4GB_ADDR_SPACE
+VBoxC_DEFS.win.x86 += _WIN32_WINNT=0x0500
+VBoxC_DEFS.win.amd64 += _WIN32_WINNT=0x0510
+
+if $(KBUILD_TARGET) != "win" && $(VBOX_GCC_VERSION_CXX) >= 40900 # gcc 4.9.x (4.8.x is causing endless RT_ELEMENT trouble)
+# VBoxC_CXXFLAGS = -Wunused -Wconversion
+endif
+
+VBoxC_SDKS = VBOX_LIBPNG VBOX_ZLIB
+
+ifdef VBOX_WITH_RECORDING
+ ifdef VBOX_WITH_LIBVPX
+ VBoxC_SDKS += VBOX_VPX
+ VBoxC_DEFS += VBOX_WITH_LIBVPX
+ endif
+ VBoxC_SOURCES += \
+ src-client/EBMLWriter.cpp \
+ src-client/WebMWriter.cpp \
+ src-client/Recording.cpp \
+ src-client/RecordingInternals.cpp \
+ src-client/RecordingCodec.cpp \
+ src-client/RecordingStream.cpp \
+ src-client/RecordingUtils.cpp
+
+ ifdef VBOX_WITH_AUDIO_RECORDING
+ VBoxC_DEFS += VBOX_WITH_AUDIO_RECORDING
+ # Needed in VBoxSVC for ISystemProperties.
+ src-server/SystemPropertiesImpl.cpp_DEFS += VBOX_WITH_AUDIO_RECORDING
+ ifdef VBOX_WITH_LIBOGG
+ VBOX_MAIN_DEFS += VBOX_WITH_LIBOGG
+ VBoxC_SDKS += VBOX_OGG
+ VBoxC_DEFS += VBOX_WITH_LIBOGG
+ ## @todo We need this for libvorbis down below. Remove once the libvorbis @todo is resolved.
+ VBoxSVC_SDKS += VBOX_OGG
+ endif
+ ifdef VBOX_WITH_LIBVORBIS
+ VBOX_MAIN_DEFS += VBOX_WITH_LIBVORBIS
+ VBoxC_SDKS += VBOX_VORBIS
+ ## @todo We also need to specify this for VBoxSVC, as we need to have VBOX_WITH_LIBVORBIS defined
+ ## for SystemProperties::getSupportedRecordingAudioCodecs().
+ # SystemPropertiesImpl_DEFS += VBOX_WITH_LIBVORBIS didn't work for precompiled headers on Windows --
+ # needs to be solved properly so that we can remove the following _SDKS from VBoxSVC again.
+ VBoxSVC_SDKS += VBOX_VORBIS
+ endif
+ VBoxC_SOURCES += src-client/DrvAudioRec.cpp
+ endif # VBOX_WITH_AUDIO_RECORDING
+endif # VBOX_WITH_RECORDING
+
+ifdef VBOX_WITH_OPENSSL_FIPS
+VBoxC_SDKS += VBOX_OPENSSL2
+endif
+
+VBoxC_INCS = \
+ include \
+ $(VBoxC_0_OUTDIR) \
+ $(VBOX_MAIN_APIWRAPPER_INCS) \
+ $(dir $(VBOX_XML_SCHEMADEFS_H)) \
+ $(VBOX_MAIN_APIWRAPPER_DIR)/dtrace \
+ $(VBOX_GRAPHICS_INCS)
+VBoxC_INCS.win = \
+ $(VBoxCOM_0_OUTDIR) \
+ .
+
+VBoxC_LDFLAGS.darwin = \
+ -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/components/VBoxC.dylib \
+ -exported_symbols_list $(VBoxC_0_OUTDIR)/VBoxC.def \
+ -framework IOKit
+VBoxC_LDFLAGS.win += /MANIFEST \
+ /DELAYLOAD:user32.dll
+
+
+VBoxC_LIBS += \
+ $(PATH_STAGE_LIB)/VBoxAPIWrap$(VBOX_SUFF_LIB)
+VBoxC_LIBS.win += \
+ $(PATH_SDK_$(VBOX_WINPSDK)_LIB)/psapi.lib \
+ $(PATH_TOOL_$(VBOX_VCC_TOOL)_LIB)/delayimp.lib
+ifdef VBOX_WITH_NETFLT
+ VBoxC_SDKS.win += VBOX_WIN_NEWDEV
+ VBoxC_LIBS.win += \
+ $(PATH_STAGE_LIB)/WinNetConfigDll$(VBOX_SUFF_LIB) \
+ $(PATH_STAGE_LIB)/VBoxDrvCfgDll$(VBOX_SUFF_LIB) \
+ $(PATH_TOOL_$(VBOX_VCC_TOOL)_LIB)/comsupp.lib \
+ $(PATH_SDK_$(VBOX_WINPSDK)_LIB)/WbemUuid.Lib
+ VBoxC_LDFLAGS.win += \
+ /DELAYLOAD:newdev.dll \
+ /DELAYLOAD:setupapi.dll \
+ /DELAYLOAD:iphlpapi.dll
+endif
+
+ifdef VBOX_WITH_DRAG_AND_DROP
+ VBoxC_LIBS += $(PATH_STAGE_LIB)/VBoxDnDHostR3Lib$(VBOX_SUFF_LIB)
+endif
+
+VBoxC_INTERMEDIATES = \
+ $(VBOX_MAIN_PREREQS) \
+ $(VBOX_XML_SCHEMADEFS_H) \
+ $(VBOX_AUTOGEN_EVENT_H) \
+ $(VBOX_AUTOGEN_STRINGIFY_ENUMS_H) \
+ $(VBOX_MAIN_APIWRAPPER_GEN_HDRS)
+
+VBoxC_SOURCES += \
+ $(VBoxAPIWrap_0_OUTDIR)/VBoxAPI.d \
+ src-all/CryptoUtils.cpp \
+ src-all/DisplayPNGUtil.cpp \
+ src-all/DisplayResampleImage.cpp \
+ src-all/EventImpl.cpp \
+ src-all/Global.cpp \
+ src-all/GlobalStatusConversion.cpp \
+ src-all/HashedPw.cpp \
+ src-all/Logging.cpp \
+ src-all/NvramStoreImpl.cpp \
+ src-all/PCIDeviceAttachmentImpl.cpp \
+ src-all/ProgressImpl.cpp \
+ src-all/SecretKeyStore.cpp \
+ src-all/SharedFolderImpl.cpp \
+ src-all/AutoCaller.cpp \
+ src-all/ThreadTask.cpp \
+ src-all/VirtualBoxBase.cpp \
+ src-all/VirtualBoxErrorInfoImpl.cpp \
+ $(if $(VBOX_WITH_EXTPACK),src-all/ExtPackManagerImpl.cpp src-all/ExtPackUtil.cpp,) \
+ src-client/UsbWebcamInterface.cpp \
+ $(if $(VBOX_WITH_USB_CARDREADER),src-client/UsbCardReader.cpp,) \
+ src-client/AdditionsFacilityImpl.cpp \
+ src-client/BusAssignmentManager.cpp \
+ $(if $(VBOX_WITH_PCI_PASSTHROUGH),src-client/PCIRawDevImpl.cpp,) \
+ src-client/ClientTokenHolder.cpp \
+ src-client/ConsoleImpl.cpp \
+ src-client/ConsoleImpl2.cpp \
+ src-client/ConsoleImplTeleporter.cpp \
+ src-client/ConsoleVRDPServer.cpp \
+ src-client/DisplayImpl.cpp \
+ src-client/DisplayImplLegacy.cpp \
+ src-client/DisplaySourceBitmapImpl.cpp \
+ src-client/EmulatedUSBImpl.cpp \
+ src-client/GuestImpl.cpp \
+ src-client/GuestCtrlImpl.cpp \
+ src-client/KeyboardImpl.cpp \
+ src-client/MachineDebuggerImpl.cpp \
+ src-client/MouseImpl.cpp \
+ src-client/RemoteUSBDeviceImpl.cpp \
+ src-client/SessionImpl.cpp \
+ src-client/USBDeviceImpl.cpp \
+ src-client/VBoxDriversRegister.cpp \
+ src-client/VirtualBoxClientImpl.cpp \
+ src-client/VMMDevInterface.cpp \
+ xml/Settings.cpp \
+ $(VBOX_AUTOGEN_EVENT_CPP) \
+ $(VBOX_AUTOGEN_STRINGIFY_ENUMS_CPP) \
+ $(VBOX_XML_SCHEMADEFS_CPP)
+
+ifdef VBOX_WITH_MAIN_NLS
+VBoxC_SOURCES += \
+ src-all/VirtualBoxTranslator.cpp \
+ src-all/QMTranslatorImpl.cpp
+endif
+
+# Experimental cloud support
+if defined(VBOX_WITH_CLOUD_NET)
+ if defined(VBOX_WITH_LIBSSH)
+ VBoxC_SDKS += VBOX_SSH VBOX_OPENSSL
+ VBoxC_DEFS += VBOX_WITH_LIBSSH
+ endif
+ VBoxC_SOURCES += src-client/CloudGateway.cpp
+
+ VBoxC_SOURCES += \
+ $(VBoxC_0_OUTDIR)/VBoxLibSshLazyLoad.asm
+ VBoxC_CLEAN += $(VBoxC_0_OUTDIR)/VBoxLibSshLazyLoad.asm
+ $$(VBoxC_0_OUTDIR)/VBoxLibSshLazyLoad.asm: $(PATH_SUB_CURRENT)/src-all/VBoxLibSsh.def $(VBOX_DEF_2_LAZY_LOAD) | $$(dir $$@)
+ $(call MSG_TOOL,VBoxDef2LazyLoad,VBoxC,$(filter %.def, $^),$@)
+ $(QUIET)$(RM) -f -- "$@"
+ $(VBOX_DEF_2_LAZY_LOAD) --explicit-load-function --library VBoxLibSsh --output "$@" $(filter %.def, $^)
+
+endif
+
+# Audio bits.
+VBoxC_SOURCES += \
+ src-client/AudioDriver.cpp \
+ $(if $(VBOX_WITH_AUDIO_VRDE),src-client/DrvAudioVRDE.cpp,)
+
+VBoxC_SOURCES.win = \
+ src-client/win/dllmain.cpp \
+ src-client/win/VBoxC.def \
+ src-client/win/VBoxC.rc
+ifdef VBOX_WITH_GUEST_CONTROL
+ VBoxC_SOURCES += \
+ src-client/GuestSessionImplTasks.cpp \
+ src-client/GuestCtrlPrivate.cpp \
+ src-client/GuestDirectoryImpl.cpp \
+ src-client/GuestFileImpl.cpp \
+ src-client/GuestFsObjInfoImpl.cpp \
+ src-client/GuestProcessImpl.cpp \
+ src-client/GuestSessionImpl.cpp
+endif
+ifdef VBOX_WITH_DRAG_AND_DROP
+ VBoxC_SOURCES += \
+ src-client/GuestDnDPrivate.cpp \
+ src-client/GuestDnDSourceImpl.cpp \
+ src-client/GuestDnDTargetImpl.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/DragAndDrop/DnDUtils.cpp
+endif
+ifdef VBOX_WITH_XPCOM
+ VBoxC_SOURCES += \
+ src-client/xpcom/module.cpp
+endif
+ifdef VBOX_WITH_HGCM
+ VBoxC_SOURCES += \
+ src-client/HGCMObjects.cpp \
+ src-client/HGCMThread.cpp \
+ src-client/HGCM.cpp
+endif
+ifdef VBOX_WITH_USB
+ VBoxC_SOURCES += \
+ src-client/RemoteUSBBackend.cpp
+endif
+ifndef VBOX_WITH_VRDEAUTH_IN_VBOXSVC
+ VBoxC_SOURCES += \
+ src-all/AuthLibrary.cpp
+endif
+
+$(call KB_FN_DO_PASS0_ON_TARGET,VBoxC) # Sets VBoxC_0_OUTDIR
+
+ifeq ($(KBUILD_TARGET),darwin)
+ VBoxC_ORDERDEPS += $(VBoxC_0_OUTDIR)/VBoxC.def
+ VBoxC_CLEAN += $(VBoxC_0_OUTDIR)/VBoxC.def
+ $(call KB_FN_AUTO_CMD_DEPS,$(VBoxC_0_OUTDIR)/VBoxC.def)
+ $(VBoxC_0_OUTDIR)/VBoxC.def:
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(RM) -f $@
+ $(APPEND) $@ _NSGetModule
+ $(APPEND) $@ _VBoxDriversRegister
+endif
+
+src-client/ConsoleImpl.cpp_DEFS = \
+ $(if $(VBOX_WITH_OPENSSL_FIPS), VBOX_OPENSSL_FIPS,)
+
+
+src-client/win/VBoxC.rc_DEPS = $(VBoxC_0_OUTDIR)/VBoxC.rgs $(VBoxCOM_0_OUTDIR)/VirtualBox.tlb
+VBoxC_CLEAN.win += $(VBoxC_0_OUTDIR)/VBoxC.rgs
+$(call KB_FN_AUTO_CMD_DEPS,$(VBoxC_0_OUTDIR)/VBoxC.rgs)
+$(VBoxC_0_OUTDIR)/VBoxC.rgs: $(VBOX_PATH_MAIN_SRC)/src-all/win/VirtualBox_rgs.xsl $(VBOX_XIDL_FILE) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(VBOX_XSLTPROC) \
+ --stringparam Application VirtualBox \
+ --stringparam Module VBoxC \
+ -o $@ \
+ $(VBOX_PATH_MAIN_SRC)/src-all/win/VirtualBox_rgs.xsl \
+ $(VBOX_XIDL_FILE)
+
+ifdef VBOX_WITH_32_ON_64_MAIN_API
+ #
+ # 32-bit VBox API Client In-Process module.
+ #
+ # This is currently just a 32-bit version of VBoxC. It might be desirable to
+ # split up VBoxC into a simple client and a VM client part later, in which
+ # case this module will be a simple client.
+ #
+ # Note! One important thing is that the typelib for this DLL must be build
+ # with the /env win32 switch and the VBoxC typelib with /env amd64, if
+ # not they will overwrite each others typelib module entry.
+ #
+ DLLS += VBoxClient-x86
+ VBoxClient-x86_TEMPLATE = VBoxMainComp-x86
+ VBoxClient-x86_EXTENDS = VBoxC
+if defined(VBOX_WITH_PRECOMPILED_HEADERS) && "$(KBUILD_TARGET)" == "win" ## @todo fix inheritance (fixed in r3253)
+ VBoxClient-x86_USES = vccprecomp
+ VBoxClient-x86_PCH_HDR := $(PATH_SUB_CURRENT)/src-client/win/precomp_vcc.h
+endif
+ VBoxClient-x86_SDKS = $(filter-out VBOX_VPX,$(VBoxC_SDKS))
+ VBoxClient-x86_DEFS = VBOX_COM_INPROC_API_CLIENT \
+ $(filter-out VBOX_WITH_RECORDING VBOX_WITH_AUDIO_RECORDING \
+ VBOX_WITH_LIBVPX,$(VBoxC_DEFS))
+ VBoxClient-x86_INCS.win = \
+ $(VBoxClient-x86_0_OUTDIR) \
+ $(VBoxCOM-x86_0_OUTDIR) \
+ $(VBoxC_INCS.win)
+ VBoxClient-x86_SOURCES = \
+ src-all/EventImpl.cpp \
+ src-all/Global.cpp \
+ src-all/GlobalStatusConversion.cpp \
+ src-all/AutoCaller.cpp \
+ src-all/ThreadTask.cpp \
+ src-all/VirtualBoxBase.cpp \
+ src-all/VirtualBoxErrorInfoImpl.cpp \
+ src-client/ClientTokenHolder.cpp \
+ src-client/SessionImpl.cpp \
+ src-client/VirtualBoxClientImpl.cpp \
+ $(VBOX_AUTOGEN_EVENT_CPP) \
+ $(VBOX_AUTOGEN_STRINGIFY_ENUMS_CPP) \
+ $(VBOX_XML_SCHEMADEFS_CPP)
+ ifdef VBOX_WITH_MAIN_NLS
+ VBoxClient-x86_SOURCES += \
+ src-all/VirtualBoxTranslator.cpp \
+ src-all/QMTranslatorImpl.cpp
+ endif
+ VBoxClient-x86_SOURCES.win = \
+ src-client/win/dllmain.cpp \
+ src-client/win/VBoxClient-x86.def \
+ src-client/win/VBoxClient-x86.rc
+ ifdef VBOX_WITH_XPCOM
+ VBoxClient-x86_SOURCES += \
+ src-client/xpcom/module.cpp
+ endif
+ VBoxClient-x86_LIBS = \
+ $(PATH_STAGE_LIB)/VBoxAPIWrap-x86$(VBOX_SUFF_LIB)
+ VBoxClient-x86_LIBS.win = $(NO_SUCH_VARIABLE)
+ VBoxClient-x86_LIBS.win.x86 += \
+ $(PATH_TOOL_$(VBOX_VCC_TOOL_STEM)X86_LIB)/delayimp.lib \
+ $(PATH_SDK_$(VBOX_WINPSDK)_LIB.x86)/WbemUuid.Lib
+ VBoxClient-x86_CLEAN.win += $(VBoxClient-x86_0_OUTDIR)/VBoxClient-x86.rgs
+ $(call KB_FN_DO_PASS0_ON_TARGET,VBoxClient-x86) # Sets VBoxClient-x86_0_OUTDIR
+
+ src-client/win/VBoxClient-x86.rc_DEPS = \
+ $(VBoxClient-x86_0_OUTDIR)/VBoxClient-x86.rgs \
+ $(VBoxCOM-x86_0_OUTDIR)/VirtualBox-x86.tlb
+
+ $(call KB_FN_AUTO_CMD_DEPS,$(VBoxClient-x86_0_OUTDIR)/VBoxClient-x86.rgs)
+ $(VBoxClient-x86_0_OUTDIR)/VBoxClient-x86.rgs: \
+ $(VBOX_PATH_MAIN_SRC)/src-all/win/VirtualBox_rgs.xsl \
+ $(VBOX_XIDL_FILE) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(VBOX_XSLTPROC) \
+ --stringparam Application VirtualBox \
+ --stringparam Module VBoxC \
+ -o $@ \
+ $(VBOX_PATH_MAIN_SRC)/src-all/win/VirtualBox_rgs.xsl \
+ $(VBOX_XIDL_FILE)
+endif
+
+#
+# The VBoxExtPackHelperApp.
+#
+ifdef VBOX_WITH_EXTPACK
+ PROGRAMS += VBoxExtPackHelperApp
+ VBoxExtPackHelperApp_TEMPLATE = VBoxR3SetUidToRoot
+ VBoxExtPackHelperApp_LDFLAGS.darwin = -framework Security
+ VBoxExtPackHelperApp_LDFLAGS.win = /SUBSYSTEM:windows
+ VBoxExtPackHelperApp_SOURCES = \
+ src-helper-apps/VBoxExtPackHelperApp.cpp \
+ src-all/ExtPackUtil.cpp
+ VBoxExtPackHelperApp_SOURCES.win = \
+ src-helper-apps/VBoxExtPackHelperApp.rc
+ VBoxExtPackHelperApp_LIBS = \
+ $(LIB_RUNTIME)
+endif # VBOX_WITH_EXTPACK
+
+#
+# VolInfo
+#
+ifdef VBOX_WITH_DEVMAPPER
+ PROGRAMS.linux += VBoxVolInfo
+ VBoxVolInfo_TEMPLATE = VBoxR3SetUidToRoot
+ VBoxVolInfo_SOURCES = src-helper-apps/VBoxVolInfo.cpp
+ VBoxVolInfo_LIBS = devmapper
+endif
+
+
+endif # !defined(VBOX_ONLY_SDK) && !defined(VBOX_ONLY_EXTPACKS) (the ifndef is far above)
+ifndef VBOX_ONLY_SDK
+
+
+#
+# VBoxCOM - COM Abstraction Layer library
+#
+LIBRARIES += VBoxCOM
+VBoxCOM_TEMPLATE = VBoxMainLib
+VBoxCOM_INTERMEDIATES = $(VBOX_MAIN_PREREQS) \
+ $(VBOX_MAIN_APIWRAPPER_GEN_HDRS)
+VBoxCOM_SOURCES = \
+ glue/com.cpp \
+ glue/VBoxLogRelCreate.cpp \
+ glue/GetVBoxUserHomeDirectory.cpp \
+ glue/initterm.cpp \
+ glue/string.cpp \
+ glue/string-base64.cpp \
+ glue/AutoLock.cpp \
+ glue/EventQueue.cpp \
+ glue/NativeEventQueue.cpp \
+ glue/ErrorInfo.cpp \
+ glue/errorprint.cpp
+VBoxCOM_INCS += $(VBOX_MAIN_APIWRAPPER_INCS) \
+ include
+ifeq ($(KBUILD_TARGET),win)
+ ifdef VBOX_WITH_MIDL_PROXY_STUB
+ VBoxCOM_DEFS.win = VBOX_WITH_AUTO_COM_REG_UPDATE
+ endif
+ ifdef VBOX_WITH_SDS
+ VBoxCOM_DEFS.win += VBOX_WITH_SDS
+ endif
+ VBoxCOM_DEFS.x86 = _WIN32_WINNT=0x0500
+ VBoxCOM_DEFS.amd64 = _WIN32_WINNT=0x0510
+ VBoxCOM_SOURCES += \
+ $(VBoxCOM_0_OUTDIR)/VirtualBox_i.c
+ VBoxCOM_CLEAN = \
+ $(VBoxCOM_0_OUTDIR)/VirtualBox_i.c \
+ $(VBoxCOM_0_OUTDIR)/VirtualBox_p.c \
+ $(VBoxCOM_0_OUTDIR)/dlldata.c \
+ $(VBoxCOM_0_OUTDIR)/VirtualBox.h \
+ $(VBoxCOM_0_OUTDIR)/VirtualBox.tlb
+ VBoxCOM_CLEAN += \
+ $(VBoxCOM_0_OUTDIR)/legacy/VirtualBox_i.c \
+ $(VBoxCOM_0_OUTDIR)/legacy/VirtualBox_p.c \
+ $(VBoxCOM_0_OUTDIR)/legacy/dlldata.c \
+ $(VBoxCOM_0_OUTDIR)/legacy/VirtualBox.h \
+ $(VBoxCOM_0_OUTDIR)/legacy/VirtualBox.tlb
+ VBoxCOM_BLDDIRS = $(VBoxCOM_0_OUTDIR)/legacy/
+else # !win
+ VBoxCOM_SOURCES += \
+ $(VBoxCOM_0_OUTDIR)/VirtualBox_XPCOM_i.c \
+ glue/xpcom/helpers.cpp
+endif # !win
+
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+glue/VBoxLogRelCreate.cpp_DEFS = KBUILD_TYPE="$(KBUILD_TYPE)"
+else
+glue/VBoxLogRelCreate.cpp_DEFS = KBUILD_TYPE=\"$(KBUILD_TYPE)\"
+endif
+
+# 32-bit version of VBoxCOM.
+if defined(VBOX_WITH_32_ON_64_MAIN_API) && !defined(VBOX_ONLY_EXTPACKS)
+ LIBRARIES += VBoxCOM-x86
+ VBoxCOM-x86_TEMPLATE = VBoxMainLib-x86
+ VBoxCOM-x86_EXTENDS = VBoxCOM
+ ifeq ($(KBUILD_TARGET),win)
+ VBoxCOM-x86_CLEAN = \
+ $(VBoxCOM-x86_0_OUTDIR)/VirtualBox_i.c \
+ $(VBoxCOM-x86_0_OUTDIR)/VirtualBox_p.c \
+ $(VBoxCOM-x86_0_OUTDIR)/dlldata.c \
+ $(VBoxCOM-x86_0_OUTDIR)/VirtualBox.h \
+ $(VBoxCOM-x86_0_OUTDIR)/VirtualBox-x86.tlb
+ endif
+endif
+
+# -MD version for the GUI in -MDd builds.
+ifeq ($(KBUILD_TARGET).$(VBOX_VCC_CRT_TYPE),win.d)
+ ifndef VBOX_ONLY_EXTPACKS
+ LIBRARIES += VBoxCOM-GUI
+ USES += qt5
+ VBoxCOM-GUI_TEMPLATE = VBOXQTGUI$(if-expr defined(VBOX_WITH_HARDENING),,EXE)
+ VBoxCOM-GUI_EXTENDS = VBoxCOM
+ endif
+endif
+
+# define qt5 tools for translation even if VBOX_ONLY_EXTPACKS is enabled
+ifdef VBOX_WITH_NLS
+ USES += qt5
+endif
+
+
+endif # !VBOX_ONLY_SDK
+
+
+#
+# Installs com related thing(s) to bin.
+#
+INSTALLS.win += VBoxMain-com-inst
+VBoxMain-com-inst_INST = $(INST_BIN)
+VBoxMain-com-inst_MODE = a+r,u+w
+VBoxMain-com-inst_SOURCES = src-all/win/comregister.cmd
+
+
+if defined(VBOX_WITH_HARDENING) && "$(KBUILD_TARGET)" == "linux" && !defined(VBOX_ONLY_EXTPACKS)
+ #
+ # Installs linux/hardening related things to components.
+ #
+ INSTALLS.linux += VBoxMain-hardening-inst
+ VBoxMain-hardening-inst_INST = $(INST_BIN)components/
+ VBoxMain-hardening-inst_SYMLINKS = \
+ VBoxDDU.so=>../VBoxDDU.so \
+ VBoxRT.so=>../VBoxRT.so \
+ VBoxVMM.so=>../VBoxVMM.so \
+ VBoxXPCOM.so=>../VBoxXPCOM.so
+endif
+
+
+if defined(VBOX_WITH_MIDL_PROXY_STUB) && "$(KBUILD_TARGET)" == "win" && !defined(VBOX_ONLY_EXTPACKS)
+ #
+ # Experimental COM proxy + stub DLL w/ automatic registration updating.
+ #
+ # The Legacy stub is for older 64-bit windows versions (pre Windows 7) as we
+ # were having various problems on windows server 2003 and 2008 with the code
+ # MIDL generated. Also, in windows 7 there are some potentially interesting
+ # changes in the generated code where it uses new helpers from OLE32.
+ #
+ DLLS += VBoxProxyStub
+ VBoxProxyStub_TEMPLATE = VBOXMAINCOMP
+ ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ VBoxProxyStub_DEFS := REGISTER_PROXY_DLL PROXY_CLSID_IS=$(quote $(VBOX_MIDL_PROXY_CLSID_IS))
+ else
+ VBoxProxyStub_DEFS := REGISTER_PROXY_DLL PROXY_CLSID_IS="$(VBOX_MIDL_PROXY_CLSID_IS)"
+ endif
+ ifdef VBOX_WITH_SDS
+ VBoxProxyStub_DEFS += VBOX_WITH_SDS
+ endif
+ ifdef VBOX_WITH_SDS_CLIENT_WATCHER
+ VBoxProxyStub_DEFS += VBOX_WITH_SDS_CLIENT_WATCHER,)
+ endif
+ VBoxProxyStub_DEFS.win.x86 = WIN32
+ VBoxProxyStub_SDKS = VBOX_NTDLL
+ VBoxProxyStub_DEFPATH = $(VBoxCOM_0_OUTDIR)/# Hack to workaround drive letter in $(VBoxCOM_0_OUTDIR)/ for CFLAGS hack below.
+ VBoxProxyStub_INCS = $(VBoxCOM_0_OUTDIR)/
+ VBoxProxyStub_SOURCES := \
+ VirtualBox_p.c \
+ VirtualBox_i.c \
+ $(PATH_SUB_CURRENT)/src-all/win/VBoxProxyStub.def \
+ $(PATH_SUB_CURRENT)/src-all/win/VBoxProxyStub.rc \
+ $(PATH_SUB_CURRENT)/src-all/win/VBoxProxyStub.c
+ src-all/win/VBoxProxyStub.rc_DEPS = $(VBoxCOM_0_OUTDIR)/VirtualBox.tlb
+ VBoxProxyStub_VirtualBox_p.c_CFLAGS.x86 = /Gz # Workaround for incorrect ObjectStublessClient* prototypes in SDK v7.1.
+
+ DLLS += VBoxProxyStubLegacy
+ VBoxProxyStubLegacy_TEMPLATE = VBOXMAINCOMP
+ VBoxProxyStubLegacy_EXTENDS = VBoxProxyStub
+ VBoxProxyStubLegacy_DEFS = $(VBoxProxyStub_DEFS) VBOX_IN_PROXY_STUB_LEGACY
+ VBoxProxyStubLegacy_INCS = $(VBoxCOM_0_OUTDIR)/legacy/
+ VBoxProxyStubLegacy_SOURCES = \
+ $(VBoxCOM_0_OUTDIR)/legacy/VirtualBox_p.c \
+ $(VBoxCOM_0_OUTDIR)/legacy/VirtualBox_i.c \
+ src-all/win/VBoxProxyStub.def \
+ src-all/win/VBoxProxyStubLegacy.rc \
+ src-all/win/VBoxProxyStub.c
+ src-all/win/VBoxProxyStubLegacy.rc_DEPS = $(VBoxCOM_0_OUTDIR)/legacy/VirtualBox.tlb
+
+ ifdef VBOX_WITH_32_ON_64_MAIN_API
+ DLLS += VBoxProxyStub-x86
+ VBoxProxyStub-x86_TEMPLATE = VBoxMainComp-x86
+ VBoxProxyStub-x86_SDKS = VBOX_NTDLL
+ VBoxProxyStub-x86_DEFS = $(VBoxProxyStub_DEFS) VBOX_PROXY_STUB_32_ON_64 WIN32
+ VBoxProxyStub-x86_INCS = $(VBoxCOM-x86_0_OUTDIR)/
+ VBoxProxyStub-x86_SOURCES = \
+ $(VBoxCOM-x86_0_OUTDIR)/VirtualBox_p.c \
+ $(VBoxCOM-x86_0_OUTDIR)/VirtualBox_i.c \
+ src-all/win/VBoxProxyStub.c \
+ src-all/win/VBoxProxyStub-x86.rc \
+ src-all/win/VBoxProxyStub.def
+ src-all/win/VBoxProxyStub-x86.rc_DEPS = $(VBoxCOM-x86_0_OUTDIR)/VirtualBox-x86.tlb
+ endif
+endif
+
+
+ifdef VBOX_WITH_MAIN_NLS
+#
+# NLS stuff.
+#
+include $(PATH_SUB_CURRENT)/nls/ApprovedLanguages.kmk
+
+PROGRAMS += VirtualBoxAPI
+VirtualBoxAPI_TEMPLATE = VBoxNLS
+VirtualBoxAPI_QT_TRANSLATIONS = $(addsuffix .ts,$(addprefix $(VBOX_PATH_MAIN_SRC)/nls/VirtualBoxAPI_,$(VBOX_APPROVED_MAIN_LANGUAGES)))
+VirtualBoxAPI_VBOX_ALL_NLS_SOURCES = $(wildcard \
+ $(VBOX_PATH_MAIN_SRC)/include/*.h\
+ $(VBOX_PATH_MAIN_SRC)/src-all/*.cpp \
+ $(VBOX_PATH_MAIN_SRC)/src-all/*.h \
+ $(VBOX_PATH_MAIN_SRC)/src-server/*.cpp \
+ $(VBOX_PATH_MAIN_SRC)/src-server/*.h \
+ $(VBOX_PATH_MAIN_SRC)/src-client/*.cpp \
+ $(VBOX_PATH_MAIN_SRC)/src-client/*.h )
+
+updatenls:: makeallnls $(VBOX_PATH_MAIN_SRC)/nls/VirtualBoxAPI_en.ts
+
+makeallnls:: $(VirtualBoxAPI_VBOX_ALL_NLS_SOURCES)
+ $(call MSG_L1,lupdate all languages (nls/*.ts))
+ $(QUIET)$(TOOL_QT5_LUPDATE) \
+ $^ \
+ -ts \
+ $(filter-out nls/VirtualBoxAPI_en.ts, $(VirtualBoxAPI_QT_TRANSLATIONS)) \
+ $(VBOX_PATH_MAIN_SRC)/nls/VirtualBoxAPI_xx_YY.ts
+
+#fake-main-nls:
+# $(foreach file, $(VirtualBoxAPI_QT_TRANSLATIONS) \
+# ,$(NLTAB)$(SED) -i \
+# -e '/<source>.*<\/source>/h' \
+# -e '/<source>.*<\/source>/p' \
+# -e '/<translation type="unfinished"><\/translation>/{' \
+# -e 'x' \
+# -e 's/<source>\(.*\)<\/source>/<translation type="unfinished">$(notdir $(file)): \1<\/translation>/' \
+# -e '}' \
+# $(file) )
+
+
+# Create the English translation file. This is something special cause it will
+# contain the plural forms only.
+$(VBOX_PATH_MAIN_SRC)/nls/VirtualBoxAPI_en.ts: $(VirtualBoxAPI_VBOX_ALL_NLS_SOURCES)
+ $(call MSG_L1,lupdate $@)
+ $(QUIET)$(TOOL_QT5_LUPDATE) \
+ $^ \
+ -ts \
+ "$@"
+ $(QUIET)$(SED) -n -i \
+ -e '/<context>/,/<\/context>/!p' \
+ -e '/<context>/h' \
+ -e '/<name>/H' \
+ -e '/<message numerus="yes">/,/<\/message>/H' \
+ -e '/<\/context>/{H;x;/<message/p}' \
+ "$@"
+
+endif # VBOX_WITH_MAIN_NLS
+
+
+# generate rules
+include $(FILE_KBUILD_SUB_FOOTER)
+
+
+
+#
+# Additions rules.
+#
+
+## @todo this hack ain't cutting it any longer. (the file name is abspath'ed since ages now.)
+glue/xpcom/helpers.cpp: $(VBOX_IDL_TYPELIB.XPCOM)
+
+$(call KB_FN_AUTO_CMD_DEPS_EX,$(VBOX_IDL_FILE.XPCOM),$(PATH_OUT)/VBOX_IDL_FILE.XPCOM.vbox-dep)
+$(VBOX_IDL_FILE.XPCOM): $(VBOX_PATH_MAIN_SRC)/idl/xpidl.xsl $(VBOX_XIDL_FILE) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS_EX,$(PATH_OUT)/VBOX_IDL_FILE.XPCOM.vbox-dep)
+ $(call MSG_TOOL,xsltproc,VBoxSVC,$(VBOX_PATH_MAIN_SRC)/idl/xpidl.xsl,$@)
+ $(QUIET)$(VBOX_XSLTPROC) -o $@ $(VBOX_PATH_MAIN_SRC)/idl/xpidl.xsl $(VBOX_XIDL_FILE)
+
+$(call KB_FN_AUTO_CMD_DEPS_EX,$(VBOX_IDL_TYPELIB.XPCOM),$(PATH_OUT)/VBOX_IDL_TYPELIB.XPCOM.vbox-dep)
+$(VBOX_IDL_TYPELIB.XPCOM): $(VBOX_IDL_FILE.XPCOM) | $$(dir $$@) $(VBOX_XPIDL)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS_EX,$(PATH_OUT)/VBOX_IDL_TYPELIB.XPCOM.vbox-dep)
+ $(call MSG_TOOL,xpidl,VBoxSVC,$(VBOX_IDL_FILE.XPCOM),$@)
+ $(QUIET)$(VBOX_XPIDL_ENV)$(VBOX_XPIDL) -m typelib -I $(VBOX_PATH_XPCOM_IDL) -e $@ $(VBOX_IDL_FILE.XPCOM)
+ $(CHMOD) 0644 $@
+## @todo ^^^^^^^^^^^^ fix horrible hack
+
+$(call KB_FN_AUTO_CMD_DEPS_EX,$(VBOX_IDL_HEADER.XPCOM),$(PATH_OUT)/VBOX_IDL_HEADER.XPCOM.vbox-dep)
+$(VBOX_IDL_HEADER.XPCOM): $(VBOX_IDL_FILE.XPCOM) | $$(dir $$@) $(VBOX_XPIDL)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS_EX,$(PATH_OUT)/VBOX_IDL_HEADER.XPCOM.vbox-dep)
+ $(call MSG_TOOL,xpidl,VBoxSVC,$(VBOX_IDL_FILE.XPCOM),$@)
+ $(QUIET)$(VBOX_XPIDL_ENV)$(VBOX_XPIDL) -m header -I $(VBOX_PATH_XPCOM_IDL) -e $@ $(VBOX_IDL_FILE.XPCOM)
+
+$(call KB_FN_AUTO_CMD_DEPS_EX,$(VBOX_IDL_FILE.MSCOM),$(PATH_OUT)/VBOX_IDL_FILE.MSCOM.vbox-dep)
+$(VBOX_IDL_FILE.MSCOM): $(VBOX_PATH_MAIN_SRC)/idl/midl.xsl $(VBOX_XIDL_FILE) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS_EX,$(PATH_OUT)/VBOX_IDL_FILE.MSCOM.vbox-dep)
+ $(call MSG_TOOL,xsltproc,VBoxSVC,$<,$@)
+ $(QUIET)$(VBOX_XSLTPROC) -o $@ \
+ $(if $(VBOX_WITH_MIDL_PROXY_STUB),-stringparam g_fGenProxy yes,) \
+ $(if $(VBOX_WITH_SDS),-stringparam g_fVBoxWithSDS yes,) \
+ $(VBOX_PATH_MAIN_SRC)/idl/midl.xsl $(VBOX_XIDL_FILE)
+
+
+# Aliases for testing purposes.
+ifdef VBOX_WITH_XPCOM
+testidl: $(VBOX_IDL_FILE.XPCOM) $(VBOX_IDL_TYPELIB.XPCOM) $(VBoxCOM_0_OUTDIR)/VirtualBox_XPCOM_i.c
+testidlhdr: $(VBOX_IDL_HEADER.XPCOM)
+else
+testidl: $(VBOX_IDL_FILE.MSCOM) $(VBoxCOM_0_OUTDIR)/VirtualBox_i.c \
+ $(if $(VBOX_WITH_32_ON_64_MAIN_API),$(VBoxCOM-x86_0_OUTDIR)/VirtualBox_i.c,)
+endif
+
+
+$(VBoxCOM_0_OUTDIR)/VirtualBox_XPCOM_i.c: $(VBOX_PATH_MAIN_SRC)/idl/xpidl_iid.xsl $(VBOX_XIDL_FILE) | $$(dir $$@)
+ $(call MSG_TOOL,xsltproc,VBoxSVC,$<,$@)
+ $(QUIET)$(VBOX_XSLTPROC) -o $@ $< $(VBOX_XIDL_FILE)
+
+if defined(VBOX_ONLY_SDK) || "$(KBUILD_TARGET)" == "win"
+
+ # Note! Because we've got a different proxy stub DLL for pre windows 7 64-bit hosts, we target windows 7 for AMD64.
+ # The output is different and hopefully more efficient, at least memory wise (using more helpers in OLE32).
+ # Note! We're checking for win.amd64 below when setting the target because of the SDK build which happens on linux.amd64
+ # but with a 32-bit Wine.
+ $(call KB_FN_AUTO_CMD_DEPS,$(VBoxCOM_0_OUTDIR)/VirtualBox_i.c)
+ $(VBoxCOM_0_OUTDIR)/VirtualBox_i.c \
+ $(if $(VBOX_WITH_MIDL_PROXY_STUB), + $(VBoxCOM_0_OUTDIR)/VirtualBox_p.c + $(VBoxCOM_0_OUTDIR)/dlldata.c,) \
+ + $(VBoxCOM_0_OUTDIR)/VirtualBox.h \
+ + $(VBoxCOM_0_OUTDIR)/VirtualBox.tlb: $(VBOX_IDL_FILE.MSCOM) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(VBOX_MIDL_REDIRECT) $(VBOX_WIN_MIDL) /nologo /W4 \
+ /env $(if-expr "$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)" == "win.amd64" ,amd64,win32) \
+ /robust /protocol all /target $(if-expr defined(VBOX_WITH_MIDL_PROXY_STUB),NT61,NT51) \
+ /out $(call VBOX_FN_MAKE_WIN_PATH,$(VBoxCOM_0_OUTDIR)) \
+ /cpp_cmd $(VBOX_MIDL_CPP_CMD) \
+ $(foreachfile dir,$(SDK_$(VBOX_WINPSDK)_INCS),/I $(call VBOX_FN_MAKE_WIN_PATH,$(dir))) \
+ /I idl \
+ $(call VBOX_FN_MAKE_WIN_PATH,$(VBOX_IDL_FILE.MSCOM))
+ $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxCOM_0_OUTDIR)/VirtualBox.h)
+ $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxCOM_0_OUTDIR)/VirtualBox_i.c)
+ $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxCOM_0_OUTDIR)/VirtualBox_p.c)
+
+ if defined(VBOX_WITH_MIDL_PROXY_STUB)
+ # -Windows Server 2003 AMD64 SP1 does not like the result when using '/protocol all' and '/target NT51'.
+ # -Vista AMD64 SP1 and Windows Server 2008 AMD64 seems to have some objections as well, but it seemed
+ # that using an older MIDL compiler (v7.00.0499 instead of v7.00.0555) helps. But the W2K3 fix also works.
+ # To avoid 32-bit vs. 64-bit differences, we do the same for 32-bit windows versions.
+ $(call KB_FN_AUTO_CMD_DEPS,$(VBoxCOM_0_OUTDIR)/legacy/VirtualBox_i.c)
+ $(VBoxCOM_0_OUTDIR)/legacy/VirtualBox_i.c \
+ + $(VBoxCOM_0_OUTDIR)/legacy/VirtualBox_p.c\
+ + $(VBoxCOM_0_OUTDIR)/legacy/dlldata.c \
+ + $(VBoxCOM_0_OUTDIR)/legacy/VirtualBox.h \
+ + $(VBoxCOM_0_OUTDIR)/legacy/VirtualBox.tlb: $(VBOX_IDL_FILE.MSCOM) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(VBOX_MIDL_REDIRECT) $(VBOX_WIN_MIDL) /nologo /W4 \
+ $(if-expr "$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)" == "win.amd64" \
+ ,/env amd64 /x64 /target NT50 /robust \
+ ,/env win32 /target NT51 /robust /protocol all ) \
+ /out $(call VBOX_FN_MAKE_WIN_PATH,$(dir $@)) \
+ /cpp_cmd $(VBOX_MIDL_CPP_CMD) \
+ $(foreachfile dir,$(SDK_$(VBOX_WINPSDK)_INCS),/I $(call VBOX_FN_MAKE_WIN_PATH,$(dir))) \
+ /I idl \
+ $(call VBOX_FN_MAKE_WIN_PATH,$(VBOX_IDL_FILE.MSCOM))
+ $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxCOM_0_OUTDIR)/legacy/VirtualBox.h)
+ $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxCOM_0_OUTDIR)/legacy/VirtualBox_i.c)
+ $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxCOM_0_OUTDIR)/legacy/VirtualBox_p.c)
+ endif
+
+ if defined(VBOX_WITH_32_ON_64_MAIN_API) && !defined(VBOX_ONLY_EXTPACKS)
+ # The XP targetted 32-bit proxy stub works with all versions of windows (the
+ # issues leading to the VBoxProxyStubLegacy.dll are only on 64-bit windows).
+ $(call KB_FN_AUTO_CMD_DEPS,$(VBoxCOM-x86_0_OUTDIR)/VirtualBox_i.c)
+ $(VBoxCOM-x86_0_OUTDIR)/VirtualBox_i.c \
+ $(if $(VBOX_WITH_MIDL_PROXY_STUB), + $(VBoxCOM-x86_0_OUTDIR)/VirtualBox_p.c + $(VBoxCOM-x86_0_OUTDIR)/dlldata.c,) \
+ + $(VBoxCOM-x86_0_OUTDIR)/VirtualBox.h \
+ + $(VBoxCOM-x86_0_OUTDIR)/VirtualBox-x86.tlb: $(VBOX_IDL_FILE.MSCOM) | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(VBOX_MIDL_REDIRECT_X86) $(VBOX_WIN_MIDL) /nologo /W4 \
+ /env win32 /target NT51 /robust /protocol all \
+ /out $(call VBOX_FN_MAKE_WIN_PATH,$(VBoxCOM-x86_0_OUTDIR)) \
+ /tlb $(call VBOX_FN_MAKE_WIN_PATH,$(VBoxCOM-x86_0_OUTDIR)/VirtualBox-x86.tlb) \
+ /cpp_cmd $(VBOX_MIDL_CPP_CMD_X86) \
+ $(foreachfile dir,$(SDK_$(VBOX_WINPSDK)_INCS),/I $(call VBOX_FN_MAKE_WIN_PATH,$(dir))) \
+ /I idl \
+ $(call VBOX_FN_MAKE_WIN_PATH,$(VBOX_IDL_FILE.MSCOM))
+ $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxCOM-x86_0_OUTDIR)/VirtualBox.h)
+ $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxCOM-x86_0_OUTDIR)/VirtualBox_i.c)
+ $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxCOM-x86_0_OUTDIR)/VirtualBox_p.c)
+ endif
+
+endif # defined(VBOX_ONLY_SDK) || "$(KBUILD_TARGET)" == "win"
+
+#
+# Translation stuff
+#
+VBoxSVC_VBOX_HEADERS = \
+ include/collection.h \
+ include/MachineImpl.h
+VBoxSVC_VBOX_TRANSLATIONS = \
+ nls/VBoxSVC_de.ts
+
+VBoxC_VBOX_HEADERS = \
+ include/ConsoleImpl.h
+
+VBoxC_VBOX_INTERMEDIATES = $(VBOX_MAIN_APIWRAPPER_GEN_HDRS)
+VBoxC_VBOX_HEADERS += $(VBOX_MAIN_APIWRAPPER_INCS)
+
+VBoxC_VBOX_TRANSLATIONS = \
+ nls/VBoxC_de.ts
+
+ifdef VBOX_WITH_JMSCOM
+
+ #
+ # Java glue JAR files
+ #
+ VBOX_JMSCOM_JAR = $(VBoxJMscom-inst-jar_0_OUTDIR)/vboxjmscom.jar
+ VBOX_JMSCOM_TARGET := $(PATH_TARGET)/vboxjmscom-gen
+ VBOX_JMSCOM_GEN = $(VBOX_JMSCOM_TARGET)/jmscomgen
+ VBOX_JMSCOM_JDEST := $(VBOX_JMSCOM_TARGET)/jdest
+ VBOX_GLUE_XSLT_DIR := $(PATH_ROOT)/src/VBox/Main/glue
+ VBOX_JACOB_DIR := $(PATH_ROOT)/src/libs/jacob-1.17
+
+ INSTALLS += VBoxJMscom-inst-jar
+ VBoxJMscom-inst-jar_INST = $(INST_SDK)bindings/mscom/java/
+ VBoxJMscom-inst-jar_MODE = a+r,u+w
+ VBoxJMscom-inst-jar_SOURCES = \
+ $(VBoxJMscom-inst-jar_0_OUTDIR)/vboxjmscom.jar
+ VBoxJMscom-inst-jar_CLEAN = \
+ $(VBoxJMscom-inst-jar_0_OUTDIR)/vboxjmscom.jar \
+ $(VBOX_JMSCOM_GEN)/jmscomglue.list \
+ $(wildcard \
+ $(VBOX_JMSCOM_GEN)/java/*.java \
+ $(VBOX_JMSCOM_JDEST)/*.class \
+ $(VBOX_JMSCOM_JDEST)/*/*.class \
+ $(VBOX_JMSCOM_JDEST)/*/*/*.class \
+ $(VBOX_JMSCOM_JDEST)/*/*/*/*.class \
+ )
+ VBoxJMscom-inst-jar_BLDDIRS += $(VBOX_JMSCOM_GEN)/java
+ $(call KB_FN_DO_PASS0_ON_TARGET,VBoxJMscom-inst-jar) # Sets VBoxJMscom-inst-jar_0_OUTDIR
+
+ $(call KB_FN_AUTO_CMD_DEPS,$(VBOX_JMSCOM_GEN)/jmscomglue.list)
+ $(VBOX_JMSCOM_GEN)/jmscomglue.list: \
+ $(VBOX_XIDL_FILE) \
+ $(VBOX_GLUE_XSLT_DIR)/glue-java.xsl \
+ $(VBOX_FILESPLIT) \
+ | $(VBOX_JMSCOM_GEN)/java/
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(call MSG_L1,Generating Java MSCOM glue files from XIDL)
+ $(QUIET)$(RM) -f $(wildcard $(VBOX_JMSCOM_GEN)/java/*.java)
+ $(QUIET)$(VBOX_XSLTPROC) \
+ --stringparam filelistonly "" \
+ --stringparam G_vboxApiSuffix $(VBOX_API_SUFFIX) \
+ --stringparam G_vboxGlueStyle mscom \
+ --stringparam G_vboxDirPrefix "" \
+ -o $(VBOX_JMSCOM_GEN)/java/merged.file $(VBOX_GLUE_XSLT_DIR)/glue-java.xsl $(VBOX_XIDL_FILE)
+ $(QUIET)$(VBOX_FILESPLIT) $(VBOX_JMSCOM_GEN)/java/merged.file $(VBOX_JMSCOM_GEN)/java
+ $(QUIET)echo $(VBOX_JMSCOM_GEN)/java/*.java > $@
+
+ $(call KB_FN_AUTO_CMD_DEPS,$(VBoxJMscom-inst-jar_0_OUTDIR)/vboxjmscom.jar)
+ $(VBoxJMscom-inst-jar_0_OUTDIR)/vboxjmscom.jar: $(VBOX_JMSCOM_GEN)/jmscomglue.list | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(call MSG_TOOL,javac,$(notdir $@),jmscomglue.list,)
+ $(QUIET)$(RM) -Rf $(VBOX_JMSCOM_JDEST)
+ $(QUIET)$(MKDIR) -p $(VBOX_JMSCOM_JDEST)
+ $(call MSG_TOOL,javac,$(notdir $@),...,)
+ $(QUIET)$(VBOX_JAVAC) $(VBOX_JAVAC_OPTS) @$(VBOX_JMSCOM_GEN)/jmscomglue.list \
+ -d $(VBOX_JMSCOM_JDEST) -classpath "$(VBOX_JMSCOM_JDEST)$(VBOX_SEP)$(VBOX_JACOB_DIR)/jacob.jar"
+ $(call MSG_LINK,$(notdir $@),$@)
+ $(QUIET)$(VBOX_JAR) cf $@ -C $(VBOX_JMSCOM_JDEST) .
+
+ ## @todo compile TestVBox.java (see below) to have sanity checking
+endif # VBOX_WITH_JMSCOM
+
+#
+# Install Java glue sample code.
+#
+INSTALLS += javagluesample
+javagluesample_INST = $(INST_SDK)bindings/glue/java/
+javagluesample_MODE = a+r,u+w
+javagluesample_SOURCES = \
+ $(VBOX_PATH_MAIN_SRC)/glue/tests/TestVBox.java=>TestVBox.java \
+ $(VBOX_PATH_MAIN_SRC)/glue/tests/Makefile=>Makefile
+
+#
+# Rules associated with VBoxAPIWrap (in typical invocation order).
+# We keep them down here to simplify the use of VBoxAPIWrap_0_OUTDIR.
+#
+
+# Generate a make include file which lists the wrapper header and source files.
+$(call KB_FN_AUTO_CMD_DEPS,$(VBoxAPIWrap_VBOX_KMK_FILE).ts)
+$(VBoxAPIWrap_VBOX_KMK_FILE).ts \
++| $(VBoxAPIWrap_VBOX_KMK_FILE): \
+ $(VBOX_XIDL_FILE) \
+ $(VBOX_PATH_MAIN_SRC)/idl/apiwrap-server-filelist.xsl \
+ $(VBOX_PATH_MAIN_SRC)/Makefile.kmk
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(call MSG_GENERATE,,$(VBoxAPIWrap_VBOX_KMK_FILE))
+ $(QUIET)$(RM) -f $@
+ $(QUIET)$(MKDIR) -p $(@D)
+ $(QUIET)$(VBOX_XSLTPROC) --stringparam KBUILD_HOST $(KBUILD_HOST) \
+ $(if $(VBOX_WITH_SDS),-stringparam g_fVBoxWithSDS yes,) \
+ -o $@ $(VBOX_PATH_MAIN_SRC)/idl/apiwrap-server-filelist.xsl $(VBOX_XIDL_FILE)
+ $(QUIET)$(CP) --changed -fv $@ $(VBoxAPIWrap_VBOX_KMK_FILE)
+
+# Generate the header files for the wrappers.
+$(call KB_FN_AUTO_CMD_DEPS,$(VBoxAPIWrap_0_OUTDIR)/apiwrappers-headers)
+$(VBoxAPIWrap_0_OUTDIR)/apiwrappers-headers \
++| $(VBOX_MAIN_APIWRAPPER_GEN_HDRS): \
+ $(VBOX_XIDL_FILE) \
+ $(VBOX_PATH_MAIN_SRC)/idl/typemap-shared.inc.xsl \
+ $(VBoxAPIWrap_VBOX_XSLT) \
+ $(VBOX_FILESPLIT) \
+ | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(call MSG_L1,Generating C++ Server API wrapper header files from XIDL)
+ $(QUIET)$(RM) -f -- $(filter-out $(VBOX_MAIN_APIWRAPPER_GEN_HDRS), $(wildcard $(VBoxAPIWrap_0_OUTDIR)/*.h))
+ $(QUIET)$(VBOX_XSLTPROC) --stringparam KBUILD_HOST $(KBUILD_HOST) \
+ --stringparam generating "headers" \
+ -o $@ $(VBoxAPIWrap_VBOX_XSLT) $(VBOX_XIDL_FILE)
+ $(QUIET)$(VBOX_FILESPLIT) $@ $(VBoxAPIWrap_0_OUTDIR)
+
+# Generate the DTrace probes for the wrappers and combine them with handcoded probes.
+$(call KB_FN_AUTO_CMD_DEPS,$(VBoxAPIWrap_0_OUTDIR)/VBoxAPI.d.ts)
+$(VBoxAPIWrap_0_OUTDIR)/VBoxAPI.d.ts \
++| $(VBoxAPIWrap_0_OUTDIR)/VBoxAPI.d: \
+ $(VBOX_XIDL_FILE) \
+ $(VBOX_PATH_MAIN_SRC)/idl/typemap-shared.inc.xsl \
+ $(VBoxAPIWrap_VBOX_XSLT) \
+ $(VBOX_FILESPLIT) \
+ | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(call MSG_L1,Generating DTrace Provider for the VBox API (from XIDL))
+ $(QUIET)$(RM) -f -- "$@" "$@.tmp"
+ $(QUIET)$(VBOX_XSLTPROC) --stringparam KBUILD_HOST $(KBUILD_HOST) \
+ --stringparam generating "dtrace-probes" \
+ -o "$@" $(VBoxAPIWrap_VBOX_XSLT) $(VBOX_XIDL_FILE)
+ $(QUIET)$(SED) -e '' -o "$@.tmp" \
+ "$(VBOX_PATH_MAIN_SRC)/src-all/VBoxAPI-start$(if-expr "$(VBOX_HOST_DTRACE_VERSION)" == "dtrace: Sun D 1.6.2",-alternative,).d" \
+ "$@" \
+ "$(VBOX_PATH_MAIN_SRC)/src-all/VBoxAPI-end$(if-expr "$(VBOX_HOST_DTRACE_VERSION)" == "dtrace: Sun D 1.6.2",-alternative,).d"
+ $(QUIET)$(MV) -- "$@.tmp" "$@"
+ $(QUIET)$(CP) --changed -fv -- "$@" "$(VBoxAPIWrap_0_OUTDIR)/VBoxAPI.d"
+
+# Generate the wrapper source files - split in two due to long processing time
+$(call KB_FN_AUTO_CMD_DEPS,$(VBoxAPIWrap_0_OUTDIR)/apiwrappers-sources-even)
+$(VBoxAPIWrap_0_OUTDIR)/apiwrappers-sources-even \
++| $(VBOX_MAIN_APIWRAPPER_GEN_SRCS_EVEN): \
+ $(VBOX_XIDL_FILE) \
+ $(VBOX_PATH_MAIN_SRC)/idl/typemap-shared.inc.xsl \
+ $(VBoxAPIWrap_VBOX_XSLT) \
+ $(VBOX_FILESPLIT) \
+ | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(call MSG_L1,Generating C++ Server API wrapper files from XIDL - part 1)
+ $(QUIET)$(RM) -f -- $(filter-out $(VBOX_MAIN_APIWRAPPER_GEN_SRCS), $(wildcard $(VBoxAPIWrap_0_OUTDIR)/*.cpp))
+ $(QUIET)$(VBOX_XSLTPROC) --stringparam KBUILD_HOST $(KBUILD_HOST) \
+ --stringparam generating "sources" \
+ --param reminder 0 \
+ -o $@ $(VBoxAPIWrap_VBOX_XSLT) $(VBOX_XIDL_FILE)
+ $(QUIET)$(VBOX_FILESPLIT) $@ $(VBoxAPIWrap_0_OUTDIR)
+
+$(call KB_FN_AUTO_CMD_DEPS,$(VBoxAPIWrap_0_OUTDIR)/apiwrappers-sources-odd)
+$(VBoxAPIWrap_0_OUTDIR)/apiwrappers-sources-odd \
++| $(VBOX_MAIN_APIWRAPPER_GEN_SRCS_ODD): \
+ $(VBOX_XIDL_FILE) \
+ $(VBOX_PATH_MAIN_SRC)/idl/typemap-shared.inc.xsl \
+ $(VBoxAPIWrap_VBOX_XSLT) \
+ $(VBOX_FILESPLIT) \
+ | $$(dir $$@)
+ $(call KB_FN_AUTO_CMD_DEPS_COMMANDS)
+ $(call MSG_L1,Generating C++ Server API wrapper files from XIDL - part 2)
+ $(QUIET)$(RM) -f -- $(filter-out $(VBOX_MAIN_APIWRAPPER_GEN_SRCS), $(wildcard $(VBoxAPIWrap_0_OUTDIR)/*.cpp))
+ $(QUIET)$(VBOX_XSLTPROC) --stringparam KBUILD_HOST $(KBUILD_HOST) \
+ --stringparam generating "sources" \
+ --param reminder 1 \
+ -o $@ $(VBoxAPIWrap_VBOX_XSLT) $(VBOX_XIDL_FILE)
+ $(QUIET)$(VBOX_FILESPLIT) $@ $(VBoxAPIWrap_0_OUTDIR)
+
+testapiwrappers:: \
+ $(VBoxAPIWrap_0_OUTDIR)/apiwrappers-headers \
+ $(VBoxAPIWrap_0_OUTDIR)/VBoxAPI.d.ts \
+ $(VBoxAPIWrap_0_OUTDIR)/apiwrappers-sources-odd \
+ $(VBoxAPIWrap_0_OUTDIR)/apiwrappers-sources-even
diff --git a/src/VBox/Main/UnattendedTemplates/Makefile.kmk b/src/VBox/Main/UnattendedTemplates/Makefile.kmk
new file mode 100644
index 00000000..0530621b
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/Makefile.kmk
@@ -0,0 +1,74 @@
+# $Id: Makefile.kmk $
+## @file
+# Top-level makefile for src/VBox/Main/UnattendedTemplates.
+#
+
+#
+# Copyright (C) 2017-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+ifdef VBOX_WITH_UNATTENDED
+
+ # Note! When updating the source list here, VBOX_UNATTENDED_TEMPLATES in
+ # ../../Installer/Config.kmk must be updated too!
+ INSTALLS += VBoxUnattendedTemplates
+ VBoxUnattendedTemplates_INST = $(INST_UNATTENDED_TEMPLATES)
+ VBoxUnattendedTemplates_MODE = a+r,u+w
+ VBoxUnattendedTemplates_SOURCES = \
+ debian_preseed.cfg \
+ ubuntu_preseed.cfg \
+ rhel3_ks.cfg \
+ rhel4_ks.cfg \
+ rhel5_ks.cfg \
+ redhat67_ks.cfg \
+ ol_ks.cfg \
+ fedora_ks.cfg \
+ suse_autoinstall.xml \
+ os2_response_files.rsp \
+ win_nt5_unattended.sif \
+ win_nt6_unattended.xml \
+ \
+ debian_postinstall.sh \
+ redhat_postinstall.sh \
+ ol_postinstall.sh \
+ os2_cid_install.cmd \
+ win_postinstall.cmd \
+ freebsd_installer.cfg \
+ freebsd_postinstall.sh
+
+ ifndef VBOX_WITH_OPEN_WATCOM
+ VBoxUnattendedTemplates_SOURCES += \
+ os2_util.exe
+ endif
+
+ ifndef VBOX_OSE
+ VBoxUnattendedTemplates_SOURCES += \
+ lgw_ks.cfg \
+ lgw_postinstall.sh
+ endif
+
+endif # VBOX_WITH_UNATTENDED
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Main/UnattendedTemplates/debian_postinstall.sh b/src/VBox/Main/UnattendedTemplates/debian_postinstall.sh
new file mode 100755
index 00000000..7ca7cf34
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/debian_postinstall.sh
@@ -0,0 +1,331 @@
+#!/bin/bash
+## @file
+# Post installation script template for debian-like distros.
+#
+# Note! This script expects to be running w/o chroot.
+# Note! When using ubiquity, this is run after installation logs have
+# been copied to /var/log/installation.
+#
+
+#
+# Copyright (C) 2017-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+#
+# Globals.
+#
+MY_TARGET="/target"
+MY_LOGFILE="${MY_TARGET}/var/log/vboxpostinstall.log"
+MY_CHROOT_CDROM="/cdrom"
+MY_CDROM_NOCHROOT="/cdrom"
+MY_EXITCODE=0
+MY_DEBUG="" # "yes"
+
+@@VBOX_COND_HAS_PROXY@@
+PROXY="@@VBOX_INSERT_PROXY@@"
+export http_proxy="${PROXY}"
+export https_proxy="${PROXY}"
+echo "HTTP proxy is ${http_proxy}" | tee -a "${MY_LOGFILE}"
+echo "HTTPS proxy is ${https_proxy}" | tee -a "${MY_LOGFILE}"
+@@VBOX_COND_END@@
+
+#
+# Do we need to exec using target bash? If so, we must do that early
+# or ash will bark 'bad substitution' and fail.
+#
+if [ "$1" = "--need-target-bash" ]; then
+ # Try figure out which directories we might need in the library path.
+ if [ -z "${LD_LIBRARY_PATH}" ]; then
+ LD_LIBRARY_PATH="${MY_TARGET}/lib"
+ fi
+ for x in \
+ ${MY_TARGET}/lib \
+ ${MY_TARGET}/usr/lib \
+ ${MY_TARGET}/lib/*linux-gnu/ \
+ ${MY_TARGET}/lib32/ \
+ ${MY_TARGET}/lib64/ \
+ ${MY_TARGET}/usr/lib/*linux-gnu/ \
+ ${MY_TARGET}/usr/lib32/ \
+ ${MY_TARGET}/usr/lib64/ \
+ ;
+ do
+ if [ -e "$x" ]; then LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${x}"; fi;
+ done
+ export LD_LIBRARY_PATH
+
+ # Append target bin directories to the PATH as busybox may not have tee.
+ PATH="${PATH}:${MY_TARGET}/bin:${MY_TARGET}/usr/bin:${MY_TARGET}/sbin:${MY_TARGET}/usr/sbin"
+ export PATH
+
+ # Drop the --need-target-bash argument and re-exec.
+ shift
+ echo "******************************************************************************" >> "${MY_LOGFILE}"
+ echo "** Relaunching using ${MY_TARGET}/bin/bash $0 $*" >> "${MY_LOGFILE}"
+ echo "** LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> "${MY_LOGFILE}"
+ echo "** PATH=${PATH}" >> "${MY_LOGFILE}"
+ exec "${MY_TARGET}/bin/bash" "$0" "$@"
+fi
+
+
+#
+# Commands.
+#
+
+# Logs execution of a command.
+log_command()
+{
+ echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+ echo "** Date: `date -R`" >> "${MY_LOGFILE}"
+ echo "** Executing: $*" >> "${MY_LOGFILE}"
+ "$@" 2>&1 | tee -a "${MY_LOGFILE}"
+ MY_TMP_EXITCODE="${PIPESTATUS[0]}"
+ if [ "${MY_TMP_EXITCODE}" != "0" ]; then
+ if [ "${MY_TMP_EXITCODE}" != "${MY_IGNORE_EXITCODE}" ]; then
+ echo "** exit code: ${MY_TMP_EXITCODE}" | tee -a "${MY_LOGFILE}"
+ MY_EXITCODE=1;
+ else
+ echo "** exit code: ${MY_TMP_EXITCODE} (ignored)" | tee -a "${MY_LOGFILE}"
+ fi
+ fi
+}
+
+# Logs execution of a command inside the target.
+log_command_in_target()
+{
+ #
+ # We should be using in-target here, however we don't get any stderr output
+ # from it because of log-output. We can get stdout by --pass-stdout, but
+ # that's not helpful for failures.
+ #
+ # So, we try do the chroot prepping that in-target does at the start of the
+ # script (see below) and just use chroot here.
+ #
+ log_command chroot "${MY_TARGET}" "$@"
+ # log_command in-target --pass-stdout "$@" # No stderr output... :-(
+}
+
+# Checks if $1 is a command on the PATH inside the target jail.
+chroot_which()
+{
+ for dir in /bin /usr/bin /sbin /usr/sbin;
+ do
+ if [ -x "${MY_TARGET}${dir}/$1" ]; then
+ return 0;
+ fi
+ done
+ return 1;
+}
+
+#
+# Log header.
+#
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+echo "** VirtualBox Unattended Guest Installation - Late installation actions" >> "${MY_LOGFILE}"
+echo "** Date: `date -R`" >> "${MY_LOGFILE}"
+echo "** Started: $0 $*" >> "${MY_LOGFILE}"
+
+
+#
+# Setup the target jail ourselves since in-target steals all the output.
+#
+if [ -f /lib/chroot-setup.sh ]; then
+ MY_HAVE_CHROOT_SETUP="yes"
+ . /lib/chroot-setup.sh
+ if chroot_setup; then
+ echo "** chroot_setup: done" | tee -a "${MY_LOGFILE}"
+ else
+ echo "** chroot_setup: failed $?" | tee -a "${MY_LOGFILE}"
+ fi
+else
+ MY_HAVE_CHROOT_SETUP=""
+fi
+
+
+#
+# We want the ISO available inside the target jail.
+#
+if [ -d "${MY_TARGET}${MY_CHROOT_CDROM}" ]; then
+ MY_RMDIR_TARGET_CDROM=
+else
+ MY_RMDIR_TARGET_CDROM="yes"
+ log_command mkdir -p ${MY_TARGET}${MY_CHROOT_CDROM}
+fi
+
+if [ -f "${MY_TARGET}${MY_CHROOT_CDROM}/vboxpostinstall.sh" ]; then
+ MY_UNMOUNT_TARGET_CDROM=
+ echo "** binding cdrom into jail: already done" | tee -a "${MY_LOGFILE}"
+else
+ MY_UNMOUNT_TARGET_CDROM="yes"
+ log_command mount -o bind "${MY_CDROM_NOCHROOT}" "${MY_TARGET}${MY_CHROOT_CDROM}"
+ if [ -f "${MY_TARGET}${MY_CHROOT_CDROM}/vboxpostinstall.sh" ]; then
+ echo "** binding cdrom into jail: success" | tee -a "${MY_LOGFILE}"
+ else
+ echo "** binding cdrom into jail: failed" | tee -a "${MY_LOGFILE}"
+ fi
+ if [ "${MY_DEBUG}" = "yes" ]; then
+ log_command find "${MY_TARGET}${MY_CHROOT_CDROM}"
+ fi
+fi
+
+
+#
+# Debug
+#
+if [ "${MY_DEBUG}" = "yes" ]; then
+ log_command id
+ log_command ps
+ log_command ps auxwwwf
+ log_command env
+ log_command df
+ log_command mount
+ log_command_in_target df
+ log_command_in_target mount
+ #log_command find /
+ MY_EXITCODE=0
+fi
+
+
+#
+# Packages needed for GAs.
+#
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing packages for building kernel modules...' | tee -a "${MY_LOGFILE}"
+log_command_in_target apt-get -y install build-essential
+log_command_in_target apt-get -y install linux-headers-$(uname -r)
+
+
+#
+# GAs
+#
+@@VBOX_COND_IS_INSTALLING_ADDITIONS@@
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing VirtualBox Guest Additions...' | tee -a "${MY_LOGFILE}"
+MY_IGNORE_EXITCODE=2 # returned if modules already loaded and reboot required.
+log_command_in_target /bin/bash "${MY_CHROOT_CDROM}/vboxadditions/VBoxLinuxAdditions.run" --nox11
+log_command_in_target /bin/bash -c "udevadm control --reload-rules" # GAs doesn't yet do this.
+log_command_in_target /bin/bash -c "udevadm trigger" # (ditto)
+MY_IGNORE_EXITCODE=
+log_command_in_target usermod -a -G vboxsf "@@VBOX_INSERT_USER_LOGIN@@"
+@@VBOX_COND_END@@
+
+
+#
+# Test Execution Service.
+#
+@@VBOX_COND_IS_INSTALLING_TEST_EXEC_SERVICE@@
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing Test Execution Service...' | tee -a "${MY_LOGFILE}"
+log_command_in_target test "${MY_CHROOT_CDROM}/vboxvalidationkit/linux/@@VBOX_INSERT_OS_ARCH@@/TestExecService"
+log_command mkdir -p "${MY_TARGET}/opt/validationkit" "${MY_TARGET}/media/cdrom"
+log_command cp -R ${MY_CDROM_NOCHROOT}/vboxvalidationkit/* "${MY_TARGET}/opt/validationkit/"
+log_command chmod -R u+rw,a+xr "${MY_TARGET}/opt/validationkit/"
+if [ -e "${MY_TARGET}/usr/bin/chcon" -o -e "${MY_TARGET}/bin/chcon" -o -e "${MY_TARGET}/usr/sbin/chcon" -o -e "${MY_TARGET}/sbin/chcon" ]; then
+ MY_IGNORE_EXITCODE=1
+ log_command_in_target chcon -R -t usr_t "/opt/validationkit/"
+ MY_IGNORE_EXITCODE=
+fi
+
+# systemd service config:
+MY_UNIT_PATH="${MY_TARGET}/lib/systemd/system"
+test -d "${MY_TARGET}/usr/lib/systemd/system" && MY_UNIT_PATH="${MY_TARGET}/usr/lib/systemd/system"
+if [ -d "${MY_UNIT_PATH}" ]; then
+ log_command cp "${MY_TARGET}/opt/validationkit/linux/vboxtxs.service" "${MY_UNIT_PATH}/vboxtxs.service"
+ log_command chmod 644 "${MY_UNIT_PATH}/vboxtxs.service"
+ log_command_in_target systemctl -q enable vboxtxs
+
+# System V like:
+elif [ -e "${MY_TARGET}/etc/init.d/" ]; then
+
+ # Install the script. On rhel6 scripts are under /etc/rc.d/ with /etc/init.d and /etc/rc?.d being symlinks.
+ if [ -d "${MY_TARGET}/etc/rc.d/init.d/" ]; then
+ MY_INIT_D_PARENT_PATH="${MY_TARGET}/etc/rc.d"
+ log_command ln -s "../../../opt/validationkit/linux/vboxtxs" "${MY_INIT_D_PARENT_PATH}/init.d/"
+ else
+ MY_INIT_D_PARENT_PATH="${MY_TARGET}/etc"
+ log_command ln -s "../../opt/validationkit/linux/vboxtxs" "${MY_INIT_D_PARENT_PATH}/init.d/"
+ fi
+
+ # Use runlevel management script if found.
+ if chroot_which chkconfig; then # Redhat based sysvinit systems
+ log_command_in_target chkconfig --add vboxtxs
+ elif chroot_which insserv; then # SUSE-based sysvinit systems
+ log_command_in_target insserv vboxtxs
+ elif chroot_which update-rc.d; then # Debian/Ubuntu-based systems
+ log_command_in_target update-rc.d vboxtxs defaults
+ elif chroot_which rc-update; then # Gentoo Linux
+ log_command_in_target rc-update add vboxtxs default
+ # Fall back on hardcoded symlinking.
+ else
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc0.d/K65vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc1.d/K65vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc6.d/K65vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc2.d/S35vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc3.d/S35vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc4.d/S35vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc5.d/S35vboxtxs"
+ fi
+else
+ echo "** error: Unknown init script system." | tee -a "${MY_LOGFILE}"
+fi
+
+@@VBOX_COND_END@@
+
+#
+# Run user command.
+#
+@@VBOX_COND_HAS_POST_INSTALL_COMMAND@@
+echo '** Running custom user command ...' | tee -a "${MY_LOGFILE}"
+log_command @@VBOX_INSERT_POST_INSTALL_COMMAND@@
+@@VBOX_COND_END@@
+
+
+#
+# Unmount the cdrom if we bound it and clean up the chroot if we set it up.
+#
+if [ -n "${MY_UNMOUNT_TARGET_CDROM}" ]; then
+ echo "** unbinding cdrom from jail..." | tee -a "${MY_LOGFILE}"
+ log_command umount "${MY_TARGET}${MY_CHROOT_CDROM}"
+fi
+
+if [ -n "${MY_RMDIR_TARGET_CDROM}" ]; then
+ log_command rmdir "${MY_TARGET}${MY_CHROOT_CDROM}"
+fi
+
+if [ -n "${MY_HAVE_CHROOT_SETUP}" ]; then
+ if chroot_cleanup; then
+ echo "** chroot_cleanup: done" | tee -a "${MY_LOGFILE}"
+ else
+ echo "** chroot_cleanup: failed $?" | tee -a "${MY_LOGFILE}"
+ fi
+fi
+
+
+#
+# Log footer.
+#
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+echo "** Date: `date -R`" >> "${MY_LOGFILE}"
+echo "** Final exit code: ${MY_EXITCODE}" >> "${MY_LOGFILE}"
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+
+exit ${MY_EXITCODE}
+
diff --git a/src/VBox/Main/UnattendedTemplates/debian_preseed.cfg b/src/VBox/Main/UnattendedTemplates/debian_preseed.cfg
new file mode 100644
index 00000000..5d5bc900
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/debian_preseed.cfg
@@ -0,0 +1,86 @@
+### Partitioning
+d-i partman-auto/disk string /dev/sda
+d-i partman-auto/method string regular
+d-i partman-lvm/device_remove_lvm boolean true
+d-i partman-md/device_remove_md boolean true
+d-i partman-auto/choose_recipe select atomic
+
+# This makes partman automatically partition without confirmation
+d-i partman-partitioning/confirm_write_new_label boolean true
+d-i partman/choose_partition select finish
+d-i partman/confirm boolean true
+d-i partman/confirm_nooverwrite boolean true
+
+# Locale
+d-i debian-installer/locale string @@VBOX_INSERT_LOCALE@@
+d-i console-setup/ask_detect boolean false
+d-i console-setup/layoutcode string us
+d-i keyboard-configuration/xkb-keymap select us
+
+# Network
+d-i netcfg/get_hostname string @@VBOX_INSERT_HOSTNAME_WITHOUT_DOMAIN@@
+d-i netcfg/get_domain string @@VBOX_INSERT_HOSTNAME_DOMAIN@@
+d-i netcfg/choose_interface select auto
+
+# Clock
+@@VBOX_COND_IS_RTC_USING_UTC@@
+d-i clock-setup/utc-auto boolean true
+d-i clock-setup/utc boolean true
+@@VBOX_COND_END@@
+@@VBOX_COND_IS_NOT_RTC_USING_UTC@@
+d-i clock-setup/utc-auto boolean false
+d-i clock-setup/utc boolean false
+@@VBOX_COND_END@@
+d-i time/zone string @@VBOX_INSERT_TIME_ZONE_UX@@
+@@VBOX_COND_IS_INSTALLING_ADDITIONS@@d-i clock-setup/ntp boolean false@@VBOX_COND_END@@
+@@VBOX_COND_IS_NOT_INSTALLING_ADDITIONS@@d-i clock-setup/ntp boolean true@@VBOX_COND_END@@
+
+# Packages, Mirrors, Image
+d-i base-installer/kernel/override-image string linux-server
+d-i base-installer/kernel/override-image string linux-image-amd64
+d-i pkgsel/install-language-support boolean false
+
+@@VBOX_COND_AVOID_UPDATES_OVER_NETWORK@@
+d-i apt-setup/use_mirror boolean false
+d-i netcfg/no_default_route true
+d-i netcfg/get_nameservers ""
+@@VBOX_COND_END@@
+
+@@VBOX_COND_IS_NOT_MINIMAL_INSTALLATION@@
+d-i apt-setup/restricted boolean true
+d-i apt-setup/universe boolean true
+@@VBOX_COND_END@@@@VBOX_COND_IS_MINIMAL_INSTALLATION@@
+tasksel tasksel/first multiselect minimal
+d-i pkgsel/include string openssh-server
+d-i pkgsel/upgrade select none
+@@VBOX_COND_END@@
+
+# Users
+d-i passwd/user-fullname string @@VBOX_INSERT_USER_FULL_NAME@@
+d-i passwd/username string @@VBOX_INSERT_USER_LOGIN@@
+d-i passwd/user-password password @@VBOX_INSERT_USER_PASSWORD@@
+d-i passwd/user-password-again password @@VBOX_INSERT_USER_PASSWORD@@
+d-i passwd/root-login boolean true
+d-i passwd/root-password password @@VBOX_INSERT_ROOT_PASSWORD@@
+d-i passwd/root-password-again password @@VBOX_INSERT_ROOT_PASSWORD@@
+d-i user-setup/allow-password-weak boolean true
+d-i passwd/user-default-groups string admin
+
+# Grub
+d-i grub-installer/grub2_instead_of_grub_legacy boolean true
+d-i grub-installer/only_debian boolean true
+
+# Due notably to potential USB sticks, the location of the MBR can not be
+# determined safely in general, so this needs to be specified:
+#d-i grub-installer/bootdev string /dev/sda
+# To install to the first device (assuming it is not a USB stick):
+d-i grub-installer/bootdev string default
+
+d-i finish-install/reboot_in_progress note
+
+# Custom Commands.
+# Note! Debian netboot images use busybox, so no bash.
+# Tell script to use target bash.
+d-i preseed/late_command string cp /cdrom/vboxpostinstall.sh /target/root/vboxpostinstall.sh \
+ && chmod +x /target/root/vboxpostinstall.sh \
+ && /bin/sh /target/root/vboxpostinstall.sh --need-target-bash --preseed-late-command
diff --git a/src/VBox/Main/UnattendedTemplates/fedora_ks.cfg b/src/VBox/Main/UnattendedTemplates/fedora_ks.cfg
new file mode 100644
index 00000000..c4c44c63
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/fedora_ks.cfg
@@ -0,0 +1,90 @@
+#platform=x86, AMD64, or Intel EM64T
+#version=DEVEL
+
+# Firewall configuration
+firewall --disabled
+
+# Install OS instead of upgrade
+install
+
+# Use CDROM installation media
+cdrom
+
+# Root password
+rootpw --plaintext @@VBOX_INSERT_ROOT_PASSWORD_SH@@
+
+# System authorization information
+auth --useshadow --passalgo=sha512
+
+# Use text mode install
+text
+
+# System keyboard
+keyboard us
+
+# System language
+lang @@VBOX_INSERT_LOCALE@@
+
+# SELinux configuration
+selinux --enforcing
+
+# Installation logging level
+logging --level=info
+
+# System timezone
+timezone@@VBOX_COND_IS_RTC_USING_UTC@@ --utc@@VBOX_COND_END@@ @@VBOX_INSERT_TIME_ZONE_UX@@
+
+# Network information
+network --bootproto=dhcp --device=eth0 --onboot=on --hostname=@@VBOX_INSERT_HOSTNAME_FQDN_SH@@
+
+# System bootloader configuration
+bootloader --location=mbr --append="nomodeset crashkernel=auto rhgb quiet"
+zerombr
+
+# Partition clearing information
+clearpart --all --initlabel
+
+# Disk partitioning information
+part / --fstype ext4 --size 6000 --grow --asprimary
+part swap --size 1024
+
+#Initial user
+user --name=@@VBOX_INSERT_USER_LOGIN_SH@@ --password=@@VBOX_INSERT_USER_PASSWORD_SH@@
+
+# Reboot after installation
+# Note! the --eject option requires Fedora 6 or later. Doesn't seem to work tough.
+# Note! doesn't really work. Maybe related to https://bugzilla.redhat.com/show_bug.cgi?id=810553 ??
+reboot --eject
+
+# Packages. We currently ignore missing packages/groups here to keep things simpler.
+%packages --ignoremissing
+@@VBOX_COND_IS_NOT_MINIMAL_INSTALLATION@@
+@standard
+@hardware-support
+@@VBOX_COND_END@@@@VBOX_COND_IS_MINIMAL_INSTALLATION@@
+@core
+@@VBOX_COND_END@@
+
+# Prepare building the additions kernel module, try get what we can from the cdrom:
+kernel-headers
+kernel-devel
+glibc-devel
+glibc-headers
+gcc
+dkms
+make
+bzip2
+perl
+
+%end
+
+# Post install happens in a different script.
+%post --nochroot --log=/mnt/sysimage/root/ks-post.log
+mkdir -p /tmp/vboxcdrom
+mount /dev/cdrom /tmp/vboxcdrom
+cp /tmp/vboxcdrom/vboxpostinstall.sh /mnt/sysimage/root/vboxpostinstall.sh
+chmod a+x /mnt/sysimage/root/vboxpostinstall.sh
+/bin/bash /mnt/sysimage/root/vboxpostinstall.sh --fedora
+umount /tmp/vboxcdrom
+%end
+
diff --git a/src/VBox/Main/UnattendedTemplates/freebsd_installer.cfg b/src/VBox/Main/UnattendedTemplates/freebsd_installer.cfg
new file mode 100644
index 00000000..06c3b97b
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/freebsd_installer.cfg
@@ -0,0 +1,30 @@
+### Partitioning
+PARTITIONS=ada0
+
+### Distribution files to install
+DISTRIBUTIONS="kernel.txz base.txz"
+
+################userad
+# Begin post installation modifications
+################
+#!/bin/sh
+
+# Set up Networking
+sysrc ifconfig_em0=DHCP
+sysrc sshd_enable=YES
+
+# Set Time Zone
+/bin/cp /usr/share/zoneinfo/UTC /etc/localtime
+/usr/bin/touch /etc/wall_cmos_clock
+/sbin/adjkerntz -a
+/usr/sbin/ntpdate -u 0.pool.ntp.org
+
+#Set Default Root Password
+echo @@VBOX_INSERT_ROOT_PASSWORD_SH@@|pw usermod root -h 0
+
+#Add configured user
+echo @@VBOX_INSERT_USER_PASSWORD_SH@@|pw useradd -c @@VBOX_INSERT_USER_FULL_NAME@@ -n @@VBOX_INSERT_USER_LOGIN_SH@@ -m -u 1001 -G wheel -s csh -h 0
+
+#Reboot System
+reboot
+
diff --git a/src/VBox/Main/UnattendedTemplates/freebsd_postinstall.sh b/src/VBox/Main/UnattendedTemplates/freebsd_postinstall.sh
new file mode 100755
index 00000000..51c6424e
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/freebsd_postinstall.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+## @file
+# Post installation script template for FreeBSD.
+#
+
+#
+# Copyright (C) 2020-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+exit 0
+
diff --git a/src/VBox/Main/UnattendedTemplates/lgw_ks.cfg b/src/VBox/Main/UnattendedTemplates/lgw_ks.cfg
new file mode 100644
index 00000000..c45c597f
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/lgw_ks.cfg
@@ -0,0 +1,91 @@
+#platform=x86, AMD64, or Intel EM64T
+#version=DEVEL
+
+# Firewall configuration
+firewall --disabled
+
+# Install OS instead of upgrade
+install
+
+# Use DVD
+cdrom
+
+# Root password
+rootpw --plaintext @@VBOX_INSERT_ROOT_PASSWORD_SH@@
+
+# System authorization information
+auth --useshadow --passalgo=sha512
+
+# Use text mode install
+text
+
+# System keyboard
+keyboard us
+
+# System language
+lang @@VBOX_INSERT_LOCALE@@
+
+# Disable the unsupported hardware popup (vmmdev?).
+unsupported_hardware
+
+# SELinux configuration
+# selinux --enforcing
+
+# Installation logging level
+logging --level=info
+
+# System timezone
+timezone@@VBOX_COND_IS_RTC_USING_UTC@@ --utc@@VBOX_COND_END@@ @@VBOX_INSERT_TIME_ZONE_UX@@
+
+# Network information
+network --bootproto=dhcp --device=enp0s3 --onboot=on --hostname=@@VBOX_INSERT_HOSTNAME_FQDN_SH@@
+
+# System bootloader configuration
+bootloader --location=mbr --append="nomodeset crashkernel=auto rhgb quiet"
+zerombr
+
+# Partition clearing information
+clearpart --all --initlabel
+
+# Disk partitioning information
+part / --fstype ext4 --size 6000 --grow --asprimary
+part swap --size 1024
+
+#Initial user
+user --name=@@VBOX_INSERT_USER_LOGIN_SH@@ --password=@@VBOX_INSERT_USER_PASSWORD_SH@@
+
+# Shut down after installation
+poweroff
+
+# Packages. We currently ignore missing packages/groups here to keep things simpler.
+%packages --ignoremissing
+@base
+@core
+
+# Prepare building the additions kernel module, try get what we can from the cdrom as it may be impossible
+# to install anything from the post script:
+kernel-headers
+kernel-devel
+glibc-devel
+glibc-headers
+gcc
+dkms
+make
+bzip2
+perl
+
+%end
+
+# Post install happens in a different script.
+# Note! We mount the CDROM explictily here since the location differs between fedora 26 to rhel5
+# and apparently there isn't any way to be certain that anaconda didn't unmount it already.
+%post --nochroot --log=/mnt/sysimage/root/ks-post.log
+df -h
+mkdir -p /tmp/vboxcdrom
+mount /dev/cdrom /tmp/vboxcdrom
+cp /tmp/vboxcdrom/vboxpostinstall.sh /mnt/sysimage/root/vboxpostinstall.sh
+chmod a+x /mnt/sysimage/root/vboxpostinstall.sh
+/bin/bash /mnt/sysimage/root/vboxpostinstall.sh --rhel
+umount /tmp/vboxcdrom
+%end
+
diff --git a/src/VBox/Main/UnattendedTemplates/lgw_postinstall.sh b/src/VBox/Main/UnattendedTemplates/lgw_postinstall.sh
new file mode 100755
index 00000000..6348f581
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/lgw_postinstall.sh
@@ -0,0 +1,524 @@
+#!/bin/bash
+## @file
+# Post installation script template for local gateway image.
+#
+# Note! This script expects to be running chrooted (inside new sytem).
+#
+
+#
+# Copyright (C) 2020-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+#
+# Globals.
+#
+MY_TARGET="/mnt/sysimage"
+MY_LOGFILE="${MY_TARGET}/var/log/vboxpostinstall.log"
+MY_CHROOT_CDROM="/cdrom"
+MY_CDROM_NOCHROOT="/tmp/vboxcdrom"
+MY_EXITCODE=0
+MY_DEBUG="" # "yes"
+
+@@VBOX_COND_HAS_PROXY@@
+PROXY="@@VBOX_INSERT_PROXY@@"
+export http_proxy="${PROXY}"
+export https_proxy="${PROXY}"
+echo "HTTP proxy is ${http_proxy}" | tee -a "${MY_LOGFILE}"
+echo "HTTPS proxy is ${https_proxy}" | tee -a "${MY_LOGFILE}"
+@@VBOX_COND_END@@
+
+#
+# Do we need to exec using target bash? If so, we must do that early
+# or ash will bark 'bad substitution' and fail.
+#
+if [ "$1" = "--need-target-bash" ]; then
+ # Try figure out which directories we might need in the library path.
+ if [ -z "${LD_LIBRARY_PATH}" ]; then
+ LD_LIBRARY_PATH="${MY_TARGET}/lib"
+ fi
+ for x in \
+ ${MY_TARGET}/lib \
+ ${MY_TARGET}/usr/lib \
+ ${MY_TARGET}/lib/*linux-gnu/ \
+ ${MY_TARGET}/lib32/ \
+ ${MY_TARGET}/lib64/ \
+ ${MY_TARGET}/usr/lib/*linux-gnu/ \
+ ${MY_TARGET}/usr/lib32/ \
+ ${MY_TARGET}/usr/lib64/ \
+ ;
+ do
+ if [ -e "$x" ]; then LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${x}"; fi;
+ done
+ export LD_LIBRARY_PATH
+
+ # Append target bin directories to the PATH as busybox may not have tee.
+ PATH="${PATH}:${MY_TARGET}/bin:${MY_TARGET}/usr/bin:${MY_TARGET}/sbin:${MY_TARGET}/usr/sbin"
+ export PATH
+
+ # Drop the --need-target-bash argument and re-exec.
+ shift
+ echo "******************************************************************************" >> "${MY_LOGFILE}"
+ echo "** Relaunching using ${MY_TARGET}/bin/bash $0 $*" >> "${MY_LOGFILE}"
+ echo "** LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> "${MY_LOGFILE}"
+ echo "** PATH=${PATH}" >> "${MY_LOGFILE}"
+ exec "${MY_TARGET}/bin/bash" "$0" "$@"
+fi
+
+
+#
+# Commands.
+#
+
+# Logs execution of a command.
+log_command()
+{
+ echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+ echo "** Date: `date -R`" >> "${MY_LOGFILE}"
+ echo "** Executing: $*" >> "${MY_LOGFILE}"
+ "$@" 2>&1 | tee -a "${MY_LOGFILE}"
+ MY_TMP_EXITCODE="${PIPESTATUS[0]}" # bashism - whatever.
+ if [ "${MY_TMP_EXITCODE}" != "0" ]; then
+ if [ "${MY_TMP_EXITCODE}" != "${MY_IGNORE_EXITCODE}" ]; then
+ echo "** exit code: ${MY_TMP_EXITCODE}" | tee -a "${MY_LOGFILE}"
+ MY_EXITCODE=1;
+ else
+ echo "** exit code: ${MY_TMP_EXITCODE} (ignored)" | tee -a "${MY_LOGFILE}"
+ fi
+ fi
+}
+
+# Logs execution of a command inside the target.
+log_command_in_target()
+{
+ log_command chroot "${MY_TARGET}" "$@"
+}
+
+# Checks if $1 is a command on the PATH inside the target jail.
+chroot_which()
+{
+ for dir in /bin /usr/bin /sbin /usr/sbin;
+ do
+ if [ -x "${MY_TARGET}${dir}/$1" ]; then
+ return 0;
+ fi
+ done
+ return 1;
+}
+
+#
+# Log header.
+#
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+echo "** VirtualBox Unattended Guest Installation - Late installation actions" >> "${MY_LOGFILE}"
+echo "** Date: `date -R`" >> "${MY_LOGFILE}"
+echo "** Started: $0 $*" >> "${MY_LOGFILE}"
+
+
+#
+# We want the ISO available inside the target jail.
+#
+if [ -d "${MY_TARGET}${MY_CHROOT_CDROM}" ]; then
+ MY_RMDIR_TARGET_CDROM=
+else
+ MY_RMDIR_TARGET_CDROM="yes"
+ log_command mkdir -p ${MY_TARGET}${MY_CHROOT_CDROM}
+fi
+
+if [ -f "${MY_TARGET}${MY_CHROOT_CDROM}/vboxpostinstall.sh" ]; then
+ MY_UNMOUNT_TARGET_CDROM=
+ echo "** binding cdrom into jail: already done" | tee -a "${MY_LOGFILE}"
+else
+ MY_UNMOUNT_TARGET_CDROM="yes"
+ log_command mount -o bind "${MY_CDROM_NOCHROOT}" "${MY_TARGET}${MY_CHROOT_CDROM}"
+ if [ -f "${MY_TARGET}${MY_CHROOT_CDROM}/vboxpostinstall.sh" ]; then
+ echo "** binding cdrom into jail: success" | tee -a "${MY_LOGFILE}"
+ else
+ echo "** binding cdrom into jail: failed" | tee -a "${MY_LOGFILE}"
+ fi
+ if [ "${MY_DEBUG}" = "yes" ]; then
+ log_command find "${MY_TARGET}${MY_CHROOT_CDROM}"
+ fi
+fi
+
+
+#
+# Debug
+#
+if [ "${MY_DEBUG}" = "yes" ]; then
+ log_command id
+ log_command ps
+ log_command ps auxwwwf
+ log_command env
+ log_command df
+ log_command mount
+ log_command_in_target df
+ log_command_in_target mount
+ #log_command find /
+ MY_EXITCODE=0
+fi
+
+
+#
+# Proxy hack for yum
+#
+@@VBOX_COND_HAS_PROXY@@
+echo "" >> "${MY_TARGET}/etc/yum.conf"
+echo "proxy=@@VBOX_INSERT_PROXY@@" >> "${MY_TARGET}/etc/yum.conf"
+@@VBOX_COND_END@@
+
+#
+# Packages needed for GAs.
+#
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing packages for building kernel modules...' | tee -a "${MY_LOGFILE}"
+log_command_in_target yum -y install "kernel-devel-$(uname -r)"
+log_command_in_target yum -y install "kernel-headers-$(uname -r)"
+log_command_in_target yum -y install gcc
+log_command_in_target yum -y install binutils
+log_command_in_target yum -y install make
+log_command_in_target yum -y install dkms
+log_command_in_target yum -y install make
+log_command_in_target yum -y install bzip2
+log_command_in_target yum -y install perl
+
+
+#
+# GAs
+#
+@@VBOX_COND_IS_INSTALLING_ADDITIONS@@
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing VirtualBox Guest Additions...' | tee -a "${MY_LOGFILE}"
+MY_IGNORE_EXITCODE=2 # returned if modules already loaded and reboot required.
+log_command_in_target /bin/bash "${MY_CHROOT_CDROM}/vboxadditions/VBoxLinuxAdditions.run" --nox11
+log_command_in_target /bin/bash -c "udevadm control --reload-rules" # GAs doesn't yet do this.
+log_command_in_target /bin/bash -c "udevadm trigger" # (ditto)
+MY_IGNORE_EXITCODE=
+log_command_in_target usermod -a -G vboxsf "@@VBOX_INSERT_USER_LOGIN@@"
+@@VBOX_COND_END@@
+
+#
+# Local gateway support
+#
+log_command_in_target yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
+#log_command_in_target yum -y update
+log_command_in_target yum -y install openvpn
+log_command_in_target yum -y install connect-proxy
+log_command_in_target usermod -a -G wheel "@@VBOX_INSERT_USER_LOGIN@@"
+
+echo "** Creating ${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/cloud-bridge.conf..." | tee -a "${MY_LOGFILE}"
+cat >"${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/cloud-bridge.conf" <<'EOT'
+# port 1194
+# proto udp
+port 443
+proto tcp-server
+dev tap0
+secret static.key
+keepalive 10 120
+compress lz4-v2
+push "compress lz4-v2"
+persist-key
+persist-tun
+status /var/log/openvpn-status.log
+log-append /var/log/openvpn.log
+verb 3
+EOT
+
+echo "** Creating ${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/cloud-bridge.sh..." | tee -a "${MY_LOGFILE}"
+cat >"${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/cloud-bridge.sh" <<'EOT'
+# Initialize variables
+br="br0"
+tap="tap0"
+vnic1=$1
+vnic2=$2
+vnic2_gw=$3
+target_mac=$4
+
+# Install openvpn if it is missing
+if ! yum list installed openvpn; then
+ sudo yum -y install openvpn
+fi
+
+# Let openvpn traffic through Linux firewall
+#sudo iptables -I INPUT -p udp --dport 1194 -j ACCEPT
+sudo iptables -I INPUT -p tcp --dport 443 -j ACCEPT
+
+# Switch to secondary VNIC
+sudo ip route change default via $vnic2_gw dev $vnic2
+sudo ip link set dev $vnic1 down
+
+# Bring up the cloud end of the tunnel
+sudo openvpn --config cloud-bridge.conf --daemon
+
+# Use target MAC for primary VNIC
+sudo ip link set dev $vnic1 address $target_mac
+
+# Bridge tap and primary VNIC
+sudo ip link add name $br type bridge
+sudo ip link set dev $vnic1 master $br
+sudo ip link set dev $tap master $br
+
+# Bring up all interfaces
+sudo ip link set dev $tap up
+sudo ip link set dev $vnic1 up
+sudo ip link set dev $br up
+EOT
+log_command chmod +x "${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/cloud-bridge.sh"
+
+echo "** Creating ${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/local-bridge.conf..." | tee -a "${MY_LOGFILE}"
+cat >"${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/local-bridge.conf" <<'EOT'
+dev tap0
+# proto udp
+# port 1194
+proto tcp-client
+port 443
+persist-key
+persist-tun
+secret static.key
+compress lz4-v2
+log-append /var/log/openvpn.log
+verb 3
+EOT
+
+echo "** Creating ${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/local-bridge.sh..." | tee -a "${MY_LOGFILE}"
+cat >"${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/local-bridge.sh" <<'EOT'
+echo Complete command line for debugging purposes:
+echo $0 $*
+
+# Make sure we are at home
+cd ~
+
+# Initialize variables
+user=opc
+cbr_ip1=$1
+cbr_ip2=$2
+target_mac=$3
+br="br0"
+tap="tap0"
+eth="enp0s8"
+
+proxy1_ssh=""
+proxy2_ssh=""
+proxy2_vpn=""
+case $4 in
+ HTTP | HTTPS)
+ proxy1_ssh="connect-proxy -w 30 -H $5:$6 %h %p"
+ ;;
+ SOCKS | SOCKS5)
+ proxy1_ssh="connect-proxy -w 30 -S $5:$6 %h %p"
+ ;;
+ SOCKS4)
+ proxy1_ssh="connect-proxy -w 30 -4 -S $5:$6 %h %p"
+ ;;
+esac
+case $7 in
+ HTTP | HTTPS)
+ proxy2_ssh="connect-proxy -w 30 -H $8:$9 %h %p"
+ proxy2_vpn="--http-proxy $8 $9"
+ ;;
+ SOCKS | SOCKS5)
+ proxy2_ssh="connect-proxy -w 30 -S $8:$9 %h %p"
+ proxy2_vpn="--socks-proxy $8 $9"
+ ;;
+ SOCKS4)
+ proxy2_ssh="connect-proxy -w 30 -4 -S $8:$9 %h %p"
+ proxy2_vpn="--socks-proxy $8 $9"
+ ;;
+esac
+
+# Generate pre-shared secret and share it with the server, bypassing proxy if necessary
+/usr/sbin/openvpn --genkey --secret static.key
+for i in 1 2 3 4
+do
+ # Go via proxy if set
+ scp ${proxy1_ssh:+ -o ProxyCommand="$proxy1_ssh"} static.key cloud-bridge.conf cloud-bridge.sh $user@$cbr_ip1:
+ if [ $? -eq 0 ]; then break; fi; sleep 15
+ # Go direct even if proxy is set
+ scp static.key cloud-bridge.conf cloud-bridge.sh $user@$cbr_ip1:
+ if [ $? -eq 0 ]; then proxy1_ssh=""; break; fi; sleep 15
+done
+
+# Get metadata info from the cloud bridge
+for i in 1 2 3 4; do metadata=$(ssh ${proxy1_ssh:+ -o ProxyCommand="$proxy1_ssh"} $user@$cbr_ip1 sudo oci-network-config) && break || sleep 15; done
+
+# Extract primary VNIC info
+vnic1_md=`echo "$metadata"|grep -E "\sUP\s"`
+vnic1_dev=`echo $vnic1_md|cut -d ' ' -f 8`
+vnic1_mac=`echo $vnic1_md|cut -d ' ' -f 12`
+# Extract secondary VNIC info
+vnic2_md=`echo "$metadata"|grep -E "\sDOWN\s"`
+vnic2_dev=`echo $vnic2_md|cut -d ' ' -f 8`
+vnic2_gw=`echo $vnic2_md|cut -d ' ' -f 5`
+
+# Configure secondary VNIC
+ssh ${proxy1_ssh:+ -o ProxyCommand="$proxy1_ssh"} $user@$cbr_ip1 sudo oci-network-config -c
+
+# Bring up the cloud bridge
+ssh ${proxy2_ssh:+ -o ProxyCommand="$proxy2_ssh"} $user@$cbr_ip2 /bin/sh -x cloud-bridge.sh $vnic1_dev $vnic2_dev $vnic2_gw $target_mac
+if [ $? -eq 0 ]
+then
+ # SSH was able to reach cloud via proxy, establish a tunnel via proxy as well
+ sudo /usr/sbin/openvpn $proxy2_vpn --config local-bridge.conf --daemon --remote $cbr_ip2
+else
+ # Retry without proxy
+ ssh $user@$cbr_ip2 /bin/sh -x cloud-bridge.sh $vnic1_dev $vnic2_dev $vnic2_gw $target_mac
+ # Establish a tunnel to the cloud bridge
+ sudo /usr/sbin/openvpn --config local-bridge.conf --daemon --remote $cbr_ip2
+fi
+
+# Bridge the openvpn tap device and the local Ethernet interface
+sudo ip link set dev $eth down
+sudo ip link add name $br type bridge
+sudo ip link set dev $eth master $br
+sudo ip link set dev $tap master $br
+
+# Bring up all interfaces
+sudo ip link set dev $tap up
+sudo ip link set dev $eth up
+sudo ip link set dev $br up
+EOT
+log_command chmod +x "${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/local-bridge.sh"
+
+echo "** Creating ${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/.ssh/config..." | tee -a "${MY_LOGFILE}"
+log_command mkdir "${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/.ssh"
+cat >"${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/.ssh/config" <<'EOT'
+Host *
+ StrictHostKeyChecking no
+EOT
+log_command chmod 400 "${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/.ssh/config"
+
+log_command_in_target chown -R @@VBOX_INSERT_USER_LOGIN@@:@@VBOX_INSERT_USER_LOGIN@@ "/home/@@VBOX_INSERT_USER_LOGIN@@"
+
+echo '** Creating /etc/systemd/system/keygen.service...' | tee -a "${MY_LOGFILE}"
+cat >"${MY_TARGET}/etc/systemd/system/keygen.service" <<'EOT'
+[Unit]
+Description=Boot-time ssh key pair generator
+After=vboxadd.service
+
+[Service]
+ExecStart=/bin/sh -c 'su - vbox -c "cat /dev/zero | ssh-keygen -q -N \\\"\\\""'
+ExecStartPost=/bin/sh -c 'VBoxControl guestproperty set "/VirtualBox/Gateway/PublicKey" "`cat ~vbox/.ssh/id_rsa.pub`" --flags TRANSIENT'
+Type=oneshot
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
+EOT
+log_command chmod 644 "${MY_TARGET}/etc/systemd/system/keygen.service"
+log_command_in_target systemctl enable keygen.service
+
+echo '** Creating /etc/sudoers.d/020_vbox_sudo...' | tee -a "${MY_LOGFILE}"
+echo "@@VBOX_INSERT_USER_LOGIN@@ ALL=(ALL) NOPASSWD: ALL" > "${MY_TARGET}/etc/sudoers.d/020_vbox_sudo"
+
+#
+# Test Execution Service.
+#
+@@VBOX_COND_IS_INSTALLING_TEST_EXEC_SERVICE@@
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing Test Execution Service...' | tee -a "${MY_LOGFILE}"
+log_command_in_target test "${MY_CHROOT_CDROM}/vboxvalidationkit/linux/@@VBOX_INSERT_OS_ARCH@@/TestExecService"
+log_command mkdir -p "${MY_TARGET}/opt/validationkit" "${MY_TARGET}/media/cdrom"
+log_command cp -R ${MY_CDROM_NOCHROOT}/vboxvalidationkit/* "${MY_TARGET}/opt/validationkit/"
+log_command chmod -R u+rw,a+xr "${MY_TARGET}/opt/validationkit/"
+if [ -e "${MY_TARGET}/usr/bin/chcon" -o -e "${MY_TARGET}/bin/chcon" -o -e "${MY_TARGET}/usr/sbin/chcon" -o -e "${MY_TARGET}/sbin/chcon" ]; then
+ MY_IGNORE_EXITCODE=1
+ log_command_in_target chcon -R -t usr_t "/opt/validationkit/"
+ MY_IGNORE_EXITCODE=
+fi
+
+# systemd service config:
+MY_UNIT_PATH="${MY_TARGET}/lib/systemd/system"
+test -d "${MY_TARGET}/usr/lib/systemd/system" && MY_UNIT_PATH="${MY_TARGET}/usr/lib/systemd/system"
+if [ -d "${MY_UNIT_PATH}" ]; then
+ log_command cp "${MY_TARGET}/opt/validationkit/linux/vboxtxs.service" "${MY_UNIT_PATH}/vboxtxs.service"
+ log_command chmod 644 "${MY_UNIT_PATH}/vboxtxs.service"
+ log_command_in_target systemctl -q enable vboxtxs
+
+# System V like:
+elif [ -e "${MY_TARGET}/etc/init.d/" ]; then
+
+ # Install the script. On rhel6 scripts are under /etc/rc.d/ with /etc/init.d and /etc/rc?.d being symlinks.
+ if [ -d "${MY_TARGET}/etc/rc.d/init.d/" ]; then
+ MY_INIT_D_PARENT_PATH="${MY_TARGET}/etc/rc.d"
+ log_command ln -s "../../../opt/validationkit/linux/vboxtxs" "${MY_INIT_D_PARENT_PATH}/init.d/"
+ else
+ MY_INIT_D_PARENT_PATH="${MY_TARGET}/etc"
+ log_command ln -s "../../opt/validationkit/linux/vboxtxs" "${MY_INIT_D_PARENT_PATH}/init.d/"
+ fi
+
+ # Use runlevel management script if found.
+ if chroot_which chkconfig; then # Redhat based sysvinit systems
+ log_command_in_target chkconfig --add vboxtxs
+ elif chroot_which insserv; then # SUSE-based sysvinit systems
+ log_command_in_target insserv vboxtxs
+ elif chroot_which update-rc.d; then # Debian/Ubuntu-based systems
+ log_command_in_target update-rc.d vboxtxs defaults
+ elif chroot_which rc-update; then # Gentoo Linux
+ log_command_in_target rc-update add vboxtxs default
+ # Fall back on hardcoded symlinking.
+ else
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc0.d/K65vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc1.d/K65vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc6.d/K65vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc2.d/S35vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc3.d/S35vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc4.d/S35vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc5.d/S35vboxtxs"
+ fi
+else
+ echo "** error: Unknown init script system." | tee -a "${MY_LOGFILE}"
+fi
+
+@@VBOX_COND_END@@
+
+
+#
+# Run user command.
+#
+@@VBOX_COND_HAS_POST_INSTALL_COMMAND@@
+echo '** Running custom user command ...' | tee -a "${MY_LOGFILE}"
+log_command @@VBOX_INSERT_POST_INSTALL_COMMAND@@
+@@VBOX_COND_END@@
+
+
+#
+# Unmount the cdrom if we bound it and clean up the chroot if we set it up.
+#
+if [ -n "${MY_UNMOUNT_TARGET_CDROM}" ]; then
+ echo "** unbinding cdrom from jail..." | tee -a "${MY_LOGFILE}"
+ log_command umount "${MY_TARGET}${MY_CHROOT_CDROM}"
+fi
+
+if [ -n "${MY_RMDIR_TARGET_CDROM}" ]; then
+ log_command rmdir "${MY_TARGET}${MY_CHROOT_CDROM}"
+fi
+
+
+#
+# Log footer.
+#
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+echo "** Date: `date -R`" >> "${MY_LOGFILE}"
+echo "** Final exit code: ${MY_EXITCODE}" >> "${MY_LOGFILE}"
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+
+exit ${MY_EXITCODE}
+
diff --git a/src/VBox/Main/UnattendedTemplates/ol_ks.cfg b/src/VBox/Main/UnattendedTemplates/ol_ks.cfg
new file mode 100644
index 00000000..8caaf38a
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/ol_ks.cfg
@@ -0,0 +1,108 @@
+#platform=x86, AMD64, or Intel EM64T
+#version=DEVEL
+
+# Firewall configuration
+firewall --disabled
+
+# Install OS instead of upgrade
+install
+
+# Use CDROM installation media
+cdrom
+
+# Root password
+rootpw --plaintext @@VBOX_INSERT_ROOT_PASSWORD_SH@@
+
+# System authorization information
+auth --useshadow --passalgo=sha512
+
+# Use text mode install
+text
+
+# System keyboard
+keyboard us
+
+# System language
+lang @@VBOX_INSERT_LOCALE@@
+
+# Disable the unsupported hardware popup (vmmdev?).
+unsupported_hardware
+
+# SELinux configuration
+selinux --enforcing
+
+# Installation logging level
+logging --level=info
+
+# System timezone
+timezone@@VBOX_COND_IS_RTC_USING_UTC@@ --utc@@VBOX_COND_END@@ @@VBOX_INSERT_TIME_ZONE_UX@@
+
+# Network information
+network --bootproto=dhcp --device=link --onboot=on --hostname=@@VBOX_INSERT_HOSTNAME_FQDN_SH@@
+
+# System bootloader configuration
+bootloader --location=mbr --append="nomodeset crashkernel=auto rhgb quiet"
+zerombr
+
+# Partition clearing information
+clearpart --all --initlabel
+
+# Disk partitioning information
+part / --fstype ext4 --size 6000 --grow --asprimary
+part swap --size 1024
+
+#Initial user
+user --name=@@VBOX_INSERT_USER_LOGIN_SH@@ --password=@@VBOX_INSERT_USER_PASSWORD_SH@@ --plaintext
+
+# Reboot after installation
+# Note! Not sure exctly when the --eject option was added. Need to find out an make it optional.
+reboot --eject
+
+# Packages. We currently ignore missing packages/groups here to keep things simpler.
+%packages --ignoremissing
+@base
+@core
+@@VBOX_COND_IS_NOT_MINIMAL_INSTALLATION@@
+@development
+@basic-desktop
+@desktop-debugging
+@desktop-platform
+@fonts
+@general-desktop
+@graphical-admin-tools
+@remote-desktop-clients
+@x11
+@@VBOX_COND_END@@
+
+# Prepare building the additions kernel module, try get what we can from the cdrom as it may be impossible
+# to install anything from the post script:
+kernel-headers
+kernel-devel
+glibc-devel
+glibc-headers
+gcc
+@@VBOX_COND[${GUEST_OS_VERSION} vgt 8.0.0]@@
+elfutils-libelf-devel
+@@VBOX_COND_END@@
+dkms
+make
+bzip2
+perl
+
+#Package cloud-init is needed for possible automation the initial setup of virtual machine
+cloud-init
+
+%end
+
+# Post install happens in a different script.
+# Note! We mount the CDROM explictily here since the location differs between fedora 26 to rhel5
+# and apparently there isn't any way to be certain that anaconda didn't unmount it already.
+%post --nochroot --log=/mnt/sysimage/root/ks-post.log
+df -h
+mkdir -p /tmp/vboxcdrom
+mount /dev/cdrom /tmp/vboxcdrom
+cp /tmp/vboxcdrom/vboxpostinstall.sh /mnt/sysimage/root/vboxpostinstall.sh
+chmod a+x /mnt/sysimage/root/vboxpostinstall.sh
+/bin/bash /mnt/sysimage/root/vboxpostinstall.sh --rhel
+umount /tmp/vboxcdrom
+%end
diff --git a/src/VBox/Main/UnattendedTemplates/ol_postinstall.sh b/src/VBox/Main/UnattendedTemplates/ol_postinstall.sh
new file mode 100755
index 00000000..2eccf830
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/ol_postinstall.sh
@@ -0,0 +1,340 @@
+#!/bin/bash
+## @file
+# Post installation script template for redhat- distros.
+#
+# Note! This script expects to be running chrooted (inside new sytem).
+#
+
+#
+# Copyright (C) 2017-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+#
+# Globals.
+#
+MY_TARGET="/mnt/sysimage"
+MY_LOGFILE="${MY_TARGET}/var/log/vboxpostinstall.log"
+MY_CHROOT_CDROM="/cdrom"
+MY_CDROM_NOCHROOT="/tmp/vboxcdrom"
+MY_EXITCODE=0
+MY_DEBUG="" # "yes"
+GUEST_VERSION=@@VBOX_INSERT_GUEST_OS_VERSION@@
+GUEST_MAJOR_VERSION=@@VBOX_INSERT_GUEST_OS_MAJOR_VERSION@@
+
+@@VBOX_COND_HAS_PROXY@@
+PROXY="@@VBOX_INSERT_PROXY@@"
+export http_proxy="${PROXY}"
+export https_proxy="${PROXY}"
+echo "HTTP proxy is ${http_proxy}" | tee -a "${MY_LOGFILE}"
+echo "HTTPS proxy is ${https_proxy}" | tee -a "${MY_LOGFILE}"
+@@VBOX_COND_END@@
+
+#
+# Do we need to exec using target bash? If so, we must do that early
+# or ash will bark 'bad substitution' and fail.
+#
+if [ "$1" = "--need-target-bash" ]; then
+ # Try figure out which directories we might need in the library path.
+ if [ -z "${LD_LIBRARY_PATH}" ]; then
+ LD_LIBRARY_PATH="${MY_TARGET}/lib"
+ fi
+ for x in \
+ ${MY_TARGET}/lib \
+ ${MY_TARGET}/usr/lib \
+ ${MY_TARGET}/lib/*linux-gnu/ \
+ ${MY_TARGET}/lib32/ \
+ ${MY_TARGET}/lib64/ \
+ ${MY_TARGET}/usr/lib/*linux-gnu/ \
+ ${MY_TARGET}/usr/lib32/ \
+ ${MY_TARGET}/usr/lib64/ \
+ ;
+ do
+ if [ -e "$x" ]; then LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${x}"; fi;
+ done
+ export LD_LIBRARY_PATH
+
+ # Append target bin directories to the PATH as busybox may not have tee.
+ PATH="${PATH}:${MY_TARGET}/bin:${MY_TARGET}/usr/bin:${MY_TARGET}/sbin:${MY_TARGET}/usr/sbin"
+ export PATH
+
+ # Drop the --need-target-bash argument and re-exec.
+ shift
+ echo "******************************************************************************" >> "${MY_LOGFILE}"
+ echo "** Relaunching using ${MY_TARGET}/bin/bash $0 $*" >> "${MY_LOGFILE}"
+ echo "** LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> "${MY_LOGFILE}"
+ echo "** PATH=${PATH}" >> "${MY_LOGFILE}"
+ exec "${MY_TARGET}/bin/bash" "$0" "$@"
+fi
+
+
+#
+# Commands.
+#
+
+# Logs execution of a command.
+log_command()
+{
+ echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+ echo "** Date: `date -R`" >> "${MY_LOGFILE}"
+ echo "** Executing: $*" >> "${MY_LOGFILE}"
+ "$@" 2>&1 | tee -a "${MY_LOGFILE}"
+ MY_TMP_EXITCODE="${PIPESTATUS[0]}" # bashism - whatever.
+ if [ "${MY_TMP_EXITCODE}" != "0" ]; then
+ if [ "${MY_TMP_EXITCODE}" != "${MY_IGNORE_EXITCODE}" ]; then
+ echo "** exit code: ${MY_TMP_EXITCODE}" | tee -a "${MY_LOGFILE}"
+ MY_EXITCODE=1;
+ else
+ echo "** exit code: ${MY_TMP_EXITCODE} (ignored)" | tee -a "${MY_LOGFILE}"
+ fi
+ fi
+}
+
+# Logs execution of a command inside the target.
+log_command_in_target()
+{
+ log_command chroot "${MY_TARGET}" "$@"
+}
+
+# Checks if $1 is a command on the PATH inside the target jail.
+chroot_which()
+{
+ for dir in /bin /usr/bin /sbin /usr/sbin;
+ do
+ if [ -x "${MY_TARGET}${dir}/$1" ]; then
+ return 0;
+ fi
+ done
+ return 1;
+}
+
+#
+# Log header.
+#
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+echo "** VirtualBox Unattended Guest Installation - Late installation actions" >> "${MY_LOGFILE}"
+echo "** Date: `date -R`" >> "${MY_LOGFILE}"
+echo "** Started: $0 $*" >> "${MY_LOGFILE}"
+
+
+#
+# We want the ISO available inside the target jail.
+#
+if [ -d "${MY_TARGET}${MY_CHROOT_CDROM}" ]; then
+ MY_RMDIR_TARGET_CDROM=
+else
+ MY_RMDIR_TARGET_CDROM="yes"
+ log_command mkdir -p ${MY_TARGET}${MY_CHROOT_CDROM}
+fi
+
+if [ -f "${MY_TARGET}${MY_CHROOT_CDROM}/vboxpostinstall.sh" ]; then
+ MY_UNMOUNT_TARGET_CDROM=
+ echo "** binding cdrom into jail: already done" | tee -a "${MY_LOGFILE}"
+else
+ MY_UNMOUNT_TARGET_CDROM="yes"
+ log_command mount -o bind "${MY_CDROM_NOCHROOT}" "${MY_TARGET}${MY_CHROOT_CDROM}"
+ if [ -f "${MY_TARGET}${MY_CHROOT_CDROM}/vboxpostinstall.sh" ]; then
+ echo "** binding cdrom into jail: success" | tee -a "${MY_LOGFILE}"
+ else
+ echo "** binding cdrom into jail: failed" | tee -a "${MY_LOGFILE}"
+ fi
+ if [ "${MY_DEBUG}" = "yes" ]; then
+ log_command find "${MY_TARGET}${MY_CHROOT_CDROM}"
+ fi
+fi
+
+
+#
+# Debug
+#
+if [ "${MY_DEBUG}" = "yes" ]; then
+ log_command id
+ log_command ps
+ log_command ps auxwwwf
+ log_command env
+ log_command df
+ log_command mount
+ log_command_in_target df
+ log_command_in_target mount
+ #log_command find /
+ MY_EXITCODE=0
+fi
+
+
+#
+# Add EPEL repository
+#
+EPEL_REPOSITORY="https://dl.fedoraproject.org/pub/epel/epel-release-latest-${GUEST_MAJOR_VERSION}.noarch.rpm"
+log_command_in_target wget ${EPEL_REPOSITORY}
+log_command_in_target yum localinstall -y "epel-release-latest-${GUEST_MAJOR_VERSION}.noarch.rpm"
+log_command_in_target rpm -q "oraclelinux-release-el${GUEST_MAJOR_VERSION}"
+log_command_in_target yum install -y yum-utils
+log_command_in_target yum-config-manager --enable epel
+
+
+#
+# Packages needed for GAs.
+#
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Finding UEK kernel...' | tee -a "${MY_LOGFILE}"
+UEK=$(find ${MY_TARGET}/boot/ -name "vmlinuz*"| grep uek | awk -F"-" 'BEGIN{OFS="-"} \
+{ kernel=$2; for (i = 3; i <= NF; i++) { kernel=sprintf("%s-%s",kernel,$i)}} END{print kernel}')
+CURRENT_KERNEL=$(uname -r)
+
+if [ -n $UEK ]; then
+ echo "UEK kernel was found - ${UEK}" | tee -a "${MY_LOGFILE}"
+ log_command_in_target yum -y install "kernel-uek-devel-${UEK}"
+fi
+echo "Installation uses kernel ${CURRENT_KERNEL}" | tee -a "${MY_LOGFILE}"
+
+echo '** Installing packages for building kernel modules...' | tee -a "${MY_LOGFILE}"
+log_command_in_target yum -y install "kernel-devel-$(uname -r)"
+log_command_in_target yum -y install "kernel-headers-$(uname -r)"
+log_command_in_target yum -y install gcc
+log_command_in_target yum -y install binutils
+log_command_in_target yum -y install make
+@@VBOX_COND[${GUEST_OS_VERSION} vgt 8.0.0]@@
+log_command_in_target yum -y install elfutils-libelf-devel
+@@VBOX_COND_END@@
+log_command_in_target yum -y install dkms
+log_command_in_target yum -y install make
+log_command_in_target yum -y install bzip2
+log_command_in_target yum -y install perl
+
+
+#
+#Package cloud-init is needed for possible automation the initial setup of virtual machine
+#
+log_command_in_target yum -y install cloud-init
+log_command_in_target systemctl enable cloud-init-local.service
+log_command_in_target systemctl enable cloud-init.service
+log_command_in_target systemctl enable cloud-config.service
+log_command_in_target systemctl enable cloud-final.service
+
+
+#
+# GAs
+#
+@@VBOX_COND_IS_INSTALLING_ADDITIONS@@
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing VirtualBox Guest Additions...' | tee -a "${MY_LOGFILE}"
+MY_IGNORE_EXITCODE=2 # returned if modules already loaded and reboot required.
+log_command_in_target /bin/bash "${MY_CHROOT_CDROM}/vboxadditions/VBoxLinuxAdditions.run" --nox11
+log_command_in_target /bin/bash -c "udevadm control --reload-rules" # GAs doesn't yet do this.
+log_command_in_target /bin/bash -c "udevadm trigger" # (ditto)
+MY_IGNORE_EXITCODE=
+log_command_in_target usermod -a -G vboxsf "@@VBOX_INSERT_USER_LOGIN@@"
+@@VBOX_COND_END@@
+
+
+#
+# Test Execution Service.
+#
+@@VBOX_COND_IS_INSTALLING_TEST_EXEC_SERVICE@@
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing Test Execution Service...' | tee -a "${MY_LOGFILE}"
+log_command_in_target test "${MY_CHROOT_CDROM}/vboxvalidationkit/linux/@@VBOX_INSERT_OS_ARCH@@/TestExecService"
+log_command mkdir -p "${MY_TARGET}/opt/validationkit" "${MY_TARGET}/media/cdrom"
+log_command cp -R ${MY_CDROM_NOCHROOT}/vboxvalidationkit/* "${MY_TARGET}/opt/validationkit/"
+log_command chmod -R u+rw,a+xr "${MY_TARGET}/opt/validationkit/"
+if [ -e "${MY_TARGET}/usr/bin/chcon" -o -e "${MY_TARGET}/bin/chcon" -o -e "${MY_TARGET}/usr/sbin/chcon" -o -e "${MY_TARGET}/sbin/chcon" ]; then
+ MY_IGNORE_EXITCODE=1
+ log_command_in_target chcon -R -t usr_t "/opt/validationkit/"
+ MY_IGNORE_EXITCODE=
+fi
+
+# systemd service config:
+MY_UNIT_PATH="${MY_TARGET}/lib/systemd/system"
+test -d "${MY_TARGET}/usr/lib/systemd/system" && MY_UNIT_PATH="${MY_TARGET}/usr/lib/systemd/system"
+if [ -d "${MY_UNIT_PATH}" ]; then
+ log_command cp "${MY_TARGET}/opt/validationkit/linux/vboxtxs.service" "${MY_UNIT_PATH}/vboxtxs.service"
+ log_command chmod 644 "${MY_UNIT_PATH}/vboxtxs.service"
+ log_command_in_target systemctl -q enable vboxtxs
+
+# System V like:
+elif [ -e "${MY_TARGET}/etc/init.d/" ]; then
+
+ # Install the script. On rhel6 scripts are under /etc/rc.d/ with /etc/init.d and /etc/rc?.d being symlinks.
+ if [ -d "${MY_TARGET}/etc/rc.d/init.d/" ]; then
+ MY_INIT_D_PARENT_PATH="${MY_TARGET}/etc/rc.d"
+ log_command ln -s "../../../opt/validationkit/linux/vboxtxs" "${MY_INIT_D_PARENT_PATH}/init.d/"
+ else
+ MY_INIT_D_PARENT_PATH="${MY_TARGET}/etc"
+ log_command ln -s "../../opt/validationkit/linux/vboxtxs" "${MY_INIT_D_PARENT_PATH}/init.d/"
+ fi
+
+ # Use runlevel management script if found.
+ if chroot_which chkconfig; then # Redhat based sysvinit systems
+ log_command_in_target chkconfig --add vboxtxs
+ elif chroot_which insserv; then # SUSE-based sysvinit systems
+ log_command_in_target insserv vboxtxs
+ elif chroot_which update-rc.d; then # Debian/Ubuntu-based systems
+ log_command_in_target update-rc.d vboxtxs defaults
+ elif chroot_which rc-update; then # Gentoo Linux
+ log_command_in_target rc-update add vboxtxs default
+ # Fall back on hardcoded symlinking.
+ else
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc0.d/K65vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc1.d/K65vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc6.d/K65vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc2.d/S35vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc3.d/S35vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc4.d/S35vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc5.d/S35vboxtxs"
+ fi
+else
+ echo "** error: Unknown init script system." | tee -a "${MY_LOGFILE}"
+fi
+
+@@VBOX_COND_END@@
+
+
+#
+# Run user command.
+#
+@@VBOX_COND_HAS_POST_INSTALL_COMMAND@@
+echo '** Running custom user command ...' | tee -a "${MY_LOGFILE}"
+log_command @@VBOX_INSERT_POST_INSTALL_COMMAND@@
+@@VBOX_COND_END@@
+
+
+#
+# Unmount the cdrom if we bound it and clean up the chroot if we set it up.
+#
+if [ -n "${MY_UNMOUNT_TARGET_CDROM}" ]; then
+ echo "** unbinding cdrom from jail..." | tee -a "${MY_LOGFILE}"
+ log_command umount "${MY_TARGET}${MY_CHROOT_CDROM}"
+fi
+
+if [ -n "${MY_RMDIR_TARGET_CDROM}" ]; then
+ log_command rmdir "${MY_TARGET}${MY_CHROOT_CDROM}"
+fi
+
+
+#
+# Log footer.
+#
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+echo "** Date: `date -R`" >> "${MY_LOGFILE}"
+echo "** Final exit code: ${MY_EXITCODE}" >> "${MY_LOGFILE}"
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+
+exit ${MY_EXITCODE}
diff --git a/src/VBox/Main/UnattendedTemplates/os2_cid_install.cmd b/src/VBox/Main/UnattendedTemplates/os2_cid_install.cmd
new file mode 100644
index 00000000..162245aa
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/os2_cid_install.cmd
@@ -0,0 +1,498 @@
+@ECHO OFF
+REM $Id: os2_cid_install.cmd $
+REM REM @fileREM
+REM VirtualBox CID Installation - main driver script for boot CD/floppy.
+REM
+
+REM
+REM Copyright (C) 2004-2022 Oracle and/or its affiliates.
+REM
+REM This file is part of VirtualBox base platform packages, as
+REM available from https://www.virtualbox.org.
+REM
+REM This program is free software; you can redistribute it and/or
+REM modify it under the terms of the GNU General Public License
+REM as published by the Free Software Foundation, in version 3 of the
+REM License.
+REM
+REM This program is distributed in the hope that it will be useful, but
+REM WITHOUT ANY WARRANTY; without even the implied warranty of
+REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+REM General Public License for more details.
+REM
+REM You should have received a copy of the GNU General Public License
+REM along with this program; if not, see <https://www.gnu.org/licenses>.
+REM
+REM SPDX-License-Identifier: GPL-3.0-only
+REM
+
+REM Check the phase argument and jump to the right section of the file.
+if "%1" == "PHASE1" goto phase1
+if "%1" == "PHASE2" goto phase2
+if "%1" == "PHASE3" goto phase3
+@echo ** error: invalid or missing parameter. Expected PHASE1, PHASE2 or PHASE3 as the first parameter to the script.
+pause
+cmd.exe
+exit /b 1
+
+REM
+REM Phase 1 - Base system installation.
+REM
+:phase1
+SET CDROM=S:
+
+@echo on
+@echo .
+@echo Step 1.1 - Partition the disk.
+@echo .
+cd %CDROM%\os2image\disk_6
+%CDROM%
+
+lvm.exe /NEWMBR:1 && goto lvm_newmbr_ok
+@echo ** error: Writing a new MBR on disk 1 failed.
+goto lvm_failed
+:lvm_newmbr_ok
+
+@REM Depends the default drive name being "[ D1 ]". However it's cosmetical,
+@REM so we don't complain if this fails.
+lvm.exe "/SETNAME:DRIVE,[ D1 ],BootDrive"
+
+lvm.exe /CREATE:PARTITION,OS2Boot,1,1024,PRIMARY,BOOTABLE && goto lvm_create_partition_ok
+@echo ** error: Creating boot partition on disk 1 failed.
+goto lvm_failed
+:lvm_create_partition_ok
+
+lvm.exe /CREATE:VOLUME,COMPATIBILITY,BOOTOS2,C:,OS2Boot,1,OS2Boot && goto lvm_create_volume_ok
+@echo ** error: Creating boot volume on disk 1 failed.
+goto lvm_failed
+:lvm_create_volume_ok
+
+lvm.exe /SETSTARTABLE:VOLUME,OS2Boot && goto lvm_set_startable_ok
+@echo ** error: Setting boot volume on disk 1 startable failed.
+goto lvm_failed
+:lvm_set_startable_ok
+
+@REM Depending on the freespace automatically getting the name "[ FS1 ]".
+lvm.exe "/CREATE:PARTITION,Data,1,LOGICAL,NotBootable,[ FS1 ]" && goto lvm_create_data_partition_ok
+@echo ** error: Creating data partition on disk 1 failed.
+goto lvm_failed
+:lvm_create_data_partition_ok
+
+lvm.exe /CREATE:VOLUME,LVM,D:,Data,1,Data && goto lvm_create_data_volume_ok
+@echo ** error: Creating data volume on disk 1 failed.
+goto lvm_failed
+:lvm_create_data_volume_ok
+
+REM pause
+lvm.exe /QUERY
+REM CMD.EXE
+goto done_step1_1
+
+:lvm_failed
+@echo .
+@echo An LVM operation failed (see above).
+@echo The process requires a blank disk with no partitions. Starting LVM
+@echo so you can manually correct this.
+@echo .
+pause
+lvm.exe
+%CDROM%\cid\exe\os2\setboot.exe /B
+exit
+
+:done_step1_1
+
+:step1_2
+@echo .
+@echo Step 1.2 - Format the volumes.
+@echo .
+cd %CDROM%\os2image\disk_3
+%CDROM%
+
+FORMAT.COM C: /FS:HPFS /V:OS2Boot < %CDROM%\VBoxCID\YES.TXT && goto format_boot_ok
+@echo ** error: Formatting C: failed.
+pause
+:format_boot_ok
+
+FORMAT.COM D: /FS:JFS /V:Data < %CDROM%\VBoxCID\YES.TXT && goto format_data_ok
+@echo ** error: Formatting D: failed.
+pause
+:format_data_ok
+
+cd \
+
+:step1_3
+@echo .
+@echo Step 1.3 - Putting response files and CID tools on C:
+@echo .
+mkdir C:\VBoxCID
+mkdir C:\OS2
+copy %CDROM%\cid\exe\os2\*.* C:\VBoxCID
+copy %CDROM%\cid\dll\os2\*.* C:\VBoxCID
+copy %CDROM%\os2image\disk_2\inst32.dll C:\VBoxCID
+copy %CDROM%\VBoxCID\*.* C:\VBoxCID && goto copy_1_ok
+@echo ** error: Copying CID stuff from CDROM to C: failed (#1).
+pause
+:copy_1_ok
+copy %CDROM%\VBoxCID.CMD C:\VBoxCID && goto copy_2_ok
+@echo ** error: Copying CID stuff from CDROM to C: failed (#2).
+pause
+:copy_2_ok
+
+:step1_4
+@echo .
+@echo Step 1.4 - Start OS/2 CID installation.
+@echo .
+SET REMOTE_INSTALL_STATE=CAS_WARP4
+cd C:\OS2
+C:
+@REM Treat 0xfe00 as a success status. It seems to mean that a reboot is required.
+C:\VBoxCID\OS2_UTIL.EXE --as-zero 0xfe00 -- C:\VBoxCID\SEMAINT.EXE /S:%CDROM%\os2image /B:C: /L1:C:\VBoxCID\1.4.1-Maint.log /T:C:\OS2 && goto semaint_ok
+C:\VBoxCID\OS2_UTIL.EXE --file-to-backdoor C:\VBoxCID\1.4.1-Maint.log
+pause
+:semaint_ok
+REM CMD.EXE
+
+cd C:\VBoxCID
+C:
+@REM Treat 0xff02 as a success status. It seems to mean that a reboot is required.
+C:\VBoxCID\OS2_UTIL.EXE --as-zero 0xff02 -- C:\VBoxCID\SEINST.EXE /S:%CDROM%\os2image /B:C: /L1:C:\VBoxCID\1.4.2-CIDInst.log /R:C:\VBoxCID\OS2.RSP /T:A:\ && goto seinst_ok
+C:\VBoxCID\OS2_UTIL.EXE --file-to-backdoor C:\VBoxCID\1.4.2-CIDInst.log
+pause
+:seinst_ok
+REM CMD.EXE
+
+:step1_5
+@echo .
+@echo Step 1.5 - Make C: bootable.
+@echo .
+C:
+cd C:\OS2
+SYSINSTX.COM C: && goto sysinstx_ok
+pause
+:sysinstx_ok
+
+@echo Copying over patched OS2LDR from A:
+attrib -R -H -S C:\OS2LDR
+copy C:\OS2LDR C:\OS2LDR.Phase1
+del C:\OS2LDR
+copy A:\OS2LDR C:\OS2LDR && goto copy_os2ldr_ok
+pause
+:copy_os2ldr_ok
+attrib +R +H +S C:\OS2LDR
+
+@REM This copy is for the end of phase 2 as someone replaces it.
+copy A:\OS2LDR C:\VBoxCID && goto copy_os2ldr_2_ok
+pause
+:copy_os2ldr_2_ok
+attrib +r C:\VBoxCID\OS2LDR
+
+@echo Enabling Alt-F2 driver logging during boot.
+@echo > "C:\ALTF2ON.$$$"
+
+@echo Install startup.cmd for phase2.
+@echo C:\VBoxCID\OS2_UTIL.EXE --tee-to-backdoor --tee-to-file C:\VBoxCID\Phase2.log --append -- C:\OS2\CMD.EXE /C C:\VBoxCID\VBoxCID.CMD PHASE2> C:\STARTUP.CMD && goto phase2_startup_ok
+pause
+:phase2_startup_ok
+
+copy C:\CONFIG.SYS C:\VBoxCID\Phase1-end-config.sys
+
+REM now reboot.
+goto reboot
+
+
+REM
+REM Phase 2 - Install GRADD drivers (VGA is horribly slow).
+REM
+:phase2
+SET CDROM=E:
+IF EXIST "%CDROM%\VBoxCID.CMD" goto phase2_found_cdrom
+SET CDROM=D:
+IF EXIST "%CDROM%\VBoxCID.CMD" goto phase2_found_cdrom
+SET CDROM=F:
+IF EXIST "%CDROM%\VBoxCID.CMD" goto phase2_found_cdrom
+SET CDROM=G:
+IF EXIST "%CDROM%\VBoxCID.CMD" goto phase2_found_cdrom
+SET CDROM=H:
+IF EXIST "%CDROM%\VBoxCID.CMD" goto phase2_found_cdrom
+SET CDROM=S:
+IF EXIST "%CDROM%\VBoxCID.CMD" goto phase2_found_cdrom
+@echo ** error: Unable to find the CDROM drive
+pause
+CMD
+SET CDROM=E:
+:phase2_found_cdrom
+cd C:\VBoxCID
+C:
+
+@echo on
+
+:step2_1
+@echo .
+@echo Step 2.1 - Install the video driver.
+@echo .
+@REM Treat 0xfe00 as a success status. It seems to mean that a reboot is required.
+C:\VBoxCID\OS2_UTIL.EXE --as-zero 0xfe00 -- C:\OS2\INSTALL\DspInstl.EXE /PD:C:\OS2\INSTALL\GENGRADD.DSC /S:%CDROM%\OS2IMAGE /T:C: /RES:1024X768X16777216 /U && goto dspinstl_ok
+C:\VBoxCID\OS2_UTIL.EXE --file-to-backdoor C:\OS2\INSTALL\DSPINSTL.LOG
+C:\VBoxCID\OS2_UTIL.EXE --file-to-backdoor C:\OS2\INSTALL\GRADD.LOG
+pause
+:dspinstl_ok
+
+@REM TODO: Error: 1 Error getting current desktop mode
+@REM UPDATE: This is probably not working because SVGA.EXE doesn't want to play along with our graphics adapter,
+@REM so it looks like there is no simple way of changing the resolution or select a better monitor.
+call VCfgCID.CMD /L1:C:\VBoxCID\2.1-Video.log /L2:C:\VBoxCID\2.1-Video-2.log /RES:1024X768X16777216 /MON:548
+goto vcfgcid_ok
+C:\VBoxCID\OS2_UTIL.EXE --file-to-backdoor C:\VBoxCID\2.1-Video.log
+pause
+:vcfgcid_ok
+cd C:\VBoxCID
+C:
+
+:step2_2
+@echo Install startup.cmd for phase3.
+ren C:\STARTUP.CMD C:\VBoxCID\Phase2-end-startup.cmd
+copy C:\CONFIG.SYS C:\VBoxCID\Phase2-end-config.sys
+@echo C:\VBoxCID\OS2_UTIL.EXE --tee-to-backdoor --tee-to-file C:\VBoxCID\Phase3.log --append -- C:\OS2\CMD.EXE /C C:\VBoxCID\VBoxCID.CMD PHASE3> C:\STARTUP.CMD && goto phase3_startup_ok
+pause
+:phase3_startup_ok
+
+REM now reboot.
+goto reboot
+
+
+REM
+REM Phase 2 - The rest of the installation running of the base install with fast GRADD drivers.
+REM
+:phase3
+SET CDROM=E:
+IF EXIST "%CDROM%\VBoxCID.CMD" goto phase3_found_cdrom
+SET CDROM=D:
+IF EXIST "%CDROM%\VBoxCID.CMD" goto phase3_found_cdrom
+SET CDROM=F:
+IF EXIST "%CDROM%\VBoxCID.CMD" goto phase3_found_cdrom
+SET CDROM=G:
+IF EXIST "%CDROM%\VBoxCID.CMD" goto phase3_found_cdrom
+SET CDROM=H:
+IF EXIST "%CDROM%\VBoxCID.CMD" goto phase3_found_cdrom
+SET CDROM=S:
+IF EXIST "%CDROM%\VBoxCID.CMD" goto phase3_found_cdrom
+@echo ** error: Unable to find the CDROM drive
+pause
+CMD
+SET CDROM=E:
+:phase3_found_cdrom
+cd C:\VBoxCID
+C:
+
+@echo on
+
+:step3_1
+@echo .
+@echo Step 3.1 - Install multimedia.
+@echo .
+cd C:\mmtemp
+C:
+@REM Does not have any /L, /L1, or /L2 options. Fixed log file: C:\MINSTALL.LOG.
+@REM Treat 0xfe00 as a success status. It seems to mean that a reboot is required.
+C:\VBoxCID\OS2_UTIL.EXE --as-zero 0xfe00 -- MInstall.EXE /M /R:C:\VBoxCID\MMOS2.RSP && goto mmos2_ok
+C:\VBoxCID\OS2_UTIL.EXE --file-to-backdoor C:\MINSTALL.LOG
+pause
+:mmos2_ok
+cd C:\VBoxCID
+
+:step3_2
+@echo .
+@echo Step 3.2 - Install features.
+@echo .
+@REM Treat 0xfe00 as a success status. It seems to mean that a reboot is required.
+C:\VBoxCID\OS2_UTIL.EXE --as-zero 0xfe00 -- CLIFI.EXE /A:C /B:C: /S:%CDROM%\os2image\fi /R:C:\OS2\INSTALL\FIBASE.RSP /L1:C:\VBoxCID\3.2-FeatureInstaller.log /R2:C:\VBoxCID\OS2.RSP
+@REM does not exit with status 0 on success.
+goto features_ok
+C:\VBoxCID\OS2_UTIL.EXE --file-to-backdoor C:\VBoxCID\3.2-FeatureInstaller.log
+pause
+:features_ok
+
+:step3_3
+@echo .
+@echo Step 3.3 - Install MPTS.
+@echo .
+@REM If we want to use non-standard drivers like the intel ones, copy the .NIF- and
+@REM .OS2-files to C:\IBMCOM\MACS before launching the installer (needs creating first).
+@REM Note! Does not accept /L2:.
+@REM Note! Omitting /TU:C in hope that it solves the lan install failure (no netbeui configured in mpts).
+CD %CDROM%\CID\SERVER\MPTS
+%CDROM%
+@REM Treat 0xfe00 as a success status. It seems to mean that a reboot is required.
+C:\VBoxCID\OS2_UTIL.EXE --as-zero 0xfe00 -- %CDROM%\CID\SERVER\MPTS\MPTS.EXE /R:C:\VBoxCID\MPTS.RSP /S:%CDROM%\CID\SERVER\MPTS /T:C: /L1:C:\VBoxCID\3.3-Mpts.log && goto mpts_ok
+C:\VBoxCID\OS2_UTIL.EXE --file-to-backdoor C:\VBoxCID\3.3-Mpts.log
+pause
+:mpts_ok
+CD %CDROM%\
+C:
+
+:step3_4
+@echo .
+@echo Step 3.4 - Install TCP/IP.
+@echo .
+CD %CDROM%\CID\SERVER\TCPAPPS
+%CDROM%
+@REM Treat 0xfe00 as a success status. It seems to mean that a reboot is required.
+C:\VBoxCID\OS2_UTIL.EXE --as-zero 0xfe00 -- CLIFI.EXE /A:C /B:C: /S:%CDROM%\CID\SERVER\TCPAPPS\INSTALL /R:%CDROM%\CID\SERVER\TCPAPPS\INSTALL\TCPINST.RSP /L1:C:\VBoxCID\3.4-tcp.log /L2:C:\VBoxCID\3.4-tcp-2.log && goto tcp_ok
+C:\VBoxCID\OS2_UTIL.EXE --file-to-backdoor C:\VBoxCID\3.4-tcp.log
+pause
+:tcp_ok
+CD %CDROM%\
+C:
+
+CD %CDROM%\CID\SERVER\TCPAPPS\INSTALL
+%CDROM%
+C:\VBoxCID\OS2_UTIL.EXE -- %CDROM%\CID\SERVER\TCPAPPS\INSTALL\makecmd.exe C:\TCPIP en_US C:\MPTS && goto makecmd_ok
+pause
+:makecmd_ok
+cd %CDROM%\
+
+:step3_5
+@echo .
+@echo Step 3.5 - Install IBM LAN Requestor/Peer.
+@echo .
+SET REMOTE_INSTALL_STATE=CAS_OS/2 Peer
+CD %CDROM%\CID\SERVER\IBMLS
+%CDROM%
+C:\VBoxCID\OS2_UTIL.EXE -- %CDROM%\CID\SERVER\IBMLS\LANINSTR.EXE /REQ /R:C:\VBoxCID\IBMLan.rsp /L1:C:\VBoxCID\3.5-IBMLan.log /L2:C:\VBoxCID\3.5-IBMLan-2.log && goto ibmlan_ok
+C:\VBoxCID\OS2_UTIL.EXE --file-to-backdoor C:\VBoxCID\3.5-IBMLan.log
+:ibmlan_ok
+CD %CDROM%\
+C:
+
+:step3_6
+@echo .
+@echo Step 3.6 - Install Netscape.
+@echo .
+CD C:\VBoxCID
+C:
+%CDROM%
+@REM Skipping as it hangs after a "Message file not found." error. (The DPATH amendment doesn't help.) Logs give no clue.
+@REM The install works fine after the phase3 reboot. Next log message then is "NS46EXIT QLTOBMCONVERT en_US, rc=0x0000",
+@REM so maybe it is related to the LANG environment variable or Locale? Hmm. LANG seems to be set...
+goto netscape_ok
+SET DPATH=%DPATH%;C:\NETSCAPE\SIUTIL;C:\NETSCAPE\PROGRAM;
+IF "x%LANG%x" == "xx" THEN SET LANG=en_US
+C:\VBoxCID\OS2_UTIL.EXE -- %CDROM%\CID\SERVER\NETSCAPE\INSTALL.EXE /X /A:I /TU:C: /C:%CDROM%\CID\SERVER\NETSCAPE\NS46.ICF /S:%CDROM%\CID\SERVER\NETSCAPE /R:C:\VBoxCID\Netscape.RSP /L1:C:\VBoxCID\3.6-Netscape.log /L2:C:\VBoxCID\3.6-Netscape-2.log && goto netscape_ok
+C:\VBoxCID\OS2_UTIL.EXE --file-to-backdoor C:\VBoxCID\3.6-Netscape.log
+pause
+:netscape_ok
+CD %CDROM%\
+C:
+
+:step3_7
+@echo .
+@echo Step 3.7 - Install feature installer.
+@echo .
+@REM No /L2: support.
+@REM The /NN option is to make it not fail if netscape is missing.
+C:\VBoxCID\OS2_UTIL.EXE -- C:\OS2\INSTALL\WSFI\FiSetup.EXE /B:C: /S:C:\OS2\INSTALL\WSFI\FISETUP /NN /L1:C:\VBoxCID\3.7-FiSetup.log && goto fisetup_ok
+C:\VBoxCID\OS2_UTIL.EXE --file-to-backdoor C:\VBoxCID\3.7-FiSetup.log
+pause
+:fisetup_ok
+
+:step3_8
+@echo .
+@echo Step 3.8 - Install the test execution service (TXS).
+@echo .
+@@VBOX_COND_IS_INSTALLING_TEST_EXEC_SERVICE@@
+mkdir C:\VBoxValKit
+mkdir D:\TestArea
+copy %CDROM%\VBoxValidationKit\*.* C:\VBoxValKit && goto valkit_copy_1_ok
+pause
+:valkit_copy_1_ok
+copy %CDROM%\VBoxValidationKit\os2\x86\*.* C:\VBoxValKit && goto valkit_copy_2_ok
+pause
+:valkit_copy_2_ok
+@@VBOX_COND_ELSE@@
+@echo Not requested. Skipping.
+@@VBOX_COND_END@@
+
+:step3_9
+@echo .
+@echo Step 3.9 - Install final startup.cmd and copy over OS2LDR again.
+@echo .
+attrib -r -h -s C:\STARTUP.CMD
+copy C:\VBoxCID\STARTUP.CMD C:\ && goto final_startup_ok
+pause
+:final_startup_ok
+
+attrib -r -h -s C:\OS2LDR
+if not exist C:\VBoxCID\OS2LDR pause
+if not exist C:\VBoxCID\OS2LDR goto final_os2ldr_ok
+copy C:\OS2LDR C:\OS2LDR.Phase2
+del C:\OS2LDR
+copy C:\VBoxCID\OS2LDR C:\OS2LDR && goto final_os2ldr_ok
+pause
+:final_os2ldr_ok
+attrib +r +h +s C:\OS2LDR
+
+:step3_10
+@REM Putting this after placing the final Startup.cmd so we can test the
+@REM installer's ability to parse and modify it.
+@echo .
+@echo Step 3.10 - Install guest additions.
+@echo .
+@@VBOX_COND_IS_INSTALLING_ADDITIONS@@
+%CDROM%\VBoxAdditions\OS2\VBoxOs2AdditionsInstall.exe --do-install && goto addition_install_ok
+pause
+:addition_install_ok
+@@VBOX_COND_ELSE@@
+@echo Not requested. Skipping.
+@@VBOX_COND_END@@
+
+:step3_11
+@echo .
+@echo Step 3.11 - Cleanup
+@echo .
+del /N C:\*.bio
+del /N C:\*.i13
+del /N C:\*.snp
+del /N C:\CONFIG.ADD
+mkdir C:\MMTEMP 2>nul
+del /N C:\MMTEMP\*.*
+@REM This is only needed if we don't install mmos2:
+@REM for %%i in (acpadd2 azt16dd azt32dd csbsaud es1688dd es1788dd es1868dd es1888dd es688dd jazzdd mvprobdd mvprodd sb16d2 sbawed2 sbd2 sbp2d2 sbpd2) do del /N C:\MMTEMP\OS2\DRIVERS\%%i\*.*
+@REM for %%i in (acpadd2 azt16dd azt32dd csbsaud es1688dd es1788dd es1868dd es1888dd es688dd jazzdd mvprobdd mvprodd sb16d2 sbawed2 sbd2 sbp2d2 sbpd2) do rmdir C:\MMTEMP\OS2\DRIVERS\%%i
+@REM rmdir C:\MMTEMP\OS2\DRIVERS
+@REM rmdir C:\MMTEMP\OS2
+rmdir C:\MMTEMP
+copy C:\CONFIG.SYS C:\VBoxCID || goto skip_sys_cleanup
+del /N C:\*.SYS
+copy C:\VBoxCID\CONFIG.SYS C:\
+:skip_sys_cleanup
+
+:step3_12
+@@VBOX_COND_HAS_POST_INSTALL_COMMAND@@
+@echo .
+@echo Step 3.12 - Custom actions: "@@VBOX_INSERT_POST_INSTALL_COMMAND@@"
+@echo .
+cd C:\VBoxCID
+C:
+@@VBOX_INSERT_POST_INSTALL_COMMAND@@
+@@VBOX_COND_END@@
+
+copy C:\CONFIG.SYS C:\VBoxCID\Phase3-end-config.sys
+
+
+REM
+REM Reboot (common to both phases).
+REM
+:reboot
+@echo .
+@echo Reboot (%1)
+@echo .
+cd C:\OS2
+C:
+
+@REM @echo debug
+@REM CMD.EXE
+
+SETBOOT /IBD:C
+pause
+CMD.EXE
+
diff --git a/src/VBox/Main/UnattendedTemplates/os2_response_files.rsp b/src/VBox/Main/UnattendedTemplates/os2_response_files.rsp
new file mode 100644
index 00000000..90788914
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/os2_response_files.rsp
@@ -0,0 +1,325 @@
+@@VBOX_SPLITTER_START[CONFIG.SYS]@@
+REM
+REM $Id: os2_response_files.rsp $
+REM VirtualBox CID Installation CONFIG.SYS for boot CD/floppy.
+REM
+
+REM Put the CDROM as letter S:.
+RESERVEDRIVELETTER=R
+LASTDRIVE=Z
+
+BUFFERS=32
+DISKCACHE=D2,LW
+
+MEMMAN=NOSWAP,PROTECT
+PROTECTONLY=YES
+IOPL=YES
+
+PAUSEONERROR=NO
+BREAK=OFF
+
+REM Just to be on the safe side, all paths include disk #0 thru #6.
+REM as it doesn't contain much interesting.
+REM Also adding typical C:\ locations for good measure...
+ LIBPATH=.;A:\;S:\OS2IMAGE\DISK_0;S:\OS2IMAGE\DISK_1;S:\OS2IMAGE\DISK_2;S:\OS2IMAGE\DISK_3;S:\OS2IMAGE\DISK_4;S:\OS2IMAGE\DISK_5;S:\OS2IMAGE\DISK_6;S:\OS2IMAGE\DISK_7;C:\OS2\DLL;C:\MPTN\DLL;C:\TCPIP\DLL;C:\IBMCOM\DLL;
+SET PATH=.;A:\;S:\OS2IMAGE\DISK_0;S:\OS2IMAGE\DISK_1;S:\OS2IMAGE\DISK_2;S:\OS2IMAGE\DISK_3;S:\OS2IMAGE\DISK_4;S:\OS2IMAGE\DISK_5;S:\OS2IMAGE\DISK_6;S:\OS2IMAGE\DISK_7;C:\OS2;C:\OS2\SYSTEM;
+SET DPATH=.;A:\;S:\OS2IMAGE\DISK_0;S:\OS2IMAGE\DISK_1;S:\OS2IMAGE\DISK_2;S:\OS2IMAGE\DISK_3;S:\OS2IMAGE\DISK_4;S:\OS2IMAGE\DISK_5;S:\OS2IMAGE\DISK_6;S:\OS2IMAGE\DISK_7;C:\OS2;C:\OS2\SYSTEM;
+
+COUNTRY=001,COUNT437.SYS
+CODEPAGE=850,437
+DEVINFO=KBD,US,KEYBOARD.DCP
+DEVINFO=SCR,VGA,VIO437.DCP
+
+BASEDEV=IBMKBD.SYS
+BASEDEV=IBM1FLPY.ADD
+BASEDEV=IBM1S506.ADD
+BASEDEV=IBMIDECD.FLT
+BASEDEV=OS2DASD.DMD
+BASEDEV=OS2LVM.DMD
+DEVICE=TESTCFG.SYS
+DEVICE=OS2CDROM.DMD
+DEVICE=DOS.SYS
+
+IFS=CDFS.IFS
+REM From here on we can load off the ISO.
+IFS=S:\OS2IMAGE\DISK_2\HPFS.IFS /C:2048
+IFS=S:\OS2IMAGE\DISK_2\JFS.IFS
+
+SET OS2_SHELL=S:\OS2IMAGE\DISK_2\CMD.EXE
+REM PROTSHELL=S:\OS2IMAGE\DISK_2\CMD.EXE
+REM Run the os2_cid_install.cmd file (renamed to VBOXCID.CMD on the ISO).
+REM PROTSHELL=S:\OS2IMAGE\DISK_2\CMD.EXE /C S:\VBOXCID\OS2_UTIL.EXE --tee-to-backdoor --tee-to-file C:\VBoxCID\Phase1.log --append -- S:\OS2IMAGE\DISK_2\CMD.EXE /C S:\VBOXCID.CMD PHASE1
+PROTSHELL=S:\VBOXCID\OS2_UTIL.EXE --tee-to-backdoor --tee-to-file C:\VBoxCID\Phase1.log --append -- S:\OS2IMAGE\DISK_2\CMD.EXE /C S:\VBOXCID.CMD PHASE1
+
+@@VBOX_SPLITTER_END[CONFIG.SYS]@@
+
+#
+# An "Y" for the format command.
+#
+@@VBOX_SPLITTER_START[YES.TXT]@@
+Y
+@@VBOX_SPLITTER_END[YES.TXT]@@
+
+#
+# This is the startup.cmd file that ends up on the final C:.
+#
+@@VBOX_SPLITTER_START[STARTUP.CMD]@@
+@REM
+@REM $Id: os2_response_files.rsp $
+@REM The startup file for C: for use after the successful install.
+@REM
+@@VBOX_COND_IS_INSTALLING_ADDITIONS@@
+C:\VBoxAdd\VBoxService.exe
+@@VBOX_COND_END@@
+dhcpstrt -i lan0
+@@VBOX_COND_IS_INSTALLING_TEST_EXEC_SERVICE@@
+SET ENDLIBPATH=C:\VBoxValKit
+C:\VBoxValKit\TestExecService.exe --scratch=D:\TestArea --cdrom=E:\ --foreground
+pause
+@@VBOX_COND_END@@
+
+@@VBOX_SPLITTER_END[STARTUP.CMD]@@
+
+#
+# The OS2.RSP file is the main installer response file.
+#
+@@VBOX_SPLITTER_START[OS2.RSP]@@
+RebootRequired=0
+ExitOnError=1
+BaseFileSystem=1
+AlternateAdapter=0
+CDROM=2
+APM=1
+AdditionalPrinters=0
+CountryCode=001
+CountryKeyboard=US
+DefaultPrinter=0
+DisplayAdapter=7
+Documentation=1
+DOSSupport=1
+WIN-OS/2Support=1
+WIN-OS/2Desktop=0
+DPMI=1
+IBMFONTA.Selection=0
+IBMFONTG.Selection=0
+IBMFONTT.Selection=0
+IBMFONTJ.Selection=0
+IBMFONTC.Selection=0
+IBMFONTS.Selection=0
+IBMFONTK.Selection=0
+IBMFONTU.Selection=1
+XIBMFONT.InstDrive=C:
+FormatPartition=0
+Locale=en_US
+MigrateConfigFiles=0
+Mouse=1
+MousePort=0
+OptionalSystemUtilities=1
+OptionalSystemComponents=1
+PCMCIA=0
+PCMCIAOptions=0
+Optical=0
+HOTPLUG.Selection=0
+WARMSWAP.Selection=0
+PrimaryCodePage=1
+PrinterPort=1
+ProcessEnvironment=1
+ProgressIndication=1
+SCSI=0
+SerialDeviceSupport=1
+SourcePath=S:\os2image
+TargetDrive=C:
+*WIN-OS/2TargetDrive=C:
+SMP=0
+SMPPath=C:\OS2\BOOT
+ToolsAndGames=1
+MultimediaSupport=1
+PUMARKNET.Selection=0
+PUMARKNET.TarDrv=C:
+PUMARKVIS.Selection=0
+PUMARKVIS.TarDrv=C:
+LVMGUI.Selection=1
+Java11.Selection=1
+Runtime.Selection=1
+FIBASE.JavaDrive=C:
+SeedConfigSysLine=PAUSEONERROR=NO
+@@VBOX_SPLITTER_END[OS2.RSP]@@
+
+#
+# Multi-Protocol Transport Services (MPTS) response file.
+#
+@@VBOX_SPLITTER_START[MPTS.RSP]@@
+INST_SECTION = (
+ UPGRADE_LEVEL = SAME
+ INSTALL = PRODUCT
+)
+MPTS = (
+ [CONTROL]
+ Local_IPC = YES
+ INET_Access = YES
+ NETBIOS_Access = YES
+)
+
+PROTOCOL = (
+[PROT_MAN]
+
+ DRIVERNAME = PROTMAN$
+
+[IBMLXCFG]
+
+ netbeui_nif = netbeui.nif
+ tcpbeui_nif = tcpbeui.nif
+ tcpip_nif = tcpip.nif
+ IBMEAN_nif = IBMEAN.nif
+
+[NETBIOS]
+
+ DriverName = netbios$
+ ADAPTER0 = netbeui$,0
+ ADAPTER1 = tcpbeui$,1
+
+[netbeui_nif]
+
+ DriverName = netbeui$
+ Bindings = IBMEAN_nif
+ ETHERAND_TYPE = "I"
+ USEADDRREV = "YES"
+ OS2TRACEMASK = 0x0
+ SESSIONS = 130
+ NCBS = 225
+ NAMES = 21
+ SELECTORS = 50
+ USEMAXDATAGRAM = "NO"
+ ADAPTRATE = 1000
+ WINDOWERRORS = 0
+ MAXDATARCV = 4168
+ TI = 30000
+ T1 = 1000
+ T2 = 200
+ MAXIN = 1
+ MAXOUT = 1
+ NETBIOSTIMEOUT = 500
+ NETBIOSRETRIES = 3
+ NAMECACHE = 1000
+ RNDOPTION = 1
+ PIGGYBACKACKS = 1
+ DATAGRAMPACKETS = 50
+ PACKETS = 300
+ LOOPPACKETS = 8
+ PIPELINE = 5
+ MAXTRANSMITS = 6
+ MINTRANSMITS = 2
+ DLCRETRIES = 10
+ FCPRIORITY = 5
+ NETFLAGS = 0x0
+
+[tcpbeui_nif]
+
+ DriverName = tcpbeui$
+ Bindings = ,IBMEAN_nif
+ NODETYPE = "B-Node"
+ OS2TRACEMASK = 0x0
+ SESSIONS = 130
+ NCBS = 225
+ NAMES = 21
+ SELECTORS = 15
+ USEMAXDATAGRAM = "NO"
+ NETBIOSTIMEOUT = 500
+ NETBIOSRETRIES = 2
+ NAMECACHE = 1000
+ PURGECACHE = 0
+ PRELOADCACHE = "NO"
+ NAMESFILE = 0
+ DATAGRAMPACKETS = 20
+ PACKETS = 50
+ ENABLEDNS = 0
+ INTERFACERATE = 300
+
+[tcpip_nif]
+
+ DriverName = TCPIP$
+ Bindings = IBMEAN_nif
+
+[IBMEAN_nif]
+
+ DriverName = IBMEAN$
+)
+@@VBOX_SPLITTER_END[MPTS.RSP]@@
+
+#
+# IBM Lan Manager response file installing both TCP and old NETBIOS.
+#
+@@VBOX_SPLITTER_START[IBMLAN.RSP]@@
+UPDATEIBMLAN = Networks<
+ net1 = NETBEUI$,0,LM10,34,70,14
+ net2 = TCPBEUI$,1,LM10,34,70,14
+>
+
+ADDIBMLAN = Requester<
+wrkservices = MESSENGER
+wrknets = NET1, NET2
+>
+
+UPDATEIBMLAN = Requester<
+Computername = VBOXACP2
+Domain = VBOXGUEST
+useallmem = Yes
+>
+
+UPDATEIBMLAN = Peer<
+srvnets = NET1,NET2
+>
+
+ConfigTargetDrive = C
+ConfigAutoStartLS = Migrate
+ConfigSourceDrive = C
+InstallAPI = INSTALLIFREQUIRED
+InstallDosLanApi = INSTALLIFREQUIRED
+InstallInstallProgram = INSTALLIFREQUIRED
+InstallPeerService = INSTALL
+InstallRequester = INSTALLIFREQUIRED
+InstallUPM = INSTALLIFREQUIRED
+InstallMSGPopup = INSTALLIFREQUIRED
+InstallGUI = INSTALL
+InstallClipBoard = INSTALLIFREQUIRED
+InstallDesktopIcons = YES
+
+@@VBOX_SPLITTER_END[IBMLAN.RSP]@@
+
+#
+# Multimedia Presentation Manager/2 response file - Setting up SB16.
+#
+@@VBOX_SPLITTER_START[MMOS2.RSP]@@
+MMINSTSOURCE = "C:\\mmtemp"
+MMINSTTARGET = "C:"
+CHANGECONFIG = "Y"
+
+MMINSTGROUPS =
+ (
+ GROUP.1 = " "
+ GROUP.2 = "Software Motion Video=NUM=1,"
+ GROUP.4 = "IBM Video Capture Adapter=NUM=0,"
+ GROUP.5 = "1"
+ GROUP.14 = " "
+ GROUP.19 = " "
+ GROUP.20 = " "
+ GROUP.43 = "Sound Blaster 16=NUM=1,V1=1,V1=5,V1=5,V1=220,V1=330,"
+ )
+@@VBOX_SPLITTER_END[MMOS2.RSP]@@
+
+#
+# Good old netscape communicator.
+#
+@@VBOX_SPLITTER_START[NETSCAPE.RSP]@@
+COMP = Netscape Communicator 4.61 for OS/2
+FILE = C:\NETSCAPE
+CFGUPDATE = AUTO
+DELETEBACKUP = NO
+OVERWRITE = YES
+SAVEBACKUP = NO
+NSCONVERTBROWSER = YES
+NSCONVERTQL = YES
+NSASSOCIATEHTML = YES
+@@VBOX_SPLITTER_END[NETSCAPE.RSP]@@
+
+
diff --git a/src/VBox/Main/UnattendedTemplates/os2_util.exe b/src/VBox/Main/UnattendedTemplates/os2_util.exe
new file mode 100644
index 00000000..98df4f0a
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/os2_util.exe
Binary files differ
diff --git a/src/VBox/Main/UnattendedTemplates/redhat67_ks.cfg b/src/VBox/Main/UnattendedTemplates/redhat67_ks.cfg
new file mode 100644
index 00000000..455655d8
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/redhat67_ks.cfg
@@ -0,0 +1,108 @@
+#platform=x86, AMD64, or Intel EM64T
+#version=DEVEL
+
+# Firewall configuration
+firewall --disabled
+
+# Install OS instead of upgrade
+install
+
+# Use CDROM installation media
+cdrom
+
+# Root password
+rootpw --plaintext @@VBOX_INSERT_ROOT_PASSWORD_SH@@
+
+# System authorization information
+auth --useshadow --passalgo=sha512
+
+# Use text mode install
+text
+
+# System keyboard
+keyboard us
+
+# System language
+lang @@VBOX_INSERT_LOCALE@@
+
+# Disable the unsupported hardware popup (vmmdev?).
+#unsupported_hardware
+
+# SELinux configuration
+selinux --enforcing
+
+# Installation logging level
+logging --level=info
+
+# System timezone
+timezone@@VBOX_COND_IS_RTC_USING_UTC@@ --utc@@VBOX_COND_END@@ @@VBOX_INSERT_TIME_ZONE_UX@@
+
+# Network information
+network --bootproto=dhcp --device=link --onboot=on --hostname=@@VBOX_INSERT_HOSTNAME_FQDN_SH@@
+
+# System bootloader configuration
+bootloader --location=mbr --append="nomodeset crashkernel=auto rhgb quiet"
+zerombr
+
+# Partition clearing information
+clearpart --all --initlabel
+
+# Disk partitioning information
+part / --fstype ext4 --size 6000 --grow --asprimary
+part swap --size 1024
+
+#Initial user
+user --name=@@VBOX_INSERT_USER_LOGIN_SH@@ --password=@@VBOX_INSERT_USER_PASSWORD_SH@@ --plaintext
+
+# Reboot after installation
+# Note! Not sure exctly when the --eject option was added. Need to find out an make it optional.
+reboot --eject
+
+# Packages. We currently ignore missing packages/groups here to keep things simpler.
+%packages --ignoremissing
+@base
+@core
+@@VBOX_COND_IS_NOT_MINIMAL_INSTALLATION@@
+@development
+@basic-desktop
+@desktop-debugging
+@desktop-platform
+@fonts
+@general-desktop
+@graphical-admin-tools
+@remote-desktop-clients
+@x11
+@@VBOX_COND_END@@
+
+# Prepare building the additions kernel module, try get what we can from the cdrom as it may be impossible
+# to install anything from the post script:
+kernel-headers
+kernel-devel
+glibc-devel
+glibc-headers
+gcc
+@@VBOX_COND[${GUEST_OS_VERSION} vgt 8.0.0]@@
+elfutils-libelf-devel
+@@VBOX_COND_END@@
+dkms
+make
+bzip2
+perl
+
+#Package cloud-init is needed for possible automation the initial setup of virtual machine
+cloud-init
+
+%end
+
+# Post install happens in a different script.
+# Note! We mount the CDROM explictily here since the location differs between fedora 26 to rhel5
+# and apparently there isn't any way to be certain that anaconda didn't unmount it already.
+%post --nochroot --log=/mnt/sysimage/root/ks-post.log
+df -h
+mkdir -p /tmp/vboxcdrom
+mount /dev/cdrom /tmp/vboxcdrom
+cp /tmp/vboxcdrom/vboxpostinstall.sh /mnt/sysimage/root/vboxpostinstall.sh
+chmod a+x /mnt/sysimage/root/vboxpostinstall.sh
+/bin/bash /mnt/sysimage/root/vboxpostinstall.sh --rhel
+umount /tmp/vboxcdrom
+%end
diff --git a/src/VBox/Main/UnattendedTemplates/redhat_postinstall.sh b/src/VBox/Main/UnattendedTemplates/redhat_postinstall.sh
new file mode 100755
index 00000000..7016b85e
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/redhat_postinstall.sh
@@ -0,0 +1,328 @@
+#!/bin/bash
+## @file
+# Post installation script template for redhat- distros.
+#
+# Note! This script expects to be running chrooted (inside new sytem).
+#
+
+#
+# Copyright (C) 2017-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+#
+# Globals.
+#
+MY_TARGET="/mnt/sysimage"
+MY_LOGFILE="${MY_TARGET}/var/log/vboxpostinstall.log"
+MY_CHROOT_CDROM="/cdrom"
+MY_CDROM_NOCHROOT="/tmp/vboxcdrom"
+MY_EXITCODE=0
+MY_DEBUG="" # "yes"
+GUEST_VERSION=@@VBOX_INSERT_GUEST_OS_VERSION@@
+GUEST_MAJOR_VERSION=@@VBOX_INSERT_GUEST_OS_MAJOR_VERSION@@
+
+@@VBOX_COND_HAS_PROXY@@
+PROXY="@@VBOX_INSERT_PROXY@@"
+export http_proxy="${PROXY}"
+export https_proxy="${PROXY}"
+echo "HTTP proxy is ${http_proxy}" | tee -a "${MY_LOGFILE}"
+echo "HTTPS proxy is ${https_proxy}" | tee -a "${MY_LOGFILE}"
+@@VBOX_COND_END@@
+
+#
+# Do we need to exec using target bash? If so, we must do that early
+# or ash will bark 'bad substitution' and fail.
+#
+if [ "$1" = "--need-target-bash" ]; then
+ # Try figure out which directories we might need in the library path.
+ if [ -z "${LD_LIBRARY_PATH}" ]; then
+ LD_LIBRARY_PATH="${MY_TARGET}/lib"
+ fi
+ for x in \
+ ${MY_TARGET}/lib \
+ ${MY_TARGET}/usr/lib \
+ ${MY_TARGET}/lib/*linux-gnu/ \
+ ${MY_TARGET}/lib32/ \
+ ${MY_TARGET}/lib64/ \
+ ${MY_TARGET}/usr/lib/*linux-gnu/ \
+ ${MY_TARGET}/usr/lib32/ \
+ ${MY_TARGET}/usr/lib64/ \
+ ;
+ do
+ if [ -e "$x" ]; then LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${x}"; fi;
+ done
+ export LD_LIBRARY_PATH
+
+ # Append target bin directories to the PATH as busybox may not have tee.
+ PATH="${PATH}:${MY_TARGET}/bin:${MY_TARGET}/usr/bin:${MY_TARGET}/sbin:${MY_TARGET}/usr/sbin"
+ export PATH
+
+ # Drop the --need-target-bash argument and re-exec.
+ shift
+ echo "******************************************************************************" >> "${MY_LOGFILE}"
+ echo "** Relaunching using ${MY_TARGET}/bin/bash $0 $*" >> "${MY_LOGFILE}"
+ echo "** LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> "${MY_LOGFILE}"
+ echo "** PATH=${PATH}" >> "${MY_LOGFILE}"
+ exec "${MY_TARGET}/bin/bash" "$0" "$@"
+fi
+
+
+#
+# Commands.
+#
+
+# Logs execution of a command.
+log_command()
+{
+ echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+ echo "** Date: `date -R`" >> "${MY_LOGFILE}"
+ echo "** Executing: $*" >> "${MY_LOGFILE}"
+ "$@" 2>&1 | tee -a "${MY_LOGFILE}"
+ MY_TMP_EXITCODE="${PIPESTATUS[0]}" # bashism - whatever.
+ if [ "${MY_TMP_EXITCODE}" != "0" ]; then
+ if [ "${MY_TMP_EXITCODE}" != "${MY_IGNORE_EXITCODE}" ]; then
+ echo "** exit code: ${MY_TMP_EXITCODE}" | tee -a "${MY_LOGFILE}"
+ MY_EXITCODE=1;
+ else
+ echo "** exit code: ${MY_TMP_EXITCODE} (ignored)" | tee -a "${MY_LOGFILE}"
+ fi
+ fi
+}
+
+# Logs execution of a command inside the target.
+log_command_in_target()
+{
+ log_command chroot "${MY_TARGET}" "$@"
+}
+
+# Checks if $1 is a command on the PATH inside the target jail.
+chroot_which()
+{
+ for dir in /bin /usr/bin /sbin /usr/sbin;
+ do
+ if [ -x "${MY_TARGET}${dir}/$1" ]; then
+ return 0;
+ fi
+ done
+ return 1;
+}
+
+#
+# Log header.
+#
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+echo "** VirtualBox Unattended Guest Installation - Late installation actions" >> "${MY_LOGFILE}"
+echo "** Date: `date -R`" >> "${MY_LOGFILE}"
+echo "** Started: $0 $*" >> "${MY_LOGFILE}"
+
+
+#
+# We want the ISO available inside the target jail.
+#
+if [ -d "${MY_TARGET}${MY_CHROOT_CDROM}" ]; then
+ MY_RMDIR_TARGET_CDROM=
+else
+ MY_RMDIR_TARGET_CDROM="yes"
+ log_command mkdir -p ${MY_TARGET}${MY_CHROOT_CDROM}
+fi
+
+if [ -f "${MY_TARGET}${MY_CHROOT_CDROM}/vboxpostinstall.sh" ]; then
+ MY_UNMOUNT_TARGET_CDROM=
+ echo "** binding cdrom into jail: already done" | tee -a "${MY_LOGFILE}"
+else
+ MY_UNMOUNT_TARGET_CDROM="yes"
+ log_command mount -o bind "${MY_CDROM_NOCHROOT}" "${MY_TARGET}${MY_CHROOT_CDROM}"
+ if [ -f "${MY_TARGET}${MY_CHROOT_CDROM}/vboxpostinstall.sh" ]; then
+ echo "** binding cdrom into jail: success" | tee -a "${MY_LOGFILE}"
+ else
+ echo "** binding cdrom into jail: failed" | tee -a "${MY_LOGFILE}"
+ fi
+ if [ "${MY_DEBUG}" = "yes" ]; then
+ log_command find "${MY_TARGET}${MY_CHROOT_CDROM}"
+ fi
+fi
+
+
+#
+# Debug
+#
+if [ "${MY_DEBUG}" = "yes" ]; then
+ log_command id
+ log_command ps
+ log_command ps auxwwwf
+ log_command env
+ log_command df
+ log_command mount
+ log_command_in_target df
+ log_command_in_target mount
+ #log_command find /
+ MY_EXITCODE=0
+fi
+
+
+#
+# Add EPEL repository
+#
+EPEL_REPOSITORY="https://dl.fedoraproject.org/pub/epel/epel-release-latest-${GUEST_MAJOR_VERSION}.noarch.rpm"
+log_command_in_target wget ${EPEL_REPOSITORY}
+log_command_in_target yum localinstall -y "epel-release-latest-${GUEST_MAJOR_VERSION}.noarch.rpm"
+log_command_in_target yum install -y yum-utils
+log_command_in_target yum-config-manager --enable epel
+
+
+#
+# Packages needed for GAs.
+#
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing packages for building kernel modules...' | tee -a "${MY_LOGFILE}"
+log_command_in_target yum -y install "kernel-devel-$(uname -r)"
+log_command_in_target yum -y install "kernel-headers-$(uname -r)"
+log_command_in_target yum -y install gcc
+log_command_in_target yum -y install binutils
+log_command_in_target yum -y install make
+@@VBOX_COND[${GUEST_OS_VERSION} vgt 8.0.0]@@
+log_command_in_target yum -y install elfutils-libelf-devel
+@@VBOX_COND_END@@
+log_command_in_target yum -y install dkms
+log_command_in_target yum -y install make
+log_command_in_target yum -y install bzip2
+log_command_in_target yum -y install perl
+
+
+#
+#Package cloud-init is needed for possible automation the initial setup of virtual machine
+#
+log_command_in_target yum -y install cloud-init
+log_command_in_target systemctl enable cloud-init-local.service
+log_command_in_target systemctl enable cloud-init.service
+log_command_in_target systemctl enable cloud-config.service
+log_command_in_target systemctl enable cloud-final.service
+
+
+#
+# GAs
+#
+@@VBOX_COND_IS_INSTALLING_ADDITIONS@@
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing VirtualBox Guest Additions...' | tee -a "${MY_LOGFILE}"
+MY_IGNORE_EXITCODE=2 # returned if modules already loaded and reboot required.
+log_command_in_target /bin/bash "${MY_CHROOT_CDROM}/vboxadditions/VBoxLinuxAdditions.run" --nox11
+log_command_in_target /bin/bash -c "udevadm control --reload-rules" # GAs doesn't yet do this.
+log_command_in_target /bin/bash -c "udevadm trigger" # (ditto)
+MY_IGNORE_EXITCODE=
+log_command_in_target usermod -a -G vboxsf "@@VBOX_INSERT_USER_LOGIN@@"
+@@VBOX_COND_END@@
+
+
+#
+# Test Execution Service.
+#
+@@VBOX_COND_IS_INSTALLING_TEST_EXEC_SERVICE@@
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing Test Execution Service...' | tee -a "${MY_LOGFILE}"
+log_command_in_target test "${MY_CHROOT_CDROM}/vboxvalidationkit/linux/@@VBOX_INSERT_OS_ARCH@@/TestExecService"
+log_command mkdir -p "${MY_TARGET}/opt/validationkit" "${MY_TARGET}/media/cdrom"
+log_command cp -R ${MY_CDROM_NOCHROOT}/vboxvalidationkit/* "${MY_TARGET}/opt/validationkit/"
+log_command chmod -R u+rw,a+xr "${MY_TARGET}/opt/validationkit/"
+if [ -e "${MY_TARGET}/usr/bin/chcon" -o -e "${MY_TARGET}/bin/chcon" -o -e "${MY_TARGET}/usr/sbin/chcon" -o -e "${MY_TARGET}/sbin/chcon" ]; then
+ MY_IGNORE_EXITCODE=1
+ log_command_in_target chcon -R -t usr_t "/opt/validationkit/"
+ MY_IGNORE_EXITCODE=
+fi
+
+# systemd service config:
+MY_UNIT_PATH="${MY_TARGET}/lib/systemd/system"
+test -d "${MY_TARGET}/usr/lib/systemd/system" && MY_UNIT_PATH="${MY_TARGET}/usr/lib/systemd/system"
+if [ -d "${MY_UNIT_PATH}" ]; then
+ log_command cp "${MY_TARGET}/opt/validationkit/linux/vboxtxs.service" "${MY_UNIT_PATH}/vboxtxs.service"
+ log_command chmod 644 "${MY_UNIT_PATH}/vboxtxs.service"
+ log_command_in_target systemctl -q enable vboxtxs
+
+# System V like:
+elif [ -e "${MY_TARGET}/etc/init.d/" ]; then
+
+ # Install the script. On rhel6 scripts are under /etc/rc.d/ with /etc/init.d and /etc/rc?.d being symlinks.
+ if [ -d "${MY_TARGET}/etc/rc.d/init.d/" ]; then
+ MY_INIT_D_PARENT_PATH="${MY_TARGET}/etc/rc.d"
+ log_command ln -s "../../../opt/validationkit/linux/vboxtxs" "${MY_INIT_D_PARENT_PATH}/init.d/"
+ else
+ MY_INIT_D_PARENT_PATH="${MY_TARGET}/etc"
+ log_command ln -s "../../opt/validationkit/linux/vboxtxs" "${MY_INIT_D_PARENT_PATH}/init.d/"
+ fi
+
+ # Use runlevel management script if found.
+ if chroot_which chkconfig; then # Redhat based sysvinit systems
+ log_command_in_target chkconfig --add vboxtxs
+ elif chroot_which insserv; then # SUSE-based sysvinit systems
+ log_command_in_target insserv vboxtxs
+ elif chroot_which update-rc.d; then # Debian/Ubuntu-based systems
+ log_command_in_target update-rc.d vboxtxs defaults
+ elif chroot_which rc-update; then # Gentoo Linux
+ log_command_in_target rc-update add vboxtxs default
+ # Fall back on hardcoded symlinking.
+ else
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc0.d/K65vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc1.d/K65vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc6.d/K65vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc2.d/S35vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc3.d/S35vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc4.d/S35vboxtxs"
+ log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc5.d/S35vboxtxs"
+ fi
+else
+ echo "** error: Unknown init script system." | tee -a "${MY_LOGFILE}"
+fi
+
+@@VBOX_COND_END@@
+
+
+#
+# Run user command.
+#
+@@VBOX_COND_HAS_POST_INSTALL_COMMAND@@
+echo '** Running custom user command ...' | tee -a "${MY_LOGFILE}"
+log_command @@VBOX_INSERT_POST_INSTALL_COMMAND@@
+@@VBOX_COND_END@@
+
+
+#
+# Unmount the cdrom if we bound it and clean up the chroot if we set it up.
+#
+if [ -n "${MY_UNMOUNT_TARGET_CDROM}" ]; then
+ echo "** unbinding cdrom from jail..." | tee -a "${MY_LOGFILE}"
+ log_command umount "${MY_TARGET}${MY_CHROOT_CDROM}"
+fi
+
+if [ -n "${MY_RMDIR_TARGET_CDROM}" ]; then
+ log_command rmdir "${MY_TARGET}${MY_CHROOT_CDROM}"
+fi
+
+
+#
+# Log footer.
+#
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+echo "** Date: `date -R`" >> "${MY_LOGFILE}"
+echo "** Final exit code: ${MY_EXITCODE}" >> "${MY_LOGFILE}"
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+
+exit ${MY_EXITCODE}
diff --git a/src/VBox/Main/UnattendedTemplates/rhel3_ks.cfg b/src/VBox/Main/UnattendedTemplates/rhel3_ks.cfg
new file mode 100644
index 00000000..797737a8
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/rhel3_ks.cfg
@@ -0,0 +1,141 @@
+#
+# Template for RHEL3 and derivatives.
+#
+# Note! RHEL3 kickstart typically just hangs if it finds something it doesn't like.
+# So, all changes to this file must be tested!
+#
+# N.B! AHCI is not supported by RHEL3
+#
+
+# Use text mode install
+text
+
+# Install OS instead of upgrade
+install
+
+# System language
+lang @@VBOX_INSERT_LOCALE@@.UTF-8
+
+# rhel4+rhel3 needs:
+langsupport --default @@VBOX_INSERT_LOCALE@@.UTF-8 @@VBOX_INSERT_LOCALE@@.UTF-8
+
+# Use CDROM installation media
+cdrom
+
+# System authorization information (rhel5: no --passalgo=sha512)
+#auth --useshadow
+authconfig --enableshadow --enablemd5
+
+# Root password (rhel5 not --plaintext groks)
+rootpw @@VBOX_INSERT_ROOT_PASSWORD_SH@@
+
+# Network information
+# rhel3: doesn't like --onboot=on.
+network --bootproto=dhcp --device=eth0 --hostname=@@VBOX_INSERT_HOSTNAME_FQDN_SH@@
+
+# Firewall configuration
+firewall --disabled
+
+# System keyboard
+keyboard us
+
+# rhel3 wants mouse config.
+mouse genericwheelps/2 --device psaux
+
+@@VBOX_COND_IS_NOT_MINIMAL_INSTALLATION@@
+# rhel3 wants xconfig.
+# TODO: Dunno VRAM size, is that a problem? --videoram 32768
+xconfig --card "VESA driver (generic)" --hsync 31.5-37.9 --vsync 50-70 --resolution 800x600 --depth 24 --startxonboot --defaultdesktop gnome
+@@VBOX_COND_END@@
+
+# SELinux configuration
+# rhel3: disable selinux
+# selinux --enforcing
+
+# Installation logging level
+#rhel4 does not grok: logging --level=info
+
+# System timezone
+timezone@@VBOX_COND_IS_RTC_USING_UTC@@ --utc@@VBOX_COND_END@@ @@VBOX_INSERT_TIME_ZONE_UX@@
+
+# System bootloader configuration
+bootloader --location=mbr --append="nomodeset crashkernel=auto rhgb quiet"
+zerombr
+
+# Partition clearing information
+clearpart --all --initlabel
+
+# Disk partitioning information (rhel5: no ext4, so use ext3)
+part / --fstype ext3 --size 6000 --grow --asprimary
+part swap --size 1024
+
+#Initial user
+#rhel4 does not grok, done in welcome sequence: user --name=@@VBOX_INSERT_USER_LOGIN_SH@@ --password=@@VBOX_INSERT_USER_PASSWORD_SH@@
+
+# Reboot after installation
+# Note! Not sure exctly when the --eject option was added. Need to find out an make it optional.
+reboot --eject
+
+# Packages. We currently ignore missing packages/groups here to keep things simpler.
+%packages --ignoremissing
+grub
+kernel
+@ base
+@ core
+@@VBOX_COND_IS_NOT_MINIMAL_INSTALLATION@@
+@ admin-tools
+@ development
+@ editors
+@ text-internet
+@ base-x
+@ graphics
+@ basic-desktop
+@ general-desktop
+@ gnome-desktop
+@ desktop-platform
+@ fonts
+@ graphical-admin-tools
+@ graphical-internet
+@ remote-desktop-clients
+@ sound-and-video
+@ x11
+@@VBOX_COND_END@@
+
+# Prepare building the additions kernel module, try get what we can from the cdrom as it may be impossible
+# to install anything from the post script (rhel3 seems to need kernel-sources):
+kernel-source
+kernel-headers
+kernel-devel
+glibc-devel
+glibc-headers
+gcc
+dkms
+make
+bzip2
+perl
+# %end - rhel5 does not like this.
+
+
+# Pre install script for mounting the cdrom, to make sure it cannot be ejcted.
+# See https://bugzilla.redhat.com/show_bug.cgi?id=239002
+%pre
+mkdir -p /tmp/vboxcdrom
+mount -t iso9660 /tmp/cdrom /tmp/vboxcdrom || mount -t iso9660 /dev/hdc /tmp/vboxcdrom || mount -t iso9660 /dev/scd0 /tmp/vboxcdrom || mount -t iso9660 /dev/sdb /tmp/vboxcdrom
+# %end - rhel5 does not like this.
+
+
+# Post install happens in a different script.
+# Note! We mount the CDROM explictily here since the location differs between fedora 26 to rhel5
+# and apparently there isn't any way to be certain that anaconda didn't unmount it already.
+# rhel5: There is not /bin/bash, so use /bin/sh
+# rhel5: There is no /dev/cdrom, so try use /dev/hdc and /dev/sdb.
+# rhel3: no --log option
+%post --nochroot
+df -h
+cp /tmp/vboxcdrom/vboxpostinstall.sh /mnt/sysimage/root/vboxpostinstall.sh
+chmod a+x /mnt/sysimage/root/vboxpostinstall.sh
+/bin/sh /mnt/sysimage/root/vboxpostinstall.sh --rhel
+umount /tmp/vboxcdrom
+rmdir /tmp/vboxcdrom
+# %end - rhel5 does not like this.
+
diff --git a/src/VBox/Main/UnattendedTemplates/rhel4_ks.cfg b/src/VBox/Main/UnattendedTemplates/rhel4_ks.cfg
new file mode 100644
index 00000000..54a2cdab
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/rhel4_ks.cfg
@@ -0,0 +1,120 @@
+# Template for RHEL5 and derivatives.
+#platform=x86, AMD64, or Intel EM64T
+#version=DEVEL
+
+# Firewall configuration
+firewall --disabled
+
+# Install OS instead of upgrade
+install
+
+# Use CDROM installation media
+cdrom
+
+# Root password (rhel5 not --plaintext groks)
+rootpw @@VBOX_INSERT_ROOT_PASSWORD_SH@@
+
+# System authorization information (rhel5: no --passalgo=sha512)
+auth --useshadow
+
+# Use text mode install
+text
+
+# System keyboard
+keyboard us
+
+# System language
+lang @@VBOX_INSERT_LOCALE@@
+# rhel4 needs:
+langsupport --default=@@VBOX_INSERT_LOCALE@@.UTF-8 @@VBOX_INSERT_LOCALE@@.UTF-8
+
+
+# SELinux configuration
+selinux --enforcing
+
+# Installation logging level
+#rhel4 does not grok: logging --level=info
+
+# System timezone
+timezone@@VBOX_COND_IS_RTC_USING_UTC@@ --utc@@VBOX_COND_END@@ @@VBOX_INSERT_TIME_ZONE_UX@@
+
+# Network information
+network --bootproto=dhcp --device=eth0 --onboot=on --hostname=@@VBOX_INSERT_HOSTNAME_FQDN_SH@@
+
+# System bootloader configuration
+bootloader --location=mbr --append="nomodeset crashkernel=auto rhgb quiet"
+zerombr
+
+# Partition clearing information
+clearpart --all --initlabel
+
+# Disk partitioning information (rhel5: no ext4, so use ext3)
+part / --fstype ext3 --size 6000 --grow --asprimary
+part swap --size 1024
+
+#Initial user
+#rhel4 does not grok, done in welcome sequence: user --name=@@VBOX_INSERT_USER_LOGIN_SH@@ --password=@@VBOX_INSERT_USER_PASSWORD_SH@@
+
+# Reboot after installation
+# Note! Not sure exctly when the --eject option was added. Need to find out an make it optional.
+reboot --eject
+
+# Packages. We currently ignore missing packages/groups here to keep things simpler.
+%packages --ignoremissing
+@ base
+@ core
+@@VBOX_COND_IS_NOT_MINIMAL_INSTALLATION@@
+@ admin-tools
+@ development
+@ editors
+@ text-internet
+@ base-x
+@ graphics
+@ basic-desktop
+@ general-desktop
+@ gnome-desktop
+@ desktop-platform
+@ fonts
+@ graphical-admin-tools
+@ graphical-internet
+@ remote-desktop-clients
+@ sound-and-video
+@ x11
+@@VBOX_COND_END@@
+
+# Prepare building the additions kernel module, try get what we can from the cdrom as it may be impossible
+# to install anything from the post script:
+kernel-headers
+kernel-devel
+glibc-devel
+glibc-headers
+gcc
+dkms
+make
+bzip2
+perl
+# %end - rhel5 does not like this.
+
+
+# Pre install script for mounting the cdrom, to make sure it cannot be ejcted.
+# See https://bugzilla.redhat.com/show_bug.cgi?id=239002
+%pre
+mkdir -p /tmp/vboxcdrom
+mount -t iso9660 /tmp/cdrom /tmp/vboxcdrom || mount -t iso9660 /dev/hdc /tmp/vboxcdrom || mount -t iso9660 /dev/scd0 /tmp/vboxcdrom || mount -t iso9660 /dev/sdb /tmp/vboxcdrom
+# %end - rhel5 does not like this.
+
+
+# Post install happens in a different script.
+# Note! We mount the CDROM explictily here since the location differs between fedora 26 to rhel5
+# and apparently there isn't any way to be certain that anaconda didn't unmount it already.
+# rhel5: There is not /bin/bash, so use /bin/sh
+# rhel5: There is no /dev/cdrom, so try use /dev/hdc and /dev/sdb.
+%post --nochroot --log=/mnt/sysimage/root/ks-post.log
+df -h 1>&2
+cp /tmp/vboxcdrom/vboxpostinstall.sh /mnt/sysimage/root/vboxpostinstall.sh
+chmod a+x /mnt/sysimage/root/vboxpostinstall.sh
+/bin/sh /mnt/sysimage/root/vboxpostinstall.sh --rhel
+umount /tmp/vboxcdrom
+rmdir /tmp/vboxcdrom
+# %end - rhel5 does not like this.
+
diff --git a/src/VBox/Main/UnattendedTemplates/rhel5_ks.cfg b/src/VBox/Main/UnattendedTemplates/rhel5_ks.cfg
new file mode 100644
index 00000000..67bc998e
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/rhel5_ks.cfg
@@ -0,0 +1,119 @@
+# Template for RHEL5 and derivatives.
+#platform=x86, AMD64, or Intel EM64T
+#version=DEVEL
+
+# Firewall configuration
+firewall --disabled
+
+# Install OS instead of upgrade
+install
+
+# Use CDROM installation media
+cdrom
+
+# Root password (rhel5 not --plaintext groks)
+rootpw @@VBOX_INSERT_ROOT_PASSWORD_SH@@
+
+# System authorization information (rhel5: no --passalgo=sha512)
+auth --useshadow
+
+# Use text mode install
+text
+
+# System keyboard
+keyboard us
+
+# System language
+lang @@VBOX_INSERT_LOCALE@@
+
+#rhel5 needs:
+@@VBOX_COND_HAS_NO_PRODUCT_KEY@@
+key --skip
+@@VBOX_COND_END@@
+@@VBOX_COND_HAS_PRODUCT_KEY@@
+key @@VBOX_INSERT_PRODUCT_KEY@@
+@@VBOX_COND_END@@
+
+
+# SELinux configuration
+selinux --enforcing
+
+# Installation logging level
+logging --level=info
+
+# System timezone
+timezone@@VBOX_COND_IS_RTC_USING_UTC@@ --utc@@VBOX_COND_END@@ @@VBOX_INSERT_TIME_ZONE_UX@@
+
+# Network information
+network --bootproto=dhcp --device=eth0 --onboot=on --hostname=@@VBOX_INSERT_HOSTNAME_FQDN_SH@@
+
+# System bootloader configuration
+bootloader --location=mbr --append="nomodeset crashkernel=auto rhgb quiet"
+zerombr
+
+# Partition clearing information
+clearpart --all --initlabel
+
+# Disk partitioning information (rhel5: no ext4, so use ext3)
+part / --fstype ext3 --size 6000 --grow --asprimary
+part swap --size 1024
+
+#Initial user
+user --name=@@VBOX_INSERT_USER_LOGIN_SH@@ --password=@@VBOX_INSERT_USER_PASSWORD_SH@@
+
+# Reboot after installation
+# Note! Not sure exctly when the --eject option was added. Need to find out an make it optional.
+reboot --eject
+
+# Packages. We currently ignore missing packages/groups here to keep things simpler.
+%packages --ignoremissing
+@base
+@core
+@@VBOX_COND_IS_NOT_MINIMAL_INSTALLATION@@
+@development
+@basic-desktop
+@desktop-debugging
+@desktop-platform
+@fonts
+@general-desktop
+@graphical-admin-tools
+@remote-desktop-clients
+@x11
+@@VBOX_COND_END@@
+
+# Prepare building the additions kernel module, try get what we can from the cdrom as it may be impossible
+# to install anything from the post script:
+kernel-headers
+kernel-devel
+glibc-devel
+glibc-headers
+gcc
+dkms
+make
+bzip2
+perl
+# %end - rhel5 does not like this.
+
+
+# Pre install script for mounting the cdrom, to make sure it cannot be ejcted.
+# See https://bugzilla.redhat.com/show_bug.cgi?id=239002
+%pre
+mkdir -p /tmp/vboxcdrom
+mount -t iso9660 /tmp/cdrom /tmp/vboxcdrom || mount -t iso9660 /dev/hdc /tmp/vboxcdrom || mount -t iso9660 /dev/scd0 /tmp/vboxcdrom || mount -t iso9660 /dev/sdb /tmp/vboxcdrom
+# %end - rhel5 does not like this.
+
+
+# Post install happens in a different script.
+# Note! We mount the CDROM explictily here since the location differs between fedora 26 to rhel5
+# and apparently there isn't any way to be certain that anaconda didn't unmount it already.
+# rhel5: There is not /bin/bash, so use /bin/sh
+# rhel5: There is no /dev/cdrom, so try use /dev/hdc and /dev/sdb.
+%post --nochroot --log=/mnt/sysimage/root/ks-post.log
+df -h 1>&2
+cp /tmp/vboxcdrom/vboxpostinstall.sh /mnt/sysimage/root/vboxpostinstall.sh
+chmod a+x /mnt/sysimage/root/vboxpostinstall.sh
+/bin/sh /mnt/sysimage/root/vboxpostinstall.sh --rhel
+umount /tmp/vboxcdrom
+rmdir /tmp/vboxcdrom
+# %end - rhel5 does not like this.
+
diff --git a/src/VBox/Main/UnattendedTemplates/suse_autoinstall.xml b/src/VBox/Main/UnattendedTemplates/suse_autoinstall.xml
new file mode 100644
index 00000000..e07161ef
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/suse_autoinstall.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2016-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+<!DOCTYPE profile>
+<profile xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns">
+ <general>
+ <mode>
+ <confirm config:type="boolean">false</confirm>
+ </mode>
+ </general>
+
+ <partitioning config:type="list">
+ <drive>
+ <use>all</use>
+ </drive>
+ </partitioning>
+
+ <software>
+ <!-- Those are necessary for Guest Additions -->
+ <packages config:type="list">
+ <package>gcc</package>
+ <package>automake</package>
+ <package>kernel-source</package>
+ <!-- 'smpppd' is required on openSUSE 11.4 -->
+ <package>smpppd</package>
+ </packages>
+
+ <patterns config:type="list">
+ <pattern>apparmor</pattern>
+ <pattern>apparmor_opt</pattern>
+ <pattern>base</pattern>
+ <pattern>kde</pattern>
+ <!--pattern>Basis-Devel</pattern-->
+ <!--pattern>devel_kernel</pattern-->
+ <pattern>default</pattern>
+ <pattern>sw_management</pattern>
+ <pattern>sw_management_kde4</pattern>
+ <pattern>yast2_install_wf</pattern>
+ </patterns>
+ </software>
+
+ <configure>
+ <x11>
+ <display_manager>kdm</display_manager>
+ </x11>
+
+ <networking>
+ <interfaces config:type="list">
+ <interface>
+ <bootproto>dhcp</bootproto>
+ <device>eth0</device>
+ <startmode>auto</startmode>
+ <usercontrol>yes</usercontrol>
+ </interface>
+ </interfaces>
+ </networking>
+
+ <users config:type="list">
+ <user>
+ <fullname>root</fullname>
+ <username>root</username>
+ <encrypted config:type="boolean">false</encrypted>
+ <user_password>$password</user_password>
+ </user>
+ <user>
+ <groups>video,dialout,vboxsf</groups>
+ <fullname>$user</fullname>
+ <username>$user</username>
+ <encrypted config:type="boolean">false</encrypted>
+ <user_password>$password</user_password>
+ </user>
+ </users>
+ </configure>
+</profile>
diff --git a/src/VBox/Main/UnattendedTemplates/ubuntu_preseed.cfg b/src/VBox/Main/UnattendedTemplates/ubuntu_preseed.cfg
new file mode 100644
index 00000000..c7439fbd
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/ubuntu_preseed.cfg
@@ -0,0 +1,101 @@
+### software sources
+d-i ubiquity/use_nonfree true
+d-i ubiquity/install_oem false
+
+### Partitioning
+d-i partman-auto/disk string /dev/sda
+d-i partman-auto/method string regular
+d-i partman-lvm/device_remove_lvm boolean true
+d-i partman-md/device_remove_md boolean true
+d-i partman-auto/choose_recipe select atomic
+
+# This makes partman automatically partition without confirmation
+d-i partman-partitioning/confirm_write_new_label boolean true
+d-i partman/choose_partition select finish
+d-i partman/confirm boolean true
+d-i partman/confirm_nooverwrite boolean true
+
+# Locale
+d-i debian-installer/locale string @@VBOX_INSERT_LOCALE@@
+d-i console-setup/ask_detect boolean false
+d-i console-setup/layoutcode string us
+d-i keyboard-configuration/xkb-keymap select us
+
+# Network
+d-i netcfg/get_hostname string @@VBOX_INSERT_HOSTNAME_WITHOUT_DOMAIN@@
+d-i netcfg/get_domain string @@VBOX_INSERT_HOSTNAME_DOMAIN@@
+d-i netcfg/choose_interface select auto
+
+# Clock
+@@VBOX_COND_IS_RTC_USING_UTC@@
+d-i clock-setup/utc-auto boolean true
+d-i clock-setup/utc boolean true
+@@VBOX_COND_END@@
+@@VBOX_COND_IS_NOT_RTC_USING_UTC@@
+d-i clock-setup/utc-auto boolean false
+d-i clock-setup/utc boolean false
+@@VBOX_COND_END@@
+d-i time/zone string @@VBOX_INSERT_TIME_ZONE_UX@@
+@@VBOX_COND_IS_INSTALLING_ADDITIONS@@d-i clock-setup/ntp boolean false@@VBOX_COND_END@@
+@@VBOX_COND_IS_NOT_INSTALLING_ADDITIONS@@d-i clock-setup/ntp boolean true@@VBOX_COND_END@@
+
+# Packages, Mirrors, Image
+d-i base-installer/kernel/override-image string linux-server
+d-i base-installer/kernel/override-image string linux-image-amd64
+## @todo use nearest mirror somehow...
+d-i mirror/country string @@VBOX_INSERT_COUNTRY@@
+d-i mirror/http/proxy string
+## @todo minimal install?
+d-i apt-setup/restricted boolean true
+d-i apt-setup/universe boolean true
+d-i pkgsel/install-language-support boolean false
+# Stuff we need to build additions modules:
+d-i pkgsel/include string build-essential linux-headers-generic perl make
+# Package cloud-init is needed for possible automation the initial setup of virtual machine
+d-i pkgsel/include cloud-init
+
+# Users
+d-i passwd/user-fullname string @@VBOX_INSERT_USER_FULL_NAME@@
+d-i passwd/username string @@VBOX_INSERT_USER_LOGIN@@
+d-i passwd/user-password password @@VBOX_INSERT_USER_PASSWORD@@
+d-i passwd/user-password-again password @@VBOX_INSERT_USER_PASSWORD@@
+d-i passwd/root-login boolean true
+d-i passwd/root-password password @@VBOX_INSERT_ROOT_PASSWORD@@
+d-i passwd/root-password-again password @@VBOX_INSERT_ROOT_PASSWORD@@
+d-i user-setup/allow-password-weak boolean true
+d-i passwd/user-default-groups string admin
+
+# Grub
+d-i grub-installer/grub2_instead_of_grub_legacy boolean true
+d-i grub-installer/only_debian boolean true
+
+@@VBOX_COND[${GUEST_OS_VERSION} vge 14.04 && ${GUEST_OS_VERSION} vle 15.04]@@
+# Due notably to potential USB sticks, the location of the MBR can not be
+# determined safely in general, so this needs to be specified:
+# grub-install fails on [14.04.0, 15.04] by complaining "not being able to find default".
+# pointing it to /dev/sda gets around that problem.
+d-i grub-installer/bootdev string /dev/sda
+@@VBOX_COND_ELSE@@
+# To install to the first device (assuming it is not a USB stick):
+d-i grub-installer/bootdev string default
+@@VBOX_COND_END@@
+
+d-i finish-install/reboot_in_progress note
+
+# Custom Commands
+d-i preseed/late_command string cp /cdrom/vboxpostinstall.sh /target/root/vboxpostinstall.sh \
+ && chmod +x /target/root/vboxpostinstall.sh \
+ && /bin/bash /root/vboxpostinstall.sh --preseed-late-command
+
+# Same as above, but for ubiquity.
+ubiquity ubiquity/success_command string vboxpostinstall.sh
+ubiquity ubiquity/success_command string cp /cdrom/vboxpostinstall.sh /target/root/vboxpostinstall.sh \
+ && chmod +x /target/root/vboxpostinstall.sh \
+ && /bin/bash /target/root/vboxpostinstall.sh --ubiquity-success-command
+
+# automatically reboot after installation.
+ubiquity ubiquity/reboot boolean true
+
+## Skip downloading updates during installation (better for testing).
+# Seems this doesn't make any difference, so why bother.
+#ubiquity ubiquity/download_updates boolean false
diff --git a/src/VBox/Main/UnattendedTemplates/win_nt5_unattended.sif b/src/VBox/Main/UnattendedTemplates/win_nt5_unattended.sif
new file mode 100644
index 00000000..fe6a7018
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/win_nt5_unattended.sif
@@ -0,0 +1,60 @@
+;SetupMgrTag
+[Data]
+ AutoPartition = 1
+ MsDosInitiated = "0"
+ UnattendedInstall = "Yes"
+
+[Unattended]
+ UnattendMode = FullUnattended
+ OemSkipEula = Yes
+ OemPreinstall = No
+ TargetPath = \WINDOWS
+ Repartition = Yes
+ UnattendSwitch = "Yes"
+ DriverSigningPolicy = Ignore
+ WaitForReboot = "No"
+
+[GuiUnattended]
+ AdminPassword = "@@VBOX_INSERT_ROOT_PASSWORD@@"
+ EncryptedAdminPassword = No
+ AutoLogon = Yes
+ OEMSkipRegional = 1
+ OemSkipWelcome = 1
+; TODO: Make timezone configurable?
+ TimeZone = @@VBOX_INSERT_TIME_ZONE_WIN_INDEX@@
+ OemSkipWelcome = 1
+
+[UserData]
+; ProductKey was introduced in XP, ProductID was used in 2K and earlier.
+ ProductKey = "@@VBOX_INSERT_PRODUCT_KEY@@"
+ ProductID = "@@VBOX_INSERT_PRODUCT_KEY@@"
+; ; TODO: we're currently setting this up as Administrator only. We should respect the --user too.
+; ; Maybe consider: https://unattended.msfn.org/unattended.xp/view/web/27/SESSID=329e04d6824e220b0bb415d0665b1fe0/
+ FullName = "@@VBOX_INSERT_USER_LOGIN@@"
+ OrgName = ""
+ ComputerName = "@@VBOX_INSERT_HOSTNAME_WITHOUT_DOMAIN_MAX_15@@"
+
+[RegionalSettings]
+; ; TODO: If we implement locales below, we must also install the necessary language groups here. Fun.
+; LanguageGroup=1,2,3,4,5,6 - installed by default with XP
+
+; ; TODO: Implement mapping locales to windows LCIDs.
+; 0409:00000409 is US.
+; SystemLocale=00000419 - russian
+; ; TODO: Make Input locale configurable?
+; InputLocale=0409:00000409,0419:00000419 - US,Russian
+
+[LicenseFilePrintData]
+; This section is used for server installs.
+ AutoMode = "PerServer"
+ AutoUsers = "5"
+
+[Identification]
+ JoinWorkgroup = WORKGROUP
+
+[Networking]
+ InstallDefaultComponents = Yes
+
+[GuiRunOnce]
+ Command0="A:\VBOXPOST.CMD --xp-or-older"
+
diff --git a/src/VBox/Main/UnattendedTemplates/win_nt6_unattended.xml b/src/VBox/Main/UnattendedTemplates/win_nt6_unattended.xml
new file mode 100644
index 00000000..2b892fec
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/win_nt6_unattended.xml
@@ -0,0 +1,298 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+<unattend xmlns="urn:schemas-microsoft-com:unattend"
+ xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
+
+ <settings pass="windowsPE">
+ <component name="Microsoft-Windows-International-Core-WinPE"
+ processorArchitecture="@@VBOX_INSERT_OS_ARCH_ATTRIB_DQ@@"
+ publicKeyToken="31bf3856ad364e35" language="neutral"
+ versionScope="nonSxS">
+ <InputLocale>en-US</InputLocale>
+ <SystemLocale>@@VBOX_INSERT_DASH_LOCALE@@</SystemLocale>
+ <UserLocale>@@VBOX_INSERT_DASH_LOCALE@@</UserLocale>
+ <!-- UILanguage must match the installation media language. Stuff like de-CH does not work for
+ example de_windows_7_enterprise_with_sp1_x64_dvd_u_677649.iso. However, stupidly we cannot
+ omit this element (kudos to brilliant minds at MS). -->
+ <UILanguage>@@VBOX_INSERT_LANGUAGE@@</UILanguage>
+ </component>
+
+ <component name="Microsoft-Windows-Setup"
+ processorArchitecture="@@VBOX_INSERT_OS_ARCH_ATTRIB_DQ@@"
+ publicKeyToken="31bf3856ad364e35" language="neutral"
+ versionScope="nonSxS">
+
+ <DiskConfiguration>
+ <WillShowUI>OnError</WillShowUI>
+ <Disk>
+ <DiskID>0</DiskID>
+ <WillWipeDisk>true</WillWipeDisk>
+@@VBOX_COND_IS_NOT_FIRMWARE_UEFI@@
+ <CreatePartitions>
+ <!-- TODO: Use the standard partitioning scheme at starting with Windows 8 maybe, using 2 partitions as described by Microsoft? -->
+ <CreatePartition>
+ <Order>1</Order>
+ <Type>Primary</Type>
+ <Extend>true</Extend>
+ </CreatePartition>
+ </CreatePartitions>
+@@VBOX_COND_END@@
+@@VBOX_COND_IS_FIRMWARE_UEFI@@
+ <CreatePartitions>
+ <CreatePartition wcm:action="add">
+ <Order>1</Order>
+ <Type>Primary</Type>
+ <Size>300</Size>
+ </CreatePartition>
+ <CreatePartition wcm:action="add">
+ <Order>2</Order>
+ <Type>EFI</Type>
+ <Size>100</Size>
+ </CreatePartition>
+ <CreatePartition wcm:action="add">
+ <Order>3</Order>
+ <Type>MSR</Type>
+ <Size>128</Size>
+ </CreatePartition>
+ <CreatePartition wcm:action="add">
+ <Order>4</Order>
+ <Type>Primary</Type>
+ <Extend>true</Extend>
+ </CreatePartition>
+ </CreatePartitions>
+ <ModifyPartitions>
+ <ModifyPartition wcm:action="add">
+ <Order>1</Order>
+ <PartitionID>1</PartitionID>
+ <Label>WINRE</Label>
+ <Format>NTFS</Format>
+ <TypeID>de94bba4-06d1-4d40-a16a-bfd50179d6ac</TypeID>
+ </ModifyPartition>
+ <ModifyPartition wcm:action="add">
+ <Order>2</Order>
+ <PartitionID>2</PartitionID>
+ <Label>EFI</Label>
+ <Format>FAT32</Format>
+ </ModifyPartition>
+ <ModifyPartition wcm:action="add">
+ <Order>3</Order>
+ <PartitionID>3</PartitionID>
+ </ModifyPartition>
+ <ModifyPartition wcm:action="add">
+ <Order>4</Order>
+ <PartitionID>4</PartitionID>
+ <Label>Windows</Label>
+ <Letter>C</Letter>
+ <Format>NTFS</Format>
+ </ModifyPartition>
+ </ModifyPartitions>
+@@VBOX_COND_END@@
+ </Disk>
+ </DiskConfiguration>
+
+ <UserData>
+ <ProductKey>
+ <Key>@@VBOX_INSERT_PRODUCT_KEY_ELEMENT@@</Key>
+ <WillShowUI>OnError</WillShowUI>
+ </ProductKey>
+ <AcceptEula>true</AcceptEula>
+ </UserData>
+
+ <ImageInstall>
+ <OSImage>
+ <InstallFrom>
+ <!-- TODO: This stuff doesn't work for en_windows_vista_enterprise_sp1_x64_and_x86.iso ... -->
+ <MetaData wcm:action="add">
+ <Key>/IMAGE/INDEX</Key>
+ <Value>@@VBOX_INSERT_IMAGE_INDEX_ELEMENT@@</Value>
+ </MetaData>
+ <!-- <Path>d:\sources\install.wim</Path> - the w7 tests doesn't specify this -->
+ </InstallFrom>
+ <InstallTo>
+ <DiskID>0</DiskID>
+@@VBOX_COND_IS_NOT_FIRMWARE_UEFI@@
+ <PartitionID>1</PartitionID>
+@@VBOX_COND_END@@
+@@VBOX_COND_IS_FIRMWARE_UEFI@@
+ <PartitionID>4</PartitionID>
+@@VBOX_COND_END@@
+ </InstallTo>
+ <WillShowUI>OnError</WillShowUI>
+ <InstallToAvailablePartition>false</InstallToAvailablePartition>
+ </OSImage>
+ </ImageInstall>
+
+ <ComplianceCheck>
+ <DisplayReport>OnError</DisplayReport>
+ </ComplianceCheck>
+
+ <!-- Apply registry tweaks to Windows PE, skipping the checks in the Windows 11 setup program. This will not make it to the final install, and should do no harm with older Windows versions. -->
+ <RunAsynchronous>
+ <RunAsynchronousCommand>
+ <Order>1</Order>
+ <Path>reg.exe ADD HKLM\SYSTEM\Setup\LabConfig /v BypassCPUCheck /t REG_DWORD /d 1 /f</Path>
+ <Description>Windows 11 disable CPU check</Description>
+ </RunAsynchronousCommand>
+ <RunAsynchronousCommand>
+ <Order>2</Order>
+ <Path>reg.exe ADD HKLM\SYSTEM\Setup\LabConfig /v BypassRAMCheck /t REG_DWORD /d 1 /f</Path>
+ <Description>Windows 11 disable RAM check</Description>
+ </RunAsynchronousCommand>
+ <RunAsynchronousCommand>
+ <Order>3</Order>
+ <Path>reg.exe ADD HKLM\SYSTEM\Setup\LabConfig /v BypassSecureBootCheck /t REG_DWORD /d 1 /f</Path>
+ <Description>Windows 11 disable Secure Boot check</Description>
+ </RunAsynchronousCommand>
+ <RunAsynchronousCommand>
+ <Order>4</Order>
+ <Path>reg.exe ADD HKLM\SYSTEM\Setup\LabConfig /v BypassStorageCheck /t REG_DWORD /d 1 /f</Path>
+ <Description>Windows 11 disable Storage check</Description>
+ </RunAsynchronousCommand>
+ <RunAsynchronousCommand>
+ <Order>5</Order>
+ <Path>reg.exe ADD HKLM\SYSTEM\Setup\LabConfig /v BypassTPMCheck /t REG_DWORD /d 1 /f</Path>
+ <Description>Windows 11 disable TPM check</Description>
+ </RunAsynchronousCommand>
+ </RunAsynchronous>
+
+ </component>
+ </settings>
+
+ <settings pass="specialize">
+ <component name="Microsoft-Windows-Shell-Setup"
+ processorArchitecture="@@VBOX_INSERT_OS_ARCH_ATTRIB_DQ@@"
+ publicKeyToken="31bf3856ad364e35" language="neutral"
+ versionScope="nonSxS">
+ <ComputerName>@@VBOX_INSERT_HOSTNAME_WITHOUT_DOMAIN_MAX_15@@</ComputerName>
+ </component>
+
+ <component name="Microsoft-Windows-Deployment"
+ processorArchitecture="@@VBOX_INSERT_OS_ARCH_ATTRIB_DQ@@"
+ publicKeyToken="31bf3856ad364e35" language="neutral"
+ versionScope="nonSxS">
+
+ <!-- Apply registry tweaks in the final Windows install, skipping the checks in the Windows 11 setup program. This means upgrades started in this install will work without compatibility complaints. -->
+ <RunAsynchronous>
+ <RunAsynchronousCommand>
+ <Order>1</Order>
+ <Path>reg.exe ADD HKLM\SYSTEM\Setup\LabConfig /v BypassCPUCheck /t REG_DWORD /d 1 /f</Path>
+ <Description>Windows 11 disable CPU check</Description>
+ </RunAsynchronousCommand>
+ <RunAsynchronousCommand>
+ <Order>2</Order>
+ <Path>reg.exe ADD HKLM\SYSTEM\Setup\LabConfig /v BypassRAMCheck /t REG_DWORD /d 1 /f</Path>
+ <Description>Windows 11 disable RAM check</Description>
+ </RunAsynchronousCommand>
+ <RunAsynchronousCommand>
+ <Order>3</Order>
+ <Path>reg.exe ADD HKLM\SYSTEM\Setup\LabConfig /v BypassSecureBootCheck /t REG_DWORD /d 1 /f</Path>
+ <Description>Windows 11 disable Secure Boot check</Description>
+ </RunAsynchronousCommand>
+ <RunAsynchronousCommand>
+ <Order>4</Order>
+ <Path>reg.exe ADD HKLM\SYSTEM\Setup\LabConfig /v BypassStorageCheck /t REG_DWORD /d 1 /f</Path>
+ <Description>Windows 11 disable Storage check</Description>
+ </RunAsynchronousCommand>
+ <RunAsynchronousCommand>
+ <Order>5</Order>
+ <Path>reg.exe ADD HKLM\SYSTEM\Setup\LabConfig /v BypassTPMCheck /t REG_DWORD /d 1 /f</Path>
+ <Description>Windows 11 disable TPM check</Description>
+ </RunAsynchronousCommand>
+ </RunAsynchronous>
+ </component>
+ </settings>
+
+ <settings pass="oobeSystem">
+ <component name="Microsoft-Windows-Shell-Setup"
+ processorArchitecture="@@VBOX_INSERT_OS_ARCH_ATTRIB_DQ@@"
+ publicKeyToken="31bf3856ad364e35" language="neutral"
+ versionScope="nonSxS">
+ <AutoLogon>
+ <Password>
+ <Value>@@VBOX_INSERT_USER_PASSWORD_ELEMENT@@</Value>
+ <PlainText>true</PlainText>
+ </Password>
+ <Enabled>true</Enabled>
+ <Username>@@VBOX_INSERT_USER_LOGIN_ELEMENT@@</Username>
+ </AutoLogon>
+
+ <UserAccounts>
+@@VBOX_COND_IS_USER_LOGIN_NOT_ADMINISTRATOR@@
+ <AdministratorPassword>
+ <Value>@@VBOX_INSERT_ROOT_PASSWORD_ELEMENT@@</Value>
+ <PlainText>true</PlainText>
+ </AdministratorPassword>
+
+ <LocalAccounts>
+ <LocalAccount wcm:action="add">
+ <Name>@@VBOX_INSERT_USER_LOGIN_ELEMENT@@</Name>
+ <DisplayName>@@VBOX_INSERT_USER_FULL_NAME_ELEMENT@@</DisplayName>
+ <Group>administrators;users</Group>
+ <Password>
+ <Value>@@VBOX_INSERT_USER_PASSWORD_ELEMENT@@</Value>
+ <PlainText>true</PlainText>
+ </Password>
+ </LocalAccount>
+ </LocalAccounts>
+@@VBOX_COND_END@@
+@@VBOX_COND_IS_USER_LOGIN_ADMINISTRATOR@@
+ <AdministratorPassword>
+ <Value>@@VBOX_INSERT_USER_PASSWORD_ELEMENT@@</Value>
+ <PlainText>true</PlainText>
+ </AdministratorPassword>
+@@VBOX_COND_END@@
+ </UserAccounts>
+
+ <VisualEffects>
+ <FontSmoothing>ClearType</FontSmoothing>
+ </VisualEffects>
+
+ <OOBE>
+ <ProtectYourPC>3</ProtectYourPC>
+ <HideEULAPage>true</HideEULAPage>
+ <SkipUserOOBE>true</SkipUserOOBE>
+ <SkipMachineOOBE>true</SkipMachineOOBE>
+ <!-- Make this (NetworkLocation) default to public and make it configurable -->
+ <NetworkLocation>Home</NetworkLocation>
+ </OOBE>
+
+ <FirstLogonCommands>
+ <SynchronousCommand wcm:action="add">
+ <!-- For which OS versions do we need to do this? -->
+ <Order>1</Order>
+ <Description>Turn Off Network Selection pop-up</Description>
+ <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Network\NewNetworkWindowOff"</CommandLine>
+ </SynchronousCommand>
+ <SynchronousCommand wcm:action="add">
+ <Order>2</Order>
+ <Description>VirtualBox post guest install steps </Description>
+ <CommandLine>cmd.exe /c @@VBOX_INSERT_AUXILIARY_INSTALL_DIR@@VBOXPOST.CMD --vista-or-newer</CommandLine>
+ </SynchronousCommand>
+ </FirstLogonCommands>
+
+ <TimeZone>@@VBOX_INSERT_TIME_ZONE_WIN_NAME@@</TimeZone>
+ </component>
+
+ </settings>
+</unattend>
+
diff --git a/src/VBox/Main/UnattendedTemplates/win_postinstall.cmd b/src/VBox/Main/UnattendedTemplates/win_postinstall.cmd
new file mode 100644
index 00000000..4ebe802a
--- /dev/null
+++ b/src/VBox/Main/UnattendedTemplates/win_postinstall.cmd
@@ -0,0 +1,164 @@
+@echo off
+rem $Id: win_postinstall.cmd $
+rem rem @file
+rem Post installation script template for Windows.
+rem
+rem This runs after the target system has been booted, typically as
+rem part of the first logon.
+rem
+
+rem
+rem Copyright (C) 2017-2022 Oracle and/or its affiliates.
+rem
+rem This file is part of VirtualBox base platform packages, as
+rem available from https://www.virtualbox.org.
+rem
+rem This program is free software; you can redistribute it and/or
+rem modify it under the terms of the GNU General Public License
+rem as published by the Free Software Foundation, in version 3 of the
+rem License.
+rem
+rem This program is distributed in the hope that it will be useful, but
+rem WITHOUT ANY WARRANTY; without even the implied warranty of
+rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+rem General Public License for more details.
+rem
+rem You should have received a copy of the GNU General Public License
+rem along with this program; if not, see <https://www.gnu.org/licenses>.
+rem
+rem SPDX-License-Identifier: GPL-3.0-only
+rem
+
+rem Globals.
+set MY_LOG_FILE=C:\vboxpostinstall.log
+
+rem Log header.
+echo *** started >> %MY_LOG_FILE%
+echo *** CD=%CD% >> %MY_LOG_FILE%
+echo *** Environment BEGIN >> %MY_LOG_FILE%
+set >> %MY_LOG_FILE%
+echo *** Environment END >> %MY_LOG_FILE%
+
+@@VBOX_COND_HAS_PROXY@@
+set PROXY=@@VBOX_INSERT_PROXY@@
+set HTTP_PROXY=%PROXY%
+set HTTPS_PROXY=%PROXY%
+echo HTTP proxy is %HTTP_PROXY% >> %MY_LOG_FILE%
+echo HTTPS proxy is %HTTPS_PROXY% >> %MY_LOG_FILE%
+@@VBOX_COND_END@@
+
+@@VBOX_COND_IS_INSTALLING_ADDITIONS@@
+rem
+rem Install the guest additions.
+rem
+
+rem First find the CDROM with the GAs on them.
+set MY_VBOX_ADDITIONS=E:\vboxadditions
+if exist %MY_VBOX_ADDITIONS%\VBoxWindowsAdditions.exe goto found_vbox_additions
+set MY_VBOX_ADDITIONS=D:\vboxadditions
+if exist %MY_VBOX_ADDITIONS%\VBoxWindowsAdditions.exe goto found_vbox_additions
+set MY_VBOX_ADDITIONS=F:\vboxadditions
+if exist %MY_VBOX_ADDITIONS%\VBoxWindowsAdditions.exe goto found_vbox_additions
+set MY_VBOX_ADDITIONS=G:\vboxadditions
+if exist %MY_VBOX_ADDITIONS%\VBoxWindowsAdditions.exe goto found_vbox_additions
+set MY_VBOX_ADDITIONS=E:
+if exist %MY_VBOX_ADDITIONS%\VBoxWindowsAdditions.exe goto found_vbox_additions
+set MY_VBOX_ADDITIONS=F:
+if exist %MY_VBOX_ADDITIONS%\VBoxWindowsAdditions.exe goto found_vbox_additions
+set MY_VBOX_ADDITIONS=G:
+if exist %MY_VBOX_ADDITIONS%\VBoxWindowsAdditions.exe goto found_vbox_additions
+set MY_VBOX_ADDITIONS=D:
+if exist %MY_VBOX_ADDITIONS%\VBoxWindowsAdditions.exe goto found_vbox_additions
+set MY_VBOX_ADDITIONS=E:\vboxadditions
+:found_vbox_additions
+echo *** MY_VBOX_ADDITIONS=%MY_VBOX_ADDITIONS%\ >> %MY_LOG_FILE%
+
+rem Then add signing certificate to trusted publishers
+echo *** Running: %MY_VBOX_ADDITIONS%\cert\VBoxCertUtil.exe ... >> %MY_LOG_FILE%
+%MY_VBOX_ADDITIONS%\cert\VBoxCertUtil.exe add-trusted-publisher %MY_VBOX_ADDITIONS%\cert\vbox*.cer --root %MY_VBOX_ADDITIONS%\cert\vbox*.cer >> %MY_LOG_FILE% 2>&1
+echo *** ERRORLEVEL: %ERRORLEVEL% >> %MY_LOG_FILE%
+
+rem Then do the installation.
+echo *** Running: %MY_VBOX_ADDITIONS%\VBoxWindowsAdditions.exe /S >> %MY_LOG_FILE%
+%MY_VBOX_ADDITIONS%\VBoxWindowsAdditions.exe /S
+echo *** ERRORLEVEL: %ERRORLEVEL% >> %MY_LOG_FILE%
+
+@@VBOX_COND_END@@
+
+
+@@VBOX_COND_IS_INSTALLING_TEST_EXEC_SERVICE@@
+rem
+rem Install the Test Execution service
+rem
+
+rem First find the CDROM with the validation kit on it.
+set MY_VBOX_VALIDATION_KIT=E:\vboxvalidationkit
+if exist %MY_VBOX_VALIDATION_KIT%\vboxtxs-readme.txt goto found_vbox_validation_kit
+set MY_VBOX_VALIDATION_KIT=D:\vboxvalidationkit
+if exist %MY_VBOX_VALIDATION_KIT%\vboxtxs-readme.txt goto found_vbox_validation_kit
+set MY_VBOX_VALIDATION_KIT=F:\vboxvalidationkit
+if exist %MY_VBOX_VALIDATION_KIT%\vboxtxs-readme.txt goto found_vbox_validation_kit
+set MY_VBOX_VALIDATION_KIT=G:\vboxvalidationkit
+if exist %MY_VBOX_VALIDATION_KIT%\vboxtxs-readme.txt goto found_vbox_validation_kit
+set MY_VBOX_VALIDATION_KIT=E:
+if exist %MY_VBOX_VALIDATION_KIT%\vboxtxs-readme.txt goto found_vbox_validation_kit
+set MY_VBOX_VALIDATION_KIT=F:
+if exist %MY_VBOX_VALIDATION_KIT%\vboxtxs-readme.txt goto found_vbox_validation_kit
+set MY_VBOX_VALIDATION_KIT=G:
+if exist %MY_VBOX_VALIDATION_KIT%\vboxtxs-readme.txt goto found_vbox_validation_kit
+set MY_VBOX_VALIDATION_KIT=D:
+if exist %MY_VBOX_VALIDATION_KIT%\vboxtxs-readme.txt goto found_vbox_validation_kit
+set MY_VBOX_VALIDATION_KIT=E:\vboxvalidationkit
+:found_vbox_validation_kit
+echo *** MY_VBOX_VALIDATION_KIT=%MY_VBOX_VALIDATION_KIT%\ >> %MY_LOG_FILE%
+
+rem Copy over the files.
+echo *** Running: mkdir %SystemDrive%\Apps >> %MY_LOG_FILE%
+mkdir %SystemDrive%\Apps >> %MY_LOG_FILE% 2>&1
+echo *** ERRORLEVEL: %ERRORLEVEL% >> %MY_LOG_FILE%
+
+echo *** Running: copy %MY_VBOX_VALIDATION_KIT%\win\* %SystemDrive%\Apps >> %MY_LOG_FILE%
+copy %MY_VBOX_VALIDATION_KIT%\win\* %SystemDrive%\Apps >> %MY_LOG_FILE% 2>&1
+echo *** ERRORLEVEL: %ERRORLEVEL% >> %MY_LOG_FILE%
+
+echo *** Running: copy %MY_VBOX_VALIDATION_KIT%\win\%PROCESSOR_ARCHITECTURE%\* %SystemDrive%\Apps >> %MY_LOG_FILE%
+copy %MY_VBOX_VALIDATION_KIT%\win\%PROCESSOR_ARCHITECTURE%\* %SystemDrive%\Apps >> %MY_LOG_FILE% 2>&1
+echo *** ERRORLEVEL: %ERRORLEVEL% >> %MY_LOG_FILE%
+
+rem Configure the firewall to allow TXS to listen.
+echo *** Running: netsh firewall add portopening TCP 5048 "TestExecService 5048" >> %MY_LOG_FILE%
+netsh firewall add portopening TCP 5048 "TestExecService 5048" >> %MY_LOG_FILE% 2>&1
+echo *** ERRORLEVEL: %ERRORLEVEL% >> %MY_LOG_FILE%
+
+echo *** Running: netsh firewall add portopening TCP 5042 "TestExecService 5042" >> %MY_LOG_FILE%
+netsh firewall add portopening TCP 5042 "TestExecService 5042" >> %MY_LOG_FILE% 2>&1
+echo *** ERRORLEVEL: %ERRORLEVEL% >> %MY_LOG_FILE%
+
+rem Update the registry to autorun the service and make sure we've got autologon.
+echo *** Running: reg.exe ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run /v NTConfiguration /d %SystemDrive%\Apps\vboxtxs.cmd /f >> %MY_LOG_FILE%
+reg.exe ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run /v NTConfiguration /d %SystemDrive%\Apps\vboxtxs.cmd /f >> %MY_LOG_FILE% 2>&1
+echo *** ERRORLEVEL: %ERRORLEVEL% >> %MY_LOG_FILE%
+
+echo *** Running: reg.exe ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v PowerdownAfterShutdown /d 1 /f >> %MY_LOG_FILE%
+reg.exe ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v PowerdownAfterShutdown /d 1 /f >> %MY_LOG_FILE% 2>&1
+echo *** ERRORLEVEL: %ERRORLEVEL% >> %MY_LOG_FILE%
+
+echo *** Running: reg.exe ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v ForceAutoLogon /d 1 /f >> %MY_LOG_FILE%
+reg.exe ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v ForceAutoLogon /d 1 /f >> %MY_LOG_FILE% 2>&1
+echo *** ERRORLEVEL: %ERRORLEVEL% >> %MY_LOG_FILE%
+rem AutoAdminLogon too if administrator?
+
+@@VBOX_COND_END@@
+
+
+@@VBOX_COND_HAS_POST_INSTALL_COMMAND@@
+rem
+rem Run user command.
+rem
+echo *** Running custom user command ... >> %MY_LOG_FILE%
+echo *** Running: "@@VBOX_INSERT_POST_INSTALL_COMMAND@@" >> %MY_LOG_FILE%
+@@VBOX_INSERT_POST_INSTALL_COMMAND@@
+@@VBOX_COND_END@@
+
+echo *** done >> %MY_LOG_FILE%
+
diff --git a/src/VBox/Main/cbinding/Makefile.kmk b/src/VBox/Main/cbinding/Makefile.kmk
new file mode 100644
index 00000000..81507164
--- /dev/null
+++ b/src/VBox/Main/cbinding/Makefile.kmk
@@ -0,0 +1,158 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VBox C Binding.
+#
+
+#
+# Copyright (C) 2009-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+
+#
+# The samples
+#
+INSTALLS += CAPISamples
+CAPISamples_MODE = a+r,u+rw
+CAPISamples_INST = \
+ $(INST_SDK)bindings/c/samples/
+CAPISamples_SOURCES = \
+ tstCAPIGlue.c \
+ makefile.tstCAPIGlue=>Makefile
+
+#
+# The ???.
+#
+INSTALLS += CAPIGlue
+CAPIGlue_MODE = a+r,u+rw
+CAPIGlue_INST = \
+ $(INST_SDK)bindings/c/glue/
+CAPIGlue_SOURCES = \
+ VBoxCAPIGlue.c \
+ $(CAPIHeaders_0_OUTDIR)/VBoxCAPIGlue.h
+CAPIGlue_CLEAN = \
+ $(CAPIHeaders_0_OUTDIR)/VBoxCAPIGlue.h
+
+$$(CAPIHeaders_0_OUTDIR)/VBoxCAPIGlue.h: \
+ $(PATH_SUB_CURRENT)/VBoxCAPIGlue.h.in \
+ $(MAKEFILE_CURRENT) \
+ | $$(dir $$@)
+ $(call MSG_GENERATE,,$@)
+ $(QUIET)$(SED) \
+ -e 's/@VBOX_API_VERSION@/$(VBOX_API_VERSION)/' \
+ < $< > $@
+
+#
+# The ???.
+#
+INSTALLS += CAPIHeaders
+CAPIHeaders_MODE = a+r,u+rw
+CAPIHeaders_INST = $(INST_SDK)bindings/c/include/
+CAPIHeaders_SOURCES = \
+ $(CAPIHeaders_0_OUTDIR)/VBoxCAPI.h=>VBoxCAPI_v$(VBOX_API_VERSION).h
+CAPIHeaders_CLEAN = \
+ $(CAPIHeaders_0_OUTDIR)/VBoxCAPI.h
+
+$$(CAPIHeaders_0_OUTDIR)/VBoxCAPI.h: \
+ $(PATH_SUB_CURRENT)/capiidl.xsl \
+ $(PATH_SUB_CURRENT)/../idl/typemap-shared.inc.xsl \
+ $(VBOX_XIDL_FILE) \
+ | $$(dir $$@)
+ $(call MSG_TOOL,xsltproc,CAPIHeaders,$<,$@)
+ $(QUIET)$(VBOX_XSLTPROC) -o $@ $^
+
+if !defined(VBOX_ONLY_SDK)
+
+ #
+ # The C API binding utility DLL
+ #
+ DLLS += VBoxCAPI
+ VBoxCAPI_TEMPLATE = VBOXMAINCLIENTDLL
+ ifdef VBOX_WITH_XPCOM
+ # Keep old name on XPCOM so that legacy code is happy.
+ VBoxCAPI_INST = $(INST_BIN)VBoxXPCOMC$(VBOX_SUFF_DLL)
+ endif
+ VBoxCAPI_DEFS = IN_VBOXCAPI
+ VBoxCAPI_SOURCES = \
+ VBoxCAPI.cpp
+ VBoxCAPI_SOURCES.win = \
+ VBoxCAPI.rc
+ VBoxCAPI_INCS = \
+ $(CAPIHeaders_0_OUTDIR)
+ VBoxCAPI_INTERMEDIATES = \
+ $(CAPIHeaders_0_OUTDIR)/VBoxCAPI.h
+
+ #
+ # The C glue library.
+ #
+ LIBRARIES += VBoxCAPIGlue
+ VBoxCAPIGlue_TEMPLATE = VBOXMAINEXE
+ VBoxCAPIGlue_DEFS = IN_VBOXCAPI
+ VBoxCAPIGlue_SOURCES = \
+ VBoxCAPIGlue.c
+ ifdef VBOX_WITH_XPCOM
+ VBoxCAPIGlue_SOURCES += \
+ $(VBOX_PATH_SDK)/bindings/xpcom/lib/VirtualBox_i.c
+ else
+ VBoxCAPIGlue_SOURCES += \
+ $(VBOX_PATH_SDK)/bindings/mscom/lib/VirtualBox_i.c
+ endif
+ VBoxCAPIGlue_INCS = \
+ $(VBOX_PATH_SDK)/bindings/c/include \
+ $(VBOX_PATH_SDK)/bindings/c/glue
+ VBoxCAPIGlue_INTERMEDIATES = \
+ $(VBOX_PATH_SDK)/bindings/c/glue/VBoxCAPIGlue.h \
+ $(VBOX_PATH_SDK)/bindings/c/include/VBoxCAPI_v$(VBOX_API_VERSION).h
+
+ if defined(VBOX_WITH_TESTCASES) && "$(KBUILD_TARGET)" != "darwin"
+ #
+ # The testcase (also in samples).
+ # C testcase using the dynamic glue.
+ #
+ PROGRAMS += tstCAPIGlue
+ tstCAPIGlue_TEMPLATE = VBOXR3TSTEXE
+ tstCAPIGlue_INCS = \
+ $(VBOX_PATH_SDK)/bindings/c/include \
+ $(VBOX_PATH_SDK)/bindings/c/glue
+ ifdef VBOX_WITH_XPCOM
+ tstCAPIGlue_INCS += \
+ $(VBOX_PATH_SDK)/bindings/xpcom/include
+ else
+ tstCAPIGlue_INCS += \
+ $(VBOX_PATH_SDK)/bindings/mscom/include
+ endif
+ tstCAPIGlue_INTERMEDIATES = \
+ $(VBOX_PATH_SDK)/bindings/c/glue/VBoxCAPIGlue.h \
+ $(VBOX_PATH_SDK)/bindings/c/include/VBoxCAPI_v$(VBOX_API_VERSION).h \
+ $(if-expr !defined(VBOX_WITH_XPCOM),$(VBOX_PATH_SDK)/bindings/mscom/include/VirtualBox.h,)
+ tstCAPIGlue_SOURCES = \
+ tstCAPIGlue.c
+ tstCAPIGlue_LIBS = \
+ $(VBoxCAPIGlue_1_TARGET)
+ endif
+
+endif # ! VBOX_ONLY_SDK
+
+# generate rules.
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Main/cbinding/VBoxCAPI.cpp b/src/VBox/Main/cbinding/VBoxCAPI.cpp
new file mode 100644
index 00000000..a0030620
--- /dev/null
+++ b/src/VBox/Main/cbinding/VBoxCAPI.cpp
@@ -0,0 +1,1026 @@
+/* $Id: VBoxCAPI.cpp $ */
+/** @file VBoxCAPI.cpp
+ * Utility functions to use with the C API binding.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN
+
+#include "VBoxCAPI.h"
+
+#ifdef VBOX_WITH_XPCOM
+# include <nsMemory.h>
+# include <nsIServiceManager.h>
+# include <nsEventQueueUtils.h>
+# include <nsIExceptionService.h>
+# include <stdlib.h>
+#endif /* VBOX_WITH_XPCOM */
+
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+#include <iprt/uuid.h>
+#include <VBox/log.h>
+#include <VBox/version.h>
+
+#include "VBox/com/com.h"
+#include "VBox/com/NativeEventQueue.h"
+
+
+#ifndef RT_OS_DARWIN /* Probably not used for xpcom, so clang gets upset: error: using directive refers to implicitly-defined namespace 'std' [-Werror]*/
+using namespace std;
+#endif
+
+/* The following 2 object references should be eliminated once the legacy
+ * way to initialize the COM/XPCOM C bindings is removed. */
+static ISession *g_Session = NULL;
+static IVirtualBox *g_VirtualBox = NULL;
+
+#ifdef VBOX_WITH_XPCOM
+/* This object reference should be eliminated once the legacy way of handling
+ * the event queue (XPCOM specific) is removed. */
+static nsIEventQueue *g_EventQueue = NULL;
+#endif /* VBOX_WITH_XPCOM */
+
+static void VBoxComUninitialize(void);
+static void VBoxClientUninitialize(void);
+
+static int
+VBoxUtf16ToUtf8(CBSTR pwszString, char **ppszString)
+{
+ if (!pwszString)
+ {
+ *ppszString = NULL;
+ return VINF_SUCCESS;
+ }
+ return RTUtf16ToUtf8(pwszString, ppszString);
+}
+
+static int
+VBoxUtf8ToUtf16(const char *pszString, BSTR *ppwszString)
+{
+ *ppwszString = NULL;
+ if (!pszString)
+ return VINF_SUCCESS;
+#ifdef VBOX_WITH_XPCOM
+ return RTStrToUtf16(pszString, ppwszString);
+#else /* !VBOX_WITH_XPCOM */
+ PRTUTF16 pwsz;
+ int vrc = RTStrToUtf16(pszString, &pwsz);
+ if (RT_SUCCESS(vrc))
+ {
+ *ppwszString = ::SysAllocString(pwsz);
+ if (!*ppwszString)
+ vrc = VERR_NO_STR_MEMORY;
+ RTUtf16Free(pwsz);
+ }
+ return vrc;
+#endif /* !VBOX_WITH_XPCOM */
+}
+
+static void
+VBoxUtf8Clear(char *pszString)
+{
+ RT_BZERO(pszString, strlen(pszString));
+}
+
+static void
+VBoxUtf16Clear(BSTR pwszString)
+{
+ RT_BZERO(pwszString, RTUtf16Len(pwszString) * sizeof(RTUTF16));
+}
+
+static void
+VBoxUtf16Free(BSTR pwszString)
+{
+#ifdef VBOX_WITH_XPCOM
+ RTUtf16Free(pwszString);
+#else
+ ::SysFreeString(pwszString);
+#endif
+}
+
+static void
+VBoxUtf8Free(char *pszString)
+{
+ RTStrFree(pszString);
+}
+
+static void
+VBoxComUnallocString(BSTR pwsz)
+{
+ if (pwsz)
+ {
+#ifdef VBOX_WITH_XPCOM
+ nsMemory::Free(pwsz);
+#else
+ ::SysFreeString(pwsz);
+#endif
+ }
+}
+
+static void
+VBoxComUnallocMem(void *pv)
+{
+ VBoxComUnallocString((BSTR)pv);
+}
+
+static ULONG
+VBoxVTElemSize(VARTYPE vt)
+{
+ switch (vt)
+ {
+ case VT_BOOL:
+ case VT_I1:
+ case VT_UI1:
+ return 1;
+ case VT_I2:
+ case VT_UI2:
+ return 2;
+ case VT_I4:
+ case VT_UI4:
+ case VT_HRESULT:
+ return 4;
+ case VT_I8:
+ case VT_UI8:
+ return 8;
+ case VT_BSTR:
+ case VT_DISPATCH:
+ case VT_UNKNOWN:
+ return sizeof(void *);
+ default:
+ return 0;
+ }
+}
+
+static SAFEARRAY *
+VBoxSafeArrayCreateVector(VARTYPE vt, LONG lLbound, ULONG cElements)
+{
+#ifdef VBOX_WITH_XPCOM
+ NOREF(lLbound);
+ ULONG cbElement = VBoxVTElemSize(vt);
+ if (!cbElement)
+ return NULL;
+ SAFEARRAY *psa = (SAFEARRAY *)RTMemAllocZ(sizeof(SAFEARRAY));
+ if (!psa)
+ return psa;
+ if (cElements)
+ {
+ void *pv = nsMemory::Alloc(cElements * cbElement);
+ if (!pv)
+ {
+ RTMemFree(psa);
+ return NULL;
+ }
+ psa->pv = pv;
+ psa->c = cElements;
+ }
+ return psa;
+#else /* !VBOX_WITH_XPCOM */
+ return SafeArrayCreateVector(vt, lLbound, cElements);
+#endif /* !VBOX_WITH_XPCOM */
+}
+
+static SAFEARRAY *
+VBoxSafeArrayOutParamAlloc(void)
+{
+#ifdef VBOX_WITH_XPCOM
+ return (SAFEARRAY *)RTMemAllocZ(sizeof(SAFEARRAY));
+#else /* !VBOX_WITH_XPCOM */
+ return NULL;
+#endif /* !VBOX_WITH_XPCOM */
+}
+
+static HRESULT
+VBoxSafeArrayDestroy(SAFEARRAY *psa)
+{
+#ifdef VBOX_WITH_XPCOM
+ if (psa)
+ {
+ if (psa->pv)
+ nsMemory::Free(psa->pv);
+ RTMemFree(psa);
+ }
+ return S_OK;
+#else /* !VBOX_WITH_XPCOM */
+ VARTYPE vt = VT_UNKNOWN;
+ HRESULT rc = SafeArrayGetVartype(psa, &vt);
+ if (FAILED(rc))
+ return rc;
+ if (vt == VT_BSTR)
+ {
+ /* Special treatment: strings are to be freed explicitly, see sample
+ * C binding code, so zap it here. No way to reach compatible code
+ * behavior between COM and XPCOM without this kind of trickery. */
+ void *pData;
+ rc = SafeArrayAccessData(psa, &pData);
+ if (FAILED(rc))
+ return rc;
+ ULONG cbElement = VBoxVTElemSize(vt);
+ if (!cbElement)
+ return E_INVALIDARG;
+ Assert(cbElement = psa->cbElements);
+ ULONG cElements = psa->rgsabound[0].cElements;
+ memset(pData, '\0', cbElement * cElements);
+ SafeArrayUnaccessData(psa);
+ }
+ return SafeArrayDestroy(psa);
+#endif /* !VBOX_WITH_XPCOM */
+}
+
+static HRESULT
+VBoxSafeArrayCopyInParamHelper(SAFEARRAY *psa, const void *pv, ULONG cb)
+{
+ if (!pv || !psa)
+ return E_POINTER;
+ if (!cb)
+ return S_OK;
+
+ void *pData;
+#ifdef VBOX_WITH_XPCOM
+ pData = psa->pv;
+#else /* !VBOX_WITH_XPCOM */
+ HRESULT rc = SafeArrayAccessData(psa, &pData);
+ if (FAILED(rc))
+ return rc;
+#endif /* !VBOX_WITH_XPCOM */
+ memcpy(pData, pv, cb);
+#ifndef VBOX_WITH_XPCOM
+ SafeArrayUnaccessData(psa);
+#endif
+ return S_OK;
+}
+
+static HRESULT
+VBoxSafeArrayCopyOutParamHelper(void **ppv, ULONG *pcb, VARTYPE vt, SAFEARRAY *psa)
+{
+ if (!ppv)
+ return E_POINTER;
+ ULONG cbElement = VBoxVTElemSize(vt);
+ if (!cbElement)
+ {
+ *ppv = NULL;
+ if (pcb)
+ *pcb = 0;
+ return E_INVALIDARG;
+ }
+#ifndef VBOX_WITH_XPCOM
+ if (psa->cDims != 1)
+ {
+ *ppv = NULL;
+ if (pcb)
+ *pcb = 0;
+ return E_INVALIDARG;
+ }
+ Assert(cbElement = psa->cbElements);
+#endif /* !VBOX_WITH_XPCOM */
+ void *pData;
+ ULONG cElements;
+#ifdef VBOX_WITH_XPCOM
+ pData = psa->pv;
+ cElements = psa->c;
+#else /* !VBOX_WITH_XPCOM */
+ HRESULT rc = SafeArrayAccessData(psa, &pData);
+ if (FAILED(rc))
+ {
+ *ppv = NULL;
+ if (pcb)
+ *pcb = 0;
+ return rc;
+ }
+ cElements = psa->rgsabound[0].cElements;
+#endif /* !VBOX_WITH_XPCOM */
+ size_t cbTotal = cbElement * cElements;
+ void *pv = NULL;
+ if (cbTotal)
+ {
+ pv = malloc(cbTotal);
+ if (!pv)
+ {
+ *ppv = NULL;
+ if (pcb)
+ *pcb = 0;
+ return E_OUTOFMEMORY;
+ }
+ else
+ memcpy(pv, pData, cbTotal);
+ }
+ *ppv = pv;
+ if (pcb)
+ *pcb = (ULONG)cbTotal;
+#ifndef VBOX_WITH_XPCOM
+ SafeArrayUnaccessData(psa);
+#endif
+ return S_OK;
+}
+
+static HRESULT
+VBoxSafeArrayCopyOutIfaceParamHelper(IUnknown ***ppaObj, ULONG *pcObj, SAFEARRAY *psa)
+{
+ ULONG mypcb;
+ HRESULT rc = VBoxSafeArrayCopyOutParamHelper((void **)ppaObj, &mypcb, VT_UNKNOWN, psa);
+ if (FAILED(rc))
+ {
+ if (pcObj)
+ *pcObj = 0;
+ return rc;
+ }
+ ULONG cElements = mypcb / sizeof(void *);
+ if (pcObj)
+ *pcObj = cElements;
+#ifndef VBOX_WITH_XPCOM
+ /* Do this only for COM, as there the SAFEARRAY destruction will release
+ * the contained references automatically. XPCOM doesn't do that, which
+ * means that copying implicitly transfers ownership. */
+ IUnknown **paObj = *ppaObj;
+ for (ULONG i = 0; i < cElements; i++)
+ {
+ IUnknown *pObj = paObj[i];
+ if (pObj)
+ pObj->AddRef();
+ }
+#endif /* VBOX_WITH_XPCOM */
+ return S_OK;
+}
+
+static HRESULT
+VBoxArrayOutFree(void *pv)
+{
+ free(pv);
+ return S_OK;
+}
+
+static void
+VBoxComInitialize(const char *pszVirtualBoxIID, IVirtualBox **ppVirtualBox,
+ const char *pszSessionIID, ISession **ppSession)
+{
+ int vrc;
+ IID virtualBoxIID;
+ IID sessionIID;
+
+ *ppSession = NULL;
+ *ppVirtualBox = NULL;
+
+ /* convert the string representation of the UUIDs (if provided) to IID */
+ if (pszVirtualBoxIID && *pszVirtualBoxIID)
+ {
+ vrc = ::RTUuidFromStr((RTUUID *)&virtualBoxIID, pszVirtualBoxIID);
+ if (RT_FAILURE(vrc))
+ return;
+ }
+ else
+ virtualBoxIID = IID_IVirtualBox;
+ if (pszSessionIID && *pszSessionIID)
+ {
+ vrc = ::RTUuidFromStr((RTUUID *)&sessionIID, pszSessionIID);
+ if (RT_FAILURE(vrc))
+ return;
+ }
+ else
+ sessionIID = IID_ISession;
+
+ HRESULT rc = com::Initialize(VBOX_COM_INIT_F_DEFAULT | VBOX_COM_INIT_F_NO_COM_PATCHING);
+ if (FAILED(rc))
+ {
+ Log(("Cbinding: COM/XPCOM could not be initialized! rc=%Rhrc\n", rc));
+ VBoxComUninitialize();
+ return;
+ }
+
+#ifdef VBOX_WITH_XPCOM
+ rc = NS_GetMainEventQ(&g_EventQueue);
+ if (FAILED(rc))
+ {
+ Log(("Cbinding: Could not get XPCOM event queue! rc=%Rhrc\n", rc));
+ VBoxComUninitialize();
+ return;
+ }
+#endif /* VBOX_WITH_XPCOM */
+
+#ifdef VBOX_WITH_XPCOM
+ nsIComponentManager *pManager;
+ rc = NS_GetComponentManager(&pManager);
+ if (FAILED(rc))
+ {
+ Log(("Cbinding: Could not get component manager! rc=%Rhrc\n", rc));
+ VBoxComUninitialize();
+ return;
+ }
+
+ rc = pManager->CreateInstanceByContractID(NS_VIRTUALBOX_CONTRACTID,
+ nsnull,
+ virtualBoxIID,
+ (void **)&g_VirtualBox);
+#else /* !VBOX_WITH_XPCOM */
+ IVirtualBoxClient *pVirtualBoxClient;
+ rc = CoCreateInstance(CLSID_VirtualBoxClient, NULL, CLSCTX_INPROC_SERVER, IID_IVirtualBoxClient, (void **)&pVirtualBoxClient);
+ if (SUCCEEDED(rc))
+ {
+ IVirtualBox *pVirtualBox;
+ rc = pVirtualBoxClient->get_VirtualBox(&pVirtualBox);
+ if (SUCCEEDED(rc))
+ {
+ rc = pVirtualBox->QueryInterface(virtualBoxIID, (void **)&g_VirtualBox);
+ pVirtualBox->Release();
+ }
+ pVirtualBoxClient->Release();
+ }
+#endif /* !VBOX_WITH_XPCOM */
+ if (FAILED(rc))
+ {
+ Log(("Cbinding: Could not instantiate VirtualBox object! rc=%Rhrc\n",rc));
+#ifdef VBOX_WITH_XPCOM
+ pManager->Release();
+ pManager = NULL;
+#endif /* VBOX_WITH_XPCOM */
+ VBoxComUninitialize();
+ return;
+ }
+
+ Log(("Cbinding: IVirtualBox object created.\n"));
+
+#ifdef VBOX_WITH_XPCOM
+ rc = pManager->CreateInstanceByContractID(NS_SESSION_CONTRACTID,
+ nsnull,
+ sessionIID,
+ (void **)&g_Session);
+#else /* !VBOX_WITH_XPCOM */
+ rc = CoCreateInstance(CLSID_Session, NULL, CLSCTX_INPROC_SERVER, sessionIID, (void **)&g_Session);
+#endif /* !VBOX_WITH_XPCOM */
+ if (FAILED(rc))
+ {
+ Log(("Cbinding: Could not instantiate Session object! rc=%Rhrc\n",rc));
+#ifdef VBOX_WITH_XPCOM
+ pManager->Release();
+ pManager = NULL;
+#endif /* VBOX_WITH_XPCOM */
+ VBoxComUninitialize();
+ return;
+ }
+
+ Log(("Cbinding: ISession object created.\n"));
+
+#ifdef VBOX_WITH_XPCOM
+ pManager->Release();
+ pManager = NULL;
+#endif /* VBOX_WITH_XPCOM */
+
+ *ppSession = g_Session;
+ *ppVirtualBox = g_VirtualBox;
+}
+
+static void
+VBoxComInitializeV1(IVirtualBox **ppVirtualBox, ISession **ppSession)
+{
+ VBoxComInitialize(NULL, ppVirtualBox, NULL, ppSession);
+}
+
+static void
+VBoxComUninitialize(void)
+{
+ if (g_Session)
+ {
+ g_Session->Release();
+ g_Session = NULL;
+ }
+ if (g_VirtualBox)
+ {
+ g_VirtualBox->Release();
+ g_VirtualBox = NULL;
+ }
+#ifdef VBOX_WITH_XPCOM
+ if (g_EventQueue)
+ {
+ g_EventQueue->Release();
+ g_EventQueue = NULL;
+ }
+#endif /* VBOX_WITH_XPCOM */
+ com::Shutdown();
+ Log(("Cbinding: Cleaned up the created objects.\n"));
+}
+
+#ifdef VBOX_WITH_XPCOM
+static void
+VBoxGetEventQueue(nsIEventQueue **ppEventQueue)
+{
+ *ppEventQueue = g_EventQueue;
+}
+#endif /* VBOX_WITH_XPCOM */
+
+static int
+VBoxProcessEventQueue(LONG64 iTimeoutMS)
+{
+ RTMSINTERVAL iTimeout;
+ if (iTimeoutMS < 0 || iTimeoutMS > UINT32_MAX)
+ iTimeout = RT_INDEFINITE_WAIT;
+ else
+ iTimeout = (RTMSINTERVAL)iTimeoutMS;
+ int vrc = com::NativeEventQueue::getMainEventQueue()->processEventQueue(iTimeout);
+ switch (vrc)
+ {
+ case VINF_SUCCESS:
+ return 0;
+ case VINF_INTERRUPTED:
+ return 1;
+ case VERR_INTERRUPTED:
+ return 2;
+ case VERR_TIMEOUT:
+ return 3;
+ case VERR_INVALID_CONTEXT:
+ return 4;
+ default:
+ return 5;
+ }
+}
+
+static int
+VBoxInterruptEventQueueProcessing(void)
+{
+ com::NativeEventQueue::getMainEventQueue()->interruptEventQueueProcessing();
+ return 0;
+}
+
+static HRESULT
+VBoxGetException(IErrorInfo **ppException)
+{
+ HRESULT rc;
+
+ *ppException = NULL;
+
+#ifdef VBOX_WITH_XPCOM
+ nsIServiceManager *mgr = NULL;
+ rc = NS_GetServiceManager(&mgr);
+ if (FAILED(rc) || !mgr)
+ return rc;
+
+ IID esid = NS_IEXCEPTIONSERVICE_IID;
+ nsIExceptionService *es = NULL;
+ rc = mgr->GetServiceByContractID(NS_EXCEPTIONSERVICE_CONTRACTID, esid, (void **)&es);
+ if (FAILED(rc) || !es)
+ {
+ mgr->Release();
+ return rc;
+ }
+
+ nsIExceptionManager *em;
+ rc = es->GetCurrentExceptionManager(&em);
+ if (FAILED(rc) || !em)
+ {
+ es->Release();
+ mgr->Release();
+ return rc;
+ }
+
+ nsIException *ex;
+ rc = em->GetCurrentException(&ex);
+ if (FAILED(rc))
+ {
+ em->Release();
+ es->Release();
+ mgr->Release();
+ return rc;
+ }
+
+ *ppException = ex;
+ em->Release();
+ es->Release();
+ mgr->Release();
+#else /* !VBOX_WITH_XPCOM */
+ IErrorInfo *ex;
+ rc = ::GetErrorInfo(0, &ex);
+ if (FAILED(rc))
+ return rc;
+
+ *ppException = ex;
+#endif /* !VBOX_WITH_XPCOM */
+
+ return rc;
+}
+
+static HRESULT
+VBoxClearException(void)
+{
+ HRESULT rc;
+
+#ifdef VBOX_WITH_XPCOM
+ nsIServiceManager *mgr = NULL;
+ rc = NS_GetServiceManager(&mgr);
+ if (FAILED(rc) || !mgr)
+ return rc;
+
+ IID esid = NS_IEXCEPTIONSERVICE_IID;
+ nsIExceptionService *es = NULL;
+ rc = mgr->GetServiceByContractID(NS_EXCEPTIONSERVICE_CONTRACTID, esid, (void **)&es);
+ if (FAILED(rc) || !es)
+ {
+ mgr->Release();
+ return rc;
+ }
+
+ nsIExceptionManager *em;
+ rc = es->GetCurrentExceptionManager(&em);
+ if (FAILED(rc) || !em)
+ {
+ es->Release();
+ mgr->Release();
+ return rc;
+ }
+
+ rc = em->SetCurrentException(NULL);
+ em->Release();
+ es->Release();
+ mgr->Release();
+#else /* !VBOX_WITH_XPCOM */
+ rc = ::SetErrorInfo(0, NULL);
+#endif /* !VBOX_WITH_XPCOM */
+
+ return rc;
+}
+
+static HRESULT
+VBoxClientInitialize(const char *pszVirtualBoxClientIID, IVirtualBoxClient **ppVirtualBoxClient)
+{
+ IID virtualBoxClientIID;
+
+ *ppVirtualBoxClient = NULL;
+
+ /* convert the string representation of UUID to IID type */
+ if (pszVirtualBoxClientIID && *pszVirtualBoxClientIID)
+ {
+ int vrc = ::RTUuidFromStr((RTUUID *)&virtualBoxClientIID, pszVirtualBoxClientIID);
+ if (RT_FAILURE(vrc))
+ return E_INVALIDARG;
+ }
+ else
+ virtualBoxClientIID = IID_IVirtualBoxClient;
+
+ HRESULT rc = com::Initialize(VBOX_COM_INIT_F_DEFAULT | VBOX_COM_INIT_F_NO_COM_PATCHING);
+ if (FAILED(rc))
+ {
+ Log(("Cbinding: COM/XPCOM could not be initialized! rc=%Rhrc\n", rc));
+ VBoxClientUninitialize();
+ return rc;
+ }
+
+#ifdef VBOX_WITH_XPCOM
+ rc = NS_GetMainEventQ(&g_EventQueue);
+ if (NS_FAILED(rc))
+ {
+ Log(("Cbinding: Could not get XPCOM event queue! rc=%Rhrc\n", rc));
+ VBoxClientUninitialize();
+ return rc;
+ }
+#endif /* VBOX_WITH_XPCOM */
+
+#ifdef VBOX_WITH_XPCOM
+ nsIComponentManager *pManager;
+ rc = NS_GetComponentManager(&pManager);
+ if (FAILED(rc))
+ {
+ Log(("Cbinding: Could not get component manager! rc=%Rhrc\n", rc));
+ VBoxClientUninitialize();
+ return rc;
+ }
+
+ rc = pManager->CreateInstanceByContractID(NS_VIRTUALBOXCLIENT_CONTRACTID,
+ nsnull,
+ virtualBoxClientIID,
+ (void **)ppVirtualBoxClient);
+#else /* !VBOX_WITH_XPCOM */
+ rc = CoCreateInstance(CLSID_VirtualBoxClient, NULL, CLSCTX_INPROC_SERVER, virtualBoxClientIID, (void **)ppVirtualBoxClient);
+#endif /* !VBOX_WITH_XPCOM */
+ if (FAILED(rc))
+ {
+ Log(("Cbinding: Could not instantiate VirtualBoxClient object! rc=%Rhrc\n",rc));
+#ifdef VBOX_WITH_XPCOM
+ pManager->Release();
+ pManager = NULL;
+#endif /* VBOX_WITH_XPCOM */
+ VBoxClientUninitialize();
+ return rc;
+ }
+
+#ifdef VBOX_WITH_XPCOM
+ pManager->Release();
+ pManager = NULL;
+#endif /* VBOX_WITH_XPCOM */
+
+ Log(("Cbinding: IVirtualBoxClient object created.\n"));
+
+ return S_OK;
+}
+
+static HRESULT
+VBoxClientThreadInitialize(void)
+{
+ return com::Initialize(VBOX_COM_INIT_F_DEFAULT | VBOX_COM_INIT_F_NO_COM_PATCHING);
+}
+
+static HRESULT
+VBoxClientThreadUninitialize(void)
+{
+ return com::Shutdown();
+}
+
+static void
+VBoxClientUninitialize(void)
+{
+#ifdef VBOX_WITH_XPCOM
+ if (g_EventQueue)
+ {
+ NS_RELEASE(g_EventQueue);
+ g_EventQueue = NULL;
+ }
+#endif /* VBOX_WITH_XPCOM */
+ com::Shutdown();
+ Log(("Cbinding: Cleaned up the created objects.\n"));
+}
+
+static unsigned int
+VBoxVersion(void)
+{
+ return VBOX_VERSION_MAJOR * 1000 * 1000 + VBOX_VERSION_MINOR * 1000 + VBOX_VERSION_BUILD;
+}
+
+static unsigned int
+VBoxAPIVersion(void)
+{
+ return VBOX_VERSION_MAJOR * 1000 + VBOX_VERSION_MINOR + (VBOX_VERSION_BUILD > 50 ? 1 : 0);
+}
+
+VBOXCAPI_DECL(PCVBOXCAPI)
+VBoxGetCAPIFunctions(unsigned uVersion)
+{
+ /* This is the first piece of code which knows that IPRT exists, so
+ * initialize it properly. The limited initialization in VBoxC is not
+ * sufficient, and causes trouble with com::Initialize() misbehaving. */
+ RTR3InitDll(0);
+
+ /*
+ * The current interface version.
+ */
+ static const VBOXCAPI s_Functions =
+ {
+ sizeof(VBOXCAPI),
+ VBOX_CAPI_VERSION,
+
+ VBoxVersion,
+ VBoxAPIVersion,
+
+ VBoxClientInitialize,
+ VBoxClientThreadInitialize,
+ VBoxClientThreadUninitialize,
+ VBoxClientUninitialize,
+
+ VBoxComInitialize,
+ VBoxComUninitialize,
+
+ VBoxComUnallocString,
+
+ VBoxUtf16ToUtf8,
+ VBoxUtf8ToUtf16,
+ VBoxUtf8Free,
+ VBoxUtf16Free,
+
+ VBoxSafeArrayCreateVector,
+ VBoxSafeArrayOutParamAlloc,
+ VBoxSafeArrayCopyInParamHelper,
+ VBoxSafeArrayCopyOutParamHelper,
+ VBoxSafeArrayCopyOutIfaceParamHelper,
+ VBoxSafeArrayDestroy,
+ VBoxArrayOutFree,
+
+#ifdef VBOX_WITH_XPCOM
+ VBoxGetEventQueue,
+#endif /* VBOX_WITH_XPCOM */
+ VBoxGetException,
+ VBoxClearException,
+ VBoxProcessEventQueue,
+ VBoxInterruptEventQueueProcessing,
+
+ VBoxUtf8Clear,
+ VBoxUtf16Clear,
+
+ VBOX_CAPI_VERSION
+ };
+
+ if ((uVersion & 0xffff0000U) == (VBOX_CAPI_VERSION & 0xffff0000U))
+ return &s_Functions;
+
+ /*
+ * Legacy interface version 3.0.
+ */
+ static const struct VBOXCAPIV3
+ {
+ /** The size of the structure. */
+ unsigned cb;
+ /** The structure version. */
+ unsigned uVersion;
+
+ unsigned int (*pfnGetVersion)(void);
+
+ unsigned int (*pfnGetAPIVersion)(void);
+
+ HRESULT (*pfnClientInitialize)(const char *pszVirtualBoxClientIID,
+ IVirtualBoxClient **ppVirtualBoxClient);
+ void (*pfnClientUninitialize)(void);
+
+ void (*pfnComInitialize)(const char *pszVirtualBoxIID,
+ IVirtualBox **ppVirtualBox,
+ const char *pszSessionIID,
+ ISession **ppSession);
+
+ void (*pfnComUninitialize)(void);
+
+ void (*pfnComUnallocMem)(void *pv);
+
+ int (*pfnUtf16ToUtf8)(CBSTR pwszString, char **ppszString);
+ int (*pfnUtf8ToUtf16)(const char *pszString, BSTR *ppwszString);
+ void (*pfnUtf8Free)(char *pszString);
+ void (*pfnUtf16Free)(BSTR pwszString);
+
+#ifdef VBOX_WITH_XPCOM
+ void (*pfnGetEventQueue)(nsIEventQueue **ppEventQueue);
+#endif /* VBOX_WITH_XPCOM */
+ HRESULT (*pfnGetException)(IErrorInfo **ppException);
+ HRESULT (*pfnClearException)(void);
+
+ /** Tail version, same as uVersion. */
+ unsigned uEndVersion;
+ } s_Functions_v3_0 =
+ {
+ sizeof(s_Functions_v3_0),
+ 0x00030000U,
+
+ VBoxVersion,
+ VBoxAPIVersion,
+
+ VBoxClientInitialize,
+ VBoxClientUninitialize,
+
+ VBoxComInitialize,
+ VBoxComUninitialize,
+
+ VBoxComUnallocMem,
+
+ VBoxUtf16ToUtf8,
+ VBoxUtf8ToUtf16,
+ VBoxUtf8Free,
+ VBoxUtf16Free,
+
+#ifdef VBOX_WITH_XPCOM
+ VBoxGetEventQueue,
+#endif /* VBOX_WITH_XPCOM */
+ VBoxGetException,
+ VBoxClearException,
+
+ 0x00030000U
+ };
+
+ if ((uVersion & 0xffff0000U) == 0x00030000U)
+ return (PCVBOXCAPI)&s_Functions_v3_0;
+
+ /*
+ * Legacy interface version 2.0.
+ */
+ static const struct VBOXCAPIV2
+ {
+ /** The size of the structure. */
+ unsigned cb;
+ /** The structure version. */
+ unsigned uVersion;
+
+ unsigned int (*pfnGetVersion)(void);
+
+ void (*pfnComInitialize)(const char *pszVirtualBoxIID,
+ IVirtualBox **ppVirtualBox,
+ const char *pszSessionIID,
+ ISession **ppSession);
+
+ void (*pfnComUninitialize)(void);
+
+ void (*pfnComUnallocMem)(void *pv);
+ void (*pfnUtf16Free)(BSTR pwszString);
+ void (*pfnUtf8Free)(char *pszString);
+
+ int (*pfnUtf16ToUtf8)(CBSTR pwszString, char **ppszString);
+ int (*pfnUtf8ToUtf16)(const char *pszString, BSTR *ppwszString);
+
+#ifdef VBOX_WITH_XPCOM
+ void (*pfnGetEventQueue)(nsIEventQueue **ppEventQueue);
+#endif /* VBOX_WITH_XPCOM */
+
+ /** Tail version, same as uVersion. */
+ unsigned uEndVersion;
+ } s_Functions_v2_0 =
+ {
+ sizeof(s_Functions_v2_0),
+ 0x00020000U,
+
+ VBoxVersion,
+
+ VBoxComInitialize,
+ VBoxComUninitialize,
+
+ VBoxComUnallocMem,
+ VBoxUtf16Free,
+ VBoxUtf8Free,
+
+ VBoxUtf16ToUtf8,
+ VBoxUtf8ToUtf16,
+
+#ifdef VBOX_WITH_XPCOM
+ VBoxGetEventQueue,
+#endif /* VBOX_WITH_XPCOM */
+
+ 0x00020000U
+ };
+
+ if ((uVersion & 0xffff0000U) == 0x00020000U)
+ return (PCVBOXCAPI)&s_Functions_v2_0;
+
+ /*
+ * Legacy interface version 1.0.
+ */
+ static const struct VBOXCAPIV1
+ {
+ /** The size of the structure. */
+ unsigned cb;
+ /** The structure version. */
+ unsigned uVersion;
+
+ unsigned int (*pfnGetVersion)(void);
+
+ void (*pfnComInitialize)(IVirtualBox **virtualBox, ISession **session);
+ void (*pfnComUninitialize)(void);
+
+ void (*pfnComUnallocMem)(void *pv);
+ void (*pfnUtf16Free)(BSTR pwszString);
+ void (*pfnUtf8Free)(char *pszString);
+
+ int (*pfnUtf16ToUtf8)(CBSTR pwszString, char **ppszString);
+ int (*pfnUtf8ToUtf16)(const char *pszString, BSTR *ppwszString);
+
+ /** Tail version, same as uVersion. */
+ unsigned uEndVersion;
+ } s_Functions_v1_0 =
+ {
+ sizeof(s_Functions_v1_0),
+ 0x00010000U,
+
+ VBoxVersion,
+
+ VBoxComInitializeV1,
+ VBoxComUninitialize,
+
+ VBoxComUnallocMem,
+ VBoxUtf16Free,
+ VBoxUtf8Free,
+
+ VBoxUtf16ToUtf8,
+ VBoxUtf8ToUtf16,
+
+ 0x00010000U
+ };
+
+ if ((uVersion & 0xffff0000U) == 0x00010000U)
+ return (PCVBOXCAPI)&s_Functions_v1_0;
+
+ /*
+ * Unsupported interface version.
+ */
+ return NULL;
+}
+
+#ifdef VBOX_WITH_XPCOM
+VBOXCAPI_DECL(PCVBOXCAPI)
+VBoxGetXPCOMCFunctions(unsigned uVersion)
+{
+ return VBoxGetCAPIFunctions(uVersion);
+}
+#endif /* VBOX_WITH_XPCOM */
+/* vim: set ts=4 sw=4 et: */
diff --git a/src/VBox/Main/cbinding/VBoxCAPI.rc b/src/VBox/Main/cbinding/VBoxCAPI.rc
new file mode 100644
index 00000000..c9913b01
--- /dev/null
+++ b/src/VBox/Main/cbinding/VBoxCAPI.rc
@@ -0,0 +1,61 @@
+/* $Id: VBoxCAPI.rc $ */
+/** @file
+ * VBoxCAPI - Resource file containing version info.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox C Bindings\0"
+ VALUE "InternalName", "VBoxCAPI\0"
+ VALUE "OriginalFilename", "VBoxCAPI.dll\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Main/cbinding/VBoxCAPIGlue.c b/src/VBox/Main/cbinding/VBoxCAPIGlue.c
new file mode 100644
index 00000000..aa909bdf
--- /dev/null
+++ b/src/VBox/Main/cbinding/VBoxCAPIGlue.c
@@ -0,0 +1,345 @@
+/* $Id: VBoxCAPIGlue.c $ */
+/** @file
+ * Glue code for dynamically linking to VBoxCAPI.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+/* NOTE: do NOT use any include files here which are only available in the
+ * VirtualBox tree, e.g. iprt. They are not available in the SDK, which is
+ * where this file will provided as source code and has to be compilable. */
+#include "VBoxCAPIGlue.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#ifdef WIN32
+# define _INTPTR 2 /* on Windows stdint.h compares this in #if, causing warnings if not defined */
+#endif /* WIN32 */
+#include <stdint.h>
+#ifndef WIN32
+# include <dlfcn.h>
+# include <pthread.h>
+#else /* WIN32 */
+# include <Windows.h>
+#endif /* WIN32 */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#if defined(__linux__) || defined(__linux_gnu__) || defined(__sun__) || defined(__FreeBSD__)
+# define DYNLIB_NAME "VBoxXPCOMC.so"
+#elif defined(__APPLE__)
+# define DYNLIB_NAME "VBoxXPCOMC.dylib"
+#elif defined(__OS2__)
+# define DYNLIB_NAME "VBoxXPCOMC.dll"
+#elif defined(WIN32)
+# define DYNLIB_NAME "VBoxCAPI.dll"
+#else
+# error "Port me"
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The so/dynsym/dll handle for VBoxCAPI. */
+#ifndef WIN32
+void *g_hVBoxCAPI = NULL;
+#else /* WIN32 */
+HMODULE g_hVBoxCAPI = NULL;
+#endif /* WIN32 */
+/** The last load error. */
+char g_szVBoxErrMsg[256] = "";
+/** Pointer to the VBOXCAPI function table. */
+PCVBOXCAPI g_pVBoxFuncs = NULL;
+/** Pointer to VBoxGetCAPIFunctions for the loaded VBoxCAPI so/dylib/dll. */
+PFNVBOXGETCAPIFUNCTIONS g_pfnGetFunctions = NULL;
+
+typedef void FNDUMMY(void);
+typedef FNDUMMY *PFNDUMMY;
+/** Just a dummy global structure containing a bunch of
+ * function pointers to code which is wanted in the link. */
+PFNDUMMY g_apfnVBoxCAPIGlue[] =
+{
+#ifndef WIN32
+ /* The following link dependency is for helping gdb as it gets hideously
+ * confused if the application doesn't drag in pthreads, but uses it. */
+ (PFNDUMMY)pthread_create,
+#endif /* !WIN32 */
+ NULL
+};
+
+
+/**
+ * Wrapper for setting g_szVBoxErrMsg. Can be an empty stub.
+ *
+ * @param fAlways When 0 the g_szVBoxErrMsg is only set if empty.
+ * @param pszFormat The format string.
+ * @param ... The arguments.
+ */
+static void setErrMsg(int fAlways, const char *pszFormat, ...)
+{
+ if ( fAlways
+ || !g_szVBoxErrMsg[0])
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ vsnprintf(g_szVBoxErrMsg, sizeof(g_szVBoxErrMsg), pszFormat, va);
+ va_end(va);
+ }
+}
+
+
+/**
+ * Try load C API .so/dylib/dll from the specified location and resolve all
+ * the symbols we need. Tries both the new style and legacy name.
+ *
+ * @returns 0 on success, -1 on failure.
+ * @param pszHome The directory where to try load VBoxCAPI/VBoxXPCOMC
+ * from. Can be NULL.
+ * @param fSetAppHome Whether to set the VBOX_APP_HOME env.var. or not
+ * (boolean).
+ */
+static int tryLoadLibrary(const char *pszHome, int fSetAppHome)
+{
+ size_t cchHome = pszHome ? strlen(pszHome) : 0;
+ size_t cbBufNeeded;
+ char szName[4096];
+
+ /*
+ * Construct the full name.
+ */
+ cbBufNeeded = cchHome + sizeof("/" DYNLIB_NAME);
+ if (cbBufNeeded > sizeof(szName))
+ {
+ setErrMsg(1, "path buffer too small: %u bytes needed",
+ (unsigned)cbBufNeeded);
+ return -1;
+ }
+ if (cchHome)
+ {
+ memcpy(szName, pszHome, cchHome);
+ szName[cchHome] = '/';
+ cchHome++;
+ }
+ memcpy(&szName[cchHome], DYNLIB_NAME, sizeof(DYNLIB_NAME));
+
+ /*
+ * Try load it by that name, setting the VBOX_APP_HOME first (for now).
+ * Then resolve and call the function table getter.
+ */
+ if (fSetAppHome)
+ {
+#ifndef WIN32
+ if (pszHome)
+ setenv("VBOX_APP_HOME", pszHome, 1 /* always override */);
+ else
+ unsetenv("VBOX_APP_HOME");
+#endif /* !WIN32 */
+ }
+
+#ifndef WIN32
+ g_hVBoxCAPI = dlopen(szName, RTLD_NOW | RTLD_LOCAL);
+#else /* WIN32 */
+ g_hVBoxCAPI = LoadLibraryExA(szName, NULL /* hFile */, 0 /* dwFlags */);
+#endif /* WIN32 */
+ if (g_hVBoxCAPI)
+ {
+ PFNVBOXGETCAPIFUNCTIONS pfnGetFunctions;
+#ifndef WIN32
+ pfnGetFunctions = (PFNVBOXGETCAPIFUNCTIONS)(uintptr_t)
+ dlsym(g_hVBoxCAPI, VBOX_GET_CAPI_FUNCTIONS_SYMBOL_NAME);
+# ifdef VBOX_GET_XPCOM_FUNCTIONS_SYMBOL_NAME
+ if (!pfnGetFunctions)
+ pfnGetFunctions = (PFNVBOXGETCAPIFUNCTIONS)(uintptr_t)
+ dlsym(g_hVBoxCAPI, VBOX_GET_XPCOM_FUNCTIONS_SYMBOL_NAME);
+# endif /* VBOX_GET_XPCOM_FUNCTIONS_SYMBOL_NAME */
+#else /* WIN32 */
+ pfnGetFunctions = (PFNVBOXGETCAPIFUNCTIONS)
+ GetProcAddress(g_hVBoxCAPI, VBOX_GET_CAPI_FUNCTIONS_SYMBOL_NAME);
+#endif /* WIN32 */
+ if (pfnGetFunctions)
+ {
+ g_pVBoxFuncs = pfnGetFunctions(VBOX_CAPI_VERSION);
+ if (g_pVBoxFuncs)
+ {
+ if ( ( VBOX_CAPI_MAJOR(g_pVBoxFuncs->uVersion)
+ == VBOX_CAPI_MAJOR(VBOX_CAPI_VERSION))
+ && ( VBOX_CAPI_MINOR(g_pVBoxFuncs->uVersion)
+ >= VBOX_CAPI_MINOR(VBOX_CAPI_VERSION)))
+ {
+ g_pfnGetFunctions = pfnGetFunctions;
+ return 0;
+ }
+ setErrMsg(1, "%.80s: pfnGetFunctions(%#x) returned incompatible version %#x",
+ szName, VBOX_CAPI_VERSION, g_pVBoxFuncs->uVersion);
+ g_pVBoxFuncs = NULL;
+ }
+ else
+ {
+ /* bail out */
+ setErrMsg(1, "%.80s: pfnGetFunctions(%#x) failed",
+ szName, VBOX_CAPI_VERSION);
+ }
+ }
+ else
+ {
+#ifndef WIN32
+ setErrMsg(1, "dlsym(%.80s/%.32s): %.128s",
+ szName, VBOX_GET_CAPI_FUNCTIONS_SYMBOL_NAME, dlerror());
+#else /* WIN32 */
+ setErrMsg(1, "GetProcAddress(%.80s/%.32s): %d",
+ szName, VBOX_GET_CAPI_FUNCTIONS_SYMBOL_NAME, GetLastError());
+#endif /* WIN32 */
+ }
+
+#ifndef WIN32
+ dlclose(g_hVBoxCAPI);
+#else /* WIN32 */
+ FreeLibrary(g_hVBoxCAPI);
+#endif /* WIN32 */
+ g_hVBoxCAPI = NULL;
+ }
+ else
+ {
+#ifndef WIN32
+ setErrMsg(0, "dlopen(%.80s): %.160s", szName, dlerror());
+#else /* WIN32 */
+ setErrMsg(0, "LoadLibraryEx(%.80s): %d", szName, GetLastError());
+#endif /* WIN32 */
+ }
+
+ return -1;
+}
+
+
+/**
+ * Tries to locate and load VBoxCAPI.so/dylib/dll, resolving all the related
+ * function pointers.
+ *
+ * @returns 0 on success, -1 on failure.
+ *
+ * @remark This should be considered moved into a separate glue library since
+ * its its going to be pretty much the same for any user of VBoxCAPI
+ * and it will just cause trouble to have duplicate versions of this
+ * source code all around the place.
+ */
+int VBoxCGlueInit(void)
+{
+ const char *pszHome;
+
+ memset(g_szVBoxErrMsg, 0, sizeof(g_szVBoxErrMsg));
+
+ /*
+ * If the user specifies the location, try only that.
+ */
+ pszHome = getenv("VBOX_APP_HOME");
+ if (pszHome)
+ return tryLoadLibrary(pszHome, 0);
+
+ /*
+ * Try the known standard locations.
+ */
+#if defined(__gnu__linux__) || defined(__linux__)
+ if (tryLoadLibrary("/opt/VirtualBox", 1) == 0)
+ return 0;
+ if (tryLoadLibrary("/usr/lib/virtualbox", 1) == 0)
+ return 0;
+#elif defined(__sun__)
+ if (tryLoadLibrary("/opt/VirtualBox/amd64", 1) == 0)
+ return 0;
+ if (tryLoadLibrary("/opt/VirtualBox/i386", 1) == 0)
+ return 0;
+#elif defined(__APPLE__)
+ if (tryLoadLibrary("/Applications/VirtualBox.app/Contents/MacOS", 1) == 0)
+ return 0;
+#elif defined(__FreeBSD__)
+ if (tryLoadLibrary("/usr/local/lib/virtualbox", 1) == 0)
+ return 0;
+#elif defined(__OS2__)
+ if (tryLoadLibrary("C:/Apps/VirtualBox", 1) == 0)
+ return 0;
+#elif defined(WIN32)
+ pszHome = getenv("ProgramFiles");
+ if (pszHome)
+ {
+ char szPath[4096];
+ size_t cb = sizeof(szPath);
+ char *tmp = szPath;
+ strncpy(tmp, pszHome, cb);
+ tmp[cb - 1] = '\0';
+ cb -= strlen(tmp);
+ tmp += strlen(tmp);
+ strncpy(tmp, "/Oracle/VirtualBox", cb);
+ tmp[cb - 1] = '\0';
+ if (tryLoadLibrary(szPath, 1) == 0)
+ return 0;
+ }
+ if (tryLoadLibrary("C:/Program Files/Oracle/VirtualBox", 1) == 0)
+ return 0;
+#else
+# error "port me"
+#endif
+
+ /*
+ * Finally try the dynamic linker search path.
+ */
+ if (tryLoadLibrary(NULL, 1) == 0)
+ return 0;
+
+ /* No luck, return failure. */
+ return -1;
+}
+
+
+/**
+ * Terminate the C glue library.
+ */
+void VBoxCGlueTerm(void)
+{
+ if (g_hVBoxCAPI)
+ {
+#if 0 /* VBoxRT.so doesn't like being reloaded. See @bugref{3725}. */
+#ifndef WIN32
+ dlclose(g_hVBoxCAPI);
+#else
+ FreeLibrary(g_hVBoxCAPI);
+#endif
+#endif
+ g_hVBoxCAPI = NULL;
+ }
+ g_pVBoxFuncs = NULL;
+ g_pfnGetFunctions = NULL;
+ memset(g_szVBoxErrMsg, 0, sizeof(g_szVBoxErrMsg));
+}
+
diff --git a/src/VBox/Main/cbinding/VBoxCAPIGlue.h.in b/src/VBox/Main/cbinding/VBoxCAPIGlue.h.in
new file mode 100644
index 00000000..f1c5b9a0
--- /dev/null
+++ b/src/VBox/Main/cbinding/VBoxCAPIGlue.h.in
@@ -0,0 +1,68 @@
+/* $Id: VBoxCAPIGlue.h.in $ */
+/** @file VBoxCAPIGlue.h
+ * Glue for dynamically linking with VBoxCAPI.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef ___VBoxCAPIGlue_h
+#define ___VBoxCAPIGlue_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#undef VBOX_WITH_GLUE
+#define VBOX_WITH_GLUE
+#include "VBoxCAPI_v@VBOX_API_VERSION@.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** The so/dynsym/dll handle for VBoxCAPI. */
+#ifndef WIN32
+extern void *g_hVBoxCAPI;
+#else
+extern HMODULE g_hVBoxCAPI;
+#endif
+/** The last load error. */
+extern char g_szVBoxErrMsg[256];
+/** Pointer to the VBOXCAPI function table. */
+extern PCVBOXCAPI g_pVBoxFuncs;
+/** Pointer to VBoxGetCAPIFunctions for the loaded VBoxCAPI so/dylib/dll. */
+extern PFNVBOXGETCAPIFUNCTIONS g_pfnGetFunctions;
+
+
+int VBoxCGlueInit(void);
+void VBoxCGlueTerm(void);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !___VBoxCAPIGlue_h */
+
diff --git a/src/VBox/Main/cbinding/capiidl.xsl b/src/VBox/Main/cbinding/capiidl.xsl
new file mode 100644
index 00000000..e375bee7
--- /dev/null
+++ b/src/VBox/Main/cbinding/capiidl.xsl
@@ -0,0 +1,2736 @@
+<?xml version="1.0"?>
+<!-- $Id: capiidl.xsl $ -->
+
+<!--
+ * A template to generate a C header file for all relevant XPCOM interfaces
+ * provided or needed for calling the VirtualBox API. The header file also
+ * works on Windows, by using the C bindings header created by the MS COM IDL
+ * compiler (which simultaneously supports C and C++, unlike XPCOM).
+-->
+<!--
+ Copyright (C) 2008-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="text"/>
+
+<xsl:strip-space elements="*"/>
+
+
+<xsl:include href="../idl/typemap-shared.inc.xsl"/>
+
+<!--
+// Keys for more efficiently looking up of types.
+/////////////////////////////////////////////////////////////////////////////
+-->
+
+<xsl:key name="G_keyInterfacesByName" match="//interface[@name]" use="@name"/>
+
+
+<!--
+// templates
+/////////////////////////////////////////////////////////////////////////////
+-->
+
+
+<!--
+ * not explicitly matched elements and attributes
+-->
+<xsl:template match="*"/>
+
+
+<!--
+ * header
+-->
+<xsl:template match="/idl">
+ <xsl:text>/*
+ * DO NOT EDIT! This is a generated file.
+ *
+ * Header file which provides C declarations for VirtualBox Main API
+ * (COM interfaces), generated from XIDL (XML interface definition).
+ * On Windows (which uses COM instead of XPCOM) the native C support
+ * is used, and most of this file is not used.
+ *
+ * Source : src/VBox/Main/idl/VirtualBox.xidl
+ * Generator : src/VBox/Main/cbinding/capiidl.xsl
+ *
+ * This file contains portions from the following Mozilla XPCOM files:
+ * xpcom/include/xpcom/nsID.h
+ * xpcom/include/nsIException.h
+ * xpcom/include/nsprpub/prtypes.h
+ * xpcom/include/xpcom/nsISupportsBase.h
+ *
+ * These files were originally triple-licensed (MPL/GPL2/LGPL2.1). Oracle
+ * elects to distribute this derived work under the LGPL2.1 only.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of a free software library; you can redistribute
+ * it and/or modify it under the terms of the GNU Lesser General
+ * Public License version 2.1 as published by the Free Software
+ * Foundation and shipped in the "COPYING.LIB" file with this library.
+ * The library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if
+ * any license choice other than GPL or LGPL is available it will
+ * apply instead, Oracle elects to use only the Lesser General Public
+ * License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the
+ * language indicating that LGPLv2 or any later version may be used,
+ * or where a choice of which version of the LGPL is applied is
+ * otherwise unspecified.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+#ifndef ___VirtualBox_CAPI_h
+#define ___VirtualBox_CAPI_h
+
+#ifdef _WIN32
+# ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable:4668 4255) /* -Wall and windows.h */
+# if _MSC_VER >= 1800 /*RT_MSC_VER_VC120*/
+# pragma warning(disable:4005) /* sdk/v7.1/include/sal_supp.h(57) : warning C4005: '__useHeader' : macro redefinition */
+# endif
+# ifdef __cplusplus
+# if _MSC_VER >= 1900 /*RT_MSC_VER_VC140*/
+# pragma warning(disable:5039) /* winbase.h(13179): warning C5039: 'TpSetCallbackCleanupGroup': pointer or reference to potentially throwing function passed to 'extern "C"' function under -EHc. Undefined behavior may occur if this function throws an exception. */
+# endif
+# endif
+# endif
+# undef COBJMACROS
+# define COBJMACROS
+# include "Windows.h"
+# pragma warning(pop)
+#endif /* _WIN32 */
+
+#ifdef WIN32
+# ifdef IN_VBOXCAPI
+# define VBOXCAPI_DECL(type) extern __declspec(dllexport) type
+# else /* !IN_VBOXCAPI */
+# define VBOXCAPI_DECL(type) __declspec(dllimport) type
+# endif /* !IN_VBOXCAPI */
+#endif /* WIN32 */
+
+#ifdef __cplusplus
+/* The C++ treatment in this file is not meant for SDK users, it only exists
+ * so that this file can be used to produce the VBoxCAPI shared library which
+ * has to use C++ as it does all the conversion magic. */
+# ifdef IN_VBOXCAPI
+# include "VBox/com/VirtualBox.h"
+# ifndef WIN32
+# include "nsIEventQueue.h"
+# endif /* !WIN32 */
+# else /* !IN_VBOXCAPI */
+# error Do not include this header file from C++ code
+# endif /* !IN_VBOXCAPI */
+#endif /* __cplusplus */
+
+#ifdef __GNUC__
+# define VBOX_EXTERN_CONST(type, name) extern const type name __attribute__((nocommon))
+#else /* !__GNUC__ */
+# define VBOX_EXTERN_CONST(type, name) extern const type name
+#endif /* !__GNUC__ */
+
+/* Treat WIN32 completely separately, as on Windows VirtualBox uses COM, not
+ * XPCOM like on all other platforms. While the code below would also compile
+ * on Windows, we need to switch to the native C support provided by the header
+ * files produced by the COM IDL compiler. */
+#ifdef WIN32
+# include "ObjBase.h"
+# include "oaidl.h"
+# include "VirtualBox.h"
+
+#ifndef __cplusplus
+/* Skip this in the C++ case as there's already a definition for CBSTR. */
+typedef const BSTR CBSTR;
+#endif /* !__cplusplus */
+
+#define VBOX_WINAPI WINAPI
+
+#define ComSafeArrayAsInParam(f) (f)
+#define ComSafeArrayAsOutParam(f) (&amp;(f))
+#define ComSafeArrayAsOutTypeParam(f,t) (&amp;(f))
+#define ComSafeArrayAsOutIfaceParam(f,t) (&amp;(f))
+
+#else /* !WIN32 */
+
+#include &lt;stddef.h&gt;
+#include "wchar.h"
+
+#ifdef IN_VBOXCAPI
+# define VBOXCAPI_DECL(type) PR_EXPORT(type)
+#else /* !IN_VBOXCAPI */
+# define VBOXCAPI_DECL(type) PR_IMPORT(type)
+#endif /* !IN_VBOXCAPI */
+
+#ifndef __cplusplus
+
+#if defined(WIN32)
+
+#define PR_EXPORT(__type) extern __declspec(dllexport) __type
+#define PR_EXPORT_DATA(__type) extern __declspec(dllexport) __type
+#define PR_IMPORT(__type) __declspec(dllimport) __type
+#define PR_IMPORT_DATA(__type) __declspec(dllimport) __type
+
+#define PR_EXTERN(__type) extern __declspec(dllexport) __type
+#define PR_IMPLEMENT(__type) __declspec(dllexport) __type
+#define PR_EXTERN_DATA(__type) extern __declspec(dllexport) __type
+#define PR_IMPLEMENT_DATA(__type) __declspec(dllexport) __type
+
+#define PR_CALLBACK
+#define PR_CALLBACK_DECL
+#define PR_STATIC_CALLBACK(__x) static __x
+
+#elif defined(XP_BEOS)
+
+#define PR_EXPORT(__type) extern __declspec(dllexport) __type
+#define PR_EXPORT_DATA(__type) extern __declspec(dllexport) __type
+#define PR_IMPORT(__type) extern __declspec(dllexport) __type
+#define PR_IMPORT_DATA(__type) extern __declspec(dllexport) __type
+
+#define PR_EXTERN(__type) extern __declspec(dllexport) __type
+#define PR_IMPLEMENT(__type) __declspec(dllexport) __type
+#define PR_EXTERN_DATA(__type) extern __declspec(dllexport) __type
+#define PR_IMPLEMENT_DATA(__type) __declspec(dllexport) __type
+
+#define PR_CALLBACK
+#define PR_CALLBACK_DECL
+#define PR_STATIC_CALLBACK(__x) static __x
+
+#elif defined(WIN16)
+
+#define PR_CALLBACK_DECL __cdecl
+
+#if defined(_WINDLL)
+#define PR_EXPORT(__type) extern __type _cdecl _export _loadds
+#define PR_IMPORT(__type) extern __type _cdecl _export _loadds
+#define PR_EXPORT_DATA(__type) extern __type _export
+#define PR_IMPORT_DATA(__type) extern __type _export
+
+#define PR_EXTERN(__type) extern __type _cdecl _export _loadds
+#define PR_IMPLEMENT(__type) __type _cdecl _export _loadds
+#define PR_EXTERN_DATA(__type) extern __type _export
+#define PR_IMPLEMENT_DATA(__type) __type _export
+
+#define PR_CALLBACK __cdecl __loadds
+#define PR_STATIC_CALLBACK(__x) static __x PR_CALLBACK
+
+#else /* this must be .EXE */
+#define PR_EXPORT(__type) extern __type _cdecl _export
+#define PR_IMPORT(__type) extern __type _cdecl _export
+#define PR_EXPORT_DATA(__type) extern __type _export
+#define PR_IMPORT_DATA(__type) extern __type _export
+
+#define PR_EXTERN(__type) extern __type _cdecl _export
+#define PR_IMPLEMENT(__type) __type _cdecl _export
+#define PR_EXTERN_DATA(__type) extern __type _export
+#define PR_IMPLEMENT_DATA(__type) __type _export
+
+#define PR_CALLBACK __cdecl __loadds
+#define PR_STATIC_CALLBACK(__x) __x PR_CALLBACK
+#endif /* _WINDLL */
+
+#elif defined(XP_MAC)
+
+#define PR_EXPORT(__type) extern __declspec(export) __type
+#define PR_EXPORT_DATA(__type) extern __declspec(export) __type
+#define PR_IMPORT(__type) extern __declspec(export) __type
+#define PR_IMPORT_DATA(__type) extern __declspec(export) __type
+
+#define PR_EXTERN(__type) extern __declspec(export) __type
+#define PR_IMPLEMENT(__type) __declspec(export) __type
+#define PR_EXTERN_DATA(__type) extern __declspec(export) __type
+#define PR_IMPLEMENT_DATA(__type) __declspec(export) __type
+
+#define PR_CALLBACK
+#define PR_CALLBACK_DECL
+#define PR_STATIC_CALLBACK(__x) static __x
+
+#elif defined(XP_OS2) &amp;&amp; defined(__declspec)
+
+#define PR_EXPORT(__type) extern __declspec(dllexport) __type
+#define PR_EXPORT_DATA(__type) extern __declspec(dllexport) __type
+#define PR_IMPORT(__type) __declspec(dllimport) __type
+#define PR_IMPORT_DATA(__type) __declspec(dllimport) __type
+
+#define PR_EXTERN(__type) extern __declspec(dllexport) __type
+#define PR_IMPLEMENT(__type) __declspec(dllexport) __type
+#define PR_EXTERN_DATA(__type) extern __declspec(dllexport) __type
+#define PR_IMPLEMENT_DATA(__type) __declspec(dllexport) __type
+
+#define PR_CALLBACK
+#define PR_CALLBACK_DECL
+#define PR_STATIC_CALLBACK(__x) static __x
+
+#elif defined(XP_OS2_VACPP)
+
+#define PR_EXPORT(__type) extern __type
+#define PR_EXPORT_DATA(__type) extern __type
+#define PR_IMPORT(__type) extern __type
+#define PR_IMPORT_DATA(__type) extern __type
+
+#define PR_EXTERN(__type) extern __type
+#define PR_IMPLEMENT(__type) __type
+#define PR_EXTERN_DATA(__type) extern __type
+#define PR_IMPLEMENT_DATA(__type) __type
+#define PR_CALLBACK _Optlink
+#define PR_CALLBACK_DECL
+#define PR_STATIC_CALLBACK(__x) static __x PR_CALLBACK
+
+#else /* Unix */
+
+# ifdef VBOX_HAVE_VISIBILITY_HIDDEN
+# define PR_EXPORT(__type) __attribute__((visibility("default"))) extern __type
+# define PR_EXPORT_DATA(__type) __attribute__((visibility("default"))) extern __type
+# define PR_IMPORT(__type) extern __type
+# define PR_IMPORT_DATA(__type) extern __type
+# define PR_EXTERN(__type) __attribute__((visibility("default"))) extern __type
+# define PR_IMPLEMENT(__type) __attribute__((visibility("default"))) __type
+# define PR_EXTERN_DATA(__type) __attribute__((visibility("default"))) extern __type
+# define PR_IMPLEMENT_DATA(__type) __attribute__((visibility("default"))) __type
+# define PR_CALLBACK
+# define PR_CALLBACK_DECL
+# define PR_STATIC_CALLBACK(__x) static __x
+# else
+# define PR_EXPORT(__type) extern __type
+# define PR_EXPORT_DATA(__type) extern __type
+# define PR_IMPORT(__type) extern __type
+# define PR_IMPORT_DATA(__type) extern __type
+# define PR_EXTERN(__type) extern __type
+# define PR_IMPLEMENT(__type) __type
+# define PR_EXTERN_DATA(__type) extern __type
+# define PR_IMPLEMENT_DATA(__type) __type
+# define PR_CALLBACK
+# define PR_CALLBACK_DECL
+# define PR_STATIC_CALLBACK(__x) static __x
+# endif
+#endif
+
+#if defined(_NSPR_BUILD_)
+#define NSPR_API(__type) PR_EXPORT(__type)
+#define NSPR_DATA_API(__type) PR_EXPORT_DATA(__type)
+#else
+#define NSPR_API(__type) PR_IMPORT(__type)
+#define NSPR_DATA_API(__type) PR_IMPORT_DATA(__type)
+#endif
+
+typedef unsigned char PRUint8;
+#if (defined(HPUX) &amp;&amp; defined(__cplusplus) \
+ &amp;&amp; !defined(__GNUC__) &amp;&amp; __cplusplus &lt; 199707L) \
+ || (defined(SCO) &amp;&amp; defined(__cplusplus) \
+ &amp;&amp; !defined(__GNUC__) &amp;&amp; __cplusplus == 1L)
+typedef char PRInt8;
+#else
+typedef signed char PRInt8;
+#endif
+
+#define PR_INT8_MAX 127
+#define PR_INT8_MIN (-128)
+#define PR_UINT8_MAX 255U
+
+typedef unsigned short PRUint16;
+typedef short PRInt16;
+
+#define PR_INT16_MAX 32767
+#define PR_INT16_MIN (-32768)
+#define PR_UINT16_MAX 65535U
+
+typedef unsigned int PRUint32;
+typedef int PRInt32;
+#define PR_INT32(x) x
+#define PR_UINT32(x) x ## U
+
+#define PR_INT32_MAX PR_INT32(2147483647)
+#define PR_INT32_MIN (-PR_INT32_MAX - 1)
+#define PR_UINT32_MAX PR_UINT32(4294967295)
+
+typedef long PRInt64;
+typedef unsigned long PRUint64;
+typedef int PRIntn;
+typedef unsigned int PRUintn;
+
+typedef double PRFloat64;
+typedef size_t PRSize;
+
+typedef ptrdiff_t PRPtrdiff;
+
+typedef unsigned long PRUptrdiff;
+
+typedef PRIntn PRBool;
+
+#define PR_TRUE 1
+#define PR_FALSE 0
+
+typedef PRUint8 PRPackedBool;
+
+/*
+** Status code used by some routines that have a single point of failure or
+** special status return.
+*/
+typedef enum { PR_FAILURE = -1, PR_SUCCESS = 0 } PRStatus;
+
+#ifndef __PRUNICHAR__
+#define __PRUNICHAR__
+#if defined(WIN32) || defined(XP_MAC)
+typedef wchar_t PRUnichar;
+#else
+typedef PRUint16 PRUnichar;
+#endif
+typedef PRUnichar *BSTR;
+typedef const PRUnichar *CBSTR;
+#endif
+
+typedef long PRWord;
+typedef unsigned long PRUword;
+
+#define nsnull 0
+typedef PRUint32 nsresult;
+
+#if defined(__GNUC__) &amp;&amp; (__GNUC__ > 2)
+#define NS_LIKELY(x) (__builtin_expect((x), 1))
+#define NS_UNLIKELY(x) (__builtin_expect((x), 0))
+#else
+#define NS_LIKELY(x) (x)
+#define NS_UNLIKELY(x) (x)
+#endif
+
+#define NS_FAILED(_nsresult) (NS_UNLIKELY((_nsresult) &amp; 0x80000000))
+#define NS_SUCCEEDED(_nsresult) (NS_LIKELY(!((_nsresult) &amp; 0x80000000)))
+
+#ifdef VBOX_WITH_XPCOM_NAMESPACE_CLEANUP
+# define PR_IntervalNow VBoxNsprPR_IntervalNow
+# define PR_TicksPerSecond VBoxNsprPR_TicksPerSecond
+# define PR_SecondsToInterval VBoxNsprPR_SecondsToInterval
+# define PR_MillisecondsToInterval VBoxNsprPR_MillisecondsToInterval
+# define PR_MicrosecondsToInterval VBoxNsprPR_MicrosecondsToInterval
+# define PR_IntervalToSeconds VBoxNsprPR_IntervalToSeconds
+# define PR_IntervalToMilliseconds VBoxNsprPR_IntervalToMilliseconds
+# define PR_IntervalToMicroseconds VBoxNsprPR_IntervalToMicroseconds
+# define PR_EnterMonitor VBoxNsprPR_EnterMonitor
+# define PR_ExitMonitor VBoxNsprPR_ExitMonitor
+# define PR_Notify VBoxNsprPR_Notify
+# define PR_NotifyAll VBoxNsprPR_NotifyAll
+# define PR_Wait VBoxNsprPR_Wait
+# define PR_NewMonitor VBoxNsprPR_NewMonitor
+# define PR_DestroyMonitor VBoxNsprPR_DestroyMonitor
+#endif /* VBOX_WITH_XPCOM_NAMESPACE_CLEANUP */
+
+typedef PRUint32 PRIntervalTime;
+
+#define PR_INTERVAL_MIN 1000UL
+#define PR_INTERVAL_MAX 100000UL
+#define PR_INTERVAL_NO_WAIT 0UL
+#define PR_INTERVAL_NO_TIMEOUT 0xffffffffUL
+
+NSPR_API(PRIntervalTime) PR_IntervalNow(void);
+NSPR_API(PRUint32) PR_TicksPerSecond(void);
+NSPR_API(PRIntervalTime) PR_SecondsToInterval(PRUint32 seconds);
+NSPR_API(PRIntervalTime) PR_MillisecondsToInterval(PRUint32 milli);
+NSPR_API(PRIntervalTime) PR_MicrosecondsToInterval(PRUint32 micro);
+NSPR_API(PRUint32) PR_IntervalToSeconds(PRIntervalTime ticks);
+NSPR_API(PRUint32) PR_IntervalToMilliseconds(PRIntervalTime ticks);
+NSPR_API(PRUint32) PR_IntervalToMicroseconds(PRIntervalTime ticks);
+
+typedef struct PRMonitor PRMonitor;
+
+NSPR_API(PRMonitor*) PR_NewMonitor(void);
+NSPR_API(void) PR_DestroyMonitor(PRMonitor *mon);
+NSPR_API(void) PR_EnterMonitor(PRMonitor *mon);
+NSPR_API(PRStatus) PR_ExitMonitor(PRMonitor *mon);
+NSPR_API(PRStatus) PR_Wait(PRMonitor *mon, PRIntervalTime ticks);
+NSPR_API(PRStatus) PR_Notify(PRMonitor *mon);
+NSPR_API(PRStatus) PR_NotifyAll(PRMonitor *mon);
+
+#ifdef VBOX_WITH_XPCOM_NAMESPACE_CLEANUP
+# define PR_CreateThread VBoxNsprPR_CreateThread
+# define PR_JoinThread VBoxNsprPR_JoinThread
+# define PR_Sleep VBoxNsprPR_Sleep
+# define PR_GetCurrentThread VBoxNsprPR_GetCurrentThread
+# define PR_GetThreadState VBoxNsprPR_GetThreadState
+# define PR_SetThreadPrivate VBoxNsprPR_SetThreadPrivate
+# define PR_GetThreadPrivate VBoxNsprPR_GetThreadPrivate
+# define PR_NewThreadPrivateIndex VBoxNsprPR_NewThreadPrivateIndex
+# define PR_GetThreadPriority VBoxNsprPR_GetThreadPriority
+# define PR_SetThreadPriority VBoxNsprPR_SetThreadPriority
+# define PR_Interrupt VBoxNsprPR_Interrupt
+# define PR_ClearInterrupt VBoxNsprPR_ClearInterrupt
+# define PR_BlockInterrupt VBoxNsprPR_BlockInterrupt
+# define PR_UnblockInterrupt VBoxNsprPR_UnblockInterrupt
+# define PR_GetThreadScope VBoxNsprPR_GetThreadScope
+# define PR_GetThreadType VBoxNsprPR_GetThreadType
+#endif /* VBOX_WITH_XPCOM_NAMESPACE_CLEANUP */
+
+typedef struct PRThread PRThread;
+typedef struct PRThreadStack PRThreadStack;
+
+typedef enum PRThreadType {
+ PR_USER_THREAD,
+ PR_SYSTEM_THREAD
+} PRThreadType;
+
+typedef enum PRThreadScope {
+ PR_LOCAL_THREAD,
+ PR_GLOBAL_THREAD,
+ PR_GLOBAL_BOUND_THREAD
+} PRThreadScope;
+
+typedef enum PRThreadState {
+ PR_JOINABLE_THREAD,
+ PR_UNJOINABLE_THREAD
+} PRThreadState;
+
+typedef enum PRThreadPriority
+{
+ PR_PRIORITY_FIRST = 0, /* just a placeholder */
+ PR_PRIORITY_LOW = 0, /* the lowest possible priority */
+ PR_PRIORITY_NORMAL = 1, /* most common expected priority */
+ PR_PRIORITY_HIGH = 2, /* slightly more aggressive scheduling */
+ PR_PRIORITY_URGENT = 3, /* it does little good to have more than one */
+ PR_PRIORITY_LAST = 3 /* this is just a placeholder */
+} PRThreadPriority;
+
+NSPR_API(PRThread*) PR_CreateThread(PRThreadType type,
+ void (PR_CALLBACK *start)(void *arg),
+ void *arg,
+ PRThreadPriority priority,
+ PRThreadScope scope,
+ PRThreadState state,
+ PRUint32 stackSize);
+NSPR_API(PRStatus) PR_JoinThread(PRThread *thread);
+NSPR_API(PRThread*) PR_GetCurrentThread(void);
+#ifndef NO_NSPR_10_SUPPORT
+#define PR_CurrentThread() PR_GetCurrentThread() /* for nspr1.0 compat. */
+#endif /* NO_NSPR_10_SUPPORT */
+NSPR_API(PRThreadPriority) PR_GetThreadPriority(const PRThread *thread);
+NSPR_API(void) PR_SetThreadPriority(PRThread *thread, PRThreadPriority priority);
+
+typedef void (PR_CALLBACK *PRThreadPrivateDTOR)(void *priv);
+
+NSPR_API(PRStatus) PR_NewThreadPrivateIndex(
+ PRUintn *newIndex, PRThreadPrivateDTOR destructor);
+NSPR_API(PRStatus) PR_SetThreadPrivate(PRUintn tpdIndex, void *priv);
+NSPR_API(void*) PR_GetThreadPrivate(PRUintn tpdIndex);
+NSPR_API(PRStatus) PR_Interrupt(PRThread *thread);
+NSPR_API(void) PR_ClearInterrupt(void);
+NSPR_API(void) PR_BlockInterrupt(void);
+NSPR_API(void) PR_UnblockInterrupt(void);
+NSPR_API(PRStatus) PR_Sleep(PRIntervalTime ticks);
+NSPR_API(PRThreadScope) PR_GetThreadScope(const PRThread *thread);
+NSPR_API(PRThreadType) PR_GetThreadType(const PRThread *thread);
+NSPR_API(PRThreadState) PR_GetThreadState(const PRThread *thread);
+
+#ifdef VBOX_WITH_XPCOM_NAMESPACE_CLEANUP
+# define PR_DestroyLock VBoxNsprPR_DestroyLock
+# define PR_Lock VBoxNsprPR_Lock
+# define PR_NewLock VBoxNsprPR_NewLock
+# define PR_Unlock VBoxNsprPR_Unlock
+#endif /* VBOX_WITH_XPCOM_NAMESPACE_CLEANUP */
+
+typedef struct PRLock PRLock;
+
+NSPR_API(PRLock*) PR_NewLock(void);
+NSPR_API(void) PR_DestroyLock(PRLock *lock);
+NSPR_API(void) PR_Lock(PRLock *lock);
+NSPR_API(PRStatus) PR_Unlock(PRLock *lock);
+
+#ifdef VBOX_WITH_XPCOM_NAMESPACE_CLEANUP
+# define PR_NewCondVar VBoxNsprPR_NewCondVar
+# define PR_DestroyCondVar VBoxNsprPR_DestroyCondVar
+# define PR_WaitCondVar VBoxNsprPR_WaitCondVar
+# define PR_NotifyCondVar VBoxNsprPR_NotifyCondVar
+# define PR_NotifyAllCondVar VBoxNsprPR_NotifyAllCondVar
+#endif /* VBOX_WITH_XPCOM_NAMESPACE_CLEANUP */
+
+typedef struct PRCondVar PRCondVar;
+
+NSPR_API(PRCondVar*) PR_NewCondVar(PRLock *lock);
+NSPR_API(void) PR_DestroyCondVar(PRCondVar *cvar);
+NSPR_API(PRStatus) PR_WaitCondVar(PRCondVar *cvar, PRIntervalTime timeout);
+NSPR_API(PRStatus) PR_NotifyCondVar(PRCondVar *cvar);
+NSPR_API(PRStatus) PR_NotifyAllCondVar(PRCondVar *cvar);
+
+typedef struct PRCListStr PRCList;
+
+struct PRCListStr {
+ PRCList *next;
+ PRCList *prev;
+};
+
+#ifdef VBOX_WITH_XPCOM_NAMESPACE_CLEANUP
+# define PL_DestroyEvent VBoxNsplPL_DestroyEvent
+# define PL_HandleEvent VBoxNsplPL_HandleEvent
+# define PL_InitEvent VBoxNsplPL_InitEvent
+# define PL_CreateEventQueue VBoxNsplPL_CreateEventQueue
+# define PL_CreateMonitoredEventQueue VBoxNsplPL_CreateMonitoredEventQueue
+# define PL_CreateNativeEventQueue VBoxNsplPL_CreateNativeEventQueue
+# define PL_DequeueEvent VBoxNsplPL_DequeueEvent
+# define PL_DestroyEventQueue VBoxNsplPL_DestroyEventQueue
+# define PL_EventAvailable VBoxNsplPL_EventAvailable
+# define PL_EventLoop VBoxNsplPL_EventLoop
+# define PL_GetEvent VBoxNsplPL_GetEvent
+# define PL_GetEventOwner VBoxNsplPL_GetEventOwner
+# define PL_GetEventQueueMonitor VBoxNsplPL_GetEventQueueMonitor
+# define PL_GetEventQueueSelectFD VBoxNsplPL_GetEventQueueSelectFD
+# define PL_MapEvents VBoxNsplPL_MapEvents
+# define PL_PostEvent VBoxNsplPL_PostEvent
+# define PL_PostSynchronousEvent VBoxNsplPL_PostSynchronousEvent
+# define PL_ProcessEventsBeforeID VBoxNsplPL_ProcessEventsBeforeID
+# define PL_ProcessPendingEvents VBoxNsplPL_ProcessPendingEvents
+# define PL_RegisterEventIDFunc VBoxNsplPL_RegisterEventIDFunc
+# define PL_RevokeEvents VBoxNsplPL_RevokeEvents
+# define PL_UnregisterEventIDFunc VBoxNsplPL_UnregisterEventIDFunc
+# define PL_WaitForEvent VBoxNsplPL_WaitForEvent
+# define PL_IsQueueNative VBoxNsplPL_IsQueueNative
+# define PL_IsQueueOnCurrentThread VBoxNsplPL_IsQueueOnCurrentThread
+# define PL_FavorPerformanceHint VBoxNsplPL_FavorPerformanceHint
+#endif /* VBOX_WITH_XPCOM_NAMESPACE_CLEANUP */
+
+typedef struct PLEvent PLEvent;
+typedef struct PLEventQueue PLEventQueue;
+
+PR_EXTERN(PLEventQueue*)
+PL_CreateEventQueue(const char* name, PRThread* handlerThread);
+PR_EXTERN(PLEventQueue *)
+ PL_CreateNativeEventQueue(
+ const char *name,
+ PRThread *handlerThread
+ );
+PR_EXTERN(PLEventQueue *)
+ PL_CreateMonitoredEventQueue(
+ const char *name,
+ PRThread *handlerThread
+ );
+PR_EXTERN(void)
+PL_DestroyEventQueue(PLEventQueue* self);
+PR_EXTERN(PRMonitor*)
+PL_GetEventQueueMonitor(PLEventQueue* self);
+
+#define PL_ENTER_EVENT_QUEUE_MONITOR(queue) \
+ PR_EnterMonitor(PL_GetEventQueueMonitor(queue))
+
+#define PL_EXIT_EVENT_QUEUE_MONITOR(queue) \
+ PR_ExitMonitor(PL_GetEventQueueMonitor(queue))
+
+PR_EXTERN(PRStatus) PL_PostEvent(PLEventQueue* self, PLEvent* event);
+PR_EXTERN(void*) PL_PostSynchronousEvent(PLEventQueue* self, PLEvent* event);
+PR_EXTERN(PLEvent*) PL_GetEvent(PLEventQueue* self);
+PR_EXTERN(PRBool) PL_EventAvailable(PLEventQueue* self);
+
+typedef void (PR_CALLBACK *PLEventFunProc)(PLEvent* event, void* data, PLEventQueue* queue);
+
+PR_EXTERN(void) PL_MapEvents(PLEventQueue* self, PLEventFunProc fun, void* data);
+PR_EXTERN(void) PL_RevokeEvents(PLEventQueue* self, void* owner);
+PR_EXTERN(void) PL_ProcessPendingEvents(PLEventQueue* self);
+PR_EXTERN(PLEvent*) PL_WaitForEvent(PLEventQueue* self);
+PR_EXTERN(void) PL_EventLoop(PLEventQueue* self);
+PR_EXTERN(PRInt32) PL_GetEventQueueSelectFD(PLEventQueue* self);
+PR_EXTERN(PRBool) PL_IsQueueOnCurrentThread( PLEventQueue *queue );
+PR_EXTERN(PRBool) PL_IsQueueNative(PLEventQueue *queue);
+
+typedef void* (PR_CALLBACK *PLHandleEventProc)(PLEvent* self);
+typedef void (PR_CALLBACK *PLDestroyEventProc)(PLEvent* self);
+PR_EXTERN(void)
+PL_InitEvent(PLEvent* self, void* owner,
+ PLHandleEventProc handler,
+ PLDestroyEventProc destructor);
+PR_EXTERN(void*) PL_GetEventOwner(PLEvent* self);
+PR_EXTERN(void) PL_HandleEvent(PLEvent* self);
+PR_EXTERN(void) PL_DestroyEvent(PLEvent* self);
+PR_EXTERN(void) PL_DequeueEvent(PLEvent* self, PLEventQueue* queue);
+PR_EXTERN(void) PL_FavorPerformanceHint(PRBool favorPerformanceOverEventStarvation, PRUint32 starvationDelay);
+
+struct PLEvent {
+ PRCList link;
+ PLHandleEventProc handler;
+ PLDestroyEventProc destructor;
+ void* owner;
+ void* synchronousResult;
+ PRLock* lock;
+ PRCondVar* condVar;
+ PRBool handled;
+#ifdef PL_POST_TIMINGS
+ PRIntervalTime postTime;
+#endif
+#ifdef XP_UNIX
+ unsigned long id;
+#endif /* XP_UNIX */
+ /* other fields follow... */
+};
+
+#if defined(XP_WIN) || defined(XP_OS2)
+
+PR_EXTERN(HWND)
+ PL_GetNativeEventReceiverWindow(
+ PLEventQueue *eqp
+ );
+#endif /* XP_WIN || XP_OS2 */
+
+#ifdef XP_UNIX
+
+PR_EXTERN(PRInt32)
+PL_ProcessEventsBeforeID(PLEventQueue *aSelf, unsigned long aID);
+
+typedef unsigned long (PR_CALLBACK *PLGetEventIDFunc)(void *aClosure);
+
+PR_EXTERN(void)
+PL_RegisterEventIDFunc(PLEventQueue *aSelf, PLGetEventIDFunc aFunc,
+ void *aClosure);
+PR_EXTERN(void) PL_UnregisterEventIDFunc(PLEventQueue *aSelf);
+
+#endif /* XP_UNIX */
+
+/* Standard "it worked" return value */
+#define NS_OK 0
+
+#define NS_ERROR_BASE ((nsresult) 0xC1F30000)
+
+/* Returned when an instance is not initialized */
+#define NS_ERROR_NOT_INITIALIZED (NS_ERROR_BASE + 1)
+
+/* Returned when an instance is already initialized */
+#define NS_ERROR_ALREADY_INITIALIZED (NS_ERROR_BASE + 2)
+
+/* Returned by a not implemented function */
+#define NS_ERROR_NOT_IMPLEMENTED ((nsresult) 0x80004001L)
+
+/* Returned when a given interface is not supported. */
+#define NS_NOINTERFACE ((nsresult) 0x80004002L)
+#define NS_ERROR_NO_INTERFACE NS_NOINTERFACE
+
+#define NS_ERROR_INVALID_POINTER ((nsresult) 0x80004003L)
+#define NS_ERROR_NULL_POINTER NS_ERROR_INVALID_POINTER
+
+/* Returned when a function aborts */
+#define NS_ERROR_ABORT ((nsresult) 0x80004004L)
+
+/* Returned when a function fails */
+#define NS_ERROR_FAILURE ((nsresult) 0x80004005L)
+
+/* Returned when an unexpected error occurs */
+#define NS_ERROR_UNEXPECTED ((nsresult) 0x8000ffffL)
+
+/* Returned when a memory allocation fails */
+#define NS_ERROR_OUT_OF_MEMORY ((nsresult) 0x8007000eL)
+
+/* Returned when an illegal value is passed */
+#define NS_ERROR_ILLEGAL_VALUE ((nsresult) 0x80070057L)
+#define NS_ERROR_INVALID_ARG NS_ERROR_ILLEGAL_VALUE
+
+/* Returned when a class doesn't allow aggregation */
+#define NS_ERROR_NO_AGGREGATION ((nsresult) 0x80040110L)
+
+/* Returned when an operation can't complete due to an unavailable resource */
+#define NS_ERROR_NOT_AVAILABLE ((nsresult) 0x80040111L)
+
+/* Returned when a class is not registered */
+#define NS_ERROR_FACTORY_NOT_REGISTERED ((nsresult) 0x80040154L)
+
+/* Returned when a class cannot be registered, but may be tried again later */
+#define NS_ERROR_FACTORY_REGISTER_AGAIN ((nsresult) 0x80040155L)
+
+/* Returned when a dynamically loaded factory couldn't be found */
+#define NS_ERROR_FACTORY_NOT_LOADED ((nsresult) 0x800401f8L)
+
+/* Returned when a factory doesn't support signatures */
+#define NS_ERROR_FACTORY_NO_SIGNATURE_SUPPORT \
+ (NS_ERROR_BASE + 0x101)
+
+/* Returned when a factory already is registered */
+#define NS_ERROR_FACTORY_EXISTS (NS_ERROR_BASE + 0x100)
+
+/**
+ * An "interface id" which can be used to uniquely identify a given
+ * interface.
+ * A "unique identifier". This is modeled after OSF DCE UUIDs.
+ */
+
+struct nsID {
+ PRUint32 m0;
+ PRUint16 m1;
+ PRUint16 m2;
+ PRUint8 m3[8];
+};
+
+typedef struct nsID nsID;
+typedef nsID nsIID;
+typedef nsID nsCID;
+
+#endif /* __cplusplus */
+
+#define VBOX_WINAPI
+
+/* Various COM types defined by their XPCOM equivalent */
+typedef PRInt64 LONG64;
+typedef PRInt32 LONG;
+typedef PRInt32 DWORD;
+typedef PRInt16 SHORT;
+typedef PRUint64 ULONG64;
+typedef PRUint32 ULONG;
+typedef PRUint16 USHORT;
+
+typedef PRBool BOOL;
+
+#ifndef FALSE
+#define FALSE 0
+#define TRUE 1
+#endif
+
+#define HRESULT nsresult
+#define SUCCEEDED NS_SUCCEEDED
+#define FAILED NS_FAILED
+
+/* OLE error codes */
+#define S_OK ((nsresult)NS_OK)
+#define E_UNEXPECTED NS_ERROR_UNEXPECTED
+#define E_NOTIMPL NS_ERROR_NOT_IMPLEMENTED
+#define E_OUTOFMEMORY NS_ERROR_OUT_OF_MEMORY
+#define E_INVALIDARG NS_ERROR_INVALID_ARG
+#define E_NOINTERFACE NS_ERROR_NO_INTERFACE
+#define E_POINTER NS_ERROR_NULL_POINTER
+#define E_ABORT NS_ERROR_ABORT
+#define E_FAIL NS_ERROR_FAILURE
+/* Note: a better analog for E_ACCESSDENIED would probably be
+ * NS_ERROR_NOT_AVAILABLE, but we want binary compatibility for now. */
+#define E_ACCESSDENIED ((nsresult)0x80070005L)
+
+/* Basic vartype for COM compatibility. */
+typedef enum VARTYPE
+{
+ VT_I2 = 2,
+ VT_I4 = 3,
+ VT_BSTR = 8,
+ VT_DISPATCH = 9,
+ VT_BOOL = 11,
+ VT_UNKNOWN = 13,
+ VT_I1 = 16,
+ VT_UI1 = 17,
+ VT_UI2 = 18,
+ VT_UI4 = 19,
+ VT_I8 = 20,
+ VT_UI8 = 21,
+ VT_HRESULT = 25
+} VARTYPE;
+
+/* Basic safearray type for COM compatibility. */
+typedef struct SAFEARRAY
+{
+ void *pv;
+ ULONG c;
+} SAFEARRAY;
+
+#define ComSafeArrayAsInParam(f) ((f) ? (f)->c : 0), ((f) ? (f)->pv : NULL)
+#define ComSafeArrayAsOutParam(f) (&amp;((f)->c)), (&amp;((f)->pv))
+#define ComSafeArrayAsOutTypeParam(f,t) (&amp;((f)->c)), (t**)(&amp;((f)->pv))
+#define ComSafeArrayAsOutIfaceParam(f,t) (&amp;((f)->c)), (t**)(&amp;((f)->pv))
+
+/* Glossing over differences between COM and XPCOM */
+#define IErrorInfo nsIException
+#define IUnknown nsISupports
+#define IDispatch nsISupports
+
+/* Make things as COM compatible as possible */
+#define interface struct
+#ifdef CONST_VTABLE
+# define CONST_VTBL const
+#else /* !CONST_VTABLE */
+# define CONST_VTBL
+#endif /* !CONST_VTABLE */
+
+#ifndef __cplusplus
+
+/** @todo this first batch of forward declarations (and the corresponding ones
+ * generated for each interface) are 100% redundant, remove eventually. */
+interface nsISupports; /* forward declaration */
+interface nsIException; /* forward declaration */
+interface nsIStackFrame; /* forward declaration */
+interface nsIEventTarget;/* forward declaration */
+interface nsIEventQueue; /* forward declaration */
+
+typedef interface nsISupports nsISupports; /* forward declaration */
+typedef interface nsIException nsIException; /* forward declaration */
+typedef interface nsIStackFrame nsIStackFrame; /* forward declaration */
+typedef interface nsIEventTarget nsIEventTarget;/* forward declaration */
+typedef interface nsIEventQueue nsIEventQueue; /* forward declaration */
+
+/* starting interface: nsISupports */
+#define NS_ISUPPORTS_IID_STR "00000000-0000-0000-c000-000000000046"
+
+#define NS_ISUPPORTS_IID \
+ { 0x00000000, 0x0000, 0x0000, \
+ {0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46} }
+
+/**
+ * Reference count values
+ *
+ * This is the return type for AddRef() and Release() in nsISupports.
+ * IUnknown of COM returns an unsigned long from equivalent functions.
+ * The following ifdef exists to maintain binary compatibility with
+ * IUnknown.
+ */
+#if defined(XP_WIN) &amp;&amp; PR_BYTES_PER_LONG == 4
+typedef unsigned long nsrefcnt;
+#else
+typedef PRUint32 nsrefcnt;
+#endif
+
+/**
+ * Basic component object model interface. Objects which implement
+ * this interface support runtime interface discovery (QueryInterface)
+ * and a reference counted memory model (AddRef/Release). This is
+ * modelled after the win32 IUnknown API.
+ */
+#ifndef VBOX_WITH_GLUE
+struct nsISupports_vtbl
+{
+ nsresult (*QueryInterface)(nsISupports *pThis, const nsID *iid, void **resultp);
+ nsrefcnt (*AddRef)(nsISupports *pThis);
+ nsrefcnt (*Release)(nsISupports *pThis);
+};
+#else /* !VBOX_WITH_GLUE */
+struct nsISupportsVtbl
+{
+ nsresult (*QueryInterface)(nsISupports *pThis, const nsID *iid, void **resultp);
+ nsrefcnt (*AddRef)(nsISupports *pThis);
+ nsrefcnt (*Release)(nsISupports *pThis);
+};
+#define nsISupports_QueryInterface(p, iid, resultp) ((p)->lpVtbl->QueryInterface(p, iid, resultp))
+#define nsISupports_AddRef(p) ((p)->lpVtbl->AddRef(p))
+#define nsISupports_Release(p) ((p)->lpVtbl->Release(p))
+#define IUnknown_QueryInterface(p, iid, resultp) ((p)->lpVtbl->QueryInterface(p, iid, resultp))
+#define IUnknown_AddRef(p) ((p)->lpVtbl->AddRef(p))
+#define IUnknown_Release(p) ((p)->lpVtbl->Release(p))
+#define IDispatch_QueryInterface(p, iid, resultp) ((p)->lpVtbl->QueryInterface(p, iid, resultp))
+#define IDispatch_AddRef(p) ((p)->lpVtbl->AddRef(p))
+#define IDispatch_Release(p) ((p)->lpVtbl->Release(p))
+#endif /* !VBOX_WITH_GLUE */
+
+interface nsISupports
+{
+#ifndef VBOX_WITH_GLUE
+ struct nsISupports_vtbl *vtbl;
+#else /* !VBOX_WITH_GLUE */
+ CONST_VTBL struct nsISupportsVtbl *lpVtbl;
+#endif /* !VBOX_WITH_GLUE */
+};
+
+/* starting interface: nsIException */
+#define NS_IEXCEPTION_IID_STR "f3a8d3b4-c424-4edc-8bf6-8974c983ba78"
+
+#define NS_IEXCEPTION_IID \
+ {0xf3a8d3b4, 0xc424, 0x4edc, \
+ { 0x8b, 0xf6, 0x89, 0x74, 0xc9, 0x83, 0xba, 0x78 }}
+
+#ifndef VBOX_WITH_GLUE
+struct nsIException_vtbl
+{
+ /* Methods from the interface nsISupports */
+ struct nsISupports_vtbl nsisupports;
+
+ nsresult (*GetMessage)(nsIException *pThis, PRUnichar * *aMessage);
+ nsresult (*GetResult)(nsIException *pThis, nsresult *aResult);
+ nsresult (*GetName)(nsIException *pThis, PRUnichar * *aName);
+ nsresult (*GetFilename)(nsIException *pThis, PRUnichar * *aFilename);
+ nsresult (*GetLineNumber)(nsIException *pThis, PRUint32 *aLineNumber);
+ nsresult (*GetColumnNumber)(nsIException *pThis, PRUint32 *aColumnNumber);
+ nsresult (*GetLocation)(nsIException *pThis, nsIStackFrame * *aLocation);
+ nsresult (*GetInner)(nsIException *pThis, nsIException * *aInner);
+ nsresult (*GetData)(nsIException *pThis, nsISupports * *aData);
+ nsresult (*ToString)(nsIException *pThis, PRUnichar **_retval);
+};
+#else /* !VBOX_WITH_GLUE */
+struct nsIExceptionVtbl
+{
+ nsresult (*QueryInterface)(nsIException *pThis, const nsID *iid, void **resultp);
+ nsrefcnt (*AddRef)(nsIException *pThis);
+ nsrefcnt (*Release)(nsIException *pThis);
+
+ nsresult (*GetMessage)(nsIException *pThis, PRUnichar * *aMessage);
+ nsresult (*GetResult)(nsIException *pThis, nsresult *aResult);
+ nsresult (*GetName)(nsIException *pThis, PRUnichar * *aName);
+ nsresult (*GetFilename)(nsIException *pThis, PRUnichar * *aFilename);
+ nsresult (*GetLineNumber)(nsIException *pThis, PRUint32 *aLineNumber);
+ nsresult (*GetColumnNumber)(nsIException *pThis, PRUint32 *aColumnNumber);
+ nsresult (*GetLocation)(nsIException *pThis, nsIStackFrame * *aLocation);
+ nsresult (*GetInner)(nsIException *pThis, nsIException * *aInner);
+ nsresult (*GetData)(nsIException *pThis, nsISupports * *aData);
+ nsresult (*ToString)(nsIException *pThis, PRUnichar **_retval);
+};
+#define nsIException_QueryInterface(p, iid, resultp) ((p)->lpVtbl->QueryInterface(p, iid, resultp))
+#define nsIException_AddRef(p) ((p)->lpVtbl->AddRef(p))
+#define nsIException_Release(p) ((p)->lpVtbl->Release(p))
+#define nsIException_get_Message(p, aMessage) ((p)->lpVtbl->GetMessage(p, aMessage))
+#define nsIException_GetMessage(p, aMessage) ((p)->lpVtbl->GetMessage(p, aMessage))
+#define nsIException_get_Result(p, aResult) ((p)->lpVtbl->GetResult(p, aResult))
+#define nsIException_GetResult(p, aResult) ((p)->lpVtbl->GetResult(p, aResult))
+#define nsIException_get_Name(p, aName) ((p)->lpVtbl->GetName(p, aName))
+#define nsIException_GetName(p, aName) ((p)->lpVtbl->GetName(p, aName))
+#define nsIException_get_Filename(p, aFilename) ((p)->lpVtbl->GetFilename(p, aFilename))
+#define nsIException_GetFilename(p, aFilename) ((p)->lpVtbl->GetFilename(p, aFilename))
+#define nsIException_get_LineNumber(p, aLineNumber) ((p)->lpVtbl->GetLineNumber(p, aLineNumber))
+#define nsIException_GetLineNumber(p, aLineNumber) ((p)->lpVtbl->GetLineNumber(p, aLineNumber))
+#define nsIException_get_ColumnNumber(p, aColumnNumber) ((p)->lpVtbl->GetColumnNumber(p, aColumnNumber))
+#define nsIException_GetColumnNumber(p, aColumnNumber) ((p)->lpVtbl->GetColumnNumber(p, aColumnNumber))
+#define nsIException_get_Inner(p, aInner) ((p)->lpVtbl->GetInner(p, aInner))
+#define nsIException_GetInner(p, aInner) ((p)->lpVtbl->GetInner(p, aInner))
+#define nsIException_get_Data(p, aData) ((p)->lpVtbl->GetData(p, aData))
+#define nsIException_GetData(p, aData) ((p)->lpVtbl->GetData(p, aData))
+#define nsIException_ToString(p, retval) ((p)->lpVtbl->ToString(p, retval))
+#define IErrorInfo_QueryInterface(p, iid, resultp) ((p)->lpVtbl->QueryInterface(p, iid, resultp))
+#define IErrorInfo_AddRef(p) ((p)->lpVtbl->AddRef(p))
+#define IErrorInfo_Release(p) ((p)->lpVtbl->Release(p))
+#define IErrorInfo_get_Message(p, aMessage) ((p)->lpVtbl->GetMessage(p, aMessage))
+#define IErrorInfo_GetMessage(p, aMessage) ((p)->lpVtbl->GetMessage(p, aMessage))
+#define IErrorInfo_get_Result(p, aResult) ((p)->lpVtbl->GetResult(p, aResult))
+#define IErrorInfo_GetResult(p, aResult) ((p)->lpVtbl->GetResult(p, aResult))
+#define IErrorInfo_get_Name(p, aName) ((p)->lpVtbl->GetName(p, aName))
+#define IErrorInfo_GetName(p, aName) ((p)->lpVtbl->GetName(p, aName))
+#define IErrorInfo_get_Filename(p, aFilename) ((p)->lpVtbl->GetFilename(p, aFilename))
+#define IErrorInfo_GetFilename(p, aFilename) ((p)->lpVtbl->GetFilename(p, aFilename))
+#define IErrorInfo_get_LineNumber(p, aLineNumber) ((p)->lpVtbl->GetLineNumber(p, aLineNumber))
+#define IErrorInfo_GetLineNumber(p, aLineNumber) ((p)->lpVtbl->GetLineNumber(p, aLineNumber))
+#define IErrorInfo_get_ColumnNumber(p, aColumnNumber) ((p)->lpVtbl->GetColumnNumber(p, aColumnNumber))
+#define IErrorInfo_GetColumnNumber(p, aColumnNumber) ((p)->lpVtbl->GetColumnNumber(p, aColumnNumber))
+#define IErrorInfo_get_Inner(p, aInner) ((p)->lpVtbl->GetInner(p, aInner))
+#define IErrorInfo_GetInner(p, aInner) ((p)->lpVtbl->GetInner(p, aInner))
+#define IErrorInfo_get_Data(p, aData) ((p)->lpVtbl->GetData(p, aData))
+#define IErrorInfo_GetData(p, aData) ((p)->lpVtbl->GetData(p, aData))
+#define IErrorInfo_ToString(p, retval) ((p)->lpVtbl->ToString(p, retval))
+#endif /* !VBOX_WITH_GLUE */
+
+interface nsIException
+{
+#ifndef VBOX_WITH_GLUE
+ struct nsIException_vtbl *vtbl;
+#else /* !VBOX_WITH_GLUE */
+ CONST_VTBL struct nsIExceptionVtbl *lpVtbl;
+#endif /* !VBOX_WITH_GLUE */
+};
+
+/* starting interface: nsIStackFrame */
+#define NS_ISTACKFRAME_IID_STR "91d82105-7c62-4f8b-9779-154277c0ee90"
+
+#define NS_ISTACKFRAME_IID \
+ {0x91d82105, 0x7c62, 0x4f8b, \
+ { 0x97, 0x79, 0x15, 0x42, 0x77, 0xc0, 0xee, 0x90 }}
+
+#ifndef VBOX_WITH_GLUE
+struct nsIStackFrame_vtbl
+{
+ /* Methods from the interface nsISupports */
+ struct nsISupports_vtbl nsisupports;
+
+ nsresult (*GetLanguage)(nsIStackFrame *pThis, PRUint32 *aLanguage);
+ nsresult (*GetLanguageName)(nsIStackFrame *pThis, PRUnichar * *aLanguageName);
+ nsresult (*GetFilename)(nsIStackFrame *pThis, PRUnichar * *aFilename);
+ nsresult (*GetName)(nsIStackFrame *pThis, PRUnichar * *aName);
+ nsresult (*GetLineNumber)(nsIStackFrame *pThis, PRInt32 *aLineNumber);
+ nsresult (*GetSourceLine)(nsIStackFrame *pThis, PRUnichar * *aSourceLine);
+ nsresult (*GetCaller)(nsIStackFrame *pThis, nsIStackFrame * *aCaller);
+ nsresult (*ToString)(nsIStackFrame *pThis, PRUnichar **_retval);
+};
+#else /* !VBOX_WITH_GLUE */
+struct nsIStackFrameVtbl
+{
+ nsresult (*QueryInterface)(nsIStackFrame *pThis, const nsID *iid, void **resultp);
+ nsrefcnt (*AddRef)(nsIStackFrame *pThis);
+ nsrefcnt (*Release)(nsIStackFrame *pThis);
+
+ nsresult (*GetLanguage)(nsIStackFrame *pThis, PRUint32 *aLanguage);
+ nsresult (*GetLanguageName)(nsIStackFrame *pThis, PRUnichar * *aLanguageName);
+ nsresult (*GetFilename)(nsIStackFrame *pThis, PRUnichar * *aFilename);
+ nsresult (*GetName)(nsIStackFrame *pThis, PRUnichar * *aName);
+ nsresult (*GetLineNumber)(nsIStackFrame *pThis, PRInt32 *aLineNumber);
+ nsresult (*GetSourceLine)(nsIStackFrame *pThis, PRUnichar * *aSourceLine);
+ nsresult (*GetCaller)(nsIStackFrame *pThis, nsIStackFrame * *aCaller);
+ nsresult (*ToString)(nsIStackFrame *pThis, PRUnichar **_retval);
+};
+#define nsIStackFrame_QueryInterface(p, iid, resultp) ((p)->lpVtbl->QueryInterface(p, iid, resultp))
+#define nsIStackFrame_AddRef(p) ((p)->lpVtbl->AddRef(p))
+#define nsIStackFrame_Release(p) ((p)->lpVtbl->Release(p))
+#define nsIStackFrame_get_Language(p, aLanguage) ((p)->lpVtbl->GetLanguge(p, aLanguage))
+#define nsIStackFrame_GetLanguage(p, aLanguage) ((p)->lpVtbl->GetLanguge(p, aLanguage))
+#define nsIStackFrame_get_LanguageName(p, aLanguageName) ((p)->lpVtbl->GetLanguageName(p, aLanguageName))
+#define nsIStackFrame_GetLanguageName(p, aLanguageName) ((p)->lpVtbl->GetLanguageName(p, aLanguageName))
+#define nsIStackFrame_get_Filename(p, aFilename) ((p)->lpVtbl->GetFilename(p, aFilename))
+#define nsIStackFrame_GetFilename(p, aFilename) ((p)->lpVtbl->GetFilename(p, aFilename))
+#define nsIStackFrame_get_Name(p, aName) ((p)->lpVtbl->GetName(p, aName))
+#define nsIStackFrame_GetName(p, aName) ((p)->lpVtbl->GetName(p, aName))
+#define nsIStackFrame_get_LineNumber(p, aLineNumber) ((p)->lpVtbl->GetLineNumber(p, aLineNumber))
+#define nsIStackFrame_GetLineNumber(p, aLineNumber) ((p)->lpVtbl->GetLineNumber(p, aLineNumber))
+#define nsIStackFrame_get_SourceLine(p, aSourceLine) ((p)->lpVtbl->GetSourceLine(p, aSourceLine))
+#define nsIStackFrame_GetSourceLine(p, aSourceLine) ((p)->lpVtbl->GetSourceLine(p, aSourceLine))
+#define nsIStackFrame_get_Caller(p, aCaller) ((p)->lpVtbl->GetCaller(p, aCaller))
+#define nsIStackFrame_GetCaller(p, aCaller) ((p)->lpVtbl->GetCaller(p, aCaller))
+#define nsIStackFrame_ToString(p, retval) ((p)->lpVtbl->ToString(p, retval))
+#endif /* !VBOX_WITH_GLUE */
+
+interface nsIStackFrame
+{
+#ifndef VBOX_WITH_GLUE
+ struct nsIStackFrame_vtbl *vtbl;
+#else /* !VBOX_WITH_GLUE */
+ CONST_VTBL struct nsIStackFrameVtbl *lpVtbl;
+#endif /* !VBOX_WITH_GLUE */
+};
+
+/* starting interface: nsIEventTarget */
+#define NS_IEVENTTARGET_IID_STR "ea99ad5b-cc67-4efb-97c9-2ef620a59f2a"
+
+#define NS_IEVENTTARGET_IID \
+ {0xea99ad5b, 0xcc67, 0x4efb, \
+ { 0x97, 0xc9, 0x2e, 0xf6, 0x20, 0xa5, 0x9f, 0x2a }}
+
+#ifndef VBOX_WITH_GLUE
+struct nsIEventTarget_vtbl
+{
+ struct nsISupports_vtbl nsisupports;
+
+ nsresult (*PostEvent)(nsIEventTarget *pThis, PLEvent * aEvent);
+ nsresult (*IsOnCurrentThread)(nsIEventTarget *pThis, PRBool *_retval);
+};
+#else /* !VBOX_WITH_GLUE */
+struct nsIEventTargetVtbl
+{
+ nsresult (*QueryInterface)(nsIEventTarget *pThis, const nsID *iid, void **resultp);
+ nsrefcnt (*AddRef)(nsIEventTarget *pThis);
+ nsrefcnt (*Release)(nsIEventTarget *pThis);
+
+ nsresult (*PostEvent)(nsIEventTarget *pThis, PLEvent * aEvent);
+ nsresult (*IsOnCurrentThread)(nsIEventTarget *pThis, PRBool *_retval);
+};
+#define nsIEventTarget_QueryInterface(p, iid, resultp) ((p)->lpVtbl->QueryInterface(p, iid, resultp))
+#define nsIEventTarget_AddRef(p) ((p)->lpVtbl->AddRef(p))
+#define nsIEventTarget_Release(p) ((p)->lpVtbl->Release(p))
+#define nsIEventTarget_PostEvent(p, aEvent) ((p)->lpVtbl->PostEvent(p, aEvent))
+#define nsIEventTarget_IsOnCurrentThread(p, retval) ((p)->lpVtbl->IsOnCurrentThread(p, retval))
+#endif /* !VBOX_WITH_GLUE */
+
+interface nsIEventTarget
+{
+#ifndef VBOX_WITH_GLUE
+ struct nsIEventTarget_vtbl *vtbl;
+#else /* !VBOX_WITH_GLUE */
+ CONST_VTBL struct nsIEventTargetVtbl *lpVtbl;
+#endif /* !VBOX_WITH_GLUE */
+};
+
+/* starting interface: nsIEventQueue */
+#define NS_IEVENTQUEUE_IID_STR "176afb41-00a4-11d3-9f2a-00400553eef0"
+
+#define NS_IEVENTQUEUE_IID \
+ {0x176afb41, 0x00a4, 0x11d3, \
+ { 0x9f, 0x2a, 0x00, 0x40, 0x05, 0x53, 0xee, 0xf0 }}
+
+#ifndef VBOX_WITH_GLUE
+struct nsIEventQueue_vtbl
+{
+ struct nsIEventTarget_vtbl nsieventtarget;
+
+ nsresult (*InitEvent)(nsIEventQueue *pThis, PLEvent * aEvent, void * owner, PLHandleEventProc handler, PLDestroyEventProc destructor);
+ nsresult (*PostSynchronousEvent)(nsIEventQueue *pThis, PLEvent * aEvent, void * *aResult);
+ nsresult (*PendingEvents)(nsIEventQueue *pThis, PRBool *_retval);
+ nsresult (*ProcessPendingEvents)(nsIEventQueue *pThis);
+ nsresult (*EventLoop)(nsIEventQueue *pThis);
+ nsresult (*EventAvailable)(nsIEventQueue *pThis, PRBool *aResult);
+ nsresult (*GetEvent)(nsIEventQueue *pThis, PLEvent * *_retval);
+ nsresult (*HandleEvent)(nsIEventQueue *pThis, PLEvent * aEvent);
+ nsresult (*WaitForEvent)(nsIEventQueue *pThis, PLEvent * *_retval);
+ PRInt32 (*GetEventQueueSelectFD)(nsIEventQueue *pThis);
+ nsresult (*Init)(nsIEventQueue *pThis, PRBool aNative);
+ nsresult (*InitFromPRThread)(nsIEventQueue *pThis, PRThread * thread, PRBool aNative);
+ nsresult (*InitFromPLQueue)(nsIEventQueue *pThis, PLEventQueue * aQueue);
+ nsresult (*EnterMonitor)(nsIEventQueue *pThis);
+ nsresult (*ExitMonitor)(nsIEventQueue *pThis);
+ nsresult (*RevokeEvents)(nsIEventQueue *pThis, void * owner);
+ nsresult (*GetPLEventQueue)(nsIEventQueue *pThis, PLEventQueue * *_retval);
+ nsresult (*IsQueueNative)(nsIEventQueue *pThis, PRBool *_retval);
+ nsresult (*StopAcceptingEvents)(nsIEventQueue *pThis);
+};
+#else /* !VBOX_WITH_GLUE */
+struct nsIEventQueueVtbl
+{
+ nsresult (*QueryInterface)(nsIEventQueue *pThis, const nsID *iid, void **resultp);
+ nsrefcnt (*AddRef)(nsIEventQueue *pThis);
+ nsrefcnt (*Release)(nsIEventQueue *pThis);
+
+ nsresult (*PostEvent)(nsIEventQueue *pThis, PLEvent * aEvent);
+ nsresult (*IsOnCurrentThread)(nsIEventQueue *pThis, PRBool *_retval);
+
+ nsresult (*InitEvent)(nsIEventQueue *pThis, PLEvent * aEvent, void * owner, PLHandleEventProc handler, PLDestroyEventProc destructor);
+ nsresult (*PostSynchronousEvent)(nsIEventQueue *pThis, PLEvent * aEvent, void * *aResult);
+ nsresult (*PendingEvents)(nsIEventQueue *pThis, PRBool *_retval);
+ nsresult (*ProcessPendingEvents)(nsIEventQueue *pThis);
+ nsresult (*EventLoop)(nsIEventQueue *pThis);
+ nsresult (*EventAvailable)(nsIEventQueue *pThis, PRBool *aResult);
+ nsresult (*GetEvent)(nsIEventQueue *pThis, PLEvent * *_retval);
+ nsresult (*HandleEvent)(nsIEventQueue *pThis, PLEvent * aEvent);
+ nsresult (*WaitForEvent)(nsIEventQueue *pThis, PLEvent * *_retval);
+ PRInt32 (*GetEventQueueSelectFD)(nsIEventQueue *pThis);
+ nsresult (*Init)(nsIEventQueue *pThis, PRBool aNative);
+ nsresult (*InitFromPRThread)(nsIEventQueue *pThis, PRThread * thread, PRBool aNative);
+ nsresult (*InitFromPLQueue)(nsIEventQueue *pThis, PLEventQueue * aQueue);
+ nsresult (*EnterMonitor)(nsIEventQueue *pThis);
+ nsresult (*ExitMonitor)(nsIEventQueue *pThis);
+ nsresult (*RevokeEvents)(nsIEventQueue *pThis, void * owner);
+ nsresult (*GetPLEventQueue)(nsIEventQueue *pThis, PLEventQueue * *_retval);
+ nsresult (*IsQueueNative)(nsIEventQueue *pThis, PRBool *_retval);
+ nsresult (*StopAcceptingEvents)(nsIEventQueue *pThis);
+};
+#define nsIEventQueue_QueryInterface(p, iid, resultp) ((p)->lpVtbl->QueryInterface(p, iid, resultp))
+#define nsIEventQueue_AddRef(p) ((p)->lpVtbl->AddRef(p))
+#define nsIEventQueue_Release(p) ((p)->lpVtbl->Release(p))
+#define nsIEventQueue_PostEvent(p, aEvent) ((p)->lpVtbl->PostEvent(p, aEvent))
+#define nsIEventQueue_IsOnCurrentThread(p, retval) ((p)->lpVtbl->IsOnCurrentThread(p, retval))
+#define nsIEventQueue_InitEvent(p, aEvent, owner, handler, destructor) ((p)->lpVtbl->InitEvent(p, aEvent, owner, handler, destructor))
+#define nsIEventQueue_PostSynchronousEvent(p, aEvent, aResult) ((p)->lpVtbl->PostSynchronousEvent(p, aEvent, aResult))
+#define nsIEventQueue_ProcessPendingEvents(p) ((p)->lpVtbl->ProcessPendingEvents(p))
+#define nsIEventQueue_EventLoop(p) ((p)->lpVtbl->EventLoop(p))
+#define nsIEventQueue_EventAvailable(p, aResult) ((p)->lpVtbl->EventAvailable(p, aResult))
+#define nsIEventQueue_get_Event(p, aEvent) ((p)->lpVtbl->GetEvent(p, aEvent))
+#define nsIEventQueue_GetEvent(p, aEvent) ((p)->lpVtbl->GetEvent(p, aEvent))
+#define nsIEventQueue_HandleEvent(p, aEvent) ((p)->lpVtbl->HandleEvent(p, aEvent))
+#define nsIEventQueue_WaitForEvent(p, aEvent) ((p)->lpVtbl->WaitForEvent(p, aEvent))
+#define nsIEventQueue_GetEventQueueSelectFD(p) ((p)->lpVtbl->GetEventQueueSelectFD(p))
+#define nsIEventQueue_Init(p, aNative) ((p)->lpVtbl->Init(p, aNative))
+#define nsIEventQueue_InitFromPLQueue(p, aQueue) ((p)->lpVtbl->InitFromPLQueue(p, aQueue))
+#define nsIEventQueue_EnterMonitor(p) ((p)->lpVtbl->EnterMonitor(p))
+#define nsIEventQueue_ExitMonitor(p) ((p)->lpVtbl->ExitMonitor(p))
+#define nsIEventQueue_RevokeEvents(p, owner) ((p)->lpVtbl->RevokeEvents(p, owner))
+#define nsIEventQueue_GetPLEventQueue(p, retval) ((p)->lpVtbl->GetPLEventQueue(p, retval))
+#define nsIEventQueue_IsQueueNative(p, retval) ((p)->lpVtbl->IsQueueNative(p, retval))
+#define nsIEventQueue_StopAcceptingEvents(p) ((p)->lpVtbl->StopAcceptingEvents(p))
+#endif /* !VBOX_WITH_GLUE */
+
+interface nsIEventQueue
+{
+#ifndef VBOX_WITH_GLUE
+ struct nsIEventQueue_vtbl *vtbl;
+#else /* !VBOX_WITH_GLUE */
+ CONST_VTBL struct nsIEventQueueVtbl *lpVtbl;
+#endif /* !VBOX_WITH_GLUE */
+};
+</xsl:text>
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+ <xsl:apply-templates/>
+ <xsl:text>
+
+#endif /* __cplusplus */
+
+#endif /* !WIN32 */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+
+/**
+ * Function table for dynamic linking.
+ * Use VBoxGetCAPIFunctions() to obtain the pointer to it.
+ */
+typedef struct VBOXCAPI
+{
+ /** The size of the structure. */
+ unsigned cb;
+ /** The structure version. */
+ unsigned uVersion;
+
+ /** Gets the VirtualBox version, major * 1000000 + minor * 1000 + patch. */
+ unsigned int (*pfnGetVersion)(void);
+
+ /** Gets the VirtualBox API version, major * 1000 + minor, e.g. 4003. */
+ unsigned int (*pfnGetAPIVersion)(void);
+
+ /**
+ * New and preferred way to initialize the C bindings for an API client.
+ *
+ * This way is much more flexible, as it can easily handle multiple
+ * sessions (important with more complicated API clients, including
+ * multithreaded ones), and even VBoxSVC crashes can be detected and
+ * processed appropriately by listening for events from the associated
+ * event source in VirtualBoxClient. It is completely up to the client
+ * to decide what to do (terminate or continue after getting new
+ * object references to server-side objects). Must be called in the
+ * primary thread of the client, later API use can be done in any
+ * thread.
+ *
+ * Note that the returned reference is owned by the caller, and thus it's
+ * the caller's responsibility to handle the reference count appropriately.
+ *
+ * @param pszVirtualBoxClientIID pass IVIRTUALBOXCLIENT_IID_STR
+ * @param ppVirtualBoxClient output parameter for VirtualBoxClient
+ * reference, handled as usual with COM/XPCOM.
+ * @returns COM/XPCOM error code
+ */
+ HRESULT (*pfnClientInitialize)(const char *pszVirtualBoxClientIID,
+ IVirtualBoxClient **ppVirtualBoxClient);
+ /**
+ * Initialize the use of the C bindings in a non-primary thread.
+ *
+ * Must be called on any newly created thread which wants to use the
+ * VirtualBox API.
+ *
+ * @returns COM/XPCOM error code
+ */
+ HRESULT (*pfnClientThreadInitialize)(void);
+ /**
+ * Uninitialize the use of the C bindings in a non-primary thread.
+ *
+ * Should be called before terminating the thread which initialized the
+ * C bindings using pfnClientThreadInitialize.
+ *
+ * @returns COM/XPCOM error code
+ */
+ HRESULT (*pfnClientThreadUninitialize)(void);
+ /**
+ * Uninitialize the C bindings for an API client.
+ *
+ * Should be called when the API client is about to terminate and does
+ * not want to use the C bindings any more. It will invalidate all
+ * object references. It is possible, however, to change one's mind,
+ * and call pfnClientInitialize again to continue using the API, as long
+ * as none of the object references from before the re-initialization
+ * are used. Must be called from the primary thread of the client.
+ */
+ void (*pfnClientUninitialize)(void);
+
+ /**
+ * Deprecated way to initialize the C bindings and getting important
+ * object references. Kept for backwards compatibility.
+ *
+ * If any returned reference is NULL then the initialization failed.
+ * Note that the returned references are owned by the C bindings. The
+ * number of calls to Release in the client code must match the number
+ * of calls to AddRef, and additionally at no point in time there can
+ * be more Release calls than AddRef calls.
+ *
+ * @param pszVirtualBoxIID pass IVIRTUALBOX_IID_STR
+ * @param ppVirtualBox output parameter for VirtualBox reference,
+ * owned by C bindings
+ * @param pszSessionIID pass ISESSION_IID_STR
+ * @param ppSession output parameter for Session reference,
+ * owned by C bindings
+ */
+ void (*pfnComInitialize)(const char *pszVirtualBoxIID,
+ IVirtualBox **ppVirtualBox,
+ const char *pszSessionIID,
+ ISession **ppSession);
+ /**
+ * Deprecated way to uninitialize the C bindings for an API client.
+ * Kept for backwards compatibility and must be used if the C bindings
+ * were initialized using pfnComInitialize. */
+ void (*pfnComUninitialize)(void);
+
+ /**
+ * Free string managed by COM/XPCOM.
+ *
+ * @param pwsz pointer to string to be freed
+ */
+ void (*pfnComUnallocString)(BSTR pwsz);
+#ifndef WIN32
+ /** Legacy function, was always for freeing strings only. */
+#define pfnComUnallocMem(pv) pfnComUnallocString((BSTR)(pv))
+#endif /* !WIN32 */
+
+ /**
+ * Convert string from UTF-16 encoding to UTF-8 encoding.
+ *
+ * @param pwszString input string
+ * @param ppszString output string
+ * @returns IPRT status code
+ */
+ int (*pfnUtf16ToUtf8)(CBSTR pwszString, char **ppszString);
+ /**
+ * Convert string from UTF-8 encoding to UTF-16 encoding.
+ *
+ * @param pszString input string
+ * @param ppwszString output string
+ * @returns IPRT status code
+ */
+ int (*pfnUtf8ToUtf16)(const char *pszString, BSTR *ppwszString);
+ /**
+ * Free memory returned by pfnUtf16ToUtf8. Do not use for anything else.
+ *
+ * @param pszString string to be freed.
+ */
+ void (*pfnUtf8Free)(char *pszString);
+ /**
+ * Free memory returned by pfnUtf8ToUtf16. Do not use for anything else.
+ *
+ * @param pwszString string to be freed.
+ */
+ void (*pfnUtf16Free)(BSTR pwszString);
+
+ /**
+ * Create a safearray (used for passing arrays to COM/XPCOM)
+ *
+ * Must be freed by pfnSafeArrayDestroy.
+ *
+ * @param vt variant type, defines the size of the elements
+ * @param lLbound lower bound of the index, should be 0
+ * @param cElements number of elements
+ * @returns pointer to safearray
+ */
+ SAFEARRAY *(*pfnSafeArrayCreateVector)(VARTYPE vt, LONG lLbound, ULONG cElements);
+ /**
+ * Pre-allocate a safearray to be used by an out safearray parameter
+ *
+ * Must be freed by pfnSafeArrayDestroy.
+ *
+ * @returns pointer to safearray (system dependent, may be NULL if
+ * there is no need to pre-allocate a safearray)
+ */
+ SAFEARRAY *(*pfnSafeArrayOutParamAlloc)(void);
+ /**
+ * Copy a C array into a safearray (for passing as an input parameter)
+ *
+ * @param psa pointer to already created safearray.
+ * @param pv pointer to memory block to copy into safearray.
+ * @param cb number of bytes to copy.
+ * @returns COM/XPCOM error code
+ */
+ HRESULT (*pfnSafeArrayCopyInParamHelper)(SAFEARRAY *psa, const void *pv, ULONG cb);
+ /**
+ * Copy a safearray into a C array (for getting an output parameter)
+ *
+ * @param ppv output pointer to newly created array, which has to
+ * be freed with pfnArrayOutFree.
+ * @param pcb number of bytes in the output buffer.
+ * @param vt variant type, defines the size of the elements
+ * @param psa pointer to safearray for getting the data
+ * @returns COM/XPCOM error code
+ */
+ HRESULT (*pfnSafeArrayCopyOutParamHelper)(void **ppv, ULONG *pcb, VARTYPE vt, SAFEARRAY *psa);
+ /**
+ * Copy a safearray into a C array (special variant for interface pointers)
+ *
+ * @param ppaObj output pointer to newly created array, which has
+ * to be freed with pfnArrayOutFree. Note that it's the caller's
+ * responsibility to call Release() on each non-NULL interface
+ * pointer before freeing.
+ * @param pcObj number of pointers in the output buffer.
+ * @param psa pointer to safearray for getting the data
+ * @returns COM/XPCOM error code
+ */
+ HRESULT (*pfnSafeArrayCopyOutIfaceParamHelper)(IUnknown ***ppaObj, ULONG *pcObj, SAFEARRAY *psa);
+ /**
+ * Free a safearray
+ *
+ * @param psa pointer to safearray
+ * @returns COM/XPCOM error code
+ */
+ HRESULT (*pfnSafeArrayDestroy)(SAFEARRAY *psa);
+ /**
+ * Free an out array created by pfnSafeArrayCopyOutParamHelper or
+ * pdnSafeArrayCopyOutIfaceParamHelper.
+ *
+ * @param psa pointer to memory block
+ * @returns COM/XPCOM error code
+ */
+ HRESULT (*pfnArrayOutFree)(void *pv);
+
+#ifndef WIN32
+ /**
+ * Get XPCOM event queue. Deprecated!
+ *
+ * @param ppEventQueue output parameter for nsIEventQueue reference,
+ * owned by C bindings.
+ */
+ void (*pfnGetEventQueue)(nsIEventQueue **ppEventQueue);
+#endif /* !WIN32 */
+
+ /**
+ * Get current COM/XPCOM exception.
+ *
+ * @param ppException output parameter for exception info reference,
+ * may be @c NULL if no exception object has been created by
+ * a previous COM/XPCOM call.
+ * @returns COM/XPCOM error code
+ */
+ HRESULT (*pfnGetException)(IErrorInfo **ppException);
+ /**
+ * Clears current COM/XPCOM exception.
+ *
+ * @returns COM/XPCOM error code
+ */
+ HRESULT (*pfnClearException)(void);
+
+ /**
+ * Process the event queue for a given amount of time.
+ *
+ * Must be called on the primary thread. Typical timeouts are from 200 to
+ * 5000 msecs, to allow for checking a volatile variable if the event queue
+ * processing should be terminated (,
+ * or 0 if only the pending events should be processed, without waiting.
+ *
+ * @param iTimeoutMS how long to process the event queue, -1 means
+ * infinitely long
+ * @returns status code
+ * @retval 0 if at least one event has been processed
+ * @retval 1 if any signal interrupted the native system call (or returned
+ * otherwise)
+ * @retval 2 if the event queue was explicitly interrupted
+ * @retval 3 if the timeout expired
+ * @retval 4 if the function was called from the wrong thread
+ * @retval 5 for all other (unexpected) errors
+ */
+ int (*pfnProcessEventQueue)(LONG64 iTimeoutMS);
+ /**
+ * Interrupt event queue processing.
+ *
+ * Can be called on any thread. Note that this function is not async-signal
+ * safe, so never use it in such a context, instead use a volatile global
+ * variable and a sensible timeout.
+ * @returns 0 if successful, 1 otherwise.
+ */
+ int (*pfnInterruptEventQueueProcessing)(void);
+
+ /**
+ * Clear memory used by a UTF-8 string. Must be zero terminated.
+ * Can be used for any UTF-8 or ASCII/ANSI string.
+ *
+ * @param pszString input/output string
+ */
+ void (*pfnUtf8Clear)(char *pszString);
+ /**
+ * Clear memory used by a UTF-16 string. Must be zero terminated.
+ * Can be used for any UTF-16 or UCS-2 string.
+ *
+ * @param pwszString input/output string
+ */
+ void (*pfnUtf16Clear)(BSTR pwszString);
+
+ /** Tail version, same as uVersion.
+ *
+ * This should only be accessed if for some reason an API client needs
+ * exactly the version it requested, or if cb is used to calculate the
+ * address of this field. It may move as the structure before this is
+ * allowed to grow as long as all the data from earlier minor versions
+ * remains at the same place.
+ */
+ unsigned uEndVersion;
+} VBOXCAPI;
+/** Pointer to a const VBOXCAPI function table. */
+typedef VBOXCAPI const *PCVBOXCAPI;
+#ifndef WIN32
+/** Backwards compatibility: Pointer to a const VBOXCAPI function table.
+ * Use PCVBOXCAPI instead. */
+typedef VBOXCAPI const *PCVBOXXPCOM;
+#endif /* !WIN32 */
+
+#ifndef WIN32
+/** Backwards compatibility: make sure old code using VBOXXPCOMC still compiles.
+ * Use VBOXCAPI instead. */
+#define VBOXXPCOMC VBOXCAPI
+#endif /* !WIN32 */
+
+/** Extract the C API style major version.
+ * Useful for comparing the interface version in VBOXCAPI::uVersion. */
+#define VBOX_CAPI_MAJOR(x) (((x) &amp; 0xffff0000U) &gt;&gt; 16)
+
+/** Extract the C API style major version.
+ * Useful for comparing the interface version in VBOXCAPI::uVersion. */
+#define VBOX_CAPI_MINOR(x) ((x) &amp; 0x0000ffffU)
+
+/** The current interface version.
+ * For use with VBoxGetCAPIFunctions and to be found in VBOXCAPI::uVersion. */
+#define VBOX_CAPI_VERSION 0x00040001U
+
+#ifndef WIN32
+/** Backwards compatibility: The current interface version.
+ * Use VBOX_CAPI_VERSION instead. */
+#define VBOX_XPCOMC_VERSION VBOX_CAPI_VERSION
+#endif /* !WIN32 */
+
+/** VBoxGetCAPIFunctions. */
+VBOXCAPI_DECL(PCVBOXCAPI) VBoxGetCAPIFunctions(unsigned uVersion);
+#ifndef WIN32
+/** Backwards compatibility: VBoxGetXPCOMCFunctions.
+ * Use VBoxGetCAPIFunctions instead. */
+VBOXCAPI_DECL(PCVBOXCAPI) VBoxGetXPCOMCFunctions(unsigned uVersion);
+#endif /* !WIN32 */
+
+/** Typedef for VBoxGetCAPIFunctions. */
+typedef PCVBOXCAPI (*PFNVBOXGETCAPIFUNCTIONS)(unsigned uVersion);
+#ifndef WIN32
+/** Backwards compatibility: Typedef for VBoxGetXPCOMCFunctions.
+ * Use PFNVBOXGETCAPIFUNCTIONS instead. */
+typedef PCVBOXCAPI (*PFNVBOXGETXPCOMCFUNCTIONS)(unsigned uVersion);
+#endif /* !WIN32 */
+
+/** The symbol name of VBoxGetCAPIFunctions. */
+#ifdef __OS2__
+# define VBOX_GET_CAPI_FUNCTIONS_SYMBOL_NAME "_VBoxGetCAPIFunctions"
+#else /* !__OS2__ */
+# define VBOX_GET_CAPI_FUNCTIONS_SYMBOL_NAME "VBoxGetCAPIFunctions"
+#endif /* !__OS2__ */
+#ifndef WIN32
+/** Backwards compatibility: The symbol name of VBoxGetXPCOMCFunctions.
+ * Use VBOX_GET_CAPI_FUNCTIONS_SYMBOL_NAME instead. */
+# ifdef __OS2__
+# define VBOX_GET_XPCOMC_FUNCTIONS_SYMBOL_NAME "_VBoxGetXPCOMCFunctions"
+# else /* !__OS2__ */
+# define VBOX_GET_XPCOMC_FUNCTIONS_SYMBOL_NAME "VBoxGetXPCOMCFunctions"
+# endif /* !__OS2__ */
+#endif /* !WIN32 */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* !___VirtualBox_CAPI_h */
+</xsl:text>
+</xsl:template>
+
+<!--
+ * ignore all |if|s except those for XPIDL target
+-->
+<xsl:template match="if">
+ <xsl:if test="@target='xpidl'">
+ <xsl:apply-templates/>
+ </xsl:if>
+</xsl:template>
+<xsl:template match="if" mode="forward">
+ <xsl:if test="@target='xpidl'">
+ <xsl:apply-templates mode="forward"/>
+ </xsl:if>
+</xsl:template>
+<xsl:template match="if" mode="forwarder">
+ <xsl:if test="@target='midl'">
+ <xsl:apply-templates mode="forwarder"/>
+ </xsl:if>
+</xsl:template>
+
+
+<!--
+ * libraries
+-->
+<xsl:template match="idl/library">
+ <!-- result codes -->
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+ <xsl:for-each select="application/result">
+ <xsl:apply-templates select="."/>
+ </xsl:for-each>
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+ <!-- forward declarations -->
+ <xsl:apply-templates select="application/interface | application/if/interface" mode="forward"/>
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+ <!-- typedef'ing the struct declarations -->
+ <xsl:apply-templates select="application/interface | application/if/interface" mode="typedef"/>
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+ <!-- all enums go first -->
+ <xsl:apply-templates select="application/enum | application/if/enum"/>
+ <!-- everything else but result codes and enums
+ <xsl:apply-templates select="*[not(self::result or self::enum) and
+ not(self::if[result] or self::if[enum])]"/> -->
+ <!-- the modules (i.e. everything else) -->
+ <xsl:apply-templates select="application/interface | application/if[interface]
+ | application/module | application/if[module]"/>
+ <!-- -->
+</xsl:template>
+
+
+<!--
+ * result codes
+-->
+<xsl:template match="result">
+ <xsl:value-of select="concat('#define ',@name,' ((HRESULT)',@value, ')')"/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * forward declarations
+-->
+<xsl:template match="interface" mode="forward">
+ <xsl:if test="not(@internal='yes')">
+ <xsl:text>interface </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;&#x0A;</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+
+<!--
+ * typedef'ing the struct declarations
+-->
+<xsl:template match="interface" mode="typedef">
+ <xsl:if test="not(@internal='yes')">
+ <xsl:text>typedef interface </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;&#x0A;</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+
+<!--
+ * COBJMACRO style convenience macros for calling methods
+-->
+<xsl:template match="interface" mode="cobjmacro">
+ <xsl:param name="iface"/>
+
+ <xsl:variable name="extends" select="@extends"/>
+ <xsl:choose>
+ <xsl:when test="$extends='$unknown'">
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_QueryInterface(p, iid, resultp) ((p)->lpVtbl->QueryInterface(p, iid, resultp))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_AddRef(p) ((p)->lpVtbl->AddRef(p))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_Release(p) ((p)->lpVtbl->Release(p))&#x0A;</xsl:text>
+ </xsl:when>
+ <xsl:when test="$extends='$errorinfo'">
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_QueryInterface(p, iid, resultp) ((p)->lpVtbl->QueryInterface(p, iid, resultp))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_AddRef(p) ((p)->lpVtbl->AddRef(p))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_Release(p) ((p)->lpVtbl->Release(p))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_get_Message(p, aMessage) ((p)->lpVtbl->GetMessage(p, aMessage))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_GetMessage(p, aMessage) ((p)->lpVtbl->GetMessage(p, aMessage))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_get_Result(p, aResult) ((p)->lpVtbl->GetResult(p, aResult))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_GetResult(p, aResult) ((p)->lpVtbl->GetResult(p, aResult))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_get_Name(p, aName) ((p)->lpVtbl->GetName(p, aName))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_GetName(p, aName) ((p)->lpVtbl->GetName(p, aName))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_get_Filename(p, aFilename) ((p)->lpVtbl->GetFilename(p, aFilename))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_GetFilename(p, aFilename) ((p)->lpVtbl->GetFilename(p, aFilename))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_get_LineNumber(p, aLineNumber) ((p)->lpVtbl->GetLineNumber(p, aLineNumber))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_GetLineNumber(p, aLineNumber) ((p)->lpVtbl->GetLineNumber(p, aLineNumber))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_get_ColumnNumber(p, aColumnNumber) ((p)->lpVtbl->GetColumnNumber(p, aColumnNumber))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_GetColumnNumber(p, aColumnNumber) ((p)->lpVtbl->GetColumnNumber(p, aColumnNumber))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_get_Location(p, aLocation) ((p)->lpVtbl->GetLocation(p, aLocation))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_GetLocation(p, aLocation) ((p)->lpVtbl->GetLocation(p, aLocation))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_get_Inner(p, aInner) ((p)->lpVtbl->GetInner(p, aInner))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_GetInner(p, aInner) ((p)->lpVtbl->GetInner(p, aInner))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_get_Data(p, aData) ((p)->lpVtbl->GetData(p, aData))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_GetData(p, aData) ((p)->lpVtbl->GetData(p, aData))&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>_ToString(p, retval) ((p)->lpVtbl->ToString(p, retval))&#x0A;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="key('G_keyInterfacesByName', $extends)" mode="cobjmacro">
+ <xsl:with-param name="iface" select="$iface"/>
+ </xsl:apply-templates>
+ </xsl:otherwise>
+ </xsl:choose>
+ <!-- attributes (properties) -->
+ <xsl:apply-templates select="attribute | if/attribute" mode="cobjmacro">
+ <xsl:with-param name="iface" select="$iface"/>
+ </xsl:apply-templates>
+ <!-- methods -->
+ <xsl:apply-templates select="method | if/method" mode="cobjmacro">
+ <xsl:with-param name="iface" select="$iface"/>
+ </xsl:apply-templates>
+</xsl:template>
+
+
+<!--
+ * emit flat vtable, compatible with COM
+-->
+<xsl:template match="interface" mode="vtab_flat">
+ <xsl:param name="iface"/>
+
+ <xsl:variable name="name" select="@name"/>
+ <xsl:variable name="extends" select="@extends"/>
+ <xsl:choose>
+ <xsl:when test="$extends='$unknown'">
+ <xsl:text> nsresult (*QueryInterface)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis, const nsID *iid, void **resultp);&#x0A;</xsl:text>
+ <xsl:text> nsrefcnt (*AddRef)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis);&#x0A;</xsl:text>
+ <xsl:text> nsrefcnt (*Release)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis);&#x0A;</xsl:text>
+ </xsl:when>
+ <xsl:when test="$extends='$errorinfo'">
+ <xsl:text> nsresult (*QueryInterface)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis, const nsID *iid, void **resultp);&#x0A;</xsl:text>
+ <xsl:text> nsrefcnt (*AddRef)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis);&#x0A;</xsl:text>
+ <xsl:text> nsrefcnt (*Release)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis);&#x0A;</xsl:text>
+ <xsl:text> nsresult (*GetMessage)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis, PRUnichar * *aMessage);&#x0A;</xsl:text>
+ <xsl:text> nsresult (*GetResult)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis, nsresult *aResult);&#x0A;</xsl:text>
+ <xsl:text> nsresult (*GetName)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text>*pThis, PRUnichar * *aName);&#x0A;</xsl:text>
+ <xsl:text> nsresult (*GetFilename)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis, PRUnichar * *aFilename);&#x0A;</xsl:text>
+ <xsl:text> nsresult (*GetLineNumber)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis, PRUint32 *aLineNumber);&#x0A;</xsl:text>
+ <xsl:text> nsresult (*GetColumnNumber)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis, PRUint32 *aColumnNumber);&#x0A;</xsl:text>
+ <xsl:text> nsresult (*GetLocation)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis, nsIStackFrame * *aLocation);&#x0A;</xsl:text>
+ <xsl:text> nsresult (*GetInner)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis, nsIException * *aInner);&#x0A;</xsl:text>
+ <xsl:text> nsresult (*GetData)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis, nsISupports * *aData);&#x0A;</xsl:text>
+ <xsl:text> nsresult (*ToString)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis, PRUnichar **_retval);&#x0A;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="key('G_keyInterfacesByName', $extends)" mode="vtab_flat">
+ <xsl:with-param name="iface" select="$iface"/>
+ </xsl:apply-templates>
+ </xsl:otherwise>
+ </xsl:choose>
+ <!-- attributes (properties) -->
+ <xsl:apply-templates select="attribute | if/attribute">
+ <xsl:with-param name="iface" select="$iface"/>
+ </xsl:apply-templates>
+ <xsl:variable name="reservedAttributes" select="@reservedAttributes"/>
+ <xsl:if test="$reservedAttributes > 0">
+ <!-- tricky way to do a "for" loop without recursion -->
+ <xsl:for-each select="(//*)[position() &lt;= $reservedAttributes]">
+ <xsl:text> nsresult (*GetInternalAndReservedAttribute</xsl:text>
+ <xsl:value-of select="concat(position(), $name)"/>
+ <xsl:text>)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis, PRUint32 *reserved);&#x0A;&#x0A;</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+ <!-- methods -->
+ <xsl:apply-templates select="method | if/method">
+ <xsl:with-param name="iface" select="$iface"/>
+ </xsl:apply-templates>
+ <xsl:variable name="reservedMethods" select="@reservedMethods"/>
+ <xsl:if test="$reservedMethods > 0">
+ <!-- tricky way to do a "for" loop without recursion -->
+ <xsl:for-each select="(//*)[position() &lt;= $reservedMethods]">
+ <xsl:text> nsresult (*InternalAndReservedMethod</xsl:text>
+ <xsl:value-of select="concat(position(), $name)"/>
+ <xsl:text>)(</xsl:text>
+ <xsl:value-of select="$iface"/>
+ <xsl:text> *pThis);&#x0A;&#x0A;</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+</xsl:template>
+
+
+<!--
+ * interfaces
+-->
+<xsl:template match="interface">
+ <xsl:if test="not(@internal='yes')">
+ <xsl:variable name="name" select="@name"/>
+ <xsl:text>/* Start of struct </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text> declaration */&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:call-template name="string-to-upper">
+ <xsl:with-param name="str" select="$name"/>
+ </xsl:call-template>
+ <xsl:value-of select="concat('_IID_STR &quot;',@uuid,'&quot;')"/>
+ <xsl:text>&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:call-template name="string-to-upper">
+ <xsl:with-param name="str" select="$name"/>
+ </xsl:call-template>
+ <xsl:text>_IID { \&#x0A;</xsl:text>
+ <xsl:text> 0x</xsl:text><xsl:value-of select="substring(@uuid,1,8)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,10,4)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,15,4)"/>
+ <xsl:text>, \&#x0A; </xsl:text>
+ <xsl:text>{ 0x</xsl:text><xsl:value-of select="substring(@uuid,20,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,22,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,25,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,27,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,29,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,31,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,33,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,35,2)"/>
+ <xsl:text> } \&#x0A;}&#x0A;</xsl:text>
+ <xsl:text>/* COM compatibility */&#x0A;</xsl:text>
+ <xsl:text>VBOX_EXTERN_CONST(nsIID, IID_</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>);&#x0A;</xsl:text>
+ <xsl:text>#ifndef VBOX_WITH_GLUE&#x0A;</xsl:text>
+ <xsl:text>struct </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>_vtbl&#x0A;{&#x0A;</xsl:text>
+ <xsl:text> </xsl:text>
+ <xsl:choose>
+ <xsl:when test="@extends='$unknown'">struct nsISupports_vtbl nsisupports;</xsl:when>
+ <xsl:when test="@extends='$errorinfo'">struct nsIException_vtbl nsiexception;</xsl:when>
+ <xsl:otherwise>
+ <xsl:text>struct </xsl:text>
+ <xsl:value-of select="@extends"/>
+ <xsl:text>_vtbl </xsl:text>
+ <xsl:call-template name="string-to-lower">
+ <xsl:with-param name="str" select="@extends"/>
+ </xsl:call-template>
+ <xsl:text>;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>&#x0A;&#x0A;</xsl:text>
+ <!-- attributes (properties) -->
+ <xsl:apply-templates select="attribute | if/attribute"/>
+ <xsl:variable name="reservedAttributes" select="@reservedAttributes"/>
+ <xsl:if test="$reservedAttributes > 0">
+ <!-- tricky way to do a "for" loop without recursion -->
+ <xsl:for-each select="(//*)[position() &lt;= $reservedAttributes]">
+ <xsl:text> nsresult (*GetInternalAndReservedAttribute</xsl:text>
+ <xsl:value-of select="concat(position(), $name)"/>
+ <xsl:text>)(</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text> *pThis, PRUint32 *reserved);&#x0A;&#x0A;</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+ <!-- methods -->
+ <xsl:apply-templates select="method | if/method"/>
+ <xsl:variable name="reservedMethods" select="@reservedMethods"/>
+ <xsl:if test="$reservedMethods > 0">
+ <!-- tricky way to do a "for" loop without recursion -->
+ <xsl:for-each select="(//*)[position() &lt;= $reservedMethods]">
+ <xsl:text> nsresult (*InternalAndReservedMethod</xsl:text>
+ <xsl:value-of select="concat(position(), $name)"/>
+ <xsl:text>)(</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text> *pThis);&#x0A;&#x0A;</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+ <!-- -->
+ <xsl:text>};&#x0A;</xsl:text>
+ <xsl:text>#else /* VBOX_WITH_GLUE */&#x0A;</xsl:text>
+ <xsl:text>struct </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>Vtbl&#x0A;{&#x0A;</xsl:text>
+ <xsl:apply-templates select="." mode="vtab_flat">
+ <xsl:with-param name="iface" select="$name"/>
+ </xsl:apply-templates>
+ <xsl:text>};&#x0A;</xsl:text>
+ <xsl:apply-templates select="." mode="cobjmacro">
+ <xsl:with-param name="iface" select="$name"/>
+ </xsl:apply-templates>
+ <!-- -->
+ <xsl:text>#endif /* VBOX_WITH_GLUE */&#x0A;</xsl:text>
+ <xsl:text>&#x0A;</xsl:text>
+ <xsl:text>interface </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>&#x0A;{&#x0A;</xsl:text>
+ <xsl:text>#ifndef VBOX_WITH_GLUE&#x0A;</xsl:text>
+ <xsl:text> struct </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>_vtbl *vtbl;&#x0A;</xsl:text>
+ <xsl:text>#else /* VBOX_WITH_GLUE */&#x0A;</xsl:text>
+ <xsl:text> CONST_VTBL struct </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>Vtbl *lpVtbl;&#x0A;</xsl:text>
+ <xsl:text>#endif /* VBOX_WITH_GLUE */&#x0A;</xsl:text>
+ <xsl:text>};&#x0A;</xsl:text>
+ <xsl:text>/* End of struct </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text> declaration */&#x0A;&#x0A;</xsl:text>
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+ </xsl:if>
+</xsl:template>
+
+
+<!--
+ * attributes
+-->
+<xsl:template match="attribute">
+ <xsl:param name="iface" select="ancestor::interface/@name"/>
+
+ <xsl:choose>
+ <!-- safearray pseudo attribute -->
+ <xsl:when test="@safearray='yes'">
+ <!-- getter -->
+ <xsl:text> nsresult (*Get</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>)(</xsl:text>
+ <xsl:value-of select="$iface" />
+ <xsl:text> *pThis, </xsl:text>
+ <!-- array size -->
+ <xsl:text>PRUint32 *</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>Size, </xsl:text>
+ <!-- array pointer -->
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:text> **</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>);&#x0A;</xsl:text>
+ <!-- setter -->
+ <xsl:if test="not(@readonly='yes')">
+ <xsl:text> nsresult (*Set</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>)(</xsl:text>
+ <xsl:value-of select="$iface" />
+ <xsl:text> *pThis, </xsl:text>
+ <!-- array size -->
+ <xsl:text>PRUint32 </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>Size, </xsl:text>
+ <!-- array pointer -->
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:text> *</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>);&#x0A;</xsl:text>
+ </xsl:if>
+ </xsl:when>
+ <!-- normal attribute -->
+ <xsl:otherwise>
+ <xsl:text> </xsl:text>
+ <xsl:if test="@readonly='yes'">
+ <xsl:text>nsresult (*Get</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>)(</xsl:text>
+ <xsl:value-of select="$iface" />
+ <xsl:text> *pThis, </xsl:text>
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:text> *</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>);&#x0A;</xsl:text>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="@readonly='yes'">
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>nsresult (*Get</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>)(</xsl:text>
+ <xsl:value-of select="$iface" />
+ <xsl:text> *pThis, </xsl:text>
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:text> *</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>);&#x0A; </xsl:text>
+ <xsl:text>nsresult (*Set</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>)(</xsl:text>
+ <xsl:value-of select="$iface" />
+ <xsl:text> *pThis, </xsl:text>
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>);&#x0A;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+<xsl:template match="attribute" mode="cobjmacro">
+ <xsl:param name="iface"/>
+
+ <!-- getter (COM compatible) -->
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="concat($iface, '_get_')"/>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>(p, a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>) ((p)->lpVtbl->Get</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>(p, a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>))&#x0A;</xsl:text>
+
+ <!-- getter (XPCOM compatible) -->
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="concat($iface, '_Get')"/>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>(p, a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>) ((p)->lpVtbl->Get</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>(p, a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>))&#x0A;</xsl:text>
+
+ <xsl:if test="not(@readonly='yes')">
+ <!-- setter (COM compatible) -->
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="concat($iface, '_put_')"/>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>(p, a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>) ((p)->lpVtbl->Set</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>(p, a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>))&#x0A;</xsl:text>
+
+ <!-- setter (XPCOM compatible) -->
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="concat($iface, '_Set')"/>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>(p, a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>) ((p)->lpVtbl->Set</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>(p, a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>))&#x0A;</xsl:text>
+
+ </xsl:if>
+</xsl:template>
+
+<!--
+ * methods
+-->
+<xsl:template match="method">
+ <xsl:param name="iface" select="ancestor::interface/@name"/>
+
+ <xsl:if test="param/@mod='ptr'">
+ <!-- methods using native types must be non-scriptable
+ <xsl:text> [noscript]&#x0A;</xsl:text>-->
+ </xsl:if>
+ <xsl:text> nsresult (*</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:if test="param">
+ <xsl:text>)(&#x0A;</xsl:text>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="$iface" />
+ <xsl:text> *pThis,&#x0A;</xsl:text>
+ <xsl:for-each select="param [position() != last()]">
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates select="."/>
+ <xsl:text>,&#x0A;</xsl:text>
+ </xsl:for-each>
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates select="param [last()]"/>
+ <xsl:text>&#x0A; );&#x0A;</xsl:text>
+ </xsl:if>
+ <xsl:if test="not(param)">
+ <xsl:text>)(</xsl:text>
+ <xsl:value-of select="$iface" />
+ <xsl:text> *pThis );&#x0A;</xsl:text>
+ </xsl:if>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+<xsl:template match="method" mode="cobjmacro">
+ <xsl:param name="iface"/>
+
+ <xsl:text>#define </xsl:text>
+ <xsl:value-of select="concat($iface, '_')"/>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>(p</xsl:text>
+ <xsl:for-each select="param">
+ <xsl:text>, a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ <xsl:text>) ((p)->lpVtbl-></xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>(p</xsl:text>
+ <xsl:for-each select="param">
+ <xsl:text>, a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ <xsl:text>))&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * modules
+-->
+<xsl:template match="module">
+ <xsl:apply-templates select="class"/>
+</xsl:template>
+
+
+<!--
+ * co-classes
+-->
+<xsl:template match="module/class">
+ <!-- class and contract id -->
+ <xsl:text>&#x0A;</xsl:text>
+ <xsl:text>#define NS_</xsl:text>
+ <xsl:call-template name="string-to-upper">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_CID { \&#x0A;</xsl:text>
+ <xsl:text> 0x</xsl:text><xsl:value-of select="substring(@uuid,1,8)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,10,4)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,15,4)"/>
+ <xsl:text>, \&#x0A; </xsl:text>
+ <xsl:text>{ 0x</xsl:text><xsl:value-of select="substring(@uuid,20,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,22,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,25,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,27,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,29,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,31,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,33,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,35,2)"/>
+ <xsl:text> } \&#x0A;}&#x0A;</xsl:text>
+ <xsl:text>#define NS_</xsl:text>
+ <xsl:call-template name="string-to-upper">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <!-- Contract ID -->
+ <xsl:text>_CONTRACTID &quot;@</xsl:text>
+ <xsl:value-of select="@namespace"/>
+ <xsl:text>/</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;1&quot;&#x0A;</xsl:text>
+ <!-- CLSID_xxx declarations for XPCOM, for compatibility with Win32 -->
+ <xsl:text>/* COM compatibility */&#x0A;</xsl:text>
+ <xsl:text>VBOX_EXTERN_CONST(nsCID, CLSID_</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>);&#x0A;</xsl:text>
+ <xsl:text>&#x0A;&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * enums
+-->
+<xsl:template match="enum">
+ <xsl:text>/* Start of enum </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> declaration */&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:call-template name="string-to-upper">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:value-of select="concat('_IID_STR &quot;',@uuid,'&quot;')"/>
+ <xsl:text>&#x0A;</xsl:text>
+ <xsl:text>#define </xsl:text>
+ <xsl:call-template name="string-to-upper">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_IID { \&#x0A;</xsl:text>
+ <xsl:text> 0x</xsl:text><xsl:value-of select="substring(@uuid,1,8)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,10,4)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,15,4)"/>
+ <xsl:text>, \&#x0A; </xsl:text>
+ <xsl:text>{ 0x</xsl:text><xsl:value-of select="substring(@uuid,20,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,22,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,25,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,27,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,29,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,31,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,33,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,35,2)"/>
+ <xsl:text> } \&#x0A;}&#x0A;</xsl:text>
+ <xsl:text>typedef enum </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>&#x0A;{&#x0A;</xsl:text>
+ <xsl:variable name="this" select="."/>
+ <xsl:for-each select="const">
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="$this/@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:value-of select="@name"/> = <xsl:value-of select="@value"/>
+ <xsl:if test="position() != last()">
+ <xsl:text>,</xsl:text>
+ </xsl:if>
+ <xsl:text>&#x0A;</xsl:text>
+ </xsl:for-each>
+ <xsl:text>} </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;&#x0A;</xsl:text>
+ <xsl:text>/* End of enum </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> declaration */
+#define </xsl:text>
+ <xsl:value-of select="concat(@name, '_T PRUint32&#x0A;&#x0A;&#x0A;')"/>
+</xsl:template>
+
+
+<!--
+ * method parameters
+-->
+<xsl:template match="method/param">
+ <xsl:choose>
+ <!-- safearray parameters -->
+ <xsl:when test="@safearray='yes'">
+ <!-- array size -->
+ <xsl:choose>
+ <xsl:when test="@dir='in'">
+ <xsl:text>PRUint32 </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>Size,&#x0A;</xsl:text>
+ </xsl:when>
+ <xsl:when test="@dir='out'">
+ <xsl:text>PRUint32 *</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>Size,&#x0A;</xsl:text>
+ </xsl:when>
+ <xsl:when test="@dir='return'">
+ <xsl:text>PRUint32 *</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>Size,&#x0A;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>PRUint32 </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>Size,&#x0A;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <!-- array pointer -->
+ <xsl:text> </xsl:text>
+ <xsl:choose>
+ <xsl:when test="@dir='in'">
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:text>*</xsl:text>
+ </xsl:when>
+ <xsl:when test="@dir='out'">
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:text>**</xsl:text>
+ </xsl:when>
+ <xsl:when test="@dir='return'">
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:text>**</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:text>*</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="@name"/>
+ </xsl:when>
+ <!-- normal and array parameters -->
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="@dir='in'">
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:text></xsl:text>
+ </xsl:when>
+ <xsl:when test="@dir='out'">
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:text> *</xsl:text>
+ </xsl:when>
+ <xsl:when test="@dir='return'">
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:text> *</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:text></xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template match="method/param" mode="forwarder">
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>PRUint32</xsl:text>
+ <xsl:if test="@dir='out' or @dir='return'">
+ <xsl:text> *</xsl:text>
+ </xsl:if>
+ <xsl:text> a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>Size, </xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:if test="@dir='out' or @dir='return'">
+ <xsl:text> *</xsl:text>
+ </xsl:if>
+ <xsl:if test="@safearray='yes'">
+ <xsl:text> *</xsl:text>
+ </xsl:if>
+ <xsl:text> a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+</xsl:template>
+
+
+<!--
+ * attribute/parameter type conversion
+-->
+<xsl:template match="attribute/@type | param/@type">
+ <xsl:variable name="self_target" select="current()/ancestor::if/@target"/>
+
+ <xsl:choose>
+ <!-- modifiers -->
+ <xsl:when test="name(current())='type' and ../@mod">
+ <xsl:choose>
+ <xsl:when test="../@mod='ptr'">
+ <xsl:choose>
+ <!-- standard types -->
+ <!--xsl:when test=".='result'">??</xsl:when-->
+ <xsl:when test=".='boolean'">booleanPtr</xsl:when>
+ <xsl:when test=".='octet'">octetPtr</xsl:when>
+ <xsl:when test=".='short'">shortPtr</xsl:when>
+ <xsl:when test=".='unsigned short'">ushortPtr</xsl:when>
+ <xsl:when test=".='long'">longPtr</xsl:when>
+ <xsl:when test=".='long long'">llongPtr</xsl:when>
+ <xsl:when test=".='unsigned long'">ulongPtr</xsl:when>
+ <xsl:when test=".='unsigned long long'">ullongPtr</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:text>attribute 'mod=</xsl:text>
+ <xsl:value-of select="concat('&quot;',../@mod,'&quot;')"/>
+ <xsl:text>' cannot be used with type </xsl:text>
+ <xsl:value-of select="concat('&quot;',current(),'&quot;!')"/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="../@mod='string'">
+ <xsl:choose>
+ <!-- standard types -->
+ <!--xsl:when test=".='result'">??</xsl:when-->
+ <xsl:when test=".='uuid'">wstring</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:text>attribute 'mod=</xsl:text>
+ <xsl:value-of select="concat('&quot;',../@mod,'&quot;')"/>
+ <xsl:text>' cannot be used with type </xsl:text>
+ <xsl:value-of select="concat('&quot;',current(),'&quot;!')"/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:value-of select="concat('value &quot;',../@mod,'&quot; ')"/>
+ <xsl:text>of attribute 'mod' is invalid!</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <!-- no modifiers -->
+ <xsl:otherwise>
+ <xsl:choose>
+ <!-- standard types -->
+ <xsl:when test=".='result'">nsresult</xsl:when>
+ <xsl:when test=".='boolean'">boolean</xsl:when>
+ <xsl:when test=".='octet'">octet</xsl:when>
+ <xsl:when test=".='short'">short</xsl:when>
+ <xsl:when test=".='unsigned short'">unsigned short</xsl:when>
+ <xsl:when test=".='long'">long</xsl:when>
+ <xsl:when test=".='long long'">long long</xsl:when>
+ <xsl:when test=".='unsigned long'">unsigned long</xsl:when>
+ <xsl:when test=".='unsigned long long'">unsigned long long</xsl:when>
+ <xsl:when test=".='char'">char</xsl:when>
+ <xsl:when test=".='wchar'">wchar</xsl:when>
+ <xsl:when test=".='string'">string</xsl:when>
+ <xsl:when test=".='wstring'">wstring</xsl:when>
+ <!-- UUID type -->
+ <xsl:when test=".='uuid'">
+ <xsl:choose>
+ <xsl:when test="name(..)='attribute'">
+ <xsl:choose>
+ <xsl:when test="../@readonly='yes'">
+ <xsl:text>nsIDPtr</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="../@name"/>
+ <xsl:text>: Non-readonly uuid attributes are not supported!</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="name(..)='param'">
+ <xsl:choose>
+ <xsl:when test="../@dir='in' and not(../@safearray='yes')">
+ <xsl:text>nsIDRef</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>nsIDPtr</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+ <!-- system interface types -->
+ <xsl:when test=".='$unknown'">nsISupports</xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <!-- enum types -->
+ <xsl:when test="
+ (ancestor::library/application/enum[@name=current()]) or
+ (ancestor::library/application/if[@target=$self_target]/enum[@name=current()])
+ ">
+ <xsl:text>PRUint32</xsl:text>
+ </xsl:when>
+ <!-- custom interface types -->
+ <xsl:when test="
+ (ancestor::library/application/interface[@name=current()]) or
+ (ancestor::library/application/if[@target=$self_target]/interface[@name=current()])
+ ">
+ <xsl:value-of select="."/>
+ </xsl:when>
+ <!-- other types -->
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:text>Unknown parameter type: </xsl:text>
+ <xsl:value-of select="."/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template match="attribute/@type | param/@type" mode="forwarder">
+
+ <xsl:variable name="self_target" select="current()/ancestor::if/@target"/>
+
+ <xsl:choose>
+ <!-- modifiers -->
+ <xsl:when test="name(current())='type' and ../@mod">
+ <xsl:choose>
+ <xsl:when test="../@mod='ptr'">
+ <xsl:choose>
+ <!-- standard types -->
+ <!--xsl:when test=".='result'">??</xsl:when-->
+ <xsl:when test=".='boolean'">PRBool *</xsl:when>
+ <xsl:when test=".='octet'">PRUint8 *</xsl:when>
+ <xsl:when test=".='short'">PRInt16 *</xsl:when>
+ <xsl:when test=".='unsigned short'">PRUint16 *</xsl:when>
+ <xsl:when test=".='long'">PRInt32 *</xsl:when>
+ <xsl:when test=".='long long'">PRInt64 *</xsl:when>
+ <xsl:when test=".='unsigned long'">PRUint32 *</xsl:when>
+ <xsl:when test=".='unsigned long long'">PRUint64 *</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:text>attribute 'mod=</xsl:text>
+ <xsl:value-of select="concat('&quot;',../@mod,'&quot;')"/>
+ <xsl:text>' cannot be used with type </xsl:text>
+ <xsl:value-of select="concat('&quot;',current(),'&quot;!')"/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="../@mod='string'">
+ <xsl:choose>
+ <!-- standard types -->
+ <!--xsl:when test=".='result'">??</xsl:when-->
+ <xsl:when test=".='uuid'">PRUnichar *</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:text>attribute 'mod=</xsl:text>
+ <xsl:value-of select="concat('&quot;',../@mod,'&quot;')"/>
+ <xsl:text>' cannot be used with type </xsl:text>
+ <xsl:value-of select="concat('&quot;',current(),'&quot;!')"/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+ <!-- no modifiers -->
+ <xsl:otherwise>
+ <xsl:choose>
+ <!-- standard types -->
+ <xsl:when test=".='result'">nsresult</xsl:when>
+ <xsl:when test=".='boolean'">PRBool</xsl:when>
+ <xsl:when test=".='octet'">PRUint8</xsl:when>
+ <xsl:when test=".='short'">PRInt16</xsl:when>
+ <xsl:when test=".='unsigned short'">PRUint16</xsl:when>
+ <xsl:when test=".='long'">PRInt32</xsl:when>
+ <xsl:when test=".='long long'">PRInt64</xsl:when>
+ <xsl:when test=".='unsigned long'">PRUint32</xsl:when>
+ <xsl:when test=".='unsigned long long'">PRUint64</xsl:when>
+ <xsl:when test=".='char'">char</xsl:when>
+ <xsl:when test=".='wchar'">PRUnichar</xsl:when>
+ <!-- string types -->
+ <xsl:when test=".='string'">char *</xsl:when>
+ <xsl:when test=".='wstring'">PRUnichar *</xsl:when>
+ <!-- UUID type -->
+ <xsl:when test=".='uuid'">
+ <xsl:choose>
+ <xsl:when test="name(..)='attribute'">
+ <xsl:choose>
+ <xsl:when test="../@readonly='yes'">
+ <xsl:text>nsID *</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="name(..)='param'">
+ <xsl:choose>
+ <xsl:when test="../@dir='in' and not(../@safearray='yes')">
+ <xsl:text>const nsID *</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>nsID *</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+ <!-- system interface types -->
+ <xsl:when test=".='$unknown'">nsISupports *</xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <!-- enum types -->
+ <xsl:when test="
+ (ancestor::library/application/enum[@name=current()]) or
+ (ancestor::library/application/if[@target=$self_target]/enum[@name=current()])
+ ">
+ <xsl:text>PRUint32</xsl:text>
+ </xsl:when>
+ <!-- custom interface types -->
+ <xsl:when test="
+ (ancestor::library/application/interface[@name=current()]) or
+ (ancestor::library/application/if[@target=$self_target]/interface[@name=current()])
+ ">
+ <xsl:value-of select="."/>
+ <xsl:text> *</xsl:text>
+ </xsl:when>
+ <!-- other types -->
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+
+<xsl:template match="application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']//module/class" />
+
+<xsl:template match="application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']/if//interface
+| application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']//interface" />
+
+<xsl:template match="application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']//interface" mode="forward" />
+
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/cbinding/makefile.tstCAPIGlue b/src/VBox/Main/cbinding/makefile.tstCAPIGlue
new file mode 100644
index 00000000..57049b98
--- /dev/null
+++ b/src/VBox/Main/cbinding/makefile.tstCAPIGlue
@@ -0,0 +1,63 @@
+# $Id: makefile.tstCAPIGlue $
+## @file makefile.tstCAPIGlue
+# Makefile for sample program illustrating use of C binding for COM/XPCOM.
+#
+
+#
+# Copyright (C) 2009-2022 Oracle and/or its affiliates.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+
+PATH_SDK = ../../..
+CAPI_INC = -I$(PATH_SDK)/bindings/c/include
+ifdef ProgramFiles
+PLATFORM_INC = -I$(PATH_SDK)/bindings/mscom/include
+PLATFORM_LIB = $(PATH_SDK)/bindings/mscom/lib
+else
+PLATFORM_INC = -I$(PATH_SDK)/bindings/xpcom/include
+PLATFORM_LIB = $(PATH_SDK)/bindings/xpcom/lib
+endif
+GLUE_DIR = $(PATH_SDK)/bindings/c/glue
+GLUE_INC = -I$(GLUE_DIR)
+
+CC = gcc
+CFLAGS = -g -Wall
+
+.PHONY: all
+all: tstCAPIGlue
+
+.PHONY: clean
+clean:
+ rm -f tstCAPIGlue.o VBoxCAPIGlue.o VirtualBox_i.o tstCAPIGlue
+
+tstCAPIGlue: tstCAPIGlue.o VBoxCAPIGlue.o VirtualBox_i.o
+ $(CC) -o $@ $^ -ldl -lpthread
+
+tstCAPIGlue.o: tstCAPIGlue.c
+ $(CC) $(CFLAGS) $(CAPI_INC) $(PLATFORM_INC) $(GLUE_INC) -o $@ -c $<
+
+VBoxCAPIGlue.o: $(GLUE_DIR)/VBoxCAPIGlue.c
+ $(CC) $(CFLAGS) $(CAPI_INC) $(PLATFORM_INC) $(GLUE_INC) -o $@ -c $<
+
+VirtualBox_i.o: $(PLATFORM_LIB)/VirtualBox_i.c
+ $(CC) $(CFLAGS) $(CAPI_INC) $(PLATFORM_INC) $(GLUE_INC) -o $@ -c $<
diff --git a/src/VBox/Main/cbinding/tstCAPIGlue.c b/src/VBox/Main/cbinding/tstCAPIGlue.c
new file mode 100644
index 00000000..eb764d58
--- /dev/null
+++ b/src/VBox/Main/cbinding/tstCAPIGlue.c
@@ -0,0 +1,1145 @@
+/* $Id: tstCAPIGlue.c $ */
+/** @file tstCAPIGlue.c
+ * Demonstrator program to illustrate use of C bindings of Main API.
+ *
+ * It has sample code showing how to retrieve all available error information,
+ * and how to handle active (event delivery through callbacks) or passive
+ * (event delivery through a polling mechanism) event listeners.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/** @todo
+ * Our appologies for the 256+ missing return code checks in this sample file.
+ *
+ * We strongly recomment users of the VBoxCAPI to check all return codes!
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxCAPIGlue.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#ifndef WIN32
+# include <signal.h>
+# include <unistd.h>
+# include <sys/poll.h>
+#endif
+#ifdef IPRT_INCLUDED_cdefs_h
+# error "not supposed to involve any IPRT or VBox headers here."
+#endif
+
+/**
+ * Select between active event listener (defined) and passive event listener
+ * (undefined). The active event listener case needs much more code, and
+ * additionally requires a lot more platform dependent code.
+ */
+#undef USE_ACTIVE_EVENT_LISTENER
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Set by Ctrl+C handler. */
+static volatile int g_fStop = 0;
+
+#ifdef USE_ACTIVE_EVENT_LISTENER
+# ifdef WIN32
+/** The COM type information for IEventListener, for implementing IDispatch. */
+static ITypeInfo *g_pTInfoIEventListener = NULL;
+# endif /* WIN32 */
+#endif /* USE_ACTIVE_EVENT_LISTENER */
+
+static const char *GetStateName(MachineState_T machineState)
+{
+ switch (machineState)
+ {
+ case MachineState_Null: return "<null>";
+ case MachineState_PoweredOff: return "PoweredOff";
+ case MachineState_Saved: return "Saved";
+ case MachineState_Teleported: return "Teleported";
+ case MachineState_Aborted: return "Aborted";
+ case MachineState_AbortedSaved: return "Aborted-Saved";
+ case MachineState_Running: return "Running";
+ case MachineState_Paused: return "Paused";
+ case MachineState_Stuck: return "Stuck";
+ case MachineState_Teleporting: return "Teleporting";
+ case MachineState_LiveSnapshotting: return "LiveSnapshotting";
+ case MachineState_Starting: return "Starting";
+ case MachineState_Stopping: return "Stopping";
+ case MachineState_Saving: return "Saving";
+ case MachineState_Restoring: return "Restoring";
+ case MachineState_TeleportingPausedVM: return "TeleportingPausedVM";
+ case MachineState_TeleportingIn: return "TeleportingIn";
+ case MachineState_DeletingSnapshotOnline: return "DeletingSnapshotOnline";
+ case MachineState_DeletingSnapshotPaused: return "DeletingSnapshotPaused";
+ case MachineState_RestoringSnapshot: return "RestoringSnapshot";
+ case MachineState_DeletingSnapshot: return "DeletingSnapshot";
+ case MachineState_SettingUp: return "SettingUp";
+ default: return "no idea";
+ }
+}
+
+/**
+ * Ctrl+C handler, terminate event listener.
+ *
+ * Remember that most function calls are not allowed in this context (including
+ * printf!), so make sure that this does as little as possible.
+ *
+ * @param iInfo Platform dependent detail info (ignored).
+ */
+#ifdef WIN32
+static BOOL VBOX_WINAPI ctrlCHandler(DWORD iInfo)
+{
+ (void)iInfo;
+ g_fStop = 1;
+ return TRUE;
+}
+#else
+static void ctrlCHandler(int iInfo)
+{
+ (void)iInfo;
+ g_fStop = 1;
+}
+#endif
+
+/**
+ * Sample event processing function, dumping some event information.
+ * Shared between active and passive event demo, to highlight that this part
+ * is identical between the two.
+ */
+static HRESULT EventListenerDemoProcessEvent(IEvent *event)
+{
+ VBoxEventType_T evType;
+ HRESULT rc;
+
+ if (!event)
+ {
+ printf("event null\n");
+ return S_OK;
+ }
+
+ evType = VBoxEventType_Invalid;
+ rc = IEvent_get_Type(event, &evType);
+ if (FAILED(rc))
+ {
+ printf("cannot get event type, rc=%#x\n", (unsigned)rc);
+ return S_OK;
+ }
+
+ switch (evType)
+ {
+ case VBoxEventType_OnMousePointerShapeChanged:
+ printf("OnMousePointerShapeChanged\n");
+ break;
+
+ case VBoxEventType_OnMouseCapabilityChanged:
+ printf("OnMouseCapabilityChanged\n");
+ break;
+
+ case VBoxEventType_OnKeyboardLedsChanged:
+ printf("OnMouseCapabilityChanged\n");
+ break;
+
+ case VBoxEventType_OnStateChanged:
+ {
+ IStateChangedEvent *ev = NULL;
+ enum MachineState state;
+ rc = IEvent_QueryInterface(event, &IID_IStateChangedEvent, (void **)&ev);
+ if (FAILED(rc))
+ {
+ printf("cannot get StateChangedEvent interface, rc=%#x\n", (unsigned)rc);
+ return S_OK;
+ }
+ if (!ev)
+ {
+ printf("StateChangedEvent reference null\n");
+ return S_OK;
+ }
+ rc = IStateChangedEvent_get_State(ev, &state);
+ if (FAILED(rc))
+ printf("warning: cannot get state, rc=%#x\n", (unsigned)rc);
+ IStateChangedEvent_Release(ev);
+ printf("OnStateChanged: %s\n", GetStateName(state));
+
+ fflush(stdout);
+ if ( state == MachineState_PoweredOff
+ || state == MachineState_Saved
+ || state == MachineState_Teleported
+ || state == MachineState_Aborted
+ || state == MachineState_AbortedSaved
+ )
+ g_fStop = 1;
+ break;
+ }
+
+ case VBoxEventType_OnAdditionsStateChanged:
+ printf("OnAdditionsStateChanged\n");
+ break;
+
+ case VBoxEventType_OnNetworkAdapterChanged:
+ printf("OnNetworkAdapterChanged\n");
+ break;
+
+ case VBoxEventType_OnSerialPortChanged:
+ printf("OnSerialPortChanged\n");
+ break;
+
+ case VBoxEventType_OnParallelPortChanged:
+ printf("OnParallelPortChanged\n");
+ break;
+
+ case VBoxEventType_OnStorageControllerChanged:
+ printf("OnStorageControllerChanged\n");
+ break;
+
+ case VBoxEventType_OnMediumChanged:
+ printf("OnMediumChanged\n");
+ break;
+
+ case VBoxEventType_OnVRDEServerChanged:
+ printf("OnVRDEServerChanged\n");
+ break;
+
+ case VBoxEventType_OnUSBControllerChanged:
+ printf("OnUSBControllerChanged\n");
+ break;
+
+ case VBoxEventType_OnUSBDeviceStateChanged:
+ printf("OnUSBDeviceStateChanged\n");
+ break;
+
+ case VBoxEventType_OnSharedFolderChanged:
+ printf("OnSharedFolderChanged\n");
+ break;
+
+ case VBoxEventType_OnRuntimeError:
+ printf("OnRuntimeError\n");
+ break;
+
+ case VBoxEventType_OnCanShowWindow:
+ printf("OnCanShowWindow\n");
+ break;
+ case VBoxEventType_OnShowWindow:
+ printf("OnShowWindow\n");
+ break;
+
+ default:
+ printf("unknown event: %d\n", evType);
+ }
+
+ return S_OK;
+}
+
+#ifdef USE_ACTIVE_EVENT_LISTENER
+
+struct IEventListenerDemo;
+typedef struct IEventListenerDemo IEventListenerDemo;
+
+typedef struct IEventListenerDemoVtbl
+{
+ HRESULT (*QueryInterface)(IEventListenerDemo *pThis, REFIID riid, void **ppvObject);
+ ULONG (*AddRef)(IEventListenerDemo *pThis);
+ ULONG (*Release)(IEventListenerDemo *pThis);
+#ifdef WIN32
+ HRESULT (*GetTypeInfoCount)(IEventListenerDemo *pThis, UINT *pctinfo);
+ HRESULT (*GetTypeInfo)(IEventListenerDemo *pThis, UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);
+ HRESULT (*GetIDsOfNames)(IEventListenerDemo *pThis, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
+ HRESULT (*Invoke)(IEventListenerDemo *pThis, DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr);
+#endif
+ HRESULT (*HandleEvent)(IEventListenerDemo *pThis, IEvent *aEvent);
+} IEventListenerDemoVtbl;
+
+typedef struct IEventListenerDemo
+{
+ struct IEventListenerDemoVtbl *lpVtbl;
+
+ int cRef;
+
+#ifdef WIN32
+ /* Active event delivery needs a free threaded marshaler, as the default
+ * proxy marshaling cannot deal correctly with this case. */
+ IUnknown *pUnkMarshaler;
+#endif
+} IEventListenerDemo;
+
+/* Defines for easily calling IEventListenerDemo functions. */
+
+/* IUnknown functions. */
+#define IEventListenerDemo_QueryInterface(This,riid,ppvObject) \
+ ( (This)->lpVtbl->QueryInterface(This,riid,ppvObject) )
+
+#define IEventListenerDemo_AddRef(This) \
+ ( (This)->lpVtbl->AddRef(This) )
+
+#define IEventListenerDemo_Release(This) \
+ ( (This)->lpVtbl->Release(This) )
+
+#ifdef WIN32
+/* IDispatch functions. */
+#define IEventListenerDemo_GetTypeInfoCount(This,pctinfo) \
+ ( (This)->lpVtbl->GetTypeInfoCount(This,pctinfo) )
+
+#define IEventListenerDemo_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \
+ ( (This)->lpVtbl->GetTypeInfo(This,iTInfo,lcid,ppTInfo) )
+
+#define IEventListenerDemo_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \
+ ( (This)->lpVtbl->GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) )
+
+#define IEventListenerDemo_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \
+ ( (This)->lpVtbl->Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) )
+#endif
+
+/* IEventListener functions. */
+#define IEventListenerDemo_HandleEvent(This,aEvent) \
+ ( (This)->lpVtbl->HandleEvent(This,aEvent) )
+
+
+/**
+ * Event handler function, for active event processing.
+ */
+static HRESULT IEventListenerDemoImpl_HandleEvent(IEventListenerDemo *pThis, IEvent *event)
+{
+ return EventListenerDemoProcessEvent(event);
+}
+
+static HRESULT IEventListenerDemoImpl_QueryInterface(IEventListenerDemo *pThis, const IID *iid, void **resultp)
+{
+ /* match iid */
+ if ( !memcmp(iid, &IID_IEventListener, sizeof(IID))
+ || !memcmp(iid, &IID_IDispatch, sizeof(IID))
+ || !memcmp(iid, &IID_IUnknown, sizeof(IID)))
+ {
+ IEventListenerDemo_AddRef(pThis);
+ *resultp = pThis;
+ return S_OK;
+ }
+#ifdef WIN32
+ if (!memcmp(iid, &IID_IMarshal, sizeof(IID)))
+ return IUnknown_QueryInterface(pThis->pUnkMarshaler, iid, resultp);
+#endif
+
+ return E_NOINTERFACE;
+}
+
+static HRESULT IEventListenerDemoImpl_AddRef(IEventListenerDemo *pThis)
+{
+ return ++(pThis->cRef);
+}
+
+static HRESULT IEventListenerDemoImpl_Release(IEventListenerDemo *pThis)
+{
+ HRESULT c;
+
+ c = --(pThis->cRef);
+ if (!c)
+ free(pThis);
+ return c;
+}
+
+#ifdef WIN32
+static HRESULT IEventListenerDemoImpl_GetTypeInfoCount(IEventListenerDemo *pThis, UINT *pctinfo)
+{
+ if (!pctinfo)
+ return E_POINTER;
+ *pctinfo = 1;
+ return S_OK;
+}
+
+static HRESULT IEventListenerDemoImpl_GetTypeInfo(IEventListenerDemo *pThis, UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
+{
+ if (!ppTInfo)
+ return E_POINTER;
+ ITypeInfo_AddRef(g_pTInfoIEventListener);
+ *ppTInfo = g_pTInfoIEventListener;
+ return S_OK;
+}
+
+static HRESULT IEventListenerDemoImpl_GetIDsOfNames(IEventListenerDemo *pThis, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
+{
+ return ITypeInfo_GetIDsOfNames(g_pTInfoIEventListener, rgszNames, cNames, rgDispId);
+}
+
+static HRESULT IEventListenerDemoImpl_Invoke(IEventListenerDemo *pThis, DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
+{
+ return ITypeInfo_Invoke(g_pTInfoIEventListener, (IDispatch *)pThis, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
+}
+
+static HRESULT LoadTypeInfo(REFIID riid, ITypeInfo **pTInfo)
+{
+ HRESULT rc;
+ ITypeLib *pTypeLib;
+ rc = LoadRegTypeLib(&LIBID_VirtualBox, 1 /* major */, 0 /* minor */, 0 /* lcid */, &pTypeLib);
+ if (FAILED(rc))
+ return rc;
+ rc = ITypeLib_GetTypeInfoOfGuid(pTypeLib, riid, pTInfo);
+
+ /* No longer need access to the type lib, release it. */
+ ITypeLib_Release(pTypeLib);
+
+ return rc;
+}
+#endif
+
+#ifdef __GNUC__
+typedef struct IEventListenerDemoVtblInt
+{
+ ptrdiff_t offset_to_top;
+ void *typeinfo;
+ IEventListenerDemoVtbl lpVtbl;
+} IEventListenerDemoVtblInt;
+
+static IEventListenerDemoVtblInt g_IEventListenerDemoVtblInt =
+{
+ 0, /* offset_to_top */
+ NULL, /* typeinfo, not vital */
+ {
+ IEventListenerDemoImpl_QueryInterface,
+ IEventListenerDemoImpl_AddRef,
+ IEventListenerDemoImpl_Release,
+#ifdef WIN32
+ IEventListenerDemoImpl_GetTypeInfoCount,
+ IEventListenerDemoImpl_GetTypeInfo,
+ IEventListenerDemoImpl_GetIDsOfNames,
+ IEventListenerDemoImpl_Invoke,
+#endif
+ IEventListenerDemoImpl_HandleEvent
+ }
+};
+#elif defined(_MSC_VER)
+typedef struct IEventListenerDemoVtblInt
+{
+ IEventListenerDemoVtbl lpVtbl;
+} IEventListenerDemoVtblInt;
+
+static IEventListenerDemoVtblInt g_IEventListenerDemoVtblInt =
+{
+ {
+ IEventListenerDemoImpl_QueryInterface,
+ IEventListenerDemoImpl_AddRef,
+ IEventListenerDemoImpl_Release,
+#ifdef WIN32
+ IEventListenerDemoImpl_GetTypeInfoCount,
+ IEventListenerDemoImpl_GetTypeInfo,
+ IEventListenerDemoImpl_GetIDsOfNames,
+ IEventListenerDemoImpl_Invoke,
+#endif
+ IEventListenerDemoImpl_HandleEvent
+ }
+};
+#else
+# error Port me!
+#endif
+
+/**
+ * Register active event listener for the selected VM.
+ *
+ * @param virtualBox ptr to IVirtualBox object
+ * @param session ptr to ISession object
+ */
+static void registerActiveEventListener(IVirtualBox *virtualBox, ISession *session)
+{
+ IConsole *console = NULL;
+ HRESULT rc;
+
+ rc = ISession_get_Console(session, &console);
+ if ((SUCCEEDED(rc)) && console)
+ {
+ IEventSource *es = NULL;
+ rc = IConsole_get_EventSource(console, &es);
+ if (SUCCEEDED(rc) && es)
+ {
+ static const ULONG s_auInterestingEvents[] =
+ {
+ VBoxEventType_OnMousePointerShapeChanged,
+ VBoxEventType_OnMouseCapabilityChanged,
+ VBoxEventType_OnKeyboardLedsChanged,
+ VBoxEventType_OnStateChanged,
+ VBoxEventType_OnAdditionsStateChanged,
+ VBoxEventType_OnNetworkAdapterChanged,
+ VBoxEventType_OnSerialPortChanged,
+ VBoxEventType_OnParallelPortChanged,
+ VBoxEventType_OnStorageControllerChanged,
+ VBoxEventType_OnMediumChanged,
+ VBoxEventType_OnVRDEServerChanged,
+ VBoxEventType_OnUSBControllerChanged,
+ VBoxEventType_OnUSBDeviceStateChanged,
+ VBoxEventType_OnSharedFolderChanged,
+ VBoxEventType_OnRuntimeError,
+ VBoxEventType_OnCanShowWindow,
+ VBoxEventType_OnShowWindow
+ };
+ SAFEARRAY *interestingEventsSA = NULL;
+ IEventListenerDemo *consoleListener = NULL;
+
+ /* The VirtualBox API expects enum values as VT_I4, which in the
+ * future can be hopefully relaxed. */
+ interestingEventsSA = g_pVBoxFuncs->pfnSafeArrayCreateVector(VT_I4, 0,
+ sizeof(s_auInterestingEvents)
+ / sizeof(s_auInterestingEvents[0]));
+ g_pVBoxFuncs->pfnSafeArrayCopyInParamHelper(interestingEventsSA, &s_auInterestingEvents,
+ sizeof(s_auInterestingEvents));
+
+ consoleListener = calloc(1, sizeof(IEventListenerDemo));
+ if (consoleListener)
+ {
+ consoleListener->lpVtbl = &(g_IEventListenerDemoVtblInt.lpVtbl);
+#ifdef WIN32
+ CoCreateFreeThreadedMarshaler((IUnknown *)consoleListener, &consoleListener->pUnkMarshaler);
+#endif
+ IEventListenerDemo_AddRef(consoleListener);
+
+ rc = IEventSource_RegisterListener(es, (IEventListener *)consoleListener,
+ ComSafeArrayAsInParam(interestingEventsSA),
+ 1 /* active */);
+ if (SUCCEEDED(rc))
+ {
+ /* Just wait here for events, no easy way to do this better
+ * as there's not much to do after this completes. */
+ printf("Entering event loop, PowerOff the machine to exit or press Ctrl-C to terminate\n");
+ fflush(stdout);
+#ifdef WIN32
+ SetConsoleCtrlHandler(ctrlCHandler, TRUE);
+#else
+ signal(SIGINT, (void (*)(int))ctrlCHandler);
+#endif
+
+ while (!g_fStop)
+ g_pVBoxFuncs->pfnProcessEventQueue(250);
+
+#ifdef WIN32
+ SetConsoleCtrlHandler(ctrlCHandler, FALSE);
+#else
+ signal(SIGINT, SIG_DFL);
+#endif
+ }
+ else
+ printf("Failed to register event listener.\n");
+ IEventSource_UnregisterListener(es, (IEventListener *)consoleListener);
+#ifdef WIN32
+ if (consoleListener->pUnkMarshaler)
+ IUnknown_Release(consoleListener->pUnkMarshaler);
+#endif
+ IEventListenerDemo_Release(consoleListener);
+ }
+ else
+ printf("Failed while allocating memory for console event listener.\n");
+ g_pVBoxFuncs->pfnSafeArrayDestroy(interestingEventsSA);
+ IEventSource_Release(es);
+ }
+ else
+ printf("Failed to get the event source instance.\n");
+ IConsole_Release(console);
+ }
+}
+
+#else /* !USE_ACTIVE_EVENT_LISTENER */
+
+/**
+ * Register passive event listener for the selected VM.
+ *
+ * @param virtualBox ptr to IVirtualBox object
+ * @param session ptr to ISession object
+ */
+static void registerPassiveEventListener(ISession *session)
+{
+ IConsole *console = NULL;
+ HRESULT rc;
+
+ rc = ISession_get_Console(session, &console);
+ if (SUCCEEDED(rc) && console)
+ {
+ IEventSource *es = NULL;
+ rc = IConsole_get_EventSource(console, &es);
+ if (SUCCEEDED(rc) && es)
+ {
+ static const ULONG s_auInterestingEvents[] =
+ {
+ VBoxEventType_OnMousePointerShapeChanged,
+ VBoxEventType_OnMouseCapabilityChanged,
+ VBoxEventType_OnKeyboardLedsChanged,
+ VBoxEventType_OnStateChanged,
+ VBoxEventType_OnAdditionsStateChanged,
+ VBoxEventType_OnNetworkAdapterChanged,
+ VBoxEventType_OnSerialPortChanged,
+ VBoxEventType_OnParallelPortChanged,
+ VBoxEventType_OnStorageControllerChanged,
+ VBoxEventType_OnMediumChanged,
+ VBoxEventType_OnVRDEServerChanged,
+ VBoxEventType_OnUSBControllerChanged,
+ VBoxEventType_OnUSBDeviceStateChanged,
+ VBoxEventType_OnSharedFolderChanged,
+ VBoxEventType_OnRuntimeError,
+ VBoxEventType_OnCanShowWindow,
+ VBoxEventType_OnShowWindow
+ };
+ SAFEARRAY *interestingEventsSA = NULL;
+ IEventListener *consoleListener = NULL;
+
+ /* The VirtualBox API expects enum values as VT_I4, which in the
+ * future can be hopefully relaxed. */
+ interestingEventsSA = g_pVBoxFuncs->pfnSafeArrayCreateVector(VT_I4, 0,
+ sizeof(s_auInterestingEvents)
+ / sizeof(s_auInterestingEvents[0]));
+ g_pVBoxFuncs->pfnSafeArrayCopyInParamHelper(interestingEventsSA, &s_auInterestingEvents,
+ sizeof(s_auInterestingEvents));
+
+ rc = IEventSource_CreateListener(es, &consoleListener);
+ if (SUCCEEDED(rc) && consoleListener)
+ {
+ rc = IEventSource_RegisterListener(es, consoleListener,
+ ComSafeArrayAsInParam(interestingEventsSA),
+ 0 /* passive */);
+ if (SUCCEEDED(rc))
+ {
+ /* Just wait here for events, no easy way to do this better
+ * as there's not much to do after this completes. */
+ printf("Entering event loop, PowerOff the machine to exit or press Ctrl-C to terminate\n");
+ fflush(stdout);
+#ifdef WIN32
+ SetConsoleCtrlHandler(ctrlCHandler, TRUE);
+#else
+ signal(SIGINT, ctrlCHandler);
+#endif
+
+ while (!g_fStop)
+ {
+ IEvent *ev = NULL;
+ rc = IEventSource_GetEvent(es, consoleListener, 250, &ev);
+ if (FAILED(rc))
+ {
+ printf("Failed getting event: %#x\n", (unsigned)rc);
+ g_fStop = 1;
+ continue;
+ }
+ /* handle timeouts, resulting in NULL events */
+ if (!ev)
+ continue;
+ rc = EventListenerDemoProcessEvent(ev);
+ if (FAILED(rc))
+ {
+ printf("Failed processing event: %#x\n", (unsigned)rc);
+ g_fStop = 1;
+ /* finish processing the event */
+ }
+ rc = IEventSource_EventProcessed(es, consoleListener, ev);
+ if (FAILED(rc))
+ {
+ printf("Failed to mark event as processed: %#x\n", (unsigned)rc);
+ g_fStop = 1;
+ /* continue with event release */
+ }
+ if (ev)
+ {
+ IEvent_Release(ev);
+ ev = NULL;
+ }
+ }
+
+#ifdef WIN32
+ SetConsoleCtrlHandler(ctrlCHandler, FALSE);
+#else
+ signal(SIGINT, SIG_DFL);
+#endif
+ }
+ else
+ printf("Failed to register event listener.\n");
+ IEventSource_UnregisterListener(es, (IEventListener *)consoleListener);
+ IEventListener_Release(consoleListener);
+ }
+ else
+ printf("Failed to create an event listener instance.\n");
+ g_pVBoxFuncs->pfnSafeArrayDestroy(interestingEventsSA);
+ IEventSource_Release(es);
+ }
+ else
+ printf("Failed to get the event source instance.\n");
+ IConsole_Release(console);
+ }
+}
+
+#endif /* !USE_ACTIVE_EVENT_LISTENER */
+
+/**
+ * Print detailed error information if available.
+ * @param pszExecutable string with the executable name
+ * @param pszErrorMsg string containing the code location specific error message
+ * @param rc COM/XPCOM result code
+ */
+static void PrintErrorInfo(const char *pszExecutable, const char *pszErrorMsg, HRESULT rc)
+{
+ IErrorInfo *ex;
+ HRESULT rc2;
+ fprintf(stderr, "%s: %s (rc=%#010x)\n", pszExecutable, pszErrorMsg, (unsigned)rc);
+ rc2 = g_pVBoxFuncs->pfnGetException(&ex);
+ if (SUCCEEDED(rc2) && ex)
+ {
+ IVirtualBoxErrorInfo *ei;
+ rc2 = IErrorInfo_QueryInterface(ex, &IID_IVirtualBoxErrorInfo, (void **)&ei);
+ if (SUCCEEDED(rc2) && ei != NULL)
+ {
+ /* got extended error info, maybe multiple infos */
+ do
+ {
+ LONG resultCode = S_OK;
+ BSTR componentUtf16 = NULL;
+ char *component = NULL;
+ BSTR textUtf16 = NULL;
+ char *text = NULL;
+ IVirtualBoxErrorInfo *ei_next = NULL;
+ fprintf(stderr, "Extended error info (IVirtualBoxErrorInfo):\n");
+
+ IVirtualBoxErrorInfo_get_ResultCode(ei, &resultCode);
+ fprintf(stderr, " resultCode=%#010x\n", (unsigned)resultCode);
+
+ IVirtualBoxErrorInfo_get_Component(ei, &componentUtf16);
+ g_pVBoxFuncs->pfnUtf16ToUtf8(componentUtf16, &component);
+ g_pVBoxFuncs->pfnComUnallocString(componentUtf16);
+ fprintf(stderr, " component=%s\n", component);
+ g_pVBoxFuncs->pfnUtf8Free(component);
+
+ IVirtualBoxErrorInfo_get_Text(ei, &textUtf16);
+ g_pVBoxFuncs->pfnUtf16ToUtf8(textUtf16, &text);
+ g_pVBoxFuncs->pfnComUnallocString(textUtf16);
+ fprintf(stderr, " text=%s\n", text);
+ g_pVBoxFuncs->pfnUtf8Free(text);
+
+ rc2 = IVirtualBoxErrorInfo_get_Next(ei, &ei_next);
+ if (FAILED(rc2))
+ ei_next = NULL;
+ IVirtualBoxErrorInfo_Release(ei);
+ ei = ei_next;
+ } while (ei);
+ }
+
+ IErrorInfo_Release(ex);
+ g_pVBoxFuncs->pfnClearException();
+ }
+}
+
+/**
+ * Start a VM.
+ *
+ * @param argv0 executable name
+ * @param virtualBox ptr to IVirtualBox object
+ * @param session ptr to ISession object
+ * @param id identifies the machine to start
+ */
+static void startVM(const char *argv0, IVirtualBox *virtualBox, ISession *session, BSTR id)
+{
+ HRESULT rc;
+ IMachine *machine = NULL;
+ IProgress *progress = NULL;
+ SAFEARRAY *env = NULL;
+ BSTR sessionType;
+ SAFEARRAY *groupsSA = g_pVBoxFuncs->pfnSafeArrayOutParamAlloc();
+
+ rc = IVirtualBox_FindMachine(virtualBox, id, &machine);
+ if (FAILED(rc) || !machine)
+ {
+ PrintErrorInfo(argv0, "Error: Couldn't get the Machine reference", rc);
+ return;
+ }
+
+ rc = IMachine_get_Groups(machine, ComSafeArrayAsOutTypeParam(groupsSA, BSTR));
+ if (SUCCEEDED(rc))
+ {
+ BSTR *groups = NULL;
+ ULONG cbGroups = 0;
+ ULONG i, cGroups;
+ g_pVBoxFuncs->pfnSafeArrayCopyOutParamHelper((void **)&groups, &cbGroups, VT_BSTR, groupsSA);
+ g_pVBoxFuncs->pfnSafeArrayDestroy(groupsSA);
+ cGroups = cbGroups / sizeof(groups[0]);
+ for (i = 0; i < cGroups; ++i)
+ {
+ /* Note that the use of %S might be tempting, but it is not
+ * available on all platforms, and even where it is usable it
+ * may depend on correct compiler options to make wchar_t a
+ * 16 bit number. So better play safe and use UTF-8. */
+ char *group;
+ g_pVBoxFuncs->pfnUtf16ToUtf8(groups[i], &group);
+ printf("Groups[%u]: %s\n", (unsigned)i, group);
+ g_pVBoxFuncs->pfnUtf8Free(group);
+ }
+ for (i = 0; i < cGroups; ++i)
+ g_pVBoxFuncs->pfnComUnallocString(groups[i]);
+ g_pVBoxFuncs->pfnArrayOutFree(groups);
+ }
+
+ g_pVBoxFuncs->pfnUtf8ToUtf16("gui", &sessionType);
+ rc = IMachine_LaunchVMProcess(machine, session, sessionType, ComSafeArrayAsInParam(env), &progress);
+ g_pVBoxFuncs->pfnUtf16Free(sessionType);
+ if (SUCCEEDED(rc))
+ {
+ BOOL completed;
+ LONG resultCode;
+
+ printf("Waiting for the remote session to open...\n");
+ IProgress_WaitForCompletion(progress, -1);
+
+ rc = IProgress_get_Completed(progress, &completed);
+ if (FAILED(rc))
+ fprintf(stderr, "Error: GetCompleted status failed\n");
+
+ IProgress_get_ResultCode(progress, &resultCode);
+ if (FAILED(resultCode))
+ {
+ IVirtualBoxErrorInfo *errorInfo;
+ BSTR textUtf16;
+ char *text;
+
+ IProgress_get_ErrorInfo(progress, &errorInfo);
+ IVirtualBoxErrorInfo_get_Text(errorInfo, &textUtf16);
+ g_pVBoxFuncs->pfnUtf16ToUtf8(textUtf16, &text);
+ printf("Error: %s\n", text);
+
+ g_pVBoxFuncs->pfnComUnallocString(textUtf16);
+ g_pVBoxFuncs->pfnUtf8Free(text);
+ IVirtualBoxErrorInfo_Release(errorInfo);
+ }
+ else
+ {
+ fprintf(stderr, "VM process has been successfully started\n");
+
+ /* Kick off the event listener demo part, which is quite separate.
+ * Ignore it if you need a more basic sample. */
+#ifdef USE_ACTIVE_EVENT_LISTENER
+ registerActiveEventListener(virtualBox, session);
+#else
+ registerPassiveEventListener(session);
+#endif
+ }
+ IProgress_Release(progress);
+ }
+ else
+ PrintErrorInfo(argv0, "Error: LaunchVMProcess failed", rc);
+
+ /* It's important to always release resources. */
+ IMachine_Release(machine);
+}
+
+/**
+ * List the registered VMs.
+ *
+ * @param argv0 executable name
+ * @param virtualBox ptr to IVirtualBox object
+ * @param session ptr to ISession object
+ */
+static void listVMs(const char *argv0, IVirtualBox *virtualBox, ISession *session)
+{
+ HRESULT rc;
+ SAFEARRAY *machinesSA = g_pVBoxFuncs->pfnSafeArrayOutParamAlloc();
+ IMachine **machines = NULL;
+ ULONG machineCnt = 0;
+ ULONG i;
+ unsigned start_id;
+
+ /*
+ * Get the list of all registered VMs.
+ */
+ rc = IVirtualBox_get_Machines(virtualBox, ComSafeArrayAsOutIfaceParam(machinesSA, IMachine *));
+ if (FAILED(rc))
+ {
+ PrintErrorInfo(argv0, "could not get list of machines", rc);
+ return;
+ }
+
+ /*
+ * Extract interface pointers from machinesSA, and update the reference
+ * counter of each object, as destroying machinesSA would call Release.
+ */
+ g_pVBoxFuncs->pfnSafeArrayCopyOutIfaceParamHelper((IUnknown ***)&machines, &machineCnt, machinesSA);
+ g_pVBoxFuncs->pfnSafeArrayDestroy(machinesSA);
+
+ if (!machineCnt)
+ {
+ g_pVBoxFuncs->pfnArrayOutFree(machines);
+ printf("\tNo VMs\n");
+ return;
+ }
+
+ printf("VM List:\n\n");
+
+ /*
+ * Iterate through the collection.
+ */
+ for (i = 0; i < machineCnt; ++i)
+ {
+ IMachine *machine = machines[i];
+ BOOL isAccessible = FALSE;
+
+ printf("\tMachine #%u\n", (unsigned)i);
+
+ if (!machine)
+ {
+ printf("\t(skipped, NULL)\n");
+ continue;
+ }
+
+ IMachine_get_Accessible(machine, &isAccessible);
+
+ if (isAccessible)
+ {
+ BSTR machineNameUtf16;
+ char *machineName;
+
+ IMachine_get_Name(machine, &machineNameUtf16);
+ g_pVBoxFuncs->pfnUtf16ToUtf8(machineNameUtf16,&machineName);
+ g_pVBoxFuncs->pfnComUnallocString(machineNameUtf16);
+ printf("\tName: %s\n", machineName);
+ g_pVBoxFuncs->pfnUtf8Free(machineName);
+ }
+ else
+ printf("\tName: <inaccessible>\n");
+
+ {
+ BSTR uuidUtf16;
+ char *uuidUtf8;
+
+ IMachine_get_Id(machine, &uuidUtf16);
+ g_pVBoxFuncs->pfnUtf16ToUtf8(uuidUtf16, &uuidUtf8);
+ g_pVBoxFuncs->pfnComUnallocString(uuidUtf16);
+ printf("\tUUID: %s\n", uuidUtf8);
+ g_pVBoxFuncs->pfnUtf8Free(uuidUtf8);
+ }
+
+ if (isAccessible)
+ {
+ {
+ BSTR configFileUtf16;
+ char *configFileUtf8;
+
+ IMachine_get_SettingsFilePath(machine, &configFileUtf16);
+ g_pVBoxFuncs->pfnUtf16ToUtf8(configFileUtf16, &configFileUtf8);
+ g_pVBoxFuncs->pfnComUnallocString(configFileUtf16);
+ printf("\tConfig file: %s\n", configFileUtf8);
+ g_pVBoxFuncs->pfnUtf8Free(configFileUtf8);
+ }
+
+ {
+ ULONG memorySize;
+
+ IMachine_get_MemorySize(machine, &memorySize);
+ printf("\tMemory size: %uMB\n", (unsigned)memorySize);
+ }
+
+ {
+ BSTR typeId;
+ BSTR osNameUtf16;
+ char *osName;
+ IGuestOSType *osType = NULL;
+
+ IMachine_get_OSTypeId(machine, &typeId);
+ IVirtualBox_GetGuestOSType(virtualBox, typeId, &osType);
+ g_pVBoxFuncs->pfnComUnallocString(typeId);
+ IGuestOSType_get_Description(osType, &osNameUtf16);
+ g_pVBoxFuncs->pfnUtf16ToUtf8(osNameUtf16,&osName);
+ g_pVBoxFuncs->pfnComUnallocString(osNameUtf16);
+ printf("\tGuest OS: %s\n\n", osName);
+ g_pVBoxFuncs->pfnUtf8Free(osName);
+
+ IGuestOSType_Release(osType);
+ }
+ }
+ }
+
+ /*
+ * Let the user chose a machine to start.
+ */
+ printf("Type Machine# to start (0 - %u) or 'quit' to do nothing: ",
+ (unsigned)(machineCnt - 1));
+ fflush(stdout);
+
+ if (scanf("%u", &start_id) == 1 && start_id < machineCnt)
+ {
+ IMachine *machine = machines[start_id];
+
+ if (machine)
+ {
+ BSTR uuidUtf16 = NULL;
+
+ IMachine_get_Id(machine, &uuidUtf16);
+ startVM(argv0, virtualBox, session, uuidUtf16);
+ g_pVBoxFuncs->pfnComUnallocString(uuidUtf16);
+ }
+ }
+
+ /*
+ * Don't forget to release the objects in the array.
+ */
+ for (i = 0; i < machineCnt; ++i)
+ {
+ IMachine *machine = machines[i];
+
+ if (machine)
+ IMachine_Release(machine);
+ }
+ g_pVBoxFuncs->pfnArrayOutFree(machines);
+}
+
+/* Main - Start the ball rolling. */
+
+int main(int argc, char **argv)
+{
+ IVirtualBoxClient *vboxclient = NULL;
+ IVirtualBox *vbox = NULL;
+ ISession *session = NULL;
+ ULONG revision = 0;
+ BSTR versionUtf16 = NULL;
+ BSTR homefolderUtf16 = NULL;
+ HRESULT rc; /* Result code of various function (method) calls. */
+ (void)argc;
+
+ printf("Starting main()\n");
+
+ if (VBoxCGlueInit())
+ {
+ fprintf(stderr, "%s: FATAL: VBoxCGlueInit failed: %s\n",
+ argv[0], g_szVBoxErrMsg);
+ return EXIT_FAILURE;
+ }
+
+ {
+ unsigned ver = g_pVBoxFuncs->pfnGetVersion();
+ printf("VirtualBox version: %u.%u.%u\n", ver / 1000000, ver / 1000 % 1000, ver % 1000);
+ ver = g_pVBoxFuncs->pfnGetAPIVersion();
+ printf("VirtualBox API version: %u.%u\n", ver / 1000, ver % 1000);
+ }
+
+ g_pVBoxFuncs->pfnClientInitialize(NULL, &vboxclient);
+ if (!vboxclient)
+ {
+ fprintf(stderr, "%s: FATAL: could not get VirtualBoxClient reference\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ printf("----------------------------------------------------\n");
+
+ rc = IVirtualBoxClient_get_VirtualBox(vboxclient, &vbox);
+ if (FAILED(rc) || !vbox)
+ {
+ PrintErrorInfo(argv[0], "FATAL: could not get VirtualBox reference", rc);
+ return EXIT_FAILURE;
+ }
+ rc = IVirtualBoxClient_get_Session(vboxclient, &session);
+ if (FAILED(rc) || !session)
+ {
+ PrintErrorInfo(argv[0], "FATAL: could not get Session reference", rc);
+ return EXIT_FAILURE;
+ }
+
+#ifdef USE_ACTIVE_EVENT_LISTENER
+# ifdef WIN32
+ rc = LoadTypeInfo(&IID_IEventListener, &g_pTInfoIEventListener);
+ if (FAILED(rc) || !g_pTInfoIEventListener)
+ {
+ PrintErrorInfo(argv[0], "FATAL: could not get type information for IEventListener", rc);
+ return EXIT_FAILURE;
+ }
+# endif /* WIN32 */
+#endif /* USE_ACTIVE_EVENT_LISTENER */
+
+ /*
+ * Now ask for revision, version and home folder information of
+ * this vbox. Were not using fancy macros here so it
+ * remains easy to see how we access C++'s vtable.
+ */
+
+ /* 1. Revision */
+ rc = IVirtualBox_get_Revision(vbox, &revision);
+ if (SUCCEEDED(rc))
+ printf("\tRevision: %u\n", (unsigned)revision);
+ else
+ PrintErrorInfo(argv[0], "GetRevision() failed", rc);
+
+ /* 2. Version */
+ rc = IVirtualBox_get_Version(vbox, &versionUtf16);
+ if (SUCCEEDED(rc))
+ {
+ char *version = NULL;
+ g_pVBoxFuncs->pfnUtf16ToUtf8(versionUtf16, &version);
+ printf("\tVersion: %s\n", version);
+ g_pVBoxFuncs->pfnUtf8Free(version);
+ g_pVBoxFuncs->pfnComUnallocString(versionUtf16);
+ }
+ else
+ PrintErrorInfo(argv[0], "GetVersion() failed", rc);
+
+ /* 3. Home Folder */
+ rc = IVirtualBox_get_HomeFolder(vbox, &homefolderUtf16);
+ if (SUCCEEDED(rc))
+ {
+ char *homefolder = NULL;
+ g_pVBoxFuncs->pfnUtf16ToUtf8(homefolderUtf16, &homefolder);
+ printf("\tHomeFolder: %s\n", homefolder);
+ g_pVBoxFuncs->pfnUtf8Free(homefolder);
+ g_pVBoxFuncs->pfnComUnallocString(homefolderUtf16);
+ }
+ else
+ PrintErrorInfo(argv[0], "GetHomeFolder() failed", rc);
+
+ listVMs(argv[0], vbox, session);
+ ISession_UnlockMachine(session);
+
+ printf("----------------------------------------------------\n");
+
+ /*
+ * Do as mom told us: always clean up after yourself.
+ */
+#ifdef USE_ACTIVE_EVENT_LISTENER
+# ifdef WIN32
+ if (g_pTInfoIEventListener)
+ {
+ ITypeInfo_Release(g_pTInfoIEventListener);
+ g_pTInfoIEventListener = NULL;
+ }
+# endif /* WIN32 */
+#endif /* USE_ACTIVE_EVENT_LISTENER */
+
+ if (session)
+ {
+ ISession_Release(session);
+ session = NULL;
+ }
+ if (vbox)
+ {
+ IVirtualBox_Release(vbox);
+ vbox = NULL;
+ }
+ if (vboxclient)
+ {
+ IVirtualBoxClient_Release(vboxclient);
+ vboxclient = NULL;
+ }
+
+ g_pVBoxFuncs->pfnClientUninitialize();
+ VBoxCGlueTerm();
+ printf("Finished main()\n");
+
+ return 0;
+}
+/* vim: set ts=4 sw=4 et: */
diff --git a/src/VBox/Main/glue/AutoLock.cpp b/src/VBox/Main/glue/AutoLock.cpp
new file mode 100644
index 00000000..4f7858ee
--- /dev/null
+++ b/src/VBox/Main/glue/AutoLock.cpp
@@ -0,0 +1,795 @@
+/* $Id: AutoLock.cpp $ */
+/** @file
+ * Automatic locks, implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define GLUE_USE_CRITSECTRW
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/cdefs.h>
+#include <iprt/critsect.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+
+#include <iprt/errcore.h>
+#include <iprt/assert.h>
+
+#if defined(RT_LOCK_STRICT)
+# include <iprt/asm.h> // for ASMReturnAddress
+#endif
+
+#include <iprt/string.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+
+#include "VBox/com/AutoLock.h"
+#include <VBox/com/string.h>
+
+#include <vector>
+#include <list>
+#include <map>
+
+
+namespace util
+{
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// RuntimeLockClass
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+typedef std::map<VBoxLockingClass, RTLOCKVALCLASS> LockValidationClassesMap;
+LockValidationClassesMap g_mapLockValidationClasses;
+#endif
+
+/**
+ * Called from initterm.cpp on process initialization (on the main thread)
+ * to give us a chance to initialize lock validation runtime data.
+ */
+void InitAutoLockSystem()
+{
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ struct
+ {
+ VBoxLockingClass cls;
+ const char *pcszDescription;
+ } aClasses[] =
+ {
+ { LOCKCLASS_VIRTUALBOXOBJECT, "2-VIRTUALBOXOBJECT" },
+ { LOCKCLASS_HOSTOBJECT, "3-HOSTOBJECT" },
+ { LOCKCLASS_LISTOFMACHINES, "4-LISTOFMACHINES" },
+ { LOCKCLASS_MACHINEOBJECT, "5-MACHINEOBJECT" },
+ { LOCKCLASS_SNAPSHOTOBJECT, "6-SNAPSHOTOBJECT" },
+ { LOCKCLASS_MEDIUMQUERY, "7-MEDIUMQUERY" },
+ { LOCKCLASS_LISTOFMEDIA, "8-LISTOFMEDIA" },
+ { LOCKCLASS_LISTOFOTHEROBJECTS, "9-LISTOFOTHEROBJECTS" },
+ { LOCKCLASS_OTHEROBJECT, "10-OTHEROBJECT" },
+ { LOCKCLASS_PROGRESSLIST, "11-PROGRESSLIST" },
+ { LOCKCLASS_OBJECTSTATE, "12-OBJECTSTATE" },
+ { LOCKCLASS_TRANSLATOR, "13-TRANSLATOR" }
+ };
+
+ RTLOCKVALCLASS hClass;
+ int vrc;
+ for (unsigned i = 0; i < RT_ELEMENTS(aClasses); ++i)
+ {
+ vrc = RTLockValidatorClassCreate(&hClass,
+ true, /*fAutodidact*/
+ RT_SRC_POS,
+ aClasses[i].pcszDescription);
+ AssertRC(vrc);
+
+ // teach the new class that the classes created previously can be held
+ // while the new class is being acquired
+ for (LockValidationClassesMap::iterator it = g_mapLockValidationClasses.begin();
+ it != g_mapLockValidationClasses.end();
+ ++it)
+ {
+ RTLOCKVALCLASS &canBeHeld = it->second;
+ vrc = RTLockValidatorClassAddPriorClass(hClass,
+ canBeHeld);
+ AssertRC(vrc);
+ }
+
+ // and store the new class
+ g_mapLockValidationClasses[aClasses[i].cls] = hClass;
+ }
+
+/* WriteLockHandle critsect1(LOCKCLASS_VIRTUALBOXOBJECT);
+ WriteLockHandle critsect2(LOCKCLASS_VIRTUALBOXLIST);
+
+ AutoWriteLock lock1(critsect1 COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock lock2(critsect2 COMMA_LOCKVAL_SRC_POS);*/
+#endif
+}
+
+bool AutoLockHoldsLocksInClass(VBoxLockingClass lockClass)
+{
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ return RTLockValidatorHoldsLocksInClass(NIL_RTTHREAD, g_mapLockValidationClasses[lockClass]);
+#else
+ RT_NOREF(lockClass);
+ return false;
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// RWLockHandle
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct RWLockHandle::Data
+{
+ Data()
+ { }
+
+#ifdef GLUE_USE_CRITSECTRW
+ mutable RTCRITSECTRW CritSect;
+#else
+ RTSEMRW sem;
+#endif
+ VBoxLockingClass lockClass;
+
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ com::Utf8Str strDescription;
+#endif
+};
+
+RWLockHandle::RWLockHandle(VBoxLockingClass lockClass)
+{
+ m = new Data();
+
+ m->lockClass = lockClass;
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ m->strDescription = com::Utf8StrFmt("r/w %RCv", this);
+#endif
+
+#ifdef GLUE_USE_CRITSECTRW
+# ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ int vrc = RTCritSectRwInitEx(&m->CritSect, 0 /*fFlags*/, g_mapLockValidationClasses[lockClass], RTLOCKVAL_SUB_CLASS_ANY, NULL);
+# else
+ int vrc = RTCritSectRwInitEx(&m->CritSect, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_ANY, NULL);
+# endif
+#else
+# ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ int vrc = RTSemRWCreateEx(&m->sem, 0 /*fFlags*/, g_mapLockValidationClasses[lockClass], RTLOCKVAL_SUB_CLASS_ANY, NULL);
+# else
+ int vrc = RTSemRWCreateEx(&m->sem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_ANY, NULL);
+# endif
+#endif
+ AssertRC(vrc);
+}
+
+/*virtual*/ RWLockHandle::~RWLockHandle()
+{
+#ifdef GLUE_USE_CRITSECTRW
+ RTCritSectRwDelete(&m->CritSect);
+#else
+ RTSemRWDestroy(m->sem);
+#endif
+ delete m;
+}
+
+/*virtual*/ bool RWLockHandle::isWriteLockOnCurrentThread() const
+{
+#ifdef GLUE_USE_CRITSECTRW
+ return RTCritSectRwIsWriteOwner(&m->CritSect);
+#else
+ return RTSemRWIsWriteOwner(m->sem);
+#endif
+}
+
+/*virtual*/ void RWLockHandle::lockWrite(LOCKVAL_SRC_POS_DECL)
+{
+#ifdef GLUE_USE_CRITSECTRW
+# ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ int vrc = RTCritSectRwEnterExclDebug(&m->CritSect, (uintptr_t)ASMReturnAddress(), RT_SRC_POS_ARGS);
+# else
+ int vrc = RTCritSectRwEnterExcl(&m->CritSect);
+# endif
+#else
+# ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ int vrc = RTSemRWRequestWriteDebug(m->sem, RT_INDEFINITE_WAIT, (uintptr_t)ASMReturnAddress(), RT_SRC_POS_ARGS);
+# else
+ int vrc = RTSemRWRequestWrite(m->sem, RT_INDEFINITE_WAIT);
+# endif
+#endif
+ AssertRC(vrc);
+}
+
+/*virtual*/ void RWLockHandle::unlockWrite()
+{
+#ifdef GLUE_USE_CRITSECTRW
+ int vrc = RTCritSectRwLeaveExcl(&m->CritSect);
+#else
+ int vrc = RTSemRWReleaseWrite(m->sem);
+#endif
+ AssertRC(vrc);
+
+}
+
+/*virtual*/ bool RWLockHandle::isReadLockedOnCurrentThread(bool fWannaHear) const
+{
+#ifdef GLUE_USE_CRITSECTRW
+ return RTCritSectRwIsReadOwner(&m->CritSect, fWannaHear);
+#else
+ return RTSemRWIsReadOwner(m->sem, fWannaHear);
+#endif
+}
+
+/*virtual*/ void RWLockHandle::lockRead(LOCKVAL_SRC_POS_DECL)
+{
+#ifdef GLUE_USE_CRITSECTRW
+# ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ int vrc = RTCritSectRwEnterSharedDebug(&m->CritSect, (uintptr_t)ASMReturnAddress(), RT_SRC_POS_ARGS);
+# else
+ int vrc = RTCritSectRwEnterShared(&m->CritSect);
+# endif
+#else
+# ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ int vrc = RTSemRWRequestReadDebug(m->sem, RT_INDEFINITE_WAIT, (uintptr_t)ASMReturnAddress(), RT_SRC_POS_ARGS);
+# else
+ int vrc = RTSemRWRequestRead(m->sem, RT_INDEFINITE_WAIT);
+# endif
+#endif
+ AssertRC(vrc);
+}
+
+/*virtual*/ void RWLockHandle::unlockRead()
+{
+#ifdef GLUE_USE_CRITSECTRW
+ int vrc = RTCritSectRwLeaveShared(&m->CritSect);
+#else
+ int vrc = RTSemRWReleaseRead(m->sem);
+#endif
+ AssertRC(vrc);
+}
+
+/*virtual*/ uint32_t RWLockHandle::writeLockLevel() const
+{
+ /* Note! This does not include read recursions done by the writer! */
+#ifdef GLUE_USE_CRITSECTRW
+ return RTCritSectRwGetWriteRecursion(&m->CritSect);
+#else
+ return RTSemRWGetWriteRecursion(m->sem);
+#endif
+}
+
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+/*virtual*/ const char* RWLockHandle::describe() const
+{
+ return m->strDescription.c_str();
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// WriteLockHandle
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct WriteLockHandle::Data
+{
+ Data()
+ { }
+
+ mutable RTCRITSECT sem;
+ VBoxLockingClass lockClass;
+
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ com::Utf8Str strDescription;
+#endif
+};
+
+WriteLockHandle::WriteLockHandle(VBoxLockingClass lockClass)
+{
+ m = new Data;
+
+ m->lockClass = lockClass;
+
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ m->strDescription = com::Utf8StrFmt("crit %RCv", this);
+ int vrc = RTCritSectInitEx(&m->sem, 0/*fFlags*/, g_mapLockValidationClasses[lockClass], RTLOCKVAL_SUB_CLASS_ANY, NULL);
+#else
+ int vrc = RTCritSectInitEx(&m->sem, 0/*fFlags*/, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_ANY, NULL);
+#endif
+ AssertRC(vrc);
+}
+
+WriteLockHandle::~WriteLockHandle()
+{
+ RTCritSectDelete(&m->sem);
+ delete m;
+}
+
+/*virtual*/ bool WriteLockHandle::isWriteLockOnCurrentThread() const
+{
+ return RTCritSectIsOwner(&m->sem);
+}
+
+/*virtual*/ void WriteLockHandle::lockWrite(LOCKVAL_SRC_POS_DECL)
+{
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ RTCritSectEnterDebug(&m->sem, (uintptr_t)ASMReturnAddress(), RT_SRC_POS_ARGS);
+#else
+ RTCritSectEnter(&m->sem);
+#endif
+}
+
+/*virtual*/ bool WriteLockHandle::isReadLockedOnCurrentThread(bool fWannaHear) const
+{
+ RT_NOREF(fWannaHear);
+ return RTCritSectIsOwner(&m->sem);
+}
+
+/*virtual*/ void WriteLockHandle::unlockWrite()
+{
+ RTCritSectLeave(&m->sem);
+}
+
+/*virtual*/ void WriteLockHandle::lockRead(LOCKVAL_SRC_POS_DECL)
+{
+ lockWrite(LOCKVAL_SRC_POS_ARGS);
+}
+
+/*virtual*/ void WriteLockHandle::unlockRead()
+{
+ unlockWrite();
+}
+
+/*virtual*/ uint32_t WriteLockHandle::writeLockLevel() const
+{
+ return RTCritSectGetRecursion(&m->sem);
+}
+
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+/*virtual*/ const char* WriteLockHandle::describe() const
+{
+ return m->strDescription.c_str();
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AutoLockBase
+//
+////////////////////////////////////////////////////////////////////////////////
+
+typedef std::vector<LockHandle*> HandlesVector;
+
+struct AutoLockBase::Data
+{
+ Data(size_t cHandles
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ , const char *pcszFile_,
+ unsigned uLine_,
+ const char *pcszFunction_
+#endif
+ )
+ : fIsLocked(false),
+ aHandles(cHandles) // size of array
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ , pcszFile(pcszFile_),
+ uLine(uLine_),
+ pcszFunction(pcszFunction_)
+#endif
+ {
+ for (uint32_t i = 0; i < cHandles; ++i)
+ aHandles[i] = NULL;
+ }
+
+ bool fIsLocked; // if true, then all items in aHandles are locked by this AutoLock and
+ // need to be unlocked in the destructor
+ HandlesVector aHandles; // array (vector) of LockHandle instances; in the case of AutoWriteLock
+ // and AutoReadLock, there will only be one item on the list; with the
+ // AutoMulti* derivatives, there will be multiple
+
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ // information about where the lock occurred (passed down from the AutoLock classes)
+ const char *pcszFile;
+ unsigned uLine;
+ const char *pcszFunction;
+#endif
+};
+
+AutoLockBase::AutoLockBase(uint32_t cHandles
+ COMMA_LOCKVAL_SRC_POS_DECL)
+{
+ m = new Data(cHandles COMMA_LOCKVAL_SRC_POS_ARGS);
+}
+
+AutoLockBase::AutoLockBase(uint32_t cHandles,
+ LockHandle *pHandle
+ COMMA_LOCKVAL_SRC_POS_DECL)
+{
+ Assert(cHandles == 1); NOREF(cHandles);
+ m = new Data(1 COMMA_LOCKVAL_SRC_POS_ARGS);
+ m->aHandles[0] = pHandle;
+}
+
+AutoLockBase::~AutoLockBase()
+{
+ delete m;
+}
+
+/**
+ * Requests ownership of all contained lock handles by calling
+ * the pure virtual callLockImpl() function on each of them,
+ * which must be implemented by the descendant class; in the
+ * implementation, AutoWriteLock will request a write lock
+ * whereas AutoReadLock will request a read lock.
+ *
+ * Does *not* modify the lock counts in the member variables.
+ */
+void AutoLockBase::callLockOnAllHandles()
+{
+ for (HandlesVector::iterator it = m->aHandles.begin();
+ it != m->aHandles.end();
+ ++it)
+ {
+ LockHandle *pHandle = *it;
+ if (pHandle)
+ // call virtual function implemented in AutoWriteLock or AutoReadLock
+ this->callLockImpl(*pHandle);
+ }
+}
+
+/**
+ * Releases ownership of all contained lock handles by calling
+ * the pure virtual callUnlockImpl() function on each of them,
+ * which must be implemented by the descendant class; in the
+ * implementation, AutoWriteLock will release a write lock
+ * whereas AutoReadLock will release a read lock.
+ *
+ * Does *not* modify the lock counts in the member variables.
+ */
+void AutoLockBase::callUnlockOnAllHandles()
+{
+ // unlock in reverse order!
+ for (HandlesVector::reverse_iterator it = m->aHandles.rbegin();
+ it != m->aHandles.rend();
+ ++it)
+ {
+ LockHandle *pHandle = *it;
+ if (pHandle)
+ // call virtual function implemented in AutoWriteLock or AutoReadLock
+ this->callUnlockImpl(*pHandle);
+ }
+}
+
+/**
+ * Destructor implementation that can also be called explicitly, if required.
+ * Restores the exact state before the AutoLock was created; that is, unlocks
+ * all contained semaphores.
+ */
+void AutoLockBase::cleanup()
+{
+ if (m->fIsLocked)
+ callUnlockOnAllHandles();
+}
+
+/**
+ * Requests ownership of all contained semaphores. Public method that can
+ * only be called once and that also gets called by the AutoLock constructors.
+ */
+void AutoLockBase::acquire()
+{
+ AssertMsgReturnVoid(!m->fIsLocked, ("m->fIsLocked is true, attempting to lock twice!"));
+ callLockOnAllHandles();
+ m->fIsLocked = true;
+}
+
+/**
+ * Releases ownership of all contained semaphores. Public method.
+ */
+void AutoLockBase::release()
+{
+ AssertMsgReturnVoid(m->fIsLocked, ("m->fIsLocked is false, cannot release!"));
+ callUnlockOnAllHandles();
+ m->fIsLocked = false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AutoReadLock
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Release all read locks acquired by this instance through the #lock()
+ * call and destroys the instance.
+ *
+ * Note that if there there are nested #lock() calls without the
+ * corresponding number of #unlock() calls when the destructor is called, it
+ * will assert. This is because having an unbalanced number of nested locks
+ * is a program logic error which must be fixed.
+ */
+/*virtual*/ AutoReadLock::~AutoReadLock()
+{
+ LockHandle *pHandle = m->aHandles[0];
+
+ if (pHandle)
+ {
+ if (m->fIsLocked)
+ callUnlockImpl(*pHandle);
+ }
+}
+
+/**
+ * Implementation of the pure virtual declared in AutoLockBase.
+ * This gets called by AutoLockBase.acquire() to actually request
+ * the semaphore; in the AutoReadLock implementation, we request
+ * the semaphore in read mode.
+ */
+/*virtual*/ void AutoReadLock::callLockImpl(LockHandle &l)
+{
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ l.lockRead(m->pcszFile, m->uLine, m->pcszFunction);
+#else
+ l.lockRead();
+#endif
+}
+
+/**
+ * Implementation of the pure virtual declared in AutoLockBase.
+ * This gets called by AutoLockBase.release() to actually release
+ * the semaphore; in the AutoReadLock implementation, we release
+ * the semaphore in read mode.
+ */
+/*virtual*/ void AutoReadLock::callUnlockImpl(LockHandle &l)
+{
+ l.unlockRead();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AutoWriteLockBase
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Implementation of the pure virtual declared in AutoLockBase.
+ * This gets called by AutoLockBase.acquire() to actually request
+ * the semaphore; in the AutoWriteLock implementation, we request
+ * the semaphore in write mode.
+ */
+/*virtual*/ void AutoWriteLockBase::callLockImpl(LockHandle &l)
+{
+#ifdef VBOX_WITH_MAIN_LOCK_VALIDATION
+ l.lockWrite(m->pcszFile, m->uLine, m->pcszFunction);
+#else
+ l.lockWrite();
+#endif
+}
+
+/**
+ * Implementation of the pure virtual declared in AutoLockBase.
+ * This gets called by AutoLockBase.release() to actually release
+ * the semaphore; in the AutoWriteLock implementation, we release
+ * the semaphore in write mode.
+ */
+/*virtual*/ void AutoWriteLockBase::callUnlockImpl(LockHandle &l)
+{
+ l.unlockWrite();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AutoWriteLock
+//
+////////////////////////////////////////////////////////////////////////////////
+
+AutoWriteLock::AutoWriteLock(uint32_t cHandles,
+ LockHandle** pHandles
+ COMMA_LOCKVAL_SRC_POS_DECL)
+ : AutoWriteLockBase(cHandles
+ COMMA_LOCKVAL_SRC_POS_ARGS)
+{
+ Assert(cHandles);
+ Assert(pHandles);
+
+ for (uint32_t i = 0; i < cHandles; ++i)
+ m->aHandles[i] = pHandles[i];
+
+ acquire();
+}
+
+
+
+/**
+ * Attaches another handle to this auto lock instance.
+ *
+ * The previous object's lock is completely released before the new one is
+ * acquired. The lock level of the new handle will be the same. This
+ * also means that if the lock was not acquired at all before #attach(), it
+ * will not be acquired on the new handle too.
+ *
+ * @param aHandle New handle to attach.
+ */
+void AutoWriteLock::attach(LockHandle *aHandle)
+{
+ LockHandle *pHandle = m->aHandles[0];
+
+ /* detect simple self-reattachment */
+ if (pHandle != aHandle)
+ {
+ bool fWasLocked = m->fIsLocked;
+
+ cleanup();
+
+ m->aHandles[0] = aHandle;
+ m->fIsLocked = fWasLocked;
+
+ if (aHandle)
+ if (fWasLocked)
+ callLockImpl(*aHandle);
+ }
+}
+
+/**
+ * Returns @c true if the current thread holds a write lock on the managed
+ * read/write semaphore. Returns @c false if the managed semaphore is @c
+ * NULL.
+ *
+ * @note Intended for debugging only.
+ */
+bool AutoWriteLock::isWriteLockOnCurrentThread() const
+{
+ return m->aHandles[0] ? m->aHandles[0]->isWriteLockOnCurrentThread() : false;
+}
+
+ /**
+ * Returns the current write lock level of the managed semaphore. The lock
+ * level determines the number of nested #lock() calls on the given
+ * semaphore handle. Returns @c 0 if the managed semaphore is @c
+ * NULL.
+ *
+ * Note that this call is valid only when the current thread owns a write
+ * lock on the given semaphore handle and will assert otherwise.
+ *
+ * @note Intended for debugging only.
+ */
+uint32_t AutoWriteLock::writeLockLevel() const
+{
+ return m->aHandles[0] ? m->aHandles[0]->writeLockLevel() : 0;
+}
+
+/**
+ * Returns @c true if the current thread holds a write lock on the managed
+ * read/write semaphore. Returns @c false if the managed semaphore is @c
+ * NULL.
+ *
+ * @note Intended for debugging only (esp. considering fWannaHear).
+ */
+bool AutoWriteLock::isReadLockedOnCurrentThread(bool fWannaHear) const
+{
+ return m->aHandles[0] ? m->aHandles[0]->isReadLockedOnCurrentThread(fWannaHear) : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AutoMultiWriteLock*
+//
+////////////////////////////////////////////////////////////////////////////////
+
+AutoMultiWriteLock2::AutoMultiWriteLock2(Lockable *pl1,
+ Lockable *pl2
+ COMMA_LOCKVAL_SRC_POS_DECL)
+ : AutoWriteLockBase(2
+ COMMA_LOCKVAL_SRC_POS_ARGS)
+{
+ if (pl1)
+ m->aHandles[0] = pl1->lockHandle();
+ if (pl2)
+ m->aHandles[1] = pl2->lockHandle();
+ acquire();
+}
+
+AutoMultiWriteLock2::AutoMultiWriteLock2(LockHandle *pl1,
+ LockHandle *pl2
+ COMMA_LOCKVAL_SRC_POS_DECL)
+ : AutoWriteLockBase(2
+ COMMA_LOCKVAL_SRC_POS_ARGS)
+{
+ m->aHandles[0] = pl1;
+ m->aHandles[1] = pl2;
+ acquire();
+}
+
+AutoMultiWriteLock3::AutoMultiWriteLock3(Lockable *pl1,
+ Lockable *pl2,
+ Lockable *pl3
+ COMMA_LOCKVAL_SRC_POS_DECL)
+ : AutoWriteLockBase(3
+ COMMA_LOCKVAL_SRC_POS_ARGS)
+{
+ if (pl1)
+ m->aHandles[0] = pl1->lockHandle();
+ if (pl2)
+ m->aHandles[1] = pl2->lockHandle();
+ if (pl3)
+ m->aHandles[2] = pl3->lockHandle();
+ acquire();
+}
+
+AutoMultiWriteLock3::AutoMultiWriteLock3(LockHandle *pl1,
+ LockHandle *pl2,
+ LockHandle *pl3
+ COMMA_LOCKVAL_SRC_POS_DECL)
+ : AutoWriteLockBase(3
+ COMMA_LOCKVAL_SRC_POS_ARGS)
+{
+ m->aHandles[0] = pl1;
+ m->aHandles[1] = pl2;
+ m->aHandles[2] = pl3;
+ acquire();
+}
+
+AutoMultiWriteLock4::AutoMultiWriteLock4(Lockable *pl1,
+ Lockable *pl2,
+ Lockable *pl3,
+ Lockable *pl4
+ COMMA_LOCKVAL_SRC_POS_DECL)
+ : AutoWriteLockBase(4
+ COMMA_LOCKVAL_SRC_POS_ARGS)
+{
+ if (pl1)
+ m->aHandles[0] = pl1->lockHandle();
+ if (pl2)
+ m->aHandles[1] = pl2->lockHandle();
+ if (pl3)
+ m->aHandles[2] = pl3->lockHandle();
+ if (pl4)
+ m->aHandles[3] = pl4->lockHandle();
+ acquire();
+}
+
+AutoMultiWriteLock4::AutoMultiWriteLock4(LockHandle *pl1,
+ LockHandle *pl2,
+ LockHandle *pl3,
+ LockHandle *pl4
+ COMMA_LOCKVAL_SRC_POS_DECL)
+ : AutoWriteLockBase(4
+ COMMA_LOCKVAL_SRC_POS_ARGS)
+{
+ m->aHandles[0] = pl1;
+ m->aHandles[1] = pl2;
+ m->aHandles[2] = pl3;
+ m->aHandles[3] = pl4;
+ acquire();
+}
+
+} /* namespace util */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/glue/ErrorInfo.cpp b/src/VBox/Main/glue/ErrorInfo.cpp
new file mode 100644
index 00000000..aec225b7
--- /dev/null
+++ b/src/VBox/Main/glue/ErrorInfo.cpp
@@ -0,0 +1,374 @@
+/* $Id: ErrorInfo.cpp $ */
+
+/** @file
+ *
+ * ErrorInfo class definition
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#if defined(VBOX_WITH_XPCOM)
+# include <nsIServiceManager.h>
+# include <nsIExceptionService.h>
+# include <nsCOMPtr.h>
+#endif
+
+#include "VBox/com/VirtualBox.h"
+#include "VBox/com/ErrorInfo.h"
+#include "VBox/com/assert.h"
+#include "VBox/com/com.h"
+#include "VBox/com/MultiResult.h"
+
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include <iprt/errcore.h>
+
+namespace com
+{
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ErrorInfo class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT ErrorInfo::getVirtualBoxErrorInfo(ComPtr<IVirtualBoxErrorInfo> &pVirtualBoxErrorInfo)
+{
+ HRESULT rc = S_OK;
+ if (mErrorInfo)
+ rc = mErrorInfo.queryInterfaceTo(pVirtualBoxErrorInfo.asOutParam());
+ else
+ pVirtualBoxErrorInfo.setNull();
+ return rc;
+}
+
+void ErrorInfo::copyFrom(const ErrorInfo &x)
+{
+ mIsBasicAvailable = x.mIsBasicAvailable;
+ mIsFullAvailable = x.mIsFullAvailable;
+
+ mResultCode = x.mResultCode;
+ mResultDetail = x.mResultDetail;
+ mInterfaceID = x.mInterfaceID;
+ mComponent = x.mComponent;
+ mText = x.mText;
+
+ if (x.m_pNext != NULL)
+ m_pNext = new ErrorInfo(*x.m_pNext);
+ else
+ m_pNext = NULL;
+
+ mInterfaceName = x.mInterfaceName;
+ mCalleeIID = x.mCalleeIID;
+ mCalleeName = x.mCalleeName;
+
+ mErrorInfo = x.mErrorInfo;
+}
+
+void ErrorInfo::cleanup()
+{
+ mIsBasicAvailable = false;
+ mIsFullAvailable = false;
+
+ if (m_pNext)
+ {
+ delete m_pNext;
+ m_pNext = NULL;
+ }
+
+ mResultCode = S_OK;
+ mResultDetail = 0;
+ mInterfaceID.clear();
+ mComponent.setNull();
+ mText.setNull();
+ mInterfaceName.setNull();
+ mCalleeIID.clear();
+ mCalleeName.setNull();
+ mErrorInfo.setNull();
+}
+
+void ErrorInfo::init(bool aKeepObj /* = false */)
+{
+ HRESULT rc = E_FAIL;
+
+#if !defined(VBOX_WITH_XPCOM)
+
+ ComPtr<IErrorInfo> err;
+ rc = ::GetErrorInfo(0, err.asOutParam());
+ if (rc == S_OK && err)
+ {
+ if (aKeepObj)
+ mErrorInfo = err;
+
+ ComPtr<IVirtualBoxErrorInfo> info;
+ rc = err.queryInterfaceTo(info.asOutParam());
+ if (SUCCEEDED(rc) && info)
+ init(info);
+
+ if (!mIsFullAvailable)
+ {
+ bool gotSomething = false;
+
+ rc = err->GetGUID(mInterfaceID.asOutParam());
+ gotSomething |= SUCCEEDED(rc);
+ if (SUCCEEDED(rc))
+ GetInterfaceNameByIID(mInterfaceID.ref(), mInterfaceName.asOutParam());
+
+ rc = err->GetSource(mComponent.asOutParam());
+ gotSomething |= SUCCEEDED(rc);
+
+ rc = err->GetDescription(mText.asOutParam());
+ gotSomething |= SUCCEEDED(rc);
+
+ if (gotSomething)
+ mIsBasicAvailable = true;
+
+ AssertMsg(gotSomething, ("Nothing to fetch!\n"));
+ }
+ }
+
+#else // defined(VBOX_WITH_XPCOM)
+
+ nsCOMPtr<nsIExceptionService> es;
+ es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &rc);
+ if (NS_SUCCEEDED(rc))
+ {
+ nsCOMPtr<nsIExceptionManager> em;
+ rc = es->GetCurrentExceptionManager(getter_AddRefs(em));
+ if (NS_SUCCEEDED(rc))
+ {
+ ComPtr<nsIException> ex;
+ rc = em->GetCurrentException(ex.asOutParam());
+ if (NS_SUCCEEDED(rc) && ex)
+ {
+ if (aKeepObj)
+ mErrorInfo = ex;
+
+ ComPtr<IVirtualBoxErrorInfo> info;
+ rc = ex.queryInterfaceTo(info.asOutParam());
+ if (NS_SUCCEEDED(rc) && info)
+ init(info);
+
+ if (!mIsFullAvailable)
+ {
+ bool gotSomething = false;
+
+ rc = ex->GetResult(&mResultCode);
+ gotSomething |= NS_SUCCEEDED(rc);
+
+ char *pszMsg;
+ rc = ex->GetMessage(&pszMsg);
+ gotSomething |= NS_SUCCEEDED(rc);
+ if (NS_SUCCEEDED(rc))
+ {
+ mText = Bstr(pszMsg);
+ nsMemory::Free(pszMsg);
+ }
+
+ if (gotSomething)
+ mIsBasicAvailable = true;
+
+ AssertMsg(gotSomething, ("Nothing to fetch!\n"));
+ }
+
+ // set the exception to NULL (to emulate Win32 behavior)
+ em->SetCurrentException(NULL);
+
+ rc = NS_OK;
+ }
+ }
+ }
+ /* Ignore failure when called after nsComponentManagerImpl::Shutdown(). */
+ else if (rc == NS_ERROR_UNEXPECTED)
+ rc = NS_OK;
+
+ AssertComRC(rc);
+
+#endif // defined(VBOX_WITH_XPCOM)
+}
+
+void ErrorInfo::init(IUnknown *aI,
+ const GUID &aIID,
+ bool aKeepObj /* = false */)
+{
+ AssertReturnVoid(aI);
+
+#if !defined(VBOX_WITH_XPCOM)
+
+ ComPtr<IUnknown> iface = aI;
+ ComPtr<ISupportErrorInfo> serr;
+ HRESULT rc = iface.queryInterfaceTo(serr.asOutParam());
+ if (SUCCEEDED(rc))
+ {
+ rc = serr->InterfaceSupportsErrorInfo(aIID);
+ if (SUCCEEDED(rc))
+ init(aKeepObj);
+ }
+
+#else
+
+ init(aKeepObj);
+
+#endif
+
+ if (mIsBasicAvailable)
+ {
+ mCalleeIID = aIID;
+ GetInterfaceNameByIID(aIID, mCalleeName.asOutParam());
+ }
+}
+
+void ErrorInfo::init(IVirtualBoxErrorInfo *info)
+{
+ AssertReturnVoid(info);
+
+ HRESULT rc = E_FAIL;
+ bool gotSomething = false;
+ bool gotAll = true;
+ LONG lrc, lrd;
+
+ rc = info->COMGETTER(ResultCode)(&lrc); mResultCode = lrc;
+ gotSomething |= SUCCEEDED(rc);
+ gotAll &= SUCCEEDED(rc);
+
+ rc = info->COMGETTER(ResultDetail)(&lrd); mResultDetail = lrd;
+ gotSomething |= SUCCEEDED(rc);
+ gotAll &= SUCCEEDED(rc);
+
+ Bstr iid;
+ rc = info->COMGETTER(InterfaceID)(iid.asOutParam());
+ gotSomething |= SUCCEEDED(rc);
+ gotAll &= SUCCEEDED(rc);
+ if (SUCCEEDED(rc))
+ {
+ mInterfaceID = iid;
+ GetInterfaceNameByIID(mInterfaceID.ref(), mInterfaceName.asOutParam());
+ }
+
+ rc = info->COMGETTER(Component)(mComponent.asOutParam());
+ gotSomething |= SUCCEEDED(rc);
+ gotAll &= SUCCEEDED(rc);
+
+ rc = info->COMGETTER(Text)(mText.asOutParam());
+ gotSomething |= SUCCEEDED(rc);
+ gotAll &= SUCCEEDED(rc);
+
+ m_pNext = NULL;
+
+ ComPtr<IVirtualBoxErrorInfo> next;
+ rc = info->COMGETTER(Next)(next.asOutParam());
+ if (SUCCEEDED(rc) && !next.isNull())
+ {
+ m_pNext = new ErrorInfo(next);
+ Assert(m_pNext != NULL);
+ if (!m_pNext)
+ rc = E_OUTOFMEMORY;
+ }
+
+ gotSomething |= SUCCEEDED(rc);
+ gotAll &= SUCCEEDED(rc);
+
+ mIsBasicAvailable = gotSomething;
+ mIsFullAvailable = gotAll;
+
+ mErrorInfo = info;
+
+ AssertMsg(gotSomething, ("Nothing to fetch!\n"));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ProgressErrorInfo class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+ProgressErrorInfo::ProgressErrorInfo(IProgress *progress) :
+ ErrorInfo(false /* aDummy */)
+{
+ Assert(progress);
+ if (!progress)
+ return;
+
+ ComPtr<IVirtualBoxErrorInfo> info;
+ HRESULT rc = progress->COMGETTER(ErrorInfo)(info.asOutParam());
+ if (SUCCEEDED(rc) && info)
+ init(info);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ErrorInfoKeeper class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT ErrorInfoKeeper::restore()
+{
+ if (mForgot)
+ return S_OK;
+
+ HRESULT rc = S_OK;
+
+#if !defined(VBOX_WITH_XPCOM)
+
+ ComPtr<IErrorInfo> err;
+ if (!mErrorInfo.isNull())
+ {
+ rc = mErrorInfo.queryInterfaceTo(err.asOutParam());
+ AssertComRC(rc);
+ }
+ rc = ::SetErrorInfo(0, err);
+
+#else // defined(VBOX_WITH_XPCOM)
+
+ nsCOMPtr <nsIExceptionService> es;
+ es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &rc);
+ if (NS_SUCCEEDED(rc))
+ {
+ nsCOMPtr <nsIExceptionManager> em;
+ rc = es->GetCurrentExceptionManager(getter_AddRefs(em));
+ if (NS_SUCCEEDED(rc))
+ {
+ ComPtr<nsIException> ex;
+ if (!mErrorInfo.isNull())
+ {
+ rc = mErrorInfo.queryInterfaceTo(ex.asOutParam());
+ AssertComRC(rc);
+ }
+ rc = em->SetCurrentException(ex);
+ }
+ }
+
+#endif // defined(VBOX_WITH_XPCOM)
+
+ if (SUCCEEDED(rc))
+ {
+ mErrorInfo.setNull();
+ mForgot = true;
+ }
+
+ return rc;
+}
+
+} /* namespace com */
+
diff --git a/src/VBox/Main/glue/EventQueue.cpp b/src/VBox/Main/glue/EventQueue.cpp
new file mode 100644
index 00000000..4f5a4a36
--- /dev/null
+++ b/src/VBox/Main/glue/EventQueue.cpp
@@ -0,0 +1,257 @@
+/* $Id: EventQueue.cpp $ */
+/** @file
+ * Event queue class declaration.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @todo Adapt / update documentation! */
+
+#include "VBox/com/EventQueue.h"
+
+#include <iprt/asm.h>
+#include <new> /* For bad_alloc. */
+
+#include <iprt/err.h>
+#include <iprt/semaphore.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/log.h>
+
+namespace com
+{
+
+// EventQueue class
+////////////////////////////////////////////////////////////////////////////////
+
+EventQueue::EventQueue(void)
+ : mUserCnt(0),
+ mShutdown(false)
+{
+ int rc = RTCritSectInit(&mCritSect);
+ AssertRC(rc);
+
+ rc = RTSemEventCreate(&mSemEvent);
+ AssertRC(rc);
+}
+
+EventQueue::~EventQueue(void)
+{
+ int rc = RTCritSectDelete(&mCritSect);
+ AssertRC(rc);
+
+ rc = RTSemEventDestroy(mSemEvent);
+ AssertRC(rc);
+
+ EventQueueListIterator it = mEvents.begin();
+ while (it != mEvents.end())
+ {
+ (*it)->Release();
+ it = mEvents.erase(it);
+ }
+}
+
+/**
+ * Process events pending on this event queue, and wait up to given timeout, if
+ * nothing is available.
+ *
+ * Must be called on same thread this event queue was created on.
+ *
+ * @param cMsTimeout The timeout specified as milliseconds. Use
+ * RT_INDEFINITE_WAIT to wait till an event is posted on the
+ * queue.
+ *
+ * @returns VBox status code
+ * @retval VINF_SUCCESS if one or more messages was processed.
+ * @retval VERR_TIMEOUT if cMsTimeout expired.
+ * @retval VERR_INVALID_CONTEXT if called on the wrong thread.
+ * @retval VERR_INTERRUPTED if interruptEventQueueProcessing was called.
+ * On Windows will also be returned when WM_QUIT is encountered.
+ * On Darwin this may also be returned when the native queue is
+ * stopped or destroyed/finished.
+ * @retval VINF_INTERRUPTED if the native system call was interrupted by a
+ * an asynchronous event delivery (signal) or just felt like returning
+ * out of bounds. On darwin it will also be returned if the queue is
+ * stopped.
+ */
+int EventQueue::processEventQueue(RTMSINTERVAL cMsTimeout)
+{
+ size_t cNumEvents;
+ int rc = RTCritSectEnter(&mCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (mUserCnt == 0) /* No concurrent access allowed. */
+ {
+ mUserCnt++;
+
+ cNumEvents = mEvents.size();
+ if (!cNumEvents)
+ {
+ int rc2 = RTCritSectLeave(&mCritSect);
+ AssertRC(rc2);
+
+ rc = RTSemEventWaitNoResume(mSemEvent, cMsTimeout);
+
+ rc2 = RTCritSectEnter(&mCritSect);
+ AssertRC(rc2);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (mShutdown)
+ rc = VERR_INTERRUPTED;
+ cNumEvents = mEvents.size();
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = processPendingEvents(cNumEvents);
+
+ Assert(mUserCnt);
+ mUserCnt--;
+ }
+ else
+ rc = VERR_WRONG_ORDER;
+
+ int rc2 = RTCritSectLeave(&mCritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ Assert(rc != VERR_TIMEOUT || cMsTimeout != RT_INDEFINITE_WAIT);
+ return rc;
+}
+
+/**
+ * Processes all pending events in the queue at the time of
+ * calling. Note: Does no initial locking, must be done by the
+ * caller!
+ *
+ * @return IPRT status code.
+ */
+int EventQueue::processPendingEvents(size_t cNumEvents)
+{
+ if (!cNumEvents) /* Nothing to process? Bail out early. */
+ return VINF_SUCCESS;
+
+ int rc = VINF_SUCCESS;
+
+ EventQueueListIterator it = mEvents.begin();
+ for (size_t i = 0;
+ i < cNumEvents
+ && it != mEvents.end(); i++)
+ {
+ Event *pEvent = *it;
+ AssertPtr(pEvent);
+
+ mEvents.erase(it);
+
+ int rc2 = RTCritSectLeave(&mCritSect);
+ AssertRC(rc2);
+
+ pEvent->handler();
+ pEvent->Release();
+
+ rc2 = RTCritSectEnter(&mCritSect);
+ AssertRC(rc2);
+
+ it = mEvents.begin();
+ if (mShutdown)
+ {
+ rc = VERR_INTERRUPTED;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Interrupt thread waiting on event queue processing.
+ *
+ * Can be called on any thread.
+ *
+ * @returns VBox status code.
+ */
+int EventQueue::interruptEventQueueProcessing(void)
+{
+ ASMAtomicWriteBool(&mShutdown, true);
+
+ return RTSemEventSignal(mSemEvent);
+}
+
+/**
+ * Posts an event to this event loop asynchronously.
+ *
+ * @param pEvent the event to post, must be allocated using |new|
+ * @return TRUE if successful and false otherwise
+ */
+BOOL EventQueue::postEvent(Event *pEvent)
+{
+ int rc = RTCritSectEnter(&mCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ try
+ {
+ if (pEvent)
+ {
+ pEvent->AddRef();
+ mEvents.push_back(pEvent);
+ }
+ else /* No locking, since we're already in our crit sect. */
+ mShutdown = true;
+
+ size_t cEvents = mEvents.size();
+ if (cEvents > _1K) /** @todo Make value configurable? */
+ {
+ static int s_cBitchedAboutLotEvents = 0;
+ if (s_cBitchedAboutLotEvents < 10)
+ LogRel(("Warning: Event queue received lots of events (%zu), expect delayed event handling (%d/10)\n",
+ cEvents, ++s_cBitchedAboutLotEvents));
+ }
+
+ /* Leave critical section before signalling event. */
+ rc = RTCritSectLeave(&mCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ int rc2 = RTSemEventSignal(mSemEvent);
+ AssertRC(rc2);
+ }
+ }
+ catch (std::bad_alloc &ba)
+ {
+ NOREF(ba);
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = RTCritSectLeave(&mCritSect);
+ AssertRC(rc2);
+ }
+ }
+
+ return RT_SUCCESS(rc) ? TRUE : FALSE;
+}
+
+}
+/* namespace com */
diff --git a/src/VBox/Main/glue/GetVBoxUserHomeDirectory.cpp b/src/VBox/Main/glue/GetVBoxUserHomeDirectory.cpp
new file mode 100644
index 00000000..84f3adb9
--- /dev/null
+++ b/src/VBox/Main/glue/GetVBoxUserHomeDirectory.cpp
@@ -0,0 +1,142 @@
+/* $Id: GetVBoxUserHomeDirectory.cpp $ */
+/** @file
+ * MS COM / XPCOM Abstraction Layer - GetVBoxUserHomeDirectory.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN
+#include <VBox/com/utils.h>
+
+#include <iprt/env.h>
+#include <iprt/dir.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#if !defined(RT_OS_DARWIN) && !defined(RT_OS_WINDOWS)
+char g_szXdgConfigHome[RTPATH_MAX] = "";
+#endif
+
+/**
+ * Possible locations for the VirtualBox user configuration folder,
+ * listed from oldest (as in legacy) to newest. These can be either
+ * absolute or relative to the home directory. We use the first entry
+ * of the list which corresponds to a real folder on storage, or
+ * create a folder corresponding to the last in the list (the least
+ * legacy) if none do.
+ */
+const char * const g_apcszUserHome[] =
+{
+#ifdef RT_OS_DARWIN
+ "Library/VirtualBox",
+#elif defined RT_OS_WINDOWS
+ ".VirtualBox",
+#else
+ ".VirtualBox", g_szXdgConfigHome,
+#endif
+};
+
+
+namespace com
+{
+
+static int composeHomePath(char *aDir, size_t aDirLen, const char *pcszBase)
+{
+ int vrc;
+ if (RTPathStartsWithRoot(pcszBase))
+ vrc = RTStrCopy(aDir, aDirLen, pcszBase);
+ else
+ {
+ /* compose the config directory (full path) */
+ /** @todo r=bird: RTPathUserHome doesn't necessarily return a
+ * full (abs) path like the comment above seems to indicate. */
+ vrc = RTPathUserHome(aDir, aDirLen);
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppend(aDir, aDirLen, pcszBase);
+ }
+ return vrc;
+}
+
+int GetVBoxUserHomeDirectory(char *aDir, size_t aDirLen, bool fCreateDir)
+{
+ AssertReturn(aDir, VERR_INVALID_POINTER);
+ AssertReturn(aDirLen > 0, VERR_BUFFER_OVERFLOW);
+
+ /* start with null */
+ *aDir = 0;
+
+ char szTmp[RTPATH_MAX];
+ int vrc = RTEnvGetEx(RTENV_DEFAULT, "VBOX_USER_HOME", szTmp, sizeof(szTmp), NULL);
+ if (RT_SUCCESS(vrc) || vrc == VERR_ENV_VAR_NOT_FOUND)
+ {
+ bool fFound = false;
+ if (RT_SUCCESS(vrc))
+ {
+ /* get the full path name */
+ vrc = RTPathAbs(szTmp, aDir, aDirLen);
+ }
+ else
+ {
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_DARWIN)
+ vrc = RTEnvGetEx(RTENV_DEFAULT, "XDG_CONFIG_HOME", g_szXdgConfigHome, sizeof(g_szXdgConfigHome), NULL);
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppend(g_szXdgConfigHome, sizeof(g_szXdgConfigHome), "VirtualBox");
+ AssertMsg(vrc == VINF_SUCCESS || vrc == VERR_ENV_VAR_NOT_FOUND, ("%Rrc\n", vrc));
+ if (RT_FAILURE_NP(vrc))
+ vrc = RTStrCopy(g_szXdgConfigHome, sizeof(g_szXdgConfigHome), ".config/VirtualBox");
+#endif
+ for (unsigned i = 0; i < RT_ELEMENTS(g_apcszUserHome); ++i)
+ {
+ vrc = composeHomePath(aDir, aDirLen, g_apcszUserHome[i]);
+ if ( RT_SUCCESS(vrc)
+ && RTDirExists(aDir))
+ {
+ fFound = true;
+ break;
+ }
+ }
+ }
+
+ /* ensure the home directory exists */
+ if (RT_SUCCESS(vrc))
+ if (!fFound && fCreateDir)
+ vrc = RTDirCreateFullPath(aDir, 0700);
+ }
+
+ return vrc;
+}
+
+} /* namespace com */
diff --git a/src/VBox/Main/glue/Makefile.kup b/src/VBox/Main/glue/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/glue/Makefile.kup
diff --git a/src/VBox/Main/glue/NativeEventQueue.cpp b/src/VBox/Main/glue/NativeEventQueue.cpp
new file mode 100644
index 00000000..fc075c6b
--- /dev/null
+++ b/src/VBox/Main/glue/NativeEventQueue.cpp
@@ -0,0 +1,676 @@
+/* $Id: NativeEventQueue.cpp $ */
+/** @file
+ * MS COM / XPCOM Abstraction Layer:
+ * Main event queue class declaration
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "VBox/com/NativeEventQueue.h"
+
+#include <new> /* For bad_alloc. */
+
+#ifdef RT_OS_DARWIN
+# include <CoreFoundation/CFRunLoop.h>
+#endif
+
+#if defined(VBOX_WITH_XPCOM) && !defined(RT_OS_DARWIN) && !defined(RT_OS_OS2)
+# define USE_XPCOM_QUEUE
+#endif
+
+#include <iprt/err.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/log.h>
+#ifdef USE_XPCOM_QUEUE
+# include <errno.h>
+#endif
+
+namespace com
+{
+
+// NativeEventQueue class
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef VBOX_WITH_XPCOM
+
+# define CHECK_THREAD_RET(ret) \
+ do { \
+ AssertMsg(GetCurrentThreadId() == mThreadId, ("Must be on event queue thread!")); \
+ if (GetCurrentThreadId() != mThreadId) \
+ return ret; \
+ } while (0)
+
+/** Magic LPARAM value for the WM_USER messages that we're posting.
+ * @remarks This magic value is duplicated in
+ * vboxapi/PlatformMSCOM::interruptWaitEvents(). */
+#define EVENTQUEUE_WIN_LPARAM_MAGIC UINT32_C(0xf241b819)
+
+
+#else // VBOX_WITH_XPCOM
+
+# define CHECK_THREAD_RET(ret) \
+ do { \
+ if (!mEventQ) \
+ return ret; \
+ BOOL isOnCurrentThread = FALSE; \
+ mEventQ->IsOnCurrentThread(&isOnCurrentThread); \
+ AssertMsg(isOnCurrentThread, ("Must be on event queue thread!")); \
+ if (!isOnCurrentThread) \
+ return ret; \
+ } while (0)
+
+#endif // VBOX_WITH_XPCOM
+
+/** Pointer to the main event queue. */
+NativeEventQueue *NativeEventQueue::sMainQueue = NULL;
+
+
+#ifdef VBOX_WITH_XPCOM
+
+struct MyPLEvent : public PLEvent
+{
+ MyPLEvent(NativeEvent *e) : event(e) {}
+ NativeEvent *event;
+};
+
+/* static */
+void *PR_CALLBACK com::NativeEventQueue::plEventHandler(PLEvent *self)
+{
+ NativeEvent *ev = ((MyPLEvent *)self)->event;
+ if (ev)
+ ev->handler();
+ else
+ {
+ NativeEventQueue *eq = (NativeEventQueue *)self->owner;
+ Assert(eq);
+ eq->mInterrupted = true;
+ }
+ return NULL;
+}
+
+/* static */
+void PR_CALLBACK com::NativeEventQueue::plEventDestructor(PLEvent *self)
+{
+ NativeEvent *ev = ((MyPLEvent *)self)->event;
+ if (ev)
+ delete ev;
+ delete self;
+}
+
+#endif // VBOX_WITH_XPCOM
+
+/**
+ * Constructs an event queue for the current thread.
+ *
+ * Currently, there can be only one event queue per thread, so if an event
+ * queue for the current thread already exists, this object is simply attached
+ * to the existing event queue.
+ */
+NativeEventQueue::NativeEventQueue()
+{
+#ifndef VBOX_WITH_XPCOM
+
+ mThreadId = GetCurrentThreadId();
+ // force the system to create the message queue for the current thread
+ MSG msg;
+ PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
+
+ if (!DuplicateHandle(GetCurrentProcess(),
+ GetCurrentThread(),
+ GetCurrentProcess(),
+ &mhThread,
+ 0 /*dwDesiredAccess*/,
+ FALSE /*bInheritHandle*/,
+ DUPLICATE_SAME_ACCESS))
+ mhThread = INVALID_HANDLE_VALUE;
+
+#else // VBOX_WITH_XPCOM
+
+ mEQCreated = false;
+ mInterrupted = false;
+
+ // Here we reference the global nsIEventQueueService instance and hold it
+ // until we're destroyed. This is necessary to keep NS_ShutdownXPCOM() away
+ // from calling StopAcceptingEvents() on all event queues upon destruction of
+ // nsIEventQueueService, and makes sense when, for some reason, this happens
+ // *before* we're able to send a NULL event to stop our event handler thread
+ // when doing unexpected cleanup caused indirectly by NS_ShutdownXPCOM()
+ // that is performing a global cleanup of everything. A good example of such
+ // situation is when NS_ShutdownXPCOM() is called while the VirtualBox component
+ // is still alive (because it is still referenced): eventually, it results in
+ // a VirtualBox::uninit() call from where it is already not possible to post
+ // NULL to the event thread (because it stopped accepting events).
+
+ nsresult rc = NS_GetEventQueueService(getter_AddRefs(mEventQService));
+
+ if (NS_SUCCEEDED(rc))
+ {
+ rc = mEventQService->GetThreadEventQueue(NS_CURRENT_THREAD,
+ getter_AddRefs(mEventQ));
+ if (rc == NS_ERROR_NOT_AVAILABLE)
+ {
+ rc = mEventQService->CreateThreadEventQueue();
+ if (NS_SUCCEEDED(rc))
+ {
+ mEQCreated = true;
+ rc = mEventQService->GetThreadEventQueue(NS_CURRENT_THREAD,
+ getter_AddRefs(mEventQ));
+ }
+ }
+ }
+ AssertComRC(rc);
+
+#endif // VBOX_WITH_XPCOM
+}
+
+NativeEventQueue::~NativeEventQueue()
+{
+#ifndef VBOX_WITH_XPCOM
+ if (mhThread != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(mhThread);
+ mhThread = INVALID_HANDLE_VALUE;
+ }
+#else // VBOX_WITH_XPCOM
+ // process all pending events before destruction
+ if (mEventQ)
+ {
+ if (mEQCreated)
+ {
+ mEventQ->StopAcceptingEvents();
+ mEventQ->ProcessPendingEvents();
+ mEventQService->DestroyThreadEventQueue();
+ }
+ mEventQ = nsnull;
+ mEventQService = nsnull;
+ }
+#endif // VBOX_WITH_XPCOM
+}
+
+/**
+ * Initializes the main event queue instance.
+ * @returns VBox status code.
+ *
+ * @remarks If you're using the rest of the COM/XPCOM glue library,
+ * com::Initialize() will take care of initializing and uninitializing
+ * the NativeEventQueue class. If you don't call com::Initialize, you must
+ * make sure to call this method on the same thread that did the
+ * XPCOM initialization or we'll end up using the wrong main queue.
+ */
+/* static */
+int NativeEventQueue::init()
+{
+ Assert(sMainQueue == NULL);
+ Assert(RTThreadIsMain(RTThreadSelf()));
+
+ try
+ {
+ sMainQueue = new NativeEventQueue();
+ AssertPtr(sMainQueue);
+#ifdef VBOX_WITH_XPCOM
+ /* Check that it actually is the main event queue, i.e. that
+ we're called on the right thread. */
+ nsCOMPtr<nsIEventQueue> q;
+ nsresult rv = NS_GetMainEventQ(getter_AddRefs(q));
+ AssertComRCReturn(rv, VERR_INVALID_POINTER);
+ Assert(q == sMainQueue->mEventQ);
+
+ /* Check that it's a native queue. */
+ PRBool fIsNative = PR_FALSE;
+ rv = sMainQueue->mEventQ->IsQueueNative(&fIsNative);
+ Assert(NS_SUCCEEDED(rv) && fIsNative);
+#endif // VBOX_WITH_XPCOM
+ }
+ catch (std::bad_alloc &ba)
+ {
+ NOREF(ba);
+ return VERR_NO_MEMORY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Uninitialize the global resources (i.e. the main event queue instance).
+ * @returns VINF_SUCCESS
+ */
+/* static */
+int NativeEventQueue::uninit()
+{
+ if (sMainQueue)
+ {
+ /* Must process all events to make sure that no NULL event is left
+ * after this point. It would need to modify the state of sMainQueue. */
+#ifdef RT_OS_DARWIN /* Do not process the native runloop, the toolkit may not be ready for it. */
+ sMainQueue->mEventQ->ProcessPendingEvents();
+#else
+ sMainQueue->processEventQueue(0);
+#endif
+ delete sMainQueue;
+ sMainQueue = NULL;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Get main event queue instance.
+ *
+ * Depends on init() being called first.
+ */
+/* static */
+NativeEventQueue* NativeEventQueue::getMainEventQueue()
+{
+ return sMainQueue;
+}
+
+#ifdef VBOX_WITH_XPCOM
+# ifdef RT_OS_DARWIN
+/**
+ * Wait for events and process them (Darwin).
+ *
+ * @retval VINF_SUCCESS
+ * @retval VERR_TIMEOUT
+ * @retval VERR_INTERRUPTED
+ *
+ * @param cMsTimeout How long to wait, or RT_INDEFINITE_WAIT.
+ */
+static int waitForEventsOnDarwin(RTMSINTERVAL cMsTimeout)
+{
+ /*
+ * Wait for the requested time, if we get a hit we do a poll to process
+ * any other pending messages.
+ *
+ * Note! About 1.0e10: According to the sources anything above 3.1556952e+9
+ * means indefinite wait and 1.0e10 is what CFRunLoopRun() uses.
+ */
+ CFTimeInterval rdTimeout = cMsTimeout == RT_INDEFINITE_WAIT ? 1e10 : (double)cMsTimeout / 1000;
+ OSStatus orc = CFRunLoopRunInMode(kCFRunLoopDefaultMode, rdTimeout, true /*returnAfterSourceHandled*/);
+ if (orc == kCFRunLoopRunHandledSource)
+ {
+ OSStatus orc2 = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, false /*returnAfterSourceHandled*/);
+ if ( orc2 == kCFRunLoopRunStopped
+ || orc2 == kCFRunLoopRunFinished)
+ orc = orc2;
+ }
+ if ( orc == 0 /*???*/
+ || orc == kCFRunLoopRunHandledSource)
+ return VINF_SUCCESS;
+ if ( orc == kCFRunLoopRunStopped
+ || orc == kCFRunLoopRunFinished)
+ return VERR_INTERRUPTED;
+ AssertMsg(orc == kCFRunLoopRunTimedOut, ("Unexpected status code from CFRunLoopRunInMode: %#x", orc));
+ return VERR_TIMEOUT;
+}
+# else // !RT_OS_DARWIN
+
+/**
+ * Wait for events (generic XPCOM).
+ *
+ * @retval VINF_SUCCESS
+ * @retval VERR_TIMEOUT
+ * @retval VINF_INTERRUPTED
+ * @retval VERR_INTERNAL_ERROR_4
+ *
+ * @param pQueue The queue to wait on.
+ * @param cMsTimeout How long to wait, or RT_INDEFINITE_WAIT.
+ */
+static int waitForEventsOnXPCOM(nsIEventQueue *pQueue, RTMSINTERVAL cMsTimeout)
+{
+ int fd = pQueue->GetEventQueueSelectFD();
+ fd_set fdsetR;
+ FD_ZERO(&fdsetR);
+ FD_SET(fd, &fdsetR);
+
+ fd_set fdsetE = fdsetR;
+
+ struct timeval tv = {0,0};
+ struct timeval *ptv;
+ if (cMsTimeout == RT_INDEFINITE_WAIT)
+ ptv = NULL;
+ else
+ {
+ tv.tv_sec = cMsTimeout / 1000;
+ tv.tv_usec = (cMsTimeout % 1000) * 1000;
+ ptv = &tv;
+ }
+
+ int rc = select(fd + 1, &fdsetR, NULL, &fdsetE, ptv);
+ if (rc > 0)
+ rc = VINF_SUCCESS;
+ else if (rc == 0)
+ rc = VERR_TIMEOUT;
+ else if (errno == EINTR)
+ rc = VINF_INTERRUPTED;
+ else
+ {
+ static uint32_t s_ErrorCount = 0;
+ if (s_ErrorCount < 500)
+ {
+ LogRel(("waitForEventsOnXPCOM rc=%d errno=%d\n", rc, errno));
+ ++s_ErrorCount;
+ }
+
+ AssertMsgFailed(("rc=%d errno=%d\n", rc, errno));
+ rc = VERR_INTERNAL_ERROR_4;
+ }
+ return rc;
+}
+
+# endif // !RT_OS_DARWIN
+#endif // VBOX_WITH_XPCOM
+
+#ifndef VBOX_WITH_XPCOM
+
+/**
+ * Dispatch a message on Windows.
+ *
+ * This will pick out our events and handle them specially.
+ *
+ * @returns @a rc or VERR_INTERRUPTED (WM_QUIT or NULL msg).
+ * @param pMsg The message to dispatch.
+ * @param rc The current status code.
+ */
+/*static*/
+int NativeEventQueue::dispatchMessageOnWindows(MSG const *pMsg, int rc)
+{
+ /*
+ * Check for and dispatch our events.
+ */
+ if ( pMsg->hwnd == NULL
+ && pMsg->message == WM_USER)
+ {
+ if (pMsg->lParam == EVENTQUEUE_WIN_LPARAM_MAGIC)
+ {
+ NativeEvent *pEvent = (NativeEvent *)pMsg->wParam;
+ if (pEvent)
+ {
+ pEvent->handler();
+ delete pEvent;
+ }
+ else
+ rc = VERR_INTERRUPTED;
+ return rc;
+ }
+ AssertMsgFailed(("lParam=%p wParam=%p\n", pMsg->lParam, pMsg->wParam));
+ }
+
+ /*
+ * Check for the quit message and dispatch the message the normal way.
+ */
+ if (pMsg->message == WM_QUIT)
+ rc = VERR_INTERRUPTED;
+ TranslateMessage(pMsg);
+ DispatchMessage(pMsg);
+
+ return rc;
+}
+
+
+/**
+ * Process pending events (Windows).
+ *
+ * @retval VINF_SUCCESS
+ * @retval VERR_TIMEOUT
+ * @retval VERR_INTERRUPTED.
+ */
+static int processPendingEvents(void)
+{
+ int rc = VERR_TIMEOUT;
+ MSG Msg;
+ if (PeekMessage(&Msg, NULL /*hWnd*/, 0 /*wMsgFilterMin*/, 0 /*wMsgFilterMax*/, PM_REMOVE))
+ {
+ rc = VINF_SUCCESS;
+ do
+ rc = NativeEventQueue::dispatchMessageOnWindows(&Msg, rc);
+ while (PeekMessage(&Msg, NULL /*hWnd*/, 0 /*wMsgFilterMin*/, 0 /*wMsgFilterMax*/, PM_REMOVE));
+ }
+ return rc;
+}
+
+#else // VBOX_WITH_XPCOM
+
+/**
+ * Process pending XPCOM events.
+ * @param pQueue The queue to process events on.
+ * @retval VINF_SUCCESS
+ * @retval VERR_TIMEOUT
+ * @retval VERR_INTERRUPTED (darwin only)
+ * @retval VERR_INTERNAL_ERROR_2
+ */
+static int processPendingEvents(nsIEventQueue *pQueue)
+{
+ /* ProcessPendingEvents doesn't report back what it did, so check here. */
+ PRBool fHasEvents = PR_FALSE;
+ nsresult hr = pQueue->PendingEvents(&fHasEvents);
+ if (NS_FAILED(hr))
+ return VERR_INTERNAL_ERROR_2;
+
+ /* Process pending events. */
+ int rc = VINF_SUCCESS;
+ if (fHasEvents)
+ pQueue->ProcessPendingEvents();
+ else
+ rc = VERR_TIMEOUT;
+
+# ifdef RT_OS_DARWIN
+ /* Process pending native events. */
+ int rc2 = waitForEventsOnDarwin(0);
+ if (rc == VERR_TIMEOUT || rc2 == VERR_INTERRUPTED)
+ rc = rc2;
+# endif
+
+ return rc;
+}
+
+#endif // VBOX_WITH_XPCOM
+
+/**
+ * Process events pending on this event queue, and wait up to given timeout, if
+ * nothing is available.
+ *
+ * Must be called on same thread this event queue was created on.
+ *
+ * @param cMsTimeout The timeout specified as milliseconds. Use
+ * RT_INDEFINITE_WAIT to wait till an event is posted on the
+ * queue.
+ *
+ * @returns VBox status code
+ * @retval VINF_SUCCESS if one or more messages was processed.
+ * @retval VERR_TIMEOUT if cMsTimeout expired.
+ * @retval VERR_INVALID_CONTEXT if called on the wrong thread.
+ * @retval VERR_INTERRUPTED if interruptEventQueueProcessing was called.
+ * On Windows will also be returned when WM_QUIT is encountered.
+ * On Darwin this may also be returned when the native queue is
+ * stopped or destroyed/finished.
+ * @retval VINF_INTERRUPTED if the native system call was interrupted by a
+ * an asynchronous event delivery (signal) or just felt like returning
+ * out of bounds. On darwin it will also be returned if the queue is
+ * stopped.
+ *
+ * @note On darwin this function will not return when the thread receives a
+ * signal, it will just resume the wait.
+ */
+int NativeEventQueue::processEventQueue(RTMSINTERVAL cMsTimeout)
+{
+ int rc;
+ CHECK_THREAD_RET(VERR_INVALID_CONTEXT);
+
+#ifdef VBOX_WITH_XPCOM
+ /*
+ * Process pending events, if none are available and we're not in a
+ * poll call, wait for some to appear. (We have to be a little bit
+ * careful after waiting for the events since Darwin will process
+ * them as part of the wait, while the XPCOM case will not.)
+ *
+ * Note! Unfortunately, WaitForEvent isn't interruptible with Ctrl-C,
+ * while select() is. So we cannot use it for indefinite waits.
+ */
+ rc = processPendingEvents(mEventQ);
+ if ( rc == VERR_TIMEOUT
+ && cMsTimeout > 0)
+ {
+# ifdef RT_OS_DARWIN
+ /** @todo check how Ctrl-C works on Darwin.
+ * Update: It doesn't work. MACH_RCV_INTERRUPT could perhaps be returned
+ * to __CFRunLoopServiceMachPort, but neither it nor __CFRunLoopRun
+ * has any way of expressing it via their return values. So, if
+ * Ctrl-C handling is important, signal needs to be handled on
+ * a different thread or something. */
+ rc = waitForEventsOnDarwin(cMsTimeout);
+# else // !RT_OS_DARWIN
+ rc = waitForEventsOnXPCOM(mEventQ, cMsTimeout);
+# endif // !RT_OS_DARWIN
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_TIMEOUT)
+ {
+ int rc2 = processPendingEvents(mEventQ);
+ /* If the wait was successful don't fail the whole operation. */
+ if (RT_FAILURE(rc) && RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ }
+
+ if ( ( RT_SUCCESS(rc)
+ || rc == VERR_INTERRUPTED
+ || rc == VERR_TIMEOUT)
+ && mInterrupted)
+ {
+ mInterrupted = false;
+ rc = VERR_INTERRUPTED;
+ }
+
+#else // !VBOX_WITH_XPCOM
+ if (cMsTimeout == RT_INDEFINITE_WAIT)
+ {
+ BOOL fRet = 0; /* Shut up MSC */
+ MSG Msg;
+ rc = VINF_SUCCESS;
+ while ( rc != VERR_INTERRUPTED
+ && (fRet = GetMessage(&Msg, NULL /*hWnd*/, WM_USER, WM_USER))
+ && fRet != -1)
+ rc = NativeEventQueue::dispatchMessageOnWindows(&Msg, rc);
+ if (fRet == 0)
+ rc = VERR_INTERRUPTED;
+ else if (fRet == -1)
+ rc = RTErrConvertFromWin32(GetLastError());
+ }
+ else
+ {
+ rc = processPendingEvents();
+ if ( rc == VERR_TIMEOUT
+ && cMsTimeout != 0)
+ {
+ DWORD rcW = MsgWaitForMultipleObjects(1,
+ &mhThread,
+ TRUE /*fWaitAll*/,
+ cMsTimeout,
+ QS_ALLINPUT);
+ AssertMsgReturn(rcW == WAIT_TIMEOUT || rcW == WAIT_OBJECT_0,
+ ("%d\n", rcW),
+ VERR_INTERNAL_ERROR_4);
+ rc = processPendingEvents();
+ }
+ }
+#endif // !VBOX_WITH_XPCOM
+
+ Assert(rc != VERR_TIMEOUT || cMsTimeout != RT_INDEFINITE_WAIT);
+ return rc;
+}
+
+/**
+ * Interrupt thread waiting on event queue processing.
+ *
+ * Can be called on any thread.
+ *
+ * @returns VBox status code.
+ */
+int NativeEventQueue::interruptEventQueueProcessing()
+{
+ /* Send a NULL event. This event will be picked up and handled specially
+ * both for XPCOM and Windows. It is the responsibility of the caller to
+ * take care of not running the loop again in a way which will hang. */
+ postEvent(NULL);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Posts an event to this event loop asynchronously.
+ *
+ * @param pEvent the event to post, must be allocated using |new|
+ * @return @c TRUE if successful and false otherwise
+ */
+BOOL NativeEventQueue::postEvent(NativeEvent *pEvent)
+{
+#ifndef VBOX_WITH_XPCOM
+ /* Note! The event == NULL case is duplicated in vboxapi/PlatformMSCOM::interruptWaitEvents(). */
+ BOOL fRc = PostThreadMessage(mThreadId, WM_USER, (WPARAM)pEvent, EVENTQUEUE_WIN_LPARAM_MAGIC);
+ if (!fRc)
+ {
+ static int s_cBitchedAboutFullNativeEventQueue = 0;
+ if ( GetLastError() == ERROR_NOT_ENOUGH_QUOTA
+ && s_cBitchedAboutFullNativeEventQueue < 10)
+ LogRel(("Warning: Asynchronous event queue (%p, thread %RI32) full, event (%p) not delivered (%d/10)\n",
+ this, mThreadId, pEvent, ++s_cBitchedAboutFullNativeEventQueue));
+ else
+ AssertFailed();
+ }
+ return fRc;
+#else // VBOX_WITH_XPCOM
+ if (!mEventQ)
+ return FALSE;
+
+ try
+ {
+ MyPLEvent *pMyEvent = new MyPLEvent(pEvent);
+ mEventQ->InitEvent(pMyEvent, this, com::NativeEventQueue::plEventHandler,
+ com::NativeEventQueue::plEventDestructor);
+ HRESULT rc = mEventQ->PostEvent(pMyEvent);
+ return NS_SUCCEEDED(rc);
+ }
+ catch (std::bad_alloc &ba)
+ {
+ AssertMsgFailed(("Out of memory while allocating memory for event=%p: %s\n",
+ pEvent, ba.what()));
+ }
+
+ return FALSE;
+#endif // VBOX_WITH_XPCOM
+}
+
+/**
+ * Get select()'able selector for this event queue.
+ * This will return -1 on platforms and queue variants not supporting such
+ * functionality.
+ */
+int NativeEventQueue::getSelectFD()
+{
+#ifdef VBOX_WITH_XPCOM
+ return mEventQ->GetEventQueueSelectFD();
+#else
+ return -1;
+#endif
+}
+
+}
+/* namespace com */
diff --git a/src/VBox/Main/glue/VBoxLogRelCreate.cpp b/src/VBox/Main/glue/VBoxLogRelCreate.cpp
new file mode 100644
index 00000000..78acffec
--- /dev/null
+++ b/src/VBox/Main/glue/VBoxLogRelCreate.cpp
@@ -0,0 +1,214 @@
+/* $Id: VBoxLogRelCreate.cpp $ */
+/** @file
+ * MS COM / XPCOM Abstraction Layer - VBoxLogRelCreate.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/com/utils.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/process.h>
+#include <iprt/time.h>
+
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+#include <VBox/version.h>
+#include "package-generated.h"
+
+
+
+namespace com
+{
+
+static const char *g_pszLogEntity = NULL;
+
+static DECLCALLBACK(void) vboxHeaderFooter(PRTLOGGER pReleaseLogger, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog)
+{
+ /* some introductory information */
+ static RTTIMESPEC s_TimeSpec;
+ char szTmp[256];
+ if (enmPhase == RTLOGPHASE_BEGIN)
+ RTTimeNow(&s_TimeSpec);
+ RTTimeSpecToString(&s_TimeSpec, szTmp, sizeof(szTmp));
+
+ switch (enmPhase)
+ {
+ case RTLOGPHASE_BEGIN:
+ {
+ bool fOldBuffered = RTLogSetBuffering(pReleaseLogger, true /*fBuffered*/);
+ pfnLog(pReleaseLogger,
+ "VirtualBox %s %s r%u %s (%s %s) release log\n"
+#ifdef VBOX_BLEEDING_EDGE
+ "EXPERIMENTAL build " VBOX_BLEEDING_EDGE "\n"
+#endif
+ "Log opened %s\n",
+ g_pszLogEntity, VBOX_VERSION_STRING, RTBldCfgRevision(),
+ RTBldCfgTargetDotArch(), __DATE__, __TIME__, szTmp);
+
+ pfnLog(pReleaseLogger, "Build Type: %s\n", KBUILD_TYPE);
+ int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pReleaseLogger, "OS Product: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pReleaseLogger, "OS Release: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pReleaseLogger, "OS Version: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pReleaseLogger, "OS Service Pack: %s\n", szTmp);
+
+ vrc = RTSystemQueryDmiString(RTSYSDMISTR_PRODUCT_NAME, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pReleaseLogger, "DMI Product Name: %s\n", szTmp);
+ vrc = RTSystemQueryDmiString(RTSYSDMISTR_PRODUCT_VERSION, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pReleaseLogger, "DMI Product Version: %s\n", szTmp);
+
+ RTSYSFWTYPE enmType;
+ vrc = RTSystemQueryFirmwareType(&enmType);
+ if (RT_SUCCESS(vrc))
+ {
+ pfnLog(pReleaseLogger, "Firmware type: %s\n", RTSystemFirmwareTypeName(enmType));
+ if (enmType == RTSYSFWTYPE_UEFI)
+ {
+ bool fValue;
+ vrc = RTSystemQueryFirmwareBoolean(RTSYSFWBOOL_SECURE_BOOT, &fValue);
+ if (RT_SUCCESS(vrc))
+ pfnLog(pReleaseLogger, "Secure Boot: %s\n", fValue ? "Enabled" : "Disabled");
+ else
+ pfnLog(pReleaseLogger, "Secure Boot: %Rrc\n", vrc);
+ }
+ }
+ else
+ pfnLog(pReleaseLogger, "Firmware type: failed - %Rrc\n", vrc);
+
+ uint64_t cbHostRam = 0, cbHostRamAvail = 0;
+ vrc = RTSystemQueryTotalRam(&cbHostRam);
+ if (RT_SUCCESS(vrc))
+ vrc = RTSystemQueryAvailableRam(&cbHostRamAvail);
+ if (RT_SUCCESS(vrc))
+ {
+ pfnLog(pReleaseLogger, "Host RAM: %lluMB", cbHostRam / _1M);
+ if (cbHostRam > _2G)
+ pfnLog(pReleaseLogger, " (%lld.%lldGB)",
+ cbHostRam / _1G, (cbHostRam % _1G) / (_1G / 10));
+ pfnLog(pReleaseLogger, " total, %lluMB", cbHostRamAvail / _1M);
+ if (cbHostRamAvail > _2G)
+ pfnLog(pReleaseLogger, " (%lld.%lldGB)",
+ cbHostRamAvail / _1G, (cbHostRamAvail % _1G) / (_1G / 10));
+ pfnLog(pReleaseLogger, " available\n");
+ }
+
+ /* the package type is interesting for Linux distributions */
+ char szExecName[RTPATH_MAX];
+ char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName));
+ pfnLog(pReleaseLogger,
+ "Executable: %s\n"
+ "Process ID: %u\n"
+ "Package type: %s"
+#ifdef VBOX_OSE
+ " (OSE)"
+#endif
+ "\n",
+ pszExecName ? pszExecName : "unknown",
+ RTProcSelf(),
+ VBOX_PACKAGE_STRING);
+ RTLogSetBuffering(pReleaseLogger, fOldBuffered);
+ break;
+ }
+
+ case RTLOGPHASE_PREROTATE:
+ pfnLog(pReleaseLogger, "Log rotated - Log started %s\n", szTmp);
+ break;
+
+ case RTLOGPHASE_POSTROTATE:
+ pfnLog(pReleaseLogger, "Log continuation - Log started %s\n", szTmp);
+ break;
+
+ case RTLOGPHASE_END:
+ pfnLog(pReleaseLogger, "End of log file - Log started %s\n", szTmp);
+ break;
+
+ default:
+ /* nothing */;
+ }
+}
+
+int VBoxLogRelCreate(const char *pcszEntity, const char *pcszLogFile,
+ uint32_t fFlags, const char *pcszGroupSettings,
+ const char *pcszEnvVarBase, uint32_t fDestFlags,
+ uint32_t cMaxEntriesPerGroup, uint32_t cHistory,
+ uint32_t uHistoryFileTime, uint64_t uHistoryFileSize,
+ PRTERRINFO pErrInfo)
+{
+ return VBoxLogRelCreateEx(pcszEntity, pcszLogFile,
+ fFlags, pcszGroupSettings,
+ pcszEnvVarBase, fDestFlags,
+ cMaxEntriesPerGroup, cHistory,
+ uHistoryFileTime, uHistoryFileSize,
+ NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/,
+ pErrInfo);
+}
+
+int VBoxLogRelCreateEx(const char *pcszEntity, const char *pcszLogFile,
+ uint32_t fFlags, const char *pcszGroupSettings,
+ const char *pcszEnvVarBase, uint32_t fDestFlags,
+ uint32_t cMaxEntriesPerGroup, uint32_t cHistory,
+ uint32_t uHistoryFileTime, uint64_t uHistoryFileSize,
+ const void *pOutputIf, void *pvOutputIfUser,
+ PRTERRINFO pErrInfo)
+{
+ /* create release logger */
+ PRTLOGGER pReleaseLogger;
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ fFlags |= RTLOGFLAGS_USECRLF;
+#endif
+ g_pszLogEntity = pcszEntity;
+ int vrc = RTLogCreateEx(&pReleaseLogger, pcszEnvVarBase, fFlags, pcszGroupSettings, RT_ELEMENTS(s_apszGroups), s_apszGroups,
+ cMaxEntriesPerGroup, 0 /*cBufDescs*/, NULL /*paBufDescs*/, fDestFlags,
+ vboxHeaderFooter, cHistory, uHistoryFileSize, uHistoryFileTime,
+ (PCRTLOGOUTPUTIF)pOutputIf, pvOutputIfUser,
+ pErrInfo, pcszLogFile ? "%s" : NULL, pcszLogFile);
+ if (RT_SUCCESS(vrc))
+ {
+ /* explicitly flush the log, to have some info when buffering */
+ RTLogFlush(pReleaseLogger);
+
+ /* register this logger as the release logger */
+ RTLogRelSetDefaultInstance(pReleaseLogger);
+ }
+ return vrc;
+}
+
+} /* namespace com */
diff --git a/src/VBox/Main/glue/com.cpp b/src/VBox/Main/glue/com.cpp
new file mode 100644
index 00000000..26d136d2
--- /dev/null
+++ b/src/VBox/Main/glue/com.cpp
@@ -0,0 +1,187 @@
+/* $Id: com.cpp $ */
+/** @file
+ * MS COM / XPCOM Abstraction Layer
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN
+#if !defined(VBOX_WITH_XPCOM)
+
+# include <iprt/win/objbase.h>
+
+#else /* !defined (VBOX_WITH_XPCOM) */
+# include <stdlib.h>
+# include <nsCOMPtr.h>
+# include <nsIServiceManagerUtils.h>
+# include <nsIComponentManager.h>
+# include <ipcIService.h>
+# include <ipcCID.h>
+# include <ipcIDConnectService.h>
+# include <nsIInterfaceInfo.h>
+# include <nsIInterfaceInfoManager.h>
+# define IPC_DCONNECTSERVICE_CONTRACTID "@mozilla.org/ipc/dconnect-service;1" // official XPCOM headers don't define it yet
+#endif /* !defined (VBOX_WITH_XPCOM) */
+
+#include "VBox/com/com.h"
+#include "VBox/com/assert.h"
+
+#include "VBox/com/Guid.h"
+#include "VBox/com/array.h"
+
+#include <iprt/string.h>
+
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+namespace com
+{
+/* static */
+const Guid Guid::Empty; /* default ctor is OK */
+
+const char Zeroes[16] = {0, };
+
+
+void GetInterfaceNameByIID(const GUID &aIID, BSTR *aName)
+{
+ AssertPtrReturnVoid(aName);
+ *aName = NULL;
+
+#if !defined(VBOX_WITH_XPCOM)
+
+ LONG rc;
+ LPOLESTR iidStr = NULL;
+ if (StringFromIID(aIID, &iidStr) == S_OK)
+ {
+ HKEY ifaceKey;
+ rc = RegOpenKeyExW(HKEY_CLASSES_ROOT, L"Interface",
+ 0, KEY_QUERY_VALUE, &ifaceKey);
+ if (rc == ERROR_SUCCESS)
+ {
+ HKEY iidKey;
+ rc = RegOpenKeyExW(ifaceKey, iidStr, 0, KEY_QUERY_VALUE, &iidKey);
+ if (rc == ERROR_SUCCESS)
+ {
+ /* determine the size and type */
+ DWORD sz, type;
+ rc = RegQueryValueExW(iidKey, NULL, NULL, &type, NULL, &sz);
+ if (rc == ERROR_SUCCESS && type == REG_SZ)
+ {
+ /* query the value to BSTR */
+ *aName = SysAllocStringLen(NULL, (sz + 1) / sizeof(TCHAR) + 1);
+ rc = RegQueryValueExW(iidKey, NULL, NULL, NULL, (LPBYTE) *aName, &sz);
+ if (rc != ERROR_SUCCESS)
+ {
+ SysFreeString(*aName);
+ *aName = NULL;
+ }
+ }
+ RegCloseKey(iidKey);
+ }
+ RegCloseKey(ifaceKey);
+ }
+ CoTaskMemFree(iidStr);
+ }
+
+#else /* !defined (VBOX_WITH_XPCOM) */
+
+ nsresult rv;
+ nsCOMPtr<nsIInterfaceInfoManager> iim =
+ do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIInterfaceInfo> iinfo;
+ rv = iim->GetInfoForIID(&aIID, getter_AddRefs(iinfo));
+ if (NS_SUCCEEDED(rv))
+ {
+ const char *iname = NULL;
+ iinfo->GetNameShared(&iname);
+ char *utf8IName = NULL;
+ if (RT_SUCCESS(RTStrCurrentCPToUtf8(&utf8IName, iname)))
+ {
+ PRTUTF16 utf16IName = NULL;
+ if (RT_SUCCESS(RTStrToUtf16(utf8IName, &utf16IName)))
+ {
+ *aName = SysAllocString((OLECHAR *) utf16IName);
+ RTUtf16Free(utf16IName);
+ }
+ RTStrFree(utf8IName);
+ }
+ }
+ }
+
+#endif /* !defined (VBOX_WITH_XPCOM) */
+}
+
+#ifdef VBOX_WITH_XPCOM
+
+HRESULT GlueCreateObjectOnServer(const CLSID &clsid,
+ const char *serverName,
+ const nsIID &id,
+ void** ppobj)
+{
+ HRESULT rc;
+ nsCOMPtr<ipcIService> ipcServ = do_GetService(IPC_SERVICE_CONTRACTID, &rc);
+ if (SUCCEEDED(rc))
+ {
+ PRUint32 serverID = 0;
+ rc = ipcServ->ResolveClientName(serverName, &serverID);
+ if (SUCCEEDED (rc))
+ {
+ nsCOMPtr<ipcIDConnectService> dconServ = do_GetService(IPC_DCONNECTSERVICE_CONTRACTID, &rc);
+ if (SUCCEEDED(rc))
+ rc = dconServ->CreateInstance(serverID,
+ clsid,
+ id,
+ ppobj);
+ }
+ }
+ return rc;
+}
+
+HRESULT GlueCreateInstance(const CLSID &clsid,
+ const nsIID &id,
+ void** ppobj)
+{
+ nsCOMPtr<nsIComponentManager> manager;
+ HRESULT rc = NS_GetComponentManager(getter_AddRefs(manager));
+ if (SUCCEEDED(rc))
+ rc = manager->CreateInstance(clsid,
+ nsnull,
+ id,
+ ppobj);
+ return rc;
+}
+
+#endif // VBOX_WITH_XPCOM
+
+} /* namespace com */
+
diff --git a/src/VBox/Main/glue/constants-python.xsl b/src/VBox/Main/glue/constants-python.xsl
new file mode 100755
index 00000000..04d0a036
--- /dev/null
+++ b/src/VBox/Main/glue/constants-python.xsl
@@ -0,0 +1,198 @@
+<xsl:stylesheet version = '1.0'
+ xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
+ xmlns:vbox="http://www.virtualbox.org/">
+
+<!--
+ constants-python.xsl:
+ XSLT stylesheet that generates VirtualBox_constants.py from
+ VirtualBox.xidl.
+-->
+<!--
+ Copyright (C) 2009-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:output
+ method="text"
+ version="1.0"
+ encoding="utf-8"
+ indent="no"/>
+
+<xsl:param name="g_sErrHFile"/>
+
+<xsl:template match="/">
+<xsl:text># -*- coding: utf-8 -*-
+
+"""
+VirtualBox COM/XPCOM constants.
+
+This file is autogenerated from VirtualBox.xidl, DO NOT EDIT!
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2009-2022 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see &lt;https://www.gnu.org/licenses&gt;.
+
+SPDX-License-Identifier: GPL-3.0-only
+"""
+
+__version__ = "$Revision: 153229 $";
+
+
+
+class VirtualBoxReflectionInfo:
+ """
+ Enum constants for the various python styles.
+ """
+
+ def __init__(self, fIsSym):
+ self.__fIsSym = fIsSym
+
+ # iprt/err.h + VBox/err.h constants:
+ __dVBoxStatuses = {</xsl:text>
+ <xsl:value-of select="document($g_sErrHFile)"/>
+
+ <xsl:text disable-output-escaping="yes"><![CDATA[
+ }
+
+ __dValues = {]]></xsl:text>
+ <xsl:for-each select="//enum">
+ <xsl:text>
+ '</xsl:text> <xsl:value-of select="@name"/><xsl:text>': {</xsl:text>
+ <xsl:for-each select="const">
+ <xsl:text>
+ '</xsl:text>
+ <xsl:value-of select="@name"/><xsl:text>': </xsl:text>
+ <xsl:value-of select="@value"/><xsl:text>,</xsl:text>
+ </xsl:for-each>
+ <xsl:text>
+ },</xsl:text>
+ </xsl:for-each>
+ <!-- VBox status codes: -->
+ <xsl:text disable-output-escaping="yes"><![CDATA[
+ # iprt/err.h + VBox/err.h constants:
+ 'VBoxStatus': __dVBoxStatuses,
+ }
+
+ __dValuesSym = {]]></xsl:text>
+ <xsl:for-each select="//enum">
+ <xsl:text>
+ '</xsl:text> <xsl:value-of select="@name"/> <xsl:text>': {</xsl:text>
+ <xsl:for-each select="const">
+ <xsl:text>
+ '</xsl:text> <xsl:value-of select="@name"/> <xsl:text>': '</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>',</xsl:text>
+ </xsl:for-each>
+ <xsl:text>
+ },</xsl:text>
+ </xsl:for-each>
+ <!-- hack alert: force new output element to avoid large reallocations. -->
+ <xsl:text disable-output-escaping="yes"><![CDATA[
+ }
+
+ __dValuesFlat = dict({]]></xsl:text>
+ <xsl:for-each select="//enum">
+ <xsl:variable name="ename">
+ <xsl:value-of select="@name"/>
+ </xsl:variable>
+ <xsl:for-each select="const">
+ <xsl:text>
+ '</xsl:text> <xsl:value-of select="$ename"/> <xsl:text>_</xsl:text>
+ <xsl:value-of select="@name"/> <xsl:text>': </xsl:text>
+ <xsl:value-of select="@value"/><xsl:text>,</xsl:text>
+ </xsl:for-each>
+ </xsl:for-each>
+ <!-- hack alert: force new output element to avoid large reallocations. -->
+ <xsl:text disable-output-escaping="yes"><![CDATA[
+ # Result constants:]]></xsl:text>
+ <xsl:for-each select="//result[@value]">
+ <xsl:text>
+ '</xsl:text> <xsl:value-of select="@name"/> <xsl:text>': </xsl:text>
+ <xsl:value-of select="@value"/><xsl:text>,</xsl:text>
+ </xsl:for-each>
+
+ <!-- hack alert: force new output element to avoid large reallocations. -->
+ <xsl:text>
+ }, **__dVBoxStatuses)
+
+ __dValuesFlatSym = {</xsl:text>
+ <xsl:for-each select="//enum">
+ <xsl:variable name="ename">
+ <xsl:value-of select="@name"/>
+ </xsl:variable>
+ <xsl:for-each select="const">
+ <xsl:variable name="eval">
+ <xsl:value-of select="concat($ename, '_', @name)"/>
+ </xsl:variable>
+ <xsl:text>
+ '</xsl:text> <xsl:value-of select="$eval"/> <xsl:text>': </xsl:text>
+ <xsl:text>'</xsl:text> <xsl:value-of select="@name"/> <xsl:text>',</xsl:text>
+ </xsl:for-each>
+ </xsl:for-each>
+ <xsl:text>
+ # Result constants:</xsl:text>
+ <xsl:for-each select="//result[@value]">
+ <xsl:text>
+ '</xsl:text> <xsl:value-of select="@name"/> <xsl:text>': </xsl:text>
+ <xsl:text>'</xsl:text><xsl:value-of select="@name"/><xsl:text>',</xsl:text>
+ </xsl:for-each>
+ <xsl:text>
+ }
+
+ def __getattr__(self, sAttrName):
+ if self.__fIsSym:
+ oValue = self.__dValuesFlatSym.get(sAttrName)
+ else:
+ oValue = self.__dValuesFlat.get(sAttrName)
+ if oValue is None:
+ raise AttributeError
+ return oValue
+
+ def all_values(self, sEnumName):
+ """ Returns a dictionary with all the value names for a given enum type. """
+ if self.__fIsSym:
+ dValues = self.__dValuesSym.get(sEnumName)
+ else:
+ dValues = self.__dValues.get(sEnumName)
+ if dValues is None:
+ dValues = {}
+ return dValues
+
+</xsl:text>
+</xsl:template>
+</xsl:stylesheet>
+
diff --git a/src/VBox/Main/glue/errorprint.cpp b/src/VBox/Main/glue/errorprint.cpp
new file mode 100644
index 00000000..0b65f42a
--- /dev/null
+++ b/src/VBox/Main/glue/errorprint.cpp
@@ -0,0 +1,223 @@
+/* $Id: errorprint.cpp $ */
+
+/** @file
+ * MS COM / XPCOM Abstraction Layer:
+ * Error info print helpers. This implements the shared code from the macros from errorprint.h.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/com/errorprint.h>
+#include <VBox/log.h>
+
+#include <ProgressImpl.h>
+
+#include <iprt/stream.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+
+namespace com
+{
+
+void GluePrintErrorInfo(const com::ErrorInfo &info)
+{
+ bool haveResultCode = false;
+#if defined (RT_OS_WIN)
+ haveResultCode = info.isFullAvailable();
+ bool haveComponent = true;
+ bool haveInterfaceID = true;
+#else /* defined (RT_OS_WIN) */
+ haveResultCode = true;
+ bool haveComponent = info.isFullAvailable();
+ bool haveInterfaceID = info.isFullAvailable();
+#endif
+
+ try
+ {
+ HRESULT rc = S_OK;
+ Utf8Str str;
+ RTCList<Utf8Str> comp;
+
+ Bstr bstrDetailsText = info.getText();
+ if (!bstrDetailsText.isEmpty())
+ str = Utf8StrFmt("%ls\n",
+ bstrDetailsText.raw());
+ if (haveResultCode)
+ {
+ rc = info.getResultCode();
+ comp.append(Utf8StrFmt("code %Rhrc (0x%RX32)", rc, rc));
+ }
+ if (haveComponent)
+ comp.append(Utf8StrFmt("component %ls",
+ info.getComponent().raw()));
+ if (haveInterfaceID)
+ comp.append(Utf8StrFmt("interface %ls",
+ info.getInterfaceName().raw()));
+ if (!info.getCalleeName().isEmpty())
+ comp.append(Utf8StrFmt("callee %ls",
+ info.getCalleeName().raw()));
+
+ if (comp.size() > 0)
+ {
+ str += "Details: ";
+ for (size_t i = 0; i < comp.size() - 1; ++i)
+ str += comp.at(i) + ", ";
+ str += comp.last();
+ str += "\n";
+ }
+
+ // print and log
+ if (FAILED(rc))
+ {
+ RTMsgError("%s", str.c_str());
+ Log(("ERROR: %s", str.c_str()));
+ }
+ else
+ {
+ RTMsgWarning("%s", str.c_str());
+ Log(("WARNING: %s", str.c_str()));
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ RTMsgError("std::bad_alloc in GluePrintErrorInfo!");
+ Log(("ERROR: std::bad_alloc in GluePrintErrorInfo!\n"));
+ }
+}
+
+void GluePrintErrorContext(const char *pcszContext, const char *pcszSourceFile, uint32_t ulLine, bool fWarning /* = false */)
+{
+ // pcszSourceFile comes from __FILE__ macro, which always contains the full path,
+ // which we don't want to see printed:
+ // print and log
+ const char *pszFilenameOnly = RTPathFilename(pcszSourceFile);
+ if (!fWarning)
+ {
+ RTMsgError("Context: \"%s\" at line %d of file %s\n", pcszContext, ulLine, pszFilenameOnly);
+ Log(("ERROR: Context: \"%s\" at line %d of file %s\n", pcszContext, ulLine, pszFilenameOnly));
+ }
+ else
+ {
+ RTMsgWarning("Context: \"%s\" at line %d of file %s\n", pcszContext, ulLine, pszFilenameOnly);
+ Log(("WARNING: Context: \"%s\" at line %d of file %s\n", pcszContext, ulLine, pszFilenameOnly));
+ }
+}
+
+void GluePrintRCMessage(HRESULT rc)
+{
+ // print and log
+ if (FAILED(rc))
+ {
+ RTMsgError("Code %Rhra (extended info not available)\n", rc);
+ Log(("ERROR: Code %Rhra (extended info not available)\n", rc));
+ }
+ else
+ {
+ RTMsgWarning("Code %Rhra (extended info not available)\n", rc);
+ Log(("WARNING: Code %Rhra (extended info not available)\n", rc));
+ }
+}
+
+static void glueHandleComErrorInternal(com::ErrorInfo &info,
+ const char *pcszContext,
+ HRESULT rc,
+ const char *pcszSourceFile,
+ uint32_t ulLine)
+{
+ if (info.isFullAvailable() || info.isBasicAvailable())
+ {
+ const com::ErrorInfo *pInfo = &info;
+ do
+ {
+ GluePrintErrorInfo(*pInfo);
+
+ /* Use rc for figuring out if there were just warnings. */
+ HRESULT rc2 = pInfo->getResultCode();
+ if ( (SUCCEEDED_WARNING(rc) && FAILED(rc2))
+ || (SUCCEEDED(rc) && (FAILED(rc2) || SUCCEEDED_WARNING(rc2))))
+ rc = rc2;
+
+ pInfo = pInfo->getNext();
+ /* If there is more than one error, separate them visually. */
+ if (pInfo)
+ {
+ /* If there are several errors then at least basic error
+ * information must be available, otherwise something went
+ * horribly wrong. */
+ Assert(pInfo->isFullAvailable() || pInfo->isBasicAvailable());
+
+ RTMsgError("--------\n");
+ }
+ }
+ while (pInfo);
+ }
+ else
+ GluePrintRCMessage(rc);
+
+ if (pcszContext != NULL || pcszSourceFile != NULL)
+ GluePrintErrorContext(pcszContext, pcszSourceFile, ulLine, SUCCEEDED_WARNING(rc));
+}
+
+void GlueHandleComError(ComPtr<IUnknown> iface,
+ const char *pcszContext,
+ HRESULT rc,
+ const char *pcszSourceFile,
+ uint32_t ulLine)
+{
+ /* If we have full error info, print something nice, and start with the
+ * actual error message. */
+ com::ErrorInfo info(iface, COM_IIDOF(IUnknown));
+
+ glueHandleComErrorInternal(info,
+ pcszContext,
+ rc,
+ pcszSourceFile,
+ ulLine);
+
+}
+
+void GlueHandleComErrorNoCtx(ComPtr<IUnknown> iface, HRESULT rc)
+{
+ GlueHandleComError(iface, NULL, rc, NULL, 0);
+}
+
+void GlueHandleComErrorProgress(ComPtr<IProgress> progress,
+ const char *pcszContext,
+ HRESULT rc,
+ const char *pcszSourceFile,
+ uint32_t ulLine)
+{
+ /* Get the error info out of the progress object. */
+ ProgressErrorInfo ei(progress);
+
+ glueHandleComErrorInternal(ei,
+ pcszContext,
+ rc,
+ pcszSourceFile,
+ ulLine);
+}
+
+} /* namespace com */
+
diff --git a/src/VBox/Main/glue/glue-java.xsl b/src/VBox/Main/glue/glue-java.xsl
new file mode 100644
index 00000000..1f08673a
--- /dev/null
+++ b/src/VBox/Main/glue/glue-java.xsl
@@ -0,0 +1,5259 @@
+<xsl:stylesheet version = '1.0'
+ xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
+ xmlns:vbox="http://www.virtualbox.org/"
+ xmlns:exsl="http://exslt.org/common"
+ extension-element-prefixes="exsl">
+
+<!--
+ glue-java.xsl:
+ XSLT stylesheet that generates Java glue code for XPCOM, MSCOM and JAX-WS from
+ VirtualBox.xidl.
+-->
+<!--
+ Copyright (C) 2010-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:output
+ method="text"
+ version="1.0"
+ encoding="utf-8"
+ indent="no"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ global XSLT variables
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:variable name="G_xsltFilename" select="'glue-java.xsl'" />
+<xsl:variable name="G_virtualBoxPackage" select="concat('org.virtualbox', $G_vboxApiSuffix)" />
+<xsl:variable name="G_virtualBoxPackageCom" select="concat('org.virtualbox', $G_vboxApiSuffix, '.', $G_vboxGlueStyle)" />
+<xsl:variable name="G_virtualBoxWsdl" select="concat('&quot;vboxwebService', $G_vboxApiSuffix, '.wsdl&quot;')" />
+<!-- collect all interfaces with "wsmap='suppress'" in a global variable for quick lookup -->
+<xsl:variable name="G_setSuppressedInterfaces"
+ select="//interface[@wsmap='suppress']" />
+
+<xsl:include href="../idl/typemap-shared.inc.xsl" />
+
+<xsl:strip-space elements="*"/>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ Keys for more efficiently looking up of types.
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:key name="G_keyEnumsByName" match="//enum[@name]" use="@name"/>
+<xsl:key name="G_keyInterfacesByName" match="//interface[@name]" use="@name"/>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template name="fileheader">
+ <xsl:param name="name" />
+ <xsl:text>/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of a free software library; you can redistribute
+ * it and/or modify it under the terms of the GNU Lesser General
+ * Public License version 2.1 as published by the Free Software
+ * Foundation and shipped in the \"COPYING.LIB\" file with this library.
+ * The library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if
+ * any license choice other than GPL or LGPL is available it will
+ * apply instead, Oracle elects to use only the Lesser General Public
+ * License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the
+ * language indicating that LGPLv2 or any later version may be used,
+ * or where a choice of which version of the LGPL is applied is
+ * otherwise unspecified.
+
+ * http://www.virtualbox.org. This library is free software; you can
+ * redistribute it and/or modify it under the terms of the GNU Lesser General
+ * Public License as published by the Free Software Foundation, in version 2.1
+ * as it comes in the "COPYING.LIB" file of the VirtualBox SDK distribution.
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+/*
+</xsl:text>
+ <xsl:value-of select="concat(' * ', $name)"/>
+<xsl:text>
+ *
+ * DO NOT EDIT! This is a generated file.
+ * Generated from: src/VBox/Main/idl/VirtualBox.xidl (VirtualBox's interface definitions in XML)
+ * Generator: src/VBox/Main/glue/glue-java.xsl
+ */
+
+</xsl:text>
+</xsl:template>
+
+<xsl:template name="startFile">
+ <xsl:param name="file" />
+ <xsl:param name="package" />
+
+ <xsl:choose>
+ <xsl:when test="$filelistonly=''">
+ <xsl:value-of select="concat('&#10;// ##### BEGINFILE &quot;', $G_vboxDirPrefix, $file, '&quot;&#10;&#10;')" />
+ <xsl:call-template name="fileheader">
+ <xsl:with-param name="name" select="$file" />
+ </xsl:call-template>
+
+ <xsl:value-of select="concat('package ', $package, ';&#10;&#10;')" />
+ <xsl:value-of select="concat('import ', $G_virtualBoxPackageCom, '.*;&#10;')" />
+
+ <xsl:choose>
+ <xsl:when test="$G_vboxGlueStyle='xpcom'">
+ <xsl:text>import org.mozilla.interfaces.*;&#10;</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='mscom'">
+ <xsl:text>import com.jacob.com.*;&#10;</xsl:text>
+ <xsl:text>import com.jacob.activeX.ActiveXComponent;&#10;</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='jaxws'">
+ <xsl:text>import javax.xml.ws.*;&#10;</xsl:text>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'no header rule (startFile)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat(' \', $G_sNewLine, '&#9;', $G_vboxDirPrefix, $file)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="endFile">
+ <xsl:param name="file" />
+ <xsl:if test="$filelistonly=''">
+ <xsl:value-of select="concat('&#10;// ##### ENDFILE &quot;', $file, '&quot;&#10;')" />
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+ </xsl:if>
+</xsl:template>
+
+<!-- strip-and-normalize-desc
+ Removes leading and trailing white space on each line in the given text.
+ -->
+<xsl:template name="strip-and-normalize-desc">
+ <xsl:param name="text"/>
+
+ <!-- Strip the whole string first so we won't leave trailing new line chars behind. -->
+ <xsl:variable name="sStrippedText">
+ <xsl:call-template name="strip-string">
+ <xsl:with-param name="text" select="$text"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:choose>
+ <!-- If there are multiple lines, strip them one by one on a recursive fasion. -->
+ <xsl:when test="contains($sStrippedText, '&#10;')">
+ <xsl:call-template name="strip-string-right">
+ <xsl:with-param name="text" select="substring-before($sStrippedText, '&#10;')"/>
+ </xsl:call-template>
+ <xsl:value-of select="'&#10;'"/>
+ <xsl:call-template name="strip-and-normalize-desc-recursive">
+ <xsl:with-param name="text" select="substring-after($sStrippedText, '&#10;')"/>
+ </xsl:call-template>
+ </xsl:when>
+
+ <!-- Single line, we're done. -->
+ <xsl:otherwise>
+ <xsl:value-of select="$sStrippedText"/>
+ </xsl:otherwise>
+ </xsl:choose>
+
+</xsl:template>
+
+<!-- Internal worker for strip-and-normalize-desc, don't use. -->
+<xsl:template name="strip-and-normalize-desc-recursive">
+ <xsl:param name="text"/>
+
+ <xsl:choose>
+ <!-- If there are multiple lines, strip them one by one on a recursive fasion. -->
+ <xsl:when test="contains($text, '&#10;')">
+ <xsl:call-template name="strip-string">
+ <xsl:with-param name="text" select="substring-before($text, '&#10;')"/>
+ </xsl:call-template>
+ <xsl:value-of select="'&#10;'"/>
+ <xsl:call-template name="strip-and-normalize-desc-recursive">
+ <xsl:with-param name="text" select="substring-after($text, '&#10;')"/>
+ </xsl:call-template>
+ </xsl:when>
+
+ <!-- Single line: Left strip it. -->
+ <xsl:otherwise>
+ <xsl:call-template name="strip-string-left">
+ <xsl:with-param name="text" select="$text"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+</xsl:template>
+
+<!-- descriptions -->
+
+<xsl:template match="*/text()">
+ <!-- TODO: strip out @c/@a for now. long term solution is changing that to a
+ tag in the xidl file, and translate it when generating doxygen etc. -->
+ <xsl:variable name="rep1">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="haystack" select="."/>
+ <xsl:with-param name="needle" select="'@c'"/>
+ <xsl:with-param name="replacement" select="''"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="rep2">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="haystack" select="$rep1"/>
+ <xsl:with-param name="needle" select="'@a'"/>
+ <xsl:with-param name="replacement" select="''"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="rep3">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="haystack" select="$rep2"/>
+ <xsl:with-param name="needle" select="'@todo'"/>
+ <xsl:with-param name="replacement" select="'TODO'"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <!-- &amp;, &lt; and &gt; must remain as they are or javadoc 8 throws a fit. -->
+ <xsl:variable name="rep4">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="haystack" select="$rep3"/>
+ <xsl:with-param name="needle" select="'&amp;'"/>
+ <xsl:with-param name="replacement" select="'&amp;amp;'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="rep5">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="haystack" select="$rep4"/>
+ <xsl:with-param name="needle" select="'&lt;'"/>
+ <xsl:with-param name="replacement" select="'&amp;lt;'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="rep6">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="haystack" select="$rep5"/>
+ <xsl:with-param name="needle" select="'&gt;'"/>
+ <xsl:with-param name="replacement" select="'&amp;gt;'"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="rep7">
+ <xsl:call-template name="strip-and-normalize-desc">
+ <xsl:with-param name="text" select="$rep6"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:value-of select="$rep7"/>
+</xsl:template>
+
+<!--
+ * all sub-elements that are not explicitly matched are considered to be
+ * html tags and copied w/o modifications
+-->
+<xsl:template match="desc//*">
+ <xsl:variable name="tagname" select="local-name()"/>
+ <xsl:choose>
+ <xsl:when test="$tagname = 'tt'">
+ <xsl:text>&lt;code&gt;</xsl:text>
+ </xsl:when>
+ <xsl:when test="$tagname = 'h3'">
+ <xsl:text>&lt;h2&gt;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('&lt;', $tagname, '&gt;')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:apply-templates/>
+ <xsl:choose>
+ <xsl:when test="$tagname = 'tt'">
+ <xsl:text>&lt;/code&gt;</xsl:text>
+ </xsl:when>
+ <xsl:when test="$tagname = 'h3'">
+ <xsl:text>&lt;/h2&gt;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('&lt;/', $tagname, '&gt;')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="emit_refsig">
+ <xsl:param name="context"/>
+ <xsl:param name="identifier"/>
+
+ <xsl:choose>
+ <xsl:when test="key('G_keyEnumsByName', $context)/const[@name=$identifier]">
+ <xsl:value-of select="$identifier"/>
+ </xsl:when>
+ <xsl:when test="key('G_keyInterfacesByName', $context)/method[@name=$identifier]">
+ <xsl:value-of select="$identifier"/>
+ <xsl:text>(</xsl:text>
+ <xsl:for-each select="key('G_keyInterfacesByName', $context)/method[@name=$identifier]/param">
+ <xsl:if test="@dir!='return'">
+ <xsl:if test="position() > 1">
+ <xsl:text>,</xsl:text>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="@dir='out'">
+ <xsl:text>Holder</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="@type"/>
+ <xsl:with-param name="safearray" select="@safearray"/>
+ <xsl:with-param name="skiplisttype" select="'yes'"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>)</xsl:text>
+ </xsl:when>
+ <xsl:when test="key('G_keyInterfacesByName', $context)/attribute[@name=$identifier]">
+ <xsl:call-template name="makeGetterName">
+ <xsl:with-param name="attrname" select="$identifier" />
+ </xsl:call-template>
+ <xsl:text>()</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('unknown reference destination in @see/@link: context=', $context, ' identifier=', $identifier)" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+ * link
+-->
+<xsl:template match="desc//link">
+ <xsl:text>{@link </xsl:text>
+ <xsl:apply-templates select="." mode="middle"/>
+ <xsl:text>}</xsl:text>
+</xsl:template>
+
+<xsl:template match="link" mode="middle">
+ <xsl:variable name="linktext">
+ <xsl:call-template name="string-replace-first">
+ <xsl:with-param name="haystack" select="@to"/>
+ <xsl:with-param name="needle" select="'_'"/>
+ <xsl:with-param name="replacement" select="'#'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="substring($linktext, 1, 1)='#'">
+ <xsl:variable name="context">
+ <xsl:choose>
+ <xsl:when test="local-name(../..)='interface' or local-name(../..)='enum'">
+ <xsl:value-of select="../../@name"/>
+ </xsl:when>
+ <xsl:when test="local-name(../../..)='interface' or local-name(../../..)='enum'">
+ <xsl:value-of select="../../../@name"/>
+ </xsl:when>
+ <xsl:when test="local-name(../../../..)='interface' or local-name(../../../..)='enum'">
+ <xsl:value-of select="../../../../@name"/>
+ </xsl:when>
+ <xsl:when test="local-name(../../../../..)='interface' or local-name(../../../../..)='enum'">
+ <xsl:value-of select="../../../../../@name"/>
+ </xsl:when>
+ <xsl:when test="local-name(../../../../../..)='interface' or local-name(../../../../../..)='enum'">
+ <xsl:value-of select="../../../../../../@name"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('cannot determine context for identifier ', $linktext)" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="linkname">
+ <xsl:value-of select="substring($linktext, 2)"/>
+ </xsl:variable>
+ <xsl:text>#</xsl:text>
+ <xsl:call-template name="emit_refsig">
+ <xsl:with-param name="context" select="$context"/>
+ <xsl:with-param name="identifier" select="$linkname"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="contains($linktext, '::')">
+ <xsl:variable name="context">
+ <xsl:value-of select="substring-before($linktext, '::')"/>
+ </xsl:variable>
+ <xsl:variable name="linkname">
+ <xsl:value-of select="substring-after($linktext, '::')"/>
+ </xsl:variable>
+ <xsl:value-of select="concat($G_virtualBoxPackage, '.', $context, '#')"/>
+ <xsl:call-template name="emit_refsig">
+ <xsl:with-param name="context" select="$context"/>
+ <xsl:with-param name="identifier" select="$linkname"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat($G_virtualBoxPackage, '.', $linktext)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+<!--
+ * note
+-->
+<xsl:template match="desc/note">
+ <xsl:if test="not(@internal='yes')">
+ <xsl:text>&#10;NOTE: </xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+<!--
+ * see
+-->
+<xsl:template match="desc/see">
+ <!-- TODO: quirk in our xidl file: only one <see> tag with <link> nested
+ into it, translate this to multiple @see lines and strip the rest.
+ Should be replaced in the xidl by multiple <see> without nested tag -->
+ <xsl:text>&#10;</xsl:text>
+ <xsl:apply-templates match="link"/>
+</xsl:template>
+
+<xsl:template match="desc/see/text()"/>
+
+<xsl:template match="desc/see/link">
+ <xsl:text>@see </xsl:text>
+ <xsl:apply-templates select="." mode="middle"/>
+ <xsl:text>&#10;</xsl:text>
+</xsl:template>
+
+<!--
+ * common comment prologue (handles group IDs)
+-->
+<xsl:template match="desc" mode="begin">
+ <!-- TODO,XXX: This is a hot spot. The whole $id crap isn't working though,
+ so it's been disabled to save precious time. -->
+<!--
+ <xsl:param name="id" select="@group | preceding::descGroup[1]/@id"/>
+ <xsl:text>&#10;/**&#10;</xsl:text>
+ <xsl:if test="$id">
+ <xsl:value-of select="concat(' @ingroup ', $id, '&#10;')"/>
+ </xsl:if>
+-->
+ <xsl:value-of select="concat($G_sNewLine, '/**', $G_sNewLine)"/>
+</xsl:template>
+
+<!--
+ * common middle part of the comment block
+-->
+<xsl:template match="desc" mode="middle">
+ <xsl:apply-templates select="text() | *[not(self::note or self::see)]"/>
+ <xsl:apply-templates select="note"/>
+ <xsl:apply-templates select="see"/>
+</xsl:template>
+
+<!--
+ * result part of the comment block
+-->
+<xsl:template match="desc" mode="results">
+ <xsl:if test="result">
+ <xsl:text>&#10;&lt;p&gt;&lt;/p&gt;&lt;dl&gt;&lt;dt&gt;&lt;b&gt;Expected result codes:&lt;/b&gt;&lt;/dt&gt;&#10;</xsl:text>
+ <xsl:for-each select="result">
+ <xsl:text>&lt;dd&gt;&lt;code&gt;</xsl:text>
+ <xsl:choose>
+ <xsl:when test="ancestor::library/result[@name=current()/@name]">
+ <xsl:value-of select="concat('@link ::', @name, ' ', @name)"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>&lt;/code&gt; - </xsl:text>
+ <xsl:apply-templates select="text() | *[not(self::note or self::see or
+ self::result)]"/>
+ <xsl:text>&lt;/dd&gt;&#10;</xsl:text>
+ </xsl:for-each>
+ <xsl:text>&lt;/dl&gt;&#10;</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+<!--
+ * comment for interfaces
+-->
+<xsl:template match="desc" mode="interface">
+ <xsl:apply-templates select="." mode="begin"/>
+ <xsl:apply-templates select="." mode="middle"/>
+ <xsl:text>&#10;&#10;Interface ID: &lt;code&gt;{</xsl:text>
+ <xsl:call-template name="string-to-upper">
+ <xsl:with-param name="str" select="../@uuid"/>
+ </xsl:call-template>
+ <xsl:text>}&lt;/code&gt;&#10;*/&#10;</xsl:text>
+</xsl:template>
+
+<!--
+ * comment for attribute getters
+-->
+<xsl:template match="desc" mode="attribute_get">
+ <xsl:apply-templates select="." mode="begin"/>
+ <xsl:apply-templates select="text() | *[not(self::note or self::see or self::result)]"/>
+ <xsl:apply-templates select="." mode="results"/>
+ <xsl:apply-templates select="note"/>
+ <xsl:text>&#10;@return </xsl:text>
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="../@type"/>
+ <xsl:with-param name="safearray" select="../@safearray"/>
+ <xsl:with-param name="doubleescape">yes</xsl:with-param>
+ </xsl:call-template>
+ <xsl:text>&#10;</xsl:text>
+ <xsl:apply-templates select="see"/>
+ <xsl:text>*/&#10;</xsl:text>
+</xsl:template>
+
+<!--
+ * comment for attribute setters
+-->
+<xsl:template match="desc" mode="attribute_set">
+ <xsl:apply-templates select="." mode="begin"/>
+ <xsl:apply-templates select="text() | *[not(self::note or self::see or self::result)]"/>
+ <xsl:apply-templates select="." mode="results"/>
+ <xsl:apply-templates select="note"/>
+ <xsl:text>&#10;@param value </xsl:text>
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="../@type"/>
+ <xsl:with-param name="safearray" select="../@safearray"/>
+ <xsl:with-param name="doubleescape">yes</xsl:with-param>
+ </xsl:call-template>
+ <xsl:text>&#10;</xsl:text>
+ <xsl:apply-templates select="see"/>
+ <xsl:text>&#10;*/&#10;</xsl:text>
+</xsl:template>
+
+<!--
+ * comment for methods
+-->
+<xsl:template match="desc" mode="method">
+ <xsl:apply-templates select="." mode="begin"/>
+ <xsl:apply-templates select="text() | *[not(self::note or self::see or self::result)]"/>
+ <xsl:apply-templates select="." mode="results"/>
+ <xsl:for-each select="../param">
+ <xsl:apply-templates select="desc"/>
+ </xsl:for-each>
+ <xsl:apply-templates select="note"/>
+ <xsl:apply-templates select="../param/desc/note"/>
+ <xsl:apply-templates select="see"/>
+ <xsl:text>&#10;*/&#10;</xsl:text>
+</xsl:template>
+
+<!--
+ * comment for method parameters
+-->
+<xsl:template match="method/param/desc">
+ <xsl:if test="text() | *[not(self::note or self::see)]">
+ <xsl:choose>
+ <xsl:when test="../@dir='return'">
+ <xsl:text>&#10;@return </xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>&#10;@param </xsl:text>
+ <xsl:value-of select="../@name"/>
+ <xsl:text> </xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:apply-templates select="text() | *[not(self::note or self::see)]"/>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+<!--
+ * comment for enums
+-->
+<xsl:template match="desc" mode="enum">
+ <xsl:apply-templates select="." mode="begin"/>
+ <xsl:apply-templates select="." mode="middle"/>
+ <xsl:text>&#10;Interface ID: &lt;code&gt;{</xsl:text>
+ <xsl:call-template name="string-to-upper">
+ <xsl:with-param name="str" select="../@uuid"/>
+ </xsl:call-template>
+ <xsl:text>}&lt;/code&gt;&#10;*/&#10;</xsl:text>
+</xsl:template>
+
+<!--
+ * comment for enum values
+-->
+<xsl:template match="desc" mode="enum_const">
+ <xsl:apply-templates select="." mode="begin"/>
+ <xsl:apply-templates select="." mode="middle"/>
+ <xsl:text>&#10;*/&#10;</xsl:text>
+</xsl:template>
+
+<!--
+ * ignore descGroups by default (processed in /idl)
+-->
+<xsl:template match="descGroup"/>
+
+
+
+<!-- actual code generation -->
+
+<xsl:template name="genEnum">
+ <xsl:param name="enumname" />
+ <xsl:param name="filename" />
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="$filename" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:apply-templates select="desc" mode="enum"/>
+ <xsl:value-of select="concat('public enum ', $enumname, '&#10;')" />
+ <xsl:text>{&#10;</xsl:text>
+ <xsl:for-each select="const">
+ <xsl:apply-templates select="desc" mode="enum_const"/>
+ <xsl:variable name="enumconst" select="@name" />
+ <xsl:value-of select="concat(' ', $enumconst, '(', @value, ')')" />
+ <xsl:choose>
+ <xsl:when test="not(position()=last())">
+ <xsl:text>,&#10;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>;&#10;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+
+ <xsl:text>&#10;</xsl:text>
+ <xsl:text> private final int value;&#10;&#10;</xsl:text>
+
+ <xsl:value-of select="concat(' ', $enumname, '(int v)&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> value = v;&#10;</xsl:text>
+ <xsl:text> }&#10;&#10;</xsl:text>
+
+ <xsl:text> public int value()&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> return value;&#10;</xsl:text>
+ <xsl:text> }&#10;&#10;</xsl:text>
+
+ <xsl:value-of select="concat(' public static ', $enumname, ' fromValue(long v)&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:value-of select="concat(' for (', $enumname, ' c: ', $enumname, '.values())&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> if (c.value == (int)v)&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> return c;&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:text> throw new IllegalArgumentException(Long.toString(v));&#10;</xsl:text>
+ <xsl:text> }&#10;&#10;</xsl:text>
+
+ <xsl:value-of select="concat(' public static ', $enumname, ' fromValue(String v)&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:value-of select="concat(' return valueOf(', $enumname, '.class, v);&#10;')" />
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:text>}&#10;&#10;</xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="$filename" />
+ </xsl:call-template>
+
+</xsl:template>
+
+<xsl:template name="startExcWrapper">
+ <xsl:param name="preventObjRelease" />
+
+ <xsl:if test="$G_vboxGlueStyle='jaxws' and $preventObjRelease">
+ <xsl:text> this.getObjMgr().preventObjRelease();&#10;</xsl:text>
+ </xsl:if>
+ <xsl:text> try&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+</xsl:template>
+
+<xsl:template name="endExcWrapper">
+ <xsl:param name="allowObjRelease" />
+
+ <xsl:choose>
+ <xsl:when test="$G_vboxGlueStyle='xpcom'">
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:text> catch (org.mozilla.xpcom.XPCOMException e)&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> throw new VBoxException(e.getMessage(), e);&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='mscom'">
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:text> catch (com.jacob.com.ComException e)&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> throw new VBoxException(e.getMessage(), e);&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='jaxws'">
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:text> catch (InvalidObjectFaultMsg e)&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> throw new VBoxException(e.getMessage(), e, this.getObjMgr(), this.port);&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:text> catch (RuntimeFaultMsg e)&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> throw new VBoxException(e.getMessage(), e, this.getObjMgr(), this.port);&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:if test="$allowObjRelease">
+ <xsl:text> finally&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> getObjMgr().allowObjRelease();&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ </xsl:if>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'no header rule (startFile)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="wrappedName">
+ <xsl:param name="ifname" />
+
+ <xsl:choose>
+ <xsl:when test="$G_vboxGlueStyle='xpcom'">
+ <xsl:value-of select="concat('org.mozilla.interfaces.', $ifname)" />
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='mscom'">
+ <xsl:text>com.jacob.com.Dispatch</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='jaxws'">
+ <xsl:text>String</xsl:text>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'no wrapper naming rule defined (wrappedName)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="fullClassName">
+ <xsl:param name="name" />
+ <xsl:param name="origname" />
+ <xsl:param name="collPrefix" />
+
+ <xsl:choose>
+ <xsl:when test="(count(key('G_keyEnumsByName', $name)) > 0) or (count(key('G_keyEnumsByName', $origname)) > 0)">
+ <xsl:value-of select="concat($G_virtualBoxPackage, concat('.', $name))" />
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $name)) > 0">
+ <xsl:value-of select="concat($G_virtualBoxPackage, concat('.', $name))" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('fullClassName: Type &quot;', $name, '&quot; is not supported.')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="typeIdl2Glue">
+ <xsl:param name="type" />
+ <xsl:param name="safearray" />
+ <xsl:param name="forceelem" />
+ <xsl:param name="skiplisttype" />
+ <xsl:param name="doubleescape" />
+
+ <xsl:variable name="needarray" select="($safearray='yes') and not($forceelem='yes')" />
+ <xsl:variable name="needlist" select="($needarray) and not($type='octet')" />
+
+ <xsl:if test="($needlist)">
+ <xsl:text>List</xsl:text>
+ <xsl:if test="not($skiplisttype='yes')">
+ <xsl:choose>
+ <xsl:when test="$doubleescape='yes'">
+ <xsl:text>&amp;lt;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>&lt;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:if>
+ </xsl:if>
+
+ <xsl:if test="not($needlist) or not($skiplisttype='yes')">
+ <!-- look up Java type from IDL type from table array in typemap-shared.inc.xsl -->
+ <xsl:variable name="javatypefield" select="exsl:node-set($G_aSharedTypes)/type[@idlname=$type]/@javaname" />
+
+ <xsl:choose>
+ <xsl:when test="string-length($javatypefield)">
+ <xsl:value-of select="$javatypefield" />
+ </xsl:when>
+ <!-- not a standard type: then it better be one of the types defined in the XIDL -->
+ <xsl:when test="$type='$unknown'">IUnknown</xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fullClassName">
+ <xsl:with-param name="name" select="$type" />
+ <xsl:with-param name="collPrefix" select="''"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="($needlist)">
+ <xsl:if test="not($skiplisttype='yes')">
+ <xsl:choose>
+ <xsl:when test="$doubleescape='yes'">
+ <xsl:text>&amp;gt;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>&gt;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:if>
+ </xsl:when>
+ <xsl:when test="($needarray)">
+ <xsl:text>[]</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+ typeIdl2Back: converts $type into a type as used by the backend.
+ -->
+<xsl:template name="typeIdl2Back">
+ <xsl:param name="type" />
+ <xsl:param name="safearray" />
+ <xsl:param name="forceelem" />
+
+ <xsl:choose>
+ <xsl:when test="($G_vboxGlueStyle='xpcom')">
+ <xsl:variable name="needarray" select="($safearray='yes') and not($forceelem='yes')" />
+
+ <xsl:choose>
+ <xsl:when test="$type='long long'">
+ <xsl:text>long</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='unsigned long'">
+ <xsl:text>long</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='long'">
+ <xsl:text>int</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='unsigned short'">
+ <xsl:text>int</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='short'">
+ <xsl:text>short</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='octet'">
+ <xsl:text>byte</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='boolean'">
+ <xsl:text>boolean</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='$unknown'">
+ <xsl:text>nsISupports</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='wstring'">
+ <xsl:text>String</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='uuid'">
+ <xsl:text>String</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="key('G_keyInterfacesByName', $type)/@wsmap='struct'">
+ <xsl:call-template name="wrappedName">
+ <xsl:with-param name="ifname" select="$type" />
+ </xsl:call-template>
+ </xsl:when>
+
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:call-template name="wrappedName">
+ <xsl:with-param name="ifname" select="$type" />
+ </xsl:call-template>
+ </xsl:when>
+
+ <xsl:when test="count(key('G_keyEnumsByName', $type)) > 0">
+ <xsl:text>long</xsl:text>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fullClassName">
+ <xsl:with-param name="name" select="$type" />
+ </xsl:call-template>
+ </xsl:otherwise>
+
+ </xsl:choose>
+ <xsl:if test="$needarray">
+ <xsl:text>[]</xsl:text>
+ </xsl:if>
+ </xsl:when>
+
+ <xsl:when test="($G_vboxGlueStyle='mscom')">
+ <xsl:text>Variant</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="($G_vboxGlueStyle='jaxws')">
+ <xsl:variable name="needarray" select="($safearray='yes' and not($type='octet')) and not($forceelem='yes')" />
+
+ <xsl:if test="$needarray">
+ <xsl:text>List&lt;</xsl:text>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="$type='$unknown'">
+ <xsl:text>String</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="key('G_keyInterfacesByName', $type)/@wsmap='managed'">
+ <xsl:text>String</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="key('G_keyInterfacesByName', $type)/@wsmap='struct'">
+ <xsl:value-of select="concat($G_virtualBoxPackageCom, '.', $type)" />
+ </xsl:when>
+
+ <xsl:when test="count(key('G_keyEnumsByName', $type)) > 0">
+ <xsl:value-of select="concat($G_virtualBoxPackageCom, '.', $type)" />
+ </xsl:when>
+
+ <!-- we encode byte arrays as Base64 strings. -->
+ <xsl:when test="$type='octet'">
+ <xsl:text>/*base64*/String</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='long long'">
+ <xsl:text>Long</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='unsigned long'">
+ <xsl:text>Long</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='long'">
+ <xsl:text>Integer</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='unsigned short'">
+ <xsl:text>Integer</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='short'">
+ <xsl:text>Short</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='boolean'">
+ <xsl:text>Boolean</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='wstring'">
+ <xsl:text>String</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='uuid'">
+ <xsl:text>String</xsl:text>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('Unhandled type ', $type, ' (typeIdl2Back)')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+
+ </xsl:choose>
+
+ <xsl:if test="$needarray">
+ <xsl:text>&gt;</xsl:text>
+ </xsl:if>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'Write typeIdl2Back for this style (typeIdl2Back)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="cookOutParamXpcom">
+ <xsl:param name="value"/>
+ <xsl:param name="idltype"/>
+ <xsl:param name="safearray"/>
+ <xsl:variable name="isstruct"
+ select="key('G_keyInterfacesByName', $idltype)/@wsmap='struct'" />
+
+ <xsl:variable name="gluetype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="elemgluetype">
+ <xsl:if test="$safearray='yes'">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="'no'" />
+ <xsl:with-param name="forceelem" select="'yes'" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$idltype = '$unknown' or (count(key('G_keyInterfacesByName', $idltype)) > 0)">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:variable name="elembacktype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ <xsl:with-param name="forceelem" select="'yes'" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat('Helper.wrap2(', $elemgluetype, '.class, ', $elembacktype, '.class, ', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('(', $value, ' != null) ? new ', $gluetype, '(', $value, ') : null')" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="count(key('G_keyEnumsByName', $idltype)) > 0">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:variable name="elembacktype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ <xsl:with-param name="forceelem" select="'yes'" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat('Helper.wrapEnum(', $elemgluetype, '.class, ', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat($gluetype, '.fromValue(', $value, ')')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="($safearray='yes') and ($idltype='octet')">
+ <xsl:value-of select="$value"/>
+ </xsl:when>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('Helper.wrap(', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$value"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="cookOutParamMscom">
+ <xsl:param name="value"/>
+ <xsl:param name="idltype"/>
+ <xsl:param name="safearray"/>
+
+ <xsl:variable name="gluetype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:variable name="elemgluetype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="'no'" />
+ <xsl:with-param name="forceelem" select="'yes'" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="($idltype='octet')">
+ <xsl:value-of select="concat('Helper.wrapBytes(', $value, '.toSafeArray())')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('Helper.wrap(', $elemgluetype, '.class, ', $value, '.toSafeArray())')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="$idltype = '$unknown' or (count(key('G_keyInterfacesByName', $idltype)) > 0)">
+ <xsl:value-of select="concat('Helper.wrapDispatch(', $gluetype, '.class, ', $value, '.getDispatch())')"/>
+ </xsl:when>
+
+ <xsl:when test="count(key('G_keyEnumsByName', $idltype)) > 0">
+ <xsl:value-of select="concat($gluetype, '.fromValue(', $value, '.getInt())')"/>
+ </xsl:when>
+
+ <xsl:when test="$idltype='wstring'">
+ <xsl:value-of select="concat($value, '.getString()')"/>
+ </xsl:when>
+
+ <xsl:when test="$idltype='uuid'">
+ <xsl:value-of select="concat($value, '.getString()')"/>
+ </xsl:when>
+
+ <xsl:when test="$idltype='boolean'">
+ <xsl:value-of select="concat($value, '.toBoolean()')"/>
+ </xsl:when>
+
+ <xsl:when test="$idltype='unsigned short'">
+ <xsl:value-of select="concat('(int)', $value, '.getShort()')"/>
+ </xsl:when>
+
+ <xsl:when test="$idltype='short'">
+ <xsl:value-of select="concat($value, '.getShort()')"/>
+ </xsl:when>
+
+ <xsl:when test="$idltype='long'">
+ <xsl:value-of select="concat($value, '.getInt()')"/>
+ </xsl:when>
+
+
+ <xsl:when test="$idltype='unsigned long'">
+ <xsl:value-of select="concat('(long)', $value, '.getInt()')"/>
+ </xsl:when>
+
+ <xsl:when test="$idltype='long'">
+ <xsl:value-of select="concat($value, '.getInt()')"/>
+ </xsl:when>
+
+ <xsl:when test="$idltype='long long'">
+ <xsl:value-of select="concat($value, '.getLong()')"/>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('Unhandled type' , $idltype, ' (cookOutParamMscom)')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+</xsl:template>
+
+<xsl:template name="cookOutParamJaxws">
+ <xsl:param name="value"/>
+ <xsl:param name="idltype"/>
+ <xsl:param name="safearray"/>
+
+ <xsl:variable name="isstruct"
+ select="key('G_keyInterfacesByName', $idltype)/@wsmap='struct'" />
+
+ <xsl:variable name="gluetype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:variable name="elemgluetype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="''" />
+ <xsl:with-param name="forceelem" select="'yes'" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="elembacktype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="''" />
+ <xsl:with-param name="forceelem" select="'yes'" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="$isstruct">
+ <xsl:value-of select="concat('Helper.wrap2(', $elemgluetype, '.class, ', $elembacktype, '.class, objMgr, port, ', $value, ')')"/>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyEnumsByName', $idltype)) > 0">
+ <xsl:value-of select="concat('Helper.convertEnums(', $elembacktype, '.class, ', $elemgluetype, '.class, ', $value, ')')"/>
+ </xsl:when>
+ <xsl:when test="$idltype = '$unknown' or (count(key('G_keyInterfacesByName', $idltype)) > 0)">
+ <xsl:value-of select="concat('Helper.wrap(', $elemgluetype, '.class, getObjMgr(), port, ', $value, ')')"/>
+ </xsl:when>
+ <xsl:when test="$idltype='octet'">
+ <xsl:value-of select="concat('Helper.decodeBase64(', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$value" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="count(key('G_keyEnumsByName', $idltype)) > 0">
+ <xsl:value-of select="concat($gluetype, '.fromValue(', $value, '.value())')"/>
+ </xsl:when>
+ <xsl:when test="$idltype='boolean'">
+ <xsl:value-of select="$value"/>
+ </xsl:when>
+ <xsl:when test="$idltype='long long'">
+ <xsl:value-of select="$value"/>
+ </xsl:when>
+ <xsl:when test="$idltype='unsigned long long'">
+ <xsl:value-of select="$value"/>
+ </xsl:when>
+ <xsl:when test="$idltype='long'">
+ <xsl:value-of select="$value"/>
+ </xsl:when>
+ <xsl:when test="$idltype='unsigned long'">
+ <xsl:value-of select="$value"/>
+ </xsl:when>
+ <xsl:when test="$idltype='short'">
+ <xsl:value-of select="$value"/>
+ </xsl:when>
+ <xsl:when test="$idltype='unsigned short'">
+ <xsl:value-of select="$value"/>
+ </xsl:when>
+ <xsl:when test="$idltype='wstring'">
+ <xsl:value-of select="$value"/>
+ </xsl:when>
+ <xsl:when test="$idltype='uuid'">
+ <xsl:value-of select="$value"/>
+ </xsl:when>
+ <xsl:when test="$isstruct">
+ <xsl:value-of select="concat('(', $value, ' != null) ? new ', $gluetype, '(', $value, ', getObjMgr(), port) : null')" />
+ </xsl:when>
+ <xsl:when test="$idltype = '$unknown' or (count(key('G_keyInterfacesByName', $idltype)) > 0)">
+ <!-- if the MOR string is empty, that means NULL, so return NULL instead of an object then -->
+ <xsl:value-of select="concat('(', $value, '.length() > 0) ? new ', $gluetype, '(', $value, ', getObjMgr(), port) : null')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('Unhandled type ', $idltype, ' (cookOutParamJaxws)')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+
+</xsl:template>
+
+<xsl:template name="cookOutParam">
+ <xsl:param name="value"/>
+ <xsl:param name="idltype"/>
+ <xsl:param name="safearray"/>
+ <xsl:choose>
+ <xsl:when test="($G_vboxGlueStyle='xpcom')">
+ <xsl:call-template name="cookOutParamXpcom">
+ <xsl:with-param name="value" select="$value" />
+ <xsl:with-param name="idltype" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="($G_vboxGlueStyle='mscom')">
+ <xsl:call-template name="cookOutParamMscom">
+ <xsl:with-param name="value" select="$value" />
+ <xsl:with-param name="idltype" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="($G_vboxGlueStyle='jaxws')">
+ <xsl:call-template name="cookOutParamJaxws">
+ <xsl:with-param name="value" select="$value" />
+ <xsl:with-param name="idltype" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'Unhandled style(cookOutParam)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="cookInParamXpcom">
+ <xsl:param name="value"/>
+ <xsl:param name="idltype"/>
+ <xsl:param name="safearray"/>
+ <xsl:variable name="isstruct"
+ select="key('G_keyInterfacesByName', $idltype)/@wsmap='struct'" />
+ <xsl:variable name="gluetype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="backtype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="elemgluetype">
+ <xsl:if test="$safearray='yes'">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="'no'" />
+ <xsl:with-param name="forceelem" select="'yes'" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="count(key('G_keyInterfacesByName', $idltype)) > 0">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:variable name="elembacktype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ <xsl:with-param name="forceelem" select="'yes'" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat('Helper.unwrap2(', $elemgluetype, '.class, ', $elembacktype, '.class, ', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('(', $value, ' != null) ? ', $value, '.getTypedWrapped() : null')" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="$idltype='$unknown'">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('Helper.unwrap2(', $elemgluetype, '.class, nsISupports.class, ', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('(', $value, ' != null) ? (nsISupports)', $value, '.getWrapped() : null')" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="count(key('G_keyEnumsByName', $idltype)) > 0">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('Helper.unwrapEnum(', $elemgluetype, '.class, ', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat($value, '.value()')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="($idltype='octet') and ($safearray='yes')">
+ <xsl:value-of select="$value"/>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:choose>
+ <xsl:when test="$idltype='boolean'">
+ <xsl:value-of select="concat('Helper.unwrapBoolean(', $value, ')')"/>
+ </xsl:when>
+ <xsl:when test="($idltype='long') or ($idltype='unsigned long') or ($idltype='integer')">
+ <xsl:value-of select="concat('Helper.unwrapInteger(', $value, ')')"/>
+ </xsl:when>
+ <xsl:when test="($idltype='short') or ($idltype='unsigned short')">
+ <xsl:value-of select="concat('Helper.unwrapUShort(', $value, ')')"/>
+ </xsl:when>
+ <xsl:when test="($idltype='unsigned long long') or ($idltype='long long')">
+ <xsl:value-of select="concat('Helper.unwrapULong(', $value, ')')"/>
+ </xsl:when>
+ <xsl:when test="($idltype='wstring') or ($idltype='uuid')">
+ <xsl:value-of select="concat('Helper.unwrapStr(', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$value"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$value"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="cookInParamMscom">
+ <xsl:param name="value"/>
+ <xsl:param name="idltype"/>
+ <xsl:param name="safearray"/>
+
+ <xsl:variable name="gluetype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="backtype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="elemgluetype">
+ <xsl:if test="$safearray='yes'">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="'no'" />
+ <xsl:with-param name="forceelem" select="'yes'" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="count(key('G_keyInterfacesByName', $idltype)) > 0">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:variable name="elembacktype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ <xsl:with-param name="forceelem" select="'yes'" />
+ </xsl:call-template>
+ </xsl:variable>
+ <!-- Sometimes javac needs a boost of self-confidence regarding
+ varargs calls, and this (Object) cast makes sure that it calls
+ the varargs method - as if there is any other. -->
+ <xsl:value-of select="concat('(Object)Helper.unwrap2(', $elemgluetype, '.class, ', $elembacktype, '.class, ', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('(', $value, ' != null) ? ', $value, '.getTypedWrapped() : null')" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="$idltype='$unknown'">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('Helper.unwrap2(', $elemgluetype, '.class, Dispatch.class, ', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('(', $value, ' != null) ? (Dispatch)', $value, '.getWrapped() : null')" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="count(key('G_keyEnumsByName', $idltype)) > 0">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('Helper.unwrapEnum(', $elemgluetype, '.class, ', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat($value, '.value()')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="$idltype='boolean'">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('Helper.unwrapBool(', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('new Variant(', $value, ')')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="($idltype='short') or ($idltype='unsigned short')">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('Helper.unwrapShort(', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('new Variant(', $value, ')')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+
+ <xsl:when test="($idltype='long') or ($idltype='unsigned long')">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('Helper.unwrapInt(', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('new Variant(', $value, ')')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="($idltype='wstring') or ($idltype='uuid')">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('Helper.unwrapString(', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('new Variant(', $value, ')')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="($idltype='unsigned long long') or ($idltype='long long')">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('Helper.unwrapLong(', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('new Variant(', $value, '.longValue())')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="($idltype='octet') and ($safearray='yes')">
+ <xsl:value-of select="$value"/>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('Unhandled type: ', $idltype)" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+</xsl:template>
+
+<xsl:template name="cookInParamJaxws">
+ <xsl:param name="value"/>
+ <xsl:param name="idltype"/>
+ <xsl:param name="safearray"/>
+ <xsl:variable name="isstruct"
+ select="key('G_keyInterfacesByName', $idltype)/@wsmap='struct'" />
+
+ <xsl:variable name="gluetype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="elemgluetype">
+ <xsl:if test="$safearray='yes'">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="'no'" />
+ <xsl:with-param name="forceelem" select="'yes'" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$idltype = '$unknown' or (count(key('G_keyInterfacesByName', $idltype)) > 0)">
+ <xsl:choose>
+ <xsl:when test="@safearray='yes'">
+ <xsl:value-of select="concat('Helper.unwrap(', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('((', $value, ' == null) ? null :', $value, '.getWrapped())')" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="count(key('G_keyEnumsByName', $idltype)) > 0">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:variable name="elembacktype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="'no'" />
+ <xsl:with-param name="forceelem" select="'yes'" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat('Helper.convertEnums(', $elemgluetype, '.class, ', $elembacktype, '.class, ', $value, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:variable name="backtype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$idltype" />
+ <xsl:with-param name="safearray" select="'no'" />
+ <xsl:with-param name="forceelem" select="'yes'" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat($backtype, '.fromValue(', $value, '.name())')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="($idltype='octet') and ($safearray='yes')">
+ <xsl:value-of select="concat('Helper.encodeBase64(', $value, ')')"/>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:value-of select="$value"/>
+ </xsl:otherwise>
+ </xsl:choose>
+
+</xsl:template>
+
+<xsl:template name="cookInParam">
+ <xsl:param name="value"/>
+ <xsl:param name="idltype"/>
+ <xsl:param name="safearray"/>
+ <xsl:choose>
+ <xsl:when test="($G_vboxGlueStyle='xpcom')">
+ <xsl:call-template name="cookInParamXpcom">
+ <xsl:with-param name="value" select="$value" />
+ <xsl:with-param name="idltype" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="($G_vboxGlueStyle='mscom')">
+ <xsl:call-template name="cookInParamMscom">
+ <xsl:with-param name="value" select="$value" />
+ <xsl:with-param name="idltype" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="($G_vboxGlueStyle='jaxws')">
+ <xsl:call-template name="cookInParamJaxws">
+ <xsl:with-param name="value" select="$value" />
+ <xsl:with-param name="idltype" select="$idltype" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'Unhandled style (cookInParam)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!-- Invoke backend method, including parameter conversion -->
+<xsl:template name="genBackMethodCall">
+ <xsl:param name="ifname"/>
+ <xsl:param name="methodname"/>
+ <xsl:param name="retval"/>
+
+ <xsl:choose>
+ <xsl:when test="($G_vboxGlueStyle='xpcom')">
+ <xsl:text> </xsl:text>
+ <xsl:if test="param[@dir='return']">
+ <xsl:value-of select="concat($retval, ' = ')" />
+ </xsl:if>
+ <xsl:value-of select="concat('getTypedWrapped().', $methodname, '(')"/>
+ <xsl:for-each select="param">
+ <xsl:choose>
+ <xsl:when test="@dir='return'">
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>null</xsl:text>
+ </xsl:if>
+ </xsl:when>
+ <xsl:when test="@dir='out'">
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>null, </xsl:text>
+ </xsl:if>
+ <xsl:value-of select="concat('tmp_', @name)" />
+ </xsl:when>
+ <xsl:when test="@dir='in'">
+ <xsl:if test="(@safearray='yes') and not(@type = 'octet')">
+ <xsl:value-of select="concat(@name, ' != null ? ', @name, '.size() : 0, ')" />
+ </xsl:if>
+ <xsl:variable name="unwrapped">
+ <xsl:call-template name="cookInParam">
+ <xsl:with-param name="value" select="@name" />
+ <xsl:with-param name="idltype" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="$unwrapped"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('Unsupported param dir: ', @dir, '&quot;.')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="not(position()=last()) and not(following-sibling::param[1]/@dir='return' and not(following-sibling::param[1]/@safearray='yes'))">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>);&#10;</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="($G_vboxGlueStyle='mscom')">
+ <xsl:text> </xsl:text>
+ <xsl:if test="param[@dir='return']">
+ <xsl:value-of select="concat($retval, ' = ')" />
+ </xsl:if>
+ <xsl:value-of select="concat('Helper.invoke(getTypedWrapped(), &quot;', $methodname, '&quot; ')"/>
+ <xsl:for-each select="param[not(@dir='return')]">
+ <xsl:text>, </xsl:text>
+ <xsl:choose>
+ <xsl:when test="@dir='out'">
+ <xsl:value-of select="concat('tmp_', @name)" />
+ </xsl:when>
+ <xsl:when test="@dir='in'">
+ <xsl:variable name="unwrapped">
+ <xsl:call-template name="cookInParam">
+ <xsl:with-param name="value" select="@name" />
+ <xsl:with-param name="idltype" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="$unwrapped"/>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:for-each>
+ <xsl:text>);&#10;</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="($G_vboxGlueStyle='jaxws')">
+ <xsl:variable name="jaxwsmethod">
+ <xsl:call-template name="makeJaxwsMethod">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="$methodname" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="portArg">
+ <xsl:if test="not(key('G_keyInterfacesByName', $ifname)/@wsmap='global')">
+ <xsl:text>obj</xsl:text>
+ </xsl:if>
+ </xsl:variable>
+ <xsl:variable name="paramsinout" select="param[@dir='in' or @dir='out']" />
+
+ <xsl:text> </xsl:text>
+ <xsl:if test="param[@dir='return'] and not(param[@dir='out'])">
+ <xsl:value-of select="concat($retval, ' = ')" />
+ </xsl:if>
+ <xsl:value-of select="concat('port.', $jaxwsmethod, '(', $portArg)" />
+ <xsl:if test="$paramsinout and not($portArg='')">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+
+ <!-- jax-ws has an oddity: if both out params and a return value exist,
+ then the return value is moved to the function's argument list... -->
+ <xsl:choose>
+ <xsl:when test="param[@dir='out'] and param[@dir='return']">
+ <xsl:for-each select="param">
+ <xsl:choose>
+ <xsl:when test="@dir='return'">
+ <xsl:value-of select="$retval"/>
+ </xsl:when>
+ <xsl:when test="@dir='out'">
+ <xsl:value-of select="concat('tmp_', @name)" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="cookInParam">
+ <xsl:with-param name="value" select="@name" />
+ <xsl:with-param name="idltype" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="not(position()=last())">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:for-each select="$paramsinout">
+ <xsl:choose>
+ <xsl:when test="@dir='return'">
+ <xsl:value-of select="$retval"/>
+ </xsl:when>
+ <xsl:when test="@dir='out'">
+ <xsl:value-of select="concat('tmp_', @name)" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="cookInParam">
+ <xsl:with-param name="value" select="@name" />
+ <xsl:with-param name="idltype" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="not(position()=last())">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>);&#10;</xsl:text>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'Style unknown (genBackMethodCall)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="genGetterCall">
+ <xsl:param name="ifname"/>
+ <xsl:param name="gettername"/>
+ <xsl:param name="backtype"/>
+ <xsl:param name="retval"/>
+
+ <xsl:choose>
+ <xsl:when test="$G_vboxGlueStyle='xpcom'">
+ <xsl:value-of select="concat(' ', $backtype, ' ', $retval, ' = getTypedWrapped().', $gettername, '(')" />
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>null</xsl:text>
+ </xsl:if>
+ <xsl:text>);&#10;</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='mscom'">
+ <xsl:value-of select="concat(' ', $backtype, ' ', $retval, ' = Dispatch.get(getTypedWrapped(), &quot;', @name, '&quot;);&#10;')" />
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='jaxws'">
+ <xsl:variable name="jaxwsGetter">
+ <xsl:call-template name="makeJaxwsMethod">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="$gettername" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' ', $backtype, ' ', $retval, ' = port.', $jaxwsGetter, '(obj);&#10;')" />
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'Style unknown (genGetterCall)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="genSetterCall">
+ <xsl:param name="ifname"/>
+ <xsl:param name="settername"/>
+ <xsl:param name="value"/>
+
+ <xsl:choose>
+ <xsl:when test="$G_vboxGlueStyle='xpcom'">
+ <xsl:value-of select="concat(' getTypedWrapped().', $settername, '(', $value, ');&#10;')" />
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='mscom'">
+ <xsl:value-of select="concat(' Dispatch.put(getTypedWrapped(), &quot;', @name, '&quot;, ', $value, ');&#10;')" />
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='jaxws'">
+ <xsl:variable name="jaxwsSetter">
+ <xsl:call-template name="makeJaxwsMethod">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="$settername" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' port.', $jaxwsSetter, '(obj, ', $value, ');&#10;')" />
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'Style unknown (genSetterCall)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="genStructWrapperJaxws">
+ <xsl:param name="ifname"/>
+
+ <xsl:value-of select="concat(' private ', $G_virtualBoxPackageCom, '.', $ifname, ' real;&#10;')"/>
+ <xsl:text> private VboxPortType port;&#10;</xsl:text>
+ <xsl:text> private ObjectRefManager objMgr;&#10;</xsl:text>
+
+ <!-- For structs which contain references to other objects we have to create the stub object during construction time
+ or it is possible that the reference was released by the garbage collector because the reference is not
+ accounted for. -->
+ <!-- Create the private members for containing objects here. -->
+ <xsl:for-each select="attribute">
+ <xsl:variable name="attrname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="attrtype"><xsl:value-of select="@type" /></xsl:variable>
+ <xsl:variable name="attrsafearray"><xsl:value-of select="@safearray" /></xsl:variable>
+
+ <xsl:if test="(not(@wsmap = 'suppress')) and ($attrtype = '$unknown' or (count(key('G_keyInterfacesByName', $attrtype)) > 0))">
+ <xsl:variable name="gluegettertype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$attrtype" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' private ', $gluegettertype, ' ', $attrname, ';&#10;')" />
+ </xsl:if>
+ </xsl:for-each>
+
+ <xsl:value-of select="concat('&#10; public ', $ifname, '(', $G_virtualBoxPackageCom, '.', $ifname, ' real, ObjectRefManager objMgr, VboxPortType port)&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> this.real = real;&#10;</xsl:text>
+ <xsl:text> this.port = port;&#10;</xsl:text>
+ <xsl:text> this.objMgr = objMgr;&#10;</xsl:text>
+ <!-- Construct stub objects for every attribute containing a reference to a webserver side object -->
+ <xsl:for-each select="attribute">
+ <xsl:variable name="attrname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="attrtype"><xsl:value-of select="@type" /></xsl:variable>
+ <xsl:variable name="attrsafearray"><xsl:value-of select="@safearray" /></xsl:variable>
+
+ <xsl:if test="(not(@wsmap = 'suppress')) and ($attrtype = '$unknown' or (count(key('G_keyInterfacesByName', $attrtype)) > 0))">
+ <xsl:variable name="backgettername">
+ <xsl:choose>
+ <!-- Stupid, but backend boolean getters called isFoo(), not getFoo() -->
+ <xsl:when test="$attrtype = 'boolean'">
+ <xsl:variable name="capsname">
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="$attrname" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat('is', $capsname)" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="makeGetterName">
+ <xsl:with-param name="attrname" select="$attrname" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:variable name="wrapped">
+ <xsl:call-template name="cookOutParam">
+ <xsl:with-param name="value" select="concat('real.', $backgettername, '()')" />
+ <xsl:with-param name="idltype" select="$attrtype" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' ', $attrname, ' = ', $wrapped, ';&#10;')" />
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text> }&#10;&#10;</xsl:text>
+
+ <xsl:text><![CDATA[ private ObjectRefManager getObjMgr()
+ {
+ return this.objMgr;
+ }
+]]></xsl:text>
+
+ <xsl:for-each select="attribute">
+ <xsl:variable name="attrname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="attrtype"><xsl:value-of select="@type" /></xsl:variable>
+ <xsl:variable name="attrsafearray"><xsl:value-of select="@safearray" /></xsl:variable>
+
+ <xsl:if test="not(@wsmap = 'suppress')">
+
+ <xsl:if test="not(@readonly = 'yes')">
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('Non read-only struct (genStructWrapperJaxws) in interface ', $ifname, ', attribute ', $attrname)" />
+ </xsl:call-template>
+ </xsl:if>
+
+ <!-- Emit getter -->
+ <xsl:variable name="backgettername">
+ <xsl:choose>
+ <!-- Stupid, but backend boolean getters called isFoo(), not getFoo() -->
+ <xsl:when test="$attrtype = 'boolean'">
+ <xsl:variable name="capsname">
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="$attrname" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat('is', $capsname)" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="makeGetterName">
+ <xsl:with-param name="attrname" select="$attrname" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:variable name="gluegettername">
+ <xsl:call-template name="makeGetterName">
+ <xsl:with-param name="attrname" select="$attrname" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="gluegettertype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$attrtype" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="backgettertype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$attrtype" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <!-- For attributes containing a reference to another object just return the already cerated stub -->
+ <xsl:apply-templates select="desc" mode="attribute_get"/>
+ <xsl:value-of select="concat(' public ', $gluegettertype, ' ', $gluegettername, '()&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:choose>
+ <xsl:when test="$attrtype = '$unknown' or (count(key('G_keyInterfacesByName', $attrtype)) > 0)">
+ <xsl:value-of select="concat(' return ', $attrname, ';&#10;')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat(' ', $backgettertype, ' retVal = real.', $backgettername, '();&#10;')" />
+ <xsl:variable name="wrapped">
+ <xsl:call-template name="cookOutParam">
+ <xsl:with-param name="value" select="'retVal'" />
+ <xsl:with-param name="idltype" select="$attrtype" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' return ', $wrapped, ';&#10;')" />
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> }&#10;</xsl:text>
+ </xsl:if>
+
+ </xsl:for-each>
+
+</xsl:template>
+
+<!-- Interface method wrapper -->
+<xsl:template name="genMethod">
+ <xsl:param name="ifname"/>
+ <xsl:param name="methodname"/>
+
+ <xsl:choose>
+ <xsl:when test="(param[@mod='ptr']) or (($G_vboxGlueStyle='jaxws') and (param[@type=($G_setSuppressedInterfaces/@name)]))" >
+ <xsl:value-of select="concat(' // Skipping method ', $methodname, ' for it has parameters with suppressed types&#10;')" />
+ </xsl:when>
+ <xsl:when test="($G_vboxGlueStyle='jaxws') and (@wsmap = 'suppress')" >
+ <xsl:value-of select="concat(' // Skipping method ', $methodname, ' for it is suppressed&#10;')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:variable name="hasReturnParms" select="param[@dir='return']" />
+ <xsl:variable name="hasOutParms" select="count(param[@dir='out']) > 0" />
+ <xsl:variable name="returnidltype" select="param[@dir='return']/@type" />
+ <xsl:variable name="returnidlsafearray" select="param[@dir='return']/@safearray" />
+ <xsl:if test="$hasOutParms and not($hasReturnParms) and (string-length(@wsmap) = 0) and (count(param[@dir='out']) = 1)">
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('genMethod: ', $ifname, $hasOutParms, not($hasReturnParms), 'a', string-length(@wsmap) = 0, 'b', @wsmap, (count(param[@dir='out']) = 1), '::', $methodname, ' has exactly one out parameter and no return parameter, this causes trouble with JAX-WS and the out parameter needs to be converted to return')" />
+ </xsl:call-template>
+ </xsl:if>
+ <xsl:variable name="returngluetype">
+ <xsl:choose>
+ <xsl:when test="$returnidltype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$returnidltype" />
+ <xsl:with-param name="safearray" select="$returnidlsafearray" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>void</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="retValValue">
+ <xsl:choose>
+ <xsl:when test="(param[@dir='out']) and ($G_vboxGlueStyle='jaxws')">
+ <xsl:text>retVal.value</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>retVal</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:apply-templates select="desc" mode="method"/>
+ <xsl:value-of select="concat(' public ', $returngluetype, ' ', $methodname, '(')" />
+ <xsl:variable name="paramsinout" select="param[@dir='in' or @dir='out']" />
+ <xsl:for-each select="exsl:node-set($paramsinout)">
+ <xsl:variable name="paramgluetype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="@dir='out'">
+ <xsl:value-of select="concat('Holder&lt;', $paramgluetype, '&gt; ', @name)" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat($paramgluetype, ' ', @name)" />
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="not(position()=last())">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>)&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+
+ <xsl:call-template name="startExcWrapper">
+ <xsl:with-param name="preventObjRelease" select="$hasReturnParms and ($returnidltype = '$unknown' or (count(key('G_keyInterfacesByName', $returnidltype)) > 0))" />
+ </xsl:call-template>
+
+ <!-- declare temp out params -->
+ <xsl:for-each select="param[@dir='out']">
+ <xsl:variable name="backouttype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="$G_vboxGlueStyle='xpcom'">
+ <xsl:value-of select="concat(' ', $backouttype, '[] tmp_', @name, ' = (', $backouttype, '[])java.lang.reflect.Array.newInstance(', $backouttype, '.class, 1);&#10;')"/>
+ </xsl:when>
+ <xsl:when test="$G_vboxGlueStyle='mscom'">
+ <xsl:value-of select="concat(' Variant tmp_', @name, ' = new Variant();&#10;')"/>
+ </xsl:when>
+ <xsl:when test="$G_vboxGlueStyle='jaxws'">
+ <xsl:value-of select="concat(' javax.xml.ws.Holder&lt;', $backouttype, '&gt; tmp_', @name, ' = new javax.xml.ws.Holder&lt;', $backouttype, '&gt;();&#10;')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'Handle out param (genMethod)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+
+ <!-- declare return param, if any -->
+ <xsl:if test="$hasReturnParms">
+ <xsl:variable name="backrettype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$returnidltype" />
+ <xsl:with-param name="safearray" select="$returnidlsafearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="(param[@dir='out']) and ($G_vboxGlueStyle='jaxws')">
+ <xsl:value-of select="concat(' javax.xml.ws.Holder&lt;', $backrettype, '&gt;',
+ ' retVal = new javax.xml.ws.Holder&lt;', $backrettype,
+ '&gt;();&#10;')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat(' ', $backrettype, ' retVal;&#10;')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:if>
+
+ <!-- Method call -->
+ <xsl:call-template name="genBackMethodCall">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="$methodname" />
+ <xsl:with-param name="retval" select="'retVal'" />
+ </xsl:call-template>
+
+ <!-- return out params -->
+ <xsl:for-each select="param[@dir='out']">
+ <xsl:variable name="varval">
+ <xsl:choose>
+ <xsl:when test="$G_vboxGlueStyle='xpcom'">
+ <xsl:value-of select="concat('tmp_', @name, '[0]')" />
+ </xsl:when>
+ <xsl:when test="$G_vboxGlueStyle='mscom'">
+ <xsl:value-of select="concat('tmp_', @name)" />
+ </xsl:when>
+ <xsl:when test="$G_vboxGlueStyle='jaxws'">
+ <xsl:value-of select="concat('tmp_', @name, '.value')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'Style unknown (genMethod, outparam)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="wrapped">
+ <xsl:call-template name="cookOutParam">
+ <xsl:with-param name="value" select="$varval" />
+ <xsl:with-param name="idltype" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' ', @name, '.value = ', $wrapped, ';&#10;')"/>
+ </xsl:for-each>
+
+ <xsl:if test="$hasReturnParms">
+ <!-- actual 'return' statement -->
+ <xsl:variable name="wrapped">
+ <xsl:call-template name="cookOutParam">
+ <xsl:with-param name="value" select="$retValValue" />
+ <xsl:with-param name="idltype" select="$returnidltype" />
+ <xsl:with-param name="safearray" select="$returnidlsafearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' return ', $wrapped, ';&#10;')" />
+ </xsl:if>
+ <xsl:call-template name="endExcWrapper">
+ <xsl:with-param name="allowObjRelease" select="$hasReturnParms and ($returnidltype = '$unknown' or (count(key('G_keyInterfacesByName', $returnidltype)) > 0))" />
+ </xsl:call-template>
+
+ <xsl:text> }&#10;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+
+</xsl:template>
+
+<!-- Callback interface method -->
+<xsl:template name="genCbMethodDecl">
+ <xsl:param name="ifname"/>
+ <xsl:param name="methodname"/>
+
+ <xsl:choose>
+ <xsl:when test="(param[@mod='ptr'])" >
+ <xsl:value-of select="concat(' // Skipping method ', $methodname, ' for it has parameters with suppressed types&#10;')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:variable name="returnidltype" select="param[@dir='return']/@type" />
+ <xsl:variable name="returnidlsafearray" select="param[@dir='return']/@safearray" />
+ <xsl:variable name="returngluetype">
+ <xsl:choose>
+ <xsl:when test="$returnidltype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$returnidltype" />
+ <xsl:with-param name="safearray" select="$returnidlsafearray" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>void</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:value-of select="concat(' public ', $returngluetype, ' ', $methodname, '(')" />
+ <xsl:variable name="paramsinout" select="param[@dir='in' or @dir='out']" />
+ <xsl:for-each select="exsl:node-set($paramsinout)">
+ <xsl:variable name="paramgluetype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="@dir='out'">
+ <xsl:value-of select="concat('Holder&lt;', $paramgluetype, '&gt; ', @name)" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat($paramgluetype, ' ', @name)" />
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="not(position()=last())">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>);&#10;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!-- queryInterface wrapper -->
+<xsl:template name="genQI">
+ <xsl:param name="ifname"/>
+ <xsl:param name="uuid" />
+
+ <xsl:value-of select="concat(' public static ', $ifname, ' queryInterface(IUnknown obj)&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:choose>
+ <xsl:when test="$G_vboxGlueStyle='xpcom'">
+ <xsl:variable name="backtype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$ifname" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:text> nsISupports nsobj = obj != null ? (nsISupports)obj.getWrapped() : null;&#10;</xsl:text>
+ <xsl:text> if (nsobj == null) return null;&#10;</xsl:text>
+ <xsl:value-of select="concat(' ', $backtype, ' qiobj = Helper.queryInterface(nsobj, &quot;{', $uuid, '}&quot;, ', $backtype, '.class);&#10;')" />
+ <xsl:value-of select="concat(' return qiobj == null ? null : new ', $ifname, '(qiobj);&#10;')" />
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='mscom'">
+ <xsl:value-of select="concat(' return', ' obj == null ? null : new ', $ifname, '((com.jacob.com.Dispatch)obj.getWrapped());&#10;')" />
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='jaxws'">
+ <!-- bad, need to check that we really can be casted to this type -->
+ <xsl:value-of select="concat(' return obj == null ? null : new ', $ifname, '(obj.getWrapped(), obj.getObjMgr(), obj.getRemoteWSPort());&#10;')" />
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'Style unknown (genQI)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+
+ </xsl:choose>
+ <xsl:text> }&#10;</xsl:text>
+</xsl:template>
+
+
+<xsl:template name="genCbMethodImpl">
+ <xsl:param name="ifname"/>
+ <xsl:param name="methodname"/>
+
+ <xsl:choose>
+ <xsl:when test="(param[@mod='ptr'])" >
+ <xsl:value-of select="concat(' // Skipping method ', $methodname, ' for it has parameters with suppressed types&#10;')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:variable name="hasReturnParms" select="param[@dir='return']" />
+ <xsl:variable name="returnidltype" select="param[@dir='return']/@type" />
+ <xsl:variable name="returnidlsafearray" select="param[@dir='return']/@safearray" />
+ <xsl:variable name="returnbacktype">
+ <xsl:choose>
+ <xsl:when test="$returnidltype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$returnidltype" />
+ <xsl:with-param name="safearray" select="$returnidlsafearray" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>void</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="paramsinout" select="param[@dir='in' or @dir='out']" />
+ <xsl:choose>
+ <xsl:when test="$G_vboxGlueStyle='xpcom'">
+ <xsl:value-of select="concat(' public ', $returnbacktype, ' ', $methodname, '(')" />
+ <xsl:for-each select="exsl:node-set($paramsinout)">
+ <xsl:variable name="parambacktype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="@dir='out'">
+ <xsl:value-of select="concat($parambacktype, '[] ', @name)" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:if test="@safearray='yes'">
+ <xsl:value-of select="concat('long len_', @name, ', ')" />
+ </xsl:if>
+ <xsl:value-of select="concat($parambacktype, ' ', @name)" />
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="not(position()=last())">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>)&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='mscom'">
+ <xsl:variable name="capsname">
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="$methodname" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' public ', $returnbacktype, ' ', $capsname, '(')" />
+ <xsl:text>Variant _args[])&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:for-each select="exsl:node-set($paramsinout)">
+ <xsl:variable name="parambacktype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' ', $parambacktype, ' ', @name, '=_args[', count(preceding-sibling::param), '];&#10;')" />
+ </xsl:for-each>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'Style unknown (genSetterCall)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <!-- declare temp out params -->
+ <xsl:for-each select="param[@dir='out']">
+ <xsl:variable name="glueouttype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' Holder&lt;', $glueouttype, '&gt; tmp_', @name, ' = new Holder&lt;', $glueouttype, '&gt;();&#10;')"/>
+ </xsl:for-each>
+
+ <!-- declare return param, if any -->
+ <xsl:if test="$hasReturnParms">
+ <xsl:variable name="gluerettype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$returnidltype" />
+ <xsl:with-param name="safearray" select="$returnidlsafearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' ', $gluerettype, ' retVal = &#10;')"/>
+ </xsl:if>
+
+ <!-- Method call -->
+ <xsl:value-of select="concat(' sink.', $methodname, '(')"/>
+ <xsl:for-each select="param[not(@dir='return')]">
+ <xsl:choose>
+ <xsl:when test="@dir='out'">
+ <xsl:value-of select="concat('tmp_', @name)" />
+ </xsl:when>
+ <xsl:when test="@dir='in'">
+ <xsl:variable name="wrapped">
+ <xsl:call-template name="cookOutParam">
+ <xsl:with-param name="value" select="@name" />
+ <xsl:with-param name="idltype" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="$wrapped"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('Unsupported param dir: ', @dir, '&quot;.')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="not(position()=last())">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>);&#10;</xsl:text>
+
+ <!-- return out params -->
+ <xsl:for-each select="param[@dir='out']">
+
+ <xsl:variable name="unwrapped">
+ <xsl:call-template name="cookInParam">
+ <xsl:with-param name="value" select="concat('tmp_', @name, '.value')" />
+ <xsl:with-param name="idltype" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="$G_vboxGlueStyle='xpcom'">
+ <xsl:value-of select="concat(' ', @name, '[0] = ', $unwrapped, ';&#10;')"/>
+ </xsl:when>
+ <xsl:when test="$G_vboxGlueStyle='mscom'">
+ <xsl:value-of select="concat(' _args[', count(preceding-sibling::param), '] = ', $unwrapped, ';&#10;')"/>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:for-each>
+
+ <xsl:if test="$hasReturnParms">
+ <!-- actual 'return' statement -->
+ <xsl:variable name="unwrapped">
+ <xsl:call-template name="cookInParam">
+ <xsl:with-param name="value" select="'retVal'" />
+ <xsl:with-param name="idltype" select="$returnidltype" />
+ <xsl:with-param name="safearray" select="$returnidlsafearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' return ', $unwrapped, ';&#10;')" />
+ </xsl:if>
+ <xsl:text> }&#10;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!-- Interface method -->
+<xsl:template name="genIfaceWrapper">
+ <xsl:param name="ifname"/>
+
+ <xsl:variable name="wrappedType">
+ <xsl:call-template name="wrappedName">
+ <xsl:with-param name="ifname" select="$ifname" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <!-- Constructor -->
+ <xsl:choose>
+ <xsl:when test="($G_vboxGlueStyle='jaxws')">
+ <xsl:value-of select="concat(' public ', $ifname, '(String wrapped, ObjectRefManager objMgr, VboxPortType port)&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> super(wrapped, objMgr, port);&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="($G_vboxGlueStyle='xpcom') or ($G_vboxGlueStyle='mscom')">
+ <xsl:value-of select="concat(' public ', $ifname, '(', $wrappedType, ' wrapped)&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> super(wrapped);&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+
+ <!-- Typed wrapped object accessor -->
+ <xsl:value-of select="concat(' public ', $wrappedType, ' getTypedWrapped()&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:value-of select="concat(' return (', $wrappedType, ') getWrapped();&#10;')" />
+ <xsl:text> }&#10;</xsl:text>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'Style unknown (root, ctr)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ <!-- Attributes -->
+ <xsl:for-each select="attribute[not(@mod='ptr')]">
+ <xsl:variable name="attrname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="attrtype"><xsl:value-of select="@type" /></xsl:variable>
+ <xsl:variable name="attrsafearray"><xsl:value-of select="@safearray" /></xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="($G_vboxGlueStyle='jaxws') and ($attrtype=($G_setSuppressedInterfaces/@name))">
+ <xsl:value-of select="concat(' // Skipping attribute ', $attrname, ' of suppressed type ', $attrtype, '&#10;&#10;')" />
+ </xsl:when>
+ <xsl:when test="($G_vboxGlueStyle='jaxws') and (@wsmap = 'suppress')" >
+ <xsl:value-of select="concat(' // Skipping attribute ', $attrname, ' for it is suppressed&#10;')" />
+ </xsl:when>
+
+ <xsl:otherwise>
+ <!-- emit getter method -->
+ <xsl:apply-templates select="desc" mode="attribute_get"/>
+ <xsl:variable name="gettername">
+ <xsl:call-template name="makeGetterName">
+ <xsl:with-param name="attrname" select="$attrname" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="gluetype">
+ <xsl:call-template name="typeIdl2Glue">
+ <xsl:with-param name="type" select="$attrtype" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="backtype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$attrtype" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="wrapped">
+ <xsl:call-template name="cookOutParam">
+ <xsl:with-param name="value" select="'retVal'" />
+ <xsl:with-param name="idltype" select="$attrtype" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' public ', $gluetype, ' ', $gettername, '()&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+
+
+ <xsl:call-template name="startExcWrapper">
+ <xsl:with-param name="preventObjRelease" select="$attrtype = '$unknown' or (count(key('G_keyInterfacesByName', $attrtype)) > 0)" />
+ </xsl:call-template>
+
+ <!-- Actual getter implementation -->
+ <xsl:call-template name="genGetterCall">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="gettername" select="$gettername" />
+ <xsl:with-param name="backtype" select="$backtype" />
+ <xsl:with-param name="retval" select="'retVal'" />
+ </xsl:call-template>
+
+ <xsl:value-of select="concat(' return ', $wrapped, ';&#10;')" />
+
+ <xsl:call-template name="endExcWrapper">
+ <xsl:with-param name="allowObjRelease" select="$attrtype = '$unknown' or (count(key('G_keyInterfacesByName', $attrtype)) > 0)" />
+ </xsl:call-template>
+
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:if test="not(@readonly = 'yes')">
+ <!-- emit setter method -->
+ <xsl:apply-templates select="desc" mode="attribute_set"/>
+ <xsl:variable name="settername"><xsl:call-template name="makeSetterName"><xsl:with-param name="attrname" select="$attrname" /></xsl:call-template></xsl:variable>
+ <xsl:variable name="unwrapped">
+ <xsl:call-template name="cookInParam">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="value" select="'value'" />
+ <xsl:with-param name="idltype" select="$attrtype" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' public void ', $settername, '(', $gluetype, ' value)&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:call-template name="startExcWrapper"/>
+ <!-- Actual setter implementation -->
+ <xsl:call-template name="genSetterCall">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="settername" select="$settername" />
+ <xsl:with-param name="value" select="$unwrapped" />
+ </xsl:call-template>
+ <xsl:call-template name="endExcWrapper"/>
+ <xsl:text> }&#10;</xsl:text>
+ </xsl:if>
+
+ </xsl:otherwise>
+ </xsl:choose>
+
+ </xsl:for-each>
+
+ <!-- emit queryInterface() *to* this class -->
+ <xsl:call-template name="genQI">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="uuid" select="@uuid" />
+ </xsl:call-template>
+
+ <!-- emit methods -->
+ <xsl:for-each select="method">
+ <xsl:call-template name="genMethod">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="@name" />
+ </xsl:call-template>
+ </xsl:for-each>
+
+</xsl:template>
+
+<xsl:template name="genIface">
+ <xsl:param name="ifname" />
+ <xsl:param name="filename" />
+
+ <xsl:variable name="wsmap" select="@wsmap" />
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="$filename" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text>import java.util.List;&#10;&#10;</xsl:text>
+
+ <xsl:apply-templates select="desc" mode="interface"/>
+
+ <xsl:choose>
+ <xsl:when test="($wsmap='struct') and ($G_vboxGlueStyle='jaxws')">
+ <xsl:value-of select="concat('public class ', $ifname, '&#10;')" />
+ <xsl:text>{&#10;&#10;</xsl:text>
+ <xsl:call-template name="genStructWrapperJaxws">
+ <xsl:with-param name="ifname" select="$ifname" />
+ </xsl:call-template>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:variable name="extends" select="key('G_keyInterfacesByName', $ifname)/@extends" />
+ <xsl:choose>
+ <xsl:when test="($extends = '$unknown') or ($extends = '$errorinfo')">
+ <xsl:value-of select="concat('public class ', $ifname, ' extends IUnknown&#10;')" />
+ <xsl:text>{&#10;&#10;</xsl:text>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $extends)) > 0">
+ <xsl:value-of select="concat('public class ', $ifname, ' extends ', $extends, '&#10;')" />
+ <xsl:text>{&#10;&#10;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('Interface generation: interface &quot;', $ifname, '&quot; has invalid &quot;extends&quot; value ', $extends, '.')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:call-template name="genIfaceWrapper">
+ <xsl:with-param name="ifname" select="$ifname" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <!-- end of class -->
+ <xsl:text>}&#10;</xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="$filename" />
+ </xsl:call-template>
+
+</xsl:template>
+
+<xsl:template name="genCb">
+ <xsl:param name="ifname" />
+ <xsl:param name="filename" />
+ <xsl:param name="filenameimpl" />
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="$filename" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:text>import java.util.List;&#10;</xsl:text>
+
+ <xsl:value-of select="concat('public interface ', $ifname, '&#10;')" />
+ <xsl:text>{&#10;</xsl:text>
+
+ <!-- emit methods declarations-->
+ <xsl:for-each select="method">
+ <xsl:call-template name="genCbMethodDecl">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="@name" />
+ </xsl:call-template>
+ </xsl:for-each>
+
+ <xsl:text>}&#10;&#10;</xsl:text>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="$filename" />
+ </xsl:call-template>
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="$filenameimpl" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:text>import java.util.List;&#10;</xsl:text>
+
+ <xsl:variable name="backtype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$ifname" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <!-- emit glue methods body -->
+ <xsl:choose>
+ <xsl:when test="$G_vboxGlueStyle='xpcom'">
+ <xsl:value-of select="concat('class ', $ifname, 'Impl extends nsISupportsBase implements ', $backtype, '&#10;')" />
+ <xsl:text>{&#10;</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='mscom'">
+ <xsl:value-of select="concat('public class ', $ifname, 'Impl&#10;')" />
+ <xsl:text>{&#10;</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+
+ <xsl:value-of select="concat(' ', $ifname, ' sink;&#10;')" />
+
+ <xsl:value-of select="concat(' ', $ifname, 'Impl(', $ifname, ' sink)&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> this.sink = sink;&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+
+ <!-- emit methods implementations -->
+ <xsl:for-each select="method">
+ <xsl:call-template name="genCbMethodImpl">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="@name" />
+ </xsl:call-template>
+ </xsl:for-each>
+
+ <xsl:text>}&#10;&#10;</xsl:text>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="$filenameimpl" />
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template name="emitHandwritten">
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'Holder.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text><![CDATA[
+public class Holder<T>
+{
+ public T value;
+
+ public Holder()
+ {
+ }
+ public Holder(T value)
+ {
+ this.value = value;
+ }
+}
+]]></xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'Holder.java'" />
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template name="emitHandwrittenXpcom">
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'IUnknown.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackageCom" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text><![CDATA[
+public class IUnknown
+{
+ private Object obj;
+ public IUnknown(Object obj)
+ {
+ this.obj = obj;
+ }
+
+ public Object getWrapped()
+ {
+ return this.obj;
+ }
+
+ public void setWrapped(Object obj)
+ {
+ this.obj = obj;
+ }
+}
+]]></xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'IUnknown.java'" />
+ </xsl:call-template>
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'Helper.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackageCom" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text><![CDATA[
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+public class Helper
+{
+ public static List<Short> wrap(byte[] values)
+ {
+ if (values == null)
+ return null;
+
+ List<Short> ret = new ArrayList<Short>(values.length);
+ for (short v : values)
+ {
+ ret.add(v);
+ }
+ return ret;
+ }
+
+ public static List<Short> wrap(short[] values)
+ {
+ if (values == null)
+ return null;
+
+ List<Short> ret = new ArrayList<Short>(values.length);
+ for (short v : values)
+ {
+ ret.add(v);
+ }
+ return ret;
+ }
+
+ public static List<Integer> wrap(int[] values)
+ {
+ if (values == null)
+ return null;
+
+ List<Integer> ret = new ArrayList<Integer>(values.length);
+ for (int v : values)
+ {
+ ret.add(v);
+ }
+ return ret;
+ }
+
+ public static List<Long> wrap(long[] values)
+ {
+ if (values == null)
+ return null;
+
+ List<Long> ret = new ArrayList<Long>(values.length);
+ for (long v : values)
+ {
+ ret.add(v);
+ }
+ return ret;
+ }
+
+ public static List<Boolean> wrap(boolean[] values)
+ {
+ if (values == null)
+ return null;
+
+ List<Boolean> ret = new ArrayList<Boolean>(values.length);
+ for (boolean v: values)
+ {
+ ret.add(v);
+ }
+ return ret;
+ }
+
+ public static List<String> wrap(String[] values)
+ {
+ if (values == null)
+ return null;
+
+ List<String> ret = new ArrayList<String>(values.length);
+ for (String v : values)
+ {
+ ret.add(v);
+ }
+ return ret;
+ }
+
+ public static <T> List<T> wrap(Class<T> wrapperClass, T[] values)
+ {
+ if (values == null)
+ return null;
+
+ List<T> ret = new ArrayList<T>(values.length);
+ for (T v : values)
+ {
+ ret.add(v);
+ }
+ return ret;
+ }
+
+ @SuppressWarnings( "unchecked")
+ public static <T> List<T> wrapEnum(Class<T> wrapperClass, long values[])
+ {
+ try
+ {
+ if (values == null)
+ return null;
+ //// This code is questionable, as it invokes a private constructor
+ //// (all enums only have default constructors), and we don't really
+ //// know what to pass as the name, and the ordinal may or may not
+ //// be sensible, especially if the long was abused as a bitset.
+ //Constructor<T> c = wrapperClass.getDeclaredConstructor(String.class, int.class, int.class);
+ //c.setAccessible(true); // make it callable
+ //List<T> ret = new ArrayList<T>(values.length);
+ //for (long v : values)
+ //{
+ // T convEnum = c.newInstance("unknown", (int)v, (int)v);
+ // ret.add(convEnum);
+ //}
+
+ // Alternative implementation: use the fromValue method, which is
+ // what the code handling single enums will do. I see no reason to
+ // use the above very ugly hack if there are better alternatives,
+ // which as a bonus complain about unknown values. This variant is
+ // slower, but also orders of magnitude safer.
+ java.lang.reflect.Method fromValue = wrapperClass.getMethod("fromValue", long.class);
+ List<T> ret = new ArrayList<T>(values.length);
+ for (long v : values)
+ {
+ T convEnum = (T)fromValue.invoke(null, v);
+ ret.add(convEnum);
+ }
+ return ret;
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new AssertionError(e);
+ }
+ //catch (InstantiationException e)
+ //{
+ // throw new AssertionError(e);
+ //}
+ catch (IllegalAccessException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new AssertionError(e);
+ }
+ }
+ public static short[] unwrapUShort(List<Short> values)
+ {
+ if (values == null)
+ return null;
+
+ short[] ret = new short[values.size()];
+ int i = 0;
+ for (short l : values)
+ {
+ ret[i++] = l;
+ }
+ return ret;
+ }
+
+ public static int[] unwrapInteger(List<Integer> values)
+ {
+ if (values == null)
+ return null;
+
+ int[] ret = new int[values.size()];
+ int i = 0;
+ for (int l : values)
+ {
+ ret[i++] = l;
+ }
+ return ret;
+ }
+
+ public static long[] unwrapULong(List<Long> values)
+ {
+ if (values == null)
+ return null;
+
+ long[] ret = new long[values.size()];
+ int i = 0;
+ for (long l : values)
+ {
+ ret[i++] = l;
+ }
+ return ret;
+ }
+
+ public static boolean[] unwrapBoolean(List<Boolean> values)
+ {
+ if (values == null)
+ return null;
+
+ boolean[] ret = new boolean[values.size()];
+ int i = 0;
+ for (boolean l : values)
+ {
+ ret[i++] = l;
+ }
+ return ret;
+ }
+
+ public static String[] unwrapStr(List<String> values)
+ {
+ if (values == null)
+ return null;
+
+ String[] ret = new String[values.size()];
+ int i = 0;
+ for (String l : values)
+ {
+ ret[i++] = l;
+ }
+ return ret;
+ }
+
+ public static <T extends Enum <T>> long[] unwrapEnum(Class<T> enumClass, List<T> values)
+ {
+ if (values == null)
+ return null;
+
+ long result[] = new long[values.size()];
+ try
+ {
+ java.lang.reflect.Method valueM = enumClass.getMethod("value");
+ int i = 0;
+ for (T v : values)
+ {
+ result[i++] = (Integer)valueM.invoke(v);
+ }
+ return result;
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch(SecurityException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new AssertionError(e);
+ }
+ }
+
+ public static <T1, T2> List<T1> wrap2(Class<T1> wrapperClass1, Class<T2> wrapperClass2, T2[] values)
+ {
+ try
+ {
+ if (values == null)
+ return null;
+
+ Constructor<T1> c = wrapperClass1.getConstructor(wrapperClass2);
+ List<T1> ret = new ArrayList<T1>(values.length);
+ for (T2 v : values)
+ {
+ ret.add(c.newInstance(v));
+ }
+ return ret;
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InstantiationException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new AssertionError(e);
+ }
+ }
+
+ @SuppressWarnings( "unchecked")
+ public static <T> T[] unwrap(Class<T> wrapperClass, List<T> values)
+ {
+ if (values == null)
+ return null;
+ if (values.size() == 0)
+ return null;
+ return (T[])values.toArray((T[])Array.newInstance(wrapperClass, values.size()));
+ }
+
+ @SuppressWarnings( "unchecked" )
+ public static <T> T queryInterface(Object obj, String uuid, Class<T> iface)
+ {
+ return (T)queryInterface(obj, uuid);
+ }
+
+ public static Object queryInterface(Object obj, String uuid)
+ {
+ try
+ {
+ /* Kind of ugly, but does the job of casting */
+ org.mozilla.xpcom.Mozilla moz = org.mozilla.xpcom.Mozilla.getInstance();
+ long xpobj = moz.wrapJavaObject(obj, uuid);
+ return moz.wrapXPCOMObject(xpobj, uuid);
+ }
+ catch (Exception e)
+ {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1 extends IUnknown, T2> T2[] unwrap2(Class<T1> wrapperClass1, Class<T2> wrapperClass2, List<T1> values)
+ {
+ if (values == null)
+ return null;
+
+ T2 ret[] = (T2[])Array.newInstance(wrapperClass2, values.size());
+ int i = 0;
+ for (T1 obj : values)
+ {
+ ret[i++] = (T2)obj.getWrapped();
+ }
+ return ret;
+ }
+}
+]]></xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'Helper.java'" />
+ </xsl:call-template>
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'VBoxException.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text>
+import org.mozilla.xpcom.*;
+
+public class VBoxException extends RuntimeException
+{
+ private int resultCode;
+ private IVirtualBoxErrorInfo errorInfo;
+
+ public VBoxException(String message)
+ {
+ super(message);
+ resultCode = -1;
+ errorInfo = null;
+ }
+
+ public VBoxException(String message, Throwable cause)
+ {
+ super(message, cause);
+ if (cause instanceof org.mozilla.xpcom.XPCOMException)
+ {
+ resultCode = (int)((org.mozilla.xpcom.XPCOMException)cause).errorcode;
+ try
+ {
+ Mozilla mozilla = Mozilla.getInstance();
+ nsIServiceManager sm = mozilla.getServiceManager();
+ nsIExceptionService es = (nsIExceptionService)sm.getServiceByContractID("@mozilla.org/exceptionservice;1", nsIExceptionService.NS_IEXCEPTIONSERVICE_IID);
+ nsIExceptionManager em = es.getCurrentExceptionManager();
+ nsIException ex = em.getCurrentException();
+ errorInfo = new IVirtualBoxErrorInfo((org.mozilla.interfaces.IVirtualBoxErrorInfo)ex.queryInterface(org.mozilla.interfaces.IVirtualBoxErrorInfo.IVIRTUALBOXERRORINFO_IID));
+ }
+ catch (NullPointerException e)
+ {
+ e.printStackTrace();
+ // nothing we can do
+ errorInfo = null;
+ }
+ }
+ else
+ resultCode = -1;
+ }
+
+ public int getResultCode()
+ {
+ return resultCode;
+ }
+
+ public IVirtualBoxErrorInfo getVirtualBoxErrorInfo()
+ {
+ return errorInfo;
+ }
+}
+</xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'VBoxException.java'" />
+ </xsl:call-template>
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'VirtualBoxManager.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text><![CDATA[
+
+import java.io.File;
+
+import org.mozilla.xpcom.*;
+import org.mozilla.interfaces.*;
+
+public class VirtualBoxManager
+{
+ private Mozilla mozilla;
+ private IVirtualBox vbox;
+ private nsIComponentManager componentManager;
+
+ private VirtualBoxManager(Mozilla mozilla)
+ {
+ this.mozilla = mozilla;
+ this.componentManager = mozilla.getComponentManager();
+ this.vbox = new IVirtualBox((org.mozilla.interfaces.IVirtualBox) this.componentManager
+ .createInstanceByContractID("@virtualbox.org/VirtualBox;1",
+ null,
+ org.mozilla.interfaces.IVirtualBox.IVIRTUALBOX_IID));
+ }
+
+ public void connect(String url, String username, String passwd)
+ {
+ throw new VBoxException("Connect doesn't make sense for local bindings");
+ }
+
+ public void disconnect()
+ {
+ throw new VBoxException("Disconnect doesn't make sense for local bindings");
+ }
+
+ public static void initPerThread()
+ {
+ }
+
+ public static void deinitPerThread()
+ {
+ }
+
+ public String getClientAPIVersion()
+ {
+ return "]]></xsl:text>
+ <xsl:value-of select="substring($G_vboxApiSuffix, 2)" />
+ <xsl:text><![CDATA[";
+ }
+
+ public IVirtualBox getVBox()
+ {
+ return this.vbox;
+ }
+
+ public ISession getSessionObject()
+ {
+ return new ISession((org.mozilla.interfaces.ISession) componentManager
+ .createInstanceByContractID("@virtualbox.org/Session;1", null,
+ org.mozilla.interfaces.ISession.ISESSION_IID));
+ }
+
+ public ISession openMachineSession(IMachine m) throws Exception
+ {
+ ISession s = getSessionObject();
+ m.lockMachine(s, LockType.Shared);
+ return s;
+ }
+
+ public void closeMachineSession(ISession s)
+ {
+ if (s != null)
+ s.unlockMachine();
+ }
+
+ private static boolean hasInstance = false;
+ private static boolean isMozillaInited = false;
+
+ public static synchronized VirtualBoxManager createInstance(String home)
+ {
+ if (hasInstance)
+ throw new VBoxException("only one instance of VirtualBoxManager at a time allowed");
+ if (home == null || home.equals(""))
+ home = System.getProperty("vbox.home");
+
+ if (home == null)
+ throw new VBoxException("vbox.home Java property must be defined to use XPCOM bridge");
+
+ File grePath = new File(home);
+
+ Mozilla mozilla = Mozilla.getInstance();
+ if (!isMozillaInited)
+ {
+ mozilla.initialize(grePath);
+ try
+ {
+ mozilla.initXPCOM(grePath, null);
+ isMozillaInited = true;
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ hasInstance = true;
+
+ return new VirtualBoxManager(mozilla);
+ }
+
+ public IEventListener createListener(Object sink)
+ {
+ return new IEventListener(new EventListenerImpl(sink));
+ }
+
+ public void cleanup()
+ {
+ deinitPerThread();
+ // cleanup, we don't do that, as XPCOM bridge doesn't cleanly
+ // shuts down, so we prefer to avoid native shutdown
+ // mozilla.shutdownXPCOM(null);
+ mozilla = null;
+ hasInstance = false;
+ }
+
+ public void waitForEvents(long tmo)
+ {
+ mozilla.waitForEvents(tmo);
+ }
+}
+]]></xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'VirtualBoxManager.java'" />
+ </xsl:call-template>
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'EventListenerImpl.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text><![CDATA[
+import org.mozilla.interfaces.*;
+
+public class EventListenerImpl extends nsISupportsBase implements org.mozilla.interfaces.IEventListener
+{
+ private Object obj;
+ private java.lang.reflect.Method handleEvent;
+ EventListenerImpl(Object obj)
+ {
+ this.obj = obj;
+ try
+ {
+ this.handleEvent = obj.getClass().getMethod("handleEvent", IEvent.class);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ public void handleEvent(org.mozilla.interfaces.IEvent ev)
+ {
+ try
+ {
+ if (obj != null && handleEvent != null)
+ handleEvent.invoke(obj, ev != null ? new IEvent(ev) : null);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+}]]></xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'EventListenerImpl.java'" />
+ </xsl:call-template>
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'VBoxObjectBase.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text><![CDATA[
+abstract class nsISupportsBase implements org.mozilla.interfaces.nsISupports
+{
+ public org.mozilla.interfaces.nsISupports queryInterface(String iid)
+ {
+ return org.mozilla.xpcom.Mozilla.queryInterface(this, iid);
+ }
+}
+
+]]></xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'VBoxObjectBase.java'" />
+ </xsl:call-template>
+</xsl:template>
+
+
+<xsl:template name="emitHandwrittenMscom">
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'IUnknown.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackageCom" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text><![CDATA[
+public class IUnknown
+{
+ private Object obj;
+ public IUnknown(Object obj)
+ {
+ this.obj = obj;
+ }
+
+ public Object getWrapped()
+ {
+ return this.obj;
+ }
+
+ public void setWrapped(Object obj)
+ {
+ this.obj = obj;
+ }
+}
+]]></xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'IUnknown.java'" />
+ </xsl:call-template>
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'Helper.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackageCom" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text><![CDATA[
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import com.jacob.com.*;
+
+public class Helper
+{
+ public static List<Short> wrap(short[] values)
+ {
+ if (values == null)
+ return null;
+
+ List<Short> ret = new ArrayList<Short>(values.length);
+ for (short v : values)
+ {
+ ret.add(v);
+ }
+ return ret;
+ }
+
+ public static List<Integer> wrap(int[] values)
+ {
+ if (values == null)
+ return null;
+
+ List<Integer> ret = new ArrayList<Integer>(values.length);
+ for (int v : values)
+ {
+ ret.add(v);
+ }
+ return ret;
+ }
+
+ public static List<Long> wrap(long[] values)
+ {
+ if (values == null)
+ return null;
+
+ List<Long> ret = new ArrayList<Long>(values.length);
+ for (long v : values)
+ {
+ ret.add(v);
+ }
+ return ret;
+ }
+
+ public static List<String> wrap(String[] values)
+ {
+ if (values == null)
+ return null;
+
+ List<String> ret = new ArrayList<String>(values.length);
+ for (String v : values)
+ {
+ ret.add(v);
+ }
+ return ret;
+ }
+
+ public static <T> T wrapDispatch(Class<T> wrapperClass, Dispatch d)
+ {
+ try
+ {
+ if (d == null || d.m_pDispatch == 0)
+ return null;
+ Constructor<T> c = wrapperClass.getConstructor(Dispatch.class);
+ return (T)c.newInstance(d);
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InstantiationException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new AssertionError(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> Object wrapVariant(Class<T> wrapperClass, Variant v)
+ {
+ if (v == null)
+ return null;
+
+ short vt = v.getvt();
+ switch (vt)
+ {
+ case Variant.VariantNull:
+ return null;
+ case Variant.VariantBoolean:
+ return v.getBoolean();
+ case Variant.VariantByte:
+ return v.getByte();
+ case Variant.VariantShort:
+ return v.getShort();
+ case Variant.VariantInt:
+ return v.getInt();
+ case Variant.VariantLongInt:
+ return v.getLong();
+ case Variant.VariantString:
+ return v.getString();
+ case Variant.VariantDispatch:
+ return wrapDispatch(wrapperClass, v.getDispatch());
+ default:
+ throw new IllegalArgumentException("unhandled variant type " + vt);
+ }
+ }
+
+ public static byte[] wrapBytes(SafeArray sa)
+ {
+ if (sa == null)
+ return null;
+
+ int saLen = sa.getUBound() - sa.getLBound() + 1;
+
+ byte[] ret = new byte[saLen];
+ int j = 0;
+ for (int i = sa.getLBound(); i <= sa.getUBound(); i++)
+ {
+ Variant v = sa.getVariant(i);
+ // come up with more effective approach!!!
+ ret[j++] = v.getByte();
+ }
+ return ret;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> List<T> wrap(Class<T> wrapperClass, SafeArray sa)
+ {
+ if (sa == null)
+ return null;
+
+ int saLen = sa.getUBound() - sa.getLBound() + 1;
+ if (saLen == 0)
+ return Collections.emptyList();
+
+ List<T> ret = new ArrayList<T>(saLen);
+ for (int i = sa.getLBound(); i <= sa.getUBound(); i++)
+ {
+ Variant v = sa.getVariant(i);
+ ret.add((T)wrapVariant(wrapperClass, v));
+ }
+ return ret;
+ }
+
+ public static <T> List<T> wrapEnum(Class<T> wrapperClass, SafeArray sa)
+ {
+ try
+ {
+ if (sa == null)
+ return null;
+
+ int saLen = sa.getUBound() - sa.getLBound() + 1;
+ if (saLen == 0)
+ return Collections.emptyList();
+ List<T> ret = new ArrayList<T>(saLen);
+ Constructor<T> c = wrapperClass.getConstructor(int.class);
+ for (int i = sa.getLBound(); i <= sa.getUBound(); i++)
+ {
+ Variant v = sa.getVariant(i);
+ ret.add(c.newInstance(v.getInt()));
+ }
+ return ret;
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InstantiationException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new AssertionError(e);
+ }
+ }
+
+ public static SafeArray unwrapInt(List<Integer> values)
+ {
+ if (values == null)
+ return null;
+ SafeArray ret = new SafeArray(Variant.VariantInt, values.size());
+ int i = 0;
+ for (int l : values)
+ {
+ ret.setInt(i++, l);
+ }
+ return ret;
+ }
+
+ public static SafeArray unwrapLong(List<Long> values)
+ {
+ if (values == null)
+ return null;
+ SafeArray ret = new SafeArray(Variant.VariantLongInt, values.size());
+ int i = 0;
+ for (long l : values)
+ {
+ ret.setLong(i++, l);
+ }
+ return ret;
+ }
+
+ public static SafeArray unwrapBool(List<Boolean> values)
+ {
+ if (values == null)
+ return null;
+
+ SafeArray result = new SafeArray(Variant.VariantBoolean, values.size());
+ int i = 0;
+ for (boolean l : values)
+ {
+ result.setBoolean(i++, l);
+ }
+ return result;
+ }
+
+
+ public static SafeArray unwrapBytes(byte[] values)
+ {
+ if (values == null)
+ return null;
+
+ SafeArray result = new SafeArray(Variant.VariantByte, values.length);
+ int i = 0;
+ for (byte l : values)
+ {
+ result.setByte(i++, l);
+ }
+ return result;
+ }
+
+
+ public static <T extends Enum <T>> SafeArray unwrapEnum(Class<T> enumClass, List<T> values)
+ {
+ if (values == null)
+ return null;
+
+ SafeArray result = new SafeArray(Variant.VariantInt, values.size());
+ try
+ {
+ java.lang.reflect.Method valueM = enumClass.getMethod("value");
+ int i = 0;
+ for (T v : values)
+ {
+ result.setInt(i++, (Integer)valueM.invoke(v));
+ }
+ return result;
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch(SecurityException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new AssertionError(e);
+ }
+ }
+ public static SafeArray unwrapString(List<String> values)
+ {
+ if (values == null)
+ return null;
+ SafeArray result = new SafeArray(Variant.VariantString, values.size());
+ int i = 0;
+ for (String l : values)
+ {
+ result.setString(i++, l);
+ }
+ return result;
+ }
+
+ public static <T1, T2> List<T1> wrap2(Class<T1> wrapperClass1, Class<T2> wrapperClass2, T2[] values)
+ {
+ try
+ {
+ if (values == null)
+ return null;
+ if (values.length == 0)
+ return Collections.emptyList();
+
+ Constructor<T1> c = wrapperClass1.getConstructor(wrapperClass2);
+ List<T1> ret = new ArrayList<T1>(values.length);
+ for (T2 v : values)
+ {
+ ret.add(c.newInstance(v));
+ }
+ return ret;
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InstantiationException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new AssertionError(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> T[] unwrap(Class<T> wrapperClass, List<T> values)
+ {
+ if (values == null)
+ return null;
+ return (T[])values.toArray((T[])Array.newInstance(wrapperClass, values.size()));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1 extends IUnknown, T2> T2[] unwrap2(Class<T1> wrapperClass1, Class<T2> wrapperClass2, List<T1> values)
+ {
+ if (values == null)
+ return null;
+
+ T2 ret[] = (T2[])Array.newInstance(wrapperClass2, values.size());
+ int i = 0;
+ for (T1 obj : values)
+ {
+ ret[i++] = (T2)obj.getWrapped();
+ }
+ return ret;
+ }
+
+ /* We have very long invoke lists sometimes */
+ public static Variant invoke(Dispatch d, String method, Object ... args)
+ {
+ return Dispatch.callN(d, method, args);
+ }
+}
+]]></xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'Helper.java'" />
+ </xsl:call-template>
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'VBoxException.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text>
+
+public class VBoxException extends RuntimeException
+{
+ private int resultCode;
+ private IVirtualBoxErrorInfo errorInfo;
+
+ public VBoxException(String message)
+ {
+ super(message);
+ resultCode = -1;
+ errorInfo = null;
+ }
+
+ public VBoxException(String message, Throwable cause)
+ {
+ super(message, cause);
+ if (cause instanceof com.jacob.com.ComException)
+ {
+ resultCode = ((com.jacob.com.ComException)cause).getHResult();
+ // JACOB doesn't support calling GetErrorInfo, which
+ // means there is no way of getting an IErrorInfo reference,
+ // and that means no way of getting to IVirtualBoxErrorInfo.
+ errorInfo = null;
+ }
+ else
+ resultCode = -1;
+ }
+
+ public int getResultCode()
+ {
+ return resultCode;
+ }
+
+ public IVirtualBoxErrorInfo getVirtualBoxErrorInfo()
+ {
+ return errorInfo;
+ }
+}
+</xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'VBoxException.java'" />
+ </xsl:call-template>
+
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'VirtualBoxManager.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text><![CDATA[
+
+import com.jacob.activeX.ActiveXComponent;
+import com.jacob.com.ComThread;
+import com.jacob.com.Dispatch;
+import com.jacob.com.Variant;
+import com.jacob.com.SafeArray;
+import com.jacob.com.DispatchEvents;
+
+public class VirtualBoxManager
+{
+ private IVirtualBox vbox;
+
+ private VirtualBoxManager()
+ {
+ initPerThread();
+ vbox = new IVirtualBox(new ActiveXComponent("VirtualBox.VirtualBox"));
+ }
+
+ public static void initPerThread()
+ {
+ ComThread.InitMTA();
+ }
+
+ public static void deinitPerThread()
+ {
+ ComThread.Release();
+ }
+
+ public void connect(String url, String username, String passwd)
+ {
+ throw new VBoxException("Connect doesn't make sense for local bindings");
+ }
+
+ public void disconnect()
+ {
+ throw new VBoxException("Disconnect doesn't make sense for local bindings");
+ }
+
+ public String getClientAPIVersion()
+ {
+ return "]]></xsl:text>
+ <xsl:value-of select="substring($G_vboxApiSuffix, 2)" />
+ <xsl:text><![CDATA[";
+ }
+
+ public IVirtualBox getVBox()
+ {
+ return this.vbox;
+ }
+
+ public ISession getSessionObject()
+ {
+ return new ISession(new ActiveXComponent("VirtualBox.Session"));
+ }
+
+ public ISession openMachineSession(IMachine m)
+ {
+ ISession s = getSessionObject();
+ m.lockMachine(s, LockType.Shared);
+ return s;
+ }
+
+ public void closeMachineSession(ISession s)
+ {
+ if (s != null)
+ s.unlockMachine();
+ }
+
+ private static boolean hasInstance = false;
+
+ public static synchronized VirtualBoxManager createInstance(String home)
+ {
+ if (hasInstance)
+ throw new VBoxException("only one instance of VirtualBoxManager at a time allowed");
+
+ hasInstance = true;
+ return new VirtualBoxManager();
+ }
+
+ public void cleanup()
+ {
+ deinitPerThread();
+ hasInstance = false;
+ }
+
+ public void waitForEvents(long tmo)
+ {
+ try
+ {
+ Thread.sleep(tmo);
+ }
+ catch (InterruptedException ie)
+ {
+ }
+ }
+}
+]]></xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'VirtualBoxManager.java'" />
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template name="emitHandwrittenJaxws">
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'IUnknown.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text><![CDATA[
+public class IUnknown
+{
+ protected String obj;
+ protected ObjectRefManager objMgr;
+ protected final VboxPortType port;
+
+ public IUnknown(String obj, ObjectRefManager objMgr, VboxPortType port)
+ {
+ this.obj = obj;
+ this.objMgr = objMgr;
+ this.port = port;
+ objMgr.registerObj(this);
+ }
+
+ public final String getWrapped()
+ {
+ return this.obj;
+ }
+
+ public final VboxPortType getRemoteWSPort()
+ {
+ return this.port;
+ }
+
+ public final ObjectRefManager getObjMgr()
+ {
+ return this.objMgr;
+ }
+
+ public synchronized void releaseRemote() throws WebServiceException
+ {
+ if (obj == null)
+ return;
+
+ try
+ {
+ this.port.iManagedObjectRefRelease(obj);
+ this.obj = null;
+ }
+ catch (InvalidObjectFaultMsg e)
+ {
+ throw new WebServiceException(e);
+ }
+ catch (RuntimeFaultMsg e)
+ {
+ throw new WebServiceException(e);
+ }
+ }
+}
+]]></xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'IUnknown.java'" />
+ </xsl:call-template>
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'Helper.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text><![CDATA[
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigInteger;
+
+public class Helper
+{
+ public static <T> List<T> wrap(Class<T> wrapperClass, ObjectRefManager objMgr, VboxPortType pt, List<String> values)
+ {
+ try
+ {
+ if (values == null)
+ return null;
+
+ Constructor<T> c = wrapperClass.getConstructor(String.class, ObjectRefManager.class, VboxPortType.class);
+ List<T> ret = new ArrayList<T>(values.size());
+ for (String v : values)
+ {
+ ret.add(c.newInstance(v, objMgr, pt));
+ }
+ return ret;
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InstantiationException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new AssertionError(e);
+ }
+ }
+
+ public static <T1, T2> List<T1> wrap2(Class<T1> wrapperClass1, Class<T2> wrapperClass2, ObjectRefManager objMgr, VboxPortType pt, List<T2> values)
+ {
+ try
+ {
+ if (values == null)
+ return null;
+
+ Constructor<T1> c = wrapperClass1.getConstructor(wrapperClass2, ObjectRefManager.class, VboxPortType.class);
+ List<T1> ret = new ArrayList<T1>(values.size());
+ for (T2 v : values)
+ {
+ ret.add(c.newInstance(v, objMgr, pt));
+ }
+ return ret;
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InstantiationException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new AssertionError(e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new AssertionError(e);
+ }
+ }
+
+ public static <T extends IUnknown> List<String> unwrap(List<T> values)
+ {
+ if (values == null)
+ return null;
+
+ List<String> ret = new ArrayList<String>(values.size());
+ for (T obj : values)
+ {
+ ret.add(obj.getWrapped());
+ }
+ return ret;
+ }
+
+ @SuppressWarnings("unchecked" )
+ public static <T1 extends Enum <T1>, T2 extends Enum <T2>> List<T2> convertEnums(Class<T1> fromClass,
+ Class<T2> toClass,
+ List<T1> values)
+ {
+ try
+ {
+ if (values == null)
+ return null;
+ List<T2> ret = new ArrayList<T2>(values.size());
+ for (T1 v : values)
+ {
+ // Ordinal based enum conversion, as JAX-WS "invents" its own
+ // enum names and has string values with the expected content.
+ int enumOrdinal = v.ordinal();
+ T2 convEnum = toClass.getEnumConstants()[enumOrdinal];
+ ret.add(convEnum);
+ }
+ return ret;
+ }
+ catch (ArrayIndexOutOfBoundsException e)
+ {
+ throw new AssertionError(e);
+ }
+ }
+
+ /* Pretty naive Base64 encoder/decoder. */
+ private static final char[] valToChar = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
+ private static final int[] charToVal = new int[256];
+
+ /* Initialize recoding alphabet. */
+ static
+ {
+ for (int i = 0; i < charToVal.length; i++)
+ charToVal[i] = -1;
+
+ for (int i = 0; i < valToChar.length; i++)
+ charToVal[valToChar[i]] = i;
+
+ charToVal['='] = 0;
+ }
+
+ public static String encodeBase64(byte[] data)
+ {
+ if (data == null)
+ return null;
+
+ if (data.length == 0)
+ return "";
+
+ int fullTriplets = data.length / 3;
+ int resultLen = ((data.length - 1) / 3 + 1) * 4;
+ char[] result = new char[resultLen];
+ int dataIndex = 0, stringIndex = 0;
+
+ for (int i = 0; i < fullTriplets; i++)
+ {
+ int ch1 = data[dataIndex++] & 0xff;
+ result[stringIndex++] = valToChar[ch1 >> 2];
+ int ch2 = data[dataIndex++] & 0xff;
+ result[stringIndex++] = valToChar[((ch1 << 4) & 0x3f) | (ch2 >> 4)];
+ int ch3 = data[dataIndex++] & 0xff;
+ result[stringIndex++] = valToChar[((ch2 << 2) & 0x3f) | (ch3 >> 6)];
+ result[stringIndex++] = valToChar[ch3 & 0x3f];
+ }
+
+ switch (data.length - dataIndex)
+ {
+ case 0:
+ // do nothing
+ break;
+ case 1:
+ {
+ int ch1 = data[dataIndex++] & 0xff;
+ result[stringIndex++] = valToChar[ch1 >> 2];
+ result[stringIndex++] = valToChar[(ch1 << 4) & 0x3f];
+ result[stringIndex++] = '=';
+ result[stringIndex++] = '=';
+ break;
+ }
+ case 2:
+ {
+ int ch1 = data[dataIndex++] & 0xff;
+ result[stringIndex++] = valToChar[ch1 >> 2];
+ int ch2 = data[dataIndex++] & 0xff;
+ result[stringIndex++] = valToChar[((ch1 << 4) & 0x3f) | (ch2 >> 4)];
+ result[stringIndex++] = valToChar[(ch2 << 2) & 0x3f];
+ result[stringIndex++] = '=';
+ break;
+ }
+ default:
+ throw new VBoxException("bug!");
+ }
+
+ return new String(result);
+ }
+
+ private static int skipInvalid(String str, int stringIndex)
+ {
+ while (charToVal[str.charAt(stringIndex)] < 0)
+ stringIndex++;
+
+ return stringIndex;
+ }
+
+ public static byte[] decodeBase64(String str)
+ {
+ if (str == null)
+ return null;
+
+ int stringLength = str.length();
+ if (stringLength == 0)
+ return new byte[0];
+
+ int validChars = 0, padChars = 0;
+ for (int i = 0; i < str.length(); i++)
+ {
+ char ch = str.charAt(i);
+
+ if (charToVal[ch] >= 0)
+ validChars++;
+
+ if (ch == '=')
+ padChars++;
+ }
+
+ if ((validChars * 3 % 4) != 0)
+ throw new VBoxException("invalid base64 encoded string " + str);
+
+ int resultLength = validChars * 3 / 4 - padChars;
+ byte[] result = new byte[resultLength];
+
+ int dataIndex = 0, stringIndex = 0;
+ int quadraplets = validChars / 4;
+
+ for (int i = 0; i < quadraplets; i++)
+ {
+ stringIndex = skipInvalid(str, stringIndex);
+ int ch1 = str.charAt(stringIndex++);
+ stringIndex = skipInvalid(str, stringIndex);
+ int ch2 = str.charAt(stringIndex++);
+ stringIndex = skipInvalid(str, stringIndex);
+ int ch3 = str.charAt(stringIndex++);
+ stringIndex = skipInvalid(str, stringIndex);
+ int ch4 = str.charAt(stringIndex++);
+
+ result[dataIndex++] = (byte)(((charToVal[ch1] << 2) | charToVal[ch2] >> 4) & 0xff);
+ /* we check this to ensure that we don't override data with '=' padding. */
+ if (dataIndex < result.length)
+ result[dataIndex++] = (byte)(((charToVal[ch2] << 4) | charToVal[ch3] >> 2) & 0xff);
+ if (dataIndex < result.length)
+ result[dataIndex++] = (byte)(((charToVal[ch3] << 6) | charToVal[ch4]) & 0xff);
+ }
+
+ return result;
+ }
+}
+]]></xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'Helper.java'" />
+ </xsl:call-template>
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'VBoxException.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text>
+public class VBoxException extends RuntimeException
+{
+ private int resultCode;
+ private IVirtualBoxErrorInfo errorInfo;
+
+ public VBoxException(String message)
+ {
+ super(message);
+ resultCode = -1;
+ errorInfo = null;
+ }
+
+ public VBoxException(String message, Throwable cause)
+ {
+ super(message, cause);
+ resultCode = -1;
+ errorInfo = null;
+ }
+
+ public VBoxException(String message, Throwable cause, ObjectRefManager objMgr, VboxPortType port)
+ {
+ super(message, cause);
+ if (cause instanceof RuntimeFaultMsg)
+ {
+ RuntimeFaultMsg m = (RuntimeFaultMsg)cause;
+ RuntimeFault f = m.getFaultInfo();
+ resultCode = f.getResultCode();
+ String retVal = f.getReturnval();
+ errorInfo = (retVal.length() > 0) ? new IVirtualBoxErrorInfo(retVal, objMgr, port) : null;
+ }
+ else
+ resultCode = -1;
+ }
+
+ public int getResultCode()
+ {
+ return resultCode;
+ }
+
+ public IVirtualBoxErrorInfo getVirtualBoxErrorInfo()
+ {
+ return errorInfo;
+ }
+}
+</xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'VBoxException.java'" />
+ </xsl:call-template>
+
+ <xsl:call-template name="startFile">
+ <xsl:with-param name="file" select="'VirtualBoxManager.java'" />
+ <xsl:with-param name="package" select="$G_virtualBoxPackage" />
+ </xsl:call-template>
+
+ <xsl:if test="$filelistonly=''">
+ <xsl:text>import java.net.URL;
+import java.math.BigInteger;
+import java.util.Iterator;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.lang.Integer;
+import java.lang.ref.WeakReference;
+import java.lang.ref.ReferenceQueue;
+import javax.xml.namespace.QName;
+import javax.xml.ws.BindingProvider;
+import javax.xml.ws.Holder;
+import javax.xml.ws.WebServiceException;
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.net.Socket;
+import java.net.InetAddress;
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.SSLSocket;
+
+class PortPool
+{
+ private final static String wsdlFile = </xsl:text>
+ <xsl:value-of select="$G_virtualBoxWsdl" />
+ <xsl:text><![CDATA[;
+ private Map<VboxPortType, Integer> known;
+ private boolean initStarted;
+ private VboxService svc;
+
+ PortPool(boolean usePreinit)
+ {
+ known = new HashMap<VboxPortType, Integer>();
+
+ if (usePreinit)
+ {
+ new Thread(new Runnable()
+ {
+ public void run()
+ {
+ // need to sync on something else but 'this'
+ synchronized (known)
+ {
+ initStarted = true;
+ known.notify();
+ }
+
+ preinit();
+ }
+ }).start();
+
+ synchronized (known)
+ {
+ while (!initStarted)
+ {
+ try
+ {
+ known.wait();
+ }
+ catch (InterruptedException e)
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private synchronized void preinit()
+ {
+ VboxPortType port = getPort();
+ releasePort(port);
+ }
+
+ synchronized VboxPortType getPort()
+ {
+ VboxPortType port = null;
+ int ttl = 0;
+
+ for (VboxPortType cur: known.keySet())
+ {
+ int value = known.get(cur);
+ if ((value & 0x10000) == 0)
+ {
+ port = cur;
+ ttl = value & 0xffff;
+ break;
+ }
+ }
+
+ if (port == null)
+ {
+ if (svc == null)
+ {
+ URL wsdl = PortPool.class.getClassLoader().getResource(wsdlFile);
+ if (wsdl == null)
+ throw new LinkageError(wsdlFile + " not found, but it should have been in the jar");
+ svc = new VboxService(wsdl,
+ new QName("http://www.virtualbox.org/Service",
+ "vboxService"));
+ }
+ port = svc.getVboxServicePort();
+ // reuse this object 0x10 times
+ ttl = 0x10;
+ }
+ // mark as used
+ known.put(port, new Integer(0x10000 | ttl));
+ return port;
+ }
+
+ synchronized void releasePort(VboxPortType port)
+ {
+ Integer val = known.get(port);
+ if (val == null || val == 0)
+ {
+ // know you not
+ return;
+ }
+
+ int v = val;
+ int ttl = v & 0xffff;
+ // decrement TTL, and throw away port if used too much times
+ if (--ttl <= 0)
+ {
+ known.remove(port);
+ }
+ else
+ {
+ v = ttl; // set new TTL and clear busy bit
+ known.put(port, v);
+ }
+ }
+}
+
+
+/**
+ * This class manages the object references between us and the webservice server.
+ * It makes sure that the object on the server side is destroyed when all
+ */
+class ObjectRefManager
+{
+ private final static ReferenceQueue<IUnknown> refQ = new ReferenceQueue<IUnknown>();
+
+ private final ConcurrentMap<String, ManagedObj> map = new ConcurrentHashMap<String, ManagedObj>();
+ private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+ private final ObjRefMgrCleanupThread objRefMgrCleanup;
+
+ public ObjectRefManager()
+ {
+ this.objRefMgrCleanup = new ObjRefMgrCleanupThread(this, 100);
+ this.objRefMgrCleanup.start();
+ }
+
+ /**
+ * Prevents the object reference manager cleanup thread from releasing any
+ * server side objects to avoid a fundamental race in the multi threaded
+ * java environment where it is possible that a wrapper got the object ID
+ * from the server but couldn't create the local stub protecting the object
+ * before the cleanup thread released it.
+ */
+ public void preventObjRelease()
+ {
+ lock.readLock().lock();
+ }
+
+ /**
+ * Allows releasing server side objects from the cleanup thread again.
+ */
+ public void allowObjRelease()
+ {
+ lock.readLock().unlock();
+ }
+
+ /**
+ * Marks the start of a run to release server side objects which don't hold
+ * a reference locally anymore.
+ */
+ public void startObjRelease()
+ {
+ lock.writeLock().lock();
+ }
+
+ /**
+ * Marks the end of a cleanup run.
+ */
+ public void endObjRelease()
+ {
+ lock.writeLock().unlock();
+ }
+
+ /**
+ * Registers a new stub object for automatic reference managing.
+ */
+ public void registerObj(IUnknown obj)
+ {
+ assert lock.getReadLockCount() > 0;
+ ManagedObjRef ref = new ManagedObjRef(obj);
+
+ ManagedObj mgrobj = map.get(obj.getWrapped());
+ if (mgrobj != null)
+ {
+ mgrobj.addObject(ref);
+ }
+ else
+ {
+ /* Create new. */
+ mgrobj = new ManagedObj(obj.getWrapped(), obj.getRemoteWSPort());
+ mgrobj.addObject(ref);
+ map.put(obj.getWrapped(), mgrobj);
+ }
+ }
+
+ /**
+ * Removes a garbage collected object reference from our reference manager.
+ *
+ * Returns the server side object wrapper if there is no stub referencing it
+ * anymore otherwise null is returned.
+ */
+ public ManagedObj unregisterObj(ManagedObjRef objRef)
+ {
+ ManagedObj obj = this.map.get(objRef.objId);
+
+ assert obj != null;
+ obj.removeObject(objRef);
+ if (!obj.isReferenced())
+ return obj;
+
+ return null;
+ }
+
+ public void releaseRemoteObj(ManagedObj obj)
+ {
+ assert lock.isWriteLockedByCurrentThread();
+
+ if (!obj.isReferenced())
+ {
+ try
+ {
+ obj.port.iManagedObjectRefRelease(obj.objId);
+ }
+ catch (InvalidObjectFaultMsg e)
+ {
+ throw new WebServiceException(e);
+ }
+ catch (RuntimeFaultMsg e)
+ {
+ throw new WebServiceException(e);
+ }
+ finally
+ {
+ this.map.remove(obj.objId);
+ }
+ }
+ }
+
+ /**
+ * An object which is living on the server side. This can be referenced
+ * by multiple stub objects here.
+ */
+ static class ManagedObj
+ {
+ private final String objId;
+ private final VboxPortType port;
+ private final ConcurrentLinkedQueue<ManagedObjRef> refQ;
+
+ ManagedObj(String objId, VboxPortType port)
+ {
+ this.objId = objId;
+ this.port = port;
+ this.refQ = new ConcurrentLinkedQueue<ManagedObjRef>();
+ }
+
+ public void addObject(ManagedObjRef obj)
+ {
+ this.refQ.add(obj);
+ }
+
+ public void removeObject(ManagedObjRef obj)
+ {
+ this.refQ.remove(obj);
+ }
+
+ public boolean isReferenced()
+ {
+ return !this.refQ.isEmpty();
+ }
+ }
+
+ /**
+ * A private class extending WeakReference to get notified about garbage
+ * collected stub objects.
+ */
+ static class ManagedObjRef extends WeakReference<IUnknown>
+ {
+ final String objId;
+
+ ManagedObjRef(IUnknown obj)
+ {
+ super(obj, refQ);
+ this.objId = obj.getWrapped();
+ }
+ }
+
+ /**
+ * A private class implementing a thread getting notified
+ * about garbage collected objects so it can release the object on the
+ * server side if it is not used anymore.
+ */
+ static class ObjRefMgrCleanupThread extends Thread
+ {
+ ObjectRefManager objRefMgr;
+ int cStubsReleased;
+ int cStubsReleaseThreshold;
+ HashMap<String, ManagedObj> mapToRelease = new HashMap<String, ManagedObj>();
+
+ ObjRefMgrCleanupThread(ObjectRefManager objRefMgr)
+ {
+ init(objRefMgr, 500);
+ }
+
+ ObjRefMgrCleanupThread(ObjectRefManager objRefMgr, int cStubsReleaseThreshold)
+ {
+ init(objRefMgr, cStubsReleaseThreshold);
+ }
+
+ private void init(ObjectRefManager objRefMgr, int cStubsReleaseThreshold)
+ {
+ this.objRefMgr = objRefMgr;
+ this.cStubsReleased = 0;
+ this.cStubsReleaseThreshold = cStubsReleaseThreshold;
+ setName("ObjectRefManager-VBoxWSObjRefGcThrd");
+ /*
+ * setDaemon() makes sure the jvm exits and is not blocked
+ * if the thread is still running so we don't have to care about
+ * tearing it down.
+ */
+ setDaemon(true);
+ }
+
+ public void run()
+ {
+ while (true)
+ {
+ while (cStubsReleased < cStubsReleaseThreshold)
+ {
+ try
+ {
+ /* Accumulate a few objects before we start. */
+ while (cStubsReleased < cStubsReleaseThreshold)
+ {
+ ManagedObjRef ref = (ManagedObjRef)refQ.remove();
+ ManagedObj obj = this.objRefMgr.unregisterObj(ref);
+ /*
+ * If the server side object is not referenced anymore
+ * promote to map for releasing later.
+ */
+ if (obj != null && !mapToRelease.containsKey(ref.objId))
+ mapToRelease.put(ref.objId, obj);
+
+ cStubsReleased++;
+ }
+ }
+ catch (InterruptedException e)
+ { /* ignore */ }
+ catch (javax.xml.ws.WebServiceException e)
+ { /* ignore */ }
+ }
+
+ /*
+ * After we released enough stubs we go over all non referenced
+ * server side objects and release them if they were not
+ * referenced again in between.
+ */
+ cStubsReleased = 0;
+ if (!mapToRelease.isEmpty())
+ {
+ this.objRefMgr.startObjRelease();
+ try
+ {
+ Iterator<ManagedObj> it = mapToRelease.values().iterator();
+ while (it.hasNext())
+ {
+ ManagedObj obj = it.next();
+ this.objRefMgr.releaseRemoteObj(obj);
+ }
+
+ mapToRelease.clear();
+ }
+ catch (javax.xml.ws.WebServiceException e)
+ { /* ignore */ }
+ finally
+ {
+ this.objRefMgr.endObjRelease();
+ }
+ }
+ }
+ }
+ }
+}
+
+class VBoxTLSSocketFactory extends SSLSocketFactory
+{
+ private final SSLSocketFactory sf;
+
+ private void setupSocket(SSLSocket s)
+ {
+ String[] oldproto = s.getEnabledProtocols();
+ List<String> protolist = new ArrayList<String>();
+ for (int i = 0; i < oldproto.length; i++)
+ if (oldproto[i].toUpperCase().startsWith("TLS"))
+ protolist.add(oldproto[i]);
+ String[] newproto = protolist.toArray(new String[protolist.size()]);
+ s.setEnabledProtocols(newproto);
+ }
+
+ public VBoxTLSSocketFactory()
+ {
+ SSLSocketFactory tmp = null;
+ try
+ {
+ SSLContext sc = SSLContext.getInstance("TLS");
+ sc.init(null, null, null);
+ tmp = sc.getSocketFactory();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ sf = tmp;
+ }
+
+ public static SocketFactory getDefault()
+ {
+ return new VBoxTLSSocketFactory();
+ }
+
+ public Socket createSocket(Socket socket, String host, int port,
+ boolean autoClose) throws IOException, UnknownHostException
+ {
+ SSLSocket s = (SSLSocket)sf.createSocket(socket, host, port, autoClose);
+ setupSocket(s);
+ return s;
+ }
+
+ public Socket createSocket() throws IOException
+ {
+ SSLSocket s = (SSLSocket)sf.createSocket();
+ setupSocket(s);
+ return s;
+ }
+
+ public Socket createSocket(InetAddress host, int port) throws IOException
+ {
+ SSLSocket s = (SSLSocket)sf.createSocket(host, port);
+ setupSocket(s);
+ return s;
+ }
+
+ public Socket createSocket(InetAddress address, int port,
+ InetAddress localAddress, int localPort) throws IOException
+ {
+ SSLSocket s = (SSLSocket)sf.createSocket(address, port, localAddress, localPort);
+ setupSocket(s);
+ return s;
+ }
+
+ public Socket createSocket(String host, int port) throws IOException, UnknownHostException
+ {
+ SSLSocket s = (SSLSocket)sf.createSocket(host, port);
+ setupSocket(s);
+ return s;
+ }
+
+ public Socket createSocket(String host, int port,
+ InetAddress localHost, int localPort) throws IOException, UnknownHostException
+ {
+ SSLSocket s = (SSLSocket)sf.createSocket(host, port, localHost, localPort);
+ setupSocket(s);
+ return s;
+ }
+
+ public String[] getDefaultCipherSuites()
+ {
+ return sf.getDefaultCipherSuites();
+ }
+
+ public String[] getSupportedCipherSuites()
+ {
+ return sf.getSupportedCipherSuites();
+ }
+}
+
+
+public class VirtualBoxManager
+{
+ private static PortPool pool = new PortPool(true);
+ private static final ObjectRefManager objMgr = new ObjectRefManager();
+ protected VboxPortType port;
+
+ private IVirtualBox vbox;
+
+ private VirtualBoxManager()
+ {
+ }
+
+ public static void initPerThread()
+ {
+ }
+
+ public static void deinitPerThread()
+ {
+ }
+
+ public void connect(String url, String username, String passwd)
+ {
+ this.port = pool.getPort();
+ try
+ {
+ ((BindingProvider)port).getRequestContext().
+ put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, url);
+
+ // Unfortunately there is no official way to make JAX-WS use
+ // TLS only, which means that a rather tedious approach is
+ // unavoidable (implementing a TLS only SSLSocketFactory,
+ // because the default one associated with a TLS SSLContext
+ // happily uses SSLv2/3 handshakes, which make TLS servers
+ // drop the connection), and additionally a not standardized,
+ // shotgun approach is needed to make the relevant JAX-WS
+ // implementations use this factory.
+ VBoxTLSSocketFactory sf = new VBoxTLSSocketFactory();
+ ((BindingProvider)port).getRequestContext().
+ put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", sf);
+ ((BindingProvider)port).getRequestContext().
+ put("com.sun.xml.ws.transport.https.client.SSLSocketFactory", sf);
+
+ String handle = port.iWebsessionManagerLogon(username, passwd);
+ this.objMgr.preventObjRelease();
+ try
+ {
+ this.vbox = new IVirtualBox(handle, this.objMgr, port);
+ }
+ finally
+ {
+ this.objMgr.allowObjRelease();
+ }
+ }
+ catch (Throwable t)
+ {
+ if (this.port != null && pool != null)
+ {
+ pool.releasePort(this.port);
+ this.port = null;
+ }
+ // we have to throw smth derived from RuntimeException
+ throw new VBoxException(t.getMessage(), t, this.objMgr, this.port);
+ }
+ }
+
+ public void connect(String url, String username, String passwd,
+ Map<String, Object> requestContext, Map<String, Object> responseContext)
+ {
+ this.port = pool.getPort();
+ try
+ {
+ ((BindingProvider)port).getRequestContext();
+ if (requestContext != null)
+ ((BindingProvider)port).getRequestContext().putAll(requestContext);
+
+ if (responseContext != null)
+ ((BindingProvider)port).getResponseContext().putAll(responseContext);
+
+ ((BindingProvider)port).getRequestContext().
+ put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, url);
+ String handle = port.iWebsessionManagerLogon(username, passwd);
+ this.objMgr.preventObjRelease();
+ try
+ {
+ this.vbox = new IVirtualBox(handle, this.objMgr, port);
+ }
+ finally
+ {
+ this.objMgr.allowObjRelease();
+ }
+ }
+ catch (Throwable t)
+ {
+ if (this.port != null && pool != null)
+ {
+ pool.releasePort(this.port);
+ this.port = null;
+ }
+ // we have to throw smth derived from RuntimeException
+ throw new VBoxException(t.getMessage(), t, this.objMgr, this.port);
+ }
+ }
+
+ public void disconnect()
+ {
+ if (this.port == null)
+ return;
+
+ try
+ {
+ if (this.vbox != null && port != null)
+ port.iWebsessionManagerLogoff(this.vbox.getWrapped());
+ }
+ catch (InvalidObjectFaultMsg e)
+ {
+ throw new VBoxException(e.getMessage(), e, this.objMgr, this.port);
+ }
+ catch (RuntimeFaultMsg e)
+ {
+ throw new VBoxException(e.getMessage(), e, this.objMgr, this.port);
+ }
+ finally
+ {
+ if (this.port != null)
+ {
+ pool.releasePort(this.port);
+ this.port = null;
+ }
+ }
+ }
+
+ public String getClientAPIVersion()
+ {
+ return "]]></xsl:text>
+ <xsl:value-of select="substring($G_vboxApiSuffix, 2)" />
+ <xsl:text><![CDATA[";
+ }
+
+ public IVirtualBox getVBox()
+ {
+ return this.vbox;
+ }
+
+ public ISession getSessionObject()
+ {
+ if (this.vbox == null)
+ throw new VBoxException("connect first");
+ try
+ {
+ String handle = port.iWebsessionManagerGetSessionObject(this.vbox.getWrapped());
+ this.objMgr.preventObjRelease();
+ try
+ {
+ return new ISession(handle, this.objMgr, port);
+ }
+ finally
+ {
+ this.objMgr.allowObjRelease();
+ }
+ }
+ catch (InvalidObjectFaultMsg e)
+ {
+ throw new VBoxException(e.getMessage(), e, this.objMgr, this.port);
+ }
+ catch (RuntimeFaultMsg e)
+ {
+ throw new VBoxException(e.getMessage(), e, this.objMgr, this.port);
+ }
+ }
+
+ public ISession openMachineSession(IMachine m) throws Exception
+ {
+ ISession s = getSessionObject();
+ m.lockMachine(s, LockType.Shared);
+ return s;
+ }
+
+ public void closeMachineSession(ISession s)
+ {
+ if (s != null)
+ s.unlockMachine();
+ }
+
+ public static synchronized VirtualBoxManager createInstance(String home)
+ {
+ return new VirtualBoxManager();
+ }
+
+ public IEventListener createListener(Object sink)
+ {
+ throw new VBoxException("no active listeners here");
+ }
+
+ public void cleanup()
+ {
+ disconnect();
+ deinitPerThread();
+ }
+
+ public void waitForEvents(long tmo)
+ {
+ try
+ {
+ Thread.sleep(tmo);
+ }
+ catch (InterruptedException ie)
+ {
+ }
+ }
+
+ protected void finalize() throws Throwable
+ {
+ try
+ {
+ cleanup();
+ }
+ catch(Exception e)
+ {
+ }
+ finally
+ {
+ super.finalize();
+ }
+ }
+}
+]]></xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="endFile">
+ <xsl:with-param name="file" select="'VirtualBoxManager.java'" />
+ </xsl:call-template>
+</xsl:template>
+
+
+<xsl:template match="/">
+
+ <xsl:if test="not($G_vboxApiSuffix)">
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'G_vboxApiSuffix must be given'" />
+ </xsl:call-template>
+ </xsl:if>
+
+ <xsl:if test="not($filelistonly='')">
+ <xsl:value-of select="concat($filelistonly, ' :=')"/>
+ </xsl:if>
+
+ <!-- Handwritten files -->
+ <xsl:call-template name="emitHandwritten"/>
+
+ <xsl:choose>
+ <xsl:when test="$G_vboxGlueStyle='xpcom'">
+ <xsl:call-template name="emitHandwrittenXpcom"/>
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='mscom'">
+ <xsl:call-template name="emitHandwrittenMscom"/>
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='jaxws'">
+ <xsl:call-template name="emitHandwrittenJaxws"/>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="'Style unknown (root)'" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <!-- Enums -->
+ <xsl:for-each select="//enum">
+ <xsl:call-template name="genEnum">
+ <xsl:with-param name="enumname" select="@name" />
+ <xsl:with-param name="filename" select="concat(@name, '.java')" />
+ </xsl:call-template>
+ </xsl:for-each>
+
+ <!-- Interfaces -->
+ <xsl:for-each select="//interface">
+ <xsl:variable name="self_target" select="current()/ancestor::if/@target"/>
+ <xsl:variable name="module" select="current()/ancestor::module/@name"/>
+
+ <xsl:choose>
+ <xsl:when test="$G_vboxGlueStyle='jaxws'">
+ <xsl:if test="not($module) and not(@wsmap='suppress')">
+ <xsl:call-template name="genIface">
+ <xsl:with-param name="ifname" select="@name" />
+ <xsl:with-param name="filename" select="concat(@name, '.java')" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:when>
+
+ <xsl:when test="$G_vboxGlueStyle='xpcom'">
+ <!-- We don't need WSDL-specific nor MIDL-specific interfaces here -->
+ <xsl:if test="not(@internal='yes') and not($self_target='wsdl') and not($module) and not($self_target='midl')">
+ <xsl:call-template name="genIface">
+ <xsl:with-param name="ifname" select="@name" />
+ <xsl:with-param name="filename" select="concat(@name, '.java')" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <!-- We don't need WSDL-specific interfaces here -->
+ <xsl:if test="not(@internal='yes') and not($self_target='wsdl') and not($module)">
+ <xsl:call-template name="genIface">
+ <xsl:with-param name="ifname" select="@name" />
+ <xsl:with-param name="filename" select="concat(@name, '.java')" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+
+ </xsl:choose>
+ </xsl:for-each>
+
+ <xsl:if test="not($filelistonly='')">
+ <xsl:value-of select="concat($G_sNewLine, $G_sNewLine)"/>
+ </xsl:if>
+
+</xsl:template>
+</xsl:stylesheet>
diff --git a/src/VBox/Main/glue/initterm.cpp b/src/VBox/Main/glue/initterm.cpp
new file mode 100644
index 00000000..bcdf57ab
--- /dev/null
+++ b/src/VBox/Main/glue/initterm.cpp
@@ -0,0 +1,863 @@
+/* $Id: initterm.cpp $ */
+/** @file
+ * MS COM / XPCOM Abstraction Layer - Initialization and Termination.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN
+#if !defined(VBOX_WITH_XPCOM)
+
+# if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x600
+# undef _WIN32_WINNT
+# define _WIN32_WINNT 0x600 /* GetModuleHandleExW */
+# endif
+# include <iprt/nt/nt-and-windows.h>
+# include <iprt/win/objbase.h>
+# include <iprt/win/rpcproxy.h>
+# include <rpcasync.h>
+
+#else /* !defined(VBOX_WITH_XPCOM) */
+
+# include <stdlib.h>
+
+# include <nsIComponentRegistrar.h>
+# include <nsIServiceManager.h>
+# include <nsCOMPtr.h>
+# include <nsEventQueueUtils.h>
+# include <nsEmbedString.h>
+
+# include <nsILocalFile.h>
+# include <nsIDirectoryService.h>
+# include <nsDirectoryServiceDefs.h>
+
+#endif /* !defined(VBOX_WITH_XPCOM) */
+
+#include "VBox/com/com.h"
+#include "VBox/com/assert.h"
+#include "VBox/com/NativeEventQueue.h"
+#include "VBox/com/AutoLock.h"
+
+#include "../include/LoggingNew.h"
+
+#include <iprt/asm.h>
+#include <iprt/env.h>
+#include <iprt/ldr.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/thread.h>
+
+#include <VBox/err.h>
+
+namespace com
+{
+
+#if defined(VBOX_WITH_XPCOM)
+
+class DirectoryServiceProvider : public nsIDirectoryServiceProvider
+{
+public:
+
+ NS_DECL_ISUPPORTS
+
+ DirectoryServiceProvider()
+ : mCompRegLocation(NULL), mXPTIDatLocation(NULL)
+ , mComponentDirLocation(NULL), mCurrProcDirLocation(NULL)
+ {}
+
+ virtual ~DirectoryServiceProvider();
+
+ HRESULT init(const char *aCompRegLocation,
+ const char *aXPTIDatLocation,
+ const char *aComponentDirLocation,
+ const char *aCurrProcDirLocation);
+
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER
+
+private:
+ /** @remarks This is not a UTF-8 string. */
+ char *mCompRegLocation;
+ /** @remarks This is not a UTF-8 string. */
+ char *mXPTIDatLocation;
+ /** @remarks This is not a UTF-8 string. */
+ char *mComponentDirLocation;
+ /** @remarks This is not a UTF-8 string. */
+ char *mCurrProcDirLocation;
+};
+
+NS_IMPL_ISUPPORTS1(DirectoryServiceProvider, nsIDirectoryServiceProvider)
+
+DirectoryServiceProvider::~DirectoryServiceProvider()
+{
+ if (mCompRegLocation)
+ {
+ RTStrFree(mCompRegLocation);
+ mCompRegLocation = NULL;
+ }
+ if (mXPTIDatLocation)
+ {
+ RTStrFree(mXPTIDatLocation);
+ mXPTIDatLocation = NULL;
+ }
+ if (mComponentDirLocation)
+ {
+ RTStrFree(mComponentDirLocation);
+ mComponentDirLocation = NULL;
+ }
+ if (mCurrProcDirLocation)
+ {
+ RTStrFree(mCurrProcDirLocation);
+ mCurrProcDirLocation = NULL;
+ }
+}
+
+/**
+ * @param aCompRegLocation Path to compreg.dat, in Utf8.
+ * @param aXPTIDatLocation Path to xpti.data, in Utf8.
+ */
+HRESULT
+DirectoryServiceProvider::init(const char *aCompRegLocation,
+ const char *aXPTIDatLocation,
+ const char *aComponentDirLocation,
+ const char *aCurrProcDirLocation)
+{
+ AssertReturn(aCompRegLocation, NS_ERROR_INVALID_ARG);
+ AssertReturn(aXPTIDatLocation, NS_ERROR_INVALID_ARG);
+
+/** @todo r=bird: Gotta check how this encoding stuff plays out on darwin!
+ * We get down to [VBoxNsxp]NS_NewNativeLocalFile and that file isn't
+ * nsLocalFileUnix.cpp on 32-bit darwin. On 64-bit darwin it's a question
+ * of what we're doing in IPRT and such... We should probably add a
+ * RTPathConvertToNative for use here. */
+ int vrc = RTStrUtf8ToCurrentCP(&mCompRegLocation, aCompRegLocation);
+ if (RT_SUCCESS(vrc))
+ vrc = RTStrUtf8ToCurrentCP(&mXPTIDatLocation, aXPTIDatLocation);
+ if (RT_SUCCESS(vrc) && aComponentDirLocation)
+ vrc = RTStrUtf8ToCurrentCP(&mComponentDirLocation, aComponentDirLocation);
+ if (RT_SUCCESS(vrc) && aCurrProcDirLocation)
+ vrc = RTStrUtf8ToCurrentCP(&mCurrProcDirLocation, aCurrProcDirLocation);
+
+ return RT_SUCCESS(vrc) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+DirectoryServiceProvider::GetFile(const char *aProp,
+ PRBool *aPersistent,
+ nsIFile **aRetval)
+{
+ nsCOMPtr <nsILocalFile> localFile;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ *aRetval = nsnull;
+ *aPersistent = PR_TRUE;
+
+ const char *fileLocation = NULL;
+
+ if (strcmp(aProp, NS_XPCOM_COMPONENT_REGISTRY_FILE) == 0)
+ fileLocation = mCompRegLocation;
+ else if (strcmp(aProp, NS_XPCOM_XPTI_REGISTRY_FILE) == 0)
+ fileLocation = mXPTIDatLocation;
+ else if (mComponentDirLocation && strcmp(aProp, NS_XPCOM_COMPONENT_DIR) == 0)
+ fileLocation = mComponentDirLocation;
+ else if (mCurrProcDirLocation && strcmp(aProp, NS_XPCOM_CURRENT_PROCESS_DIR) == 0)
+ fileLocation = mCurrProcDirLocation;
+ else
+ return NS_ERROR_FAILURE;
+
+ rv = NS_NewNativeLocalFile(nsEmbedCString(fileLocation),
+ PR_TRUE, getter_AddRefs(localFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return localFile->QueryInterface(NS_GET_IID(nsIFile), (void **)aRetval);
+}
+
+/**
+ * Global XPCOM initialization flag (we maintain it ourselves since XPCOM
+ * doesn't provide such functionality)
+ */
+static bool volatile gIsXPCOMInitialized = false;
+
+/**
+ * Number of Initialize() calls on the main thread.
+ */
+static unsigned int gXPCOMInitCount = 0;
+
+#else /* !defined(VBOX_WITH_XPCOM) */
+
+/**
+ * Replacement function for the InvokeStub method for the IRundown stub.
+ */
+static HRESULT STDMETHODCALLTYPE
+Rundown_InvokeStub(IRpcStubBuffer *pThis, RPCOLEMESSAGE *pMsg, IRpcChannelBuffer *pBuf) RT_NOTHROW_DEF
+{
+ /*
+ * Our mission here is to prevent remote calls to methods #8 and #9,
+ * as these contain raw pointers to callback functions.
+ *
+ * Note! APIs like I_RpcServerInqTransportType, I_RpcBindingInqLocalClientPID
+ * and RpcServerInqCallAttributesW are not usable in this context without
+ * a rpc binding handle (latter two).
+ *
+ * P.S. In more recent windows versions, the buffer implements a interface
+ * IID_IRpcChannelBufferMarshalingContext (undocumented) which has a
+ * GetIMarshallingContextAttribute() method that will return the client PID
+ * when asking for attribute #0x8000000e.
+ */
+ uint32_t const iMethod = pMsg->iMethod & 0xffff; /* Uncertain, but there are hints that the upper bits are flags. */
+ HRESULT hrc;
+ if ( ( iMethod != 8
+ && iMethod != 9)
+ || (pMsg->rpcFlags & RPCFLG_LOCAL_CALL) )
+ hrc = CStdStubBuffer_Invoke(pThis, pMsg, pBuf);
+ else
+ {
+ LogRel(("Rundown_InvokeStub: Rejected call to CRundown::%s: rpcFlags=%#x cbBuffer=%#x dataRepresentation=%d buffer=%p:{%.*Rhxs} reserved1=%p reserved2={%p,%p,%p,%p,%p}\n",
+ pMsg->iMethod == 8 ? "DoCallback" : "DoNonreentrantCallback", pMsg->rpcFlags, pMsg->cbBuffer,
+ pMsg->dataRepresentation, pMsg->Buffer, RT_VALID_PTR(pMsg->Buffer) ? pMsg->cbBuffer : 0, pMsg->Buffer,
+ pMsg->reserved1, pMsg->reserved2[0], pMsg->reserved2[1], pMsg->reserved2[2], pMsg->reserved2[3], pMsg->reserved2[4]));
+ hrc = E_ACCESSDENIED;
+ }
+ return hrc;
+}
+
+/**
+ * Replacement function for the InvokeStub method for the IDLLHost stub.
+ */
+static HRESULT STDMETHODCALLTYPE
+DLLHost_InvokeStub(IRpcStubBuffer *pThis, RPCOLEMESSAGE *pMsg, IRpcChannelBuffer *pBuf) RT_NOTHROW_DEF
+{
+ /*
+ * Our mission here is to prevent remote calls to this interface as method #3
+ * contain a raw pointer an DllGetClassObject function. There are only that
+ * method in addition to the IUnknown stuff, and it's ASSUMED that it's
+ * process internal only (cross apartment stuff).
+ */
+ uint32_t const iMethod = pMsg->iMethod & 0xffff; /* Uncertain, but there are hints that the upper bits are flags. */
+ HRESULT hrc;
+ if (pMsg->rpcFlags & RPCFLG_LOCAL_CALL)
+ hrc = CStdStubBuffer_Invoke(pThis, pMsg, pBuf);
+ else
+ {
+ LogRel(("DLLHost_InvokeStub: Rejected call to CDLLHost::%s: rpcFlags=%#x cbBuffer=%#x dataRepresentation=%d buffer=%p:{%.*Rhxs} reserved1=%p reserved2={%p,%p,%p,%p,%p}\n",
+ pMsg->iMethod == 0 ? "QueryInterface" :
+ pMsg->iMethod == 1 ? "AddRef" :
+ pMsg->iMethod == 2 ? "ReleaseRef" :
+ pMsg->iMethod == 3 ? "DllGetClassObject" : "Unknown", pMsg->rpcFlags, pMsg->cbBuffer,
+ pMsg->dataRepresentation, pMsg->Buffer, RT_VALID_PTR(pMsg->Buffer) ? pMsg->cbBuffer : 0, pMsg->Buffer,
+ pMsg->reserved1, pMsg->reserved2[0], pMsg->reserved2[1], pMsg->reserved2[2], pMsg->reserved2[3], pMsg->reserved2[4]));
+ hrc = E_ACCESSDENIED;
+ }
+ return hrc;
+}
+
+/**
+ * Replaces the IRundown InvokeStub method with Rundown_InvokeStub so we can
+ * reject remote calls to a couple of misdesigned methods.
+ *
+ * Also replaces the IDLLHost for the same reasons.
+ */
+void PatchComBugs(void)
+{
+ static volatile bool s_fPatched = false;
+ if (s_fPatched)
+ return;
+
+ /*
+ * The combase.dll / ole32.dll is exporting a DllGetClassObject function
+ * that is implemented using NdrDllGetClassObject just like our own
+ * proxy/stub DLL. This means we can get at the stub interface lists,
+ * since what NdrDllGetClassObject has CStdPSFactoryBuffer as layout.
+ *
+ * Note! Tried using CoRegisterPSClsid instead of this mess, but no luck.
+ */
+ /* Locate the COM DLL, it must be loaded by now: */
+ HMODULE hmod = GetModuleHandleW(L"COMBASE.DLL");
+ if (!hmod)
+ hmod = GetModuleHandleW(L"OLE32.DLL"); /* w7 */
+ AssertReturnVoid(hmod != NULL);
+
+ /* Resolve the class getter: */
+ LPFNGETCLASSOBJECT pfnGetClassObject = (LPFNGETCLASSOBJECT)GetProcAddress(hmod, "DllGetClassObject");
+ AssertReturnVoid(pfnGetClassObject != NULL);
+
+ /* Get the factory instance: */
+ static const CLSID s_PSOlePrx32ClsId = {0x00000320,0x0000,0x0000,{0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
+ CStdPSFactoryBuffer *pFactoryBuffer = NULL;
+ HRESULT hrc = pfnGetClassObject(s_PSOlePrx32ClsId, IID_IPSFactoryBuffer, (void **)&pFactoryBuffer);
+ AssertMsgReturnVoid(SUCCEEDED(hrc), ("hrc=%Rhrc\n", hrc));
+ AssertReturnVoid(pFactoryBuffer != NULL);
+
+ /*
+ * Search thru the file list for the interface we want to patch.
+ */
+ static const IID s_IID_Rundown = {0x00000134,0x0000,0x0000,{0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
+ static const IID s_IID_DLLHost = {0x00000141,0x0000,0x0000,{0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
+ decltype(CStdStubBuffer_Invoke) *pfnInvoke = (decltype(pfnInvoke))GetProcAddress(hmod, "CStdStubBuffer_Invoke");
+ if (!pfnInvoke)
+ pfnInvoke = (decltype(pfnInvoke))GetProcAddress(GetModuleHandleW(L"RPCRT4.DLL"), "CStdStubBuffer_Invoke");
+
+ unsigned cPatched = 0;
+ unsigned cAlreadyPatched = 0;
+ Assert(pFactoryBuffer->pProxyFileList != NULL);
+ for (ProxyFileInfo const **ppCur = pFactoryBuffer->pProxyFileList; *ppCur != NULL; ppCur++)
+ {
+ ProxyFileInfo const *pCur = *ppCur;
+
+ if (pCur->pStubVtblList)
+ {
+ for (PCInterfaceStubVtblList const *ppCurStub = pCur->pStubVtblList; *ppCurStub != NULL; ppCurStub++)
+ {
+ PCInterfaceStubVtblList const pCurStub = *ppCurStub;
+ IID const *piid = pCurStub->header.piid;
+ if (piid)
+ {
+ if (IsEqualIID(*piid, s_IID_Rundown))
+ {
+ if (pCurStub->Vtbl.Invoke == pfnInvoke)
+ {
+ DWORD fOld = 0;
+ if (VirtualProtect(&pCurStub->Vtbl.Invoke, sizeof(pCurStub->Vtbl.Invoke), PAGE_READWRITE, &fOld))
+ {
+ pCurStub->Vtbl.Invoke = Rundown_InvokeStub;
+ VirtualProtect(&pCurStub->Vtbl.Invoke, sizeof(pCurStub->Vtbl.Invoke), fOld, &fOld);
+ cPatched++;
+ }
+ else
+ AssertMsgFailed(("%d\n", GetLastError()));
+ }
+ else
+ cAlreadyPatched++;
+ }
+ else if (IsEqualIID(*piid, s_IID_DLLHost))
+ {
+ if (pCurStub->Vtbl.Invoke == pfnInvoke)
+ {
+ DWORD fOld = 0;
+ if (VirtualProtect(&pCurStub->Vtbl.Invoke, sizeof(pCurStub->Vtbl.Invoke), PAGE_READWRITE, &fOld))
+ {
+ pCurStub->Vtbl.Invoke = DLLHost_InvokeStub;
+ VirtualProtect(&pCurStub->Vtbl.Invoke, sizeof(pCurStub->Vtbl.Invoke), fOld, &fOld);
+ cPatched++;
+ }
+ else
+ AssertMsgFailed(("%d\n", GetLastError()));
+ }
+ else
+ cAlreadyPatched++;
+ }
+ }
+ }
+ }
+ }
+
+ /* done */
+ pFactoryBuffer->lpVtbl->Release((IPSFactoryBuffer *)pFactoryBuffer);
+
+ /*
+ * If we patched anything we should try prevent being unloaded.
+ */
+ if (cPatched > 0)
+ {
+ s_fPatched = true;
+ HMODULE hmodSelf;
+ AssertLogRelMsg(GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN,
+ (LPCWSTR)(uintptr_t)Rundown_InvokeStub, &hmodSelf),
+ ("last error: %u; Rundown_InvokeStub=%p\n", GetLastError(), Rundown_InvokeStub));
+ }
+ AssertLogRelMsg(cAlreadyPatched + cPatched >= 2,
+ ("COM patching of IRundown/IDLLHost failed! (%d+%d)\n", cAlreadyPatched, cPatched));
+}
+
+
+/**
+ * The COM main thread handle. (The first caller of com::Initialize().)
+ */
+static RTTHREAD volatile gCOMMainThread = NIL_RTTHREAD;
+
+/**
+ * Number of Initialize() calls on the main thread.
+ */
+static uint32_t gCOMMainInitCount = 0;
+
+#endif /* !defined(VBOX_WITH_XPCOM) */
+
+
+/**
+ * Initializes the COM runtime.
+ *
+ * This method must be called on each thread of the client application that
+ * wants to access COM facilities. The initialization must be performed before
+ * calling any other COM method or attempting to instantiate COM objects.
+ *
+ * On platforms using XPCOM, this method uses the following scheme to search for
+ * XPCOM runtime:
+ *
+ * 1. If the VBOX_APP_HOME environment variable is set, the path it specifies
+ * is used to search XPCOM libraries and components. If this method fails to
+ * initialize XPCOM runtime using this path, it will immediately return a
+ * failure and will NOT check for other paths as described below.
+ *
+ * 2. If VBOX_APP_HOME is not set, this methods tries the following paths in the
+ * given order:
+ *
+ * a) Compiled-in application data directory (as returned by
+ * RTPathAppPrivateArch())
+ * b) "/usr/lib/virtualbox" (Linux only)
+ * c) "/opt/VirtualBox" (Linux only)
+ *
+ * The first path for which the initialization succeeds will be used.
+ *
+ * On MS COM platforms, the COM runtime is provided by the system and does not
+ * need to be searched for.
+ *
+ * Once the COM subsystem is no longer necessary on a given thread, Shutdown()
+ * must be called to free resources allocated for it. Note that a thread may
+ * call Initialize() several times but for each of tese calls there must be a
+ * corresponding Shutdown() call.
+ *
+ * @return S_OK on success and a COM result code in case of failure.
+ */
+HRESULT Initialize(uint32_t fInitFlags /*=VBOX_COM_INIT_F_DEFAULT*/)
+{
+ HRESULT rc = E_FAIL;
+
+#if !defined(VBOX_WITH_XPCOM)
+
+# ifdef VBOX_WITH_AUTO_COM_REG_UPDATE
+ /*
+ * First time we're called in a process, we refresh the VBox COM
+ * registrations. Use a global mutex to prevent updating when there are
+ * API users already active, as that could lead to a bit of a mess.
+ */
+ if ( (fInitFlags & VBOX_COM_INIT_F_AUTO_REG_UPDATE)
+ && gCOMMainThread == NIL_RTTHREAD)
+ {
+ SetLastError(ERROR_SUCCESS);
+ HANDLE hLeakIt = CreateMutexW(NULL/*pSecAttr*/, FALSE, L"Global\\VirtualBoxComLazyRegistrationMutant");
+ DWORD dwErr = GetLastError();
+ AssertMsg(dwErr == ERROR_SUCCESS || dwErr == ERROR_ALREADY_EXISTS || dwErr == ERROR_ACCESS_DENIED, ("%u\n", dwErr));
+ if (dwErr == ERROR_SUCCESS)
+ {
+ char szPath[RTPATH_MAX];
+ int vrc = RTPathAppPrivateArch(szPath, sizeof(szPath));
+ if (RT_SUCCESS(vrc))
+# ifndef VBOX_IN_32_ON_64_MAIN_API
+ vrc = RTPathAppend(szPath, sizeof(szPath),
+ RT_MAKE_U64(((PKUSER_SHARED_DATA)MM_SHARED_USER_DATA_VA)->NtMinorVersion,
+ ((PKUSER_SHARED_DATA)MM_SHARED_USER_DATA_VA)->NtMajorVersion)
+ >= RT_MAKE_U64(1/*Lo*/,6/*Hi*/)
+ ? "VBoxProxyStub.dll" : "VBoxProxyStubLegacy.dll");
+# else
+ vrc = RTPathAppend(szPath, sizeof(szPath), "x86\\VBoxProxyStub-x86.dll");
+# endif
+ if (RT_SUCCESS(vrc))
+ {
+ RTLDRMOD hMod;
+ vrc = RTLdrLoad(szPath, &hMod);
+ if (RT_SUCCESS(vrc))
+ {
+ union
+ {
+ void *pv;
+ DECLCALLBACKMEMBER(uint32_t, pfnRegUpdate,(void));
+ } u;
+ vrc = RTLdrGetSymbol(hMod, "VbpsUpdateRegistrations", &u.pv);
+ if (RT_SUCCESS(vrc))
+ u.pfnRegUpdate();
+ /* Just keep it loaded. */
+ }
+ }
+ Assert(hLeakIt != NULL); NOREF(hLeakIt);
+ }
+ }
+# endif
+
+ /*
+ * We initialize COM in GUI thread in STA, to be compliant with QT and
+ * OLE requirments (for example to allow D&D), while other threads
+ * initialized in regular MTA. To allow fast proxyless access from
+ * GUI thread to COM objects, we explicitly provide our COM objects
+ * with free threaded marshaller.
+ * !!!!! Please think twice before touching this code !!!!!
+ */
+ DWORD flags = fInitFlags & VBOX_COM_INIT_F_GUI
+ ? COINIT_APARTMENTTHREADED
+ | COINIT_SPEED_OVER_MEMORY
+ : COINIT_MULTITHREADED
+ | COINIT_DISABLE_OLE1DDE
+ | COINIT_SPEED_OVER_MEMORY;
+
+ rc = CoInitializeEx(NULL, flags);
+
+ /* the overall result must be either S_OK or S_FALSE (S_FALSE means
+ * "already initialized using the same apartment model") */
+ AssertMsg(rc == S_OK || rc == S_FALSE, ("rc=%08X\n", rc));
+
+#if defined(VBOX_WITH_SDS)
+ // Setup COM Security to enable impersonation
+ HRESULT hrGUICoInitializeSecurity = CoInitializeSecurity(NULL,
+ -1,
+ NULL,
+ NULL,
+ RPC_C_AUTHN_LEVEL_DEFAULT,
+ RPC_C_IMP_LEVEL_IMPERSONATE,
+ NULL,
+ EOAC_NONE,
+ NULL);
+ NOREF(hrGUICoInitializeSecurity);
+ Assert(SUCCEEDED(hrGUICoInitializeSecurity) || hrGUICoInitializeSecurity == RPC_E_TOO_LATE);
+#endif
+
+ /*
+ * IRundown has unsafe two methods we need to patch to prevent remote access.
+ * Do that before we start using COM and open ourselves to possible attacks.
+ */
+ if (!(fInitFlags & VBOX_COM_INIT_F_NO_COM_PATCHING))
+ PatchComBugs();
+
+ /* To be flow compatible with the XPCOM case, we return here if this isn't
+ * the main thread or if it isn't its first initialization call.
+ * Note! CoInitializeEx and CoUninitialize does it's own reference
+ * counting, so this exercise is entirely for the EventQueue init. */
+ bool fRc;
+ RTTHREAD hSelf = RTThreadSelf();
+ if (hSelf != NIL_RTTHREAD)
+ ASMAtomicCmpXchgHandle(&gCOMMainThread, hSelf, NIL_RTTHREAD, fRc);
+ else
+ fRc = false;
+
+ if (fInitFlags & VBOX_COM_INIT_F_GUI)
+ Assert(RTThreadIsMain(hSelf));
+
+ if (!fRc)
+ {
+ if ( gCOMMainThread == hSelf
+ && SUCCEEDED(rc))
+ gCOMMainInitCount++;
+
+ AssertComRC(rc);
+ return rc;
+ }
+ Assert(RTThreadIsMain(hSelf));
+
+ /* this is the first main thread initialization */
+ Assert(gCOMMainInitCount == 0);
+ if (SUCCEEDED(rc))
+ gCOMMainInitCount = 1;
+
+#else /* !defined(VBOX_WITH_XPCOM) */
+
+ /* Unused here */
+ RT_NOREF(fInitFlags);
+
+ if (ASMAtomicXchgBool(&gIsXPCOMInitialized, true) == true)
+ {
+ /* XPCOM is already initialized on the main thread, no special
+ * initialization is necessary on additional threads. Just increase
+ * the init counter if it's a main thread again (to correctly support
+ * nested calls to Initialize()/Shutdown() for compatibility with
+ * Win32). */
+
+ nsCOMPtr<nsIEventQueue> eventQ;
+ rc = NS_GetMainEventQ(getter_AddRefs(eventQ));
+
+ if (NS_SUCCEEDED(rc))
+ {
+ PRBool isOnMainThread = PR_FALSE;
+ rc = eventQ->IsOnCurrentThread(&isOnMainThread);
+ if (NS_SUCCEEDED(rc) && isOnMainThread)
+ ++gXPCOMInitCount;
+ }
+
+ AssertComRC(rc);
+ return rc;
+ }
+ Assert(RTThreadIsMain(RTThreadSelf()));
+
+ /* this is the first initialization */
+ gXPCOMInitCount = 1;
+
+ /* prepare paths for registry files */
+ char szCompReg[RTPATH_MAX];
+ char szXptiDat[RTPATH_MAX];
+
+ int vrc = GetVBoxUserHomeDirectory(szCompReg, sizeof(szCompReg));
+ if (vrc == VERR_ACCESS_DENIED)
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ AssertRCReturn(vrc, NS_ERROR_FAILURE);
+ vrc = RTStrCopy(szXptiDat, sizeof(szXptiDat), szCompReg);
+ AssertRCReturn(vrc, NS_ERROR_FAILURE);
+# ifdef VBOX_IN_32_ON_64_MAIN_API
+ vrc = RTPathAppend(szCompReg, sizeof(szCompReg), "compreg-x86.dat");
+ AssertRCReturn(vrc, NS_ERROR_FAILURE);
+ vrc = RTPathAppend(szXptiDat, sizeof(szXptiDat), "xpti-x86.dat");
+ AssertRCReturn(vrc, NS_ERROR_FAILURE);
+# else
+ vrc = RTPathAppend(szCompReg, sizeof(szCompReg), "compreg.dat");
+ AssertRCReturn(vrc, NS_ERROR_FAILURE);
+ vrc = RTPathAppend(szXptiDat, sizeof(szXptiDat), "xpti.dat");
+ AssertRCReturn(vrc, NS_ERROR_FAILURE);
+# endif
+
+ LogFlowFunc(("component registry : \"%s\"\n", szCompReg));
+ LogFlowFunc(("XPTI data file : \"%s\"\n", szXptiDat));
+
+ static const char *kAppPathsToProbe[] =
+ {
+ NULL, /* 0: will use VBOX_APP_HOME */
+ NULL, /* 1: will try RTPathAppPrivateArch(), correctly installed release builds will never go further */
+ NULL, /* 2: will try parent directory of RTPathAppPrivateArch(), only for testcases in non-hardened builds */
+ /* There used to be hard coded paths, but they only caused trouble
+ * because they often led to mixing of builds or even versions.
+ * If you feel tempted to add anything here, think again. They would
+ * only be used if option 1 would not work, which is a sign of a big
+ * problem, as it returns a fixed location defined at compile time.
+ * It is better to fail than blindly trying to cover the problem. */
+ };
+
+ /* Find out the directory where VirtualBox binaries are located */
+ for (size_t i = 0; i < RT_ELEMENTS(kAppPathsToProbe); ++ i)
+ {
+ char szAppHomeDir[RTPATH_MAX];
+
+ if (i == 0)
+ {
+ /* Use VBOX_APP_HOME if present */
+ vrc = RTEnvGetEx(RTENV_DEFAULT, "VBOX_APP_HOME", szAppHomeDir, sizeof(szAppHomeDir), NULL);
+ if (vrc == VERR_ENV_VAR_NOT_FOUND)
+ continue;
+ AssertRC(vrc);
+ }
+ else if (i == 1)
+ {
+ /* Use RTPathAppPrivateArch() first */
+ vrc = RTPathAppPrivateArch(szAppHomeDir, sizeof(szAppHomeDir));
+ AssertRC(vrc);
+ }
+ else if (i == 2)
+ {
+# ifdef VBOX_WITH_HARDENING
+ continue;
+# else /* !VBOX_WITH_HARDENING */
+ /* Use parent of RTPathAppPrivateArch() if ends with "testcase" */
+ vrc = RTPathAppPrivateArch(szAppHomeDir, sizeof(szAppHomeDir));
+ AssertRC(vrc);
+ vrc = RTPathStripTrailingSlash(szAppHomeDir);
+ AssertRC(vrc);
+ char *filename = RTPathFilename(szAppHomeDir);
+ if (!filename || strcmp(filename, "testcase"))
+ continue;
+ RTPathStripFilename(szAppHomeDir);
+# endif /* !VBOX_WITH_HARDENING */
+ }
+ else
+ {
+ /* Iterate over all other paths */
+ RTStrCopy(szAppHomeDir, sizeof(szAppHomeDir), kAppPathsToProbe[i]);
+ vrc = VINF_SUCCESS;
+ }
+ if (RT_FAILURE(vrc))
+ {
+ rc = NS_ERROR_FAILURE;
+ continue;
+ }
+ char szCompDir[RTPATH_MAX];
+ vrc = RTStrCopy(szCompDir, sizeof(szCompDir), szAppHomeDir);
+ if (RT_FAILURE(vrc))
+ {
+ rc = NS_ERROR_FAILURE;
+ continue;
+ }
+ vrc = RTPathAppend(szCompDir, sizeof(szCompDir), "components");
+ if (RT_FAILURE(vrc))
+ {
+ rc = NS_ERROR_FAILURE;
+ continue;
+ }
+ LogFlowFunc(("component directory : \"%s\"\n", szCompDir));
+
+ nsCOMPtr<DirectoryServiceProvider> dsProv;
+ dsProv = new DirectoryServiceProvider();
+ if (dsProv)
+ rc = dsProv->init(szCompReg, szXptiDat, szCompDir, szAppHomeDir);
+ else
+ rc = NS_ERROR_OUT_OF_MEMORY;
+ if (NS_FAILED(rc))
+ break;
+
+ /* Setup the application path for NS_InitXPCOM2. Note that we properly
+ * answer the NS_XPCOM_CURRENT_PROCESS_DIR query in our directory
+ * service provider but it seems to be activated after the directory
+ * service is used for the first time (see the source NS_InitXPCOM2). So
+ * use the same value here to be on the safe side. */
+ nsCOMPtr <nsIFile> appDir;
+ {
+ char *appDirCP = NULL;
+ vrc = RTStrUtf8ToCurrentCP(&appDirCP, szAppHomeDir);
+ if (RT_SUCCESS(vrc))
+ {
+ nsCOMPtr<nsILocalFile> file;
+ rc = NS_NewNativeLocalFile(nsEmbedCString(appDirCP),
+ PR_FALSE, getter_AddRefs(file));
+ if (NS_SUCCEEDED(rc))
+ appDir = do_QueryInterface(file, &rc);
+
+ RTStrFree(appDirCP);
+ }
+ else
+ rc = NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(rc))
+ break;
+
+ /* Set VBOX_XPCOM_HOME to the same app path to make XPCOM sources that
+ * still use it instead of the directory service happy */
+ vrc = RTEnvSetEx(RTENV_DEFAULT, "VBOX_XPCOM_HOME", szAppHomeDir);
+ AssertRC(vrc);
+
+ /* Finally, initialize XPCOM */
+ {
+ nsCOMPtr<nsIServiceManager> serviceManager;
+ rc = NS_InitXPCOM2(getter_AddRefs(serviceManager), appDir, dsProv);
+ if (NS_SUCCEEDED(rc))
+ {
+ nsCOMPtr<nsIComponentRegistrar> registrar =
+ do_QueryInterface(serviceManager, &rc);
+ if (NS_SUCCEEDED(rc))
+ {
+ rc = registrar->AutoRegister(nsnull);
+ if (NS_SUCCEEDED(rc))
+ {
+ /* We succeeded, stop probing paths */
+ LogFlowFunc(("Succeeded.\n"));
+ break;
+ }
+ }
+ }
+ }
+
+ /* clean up before the new try */
+ HRESULT rc2 = NS_ShutdownXPCOM(nsnull);
+ if (SUCCEEDED(rc))
+ rc = rc2;
+
+ if (i == 0)
+ {
+ /* We failed with VBOX_APP_HOME, don't probe other paths */
+ break;
+ }
+ }
+
+#endif /* !defined(VBOX_WITH_XPCOM) */
+
+ AssertComRCReturnRC(rc);
+
+ // for both COM and XPCOM, we only get here if this is the main thread;
+ // only then initialize the autolock system (AutoLock.cpp)
+ Assert(RTThreadIsMain(RTThreadSelf()));
+ util::InitAutoLockSystem();
+
+ /*
+ * Init the main event queue (ASSUMES it cannot fail).
+ */
+ if (SUCCEEDED(rc))
+ NativeEventQueue::init();
+
+ return rc;
+}
+
+HRESULT Shutdown()
+{
+ HRESULT rc = S_OK;
+
+#if !defined(VBOX_WITH_XPCOM)
+
+ /* EventQueue::uninit reference counting fun. */
+ RTTHREAD hSelf = RTThreadSelf();
+ if ( hSelf == gCOMMainThread
+ && hSelf != NIL_RTTHREAD)
+ {
+ if (-- gCOMMainInitCount == 0)
+ {
+ NativeEventQueue::uninit();
+ ASMAtomicWriteHandle(&gCOMMainThread, NIL_RTTHREAD);
+ }
+ }
+
+ CoUninitialize();
+
+#else /* !defined(VBOX_WITH_XPCOM) */
+
+ nsCOMPtr<nsIEventQueue> eventQ;
+ rc = NS_GetMainEventQ(getter_AddRefs(eventQ));
+
+ if (NS_SUCCEEDED(rc) || rc == NS_ERROR_NOT_AVAILABLE)
+ {
+ /* NS_ERROR_NOT_AVAILABLE seems to mean that
+ * nsIEventQueue::StopAcceptingEvents() has been called (see
+ * nsEventQueueService.cpp). We hope that this error code always means
+ * just that in this case and assume that we're on the main thread
+ * (it's a kind of unexpected behavior if a non-main thread ever calls
+ * StopAcceptingEvents() on the main event queue). */
+
+ PRBool isOnMainThread = PR_FALSE;
+ if (NS_SUCCEEDED(rc))
+ {
+ rc = eventQ->IsOnCurrentThread(&isOnMainThread);
+ eventQ = nsnull; /* early release before shutdown */
+ }
+ else
+ {
+ isOnMainThread = RTThreadIsMain(RTThreadSelf());
+ rc = NS_OK;
+ }
+
+ if (NS_SUCCEEDED(rc) && isOnMainThread)
+ {
+ /* only the main thread needs to uninitialize XPCOM and only if
+ * init counter drops to zero */
+ if (--gXPCOMInitCount == 0)
+ {
+ NativeEventQueue::uninit();
+ rc = NS_ShutdownXPCOM(nsnull);
+
+ /* This is a thread initialized XPCOM and set gIsXPCOMInitialized to
+ * true. Reset it back to false. */
+ bool wasInited = ASMAtomicXchgBool(&gIsXPCOMInitialized, false);
+ Assert(wasInited == true);
+ NOREF(wasInited);
+ }
+ }
+ }
+
+#endif /* !defined(VBOX_WITH_XPCOM) */
+
+ AssertComRC(rc);
+
+ return rc;
+}
+
+} /* namespace com */
diff --git a/src/VBox/Main/glue/string-base64.cpp b/src/VBox/Main/glue/string-base64.cpp
new file mode 100644
index 00000000..b9b85a74
--- /dev/null
+++ b/src/VBox/Main/glue/string-base64.cpp
@@ -0,0 +1,61 @@
+/* $Id: string-base64.cpp $ */
+/** @file
+ * MS COM / XPCOM Abstraction Layer - UTF-8 and UTF-16 string classes, BASE64 bits.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "VBox/com/string.h"
+
+#include <iprt/base64.h>
+#include <iprt/errcore.h>
+
+
+namespace com
+{
+
+HRESULT Bstr::base64Encode(const void *pvData, size_t cbData, bool fLineBreaks /*= false*/)
+{
+ uint32_t const fFlags = fLineBreaks ? RTBASE64_FLAGS_EOL_LF : RTBASE64_FLAGS_NO_LINE_BREAKS;
+ size_t cwcEncoded = RTBase64EncodedUtf16LengthEx(cbData, fFlags);
+ HRESULT hrc = reserveNoThrow(cwcEncoded + 1);
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = RTBase64EncodeUtf16Ex(pvData, cbData, fFlags, mutableRaw(), cwcEncoded + 1, &cwcEncoded);
+ AssertRCReturnStmt(vrc, setNull(), E_FAIL);
+ hrc = joltNoThrow(cwcEncoded);
+ }
+ return hrc;
+}
+
+int Bstr::base64Decode(void *pvData, size_t cbData, size_t *pcbActual /*= NULL*/, PRTUTF16 *ppwszEnd /*= NULL*/)
+{
+ return RTBase64DecodeUtf16Ex(raw(), RTSTR_MAX, pvData, cbData, pcbActual, ppwszEnd);
+}
+
+ssize_t Bstr::base64DecodedSize(PRTUTF16 *ppwszEnd /*= NULL*/)
+{
+ return RTBase64DecodedUtf16SizeEx(raw(), RTSTR_MAX, ppwszEnd);
+}
+
+} /* namespace com */
diff --git a/src/VBox/Main/glue/string.cpp b/src/VBox/Main/glue/string.cpp
new file mode 100644
index 00000000..581e4133
--- /dev/null
+++ b/src/VBox/Main/glue/string.cpp
@@ -0,0 +1,1037 @@
+/* $Id: string.cpp $ */
+/** @file
+ * MS COM / XPCOM Abstraction Layer - UTF-8 and UTF-16 string classes.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "VBox/com/string.h"
+
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+namespace com
+{
+
+// BSTR representing a null wide char with 32 bits of length prefix (0);
+// this will work on Windows as well as other platforms where BSTR does
+// not use length prefixes
+const OLECHAR g_achEmptyBstr[3] = { 0, 0, 0 };
+const BSTR g_bstrEmpty = (BSTR)&g_achEmptyBstr[2];
+
+/* static */
+const Bstr Bstr::Empty; /* default ctor is OK */
+
+
+Bstr &Bstr::printf(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ HRESULT hrc = printfVNoThrow(pszFormat, va);
+ va_end(va);
+#ifdef RT_EXCEPTIONS_ENABLED
+ if (hrc == S_OK)
+ { /* likely */ }
+ else
+ throw std::bad_alloc();
+#else
+ Assert(hrc == S_OK); RT_NOREF(hrc);
+#endif
+ return *this;
+}
+
+HRESULT Bstr::printfNoThrow(const char *pszFormat, ...) RT_NOEXCEPT
+{
+ va_list va;
+ va_start(va, pszFormat);
+ HRESULT hrc = printfVNoThrow(pszFormat, va);
+ va_end(va);
+ return hrc;
+}
+
+
+Bstr &Bstr::printfV(const char *pszFormat, va_list va)
+{
+ HRESULT hrc = printfVNoThrow(pszFormat, va);
+#ifdef RT_EXCEPTIONS_ENABLED
+ if (hrc == S_OK)
+ { /* likely */ }
+ else
+ throw std::bad_alloc();
+#else
+ Assert(hrc == S_OK); RT_NOREF(hrc);
+#endif
+ return *this;
+}
+
+struct BSTRNOTHROW
+{
+ Bstr *pThis;
+ size_t cwcAlloc;
+ size_t offDst;
+ HRESULT hrc;
+};
+
+/**
+ * Callback used with RTStrFormatV by Bstr::printfVNoThrow.
+ *
+ * @returns The number of bytes added (not used).
+ *
+ * @param pvArg Pointer to a BSTRNOTHROW structure.
+ * @param pachChars The characters to append.
+ * @param cbChars The number of characters. 0 on the final callback.
+ */
+/*static*/ DECLCALLBACK(size_t)
+Bstr::printfOutputCallbackNoThrow(void *pvArg, const char *pachChars, size_t cbChars) RT_NOEXCEPT
+{
+ BSTRNOTHROW *pArgs = (BSTRNOTHROW *)pvArg;
+ if (cbChars)
+ {
+ size_t cwcAppend;
+ int rc = ::RTStrCalcUtf16LenEx(pachChars, cbChars, &cwcAppend);
+ AssertRCReturnStmt(rc, pArgs->hrc = E_UNEXPECTED, 0);
+
+ /*
+ * Ensure we've got sufficient memory.
+ */
+ Bstr *pThis = pArgs->pThis;
+ size_t const cwcBoth = pArgs->offDst + cwcAppend;
+ if (cwcBoth >= pArgs->cwcAlloc)
+ {
+ if (pArgs->hrc == S_OK)
+ {
+ /* Double the buffer size, if it's less that _1M. Align sizes like
+ for append. */
+ size_t cwcAlloc = RT_ALIGN_Z(pArgs->cwcAlloc, 128);
+ cwcAlloc += RT_MIN(cwcAlloc, _1M);
+ if (cwcAlloc <= cwcBoth)
+ cwcAlloc = RT_ALIGN_Z(cwcBoth + 1, 512);
+ pArgs->hrc = pThis->reserveNoThrow(cwcAlloc, true /*fForce*/);
+ AssertMsgReturn(pArgs->hrc == S_OK, ("cwcAlloc=%#zx\n", cwcAlloc), 0);
+ pArgs->cwcAlloc = cwcAlloc;
+ }
+ else
+ return 0;
+ }
+
+ /*
+ * Do the conversion.
+ */
+ PRTUTF16 pwszDst = pThis->m_bstr + pArgs->offDst;
+ Assert(pArgs->cwcAlloc > pArgs->offDst);
+ rc = ::RTStrToUtf16Ex(pachChars, cbChars, &pwszDst, pArgs->cwcAlloc - pArgs->offDst, &cwcAppend);
+ AssertRCReturnStmt(rc, pArgs->hrc = E_UNEXPECTED, 0);
+ pArgs->offDst += cwcAppend;
+ }
+ return cbChars;
+}
+
+HRESULT Bstr::printfVNoThrow(const char *pszFormat, va_list va) RT_NOEXCEPT
+{
+ cleanup();
+
+ BSTRNOTHROW Args = { this, 0, 0, S_OK };
+ RTStrFormatV(printfOutputCallbackNoThrow, &Args, NULL, NULL, pszFormat, va);
+ if (Args.hrc == S_OK)
+ {
+ Args.hrc = joltNoThrow(Args.offDst);
+ if (Args.hrc == S_OK)
+ return S_OK;
+ }
+
+ cleanup();
+ return Args.hrc;
+}
+
+void Bstr::copyFromN(const char *a_pszSrc, size_t a_cchMax)
+{
+ /*
+ * Initialize m_bstr first in case of throws further down in the code, then
+ * check for empty input (m_bstr == NULL means empty, there are no NULL
+ * strings).
+ */
+ m_bstr = NULL;
+ if (!a_cchMax || !a_pszSrc || !*a_pszSrc)
+ return;
+
+ /*
+ * Calculate the length and allocate a BSTR string buffer of the right
+ * size, i.e. optimize heap usage.
+ */
+ size_t cwc;
+ int vrc = ::RTStrCalcUtf16LenEx(a_pszSrc, a_cchMax, &cwc);
+ if (RT_SUCCESS(vrc))
+ {
+ m_bstr = ::SysAllocStringByteLen(NULL, (unsigned)(cwc * sizeof(OLECHAR)));
+ if (RT_LIKELY(m_bstr))
+ {
+ PRTUTF16 pwsz = (PRTUTF16)m_bstr;
+ vrc = ::RTStrToUtf16Ex(a_pszSrc, a_cchMax, &pwsz, cwc + 1, NULL);
+ if (RT_SUCCESS(vrc))
+ return;
+
+ /* This should not happen! */
+ AssertRC(vrc);
+ cleanup();
+ }
+ }
+ else /* ASSUME: input is valid Utf-8. Fake out of memory error. */
+ AssertLogRelMsgFailed(("%Rrc %.*Rhxs\n", vrc, RTStrNLen(a_pszSrc, a_cchMax), a_pszSrc));
+#ifdef RT_EXCEPTIONS_ENABLED
+ throw std::bad_alloc();
+#endif
+}
+
+HRESULT Bstr::cleanupAndCopyFromNoThrow(const char *a_pszSrc, size_t a_cchMax) RT_NOEXCEPT
+{
+ /*
+ * Check for empty input (m_bstr == NULL means empty, there are no NULL strings).
+ */
+ cleanup();
+ if (!a_cchMax || !a_pszSrc || !*a_pszSrc)
+ return S_OK;
+
+ /*
+ * Calculate the length and allocate a BSTR string buffer of the right
+ * size, i.e. optimize heap usage.
+ */
+ HRESULT hrc;
+ size_t cwc;
+ int vrc = ::RTStrCalcUtf16LenEx(a_pszSrc, a_cchMax, &cwc);
+ if (RT_SUCCESS(vrc))
+ {
+ m_bstr = ::SysAllocStringByteLen(NULL, (unsigned)(cwc * sizeof(OLECHAR)));
+ if (RT_LIKELY(m_bstr))
+ {
+ PRTUTF16 pwsz = (PRTUTF16)m_bstr;
+ vrc = ::RTStrToUtf16Ex(a_pszSrc, a_cchMax, &pwsz, cwc + 1, NULL);
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+
+ /* This should not happen! */
+ AssertRC(vrc);
+ cleanup();
+ hrc = E_UNEXPECTED;
+ }
+ else
+ hrc = E_OUTOFMEMORY;
+ }
+ else
+ {
+ /* Unexpected: Invalid UTF-8 input. */
+ AssertLogRelMsgFailed(("%Rrc %.*Rhxs\n", vrc, RTStrNLen(a_pszSrc, a_cchMax), a_pszSrc));
+ hrc = E_UNEXPECTED;
+ }
+ return hrc;
+}
+
+
+int Bstr::compareUtf8(const char *a_pszRight, CaseSensitivity a_enmCase /*= CaseSensitive*/) const
+{
+ PCRTUTF16 pwszLeft = m_bstr;
+
+ /*
+ * Special case for null/empty strings. Unlike RTUtf16Cmp we
+ * treat null and empty equally.
+ */
+ if (!pwszLeft)
+ return !a_pszRight || *a_pszRight == '\0' ? 0 : -1;
+ if (!a_pszRight)
+ return *pwszLeft == '\0' ? 0 : 1;
+
+ /*
+ * Compare with a UTF-8 string by enumerating them char by char.
+ */
+ for (;;)
+ {
+ RTUNICP ucLeft;
+ int rc = RTUtf16GetCpEx(&pwszLeft, &ucLeft);
+ AssertRCReturn(rc, 1);
+
+ RTUNICP ucRight;
+ rc = RTStrGetCpEx(&a_pszRight, &ucRight);
+ AssertRCReturn(rc, -1);
+ if (ucLeft == ucRight)
+ {
+ if (ucLeft)
+ continue;
+ return 0;
+ }
+
+ if (a_enmCase == CaseInsensitive)
+ {
+ if (RTUniCpToUpper(ucLeft) == RTUniCpToUpper(ucRight))
+ continue;
+ if (RTUniCpToLower(ucLeft) == RTUniCpToLower(ucRight))
+ continue;
+ }
+
+ return ucLeft < ucRight ? -1 : 1;
+ }
+}
+
+
+bool Bstr::startsWith(Bstr const &a_rStart) const
+{
+ return RTUtf16NCmp(m_bstr, a_rStart.m_bstr, a_rStart.length()) == 0;
+}
+
+
+bool Bstr::startsWith(RTCString const &a_rStart) const
+{
+ return RTUtf16NCmpUtf8(m_bstr, a_rStart.c_str(), RTSTR_MAX, a_rStart.length()) == 0;
+}
+
+
+bool Bstr::startsWith(const char *a_pszStart) const
+{
+ return RTUtf16NCmpUtf8(m_bstr, a_pszStart, RTSTR_MAX, strlen(a_pszStart)) == 0;
+}
+
+
+#ifndef VBOX_WITH_XPCOM
+
+HRESULT Bstr::joltNoThrow(ssize_t cwcNew /* = -1*/) RT_NOEXCEPT
+{
+ if (m_bstr)
+ {
+ size_t const cwcAlloc = ::SysStringLen(m_bstr);
+ size_t const cwcActual = cwcNew < 0 ? ::RTUtf16Len(m_bstr) : (size_t)cwcNew;
+ Assert(cwcNew < 0 || cwcActual == ::RTUtf16Len(m_bstr));
+ if (cwcActual != cwcAlloc)
+ {
+ Assert(cwcActual <= cwcAlloc);
+ Assert((unsigned int)cwcActual == cwcActual);
+
+ /* Official way: Reallocate the string. We could of course just update the size-prefix if we dared... */
+ if (!::SysReAllocStringLen(&m_bstr, NULL, (unsigned int)cwcActual))
+ {
+ AssertFailed();
+ return E_OUTOFMEMORY;
+ }
+ }
+ }
+ else
+ Assert(cwcNew <= 0);
+ return S_OK;
+}
+
+
+void Bstr::jolt(ssize_t cwcNew /* = -1*/)
+{
+ HRESULT hrc = joltNoThrow(cwcNew);
+# ifdef RT_EXCEPTIONS_ENABLED
+ if (hrc != S_OK)
+ throw std::bad_alloc();
+# else
+ Assert(hrc == S_OK); RT_NOREF(hrc);
+# endif
+}
+
+#endif /* !VBOX_WITH_XPCOM */
+
+
+HRESULT Bstr::reserveNoThrow(size_t cwcMin, bool fForce /*= false*/) RT_NOEXCEPT
+{
+ /* If not forcing the string to the cwcMin length, check cwcMin against the
+ current string length: */
+ if (!fForce)
+ {
+ size_t cwcCur = m_bstr ? ::SysStringLen(m_bstr) : 0;
+ if (cwcCur >= cwcMin)
+ return S_OK;
+ }
+
+ /* The documentation for SysReAllocStringLen hints about it being allergic
+ to NULL in some way or another, so we call SysAllocStringLen directly
+ when appropriate: */
+ if (m_bstr)
+ AssertReturn(::SysReAllocStringLen(&m_bstr, NULL, (unsigned int)cwcMin) != FALSE, E_OUTOFMEMORY);
+ else if (cwcMin > 0)
+ {
+ m_bstr = ::SysAllocStringLen(NULL, (unsigned int)cwcMin);
+ AssertReturn(m_bstr, E_OUTOFMEMORY);
+ }
+
+ return S_OK;
+}
+
+
+void Bstr::reserve(size_t cwcMin, bool fForce /*= false*/)
+{
+ HRESULT hrc = reserveNoThrow(cwcMin, fForce);
+#ifdef RT_EXCEPTIONS_ENABLED
+ if (hrc != S_OK)
+ throw std::bad_alloc();
+#else
+ Assert(hrc == S_OK); RT_NOREF(hrc);
+#endif
+}
+
+
+Bstr &Bstr::append(const Bstr &rThat)
+{
+ if (rThat.isNotEmpty())
+ return appendWorkerUtf16(rThat.m_bstr, rThat.length());
+ return *this;
+}
+
+
+HRESULT Bstr::appendNoThrow(const Bstr &rThat) RT_NOEXCEPT
+{
+ if (rThat.isNotEmpty())
+ return appendWorkerUtf16NoThrow(rThat.m_bstr, rThat.length());
+ return S_OK;
+}
+
+
+Bstr &Bstr::append(const RTCString &rThat)
+{
+ if (rThat.isNotEmpty())
+ return appendWorkerUtf8(rThat.c_str(), rThat.length());
+ return *this;
+}
+
+
+HRESULT Bstr::appendNoThrow(const RTCString &rThat) RT_NOEXCEPT
+{
+ if (rThat.isNotEmpty())
+ return appendWorkerUtf8NoThrow(rThat.c_str(), rThat.length());
+ return S_OK;
+}
+
+
+Bstr &Bstr::append(CBSTR pwszSrc)
+{
+ if (pwszSrc && *pwszSrc)
+ return appendWorkerUtf16(pwszSrc, RTUtf16Len(pwszSrc));
+ return *this;
+}
+
+
+HRESULT Bstr::appendNoThrow(CBSTR pwszSrc) RT_NOEXCEPT
+{
+ if (pwszSrc && *pwszSrc)
+ return appendWorkerUtf16NoThrow(pwszSrc, RTUtf16Len(pwszSrc));
+ return S_OK;
+}
+
+
+Bstr &Bstr::append(const char *pszSrc)
+{
+ if (pszSrc && *pszSrc)
+ return appendWorkerUtf8(pszSrc, strlen(pszSrc));
+ return *this;
+}
+
+
+HRESULT Bstr::appendNoThrow(const char *pszSrc) RT_NOEXCEPT
+{
+ if (pszSrc && *pszSrc)
+ return appendWorkerUtf8NoThrow(pszSrc, strlen(pszSrc));
+ return S_OK;
+}
+
+
+Bstr &Bstr::append(const Bstr &rThat, size_t offStart, size_t cwcMax /*= RTSTR_MAX*/)
+{
+ size_t cwcSrc = rThat.length();
+ if (offStart < cwcSrc)
+ return appendWorkerUtf16(rThat.raw() + offStart, RT_MIN(cwcSrc - offStart, cwcMax));
+ return *this;
+}
+
+
+HRESULT Bstr::appendNoThrow(const Bstr &rThat, size_t offStart, size_t cwcMax /*= RTSTR_MAX*/) RT_NOEXCEPT
+{
+ size_t cwcSrc = rThat.length();
+ if (offStart < cwcSrc)
+ return appendWorkerUtf16NoThrow(rThat.raw() + offStart, RT_MIN(cwcSrc - offStart, cwcMax));
+ return S_OK;
+}
+
+
+Bstr &Bstr::append(const RTCString &rThat, size_t offStart, size_t cchMax /*= RTSTR_MAX*/)
+{
+ if (offStart < rThat.length())
+ return appendWorkerUtf8(rThat.c_str() + offStart, RT_MIN(rThat.length() - offStart, cchMax));
+ return *this;
+}
+
+
+HRESULT Bstr::appendNoThrow(const RTCString &rThat, size_t offStart, size_t cchMax /*= RTSTR_MAX*/) RT_NOEXCEPT
+{
+ if (offStart < rThat.length())
+ return appendWorkerUtf8NoThrow(rThat.c_str() + offStart, RT_MIN(rThat.length() - offStart, cchMax));
+ return S_OK;
+}
+
+
+Bstr &Bstr::append(CBSTR pwszThat, size_t cchMax)
+{
+ return appendWorkerUtf16(pwszThat, RTUtf16NLen(pwszThat, cchMax));
+}
+
+
+HRESULT Bstr::appendNoThrow(CBSTR pwszThat, size_t cchMax) RT_NOEXCEPT
+{
+ return appendWorkerUtf16NoThrow(pwszThat, RTUtf16NLen(pwszThat, cchMax));
+}
+
+
+Bstr &Bstr::append(const char *pszThat, size_t cchMax)
+{
+ return appendWorkerUtf8(pszThat, RTStrNLen(pszThat, cchMax));
+}
+
+
+HRESULT Bstr::appendNoThrow(const char *pszThat, size_t cchMax) RT_NOEXCEPT
+{
+ return appendWorkerUtf8NoThrow(pszThat, RTStrNLen(pszThat, cchMax));
+}
+
+
+Bstr &Bstr::append(char ch)
+{
+ AssertMsg(ch > 0 && ch < 127, ("%#x\n", ch));
+ return appendWorkerUtf8(&ch, 1);
+}
+
+
+HRESULT Bstr::appendNoThrow(char ch) RT_NOEXCEPT
+{
+ AssertMsg(ch > 0 && ch < 127, ("%#x\n", ch));
+ return appendWorkerUtf8NoThrow(&ch, 1);
+}
+
+
+Bstr &Bstr::appendCodePoint(RTUNICP uc)
+{
+ RTUTF16 wszTmp[3];
+ PRTUTF16 pwszEnd = RTUtf16PutCp(wszTmp, uc);
+ *pwszEnd = '\0';
+ return appendWorkerUtf16(&wszTmp[0], pwszEnd - &wszTmp[0]);
+}
+
+
+HRESULT Bstr::appendCodePointNoThrow(RTUNICP uc) RT_NOEXCEPT
+{
+ RTUTF16 wszTmp[3];
+ PRTUTF16 pwszEnd = RTUtf16PutCp(wszTmp, uc);
+ *pwszEnd = '\0';
+ return appendWorkerUtf16NoThrow(&wszTmp[0], pwszEnd - &wszTmp[0]);
+}
+
+
+Bstr &Bstr::appendWorkerUtf16(PCRTUTF16 pwszSrc, size_t cwcSrc)
+{
+ size_t cwcOld = length();
+ size_t cwcTotal = cwcOld + cwcSrc;
+ reserve(cwcTotal, true /*fForce*/);
+ if (cwcSrc)
+ memcpy(&m_bstr[cwcOld], pwszSrc, cwcSrc * sizeof(RTUTF16));
+ m_bstr[cwcTotal] = '\0';
+ return *this;
+}
+
+
+HRESULT Bstr::appendWorkerUtf16NoThrow(PCRTUTF16 pwszSrc, size_t cwcSrc) RT_NOEXCEPT
+{
+ size_t cwcOld = length();
+ size_t cwcTotal = cwcOld + cwcSrc;
+ HRESULT hrc = reserveNoThrow(cwcTotal, true /*fForce*/);
+ if (hrc == S_OK)
+ {
+ if (cwcSrc)
+ memcpy(&m_bstr[cwcOld], pwszSrc, cwcSrc * sizeof(RTUTF16));
+ m_bstr[cwcTotal] = '\0';
+ }
+ return hrc;
+}
+
+
+Bstr &Bstr::appendWorkerUtf8(const char *pszSrc, size_t cchSrc)
+{
+ size_t cwcSrc;
+ int rc = RTStrCalcUtf16LenEx(pszSrc, cchSrc, &cwcSrc);
+#ifdef RT_EXCEPTIONS_ENABLED
+ AssertRCStmt(rc, throw std::bad_alloc());
+#else
+ AssertRCReturn(rc, *this);
+#endif
+
+ size_t cwcOld = length();
+ size_t cwcTotal = cwcOld + cwcSrc;
+ reserve(cwcTotal, true /*fForce*/);
+ if (cwcSrc)
+ {
+ PRTUTF16 pwszDst = &m_bstr[cwcOld];
+ rc = RTStrToUtf16Ex(pszSrc, cchSrc, &pwszDst, cwcSrc + 1, NULL);
+#ifdef RT_EXCEPTIONS_ENABLED
+ AssertRCStmt(rc, throw std::bad_alloc());
+#else
+ AssertRC(rc);
+#endif
+ }
+ m_bstr[cwcTotal] = '\0';
+ return *this;
+}
+
+
+HRESULT Bstr::appendWorkerUtf8NoThrow(const char *pszSrc, size_t cchSrc) RT_NOEXCEPT
+{
+ size_t cwcSrc;
+ int rc = RTStrCalcUtf16LenEx(pszSrc, cchSrc, &cwcSrc);
+ AssertRCStmt(rc, E_INVALIDARG);
+
+ size_t cwcOld = length();
+ size_t cwcTotal = cwcOld + cwcSrc;
+ HRESULT hrc = reserveNoThrow(cwcTotal, true /*fForce*/);
+ AssertReturn(hrc == S_OK, hrc);
+ if (cwcSrc)
+ {
+ PRTUTF16 pwszDst = &m_bstr[cwcOld];
+ rc = RTStrToUtf16Ex(pszSrc, cchSrc, &pwszDst, cwcSrc + 1, NULL);
+ AssertRCStmt(rc, E_INVALIDARG);
+ }
+ m_bstr[cwcTotal] = '\0';
+ return S_OK;
+}
+
+
+Bstr &Bstr::appendPrintf(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ HRESULT hrc = appendPrintfVNoThrow(pszFormat, va);
+ va_end(va);
+#ifdef RT_EXCEPTIONS_ENABLED
+ if (hrc != S_OK)
+ throw std::bad_alloc();
+#else
+ Assert(hrc == S_OK); RT_NOREF(hrc);
+#endif
+ return *this;
+}
+
+
+HRESULT Bstr::appendPrintfNoThrow(const char *pszFormat, ...) RT_NOEXCEPT
+{
+ va_list va;
+ va_start(va, pszFormat);
+ HRESULT hrc = appendPrintfVNoThrow(pszFormat, va);
+ va_end(va);
+ return hrc;
+}
+
+
+Bstr &Bstr::appendPrintfV(const char *pszFormat, va_list va)
+{
+ HRESULT hrc = appendPrintfVNoThrow(pszFormat, va);
+#ifdef RT_EXCEPTIONS_ENABLED
+ if (hrc != S_OK)
+ throw std::bad_alloc();
+#else
+ Assert(hrc == S_OK); RT_NOREF(hrc);
+#endif
+ return *this;
+}
+
+
+HRESULT Bstr::appendPrintfVNoThrow(const char *pszFormat, va_list va) RT_NOEXCEPT
+{
+ size_t const cwcOld = length();
+ BSTRNOTHROW Args = { this, cwcOld, cwcOld, S_OK };
+
+ RTStrFormatV(printfOutputCallbackNoThrow, &Args, NULL, NULL, pszFormat, va);
+ if (Args.hrc == S_OK)
+ {
+ Args.hrc = joltNoThrow(Args.offDst);
+ if (Args.hrc == S_OK)
+ return S_OK;
+ }
+
+ if (m_bstr)
+ m_bstr[cwcOld] = '\0';
+ return Args.hrc;
+}
+
+
+Bstr &Bstr::erase(size_t offStart /*= 0*/, size_t cwcLength /*= RTSTR_MAX*/) RT_NOEXCEPT
+{
+ size_t cwc = length();
+ if (offStart < cwc)
+ {
+ if (cwcLength >= cwc - offStart)
+ {
+ if (!offStart)
+ cleanup();
+ else
+ {
+ /* Trail removal, nothing to move. */
+ m_bstr[offStart] = '\0';
+ joltNoThrow(offStart); /* not entirely optimal... */
+ }
+ }
+ else if (cwcLength > 0)
+ {
+ /* Pull up the tail to offStart. */
+ size_t cwcAfter = cwc - offStart - cwcLength;
+ memmove(&m_bstr[offStart], &m_bstr[offStart + cwcLength], cwcAfter * sizeof(*m_bstr));
+ cwc -= cwcLength;
+ m_bstr[cwc] = '\0';
+ joltNoThrow(cwc); /* not entirely optimal... */
+ }
+ }
+ return *this;
+}
+
+
+void Bstr::cleanup()
+{
+ if (m_bstr)
+ {
+ ::SysFreeString(m_bstr);
+ m_bstr = NULL;
+ }
+}
+
+
+void Bstr::copyFrom(const OLECHAR *a_bstrSrc)
+{
+ if (a_bstrSrc && *a_bstrSrc)
+ {
+ m_bstr = ::SysAllocString(a_bstrSrc);
+#ifdef RT_EXCEPTIONS_ENABLED
+ if (RT_LIKELY(m_bstr))
+ { /* likely */ }
+ else
+ throw std::bad_alloc();
+#else
+ Assert(m_bstr);
+#endif
+ }
+ else
+ m_bstr = NULL;
+}
+
+
+void Bstr::cleanupAndCopyFrom(const OLECHAR *a_bstrSrc)
+{
+ cleanup();
+ copyFrom(a_bstrSrc);
+}
+
+
+HRESULT Bstr::cleanupAndCopyFromEx(const OLECHAR *a_bstrSrc) RT_NOEXCEPT
+{
+ cleanup();
+
+ if (a_bstrSrc && *a_bstrSrc)
+ {
+ m_bstr = ::SysAllocString(a_bstrSrc);
+ if (RT_LIKELY(m_bstr))
+ { /* likely */ }
+ else
+ return E_OUTOFMEMORY;
+ }
+ else
+ m_bstr = NULL;
+ return S_OK;
+}
+
+
+
+/*********************************************************************************************************************************
+* Utf8Str Implementation *
+*********************************************************************************************************************************/
+
+/* static */
+const Utf8Str Utf8Str::Empty; /* default ctor is OK */
+
+#if defined(VBOX_WITH_XPCOM)
+void Utf8Str::cloneTo(char **pstr) const
+{
+ size_t cb = length() + 1;
+ *pstr = (char *)nsMemory::Alloc(cb);
+ if (RT_LIKELY(*pstr))
+ memcpy(*pstr, c_str(), cb);
+ else
+#ifdef RT_EXCEPTIONS_ENABLED
+ throw std::bad_alloc();
+#else
+ AssertFailed();
+#endif
+}
+
+HRESULT Utf8Str::cloneToEx(char **pstr) const
+{
+ size_t cb = length() + 1;
+ *pstr = (char *)nsMemory::Alloc(cb);
+ if (RT_LIKELY(*pstr))
+ {
+ memcpy(*pstr, c_str(), cb);
+ return S_OK;
+ }
+ return E_OUTOFMEMORY;
+}
+#endif
+
+HRESULT Utf8Str::cloneToEx(BSTR *pbstr) const RT_NOEXCEPT
+{
+ if (!pbstr)
+ return S_OK;
+ Bstr bstr;
+ HRESULT hrc = bstr.assignEx(*this);
+ if (SUCCEEDED(hrc))
+ hrc = bstr.detachToEx(pbstr);
+ return hrc;
+}
+
+Utf8Str& Utf8Str::stripTrailingSlash()
+{
+ if (length())
+ {
+ ::RTPathStripTrailingSlash(m_psz);
+ jolt();
+ }
+ return *this;
+}
+
+Utf8Str& Utf8Str::stripFilename()
+{
+ if (length())
+ {
+ RTPathStripFilename(m_psz);
+ jolt();
+ }
+ return *this;
+}
+
+Utf8Str& Utf8Str::stripPath()
+{
+ if (length())
+ {
+ char *pszName = ::RTPathFilename(m_psz);
+ if (pszName)
+ {
+ size_t cchName = length() - (pszName - m_psz);
+ memmove(m_psz, pszName, cchName + 1);
+ jolt();
+ }
+ else
+ cleanup();
+ }
+ return *this;
+}
+
+Utf8Str& Utf8Str::stripSuffix()
+{
+ if (length())
+ {
+ RTPathStripSuffix(m_psz);
+ jolt();
+ }
+ return *this;
+}
+
+size_t Utf8Str::parseKeyValue(Utf8Str &a_rKey, Utf8Str &a_rValue, size_t a_offStart /* = 0*/,
+ const Utf8Str &a_rPairSeparator /*= ","*/, const Utf8Str &a_rKeyValueSeparator /*= "="*/) const
+{
+ /* Find the end of the next pair, skipping empty pairs.
+ Note! The skipping allows us to pass the return value of a parseKeyValue()
+ call as offStart to the next call. */
+ size_t offEnd;
+ while ( a_offStart == (offEnd = find(&a_rPairSeparator, a_offStart))
+ && offEnd != npos)
+ a_offStart++;
+
+ /* Look for a key/value separator before the end of the pair.
+ ASSUMES npos value returned by find when the substring is not found is
+ really high. */
+ size_t offKeyValueSep = find(&a_rKeyValueSeparator, a_offStart);
+ if (offKeyValueSep < offEnd)
+ {
+ a_rKey = substr(a_offStart, offKeyValueSep - a_offStart);
+ if (offEnd == npos)
+ offEnd = m_cch; /* No confusing npos when returning strings. */
+ a_rValue = substr(offKeyValueSep + 1, offEnd - offKeyValueSep - 1);
+ }
+ else
+ {
+ a_rKey.setNull();
+ a_rValue.setNull();
+ }
+
+ return offEnd;
+}
+
+/**
+ * Internal function used in Utf8Str copy constructors and assignment when
+ * copying from a UTF-16 string.
+ *
+ * As with the RTCString::copyFrom() variants, this unconditionally sets the
+ * members to a copy of the given other strings and makes no assumptions about
+ * previous contents. This can therefore be used both in copy constructors,
+ * when member variables have no defined value, and in assignments after having
+ * called cleanup().
+ *
+ * This variant converts from a UTF-16 string, most probably from
+ * a Bstr assignment.
+ *
+ * @param a_pbstr The source string. The caller guarantees that this
+ * is valid UTF-16.
+ * @param a_cwcMax The number of characters to be copied. If set to RTSTR_MAX,
+ * the entire string will be copied.
+ *
+ * @sa RTCString::copyFromN
+ */
+void Utf8Str::copyFrom(CBSTR a_pbstr, size_t a_cwcMax)
+{
+ if (a_pbstr && *a_pbstr)
+ {
+ int vrc = RTUtf16ToUtf8Ex((PCRTUTF16)a_pbstr,
+ a_cwcMax, // size_t cwcString: translate entire string
+ &m_psz, // char **ppsz: output buffer
+ 0, // size_t cch: if 0, func allocates buffer in *ppsz
+ &m_cch); // size_t *pcch: receives the size of the output string, excluding the terminator.
+ if (RT_SUCCESS(vrc))
+ m_cbAllocated = m_cch + 1;
+ else
+ {
+ if ( vrc != VERR_NO_STR_MEMORY
+ && vrc != VERR_NO_MEMORY)
+ {
+ /* ASSUME: input is valid Utf-16. Fake out of memory error. */
+ AssertLogRelMsgFailed(("%Rrc %.*Rhxs\n", vrc, RTUtf16Len(a_pbstr) * sizeof(RTUTF16), a_pbstr));
+ }
+
+ m_cch = 0;
+ m_cbAllocated = 0;
+ m_psz = NULL;
+
+#ifdef RT_EXCEPTIONS_ENABLED
+ throw std::bad_alloc();
+#else
+ AssertFailed();
+#endif
+ }
+ }
+ else
+ {
+ m_cch = 0;
+ m_cbAllocated = 0;
+ m_psz = NULL;
+ }
+}
+
+/**
+ * A variant of Utf8Str::copyFrom that does not throw any exceptions but returns
+ * E_OUTOFMEMORY instead.
+ *
+ * @param a_pbstr The source string.
+ * @returns S_OK or E_OUTOFMEMORY.
+ */
+HRESULT Utf8Str::copyFromEx(CBSTR a_pbstr)
+{
+ if (a_pbstr && *a_pbstr)
+ {
+ int vrc = RTUtf16ToUtf8Ex((PCRTUTF16)a_pbstr,
+ RTSTR_MAX, // size_t cwcString: translate entire string
+ &m_psz, // char **ppsz: output buffer
+ 0, // size_t cch: if 0, func allocates buffer in *ppsz
+ &m_cch); // size_t *pcch: receives the size of the output string, excluding the terminator.
+ if (RT_SUCCESS(vrc))
+ m_cbAllocated = m_cch + 1;
+ else
+ {
+ if ( vrc != VERR_NO_STR_MEMORY
+ && vrc != VERR_NO_MEMORY)
+ {
+ /* ASSUME: input is valid Utf-16. Fake out of memory error. */
+ AssertLogRelMsgFailed(("%Rrc %.*Rhxs\n", vrc, RTUtf16Len(a_pbstr) * sizeof(RTUTF16), a_pbstr));
+ }
+
+ m_cch = 0;
+ m_cbAllocated = 0;
+ m_psz = NULL;
+
+ return E_OUTOFMEMORY;
+ }
+ }
+ else
+ {
+ m_cch = 0;
+ m_cbAllocated = 0;
+ m_psz = NULL;
+ }
+ return S_OK;
+}
+
+
+/**
+ * A variant of Utf8Str::copyFromN that does not throw any exceptions but
+ * returns E_OUTOFMEMORY instead.
+ *
+ * @param a_pcszSrc The source string.
+ * @param a_offSrc Start offset to copy from.
+ * @param a_cchSrc How much to copy
+ * @returns S_OK or E_OUTOFMEMORY.
+ *
+ * @remarks This calls cleanup() first, so the caller doesn't have to. (Saves
+ * code space.)
+ */
+HRESULT Utf8Str::copyFromExNComRC(const char *a_pcszSrc, size_t a_offSrc, size_t a_cchSrc)
+{
+ Assert(!a_cchSrc || !m_psz || (uintptr_t)&a_pcszSrc[a_offSrc] - (uintptr_t)m_psz >= (uintptr_t)m_cbAllocated);
+ cleanup();
+ if (a_cchSrc)
+ {
+ m_psz = RTStrAlloc(a_cchSrc + 1);
+ if (RT_LIKELY(m_psz))
+ {
+ m_cch = a_cchSrc;
+ m_cbAllocated = a_cchSrc + 1;
+ memcpy(m_psz, a_pcszSrc + a_offSrc, a_cchSrc);
+ m_psz[a_cchSrc] = '\0';
+ }
+ else
+ {
+ m_cch = 0;
+ m_cbAllocated = 0;
+ return E_OUTOFMEMORY;
+ }
+ }
+ else
+ {
+ m_cch = 0;
+ m_cbAllocated = 0;
+ m_psz = NULL;
+ }
+ return S_OK;
+}
+
+} /* namespace com */
diff --git a/src/VBox/Main/glue/tests/Makefile b/src/VBox/Main/glue/tests/Makefile
new file mode 100644
index 00000000..f15af5ac
--- /dev/null
+++ b/src/VBox/Main/glue/tests/Makefile
@@ -0,0 +1,81 @@
+## @file
+# Makefile for a sample/testcase using the 'glue' Java API bindings
+
+#
+# Copyright (C) 2010-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+#
+# User serviceable parts: adjust the following lines appropriately
+#
+
+# The host OS: linux, solaris, win, darwin, freebsd
+HOSTOS=linux
+# Absolute (!) path to the VirtualBox install directory
+VBOX_BIN = /opt/VirtualBox
+# Path to the sdk directory of the unpacked VirtualBox SDK
+VBOX_SDK = ../../..
+# On Windows, if you want to use the COM API: install directory of Jacob lib
+JACOB_DIR =
+# Extra classpath entries needed for compiling/running the sample
+CLASSPATH =
+# Java compiler to use
+JAVAC = javac
+# Java VM to use
+JAVA = java
+
+
+#
+# No changes should be necessary after this point.
+#
+
+ifeq ($(HOSTOS),win)
+ JACOB_JAR=$(JACOB_DIR)/jacob.jar
+ VBOX_JAR=$(VBOX_SDK)/bindings/mscom/java/vboxjmscom.jar
+ SEP=\;
+ JAVA_COM_ARGS += -Djava.library.path=$(JACOB_DIR)
+ CLASSPATH += $(JACOB_JAR)$(SEP)
+else
+ VBOX_JAR=$(VBOX_SDK)/bindings/xpcom/java/vboxjxpcom.jar
+ SEP=:
+ JAVA_COM_ARGS += -Dvbox.home=$(VBOX_BIN)
+endif
+
+VBOX_JAR_WS=$(VBOX_SDK)/bindings/webservice/java/jax-ws/vboxjws.jar
+
+
+all: ws/TestVBox.class com/TestVBox.class
+
+test: ws/test-TestVBox com/test-TestVBox
+
+com/TestVBox.class:
+ @mkdir com 2>/dev/null || true
+ $(JAVAC) -d com -cp $(VBOX_JAR)$(SEP)$(CLASSPATH) TestVBox.java
+
+com/test-TestVBox: com/TestVBox.class
+ $(JAVA) $(JAVA_COM_ARGS) -cp com$(SEP)$(VBOX_JAR)$(SEP)$(CLASSPATH) TestVBox
+
+ws/TestVBox.class:
+ @mkdir ws 2>/dev/null || true
+ $(JAVAC) -d ws -cp $(VBOX_JAR_WS)$(SEP)$(CLASSPATH) TestVBox.java
+
+ws/test-TestVBox: ws/TestVBox.class
+ $(JAVA) $(JAVA_WS_ARGS) -cp ws$(SEP)$(VBOX_JAR_WS)$(SEP)$(CLASSPATH) TestVBox -w -url http://localhost:18083/
diff --git a/src/VBox/Main/glue/tests/TestVBox.java b/src/VBox/Main/glue/tests/TestVBox.java
new file mode 100644
index 00000000..dbd8a4b3
--- /dev/null
+++ b/src/VBox/Main/glue/tests/TestVBox.java
@@ -0,0 +1,310 @@
+/* $Id: TestVBox.java $ */
+/*! file
+ * Small sample/testcase which demonstrates that the same source code can
+ * be used to connect to the webservice and (XP)COM APIs.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+import org.virtualbox_6_2.*;
+import java.util.List;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.math.BigInteger;
+
+public class TestVBox
+{
+ static void processEvent(IEvent ev)
+ {
+ System.out.println("got event: " + ev);
+ VBoxEventType type = ev.getType();
+ System.out.println("type = " + type);
+ switch (type)
+ {
+ case OnMachineStateChanged:
+ {
+ IMachineStateChangedEvent mcse = IMachineStateChangedEvent.queryInterface(ev);
+ if (mcse == null)
+ System.out.println("Cannot query an interface");
+ else
+ System.out.println("mid=" + mcse.getMachineId());
+ break;
+ }
+ }
+ }
+
+ static class EventHandler
+ {
+ EventHandler() {}
+ public void handleEvent(IEvent ev)
+ {
+ try {
+ processEvent(ev);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+ }
+
+ static void testEvents(VirtualBoxManager mgr, IEventSource es)
+ {
+ // active mode for Java doesn't fully work yet, and using passive
+ // is more portable (the only mode for MSCOM and WS) and thus generally
+ // recommended
+ IEventListener listener = es.createListener();
+
+ es.registerListener(listener, Arrays.asList(VBoxEventType.Any), false);
+
+ try {
+ for (int i = 0; i < 50; i++)
+ {
+ System.out.print(".");
+ IEvent ev = es.getEvent(listener, 500);
+ if (ev != null)
+ {
+ processEvent(ev);
+ es.eventProcessed(listener, ev);
+ }
+ // process system event queue
+ mgr.waitForEvents(0);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ es.unregisterListener(listener);
+ }
+
+ static void testEnumeration(VirtualBoxManager mgr, IVirtualBox vbox)
+ {
+ List<IMachine> machs = vbox.getMachines();
+ for (IMachine m : machs)
+ {
+ String name;
+ Long ram = 0L;
+ boolean hwvirtEnabled = false, hwvirtNestedPaging = false;
+ boolean paeEnabled = false;
+ boolean inaccessible = false;
+ try
+ {
+ name = m.getName();
+ ram = m.getMemorySize();
+ hwvirtEnabled = m.getHWVirtExProperty(HWVirtExPropertyType.Enabled);
+ hwvirtNestedPaging = m.getHWVirtExProperty(HWVirtExPropertyType.NestedPaging);
+ paeEnabled = m.getCPUProperty(CPUPropertyType.PAE);
+ String osType = m.getOSTypeId();
+ IGuestOSType foo = vbox.getGuestOSType(osType);
+ }
+ catch (VBoxException e)
+ {
+ name = "<inaccessible>";
+ inaccessible = true;
+ }
+ System.out.println("VM name: " + name);
+ if (!inaccessible)
+ {
+ System.out.println(" RAM size: " + ram + "MB"
+ + ", HWVirt: " + hwvirtEnabled
+ + ", Nested Paging: " + hwvirtNestedPaging
+ + ", PAE: " + paeEnabled);
+ }
+ }
+ // process system event queue
+ mgr.waitForEvents(0);
+ }
+
+ static boolean progressBar(VirtualBoxManager mgr, IProgress p, long waitMillis)
+ {
+ long end = System.currentTimeMillis() + waitMillis;
+ while (!p.getCompleted())
+ {
+ // process system event queue
+ mgr.waitForEvents(0);
+ // wait for completion of the task, but at most 200 msecs
+ p.waitForCompletion(200);
+ if (System.currentTimeMillis() >= end)
+ return false;
+ }
+ return true;
+ }
+
+ static void testStart(VirtualBoxManager mgr, IVirtualBox vbox)
+ {
+ IMachine m = vbox.getMachines().get(0);
+ String name = m.getName();
+ System.out.println("\nAttempting to start VM '" + name + "'");
+
+ ISession session = mgr.getSessionObject();
+ ArrayList<String> env = new ArrayList<String>();
+ IProgress p = m.launchVMProcess(session, "gui", env);
+ progressBar(mgr, p, 10000);
+ session.unlockMachine();
+ // process system event queue
+ mgr.waitForEvents(0);
+ }
+
+ static void testMultiServer()
+ {
+ VirtualBoxManager mgr1 = VirtualBoxManager.createInstance(null);
+ VirtualBoxManager mgr2 = VirtualBoxManager.createInstance(null);
+
+ try {
+ mgr1.connect("http://i7:18083", "", "");
+ mgr2.connect("http://main:18083", "", "");
+
+ IMachine m1 = mgr1.getVBox().getMachines().get(0);
+ IMachine m2 = mgr2.getVBox().getMachines().get(0);
+ String name1 = m1.getName();
+ String name2 = m2.getName();
+ ISession session1 = mgr1.getSessionObject();
+ ISession session2 = mgr2.getSessionObject();
+ ArrayList<String> env = new ArrayList<String>();
+ IProgress p1 = m1.launchVMProcess(session1, "gui", env);
+ IProgress p2 = m2.launchVMProcess(session2, "gui", env);
+ progressBar(mgr1, p1, 10000);
+ progressBar(mgr2, p2, 10000);
+ session1.unlockMachine();
+ session2.unlockMachine();
+ // process system event queue
+ mgr1.waitForEvents(0);
+ mgr2.waitForEvents(0);
+ } finally {
+ mgr1.cleanup();
+ mgr2.cleanup();
+ }
+ }
+
+ static void testReadLog(VirtualBoxManager mgr, IVirtualBox vbox)
+ {
+ IMachine m = vbox.getMachines().get(0);
+ long logNo = 0;
+ long off = 0;
+ long size = 16 * 1024;
+ while (true)
+ {
+ byte[] buf = m.readLog(logNo, off, size);
+ if (buf.length == 0)
+ break;
+ System.out.print(new String(buf));
+ off += buf.length;
+ }
+ // process system event queue
+ mgr.waitForEvents(0);
+ }
+
+ static void printErrorInfo(VBoxException e)
+ {
+ System.out.println("VBox error: " + e.getMessage());
+ System.out.println("Error cause message: " + e.getCause());
+ System.out.println("Overall result code: " + Integer.toHexString(e.getResultCode()));
+ int i = 1;
+ for (IVirtualBoxErrorInfo ei = e.getVirtualBoxErrorInfo(); ei != null; ei = ei.getNext(), i++)
+ {
+ System.out.println("Detail information #" + i);
+ System.out.println("Error mesage: " + ei.getText());
+ System.out.println("Result code: " + Integer.toHexString(ei.getResultCode()));
+ // optional, usually provides little additional information:
+ System.out.println("Component: " + ei.getComponent());
+ System.out.println("Interface ID: " + ei.getInterfaceID());
+ }
+ }
+
+
+ public static void main(String[] args)
+ {
+ VirtualBoxManager mgr = VirtualBoxManager.createInstance(null);
+
+ boolean ws = false;
+ String url = null;
+ String user = null;
+ String passwd = null;
+
+ for (int i = 0; i < args.length; i++)
+ {
+ if (args[i].equals("-w"))
+ ws = true;
+ else if (args[i].equals("-url"))
+ url = args[++i];
+ else if (args[i].equals("-user"))
+ user = args[++i];
+ else if (args[i].equals("-passwd"))
+ passwd = args[++i];
+ }
+
+ if (ws)
+ {
+ try {
+ mgr.connect(url, user, passwd);
+ } catch (VBoxException e) {
+ e.printStackTrace();
+ System.out.println("Cannot connect, start webserver first!");
+ }
+ }
+
+ try
+ {
+ IVirtualBox vbox = mgr.getVBox();
+ if (vbox != null)
+ {
+ System.out.println("VirtualBox version: " + vbox.getVersion() + "\n");
+ testEnumeration(mgr, vbox);
+ testReadLog(mgr, vbox);
+ testStart(mgr, vbox);
+ testEvents(mgr, vbox.getEventSource());
+
+ System.out.println("done, press Enter...");
+ int ch = System.in.read();
+ }
+ }
+ catch (VBoxException e)
+ {
+ printErrorInfo(e);
+ System.out.println("Java stack trace:");
+ e.printStackTrace();
+ }
+ catch (RuntimeException e)
+ {
+ System.out.println("Runtime error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ catch (java.io.IOException e)
+ {
+ e.printStackTrace();
+ }
+
+ // process system event queue
+ mgr.waitForEvents(0);
+ if (ws)
+ {
+ try {
+ mgr.disconnect();
+ } catch (VBoxException e) {
+ e.printStackTrace();
+ }
+ }
+
+ mgr.cleanup();
+
+ }
+
+}
diff --git a/src/VBox/Main/glue/tests/TestVBoxNATEngine.java b/src/VBox/Main/glue/tests/TestVBoxNATEngine.java
new file mode 100644
index 00000000..bebf5227
--- /dev/null
+++ b/src/VBox/Main/glue/tests/TestVBoxNATEngine.java
@@ -0,0 +1,206 @@
+/* $Id: TestVBoxNATEngine.java $ */
+/*!file
+ * Small sample/testcase which demonstrates that the same source code can
+ * be used to connect to the webservice and (XP)COM APIs.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+import org.virtualbox_5_0.*;
+import java.util.List;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.math.BigInteger;
+
+public class TestVBoxNATEngine
+{
+ void testEnumeration(VirtualBoxManager mgr, IVirtualBox vbox, IMachine vm)
+ {
+ String name;
+ boolean inaccessible = false;
+ /* different chipsets might have different number of attachments */
+ ChipsetType chipsetType = vm.getChipsetType();
+ INetworkAdapter adapters[] =
+ new INetworkAdapter[
+ vbox.getSystemProperties().getMaxNetworkAdapters(chipsetType).intValue()];
+
+ try
+ {
+ name = vm.getName();
+ /*
+ * Dump adapters and if it's got NAT attachment
+ * dump it settings
+ */
+
+ for (int nidx = 0; nidx < adapters.length; ++nidx)
+ {
+ /* select available and NATs only. */
+ adapters[nidx] = vm.getNetworkAdapter(new Long(nidx));
+ INetworkAdapter n = adapters[nidx];
+
+ if (n == null)
+ continue;
+ NetworkAttachmentType attachmentType = n.getAttachmentType();
+ if (attachmentType.equals(NetworkAttachmentType.NAT))
+ {
+ INATEngine nat = n.getNATEngine();
+ List<String> portForward = nat.getRedirects();
+ String pf = null;
+ Iterator<String> itPortForward = portForward.iterator();
+ for (;itPortForward.hasNext();)
+ {
+ pf = itPortForward.next();
+ System.out.println(name + ":NIC" + n.getSlot() /* name:NIC<slot number>*/
+ + " pf: " + pf); /* port-forward rule */
+ }
+ if (pf != null)
+ {
+ String pfAttributes[] = pf.split(",");
+ /* name,proto,hostip,host,hostport,guestip,guestport */
+ nat.removeRedirect(pfAttributes[0]);
+ nat.addRedirect("",
+ NATProtocol.fromValue(new Integer(pfAttributes[1]).longValue()),
+ pfAttributes[2],
+ new Integer(
+ new Integer(pfAttributes[3]).intValue() + 1),
+ pfAttributes[4],
+ new Integer(pfAttributes[5]));
+ }
+
+ }
+ }
+
+ }
+ catch (VBoxException e)
+ {
+ name = "<inaccessible>";
+ inaccessible = true;
+ }
+
+ // process system event queue
+ mgr.waitForEvents(0);
+ }
+
+ static void testStart(VirtualBoxManager mgr, IVirtualBox vbox, IMachine vm)
+ {
+ System.out.println("\nAttempting to start VM '" + vm.getName() + "'");
+ mgr.startVm(vm.getName(), null, 7000);
+ // process system event queue
+ mgr.waitForEvents(0);
+ }
+
+ public TestVBoxNATEngine(String[] args)
+ {
+ VirtualBoxManager mgr = VirtualBoxManager.createInstance(null);
+
+ boolean ws = false;
+ String url = null;
+ String user = null;
+ String passwd = null;
+ String vmname = null;
+ IMachine vm = null;
+
+ for (int i = 0; i<args.length; i++)
+ {
+ if ("-w".equals(args[i]))
+ ws = true;
+ else if ("-url".equals(args[i]))
+ url = args[++i];
+ else if ("-user".equals(args[i]))
+ user = args[++i];
+ else if ("-passwd".equals(args[i]))
+ passwd = args[++i];
+ else if ("-vm".equals(args[i]))
+ vmname = args[++i];
+ }
+
+ if (ws)
+ {
+ try {
+ mgr.connect(url, user, passwd);
+ } catch (VBoxException e) {
+ e.printStackTrace();
+ System.out.println("Cannot connect, start webserver first!");
+ }
+ }
+
+ try
+ {
+ IVirtualBox vbox = mgr.getVBox();
+ if (vbox != null)
+ {
+ if (vmname != null)
+ {
+ for (IMachine m:vbox.getMachines())
+ {
+ if (m.getName().equals(vmname))
+ {
+ vm = m;
+ break;
+ }
+ }
+
+ }
+ else
+ vm = vbox.getMachines().get(0);
+ System.out.println("VirtualBox version: " + vbox.getVersion() + "\n");
+ if (vm != null)
+ {
+ testEnumeration(mgr, vbox, vm);
+ testStart(mgr, vbox, vm);
+ }
+ System.out.println("done, press Enter...");
+ int ch = System.in.read();
+ }
+ }
+ catch (VBoxException e)
+ {
+ System.out.println("VBox error: "+e.getMessage()+" original="+e.getWrapped());
+ e.printStackTrace();
+ }
+ catch (java.io.IOException e)
+ {
+ e.printStackTrace();
+ }
+
+ // process system event queue
+ mgr.waitForEvents(0);
+
+ if (ws)
+ {
+ try {
+ mgr.disconnect();
+ } catch (VBoxException e) {
+ e.printStackTrace();
+ }
+ }
+ /* cleanup do the disconnect */
+ mgr.cleanup();
+
+ }
+ public static void main(String[] args)
+ {
+ new TestVBoxNATEngine(args);
+ }
+
+}
diff --git a/src/VBox/Main/glue/vbox-err-consts.sed b/src/VBox/Main/glue/vbox-err-consts.sed
new file mode 100644
index 00000000..9bc8b290
--- /dev/null
+++ b/src/VBox/Main/glue/vbox-err-consts.sed
@@ -0,0 +1,59 @@
+# $Id: vbox-err-consts.sed $
+## @file
+# IPRT - SED script for converting */err.h to a python dictionary.
+#
+
+#
+# Copyright (C) 2006-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# Header and footer text:
+1i <text>
+$a </text>
+
+# Handle text inside the markers.
+/SED-START/,/SED-END/{
+
+# if (#define) goto defines
+/^[[:space:]]*#[[:space:]]*define/b defines
+
+}
+
+# Everything else is deleted!
+:delete
+d
+b end
+
+
+##
+# Convert the defines
+:defines
+
+/^[[:space:]]*#[[:space:]]*define[[:space:]]*\([[:alnum:]_]*\)[[:space:](]*\([-+]*[[:space:]]*[[:digit:]][[:digit:]]*\)[[:space:])]*$/b define_okay
+b delete
+:define_okay
+s/^[[:space:]]*#[[:space:]]*define[[:space:]]*\([[:alnum:]_]*\)[[:space:](]*\([-+]*[[:space:]]*[[:digit:]][[:digit:]]*\)[[:space:])]*$/ '\1': \2,/
+
+b end
+
+
+# next expression
+:end
diff --git a/src/VBox/Main/glue/vboxapi.py b/src/VBox/Main/glue/vboxapi.py
new file mode 100755
index 00000000..eac6168f
--- /dev/null
+++ b/src/VBox/Main/glue/vboxapi.py
@@ -0,0 +1,1294 @@
+# -*- coding: utf-8 -*-
+# $Id: vboxapi.py $
+"""
+VirtualBox Python API Glue.
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2009-2022 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+in the VirtualBox distribution, in which case the provisions of the
+CDDL are applicable instead of those of the GPL.
+
+You may elect to license modified versions of this file under the
+terms and conditions of either the GPL or the CDDL or both.
+
+SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+"""
+__version__ = "$Revision: 153224 $"
+
+
+# Note! To set Python bitness on OSX use 'export VERSIONER_PYTHON_PREFER_32_BIT=yes'
+
+
+# Standard Python imports.
+import os
+import sys
+import traceback
+
+
+if sys.version_info >= (3, 0):
+ xrange = range
+ long = int
+
+#
+# Globals, environment and sys.path changes.
+#
+import platform
+VBoxBinDir = os.environ.get("VBOX_PROGRAM_PATH", None)
+VBoxSdkDir = os.environ.get("VBOX_SDK_PATH", None)
+
+if VBoxBinDir is None:
+ if platform.system() == 'Darwin':
+ VBoxBinDir = '/Applications/VirtualBox.app/Contents/MacOS'
+ else: # Will be set by the installer
+ VBoxBinDir = "%VBOX_INSTALL_PATH%"
+else:
+ VBoxBinDir = os.path.abspath(VBoxBinDir)
+
+if VBoxSdkDir is None:
+ if platform.system() == 'Darwin':
+ VBoxSdkDir = '/Applications/VirtualBox.app/Contents/MacOS/sdk'
+ else: # Will be set by the installer
+ VBoxSdkDir = "%VBOX_SDK_PATH%"
+else:
+ VBoxSdkDir = os.path.abspath(VBoxSdkDir)
+
+os.environ["VBOX_PROGRAM_PATH"] = VBoxBinDir
+os.environ["VBOX_SDK_PATH"] = VBoxSdkDir
+sys.path.append(VBoxBinDir)
+
+
+#
+# Import the generated VirtualBox constants.
+#
+from .VirtualBox_constants import VirtualBoxReflectionInfo
+
+
+class PerfCollector(object):
+ """ This class provides a wrapper over IPerformanceCollector in order to
+ get more 'pythonic' interface.
+
+ To begin collection of metrics use setup() method.
+
+ To get collected data use query() method.
+
+ It is possible to disable metric collection without changing collection
+ parameters with disable() method. The enable() method resumes metric
+ collection.
+ """
+
+ def __init__(self, mgr, vbox):
+ """ Initializes the instance.
+
+ """
+ self.mgr = mgr
+ self.isMscom = (mgr.type == 'MSCOM')
+ self.collector = vbox.performanceCollector
+
+ def setup(self, names, objects, period, nsamples):
+ """ Discards all previously collected values for the specified
+ metrics, sets the period of collection and the number of retained
+ samples, enables collection.
+ """
+ self.collector.setupMetrics(names, objects, period, nsamples)
+
+ def enable(self, names, objects):
+ """ Resumes metric collection for the specified metrics.
+ """
+ self.collector.enableMetrics(names, objects)
+
+ def disable(self, names, objects):
+ """ Suspends metric collection for the specified metrics.
+ """
+ self.collector.disableMetrics(names, objects)
+
+ def query(self, names, objects):
+ """ Retrieves collected metric values as well as some auxiliary
+ information. Returns an array of dictionaries, one dictionary per
+ metric. Each dictionary contains the following entries:
+ 'name': metric name
+ 'object': managed object this metric associated with
+ 'unit': unit of measurement
+ 'scale': divide 'values' by this number to get float numbers
+ 'values': collected data
+ 'values_as_string': pre-processed values ready for 'print' statement
+ """
+ # Get around the problem with input arrays returned in output
+ # parameters (see #3953) for MSCOM.
+ if self.isMscom:
+ (values, names, objects, names_out, objects_out, units, scales, sequence_numbers,
+ indices, lengths) = self.collector.queryMetricsData(names, objects)
+ else:
+ (values, names_out, objects_out, units, scales, sequence_numbers,
+ indices, lengths) = self.collector.queryMetricsData(names, objects)
+ out = []
+ for i in xrange(0, len(names_out)):
+ scale = int(scales[i])
+ if scale != 1:
+ fmt = '%.2f%s'
+ else:
+ fmt = '%d %s'
+ out.append({
+ 'name': str(names_out[i]),
+ 'object': str(objects_out[i]),
+ 'unit': str(units[i]),
+ 'scale': scale,
+ 'values': [int(values[j]) for j in xrange(int(indices[i]), int(indices[i]) + int(lengths[i]))],
+ 'values_as_string': '[' + ', '.join([fmt % (int(values[j]) / scale, units[i]) for j in
+ xrange(int(indices[i]), int(indices[i]) + int(lengths[i]))]) + ']'
+ })
+ return out
+
+
+#
+# Attribute hacks.
+#
+def ComifyName(name):
+ return name[0].capitalize() + name[1:]
+
+
+## This is for saving the original DispatchBaseClass __getattr__ and __setattr__
+# method references.
+_g_dCOMForward = {
+ 'getattr': None,
+ 'setattr': None,
+}
+
+
+def _CustomGetAttr(self, sAttr):
+ """ Our getattr replacement for DispatchBaseClass. """
+ # Fastpath.
+ oRet = self.__class__.__dict__.get(sAttr)
+ if oRet is not None:
+ return oRet
+
+ # Try case-insensitivity workaround for class attributes (COM methods).
+ sAttrLower = sAttr.lower()
+ for k in list(self.__class__.__dict__.keys()):
+ if k.lower() == sAttrLower:
+ setattr(self.__class__, sAttr, self.__class__.__dict__[k])
+ return getattr(self, k)
+
+ # Slow path.
+ try:
+ return _g_dCOMForward['getattr'](self, ComifyName(sAttr))
+ except AttributeError:
+ return _g_dCOMForward['getattr'](self, sAttr)
+
+
+def _CustomSetAttr(self, sAttr, oValue):
+ """ Our setattr replacement for DispatchBaseClass. """
+ try:
+ return _g_dCOMForward['setattr'](self, ComifyName(sAttr), oValue)
+ except AttributeError:
+ return _g_dCOMForward['setattr'](self, sAttr, oValue)
+
+
+class PlatformBase(object):
+ """
+ Base class for the platform specific code.
+ """
+
+ def __init__(self, aoParams):
+ _ = aoParams
+
+ def getVirtualBox(self):
+ """
+ Gets a the IVirtualBox singleton.
+ """
+ return None
+
+ def getSessionObject(self):
+ """
+ Get a session object that can be used for opening machine sessions.
+
+ The oIVBox parameter is an getVirtualBox() return value, i.e. an
+ IVirtualBox reference.
+
+ See also openMachineSession.
+ """
+ return None
+
+ def getType(self):
+ """ Returns the platform type (class name sans 'Platform'). """
+ return None
+
+ def isRemote(self):
+ """
+ Returns True if remote (web services) and False if local (COM/XPCOM).
+ """
+ return False
+
+ def getArray(self, oInterface, sAttrib):
+ """
+ Retrives the value of the array attribute 'sAttrib' from
+ interface 'oInterface'.
+
+ This is for hiding platform specific differences in attributes
+ returning arrays.
+ """
+ _ = oInterface
+ _ = sAttrib
+ return None
+
+ def setArray(self, oInterface, sAttrib, aoArray):
+ """
+ Sets the value (aoArray) of the array attribute 'sAttrib' in
+ interface 'oInterface'.
+
+ This is for hiding platform specific differences in attributes
+ setting arrays.
+ """
+ _ = oInterface
+ _ = sAttrib
+ _ = aoArray
+ return None
+
+ def initPerThread(self):
+ """
+ Does backend specific initialization for the calling thread.
+ """
+ return True
+
+ def deinitPerThread(self):
+ """
+ Does backend specific uninitialization for the calling thread.
+ """
+ return True
+
+ def createListener(self, oImplClass, dArgs):
+ """
+ Instantiates and wraps an active event listener class so it can be
+ passed to an event source for registration.
+
+ oImplClass is a class (type, not instance) which implements
+ IEventListener.
+
+ dArgs is a dictionary with string indexed variables. This may be
+ modified by the method to pass platform specific parameters. Can
+ be None.
+
+ This currently only works on XPCOM. COM support is not possible due to
+ shortcuts taken in the COM bridge code, which is not under our control.
+ Use passive listeners for COM and web services.
+ """
+ _ = oImplClass
+ _ = dArgs
+ raise Exception("No active listeners for this platform")
+
+ def waitForEvents(self, cMsTimeout):
+ """
+ Wait for events to arrive and process them.
+
+ The timeout (cMsTimeout) is in milliseconds for how long to wait for
+ events to arrive. A negative value means waiting for ever, while 0
+ does not wait at all.
+
+ Returns 0 if events was processed.
+ Returns 1 if timed out or interrupted in some way.
+ Returns 2 on error (like not supported for web services).
+
+ Raises an exception if the calling thread is not the main thread (the one
+ that initialized VirtualBoxManager) or if the time isn't an integer.
+ """
+ _ = cMsTimeout
+ return 2
+
+ def interruptWaitEvents(self):
+ """
+ Interrupt a waitForEvents call.
+ This is normally called from a worker thread to wake up the main thread.
+
+ Returns True on success, False on failure.
+ """
+ return False
+
+ def deinit(self):
+ """
+ Unitializes the platform specific backend.
+ """
+ return None
+
+ def queryInterface(self, oIUnknown, sClassName):
+ """
+ IUnknown::QueryInterface wrapper.
+
+ oIUnknown is who to ask.
+ sClassName is the name of the interface we're asking for.
+ """
+ return None
+
+ #
+ # Error (exception) access methods.
+ #
+
+ def xcptGetStatus(self, oXcpt):
+ """
+ Returns the COM status code from the VBox API given exception.
+ """
+ return None
+
+ def xcptIsDeadInterface(self, oXcpt):
+ """
+ Returns True if the exception indicates that the interface is dead, False if not.
+ """
+ return False
+
+ def xcptIsEqual(self, oXcpt, hrStatus):
+ """
+ Checks if the exception oXcpt is equal to the COM/XPCOM status code
+ hrStatus.
+
+ The oXcpt parameter can be any kind of object, we'll just return True
+ if it doesn't behave like a our exception class.
+
+ Will not raise any exception as long as hrStatus and self are not bad.
+ """
+ try:
+ hrXcpt = self.xcptGetStatus(oXcpt)
+ except AttributeError:
+ return False
+ if hrXcpt == hrStatus:
+ return True
+
+ # Fudge for 32-bit signed int conversion.
+ if 0x7fffffff < hrStatus <= 0xffffffff and hrXcpt < 0:
+ if (hrStatus - 0x100000000) == hrXcpt:
+ return True
+ return False
+
+ def xcptGetMessage(self, oXcpt):
+ """
+ Returns the best error message found in the COM-like exception.
+ Returns None to fall back on xcptToString.
+ Raises exception if oXcpt isn't our kind of exception object.
+ """
+ return None
+
+ def xcptGetBaseXcpt(self):
+ """
+ Returns the base exception class.
+ """
+ return None
+
+ def xcptSetupConstants(self, oDst):
+ """
+ Copy/whatever all error constants onto oDst.
+ """
+ return oDst
+
+ @staticmethod
+ def xcptCopyErrorConstants(oDst, oSrc):
+ """
+ Copy everything that looks like error constants from oDst to oSrc.
+ """
+ for sAttr in dir(oSrc):
+ if sAttr[0].isupper() and (sAttr[1].isupper() or sAttr[1] == '_'):
+ oAttr = getattr(oSrc, sAttr)
+ if type(oAttr) is int:
+ setattr(oDst, sAttr, oAttr)
+ return oDst
+
+
+class PlatformMSCOM(PlatformBase):
+ """
+ Platform specific code for MS COM.
+ """
+
+ ## @name VirtualBox COM Typelib definitions (should be generate)
+ #
+ # @remarks Must be updated when the corresponding VirtualBox.xidl bits
+ # are changed. Fortunately this isn't very often.
+ # @{
+ VBOX_TLB_GUID = '{D7569351-1750-46F0-936E-BD127D5BC264}'
+ VBOX_TLB_LCID = 0
+ VBOX_TLB_MAJOR = 1
+ VBOX_TLB_MINOR = 3
+ ## @}
+
+ def __init__(self, dParams):
+ PlatformBase.__init__(self, dParams)
+
+ #
+ # Since the code runs on all platforms, we have to do a lot of
+ # importing here instead of at the top of the file where it's normally located.
+ #
+ from win32com import universal
+ from win32com.client import gencache, DispatchBaseClass
+ from win32com.client import constants, getevents
+ import win32com
+ import pythoncom
+ import win32api
+ import winerror
+ from win32con import DUPLICATE_SAME_ACCESS
+ from win32api import GetCurrentThread, GetCurrentThreadId, DuplicateHandle, GetCurrentProcess
+ import threading
+
+ self.winerror = winerror
+
+ # Setup client impersonation in COM calls.
+ try:
+ pythoncom.CoInitializeSecurity(None,
+ None,
+ None,
+ pythoncom.RPC_C_AUTHN_LEVEL_DEFAULT,
+ pythoncom.RPC_C_IMP_LEVEL_IMPERSONATE,
+ None,
+ pythoncom.EOAC_NONE,
+ None)
+ except:
+ _, oXcpt, _ = sys.exc_info();
+ if isinstance(oXcpt, pythoncom.com_error) and self.xcptGetStatus(oXcpt) == -2147417831: # RPC_E_TOO_LATE
+ print("Warning: CoInitializeSecurity was already called");
+ else:
+ print("Warning: CoInitializeSecurity failed: ", oXcpt);
+
+ # Remember this thread ID and get its handle so we can wait on it in waitForEvents().
+ self.tid = GetCurrentThreadId()
+ pid = GetCurrentProcess()
+ self.aoHandles = [DuplicateHandle(pid, GetCurrentThread(), pid, 0, 0, DUPLICATE_SAME_ACCESS),] # type: list[PyHANDLE]
+
+ # Hack the COM dispatcher base class so we can modify method and
+ # attribute names to match those in xpcom.
+ if _g_dCOMForward['setattr'] is None:
+ _g_dCOMForward['getattr'] = DispatchBaseClass.__dict__['__getattr__']
+ _g_dCOMForward['setattr'] = DispatchBaseClass.__dict__['__setattr__']
+ setattr(DispatchBaseClass, '__getattr__', _CustomGetAttr)
+ setattr(DispatchBaseClass, '__setattr__', _CustomSetAttr)
+
+ # Hack the exception base class so the users doesn't need to check for
+ # XPCOM or COM and do different things.
+ ## @todo
+
+ #
+ # Make sure the gencache is correct (we don't quite follow the COM
+ # versioning rules).
+ #
+ self.flushGenPyCache(win32com.client.gencache)
+ win32com.client.gencache.EnsureDispatch('VirtualBox.Session')
+ win32com.client.gencache.EnsureDispatch('VirtualBox.VirtualBox')
+ win32com.client.gencache.EnsureDispatch('VirtualBox.VirtualBoxClient')
+
+ self.oClient = None ##< instance of client used to support lifetime of VBoxSDS
+ self.oIntCv = threading.Condition()
+ self.fInterrupted = False
+
+ _ = dParams
+
+ def flushGenPyCache(self, oGenCache):
+ """
+ Flushes VBox related files in the win32com gen_py cache.
+
+ This is necessary since we don't follow the typelib versioning rules
+ that everyeone else seems to subscribe to.
+ """
+ #
+ # The EnsureModule method have broken validation code, it doesn't take
+ # typelib module directories into account. So we brute force them here.
+ # (It's possible the directory approach is from some older pywin
+ # version or the result of runnig makepy or gencache manually, but we
+ # need to cover it as well.)
+ #
+ sName = oGenCache.GetGeneratedFileName(self.VBOX_TLB_GUID, self.VBOX_TLB_LCID,
+ self.VBOX_TLB_MAJOR, self.VBOX_TLB_MINOR)
+ sGenPath = oGenCache.GetGeneratePath()
+ if len(sName) > 36 and len(sGenPath) > 5:
+ sTypelibPath = os.path.join(sGenPath, sName)
+ if os.path.isdir(sTypelibPath):
+ import shutil
+ shutil.rmtree(sTypelibPath, ignore_errors=True)
+
+ #
+ # Ensure that our typelib is valid.
+ #
+ return oGenCache.EnsureModule(self.VBOX_TLB_GUID, self.VBOX_TLB_LCID, self.VBOX_TLB_MAJOR, self.VBOX_TLB_MINOR)
+
+ def getSessionObject(self):
+ import win32com
+ from win32com.client import Dispatch
+ return win32com.client.Dispatch("VirtualBox.Session")
+
+ def getVirtualBox(self):
+ # Caching self.oClient is the trick for SDS. It allows to keep the
+ # VBoxSDS in the memory until the end of PlatformMSCOM lifetme.
+ if self.oClient is None:
+ import win32com
+ from win32com.client import Dispatch
+ self.oClient = win32com.client.Dispatch("VirtualBox.VirtualBoxClient")
+ return self.oClient.virtualBox
+
+ def getType(self):
+ return 'MSCOM'
+
+ def getArray(self, oInterface, sAttrib):
+ return oInterface.__getattr__(sAttrib)
+
+ def setArray(self, oInterface, sAttrib, aoArray):
+ #
+ # HACK ALERT!
+ #
+ # With pywin32 build 218, we're seeing type mismatch errors here for
+ # IGuestSession::environmentChanges (safearray of BSTRs). The Dispatch
+ # object (_oleobj_) seems to get some type conversion wrong and COM
+ # gets upset. So, we redo some of the dispatcher work here, picking
+ # the missing type information from the getter.
+ #
+ oOleObj = getattr(oInterface, '_oleobj_')
+ aPropMapGet = getattr(oInterface, '_prop_map_get_')
+ aPropMapPut = getattr(oInterface, '_prop_map_put_')
+ sComAttrib = sAttrib if sAttrib in aPropMapGet else ComifyName(sAttrib)
+ try:
+ aArgs, aDefaultArgs = aPropMapPut[sComAttrib]
+ aGetArgs = aPropMapGet[sComAttrib]
+ except KeyError: # fallback.
+ return oInterface.__setattr__(sAttrib, aoArray)
+
+ import pythoncom
+ oOleObj.InvokeTypes(aArgs[0], # dispid
+ aArgs[1], # LCID
+ aArgs[2], # DISPATCH_PROPERTYPUT
+ (pythoncom.VT_HRESULT, 0), # retType - or void?
+ (aGetArgs[2],), # argTypes - trick: we get the type from the getter.
+ aoArray,) # The array
+
+ def initPerThread(self):
+ import pythoncom
+ pythoncom.CoInitializeEx(0)
+
+ def deinitPerThread(self):
+ import pythoncom
+ pythoncom.CoUninitialize()
+
+ def createListener(self, oImplClass, dArgs):
+ if True:
+ raise Exception('no active listeners on Windows as PyGatewayBase::QueryInterface() '
+ 'returns new gateway objects all the time, thus breaking EventQueue '
+ 'assumptions about the listener interface pointer being constants between calls ')
+ # Did this code ever really work?
+ d = {}
+ d['BaseClass'] = oImplClass
+ d['dArgs'] = dArgs
+ d['tlb_guid'] = PlatformMSCOM.VBOX_TLB_GUID
+ d['tlb_major'] = PlatformMSCOM.VBOX_TLB_MAJOR
+ d['tlb_minor'] = PlatformMSCOM.VBOX_TLB_MINOR
+ str_ = ""
+ str_ += "import win32com.server.util\n"
+ str_ += "import pythoncom\n"
+
+ str_ += "class ListenerImpl(BaseClass):\n"
+ str_ += " _com_interfaces_ = ['IEventListener']\n"
+ str_ += " _typelib_guid_ = tlb_guid\n"
+ str_ += " _typelib_version_ = tlb_major, tlb_minor\n"
+ str_ += " _reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER\n"
+ # Maybe we'd better implement Dynamic invoke policy, to be more flexible here
+ str_ += " _reg_policy_spec_ = 'win32com.server.policy.EventHandlerPolicy'\n"
+
+ # capitalized version of listener method
+ str_ += " HandleEvent=BaseClass.handleEvent\n"
+ str_ += " def __init__(self): BaseClass.__init__(self, dArgs)\n"
+ str_ += "result = win32com.server.util.wrap(ListenerImpl())\n"
+ exec(str_, d, d)
+ return d['result']
+
+ def waitForEvents(self, timeout):
+ from win32api import GetCurrentThreadId
+ from win32event import INFINITE
+ from win32event import MsgWaitForMultipleObjects, QS_ALLINPUT, WAIT_TIMEOUT, WAIT_OBJECT_0
+ from pythoncom import PumpWaitingMessages
+ import types
+
+ if not isinstance(timeout, int):
+ raise TypeError("The timeout argument is not an integer")
+ if self.tid != GetCurrentThreadId():
+ raise Exception("wait for events from the same thread you inited!")
+
+ if timeout < 0:
+ cMsTimeout = INFINITE
+ else:
+ cMsTimeout = timeout
+ rc = MsgWaitForMultipleObjects(self.aoHandles, 0, cMsTimeout, QS_ALLINPUT)
+ if WAIT_OBJECT_0 <= rc < WAIT_OBJECT_0 + len(self.aoHandles):
+ # is it possible?
+ rc = 2
+ elif rc == WAIT_OBJECT_0 + len(self.aoHandles):
+ # Waiting messages
+ PumpWaitingMessages()
+ rc = 0
+ else:
+ # Timeout
+ rc = 1
+
+ # check for interruption
+ self.oIntCv.acquire()
+ if self.fInterrupted:
+ self.fInterrupted = False
+ rc = 1
+ self.oIntCv.release()
+
+ return rc
+
+ def interruptWaitEvents(self):
+ """
+ Basically a python implementation of NativeEventQueue::postEvent().
+
+ The magic value must be in sync with the C++ implementation or this
+ won't work.
+
+ Note that because of this method we cannot easily make use of a
+ non-visible Window to handle the message like we would like to do.
+ """
+ from win32api import PostThreadMessage
+ from win32con import WM_USER
+
+ self.oIntCv.acquire()
+ self.fInterrupted = True
+ self.oIntCv.release()
+ try:
+ PostThreadMessage(self.tid, WM_USER, None, 0xf241b819)
+ except:
+ return False
+ return True
+
+ def deinit(self):
+ for oHandle in self.aoHandles:
+ if oHandle is not None:
+ oHandle.Close();
+ self.oHandle = None;
+
+ del self.oClient;
+ self.oClient = None;
+
+ # This non-sense doesn't pair up with any pythoncom.CoInitialize[Ex].
+ # See @bugref{9037}.
+ #import pythoncom
+ #pythoncom.CoUninitialize()
+
+ def queryInterface(self, oIUnknown, sClassName):
+ from win32com.client import CastTo
+ return CastTo(oIUnknown, sClassName)
+
+ def xcptGetStatus(self, oXcpt):
+ # The DISP_E_EXCEPTION + excptinfo fun needs checking up, only
+ # empirical info on it so far.
+ hrXcpt = oXcpt.hresult
+ if hrXcpt == self.winerror.DISP_E_EXCEPTION:
+ try:
+ hrXcpt = oXcpt.excepinfo[5]
+ except:
+ pass
+ return hrXcpt
+
+ def xcptIsDeadInterface(self, oXcpt):
+ return self.xcptGetStatus(oXcpt) in [
+ 0x800706ba, -2147023174, # RPC_S_SERVER_UNAVAILABLE.
+ 0x800706be, -2147023170, # RPC_S_CALL_FAILED.
+ 0x800706bf, -2147023169, # RPC_S_CALL_FAILED_DNE.
+ 0x80010108, -2147417848, # RPC_E_DISCONNECTED.
+ 0x800706b5, -2147023179, # RPC_S_UNKNOWN_IF
+ ]
+
+ def xcptGetMessage(self, oXcpt):
+ if hasattr(oXcpt, 'excepinfo'):
+ try:
+ if len(oXcpt.excepinfo) >= 3:
+ sRet = oXcpt.excepinfo[2]
+ if len(sRet) > 0:
+ return sRet[0:]
+ except:
+ pass
+ if hasattr(oXcpt, 'strerror'):
+ try:
+ sRet = oXcpt.strerror
+ if len(sRet) > 0:
+ return sRet
+ except:
+ pass
+ return None
+
+ def xcptGetBaseXcpt(self):
+ import pythoncom
+
+ return pythoncom.com_error
+
+ def xcptSetupConstants(self, oDst):
+ import winerror
+
+ oDst = self.xcptCopyErrorConstants(oDst, winerror)
+
+ # XPCOM compatability constants.
+ oDst.NS_OK = oDst.S_OK
+ oDst.NS_ERROR_FAILURE = oDst.E_FAIL
+ oDst.NS_ERROR_ABORT = oDst.E_ABORT
+ oDst.NS_ERROR_NULL_POINTER = oDst.E_POINTER
+ oDst.NS_ERROR_NO_INTERFACE = oDst.E_NOINTERFACE
+ oDst.NS_ERROR_INVALID_ARG = oDst.E_INVALIDARG
+ oDst.NS_ERROR_OUT_OF_MEMORY = oDst.E_OUTOFMEMORY
+ oDst.NS_ERROR_NOT_IMPLEMENTED = oDst.E_NOTIMPL
+ oDst.NS_ERROR_UNEXPECTED = oDst.E_UNEXPECTED
+ return oDst
+
+
+class PlatformXPCOM(PlatformBase):
+ """
+ Platform specific code for XPCOM.
+ """
+
+ def __init__(self, dParams):
+ PlatformBase.__init__(self, dParams)
+ sys.path.append(VBoxSdkDir + '/bindings/xpcom/python/')
+ import xpcom.vboxxpcom
+ import xpcom
+ import xpcom.components
+ _ = dParams
+
+ def getSessionObject(self):
+ import xpcom.components
+ return xpcom.components.classes["@virtualbox.org/Session;1"].createInstance()
+
+ def getVirtualBox(self):
+ import xpcom.components
+ client = xpcom.components.classes["@virtualbox.org/VirtualBoxClient;1"].createInstance()
+ return client.virtualBox
+
+ def getType(self):
+ return 'XPCOM'
+
+ def getArray(self, oInterface, sAttrib):
+ return oInterface.__getattr__('get' + ComifyName(sAttrib))()
+
+ def setArray(self, oInterface, sAttrib, aoArray):
+ return oInterface.__getattr__('set' + ComifyName(sAttrib))(aoArray)
+
+ def initPerThread(self):
+ import xpcom
+ xpcom._xpcom.AttachThread()
+
+ def deinitPerThread(self):
+ import xpcom
+ xpcom._xpcom.DetachThread()
+
+ def createListener(self, oImplClass, dArgs):
+ d = {}
+ d['BaseClass'] = oImplClass
+ d['dArgs'] = dArgs
+ str = ""
+ str += "import xpcom.components\n"
+ str += "class ListenerImpl(BaseClass):\n"
+ str += " _com_interfaces_ = xpcom.components.interfaces.IEventListener\n"
+ str += " def __init__(self): BaseClass.__init__(self, dArgs)\n"
+ str += "result = ListenerImpl()\n"
+ exec(str, d, d)
+ return d['result']
+
+ def waitForEvents(self, timeout):
+ import xpcom
+ return xpcom._xpcom.WaitForEvents(timeout)
+
+ def interruptWaitEvents(self):
+ import xpcom
+ return xpcom._xpcom.InterruptWait()
+
+ def deinit(self):
+ import xpcom
+ xpcom._xpcom.DeinitCOM()
+
+ def queryInterface(self, oIUnknown, sClassName):
+ import xpcom.components
+ return oIUnknown.queryInterface(getattr(xpcom.components.interfaces, sClassName))
+
+ def xcptGetStatus(self, oXcpt):
+ return oXcpt.errno
+
+ def xcptIsDeadInterface(self, oXcpt):
+ return self.xcptGetStatus(oXcpt) in [
+ 0x80004004, -2147467260, # NS_ERROR_ABORT
+ 0x800706be, -2147023170, # NS_ERROR_CALL_FAILED (RPC_S_CALL_FAILED)
+ ]
+
+ def xcptGetMessage(self, oXcpt):
+ if hasattr(oXcpt, 'msg'):
+ try:
+ sRet = oXcpt.msg
+ if len(sRet) > 0:
+ return sRet
+ except:
+ pass
+ return None
+
+ def xcptGetBaseXcpt(self):
+ import xpcom
+ return xpcom.Exception
+
+ def xcptSetupConstants(self, oDst):
+ import xpcom
+ oDst = self.xcptCopyErrorConstants(oDst, xpcom.nsError)
+
+ # COM compatability constants.
+ oDst.E_ACCESSDENIED = -2147024891 # see VBox/com/defs.h
+ oDst.S_OK = oDst.NS_OK
+ oDst.E_FAIL = oDst.NS_ERROR_FAILURE
+ oDst.E_ABORT = oDst.NS_ERROR_ABORT
+ oDst.E_POINTER = oDst.NS_ERROR_NULL_POINTER
+ oDst.E_NOINTERFACE = oDst.NS_ERROR_NO_INTERFACE
+ oDst.E_INVALIDARG = oDst.NS_ERROR_INVALID_ARG
+ oDst.E_OUTOFMEMORY = oDst.NS_ERROR_OUT_OF_MEMORY
+ oDst.E_NOTIMPL = oDst.NS_ERROR_NOT_IMPLEMENTED
+ oDst.E_UNEXPECTED = oDst.NS_ERROR_UNEXPECTED
+ oDst.DISP_E_EXCEPTION = -2147352567 # For COM compatability only.
+ return oDst
+
+
+class PlatformWEBSERVICE(PlatformBase):
+ """
+ VirtualBox Web Services API specific code.
+ """
+
+ def __init__(self, dParams):
+ PlatformBase.__init__(self, dParams)
+ # Import web services stuff. Fix the sys.path the first time.
+ sWebServLib = os.path.join(VBoxSdkDir, 'bindings', 'webservice', 'python', 'lib')
+ if sWebServLib not in sys.path:
+ sys.path.append(sWebServLib)
+ import VirtualBox_wrappers
+ from VirtualBox_wrappers import IWebsessionManager2
+
+ # Initialize instance variables from parameters.
+ if dParams is not None:
+ self.user = dParams.get("user", "")
+ self.password = dParams.get("password", "")
+ self.url = dParams.get("url", "")
+ else:
+ self.user = ""
+ self.password = ""
+ self.url = None
+ self.vbox = None
+ self.wsmgr = None
+
+ #
+ # Base class overrides.
+ #
+
+ def getSessionObject(self):
+ return self.wsmgr.getSessionObject(self.vbox)
+
+ def getVirtualBox(self):
+ return self.connect(self.url, self.user, self.password)
+
+ def getType(self):
+ return 'WEBSERVICE'
+
+ def isRemote(self):
+ """ Returns True if remote VBox host, False if local. """
+ return True
+
+ def getArray(self, oInterface, sAttrib):
+ return oInterface.__getattr__(sAttrib)
+
+ def setArray(self, oInterface, sAttrib, aoArray):
+ return oInterface.__setattr__(sAttrib, aoArray)
+
+ def waitForEvents(self, timeout):
+ # Webservices cannot do that yet
+ return 2
+
+ def interruptWaitEvents(self, timeout):
+ # Webservices cannot do that yet
+ return False
+
+ def deinit(self):
+ try:
+ self.disconnect()
+ except:
+ pass
+
+ def queryInterface(self, oIUnknown, sClassName):
+ d = {}
+ d['oIUnknown'] = oIUnknown
+ str = ""
+ str += "from VirtualBox_wrappers import " + sClassName + "\n"
+ str += "result = " + sClassName + "(oIUnknown.mgr, oIUnknown.handle)\n"
+ # wrong, need to test if class indeed implements this interface
+ exec(str, d, d)
+ return d['result']
+
+ #
+ # Web service specific methods.
+ #
+
+ def connect(self, url, user, passwd):
+ if self.vbox is not None:
+ self.disconnect()
+ from VirtualBox_wrappers import IWebsessionManager2
+
+ if url is None:
+ url = ""
+ self.url = url
+ if user is None:
+ user = ""
+ self.user = user
+ if passwd is None:
+ passwd = ""
+ self.password = passwd
+ self.wsmgr = IWebsessionManager2(self.url)
+ self.vbox = self.wsmgr.logon(self.user, self.password)
+ if not self.vbox.handle:
+ raise Exception("cannot connect to '" + self.url + "' as '" + self.user + "'")
+ return self.vbox
+
+ def disconnect(self):
+ if self.vbox is not None and self.wsmgr is not None:
+ self.wsmgr.logoff(self.vbox)
+ self.vbox = None
+ self.wsmgr = None
+
+
+## The current (last) exception class.
+# This is reinitalized whenever VirtualBoxManager is called, so it will hold
+# the reference to the error exception class for the last platform/style that
+# was used. Most clients does talk to multiple VBox instance on different
+# platforms at the same time, so this should be sufficent for most uses and
+# be way simpler to use than VirtualBoxManager::oXcptClass.
+CurXcptClass = None
+
+
+class VirtualBoxManager(object):
+ """
+ VirtualBox API manager class.
+
+ The API users will have to instantiate this. If no parameters are given,
+ it will default to interface with the VirtualBox running on the local
+ machine. sStyle can be None (default), MSCOM, XPCOM or WEBSERVICES. Most
+ users will either be specifying None or WEBSERVICES.
+
+ The dPlatformParams is an optional dictionary for passing parameters to the
+ WEBSERVICE backend.
+ """
+
+ class Statuses(object):
+ def __init__(self):
+ pass
+
+ def __init__(self, sStyle=None, dPlatformParams=None):
+ if sStyle is None:
+ if sys.platform == 'win32':
+ sStyle = "MSCOM"
+ else:
+ sStyle = "XPCOM"
+ if sStyle == 'XPCOM':
+ self.platform = PlatformXPCOM(dPlatformParams)
+ elif sStyle == 'MSCOM':
+ self.platform = PlatformMSCOM(dPlatformParams)
+ elif sStyle == 'WEBSERVICE':
+ self.platform = PlatformWEBSERVICE(dPlatformParams)
+ else:
+ raise Exception('Unknown sStyle=%s' % (sStyle,))
+ self.style = sStyle
+ self.type = self.platform.getType()
+ self.remote = self.platform.isRemote()
+ ## VirtualBox API constants (for webservices, enums are symbolic).
+ self.constants = VirtualBoxReflectionInfo(sStyle == "WEBSERVICE")
+
+ ## Status constants.
+ self.statuses = self.platform.xcptSetupConstants(VirtualBoxManager.Statuses())
+ ## @todo Add VBOX_E_XXX to statuses? They're already in constants...
+ ## Dictionary for errToString, built on demand.
+ self._dErrorValToName = None
+
+ ## Dictionary for resolving enum values to names, two levels of dictionaries.
+ ## First level is indexed by enum name, the next by value.
+ self._ddEnumValueToName = {};
+
+ ## The exception class for the selected platform.
+ self.oXcptClass = self.platform.xcptGetBaseXcpt()
+ global CurXcptClass
+ CurXcptClass = self.oXcptClass
+
+ # Get the virtualbox singleton.
+ try:
+ vbox = self.platform.getVirtualBox()
+ except NameError:
+ print("Installation problem: check that appropriate libs in place")
+ traceback.print_exc()
+ raise
+ except Exception:
+ _, e, _ = sys.exc_info()
+ print("init exception: ", e)
+ traceback.print_exc()
+
+ def __del__(self):
+ self.deinit()
+
+ def getPythonApiRevision(self):
+ """
+ Returns a Python API revision number.
+ This will be incremented when features are added to this file.
+ """
+ return 3
+
+ @property
+ def mgr(self):
+ """
+ This used to be an attribute referring to a session manager class with
+ only one method called getSessionObject. It moved into this class.
+ """
+ return self
+
+ #
+ # Wrappers for self.platform methods.
+ #
+ def getVirtualBox(self):
+ """ See PlatformBase::getVirtualBox(). """
+ return self.platform.getVirtualBox()
+
+ def getSessionObject(self, oIVBox = None):
+ """ See PlatformBase::getSessionObject(). """
+ # ignore parameter which was never needed
+ _ = oIVBox
+ return self.platform.getSessionObject()
+
+ def getArray(self, oInterface, sAttrib):
+ """ See PlatformBase::getArray(). """
+ return self.platform.getArray(oInterface, sAttrib)
+
+ def setArray(self, oInterface, sAttrib, aoArray):
+ """ See PlatformBase::setArray(). """
+ return self.platform.setArray(oInterface, sAttrib, aoArray)
+
+ def createListener(self, oImplClass, dArgs=None):
+ """ See PlatformBase::createListener(). """
+ return self.platform.createListener(oImplClass, dArgs)
+
+ def waitForEvents(self, cMsTimeout):
+ """ See PlatformBase::waitForEvents(). """
+ return self.platform.waitForEvents(cMsTimeout)
+
+ def interruptWaitEvents(self):
+ """ See PlatformBase::interruptWaitEvents(). """
+ return self.platform.interruptWaitEvents()
+
+ def queryInterface(self, oIUnknown, sClassName):
+ """ See PlatformBase::queryInterface(). """
+ return self.platform.queryInterface(oIUnknown, sClassName)
+
+ #
+ # Init and uninit.
+ #
+ def initPerThread(self):
+ """ See PlatformBase::deinitPerThread(). """
+ self.platform.initPerThread()
+
+ def deinitPerThread(self):
+ """ See PlatformBase::deinitPerThread(). """
+ return self.platform.deinitPerThread()
+
+ def deinit(self):
+ """
+ For unitializing the manager.
+ Do not access it after calling this method.
+ """
+ if hasattr(self, "platform") and self.platform is not None:
+ self.platform.deinit()
+ self.platform = None
+ return True
+
+ #
+ # Utility methods.
+ #
+ def openMachineSession(self, oIMachine, fPermitSharing=True):
+ """
+ Attempts to open the a session to the machine.
+ Returns a session object on success.
+ Raises exception on failure.
+ """
+ oSession = self.getSessionObject()
+ if fPermitSharing:
+ eType = self.constants.LockType_Shared
+ else:
+ eType = self.constants.LockType_Write
+ oIMachine.lockMachine(oSession, eType)
+ return oSession
+
+ def closeMachineSession(self, oSession):
+ """
+ Closes a session opened by openMachineSession.
+ Ignores None parameters.
+ """
+ if oSession is not None:
+ oSession.unlockMachine()
+ return True
+
+ def getPerfCollector(self, oIVBox):
+ """
+ Returns a helper class (PerfCollector) for accessing performance
+ collector goodies. See PerfCollector for details.
+ """
+ return PerfCollector(self, oIVBox)
+
+ def getBinDir(self):
+ """
+ Returns the VirtualBox binary directory.
+ """
+ global VBoxBinDir
+ return VBoxBinDir
+
+ def getSdkDir(self):
+ """
+ Returns the VirtualBox SDK directory.
+ """
+ global VBoxSdkDir
+ return VBoxSdkDir
+
+ def getEnumValueName(self, sEnumTypeNm, oEnumValue, fTypePrefix = False):
+ """
+ Returns the name (string) for the corresponding enum value.
+ """
+ # Cache lookup:
+ dValueNames = self._ddEnumValueToName.get(sEnumTypeNm);
+ if dValueNames is not None:
+ sValueName = dValueNames.get(oEnumValue);
+ if sValueName:
+ return sValueName if not fTypePrefix else '%s_%s' % (sEnumTypeNm, sValueName);
+ else:
+ # Cache miss. Build the reverse lookup dictionary and add it to the cache:
+ dNamedValues = self.constants.all_values(sEnumTypeNm);
+ if len(dNamedValues) > 0:
+
+ dValueNames = dict();
+ for sName in dNamedValues:
+ dValueNames[dNamedValues[sName]] = sName;
+ self._ddEnumValueToName[sEnumTypeNm] = dValueNames;
+
+ # Lookup the value:
+ sValueName = dValueNames.get(oEnumValue);
+ if sValueName:
+ return sValueName if not fTypePrefix else '%s_%s' % (sEnumTypeNm, sValueName);
+
+ # Fallback:
+ return '%s_Unknown_%s' % (sEnumTypeNm, oEnumValue);
+
+ #
+ # Error code utilities.
+ #
+ ## @todo port to webservices!
+ def xcptGetStatus(self, oXcpt=None):
+ """
+ Gets the status code from an exception. If the exception parameter
+ isn't specified, the current exception is examined.
+ """
+ if oXcpt is None:
+ oXcpt = sys.exc_info()[1]
+ return self.platform.xcptGetStatus(oXcpt)
+
+ def xcptIsDeadInterface(self, oXcpt=None):
+ """
+ Returns True if the exception indicates that the interface is dead,
+ False if not. If the exception parameter isn't specified, the current
+ exception is examined.
+ """
+ if oXcpt is None:
+ oXcpt = sys.exc_info()[1]
+ return self.platform.xcptIsDeadInterface(oXcpt)
+
+ def xcptIsOurXcptKind(self, oXcpt=None):
+ """
+ Checks if the exception is one that could come from the VBox API. If
+ the exception parameter isn't specified, the current exception is
+ examined.
+ """
+ if self.oXcptClass is None: # @todo find the exception class for web services!
+ return False
+ if oXcpt is None:
+ oXcpt = sys.exc_info()[1]
+ return isinstance(oXcpt, self.oXcptClass)
+
+ def xcptIsEqual(self, oXcpt, hrStatus):
+ """
+ Checks if the exception oXcpt is equal to the COM/XPCOM status code
+ hrStatus.
+
+ The oXcpt parameter can be any kind of object, we'll just return True
+ if it doesn't behave like a our exception class. If it's None, we'll
+ query the current exception and examine that.
+
+ Will not raise any exception as long as hrStatus and self are not bad.
+ """
+ if oXcpt is None:
+ oXcpt = sys.exc_info()[1]
+ return self.platform.xcptIsEqual(oXcpt, hrStatus)
+
+ def xcptIsNotEqual(self, oXcpt, hrStatus):
+ """
+ Negated xcptIsEqual.
+ """
+ return not self.xcptIsEqual(oXcpt, hrStatus)
+
+ def xcptToString(self, hrStatusOrXcpt=None):
+ """
+ Converts the specified COM status code, or the status code of the
+ specified exception, to a C constant string. If the parameter isn't
+ specified (is None), the current exception is examined.
+ """
+
+ # Deal with exceptions.
+ if hrStatusOrXcpt is None or self.xcptIsOurXcptKind(hrStatusOrXcpt):
+ hrStatus = self.xcptGetStatus(hrStatusOrXcpt)
+ else:
+ hrStatus = hrStatusOrXcpt
+
+ # Build the dictionary on demand.
+ if self._dErrorValToName is None:
+ dErrorValToName = dict()
+ for sKey in dir(self.statuses):
+ if sKey[0].isupper():
+ oValue = getattr(self.statuses, sKey)
+ if isinstance(oValue, (int, long)):
+ dErrorValToName[int(oValue)] = sKey
+ # Always prefer the COM names (see aliasing in platform specific code):
+ for sKey in ('S_OK', 'E_FAIL', 'E_ABORT', 'E_POINTER', 'E_NOINTERFACE', 'E_INVALIDARG',
+ 'E_OUTOFMEMORY', 'E_NOTIMPL', 'E_UNEXPECTED',):
+ oValue = getattr(self.statuses, sKey, None)
+ if oValue is not None:
+ dErrorValToName[oValue] = sKey
+ self._dErrorValToName = dErrorValToName
+
+ # Do the lookup, falling back on formatting the status number.
+ try:
+ sStr = self._dErrorValToName[int(hrStatus)]
+ except KeyError:
+ hrLong = long(hrStatus)
+ sStr = '%#x (%d)' % (hrLong & 0xffffffff, hrLong)
+ return sStr
+
+ def xcptGetMessage(self, oXcpt=None):
+ """
+ Returns the best error message found in the COM-like exception. If the
+ exception parameter isn't specified, the current exception is examined.
+ """
+ if oXcpt is None:
+ oXcpt = sys.exc_info()[1]
+ sRet = self.platform.xcptGetMessage(oXcpt)
+ if sRet is None:
+ sRet = self.xcptToString(oXcpt)
+ return sRet
+
diff --git a/src/VBox/Main/glue/xpcom/Makefile.kup b/src/VBox/Main/glue/xpcom/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/glue/xpcom/Makefile.kup
diff --git a/src/VBox/Main/glue/xpcom/helpers.cpp b/src/VBox/Main/glue/xpcom/helpers.cpp
new file mode 100644
index 00000000..de185186
--- /dev/null
+++ b/src/VBox/Main/glue/xpcom/helpers.cpp
@@ -0,0 +1,204 @@
+/* $Id: helpers.cpp $ */
+/** @file
+ * COM helper functions for XPCOM
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "VBox/com/defs.h"
+#include <nsMemory.h>
+#include <iprt/assertcompile.h>
+#include <iprt/utf16.h>
+
+
+//
+// OLE Automation string APIs
+//
+
+// Note: on Windows, every BSTR stores its length in the
+// byte just before the pointer you get. If we do it like this,
+// the caller cannot just use nsMemory::Free() on our strings.
+// Therefore we'll try to implement it differently and hope that
+// we don't run into problems.
+
+/**
+ * Copies a string into a new memory block including the terminating UCS2 NULL.
+ *
+ * @param pwsz Source string to duplicate.
+ * @returns New BSTR string buffer.
+ */
+BSTR SysAllocString(const OLECHAR *pwszSrc)
+{
+ AssertCompile(sizeof(*pwszSrc) == sizeof(PRUnichar));
+ if (pwszSrc)
+ return SysAllocStringLen(pwszSrc, RTUtf16Len((PCRTUTF16)pwszSrc));
+ return NULL;
+}
+
+/**
+ * Duplicates an ANSI string into a BSTR / allocates a BSTR with a size given in
+ * bytes.
+ *
+ * No conversion is done.
+ *
+ * @param pszSrc Source string to copy, optional.
+ * @param cbSrcReq Length of the source string / memory request in bytes.
+ * @returns new BSTR string buffer, NULL on failure.
+ */
+BSTR SysAllocStringByteLen(char const *pszSrc, unsigned int cbSrcReq)
+{
+ BSTR pBstrNew = (BSTR)nsMemory::Alloc(RT_ALIGN_Z(cbSrcReq + sizeof(OLECHAR), sizeof(OLECHAR)));
+ AssertCompile(sizeof(*pBstrNew) == sizeof(OLECHAR));
+ if (pBstrNew)
+ {
+ if (!pszSrc)
+ memset(pBstrNew, 0, cbSrcReq + sizeof(OLECHAR));
+ else
+ {
+ // Copy the string and make sure it is terminated.
+ memcpy(pBstrNew, pszSrc, cbSrcReq);
+ char *pchTerminator = (char *)pBstrNew;
+ pchTerminator[cbSrcReq] = '\0';
+ pchTerminator[cbSrcReq + 1] = '\0';
+ }
+ }
+ return pBstrNew;
+}
+
+/**
+ * Duplicates a UTF-16 string into a BSTR / Allocates a BSTR with a size given
+ * in UTF-16 characters.
+ *
+ * @param pwszSrc Pointer to the source string, optional.
+ * @param cwcSrcReq Length of the source string / memory request in UTF-16
+ * characters.
+ * @returns new BSTR string buffer, NULL on failure.
+ */
+BSTR SysAllocStringLen(const OLECHAR *pwszSrc, unsigned int cwcSrcReq)
+{
+ size_t const cbReq = (cwcSrcReq + 1) * sizeof(OLECHAR);
+ BSTR pBstrNew = (BSTR)nsMemory::Alloc(cbReq);
+ AssertCompile(sizeof(*pBstrNew) == sizeof(OLECHAR));
+ if (pBstrNew)
+ {
+ if (!pwszSrc)
+ memset(pBstrNew, 0, cbReq);
+ else
+ {
+ // Copy the string and make sure it is terminated.
+ memcpy(pBstrNew, pwszSrc, cbReq - sizeof(OLECHAR));
+ pBstrNew[cwcSrcReq] = L'\0';
+ }
+ }
+ return pBstrNew;
+}
+
+/**
+ * Frees the memory associated with the given BSTR.
+ *
+ * @param pBstr The string to free. NULL is ignored.
+ */
+void SysFreeString(BSTR pBstr)
+{
+ if (pBstr)
+ nsMemory::Free(pBstr);
+}
+
+/**
+ * Duplicates @a pwszSrc into an exsting BSTR, adjust its size to make it fit.
+ *
+ * @param ppBstr The existing BSTR buffer pointer.
+ * @param pwszSrc Source string to copy. If NULL, the existing BSTR is freed.
+ * @returns success indicator (TRUE/FALSE)
+ */
+int SysReAllocString(BSTR *ppBstr, const OLECHAR *pwszSrc)
+{
+ if (pwszSrc)
+ return SysReAllocStringLen(ppBstr, pwszSrc, RTUtf16Len((PCRTUTF16)pwszSrc));
+ SysFreeString(*ppBstr);
+ *ppBstr = NULL;
+ return 1;
+}
+
+/**
+ * Duplicates @a pwszSrc into an exsting BSTR / resizing an existing BSTR buffer
+ * into the given size (@a cwcSrcReq).
+ *
+ * @param ppBstr The existing BSTR buffer pointer.
+ * @param pwszSrc Source string to copy into the adjusted pbstr, optional.
+ * @param cwcSrcReq Length of the source string / request in UCS2
+ * characters, a zero terminator is always added.
+ * @returns success indicator (TRUE/FALSE)
+ */
+int SysReAllocStringLen(BSTR *ppBstr, const OLECHAR *pwszSrc, unsigned int cwcSrcReq)
+{
+ BSTR pBstrOld = *ppBstr;
+ AssertCompile(sizeof(*pBstrOld) == sizeof(OLECHAR));
+ if (pBstrOld)
+ {
+ if ((BSTR)pwszSrc == pBstrOld)
+ pwszSrc = NULL;
+
+ size_t cbReq = (cwcSrcReq + 1) * sizeof(OLECHAR);
+ BSTR pBstrNew = (BSTR)nsMemory::Realloc(pBstrOld, cbReq);
+ if (pBstrNew)
+ {
+ if (pwszSrc)
+ memcpy(pBstrNew, pwszSrc, cbReq - sizeof(OLECHAR));
+ pBstrNew[cwcSrcReq] = L'\0';
+ *ppBstr = pBstrNew;
+ return 1;
+ }
+ }
+ else
+ {
+ // allocate a new string
+ *ppBstr = SysAllocStringLen(pwszSrc, cwcSrcReq);
+ if (*ppBstr)
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * Returns the string length in bytes without the terminator.
+ *
+ * @param pBstr The BSTR to get the byte length of.
+ * @returns String length in bytes.
+ */
+unsigned int SysStringByteLen(BSTR pBstr)
+{
+ AssertCompile(sizeof(OLECHAR) == sizeof(*pBstr));
+ return RTUtf16Len(pBstr) * sizeof(OLECHAR);
+}
+
+/**
+ * Returns the string length in OLECHARs without the terminator.
+ *
+ * @param pBstr The BSTR to get the length of.
+ * @returns String length in OLECHARs.
+ */
+unsigned int SysStringLen(BSTR pBstr)
+{
+ return RTUtf16Len(pBstr);
+}
diff --git a/src/VBox/Main/idl/Makefile.kup b/src/VBox/Main/idl/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/idl/Makefile.kup
diff --git a/src/VBox/Main/idl/VirtualBox.dtd b/src/VBox/Main/idl/VirtualBox.dtd
new file mode 100644
index 00000000..d3f27261
--- /dev/null
+++ b/src/VBox/Main/idl/VirtualBox.dtd
@@ -0,0 +1,159 @@
+<!--
+ Unofficial DTD for the VirtualBox.xidl file. This is not currently used:
+ neither by the VirtualBox build process nor at runtime, so it's not shipped
+ with the product either, and thus not guaranteed to be up to date.
+ It is still the only sort-of-documentation available about what is valid
+ XIDL syntax.
+-->
+<!--
+ Copyright (C) 2008-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+ <!ELEMENT idl (desc|if|library)*> <!-- done -->
+ <!ELEMENT if ANY> <!-- done -->
+ <!ATTLIST if target (midl|xpidl|wsdl) #REQUIRED>
+ <!ELEMENT cpp ANY> <!-- done -->
+ <!ATTLIST cpp line CDATA #IMPLIED>
+ <!ELEMENT library (application|if)*> <!-- done -->
+ <!ATTLIST library name CDATA #REQUIRED>
+ <!ATTLIST library uuid CDATA #REQUIRED>
+ <!ATTLIST library version CDATA #REQUIRED>
+ <!ELEMENT application (descGroup|if|result|enum|interface|module)*> <!-- done -->
+ <!ATTLIST application name CDATA #REQUIRED>
+ <!ATTLIST application uuid CDATA #REQUIRED>
+ <!ATTLIST application supportsErrorInfo CDATA #REQUIRED>
+ <!ELEMENT result (#PCDATA|desc|link)*> <!-- done -->
+ <!ATTLIST result name CDATA #REQUIRED>
+ <!ATTLIST result value CDATA #IMPLIED>
+ <!ELEMENT module (class)*> <!-- done -->
+ <!ATTLIST module name CDATA #REQUIRED>
+ <!ATTLIST module context CDATA #REQUIRED>
+ <!ATTLIST module threadingModel CDATA #IMPLIED>
+ <!ELEMENT enum (desc?, const+)> <!-- done -->
+ <!ATTLIST enum name CDATA #REQUIRED>
+ <!ATTLIST enum uuid CDATA #REQUIRED>
+ <!ELEMENT const (desc?)> <!-- done -->
+ <!ATTLIST const name CDATA #REQUIRED>
+ <!ATTLIST const value CDATA #REQUIRED>
+ <!ATTLIST const wsmap (managed|suppress) "managed">
+ <!ELEMENT interface (desc?, (attribute|method|class|if)*)> <!-- done -->
+ <!ATTLIST interface name CDATA #REQUIRED>
+ <!ATTLIST interface extends CDATA #IMPLIED>
+ <!ATTLIST interface uuid CDATA #IMPLIED>
+ <!ATTLIST interface supportsErrorInfo (yes|no) #IMPLIED>
+ <!ATTLIST interface default (yes|no) "no">
+ <!ATTLIST interface internal (yes|no) "no">
+ <!ATTLIST interface wsmap (fail|global|struct|managed|suppress) "fail">
+ <!-- wsmap specifies how this interface is mapped to the
+ web services API (WSDL). One of the following must be specified:
+ fail: the default value, for which vboxweb.xsl will raise an error and die.
+ global: object is a singleton and resides in global variable in the web service.
+ managed: objects of this type are referenced by managed object referenced
+ struct: object is a simple struct and can be copied as such
+ suppress: Skip this interface entirely, and all methods that use it -->
+ <!ATTLIST interface wscpp (generate|hardcoded) "generate">
+ <!-- wscpp specifies whether C++ code should be generated in methodmaps.cpp
+ as a mapper to COM APIs. By default, this is "generate"; however, if set
+ to "hardcoded", then no automatic C++ code should be generated. This is done
+ for webservice APIs that have no counterpart in COM and are hard-coded in
+ the webservice server, such as IManagedObjectReference and ISessionManager. -->
+ <!ATTLIST interface rest (managed|suppress) "suppress">
+ <!-- rest specifies how this interface is mapped to the
+ web services API (RESTful). One of the following must be specified:
+ managed: objects of this type are represented
+ suppress: the default value, skip this interface entirely -->
+ <!ATTLIST interface autogen CDATA #IMPLIED>
+ <!-- autogen names the style of code auto-generation for this
+ interface (currently only VBoxEvent). -->
+ <!ATTLIST interface autogenflags CDATA #IMPLIED>
+ <!-- autogenflags contains autogen tweaks.
+ For autoget=VBoxEvent: 'BSTR' - generate IN_BSTR variants of the functions. -->
+ <!ATTLIST interface id CDATA #IMPLIED>
+ <!-- id is only relevant for event interfaces, and specifies
+ which const name will be generated. -->
+ <!ATTLIST interface waitable (yes|no) "no">
+ <!-- waitable is only relevant for event interfaces, and
+ specifies that this event can be waited for. -->
+ <!ATTLIST interface wrap-hint-server-addinterfaces CDATA #IMPLIED>
+ <!ATTLIST interface wrap-hint-server CDATA #IMPLIED>
+ <!ATTLIST interface wrap-gen-hook (yes|no) "no">
+ <!ATTLIST interface notdual (yes|no) "no">
+ <!-- DTrace has a probe name length limit, so dtracename helps dealing with excessivly long names. -->
+ <!ATTLIST interface dtracename CDATA #IMPLIED>
+ <!ATTLIST interface reservedAttributes CDATA #IMPLIED>
+ <!ATTLIST interface reservedMethods CDATA #IMPLIED>
+ <!ELEMENT class (interface)> <!-- done -->
+ <!ATTLIST class name CDATA #REQUIRED>
+ <!ATTLIST class uuid CDATA #REQUIRED>
+ <!ATTLIST class namespace CDATA #REQUIRED>
+ <!ELEMENT attribute (desc?)> <!-- done -->
+ <!ATTLIST attribute name CDATA #REQUIRED>
+ <!ATTLIST attribute type CDATA #REQUIRED>
+ <!ATTLIST attribute default CDATA #IMPLIED>
+ <!ATTLIST attribute readonly (yes|no) "no">
+ <!ATTLIST attribute mod (ptr|string) #IMPLIED>
+ <!ATTLIST attribute internal (yes|no) "no">
+ <!ATTLIST attribute safearray (yes|no) "no">
+ <!ATTLIST attribute wsmap (managed|suppress) "managed">
+ <!ATTLIST attribute rest (uuid|suppress|default) "default">
+ <!ATTLIST attribute wrap-hint-server CDATA #IMPLIED>
+ <!-- DTrace has a probe name length limit, so dtracename helps dealing with excessivly long names. -->
+ <!ATTLIST attribute dtracename CDATA #IMPLIED>
+ <!ELEMENT method (rest?,desc?,param*,result*)> <!-- done -->
+ <!ATTLIST method name CDATA #REQUIRED>
+ <!ATTLIST method const CDATA "no">
+ <!ATTLIST method internal (yes|no) "no">
+ <!ATTLIST method wsmap (managed|suppress) "managed">
+ <!ATTLIST method wrap-hint-server CDATA #IMPLIED>
+ <!-- DTrace has a probe name length limit, so dtracename helps dealing with excessivly long names. -->
+ <!ATTLIST method dtracename CDATA #IMPLIED>
+ <!ELEMENT rest ANY> <!-- done -->
+ <!ATTLIST rest name CDATA #IMPLIED>
+ <!ATTLIST rest request (get|put|post|delete|patch) "get">
+ <!ATTLIST rest path CDATA #REQUIRED>
+ <!ELEMENT param (desc?)> <!-- done -->
+ <!ATTLIST param name CDATA #REQUIRED>
+ <!ATTLIST param type CDATA #REQUIRED>
+ <!ATTLIST param dir (in|out|return) #REQUIRED>
+ <!ATTLIST param mod (ptr|string) #IMPLIED>
+ <!ATTLIST param safearray (yes|no) "no">
+ <!ELEMENT descGroup (desc)*> <!-- done (ignoring, butt-ugly hack, improper nesting enforced all over the .xsl files!) -->
+ <!ATTLIST descGroup id CDATA #IMPLIED>
+ <!ATTLIST descGroup title CDATA #IMPLIED>
+ <!ELEMENT desc (#PCDATA|link|note|see|b|tt|i|pre|para|ul|ol|h3|table|result)*> <!-- done (ignoring) -->
+ <!-- the following only appear within descriptions -->
+ <!ELEMENT link (#PCDATA)>
+ <!ATTLIST link to CDATA #REQUIRED>
+ <!ELEMENT h3 ANY>
+ <!ELEMENT para ANY>
+ <!ELEMENT b ANY>
+ <!ELEMENT i ANY>
+ <!ELEMENT ul (#PCDATA|li)*>
+ <!ELEMENT ol (#PCDATA|li)*>
+ <!ELEMENT li ANY>
+ <!ELEMENT pre ANY>
+ <!ELEMENT tt ANY>
+ <!ELEMENT see (#PCDATA|link)*>
+ <!ELEMENT note ANY>
+ <!ATTLIST note internal (yes|no) "no">
+ <!ELEMENT table (tr)+>
+ <!ELEMENT tr (td|th)+>
+ <!ELEMENT th ANY>
+ <!ELEMENT td ANY>
diff --git a/src/VBox/Main/idl/VirtualBox.xidl b/src/VBox/Main/idl/VirtualBox.xidl
new file mode 100644
index 00000000..886c03cf
--- /dev/null
+++ b/src/VBox/Main/idl/VirtualBox.xidl
@@ -0,0 +1,30710 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2006-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<!--
+ This is the master declaration for VirtualBox's Main API,
+ represented by COM/XPCOM and web service interfaces.
+
+ From this document, the build system generates several files
+ via XSLT that are then used during the build process.
+
+ Below is the list of XSL templates that operate on this file and
+ output files they generate. These XSL templates must be updated
+ whenever the schema of this file changes:
+
+ 1. src/VBox/Main/idl/midl.xsl =>
+ out/<platform>/bin/sdk/idl/VirtualBox.idl
+ (MS COM interface definition file for Main API)
+
+ 2. src/VBox/Main/idl/xpidl.xsl =>
+ out/<platform>/bin/sdk/idl/VirtualBox_XPCOM.idl
+ (XPCOM interface definition file for Main API)
+
+ 3. src/VBox/Main/idl/doxygen.xsl =>
+ out/<platform>/obj/src/VBox/Main/VirtualBox.idl
+ (pseudo-IDL for Doxygen to generate the official Main API
+ documentation)
+
+ 4. src/VBox/Main/webservice/*.xsl =>
+ a bunch of WSDL and C++ files
+ (VirtualBox web service sources and SOAP mappers;
+ see src/VBox/Main/webservice/Makefile.kmk for details)
+
+ 5. src/VBox/Frontends/VirtualBox/include/COMWrappers.xsl =>
+ out/<platform>/obj/src/VBox/Frontends/VirtualBox/VirtualBox/include/COMWrappers.h
+ (smart Qt-based C++ wrapper classes for COM interfaces
+ of the Main API)
+
+ 6. src/VBox/Installer/win32/VirtualBox_TypeLib.xsl =>
+ out/<platform>/obj/src/VBox/Installer/win32/VirtualBox_TypeLib.wxi
+ (Main API TypeLib block for the WiX installer)
+
+ 7. src/VBox/Runtime/common/err/errmsgvboxcom.xsl =>
+ out/<platform>/obj/Runtime/errmsgvboxcomdata.h
+ (<result> extraction for the %Rhrc format specifier)
+
+
+ Note! SlickEdit users can use the 'xml_validate' command on the SlickEdit
+ command line to verify the document against the DTD. Uncomment the
+ DOCTYPE below first (java doesn't seem to like it).
+-->
+
+<!DOCTYPE idl SYSTEM "VirtualBox.dtd">
+
+<idl>
+
+<desc>
+ Welcome to the <b>VirtualBox Main API documentation</b>. This documentation
+ describes the so-called <i>VirtualBox Main API</i> which comprises all public
+ COM interfaces and components provided by the VirtualBox server and by the
+ VirtualBox client library.
+
+ VirtualBox employs a client-server design, meaning that whenever any part of
+ VirtualBox is running -- be it the Qt GUI, the VBoxManage command-line
+ interface or any virtual machine --, a dedicated server process named
+ VBoxSVC runs in the background. This allows multiple processes working with
+ VirtualBox to cooperate without conflicts. These processes communicate to each
+ other using inter-process communication facilities provided by the COM
+ implementation of the host computer.
+
+ On Windows platforms, the VirtualBox Main API uses Microsoft COM, a native COM
+ implementation. On all other platforms, Mozilla XPCOM, an open-source COM
+ implementation, is used.
+
+ All the parts that a typical VirtualBox user interacts with (the Qt GUI
+ and the VBoxManage command-line interface) are technically
+ front-ends to the Main API and only use the interfaces that are documented
+ in this Main API documentation. This ensures that, with any given release
+ version of VirtualBox, all capabilities of the product that could be useful
+ to an external client program are always exposed by way of this API.
+
+ The VirtualBox Main API (also called the <i>VirtualBox COM library</i>)
+ contains two public component classes:
+ <tt>%VirtualBox.VirtualBox</tt> and <tt>%VirtualBox.Session</tt>, which
+ implement IVirtualBox and ISession interfaces respectively. These two classes
+ are of supreme importance and will be needed in order for any front-end
+ program to do anything useful. It is recommended to read the documentation of
+ the mentioned interfaces first.
+
+ The <tt>%VirtualBox.VirtualBox</tt> class is a singleton. This means that
+ there can be only one object of this class on the local machine at any given
+ time. This object is a parent of many other objects in the VirtualBox COM
+ library and lives in the VBoxSVC process. In fact, when you create an instance
+ of the <tt>VirtualBox.VirtualBox</tt>, the COM subsystem checks if the VBoxSVC
+ process is already running, starts it if not, and returns you a reference to
+ the <tt>VirtualBox</tt> object created in this process. When the last reference
+ to this object is released, the VBoxSVC process ends (with a 5 second delay to
+ protect from too frequent restarts).
+
+ The <tt>%VirtualBox.Session</tt> class is a regular component. You can create
+ as many <tt>Session</tt> objects as you need but all of them will live in a
+ process which issues the object instantiation call. <tt>Session</tt> objects
+ represent virtual machine sessions which are used to configure virtual
+ machines and control their execution.
+
+ The naming of methods and attributes is very clearly defined: they all start
+ with a lowercase letter (except if they start with an acronym), and are using
+ CamelCase style otherwise. This naming only applies to the IDL description,
+ and is modified by the various language bindings (some convert the first
+ character to upper case, some not). See the SDK reference for more details
+ about how to call a method or attribute from a specific programming language.
+</desc>
+
+<if target="midl">
+ <cpp line="enum {"/>
+ <cpp line=" kTypeLibraryMajorVersion = 1,"/>
+ <cpp line=" kTypeLibraryMinorVersion = 3"/>
+ <cpp line="};"/>
+</if>
+
+<if target="xpidl">
+ <!-- NS_IMPL_THREADSAFE_ISUPPORTSxx_CI macros are placed here, for convenience -->
+ <cpp>
+/* currently, nsISupportsImpl.h lacks the below-like macros */
+
+#define NS_IMPL_THREADSAFE_QUERY_INTERFACE1_CI NS_IMPL_QUERY_INTERFACE1_CI
+#define NS_IMPL_THREADSAFE_QUERY_INTERFACE2_CI NS_IMPL_QUERY_INTERFACE2_CI
+#define NS_IMPL_THREADSAFE_QUERY_INTERFACE3_CI NS_IMPL_QUERY_INTERFACE3_CI
+#define NS_IMPL_THREADSAFE_QUERY_INTERFACE4_CI NS_IMPL_QUERY_INTERFACE4_CI
+#define NS_IMPL_THREADSAFE_QUERY_INTERFACE5_CI NS_IMPL_QUERY_INTERFACE5_CI
+#define NS_IMPL_THREADSAFE_QUERY_INTERFACE6_CI NS_IMPL_QUERY_INTERFACE6_CI
+#define NS_IMPL_THREADSAFE_QUERY_INTERFACE7_CI NS_IMPL_QUERY_INTERFACE7_CI
+#define NS_IMPL_THREADSAFE_QUERY_INTERFACE8_CI NS_IMPL_QUERY_INTERFACE8_CI
+
+
+#ifndef NS_IMPL_THREADSAFE_ISUPPORTS1_CI
+# define NS_IMPL_THREADSAFE_ISUPPORTS1_CI(_class, _interface) \
+ NS_IMPL_THREADSAFE_ADDREF(_class) \
+ NS_IMPL_THREADSAFE_RELEASE(_class) \
+ NS_IMPL_THREADSAFE_QUERY_INTERFACE1_CI(_class, _interface) \
+ NS_IMPL_CI_INTERFACE_GETTER1(_class, _interface)
+#endif
+
+#ifndef NS_IMPL_THREADSAFE_ISUPPORTS2_CI
+# define NS_IMPL_THREADSAFE_ISUPPORTS2_CI(_class, _i1, _i2) \
+ NS_IMPL_THREADSAFE_ADDREF(_class) \
+ NS_IMPL_THREADSAFE_RELEASE(_class) \
+ NS_IMPL_THREADSAFE_QUERY_INTERFACE2_CI(_class, _i1, _i2) \
+ NS_IMPL_CI_INTERFACE_GETTER2(_class, _i1, _i2)
+#endif
+
+#ifndef NS_IMPL_THREADSAFE_ISUPPORTS3_CI
+# define NS_IMPL_THREADSAFE_ISUPPORTS3_CI(_class, _i1, _i2, _i3) \
+ NS_IMPL_THREADSAFE_ADDREF(_class) \
+ NS_IMPL_THREADSAFE_RELEASE(_class) \
+ NS_IMPL_THREADSAFE_QUERY_INTERFACE3_CI(_class, _i1, _i2, _i3) \
+ NS_IMPL_CI_INTERFACE_GETTER3(_class, _i1, _i2, _i3)
+#endif
+
+#ifndef NS_IMPL_THREADSAFE_ISUPPORTS4_CI
+# define NS_IMPL_THREADSAFE_ISUPPORTS4_CI(_class, _i1, _i2, _i3, _i4) \
+ NS_IMPL_THREADSAFE_ADDREF(_class) \
+ NS_IMPL_THREADSAFE_RELEASE(_class) \
+ NS_IMPL_THREADSAFE_QUERY_INTERFACE4_CI(_class, _i1, _i2, _i3, _i4) \
+ NS_IMPL_CI_INTERFACE_GETTER4(_class, _i1, _i2, _i3, _i4)
+#endif
+
+#ifndef NS_IMPL_THREADSAFE_ISUPPORTS5_CI
+# define NS_IMPL_THREADSAFE_ISUPPORTS5_CI(_class, _i1, _i2, _i3, _i4, _i5) \
+ NS_IMPL_THREADSAFE_ADDREF(_class) \
+ NS_IMPL_THREADSAFE_RELEASE(_class) \
+ NS_IMPL_THREADSAFE_QUERY_INTERFACE5_CI(_class, _i1, _i2, _i3, _i4, _i5) \
+ NS_IMPL_CI_INTERFACE_GETTER5(_class, _i1, _i2, _i3, _i4, _i5)
+#endif
+
+#ifndef NS_IMPL_THREADSAFE_ISUPPORTS6_CI
+# define NS_IMPL_THREADSAFE_ISUPPORTS6_CI(_class, _i1, _i2, _i3, _i4, _i5, _i6) \
+ NS_IMPL_THREADSAFE_ADDREF(_class) \
+ NS_IMPL_THREADSAFE_RELEASE(_class) \
+ NS_IMPL_THREADSAFE_QUERY_INTERFACE6_CI(_class, _i1, _i2, _i3, _i4, _i5, _i6) \
+ NS_IMPL_CI_INTERFACE_GETTER6(_class, _i1, _i2, _i3, _i4, _i5, _i6)
+#endif
+
+#ifndef NS_IMPL_THREADSAFE_ISUPPORTS7_CI
+# define NS_IMPL_THREADSAFE_ISUPPORTS7_CI(_class, _i1, _i2, _i3, _i4, _i5, _i6, _i7) \
+ NS_IMPL_THREADSAFE_ADDREF(_class) \
+ NS_IMPL_THREADSAFE_RELEASE(_class) \
+ NS_IMPL_THREADSAFE_QUERY_INTERFACE7_CI(_class, _i1, _i2, _i3, _i4, _i5, _i6, _i7) \
+ NS_IMPL_CI_INTERFACE_GETTER7(_class, _i1, _i2, _i3, _i4, _i5, _i6, _i7)
+#endif
+
+#ifndef NS_IMPL_THREADSAFE_ISUPPORTS8_CI
+# define NS_IMPL_THREADSAFE_ISUPPORTS8_CI(_class, _i1, _i2, _i3, _i4, _i5, _i6, _i7, _i8) \
+ NS_IMPL_THREADSAFE_ADDREF(_class) \
+ NS_IMPL_THREADSAFE_RELEASE(_class) \
+ NS_IMPL_THREADSAFE_QUERY_INTERFACE8_CI(_class, _i1, _i2, _i3, _i4, _i5, _i6, _i7, _i8) \
+ NS_IMPL_CI_INTERFACE_GETTER8(_class, _i1, _i2, _i3, _i4, _i5, _i6, _i7, _i8)
+#endif
+
+#ifndef NS_IMPL_QUERY_INTERFACE1_AMBIGUOUS_CI
+# define NS_IMPL_QUERY_INTERFACE1_AMBIGUOUS_CI(_class, _i1, _ic1) \
+ NS_INTERFACE_MAP_BEGIN(_class) \
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(_i1, _ic1) \
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, _ic1) \
+ NS_IMPL_QUERY_CLASSINFO(_class) \
+ NS_INTERFACE_MAP_END
+#endif
+
+#ifndef NS_IMPL_QUERY_INTERFACE2_AMBIGUOUS_CI
+# define NS_IMPL_QUERY_INTERFACE2_AMBIGUOUS_CI(_class, _i1, _ic1, \
+ _i2, _ic2) \
+ NS_INTERFACE_MAP_BEGIN(_class) \
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(_i1, _ic1) \
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(_i2, _ic2) \
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, _ic1) \
+ NS_IMPL_QUERY_CLASSINFO(_class) \
+ NS_INTERFACE_MAP_END
+#endif
+
+#ifndef NS_IMPL_QUERY_INTERFACE3_AMBIGUOUS_CI
+# define NS_IMPL_QUERY_INTERFACE3_AMBIGUOUS_CI(_class, _i1, _ic1, \
+ _i2, _ic2, _i3, _ic3) \
+ NS_INTERFACE_MAP_BEGIN(_class) \
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(_i1, _ic1) \
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(_i2, _ic2) \
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(_i3, _ic3) \
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, _ic1) \
+ NS_IMPL_QUERY_CLASSINFO(_class) \
+ NS_INTERFACE_MAP_END
+#endif
+
+#define NS_IMPL_THREADSAFE_QUERY_INTERFACE1_AMBIGUOUS_CI NS_IMPL_QUERY_INTERFACE1_AMBIGUOUS_CI
+#define NS_IMPL_THREADSAFE_QUERY_INTERFACE2_AMBIGUOUS_CI NS_IMPL_QUERY_INTERFACE2_AMBIGUOUS_CI
+#define NS_IMPL_THREADSAFE_QUERY_INTERFACE3_AMBIGUOUS_CI NS_IMPL_QUERY_INTERFACE3_AMBIGUOUS_CI
+
+#ifndef NS_IMPL_THREADSAFE_ISUPPORTS1_AMBIGUOUS_CI
+# define NS_IMPL_THREADSAFE_ISUPPORTS1_AMBIGUOUS_CI(_class, _i1, _ic1) \
+ NS_IMPL_THREADSAFE_ADDREF(_class) \
+ NS_IMPL_THREADSAFE_RELEASE(_class) \
+ NS_IMPL_THREADSAFE_QUERY_INTERFACE1_AMBIGUOUS_CI(_class, _i1, _ic1) \
+ NS_IMPL_CI_INTERFACE_GETTER1(_class, _i1)
+#endif
+
+#ifndef NS_IMPL_THREADSAFE_ISUPPORTS2_AMBIGUOUS_CI
+# define NS_IMPL_THREADSAFE_ISUPPORTS2_AMBIGUOUS_CI(_class, _i1, _ic1, \
+ _i2, _ic2) \
+ NS_IMPL_THREADSAFE_ADDREF(_class) \
+ NS_IMPL_THREADSAFE_RELEASE(_class) \
+ NS_IMPL_THREADSAFE_QUERY_INTERFACE2_AMBIGUOUS_CI(_class, _i1, _ic1, \
+ _i2, _ic2) \
+ NS_IMPL_CI_INTERFACE_GETTER2(_class, _i1, _i2)
+#endif
+
+#ifndef NS_IMPL_THREADSAFE_ISUPPORTS3_AMBIGUOUS_CI
+# define NS_IMPL_THREADSAFE_ISUPPORTS3_AMBIGUOUS_CI(_class, _i1, _ic1, \
+ _i2, _ic2, _i3, _ic3) \
+ NS_IMPL_THREADSAFE_ADDREF(_class) \
+ NS_IMPL_THREADSAFE_RELEASE(_class) \
+ NS_IMPL_THREADSAFE_QUERY_INTERFACE3_AMBIGUOUS_CI(_class, _i1, _ic1, \
+ _i2, _ic2, _i3, _ic3) \
+ NS_IMPL_CI_INTERFACE_GETTER3(_class, _i1, _i2, _i3)
+#endif
+
+ </cpp>
+</if>
+
+<!--
+ Note!! Don't forget to update the python bindings (++) when changing the
+ UUID or version!!
+-->
+<library
+ name="VirtualBox"
+ uuid="d7569351-1750-46f0-936e-bd127d5bc264"
+ version="1.3"
+>
+
+<application
+ name="VirtualBox"
+ uuid="819B4D85-9CEE-493C-B6FC-64FFE759B3C9"
+ supportsErrorInfo="yes"
+>
+
+
+ <!--
+ // COM result codes for VirtualBox
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <descGroup id="VirtualBox_COM_result_codes" title="VirtualBox COM result codes">
+ <desc>
+ This section describes all VirtualBox-specific COM result codes that may
+ be returned by methods of VirtualBox COM interfaces in addition to
+ standard COM result codes.
+
+ Note that along with the result code, every VirtualBox method returns
+ extended error information through the IVirtualBoxErrorInfo interface on
+ failure. This interface is a preferred way to present the error to the end
+ user because it contains a human readable description of the error. Raw
+ result codes, both standard and described in this section, are intended to
+ be used by programs to analyze the reason of a failure and select an
+ appropriate course of action without involving the end user (for example,
+ retry the operation later or make a different call).
+
+ The standard COM result codes that may originate from our methods include:
+
+ <dl>
+ <dt><tt>E_INVALIDARG</tt></dt>
+ <dd>
+ Returned when the value of the method's argument is not within the range
+ of valid values. This should not be confused with situations when the
+ value is within the range but simply doesn't suit the current object
+ state and there is a possibility that it will be accepted later (in such
+ cases VirtualBox-specific codes are returned, for example,
+ <link to="VBOX_E_OBJECT_NOT_FOUND"/>).
+ </dd>
+ <dt><tt>E_POINTER</tt></dt>
+ <dd>
+ Returned if a memory pointer for the output argument is invalid (for
+ example, @c null). When pointers representing input arguments (such
+ as strings) are invalid, E_INVALIDARG is returned.
+ </dd>
+ <dt><tt>E_ACCESSDENIED</tt></dt>
+ <dd>
+ Returned when the called object is not ready. Since the lifetime of a
+ public COM object cannot be fully controlled by the implementation,
+ VirtualBox maintains the readiness state for all objects it creates and
+ returns this code in response to any method call on the object that was
+ deactivated by VirtualBox and is not functioning any more.
+ </dd>
+ <dt><tt>E_OUTOFMEMORY</tt></dt>
+ <dd>
+ Returned when a memory allocation operation fails.
+ </dd>
+ </dl>
+ </desc>
+ </descGroup>
+
+ <!--
+ Note that src/VBox/Runtime/common/err/errmsgvboxcom.xsl will ignore
+ everything in <result>/<desc> after (and including) the first dot, so express
+ the matter of the error code in the first sentence and keep it short.
+ -->
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND" value="0x80BB0001">
+ <desc>
+ Object corresponding to the supplied arguments does not exist.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_INVALID_VM_STATE" value="0x80BB0002">
+ <desc>
+ Current virtual machine state prevents the operation.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_VM_ERROR" value="0x80BB0003">
+ <desc>
+ Virtual machine error occurred attempting the operation.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_FILE_ERROR" value="0x80BB0004">
+ <desc>
+ File not accessible or erroneous file contents.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_IPRT_ERROR" value="0x80BB0005">
+ <desc>
+ Runtime subsystem error.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_PDM_ERROR" value="0x80BB0006">
+ <desc>
+ Pluggable Device Manager error.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_INVALID_OBJECT_STATE" value="0x80BB0007">
+ <desc>
+ Current object state prohibits operation.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_HOST_ERROR" value="0x80BB0008">
+ <desc>
+ Host operating system related error.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_NOT_SUPPORTED" value="0x80BB0009">
+ <desc>
+ Requested operation is not supported.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_XML_ERROR" value="0x80BB000A">
+ <desc>
+ Invalid XML found.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_INVALID_SESSION_STATE" value="0x80BB000B">
+ <desc>
+ Current session state prohibits operation.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_OBJECT_IN_USE" value="0x80BB000C">
+ <desc>
+ Object being in use prohibits operation.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_PASSWORD_INCORRECT" value="0x80BB000D">
+ <desc>
+ A provided password was incorrect.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_MAXIMUM_REACHED" value="0x80BB000E">
+ <desc>
+ A maximum has been reached.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_GSTCTL_GUEST_ERROR" value="0x80BB000F">
+ <desc>
+ Guest Control reported an error from the guest side.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_TIMEOUT" value="0x80BB0010">
+ <desc>
+ The operation ran into an explicitly requested timeout.
+ </desc>
+ </result>
+
+ <result name="VBOX_E_DND_ERROR" value="0x80BB0011">
+ <desc>
+ A drag and drop error has occurred.
+ </desc>
+ </result>
+
+ <!--
+ Note that src/VBox/Runtime/common/err/errmsgvboxcom.xsl will ignore
+ everything in <result>/<desc> after (and including) the first dot, so express
+ the matter of the error code in the first sentence and keep it short.
+ -->
+
+ <descGroup/>
+
+ <!--
+ // all common enums
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="SettingsVersion"
+ uuid="b4cc23c2-96f2-419d-830b-bd13c1135dfb"
+ >
+ <desc>
+ Settings version of VirtualBox settings files. This is written to
+ the "version" attribute of the root "VirtualBox" element in the settings
+ file XML and indicates which VirtualBox version wrote the file.
+ </desc>
+
+ <const name="Null" value="0">
+ <desc>Null value, indicates invalid version.</desc>
+ </const>
+ <const name="v1_0" value="1">
+ <desc>Legacy settings version, not currently supported.</desc>
+ </const>
+ <const name="v1_1" value="2">
+ <desc>Legacy settings version, not currently supported.</desc>
+ </const>
+ <const name="v1_2" value="3">
+ <desc>Legacy settings version, not currently supported.</desc>
+ </const>
+ <const name="v1_3pre" value="4">
+ <desc>Legacy settings version, not currently supported.</desc>
+ </const>
+ <const name="v1_3" value="5">
+ <desc>Settings version "1.3", written by VirtualBox 2.0.12.</desc>
+ <!--
+ Machine XML: Capitalization of Uart, Lpt elements and many attributes changed.
+ -->
+ </const>
+ <const name="v1_4" value="6">
+ <desc>Intermediate settings version, understood by VirtualBox 2.1.x.</desc>
+ <!--
+ VirtualBox.xml: big DiskRegistry -> MediaRegistry revamp, various HardDisk types merged
+ (was VirtualDiskImage, VMDKImage, VHDImage, ISCSIHardDisk, CustomHardDisk, DiffHardDisk)
+ -->
+ </const>
+ <const name="v1_5" value="7">
+ <desc>Intermediate settings version, understood by VirtualBox 2.1.x.</desc>
+ <!--
+ 2008-09-04: 2.0.0 released
+ 2008-11-20: settings version 1.5 introduced
+ 2008-12-17: 2.1.0 released
+ Machine changes:
+ guest OS identifiers changed;
+ Machine/Hardware/Display/MonitorCount renamed to monitorCount;
+ Machine/Hardware/Display/Accelerate3D renamed to accelerate3D;
+ Machine/Hardware/CPU/CPUCount/@count changed to CPU/@count
+ -->
+ </const>
+ <const name="v1_6" value="8">
+ <desc>Settings version "1.6", written by VirtualBox 2.1.4 (at least).</desc>
+ <!--
+ 2008-12-17: 2.1.0 released
+ 2008-12-19: settings version 1.6 introduced (is in 2.1 branch)
+ 2009-04-08: 2.2.0 released
+ Machine changes: remove all Machine/Hardware/Network/Adapter/HostInterface[@TAPSetup or @TAPTerminate]/ attributes (done)
+ -->
+ </const>
+ <const name="v1_7" value="9">
+ <desc>Settings version "1.7", written by VirtualBox 2.2.x and 3.0.x.</desc>
+ <!--
+ 2008-12-17: 2.1.0 released
+ 2009-03-11: settings version 1.7 introduced (is in 2.2 branch)
+ 2009-04-08: 2.2.0 released
+ VirtualBox.xml additions: NetserviceRegistry with DHCPServers (done)
+ Machine changes: HardDiskAttachments is now StorageControllers (done)
+ -->
+ </const>
+ <const name="v1_8" value="10">
+ <desc>Intermediate settings version "1.8", understood by VirtualBox 3.1.x.</desc>
+ <!--
+ Machine additions: Display/@accelerate2DVideo (done)
+ -->
+ </const>
+ <const name="v1_9" value="11">
+ <desc>Settings version "1.9", written by VirtualBox 3.1.x.</desc>
+ <!--
+ The big storage controller / DVD / Floppy rework (done)
+ -->
+ </const>
+ <const name="v1_10" value="12">
+ <desc>Settings version "1.10", written by VirtualBox 3.2.x.</desc>
+ <!--
+ Machine changes: RTC localOrUTC (done)
+ CPU hot-plug support
+ -->
+ </const>
+ <const name="v1_11" value="13">
+ <desc>Settings version "1.11", written by VirtualBox 4.0.x.</desc>
+ <!--
+ Machine changes: HD Audio controller, per-machine disk registries,
+ /@format attribute for DVD and floppy images.
+ -->
+ </const>
+ <const name="v1_12" value="14">
+ <desc>Settings version "1.12", written by VirtualBox 4.1.x.</desc>
+ <!--
+ Machine changes: raw PCI device attachment;
+ NetworkAdapter changes: bandwidth group.
+ -->
+ </const>
+ <const name="v1_13" value="15">
+ <desc>Settings version "1.13", written by VirtualBox 4.2.x.</desc>
+ <!--
+ Machine changes: tracing config, groups, autostart;
+ NetworkAdapter changes: unit for bandwidth group limits.
+ -->
+ </const>
+ <const name="v1_14" value="16">
+ <desc>Settings version "1.14", written by VirtualBox 4.3.x.</desc>
+ <!--
+ Machine changes: default frontend, USB rework.
+ -->
+ </const>
+ <const name="v1_15" value="17">
+ <desc>Settings version "1.15", written by VirtualBox 5.0.x.</desc>
+ <!--
+ Machine changes: hot-plug flag for storage devices.
+ -->
+ </const>
+ <const name="v1_16" value="18">
+ <desc>Settings version "1.16", written by VirtualBox 5.1.x.</desc>
+ <!--
+ Machine changes: NVMe storage controller, paravirt debug options, CPU
+ profile and BIOS/CPU APIC settings.
+ VirtualBox.xml: Add support for additional USB device sources (e.g. USB/IP)
+ -->
+ </const>
+ <const name="v1_17" value="19">
+ <desc>Settings version "1.17", written by VirtualBox 6.0.x.</desc>
+ <!--
+ Machine changes: nested hardware virtualization, hardware
+ virtualization using native API, shared folder automount point,
+ UART type selection and VM process priority.
+ -->
+ </const>
+ <const name="v1_18" value="20">
+ <desc>Settings version "1.18", written by VirtualBox 6.1.x.</desc>
+ <!--
+ Machine changes: virtio-scsi storage controller, NVRAM device.
+ -->
+ </const>
+ <const name="v1_19" value="21">
+ <desc>Settings version "1.19", written by VirtualBox 7.0.x.</desc>
+ <!--
+ VirtualBox.xml additions: HostOnlyNetworks in NetserviceRegistry
+ Machine changes: IOMMU device, HostOnlyNetwork mode for
+ NetworkAdapter, full VM encryption.
+ -->
+ </const>
+ <const name="Future" value="99999">
+ <desc>Settings version greater than "1.19", written by a future VirtualBox version.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="AccessMode"
+ uuid="1da0007c-ddf7-4be8-bcac-d84a1558785f"
+ >
+ <desc>
+ Access mode for opening files.
+ </desc>
+
+ <const name="ReadOnly" value="1"/>
+ <const name="ReadWrite" value="2"/>
+ </enum>
+
+ <enum
+ name="MachineState"
+ uuid="00bc01b5-00a4-48db-000a-9061008357aa"
+ >
+ <desc>
+ Virtual machine execution state.
+
+ This enumeration represents possible values of the <link
+ to="IMachine::state"/> attribute.
+
+ Below is the basic virtual machine state diagram. It shows how the state
+ changes during virtual machine execution. The text in square braces shows
+ a method of the IConsole or IMachine interface that performs the given state
+ transition.
+
+ <pre>
+ +---------[powerDown()] &lt;- Stuck &lt;--[failure]-+
+ V |
+ +-&gt; PoweredOff --+--&gt;[powerUp()]--&gt; Starting --+ | +-----[resume()]-----+
+ | | | | V |
+ | Aborted &lt;----+ +--&gt; Running --[pause()]--&gt; Paused
+ | | ^ | ^ |
+ | AbortedSaved &lt;-----------[failure]-----&lt;----+ | | | |
+ | | | | | | |
+ | +-------------+ | | | | |
+ | V | | | | |
+ | Saved --------[powerUp()]-----&gt; Restoring -+ | | | |
+ | ^ | | | |
+ | | +-----------------------------------------+-|-------------------+ +
+ | | | | |
+ | | +- OnlineSnapshotting &lt;--[takeSnapshot()]&lt;--+---------------------+
+ | | | |
+ | +-------- Saving &lt;--------[saveState()]&lt;----------+---------------------+
+ | | |
+ +-------------- Stopping -------[powerDown()]&lt;----------+---------------------+
+ </pre>
+
+ Note that states to the right of PoweredOff, Aborted, AbortedSaved, and
+ Saved in the above diagram are called <i>online VM states</i>. These
+ states represent the virtual machine which is being executed in a
+ dedicated process (usually with a GUI window attached to it where you can
+ see the activity of the virtual machine and interact with it). There are
+ two special pseudo-states, FirstOnline and LastOnline, that can be used
+ in relational expressions to detect if the given machine state is online
+ or not:
+
+ <pre>
+ if (machine.GetState() &gt;= MachineState_FirstOnline &amp;&amp;
+ machine.GetState() &lt;= MachineState_LastOnline)
+ {
+ ...the machine is being executed...
+ }
+ </pre>
+
+ When the virtual machine is in one of the online VM states (that is, being
+ executed), only a few machine settings can be modified. Methods working
+ with such settings contain an explicit note about that. An attempt to
+ change any other setting or perform a modifying operation during this time
+ will result in the @c VBOX_E_INVALID_VM_STATE error.
+
+ All online states except Running, Paused and Stuck are transitional: they
+ represent temporary conditions of the virtual machine that will last as
+ long as the operation that initiated such a condition.
+
+ The Stuck state is a special case. It means that execution of the machine
+ has reached the "Guru Meditation" condition. This condition indicates an
+ internal VMM (virtual machine manager) failure which may happen as a
+ result of either an unhandled low-level virtual hardware exception or one
+ of the recompiler exceptions (such as the <i>too-many-traps</i>
+ condition).
+
+ Note also that any online VM state may transit to the Aborted state. This
+ happens if the process that is executing the virtual machine terminates
+ unexpectedly (for example, crashes). Other than that, the Aborted state is
+ equivalent to PoweredOff.
+
+ There are also a few additional state diagrams that do not deal with
+ virtual machine execution and therefore are shown separately. The states
+ shown on these diagrams are called <i>offline VM states</i> (this includes
+ PoweredOff, Aborted, AbortedSaved and Saved too).
+
+ The first diagram shows what happens when a lengthy setup operation is
+ being executed (such as <link to="IMachine::attachDevice"/>).
+
+ <pre>
+ +----------------------------------(same state as before the call)------+
+ | |
+ +-&gt; PoweredOff --+ |
+ | | |
+ |-&gt; Aborted -----+--&gt;[lengthy VM configuration call] --&gt; SettingUp -----+
+ | |
+ +-&gt; Saved -------+
+ </pre>
+
+ The next two diagrams demonstrate the process of taking a snapshot of a
+ powered off virtual machine, restoring the state to that as of a snapshot
+ or deleting a snapshot, respectively.
+
+ <pre>
+ +----------------------------------(same state as before the call)------+
+ | |
+ +-&gt; PoweredOff --+ |
+ | +--&gt;[takeSnapshot()] ------------------&gt; Snapshotting -+
+ +-&gt; Aborted -----+
+
+ +-&gt; PoweredOff --+
+ | |
+ | Aborted -----+--&gt;[restoreSnapshot() ]-------&gt; RestoringSnapshot -+
+ | | [deleteSnapshot() ]-------&gt; DeletingSnapshot --+
+ +-&gt; Saved -------+ |
+ | |
+ +---(Saved if restored from an online snapshot, PoweredOff otherwise)---+
+ </pre>
+
+ <note internal="yes">
+ For whoever decides to touch this enum: In order to keep the
+ comparisons involving FirstOnline and LastOnline pseudo-states valid,
+ the numeric values of these states must be correspondingly updated if
+ needed: for any online VM state, the condition
+ <tt>FirstOnline &lt;= state &lt;= LastOnline</tt> must be
+ @c true. The same relates to transient states for which
+ the condition <tt>FirstOnline &lt;= state &lt;= LastOnline</tt> must be
+ @c true.
+ </note>
+ </desc>
+
+ <const name="Null" value="0">
+ <desc>Null value (never used by the API).</desc>
+ </const>
+ <const name="PoweredOff" value="1">
+ <desc>
+ The machine is not running and has no saved execution state; it has
+ either never been started or been shut down successfully.
+ </desc>
+ </const>
+ <const name="Saved" value="2">
+ <desc>
+ The machine is not currently running, but the execution state of the machine
+ has been saved to an external file when it was running, from where
+ it can be resumed.
+ </desc>
+ </const>
+ <const name="Teleported" value="3">
+ <desc>
+ The machine was teleported to a different host (or process) and then
+ powered off. Take care when powering it on again may corrupt resources
+ it shares with the teleportation target (e.g. disk and network).
+ </desc>
+ </const>
+ <const name="Aborted" value="4">
+ <desc>
+ The process running the machine has terminated abnormally. This may
+ indicate a crash of the VM process in host execution context, or
+ the VM process has been terminated externally.
+ </desc>
+ </const>
+ <const name="AbortedSaved" value="5">
+ <desc>
+ A machine in the @c Saved stated has terminated abnormally before
+ reaching the @c Running state. Similar to the @c Aborted state,
+ this may indicate a crash of the VM process in host execution
+ context, or the VM process has been terminated externally.
+ </desc>
+ </const>
+
+ <const name="Running" value="6">
+ <desc>
+ The machine is currently being executed.
+ <note internal="yes">
+ For whoever decides to touch this enum: In order to keep the
+ comparisons in the old source code valid, this state must immediately
+ precede the Paused state.
+
+ @todo Lift this spectacularly wonderful restriction.
+ </note>
+ </desc>
+ </const>
+ <const name="Paused" value="7">
+ <desc>
+ Execution of the machine has been paused.
+ <note internal="yes">
+ For whoever decides to touch this enum: In order to keep the
+ comparisons in the old source code valid, this state must immediately
+ follow the Running state.
+
+ @todo Lift this spectacularly wonderful restriction.
+ </note>
+ </desc>
+ </const>
+ <const name="Stuck" value="8">
+ <desc>
+ Execution of the machine has reached the "Guru Meditation"
+ condition. This indicates a severe error in the hypervisor itself.
+ <note internal="yes">
+ bird: Why this uncool name? Could we rename it to "GuruMeditation" or
+ "Guru", perhaps? Or are there some other VMM states that are
+ intended to be lumped in here as well?
+ </note>
+ </desc>
+ </const>
+ <const name="Teleporting" value="9">
+ <desc>
+ The machine is about to be teleported to a different host or process.
+ It is possible to pause a machine in this state, but it will go to the
+ @c TeleportingPausedVM state and it will not be
+ possible to resume it again unless the teleportation fails.
+ </desc>
+ </const>
+ <const name="LiveSnapshotting" value="10">
+ <desc>
+ A live snapshot is being taken. The machine is running normally, but
+ some of the runtime configuration options are inaccessible. Also, if
+ paused while in this state it will transition to
+ @c OnlineSnapshotting and it will not be resume the
+ execution until the snapshot operation has completed.
+ </desc>
+ </const>
+ <const name="Starting" value="11">
+ <desc>
+ Machine is being started after powering it on from a
+ zero execution state.
+ </desc>
+ </const>
+ <const name="Stopping" value="12">
+ <desc>
+ Machine is being normally stopped powering it off, or after the guest OS
+ has initiated a shutdown sequence.
+ </desc>
+ </const>
+ <const name="Saving" value="13">
+ <desc>
+ Machine is saving its execution state to a file.
+ </desc>
+ </const>
+ <const name="Restoring" value="14">
+ <desc>
+ Execution state of the machine is being restored from a file
+ after powering it on from either the @c Saved or @c AbortedSaved
+ execution state.
+ </desc>
+ </const>
+ <const name="TeleportingPausedVM" value="15">
+ <desc>
+ The machine is being teleported to another host or process, but it is
+ not running. This is the paused variant of the
+ @c Teleporting state.
+ </desc>
+ </const>
+ <const name="TeleportingIn" value="16">
+ <desc>
+ Teleporting the machine state in from another host or process.
+ </desc>
+ </const>
+ <const name="DeletingSnapshotOnline" value="17">
+ <desc>
+ Like @c DeletingSnapshot, but the merging of media is ongoing in
+ the background while the machine is running.
+ </desc>
+ </const>
+ <const name="DeletingSnapshotPaused" value="18">
+ <desc>
+ Like @c DeletingSnapshotOnline, but the machine was paused when the
+ merging of differencing media was started.
+ </desc>
+ </const>
+ <const name="OnlineSnapshotting" value="19">
+ <desc>
+ Like @c LiveSnapshotting, but the machine was paused when the
+ merging of differencing media was started.
+ </desc>
+ </const>
+ <const name="RestoringSnapshot" value="20">
+ <desc>
+ A machine snapshot is being restored; this typically does not take long.
+ </desc>
+ </const>
+ <const name="DeletingSnapshot" value="21">
+ <desc>
+ A machine snapshot is being deleted; this can take a long time since this
+ may require merging differencing media. This value indicates that the
+ machine is not running while the snapshot is being deleted.
+ </desc>
+ </const>
+ <const name="SettingUp" value="22">
+ <desc>
+ Lengthy setup operation is in progress.
+ </desc>
+ </const>
+ <const name="Snapshotting" value="23">
+ <desc>
+ Taking an (offline) snapshot.
+ </desc>
+ </const>
+
+ <const name="FirstOnline" value="6" wsmap="suppress"> <!-- Running -->
+ <desc>
+ Pseudo-state: first online state (for use in relational expressions).
+ </desc>
+ </const>
+ <const name="LastOnline" value="19" wsmap="suppress"> <!-- OnlineSnapshotting -->
+ <desc>
+ Pseudo-state: last online state (for use in relational expressions).
+ </desc>
+ </const>
+
+ <const name="FirstTransient" value="9" wsmap="suppress"> <!-- Teleporting -->
+ <desc>
+ Pseudo-state: first transient state (for use in relational expressions).
+ </desc>
+ </const>
+ <const name="LastTransient" value="23" wsmap="suppress"> <!-- Snapshotting -->
+ <desc>
+ Pseudo-state: last transient state (for use in relational expressions).
+ </desc>
+ </const>
+
+ </enum>
+
+ <enum
+ name="SessionState"
+ uuid="cf2700c0-ea4b-47ae-9725-7810114b94d8"
+ >
+ <desc>
+ Session state. This enumeration represents possible values of
+ <link to="IMachine::sessionState"/> and <link to="ISession::state"/>
+ attributes.
+ </desc>
+
+ <const name="Null" value="0">
+ <desc>Null value (never used by the API).</desc>
+ </const>
+ <const name="Unlocked" value="1">
+ <desc>
+ In <link to="IMachine::sessionState"/>, this means that the machine
+ is not locked for any sessions.
+
+ In <link to="ISession::state"/>, this means that no machine is
+ currently locked for this session.
+ </desc>
+ </const>
+ <const name="Locked" value="2">
+ <desc>
+ In <link to="IMachine::sessionState"/>, this means that the machine
+ is currently locked for a session, whose process identifier can
+ then be found in the <link to="IMachine::sessionPID" /> attribute.
+
+ In <link to="ISession::state"/>, this means that a machine is
+ currently locked for this session, and the mutable machine object
+ can be found in the <link to="ISession::machine"/> attribute
+ (see <link to="IMachine::lockMachine" /> for details).
+ </desc>
+ </const>
+ <const name="Spawning" value="3">
+ <desc>
+ A new process is being spawned for the machine as a result of
+ <link to="IMachine::launchVMProcess"/> call. This state also occurs
+ as a short transient state during an <link to="IMachine::lockMachine"/>
+ call.
+ </desc>
+ </const>
+ <const name="Unlocking" value="4">
+ <desc>
+ The session is being unlocked.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="CPUArchitecture"
+ uuid="4a2c9915-12f1-43b2-bb84-e4bd4d5ca227"
+ >
+ <desc>Basic CPU architecture types.</desc>
+ <const name="Any" value="0">
+ <desc>Matches any CPU architecture.</desc>
+ </const>
+ <const name="x86" value="1">
+ <desc>32-bit (and 16-bit) x86.</desc>
+ </const>
+ <const name="AMD64" value="2">
+ <desc>64-bit x86. (Also known as x86-64 or x64.)</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="CPUPropertyType"
+ uuid="3fcfe589-ca66-468f-e313-656f9d0b2eb6"
+ >
+ <desc>
+ Virtual CPU property type. This enumeration represents possible values of the
+ IMachine get- and setCPUProperty methods.
+ </desc>
+ <const name="Null" value="0">
+ <desc>Null value (never used by the API).</desc>
+ </const>
+ <const name="PAE" value="1">
+ <desc>
+ This setting determines whether VirtualBox will expose the Physical Address
+ Extension (PAE) feature of the host CPU to the guest. Note that in case PAE
+ is not available, it will not be reported.
+ </desc>
+ </const>
+ <const name="LongMode" value="2">
+ <desc>
+ This setting determines whether VirtualBox will advertise long mode
+ (i.e. 64-bit guest support) and let the guest enter it.
+ </desc>
+ </const>
+ <const name="TripleFaultReset" value="3">
+ <desc>
+ This setting determines whether a triple fault within a guest will
+ trigger an internal error condition and stop the VM (default) or reset
+ the virtual CPU/VM and continue execution.
+ </desc>
+ </const>
+ <const name="APIC" value="4">
+ <desc>
+ This setting determines whether an APIC is part of the virtual CPU.
+ This feature can only be turned off when the X2APIC feature is off.
+ </desc>
+ </const>
+ <const name="X2APIC" value="5">
+ <desc>
+ This setting determines whether an x2APIC is part of the virtual CPU.
+ Since this feature implies that the APIC feature is present, it
+ automatically enables the APIC feature when set.
+ </desc>
+ </const>
+ <const name="IBPBOnVMExit" value="6">
+ <desc>
+ If set, force an indirect branch prediction barrier on VM exits if the
+ host CPU supports it. This setting will significantly slow down workloads
+ causing many VM exits, so it is only recommended for situation where there
+ is a real need to be paranoid.
+ </desc>
+ </const>
+ <const name="IBPBOnVMEntry" value="7">
+ <desc>
+ If set, force an indirect branch prediction barrier on VM entry if the
+ host CPU supports it. This setting will significantly slow down workloads
+ causing many VM exits, so it is only recommended for situation where there
+ is a real need to be paranoid.
+ </desc>
+ </const>
+ <const name="HWVirt" value="8">
+ <desc>
+ Enabled the hardware virtualization (AMD-V/VT-x) feature on the guest CPU.
+ This requires hardware virtualization on the host CPU.
+ </desc>
+ </const>
+ <const name="SpecCtrl" value="9">
+ <desc>
+ If set, the speculation control CPUID bits and MSRs, when available on the
+ host, are exposed to the guest. Depending on the host CPU and operating
+ system, this may significantly slow down workloads causing many VM exits.
+ </desc>
+ </const>
+ <const name="SpecCtrlByHost" value="10">
+ <desc>
+ If set, the speculation controls are managed by the host. This is intended
+ for guests which do not set the speculation controls themselves.
+ Note! This has not yet been implemented beyond leaving everything to the host OS.
+ </desc>
+ </const>
+ <const name="L1DFlushOnEMTScheduling" value="11">
+ <desc>
+ If set and the host is affected by CVE-2018-3646, flushes the level 1 data
+ cache when the EMT is scheduled to do ring-0 guest execution. There could
+ be a small performance penalty for certain typs of workloads.
+ For security reasons this setting will be enabled by default.
+ </desc>
+ </const>
+ <const name="L1DFlushOnVMEntry" value="12">
+ <desc>
+ If set and the host is affected by CVE-2018-3646, flushes the level 1 data
+ on every VM entry. This setting may significantly slow down workloads
+ causing many VM exits, so it is only recommended for situation where there
+ is a real need to be paranoid.
+ </desc>
+ </const>
+ <const name="MDSClearOnEMTScheduling" value="13">
+ <desc>
+ If set and the host is affected by CVE-2018-12126, CVE-2018-12127, or
+ CVE-2018-12130, clears the relevant MDS buffers when the EMT is scheduled
+ to do ring-0 guest execution. There could be a small performance penalty
+ for certain typs of workloads. For security reasons this setting will be
+ enabled by default.
+ </desc>
+ </const>
+ <const name="MDSClearOnVMEntry" value="14">
+ <desc>
+ If set and the host is affected by CVE-2018-12126, CVE-2018-12127, or
+ CVE-2018-12130, clears the relevant MDS buffers on every VM entry. This
+ setting may slow down workloads causing many VM exits, so it is only
+ recommended for situation where there is a real need to be paranoid.
+ </desc>
+ </const>
+ </enum>
+
+
+ <enum
+ name="HWVirtExPropertyType"
+ uuid="00069d9c-00b5-460c-00dd-64250024f7aa"
+ >
+ <desc>
+ Hardware virtualization property type. This enumeration represents possible values
+ for the <link to="IMachine::getHWVirtExProperty"/> and
+ <link to="IMachine::setHWVirtExProperty"/> methods.
+ </desc>
+ <const name="Null" value="0">
+ <desc>Null value (never used by the API).</desc>
+ </const>
+ <const name="Enabled" value="1">
+ <desc>
+ Whether hardware virtualization (VT-x/AMD-V) is enabled at all. If
+ such extensions are not available, they will not be used.
+ </desc>
+ </const>
+ <const name="VPID" value="2">
+ <desc>
+ Whether VT-x VPID is enabled. If this extension is not available, it will not be used.
+ </desc>
+ </const>
+ <const name="NestedPaging" value="3">
+ <desc>
+ Whether Nested Paging is enabled. If this extension is not available, it will not be used.
+ </desc>
+ </const>
+ <const name="UnrestrictedExecution" value="4">
+ <desc>
+ Whether VT-x unrestricted execution is enabled. If this feature is not available, it will not be used.
+ </desc>
+ </const>
+ <const name="LargePages" value="5">
+ <desc>
+ Whether large page allocation is enabled; requires nested paging and a 64-bit host.
+ </desc>
+ </const>
+ <const name="Force" value="6">
+ <desc>
+ Whether the VM should fail to start if hardware virtualization (VT-x/AMD-V) cannot be used. If
+ not set, there will be an automatic fallback to software virtualization.
+ </desc>
+ </const>
+ <const name="UseNativeApi" value="7">
+ <desc>
+ Use the native hypervisor API instead of the VirtualBox one (HM) for VT-X/AMD-V. This is
+ ignored if <link to="HWVirtExPropertyType_Enabled"/> isn't set.
+ </desc>
+ </const>
+ <const name="VirtVmsaveVmload" value="8">
+ <desc>
+ Whether AMD-V Virtualized VMSAVE/VMLOAD is enabled. If this feature is not available, it will not
+ be used.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="ParavirtProvider"
+ uuid="696453ec-3742-4a05-bead-658ccbf2c944"
+ >
+ <desc>
+ The paravirtualized guest interface provider. This enumeration represents possible
+ values for the <link to="IMachine::paravirtProvider"/> attribute.
+ </desc>
+ <const name="None" value="0">
+ <desc>No provider is used.</desc>
+ </const>
+ <const name="Default" value="1">
+ <desc>A default provider is automatically chosen according to the guest OS type.</desc>
+ </const>
+ <const name="Legacy" value="2">
+ <desc>Used for VMs which didn't used to have any provider settings. Usually
+ interpreted as @c None for most VMs.</desc>
+ </const>
+ <const name="Minimal" value="3">
+ <desc>A minimal set of features to expose to the paravirtualized guest.</desc>
+ </const>
+ <const name="HyperV" value="4">
+ <desc>Microsoft Hyper-V.</desc>
+ </const>
+ <const name="KVM" value="5">
+ <desc>Linux KVM.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="LockType"
+ uuid="678aaf14-2815-4c3e-b20a-e86ed0216498"
+ >
+ <desc>
+ Used with <link to="IMachine::lockMachine" />.
+ </desc>
+ <const name="Null" value="0">
+ <desc>Placeholder value, do not use when obtaining a lock.</desc>
+ </const>
+ <const name="Shared" value="1">
+ <desc>Request only a shared lock for remote-controlling the machine.
+ Such a lock allows changing certain VM settings which can be safely
+ modified for a running VM.</desc>
+ </const>
+ <const name="Write" value="2">
+ <desc>Lock the machine for writing. This requests an exclusive lock, i.e.
+ there cannot be any other API client holding any type of lock for this
+ VM concurrently. Remember that a VM process counts as an API client
+ which implicitly holds the equivalent of a shared lock during the
+ entire VM runtime.</desc>
+ </const>
+ <const name="VM" value="3">
+ <desc>Lock the machine for writing, and create objects necessary for
+ running a VM in this process.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="SessionType"
+ uuid="A13C02CB-0C2C-421E-8317-AC0E8AAA153A"
+ >
+ <desc>
+ Session type. This enumeration represents possible values of the
+ <link to="ISession::type"/> attribute.
+ </desc>
+
+ <const name="Null" value="0">
+ <desc>Null value (never used by the API).</desc>
+ </const>
+ <const name="WriteLock" value="1">
+ <desc>
+ Session has acquired an exclusive write lock on a machine
+ using <link to="IMachine::lockMachine"/>.
+ </desc>
+ </const>
+ <const name="Remote" value="2">
+ <desc>
+ Session has launched a VM process using
+ <link to="IMachine::launchVMProcess"/>
+ </desc>
+ </const>
+ <const name="Shared" value="3">
+ <desc>
+ Session has obtained a link to another session using
+ <link to="IMachine::lockMachine"/>
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="DeviceType"
+ uuid="cb977be1-d1fb-41f8-ad7e-951736c6cb3e"
+ >
+ <desc>
+ Device type.
+ </desc>
+ <const name="Null" value="0">
+ <desc>
+ Null value, may also mean "no device" (not allowed for
+ <link to="IConsole::getDeviceActivity"/>).
+ </desc>
+ </const>
+ <const name="Floppy" value="1">
+ <desc>Floppy device.</desc>
+ </const>
+ <const name="DVD" value="2">
+ <desc>CD/DVD-ROM device.</desc>
+ </const>
+ <const name="HardDisk" value="3">
+ <desc>Hard disk device.</desc>
+ </const>
+ <const name="Network" value="4">
+ <desc>Network device.</desc>
+ </const>
+ <const name="USB" value="5">
+ <desc>USB device.</desc>
+ </const>
+ <const name="SharedFolder" value="6">
+ <desc>Shared folder device.</desc>
+ </const>
+ <const name="Graphics3D" value="7">
+ <desc>Graphics device 3D activity.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="DeviceActivity"
+ uuid="6FC8AEAA-130A-4eb5-8954-3F921422D707"
+ >
+ <desc>
+ Device activity for <link to="IConsole::getDeviceActivity"/>.
+ </desc>
+
+ <const name="Null" value="0"/>
+ <const name="Idle" value="1"/>
+ <const name="Reading" value="2"/>
+ <const name="Writing" value="3"/>
+ </enum>
+
+ <enum
+ name="ClipboardMode"
+ uuid="33364716-4008-4701-8f14-be0fa3d62950"
+ >
+ <desc>
+ Host-Guest clipboard interchange mode.
+ </desc>
+
+ <const name="Disabled" value="0"/>
+ <const name="HostToGuest" value="1"/>
+ <const name="GuestToHost" value="2"/>
+ <const name="Bidirectional" value="3"/>
+ </enum>
+
+ <enum
+ name="DnDMode"
+ uuid="07af8800-f936-4b33-9172-cd400e83c148"
+ >
+ <desc>
+ Drag and drop interchange mode.
+ </desc>
+
+ <const name="Disabled" value="0"/>
+ <const name="HostToGuest" value="1"/>
+ <const name="GuestToHost" value="2"/>
+ <const name="Bidirectional" value="3"/>
+ </enum>
+
+ <enum
+ name="Scope"
+ uuid="7c91096e-499e-4eca-9f9b-9001438d7855"
+ >
+ <desc>
+ Scope of the operation.
+
+ A generic enumeration used in various methods to define the action or
+ argument scope.
+ </desc>
+
+ <const name="Global" value="0"/>
+ <const name="Machine" value="1"/>
+ <const name="Session" value="2"/>
+ </enum>
+
+ <enum
+ name="BIOSBootMenuMode"
+ uuid="ae4fb9f7-29d2-45b4-b2c7-d579603135d5"
+ >
+ <desc>
+ BIOS boot menu mode.
+ </desc>
+
+ <const name="Disabled" value="0"/>
+ <const name="MenuOnly" value="1"/>
+ <const name="MessageAndMenu" value="2"/>
+ </enum>
+
+ <enum
+ name="APICMode"
+ uuid="c6884ba5-3cc4-4a92-a7f6-4410f9fd894e"
+ >
+ <desc>
+ BIOS APIC initialization mode. If the hardware does not support the
+ mode then the code falls back to a lower mode.
+ </desc>
+
+ <const name="Disabled" value="0"/>
+ <const name="APIC" value="1"/>
+ <const name="X2APIC" value="2"/>
+ </enum>
+
+ <enum
+ name="ProcessorFeature"
+ uuid="0064dece-000e-4963-00f8-eb9b00674c8a"
+ >
+ <desc>
+ CPU features.
+ </desc>
+
+ <const name="HWVirtEx" value="0"/>
+ <const name="PAE" value="1"/>
+ <const name="LongMode" value="2"/>
+ <const name="NestedPaging" value="3"/>
+ <const name="UnrestrictedGuest" value="4"/>
+ <const name="NestedHWVirt" value="5"/>
+ <const name="VirtVmsaveVmload" value="6"/>
+ </enum>
+
+ <enum
+ name="FirmwareType"
+ uuid="b903f264-c230-483e-ac74-2b37ce60d371"
+ >
+ <desc>
+ Firmware type.
+ </desc>
+ <const name="BIOS" value="1">
+ <desc>BIOS Firmware.</desc>
+ </const>
+ <const name="EFI" value="2">
+ <desc>EFI Firmware, bitness detected basing on OS type.</desc>
+ </const>
+ <const name="EFI32" value="3">
+ <desc>EFI firmware, 32-bit.</desc>
+ </const>
+ <const name="EFI64" value="4">
+ <desc>EFI firmware, 64-bit.</desc>
+ </const>
+ <const name="EFIDUAL" value="5">
+ <desc>EFI firmware, combined 32 and 64-bit.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="PointingHIDType"
+ uuid="b3fd8215-6870-4e61-b6d9-2998fa625de0"
+ >
+ <desc>
+ Type of pointing device used in a virtual machine.
+ </desc>
+ <const name="None" value="1">
+ <desc>No mouse.</desc>
+ </const>
+ <const name="PS2Mouse" value="2">
+ <desc>PS/2 auxiliary device, a.k.a. mouse.</desc>
+ </const>
+ <const name="USBMouse" value="3">
+ <desc>USB mouse (relative pointer).</desc>
+ </const>
+ <const name="USBTablet" value="4">
+ <desc>
+ USB tablet (absolute pointer). Also enables a relative USB mouse in
+ addition.
+ </desc>
+ </const>
+ <const name="ComboMouse" value="5">
+ <desc>
+ Combined device, working as PS/2 or USB mouse, depending on guest
+ behavior. Using this device can have negative performance implications.
+ </desc>
+ </const>
+ <const name="USBMultiTouch" value="6">
+ <desc>
+ USB multi-touch device, just touchscreen. It is a specific mode of the
+ USB tablet and also enables the mouse device.
+ </desc>
+ </const>
+ <const name="USBMultiTouchScreenPlusPad" value="7">
+ <desc>
+ USB multi-touch device, touchscreen plus touchpad. It also enables the
+ mouse device.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="KeyboardHIDType"
+ uuid="383e43d7-5c7c-4ec8-9cb8-eda1bccd6699"
+ >
+ <desc>
+ Type of keyboard device used in a virtual machine.
+ </desc>
+ <const name="None" value="1">
+ <desc>No keyboard.</desc>
+ </const>
+ <const name="PS2Keyboard" value="2">
+ <desc>PS/2 keyboard.</desc>
+ </const>
+ <const name="USBKeyboard" value="3">
+ <desc>USB keyboard.</desc>
+ </const>
+ <const name="ComboKeyboard" value="4">
+ <desc>Combined device, working as PS/2 or USB keyboard, depending on guest behavior.
+ Using of such device can have negative performance implications.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="BitmapFormat"
+ uuid="afb2bf39-8b1e-4f9f-8948-d1b887f83eb0"
+ >
+ <desc>
+ Format of a bitmap. Generic values for formats used by
+ the source bitmap, the screen shot or image update APIs.
+ </desc>
+
+ <const name="Opaque" value="0">
+ <desc>
+ Unknown buffer format (the user may not assume any particular format of
+ the buffer).
+ </desc>
+ </const>
+ <const name="BGR" value="0x20524742">
+ <desc>
+ Generic BGR format without alpha channel.
+ Pixel layout depends on the number of bits per pixel:
+ <ul>
+ <li>
+ <b>32</b> - bits 31:24 undefined, bits 23:16 R, bits 15:8 G, bits 7:0 B.
+ </li>
+
+ <li>
+ <b>16</b> - bits 15:11 R, bits 10:5 G, bits 4:0 B.
+ </li>
+ </ul>
+ </desc>
+ </const>
+ <const name="BGR0" value="0x30524742">
+ <desc>
+ 4 bytes per pixel: B, G, R, 0.
+ </desc>
+ </const>
+ <const name="BGRA" value="0x41524742">
+ <desc>
+ 4 bytes per pixel: B, G, R, A.
+ </desc>
+ </const>
+ <const name="RGBA" value="0x41424752">
+ <desc>
+ 4 bytes per pixel: R, G, B, A.
+ </desc>
+ </const>
+ <const name="PNG" value="0x20474E50">
+ <desc>
+ PNG image.
+ </desc>
+ </const>
+ <const name="JPEG" value="0x4745504A">
+ <desc>
+ JPEG image.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="PartitioningType"
+ uuid="64c4c806-8908-4c0b-9a51-2d7a0151321f"
+ >
+ <desc>
+ The type of the disk partition.
+ </desc>
+
+ <const name="MBR" value="0"/>
+ <const name="GPT" value="1"/>
+ </enum>
+
+ <enum
+ name="PartitionType"
+ uuid="84a6629c-8e9c-474c-adbb-21995671597f"
+ >
+ <desc></desc>
+
+ <!-- MBR partition IDs using the same value: -->
+ <const name="Empty" value="0"><desc>Empty partition entry</desc></const>
+ <const name="FAT12" value="1"> <desc>FAT12 if partition size less than 65536 sectors</desc></const>
+ <const name="FAT16" value="4"> <desc>FAT16 if partition size less than 65536 sectors</desc></const>
+ <const name="FAT" value="6"> <desc>FAT12 or FAT16 if partition size greater or equal than 65536 sectors</desc></const>
+ <const name="IFS" value="7"> <desc>NT and OS/2 installable file system, e.g. NTFS, exFAT, HPFS.</desc></const>
+ <const name="FAT32CHS" value="11"> <desc>The FAT32 with CHS addressing.</desc></const>
+ <const name="FAT32LBA" value="12"> <desc>The FAT32 with LBA addressing.</desc></const>
+ <const name="FAT16B" value="14"> <desc>The FAT16 with LBA addressing.</desc></const>
+ <const name="Extended" value="15"> <desc>The extended partition with LBA addressing.</desc></const>
+ <const name="WindowsRE" value="39"> <desc>Windows Recovery Environment (RE) partition (hidden NTFS partition).</desc></const>
+ <const name="LinuxSwapOld" value="66"> <desc>The linux swap partition (old versions).</desc></const>
+ <const name="LinuxOld" value="67"> <desc>The linux native partition (old versions).</desc></const>
+ <const name="DragonFlyBSDSlice" value="108"><desc>The BSD slice.</desc></const>
+ <const name="LinuxSwap" value="130"><desc>The linux swap partition.</desc></const>
+ <const name="Linux" value="131"><desc>The linux native partition.</desc></const>
+ <const name="LinuxExtended" value="133"><desc>The linux extended partition.</desc></const>
+ <const name="LinuxLVM" value="142"><desc>The linux LVM partition.</desc></const>
+ <const name="BSDSlice" value="165"><desc>The BSD slice.</desc></const>
+ <const name="AppleUFS" value="168"><desc>The Apple UFS partition.</desc></const>
+ <const name="AppleHFS" value="175"><desc>The Apple HFS partition.</desc></const>
+ <const name="Solaris" value="191"><desc>The Apple HFS partition.</desc></const>
+ <const name="GPT" value="238"><desc>The GPT protective MBR partition.</desc></const>
+ <const name="EFI" value="239"><desc>The EFI system partition.</desc></const>
+
+ <!-- Dummy value: -->
+ <const name="Unknown" value="256"><desc>Unknown partition type.</desc></const>
+
+ <!-- GPT partition types: -->
+ <const name="MBR" value="257"><desc>MBR partition scheme.</desc></const>
+ <const name="iFFS" value="258"><desc>Intel Fast Flash (iFFS) partition.</desc></const>
+ <const name="SonyBoot" value="259"><desc>Sony boot partition.</desc></const>
+ <const name="LenovoBoot" value="260"><desc>Lenovo boot partition.</desc></const>
+
+ <const name="WindowsMSR" value="270"><desc>Microsoft Reserved Partition (MSR).</desc></const>
+ <const name="WindowsBasicData" value="271"><desc>Windows Basic data partition</desc></const>
+ <const name="WindowsLDMMeta" value="272"><desc>Windows Logical Disk Manager (LDM) metadata partition.</desc></const>
+ <const name="WindowsLDMData" value="273"><desc>Windows Logical Disk Manager data partition.</desc></const>
+ <const name="WindowsRecovery" value="274"><desc>Windows Recovery Environment.</desc></const>
+ <const name="WindowsStorageSpaces" value="276"><desc>Windows Storage Spaces partition.</desc></const>
+ <const name="WindowsStorageReplica" value="277"><desc>Windows Storage Replica partition.</desc></const>
+ <const name="IBMGPFS" value="275"><desc>IBM General Parallel File System (GPFS) partition.</desc></const>
+
+ <const name="LinuxData" value="300"><desc>Linux filesystem data.</desc></const>
+ <const name="LinuxRAID" value="301"><desc>Linux RAID partition.</desc></const>
+ <const name="LinuxRootX86" value="302"><desc>Linux root partition for x86.</desc></const>
+ <const name="LinuxRootAMD64" value="303"><desc>Linux root partition for AMD64.</desc></const>
+ <const name="LinuxRootARM32" value="304"><desc>Linux root partition for ARM32.</desc></const>
+ <const name="LinuxRootARM64" value="305"><desc>Linux root partition for ARM64 / AArch64.</desc></const>
+ <const name="LinuxHome" value="306"><desc>Linux /home partition.</desc></const>
+ <const name="LinuxSrv" value="307"><desc>Linux /srv partition.</desc></const>
+ <const name="LinuxPlainDmCrypt" value="308"><desc>Linux plain dm-crypt partition.</desc></const>
+ <const name="LinuxLUKS" value="309"><desc>Linux unitified key setup (LUKS) partition.</desc></const>
+ <const name="LinuxReserved" value="310"><desc>Linux reserved partition.</desc></const>
+
+ <const name="FreeBSDBoot" value="330"><desc>FreeBSD boot partition.</desc></const>
+ <const name="FreeBSDData" value="331"><desc>FreeBSD data partition.</desc></const>
+ <const name="FreeBSDSwap" value="332"><desc>FreeBSD swap partition.</desc></const>
+ <const name="FreeBSDUFS" value="333"><desc>FreeBSD unix file system (UFS) partition.</desc></const>
+ <const name="FreeBSDVinum" value="334"><desc>FreeBSD Vinum volume manager partition.</desc></const>
+ <const name="FreeBSDZFS" value="335"><desc>FreeBSD ZFS partition.</desc></const>
+ <const name="FreeBSDUnknown" value="359"><desc>Unknown FreeBSD partition.</desc></const>
+
+ <const name="AppleHFSPlus" value="360"><desc>Apple hierarchical file system plus (HFS+) partition.</desc></const>
+ <const name="AppleAPFS" value="361"><desc>Apple APFS/FileFault container partition.</desc></const>
+ <const name="AppleRAID" value="362"><desc>Apple RAID partition.</desc></const>
+ <const name="AppleRAIDOffline" value="363"><desc>Apple RAID partition, offline.</desc></const>
+ <const name="AppleBoot" value="364"><desc>Apple boot partition.</desc></const>
+ <const name="AppleLabel" value="365"><desc>Apple label.</desc></const>
+ <const name="AppleTvRecovery" value="366"><desc>Apple TV recovery partition.</desc></const>
+ <const name="AppleCoreStorage" value="367"><desc>Apple Core Storage Containe.</desc></const>
+ <const name="SoftRAIDStatus" value="370"><desc>SoftRAID status.</desc></const>
+ <const name="SoftRAIDScratch" value="371"><desc>SoftRAID scratch.</desc></const>
+ <const name="SoftRAIDVolume" value="372"><desc>SoftRAID volume.</desc></const>
+ <const name="SoftRAIDCache" value="373"><desc>SoftRAID cache.</desc></const>
+ <const name="AppleUnknown" value="389"><desc>Unknown Apple partition.</desc></const>
+
+ <const name="SolarisBoot" value="390"><desc>Solaris boot partition.</desc></const>
+ <const name="SolarisRoot" value="391"><desc>Solaris root partition.</desc></const>
+ <const name="SolarisSwap" value="392"><desc>Solaris swap partition.</desc></const>
+ <const name="SolarisBackup" value="393"><desc>Solaris backup partition.</desc></const>
+ <const name="SolarisUsr" value="394"><desc>Solaris /usr partition.</desc></const>
+ <const name="SolarisVar" value="395"><desc>Solaris /var partition.</desc></const>
+ <const name="SolarisHome" value="396"><desc>Solaris /home partition.</desc></const>
+ <const name="SolarisAltSector" value="397"><desc>Solaris alternate sector.</desc></const>
+ <const name="SolarisReserved" value="398"><desc>Solaris reserved partition.</desc></const>
+ <const name="SolarisUnknown" value="419"><desc>Unknown Solaris partition.</desc></const>
+
+ <const name="NetBSDSwap" value="420"><desc>NetBSD swap partition.</desc></const>
+ <const name="NetBSDFFS" value="421"><desc>NetBSD fast file system (FFS) partition.</desc></const>
+ <const name="NetBSDLFS" value="422"><desc>NetBSD log structured file system (LFS) partition.</desc></const>
+ <const name="NetBSDRAID" value="423"><desc>NetBSD RAID partition.</desc></const>
+ <const name="NetBSDConcatenated" value="424"><desc>NetBSD concatenated partition.</desc></const>
+ <const name="NetBSDEncrypted" value="425"><desc>NetBSD encrypted partition.</desc></const>
+ <const name="NetBSDUnknown" value="449"><desc>Unknown NetBSD partition.</desc></const>
+
+ <const name="ChromeOSKernel" value="450"><desc>Chrome OS kernel partition.</desc></const>
+ <const name="ChromeOSRootFS" value="451"><desc>Chrome OS root file system partition.</desc></const>
+ <const name="ChromeOSFuture" value="452"><desc>Chrome OS partition reserved for future.</desc></const>
+
+ <const name="ContLnxUsr" value="480"><desc>Container Linux /usr partition.</desc></const>
+ <const name="ContLnxRoot" value="481"><desc>Container Linux resizable root filesystem partition.</desc></const>
+ <const name="ContLnxReserved" value="482"><desc>Container Linux OEM customization partition.</desc></const>
+ <const name="ContLnxRootRAID" value="483"><desc>Container Linux root filesystem on RAID partition.</desc></const>
+
+ <const name="HaikuBFS" value="510"><desc>Haiku BFS</desc></const>
+
+ <const name="MidntBSDBoot" value="540"><desc>MidnightBSD boot partition.</desc></const>
+ <const name="MidntBSDData" value="541"><desc>MidnightBSD data partition.</desc></const>
+ <const name="MidntBSDSwap" value="542"><desc>MidnightBSD swap partition.</desc></const>
+ <const name="MidntBSDUFS" value="543"><desc>MidnightBSD unix file system (UFS) partition.</desc></const>
+ <const name="MidntBSDVium" value="544"><desc>MidnightBSD Vinum volume manager partition.</desc></const>
+ <const name="MidntBSDZFS" value="545"><desc>MidnightBSD ZFS partition.</desc></const>
+ <const name="MidntBSDUnknown" value="569"><desc>Unknown MidnightBSD partition.</desc></const>
+
+ <const name="OpenBSDData" value="570"><desc>OpenBSD data partition.</desc></const>
+
+ <const name="QNXPowerSafeFS" value="600"><desc>QNX power-safe file system partition.</desc></const>
+
+ <const name="Plan9" value="630"><desc>Plan 9 partition.</desc></const>
+
+ <const name="VMWareVMKCore" value="660"><desc>VMWare ESX coredump partition.</desc></const>
+ <const name="VMWareVMFS" value="661"><desc>VMWare ESX virtual machine file system (VMFS) partition.</desc></const>
+ <const name="VMWareReserved" value="662"><desc>VMWare ESX reserved partition.</desc></const>
+ <const name="VMWareUnknown" value="689"><desc>Unknown VMWare partition.</desc></const>
+
+ <const name="AndroidX86Bootloader" value="690"><desc>Android x86 bootloader partition.</desc></const>
+ <const name="AndroidX86Bootloader2" value="691"><desc>Android x86 bootloader2 partition.</desc></const>
+ <const name="AndroidX86Boot" value="692"><desc>Android x86 boot partition.</desc></const>
+ <const name="AndroidX86Recovery" value="693"><desc>Android x86 recovery partition.</desc></const>
+ <const name="AndroidX86Misc" value="694"><desc>Android x86 misc partition.</desc></const>
+ <const name="AndroidX86Metadata" value="695"><desc>Android x86 metadata partition.</desc></const>
+ <const name="AndroidX86System" value="696"><desc>Android x86 system partition.</desc></const>
+ <const name="AndroidX86Cache" value="697"><desc>Android x86 cache partition.</desc></const>
+ <const name="AndroidX86Data" value="698"><desc>Android x86 data partition.</desc></const>
+ <const name="AndroidX86Persistent" value="699"><desc>Android x86 persistent data partition.</desc></const>
+ <const name="AndroidX86Vendor" value="700"><desc>Android x86 vendor partition.</desc></const>
+ <const name="AndroidX86Config" value="701"><desc>Android x86 config partition.</desc></const>
+ <const name="AndroidX86Factory" value="702"><desc>Android x86 factory partition.</desc></const>
+ <const name="AndroidX86FactoryAlt" value="703"><desc>Android x86 alternative factory partition.</desc></const>
+ <const name="AndroidX86Fastboot" value="704"><desc>Android x86 fastboot partition.</desc></const>
+ <const name="AndroidX86OEM" value="705"><desc>Android x86 OEM partition.</desc></const>
+
+ <const name="AndroidARMMeta" value="720"><desc>Android ARM meta partition.</desc></const>
+ <const name="AndroidARMExt" value="721"><desc>Android ARM EXT partition.</desc></const>
+
+ <const name="ONIEBoot" value="750"><desc>Open Network Install Environment (ONIE) boot partition.</desc></const>
+ <const name="ONIEConfig" value="751"><desc>Open Network Install Environment (ONIE) config partition.</desc></const>
+
+ <const name="PowerPCPrep" value="780"><desc>PowerPC PReP boot partition.</desc></const>
+
+ <const name="XDGShrBootConfig" value="810"><desc>freedesktop.org shared boot loader configuration partition.</desc></const>
+
+ <const name="CephBlock" value="830"><desc>Ceph block partition.</desc></const>
+ <const name="CephBlockDB" value="831"><desc>Ceph block DB partition.</desc></const>
+ <const name="CephBlockDBDmc" value="832"><desc>Ceph dm-crypt block DB partition.</desc></const>
+ <const name="CephBlockDBDmcLUKS" value="833"><desc>Ceph dm-crypt Linux unitified key setup (LUKS) block DB partition.</desc></const>
+ <const name="CephBlockDmc" value="834"><desc>Ceph dm-crypt block partition.</desc></const>
+ <const name="CephBlockDmcLUKS" value="835"><desc>Ceph dm-crypt Linux unitified key setup (LUKS) block partition.</desc></const>
+ <const name="CephBlockWALog" value="836"><desc>Ceph block write-ahead log partition.</desc></const>
+ <const name="CephBlockWALogDmc" value="837"><desc>Ceph dm-crypt block write-ahead log partition.</desc></const>
+ <const name="CephBlockWALogDmcLUKS" value="838"><desc>Ceph dm-crypt Linux unitified key setup (LUKS) block write-ahead log partition.</desc></const>
+ <const name="CephDisk" value="839"><desc>Ceph disk in creation partition.</desc></const>
+ <const name="CephDiskDmc" value="840"><desc>Ceph dm-crypt disk in creation partition.</desc></const>
+ <const name="CephJournal" value="841"><desc>Ceph Journal partition.</desc></const>
+ <const name="CephJournalDmc" value="842"><desc>Ceph dm-crypt journal partition.</desc></const>
+ <const name="CephJournalDmcLUKS" value="843"><desc>Ceph dm-crypt Linux unitified key setup (LUKS) journal partition.</desc></const>
+ <const name="CephLockbox" value="844"><desc>Ceph Lockbox for dm-crypt keys partition.</desc></const>
+ <const name="CephMultipathBlock1" value="845"><desc>Ceph multipath block 1 partition.</desc></const>
+ <const name="CephMultipathBlock2" value="846"><desc>Ceph multipath block 2 partition.</desc></const>
+ <const name="CephMultipathBlockDB" value="847"><desc>Ceph multipath block DB partition.</desc></const>
+ <const name="CephMultipathBLockWALog" value="848"><desc>Ceph multipath block write-ahead log partition.</desc></const>
+ <const name="CephMultipathJournal" value="849"><desc>Ceph multipath journal partition.</desc></const>
+ <const name="CephMultipathOSD" value="851"><desc>Ceph multipath object storage deamon (OSD) partition.</desc></const>
+ <const name="CephOSD" value="852"><desc>Ceph object storage deamon (OSD) partition.</desc></const>
+ <const name="CephOSDDmc" value="853"><desc>Ceph dm-crypt object storage deamon (OSD) partition.</desc></const>
+ <const name="CephOSDDmcLUKS" value="854"><desc>Ceph dm-crypt Linux unitified key setup (LUKS) object storage deamon (OSD) partition.</desc></const>
+ </enum>
+
+
+ <!--
+ // IVirtualBoxErrorInfo
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IVirtualBoxErrorInfo" extends="$errorinfo"
+ uuid="c1bcc6d5-7966-481d-ab0b-d0ed73e28135"
+ supportsErrorInfo="no"
+ wsmap="managed"
+ rest="managed"
+ >
+ <desc>
+ The IVirtualBoxErrorInfo interface represents extended error information.
+
+ Extended error information can be set by VirtualBox components after
+ unsuccessful or partially successful method invocation. This information
+ can be retrieved by the calling party as an IVirtualBoxErrorInfo object
+ and then shown to the client in addition to the plain 32-bit result code.
+
+ In MS COM, this interface extends the IErrorInfo interface,
+ in XPCOM, it extends the nsIException interface. In both cases,
+ it provides a set of common attributes to retrieve error
+ information.
+
+ Sometimes invocation of some component's method may involve methods of
+ other components that may also fail (independently of this method's
+ failure), or a series of non-fatal errors may precede a fatal error that
+ causes method failure. In cases like that, it may be desirable to preserve
+ information about all errors happened during method invocation and deliver
+ it to the caller. The <link to="#next"/> attribute is intended
+ specifically for this purpose and allows to represent a chain of errors
+ through a single IVirtualBoxErrorInfo object set after method invocation.
+
+ <note>errors are stored to a chain in the reverse order, i.e. the
+ initial error object you query right after method invocation is the last
+ error set by the callee, the object it points to in the @a next attribute
+ is the previous error and so on, up to the first error (which is the last
+ in the chain).</note>
+ </desc>
+
+ <attribute name="resultCode" type="long" readonly="yes">
+ <desc>
+ Result code of the error.
+ Usually, it will be the same as the result code returned
+ by the method that provided this error information, but not
+ always. For example, on Win32, CoCreateInstance() will most
+ likely return E_NOINTERFACE upon unsuccessful component
+ instantiation attempt, but not the value the component factory
+ returned. Value is typed 'long', not 'result',
+ to make interface usable from scripting languages.
+ <note>
+ In MS COM, there is no equivalent.
+ In XPCOM, it is the same as nsIException::result.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="resultDetail" type="long" readonly="yes">
+ <desc>
+ Optional result data of this error. This will vary depending on the
+ actual error usage. By default this attribute is not being used.
+ </desc>
+ </attribute>
+
+ <attribute name="interfaceID" type="uuid" mod="string" readonly="yes">
+ <desc>
+ UUID of the interface that defined the error.
+ <note>
+ In MS COM, it is the same as IErrorInfo::GetGUID, except for the
+ data type.
+ In XPCOM, there is no equivalent.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="component" type="wstring" readonly="yes">
+ <desc>
+ Name of the component that generated the error.
+ <note>
+ In MS COM, it is the same as IErrorInfo::GetSource.
+ In XPCOM, there is no equivalent.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="text" type="wstring" readonly="yes">
+ <desc>
+ Text description of the error.
+ <note>
+ In MS COM, it is the same as IErrorInfo::GetDescription.
+ In XPCOM, it is the same as nsIException::message.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="next" type="IVirtualBoxErrorInfo" readonly="yes">
+ <desc>
+ Next error object if there is any, or @c null otherwise.
+ <note>
+ In MS COM, there is no equivalent.
+ In XPCOM, it is the same as nsIException::inner.
+ </note>
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <!--
+ // INATNetwork
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface name="INATNetwork" extends="$unknown"
+ uuid="4fdebbf0-be30-49c0-b315-e9749e1bded1"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="2" reservedAttributes="8"
+ >
+
+ <attribute name="networkName" type="wstring">
+ <desc>
+ TBD: the idea, technically we can start any number of the NAT networks,
+ but we should expect that at some point we will get collisions because of
+ port-forwanding rules. so perhaps we should support only single instance of NAT
+ network.
+ </desc>
+ </attribute>
+ <attribute name="enabled" type="boolean"/>
+ <attribute name="network" type="wstring">
+ <desc>
+ This is CIDR IPv4 string. Specifying it user defines IPv4 addresses
+ of gateway (low address + 1) and DHCP server (= low address + 2).
+ Note: If there are defined IPv4 port-forward rules update of network
+ will be ignored (because new assignment could break existing rules).
+ </desc>
+ </attribute>
+ <attribute name="gateway" type="wstring" readonly="yes">
+ <desc>
+ This attribute is read-only. It's recalculated on changing
+ network attribute (low address of network + 1).
+ </desc>
+ </attribute>
+ <attribute name="IPv6Enabled" type="boolean">
+ <desc>
+ This attribute define whether gateway will support IPv6 or not.
+ </desc>
+ </attribute>
+ <attribute name="IPv6Prefix" type="wstring">
+ <desc>
+ This a CIDR IPv6 defining prefix for link-local addresses
+ autoconfiguration within network. Note: ignored if attribute
+ IPv6Enabled is false.
+ </desc>
+ </attribute>
+ <attribute name="advertiseDefaultIPv6RouteEnabled" type="boolean" dtracename="advertiseDefaultIPv6Route"/>
+ <attribute name="needDhcpServer" type="boolean"/>
+ <attribute name="eventSource" type="IEventSource" readonly="yes"/>
+ <attribute name="portForwardRules4" type="wstring" readonly="yes" safearray="yes">
+ <desc>Array of NAT port-forwarding rules in string representation,
+ in the following format:
+ "name:protocolid:[host ip]:host port:[guest ip]:guest port".
+ </desc>
+ </attribute>
+ <attribute name="localMappings" type="wstring" readonly="yes" safearray="yes">
+ <desc>Array of mappings (address,offset),e.g. ("127.0.1.1=4") maps 127.0.1.1 to networkid + 4.
+ </desc>
+ </attribute>
+
+ <method name="addLocalMapping">
+ <rest request="post" path="/natnetworks/{networkid}/configuration/"/>
+ <desc>
+ </desc>
+ <param name="hostid" type="wstring" dir="in"/>
+ <param name="offset" type="long" dir="in"/>
+ </method>
+
+ <attribute name="loopbackIp6" type="long">
+ <desc>Offset in ipv6 network from network id for address mapped into loopback6 interface of the host.
+ </desc>
+ </attribute>
+
+ <attribute name="portForwardRules6" type="wstring" readonly="yes" safearray="yes">
+ <desc>Array of NAT port-forwarding rules in string representation, in the
+ following format: "name:protocolid:[host ip]:host port:[guest ip]:guest port".
+ </desc>
+ </attribute>
+ <method name="addPortForwardRule">
+ <rest request="post" path="/natnetworks/{networkid}/configuration/"/>
+ <param name="isIpv6" type="boolean" dir="in"/>
+ <param name="ruleName" type="wstring" dir="in"/>
+ <param name="proto" type="NATProtocol" dir="in">
+ <desc>Protocol handled with the rule.</desc>
+ </param>
+ <param name="hostIP" type="wstring" dir="in">
+ <desc>IP of the host interface to which the rule should apply.
+ An empty ip address is acceptable, in which case the NAT engine
+ binds the handling socket to any interface.
+ </desc>
+ </param>
+ <param name="hostPort" type="unsigned short" dir="in">
+ <desc>The port number to listen on.</desc>
+ </param>
+ <param name="guestIP" type="wstring" dir="in">
+ <desc>The IP address of the guest which the NAT engine will forward
+ matching packets to. An empty IP address is not acceptable.</desc>
+ </param>
+ <param name="guestPort" type="unsigned short" dir="in">
+ <desc>The port number to forward.</desc>
+ </param>
+ </method>
+ <method name="removePortForwardRule">
+ <rest request="post" path="/natnetworks/{networkid}/configuration/"/>
+ <param name="isIpv6" type="boolean" dir="in"/>
+ <param name="ruleName" type="wstring" dir="in"/>
+ </method>
+ <method name="start">
+ <rest request="post" path="/natnetworks/{networkid}/actions/"/>
+ </method>
+ <method name="stop">
+ <rest request="post" path="/natnetworks/{networkid}/actions/"/>
+ </method>
+ </interface>
+
+
+ <!--
+ // ICloudNetwork
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface name="ICloudNetwork" extends="$unknown"
+ uuid="d8e3496e-735f-4fde-8a54-427d49409b5f"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="12"
+ >
+
+ <attribute name="networkName" type="wstring">
+ <desc>
+ TBD: User-friendly, descriptive name of cloud subnet. For example, domain
+ names of subnet and vcn, separated by dot.
+ </desc>
+ </attribute>
+ <attribute name="enabled" type="boolean"/>
+ <attribute name="provider" type="wstring">
+ <desc>
+ Cloud provider short name.
+ </desc>
+ </attribute>
+ <attribute name="profile" type="wstring">
+ <desc>
+ Cloud profile name.
+ </desc>
+ </attribute>
+ <attribute name="networkId" type="wstring">
+ <desc>
+ Cloud network id.
+ </desc>
+ </attribute>
+ </interface>
+
+ <!--
+ // IHostOnlyNetwork
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface name="IHostOnlyNetwork" extends="$unknown"
+ uuid="6eb5de7d-9a67-4fde-83be-f768084d03b5"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+
+ <attribute name="networkName" type="wstring">
+ <desc>
+ TBD: User-friendly, descriptive name of host-only network. For example,
+ "Host-only network 192.168.56.0".
+ </desc>
+ </attribute>
+ <attribute name="enabled" type="boolean"/>
+ <attribute name="networkMask" type="wstring">
+ <desc>
+ specifies network mask
+ </desc>
+ </attribute>
+
+ <attribute name="hostIP" type="wstring" readonly="yes">
+ <desc>
+ host IP address, which usually is the lower IP address of DHCP range.
+ </desc>
+ </attribute>
+
+ <attribute name="lowerIP" type="wstring">
+ <desc>
+ specifies from IP address in DHCP address range
+ </desc>
+ </attribute>
+
+ <attribute name="upperIP" type="wstring">
+ <desc>
+ specifies to IP address in DHCP address range
+ </desc>
+ </attribute>
+
+ <attribute name="id" type="uuid" mod="string">
+ <desc>
+ Host-only network ID.
+ </desc>
+ </attribute>
+ </interface>
+
+ <!--
+ // IDHCPServer and associates
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="DHCPOption"
+ uuid="00f5b10f-0021-4513-00f7-5bf4000982bf">
+ <!-- Names should exactly match those given in DhcpOptions.h.
+ Everything must be documented properly.
+
+ Note! We use these descriptions in the VBoxManage man page / manual.
+ When making changes remember to regenerate the man page bits:
+ kmk -C doc/manual dhcpoptions
+ -->
+ <const name="SubnetMask" value="1"><desc>IPv4 netmask. Set to <link to="IDHCPServer::networkMask"/> by default.</desc></const>
+ <const name="TimeOffset" value="2"><desc>UTC offset in seconds (32-bit decimal value).</desc></const>
+ <const name="Routers" value="3"><desc>Space separated list of IPv4 router addresses.</desc></const>
+ <const name="TimeServers" value="4"><desc>Space separated list of IPv4 time server (RFC 868) addresses.</desc></const>
+ <const name="NameServers" value="5"><desc>Space separated list of IPv4 name server (IEN 116) addresses.</desc></const>
+ <const name="DomainNameServers" value="6"><desc>Space separated list of IPv4 DNS addresses.</desc></const>
+ <const name="LogServers" value="7"><desc>Space separated list of IPv4 log server addresses.</desc></const>
+ <const name="CookieServers" value="8"><desc>Space separated list of IPv4 cookie server (RFC 865) addresses.</desc></const>
+ <const name="LPRServers" value="9"><desc>Space separated list of IPv4 line printer server (RFC 1179) addresses.</desc></const>
+ <const name="ImpressServers" value="10"><desc>Space separated list of IPv4 imagen impress server addresses.</desc></const>
+ <const name="ResourseLocationServers" value="11"><desc>Space separated list of IPv4 resource location (RFC 887) addresses.</desc></const>
+ <const name="HostName" value="12"><desc>The client name. See RFC 1035 for character limits. </desc></const>
+ <const name="BootFileSize" value="13"><desc>Number of 512 byte blocks making up the boot file (16-bit decimal value).</desc></const>
+ <const name="MeritDumpFile" value="14"><desc>Client core file.</desc></const>
+ <const name="DomainName" value="15"><desc>Domain name for the client.</desc></const>
+ <const name="SwapServer" value="16"><desc>IPv4 address of the swap server that the client should use.</desc></const>
+ <const name="RootPath" value="17"><desc>The path to the root disk the client should use.</desc></const>
+ <const name="ExtensionPath" value="18"><desc>Path to a file containing additional DHCP options (RFC2123).</desc></const>
+ <const name="IPForwarding" value="19"><desc>Whether IP forwarding should be enabled by the client (boolean).</desc></const>
+ <const name="OptNonLocalSourceRouting" value="20"><desc>Whether non-local datagrams should be forwarded by the client (boolean)</desc></const>
+ <const name="PolicyFilter" value="21"><desc>List of IPv4 addresses and masks paris controlling non-local source routing.</desc></const>
+ <const name="MaxDgramReassemblySize" value="22"><desc>The maximum datagram size the client should reassemble (16-bit decimal value).</desc></const>
+ <const name="DefaultIPTTL" value="23"><desc>The default time-to-leave on outgoing (IP) datagrams (8-bit decimal value).</desc></const>
+ <const name="PathMTUAgingTimeout" value="24"><desc>RFC1191 path MTU discovery timeout value in seconds (32-bit decimal value).</desc></const>
+ <const name="PathMTUPlateauTable" value="25"><desc>RFC1191 path MTU discovery size table, sorted in ascending order (list of 16-bit decimal values).</desc></const>
+ <const name="InterfaceMTU" value="26"><desc>The MTU size for the interface (16-bit decimal value).</desc></const>
+ <const name="AllSubnetsAreLocal" value="27"><desc>Indicates whether the MTU size is the same for all subnets (boolean).</desc></const>
+ <const name="BroadcastAddress" value="28"><desc>Broadcast address (RFC1122) for the client to use (IPv4 address).</desc></const>
+ <const name="PerformMaskDiscovery" value="29"><desc>Whether to perform subnet mask discovery via ICMP (boolean).</desc></const>
+ <const name="MaskSupplier" value="30"><desc>Whether to respond to subnet mask requests via ICMP (boolean).</desc></const>
+ <const name="PerformRouterDiscovery" value="31"><desc>Whether to perform router discovery (RFC1256) (boolean).</desc></const>
+ <const name="RouterSolicitationAddress" value="32"><desc>Where to send router solicitation requests (RFC1256) (IPv4 address).</desc></const>
+ <const name="StaticRoute" value="33"><desc>List of network and router address pairs addresses.</desc></const>
+ <const name="TrailerEncapsulation" value="34"><desc>Whether to negotiate the use of trailers for ARP (RTF893) (boolean).</desc></const>
+ <const name="ARPCacheTimeout" value="35"><desc>The timeout in seconds for ARP cache entries (32-bit decimal value).</desc></const>
+ <const name="EthernetEncapsulation" value="36"><desc>Whether to use IEEE 802.3 (RTF1042) rather than of v2 (RFC894) ethernet encapsulation (boolean).</desc></const>
+ <const name="TCPDefaultTTL" value="37"><desc>Default time-to-live for TCP sends (non-zero 8-bit decimal value).</desc></const>
+ <const name="TCPKeepaliveInterval" value="38"><desc>The interface in seconds between TCP keepalive messages (32-bit decimal value).</desc></const>
+ <const name="TCPKeepaliveGarbage" value="39"><desc>Whether to include a byte of garbage in TCP keepalive messages for backward compatibility (boolean).</desc></const>
+ <const name="NISDomain" value="40"><desc>The NIS (Sun Network Information Services) domain name (string).</desc></const>
+ <const name="NISServers" value="41"><desc>Space separated list of IPv4 NIS server addresses.</desc></const>
+ <const name="NTPServers" value="42"><desc>Space separated list of IPv4 NTP (RFC1035) server addresses.</desc></const>
+ <const name="VendorSpecificInfo" value="43"><desc>Vendor specific information. Only accessible using <link to="DHCPOptionEncoding_Hex"/>.</desc></const>
+ <const name="NetBIOSNameServers" value="44"><desc>Space separated list of IPv4 NetBIOS name server (NBNS) addresses (RFC1001,RFC1002).</desc></const>
+ <const name="NetBIOSDatagramServers" value="45"><desc>Space separated list of IPv4 NetBIOS datagram distribution server (NBDD) addresses (RFC1001,RFC1002).</desc></const>
+ <const name="NetBIOSNodeType" value="46"><desc>NetBIOS node type (RFC1001,RFC1002): 1=B-node, 2=P-node, 4=M-node, and 8=H-node (8-bit decimal value).</desc></const>
+ <const name="NetBIOSScope" value="47"><desc>NetBIOS scope (RFC1001,RFC1002). Only accessible using <link to="DHCPOptionEncoding_Hex"/>.</desc></const>
+ <const name="XWindowsFontServers" value="48"><desc>Space separated list of IPv4 X windows font server addresses.</desc></const>
+ <const name="XWindowsDisplayManager" value="49"><desc>Space separated list of IPv4 X windows display manager addresses.</desc></const>
+ <!-- Options 50 thru 59 are considered DHCP internal and currently not settable. -->
+ <const name="NetWareIPDomainName" value="62"><desc>Netware IP domain name (RFC2242) (string).</desc></const>
+ <const name="NetWareIPInformation" value="63"><desc>Netware IP information (RFC2242). Only accessible using <link to="DHCPOptionEncoding_Hex"/>.</desc></const>
+ <const name="NISPlusDomain" value="64"><desc>The NIS+ domain name (string).</desc></const>
+ <const name="NISPlusServers" value="65"><desc>Space separated list of IPv4 NIS+ server addresses.</desc></const>
+ <const name="TFTPServerName" value="66"><desc>TFTP server name (string).</desc></const>
+ <const name="BootfileName" value="67"><desc>Bootfile name (string).</desc></const>
+ <const name="MobileIPHomeAgents" value="68"><desc>Space separated list of IPv4 mobile IP agent addresses.</desc></const>
+ <const name="SMTPServers" value="69"><desc>Space separated list of IPv4 simple mail transport protocol (SMPT) server addresses.</desc></const>
+ <const name="POP3Servers" value="70"><desc>Space separated list of IPv4 post office protocol 3 (POP3) server addresses.</desc></const>
+ <const name="NNTPServers" value="71"><desc>Space separated list of IPv4 network news transport protocol (NTTP) server addresses.</desc></const>
+ <const name="WWWServers" value="72"><desc>Space separated list of default IPv4 world wide web (WWW) server addresses.</desc></const>
+ <const name="FingerServers" value="73"><desc>Space separated list of default IPv4 finger server addresses.</desc></const>
+ <const name="IRCServers" value="74"><desc>Space separated list of default IPv4 internet relay chat (IRC) server addresses.</desc></const>
+ <const name="StreetTalkServers" value="75"><desc>Space separated list of IPv4 StreetTalk server addresses.</desc></const>
+ <const name="STDAServers" value="76"><desc>Space separated list of IPv4 StreetTalk directory assistance (STDA) server addresses.</desc></const>
+ <!-- 77 is client only -->
+ <const name="SLPDirectoryAgent" value="78"><desc>Addresses of one or more service location protocol (SLP) directory agent, and an indicator of whether their use is mandatory. Only accessible using <link to="DHCPOptionEncoding_Hex"/>.</desc></const>
+ <const name="SLPServiceScope" value="79"><desc>List of service scopes for the service location protocol (SLP) and whether using the list is mandator. Only accessible using <link to="DHCPOptionEncoding_Hex"/>.</desc></const>
+ <!-- 80 is client only -->
+ <const name="DomainSearch" value="119"><desc>Domain search list, see RFC3397 and section 4.1.4 in RFC1035 for encoding. Only accessible using <link to="DHCPOptionEncoding_Hex"/>.</desc></const>
+ </enum>
+
+ <enum
+ name="DHCPOptionEncoding"
+ uuid="84b6d460-2838-4682-c0d6-ef5b573ef28a">
+ <const name="Normal" value="0">
+ <desc>Value format is specific to the option and generally user friendly.</desc>
+ </const>
+ <const name="Hex" value="1">
+ <desc>Value format is a series of hex bytes (<tt>09314f3200fe</tt>),
+ optionally colons as byte separators (<tt>9:31:4f:32::fe</tt>).</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="DHCPConfigScope"
+ uuid="469c42e4-b9ec-43f2-bdcb-9e9d1eb434ae">
+ <const name="Global" value="0"><desc><link to="IDHCPServer::globalConfig"/></desc></const>
+ <const name="Group" value="1"><desc><link to="IDHCPServer::groupConfigs"/></desc></const>
+ <const name="MachineNIC" value="2"><desc><link to="IDHCPServer::individualConfigs"/></desc></const>
+ <const name="MAC" value="3"><desc><link to="IDHCPServer::individualConfigs"/></desc></const>
+ </enum>
+
+ <enum
+ name="DHCPGroupConditionType"
+ uuid="2cb9280f-ada2-4194-dee8-bfb8ad77119d"
+ >
+ <const name="MAC" value="0"><desc>MAC address</desc></const>
+ <const name="MACWildcard" value="1"><desc>MAC address wildcard pattern.</desc></const>
+ <const name="vendorClassID" value="2"><desc>Vendor class ID</desc></const>
+ <const name="vendorClassIDWildcard" value="3"><desc>Vendor class ID wildcard pattern.</desc></const>
+ <const name="userClassID" value="4"><desc>User class ID</desc></const>
+ <const name="userClassIDWildcard" value="5"><desc>User class ID wildcard pattern.</desc></const>
+ </enum>
+
+ <interface
+ name="IDHCPServer" extends="$unknown"
+ uuid="cadef0a2-a1a9-4ac2-8e80-c049af69dac8"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="0" reservedAttributes="3"
+ >
+ <desc>
+ The IDHCPServer interface represents the VirtualBox DHCP server configuration.
+
+ To enumerate all the DHCP servers on the host, use the
+ <link to="IVirtualBox::DHCPServers"/> attribute.
+ </desc>
+ <!-- We want to keep our "real" servers updated about configuration changes -->
+ <attribute name="eventSource" type="IEventSource" readonly="yes"/>
+ <attribute name="enabled" type="boolean">
+ <desc>
+ specifies if the DHCP server is enabled
+ </desc>
+ </attribute>
+
+ <attribute name="IPAddress" type="wstring" readonly="yes">
+ <desc>
+ specifies server IP
+ </desc>
+ </attribute>
+
+ <attribute name="networkMask" type="wstring" readonly="yes">
+ <desc>
+ specifies server network mask
+ </desc>
+ </attribute>
+
+ <attribute name="networkName" type="wstring" readonly="yes">
+ <desc>
+ specifies internal network name the server is used for
+ </desc>
+ </attribute>
+
+ <attribute name="lowerIP" type="wstring" readonly="yes">
+ <desc>
+ specifies from IP address in server address range
+ </desc>
+ </attribute>
+
+ <attribute name="upperIP" type="wstring" readonly="yes">
+ <desc>
+ specifies to IP address in server address range
+ </desc>
+ </attribute>
+
+ <attribute name="globalConfig" type="IDHCPGlobalConfig" readonly="yes">
+ <desc>Global configuration that applies to all clients.</desc>
+ </attribute>
+
+ <attribute name="groupConfigs" type="IDHCPGroupConfig" safearray="yes" readonly="yes">
+ <desc>Configuration groups that applies to selected clients, selection is flexible.</desc>
+ </attribute>
+
+ <attribute name="individualConfigs" type="IDHCPIndividualConfig" safearray="yes" readonly="yes">
+ <desc>Individual NIC configurations either by MAC address or VM + NIC number.</desc>
+ </attribute>
+
+ <!-- VM-slot settings
+ It's corresponds to dhcpd.conf entries, like:
+
+ host ncd1 { hardware ethernet 0:c0:c3:11:90:23; }
+
+ - in Main we can match (vm name, slot) to ethernet address
+ that why it's easy to configure dhcp server in such way,
+ then ask user to to enter valid name,
+
+ - mac address might change because of adapter type (e1k <-> pcnet) or because
+ of import/export
+
+ - it's easy to provide vm-name.rom in boot filename VM with network boot.
+
+ XXX: do we need classic getOptionValueByMAC?
+
+ XML: O-o-oh, this settings are per VM, so perhaps they should be stored in
+ VM configuration files. And we haven't got the attachment type for
+ NATNetwork.
+ -->
+ <method name="setConfiguration">
+ <rest request="post" path="/dhcpservers/{serverid}/configuration/"/>
+ <desc>
+ configures the server
+ <result name="E_INVALIDARG">
+ invalid configuration supplied
+ </result>
+ </desc>
+ <param name="IPAddress" type="wstring" dir="in">
+ <desc>
+ server IP address
+ </desc>
+ </param>
+ <param name="networkMask" type="wstring" dir="in">
+ <desc>
+ server network mask
+ </desc>
+ </param>
+ <param name="FromIPAddress" type="wstring" dir="in">
+ <desc>
+ server From IP address for address range
+ </desc>
+ </param>
+ <param name="ToIPAddress" type="wstring" dir="in">
+ <desc>
+ server To IP address for address range
+ </desc>
+ </param>
+ </method>
+
+ <method name="start">
+ <rest request="post" path="/dhcpservers/{serverid}/actions/"/>
+ <desc>
+ Starts DHCP server process.
+ <result name="E_FAIL">
+ Failed to start the process.
+ </result>
+ </desc>
+ <param name="trunkName" type="wstring" dir="in">
+ <desc>
+ Name of internal network trunk.
+ </desc>
+ </param>
+ <param name="trunkType" type="wstring" dir="in">
+ <desc>
+ Type of internal network trunk.
+ </desc>
+ </param>
+ </method>
+
+ <method name="stop">
+ <rest request="post" path="/dhcpservers/{serverid}/actions/"/>
+ <desc>
+ Stops DHCP server process.
+ <result name="E_FAIL">
+ Failed to stop the process.
+ </result>
+ </desc>
+ </method>
+
+ <method name="restart">
+ <rest request="post" path="/dhcpservers/{serverid}/actions/"/>
+ <desc>
+ Restart running DHCP server process.
+ <result name="E_FAIL">
+ Failed to restart the process.
+ </result>
+ </desc>
+ </method>
+
+ <method name="findLeaseByMAC">
+ <desc>
+ Queries the persistent lease database by MAC address.
+
+ This is handy if the host wants to connect to a server running inside
+ a VM on a host only network.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">If MAC address not in the database.</result>
+ <result name="VBOX_E_FILE_ERROR">If not able to read the lease database file.</result>
+ </desc>
+ <param name="mac" type="wstring" dir="in">
+ <desc>The MAC address to look up.</desc>
+ </param>
+ <param name="type" type="long" dir="in">
+ <desc>Reserved, MBZ.</desc>
+ </param>
+ <param name="address" type="wstring" dir="out">
+ <desc>The assigned address.</desc>
+ </param>
+ <param name="state" type="wstring" dir="out">
+ <desc>The lease state.</desc>
+ </param>
+ <param name="issued" type="long long" dir="out">
+ <desc>Timestamp of when the lease was issued, in seconds since 1970-01-01 UTC.</desc>
+ </param>
+ <param name="expire" type="long long" dir="out">
+ <desc>Timestamp of when the lease expires/expired, in seconds since 1970-01-01 UTC.</desc>
+ </param>
+ </method>
+
+ <method name="getConfig">
+ <rest request="get" path="/dhcpservers/{serverid}/configuration/"/>
+ <desc>
+ Gets or adds a configuration.
+ </desc>
+ <param name="scope" dir="in" type="DHCPConfigScope">
+ <desc>The kind of configuration being sought or added.</desc>
+ </param>
+ <param name="name" dir="in" type="wstring">
+ <desc>
+ Meaning depends on the @a scope:
+ - Ignored when the @a scope is <link to="DHCPConfigScope_Global"/>.
+ - A VM name or UUID for <link to="DHCPConfigScope_MachineNIC"/>.
+ - A MAC address for <link to="DHCPConfigScope_MAC"/>.
+ - A group name for <link to="DHCPConfigScope_Group"/>.
+ </desc>
+ </param>
+ <param name="slot" dir="in" type="unsigned long">
+ <desc>The NIC slot when @a scope is set to <link to="DHCPConfigScope_MachineNIC"/>,
+ must be zero for all other scope values.
+ </desc>
+ </param>
+ <param name="mayAdd" dir="in" type="boolean">
+ <desc>
+ Set to @c TRUE if the configuration should be added if not found.
+ If set to @c FALSE the method will fail with VBOX_E_OBJECT_NOT_FOUND.
+ </desc>
+ </param>
+ <param name="config" dir="return" type="IDHCPConfig">
+ <desc>The requested configuration.</desc>
+ </param>
+ </method>
+
+ <!-- @todo Add variant of queryLeaseByMac that goes by VM name and optionally slot. -->
+ <!-- @todo Add lease database attribute (readonly) -->
+
+ </interface>
+
+ <interface
+ name="IDHCPConfig" extends="$unknown"
+ uuid="00f4a8dc-0002-4b81-0077-1dcb004571ba"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="8" reservedAttributes="16"
+ >
+ <desc>
+ The DHCP server has several configuration levels: global, group and
+ individual MAC. This interface implements the settings common to
+ each level.
+ </desc>
+
+ <attribute name="scope" type="DHCPConfigScope" readonly="yes">
+ <desc>
+ Indicates the kind of config this is (mostly for IDHCPIndividualConfig).
+ </desc>
+ </attribute>
+
+ <attribute name="minLeaseTime" type="unsigned long">
+ <desc>The minimum lease time in seconds, ignored if zero.</desc>
+ </attribute>
+ <attribute name="defaultLeaseTime" type="unsigned long">
+ <desc>The default lease time in seconds, ignored if zero.</desc>
+ </attribute>
+ <attribute name="maxLeaseTime" type="unsigned long">
+ <desc>The maximum lease time in seconds, ignored if zero.</desc>
+ </attribute>
+
+ <attribute name="forcedOptions" type="DHCPOption" safearray="yes">
+ <desc>List of DHCP options which should be forced upon the clients in this
+ config scope when they are available, whether the clients asks for them
+ or not.</desc>
+ </attribute>
+
+ <attribute name="suppressedOptions" type="DHCPOption" safearray="yes">
+ <desc>List of DHCP options which should not be sent to the clients in
+ this config scope. This is intended for cases where one client or a
+ group of clients shouldn't see one or more (typically global) options.</desc>
+ </attribute>
+
+ <method name="setOption">
+ <desc>
+ Sets a DHCP option.
+ </desc>
+ <param name="option" dir="in" type="DHCPOption">
+ <desc>The DHCP option.</desc>
+ </param>
+ <param name="encoding" dir="in" type="DHCPOptionEncoding">
+ <desc>The value encoding.</desc>
+ </param>
+ <param name="value" dir="in" type="wstring">
+ <desc>The DHCP option value. The exact format depends on the DHCP
+ @a option value and @a encoding, see see <link to="DHCPOption"/>
+ for the <link to="DHCPOptionEncoding_Normal"/> format.
+ </desc>
+ </param>
+ </method>
+
+ <method name="removeOption">
+ <desc>Removes the given DHCP option.</desc>
+ <param name="option" dir="in" type="DHCPOption"/>
+ </method>
+
+ <method name="removeAllOptions">
+ <desc>
+ Removes all the options.
+
+ One exception here is the DhcpOpt_SubnetMask option in the global scope
+ that is linked to the <link to="IDHCPServer::networkMask"/> attribute
+ and therefore cannot be removed.
+ </desc>
+ </method>
+
+ <method name="getOption">
+ <desc>Gets the value of a single DHCP option.</desc>
+ <param name="option" dir="in" type="DHCPOption">
+ <desc>The DHCP option being sought.</desc>
+ </param>
+ <param name="encoding" dir="out" type="DHCPOptionEncoding">
+ <desc>The value encoding.</desc>
+ </param>
+ <param name="value" dir="return" type="wstring">
+ <desc>The value of the requested DHCP option. The exact format depends on
+ the DHCP @a option value and the @a encoding, see <link to="DHCPOption"/>
+ for the <link to="DHCPOptionEncoding_Normal"/> format.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getAllOptions">
+ <desc>Gets all DHCP options and their values</desc>
+ <param name="options" dir="out" type="DHCPOption" safearray="yes">
+ <desc>Array containing the DHCP option numbers.</desc>
+ </param>
+ <param name="encodings" dir="out" type="DHCPOptionEncoding" safearray="yes">
+ <desc>Array of value encodings that runs parallel to @a options.</desc>
+ </param>
+ <param name="values" dir="return" type="wstring" safearray="yes">
+ <desc>Array of values that runs parallel to @a options and @a encodings. The
+ format depends on both of those.
+ </desc>
+ </param>
+ </method>
+
+ <method name="remove">
+ <desc>
+ Remove this group or individual configuration.
+ Will of course not work on global configurations.
+ </desc>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IDHCPGlobalConfig" extends="IDHCPConfig"
+ uuid="46735de7-f4c4-4020-a185-0d2881bcfa8b"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="4"
+ >
+ <desc>
+ The global DHCP server configuration, see <link to="IDHCPServer::globalConfig"/>.
+ </desc>
+
+ </interface>
+
+ <interface
+ name="IDHCPGroupCondition" extends="$unknown"
+ uuid="5ca9e537-5a1d-43f1-6f27-6a0db298a9a8"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="3" reservedAttributes="3"
+ >
+ <attribute name="inclusive" type="boolean">
+ <desc>Whether this is an inclusive or exclusive group membership condition</desc>
+ </attribute>
+ <attribute name="type" type="DHCPGroupConditionType">
+ <desc>Defines how the <link to="IDHCPGroupCondition::value"/> is interpreted.</desc>
+ </attribute>
+ <attribute name="value" type="wstring">
+ <desc>The condition value.</desc>
+ </attribute>
+ <method name="remove">
+ <desc>Remove this condition from the group.</desc>
+ </method>
+ </interface>
+
+ <interface
+ name="IDHCPGroupConfig" extends="IDHCPConfig"
+ uuid="537707f7-ebf9-4d5c-7aea-877bfc4256ba"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="8" reservedAttributes="8"
+ >
+ <desc>
+ A configuration that applies to a group of NICs.
+ </desc>
+
+ <attribute name="name" type="wstring">
+ <desc>The group name.</desc>
+ </attribute>
+
+ <attribute name="conditions" type="IDHCPGroupCondition" safearray="yes" readonly="yes">
+ <desc>
+ Group membership conditions.
+
+ Add new conditions by calling <link to="IDHCPGroupConfig::addCondition"/>
+ and use <link to="IDHCPGroupCondition::remove"/> to remove.
+ </desc>
+ </attribute>
+
+ <method name="addCondition">
+ <desc>Adds a new condition.</desc>
+ <param name="inclusive" dir="in" type="boolean"/>
+ <param name="type" dir="in" type="DHCPGroupConditionType"/>
+ <param name="value" dir="in" type="wstring"/>
+ <param name="condition" dir="return" type="IDHCPGroupCondition"/>
+ </method>
+
+ <method name="removeAllConditions">
+ <desc>Removes all conditions.</desc>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IDHCPIndividualConfig" extends="IDHCPConfig"
+ uuid="c40c2b86-73a5-46cc-8227-93fe57d006a6"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="8" reservedAttributes="8"
+ >
+ <desc>
+ Configuration for a single NIC, either given directly by MAC address or by
+ VM + adaptor slot number.
+ </desc>
+
+ <attribute name="MACAddress" type="wstring" readonly="yes">
+ <desc>
+ The MAC address. If a <link to="DHCPConfigScope_MachineNIC"/> config, this
+ will be queried via the VM ID.
+ </desc>
+ </attribute>
+
+ <attribute name="machineId" type="uuid" mod="string" readonly="yes">
+ <desc>
+ The virtual machine ID if a <link to="DHCPConfigScope_MachineNIC"/> config,
+ null UUID for <link to="DHCPConfigScope_MAC"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="slot" type="unsigned long" readonly="yes">
+ <desc>The NIC slot number of the VM if a <link to="DHCPConfigScope_MachineNIC"/> config.</desc>
+ </attribute>
+
+ <attribute name="fixedAddress" type="wstring">
+ <desc>Fixed IPv4 address assignment, dynamic if empty.</desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IVirtualBox" extends="$unknown"
+ uuid="7682d5eb-f00e-44f1-8ca2-99d08b1cd607"
+ wsmap="managed"
+ rest="managed"
+ wrap-gen-hook="yes"
+ reservedMethods="8" reservedAttributes="12"
+ >
+ <desc>
+ The IVirtualBox interface represents the main interface exposed by the
+ product that provides virtual machine management.
+
+ An instance of IVirtualBox is required for the product to do anything
+ useful. Even though the interface does not expose this, internally,
+ IVirtualBox is implemented as a singleton and actually lives in the
+ process of the VirtualBox server (VBoxSVC.exe). This makes sure that
+ IVirtualBox can track the state of all virtual machines on a particular
+ host, regardless of which frontend started them.
+
+ To enumerate all the virtual machines on the host, use the
+ <link to="IVirtualBox::machines"/> attribute.
+
+ Error information handling is a bit special with IVirtualBox: creating
+ an instance will always succeed. The return of the actual error
+ code/information is postponed to any attribute or method call. The
+ reason for this is that COM likes to mutilate the error code and lose
+ the detailed error information returned by instance creation.
+ </desc>
+
+ <attribute name="version" type="wstring" readonly="yes">
+ <desc>
+ A string representing the version number of the product. The
+ format is 3 integer numbers divided by dots (e.g. 1.0.1). The
+ last number represents the build number and will frequently change.
+
+ This may be followed by a _ALPHA[0-9]*, _BETA[0-9]* or _RC[0-9]* tag
+ in prerelease builds. Non-Oracle builds may (/shall) also have a
+ publisher tag, at the end. The publisher tag starts with an underscore
+ just like the prerelease build type tag.
+ </desc>
+ </attribute>
+
+ <attribute name="versionNormalized" type="wstring" readonly="yes">
+ <desc>
+ A string representing the version number of the product,
+ without the publisher information (but still with other tags).
+ See <link to="#version" />.
+ </desc>
+ </attribute>
+
+ <attribute name="revision" type="unsigned long" readonly="yes">
+ <desc>
+ The internal build revision number of the product.
+ </desc>
+ </attribute>
+
+ <attribute name="packageType" type="wstring" readonly="yes">
+ <desc>
+ A string representing the package type of this product. The
+ format is OS_ARCH_DIST where OS is either WINDOWS, LINUX,
+ SOLARIS, DARWIN. ARCH is either 32BITS or 64BITS. DIST
+ is either GENERIC, UBUNTU_606, UBUNTU_710, or something like
+ this.
+ </desc>
+ </attribute>
+
+ <attribute name="APIVersion" type="wstring" readonly="yes">
+ <desc>
+ A string representing the VirtualBox API version number. The format is
+ 2 integer numbers divided by an underscore (e.g. 1_0). After the
+ first public release of packages with a particular API version the
+ API will not be changed in an incompatible way. Note that this
+ guarantee does not apply to development builds, and also there is no
+ guarantee that this version is identical to the first two integer
+ numbers of the package version.
+ </desc>
+ </attribute>
+
+ <attribute name="APIRevision" type="long long" readonly="yes">
+ <desc>
+ This is mainly intended for the VBox Validation Kit so it can fluently
+ deal with incompatible API changes and new functionality during
+ development (i.e. on trunk).
+
+ The high 7 bits (62:56) is the major version number, the next 8 bits
+ (55:48) are the minor version number, the next 8 bits (47:40) are the
+ build number, and the rest (39:0) is the API revision number.
+
+ The API revision number is manually increased on trunk when making
+ incompatible changes that the validation kit or others needs to be able
+ to detect and cope with dynamically. It can also be used to indicate
+ the presence of new features on both trunk and branches.
+ </desc>
+ </attribute>
+
+ <attribute name="homeFolder" type="wstring" readonly="yes">
+ <desc>
+ Full path to the directory where the global settings file,
+ <tt>VirtualBox.xml</tt>, is stored.
+
+ In this version of VirtualBox, the value of this property is
+ always <tt>&lt;user_dir&gt;/.VirtualBox</tt> (where
+ <tt>&lt;user_dir&gt;</tt> is the path to the user directory,
+ as determined by the host OS), and cannot be changed.
+
+ This path is also used as the base to resolve relative paths in
+ places where relative paths are allowed (unless otherwise
+ expressly indicated).
+ </desc>
+ </attribute>
+
+ <attribute name="settingsFilePath" type="wstring" readonly="yes">
+ <desc>
+ Full name of the global settings file.
+ The value of this property corresponds to the value of
+ <link to="#homeFolder"/> plus <tt>/VirtualBox.xml</tt>.
+ </desc>
+ </attribute>
+
+ <attribute name="host" type="IHost" readonly="yes">
+ <desc>Associated host object.</desc>
+ </attribute>
+
+ <attribute name="systemProperties" type="ISystemProperties" readonly="yes">
+ <desc>Associated system information object.</desc>
+ </attribute>
+
+ <attribute name="machines" type="IMachine" readonly="yes" safearray="yes">
+ <desc>
+ Array of machine objects registered within this VirtualBox instance.
+ </desc>
+ </attribute>
+
+ <attribute name="machineGroups" type="wstring" readonly="yes" safearray="yes">
+ <desc>
+ Array of all machine group names which are used by the machines which
+ are accessible. Each group is only listed once, however they are listed
+ in no particular order and there is no guarantee that there are no gaps
+ in the group hierarchy (i.e. <tt>"/"</tt>, <tt>"/group/subgroup"</tt>
+ is a valid result).
+ </desc>
+ </attribute>
+
+ <attribute name="hardDisks" type="IMedium" readonly="yes" safearray="yes">
+ <desc>
+ Array of medium objects known to this VirtualBox installation.
+
+ This array contains only base media. All differencing
+ media of the given base medium can be enumerated using
+ <link to="IMedium::children"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="DVDImages" type="IMedium" readonly="yes" safearray="yes">
+ <desc>
+ Array of CD/DVD image objects currently in use by this VirtualBox instance.
+ </desc>
+ </attribute>
+
+ <attribute name="floppyImages" type="IMedium" readonly="yes" safearray="yes">
+ <desc>
+ Array of floppy image objects currently in use by this VirtualBox instance.
+ </desc>
+ </attribute>
+
+ <attribute name="progressOperations" type="IProgress" readonly="yes" safearray="yes"/>
+
+ <attribute name="guestOSTypes" type="IGuestOSType" readonly="yes" safearray="yes"/>
+
+ <attribute name="sharedFolders" type="ISharedFolder" readonly="yes" safearray="yes">
+ <desc>
+ Collection of global shared folders. Global shared folders are
+ available to all virtual machines.
+
+ New shared folders are added to the collection using
+ <link to="#createSharedFolder"/>. Existing shared folders can be
+ removed using <link to="#removeSharedFolder"/>.
+
+ <note>
+ In the current version of the product, global shared folders are not
+ implemented and therefore this collection is always empty.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="performanceCollector" type="IPerformanceCollector" readonly="yes">
+ <desc>
+ Associated performance collector object.
+ </desc>
+ </attribute>
+
+ <attribute name="DHCPServers" type="IDHCPServer" safearray="yes" readonly="yes">
+ <desc>
+ DHCP servers.
+ </desc>
+ </attribute>
+ <!-- Array of NAT networks -->
+ <attribute name="NATNetworks" type="INATNetwork" safearray="yes" readonly="yes"/>
+
+ <attribute name="eventSource" type="IEventSource" readonly="yes">
+ <desc>
+ Event source for VirtualBox events.
+ </desc>
+ </attribute>
+
+ <attribute name="extensionPackManager" type="IExtPackManager" readonly="yes">
+ <desc>
+ The extension pack manager.
+ </desc>
+ </attribute>
+
+
+ <attribute name="internalNetworks" type="wstring" safearray="yes" readonly="yes">
+ <desc>
+ Names of all internal networks.
+ </desc>
+ </attribute>
+
+ <attribute name="hostOnlyNetworks" type="IHostOnlyNetwork" safearray="yes" readonly="yes">
+ <desc>
+ Names of all host-only networks.
+ </desc>
+ </attribute>
+
+ <attribute name="genericNetworkDrivers" type="wstring" safearray="yes" readonly="yes">
+ <desc>
+ Names of all generic network drivers.
+ </desc>
+ </attribute>
+
+ <attribute name="cloudNetworks" type="ICloudNetwork" safearray="yes" readonly="yes">
+ <desc>
+ Names of all configured cloud networks.
+ </desc>
+ </attribute>
+
+ <attribute name="cloudProviderManager" type="ICloudProviderManager" readonly="yes">
+ <desc>
+ The cloud provider manager (singleton).
+ </desc>
+ </attribute>
+
+ <method name="composeMachineFilename">
+ <rest request="get" path="/server/methods/"/>
+ <desc>
+ Returns a recommended full path of the settings file name for a new virtual
+ machine.
+
+ This API serves two purposes:
+
+ <ul>
+ <li>It gets called by <link to="#createMachine" /> if @c null or
+ empty string (which is recommended) is specified for the
+ @a settingsFile argument there, which means that API should use
+ a recommended default file name.</li>
+
+ <li>It can be called manually by a client software before creating a machine,
+ e.g. if that client wants to pre-create the machine directory to create
+ virtual hard disks in that directory together with the new machine
+ settings file. In that case, the file name should be stripped from the
+ full settings file path returned by this function to obtain the
+ machine directory.</li>
+ </ul>
+
+ See <link to="IMachine::name"/> and <link to="#createMachine"/> for more
+ details about the machine name.
+
+ @a groupName defines which additional subdirectory levels should be
+ included. It must be either a valid group name or @c null or empty
+ string which designates that the machine will not be related to a
+ machine group.
+
+ If @a baseFolder is a @c null or empty string (which is recommended), the
+ default machine settings folder
+ (see <link to="ISystemProperties::defaultMachineFolder" />) will be used as
+ a base folder for the created machine, resulting in a file name like
+ "/home/user/VirtualBox VMs/name/name.vbox". Otherwise the given base folder
+ will be used.
+
+ This method does not access the host disks. In particular, it does not check
+ for whether a machine with this name already exists.
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Suggested machine name.</desc>
+ </param>
+ <param name="group" type="wstring" dir="in">
+ <desc>Machine group name for the new machine or machine group. It is
+ used to determine the right subdirectory.</desc>
+ </param>
+ <param name="createFlags" type="wstring" dir="in">
+ <desc>Machine creation flags, see <link to="#createMachine" /> (optional).</desc>
+ </param>
+ <param name="baseFolder" type="wstring" dir="in">
+ <desc>Base machine folder (optional).</desc>
+ </param>
+ <param name="file" type="wstring" dir="return">
+ <desc>Fully qualified path where the machine would be created.</desc>
+ </param>
+ </method>
+
+ <method name="createMachine">
+ <rest name="create" request="post" path="/vms/"/>
+ <desc>
+ Creates a new virtual machine by creating a machine settings file at
+ the given location.
+
+ VirtualBox machine settings files use a custom XML dialect. Starting
+ with VirtualBox 4.0, a ".vbox" extension is recommended, but not enforced,
+ and machine files can be created at arbitrary locations.
+
+ However, it is recommended that machines are created in the default
+ machine folder (e.g. "/home/user/VirtualBox VMs/name/name.vbox"; see
+ <link to="ISystemProperties::defaultMachineFolder" />). If you specify
+ @c null or empty string (which is recommended) for the @a settingsFile
+ argument, <link to="#composeMachineFilename" /> is called automatically
+ to have such a recommended name composed based on the machine name
+ given in the @a name argument and the primary group.
+
+ If the resulting settings file already exists, this method will fail,
+ unless the forceOverwrite flag is set.
+
+ The new machine is created unregistered, with the initial configuration
+ set according to the specified guest OS type. A typical sequence of
+ actions to create a new virtual machine is as follows:
+
+ <ol>
+ <li>
+ Call this method to have a new machine created. The returned machine
+ object will be "mutable" allowing to change any machine property.
+ </li>
+
+ <li>
+ Configure the machine using the appropriate attributes and methods.
+ </li>
+
+ <li>
+ Call <link to="IMachine::saveSettings" /> to write the settings
+ to the machine's XML settings file. The configuration of the newly
+ created machine will not be saved to disk until this method is
+ called.
+ </li>
+
+ <li>
+ Call <link to="#registerMachine" /> to add the machine to the list
+ of machines known to VirtualBox.
+ </li>
+ </ol>
+
+ The specified guest OS type identifier must match an ID of one of known
+ guest OS types listed in the <link to="IVirtualBox::guestOSTypes"/>
+ array.
+
+ <note>
+ <link to="IMachine::settingsModified"/> will return
+ @c false for the created machine, until any of machine settings
+ are changed.
+ </note>
+
+ <note>
+ There is no way to change the name of the settings file or
+ subfolder of the created machine directly.
+ </note>
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ @a osTypeId is invalid.
+ </result>
+ <result name="VBOX_E_FILE_ERROR">
+ Resulting settings file name is invalid or the settings file already
+ exists or could not be created due to an I/O error.
+ </result>
+ <result name="E_INVALIDARG">
+ @a name is empty or @c null.
+ </result>
+ </desc>
+
+ <param name="settingsFile" type="wstring" dir="in">
+ <desc>Fully qualified path where the settings file should be created,
+ empty string or @c null for a default folder and file based on the
+ @a name argument and the primary group.
+ (see <link to="#composeMachineFilename" />).</desc>
+ </param>
+ <param name="name" type="wstring" dir="in">
+ <desc>Machine name.</desc>
+ </param>
+ <param name="groups" type="wstring" safearray="yes" dir="in">
+ <desc>Array of group names. @c null or an empty array have the same
+ meaning as an array with just the empty string or <tt>"/"</tt>, i.e.
+ create a machine without group association.</desc>
+ </param>
+ <param name="osTypeId" type="wstring" dir="in">
+ <desc>Guest OS Type ID.</desc>
+ </param>
+ <param name="flags" type="wstring" dir="in">
+ <desc>
+ Additional property parameters, passed as a comma-separated list of
+ "name=value" type entries. The following ones are recognized:
+ <tt>forceOverwrite=1</tt> to overwrite an existing machine settings
+ file, <tt>UUID=&lt;uuid&gt;</tt> to specify a machine UUID and
+ <tt>directoryIncludesUUID=1</tt> to switch to a special VM directory
+ naming scheme which should not be used unless necessary.
+ </desc>
+ </param>
+ <param name="cipher" type="wstring" dir="in">
+ <desc>
+ The cipher. It should be empty if encryption is not required.
+ </desc>
+ </param>
+ <param name="passwordId" type="wstring" dir="in">
+ <desc>
+ The password id. It should be empty if encryption is not required.
+ </desc>
+ </param>
+ <param name="password" type="wstring" dir="in">
+ <desc>
+ The password. It should be empty if encryption is not required.
+ </desc>
+ </param>
+ <param name="machine" type="IMachine" dir="return">
+ <desc>Created machine object.</desc>
+ </param>
+ </method>
+
+ <method name="openMachine">
+ <rest name="open" request="post" path="/vms/{vmid}/actions/"/>
+ <desc>
+ Opens a virtual machine from the existing settings file.
+ The opened machine remains unregistered until you call
+ <link to="#registerMachine"/>.
+
+ The specified settings file name must be fully qualified.
+ The file must exist and be a valid machine XML settings file
+ whose contents will be used to construct the machine object.
+
+ <note>
+ If the VM is encrypted and password is incorrect
+ the method returns success allowing you to register the
+ encrypted machine but it remains in inaccessible state. You
+ can check <link to="IMachine::accessible"/> and
+ <link to="IMachine::accessError"/> properties to determine the
+ real machine state.
+ </note>
+
+ <note>
+ <link to="IMachine::settingsModified"/> will return
+ @c false for the opened machine, until any of machine settings
+ are changed.
+ </note>
+
+ <result name="VBOX_E_FILE_ERROR">
+ Settings file name invalid, not found or sharing violation.
+ </result>
+ </desc>
+ <param name="settingsFile" type="wstring" dir="in">
+ <desc>
+ Name of the machine settings file.
+ </desc>
+ </param>
+ <param name="password" type="wstring" dir="in">
+ <desc>
+ The password. If the machine is not encrypted the parameter is
+ ignored.
+ </desc>
+ </param>
+ <param name="machine" type="IMachine" dir="return">
+ <desc>Opened machine object.</desc>
+ </param>
+ </method>
+
+ <method name="registerMachine">
+ <rest name="register" request="post" path="/vms/{vmid}/actions/"/>
+ <desc>
+
+ Registers the machine previously created using
+ <link to="#createMachine"/> or opened using
+ <link to="#openMachine"/> within this VirtualBox installation. After
+ successful method invocation, the
+ <link to="IMachineRegisteredEvent"/> event is fired.
+
+ <note>
+ This method implicitly calls <link to="IMachine::saveSettings"/>
+ to save all current machine settings before registering it.
+ </note>
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ No matching virtual machine found.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Virtual machine was not created within this VirtualBox instance.
+ </result>
+
+ </desc>
+ <param name="machine" type="IMachine" dir="in"/>
+ </method>
+
+ <method name="findMachine" const="yes">
+ <rest name="find" request="get" path="/vms/{vmid}/"/>
+ <desc>
+ Attempts to find a virtual machine given its name or UUID.
+
+ <note>Inaccessible machines cannot be found by name, only by UUID, because their name
+ cannot safely be determined.</note>
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Could not find registered machine matching @a nameOrId.
+ </result>
+
+ </desc>
+ <param name="nameOrId" type="wstring" dir="in">
+ <desc>What to search for. This can either be the UUID or the name of a virtual machine.</desc>
+ </param>
+ <param name="machine" type="IMachine" dir="return">
+ <desc>Machine object, if found.</desc>
+ </param>
+ </method>
+
+ <method name="getMachinesByGroups">
+ <rest request="get" path="/server/methods/"/>
+ <desc>
+ Gets all machine references which are in one of the specified groups.
+ </desc>
+ <param name="groups" type="wstring" dir="in" safearray="yes">
+ <desc>What groups to match. The usual group list rules apply, i.e.
+ passing an empty list will match VMs in the toplevel group, likewise
+ the empty string.</desc>
+ </param>
+ <param name="machines" type="IMachine" dir="return" safearray="yes">
+ <desc>All machines which matched.</desc>
+ </param>
+ </method>
+
+ <method name="getMachineStates">
+ <rest request="get" path="/server/methods/"/>
+ <desc>
+ Gets the state of several machines in a single operation.
+ </desc>
+ <param name="machines" type="IMachine" dir="in" safearray="yes">
+ <desc>Array with the machine references.</desc>
+ </param>
+ <param name="states" type="MachineState" dir="return" safearray="yes">
+ <desc>Machine states, corresponding to the machines.</desc>
+ </param>
+ </method>
+
+ <method name="createAppliance">
+ <rest name="create" request="post" path="/appliances/"/>
+ <desc>
+ Creates a new appliance object, which represents an appliance in the Open Virtual Machine
+ Format (OVF). This can then be used to import an OVF appliance into VirtualBox or to export
+ machines as an OVF appliance; see the documentation for <link to="IAppliance" /> for details.
+ </desc>
+ <param name="appliance" type="IAppliance" dir="return">
+ <desc>New appliance.</desc>
+ </param>
+ </method>
+
+ <method name="createUnattendedInstaller">
+ <rest request="post" path="/server/methods/"/>
+ <desc>
+ Creates a new <link to="IUnattended"/> guest installation object. This can be used to
+ analyze an installation ISO to create and configure a new machine for it to be installed
+ on. It can also be used to (re)install an existing machine.
+ </desc>
+ <param name="unattended" type="IUnattended" dir="return">
+ <desc>New unattended object.</desc>
+ </param>
+ </method>
+
+ <method name="createMedium">
+ <rest name="create" request="post" path="/mediums/"/>
+ <desc>
+ Creates a new base medium object that will use the given storage
+ format and location for medium data.
+
+ The actual storage unit is not created by this method. In order to
+ do it, and before you are able to attach the created medium to
+ virtual machines, you must call one of the following methods to
+ allocate a format-specific storage unit at the specified location:
+ <ul>
+ <li><link to="IMedium::createBaseStorage"/></li>
+ <li><link to="IMedium::createDiffStorage"/></li>
+ </ul>
+
+ Some medium attributes, such as <link to="IMedium::id"/>, may
+ remain uninitialized until the medium storage unit is successfully
+ created by one of the above methods.
+
+ Depending on the given device type, the file at the storage location
+ must be in one of the media formats understood by VirtualBox:
+
+ <ul>
+ <li>With a "HardDisk" device type, the file must be a hard disk image
+ in one of the formats supported by VirtualBox (see
+ <link to="ISystemProperties::mediumFormats" />).
+ After the storage unit is successfully created and this method succeeds,
+ if the medium is a base medium, it
+ will be added to the <link to="#hardDisks"/> array attribute. </li>
+ <li>With a "DVD" device type, the file must be an ISO 9960 CD/DVD image.
+ After this method succeeds, the medium will be added to the
+ <link to="#DVDImages"/> array attribute.</li>
+ <li>With a "Floppy" device type, the file must be an RAW floppy image.
+ After this method succeeds, the medium will be added to the
+ <link to="#floppyImages"/> array attribute.</li>
+ </ul>
+
+ The list of all storage formats supported by this VirtualBox
+ installation can be obtained using
+ <link to="ISystemProperties::mediumFormats"/>. If the @a format
+ attribute is empty or @c null then the default storage format
+ specified by <link to="ISystemProperties::defaultHardDiskFormat"/> will
+ be used for disks r creating a storage unit of the medium.
+
+ Note that the format of the location string is storage format specific.
+ See <link to="IMedium::location"/> and IMedium for more details.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ @a format identifier is invalid. See
+ <link to="ISystemProperties::mediumFormats"/>.
+ </result>
+ <result name="VBOX_E_FILE_ERROR">
+ @a location is a not valid file name (for file-based formats only).
+ </result>
+ </desc>
+ <param name="format" type="wstring" dir="in">
+ <desc>
+ Identifier of the storage format to use for the new medium.
+ </desc>
+ </param>
+ <param name="location" type="wstring" dir="in">
+ <desc>
+ Location of the storage unit for the new medium.
+ </desc>
+ </param>
+ <param name="accessMode" type="AccessMode" dir="in">
+ <desc>Whether to open the image in read/write or read-only mode. For
+ a "DVD" device type, this is ignored and read-only mode is always assumed.</desc>
+ </param>
+ <param name="aDeviceTypeType" type="DeviceType" dir="in">
+ <desc>
+ Must be one of "HardDisk", "DVD" or "Floppy".
+ </desc>
+ </param>
+ <param name="medium" type="IMedium" dir="return">
+ <desc>Created medium object.</desc>
+ </param>
+ </method>
+
+ <method name="openMedium">
+ <rest request="post" path="/server/methods/"/>
+ <desc>
+ Finds existing media or opens a medium from an existing storage location.
+
+ Once a medium has been opened, it can be passed to other VirtualBox
+ methods, in particular to <link to="IMachine::attachDevice" />.
+
+ Depending on the given device type, the file at the storage location
+ must be in one of the media formats understood by VirtualBox:
+
+ <ul>
+ <li>With a "HardDisk" device type, the file must be a hard disk image
+ in one of the formats supported by VirtualBox (see
+ <link to="ISystemProperties::mediumFormats" />).
+ After this method succeeds, if the medium is a base medium, it
+ will be added to the <link to="#hardDisks"/> array attribute. </li>
+ <li>With a "DVD" device type, the file must be an ISO 9960 CD/DVD image.
+ After this method succeeds, the medium will be added to the
+ <link to="#DVDImages"/> array attribute.</li>
+ <li>With a "Floppy" device type, the file must be an RAW floppy image.
+ After this method succeeds, the medium will be added to the
+ <link to="#floppyImages"/> array attribute.</li>
+ </ul>
+
+ After having been opened, the medium can be re-found by this method
+ and can be attached to virtual machines. See <link to="IMedium" /> for
+ more details.
+
+ The UUID of the newly opened medium will either be retrieved from the
+ storage location, if the format supports it (e.g. for hard disk images),
+ or a new UUID will be randomly generated (e.g. for ISO and RAW files).
+ If for some reason you need to change the medium's UUID, use
+ <link to="IMedium::setIds" />.
+
+ If a differencing hard disk medium is to be opened by this method, the
+ operation will succeed only if its parent medium and all ancestors,
+ if any, are already known to this VirtualBox installation (for example,
+ were opened by this method before).
+
+ This method attempts to guess the storage format of the specified medium
+ by reading medium data at the specified location.
+
+ If @a accessMode is ReadWrite (which it should be for hard disks and floppies),
+ the image is opened for read/write access and must have according permissions,
+ as VirtualBox may actually write status information into the disk's metadata
+ sections.
+
+ Note that write access is required for all typical hard disk usage in VirtualBox,
+ since VirtualBox may need to write metadata such as a UUID into the image.
+ The only exception is opening a source image temporarily for copying and
+ cloning (see <link to="IMedium::cloneTo" /> when the image will be closed
+ again soon.
+
+ The format of the location string is storage format specific. See
+ <link to="IMedium::location"/> and IMedium for more details.
+
+ <result name="VBOX_E_FILE_ERROR">
+ Invalid medium storage file location or could not find the medium
+ at the specified location.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ Could not get medium storage format.
+ </result>
+ <result name="E_INVALIDARG">
+ Invalid medium storage format.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Medium has already been added to a media registry.
+ </result>
+ </desc>
+ <param name="location" type="wstring" dir="in">
+ <desc>
+ Location of the storage unit that contains medium data in one of
+ the supported storage formats.
+ </desc>
+ </param>
+ <param name="deviceType" type="DeviceType" dir="in">
+ <desc>
+ Must be one of "HardDisk", "DVD" or "Floppy".
+ </desc>
+ </param>
+ <param name="accessMode" type="AccessMode" dir="in">
+ <desc>Whether to open the image in read/write or read-only mode. For
+ a "DVD" device type, this is ignored and read-only mode is always assumed.</desc>
+ </param>
+ <param name="forceNewUuid" type="boolean" dir="in">
+ <desc>Allows the caller to request a completely new medium UUID for
+ the image which is to be opened. Useful if one intends to open an exact
+ copy of a previously opened image, as this would normally fail due to
+ the duplicate UUID.</desc>
+ </param>
+ <param name="medium" type="IMedium" dir="return">
+ <desc>Opened medium object.</desc>
+ </param>
+ </method>
+
+ <method name="getGuestOSType">
+ <rest request="get" path="/server/methods/"/>
+ <desc>
+ Returns an object describing the specified guest OS type.
+
+ The requested guest OS type is specified using a string which is a
+ mnemonic identifier of the guest operating system, such as
+ <tt>"win31"</tt> or <tt>"ubuntu"</tt>. The guest OS type ID of a
+ particular virtual machine can be read or set using the
+ <link to="IMachine::OSTypeId"/> attribute.
+
+ The <link to="IVirtualBox::guestOSTypes"/> collection contains all
+ available guest OS type objects. Each object has an
+ <link to="IGuestOSType::id"/> attribute which contains an identifier of
+ the guest OS this object describes.
+
+ While this function returns an error for unknown guest OS types, they
+ can be still used without serious problems (if one accepts the fact
+ that there is no default VM config information).
+
+ <result name="E_INVALIDARG">
+ @a id is not a valid Guest OS type.
+ </result>
+
+ </desc>
+ <param name="id" type="wstring" dir="in">
+ <desc>Guest OS type ID string.</desc>
+ </param>
+ <param name="type" type="IGuestOSType" dir="return">
+ <desc>Guest OS type object.</desc>
+ </param>
+ </method>
+
+ <method name="createSharedFolder">
+ <rest name="create" request="post" path="/sharedfolders/"/>
+ <desc>
+ Creates a new global shared folder by associating the given logical
+ name with the given host path, adds it to the collection of shared
+ folders and starts sharing it. Refer to the description of
+ <link to="ISharedFolder"/> to read more about logical names.
+ <note>
+ In the current implementation, this operation is not
+ implemented.
+ </note>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Unique logical name of the shared folder.</desc>
+ </param>
+ <param name="hostPath" type="wstring" dir="in">
+ <desc>Full path to the shared folder in the host file system.</desc>
+ </param>
+ <param name="writable" type="boolean" dir="in">
+ <desc>Whether the share is writable or readonly</desc>
+ </param>
+ <param name="automount" type="boolean" dir="in">
+ <desc>Whether the share gets automatically mounted by the guest
+ or not.</desc>
+ </param>
+ <param name="autoMountPoint" type="wstring" dir="in">
+ <desc>Where the guest should automatically mount the folder, if possible.
+ For Windows and OS/2 guests this should be a drive letter, while other
+ guests it should be a absolute directory.
+ </desc>
+ </param>
+ </method>
+
+ <method name="removeSharedFolder">
+ <rest name="remove" request="delete" path="/sharedfolders/"/>
+ <desc>
+ Removes the global shared folder with the given name previously
+ created by <link to="#createSharedFolder"/> from the collection of
+ shared folders and stops sharing it.
+ <note>
+ In the current implementation, this operation is not
+ implemented.
+ </note>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Logical name of the shared folder to remove.</desc>
+ </param>
+ </method>
+
+ <method name="getExtraDataKeys">
+ <rest request="get" path="/server/methods/"/>
+ <desc>
+ Returns an array representing the global extra data keys which currently
+ have values defined.
+ </desc>
+ <param name="keys" type="wstring" dir="return" safearray="yes">
+ <desc>Array of extra data keys.</desc>
+ </param>
+ </method>
+
+ <method name="getExtraData">
+ <rest request="get" path="/server/methods/"/>
+ <desc>
+ Returns associated global extra data.
+
+ If the requested data @a key does not exist, this function will
+ succeed and return an empty string in the @a value argument.
+
+ <result name="VBOX_E_FILE_ERROR">
+ Settings file not accessible.
+ </result>
+ <result name="VBOX_E_XML_ERROR">
+ Could not parse the settings file.
+ </result>
+
+ </desc>
+ <param name="key" type="wstring" dir="in">
+ <desc>Name of the data key to get.</desc>
+ </param>
+ <param name="value" type="wstring" dir="return">
+ <desc>Value of the requested data key.</desc>
+ </param>
+ </method>
+
+ <method name="setExtraData">
+ <rest request="put" path="/server/methods/"/>
+ <desc>
+ Sets associated global extra data.
+
+ If you pass @c null or an empty string as a key @a value, the given
+ @a key will be deleted.
+
+ <note>
+ Key must contain printable (non-control) UTF-8 characters only.
+ </note>
+ <note>
+ Before performing the actual data change, this method will ask all
+ registered event listeners using the
+ <link to="IExtraDataCanChangeEvent"/>
+ notification for a permission. If one of the listeners refuses the
+ new value, the change will not be performed.
+ </note>
+ <note>
+ On success, the
+ <link to="IExtraDataChangedEvent"/> notification
+ is called to inform all registered listeners about a successful data
+ change.
+ </note>
+
+ <result name="VBOX_E_FILE_ERROR">
+ Settings file not accessible.
+ </result>
+ <result name="VBOX_E_XML_ERROR">
+ Could not parse the settings file.
+ </result>
+ <result name="E_ACCESSDENIED">
+ Modification request refused.
+ </result>
+ <result name="E_INVALIDARG">
+ Key contains invalid characters.
+ </result>
+
+ </desc>
+ <param name="key" type="wstring" dir="in">
+ <desc>Name of the data key to set.</desc>
+ </param>
+ <param name="value" type="wstring" dir="in">
+ <desc>Value to assign to the key.</desc>
+ </param>
+ </method>
+
+ <method name="setSettingsSecret">
+ <desc>
+ Unlocks the secret data by passing the unlock password to the
+ server. The server will cache the password for that machine.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine is not mutable.
+ </result>
+
+ </desc>
+ <param name="password" type="wstring" dir="in">
+ <desc>
+ The cipher key.
+ </desc>
+ </param>
+ </method>
+
+ <!--method name="createDHCPServerForInterface">
+ <desc>
+ Creates a DHCP server settings to be used for the given interface
+ <result name="E_INVALIDARG">
+ Host network interface @a name already exists.
+ </result>
+ </desc>
+ <param name="interface" type="IHostNetworkInterface" dir="in">
+ <desc>Network Interface</desc>
+ </param>
+ <param name="server" type="IDHCPServer" dir="out">
+ <desc>DHCP server settings</desc>
+ </param>
+ </method-->
+
+ <method name="createDHCPServer">
+ <rest name="create" request="post" path="/dhcpservers/"/>
+ <desc>
+ Creates a DHCP server settings to be used for the given internal network name
+ <result name="E_INVALIDARG">
+ Host network interface @a name already exists.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>server name</desc>
+ </param>
+ <param name="server" type="IDHCPServer" dir="return">
+ <desc>DHCP server settings</desc>
+ </param>
+ </method>
+
+ <method name="findDHCPServerByNetworkName">
+ <rest name="find" request="get" path="/dhcpservers/"/>
+ <desc>
+ Searches a DHCP server settings to be used for the given internal network name
+ <result name="E_INVALIDARG">
+ Host network interface @a name already exists.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>server name</desc>
+ </param>
+ <param name="server" type="IDHCPServer" dir="return">
+ <desc>DHCP server settings</desc>
+ </param>
+ </method>
+
+ <!--method name="findDHCPServerForInterface">
+ <desc>
+ Searches a DHCP server settings to be used for the given interface
+ <result name="E_INVALIDARG">
+ Host network interface @a name already exists.
+ </result>
+ </desc>
+ <param name="interface" type="IHostNetworkInterface" dir="in">
+ <desc>Network Interface</desc>
+ </param>
+ <param name="server" type="IDHCPServer" dir="out">
+ <desc>DHCP server settings</desc>
+ </param>
+ </method-->
+
+ <method name="removeDHCPServer">
+ <rest name="remove" request="delete" path="/dhcpservers/"/>
+ <desc>
+ Removes the DHCP server settings
+ <result name="E_INVALIDARG">
+ Host network interface @a name already exists.
+ </result>
+ </desc>
+ <param name="server" type="IDHCPServer" dir="in">
+ <desc>DHCP server settings to be removed</desc>
+ </param>
+ </method>
+
+ <!-- bunch of methods to create NAT -->
+ <method name="createNATNetwork">
+ <!-- Here we create a record in NAT network array with name
+ and gateway/network parameters this information should
+ be enough for VBoxNetNAT and VBoxNetDHCP for
+ servicing the guests.
+ -->
+ <rest name="create" request="post" path="/natnetworks/"/>
+ <param name="networkName" type="wstring" dir="in"/>
+ <param name="network" type="INATNetwork" dir="return"/>
+ </method>
+
+ <!--
+ Returns the NATNetwork by name, e.g. for adding port forward rule or deletion.
+ -->
+ <method name="findNATNetworkByName">
+ <rest name="find" request="get" path="/natnetworks/"/>
+ <param name="networkName" type="wstring" dir="in"/>
+ <param name="network" type="INATNetwork" dir="return"/>
+ </method>
+ <!--
+ Deletes given NAT network.
+ -->
+ <method name="removeNATNetwork">
+ <rest name="remove" request="delete" path="/natnetworks/"/>
+ <param name="network" type="INATNetwork" dir="in"/>
+ </method>
+
+ <!-- bunch of methods to manage host-only networks -->
+ <!--
+ Here we create a record in cloud network array with specified name.
+ -->
+ <method name="createHostOnlyNetwork">
+ <rest name="create" request="post" path="/hostonlynetworks/"/>
+ <param name="networkName" type="wstring" dir="in"/>
+ <param name="network" type="IHostOnlyNetwork" dir="return"/>
+ </method>
+
+ <!--
+ Returns the host-only network by name.
+ -->
+ <method name="findHostOnlyNetworkByName">
+ <param name="networkName" type="wstring" dir="in"/>
+ <param name="network" type="IHostOnlyNetwork" dir="return"/>
+ </method>
+ <!--
+ Returns the host-only network by id.
+ -->
+ <method name="findHostOnlyNetworkById">
+ <rest name="find" request="get" path="/hostonlynetworks/"/>
+ <desc>
+ Searches through all host networks for one with
+ the given GUID.
+ <note>
+ The method returns an error if the given GUID does not
+ correspond to any host network.
+ </note>
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in">
+ <desc>GUID of the host-only network to search for.</desc>
+ </param>
+ <param name="network" type="IHostOnlyNetwork" dir="return">
+ <desc>Found host-only network object.</desc>
+ </param>
+ </method>
+ <!--
+ Deletes given host-only network.
+ -->
+ <method name="removeHostOnlyNetwork">
+ <rest name="remove" request="delete" path="/hostonlynetworks/"/>
+ <param name="network" type="IHostOnlyNetwork" dir="in"/>
+ </method>
+
+ <!-- bunch of methods to manage cloud networks -->
+ <method name="createCloudNetwork">
+ <!-- Here we create a record in cloud network array with specified name.
+ -->
+ <rest name="create" request="post" path="/cloudnetworks/"/>
+ <param name="networkName" type="wstring" dir="in"/>
+ <param name="network" type="ICloudNetwork" dir="return"/>
+ </method>
+
+ <!--
+ Returns the cloud network by name.
+ -->
+ <method name="findCloudNetworkByName">
+ <rest name="find" request="get" path="/cloudnetworks/"/>
+ <param name="networkName" type="wstring" dir="in"/>
+ <param name="network" type="ICloudNetwork" dir="return"/>
+ </method>
+ <!--
+ Deletes given cloud network.
+ -->
+ <method name="removeCloudNetwork">
+ <rest name="remove" request="delete" path="/cloudnetworks/"/>
+ <param name="network" type="ICloudNetwork" dir="in"/>
+ </method>
+
+ <method name="checkFirmwarePresent">
+ <rest request="get" path="/server/methods/"/>
+ <desc>
+ Check if this VirtualBox installation has a firmware
+ of the given type available, either system-wide or per-user.
+ Optionally, this may return a hint where this firmware can be
+ downloaded from.
+ </desc>
+ <param name="firmwareType" type="FirmwareType" dir="in">
+ <desc>
+ Type of firmware to check.
+ </desc>
+ </param>
+ <param name="version" type="wstring" dir="in">
+ <desc>Expected version number, usually empty string (presently ignored).</desc>
+ </param>
+
+ <param name="url" type="wstring" dir="out">
+ <desc>
+ Suggested URL to download this firmware from.
+ </desc>
+ </param>
+
+ <param name="file" type="wstring" dir="out">
+ <desc>
+ Filename of firmware, only valid if result == TRUE.
+ </desc>
+ </param>
+
+ <param name="result" type="boolean" dir="return">
+ <desc>If firmware of this type and version is available.</desc>
+ </param>
+ </method>
+
+ <method name="findProgressById">
+ <rest name="find" request="get" path="/progresses/{progressid}/"/>
+ <desc>
+ Searches through all progress objects known to VBoxSVC for an
+ instance with the given GUID.
+ <note>
+ The method returns an error if the given GUID does not
+ correspond to any currently known progress object.
+ </note>
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in">
+ <desc>GUID of the progress object to search for.</desc>
+ </param>
+ <param name="progressObject" type="IProgress" dir="return">
+ <desc>Found progress object.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <!--
+ // IVFSExplorer
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="VFSType"
+ uuid="813999ba-b949-48a8-9230-aadc6285e2f2"
+ >
+ <desc>
+ Virtual file systems supported by VFSExplorer.
+ </desc>
+
+ <const name="File" value="1" />
+ <const name="Cloud" value="2" />
+ <const name="S3" value="3" />
+ <const name="WebDav" value="4" />
+ </enum>
+
+ <interface
+ name="IVFSExplorer" extends="$unknown"
+ uuid="fb220201-2fd3-47e2-a5dc-2c2431d833cc"
+ wsmap="managed"
+ >
+ <desc>
+ The VFSExplorer interface unifies access to different file system
+ types. This includes local file systems as well remote file systems like
+ S3. For a list of supported types see <link to="VFSType" />.
+ An instance of this is returned by <link to="IAppliance::createVFSExplorer" />.
+ </desc>
+
+ <attribute name="path" type="wstring" readonly="yes">
+ <desc>Returns the current path in the virtual file system.</desc>
+ </attribute>
+
+ <attribute name="type" type="VFSType" readonly="yes">
+ <desc>Returns the file system type which is currently in use.</desc>
+ </attribute>
+
+ <method name="update">
+ <desc>Updates the internal list of files/directories from the
+ current directory level. Use <link to="#entryList" /> to get the full list
+ after a call to this method.</desc>
+
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="cd">
+ <desc>Change the current directory level.</desc>
+
+ <param name="dir" type="wstring" dir="in">
+ <desc>The name of the directory to go in.</desc>
+ </param>
+
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="cdUp">
+ <desc>Go one directory upwards from the current directory level.</desc>
+
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="entryList">
+ <desc>Returns a list of files/directories after a call to <link
+ to="#update" />. The user is responsible for keeping this internal
+ list up do date.</desc>
+
+ <param name="names" type="wstring" safearray="yes" dir="out">
+ <desc>The list of names for the entries.</desc>
+ </param>
+
+ <param name="types" type="unsigned long" safearray="yes" dir="out">
+ <desc>The list of types for the entries. <link to="FsObjType"/></desc>
+ </param>
+
+ <param name="sizes" type="long long" safearray="yes" dir="out">
+ <desc>The list of sizes (in bytes) for the entries.</desc>
+ </param>
+
+ <param name="modes" type="unsigned long" safearray="yes" dir="out">
+ <desc>The list of file modes (in octal form) for the entries.</desc>
+ </param>
+ </method>
+
+ <method name="exists">
+ <desc>Checks if the given file list exists in the current directory
+ level.</desc>
+
+ <param name="names" type="wstring" safearray="yes" dir="in">
+ <desc>The names to check.</desc>
+ </param>
+
+ <param name="exists" type="wstring" safearray="yes" dir="return">
+ <desc>The names which exist.</desc>
+ </param>
+ </method>
+
+ <method name="remove">
+ <desc>Deletes the given files in the current directory level.</desc>
+
+ <param name="names" type="wstring" safearray="yes" dir="in">
+ <desc>The names to remove.</desc>
+ </param>
+
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <enum
+ name="ImportOptions"
+ uuid="0a981523-3b20-4004-8ee3-dfd322202ace"
+ >
+
+ <desc>
+ Import options, used with <link to="IAppliance::importMachines" />.
+ </desc>
+
+ <const name="KeepAllMACs" value="1">
+ <desc>Don't generate new MAC addresses of the attached network adapters.</desc>
+ </const>
+ <const name="KeepNATMACs" value="2">
+ <desc>Don't generate new MAC addresses of the attached network adapters when they are using NAT.</desc>
+ </const>
+
+ <const name="ImportToVDI" value="3">
+ <desc>Import all disks to VDI format</desc>
+ </const>
+
+ </enum>
+
+ <enum
+ name="ExportOptions"
+ uuid="8f45eb08-fd34-41ee-af95-a880bdee5554"
+ >
+
+ <desc>
+ Export options, used with <link to="IAppliance::write" />.
+ </desc>
+
+ <const name="CreateManifest" value="1">
+ <desc>Write the optional manifest file (.mf) which is used for integrity
+ checks prior import.</desc>
+ </const>
+ <const name="ExportDVDImages" value="2">
+ <desc>Export DVD images. Default is not to export them as it is rarely
+ needed for typical VMs.</desc>
+ </const>
+ <const name="StripAllMACs" value="3">
+ <desc>Do not export any MAC address information. Default is to keep them
+ to avoid losing information which can cause trouble after import, at the
+ price of risking duplicate MAC addresses, if the import options are used
+ to keep them.</desc>
+ </const>
+ <const name="StripAllNonNATMACs" value="4">
+ <desc>Do not export any MAC address information, except for adapters
+ using NAT. Default is to keep them to avoid losing information which can
+ cause trouble after import, at the price of risking duplicate MAC
+ addresses, if the import options are used to keep them.</desc>
+ </const>
+
+ </enum>
+
+ <enum
+ name="CertificateVersion"
+ uuid="9e232a99-51d0-4dbd-96a0-ffac4bc3e2a8"
+ >
+ <desc>
+ X.509 certificate version numbers.
+ </desc>
+ <const name="V1" value="1"/>
+ <const name="V2" value="2"/>
+ <const name="V3" value="3"/>
+ <const name="Unknown" value="99"/>
+ </enum>
+
+ <!--
+ // ICertificate
+ /////////////////////////////////////////////////////////////////////////
+ -->
+ <interface
+ name="ICertificate" extends="$unknown"
+ uuid="392f1de4-80e1-4a8a-93a1-67c5f92a838a"
+ wsmap="managed"
+ rest="managed"
+ reservedAttributes="12" reservedMethods="2"
+ >
+ <desc>
+ X.509 certificate details.
+ </desc>
+ <attribute name="versionNumber" type="CertificateVersion" readonly="yes">
+ <desc>Certificate version number.</desc>
+ </attribute>
+ <attribute name="serialNumber" type="wstring" readonly="yes">
+ <desc>Certificate serial number.</desc>
+ </attribute>
+ <attribute name="signatureAlgorithmOID" type="wstring" readonly="yes">
+ <desc>The dotted OID of the signature algorithm.</desc>
+ </attribute>
+ <attribute name="signatureAlgorithmName" type="wstring" readonly="yes">
+ <desc>The signature algorithm name if known (if known).</desc>
+ </attribute>
+ <attribute name="issuerName" type="wstring" readonly="yes" safearray="yes">
+ <desc>Issuer name. Each member of the array is on the format
+ COMPONENT=NAME, e.g. "C=DE", "ST=Example", "L=For Instance", "O=Beispiel GmbH",
+ "CN=beispiel.example.org".
+ </desc>
+ </attribute>
+ <attribute name="subjectName" type="wstring" readonly="yes" safearray="yes">
+ <desc>Subject name. Same format as issuerName.</desc>
+ </attribute>
+ <attribute name="friendlyName" type="wstring" readonly="yes">
+ <desc>Friendly subject name or similar.</desc>
+ </attribute>
+ <attribute name="validityPeriodNotBefore" type="wstring" readonly="yes">
+ <desc>Certificate not valid before ISO timestamp.</desc>
+ </attribute>
+ <attribute name="validityPeriodNotAfter" type="wstring" readonly="yes">
+ <desc>Certificate not valid after ISO timestamp.</desc>
+ </attribute>
+ <attribute name="publicKeyAlgorithmOID" type="wstring" readonly="yes">
+ <desc>The dotted OID of the public key algorithm.</desc>
+ </attribute>
+ <attribute name="publicKeyAlgorithm" type="wstring" readonly="yes">
+ <desc>The public key algorithm name (if known).</desc>
+ </attribute>
+ <attribute name="subjectPublicKey" type="octet" safearray="yes" readonly="yes">
+ <desc>The raw public key bytes.</desc>
+ </attribute>
+ <attribute name="issuerUniqueIdentifier" type="wstring" readonly="yes">
+ <desc>Unique identifier of the issuer (empty string if not present).</desc>
+ </attribute>
+ <attribute name="subjectUniqueIdentifier" type="wstring" readonly="yes">
+ <desc>Unique identifier of this certificate (empty string if not present).</desc>
+ </attribute>
+ <attribute name="certificateAuthority" type="boolean" readonly="yes">
+ <desc>Whether this certificate is a certificate authority. Will return E_FAIL
+ if this attribute is not present.
+ </desc>
+ </attribute>
+ <attribute name="keyUsage" type="unsigned long" readonly="yes">
+ <desc>Key usage mask. Will return 0 if not present.</desc>
+ </attribute>
+ <attribute name="extendedKeyUsage" type="wstring" safearray="yes" readonly="yes">
+ <desc>Array of dotted extended key usage OIDs. Empty array if not present.</desc>
+ </attribute>
+ <attribute name="rawCertData" type="octet" safearray="yes" readonly="yes">
+ <desc>The raw certificate bytes.</desc>
+ </attribute>
+ <attribute name="selfSigned" type="boolean" readonly="yes">
+ <desc>Set if self signed certificate.</desc>
+ </attribute>
+
+ <!-- The following is subject to the parent object views. -->
+ <attribute name="trusted" type="boolean" readonly="yes">
+ <desc>Set if the certificate is trusted (by the parent object).</desc>
+ </attribute>
+ <attribute name="expired" type="boolean" readonly="yes"> <!-- isCurrentlyExpired is clearer than isCurrentlyValid. -->
+ <desc>Set if the certificate has expired (relevant to the parent object)/</desc>
+ </attribute>
+
+ <method name="isCurrentlyExpired">
+ <desc>
+ Tests if the certificate has expired at the present time according to
+ the X.509 validity of the certificate.</desc>
+ <param name="result" type="boolean" dir="return" />
+ </method>
+
+ <method name="queryInfo">
+ <desc>Way to extend the interface.</desc>
+ <param name="what" type="long" dir="in"/>
+ <param name="result" type="wstring" dir="return" />
+ </method>
+
+ </interface>
+
+
+ <!--
+ // IAppliance
+ /////////////////////////////////////////////////////////////////////////
+ -->
+ <interface
+ name="IAppliance" extends="$unknown"
+ uuid="86a98347-7619-41aa-aece-b21ac5c1a7e6"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="7" reservedAttributes="8"
+ >
+ <desc>
+ Represents a platform-independent appliance in OVF format. An instance of this is returned
+ by <link to="IVirtualBox::createAppliance" />, which can then be used to import and export
+ virtual machines within an appliance with VirtualBox.
+
+ The OVF standard suggests two different physical file formats:
+
+ <ol>
+ <li>If the appliance is distributed as a set of files, there must be at least one XML descriptor
+ file that conforms to the OVF standard and carries an <tt>.ovf</tt> file extension. If
+ this descriptor file references other files such as disk images, as OVF appliances typically
+ do, those additional files must be in the same directory as the descriptor file.
+ </li>
+
+ <li>If the appliance is distributed as a single file, it must be in TAR format and have the
+ <tt>.ova</tt> file extension. This TAR file must then contain at least the OVF descriptor
+ files and optionally other files.
+
+ At this time, VirtualBox does not not yet support the packed (TAR) variant; support will
+ be added with a later version.
+ </li>
+ </ol>
+
+ <b>Importing</b> an OVF appliance into VirtualBox as instances of
+ <link to="IMachine" /> involves the following sequence of API calls:
+
+ <ol>
+ <li>Call <link to="IVirtualBox::createAppliance" />. This will create an empty IAppliance object.
+ </li>
+
+ <li>On the new object, call <link to="#read" /> with the full path of the OVF file you
+ would like to import. So long as this file is syntactically valid, this will succeed
+ and fill the appliance object with the parsed data from the OVF file.
+ </li>
+
+ <li>Next, call <link to="#interpret" />, which analyzes the OVF data and sets up the
+ contents of the IAppliance attributes accordingly. These can be inspected by a
+ VirtualBox front-end such as the GUI, and the suggestions can be displayed to the
+ user. In particular, the <link to="#virtualSystemDescriptions" /> array contains
+ instances of <link to="IVirtualSystemDescription" /> which represent the virtual
+ systems (machines) in the OVF, which in turn describe the virtual hardware prescribed
+ by the OVF (network and hardware adapters, virtual disk images, memory size and so on).
+ The GUI can then give the user the option to confirm and/or change these suggestions.
+ </li>
+
+ <li>If desired, call <link to="IVirtualSystemDescription::setFinalValues" /> for each
+ virtual system (machine) to override the suggestions made by the <link to="#interpret" /> routine.
+ </li>
+
+ <li>Finally, call <link to="#importMachines" /> to create virtual machines in
+ VirtualBox as instances of <link to="IMachine" /> that match the information in the
+ virtual system descriptions. After this call succeeded, the UUIDs of the machines created
+ can be found in the <link to="#machines" /> array attribute.
+ </li>
+ </ol>
+
+ <b>Exporting</b> VirtualBox machines into an OVF appliance involves the following steps:
+
+ <ol>
+ <li>As with importing, first call <link to="IVirtualBox::createAppliance" /> to create
+ an empty IAppliance object.
+ </li>
+
+ <li>For each machine you would like to export, call <link to="IMachine::exportTo" />
+ with the IAppliance object you just created. Each such call creates one instance of
+ <link to="IVirtualSystemDescription" /> inside the appliance.
+ </li>
+
+ <li>If desired, call <link to="IVirtualSystemDescription::setFinalValues" /> for each
+ virtual system (machine) to override the suggestions made by the <link to="IMachine::exportTo"/> routine.
+ </li>
+
+ <li>Finally, call <link to="#write" /> with a path specification to have the OVF
+ file written.</li>
+ </ol>
+
+ </desc>
+
+ <attribute name="path" type="wstring" readonly="yes">
+ <desc>Path to the main file of the OVF appliance, which is either the <tt>.ovf</tt> or
+ the <tt>.ova</tt> file passed to <link to="#read" /> (for import) or
+ <link to="#write" /> (for export).
+ This attribute is empty until one of these methods has been called.
+ </desc>
+ </attribute>
+
+ <attribute name="disks" type="wstring" readonly="yes" safearray="yes">
+ <desc>
+ Array of virtual disk definitions. One such description exists for each
+ disk definition in the OVF; each string array item represents one such piece of
+ disk information, with the information fields separated by tab (\\t) characters.
+
+ The caller should be prepared for additional fields being appended to
+ this string in future versions of VirtualBox and therefore check for
+ the number of tabs in the strings returned.
+
+ In the current version, the following eight fields are returned per string
+ in the array:
+
+ <ol>
+ <li>Disk ID (unique string identifier given to disk)</li>
+
+ <li>Capacity (unsigned integer indicating the maximum capacity of the disk)</li>
+
+ <li>Populated size (optional unsigned integer indicating the current size of the
+ disk; can be approximate; -1 if unspecified)</li>
+
+ <li>Format (string identifying the disk format, typically
+ "http://www.vmware.com/specifications/vmdk.html#sparse")</li>
+
+ <li>Reference (where to find the disk image, typically a file name; if empty,
+ then the disk should be created on import)</li>
+
+ <li>Image size (optional unsigned integer indicating the size of the image,
+ which need not necessarily be the same as the values specified above, since
+ the image may be compressed or sparse; -1 if not specified)</li>
+
+ <li>Chunk size (optional unsigned integer if the image is split into chunks;
+ presently unsupported and always -1)</li>
+
+ <li>Compression (optional string equaling "gzip" if the image is gzip-compressed)</li>
+ </ol>
+ </desc>
+ </attribute>
+
+ <attribute name="virtualSystemDescriptions" type="IVirtualSystemDescription" readonly="yes" safearray="yes">
+ <desc> Array of virtual system descriptions. One such description is created
+ for each virtual system (machine) found in the OVF.
+ This array is empty until either <link to="#interpret" /> (for import) or <link to="IMachine::exportTo" />
+ (for export) has been called.
+ </desc>
+ </attribute>
+
+ <attribute name="machines" type="wstring" readonly="yes" safearray="yes">
+ <desc>
+ Contains the UUIDs of the machines created from the information in this appliances. This is only
+ relevant for the import case, and will only contain data after a call to <link to="#importMachines" />
+ succeeded.
+ </desc>
+ </attribute>
+
+ <attribute name="certificate" type="ICertificate" readonly="yes">
+ <desc>
+ The X.509 signing certificate, if the imported OVF was signed, @c null
+ if not signed. This is available after calling <link to="#read"/>.
+ </desc>
+ </attribute>
+
+ <method name="read">
+ <rest request="post" path="/appliances/{applianceid}/actions/"/>
+ <desc>
+ Reads an OVF file into the appliance object.
+
+ This method succeeds if the OVF is syntactically valid and, by itself, without errors. The
+ mere fact that this method returns successfully does not mean that VirtualBox supports all
+ features requested by the appliance; this can only be examined after a call to <link to="#interpret" />.
+ </desc>
+ <param name="file" type="wstring" dir="in">
+ <desc>
+ Name of appliance file to open (either with an <tt>.ovf</tt> or <tt>.ova</tt> extension, depending
+ on whether the appliance is distributed as a set of files or as a single file, respectively).
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="interpret">
+ <rest request="post" path="/appliances/{applianceid}/actions/"/>
+ <desc>
+ Interprets the OVF data that was read when the appliance was constructed. After
+ calling this method, one can inspect the
+ <link to="#virtualSystemDescriptions" /> array attribute, which will then contain
+ one <link to="IVirtualSystemDescription" /> for each virtual machine found in
+ the appliance.
+
+ Calling this method is the second step of importing an appliance into VirtualBox;
+ see <link to="IAppliance" /> for an overview.
+
+ After calling this method, one should call <link to="#getWarnings" /> to find out
+ if problems were encountered during the processing which might later lead to
+ errors.
+ </desc>
+ </method>
+
+ <method name="importMachines">
+ <rest request="post" path="/appliances/{applianceid}/actions/"/>
+ <desc>
+ Imports the appliance into VirtualBox by creating instances of <link to="IMachine" />
+ and other interfaces that match the information contained in the appliance as
+ closely as possible, as represented by the import instructions in the
+ <link to="#virtualSystemDescriptions" /> array.
+
+ Calling this method is the final step of importing an appliance into VirtualBox;
+ see <link to="IAppliance" /> for an overview.
+
+ Since importing the appliance will most probably involve copying and converting
+ disk images, which can take a long time, this method operates asynchronously and
+ returns an IProgress object to allow the caller to monitor the progress.
+
+ After the import succeeded, the UUIDs of the IMachine instances created can be
+ retrieved from the <link to="#machines" /> array attribute.
+ </desc>
+
+ <param name="options" type="ImportOptions" dir="in" safearray="yes">
+ <desc>Options for the importing operation.</desc>
+ </param>
+
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="createVFSExplorer">
+ <desc>Returns a <link to="IVFSExplorer" /> object for the given URI.</desc>
+
+ <param name="URI" type="wstring" dir="in">
+ <desc>The URI describing the file system to use.</desc>
+ </param>
+
+ <param name="explorer" type="IVFSExplorer" dir="return">
+ <desc></desc>
+ </param>
+ </method>
+
+ <method name="write">
+ <rest request="post" path="/appliances/{applianceid}/actions/"/>
+ <desc>
+ Writes the contents of the appliance exports into a new OVF file.
+
+ Calling this method is the final step of exporting an appliance from VirtualBox;
+ see <link to="IAppliance" /> for an overview.
+
+ Since exporting the appliance will most probably involve copying and converting
+ disk images, which can take a long time, this method operates asynchronously and
+ returns an IProgress object to allow the caller to monitor the progress.
+ </desc>
+ <param name="format" type="wstring" dir="in">
+ <desc>
+ Output format, as a string. Currently supported formats are "ovf-0.9", "ovf-1.0",
+ "ovf-2.0" and "opc-1.0"; future versions of VirtualBox may support additional formats.
+ The "opc-1.0" format is for creating tarballs for the Oracle Public Cloud.
+ </desc>
+ </param>
+ <param name="options" type="ExportOptions" dir="in" safearray="yes">
+ <desc>Options for the exporting operation.</desc>
+ </param>
+ <param name="path" type="wstring" dir="in">
+ <desc>
+ Name of appliance file to create. There are certain restrictions with regard
+ to the file name suffix. If the format parameter is "opc-1.0" a <tt>.tar.gz</tt>
+ suffix is required. Otherwise the suffix must either be <tt>.ovf</tt> or
+ <tt>.ova</tt>, depending on whether the appliance is distributed as a set of
+ files or as a single file, respectively.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="getWarnings">
+ <rest request="get" path="/appliances/{applianceid}/configuration/"/>
+ <desc>Returns textual warnings which occurred during execution of <link to="#interpret" />.</desc>
+
+ <param name="warnings" type="wstring" dir="return" safearray="yes">
+ <desc></desc>
+ </param>
+ </method>
+
+ <method name="getPasswordIds">
+ <rest request="get" path="/appliances/{applianceid}/configuration/"/>
+ <desc>
+ Returns a list of password identifiers which must be supplied to import or export
+ encrypted virtual machines.
+ </desc>
+
+ <param name="identifiers" type="wstring" dir="return" safearray="yes">
+ <desc>The list of password identifiers required for export on success.</desc>
+ </param>
+ </method>
+
+ <method name="getMediumIdsForPasswordId">
+ <rest request="get" path="/appliances/{applianceid}/configuration/"/>
+ <desc>
+ Returns a list of medium identifiers which use the given password identifier.
+ </desc>
+
+ <param name="passwordId" type="wstring" dir="in">
+ <desc>The password identifier to get the medium identifiers for.</desc>
+ </param>
+ <param name="identifiers" type="uuid" mod="string" dir="return" safearray="yes">
+ <desc>The list of medium identifiers returned on success.</desc>
+ </param>
+ </method>
+
+ <method name="addPasswords">
+ <rest request="post" path="/appliances/{applianceid}/configuration/"/>
+ <desc>
+ Adds a list of passwords required to import or export encrypted virtual
+ machines.
+ </desc>
+
+ <param name="identifiers" type="wstring" dir="in" safearray="yes">
+ <desc>List of identifiers.</desc>
+ </param>
+
+ <param name="passwords" type="wstring" dir="in" safearray="yes">
+ <desc>List of matching passwords.</desc>
+ </param>
+ </method>
+
+ <method name="createVirtualSystemDescriptions">
+ <rest request="post" path="/appliances/{applianceid}/configuration/"/>
+ <desc>Creates a number of <link to="IVirtualSystemDescription" /> objects and store them
+ in the <link to="#virtualSystemDescriptions" /> array.
+ </desc>
+
+ <param name="requested" type="unsigned long" dir="in">
+ <desc>Requested number of new virtual system description objects</desc>
+ </param>
+
+ <param name="created" type="unsigned long" dir="return">
+ <desc>Actually created number of virtual system description objects</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <enum
+ name="VirtualSystemDescriptionType"
+ uuid="247f8b6f-1042-4c15-8910-3e3c64395eb7"
+ >
+ <desc>Used with <link to="IVirtualSystemDescription" /> to describe the type of
+ a configuration value.</desc>
+
+ <const name="Ignore" value="1" />
+ <const name="OS" value="2" />
+ <const name="Name" value="3" />
+ <const name="Product" value="4" />
+ <const name="Vendor" value="5" />
+ <const name="Version" value="6" />
+ <const name="ProductUrl" value="7" />
+ <const name="VendorUrl" value="8" />
+ <const name="Description" value="9" />
+ <const name="License" value="10" />
+ <const name="Miscellaneous" value="11" />
+ <const name="CPU" value="12" />
+ <const name="Memory" value="13" />
+ <const name="HardDiskControllerIDE" value="14" />
+ <const name="HardDiskControllerSATA" value="15" />
+ <const name="HardDiskControllerSCSI" value="16" />
+ <const name="HardDiskControllerSAS" value="17" />
+ <const name="HardDiskImage" value="18" />
+ <const name="Floppy" value="19" />
+ <const name="CDROM" value="20" />
+ <const name="NetworkAdapter" value="21" />
+ <const name="USBController" value="22" />
+ <const name="SoundCard" value="23" />
+ <const name="SettingsFile" value="24">
+ <desc>Optional, may be unset by the API caller. If this is changed by the
+ API caller it defines the absolute path of the VM settings file and
+ therefore also the VM folder with highest priority.</desc>
+ </const>
+ <const name="BaseFolder" value="25">
+ <desc>Optional, may be unset by the API caller. If set (and
+ <link to="VirtualSystemDescriptionType_SettingsFile"/> is not changed),
+ defines the VM base folder (taking the primary group into account if
+ also set).</desc>
+ </const>
+ <const name="PrimaryGroup" value="26">
+ <desc>Optional, empty by default and may be unset by the API caller.
+ Defines the primary group of the VM after import. May influence the
+ selection of the VM folder. Additional groups may be configured later
+ using <link to="IMachine::groups"/>, after importing.</desc>
+ </const>
+ <const name="CloudInstanceShape" value="27" />
+ <const name="CloudDomain" value="28" />
+ <const name="CloudBootDiskSize" value="29" />
+ <const name="CloudBucket" value="30" />
+ <const name="CloudOCIVCN" value="31" />
+ <const name="CloudPublicIP" value="32" />
+ <const name="CloudProfileName" value="33" />
+ <const name="CloudOCISubnet" value="34" />
+ <const name="CloudKeepObject" value="35" />
+ <const name="CloudLaunchInstance" value="36" />
+ <const name="CloudInstanceId" value="37" />
+ <const name="CloudImageId" value="38" />
+ <const name="CloudInstanceState" value="39" />
+ <const name="CloudImageState" value="40" />
+ <const name="CloudInstanceDisplayName" value="41" />
+ <const name="CloudImageDisplayName" value="42" />
+ <const name="CloudOCILaunchMode" value="43" />
+ <const name="CloudPrivateIP" value="44" />
+ <const name="CloudBootVolumeId" value="45" />
+ <const name="CloudOCIVCNCompartment" value="46" />
+ <const name="CloudOCISubnetCompartment" value="47" />
+ <const name="CloudPublicSSHKey" value="48" />
+ <const name="BootingFirmware" value="49" />
+ <const name="CloudInitScriptPath" value="50" />
+ <const name="CloudCompartmentId" value="51" />
+ <const name="CloudShapeCpus" value="52" />
+ <const name="CloudShapeMemory" value="53" />
+ <const name="HardDiskControllerVirtioSCSI" value="60" />
+ </enum>
+
+ <enum
+ name="VirtualSystemDescriptionValueType"
+ uuid="56d9403f-3425-4118-9919-36f2a9b8c77c"
+ >
+ <desc>Used with <link to="IVirtualSystemDescription::getValuesByType" /> to describe the value
+ type to fetch.</desc>
+
+ <const name="Reference" value="1" />
+ <const name="Original" value="2" />
+ <const name="Auto" value="3" />
+ <const name="ExtraConfig" value="4" />
+
+ </enum>
+
+ <interface
+ name="IVirtualSystemDescription" extends="$unknown"
+ uuid="01510f40-c196-4d26-b8db-4c8c389f1f82"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="4"
+ >
+
+ <desc>
+ Represents one virtual system (machine) in an appliance. This interface is used in
+ the <link to="IAppliance::virtualSystemDescriptions" /> array. After
+ <link to="IAppliance::interpret" /> has been called, that array contains information
+ about how the virtual systems described in the OVF should best be imported into
+ VirtualBox virtual machines. See <link to="IAppliance" /> for the steps required to
+ import an OVF into VirtualBox.
+ </desc>
+
+ <attribute name="count" type="unsigned long" readonly="yes">
+ <desc>Return the number of virtual system description entries.</desc>
+ </attribute>
+
+ <method name="getDescription">
+ <desc>Returns information about the virtual system as arrays of instruction items. In each array, the
+ items with the same indices correspond and jointly represent an import instruction for VirtualBox.
+
+ The list below identifies the value sets that are possible depending on the
+ <link to="VirtualSystemDescriptionType" /> enum value in the array item in @a aTypes[]. In each case,
+ the array item with the same index in @a OVFValues[] will contain the original value as contained
+ in the OVF file (just for informational purposes), and the corresponding item in @a aVBoxValues[]
+ will contain a suggested value to be used for VirtualBox. Depending on the description type,
+ the @a aExtraConfigValues[] array item may also be used.
+
+ <ul>
+ <li>
+ "OS": the guest operating system type. There must be exactly one such array item on import. The
+ corresponding item in @a aVBoxValues[] contains the suggested guest operating system for VirtualBox.
+ This will be one of the values listed in <link to="IVirtualBox::guestOSTypes" />. The corresponding
+ item in @a OVFValues[] will contain a numerical value that described the operating system in the OVF.
+ </li>
+ <li>
+ "Name": the name to give to the new virtual machine. There can be at most one such array item;
+ if none is present on import, then an automatic name will be created from the operating system
+ type. The corresponding item im @a OVFValues[] will contain the suggested virtual machine name
+ from the OVF file, and @a aVBoxValues[] will contain a suggestion for a unique VirtualBox
+ <link to="IMachine" /> name that does not exist yet.
+ </li>
+ <li>
+ "Description": an arbitrary description.
+ </li>
+ <li>
+ "License": the EULA section from the OVF, if present. It is the responsibility of the calling
+ code to display such a license for agreement; the Main API does not enforce any such policy.
+ </li>
+ <li>
+ Miscellaneous: reserved for future use.
+ </li>
+ <li>
+ "CPU": the number of CPUs. There can be at most one such item, which will presently be ignored.
+ </li>
+ <li>
+ "Memory": the amount of guest RAM, in bytes. There can be at most one such array item; if none
+ is present on import, then VirtualBox will set a meaningful default based on the operating system
+ type.
+ </li>
+ <li>
+ "HardDiskControllerIDE": an IDE hard disk controller. There can be at most two such items.
+ An optional value in @a OVFValues[] and @a aVBoxValues[] can be "PIIX3" or "PIIX4" to specify
+ the type of IDE controller; this corresponds to the ResourceSubType element which VirtualBox
+ writes into the OVF.
+ The matching item in the @a aRefs[] array will contain an integer that items of the "Harddisk"
+ type can use to specify which hard disk controller a virtual disk should be connected to.
+ Note that in OVF, an IDE controller has two channels, corresponding to "master" and "slave"
+ in traditional terminology, whereas the IDE storage controller that VirtualBox supports in
+ its virtual machines supports four channels (primary master, primary slave, secondary master,
+ secondary slave) and thus maps to two IDE controllers in the OVF sense.
+ </li>
+ <li>
+ "HardDiskControllerSATA": an SATA hard disk controller. There can be at most one such item. This
+ has no value in @a OVFValues[] or @a aVBoxValues[].
+ The matching item in the @a aRefs[] array will be used as with IDE controllers (see above).
+ </li>
+ <li>
+ "HardDiskControllerSCSI": a SCSI hard disk controller. There can be at most one such item.
+ The items in @a OVFValues[] and @a aVBoxValues[] will either be "LsiLogic", "BusLogic" or
+ "LsiLogicSas". (Note that in OVF, the LsiLogicSas controller is treated as a SCSI controller
+ whereas VirtualBox considers it a class of storage controllers of its own; see
+ <link to="StorageControllerType" />).
+ The matching item in the @a aRefs[] array will be used as with IDE controllers (see above).
+ </li>
+ <li>
+ "HardDiskImage": a virtual hard disk, most probably as a reference to an image file. There can be an
+ arbitrary number of these items, one for each virtual disk image that accompanies the OVF.
+
+ The array item in @a OVFValues[] will contain the file specification from the OVF file (without
+ a path since the image file should be in the same location as the OVF file itself), whereas the
+ item in @a aVBoxValues[] will contain a qualified path specification to where VirtualBox uses the
+ hard disk image. This means that on import the image will be copied and converted from the
+ "ovf" location to the "vbox" location; on export, this will be handled the other way round.
+
+ The matching item in the @a aExtraConfigValues[] array must contain a string of the following
+ format: "controller=&lt;index&gt;;channel=&lt;c&gt;"
+ In this string, &lt;index&gt; must be an integer specifying the hard disk controller to connect
+ the image to. That number must be the index of an array item with one of the hard disk controller
+ types (HardDiskControllerSCSI, HardDiskControllerSATA, HardDiskControllerIDE).
+ In addition, &lt;c&gt; must specify the channel to use on that controller. For IDE controllers,
+ this can be 0 or 1 for master or slave, respectively. For compatibility with VirtualBox versions
+ before 3.2, the values 2 and 3 (for secondary master and secondary slave) are also supported, but
+ no longer exported. For SATA and SCSI controllers, the channel can range from 0-29.
+ </li>
+ <li>
+ "CDROM": a virtual CD-ROM drive. The matching item in @a aExtraConfigValue[] contains the same
+ attachment information as with "HardDiskImage" items.
+ </li>
+ <li>
+ "CDROM": a virtual floppy drive. The matching item in @a aExtraConfigValue[] contains the same
+ attachment information as with "HardDiskImage" items.
+ </li>
+ <li>
+ "NetworkAdapter": a network adapter. The array item in @a aVBoxValues[] will specify the hardware
+ for the network adapter, whereas the array item in @a aExtraConfigValues[] will have a string
+ of the "type=&lt;X&gt;" format, where &lt;X&gt; must be either "NAT" or "Bridged".
+ </li>
+ <li>
+ "USBController": a USB controller. There can be at most one such item. If, and only if, such an
+ item is present, USB support will be enabled for the new virtual machine.
+ </li>
+ <li>
+ "SoundCard": a sound card. There can be at most one such item. If and only if such an item is
+ present, sound support will be enabled for the new virtual machine. Note that the virtual
+ machine in VirtualBox will always be presented with the standard VirtualBox soundcard, which
+ may be different from the virtual soundcard expected by the appliance.
+ </li>
+ </ul>
+
+ </desc>
+
+ <param name="types" type="VirtualSystemDescriptionType" dir="out" safearray="yes">
+ <desc></desc>
+ </param>
+
+ <param name="refs" type="wstring" dir="out" safearray="yes">
+ <desc></desc>
+ </param>
+
+ <param name="OVFValues" type="wstring" dir="out" safearray="yes">
+ <desc></desc>
+ </param>
+
+ <param name="VBoxValues" type="wstring" dir="out" safearray="yes">
+ <desc></desc>
+ </param>
+
+ <param name="extraConfigValues" type="wstring" dir="out" safearray="yes">
+ <desc></desc>
+ </param>
+
+ </method>
+
+ <method name="getDescriptionByType">
+ <desc>This is the same as <link to="#getDescription" /> except that you can specify which types
+ should be returned.</desc>
+
+ <param name="type" type="VirtualSystemDescriptionType" dir="in">
+ <desc></desc>
+ </param>
+
+ <param name="types" type="VirtualSystemDescriptionType" dir="out" safearray="yes">
+ <desc></desc>
+ </param>
+
+ <param name="refs" type="wstring" dir="out" safearray="yes">
+ <desc></desc>
+ </param>
+
+ <param name="OVFValues" type="wstring" dir="out" safearray="yes">
+ <desc></desc>
+ </param>
+
+ <param name="VBoxValues" type="wstring" dir="out" safearray="yes">
+ <desc></desc>
+ </param>
+
+ <param name="extraConfigValues" type="wstring" dir="out" safearray="yes">
+ <desc></desc>
+ </param>
+
+ </method>
+
+ <method name="removeDescriptionByType">
+ <desc>Delete all records which are equal to the passed type from the list</desc>
+
+ <param name="type" type="VirtualSystemDescriptionType" dir="in">
+ <desc></desc>
+ </param>
+ </method>
+
+ <method name="getValuesByType">
+ <desc>This is the same as <link to="#getDescriptionByType" /> except that you can specify which
+ value types should be returned. See <link to="VirtualSystemDescriptionValueType" /> for possible
+ values.</desc>
+
+ <param name="type" type="VirtualSystemDescriptionType" dir="in">
+ <desc></desc>
+ </param>
+
+ <param name="which" type="VirtualSystemDescriptionValueType" dir="in">
+ <desc></desc>
+ </param>
+
+ <param name="values" type="wstring" dir="return" safearray="yes">
+ <desc></desc>
+ </param>
+
+ </method>
+
+ <method name="setFinalValues">
+ <desc>
+ This method allows the appliance's user to change the configuration for the virtual
+ system descriptions. For each array item returned from <link to="#getDescription" />,
+ you must pass in one boolean value and one configuration value.
+
+ Each item in the boolean array determines whether the particular configuration item
+ should be enabled.
+ You can only disable items of the types HardDiskControllerIDE, HardDiskControllerSATA,
+ HardDiskControllerSCSI, HardDiskImage, CDROM, Floppy, NetworkAdapter, USBController
+ and SoundCard.
+
+ For the "vbox" and "extra configuration" values, if you pass in the same arrays
+ as returned in the aVBoxValues and aExtraConfigValues arrays from <link to="#getDescription"/>,
+ the configuration remains unchanged. Please see the documentation for <link to="#getDescription"/>
+ for valid configuration values for the individual array item types. If the
+ corresponding item in the aEnabled array is @c false, the configuration value is ignored.
+ </desc>
+
+ <param name="enabled" type="boolean" dir="in" safearray="yes">
+ <desc></desc>
+ </param>
+
+ <param name="VBoxValues" type="wstring" dir="in" safearray="yes">
+ <desc></desc>
+ </param>
+
+ <param name="extraConfigValues" type="wstring" dir="in" safearray="yes">
+ <desc></desc>
+ </param>
+ </method>
+
+ <method name="addDescription">
+ <desc>
+ This method adds an additional description entry to the stack of already
+ available descriptions for this virtual system. This is handy for writing
+ values which aren't directly supported by VirtualBox. One example would
+ be the License type of <link to="VirtualSystemDescriptionType" />.
+ </desc>
+
+ <param name="type" type="VirtualSystemDescriptionType" dir="in">
+ <desc></desc>
+ </param>
+
+ <param name="VBoxValue" type="wstring" dir="in">
+ <desc></desc>
+ </param>
+
+ <param name="extraConfigValue" type="wstring" dir="in">
+ <desc></desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <!--
+ // IUnattended
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IUnattended" extends="$unknown"
+ uuid="6f89464f-7773-436a-a4df-592e4e537fa0"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="16"
+ >
+
+ <desc>
+ The IUnattended interface represents the pipeline for preparing
+ the Guest OS for fully automated install.
+
+ The typical workflow is:
+ <ol>
+ <li>Call <link to="IVirtualBox::createUnattendedInstaller"/> to create the object</li>
+ <li>Set <link to="IUnattended::isoPath"/> and call <link to="IUnattended::detectIsoOS"/></li>
+ <li>Create, configure and register a machine according to <link to="IUnattended::detectedOSTypeId"/>
+ and the other detectedOS* attributes.</li>
+ <li>Set <link to="IUnattended::machine"/> to the new IMachine instance.</li>
+ <li>Set the other IUnattended attributes as desired.</li>
+ <li>Call <link to="IUnattended::prepare"/> for the object to check the
+ attribute values and create an internal installer instance.</li>
+ <li>Call <link to="IUnattended::constructMedia"/> to create additional
+ media files (ISO/floppy) needed.</li>
+ <li>Call <link to="IUnattended::reconfigureVM"/> to reconfigure the VM
+ with the installation ISO, additional media files and whatnot </li>
+ <li>Optionally call <link to="IUnattended::done"/> to destroy the internal
+ installer and allow restarting from the second step.</li>
+ </ol>
+
+ Note! Step two is currently not implemented.
+ </desc>
+
+ <attribute name="isoPath" type="wstring">
+ <desc>
+ Guest operating system ISO image
+ </desc>
+ </attribute>
+
+ <attribute name="machine" type="IMachine">
+ <desc>
+ The associated machine object.
+
+ This must be set before <link to="IUnattended::prepare"/> is called.
+ The VM must be registered.
+ </desc>
+ </attribute>
+
+ <attribute name="user" type="wstring">
+ <desc>
+ Assign an user login name.
+ </desc>
+ </attribute>
+
+ <attribute name="password" type="wstring">
+ <desc>
+ Assign a password to the user. The password is the same for both
+ normal user and for Administrator / 'root' accounts.
+ </desc>
+ </attribute>
+
+ <attribute name="fullUserName" type="wstring">
+ <desc>
+ The full name of the user. This is optional and defaults to
+ <link to="IUnattended::user"/>. Please note that not all guests picks
+ up this attribute.
+ </desc>
+ </attribute>
+
+ <attribute name="productKey" type="wstring">
+ <desc>
+ Any key which is used as authorization of access to install genuine OS
+ </desc>
+ </attribute>
+
+ <attribute name="additionsIsoPath" type="wstring">
+ <desc>
+ Guest Additions ISO image path. This defaults to
+ <link to="ISystemProperties::defaultAdditionsISO"/> when the Unattended
+ object is instantiated.
+
+ This property is ignored when <link to="IUnattended::installGuestAdditions"/> is false.
+ </desc>
+ </attribute>
+
+ <attribute name="installGuestAdditions" type="boolean">
+ <desc>
+ Indicates whether the Guest Additions should be installed or not.
+
+ Setting this to false does not affect additions shipped with the linux
+ distribution, only the installation of additions pointed to by
+ <link to="IUnattended::additionsIsoPath"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="validationKitIsoPath" type="wstring">
+ <desc>
+ VirtualBox ValidationKit ISO image path. This is used when
+ <link to="IUnattended::installTestExecService"/> is set to true.
+ </desc>
+ </attribute>
+
+ <attribute name="installTestExecService" type="boolean">
+ <desc>
+ Indicates whether the test execution service (TXS) from the VBox
+ ValidationKit should be installed.
+
+ The TXS binary will be taken from the ISO indicated by
+ <link to="IUnattended::validationKitIsoPath"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="timeZone" type="wstring">
+ <desc>
+ The guest time zone specifier.
+
+ This is unfortunately guest OS specific.
+
+ Windows XP and earlier takes the index number from this table:
+ https://support.microsoft.com/en-gb/help/973627/microsoft-time-zone-index-values
+
+ Windows Vista and later takes the time zone string from this table:
+ https://technet.microsoft.com/en-us/library/cc749073(v=ws.10).aspx
+
+ Linux usually takes the TZ string from this table:
+ https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
+
+ The default is currently UTC/GMT, but this may change to be same as
+ the host later.
+
+ TODO: Investigate automatic mapping between linux and the two windows
+ time zone formats.
+ TODO: Take default from host (this requires mapping).
+ </desc>
+ </attribute>
+
+ <attribute name="locale" type="wstring">
+ <desc>
+ The 5 letter locale identifier, no codesets or such.
+
+ The format is two lower case language letters (ISO 639-1), underscore ('_'),
+ and two upper case country letters (ISO 3166-1 alpha-2). For instance
+ 'en_US', 'de_DE', or 'ny_NO'.
+
+ The default is taken from the host if possible, with 'en_US' as fallback.
+ </desc>
+ </attribute>
+
+ <attribute name="language" type="wstring">
+ <desc>
+ This is more or less a Windows specific setting for choosing the UI language
+ setting of the installer.
+
+ The value should be from the list available via <link to="IUnattended::detectedOSLanguages"/>.
+ The typical format is {language-code}-{COUNTRY} but windows may also use
+ {16-bit code}:{32-bit code} or insert another component between the language
+ and country codes. We consider the format guest OS specific.
+
+ Note that it is crucial that this is correctly specified for Windows
+ installations. If an unsupported value is given the installer will ask
+ for an installation language and wait for user input. Best to leave it
+ to the default value.
+
+ The default is the first one from <link to="IUnattended::detectedOSLanguages"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="country" type="wstring">
+ <desc>
+ The 2 upper case letter country identifier, ISO 3166-1 alpha-2.
+
+ This is used for mirrors and such.
+
+ The default is taken from the host when possible, falling back on
+ <link to="IUnattended::locale"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="proxy" type="wstring">
+ <desc>
+ Proxy incantation to pass on to the guest OS installer.
+
+ This is important to get right if the guest OS installer is of the type
+ that goes online to fetch the packages (e.g. debian-*-netinst.iso) or
+ to fetch updates during the install process.
+
+ Format: [schema=]schema://[login[:password]@]proxy[:port][;...]
+
+ The default is taken from the host proxy configuration (once implemented).
+ </desc>
+ </attribute>
+
+ <attribute name="packageSelectionAdjustments" type="wstring">
+ <desc>
+ Guest OS specific package selection adjustments.
+
+ This is a semicolon separated list of keywords, and later maybe guest OS
+ package specifiers. Currently the 'minimal' is the only recognized value,
+ and this only works with a selection of linux installers.
+ </desc>
+ </attribute>
+
+ <attribute name="hostname" type="wstring">
+ <desc>
+ The fully qualified guest hostname.
+
+ This defaults to machine-name + ".myguest.virtualbox.org", though it may
+ change to the host domain name later.
+ </desc>
+ </attribute>
+
+ <attribute name="auxiliaryBasePath" type="wstring">
+ <desc>
+ The path + basename for auxiliary files generated by the unattended
+ installation. This defaults to the VM folder + Unattended + VM UUID.
+
+ The files which gets generated depends on the OS being installed. When
+ installing Windows there is currently only a auxiliaryBasePath + "floppy.img"
+ being created. But for linux, a "cdrom.viso" and one or more configuration
+ files are generate generated.
+ </desc>
+ </attribute>
+
+ <attribute name="imageIndex" type="unsigned long">
+ <desc>
+ The image index on installation CD/DVD used to install. This index should be
+ one of the indices of <link to="IUnattended::detectedImageIndices"/>.
+
+ Changing this after ISO detection may cause
+ <link to="IUnattended::detectedOSTypeId"/>, <link to="IUnattended::detectedOSVersion"/>,
+ <link to="IUnattended::detectedOSFlavor"/>, <link to="IUnattended::detectedOSLanguages"/>,
+ and <link to="IUnattended::detectedOSHints"/> to be updated with values
+ specific to the selected image.
+
+ Used only with Windows installation CD/DVD:
+ https://technet.microsoft.com/en-us/library/cc766022%28v=ws.10%29.aspx
+ </desc>
+ </attribute>
+
+ <attribute name="scriptTemplatePath" type="wstring">
+ <desc>
+ The unattended installation script template file.
+
+ The template default is based on the guest OS type and is determined by the
+ internal installer when when <link to="IUnattended::prepare"/> is invoked.
+ Most users will want the defaults.
+
+ After <link to="IUnattended::prepare"/> is called, it can be read to see
+ which file is being used.
+ </desc>
+ </attribute>
+
+ <attribute name="postInstallScriptTemplatePath" type="wstring">
+ <desc>
+ The post installation (shell/batch) script template file.
+
+ The template default is based on the guest OS type and is determined by the
+ internal installer when when <link to="IUnattended::prepare"/> is invoked.
+ Most users will want the defaults.
+
+ After <link to="IUnattended::prepare"/> is called, it can be read to see
+ which file is being used.
+ </desc>
+ </attribute>
+
+ <attribute name="postInstallCommand" type="wstring">
+ <desc>
+ Custom post installation command.
+
+ Exactly what is expected as input here depends on the guest OS installer
+ and the post installation script template (see
+ <link to="IUnattended::postInstallScriptTemplatePath"/>).
+ Most users will not need to set this attribute.
+ </desc>
+ </attribute>
+
+ <attribute name="extraInstallKernelParameters" type="wstring">
+ <desc>
+ Extra kernel arguments passed to the install kernel of some guests.
+
+ This is currently only picked up by linux guests. The exact parameters
+ are specific to the guest OS being installed of course.
+
+ After <link to="IUnattended::prepare"/> is called, it can be read to see
+ which parameters are being used.
+ </desc>
+ </attribute>
+
+ <attribute name="detectedOSTypeId" type="wstring" readonly="yes">
+ <desc>
+ The detected OS type ID (<link to="IGuestOSType::id"/>).
+
+ Set by <link to="IUnattended::detectIsoOS"/> or <link to="IUnattended::prepare"/>.
+
+ Not yet implemented.
+ </desc>
+ </attribute>
+
+ <attribute name="detectedOSVersion" type="wstring" readonly="yes">
+ <desc>
+ The detected OS version string.
+
+ Set by <link to="IUnattended::detectIsoOS"/> or <link to="IUnattended::prepare"/>.
+
+ Not yet implemented.
+ </desc>
+ </attribute>
+
+ <attribute name="detectedOSFlavor" type="wstring" readonly="yes">
+ <desc>
+ The detected OS flavor (e.g. server, desktop, etc)
+
+ Set by <link to="IUnattended::detectIsoOS"/> or <link to="IUnattended::prepare"/>.
+
+ Not yet implemented.
+ </desc>
+ </attribute>
+
+ <attribute name="detectedOSLanguages" type="wstring" readonly="yes">
+ <desc>
+ The space separated list of (Windows) installation UI languages we detected (lang.ini).
+
+ The language specifier format is specific to the guest OS. They are
+ used to set <link to="IUnattended::language"/>.
+
+ Set by <link to="IUnattended::detectIsoOS"/> or <link to="IUnattended::prepare"/>.
+
+ Partially implemented.
+ </desc>
+ </attribute>
+
+ <attribute name="detectedOSHints" type="wstring" readonly="yes">
+ <desc>
+ Space separated list of other stuff detected about the OS and the
+ installation ISO.
+
+ Set by <link to="IUnattended::detectIsoOS"/> or <link to="IUnattended::prepare"/>.
+
+ Not yet implemented.
+ </desc>
+ </attribute>
+
+ <attribute name="detectedImageNames" type="wstring" readonly="yes" safearray="yes">
+ <desc>
+ A list of names of the images detected from install.wim file of a Windows Vista
+ or later ISO. This array is parallel to <link to="IUnattended::detectedImageIndices"/>.
+ This will be empty for older Windows ISOs and non-Windows ISOs.
+ </desc>
+ </attribute>
+
+ <attribute name="detectedImageIndices" type="unsigned long" readonly="yes" safearray="yes">
+ <desc>
+ A list of image indexes detected from install.wim file of a Windows ISO. This array
+ is parallel to <link to="IUnattended::detectedImageNames"/>. <link to="IUnattended::imageIndex"/>
+ should be set to one of these indices for Vista and later Windows ISOs.
+ </desc>
+ </attribute>
+
+ <attribute name="isUnattendedInstallSupported" type="boolean" readonly="yes">
+ <desc>
+ Checks the detected OS type and version against a set of rules and returns
+ whether unattended install is supported or not. Note that failing detecting
+ OS type from the ISO causes this attribute to be false by default.
+ </desc>
+ </attribute>
+
+ <attribute name="avoidUpdatesOverNetwork" type="boolean">
+ <desc>
+ When set to true installation is configured to abstain from
+ using network to update/get data. Especially useful when network
+ is not available (as in our test boxes).
+ </desc>
+ </attribute>
+
+ <method name="detectIsoOS">
+ <desc>
+ Detects the OS on the ISO given by <link to="IUnattended::isoPath"/> and sets
+ <link to="IUnattended::detectedOSTypeId"/>, <link to="IUnattended::detectedOSVersion"/>
+ <link to="IUnattended::detectedOSFlavor"/>, <link to="IUnattended::detectedOSLanguages"/>,
+ <link to="IUnattended::detectedOSHints"/>, <link to="IUnattended::detectedImageNames"/>,
+ and <link to="IUnattended::detectedImageIndices"/>,
+
+ Not really yet implemented.
+ </desc>
+ </method>
+
+ <method name="prepare">
+ <desc>
+ Prepare for running the unattended process of installation.
+
+ This will perform <link to="IUnattended::detectIsoOS"/> if not yet called on the
+ current <link to="IUnattended::isoPath"/> value. It may then may cause
+ <link to="IUnattended::detectedOSTypeId"/>, <link to="IUnattended::detectedOSVersion"/>,
+ <link to="IUnattended::detectedOSFlavor"/>, <link to="IUnattended::detectedOSLanguages"/>,
+ and <link to="IUnattended::detectedOSHints"/> to be updated with values
+ specific to the image selected by <link to="IUnattended::imageIndex"/> if the ISO
+ contains images.
+
+ This method will then instantiate the installer based on the detected guest type
+ (see <link to="IUnattended::detectedOSTypeId"/>).
+ </desc>
+ </method>
+
+ <method name="constructMedia">
+ <desc>
+ Constructors the necessary ISO/VISO/Floppy images, with unattended scripts
+ and all necessary bits on them.
+ </desc>
+ </method>
+
+ <method name="reconfigureVM">
+ <desc>
+ Reconfigures the machine to start the installation.
+
+ This involves mounting the ISOs and floppy images created by
+ <link to="IUnattended::constructMedia"/>, attaching new DVD and floppy
+ drives as necessary, and possibly modifying the boot order.
+ </desc>
+ </method>
+
+ <method name="done">
+ <desc>
+ Done - time to start the VM.
+
+ This deletes the internal installer instance that <link to="IUnattended::prepare"/>
+ created. Before done() is called, it is not possible to start over again
+ from <link to="IUnattended::prepare"/>.
+ </desc>
+ </method>
+
+ </interface>
+
+ <!--
+ // IMachine
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IInternalMachineControl" extends="$unknown"
+ uuid="EA05E40C-CB31-423B-B3B7-A5B19300F40C"
+ internal="yes"
+ wsmap="suppress"
+ >
+
+ <method name="updateState">
+ <desc>
+ Updates the VM state.
+ <note>
+ This operation will also update the settings file with the correct
+ information about the saved state file and delete this file from disk
+ when appropriate.
+ </note>
+ </desc>
+ <param name="state" type="MachineState" dir="in"/>
+ </method>
+
+ <method name="beginPowerUp">
+ <desc>
+ Tells VBoxSVC that <link to="IConsole::powerUp"/> is under ways and
+ gives it the progress object that should be part of any pending
+ <link to="IMachine::launchVMProcess"/> operations. The progress
+ object may be called back to reflect an early cancelation, so some care
+ have to be taken with respect to any cancelation callbacks. The console
+ object will call <link to="IInternalMachineControl::endPowerUp"/>
+ to signal the completion of the progress object.
+ </desc>
+ <param name="progress" type="IProgress" dir="in" />
+ </method>
+
+ <method name="endPowerUp">
+ <desc>
+ Tells VBoxSVC that <link to="IConsole::powerUp"/> has completed.
+ This method may query status information from the progress object it
+ received in <link to="IInternalMachineControl::beginPowerUp"/> and copy
+ it over to any in-progress <link to="IMachine::launchVMProcess"/>
+ call in order to complete that progress object.
+ </desc>
+ <param name="result" type="long" dir="in"/>
+ </method>
+
+ <method name="beginPoweringDown">
+ <desc>
+ Called by the VM process to inform the server it wants to
+ stop the VM execution and power down.
+ </desc>
+ <param name="progress" type="IProgress" dir="out">
+ <desc>
+ Progress object created by VBoxSVC to wait until
+ the VM is powered down.
+ </desc>
+ </param>
+ </method>
+
+ <method name="endPoweringDown">
+ <desc>
+ Called by the VM process to inform the server that powering
+ down previously requested by #beginPoweringDown is either
+ successfully finished or there was a failure.
+
+ <result name="VBOX_E_FILE_ERROR">
+ Settings file not accessible.
+ </result>
+ <result name="VBOX_E_XML_ERROR">
+ Could not parse the settings file.
+ </result>
+
+ </desc>
+
+ <param name="result" type="long" dir="in">
+ <desc>@c S_OK to indicate success.
+ </desc>
+ </param>
+ <param name="errMsg" type="wstring" dir="in">
+ <desc>@c human readable error message in case of failure.
+ </desc>
+ </param>
+ </method>
+
+ <method name="runUSBDeviceFilters">
+ <desc>
+ Asks the server to run USB devices filters of the associated
+ machine against the given USB device and tell if there is
+ a match.
+ <note>
+ Intended to be used only for remote USB devices. Local
+ ones don't require to call this method (this is done
+ implicitly by the Host and USBProxyService).
+ </note>
+ </desc>
+ <param name="device" type="IUSBDevice" dir="in"/>
+ <param name="matched" type="boolean" dir="out"/>
+ <param name="maskedInterfaces" type="unsigned long" dir="out"/>
+ </method>
+
+ <method name="captureUSBDevice">
+ <desc>
+ Requests a capture of the given host USB device.
+ When the request is completed, the VM process will
+ get a <link to="IInternalSessionControl::onUSBDeviceAttach"/>
+ notification.
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in"/>
+ <param name="captureFilename" type="wstring" dir="in"/>
+ </method>
+
+ <method name="detachUSBDevice">
+ <desc>
+ Notification that a VM is going to detach (@a done = @c false) or has
+ already detached (@a done = @c true) the given USB device.
+ When the @a done = @c true request is completed, the VM process will
+ get a <link to="IInternalSessionControl::onUSBDeviceDetach"/>
+ notification.
+ <note>
+ In the @a done = @c true case, the server must run its own filters
+ and filters of all VMs but this one on the detached device
+ as if it were just attached to the host computer.
+ </note>
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in"/>
+ <param name="done" type="boolean" dir="in"/>
+ </method>
+
+ <method name="autoCaptureUSBDevices">
+ <desc>
+ Requests a capture all matching USB devices attached to the host.
+ When the request is completed, the VM process will
+ get a <link to="IInternalSessionControl::onUSBDeviceAttach"/>
+ notification per every captured device.
+ </desc>
+ </method>
+
+ <method name="detachAllUSBDevices">
+ <desc>
+ Notification that a VM that is being powered down. The done
+ parameter indicates whether which stage of the power down
+ we're at. When @a done = @c false the VM is announcing its
+ intentions, while when @a done = @c true the VM is reporting
+ what it has done.
+ <note>
+ In the @a done = @c true case, the server must run its own filters
+ and filters of all VMs but this one on all detach devices as
+ if they were just attached to the host computer.
+ </note>
+ </desc>
+ <param name="done" type="boolean" dir="in"/>
+ </method>
+
+ <method name="onSessionEnd">
+ <desc>
+ Triggered by the given session object when the session is about
+ to close normally.
+ </desc>
+ <param name="session" type="ISession" dir="in">
+ <desc>Session that is being closed</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Used to wait until the corresponding machine is actually
+ dissociated from the given session on the server.
+ Returned only when this session is a direct one.
+ </desc>
+ </param>
+ </method>
+
+ <method name="finishOnlineMergeMedium">
+ <desc>
+ Gets called by <link to="IInternalSessionControl::onlineMergeMedium"/>.
+ All necessary state information is available at the called object.
+ </desc>
+ </method>
+
+ <method name="pullGuestProperties">
+ <desc>
+ Get the list of the guest properties matching a set of patterns along
+ with their values, timestamps and flags and give responsibility for
+ managing properties to the console.
+ </desc>
+ <param name="names" type="wstring" dir="out" safearray="yes">
+ <desc>
+ The names of the properties returned.
+ </desc>
+ </param>
+ <param name="values" type="wstring" dir="out" safearray="yes">
+ <desc>
+ The values of the properties returned. The array entries match the
+ corresponding entries in the @a name array.
+ </desc>
+ </param>
+ <param name="timestamps" type="long long" dir="out" safearray="yes">
+ <desc>
+ The timestamps of the properties returned. The array entries match
+ the corresponding entries in the @a name array.
+ </desc>
+ </param>
+ <param name="flags" type="wstring" dir="out" safearray="yes">
+ <desc>
+ The flags of the properties returned. The array entries match the
+ corresponding entries in the @a name array.
+ </desc>
+ </param>
+ </method>
+ <method name="pushGuestProperty">
+ <desc>
+ Update a single guest property in IMachine.
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>
+ The name of the property to be updated.
+ </desc>
+ </param>
+ <param name="value" type="wstring" dir="in">
+ <desc>
+ The value of the property.
+ </desc>
+ </param>
+ <param name="timestamp" type="long long" dir="in">
+ <desc>
+ The timestamp of the property.
+ </desc>
+ </param>
+ <param name="flags" type="wstring" dir="in">
+ <desc>
+ The flags of the property.
+ </desc>
+ </param>
+ <param name="fWasDeleted" type="boolean" dir="in">
+ <desc>
+ The flag which indicates if property was deleted.
+ </desc>
+ </param>
+ </method>
+
+ <method name="lockMedia">
+ <desc>
+ Locks all media attached to the machine for writing and parents of
+ attached differencing media (if any) for reading. This operation is
+ atomic so that if it fails no media is actually locked.
+
+ This method is intended to be called when the machine is in Starting or
+ Restoring state. The locked media will be automatically unlocked when
+ the machine is powered off or crashed.
+ </desc>
+ </method>
+ <method name="unlockMedia">
+ <desc>
+ Unlocks all media previously locked using
+ <link to="IInternalMachineControl::lockMedia"/>.
+
+ This method is intended to be used with teleportation so that it is
+ possible to teleport between processes on the same machine.
+ </desc>
+ </method>
+
+ <method name="ejectMedium">
+ <desc>
+ Tells VBoxSVC that the guest has ejected the medium associated with
+ the medium attachment.
+ </desc>
+ <param name="attachment" type="IMediumAttachment" dir="in">
+ <desc>
+ The medium attachment where the eject happened.
+ </desc>
+ </param>
+ <param name="newAttachment" type="IMediumAttachment" dir="return">
+ <desc>
+ A new reference to the medium attachment, as the config change can
+ result in the creation of a new instance.
+ </desc>
+ </param>
+ </method>
+
+ <method name="reportVmStatistics">
+ <desc>
+ Passes statistics collected by VM (including guest statistics) to VBoxSVC.
+ </desc>
+ <param name="validStats" type="unsigned long" dir="in">
+ <desc>
+ Mask defining which parameters are valid. For example: 0x11 means
+ that cpuIdle and XXX are valid. Other parameters should be ignored.
+ </desc>
+ </param>
+ <param name="cpuUser" type="unsigned long" dir="in">
+ <desc>Percentage of processor time spent in user mode as seen by the guest.</desc>
+ </param>
+ <param name="cpuKernel" type="unsigned long" dir="in">
+ <desc>Percentage of processor time spent in kernel mode as seen by the guest.</desc>
+ </param>
+ <param name="cpuIdle" type="unsigned long" dir="in">
+ <desc>Percentage of processor time spent idling as seen by the guest.</desc>
+ </param>
+ <param name="memTotal" type="unsigned long" dir="in">
+ <desc>Total amount of physical guest RAM.</desc>
+ </param>
+ <param name="memFree" type="unsigned long" dir="in">
+ <desc>Free amount of physical guest RAM.</desc>
+ </param>
+ <param name="memBalloon" type="unsigned long" dir="in">
+ <desc>Amount of ballooned physical guest RAM.</desc>
+ </param>
+ <param name="memShared" type="unsigned long" dir="in">
+ <desc>Amount of shared physical guest RAM.</desc>
+ </param>
+ <param name="memCache" type="unsigned long" dir="in">
+ <desc>Total amount of guest (disk) cache memory.</desc>
+ </param>
+ <param name="pagedTotal" type="unsigned long" dir="in">
+ <desc>Total amount of space in the page file.</desc>
+ </param>
+ <param name="memAllocTotal" type="unsigned long" dir="in">
+ <desc>Total amount of memory allocated by the hypervisor.</desc>
+ </param>
+ <param name="memFreeTotal" type="unsigned long" dir="in">
+ <desc>Total amount of free memory available in the hypervisor.</desc>
+ </param>
+ <param name="memBalloonTotal" type="unsigned long" dir="in">
+ <desc>Total amount of memory ballooned by the hypervisor.</desc>
+ </param>
+ <param name="memSharedTotal" type="unsigned long" dir="in">
+ <desc>Total amount of shared memory in the hypervisor.</desc>
+ </param>
+ <param name="vmNetRx" type="unsigned long" dir="in">
+ <desc>Network receive rate for VM.</desc>
+ </param>
+ <param name="vmNetTx" type="unsigned long" dir="in">
+ <desc>Network transmit rate for VM.</desc>
+ </param>
+ </method>
+
+ <method name="authenticateExternal">
+ <desc>
+ Verify credentials using the external auth library.
+ </desc>
+ <param name="authParams" type="wstring" dir="in" safearray="yes">
+ <desc>
+ The auth parameters, credentials, etc.
+ </desc>
+ </param>
+ <param name="result" type="wstring" dir="out">
+ <desc>
+ The authentification result.
+ </desc>
+ </param>
+ </method>
+ </interface>
+
+ <interface
+ name="IGraphicsAdapter" extends="$unknown"
+ uuid="f692806f-febe-4049-b476-1292a8e45b09"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ The IGraphicsAdapter interface represents the graphics adapter
+ of the virtual machine.
+ </desc>
+
+ <attribute name="graphicsControllerType" type="GraphicsControllerType">
+ <desc>Graphics controller type.</desc>
+ </attribute>
+
+ <attribute name="VRAMSize" type="unsigned long">
+ <desc>Video memory size in megabytes.</desc>
+ </attribute>
+
+ <attribute name="accelerate3DEnabled" type="boolean" default="false">
+ <desc>
+ This setting determines whether VirtualBox allows this machine to make
+ use of the 3D graphics support available on the host.</desc>
+ </attribute>
+
+ <attribute name="accelerate2DVideoEnabled" type="boolean" default="false">
+ <desc>
+ This setting determines whether VirtualBox allows this machine to make
+ use of the 2D video acceleration support available on the host.</desc>
+ </attribute>
+
+ <attribute name="monitorCount" type="unsigned long">
+ <desc>
+ Number of virtual monitors.
+ <note>
+ Only effective on Windows XP and later guests with
+ Guest Additions installed.
+ </note>
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IBIOSSettings" extends="$unknown"
+ uuid="a0a7f210-b857-4468-be26-c29f36a84345"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="2" reservedAttributes="8"
+ >
+ <desc>
+ The IBIOSSettings interface represents BIOS settings of the virtual
+ machine. This is used only in the <link to="IMachine::BIOSSettings" /> attribute.
+ </desc>
+ <attribute name="logoFadeIn" type="boolean">
+ <desc>Fade in flag for BIOS logo animation.</desc>
+ </attribute>
+
+ <attribute name="logoFadeOut" type="boolean">
+ <desc>Fade out flag for BIOS logo animation.</desc>
+ </attribute>
+
+ <attribute name="logoDisplayTime" type="unsigned long">
+ <desc>BIOS logo display time in milliseconds (0 = default).</desc>
+ </attribute>
+
+ <attribute name="logoImagePath" type="wstring">
+ <desc>
+ Local file system path for external BIOS splash image. Empty string
+ means the default image is shown on boot.
+ </desc>
+ </attribute>
+
+ <attribute name="bootMenuMode" type="BIOSBootMenuMode">
+ <desc>Mode of the BIOS boot device menu.</desc>
+ </attribute>
+
+ <attribute name="ACPIEnabled" type="boolean">
+ <desc>ACPI support flag.</desc>
+ </attribute>
+
+ <attribute name="IOAPICEnabled" type="boolean">
+ <desc>
+ I/O-APIC support flag. If set, VirtualBox will provide an I/O-APIC
+ and support IRQs above 15.
+ </desc>
+ </attribute>
+
+ <attribute name="APICMode" type="APICMode">
+ <desc>
+ APIC mode to set up by the firmware.
+ </desc>
+ </attribute>
+
+ <attribute name="timeOffset" type="long long">
+ <desc>
+ Offset in milliseconds from the host system time. This allows for
+ guests running with a different system date/time than the host.
+ It is equivalent to setting the system date/time in the BIOS except
+ it is not an absolute value but a relative one. Guest Additions
+ time synchronization honors this offset.
+ </desc>
+ </attribute>
+
+ <attribute name="PXEDebugEnabled" type="boolean">
+ <desc>
+ PXE debug logging flag. If set, VirtualBox will write extensive
+ PXE trace information to the release log.
+ </desc>
+ </attribute>
+
+ <attribute name="SMBIOSUuidLittleEndian" type="boolean">
+ <desc>
+ Flag to control whether the SMBIOS system UUID is presented in little endian
+ form to the guest as mandated by the SMBIOS spec chapter 7.2.1.
+ Before VirtualBox version 6.1 it was always presented in big endian form
+ and to retain the old behavior this flag was introduced so it can be changed.
+ VMs created with VBox 6.1 will default to true for this flag.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <enum
+ name="TpmType"
+ uuid="c669b9f7-a547-42b6-8464-636aa53401eb"
+ >
+ <desc>
+ TPM type enumeration.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No TPM present in the virtual machine.</desc>
+ </const>
+ <const name="v1_2" value="1">
+ <desc>A TPM compliant to the 1.2 TCG specification is emulated.</desc>
+ </const>
+ <const name="v2_0" value="2">
+ <desc>A TPM compliant to the 2.0 TCG specification is emulated.</desc>
+ </const>
+ <const name="Host" value="3">
+ <desc>The host TPM is passed through, might not be available on all host platforms.</desc>
+ </const>
+ <const name="Swtpm" value="4">
+ <desc>The VM will attach to an external software TPM emulation compliant to swtpm.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="ITrustedPlatformModule" extends="$unknown"
+ uuid="cf11d345-0241-4ea9-ac4c-c69ed3d674e3"
+ wsmap="managed"
+ reservedMethods="2" reservedAttributes="8"
+ >
+ <desc>
+ The ITrustedPlatformModule interface represents the settings of the virtual
+ machine's trusted platform module. This is used only in the <link to="IMachine::trustedPlatformModule"/> attribute.
+ </desc>
+
+ <attribute name="type" type="TpmType">
+ <desc>
+ Type of TPM configured for the virtual machine.
+ </desc>
+ </attribute>
+
+ <attribute name="location" type="wstring">
+ <desc>
+ Location of the TPM. This is only used for the TpmType_Swtpm type of TPM
+ where the location denotes a <b>hostname:port</b> where to connect to.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <enum
+ name="RecordingDestination"
+ uuid="11E3F06B-DEC1-48B9-BDC4-1E618D72893C"
+ >
+ <desc>
+ Recording destination enumeration.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No destination.</desc>
+ </const>
+ <const name="File" value="1">
+ <desc>Destination is a regular file.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="RecordingFeature"
+ uuid="A7DDC6A5-DAA8-4485-B860-E9F2E98A7794"
+ >
+ <desc>
+ Recording feature flags. More than one flag may be set.
+ </desc>
+
+ <const name="None" value="0x0">
+ <desc>No feature set.</desc>
+ </const>
+ <const name="Video" value="0x1">
+ <desc>Video recording.</desc>
+ </const>
+ <const name="Audio" value="0x2">
+ <desc>Audio recording.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="RecordingAudioCodec"
+ uuid="16c42be8-1713-4717-a8b9-c65a6549fbcd"
+ >
+ <desc>
+ Recording audio codec enumeration.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No codec set.</desc>
+ </const>
+ <const name="WavPCM" value="1">
+ <desc>WAV format, linear PCM, uncompressed.</desc>
+ </const>
+ <const name="MP3" value="2">
+ <desc>MPEG-2 Audio Layer III.</desc>
+ </const>
+ <const name="OggVorbis" value="3">
+ <desc>Ogg Vorbis.</desc>
+ </const>
+ <const name="Opus" value="4">
+ <desc>Opus Audio.</desc>
+ </const>
+ <const name="Other" value="5">
+ <desc>Other codec.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="RecordingCodecDeadline"
+ uuid="53627ae1-31cf-4303-9cd5-0c22fd2637cf"
+ >
+ <desc>
+ Recording codec deadline.
+
+ How (and if at all) this deadline is being implemented depends on the codec being used.
+ </desc>
+
+ <const name="Default" value="0">
+ <desc>Default deadline.</desc>
+ </const>
+ <const name="Realtime" value="1">
+ <desc>Realtime quality, often resulting in poor / basic quality.</desc>
+ </const>
+ <const name="Good" value="2">
+ <desc>Balance between realtime and best deadline.</desc>
+ </const>
+ <const name="Best" value="3">
+ <desc>Best quality, slowest.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="RecordingVideoCodec"
+ uuid="93791063-3e8c-4310-987c-ddb43ff03ffe"
+ >
+ <desc>
+ Recording video codec enumeration.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No codec set.</desc>
+ </const>
+ <const name="MJPEG" value="1">
+ <desc>Motion JPEG.</desc>
+ </const>
+ <const name="H262" value="2">
+ <desc>MPEG-2, Part 2.</desc>
+ </const>
+ <const name="H264" value="3">
+ <desc>Advanced Video Coding (AVC), MPEG-4 Part 10.</desc>
+ </const>
+ <const name="H265" value="4">
+ <desc>High Efficiency Video Coding (HEVC), MPEG-H Part 2.</desc>
+ </const>
+ <const name="H266" value="5">
+ <desc>Versatile Video Coding (VVC), MPEG-I Part 3.</desc>
+ </const>
+ <const name="VP8" value="6">
+ <desc>VP8 codec.</desc>
+ </const>
+ <const name="VP9" value="7">
+ <desc>VP9 codec.</desc>
+ </const>
+ <const name="AV1" value="8">
+ <desc>AOMedia Video 1 codec.</desc>
+ </const>
+ <const name="Other" value="9">
+ <desc>Other codec.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="RecordingVideoScalingMode"
+ uuid="0dd1127d-4f62-4b82-beee-91086a9f1d24"
+ >
+ <desc>
+ Recording video scaling method enumeration.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No scaling performed.
+ The image wil be cropped when the source is bigger than the target size.</desc>
+ </const>
+ <const name="NearestNeighbor" value="1">
+ <desc>Performs scaling via nearest-neighbor interpolation.
+ Not yet implemented.</desc>
+ </const>
+ <const name="Bilinear" value="2">
+ <desc>Performs scaling via bilinear interpolation.
+ Not yet implemented.</desc>
+ </const>
+ <const name="Bicubic" value="3">
+ <desc>Performs scaling via bicubic interpolation.
+ Not yet implemented.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="RecordingRateControlMode"
+ uuid="d07a33ac-b4ea-4917-942d-c03be14fbe2f"
+ >
+ <desc>
+ Recording video rate control mode enumeration.
+ <note>Not all codecs may support all rate control modes.</note>
+ </desc>
+
+ <const name="ABR" value="0">
+ <desc>Average bit rate (ABR). Constrained to a certain size.</desc>
+ </const>
+ <const name="CBR" value="1">
+ <desc>Constant bit rate (CBR). Constrained to a certain size.</desc>
+ </const>
+ <const name="VBR" value="2">
+ <desc>Variable bit rate (VBR). Constrained to a certain quality.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IRecordingScreenSettings" extends="$unknown"
+ uuid="c1844087-ec6b-488d-afbb-c90f6452a04b"
+ wsmap="managed"
+ rest="managed"
+ >
+
+ <desc>
+ The IRecordingScreenSettings interface represents recording settings of a
+ single virtual screen. This is used only in the <link to="IRecordingSettings" />
+ interface.
+ </desc>
+
+ <method name="isFeatureEnabled">
+ <desc>Returns whether a particular recording feature is enabled for this
+ screen or not.</desc>
+ <param name="feature" type="RecordingFeature" dir="in">
+ <desc>Feature to check for.</desc>
+ </param>
+ <param name="enabled" type="boolean" dir="return">
+ <desc>@c true if the feature is enabled, @c false if not.</desc>
+ </param>
+ </method>
+
+ <attribute name="id" type="unsigned long" readonly="yes">
+ <desc>
+ This attribute contains the screen ID bound to these settings.
+ </desc>
+ </attribute>
+
+ <attribute name="enabled" type="boolean" default="false">
+ <desc>
+ This setting determines whether this screen is enabled while recording.
+ </desc>
+ </attribute>
+
+ <attribute name="features" type="RecordingFeature" safearray="yes">
+ <desc>This setting determines all enabled recording features for this
+ screen.</desc>
+ </attribute>
+
+ <attribute name="destination" type="RecordingDestination">
+ <desc>This setting determines the recording destination for this
+ screen.</desc>
+ </attribute>
+
+ <attribute name="filename" type="wstring">
+ <desc>
+ This setting determines the filename VirtualBox uses to save
+ the recorded content. This setting cannot be changed while video
+ recording is enabled.
+ <note>
+ When setting this attribute, the specified path has to be
+ absolute (full path). When reading this attribute, a full path is
+ always returned.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="maxTime" type="unsigned long" default="0">
+ <desc>
+ This setting defines the maximum amount of time in seconds
+ to record. Recording will stop as soon as the defined time
+ interval has elapsed. If this value is zero, recording will not be
+ limited by time. This setting cannot be changed while recording is
+ enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="maxFileSize" type="unsigned long" default="0">
+ <desc>
+ This setting determines the maximal number of recording file
+ size in MB. Recording will stop as soon as the file size has
+ reached the defined value. If this value is zero, recording
+ will not be limited by the file size. This setting cannot be changed
+ while recording is enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="options" type="wstring">
+ <desc>
+ This setting contains any additional recording options required in
+ comma-separated key=value format, which are currently not represented
+ via own attribtues. Consider these options as experimental and mostly
+ for codec-specific settings, and are subject to change. This setting
+ cannot be changed while recording is enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="audioCodec" type="RecordingAudioCodec">
+ <desc>
+ Determines the audio codec to use for encoding the
+ recorded audio data. This setting cannot be changed while recording is
+ enabled.
+
+ <note>
+ Only the Opus codec is supported currently.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="audioRateControlMode" type="RecordingRateControlMode">
+ <desc>
+ Determines the audio rate control mode. This setting cannot be changed
+ while recording is enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="audioDeadline" type="RecordingCodecDeadline" default="Default">
+ <desc>
+ Determines the audio deadline to use.
+ This setting cannot be changed while recording is enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="audioHz" type="unsigned long" default="22050">
+ <desc>
+ Determines the Hertz (Hz) rate of the recorded audio data. This setting
+ cannot be changed while recording is enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="audioBits" type="unsigned long" default="16">
+ <desc>
+ Determines the bits per sample of the recorded audio data. This setting
+ cannot be changed while recording is enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="audioChannels" type="unsigned long" default="2">
+ <desc>
+ Determines the audio channels of the recorded audio data.
+ Specifiy 2 for stereo or 1 for mono. More than stereo (2) channels
+ are not supported at the moment. This setting cannot be changed while
+ recording is enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="videoCodec" type="RecordingVideoCodec">
+ <desc>
+ Determines the video codec to use for encoding the recorded video data.
+ This setting cannot be changed while recording is enabled.
+
+ <note>
+ Only the VP8 codec is supported currently.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="videoDeadline" type="RecordingCodecDeadline" default="Default">
+ <desc>
+ Determines the video deadline to use.
+ This setting cannot be changed while recording is enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="videoWidth" type="unsigned long" default="1024">
+ <desc>
+ Determines the horizontal resolution of the recorded video data. This
+ setting cannot be changed while recording is enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="videoHeight" type="unsigned long" default="768">
+ <desc>
+ Determines the vertical resolution of the recorded video data. This
+ setting cannot be changed while recording is enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="videoRate" type="unsigned long" default="512">
+ <desc>
+ Determines the bitrate in kilobits per second. Increasing this value
+ makes the video look better for the cost of an increased file size or
+ transfer rate. This setting cannot be changed while recording is enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="videoRateControlMode" type="RecordingRateControlMode">
+ <desc>
+ Determines the video rate control mode. This setting cannot be changed
+ while recording is enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="videoFPS" type="unsigned long" default="25">
+ <desc>
+ Determines the maximum number of frames per second (FPS). Frames with
+ a higher frequency will be skipped. Reducing this value increases the
+ number of skipped frames and reduces the file size or transfer rate.
+ This setting cannot be changed while recording is enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="videoScalingMode" type="RecordingVideoScalingMode">
+ <desc>
+ Determines the video scaling mode to use.
+ This setting cannot be changed while recording is enabled.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IRecordingSettings" extends="$unknown"
+ uuid="D88F2A5A-47C7-4A3F-AAE1-1B516817DB41"
+ wsmap="managed"
+ rest="managed"
+ >
+
+ <desc>
+ The IRecordingSettings interface represents recording settings of the virtual
+ machine. This is used only in the <link to="IMachine::recordingSettings" />
+ attribute.
+ </desc>
+
+ <method name="getScreenSettings">
+ <desc>Returns the recording settings for a particular screen.</desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>Screen ID to retrieve recording screen settings for.</desc>
+ </param>
+ <param name="recordScreenSettings" type="IRecordingScreenSettings" dir="return">
+ <desc>Recording screen settings for the requested screen.</desc>
+ </param>
+ </method>
+
+ <attribute name="enabled" type="boolean" default="false">
+ <desc>
+ This setting determines whether VirtualBox uses recording to record a
+ VM session.</desc>
+ </attribute>
+
+ <attribute name="screens" type="IRecordingScreenSettings" readonly="yes" safearray="yes">
+ <desc>
+ This setting returns an array for recording settings of all configured
+ virtual screens.</desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IPCIAddress" extends="$unknown"
+ uuid="c984d15f-e191-400b-840e-970f3dad7296"
+ wsmap="managed"
+ rest="managed"
+ >
+
+ <desc>
+ Address on the PCI bus.
+ </desc>
+
+ <attribute name="bus" type="short">
+ <desc>
+ Bus number.
+ </desc>
+ </attribute>
+
+ <attribute name="device" type="short">
+ <desc>
+ Device number.
+ </desc>
+ </attribute>
+
+ <attribute name="devFunction" type="short">
+ <desc>
+ Device function number.
+ </desc>
+ </attribute>
+
+ <method name="asLong">
+ <desc>
+ Convert PCI address into long.
+ </desc>
+ <param name="result" type="long" dir="return" />
+ </method>
+
+ <method name="fromLong">
+ <desc>
+ Make PCI address from long.
+ </desc>
+ <param name="number" type="long" dir="in" />
+ </method>
+ </interface>
+
+ <interface
+ name="IPCIDeviceAttachment" extends="$unknown"
+ uuid="91f33d6f-e621-4f70-a77e-15f0e3c714d5"
+ wsmap="struct"
+ rest="managed"
+ >
+
+ <desc>
+ Information about PCI attachments.
+ </desc>
+
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>
+ Device name.
+ </desc>
+ </attribute>
+
+ <attribute name="isPhysicalDevice" type="boolean" readonly="yes">
+ <desc>
+ If this is physical or virtual device.
+ </desc>
+ </attribute>
+
+ <attribute name="hostAddress" type="long" readonly="yes">
+ <desc>
+ Address of device on the host, applicable only to host devices.
+ </desc>
+ </attribute>
+
+ <attribute name="guestAddress" type="long" readonly="yes">
+ <desc>
+ Address of device in the guest.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <enum
+ name="SignatureType"
+ uuid="6f6e67ef-9a32-4084-af84-5702679f882a"
+ >
+ <desc>
+ UEFI signature type enumeration.
+ </desc>
+
+ <const name="X509" value="0">
+ <desc>X.509 certificate.</desc>
+ </const>
+ <const name="Sha256" value="1">
+ <desc>SHA256 hash.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="UefiVariableAttributes"
+ uuid="cda505ec-b444-4aef-b55c-b687717bdac5"
+ >
+ <desc>
+ Possible variable attributes. More than one flag may be set.
+ <see><link to="IUefiVariableStore"/></see>
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No attributes set.</desc>
+ </const>
+ <const name="NonVolatile" value="0x01">
+ <desc>Variable is stored in non volatile storage.</desc>
+ </const>
+ <const name="BootServiceAccess" value="0x02">
+ <desc>Variable can be accessed from the boot services.</desc>
+ </const>
+ <const name="RuntimeAccess" value="0x04">
+ <desc>Variable can be accessed from the runtime.</desc>
+ </const>
+ <const name="HwErrorRecord" value="0x08">
+ <desc>Variable contains a hardware error record.</desc>
+ </const>
+ <const name="AuthWriteAccess" value="0x100">
+ <desc>Writing to this variable requires an authenticated source.</desc>
+ </const>
+ <const name="AuthTimeBasedWriteAccess" value="0x200">
+ <desc>Variable was written with a time based authentication.</desc>
+ </const>
+ <const name="AuthAppendWrite" value="0x400">
+ <desc>The variable can be appended only.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IUefiVariableStore" extends="$unknown"
+ uuid="d134c6b6-4479-430d-bb73-68a452ba3e67"
+ wsmap="managed"
+ reservedMethods="10" reservedAttributes="5"
+ >
+ <desc>
+ The IUefiVariableStore interface allows inspecting and manipulating the content
+ of an existing UEFI variable store in a NVRAM file. This is used only in the
+ <link to="INvramStore::uefiVariableStore" /> attribute.
+ </desc>
+
+ <attribute name="secureBootEnabled" type="boolean">
+ <desc>Flag whether secure boot is currently enabled for the VM.</desc>
+ </attribute>
+
+ <method name="addVariable">
+ <desc>Adds a new variable to the non volatile storage area.</desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the variable.</desc>
+ </param>
+ <param name="owner" type="uuid" mod="string" dir="in">
+ <desc>UUID of the variable owner.</desc>
+ </param>
+ <param name="attributes" type="UefiVariableAttributes" safearray="yes" dir="in">
+ <desc>Attributes of the variable.</desc>
+ </param>
+ <param name="data" type="octet" dir="in" safearray="yes">
+ <desc>The variable data.</desc>
+ </param>
+ </method>
+
+ <method name="deleteVariable">
+ <desc>Deletes the given variable from the non volatile storage area.</desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the variable.</desc>
+ </param>
+ <param name="owner" type="uuid" mod="string" dir="in">
+ <desc>UUID of the variable owner.</desc>
+ </param>
+ </method>
+
+ <method name="changeVariable">
+ <desc>Changes the data of the given variable.</desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the variable.</desc>
+ </param>
+ <param name="data" type="octet" dir="in" safearray="yes">
+ <desc>The new variable data.</desc>
+ </param>
+ </method>
+
+ <method name="queryVariableByName">
+ <desc>Queries the variable content variable by the given name.</desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the variable to look for.</desc>
+ </param>
+ <param name="owner" type="uuid" mod="string" dir="out">
+ <desc>UUID of the variable owner returned on success.</desc>
+ </param>
+ <param name="attributes" type="UefiVariableAttributes" safearray="yes" dir="out">
+ <desc>Attributes of the variable.</desc>
+ </param>
+ <param name="data" type="octet" dir="out" safearray="yes">
+ <desc>The variable data returned on success.</desc>
+ </param>
+ </method>
+
+ <method name="queryVariables">
+ <desc>
+ Queries all variables in the non volatile storage and returns their names.
+ </desc>
+ <param name="names" type="wstring" dir="out" safearray="yes">
+ <desc>The variable names returned on success.</desc>
+ </param>
+ <param name="owners" type="uuid" mod="string" dir="out" safearray="yes">
+ <desc>UUID of the variable owners returned on success.</desc>
+ </param>
+ </method>
+
+ <method name="enrollOraclePlatformKey">
+ <desc>
+ Enroll the default platform key from Oracle for enabling Secure Boot.
+ </desc>
+ </method>
+
+ <method name="enrollPlatformKey">
+ <desc>
+ Convenience method to enroll a new platform key (PK) for enabling Secure Boot.
+ </desc>
+ <param name="platformKey" type="octet" safearray="yes" dir="in">
+ <desc>The platform key (PK) to enroll.</desc>
+ </param>
+ <param name="owner" type="uuid" mod="string" dir="in">
+ <desc>UUID of the PK owner.</desc>
+ </param>
+ </method>
+
+ <method name="addKek">
+ <desc>
+ Convenience method to add a new Key Encryption Key (KEK) for Secure Boot.
+ </desc>
+ <param name="keyEncryptionKey" type="octet" safearray="yes" dir="in">
+ <desc>The Key Encryption Key (KEK) to add.</desc>
+ </param>
+ <param name="owner" type="uuid" mod="string" dir="in">
+ <desc>UUID of the KEK owner.</desc>
+ </param>
+ <param name="signatureType" type="SignatureType" dir="in">
+ <desc>Type of the signature.</desc>
+ </param>
+ </method>
+
+ <method name="addSignatureToDb">
+ <desc>
+ Convenience method to add a new entry to the signature database.
+ </desc>
+ <param name="signature" type="octet" safearray="yes" dir="in">
+ <desc>The signature to add.</desc>
+ </param>
+ <param name="owner" type="uuid" mod="string" dir="in">
+ <desc>UUID of the signature owner.</desc>
+ </param>
+ <param name="signatureType" type="SignatureType" dir="in">
+ <desc>Type of the signature.</desc>
+ </param>
+ </method>
+
+ <method name="addSignatureToDbx">
+ <desc>
+ Convenience method to add a new entry to the forbidden signature database.
+ </desc>
+ <param name="signature" type="octet" safearray="yes" dir="in">
+ <desc>The signature to add.</desc>
+ </param>
+ <param name="owner" type="uuid" mod="string" dir="in">
+ <desc>UUID of the signature owner.</desc>
+ </param>
+ <param name="signatureType" type="SignatureType" dir="in">
+ <desc>Type of the signature.</desc>
+ </param>
+ </method>
+
+ <method name="enrollDefaultMsSignatures">
+ <desc>
+ Convenience method to enroll the standard Microsoft KEK and signatures
+ in the signature databases.
+ </desc>
+ </method>
+
+ </interface>
+
+ <interface
+ name="INvramStore" extends="$unknown"
+ uuid="5bfd8965-b81b-469f-8649-f717ce97a5d5"
+ wsmap="managed"
+ >
+
+ <desc>
+ Provides access to the NVRAM store collecting all permanent states from different sources
+ (UEFI, TPM, etc.).
+ </desc>
+
+ <attribute name="nonVolatileStorageFile" type="wstring" readonly="yes">
+ <desc>
+ The location of the file storing the non-volatile memory content when
+ the VM is powered off. The file does not always exist.
+ </desc>
+ </attribute>
+
+ <attribute name="uefiVariableStore" type="IUefiVariableStore" readonly="yes">
+ <desc>Object to manipulate the data in an existing UEFI variable store.</desc>
+ </attribute>
+
+ <attribute name="keyId" type="wstring" readonly="yes">
+ <desc>
+ Key Id of the password used for encrypting the NVRAM file.
+ Internal use only for now.
+ </desc>
+ </attribute>
+
+ <attribute name="keyStore" type="wstring" readonly="yes">
+ <desc>
+ Key store used for encrypting the NVRAM file.
+ Internal use only for now.
+ </desc>
+ </attribute>
+
+ <method name="initUefiVariableStore">
+ <desc>Initializes the UEFI variable store.</desc>
+ <param name="size" type="unsigned long" dir="in">
+ <desc>
+ Size in bytes of the UEFI variable store. Must be 0 for now to initialize to the
+ default size.
+ </desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <enum
+ name="GraphicsControllerType"
+ uuid="3e009bb0-2b57-4283-a39b-4c363d4f0808"
+ >
+ <desc>Graphics controller type, used with <link to="IGraphicsAdapter::graphicsControllerType" />.
+ </desc>
+ <const name="Null" value="0">
+ <desc>Reserved value, invalid.</desc>
+ </const>
+ <const name="VBoxVGA" value="1">
+ <desc>VirtualBox VGA device.</desc>
+ </const>
+ <const name="VMSVGA" value="2">
+ <desc>VMware SVGA II device.</desc>
+ </const>
+ <const name="VBoxSVGA" value="3">
+ <desc>VirtualBox VGA device with VMware SVGA II extensions.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="CleanupMode"
+ uuid="67897c50-7cca-47a9-83f6-ce8fd8eb5441"
+ >
+ <desc>Cleanup mode, used with <link to="IMachine::unregister" />.
+ </desc>
+ <const name="UnregisterOnly" value="1">
+ <desc>Unregister only the machine, but neither delete snapshots nor detach media.</desc>
+ </const>
+ <const name="DetachAllReturnNone" value="2">
+ <desc>Delete all snapshots and detach all media but return none; this will keep all media registered.</desc>
+ </const>
+ <const name="DetachAllReturnHardDisksOnly" value="3">
+ <desc>Delete all snapshots, detach all media and return hard disks for closing, but not removable media.</desc>
+ </const>
+ <const name="Full" value="4">
+ <desc>Delete all snapshots, detach all media and return all media for closing.</desc>
+ </const>
+ <const name="DetachAllReturnHardDisksAndVMRemovable" value="5">
+ <desc>Delete all snapshots, detach all media and return hard disks and removable locating in the folder of the machine to be deleted and referenced only to this machine, for closing.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="CloneMode"
+ uuid="A7A159FE-5096-4B8D-8C3C-D033CB0B35A8"
+ >
+
+ <desc>
+ Clone mode, used with <link to="IMachine::cloneTo" />.
+ </desc>
+
+ <const name="MachineState" value="1">
+ <desc>Clone the state of the selected machine.</desc>
+ </const>
+ <const name="MachineAndChildStates" value="2">
+ <desc>Clone the state of the selected machine and its child snapshots if present.</desc>
+ </const>
+ <const name="AllStates" value="3">
+ <desc>Clone all states (including all snapshots) of the machine, regardless of the machine object used.</desc>
+ </const>
+
+ </enum>
+
+ <enum
+ name="CloneOptions"
+ uuid="22243f8e-96ab-497c-8cf0-f40a566c630b"
+ >
+
+ <desc>
+ Clone options, used with <link to="IMachine::cloneTo" />.
+ </desc>
+
+ <const name="Link" value="1">
+ <desc>Create a clone VM where all virtual disks are linked to the original VM.</desc>
+ </const>
+ <const name="KeepAllMACs" value="2">
+ <desc>Don't generate new MAC addresses of the attached network adapters.</desc>
+ </const>
+ <const name="KeepNATMACs" value="3">
+ <desc>Don't generate new MAC addresses of the attached network adapters when they are using NAT.</desc>
+ </const>
+ <const name="KeepDiskNames" value="4">
+ <desc>Don't change the disk names.</desc>
+ </const>
+ <const name="KeepHwUUIDs" value="5">
+ <desc>Don't change UUID of the machine hardware.</desc>
+ </const>
+
+ </enum>
+
+ <enum
+ name="AutostopType"
+ uuid="6bb96740-cf34-470d-aab2-2cd48ea2e10e"
+ >
+
+ <desc>
+ Autostop types, used with <link to="IMachine::autostopType" />.
+ </desc>
+
+ <const name="Disabled" value="1">
+ <desc>Stopping the VM during system shutdown is disabled.</desc>
+ </const>
+ <const name="SaveState" value="2">
+ <desc>The state of the VM will be saved when the system shuts down.</desc>
+ </const>
+ <const name="PowerOff" value="3">
+ <desc>The VM is powered off when the system shuts down.</desc>
+ </const>
+ <const name="AcpiShutdown" value="4">
+ <desc>An ACPI shutdown event is generated.</desc>
+ </const>
+
+ </enum>
+
+ <enum
+ name="VMProcPriority"
+ uuid="6fa72dd5-19b7-46ba-bc52-f223c98c7d80"
+ >
+ <desc>
+ Virtual machine process priorities.
+ </desc>
+
+ <const name="Invalid" value="0">
+ <desc>Invalid priority, do not use.</desc>
+ </const>
+ <const name="Default" value="1">
+ <desc>Default process priority determined by the OS.</desc>
+ </const>
+ <const name="Flat" value="2">
+ <desc>
+ Assumes a scheduling policy which puts the process at the default
+ priority and with all threads at the same priority
+ </desc>
+ </const>
+ <const name="Low" value="3">
+ <desc>
+ Assumes a scheduling policy which puts the process mostly below the
+ default priority of the host OS.
+ </desc>
+ </const>
+ <const name="Normal" value="5">
+ <desc>
+ Assume a scheduling policy which shares the CPU resources fairly with
+ other processes running with the default priority of the host OS.
+ </desc>
+ </const>
+ <const name="High" value="6">
+ <desc>
+ Assumes a scheduling policy which puts the task above the default
+ priority of the host OS. This policy might easily cause other tasks
+ in the system to starve.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="IommuType"
+ uuid="f47339cb-b94e-48fe-b507-2900103d7b9f"
+ >
+ <desc>
+ The IOMMU type. This enumeration represents possible
+ values for the <link to="IMachine::iommuType"/> attribute.
+ </desc>
+ <const name="None" value="0">
+ <desc>No IOMMU is present.</desc>
+ </const>
+ <const name="Automatic" value="1">
+ <desc>No IOMMU is present.</desc>
+ </const>
+ <const name="AMD" value="2">
+ <desc>An AMD IOMMU.</desc>
+ </const>
+ <const name="Intel" value="3">
+ <desc>An Intel IOMMU.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IMachine" extends="$unknown"
+ uuid="300763af-5d6b-46e6-aa96-273eac15538a"
+ wsmap="managed"
+ rest="managed"
+ wrap-hint-server-addinterfaces="IInternalMachineControl"
+ wrap-hint-server="manualaddinterfaces"
+ reservedMethods="8" reservedAttributes="16"
+ >
+ <!-- Note! This interface is not compatible between 5.0 and 5.1 as it had too many
+ methods/attributes for midl and the reservedAttributes had to be
+ decreased. Max methods+attributes is 256, so in 6.0 this interface must
+ be refactored a little! In the mean time, take from reservedMethods and
+ reservedAttributes and update the UUID like always. -->
+ <desc>
+ The IMachine interface represents a virtual machine, or guest, created
+ in VirtualBox.
+
+ This interface is used in two contexts. First of all, a collection of
+ objects implementing this interface is stored in the
+ <link to="IVirtualBox::machines"/> attribute which lists all the virtual
+ machines that are currently registered with this VirtualBox
+ installation. Also, once a session has been opened for the given virtual
+ machine (e.g. the virtual machine is running), the machine object
+ associated with the open session can be queried from the session object;
+ see <link to="ISession"/> for details.
+
+ The main role of this interface is to expose the settings of the virtual
+ machine and provide methods to change various aspects of the virtual
+ machine's configuration. For machine objects stored in the
+ <link to="IVirtualBox::machines"/> collection, all attributes are
+ read-only unless explicitly stated otherwise in individual attribute
+ and method descriptions.
+
+ In order to change a machine setting, a session for this machine must be
+ opened using one of the <link to="IMachine::lockMachine" /> or
+ <link to="IMachine::launchVMProcess"/> methods. After the
+ machine has been successfully locked for a session, a mutable machine object
+ needs to be queried from the session object and then the desired settings
+ changes can be applied to the returned object using IMachine attributes and
+ methods. See the <link to="ISession"/> interface description for more
+ information about sessions.
+
+ Note that IMachine does not provide methods to control virtual machine
+ execution (such as start the machine, or power it down) -- these methods
+ are grouped in a separate interface called <link to="IConsole" />.
+
+ <see><link to="ISession"/>, <link to="IConsole"/></see>
+ </desc>
+
+ <attribute name="parent" type="IVirtualBox" readonly="yes" wrap-hint-server="limitedcaller" rest="suppress">
+ <desc>Associated parent object.</desc>
+ </attribute>
+
+ <attribute name="icon" type="octet" safearray="yes">
+ <desc>Overridden VM Icon details.</desc>
+ </attribute>
+
+ <attribute name="accessible" type="boolean" readonly="yes" wrap-hint-server="limitedcaller">
+ <desc>
+ Whether this virtual machine is currently accessible or not.
+
+ A machine is always deemed accessible unless it is registered <i>and</i>
+ its settings file cannot be read or parsed (either because the file itself
+ is unavailable or has invalid XML contents).
+
+ Every time this property is read, the accessibility state of
+ this machine is re-evaluated. If the returned value is @c false,
+ the <link to="#accessError"/> property may be used to get the
+ detailed error information describing the reason of
+ inaccessibility, including XML error messages.
+
+ When the machine is inaccessible, only the following properties
+ can be used on it:
+ <ul>
+ <li><link to="#parent"/></li>
+ <li><link to="#id"/></li>
+ <li><link to="#settingsFilePath"/></li>
+ <li><link to="#accessible"/></li>
+ <li><link to="#accessError"/></li>
+ </ul>
+
+ An attempt to access any other property or method will return
+ an error.
+
+ The only possible action you can perform on an inaccessible
+ machine is to unregister it using the
+ <link to="IMachine::unregister"/> call (or, to check
+ for the accessibility state once more by querying this
+ property).
+
+ <note>
+ In the current implementation, once this property returns
+ @c true, the machine will never become inaccessible
+ later, even if its settings file cannot be successfully
+ read/written any more (at least, until the VirtualBox
+ server is restarted). This limitation may be removed in
+ future releases.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="accessError" type="IVirtualBoxErrorInfo" readonly="yes" wrap-hint-server="limitedcaller">
+ <desc>
+ Error information describing the reason of machine
+ inaccessibility.
+
+ Reading this property is only valid after the last call to
+ <link to="#accessible"/> returned @c false (i.e. the
+ machine is currently inaccessible). Otherwise, a @c null
+ IVirtualBoxErrorInfo object will be returned.
+ </desc>
+ </attribute>
+
+ <attribute name="name" type="wstring">
+ <desc>
+ Name of the virtual machine.
+
+ Besides being used for human-readable identification purposes
+ everywhere in VirtualBox, the virtual machine name is also used
+ as a name of the machine's settings file and as a name of the
+ subdirectory this settings file resides in. Thus, every time you
+ change the value of this property, the settings file will be
+ renamed once you call <link to="#saveSettings"/> to confirm the
+ change. The containing subdirectory will be also renamed, but
+ only if it has exactly the same name as the settings file
+ itself prior to changing this property (for backward compatibility
+ with previous API releases). The above implies the following
+ limitations:
+ <ul>
+ <li>The machine name cannot be empty.</li>
+ <li>The machine name can contain only characters that are valid
+ file name characters according to the rules of the file
+ system used to store VirtualBox configuration.</li>
+ <li>You cannot have two or more machines with the same name
+ if they use the same subdirectory for storing the machine
+ settings files.</li>
+ <li>You cannot change the name of the machine if it is running,
+ or if any file in the directory containing the settings file
+ is being used by another running machine or by any other
+ process in the host operating system at a time when
+ <link to="#saveSettings"/> is called.
+ </li>
+ </ul>
+ If any of the above limitations are hit, <link to="#saveSettings"/>
+ will return an appropriate error message explaining the exact
+ reason and the changes you made to this machine will not be saved.
+
+ Starting with VirtualBox 4.0, a ".vbox" extension of the settings
+ file is recommended, but not enforced. (Previous versions always
+ used a generic ".xml" extension.)
+ </desc>
+ </attribute>
+
+ <attribute name="description" type="wstring">
+ <desc>
+ Description of the virtual machine.
+
+ The description attribute can contain any text and is
+ typically used to describe the hardware and software
+ configuration of the virtual machine in detail (i.e. network
+ settings, versions of the installed software and so on).
+ </desc>
+ </attribute>
+
+ <attribute name="id" type="uuid" mod="string" readonly="yes" wrap-hint-server="limitedcaller">
+ <desc>UUID of the virtual machine.</desc>
+ </attribute>
+
+ <attribute name="groups" type="wstring" safearray="yes">
+ <desc>
+ Array of machine group names of which this machine is a member.
+ <tt>""</tt> and <tt>"/"</tt> are synonyms for the toplevel group. Each
+ group is only listed once, however they are listed in no particular
+ order and there is no guarantee that there are no gaps in the group
+ hierarchy (i.e. <tt>"/group"</tt>,
+ <tt>"/group/subgroup/subsubgroup"</tt> is a valid result).
+ </desc>
+ </attribute>
+
+ <attribute name="OSTypeId" type="wstring">
+ <desc>
+ User-defined identifier of the Guest OS type.
+ You may use <link to="IVirtualBox::getGuestOSType"/> to obtain
+ an IGuestOSType object representing details about the given
+ Guest OS type. All Guest OS types are considered valid, even those
+ which are not known to <link to="IVirtualBox::getGuestOSType"/>.
+ <note>
+ This value may differ from the value returned by
+ <link to="IGuest::OSTypeId"/> if Guest Additions are
+ installed to the guest OS.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="hardwareVersion" type="wstring">
+ <desc>Hardware version identifier. Internal use only for now.</desc>
+ </attribute>
+
+ <attribute name="hardwareUUID" type="uuid" mod="string">
+ <desc>
+ The UUID presented to the guest via memory tables, hardware and guest
+ properties. For most VMs this is the same as the @a id, but for VMs
+ which have been cloned or teleported it may be the same as the source
+ VM. The latter is because the guest shouldn't notice that it was
+ cloned or teleported.
+ </desc>
+ </attribute>
+
+ <attribute name="CPUCount" type="unsigned long">
+ <desc>Number of virtual CPUs in the VM.</desc>
+ </attribute>
+
+ <attribute name="CPUHotPlugEnabled" type="boolean">
+ <desc>
+ This setting determines whether VirtualBox allows CPU
+ hotplugging for this machine.</desc>
+ </attribute>
+
+ <attribute name="CPUExecutionCap" type="unsigned long">
+ <desc>
+ Means to limit the number of CPU cycles a guest can use. The unit
+ is percentage of host CPU cycles per second. The valid range
+ is 1 - 100. 100 (the default) implies no limit.
+ </desc>
+ </attribute>
+
+ <attribute name="CPUIDPortabilityLevel" type="unsigned long">
+ <desc>Virtual CPUID portability level, the higher number the fewer newer
+ or vendor specific CPU feature is reported to the guest (via the CPUID
+ instruction). The default level of zero (0) means that all virtualized
+ features supported by the host is pass thru to the guest. While the
+ three (3) is currently the level supressing the most features.
+
+ Exactly which of the CPUID features are left out by the VMM at which
+ level is subject to change with each major version.
+ </desc>
+ </attribute>
+
+ <attribute name="memorySize" type="unsigned long">
+ <desc>System memory size in megabytes.</desc>
+ </attribute>
+
+ <attribute name="memoryBalloonSize" type="unsigned long">
+ <desc>Memory balloon size in megabytes.</desc>
+ </attribute>
+
+ <attribute name="pageFusionEnabled" type="boolean">
+ <desc>
+ This setting determines whether VirtualBox allows page
+ fusion for this machine (64-bit hosts only).
+ </desc>
+ </attribute>
+
+ <attribute name="graphicsAdapter" type="IGraphicsAdapter" readonly="yes">
+ <desc>Graphics adapter object.</desc>
+ </attribute>
+
+ <attribute name="BIOSSettings" type="IBIOSSettings" readonly="yes">
+ <desc>Object containing all BIOS settings.</desc>
+ </attribute>
+
+ <attribute name="trustedPlatformModule" type="ITrustedPlatformModule" readonly="yes">
+ <desc>Object containing all TPM settings.</desc>
+ </attribute>
+
+ <attribute name="nonVolatileStore" type="INvramStore" readonly="yes">
+ <desc>Object to manipulate data in the non volatile storage file.</desc>
+ </attribute>
+
+ <attribute name="recordingSettings" type="IRecordingSettings" readonly="yes">
+ <desc>Object containing all recording settings.</desc>
+ </attribute>
+
+ <attribute name="firmwareType" type="FirmwareType">
+ <desc>Type of firmware (such as legacy BIOS or EFI), used for initial
+ bootstrap in this VM.</desc>
+ </attribute>
+
+ <attribute name="pointingHIDType" type="PointingHIDType">
+ <desc>Type of pointing HID (such as mouse or tablet) used in this VM.
+ The default is typically "PS2Mouse" but can vary depending on the
+ requirements of the guest operating system.</desc>
+ </attribute>
+
+ <attribute name="keyboardHIDType" type="KeyboardHIDType">
+ <desc>Type of keyboard HID used in this VM.
+ The default is typically "PS2Keyboard" but can vary depending on the
+ requirements of the guest operating system.</desc>
+ </attribute>
+
+ <attribute name="HPETEnabled" type="boolean">
+ <desc>This attribute controls if High Precision Event Timer (HPET) is
+ enabled in this VM. Use this property if you want to provide guests
+ with additional time source, or if guest requires HPET to function correctly.
+ Default is false.</desc>
+ </attribute>
+
+ <attribute name="chipsetType" type="ChipsetType">
+ <desc>Chipset type used in this VM.</desc>
+ </attribute>
+
+ <attribute name="iommuType" type="IommuType">
+ <desc>IOMMU type used in this VM.</desc>
+ </attribute>
+
+ <attribute name="snapshotFolder" type="wstring">
+ <desc>
+ Full path to the directory used to store snapshot data
+ (differencing media and saved state files) of this machine.
+
+ The initial value of this property is
+ <tt>&lt;</tt><link to="#settingsFilePath">
+ path_to_settings_file</link><tt>&gt;/&lt;</tt>
+ <link to="#id">machine_uuid</link>
+ <tt>&gt;</tt>.
+
+ Currently, it is an error to try to change this property on
+ a machine that has snapshots (because this would require to
+ move possibly large files to a different location).
+ A separate method will be available for this purpose later.
+
+ <note>
+ Setting this property to @c null or to an empty string will restore
+ the initial value.
+ </note>
+ <note>
+ When setting this property, the specified path can be
+ absolute (full path) or relative to the directory where the
+ <link to="#settingsFilePath">machine settings file</link>
+ is located. When reading this property, a full path is
+ always returned.
+ </note>
+ <note>
+ The specified path may not exist, it will be created
+ when necessary.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="VRDEServer" type="IVRDEServer" readonly="yes">
+ <desc>VirtualBox Remote Desktop Extension (VRDE) server object.</desc>
+ </attribute>
+
+ <attribute name="emulatedUSBCardReaderEnabled" type="boolean" default="false"/>
+
+ <attribute name="mediumAttachments" type="IMediumAttachment" readonly="yes" safearray="yes">
+ <desc>Array of media attached to this machine.</desc>
+ </attribute>
+
+ <attribute name="USBControllers" type="IUSBController" readonly="yes" safearray="yes">
+ <desc>
+ Array of USB controllers attached to this machine.
+
+ <note>
+ If USB functionality is not available in the given edition of
+ VirtualBox, this method will set the result code to @c E_NOTIMPL.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="USBDeviceFilters" type="IUSBDeviceFilters" readonly="yes">
+ <desc>
+ Associated USB device filters object.
+
+ <note>
+ If USB functionality is not available in the given edition of
+ VirtualBox, this method will set the result code to @c E_NOTIMPL.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="audioSettings" type="IAudioSettings" readonly="yes">
+ <desc>
+ The machine's audio settings.
+ </desc>
+ </attribute>
+
+ <attribute name="storageControllers" type="IStorageController" readonly="yes" safearray="yes">
+ <desc>Array of storage controllers attached to this machine.</desc>
+ </attribute>
+
+ <attribute name="settingsFilePath" type="wstring" readonly="yes" wrap-hint-server="limitedcaller">
+ <desc>
+ Full name of the file containing machine settings data.
+ </desc>
+ </attribute>
+
+ <attribute name="settingsAuxFilePath" type="wstring" readonly="yes" wrap-hint-server="limitedcaller" rest="suppress">
+ <desc>
+ Full name of the file containing auxiliary machine settings data.
+ </desc>
+ </attribute>
+
+ <attribute name="settingsModified" type="boolean" readonly="yes" rest="suppress">
+ <desc>
+ Whether the settings of this machine have been modified
+ (but neither yet saved nor discarded).
+ <note>
+ Reading this property is only valid on instances returned
+ by <link to="ISession::machine"/> and on new machines
+ created by <link to="IVirtualBox::createMachine"/> or opened
+ by <link to="IVirtualBox::openMachine"/> but not
+ yet registered, or on unregistered machines after calling
+ <link to="IMachine::unregister"/>. For all other
+ cases, the settings can never be modified.
+ </note>
+ <note>
+ For newly created unregistered machines, the value of this
+ property is always @c true until <link to="#saveSettings"/>
+ is called (no matter if any machine settings have been
+ changed after the creation or not). For opened machines
+ the value is set to @c false (and then follows to normal rules).
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="sessionState" type="SessionState" readonly="yes">
+ <desc>Current session state for this machine.</desc>
+ </attribute>
+
+ <attribute name="sessionName" type="wstring" readonly="yes">
+ <desc>
+ Name of the session. If <link to="#sessionState"/> is
+ Spawning or Locked, this attribute contains the
+ same value as passed to the
+ <link to="IMachine::launchVMProcess"/> method in the
+ @a name parameter. If the session was established with
+ <link to="IMachine::lockMachine" />, it is the name of the session
+ (if set, otherwise empty string). If
+ <link to="#sessionState"/> is SessionClosed, the value of this
+ attribute is an empty string.
+ </desc>
+ </attribute>
+
+ <attribute name="sessionPID" type="unsigned long" readonly="yes">
+ <desc>
+ Identifier of the session process. This attribute contains the
+ platform-dependent identifier of the process whose session was
+ used with <link to="IMachine::lockMachine" /> call. The returned
+ value is only valid if <link to="#sessionState"/> is Locked or
+ Unlocking by the time this property is read.
+ </desc>
+ </attribute>
+
+ <attribute name="state" type="MachineState" readonly="yes">
+ <desc>Current execution state of this machine.</desc>
+ </attribute>
+
+ <attribute name="lastStateChange" type="long long" readonly="yes">
+ <desc>
+ Timestamp of the last execution state change,
+ in milliseconds since 1970-01-01 UTC.
+ </desc>
+ </attribute>
+
+ <attribute name="stateFilePath" type="wstring" readonly="yes">
+ <desc>
+ Full path to the file that stores the execution state of
+ the machine when it is in either the <link to="MachineState_Saved"/>
+ or <link to="MachineState_AbortedSaved"/> state.
+ <note>
+ When the machine is not in the Saved or AbortedSaved state, this
+ attribute is an empty string.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="logFolder" type="wstring" readonly="yes">
+ <desc>
+ Full path to the folder that stores a set of rotated log files
+ recorded during machine execution. The most recent log file is
+ named <tt>VBox.log</tt>, the previous log file is
+ named <tt>VBox.log.1</tt> and so on (up to <tt>VBox.log.3</tt>
+ in the current version).
+ </desc>
+ </attribute>
+
+ <attribute name="currentSnapshot" type="ISnapshot" readonly="yes">
+ <desc>
+ Current snapshot of this machine. This is @c null if the machine
+ currently has no snapshots. If it is not @c null, then it was
+ set by one of <link to="#takeSnapshot" />, <link to="#deleteSnapshot" />
+ or <link to="#restoreSnapshot" />, depending on which was called last.
+ See <link to="ISnapshot"/> for details.
+ </desc>
+ </attribute>
+
+ <attribute name="snapshotCount" type="unsigned long" readonly="yes">
+ <desc>
+ Number of snapshots taken on this machine. Zero means the
+ machine doesn't have any snapshots.
+ </desc>
+ </attribute>
+
+ <attribute name="currentStateModified" type="boolean" readonly="yes">
+ <desc>
+ Returns @c true if the current state of the machine is not
+ identical to the state stored in the current snapshot.
+
+ The current state is identical to the current snapshot only
+ directly after one of the following calls are made:
+
+ <ul>
+ <li><link to="#restoreSnapshot"/>
+ </li>
+ <li><link to="#takeSnapshot"/> (issued on a "powered off" or "saved"
+ machine, for which <link to="#settingsModified"/> returns @c false)
+ </li>
+ </ul>
+
+ The current state remains identical until one of the following
+ happens:
+ <ul>
+ <li>settings of the machine are changed</li>
+ <li>the saved state is deleted</li>
+ <li>the current snapshot is deleted</li>
+ <li>an attempt to execute the machine is made</li>
+ </ul>
+
+ <note>
+ For machines that don't have snapshots, this property is
+ always @c false.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="sharedFolders" type="ISharedFolder" readonly="yes" safearray="yes">
+ <desc>
+ Collection of shared folders for this machine (permanent shared
+ folders). These folders are shared automatically at machine startup
+ and available only to the guest OS installed within this machine.
+
+ New shared folders are added to the collection using
+ <link to="#createSharedFolder"/>. Existing shared folders can be
+ removed using <link to="#removeSharedFolder"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="clipboardMode" type="ClipboardMode">
+ <desc>
+ Synchronization mode between the host OS clipboard
+ and the guest OS clipboard.
+ </desc>
+ </attribute>
+
+ <attribute name="clipboardFileTransfersEnabled" type="boolean">
+ <desc>
+ Sets or retrieves whether clipboard file transfers are allowed or not.
+
+ When set to @a true, clipboard file transfers between supported
+ host and guest OSes are allowed.
+ </desc>
+ </attribute>
+
+ <attribute name="dnDMode" type="DnDMode">
+ <desc>
+ Sets or retrieves the current drag'n drop mode.
+ </desc>
+ </attribute>
+
+ <attribute name="teleporterEnabled" type="boolean">
+ <desc>
+ When set to @a true, the virtual machine becomes a target teleporter
+ the next time it is powered on. This can only set to @a true when the
+ VM is in the @a PoweredOff or @a Aborted state.
+
+ <!-- This property is automatically set to @a false when the VM is powered
+ on. (bird: This doesn't work yet ) -->
+ </desc>
+ </attribute>
+
+ <attribute name="teleporterPort" type="unsigned long">
+ <desc>
+ The TCP port the target teleporter will listen for incoming
+ teleportations on.
+
+ 0 means the port is automatically selected upon power on. The actual
+ value can be read from this property while the machine is waiting for
+ incoming teleportations.
+ </desc>
+ </attribute>
+
+ <attribute name="teleporterAddress" type="wstring">
+ <desc>
+ The address the target teleporter will listen on. If set to an empty
+ string, it will listen on all addresses.
+ </desc>
+ </attribute>
+
+ <attribute name="teleporterPassword" type="wstring">
+ <desc>
+ The password to check for on the target teleporter. This is just a
+ very basic measure to prevent simple hacks and operators accidentally
+ beaming a virtual machine to the wrong place.
+
+ Note that you SET a plain text password while reading back a HASHED
+ password. Setting a hashed password is currently not supported.
+ </desc>
+ </attribute>
+
+ <attribute name="paravirtProvider" type="ParavirtProvider">
+ <desc>
+ The paravirtualized guest interface provider.
+ </desc>
+ </attribute>
+
+ <attribute name="RTCUseUTC" type="boolean">
+ <desc>
+ When set to @a true, the RTC device of the virtual machine will run
+ in UTC time, otherwise in local time. Especially Unix guests prefer
+ the time in UTC.
+ </desc>
+ </attribute>
+
+ <attribute name="IOCacheEnabled" type="boolean">
+ <desc>
+ When set to @a true, the builtin I/O cache of the virtual machine
+ will be enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="IOCacheSize" type="unsigned long">
+ <desc>
+ Maximum size of the I/O cache in MB.
+ </desc>
+ </attribute>
+
+ <attribute name="PCIDeviceAssignments" type="IPCIDeviceAttachment" readonly="yes" safearray="yes">
+ <desc>Array of PCI devices assigned to this machine, to get list of all
+ PCI devices attached to the machine use
+ <link to="IConsole::attachedPCIDevices"/> attribute, as this attribute
+ is intended to list only devices additional to what described in
+ virtual hardware config. Usually, this list keeps host's physical
+ devices assigned to the particular machine.
+ </desc>
+ </attribute>
+
+ <attribute name="bandwidthControl" type="IBandwidthControl" readonly="yes">
+ <desc>
+ Bandwidth control manager.
+ </desc>
+ </attribute>
+
+ <attribute name="tracingEnabled" type="boolean">
+ <desc>
+ Enables the tracing facility in the VMM (including PDM devices +
+ drivers). The VMM will consume about 0.5MB of more memory when
+ enabled and there may be some extra overhead from tracepoints that are
+ always enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="tracingConfig" type="wstring">
+ <desc>
+ Tracepoint configuration to apply at startup when
+ <link to="IMachine::tracingEnabled" /> is true. The string specifies
+ a space separated of tracepoint group names to enable. The special
+ group 'all' enables all tracepoints. Check DBGFR3TracingConfig for
+ more details on available tracepoint groups and such.
+
+ Note that on hosts supporting DTrace (or similar), a lot of the
+ tracepoints may be implemented exclusively as DTrace probes. So, the
+ effect of the same config may differ between Solaris and Windows for
+ example.
+ </desc>
+ </attribute>
+
+ <attribute name="allowTracingToAccessVM" type="boolean">
+ <desc>
+ Enables tracepoints in PDM devices and drivers to use the VMCPU or VM
+ structures when firing off trace points. This is especially useful
+ with DTrace tracepoints, as it allows you to use the VMCPU or VM
+ pointer to obtain useful information such as guest register state.
+
+ This is disabled by default because devices and drivers normally has no
+ business accessing the VMCPU or VM structures, and are therefore unable
+ to get any pointers to these.
+ </desc>
+ </attribute>
+
+ <attribute name="autostartEnabled" type="boolean">
+ <desc>
+ Enables autostart of the VM during system boot.
+ </desc>
+ </attribute>
+
+ <attribute name="autostartDelay" type="unsigned long">
+ <desc>
+ Number of seconds to wait until the VM should be started during system boot.
+ </desc>
+ </attribute>
+
+ <attribute name="autostopType" type="AutostopType">
+ <desc>
+ Action type to do when the system is shutting down.
+ </desc>
+ </attribute>
+
+ <attribute name="defaultFrontend" type="wstring">
+ <desc>
+ Selects which VM frontend should be used by default when launching
+ this VM through the <link to="IMachine::launchVMProcess" /> method.
+ Empty or @c null strings do not define a particular default, it is up
+ to <link to="IMachine::launchVMProcess" /> to select one. See the
+ description of <link to="IMachine::launchVMProcess" /> for the valid
+ frontend types.
+
+ This per-VM setting overrides the default defined by
+ <link to="ISystemProperties::defaultFrontend" /> attribute, and is
+ overridden by a frontend type passed to
+ <link to="IMachine::launchVMProcess" />.
+ </desc>
+ </attribute>
+
+ <attribute name="USBProxyAvailable" type="boolean" readonly="yes">
+ <desc>
+ Returns whether there is an USB proxy available.
+ </desc>
+ </attribute>
+
+ <attribute name="VMProcessPriority" type="VMProcPriority">
+ <desc>
+ Sets the priority of the VM process. It is a VM setting which can
+ be changed both before starting the VM and at runtime.
+
+ The default value is 'Default', which selects the default
+ process priority.
+
+ <result name="E_NOTIMPL">
+ This attribute is currently not implemented.
+ </result>
+ </desc>
+ </attribute>
+
+ <attribute name="paravirtDebug" type="wstring">
+ <desc>
+ Debug parameters for the paravirtualized guest interface provider.
+ </desc>
+ </attribute>
+
+ <attribute name="CPUProfile" type="wstring">
+ <desc>
+ Experimental feature to select the guest CPU profile. The default
+ is "host", which indicates the host CPU. All other names are subject
+ to change.
+
+ Use the <link to="ISystemProperties::getCPUProfiles" /> method to get
+ currently available CPU profiles.
+ </desc>
+ </attribute>
+
+ <attribute name="stateKeyId" type="wstring" readonly="yes">
+ <desc>
+ Key Id of the password used for encrypting the state file.
+ Internal use only for now.
+ </desc>
+ </attribute>
+
+ <attribute name="stateKeyStore" type="wstring" readonly="yes">
+ <desc>
+ Key store used for encrypting the state file.
+ Internal use only for now.
+ </desc>
+ </attribute>
+ <attribute name="logKeyId" type="wstring" readonly="yes">
+ <desc>
+ Key Id of the password used for encrypting the log files.
+ Internal use only for now.
+ </desc>
+ </attribute>
+
+ <attribute name="logKeyStore" type="wstring" readonly="yes">
+ <desc>
+ Key store used for encrypting the log files.
+ Internal use only for now.
+ </desc>
+ </attribute>
+
+ <attribute name="guestDebugControl" type="IGuestDebugControl" readonly="yes">
+ <desc>
+ Guest debugging configuration.
+ </desc>
+ </attribute>
+
+ <method name="lockMachine">
+ <rest name="lock" request="post" path="/vms/{vmid}/actions/"/>
+ <desc>
+ Locks the machine for the given session to enable the caller
+ to make changes to the machine or start the VM or control
+ VM execution.
+
+ There are two ways to lock a machine for such uses:
+
+ <ul>
+ <li>If you want to make changes to the machine settings,
+ you must obtain an exclusive write lock on the machine
+ by setting @a lockType to @c Write.
+
+ This will only succeed if no other process has locked
+ the machine to prevent conflicting changes. Only after
+ an exclusive write lock has been obtained using this method, one
+ can change all VM settings or execute the VM in the process
+ space of the session object. (Note that the latter is only of
+ interest if you actually want to write a new front-end for
+ virtual machines; but this API gets called internally by
+ the existing front-ends such as VBoxHeadless and the VirtualBox
+ GUI to acquire a write lock on the machine that they are running.)
+
+ On success, write-locking the machine for a session creates
+ a second copy of the IMachine object. It is this second object
+ upon which changes can be made; in VirtualBox terminology, the
+ second copy is "mutable". It is only this second, mutable machine
+ object upon which you can call methods that change the
+ machine state. After having called this method, you can
+ obtain this second, mutable machine object using the
+ <link to="ISession::machine" /> attribute.
+ </li>
+ <li>If you only want to check the machine state or control
+ machine execution without actually changing machine
+ settings (e.g. to get access to VM statistics or take
+ a snapshot or save the machine state), then set the
+ @a lockType argument to @c Shared.
+
+ If no other session has obtained a lock, you will obtain an
+ exclusive write lock as described above. However, if another
+ session has already obtained such a lock, then a link to that
+ existing session will be established which allows you
+ to control that existing session.
+
+ To find out which type of lock was obtained, you can
+ inspect <link to="ISession::type" />, which will have been
+ set to either @c WriteLock or @c Shared.
+ </li>
+ </ul>
+
+ In either case, you can get access to the <link to="IConsole" />
+ object which controls VM execution.
+
+ Also in all of the above cases, one must always call
+ <link to="ISession::unlockMachine" /> to release the lock on the machine, or
+ the machine's state will eventually be set to "Aborted".
+
+ To change settings on a machine, the following sequence is typically
+ performed:
+
+ <ol>
+ <li>Call this method to obtain an exclusive write lock for the current session.</li>
+
+ <li>Obtain a mutable IMachine object from <link to="ISession::machine" />.</li>
+
+ <li>Change the settings of the machine by invoking IMachine methods.</li>
+
+ <li>Call <link to="IMachine::saveSettings" />.</li>
+
+ <li>Release the write lock by calling <link to="ISession::unlockMachine"/>.</li>
+ </ol>
+
+ <result name="E_UNEXPECTED">
+ Virtual machine not registered.
+ </result>
+ <result name="E_ACCESSDENIED">
+ Process not started by <link to="IMachine::launchVMProcess"/>.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session already open or being opened.
+ </result>
+ <result name="VBOX_E_VM_ERROR">
+ Failed to assign machine to session.
+ </result>
+ </desc>
+ <param name="session" type="ISession" dir="in">
+ <desc>
+ Session object for which the machine will be locked.
+ </desc>
+ </param>
+ <param name="lockType" type="LockType" dir="in">
+ <desc>
+ If set to @c Write, then attempt to acquire an exclusive write lock or fail.
+ If set to @c Shared, then either acquire an exclusive write lock or establish
+ a link to an existing session.
+ </desc>
+ </param>
+ </method>
+
+ <method name="launchVMProcess">
+ <rest name="launch" request="post" path="/vms/{vmid}/actions/"/>
+ <desc>
+ Spawns a new process that will execute the virtual machine and obtains a shared
+ lock on the machine for the calling session.
+
+ If launching the VM succeeds, the new VM process will create its own session
+ and write-lock the machine for it, preventing conflicting changes from other
+ processes. If the machine is already locked (because it is already running or
+ because another session has a write lock), launching the VM process will therefore
+ fail. Reversely, future attempts to obtain a write lock will also fail while the
+ machine is running.
+
+ The caller's session object remains separate from the session opened by the new
+ VM process. It receives its own <link to="IConsole" /> object which can be used
+ to control machine execution, but it cannot be used to change all VM settings
+ which would be available after a <link to="#lockMachine" /> call.
+
+ The caller must eventually release the session's shared lock by calling
+ <link to="ISession::unlockMachine" /> on the local session object once this call
+ has returned. However, the session's state (see <link to="ISession::state" />)
+ will not return to "Unlocked" until the remote session has also unlocked
+ the machine (i.e. the machine has stopped running).
+
+ Launching a VM process can take some time (a new VM is started in a new process,
+ for which memory and other resources need to be set up). Because of this,
+ an <link to="IProgress" /> object is returned to allow the caller to wait
+ for this asynchronous operation to be completed. Until then, the caller's
+ session object remains in the "Unlocked" state, and its <link to="ISession::machine" />
+ and <link to="ISession::console" /> attributes cannot be accessed.
+ It is recommended to use <link to="IProgress::waitForCompletion" /> or
+ similar calls to wait for completion. Completion is signalled when the VM
+ is powered on. If launching the VM fails, error messages can be queried
+ via the progress object, if available.
+
+ The progress object will have at least 2 sub-operations. The first
+ operation covers the period up to the new VM process calls powerUp.
+ The subsequent operations mirror the <link to="IConsole::powerUp"/>
+ progress object. Because <link to="IConsole::powerUp"/> may require
+ some extra sub-operations, the <link to="IProgress::operationCount"/>
+ may change at the completion of operation.
+
+ For details on the teleportation progress operation, see
+ <link to="IConsole::powerUp"/>.
+
+ The @a environmentChanges argument is a list of strings where every string contains
+ environment variable in the putenv style, i.e. "VAR=VALUE" for setting/replacing
+ and "VAR" for unsetting. These environment variables will be applied to the environment
+ of the VirtualBox server process. If an environment variable exists both in the server
+ process and in this list, the value from this list takes precedence over the
+ server's variable. If the value of the environment variable is omitted, this variable
+ will be removed from the resulting environment. If the list is empty, the server
+ environment is inherited by the started process as is.
+
+ <result name="E_UNEXPECTED">
+ Virtual machine not registered.
+ </result>
+ <result name="E_INVALIDARG">
+ Invalid session type @a type.
+ </result>
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ No machine matching @a machineId found.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session already open or being opened.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ Launching process for machine failed.
+ </result>
+ <result name="VBOX_E_VM_ERROR">
+ Failed to assign machine to session.
+ </result>
+ </desc>
+ <param name="session" type="ISession" dir="in">
+ <desc>
+ Client session object to which the VM process will be connected (this
+ must be in "Unlocked" state).
+ </desc>
+ </param>
+ <param name="name" type="wstring" dir="in">
+ <desc>
+ Front-end to use for the new VM process. The following are currently supported:
+ <ul>
+ <li><tt>"gui"</tt>: VirtualBox Qt GUI front-end</li>
+ <li><tt>"headless"</tt>: VBoxHeadless (VRDE Server) front-end</li>
+ <li><tt>"sdl"</tt>: VirtualBox SDL front-end</li>
+ <li><tt>"emergencystop"</tt>: reserved value, used for aborting
+ the currently running VM or session owner. In this case the
+ @a session parameter may be @c null (if it is non-null it isn't
+ used in any way), and the @a progress return value will be always
+ @c null. The operation completes immediately.</li>
+ <li><tt>""</tt>: use the per-VM default frontend if set, otherwise
+ the global default defined in the system properties. If neither
+ are set, the API will launch a <tt>"gui"</tt> session, which may
+ fail if there is no windowing environment available. See
+ <link to="IMachine::defaultFrontend"/> and
+ <link to="ISystemProperties::defaultFrontend"/>.</li>
+ </ul>
+ </desc>
+ </param>
+ <param name="environmentChanges" type="wstring" safearray="yes" dir="in">
+ <desc>
+ The list of putenv-style changes to the VM process environment.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="setBootOrder">
+ <rest name="bootorder" request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Puts the given device to the specified position in
+ the boot order.
+
+ To indicate that no device is associated with the given position,
+ <link to="DeviceType_Null"/> should be used.
+
+ @todo setHardDiskBootOrder(), setNetworkBootOrder()
+
+ <result name="E_INVALIDARG">
+ Boot @a position out of range.
+ </result>
+ <result name="E_NOTIMPL">
+ Booting from USB @a device currently not supported.
+ </result>
+
+ </desc>
+ <param name="position" type="unsigned long" dir="in">
+ <desc>
+ Position in the boot order (@c 1 to the total number of
+ devices the machine can boot from, as returned by
+ <link to="ISystemProperties::maxBootPosition"/>).
+ </desc>
+ </param>
+ <param name="device" type="DeviceType" dir="in">
+ <desc>
+ The type of the device used to boot at the given position.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getBootOrder" const="yes">
+ <rest name="bootorder" request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns the device type that occupies the specified
+ position in the boot order.
+
+ @todo [remove?]
+ If the machine can have more than one device of the returned type
+ (such as hard disks), then a separate method should be used to
+ retrieve the individual device that occupies the given position.
+
+ If here are no devices at the given position, then
+ <link to="DeviceType_Null"/> is returned.
+
+ @todo getHardDiskBootOrder(), getNetworkBootOrder()
+
+ <result name="E_INVALIDARG">
+ Boot @a position out of range.
+ </result>
+
+ </desc>
+ <param name="position" type="unsigned long" dir="in">
+ <desc>
+ Position in the boot order (@c 1 to the total number of
+ devices the machine can boot from, as returned by
+ <link to="ISystemProperties::maxBootPosition"/>).
+ </desc>
+ </param>
+ <param name="device" type="DeviceType" dir="return">
+ <desc>
+ Device at the given position.
+ </desc>
+ </param>
+ </method>
+
+ <method name="attachDevice">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Attaches a device and optionally mounts a medium to the given storage
+ controller (<link to="IStorageController" />, identified by @a name),
+ at the indicated port and device.
+
+ This method is intended for managing storage devices in general while a
+ machine is powered off. It can be used to attach and detach fixed
+ and removable media. The following kind of media can be attached
+ to a machine:
+
+ <ul>
+ <li>For fixed and removable media, you can pass in a medium that was
+ previously opened using <link to="IVirtualBox::openMedium" />.
+ </li>
+
+ <li>Only for storage devices supporting removable media (such as
+ DVDs and floppies), you can also specify a null pointer to
+ indicate an empty drive or one of the medium objects listed
+ in the <link to="IHost::DVDDrives" /> and <link to="IHost::floppyDrives"/>
+ arrays to indicate a host drive.
+ For removable devices, you can also use <link to="IMachine::mountMedium"/>
+ to change the media while the machine is running.
+ </li>
+ </ul>
+
+ In a VM's default configuration of virtual machines, the secondary
+ master of the IDE controller is used for a CD/DVD drive.
+
+ After calling this returns successfully, a new instance of
+ <link to="IMediumAttachment"/> will appear in the machine's list of medium
+ attachments (see <link to="IMachine::mediumAttachments"/>).
+
+ See <link to="IMedium"/> and <link to="IMediumAttachment"/> for more
+ information about attaching media.
+
+ The specified device slot must not have a device attached to it,
+ or this method will fail.
+
+ <note>
+ You cannot attach a device to a newly created machine until
+ this machine's settings are saved to disk using
+ <link to="#saveSettings"/>.
+ </note>
+ <note>
+ If the medium is being attached indirectly, a new differencing medium
+ will implicitly be created for it and attached instead. If the
+ changes made to the machine settings (including this indirect
+ attachment) are later cancelled using <link to="#discardSettings"/>,
+ this implicitly created differencing medium will implicitly
+ be deleted.
+ </note>
+
+ <result name="E_INVALIDARG">
+ SATA device, SATA port, IDE port or IDE slot out of range, or
+ file or UUID not found.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Machine must be registered before media can be attached.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Invalid machine state.
+ </result>
+ <result name="VBOX_E_OBJECT_IN_USE">
+ A medium is already attached to this or another virtual machine.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the storage controller to attach the device to.</desc>
+ </param>
+ <param name="controllerPort" type="long" dir="in">
+ <desc>Port to attach the device to. For an IDE controller, 0 specifies
+ the primary controller and 1 specifies the secondary controller.
+ For a SCSI controller, this must range from 0 to 15; for a SATA controller,
+ from 0 to 29; for an SAS controller, from 0 to 7.</desc>
+ </param>
+ <param name="device" type="long" dir="in">
+ <desc>Device slot in the given port to attach the device to. This is only
+ relevant for IDE controllers, for which 0 specifies the master device and
+ 1 specifies the slave device. For all other controller types, this must
+ be 0.</desc>
+ </param>
+ <param name="type" type="DeviceType" dir="in">
+ <desc>Device type of the attached device. For media opened by
+ <link to="IVirtualBox::openMedium" />, this must match the device type
+ specified there.</desc>
+ </param>
+ <param name="medium" type="IMedium" dir="in">
+ <desc>Medium to mount or @c null for an empty drive.</desc>
+ </param>
+ </method>
+
+ <method name="attachDeviceWithoutMedium">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Attaches a device and optionally mounts a medium to the given storage
+ controller (<link to="IStorageController" />, identified by @a name),
+ at the indicated port and device.
+
+ This method is intended for managing storage devices in general while a
+ machine is powered off. It can be used to attach and detach fixed
+ and removable media. The following kind of media can be attached
+ to a machine:
+ <ul>
+ <li>
+ For fixed and removable media, you can pass in a medium that was
+ previously opened using <link to="IVirtualBox::openMedium" />.
+ </li>
+
+ <li>Only for storage devices supporting removable media (such as
+ DVDs and floppies) with an empty drive or one of the medium objects listed
+ in the <link to="IHost::DVDDrives" /> and <link to="IHost::floppyDrives"/>
+ arrays to indicate a host drive.
+ For removable devices, you can also use <link to="IMachine::mountMedium"/>
+ to change the media while the machine is running.
+ </li>
+ </ul>
+
+ In a VM's default configuration of virtual machines, the secondary
+ master of the IDE controller is used for a CD/DVD drive.
+ <link to="IMediumAttachment"/> will appear in the machine's list of medium
+ attachments (see <link to="IMachine::mediumAttachments"/>).
+
+ See <link to="IMedium"/> and <link to="IMediumAttachment"/> for more
+ information about attaching media.
+
+ The specified device slot must not have a device attached to it,
+ or this method will fail.
+ <note>
+ You cannot attach a device to a newly created machine until
+ this machine's settings are saved to disk using
+ <link to="#saveSettings"/>.
+ </note>
+ <note>
+ If the medium is being attached indirectly, a new differencing medium
+ will implicitly be created for it and attached instead. If the
+ changes made to the machine settings (including this indirect
+ attachment) are later cancelled using <link to="#discardSettings"/>,
+ this implicitly created differencing medium will implicitly
+ be deleted.
+ </note>
+
+ <result name="E_INVALIDARG">
+ SATA device, SATA port, IDE port or IDE slot out of range, or
+ file or UUID not found.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Machine must be registered before media can be attached.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Invalid machine state.
+ </result>
+ <result name="VBOX_E_OBJECT_IN_USE">
+ A medium is already attached to this or another virtual machine.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the storage controller to attach the device to.</desc>
+ </param>
+ <param name="controllerPort" type="long" dir="in">
+ <desc>Port to attach the device to. For an IDE controller, 0 specifies
+ the primary controller and 1 specifies the secondary controller.
+ For a SCSI controller, this must range from 0 to 15; for a SATA controller,
+ from 0 to 29; for an SAS controller, from 0 to 7.</desc>
+ </param>
+ <param name="device" type="long" dir="in">
+ <desc>Device slot in the given port to attach the device to. This is only
+ relevant for IDE controllers, for which 0 specifies the master device and
+ 1 specifies the slave device. For all other controller types, this must
+ be 0.</desc>
+ </param>
+ <param name="type" type="DeviceType" dir="in">
+ <desc>Device type of the attached device. For media opened by
+ <link to="IVirtualBox::openMedium" />, this must match the device type
+ specified there.</desc>
+ </param>
+ </method>
+
+ <method name="detachDevice">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Detaches the device attached to a device slot of the specified bus.
+
+ Detaching the device from the virtual machine is deferred. This means
+ that the medium remains associated with the machine when this method
+ returns and gets actually de-associated only after a successful
+ <link to="#saveSettings"/> call. See <link to="IMedium"/>
+ for more detailed information about attaching media.
+
+ <note>
+ You cannot detach a device from a running machine.
+ </note>
+ <note>
+ Detaching differencing media implicitly created by <link
+ to="#attachDevice"/> for the indirect attachment using this
+ method will <b>not</b> implicitly delete them. The
+ <link to="IMedium::deleteStorage"/> operation should be
+ explicitly performed by the caller after the medium is successfully
+ detached and the settings are saved with
+ <link to="#saveSettings"/>, if it is the desired action.
+ </note>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Attempt to detach medium from a running virtual machine.
+ </result>
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ No medium attached to given slot/bus.
+ </result>
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Medium format does not support storage deletion (only for implicitly
+ created differencing media, should not happen).
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the storage controller to detach the medium from.</desc>
+ </param>
+ <param name="controllerPort" type="long" dir="in">
+ <desc>Port number to detach the medium from.</desc>
+ </param>
+ <param name="device" type="long" dir="in">
+ <desc>Device slot number to detach the medium from.</desc>
+ </param>
+ </method>
+
+ <method name="passthroughDevice">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Sets the passthrough mode of an existing DVD device. Changing the
+ setting while the VM is running is forbidden. The setting is only used
+ if at VM start the device is configured as a host DVD drive, in all
+ other cases it is ignored. The device must already exist; see
+ <link to="IMachine::attachDevice"/> for how to attach a new device.
+
+ The @a controllerPort and @a device parameters specify the device slot and
+ have have the same meaning as with <link to="IMachine::attachDevice" />.
+
+ <result name="E_INVALIDARG">
+ SATA device, SATA port, IDE port or IDE slot out of range.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Attempt to modify an unregistered virtual machine.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Invalid machine state.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the storage controller.</desc>
+ </param>
+ <param name="controllerPort" type="long" dir="in">
+ <desc>Storage controller port.</desc>
+ </param>
+ <param name="device" type="long" dir="in">
+ <desc>Device slot in the given port.</desc>
+ </param>
+ <param name="passthrough" type="boolean" dir="in">
+ <desc>New value for the passthrough setting.</desc>
+ </param>
+ </method>
+
+ <method name="temporaryEjectDevice">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Sets the behavior for guest-triggered medium eject. In some situations
+ it is desirable that such ejects update the VM configuration, and in
+ others the eject should keep the VM configuration. The device must
+ already exist; see <link to="IMachine::attachDevice"/> for how to
+ attach a new device.
+
+ The @a controllerPort and @a device parameters specify the device slot and
+ have have the same meaning as with <link to="IMachine::attachDevice" />.
+
+ <result name="E_INVALIDARG">
+ SATA device, SATA port, IDE port or IDE slot out of range.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Attempt to modify an unregistered virtual machine.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Invalid machine state.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the storage controller.</desc>
+ </param>
+ <param name="controllerPort" type="long" dir="in">
+ <desc>Storage controller port.</desc>
+ </param>
+ <param name="device" type="long" dir="in">
+ <desc>Device slot in the given port.</desc>
+ </param>
+ <param name="temporaryEject" type="boolean" dir="in">
+ <desc>New value for the eject behavior.</desc>
+ </param>
+ </method>
+
+ <method name="nonRotationalDevice">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Sets a flag in the device information which indicates that the medium
+ is not based on rotational technology, i.e. that the access times are
+ more or less independent of the position on the medium. This may or may
+ not be supported by a particular drive, and is silently ignored in the
+ latter case. At the moment only hard disks (which is a misnomer in this
+ context) accept this setting. Changing the setting while the VM is
+ running is forbidden. The device must already exist; see
+ <link to="IMachine::attachDevice"/> for how to attach a new device.
+
+ The @a controllerPort and @a device parameters specify the device slot and
+ have have the same meaning as with <link to="IMachine::attachDevice" />.
+
+ <result name="E_INVALIDARG">
+ SATA device, SATA port, IDE port or IDE slot out of range.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Attempt to modify an unregistered virtual machine.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Invalid machine state.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the storage controller.</desc>
+ </param>
+ <param name="controllerPort" type="long" dir="in">
+ <desc>Storage controller port.</desc>
+ </param>
+ <param name="device" type="long" dir="in">
+ <desc>Device slot in the given port.</desc>
+ </param>
+ <param name="nonRotational" type="boolean" dir="in">
+ <desc>New value for the non-rotational device flag.</desc>
+ </param>
+ </method>
+
+ <method name="setAutoDiscardForDevice">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Sets a flag in the device information which indicates that the medium
+ supports discarding unused blocks (called trimming for SATA or unmap
+ for SCSI devices) .This may or may not be supported by a particular drive,
+ and is silently ignored in the latter case. At the moment only hard disks
+ (which is a misnomer in this context) accept this setting. Changing the
+ setting while the VM is running is forbidden. The device must already
+ exist; see <link to="IMachine::attachDevice"/> for how to attach a new
+ device.
+
+ The @a controllerPort and @a device parameters specify the device slot and
+ have have the same meaning as with <link to="IMachine::attachDevice" />.
+
+ <result name="E_INVALIDARG">
+ SATA device, SATA port, SCSI port out of range.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Attempt to modify an unregistered virtual machine.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Invalid machine state.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the storage controller.</desc>
+ </param>
+ <param name="controllerPort" type="long" dir="in">
+ <desc>Storage controller port.</desc>
+ </param>
+ <param name="device" type="long" dir="in">
+ <desc>Device slot in the given port.</desc>
+ </param>
+ <param name="discard" type="boolean" dir="in">
+ <desc>New value for the discard device flag.</desc>
+ </param>
+ </method>
+
+ <method name="setHotPluggableForDevice">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Sets a flag in the device information which indicates that the attached
+ device is hot pluggable or not. This may or may not be supported by a
+ particular controller and/or drive, and is silently ignored in the
+ latter case. Changing the setting while the VM is running is forbidden.
+ The device must already exist; see <link to="IMachine::attachDevice"/>
+ for how to attach a new device.
+
+ The @a controllerPort and @a device parameters specify the device slot and
+ have have the same meaning as with <link to="IMachine::attachDevice" />.
+
+ <result name="E_INVALIDARG">
+ SATA device, SATA port, IDE port or IDE slot out of range.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Attempt to modify an unregistered virtual machine.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Invalid machine state.
+ </result>
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Controller doesn't support hot plugging.
+ </result>
+ </desc>
+
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the storage controller.</desc>
+ </param>
+ <param name="controllerPort" type="long" dir="in">
+ <desc>Storage controller port.</desc>
+ </param>
+ <param name="device" type="long" dir="in">
+ <desc>Device slot in the given port.</desc>
+ </param>
+ <param name="hotPluggable" type="boolean" dir="in">
+ <desc>New value for the hot-pluggable device flag.</desc>
+ </param>
+ </method>
+
+ <method name="setBandwidthGroupForDevice">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Sets the bandwidth group of an existing storage device.
+ The device must already exist; see <link to="IMachine::attachDevice"/>
+ for how to attach a new device.
+
+ The @a controllerPort and @a device parameters specify the device slot and
+ have have the same meaning as with <link to="IMachine::attachDevice" />.
+
+ <result name="E_INVALIDARG">
+ SATA device, SATA port, IDE port or IDE slot out of range.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Attempt to modify an unregistered virtual machine.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Invalid machine state.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the storage controller.</desc>
+ </param>
+ <param name="controllerPort" type="long" dir="in">
+ <desc>Storage controller port.</desc>
+ </param>
+ <param name="device" type="long" dir="in">
+ <desc>Device slot in the given port.</desc>
+ </param>
+ <param name="bandwidthGroup" type="IBandwidthGroup" dir="in">
+ <desc>New value for the bandwidth group or @c null for no group.</desc>
+ </param>
+ </method>
+
+ <method name="setNoBandwidthGroupForDevice">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Sets no bandwidth group for an existing storage device.
+ The device must already exist; see <link to="IMachine::attachDevice"/>
+ for how to attach a new device.
+ The @a controllerPort and @a device parameters specify the device slot and
+ have have the same meaning as with <link to="IMachine::attachDevice" />.
+ <result name="E_INVALIDARG">
+ SATA device, SATA port, IDE port or IDE slot out of range.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Attempt to modify an unregistered virtual machine.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Invalid machine state.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the storage controller.</desc>
+ </param>
+ <param name="controllerPort" type="long" dir="in">
+ <desc>Storage controller port.</desc>
+ </param>
+ <param name="device" type="long" dir="in">
+ <desc>Device slot in the given port.</desc>
+ </param>
+ </method>
+
+ <method name="unmountMedium">
+ <rest name="unmount" request="post" path="/vms/{vmid}/mediums/{mediumid}/actions/"/>
+ <desc>
+ Unmounts any currently mounted medium (<link to="IMedium" />,
+ identified by the given UUID @a id) to the given storage controller
+ (<link to="IStorageController" />, identified by @a name),
+ at the indicated port and device. The device must already exist;
+
+ This method is intended only for managing removable media, where the
+ device is fixed but media is changeable at runtime (such as DVDs
+ and floppies). It cannot be used for fixed media such as hard disks.
+
+ The @a controllerPort and @a device parameters specify the device slot
+ and have have the same meaning as with
+ <link to="IMachine::attachDevice" />.
+
+ The specified device slot must have a medium mounted, which will be
+ unmounted. If there is no mounted medium it will do nothing.
+ See <link to="IMedium"/> for more detailed information about
+ attaching/unmounting media.
+
+ <result name="E_INVALIDARG">
+ SATA device, SATA port, IDE port or IDE slot out of range.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Attempt to unmount medium that is not removable - not DVD or floppy.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Invalid machine state.
+ </result>
+ <result name="VBOX_E_OBJECT_IN_USE">
+ Medium already attached to this or another virtual machine.
+ </result>
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Medium not attached to specified port, device, controller.
+ </result>
+ </desc>
+
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the storage controller to unmount the medium from.</desc>
+ </param>
+ <param name="controllerPort" type="long" dir="in">
+ <desc>Port to unmount the medium from.</desc>
+ </param>
+ <param name="device" type="long" dir="in">
+ <desc>Device slot in the given port to unmount the medium from.</desc>
+ </param>
+ <param name="force" type="boolean" dir="in">
+ <desc>Allows to force unmount of a medium which is locked by
+ the device slot in the given port medium is attached to.</desc>
+ </param>
+ </method>
+
+ <method name="mountMedium">
+ <rest name="mount" request="post" path="/vms/{vmid}/mediums/{mediumid}/actions/"/>
+ <desc>
+ Mounts a medium (<link to="IMedium" />, identified
+ by the given UUID @a id) to the given storage controller
+ (<link to="IStorageController" />, identified by @a name),
+ at the indicated port and device. The device must already exist;
+ see <link to="IMachine::attachDevice"/> for how to attach a new device.
+
+ This method is intended only for managing removable media, where the
+ device is fixed but media is changeable at runtime (such as DVDs
+ and floppies). It cannot be used for fixed media such as hard disks.
+
+ The @a controllerPort and @a device parameters specify the device slot and
+ have have the same meaning as with <link to="IMachine::attachDevice" />.
+
+ The specified device slot can have a medium mounted, which will be
+ unmounted first. Specifying a zero UUID (or an empty string) for
+ @a medium does just an unmount.
+
+ See <link to="IMedium"/> for more detailed information about
+ attaching media.
+
+ <result name="E_INVALIDARG">
+ SATA device, SATA port, IDE port or IDE slot out of range.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Attempt to attach medium to an unregistered virtual machine.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Invalid machine state.
+ </result>
+ <result name="VBOX_E_OBJECT_IN_USE">
+ Medium already attached to this or another virtual machine.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the storage controller to attach the medium to.</desc>
+ </param>
+ <param name="controllerPort" type="long" dir="in">
+ <desc>Port to attach the medium to.</desc>
+ </param>
+ <param name="device" type="long" dir="in">
+ <desc>Device slot in the given port to attach the medium to.</desc>
+ </param>
+ <param name="medium" type="IMedium" dir="in">
+ <desc>Medium to mount or @c null for an empty drive.</desc>
+ </param>
+ <param name="force" type="boolean" dir="in">
+ <desc>Allows to force unmount/mount of a medium which is locked by
+ the device slot in the given port to attach the medium to.</desc>
+ </param>
+ </method>
+
+ <method name="getMedium" const="yes">
+ <rest name="find" request="get" path="/vms/{vmid}/mediums/{mediumid}/"/>
+ <desc>
+ Returns the virtual medium attached to a device slot of the specified
+ bus.
+
+ Note that if the medium was indirectly attached by
+ <link to="#mountMedium"/> to the given device slot then this
+ method will return not the same object as passed to the
+ <link to="#mountMedium"/> call. See <link to="IMedium"/> for
+ more detailed information about mounting a medium.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ No medium attached to given slot/bus.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the storage controller the medium is attached to.</desc>
+ </param>
+ <param name="controllerPort" type="long" dir="in">
+ <desc>Port to query.</desc>
+ </param>
+ <param name="device" type="long" dir="in">
+ <desc>Device slot in the given port to query.</desc>
+ </param>
+ <param name="medium" type="IMedium" dir="return">
+ <desc>Attached medium object.</desc>
+ </param>
+ </method>
+
+ <method name="getMediumAttachmentsOfController" const="yes">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns an array of medium attachments which are attached to the
+ the controller with the given name.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ A storage controller with given name doesn't exist.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in"/>
+ <param name="mediumAttachments" type="IMediumAttachment" safearray="yes" dir="return"/>
+ </method>
+
+ <method name="getMediumAttachment" const="yes">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns a medium attachment which corresponds to the controller with
+ the given name, on the given port and device slot.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ No attachment exists for the given controller/port/device combination.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in"/>
+ <param name="controllerPort" type="long" dir="in"/>
+ <param name="device" type="long" dir="in"/>
+ <param name="attachment" type="IMediumAttachment" dir="return"/>
+ </method>
+
+ <method name="attachHostPCIDevice">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Attaches host PCI device with the given (host) PCI address to the
+ PCI bus of the virtual machine. Please note, that this operation
+ is two phase, as real attachment will happen when VM will start,
+ and most information will be delivered as IHostPCIDevicePlugEvent
+ on IVirtualBox event source.
+
+ <see><link to="IHostPCIDevicePlugEvent"/></see>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine state is not stopped (PCI hotplug not yet implemented).
+ </result>
+ <result name="VBOX_E_PDM_ERROR">
+ Virtual machine does not have a PCI controller allowing attachment of physical devices.
+ </result>
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Hardware or host OS doesn't allow PCI device passthrough.
+ </result>
+ </desc>
+ <param name="hostAddress" type="long" dir="in">
+ <desc>Address of the host PCI device.</desc>
+ </param>
+ <param name="desiredGuestAddress" type="long" dir="in">
+ <desc>Desired position of this device on guest PCI bus.</desc>
+ </param>
+ <param name="tryToUnbind" type="boolean" dir="in">
+ <desc>If VMM shall try to unbind existing drivers from the
+ device before attaching it to the guest.</desc>
+ </param>
+ </method>
+
+ <method name="detachHostPCIDevice">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Detach host PCI device from the virtual machine.
+ Also HostPCIDevicePlugEvent on IVirtualBox event source
+ will be delivered. As currently we don't support hot device
+ unplug, IHostPCIDevicePlugEvent event is delivered immediately.
+
+ <see><link to="IHostPCIDevicePlugEvent"/></see>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine state is not stopped (PCI hotplug not yet implemented).
+ </result>
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ This host device is not attached to this machine.
+ </result>
+ <result name="VBOX_E_PDM_ERROR">
+ Virtual machine does not have a PCI controller allowing attachment of physical devices.
+ </result>
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Hardware or host OS doesn't allow PCI device passthrough.
+ </result>
+ </desc>
+ <param name="hostAddress" type="long" dir="in">
+ <desc>Address of the host PCI device.</desc>
+ </param>
+ </method>
+
+ <method name="getNetworkAdapter" const="yes">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns the network adapter associated with the given slot.
+ Slots are numbered sequentially, starting with zero. The total
+ number of adapters per machine is defined by the
+ <link to="ISystemProperties::getMaxNetworkAdapters"/> property,
+ so the maximum slot number is one less than that property's value.
+
+ <result name="E_INVALIDARG">
+ Invalid @a slot number.
+ </result>
+
+ </desc>
+ <param name="slot" type="unsigned long" dir="in"/>
+ <param name="adapter" type="INetworkAdapter" dir="return"/>
+ </method>
+
+ <method name="addStorageController">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Adds a new storage controller (SCSI, SAS or SATA controller) to the
+ machine and returns it as an instance of
+ <link to="IStorageController" />.
+
+ @a name identifies the controller for subsequent calls such as
+ <link to="#getStorageControllerByName" />,
+ <link to="#getStorageControllerByInstance" />,
+ <link to="#removeStorageController" />,
+ <link to="#attachDevice" /> or <link to="#mountMedium" />.
+
+ After the controller has been added, you can set its exact
+ type by setting the <link to="IStorageController::controllerType" />.
+
+ <result name="VBOX_E_OBJECT_IN_USE">
+ A storage controller with given name exists already.
+ </result>
+ <result name="E_INVALIDARG">
+ Invalid @a controllerType.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in"/>
+ <param name="connectionType" type="StorageBus" dir="in"/>
+ <param name="controller" type="IStorageController" dir="return"/>
+ </method>
+
+ <method name="getStorageControllerByName" const="yes">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns a storage controller with the given name.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ A storage controller with given name doesn't exist.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in"/>
+ <param name="storageController" type="IStorageController" dir="return"/>
+ </method>
+
+ <method name="getStorageControllerByInstance" const="yes">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns a storage controller of a specific storage bus
+ with the given instance number.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ A storage controller with given instance number doesn't exist.
+ </result>
+ </desc>
+ <param name="connectionType" type="StorageBus" dir="in"/>
+ <param name="instance" type="unsigned long" dir="in"/>
+ <param name="storageController" type="IStorageController" dir="return"/>
+ </method>
+
+ <method name="removeStorageController">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Removes a storage controller from the machine with all devices attached to it.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ A storage controller with given name doesn't exist.
+ </result>
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Medium format does not support storage deletion (only for implicitly
+ created differencing media, should not happen).
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in"/>
+ </method>
+
+ <method name="setStorageControllerBootable">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Sets the bootable flag of the storage controller with the given name.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ A storage controller with given name doesn't exist.
+ </result>
+ <result name="VBOX_E_OBJECT_IN_USE">
+ Another storage controller is marked as bootable already.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in"/>
+ <param name="bootable" type="boolean" dir="in"/>
+ </method>
+
+ <method name="addUSBController">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Adds a new USB controller to the machine and returns it as an instance of
+ <link to="IUSBController" />.
+
+ <result name="VBOX_E_OBJECT_IN_USE">
+ A USB controller with given type exists already.
+ </result>
+ <result name="E_INVALIDARG">
+ Invalid @a controllerType.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in"/>
+ <param name="type" type="USBControllerType" dir="in"/>
+ <param name="controller" type="IUSBController" dir="return"/>
+ </method>
+
+ <method name="removeUSBController">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Removes a USB controller from the machine.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ A USB controller with given type doesn't exist.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in"/>
+ </method>
+
+ <method name="getUSBControllerByName" const="yes">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns a USB controller with the given type.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ A USB controller with given name doesn't exist.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in"/>
+ <param name="controller" type="IUSBController" dir="return"/>
+ </method>
+
+ <method name="getUSBControllerCountByType" const="yes">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns the number of USB controllers of the given type attached to the VM.
+ </desc>
+ <param name="type" type="USBControllerType" dir="in"/>
+ <param name="controllers" type="unsigned long" dir="return"/>
+ </method>
+
+ <method name="getSerialPort" const="yes">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns the serial port associated with the given slot.
+ Slots are numbered sequentially, starting with zero. The total
+ number of serial ports per machine is defined by the
+ <link to="ISystemProperties::serialPortCount"/> property,
+ so the maximum slot number is one less than that property's value.
+
+ <result name="E_INVALIDARG">
+ Invalid @a slot number.
+ </result>
+
+ </desc>
+ <param name="slot" type="unsigned long" dir="in"/>
+ <param name="port" type="ISerialPort" dir="return"/>
+ </method>
+
+ <method name="getParallelPort" const="yes">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns the parallel port associated with the given slot.
+ Slots are numbered sequentially, starting with zero. The total
+ number of parallel ports per machine is defined by the
+ <link to="ISystemProperties::parallelPortCount"/> property,
+ so the maximum slot number is one less than that property's value.
+
+ <result name="E_INVALIDARG">
+ Invalid @a slot number.
+ </result>
+
+ </desc>
+ <param name="slot" type="unsigned long" dir="in"/>
+ <param name="port" type="IParallelPort" dir="return"/>
+ </method>
+
+ <method name="getExtraDataKeys">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns an array representing the machine-specific extra data keys
+ which currently have values defined.
+ </desc>
+ <param name="keys" type="wstring" dir="return" safearray="yes">
+ <desc>Array of extra data keys.</desc>
+ </param>
+ </method>
+
+ <method name="getExtraData">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns associated machine-specific extra data.
+
+ If the requested data @a key does not exist, this function will
+ succeed and return an empty string in the @a value argument.
+
+ <result name="VBOX_E_FILE_ERROR">
+ Settings file not accessible.
+ </result>
+ <result name="VBOX_E_XML_ERROR">
+ Could not parse the settings file.
+ </result>
+
+ </desc>
+ <param name="key" type="wstring" dir="in">
+ <desc>Name of the data key to get.</desc>
+ </param>
+ <param name="value" type="wstring" dir="return">
+ <desc>Value of the requested data key.</desc>
+ </param>
+ </method>
+
+ <method name="setExtraData">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Sets associated machine-specific extra data.
+
+ If you pass @c null or an empty string as a key @a value, the given
+ @a key will be deleted.
+
+ <note>
+ Key must contain printable (non-control) UTF-8 characters only.
+ </note>
+ <note>
+ Before performing the actual data change, this method will ask all
+ registered event listeners using the
+ <link to="IExtraDataCanChangeEvent"/>
+ notification for a permission. If one of the listeners refuses the
+ new value, the change will not be performed.
+ </note>
+ <note>
+ On success, the
+ <link to="IExtraDataChangedEvent"/> notification
+ is called to inform all registered listeners about a successful data
+ change.
+ </note>
+ <note>
+ This method can be called outside the machine session and therefore
+ it's a caller's responsibility to handle possible race conditions
+ when several clients change the same key at the same time.
+ </note>
+
+ <result name="VBOX_E_FILE_ERROR">
+ Settings file not accessible.
+ </result>
+ <result name="VBOX_E_XML_ERROR">
+ Could not parse the settings file.
+ </result>
+ <result name="E_INVALIDARG">
+ Key contains invalid characters.
+ </result>
+
+ </desc>
+ <param name="key" type="wstring" dir="in">
+ <desc>Name of the data key to set.</desc>
+ </param>
+ <param name="value" type="wstring" dir="in">
+ <desc>Value to assign to the key.</desc>
+ </param>
+ </method>
+
+ <method name="getCPUProperty" const="yes">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns the virtual CPU boolean value of the specified property.
+
+ <result name="E_INVALIDARG">
+ Invalid property.
+ </result>
+
+ </desc>
+ <param name="property" type="CPUPropertyType" dir="in">
+ <desc>
+ Property type to query.
+ </desc>
+ </param>
+ <param name="value" type="boolean" dir="return">
+ <desc>
+ Property value.
+ </desc>
+ </param>
+ </method>
+
+ <method name="setCPUProperty">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Sets the virtual CPU boolean value of the specified property.
+
+ <result name="E_INVALIDARG">
+ Invalid property.
+ </result>
+
+ </desc>
+ <param name="property" type="CPUPropertyType" dir="in">
+ <desc>
+ Property type to query.
+ </desc>
+ </param>
+ <param name="value" type="boolean" dir="in">
+ <desc>
+ Property value.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getCPUIDLeafByOrdinal" const="yes">
+ <desc>
+ Used to enumerate CPUID information override values.
+
+ <result name="E_INVALIDARG">
+ Invalid ordinal number is out of range.
+ </result>
+ </desc>
+ <param name="ordinal" type="unsigned long" dir="in">
+ <desc>
+ The ordinal number of the leaf to get.
+ </desc>
+ </param>
+ <param name="idx" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf index.
+ </desc>
+ </param>
+ <param name="idxSub" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf sub-index.
+ </desc>
+ </param>
+ <param name="valEax" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf value for register eax.
+ </desc>
+ </param>
+ <param name="valEbx" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf value for register ebx.
+ </desc>
+ </param>
+ <param name="valEcx" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf value for register ecx.
+ </desc>
+ </param>
+ <param name="valEdx" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf value for register edx.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getCPUIDLeaf" const="yes">
+ <desc>
+ Returns the virtual CPU cpuid information for the specified leaf.
+
+ Currently supported index values for cpuid:
+ Standard CPUID leaves: 0 - 0x1f
+ Extended CPUID leaves: 0x80000000 - 0x8000001f
+ VIA CPUID leaves: 0xc0000000 - 0xc000000f
+
+ See the Intel, AMD and VIA programmer's manuals for detailed information
+ about the CPUID instruction and its leaves.
+ <result name="E_INVALIDARG">
+ Invalid index.
+ </result>
+
+ </desc>
+ <param name="idx" type="unsigned long" dir="in">
+ <desc>
+ CPUID leaf index.
+ </desc>
+ </param>
+ <param name="idxSub" type="unsigned long" dir="in">
+ <desc>
+ CPUID leaf sub-index (ECX). Set to 0xffffffff (or 0) if not applicable.
+ </desc>
+ </param>
+ <param name="valEax" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf value for register eax.
+ </desc>
+ </param>
+ <param name="valEbx" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf value for register ebx.
+ </desc>
+ </param>
+ <param name="valEcx" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf value for register ecx.
+ </desc>
+ </param>
+ <param name="valEdx" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf value for register edx.
+ </desc>
+ </param>
+ </method>
+
+ <method name="setCPUIDLeaf">
+ <desc>
+ Sets the virtual CPU cpuid information for the specified leaf. Note that these values
+ are not passed unmodified. VirtualBox clears features that it doesn't support.
+
+ Currently supported index values for cpuid:
+ Standard CPUID leaves: 0 - 0x1f
+ Extended CPUID leaves: 0x80000000 - 0x8000001f
+ VIA CPUID leaves: 0xc0000000 - 0xc000000f
+
+ The subleaf index is only applicable to certain leaves (see manuals as this is
+ subject to change).
+
+ See the Intel, AMD and VIA programmer's manuals for detailed information
+ about the cpuid instruction and its leaves.
+
+ Do not use this method unless you know exactly what you're doing. Misuse can lead to
+ random crashes inside VMs.
+ <result name="E_INVALIDARG">
+ Invalid index.
+ </result>
+
+ </desc>
+ <param name="idx" type="unsigned long" dir="in">
+ <desc>
+ CPUID leaf index.
+ </desc>
+ </param>
+ <param name="idxSub" type="unsigned long" dir="in">
+ <desc>
+ CPUID leaf sub-index (ECX). Set to 0xffffffff (or 0) if not applicable.
+ The 0xffffffff causes it to remove all other subleaves before adding one
+ with sub-index 0.
+ </desc>
+ </param>
+ <param name="valEax" type="unsigned long" dir="in">
+ <desc>
+ CPUID leaf value for register eax.
+ </desc>
+ </param>
+ <param name="valEbx" type="unsigned long" dir="in">
+ <desc>
+ CPUID leaf value for register ebx.
+ </desc>
+ </param>
+ <param name="valEcx" type="unsigned long" dir="in">
+ <desc>
+ CPUID leaf value for register ecx.
+ </desc>
+ </param>
+ <param name="valEdx" type="unsigned long" dir="in">
+ <desc>
+ CPUID leaf value for register edx.
+ </desc>
+ </param>
+ </method>
+
+ <method name="removeCPUIDLeaf">
+ <desc>
+ Removes the virtual CPU cpuid leaf for the specified index
+
+ <result name="E_INVALIDARG">
+ Invalid index.
+ </result>
+
+ </desc>
+ <param name="idx" type="unsigned long" dir="in">
+ <desc>
+ CPUID leaf index.
+ </desc>
+ </param>
+ <param name="idxSub" type="unsigned long" dir="in">
+ <desc>
+ CPUID leaf sub-index (ECX). Set to 0xffffffff (or 0) if not applicable.
+ The 0xffffffff value works like a wildcard.
+ </desc>
+ </param>
+ </method>
+
+ <method name="removeAllCPUIDLeaves">
+ <desc>
+ Removes all the virtual CPU cpuid leaves
+ </desc>
+ </method>
+
+ <method name="getHWVirtExProperty" const="yes">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns the value of the specified hardware virtualization boolean property.
+
+ <result name="E_INVALIDARG">
+ Invalid property.
+ </result>
+
+ </desc>
+ <param name="property" type="HWVirtExPropertyType" dir="in">
+ <desc>
+ Property type to query.
+ </desc>
+ </param>
+ <param name="value" type="boolean" dir="return">
+ <desc>
+ Property value.
+ </desc>
+ </param>
+ </method>
+
+ <method name="setHWVirtExProperty">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Sets a new value for the specified hardware virtualization boolean property.
+
+ <result name="E_INVALIDARG">
+ Invalid property.
+ </result>
+
+ </desc>
+ <param name="property" type="HWVirtExPropertyType" dir="in">
+ <desc>
+ Property type to set.
+ </desc>
+ </param>
+ <param name="value" type="boolean" dir="in">
+ <desc>
+ New property value.
+ </desc>
+ </param>
+ </method>
+
+ <method name="setSettingsFilePath">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Currently, it is an error to change this property on any machine.
+ Later this will allow setting a new path for the settings file, with
+ automatic relocation of all files (including snapshots and disk images)
+ which are inside the base directory. This operation is only allowed
+ when there are no pending unsaved settings.
+
+ <note>
+ Setting this property to @c null or to an empty string is forbidden.
+ When setting this property, the specified path must be absolute.
+ The specified path may not exist, it will be created when necessary.
+ </note>
+
+ <result name="E_NOTIMPL">
+ The operation is not implemented yet.
+ </result>
+ </desc>
+
+ <param name="settingsFilePath" type="wstring" dir="in">
+ <desc>New settings file path, will be used to determine the new
+ location for the attached media if it is in the same directory or
+ below as the original settings file.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="saveSettings">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Saves any changes to machine settings made since the session
+ has been opened or a new machine has been created, or since the
+ last call to <link to="#saveSettings"/> or <link to="#discardSettings"/>.
+ For registered machines, new settings become visible to all
+ other VirtualBox clients after successful invocation of this
+ method.
+ <note>
+ The method sends <link to="IMachineDataChangedEvent"/>
+ notification event after the configuration has been successfully
+ saved (only for registered machines).
+ </note>
+ <note>
+ Calling this method is only valid on instances returned
+ by <link to="ISession::machine"/> and on new machines
+ created by <link to="IVirtualBox::createMachine"/> but not
+ yet registered, or on unregistered machines after calling
+ <link to="IMachine::unregister"/>.
+ </note>
+
+ <result name="VBOX_E_FILE_ERROR">
+ Settings file not accessible.
+ </result>
+ <result name="VBOX_E_XML_ERROR">
+ Could not parse the settings file.
+ </result>
+ <result name="E_ACCESSDENIED">
+ Modification request refused.
+ </result>
+
+ </desc>
+ </method>
+
+ <method name="discardSettings">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Discards any changes to the machine settings made since the session
+ has been opened or since the last call to <link to="#saveSettings"/>
+ or <link to="#discardSettings"/>.
+ <note>
+ Calling this method is only valid on instances returned
+ by <link to="ISession::machine"/> and on new machines
+ created by <link to="IVirtualBox::createMachine"/> or
+ opened by <link to="IVirtualBox::openMachine"/> but not
+ yet registered, or on unregistered machines after calling
+ <link to="IMachine::unregister"/>.
+ </note>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine is not mutable.
+ </result>
+
+ </desc>
+ </method>
+
+ <method name="unregister" wrap-hint-server="limitedcaller,passcaller">
+ <rest request="post" path="/vms/{vmid}/actions/"/>
+ <desc>
+ Unregisters a machine previously registered with
+ <link to="IVirtualBox::registerMachine"/> and optionally do additional
+ cleanup before the machine is unregistered.
+
+ This method does not delete any files. It only changes the machine configuration and
+ the list of registered machines in the VirtualBox object. To delete the files which
+ belonged to the machine, including the XML file of the machine itself, call
+ <link to="#deleteConfig"/>, optionally with the array of IMedium objects which was returned
+ from this method.
+
+ How thoroughly this method cleans up the machine configuration before unregistering
+ the machine depends on the @a cleanupMode argument.
+
+ <ul>
+ <li>With "UnregisterOnly", the machine will only be unregistered, but no additional
+ cleanup will be performed. The call will fail if the machine has any snapshots or
+ any media attached (see <link to="IMediumAttachment" />). It is the responsibility
+ of the caller to delete all such configuration in this mode. In this mode, the API
+ behaves like the former @c IVirtualBox::unregisterMachine() API which it replaces.</li>
+ <li>With "DetachAllReturnNone", the call will succeed even if the machine is in "Saved"
+ state or if it has snapshots or media attached. All media attached to the current machine
+ state or in snapshots will be detached. No medium objects will be returned;
+ all of the machine's media will remain open.</li>
+ <li>With "DetachAllReturnHardDisksOnly", the call will behave like with "DetachAllReturnNone",
+ except that all the hard disk medium objects which were detached from the machine will
+ be returned as an array. This allows for quickly passing them to the <link to="#deleteConfig" />
+ API for closing and deletion.</li>
+ <li>With "Full", the call will behave like with "DetachAllReturnHardDisksOnly", except
+ that all media will be returned in the array, including removable media like DVDs and
+ floppies. This might be useful if the user wants to inspect in detail which media were
+ attached to the machine. Be careful when passing the media array to <link to="#deleteConfig" />
+ in that case because users will typically want to preserve ISO and RAW image files.</li>
+ </ul>
+
+ A typical implementation will use "DetachAllReturnHardDisksOnly" and then pass the
+ resulting IMedium array to <link to="#deleteConfig"/>. This way, the machine is completely
+ deleted with all its saved states and hard disk images, but images for removable
+ drives (such as ISO and RAW files) will remain on disk.
+
+ This API does not verify whether the media files returned in the array are still
+ attached to other machines (i.e. shared between several machines). If such a shared
+ image is passed to <link to="#deleteConfig" /> however, closing the image will fail there
+ and the image will be silently skipped.
+
+ This API may, however, move media from this machine's media registry to other media
+ registries (see <link to="IMedium" /> for details on media registries). For machines
+ created with VirtualBox 4.0 or later, if media from this machine's media registry
+ are also attached to another machine (shared attachments), each such medium will be
+ moved to another machine's registry. This is because without this machine's media
+ registry, the other machine cannot find its media any more and would become inaccessible.
+
+ This API implicitly calls <link to="#saveSettings"/> to save all current machine settings
+ before unregistering it. It may also silently call <link to="#saveSettings"/> on other machines
+ if media are moved to other machines' media registries.
+
+ After successful method invocation, the <link to="IMachineRegisteredEvent"/> event
+ is fired.
+
+ The call will fail if the machine is currently locked (see <link to="ISession" />).
+
+ <note>
+ If the given machine is inaccessible (see <link to="#accessible"/>), it
+ will be unregistered and fully uninitialized right afterwards. As a result,
+ the returned machine object will be unusable and an attempt to call
+ <b>any</b> method will return the "Object not ready" error.
+ </note>
+
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Machine is currently locked for a session.
+ </result>
+ </desc>
+
+ <param name="cleanupMode" type="CleanupMode" dir="in">
+ <desc>How to clean up after the machine has been unregistered.</desc>
+ </param>
+ <param name="media" type="IMedium" safearray="yes" dir="return">
+ <desc>List of media detached from the machine, depending on the @a cleanupMode parameter.</desc>
+ </param>
+ </method>
+
+ <method name="deleteConfig">
+ <rest name="remove" request="delete" path="/vms/{vmid}/"/>
+ <desc>
+ Deletes the files associated with this machine from disk. If medium objects are passed
+ in with the @a aMedia argument, they are closed and, if closing was successful, their
+ storage files are deleted as well. For convenience, this array of media files can be
+ the same as the one returned from a previous <link to="#unregister" /> call.
+
+ This method must only be called on machines which are either write-locked (i.e. on instances
+ returned by <link to="ISession::machine"/>) or on unregistered machines (i.e. not yet
+ registered machines created by <link to="IVirtualBox::createMachine"/> or opened by
+ <link to="IVirtualBox::openMachine"/>, or after having called <link to="#unregister"/>).
+
+ The following files will be deleted by this method:
+ <ul>
+ <li>If <link to="#unregister" /> had been previously called with a @a cleanupMode
+ argument other than "UnregisterOnly", this will delete all saved state files that
+ the machine had in use; possibly one if the machine was in either the "Saved" or
+ "AbortedSaved" state and one for each online snapshot that the machine had.</li>
+ <li>On each medium object passed in the @a aMedia array, this will call
+ <link to="IMedium::close" />. If that succeeds, this will attempt to delete the
+ medium's storage on disk. Since the <link to="IMedium::close"/> call will fail if the medium is still
+ in use, e.g. because it is still attached to a second machine; in that case the
+ storage will not be deleted.</li>
+ <li>Finally, the machine's own XML file will be deleted.</li>
+ </ul>
+
+ Since deleting large disk image files can be a time-consuming I/O operation, this
+ method operates asynchronously and returns an IProgress object to allow the caller
+ to monitor the progress. There will be one sub-operation for each file that is
+ being deleted (saved state or medium storage file).
+
+ <note>
+ <link to="#settingsModified"/> will return @c true after this
+ method successfully returns.
+ </note>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Machine is registered but not write-locked.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ Could not delete the settings file.
+ </result>
+ </desc>
+ <param name="media" type="IMedium" safearray="yes" dir="in">
+ <desc>List of media to be closed and whose storage files will be deleted.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="exportTo">
+ <rest name="export" request="post" path="/vms/{vmid}/actions/"/>
+ <desc>Exports the machine to an OVF appliance. See <link to="IAppliance" /> for the
+ steps required to export VirtualBox machines to OVF.
+ </desc>
+
+ <param name="appliance" type="IAppliance" dir="in">
+ <desc>Appliance to export this machine to.</desc>
+ </param>
+ <param name="location" type="wstring" dir="in">
+ <desc>The target location.</desc>
+ </param>
+ <param name="description" type="IVirtualSystemDescription" dir="return">
+ <desc>VirtualSystemDescription object which is created for this machine.</desc>
+ </param>
+ </method>
+
+ <method name="findSnapshot">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns a snapshot of this machine with the given name or UUID.
+
+ Returns a snapshot of this machine with the given UUID.
+ A @c null argument can be used to obtain the first snapshot
+ taken on this machine. To traverse the whole tree of snapshots
+ starting from the root, inspect the root snapshot's
+ <link to="ISnapshot::children" /> attribute and recurse over those children.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Virtual machine has no snapshots or snapshot not found.
+ </result>
+
+ </desc>
+ <param name="nameOrId" type="wstring" dir="in">
+ <desc>What to search for. Name or UUID of the snapshot to find</desc>
+ </param>
+ <param name="snapshot" type="ISnapshot" dir="return">
+ <desc>Snapshot object with the given name.</desc>
+ </param>
+ </method>
+
+ <method name="createSharedFolder">
+ <rest request="post" path="/vms/{vmid}/methods/"/>
+ <desc>
+ Creates a new permanent shared folder by associating the given logical
+ name with the given host path, adds it to the collection of shared
+ folders and starts sharing it. Refer to the description of
+ <link to="ISharedFolder"/> to read more about logical names.
+
+ <result name="VBOX_E_OBJECT_IN_USE">
+ Shared folder already exists.
+ </result>
+ <result name="VBOX_E_FILE_ERROR">
+ Shared folder @a hostPath not accessible.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Unique logical name of the shared folder.</desc>
+ </param>
+ <param name="hostPath" type="wstring" dir="in">
+ <desc>Full path to the shared folder in the host file system.</desc>
+ </param>
+ <param name="writable" type="boolean" dir="in">
+ <desc>Whether the share is writable or read-only.</desc>
+ </param>
+ <param name="automount" type="boolean" dir="in">
+ <desc>Whether the share gets automatically mounted by the guest
+ or not.</desc>
+ </param>
+ <param name="autoMountPoint" type="wstring" dir="in">
+ <desc>Where the guest should automatically mount the folder, if possible.
+ For Windows and OS/2 guests this should be a drive letter, while other
+ guests it should be a absolute directory.
+ </desc>
+ </param>
+ </method>
+
+ <method name="removeSharedFolder">
+ <rest request="delete" path="/vms/{vmid}/methods/"/>
+ <desc>
+ Removes the permanent shared folder with the given name previously
+ created by <link to="#createSharedFolder"/> from the collection of
+ shared folders and stops sharing it.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine is not mutable.
+ </result>
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Shared folder @a name does not exist.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Logical name of the shared folder to remove.</desc>
+ </param>
+ </method>
+
+ <method name="canShowConsoleWindow">
+ <desc>
+ Returns @c true if the VM console process can activate the
+ console window and bring it to foreground on the desktop of
+ the host PC.
+ <note>
+ This method will fail if a session for this machine is not
+ currently open.
+ </note>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Machine session is not open.
+ </result>
+
+ </desc>
+ <param name="canShow" type="boolean" dir="return">
+ <desc>
+ @c true if the console window can be shown and @c false otherwise.
+ </desc>
+ </param>
+ </method>
+
+ <method name="showConsoleWindow">
+ <desc>
+ Activates the console window and brings it to foreground on
+ the desktop of the host PC. Many modern window managers on
+ many platforms implement some sort of focus stealing
+ prevention logic, so that it may be impossible to activate
+ a window without the help of the currently active
+ application. In this case, this method will return a non-zero
+ identifier that represents the top-level window of the VM
+ console process. The caller, if it represents a currently
+ active process, is responsible to use this identifier (in a
+ platform-dependent manner) to perform actual window
+ activation.
+ <note>
+ This method will fail if a session for this machine is not
+ currently open.
+ </note>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Machine session is not open.
+ </result>
+
+ </desc>
+ <param name="winId" type="long long" dir="return">
+ <desc>
+ Platform-dependent identifier of the top-level VM console
+ window, or zero if this method has performed all actions
+ necessary to implement the <i>show window</i> semantics for
+ the given platform and/or VirtualBox front-end.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getGuestProperty" const="yes">
+ <rest name="Property" request="get" path="/vms/{vmid}/guest/"/>
+
+ <desc>
+ Reads an entry from the machine's guest property store.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Machine session is not open.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>
+ The name of the property to read.
+ </desc>
+ </param>
+ <param name="value" type="wstring" dir="out">
+ <desc>
+ The value of the property. If the property does not exist then this
+ will be empty.
+ </desc>
+ </param>
+ <param name="timestamp" type="long long" dir="out">
+ <desc>
+ The time at which the property was last modified, as seen by the
+ server process.
+ </desc>
+ </param>
+ <param name="flags" type="wstring" dir="out">
+ <desc>
+ Additional property parameters, passed as a comma-separated list of
+ "name=value" type entries.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getGuestPropertyValue" const="yes">
+ <desc>
+ Reads a value from the machine's guest property store.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Machine session is not open.
+ </result>
+
+ </desc>
+ <param name="property" type="wstring" dir="in">
+ <desc>
+ The name of the property to read.
+ </desc>
+ </param>
+ <param name="value" type="wstring" dir="return">
+ <desc>
+ The value of the property. If the property does not exist then this
+ will be empty.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getGuestPropertyTimestamp" const="yes">
+ <desc>
+ Reads a property timestamp from the machine's guest property store.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Machine session is not open.
+ </result>
+
+ </desc>
+ <param name="property" type="wstring" dir="in">
+ <desc>
+ The name of the property to read.
+ </desc>
+ </param>
+ <param name="value" type="long long" dir="return">
+ <desc>
+ The timestamp. If the property does not exist then this will be
+ empty.
+ </desc>
+ </param>
+ </method>
+
+ <method name="setGuestProperty">
+ <rest name="Property" request="patch" path="/vms/{vmid}/guest/"/>
+ <desc>
+ Sets, changes or deletes an entry in the machine's guest property
+ store.
+
+ <result name="E_ACCESSDENIED">
+ Property cannot be changed.
+ </result>
+ <result name="E_INVALIDARG">
+ Invalid @a flags.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine is not mutable or session not open.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Cannot set transient property when machine not running.
+ </result>
+
+ </desc>
+ <param name="property" type="wstring" dir="in">
+ <desc>
+ The name of the property to set, change or delete.
+ </desc>
+ </param>
+ <param name="value" type="wstring" dir="in">
+ <desc>
+ The new value of the property to set, change or delete. If the
+ property does not yet exist and value is non-empty, it will be
+ created. If the value is @c null or empty, the property will be
+ deleted if it exists.
+ </desc>
+ </param>
+ <param name="flags" type="wstring" dir="in">
+ <desc>
+ Additional property parameters, passed as a comma-separated list of
+ "name=value" type entries.
+ </desc>
+ </param>
+ </method>
+
+ <method name="setGuestPropertyValue">
+ <desc>
+ Sets or changes a value in the machine's guest property
+ store. The flags field will be left unchanged or created empty for a
+ new property.
+
+ <result name="E_ACCESSDENIED">
+ Property cannot be changed.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine is not mutable or session not open.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Cannot set transient property when machine not running.
+ </result>
+ </desc>
+
+ <param name="property" type="wstring" dir="in">
+ <desc>
+ The name of the property to set or change.
+ </desc>
+ </param>
+ <param name="value" type="wstring" dir="in">
+ <desc>
+ The new value of the property to set or change. If the
+ property does not yet exist and value is non-empty, it will be
+ created.
+ </desc>
+ </param>
+ </method>
+
+ <method name="deleteGuestProperty" const="yes">
+ <rest name="Property" request="delete" path="/vms/{vmid}/guest/"/>
+ <desc>
+ Deletes an entry from the machine's guest property store.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Machine session is not open.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>
+ The name of the property to delete.
+ </desc>
+ </param>
+ </method>
+
+ <method name="enumerateGuestProperties" const="yes">
+ <desc>
+ Return a list of the guest properties matching a set of patterns along
+ with their values, timestamps and flags.
+ </desc>
+ <param name="patterns" type="wstring" dir="in">
+ <desc>
+ The patterns to match the properties against, separated by '|'
+ characters. If this is empty or @c null, all properties will match.
+ </desc>
+ </param>
+ <param name="names" type="wstring" dir="out" safearray="yes">
+ <desc>
+ The names of the properties returned.
+ </desc>
+ </param>
+ <param name="values" type="wstring" dir="out" safearray="yes">
+ <desc>
+ The values of the properties returned. The array entries match the
+ corresponding entries in the @a name array.
+ </desc>
+ </param>
+ <param name="timestamps" type="long long" dir="out" safearray="yes">
+ <desc>
+ The timestamps of the properties returned. The array entries match
+ the corresponding entries in the @a name array.
+ </desc>
+ </param>
+ <param name="flags" type="wstring" dir="out" safearray="yes">
+ <desc>
+ The flags of the properties returned. The array entries match the
+ corresponding entries in the @a name array.
+ </desc>
+ </param>
+ </method>
+
+ <method name="querySavedGuestScreenInfo" const="yes">
+ <desc>
+ Returns the guest dimensions from the saved state.
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>
+ Saved guest screen to query info from.
+ </desc>
+ </param>
+ <param name="originX" type="unsigned long" dir="out">
+ <desc>
+ The X position of the guest monitor top left corner.
+ </desc>
+ </param>
+ <param name="originY" type="unsigned long" dir="out">
+ <desc>
+ The Y position of the guest monitor top left corner.
+ </desc>
+ </param>
+ <param name="width" type="unsigned long" dir="out">
+ <desc>
+ Guest width at the time of the saved state was taken.
+ </desc>
+ </param>
+ <param name="height" type="unsigned long" dir="out">
+ <desc>
+ Guest height at the time of the saved state was taken.
+ </desc>
+ </param>
+ <param name="enabled" type="boolean" dir="out">
+ <desc>
+ Whether the monitor is enabled in the guest.
+ </desc>
+ </param>
+ </method>
+
+ <method name="readSavedThumbnailToArray">
+ <desc>
+ Thumbnail is retrieved to an array of bytes in the requested format.
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>
+ Saved guest screen to read from.
+ </desc>
+ </param>
+ <param name="bitmapFormat" type="BitmapFormat" dir="in">
+ <desc>
+ The requested format.
+ </desc>
+ </param>
+ <param name="width" type="unsigned long" dir="out">
+ <desc>
+ Bitmap width.
+ </desc>
+ </param>
+ <param name="height" type="unsigned long" dir="out">
+ <desc>
+ Bitmap height.
+ </desc>
+ </param>
+ <param name="data" type="octet" safearray="yes" dir="return">
+ <desc>
+ Array with resulting bitmap data.
+ </desc>
+ </param>
+ </method>
+
+ <method name="querySavedScreenshotInfo">
+ <desc>
+ Returns available formats and size of the screenshot from saved state.
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>
+ Saved guest screen to query info from.
+ </desc>
+ </param>
+ <param name="width" type="unsigned long" dir="out">
+ <desc>
+ Image width.
+ </desc>
+ </param>
+ <param name="height" type="unsigned long" dir="out">
+ <desc>
+ Image height.
+ </desc>
+ </param>
+ <param name="bitmapFormats" type="BitmapFormat" safearray="yes" dir="return">
+ <desc>
+ Formats supported by readSavedScreenshotToArray.
+ </desc>
+ </param>
+ </method>
+
+ <method name="readSavedScreenshotToArray">
+ <desc>
+ Screenshot in requested format is retrieved to an array of bytes.
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>
+ Saved guest screen to read from.
+ </desc>
+ </param>
+ <param name="bitmapFormat" type="BitmapFormat" dir="in">
+ <desc>
+ The requested format.
+ </desc>
+ </param>
+ <param name="width" type="unsigned long" dir="out">
+ <desc>
+ Image width.
+ </desc>
+ </param>
+ <param name="height" type="unsigned long" dir="out">
+ <desc>
+ Image height.
+ </desc>
+ </param>
+ <param name="data" type="octet" dir="return" safearray="yes">
+ <desc>
+ Array with resulting image data.
+ </desc>
+ </param>
+ </method>
+
+ <method name="hotPlugCPU">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Plugs a CPU into the machine.
+ </desc>
+ <param name="cpu" type="unsigned long" dir="in">
+ <desc>
+ The CPU id to insert.
+ </desc>
+ </param>
+ </method>
+
+ <method name="hotUnplugCPU">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Removes a CPU from the machine.
+ </desc>
+ <param name="cpu" type="unsigned long" dir="in">
+ <desc>
+ The CPU id to remove.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getCPUStatus">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns the current status of the given CPU.
+ </desc>
+ <param name="cpu" type="unsigned long" dir="in">
+ <desc>
+ The CPU id to check for.
+ </desc>
+ </param>
+ <param name="attached" type="boolean" dir="return">
+ <desc>
+ Status of the CPU.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getEffectiveParavirtProvider" const="yes">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Returns the effective paravirtualization provider for this VM.
+ </desc>
+ <param name="paravirtProvider" type="ParavirtProvider" dir="return">
+ <desc>
+ The effective paravirtualization provider for this VM.
+ </desc>
+ </param>
+ </method>
+
+ <method name="queryLogFilename">
+ <rest request="get" path="/vms/{vmid}/logs/"/>
+ <desc>
+ Queries for the VM log file name of an given index. Returns an empty
+ string if a log file with that index doesn't exists.
+ </desc>
+ <param name="idx" type="unsigned long" dir="in">
+ <desc>
+ Which log file name to query. 0=current log file.
+ </desc>
+ </param>
+ <param name="filename" type="wstring" dir="return">
+ <desc>
+ On return the full path to the log file or an empty string on error.
+ </desc>
+ </param>
+ </method>
+
+ <method name="readLog">
+ <desc>
+ Reads the VM log file. The chunk size is limited, so even if you
+ ask for a big piece there might be less data returned.
+ </desc>
+ <param name="idx" type="unsigned long" dir="in">
+ <desc>
+ Which log file to read. 0=current log file.
+ </desc>
+ </param>
+ <param name="offset" type="long long" dir="in">
+ <desc>
+ Offset in the log file.
+ </desc>
+ </param>
+ <param name="size" type="long long" dir="in">
+ <desc>
+ Chunk size to read in the log file.
+ </desc>
+ </param>
+ <param name="data" type="octet" dir="return" safearray="yes">
+ <desc>
+ Data read from the log file. A data size of 0 means end of file
+ if the requested chunk size was not 0. This is the unprocessed
+ file data, i.e. the line ending style depends on the platform of
+ the system the server is running on.
+ </desc>
+ </param>
+ </method>
+
+ <method name="cloneTo">
+ <rest name="clone" request="put" path="/vms/{vmid}/actions/"/>
+ <desc>
+ Creates a clone of this machine, either as a full clone (which means
+ creating independent copies of the hard disk media, save states and so
+ on), or as a linked clone (which uses its own differencing media,
+ sharing the parent media with the source machine).
+
+ The target machine object must have been created previously with <link
+ to="IVirtualBox::createMachine"/>, and all the settings will be
+ transferred except the VM name and the hardware UUID. You can set the
+ VM name and the new hardware UUID when creating the target machine. The
+ network MAC addresses are newly created for all enabled network
+ adapters. You can change that behaviour with the options parameter.
+ The operation is performed asynchronously, so the machine object will
+ be not be usable until the @a progress object signals completion.
+
+ <result name="E_INVALIDARG">
+ @a target is @c null.
+ </result>
+ </desc>
+
+ <param name="target" type="IMachine" dir="in">
+ <desc>Target machine object.</desc>
+ </param>
+ <param name="mode" type="CloneMode" dir="in">
+ <desc>Which states should be cloned.</desc>
+ </param>
+ <param name="options" type="CloneOptions" dir="in" safearray="yes">
+ <desc>Options for the cloning operation.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="moveTo">
+ <rest name="move" request="post" path="/vms/{vmid}/actions/"/>
+ <desc>
+ Move machine on to new place/folder
+ </desc>
+
+ <param name="folder" type="wstring" dir="in">
+ <desc>Target folder where machine is moved. May be the same folder
+ where the VM already is located or the empty string, in which
+ case the machine is kept in this location and the disk images
+ and other files which are stored elsewhere are moved.</desc>
+ </param>
+
+ <param name="type" type="wstring" dir="in">
+ <desc>Type of moving.
+ Possible values:
+ basic - Only the files which belong solely to this machine
+ are moved from the original machine's folder to
+ a new folder.
+ </desc>
+ </param>
+
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="saveState">
+ <rest name="save" request="post" path="/vms/{vmid}/actions/"/>
+ <desc>
+ Saves the current execution state of a running virtual machine
+ and stops its execution.
+
+ After this operation completes, the machine will go to the
+ Saved state. The next time it is powered up this state will
+ be restored and the machine will continue its execution from
+ the place where it was saved.
+
+ This operation differs from taking a snapshot to the effect
+ that it doesn't create new differencing media. Also, once
+ the machine is powered up from the state saved using this method,
+ the saved state is deleted, so it will be impossible to return
+ to this state later.
+
+ <note>
+ On success, this method implicitly calls
+ <link to="#saveSettings"/> to save all current machine
+ settings (including runtime changes to the DVD medium, etc.).
+ Together with the impossibility to change any VM settings when it is
+ in the Saved state, this guarantees adequate hardware
+ configuration of the machine when it is restored from the saved
+ state file.
+ </note>
+
+ <note>
+ The machine must be in the Running or Paused state, otherwise
+ the operation will fail.
+ </note>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine state neither Running nor Paused.
+ </result>
+ <result name="VBOX_E_FILE_ERROR">
+ Failed to create directory for saved state file.
+ </result>
+
+ <see><link to="#takeSnapshot"/></see>
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="adoptSavedState">
+ <desc>
+ Associates the given saved state file to the virtual machine.
+
+ On success, the machine will go to the Saved state. The next time it is
+ powered up it will be restored from the adopted saved state and
+ continue execution from the place where the saved state file was
+ created.
+
+ The specified saved state file path may be absolute or relative to the
+ folder the VM normally saves the state to (usually,
+ <link to="#snapshotFolder"/>).
+
+ <note>
+ It's a caller's responsibility to make sure the given saved state
+ file is compatible with the settings of this virtual machine that
+ represent its virtual hardware (memory size, storage disk configuration
+ etc.). If there is a mismatch, the behavior of the virtual machine
+ is undefined.
+ </note>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine state neither PoweredOff nor Aborted.
+ </result>
+ </desc>
+ <param name="savedStateFile" type="wstring" dir="in">
+ <desc>Path to the saved state file to adopt.</desc>
+ </param>
+ </method>
+
+ <method name="discardSavedState">
+ <rest request="post" path="/vms/{vmid}/actions/"/>
+ <desc>
+ Forcibly resets the machine to "Powered Off" state if it is
+ currently in the "Saved" state previously created by <link to="#saveState"/>)
+ or in the "AbortedSaved" state. The next time the machine is powered up
+ a clean boot will occur.
+ <note>
+ This operation is equivalent to resetting or powering off
+ the machine without doing a proper shutdown of the guest
+ operating system; as with resetting a running phyiscal
+ computer, it can can lead to data loss.
+ </note>
+ If @a fRemoveFile is @c true, the file in the machine directory
+ into which the machine state was saved is also deleted. If
+ this is @c false, then the state can be recovered and later
+ re-inserted into a machine using <link to="#adoptSavedState" />.
+ The location of the file can be found in the
+ <link to="#stateFilePath" /> attribute.
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine not in either the Saved or AbortedSaved state.
+ </result>
+ </desc>
+ <param name="fRemoveFile" type="boolean" dir="in" >
+ <desc>Whether to also remove the saved state file.</desc>
+ </param>
+ </method>
+
+ <method name="takeSnapshot">
+ <rest request="put" path="/vms/{vmid}/actions/"/>
+ <desc>
+ Saves the current execution state
+ and all settings of the machine and creates differencing images
+ for all normal (non-independent) media.
+ See <link to="ISnapshot" /> for an introduction to snapshots.
+
+ This method can be called for a PoweredOff, Saved (see
+ <link to="#saveState"/>), AbortedSaved, Running or
+ Paused virtual machine. When the machine is PoweredOff, an
+ offline snapshot is created. When the machine is Running a live
+ snapshot is created, and an online snapshot is created when Paused.
+
+ The taken snapshot is always based on the
+ <link to="#currentSnapshot">current snapshot</link>
+ of the associated virtual machine and becomes a new current snapshot.
+
+ <note>
+ This method implicitly calls <link to="#saveSettings"/> to
+ save all current machine settings before taking an offline snapshot.
+ </note>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine currently changing state.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Short name for the snapshot.</desc>
+ </param>
+ <param name="description" type="wstring" dir="in">
+ <desc>Optional description of the snapshot.</desc>
+ </param>
+ <param name="pause" type="boolean" dir="in">
+ <desc>Whether the VM should be paused while taking the snapshot. Only
+ relevant when the VM is running, and distinguishes between online
+ (@c true) and live (@c false) snapshots. When the VM is not running
+ the result is always an offline snapshot.</desc>
+ </param>
+ <param name="id" type="uuid" mod="string" dir="out">
+ <desc>UUID of the snapshot which will be created. Useful for follow-up
+ operations after the snapshot has been created.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="deleteSnapshot">
+ <rest request="delete" path="/vms/{vmid}/actions/"/>
+ <desc>
+ Starts deleting the specified snapshot asynchronously.
+ See <link to="ISnapshot" /> for an introduction to snapshots.
+
+ The execution state and settings of the associated machine stored in
+ the snapshot will be deleted. The contents of all differencing media of
+ this snapshot will be merged with the contents of their dependent child
+ media to keep the medium chain valid (in other words, all changes
+ represented by media being deleted will be propagated to their child
+ medium). After that, this snapshot's differencing medium will be
+ deleted. The parent of this snapshot will become a new parent for all
+ its child snapshots.
+
+ If the deleted snapshot is the current one, its parent snapshot will
+ become a new current snapshot. The current machine state is not directly
+ affected in this case, except that currently attached differencing
+ media based on media of the deleted snapshot will be also merged as
+ described above.
+
+ If the deleted snapshot is the first or current snapshot, then the
+ respective IMachine attributes will be adjusted. Deleting the current
+ snapshot will also implicitly call <link to="#saveSettings"/>
+ to make all current machine settings permanent.
+
+ Deleting a snapshot has the following preconditions:
+
+ <ul>
+ <li>Child media of all normal media of the deleted snapshot
+ must be accessible (see <link to="IMedium::state"/>) for this
+ operation to succeed. If only one running VM refers to all images
+ which participates in merging the operation can be performed while
+ the VM is running. Otherwise all virtual machines whose media are
+ directly or indirectly based on the media of deleted snapshot must
+ be powered off. In any case, online snapshot deleting usually is
+ slower than the same operation without any running VM.</li>
+
+ <li>You cannot delete the snapshot if a medium attached to it has
+ more than one child medium (differencing images) because otherwise
+ merging would be impossible. This might be the case if there is
+ more than one child snapshot or differencing images were created
+ for other reason (e.g. implicitly because of multiple machine
+ attachments).</li>
+ </ul>
+
+ The virtual machine's <link to="#state">state</link> is
+ changed to "DeletingSnapshot", "DeletingSnapshotOnline" or
+ "DeletingSnapshotPaused" while this operation is in progress.
+
+ <note>
+ Merging medium contents can be very time and disk space
+ consuming, if these media are big in size and have many
+ children. However, if the snapshot being deleted is the last
+ (head) snapshot on the branch, the operation will be rather
+ quick.
+ </note>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ The running virtual machine prevents deleting this snapshot. This
+ happens only in very specific situations, usually snapshots can be
+ deleted without trouble while a VM is running. The error message
+ text explains the reason for the failure.
+ </result>
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in">
+ <desc>UUID of the snapshot to delete.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="deleteSnapshotAndAllChildren">
+ <rest request="post" path="/vms/{vmid}/actions/"/>
+ <desc>
+ Starts deleting the specified snapshot and all its children
+ asynchronously. See <link to="ISnapshot" /> for an introduction to
+ snapshots. The conditions and many details are the same as with
+ <link to="#deleteSnapshot"/>.
+
+ This operation is very fast if the snapshot subtree does not include
+ the current state. It is still significantly faster than deleting the
+ snapshots one by one if the current state is in the subtree and there
+ are more than one snapshots from current state to the snapshot which
+ marks the subtree, since it eliminates the incremental image merging.
+
+ <note>This API method is right now not implemented!</note>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ The running virtual machine prevents deleting this snapshot. This
+ happens only in very specific situations, usually snapshots can be
+ deleted without trouble while a VM is running. The error message
+ text explains the reason for the failure.
+ </result>
+ <result name="E_NOTIMPL">
+ The method is not implemented yet.
+ </result>
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in">
+ <desc>UUID of the snapshot to delete, including all its children.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="deleteSnapshotRange">
+ <rest request="post" path="/vms/{vmid}/actions/"/>
+ <desc>
+ Starts deleting the specified snapshot range. This is limited to
+ linear snapshot lists, which means there may not be any other child
+ snapshots other than the direct sequence between the start and end
+ snapshot. If the start and end snapshot point to the same snapshot this
+ method is completely equivalent to <link to="#deleteSnapshot"/>. See
+ <link to="ISnapshot" /> for an introduction to snapshots. The
+ conditions and many details are the same as with
+ <link to="#deleteSnapshot"/>.
+
+ This operation is generally faster than deleting snapshots one by one
+ and often also needs less extra disk space before freeing up disk space
+ by deleting the removed disk images corresponding to the snapshot.
+
+ <note>This API method is right now not implemented!</note>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ The running virtual machine prevents deleting this snapshot. This
+ happens only in very specific situations, usually snapshots can be
+ deleted without trouble while a VM is running. The error message
+ text explains the reason for the failure.
+ </result>
+ <result name="E_NOTIMPL">
+ The method is not implemented yet.
+ </result>
+ </desc>
+ <param name="startId" type="uuid" mod="string" dir="in">
+ <desc>UUID of the first snapshot to delete.</desc>
+ </param>
+ <param name="endId" type="uuid" mod="string" dir="in">
+ <desc>UUID of the last snapshot to delete.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="restoreSnapshot">
+ <rest request="post" path="/vms/{vmid}/actions/"/>
+ <desc>
+ Starts resetting the machine's current state to the state contained
+ in the given snapshot, asynchronously. All current settings of the
+ machine will be reset and changes stored in differencing media
+ will be lost.
+ See <link to="ISnapshot" /> for an introduction to snapshots.
+
+ After this operation is successfully completed, new empty differencing
+ media are created for all normal media of the machine.
+
+ If the given snapshot is an online snapshot, the machine will go to
+ the <link to="MachineState_Saved">Saved</link> state, so that the
+ next time it is powered on, the execution state will be restored
+ from the state of the snapshot.
+
+ <note>
+ The machine must not be running, otherwise the operation will fail.
+ </note>
+
+ <note>
+ If the machine is in the <link to="MachineState_Saved">Saved</link>
+ state prior to this operation, the saved state file will be implicitly
+ deleted (as if <link to="IMachine::discardSavedState"/> were
+ called).
+ </note>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine is running.
+ </result>
+ </desc>
+ <param name="snapshot" type="ISnapshot" dir="in">
+ <desc>The snapshot to restore the VM state from.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="applyDefaults">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Applies the defaults for the configured guest OS type. This is
+ primarily for getting sane settings straight after creating a
+ new VM, but it can also be applied later.
+
+ <note>
+ This is primarily a shortcut, centralizing the tedious job of
+ getting the recommended settings and translating them into
+ settings updates. The settings are made at the end of the call,
+ but not saved.
+ </note>
+
+ <result name="E_FAIL">
+ General error.
+ </result>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ The machine is in invalid state.
+ </result>
+ <result name="VBOX_E_OBJECT_IN_USE">
+ Some of the applied objects already exist. The method has been
+ called to already configured machine.
+ </result>
+ </desc>
+ <param name="flags" type="wstring" dir="in">
+ <desc>
+ Additional flags, to be defined later.
+ </desc>
+ </param>
+ </method>
+
+ <method name="changeEncryption" wrap-hint-server="limitedcaller">
+ <desc>
+ Starts encryption of this VM. This means that the stored data of the VM is encrypted.
+
+ Please note that the results can be either returned straight away,
+ or later as the result of the background operation via the object
+ returned via the @a progress parameter.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Encryption is not supported for various reasons e.g. unsupported cipher.
+ </result>
+ </desc>
+ <param name="currentPassword" type="wstring" dir="in">
+ <desc>
+ The current password the VM is protected with. Use an empty string to indicate
+ that the VM isn't encrypted.
+ </desc>
+ </param>
+ <param name="cipher" type="wstring" dir="in">
+ <desc>
+ The cipher to use for encryption. An empty string indicates no encryption for the
+ result.
+ </desc>
+ </param>
+ <param name="newPassword" type="wstring" dir="in">
+ <desc>
+ The new password the VM should be protected with. An empty password and password ID
+ will result in the VM being encrypted with the current password.
+ </desc>
+ </param>
+ <param name="newPasswordId" type="wstring" dir="in">
+ <desc>The ID of the new password when unlocking the VM.</desc>
+ </param>
+ <param name="force" type="boolean" dir="in">
+ <desc>
+ Force reencryption of the data if just password is changed.
+ Otherwise, if data already encrypted and cipher doesn't changed
+ only the password is changed.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="getEncryptionSettings" const="yes" wrap-hint-server="limitedcaller">
+ <desc>
+ Returns the encryption settings for this VM.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Encryption is not configured for this VM.
+ </result>
+ </desc>
+ <param name="cipher" type="wstring" dir="out">
+ <desc>The cipher used for encryption.</desc>
+ </param>
+ <param name="passwordId" type="wstring" dir="out">
+ <desc>The ID of the password when unlocking the VM.</desc>
+ </param>
+ </method>
+
+ <method name="checkEncryptionPassword" const="yes" wrap-hint-server="limitedcaller">
+ <desc>
+ Checks whether the supplied password is correct for the VM.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Encryption is not configured for this VM.
+ </result>
+ <result name="VBOX_E_PASSWORD_INCORRECT">
+ The given password is incorrect.
+ </result>
+ </desc>
+ <param name="password" type="wstring" dir="in">
+ <desc>The password to check.</desc>
+ </param>
+ </method>
+
+ <method name="addEncryptionPassword" wrap-hint-server="limitedcaller">
+ <desc>
+ Adds a password used for encryption. Updates the accesibility
+ state if password used the VM encryption.
+
+ <result name="VBOX_E_PASSWORD_INCORRECT">
+ The password provided wasn't correct for the VM using the provided
+ ID.
+ </result>
+ </desc>
+ <param name="id" type="wstring" dir="in">
+ <desc>
+ The identifier used for the password. Must match the identifier
+ used when the encrypted VM was created.
+ </desc>
+ </param>
+ <param name="password" type="wstring" dir="in">
+ <desc>The password.</desc>
+ </param>
+ </method>
+
+ <method name="addEncryptionPasswords" wrap-hint-server="limitedcaller">
+ <desc>
+ Adds passwords used for encryption. Updates the accesibility
+ state if the list contains password used the VM encryption.
+
+ <result name="VBOX_E_PASSWORD_INCORRECT">
+ The password provided wasn't correct for the VM using the provided
+ ID.
+ </result>
+ <result name="E_INVALIDARG">
+ Id and passwords arrays have different size.
+ </result>
+ </desc>
+ <param name="ids" type="wstring" dir="in" safearray="yes">
+ <desc>
+ List of identifiers for the passwords. Must match the identifier
+ used when the encrypted VM was created.
+ </desc>
+ </param>
+ <param name="passwords" type="wstring" dir="in" safearray="yes">
+ <desc>List of passwords.</desc>
+ </param>
+ </method>
+
+ <method name="removeEncryptionPassword" wrap-hint-server="limitedcaller,passcaller">
+ <desc>
+ Removes a password used for the VM encryption/decryption.
+ The password can be removed only if the VM is powered off.
+ Removing the password causes the VM goes to the inaccessible
+ state and the password must be provided again.
+ <note>
+ If machine becomes inaccessible all passwords are purged.
+ One has to add required passwords again using either
+ <link to="IMachine::addEncryptionPassword"/> or
+ <link to="IMachine::addEncryptionPasswords"/> methods.
+ </note>
+ </desc>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ The VM is not in proper state e.g powered on.
+ </result>
+ <param name="id" type="wstring" dir="in">
+ <desc>
+ The identifier used for the password. Must match the identifier
+ used when the encrypted VM was created.
+ </desc>
+ </param>
+ </method>
+
+ <method name="clearAllEncryptionPasswords" wrap-hint-server="limitedcaller,passcaller">
+ <desc>
+ Clears all provided VM passwords.
+ The passwords can be removed only if the VM is powered off.
+ Removing the passwords causes the VM goes to the inaccessible
+ state and the password must be provided again.
+ </desc>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ The VM is not in proper state e.g powered on.
+ </result>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IEmulatedUSB" extends="$unknown"
+ uuid="6e253ee8-477a-2497-6759-88b8292a5af0"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="4"
+ >
+ <desc>
+ Manages emulated USB devices.
+ </desc>
+
+ <method name="webcamAttach">
+ <desc>
+ Attaches the emulated USB webcam to the VM, which will use a host video capture device.
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>The host path of the capture device to use.</desc>
+ </param>
+ <param name="settings" type="wstring" dir="in">
+ <desc>Optional settings.</desc>
+ </param>
+ </method>
+
+ <method name="webcamDetach">
+ <desc>
+ Detaches the emulated USB webcam from the VM
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>The host path of the capture device to detach.</desc>
+ </param>
+ </method>
+
+ <attribute name="webcams" type="wstring" safearray="yes" readonly="yes">
+ <desc>Lists attached virtual webcams.</desc>
+ </attribute>
+ </interface>
+
+ <!--
+ // IConsole
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IVRDEServerInfo" extends="$unknown"
+ uuid="c39ef4d6-7532-45e8-96da-eb5986ae76e4"
+ wsmap="struct"
+ rest="managed"
+ reservedAttributes="8"
+ >
+ <desc>
+ Contains information about the remote desktop (VRDE) server capabilities and status.
+ This is used in the <link to="IConsole::VRDEServerInfo" /> attribute.
+ </desc>
+
+ <attribute name="active" type="boolean" readonly="yes">
+ <desc>
+ Whether the remote desktop connection is active.
+ </desc>
+ </attribute>
+
+ <attribute name="port" type="long" readonly="yes">
+ <desc>
+ VRDE server port number. If this property is equal to <tt>0</tt>, then
+ the VRDE server failed to start, usually because there are no free IP
+ ports to bind to. If this property is equal to <tt>-1</tt>, then the VRDE
+ server has not yet been started.
+ </desc>
+ </attribute>
+
+ <attribute name="numberOfClients" type="unsigned long" readonly="yes">
+ <desc>
+ How many times a client connected.
+ </desc>
+ </attribute>
+
+ <attribute name="beginTime" type="long long" readonly="yes">
+ <desc>
+ When the last connection was established, in milliseconds since 1970-01-01 UTC.
+ </desc>
+ </attribute>
+
+ <attribute name="endTime" type="long long" readonly="yes">
+ <desc>
+ When the last connection was terminated or the current time, if
+ connection is still active, in milliseconds since 1970-01-01 UTC.
+ </desc>
+ </attribute>
+
+ <attribute name="bytesSent" type="long long" readonly="yes">
+ <desc>
+ How many bytes were sent in last or current, if still active, connection.
+ </desc>
+ </attribute>
+
+ <attribute name="bytesSentTotal" type="long long" readonly="yes">
+ <desc>
+ How many bytes were sent in all connections.
+ </desc>
+ </attribute>
+
+ <attribute name="bytesReceived" type="long long" readonly="yes">
+ <desc>
+ How many bytes were received in last or current, if still active, connection.
+ </desc>
+ </attribute>
+
+ <attribute name="bytesReceivedTotal" type="long long" readonly="yes">
+ <desc>
+ How many bytes were received in all connections.
+ </desc>
+ </attribute>
+
+ <attribute name="user" type="wstring" readonly="yes">
+ <desc>
+ Login user name supplied by the client.
+ </desc>
+ </attribute>
+
+ <attribute name="domain" type="wstring" readonly="yes">
+ <desc>
+ Login domain name supplied by the client.
+ </desc>
+ </attribute>
+
+ <attribute name="clientName" type="wstring" readonly="yes">
+ <desc>
+ The client name supplied by the client.
+ </desc>
+ </attribute>
+
+ <attribute name="clientIP" type="wstring" readonly="yes">
+ <desc>
+ The IP address of the client.
+ </desc>
+ </attribute>
+
+ <attribute name="clientVersion" type="unsigned long" readonly="yes">
+ <desc>
+ The client software version number.
+ </desc>
+ </attribute>
+
+ <attribute name="encryptionStyle" type="unsigned long" readonly="yes">
+ <desc>
+ Public key exchange method used when connection was established.
+ Values: 0 - RDP4 public key exchange scheme.
+ 1 - X509 certificates were sent to client.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IConsole" extends="$unknown"
+ uuid="6ac83d89-6ee7-4e33-8ae6-b257b2e81be8"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="8" reservedAttributes="8"
+ >
+ <desc>
+ The IConsole interface represents an interface to control virtual
+ machine execution.
+
+ A console object gets created when a machine has been locked for a
+ particular session (client process) using <link to="IMachine::lockMachine" />
+ or <link to="IMachine::launchVMProcess"/>. The console object can
+ then be found in the session's <link to="ISession::console" /> attribute.
+
+ Methods of the IConsole interface allow the caller to query the current
+ virtual machine execution state, pause the machine or power it down, save
+ the machine state or take a snapshot, attach and detach removable media
+ and so on.
+
+ <see><link to="ISession"/></see>
+ </desc>
+
+ <attribute name="machine" type="IMachine" readonly="yes" rest="uuid">
+ <desc>
+ Machine object for this console session.
+ <note>
+ This is a convenience property, it has the same value as
+ <link to="ISession::machine"/> of the corresponding session
+ object.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="state" type="MachineState" readonly="yes">
+ <desc>
+ Current execution state of the machine.
+ <note>
+ This property always returns the same value as the corresponding
+ property of the IMachine object for this console session.
+ For the process that owns (executes) the VM, this is the
+ preferable way of querying the VM state, because no IPC
+ calls are made.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="guest" type="IGuest" readonly="yes">
+ <desc>Guest object.</desc>
+ </attribute>
+
+ <attribute name="keyboard" type="IKeyboard" readonly="yes">
+ <desc>
+ Virtual keyboard object.
+ <note>
+ If the machine is not running, any attempt to use
+ the returned object will result in an error.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="mouse" type="IMouse" readonly="yes">
+ <desc>
+ Virtual mouse object.
+ <note>
+ If the machine is not running, any attempt to use
+ the returned object will result in an error.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="display" type="IDisplay" readonly="yes">
+ <desc>Virtual display object.
+ <note>
+ If the machine is not running, any attempt to use
+ the returned object will result in an error.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="debugger" type="IMachineDebugger" readonly="yes">
+ <desc>Debugging interface.</desc>
+ </attribute>
+
+ <attribute name="USBDevices" type="IUSBDevice" readonly="yes" safearray="yes">
+ <desc>
+ Collection of USB devices currently attached to the virtual
+ USB controller.
+ <note>
+ The collection is empty if the machine is not running.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="remoteUSBDevices" type="IHostUSBDevice" readonly="yes" safearray="yes">
+ <desc>
+ List of USB devices currently attached to the remote VRDE client.
+ Once a new device is physically attached to the remote host computer,
+ it appears in this list and remains there until detached.
+ </desc>
+ </attribute>
+
+ <attribute name="sharedFolders" type="ISharedFolder" readonly="yes" safearray="yes">
+ <desc>
+ Collection of shared folders for the current session. These folders
+ are called transient shared folders because they are available to the
+ guest OS running inside the associated virtual machine only for the
+ duration of the session (as opposed to
+ <link to="IMachine::sharedFolders"/> which represent permanent shared
+ folders). When the session is closed (e.g. the machine is powered down),
+ these folders are automatically discarded.
+
+ New shared folders are added to the collection using
+ <link to="#createSharedFolder"/>. Existing shared folders can be
+ removed using <link to="#removeSharedFolder"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="VRDEServerInfo" type="IVRDEServerInfo" readonly="yes">
+ <desc>
+ Interface that provides information on Remote Desktop Extension (VRDE) connection.
+ </desc>
+ </attribute>
+
+ <attribute name="eventSource" type="IEventSource" readonly="yes">
+ <desc>
+ Event source for console events.
+ </desc>
+ </attribute>
+
+ <attribute name="attachedPCIDevices" type="IPCIDeviceAttachment" readonly="yes" safearray="yes">
+ <desc>Array of PCI devices attached to this machine.</desc>
+ </attribute>
+
+ <attribute name="useHostClipboard" type="boolean">
+ <desc>
+ Whether the guest clipboard should be connected to the host one or
+ whether it should only be allowed access to the VRDE clipboard. This
+ setting may not affect existing guest clipboard connections which
+ are already connected to the host clipboard.
+ </desc>
+ </attribute>
+
+ <attribute name="emulatedUSB" type="IEmulatedUSB" readonly="yes">
+ <desc>
+ Interface that manages emulated USB devices.
+ </desc>
+ </attribute>
+
+ <method name="powerUp">
+ <rest request="post" path="/vms/{vmid}/console/actions/"/>
+ <desc>
+ Starts the virtual machine execution using the current machine
+ state (that is, its current execution state, current settings and
+ current storage devices).
+
+ <note>
+ This method is only useful for front-ends that want to actually
+ execute virtual machines in their own process (like the VirtualBox
+ or VBoxSDL front-ends). Unless you are intending to write such a
+ front-end, do not call this method. If you simply want to
+ start virtual machine execution using one of the existing front-ends
+ (for example the VirtualBox GUI or headless server), use
+ <link to="IMachine::launchVMProcess"/> instead; these
+ front-ends will power up the machine automatically for you.
+ </note>
+
+ If the machine is powered off or aborted, the execution will
+ start from the beginning (as if the real hardware were just
+ powered on).
+
+ If the machine is in the <link to="MachineState_Saved"/> state or the
+ <link to="MachineState_AbortedSaved"/> state it will continue its
+ execution from the point where the state was saved.
+
+ If the machine <link to="IMachine::teleporterEnabled"/> property is
+ enabled on the machine being powered up, the machine will wait for an
+ incoming teleportation in the <link to="MachineState_TeleportingIn"/>
+ state. The returned progress object will have at least three
+ operations where the last three are defined as: (1) powering up and
+ starting TCP server, (2) waiting for incoming teleportations, and
+ (3) perform teleportation. These operations will be reflected as the
+ last three operations of the progress objected returned by
+ <link to="IMachine::launchVMProcess"/> as well.
+
+ <see><link to="IMachine::saveState"/></see>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine already running.
+ </result>
+ <result name="VBOX_E_HOST_ERROR">
+ Host interface does not exist or name not set.
+ </result>
+ <result name="VBOX_E_FILE_ERROR">
+ Invalid saved state file.
+ </result>
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="powerUpPaused">
+ <rest request="post" path="/vms/{vmid}/console/actions/"/>
+ <desc>
+ Identical to powerUp except that the VM will enter the
+ <link to="MachineState_Paused"/> state, instead of
+ <link to="MachineState_Running"/>.
+
+ <see><link to="#powerUp"/></see>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine already running.
+ </result>
+ <result name="VBOX_E_HOST_ERROR">
+ Host interface does not exist or name not set.
+ </result>
+ <result name="VBOX_E_FILE_ERROR">
+ Invalid saved state file.
+ </result>
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="powerDown">
+ <rest request="post" path="/vms/{vmid}/console/actions/"/>
+ <desc>
+ Initiates the power down procedure to stop the virtual machine
+ execution.
+
+ The completion of the power down procedure is tracked using the returned
+ IProgress object. After the operation is complete, the machine will go
+ to the PoweredOff state.
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine must be Running, Paused or Stuck to be powered down.
+ </result>
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="reset">
+ <rest request="post" path="/vms/{vmid}/console/actions/"/>
+ <desc>Resets the virtual machine.
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine not in Running state.
+ </result>
+ <result name="VBOX_E_VM_ERROR">
+ Virtual machine error in reset operation.
+ </result>
+ </desc>
+ </method>
+
+ <method name="pause">
+ <rest request="post" path="/vms/{vmid}/console/actions/"/>
+ <desc>Pauses the virtual machine execution.
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine not in Running state.
+ </result>
+ <result name="VBOX_E_VM_ERROR">
+ Virtual machine error in suspend operation.
+ </result>
+ </desc>
+ </method>
+
+ <method name="resume">
+ <rest request="post" path="/vms/{vmid}/console/actions/"/>
+ <desc>Resumes the virtual machine execution.
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine not in Paused state.
+ </result>
+ <result name="VBOX_E_VM_ERROR">
+ Virtual machine error in resume operation.
+ </result>
+ </desc>
+ </method>
+
+ <method name="powerButton">
+ <rest request="post" path="/vms/{vmid}/console/actions/"/>
+ <desc>Sends the ACPI power button event to the guest.
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine not in Running state.
+ </result>
+ <result name="VBOX_E_PDM_ERROR">
+ Controlled power off failed.
+ </result>
+ </desc>
+ </method>
+
+ <method name="sleepButton">
+ <rest request="post" path="/vms/{vmid}/console/actions/"/>
+ <desc>Sends the ACPI sleep button event to the guest.
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine not in Running state.
+ </result>
+ <result name="VBOX_E_PDM_ERROR">
+ Sending sleep button event failed.
+ </result>
+ </desc>
+ </method>
+
+ <method name="getPowerButtonHandled">
+ <desc>Checks if the last power button event was handled by guest.
+ <result name="VBOX_E_PDM_ERROR">
+ Checking if the event was handled by the guest OS failed.
+ </result>
+ </desc>
+ <param name="handled" type="boolean" dir="return"/>
+ </method>
+
+ <method name="getGuestEnteredACPIMode">
+ <desc>Checks if the guest entered the ACPI mode G0 (working) or
+ G1 (sleeping). If this method returns @c false, the guest will
+ most likely not respond to external ACPI events.
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine not in Running state.
+ </result>
+ </desc>
+ <param name="entered" type="boolean" dir="return"/>
+ </method>
+
+ <method name="getDeviceActivity">
+ <desc>
+ Gets the current activity type of given devices or device groups.
+ <result name="E_INVALIDARG">
+ Invalid device type.
+ </result>
+ </desc>
+ <param name="type" type="DeviceType" safearray="yes" dir="in"/>
+ <param name="activity" type="DeviceActivity" safearray="yes" dir="return"/>
+ </method>
+
+ <method name="attachUSBDevice">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Attaches a host USB device with the given UUID to the
+ USB controller of the virtual machine.
+
+ The device needs to be in one of the following states:
+ <link to="USBDeviceState_Busy"/>,
+ <link to="USBDeviceState_Available"/> or
+ <link to="USBDeviceState_Held"/>,
+ otherwise an error is immediately returned.
+
+ When the device state is
+ <link to="USBDeviceState_Busy">Busy</link>, an error may also
+ be returned if the host computer refuses to release it for some reason.
+
+ <see><link to="IUSBDeviceFilters::deviceFilters"/>,
+ <link to="USBDeviceState"/></see>
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine state neither Running nor Paused.
+ </result>
+ <result name="VBOX_E_PDM_ERROR">
+ Virtual machine does not have a USB controller.
+ </result>
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in">
+ <desc>UUID of the host USB device to attach.</desc>
+ </param>
+ <param name="captureFilename" type="wstring" dir="in">
+ <desc>Filename to capture the USB traffic to.</desc>
+ </param>
+ </method>
+
+ <method name="detachUSBDevice">
+ <rest request="post" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Detaches an USB device with the given UUID from the USB controller
+ of the virtual machine.
+
+ After this method succeeds, the VirtualBox server re-initiates
+ all USB filters as if the device were just physically attached
+ to the host, but filters of this machine are ignored to avoid
+ a possible automatic re-attachment.
+
+ <see><link to="IUSBDeviceFilters::deviceFilters"/>,
+ <link to="USBDeviceState"/></see>
+
+ <result name="VBOX_E_PDM_ERROR">
+ Virtual machine does not have a USB controller.
+ </result>
+ <result name="E_INVALIDARG">
+ USB device not attached to this virtual machine.
+ </result>
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in">
+ <desc>UUID of the USB device to detach.</desc>
+ </param>
+ <param name="device" type="IUSBDevice" dir="return">
+ <desc>Detached USB device.</desc>
+ </param>
+ </method>
+
+ <method name="findUSBDeviceByAddress">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Searches for a USB device with the given host address.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Given @c name does not correspond to any USB device.
+ </result>
+
+ <see><link to="IUSBDevice::address"/></see>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>
+ Address of the USB device (as assigned by the host) to
+ search for.
+ </desc>
+ </param>
+ <param name="device" type="IUSBDevice" dir="return">
+ <desc>Found USB device object.</desc>
+ </param>
+ </method>
+
+ <method name="findUSBDeviceById">
+ <rest request="get" path="/vms/{vmid}/configuration/"/>
+ <desc>
+ Searches for a USB device with the given UUID.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Given @c id does not correspond to any USB device.
+ </result>
+
+ <see><link to="IUSBDevice::id"/></see>
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in">
+ <desc>UUID of the USB device to search for.</desc>
+ </param>
+ <param name="device" type="IUSBDevice" dir="return">
+ <desc>Found USB device object.</desc>
+ </param>
+ </method>
+
+ <method name="createSharedFolder">
+ <rest request="post" path="/vms/{vmid}/console/methods/"/>
+ <desc>
+ Creates a transient new shared folder by associating the given logical
+ name with the given host path, adds it to the collection of shared
+ folders and starts sharing it. Refer to the description of
+ <link to="ISharedFolder"/> to read more about logical names.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine is in the Saved or AbortedSaved state or currently changing state.
+ </result>
+ <result name="VBOX_E_FILE_ERROR">
+ Shared folder already exists or not accessible.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Unique logical name of the shared folder.</desc>
+ </param>
+ <param name="hostPath" type="wstring" dir="in">
+ <desc>Full path to the shared folder in the host file system.</desc>
+ </param>
+ <param name="writable" type="boolean" dir="in">
+ <desc>Whether the share is writable or readonly</desc>
+ </param>
+ <param name="automount" type="boolean" dir="in">
+ <desc>Whether the share gets automatically mounted by the guest
+ or not.</desc>
+ </param>
+ <param name="autoMountPoint" type="wstring" dir="in">
+ <desc>Where the guest should automatically mount the folder, if possible.
+ For Windows and OS/2 guests this should be a drive letter, while other
+ guests it should be a absolute directory.
+ </desc>
+ </param>
+ </method>
+
+ <method name="removeSharedFolder">
+ <rest request="post" path="/vms/{vmid}/console/methods/"/>
+ <desc>
+ Removes a transient shared folder with the given name previously
+ created by <link to="#createSharedFolder"/> from the collection of
+ shared folders and stops sharing it.
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine is in the Saved or AbortedSaved state or currently changing state.
+ </result>
+ <result name="VBOX_E_FILE_ERROR">
+ Shared folder does not exists.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Logical name of the shared folder to remove.</desc>
+ </param>
+ </method>
+
+ <method name="teleport">
+ <desc>
+ Teleport the VM to a different host machine or process.
+
+ @todo Explain the details.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine not running or paused.
+ </result>
+ </desc>
+ <param name="hostname" type="wstring" dir="in">
+ <desc>The name or IP of the host to teleport to.</desc>
+ </param>
+ <param name="tcpport" type="unsigned long" dir="in">
+ <desc>The TCP port to connect to (1..65535).</desc>
+ </param>
+ <param name="password" type="wstring" dir="in">
+ <desc>The password.</desc>
+ </param>
+ <param name="maxDowntime" type="unsigned long" dir="in">
+ <desc>
+ The maximum allowed downtime given as milliseconds. 0 is not a valid
+ value. Recommended value: 250 ms.
+
+ The higher the value is, the greater the chance for a successful
+ teleportation. A small value may easily result in the teleportation
+ process taking hours and eventually fail.
+
+ <note>
+ The current implementation treats this a guideline, not as an
+ absolute rule.
+ </note>
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="addEncryptionPassword">
+ <rest request="post" path="/vms/{vmid}/console/methods/"/>
+ <desc>
+ Adds a password used for encryption/decryption.
+
+ <result name="VBOX_E_PASSWORD_INCORRECT">
+ The password provided wasn't correct for at least one disk using the provided
+ ID.
+ </result>
+ </desc>
+ <param name="id" type="wstring" dir="in">
+ <desc>
+ The identifier used for the password. Must match the identifier
+ used when the encrypted medium was created.
+ </desc>
+ </param>
+ <param name="password" type="wstring" dir="in">
+ <desc>The password.</desc>
+ </param>
+ <param name="clearOnSuspend" type="boolean" dir="in">
+ <desc>
+ Flag whether to clear the password on VM suspend (due to a suspending host
+ for example). The password must be supplied again before the VM can resume.
+ </desc>
+ </param>
+ </method>
+
+ <method name="addEncryptionPasswords">
+ <rest request="post" path="/vms/{vmid}/console/methods/"/>
+ <desc>
+ Adds a password used for encryption/decryption.
+
+ <result name="VBOX_E_PASSWORD_INCORRECT">
+ The password provided wasn't correct for at least one disk using the provided
+ ID.
+ </result>
+ </desc>
+ <param name="ids" type="wstring" dir="in" safearray="yes">
+ <desc>
+ List of identifiers for the passwords. Must match the identifier
+ used when the encrypted medium was created.
+ </desc>
+ </param>
+ <param name="passwords" type="wstring" dir="in" safearray="yes">
+ <desc>List of passwords.</desc>
+ </param>
+ <param name="clearOnSuspend" type="boolean" dir="in">
+ <desc>
+ Flag whether to clear the given passwords on VM suspend (due to a suspending host
+ for example). The passwords must be supplied again before the VM can resume.
+ </desc>
+ </param>
+ </method>
+
+ <method name="removeEncryptionPassword">
+ <rest request="post" path="/vms/{vmid}/console/methods/"/>
+ <desc>
+ Removes a password used for hard disk encryption/decryption from
+ the running VM. As soon as the medium requiring this password
+ is accessed the VM is paused with an error and the password must be
+ provided again.
+ </desc>
+ <param name="id" type="wstring" dir="in">
+ <desc>
+ The identifier used for the password. Must match the identifier
+ used when the encrypted medium was created.
+ </desc>
+ </param>
+ </method>
+
+ <method name="clearAllEncryptionPasswords">
+ <rest request="post" path="/vms/{vmid}/console/methods/"/>
+ <desc>Clears all provided supplied encryption passwords.</desc>
+ </method>
+ </interface>
+
+ <!--
+ // IHost
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="HostNetworkInterfaceMediumType"
+ uuid="1aa54aaf-2497-45a2-bfb1-8eb225e93d5b"
+ >
+ <!-- Must match NETIFTYPE in netif.h exactly. -->
+ <desc>
+ Type of encapsulation. Ethernet encapsulation includes both wired and
+ wireless Ethernet connections.
+ <see><link to="IHostNetworkInterface"/></see>
+ </desc>
+
+ <const name="Unknown" value="0">
+ <desc>
+ The type of interface cannot be determined.
+ </desc>
+ </const>
+ <const name="Ethernet" value="1">
+ <desc>
+ Ethernet frame encapsulation.
+ </desc>
+ </const>
+ <const name="PPP" value="2">
+ <desc>
+ Point-to-point protocol encapsulation.
+ </desc>
+ </const>
+ <const name="SLIP" value="3">
+ <desc>
+ Serial line IP encapsulation.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="HostNetworkInterfaceStatus"
+ uuid="CC474A69-2710-434B-8D99-C38E5D5A6F41"
+ >
+ <!-- Must match NETIFSTATUS in netif.h exactly. -->
+ <desc>
+ Current status of the interface.
+ <see><link to="IHostNetworkInterface"/></see>
+ </desc>
+
+ <const name="Unknown" value="0">
+ <desc>
+ The state of interface cannot be determined.
+ </desc>
+ </const>
+ <const name="Up" value="1">
+ <desc>
+ The interface is fully operational.
+ </desc>
+ </const>
+ <const name="Down" value="2">
+ <desc>
+ The interface is not functioning.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="HostNetworkInterfaceType"
+ uuid="67431b00-9946-48a2-bc02-b25c5919f4f3"
+ >
+ <desc>
+ Network interface type.
+ </desc>
+ <const name="Bridged" value="1"/>
+ <const name="HostOnly" value="2"/>
+ </enum>
+
+ <interface
+ name="IHostNetworkInterface" extends="$unknown"
+ uuid="455f8c45-44a0-a470-ba20-27890b96dba9"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="2" reservedAttributes="4"
+ >
+ <desc>
+ Represents one of host's network interfaces. IP V6 address and network
+ mask are strings of 32 hexadecimal digits grouped by four. Groups are
+ separated by colons.
+ For example, fe80:0000:0000:0000:021e:c2ff:fed2:b030.
+ </desc>
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>Returns the host network interface name.</desc>
+ </attribute>
+
+ <attribute name="shortName" type="wstring" readonly="yes">
+ <desc>Returns the host network interface short name.</desc>
+ </attribute>
+
+ <attribute name="id" type="uuid" mod="string" readonly="yes">
+ <desc>Returns the interface UUID.</desc>
+ </attribute>
+
+ <attribute name="networkName" type="wstring" readonly="yes">
+ <desc>Returns the name of a virtual network the interface gets attached to.</desc>
+ </attribute>
+
+ <attribute name="DHCPEnabled" type="boolean" readonly="yes">
+ <desc>Specifies whether the DHCP is enabled for the interface.</desc>
+ </attribute>
+
+ <attribute name="IPAddress" type="wstring" readonly="yes">
+ <desc>Returns the IP V4 address of the interface.</desc>
+ </attribute>
+
+ <attribute name="networkMask" type="wstring" readonly="yes">
+ <desc>Returns the network mask of the interface.</desc>
+ </attribute>
+
+ <attribute name="IPV6Supported" type="boolean" readonly="yes">
+ <desc>Specifies whether the IP V6 is supported/enabled for the interface.</desc>
+ </attribute>
+
+ <attribute name="IPV6Address" type="wstring" readonly="yes">
+ <desc>Returns the IP V6 address of the interface.</desc>
+ </attribute>
+
+ <attribute name="IPV6NetworkMaskPrefixLength" type="unsigned long" readonly="yes">
+ <desc>Returns the length IP V6 network mask prefix of the interface.</desc>
+ </attribute>
+
+ <attribute name="hardwareAddress" type="wstring" readonly="yes">
+ <desc>Returns the hardware address. For Ethernet it is MAC address.</desc>
+ </attribute>
+
+ <attribute name="mediumType" type="HostNetworkInterfaceMediumType" readonly="yes">
+ <desc>Type of protocol encapsulation used.</desc>
+ </attribute>
+
+ <attribute name="status" type="HostNetworkInterfaceStatus" readonly="yes">
+ <desc>Status of the interface.</desc>
+ </attribute>
+
+ <attribute name="interfaceType" type="HostNetworkInterfaceType" readonly="yes">
+ <desc>specifies the host interface type.</desc>
+ </attribute>
+
+ <attribute name="wireless" type="boolean" readonly="yes">
+ <desc>Specifies whether the interface is wireless.</desc>
+ </attribute>
+
+ <method name="enableStaticIPConfig">
+ <desc>sets and enables the static IP V4 configuration for the given interface.</desc>
+ <param name="IPAddress" type="wstring" dir="in">
+ <desc>
+ IP address.
+ </desc>
+ </param>
+ <param name="networkMask" type="wstring" dir="in">
+ <desc>
+ network mask.
+ </desc>
+ </param>
+ </method>
+
+ <method name="enableStaticIPConfigV6">
+ <desc>sets and enables the static IP V6 configuration for the given interface.</desc>
+ <param name="IPV6Address" type="wstring" dir="in">
+ <desc>
+ IP address.
+ </desc>
+ </param>
+ <param name="IPV6NetworkMaskPrefixLength" type="unsigned long" dir="in">
+ <desc>
+ network mask.
+ </desc>
+ </param>
+ </method>
+
+ <method name="enableDynamicIPConfig">
+ <desc>enables the dynamic IP configuration.</desc>
+ </method>
+
+ <method name="DHCPRediscover">
+ <desc>refreshes the IP configuration for DHCP-enabled interface.</desc>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IHostVideoInputDevice" extends="$unknown"
+ uuid="e8c25d4d-ac97-4c16-b3e2-81bd8a57cc27"
+ wsmap="managed"
+ rest="managed"
+ reservedAttributes="4"
+ >
+ <desc>
+ Represents one of host's video capture devices, for example a webcam.
+ </desc>
+
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>User friendly name.</desc>
+ </attribute>
+
+ <attribute name="path" type="wstring" readonly="yes">
+ <desc>The host path of the device.</desc>
+ </attribute>
+
+ <attribute name="alias" type="wstring" readonly="yes">
+ <desc>An alias which can be used for <link to="IEmulatedUSB::webcamAttach"/></desc>
+ </attribute>
+
+ </interface>
+
+ <enum
+ name="UpdateChannel"
+ uuid="9f1562a5-e47a-496b-a818-66776f06ce40"
+ >
+ <const name="Invalid" value="0">
+ <desc>No channel specified. Do not use this.</desc>
+ </const>
+ <const name="Stable" value="1">
+ <desc>All stable releases (maintenance and minor releases within the same major release).</desc>
+ </const>
+ <const name="All" value="2">
+ <desc>All stable releases, including major versions.</desc>
+ </const>
+ <const name="WithBetas" value="3">
+ <desc>All stable and major releases, including beta versions.</desc>
+ </const>
+ <const name="WithTesting" value="4">
+ <desc>All stable, major and beta releases, including testing versions.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="UpdateSeverity"
+ uuid="359a5fee-8a06-4306-8068-2f2dd5cde06f"
+ >
+ <const name="Invalid" value="0">
+ <desc>No severity specified. Do not use this.</desc>
+ </const>
+ <const name="Critical" value="1">
+ <desc>Update contains critical patches.</desc>
+ </const>
+ <const name="Major" value="2">
+ <desc>Update contains a major version.</desc>
+ </const>
+ <const name="Minor" value="3">
+ <desc>Update contains a minor version.</desc>
+ </const>
+ <const name="Testing" value="4">
+ <desc>Update contains a testing version. Use with caution!</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="UpdateState"
+ uuid="6623e363-c892-45f8-80cb-4e8ffd9b4e60"
+ >
+ <const name="Invalid" value="0">
+ <desc>Invalid / not set update state.</desc>
+ </const>
+ <const name="Available" value="1">
+ <desc>An uppdate is available.</desc>
+ </const>
+ <const name="NotAvailable" value="2">
+ <desc>No update available.</desc>
+ </const>
+ <const name="Downloading" value="3">
+ <desc>Update is being downloaded currently.</desc>
+ </const>
+ <const name="Downloaded" value="4">
+ <desc>Update has been successfully downloaded.</desc>
+ </const>
+ <const name="Installing" value="5">
+ <desc>Update is being installed currently.</desc>
+ </const>
+ <const name="Installed" value="6">
+ <desc>Update has been successfully installed.</desc>
+ </const>
+ <const name="UserInteraction" value="7">
+ <desc>Update requires user interaction to continue.</desc>
+ </const>
+ <const name="Canceled" value="8">
+ <desc>Update has been canceled.</desc>
+ </const>
+ <const name="Maintenance" value="9">
+ <desc>Update service currently is in maintenance mode.</desc>
+ </const>
+ <const name="Error" value="10">
+ <desc>An error occurred while updating.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IUpdateAgent" extends="$unknown"
+ uuid="c4b1b5f4-8cdf-4923-9ef6-b92476a84109"
+ wsmap="managed"
+ reservedMethods="2" reservedAttributes="4"
+ >
+
+ <desc>
+ Abstract parent interface for handling updateable software components.
+ </desc>
+
+ <method name="checkFor">
+ <!-- Initially named just "check", but older xcode versions on the Mac have trouble naming interface
+ functions like this and thus failing to build, sigh. -->
+ <desc>
+ Checks for an update.
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="download">
+ <desc>
+ Downloads the update.
+ </desc>
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Downloading update not supported.
+ </result>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="install">
+ <desc>
+ Installs the update.
+ </desc>
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Installing update not supported.
+ </result>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="rollback">
+ <desc>
+ Rolls back installing the update.
+ </desc>
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Rolling back update not supported.
+ </result>
+ </method>
+
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>
+ Name of the update component.
+ </desc>
+ </attribute>
+
+ <attribute name="eventSource" type="IEventSource" readonly="yes">
+ <desc>
+ Event source for update agent events.
+ </desc>
+ </attribute>
+
+ <attribute name="order" type="unsigned long" readonly="yes">
+ <desc>
+ Order hint the update component needs to run at, in conjunction with other update components.
+ </desc>
+ <result name="0">
+ No order preferred / disabled.
+ </result>
+ <note>
+ Lower numbers mean higher priority. 0 means no order or disabled.
+ </note>
+ </attribute>
+
+ <attribute name="dependsOn" type="wstring" readonly="yes" safearray="yes">
+ <desc>
+ Array of other update component names this component depends on before being able to get installed.
+ </desc>
+ <note>
+ Ordered entries, highest priority first. No dependencies when array is empty.
+ Dependency entries also can contain a minimum version number after the update component name, separated by an "@",
+ e.g. "Guest Additions@7.1.0_BETA2".
+ </note>
+ </attribute>
+
+ <attribute name="version" type="wstring" readonly="yes">
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Result not available yet.
+ </result>
+ <desc>
+ Version the update contains.
+ </desc>
+ </attribute>
+
+ <attribute name="downloadUrl" type="wstring" readonly="yes">
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Result not available yet.
+ </result>
+ <desc>
+ Download URL of the update.
+ </desc>
+ </attribute>
+
+ <attribute name="webUrl" type="wstring" readonly="yes">
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Result not available yet.
+ </result>
+ <desc>
+ Web URL of the update.
+ </desc>
+ </attribute>
+
+ <attribute name="releaseNotes" type="wstring" readonly="yes">
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Result not available yet.
+ </result>
+ <desc>
+ Release notes of the update.
+ </desc>
+ </attribute>
+
+ <attribute name="enabled" type="boolean" readonly="no">
+ <desc>
+ Enables or disables the update component.
+ </desc>
+ </attribute>
+
+ <attribute name="hidden" type="boolean" readonly="yes">
+ <desc>
+ Whether the update component shall be hidden from the user or not.
+ </desc>
+ </attribute>
+
+ <attribute name="state" type="UpdateState" readonly="yes">
+ <desc>
+ Returns the current update state.
+ </desc>
+ </attribute>
+
+ <attribute name="checkFrequency" type="unsigned long" readonly="no">
+ <desc>
+ The update check frequency (in seconds).
+ </desc>
+ </attribute>
+
+ <attribute name="channel" type="UpdateChannel" readonly="no">
+ <desc>
+ Update channel to use for checking for updates.
+ </desc>
+ </attribute>
+
+ <attribute name="repositoryURL" type="wstring" readonly="no">
+ <desc>
+ Update repository URL to use for retrieving the update.
+ </desc>
+ <note>
+ Only repositories with the https:// URL scheme are allowed.
+ </note>
+ </attribute>
+
+ <attribute name="lastCheckDate" type="wstring" readonly="yes">
+ <desc>
+ Date of last update check.
+ </desc>
+ <note>
+ In ISO 8601 format (e.g. 2020-05-11T21:13:39.348416000Z).
+ </note>
+ </attribute>
+
+ <attribute name="checkCount" type="unsigned long" readonly="yes">
+ <desc>
+ How many times the update check has happened already.
+ </desc>
+ </attribute>
+
+ <attribute name="isCheckNeeded" type="boolean" readonly="yes">
+ <desc>
+ Returns @c TRUE if an update check is needed, or @c FALSE if not.
+
+ <note>Compares the system's current date with the last
+ update check date and currently set check frequency.</note>
+ </desc>
+ </attribute>
+
+ <attribute name="supportedChannels" type="UpdateChannel" safearray="yes" readonly="yes">
+ <desc>
+ Returns a safe array of all supported update channels this agents offers.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IHostUpdateAgent" extends="IUpdateAgent"
+ uuid="d782dba7-cd4f-4ace-951a-58321c23e258"
+ wsmap="managed"
+ reservedMethods="2" reservedAttributes="4"
+ >
+ <desc>
+ Implementation of the <link to="IUpdateAgent" /> object
+ for VirtualBox host updates.
+ </desc>
+ <note>
+ If no proxy is explicitly set, the proxy settings from <link to="ISystemProperties" /> will be used.
+ </note>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IHostDrivePartition" extends="$unknown"
+ uuid="4f529a14-ace3-407c-9c49-066e8e8027f0"
+ wsmap="struct"
+ rest="managed"
+ >
+ <desc>
+ The IHostDrivePartition interface represents the partition of the host drive.
+ To enumerate all available drives partitions in the host, use the
+ <link to="IHost::hostDrives"/> attribute.
+ </desc>
+
+ <attribute name="number" type="unsigned long" readonly="yes">
+ <desc>
+ The number of the partition. Represents the system number of the
+ partition, e.g. /dev/sdX in the linux, where X is the number
+ returned.
+ </desc>
+ </attribute>
+
+ <attribute name="size" type="long long" readonly="yes">
+ <desc>
+ The partition size in bytes.
+ </desc>
+ </attribute>
+
+ <attribute name="start" type="long long" readonly="yes">
+ <desc>
+ The start byte offset of this partition in bytes relative to the
+ beginning of the hard disk.
+ </desc>
+ </attribute>
+
+ <attribute name="type" type="PartitionType" readonly="yes">
+ <desc>
+ A translation of <link to="IHostDrivePartition::typeMBR"/> and
+ <link to="IHostDrivePartition::typeUuid"/> when possible, otherwise
+ set to <link to="PartitionType_Unknown" />.
+ </desc>
+ </attribute>
+
+ <attribute name="active" type="boolean" readonly="yes">
+ <desc>
+ The partition is bootable when TRUE.
+ </desc>
+ </attribute>
+
+ <!-- MBR: -->
+
+ <attribute name="typeMBR" type="unsigned long" readonly="yes">
+ <desc>
+ The raw MBR partition type, 0 for non-MBR disks.
+ </desc>
+ </attribute>
+
+ <attribute name="startCylinder" type="unsigned long" readonly="yes">
+ <desc>
+ The cylinder (0..1023) of the first sector in the partition on an MBR disk, zero for not an MBR disk.
+ </desc>
+ </attribute>
+
+ <attribute name="startHead" type="unsigned long" readonly="yes">
+ <desc>
+ The head (0..255) of the first sector in the partition on an MBR disk, zero for not an MBR disk.
+ </desc>
+ </attribute>
+
+ <attribute name="startSector" type="unsigned long" readonly="yes">
+ <desc>
+ The sector (0..63) of the first sector in the partition on an MBR disk, zero for not an MBR disk.
+ </desc>
+ </attribute>
+
+ <attribute name="endCylinder" type="unsigned long" readonly="yes">
+ <desc>
+ The cylinder (0..1023) of the last sector (inclusive) in the partition on an MBR disk, zero for not an MBR disk.
+ </desc>
+ </attribute>
+
+ <attribute name="endHead" type="unsigned long" readonly="yes">
+ <desc>
+ The head (0..255) of the last sector (inclusive) in the partition on an MBR disk, zero for not an MBR disk.
+ </desc>
+ </attribute>
+
+ <attribute name="endSector" type="unsigned long" readonly="yes">
+ <desc>
+ The sector (1..63) of the last sector (inclusive) in the partition on an MBR disk, zero for not an MBR disk.
+ </desc>
+ </attribute>
+
+ <!-- GPT: -->
+ <attribute name="typeUuid" type="uuid" mod="string" readonly="yes">
+ <!-- @todo r=bird: Not sure about the casing here. typeUUID Klaus? -->
+ <desc>
+ The partition type when GUID partitioning scheme is used, NULL UUID value for not a GPT disks.
+ </desc>
+ </attribute>
+
+ <attribute name="uuid" type="uuid" mod="string" readonly="yes">
+ <desc>
+ The GUID of the partition when GUID partitioning scheme is used, NULL UUID value for not a GPT disks.
+ </desc>
+ </attribute>
+
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>
+ The name of the partition if GPT partitioning is used, empty if not a GPT disk.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IHostDrive" extends="$unknown"
+ uuid="70e2e0c3-332c-4d72-b822-2db16e2cb31b"
+ wsmap="managed"
+ rest="managed"
+ >
+ <desc>
+ The IHostDrive interface represents the drive of the physical machine.
+ It is not a complete medium description and, therefore, it is not IMedium
+ based. The interface is used to get information about a host drive and
+ its partitioning.
+ <note>
+ The object operates in limited mode if the user cannot open the drive
+ and parse the partition table. In limited mode on the
+ <link to="IHostDrive::drivePath" /> and <link to="IHostDrive::model" />
+ attributes can be accessed, the rest will fail with E_ACCESSDENIED.
+ </note>
+ </desc>
+
+ <attribute name="drivePath" type="wstring" readonly="yes" wrap-hint-server="limitedcaller">
+ <desc>
+ The path of the drive. Platform dependent.
+ </desc>
+ </attribute>
+
+ <attribute name="partitioningType" type="PartitioningType" readonly="yes">
+ <desc>
+ The scheme of the partitions the disk has.
+ </desc>
+ </attribute>
+
+ <attribute name="uuid" type="uuid" mod="string" readonly="yes">
+ <desc>
+ The GUID of the disk.
+ </desc>
+ </attribute>
+
+ <attribute name="sectorSize" type="unsigned long" readonly="yes">
+ <desc>
+ The size of the sector in bytes.
+ </desc>
+ </attribute>
+
+ <attribute name="size" type="long long" readonly="yes">
+ <desc>
+ The size of the disk in bytes.
+ </desc>
+ </attribute>
+
+ <attribute name="model" type="wstring" readonly="yes" wrap-hint-server="limitedcaller">
+ <desc>
+ The model string of the drive if available.
+ </desc>
+ </attribute>
+
+ <attribute name="partitions" type="IHostDrivePartition" readonly="yes" safearray="yes">
+ <desc>List of partitions available on the host drive.</desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IHost" extends="$unknown"
+ uuid="e54f6256-97a7-4947-8a78-10c013ddf4b8"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="6" reservedAttributes="12"
+ >
+ <desc>
+ The IHost interface represents the physical machine that this VirtualBox
+ installation runs on.
+
+ An object implementing this interface is returned by the
+ <link to="IVirtualBox::host" /> attribute. This interface contains
+ read-only information about the host's physical hardware (such as what
+ processors and disks are available, what the host operating system is,
+ and so on) and also allows for manipulating some of the host's hardware,
+ such as global USB device filters and host interface networking.
+
+ </desc>
+ <attribute name="DVDDrives" type="IMedium" readonly="yes" safearray="yes">
+ <desc>List of DVD drives available on the host.</desc>
+ </attribute>
+
+ <attribute name="floppyDrives" type="IMedium" readonly="yes" safearray="yes">
+ <desc>List of floppy drives available on the host.</desc>
+ </attribute>
+
+ <attribute name="audioDevices" type="IHostAudioDevice" readonly="yes" safearray="yes">
+ <desc>List of audio devices currently available on the host.
+ <result name="E_NOTIMPL">
+ This attribute is not implemented yet.
+ </result>
+ </desc>
+ </attribute>
+
+ <attribute name="USBDevices" type="IHostUSBDevice" readonly="yes" safearray="yes">
+ <desc>
+ List of USB devices currently attached to the host.
+ Once a new device is physically attached to the host computer,
+ it appears in this list and remains there until detached.
+
+ <note>
+ If USB functionality is not available in the given edition of
+ VirtualBox, this method will set the result code to @c E_NOTIMPL.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="USBDeviceFilters" type="IHostUSBDeviceFilter" readonly="yes" safearray="yes">
+ <desc>
+ List of USB device filters in action.
+ When a new device is physically attached to the host computer,
+ filters from this list are applied to it (in order they are stored
+ in the list). The first matched filter will determine the
+ <link to="IHostUSBDeviceFilter::action">action</link>
+ performed on the device.
+
+ Unless the device is ignored by these filters, filters of all
+ currently running virtual machines
+ (<link to="IUSBDeviceFilters::deviceFilters"/>) are applied to it.
+
+ <note>
+ If USB functionality is not available in the given edition of
+ VirtualBox, this method will set the result code to @c E_NOTIMPL.
+ </note>
+
+ <see><link to="IHostUSBDeviceFilter"/>,
+ <link to="USBDeviceState"/></see>
+ </desc>
+ </attribute>
+
+ <attribute name="networkInterfaces" type="IHostNetworkInterface" safearray="yes" readonly="yes">
+ <desc>List of host network interfaces currently defined on the host.</desc>
+ </attribute>
+
+ <attribute name="nameServers" type="wstring" safearray="yes" readonly="yes">
+ <desc> The list of nameservers registered in host's name resolving system.</desc>
+ </attribute>
+
+ <attribute name="domainName" type="wstring" readonly="yes">
+ <desc>Domain name used for name resolving.</desc>
+ </attribute>
+
+ <attribute name="searchStrings" type="wstring" safearray="yes" readonly="yes">
+ <desc>Search string registered for name resolving.</desc>
+ </attribute>
+
+ <attribute name="processorCount" type="unsigned long" readonly="yes">
+ <desc>Number of (logical) CPUs installed in the host system.</desc>
+ </attribute>
+
+ <attribute name="processorOnlineCount" type="unsigned long" readonly="yes">
+ <desc>Number of (logical) CPUs online in the host system.</desc>
+ </attribute>
+
+ <attribute name="processorCoreCount" type="unsigned long" readonly="yes">
+ <desc>Number of physical processor cores installed in the host system.</desc>
+ </attribute>
+
+ <attribute name="processorOnlineCoreCount" type="unsigned long" readonly="yes">
+ <desc>Number of physical processor cores online in the host system.</desc>
+ </attribute>
+
+ <attribute name="hostDrives" type="IHostDrive" readonly="yes" safearray="yes">
+ <desc>List of the host drive available to use in the VirtualBox.</desc>
+ </attribute>
+
+ <method name="getProcessorSpeed">
+ <desc>Query the (approximate) maximum speed of a specified host CPU in
+ Megahertz.
+ </desc>
+ <param name="cpuId" type="unsigned long" dir="in">
+ <desc>
+ Identifier of the CPU.
+ </desc>
+ </param>
+ <param name="speed" type="unsigned long" dir="return">
+ <desc>
+ Speed value. 0 is returned if value is not known or @a cpuId is
+ invalid.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getProcessorFeature">
+ <desc>Query whether a CPU feature is supported or not.</desc>
+ <param name="feature" type="ProcessorFeature" dir="in">
+ <desc>
+ CPU Feature identifier.
+ </desc>
+ </param>
+ <param name="supported" type="boolean" dir="return">
+ <desc>
+ Feature is supported or not.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getProcessorDescription">
+ <desc>Query the model string of a specified host CPU.
+ </desc>
+ <param name="cpuId" type="unsigned long" dir="in">
+ <desc>
+ Identifier of the CPU.
+ <note>
+ The current implementation might not necessarily return the
+ description for this exact CPU.
+ </note>
+ </desc>
+ </param>
+ <param name="description" type="wstring" dir="return">
+ <desc>
+ Model string. An empty string is returned if value is not known or
+ @a cpuId is invalid.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getProcessorCPUIDLeaf">
+ <desc>
+ Returns the CPU cpuid information for the specified leaf.
+ </desc>
+ <param name="cpuId" type="unsigned long" dir="in">
+ <desc>
+ Identifier of the CPU. The CPU most be online.
+ <note>
+ The current implementation might not necessarily return the
+ description for this exact CPU.
+ </note>
+ </desc>
+ </param>
+ <param name="leaf" type="unsigned long" dir="in">
+ <desc>
+ CPUID leaf index (eax).
+ </desc>
+ </param>
+ <param name="subLeaf" type="unsigned long" dir="in">
+ <desc>
+ CPUID leaf sub index (ecx). This currently only applies to cache
+ information on Intel CPUs. Use 0 if retrieving values for
+ <link to="IMachine::setCPUIDLeaf"/>.
+ </desc>
+ </param>
+ <param name="valEax" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf value for register eax.
+ </desc>
+ </param>
+ <param name="valEbx" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf value for register ebx.
+ </desc>
+ </param>
+ <param name="valEcx" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf value for register ecx.
+ </desc>
+ </param>
+ <param name="valEdx" type="unsigned long" dir="out">
+ <desc>
+ CPUID leaf value for register edx.
+ </desc>
+ </param>
+ </method>
+
+ <attribute name="memorySize" type="unsigned long" readonly="yes">
+ <desc>Amount of system memory in megabytes installed in the host system.</desc>
+ </attribute>
+
+ <attribute name="memoryAvailable" type="unsigned long" readonly="yes">
+ <desc>Available system memory in the host system.</desc>
+ </attribute>
+
+ <attribute name="operatingSystem" type="wstring" readonly="yes">
+ <desc>Name of the host system's operating system.</desc>
+ </attribute>
+
+ <attribute name="OSVersion" type="wstring" readonly="yes">
+ <desc>Host operating system's version string.</desc>
+ </attribute>
+
+ <attribute name="UTCTime" type="long long" readonly="yes">
+ <desc>Returns the current host time in milliseconds since 1970-01-01 UTC.</desc>
+ </attribute>
+
+ <attribute name="acceleration3DAvailable" type="boolean" readonly="yes">
+ <desc>Returns @c true when the host supports 3D hardware acceleration.</desc>
+ </attribute>
+
+ <method name="createHostOnlyNetworkInterface">
+ <desc>
+ Creates a new adapter for Host Only Networking.
+ <result name="E_INVALIDARG">
+ Host network interface @a name already exists.
+ </result>
+ </desc>
+ <param name="hostInterface" type="IHostNetworkInterface" dir="out">
+ <desc>
+ Created host interface object.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="removeHostOnlyNetworkInterface">
+ <desc>
+ Removes the given Host Only Networking interface.
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ No host network interface matching @a id found.
+ </result>
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in">
+ <desc>
+ Adapter GUID.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="createUSBDeviceFilter">
+ <desc>
+ Creates a new USB device filter. All attributes except
+ the filter name are set to empty (any match),
+ <i>active</i> is @c false (the filter is not active).
+
+ The created filter can be added to the list of filters using
+ <link to="#insertUSBDeviceFilter"/>.
+
+ <see><link to="#USBDeviceFilters"/></see>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>
+ Filter name. See <link to="IUSBDeviceFilter::name"/> for more information.
+ </desc>
+ </param>
+ <param name="filter" type="IHostUSBDeviceFilter" dir="return">
+ <desc>Created filter object.</desc>
+ </param>
+ </method>
+
+ <method name="insertUSBDeviceFilter">
+ <desc>
+ Inserts the given USB device to the specified position
+ in the list of filters.
+
+ Positions are numbered starting from @c 0. If the specified
+ position is equal to or greater than the number of elements in
+ the list, the filter is added at the end of the collection.
+
+ <note>
+ Duplicates are not allowed, so an attempt to insert a
+ filter already in the list is an error.
+ </note>
+ <note>
+ If USB functionality is not available in the given edition of
+ VirtualBox, this method will set the result code to @c E_NOTIMPL.
+ </note>
+
+ <see><link to="#USBDeviceFilters"/></see>
+
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ USB device filter is not created within this VirtualBox instance.
+ </result>
+ <result name="E_INVALIDARG">
+ USB device filter already in list.
+ </result>
+
+ </desc>
+ <param name="position" type="unsigned long" dir="in">
+ <desc>Position to insert the filter to.</desc>
+ </param>
+ <param name="filter" type="IHostUSBDeviceFilter" dir="in">
+ <desc>USB device filter to insert.</desc>
+ </param>
+ </method>
+
+ <method name="removeUSBDeviceFilter">
+ <desc>
+ Removes a USB device filter from the specified position in the
+ list of filters.
+
+ Positions are numbered starting from @c 0. Specifying a
+ position equal to or greater than the number of elements in
+ the list will produce an error.
+
+ <note>
+ If USB functionality is not available in the given edition of
+ VirtualBox, this method will set the result code to @c E_NOTIMPL.
+ </note>
+
+ <see><link to="#USBDeviceFilters"/></see>
+
+ <result name="E_INVALIDARG">
+ USB device filter list empty or invalid @a position.
+ </result>
+
+ </desc>
+ <param name="position" type="unsigned long" dir="in">
+ <desc>Position to remove the filter from.</desc>
+ </param>
+ </method>
+
+ <method name="findHostDVDDrive">
+ <desc>
+ Searches for a host DVD drive with the given @c name.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Given @c name does not correspond to any host drive.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the host drive to search for</desc>
+ </param>
+ <param name="drive" type="IMedium" dir="return">
+ <desc>Found host drive object</desc>
+ </param>
+ </method>
+
+ <method name="findHostFloppyDrive">
+ <desc>
+ Searches for a host floppy drive with the given @c name.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Given @c name does not correspond to any host floppy drive.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the host floppy drive to search for</desc>
+ </param>
+ <param name="drive" type="IMedium" dir="return">
+ <desc>Found host floppy drive object</desc>
+ </param>
+ </method>
+
+ <method name="findHostNetworkInterfaceByName">
+ <desc>
+ Searches through all host network interfaces for an interface with
+ the given @c name.
+ <note>
+ The method returns an error if the given @c name does not
+ correspond to any host network interface.
+ </note>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the host network interface to search for.</desc>
+ </param>
+ <param name="networkInterface" type="IHostNetworkInterface" dir="return">
+ <desc>Found host network interface object.</desc>
+ </param>
+ </method>
+ <method name="findHostNetworkInterfaceById">
+ <desc>
+ Searches through all host network interfaces for an interface with
+ the given GUID.
+ <note>
+ The method returns an error if the given GUID does not
+ correspond to any host network interface.
+ </note>
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in">
+ <desc>GUID of the host network interface to search for.</desc>
+ </param>
+ <param name="networkInterface" type="IHostNetworkInterface" dir="return">
+ <desc>Found host network interface object.</desc>
+ </param>
+ </method>
+ <method name="findHostNetworkInterfacesOfType">
+ <desc>
+ Searches through all host network interfaces and returns a list of interfaces of the specified type
+ </desc>
+ <param name="type" type="HostNetworkInterfaceType" dir="in">
+ <desc>type of the host network interfaces to search for.</desc>
+ </param>
+ <param name="networkInterfaces" type="IHostNetworkInterface" safearray="yes" dir="return">
+ <desc>Found host network interface objects.</desc>
+ </param>
+ </method>
+
+ <method name="findUSBDeviceById">
+ <desc>
+ Searches for a USB device with the given UUID.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Given @c id does not correspond to any USB device.
+ </result>
+
+ <see><link to="IUSBDevice::id"/></see>
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in">
+ <desc>UUID of the USB device to search for.</desc>
+ </param>
+ <param name="device" type="IHostUSBDevice" dir="return">
+ <desc>Found USB device object.</desc>
+ </param>
+ </method>
+
+ <method name="findUSBDeviceByAddress">
+ <desc>
+ Searches for a USB device with the given host address.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Given @c name does not correspond to any USB device.
+ </result>
+
+ <see><link to="IUSBDevice::address"/></see>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>
+ Address of the USB device (as assigned by the host) to
+ search for.
+ </desc>
+ </param>
+ <param name="device" type="IHostUSBDevice" dir="return">
+ <desc>Found USB device object.</desc>
+ </param>
+ </method>
+
+ <method name="generateMACAddress">
+ <desc>
+ Generates a valid Ethernet MAC address, 12 hexadecimal characters.
+ </desc>
+ <param name="address" type="wstring" dir="return">
+ <desc>New Ethernet MAC address.</desc>
+ </param>
+ </method>
+
+ <attribute name="videoInputDevices" type="IHostVideoInputDevice" safearray="yes" readonly="yes">
+ <desc>List of currently available host video capture devices.</desc>
+ </attribute>
+
+ <method name="addUSBDeviceSource">
+ <desc>
+ Adds a new USB device source.
+ </desc>
+ <param name="backend" type="wstring" dir="in">
+ <desc>The backend to use as the new device source.</desc>
+ </param>
+ <param name="id" type="wstring" dir="in">
+ <desc>Unique ID to identify the source.</desc>
+ </param>
+ <param name="address" type="wstring" dir="in">
+ <desc>
+ Address to use, the format is dependent on the backend.
+ For USB/IP backends for example the notation is host[:port].
+ </desc>
+ </param>
+ <param name="propertyNames" type="wstring" safearray="yes" dir="in">
+ <desc>Array of property names for more detailed configuration. Not used at the moment.</desc>
+ </param>
+ <param name="propertyValues" type="wstring" safearray="yes" dir="in">
+ <desc>Array of property values for more detailed configuration. Not used at the moment.</desc>
+ </param>
+ </method>
+
+ <method name="removeUSBDeviceSource">
+ <desc>
+ Removes a previously added USB device source.
+ </desc>
+ <param name="id" type="wstring" dir="in">
+ <desc>The identifier used when the source was added.</desc>
+ </param>
+ </method>
+
+ <attribute name="updateHost" type="IUpdateAgent" readonly="yes">
+ <desc>Checks for new VirtualBox host versions.</desc>
+ </attribute>
+
+ <attribute name="updateExtPack" type="IUpdateAgent" readonly="yes">
+ <desc>Checks for new VirtualBox Extension Pack versions.</desc>
+ </attribute>
+
+ <attribute name="updateGuestAdditions" type="IUpdateAgent" readonly="yes">
+ <desc>Checks for new Guest Additions versions.</desc>
+ </attribute>
+
+ </interface>
+
+ <!--
+ // ICPUProfile
+ /////////////////////////////////////////////////////////////////////////
+ -->
+ <interface
+ name="ICPUProfile"
+ extends="$unknown"
+ uuid="b7fda727-7a08-46ee-8dd8-f8d7308b519c"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>CPU profile. Immutable.</desc>
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>The name.</desc>
+ </attribute>
+ <attribute name="fullName" type="wstring" readonly="yes">
+ <desc>The full name.</desc>
+ </attribute>
+ <attribute name="architecture" type="CPUArchitecture" readonly="yes">
+ <desc>The CPU architecture.</desc>
+ </attribute>
+ </interface>
+
+ <!--
+ // ISystemProperties
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum name="ProxyMode" uuid="885264b3-b517-40fc-ce46-36e3bae895a4">
+ <desc> Proxy setting: System (default), NoProxy and Manual. <link to="ISystemProperties::proxyMode"/></desc>
+ <const name="System" value="0">
+ <desc>Use the system proxy settings as far as possible.</desc>
+ </const>
+ <const name="NoProxy" value="1">
+ <desc>Direct connection to the Internet.</desc>
+ </const>
+ <const name="Manual" value="2">
+ <desc>Use the manual proxy from <link to="ISystemProperties::proxyURL"/>.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="ISystemProperties"
+ extends="$unknown"
+ uuid="aac6c7cb-a371-4c58-ab51-0616896b2f2c"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="16"
+ >
+ <desc>
+ The ISystemProperties interface represents global properties of the given
+ VirtualBox installation.
+
+ These properties define limits and default values for various attributes
+ and parameters. Most of the properties are read-only, but some can be
+ changed by a user.
+ </desc>
+
+ <attribute name="minGuestRAM" type="unsigned long" readonly="yes">
+ <desc>Minimum guest system memory in Megabytes.</desc>
+ </attribute>
+
+ <attribute name="maxGuestRAM" type="unsigned long" readonly="yes">
+ <desc>Maximum guest system memory in Megabytes.</desc>
+ </attribute>
+
+ <attribute name="minGuestVRAM" type="unsigned long" readonly="yes">
+ <desc>Minimum guest video memory in Megabytes.</desc>
+ </attribute>
+
+ <attribute name="maxGuestVRAM" type="unsigned long" readonly="yes">
+ <desc>Maximum guest video memory in Megabytes.</desc>
+ </attribute>
+
+ <attribute name="minGuestCPUCount" type="unsigned long" readonly="yes">
+ <desc>Minimum CPU count.</desc>
+ </attribute>
+
+ <attribute name="maxGuestCPUCount" type="unsigned long" readonly="yes">
+ <desc>Maximum CPU count.</desc>
+ </attribute>
+
+ <attribute name="maxGuestMonitors" type="unsigned long" readonly="yes">
+ <desc>Maximum of monitors which could be connected.</desc>
+ </attribute>
+
+ <attribute name="infoVDSize" type="long long" readonly="yes">
+ <desc>Maximum size of a virtual disk image in bytes. Informational value,
+ does not reflect the limits of any virtual disk image format.</desc>
+ </attribute>
+
+ <attribute name="serialPortCount" type="unsigned long" readonly="yes">
+ <desc>
+ Maximum number of serial ports associated with every
+ <link to="IMachine"/> instance.
+ </desc>
+ </attribute>
+
+ <attribute name="parallelPortCount" type="unsigned long" readonly="yes">
+ <desc>
+ Maximum number of parallel ports associated with every
+ <link to="IMachine"/> instance.
+ </desc>
+ </attribute>
+
+ <attribute name="maxBootPosition" type="unsigned long" readonly="yes">
+ <desc>
+ Maximum device position in the boot order. This value corresponds
+ to the total number of devices a machine can boot from, to make it
+ possible to include all possible devices to the boot list.
+ <see><link to="IMachine::setBootOrder"/></see>
+ </desc>
+ </attribute>
+
+ <attribute name="rawModeSupported" type="boolean" readonly="yes">
+ <desc>
+ Indicates whether VirtualBox was built with raw-mode support.
+
+ When this reads as False, the <link to="HWVirtExPropertyType_Enabled"/>
+ setting will be ignored and assumed to be True.
+ </desc>
+ </attribute>
+
+ <attribute name="exclusiveHwVirt" type="boolean">
+ <desc>
+ Exclusive use of hardware virtualization by VirtualBox. When enabled,
+ VirtualBox assumes it can obtain full and exclusive access to the VT-x
+ or AMD-V feature of the host. To share hardware virtualization with
+ other hypervisors, this property must be disabled.
+
+ <note>This is ignored on OS X, the kernel mediates hardware
+ access there.</note>
+ </desc>
+ </attribute>
+
+ <attribute name="defaultMachineFolder" type="wstring">
+ <desc>
+ Full path to the default directory used to create new or open
+ existing machines when a machine settings file name contains no
+ path.
+
+ Starting with VirtualBox 4.0, by default, this attribute contains
+ the full path of folder named "VirtualBox VMs" in the user's
+ home directory, which depends on the host platform.
+
+ When setting this attribute, a full path must be specified.
+ Setting this property to @c null or an empty string or the
+ special value "Machines" (for compatibility reasons) will restore
+ that default value.
+
+ If the folder specified herein does not exist, it will be created
+ automatically as needed.
+
+ <see>
+ <link to="IVirtualBox::createMachine"/>,
+ <link to="IVirtualBox::openMachine"/>
+ </see>
+ </desc>
+ </attribute>
+
+ <attribute name="loggingLevel" type="wstring">
+ <desc>
+ Specifies the logging level in current use by VirtualBox.
+ </desc>
+ </attribute>
+
+ <attribute name="mediumFormats" type="IMediumFormat" safearray="yes" readonly="yes">
+ <desc>
+ List of all medium storage formats supported by this VirtualBox
+ installation.
+
+ Keep in mind that the medium format identifier
+ (<link to="IMediumFormat::id"/>) used in other API calls like
+ <link to="IVirtualBox::createMedium"/> to refer to a particular
+ medium format is a case-insensitive string. This means that, for
+ example, all of the following strings:
+ <pre>
+ "VDI"
+ "vdi"
+ "VdI"</pre>
+ refer to the same medium format.
+
+ Note that the virtual medium framework is backend-based, therefore
+ the list of supported formats depends on what backends are currently
+ installed.
+
+ <see><link to="IMediumFormat"/></see>
+ </desc>
+ </attribute>
+
+ <attribute name="defaultHardDiskFormat" type="wstring">
+ <desc>
+ Identifier of the default medium format used by VirtualBox.
+
+ The medium format set by this attribute is used by VirtualBox
+ when the medium format was not specified explicitly. One example is
+ <link to="IVirtualBox::createMedium"/> with the empty
+ format argument. A more complex example is implicit creation of
+ differencing media when taking a snapshot of a virtual machine:
+ this operation will try to use a format of the parent medium first
+ and if this format does not support differencing media the default
+ format specified by this argument will be used.
+
+ The list of supported medium formats may be obtained by the
+ <link to="#mediumFormats"/> call. Note that the default medium
+ format must have a capability to create differencing media;
+ otherwise operations that create media implicitly may fail
+ unexpectedly.
+
+ The initial value of this property is <tt>"VDI"</tt> in the current
+ version of the VirtualBox product, but may change in the future.
+
+ <note>
+ Setting this property to @c null or empty string will restore the
+ initial value.
+ </note>
+
+ <see>
+ <link to="#mediumFormats"/>,
+ <link to="IMediumFormat::id"/>,
+ <link to="IVirtualBox::createMedium"/>
+ </see>
+ </desc>
+ </attribute>
+
+ <attribute name="freeDiskSpaceWarning" type="long long">
+ <desc>Issue a warning if the free disk space is below (or in some disk
+ intensive operation is expected to go below) the given size in
+ bytes.</desc>
+ </attribute>
+
+ <attribute name="freeDiskSpacePercentWarning" type="unsigned long">
+ <desc>Issue a warning if the free disk space is below (or in some disk
+ intensive operation is expected to go below) the given percentage.</desc>
+ </attribute>
+
+ <attribute name="freeDiskSpaceError" type="long long">
+ <desc>Issue an error if the free disk space is below (or in some disk
+ intensive operation is expected to go below) the given size in
+ bytes.</desc>
+ </attribute>
+
+ <attribute name="freeDiskSpacePercentError" type="unsigned long">
+ <desc>Issue an error if the free disk space is below (or in some disk
+ intensive operation is expected to go below) the given percentage.</desc>
+ </attribute>
+
+ <attribute name="VRDEAuthLibrary" type="wstring">
+ <desc>
+ Library that provides authentication for Remote Desktop clients. The library
+ is used if a virtual machine's authentication type is set to "external"
+ in the VM RemoteDisplay configuration.
+
+ The system library extension (".DLL" or ".so") must be omitted.
+ A full path can be specified; if not, then the library must reside on the
+ system's default library path.
+
+ The default value of this property is <tt>"VBoxAuth"</tt>. There is a library
+ of that name in one of the default VirtualBox library directories.
+
+ For details about VirtualBox authentication libraries and how to implement
+ them, please refer to the VirtualBox manual.
+
+ <note>
+ Setting this property to @c null or empty string will restore the
+ initial value.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="webServiceAuthLibrary" type="wstring">
+ <desc>
+ Library that provides authentication for webservice clients. The library
+ is used if a virtual machine's authentication type is set to "external"
+ in the VM RemoteDisplay configuration and will be called from
+ within the <link to="IWebsessionManager::logon" /> implementation.
+
+ As opposed to <link to="ISystemProperties::VRDEAuthLibrary" />,
+ there is no per-VM setting for this, as the webservice is a global
+ resource (if it is running). Only for this setting (for the webservice),
+ setting this value to a literal <tt>"null"</tt> string disables authentication,
+ meaning that <link to="IWebsessionManager::logon" /> will always succeed,
+ no matter what user name and password are supplied.
+
+ The initial value of this property is <tt>"VBoxAuth"</tt>,
+ meaning that the webservice will use the same authentication
+ library that is used by default for VRDE (again, see
+ <link to="ISystemProperties::VRDEAuthLibrary" />).
+ The format and calling convention of authentication libraries
+ is the same for the webservice as it is for VRDE.
+
+ <note>
+ Setting this property to @c null or empty string will restore the
+ initial value.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="defaultVRDEExtPack" type="wstring">
+ <desc>
+ The name of the extension pack providing the default VRDE.
+
+ This attribute is for choosing between multiple extension packs
+ providing VRDE. If only one is installed, it will automatically be the
+ default one. The attribute value can be empty if no VRDE extension
+ pack is installed.
+
+ For details about VirtualBox Remote Desktop Extension and how to
+ implement one, please refer to the VirtualBox SDK.
+ </desc>
+ </attribute>
+
+ <attribute name="defaultCryptoExtPack" type="wstring">
+ <desc>
+ The name of the extension pack providing the default cryptographic support
+ for full VM encryption.
+
+ This attribute is for choosing between multiple extension packs
+ providing cryptographic support. If only one is installed, it will automatically be the
+ default one. The attribute value can be empty if no cryptographic extension
+ pack is installed.
+ </desc>
+ </attribute>
+
+ <attribute name="logHistoryCount" type="unsigned long">
+ <desc>
+ This value specifies how many old release log files are kept.
+ </desc>
+ </attribute>
+
+ <attribute name="defaultAudioDriver" type="AudioDriverType" readonly="yes">
+ <desc>This value hold the default audio driver for the current
+ system.</desc>
+ </attribute>
+
+ <attribute name="autostartDatabasePath" type="wstring">
+ <desc>
+ The path to the autostart database. Depending on the host this might
+ be a filesystem path or something else.
+ </desc>
+ </attribute>
+
+ <attribute name="defaultAdditionsISO" type="wstring">
+ <desc>
+ The path to the default Guest Additions ISO image. Can be empty if
+ the location is not known in this installation.
+ </desc>
+ </attribute>
+
+ <attribute name="defaultFrontend" type="wstring">
+ <desc>
+ Selects which VM frontend should be used by default when launching
+ a VM through the <link to="IMachine::launchVMProcess" /> method.
+ Empty or @c null strings do not define a particular default, it is up
+ to <link to="IMachine::launchVMProcess" /> to select one. See the
+ description of <link to="IMachine::launchVMProcess" /> for the valid
+ frontend types.
+
+ This global setting is overridden by the per-VM attribute
+ <link to="IMachine::defaultFrontend" /> or a frontend type
+ passed to <link to="IMachine::launchVMProcess" />.
+ </desc>
+ </attribute>
+
+ <attribute name="screenShotFormats" type="BitmapFormat" safearray="yes" readonly="yes">
+ <desc>
+ Supported bitmap formats which can be used with takeScreenShot
+ and takeScreenShotToArray methods.
+ </desc>
+ </attribute>
+
+ <attribute name="proxyMode" type="ProxyMode">
+ <desc> The proxy mode setting: System, NoProxy or Manual.</desc>
+ </attribute>
+
+ <attribute name="proxyURL" type="wstring">
+ <desc>
+ Proxy server URL for the <link to="ProxyMode_Manual" /> proxy mode.
+
+ The format is: [{type}"://"][{userid}[:{password}]@]{server}[":"{port}]
+
+ Valid types are: http (default), https, socks4, socks4a, socks5, socks5h and direct.
+ Please note that these are proxy types defining how the proxy operates rather than
+ how to proxy any similarly named protocol (i.e. don't confuse a http-proxy with
+ proxying the http protocol, as a http-proxy usually can proxy https and other protocols too).
+
+ The port number defaults to 80 for http, 443 for https and 1080 for the socks ones.
+
+ <note>The password is currently stored as plain text! Use the <link to="ProxyMode_System" />
+ mode if you consider the proxy password to be sensitive.</note>
+
+ An empty string will cause the behavior to be identical to <link to="ProxyMode_System" />.
+ For compatibility with libproxy, an URL starting with "direct://" will cause
+ <link to="ProxyMode_NoProxy" /> behavior.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedParavirtProviders" type="ParavirtProvider" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="ParavirtProvider"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedClipboardModes" type="ClipboardMode" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="ClipboardMode"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedDnDModes" type="DnDMode" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="DnDMode"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedFirmwareTypes" type="FirmwareType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="FirmwareType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedPointingHIDTypes" type="PointingHIDType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="PointingHIDType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedKeyboardHIDTypes" type="KeyboardHIDType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="KeyboardHIDType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedVFSTypes" type="VFSType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="VFSType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedImportOptions" type="ImportOptions" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="ImportOptions"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedExportOptions" type="ExportOptions" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="ExportOptions"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedRecordingFeatures" type="RecordingFeature" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="RecordingFeature"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedRecordingAudioCodecs" type="RecordingAudioCodec" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="RecordingAudioCodec"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedRecordingVideoCodecs" type="RecordingVideoCodec" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="RecordingVideoCodec"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedRecordingVSModes" type="RecordingVideoScalingMode" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="RecordingVideoScalingMode"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedRecordingARCModes" type="RecordingRateControlMode" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported audio codec values for enum <link to="RecordingRateControlMode"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedRecordingVRCModes" type="RecordingRateControlMode" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported video codec values for enum <link to="RecordingRateControlMode"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedGraphicsControllerTypes" type="GraphicsControllerType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="GraphicsControllerType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedCloneOptions" type="CloneOptions" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="CloneOptions"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedAutostopTypes" type="AutostopType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="AutostopType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedVMProcPriorities" type="VMProcPriority" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="VMProcPriority"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedNetworkAttachmentTypes" type="NetworkAttachmentType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="NetworkAttachmentType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedNetworkAdapterTypes" type="NetworkAdapterType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="NetworkAdapterType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedPortModes" type="PortMode" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="PortMode"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedUartTypes" type="UartType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="UartType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedUSBControllerTypes" type="USBControllerType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="USBControllerType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedAudioDriverTypes" type="AudioDriverType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="AudioDriverType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedAudioControllerTypes" type="AudioControllerType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="AudioControllerType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedStorageBuses" type="StorageBus" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="StorageBus"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedStorageControllerTypes" type="StorageControllerType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="StorageControllerType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedChipsetTypes" type="ChipsetType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="ChipsetType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedIommuTypes" type="IommuType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="IommuType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="supportedTpmTypes" type="TpmType" safearray="yes" readonly="yes">
+ <desc>
+ Returns an array of officially supported values for enum <link to="TpmType"/>,
+ in the sense of what is e.g. worth offering in the VirtualBox GUI.
+ </desc>
+ </attribute>
+
+ <attribute name="languageId" type="wstring">
+ <desc>
+ The API language ID used to translate messages to client.
+ </desc>
+ </attribute>
+
+ <method name="getMaxNetworkAdapters">
+ <desc>
+ Maximum total number of network adapters associated with every
+ <link to="IMachine"/> instance.
+ </desc>
+
+ <param name="chipset" type="ChipsetType" dir="in">
+ <desc>The chipset type to get the value for.</desc>
+ </param>
+
+
+ <param name="maxNetworkAdapters" type="unsigned long" dir="return">
+ <desc>The maximum total number of network adapters allowed.</desc>
+ </param>
+
+ </method>
+
+ <method name="getMaxNetworkAdaptersOfType">
+ <desc>
+ Maximum number of network adapters of a given attachment type,
+ associated with every <link to="IMachine"/> instance.
+ </desc>
+
+ <param name="chipset" type="ChipsetType" dir="in">
+ <desc>The chipset type to get the value for.</desc>
+ </param>
+
+ <param name="type" type="NetworkAttachmentType" dir="in">
+ <desc>Type of attachment.</desc>
+ </param>
+
+ <param name="maxNetworkAdapters" type="unsigned long" dir="return">
+ <desc>The maximum number of network adapters allowed for
+ particular chipset and attachment type.</desc>
+ </param>
+
+ </method>
+
+
+ <method name="getMaxDevicesPerPortForStorageBus">
+ <desc>Returns the maximum number of devices which can be attached to a port
+ for the given storage bus.</desc>
+
+ <param name="bus" type="StorageBus" dir="in">
+ <desc>The storage bus type to get the value for.</desc>
+ </param>
+
+ <param name="maxDevicesPerPort" type="unsigned long" dir="return">
+ <desc>The maximum number of devices which can be attached to the port for the given
+ storage bus.</desc>
+ </param>
+ </method>
+
+ <method name="getMinPortCountForStorageBus">
+ <desc>Returns the minimum number of ports the given storage bus supports.</desc>
+
+ <param name="bus" type="StorageBus" dir="in">
+ <desc>The storage bus type to get the value for.</desc>
+ </param>
+
+ <param name="minPortCount" type="unsigned long" dir="return">
+ <desc>The minimum number of ports for the given storage bus.</desc>
+ </param>
+ </method>
+
+ <method name="getMaxPortCountForStorageBus">
+ <desc>Returns the maximum number of ports the given storage bus supports.</desc>
+
+ <param name="bus" type="StorageBus" dir="in">
+ <desc>The storage bus type to get the value for.</desc>
+ </param>
+
+ <param name="maxPortCount" type="unsigned long" dir="return">
+ <desc>The maximum number of ports for the given storage bus.</desc>
+ </param>
+ </method>
+
+ <method name="getMaxInstancesOfStorageBus">
+ <desc>Returns the maximum number of storage bus instances which
+ can be configured for each VM. This corresponds to the number of
+ storage controllers one can have. Value may depend on chipset type
+ used.</desc>
+
+ <param name="chipset" type="ChipsetType" dir="in">
+ <desc>The chipset type to get the value for.</desc>
+ </param>
+
+ <param name="bus" type="StorageBus" dir="in">
+ <desc>The storage bus type to get the value for.</desc>
+ </param>
+
+ <param name="maxInstances" type="unsigned long" dir="return">
+ <desc>The maximum number of instances for the given storage bus.</desc>
+ </param>
+ </method>
+
+ <method name="getDeviceTypesForStorageBus">
+ <desc>Returns list of all the supported device types
+ (<link to="DeviceType"/>) for the given type of storage
+ bus.</desc>
+
+ <param name="bus" type="StorageBus" dir="in">
+ <desc>The storage bus type to get the value for.</desc>
+ </param>
+
+ <param name="deviceTypes" type="DeviceType" safearray="yes" dir="return">
+ <desc>The list of all supported device types for the given storage bus.</desc>
+ </param>
+ </method>
+
+ <method name="getStorageBusForStorageControllerType">
+ <desc>Returns the <link to="StorageBus"/> enum value
+ for a given storage controller type.</desc>
+
+ <param name="storageControllerType" type="StorageControllerType" dir="in">
+ <desc>The storage controller type to get the value for.</desc>
+ </param>
+
+ <param name="storageBus" type="StorageBus" dir="return">
+ <desc>The storage bus which is applicable.</desc>
+ </param>
+ </method>
+
+ <method name="getStorageControllerTypesForStorageBus">
+ <desc>Returns the possible <link to="StorageControllerType"/> enum values
+ for a given storage bus.</desc>
+
+ <param name="storageBus" type="StorageBus" dir="in">
+ <desc>The storage bus type to get the values for.</desc>
+ </param>
+
+ <param name="storageControllerType" type="StorageControllerType" safearray="yes" dir="return">
+ <desc>The enum values (sorted by what should be a sensible decreasing
+ importance of the type) which are valid.</desc>
+ </param>
+ </method>
+
+ <method name="getDefaultIoCacheSettingForStorageController" dtracename="getDefaultStorageCtrlCacheSetting">
+ <desc>Returns the default I/O cache setting for the
+ given storage controller</desc>
+
+ <param name="controllerType" type="StorageControllerType" dir="in">
+ <desc>The storage controller type to get the setting for.</desc>
+ </param>
+
+ <param name="enabled" type="boolean" dir="return">
+ <desc>Returned flag indicating the default value</desc>
+ </param>
+ </method>
+
+ <method name="getStorageControllerHotplugCapable">
+ <desc>Returns whether the given storage controller supports
+ hot-plugging devices.</desc>
+
+ <param name="controllerType" type="StorageControllerType" dir="in">
+ <desc>The storage controller to check the setting for.</desc>
+ </param>
+
+ <param name="hotplugCapable" type="boolean" dir="return">
+ <desc>Returned flag indicating whether the controller is hotplug capable</desc>
+ </param>
+ </method>
+
+ <method name="getMaxInstancesOfUSBControllerType">
+ <desc>Returns the maximum number of USB controller instances which
+ can be configured for each VM. This corresponds to the number of
+ USB controllers one can have. Value may depend on chipset type
+ used.</desc>
+
+ <param name="chipset" type="ChipsetType" dir="in">
+ <desc>The chipset type to get the value for.</desc>
+ </param>
+
+ <param name="type" type="USBControllerType" dir="in">
+ <desc>The USB controller type to get the value for.</desc>
+ </param>
+
+ <param name="maxInstances" type="unsigned long" dir="return">
+ <desc>The maximum number of instances for the given USB controller type.</desc>
+ </param>
+ </method>
+
+ <method name="getCPUProfiles">
+ <desc>Returns CPU profiles matching the given criteria.</desc>
+
+ <param name="architecture" type="CPUArchitecture" dir="in">
+ <desc>The architecture to get profiles for. Required.</desc>
+ </param>
+ <param name="namePattern" type="wstring" dir="in">
+ <desc>Name pattern. Simple wildcard matching using asterisk (*) and
+ question mark (?).</desc>
+ </param>
+ <param name="profiles" type="ICPUProfile" safearray="yes" dir="return">
+ <desc>The matching CPU profiles.</desc>
+ </param>
+ </method>
+ </interface>
+
+ <!--
+ // IGuest
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IGuestOSType" extends="$unknown"
+ uuid="966303d0-36a8-4180-8971-18650b0d1055"
+ wsmap="struct"
+ rest="managed"
+ reservedAttributes="16"
+ >
+ <desc>
+ </desc>
+
+ <attribute name="familyId" type="wstring" readonly="yes">
+ <desc>Guest OS family identifier string.</desc>
+ </attribute>
+
+ <attribute name="familyDescription" type="wstring" readonly="yes">
+ <desc>Human readable description of the guest OS family.</desc>
+ </attribute>
+
+ <attribute name="id" type="wstring" readonly="yes">
+ <desc>Guest OS identifier string.</desc>
+ </attribute>
+
+ <attribute name="description" type="wstring" readonly="yes">
+ <desc>Human readable description of the guest OS.</desc>
+ </attribute>
+
+ <attribute name="is64Bit" type="boolean" readonly="yes">
+ <desc>Returns @c true if the given OS is 64-bit</desc>
+ </attribute>
+
+ <attribute name="recommendedIOAPIC" type="boolean" readonly="yes">
+ <desc>Returns @c true if I/O-APIC recommended for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedVirtEx" type="boolean" readonly="yes">
+ <desc>Returns @c true if VT-x or AMD-V recommended for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedRAM" type="unsigned long" readonly="yes">
+ <desc>Recommended RAM size in Megabytes.</desc>
+ </attribute>
+
+ <attribute name="recommendedGraphicsController" type="GraphicsControllerType" readonly="yes">
+ <desc>Recommended graphics controller type.</desc>
+ </attribute>
+
+ <attribute name="recommendedVRAM" type="unsigned long" readonly="yes">
+ <desc>Recommended video RAM size in Megabytes.</desc>
+ </attribute>
+
+ <attribute name="recommended2DVideoAcceleration" type="boolean" readonly="yes">
+ <desc>Returns @c true if 2D video acceleration is recommended for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommended3DAcceleration" type="boolean" readonly="yes">
+ <desc>Returns @c true if 3D acceleration is recommended for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedHDD" type="long long" readonly="yes">
+ <desc>Recommended hard disk size in bytes.</desc>
+ </attribute>
+
+ <attribute name="adapterType" type="NetworkAdapterType" readonly="yes">
+ <desc>Returns recommended network adapter for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedPAE" type="boolean" readonly="yes">
+ <desc>Returns @c true if using PAE is recommended for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedDVDStorageController" type="StorageControllerType" readonly="yes">
+ <desc>Recommended storage controller type for DVD/CD drives.</desc>
+ </attribute>
+
+ <attribute name="recommendedDVDStorageBus" type="StorageBus" readonly="yes">
+ <desc>Recommended storage bus type for DVD/CD drives.</desc>
+ </attribute>
+
+ <attribute name="recommendedHDStorageController" type="StorageControllerType" readonly="yes">
+ <desc>Recommended storage controller type for HD drives.</desc>
+ </attribute>
+
+ <attribute name="recommendedHDStorageBus" type="StorageBus" readonly="yes">
+ <desc>Recommended storage bus type for HD drives.</desc>
+ </attribute>
+
+ <attribute name="recommendedFirmware" type="FirmwareType" readonly="yes">
+ <desc>Recommended firmware type.</desc>
+ </attribute>
+
+ <attribute name="recommendedUSBHID" type="boolean" readonly="yes">
+ <desc>Returns @c true if using USB Human Interface Devices, such as keyboard and mouse recommended.</desc>
+ </attribute>
+
+ <attribute name="recommendedHPET" type="boolean" readonly="yes">
+ <desc>Returns @c true if using HPET is recommended for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedUSBTablet" type="boolean" readonly="yes">
+ <desc>Returns @c true if using a USB Tablet is recommended.</desc>
+ </attribute>
+
+ <attribute name="recommendedRTCUseUTC" type="boolean" readonly="yes">
+ <desc>Returns @c true if the RTC of this VM should be set to UTC</desc>
+ </attribute>
+
+ <attribute name="recommendedChipset" type="ChipsetType" readonly="yes">
+ <desc>Recommended chipset type.</desc>
+ </attribute>
+
+ <attribute name="recommendedIommuType" type="IommuType" readonly="yes">
+ <desc>Recommended IOMMU type.</desc>
+ </attribute>
+
+ <attribute name="recommendedAudioController" type="AudioControllerType" readonly="yes">
+ <desc>Recommended audio controller type.</desc>
+ </attribute>
+
+ <attribute name="recommendedAudioCodec" type="AudioCodecType" readonly="yes">
+ <desc>Recommended audio codec type.</desc>
+ </attribute>
+
+ <attribute name="recommendedFloppy" type="boolean" readonly="yes">
+ <desc>Returns @c true a floppy drive is recommended for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedUSB" type="boolean" readonly="yes">
+ <desc>Returns @c true a USB controller is recommended for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedUSB3" type="boolean" readonly="yes">
+ <desc>Returns @c true an xHCI (USB 3) controller is recommended for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedTFReset" type="boolean" readonly="yes">
+ <desc>Returns @c true if using VCPU reset on triple fault is recommended for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedX2APIC" type="boolean" readonly="yes">
+ <desc>Returns @c true if X2APIC is recommended for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedCPUCount" type="unsigned long" readonly="yes">
+ <desc>Number of vCPUs recommended for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedTpmType" type="TpmType" readonly="yes">
+ <desc>Returns the recommended trusted platform module type for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedSecureBoot" type="boolean" readonly="yes">
+ <desc>Returns @c true if EFI secure boot is recommended for this OS type.</desc>
+ </attribute>
+
+ <attribute name="recommendedWDDMGraphics" type="boolean" readonly="yes">
+ <desc>Returns @c true if this OS usually has a WDDM graphics driver
+ from guest additions.</desc>
+ </attribute>
+
+ </interface>
+
+ <enum
+ name="AdditionsFacilityType"
+ uuid="c4b10d74-dd48-4ff4-9a40-785a2a389ade"
+ >
+ <desc>
+ Guest Additions facility IDs.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No/invalid facility.</desc>
+ </const>
+ <const name="VBoxGuestDriver" value="20">
+ <desc>VirtualBox base driver (VBoxGuest).</desc>
+ </const>
+ <const name="AutoLogon" value="90">
+ <desc>Auto-logon modules (VBoxGINA, VBoxCredProv, pam_vbox).</desc>
+ </const>
+ <const name="VBoxService" value="100">
+ <desc>VirtualBox system service (VBoxService).</desc>
+ </const>
+ <const name="VBoxTrayClient" value="101">
+ <desc>VirtualBox desktop integration (VBoxTray on Windows, VBoxClient on non-Windows).</desc>
+ </const>
+ <const name="Seamless" value="1000">
+ <desc>Seamless guest desktop integration.</desc>
+ </const>
+ <const name="Graphics" value="1100">
+ <desc>Guest graphics mode. If not enabled, seamless rendering will not work, resize hints
+ are not immediately acted on and guest display resizes are probably not initiated by
+ the Guest Additions.
+ </desc>
+ </const>
+ <const name="MonitorAttach" value="1101">
+ <desc>Guest supports monitor hotplug.
+ </desc>
+ </const>
+ <const name="All" value="2147483646">
+ <desc>All facilities selected.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="AdditionsFacilityClass"
+ uuid="446451b2-c88d-4e5d-84c9-91bc7f533f5f"
+ >
+ <desc>
+ Guest Additions facility classes.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No/invalid class.</desc>
+ </const>
+ <const name="Driver" value="10">
+ <desc>Driver.</desc>
+ </const>
+ <const name="Service" value="30">
+ <desc>System service.</desc>
+ </const>
+ <const name="Program" value="50">
+ <desc>Program.</desc>
+ </const>
+ <const name="Feature" value="100">
+ <desc>Feature.</desc>
+ </const>
+ <const name="ThirdParty" value="999">
+ <desc>Third party.</desc>
+ </const>
+ <const name="All" value="2147483646">
+ <desc>All facility classes selected.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="AdditionsFacilityStatus"
+ uuid="ce06f9e1-394e-4fe9-9368-5a88c567dbde"
+ >
+ <desc>
+ Guest Additions facility states.
+ </desc>
+
+ <const name="Inactive" value="0">
+ <desc>Facility is not active.</desc>
+ </const>
+ <const name="Paused" value="1">
+ <desc>Facility has been paused.</desc>
+ </const>
+ <const name="PreInit" value="20">
+ <desc>Facility is preparing to initialize.</desc>
+ </const>
+ <const name="Init" value="30">
+ <desc>Facility is initializing.</desc>
+ </const>
+ <const name="Active" value="50">
+ <desc>Facility is up and running.</desc>
+ </const>
+ <const name="Terminating" value="100">
+ <desc>Facility is shutting down.</desc>
+ </const>
+ <const name="Terminated" value="101">
+ <desc>Facility successfully shut down.</desc>
+ </const>
+ <const name="Failed" value="800">
+ <desc>Facility failed to start.</desc>
+ </const>
+ <const name="Unknown" value="999">
+ <desc>Facility status is unknown.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IAdditionsFacility" extends="$unknown"
+ uuid="f2f7fae4-4a06-81fc-a916-78b2da1fa0e5"
+ wsmap="struct"
+ rest="managed"
+ reservedAttributes="2"
+ >
+ <desc>
+ Structure representing a Guest Additions facility.
+ </desc>
+
+ <attribute name="classType" type="AdditionsFacilityClass" readonly="yes">
+ <desc>The class this facility is part of.</desc>
+ </attribute>
+
+ <attribute name="lastUpdated" type="long long" readonly="yes">
+ <desc>
+ Timestamp of the last status update,
+ in milliseconds since 1970-01-01 UTC.
+ </desc>
+ </attribute>
+
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>The facility's friendly name.</desc>
+ </attribute>
+
+ <attribute name="status" type="AdditionsFacilityStatus" readonly="yes">
+ <desc>The current status.</desc>
+ </attribute>
+
+ <attribute name="type" type="AdditionsFacilityType" readonly="yes">
+ <desc>The facility's type ID.</desc>
+ </attribute>
+ </interface>
+
+ <enum
+ name="AdditionsRunLevelType"
+ uuid="a25417ee-a9dd-4f5b-b0dc-377860087754"
+ >
+ <desc>
+ Guest Additions run level type.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>Guest Additions are not loaded.</desc>
+ </const>
+ <const name="System" value="1">
+ <desc>Guest drivers are loaded.</desc>
+ </const>
+ <const name="Userland" value="2">
+ <desc>Common components (such as application services) are loaded.</desc>
+ </const>
+ <const name="Desktop" value="3">
+ <desc>Per-user desktop components are loaded.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="AdditionsUpdateFlag"
+ uuid="726a818d-18d6-4389-94e8-3e9e6826171a"
+ >
+ <desc>
+ Guest Additions update flags.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No flag set.</desc>
+ </const>
+ <const name="WaitForUpdateStartOnly" value="1">
+ <desc>Starts the regular updating process and waits until the
+ actual Guest Additions update inside the guest was started.
+ This can be necessary due to needed interaction with the guest
+ OS during the installation phase.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="GuestShutdownFlag"
+ uuid="28D19C9C-5862-4930-B29A-F117712B4864"
+ >
+ <desc>
+ Guest shutdown flags.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No flag set.</desc>
+ </const>
+ <const name="PowerOff" value="1">
+ <desc>Performs a reboot after shutdown.</desc>
+ </const>
+ <const name="Reboot" value="2">
+ <desc>Performs a reboot after shutdown.</desc>
+ </const>
+ <const name="Force" value="4">
+ <desc>Force the system to shutdown/reboot regardless of objecting
+ application or other stuff. This flag might not be realized on
+ all systems.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="GuestSessionStatus"
+ uuid="ac2669da-4624-44f2-85b5-0b0bfb8d8673"
+ >
+ <desc>
+ Guest session status. This enumeration represents possible values of
+ the <link to="IGuestSession::status"/> attribute.
+ </desc>
+ <const name="Undefined" value="0">
+ <desc>Guest session is in an undefined state.</desc>
+ </const>
+ <const name="Starting" value="10">
+ <desc>Guest session is being started.</desc>
+ </const>
+ <const name="Started" value="100">
+ <desc>Guest session has been started.</desc>
+ </const>
+ <const name="Terminating" value="480">
+ <desc>Guest session is being terminated.</desc>
+ </const>
+ <const name="Terminated" value="500">
+ <desc>Guest session terminated normally.</desc>
+ </const>
+ <const name="TimedOutKilled" value="512">
+ <desc>Guest session timed out and was killed.</desc>
+ </const>
+ <const name="TimedOutAbnormally" value="513">
+ <desc>Guest session timed out and was not killed successfully.</desc>
+ </const>
+ <const name="Down" value="600">
+ <desc>Service/OS is stopping, guest session was killed.</desc>
+ </const>
+ <const name="Error" value="800">
+ <desc>Something went wrong.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="GuestSessionWaitForFlag"
+ uuid="bb7a372a-f635-4e11-a81a-e707f3a52ef5"
+ >
+ <desc>
+ Guest session waiting flags.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No waiting flags specified. Do not use this.</desc>
+ </const>
+ <const name="Start" value="1">
+ <desc>Wait for the guest session being started.</desc>
+ </const>
+ <const name="Terminate" value="2">
+ <desc>Wait for the guest session being terminated.</desc>
+ </const>
+ <const name="Status" value="4">
+ <desc>Wait for the next guest session status change.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="GuestSessionWaitResult"
+ uuid="c0f6a8a5-fdb6-42bf-a582-56c6f82bcd2d"
+ >
+ <desc>
+ Guest session waiting results. Depending on the session waiting flags (for
+ more information see <link to="GuestSessionWaitForFlag"/>) the waiting result
+ can vary based on the session's current status.
+
+ To wait for a guest session to terminate after it has been
+ created by <link to="IGuest::createSession"/> one would specify
+ GuestSessionWaitResult_Terminate.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No result was returned. Not being used.</desc>
+ </const>
+ <const name="Start" value="1">
+ <desc>The guest session has been started.</desc>
+ </const>
+ <const name="Terminate" value="2">
+ <desc>The guest session has been terminated.</desc>
+ </const>
+ <const name="Status" value="3">
+ <desc>
+ The guest session has changed its status. The status then can
+ be retrieved via <link to="IGuestSession::status"/>.
+ </desc>
+ </const>
+ <const name="Error" value="4">
+ <desc>Error while executing the process.</desc>
+ </const>
+ <const name="Timeout" value="5">
+ <desc>
+ The waiting operation timed out. This also will happen
+ when no event has been occurred matching the
+ current waiting flags in a <link to="IGuestSession::waitFor"/> call.
+ </desc>
+ </const>
+ <const name="WaitFlagNotSupported" value="6">
+ <desc>
+ A waiting flag specified in the <link to="IGuestSession::waitFor"/> call
+ is not supported by the guest.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="GuestUserState"
+ uuid="b2a82b02-fd3d-4fc2-ba84-6ba5ac8be198"
+ >
+ <desc>
+ State a guest user has been changed to.
+ </desc>
+
+ <const name="Unknown" value="0">
+ <desc>Unknown state. Not being used.</desc>
+ </const>
+ <const name="LoggedIn" value="1">
+ <desc>A guest user has been successfully logged into
+ the guest OS.
+ <note>This property is not implemented yet!</note>
+ </desc>
+ </const>
+ <const name="LoggedOut" value="2">
+ <desc>A guest user has been successfully logged out
+ of the guest OS.
+ <note>This property is not implemented yet!</note>
+ </desc>
+ </const>
+ <const name="Locked" value="3">
+ <desc>A guest user has locked its account. This might
+ include running a password-protected screensaver
+ in the guest.
+ <note>This property is not implemented yet!</note>
+ </desc>
+ </const>
+ <const name="Unlocked" value="4">
+ <desc>A guest user has unlocked its account.
+ <note>This property is not implemented yet!</note>
+ </desc>
+ </const>
+ <const name="Disabled" value="5">
+ <desc>A guest user has been disabled by the guest OS.
+ <note>This property is not implemented yet!</note>
+ </desc>
+ </const>
+ <const name="Idle" value="6">
+ <desc>
+ A guest user currently is not using the guest OS.
+ <note>Currently only available for Windows guests since
+ Windows 2000 SP2.</note>
+ <note>On Windows guests this function currently only supports
+ reporting contiguous idle times up to 49.7 days per user.</note>
+ The event will be triggered if a guest user is not active for
+ at least 5 seconds. This threshold can be adjusted by either altering
+ VBoxService's command line in the guest to
+ <pre>--vminfo-user-idle-threshold &lt;ms&gt;</pre>
+ , or by setting the per-VM guest property
+ <pre>/VirtualBox/GuestAdd/VBoxService/--vminfo-user-idle-threshold &lt;ms&gt;</pre>
+ with the RDONLYGUEST flag on the host. In both cases VBoxService needs
+ to be restarted in order to get the changes applied.
+ </desc>
+ </const>
+ <const name="InUse" value="7">
+ <desc>A guest user continued using the guest OS after
+ being idle.</desc>
+ </const>
+ <const name="Created" value="8">
+ <desc>A guest user has been successfully created.
+ <note>This property is not implemented yet!</note>
+ </desc>
+ </const>
+ <const name="Deleted" value="9">
+ <desc>A guest user has been successfully deleted.
+ <note>This property is not implemented yet!</note>
+ </desc>
+ </const>
+ <const name="SessionChanged" value="10">
+ <desc>To guest OS has changed the session of a user.
+ <note>This property is not implemented yet!</note>
+ </desc>
+ </const>
+ <const name="CredentialsChanged" value="11">
+ <desc>To guest OS has changed the authentication
+ credentials of a user. This might include changed passwords
+ and authentication types.
+ <note>This property is not implemented yet!</note>
+ </desc>
+ </const>
+ <const name="RoleChanged" value="12">
+ <desc>To guest OS has changed the role of a user permanently,
+ e.g. granting / denying administrative rights.
+ <note>This property is not implemented yet!</note>
+ </desc>
+ </const>
+ <const name="GroupAdded" value="13">
+ <desc>To guest OS has added a user to a specific
+ user group.
+ <note>This property is not implemented yet!</note>
+ </desc>
+ </const>
+ <const name="GroupRemoved" value="14">
+ <desc>To guest OS has removed a user from a specific
+ user group.
+ <note>This property is not implemented yet!</note>
+ </desc>
+ </const>
+ <const name="Elevated" value="15">
+ <desc>To guest OS temporarily has elevated a user
+ to perform a certain task.
+ <note>This property is not implemented yet!</note>
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="FileSeekOrigin"
+ uuid="ad32f789-4279-4530-979c-f16892e1c263"
+ >
+ <desc>
+ What a file seek (<link to="IFile::seek"/>) is relative to.
+ </desc>
+
+ <const name="Begin" value="0">
+ <desc>Seek from the beginning of the file.</desc>
+ </const>
+ <const name="Current" value="1">
+ <desc>Seek from the current file position.</desc>
+ </const>
+ <const name="End" value="2">
+ <desc>Seek relative to the end of the file. To seek to the position two
+ bytes from the end of the file, specify -2 as the seek offset.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="ProcessInputFlag"
+ uuid="5d38c1dd-2604-4ddf-92e5-0c0cdd3bdbd5"
+ >
+ <desc>
+ Guest process input flags.
+ </desc>
+ <const name="None" value="0">
+ <desc>No flag set.</desc>
+ </const>
+ <const name="EndOfFile" value="1">
+ <desc>End of file (input) reached.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="ProcessOutputFlag"
+ uuid="9979e85a-52bb-40b7-870c-57115e27e0f1"
+ >
+ <desc>
+ Guest process output flags for specifying which
+ type of output to retrieve.
+ </desc>
+ <const name="None" value="0">
+ <desc>No flags set. Get output from stdout.</desc>
+ </const>
+ <const name="StdErr" value="1">
+ <desc>Get output from stderr.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="ProcessWaitForFlag"
+ uuid="23b550c7-78e1-437e-98f0-65fd9757bcd2"
+ >
+ <desc>
+ Process waiting flags.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No waiting flags specified. Do not use this.</desc>
+ </const>
+ <const name="Start" value="1">
+ <desc>Wait for the process being started.</desc>
+ </const>
+ <const name="Terminate" value="2">
+ <desc>Wait for the process being terminated.</desc>
+ </const>
+ <const name="StdIn" value="4">
+ <desc>Wait for stdin becoming available.</desc>
+ </const>
+ <const name="StdOut" value="8">
+ <desc>Wait for data becoming available on stdout.</desc>
+ </const>
+ <const name="StdErr" value="16">
+ <desc>Wait for data becoming available on stderr.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="ProcessWaitResult"
+ uuid="40719cbe-f192-4fe9-a231-6697b3c8e2b4"
+ >
+ <desc>
+ Process waiting results. Depending on the process waiting flags (for
+ more information see <link to="ProcessWaitForFlag"/>) the waiting result
+ can vary based on the processes' current status.
+
+ To wait for a guest process to terminate after it has been
+ created by <link to="IGuestSession::processCreate"/> or <link to="IGuestSession::processCreateEx"/>
+ one would specify ProcessWaitFor_Terminate.
+
+ If a guest process has been started with ProcessCreateFlag_WaitForStdOut
+ a client can wait with ProcessWaitResult_StdOut for new data to arrive on
+ stdout; same applies for ProcessCreateFlag_WaitForStdErr and
+ ProcessWaitResult_StdErr.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No result was returned. Not being used.</desc>
+ </const>
+ <const name="Start" value="1">
+ <desc>The process has been started.</desc>
+ </const>
+ <const name="Terminate" value="2">
+ <desc>The process has been terminated.</desc>
+ </const>
+ <const name="Status" value="3">
+ <desc>
+ The process has changed its status. The status then can
+ be retrieved via <link to="IProcess::status"/>.
+ </desc>
+ </const>
+ <const name="Error" value="4">
+ <desc>Error while executing the process.</desc>
+ </const>
+ <const name="Timeout" value="5">
+ <desc>
+ The waiting operation timed out. Also use if the guest process has
+ timed out in the guest side (kill attempted).
+ </desc>
+ </const>
+ <const name="StdIn" value="6">
+ <desc>The process signalled that stdin became available for writing.</desc>
+ </const>
+ <const name="StdOut" value="7">
+ <desc>Data on stdout became available for reading.</desc>
+ </const>
+ <const name="StdErr" value="8">
+ <desc>Data on stderr became available for reading.</desc>
+ </const>
+ <const name="WaitFlagNotSupported" value="9">
+ <desc>
+ A waiting flag specified in the <link to="IProcess::waitFor"/> call
+ is not supported by the guest.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="FileCopyFlag"
+ uuid="791909d7-4c64-2fa4-4303-adb10658d347"
+ >
+ <desc>
+ File copying flags.
+ </desc>
+ <const name="None" value="0">
+ <desc>No flag set.</desc>
+ </const>
+ <const name="NoReplace" value="1">
+ <!-- Would make more sense to not replace by default, however we the GAs
+ only supports replacing as of writing, so we currently have no choice. -->
+ <desc>
+ Do not replace the destination file if it exists.
+ </desc>
+ </const>
+ <const name="FollowLinks" value="2">
+ <desc>
+ Follow symbolic links.
+ </desc>
+ </const>
+ <const name="Update" value="4">
+ <desc>
+ Only copy when the source file is newer than the destination file
+ or when the destination file is missing.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="FsObjMoveFlag"
+ uuid="2450a05d-80c6-4c96-9a17-94d73293ff86"
+ >
+ <desc>
+ File moving flags.
+ </desc>
+ <const name="None" value="0">
+ <desc>No flag set.</desc>
+ </const>
+ <const name="Replace" value="1">
+ <desc>
+ Replace the destination file, symlink, etc if it exists, however this
+ does not allow replacing any directories.
+ </desc>
+ </const>
+ <const name="FollowLinks" value="2">
+ <desc>
+ Follow symbolic links in the final components or not (only applied to
+ the given source and target paths, not to anything else).
+ </desc>
+ </const>
+ <const name="AllowDirectoryMoves" value="4">
+ <desc>
+ Allow moving directories accross file system boundraries. Because it
+ is could be a big undertaking, we require extra assurance that we
+ should do it when requested.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="DirectoryCreateFlag"
+ uuid="bd721b0e-ced5-4f79-b368-249897c32a36"
+ >
+ <desc>
+ Directory creation flags.
+ </desc>
+ <const name="None" value="0">
+ <desc>No flag set.</desc>
+ </const>
+ <const name="Parents" value="1">
+ <desc>No error if existing, make parent directories as needed.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="DirectoryCopyFlag"
+ uuid="20108C67-B1EB-4EF6-869B-25539A47A18E"
+ >
+ <desc>
+ Directory copying flags.
+ </desc>
+ <const name="None" value="0">
+ <desc>No flag set.</desc>
+ </const>
+ <const name="CopyIntoExisting" value="1">
+ <desc>Allow copying into an existing destination directory.</desc>
+ </const>
+ <const name="Recursive" value="2">
+ <desc>Copy directories recursively.</desc>
+ </const>
+ <const name="FollowLinks" value="4">
+ <desc>Follow symbolic links.</desc>
+ </const>
+ <!-- Later, basically have to see what cp and xcopy offers. -->
+ </enum>
+
+ <enum
+ name="DirectoryRemoveRecFlag"
+ uuid="455aabf0-7692-48f6-9061-f21579b65769"
+ >
+ <desc>
+ Directory recursive removement flags.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No flag set.</desc>
+ </const>
+ <const name="ContentAndDir" value="1">
+ <desc>Delete the content of the directory and the directory itself.</desc>
+ </const>
+ <const name="ContentOnly" value="2">
+ <desc>Only delete the content of the directory, omit the directory it self.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="FsObjRenameFlag"
+ uuid="59bbf3a1-4e23-d7cf-05d5-ccae32080ed2"
+ >
+ <desc>
+ Flags for use when renaming file system objects (files, directories,
+ symlink, etc), see <link to="IGuestSession::fsObjRename"/>.
+ </desc>
+
+ <const name="NoReplace" value="0">
+ <desc>Do not replace any destination object.</desc>
+ </const>
+ <const name="Replace" value="1">
+ <desc>This will attempt to replace any destination object other except
+ directories. (The default is to fail if the destination exists.)</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="ProcessCreateFlag"
+ uuid="C544CD2B-F02D-4886-9901-71C523DB8DC5"
+ >
+ <desc>
+ Guest process execution flags.
+ <note>The values are passed to the Guest Additions, so its not possible
+ to change (move) or reuse values.here. See GUEST_PROC_CREATE_FLAG_XXX
+ in GuestControlSvc.h.</note>
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No flag set.</desc>
+ </const>
+ <const name="WaitForProcessStartOnly" value="1">
+ <desc>Only use the specified timeout value to wait for starting the guest process - the guest
+ process itself then uses an infinite timeout.</desc>
+ </const>
+ <const name="IgnoreOrphanedProcesses" value="2">
+ <desc>Do not report an error when executed processes are still alive when VBoxService or the guest OS is shutting down.</desc>
+ </const>
+ <const name="Hidden" value="4">
+ <desc>Do not show the started process according to the guest OS guidelines.</desc>
+ </const>
+ <const name="Profile" value="8">
+ <desc>Utilize the user's profile data when exeuting a process. Only available for Windows guests at the moment.</desc>
+ </const>
+ <const name="WaitForStdOut" value="16">
+ <desc>The guest process waits until all data from stdout is read out.</desc>
+ </const>
+ <const name="WaitForStdErr" value="32">
+ <desc>The guest process waits until all data from stderr is read out.</desc>
+ </const>
+ <const name="ExpandArguments" value="64">
+ <desc>Expands environment variables in process arguments.
+ <note>
+ This is not yet implemented and is currently silently ignored.
+ We will document the protocolVersion number for this feature once it
+ appears, so don't use it till then.
+ </note>
+ </desc>
+ </const>
+ <const name="UnquotedArguments" value="128">
+ <desc>Work around for Windows and OS/2 applications not following normal
+ argument quoting and escaping rules. The arguments are passed to the
+ application without any extra quoting, just a single space between each.
+ <note>Present since VirtualBox 4.3.28 and 5.0 beta 3.</note>
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="ProcessPriority"
+ uuid="ee8cac50-e232-49fe-806b-d1214d9c2e49"
+ >
+ <desc>
+ Process priorities.
+ </desc>
+
+ <const name="Invalid" value="0">
+ <desc>Invalid priority, do not use.</desc>
+ </const>
+ <const name="Default" value="1">
+ <desc>Default process priority determined by the OS.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="SymlinkType"
+ uuid="37794668-f8f1-4714-98a5-6f8fa2ed0118"
+ >
+ <desc>
+ Symbolic link types. This is significant when creating links on the
+ Windows platform, ignored elsewhere.
+ </desc>
+
+ <const name="Unknown" value="0">
+ <desc>It is not known what is being targeted.</desc>
+ </const>
+ <const name="Directory" value="1">
+ <desc>The link targets a directory.</desc>
+ </const>
+ <const name="File" value="2">
+ <desc>The link targets a file (or whatever else except directories).</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="SymlinkReadFlag"
+ uuid="b7fe2b9d-790e-4b25-8adf-1ca33026931f"
+ >
+ <desc>
+ Symbolic link reading flags.
+ </desc>
+
+ <const name="None" value="0">
+ <desc>No flags set.</desc>
+ </const>
+ <const name="NoSymlinks" value="1">
+ <desc>Don't allow symbolic links as part of the path.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="ProcessStatus"
+ uuid="4d52368f-5b48-4bfe-b486-acf89139b52f"
+ >
+ <desc>
+ Process execution statuses.
+ </desc>
+
+ <const name="Undefined" value="0">
+ <desc>Process is in an undefined state.</desc>
+ </const>
+ <const name="Starting" value="10">
+ <desc>Process is being started.</desc>
+ </const>
+ <const name="Started" value="100">
+ <desc>Process has been started.</desc>
+ </const>
+ <const name="Paused" value="110">
+ <desc>Process has been paused.</desc>
+ </const>
+ <const name="Terminating" value="480">
+ <desc>Process is being terminated.</desc>
+ </const>
+ <const name="TerminatedNormally" value="500">
+ <desc>Process terminated normally.</desc>
+ </const>
+ <const name="TerminatedSignal" value="510">
+ <desc>Process terminated via signal.</desc>
+ </const>
+ <const name="TerminatedAbnormally" value="511">
+ <desc>Process terminated abnormally.</desc>
+ </const>
+ <const name="TimedOutKilled" value="512">
+ <desc>Process timed out and was killed.</desc>
+ </const>
+ <const name="TimedOutAbnormally" value="513">
+ <desc>Process timed out and was not killed successfully.</desc>
+ </const>
+ <const name="Down" value="600">
+ <desc>Service/OS is stopping, process was killed.</desc>
+ </const>
+ <const name="Error" value="800">
+ <desc>Something went wrong.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="ProcessInputStatus"
+ uuid="a4a0ef9c-29cc-4805-9803-c8215ae9da6c"
+ >
+ <desc>
+ Process input statuses.
+ </desc>
+
+ <const name="Undefined" value="0">
+ <desc>Undefined state.</desc>
+ </const>
+ <const name="Broken" value="1">
+ <desc>Input pipe is broken.</desc>
+ </const>
+ <const name="Available" value="10">
+ <desc>Input pipe became available for writing.</desc>
+ </const>
+ <const name="Written" value="50">
+ <desc>Data has been successfully written.</desc>
+ </const>
+ <const name="Overflow" value="100">
+ <desc>Too much input data supplied, data overflow.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="PathStyle"
+ uuid="97303a5b-42e8-0a55-d16f-d2a92c295261"
+ >
+ <desc>
+ The path style of a system.
+ (Values matches the RTPATH_STR_F_STYLE_XXX defines in iprt/path.h!)
+ </desc>
+ <const name="DOS" value="1">
+ <desc>DOS-style paths with forward and backward slashes, drive
+ letters and UNC. Known from DOS, OS/2 and Windows.</desc>
+ </const>
+ <const name="UNIX" value="2">
+ <desc>UNIX-style paths with forward slashes only.</desc>
+ </const>
+ <const name="Unknown" value="8">
+ <desc>
+ The path style is not known, most likely because the Guest Additions
+ aren't active yet.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="FileAccessMode"
+ uuid="231a578f-47fb-ea30-3b3e-8489558227f0"
+ >
+ <desc>
+ File open access mode for use with <link to="IGuestSession::fileOpen"/>
+ and <link to="IGuestSession::fileOpenEx"/>.
+ </desc>
+ <const name="ReadOnly" value="1">
+ <desc>Open the file only with read access.</desc>
+ </const>
+ <const name="WriteOnly" value="2">
+ <desc>Open the file only with write access.</desc>
+ </const>
+ <const name="ReadWrite" value="3">
+ <desc>Open the file with both read and write access.</desc>
+ </const>
+ <const name="AppendOnly" value="4">
+ <desc>Open the file for appending only, no read or seek access.
+ <note>Not yet implemented.</note>
+ </desc>
+ </const>
+ <const name="AppendRead" value="5">
+ <desc>Open the file for appending and read. Writes always goes to the
+ end of the file while reads are done at the current or specified file
+ position.
+ <note>Not yet implemented.</note>
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="FileOpenAction"
+ uuid="12bc97e2-4fc6-a8b4-4f84-0cbf4ab970d2"
+ >
+ <desc>
+ What action <link to="IGuestSession::fileOpen"/> and <link to="IGuestSession::fileOpenEx"/>
+ should take whether the file being opened exists or not.
+ </desc>
+ <const name="OpenExisting" value="1">
+ <desc>Opens an existing file, fails if no file exists. (Was "oe".)</desc>
+ </const>
+ <const name="OpenOrCreate" value="2">
+ <desc>Opens an existing file, creates a new one if no file exists. (Was "oc".)</desc>
+ </const>
+ <const name="CreateNew" value="3">
+ <desc>Creates a new file is no file exists, fails if there is a file there already. (Was "ce".)</desc>
+ </const>
+ <const name="CreateOrReplace" value="4">
+ <desc>
+ Creates a new file, replace any existing file. (Was "ca".)
+ <note>
+ Currently undefined whether we will inherit mode and ACLs from the
+ existing file or replace them.
+ </note>
+ </desc>
+ </const>
+ <const name="OpenExistingTruncated" value="5">
+ <desc>Opens and truncate an existing file, fails if no file exists. (Was "ot".)</desc>
+ </const>
+ <const name="AppendOrCreate" value="99">
+ <desc>Opens an existing file and places the file pointer at the end of
+ the file, creates the file if it does not exist. This action implies
+ write access. (Was "oa".)
+ <note>
+ <!-- @todo r=bird: See iprt/file.h, RTFILE_O_APPEND - not an action/disposition!
+ Moving the file pointer to the end, is almost fine, but implying 'write' access
+ isn't. That is something that is exclusively reserved for the opening mode. -->
+ Deprecated. Only here for historical reasons. Do not use!
+ </note>
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="FileSharingMode"
+ uuid="f87dfe58-425b-c5ba-7d6d-22adeea25de1"
+ >
+ <desc>
+ File sharing mode for <link to="IGuestSession::fileOpenEx"/>.
+ </desc>
+ <const name="Read" value="1">
+ <desc>Only share read access to the file.</desc>
+ </const>
+ <const name="Write" value="2">
+ <desc>Only share write access to the file.</desc>
+ </const>
+ <const name="ReadWrite" value="3">
+ <desc>Share both read and write access to the file, but deny deletion.</desc>
+ </const>
+ <const name="Delete" value="4">
+ <desc>Only share delete access, denying read and write.</desc>
+ </const>
+ <const name="ReadDelete" value="5">
+ <desc>Share read and delete access to the file, denying writing.</desc>
+ </const>
+ <const name="WriteDelete" value="6">
+ <desc>Share write and delete access to the file, denying reading.</desc>
+ </const>
+ <const name="All" value="7">
+ <desc>Share all access, i.e. read, write and delete, to the file.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="FileOpenExFlag"
+ uuid="4671abd4-f70c-42aa-8542-6c169cb87a5c"
+ >
+ <desc>
+ Open flags for <link to="IGuestSession::fileOpenEx"/>.
+ </desc>
+ <const name="None" value="0">
+ <desc>No flag set.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="FileStatus"
+ uuid="8c86468b-b97b-4080-8914-e29f5b0abd2c"
+ >
+ <desc>
+ File statuses.
+ </desc>
+
+ <const name="Undefined" value="0">
+ <desc>File is in an undefined state.</desc>
+ </const>
+ <const name="Opening" value="10">
+ <desc>Guest file is opening.</desc>
+ </const>
+ <const name="Open" value="100">
+ <desc>Guest file has been successfully opened.</desc>
+ </const>
+ <const name="Closing" value="150">
+ <desc>Guest file closing.</desc>
+ </const>
+ <const name="Closed" value="200">
+ <desc>Guest file has been closed.</desc>
+ </const>
+ <const name="Down" value="600">
+ <desc>Service/OS is stopping, guest file was closed.</desc>
+ </const>
+ <const name="Error" value="800">
+ <desc>Something went wrong.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="FsObjType"
+ uuid="34a0d1aa-491e-e209-e150-84964d6cee5f"
+ >
+ <desc>
+ File system object (file) types.
+ </desc>
+ <const name="Unknown" value="1">
+ <desc>Used either if the object has type that is not in this enum, or
+ if the type has not yet been determined or set.</desc>
+ </const>
+ <const name="Fifo" value="2">
+ <desc>FIFO or named pipe, depending on the platform/terminology.</desc>
+ </const>
+ <const name="DevChar" value="3">
+ <desc>Character device.</desc>
+ </const>
+ <const name="Directory" value="4">
+ <desc>Directory.</desc>
+ </const>
+ <const name="DevBlock" value="5">
+ <desc>Block device.</desc>
+ </const>
+ <const name="File" value="6">
+ <desc>Regular file.</desc>
+ </const>
+ <const name="Symlink" value="7">
+ <desc>Symbolic link.</desc>
+ </const>
+ <const name="Socket" value="8">
+ <desc>Socket.</desc>
+ </const>
+ <const name="WhiteOut" value="9">
+ <desc>A white-out file. Found in union mounts where it is used for
+ hiding files after deletion, I think. </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="DnDAction"
+ uuid="17609e74-778e-4d0e-8827-35f5230f287b"
+ >
+ <desc>
+ Possible actions of a drag'n drop operation.
+ </desc>
+
+ <const name="Ignore" value="0">
+ <desc>Do nothing.</desc>
+ </const>
+
+ <const name="Copy" value="1">
+ <desc>Copy the item to the target.</desc>
+ </const>
+
+ <const name="Move" value="2">
+ <desc>Move the item to the target.</desc>
+ </const>
+
+ <const name="Link" value="3">
+ <desc>Link the item from within the target.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="DirectoryOpenFlag"
+ uuid="5138837a-8fd2-4194-a1b0-08f7bc3949d0"
+ >
+ <desc>
+ Directory open flags.
+ </desc>
+ <const name="None" value="0">
+ <desc>No flag set.</desc>
+ </const>
+ <const name="NoSymlinks" value="1">
+ <desc>Don't allow symbolic links as part of the path.</desc>
+ </const>
+<!-- r=bird: need a "NoFollowSymlinks" value here. IPRT probably needs that too. -->
+ </enum>
+
+ <interface
+ name="IDnDBase" extends="$unknown"
+ uuid="00727A73-000A-4C4A-006D-E7D300351186"
+ wsmap="managed"
+ reservedMethods="1" reservedAttributes="2"
+ >
+ <desc>Base abstract interface for drag'n drop.</desc>
+
+ <attribute name="formats" type="wstring" safearray="yes" readonly="yes">
+ <desc>Returns all supported drag'n drop formats.</desc>
+ </attribute>
+
+ <method name="isFormatSupported" >
+ <desc>
+ Checks if a specific drag'n drop MIME / Content-type format is supported.
+ </desc>
+ <param name="format" type="wstring" dir="in">
+ <desc>Format to check for.</desc>
+ </param>
+ <param name="supported" type="boolean" dir="return">
+ <desc>Returns @c true if the specified format is supported, @c false if not.</desc>
+ </param>
+ </method>
+
+ <method name="addFormats" >
+ <desc>
+ Adds MIME / Content-type formats to the supported formats.
+ </desc>
+ <param name="formats" type="wstring" safearray="yes" dir="in">
+ <desc>Collection of formats to add.</desc>
+ </param>
+ </method>
+
+ <method name="removeFormats" >
+ <desc>
+ Removes MIME / Content-type formats from the supported formats.
+ </desc>
+ <param name="formats" type="wstring" safearray="yes" dir="in">
+ <desc>Collection of formats to remove.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IDnDSource" extends="IDnDBase"
+ uuid="d23a9ca3-42da-c94b-8aec-21968e08355d"
+ wsmap="managed"
+ reservedMethods="1" reservedAttributes="2"
+ >
+ <desc>Abstract interface for handling drag'n drop sources.</desc>
+
+ <method name="dragIsPending">
+ <desc>
+ Ask the source if there is any drag and drop operation pending.
+ If no drag and drop operation is pending currently, DnDAction_Ignore is returned.
+
+ <result name="VBOX_E_VM_ERROR">
+ VMM device is not available.
+ </result>
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>The screen ID where the drag and drop event occurred.</desc>
+ </param>
+ <param name="formats" type="wstring" dir="out" safearray="yes">
+ <desc>On return the supported mime types.</desc>
+ </param>
+ <param name="allowedActions" type="DnDAction" dir="out" safearray="yes">
+ <desc>On return the actions which are allowed.</desc>
+ </param>
+ <param name="defaultAction" type="DnDAction" dir="return">
+ <desc>On return the default action to use.</desc>
+ </param>
+ </method>
+
+ <method name="drop">
+ <desc>
+ Informs the source that a drop event occurred for a pending
+ drag and drop operation.
+
+ <result name="VBOX_E_VM_ERROR">
+ VMM device is not available.
+ </result>
+ </desc>
+
+ <param name="format" type="wstring" dir="in">
+ <desc>The mime type the data must be in.</desc>
+ </param>
+ <param name="action" type="DnDAction" dir="in">
+ <desc>The action to use.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="receiveData">
+ <desc>
+ Receive the data of a previously drag and drop event from the source.
+
+ <result name="VBOX_E_VM_ERROR">
+ VMM device is not available.
+ </result>
+
+ </desc>
+
+ <param name="data" type="octet" safearray="yes" dir="return">
+ <desc>The actual data.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IGuestDnDSource" extends="IDnDSource"
+ uuid="dedfb5d9-4c1b-edf7-fdf3-c1be6827dc28"
+ wsmap="managed"
+ >
+ <desc>
+ Implementation of the <link to="IDnDSource" /> object
+ for source drag'n drop operations on the guest.
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IDnDTarget" extends="IDnDBase"
+ uuid="ff5befc3-4ba3-7903-2aa4-43988ba11554"
+ wsmap="managed"
+ reservedMethods="1" reservedAttributes="2"
+ >
+ <desc>Abstract interface for handling drag'n drop targets.</desc>
+
+ <method name="enter">
+ <desc>
+ Informs the target about a drag and drop enter event.
+
+ <result name="VBOX_E_VM_ERROR">
+ VMM device is not available.
+ </result>
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>The screen ID where the drag and drop event occurred.</desc>
+ </param>
+ <param name="y" type="unsigned long" dir="in">
+ <desc>Y-position of the event.</desc>
+ </param>
+ <param name="x" type="unsigned long" dir="in">
+ <desc>X-position of the event.</desc>
+ </param>
+ <param name="defaultAction" type="DnDAction" dir="in">
+ <desc>The default action to use.</desc>
+ </param>
+ <param name="allowedActions" type="DnDAction" dir="in" safearray="yes">
+ <desc>The actions which are allowed.</desc>
+ </param>
+ <param name="formats" type="wstring" dir="in" safearray="yes">
+ <desc>The supported MIME types.</desc>
+ </param>
+ <param name="resultAction" type="DnDAction" dir="return">
+ <desc>The resulting action of this event.</desc>
+ </param>
+ </method>
+
+ <method name="move">
+ <desc>
+ Informs the target about a drag and drop move event.
+
+ <result name="VBOX_E_VM_ERROR">
+ VMM device is not available.
+ </result>
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>The screen ID where the drag and drop event occurred.</desc>
+ </param>
+ <param name="x" type="unsigned long" dir="in">
+ <desc>X-position of the event.</desc>
+ </param>
+ <param name="y" type="unsigned long" dir="in">
+ <desc>Y-position of the event.</desc>
+ </param>
+ <param name="defaultAction" type="DnDAction" dir="in">
+ <desc>The default action to use.</desc>
+ </param>
+ <param name="allowedActions" type="DnDAction" dir="in" safearray="yes">
+ <desc>The actions which are allowed.</desc>
+ </param>
+ <param name="formats" type="wstring" dir="in" safearray="yes">
+ <desc>The supported MIME types.</desc>
+ </param>
+ <param name="resultAction" type="DnDAction" dir="return">
+ <desc>The resulting action of this event.</desc>
+ </param>
+ </method>
+
+ <method name="leave">
+ <desc>
+ Informs the target about a drag and drop leave event.
+
+ <result name="VBOX_E_VM_ERROR">
+ VMM device is not available.
+ </result>
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>The screen ID where the drag and drop event occurred.</desc>
+ </param>
+ </method>
+
+ <method name="drop">
+ <desc>
+ Informs the target about a drop event.
+
+ <result name="VBOX_E_VM_ERROR">
+ VMM device is not available.
+ </result>
+
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>The screen ID where the Drag and Drop event occurred.</desc>
+ </param>
+ <param name="x" type="unsigned long" dir="in">
+ <desc>X-position of the event.</desc>
+ </param>
+ <param name="y" type="unsigned long" dir="in">
+ <desc>Y-position of the event.</desc>
+ </param>
+ <param name="defaultAction" type="DnDAction" dir="in">
+ <desc>The default action to use.</desc>
+ </param>
+ <param name="allowedActions" type="DnDAction" dir="in" safearray="yes">
+ <desc>The actions which are allowed.</desc>
+ </param>
+ <param name="formats" type="wstring" dir="in" safearray="yes">
+ <desc>The supported MIME types.</desc>
+ </param>
+ <param name="format" type="wstring" dir="out">
+ <desc>The resulting format of this event.</desc>
+ </param>
+ <param name="resultAction" type="DnDAction" dir="return">
+ <desc>The resulting action of this event.</desc>
+ </param>
+ </method>
+
+ <method name="sendData">
+ <desc>
+ Initiates sending data to the target.
+
+ <result name="VBOX_E_VM_ERROR">
+ VMM device is not available.
+ </result>
+
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>The screen ID where the drag and drop event occurred.</desc>
+ </param>
+ <param name="format" type="wstring" dir="in">
+ <desc>The MIME type the data is in.</desc>
+ </param>
+ <param name="data" type="octet" dir="in" safearray="yes">
+ <desc>The actual data.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="cancel">
+ <desc>
+ Requests cancelling the current operation. The target can veto
+ the request in case the operation is not cancelable at the moment.
+
+ <result name="VBOX_E_VM_ERROR">
+ VMM device is not available.
+ </result>
+
+ </desc>
+
+ <param name="veto" type="boolean" dir="return">
+ <desc>Whether the target has vetoed cancelling the operation.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IGuestDnDTarget" extends="IDnDTarget"
+ uuid="50ce4b51-0ff7-46b7-a138-3c6e5ac946b4"
+ wsmap="managed"
+ >
+ <desc>
+ Implementation of the <link to="IDnDTarget" /> object
+ for target drag'n drop operations on the guest.
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IGuestSession" extends="$unknown"
+ uuid="234f0627-866d-48c2-91a5-4c9d50f04928"
+ wsmap="managed"
+ reservedMethods="8" reservedAttributes="12"
+ >
+ <desc>
+ A guest session represents one impersonated user account in the guest, so
+ every operation will use the same credentials specified when creating
+ the session object via <link to="IGuest::createSession"/>.
+
+ There can be a maximum of 32 sessions at once per VM, whereas session 0
+ always is reserved for the root session (the root session is part of that
+ limit).
+
+ This root session is controlling all other guest sessions and also is
+ responsible for actions which require system level privileges.
+
+ Each guest session keeps track of the guest directories and files that
+ it opened as well as guest processes it has created. To work on guest
+ files or directories a guest session offers methods to open or create
+ such objects (see <link to="IGuestSession::fileOpen"/> or
+ <link to="IGuestSession::directoryOpen"/> for instance). Similarly,
+ there a methods for creating guest processes.
+
+ There can be up to 2048 objects (guest processes, files and directories)
+ a time per guest session. Exceeding the limit will result in an error (see
+ the corresponding functions for more).
+
+ When done with either of these objects, including the guest session itself,
+ use the appropriate close() method to let the object do its cleanup work.
+
+ Closing a session via <link to="IGuestSession::close" /> will try to close
+ all the mentioned objects above unless these objects are still used by
+ a client.
+
+ A set of environment variables changes is associated with each session
+ (<link to="IGuestSession::environmentChanges"/>). These are applied to
+ the base environment of the impersonated guest user when creating a new
+ guest process. For additional flexibility the <link to="IGuestSession::processCreate"/>
+ and <link to="IGuestSession::processCreateEx"/> methods allows you to
+ specify individual environment changes for each process you create.
+ With newer guest addition versions, the base environment is also made
+ available via <link to="IGuestSession::environmentBase"/>. (One reason
+ for why we record changes to a base environment instead of working
+ directly on an environment block is that we need to be compatible
+ with older Guest Additions. Another reason is that this way it is always
+ possible to undo all the changes you've scheduled.)
+ </desc>
+
+ <attribute name="user" type="wstring" readonly="yes">
+ <desc>Returns the user name used by this session to impersonate
+ users in the guest.
+ </desc>
+ </attribute>
+ <attribute name="domain" type="wstring" readonly="yes">
+ <desc>Returns the domain name used by this session to impersonate
+ users in the guest.
+ </desc>
+ </attribute>
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>Returns the session's friendly name.</desc>
+ </attribute>
+ <attribute name="id" type="unsigned long" readonly="yes">
+ <desc>Returns the internal session ID.</desc>
+ </attribute>
+ <attribute name="timeout" type="unsigned long">
+ <desc>
+<!-- r=bird: Using 'Returns' for writable attributes is misleading. -->
+ Returns the session timeout (in ms).
+ <result name="E_NOTIMPL">
+ This attribute is not implemented yet.
+ </result>
+ </desc>
+ </attribute>
+ <attribute name="protocolVersion" type="unsigned long" readonly="yes">
+ <desc>Returns the protocol version which is used by this session to
+ communicate with the guest.</desc>
+ </attribute>
+ <attribute name="status" type="GuestSessionStatus" readonly="yes">
+ <desc>Returns the current session status.</desc>
+ </attribute>
+ <attribute name="environmentChanges" type="wstring" safearray="yes">
+ <desc>
+ The set of scheduled environment changes to the base environment of the
+ session. They are in putenv format, i.e. "VAR=VALUE" for setting and
+ "VAR" for unsetting. One entry per variable (change). The changes are
+ applied when creating new guest processes.
+
+ This is writable, so to undo all the scheduled changes, assign it an
+ empty array.
+ </desc>
+ </attribute>
+ <attribute name="environmentBase" type="wstring" safearray="yes" readonly="yes">
+ <desc>
+ The base environment of the session. They are on the "VAR=VALUE" form,
+ one array entry per variable.
+ <!-- @todo/TODO/FIXME: This doesn't end up in the PDF.
+ <result name="VBOX_E_NOT_SUPPORTED">If the Guest Additions does not
+ support the session base environment feature. Support for this was
+ introduced with protocol version XXX.</result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">If the Guest Additions has
+ yet to report the session base environment.</result> -->
+
+ Access fails with VBOX_E_NOT_SUPPORTED if the Guest Additions does not
+ support the session base environment feature. Support for this was
+ introduced with protocol version XXXX.
+
+ Access fails with VBOX_E_INVALID_OBJECT_STATE if the Guest Additions
+ has yet to report the session base environment.
+ </desc>
+ </attribute>
+ <attribute name="processes" type="IGuestProcess" readonly="yes" safearray="yes">
+ <desc>
+ Returns all current guest processes.
+ </desc>
+ </attribute>
+ <attribute name="pathStyle" type="PathStyle" readonly="yes">
+ <desc>
+ The style of paths used by the guest. Handy for giving the right kind
+ of path specifications to <link to="IGuestSession::fileOpen"/> and similar methods.
+ </desc>
+ </attribute>
+ <attribute name="currentDirectory" type="wstring">
+ <desc>
+ Gets or sets the current directory of the session. Guest path style.
+ <result name="E_NOTIMPL">
+ This attribute is not implemented yet.
+ </result>
+ </desc>
+ </attribute>
+ <attribute name="userHome" type="wstring" readonly="yes">
+ <desc>
+ Returns the user's home / profile directory. Guest path style.
+ </desc>
+ </attribute>
+ <attribute name="userDocuments" type="wstring" readonly="yes">
+ <desc>
+ Returns the user's documents directory. Guest path style.
+ </desc>
+ </attribute>
+ <attribute name="directories" type="IGuestDirectory" readonly="yes" safearray="yes">
+ <desc>
+ Returns all currently opened guest directories.
+ </desc>
+ </attribute>
+ <attribute name="files" type="IGuestFile" readonly="yes" safearray="yes">
+ <desc>
+ Returns all currently opened guest files.
+ </desc>
+ </attribute>
+ <attribute name="eventSource" type="IEventSource" readonly="yes">
+ <desc>
+ Event source for guest session events.
+ </desc>
+ </attribute>
+
+ <method name="close">
+ <desc>
+ Closes this session. All opened guest directories, files and
+ processes which are not referenced by clients anymore will be
+ closed. Guest processes which fall into this category and still
+ are running in the guest will be terminated automatically.
+ </desc>
+ </method>
+
+ <method name="copyFromGuest">
+ <desc>
+ Copies directories and/or files from guest to the host.
+
+ This function requires several parallel arrays to be supplied, one
+ set for each source.
+ </desc>
+ <param name="sources" type="wstring" dir="in" safearray="yes">
+ <desc>Paths to directories and/or files on the guest side that should be
+ copied to the host. If the path ends with a path delimiter, only
+ the directory's content is being copied. Guest path style.</desc>
+ </param>
+ <param name="filters" type="wstring" dir="in" safearray="yes">
+ <desc>Array of source filters. This uses the
+ DOS/NT style wildcard characters '?' and '*'.</desc>
+ </param>
+ <param name="flags" type="wstring" dir="in" safearray="yes">
+ <desc>Array of comma-separated list of source flags.
+
+ The following flags are available:
+
+ <dl>
+ <dt><tt>CopyIntoExisting</tt></dt>
+ <dd>Allow copying into an existing destination directory.</dd>
+ <dt><tt>NoReplace</tt></dt>
+ <dd>Do not replace any existing destination files on
+ the destination.</dd>
+ <dt><tt>FollowLinks</tt></dt>
+ <dd>Follows (and handles) (symbolic) links.</dd>
+ <dt><tt>Update</tt></dt>
+ <dd>Only copy when the source file is newer than the destination
+ file or when the destination file is missing.</dd>
+ </dl>
+ </desc>
+ </param>
+ <param name="destination" type="wstring" dir="in">
+ <desc>Where to put the sources on the host. Host path style.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation to completion.</desc>
+ </param>
+ </method>
+
+ <method name="copyToGuest">
+ <desc>
+ Copies directories and/or files from host to the guest.
+
+ This function requires several parallel arrays to be supplied, one
+ set for each source.
+ </desc>
+ <param name="sources" type="wstring" dir="in" safearray="yes">
+ <desc>Paths to directories and/or files on the host side that should be
+ copied to the guest. If the path ends with a path delimiter, only
+ the directory's content is being copied. Host path style.</desc>
+ </param>
+ <param name="filters" type="wstring" dir="in" safearray="yes">
+ <desc>Array of source filters. This uses the
+ DOS/NT style wildcard characters '?' and '*'.</desc>
+ </param>
+ <param name="flags" type="wstring" dir="in" safearray="yes">
+ <desc>Array of comma-separated list of source flags.
+
+ The following flags are available:
+
+ <dl>
+ <dt><tt>CopyIntoExisting</tt></dt>
+ <dd>Allow copying into an existing destination directory.</dd>
+ <dt><tt>NoReplace</tt></dt>
+ <dd>Do not replace any existing destination files on
+ the destination.</dd>
+ <dt><tt>FollowLinks</tt></dt>
+ <dd>Follows (and handles) (symbolic) links.</dd>
+ <dt><tt>Update</tt></dt>
+ <dd>Only copy when the source file is newer than the destination
+ file or when the destination file is missing.</dd>
+ </dl>
+
+ </desc>
+ </param>
+ <param name="destination" type="wstring" dir="in">
+ <desc>Where to put the sources on the guest. Guest path style.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation to completion.</desc>
+ </param>
+ </method>
+
+ <!-- Directory related methods. -->
+
+ <method name="directoryCopy">
+ <desc>
+ Recursively copies a directory from one guest location to another.
+
+ <result name="E_NOTIMPL">
+ Not yet implemented.
+ </result>
+ </desc>
+ <param name="source" type="wstring" dir="in">
+ <desc>The path to the directory to copy (in the guest). Guest path style.</desc>
+ </param>
+ <param name="destination" type="wstring" dir="in">
+ <desc>The path to the target directory (in the guest). Unless the
+ <link to="DirectoryCopyFlag_CopyIntoExisting"/> flag is given, the
+ directory shall not already exist. Guest path style.</desc>
+ </param>
+ <param name="flags" type="DirectoryCopyFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="DirectoryCopyFlag"/> values.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation to completion.</desc>
+ </param>
+ </method>
+
+ <method name="directoryCopyFromGuest">
+ <desc>
+ Recursively copies a directory from the guest to the host.
+ </desc>
+ <param name="source" type="wstring" dir="in">
+ <desc>Path to the directory on the guest side that should be copied to
+ the host. Guest path style.</desc>
+ </param>
+ <param name="destination" type="wstring" dir="in">
+ <desc>Where to put the directory on the host. Unless the
+ <link to="DirectoryCopyFlag_CopyIntoExisting"/> flag is given, the
+ directory shall not already exist. Host path style.</desc>
+ </param>
+ <param name="flags" type="DirectoryCopyFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="DirectoryCopyFlag"/> values.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation to completion.</desc>
+ </param>
+ </method>
+
+ <method name="directoryCopyToGuest">
+ <desc>
+ Recursively copies a directory from the host to the guest.
+ </desc>
+ <param name="source" type="wstring" dir="in">
+ <desc>Path to the directory on the host side that should be copied to
+ the guest. Host path style.</desc>
+ </param>
+ <param name="destination" type="wstring" dir="in">
+ <desc>Where to put the file in the guest. Unless the
+ <link to="DirectoryCopyFlag_CopyIntoExisting"/> flag is given, the
+ directory shall not already exist. Guest style path.</desc>
+ </param>
+ <param name="flags" type="DirectoryCopyFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="DirectoryCopyFlag"/> values.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation to completion.</desc>
+ </param>
+ </method>
+
+ <method name="directoryCreate">
+ <desc>
+ Creates a directory in the guest.
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Error while creating the directory.
+ </result>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Path to the directory directory to be created. Guest path style.</desc>
+ </param>
+ <param name="mode" type="unsigned long" dir="in">
+ <desc>
+ The UNIX-style access mode mask to create the directory with.
+ Whether/how all three access groups and associated access rights are
+ realized is guest OS dependent. The API does the best it can on each
+ OS.
+ </desc>
+ </param>
+ <param name="flags" type="DirectoryCreateFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="DirectoryCreateFlag"/> flags.</desc>
+ </param>
+ </method>
+
+ <method name="directoryCreateTemp">
+ <desc>
+ Creates a temporary directory in the guest.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ The operation is not possible as requested on this particular
+ guest type.
+ </result>
+ <result name="E_INVALIDARG">
+ Invalid argument. This includes an incorrectly formatted template,
+ or a non-absolute path.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ The temporary directory could not be created. Possible reasons
+ include a non-existing path or an insecure path when the secure
+ option was requested.
+ </result>
+ </desc>
+ <param name="templateName" type="wstring" dir="in">
+ <desc>Template for the name of the directory to create. This must
+ contain at least one 'X' character. The first group of consecutive
+ 'X' characters in the template will be replaced by a random
+ alphanumeric string to produce a unique name.</desc>
+ </param>
+ <param name="mode" type="unsigned long" dir="in">
+ <desc>
+ The UNIX-style access mode mask to create the directory with.
+ Whether/how all three access groups and associated access rights are
+ realized is guest OS dependent. The API does the best it can on each
+ OS.
+
+ This parameter is ignored if the @a secure parameter is set to @c true.
+ <note>It is strongly recommended to use 0700.</note>
+ </desc>
+ </param>
+ <param name="path" type="wstring" dir="in">
+ <desc>The path to the directory in which the temporary directory should
+ be created. Guest path style.</desc>
+ </param>
+ <param name="secure" type="boolean" dir="in">
+ <desc>
+ Whether to fail if the directory can not be securely created.
+ Currently this means that another unprivileged user cannot
+ manipulate the path specified or remove the temporary directory
+ after it has been created. Also causes the mode specified to be
+ ignored. May not be supported on all guest types.
+ </desc>
+ </param>
+ <param name="directory" type="wstring" dir="return">
+ <desc>On success this will contain the full path to the created
+ directory. Guest path style.</desc>
+ </param>
+ </method>
+
+ <method name="directoryExists">
+ <desc>
+ Checks whether a directory exists in the guest or not.
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Error while checking existence of the directory specified.
+ </result>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Path to the directory to check if exists. Guest path style.</desc>
+ </param>
+ <param name="followSymlinks" type="boolean" dir="in">
+ <desc>
+ If @c true, symbolic links in the final component will be followed
+ and the existance of the symlink target made the question for this method.
+ If @c false, a symbolic link in the final component will make the
+ method return @c false (because a symlink isn't a directory).
+ </desc>
+ </param>
+ <param name="exists" type="boolean" dir="return">
+ <desc>Returns @c true if the directory exists, @c false if not, or is not a directory.</desc>
+ </param>
+ </method>
+
+ <method name="directoryOpen">
+ <desc>
+ Opens a directory in the guest and creates a <link to="IGuestDirectory"/>
+ object that can be used for further operations.
+
+ <note>This method follows symbolic links by default at the moment, this
+ may change in the future.</note>
+
+ <note>One idiosyncrasy of the current implementation is that you will NOT
+ get VBOX_E_OBJECT_NOT_FOUND returned here if the directory doesn't exist.
+ Instead the read function will fail with VBOX_E_IPRT_ERROR. This will
+ be fixed soon.</note>
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Directory to open was not found.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ Error while opening the directory.
+ </result>
+ <result name="VBOX_E_MAXIMUM_REACHED">
+ The maximum of concurrent guest directories has been reached.
+ </result>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Path to the directory to open. Guest path style.</desc>
+ </param>
+ <param name="filter" type="wstring" dir="in">
+ <desc>Optional directory listing filter to apply. This uses the DOS/NT
+ style wildcard characters '?' and '*'.</desc>
+ </param>
+ <param name="flags" type="DirectoryOpenFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="DirectoryOpenFlag"/> flags.</desc>
+ </param>
+ <param name="directory" type="IGuestDirectory" dir="return">
+ <desc><link to="IGuestDirectory"/> object containing the opened directory.</desc>
+ </param>
+ </method>
+
+ <method name="directoryRemove">
+ <desc>
+ Removes a guest directory if empty.
+
+ <note>Symbolic links in the final component will not be followed,
+ instead an not-a-directory error is reported.</note>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Path to the directory that should be removed. Guest path style.</desc>
+ </param>
+ </method>
+
+ <method name="directoryRemoveRecursive">
+ <desc>
+ Removes a guest directory recursively.
+
+<!-- Add this back when the warning can be removed:
+ Unless <link to="DirectoryRemoveRecFlag_ContentAndDir"/> or
+ <link to="DirectoryRemoveRecFlag_ContentOnly"/> is given, only the
+ directory structure is removed. Which means it will fail if there are
+ directories which are not empty in the directory tree @a path points to.
+-->
+
+ <note> WARNING!! THE FLAGS ARE NOT CURRENTLY IMPLEMENTED. THE IMPLEMENTATION
+ WORKS AS IF FLAGS WAS SET TO <link to="DirectoryRemoveRecFlag_ContentAndDir"/>.
+ </note>
+
+ <note>If the final path component is a symbolic link, this method will
+ fail as it can only be applied to directories.</note>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Path of the directory that is to be removed recursively. Guest
+ path style.</desc>
+ </param>
+ <param name="flags" type="DirectoryRemoveRecFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="DirectoryRemoveRecFlag"/> flags.
+ <note>WARNING! SPECIFYING <link to="DirectoryRemoveRecFlag_ContentAndDir"/> IS
+ MANDATORY AT THE MOMENT!!</note>
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion. This is not implemented
+ yet and therefore this method call will block until deletion is completed.</desc>
+ </param>
+ </method>
+
+ <!-- Environment related methods. -->
+
+ <method name="environmentScheduleSet">
+ <desc>
+ Schedules setting an environment variable when creating the next guest
+ process. This affects the <link to="IGuestSession::environmentChanges"/>
+ attribute.
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the environment variable to set. This cannot be empty
+ nor can it contain any equal signs.</desc>
+ </param>
+ <param name="value" type="wstring" dir="in">
+ <desc>Value to set the session environment variable to.</desc>
+ </param>
+ </method>
+
+ <method name="environmentScheduleUnset">
+ <desc>
+ Schedules unsetting (removing) an environment variable when creating
+ the next guest process. This affects the
+ <link to="IGuestSession::environmentChanges"/> attribute.
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the environment variable to unset. This cannot be empty
+ nor can it contain any equal signs.</desc>
+ </param>
+ </method>
+
+ <method name="environmentGetBaseVariable">
+ <desc>
+ Gets an environment variable from the session's base environment
+ (<link to="IGuestSession::environmentBase"/>).
+
+ <result name="VBOX_E_NOT_SUPPORTED">If the Guest Additions does not
+ support the session base environment feature. Support for this was
+ introduced with protocol version XXXX.</result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">If the Guest Additions has
+ yet to report the session base environment.</result>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the environment variable to get.This cannot be empty
+ nor can it contain any equal signs.</desc>
+ </param>
+ <param name="value" type="wstring" dir="return">
+ <desc>
+ The value of the variable. Empty if not found. To deal with
+ variables that may have empty values, use
+ <link to="IGuestSession::environmentDoesBaseVariableExist"/>.
+ </desc>
+ </param>
+ </method>
+
+ <method name="environmentDoesBaseVariableExist">
+ <desc>
+ Checks if the given environment variable exists in the session's base
+ environment (<link to="IGuestSession::environmentBase"/>).
+
+ <result name="VBOX_E_NOT_SUPPORTED">If the Guest Additions does not
+ support the session base environment feature. Support for this was
+ introduced with protocol version XXXX.</result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">If the Guest Additions has
+ yet to report the session base environment.</result>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the environment variable to look for. This cannot be
+ empty nor can it contain any equal signs.</desc>
+ </param>
+ <param name="exists" type="boolean" dir="return">
+ <desc>TRUE if the variable exists, FALSE if not.</desc>
+ </param>
+ </method>
+
+ <!-- File related methods. -->
+
+ <method name="fileCopy">
+ <desc>
+ Copies a file from one guest location to another.
+
+ <note>Will overwrite the destination file unless
+ <link to="FileCopyFlag_NoReplace"/> is specified.</note>
+
+ <result name="E_NOTIMPL">
+ Not yet implemented.
+ </result>
+ </desc>
+ <param name="source" type="wstring" dir="in">
+ <desc>The path to the file to copy (in the guest). Guest path style.</desc>
+ </param>
+ <param name="destination" type="wstring" dir="in">
+ <desc>The path to the target file (in the guest). This cannot be a
+ directory. Guest path style.</desc>
+ </param>
+ <param name="flags" type="FileCopyFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="FileCopyFlag"/> values.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation to completion.</desc>
+ </param>
+ </method>
+
+ <method name="fileCopyFromGuest">
+ <desc>
+ Copies a file from the guest to the host.
+
+ <note>Will overwrite the destination file unless
+ <link to="FileCopyFlag_NoReplace"/> is specified.</note>
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Error starting the copy operation.
+ </result>
+ </desc>
+ <param name="source" type="wstring" dir="in">
+ <desc>Path to the file on the guest side that should be copied to the
+ host. Guest path style.</desc>
+ </param>
+ <param name="destination" type="wstring" dir="in">
+ <desc>Where to put the file on the host (file, not directory). Host
+ path style.</desc>
+ </param>
+ <param name="flags" type="FileCopyFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="FileCopyFlag"/> values.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation to completion.</desc>
+ </param>
+ </method>
+
+ <method name="fileCopyToGuest">
+ <desc>
+ Copies a file from the host to the guest.
+
+ <note>Will overwrite the destination file unless
+ <link to="FileCopyFlag_NoReplace"/> is specified.</note>
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Error starting the copy operation.
+ </result>
+ </desc>
+ <param name="source" type="wstring" dir="in">
+ <desc>Path to the file on the host side that should be copied to the
+ guest. Host path style.</desc>
+ </param>
+ <param name="destination" type="wstring" dir="in">
+ <desc>Where to put the file in the guest (file, not directory). Guest
+ style path.</desc>
+ </param>
+ <param name="flags" type="FileCopyFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="FileCopyFlag"/> values.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation to completion.</desc>
+ </param>
+ </method>
+
+ <method name="fileCreateTemp">
+ <desc>
+ Creates a temporary file in the guest.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ The operation is not possible as requested on this particular
+ guest OS.
+ </result>
+ <result name="E_INVALIDARG">
+ Invalid argument. This includes an incorrectly formatted template,
+ or a non-absolute path.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ The temporary file could not be created. Possible reasons include
+ a non-existing path or an insecure path when the secure
+ option was requested.
+ </result>
+ </desc>
+ <param name="templateName" type="wstring" dir="in">
+ <desc>Template for the name of the file to create. This must contain
+ at least one 'X' character. The first group of consecutive 'X'
+ characters in the template will be replaced by a random
+ alphanumeric string to produce a unique name.
+ </desc>
+ </param>
+ <param name="mode" type="unsigned long" dir="in">
+ <desc>
+ The UNIX-style access mode mask to create the file with.
+ Whether/how all three access groups and associated access rights are
+ realized is guest OS dependent. The API does the best it can on each
+ OS.
+
+ This parameter is ignore if the @a secure parameter is set to @c true.
+ <note>It is strongly recommended to use 0600.</note>
+ </desc>
+ </param>
+ <param name="path" type="wstring" dir="in">
+ <desc>The path to the directory in which the temporary file should be
+ created.</desc>
+ </param>
+ <param name="secure" type="boolean" dir="in">
+ <desc>Whether to fail if the file can not be securely created.
+ Currently this means that another unprivileged user cannot
+ manipulate the path specified or remove the temporary file after
+ it has been created. Also causes the mode specified to be ignored.
+ May not be supported on all guest types.</desc>
+ </param>
+ <param name="file" type="IGuestFile" dir="return">
+ <desc>On success this will contain an open file object for the new
+ temporary file.
+ </desc>
+ </param>
+ </method>
+
+ <method name="fileExists">
+ <desc>
+ Checks whether a regular file exists in the guest or not.
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Error while checking existence of the file specified.
+ </result>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Path to the alleged regular file. Guest path style.</desc>
+ </param>
+ <param name="followSymlinks" type="boolean" dir="in">
+ <desc>
+ If @c true, symbolic links in the final component will be followed
+ and the existance of the symlink target made the question for this method.
+ If @c false, a symbolic link in the final component will make the
+ method return @c false (because a symlink isn't a regular file).
+ </desc>
+ </param>
+ <param name="exists" type="boolean" dir="return">
+ <desc>Returns @c true if the file exists, @c false if not. @c false is
+ also return if this @a path does not point to a file object.</desc>
+ </param>
+ </method>
+
+ <method name="fileOpen">
+ <desc>
+ Opens a file and creates a <link to="IGuestFile"/> object that
+ can be used for further operations.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ File to open was not found.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ Error while opening the file.
+ </result>
+ <result name="VBOX_E_MAXIMUM_REACHED">
+ The maximum of concurrent guest files has been reached.
+ </result>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Path to file to open. Guest path style.</desc>
+ </param>
+ <param name="accessMode" type="FileAccessMode" dir="in">
+ <desc>The file access mode (read, write and/or append).
+ See <link to="FileAccessMode"/> for details.</desc>
+ </param>
+ <param name="openAction" type="FileOpenAction" dir="in">
+ <desc>What action to take depending on whether the file exists or not.
+ See <link to="FileOpenAction"/> for details.</desc>
+ </param>
+ <param name="creationMode" type="unsigned long" dir="in">
+ <desc>
+ The UNIX-style access mode mask to create the file with if @a openAction
+ requested the file to be created (otherwise ignored). Whether/how all
+ three access groups and associated access rights are realized is guest
+ OS dependent. The API does the best it can on each OS.
+ </desc>
+ </param>
+ <param name="file" type="IGuestFile" dir="return">
+ <desc><link to="IGuestFile"/> object representing the opened file.</desc>
+ </param>
+ </method>
+
+ <method name="fileOpenEx">
+ <desc>
+ Opens a file and creates a <link to="IGuestFile"/> object that
+ can be used for further operations, extended version.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ File to open was not found.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ Error while opening the file.
+ </result>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Path to file to open. Guest path style.</desc>
+ </param>
+ <param name="accessMode" type="FileAccessMode" dir="in">
+ <desc>The file access mode (read, write and/or append).
+ See <link to="FileAccessMode"/> for details.</desc>
+ </param>
+ <param name="openAction" type="FileOpenAction" dir="in">
+ <desc>What action to take depending on whether the file exists or not.
+ See <link to="FileOpenAction"/> for details.</desc>
+ </param>
+ <param name="sharingMode" type="FileSharingMode" dir="in">
+ <desc>The file sharing mode in the guest. This parameter is currently
+ ignore for all guest OSes. It will in the future be implemented for
+ Windows, OS/2 and maybe Solaris guests only, the others will ignore it.
+ Use <link to="FileSharingMode_All"/>.
+ </desc>
+ </param>
+ <param name="creationMode" type="unsigned long" dir="in">
+ <desc>
+ The UNIX-style access mode mask to create the file with if @a openAction
+ requested the file to be created (otherwise ignored). Whether/how all
+ three access groups and associated access rights are realized is guest
+ OS dependent. The API does the best it can on each OS.
+ </desc>
+ </param>
+ <param name="flags" type="FileOpenExFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="FileOpenExFlag"/> values. </desc>
+ </param>
+ <param name="file" type="IGuestFile" dir="return">
+ <desc><link to="IGuestFile"/> object representing the opened file.</desc>
+ </param>
+ </method>
+
+ <method name="fileQuerySize">
+ <desc>
+ Queries the size of a regular file in the guest.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ File to was not found.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ Error querying file size.
+ </result>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Path to the file which size is requested. Guest path style.</desc>
+ </param>
+ <param name="followSymlinks" type="boolean" dir="in">
+ <desc>
+ It @c true, symbolic links in the final path component will be
+ followed to their target, and the size of the target is returned.
+ If @c false, symbolic links in the final path component will make
+ the method call fail (symblink is not a regular file).
+ </desc>
+ </param>
+ <param name="size" type="long long" dir="return">
+ <desc>Queried file size.</desc>
+ </param>
+ </method>
+
+ <!-- File System Object Level -->
+
+ <method name="fsObjExists">
+ <desc>
+ Checks whether a file system object (file, directory, etc) exists in
+ the guest or not.
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Error while checking existence of the file specified.
+ </result>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Path to the file system object to check the existance of. Guest
+ path style.</desc>
+ </param>
+ <param name="followSymlinks" type="boolean" dir="in">
+ <desc>
+ If @c true, symbolic links in the final component will be followed
+ and the method will instead check if the target exists.
+ If @c false, symbolic links in the final component will satisfy the
+ method and it will return @c true in @a exists.
+ </desc>
+ </param>
+ <param name="exists" type="boolean" dir="return">
+ <desc>Returns @c true if the file exists, @c false if not.</desc>
+ </param>
+ </method>
+
+ <method name="fsObjQueryInfo">
+ <desc>
+ Queries information about a file system object (file, directory, etc)
+ in the guest.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ The file system object was not found.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ Error while querying information.
+ </result>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Path to the file system object to gather information about.
+ Guest path style.</desc>
+ </param>
+ <param name="followSymlinks" type="boolean" dir="in">
+ <desc>
+ Information about symbolic links is returned if @c false. Otherwise,
+ symbolic links are followed and the returned information concerns
+ itself with the symlink target if @c true.
+ </desc>
+ </param>
+ <param name="info" type="IGuestFsObjInfo" dir="return">
+ <desc><link to="IGuestFsObjInfo"/> object containing the information.</desc>
+ </param>
+ </method>
+
+ <method name="fsObjRemove">
+ <desc>
+ Removes a file system object (file, symlink, etc) in the guest. Will
+ not work on directories, use <link to="IGuestSession::directoryRemove"/>
+ to remove directories.
+
+ <note>This method will remove symbolic links in the final path
+ component, not follow them.</note>
+
+ <result name="E_NOTIMPL">
+ The method has not been implemented yet.
+ </result>
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ The file system object was not found.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ For most other errors. We know this is unhelpful, will fix shortly...
+ </result>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Path to the file system object to remove. Guest style path.</desc>
+ </param>
+ </method>
+
+ <method name="fsObjRemoveArray">
+ <desc>
+ Removes multiple file system objects (files, directories, symlinks, etc)
+ in the guest. Use with caution.
+
+ <note>This method is not implemented yet and will return E_NOTIMPL.</note>
+
+ <note>This method will remove symbolic links in the final path
+ component, not follow them.</note>
+
+ <result name="E_NOTIMPL">
+ The method has not been implemented yet.
+ </result>
+ </desc>
+ <param name="path" type="wstring" dir="in" safearray="yes">
+ <desc>Array of paths to the file system objects to remove. Guest style path.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation to completion.</desc>
+ </param>
+ </method>
+
+ <method name="fsObjRename">
+ <desc>
+ Renames a file system object (file, directory, symlink, etc) in the
+ guest.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ The file system object was not found.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ For most other errors. We know this is unhelpful, will fix shortly...
+ </result>
+ </desc>
+ <param name="oldPath" type="wstring" dir="in">
+ <desc>The current path to the object. Guest path style.</desc>
+ </param>
+ <param name="newPath" type="wstring" dir="in">
+ <desc>The new path to the object. Guest path style.</desc>
+ </param>
+ <param name="flags" type="FsObjRenameFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="FsObjRenameFlag"/> values.</desc>
+ </param>
+ </method>
+
+ <method name="fsObjMove">
+ <desc>
+ Moves a file system object (file, directory, symlink, etc) from one
+ guest location to another.
+
+ This differs from <link to="IGuestSession::fsObjRename"/> in that it
+ can move accross file system boundraries. In that case it will
+ perform a copy and then delete the original. For directories, this
+ can take a while and is subject to races.
+
+ <result name="E_NOTIMPL">
+ Not yet implemented.
+ </result>
+ </desc>
+ <param name="source" type="wstring" dir="in">
+ <desc>Path to the file to move. Guest path style.</desc>
+ </param>
+ <param name="destination" type="wstring" dir="in">
+ <desc>Where to move the file to (file, not directory). Guest path
+ style.</desc>
+ </param>
+ <param name="flags" type="FsObjMoveFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="FsObjMoveFlag"/> values.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation to completion.</desc>
+ </param>
+ </method>
+
+ <method name="fsObjMoveArray">
+ <desc>
+ Moves file system objects (files, directories, symlinks, etc) from one
+ guest location to another.
+
+ <result name="E_NOTIMPL">
+ Not yet implemented.
+ </result>
+ </desc>
+ <param name="source" type="wstring" dir="in" safearray="yes">
+ <desc>Array of paths to the file system objects to move. Guest style path.</desc>
+ </param>
+ <param name="destination" type="wstring" dir="in">
+ <desc>Where to move the file system objects to (directory). Guest path
+ style.</desc>
+ </param>
+ <param name="flags" type="FsObjMoveFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="FsObjMoveFlag"/> values.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation to completion.</desc>
+ </param>
+ </method>
+
+ <method name="fsObjCopyArray">
+ <desc>
+ Copies file system objects (files, directories, symlinks, etc) from one
+ guest location to another.
+
+ <result name="E_NOTIMPL">
+ Not yet implemented.
+ </result>
+ </desc>
+ <param name="source" type="wstring" dir="in" safearray="yes">
+ <desc>Array of paths to the file system objects to copy. Guest style path.</desc>
+ </param>
+ <param name="destination" type="wstring" dir="in">
+ <desc>Where to copy the file system objects to (directory). Guest path
+ style.</desc>
+ </param>
+ <param name="flags" type="FileCopyFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="FileCopyFlag"/> values.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation to completion.</desc>
+ </param>
+ </method>
+
+ <method name="fsObjSetACL">
+ <desc>
+ Sets the access control list (ACL) of a file system object (file,
+ directory, etc) in the guest.
+
+ <result name="E_NOTIMPL">
+ The method is not implemented yet.
+ </result>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Full path of the file system object which ACL to set.</desc>
+ </param>
+ <param name="followSymlinks" type="boolean" dir="in">
+ <desc>
+ If @c true symbolic links in the final component will be followed,
+ otherwise, if @c false, the method will work directly on a symbolic
+ link in the final component.
+ </desc>
+ </param>
+ <param name="acl" type="wstring" dir="in">
+ <desc>The ACL specification string. To-be-defined.</desc>
+ </param>
+ <param name="mode" type="unsigned long" dir="in">
+ <desc>UNIX-style mode mask to use if @a acl is empty. As mention in
+ <link to="IGuestSession::directoryCreate"/> this is realized on
+ a best effort basis and the exact behavior depends on the Guest OS.
+ </desc>
+ </param>
+ </method>
+
+ <method name="fsQueryFreeSpace">
+ <desc>
+ Returns the free space (in bytes) of a given path.
+
+ <result name="E_NOTIMPL">
+ The method is not implemented yet.
+ </result>
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>Full path to return the free space for.</desc>
+ </param>
+ <param name="freeSpace" type="long long" dir="return">
+ <desc>Free space (in bytes).</desc>
+ </param>
+ </method>
+
+ <method name="fsQueryInfo">
+ <desc>
+ Returns file system information for a given path.
+
+ <result name="E_NOTIMPL">
+ The method is not implemented yet.
+ </result>
+ </desc>
+
+ <param name="path" type="wstring" dir="in">
+ <desc>Full path to return file system information for.</desc>
+ </param>
+ <param name="info" type="IGuestFsInfo" dir="return">
+ <desc>IGuestFsInfo object containing the information.</desc>
+ </param>
+
+ </method>
+
+ <!-- Process methods -->
+
+ <method name="processCreate">
+ <desc>
+ Creates a new process running in the guest. The new process will be
+ started asynchronously, meaning on return of this function it is not
+ be guaranteed that the guest process is in a started state. To wait for
+ successful startup, use the <link to="IProcess::waitFor"/> call.
+
+ <note>
+ Starting at VirtualBox 4.2 guest process execution by is default limited
+ to serve up to 255 guest processes at a time. If all 255 guest processes
+ are active and running, creating a new guest process will result in an
+ error.
+
+ If ProcessCreateFlag_WaitForStdOut and/or ProcessCreateFlag_WaitForStdErr
+ are set, the guest process will not enter the terminated state until
+ all data from the specified streams have been read read.
+ </note>
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Error creating guest process.
+ </result>
+
+ <result name="VBOX_E_MAXIMUM_REACHED">
+ The maximum of concurrent guest processes has been reached.
+ </result>
+ </desc>
+ <param name="executable" type="wstring" dir="in">
+ <desc>
+ Full path to the file to execute in the guest. The file has to
+ exists in the guest VM with executable right to the session user in
+ order to succeed. If empty/null, the first entry in the
+ @a arguments array will be used instead (i.e. argv[0]).
+ </desc>
+ </param>
+ <param name="arguments" type="wstring" dir="in" safearray="yes">
+ <desc>Array of arguments passed to the new process.
+ <note>
+ Starting with VirtualBox 5.0 this array starts with argument 0
+ instead of argument 1 as in previous versions. Whether the zeroth
+ argument can be passed to the guest depends on the VBoxService
+ version running there. If you depend on this, check that the
+ <link to="IGuestSession::protocolVersion"/> is 3 or higher.
+ </note>
+ </desc>
+ </param>
+ <param name="environmentChanges" type="wstring" dir="in" safearray="yes">
+ <desc>
+ Set of environment changes to complement
+ <link to="IGuestSession::environmentChanges"/>. Takes precedence
+ over the session ones. The changes are in putenv format, i.e.
+ "VAR=VALUE" for setting and "VAR" for unsetting.
+
+ The changes are applied to the base environment of the impersonated
+ guest user (<link to="IGuestSession::environmentBase"/>) when
+ creating the process. (This is done on the guest side of things in
+ order to be compatible with older Guest Additions. That is one of
+ the motivations for not passing in the whole environment here.)
+ </desc>
+ </param>
+ <param name="flags" type="ProcessCreateFlag" dir="in" safearray="yes">
+ <desc>
+ Process creation flags;
+ see <link to="ProcessCreateFlag"/> for more information.
+ </desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) for limiting the guest process' running time.
+ Pass 0 for an infinite timeout. On timeout the guest process will be
+ killed and its status will be put to an appropriate value. See
+ <link to="ProcessStatus"/> for more information.
+ </desc>
+ </param>
+ <param name="guestProcess" type="IGuestProcess" dir="return">
+ <desc>Guest process object of the newly created process.</desc>
+ </param>
+ </method>
+
+ <method name="processCreateEx">
+ <desc>
+ Creates a new process running in the guest with the extended options
+ for setting the process priority and affinity.
+
+ See <link to="IGuestSession::processCreate"/> for more information.
+ </desc>
+ <param name="executable" type="wstring" dir="in">
+ <desc>
+ Full path to the file to execute in the guest. The file has to
+ exists in the guest VM with executable right to the session user in
+ order to succeed. If empty/null, the first entry in the
+ @a arguments array will be used instead (i.e. argv[0]).
+ </desc>
+ </param>
+ <param name="arguments" type="wstring" dir="in" safearray="yes">
+ <desc>Array of arguments passed to the new process.
+ <note>
+ Starting with VirtualBox 5.0 this array starts with argument 0
+ instead of argument 1 as in previous versions. Whether the zeroth
+ argument can be passed to the guest depends on the VBoxService
+ version running there. If you depend on this, check that the
+ <link to="IGuestSession::protocolVersion"/> is 3 or higher.
+ </note>
+ </desc>
+ </param>
+ <param name="environmentChanges" type="wstring" dir="in" safearray="yes">
+ <desc>
+ Set of environment changes to complement
+ <link to="IGuestSession::environmentChanges"/>. Takes precedence
+ over the session ones. The changes are in putenv format, i.e.
+ "VAR=VALUE" for setting and "VAR" for unsetting.
+
+ The changes are applied to the base environment of the impersonated
+ guest user (<link to="IGuestSession::environmentBase"/>) when
+ creating the process. (This is done on the guest side of things in
+ order to be compatible with older Guest Additions. That is one of
+ the motivations for not passing in the whole environment here.)
+ </desc>
+ </param>
+ <param name="flags" type="ProcessCreateFlag" dir="in" safearray="yes">
+ <desc>
+ Process creation flags, see <link to="ProcessCreateFlag"/> for
+ detailed description of available flags.
+ </desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) for limiting the guest process' running time.
+ Pass 0 for an infinite timeout. On timeout the guest process will be
+ killed and its status will be put to an appropriate value. See
+ <link to="ProcessStatus"/> for more information.
+ </desc>
+ </param>
+ <param name="priority" type="ProcessPriority" dir="in">
+ <desc>
+ Process priority to use for execution, see <link to="ProcessPriority"/>
+ for available priority levels.
+ <note>This is silently ignored if not supported by Guest Additions.</note>
+ </desc>
+ </param>
+ <param name="affinity" type="long" dir="in" safearray="yes">
+ <desc>
+ Processor affinity to set for the new process. This is a list of
+ guest CPU numbers the process is allowed to run on.
+ <note>
+ This is silently ignored if the guest does not support setting the
+ affinity of processes, or if the Guest Additions does not implemet
+ this feature.
+ </note>
+ </desc>
+ </param>
+ <param name="guestProcess" type="IGuestProcess" dir="return">
+ <desc>Guest process object of the newly created process.</desc>
+ </param>
+ </method>
+
+ <method name="processGet">
+ <desc>
+ Gets a certain guest process by its process ID (PID).
+ </desc>
+ <param name="pid" type="unsigned long" dir="in">
+ <desc>Process ID (PID) to get guest process for.</desc>
+ </param>
+ <param name="guestProcess" type="IGuestProcess" dir="return">
+ <desc>Guest process of specified process ID (PID).</desc>
+ </param>
+ </method>
+
+ <!-- Symbolic link methods -->
+
+ <method name="symlinkCreate">
+ <desc>
+ Creates a symbolic link in the guest.
+
+ <result name="E_NOTIMPL">
+ The method is not implemented yet.
+ </result>
+ </desc>
+ <param name="symlink" type="wstring" dir="in">
+ <desc>Path to the symbolic link that should be created. Guest path
+ style.</desc>
+ </param>
+ <param name="target" type="wstring" dir="in">
+ <desc>
+ The path to the symbolic link target. If not an absolute, this will
+ be relative to the @a symlink location at access time. Guest path
+ style.
+ </desc>
+ </param>
+ <param name="type" type="SymlinkType" dir="in">
+ <desc>
+ The symbolic link type (mainly for Windows). See <link to="SymlinkType"/>
+ for more information.
+ </desc>
+ </param>
+ </method>
+
+ <method name="symlinkExists">
+ <desc>
+ Checks whether a symbolic link exists in the guest.
+
+ <result name="E_NOTIMPL">
+ The method is not implemented yet.
+ </result>
+ </desc>
+ <param name="symlink" type="wstring" dir="in">
+ <desc>Path to the alleged symbolic link. Guest path style.</desc>
+ </param>
+ <param name="exists" type="boolean" dir="return">
+ <desc>
+ Returns @c true if the symbolic link exists. Returns @c false if it
+ does not exist, if the file system object identified by the path is
+ not a symbolic link, or if the object type is inaccessible to the
+ user, or if the @a symlink argument is empty.
+ </desc>
+ </param>
+ </method>
+
+ <method name="symlinkRead">
+ <desc>
+ Reads the target value of a symbolic link in the guest.
+
+ <result name="E_NOTIMPL">
+ The method is not implemented yet.
+ </result>
+ </desc>
+ <param name="symlink" type="wstring" dir="in">
+ <desc>Path to the symbolic link to read.</desc>
+ </param>
+ <param name="flags" type="SymlinkReadFlag" dir="in" safearray="yes">
+ <desc>Zero or more <link to="SymlinkReadFlag"/> values.</desc>
+ </param>
+ <param name="target" type="wstring" dir="return">
+ <desc>Target value of the symbolic link. Guest path style.</desc>
+ </param>
+ </method>
+
+ <!-- Session wait methods -->
+
+ <method name="waitFor">
+ <desc>
+ Waits for one or more events to happen.
+ </desc>
+ <param name="waitFor" type="unsigned long" dir="in">
+ <desc>
+ Specifies what to wait for;
+ see <link to="GuestSessionWaitForFlag"/> for more information.
+ </desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) to wait for the operation to complete.
+ Pass 0 for an infinite timeout.
+ </desc>
+ </param>
+ <param name="reason" type="GuestSessionWaitResult" dir="return">
+ <desc>
+ The overall wait result;
+ see <link to="GuestSessionWaitResult"/> for more information.
+ </desc>
+ </param>
+ </method>
+
+ <method name="waitForArray">
+ <desc>
+ Waits for one or more events to happen.
+ Scriptable version of <link to="#waitFor" />.
+ </desc>
+ <param name="waitFor" type="GuestSessionWaitForFlag" dir="in" safearray="yes">
+ <desc>
+ Specifies what to wait for;
+ see <link to="GuestSessionWaitForFlag"/> for more information.
+ </desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) to wait for the operation to complete.
+ Pass 0 for an infinite timeout.
+ </desc>
+ </param>
+ <param name="reason" type="GuestSessionWaitResult" dir="return">
+ <desc>
+ The overall wait result;
+ see <link to="GuestSessionWaitResult"/> for more information.
+ </desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IProcess" extends="$unknown"
+ uuid="bc68370c-8a02-45f3-a07d-a67aa72756aa"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ Abstract parent interface for processes handled by VirtualBox.
+ </desc>
+
+ <attribute name="arguments" type="wstring" readonly="yes" safearray="yes">
+ <desc>
+ The arguments this process is using for execution.
+ </desc>
+ </attribute>
+ <attribute name="environment" type="wstring" readonly="yes" safearray="yes">
+ <desc>
+ The initial process environment. Not yet implemented.
+ </desc>
+ </attribute>
+ <attribute name="eventSource" type="IEventSource" readonly="yes">
+ <desc>
+ Event source for process events.
+ </desc>
+ </attribute>
+ <attribute name="executablePath" type="wstring" readonly="yes">
+ <desc>Full path of the actual executable image.</desc>
+ </attribute>
+ <attribute name="exitCode" type="long" readonly="yes">
+ <desc>
+ The exit code. Only available when the process has been
+ terminated normally.
+ </desc>
+ </attribute>
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>
+ The friendly name of this process.
+ </desc>
+ </attribute>
+ <attribute name="PID" type="unsigned long" readonly="yes">
+ <desc>
+ The process ID (PID).
+ </desc>
+ </attribute>
+ <attribute name="status" type="ProcessStatus" readonly="yes">
+ <desc>
+ The current process status; see <link to="ProcessStatus"/>
+ for more information.
+ </desc>
+ </attribute>
+
+ <method name="waitFor">
+ <desc>
+ Waits for one or more events to happen.
+ </desc>
+ <param name="waitFor" type="unsigned long" dir="in">
+ <desc>
+ Specifies what to wait for;
+ see <link to="ProcessWaitForFlag"/> for more information.
+ </desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) to wait for the operation to complete.
+ Pass 0 for an infinite timeout.
+ </desc>
+ </param>
+ <param name="reason" type="ProcessWaitResult" dir="return">
+ <desc>
+ The overall wait result;
+ see <link to="ProcessWaitResult"/> for more information.
+ </desc>
+ </param>
+ </method>
+
+ <method name="waitForArray">
+ <desc>
+ Waits for one or more events to happen.
+ Scriptable version of <link to="#waitFor" />.
+ </desc>
+ <param name="waitFor" type="ProcessWaitForFlag" dir="in" safearray="yes">
+ <desc>
+ Specifies what to wait for;
+ see <link to="ProcessWaitForFlag"/> for more information.
+ </desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) to wait for the operation to complete.
+ Pass 0 for an infinite timeout.
+ </desc>
+ </param>
+ <param name="reason" type="ProcessWaitResult" dir="return">
+ <desc>
+ The overall wait result;
+ see <link to="ProcessWaitResult"/> for more information.
+ </desc>
+ </param>
+ </method>
+
+ <method name="read">
+ <desc>
+ Reads data from a running process.
+ </desc>
+ <param name="handle" type="unsigned long" dir="in">
+ <desc>Handle to read from. Usually 0 is stdin.</desc>
+ </param>
+ <param name="toRead" type="unsigned long" dir="in">
+ <desc>Number of bytes to read.</desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) to wait for the operation to complete.
+ Pass 0 for an infinite timeout.
+ </desc>
+ </param>
+ <param name="data" type="octet" dir="return" safearray="yes">
+ <desc>Array of data read.</desc>
+ </param>
+ </method>
+
+ <method name="write">
+ <desc>
+ Writes data to a running process.
+ </desc>
+ <param name="handle" type="unsigned long" dir="in">
+ <desc>Handle to write to. Usually 0 is stdin, 1 is stdout and 2 is stderr.</desc>
+ </param>
+ <param name="flags" type="unsigned long" dir="in">
+ <desc>
+ A combination of <link to="ProcessInputFlag"/> flags.
+ </desc>
+ </param>
+ <param name="data" type="octet" dir="in" safearray="yes">
+ <desc>
+ Array of bytes to write. The size of the array also specifies
+ how much to write.
+ </desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) to wait for the operation to complete.
+ Pass 0 for an infinite timeout.
+ </desc>
+ </param>
+ <param name="written" type="unsigned long" dir="return">
+ <desc>How many bytes were written.</desc>
+ </param>
+ </method>
+
+ <method name="writeArray">
+ <desc>
+ Writes data to a running process.
+ Scriptable version of <link to="#write" />.
+ </desc>
+ <param name="handle" type="unsigned long" dir="in">
+ <desc>Handle to write to. Usually 0 is stdin, 1 is stdout and 2 is stderr.</desc>
+ </param>
+ <param name="flags" type="ProcessInputFlag" dir="in" safearray="yes">
+ <desc>
+ A combination of <link to="ProcessInputFlag"/> flags.
+ </desc>
+ </param>
+ <param name="data" type="octet" dir="in" safearray="yes">
+ <desc>
+ Array of bytes to write. The size of the array also specifies
+ how much to write.
+ </desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) to wait for the operation to complete.
+ Pass 0 for an infinite timeout.
+ </desc>
+ </param>
+ <param name="written" type="unsigned long" dir="return">
+ <desc>How may bytes were written.</desc>
+ </param>
+ </method>
+
+ <method name="terminate">
+ <desc>
+ Terminates (kills) a running process.
+ <note>It can take up to 30 seconds to get a guest process killed. In
+ case a guest process could not be killed an appropriate error is
+ returned.</note>
+ </desc>
+ </method>
+ </interface>
+
+ <interface
+ name="IGuestProcess" extends="IProcess"
+ uuid="35cf4b3f-4453-4f3e-c9b8-5686939c80b6"
+ wsmap="managed"
+ >
+ <desc>
+ Implementation of the <link to="IProcess" /> object
+ for processes the host has started in the guest.
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IDirectory" extends="$unknown"
+ uuid="758d7eac-e4b1-486a-8f2e-747ae346c3e9"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ Abstract parent interface for directories handled by VirtualBox.
+ </desc>
+
+ <attribute name="directoryName" type="wstring" readonly="yes">
+ <desc>The path specified when opening the directory.</desc>
+ </attribute>
+
+ <attribute name="filter" type="wstring" readonly="yes">
+ <desc>Directory listing filter to (specified when opening the directory).</desc>
+ </attribute>
+
+ <method name="close">
+ <desc>
+ Closes this directory. After closing operations like reading the next
+ directory entry will not be possible anymore.
+ </desc>
+ </method>
+
+ <method name="read">
+ <desc>
+ Reads the next directory entry of this directory.
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ No more directory entries to read.
+ </result>
+ </desc>
+ <param name="objInfo" type="IFsObjInfo" dir="return">
+ <desc>Object information of the current directory entry read. Also see
+ <link to="IFsObjInfo"/>.</desc>
+ </param>
+ </method>
+
+ <!-- Would be useful to add queryInfo() and setACL() here later, but at
+ present IPRT isn't doing a race free job with the former and doesn't
+ have the latter. So, let it be for now. -->
+
+ </interface>
+
+ <interface
+ name="IGuestDirectory" extends="IDirectory"
+ uuid="cc830458-4974-a19c-4dc6-cc98c2269626"
+ wsmap="managed"
+ >
+ <desc>
+ Implementation of the <link to="IDirectory" /> object
+ for directories in the guest.
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IFile" extends="$unknown"
+ uuid="59a235ac-2f1a-4d6c-81fc-e3fa843f49ae"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ Abstract parent interface for files handled by VirtualBox.
+ </desc>
+
+ <attribute name="eventSource" type="IEventSource" readonly="yes">
+ <desc>
+ Event source for file events.
+ </desc>
+ </attribute>
+ <attribute name="id" type="unsigned long" readonly="yes">
+ <desc>
+ The ID VirtualBox internally assigned to the open file.
+ </desc>
+ </attribute>
+ <attribute name="initialSize" type="long long" readonly="yes">
+ <desc>
+ The initial size in bytes when opened.
+ </desc>
+ </attribute>
+ <attribute name="offset" type="long long" readonly="yes">
+ <desc>
+ The current file position.
+
+ The file current position always applies to the <link to="IFile::read"/>
+ method, which updates it upon return. Same goes for the <link to="IFile::write"/>
+ method except when <link to="IFile::accessMode"/> is <link to="FileAccessMode_AppendOnly"/>
+ or <link to="FileAccessMode_AppendRead"/>, where it will always write
+ to the end of the file and will leave this attribute unchanged.
+
+ The <link to="IFile::seek"/> is used to change this attribute without
+ transfering any file data like read and write does.
+
+ <note> This will not always be correct with older Guest Additions
+ (version 5.2.30 and earlier, as well as versions 6.0.0 thru 6.0.8)
+ after a calling <link to="IFile::readAt"/> or <link to="IFile::writeAt"/>,
+ or after calling <link to="IFile::write"/> on a file in append mode.
+ The correct file offset can be obtained using <link to="IFile::seek"/>.</note>
+
+ </desc>
+ </attribute>
+ <attribute name="status" type="FileStatus" readonly="yes">
+ <desc>
+ Current file status.
+ </desc>
+ </attribute>
+
+ <!-- The following attributes just remembers the fileOpen parameters for you.
+ Nice for introspection and probably doesn't cost us much. -->
+ <attribute name="filename" type="wstring" readonly="yes">
+ <desc>Full path of the actual file name of this file.
+ <!-- r=bird: The 'actual' file name is too tough, we cannot guarentee
+ that on unix guests. Seeing how IGuestDirectory did things,
+ I'm questioning the 'Full path' part too. Not urgent to check. -->
+ </desc>
+ </attribute>
+ <attribute name="creationMode" type="unsigned long" readonly="yes">
+ <desc>The UNIX-style creation mode specified when opening the file.</desc>
+ </attribute>
+ <attribute name="openAction" type="FileOpenAction" readonly="yes">
+ <desc>The opening action specified when opening the file.</desc>
+ </attribute>
+ <attribute name="accessMode" type="FileAccessMode" readonly="yes">
+ <desc>The file access mode.</desc>
+ </attribute>
+
+ <method name="close">
+ <desc>
+ Closes this file. After closing operations like reading data,
+ writing data or querying information will not be possible anymore.
+ </desc>
+ </method>
+
+ <method name="queryInfo">
+ <desc>
+ Queries information about this file.
+ </desc>
+ <param name="objInfo" type="IFsObjInfo" dir="return">
+ <desc>Object information of this file. Also see
+ <link to="IFsObjInfo"/>.</desc>
+ </param>
+ </method>
+
+ <method name="querySize">
+ <!-- Can also be gotten via seek(Current, 0), but this is easier to us.
+ This is a query method both to match IGuestSession::fileQuerySize to
+ highlight that it is a value that is not cacheable as others may be
+ changing the file concurrently (imagine reading /var/log/messages). -->
+ <desc>
+ Queries the current file size.
+ </desc>
+ <param name="size" type="long long" dir="return">
+ <desc>Queried file size.</desc>
+ </param>
+ </method>
+
+ <method name="read">
+ <desc>
+ Reads data from this file.
+
+ The file current position (<link to="IFile::offset"/>) is updated on success.
+ </desc>
+ <param name="toRead" type="unsigned long" dir="in">
+ <desc>Number of bytes to read.</desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) to wait for the operation to complete.
+ Pass 0 for an infinite timeout.
+ </desc>
+ </param>
+ <param name="data" type="octet" dir="return" safearray="yes">
+ <desc>Array of data read.</desc>
+ </param>
+ </method>
+
+ <method name="readAt">
+ <desc>
+ Reads data from an offset of this file.
+
+ The file current position (<link to="IFile::offset"/>) is updated on success.
+ </desc>
+ <param name="offset" type="long long" dir="in">
+ <desc>Offset in bytes to start reading.</desc>
+ </param>
+ <param name="toRead" type="unsigned long" dir="in">
+ <desc>Number of bytes to read.</desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) to wait for the operation to complete.
+ Pass 0 for an infinite timeout.
+ </desc>
+ </param>
+ <param name="data" type="octet" dir="return" safearray="yes">
+ <desc>Array of data read.</desc>
+ </param>
+ </method>
+
+ <method name="seek">
+ <desc>
+ Changes the current file position of this file.
+
+ The file current position always applies to the <link to="IFile::read"/>
+ method. Same for the <link to="IFile::write"/> method it except when
+ the <link to="IFile::accessMode"/> is <link to="FileAccessMode_AppendOnly"/>
+ or <link to="FileAccessMode_AppendRead"/>.
+ </desc>
+ <param name="offset" type="long long" dir="in">
+ <desc>Offset to seek relative to the position specified by @a whence.</desc>
+ </param>
+ <param name="whence" type="FileSeekOrigin" dir="in">
+ <desc>
+ One of the <link to="FileSeekOrigin"/> seek starting points.
+ </desc>
+ </param>
+ <param name="newOffset" type="long long" dir="return">
+ <desc>The new file offset after the seek operation.</desc>
+ </param>
+ </method>
+
+ <method name="setACL">
+ <desc>
+ Sets the ACL of this file.
+
+ <result name="E_NOTIMPL">
+ The method is not implemented yet.
+ </result>
+ </desc>
+ <param name="acl" type="wstring" dir="in">
+ <desc>The ACL specification string. To-be-defined.</desc>
+ </param>
+ <param name="mode" type="unsigned long" dir="in">
+ <desc>UNIX-style mode mask to use if @a acl is empty. As mention in
+ <link to="IGuestSession::directoryCreate"/> this is realized on
+ a best effort basis and the exact behavior depends on the Guest OS.
+ </desc>
+ </param>
+ </method>
+
+ <method name="setSize">
+ <desc>
+ Changes the file size.
+ </desc>
+ <param name="size" type="long long" dir="in">
+ <desc>The new file size.</desc>
+ </param>
+ </method>
+
+ <method name="write">
+ <desc>
+ Writes bytes to this file.
+
+ The file current position (<link to="IFile::offset"/>) is updated on success.
+ </desc>
+ <param name="data" type="octet" dir="in" safearray="yes">
+ <desc>
+ Array of bytes to write. The size of the array also specifies
+ how much to write.
+ </desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) to wait for the operation to complete.
+ Pass 0 for an infinite timeout.
+ </desc>
+ </param>
+ <param name="written" type="unsigned long" dir="return">
+ <desc>How many bytes were written.</desc>
+ </param>
+ </method>
+
+ <method name="writeAt">
+ <desc>
+ Writes bytes at a certain offset to this file.
+
+ The file current position (<link to="IFile::offset"/>) is updated on success.
+ </desc>
+ <param name="offset" type="long long" dir="in">
+ <desc>Offset in bytes to start writing. If the file was opened with the
+ <link to="IFile::accessMode"/> set to <link to="FileAccessMode_AppendOnly"/>
+ or <link to="FileAccessMode_AppendRead"/>, the offset is ignored and the
+ write always goes to the end of the file.</desc>
+ </param>
+ <param name="data" type="octet" dir="in" safearray="yes">
+ <desc>
+ Array of bytes to write. The size of the array also specifies
+ how much to write.
+ </desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) to wait for the operation to complete.
+ Pass 0 for an infinite timeout.
+ </desc>
+ </param>
+ <param name="written" type="unsigned long" dir="return">
+ <desc>How many bytes were written.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IGuestFile" extends="IFile"
+ uuid="92f21dc0-44de-1653-b717-2ebf0ca9b664"
+ wsmap="managed"
+ >
+ <desc>
+ Implementation of the <link to="IFile" /> object
+ for files in the guest.
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IFsInfo" extends="$unknown"
+ uuid="3db2ab1a-6cf7-42f1-8bf5-e1c0553e0b30"
+ wsmap="managed"
+ reservedAttributes="8"
+ >
+ <desc>
+ Abstract parent interface for VirtualBox file system information.
+ This can be information about a host or guest file system, for example.
+ </desc>
+
+ <attribute name="freeSize" type="long long" readonly="yes">
+ <desc>
+ Remaining free space (in bytes) of the filesystem.
+ </desc>
+ </attribute>
+ <attribute name="totalSize" type="long long" readonly="yes">
+ <desc>
+ Total space (in bytes) of the filesystem.
+ </desc>
+ </attribute>
+ <attribute name="blockSize" type="unsigned long" readonly="yes">
+ <desc>
+ Block size (in bytes) of the filesystem.
+ </desc>
+ </attribute>
+ <attribute name="sectorSize" type="unsigned long" readonly="yes">
+ <desc>
+ Sector size (in bytes) of the filesystem.
+ </desc>
+ </attribute>
+ <attribute name="serialNumber" type="unsigned long" readonly="yes">
+ <desc>
+ Serial number of the filesystem.
+ </desc>
+ </attribute>
+ <attribute name="isRemote" type="boolean" readonly="yes">
+ <desc>
+ @c TRUE if the filesystem is remote, @c FALSE if the filesystem is local.
+ </desc>
+ </attribute>
+ <attribute name="isCaseSensitive" type="boolean" readonly="yes">
+ <desc>
+ @c TRUE if the filesystem is case sensitive, @c FALSE if the filesystem is case insensitive.
+ </desc>
+ </attribute>
+ <attribute name="isReadOnly" type="boolean" readonly="yes">
+ <desc>
+ @c TRUE if the filesystem is mounted read only, @c FALSE if the filesystem is mounted read write.
+ </desc>
+ </attribute>
+ <attribute name="isCompressed" type="boolean" readonly="yes">
+ <desc>
+ @c TRUE if the filesystem is compressed, @c FALSE if it isn't or we don't know.
+ </desc>
+ </attribute>
+ <attribute name="supportsFileCompression" type="boolean" readonly="yes">
+ <desc>
+ @c TRUE if the filesystem compresses of individual files, @c FALSE if it doesn't or we don't know.
+ </desc>
+ </attribute>
+ <attribute name="maxComponent" type="unsigned long" readonly="yes">
+ <desc>
+ The maximum size of a filesystem object name.
+ </desc>
+ </attribute>
+ <attribute name="type" type="wstring" readonly="yes">
+ <desc>
+ Name of the filesystem.
+ </desc>
+ </attribute>
+ <attribute name="label" type="wstring" readonly="yes">
+ <desc>
+ Label of the filesystem.
+ </desc>
+ </attribute>
+ <attribute name="mountPoint" type="wstring" readonly="yes">
+ <desc>
+ Mount point of the filesystem.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IFsObjInfo" extends="$unknown"
+ uuid="081fc833-c6fa-430e-6020-6a505d086387"
+ wsmap="managed"
+ reservedAttributes="8"
+ >
+ <desc>
+ Abstract parent interface for VirtualBox file system object information.
+ This can be information about a file or a directory, for example.
+ </desc>
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>
+ The object's name.
+ </desc>
+ </attribute>
+ <attribute name="type" type="FsObjType" readonly="yes">
+ <desc>
+ The object type. See <link to="FsObjType" /> for more.
+ </desc>
+ </attribute>
+ <attribute name="fileAttributes" type="wstring" readonly="yes">
+ <desc>
+ File attributes. Not implemented yet.
+ </desc>
+ </attribute>
+ <attribute name="objectSize" type="long long" readonly="yes">
+ <desc>
+ The logical size (st_size). For normal files this is the size of the file.
+ For symbolic links, this is the length of the path name contained in the
+ symbolic link. For other objects this fields needs to be specified.
+ </desc>
+ </attribute>
+ <attribute name="allocatedSize" type="long long" readonly="yes">
+ <desc>
+ Disk allocation size (st_blocks * DEV_BSIZE).
+ </desc>
+ </attribute>
+ <!-- Time attributes: -->
+ <attribute name="accessTime" type="long long" readonly="yes">
+ <desc>
+ Time of last access (st_atime).
+ </desc>
+ </attribute>
+ <attribute name="birthTime" type="long long" readonly="yes">
+ <desc>
+ Time of file birth (st_birthtime).
+ </desc>
+ </attribute>
+ <attribute name="changeTime" type="long long" readonly="yes">
+ <desc>
+ Time of last status change (st_ctime).
+ </desc>
+ </attribute>
+ <attribute name="modificationTime" type="long long" readonly="yes">
+ <desc>
+ Time of last data modification (st_mtime).
+ </desc>
+ </attribute>
+ <!-- Ownership attributes: -->
+ <attribute name="UID" type="long" readonly="yes">
+ <desc>
+ The user owning the filesystem object (st_uid). This is -1 if not available.
+ </desc>
+ </attribute>
+ <attribute name="userName" type="wstring" readonly="yes">
+ <desc>
+ The user name.
+ </desc>
+ </attribute>
+ <attribute name="GID" type="long" readonly="yes">
+ <desc>
+ The group the filesystem object is assigned (st_gid). This is -1 if not available.
+ </desc>
+ </attribute>
+ <attribute name="groupName" type="wstring" readonly="yes">
+ <desc>
+ The group name.
+ </desc>
+ </attribute>
+ <!-- More esoteric attributes: -->
+ <attribute name="nodeId" type="long long" readonly="yes">
+ <desc>
+ The unique identifier (within the filesystem) of this filesystem object (st_ino).
+ This is zero if not availalbe.
+ </desc>
+ </attribute>
+ <attribute name="nodeIdDevice" type="unsigned long" readonly="yes">
+ <desc>
+ The device number of the device which this filesystem object resides on (st_dev).
+ </desc>
+ </attribute>
+ <attribute name="hardLinks" type="unsigned long" readonly="yes">
+ <desc>
+ Number of hard links to this filesystem object (st_nlink).
+ </desc>
+ </attribute>
+ <attribute name="deviceNumber" type="unsigned long" readonly="yes">
+ <desc>
+ The device number of a character or block device type object (st_rdev).
+ </desc>
+ </attribute>
+ <attribute name="generationId" type="unsigned long" readonly="yes">
+ <desc>
+ The current generation number (st_gen).
+ </desc>
+ </attribute>
+ <attribute name="userFlags" type="unsigned long" readonly="yes">
+ <desc>
+ User flags (st_flags).
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IGuestFsInfo" extends="IFsInfo"
+ uuid="726eaca9-091e-41b4-bca6-355efe864107"
+ wsmap="managed"
+ >
+ <desc>
+ Represents the guest implementation of the
+ <link to="IFsInfo" /> object.
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IGuestFsObjInfo" extends="IFsObjInfo"
+ uuid="6620db85-44e0-ca69-e9e0-d4907ceccbe5"
+ wsmap="managed"
+ >
+ <desc>
+ Represents the guest implementation of the
+ <link to="IFsObjInfo" /> object.
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IGuest" extends="$unknown"
+ uuid="00892186-A4AF-4627-B21F-FC561CE4473C"
+ wsmap="managed"
+ reservedMethods="8" reservedAttributes="16"
+ >
+ <desc>
+ The IGuest interface represents information about the operating system
+ running inside the virtual machine. Used in
+ <link to="IConsole::guest"/>.
+
+ IGuest provides information about the guest operating system, whether
+ Guest Additions are installed and other OS-specific virtual machine
+ properties.
+ </desc>
+
+ <attribute name="OSTypeId" type="wstring" readonly="yes">
+ <desc>
+ Identifier of the Guest OS type as reported by the Guest
+ Additions.
+ You may use <link to="IVirtualBox::getGuestOSType"/> to obtain
+ an IGuestOSType object representing details about the given
+ Guest OS type.
+ <note>
+ If Guest Additions are not installed, this value will be
+ the same as <link to="IMachine::OSTypeId"/>.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="additionsRunLevel" type="AdditionsRunLevelType" readonly="yes">
+ <desc>
+ Current run level of the installed Guest Additions.
+ </desc>
+ </attribute>
+
+ <attribute name="additionsVersion" type="wstring" readonly="yes">
+ <desc>
+ Version of the installed Guest Additions in the same format as
+ <link to="IVirtualBox::version"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="additionsRevision" type="unsigned long" readonly="yes">
+ <desc>
+ The internal build revision number of the installed Guest Additions.
+
+ See also <link to="IVirtualBox::revision"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="dnDSource" type="IGuestDnDSource" readonly="yes">
+ <desc>
+ Retrieves the drag'n drop source implementation for the guest side, that
+ is, handling and retrieving drag'n drop data from the guest.
+ </desc>
+ </attribute>
+
+ <attribute name="dnDTarget" type="IGuestDnDTarget" readonly="yes">
+ <desc>
+ Retrieves the drag'n drop source implementation for the host side. This
+ will allow the host to handle and initiate a drag'n drop operation to copy
+ data from the host to the guest.
+ </desc>
+ </attribute>
+
+ <attribute name="eventSource" type="IEventSource" readonly="yes">
+ <desc>
+ Event source for guest events.
+ </desc>
+ </attribute>
+
+ <attribute name="facilities" type="IAdditionsFacility" readonly="yes" safearray="yes">
+ <desc>
+ Returns a collection of current known facilities. Only returns facilities where
+ a status is known, e.g. facilities with an unknown status will not be returned.
+ </desc>
+ </attribute>
+
+ <attribute name="sessions" type="IGuestSession" readonly="yes" safearray="yes">
+ <desc>Returns a collection of all opened guest sessions.</desc>
+ </attribute>
+
+ <attribute name="memoryBalloonSize" type="unsigned long">
+ <desc>Guest system memory balloon size in megabytes (transient property).</desc>
+ </attribute>
+
+ <attribute name="statisticsUpdateInterval" type="unsigned long">
+ <desc>Interval to update guest statistics in seconds.</desc>
+ </attribute>
+
+ <method name="internalGetStatistics">
+ <desc>
+ Internal method; do not use as it might change at any time.
+ </desc>
+ <param name="cpuUser" type="unsigned long" dir="out">
+ <desc>Percentage of processor time spent in user mode as seen by the guest.</desc>
+ </param>
+ <param name="cpuKernel" type="unsigned long" dir="out">
+ <desc>Percentage of processor time spent in kernel mode as seen by the guest.</desc>
+ </param>
+ <param name="cpuIdle" type="unsigned long" dir="out">
+ <desc>Percentage of processor time spent idling as seen by the guest.</desc>
+ </param>
+ <param name="memTotal" type="unsigned long" dir="out">
+ <desc>Total amount of physical guest RAM.</desc>
+ </param>
+ <param name="memFree" type="unsigned long" dir="out">
+ <desc>Free amount of physical guest RAM.</desc>
+ </param>
+ <param name="memBalloon" type="unsigned long" dir="out">
+ <desc>Amount of ballooned physical guest RAM.</desc>
+ </param>
+ <param name="memShared" type="unsigned long" dir="out">
+ <desc>Amount of shared physical guest RAM.</desc>
+ </param>
+ <param name="memCache" type="unsigned long" dir="out">
+ <desc>Total amount of guest (disk) cache memory.</desc>
+ </param>
+ <param name="pagedTotal" type="unsigned long" dir="out">
+ <desc>Total amount of space in the page file.</desc>
+ </param>
+ <param name="memAllocTotal" type="unsigned long" dir="out">
+ <desc>Total amount of memory allocated by the hypervisor.</desc>
+ </param>
+ <param name="memFreeTotal" type="unsigned long" dir="out">
+ <desc>Total amount of free memory available in the hypervisor.</desc>
+ </param>
+ <param name="memBalloonTotal" type="unsigned long" dir="out">
+ <desc>Total amount of memory ballooned by the hypervisor.</desc>
+ </param>
+ <param name="memSharedTotal" type="unsigned long" dir="out">
+ <desc>Total amount of shared memory in the hypervisor.</desc>
+ </param>
+ </method>
+
+ <method name="getFacilityStatus">
+ <desc>
+ Get the current status of a Guest Additions facility.
+ </desc>
+ <param name="facility" type="AdditionsFacilityType" dir="in">
+ <desc>Facility to check status for.</desc>
+ </param>
+ <param name="timestamp" type="long long" dir="out">
+ <desc>Timestamp (in ms) of last status update seen by the host.</desc>
+ </param>
+ <param name="status" type="AdditionsFacilityStatus" dir="return">
+ <desc>The current (latest) facility status.</desc>
+ </param>
+ </method>
+
+ <method name="getAdditionsStatus">
+ <desc>
+ Retrieve the current status of a certain Guest Additions run level.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Wrong status level specified.
+ </result>
+
+ </desc>
+ <param name="level" type="AdditionsRunLevelType" dir="in">
+ <desc>Status level to check</desc>
+ </param>
+ <param name="active" type="boolean" dir="return">
+ <desc>Flag whether the status level has been reached or not</desc>
+ </param>
+ </method>
+
+ <method name="setCredentials">
+ <desc>
+ Store login credentials that can be queried by guest operating
+ systems with Additions installed. The credentials are transient
+ to the session and the guest may also choose to erase them. Note
+ that the caller cannot determine whether the guest operating system
+ has queried or made use of the credentials.
+
+ <result name="VBOX_E_VM_ERROR">
+ VMM device is not available.
+ </result>
+
+ </desc>
+ <param name="userName" type="wstring" dir="in">
+ <desc>User name string, can be empty</desc>
+ </param>
+ <param name="password" type="wstring" dir="in">
+ <desc>Password string, can be empty</desc>
+ </param>
+ <param name="domain" type="wstring" dir="in">
+ <desc>Domain name (guest logon scheme specific), can be empty</desc>
+ </param>
+ <param name="allowInteractiveLogon" type="boolean" dir="in">
+ <desc>
+ Flag whether the guest should alternatively allow the user to
+ interactively specify different credentials. This flag might
+ not be supported by all versions of the Additions.
+ </desc>
+ </param>
+ </method>
+
+ <method name="createSession">
+ <desc>
+ Creates a new guest session for controlling the guest. The new session
+ will be started asynchronously, meaning on return of this function it is
+ not guaranteed that the guest session is in a started and/or usable state.
+ To wait for successful startup, use the <link to="IGuestSession::waitFor"/>
+ call.
+
+ A guest session represents one impersonated user account in the guest, so
+ every operation will use the same credentials specified when creating
+ the session object via <link to="IGuest::createSession"/>. Anonymous
+ sessions, that is, sessions without specifying a valid
+ user account in the guest are not allowed reasons of security.
+
+ There can be a maximum of 32 sessions at once per VM. An error will
+ be returned if this has been reached.
+
+ For more information please consult <link to="IGuestSession"/>
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Error creating guest session.
+ </result>
+
+ <result name="VBOX_E_MAXIMUM_REACHED">
+ The maximum of concurrent guest sessions has been reached.
+ </result>
+ </desc>
+ <param name="user" type="wstring" dir="in">
+ <desc>
+ User name this session will be using to control the guest; has to exist
+ and have the appropriate rights to execute programs in the VM. Must not
+ be empty.
+ </desc>
+ </param>
+ <param name="password" type="wstring" dir="in">
+ <desc>
+ Password of the user account to be used. Empty passwords are allowed.
+ </desc>
+ </param>
+ <param name="domain" type="wstring" dir="in">
+ <desc>
+ Domain name of the user account to be used if the guest is part of
+ a domain. Optional. This feature is not implemented yet.
+ </desc>
+ </param>
+ <param name="sessionName" type="wstring" dir="in">
+ <desc>
+ The session's friendly name. Optional, can be empty.
+ </desc>
+ </param>
+ <param name="guestSession" type="IGuestSession" dir="return">
+ <desc>
+ The newly created session object.
+ </desc>
+ </param>
+ </method>
+
+ <method name="findSession">
+ <desc>
+ Finds guest sessions by their friendly name and returns an interface
+ array with all found guest sessions.
+ </desc>
+ <param name="sessionName" type="wstring" dir="in">
+ <desc>
+ The session's friendly name to find. Wildcards like ? and * are allowed.
+ </desc>
+ </param>
+ <param name="sessions" type="IGuestSession" safearray="yes" dir="return">
+ <desc>
+ Array with all guest sessions found matching the name specified.
+ </desc>
+ </param>
+ </method>
+
+ <method name="shutdown">
+ <desc>
+ Shuts down (and optionally halts and/or reboots) the guest.
+ Needs supported Guest Additions installed.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Guest OS is not supported for shutting down, or the
+ already installed Guest Additions are not supported.
+ </result>
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Error while shutting down.
+ </result>
+
+ </desc>
+ <param name="flags" type="GuestShutdownFlag" dir="in" safearray="yes">
+ <desc>
+ <link to="GuestShutdownFlag"/> flags.
+ </desc>
+ </param>
+ </method>
+
+ <method name="updateGuestAdditions">
+ <desc>
+ Automatically updates already installed Guest Additions in a VM.
+
+ At the moment only Windows and Linux guests are supported.
+
+ Because the VirtualBox Guest Additions drivers are not WHQL-certified
+ yet there might be warning dialogs during the actual Guest Additions
+ update. These need to be confirmed manually in order to continue the
+ installation process. This applies to Windows 2000 and Windows XP guests
+ and therefore these guests can't be updated in a fully automated fashion
+ without user interaction. However, to start a Guest Additions update for
+ the mentioned Windows versions anyway, the flag
+ AdditionsUpdateFlag_WaitForUpdateStartOnly can be specified. See
+ <link to="AdditionsUpdateFlag"/> for more information.
+
+ The guest needs to be restarted in order to make use of the updated
+ Guest Additions.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Guest OS is not supported for automated Guest Additions updates or the
+ already installed Guest Additions are not ready yet.
+ </result>
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Error while updating.
+ </result>
+
+ </desc>
+ <param name="source" type="wstring" dir="in">
+ <desc>
+ Path to the Guest Additions .ISO file to use for the update.
+ </desc>
+ </param>
+ <param name="arguments" type="wstring" dir="in" safearray="yes">
+ <desc>
+ Optional command line arguments to use for the Guest Additions
+ installer. Useful for retrofitting features which weren't installed
+ before in the guest.
+ </desc>
+ </param>
+ <param name="flags" type="AdditionsUpdateFlag" dir="in" safearray="yes">
+ <desc>
+ <link to="AdditionsUpdateFlag"/> flags.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+
+ <!--
+ // IProgress
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IProgress" extends="$unknown"
+ uuid="d7b98d2b-30e8-447e-99cb-e31becae6ae4"
+ wsmap="managed"
+ rest="managed"
+ wrap-hint-server-addinterfaces="IInternalProgressControl"
+ reservedMethods="8" reservedAttributes="12"
+ >
+ <desc>
+ The IProgress interface is used to track and control
+ asynchronous tasks within VirtualBox.
+
+ An instance of this is returned every time VirtualBox starts
+ an asynchronous task (in other words, a separate thread) which
+ continues to run after a method call returns. For example,
+ <link to="IMachine::saveState" />, which saves the state of
+ a running virtual machine, can take a long time to complete.
+ To be able to display a progress bar, a user interface such as
+ the VirtualBox graphical user interface can use the IProgress
+ object returned by that method.
+
+ Note that IProgress is a "read-only" interface in the sense
+ that only the VirtualBox internals behind the Main API can
+ create and manipulate progress objects, whereas client code
+ can only use the IProgress object to monitor a task's
+ progress and, if <link to="#cancelable" /> is @c true,
+ cancel the task by calling <link to="#cancel" />.
+
+ A task represented by IProgress consists of either one or
+ several sub-operations that run sequentially, one by one (see
+ <link to="#operation" /> and <link to="#operationCount" />).
+ Every operation is identified by a number (starting from 0)
+ and has a separate description.
+
+ You can find the individual percentage of completion of the current
+ operation in <link to="#operationPercent" /> and the
+ percentage of completion of the task as a whole
+ in <link to="#percent" />.
+
+ Similarly, you can wait for the completion of a particular
+ operation via <link to="#waitForOperationCompletion" /> or
+ for the completion of the whole task via
+ <link to="#waitForCompletion" />.
+ </desc>
+
+ <attribute name="id" type="uuid" mod="string" readonly="yes">
+ <desc>ID of the task.</desc>
+ </attribute>
+
+ <attribute name="description" type="wstring" readonly="yes">
+ <desc>Description of the task.</desc>
+ </attribute>
+
+ <attribute name="initiator" type="$unknown" readonly="yes">
+ <desc>Initiator of the task.</desc>
+ </attribute>
+
+ <attribute name="cancelable" type="boolean" readonly="yes">
+ <desc>Whether the task can be interrupted.</desc>
+ </attribute>
+
+ <attribute name="percent" type="unsigned long" readonly="yes">
+ <desc>
+ Current progress value of the task as a whole, in percent.
+ This value depends on how many operations are already complete.
+ Returns 100 if <link to="#completed" /> is @c true.
+ </desc>
+ </attribute>
+
+ <attribute name="timeRemaining" type="long" readonly="yes">
+ <desc>
+ Estimated remaining time until the task completes, in
+ seconds. Returns 0 once the task has completed; returns -1
+ if the remaining time cannot be computed, in particular if
+ the current progress is 0.
+
+ Even if a value is returned, the estimate will be unreliable
+ for low progress values. It will become more reliable as the
+ task progresses; it is not recommended to display an ETA
+ before at least 20% of a task have completed.
+ </desc>
+ </attribute>
+
+ <attribute name="completed" type="boolean" readonly="yes">
+ <desc>Whether the task has been completed.</desc>
+ </attribute>
+
+ <attribute name="canceled" type="boolean" readonly="yes">
+ <desc>Whether the task has been canceled.</desc>
+ </attribute>
+
+ <attribute name="resultCode" type="long" readonly="yes">
+ <desc>
+ Result code of the progress task.
+ Valid only if <link to="#completed"/> is @c true.
+ </desc>
+ </attribute>
+
+ <attribute name="errorInfo" type="IVirtualBoxErrorInfo" readonly="yes">
+ <desc>
+ Extended information about the unsuccessful result of the
+ progress operation. May be @c null if no extended information
+ is available.
+ Valid only if <link to="#completed"/> is @c true and
+ <link to="#resultCode"/> indicates a failure.
+ </desc>
+ </attribute>
+
+ <attribute name="operationCount" type="unsigned long" readonly="yes">
+ <desc>
+ Number of sub-operations this task is divided into.
+ Every task consists of at least one suboperation.
+ </desc>
+ </attribute>
+
+ <attribute name="operation" type="unsigned long" readonly="yes">
+ <desc>Number of the sub-operation being currently executed.</desc>
+ </attribute>
+
+ <attribute name="operationDescription" type="wstring" readonly="yes">
+ <desc>
+ Description of the sub-operation being currently executed.
+ </desc>
+ </attribute>
+
+ <attribute name="operationPercent" type="unsigned long" readonly="yes">
+ <desc>Progress value of the current sub-operation only, in percent.</desc>
+ </attribute>
+
+ <attribute name="operationWeight" type="unsigned long" readonly="yes">
+ <desc>Weight value of the current sub-operation only.</desc>
+ </attribute>
+
+ <attribute name="timeout" type="unsigned long">
+ <desc>
+ When non-zero, this specifies the number of milliseconds after which
+ the operation will automatically be canceled. This can only be set on
+ cancelable objects.
+ </desc>
+ </attribute>
+
+ <attribute name="eventSource" type="IEventSource" readonly="yes" rest="suppress"/>
+
+ <method name="waitForCompletion">
+ <rest request="post" path="/progresses/{progressid}/actions/"/>
+ <desc>
+ Waits until the task is done (including all sub-operations)
+ with a given timeout in milliseconds; specify -1 for an indefinite wait.
+
+ Note that the VirtualBox/XPCOM/COM/native event queues of the calling
+ thread are not processed while waiting. Neglecting event queues may
+ have dire consequences (degrade performance, resource hogs,
+ deadlocks, etc.), this is specially so for the main thread on
+ platforms using XPCOM. Callers are advised wait for short periods
+ and service their event queues between calls, or to create a worker
+ thread to do the waiting.
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Failed to wait for task completion.
+ </result>
+ </desc>
+
+ <param name="timeout" type="long" dir="in">
+ <desc>
+ Maximum time in milliseconds to wait or -1 to wait indefinitely.
+ </desc>
+ </param>
+ </method>
+
+ <method name="waitForOperationCompletion">
+ <rest request="post" path="/progresses/{progressid}/actions/"/>
+ <desc>
+ Waits until the given operation is done with a given timeout in
+ milliseconds; specify -1 for an indefinite wait.
+
+ See <link to="#waitForCompletion"> for event queue considerations.</link>
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Failed to wait for operation completion.
+ </result>
+
+ </desc>
+ <param name="operation" type="unsigned long" dir="in">
+ <desc>
+ Number of the operation to wait for.
+ Must be less than <link to="#operationCount"/>.
+ </desc>
+ </param>
+ <param name="timeout" type="long" dir="in">
+ <desc>
+ Maximum time in milliseconds to wait or -1 to wait indefinitely.
+ </desc>
+ </param>
+ </method>
+
+ <method name="cancel">
+ <rest request="post" path="/progresses/{progressid}/actions/"/>
+ <desc>
+ Cancels the task.
+ <note>
+ If <link to="#cancelable"/> is @c false, then this method will fail.
+ </note>
+
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Operation cannot be canceled.
+ </result>
+
+ </desc>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IInternalProgressControl" extends="$unknown"
+ uuid="41a033b8-cc87-4f6e-a0e9-47bb7f2d4be5"
+ internal="yes"
+ wsmap="suppress"
+ reservedMethods="8" reservedAttributes="8"
+ >
+
+ <method name="setCurrentOperationProgress">
+ <desc>Internal method, not to be called externally.</desc>
+ <param name="percent" type="unsigned long" dir="in" />
+ </method>
+
+ <method name="waitForOtherProgressCompletion">
+ <desc>
+ Internal method, not to be called externally.
+
+ Waits until the other task is completed (including all sub-operations)
+ and forward all changes from the other progress to this progress. This
+ means sub-operation number, description, percent and so on.
+
+ The caller is responsible for having at least the same count of
+ sub-operations in this progress object as there are in the other
+ progress object.
+
+ If the other progress object supports cancel and this object gets any
+ cancel request (when here enabled as well), it will be forwarded to
+ the other progress object.
+
+ Error information is automatically preserved (by transferring it to
+ the current thread's error information). If the caller wants to set it
+ as the completion state of this progress it needs to be done separately.
+
+ <result name="VBOX_E_TIMEOUT">
+ Waiting time has expired.
+ </result>
+ </desc>
+ <param name="progressOther" type="IProgress" dir="in">
+ <desc>Other progress object to be "cloned".</desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>Timeout (in ms). Pass 0 for an infinite timeout.</desc>
+ </param>
+ </method>
+
+ <method name="setNextOperation">
+ <desc>Internal method, not to be called externally.</desc>
+ <param name="nextOperationDescription" type="wstring" dir="in" />
+ <param name="nextOperationsWeight" type="unsigned long" dir="in" />
+ </method>
+
+ <method name="notifyPointOfNoReturn">
+ <desc>Internal method, not to be called externally.</desc>
+ </method>
+
+ <method name="notifyComplete">
+ <desc>Internal method, not to be called externally.</desc>
+ <param name="resultCode" type="long" dir="in" />
+ <param name="errorInfo" type="IVirtualBoxErrorInfo" dir="in" />
+ </method>
+
+ </interface>
+
+ <!--
+ // ISnapshot
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="ISnapshot" extends="$unknown"
+ uuid="6cc49055-dad4-4496-85cf-3f76bcb3b5fa"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ The ISnapshot interface represents a snapshot of the virtual
+ machine.
+
+ Together with the differencing media that are created
+ when a snapshot is taken, a machine can be brought back to
+ the exact state it was in when the snapshot was taken.
+
+ The ISnapshot interface has no methods, only attributes; snapshots
+ are controlled through methods of the <link to="IMachine" /> interface
+ which also manage the media associated with the snapshot.
+ The following operations exist:
+
+ <ul>
+ <li><link to="IMachine::takeSnapshot"/> creates a new snapshot
+ by creating new, empty differencing images for the machine's
+ media and saving the VM settings and (if the VM is running)
+ the current VM state in the snapshot.
+
+ The differencing images will then receive all data written to
+ the machine's media, while their parent (base) images
+ remain unmodified after the snapshot has been taken (see
+ <link to="IMedium" /> for details about differencing images).
+ This simplifies restoring a machine to the state of a snapshot:
+ only the differencing images need to be deleted.
+
+ The current machine state is not changed by taking a snapshot
+ except that <link to="IMachine::currentSnapshot" /> is set to
+ the newly created snapshot, which is also added to the machine's
+ snapshots tree.
+ </li>
+
+ <li><link to="IMachine::restoreSnapshot"/> resets a machine to
+ the state of a previous snapshot by deleting the differencing
+ image of each of the machine's media and setting the machine's
+ settings and state to the state that was saved in the snapshot (if any).
+
+ This destroys the machine's current state. After calling this,
+ <link to="IMachine::currentSnapshot" /> points to the snapshot
+ that was restored.
+ </li>
+
+ <li><link to="IMachine::deleteSnapshot"/> deletes a snapshot
+ without affecting the current machine state.
+
+ This does not change the current machine state, but instead frees the
+ resources allocated when the snapshot was taken: the settings and machine
+ state file are deleted (if any), and the snapshot's differencing image for
+ each of the machine's media gets merged with its parent image.
+
+ Neither the current machine state nor other snapshots are affected
+ by this operation, except that parent media will be modified
+ to contain the disk data associated with the snapshot being deleted.
+
+ When deleting the current snapshot, the <link to="IMachine::currentSnapshot" />
+ attribute is set to the current snapshot's parent or @c null if it
+ has no parent. Otherwise the attribute is unchanged.
+ </li>
+ </ul>
+
+ Each snapshot contains a copy of virtual machine's settings (hardware
+ configuration etc.). This copy is contained in an immutable (read-only)
+ instance of <link to="IMachine" /> which is available from the snapshot's
+ <link to="#machine" /> attribute. When restoring the snapshot, these
+ settings are copied back to the original machine.
+
+ In addition, if the machine was running when the
+ snapshot was taken (<link to="IMachine::state"/> is <link to="MachineState_Running"/>),
+ the current VM state is saved in the snapshot (similarly to what happens
+ when a VM's state is saved). The snapshot is then said to be <i>online</i>
+ because when restoring it, the VM will be running.
+
+ If the machine was in the <link to="MachineState_Saved">Saved</link> or
+ <link to="MachineState_AbortedSaved">AbortedSaved</link> state, the snapshot
+ receives a copy of the execution state file (<link to="IMachine::stateFilePath"/>).
+
+ Otherwise, if the machine was not running (<link to="MachineState_PoweredOff"/>
+ or <link to="MachineState_Aborted"/>), the snapshot is <i>offline</i>;
+ it then contains a so-called "zero execution state", representing a
+ machine that is powered off.
+ </desc>
+
+ <attribute name="id" type="uuid" mod="string" readonly="yes">
+ <desc>UUID of the snapshot.</desc>
+ </attribute>
+
+ <attribute name="name" type="wstring">
+ <desc>Short name of the snapshot.
+ <note>Setting this attribute causes <link to="IMachine::saveSettings" /> to
+ be called implicitly.</note>
+ </desc>
+ </attribute>
+
+ <attribute name="description" type="wstring">
+ <desc>Optional description of the snapshot.
+ <note>Setting this attribute causes <link to="IMachine::saveSettings" /> to
+ be called implicitly.</note>
+ </desc>
+ </attribute>
+
+ <attribute name="timeStamp" type="long long" readonly="yes">
+ <desc>
+ Timestamp of the snapshot, in milliseconds since 1970-01-01 UTC.
+ </desc>
+ </attribute>
+
+ <attribute name="online" type="boolean" readonly="yes">
+ <desc>
+ @c true if this snapshot is an online snapshot and @c false otherwise.
+
+ When this attribute is @c true, the
+ <link to="IMachine::stateFilePath"/> attribute of the
+ <link to="#machine"/> object associated with this snapshot
+ will point to the saved state file. Otherwise, it will be
+ an empty string.
+ </desc>
+ </attribute>
+
+ <attribute name="machine" type="IMachine" readonly="yes" rest="uuid">
+ <desc>
+ Virtual machine this snapshot is taken on. This object
+ stores all settings the machine had when taking this snapshot.
+ <note>
+ The returned machine object is immutable, i.e. no
+ any settings can be changed.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="parent" type="ISnapshot" readonly="yes" rest="uuid">
+ <desc>
+ Parent snapshot (a snapshot this one is based on), or
+ @c null if the snapshot has no parent (i.e. is the first snapshot).
+ </desc>
+ </attribute>
+
+ <attribute name="children" type="ISnapshot" readonly="yes" safearray="yes" rest="uuid">
+ <desc>
+ Child snapshots (all snapshots having this one as a parent).
+ By inspecting this attribute starting with a machine's root snapshot
+ (which can be obtained by calling <link to="IMachine::findSnapshot" />
+ with a @c null UUID), a machine's snapshots tree can be iterated over.
+ </desc>
+ </attribute>
+
+ <attribute name="childrenCount" type="unsigned long" readonly="yes">
+ <desc>
+ Returns the number of direct children of this snapshot.
+ </desc>
+ </attribute>
+
+ </interface>
+
+
+ <!--
+ // IMedium
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="MediumState"
+ uuid="ef41e980-e012-43cd-9dea-479d4ef14d13"
+ >
+ <desc>
+ Virtual medium state.
+ <see><link to="IMedium"/></see>
+ </desc>
+
+ <const name="NotCreated" value="0">
+ <desc>
+ Associated medium storage does not exist (either was not created yet or
+ was deleted).
+ </desc>
+ </const>
+ <const name="Created" value="1">
+ <desc>
+ Associated storage exists and accessible; this gets set if the
+ accessibility check performed by <link to="IMedium::refreshState" />
+ was successful.
+ </desc>
+ </const>
+ <const name="LockedRead" value="2">
+ <desc>
+ Medium is locked for reading (see <link to="IMedium::lockRead"/>),
+ no data modification is possible.
+ </desc>
+ </const>
+ <const name="LockedWrite" value="3">
+ <desc>
+ Medium is locked for writing (see <link to="IMedium::lockWrite"/>),
+ no concurrent data reading or modification is possible.
+ </desc>
+ </const>
+ <const name="Inaccessible" value="4">
+ <desc>
+ Medium accessibility check (see <link to="IMedium::refreshState" />) has
+ not yet been performed, or else, associated medium storage is not
+ accessible. In the first case, <link to="IMedium::lastAccessError"/>
+ is empty, in the second case, it describes the error that occurred.
+ </desc>
+ </const>
+ <const name="Creating" value="5">
+ <desc>
+ Associated medium storage is being created.
+ </desc>
+ </const>
+ <const name="Deleting" value="6">
+ <desc>
+ Associated medium storage is being deleted.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="MediumType"
+ uuid="fe663fb5-c244-4e1b-9d81-c628b417dd04"
+ >
+ <desc>
+ Virtual medium type. For each <link to="IMedium" />, this defines how the medium is
+ attached to a virtual machine (see <link to="IMediumAttachment" />) and what happens
+ when a snapshot (see <link to="ISnapshot" />) is taken of a virtual machine which has
+ the medium attached. At the moment DVD and floppy media are always of type "writethrough".
+ </desc>
+
+ <const name="Normal" value="0">
+ <desc>
+ Normal medium (attached directly or indirectly, preserved
+ when taking snapshots).
+ </desc>
+ </const>
+ <const name="Immutable" value="1">
+ <desc>
+ Immutable medium (attached indirectly, changes are wiped out
+ the next time the virtual machine is started).
+ </desc>
+ </const>
+ <const name="Writethrough" value="2">
+ <desc>
+ Write through medium (attached directly, ignored when
+ taking snapshots).
+ </desc>
+ </const>
+ <const name="Shareable" value="3">
+ <desc>
+ Allow using this medium concurrently by several machines.
+ <note>Present since VirtualBox 3.2.0, and accepted since 3.2.8.</note>
+ </desc>
+ </const>
+ <const name="Readonly" value="4">
+ <desc>
+ A readonly medium, which can of course be used by several machines.
+ <note>Present and accepted since VirtualBox 4.0.</note>
+ </desc>
+ </const>
+ <const name="MultiAttach" value="5">
+ <desc>
+ A medium which is indirectly attached, so that one base medium can
+ be used for several VMs which have their own differencing medium to
+ store their modifications. In some sense a variant of Immutable
+ with unset AutoReset flag in each differencing medium.
+ <note>Present and accepted since VirtualBox 4.0.</note>
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="MediumVariant"
+ uuid="0282e97f-4ef3-4411-a8e0-47c384803cb6"
+ >
+ <desc>
+ Virtual medium image variant. More than one flag may be set.
+ <see><link to="IMedium"/></see>
+ </desc>
+
+ <const name="Standard" value="0">
+ <desc>
+ No particular variant requested, results in using the backend default.
+ </desc>
+ </const>
+ <const name="VmdkSplit2G" value="0x01">
+ <desc>
+ VMDK image split in chunks of less than 2GByte.
+ </desc>
+ </const>
+ <const name="VmdkRawDisk" value="0x02">
+ <desc>
+ VMDK image representing a raw disk.
+ </desc>
+ </const>
+ <const name="VmdkStreamOptimized" value="0x04">
+ <desc>
+ VMDK streamOptimized image. Special import/export format which is
+ read-only/append-only.
+ </desc>
+ </const>
+ <const name="VmdkESX" value="0x08">
+ <desc>
+ VMDK format variant used on ESX products.
+ </desc>
+ </const>
+ <const name="VdiZeroExpand" value="0x100">
+ <desc>
+ Fill new blocks with zeroes while expanding image file.
+ </desc>
+ </const>
+ <const name="Fixed" value="0x10000">
+ <desc>
+ Fixed image. Only allowed for base images.
+ </desc>
+ </const>
+ <const name="Diff" value="0x20000">
+ <desc>
+ Differencing image. Only allowed for child images.
+ </desc>
+ </const>
+ <const name="Formatted" value="0x20000000">
+ <desc>
+ Special flag which requests formatting the disk image. Right now
+ supported for floppy images only.
+ </desc>
+ </const>
+ <const name="NoCreateDir" value="0x40000000">
+ <desc>
+ Special flag which suppresses automatic creation of the subdirectory.
+ Only used when passing the medium variant as an input parameter.
+ </desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IMediumAttachment" extends="$unknown"
+ uuid="8d095cb0-0126-43e0-b05d-326e74abb356"
+ wsmap="struct"
+ rest="managed"
+ reservedAttributes="8"
+ >
+ <desc>
+ The IMediumAttachment interface links storage media to virtual machines.
+ For each medium (<link to="IMedium"/>) which has been attached to a
+ storage controller (<link to="IStorageController"/>) of a machine
+ (<link to="IMachine"/>) via the <link to="IMachine::attachDevice" />
+ method, one instance of IMediumAttachment is added to the machine's
+ <link to="IMachine::mediumAttachments"/> array attribute.
+
+ Each medium attachment specifies the storage controller as well as a
+ port and device number and the IMedium instance representing a virtual
+ hard disk or floppy or DVD image.
+
+ For removable media (DVDs or floppies), there are two additional
+ options. For one, the IMedium instance can be @c null to represent
+ an empty drive with no media inserted (see <link to="IMachine::mountMedium" />);
+ secondly, the medium can be one of the pseudo-media for host drives
+ listed in <link to="IHost::DVDDrives"/> or <link to="IHost::floppyDrives"/>.
+
+ <h3>Attaching Hard Disks</h3>
+
+ Hard disks are attached to virtual machines using the
+ <link to="IMachine::attachDevice"/> method and detached using the
+ <link to="IMachine::detachDevice"/> method. Depending on a medium's
+ type (see <link to="IMedium::type" />), hard disks are attached either
+ <i>directly</i> or <i>indirectly</i>.
+
+ When a hard disk is being attached directly, it is associated with the
+ virtual machine and used for hard disk operations when the machine is
+ running. When a hard disk is being attached indirectly, a new differencing
+ hard disk linked to it is implicitly created and this differencing hard
+ disk is associated with the machine and used for hard disk operations.
+ This also means that if <link to="IMachine::attachDevice"/> performs
+ a direct attachment then the same hard disk will be returned in response
+ to the subsequent <link to="IMachine::getMedium"/> call; however if
+ an indirect attachment is performed then
+ <link to="IMachine::getMedium"/> will return the implicitly created
+ differencing hard disk, not the original one passed to <link
+ to="IMachine::attachDevice"/>. In detail:
+
+ <ul>
+ <li><b>Normal base</b> hard disks that do not have children (i.e.
+ differencing hard disks linked to them) and that are not already
+ attached to virtual machines in snapshots are attached <b>directly</b>.
+ Otherwise, they are attached <b>indirectly</b> because having
+ dependent children or being part of the snapshot makes it impossible
+ to modify hard disk contents without breaking the integrity of the
+ dependent party. The <link to="IMedium::readOnly"/> attribute allows to
+ quickly determine the kind of the attachment for the given hard
+ disk. Note that if a normal base hard disk is to be indirectly
+ attached to a virtual machine with snapshots then a special
+ procedure called <i>smart attachment</i> is performed (see below).</li>
+ <li><b>Normal differencing</b> hard disks are like normal base hard disks:
+ they are attached <b>directly</b> if they do not have children and are
+ not attached to virtual machines in snapshots, and <b>indirectly</b>
+ otherwise. Note that the smart attachment procedure is never performed
+ for differencing hard disks.</li>
+ <li><b>Immutable</b> hard disks are always attached <b>indirectly</b> because
+ they are designed to be non-writable. If an immutable hard disk is
+ attached to a virtual machine with snapshots then a special
+ procedure called smart attachment is performed (see below).</li>
+ <li><b>Writethrough</b> hard disks are always attached <b>directly</b>,
+ also as designed. This also means that writethrough hard disks cannot
+ have other hard disks linked to them at all.</li>
+ <li><b>Shareable</b> hard disks are always attached <b>directly</b>,
+ also as designed. This also means that shareable hard disks cannot
+ have other hard disks linked to them at all. They behave almost
+ like writethrough hard disks, except that shareable hard disks can
+ be attached to several virtual machines which are running, allowing
+ concurrent accesses. You need special cluster software running in
+ the virtual machines to make use of such disks.</li>
+ </ul>
+
+ Note that the same hard disk, regardless of its type, may be attached to
+ more than one virtual machine at a time. In this case, the machine that is
+ started first gains exclusive access to the hard disk and attempts to
+ start other machines having this hard disk attached will fail until the
+ first machine is powered down.
+
+ Detaching hard disks is performed in a <i>deferred</i> fashion. This means
+ that the given hard disk remains associated with the given machine after a
+ successful <link to="IMachine::detachDevice"/> call until
+ <link to="IMachine::saveSettings"/> is called to save all changes to
+ machine settings to disk. This deferring is necessary to guarantee that
+ the hard disk configuration may be restored at any time by a call to
+ <link to="IMachine::discardSettings"/> before the settings
+ are saved (committed).
+
+ Note that if <link to="IMachine::discardSettings"/> is called after
+ indirectly attaching some hard disks to the machine but before a call to
+ <link to="IMachine::saveSettings"/> is made, it will implicitly delete
+ all differencing hard disks implicitly created by
+ <link to="IMachine::attachDevice"/> for these indirect attachments.
+ Such implicitly created hard disks will also be immediately deleted when
+ detached explicitly using the <link to="IMachine::detachDevice"/>
+ call if it is made before <link to="IMachine::saveSettings"/>. This
+ implicit deletion is safe because newly created differencing hard
+ disks do not contain any user data.
+
+ However, keep in mind that detaching differencing hard disks that were
+ implicitly created by <link to="IMachine::attachDevice"/>
+ before the last <link to="IMachine::saveSettings"/> call will
+ <b>not</b> implicitly delete them as they may already contain some data
+ (for example, as a result of virtual machine execution). If these hard
+ disks are no more necessary, the caller can always delete them explicitly
+ using <link to="IMedium::deleteStorage"/> after they are actually de-associated
+ from this machine by the <link to="IMachine::saveSettings"/> call.
+
+ <h3>Smart Attachment</h3>
+
+ When normal base or immutable hard disks are indirectly attached to a
+ virtual machine then some additional steps are performed to make sure the
+ virtual machine will have the most recent "view" of the hard disk being
+ attached. These steps include walking through the machine's snapshots
+ starting from the current one and going through ancestors up to the first
+ snapshot. Hard disks attached to the virtual machine in all
+ of the encountered snapshots are checked whether they are descendants of
+ the given normal base or immutable hard disk. The first found child (which
+ is the differencing hard disk) will be used instead of the normal base or
+ immutable hard disk as a parent for creating a new differencing hard disk
+ that will be actually attached to the machine. And only if no descendants
+ are found or if the virtual machine does not have any snapshots then the
+ normal base or immutable hard disk will be used itself as a parent for
+ this differencing hard disk.
+
+ It is easier to explain what smart attachment does using the
+ following example:
+ <pre>
+BEFORE attaching B.vdi: AFTER attaching B.vdi:
+
+Snapshot 1 (B.vdi) Snapshot 1 (B.vdi)
+ Snapshot 2 (D1->B.vdi) Snapshot 2 (D1->B.vdi)
+ Snapshot 3 (D2->D1.vdi) Snapshot 3 (D2->D1.vdi)
+ Snapshot 4 (none) Snapshot 4 (none)
+ CurState (none) CurState (D3->D2.vdi)
+
+ NOT
+ ...
+ CurState (D3->B.vdi)
+ </pre>
+ The first column is the virtual machine configuration before the base hard
+ disk <tt>B.vdi</tt> is attached, the second column shows the machine after
+ this hard disk is attached. Constructs like <tt>D1->B.vdi</tt> and similar
+ mean that the hard disk that is actually attached to the machine is a
+ differencing hard disk, <tt>D1.vdi</tt>, which is linked to (based on)
+ another hard disk, <tt>B.vdi</tt>.
+
+ As we can see from the example, the hard disk <tt>B.vdi</tt> was detached
+ from the machine before taking Snapshot 4. Later, after Snapshot 4 was
+ taken, the user decides to attach <tt>B.vdi</tt> again. <tt>B.vdi</tt> has
+ dependent child hard disks (<tt>D1.vdi</tt>, <tt>D2.vdi</tt>), therefore
+ it cannot be attached directly and needs an indirect attachment (i.e.
+ implicit creation of a new differencing hard disk). Due to the smart
+ attachment procedure, the new differencing hard disk
+ (<tt>D3.vdi</tt>) will be based on <tt>D2.vdi</tt>, not on
+ <tt>B.vdi</tt> itself, since <tt>D2.vdi</tt> is the most recent view of
+ <tt>B.vdi</tt> existing for this snapshot branch of the given virtual
+ machine.
+
+ Note that if there is more than one descendant hard disk of the given base
+ hard disk found in a snapshot, and there is an exact device, channel and
+ bus match, then this exact match will be used. Otherwise, the youngest
+ descendant will be picked up.
+
+ There is one more important aspect of the smart attachment procedure which
+ is not related to snapshots at all. Before walking through the snapshots
+ as described above, the backup copy of the current list of hard disk
+ attachment is searched for descendants. This backup copy is created when
+ the hard disk configuration is changed for the first time after the last
+ <link to="IMachine::saveSettings"/> call and used by
+ <link to="IMachine::discardSettings"/> to undo the recent hard disk
+ changes. When such a descendant is found in this backup copy, it will be
+ simply re-attached back, without creating a new differencing hard disk for
+ it. This optimization is necessary to make it possible to re-attach the
+ base or immutable hard disk to a different bus, channel or device slot
+ without losing the contents of the differencing hard disk actually
+ attached to the machine in place of it.
+
+ </desc>
+
+ <attribute name="machine" type="IMachine" readonly="yes" rest="uuid">
+ <desc>Machine object for this medium attachment.</desc>
+ </attribute>
+
+ <attribute name="medium" type="IMedium" readonly="yes" rest="uuid">
+ <desc>Medium object associated with this attachment; it
+ can be @c null for removable devices.</desc>
+ </attribute>
+
+ <attribute name="controller" type="wstring" readonly="yes">
+ <desc>Name of the storage controller of this attachment; this
+ refers to one of the controllers in <link to="IMachine::storageControllers" />
+ by name.</desc>
+ </attribute>
+
+ <attribute name="port" type="long" readonly="yes">
+ <desc>Port number of this attachment.
+ See <link to="IMachine::attachDevice" /> for the meaning of this value for the different controller types.
+ </desc>
+ </attribute>
+
+ <attribute name="device" type="long" readonly="yes">
+ <desc>Device slot number of this attachment.
+ See <link to="IMachine::attachDevice" /> for the meaning of this value for the different controller types.
+ </desc>
+ </attribute>
+
+ <attribute name="type" type="DeviceType" readonly="yes">
+ <desc>Device type of this attachment.</desc>
+ </attribute>
+
+ <attribute name="passthrough" type="boolean" readonly="yes">
+ <desc>Pass I/O requests through to a device on the host.</desc>
+ </attribute>
+
+ <attribute name="temporaryEject" type="boolean" readonly="yes">
+ <desc>Whether guest-triggered eject results in unmounting the medium.</desc>
+ </attribute>
+
+ <attribute name="isEjected" type="boolean" readonly="yes">
+ <desc>Signals that the removable medium has been ejected. This is not
+ necessarily equivalent to having a @c null medium association.</desc>
+ </attribute>
+
+ <attribute name="nonRotational" type="boolean" readonly="yes">
+ <desc>Whether the associated medium is non-rotational.</desc>
+ </attribute>
+
+ <attribute name="discard" type="boolean" readonly="yes">
+ <desc>Whether the associated medium supports discarding unused blocks.</desc>
+ </attribute>
+
+ <attribute name="hotPluggable" type="boolean" readonly="yes">
+ <desc>Whether this attachment is hot pluggable or not.</desc>
+ </attribute>
+
+ <attribute name="bandwidthGroup" type="IBandwidthGroup" readonly="yes">
+ <desc>The bandwidth group this medium attachment is assigned to.</desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IMedium" extends="$unknown"
+ uuid="ad47ad09-787b-44ab-b343-a082a3f2dfb1"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="7" reservedAttributes="12"
+ >
+ <desc>
+ The IMedium interface represents virtual storage for a machine's
+ hard disks, CD/DVD or floppy drives. It will typically represent
+ a disk image on the host, for example a VDI or VMDK file representing
+ a virtual hard disk, or an ISO or RAW file representing virtual
+ removable media, but can also point to a network location (e.g.
+ for iSCSI targets).
+
+ Instances of IMedium are connected to virtual machines by way of medium
+ attachments, which link the storage medium to a particular device slot
+ of a storage controller of the virtual machine.
+ In the VirtualBox API, virtual storage is therefore always represented
+ by the following chain of object links:
+
+ <ul>
+ <li><link to="IMachine::storageControllers"/> contains an array of
+ storage controllers (IDE, SATA, SCSI, SAS or a floppy controller;
+ these are instances of <link to="IStorageController"/>).</li>
+ <li><link to="IMachine::mediumAttachments"/> contains an array of
+ medium attachments (instances of <link to="IMediumAttachment"/>
+ created by <link to="IMachine::attachDevice" />),
+ each containing a storage controller from the above array, a
+ port/device specification, and an instance of IMedium representing
+ the medium storage (image file).
+
+ For removable media, the storage medium is optional; a medium
+ attachment with no medium represents a CD/DVD or floppy drive
+ with no medium inserted. By contrast, hard disk attachments
+ will always have an IMedium object attached.</li>
+ <li>Each IMedium in turn points to a storage unit (such as a file
+ on the host computer or a network resource) that holds actual
+ data. This location is represented by the <link to="#location"/>
+ attribute.</li>
+ </ul>
+
+ Existing media are opened using <link to="IVirtualBox::openMedium"/>;
+ new hard disk media can be created with the VirtualBox API using the
+ <link to="IVirtualBox::createMedium"/> method. Differencing hard
+ disks (see below) are usually implicitly created by VirtualBox as
+ needed, but may also be created explicitly using <link to="#createDiffStorage"/>.
+ VirtualBox cannot create CD/DVD or floppy images (ISO and RAW files); these
+ should be created with external tools and then opened from within VirtualBox.
+
+ Only for CD/DVDs and floppies, an IMedium instance can also represent a host
+ drive. In that case the <link to="#id" /> attribute contains the UUID of
+ one of the drives in <link to="IHost::DVDDrives" /> or <link to="IHost::floppyDrives" />.
+
+ <h3>Media registries</h3>
+
+ When a medium has been opened or created using one of the aforementioned
+ APIs, it becomes "known" to VirtualBox. Known media can be attached
+ to virtual machines and re-found through <link to="IVirtualBox::openMedium"/>.
+ They also appear in the global
+ <link to="IVirtualBox::hardDisks" />,
+ <link to="IVirtualBox::DVDImages" /> and
+ <link to="IVirtualBox::floppyImages" /> arrays.
+
+ Prior to VirtualBox 4.0, opening a medium added it to a global media registry
+ in the VirtualBox.xml file, which was shared between all machines and made
+ transporting machines and their media from one host to another difficult.
+
+ Starting with VirtualBox 4.0, media are only added to a registry when they are
+ <i>attached</i> to a machine using <link to="IMachine::attachDevice" />. For
+ backwards compatibility, which registry a medium is added to depends on which
+ VirtualBox version created a machine:
+
+ <ul>
+ <li>If the medium has first been attached to a machine which was created by
+ VirtualBox 4.0 or later, it is added to that machine's media registry in
+ the machine XML settings file. This way all information about a machine's
+ media attachments is contained in a single file and can be transported
+ easily.</li>
+ <li>For older media attachments (i.e. if the medium was first attached to a
+ machine which was created with a VirtualBox version before 4.0), media
+ continue to be registered in the global VirtualBox settings file, for
+ backwards compatibility.</li>
+ </ul>
+
+ See <link to="IVirtualBox::openMedium" /> for more information.
+
+ Media are removed from media registries by the <link to="IMedium::close"/>,
+ <link to="#deleteStorage"/> and <link to="#mergeTo"/> methods.
+
+ <h3>Accessibility checks</h3>
+
+ VirtualBox defers media accessibility checks until the <link to="#refreshState" />
+ method is called explicitly on a medium. This is done to make the VirtualBox object
+ ready for serving requests as fast as possible and let the end-user
+ application decide if it needs to check media accessibility right away or not.
+
+ As a result, when VirtualBox starts up (e.g. the VirtualBox
+ object gets created for the first time), all known media are in the
+ "Inaccessible" state, but the value of the <link to="#lastAccessError"/>
+ attribute is an empty string because no actual accessibility check has
+ been made yet.
+
+ After calling <link to="#refreshState" />, a medium is considered
+ <i>accessible</i> if its storage unit can be read. In that case, the
+ <link to="#state"/> attribute has a value of "Created". If the storage
+ unit cannot be read (for example, because it is located on a disconnected
+ network resource, or was accidentally deleted outside VirtualBox),
+ the medium is considered <i>inaccessible</i>, which is indicated by the
+ "Inaccessible" state. The exact reason why the medium is inaccessible can be
+ obtained by reading the <link to="#lastAccessError"/> attribute.
+
+ <h3>Medium types</h3>
+
+ There are five types of medium behavior which are stored in the
+ <link to="#type"/> attribute (see <link to="MediumType" />) and
+ which define the medium's behavior with attachments and snapshots.
+
+ All media can be also divided in two groups: <i>base</i> media and
+ <i>differencing</i> media. A base medium contains all sectors of the
+ medium data in its own storage and therefore can be used independently.
+ In contrast, a differencing medium is a "delta" to some other medium and
+ contains only those sectors which differ from that other medium, which is
+ then called a <i>parent</i>. The differencing medium is said to be
+ <i>linked to</i> that parent. The parent may be itself a differencing
+ medium, thus forming a chain of linked media. The last element in that
+ chain must always be a base medium. Note that several differencing
+ media may be linked to the same parent medium.
+
+ Differencing media can be distinguished from base media by querying the
+ <link to="#parent"/> attribute: base media do not have parents they would
+ depend on, so the value of this attribute is always @c null for them.
+ Using this attribute, it is possible to walk up the medium tree (from the
+ child medium to its parent). It is also possible to walk down the tree
+ using the <link to="#children"/> attribute.
+
+ Note that the type of all differencing media is "normal"; all other
+ values are meaningless for them. Base media may be of any type.
+
+ <h3>Automatic composition of the file name part</h3>
+
+ Another extension to the <link to="IMedium::location"/> attribute is that
+ there is a possibility to cause VirtualBox to compose a unique value for
+ the file name part of the location using the UUID of the hard disk. This
+ applies only to hard disks in <link to="MediumState_NotCreated"/> state,
+ e.g. before the storage unit is created, and works as follows. You set the
+ value of the <link to="IMedium::location"/> attribute to a location
+ specification which only contains the path specification but not the file
+ name part and ends with either a forward slash or a backslash character.
+ In response, VirtualBox will generate a new UUID for the hard disk and
+ compose the file name using the following pattern:
+ <pre>
+ &lt;path&gt;/{&lt;uuid&gt;}.&lt;ext&gt;
+ </pre>
+ where <tt>&lt;path&gt;</tt> is the supplied path specification,
+ <tt>&lt;uuid&gt;</tt> is the newly generated UUID and <tt>&lt;ext&gt;</tt>
+ is the default extension for the storage format of this hard disk. After
+ that, you may call any of the methods that create a new hard disk storage
+ unit and they will use the generated UUID and file name.
+ </desc>
+
+ <attribute name="id" type="uuid" mod="string" readonly="yes">
+ <desc>
+ UUID of the medium. For a newly created medium, this value is a randomly
+ generated UUID.
+
+ <note>
+ For media in one of MediumState_NotCreated, MediumState_Creating or
+ MediumState_Deleting states, the value of this property is undefined
+ and will most likely be an empty UUID.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="description" type="wstring" wrap-hint-server="passcaller">
+ <desc>
+ Optional description of the medium. For a newly created medium the value
+ of this attribute is an empty string.
+
+ Medium types that don't support this attribute will return E_NOTIMPL in
+ attempt to get or set this attribute's value.
+
+ <note>
+ For some storage types, reading this attribute may return an outdated
+ (last known) value when <link to="#state"/> is <link
+ to="MediumState_Inaccessible"/> or <link
+ to="MediumState_LockedWrite"/> because the value of this attribute is
+ stored within the storage unit itself. Also note that changing the
+ attribute value is not possible in such case, as well as when the
+ medium is the <link to="MediumState_LockedRead"/> state.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="state" type="MediumState" readonly="yes">
+ <desc>
+ Returns the current medium state, which is the last state set by
+ the accessibility check performed by <link to="#refreshState"/>.
+ If that method has not yet been called on the medium, the state
+ is "Inaccessible"; as opposed to truly inaccessible media, the
+ value of <link to="#lastAccessError"/> will be an empty string in
+ that case.
+
+ <note>As of version 3.1, this no longer performs an accessibility check
+ automatically; call <link to="#refreshState"/> for that.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="variant" type="MediumVariant" safearray="yes" readonly="yes">
+ <desc>
+ Returns the storage format variant information for this medium
+ as an array of the flags described at <link to="MediumVariant" />.
+ Before <link to="#refreshState"/> is called this method returns
+ an undefined value.
+ </desc>
+ </attribute>
+
+ <attribute name="location" type="wstring">
+ <desc>
+ Location of the storage unit holding medium data.
+
+ The format of the location string is medium type specific. For medium
+ types using regular files in a host's file system, the location
+ string is the full file name.
+ </desc>
+ </attribute>
+
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>
+ Name of the storage unit holding medium data.
+
+ The returned string is a short version of the <link to="#location"/>
+ attribute that is suitable for representing the medium in situations
+ where the full location specification is too long (such as lists
+ and comboboxes in GUI frontends). This string is also used by frontends
+ to sort the media list alphabetically when needed.
+
+ For example, for locations that are regular files in the host's file
+ system, the value of this attribute is just the file name (+ extension),
+ without the path specification.
+
+ Note that as opposed to the <link to="#location"/> attribute, the name
+ attribute will not necessary be unique for a list of media of the
+ given type and format.
+ </desc>
+ </attribute>
+
+ <attribute name="deviceType" type="DeviceType" readonly="yes">
+ <desc>Kind of device (DVD/Floppy/HardDisk) which is applicable to this
+ medium.</desc>
+ </attribute>
+
+ <attribute name="hostDrive" type="boolean" readonly="yes">
+ <desc>True if this corresponds to a drive on the host.</desc>
+ </attribute>
+
+ <attribute name="size" type="long long" readonly="yes">
+ <desc>
+ Physical size of the storage unit used to hold medium data (in bytes).
+
+ <note>
+ For media whose <link to="#state"/> is <link
+ to="MediumState_Inaccessible"/>, the value of this property is the
+ last known size. For <link to="MediumState_NotCreated"/> media,
+ the returned value is zero.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="format" type="wstring" readonly="yes">
+ <desc>
+ Storage format of this medium.
+
+ The value of this attribute is a string that specifies a backend used
+ to store medium data. The storage format is defined when you create a
+ new medium or automatically detected when you open an existing medium,
+ and cannot be changed later.
+
+ The list of all storage formats supported by this VirtualBox
+ installation can be obtained using
+ <link to="ISystemProperties::mediumFormats"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="mediumFormat" type="IMediumFormat" readonly="yes">
+ <desc>
+ Storage medium format object corresponding to this medium.
+
+ The value of this attribute is a reference to the medium format object
+ that specifies the backend properties used to store medium data. The
+ storage format is defined when you create a new medium or automatically
+ detected when you open an existing medium, and cannot be changed later.
+
+ <note>@c null is returned if there is no associated medium format
+ object. This can e.g. happen for medium objects representing host
+ drives and other special medium objects.</note>
+ </desc>
+ </attribute>
+
+ <attribute name="type" type="MediumType" wrap-hint-server="passcaller">
+ <desc>
+ Type (role) of this medium.
+
+ The following constraints apply when changing the value of this
+ attribute:
+ <ul>
+ <li>If a medium is attached to a virtual machine (either in the
+ current state or in one of the snapshots), its type cannot be
+ changed.
+ </li>
+ <li>As long as the medium has children, its type cannot be set
+ to <link to="MediumType_Writethrough"/>.
+ </li>
+ <li>The type of all differencing media is
+ <link to="MediumType_Normal"/> and cannot be changed.
+ </li>
+ </ul>
+
+ The type of a newly created or opened medium is set to
+ <link to="MediumType_Normal"/>, except for DVD and floppy media,
+ which have a type of <link to="MediumType_Writethrough"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="allowedTypes" type="MediumType" safearray="yes" readonly="yes">
+ <desc>
+ Returns which medium types can selected for this medium.
+
+ <result name="E_NOTIMPL">
+ This attribute is not implemented at the moment.
+ </result>
+ </desc>
+ </attribute>
+
+ <attribute name="parent" type="IMedium" readonly="yes" wrap-hint-server="passcaller" rest="uuid">
+ <desc>
+ Parent of this medium (the medium this medium is directly based
+ on).
+
+ Only differencing media have parents. For base (non-differencing)
+ media, @c null is returned.
+ </desc>
+ </attribute>
+
+ <attribute name="children" type="IMedium" safearray="yes" readonly="yes" wrap-hint-server="passcaller" rest="uuid">
+ <desc>
+ Children of this medium (all differencing media directly based
+ on this medium). A @c null array is returned if this medium
+ does not have any children.
+ </desc>
+ </attribute>
+
+ <attribute name="base" type="IMedium" readonly="yes" wrap-hint-server="passcaller" rest="uuid">
+ <desc>
+ Base medium of this medium.
+
+ If this is a differencing medium, its base medium is the medium
+ the given medium branch starts from. For all other types of media, this
+ property returns the medium object itself (i.e. the same object this
+ property is read on).
+ </desc>
+ </attribute>
+
+ <attribute name="readOnly" type="boolean" readonly="yes" wrap-hint-server="passcaller">
+ <desc>
+ Returns @c true if this medium is read-only and @c false otherwise.
+
+ A medium is considered to be read-only when its contents cannot be
+ modified without breaking the integrity of other parties that depend on
+ this medium such as its child media or snapshots of virtual machines
+ where this medium is attached to these machines. If there are no
+ children and no such snapshots then there is no dependency and the
+ medium is not read-only.
+
+ The value of this attribute can be used to determine the kind of the
+ attachment that will take place when attaching this medium to a
+ virtual machine. If the value is @c false then the medium will
+ be attached directly. If the value is @c true then the medium
+ will be attached indirectly by creating a new differencing child
+ medium for that. See the interface description for more information.
+
+ Note that all <link to="MediumType_Immutable">Immutable</link> media
+ are always read-only while all
+ <link to="MediumType_Writethrough">Writethrough</link> media are
+ always not.
+
+ <note>
+ The read-only condition represented by this attribute is related to
+ the medium type and usage, not to the current
+ <link to="IMedium::state">medium state</link> and not to the read-only
+ state of the storage unit.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="logicalSize" type="long long" readonly="yes">
+ <desc>
+ Logical size of this medium (in bytes), as reported to the
+ guest OS running inside the virtual machine this medium is
+ attached to. The logical size is defined when the medium is created
+ and cannot be changed later.
+
+ <note>
+ For media whose state is <link to="#state"/> is <link
+ to="MediumState_Inaccessible"/>, the value of this property is the
+ last known logical size. For <link to="MediumState_NotCreated"/>
+ media, the returned value is zero.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="autoReset" type="boolean">
+ <desc>
+ Whether this differencing medium will be automatically reset each
+ time a virtual machine it is attached to is powered up. This
+ attribute is automatically set to @c true for the last
+ differencing image of an "immutable" medium (see
+ <link to="MediumType" />).
+
+ See <link to="#reset"/> for more information about resetting
+ differencing media.
+
+ <note>
+ Reading this property on a base (non-differencing) medium will
+ always @c false. Changing the value of this property in this
+ case is not supported.
+ </note>
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ This is not a differencing medium (when changing the attribute
+ value).
+ </result>
+ </desc>
+ </attribute>
+
+ <attribute name="lastAccessError" type="wstring" readonly="yes">
+ <desc>
+ Text message that represents the result of the last accessibility
+ check performed by <link to="#refreshState"/>.
+
+ An empty string is returned if the last accessibility check
+ was successful or has not yet been called. As a result, if
+ <link to="#state" /> is "Inaccessible" and this attribute is empty,
+ then <link to="#refreshState"/> has yet to be called; this is the
+ default value of media after VirtualBox initialization.
+ A non-empty string indicates a failure and should normally describe
+ a reason of the failure (for example, a file read error).
+ </desc>
+ </attribute>
+
+ <attribute name="machineIds" type="uuid" mod="string" safearray="yes" readonly="yes">
+ <desc>
+ Array of UUIDs of all machines this medium is attached to.
+
+ A @c null array is returned if this medium is not attached to any
+ machine or to any machine's snapshot.
+
+ <note>
+ The returned array will include a machine even if this medium is not
+ attached to that machine in the current state but attached to it in
+ one of the machine's snapshots. See <link to="#getSnapshotIds"/> for
+ details.
+ </note>
+ </desc>
+ </attribute>
+
+ <method name="setIds" wrap-hint-server="passcaller">
+ <rest request="post" path="/mediums/{mediumid}/configuration/"/>
+ <desc>
+ Changes the UUID and parent UUID for a hard disk medium.
+ </desc>
+ <param name="setImageId" type="boolean" dir="in">
+ <desc>
+ Select whether a new image UUID is set or not.
+ </desc>
+ </param>
+ <param name="imageId" type="uuid" mod="string" dir="in">
+ <desc>
+ New UUID for the image. If an empty string is passed, then a new
+ UUID is automatically created, provided that @a setImageId is @c true.
+ Specifying a zero UUID is not allowed.
+ </desc>
+ </param>
+ <param name="setParentId" type="boolean" dir="in">
+ <desc>
+ Select whether a new parent UUID is set or not.
+ </desc>
+ </param>
+ <param name="parentId" type="uuid" mod="string" dir="in">
+ <desc>
+ New parent UUID for the image. If an empty string is passed, then a
+ new UUID is automatically created, provided @a setParentId is
+ @c true. A zero UUID is valid.
+ </desc>
+ </param>
+ <result name="E_INVALIDARG">
+ Invalid parameter combination.
+ </result>
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Medium is not a hard disk medium.
+ </result>
+ </method>
+
+ <method name="refreshState" wrap-hint-server="passcaller">
+ <rest request="post" path="/mediums/{mediumid}/configuration/"/>
+ <desc>
+ If the current medium state (see <link to="MediumState"/>) is one of
+ "Created", "Inaccessible" or "LockedRead", then this performs an
+ accessibility check on the medium and sets the value of the <link to="#state"/>
+ attribute accordingly; that value is also returned for convenience.
+
+ For all other state values, this does not perform a refresh but returns
+ the state only.
+
+ The refresh, if performed, may take a long time (several seconds or even
+ minutes, depending on the storage unit location and format) because it performs an
+ accessibility check of the storage unit. This check may cause a significant
+ delay if the storage unit of the given medium is, for example, a file located
+ on a network share which is not currently accessible due to connectivity
+ problems. In that case, the call will not return until a timeout
+ interval defined by the host OS for this operation expires. For this reason,
+ it is recommended to never read this attribute on the main UI thread to avoid
+ making the UI unresponsive.
+
+ If the last known state of the medium is "Created" and the accessibility
+ check fails, then the state would be set to "Inaccessible", and
+ <link to="#lastAccessError"/> may be used to get more details about the
+ failure. If the state of the medium is "LockedRead", then it remains the
+ same, and a non-empty value of <link to="#lastAccessError"/> will
+ indicate a failed accessibility check in this case.
+
+ Note that not all medium states are applicable to all medium types.
+ </desc>
+ <param name="state" type="MediumState" dir="return">
+ <desc>
+ New medium state.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getSnapshotIds">
+ <rest request="get" path="/mediums/{mediumid}/configuration/"/>
+ <desc>
+ Returns an array of UUIDs of all snapshots of the given machine where
+ this medium is attached to.
+
+ If the medium is attached to the machine in the current state, then the
+ first element in the array will always be the ID of the queried machine
+ (i.e. the value equal to the @c machineId argument), followed by
+ snapshot IDs (if any).
+
+ If the medium is not attached to the machine in the current state, then
+ the array will contain only snapshot IDs.
+
+ The returned array may be @c null if this medium is not attached
+ to the given machine at all, neither in the current state nor in one of
+ the snapshots.
+ </desc>
+ <param name="machineId" type="uuid" mod="string" dir="in">
+ <desc>
+ UUID of the machine to query.
+ </desc>
+ </param>
+ <param name="snapshotIds" type="uuid" mod="string" safearray="yes" dir="return">
+ <desc>
+ Array of snapshot UUIDs of the given machine using this medium.
+ </desc>
+ </param>
+ </method>
+
+ <method name="lockRead">
+ <rest request="post" path="/mediums/{mediumid}/configuration/"/>
+ <desc>
+ Locks this medium for reading.
+
+ A read lock is shared: many clients can simultaneously lock the
+ same medium for reading unless it is already locked for writing (see
+ <link to="#lockWrite"/>) in which case an error is returned.
+
+ When the medium is locked for reading, it cannot be modified
+ from within VirtualBox. This means that any method that changes
+ the properties of this medium or contents of the storage unit
+ will return an error (unless explicitly stated otherwise). That
+ includes an attempt to start a virtual machine that wants to
+ write to the medium.
+
+ When the virtual machine is started up, it locks for reading all
+ media it uses in read-only mode. If some medium cannot be locked
+ for reading, the startup procedure will fail.
+ A medium is typically locked for reading while it is used by a running
+ virtual machine but has a depending differencing image that receives
+ the actual write operations. This way one base medium can have
+ multiple child differencing images which can be written to
+ simultaneously. Read-only media such as DVD and floppy images are
+ also locked for reading only (so they can be in use by multiple
+ machines simultaneously).
+
+ A medium is also locked for reading when it is the source of a
+ write operation such as <link to="#cloneTo"/> or <link to="#mergeTo"/>.
+
+ The medium locked for reading must be unlocked by abandoning the
+ returned token object, see <link to="IToken"/>. Calls to
+ <link to="#lockRead"/> can be nested and the lock is actually released
+ when all callers have abandoned the token.
+
+ This method sets the medium state (see <link to="#state"/>) to
+ "LockedRead" on success. The medium's previous state must be
+ one of "Created", "Inaccessible" or "LockedRead".
+
+ Locking an inaccessible medium is not an error; this method performs
+ a logical lock that prevents modifications of this medium through
+ the VirtualBox API, not a physical file-system lock of the underlying
+ storage unit.
+
+ This method returns the current state of the medium
+ <i>before</i> the operation.
+
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Invalid medium state (e.g. not created, locked, inaccessible,
+ creating, deleting).
+ </result>
+
+ </desc>
+ <param name="token" type="IToken" dir="return">
+ <desc>
+ Token object, when this is released (reference count reaches 0) then
+ the lock count is decreased. The lock is released when the lock count
+ reaches 0.
+ </desc>
+ </param>
+ </method>
+
+ <method name="lockWrite">
+ <rest request="post" path="/mediums/{mediumid}/configuration/"/>
+ <desc>
+ Locks this medium for writing.
+
+ A write lock, as opposed to <link to="#lockRead"/>, is
+ exclusive: there may be only one client holding a write lock,
+ and there may be no read locks while the write lock is held.
+ As a result, read-locking fails if a write lock is held, and
+ write-locking fails if either a read or another write lock is held.
+
+ When a medium is locked for writing, it cannot be modified
+ from within VirtualBox, and it is not guaranteed that the values
+ of its properties are up-to-date. Any method that changes the
+ properties of this medium or contents of the storage unit will
+ return an error (unless explicitly stated otherwise).
+
+ When a virtual machine is started up, it locks for writing all
+ media it uses to write data to. If any medium could not be locked
+ for writing, the startup procedure will fail. If a medium has
+ differencing images, then while the machine is running, only
+ the last ("leaf") differencing image is locked for writing,
+ whereas its parents are locked for reading only.
+
+ A medium is also locked for writing when it is the target of a
+ write operation such as <link to="#cloneTo"/> or <link to="#mergeTo"/>.
+
+ The medium locked for writing must be unlocked by abandoning the
+ returned token object, see <link to="IToken"/>. Write locks
+ <i>cannot</i> be nested.
+
+ This method sets the medium state (see <link to="#state"/>) to
+ "LockedWrite" on success. The medium's previous state must be
+ either "Created" or "Inaccessible".
+
+ Locking an inaccessible medium is not an error; this method performs
+ a logical lock that prevents modifications of this medium through
+ the VirtualBox API, not a physical file-system lock of the underlying
+ storage unit.
+
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Invalid medium state (e.g. not created, locked, inaccessible,
+ creating, deleting).
+ </result>
+
+ </desc>
+ <param name="token" type="IToken" dir="return">
+ <desc>
+ Token object, when this is released (reference count reaches 0) then
+ the lock is released.
+ </desc>
+ </param>
+ </method>
+
+ <method name="close" wrap-hint-server="passcaller">
+ <rest request="post" path="/mediums/{mediumid}/actions/"/>
+ <desc>
+ Closes this medium.
+
+ The medium must not be attached to any known virtual machine
+ and must not have any known child media, otherwise the
+ operation will fail.
+
+ When the medium is successfully closed, it is removed from
+ the list of registered media, but its storage unit is not
+ deleted. In particular, this means that this medium can
+ later be opened again using the <link to="IVirtualBox::openMedium"/>
+ call.
+
+ Note that after this method successfully returns, the given medium
+ object becomes uninitialized. This means that any attempt
+ to call any of its methods or attributes will fail with the
+ <tt>"Object not ready" (E_ACCESSDENIED)</tt> error.
+
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Invalid medium state (other than not created, created or
+ inaccessible).
+ </result>
+ <result name="VBOX_E_OBJECT_IN_USE">
+ Medium attached to virtual machine.
+ </result>
+ <result name="VBOX_E_FILE_ERROR">
+ Settings file not accessible.
+ </result>
+ <result name="VBOX_E_XML_ERROR">
+ Could not parse the settings file.
+ </result>
+
+ </desc>
+ </method>
+
+ <!-- property methods -->
+
+ <method name="getProperty" const="yes">
+ <rest request="get" path="/mediums/{mediumid}/configuration/"/>
+ <desc>
+ Returns the value of the custom medium property with the given name.
+
+ The list of all properties supported by the given medium format can
+ be obtained with <link to="IMediumFormat::describeProperties"/>.
+
+ <note>If this method returns an empty string in @a value, the requested
+ property is supported but currently not assigned any value.</note>
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Requested property does not exist (not supported by the format).
+ </result>
+ <result name="E_INVALIDARG">@a name is @c null or empty.</result>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the property to get.</desc>
+ </param>
+ <param name="value" type="wstring" dir="return">
+ <desc>Current property value.</desc>
+ </param>
+ </method>
+
+ <method name="setProperty">
+ <rest request="post" path="/mediums/{mediumid}/configuration/"/>
+ <desc>
+ Sets the value of the custom medium property with the given name.
+
+ The list of all properties supported by the given medium format can
+ be obtained with <link to="IMediumFormat::describeProperties"/>.
+
+ <note>Setting the property value to @c null or an empty string is
+ equivalent to deleting the existing value. A default value (if it is
+ defined for this property) will be used by the format backend in this
+ case.</note>
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Requested property does not exist (not supported by the format).
+ </result>
+ <result name="E_INVALIDARG">@a name is @c null or empty.</result>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the property to set.</desc>
+ </param>
+ <param name="value" type="wstring" dir="in">
+ <desc>Property value to set.</desc>
+ </param>
+ </method>
+
+ <method name="getProperties" const="yes">
+ <rest request="get" path="/mediums/{mediumid}/configuration/"/>
+ <desc>
+ Returns values for a group of properties in one call.
+
+ The names of the properties to get are specified using the @a names
+ argument which is a list of comma-separated property names or
+ an empty string if all properties are to be returned.
+ <note>Currently the value of this argument is ignored and the method
+ always returns all existing properties.</note>
+
+ The list of all properties supported by the given medium format can
+ be obtained with <link to="IMediumFormat::describeProperties"/>.
+
+ The method returns two arrays, the array of property names corresponding
+ to the @a names argument and the current values of these properties.
+ Both arrays have the same number of elements with each element at the
+ given index in the first array corresponds to an element at the same
+ index in the second array.
+
+ For properties that do not have assigned values, an empty string is
+ returned at the appropriate index in the @a returnValues array.
+
+ </desc>
+ <param name="names" type="wstring" dir="in">
+ <desc>
+ Names of properties to get.
+ </desc>
+ </param>
+ <param name="returnNames" type="wstring" safearray="yes" dir="out">
+ <desc>Names of returned properties.</desc>
+ </param>
+ <param name="returnValues" type="wstring" safearray="yes" dir="return">
+ <desc>Values of returned properties.</desc>
+ </param>
+ </method>
+
+ <method name="setProperties">
+ <rest request="post" path="/mediums/{mediumid}/configuration/"/>
+ <desc>
+ Sets values for a group of properties in one call.
+
+ The names of the properties to set are passed in the @a names
+ array along with the new values for them in the @a values array. Both
+ arrays have the same number of elements with each element at the given
+ index in the first array corresponding to an element at the same index
+ in the second array.
+
+ If there is at least one property name in @a names that is not valid,
+ the method will fail before changing the values of any other properties
+ from the @a names array.
+
+ Using this method over <link to="#setProperty"/> is preferred if you
+ need to set several properties at once since it is more efficient.
+
+ The list of all properties supported by the given medium format can
+ be obtained with <link to="IMediumFormat::describeProperties"/>.
+
+ Setting the property value to @c null or an empty string is equivalent
+ to deleting the existing value. A default value (if it is defined for
+ this property) will be used by the format backend in this case.
+ </desc>
+ <param name="names" type="wstring" safearray="yes" dir="in">
+ <desc>Names of properties to set.</desc>
+ </param>
+ <param name="values" type="wstring" safearray="yes" dir="in">
+ <desc>Values of properties to set.</desc>
+ </param>
+ </method>
+
+ <!-- storage methods -->
+
+ <method name="createBaseStorage">
+ <rest request="post" path="/mediums/{mediumid}/actions/"/>
+ <desc>
+ Starts creating a hard disk storage unit (fixed/dynamic, according
+ to the variant flags) in the background. The previous storage unit
+ created for this object, if any, must first be deleted using
+ <link to="#deleteStorage"/>, otherwise the operation will fail.
+
+ Before the operation starts, the medium is placed in
+ <link to="MediumState_Creating"/> state. If the create operation
+ fails, the medium will be placed back in <link to="MediumState_NotCreated"/>
+ state.
+
+ After the returned progress object reports that the operation has
+ successfully completed, the medium state will be set to <link
+ to="MediumState_Created"/>, the medium will be remembered by this
+ VirtualBox installation and may be attached to virtual machines.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ The variant of storage creation operation is not supported. See <link
+ to="IMediumFormat::capabilities"/>.
+ </result>
+ </desc>
+ <param name="logicalSize" type="long long" dir="in">
+ <desc>Maximum logical size of the medium in bytes.</desc>
+ </param>
+ <param name="variant" type="MediumVariant" safearray="yes" dir="in">
+ <desc>Exact image variant which should be created (as a combination of
+ <link to="MediumVariant" /> flags).</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="deleteStorage">
+ <rest request="post" path="/mediums/{mediumid}/actions/"/>
+ <desc>
+ Starts deleting the storage unit of this medium.
+
+ The medium must not be attached to any known virtual machine and must
+ not have any known child media, otherwise the operation will fail.
+ It will also fail if there is no storage unit to delete or if deletion
+ is already in progress, or if the medium is being in use (locked for
+ read or for write) or inaccessible. Therefore, the only valid state for
+ this operation to succeed is <link to="MediumState_Created"/>.
+
+ Before the operation starts, the medium is placed in
+ <link to="MediumState_Deleting"/> state and gets removed from the list
+ of remembered hard disks (media registry). If the delete operation
+ fails, the medium will be remembered again and placed back to
+ <link to="MediumState_Created"/> state.
+
+ After the returned progress object reports that the operation is
+ complete, the medium state will be set to
+ <link to="MediumState_NotCreated"/> and you will be able to use one of
+ the storage creation methods to create it again.
+
+ <see><link to="#close"/></see>
+
+ <result name="VBOX_E_OBJECT_IN_USE">
+ Medium is attached to a virtual machine.
+ </result>
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Storage deletion is not allowed because neither of storage creation
+ operations are supported. See
+ <link to="IMediumFormat::capabilities"/>.
+ </result>
+
+ <note>
+ If the deletion operation fails, it is not guaranteed that the storage
+ unit still exists. You may check the <link to="IMedium::state"/> value
+ to answer this question.
+ </note>
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <!-- diff methods -->
+
+ <method name="createDiffStorage" wrap-hint-server="passcaller">
+ <rest request="post" path="/mediums/{mediumid}/actions/"/>
+ <desc>
+ Starts creating an empty differencing storage unit based on this
+ medium in the format and at the location defined by the @a target
+ argument.
+
+ The target medium must be in <link to="MediumState_NotCreated"/>
+ state (i.e. must not have an existing storage unit). Upon successful
+ completion, this operation will set the type of the target medium to
+ <link to="MediumType_Normal"/> and create a storage unit necessary to
+ represent the differencing medium data in the given format (according
+ to the storage format of the target object).
+
+ After the returned progress object reports that the operation is
+ successfully complete, the target medium gets remembered by this
+ VirtualBox installation and may be attached to virtual machines.
+
+ <note>
+ The medium will be set to <link to="MediumState_LockedRead"/>
+ state for the duration of this operation.
+ </note>
+ <result name="VBOX_E_OBJECT_IN_USE">
+ Medium not in @c NotCreated state.
+ </result>
+ </desc>
+ <param name="target" type="IMedium" dir="in">
+ <desc>Target medium.</desc>
+ </param>
+ <param name="variant" type="MediumVariant" safearray="yes" dir="in">
+ <desc>Exact image variant which should be created (as a combination of
+ <link to="MediumVariant" /> flags).</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="mergeTo">
+ <rest request="post" path="/mediums/{mediumid}/actions/"/>
+ <desc>
+ Starts merging the contents of this medium and all intermediate
+ differencing media in the chain to the given target medium.
+
+ The target medium must be either a descendant of this medium or
+ its ancestor (otherwise this method will immediately return a failure).
+ It follows that there are two logical directions of the merge operation:
+ from ancestor to descendant (<i>forward merge</i>) and from descendant to
+ ancestor (<i>backward merge</i>). Let us consider the following medium
+ chain:
+
+ <pre>Base &lt;- Diff_1 &lt;- Diff_2</pre>
+
+ Here, calling this method on the <tt>Base</tt> medium object with
+ <tt>Diff_2</tt> as an argument will be a forward merge; calling it on
+ <tt>Diff_2</tt> with <tt>Base</tt> as an argument will be a backward
+ merge. Note that in both cases the contents of the resulting medium
+ will be the same, the only difference is the medium object that takes
+ the result of the merge operation. In case of the forward merge in the
+ above example, the result will be written to <tt>Diff_2</tt>; in case of
+ the backward merge, the result will be written to <tt>Base</tt>. In
+ other words, the result of the operation is always stored in the target
+ medium.
+
+ Upon successful operation completion, the storage units of all media in
+ the chain between this (source) medium and the target medium, including
+ the source medium itself, will be automatically deleted and the
+ relevant medium objects (including this medium) will become
+ uninitialized. This means that any attempt to call any of
+ their methods or attributes will fail with the
+ <tt>"Object not ready" (E_ACCESSDENIED)</tt> error. Applied to the above
+ example, the forward merge of <tt>Base</tt> to <tt>Diff_2</tt> will
+ delete and uninitialize both <tt>Base</tt> and <tt>Diff_1</tt> media.
+ Note that <tt>Diff_2</tt> in this case will become a base medium
+ itself since it will no longer be based on any other medium.
+
+ Considering the above, all of the following conditions must be met in
+ order for the merge operation to succeed:
+ <ul>
+ <li>
+ Neither this (source) medium nor any intermediate
+ differencing medium in the chain between it and the target
+ medium is attached to any virtual machine.
+ </li>
+ <li>
+ Neither the source medium nor the target medium is an
+ <link to="MediumType_Immutable"/> medium.
+ </li>
+ <li>
+ The part of the medium tree from the source medium to the
+ target medium is a linear chain, i.e. all medium in this
+ chain have exactly one child which is the next medium in this
+ chain. The only exception from this rule is the target medium in
+ the forward merge operation; it is allowed to have any number of
+ child media because the merge operation will not change its
+ logical contents (as it is seen by the guest OS or by children).
+ </li>
+ <li>
+ None of the involved media are in
+ <link to="MediumState_LockedRead"/> or
+ <link to="MediumState_LockedWrite"/> state.
+ </li>
+ </ul>
+
+ <note>
+ This (source) medium and all intermediates will be placed to <link
+ to="MediumState_Deleting"/> state and the target medium will be
+ placed to <link to="MediumState_LockedWrite"/> state and for the
+ duration of this operation.
+ </note>
+ </desc>
+ <param name="target" type="IMedium" dir="in">
+ <desc>Target medium.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <!-- clone method -->
+
+ <method name="cloneTo">
+ <rest request="post" path="/mediums/{mediumid}/actions/"/>
+ <desc>
+ Starts creating a clone of this medium in the format and at the
+ location defined by the @a target argument.
+
+ The target medium must be either in <link to="MediumState_NotCreated"/>
+ state (i.e. must not have an existing storage unit) or in
+ <link to="MediumState_Created"/> state (i.e. created and not locked, and
+ big enough to hold the data or else the copy will be partial). Upon
+ successful completion, the cloned medium will contain exactly the
+ same sector data as the medium being cloned, except that in the
+ first case a new UUID for the clone will be randomly generated, and in
+ the second case the UUID will remain unchanged.
+
+ The @a parent argument defines which medium will be the parent
+ of the clone. Passing a @c null reference indicates that the clone will
+ be a base image, i.e. completely independent. It is possible to specify
+ an arbitrary medium for this parameter, including the parent of the
+ medium which is being cloned. Even cloning to a child of the source
+ medium is possible. Note that when cloning to an existing image, the
+ @a parent argument is ignored.
+
+ After the returned progress object reports that the operation is
+ successfully complete, the target medium gets remembered by this
+ VirtualBox installation and may be attached to virtual machines.
+
+ <note>
+ This medium will be placed to <link to="MediumState_LockedRead"/>
+ state for the duration of this operation.
+ </note>
+ <result name="E_NOTIMPL">
+ The specified cloning variant is not supported at the moment.
+ </result>
+ </desc>
+ <param name="target" type="IMedium" dir="in">
+ <desc>Target medium.</desc>
+ </param>
+ <param name="variant" type="MediumVariant" safearray="yes" dir="in">
+ <desc>Exact image variant which should be created (as a combination of
+ <link to="MediumVariant" /> flags).</desc>
+ </param>
+ <param name="parent" type="IMedium" dir="in">
+ <desc>Parent of the cloned medium.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="cloneToBase">
+ <rest request="post" path="/mediums/{mediumid}/actions/"/>
+ <desc>
+ Starts creating a clone of this medium in the format and at the
+ location defined by the @a target argument.
+
+ The target medium must be either in <link to="MediumState_NotCreated"/>
+ state (i.e. must not have an existing storage unit) or in
+ <link to="MediumState_Created"/> state (i.e. created and not locked, and
+ big enough to hold the data or else the copy will be partial). Upon
+ successful completion, the cloned medium will contain exactly the
+ same sector data as the medium being cloned, except that in the
+ first case a new UUID for the clone will be randomly generated, and in
+ the second case the UUID will remain unchanged.
+
+ The @a parent argument defines which medium will be the parent
+ of the clone. In this case the clone will be a base image, i.e.
+ completely independent. It is possible to specify an arbitrary
+ medium for this parameter, including the parent of the
+ medium which is being cloned. Even cloning to a child of the source
+ medium is possible. Note that when cloning to an existing image, the
+ @a parent argument is ignored.
+
+ After the returned progress object reports that the operation is
+ successfully complete, the target medium gets remembered by this
+ VirtualBox installation and may be attached to virtual machines.
+
+ <note>
+ This medium will be placed to <link to="MediumState_LockedRead"/>
+ state for the duration of this operation.
+ </note>
+ <result name="E_NOTIMPL">
+ The specified cloning variant is not supported at the moment.
+ </result>
+ </desc>
+ <param name="target" type="IMedium" dir="in">
+ <desc>Target medium.</desc>
+ </param>
+ <param name="variant" type="MediumVariant" safearray="yes" dir="in">
+ <desc><link to="MediumVariant" /> flags).</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <!-- other methods -->
+
+ <method name="moveTo" wrap-hint-server="passcaller">
+ <rest request="post" path="/mediums/{mediumid}/actions/"/>
+ <desc>
+ Changes the location of this medium. Some medium types may support
+ changing the storage unit location by simply changing the value of the
+ associated property. In this case the operation is performed
+ immediately, and @a progress is returning a @c null reference.
+ Otherwise on success there is a progress object returned, which
+ signals progress and completion of the operation. This distinction is
+ necessary because for some formats the operation is very fast, while
+ for others it can be very slow (moving the image file by copying all
+ data), and in the former case it'd be a waste of resources to create
+ a progress object which will immediately signal completion.
+
+ When setting a location for a medium which corresponds to a/several
+ regular file(s) in the host's file system, the given file name may be
+ either relative to the <link to="IVirtualBox::homeFolder">VirtualBox
+ home folder</link> or absolute. Note that if the given location
+ specification does not contain the file extension part then a proper
+ default extension will be automatically appended by the implementation
+ depending on the medium type.
+
+ <result name="E_NOTIMPL">
+ The operation is not implemented yet.
+ </result>
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Medium format does not support changing the location.
+ </result>
+ </desc>
+ <param name="location" type="wstring" dir="in">
+ <desc>New location.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="compact">
+ <rest request="post" path="/mediums/{mediumid}/actions/"/>
+ <desc>
+ Starts compacting of this medium. This means that the medium is
+ transformed into a possibly more compact storage representation.
+ This potentially creates temporary images, which can require a
+ substantial amount of additional disk space.
+
+ This medium will be placed to <link to="MediumState_LockedWrite"/>
+ state and all its parent media (if any) will be placed to
+ <link to="MediumState_LockedRead"/> state for the duration of this
+ operation.
+
+ Please note that the results can be either returned straight away,
+ or later as the result of the background operation via the object
+ returned via the @a progress parameter.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Medium format does not support compacting (but potentially
+ needs it).
+ </result>
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="resize">
+ <rest request="post" path="/mediums/{mediumid}/actions/"/>
+ <desc>
+ Starts resizing this medium. This means that the nominal size of the
+ medium is set to the new value. Both increasing and decreasing the
+ size is possible, and there are no safety checks, since VirtualBox
+ does not make any assumptions about the medium contents.
+
+ Resizing usually needs additional disk space, and possibly also
+ some temporary disk space. Note that resize does not create a full
+ temporary copy of the medium, so the additional disk space requirement
+ is usually much lower than using the clone operation.
+
+ This medium will be placed to <link to="MediumState_LockedWrite"/>
+ state for the duration of this operation.
+
+ Please note that the results can be either returned straight away,
+ or later as the result of the background operation via the object
+ returned via the @a progress parameter.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Medium format does not support resizing.
+ </result>
+ </desc>
+ <param name="logicalSize" type="long long" dir="in">
+ <desc>New nominal capacity of the medium in bytes.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="reset" wrap-hint-server="passcaller">
+ <rest request="post" path="/mediums/{mediumid}/actions/"/>
+ <desc>
+ Starts erasing the contents of this differencing medium.
+
+ This operation will reset the differencing medium to its initial
+ state when it does not contain any sector data and any read operation is
+ redirected to its parent medium. This automatically gets called
+ during VM power-up for every medium whose <link to="#autoReset" />
+ attribute is @c true.
+
+ The medium will be write-locked for the duration of this operation (see
+ <link to="#lockWrite" />).
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ This is not a differencing medium.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Medium is not in <link to="MediumState_Created"/> or
+ <link to="MediumState_Inaccessible"/> state.
+ </result>
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="changeEncryption">
+ <rest request="post" path="/mediums/{mediumid}/actions/"/>
+ <desc>
+ Starts encryption of this medium. This means that the stored data in the
+ medium is encrypted.
+
+ This medium will be placed to <link to="MediumState_LockedWrite"/>
+ state.
+
+ Please note that the results can be either returned straight away,
+ or later as the result of the background operation via the object
+ returned via the @a progress parameter.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Encryption is not supported for this medium because it is attached to more than one VM
+ or has children.
+ </result>
+ </desc>
+ <param name="currentPassword" type="wstring" dir="in">
+ <desc>
+ The current password the medium is protected with. Use an empty string to indicate
+ that the medium isn't encrypted.
+ </desc>
+ </param>
+ <param name="cipher" type="wstring" dir="in">
+ <desc>
+ The cipher to use for encryption. An empty string indicates no encryption for the
+ result.
+ </desc>
+ </param>
+ <param name="newPassword" type="wstring" dir="in">
+ <desc>
+ The new password the medium should be protected with. An empty password and password ID
+ will result in the medium being encrypted with the current password.
+ </desc>
+ </param>
+ <param name="newPasswordId" type="wstring" dir="in">
+ <desc>The ID of the new password when unlocking the medium.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="getEncryptionSettings" const="yes" wrap-hint-server="passcaller">
+ <rest request="get" path="/mediums/{mediumid}/configuration/"/>
+ <desc>
+ Returns the encryption settings for this medium.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Encryption is not configured for this medium.
+ </result>
+ </desc>
+ <param name="cipher" type="wstring" dir="out">
+ <desc>The cipher used for encryption.</desc>
+ </param>
+ <param name="passwordId" type="wstring" dir="return">
+ <desc>The ID of the password when unlocking the medium.</desc>
+ </param>
+ </method>
+
+ <method name="checkEncryptionPassword" const="yes">
+ <rest request="get" path="/mediums/{mediumid}/configuration/"/>
+ <desc>
+ Checks whether the supplied password is correct for the medium.
+
+ <result name="VBOX_E_NOT_SUPPORTED">
+ Encryption is not configured for this medium.
+ </result>
+ <result name="VBOX_E_PASSWORD_INCORRECT">
+ The given password is incorrect.
+ </result>
+ </desc>
+ <param name="password" type="wstring" dir="in">
+ <desc>The password to check.</desc>
+ </param>
+ </method>
+
+ <method name="openForIO">
+ <desc>
+ Open the medium for I/O.
+ </desc>
+ <param name="writable" type="boolean" dir="in">
+ <desc>Set this to open the medium for both reading and writing. When
+ not set the medium is opened readonly.</desc>
+ </param>
+ <param name="password" type="wstring" dir="in">
+ <desc>Password for accessing an encrypted medium. Must be empty if not encrypted.</desc>
+ </param>
+ <param name="mediumIO" type="IMediumIO" dir="return">
+ <desc>Medium I/O object.</desc>
+ </param>
+ </method>
+
+ <method name="resizeAndCloneTo">
+ <rest request="post" path="/mediums/{mediumid}/actions/"/>
+
+ <desc>
+ This is a helper function that combines the functionality of
+ <link to="IMedium::cloneTo"/> and <link to="IMedium::resize"/>. The target medium will take the
+ contents of the calling medium.
+ <result name="E_NOTIMPL">
+ The specified cloning variant is not supported at the moment.
+ </result>
+
+ <note>
+ Method will be moved to appropriate location at the next major API release.
+ </note>
+ </desc>
+
+ <param name="target" type="IMedium" dir="in">
+ <desc>Target medium.</desc>
+ </param>
+ <param name="logicalSize" type="long long" dir="in">
+ <desc>New nominal capacity of the medium in bytes.</desc>
+ </param>
+ <param name="variant" type="MediumVariant" safearray="yes" dir="in">
+ <desc>Exact image variant which should be created (as a combination of
+ <link to="MediumVariant" /> flags).</desc>
+ </param>
+ <param name="parent" type="IMedium" dir="in">
+ <desc>Parent of the cloned medium.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+
+ <!--
+ // IMediumFormat
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="DataType"
+ uuid="d90ea51e-a3f1-4a01-beb1-c1723c0d3ba7"
+ >
+ <const name="Int32" value="0"/>
+ <const name="Int8" value="1"/>
+ <const name="String" value="2"/>
+ </enum>
+
+ <enum
+ name="DataFlags"
+ uuid="86884dcf-1d6b-4f1b-b4bf-f5aa44959d60"
+ >
+ <const name="None" value="0x00"/>
+ <const name="Mandatory" value="0x01"/>
+ <const name="Expert" value="0x02"/>
+ <const name="Array" value="0x04"/>
+ <const name="FlagMask" value="0x07"/>
+ </enum>
+
+ <enum
+ name="MediumFormatCapabilities"
+ uuid="7342ba79-7ce0-4d94-8f86-5ed5a185d9bd"
+ >
+ <desc>
+ Medium format capability flags.
+ </desc>
+
+ <const name="Uuid" value="0x01">
+ <desc>
+ Supports UUIDs as expected by VirtualBox code.
+ </desc>
+ </const>
+
+ <const name="CreateFixed" value="0x02">
+ <desc>
+ Supports creating fixed size images, allocating all space instantly.
+ </desc>
+ </const>
+
+ <const name="CreateDynamic" value="0x04">
+ <desc>
+ Supports creating dynamically growing images, allocating space on
+ demand.
+ </desc>
+ </const>
+
+ <const name="CreateSplit2G" value="0x08">
+ <desc>
+ Supports creating images split in chunks of a bit less than 2 GBytes.
+ </desc>
+ </const>
+
+ <const name="Differencing" value="0x10">
+ <desc>
+ Supports being used as a format for differencing media (see <link
+ to="IMedium::createDiffStorage"/>).
+ </desc>
+ </const>
+
+ <const name="Asynchronous" value="0x20">
+ <desc>
+ Supports asynchronous I/O operations for at least some configurations.
+ </desc>
+ </const>
+
+ <const name="File" value="0x40">
+ <desc>
+ The format backend operates on files (the <link to="IMedium::location"/>
+ attribute of the medium specifies a file used to store medium
+ data; for a list of supported file extensions see
+ <link to="IMediumFormat::describeFileExtensions"/>).
+ </desc>
+ </const>
+
+ <const name="Properties" value="0x80">
+ <desc>
+ The format backend uses the property interface to configure the storage
+ location and properties (the <link to="IMediumFormat::describeProperties"/>
+ method is used to get access to properties supported by the given medium format).
+ </desc>
+ </const>
+
+ <const name="TcpNetworking" value="0x100">
+ <desc>
+ The format backend uses the TCP networking interface for network access.
+ </desc>
+ </const>
+
+ <const name="VFS" value="0x200">
+ <desc>
+ The format backend supports virtual filesystem functionality.
+ </desc>
+ </const>
+
+ <const name="Discard" value="0x400">
+ <desc>
+ The format backend supports discarding blocks.
+ </desc>
+ </const>
+
+ <const name="Preferred" value="0x800">
+ <desc>
+ Indicates that this is a frequently used format backend.
+ </desc>
+ </const>
+
+ <const name="CapabilityMask" value="0xFFF"/>
+ </enum>
+
+ <interface
+ name="IMediumFormat" extends="$unknown"
+ uuid="11be93c7-a862-4dc9-8c89-bf4ba74a886a"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="2" reservedAttributes="4"
+ >
+ <desc>
+ The IMediumFormat interface represents a medium format.
+
+ Each medium format has an associated backend which is used to handle
+ media stored in this format. This interface provides information
+ about the properties of the associated backend.
+
+ Each medium format is identified by a string represented by the
+ <link to="#id"/> attribute. This string is used in calls like
+ <link to="IVirtualBox::createMedium"/> to specify the desired
+ format.
+
+ The list of all supported medium formats can be obtained using
+ <link to="ISystemProperties::mediumFormats"/>.
+
+ <see><link to="IMedium"/></see>
+ </desc>
+
+ <attribute name="id" type="wstring" readonly="yes">
+ <desc>
+ Identifier of this format.
+
+ The format identifier is a non-@c null non-empty ASCII string. Note that
+ this string is case-insensitive. This means that, for example, all of
+ the following strings:
+ <pre>
+ "VDI"
+ "vdi"
+ "VdI"</pre>
+ refer to the same medium format.
+
+ This string is used in methods of other interfaces where it is necessary
+ to specify a medium format, such as
+ <link to="IVirtualBox::createMedium"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>
+ Human readable description of this format.
+
+ Mainly for use in file open dialogs.
+ </desc>
+ </attribute>
+
+ <attribute name="capabilities" type="MediumFormatCapabilities" safearray="yes" readonly="yes">
+ <desc>
+ Capabilities of the format as an array of the flags.
+
+ For the meaning of individual capability flags see
+ <link to="MediumFormatCapabilities"/>.
+ </desc>
+ </attribute>
+
+ <method name="describeFileExtensions" const="yes">
+ <desc>
+ Returns two arrays describing the supported file extensions.
+
+ The first array contains the supported extensions and the seconds one
+ the type each extension supports. Both have the same size.
+
+ Note that some backends do not work on files, so this array may be
+ empty.
+
+ <see><link to="IMediumFormat::capabilities"/></see>
+ </desc>
+ <param name="extensions" type="wstring" safearray="yes" dir="out">
+ <desc>The array of supported extensions.</desc>
+ </param>
+ <param name="types" type="DeviceType" safearray="yes" dir="out">
+ <desc>The array which indicates the device type for every given extension.</desc>
+ </param>
+ </method>
+
+ <method name="describeProperties" const="yes">
+ <desc>
+ Returns several arrays describing the properties supported by this
+ format.
+
+ An element with the given index in each array describes one
+ property. Thus, the number of elements in each returned array is the
+ same and corresponds to the number of supported properties.
+
+ The returned arrays are filled in only if the
+ <link to="MediumFormatCapabilities_Properties"/> flag is set.
+ All arguments must be non-@c null.
+
+ <see><link to="DataType"/>, <link to="DataFlags"/></see>
+ </desc>
+
+ <param name="names" type="wstring" safearray="yes" dir="out">
+ <desc>Array of property names.</desc>
+ </param>
+ <param name="descriptions" type="wstring" safearray="yes" dir="out">
+ <desc>Array of property descriptions.</desc>
+ </param>
+ <param name="types" type="DataType" safearray="yes" dir="out">
+ <desc>Array of property types.</desc>
+ </param>
+ <param name="flags" type="unsigned long" safearray="yes" dir="out">
+ <desc>Array of property flags.</desc>
+ </param>
+ <param name="defaults" type="wstring" safearray="yes" dir="out">
+ <desc>Array of default property values.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <!--
+ // IDataStream
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IDataStream" extends="$unknown"
+ uuid="a338ed20-58d9-43ae-8b03-c1fd7088ef15"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ The IDataStream interface is used to retrieve a data stream. It is
+ returned by <link to="IMediumIO::convertToStream"/>.
+ </desc>
+
+ <attribute name="readSize" type="unsigned long" readonly="yes">
+ <desc>Recommended size of a read. Requesting a larger read may be
+ possible in certain situations, but it is not guaranteed.</desc>
+ </attribute>
+
+ <method name="read">
+ <desc>
+ Read data from the stream.
+ <result name="VBOX_E_TIMEOUT">
+ Waiting time has expired.
+ </result>
+ </desc>
+ <param name="size" type="unsigned long" dir="in">
+ <desc>How many bytes to try read.</desc>
+ </param>
+ <param name="timeoutMS" type="unsigned long" dir="in">
+ <desc>
+ Timeout (in ms) for limiting the wait time for data to be available.
+ Pass 0 for an infinite timeout.
+ </desc>
+ </param>
+ <param name="data" type="octet" dir="return" safearray="yes">
+ <desc>Array of data read. This may be shorter than the specified size.
+ Returning a zero-sized array indicates the end of the stream, if the
+ status is successful.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <!--
+ // IMediumIO
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="PartitionTableType"
+ uuid="360066eb-d19e-4fa1-57ef-fed434fbe2a9"
+ >
+ <desc>
+ Partition table types.
+ </desc>
+
+ <const name="MBR" value="1"/>
+ <const name="GPT" value="2"/>
+ </enum>
+
+
+ <interface
+ name="IMediumIO" extends="$unknown"
+ uuid="e4b301a9-5f86-4d65-ad1b-87ca284fb1c8"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ The IMediumIO interface is used to access and modify the content of a
+ medium. It is returned by <link to="IMedium::openForIO"/>.
+ </desc>
+
+ <attribute name="medium" type="IMedium" readonly="yes">
+ <desc>The open medium.</desc>
+ </attribute>
+
+ <attribute name="writable" type="boolean" readonly="yes">
+ <desc>Whether the medium can be written to. (It can always be read from.)</desc>
+ </attribute>
+
+ <attribute name="explorer" type="IVFSExplorer" readonly="yes">
+ <desc>
+ Returns the virtual file system explorer for the medium.
+
+ This will attempt to recognize the format of the medium content and
+ present it as a virtual directory structure to the API user.
+
+ A FAT floppy image will be represented will a single root subdir 'fat12'
+ that gives access to the file system content.
+
+ A ISO-9660 image will have one subdir in the root for each format present
+ in the image, so the API user can select which data view to access (iso9660,
+ rockridge, joliet, udf, hfs, ...).
+
+ A partitioned harddisk image will have subdirs for each partition. The
+ the filesystem content of each partition can be accessed thru the subdirs
+ if we have a file system interpreter for it. There will also be raw files
+ for each subdirectory, to provide a simple way of accessing raw partition
+ data from an API client.
+
+ Please note that the explorer may show inconsistent information if
+ the API user modifies the raw image content after it was opened.
+ </desc>
+ </attribute>
+
+ <method name="read">
+ <desc>
+ Read data from the medium.
+ </desc>
+ <param name="offset" type="long long" dir="in">
+ <desc>The byte offset into the medium to start reading at.</desc>
+ </param>
+ <param name="size" type="unsigned long" dir="in">
+ <desc>How many bytes to try read.</desc>
+ </param>
+ <param name="data" type="octet" dir="return" safearray="yes">
+ <desc>Array of data read. This may be shorter than the specified size.</desc>
+ </param>
+ </method>
+
+ <method name="write">
+ <desc>
+ Write data to the medium.
+ </desc>
+ <param name="offset" type="long long" dir="in">
+ <desc>The byte offset into the medium to start reading at.</desc>
+ </param>
+ <param name="data" type="octet" dir="in" safearray="yes">
+ <desc>Array of data to write.</desc>
+ </param>
+ <param name="written" type="unsigned long" dir="return">
+ <desc>How many bytes were actually written.</desc>
+ </param>
+ </method>
+
+ <method name="formatFAT">
+ <desc>
+ Formats the medium as FAT. Generally only useful for floppy images as
+ no partition table will be created.
+ </desc>
+ <param name="quick" type="boolean" dir="in">
+ <desc>Quick format it when set.</desc>
+ </param>
+ </method>
+
+ <method name="initializePartitionTable">
+ <desc>
+ Writes an empty partition table to the disk.
+ </desc>
+ <param name="format" type="PartitionTableType" dir="in">
+ <desc>The partition table format.</desc>
+ </param>
+ <param name="wholeDiskInOneEntry" type="boolean" dir="in">
+ <desc>
+ When @c true a partition table entry for the whole disk is created.
+ Otherwise the partition table is empty.
+ </desc>
+ </param>
+ </method>
+
+ <method name="convertToStream">
+ <desc>
+ Converts the currently opened image into a stream of the specified
+ image type/variant. It is sufficient to open the image in read-only
+ mode. Only few types and variants are supported due to the inherent
+ restrictions of the output style.
+ <result name="VBOX_E_NOT_SUPPORTED">
+ The requested format/variant combination cannot handle stream output.
+ </result>
+ <result name="VBOX_E_FILE_ERROR">
+ An error occurred during the conversion.
+ </result>
+ </desc>
+ <param name="format" type="wstring" dir="in">
+ <desc>Identifier of the storage format to use for output.</desc>
+ </param>
+ <param name="variant" type="MediumVariant" safearray="yes" dir="in">
+ <desc>The partition table format.</desc>
+ </param>
+ <param name="bufferSize" type="unsigned long" dir="in">
+ <desc>
+ Requested buffer size (in bytes) for efficient conversion. Sizes
+ which are too small or too large are silently truncated to suitable
+ values. Tens to hundreds of Megabytes are a good choice.
+ </desc>
+ </param>
+ <param name="stream" type="IDataStream" dir="out">
+ <desc>Data stream object for reading the target image.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="close">
+ <desc>
+ Explictly close the medium I/O rather than waiting for garbage
+ collection and the destructor.
+
+ This will wait for any pending reads and writes to complete and then
+ close down the I/O access without regard for open explorer instances or
+ anything like that.
+ </desc>
+ </method>
+
+ </interface>
+
+
+ <!--
+ // IToken
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IToken" extends="$unknown"
+ uuid="20479eaf-d8ed-44cf-85ac-c83a26c95a4d"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="1" reservedAttributes="2"
+ >
+ <desc>
+ The IToken interface represents a token passed to an API client, which
+ triggers cleanup actions when it is explicitly released by calling the
+ <link to="#abandon"/> method (preferred, as it is accurately defined
+ when the release happens), or when the object reference count drops
+ to 0. The latter way is implicitly used when an API client crashes,
+ however the discovery that there was a crash can take rather long,
+ depending on the platform (COM needs 6 minutes). So better don't rely
+ on the crash behavior too much.
+ </desc>
+
+ <method name="abandon" wrap-hint-server="passcaller">
+ <desc>Releases this token. Cannot be undone in any way, and makes the
+ token object unusable (even the <link to="#dummy"/> method will return
+ an error), ready for releasing. It is a more defined way than just
+ letting the reference count drop to 0, because the latter (depending
+ on the platform) can trigger asynchronous cleanup activity.
+ </desc>
+ </method>
+
+ <method name="dummy">
+ <desc>Purely a NOOP. Useful when using proxy type API bindings (e.g. the
+ webservice) which manage objects on behalf of the actual client, using
+ an object reference expiration time based garbage collector.
+ </desc>
+ </method>
+
+ </interface>
+
+
+ <!--
+ // IKeyboard
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="KeyboardLED"
+ uuid="ef29ea38-409b-49c7-a817-c858d426dfba"
+ >
+ <desc>
+ Keyboard LED indicators.
+ </desc>
+
+ <const name="NumLock" value="0x01"/>
+ <const name="CapsLock" value="0x02"/>
+ <const name="ScrollLock" value="0x04"/>
+ </enum>
+
+ <interface
+ name="IKeyboard" extends="$unknown"
+ uuid="755e6bdf-1640-41f9-bd74-3ef5fd653250"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="4"
+ >
+ <desc>
+ The IKeyboard interface represents the virtual machine's keyboard. Used
+ in <link to="IConsole::keyboard"/>.
+
+ Use this interface to send keystrokes or the Ctrl-Alt-Del sequence
+ to the virtual machine.
+ </desc>
+
+ <attribute name="keyboardLEDs" type="KeyboardLED" safearray="yes" readonly="yes">
+ <desc>
+ Current status of the guest keyboard LEDs.
+ </desc>
+ </attribute>
+
+ <method name="putScancode">
+ <desc>Sends a scancode to the keyboard.
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Could not send scan code to virtual keyboard.
+ </result>
+
+ </desc>
+ <param name="scancode" type="long" dir="in"/>
+ </method>
+
+ <method name="putScancodes">
+ <desc>Sends an array of scancodes to the keyboard.
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Could not send all scan codes to virtual keyboard.
+ </result>
+
+ </desc>
+ <param name="scancodes" type="long" dir="in" safearray="yes"/>
+ <param name="codesStored" type="unsigned long" dir="return"/>
+ </method>
+
+ <method name="putCAD">
+ <desc>Sends the Ctrl-Alt-Del sequence to the keyboard. This
+ function is nothing special, it is just a convenience function
+ calling <link to="IKeyboard::putScancodes"/> with the proper scancodes.
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Could not send all scan codes to virtual keyboard.
+ </result>
+
+ </desc>
+ </method>
+
+ <method name="releaseKeys">
+ <desc>Causes the virtual keyboard to release any keys which are
+ currently pressed. Useful when host and guest keyboard may be out
+ of sync.
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Could not release some or all keys.
+ </result>
+
+ </desc>
+ </method>
+
+ <method name="putUsageCode">
+ <desc>Sends a USB HID usage code and page to the keyboard. The
+ keyRelease flag is set when the key is being released.
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Could not send usage code to virtual keyboard.
+ </result>
+
+ </desc>
+ <param name="usageCode" type="long" dir="in"/>
+ <param name="usagePage" type="long" dir="in"/>
+ <param name="keyRelease" type="boolean" dir="in"/>
+ </method>
+
+ <attribute name="eventSource" type="IEventSource" readonly="yes">
+ <desc>
+ Event source for keyboard events.
+ </desc>
+ </attribute>
+
+ </interface>
+
+
+ <!--
+ // IMouse
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="MouseButtonState"
+ uuid="9ee094b8-b28a-4d56-a166-973cb588d7f8"
+ >
+ <desc>
+ Mouse button state.
+ </desc>
+
+ <const name="LeftButton" value="0x01"/>
+ <const name="RightButton" value="0x02"/>
+ <const name="MiddleButton" value="0x04"/>
+ <const name="WheelUp" value="0x08"/>
+ <const name="WheelDown" value="0x10"/>
+ <const name="XButton1" value="0x20"/>
+ <const name="XButton2" value="0x40"/>
+ <const name="MouseStateMask" value="0x7F"/>
+ </enum>
+
+ <enum
+ name="TouchContactState"
+ uuid="3f942686-2506-421c-927c-90d4b45f4a38"
+ >
+ <desc>
+ Touch event contact state.
+ </desc>
+
+ <const name="None" value="0x00">
+ <desc>The touch has finished.</desc>
+ </const>
+ <const name="InContact" value="0x01">
+ <desc>Whether the touch is really touching the device.</desc>
+ </const>
+ <const name="InRange" value="0x02">
+ <desc>
+ Whether the touch is close enough to the device to be detected.
+ </desc>
+ </const>
+ <const name="ContactStateMask" value="0x03"/>
+ </enum>
+
+ <interface
+ name="IMousePointerShape" extends="$unknown"
+ uuid="1e775ea3-9070-4f9c-b0d5-53054496dbe0"
+ wsmap="managed"
+ rest="managed"
+ reservedAttributes="4"
+ >
+ <desc>
+ The guest mouse pointer description.
+ </desc>
+
+ <attribute name="visible" type="boolean" readonly="yes">
+ <desc>
+ Flag whether the pointer is visible.
+ </desc>
+ </attribute>
+ <attribute name="alpha" type="boolean" readonly="yes">
+ <desc>
+ Flag whether the pointer has an alpha channel.
+ </desc>
+ </attribute>
+ <attribute name="hotX" type="unsigned long" readonly="yes">
+ <desc>
+ The pointer hot spot X coordinate.
+ </desc>
+ </attribute>
+ <attribute name="hotY" type="unsigned long" readonly="yes">
+ <desc>
+ The pointer hot spot Y coordinate.
+ </desc>
+ </attribute>
+ <attribute name="width" type="unsigned long" readonly="yes">
+ <desc>
+ Width of the pointer shape in pixels.
+ </desc>
+ </attribute>
+ <attribute name="height" type="unsigned long" readonly="yes">
+ <desc>
+ Height of the pointer shape in pixels.
+ </desc>
+ </attribute>
+ <attribute name="shape" type="octet" safearray="yes" readonly="yes">
+ <desc>
+ Shape bitmaps.
+
+ The @a shape buffer contains a 1bpp (bits per pixel) AND mask
+ followed by a 32bpp XOR (color) mask.
+
+ For pointers without alpha channel the XOR mask pixels are
+ 32 bit values: (lsb)BGR0(msb). For pointers with alpha channel
+ the XOR mask consists of (lsb)BGRA(msb) 32 bit values.
+
+ An AND mask is provided for pointers with alpha channel, so if the
+ client does not support alpha, the pointer could be
+ displayed as a normal color pointer.
+
+ The AND mask is a 1bpp bitmap with byte aligned scanlines. The
+ size of the AND mask therefore is <tt>cbAnd = (width + 7) / 8 *
+ height</tt>. The padding bits at the end of each scanline are
+ undefined.
+
+ The XOR mask follows the AND mask on the next 4-byte aligned
+ offset: <tt>uint8_t *pu8Xor = pu8And + (cbAnd + 3) &amp; ~3</tt>.
+ Bytes in the gap between the AND and the XOR mask are undefined.
+ The XOR mask scanlines have no gap between them and the size of
+ the XOR mask is: <tt>cbXor = width * 4 * height</tt>.
+
+ <note>
+ If @a shape size is 0, then the shape is not known or did not change.
+ This can happen if only the pointer visibility is changed.
+ </note>
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IMouse" extends="$unknown"
+ uuid="25360a74-55e5-4f14-ac2a-f5cf8e62e4af"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="4"
+ >
+ <desc>
+ The IMouse interface represents the virtual machine's mouse. Used in
+ <link to="IConsole::mouse"/>.
+
+ Through this interface, the virtual machine's virtual mouse can be
+ controlled.
+ </desc>
+
+ <attribute name="absoluteSupported" type="boolean" readonly="yes">
+ <desc>
+ Whether the guest OS supports absolute mouse pointer positioning
+ or not.
+ <note>
+ You can use the <link to="IMouseCapabilityChangedEvent"/>
+ event to be instantly informed about changes of this attribute
+ during virtual machine execution.
+ </note>
+ <see><link to="#putMouseEventAbsolute"/></see>
+ </desc>
+ </attribute>
+
+ <attribute name="relativeSupported" type="boolean" readonly="yes">
+ <desc>
+ Whether the guest OS supports relative mouse pointer positioning
+ or not.
+ <note>
+ You can use the <link to="IMouseCapabilityChangedEvent"/>
+ event to be instantly informed about changes of this attribute
+ during virtual machine execution.
+ </note>
+ <see><link to="#putMouseEvent"/></see>
+ </desc>
+ </attribute>
+
+ <attribute name="touchScreenSupported" type="boolean" readonly="yes">
+ <desc>
+ Whether the guest OS has enabled the multi-touch reporting device,
+ touchscreen variant.
+ <note>
+ You can use the <link to="IMouseCapabilityChangedEvent"/>
+ event to be instantly informed about changes of this attribute
+ during virtual machine execution.
+ </note>
+ <see><link to="#putMouseEvent"/></see>
+ </desc>
+ </attribute>
+
+ <attribute name="touchPadSupported" type="boolean" readonly="yes">
+ <desc>
+ Whether the guest OS has enabled the multi-touch reporting device,
+ touchpad variant.
+ <note>
+ You can use the <link to="IMouseCapabilityChangedEvent"/>
+ event to be instantly informed about changes of this attribute
+ during virtual machine execution.
+ </note>
+ <see><link to="#putMouseEvent"/></see>
+ </desc>
+ </attribute>
+
+ <attribute name="needsHostCursor" type="boolean" readonly="yes">
+ <desc>
+ Whether the guest OS can currently switch to drawing it's own mouse
+ cursor on demand.
+ <note>
+ You can use the <link to="IMouseCapabilityChangedEvent"/>
+ event to be instantly informed about changes of this attribute
+ during virtual machine execution.
+ </note>
+ <see><link to="#putMouseEvent"/></see>
+ </desc>
+ </attribute>
+
+ <attribute name="pointerShape" type="IMousePointerShape" readonly="yes">
+ <desc>
+ The current mouse pointer used by the guest.
+ </desc>
+ </attribute>
+
+ <method name="putMouseEvent">
+ <desc>
+ Initiates a mouse event using relative pointer movements
+ along x and y axis.
+
+ <result name="E_ACCESSDENIED">
+ Console not powered up.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ Could not send mouse event to virtual mouse.
+ </result>
+
+ </desc>
+
+ <param name="dx" type="long" dir="in">
+ <desc>
+ Amount of pixels the mouse should move to the right.
+ Negative values move the mouse to the left.
+ </desc>
+ </param>
+ <param name="dy" type="long" dir="in">
+ <desc>
+ Amount of pixels the mouse should move downwards.
+ Negative values move the mouse upwards.
+ </desc>
+ </param>
+ <param name="dz" type="long" dir="in">
+ <desc>
+ Amount of mouse wheel moves.
+ Positive values describe clockwise wheel rotations,
+ negative values describe counterclockwise rotations.
+ </desc>
+ </param>
+ <param name="dw" type="long" dir="in">
+ <desc>
+ Amount of horizontal mouse wheel moves.
+ Positive values describe a movement to the left,
+ negative values describe a movement to the right.
+ </desc>
+ </param>
+ <param name="buttonState" type="long" dir="in">
+ <desc>
+ The current state of mouse buttons. Every bit represents
+ a mouse button as follows:
+ <dl>
+ <dt>Bit 0 (<tt>0x01</tt>)</dt><dd>left mouse button</dd>
+ <dt>Bit 1 (<tt>0x02</tt>)</dt><dd>right mouse button</dd>
+ <dt>Bit 2 (<tt>0x04</tt>)</dt><dd>middle mouse button</dd>
+ </dl>
+ A value of @c 1 means the corresponding button is pressed.
+ otherwise it is released.
+ </desc>
+ </param>
+ </method>
+
+ <method name="putMouseEventAbsolute">
+ <desc>
+ Positions the mouse pointer using absolute x and y coordinates.
+ These coordinates are expressed in pixels and
+ start from <tt>[1,1]</tt> which corresponds to the top left
+ corner of the virtual display. The values <tt>[-1,-1]</tt> and
+ <tt>[0x7fffffff,0x7fffffff]</tt> have special meanings as
+ respectively "no data" (to signal that the host wishes to report
+ absolute pointer data in future) and "out of range" (the host
+ pointer is outside of all guest windows).
+
+ <result name="E_ACCESSDENIED">
+ Console not powered up.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ Could not send mouse event to virtual mouse.
+ </result>
+
+ <note>
+ This method will have effect only if absolute mouse
+ positioning is supported by the guest OS.
+ </note>
+
+ <see><link to="#absoluteSupported"/></see>
+ </desc>
+
+ <param name="x" type="long" dir="in">
+ <desc>
+ X coordinate of the pointer in pixels, starting from @c 1.
+ </desc>
+ </param>
+ <param name="y" type="long" dir="in">
+ <desc>
+ Y coordinate of the pointer in pixels, starting from @c 1.
+ </desc>
+ </param>
+ <param name="dz" type="long" dir="in">
+ <desc>
+ Amount of mouse wheel moves.
+ Positive values describe clockwise wheel rotations,
+ negative values describe counterclockwise rotations.
+ </desc>
+ </param>
+ <param name="dw" type="long" dir="in">
+ <desc>
+ Amount of horizontal mouse wheel moves.
+ Positive values describe a movement to the left,
+ negative values describe a movement to the right.
+ </desc>
+ </param>
+ <param name="buttonState" type="long" dir="in">
+ <desc>
+ The current state of mouse buttons. Every bit represents
+ a mouse button as follows:
+ <dl>
+ <dt>Bit 0 (<tt>0x01</tt>)</dt><dd>left mouse button</dd>
+ <dt>Bit 1 (<tt>0x02</tt>)</dt><dd>right mouse button</dd>
+ <dt>Bit 2 (<tt>0x04</tt>)</dt><dd>middle mouse button</dd>
+ </dl>
+ A value of @c 1 means the corresponding button is pressed.
+ otherwise it is released.
+ </desc>
+ </param>
+ </method>
+
+ <method name="putEventMultiTouch">
+ <desc>
+ Sends a multi-touch pointer event. For touchscreen events the
+ coordinates are expressed in pixels and start from <tt>[1,1]</tt> which
+ corresponds to the top left corner of the virtual display, for
+ touchpad events the coordinates are normalized to the range 0..0xffff.
+
+ <result name="E_ACCESSDENIED">
+ Console not powered up.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ Could not send event to virtual device.
+ </result>
+
+ <note>
+ The guest may not understand or may choose to ignore this event.
+ </note>
+
+ <see><link to="#touchScreenSupported"/> and <link to="#touchPadSupported"/></see>
+ </desc>
+
+ <param name="count" type="long" dir="in">
+ <desc>
+ Number of contacts in the event.
+ </desc>
+ </param>
+
+ <param name="contacts" type="long long" dir="in" safearray="yes">
+ <desc>
+ Each array element contains packed information about one contact.
+ Bits 0..15: X coordinate in pixels or normalized X position.
+ Bits 16..31: Y coordinate in pixels or normalized Y position.
+ Bits 32..39: contact identifier.
+ Bit 40: "in contact" flag, which indicates that there is a contact with the touch surface.
+ Bit 41: "in range" flag, the contact is close enough to the touch surface.
+ All other bits are reserved for future use and must be set to 0.
+ </desc>
+ </param>
+
+ <param name="isTouchScreen" type="boolean" dir="in">
+ <desc>
+ Distinguishes between touchscreen and touchpad events.
+ </desc>
+ </param>
+
+ <param name="scanTime" type="unsigned long" dir="in">
+ <desc>
+ Timestamp of the event in milliseconds. Only relative time between events is important.
+ </desc>
+ </param>
+ </method>
+
+ <method name="putEventMultiTouchString">
+ <desc>
+ <see><link to="#putEventMultiTouch"/></see>
+ </desc>
+
+ <param name="count" type="long" dir="in">
+ <desc>
+ <see><link to="#putEventMultiTouch"/></see>
+ </desc>
+ </param>
+
+ <param name="contacts" type="wstring" dir="in">
+ <desc>
+ Contains information about all contacts:
+ "id1,x1,y1,inContact1,inRange1;...;idN,xN,yN,inContactN,inRangeN".
+ For example for two contacts: "0,10,20,1,1;1,30,40,1,1"
+ </desc>
+ </param>
+
+ <param name="isTouchScreen" type="boolean" dir="in">
+ <desc>
+ Distinguishes between touchscreen and touchpad events.
+ </desc>
+ </param>
+
+ <param name="scanTime" type="unsigned long" dir="in">
+ <desc>
+ <see><link to="#putEventMultiTouch"/></see>
+ </desc>
+ </param>
+ </method>
+
+ <attribute name="eventSource" type="IEventSource" readonly="yes">
+ <desc>
+ Event source for mouse events.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <!--
+ // IDisplay
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IDisplaySourceBitmap" extends="$unknown"
+ uuid="5094f67a-8084-11e9-b185-dbe296e54799"
+ wsmap="suppress"
+ rest="managed"
+ >
+ <attribute name="screenId" type="unsigned long" readonly="yes"/>
+ <method name="queryBitmapInfo">
+ <desc>Information about the screen bitmap.</desc>
+ <param name="address" type="octet" mod="ptr" dir="out"/>
+ <param name="width" type="unsigned long" dir="out"/>
+ <param name="height" type="unsigned long" dir="out"/>
+ <param name="bitsPerPixel" type="unsigned long" dir="out"/>
+ <param name="bytesPerLine" type="unsigned long" dir="out"/>
+ <param name="bitmapFormat" type="BitmapFormat" dir="out"/>
+ </method>
+
+ </interface>
+
+ <enum
+ name="FramebufferCapabilities"
+ uuid="cc395839-30fa-4ca5-ae65-e6360e3edd7a"
+ >
+ <desc>
+ Framebuffer capability flags.
+ </desc>
+
+ <const name="UpdateImage" value="0x01">
+ <desc>
+ Requires NotifyUpdateImage. NotifyUpdate must not be called.
+ </desc>
+ </const>
+
+ <const name="VHWA" value="0x02">
+ <desc>
+ Supports VHWA interface. If set, then
+ IFramebuffer::processVHWACommand can be called. <!-- no link, otherwise trouble with javadoc -->
+ </desc>
+ </const>
+
+ <const name="VisibleRegion" value="0x04">
+ <desc>
+ Supports visible region. If set, then
+ IFramebuffer::setVisibleRegion can be called. <!-- no link, otherwise trouble with javadoc -->
+ </desc>
+ </const>
+
+ <const name="RenderCursor" value="0x08">
+ <desc>
+ This framebuffer implementation can render a pointer cursor itself. Unless the
+ MoveCursor capability is also set the cursor will always be rendered at the
+ location of (and usually using) the host pointer.
+ </desc>
+ </const>
+
+ <const name="MoveCursor" value="0x10">
+ <desc>
+ Supports rendering a pointer cursor anywhere within the guest screen. Implies
+ RenderCursor.
+ </desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IFramebuffer" extends="$unknown"
+ uuid="1e8d3f27-b45c-48ae-8b36-d35e83d207aa"
+ wsmap="managed"
+ >
+ <attribute name="width" type="unsigned long" readonly="yes">
+ <desc>Frame buffer width, in pixels.</desc>
+ </attribute>
+
+ <attribute name="height" type="unsigned long" readonly="yes">
+ <desc>Frame buffer height, in pixels.</desc>
+ </attribute>
+
+ <attribute name="bitsPerPixel" type="unsigned long" readonly="yes">
+ <desc>
+ Color depth, in bits per pixel.
+ </desc>
+ </attribute>
+
+ <attribute name="bytesPerLine" type="unsigned long" readonly="yes">
+ <desc>
+ Scan line size, in bytes.
+ </desc>
+ </attribute>
+
+ <attribute name="pixelFormat" type="BitmapFormat" readonly="yes">
+ <desc>
+ Frame buffer pixel format. It's one of the values defined by <link
+ to="BitmapFormat"/>.
+ <note>
+ This attribute must never (and will never) return <link
+ to="BitmapFormat_Opaque"/> -- the format of the frame
+ buffer must be always known.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="heightReduction" type="unsigned long" readonly="yes">
+ <desc>
+ Hint from the frame buffer about how much of the standard
+ screen height it wants to use for itself. This information is
+ exposed to the guest through the VESA BIOS and VMMDev interface
+ so that it can use it for determining its video mode table. It
+ is not guaranteed that the guest respects the value.
+ </desc>
+ </attribute>
+
+ <attribute name="overlay" type="IFramebufferOverlay" readonly="yes">
+ <desc>
+ An alpha-blended overlay which is superposed over the frame buffer.
+ The initial purpose is to allow the display of icons providing
+ information about the VM state, including disk activity, in front
+ ends which do not have other means of doing that. The overlay is
+ designed to controlled exclusively by IDisplay. It has no locking
+ of its own, and any changes made to it are not guaranteed to be
+ visible until the affected portion of IFramebuffer is updated. The
+ overlay can be created lazily the first time it is requested. This
+ attribute can also return @c null to signal that the overlay is not
+ implemented.
+ </desc>
+ </attribute>
+
+ <attribute name="winId" type="long long" readonly="yes" wsmap="suppress">
+ <desc>
+ Platform-dependent identifier of the window where context of this
+ frame buffer is drawn, or zero if there's no such window.
+ </desc>
+ </attribute>
+
+ <attribute name="capabilities" type="FramebufferCapabilities" safearray="yes" readonly="yes">
+ <desc>
+ Capabilities of the framebuffer instance.
+
+ For the meaning of individual capability flags see
+ <link to="FramebufferCapabilities"/>.
+ </desc>
+ </attribute>
+
+ <method name="notifyUpdate">
+ <desc>
+ Informs about an update.
+ Gets called by the display object where this buffer is
+ registered.
+ </desc>
+ <param name="x" type="unsigned long" dir="in">
+ <desc>X position of update.</desc>
+ </param>
+ <param name="y" type="unsigned long" dir="in">
+ <desc>Y position of update.</desc>
+ </param>
+ <param name="width" type="unsigned long" dir="in">
+ <desc>Width of update.</desc>
+ </param>
+ <param name="height" type="unsigned long" dir="in">
+ <desc>Height of update.</desc>
+ </param>
+ </method>
+
+ <method name="notifyUpdateImage">
+ <desc>
+ Informs about an update and provides 32bpp bitmap.
+ </desc>
+ <param name="x" type="unsigned long" dir="in">
+ <desc>X position of update.</desc>
+ </param>
+ <param name="y" type="unsigned long" dir="in">
+ <desc>Y position of update.</desc>
+ </param>
+ <param name="width" type="unsigned long" dir="in">
+ <desc>Width of update.</desc>
+ </param>
+ <param name="height" type="unsigned long" dir="in">
+ <desc>Height of update.</desc>
+ </param>
+ <param name="image" type="octet" dir="in" safearray="yes">
+ <desc>Array with 32BPP image data.</desc>
+ </param>
+ </method>
+
+ <method name="notifyChange">
+ <desc>
+ Requests a size change.
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>
+ Logical guest screen number.
+ </desc>
+ </param>
+ <param name="xOrigin" type="unsigned long" dir="in">
+ <desc>Location of the screen in the guest.</desc>
+ </param>
+ <param name="yOrigin" type="unsigned long" dir="in">
+ <desc>Location of the screen in the guest.</desc>
+ </param>
+ <param name="width" type="unsigned long" dir="in">
+ <desc>Width of the guest display, in pixels.</desc>
+ </param>
+ <param name="height" type="unsigned long" dir="in">
+ <desc>Height of the guest display, in pixels.</desc>
+ </param>
+ </method>
+
+ <method name="videoModeSupported">
+ <desc>
+ Returns whether the frame buffer implementation is willing to
+ support a given video mode. In case it is not able to render
+ the video mode (or for some reason not willing), it should
+ return @c false. Usually this method is called when the guest
+ asks the VMM device whether a given video mode is supported
+ so the information returned is directly exposed to the guest.
+ It is important that this method returns very quickly.
+ </desc>
+ <param name="width" type="unsigned long" dir="in"/>
+ <param name="height" type="unsigned long" dir="in"/>
+ <param name="bpp" type="unsigned long" dir="in"/>
+ <param name="supported" type="boolean" dir="return"/>
+ </method>
+
+ <method name="getVisibleRegion" wsmap="suppress">
+ <desc>
+ Returns the visible region of this frame buffer.
+
+ If the @a rectangles parameter is @c null then the value of the
+ @a count parameter is ignored and the number of elements necessary to
+ describe the current visible region is returned in @a countCopied.
+
+ If @a rectangles is not @c null but @a count is less
+ than the required number of elements to store region data, the method
+ will report a failure. If @a count is equal or greater than the
+ required number of elements, then the actual number of elements copied
+ to the provided array will be returned in @a countCopied.
+
+ <note>
+ The address of the provided array must be in the process space of
+ this IFramebuffer object.
+ </note>
+ <note>
+ Method not yet implemented.
+ </note>
+ </desc>
+ <param name="rectangles" type="octet" mod="ptr" dir="in">
+ <desc>Pointer to the @c RTRECT array to receive region data.</desc>
+ </param>
+ <param name="count" type="unsigned long" dir="in">
+ <desc>Number of @c RTRECT elements in the @a rectangles array.</desc>
+ </param>
+ <param name="countCopied" type="unsigned long" dir="return">
+ <desc>Number of elements copied to the @a rectangles array.</desc>
+ </param>
+ </method>
+
+ <method name="setVisibleRegion" wsmap="suppress">
+ <desc>
+ Suggests a new visible region to this frame buffer. This region
+ represents the area of the VM display which is a union of regions of
+ all top-level windows of the guest operating system running inside the
+ VM (if the Guest Additions for this system support this
+ functionality). This information may be used by the frontends to
+ implement the seamless desktop integration feature.
+
+ <note>
+ The address of the provided array must be in the process space of
+ this IFramebuffer object.
+ </note>
+ <note>
+ The IFramebuffer implementation must make a copy of the provided
+ array of rectangles.
+ </note>
+ <note>
+ Method not yet implemented.
+ </note>
+ </desc>
+ <param name="rectangles" type="octet" mod="ptr" dir="in">
+ <desc>Pointer to the @c RTRECT array.</desc>
+ </param>
+ <param name="count" type="unsigned long" dir="in">
+ <desc>Number of @c RTRECT elements in the @a rectangles array.</desc>
+ </param>
+ </method>
+
+ <method name="processVHWACommand" wsmap="suppress">
+ <desc>
+ Posts a Video HW Acceleration Command to the frame buffer for processing.
+ The commands used for 2D video acceleration (DDraw surface creation/destroying, blitting, scaling, color conversion, overlaying, etc.)
+ are posted from quest to the host to be processed by the host hardware.
+
+ <note>
+ The address of the provided command must be in the process space of
+ this IFramebuffer object.
+ </note>
+ </desc>
+
+ <param name="command" type="octet" mod="ptr" dir="in">
+ <desc>Pointer to VBOXVHWACMD containing the command to execute.</desc>
+ </param>
+ <param name="enmCmd" type="long" dir="in">
+ <desc>The validated VBOXVHWACMD::enmCmd value from the command.</desc>
+ </param>
+ <param name="fromGuest" type="boolean" dir="in">
+ <desc>Set when the command origins from the guest, clear if host.</desc>
+ </param>
+ </method>
+
+ <method name="notify3DEvent">
+ <desc>
+ Notifies framebuffer about 3D backend event.
+ </desc>
+
+ <param name="type" type="unsigned long" dir="in">
+ <desc>event type. VBOX3D_NOTIFY_TYPE_* in VBoxVideo3D.h</desc>
+ </param>
+ <param name="data" type="octet" dir="in" safearray="yes">
+ <desc>event-specific data, depends on the supplied event type</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IFramebufferOverlay" extends="IFramebuffer"
+ uuid="af398a9a-6b76-4805-8fab-00a9dcf4732b"
+ wsmap="managed"
+ >
+ <desc>
+ The IFramebufferOverlay interface represents an alpha blended overlay
+ for displaying status icons above an IFramebuffer. It is always created
+ not visible, so that it must be explicitly shown. It only covers a
+ portion of the IFramebuffer, determined by its width, height and
+ co-ordinates. It is always in packed pixel little-endian 32bit ARGB (in
+ that order) format, and may be written to directly. Do re-read the
+ width though, after setting it, as it may be adjusted (increased) to
+ make it more suitable for the front end.
+ </desc>
+ <attribute name="x" type="unsigned long" readonly="yes">
+ <desc>X position of the overlay, relative to the frame buffer.</desc>
+ </attribute>
+
+ <attribute name="y" type="unsigned long" readonly="yes">
+ <desc>Y position of the overlay, relative to the frame buffer.</desc>
+ </attribute>
+
+ <attribute name="visible" type="boolean">
+ <desc>
+ Whether the overlay is currently visible.
+ </desc>
+ </attribute>
+
+ <attribute name="alpha" type="unsigned long">
+ <desc>
+ The global alpha value for the overlay. This may or may not be
+ supported by a given front end.
+ </desc>
+ </attribute>
+
+ <method name="move">
+ <desc>
+ Changes the overlay's position relative to the IFramebuffer.
+ </desc>
+ <param name="x" type="unsigned long" dir="in"/>
+ <param name="y" type="unsigned long" dir="in"/>
+ </method>
+
+ </interface>
+
+ <enum
+ name="GuestMonitorStatus"
+ uuid="6b8d3f71-39cb-459e-a916-48917ed43e19"
+ >
+ <desc>
+ The current status of the guest display.
+ </desc>
+
+ <const name="Disabled" value="0">
+ <desc>
+ The guest monitor is disabled in the guest.
+ </desc>
+ </const>
+
+ <const name="Enabled" value="1">
+ <desc>
+ The guest monitor is enabled in the guest.
+ </desc>
+ </const>
+
+ <const name="Blank" value="2">
+ <desc>
+ The guest monitor is enabled in the guest but should display nothing.
+ </desc>
+ </const>
+ </enum>
+
+ <enum
+ name="ScreenLayoutMode"
+ uuid="8fa1964c-8774-11e9-ae5d-1f419105e68d"
+ >
+ <desc>
+ How <link to="IDisplay::setScreenLayout"/> method should work.
+ </desc>
+
+ <const name="Apply" value="0">
+ <desc>
+ If the guest is already at desired mode then the API might avoid setting the mode.
+ </desc>
+ </const>
+
+ <const name="Reset" value="1">
+ <desc>
+ Always set the new mode even if the guest is already at desired mode.
+ </desc>
+ </const>
+
+ <const name="Attach" value="2">
+ <desc>
+ Attach new screens and always set the new mode for existing screens.
+ </desc>
+ </const>
+
+ <const name="Silent" value="3">
+ <desc>
+ Do not notify the guest of the change. Normally this is wished, but it
+ might not be when re-setting monitor information from the last session
+ (no hotplug happened, as it is still the same virtual monitor).
+ </desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IGuestScreenInfo" extends="$unknown"
+ uuid="6b2f98f8-9641-4397-854a-040439d0114b"
+ wsmap="managed"
+ >
+ <attribute name="screenId" type="unsigned long" readonly="yes"/>
+ <attribute name="guestMonitorStatus" type="GuestMonitorStatus" readonly="yes"/>
+ <attribute name="primary" type="boolean" readonly="yes"/>
+ <attribute name="origin" type="boolean" readonly="yes"/>
+ <attribute name="originX" type="long" readonly="yes"/>
+ <attribute name="originY" type="long" readonly="yes"/>
+ <attribute name="width" type="unsigned long" readonly="yes"/>
+ <attribute name="height" type="unsigned long" readonly="yes"/>
+ <attribute name="bitsPerPixel" type="unsigned long" readonly="yes"/>
+ <attribute name="extendedInfo" type="wstring" readonly="yes"/>
+ </interface>
+
+ <interface
+ name="IDisplay" extends="$unknown"
+ uuid="4680b2de-8690-11e9-b83d-5719e53cf1de"
+ wsmap="managed"
+ wrap-hint-server-addinterfaces="IEventListener"
+ reservedMethods="8" reservedAttributes="16"
+ >
+ <desc>
+ The IDisplay interface represents the virtual machine's display.
+
+ The object implementing this interface is contained in each
+ <link to="IConsole::display"/> attribute and represents the visual
+ output of the virtual machine.
+
+ The virtual display supports pluggable output targets represented by the
+ IFramebuffer interface. Examples of the output target are a window on
+ the host computer or an RDP session's display on a remote computer.
+ </desc>
+
+ <attribute name="guestScreenLayout" type="IGuestScreenInfo" safearray="yes" readonly="yes">
+ <desc>
+ Layout of the guest screens.
+ </desc>
+ </attribute>
+
+ <method name="getScreenResolution">
+ <desc>
+ Queries certain attributes such as display width, height, color depth
+ and the X and Y origin for a given guest screen.
+
+ The parameters @a xOrigin and @a yOrigin return the X and Y
+ coordinates of the framebuffer's origin.
+
+ All return parameters are optional.</desc>
+ <param name="screenId" type="unsigned long" dir="in"/>
+ <param name="width" type="unsigned long" dir="out"/>
+ <param name="height" type="unsigned long" dir="out"/>
+ <param name="bitsPerPixel" type="unsigned long" dir="out"/>
+ <param name="xOrigin" type="long" dir="out"/>
+ <param name="yOrigin" type="long" dir="out"/>
+ <param name="guestMonitorStatus" type="GuestMonitorStatus" dir="out"/>
+ </method>
+
+ <method name="attachFramebuffer">
+ <desc>
+ Sets the graphics update target for a screen.
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in"/>
+ <param name="framebuffer" type="IFramebuffer" dir="in"/>
+ <param name="id" type="uuid" mod="string" dir="return"/>
+ </method>
+
+ <method name="detachFramebuffer">
+ <desc>
+ Removes the graphics updates target for a screen.
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in"/>
+ <param name="id" type="uuid" mod="string" dir="in"/>
+ </method>
+
+ <method name="queryFramebuffer">
+ <desc>
+ Queries the graphics updates targets for a screen.
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in"/>
+ <param name="framebuffer" type="IFramebuffer" dir="return"/>
+ </method>
+
+ <method name="setVideoModeHint">
+ <desc>
+ Changes the monitor information reported by a given output of the guest
+ graphics device. This information can be read by the guest if suitable
+ drivers and driver tools are available, including but not limited to
+ those in the Guest Additions. The guest will receive monitor hotplug
+ notification when the monitor information is changed, and the
+ information itself will be available to the guest until the next change.
+ The information should not be resent if the guest does not resize in
+ response. The guest might have chosen to ignore the change, or the
+ resize might happen later when a suitable driver is started.
+
+ Specifying @c 0 for either @a width, @a height or @a bitsPerPixel
+ parameters means that the corresponding values should be taken from the
+ current video mode (i.e. left unchanged).
+
+ @todo Rename this to @a setMonitorInfo for 7.0.
+
+ <result name="E_INVALIDARG">
+ The @a display value is higher then the number of outputs.
+ </result>
+
+ </desc>
+ <param name="display" type="unsigned long" dir="in">
+ <desc>
+ The number of the guest output to change.
+ </desc>
+ </param>
+ <param name="enabled" type="boolean" dir="in">
+ <desc>
+ @c True if a monitor is connected,
+ @c False otherwise.
+ <note internal="yes">For historical reasons the Windows drivers can
+ and do override this setting. Call this a virtual hardware quirk.
+ </note>
+ </desc>
+ </param>
+ <param name="changeOrigin" type="boolean" dir="in">
+ <desc>
+ @c True, if the position of the guest screen is specified,
+ @c False otherwise.
+ </desc>
+ </param>
+ <param name="originX" type="long" dir="in">
+ <desc>
+ The X origin of the guest screen.
+ </desc>
+ </param>
+ <param name="originY" type="long" dir="in">
+ <desc>
+ The Y origin of the guest screen.
+ </desc>
+ </param>
+ <param name="width" type="unsigned long" dir="in">
+ <desc>
+ The width of the guest screen.
+ </desc>
+ </param>
+ <param name="height" type="unsigned long" dir="in">
+ <desc>
+ The height of the guest screen.
+ </desc>
+ </param>
+ <param name="bitsPerPixel" type="unsigned long" dir="in">
+ <desc>
+ The number of bits per pixel of the guest screen.
+ </desc>
+ </param>
+ <param name="notify" type="boolean" dir="in">
+ <desc>
+ Whether the guest should be notified of the change. Normally this
+ is wished, but it might not be when re-setting monitor information
+ from the last session (no hotplug happened, as it is still the same
+ virtual monitor). Might also be useful if several monitors are to be
+ changed at once, but this would not reflect physical hardware well,
+ and we also have @a setScreenLayout for that.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getVideoModeHint">
+ <desc>
+ Queries the monitor information for a given guest output. See
+ @a setVideoModeHint. If no monitor information has been set yet by a
+ front-end the preferred mode values returned will be zero.
+
+ @todo Rename this to @a getMonitorInfo for 7.0.
+
+ <result name="E_INVALIDARG">
+ The @a display value is higher than the number of outputs.
+ </result>
+
+ </desc>
+ <param name="display" type="unsigned long" dir="in">
+ <desc>
+ The number of the guest output to query.
+ </desc>
+ </param>
+ <param name="enabled" type="boolean" dir="out">
+ <desc>
+ @c True if a monitor is connected,
+ @c False otherwise.
+ <note internal="yes">For historical reasons the Windows drivers can
+ and do override the setting requested by the host.</note>
+ </desc>
+ </param>
+ <param name="changeOrigin" type="boolean" dir="out">
+ <desc>
+ @c True, if the position of the guest screen was specified,
+ @c False otherwise.
+ </desc>
+ </param>
+ <param name="originX" type="long" dir="out">
+ <desc>
+ The X origin of the guest screen.
+ </desc>
+ </param>
+ <param name="originY" type="long" dir="out">
+ <desc>
+ The Y origin of the guest screen.
+ </desc>
+ </param>
+ <param name="width" type="unsigned long" dir="out">
+ <desc>
+ The width of the monitor preferred mode.
+ </desc>
+ </param>
+ <param name="height" type="unsigned long" dir="out">
+ <desc>
+ The height of the monitor preferred mode.
+ </desc>
+ </param>
+ <param name="bitsPerPixel" type="unsigned long" dir="out">
+ <desc>
+ The number of bits per pixel of the monitor preferred mode.
+ </desc>
+ </param>
+ </method>
+
+ <method name="setSeamlessMode">
+ <desc>
+ Enables or disables seamless guest display rendering (seamless desktop
+ integration) mode.
+ <note>
+ Calling this method has no effect if <link
+ to="IGuest::getFacilityStatus"/> with facility @c Seamless
+ does not return @c Active.
+ </note>
+ </desc>
+ <param name="enabled" type="boolean" dir="in"/>
+ </method>
+
+ <method name="takeScreenShot">
+ <desc>
+ Takes a screen shot of the requested size and format and copies it to the
+ buffer allocated by the caller and pointed to by @a address.
+ The buffer size must be enough for a 32 bits per pixel bitmap,
+ i.e. width * height * 4 bytes.
+
+ <note>This API can be used only locally by a VM process through the
+ COM/XPCOM C++ API as it requires pointer support. It is not
+ available for scripting languages, Java or any webservice clients.
+ Unless you are writing a new VM frontend use
+ <link to="#takeScreenShotToArray" />.
+ </note>
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in"/>
+ <param name="address" type="octet" mod="ptr" dir="in"/>
+ <param name="width" type="unsigned long" dir="in"/>
+ <param name="height" type="unsigned long" dir="in"/>
+ <param name="bitmapFormat" type="BitmapFormat" dir="in"/>
+ </method>
+
+ <method name="takeScreenShotToArray">
+ <desc>
+ Takes a guest screen shot of the requested size and format
+ and returns it as an array of bytes.
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>
+ The guest monitor to take screenshot from.
+ </desc>
+ </param>
+ <param name="width" type="unsigned long" dir="in">
+ <desc>
+ Desired image width.
+ </desc>
+ </param>
+ <param name="height" type="unsigned long" dir="in">
+ <desc>
+ Desired image height.
+ </desc>
+ </param>
+ <param name="bitmapFormat" type="BitmapFormat" dir="in">
+ <desc>
+ The requested format.
+ </desc>
+ </param>
+ <param name="screenData" type="octet" dir="return" safearray="yes">
+ <desc>
+ Array with resulting screen data.
+ </desc>
+ </param>
+ </method>
+
+ <method name="drawToScreen">
+ <desc>
+ Draws a 32-bpp image of the specified size from the given buffer
+ to the given point on the VM display.
+
+ <result name="E_NOTIMPL">
+ Feature not implemented.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ Could not draw to screen.
+ </result>
+
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>
+ Monitor to take the screenshot from.
+ </desc>
+ </param>
+ <param name="address" type="octet" mod="ptr" dir="in">
+ <desc>
+ Address to store the screenshot to
+ </desc>
+ </param>
+ <param name="x" type="unsigned long" dir="in">
+ <desc>
+ Relative to the screen top left corner.
+ </desc>
+ </param>
+ <param name="y" type="unsigned long" dir="in">
+ <desc>
+ Relative to the screen top left corner.
+ </desc>
+ </param>
+ <param name="width" type="unsigned long" dir="in">
+ <desc>
+ Desired image width.
+ </desc>
+ </param>
+ <param name="height" type="unsigned long" dir="in">
+ <desc>
+ Desired image height.
+ </desc>
+ </param>
+ </method>
+
+ <method name="invalidateAndUpdate">
+ <desc>
+ Does a full invalidation of the VM display and instructs the VM
+ to update it.
+
+ <result name="VBOX_E_IPRT_ERROR">
+ Could not invalidate and update screen.
+ </result>
+
+ </desc>
+ </method>
+
+ <method name="invalidateAndUpdateScreen">
+ <desc>
+ Redraw the specified VM screen.
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>
+ The guest screen to redraw.
+ </desc>
+ </param>
+ </method>
+
+ <method name="completeVHWACommand">
+ <desc>
+ Signals that the Video HW Acceleration command has completed.
+ </desc>
+
+ <param name="command" type="octet" mod="ptr" dir="in">
+ <desc>Pointer to VBOXVHWACMD containing the completed command.</desc>
+ </param>
+ </method>
+
+ <method name="viewportChanged">
+ <desc>
+ Signals that framebuffer window viewport has changed.
+
+ <result name="E_INVALIDARG">
+ The specified viewport data is invalid.
+ </result>
+
+ </desc>
+
+ <param name="screenId" type="unsigned long" dir="in">
+ <desc>
+ Monitor to take the screenshot from.
+ </desc>
+ </param>
+ <param name="x" type="unsigned long" dir="in">
+ <desc>
+ Framebuffer x offset.
+ </desc>
+ </param>
+ <param name="y" type="unsigned long" dir="in">
+ <desc>
+ Framebuffer y offset.
+ </desc>
+ </param>
+ <param name="width" type="unsigned long" dir="in">
+ <desc>
+ Viewport width.
+ </desc>
+ </param>
+ <param name="height" type="unsigned long" dir="in">
+ <desc>
+ Viewport height.
+ </desc>
+ </param>
+ </method>
+
+ <method name="querySourceBitmap" wsmap="suppress">
+ <desc>
+ Obtains the guest screen bitmap parameters.
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in"/>
+ <param name="displaySourceBitmap" type="IDisplaySourceBitmap" dir="out"/>
+ </method>
+
+ <method name="notifyScaleFactorChange">
+ <desc>
+ Notify OpenGL HGCM host service about graphics content scaling factor change.
+ </desc>
+ <param name="screenId" type="unsigned long" dir="in"/>
+ <param name="u32ScaleFactorWMultiplied" type="unsigned long" dir="in"/>
+ <param name="u32ScaleFactorHMultiplied" type="unsigned long" dir="in"/>
+ </method>
+
+ <method name="notifyHiDPIOutputPolicyChange">
+ <desc>
+ Notify OpenGL HGCM host service about HiDPI monitor scaling policy change.
+ </desc>
+ <param name="fUnscaledHiDPI" type="boolean" dir="in"/>
+ </method>
+
+ <method name="setScreenLayout">
+ <desc>
+ Set video modes for the guest screens.
+ </desc>
+ <param name="screenLayoutMode" type="ScreenLayoutMode" dir="in"/>
+ <param name="guestScreenInfo" type="IGuestScreenInfo" safearray="yes" dir="in"/>
+ </method>
+
+ <method name="detachScreens">
+ <desc>
+ Unplugs monitors from the virtual graphics card.
+ </desc>
+ <param name="screenIds" type="long" safearray="yes" dir="in"/>
+ </method>
+
+ <method name="createGuestScreenInfo">
+ <desc>
+ Make a IGuestScreenInfo object with the provided parameters.
+ </desc>
+ <param name="display" type="unsigned long" dir="in">
+ <desc>
+ The number of the guest display.
+ </desc>
+ </param>
+ <param name="status" type="GuestMonitorStatus" dir="in">
+ <desc>
+ @c True, if this guest screen is enabled,
+ @c False otherwise.
+ </desc>
+ </param>
+ <param name="primary" type="boolean" dir="in">
+ <desc>
+ Whether this guest monitor must be primary.
+ </desc>
+ </param>
+ <param name="changeOrigin" type="boolean" dir="in">
+ <desc>
+ @c True, if the origin of the guest screen should be changed,
+ @c False otherwise.
+ </desc>
+ </param>
+ <param name="originX" type="long" dir="in">
+ <desc>
+ The X origin of the guest screen.
+ </desc>
+ </param>
+ <param name="originY" type="long" dir="in">
+ <desc>
+ The Y origin of the guest screen.
+ </desc>
+ </param>
+ <param name="width" type="unsigned long" dir="in">
+ <desc>
+ The width of the guest screen.
+ </desc>
+ </param>
+ <param name="height" type="unsigned long" dir="in">
+ <desc>
+ The height of the guest screen.
+ </desc>
+ </param>
+ <param name="bitsPerPixel" type="unsigned long" dir="in">
+ <desc>
+ The number of bits per pixel of the guest screen.
+ </desc>
+ </param>
+ <param name="guestScreenInfo" type="IGuestScreenInfo" dir="return">
+ <desc>
+ The created object.
+ </desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <!--
+ // INetworkAdapter
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="NetworkAttachmentType"
+ uuid="cbfd17fa-fdc3-4d1c-97ec-4674da8d2a5d"
+ >
+ <desc>
+ Network attachment type.
+ </desc>
+
+ <const name="Null" value="0">
+ <desc>Null value, also means "not attached".</desc>
+ </const>
+ <const name="NAT" value="1"/>
+ <const name="Bridged" value="2"/>
+ <const name="Internal" value="3"/>
+ <const name="HostOnly" value="4"/>
+ <const name="Generic" value="5"/>
+ <const name="NATNetwork" value="6"/>
+ <const name="Cloud" value="7"/>
+ <const name="HostOnlyNetwork" value="8"/>
+ </enum>
+
+ <enum
+ name="NetworkAdapterType"
+ uuid="3c2281e4-d952-4e87-8c7d-24379cb6a81c"
+ >
+ <desc>
+ Network adapter type.
+ </desc>
+
+ <const name="Null" value="0">
+ <desc>Null value (never used by the API).</desc>
+ </const>
+ <const name="Am79C970A" value="1">
+ <desc>AMD PCNet-PCI II network card (Am79C970A).</desc>
+ </const>
+ <const name="Am79C973" value="2">
+ <desc>AMD PCNet-FAST III network card (Am79C973).</desc>
+ </const>
+ <const name="I82540EM" value="3">
+ <desc>Intel PRO/1000 MT Desktop network card (82540EM).</desc>
+ </const>
+ <const name="I82543GC" value="4">
+ <desc>Intel PRO/1000 T Server network card (82543GC).</desc>
+ </const>
+ <const name="I82545EM" value="5">
+ <desc>Intel PRO/1000 MT Server network card (82545EM).</desc>
+ </const>
+ <const name="Virtio" value="6">
+ <desc>Virtio network device.</desc>
+ </const>
+ <const name="Am79C960" value="7">
+ <desc>AMD PCnet-ISA/NE2100 network card (Am79C960).</desc>
+ </const>
+ <const name="NE2000" value="8">
+ <desc>Novell NE2000 network card (NE2000).</desc>
+ </const>
+ <const name="NE1000" value="9">
+ <desc>Novell NE1000 network card (NE1000).</desc>
+ </const>
+ <const name="WD8013" value="10">
+ <desc>WD/SMC EtherCard Plus 16 network card (WD8013EBT).</desc>
+ </const>
+ <const name="WD8003" value="11">
+ <desc>WD/SMC EtherCard Plus network card (WD8003E).</desc>
+ </const>
+ <const name="ELNK2" value="12">
+ <desc>3Com EtherLink II network card (3C503).</desc>
+ </const>
+ <const name="ELNK1" value="13">
+ <desc>3Com EtherLink network card (3C501/3C500).</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="NetworkAdapterPromiscModePolicy"
+ uuid="c963768a-376f-4c85-8d84-d8ced4b7269e"
+ >
+ <desc>
+ The promiscuous mode policy of an interface.
+ </desc>
+
+ <const name="Deny" value="1">
+ <desc>Deny promiscuous mode requests.</desc>
+ </const>
+ <const name="AllowNetwork" value="2">
+ <desc>
+ Allow promiscuous mode, but restrict the scope it to the internal
+ network so that it only applies to other VMs.
+ </desc>
+ </const>
+ <const name="AllowAll" value="3">
+ <desc>
+ Allow promiscuous mode, include unrelated traffic going over the wire
+ and internally on the host.
+ </desc>
+ </const>
+ </enum>
+
+ <interface
+ name="INetworkAdapter" extends="$unknown"
+ uuid="dcf47a1d-ed70-4db8-9a4b-2646bd166905"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ Represents a virtual network adapter that is attached to a virtual machine.
+ Each virtual machine has a fixed number of network adapter slots with one
+ instance of this attached to each of them. Call
+ <link to="IMachine::getNetworkAdapter" /> to get the network adapter that
+ is attached to a given slot in a given machine.
+
+ Each network adapter can be in one of five attachment modes, which are
+ represented by the <link to="NetworkAttachmentType" /> enumeration;
+ see the <link to="#attachmentType" /> attribute.
+ </desc>
+
+ <attribute name="adapterType" type="NetworkAdapterType">
+ <desc>
+ Type of the virtual network adapter. Depending on this value,
+ VirtualBox will provide a different virtual network hardware
+ to the guest.
+ </desc>
+ </attribute>
+
+ <attribute name="slot" type="unsigned long" readonly="yes">
+ <desc>
+ Slot number this adapter is plugged into. Corresponds to
+ the value you pass to <link to="IMachine::getNetworkAdapter"/>
+ to obtain this instance.
+ </desc>
+ </attribute>
+
+ <attribute name="enabled" type="boolean">
+ <desc>
+ Flag whether the network adapter is present in the
+ guest system. If disabled, the virtual guest hardware will
+ not contain this network adapter. Can only be changed when
+ the VM is not running.
+ </desc>
+ </attribute>
+
+ <attribute name="MACAddress" type="wstring">
+ <desc>
+ Ethernet MAC address of the adapter, 12 hexadecimal characters. When
+ setting it to @c null or an empty string for an enabled adapter,
+ VirtualBox will generate a unique MAC address. Disabled adapters can
+ have an empty MAC address.
+ </desc>
+ </attribute>
+
+ <attribute name="attachmentType" type="NetworkAttachmentType">
+ <desc>
+ Sets/Gets network attachment type of this network adapter.
+ </desc>
+ </attribute>
+
+ <attribute name="bridgedInterface" type="wstring">
+ <desc>
+ Name of the network interface the VM should be bridged to.
+ </desc>
+ </attribute>
+
+ <attribute name="hostOnlyInterface" type="wstring">
+ <desc>
+ Name of the host only network interface the VM is attached to.
+ </desc>
+ </attribute>
+
+ <attribute name="hostOnlyNetwork" type="wstring">
+ <desc>
+ Name of the host only network the VM is attached to.
+ </desc>
+ </attribute>
+
+ <attribute name="internalNetwork" type="wstring">
+ <desc>
+ Name of the internal network the VM is attached to.
+ </desc>
+ </attribute>
+
+ <attribute name="NATNetwork" type="wstring">
+ <desc>
+ Name of the NAT network the VM is attached to.
+ </desc>
+ </attribute>
+
+ <attribute name="genericDriver" type="wstring">
+ <desc>
+ Name of the driver to use for the "Generic" network attachment type.
+ </desc>
+ </attribute>
+
+ <attribute name="cloudNetwork" type="wstring">
+ <desc>
+ Name of the cloud network the VM is attached to.
+ </desc>
+ </attribute>
+
+ <attribute name="cableConnected" type="boolean">
+ <desc>
+ Flag whether the adapter reports the cable as connected or not.
+ It can be used to report offline situations to a VM.
+ </desc>
+ </attribute>
+
+ <attribute name="lineSpeed" type="unsigned long">
+ <desc>
+ Line speed reported by custom drivers, in units of 1 kbps.
+ </desc>
+ </attribute>
+
+ <attribute name="promiscModePolicy" type="NetworkAdapterPromiscModePolicy">
+ <desc>
+ The promiscuous mode policy of the network adapter when attached to an
+ internal network, host only network or a bridge.
+ </desc>
+ </attribute>
+
+ <attribute name="traceEnabled" type="boolean">
+ <desc>
+ Flag whether network traffic from/to the network card should be traced.
+ Can only be toggled when the VM is turned off.
+ </desc>
+ </attribute>
+
+ <attribute name="traceFile" type="wstring">
+ <desc>
+ Filename where a network trace will be stored. If not set, VBox-pid.pcap
+ will be used.
+ </desc>
+ </attribute>
+
+ <attribute name="NATEngine" type="INATEngine" readonly="yes">
+ <desc>
+ Points to the NAT engine which handles the network address translation
+ for this interface. This is active only when the interface actually uses
+ NAT.
+ </desc>
+ </attribute>
+
+ <attribute name="bootPriority" type="unsigned long">
+ <desc>
+ Network boot priority of the adapter. Priority 1 is highest. If not set,
+ the priority is considered to be at the lowest possible setting.
+ </desc>
+ </attribute>
+
+ <attribute name="bandwidthGroup" type="IBandwidthGroup">
+ <desc>The bandwidth group this network adapter is assigned to.</desc>
+ </attribute>
+
+ <!-- property methods -->
+
+ <method name="getProperty" const="yes">
+ <desc>
+ Returns the value of the network attachment property with the given name.
+
+ If the requested data @a key does not exist, this function will
+ succeed and return an empty string in the @a value argument.
+
+ <result name="E_INVALIDARG">@a name is @c null or empty.</result>
+ </desc>
+ <param name="key" type="wstring" dir="in">
+ <desc>Name of the property to get.</desc>
+ </param>
+ <param name="value" type="wstring" dir="return">
+ <desc>Current property value.</desc>
+ </param>
+ </method>
+
+ <method name="setProperty">
+ <desc>
+ Sets the value of the network attachment property with the given name.
+
+ Setting the property value to @c null or an empty string is equivalent
+ to deleting the existing value.
+
+ <result name="E_INVALIDARG">@a name is @c null or empty.</result>
+ </desc>
+ <param name="key" type="wstring" dir="in">
+ <desc>Name of the property to set.</desc>
+ </param>
+ <param name="value" type="wstring" dir="in">
+ <desc>Property value to set.</desc>
+ </param>
+ </method>
+
+ <method name="getProperties" const="yes">
+ <desc>
+ Returns values for a group of properties in one call.
+
+ The names of the properties to get are specified using the @a names
+ argument which is a list of comma-separated property names or
+ an empty string if all properties are to be returned.
+ <note>Currently the value of this argument is ignored and the method
+ always returns all existing properties.</note>
+
+ The method returns two arrays, the array of property names corresponding
+ to the @a names argument and the current values of these properties.
+ Both arrays have the same number of elements with each element at the
+ given index in the first array corresponds to an element at the same
+ index in the second array.
+ </desc>
+ <param name="names" type="wstring" dir="in">
+ <desc>
+ Names of properties to get.
+ </desc>
+ </param>
+ <param name="returnNames" type="wstring" safearray="yes" dir="out">
+ <desc>Names of returned properties.</desc>
+ </param>
+ <param name="returnValues" type="wstring" safearray="yes" dir="return">
+ <desc>Values of returned properties.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+
+ <!--
+ // ISerialPort
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="PortMode"
+ uuid="7485fcfd-d603-470a-87af-26d33beb7de9"
+ >
+ <desc>
+ The PortMode enumeration represents possible communication modes for
+ the virtual serial port device.
+ </desc>
+
+ <const name="Disconnected" value="0">
+ <desc>Virtual device is not attached to any real host device.</desc>
+ </const>
+ <const name="HostPipe" value="1">
+ <desc>Virtual device is attached to a host pipe.</desc>
+ </const>
+ <const name="HostDevice" value="2">
+ <desc>Virtual device is attached to a host device.</desc>
+ </const>
+ <const name="RawFile" value="3">
+ <desc>Virtual device is attached to a raw file.</desc>
+ </const>
+ <const name="TCP" value="4">
+ <desc>Virtual device is attached to a TCP socket.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="UartType"
+ uuid="c8899d39-0b90-4265-9d02-1e38bd4d1b39"
+ >
+ <desc>
+ The UART type represents the emulated UART chip for the serial port device.
+ </desc>
+
+ <const name="U16450" value="0">
+ <desc>The most basic emulated UART which doesn't support FIFO operation.</desc>
+ </const>
+ <const name="U16550A" value="1">
+ <desc>
+ The successor of the 16450 UART introducing a 16 byte FIFO to reduce
+ operational overhead.
+ </desc>
+ </const>
+ <const name="U16750" value="2">
+ <desc>
+ This UART developed by Texas Instruments introduced a 64 byte FIFO
+ and hardware flow control.
+ </desc>
+ </const>
+ </enum>
+
+ <interface
+ name="ISerialPort" extends="$unknown"
+ uuid="5587d0f6-a227-4f23-8278-2f675eea1bb2"
+ wsmap="managed"
+ rest="managed"
+ reservedAttributes="4"
+ >
+
+ <desc>
+ The ISerialPort interface represents the virtual serial port device.
+
+ The virtual serial port device acts like an ordinary serial port
+ inside the virtual machine. This device communicates to the real
+ serial port hardware in one of two modes: host pipe or host device.
+
+ In host pipe mode, the #path attribute specifies the path to the pipe on
+ the host computer that represents a serial port. The #server attribute
+ determines if this pipe is created by the virtual machine process at
+ machine startup or it must already exist before starting machine
+ execution.
+
+ In host device mode, the #path attribute specifies the name of the
+ serial port device on the host computer.
+
+ There is also a third communication mode: the disconnected mode. In this
+ mode, the guest OS running inside the virtual machine will be able to
+ detect the serial port, but all port write operations will be discarded
+ and all port read operations will return no data.
+
+ <see><link to="IMachine::getSerialPort"/></see>
+ </desc>
+
+ <attribute name="slot" type="unsigned long" readonly="yes">
+ <desc>
+ Slot number this serial port is plugged into. Corresponds to
+ the value you pass to <link to="IMachine::getSerialPort"/>
+ to obtain this instance.
+ </desc>
+ </attribute>
+
+ <attribute name="enabled" type="boolean">
+ <desc>
+ Flag whether the serial port is enabled. If disabled,
+ the serial port will not be reported to the guest OS.
+ </desc>
+ </attribute>
+
+ <attribute name="IOBase" type="unsigned long">
+ <desc>Base I/O address of the serial port.</desc>
+ </attribute>
+
+ <attribute name="IRQ" type="unsigned long">
+ <desc>IRQ number of the serial port.</desc>
+ </attribute>
+
+ <attribute name="hostMode" type="PortMode">
+ <desc>
+ How is this port connected to the host.
+ <note>
+ Changing this attribute may fail if the conditions for
+ <link to="#path"/> are not met.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="server" type="boolean">
+ <desc>
+ Flag whether this serial port acts as a server (creates a new pipe on
+ the host) or as a client (uses the existing pipe). This attribute is
+ used only when <link to="#hostMode"/> is PortMode_HostPipe or PortMode_TCP.
+ </desc>
+ </attribute>
+
+ <attribute name="path" type="wstring">
+ <desc>
+ Path to the serial port's pipe on the host when <link to="ISerialPort::hostMode"/> is
+ PortMode_HostPipe, the host serial device name when
+ <link to="ISerialPort::hostMode"/> is PortMode_HostDevice or the TCP
+ <b>port</b> (server) or <b>hostname:port</b> (client) when
+ <link to="ISerialPort::hostMode"/> is PortMode_TCP.
+ For those cases, setting a @c null or empty string as the attribute's
+ value is an error. Otherwise, the value of this property is ignored.
+ </desc>
+ </attribute>
+
+ <attribute name="uartType" type="UartType">
+ <desc>Selects the emulated UART implementation.</desc>
+ </attribute>
+
+ </interface>
+
+ <!--
+ // IParallelPort
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IParallelPort" extends="$unknown"
+ uuid="788b87df-7708-444b-9eef-c116ce423d39"
+ wsmap="managed"
+ rest="managed"
+ reservedAttributes="4"
+ >
+
+ <desc>
+ The IParallelPort interface represents the virtual parallel port device.
+
+ The virtual parallel port device acts like an ordinary parallel port
+ inside the virtual machine. This device communicates to the real
+ parallel port hardware using the name of the parallel device on the host
+ computer specified in the #path attribute.
+
+ Each virtual parallel port device is assigned a base I/O address and an
+ IRQ number that will be reported to the guest operating system and used
+ to operate the given parallel port from within the virtual machine.
+
+ <see><link to="IMachine::getParallelPort"/></see>
+ </desc>
+
+ <attribute name="slot" type="unsigned long" readonly="yes">
+ <desc>
+ Slot number this parallel port is plugged into. Corresponds to
+ the value you pass to <link to="IMachine::getParallelPort"/>
+ to obtain this instance.
+ </desc>
+ </attribute>
+
+ <attribute name="enabled" type="boolean">
+ <desc>
+ Flag whether the parallel port is enabled. If disabled,
+ the parallel port will not be reported to the guest OS.
+ </desc>
+ </attribute>
+
+ <attribute name="IOBase" type="unsigned long">
+ <desc>Base I/O address of the parallel port.</desc>
+ </attribute>
+
+ <attribute name="IRQ" type="unsigned long">
+ <desc>IRQ number of the parallel port.</desc>
+ </attribute>
+
+ <attribute name="path" type="wstring">
+ <desc>
+ Host parallel device name. If this parallel port is enabled, setting a
+ @c null or an empty string as this attribute's value will result in
+ the parallel port behaving as if not connected to any device.
+ </desc>
+ </attribute>
+
+ </interface>
+
+
+ <!--
+ // IMachineDebugger
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="VMExecutionEngine"
+ uuid="6e3f78f9-2dfe-4ed7-863d-67cad351e9d8"
+ >
+ <desc>
+ The main execution engine of a VM.
+ </desc>
+ <const name="NotSet" value="0">
+ <desc>Has not yet been set (try again later).</desc>
+ </const>
+ <const name="Emulated" value="1">
+ <desc>Emulated thru IEM.</desc>
+ </const>
+ <const name="HwVirt" value="2">
+ <desc>Hardware assisted virtualization thru HM.</desc>
+ </const>
+ <const name="NativeApi" value="3">
+ <desc>Hardware assisted virtualization thru native API (NEM).</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IMachineDebugger" extends="$unknown"
+ uuid="fa43579a-2272-47c4-a443-9713f19a902f"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="16" reservedAttributes="16"
+ >
+ <method name="dumpGuestCore">
+ <desc>
+ Takes a core dump of the guest.
+
+ See include/VBox/dbgfcorefmt.h for details on the file format.
+ </desc>
+ <param name="filename" type="wstring" dir="in">
+ <desc>
+ The name of the output file. The file must not exist.
+ </desc>
+ </param>
+ <param name="compression" type="wstring" dir="in">
+ <desc>
+ Reserved for future compression method indicator.
+ </desc>
+ </param>
+ </method>
+
+ <method name="dumpHostProcessCore">
+ <desc>
+ Takes a core dump of the VM process on the host.
+
+ This feature is not implemented in the 4.0.0 release but it may show up
+ in a dot release.
+ </desc>
+ <param name="filename" type="wstring" dir="in">
+ <desc>
+ The name of the output file. The file must not exist.
+ </desc>
+ </param>
+ <param name="compression" type="wstring" dir="in">
+ <desc>
+ Reserved for future compression method indicator.
+ </desc>
+ </param>
+ </method>
+
+ <method name="info">
+ <desc>
+ Interfaces with the info dumpers (DBGFInfo).
+
+ This feature is not implemented in the 4.0.0 release but it may show up
+ in a dot release.
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>
+ The name of the info item.
+ </desc>
+ </param>
+ <param name="args" type="wstring" dir="in">
+ <desc>
+ Arguments to the info dumper.
+ </desc>
+ </param>
+ <param name="info" type="wstring" dir="return">
+ <desc>
+ The into string.
+ </desc>
+ </param>
+ </method>
+
+ <method name="injectNMI">
+ <desc>
+ Inject an NMI into a running VT-x/AMD-V VM.
+ </desc>
+ </method>
+
+ <method name="modifyLogGroups">
+ <desc>
+ Modifies the group settings of the debug or release logger.
+ </desc>
+ <param name="settings" type="wstring" dir="in">
+ <desc>
+ The group settings string. See iprt/log.h for details. To target the
+ release logger, prefix the string with "release:".
+ </desc>
+ </param>
+ </method>
+
+ <method name="modifyLogFlags">
+ <desc>
+ Modifies the debug or release logger flags.
+ </desc>
+ <param name="settings" type="wstring" dir="in">
+ <desc>
+ The flags settings string. See iprt/log.h for details. To target the
+ release logger, prefix the string with "release:".
+ </desc>
+ </param>
+ </method>
+
+ <method name="modifyLogDestinations">
+ <desc>
+ Modifies the debug or release logger destinations.
+ </desc>
+ <param name="settings" type="wstring" dir="in">
+ <desc>
+ The destination settings string. See iprt/log.h for details. To target the
+ release logger, prefix the string with "release:".
+ </desc>
+ </param>
+ </method>
+
+ <method name="readPhysicalMemory">
+ <desc>
+ Reads guest physical memory, no side effects (MMIO++).
+
+ This feature is not implemented in the 4.0.0 release but may show up
+ in a dot release.
+ </desc>
+ <param name="address" type="long long" dir="in">
+ <desc>The guest physical address.</desc>
+ </param>
+ <param name="size" type="unsigned long" dir="in">
+ <desc>The number of bytes to read.</desc>
+ </param>
+ <param name="bytes" type="octet" safearray="yes" dir="return">
+ <desc>The bytes read.</desc>
+ </param>
+ </method>
+
+ <method name="writePhysicalMemory">
+ <desc>
+ Writes guest physical memory, access handles (MMIO++) are ignored.
+
+ This feature is not implemented in the 4.0.0 release but may show up
+ in a dot release.
+ </desc>
+ <param name="address" type="long long" dir="in">
+ <desc>The guest physical address.</desc>
+ </param>
+ <param name="size" type="unsigned long" dir="in">
+ <desc>The number of bytes to read.</desc>
+ </param>
+ <param name="bytes" type="octet" safearray="yes" dir="in">
+ <desc>The bytes to write.</desc>
+ </param>
+ </method>
+
+ <method name="readVirtualMemory">
+ <desc>
+ Reads guest virtual memory, no side effects (MMIO++).
+
+ This feature is not implemented in the 4.0.0 release but may show up
+ in a dot release.
+ </desc>
+ <param name="cpuId" type="unsigned long" dir="in">
+ <desc>The identifier of the Virtual CPU.</desc>
+ </param>
+ <param name="address" type="long long" dir="in">
+ <desc>The guest virtual address.</desc>
+ </param>
+ <param name="size" type="unsigned long" dir="in">
+ <desc>The number of bytes to read.</desc>
+ </param>
+ <param name="bytes" type="octet" safearray="yes" dir="return">
+ <desc>The bytes read.</desc>
+ </param>
+ </method>
+
+ <method name="writeVirtualMemory">
+ <desc>
+ Writes guest virtual memory, access handles (MMIO++) are ignored.
+
+ This feature is not implemented in the 4.0.0 release but may show up
+ in a dot release.
+ </desc>
+ <param name="cpuId" type="unsigned long" dir="in">
+ <desc>The identifier of the Virtual CPU.</desc>
+ </param>
+ <param name="address" type="long long" dir="in">
+ <desc>The guest virtual address.</desc>
+ </param>
+ <param name="size" type="unsigned long" dir="in">
+ <desc>The number of bytes to read.</desc>
+ </param>
+ <param name="bytes" type="octet" safearray="yes" dir="in">
+ <desc>The bytes to write.</desc>
+ </param>
+ </method>
+
+ <method name="loadPlugIn">
+ <desc> Loads a DBGF plug-in. </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>The plug-in name or DLL. Special name 'all' loads all installed plug-ins.</desc>
+ </param>
+ <param name="plugInName" type="wstring" dir="return">
+ <desc>The name of the loaded plug-in.</desc>
+ </param>
+ </method>
+
+ <method name="unloadPlugIn">
+ <desc>Unloads a DBGF plug-in.</desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>The plug-in name or DLL. Special name 'all' unloads all plug-ins.</desc>
+ </param>
+ </method>
+
+ <method name="detectOS">
+ <desc>
+ Tries to (re-)detect the guest OS kernel.
+
+ This feature is not implemented in the 4.0.0 release but may show up
+ in a dot release.
+ </desc>
+ <param name="os" type="wstring" dir="return">
+ <desc>
+ The detected OS kernel on success.
+ </desc>
+ </param>
+ </method>
+
+ <method name="queryOSKernelLog">
+ <desc>
+ Tries to get the kernel log (dmesg) of the guest OS.
+
+ </desc>
+ <param name="maxMessages" type="unsigned long" dir="in">
+ <desc>Max number of messages to return, counting from the end of the
+ log. If 0, there is no limit.</desc>
+ </param>
+ <param name="dmesg" type="wstring" dir="return">
+ <desc>
+ The kernel log.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getRegister">
+ <desc>
+ Gets one register.
+ </desc>
+ <param name="cpuId" type="unsigned long" dir="in">
+ <desc>The identifier of the Virtual CPU.</desc>
+ </param>
+ <param name="name" type="wstring" dir="in">
+ <desc>The register name, case is ignored.</desc>
+ </param>
+ <param name="value" type="wstring" dir="return">
+ <desc>
+ The register value. This is usually a hex value (always 0x prefixed)
+ but other format may be used for floating point registers (TBD).
+ </desc>
+ </param>
+ </method>
+
+ <method name="getRegisters">
+ <desc>
+ Gets all the registers for the given CPU.
+ </desc>
+ <param name="cpuId" type="unsigned long" dir="in">
+ <desc>The identifier of the Virtual CPU.</desc>
+ </param>
+ <param name="names" type="wstring" dir="out" safearray="yes">
+ <desc>Array containing the lowercase register names.</desc>
+ </param>
+ <param name="values" type="wstring" dir="out" safearray="yes">
+ <desc>
+ Array parallel to the names holding the register values as if the
+ register was returned by <link to="IMachineDebugger::getRegister"/>.
+ </desc>
+ </param>
+ </method>
+
+ <method name="setRegister">
+ <desc>
+ Gets one register.
+
+ This feature is not implemented in the 4.0.0 release but may show up
+ in a dot release.
+ </desc>
+ <param name="cpuId" type="unsigned long" dir="in">
+ <desc>The identifier of the Virtual CPU.</desc>
+ </param>
+ <param name="name" type="wstring" dir="in">
+ <desc>The register name, case is ignored.</desc>
+ </param>
+ <param name="value" type="wstring" dir="in">
+ <desc>
+ The new register value. Hexadecimal, decimal and octal formattings
+ are supported in addition to any special formattings returned by
+ the getters.
+ </desc>
+ </param>
+ </method>
+
+ <method name="setRegisters">
+ <desc>
+ Sets zero or more registers atomically.
+
+ This feature is not implemented in the 4.0.0 release but may show up
+ in a dot release.
+ </desc>
+ <param name="cpuId" type="unsigned long" dir="in">
+ <desc>The identifier of the Virtual CPU.</desc>
+ </param>
+ <param name="names" type="wstring" dir="in" safearray="yes">
+ <desc>Array containing the register names, case ignored.</desc>
+ </param>
+ <param name="values" type="wstring" dir="in" safearray="yes">
+ <desc>
+ Array paralell to the names holding the register values. See
+ <link to="IMachineDebugger::setRegister"/> for formatting
+ guidelines.
+ </desc>
+ </param>
+ </method>
+
+ <method name="dumpGuestStack">
+ <desc>
+ Produce a simple stack dump using the current guest state.
+
+ This feature is not implemented in the 4.0.0 release but may show up
+ in a dot release.
+ </desc>
+ <param name="cpuId" type="unsigned long" dir="in">
+ <desc>The identifier of the Virtual CPU.</desc>
+ </param>
+ <param name="stack" type="wstring" dir="return">
+ <desc>String containing the formatted stack dump.</desc>
+ </param>
+ </method>
+
+ <method name="resetStats">
+ <desc>
+ Reset VM statistics.
+ </desc>
+ <param name="pattern" type="wstring" dir="in">
+ <desc>The selection pattern. A bit similar to filename globbing.
+ Wildchars are '*' and '?', where the asterisk matches zero or
+ more characters and question mark matches exactly one character.
+ Multiple pattern can be joined by putting '|' between them.</desc>
+ </param>
+ </method>
+
+ <method name="dumpStats">
+ <desc>
+ Dumps VM statistics.
+ </desc>
+ <param name="pattern" type="wstring" dir="in">
+ <desc>The selection pattern. A bit similar to filename globbing.
+ Wildchars are '*' and '?', where the asterisk matches zero or
+ more characters and question mark matches exactly one character.
+ Multiple pattern can be joined by putting '|' between them.</desc>
+ </param>
+ </method>
+
+ <method name="getStats">
+ <desc>
+ Get the VM statistics in a XMLish format.
+ </desc>
+ <param name="pattern" type="wstring" dir="in">
+ <desc>The selection pattern. A bit similar to filename globbing.
+ Wildchars are '*' and '?', where the asterisk matches zero or
+ more characters and question mark matches exactly one character.
+ Multiple pattern can be joined by putting '|' between them.</desc>
+ </param>
+ <param name="withDescriptions" type="boolean" dir="in">
+ <desc>Whether to include the descriptions.</desc>
+ </param>
+ <param name="stats" type="wstring" dir="return">
+ <desc>The XML document containing the statistics.</desc>
+ </param>
+ </method>
+
+ <method name="getCPULoad">
+ <desc>
+ Get the load percentages (as observed by the VMM) for all virtual CPUs
+ or a specific one.
+ </desc>
+ <param name="cpuId" type="unsigned long" dir="in">
+ <desc>The ID of the virtual CPU to retrieve stats for, pass 0x7fffffff
+ or higher for the average accross all CPUs.</desc>
+ </param>
+ <param name="pctExecuting" type="unsigned long" dir="out">
+ <desc>Percentage of the interval that the CPU(s) spend executing guest code.</desc>
+ </param>
+ <param name="pctHalted" type="unsigned long" dir="out">
+ <desc>Percentage of the interval that the CPU(s) spend halted.</desc>
+ </param>
+ <param name="pctOther" type="unsigned long" dir="out">
+ <desc>Percentage of the interval that the CPU(s) preempted by the host
+ scheduler, on virtualization overhead and on other tasks.</desc>
+ </param>
+ <param name="msInterval" type="long long" dir="return">
+ <desc>The interval the percentage was calculated over in milliseconds</desc>
+ </param>
+ </method>
+
+ <method name="takeGuestSample">
+ <desc>
+ Creates a sample report of the guest and emulated device activity.
+ </desc>
+ <param name="filename" type="wstring" dir="in">
+ <desc>The file to dump the report to.</desc>
+ </param>
+ <param name="usInterval" type="unsigned long" dir="in">
+ <desc>The sample interval.</desc>
+ </param>
+ <param name="usSampleTime" type="long long" dir="in">
+ <desc>The number of microseconds to sample.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>The progress object on return.</desc>
+ </param>
+ </method>
+
+ <method name="getUVMAndVMMFunctionTable" wsmap="suppress">
+ <desc>
+ Gets the user-mode VM handle, with a reference, and the VMM function table.
+ The VM handle must be passed to VMR3ReleaseUVM when done. This can only
+ be called from within the VM process, remote calls will always fail.
+ </desc>
+ <param name="magicVersion" type="long long" dir="in">
+ <desc>The VMMR3VTABLE_MAGIC_VERSION value of the caller. The method
+ will fail if this is not compatible with the VMM function table.</desc>
+ </param>
+ <param name="VMMFunctionTable" type="long long" dir="out">
+ <desc>The VMM function table address.</desc>
+ </param>
+ <param name="UVM" type="long long" dir="return">
+ <desc>The user-mode VM handle.</desc>
+ </param>
+ </method>
+
+ <attribute name="singleStep" type="boolean">
+ <desc>Switch for enabling single-stepping.</desc>
+ </attribute>
+
+ <attribute name="executeAllInIEM" type="boolean">
+ <desc>
+ Whether to execute all the code in the instruction interpreter. This
+ is mainly for testing the interpreter and not an execution mode
+ intended for general consumption.
+ </desc>
+ </attribute>
+
+ <attribute name="logEnabled" type="boolean">
+ <desc>Switch for enabling and disabling the debug logger.</desc>
+ </attribute>
+
+ <attribute name="logDbgFlags" type="wstring" readonly="yes">
+ <desc>The debug logger flags.</desc>
+ </attribute>
+
+ <attribute name="logDbgGroups" type="wstring" readonly="yes">
+ <desc>The debug logger's group settings.</desc>
+ </attribute>
+
+ <attribute name="logDbgDestinations" type="wstring" readonly="yes">
+ <desc>The debug logger's destination settings.</desc>
+ </attribute>
+
+ <attribute name="logRelFlags" type="wstring" readonly="yes">
+ <desc>The release logger flags.</desc>
+ </attribute>
+
+ <attribute name="logRelGroups" type="wstring" readonly="yes">
+ <desc>The release logger's group settings.</desc>
+ </attribute>
+
+ <attribute name="logRelDestinations" type="wstring" readonly="yes">
+ <desc>The relase logger's destination settings.</desc>
+ </attribute>
+
+ <attribute name="executionEngine" type="VMExecutionEngine" readonly="yes">
+ <desc>Gets the main execution engine of the VM.</desc>
+ </attribute>
+
+ <attribute name="HWVirtExNestedPagingEnabled" type="boolean" readonly="yes">
+ <desc>
+ Flag indicating whether the VM is currently making use of the nested paging
+ CPU hardware virtualization extension.
+ </desc>
+ </attribute>
+
+ <attribute name="HWVirtExVPIDEnabled" type="boolean" readonly="yes">
+ <desc>
+ Flag indicating whether the VM is currently making use of the VPID
+ VT-x extension.
+ </desc>
+ </attribute>
+
+ <attribute name="HWVirtExUXEnabled" type="boolean" readonly="yes">
+ <desc>
+ Flag indicating whether the VM is currently making use of the
+ unrestricted execution feature of VT-x.
+ </desc>
+ </attribute>
+
+ <attribute name="OSName" type="wstring" readonly="yes">
+ <desc>
+ Query the guest OS kernel name as detected by the DBGF.
+
+ This feature is not implemented in the 4.0.0 release but may show up
+ in a dot release.
+ </desc>
+ </attribute>
+
+ <attribute name="OSVersion" type="wstring" readonly="yes">
+ <desc>
+ Query the guest OS kernel version string as detected by the DBGF.
+
+ This feature is not implemented in the 4.0.0 release but may show up
+ in a dot release.
+ </desc>
+ </attribute>
+
+ <attribute name="PAEEnabled" type="boolean" readonly="yes">
+ <desc>
+ Flag indicating whether the VM is currently making use of the Physical
+ Address Extension CPU feature.
+ </desc>
+ </attribute>
+
+ <attribute name="virtualTimeRate" type="unsigned long">
+ <desc>
+ The rate at which the virtual time runs expressed as a percentage.
+ The accepted range is 2% to 20000%.
+ </desc>
+ </attribute>
+
+ <attribute name="uptime" type="long long" readonly="yes">
+ <desc>VM uptime in milliseconds, i.e. time in which it could have been
+ executing guest code. Excludes the time when the VM was paused.</desc>
+ </attribute>
+
+ </interface>
+
+ <!--
+ // IUSBDeviceFilters
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IUSBDeviceFilters" extends="$unknown"
+ uuid="9709db9b-3346-49d6-8f1c-41b0c4784ff2"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="2" reservedAttributes="2"
+ >
+
+ <attribute name="deviceFilters" type="IUSBDeviceFilter" readonly="yes" safearray="yes">
+ <desc>
+ List of USB device filters associated with the machine.
+
+ If the machine is currently running, these filters are activated
+ every time a new (supported) USB device is attached to the host
+ computer that was not ignored by global filters
+ (<link to="IHost::USBDeviceFilters"/>).
+
+ These filters are also activated when the machine is powered up.
+ They are run against a list of all currently available USB
+ devices (in states
+ <link to="USBDeviceState_Available"/>,
+ <link to="USBDeviceState_Busy"/>,
+ <link to="USBDeviceState_Held"/>) that were not previously
+ ignored by global filters.
+
+ If at least one filter matches the USB device in question, this
+ device is automatically captured (attached to) the virtual USB
+ controller of this machine.
+
+ <see><link to="IUSBDeviceFilter"/>, <link to="IUSBController"/></see>
+ </desc>
+ </attribute>
+
+ <method name="createDeviceFilter">
+ <desc>
+ Creates a new USB device filter. All attributes except
+ the filter name are set to empty (any match),
+ <i>active</i> is @c false (the filter is not active).
+
+ The created filter can then be added to the list of filters using
+ <link to="#insertDeviceFilter"/>.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ The virtual machine is not mutable.
+ </result>
+
+ <see><link to="#deviceFilters"/></see>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>
+ Filter name. See <link to="IUSBDeviceFilter::name"/>
+ for more info.
+ </desc>
+ </param>
+ <param name="filter" type="IUSBDeviceFilter" dir="return">
+ <desc>Created filter object.</desc>
+ </param>
+ </method>
+
+ <method name="insertDeviceFilter">
+ <desc>
+ Inserts the given USB device to the specified position
+ in the list of filters.
+
+ Positions are numbered starting from <tt>0</tt>. If the specified
+ position is equal to or greater than the number of elements in
+ the list, the filter is added to the end of the collection.
+
+ <note>
+ Duplicates are not allowed, so an attempt to insert a
+ filter that is already in the collection, will return an
+ error.
+ </note>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine is not mutable.
+ </result>
+ <result name="E_INVALIDARG">
+ USB device filter not created within this VirtualBox instance.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ USB device filter already in list.
+ </result>
+
+ <see><link to="#deviceFilters"/></see>
+ </desc>
+ <param name="position" type="unsigned long" dir="in">
+ <desc>Position to insert the filter to.</desc>
+ </param>
+ <param name="filter" type="IUSBDeviceFilter" dir="in">
+ <desc>USB device filter to insert.</desc>
+ </param>
+ </method>
+
+ <method name="removeDeviceFilter">
+ <desc>
+ Removes a USB device filter from the specified position in the
+ list of filters.
+
+ Positions are numbered starting from <tt>0</tt>. Specifying a
+ position equal to or greater than the number of elements in
+ the list will produce an error.
+
+ <see><link to="#deviceFilters"/></see>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine is not mutable.
+ </result>
+ <result name="E_INVALIDARG">
+ USB device filter list empty or invalid @a position.
+ </result>
+
+ </desc>
+ <param name="position" type="unsigned long" dir="in">
+ <desc>Position to remove the filter from.</desc>
+ </param>
+ <param name="filter" type="IUSBDeviceFilter" dir="return">
+ <desc>Removed USB device filter.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <!--
+ // IUSBController
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="USBControllerType"
+ uuid="8fdd1c6a-5412-41da-ab07-7baed7d6e18e"
+ >
+ <desc>
+ The USB controller type. <link to="IUSBController::type" />.
+ </desc>
+ <const name="Null" value="0">
+ <desc>@c null value. Never used by the API.</desc>
+ </const>
+ <const name="OHCI" value="1"/>
+ <const name="EHCI" value="2"/>
+ <const name="XHCI" value="3"/>
+ <const name="Last" value="4">
+ <desc>Last element (invalid). Used for parameter checks.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IUSBController" extends="$unknown"
+ uuid="ee206a6e-7ff8-4a84-bd34-0c651e118bb5"
+ wsmap="managed"
+ rest="managed"
+ reservedAttributes="4"
+ >
+
+ <attribute name="name" type="wstring">
+ <desc>
+ The USB Controller name.
+ </desc>
+ </attribute>
+
+ <attribute name="type" type="USBControllerType">
+ <desc>
+ The USB Controller type.
+ </desc>
+ </attribute>
+
+ <attribute name="USBStandard" type="unsigned short" readonly="yes">
+ <desc>
+ USB standard version which the controller implements.
+ This is a BCD which means that the major version is in the
+ high byte and minor version is in the low byte.
+ </desc>
+ </attribute>
+
+ </interface>
+
+
+ <!--
+ // IUSBDevice
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="USBConnectionSpeed"
+ uuid="d2915840-ea26-4fb4-b72a-21eaf6b888ff"
+ >
+ <desc>
+ USB device/port speed state. This enumeration represents speeds at
+ which a USB device can communicate with the host.
+
+ The speed is a function of both the device itself and the port which
+ it is attached to, including hubs and cables in the path.
+
+ <note>
+ Due to differences in USB stack implementations on various hosts,
+ the reported speed may not exactly match the actual speed.
+ </note>
+
+ <see><link to="IHostUSBDevice"/></see>
+ </desc>
+
+ <const name="Null" value="0">
+ <desc>
+ @c null value. Never returned by the API.
+ </desc>
+ </const>
+ <const name="Low" value="1">
+ <desc>
+ Low speed, 1.5 Mbps.
+ </desc>
+ </const>
+ <const name="Full" value="2">
+ <desc>
+ Full speed, 12 Mbps.
+ </desc>
+ </const>
+ <const name="High" value="3">
+ <desc>
+ High speed, 480 Mbps.
+ </desc>
+ </const>
+ <const name="Super" value="4">
+ <desc>
+ SuperSpeed, 5 Gbps.
+ </desc>
+ </const>
+ <const name="SuperPlus" value="5">
+ <desc>
+ SuperSpeedPlus, 10 Gbps.
+ </desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IUSBDevice" extends="$unknown"
+ uuid="6dc83c2c-81a9-4005-9d52-fc45a78bf3f5"
+ wsmap="managed"
+ rest="managed"
+ reservedAttributes="4"
+ >
+ <desc>
+ The IUSBDevice interface represents a virtual USB device attached to the
+ virtual machine.
+
+ A collection of objects implementing this interface is stored in the
+ <link to="IConsole::USBDevices"/> attribute which lists all USB devices
+ attached to a running virtual machine's USB controller.
+ </desc>
+
+ <attribute name="id" type="uuid" mod="string" readonly="yes">
+ <desc>
+ Unique USB device ID. This ID is built from #vendorId,
+ #productId, #revision and #serialNumber.
+ </desc>
+ </attribute>
+
+ <attribute name="vendorId" type="unsigned short" readonly="yes">
+ <desc>Vendor ID.</desc>
+ </attribute>
+
+ <attribute name="productId" type="unsigned short" readonly="yes">
+ <desc>Product ID.</desc>
+ </attribute>
+
+ <attribute name="revision" type="unsigned short" readonly="yes">
+ <desc>
+ Product revision number. This is a packed BCD represented as
+ unsigned short. The high byte is the integer part and the low
+ byte is the decimal.
+ </desc>
+ </attribute>
+
+ <attribute name="manufacturer" type="wstring" readonly="yes">
+ <desc>Manufacturer string.</desc>
+ </attribute>
+
+ <attribute name="product" type="wstring" readonly="yes">
+ <desc>Product string.</desc>
+ </attribute>
+
+ <attribute name="serialNumber" type="wstring" readonly="yes">
+ <desc>Serial number string.</desc>
+ </attribute>
+
+ <attribute name="address" type="wstring" readonly="yes">
+ <desc>
+ Host-specific address of the device, uniquely
+ identifying a physically connected device in the system.
+ Note that the address of a USB device may change across
+ device re-plugs and host suspend/resume cycles or reboots.
+ </desc>
+ </attribute>
+
+ <attribute name="port" type="unsigned short" readonly="yes">
+ <desc>
+ Host USB port number on the hub the device is physically
+ connected to.
+ </desc>
+ </attribute>
+
+ <attribute name="portPath" type="wstring" readonly="yes">
+ <desc>
+ Host-specific identifier of the port (including hub) the USB
+ device is physically connected to. Note that hubs may be
+ dynamically added and removed, and that hub enumeration may not
+ be consistent across host reboots.
+ </desc>
+ </attribute>
+
+ <attribute name="version" type="unsigned short" readonly="yes">
+ <desc>
+ The major USB version of the device - 1, 2 or 3.
+ </desc>
+ </attribute>
+
+ <attribute name="speed" type="USBConnectionSpeed" readonly="yes">
+ <desc>
+ The speed at which the device is currently communicating.
+ </desc>
+ </attribute>
+
+ <attribute name="remote" type="boolean" readonly="yes">
+ <desc>
+ Whether the device is physically connected to a remote VRDE
+ client or to a local host machine.
+ </desc>
+ </attribute>
+
+ <attribute name="deviceInfo" type="wstring" readonly="yes" safearray="yes">
+ <desc>
+ Array of device attributes as single strings.
+
+ So far the following are used:
+ 0: The manufacturer string, if the device doesn't expose the ID one is taken
+ from an internal database or an empty string if none is found.
+ 1: The product string, if the device doesn't expose the ID one is taken
+ from an internal database or an empty string if none is found.
+ </desc>
+ </attribute>
+
+ <attribute name="backend" type="wstring" readonly="yes">
+ <desc>
+ The backend which will be used to communicate with this device.
+ </desc>
+ </attribute>
+
+ </interface>
+
+
+ <!--
+ // IUSBDeviceFilter
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IUSBDeviceFilter" extends="$unknown"
+ uuid="45587218-4289-ef4e-8e6a-e5b07816b631"
+ wsmap="managed"
+ rest="managed"
+ reservedAttributes="8"
+ >
+ <desc>
+ The IUSBDeviceFilter interface represents an USB device filter used
+ to perform actions on a group of USB devices.
+
+ This type of filters is used by running virtual machines to
+ automatically capture selected USB devices once they are physically
+ attached to the host computer.
+
+ A USB device is matched to the given device filter if and only if all
+ attributes of the device match the corresponding attributes of the
+ filter (that is, attributes are joined together using the logical AND
+ operation). On the other hand, all together, filters in the list of
+ filters carry the semantics of the logical OR operation. So if it is
+ desirable to create a match like "this vendor id OR this product id",
+ one needs to create two filters and specify "any match" (see below)
+ for unused attributes.
+
+ All filter attributes used for matching are strings. Each string
+ is an expression representing a set of values of the corresponding
+ device attribute, that will match the given filter. Currently, the
+ following filtering expressions are supported:
+
+ <ul>
+ <li><i>Interval filters</i>. Used to specify valid intervals for
+ integer device attributes (Vendor ID, Product ID and Revision).
+ The format of the string is:
+
+ <tt>int:((m)|([m]-[n]))(,(m)|([m]-[n]))*</tt>
+
+ where <tt>m</tt> and <tt>n</tt> are integer numbers, either in octal
+ (starting from <tt>0</tt>), hexadecimal (starting from <tt>0x</tt>)
+ or decimal (otherwise) form, so that <tt>m &lt; n</tt>. If <tt>m</tt>
+ is omitted before a dash (<tt>-</tt>), the minimum possible integer
+ is assumed; if <tt>n</tt> is omitted after a dash, the maximum
+ possible integer is assumed.
+ </li>
+ <li><i>Boolean filters</i>. Used to specify acceptable values for
+ boolean device attributes. The format of the string is:
+
+ <tt>true|false|yes|no|0|1</tt>
+
+ </li>
+ <li><i>Exact match</i>. Used to specify a single value for the given
+ device attribute. Any string that doesn't start with <tt>int:</tt>
+ represents the exact match. String device attributes are compared to
+ this string including case of symbols. Integer attributes are first
+ converted to a string (see individual filter attributes) and then
+ compared ignoring case.
+
+ </li>
+ <li><i>Any match</i>. Any value of the corresponding device attribute
+ will match the given filter. An empty or @c null string is
+ used to construct this type of filtering expressions.
+
+ </li>
+ </ul>
+
+ <note>
+ On the Windows host platform, interval filters are not currently
+ available. Also all string filter attributes
+ (<link to="#manufacturer"/>, <link to="#product"/>,
+ <link to="#serialNumber"/>) are ignored, so they behave as
+ <i>any match</i> no matter what string expression is specified.
+ </note>
+
+ <see><link to="IUSBDeviceFilters::deviceFilters"/>,
+ <link to="IHostUSBDeviceFilter"/></see>
+ </desc>
+
+ <attribute name="name" type="wstring">
+ <desc>
+ Visible name for this filter.
+ This name is used to visually distinguish one filter from another,
+ so it can neither be @c null nor an empty string.
+ </desc>
+ </attribute>
+
+ <attribute name="active" type="boolean">
+ <desc>Whether this filter active or has been temporarily disabled.</desc>
+ </attribute>
+
+ <attribute name="vendorId" type="wstring">
+ <desc>
+ <link to="IUSBDevice::vendorId">Vendor ID</link> filter.
+ The string representation for the <i>exact matching</i>
+ has the form <tt>XXXX</tt>, where <tt>X</tt> is the hex digit
+ (including leading zeroes).
+ </desc>
+ </attribute>
+
+ <attribute name="productId" type="wstring">
+ <desc>
+ <link to="IUSBDevice::productId">Product ID</link> filter.
+ The string representation for the <i>exact matching</i>
+ has the form <tt>XXXX</tt>, where <tt>X</tt> is the hex digit
+ (including leading zeroes).
+ </desc>
+ </attribute>
+
+ <attribute name="revision" type="wstring">
+ <desc>
+ <link to="IUSBDevice::productId">Product revision number</link>
+ filter. The string representation for the <i>exact matching</i>
+ has the form <tt>IIFF</tt>, where <tt>I</tt> is the decimal digit
+ of the integer part of the revision, and <tt>F</tt> is the
+ decimal digit of its fractional part (including leading and
+ trailing zeros).
+ Note that for interval filters, it's best to use the hexadecimal
+ form, because the revision is stored as a 16 bit packed BCD value;
+ so the expression <tt>int:0x0100-0x0199</tt> will match any
+ revision from <tt>1.0</tt> to <tt>1.99</tt>.
+ </desc>
+ </attribute>
+
+ <attribute name="manufacturer" type="wstring">
+ <desc>
+ <link to="IUSBDevice::manufacturer">Manufacturer</link> filter.
+ </desc>
+ </attribute>
+
+ <attribute name="product" type="wstring">
+ <desc>
+ <link to="IUSBDevice::product">Product</link> filter.
+ </desc>
+ </attribute>
+
+ <attribute name="serialNumber" type="wstring">
+ <desc>
+ <link to="IUSBDevice::serialNumber">Serial number</link> filter.
+ </desc>
+ </attribute>
+
+ <attribute name="port" type="wstring">
+ <desc>
+ <link to="IUSBDevice::port">Host USB port</link> filter.
+ </desc>
+ </attribute>
+
+ <attribute name="remote" type="wstring">
+ <desc>
+ <link to="IUSBDevice::remote">Remote state</link> filter.
+ <note>
+ This filter makes sense only for machine USB filters,
+ i.e. it is ignored by IHostUSBDeviceFilter objects.
+ </note>
+ </desc>
+ </attribute>
+
+ <attribute name="maskedInterfaces" type="unsigned long">
+ <desc>
+ This is an advanced option for hiding one or more USB interfaces
+ from the guest. The value is a bit mask where the bits that are set
+ means the corresponding USB interface should be hidden, masked off
+ if you like.
+ This feature only works on Linux hosts.
+ </desc>
+ </attribute>
+
+ </interface>
+
+
+ <!--
+ // IHostUSBDevice
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="USBDeviceState"
+ uuid="b99a2e65-67fb-4882-82fd-f3e5e8193ab4"
+ >
+ <desc>
+ USB device state. This enumeration represents all possible states
+ of the USB device physically attached to the host computer regarding
+ its state on the host computer and availability to guest computers
+ (all currently running virtual machines).
+
+ Once a supported USB device is attached to the host, global USB
+ filters (<link to="IHost::USBDeviceFilters"/>) are activated. They can
+ either ignore the device, or put it to USBDeviceState_Held state, or do
+ nothing. Unless the device is ignored by global filters, filters of all
+ currently running guests (<link to="IUSBDeviceFilters::deviceFilters"/>) are
+ activated that can put it to USBDeviceState_Captured state.
+
+ If the device was ignored by global filters, or didn't match
+ any filters at all (including guest ones), it is handled by the host
+ in a normal way. In this case, the device state is determined by
+ the host and can be one of USBDeviceState_Unavailable, USBDeviceState_Busy
+ or USBDeviceState_Available, depending on the current device usage.
+
+ Besides auto-capturing based on filters, the device can be manually
+ captured by guests (<link to="IConsole::attachUSBDevice"/>) if its
+ state is USBDeviceState_Busy, USBDeviceState_Available or
+ USBDeviceState_Held.
+
+ <note>
+ Due to differences in USB stack implementations in Linux and Win32,
+ states USBDeviceState_Busy and USBDeviceState_Unavailable are applicable
+ only to the Linux version of the product. This also means that (<link
+ to="IConsole::attachUSBDevice"/>) can only succeed on Win32 if the
+ device state is USBDeviceState_Held.
+ </note>
+
+ <see><link to="IHostUSBDevice"/>, <link to="IHostUSBDeviceFilter"/></see>
+ </desc>
+
+ <const name="NotSupported" value="0">
+ <desc>
+ Not supported by the VirtualBox server, not available to guests.
+ </desc>
+ </const>
+ <const name="Unavailable" value="1">
+ <desc>
+ Being used by the host computer exclusively,
+ not available to guests.
+ </desc>
+ </const>
+ <const name="Busy" value="2">
+ <desc>
+ Being used by the host computer, potentially available to guests.
+ </desc>
+ </const>
+ <const name="Available" value="3">
+ <desc>
+ Not used by the host computer, available to guests (the host computer
+ can also start using the device at any time).
+ </desc>
+ </const>
+ <const name="Held" value="4">
+ <desc>
+ Held by the VirtualBox server (ignored by the host computer),
+ available to guests.
+ </desc>
+ </const>
+ <const name="Captured" value="5">
+ <desc>
+ Captured by one of the guest computers, not available
+ to anybody else.
+ </desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IHostUSBDevice" extends="IUSBDevice"
+ uuid="c19073dd-cc7b-431b-98b2-951fda8eab89"
+ wsmap="managed"
+ rest="managed"
+ reservedAttributes="4"
+ >
+ <desc>
+ The IHostUSBDevice interface represents a physical USB device attached
+ to the host computer.
+
+ Besides properties inherited from IUSBDevice, this interface adds the
+ <link to="#state"/> property that holds the current state of the USB
+ device.
+
+ <see><link to="IHost::USBDevices"/>,
+ <link to="IHost::USBDeviceFilters"/></see>
+ </desc>
+
+ <attribute name="state" type="USBDeviceState" readonly="yes">
+ <desc>
+ Current state of the device.
+ </desc>
+ </attribute>
+
+ <!-- @todo add class, subclass, bandwidth, configs, interfaces endpoints and such later. -->
+
+ </interface>
+
+
+ <!--
+ // IHostUSBDeviceFilter
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="USBDeviceFilterAction"
+ uuid="cbc30a49-2f4e-43b5-9da6-121320475933"
+ >
+ <desc>
+ Actions for host USB device filters.
+ <see><link to="IHostUSBDeviceFilter"/>, <link to="USBDeviceState"/></see>
+ </desc>
+
+ <const name="Null" value="0">
+ <desc>Null value (never used by the API).</desc>
+ </const>
+ <const name="Ignore" value="1">
+ <desc>Ignore the matched USB device.</desc>
+ </const>
+ <const name="Hold" value="2">
+ <desc>Hold the matched USB device.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IHostUSBDeviceFilter" extends="IUSBDeviceFilter"
+ uuid="01adb2d6-aedf-461c-be2c-99e91bdad8a1"
+ wsmap="managed"
+ rest="managed"
+ reservedAttributes="8"
+ >
+ <desc>
+ The IHostUSBDeviceFilter interface represents a global filter for a
+ physical USB device used by the host computer. Used indirectly in
+ <link to="IHost::USBDeviceFilters"/>.
+
+ Using filters of this type, the host computer determines the initial
+ state of the USB device after it is physically attached to the
+ host's USB controller.
+
+ <note>
+ The <link to="IUSBDeviceFilter::remote"/> attribute is ignored by this type of
+ filters, because it makes sense only for
+ <link to="IUSBDeviceFilters::deviceFilters">machine USB filters</link>.
+ </note>
+
+ <see><link to="IHost::USBDeviceFilters"/></see>
+ </desc>
+
+ <attribute name="action" type="USBDeviceFilterAction">
+ <desc>
+ Action performed by the host when an attached USB device
+ matches this filter.
+ </desc>
+ </attribute>
+
+ </interface>
+
+
+ <!--
+ // IUSBProxyBackend
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IUSBProxyBackend" extends="$unknown"
+ uuid="dfe56449-6989-4002-80cf-3607f377d40c"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ The USBProxyBackend interface represents a source for USB devices available
+ to the host for attaching to the VM.
+ </desc>
+
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>
+ The unique name of the proxy backend.
+ </desc>
+ </attribute>
+
+ <attribute name="type" type="wstring" readonly="yes">
+ <desc>
+ The type of the backend.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <!--
+ // IAudioAdapter
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="AudioDriverType"
+ uuid="32b4acfd-79ab-4b7e-9a1c-92e99f4e000b"
+ >
+ <desc>
+ Host audio driver type.
+ </desc>
+
+ <const name="Default" value="0">
+ <desc>Use the default audio driver automatically determined for the host
+ that this VirtualBox instance is running on.
+ Useful for VMs which need to run on different host OSes.
+ </desc>
+ </const>
+ <!-- When changing the enum anyway, keep the enum grouped per platforms, starting
+ from oldest audio stack to newest one (if possible). -->
+ <const name="Null" value="1">
+ <desc>Null value, also means "dummy audio driver".</desc>
+ </const>
+ <const name="OSS" value="2">
+ <desc>Open Sound System (Linux / Unix hosts only).</desc>
+ </const>
+ <const name="ALSA" value="3">
+ <desc>Advanced Linux Sound Architecture (Linux hosts only).</desc>
+ </const>
+ <const name="Pulse" value="4">
+ <desc>PulseAudio (Linux hosts only).</desc>
+ </const>
+ <const name="WinMM" value="5">
+ <desc>Windows multimedia (Windows hosts only, not supported at the moment).</desc>
+ </const>
+ <const name="DirectSound" value="6">
+ <desc>DirectSound (Windows hosts only).</desc>
+ </const>
+ <const name="WAS" value="7">
+ <desc>Windows Audio Session (Windows hosts only).</desc>
+ </const>
+ <const name="CoreAudio" value="8">
+ <desc>CoreAudio (Mac hosts only).</desc>
+ </const>
+ <const name="MMPM" value="9">
+ <desc>Reserved for historical reasons.</desc>
+ </const>
+ <const name="SolAudio" value="10">
+ <desc>Reserved for historical reasons.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="AudioControllerType"
+ uuid="7afd395c-42c3-444e-8788-3ce80292f36c"
+ >
+ <desc>
+ Virtual audio controller type.
+ </desc>
+
+ <const name="AC97" value="0"/>
+ <const name="SB16" value="1"/>
+ <const name="HDA" value="2"/>
+ </enum>
+
+ <enum
+ name="AudioCodecType"
+ uuid="7b406301-f520-420c-9805-8ce11c086370"
+ >
+ <desc>
+ The exact variant of audio codec hardware presented
+ to the guest; see <link to="IAudioAdapter::audioCodec" />.
+ </desc>
+
+ <const name="Null" value="0">
+ <desc>@c null value. Never used by the API.</desc>
+ </const>
+ <const name="SB16" value="1">
+ <desc>SB16; this is the only option for the SB16 device.</desc>
+ </const>
+ <const name="STAC9700" value="2">
+ <desc>A STAC9700 AC'97 codec.</desc>
+ </const>
+ <const name="AD1980" value="3">
+ <desc>An AD1980 AC'97 codec. Recommended for Linux guests.</desc>
+ </const>
+ <const name="STAC9221" value="4">
+ <desc>A STAC9221 HDA codec.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IAudioAdapter" extends="$unknown"
+ uuid="5155bfd3-7ba7-45a8-b26d-c91ae3754e37"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ The IAudioAdapter interface represents the virtual audio adapter of
+ the virtual machine. Used in <link to="IAudioSettings::adapter"/>.
+ </desc>
+ <attribute name="enabled" type="boolean">
+ <desc>
+ Flag whether the audio adapter is present in the
+ guest system. If disabled, the virtual guest hardware will
+ not contain any audio adapter. Can only be changed when
+ the VM is not running.
+ </desc>
+ </attribute>
+ <attribute name="enabledIn" type="boolean">
+ <desc>
+ Flag whether the audio adapter is enabled for audio
+ input. Only relevant if the adapter is enabled.
+ </desc>
+ </attribute>
+ <attribute name="enabledOut" type="boolean">
+ <desc>
+ Flag whether the audio adapter is enabled for audio
+ output. Only relevant if the adapter is enabled.
+ </desc>
+ </attribute>
+ <attribute name="audioController" type="AudioControllerType">
+ <desc>
+ The emulated audio controller.
+ </desc>
+ </attribute>
+ <attribute name="audioCodec" type="AudioCodecType">
+ <desc>
+ The exact variant of audio codec hardware presented
+ to the guest.
+ For HDA and SB16, only one variant is available, but for AC'97,
+ there are several.
+ </desc>
+ </attribute>
+ <attribute name="audioDriver" type="AudioDriverType">
+ <desc>
+ Audio driver the adapter is connected to. This setting
+ can only be changed when the VM is not running.
+ </desc>
+ </attribute>
+ <attribute name="propertiesList" type="wstring" readonly="yes" safearray="yes">
+ <desc>
+ Array of names of tunable properties, which can be supported by audio driver.
+ </desc>
+ </attribute>
+
+ <method name="setProperty">
+ <desc>
+ Sets an audio specific property string.
+
+ If you pass @c null or empty string as a key @a value, the given @a key
+ will be deleted.
+
+ </desc>
+ <param name="key" type="wstring" dir="in">
+ <desc>Name of the key to set.</desc>
+ </param>
+ <param name="value" type="wstring" dir="in">
+ <desc>Value to assign to the key.</desc>
+ </param>
+ </method>
+
+ <method name="getProperty" const="yes">
+ <desc>
+ Returns an audio specific property string.
+
+ If the requested data @a key does not exist, this function will
+ succeed and return an empty string in the @a value argument.
+
+ </desc>
+ <param name="key" type="wstring" dir="in">
+ <desc>Name of the key to get.</desc>
+ </param>
+ <param name="value" type="wstring" dir="return">
+ <desc>Value of the requested key.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <enum
+ name="AudioDirection"
+ uuid="f27d209b-040c-4ae9-beae-85f9693ca07a"
+ >
+ <desc>
+ Audio direction enumeration.
+ </desc>
+
+ <const name="Unknown" value="0">
+ <desc>Direction cannot be determined.</desc>
+ </const>
+ <const name="In" value="1">
+ <desc>Input (Recording).</desc>
+ </const>
+ <const name="Out" value="2">
+ <desc>Output (Playback).</desc>
+ </const>
+ <const name="Duplex" value="3">
+ <desc>Duplex (Recording + Playback).</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="AudioDeviceType"
+ uuid="95457071-ef88-45a9-8416-fc05b08731d2"
+ >
+ <desc>
+ Audio device type enumeration.
+ </desc>
+
+ <const name="Unknown" value="0">
+ <desc>Device type is unknown / cannot be determined</desc>
+ </const>
+ <const name="BuiltLin" value="1">
+ <desc>Built-in device (cannot be removed).</desc>
+ </const>
+ <const name="ExternalUSB" value="2">
+ <desc>External device, connected via USB.</desc>
+ </const>
+ <const name="ExternalOther" value="3">
+ <desc>External device, connected via some other method.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="AudioDeviceState"
+ uuid="45c1b969-556a-4277-8570-b438d2ef5ebe"
+ >
+ <desc>
+ Audio device state enumeration.
+ </desc>
+
+ <const name="Unknown" value="0">
+ <desc>Device state is unknown / cannot be determined</desc>
+ </const>
+ <const name="Active" value="1">
+ <desc>Device is active and can be used.</desc>
+ </const>
+ <const name="Disabled" value="2">
+ <desc>Device is in a disabled state.</desc>
+ </const>
+ <const name="NotPresent" value="3">
+ <desc>Device is marked as not being present.</desc>
+ </const>
+ <const name="Unplugged" value="4">
+ <desc>Device has been unplugged.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IHostAudioDevice" extends="$unknown"
+ uuid="cfde1265-3140-4048-a81f-a1e280dfbd75"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ Represents an audio device provided by the host OS.
+ </desc>
+
+ <attribute name="id" type="uuid" mod="string" readonly="yes">
+ <desc>
+ Unique device ID.
+ </desc>
+ </attribute>
+
+ <attribute name="name" type="wstring">
+ <desc>
+ Friendly name of the device.
+ </desc>
+ </attribute>
+ <attribute name="type" type="AudioDeviceType">
+ <desc>
+ Device type.
+ </desc>
+ </attribute>
+ <attribute name="usage" type="AudioDirection">
+ <desc>
+ Usage type of the device.
+ </desc>
+ </attribute>
+ <attribute name="defaultIn" type="boolean">
+ <desc>
+ Whether this device is being marked as the default input
+ device by the host OS.
+ </desc>
+ </attribute>
+ <attribute name="defaultOut" type="boolean">
+ <desc>
+ Whether this device is being marked as the default output
+ device by the host OS.
+ </desc>
+ </attribute>
+ <attribute name="isHotPlug" type="boolean">
+ <desc>
+ Whether this device is being marked as a hotplug device, i.e. can
+ be removed from the system.
+ </desc>
+ </attribute>
+ <attribute name="state" type="AudioDeviceState">
+ <desc>
+ Current device state.
+ </desc>
+ </attribute>
+ <method name="getProperty" const="yes">
+ <desc>
+ Returns an audio specific property string.
+
+ If the requested data @a key does not exist, this function will
+ succeed and return an empty string in the @a value argument.
+
+ </desc>
+ <param name="key" type="wstring" dir="in">
+ <desc>Name of the key to get.</desc>
+ </param>
+ <param name="value" type="wstring" dir="return">
+ <desc>Value of the requested key.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IAudioSettings" extends="$unknown"
+ uuid="52f40b16-520e-473f-9428-3e69b0d915c3"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ The IAudioSettings interface represents the audio settings for a virtual machine.
+ </desc>
+
+ <method name="getHostAudioDevice">
+ <desc>
+ Returns the machine's current host audio device for the specified usage.
+ <result name="E_NOTIMPL">
+ This method is not implemented yet.
+ </result>
+ </desc>
+ <param name="usage" type="AudioDirection" dir="in">
+ <desc>Usage to retrieve audio device for.</desc>
+ </param>
+ <param name="device" type="IHostAudioDevice" dir="return">
+ <desc>Host audio device for the specified direction.</desc>
+ </param>
+ </method>
+
+ <method name="setHostAudioDevice">
+ <desc>
+ Sets the machine's current host audio device for the specified usage.
+ <result name="E_NOTIMPL">
+ This method is not implemented yet.
+ </result>
+ </desc>
+ <param name="device" type="IHostAudioDevice" dir="in">
+ <desc>Sets the host audio device for the specified usage.</desc>
+ </param>
+ <param name="usage" type="AudioDirection" dir="in">
+ <desc>Usage to set audio device for.</desc>
+ </param>
+ </method>
+
+ <attribute name="adapter" type="IAudioAdapter" readonly="yes">
+ <desc>Associated audio adapter, always present.</desc>
+ </attribute>
+
+ </interface>
+
+ <enum
+ name="AuthType"
+ uuid="7eef6ef6-98c2-4dc2-ab35-10d2b292028d"
+ >
+ <desc>
+ VirtualBox authentication type.
+ </desc>
+
+ <const name="Null" value="0">
+ <desc>Null value, also means "no authentication".</desc>
+ </const>
+ <const name="External" value="1"/>
+ <const name="Guest" value="2"/>
+ </enum>
+
+ <!--
+ // IVRDEServer
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IVRDEServer" extends="$unknown"
+ uuid="08e25756-08a2-41af-a05f-d7c661abaebe"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="2" reservedAttributes="4"
+ >
+
+ <attribute name="enabled" type="boolean">
+ <desc>Flag if VRDE server is enabled.</desc>
+ </attribute>
+
+ <attribute name="authType" type="AuthType">
+ <desc>VRDE authentication method.</desc>
+ </attribute>
+
+ <attribute name="authTimeout" type="unsigned long">
+ <desc>Timeout for guest authentication. Milliseconds.</desc>
+ </attribute>
+
+ <attribute name="allowMultiConnection" type="boolean">
+ <desc>
+ Flag whether multiple simultaneous connections to the VM are permitted.
+ Note that this will be replaced by a more powerful mechanism in the future.
+ </desc>
+ </attribute>
+
+ <attribute name="reuseSingleConnection" type="boolean">
+ <desc>
+ Flag whether the existing connection must be dropped and a new connection
+ must be established by the VRDE server, when a new client connects in single
+ connection mode.
+ </desc>
+ </attribute>
+
+ <attribute name="VRDEExtPack" type="wstring">
+ <desc>
+ The name of Extension Pack providing VRDE for this VM. Overrides
+ <link to="ISystemProperties::defaultVRDEExtPack"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="authLibrary" type="wstring">
+ <desc>
+ Library used for authentication of RDP clients by this VM. Overrides
+ <link to="ISystemProperties::VRDEAuthLibrary"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="VRDEProperties" type="wstring" readonly="yes" safearray="yes">
+ <desc>
+ Array of names of properties, which are supported by this VRDE server.
+ </desc>
+ </attribute>
+
+ <method name="setVRDEProperty">
+ <desc>
+ Sets a VRDE specific property string.
+
+ If you pass @c null or empty string as a key @a value, the given @a key
+ will be deleted.
+
+ </desc>
+ <param name="key" type="wstring" dir="in">
+ <desc>Name of the key to set.</desc>
+ </param>
+ <param name="value" type="wstring" dir="in">
+ <desc>Value to assign to the key.</desc>
+ </param>
+ </method>
+
+ <method name="getVRDEProperty" const="yes">
+ <desc>
+ Returns a VRDE specific property string.
+
+ If the requested data @a key does not exist, this function will
+ succeed and return an empty string in the @a value argument.
+
+ </desc>
+ <param name="key" type="wstring" dir="in">
+ <desc>Name of the key to get.</desc>
+ </param>
+ <param name="value" type="wstring" dir="return">
+ <desc>Value of the requested key.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+
+ <!--
+ // ISharedFolder
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="ISharedFolder" extends="$unknown"
+ uuid="9622225a-5409-414b-bd16-77df7ba3451e"
+ wsmap="managed"
+ rest="managed"
+ reservedAttributes="8"
+ >
+ <desc>
+ The ISharedFolder interface represents a folder in the host computer's
+ file system accessible from the guest OS running inside a virtual
+ machine using an associated logical name.
+
+ There are three types of shared folders:
+ <ul>
+ <li><i>Global</i> (<link to="IVirtualBox::sharedFolders"/>), shared
+ folders available to all virtual machines.</li>
+ <li><i>Permanent</i> (<link to="IMachine::sharedFolders"/>),
+ VM-specific shared folders available to the given virtual machine at
+ startup.</li>
+ <li><i>Transient</i> (<link to="IConsole::sharedFolders"/>),
+ VM-specific shared folders created in the session context (for
+ example, when the virtual machine is running) and automatically
+ discarded when the session is closed (the VM is powered off).</li>
+ </ul>
+
+ Logical names of shared folders must be unique within the given scope
+ (global, permanent or transient). However, they do not need to be unique
+ across scopes. In this case, the definition of the shared folder in a
+ more specific scope takes precedence over definitions in all other
+ scopes. The order of precedence is (more specific to more general):
+ <ol>
+ <li>Transient definitions</li>
+ <li>Permanent definitions</li>
+ <li>Global definitions</li>
+ </ol>
+
+ For example, if MyMachine has a shared folder named
+ <tt>C_DRIVE</tt> (that points to <tt>C:\\</tt>), then creating a
+ transient shared folder named <tt>C_DRIVE</tt> (that points
+ to <tt>C:\\\\WINDOWS</tt>) will change the definition
+ of <tt>C_DRIVE</tt> in the guest OS so
+ that <tt>\\\\VBOXSVR\\C_DRIVE</tt> will give access
+ to <tt>C:\\WINDOWS</tt> instead of <tt>C:\\</tt> on the host
+ PC. Removing the transient shared folder <tt>C_DRIVE</tt> will restore
+ the previous (permanent) definition of <tt>C_DRIVE</tt> that points
+ to <tt>C:\\</tt> if it still exists.
+
+ Note that permanent and transient shared folders of different machines
+ are in different name spaces, so they don't overlap and don't need to
+ have unique logical names.
+
+ <note>
+ Global shared folders are not implemented in the current version of the
+ product.
+ </note>
+ </desc>
+
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>Logical name of the shared folder.</desc>
+ </attribute>
+
+ <attribute name="hostPath" type="wstring" readonly="yes">
+ <desc>Full path to the shared folder in the host file system.</desc>
+ </attribute>
+
+ <attribute name="accessible" type="boolean" readonly="yes">
+ <desc>
+ Whether the folder defined by the host path is currently
+ accessible or not.
+
+ For example, the folder can be inaccessible if it is placed
+ on the network share that is not available by the time
+ this property is read.
+ </desc>
+ </attribute>
+
+ <attribute name="writable" type="boolean">
+ <desc>
+ Whether the folder defined by the host path is writable or
+ not.
+ </desc>
+ </attribute>
+
+ <attribute name="autoMount" type="boolean">
+ <desc>
+ Whether the folder gets automatically mounted by the guest or not.
+ </desc>
+ </attribute>
+
+ <attribute name="autoMountPoint" type="wstring">
+ <desc>
+ Desired mount point in the guest for automatically mounting the folder
+ when <link to="ISharedFolder::autoMount"/> is set. For Windows and
+ OS/2 guests this should be a drive letter, while other guests it should
+ be a absolute directory. It is possible to combine the two, e.g.
+ "T:/mnt/testrsrc" will be attached to "T:" by windows and OS/2 while
+ the unixy guests will mount it at "/mnt/testrsrc".
+
+ When empty the guest will choose a mount point. The guest may do so
+ too should the specified mount point be in use or otherwise unusable.
+ </desc>
+ </attribute>
+
+ <attribute name="lastAccessError" type="wstring" readonly="yes">
+ <desc>
+ Text message that represents the result of the last accessibility
+ check.
+
+ Accessibility checks are performed each time the <link to="#accessible"/>
+ attribute is read. An empty string is returned if the last
+ accessibility check was successful. A non-empty string indicates a
+ failure and should normally describe a reason of the failure (for
+ example, a file read error).
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <!--
+ // ISession
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="Reason"
+ uuid="e7e8e097-299d-4e98-8bbc-c31c2d47d0cc"
+ >
+ <desc>
+ Internal event reason type.
+ </desc>
+
+ <const name="Unspecified" value="0">
+ <desc>Null value, means "no known reason".</desc>
+ </const>
+ <const name="HostSuspend" value="1">
+ <desc>Host is being suspended (power management event).</desc>
+ </const>
+ <const name="HostResume" value="2">
+ <desc>Host is being resumed (power management event).</desc>
+ </const>
+ <const name="HostBatteryLow" value="3">
+ <desc>Host is running low on battery (power management event).</desc>
+ </const>
+ <const name="Snapshot" value="4">
+ <desc>A snapshot of the VM is being taken.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IInternalSessionControl" extends="$unknown"
+ uuid="5045c372-2e8f-4d9e-ad9d-121ab1661146"
+ internal="yes"
+ wsmap="suppress"
+ >
+ <attribute name="PID" type="unsigned long" readonly="yes">
+ <desc>PID of the process that has created this Session object.
+ </desc>
+ </attribute>
+
+ <attribute name="remoteConsole" type="IConsole" readonly="yes">
+ <desc>
+ Returns the console object suitable for remote control.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ </attribute>
+
+ <attribute name="nominalState" type="MachineState" readonly="yes">
+ <desc>Returns suitable machine state for the VM execution state. Useful
+ for choosing a sensible machine state after a complex operation which
+ failed or otherwise resulted in an unclear situation.
+ </desc>
+ </attribute>
+
+<if target="midl">
+ <method name="assignMachine">
+ <desc>
+ Assigns the machine object associated with this direct-type
+ session or informs the session that it will be a remote one
+ (if @a machine == @c null).
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="machine" type="IMachine" dir="in"/>
+ <param name="lockType" type="LockType" dir="in"/>
+ <param name="tokenId" type="wstring" dir="in"/>
+ </method>
+</if>
+<if target="xpidl">
+ <method name="assignMachine">
+ <desc>
+ Assigns the machine object associated with this direct-type
+ session or informs the session that it will be a remote one
+ (if @a machine == @c null).
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="machine" type="IMachine" dir="in"/>
+ <param name="lockType" type="LockType" dir="in"/>
+ <param name="token" type="IToken" dir="in"/>
+ </method>
+</if>
+
+ <method name="assignRemoteMachine">
+ <desc>
+ Assigns the machine and the (remote) console object associated with
+ this remote-type session.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+
+ </desc>
+ <param name="machine" type="IMachine" dir="in"/>
+ <param name="console" type="IConsole" dir="in"/>
+ </method>
+
+ <method name="updateMachineState">
+ <desc>
+ Updates the machine state in the VM process.
+ Must be called only in certain cases
+ (see the method implementation).
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="machineState" type="MachineState" dir="in"/>
+ </method>
+
+ <method name="uninitialize">
+ <desc>
+ Uninitializes (closes) this session. Used by VirtualBox to close
+ the corresponding remote session when the direct session dies
+ or gets closed.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+
+ </desc>
+ </method>
+
+ <method name="onNetworkAdapterChange">
+ <desc>
+ Triggered when settings of a network adapter of the
+ associated virtual machine have changed.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="networkAdapter" type="INetworkAdapter" dir="in"/>
+ <param name="changeAdapter" type="boolean" dir="in"/>
+ </method>
+
+ <method name="onAudioAdapterChange">
+ <desc>
+ Triggerd when settings of the audio adapter of the
+ associated virtual machine have changed.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="audioAdapter" type="IAudioAdapter" dir="in"/>
+ </method>
+
+ <method name="onHostAudioDeviceChange">
+ <desc>
+ Triggerd when the state of a host audio device
+ has changed.
+ </desc>
+ <param name="device" type="IHostAudioDevice" dir="in" readonly="yes">
+ <desc>Device the state has been changed for.</desc>
+ </param>
+ <param name="isNew" type="boolean" dir="in" readonly="yes">
+ <desc>Whether this is a newly detected device or not.</desc>
+ </param>
+ <param name="state" type="AudioDeviceState" dir="in" readonly="yes">
+ <desc>The new device state.</desc>
+ </param>
+ <param name="errorInfo" type="IVirtualBoxErrorInfo" dir="in" readonly="yes">
+ <desc>Error information.</desc>
+ </param>
+ </method>
+
+ <method name="onSerialPortChange">
+ <desc>
+ Triggered when settings of a serial port of the
+ associated virtual machine have changed.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="serialPort" type="ISerialPort" dir="in"/>
+ </method>
+
+ <method name="onParallelPortChange">
+ <desc>
+ Triggered when settings of a parallel port of the
+ associated virtual machine have changed.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="parallelPort" type="IParallelPort" dir="in"/>
+ </method>
+
+ <method name="onStorageControllerChange">
+ <desc>
+ Triggered when settings of a storage controller of the
+ associated virtual machine have changed.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="machineId" type="uuid" mod="string" dir="in">
+ <desc>The id of the machine containing the storage controller.</desc>
+ </param>
+ <param name="controllerName" type="wstring" dir="in">
+ <desc>The name of the storage controller.</desc>
+ </param>
+ </method>
+
+ <method name="onMediumChange">
+ <desc>
+ Triggered when attached media of the
+ associated virtual machine have changed.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+
+ <param name="mediumAttachment" type="IMediumAttachment" dir="in">
+ <desc>The medium attachment which changed.</desc>
+ </param>
+ <param name="force" type="boolean" dir="in">
+ <desc>If the medium change was forced.</desc>
+ </param>
+ </method>
+
+ <method name="onStorageDeviceChange">
+ <desc>
+ Triggered when attached storage devices of the
+ associated virtual machine have changed.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+
+ <param name="mediumAttachment" type="IMediumAttachment" dir="in">
+ <desc>The medium attachment which changed.</desc>
+ </param>
+ <param name="remove" type="boolean" dir="in">
+ <desc>TRUE if the device is removed, FALSE if it was added.</desc>
+ </param>
+ <param name="silent" type="boolean" dir="in">
+ <desc>TRUE if the device is is silently reconfigured without
+ notifying the guest about it.</desc>
+ </param>
+ </method>
+
+ <method name="onVMProcessPriorityChange">
+ <desc>
+ Triggered when process priority of the
+ associated virtual machine have changed.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+ <result name="VBOX_E_VM_ERROR">
+ Error from underlying level. See additional error info.
+ </result>
+
+ </desc>
+
+ <param name="priority" type="VMProcPriority" dir="in">
+ <desc>The priority which set.</desc>
+ </param>
+ </method>
+
+ <method name="onClipboardModeChange">
+ <desc>
+ Notification when the shared clipboard mode changes.
+ </desc>
+ <param name="clipboardMode" type="ClipboardMode" dir="in">
+ <desc>The new shared clipboard mode.</desc>
+ </param>
+ </method>
+
+ <method name="onClipboardFileTransferModeChange">
+ <desc>
+ Notification when the shared clipboard file transfers mode changes.
+ </desc>
+ <param name="enabled" type="boolean" dir="in">
+ <desc>Flag whether clipboard file transfers are allowed or not.</desc>
+ </param>
+ </method>
+
+ <method name="onDnDModeChange">
+ <desc>
+ Notification when the drag'n drop mode changes.
+ </desc>
+ <param name="dndMode" type="DnDMode" dir="in">
+ <desc>The new mode for drag'n drop.</desc>
+ </param>
+ </method>
+
+ <method name="onCPUChange">
+ <desc>
+ Notification when a CPU changes.
+ </desc>
+ <param name="cpu" type="unsigned long" dir="in">
+ <desc>The CPU which changed</desc>
+ </param>
+ <param name="add" type="boolean" dir="in">
+ <desc>Flag whether the CPU was added or removed</desc>
+ </param>
+ </method>
+
+ <method name="onCPUExecutionCapChange">
+ <desc>
+ Notification when the CPU execution cap changes.
+ </desc>
+ <param name="executionCap" type="unsigned long" dir="in">
+ <desc>The new CPU execution cap value. (1-100)</desc>
+ </param>
+ </method>
+
+ <method name="onVRDEServerChange">
+ <desc>
+ Triggered when settings of the VRDE server object of the
+ associated virtual machine have changed.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="restart" type="boolean" dir="in">
+ <desc>Flag whether the server must be restarted</desc>
+ </param>
+ </method>
+
+ <method name="onRecordingChange">
+ <desc>
+ Triggered when recording settings have changed.
+ </desc>
+ <param name="enable" type="boolean" dir="in">
+ <desc>TODO</desc>
+ </param>
+ </method>
+
+ <method name="onUSBControllerChange">
+ <desc>
+ Triggered when settings of the USB controller object of the
+ associated virtual machine have changed.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ </method>
+
+ <method name="onSharedFolderChange">
+ <desc>
+ Triggered when a permanent (global or machine) shared folder has been
+ created or removed.
+ <note>
+ We don't pass shared folder parameters in this notification because
+ the order in which parallel notifications are delivered is not defined,
+ therefore it could happen that these parameters were outdated by the
+ time of processing this notification.
+ </note>
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="global" type="boolean" dir="in"/>
+ </method>
+
+ <method name="onGuestDebugControlChange">
+ <desc>
+ Triggered when settings of the guest debug settings of the
+ associated virtual machine have changed.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="guestDebugControl" type="IGuestDebugControl" dir="in"/>
+ </method>
+
+ <method name="onUSBDeviceAttach">
+ <desc>
+ Triggered when a request to capture a USB device (as a result
+ of matched USB filters or direct call to
+ <link to="IConsole::attachUSBDevice"/>) has completed.
+ A @c null @a error object means success, otherwise it
+ describes a failure.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="device" type="IUSBDevice" dir="in"/>
+ <param name="error" type="IVirtualBoxErrorInfo" dir="in"/>
+ <param name="maskedInterfaces" type="unsigned long" dir="in"/>
+ <param name="captureFilename" type="wstring" dir="in"/>
+ </method>
+
+ <method name="onUSBDeviceDetach">
+ <desc>
+ Triggered when a request to release the USB device (as a result
+ of machine termination or direct call to
+ <link to="IConsole::detachUSBDevice"/>) has completed.
+ A @c null @a error object means success, otherwise it
+ describes a failure.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Session state prevents operation.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in"/>
+ <param name="error" type="IVirtualBoxErrorInfo" dir="in"/>
+ </method>
+
+ <method name="onShowWindow">
+ <desc>
+ Called by <link to="IMachine::canShowConsoleWindow"/> and by
+ <link to="IMachine::showConsoleWindow"/> in order to notify
+ console listeners
+ <link to="ICanShowWindowEvent"/>
+ and <link to="IShowWindowEvent"/>.
+
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type prevents operation.
+ </result>
+
+ </desc>
+ <param name="check" type="boolean" dir="in"/>
+ <param name="canShow" type="boolean" dir="out"/>
+ <param name="winId" type="long long" dir="out"/>
+ </method>
+
+ <method name="onBandwidthGroupChange">
+ <desc>
+ Notification when one of the bandwidth groups change.
+ </desc>
+ <param name="bandwidthGroup" type="IBandwidthGroup" dir="in">
+ <desc>The bandwidth group which changed.</desc>
+ </param>
+ </method>
+
+ <method name="accessGuestProperty">
+ <desc>
+ Called by <link to="IMachine::getGuestProperty"/> and by
+ <link to="IMachine::setGuestProperty"/> in order to read and
+ modify guest properties.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Machine session is not open.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type is not direct.
+ </result>
+
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of guest property.</desc>
+ </param>
+ <param name="value" type="wstring" dir="in">
+ <desc>Value of guest property.</desc>
+ </param>
+ <param name="flags" type="wstring" dir="in">
+ <desc>Flags of guest property.</desc>
+ </param>
+ <param name="accessMode" type="unsigned long" dir="in">
+ <desc>0 = get, 1 = set, 2 = delete.</desc>
+ </param>
+ <param name="retValue" type="wstring" dir="out">
+ <desc>When getting: Value of guest property.</desc>
+ </param>
+ <param name="retTimestamp" type="long long" dir="out">
+ <desc>When getting: Timestamp of guest property.</desc>
+ </param>
+ <param name="retFlags" type="wstring" dir="out">
+ <desc>When getting: Flags of guest property.</desc>
+ </param>
+ </method>
+
+ <method name="enumerateGuestProperties" const="yes">
+ <desc>
+ Return a list of the guest properties matching a set of patterns along
+ with their values, timestamps and flags.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Machine session is not open.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type is not direct.
+ </result>
+
+ </desc>
+ <param name="patterns" type="wstring" dir="in">
+ <desc>
+ The patterns to match the properties against as a comma-separated
+ string. If this is empty, all properties currently set will be
+ returned.
+ </desc>
+ </param>
+ <param name="keys" type="wstring" dir="out" safearray="yes">
+ <desc>
+ The key names of the properties returned.
+ </desc>
+ </param>
+ <param name="values" type="wstring" dir="out" safearray="yes">
+ <desc>
+ The values of the properties returned. The array entries match the
+ corresponding entries in the @a key array.
+ </desc>
+ </param>
+ <param name="timestamps" type="long long" dir="out" safearray="yes">
+ <desc>
+ The timestamps of the properties returned. The array entries match
+ the corresponding entries in the @a key array.
+ </desc>
+ </param>
+ <param name="flags" type="wstring" dir="out" safearray="yes">
+ <desc>
+ The flags of the properties returned. The array entries match the
+ corresponding entries in the @a key array.
+ </desc>
+ </param>
+ </method>
+
+ <method name="onlineMergeMedium">
+ <desc>
+ Triggers online merging of a hard disk. Used internally when deleting
+ a snapshot while a VM referring to the same hard disk chain is running.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Machine session is not open.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type is not direct.
+ </result>
+
+ </desc>
+ <param name="mediumAttachment" type="IMediumAttachment" dir="in">
+ <desc>The medium attachment to identify the medium chain.</desc>
+ </param>
+ <param name="sourceIdx" type="unsigned long" dir="in">
+ <desc>The index of the source image in the chain.
+ Redundant, but drastically reduces IPC.</desc>
+ </param>
+ <param name="targetIdx" type="unsigned long" dir="in">
+ <desc>The index of the target image in the chain.
+ Redundant, but drastically reduces IPC.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="in">
+ <desc>
+ Progress object for this operation.
+ </desc>
+ </param>
+ </method>
+
+ <method name="reconfigureMediumAttachments">
+ <desc>
+ Reconfigure all specified medium attachments in one go, making sure
+ the current state corresponds to the specified medium.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Machine session is not open.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type is not direct.
+ </result>
+ </desc>
+ <param name="attachments" type="IMediumAttachment" dir="in" safearray="yes">
+ <desc>Array containing the medium attachments which need to be
+ reconfigured.</desc>
+ </param>
+ </method>
+
+ <method name="enableVMMStatistics">
+ <desc>
+ Enables or disables collection of VMM RAM statistics.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Machine session is not open.
+ </result>
+ <result name="VBOX_E_INVALID_OBJECT_STATE">
+ Session type is not direct.
+ </result>
+
+ </desc>
+ <param name="enable" type="boolean" dir="in">
+ <desc>True enables statistics collection.</desc>
+ </param>
+ </method>
+
+ <method name="pauseWithReason">
+ <desc>
+ Internal method for triggering a VM pause with a specified reason code.
+ The reason code can be interpreted by device/drivers and thus it might
+ behave slightly differently than a normal VM pause.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine not in Running state.
+ </result>
+ <result name="VBOX_E_VM_ERROR">
+ Virtual machine error in suspend operation.
+ </result>
+ <see><link to="IConsole::pause"/></see>
+ </desc>
+
+ <param name="reason" type="Reason" dir="in">
+ <desc>Specify the best matching reason code please.</desc>
+ </param>
+ </method>
+
+ <method name="resumeWithReason">
+ <desc>
+ Internal method for triggering a VM resume with a specified reason code.
+ The reason code can be interpreted by device/drivers and thus it might
+ behave slightly differently than a normal VM resume.
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine not in Paused state.
+ </result>
+ <result name="VBOX_E_VM_ERROR">
+ Virtual machine error in resume operation.
+ </result>
+ <see><link to="IConsole::resume"/></see>
+ </desc>
+
+ <param name="reason" type="Reason" dir="in">
+ <desc>Specify the best matching reason code please.</desc>
+ </param>
+ </method>
+
+ <method name="saveStateWithReason">
+ <desc>
+ Internal method for triggering a VM save state with a specified reason
+ code. The reason code can be interpreted by device/drivers and thus it
+ might behave slightly differently than a normal VM save state.
+
+ This call is fully synchronous, and the caller is expected to have set
+ the machine state appropriately (and has to set the follow-up machine
+ state if this call failed).
+
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine state is not one of the expected values.
+ </result>
+ <result name="VBOX_E_FILE_ERROR">
+ Failed to create directory for saved state file.
+ </result>
+ <see><link to="IMachine::saveState"/></see>
+ </desc>
+
+ <param name="reason" type="Reason" dir="in">
+ <desc>Specify the best matching reason code please.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="in">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ <param name="snapshot" type="ISnapshot" dir="in">
+ <desc>Snapshot object for which this save state operation is executed.</desc>
+ </param>
+ <param name="stateFilePath" type="wstring" dir="in">
+ <desc>File path the VM process must save the execution state to.</desc>
+ </param>
+ <param name="pauseVM" type="boolean" dir="in">
+ <desc>The VM should be paused before saving state. It is automatically
+ unpaused on error in the "vanilla save state" case.</desc>
+ </param>
+ <param name="leftPaused" type="boolean" dir="return">
+ <desc>Returns if the VM was left in paused state, which is necessary
+ in many situations (snapshots, teleportation).</desc>
+ </param>
+ </method>
+
+ <method name="cancelSaveStateWithReason">
+ <desc>
+ Internal method for cancelling a VM save state.
+ <see><link to="IInternalSessionControl::saveStateWithReason"/></see>
+ </desc>
+ </method>
+
+ </interface>
+
+ <interface
+ name="ISession" extends="$unknown"
+ uuid="c0447716-ff5a-4795-b57a-ecd5fffa18a4"
+ wsmap="managed"
+ rest="managed"
+ wrap-hint-server-addinterfaces="IInternalSessionControl"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ The ISession interface represents a client process and allows for locking
+ virtual machines (represented by IMachine objects) to prevent conflicting
+ changes to the machine.
+
+ Any caller wishing to manipulate a virtual machine needs to create a session
+ object first, which lives in its own process space. Such session objects are
+ then associated with <link to="IMachine" /> objects living in the VirtualBox
+ server process to coordinate such changes.
+
+ There are two typical scenarios in which sessions are used:
+
+ <ul>
+ <li>To alter machine settings or control a running virtual machine, one
+ needs to lock a machine for a given session (client process) by calling
+ <link to="IMachine::lockMachine"/>.
+
+ Whereas multiple sessions may control a running virtual machine, only
+ one process can obtain a write lock on the machine to prevent conflicting
+ changes. A write lock is also needed if a process wants to actually run a
+ virtual machine in its own context, such as the VirtualBox GUI or
+ VBoxHeadless front-ends. They must also lock a machine for their own
+ sessions before they are allowed to power up the virtual machine.
+
+ As a result, no machine settings can be altered while another process is
+ already using it, either because that process is modifying machine settings
+ or because the machine is running.
+ </li>
+ <li>To start a VM using one of the existing VirtualBox front-ends (e.g. the
+ VirtualBox GUI or VBoxHeadless), one would use
+ <link to="IMachine::launchVMProcess"/>, which also takes a session object
+ as its first parameter. This session then identifies the caller and lets the
+ caller control the started machine (for example, pause machine execution or
+ power it down) as well as be notified about machine execution state changes.
+ </li>
+ </ul>
+
+ How sessions objects are created in a client process depends on whether you use
+ the Main API via COM or via the webservice:
+
+ <ul>
+ <li>When using the COM API directly, an object of the Session class from the
+ VirtualBox type library needs to be created. In regular COM C++ client code,
+ this can be done by calling <tt>createLocalObject()</tt>, a standard COM API.
+ This object will then act as a local session object in further calls to open
+ a session.
+ </li>
+
+ <li>In the webservice, the session manager (IWebsessionManager) instead creates
+ a session object automatically whenever <link to="IWebsessionManager::logon" />
+ is called. A managed object reference to that session object can be retrieved by
+ calling <link to="IWebsessionManager::getSessionObject" />.
+ </li>
+ </ul>
+ </desc>
+
+ <attribute name="state" type="SessionState" readonly="yes">
+ <desc>Current state of this session.</desc>
+ </attribute>
+
+ <attribute name="type" type="SessionType" readonly="yes">
+ <desc>
+ Type of this session. The value of this attribute is valid only
+ if the session currently has a machine locked (i.e. its
+ <link to="#state" /> is Locked), otherwise an error will be returned.
+ </desc>
+ </attribute>
+
+ <attribute name="name" type="wstring">
+ <desc>
+ Name of this session. Important only for VM sessions, otherwise it
+ it will be remembered, but not used for anything significant (and can
+ be left at the empty string which is the default). The value can only
+ be changed when the session state is SessionState_Unlocked. Make sure
+ that you use a descriptive name which does not conflict with the VM
+ process session names: "GUI/Qt", "GUI/SDL" and "headless".
+ </desc>
+ </attribute>
+
+ <attribute name="machine" type="IMachine" readonly="yes" rest="uuid">
+ <desc>Machine object associated with this session.</desc>
+ </attribute>
+
+ <attribute name="console" type="IConsole" readonly="yes">
+ <desc>Console object associated with this session. Only sessions
+ which locked the machine for a VM process have a non-null console.</desc>
+ </attribute>
+
+ <method name="unlockMachine">
+ <desc>
+ Unlocks a machine that was previously locked for the current session.
+
+ Calling this method is required every time a machine has been locked
+ for a particular session using the <link to="IMachine::launchVMProcess" />
+ or <link to="IMachine::lockMachine" /> calls. Otherwise the state of
+ the machine will be set to <link to="MachineState_Aborted" /> on the
+ server, and changes made to the machine settings will be lost.
+
+ Generally, it is recommended to unlock all machines explicitly
+ before terminating the application (regardless of the reason for
+ the termination).
+
+ <note>
+ Do not expect the session state (<link to="ISession::state" />
+ to return to "Unlocked" immediately after you invoke this method,
+ particularly if you have started a new VM process. The session
+ state will automatically return to "Unlocked" once the VM is no
+ longer executing, which can of course take a very long time.
+ </note>
+
+ <result name="E_UNEXPECTED">
+ Session is not locked.
+ </result>
+
+ </desc>
+ </method>
+
+ </interface>
+
+ <!--
+ // IStorageController
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <enum
+ name="StorageBus"
+ uuid="f9510869-7d07-46ba-96a6-6728fbf4adee"
+ >
+ <desc>
+ The bus type of the storage controller (IDE, SATA, SCSI, SAS or Floppy);
+ see <link to="IStorageController::bus" />.
+ </desc>
+ <const name="Null" value="0">
+ <desc>@c null value. Never used by the API.</desc>
+ </const>
+ <const name="IDE" value="1"/>
+ <const name="SATA" value="2"/>
+ <const name="SCSI" value="3"/>
+ <const name="Floppy" value="4"/>
+ <const name="SAS" value="5"/>
+ <const name="USB" value="6"/>
+ <const name="PCIe" value="7"/>
+ <const name="VirtioSCSI" value="8"/>
+ </enum>
+
+ <enum
+ name="StorageControllerType"
+ uuid="a77d457d-66a3-4368-b24c-293d0f562a9f"
+ >
+ <desc>
+ The exact variant of storage controller hardware presented
+ to the guest; see <link to="IStorageController::controllerType" />.
+ </desc>
+
+ <const name="Null" value="0">
+ <desc>@c null value. Never used by the API.</desc>
+ </const>
+ <const name="LsiLogic" value="1">
+ <desc>A SCSI controller of the LsiLogic variant.</desc>
+ </const>
+ <const name="BusLogic" value="2">
+ <desc>A SCSI controller of the BusLogic variant.</desc>
+ </const>
+ <const name="IntelAhci" value="3">
+ <desc>An Intel AHCI SATA controller; this is the only variant for SATA.</desc>
+ </const>
+ <const name="PIIX3" value="4">
+ <desc>An IDE controller of the PIIX3 variant.</desc>
+ </const>
+ <const name="PIIX4" value="5">
+ <desc>An IDE controller of the PIIX4 variant.</desc>
+ </const>
+ <const name="ICH6" value="6">
+ <desc>An IDE controller of the ICH6 variant.</desc>
+ </const>
+ <const name="I82078" value="7">
+ <desc>A floppy disk controller; this is the only variant for floppy drives.</desc>
+ </const>
+ <const name="LsiLogicSas" value="8">
+ <desc>A variant of the LsiLogic controller using SAS.</desc>
+ </const>
+ <const name="USB" value="9">
+ <desc>Special USB based storage controller.</desc>
+ </const>
+ <const name="NVMe" value="10">
+ <desc>An NVMe storage controller.</desc>
+ </const>
+ <const name="VirtioSCSI" value="11">
+ <desc>Virtio SCSI storage controller.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="ChipsetType"
+ uuid="8b4096a8-a7c3-4d3b-bbb1-05a0a51ec394"
+ >
+ <desc>
+ Type of emulated chipset (mostly southbridge).
+ </desc>
+
+ <const name="Null" value="0">
+ <desc>@c null value. Never used by the API.</desc>
+ </const>
+ <const name="PIIX3" value="1">
+ <desc>A PIIX3 (PCI IDE ISA Xcelerator) chipset.</desc>
+ </const>
+ <const name="ICH9" value="2">
+ <desc>A ICH9 (I/O Controller Hub) chipset.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IStorageController" extends="$unknown"
+ uuid="ddca7247-bf98-47fb-ab2f-b5177533f493"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="8"
+ rest="managed"
+ >
+ <desc>
+ Represents a storage controller that is attached to a virtual machine
+ (<link to="IMachine" />). Just as drives (hard disks, DVDs, FDs) are
+ attached to storage controllers in a real computer, virtual drives
+ (represented by <link to="IMediumAttachment" />) are attached to virtual
+ storage controllers, represented by this interface.
+
+ As opposed to physical hardware, VirtualBox has a very generic concept
+ of a storage controller, and for purposes of the Main API, all virtual
+ storage is attached to virtual machines via instances of this interface.
+ There are five types of such virtual storage controllers: IDE, SCSI, SATA,
+ SAS and Floppy (see <link to="#bus" />). Depending on which of these four
+ is used, certain sub-types may be available and can be selected in
+ <link to="#controllerType" />.
+
+ Depending on these settings, the guest operating system might see
+ significantly different virtual hardware.
+ </desc>
+
+ <attribute name="name" type="wstring">
+ <desc>
+ Name of the storage controller, as originally specified with
+ <link to="IMachine::addStorageController" />. This then uniquely
+ identifies this controller with other method calls such as
+ <link to="IMachine::attachDevice" /> and <link to="IMachine::mountMedium" />.
+ </desc>
+ </attribute>
+
+ <attribute name="maxDevicesPerPortCount" type="unsigned long" readonly="yes">
+ <desc>
+ Maximum number of devices which can be attached to one port.
+ </desc>
+ </attribute>
+
+ <attribute name="minPortCount" type="unsigned long" readonly="yes">
+ <desc>
+ Minimum number of ports that <link to="IStorageController::portCount"/> can be set to.
+ </desc>
+ </attribute>
+
+ <attribute name="maxPortCount" type="unsigned long" readonly="yes">
+ <desc>
+ Maximum number of ports that <link to="IStorageController::portCount"/> can be set to.
+ </desc>
+ </attribute>
+
+ <attribute name="instance" type="unsigned long">
+ <desc>
+ The instance number of the device in the running VM.
+ </desc>
+ </attribute>
+
+ <attribute name="portCount" type="unsigned long">
+ <desc>
+ The number of currently usable ports on the controller.
+ The minimum and maximum number of ports for one controller are
+ stored in <link to="IStorageController::minPortCount"/>
+ and <link to="IStorageController::maxPortCount"/>.
+ </desc>
+ </attribute>
+
+ <attribute name="bus" type="StorageBus" readonly="yes">
+ <desc>
+ The bus type of the storage controller (IDE, SATA, SCSI, SAS or Floppy).
+ </desc>
+ </attribute>
+
+ <attribute name="controllerType" type="StorageControllerType">
+ <desc>
+ The exact variant of storage controller hardware presented
+ to the guest.
+ Depending on this value, VirtualBox will provide a different
+ virtual storage controller hardware to the guest.
+ For SATA, SAS and floppy controllers, only one variant is
+ available, but for IDE and SCSI, there are several.
+
+ For SCSI controllers, the default type is LsiLogic.
+ </desc>
+ </attribute>
+
+ <attribute name="useHostIOCache" type="boolean">
+ <desc>
+ If true, the storage controller emulation will use a dedicated I/O thread, enable the host I/O
+ caches and use synchronous file APIs on the host. This was the only option in the API before
+ VirtualBox 3.2 and is still the default for IDE controllers.
+
+ If false, the host I/O cache will be disabled for image files attached to this storage controller.
+ Instead, the storage controller emulation will use asynchronous I/O APIs on the host. This makes
+ it possible to turn off the host I/O caches because the emulation can handle unaligned access to
+ the file. This should be used on OS X and Linux hosts if a high I/O load is expected or many
+ virtual machines are running at the same time to prevent I/O cache related hangs.
+ This option new with the API of VirtualBox 3.2 and is now the default for non-IDE storage controllers.
+ </desc>
+ </attribute>
+
+ <attribute name="bootable" type="boolean" readonly="yes">
+ <desc>
+ Returns whether it is possible to boot from disks attached to this controller.
+ </desc>
+ </attribute>
+ </interface>
+
+<if target="wsdl">
+
+ <!--
+ // IManagedObjectRef
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IManagedObjectRef" extends="$unknown"
+ uuid="9474d09d-2313-46de-b568-a42b8718e8ed"
+ internal="yes"
+ wsmap="managed"
+ wscpp="hardcoded"
+ >
+ <desc>
+ Managed object reference.
+
+ Only within the webservice, a managed object reference (which is really
+ an opaque number) allows a webservice client to address an object
+ that lives in the address space of the webservice server.
+
+ Behind each managed object reference, there is a COM object that lives
+ in the webservice server's address space. The COM object is not freed
+ until the managed object reference is released, either by an explicit
+ call to <link to="IManagedObjectRef::release" /> or by logging off from
+ the webservice (<link to="IWebsessionManager::logoff" />), which releases
+ all objects created during the webservice session.
+
+ Whenever a method call of the VirtualBox API returns a COM object, the
+ webservice representation of that method will instead return a
+ managed object reference, which can then be used to invoke methods
+ on that object.
+ </desc>
+
+ <method name="getInterfaceName">
+ <desc>
+ Returns the name of the interface that this managed object represents,
+ for example, "IMachine", as a string.
+ </desc>
+ <param name="return" type="wstring" dir="return"/>
+ </method>
+
+ <method name="release">
+ <desc>
+ Releases this managed object reference and frees the resources that
+ were allocated for it in the webservice server process. After calling
+ this method, the identifier of the reference can no longer be used.
+ </desc>
+ </method>
+
+ </interface>
+
+ <!--
+ // IWebsessionManager
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IWebsessionManager" extends="$unknown"
+ uuid="930c8fea-453a-4a65-aca9-19ed9a872f88"
+ internal="yes"
+ wsmap="global"
+ wscpp="hardcoded"
+ >
+ <desc>
+ Websession manager. This provides essential services
+ to webservice clients.
+ </desc>
+ <method name="logon">
+ <desc>
+ Logs a new client onto the webservice and returns a managed object reference to
+ the IVirtualBox instance, which the client can then use as a basis to further
+ queries, since all calls to the VirtualBox API are based on the IVirtualBox
+ interface, in one way or the other.
+ </desc>
+ <param name="username" type="wstring" dir="in"/>
+ <param name="password" type="wstring" dir="in"/>
+ <param name="return" type="IVirtualBox" dir="return"/>
+ </method>
+
+ <method name="getSessionObject">
+ <desc>
+ Returns a managed object reference to a new ISession object for every
+ call to this method.
+
+ <see><link to="ISession"/></see>
+ </desc>
+ <param name="refIVirtualBox" type="IVirtualBox" dir="in"/>
+ <param name="return" type="ISession" dir="return"/>
+ </method>
+
+ <method name="logoff">
+ <desc>
+ Logs off the client who has previously logged on with <link to="IWebsessionManager::logon" />
+ and destroys all resources associated with the websession (most
+ importantly, all managed objects created in the server while the
+ websession was active).
+ </desc>
+ <param name="refIVirtualBox" type="IVirtualBox" dir="in"/>
+ </method>
+
+ </interface>
+
+</if>
+
+ <!--
+ // IPerformanceCollector & friends
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IPerformanceMetric" extends="$unknown"
+ uuid="81314d14-fd1c-411a-95c5-e9bb1414e632" wsmap="managed"
+ rest="managed"
+ reservedAttributes="8"
+ >
+ <desc>
+ The IPerformanceMetric interface represents parameters of the given
+ performance metric.
+ </desc>
+
+ <attribute name="metricName" type="wstring" readonly="yes">
+ <desc>
+ Name of the metric.
+ </desc>
+ </attribute>
+
+ <attribute name="object" type="$unknown" readonly="yes">
+ <desc>
+ Object this metric belongs to.
+ </desc>
+ </attribute>
+
+ <attribute name="description" type="wstring" readonly="yes">
+ <desc>
+ Textual description of the metric.
+ </desc>
+ </attribute>
+
+ <attribute name="period" type="unsigned long" readonly="yes">
+ <desc>
+ Time interval between samples, measured in seconds.
+ </desc>
+ </attribute>
+
+ <attribute name="count" type="unsigned long" readonly="yes">
+ <desc>
+ Number of recent samples retained by the performance collector for this
+ metric.
+
+ When the collected sample count exceeds this number, older samples
+ are discarded.
+ </desc>
+ </attribute>
+
+ <attribute name="unit" type="wstring" readonly="yes">
+ <desc>
+ Unit of measurement.
+ </desc>
+ </attribute>
+
+ <attribute name="minimumValue" type="long" readonly="yes">
+ <desc>
+ Minimum possible value of this metric.
+ </desc>
+ </attribute>
+
+ <attribute name="maximumValue" type="long" readonly="yes">
+ <desc>
+ Maximum possible value of this metric.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IPerformanceCollector" extends="$unknown"
+ uuid="b14290ad-cd54-400c-b858-797bcb82570e"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ The IPerformanceCollector interface represents a service that collects
+ and stores performance metrics data.
+
+ Performance metrics are associated with objects of interfaces like IHost
+ and IMachine. Each object has a distinct set of performance metrics. The
+ set can be obtained with <link to="IPerformanceCollector::getMetrics"/>.
+
+ Metric data is collected at the specified intervals and is retained
+ internally. The interval and the number of retained samples can be set
+ with <link to="IPerformanceCollector::setupMetrics" />. Both metric data
+ and collection settings are not persistent, they are discarded as soon as
+ VBoxSVC process terminates. Moreover, metric settings and data associated
+ with a particular VM only exist while VM is running. They disappear as
+ soon as VM shuts down. It is not possible to set up metrics for machines
+ that are powered off. One needs to start VM first, then set up metric
+ collection parameters.
+
+ Metrics are organized hierarchically, with each level separated by a
+ slash (/) character. Generally, the scheme for metric names is like this:
+
+ <tt>Category/Metric[/SubMetric][:aggregation]</tt>
+
+ "Category/Metric" together form the base metric name. A base metric is
+ the smallest unit for which a sampling interval and the number of
+ retained samples can be set. Only base metrics can be enabled and
+ disabled. All sub-metrics are collected when their base metric is
+ collected. Collected values for any set of sub-metrics can be queried
+ with <link to="IPerformanceCollector::queryMetricsData" />.
+
+ For example "CPU/Load/User:avg" metric name stands for the "CPU"
+ category, "Load" metric, "User" submetric, "average" aggregate. An
+ aggregate function is computed over all retained data. Valid aggregate
+ functions are:
+
+ <ul>
+ <li>avg -- average</li>
+ <li>min -- minimum</li>
+ <li>max -- maximum</li>
+ </ul>
+
+ When setting up metric parameters, querying metric data, enabling or
+ disabling metrics wildcards can be used in metric names to specify a
+ subset of metrics. For example, to select all CPU-related metrics
+ use <tt>CPU/*</tt>, all averages can be queried using <tt>*:avg</tt> and
+ so on. To query metric values without aggregates <tt>*:</tt> can be used.
+
+ The valid names for base metrics are:
+
+ <ul>
+ <li>CPU/Load</li>
+ <li>CPU/MHz</li>
+ <li>RAM/Usage</li>
+ <li>RAM/VMM</li>
+ </ul>
+
+ The general sequence for collecting and retrieving the metrics is:
+ <ul>
+ <li>
+ Obtain an instance of IPerformanceCollector with
+ <link to="IVirtualBox::performanceCollector" />
+ </li>
+ <li>
+ Allocate and populate an array with references to objects the metrics
+ will be collected for. Use references to IHost and IMachine objects.
+ </li>
+ <li>
+ Allocate and populate an array with base metric names the data will
+ be collected for.
+ </li>
+ <li>
+ Call <link to="IPerformanceCollector::setupMetrics" />. From now on
+ the metric data will be collected and stored.
+ </li>
+ <li>
+ Wait for the data to get collected.
+ </li>
+ <li>
+ Allocate and populate an array with references to objects the metric
+ values will be queried for. You can re-use the object array used for
+ setting base metrics.
+ </li>
+ <li>
+ Allocate and populate an array with metric names the data will be
+ collected for. Note that metric names differ from base metric names.
+ </li>
+ <li>
+ Call <link to="IPerformanceCollector::queryMetricsData" />. The data
+ that have been collected so far are returned. Note that the values
+ are still retained internally and data collection continues.
+ </li>
+ </ul>
+
+ For an example of usage refer to the following files in VirtualBox SDK:
+ <ul>
+ <li>
+ Java: <tt>bindings/webservice/java/jax-ws/samples/metrictest.java</tt>
+ </li>
+ <li>
+ Python: <tt>bindings/xpcom/python/sample/shellcommon.py</tt>
+ </li>
+ </ul>
+ </desc>
+
+ <attribute name="metricNames" type="wstring" readonly="yes" safearray="yes">
+ <desc>
+ Array of unique names of metrics.
+
+ This array represents all metrics supported by the performance
+ collector. Individual objects do not necessarily support all of them.
+ <link to="IPerformanceCollector::getMetrics"/> can be used to get the
+ list of supported metrics for a particular object.
+ </desc>
+ </attribute>
+
+ <method name="getMetrics">
+ <desc>
+ Returns parameters of specified metrics for a set of objects.
+ <note>
+ @c Null metrics array means all metrics. @c Null object array means
+ all existing objects.
+ </note>
+ </desc>
+ <param name="metricNames" type="wstring" dir="in" safearray="yes">
+ <desc>
+ Metric name filter. Currently, only a comma-separated list of metrics
+ is supported.
+ </desc>
+ </param>
+ <param name="objects" type="$unknown" dir="in" safearray="yes">
+ <desc>
+ Set of objects to return metric parameters for.
+ </desc>
+ </param>
+ <param name="metrics" type="IPerformanceMetric" dir="return" safearray="yes">
+ <desc>
+ Array of returned metric parameters.
+ </desc>
+ </param>
+ </method>
+
+ <method name="setupMetrics">
+ <desc>
+ Sets parameters of specified base metrics for a set of objects. Returns
+ an array of <link to="IPerformanceMetric" /> describing the metrics
+ have been affected.
+ <note>
+ @c Null or empty metric name array means all metrics. @c Null or
+ empty object array means all existing objects. If metric name array
+ contains a single element and object array contains many, the single
+ metric name array element is applied to each object array element to
+ form metric/object pairs.
+ </note>
+ </desc>
+ <param name="metricNames" type="wstring" dir="in" safearray="yes">
+ <desc>
+ Metric name filter. Comma-separated list of metrics with wildcard
+ support.
+ </desc>
+ </param>
+ <param name="objects" type="$unknown" dir="in" safearray="yes">
+ <desc>
+ Set of objects to setup metric parameters for.
+ </desc>
+ </param>
+ <param name="period" type="unsigned long" dir="in">
+ <desc>
+ Time interval in seconds between two consecutive samples of
+ performance data.
+ </desc>
+ </param>
+ <param name="count" type="unsigned long" dir="in">
+ <desc>
+ Number of samples to retain in performance data history. Older
+ samples get discarded.
+ </desc>
+ </param>
+ <param name="affectedMetrics" type="IPerformanceMetric" dir="return" safearray="yes">
+ <desc>
+ Array of metrics that have been modified by the call to this method.
+ </desc>
+ </param>
+ </method>
+
+ <method name="enableMetrics">
+ <desc>
+ Turns on collecting specified base metrics. Returns an array of
+ <link to="IPerformanceMetric" /> describing the metrics have been
+ affected.
+ <note>
+ @c Null or empty metric name array means all metrics. @c Null or
+ empty object array means all existing objects. If metric name array
+ contains a single element and object array contains many, the single
+ metric name array element is applied to each object array element to
+ form metric/object pairs.
+ </note>
+ </desc>
+ <param name="metricNames" type="wstring" dir="in" safearray="yes">
+ <desc>
+ Metric name filter. Comma-separated list of metrics with wildcard
+ support.
+ </desc>
+ </param>
+ <param name="objects" type="$unknown" dir="in" safearray="yes">
+ <desc>
+ Set of objects to enable metrics for.
+ </desc>
+ </param>
+ <param name="affectedMetrics" type="IPerformanceMetric" dir="return" safearray="yes">
+ <desc>
+ Array of metrics that have been modified by the call to this method.
+ </desc>
+ </param>
+ </method>
+
+ <method name="disableMetrics">
+ <desc>
+ Turns off collecting specified base metrics. Returns an array of
+ <link to="IPerformanceMetric" /> describing the metrics have been
+ affected.
+ <note>
+ @c Null or empty metric name array means all metrics. @c Null or
+ empty object array means all existing objects. If metric name array
+ contains a single element and object array contains many, the single
+ metric name array element is applied to each object array element to
+ form metric/object pairs.
+ </note>
+ </desc>
+ <param name="metricNames" type="wstring" dir="in" safearray="yes">
+ <desc>
+ Metric name filter. Comma-separated list of metrics with wildcard
+ support.
+ </desc>
+ </param>
+ <param name="objects" type="$unknown" dir="in" safearray="yes">
+ <desc>
+ Set of objects to disable metrics for.
+ </desc>
+ </param>
+ <param name="affectedMetrics" type="IPerformanceMetric" dir="return" safearray="yes">
+ <desc>
+ Array of metrics that have been modified by the call to this method.
+ </desc>
+ </param>
+ </method>
+
+ <method name="queryMetricsData">
+ <desc>
+ Queries collected metrics data for a set of objects.
+
+ The data itself and related metric information are returned in seven
+ parallel and one flattened array of arrays. Elements of
+ <tt>returnMetricNames, returnObjects, returnUnits, returnScales,
+ returnSequenceNumbers, returnDataIndices and returnDataLengths</tt> with
+ the same index describe one set of values corresponding to a single
+ metric.
+
+ The <tt>returnData</tt> parameter is a flattened array of arrays. Each
+ start and length of a sub-array is indicated by
+ <tt>returnDataIndices</tt> and <tt>returnDataLengths</tt>. The first
+ value for metric <tt>metricNames[i]</tt> is at
+ <tt>returnData[returnIndices[i]]</tt>.
+
+ <note>
+ @c Null or empty metric name array means all metrics. @c Null or
+ empty object array means all existing objects. If metric name array
+ contains a single element and object array contains many, the single
+ metric name array element is applied to each object array element to
+ form metric/object pairs.
+ </note>
+ <note>
+ Data collection continues behind the scenes after call to
+ @c queryMetricsData. The return data can be seen as the snapshot of
+ the current state at the time of @c queryMetricsData call. The
+ internally kept metric values are not cleared by the call. This
+ allows querying different subsets of metrics or aggregates with
+ subsequent calls. If periodic querying is needed it is highly
+ suggested to query the values with @c interval*count period to avoid
+ confusion. This way a completely new set of data values will be
+ provided by each query.
+ </note>
+ </desc>
+ <param name="metricNames" type="wstring" dir="in" safearray="yes">
+ <desc>
+ Metric name filter. Comma-separated list of metrics with wildcard
+ support.
+ </desc>
+ </param>
+ <param name="objects" type="$unknown" dir="in" safearray="yes">
+ <desc>
+ Set of objects to query metrics for.
+ </desc>
+ </param>
+ <param name="returnMetricNames" type="wstring" dir="out" safearray="yes">
+ <desc>
+ Names of metrics returned in @c returnData.
+ </desc>
+ </param>
+ <param name="returnObjects" type="$unknown" dir="out" safearray="yes">
+ <desc>
+ Objects associated with metrics returned in @c returnData.
+ </desc>
+ </param>
+ <param name="returnUnits" type="wstring" dir="out" safearray="yes">
+ <desc>
+ Units of measurement for each returned metric.
+ </desc>
+ </param>
+ <param name="returnScales" type="unsigned long" dir="out" safearray="yes">
+ <desc>
+ Divisor that should be applied to return values in order to get
+ floating point values. For example:
+ <tt>(double)returnData[returnDataIndices[0]+i] / returnScales[0]</tt>
+ will retrieve the floating point value of i-th sample of the first
+ metric.
+ </desc>
+ </param>
+ <param name="returnSequenceNumbers" type="unsigned long" dir="out" safearray="yes">
+ <desc>
+ Sequence numbers of the first elements of value sequences of
+ particular metrics returned in @c returnData. For aggregate metrics
+ it is the sequence number of the sample the aggregate started
+ calculation from.
+ </desc>
+ </param>
+ <param name="returnDataIndices" type="unsigned long" dir="out" safearray="yes">
+ <desc>
+ Indices of the first elements of value sequences of particular
+ metrics returned in @c returnData.
+ </desc>
+ </param>
+ <param name="returnDataLengths" type="unsigned long" dir="out" safearray="yes">
+ <desc>
+ Lengths of value sequences of particular metrics.
+ </desc>
+ </param>
+ <param name="returnData" type="long" dir="return" safearray="yes">
+ <desc>
+ Flattened array of all metric data containing sequences of values for
+ each metric.
+ </desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <enum
+ name="NATAliasMode"
+ uuid="67772168-50d9-11df-9669-7fb714ee4fa1"
+ >
+ <desc></desc>
+ <const name="AliasLog" value="0x1">
+ <desc></desc>
+ </const>
+ <const name="AliasProxyOnly" value="0x02">
+ <desc></desc>
+ </const>
+ <const name="AliasUseSamePorts" value="0x04">
+ <desc></desc>
+ </const>
+ </enum>
+
+ <enum
+ name="NATProtocol"
+ uuid="e90164be-eb03-11de-94af-fff9b1c1b19f"
+ >
+ <desc>Protocol definitions used with NAT port-forwarding rules.</desc>
+ <const name="UDP" value="0">
+ <desc>Port-forwarding uses UDP protocol.</desc>
+ </const>
+ <const name="TCP" value="1">
+ <desc>Port-forwarding uses TCP protocol.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="INATEngine" extends="$unknown"
+ uuid="a06253a7-dcd2-44e3-8689-9c9c4b6b6234"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>Interface for managing a NAT engine which is used with a virtual machine. This
+ allows for changing NAT behavior such as port-forwarding rules. This interface is
+ used in the <link to="INetworkAdapter::NATEngine" /> attribute.</desc>
+ <attribute name="network" type="wstring">
+ <desc>The network attribute of the NAT engine (the same value is used with built-in
+ DHCP server to fill corresponding fields of DHCP leases).</desc>
+ </attribute>
+ <attribute name="hostIP" type="wstring">
+ <desc>IP of host interface to bind all opened sockets to.
+ <note>Changing this does not change binding of port forwarding.</note>
+ </desc>
+ </attribute>
+ <attribute name="TFTPPrefix" type="wstring">
+ <desc>TFTP prefix attribute which is used with the built-in DHCP server to fill
+ the corresponding fields of DHCP leases.</desc>
+ </attribute>
+ <attribute name="TFTPBootFile" type="wstring">
+ <desc>TFTP boot file attribute which is used with the built-in DHCP server to fill
+ the corresponding fields of DHCP leases.</desc>
+ </attribute>
+ <attribute name="TFTPNextServer" type="wstring">
+ <desc>TFTP server attribute which is used with the built-in DHCP server to fill
+ the corresponding fields of DHCP leases.
+ <note>The preferred form is IPv4 addresses.</note>
+ </desc>
+ </attribute>
+ <attribute name="aliasMode" type="unsigned long">
+ <desc></desc>
+ </attribute>
+ <attribute name="DNSPassDomain" type="boolean">
+ <desc>Whether the DHCP server should pass the DNS domain used by the host.</desc>
+ </attribute>
+ <attribute name="DNSProxy" type="boolean">
+ <desc>Whether the DHCP server (and the DNS traffic by NAT) should pass the address
+ of the DNS proxy and process traffic using DNS servers registered on the host.</desc>
+ </attribute>
+ <attribute name="DNSUseHostResolver" type="boolean">
+ <desc>Whether the DHCP server (and the DNS traffic by NAT) should pass the address
+ of the DNS proxy and process traffic using the host resolver mechanism.</desc>
+ </attribute>
+ <attribute name="redirects" type="wstring" readonly="yes" safearray="yes">
+ <desc>Array of NAT port-forwarding rules in string representation, in the following
+ format: "name,protocol id,host ip,host port,guest ip,guest port".</desc>
+ </attribute>
+ <attribute name="localhostReachable" type="boolean">
+ <desc>Whether traffic from the guest directed to 10.0.2.2 will reach the
+ host's loopback interface, i.e. localhost or 127.0.0.1.</desc>
+ </attribute>
+ <method name="setNetworkSettings">
+ <desc>Sets network configuration of the NAT engine.</desc>
+ <param name="mtu" type="unsigned long" dir="in">
+ <desc>MTU (maximum transmission unit) of the NAT engine in bytes.</desc>
+ </param>
+ <param name="sockSnd" type="unsigned long" dir="in">
+ <desc>Capacity of the socket send buffer in bytes when creating a new socket.</desc>
+ </param>
+ <param name="sockRcv" type="unsigned long" dir="in">
+ <desc>Capacity of the socket receive buffer in bytes when creating a new socket.</desc>
+ </param>
+ <param name="TcpWndSnd" type="unsigned long" dir="in">
+ <desc>Initial size of the NAT engine's sending TCP window in bytes when
+ establishing a new TCP connection.</desc>
+ </param>
+ <param name="TcpWndRcv" type="unsigned long" dir="in">
+ <desc>Initial size of the NAT engine's receiving TCP window in bytes when
+ establishing a new TCP connection.</desc>
+ </param>
+ </method>
+ <method name="getNetworkSettings">
+ <desc>Returns network configuration of NAT engine. See <link to="#setNetworkSettings" />
+ for parameter descriptions.</desc>
+ <param name="mtu" type="unsigned long" dir="out" />
+ <param name="sockSnd" type="unsigned long" dir="out" />
+ <param name="sockRcv" type="unsigned long" dir="out" />
+ <param name="TcpWndSnd" type="unsigned long" dir="out" />
+ <param name="TcpWndRcv" type="unsigned long" dir="out" />
+ </method>
+ <method name="addRedirect">
+ <desc>Adds a new NAT port-forwarding rule.</desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>The name of the rule. An empty name is acceptable, in which case the NAT engine
+ auto-generates one using the other parameters.</desc>
+ </param>
+ <param name="proto" type="NATProtocol" dir="in">
+ <desc>Protocol handled with the rule.</desc>
+ </param>
+ <param name="hostIP" type="wstring" dir="in">
+ <desc>IP of the host interface to which the rule should apply. An empty ip address is
+ acceptable, in which case the NAT engine binds the handling socket to any interface.</desc>
+ </param>
+ <param name="hostPort" type="unsigned short" dir="in">
+ <desc>The port number to listen on.</desc>
+ </param>
+ <param name="guestIP" type="wstring" dir="in">
+ <desc>The IP address of the guest which the NAT engine will forward matching packets
+ to. An empty IP address is acceptable, in which case the NAT engine will forward
+ packets to the first DHCP lease (x.x.x.15).</desc>
+ </param>
+ <param name="guestPort" type="unsigned short" dir="in">
+ <desc>The port number to forward.</desc>
+ </param>
+ </method>
+ <method name="removeRedirect">
+ <desc>Removes a port-forwarding rule that was previously registered.</desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>The name of the rule to delete.</desc>
+ </param>
+ </method>
+ </interface>
+
+ <!--
+ // IExtPackManager
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IExtPackPlugIn" extends="$unknown"
+ uuid="78861431-d545-44aa-8013-181b8c288554"
+ wsmap="suppress"
+ reservedAttributes="4"
+ >
+ <desc>
+ Interface for keeping information about a plug-in that ships with an
+ extension pack.
+ </desc>
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>The plug-in name.</desc>
+ </attribute>
+ <attribute name="description" type="wstring" readonly="yes">
+ <desc>The plug-in description.</desc>
+ </attribute>
+ <attribute name="frontend" type="wstring" readonly="yes">
+ <desc>
+ The name of the frontend or component name this plug-in plugs into.
+ </desc>
+ </attribute>
+ <attribute name="modulePath" type="wstring" readonly="yes">
+ <desc> The module path. </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IExtPackBase" extends="$unknown"
+ uuid="aa204a12-5b29-45a5-b5d6-c2bafcdb9b0b"
+ wsmap="suppress"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ Interface for querying information about an extension pack as well as
+ accessing COM objects within it.
+ </desc>
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>The extension pack name. This is unique.</desc>
+ </attribute>
+ <attribute name="description" type="wstring" readonly="yes">
+ <desc>The extension pack description.</desc>
+ </attribute>
+ <attribute name="version" type="wstring" readonly="yes">
+ <desc>
+ The extension pack version string. This is restricted to the dotted
+ version number and optionally a build indicator. No tree revision or
+ tag will be included in the string as those things are available as
+ separate properties. An optional publisher tag may be present like for
+ <link to="IVirtualBox::version"/>.
+
+ Examples: "1.2.3", "1.2.3_BETA1" and "1.2.3_RC2".
+ </desc>
+ </attribute>
+ <attribute name="revision" type="unsigned long" readonly="yes">
+ <desc>The extension pack internal revision number.</desc>
+ </attribute>
+ <attribute name="edition" type="wstring" readonly="yes">
+ <desc>
+ Edition indicator. This is usually empty.
+
+ Can for instance be used to help distinguishing between two editions
+ of the same extension pack where only the license, service contract or
+ something differs.
+ </desc>
+ </attribute>
+ <attribute name="VRDEModule" type="wstring" readonly="yes">
+ <desc>The name of the VRDE module if the extension pack sports one.</desc>
+ </attribute>
+ <attribute name="CryptoModule" type="wstring" readonly="yes">
+ <desc>
+ The name of the crypto module if the extension pack sports one.
+ This module is required for full VM encryption.
+ </desc>
+ </attribute>
+ <attribute name="plugIns" type="IExtPackPlugIn" safearray="yes" readonly="yes">
+ <desc>Plug-ins provided by this extension pack.</desc>
+ </attribute>
+ <attribute name="usable" type="boolean" readonly="yes">
+ <desc>
+ Indicates whether the extension pack is usable or not.
+
+ There are a number of reasons why an extension pack might be unusable,
+ typical examples would be broken installation/file or that it is
+ incompatible with the current VirtualBox version.
+ </desc>
+ </attribute>
+ <attribute name="whyUnusable" type="wstring" readonly="yes">
+ <desc>
+ String indicating why the extension pack is not usable. This is an
+ empty string if usable and always a non-empty string if not usable.
+ </desc>
+ </attribute>
+ <attribute name="showLicense" type="boolean" readonly="yes">
+ <desc>Whether to show the license before installation</desc>
+ </attribute>
+ <attribute name="license" type="wstring" readonly="yes">
+ <desc>
+ The default HTML license text for the extension pack. Same as
+ calling <link to="#queryLicense">queryLicense</link> with
+ preferredLocale and preferredLanguage as empty strings and format set
+ to html.
+ </desc>
+ </attribute>
+
+ <method name="queryLicense">
+ <desc>
+ Full feature version of the license attribute.
+ </desc>
+ <param name="preferredLocale" type="wstring" dir="in">
+ <desc>
+ The preferred license locale. Pass an empty string to get the default
+ license.
+ </desc>
+ </param>
+ <param name="preferredLanguage" type="wstring" dir="in">
+ <desc>
+ The preferred license language. Pass an empty string to get the
+ default language for the locale.
+ </desc>
+ </param>
+ <param name="format" type="wstring" dir="in">
+ <desc>
+ The license format: html, rtf or txt. If a license is present there
+ will always be an HTML of it, the rich text format (RTF) and plain
+ text (txt) versions are optional. If
+ </desc>
+ </param>
+ <param name="licenseText" type="wstring" dir="return">
+ <desc>The license text.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IExtPack" extends="IExtPackBase"
+ uuid="431685da-3618-4ebc-b038-833ba829b4b2"
+ wsmap="suppress"
+ >
+ <desc>
+ Interface for querying information about an extension pack as well as
+ accessing COM objects within it.
+ </desc>
+ <method name="queryObject">
+ <desc>
+ Queries the IUnknown interface to an object in the extension pack
+ main module. This allows plug-ins and others to talk directly to an
+ extension pack.
+ </desc>
+ <param name="objUuid" type="wstring" dir="in">
+ <desc>The object ID. What exactly this is </desc>
+ </param>
+ <param name="returnInterface" type="$unknown" dir="return">
+ <desc>The queried interface.</desc>
+ </param>
+ </method>
+ </interface>
+
+ <interface
+ name="IExtPackFile" extends="IExtPackBase"
+ uuid="41304f1b-7e72-4f34-b8f6-682785620c57"
+ wsmap="suppress"
+ reservedMethods="2" reservedAttributes="4"
+ >
+ <desc>
+ Extension pack file (aka tarball, .vbox-extpack) representation returned
+ by <link to="IExtPackManager::openExtPackFile"/>. This provides the base
+ extension pack information with the addition of the file name.
+ </desc>
+ <attribute name="filePath" type="wstring" readonly="yes">
+ <desc>
+ The path to the extension pack file.
+ </desc>
+ </attribute>
+
+ <method name="install">
+ <desc>
+ Install the extension pack.
+ </desc>
+ <param name="replace" type="boolean" dir="in">
+ <desc>
+ Set this to automatically uninstall any existing extension pack with
+ the same name as the one being installed.
+ </desc>
+ </param>
+ <param name="displayInfo" type="wstring" dir="in">
+ <desc>
+ Platform specific display information. Reserved for future hacks.
+ </desc>
+ </param>
+ <param name="progess" type="IProgress" dir="return">
+ <desc>
+ Progress object for the operation.
+ </desc>
+ </param>
+ </method>
+ </interface>
+
+ <interface
+ name="IExtPackManager" extends="$unknown"
+ uuid="70401eef-c8e9-466b-9660-45cb3e9979e4"
+ wsmap="suppress"
+ reservedMethods="4" reservedAttributes="8"
+ >
+ <desc>
+ Interface for managing VirtualBox Extension Packs.
+
+ @todo Describe extension packs, how they are managed and how to create one.
+ </desc>
+
+ <attribute name="installedExtPacks" type="IExtPack" safearray="yes" readonly="yes">
+ <desc>
+ List of the installed extension packs.
+ </desc>
+ </attribute>
+
+ <method name="find">
+ <desc>
+ Returns the extension pack with the specified name if found.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ No extension pack matching @a name was found.
+ </result>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>The name of the extension pack to locate.</desc>
+ </param>
+ <param name="returnData" type="IExtPack" dir="return">
+ <desc>The extension pack if found.</desc>
+ </param>
+ </method>
+
+ <method name="openExtPackFile">
+ <desc>
+ Attempts to open an extension pack file in preparation for
+ installation.
+ </desc>
+ <param name="path" type="wstring" dir="in">
+ <desc>The path of the extension pack tarball. This can optionally be
+ followed by a "::SHA-256=hex-digit" of the tarball. </desc>
+ </param>
+ <param name="file" type="IExtPackFile" dir="return">
+ <desc>The interface of the extension pack file object.</desc>
+ </param>
+ </method>
+
+ <method name="uninstall">
+ <desc>Uninstalls an extension pack, removing all related files.</desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>The name of the extension pack to uninstall.</desc>
+ </param>
+ <param name="forcedRemoval" type="boolean" dir="in">
+ <desc>
+ Forced removal of the extension pack. This means that the uninstall
+ hook will not be called.
+ </desc>
+ </param>
+ <param name="displayInfo" type="wstring" dir="in">
+ <desc>
+ Platform specific display information. Reserved for future hacks.
+ </desc>
+ </param>
+ <param name="progess" type="IProgress" dir="return">
+ <desc>
+ Progress object for the operation.
+ </desc>
+ </param>
+ </method>
+
+ <method name="cleanup">
+ <desc>Cleans up failed installs and uninstalls</desc>
+ </method>
+
+ <method name="queryAllPlugInsForFrontend">
+ <desc>
+ Gets the path to all the plug-in modules for a given frontend.
+
+ This is a convenience method that is intended to simplify the plug-in
+ loading process for a frontend.
+ </desc>
+ <param name="frontendName" type="wstring" dir="in">
+ <desc>The name of the frontend or component.</desc>
+ </param>
+ <param name="plugInModules" type="wstring" dir="return" safearray="yes">
+ <desc>Array containing the plug-in modules (full paths).</desc>
+ </param>
+ </method>
+
+ <method name="isExtPackUsable">
+ <desc>Check if the given extension pack is loaded and usable.</desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>The name of the extension pack to check for.</desc>
+ </param>
+ <param name="usable" type="boolean" dir="return">
+ <desc>Is the given extension pack loaded and usable.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <!--
+ // BandwidthGroupType
+ /////////////////////////////////////////////////////////////////////////
+ -->
+ <enum
+ name="BandwidthGroupType"
+ uuid="1d92b67d-dc69-4be9-ad4c-93a01e1e0c8e">
+
+ <desc>
+ Type of a bandwidth control group.
+ </desc>
+
+ <const name="Null" value="0">
+ <desc>
+ Null type, must be first.
+ </desc>
+ </const>
+
+ <const name="Disk" value="1">
+ <desc>
+ The bandwidth group controls disk I/O.
+ </desc>
+ </const>
+
+ <const name="Network" value="2">
+ <desc>
+ The bandwidth group controls network I/O.
+ </desc>
+ </const>
+
+ </enum>
+
+ <!--
+ // IBandwidthGroup
+ /////////////////////////////////////////////////////////////////////////
+ -->
+ <interface
+ name="IBandwidthGroup" extends="$unknown"
+ uuid="31587f93-2d12-4d7c-ba6d-ce51d0d5b265"
+ wsmap="managed"
+ rest="managed"
+ reservedAttributes="4"
+ >
+ <desc>Represents one bandwidth group.</desc>
+
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>Name of the group.</desc>
+ </attribute>
+
+ <attribute name="type" type="BandwidthGroupType" readonly="yes">
+ <desc>Type of the group.</desc>
+ </attribute>
+
+ <attribute name="reference" type="unsigned long" readonly="yes">
+ <desc>How many devices/medium attachments use this group.</desc>
+ </attribute>
+
+ <attribute name="maxBytesPerSec" type="long long">
+ <desc>The maximum number of bytes which can be transfered by all
+ entities attached to this group during one second.</desc>
+ </attribute>
+
+ </interface>
+
+ <!--
+ // IBandwidthControl
+ /////////////////////////////////////////////////////////////////////////
+ -->
+ <interface
+ name="IBandwidthControl" extends="$unknown"
+ uuid="48c7f4c0-c9d6-4742-957c-a6fd52e8c4ae"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="2" reservedAttributes="2"
+ >
+ <desc>
+ Controls the bandwidth groups of one machine used to cap I/O done by a VM.
+ This includes network and disk I/O.
+ </desc>
+
+ <attribute name="numGroups" type="unsigned long" readonly="yes">
+ <desc>
+ The current number of existing bandwidth groups managed.
+ </desc>
+ </attribute>
+
+ <method name="createBandwidthGroup">
+ <desc>
+ Creates a new bandwidth group.
+ </desc>
+
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the bandwidth group.</desc>
+ </param>
+ <param name="type" type="BandwidthGroupType" dir="in">
+ <desc>The type of the bandwidth group (network or disk).</desc>
+ </param>
+ <param name="maxBytesPerSec" type="long long" dir="in">
+ <desc>The maximum number of bytes which can be transfered by all
+ entities attached to this group during one second.</desc>
+ </param>
+ </method>
+
+ <method name="deleteBandwidthGroup">
+ <desc>
+ Deletes a new bandwidth group.
+ </desc>
+
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the bandwidth group to delete.</desc>
+ </param>
+ </method>
+
+ <method name="getBandwidthGroup" const="yes">
+ <desc>
+ Get a bandwidth group by name.
+ </desc>
+
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the bandwidth group to get.</desc>
+ </param>
+ <param name="bandwidthGroup" type="IBandwidthGroup" dir="return">
+ <desc>Where to store the bandwidth group on success.</desc>
+ </param>
+ </method>
+
+ <method name="getAllBandwidthGroups" const="yes">
+ <desc>
+ Get all managed bandwidth groups.
+ </desc>
+
+ <param name="bandwidthGroups" type="IBandwidthGroup" dir="return" safearray="yes">
+ <desc>The array of managed bandwidth groups.</desc>
+ </param>
+ </method>
+ </interface>
+
+ <!--
+ // IGuestDebugControl
+ /////////////////////////////////////////////////////////////////////////
+ -->
+ <enum
+ name="GuestDebugProvider"
+ uuid="acdaddc5-aa0f-4f2e-be1f-a9be2828d24a"
+ >
+ <desc>
+ The enabled guest debug provider. This enumeration represents possible
+ values for the <link to="IGuestDebugControl::debugProvider"/> attribute.
+ </desc>
+ <const name="None" value="0">
+ <desc>Guest debugging is disabled.</desc>
+ </const>
+ <const name="Native" value="1">
+ <desc>The native debugger console is enabled.</desc>
+ </const>
+ <const name="GDB" value="2">
+ <desc>The GDB remote stub is enabled.</desc>
+ </const>
+ <const name="KD" value="3">
+ <desc>The WinDbg/KD remote stub is enabled.</desc>
+ </const>
+ </enum>
+
+ <enum
+ name="GuestDebugIoProvider"
+ uuid="0cf00b1b-2ff7-414c-81c6-6cf410eaec4a"
+ >
+ <desc>
+ The enabled guest debug I/O provider. This enumeration represents possible
+ values for the <link to="IGuestDebugControl::debugIoProvider"/> attribute.
+ </desc>
+ <const name="None" value="0">
+ <desc>No connection available (only useful with
+ <link to="GuestDebugProvider::None"/>).
+ </desc>
+ </const>
+ <const name="TCP" value="1">
+ <desc>The remote stub is available through a TCP connection.</desc>
+ </const>
+ <const name="UDP" value="2">
+ <desc>The remote stub is available through a UDP connection.</desc>
+ </const>
+ <const name="IPC" value="3">
+ <desc>The remote stub is available through a IPC connection,
+ namely a named pipe on Windows or a unix socket on other hosts.</desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IGuestDebugControl" extends="$unknown"
+ uuid="1474bb3a-f096-4cd7-a857-8d8e3cea7331"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="2" reservedAttributes="2"
+ >
+ <desc>
+ Controls the guest debug settings of one virtual machine.
+ </desc>
+
+ <attribute name="debugProvider" type="GuestDebugProvider">
+ <desc>The currently active debug provider.</desc>
+ </attribute>
+
+ <attribute name="debugIoProvider" type="GuestDebugIoProvider">
+ <desc>The I/O backend for the selected debug provider.</desc>
+ </attribute>
+
+ <attribute name="debugAddress" type="wstring">
+ <desc>
+ The address to connect to or listen on,
+ depending on the type.
+ </desc>
+ </attribute>
+
+ <attribute name="debugPort" type="unsigned long">
+ <desc>
+ The port to listen on or connect to, depending on the selected I/O
+ provider. Might be ignored by some providers.
+ </desc>
+ </attribute>
+ </interface>
+
+ <!--
+ // IVirtualBoxClient
+ /////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IVirtualBoxClient" extends="$unknown"
+ uuid="d2937a8e-cb8d-4382-90ba-b7da78a74573"
+ wsmap="suppress"
+ reservedMethods="4" reservedAttributes="4"
+ >
+ <desc>
+ Convenience interface for client applications. Treat this as a
+ singleton, i.e. never create more than one instance of this interface.
+
+ At the moment only available for clients of the local API (not usable
+ via the webservice). Once the session logic is redesigned this might
+ change.
+
+ Error information handling is a bit special with IVirtualBoxClient:
+ creating an instance will always succeed. The return of the actual error
+ code/information is postponed to any attribute or method call. The
+ reason for this is that COM likes to mutilate the error code and lose
+ the detailed error information returned by instance creation.
+ </desc>
+
+ <attribute name="virtualBox" type="IVirtualBox" readonly="yes">
+ <desc>
+ Reference to the server-side API root object.
+ </desc>
+ </attribute>
+
+ <attribute name="session" type="ISession" readonly="yes">
+ <desc>
+ Create a new session object and return the reference to it.
+ </desc>
+ </attribute>
+
+ <attribute name="eventSource" type="IEventSource" readonly="yes">
+ <desc>
+ Event source for VirtualBoxClient events.
+ </desc>
+ </attribute>
+
+ <method name="checkMachineError">
+ <desc>
+ Perform error checking before using an <link to="IMachine"/> object.
+ Generally useful before starting a VM and all other uses. If anything
+ is not as it should be then this method will return an appropriate
+ error.
+ </desc>
+
+ <param name="machine" type="IMachine" dir="in">
+ <desc>The machine object to check.</desc>
+ </param>
+ </method>
+ </interface>
+
+ <!--
+ // Events
+ /////////////////////////////////////////////////////////////////////////
+ -->
+ <enum
+ name="VBoxEventType"
+ uuid="e4c5252d-7d1a-4051-8cfb-5b2d7a73d992"
+ >
+
+ <desc>
+ Type of an event.
+ See <link to="IEvent" /> for an introduction to VirtualBox event handling.
+ </desc>
+
+ <const name="Invalid" value="0">
+ <desc>
+ Invalid event, must be first.
+ </desc>
+ </const>
+
+ <const name="Any" value="1">
+ <desc>
+ Wildcard for all events.
+ Events of this type are never delivered, and only used in
+ <link to="IEventSource::registerListener"/> call to simplify registration.
+ </desc>
+ </const>
+
+ <const name="Vetoable" value="2">
+ <desc>
+ Wildcard for all vetoable events. Events of this type are never delivered, and only
+ used in <link to="IEventSource::registerListener"/> call to simplify registration.
+ </desc>
+ </const>
+
+ <const name="MachineEvent" value="3">
+ <desc>
+ Wildcard for all machine events. Events of this type are never delivered, and only used in
+ <link to="IEventSource::registerListener"/> call to simplify registration.
+ </desc>
+ </const>
+
+ <const name="SnapshotEvent" value="4">
+ <desc>
+ Wildcard for all snapshot events. Events of this type are never delivered, and only used in
+ <link to="IEventSource::registerListener"/> call to simplify registration.
+ </desc>
+ </const>
+
+ <const name="InputEvent" value="5">
+ <desc>
+ Wildcard for all input device (keyboard, mouse) events.
+ Events of this type are never delivered, and only used in
+ <link to="IEventSource::registerListener"/> call to simplify registration.
+ </desc>
+ </const>
+
+ <const name="LastWildcard" value="31">
+ <desc>
+ Last wildcard.
+ </desc>
+ </const>
+
+ <const name="OnMachineStateChanged" value="32">
+ <desc>
+ See <link to="IMachineStateChangedEvent">IMachineStateChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnMachineDataChanged" value="33">
+ <desc>
+ See <link to="IMachineDataChangedEvent">IMachineDataChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnExtraDataChanged" value="34">
+ <desc>
+ See <link to="IExtraDataChangedEvent">IExtraDataChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnExtraDataCanChange" value="35">
+ <desc>
+ See <link to="IExtraDataCanChangeEvent">IExtraDataCanChangeEvent</link>.
+ </desc>
+ </const>
+ <const name="OnMediumRegistered" value="36">
+ <desc>
+ See <link to="IMediumRegisteredEvent">IMediumRegisteredEvent</link>.
+ </desc>
+ </const>
+ <const name="OnMachineRegistered" value="37">
+ <desc>
+ See <link to="IMachineRegisteredEvent">IMachineRegisteredEvent</link>.
+ </desc>
+ </const>
+ <const name="OnSessionStateChanged" value="38">
+ <desc>
+ See <link to="ISessionStateChangedEvent">ISessionStateChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnSnapshotTaken" value="39">
+ <desc>
+ See <link to="ISnapshotTakenEvent">ISnapshotTakenEvent</link>.
+ </desc>
+ </const>
+ <const name="OnSnapshotDeleted" value="40">
+ <desc>
+ See <link to="ISnapshotDeletedEvent">ISnapshotDeletedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnSnapshotChanged" value="41">
+ <desc>
+ See <link to="ISnapshotChangedEvent">ISnapshotChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestPropertyChanged" value="42">
+ <desc>
+ See <link to="IGuestPropertyChangedEvent">IGuestPropertyChangedEvent</link>.
+ </desc>
+ </const>
+ <!-- Console events -->
+ <const name="OnMousePointerShapeChanged" value="43">
+ <desc>
+ See <link to="IMousePointerShapeChangedEvent">IMousePointerShapeChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnMouseCapabilityChanged" value="44">
+ <desc>
+ See <link to="IMouseCapabilityChangedEvent">IMouseCapabilityChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnKeyboardLedsChanged" value="45">
+ <desc>
+ See <link to="IKeyboardLedsChangedEvent">IKeyboardLedsChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnStateChanged" value="46">
+ <desc>
+ See <link to="IStateChangedEvent">IStateChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnAdditionsStateChanged" value="47">
+ <desc>
+ See <link to="IAdditionsStateChangedEvent">IAdditionsStateChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnNetworkAdapterChanged" value="48">
+ <desc>
+ See <link to="INetworkAdapterChangedEvent">INetworkAdapterChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnSerialPortChanged" value="49">
+ <desc>
+ See <link to="ISerialPortChangedEvent">ISerialPortChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnParallelPortChanged" value="50">
+ <desc>
+ See <link to="IParallelPortChangedEvent">IParallelPortChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnStorageControllerChanged" value="51">
+ <desc>
+ See <link to="IStorageControllerChangedEvent">IStorageControllerChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnMediumChanged" value="52">
+ <desc>
+ See <link to="IMediumChangedEvent">IMediumChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnVRDEServerChanged" value="53">
+ <desc>
+ See <link to="IVRDEServerChangedEvent">IVRDEServerChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnUSBControllerChanged" value="54">
+ <desc>
+ See <link to="IUSBControllerChangedEvent">IUSBControllerChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnUSBDeviceStateChanged" value="55">
+ <desc>
+ See <link to="IUSBDeviceStateChangedEvent">IUSBDeviceStateChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnSharedFolderChanged" value="56">
+ <desc>
+ See <link to="ISharedFolderChangedEvent">ISharedFolderChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnRuntimeError" value="57">
+ <desc>
+ See <link to="IRuntimeErrorEvent">IRuntimeErrorEvent</link>.
+ </desc>
+ </const>
+ <const name="OnCanShowWindow" value="58">
+ <desc>
+ See <link to="ICanShowWindowEvent">ICanShowWindowEvent</link>.
+ </desc>
+ </const>
+ <const name="OnShowWindow" value="59">
+ <desc>
+ See <link to="IShowWindowEvent">IShowWindowEvent</link>.
+ </desc>
+ </const>
+ <const name="OnCPUChanged" value="60">
+ <desc>
+ See <link to="ICPUChangedEvent">ICPUChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnVRDEServerInfoChanged" value="61">
+ <desc>
+ See <link to="IVRDEServerInfoChangedEvent">IVRDEServerInfoChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnEventSourceChanged" value="62">
+ <desc>
+ See <link to="IEventSourceChangedEvent">IEventSourceChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnCPUExecutionCapChanged" value="63">
+ <desc>
+ See <link to="ICPUExecutionCapChangedEvent">ICPUExecutionCapChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestKeyboard" value="64">
+ <desc>
+ See <link to="IGuestKeyboardEvent">IGuestKeyboardEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestMouse" value="65">
+ <desc>
+ See <link to="IGuestMouseEvent">IGuestMouseEvent</link>.
+ </desc>
+ </const>
+ <const name="OnNATRedirect" value="66">
+ <desc>
+ See <link to="INATRedirectEvent">INATRedirectEvent</link>.
+ </desc>
+ </const>
+ <const name="OnHostPCIDevicePlug" value="67">
+ <desc>
+ See <link to="IHostPCIDevicePlugEvent">IHostPCIDevicePlugEvent</link>.
+ </desc>
+ </const>
+ <const name="OnVBoxSVCAvailabilityChanged" value="68">
+ <desc>
+ See <link to="IVBoxSVCAvailabilityChangedEvent">IVBoxSVCAvailablityChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnBandwidthGroupChanged" value="69">
+ <desc>
+ See <link to="IBandwidthGroupChangedEvent">IBandwidthGroupChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestMonitorChanged" value="70">
+ <desc>
+ See <link to="IGuestMonitorChangedEvent">IGuestMonitorChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnStorageDeviceChanged" value="71">
+ <desc>
+ See <link to="IStorageDeviceChangedEvent">IStorageDeviceChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnClipboardModeChanged" value="72">
+ <desc>
+ See <link to="IClipboardModeChangedEvent">IClipboardModeChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnDnDModeChanged" value="73">
+ <desc>
+ See <link to="IDnDModeChangedEvent">IDnDModeChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnNATNetworkChanged" value="74">
+ <desc>
+ See <link to="INATNetworkChangedEvent">INATNetworkChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnNATNetworkStartStop" value="75">
+ <desc>
+ See <link to="INATNetworkStartStopEvent">INATNetworkStartStopEvent</link>.
+ </desc>
+ </const>
+ <const name="OnNATNetworkAlter" value="76">
+ <desc>
+ See <link to="INATNetworkAlterEvent">INATNetworkAlterEvent</link>.
+ </desc>
+ </const>
+ <const name="OnNATNetworkCreationDeletion" value="77">
+ <desc>
+ See <link to="INATNetworkCreationDeletionEvent">INATNetworkCreationDeletionEvent</link>.
+ </desc>
+ </const>
+ <const name="OnNATNetworkSetting" value="78">
+ <desc>
+ See <link to="INATNetworkSettingEvent">INATNetworkSettingEvent</link>.
+ </desc>
+ </const>
+ <const name="OnNATNetworkPortForward" value="79">
+ <desc>
+ See <link to="INATNetworkPortForwardEvent">INATNetworkPortForwardEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestSessionStateChanged" value="80">
+ <desc>
+ See <link to="IGuestSessionStateChangedEvent">IGuestSessionStateChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestSessionRegistered" value="81">
+ <desc>
+ See <link to="IGuestSessionRegisteredEvent">IGuestSessionRegisteredEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestProcessRegistered" value="82">
+ <desc>
+ See <link to="IGuestProcessRegisteredEvent">IGuestProcessRegisteredEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestProcessStateChanged" value="83">
+ <desc>
+ See <link to="IGuestProcessStateChangedEvent">IGuestProcessStateChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestProcessInputNotify" value="84">
+ <desc>
+ See <link to="IGuestProcessInputNotifyEvent">IGuestProcessInputNotifyEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestProcessOutput" value="85">
+ <desc>
+ See <link to="IGuestProcessOutputEvent">IGuestProcessOutputEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestFileRegistered" value="86">
+ <desc>
+ See <link to="IGuestFileRegisteredEvent">IGuestFileRegisteredEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestFileStateChanged" value="87">
+ <desc>
+ See <link to="IGuestFileStateChangedEvent">IGuestFileStateChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestFileOffsetChanged" value="88">
+ <desc>
+ See <link to="IGuestFileOffsetChangedEvent">IGuestFileOffsetChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestFileRead" value="89">
+ <desc>
+ See <link to="IGuestFileReadEvent">IGuestFileReadEvent</link>.
+
+ <note internal="yes">For performance reasons this is a separate event to
+ not unnecessarily overflow the event queue.</note>
+ </desc>
+ </const>
+ <const name="OnGuestFileWrite" value="90">
+ <desc>
+ See <link to="IGuestFileWriteEvent">IGuestFileWriteEvent</link>.
+
+ <note internal="yes">For performance reasons this is a separate event to
+ not unnecessarily overflow the event queue.</note>
+ </desc>
+ </const>
+ <const name="OnRecordingChanged" value="91">
+ <desc>
+ See <link to="IRecordingChangedEvent">IRecordingChangeEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestUserStateChanged" value="92">
+ <desc>
+ See <link to="IGuestUserStateChangedEvent">IGuestUserStateChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestMultiTouch" value="93">
+ <desc>
+ See <link to="IGuestMultiTouchEvent">IGuestMultiTouchEvent</link>.
+ </desc>
+ </const>
+ <const name="OnHostNameResolutionConfigurationChange" value="94">
+ <desc>
+ See <link to="IHostNameResolutionConfigurationChangeEvent">IHostNameResolutionConfigurationChangeEvent</link>.
+ </desc>
+ </const>
+ <const name="OnSnapshotRestored" value="95">
+ <desc>
+ See <link to="ISnapshotRestoredEvent">ISnapshotRestoredEvent</link>.
+ </desc>
+ </const>
+ <const name="OnMediumConfigChanged" value="96">
+ <desc>
+ See <link to="IMediumConfigChangedEvent">IMediumConfigChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnAudioAdapterChanged" value="97">
+ <desc>
+ See <link to="IAudioAdapterChangedEvent">IAudioAdapterChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnProgressPercentageChanged" value="98">
+ <desc>
+ See <link to="IProgressPercentageChangedEvent">IProgressPercentageChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnProgressTaskCompleted" value="99">
+ <desc>
+ See <link to="IProgressTaskCompletedEvent">IProgressTaskCompletedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnCursorPositionChanged" value="100">
+ <desc>
+ See <link to="ICursorPositionChangedEvent">ICursorPositionChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestAdditionsStatusChanged" value="101">
+ <desc>
+ See <link to="IGuestAdditionsStatusChangedEvent">IGuestAdditionsStatusChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestMonitorInfoChanged" value="102">
+ <desc>
+ See <link to="IGuestMonitorInfoChangedEvent">IGuestMonitorInfoChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestFileSizeChanged" value="103">
+ <desc>
+ See <link to="IGuestFileSizeChangedEvent">IGuestFileSizeChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnClipboardFileTransferModeChanged" value="104">
+ <desc>
+ See <link to="IClipboardFileTransferModeChangedEvent">IClipboardFileTransferModeChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnCloudProviderListChanged" value="105">
+ <desc>
+ See <link to="ICloudProviderListChangedEvent"/>.
+ </desc>
+ </const>
+ <const name="OnCloudProviderRegistered" value="106">
+ <desc>
+ See <link to="ICloudProviderRegisteredEvent"/>.
+ </desc>
+ </const>
+ <const name="OnCloudProviderUninstall" value="107">
+ <desc>
+ See <link to="ICloudProviderUninstallEvent"/>.
+ </desc>
+ </const>
+ <const name="OnCloudProfileRegistered" value="108">
+ <desc>
+ See <link to="ICloudProfileRegisteredEvent"/>.
+ </desc>
+ </const>
+ <const name="OnCloudProfileChanged" value="109">
+ <desc>
+ See <link to="ICloudProfileChangedEvent"/>.
+ </desc>
+ </const>
+ <const name="OnProgressCreated" value="110">
+ <desc>
+ See <link to="IProgressCreatedEvent"/>.
+ </desc>
+ </const>
+ <const name="OnLanguageChanged" value="111">
+ <desc>
+ See <link to="ILanguageChangedEvent"/>.
+ </desc>
+ </const>
+ <const name="OnUpdateAgentAvailable" value="112">
+ <desc>
+ See <link to="IUpdateAgentAvailableEvent">IUpdateAgentAvailableEvent</link>.
+ </desc>
+ </const>
+ <const name="OnUpdateAgentError" value="113">
+ <desc>
+ See <link to="IUpdateAgentErrorEvent">IUpdateAgentErrorEvent</link>.
+ </desc>
+ </const>
+ <const name="OnUpdateAgentSettingsChanged" value="114">
+ <desc>
+ See <link to="IUpdateAgentSettingsChangedEvent">IUpdateAgentSettingsChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnUpdateAgentStateChanged" value="115">
+ <desc>
+ See <link to="IUpdateAgentStateChangedEvent">IUpdateAgentStateChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnHostAudioDeviceChanged" value="116">
+ <desc>
+ See <link to="IHostAudioDeviceChangedEvent">IHostAudioDeviceChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnGuestDebugControlChanged" value="117">
+ <desc>
+ See <link to="IGuestDebugControlChangedEvent">IGuestDebugControlChangedEvent</link>.
+ </desc>
+ </const>
+ <const name="OnMachineGroupsChanged" value="118">
+ <desc>
+ See <link to="IMachineGroupsChangedEvent">IMachineGroupsChangedEvent</link>.
+ </desc>
+ </const>
+ <!-- End event marker -->
+ <const name="End" value="119">
+ <desc>
+ Must be last event, used for iterations and structures relying on numerical event values.
+ </desc>
+ </const>
+
+ </enum>
+
+ <interface
+ name="IEventSource" extends="$unknown"
+ uuid="9b6e1aee-35f3-4f4d-b5bb-ed0ecefd8538"
+ wsmap="managed"
+ rest="managed"
+ >
+ <desc>
+ Event source. Generally, any object which could generate events can be an event source,
+ or aggregate one. To simplify using one-way protocols such as webservices running on top of HTTP(S),
+ an event source can work with listeners in either active or passive mode. In active mode it is up to
+ the IEventSource implementation to call <link to="IEventListener::handleEvent" />, in passive mode the
+ event source keeps track of pending events for each listener and returns available events on demand.
+
+ See <link to="IEvent" /> for an introduction to VirtualBox event handling.
+ </desc>
+
+ <method name="createListener">
+ <desc>
+ Creates a new listener object, useful for passive mode.
+ </desc>
+ <param name="listener" type="IEventListener" dir="return"/>
+ </method>
+
+ <method name="createAggregator">
+ <desc>
+ Creates an aggregator event source, collecting events from multiple sources.
+ This way a single listener can listen for events coming from multiple sources,
+ using a single blocking <link to="#getEvent"/> on the returned aggregator.
+ </desc>
+ <param name="subordinates" type="IEventSource" dir="in" safearray="yes">
+ <desc>
+ Subordinate event source this one aggregates.
+ </desc>
+ </param>
+ <param name="result" type="IEventSource" dir="return">
+ <desc>
+ Event source aggregating passed sources.
+ </desc>
+ </param>
+ </method>
+
+ <method name="registerListener">
+ <desc>
+ Register an event listener.
+
+ <note>
+ To avoid system overload, the VirtualBox server process checks if passive event
+ listeners call <link to="IEventSource::getEvent"/> frequently enough. In the
+ current implementation, if more than 500 pending events are detected for a passive
+ event listener, it is forcefully unregistered by the system, and further
+ <link to="#getEvent" /> calls will return @c VBOX_E_OBJECT_NOT_FOUND.
+ </note>
+ </desc>
+ <param name="listener" type="IEventListener" dir="in">
+ <desc>Listener to register.</desc>
+ </param>
+ <param name="interesting" type="VBoxEventType" dir="in" safearray="yes">
+ <desc>
+ Event types listener is interested in. One can use wildcards like -
+ <link to="VBoxEventType_Any"/> to specify wildcards, matching more
+ than one event.
+ </desc>
+ </param>
+ <param name="active" type="boolean" dir="in">
+ <desc>
+ Which mode this listener is operating in.
+ In active mode, <link to="IEventListener::handleEvent" /> is called directly.
+ In passive mode, an internal event queue is created for this this IEventListener.
+ For each event coming in, it is added to queues for all interested registered passive
+ listeners. It is then up to the external code to call the listener's
+ <link to="IEventListener::handleEvent" /> method. When done with an event, the
+ external code must call <link to="#eventProcessed" />.
+ </desc>
+ </param>
+ </method>
+
+ <method name="unregisterListener">
+ <desc>
+ Unregister an event listener. If listener is passive, and some waitable events are still
+ in queue they are marked as processed automatically.
+ </desc>
+ <param name="listener" type="IEventListener" dir="in">
+ <desc>Listener to unregister.</desc>
+ </param>
+ </method>
+
+ <method name="fireEvent">
+ <desc>
+ Fire an event for this source.
+ </desc>
+ <param name="event" type="IEvent" dir="in">
+ <desc>Event to deliver.</desc>
+ </param>
+ <param name="timeout" type="long" dir="in">
+ <desc>
+ Maximum time to wait for event processing (if event is waitable), in ms;
+ 0 = no wait, -1 = indefinite wait.
+ </desc>
+ </param>
+ <param name="result" type="boolean" dir="return">
+ <desc>true if an event was delivered to all targets, or is non-waitable.</desc>
+ </param>
+ </method>
+
+ <method name="getEvent">
+ <desc>
+ Get events from this peer's event queue (for passive mode). Calling this method
+ regularly is required for passive event listeners to avoid system overload;
+ see <link to="IEventSource::registerListener" /> for details.
+
+ <result name="VBOX_E_OBJECT_NOT_FOUND">
+ Listener is not registered, or autounregistered.
+ </result>
+ </desc>
+ <param name="listener" type="IEventListener" dir="in">
+ <desc>Which listener to get data for.</desc>
+ </param>
+ <param name="timeout" type="long" dir="in">
+ <desc>
+ Maximum time to wait for events, in ms;
+ 0 = no wait, -1 = indefinite wait.
+ </desc>
+ </param>
+ <param name="event" type="IEvent" dir="return">
+ <desc>Event retrieved, or null if none available.</desc>
+ </param>
+ </method>
+
+ <method name="eventProcessed">
+ <desc>
+ Must be called for waitable events after a particular listener finished its
+ event processing. When all listeners of a particular event have called this
+ method, the system will then call <link to="IEvent::setProcessed" />.
+ </desc>
+ <param name="listener" type="IEventListener" dir="in">
+ <desc>Which listener processed event.</desc>
+ </param>
+ <param name="event" type="IEvent" dir="in">
+ <desc>Which event.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IEventListener" extends="$unknown"
+ uuid="67099191-32e7-4f6c-85ee-422304c71b90"
+ wsmap="managed"
+ rest="managed"
+ >
+ <desc>
+ Event listener. An event listener can work in either active or passive mode, depending on the way
+ it was registered.
+ See <link to="IEvent" /> for an introduction to VirtualBox event handling.
+ </desc>
+
+ <method name="handleEvent">
+ <desc>
+ Handle event callback for active listeners. It is not called for
+ passive listeners. After calling <link to="#handleEvent"/> on all active listeners
+ and having received acknowledgement from all passive listeners via
+ <link to="IEventSource::eventProcessed"/>, the event is marked as
+ processed and <link to="IEvent::waitProcessed"/> will return
+ immediately.
+ </desc>
+ <param name="event" type="IEvent" dir="in">
+ <desc>Event available.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IEvent" extends="$unknown"
+ uuid="0ca2adba-8f30-401b-a8cd-fe31dbe839c0"
+ wsmap="managed"
+ rest="managed"
+ >
+ <desc>
+ Abstract parent interface for VirtualBox events. Actual events will typically implement
+ a more specific interface which derives from this (see below).
+
+ <b>Introduction to VirtualBox events</b>
+
+ Generally speaking, an event (represented by this interface) signals that something
+ happened, while an event listener (see <link to="IEventListener" />) represents an
+ entity that is interested in certain events. In order for this to work with
+ unidirectional protocols (i.e. web services), the concepts of passive and active
+ listener are used.
+
+ Event consumers can register themselves as listeners, providing an array of
+ events they are interested in (see <link to="IEventSource::registerListener" />).
+ When an event triggers, the listener is notified about the event. The exact
+ mechanism of the notification depends on whether the listener was registered as
+ an active or passive listener:
+
+ <ul>
+ <li>An active listener is very similar to a callback: it is a function invoked
+ by the API. As opposed to the callbacks that were used in the API before
+ VirtualBox 4.0 however, events are now objects with an interface hierarchy.
+ </li>
+
+ <li>Passive listeners are somewhat trickier to implement, but do not require
+ a client function to be callable, which is not an option with scripting
+ languages or web service clients. Internally the <link to="IEventSource" />
+ implementation maintains an event queue for each passive listener, and
+ newly arrived events are put in this queue. When the listener calls
+ <link to="IEventSource::getEvent"/>, first element from its internal event
+ queue is returned. When the client completes processing of an event,
+ the <link to="IEventSource::eventProcessed" /> function must be called,
+ acknowledging that the event was processed. It supports implementing
+ waitable events. On passive listener unregistration, all events from its
+ queue are auto-acknowledged.
+ </li>
+ </ul>
+
+ Waitable events are useful in situations where the event generator wants to track
+ delivery or a party wants to wait until all listeners have completed the event. A
+ typical example would be a vetoable event (see <link to="IVetoEvent" />) where a
+ listeners might veto a certain action, and thus the event producer has to make
+ sure that all listeners have processed the event and not vetoed before taking
+ the action.
+
+ A given event may have both passive and active listeners at the same time.
+
+ <b>Using events</b>
+
+ Any VirtualBox object capable of producing externally visible events provides an
+ @c eventSource read-only attribute, which is of the type <link to="IEventSource" />.
+ This event source object is notified by VirtualBox once something has happened, so
+ consumers may register event listeners with this event source. To register a listener,
+ an object implementing the <link to="IEventListener" /> interface must be provided.
+ For active listeners, such an object is typically created by the consumer, while for
+ passive listeners <link to="IEventSource::createListener" /> should be used. Please
+ note that a listener created with <link to="IEventSource::createListener"/> must not be used as an active listener.
+
+ Once created, the listener must be registered to listen for the desired events
+ (see <link to="IEventSource::registerListener" />), providing an array of
+ <link to="VBoxEventType" /> enums. Those elements can either be the individual
+ event IDs or wildcards matching multiple event IDs.
+
+ After registration, the callback's <link to="IEventListener::handleEvent" /> method is
+ called automatically when the event is triggered, while passive listeners have to call
+ <link to="IEventSource::getEvent" /> and <link to="IEventSource::eventProcessed" /> in
+ an event processing loop.
+
+ The IEvent interface is an abstract parent interface for all such VirtualBox events
+ coming in. As a result, the standard use pattern inside <link to="IEventListener::handleEvent" />
+ or the event processing loop is to check the <link to="#type" /> attribute of the event and
+ then cast to the appropriate specific interface using @c QueryInterface().
+ </desc>
+
+ <attribute name="type" readonly="yes" type="VBoxEventType">
+ <desc>
+ Event type.
+ </desc>
+ </attribute>
+
+ <attribute name="source" readonly="yes" type="IEventSource">
+ <desc>
+ Source of this event.
+ </desc>
+ </attribute>
+
+ <attribute name="waitable" readonly="yes" type="boolean">
+ <desc>
+ If we can wait for this event being processed. If false, <link to="#waitProcessed"/> returns immediately,
+ and <link to="#setProcessed"/> doesn't make sense. Non-waitable events are generally better performing,
+ as no additional overhead associated with waitability imposed.
+ Waitable events are needed when one need to be able to wait for particular event processed,
+ for example for vetoable changes, or if event refers to some resource which need to be kept immutable
+ until all consumers confirmed events.
+ </desc>
+ </attribute>
+
+ <method name="setProcessed">
+ <desc>
+ Internal method called by the system when all listeners of a particular event have called
+ <link to="IEventSource::eventProcessed" />. This should not be called by client code.
+ </desc>
+ </method>
+
+ <method name="waitProcessed">
+ <desc>
+ Wait until time outs, or this event is processed. Event must be waitable for this operation to have
+ described semantics, for non-waitable returns true immediately.
+ </desc>
+ <param name="timeout" type="long" dir="in">
+ <desc>
+ Maximum time to wait for event processing, in ms;
+ 0 = no wait, -1 = indefinite wait.
+ </desc>
+ </param>
+ <param name="result" type="boolean" dir="return">
+ <desc>If this event was processed before timeout.</desc>
+ </param>
+ </method>
+ </interface>
+
+
+ <interface
+ name="IReusableEvent" extends="IEvent"
+ uuid="69bfb134-80f6-4266-8e20-16371f68fa25"
+ wsmap="managed"
+ >
+ <desc>Base abstract interface for all reusable events.</desc>
+
+ <attribute name="generation" readonly="yes" type="unsigned long">
+ <desc>Current generation of event, incremented on reuse.</desc>
+ </attribute>
+
+ <method name="reuse">
+ <desc>
+ Marks an event as reused, increments 'generation', fields shall no
+ longer be considered valid.
+ </desc>
+ </method>
+ </interface>
+
+ <interface
+ name="IMachineEvent" extends="IEvent"
+ uuid="92ed7b1a-0d96-40ed-ae46-a564d484325e"
+ wsmap="managed" id="MachineEvent"
+ >
+ <desc>Base abstract interface for all machine events.</desc>
+
+ <attribute name="machineId" readonly="yes" type="uuid" mod="string">
+ <desc>ID of the machine this event relates to.</desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IMachineStateChangedEvent" extends="IMachineEvent"
+ uuid="5748F794-48DF-438D-85EB-98FFD70D18C9"
+ wsmap="managed" autogen="VBoxEvent" id="OnMachineStateChanged"
+ >
+ <desc>Machine state change event.</desc>
+
+ <attribute name="state" readonly="yes" type="MachineState">
+ <desc>New execution state.</desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IMachineDataChangedEvent" extends="IMachineEvent"
+ uuid="abe94809-2e88-4436-83d7-50f3e64d0503"
+ wsmap="managed" autogen="VBoxEvent" id="OnMachineDataChanged"
+ >
+ <desc>
+ Any of the settings of the given machine has changed.
+ </desc>
+
+ <attribute name="temporary" readonly="yes" type="boolean">
+ <desc>@c true if the settings change is temporary. All permanent
+ settings changes will trigger an event, and only temporary settings
+ changes for running VMs will trigger an event. Note: sending events
+ for temporary changes is NOT IMPLEMENTED.</desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IMediumRegisteredEvent" extends="IEvent"
+ uuid="53fac49a-b7f1-4a5a-a4ef-a11dd9c2a458"
+ wsmap="managed" autogen="VBoxEvent" id="OnMediumRegistered"
+ >
+ <desc>
+ The given medium was registered or unregistered
+ within this VirtualBox installation.
+ </desc>
+
+ <attribute name="mediumId" readonly="yes" type="uuid" mod="string">
+ <desc>ID of the medium this event relates to.</desc>
+ </attribute>
+
+ <attribute name="mediumType" readonly="yes" type="DeviceType">
+ <desc>Type of the medium this event relates to.</desc>
+ </attribute>
+
+ <attribute name="registered" type="boolean" readonly="yes">
+ <desc>
+ If @c true, the medium was registered, otherwise it was
+ unregistered.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IMediumConfigChangedEvent" extends="IEvent"
+ uuid="dd3e2654-a161-41f1-b583-4892f4a9d5d5"
+ wsmap="managed" autogen="VBoxEvent" id="OnMediumConfigChanged"
+ >
+ <desc>
+ The configuration of the given medium was changed (location, properties,
+ child/parent or anything else).
+ </desc>
+
+ <attribute name="medium" type="IMedium" readonly="yes">
+ <desc>ID of the medium this event relates to.</desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IMachineRegisteredEvent" extends="IMachineEvent"
+ uuid="c354a762-3ff2-4f2e-8f09-07382ee25088"
+ wsmap="managed" autogen="VBoxEvent" id="OnMachineRegistered"
+ >
+ <desc>
+ The given machine was registered or unregistered
+ within this VirtualBox installation.
+ </desc>
+
+ <attribute name="registered" type="boolean" readonly="yes">
+ <desc>
+ If @c true, the machine was registered, otherwise it was
+ unregistered.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="ISessionStateChangedEvent" extends="IMachineEvent"
+ uuid="714a3eef-799a-4489-86cd-fe8e45b2ff8e"
+ wsmap="managed" autogen="VBoxEvent" id="OnSessionStateChanged"
+ >
+ <desc>
+ The state of the session for the given machine was changed.
+ <see><link to="IMachine::sessionState"/></see>
+ </desc>
+
+ <attribute name="state" type="SessionState" readonly="yes">
+ <desc>
+ New session state.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IGuestPropertyChangedEvent" extends="IMachineEvent"
+ uuid="2d0f4c6f-a77e-45c5-96d2-7ca7daae63a9"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestPropertyChanged"
+ >
+ <desc>
+ Notification when a guest property has changed.
+ </desc>
+
+ <attribute name="name" readonly="yes" type="wstring">
+ <desc>
+ The name of the property that has changed.
+ </desc>
+ </attribute>
+
+ <attribute name="value" readonly="yes" type="wstring">
+ <desc>
+ The new property value.
+ </desc>
+ </attribute>
+
+ <attribute name="flags" readonly="yes" type="wstring">
+ <desc>
+ The new property flags.
+ </desc>
+ </attribute>
+
+ <attribute name="fWasDeleted" readonly="yes" type="boolean">
+ <desc>
+ A flag which indicates that property was deleted.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="ISnapshotEvent" extends="IMachineEvent"
+ uuid="21637b0e-34b8-42d3-acfb-7e96daf77c22"
+ wsmap="managed" id="SnapshotEvent"
+ >
+ <desc>Base interface for all snapshot events.</desc>
+
+ <attribute name="snapshotId" readonly="yes" type="uuid" mod="string">
+ <desc>ID of the snapshot this event relates to.</desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="ISnapshotTakenEvent" extends="ISnapshotEvent"
+ uuid="d27c0b3d-6038-422c-b45e-6d4a0503d9f1"
+ wsmap="managed" autogen="VBoxEvent" id="OnSnapshotTaken"
+ >
+ <desc>
+ A new snapshot of the machine has been taken.
+ <see><link to="ISnapshot"/></see>
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="ISnapshotDeletedEvent" extends="ISnapshotEvent"
+ uuid="c48f3401-4a9e-43f4-b7a7-54bd285e22f4"
+ wsmap="managed" autogen="VBoxEvent" id="OnSnapshotDeleted"
+ >
+ <desc>
+ Snapshot of the given machine has been deleted.
+
+ <note>
+ This notification is delivered <b>after</b> the snapshot
+ object has been uninitialized on the server (so that any
+ attempt to call its methods will return an error).
+ </note>
+
+ <see><link to="ISnapshot"/></see>
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="ISnapshotRestoredEvent" extends="ISnapshotEvent"
+ uuid="f4d803b4-9b2d-4377-bfe6-9702e881516b"
+ wsmap="managed" autogen="VBoxEvent" id="OnSnapshotRestored"
+ >
+ <desc>
+ Snapshot of the given machine has been restored.
+ <see><link to="ISnapshot"/></see>
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="ISnapshotChangedEvent" extends="ISnapshotEvent"
+ uuid="07541941-8079-447a-a33e-47a69c7980db"
+ wsmap="managed" autogen="VBoxEvent" id="OnSnapshotChanged"
+ >
+ <desc>
+ Snapshot properties (name and/or description) have been changed.
+ <see><link to="ISnapshot"/></see>
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IMousePointerShapeChangedEvent" extends="IEvent"
+ uuid="a6dcf6e8-416b-4181-8c4a-45ec95177aef"
+ wsmap="managed" autogen="VBoxEvent" id="OnMousePointerShapeChanged"
+ >
+ <desc>
+ Notification when the guest mouse pointer shape has
+ changed. The new shape data is given.
+ </desc>
+
+ <attribute name="visible" type="boolean" readonly="yes">
+ <desc>
+ Flag whether the pointer is visible.
+ </desc>
+ </attribute>
+ <attribute name="alpha" type="boolean" readonly="yes">
+ <desc>
+ Flag whether the pointer has an alpha channel.
+ </desc>
+ </attribute>
+ <attribute name="xhot" type="unsigned long" readonly="yes">
+ <desc>
+ The pointer hot spot X coordinate.
+ </desc>
+ </attribute>
+ <attribute name="yhot" type="unsigned long" readonly="yes">
+ <desc>
+ The pointer hot spot Y coordinate.
+ </desc>
+ </attribute>
+ <attribute name="width" type="unsigned long" readonly="yes">
+ <desc>
+ Width of the pointer shape in pixels.
+ </desc>
+ </attribute>
+ <attribute name="height" type="unsigned long" readonly="yes">
+ <desc>
+ Height of the pointer shape in pixels.
+ </desc>
+ </attribute>
+ <attribute name="shape" type="octet" safearray="yes" readonly="yes">
+ <desc>
+ Shape buffer arrays.
+
+ The @a shape buffer contains a 1-bpp (bits per pixel) AND mask
+ followed by a 32-bpp XOR (color) mask.
+
+ For pointers without alpha channel the XOR mask pixels are
+ 32-bit values: (lsb)BGR0(msb). For pointers with alpha channel
+ the XOR mask consists of (lsb)BGRA(msb) 32-bit values.
+
+ An AND mask is used for pointers with alpha channel, so if the
+ callback does not support alpha, the pointer could be
+ displayed as a normal color pointer.
+
+ The AND mask is a 1-bpp bitmap with byte aligned scanlines. The
+ size of the AND mask therefore is <tt>cbAnd = (width + 7) / 8 *
+ height</tt>. The padding bits at the end of each scanline are
+ undefined.
+
+ The XOR mask follows the AND mask on the next 4-byte aligned
+ offset: <tt>uint8_t *pXor = pAnd + (cbAnd + 3) &amp; ~3</tt>.
+ Bytes in the gap between the AND and the XOR mask are undefined.
+ The XOR mask scanlines have no gap between them and the size of
+ the XOR mask is: <tt>cXor = width * 4 * height</tt>.
+
+ <note>
+ If @a shape is 0, only the pointer visibility is changed.
+ </note>
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IMouseCapabilityChangedEvent" extends="IEvent"
+ uuid="4a773393-7a8c-4d57-b228-9ade4049a81f"
+ wsmap="managed" autogen="VBoxEvent" id="OnMouseCapabilityChanged"
+ >
+ <desc>
+ Notification when the mouse capabilities reported by the
+ guest have changed. The new capabilities are passed.
+ </desc>
+ <attribute name="supportsAbsolute" type="boolean" readonly="yes">
+ <desc>
+ Supports absolute coordinates.
+ </desc>
+ </attribute>
+ <attribute name="supportsRelative" type="boolean" readonly="yes">
+ <desc>
+ Supports relative coordinates.
+ </desc>
+ </attribute>
+ <attribute name="supportsTouchScreen" type="boolean" readonly="yes">
+ <desc>
+ Supports multi-touch events, touchscreen variant.
+ </desc>
+ </attribute>
+ <attribute name="supportsTouchPad" type="boolean" readonly="yes">
+ <desc>
+ Supports multi-touch events, touchpad variant.
+ </desc>
+ </attribute>
+ <attribute name="needsHostCursor" type="boolean" readonly="yes">
+ <desc>
+ If host cursor is needed.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IKeyboardLedsChangedEvent" extends="IEvent"
+ uuid="6DDEF35E-4737-457B-99FC-BC52C851A44F"
+ wsmap="managed" autogen="VBoxEvent" id="OnKeyboardLedsChanged"
+ >
+ <desc>
+ Notification when the guest OS executes the KBD_CMD_SET_LEDS command
+ to alter the state of the keyboard LEDs.
+ </desc>
+ <attribute name="numLock" type="boolean" readonly="yes">
+ <desc>
+ NumLock status.
+ </desc>
+ </attribute>
+ <attribute name="capsLock" type="boolean" readonly="yes">
+ <desc>
+ CapsLock status.
+ </desc>
+ </attribute>
+ <attribute name="scrollLock" type="boolean" readonly="yes">
+ <desc>
+ ScrollLock status.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IStateChangedEvent" extends="IEvent"
+ uuid="4376693C-CF37-453B-9289-3B0F521CAF27"
+ wsmap="managed" autogen="VBoxEvent" id="OnStateChanged"
+ >
+ <desc>
+ Notification when the execution state of the machine has changed.
+ The new state is given.
+ </desc>
+ <attribute name="state" type="MachineState" readonly="yes">
+ <desc>
+ New machine state.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IAdditionsStateChangedEvent" extends="IEvent"
+ uuid="D70F7915-DA7C-44C8-A7AC-9F173490446A"
+ wsmap="managed" autogen="VBoxEvent" id="OnAdditionsStateChanged"
+ >
+ <desc>
+ Notification when a Guest Additions property changes.
+ Interested callees should query IGuest attributes to
+ find out what has changed.
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="INetworkAdapterChangedEvent" extends="IEvent"
+ uuid="08889892-1EC6-4883-801D-77F56CFD0103"
+ wsmap="managed" autogen="VBoxEvent" id="OnNetworkAdapterChanged"
+ >
+ <desc>
+ Notification when a property of one of the
+ virtual <link to="IMachine::getNetworkAdapter">network adapters</link>
+ changes. Interested callees should use INetworkAdapter methods and
+ attributes to find out what has changed.
+ </desc>
+ <attribute name="networkAdapter" type="INetworkAdapter" readonly="yes">
+ <desc>
+ Network adapter that is subject to change.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IAudioAdapterChangedEvent" extends="IEvent"
+ uuid="D5ABC823-04D0-4DB6-8D66-DC2F033120E1"
+ wsmap="managed" autogen="VBoxEvent" id="OnAudioAdapterChanged"
+ >
+ <desc>
+ Notification when a property of the audio adapter changes.
+ Interested callees should use IAudioAdapter methods and attributes
+ to find out what has changed.
+ </desc>
+ <attribute name="audioAdapter" type="IAudioAdapter" readonly="yes">
+ <desc>
+ Audio adapter that is subject to change.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="ISerialPortChangedEvent" extends="IEvent"
+ uuid="3BA329DC-659C-488B-835C-4ECA7AE71C6C"
+ wsmap="managed" autogen="VBoxEvent" id="OnSerialPortChanged"
+ >
+ <desc>
+ Notification when a property of one of the
+ virtual <link to="IMachine::getSerialPort">serial ports</link> changes.
+ Interested callees should use ISerialPort methods and attributes
+ to find out what has changed.
+ </desc>
+ <attribute name="serialPort" type="ISerialPort" readonly="yes">
+ <desc>
+ Serial port that is subject to change.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IParallelPortChangedEvent" extends="IEvent"
+ uuid="813C99FC-9849-4F47-813E-24A75DC85615"
+ wsmap="managed" autogen="VBoxEvent" id="OnParallelPortChanged"
+ >
+ <desc>
+ Notification when a property of one of the
+ virtual <link to="IMachine::getParallelPort">parallel ports</link>
+ changes. Interested callees should use ISerialPort methods and
+ attributes to find out what has changed.
+ </desc>
+ <attribute name="parallelPort" type="IParallelPort" readonly="yes">
+ <desc>
+ Parallel port that is subject to change.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IStorageControllerChangedEvent" extends="IEvent"
+ uuid="6BB335CC-1C58-440C-BB7B-3A1397284C7B"
+ wsmap="managed" autogen="VBoxEvent" id="OnStorageControllerChanged"
+ >
+ <desc>
+ Notification when a
+ <link to="IMachine::storageControllers">storage controllers</link>
+ changes.
+ </desc>
+ <attribute name="machinId" type="uuid" mod="string" readonly="yes">
+ <desc>
+ The id of the machine containing the storage controller.
+ </desc>
+ </attribute>
+ <attribute name="controllerName" type="wstring" readonly="yes">
+ <desc>
+ The name of the storage controller.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IMediumChangedEvent" extends="IEvent"
+ uuid="0FE2DA40-5637-472A-9736-72019EABD7DE"
+ wsmap="managed" autogen="VBoxEvent" id="OnMediumChanged"
+ >
+ <desc>
+ Notification when a
+ <link to="IMachine::mediumAttachments">medium attachment</link>
+ changes.
+ </desc>
+ <attribute name="mediumAttachment" type="IMediumAttachment" readonly="yes">
+ <desc>
+ Medium attachment that is subject to change.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IClipboardModeChangedEvent" extends="IEvent"
+ uuid="cac21692-7997-4595-a731-3a509db604e5"
+ wsmap="managed" autogen="VBoxEvent" id="OnClipboardModeChanged"
+ >
+ <desc>
+ Notification when the shared clipboard mode changes.
+ </desc>
+ <attribute name="clipboardMode" type="ClipboardMode" readonly="yes">
+ <desc>
+ The new clipboard mode.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IClipboardFileTransferModeChangedEvent" extends="IEvent"
+ uuid="00391758-00B1-4E9D-0000-11FA00F9D583"
+ wsmap="managed" autogen="VBoxEvent" id="OnClipboardFileTransferModeChanged"
+ >
+ <desc>
+ Notification when the shared clipboard file transfer mode changes.
+ </desc>
+ <attribute name="enabled" type="boolean" readonly="yes">
+ <desc>
+ Whether file transfers are allowed or not.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IDnDModeChangedEvent" extends="IEvent"
+ uuid="b55cf856-1f8b-4692-abb4-462429fae5e9"
+ wsmap="managed" autogen="VBoxEvent" id="OnDnDModeChanged"
+ >
+ <desc>
+ Notification when the drag'n drop mode changes.
+ </desc>
+ <attribute name="dndMode" type="DnDMode" readonly="yes">
+ <desc>
+ The new drag'n drop mode.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="ICPUChangedEvent" extends="IEvent"
+ uuid="4da2dec7-71b2-4817-9a64-4ed12c17388e"
+ wsmap="managed" autogen="VBoxEvent" id="OnCPUChanged"
+ >
+ <desc>
+ Notification when a CPU changes.
+ </desc>
+ <attribute name="CPU" type="unsigned long" readonly="yes">
+ <desc>
+ The CPU which changed.
+ </desc>
+ </attribute>
+ <attribute name="add" type="boolean" readonly="yes">
+ <desc>
+ Flag whether the CPU was added or removed.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="ICPUExecutionCapChangedEvent" extends="IEvent"
+ uuid="dfa7e4f5-b4a4-44ce-85a8-127ac5eb59dc"
+ wsmap="managed" autogen="VBoxEvent" id="OnCPUExecutionCapChanged"
+ >
+ <desc>
+ Notification when the CPU execution cap changes.
+ </desc>
+ <attribute name="executionCap" type="unsigned long" readonly="yes">
+ <desc>
+ The new CPU execution cap value. (1-100)
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IGuestKeyboardEvent" extends="IEvent"
+ uuid="88394258-7006-40d4-b339-472ee3801844"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestKeyboard"
+ >
+ <desc>
+ Notification when guest keyboard event happens.
+ </desc>
+ <attribute name="scancodes" type="long" safearray="yes" readonly="yes">
+ <desc>
+ Array of scancodes.
+ </desc>
+ </attribute>
+ </interface>
+
+ <enum
+ name="GuestMouseEventMode"
+ uuid="4b500146-ebba-4b7c-bc29-69c2d57a5caf"
+ >
+
+ <desc>
+ The mode (relative, absolute, multi-touch) of a pointer event.
+
+ @todo A clear pattern seems to be emerging that we should usually have
+ multiple input devices active for different types of reporting, so we
+ should really have different event types for relative (including wheel),
+ absolute (not including wheel) and multi-touch events.
+ </desc>
+
+ <const name="Relative" value="0">
+ <desc>
+ Relative event.
+ </desc>
+ </const>
+
+ <const name="Absolute" value="1">
+ <desc>
+ Absolute event.
+ </desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IGuestMouseEvent" extends="IReusableEvent"
+ uuid="179f8647-319c-4e7e-8150-c5837bd265f6"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestMouse"
+ >
+ <desc>
+ Notification when guest mouse event happens.
+ </desc>
+
+ <attribute name="mode" type="GuestMouseEventMode" readonly="yes">
+ <desc>
+ If this event is relative, absolute or multi-touch.
+ </desc>
+ </attribute>
+
+ <attribute name="x" type="long" readonly="yes">
+ <desc>
+ New X position, or X delta.
+ </desc>
+ </attribute>
+
+ <attribute name="y" type="long" readonly="yes">
+ <desc>
+ New Y position, or Y delta.
+ </desc>
+ </attribute>
+
+ <attribute name="z" type="long" readonly="yes">
+ <desc>
+ Z delta.
+ </desc>
+ </attribute>
+
+ <attribute name="w" type="long" readonly="yes">
+ <desc>
+ W delta.
+ </desc>
+ </attribute>
+
+ <attribute name="buttons" type="long" readonly="yes">
+ <desc>
+ Button state bitmask.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestMultiTouchEvent" extends="IEvent"
+ uuid="1f99d9dc-c144-4c28-9f88-e6f488db5441"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestMultiTouch"
+ >
+ <desc>
+ Notification when guest touch screen event happens.
+ </desc>
+ <attribute name="contactCount" type="long" readonly="yes">
+ <desc>
+ Number of contacts in the event.
+ </desc>
+ </attribute>
+ <attribute name="xPositions" type="short" safearray="yes" readonly="yes">
+ <desc>
+ X positions.
+ </desc>
+ </attribute>
+ <attribute name="yPositions" type="short" safearray="yes" readonly="yes">
+ <desc>
+ Y positions.
+ </desc>
+ </attribute>
+ <attribute name="contactIds" type="unsigned short" safearray="yes" readonly="yes">
+ <desc>
+ Contact identifiers.
+ </desc>
+ </attribute>
+ <attribute name="contactFlags" type="unsigned short" safearray="yes" readonly="yes">
+ <desc>
+ Contact state.
+ Bit 0: in contact.
+ Bit 1: in range.
+ </desc>
+ </attribute>
+ <attribute name="isTouchScreen" type="boolean" readonly="yes">
+ <desc>
+ Distinguishes between touchscreen and touchpad events.
+ </desc>
+ </attribute>
+ <attribute name="scanTime" type="unsigned long" readonly="yes">
+ <desc>
+ Timestamp of the event in milliseconds. Only relative time between events is important.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IGuestSessionEvent" extends="IEvent"
+ uuid="b9acd33f-647d-45ac-8fe9-f49b3183ba37"
+ wsmap="managed"
+ >
+ <desc>Base abstract interface for all guest session events.</desc>
+
+ <attribute name="session" type="IGuestSession" readonly="yes">
+ <desc>Guest session that is subject to change.</desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestSessionStateChangedEvent" extends="IGuestSessionEvent"
+ uuid="327e3c00-ee61-462f-aed3-0dff6cbf9904"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestSessionStateChanged"
+ >
+ <desc>
+ Notification when a guest session changed its state.
+ </desc>
+
+ <attribute name="id" type="unsigned long" readonly="yes">
+ <desc>
+ Session ID of guest session which was changed.
+ </desc>
+ </attribute>
+ <attribute name="status" type="GuestSessionStatus" readonly="yes">
+ <desc>
+ New session status.
+ </desc>
+ </attribute>
+ <attribute name="error" type="IVirtualBoxErrorInfo" readonly="yes">
+ <desc>
+ Error information in case of new session status is indicating an error.
+
+ The attribute <link to="IVirtualBoxErrorInfo::resultDetail"/> will contain
+ the runtime (IPRT) error code from the guest. See include/iprt/err.h and
+ include/VBox/err.h for details.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestSessionRegisteredEvent" extends="IGuestSessionEvent"
+ uuid="b79de686-eabd-4fa6-960a-f1756c99ea1c"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestSessionRegistered"
+ >
+ <desc>
+ Notification when a guest session was registered or unregistered.
+ </desc>
+
+ <attribute name="registered" type="boolean" readonly="yes">
+ <desc>
+ If @c true, the guest session was registered, otherwise it was
+ unregistered.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestProcessEvent" extends="IGuestSessionEvent"
+ uuid="2405f0e5-6588-40a3-9b0a-68c05ba52c4b"
+ wsmap="managed"
+ >
+ <desc>Base abstract interface for all guest process events.</desc>
+
+ <attribute name="process" type="IGuestProcess" readonly="yes">
+ <desc>
+ Guest process object which is related to this event.
+ </desc>
+ </attribute>
+ <attribute name="pid" type="unsigned long" readonly="yes">
+ <desc>
+ Guest process ID (PID).
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestProcessRegisteredEvent" extends="IGuestProcessEvent"
+ uuid="1d89e2b3-c6ea-45b6-9d43-dc6f70cc9f02"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestProcessRegistered"
+ >
+ <desc>
+ Notification when a guest process was registered or unregistered.
+ </desc>
+
+ <attribute name="registered" type="boolean" readonly="yes">
+ <desc>
+ If @c true, the guest process was registered, otherwise it was
+ unregistered.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestProcessStateChangedEvent" extends="IGuestProcessEvent"
+ uuid="c365fb7b-4430-499f-92c8-8bed814a567a"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestProcessStateChanged"
+ >
+ <desc>
+ Notification when a guest process changed its state.
+ </desc>
+
+ <attribute name="status" type="ProcessStatus" readonly="yes">
+ <desc>
+ New guest process status.
+ </desc>
+ </attribute>
+ <attribute name="error" type="IVirtualBoxErrorInfo" readonly="yes">
+ <desc>
+ Error information in case of new session status is indicating an error.
+
+ The attribute <link to="IVirtualBoxErrorInfo::resultDetail"/> will contain
+ the runtime (IPRT) error code from the guest. See include/iprt/err.h and
+ include/VBox/err.h for details.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestProcessIOEvent" extends="IGuestProcessEvent"
+ uuid="9ea9227c-e9bb-49b3-bfc7-c5171e93ef38"
+ wsmap="managed"
+ >
+ <desc>
+ Base abstract interface for all guest process input/output (IO) events.
+ </desc>
+
+ <attribute name="handle" type="unsigned long" readonly="yes">
+ <desc>
+ Input/output (IO) handle involved in this event. Usually 0 is stdin,
+ 1 is stdout and 2 is stderr.
+ </desc>
+ </attribute>
+
+ <attribute name="processed" type="unsigned long" readonly="yes">
+ <desc>
+ Processed input or output (in bytes).
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestProcessInputNotifyEvent" extends="IGuestProcessIOEvent"
+ uuid="0de887f2-b7db-4616-aac6-cfb94d89ba78"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestProcessInputNotify"
+ >
+ <desc>
+ Notification when a guest process' stdin became available.
+ <note>This event is right now not implemented!</note>
+ </desc>
+
+ <attribute name="status" type="ProcessInputStatus" readonly="yes">
+ <desc>
+ Current process input status.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestProcessOutputEvent" extends="IGuestProcessIOEvent"
+ uuid="d3d5f1ee-bcb2-4905-a7ab-cc85448a742b"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestProcessOutput"
+ >
+ <desc>
+ Notification when there is guest process output available for reading.
+ </desc>
+
+ <attribute name="data" type="octet" safearray="yes" readonly="yes">
+ <desc>
+ Actual output data.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestFileEvent" extends="IGuestSessionEvent"
+ uuid="c8adb7b0-057d-4391-b928-f14b06b710c5"
+ wsmap="managed"
+ >
+ <desc>Base abstract interface for all guest file events.</desc>
+
+ <attribute name="file" type="IGuestFile" readonly="yes">
+ <desc>
+ Guest file object which is related to this event.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestFileRegisteredEvent" extends="IGuestFileEvent"
+ uuid="d0d93830-70a2-487e-895e-d3fc9679f7b3"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestFileRegistered"
+ >
+ <desc>
+ Notification when a guest file was registered or unregistered.
+ </desc>
+
+ <attribute name="registered" type="boolean" readonly="yes">
+ <desc>
+ If @c true, the guest file was registered, otherwise it was
+ unregistered.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestFileStateChangedEvent" extends="IGuestFileEvent"
+ uuid="d37fe88f-0979-486c-baa1-3abb144dc82d"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestFileStateChanged"
+ >
+ <desc>
+ Notification when a guest file changed its state.
+ </desc>
+
+ <attribute name="status" type="FileStatus" readonly="yes">
+ <desc>
+ New guest file status.
+ </desc>
+ </attribute>
+ <attribute name="error" type="IVirtualBoxErrorInfo" readonly="yes">
+ <desc>
+ Error information in case of new session status is indicating an error.
+
+ The attribute <link to="IVirtualBoxErrorInfo::resultDetail"/> will contain
+ the runtime (IPRT) error code from the guest. See include/iprt/err.h and
+ include/VBox/err.h for details.
+ </desc>
+ </attribute>
+ <!-- Note: No events for reads/writes for performance reasons.
+ See dedicated events IGuestFileReadEvent and
+ IGuestFileWriteEvent. -->
+
+ </interface>
+
+ <interface
+ name="IGuestFileIOEvent" extends="IGuestFileEvent"
+ uuid="b5191a7c-9536-4ef8-820e-3b0e17e5bbc8"
+ wsmap="managed"
+ >
+ <desc>
+ Base abstract interface for all guest file input/output (IO) events.
+ </desc>
+
+ <attribute name="offset" type="long long" readonly="yes">
+ <desc>
+ Current offset (in bytes).
+ </desc>
+ </attribute>
+ <attribute name="processed" type="unsigned long" readonly="yes">
+ <desc>
+ Processed input or output (in bytes).
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestFileOffsetChangedEvent" extends="IGuestFileIOEvent"
+ uuid="e8f79a21-1207-4179-94cf-ca250036308f"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestFileOffsetChanged"
+ >
+ <desc>
+ Notification when a guest file changed its current offset via <link to="IFile::seek" />.
+ </desc>
+
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IGuestFileSizeChangedEvent" extends="IGuestFileEvent"
+ uuid="d78374e9-486e-472f-481b-969746af2480"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestFileSizeChanged"
+ >
+ <desc>
+ Notification when a guest file changed its size via <link to="IFile::setSize" />.
+ </desc>
+
+ <attribute name="newSize" readonly="yes" type="long long"/>
+ </interface>
+
+
+ <interface
+ name="IGuestFileReadEvent" extends="IGuestFileIOEvent"
+ uuid="4ee3cbcb-486f-40db-9150-deee3fd24189"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestFileRead"
+ >
+ <desc>
+ Notification when data has been read from a guest file.
+ </desc>
+
+ <attribute name="data" type="octet" safearray="yes" readonly="yes">
+ <desc>
+ Actual data read.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestFileWriteEvent" extends="IGuestFileIOEvent"
+ uuid="e062a915-3cf5-4c0a-bc90-9b8d4cc94d89"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestFileWrite"
+ >
+ <desc>
+ Notification when data has been written to a guest file.
+ </desc>
+
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IVRDEServerChangedEvent" extends="IEvent"
+ uuid="a06fd66a-3188-4c8c-8756-1395e8cb691c"
+ wsmap="managed" autogen="VBoxEvent" id="OnVRDEServerChanged"
+ >
+ <desc>
+ Notification when a property of the
+ <link to="IMachine::VRDEServer">VRDE server</link> changes.
+ Interested callees should use IVRDEServer methods and attributes to
+ find out what has changed.
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IVRDEServerInfoChangedEvent" extends="IEvent"
+ uuid="dd6a1080-e1b7-4339-a549-f0878115596e"
+ wsmap="managed" autogen="VBoxEvent" id="OnVRDEServerInfoChanged"
+ >
+ <desc>
+ Notification when the status of the VRDE server changes. Interested callees
+ should use <link to="IConsole::VRDEServerInfo">IVRDEServerInfo</link>
+ attributes to find out what is the current status.
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IRecordingChangedEvent" extends="IEvent"
+ uuid="B5DDB370-08A7-4C8F-910D-47AABD67253A"
+ wsmap="managed" autogen="VBoxEvent" id="OnRecordingChanged"
+ >
+ <desc>
+ Notification when recording settings have changed.
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IUSBControllerChangedEvent" extends="IEvent"
+ uuid="93BADC0C-61D9-4940-A084-E6BB29AF3D83"
+ wsmap="managed" autogen="VBoxEvent" id="OnUSBControllerChanged"
+ >
+ <desc>
+ Notification when a property of the virtual
+ <link to="IMachine::USBControllers">USB controllers</link> changes.
+ Interested callees should use IUSBController methods and attributes to
+ find out what has changed.
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IUSBDeviceStateChangedEvent" extends="IEvent"
+ uuid="806da61b-6679-422a-b629-51b06b0c6d93"
+ wsmap="managed" autogen="VBoxEvent" id="OnUSBDeviceStateChanged"
+ >
+ <desc>
+ Notification when a USB device is attached to or detached from
+ the virtual USB controller.
+
+ This notification is sent as a result of the indirect
+ request to attach the device because it matches one of the
+ machine USB filters, or as a result of the direct request
+ issued by <link to="IConsole::attachUSBDevice"/> or
+ <link to="IConsole::detachUSBDevice"/>.
+
+ This notification is sent in case of both a succeeded and a
+ failed request completion. When the request succeeds, the
+ @a error parameter is @c null, and the given device has been
+ already added to (when @a attached is @c true) or removed from
+ (when @a attached is @c false) the collection represented by
+ <link to="IConsole::USBDevices"/>. On failure, the collection
+ doesn't change and the @a error parameter represents the error
+ message describing the failure.
+ </desc>
+ <attribute name="device" type="IUSBDevice" readonly="yes">
+ <desc>
+ Device that is subject to state change.
+ </desc>
+ </attribute>
+ <attribute name="attached" type="boolean" readonly="yes">
+ <desc>
+ @c true if the device was attached and @c false otherwise.
+ </desc>
+ </attribute>
+ <attribute name="error" type="IVirtualBoxErrorInfo" readonly="yes">
+ <desc>
+ @c null on success or an error message object on failure.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="ISharedFolderChangedEvent" extends="IEvent"
+ uuid="B66349B5-3534-4239-B2DE-8E1535D94C0B"
+ wsmap="managed" autogen="VBoxEvent" id="OnSharedFolderChanged"
+ >
+ <desc>
+ Notification when a shared folder is added or removed.
+ The @a scope argument defines one of three scopes:
+ <link to="IVirtualBox::sharedFolders">global shared folders</link>
+ (<link to="Scope_Global">Global</link>),
+ <link to="IMachine::sharedFolders">permanent shared folders</link> of
+ the machine (<link to="Scope_Machine">Machine</link>) or <link
+ to="IConsole::sharedFolders">transient shared folders</link> of the
+ machine (<link to="Scope_Session">Session</link>). Interested callees
+ should use query the corresponding collections to find out what has
+ changed.
+ </desc>
+ <attribute name="scope" type="Scope" readonly="yes">
+ <desc>
+ Scope of the notification.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IRuntimeErrorEvent" extends="IEvent"
+ uuid="883DD18B-0721-4CDE-867C-1A82ABAF914C"
+ wsmap="managed" autogen="VBoxEvent" autogenflags="BSTR" id="OnRuntimeError"
+ >
+ <desc>
+ Notification when an error happens during the virtual
+ machine execution.
+
+ There are three kinds of runtime errors:
+ <ul>
+ <li><i>fatal</i></li>
+ <li><i>non-fatal with retry</i></li>
+ <li><i>non-fatal warnings</i></li>
+ </ul>
+
+ <b>Fatal</b> errors are indicated by the @a fatal parameter set
+ to @c true. In case of fatal errors, the virtual machine
+ execution is always paused before calling this notification, and
+ the notification handler is supposed either to immediately save
+ the virtual machine state using <link to="IMachine::saveState"/>
+ or power it off using <link to="IConsole::powerDown"/>.
+ Resuming the execution can lead to unpredictable results.
+
+ <b>Non-fatal</b> errors and warnings are indicated by the
+ @a fatal parameter set to @c false. If the virtual machine
+ is in the Paused state by the time the error notification is
+ received, it means that the user can <i>try to resume</i> the machine
+ execution after attempting to solve the problem that caused the
+ error. In this case, the notification handler is supposed
+ to show an appropriate message to the user (depending on the
+ value of the @a id parameter) that offers several actions such
+ as <i>Retry</i>, <i>Save</i> or <i>Power Off</i>. If the user
+ wants to retry, the notification handler should continue
+ the machine execution using the <link to="IConsole::resume"/>
+ call. If the machine execution is not Paused during this
+ notification, then it means this notification is a <i>warning</i>
+ (for example, about a fatal condition that can happen very soon);
+ no immediate action is required from the user, the machine
+ continues its normal execution.
+
+ Note that in either case the notification handler
+ <b>must not</b> perform any action directly on a thread
+ where this notification is called. Everything it is allowed to
+ do is to post a message to another thread that will then talk
+ to the user and take the corresponding action.
+
+ Currently, the following error identifiers are known:
+ <ul>
+ <li><tt>"HostMemoryLow"</tt></li>
+ <li><tt>"HostAudioNotResponding"</tt></li>
+ <li><tt>"VDIStorageFull"</tt></li>
+ <li><tt>"3DSupportIncompatibleAdditions"</tt></li>
+ </ul>
+ </desc>
+ <attribute name="fatal" type="boolean" readonly="yes">
+ <desc>
+ Whether the error is fatal or not.
+ </desc>
+ </attribute>
+ <attribute name="id" type="wstring" readonly="yes">
+ <desc>
+ Error identifier.
+ </desc>
+ </attribute>
+ <attribute name="message" type="wstring" readonly="yes">
+ <desc>
+ Optional error message.
+ </desc>
+ </attribute>
+ </interface>
+
+
+ <interface
+ name="IEventSourceChangedEvent" extends="IEvent"
+ uuid="e7932cb8-f6d4-4ab6-9cbf-558eb8959a6a"
+ waitable="yes"
+ wsmap="managed" autogen="VBoxEvent" id="OnEventSourceChanged"
+ >
+ <desc>
+ Notification when an event source state changes (listener added or removed).
+ </desc>
+
+ <attribute name="listener" type="IEventListener" readonly="yes">
+ <desc>
+ Event listener which has changed.
+ </desc>
+ </attribute>
+
+ <attribute name="add" type="boolean" readonly="yes">
+ <desc>
+ Flag whether listener was added or removed.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IExtraDataChangedEvent" extends="IEvent"
+ uuid="024F00CE-6E0B-492A-A8D0-968472A94DC7"
+ wsmap="managed" autogen="VBoxEvent" autogenflags="BSTR" id="OnExtraDataChanged"
+ >
+ <desc>
+ Notification when machine specific or global extra data
+ has changed.
+ </desc>
+ <attribute name="machineId" type="uuid" mod="string" readonly="yes">
+ <desc>
+ ID of the machine this event relates to.
+ Null for global extra data changes.
+ </desc>
+ </attribute>
+ <attribute name="key" type="wstring" readonly="yes">
+ <desc>
+ Extra data key that has changed.
+ </desc>
+ </attribute>
+ <attribute name="value" type="wstring" readonly="yes">
+ <desc>
+ Extra data value for the given key.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IVetoEvent" extends="IEvent"
+ uuid="7c5e945f-2354-4267-883f-2f417d216519"
+ wsmap="managed"
+ >
+ <desc>Base abstract interface for veto events.</desc>
+
+ <method name="addVeto">
+ <desc>
+ Adds a veto on this event.
+ </desc>
+ <param name="reason" type="wstring" dir="in">
+ <desc>
+ Reason for veto, could be null or empty string.
+ </desc>
+ </param>
+ </method>
+
+ <method name="isVetoed">
+ <desc>
+ If this event was vetoed.
+ </desc>
+ <param name="result" type="boolean" dir="return">
+ <desc>
+ Reason for veto.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getVetos">
+ <desc>
+ Current veto reason list, if size is 0 - no veto.
+ </desc>
+ <param name="result" type="wstring" dir="return" safearray="yes">
+ <desc>
+ Array of reasons for veto provided by different event handlers.
+ </desc>
+ </param>
+ </method>
+
+ <method name="addApproval">
+ <desc>
+ Adds an approval on this event.
+ </desc>
+ <param name="reason" type="wstring" dir="in">
+ <desc>
+ Reason for approval, could be null or empty string.
+ </desc>
+ </param>
+ </method>
+
+ <method name="isApproved">
+ <desc>
+ If this event was approved.
+ </desc>
+ <param name="result" type="boolean" dir="return" />
+ </method>
+
+ <method name="getApprovals">
+ <desc>
+ Current approval reason list, if size is 0 - no approvals.
+ </desc>
+ <param name="result" type="wstring" dir="return" safearray="yes">
+ <desc>
+ Array of reasons for approval provided by different event handlers.
+ </desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <interface
+ name="IExtraDataCanChangeEvent" extends="IVetoEvent"
+ uuid="245d88bd-800a-40f8-87a6-170d02249a55"
+ wsmap="managed" autogen="VBoxEvent" id="OnExtraDataCanChange"
+ waitable="yes"
+ >
+ <desc>
+ Notification when someone tries to change extra data for
+ either the given machine or (if @c null) global extra data.
+ This gives the chance to veto against changes.
+ </desc>
+ <attribute name="machineId" type="uuid" mod="string" readonly="yes">
+ <desc>
+ ID of the machine this event relates to.
+ Null for global extra data changes.
+ </desc>
+ </attribute>
+ <attribute name="key" type="wstring" readonly="yes">
+ <desc>
+ Extra data key that has changed.
+ </desc>
+ </attribute>
+ <attribute name="value" type="wstring" readonly="yes">
+ <desc>
+ Extra data value for the given key.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="ICanShowWindowEvent" extends="IVetoEvent"
+ uuid="adf292b0-92c9-4a77-9d35-e058b39fe0b9"
+ wsmap="managed" autogen="VBoxEvent" id="OnCanShowWindow"
+ waitable="yes"
+ >
+ <desc>
+ Notification when a call to
+ <link to="IMachine::canShowConsoleWindow"/> is made by a
+ front-end to check if a subsequent call to
+ <link to="IMachine::showConsoleWindow"/> can succeed.
+
+ The callee should give an answer appropriate to the current
+ machine state using event veto. This answer must
+ remain valid at least until the next
+ <link to="IConsole::state">machine state</link> change.
+ </desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IShowWindowEvent" extends="IEvent"
+ uuid="B0A0904D-2F05-4D28-855F-488F96BAD2B2"
+ wsmap="managed" autogen="VBoxEvent" id="OnShowWindow"
+ waitable="yes"
+ >
+ <desc>
+ Notification when a call to
+ <link to="IMachine::showConsoleWindow"/>
+ requests the console window to be activated and brought to
+ foreground on the desktop of the host PC.
+
+ This notification should cause the VM console process to
+ perform the requested action as described above. If it is
+ impossible to do it at a time of this notification, this
+ method should return a failure.
+
+ Note that many modern window managers on many platforms
+ implement some sort of focus stealing prevention logic, so
+ that it may be impossible to activate a window without the
+ help of the currently active application (which is supposedly
+ an initiator of this notification). In this case, this method
+ must return a non-zero identifier that represents the
+ top-level window of the VM console process. The caller, if it
+ represents a currently active process, is responsible to use
+ this identifier (in a platform-dependent manner) to perform
+ actual window activation.
+
+ This method must set @a winId to zero if it has performed all
+ actions necessary to complete the request and the console
+ window is now active and in foreground, to indicate that no
+ further action is required on the caller's side.
+ </desc>
+ <attribute name="winId" type="long long">
+ <desc>
+ Platform-dependent identifier of the top-level VM console
+ window, or zero if this method has performed all actions
+ necessary to implement the <i>show window</i> semantics for
+ the given platform and/or this VirtualBox front-end.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="INATRedirectEvent" extends="IMachineEvent"
+ uuid="24eef068-c380-4510-bc7c-19314a7352f1"
+ wsmap="managed" autogen="VBoxEvent" id="OnNATRedirect"
+ >
+ <desc>
+ Notification when NAT redirect rule added or removed.
+ </desc>
+ <attribute name="slot" type="unsigned long" readonly="yes">
+ <desc>
+ Adapter which NAT attached to.
+ </desc>
+ </attribute>
+ <attribute name="remove" type="boolean" readonly="yes">
+ <desc>
+ Whether rule remove or add.
+ </desc>
+ </attribute>
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>
+ Name of the rule.
+ </desc>
+ </attribute>
+ <attribute name="proto" type="NATProtocol" readonly="yes">
+ <desc>
+ Protocol (TCP or UDP) of the redirect rule.
+ </desc>
+ </attribute>
+ <attribute name="hostIP" type="wstring" readonly="yes">
+ <desc>
+ Host ip address to bind socket on.
+ </desc>
+ </attribute>
+ <attribute name="hostPort" type="long" readonly="yes">
+ <desc>
+ Host port to bind socket on.
+ </desc>
+ </attribute>
+ <attribute name="guestIP" type="wstring" readonly="yes">
+ <desc>
+ Guest ip address to redirect to.
+ </desc>
+ </attribute>
+ <attribute name="guestPort" type="long" readonly="yes">
+ <desc>
+ Guest port to redirect to.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IHostAudioDeviceChangedEvent" extends="IEvent"
+ waitable="yes"
+ uuid="8dcc633f-7b03-4f0a-9f40-7a784dd0835a"
+ wsmap="managed" autogen="VBoxEvent" id="OnHostAudioDeviceChangedEvent"
+ >
+ <desc>
+ Notification when a host audio device state has changed.
+ </desc>
+
+ <attribute name="device" type="IHostAudioDevice" readonly="yes">
+ <desc>
+ Host audio device that has changed.
+ </desc>
+ </attribute>
+
+ <attribute name="new" type="boolean" readonly="yes">
+ <desc>
+ @c true if the host device is a newly detected device, @c false if not.
+ </desc>
+ </attribute>
+
+ <attribute name="state" type="AudioDeviceState" readonly="yes">
+ <desc>
+ New audio device state.
+ </desc>
+ </attribute>
+
+ <attribute name="error" type="IVirtualBoxErrorInfo" readonly="yes">
+ <desc>
+ @c null on success or an error message object on failure.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IHostPCIDevicePlugEvent" extends="IMachineEvent"
+ waitable="yes"
+ uuid="a0bad6df-d612-47d3-89d4-db3992533948"
+ wsmap="managed" autogen="VBoxEvent" autogenflags="BSTR" id="OnHostPCIDevicePlug"
+ >
+ <desc>
+ Notification when host PCI device is plugged/unplugged. Plugging
+ usually takes place on VM startup, unplug - when
+ <link to="IMachine::detachHostPCIDevice"/> is called.
+
+ <see><link to="IMachine::detachHostPCIDevice"/></see>
+
+ </desc>
+
+ <attribute name="plugged" type="boolean" readonly="yes">
+ <desc>
+ If device successfully plugged or unplugged.
+ </desc>
+ </attribute>
+
+ <attribute name="success" type="boolean" readonly="yes">
+ <desc>
+ If operation was successful, if false - 'message' attribute
+ may be of interest.
+ </desc>
+ </attribute>
+
+ <attribute name="attachment" type="IPCIDeviceAttachment" readonly="yes">
+ <desc>
+ Attachment info for this device.
+ </desc>
+ </attribute>
+
+ <attribute name="message" type="wstring" readonly="yes">
+ <desc>
+ Optional error message.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IVBoxSVCAvailabilityChangedEvent" extends="IEvent"
+ uuid="97c78fcd-d4fc-485f-8613-5af88bfcfcdc"
+ wsmap="managed" autogen="VBoxEvent" id="OnVBoxSVCAvailabilityChanged"
+ >
+ <desc>
+ Notification when VBoxSVC becomes unavailable (due to a crash or similar
+ unexpected circumstances) or available again.
+ </desc>
+
+ <attribute name="available" type="boolean" readonly="yes">
+ <desc>
+ Whether VBoxSVC is available now.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IBandwidthGroupChangedEvent" extends="IEvent"
+ uuid="334df94a-7556-4cbc-8c04-043096b02d82"
+ wsmap="managed" autogen="VBoxEvent" id="OnBandwidthGroupChanged"
+ >
+ <desc>
+ Notification when one of the bandwidth groups changed
+ </desc>
+ <attribute name="bandwidthGroup" type="IBandwidthGroup" readonly="yes">
+ <desc>
+ The changed bandwidth group.
+ </desc>
+ </attribute>
+ </interface>
+
+ <enum
+ name="GuestMonitorChangedEventType"
+ uuid="ef172985-7e36-4297-95be-e46396968d66"
+ >
+
+ <desc>
+ How the guest monitor has been changed.
+ </desc>
+
+ <const name="Enabled" value="0">
+ <desc>
+ The guest monitor has been enabled by the guest.
+ </desc>
+ </const>
+
+ <const name="Disabled" value="1">
+ <desc>
+ The guest monitor has been disabled by the guest.
+ </desc>
+ </const>
+
+ <const name="NewOrigin" value="2">
+ <desc>
+ The guest monitor origin has changed in the guest.
+ </desc>
+ </const>
+ </enum>
+
+ <interface
+ name="IGuestMonitorChangedEvent" extends="IEvent"
+ uuid="0f7b8a22-c71f-4a36-8e5f-a77d01d76090"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestMonitorChanged"
+ >
+ <desc>
+ Notification when the guest enables one of its monitors.
+ </desc>
+
+ <attribute name="changeType" type="GuestMonitorChangedEventType" readonly="yes">
+ <desc>
+ What was changed for this guest monitor.
+ </desc>
+ </attribute>
+
+ <attribute name="screenId" type="unsigned long" readonly="yes">
+ <desc>
+ The monitor which was changed.
+ </desc>
+ </attribute>
+
+ <attribute name="originX" type="unsigned long" readonly="yes">
+ <desc>
+ Physical X origin relative to the primary screen.
+ Valid for Enabled and NewOrigin.
+ </desc>
+ </attribute>
+
+ <attribute name="originY" type="unsigned long" readonly="yes">
+ <desc>
+ Physical Y origin relative to the primary screen.
+ Valid for Enabled and NewOrigin.
+ </desc>
+ </attribute>
+
+ <attribute name="width" type="unsigned long" readonly="yes">
+ <desc>
+ Width of the screen.
+ Valid for Enabled.
+ </desc>
+ </attribute>
+
+ <attribute name="height" type="unsigned long" readonly="yes">
+ <desc>
+ Height of the screen.
+ Valid for Enabled.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestUserStateChangedEvent" extends="IEvent"
+ uuid="39b4e759-1ec0-4c0f-857f-fbe2a737a256"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestUserStateChanged"
+ >
+ <desc>
+ Notification when a guest user changed its state.
+ </desc>
+
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>
+ Name of the guest user whose state changed.
+ </desc>
+ </attribute>
+ <attribute name="domain" type="wstring" readonly="yes">
+ <desc>
+ Name of the FQDN (fully qualified domain name) this user is bound
+ to. Optional.
+ </desc>
+ </attribute>
+ <attribute name="state" type="GuestUserState" readonly="yes">
+ <desc>
+ What was changed for this guest user. See <link to="GuestUserState"/> for
+ more information.
+ </desc>
+ </attribute>
+ <attribute name="stateDetails" type="wstring" readonly="yes">
+ <desc>
+ Optional state details, depending on the <link to="#state"/> attribute.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IStorageDeviceChangedEvent" extends="IEvent"
+ uuid="232e9151-ae84-4b8e-b0f3-5c20c35caac9"
+ wsmap="managed" autogen="VBoxEvent" id="OnStorageDeviceChanged"
+ >
+ <desc>
+ Notification when a
+ <link to="IMachine::mediumAttachments">storage device</link>
+ is attached or removed.
+ </desc>
+ <attribute name="storageDevice" type="IMediumAttachment" readonly="yes">
+ <desc>
+ Storage device that is subject to change.
+ </desc>
+ </attribute>
+ <attribute name="removed" type="boolean" readonly="yes">
+ <desc>
+ Flag whether the device was removed or added to the VM.
+ </desc>
+ </attribute>
+ <attribute name="silent" type="boolean" readonly="yes">
+ <desc>
+ Flag whether the guest should be notified about the change.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="INATNetworkChangedEvent" extends="IEvent"
+ uuid="101ae042-1a29-4a19-92cf-02285773f3b5"
+ wsmap="managed" autogen="VBoxEvent" id="OnNATNetworkChanged"
+ >
+ <!-- network name is common setting for all event types -->
+ <attribute name="networkName" type="wstring" readonly="yes"/>
+ </interface>
+
+ <!-- base class for start/stop events -->
+ <interface name="INATNetworkStartStopEvent" extends="INATNetworkChangedEvent"
+ uuid="269d8f6b-fa1e-4cee-91c7-6d8496bea3c1"
+ wsmap="managed" autogen="VBoxEvent" id="OnNATNetworkStartStop">
+ <attribute name="startEvent" type="boolean" readonly="yes">
+ <desc>
+ IsStartEvent is true when NAT network is started and false on stopping.
+ </desc>
+ </attribute>
+ </interface>
+
+ <!-- base class for modification events -->
+ <interface
+ name="INATNetworkAlterEvent" extends="INATNetworkChangedEvent"
+ uuid="d947adf5-4022-dc80-5535-6fb116815604"
+ wsmap="managed" autogen="VBoxEvent" id="OnNATNetworkAlter"
+ >
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface name="INATNetworkCreationDeletionEvent" extends="INATNetworkAlterEvent"
+ uuid="8d984a7e-b855-40b8-ab0c-44d3515b4528"
+ wsmap="managed" autogen="VBoxEvent" autogenflags="BSTR" id="OnNATNetworkCreationDeletion">
+ <attribute name="creationEvent" type="boolean" readonly="yes"/>
+ </interface>
+
+ <interface name="INATNetworkSettingEvent" extends="INATNetworkAlterEvent"
+ uuid="9db3a9e6-7f29-4aae-a627-5a282c83092c"
+ wsmap="managed" autogen="VBoxEvent" id="OnNATNetworkSetting">
+ <attribute name="enabled" type="boolean" readonly="yes"/>
+ <attribute name="network" type="wstring" readonly="yes"/>
+ <attribute name="gateway" type="wstring" readonly="yes"/>
+ <attribute name="advertiseDefaultIPv6RouteEnabled" type="boolean" readonly="yes" dtracename="advertiseDefIPv6Route"/>
+ <attribute name="needDhcpServer" type="boolean" readonly="yes"/>
+ </interface>
+
+ <interface name="INATNetworkPortForwardEvent" extends="INATNetworkAlterEvent"
+ uuid="2514881b-23d0-430a-a7ff-7ed7f05534bc"
+ wsmap="managed" autogen="VBoxEvent" id="OnNATNetworkPortForward">
+ <attribute name="create" type="boolean" readonly="yes"/>
+ <attribute name="ipv6" type="boolean" readonly="yes"/>
+ <attribute name="name" type="wstring" readonly="yes"/>
+ <attribute name="proto" type="NATProtocol" readonly="yes"/>
+ <attribute name="hostIp" type="wstring" readonly="yes"/>
+ <attribute name="hostPort" type="long" readonly="yes"/>
+ <attribute name="guestIp" type="wstring" readonly="yes"/>
+ <attribute name="guestPort" type="long" readonly="yes"/>
+ </interface>
+
+ <interface
+ name="IHostNameResolutionConfigurationChangeEvent" extends="IEvent"
+ uuid="f9b9e1cf-cb63-47a1-84fb-02c4894b89a9"
+ wsmap="managed" autogen="VBoxEvent" id="OnHostNameResolutionConfigurationChange"
+ dtracename="HostNameResCfgChangeEvent"
+ >
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="IProgressEvent" extends="IEvent"
+ uuid="daaf9016-1f04-4191-aa2f-1fac9646ae4c"
+ wsmap="managed" id="ProgressEvent">
+ <desc>Base abstract interface for all progress events.</desc>
+
+ <attribute name="progressId" readonly="yes" type="uuid" mod="string">
+ <desc>GUID of the progress this event relates to.</desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IProgressCreatedEvent" extends="IProgressEvent"
+ uuid="a85bba40-1b93-47bb-b125-dec708c30fc0"
+ wsmap="managed" autogen="VBoxEvent" id="OnProgressCreated">
+ <desc>Notification of a new progress object creation/deletion.
+ Covers purely projess objects in VBoxSVC. This
+ event is signaled on the event source of IVirtualBox, unlike
+ the other progress events.</desc>
+
+ <attribute name="create" readonly="yes" type="boolean">
+ <desc>If @c true, the progress object was created, otherwise it
+ was deleted.</desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IProgressPercentageChangedEvent" extends="IProgressEvent"
+ uuid="f05d7e60-1bcf-4218-9807-04e036cc70f1"
+ wsmap="managed" autogen="VBoxEvent" id="OnProgressPercentageChanged">
+ <desc>Progress state change event.</desc>
+
+ <attribute name="percent" readonly="yes" type="long">
+ <desc>New percent</desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IProgressTaskCompletedEvent" extends="IProgressEvent"
+ uuid="a5bbdb7d-8ce7-469f-a4c2-6476f581ff72"
+ wsmap="managed" autogen="VBoxEvent" id="OnProgressTaskCompleted">
+ <desc>Progress task completion event.</desc>
+ <attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/>
+ </interface>
+
+ <interface
+ name="ICursorPositionChangedEvent" extends="IEvent"
+ uuid="6f302674-c927-11e7-b788-33c248e71fc7"
+ wsmap="managed" autogen="VBoxEvent" id="OnCursorPositionChanged">
+ <desc>The guest reports cursor position data.</desc>
+
+ <attribute name="hasData" readonly="yes" type="boolean">
+ <desc>Event contains valid data. If not set, switch back to using the host cursor.</desc>
+ </attribute>
+ <attribute name="x" readonly="yes" type="unsigned long">
+ <desc>Reported X position</desc>
+ </attribute>
+ <attribute name="y" readonly="yes" type="unsigned long">
+ <desc>Reported Y position</desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IGuestAdditionsStatusChangedEvent" extends="IEvent"
+ uuid="a443da5b-aa82-4720-bc84-bd097b2b13b8"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestAdditionsStatusChanged">
+ <desc>The guest addition status changed.</desc>
+
+ <attribute name="facility" type="AdditionsFacilityType" readonly="yes">
+ <desc>Facility this event relates to.</desc>
+ </attribute>
+ <attribute name="status" type="AdditionsFacilityStatus" readonly="yes">
+ <desc>The new facility status.</desc>
+ </attribute>
+ <attribute name="runLevel" type="AdditionsRunLevelType" readonly="yes">
+ <desc>The new run level.</desc>
+ </attribute>
+ <attribute name="timestamp" type="long long" readonly="yes">
+ <desc>The millisecond timestamp associated with the event.</desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IGuestMonitorInfoChangedEvent" extends="IEvent"
+ uuid="0b3cdeb2-808e-11e9-b773-133d9330f849"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestMonitorInfoChanged">
+ <desc>The guest reports cursor position data.</desc>
+
+ <attribute name="output" readonly="yes" type="unsigned long">
+ <desc>The virtual display output on which the monitor has changed.</desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IUpdateAgentEvent" extends="IEvent"
+ uuid="ff58a51d-54a1-411c-93e9-3047eb4dcd21"
+ wsmap="managed">
+ <desc>
+ Abstract base interface for update agent events.
+ </desc>
+ <attribute name="agent" readonly="yes" type="IUpdateAgent">
+ <desc>Update agent this event belongs to.</desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IUpdateAgentSettingsChangedEvent" extends="IUpdateAgentEvent"
+ uuid="abef51ae-1493-49f4-aa03-efaf106bf086"
+ wsmap="managed" autogen="VBoxEvent" id="OnUpdateAgentSettingsChanged"
+ >
+ <desc>
+ Notification when update agent settings have been changed.
+ </desc>
+ <attribute name="attributeHint" readonly="yes" type="wstring"/>
+ </interface>
+
+ <interface
+ name="IUpdateAgentErrorEvent" extends="IUpdateAgentEvent"
+ uuid="2a88033d-82db-4ac2-97b5-e786c839420e"
+ wsmap="managed" autogen="VBoxEvent" id="OnUpdateAgentError"
+ >
+ <desc>
+ Notification when an update agent error occurred.
+ </desc>
+
+ <attribute name="msg" type="wstring" readonly="yes">
+ <desc>
+ Error message in human readable format.
+ </desc>
+ </attribute>
+
+ <attribute name="rcError" type="long" readonly="yes">
+ <desc>
+ IPRT-style error code.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IUpdateAgentAvailableEvent" extends="IUpdateAgentEvent"
+ uuid="243829cb-15b7-42a4-8664-7aa4e34993da"
+ wsmap="managed" autogen="VBoxEvent" id="OnUpdateAgentAvailable"
+ >
+ <desc>
+ Notification when an update is available.
+ </desc>
+
+ <attribute name="version" type="wstring" readonly="yes">
+ <desc>
+ Version of the update.
+ </desc>
+ </attribute>
+
+ <attribute name="channel" type="UpdateChannel" readonly="yes">
+ <desc>
+ Channel containing the update.
+ </desc>
+ </attribute>
+
+ <attribute name="severity" type="UpdateSeverity" readonly="yes">
+ <desc>
+ Severity of the update.
+ </desc>
+ </attribute>
+
+ <attribute name="downloadURL" type="wstring" readonly="yes">
+ <desc>
+ Download URL of the update.
+ </desc>
+ </attribute>
+
+ <attribute name="webURL" type="wstring" readonly="yes">
+ <desc>
+ Web URL of the update.
+ </desc>
+ </attribute>
+
+ <attribute name="releaseNotes" type="wstring" readonly="yes">
+ <desc>
+ Release notes of the update.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IUpdateAgentStateChangedEvent" extends="IUpdateAgentEvent"
+ uuid="eb000a0e-2079-4f47-bbcc-c6b28a4e50df"
+ wsmap="managed" autogen="VBoxEvent" id="OnUpdateAgentStateChanged"
+ >
+ <desc>
+ Notification when an update agent state has been changed.
+ </desc>
+
+ <attribute name="state" type="UpdateState" readonly="yes">
+ <desc>
+ New update agent state.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <interface
+ name="IGuestDebugControlChangedEvent" extends="IEvent"
+ uuid="a3d2799e-d3ad-4f73-91ef-7d839689f6d6"
+ wsmap="managed" autogen="VBoxEvent" id="OnGuestDebugControlChanged"
+ >
+ <desc>
+ Notification when a property of the <link to="IMachine::guestDebugControl">guest debug</link>
+ settings changes. Interested callees should use IGuestDebugControl methods and attributes
+ to find out what has changed.
+ </desc>
+ <attribute name="guestDebugControl" type="IGuestDebugControl" readonly="yes">
+ <desc>
+ Guest debug control object that is subject to change.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface
+ name="IMachineGroupsChangedEvent" extends="IMachineEvent"
+ uuid="ee37afb5-7002-4786-a5c4-a9c29e1cce75"
+ wsmap="managed" autogen="VBoxEvent" id="OnMachineGroupsChanged"
+ >
+ <desc>
+ The groups of the machine have changed.
+ </desc>
+
+ <attribute name="dummy" type="boolean" readonly="yes">
+ <desc>
+ Dummy state because you can't have an event without attributes apparently.
+ </desc>
+ </attribute>
+ </interface>
+
+ <!--
+ // Auxiliary containers
+ //////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface name="IStringArray" extends="$unknown"
+ uuid="3890b2c8-604d-11e9-92d3-53cb473db9fb"
+ wsmap="managed"
+ reservedMethods="4">
+ <desc>
+ When you need to return an array of strings asynchronously
+ (under a progress) you cannot use by-value out parameter
+ <tt>type=&quot;wstring&quot; safearray=&quot;yes&quot;
+ dir=&quot;out&quot;</tt>, hence this wrapper.
+ </desc>
+ <attribute name="values" type="wstring" safearray="yes" readonly="yes"/>
+ </interface>
+
+
+ <!--
+ // IForm
+ //////////////////////////////////////////////////////////////////////////
+ -->
+ <enum name="FormValueType"
+ uuid="43d794a0-7c98-11e9-a346-a36d5fa858a5">
+ <const name="Boolean" value="0"/>
+ <const name="String" value="1"/>
+ <const name="Choice" value="2"/>
+ <const name="RangedInteger" value="3"/>
+ </enum>
+
+ <interface name="IFormValue" extends="$unknown"
+ uuid="67c50afe-3e78-11e9-b25e-7768f80c0e07"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="8">
+ <attribute name="type" type="FormValueType" readonly="yes"/>
+ <attribute name="generation" type="long" readonly="yes"/>
+ <attribute name="enabled" type="boolean" readonly="yes"/>
+ <attribute name="visible" type="boolean" readonly="yes"/>
+ <attribute name="label" type="wstring" readonly="yes"/>
+ <attribute name="description" type="wstring" readonly="yes"/>
+ <attribute name="help" type="wstring" readonly="yes"/>
+ </interface>
+
+ <interface name="IBooleanFormValue" extends="IFormValue"
+ uuid="4f4adcf6-3e87-11e9-8af2-576e84223953"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="4">
+ <method name="getSelected">
+ <param name="selected" type="boolean" dir="return"/>
+ </method>
+ <method name="setSelected">
+ <param name="selected" type="boolean" dir="in"/>
+ <param name="progress" type="IProgress" dir="return"/>
+ </method>
+ </interface>
+
+ <interface name="IRangedIntegerFormValue" extends="IFormValue"
+ uuid="b31c4052-7bdc-11e9-8bc2-8ffdb8b19219"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="4">
+ <attribute name="suffix" type="wstring" readonly="yes">
+ <desc>
+ Counterpart of the <link to="IFormValue::label"/> attribute.
+ May be null or empty. Usually used for units.
+ </desc>
+ </attribute>
+ <attribute name="minimum" type="long" readonly="yes"/>
+ <attribute name="maximum" type="long" readonly="yes"/>
+ <method name="getInteger">
+ <param name="value" type="long" dir="return"/>
+ </method>
+ <method name="setInteger">
+ <param name="value" type="long" dir="in"/>
+ <param name="progress" type="IProgress" dir="return"/>
+ </method>
+ </interface>
+
+ <interface name="IStringFormValue" extends="IFormValue"
+ uuid="cb6f0f2c-8384-11e9-921d-8b984e28a686"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="4">
+ <attribute name="multiline" type="boolean" readonly="yes"/>
+ <method name="getString">
+ <param name="text" type="wstring" dir="return"/>
+ </method>
+ <method name="setString">
+ <param name="text" type="wstring" dir="in"/>
+ <param name="progress" type="IProgress" dir="return"/>
+ </method>
+
+ <attribute name="clipboardString" type="wstring" readonly="yes">
+ <desc>
+ Intnded for cases when a read-only string value is used to
+ display information and different string is to be used when
+ copying to the clipboard.
+ </desc>
+ </attribute>
+ </interface>
+
+ <interface name="IChoiceFormValue" extends="IFormValue"
+ uuid="7191cf38-3e8a-11e9-825c-ab7b2cabce23"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="4">
+ <attribute name="values" type="wstring" safearray="yes" readonly="yes"/>
+ <method name="getSelectedIndex">
+ <param name="index" type="long" dir="return"/>
+ </method>
+ <method name="setSelectedIndex">
+ <param name="index" type="long" dir="in"/>
+ <param name="progress" type="IProgress" dir="return"/>
+ </method>
+ </interface>
+
+ <interface name="IForm" extends="$unknown"
+ uuid="d05c91e2-3e8a-11e9-8082-db8ae479ef87"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="4">
+ <attribute name="values" type="IFormValue" safearray="yes" readonly="yes"/>
+
+ <method name="getFieldGroup">
+ <param name="field" type="wstring" dir="in"/>
+ <param name="group" type="wstring" safearray="yes" dir="return"/>
+ </method>
+
+ <method name="apply">
+ <param name="progress" type="IProgress" dir="return"/>
+ </method>
+ </interface>
+
+ <interface name="IVirtualSystemDescriptionForm" extends="IForm"
+ uuid="14c2db8a-3ee4-11e9-b872-cb9447aad965"
+ wsmap="managed"
+ reservedMethods="4" reservedAttributes="4">
+ <method name="getVirtualSystemDescription">
+ <param name="description" type="IVirtualSystemDescription" dir="return"/>
+ </method>
+ </interface>
+
+ <enum
+ name="CloudMachineState"
+ uuid="67b6d054-0154-4f5d-b71b-6ac406e1ff78"
+ >
+ <desc>Cloud instance execution state</desc>
+ <const name="Invalid" value="0">
+ <desc>Invalid state</desc>
+ </const>
+ <const name="Provisioning" value="1">
+ <desc>The machine is in the process of provisioning</desc>
+ </const>
+ <const name="Running" value="2">
+ <desc>The machine runs</desc>
+ </const>
+ <const name="Starting" value="3">
+ <desc>The machine is in the process of starting</desc>
+ </const>
+ <const name="Stopping" value="4">
+ <desc>The machine is in the process of stopping</desc>
+ </const>
+ <const name="Stopped" value="5">
+ <desc>The machine was stopped</desc>
+ </const>
+ <const name="CreatingImage" value="6">
+ <desc>The machine is in the process of creating image</desc>
+ </const>
+ <const name="Terminating" value="7">
+ <desc>The machine is in the process of terminating</desc>
+ </const>
+ <const name="Terminated" value="8">
+ <desc>The machine was terminated</desc>
+ </const>
+</enum>
+
+ <enum
+ name="CloudImageState"
+ uuid="6e5d6762-eea2-4f2c-b104-2952d0aa8a0a"
+ >
+ <desc>Cloud image state</desc>
+ <const name="Invalid" value="0">
+ <desc>Invalid state</desc>
+ </const>
+ <const name="Provisioning" value="1">
+ <desc>The image is in the process of provisioning</desc>
+ </const>
+ <const name="Importing" value="2">
+ <desc>The image is in the process of importing</desc>
+ </const>
+ <const name="Available" value="3">
+ <desc>The image is avalable</desc>
+ </const>
+ <const name="Exporting" value="4">
+ <desc>The image is in the process of exporting</desc>
+ </const>
+ <const name="Disabled" value="5">
+ <desc>The image is disabled</desc>
+ </const>
+ <const name="Deleted" value="6">
+ <desc>The image was deleted</desc>
+ </const>
+</enum>
+
+ <interface name="ICloudNetworkGatewayInfo" extends="$unknown"
+ uuid="89a63ace-0c65-11ea-ad23-0ff257c71a7f"
+ wsmap="managed"
+ reservedAttributes="5">
+ <attribute name="publicIP" type="wstring" readonly="yes"/>
+ <attribute name="secondaryPublicIP" type="wstring" readonly="yes"/>
+ <attribute name="macAddress" type="wstring" readonly="yes"/>
+ <attribute name="instanceId" type="wstring" readonly="yes"/>
+ </interface>
+
+
+ <interface name="ICloudNetworkEnvironmentInfo" extends="$unknown"
+ uuid="181dfb55-394d-44d3-9edb-af2c4472c40a"
+ wsmap="managed"
+ reservedAttributes="7">
+ <attribute name="tunnelNetworkId" type="wstring" readonly="yes"/>
+ </interface>
+
+
+ <interface name="ICloudMachine" extends="$unknown"
+ uuid="147816c8-17e0-11eb-81fa-87cea6263e1a"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="16" reservedAttributes="8"
+ >
+ <desc>
+ Virtual virtual machine (sic) in the cloud.
+
+ Reading object attributes returns cached values, use
+ <link to="#refresh"/> to refresh them.
+ </desc>
+
+ <attribute name="id" type="uuid" mod="string" readonly="yes">
+ <desc>UUID of the cloud machine.</desc>
+ </attribute>
+
+ <attribute name="accessible" type="boolean" readonly="yes"
+ wrap-hint-server="limitedcaller">
+ <desc>
+ Whether this virtual machine is currently accessible or not.
+ TBD...
+ </desc>
+ </attribute>
+
+ <attribute name="accessError" type="IVirtualBoxErrorInfo" readonly="yes"
+ wrap-hint-server="limitedcaller">
+ <desc>
+ Error information describing the reason of machine
+ inaccessibility.
+
+ Reading this property is only valid after the last call to
+ <link to="#accessible"/> returned @c false (i.e. the
+ machine is currently inaccessible). Otherwise, a @c null
+ IVirtualBoxErrorInfo object will be returned.
+ </desc>
+ </attribute>
+
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>
+ Convenience shortcut to retrieve the name of the cloud
+ machine. The name is part of the machine settings that are
+ hidden behind the settings form (see <link to="#getSettingsForm"/>).
+ </desc>
+ </attribute>
+
+ <attribute name="OSTypeId" type="wstring" readonly="yes">
+ <desc>
+ Convenience shortcut to retrieve the OS Type id of the cloud
+ machine. It is part of the machine settings that are hidden
+ behind the settings form (see <link to="#getSettingsForm"/>).
+ </desc>
+ </attribute>
+
+ <attribute name="state" type="CloudMachineState" readonly="yes">
+ <desc>
+ Machine state.
+ </desc>
+ </attribute>
+
+ <method name="refresh">
+ <desc>
+ Refresh information by reading it from the cloud.
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getDetailsForm" const="yes">
+ <desc>
+ Obtain a form with the current settings for this cloud
+ machine. The form is not editable.
+ </desc>
+ <param name="form" type="IForm" dir="return">
+ <desc>
+ A form with the cloud machine settings.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getSettingsForm">
+ <desc>
+ Obtain a form with settings for this cloud machine.
+ The form is editable.
+ </desc>
+ <param name="form" type="IForm" dir="out">
+ <desc>
+ A form with the cloud machine settings.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="powerUp">
+ <desc>
+ Start cloud virtual machine execution.
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="reboot">
+ <desc>
+ Reboot cloud virtual machine.
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="shutdown">
+ <desc>
+ Shutdown cloud virtual machine.
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="powerDown">
+ <desc>
+ Initiates the power down procedure to stop the virtual machine
+ execution.
+
+ The completion of the power down procedure is tracked using the returned
+ IProgress object. After the operation is complete, the machine will go
+ to the PoweredOff state.
+ <result name="VBOX_E_INVALID_VM_STATE">
+ Virtual machine must be Running, to be powered down.
+ </result>
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="terminate">
+ <desc>
+ Terminate cloud virtual machine.
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="unregister" wrap-hint-server="limitedcaller,passcaller">
+ <desc>
+ Unregister this cloud machine, but leave the cloud artifacts
+ intact.
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="remove" wrap-hint-server="limitedcaller,passcaller">
+ <desc>
+ Unregister this cloud machine and delete all its cloud artifacts.
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getConsoleHistory">
+ <desc>
+ Get the backlog of the machine's console. If you have ever
+ seen console teletypes, this is like looking at the last few
+ meters of the paper it spewed.
+ </desc>
+ <param name="stream" type="IDataStream" dir="out">
+ <desc>
+ Data stream object for reading the console history. For now
+ we are abusing/repurposing this interface from the media
+ convertion API to avoid marshalling a huge string through
+ xpcom.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return"><desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="createConsoleConnection">
+ <param name="sshPublicKey" type="wstring" dir="in">
+ <desc>
+ SSH public key authorized to connect to the console.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="deleteConsoleConnection">
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <attribute name="consoleConnectionFingerprint" type="wstring" readonly="yes">
+ <desc>
+ The fingerprint of the ssh key that is authorized to access
+ the machine's console connection.
+ </desc>
+ </attribute>
+
+ <attribute name="serialConsoleCommand" type="wstring" readonly="yes">
+ <desc>
+ The shell command to establish ssh connection to the cloud
+ machine serial console.
+ </desc>
+ </attribute>
+
+ <attribute name="serialConsoleCommandWindows" type="wstring" readonly="yes">
+ <desc>
+ The PowerShell command to establish ssh connection to the
+ cloud machine serial console using PuTTY's plink.
+ </desc>
+ </attribute>
+
+ <attribute name="VNCConsoleCommand" type="wstring" readonly="yes">
+ <desc>
+ The shell command to establish ssh port forwarding for the
+ VNC connection to the cloud machine console.
+ </desc>
+ </attribute>
+
+ <attribute name="VNCConsoleCommandWindows" type="wstring" readonly="yes">
+ <desc>
+ The PowerShell command to establish ssh port forwarding for the
+ VNC connection to the cloud machine console using PuTTY's plink.
+ </desc>
+ </attribute>
+
+ </interface>
+
+ <!--
+ // ICloudClient
+ //////////////////////////////////////////////////////////////////////////
+ -->
+ <interface
+ name="ICloudClient" extends="$unknown"
+ uuid="c2db178a-7485-11ec-aec4-2fbf90681a84"
+ wsmap="managed"
+ reservedMethods="16" reservedAttributes="8"
+ >
+
+ <method name="getExportDescriptionForm">
+ <desc>
+ Returns a form for editing the virtual system description for
+ exporting a local VM into a cloud custom image.
+ </desc>
+ <param name="description" type="IVirtualSystemDescription" dir="in">
+ <desc>
+ Virtual system description to be edited.
+ </desc>
+ </param>
+ <param name="form" type="IVirtualSystemDescriptionForm" dir="out">
+ <desc>
+ An IForm instance for editing the virtual system description.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="exportVM">
+ <desc>
+ Export local VM into the cloud, creating a custom image.
+ </desc>
+ <param name="description" type="IVirtualSystemDescription" dir="in">
+ <desc>
+ Virtual system description object which describes the
+ machine and all required parameters.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="in">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getLaunchDescriptionForm">
+ <param name="description" type="IVirtualSystemDescription" dir="in">
+ <desc>
+ Virtual system description to be edited.
+ </desc>
+ </param>
+ <param name="form" type="IVirtualSystemDescriptionForm" dir="out">
+ <desc>
+ An IForm instance for editing the virtual system description.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="launchVM">
+ <param name="description" type="IVirtualSystemDescription" dir="in">
+ <desc>
+ Virtual system description object which describes the
+ machine and all required parameters.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getImportDescriptionForm">
+ <desc>
+ Returns a form for editing the virtual system description for
+ import from cloud.
+ </desc>
+ <param name="description" type="IVirtualSystemDescription" dir="in">
+ <desc>
+ Virtual system description to be edited.
+ </desc>
+ </param>
+ <param name="form" type="IVirtualSystemDescriptionForm" dir="out">
+ <desc>
+ An IForm instance for editing the virtual system description.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="importInstance">
+ <desc>
+ Import an existing cloud instance to the local host.
+ All needed parameters are passed in the description (VSD).
+ </desc>
+ <param name="description" type="IVirtualSystemDescription" dir="in">
+ <desc>VirtualSystemDescription object which is describing a machine and all required parameters.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="in">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="getCloudMachine">
+ <desc>
+ Create an object that represents a cloud machine with the
+ specified UUID. Note that the operation is synchronous. The
+ returned machine is initiatally in inaccessible state and
+ requires a refresh to get its data from the cloud.
+ </desc>
+ <param name="id" type="uuid" mod="string" dir="in">
+ <desc>
+ UUID of a cloud machine.
+ </desc>
+ </param>
+ <param name="machine" type="ICloudMachine" dir="return">
+ <desc>
+ Object that represents the cloud machine with the specified UUID.
+ </desc>
+ </param>
+ </method>
+
+ <method name="readCloudMachineList">
+ <desc>
+ Make the list of cloud machines available via
+ <link to="#cloudMachineList"/> attribute.
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+ <attribute name="cloudMachineList" type="ICloudMachine" safearray="yes" readonly="yes">
+ <desc>
+ See <link to="#readCloudMachineList"/>.
+ </desc>
+ </attribute>
+
+ <method name="readCloudMachineStubList">
+ <desc>
+ Make the list of cloud machine stubs available via
+ <link to="#cloudMachineStubList"/> attribute.
+ Like with <link to="#getCloudMachine"/>, the returned machines
+ are initiatally inaccessible and require a refresh to get
+ their data from the cloud.
+ </desc>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+ <attribute name="cloudMachineStubList" type="ICloudMachine" safearray="yes" readonly="yes">
+ <desc>
+ See <link to="#readCloudMachineStubList"/>.
+ </desc>
+ </attribute>
+
+ <method name="addCloudMachine">
+ <desc>
+ Adopt a running instance and register it as cloud machine.
+ This is kinda like adding a local .vbox file as a local VM.
+ </desc>
+ <param name="instanceId" type="wstring" dir="in">
+ <desc>
+ ID of an instance to be added as a cloud machine.
+ </desc>
+ </param>
+ <param name="machine" type="ICloudMachine" dir="out">
+ <desc>
+ Object that represents the newly registered cloud machine.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="createCloudMachine">
+ <desc>
+ This is transitional method that combines
+ <link to="#launchVM"/> and <link to="#addCloudMachine"/>.
+ </desc>
+ <param name="description" type="IVirtualSystemDescription" dir="in">
+ <desc>
+ Virtual system description object which describes the
+ machine and all required parameters.
+ </desc>
+ </param>
+ <param name="machine" type="ICloudMachine" dir="out">
+ <desc>
+ Object that represents the newly created cloud machine.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="listInstances" const="yes">
+ <desc>
+ Returns the list of the instances in the Cloud.
+ </desc>
+ <param name="machineState" type="CloudMachineState" dir="in" safearray="yes">
+ <desc>State of each VM.</desc>
+ </param>
+ <param name="returnNames" type="IStringArray" dir="out">
+ <desc>VM names.</desc>
+ </param>
+ <param name="returnIds" type="IStringArray" dir="out">
+ <desc>VM ids.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="listSourceInstances" const="yes">
+ <desc>
+ Returns the list of instances in the cloud that can be
+ added/adopted as VirtualBox cloud machines.
+ </desc>
+ <param name="returnNames" type="IStringArray" dir="out">
+ <desc>Instance names.</desc>
+ </param>
+ <param name="returnIds" type="IStringArray" dir="out">
+ <desc>Instance idss.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="listImages" const="yes">
+ <desc>
+ Returns the list of the images in the Cloud.
+ </desc>
+ <param name="imageState" type="CloudImageState" dir="in" safearray="yes">
+ <desc>State of each image.</desc>
+ </param>
+ <param name="returnNames" type="IStringArray" dir="out">
+ <desc>Images names.</desc>
+ </param>
+ <param name="returnIds" type="IStringArray" dir="out">
+ <desc>Images ids.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <!-- quick hack -->
+ <method name="listBootVolumes" const="yes">
+ <desc>
+ Returns the list of boot volumes in the Cloud.
+ </desc>
+ <param name="returnNames" type="IStringArray" dir="out">
+ <desc>Boot volume names.</desc>
+ </param>
+ <param name="returnIds" type="IStringArray" dir="out">
+ <desc>Boot volume ids.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="listSourceBootVolumes" const="yes">
+ <desc>
+ Returns the list of boot volumes in the cloud that can be
+ added/adopted as VirtualBox cloud machines.
+ </desc>
+ <param name="returnNames" type="IStringArray" dir="out">
+ <desc>Boot volume names.</desc>
+ </param>
+ <param name="returnIds" type="IStringArray" dir="out">
+ <desc>Boot volume ids.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="listVnicAttachments" const="yes">
+ <desc>
+ Returns the list of the Vnic attachements in the Cloud.
+ </desc>
+ <param name="parameters" type="wstring" dir="in" safearray="yes">
+ <desc>Each parameter in the array must be in the form "name=value".</desc>
+ </param>
+ <param name="returnVnicAttachmentIds" type="IStringArray" dir="out">
+ <desc>VM ids.</desc>
+ </param>
+ <param name="returnVnicIds" type="IStringArray" dir="out">
+ <desc>VM ids.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getInstanceInfo" const="yes">
+ <desc>
+ Returns the information about an instance in the Cloud.
+ </desc>
+ <param name="uid" type="wstring" dir="in">
+ <desc>The id of instance in the Cloud.</desc>
+ </param>
+ <param name="description" type="IVirtualSystemDescription" dir="in">
+ <desc>VirtualSystemDescription object which is describing a machine</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="startInstance">
+ <desc>
+ Start an existing instance with passed id.
+ </desc>
+ <param name="uid" type="wstring" dir="in">
+ <desc>The id of instance in the Cloud.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="pauseInstance">
+ <desc>
+ Pause an existing instance with passed id.
+ </desc>
+ <param name="uid" type="wstring" dir="in">
+ <desc>The id of instance in the Cloud.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="terminateInstance">
+ <desc>
+ Terminate an existing instance with passed id.
+ </desc>
+ <param name="uid" type="wstring" dir="in">
+ <desc>the id of instance in the Cloud.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="createImage">
+ <desc>
+ Create an image in the Cloud.
+ </desc>
+ <param name="parameters" type="wstring" dir="in" safearray="yes">
+ <desc>Each parameter in the array must be in the form "name=value".</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="exportImage">
+ <desc>
+ Export an existing VBox image in the Cloud.
+ </desc>
+ <param name="image" type="IMedium" dir="in">
+ <desc>Reference to the existing VBox image.</desc>
+ </param>
+ <param name="parameters" type="wstring" dir="in" safearray="yes">
+ <desc>Each parameter in the array must be in the form "name=value".</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="importImage">
+ <desc>
+ Import an existing image in the Cloud to the local host.
+ </desc>
+ <param name="uid" type="wstring" dir="in">
+ <desc>the id of image in the Cloud.</desc>
+ </param>
+ <param name="parameters" type="wstring" dir="in" safearray="yes">
+ <desc>Each parameter in the array must be in the form "name=value".</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="deleteImage">
+ <desc>
+ Delete an existing image with passed id from the Cloud.
+ </desc>
+ <param name="uid" type="wstring" dir="in">
+ <desc>The id of image in the Cloud.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="getImageInfo" const="yes">
+ <desc>
+ Returns the information about an image in the Cloud.
+ </desc>
+ <param name="uid" type="wstring" dir="in">
+ <desc>The id of image in the Cloud.</desc>
+ </param>
+ <param name="infoArray" type="IStringArray" dir="out">
+ <desc>
+ An array where the image settings or properties is returned.
+ Each parameter in the array must be in the form "name=value".
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="startCloudNetworkGateway">
+ <param name="network" type="ICloudNetwork" dir="in">
+ <desc>The id of image in the Cloud.</desc>
+ </param>
+ <param name="sshPublicKey" type="wstring" dir="in">
+ <desc>The id of image in the Cloud.</desc>
+ </param>
+ <param name="gatewayInfo" type="ICloudNetworkGatewayInfo" dir="out">
+ <desc>Information about the started gateway.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="setupCloudNetworkEnvironment">
+ <param name="tunnelNetworkName" type="wstring" dir="in">
+ <desc>The name of tunnelling network to be created in the Cloud. If this parameter
+ is empty the default value "VirtualBox Tunneling Network" is assumed.</desc>
+ </param>
+ <param name="tunnelNetworkRange" type="wstring" dir="in">
+ <desc>The IP address range of tunnelling network to be created in the Cloud. If this
+ parameter is empty the default value "10.0.0.0/16" is assumed.</desc>
+ </param>
+ <param name="gatewayOsName" type="wstring" dir="in">
+ <desc>The name of the operating system to be used for cloud gateway instances.
+ The default value is "Oracle Linux".</desc>
+ </param>
+ <param name="gatewayOsVersion" type="wstring" dir="in">
+ <desc>The version of the operating system to be used for cloud gateway instances.
+ The default value is "7.8".</desc>
+ </param>
+ <param name="gatewayShape" type="wstring" dir="in">
+ <desc>The shape of cloud gateway instance. The default value is "VM.Standard2.1".</desc>
+ </param>
+ <param name="networkEnvironmentInfo" type="ICloudNetworkEnvironmentInfo" dir="out">
+ <desc>Information about the created network environment.</desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>Progress object to track the operation completion.</desc>
+ </param>
+ </method>
+
+ <method name="getVnicInfo" const="yes">
+ <desc>
+ Returns the information about Vnic in the Cloud.
+ </desc>
+ <param name="uid" type="wstring" dir="in">
+ <desc>The id of vnic in the Cloud.</desc>
+ </param>
+ <param name="infoArray" type="IStringArray" dir="out">
+ <desc>
+ An array where the Vnic settings/properties is returned.
+ Each parameter in the array must be in the form "name=value".
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+ <method name="getSubnetSelectionForm">
+ <param name="description" type="IVirtualSystemDescription" dir="in">
+ <desc>
+ Virtual system description to be edited.
+ </desc>
+ </param>
+ <param name="form" type="IVirtualSystemDescriptionForm" dir="out">
+ <desc>
+ An IForm instance for editing the virtual system description.
+ </desc>
+ </param>
+ <param name="progress" type="IProgress" dir="return">
+ <desc>
+ Progress object to track the operation completion.
+ </desc>
+ </param>
+ </method>
+
+
+ </interface>
+
+ <!--
+ // ICloudProfile
+ //////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="ICloudProfile" extends="$unknown"
+ uuid="b1d978b8-f7b7-4b05-900e-2a9253c00f51"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+
+ <attribute name="name" type="wstring">
+ <desc>
+ Returns the profile name.
+ </desc>
+ </attribute>
+
+ <attribute name="providerId" type="uuid" mod="string" readonly="yes">
+ <desc>
+ Returns provider identifier tied with this profile.
+ </desc>
+ </attribute>
+
+ <method name="getProperty" const="yes">
+ <desc>
+ Returns the value of the cloud profile property with the given name.
+
+ If the requested data @a name does not exist, this function will
+ succeed and return an empty string in the @a value argument.
+
+ <result name="E_INVALIDARG">@a name is @c null or empty.</result>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the property to get.</desc>
+ </param>
+ <param name="value" type="wstring" dir="return">
+ <desc>Current property value.</desc>
+ </param>
+ </method>
+
+ <method name="setProperty" const="yes">
+ <desc>
+ Sets the value of the cloud profile property with the given name.
+
+ Setting the property value to @c null or an empty string is equivalent
+ to deleting the existing value.
+
+ <result name="E_INVALIDARG">@a name is @c null or empty.</result>
+ </desc>
+ <param name="name" type="wstring" dir="in">
+ <desc>Name of the property to set.</desc>
+ </param>
+ <param name="value" type="wstring" dir="in">
+ <desc>Property value to set.</desc>
+ </param>
+ </method>
+
+ <method name="getProperties" const="yes">
+ <desc>
+ Returns values for a group of properties in one call.
+
+ The names of the properties to get are specified using the @a names
+ argument which is a list of comma-separated property names or
+ an empty string if all properties are to be returned.
+ <note>Currently the value of this argument is ignored and the method
+ always returns all existing properties.</note>
+
+ The method returns two arrays, the array of property names corresponding
+ to the @a names argument and the current values of these properties.
+ Both arrays have the same number of elements with each element at the
+ given index in the first array corresponds to an element at the same
+ index in the second array.
+ </desc>
+ <param name="names" type="wstring" dir="in">
+ <desc>
+ Names of properties to get.
+ </desc>
+ </param>
+ <param name="returnNames" type="wstring" safearray="yes" dir="out">
+ <desc>Names of returned properties.</desc>
+ </param>
+ <param name="returnValues" type="wstring" safearray="yes" dir="return">
+ <desc>Values of returned properties.</desc>
+ </param>
+ </method>
+
+ <method name="setProperties">
+ <desc>
+ Updates profile, changing/adding/removing properties.
+
+ The names of the properties to set are passed in the @a names
+ array along with the new values for them in the @a values array. Both
+ arrays have the same number of elements with each element at the given
+ index in the first array corresponding to an element at the same index
+ in the second array.
+
+ If there is at least one property name in @a names that is not valid,
+ the method will fail before changing the values of any other properties
+ from the @a names array.
+
+ Using this method over <link to="#setProperty"/> is preferred if you
+ need to set several properties at once since it is more efficient.
+
+ Setting the property value to @c null or an empty string is equivalent
+ to deleting the existing value.
+ </desc>
+ <param name="names" type="wstring" safearray="yes" dir="in">
+ <desc>Names of properties.</desc>
+ </param>
+ <param name="values" type="wstring" safearray="yes" dir="in">
+ <desc>Values of set properties.</desc>
+ </param>
+ </method>
+
+ <method name="remove">
+ <desc>
+ Deletes a profile.
+ </desc>
+ </method>
+
+ <method name="createCloudClient">
+ <desc>
+ Creates a cloud client for this cloud profile.
+ </desc>
+ <param name="cloudClient" type="ICloudClient" dir="return">
+ <desc>
+ The cloud client object reference.
+ </desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <!--
+ // ICloudProvider
+ //////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="ICloudProvider" extends="$unknown"
+ uuid="22363cfc-07da-41ec-ac4a-3dd99db35594"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="8" reservedAttributes="16"
+ >
+
+ <attribute name="name" type="wstring" readonly="yes">
+ <desc>Returns the long name of the provider. Includes vendor and precise
+ product name spelled out in the preferred way.</desc>
+ </attribute>
+
+ <attribute name="shortName" type="wstring" readonly="yes">
+ <desc>Returns the short name of the provider. Less than 8 ASCII chars,
+ using acronyms. No vendor name, but can contain a hint if it's a 3rd
+ party implementation for this cloud provider, to keep it unique.</desc>
+ </attribute>
+
+ <attribute name="id" type="uuid" mod="string" readonly="yes">
+ <desc>Returns the UUID of this cloud provider.</desc>
+ </attribute>
+
+ <!-- if there are any generally useful, static pieces of information about
+ a particular cloud provider, please add them here. Maybe a description
+ of the functionality of the cloud provider implementation?-->
+
+ <attribute name="profiles" type="ICloudProfile" safearray="yes" readonly="yes">
+ <desc>Returns all profiles for this cloud provider.</desc>
+ </attribute>
+
+ <attribute name="profileNames" type="wstring" safearray="yes" readonly="yes">
+ <desc>Returns all profile names for this cloud provider.</desc>
+ </attribute>
+
+ <attribute name="supportedPropertyNames" type="wstring" safearray="yes" readonly="yes">
+ <desc>
+ Returns the supported property names.
+ </desc>
+ </attribute>
+
+ <method name="getPropertyDescription" const="yes">
+ <param name="name" type="wstring" dir="in">
+ <desc>Property name.</desc>
+ </param>
+ <param name="description" type="wstring" dir="return">
+ <desc>Property description.</desc>
+ </param>
+ </method>
+
+ <method name="createProfile">
+ <desc>
+ Creates a new profile.
+ </desc>
+ <param name="profileName" type="wstring" dir="in">
+ <desc>
+ The profile name. Must not exist, otherwise an error is raised.
+ </desc>
+ </param>
+ <param name="names" type="wstring" safearray="yes" dir="in">
+ <desc>Names of properties.</desc>
+ </param>
+ <param name="values" type="wstring" safearray="yes" dir="in">
+ <desc>Values of set properties.</desc>
+ </param>
+ </method>
+
+ <method name="importProfiles">
+ <desc>
+ Import the profiles from the original source
+ </desc>
+ </method>
+
+ <method name="restoreProfiles">
+ <desc>
+ Restores the old local profiles if they exist
+ </desc>
+ </method>
+
+ <method name="saveProfiles">
+ <desc>
+ Saves the local profiles
+ </desc>
+ </method>
+
+ <method name="getProfileByName">
+ <param name="profileName" type="wstring" dir="in"/>
+ <param name="profile" type="ICloudProfile" dir="return"/>
+ </method>
+
+ <method name="prepareUninstall" wrap-hint-server="passcaller">
+ <desc>
+ The caller requests the cloud provider to cease operation. Should
+ return an error if this is currently not possible (due to ongoing
+ cloud activity, possibly by a different API client). However, this
+ must not wait for the completion for a larger amount of time (ideally
+ stays below a second of execution time). If this succeeds it should
+ leave the cloud provider in a state which does not allow starting new
+ operations.
+ </desc>
+ </method>
+
+ </interface>
+
+ <!--
+ // ICloudProviderManager
+ //////////////////////////////////////////////////////////////////////////
+ -->
+ <interface
+ name="ICloudProviderManager" extends="$unknown"
+ uuid="9128800f-762e-4120-871c-a2014234a607"
+ wsmap="managed"
+ rest="managed"
+ reservedMethods="4" reservedAttributes="8"
+ >
+
+ <attribute name="providers" type="ICloudProvider" safearray="yes" readonly="yes">
+ <desc>Returns all supported cloud providers.</desc>
+ </attribute>
+
+ <method name="getProviderById">
+ <param name="providerId" type="uuid" mod="string" dir="in"/>
+ <param name="provider" type="ICloudProvider" dir="return"/>
+ </method>
+
+ <method name="getProviderByShortName">
+ <param name="providerName" type="wstring" dir="in"/>
+ <param name="provider" type="ICloudProvider" dir="return"/>
+ </method>
+
+ <method name="getProviderByName">
+ <param name="providerName" type="wstring" dir="in"/>
+ <param name="provider" type="ICloudProvider" dir="return"/>
+ </method>
+
+ </interface>
+
+ <interface name="ICloudProviderListChangedEvent" extends="IEvent"
+ uuid="a54d9cca-f23f-11ea-9755-efd0f1f792d9"
+ wsmap="managed" autogen="VBoxEvent"
+ id="OnCloudProviderListChanged"
+ >
+ <desc>
+ Each individual provider that is installed or uninstalled is
+ reported as an <link to="ICloudProviderRegisteredEvent"/>. When
+ the batch is done this event is sent and listerns can get the
+ new list.
+ </desc>
+ <attribute name="registered" type="boolean" readonly="yes"/>
+ </interface>
+
+ <interface name="ICloudProviderRegisteredEvent" extends="IEvent"
+ uuid="e28e227a-f231-11ea-9641-9b500c6d5365"
+ wsmap="managed" autogen="VBoxEvent"
+ id="OnCloudProviderRegistered"
+ >
+ <desc>
+ A cloud provider was installed or uninstalled.
+ See also <link to="ICloudProviderListChangedEvent"/>.
+ </desc>
+ <attribute name="id" type="uuid" mod="string" readonly="yes"/>
+ <attribute name="registered" type="boolean" readonly="yes"/>
+ </interface>
+
+ <interface name="ICloudProviderUninstallEvent" extends="IEvent"
+ uuid="f01f1066-f231-11ea-8eee-33bb2afb0b6e"
+ wsmap="managed" autogen="VBoxEvent"
+ id="OnCloudProviderCanUninstall" waitable="yes"
+ >
+ <desc>
+ A cloud provider is about to be uninstalled.
+ Unlike <link to="ICloudProviderRegisteredEvent"/> this one is
+ waitable and is sent <i>before</i> an attempt is made to
+ uninstall a provider. Listerns should release references to the
+ provider and related objects if they have any, or the
+ uninstallation of the provider is going to fail because it's
+ still in use.
+ </desc>
+ <attribute name="id" type="uuid" mod="string" readonly="yes"/>
+ </interface>
+
+ <!-- not autogenerated, @id is just for reference -->
+ <interface name="ICloudProfileRegisteredEvent" extends="IEvent"
+ uuid="6a5e65ba-eeb9-11ea-ae38-73242bc0f172"
+ wsmap="managed"
+ id="OnCloudProfileRegistered"
+ >
+ <attribute name="providerId" type="uuid" mod="string" readonly="yes"/>
+ <attribute name="name" type="wstring" readonly="yes"/>
+ <attribute name="registered" type="boolean" readonly="yes"/>
+ </interface>
+
+ <!-- not autogenerated, @id is just for reference -->
+ <interface name="ICloudProfileChangedEvent" extends="IEvent"
+ uuid="83795a4c-fce1-11ea-8a17-636028ae0be2"
+ wsmap="managed"
+ id="OnCloudProfileChanged"
+ >
+ <attribute name="providerId" type="uuid" mod="string" readonly="yes"/>
+ <attribute name="name" type="wstring" readonly="yes"/>
+ </interface>
+
+ <interface name="ILanguageChangedEvent" extends="IEvent"
+ uuid="28935887-782b-4c94-8410-ce557b9cfe44"
+ wsmap="managed" autogen="VBoxEvent"
+ id="OnLanguageChanged"
+ >
+ <attribute name="languageId" type="wstring" readonly="yes"/>
+ </interface>
+
+ <module name="VBoxSVC" context="LocalServer">
+ <class name="VirtualBox" uuid="B1A7A4F2-47B9-4A1E-82B2-07CCD5323C3F"
+ namespace="virtualbox.org">
+ <interface name="IVirtualBox" default="yes"/>
+ </class>
+ </module>
+
+ <module name="VBoxC" context="InprocServer" threadingModel="Free">
+ <class name="VirtualBoxClient" uuid="dd3fc71d-26c0-4fe1-bf6f-67f633265bba"
+ namespace="virtualbox.org">
+ <interface name="IVirtualBoxClient" default="yes"/>
+ </class>
+
+ <class name="Session" uuid="3C02F46D-C9D2-4F11-A384-53F0CF917214"
+ namespace="virtualbox.org">
+ <interface name="ISession" default="yes"/>
+ </class>
+ </module>
+
+</application>
+
+<if target="midl">
+<!-- Pay attention! Application uuid of this application is used in midl.xsl
+ So if you change it then change filter in "interface" and "class" template in midl.xsl too
+-->
+<application
+ name="VirtualBox System Service"
+ uuid="ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa"
+ supportsErrorInfo="yes"
+>
+
+ <!--
+ // IVBoxSVC
+ // Note! This actually extends IUnknown rather than IDispatch, notdual=yes
+ // sees to that.
+ //////////////////////////////////////////////////////////////////////////
+ -->
+ <interface
+ name="IVBoxSVCRegistration" extends="$unknown" notdual="yes"
+ uuid="9e106366-4521-44cc-df95-186e4d057c83"
+ wsmap="suppress" internal="yes"
+ reservedMethods="0" reservedAttributes="0"
+ >
+ <desc>
+ Implemented by the VirtualBox class factory and registered with VBoxSDS
+ so it can retrieve IVirtualBox on behalf of other VBoxSVCs.
+ </desc>
+
+ <method name="getVirtualBox" >
+ <desc> Gets an IUnknown interface to the VirtualBox object in the VBoxSVC process. </desc>
+ <param name="result" type="$unknown" dir="return" >
+ <desc> Where to return the IUnknown interface. </desc>
+ </param>
+ </method>
+
+ </interface>
+
+ <!--
+ // IVirtualBoxSDS
+ // Note! This actually extends IUnknown rather than IDispatch, notdual=yes
+ // sees to that.
+ //////////////////////////////////////////////////////////////////////////
+ -->
+
+ <interface
+ name="IVirtualBoxSDS" extends="$unknown" notdual="yes"
+ uuid="890ed3dc-cc19-43fa-8ebf-baecb6b9ec87"
+ wsmap="suppress" internal="yes"
+ reservedMethods="0" reservedAttributes="0"
+ >
+ <desc>
+ The IVirtualBoxSDS interface represents the system-wide directory service
+ helper.
+
+ It exists only on Windows host, and its purpose is to work around design
+ flaws in Microsoft's (D)COM, in particular the local server instantiation
+ behavior.
+ </desc>
+
+ <method name="registerVBoxSVC">
+ <desc>
+ Registers a VBoxSVC instance with VBoxSDS. If the caller is not running
+ in a Windows 0 session, the method attempts to run VBoxSVC in that
+ session.
+ <result name="E_PENDING">
+ The caller is not running in a Windows session 0 and no VBoxSVC
+ is registered. VBoxSVC registration begins in Windows session 0.
+ You should call this method again later.
+ </result>
+ </desc>
+ <param name="vboxSVC" type="IVBoxSVCRegistration" dir="in">
+ <desc>Interface implemented by the VirtualBox class factory.</desc>
+ </param>
+ <param name="pid" type="long" dir="in">
+ <desc>The process ID of the VBoxSVC instance.</desc>
+ </param>
+ <param name="existingVirtualBox" type="$unknown" dir="return">
+ <desc>If there is already an VBoxSVC for this user, the an IUnknown
+ interface to its VirtualBox object is returned here, otherwise it
+ is set to NULL.</desc>
+ </param>
+ </method>
+
+ <method name="deregisterVBoxSVC">
+ <desc>Registers a VBoxSVC instance with the SDS.</desc>
+ <param name="vboxSVC" type="IVBoxSVCRegistration" dir="in">
+ <desc>Same as specified during registration.</desc>
+ </param>
+ <param name="pid" type="long" dir="in">
+ <desc>The process ID of the VBoxSVC instance (same as during registration).</desc>
+ </param>
+ </method>
+
+ <method name="launchVMProcess">
+ <desc>
+ Spawns a new process that will execute the virtual machine in the interactive
+ windows session of calling user. Any additional checks are performed by created
+ process itself
+
+ If launching the VM succeeds, the new VM process will create its own session
+ and write-lock the machine for it, preventing conflicting changes from other
+ processes. If the machine is already locked (because it is already running or
+ because another session has a write lock), launching the VM process will therefore
+ fail. Reversely, future attempts to obtain a write lock will also fail while the
+ machine is running.
+
+ Launching a VM process can take some time (a new VM is started in a new process,
+ for which memory and other resources need to be set up) but the method does
+ not wait for completion and just returns the PID of created process.
+
+ <result name="E_INVALIDARG">
+ Some of the parameters are invalid.
+ </result>
+ <result name="VBOX_E_IPRT_ERROR">
+ Launching process for machine failed.
+ </result>
+ </desc>
+ <param name="machine" type="wstring" dir="in">
+ <desc>
+ The name or id of the machine the VM will start for.
+ </desc>
+ </param>
+ <param name="comment" type="wstring" dir="in">
+ <desc>
+ The comment for VM.
+ </desc>
+ </param>
+ <param name="frontend" type="wstring" dir="in">
+ <desc>
+ Front-end to use for the new VM process. The following are currently supported:
+ <ul>
+ <li><tt>"gui"</tt>: VirtualBox Qt GUI front-end</li>
+ <li><tt>"headless"</tt>: VBoxHeadless (VRDE Server) front-end</li>
+ <li><tt>"sdl"</tt>: VirtualBox SDL front-end</li>
+ <li><tt>""</tt>: use the per-VM default frontend if set, otherwise
+ the global default defined in the system properties. If neither
+ are set, the API will launch a <tt>"gui"</tt> session, which may
+ fail if there is no windowing environment available. See
+ <link to="IMachine::defaultFrontend"/> and
+ <link to="ISystemProperties::defaultFrontend"/>.</li>
+ </ul>
+ </desc>
+ </param>
+ <param name="environmentChanges" type="wstring" safearray="yes" dir="in">
+ <desc>
+ The list of putenv-style changes to the VM process environment.
+ See <link to="IMachine::launchVMProcess" /> for details.
+ </desc>
+ </param>
+ <param name="cmdOptions" type="wstring" dir="in">
+ <desc>
+ Additional command line options to pass to the VM process.
+ </desc>
+ </param>
+ <param name="sessionId" type="unsigned long" dir="in">
+ <desc>
+ Windows session where the VM process should be launched.
+ </desc>
+ </param>
+ <param name="pid" type="unsigned long" dir="return">
+ <desc>The PID of created process.</desc>
+ </param>
+ </method>
+
+ </interface>
+
+<!-- Warning: the name of module should coincide with real windows service name.
+ VirtualBox_Typelib.xsl and VirtualBox_TypeLibWithInterfaces.xsl uses this name to make list of COM registration
+ actions in Wix. Installer will not able to register COM windows service properly if the name of module
+ differs from SCM windows service name.
+ The Name of windows service is referenced in different places.
+ Don't forget to change it in other places too If you change it here :
+ VBoxMergeApp.wxi (cp_VBoxSDS component),
+ VBoxSDS.cpp and VBoxProxyStub.cpp
+ -->
+ <module name="VBoxSDS" context="LocalService">
+ <class name="VirtualBoxSDS" uuid="74ab5ffe-8726-4435-aa7e-876d705bcba5"
+ namespace="virtualbox.org">
+ <interface name="IVirtualBoxSDS" default="yes"/>
+ </class>
+ </module>
+</application>
+</if>
+
+</library>
+
+</idl>
+
+
+<!-- vim: set shiftwidth=2 tabstop=2 expandtab: -->
diff --git a/src/VBox/Main/idl/apiwrap-server-filelist.xsl b/src/VBox/Main/idl/apiwrap-server-filelist.xsl
new file mode 100644
index 00000000..e3594662
--- /dev/null
+++ b/src/VBox/Main/idl/apiwrap-server-filelist.xsl
@@ -0,0 +1,192 @@
+<?xml version="1.0"?>
+
+<!--
+ apiwrap-server-filelist.xsl:
+
+ XSLT stylesheet that generate a makefile include with
+ the lists of files that apiwrap-server.xsl produces
+ from VirtualBox.xidl.
+-->
+<!--
+ Copyright (C) 2015-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ extension-element-prefixes="exsl">
+
+<xsl:output method="text"/>
+
+<xsl:strip-space elements="*"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ XSLT parameters
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<!-- Whether to generate wrappers for VBoxSDS-->
+<xsl:param name="g_fVBoxWithSDS" select="'no'"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ global XSLT variables
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:variable name="G_sNewLine">
+ <xsl:choose>
+ <xsl:when test="$KBUILD_HOST = 'win'">
+ <xsl:value-of select="'&#13;&#10;'"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="'&#10;'"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:variable>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ wildcard match, ignore everything which has no explicit match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="*" mode="filelist-even-sources"/>
+<xsl:template match="*" mode="filelist-odd-sources"/>
+<xsl:template match="*" mode="filelist-headers"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ interface match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="interface" mode="filelist-even-sources">
+ <xsl:if test="not(@internal='yes') and not(@autogen='VBoxEvent') and not(@supportsErrorInfo='no') and (position() mod 2) = 0">
+ <xsl:value-of select="concat(' \', $G_sNewLine, '&#9;$(VBOX_MAIN_APIWRAPPER_DIR)/', substring(@name, 2), 'Wrap.cpp')"/>
+ </xsl:if>
+</xsl:template>
+
+<xsl:template match="interface" mode="filelist-odd-sources">
+ <xsl:if test="not(@internal='yes') and not(@autogen='VBoxEvent') and not(@supportsErrorInfo='no') and (position() mod 2) = 1">
+ <xsl:value-of select="concat(' \', $G_sNewLine, '&#9;$(VBOX_MAIN_APIWRAPPER_DIR)/', substring(@name, 2), 'Wrap.cpp')"/>
+ </xsl:if>
+</xsl:template>
+
+<xsl:template match="interface" mode="filelist-headers">
+ <xsl:if test="not(@internal='yes') and not(@autogen='VBoxEvent') and not(@supportsErrorInfo='no')">
+ <xsl:value-of select="concat(' \', $G_sNewLine, '&#9;$(VBOX_MAIN_APIWRAPPER_DIR)/', substring(@name, 2), 'Wrap.h')"/>
+ </xsl:if>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ ignore all if tags except those for XPIDL or MIDL target
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="if" mode="filelist-even-sources">
+ <xsl:if test="(@target = 'xpidl') or (@target = 'midl')">
+ <xsl:apply-templates mode="filelist-even-sources"/>
+ </xsl:if>
+</xsl:template>
+
+<xsl:template match="if" mode="filelist-odd-sources">
+ <xsl:if test="(@target = 'xpidl') or (@target = 'midl')">
+ <xsl:apply-templates mode="filelist-odd-sources"/>
+ </xsl:if>
+</xsl:template>
+
+<xsl:template match="if" mode="filelist-headers">
+ <xsl:if test="(@target = 'xpidl') or (@target = 'midl')">
+ <xsl:apply-templates mode="filelist-headers"/>
+ </xsl:if>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ application match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="application" mode="filelist-even-sources" name="template_app_filelist_even_sources">
+ <xsl:apply-templates mode="filelist-even-sources"/>
+</xsl:template>
+
+<xsl:template match="application" mode="filelist-odd-sources" name="template_app_filelist_odd_sources">
+ <xsl:apply-templates mode="filelist-odd-sources"/>
+</xsl:template>
+
+<xsl:template match="application" mode="filelist-headers" name="template_app_filelist_headers">
+ <xsl:apply-templates mode="filelist-headers"/>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ library match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="library" mode="filelist-even-sources">
+ <xsl:apply-templates mode="filelist-even-sources"/>
+</xsl:template>
+
+<xsl:template match="library" mode="filelist-odd-sources">
+ <xsl:apply-templates mode="filelist-odd-sources"/>
+</xsl:template>
+
+<xsl:template match="library" mode="filelist-headers">
+ <xsl:apply-templates mode="filelist-headers"/>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ root match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="/idl">
+ <xsl:text>VBOX_MAIN_APIWRAPPER_GEN_SRCS_EVEN := </xsl:text>
+ <xsl:apply-templates mode="filelist-even-sources"/>
+ <xsl:value-of select="concat($G_sNewLine, $G_sNewLine)"/>
+
+ <xsl:text>VBOX_MAIN_APIWRAPPER_GEN_SRCS_ODD := </xsl:text>
+ <xsl:apply-templates mode="filelist-odd-sources"/>
+ <xsl:value-of select="concat($G_sNewLine, $G_sNewLine)"/>
+
+ <xsl:text>VBOX_MAIN_APIWRAPPER_GEN_SRCS := $(VBOX_MAIN_APIWRAPPER_GEN_SRCS_EVEN) $(VBOX_MAIN_APIWRAPPER_GEN_SRCS_ODD)</xsl:text>
+ <xsl:value-of select="concat($G_sNewLine, $G_sNewLine)"/>
+
+ <xsl:text>VBOX_MAIN_APIWRAPPER_GEN_HDRS := </xsl:text>
+ <xsl:apply-templates mode="filelist-headers"/>
+ <xsl:value-of select="concat($G_sNewLine, $G_sNewLine)"/>
+</xsl:template>
+
+
+ <xsl:template match="application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']" mode="filelist-even-sources" >
+ <xsl:if test="$g_fVBoxWithSDS='yes'" >
+ <xsl:call-template name="template_app_filelist_even_sources" />
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']" mode="filelist-headers" >
+ <xsl:if test="$g_fVBoxWithSDS='yes'" >
+ <xsl:call-template name="template_app_filelist_headers" />
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']" mode="filelist-odd-sources" >
+ <xsl:if test="$g_fVBoxWithSDS='yes'" >
+ <xsl:call-template name="template_app_filelist_odd_sources" />
+ </xsl:if>
+ </xsl:template>
+
+
+</xsl:stylesheet>
+<!-- vi: set tabstop=4 shiftwidth=4 expandtab: -->
+
diff --git a/src/VBox/Main/idl/apiwrap-server.xsl b/src/VBox/Main/idl/apiwrap-server.xsl
new file mode 100644
index 00000000..baa629ae
--- /dev/null
+++ b/src/VBox/Main/idl/apiwrap-server.xsl
@@ -0,0 +1,2592 @@
+<?xml version="1.0"?>
+
+<!--
+ apiwrap-server.xsl:
+ XSLT stylesheet that generates C++ API wrappers (server side) from
+ VirtualBox.xidl.
+-->
+<!--
+ Copyright (C) 2010-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ extension-element-prefixes="exsl">
+
+<xsl:output method="text"/>
+
+<xsl:strip-space elements="*"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ global XSLT variables
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:variable name="G_xsltFilename" select="'apiwrap-server.xsl'"/>
+
+<xsl:variable name="G_root" select="/"/>
+
+<xsl:include href="typemap-shared.inc.xsl"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ Keys for more efficiently looking up of types.
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:key name="G_keyEnumsByName" match="//enum[@name]" use="@name"/>
+<xsl:key name="G_keyInterfacesByName" match="//interface[@name]" use="@name"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+templates for file separation
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="interface" mode="startfile">
+ <xsl:param name="file"/>
+
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+ <xsl:value-of select="concat($G_sNewLine, '// ##### BEGINFILE &quot;', $file, '&quot;', $G_sNewLine)"/>
+</xsl:template>
+
+<xsl:template match="interface" mode="endfile">
+ <xsl:param name="file"/>
+
+ <xsl:value-of select="concat($G_sNewLine, '// ##### ENDFILE &quot;', $file, '&quot;', $G_sNewLine)"/>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+templates for file headers/footers
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template name="fileheader">
+ <xsl:param name="class"/>
+ <xsl:param name="name"/>
+ <xsl:param name="type"/>
+
+ <xsl:text>/** @file
+</xsl:text>
+ <xsl:value-of select="concat(' * VirtualBox API class wrapper ', $type, ' for I', $class, '.')"/>
+ <xsl:text>
+ *
+ * DO NOT EDIT! This is a generated file.
+ * Generated from: src/VBox/Main/idl/VirtualBox.xidl
+ * Generator: src/VBox/Main/idl/apiwrap-server.xsl
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see &lt;https://www.gnu.org/licenses&gt;.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+</xsl:text>
+</xsl:template>
+
+<!-- Emits COM_INTERFACE_ENTRY statements for the current interface node and whatever it inherits from. -->
+<xsl:template name="emitCOMInterfaces">
+ <xsl:value-of select="concat(' COM_INTERFACE_ENTRY(', @name, ')' , $G_sNewLine)"/>
+ <!-- now recurse to emit all base interfaces -->
+ <xsl:variable name="extends" select="@extends"/>
+ <xsl:if test="$extends and not($extends='$unknown') and not($extends='$errorinfo')">
+ <xsl:for-each select="key('G_keyInterfacesByName', $extends)">
+ <xsl:call-template name="emitCOMInterfaces"/>
+ </xsl:for-each>
+ </xsl:if>
+</xsl:template>
+
+<xsl:template match="interface" mode="classheader">
+ <xsl:param name="addinterfaces"/>
+ <xsl:value-of select="concat('#ifndef ', substring(@name, 2), 'Wrap_H_', $G_sNewLine)"/>
+ <xsl:value-of select="concat('#define ', substring(@name, 2), 'Wrap_H_')"/>
+ <xsl:text>
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VirtualBoxBase.h"
+#include "Wrapper.h"
+
+</xsl:text>
+ <xsl:value-of select="concat('class ATL_NO_VTABLE ', substring(@name, 2), 'Wrap')"/>
+ <xsl:text>
+ : public VirtualBoxBase
+</xsl:text>
+ <xsl:value-of select="concat(' , VBOX_SCRIPTABLE_IMPL(', @name, ')')"/>
+ <xsl:value-of select="$G_sNewLine"/>
+ <xsl:for-each select="exsl:node-set($addinterfaces)/token">
+ <xsl:value-of select="concat(' , VBOX_SCRIPTABLE_IMPL(', text(), ')')"/>
+ <xsl:value-of select="$G_sNewLine"/>
+ </xsl:for-each>
+ <xsl:text>{
+ Q_OBJECT
+
+public:
+</xsl:text>
+ <xsl:value-of select="concat(' VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(', substring(@name, 2), 'Wrap, ', @name, ')', $G_sNewLine)"/>
+ <xsl:value-of select="concat(' DECLARE_NOT_AGGREGATABLE(', substring(@name, 2), 'Wrap)', $G_sNewLine)"/>
+ <xsl:text> DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+</xsl:text>
+ <xsl:value-of select="concat(' BEGIN_COM_MAP(', substring(@name, 2), 'Wrap)', $G_sNewLine)"/>
+ <xsl:text> COM_INTERFACE_ENTRY(ISupportErrorInfo)
+</xsl:text>
+ <xsl:call-template name="emitCOMInterfaces"/>
+ <xsl:value-of select="concat(' COM_INTERFACE_ENTRY2(IDispatch, ', @name, ')', $G_sNewLine)"/>
+ <xsl:variable name="manualAddInterfaces">
+ <xsl:call-template name="checkoption">
+ <xsl:with-param name="optionlist" select="@wrap-hint-server"/>
+ <xsl:with-param name="option" select="'manualaddinterfaces'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:if test="not($manualAddInterfaces = 'true')">
+ <xsl:for-each select="exsl:node-set($addinterfaces)/token">
+ <!-- This is super tricky, as the for-each switches to the node set,
+ which means the normal document isn't available any more. We get
+ the data we need, uses a for-each to switch document and then a
+ key() to look up the interface by name. -->
+ <xsl:variable name="addifname">
+ <xsl:value-of select="string(.)"/>
+ </xsl:variable>
+ <xsl:for-each select="$G_root">
+ <xsl:for-each select="key('G_keyInterfacesByName', $addifname)">
+ <xsl:call-template name="emitCOMInterfaces"/>
+ </xsl:for-each>
+ </xsl:for-each>
+ </xsl:for-each>
+ </xsl:if>
+ <xsl:value-of select="concat(' VBOX_TWEAK_INTERFACE_ENTRY(', @name, ')', $G_sNewLine)"/>
+ <xsl:text> END_COM_MAP()
+
+</xsl:text>
+ <xsl:value-of select="concat(' DECLARE_COMMON_CLASS_METHODS(', substring(@name, 2), 'Wrap)', $G_sNewLine)"/>
+</xsl:template>
+
+<xsl:template match="interface" mode="classfooter">
+ <xsl:param name="addinterfaces"/>
+ <xsl:if test="@wrap-gen-hook = 'yes'">
+ <xsl:text>
+public:
+ virtual void i_callHook(const char *a_pszFunction) { RT_NOREF_PV(a_pszFunction); }
+</xsl:text>
+ </xsl:if>
+ <xsl:text>
+private:
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(</xsl:text>
+ <xsl:value-of select="concat(substring(@name, 2),'Wrap')"/>
+ <xsl:text>); /* Shuts up MSC warning C4625. */
+
+};
+
+</xsl:text>
+ <xsl:value-of select="concat('#endif // !', substring(@name, 2), 'Wrap_H_', $G_sNewLine)"/>
+</xsl:template>
+
+<xsl:template match="interface" mode="codeheader">
+ <xsl:param name="addinterfaces"/>
+ <xsl:value-of select="concat('#define LOG_GROUP LOG_GROUP_MAIN_', translate(substring(@name, 2), $G_lowerCase, $G_upperCase), $G_sNewLine, $G_sNewLine)"/>
+ <xsl:value-of select="concat('#include &quot;', substring(@name, 2), 'Wrap.h&quot;', $G_sNewLine)"/>
+ <xsl:text>#include "LoggingNew.h"
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+# include "dtrace/VBoxAPI.h"
+#endif
+
+</xsl:text>
+</xsl:template>
+
+<xsl:template name="emitISupports">
+ <xsl:param name="classname"/>
+ <xsl:param name="extends"/>
+ <xsl:param name="addinterfaces"/>
+ <xsl:param name="depth"/>
+ <xsl:param name="interfacelist"/>
+
+ <xsl:choose>
+ <xsl:when test="$extends and not($extends='$unknown') and not($extends='$errorinfo')">
+ <xsl:variable name="newextends" select="key('G_keyInterfacesByName', $extends)/@extends"/>
+ <xsl:variable name="newiflist" select="concat($interfacelist, ', ', $extends)"/>
+ <xsl:call-template name="emitISupports">
+ <xsl:with-param name="classname" select="$classname"/>
+ <xsl:with-param name="extends" select="$newextends"/>
+ <xsl:with-param name="addinterfaces" select="$addinterfaces"/>
+ <xsl:with-param name="depth" select="$depth + 1"/>
+ <xsl:with-param name="interfacelist" select="$newiflist"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:variable name="addinterfaces_ns" select="exsl:node-set($addinterfaces)"/>
+ <xsl:choose>
+ <xsl:when test="count($addinterfaces_ns/token) > 0">
+ <xsl:variable name="addifname" select="$addinterfaces_ns/token[1]"/>
+ <xsl:variable name="addif" select="key('G_keyInterfacesByName', $addifname)/@extends"/>
+ <xsl:variable name="newextends" select="$addif/@extends"/>
+ <xsl:variable name="newaddinterfaces" select="$addinterfaces_ns/token[position() > 1]"/>
+ <xsl:variable name="newiflist" select="concat($interfacelist, ', ', $addifname)"/>
+ <xsl:call-template name="emitISupports">
+ <xsl:with-param name="classname" select="$classname"/>
+ <xsl:with-param name="extends" select="$newextends"/>
+ <xsl:with-param name="addinterfaces" select="$newaddinterfaces"/>
+ <xsl:with-param name="depth" select="$depth + 1"/>
+ <xsl:with-param name="interfacelist" select="$newiflist"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('NS_IMPL_THREADSAFE_ISUPPORTS', $depth, '_CI(', $classname, ', ', $interfacelist, ')', $G_sNewLine)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template match="interface" mode="codefooter">
+ <xsl:param name="addinterfaces"/>
+ <xsl:text>#ifdef VBOX_WITH_XPCOM
+</xsl:text>
+ <xsl:value-of select="concat('NS_DECL_CLASSINFO(', substring(@name, 2), 'Wrap)', $G_sNewLine)"/>
+
+ <xsl:variable name="manualAddInterfaces">
+ <xsl:call-template name="checkoption">
+ <xsl:with-param name="optionlist" select="@wrap-hint-server"/>
+ <xsl:with-param name="option" select="'manualaddinterfaces'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="$manualAddInterfaces = 'true'">
+ <xsl:variable name="nulladdinterfaces"></xsl:variable>
+ <xsl:call-template name="emitISupports">
+ <xsl:with-param name="classname" select="concat(substring(@name, 2), 'Wrap')"/>
+ <xsl:with-param name="extends" select="@extends"/>
+ <xsl:with-param name="addinterfaces" select="$nulladdinterfaces"/>
+ <xsl:with-param name="depth" select="1"/>
+ <xsl:with-param name="interfacelist" select="@name"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="emitISupports">
+ <xsl:with-param name="classname" select="concat(substring(@name, 2), 'Wrap')"/>
+ <xsl:with-param name="extends" select="@extends"/>
+ <xsl:with-param name="addinterfaces" select="$addinterfaces"/>
+ <xsl:with-param name="depth" select="1"/>
+ <xsl:with-param name="interfacelist" select="@name"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>#endif // VBOX_WITH_XPCOM
+</xsl:text>
+</xsl:template>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ templates for dealing with names and parameters
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template name="tospace">
+ <xsl:param name="str"/>
+ <xsl:value-of select="translate($str, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_', ' ')"/>
+</xsl:template>
+
+<xsl:template name="checkoption">
+ <xsl:param name="optionlist"/>
+ <xsl:param name="option"/>
+ <xsl:value-of select="string-length($option) > 0 and contains(concat(',', translate($optionlist, ' ', ''), ','), concat(',', $option, ','))"/>
+</xsl:template>
+
+<xsl:template name="getattrlist">
+ <xsl:param name="val"/>
+ <xsl:param name="separator" select="','"/>
+
+ <xsl:if test="$val and $val != ''">
+ <xsl:choose>
+ <xsl:when test="contains($val,$separator)">
+ <token>
+ <xsl:value-of select="substring-before($val,$separator)"/>
+ </token>
+ <xsl:call-template name="getattrlist">
+ <xsl:with-param name="val" select="substring-after($val,$separator)"/>
+ <xsl:with-param name="separator" select="$separator"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <token><xsl:value-of select="$val"/></token>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:if>
+</xsl:template>
+
+<xsl:template name="translatepublictype">
+ <xsl:param name="type"/>
+ <xsl:param name="dir"/>
+ <xsl:param name="mod"/>
+
+ <xsl:choose>
+ <xsl:when test="$type='wstring' or $type='uuid'">
+ <xsl:if test="$dir='in'">
+ <xsl:text>IN_</xsl:text>
+ </xsl:if>
+ <xsl:text>BSTR</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='$unknown'">
+ <xsl:text>IUnknown *</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:value-of select="concat($type, ' *')"/>
+ </xsl:when>
+
+ <xsl:when test="count(key('G_keyEnumsByName', $type)) > 0">
+ <xsl:value-of select="concat($type, '_T')"/>
+ </xsl:when>
+
+ <!-- Micro optimizations: Put off wraptypefield calculation as long as possible; Check interfaces before enums. -->
+ <xsl:otherwise>
+ <!-- get C++ glue type from IDL type from table in typemap-shared.inc.xsl -->
+ <xsl:variable name="gluetypefield" select="exsl:node-set($G_aSharedTypes)/type[@idlname=$type]/@gluename"/>
+ <xsl:choose>
+ <xsl:when test="string-length($gluetypefield)">
+ <xsl:value-of select="$gluetypefield"/>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('translatepublictype: Type &quot;', $type, '&quot; is not supported.')"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="$mod='ptr'">
+ <xsl:text> *</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+<xsl:template name="translatewrappedtype">
+ <xsl:param name="type"/>
+ <xsl:param name="dir"/>
+ <xsl:param name="mod"/>
+ <xsl:param name="safearray"/>
+
+ <xsl:choose>
+ <xsl:when test="$type='wstring'">
+ <xsl:if test="$dir='in' and not($safearray='yes')">
+ <xsl:text>const </xsl:text>
+ </xsl:if>
+ <xsl:text>com::Utf8Str &amp;</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='uuid'">
+ <xsl:if test="$dir='in'">
+ <xsl:text>const </xsl:text>
+ </xsl:if>
+ <xsl:text>com::Guid &amp;</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$type='$unknown'">
+ <xsl:if test="$dir='in' and not($safearray='yes')">
+ <xsl:text>const </xsl:text>
+ </xsl:if>
+ <xsl:text>ComPtr&lt;IUnknown&gt; &amp;</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:if test="$dir='in' and not($safearray='yes')">
+ <xsl:text>const </xsl:text>
+ </xsl:if>
+ <xsl:value-of select="concat('ComPtr&lt;', $type, '&gt; &amp;')"/>
+ </xsl:when>
+
+ <xsl:when test="count(key('G_keyEnumsByName', $type)) > 0">
+ <xsl:value-of select="concat($type, '_T')"/>
+ </xsl:when>
+
+ <!-- Micro optimizations: Put off wraptypefield calculation as long as possible; Check interfaces before enums. -->
+ <xsl:otherwise>
+ <!-- get C++ wrap type from IDL type from table in typemap-shared.inc.xsl -->
+ <xsl:variable name="wraptypefield" select="exsl:node-set($G_aSharedTypes)/type[@idlname=$type]/@gluename"/>
+ <xsl:choose>
+ <xsl:when test="string-length($wraptypefield)">
+ <xsl:value-of select="$wraptypefield"/>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('translatewrappedtype: Type &quot;', $type, '&quot; is not supported.')"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="$mod='ptr'">
+ <xsl:text> *</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+<xsl:template name="translatefmtspectype">
+ <xsl:param name="type"/>
+ <xsl:param name="dir"/>
+ <xsl:param name="mod"/>
+ <xsl:param name="safearray"/>
+ <xsl:param name="isref"/>
+
+ <!-- get C format string for IDL type from table in typemap-shared.inc.xsl -->
+ <xsl:variable name="wrapfmt" select="exsl:node-set($G_aSharedTypes)/type[@idlname=$type]/@gluefmt"/>
+ <xsl:choose>
+ <xsl:when test="$mod='ptr' or ($isref='yes' and $dir!='in')">
+ <xsl:text>%p</xsl:text>
+ </xsl:when>
+ <xsl:when test="$safearray='yes'">
+ <xsl:text>%zu</xsl:text>
+ </xsl:when>
+ <xsl:when test="string-length($wrapfmt)">
+ <xsl:value-of select="$wrapfmt"/>
+ </xsl:when>
+ <xsl:when test="$type='$unknown'">
+ <xsl:text>%p</xsl:text>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyEnumsByName', $type)) > 0">
+ <xsl:text>%RU32</xsl:text>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:text>%p</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('translatefmtcpectype: Type &quot;', $type, '&quot; is not supported.')"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="translatedtracetype">
+ <xsl:param name="type"/>
+ <xsl:param name="dir"/>
+ <xsl:param name="mod"/>
+
+ <!-- get dtrace probe type from IDL type from table in typemap-shared.inc.xsl -->
+ <xsl:variable name="dtracetypefield" select="exsl:node-set($G_aSharedTypes)/type[@idlname=$type]/@dtracename"/>
+ <xsl:choose>
+ <xsl:when test="string-length($dtracetypefield)">
+ <xsl:value-of select="$dtracetypefield"/>
+ </xsl:when>
+ <xsl:when test="$type='$unknown'">
+ <!-- <xsl:text>struct IUnknown *</xsl:text> -->
+ <xsl:text>void *</xsl:text>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyEnumsByName', $type)) > 0">
+ <!-- <xsl:value-of select="concat($type, '_T')"/> - later we can emit enums into dtrace the library -->
+ <xsl:text>int</xsl:text>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <!--
+ <xsl:value-of select="concat('struct ', $type, ' *')"/>
+ -->
+ <xsl:text>void *</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('translatedtracetype Type &quot;', $type, '&quot; is not supported.')"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="$mod='ptr'">
+ <xsl:text> *</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ templates for handling entire interfaces and their contents
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<!-- Called on interface node. -->
+<xsl:template name="emitInterface">
+ <!-- sources, headers and dtrace-probes all needs attribute lists -->
+ <xsl:variable name="addinterfaces">
+ <xsl:call-template name="getattrlist">
+ <xsl:with-param name="val" select="@wrap-hint-server-addinterfaces"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <!-- interface sanity check, prevents crashes -->
+ <xsl:if test="(count(attribute) + count(method) + sum(@reservedMethods[number()= number()]) + sum(@reservedAttributes[number()= number()])) = 0">
+ <xsl:message terminate="yes">
+ Interface <xsl:value-of select="@name"/> is empty which causes midl generated proxy
+ stubs to crash. Please add a dummy:<xsl:value-of select="$G_sNewLine"/>
+ &lt;attribute name="midlDoesNotLikeEmptyInterfaces" readonly="yes" type="boolean"/&gt;
+ </xsl:message>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="$generating = 'sources'">
+ <xsl:if test="(position() mod 2) = $reminder">
+ <xsl:call-template name="emitCode">
+ <xsl:with-param name="addinterfaces" select="$addinterfaces"/>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:when>
+ <xsl:when test="$generating = 'headers'">
+ <xsl:call-template name="emitHeader">
+ <xsl:with-param name="addinterfaces" select="$addinterfaces"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="$generating = 'dtrace-probes'">
+ <xsl:call-template name="emitDTraceProbes">
+ <xsl:with-param name="addinterfaces" select="$addinterfaces"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise><xsl:message terminate="yes">Otherwise oops in emitInterface</xsl:message></xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!-- Called on a method param or attribute node. -->
+<xsl:template name="emitPublicParameter">
+ <xsl:param name="dir"/>
+
+ <xsl:variable name="gluetype">
+ <xsl:call-template name="translatepublictype">
+ <xsl:with-param name="type" select="@type"/>
+ <xsl:with-param name="dir" select="$dir"/>
+ <xsl:with-param name="mod" select="@mod"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="@safearray='yes'">
+ <xsl:choose>
+ <xsl:when test="$dir='in'">
+ <xsl:text>ComSafeArrayIn(</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>ComSafeArrayOut(</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:value-of select="$gluetype"/>
+ <xsl:text>, a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>)</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$gluetype"/>
+ <xsl:if test="substring($gluetype,string-length($gluetype))!='*'">
+ <xsl:text> </xsl:text>
+ </xsl:if>
+ <xsl:if test="$dir != 'in'">
+ <xsl:text>*</xsl:text>
+ </xsl:if>
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template match="attribute/@type | param/@type" mode="wrapped">
+ <xsl:param name="dir"/>
+
+ <xsl:variable name="wraptype">
+ <xsl:call-template name="translatewrappedtype">
+ <xsl:with-param name="type" select="."/>
+ <xsl:with-param name="dir" select="$dir"/>
+ <xsl:with-param name="mod" select="../@mod"/>
+ <xsl:with-param name="safearray" select="../@safearray"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="lastchar">
+ <xsl:value-of select="substring($wraptype, string-length($wraptype))"/>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="../@safearray='yes'">
+ <xsl:if test="$dir='in'">
+ <xsl:text>const </xsl:text>
+ </xsl:if>
+ <xsl:text>std::vector&lt;</xsl:text>
+ <xsl:choose>
+ <xsl:when test="$lastchar = '&amp;'">
+ <xsl:variable name="wraptype2">
+ <xsl:value-of select="substring($wraptype, 1, string-length($wraptype)-2)"/>
+ </xsl:variable>
+ <xsl:value-of select="$wraptype2"/>
+ <xsl:if test="substring($wraptype2,string-length($wraptype2)) = '&gt;'">
+ <xsl:text> </xsl:text>
+ </xsl:if>
+ </xsl:when>
+ <xsl:when test="lastchar = '&gt;'">
+ <xsl:value-of select="concat($wraptype, ' ')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$wraptype"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>&gt; &amp;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$wraptype"/>
+ <xsl:if test="$lastchar != '&amp;'">
+ <xsl:if test="$lastchar != '*'">
+ <xsl:text> </xsl:text>
+ </xsl:if>
+ <xsl:if test="$dir != 'in'">
+ <xsl:text>*</xsl:text>
+ </xsl:if>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="../@name"/>
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="attribute/@type | param/@type" mode="logparamtext">
+ <xsl:param name="dir"/>
+ <xsl:param name="isref"/>
+
+ <xsl:if test="$isref!='yes' and ($dir='out' or $dir='ret')">
+ <xsl:text>*</xsl:text>
+ </xsl:if>
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="../@name"/>
+ </xsl:call-template>
+ <xsl:text>=</xsl:text>
+ <xsl:call-template name="translatefmtspectype">
+ <xsl:with-param name="type" select="."/>
+ <xsl:with-param name="dir" select="$dir"/>
+ <xsl:with-param name="mod" select="../@mod"/>
+ <xsl:with-param name="safearray" select="../@safearray"/>
+ <xsl:with-param name="isref" select="$isref"/>
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="attribute/@type | param/@type" mode="logparamval">
+ <xsl:param name="dir"/>
+ <xsl:param name="isref"/>
+
+ <xsl:choose>
+ <xsl:when test="../@safearray='yes' and $isref!='yes'">
+ <xsl:text>ComSafeArraySize(</xsl:text>
+ <xsl:if test="$isref!='yes' and $dir!='in'">
+ <xsl:text>*</xsl:text>
+ </xsl:if>
+ </xsl:when>
+ <xsl:when test="$isref!='yes' and $dir!='in'">
+ <xsl:text>*</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="../@name"/>
+ </xsl:call-template>
+ <xsl:choose>
+ <xsl:when test="../@safearray='yes' and $isref!='yes'">
+ <xsl:text>)</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+</xsl:template>
+
+<!-- Emits the DTrace probe parameter value (using tmps), invoked on param or attribute node. -->
+<xsl:template name="emitDTraceParamValue">
+ <xsl:param name="dir"/>
+
+ <xsl:variable name="viatmpvar">
+ <xsl:for-each select="@type">
+ <xsl:call-template name="paramconversionviatmp">
+ <xsl:with-param name="dir" select="$dir"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:variable>
+
+ <xsl:variable name="type" select="@type"/>
+ <xsl:choose>
+ <!-- Doesn't help to inline paramconversionviatmp: <xsl:when test="$type = 'wstring' or $type = '$unknown' or $type = 'uuid' or @safearray = 'yes' or count(key('G_keyInterfacesByName', $type)) > 0"> -->
+ <xsl:when test="$viatmpvar = 'yes'">
+ <xsl:variable name="tmpname">
+ <xsl:text>Tmp</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="@safearray = 'yes'">
+ <xsl:text>(uint32_t)</xsl:text>
+ <xsl:value-of select="$tmpname"/>
+ <xsl:text>.array().size(), </xsl:text>
+ <!-- Later:
+ <xsl:value-of select="concat($tmpname, '.array().data(), ')"/>
+ -->
+ <xsl:text>NULL /*for now*/</xsl:text>
+ </xsl:when>
+ <xsl:when test="$type = 'wstring'">
+ <xsl:value-of select="$tmpname"/>
+ <xsl:text>.str().c_str()</xsl:text>
+ </xsl:when>
+ <xsl:when test="$type = 'uuid'">
+ <xsl:value-of select="$tmpname"/>
+ <xsl:text>.uuid().toStringCurly().c_str()</xsl:text>
+ </xsl:when>
+ <xsl:when test="$type = '$unknown'">
+ <xsl:text>(void *)</xsl:text>
+ <xsl:value-of select="$tmpname"/>
+ <xsl:text>.ptr()</xsl:text>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:text>(void *)</xsl:text>
+ <xsl:value-of select="$tmpname"/>
+ <xsl:text>.ptr()</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$tmpname"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:if test="$dir != 'in'">
+ <xsl:text>*</xsl:text>
+ </xsl:if>
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+
+ <xsl:if test="$type = 'boolean'">
+ <xsl:text> != FALSE</xsl:text>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+Same as emitDTraceParamValue except no temporary variables are used (they are out of scope).
+Note! There are two other instances of this code with different @dir values, see below.
+-->
+<xsl:template name="emitDTraceParamValNoTmp">
+ <!-- To speed this up, the logic of paramconversionviatmp has been duplicated/inlined here. -->
+ <xsl:variable name="type" select="@type"/>
+ <xsl:choose>
+ <xsl:when test="@safearray = 'yes'">
+ <xsl:text>0, 0</xsl:text>
+ </xsl:when>
+ <xsl:when test="$type = 'wstring' or $type = '$unknown' or $type = 'uuid' or count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:text>0</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:if test="@dir != 'in'">
+ <xsl:text>*</xsl:text>
+ </xsl:if>
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:if test="$type = 'boolean'">
+ <xsl:text> != FALSE</xsl:text>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!-- Copy of emitDTraceParamValNoTmp with @dir = 'in' for speeding up the code (noticable difference). -->
+<xsl:template name="emitDTraceParamValNoTmp-DirIn">
+ <xsl:variable name="type" select="@type"/>
+ <xsl:choose>
+ <xsl:when test="@safearray = 'yes'">
+ <xsl:text>0, 0</xsl:text>
+ </xsl:when>
+ <xsl:when test="$type = 'wstring' or $type = '$unknown' or $type = 'uuid' or count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:text>0</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:if test="$type = 'boolean'">
+ <xsl:text> != FALSE</xsl:text>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!-- Copy of emitDTraceParamValNoTmp with @dir != 'in' for speeding up attributes (noticable difference). -->
+<xsl:template name="emitDTraceParamValNoTmp-DirNotIn">
+ <xsl:variable name="type" select="@type"/>
+ <xsl:choose>
+ <xsl:when test="@safearray = 'yes'">
+ <xsl:text>0, 0</xsl:text>
+ </xsl:when>
+ <xsl:when test="$type = 'wstring' or $type = '$unknown' or $type = 'uuid' or count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:text>0</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>*a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:if test="$type = 'boolean'">
+ <xsl:text> != FALSE</xsl:text>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template match="attribute/@type | param/@type" mode="dtraceparamdecl">
+ <xsl:param name="dir"/>
+
+ <xsl:variable name="gluetype">
+ <xsl:call-template name="translatedtracetype">
+ <xsl:with-param name="type" select="."/>
+ <xsl:with-param name="dir" select="$dir"/>
+ <xsl:with-param name="mod" select="../@mod"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <!-- Safe arrays get an extra size parameter. -->
+ <xsl:if test="../@safearray='yes'">
+ <xsl:text>uint32_t a_c</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="../@name"/>
+ </xsl:call-template>
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+
+ <xsl:value-of select="$gluetype"/>
+ <xsl:choose>
+ <xsl:when test="../@safearray='yes'">
+ <xsl:text> *a_pa</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:if test="substring($gluetype,string-length($gluetype))!='*'">
+ <xsl:text> </xsl:text>
+ </xsl:if>
+ <xsl:text>a_</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="../@name"/>
+ </xsl:call-template>
+</xsl:template>
+
+<!-- Call this to determine whether a temporary conversion variable is used for the current parameter.
+Returns empty if not needed, non-empty ('yes') if needed. -->
+<xsl:template name="paramconversionviatmp">
+ <xsl:param name="dir"/>
+ <xsl:variable name="type" select="."/>
+ <xsl:choose>
+ <xsl:when test="$type = 'wstring' or $type = '$unknown' or $type = 'uuid'">
+ <xsl:text>yes</xsl:text>
+ </xsl:when>
+ <xsl:when test="../@safearray = 'yes'">
+ <xsl:text>yes</xsl:text>
+ </xsl:when>
+ <xsl:when test="$type = 'boolean' or $type = 'long' or $type = 'long' or $type = 'long long'"/> <!-- XXX: drop this? -->
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:text>yes</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+</xsl:template>
+
+<!-- Call this to get the argument conversion class, if any is needed. -->
+<xsl:template name="paramconversionclass">
+ <xsl:param name="dir"/>
+
+ <xsl:variable name="type" select="."/>
+ <xsl:choose>
+ <xsl:when test="$type='$unknown'">
+ <xsl:if test="../@safearray='yes'">
+ <xsl:text>Array</xsl:text>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="$dir='in'">
+ <xsl:text>ComTypeInConverter&lt;IUnknown&gt;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>ComTypeOutConverter&lt;IUnknown&gt;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="$type='wstring'">
+ <xsl:if test="../@safearray='yes'">
+ <xsl:text>Array</xsl:text>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="$dir='in'">
+ <xsl:text>BSTRInConverter</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>BSTROutConverter</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="$type='uuid'">
+ <xsl:if test="../@safearray='yes'">
+ <xsl:text>Array</xsl:text>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="$dir='in'">
+ <xsl:text>UuidInConverter</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>UuidOutConverter</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:if test="../@safearray='yes'">
+ <xsl:text>Array</xsl:text>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="$dir='in'">
+ <xsl:text>ComTypeInConverter</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>ComTypeOutConverter</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:value-of select="concat('&lt;', $type, '&gt;')"/>
+ </xsl:when>
+
+ <xsl:when test="../@safearray='yes'">
+ <xsl:text>Array</xsl:text>
+ <xsl:choose>
+ <xsl:when test="$dir='in'">
+ <xsl:text>InConverter</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>OutConverter</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:variable name="gluetype">
+ <xsl:call-template name="translatepublictype">
+ <xsl:with-param name="type" select="."/>
+ <xsl:with-param name="dir" select="$dir"/>
+ <xsl:with-param name="mod" select="../@mod"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat('&lt;', $gluetype, '&gt;')"/>
+ </xsl:when>
+ </xsl:choose>
+</xsl:template>
+
+<!-- Emits code for converting the parameter to a temporary variable. -->
+<xsl:template match="attribute/@type | param/@type" mode="paramvalconversion2tmpvar">
+ <xsl:param name="dir"/>
+
+ <xsl:variable name="conversionclass">
+ <xsl:call-template name="paramconversionclass">
+ <xsl:with-param name="dir" select="$dir"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:if test="$conversionclass != ''">
+ <xsl:value-of select="$conversionclass"/>
+ <xsl:text> Tmp</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="../@name"/>
+ </xsl:call-template>
+ <xsl:text>(</xsl:text>
+ <xsl:if test="../@safearray = 'yes'">
+ <xsl:choose>
+ <xsl:when test="$dir = 'in'">
+ <xsl:text>ComSafeArrayInArg(</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>ComSafeArrayOutArg(</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:if>
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="../@name"/>
+ </xsl:call-template>
+ <xsl:if test="../@safearray = 'yes'">
+ <xsl:text>)</xsl:text>
+ </xsl:if>
+ <xsl:text>);</xsl:text>
+ </xsl:if>
+
+</xsl:template>
+
+<!-- Partner to paramvalconversion2tmpvar that emits the parameter when calling call the internal worker method. -->
+<xsl:template match="attribute/@type | param/@type" mode="paramvalconversionusingtmp">
+ <xsl:param name="dir"/>
+
+ <xsl:variable name="viatmpvar">
+ <xsl:call-template name="paramconversionviatmp">
+ <xsl:with-param name="dir" select="$dir"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="type" select="."/>
+
+ <xsl:choose>
+ <xsl:when test="$viatmpvar = 'yes'">
+ <xsl:text>Tmp</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="../@name"/>
+ </xsl:call-template>
+
+ <xsl:choose>
+ <xsl:when test="../@safearray='yes'">
+ <xsl:text>.array()</xsl:text>
+ </xsl:when>
+ <xsl:when test="$type = 'wstring'">
+ <xsl:text>.str()</xsl:text>
+ </xsl:when>
+ <xsl:when test="$type = 'uuid'">
+ <xsl:text>.uuid()</xsl:text>
+ </xsl:when>
+ <xsl:when test="$type = '$unknown'">
+ <xsl:text>.ptr()</xsl:text>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:text>.ptr()</xsl:text>
+ </xsl:when>
+ <xsl:otherwise><xsl:message terminate="yes">Oops #1</xsl:message></xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="../@name"/>
+ </xsl:call-template>
+
+ <!-- Make sure BOOL values we pass down are either TRUE or FALSE. -->
+ <xsl:if test="$type = 'boolean' and $dir = 'in'">
+ <xsl:text> != FALSE</xsl:text>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ emit attribute
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="attribute" mode="public">
+ <xsl:param name="target"/>
+
+ <xsl:call-template name="emitTargetBegin">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:call-template>
+
+ <xsl:variable name="attrbasename">
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:value-of select="concat(' STDMETHOD(COMGETTER(', $attrbasename, '))(')"/>
+ <xsl:call-template name="emitPublicParameter">
+ <xsl:with-param name="dir">out</xsl:with-param>
+ </xsl:call-template>
+ <xsl:text>);
+</xsl:text>
+
+ <xsl:if test="not(@readonly) or @readonly!='yes'">
+ <xsl:value-of select="concat(' STDMETHOD(COMSETTER(', $attrbasename, '))(')"/>
+ <xsl:call-template name="emitPublicParameter">
+ <xsl:with-param name="dir">in</xsl:with-param>
+ </xsl:call-template>
+ <xsl:text>);
+</xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="emitTargetEnd">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="attribute" mode="wrapped">
+ <xsl:param name="target"/>
+
+ <xsl:call-template name="emitTargetBegin">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:call-template>
+
+ <xsl:variable name="attrbasename">
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:if test="$attrbasename = 'MidlDoesNotLikeEmptyInterfaces'">
+ <xsl:text> //</xsl:text>
+ </xsl:if>
+
+ <xsl:value-of select="concat(' virtual HRESULT get', $attrbasename, '(')"/>
+ <xsl:variable name="passAutoCaller">
+ <xsl:call-template name="checkoption">
+ <xsl:with-param name="optionlist" select="@wrap-hint-server"/>
+ <xsl:with-param name="option" select="'passcaller'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:if test="$passAutoCaller = 'true'">
+ <xsl:text>AutoCaller &amp;aAutoCaller, </xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@type" mode="wrapped">
+ <xsl:with-param name="dir" select="'out'"/>
+ </xsl:apply-templates>
+ <xsl:text>) = 0;
+</xsl:text>
+
+ <xsl:if test="not(@readonly) or @readonly!='yes'">
+ <xsl:value-of select="concat(' virtual HRESULT set', $attrbasename, '(')"/>
+ <xsl:if test="$passAutoCaller = 'true'">
+ <xsl:text>AutoCaller &amp;aAutoCaller, </xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@type" mode="wrapped">
+ <xsl:with-param name="dir" select="'in'"/>
+ </xsl:apply-templates>
+ <xsl:text>) = 0;
+</xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="emitTargetEnd">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="attribute" mode="code">
+ <xsl:param name="topclass"/>
+ <xsl:param name="dtracetopclass"/>
+ <xsl:param name="target"/>
+
+ <xsl:call-template name="emitTargetBegin">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:call-template>
+
+ <xsl:variable name="attrbasename">
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="limitedAutoCaller">
+ <xsl:call-template name="checkoption">
+ <xsl:with-param name="optionlist" select="@wrap-hint-server"/>
+ <xsl:with-param name="option" select="'limitedcaller'"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="dtraceattrname">
+ <xsl:choose>
+ <xsl:when test="@dtracename">
+ <xsl:value-of select="@dtracename"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$attrbasename"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:value-of select="concat('STDMETHODIMP ', $topclass, 'Wrap::COMGETTER(', $attrbasename, ')(')"/>
+ <xsl:call-template name="emitPublicParameter">
+ <xsl:with-param name="dir">out</xsl:with-param>
+ </xsl:call-template>
+ <xsl:text>)
+{</xsl:text>
+ <xsl:if test="$attrbasename = 'MidlDoesNotLikeEmptyInterfaces'">
+ <xsl:text>
+#if 0 /* This is a dummy attribute */</xsl:text>
+ </xsl:if>
+ <xsl:text>
+ LogRelFlow(("{%p} %s: enter </xsl:text>
+ <xsl:apply-templates select="@type" mode="logparamtext">
+ <xsl:with-param name="dir" select="'out'"/>
+ <xsl:with-param name="isref" select="'yes'"/>
+ </xsl:apply-templates>
+ <xsl:text>\n", this, </xsl:text>
+ <xsl:value-of select="concat('&quot;', $topclass, '::get', $attrbasename, '&quot;, ')"/>
+ <xsl:apply-templates select="@type" mode="logparamval">
+ <xsl:with-param name="dir" select="'out'"/>
+ <xsl:with-param name="isref" select="'yes'"/>
+ </xsl:apply-templates>
+ <xsl:text>));
+</xsl:text>
+ <xsl:if test="ancestor::interface[@wrap-gen-hook = 'yes']">
+ <xsl:text>
+ i_callHook(__FUNCTION__);</xsl:text>
+ </xsl:if>
+<xsl:text>
+ // Clear error info, to make in-process calls behave the same as
+ // cross-apartment calls or out-of-process calls.
+ VirtualBoxBase::clearError();
+
+ HRESULT hrc;
+
+ try
+ {
+ CheckComArgOutPointerValidThrow(a</xsl:text>
+ <xsl:value-of select="$attrbasename"/>
+ <xsl:text>);
+ </xsl:text>
+ <xsl:apply-templates select="@type" mode="paramvalconversion2tmpvar">
+ <xsl:with-param name="dir" select="'out'"/>
+ </xsl:apply-templates>
+ <xsl:if test="$attrbasename != 'MidlDoesNotLikeEmptyInterfaces'">
+ <xsl:text>
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+ </xsl:text>
+ <xsl:value-of select="translate(concat('VBOXAPI_', $dtracetopclass, '_GET_', $dtraceattrname, '_ENTER('), $G_lowerCase, $G_upperCase)"/>
+ <xsl:text>this);
+#endif</xsl:text>
+ </xsl:if>
+ <xsl:text>
+ </xsl:text>
+ <xsl:choose>
+ <xsl:when test="$limitedAutoCaller = 'true'">
+ <xsl:text>AutoLimitedCaller</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>AutoCaller</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> autoCaller(this);
+ hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+</xsl:text>
+ <xsl:value-of select="concat(' hrc = get', $attrbasename, '(')"/>
+ <xsl:variable name="passAutoCaller">
+ <xsl:call-template name="checkoption">
+ <xsl:with-param name="optionlist" select="@wrap-hint-server"/>
+ <xsl:with-param name="option" select="'passcaller'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:if test="$passAutoCaller = 'true'">
+ <xsl:text>autoCaller, </xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@type" mode="paramvalconversionusingtmp">
+ <xsl:with-param name="dir" select="'out'"/>
+ </xsl:apply-templates>
+ <xsl:text>);
+ }</xsl:text>
+ <xsl:if test="$attrbasename != 'MidlDoesNotLikeEmptyInterfaces'">
+ <xsl:text>
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+ </xsl:text>
+ <xsl:value-of select="translate(concat('VBOXAPI_', $dtracetopclass, '_GET_', $dtraceattrname, '_RETURN('), $G_lowerCase, $G_upperCase)"/>
+ <xsl:text>this, hrc, 0 /*normal*/,</xsl:text>
+ <xsl:call-template name="emitDTraceParamValue">
+ <xsl:with-param name="dir">out</xsl:with-param>
+ </xsl:call-template>
+ <xsl:text>);
+#endif</xsl:text>
+ </xsl:if>
+ <xsl:text>
+ }
+ catch (HRESULT hrc2)
+ {
+ hrc = hrc2;</xsl:text>
+ <xsl:if test="$attrbasename != 'MidlDoesNotLikeEmptyInterfaces'">
+ <xsl:text>
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+ </xsl:text>
+ <xsl:value-of select="translate(concat('VBOXAPI_', $dtracetopclass, '_GET_', $dtraceattrname, '_RETURN('), $G_lowerCase, $G_upperCase)"/>
+ <xsl:text>this, hrc, 1 /*hrc exception*/,</xsl:text>
+ <xsl:call-template name="emitDTraceParamValNoTmp-DirNotIn"/>
+ <xsl:text>);
+#endif</xsl:text>
+ </xsl:if>
+ <xsl:text>
+ }
+ catch (...)
+ {
+ hrc = VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS);</xsl:text>
+ <xsl:if test="$attrbasename != 'MidlDoesNotLikeEmptyInterfaces'">
+ <xsl:text>
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+ </xsl:text>
+ <xsl:value-of select="translate(concat('VBOXAPI_', $dtracetopclass, '_GET_', $dtraceattrname, '_RETURN('), $G_lowerCase, $G_upperCase)"/>
+ <xsl:text>this, hrc, 9 /*unhandled exception*/,</xsl:text>
+ <xsl:call-template name="emitDTraceParamValNoTmp-DirNotIn"/>
+ <xsl:text>);
+#endif</xsl:text>
+ </xsl:if>
+ <xsl:text>
+ }
+
+ LogRelFlow(("{%p} %s: leave </xsl:text>
+ <xsl:apply-templates select="@type" mode="logparamtext">
+ <xsl:with-param name="dir" select="'out'"/>
+ <xsl:with-param name="isref" select="''"/>
+ </xsl:apply-templates>
+ <xsl:text> hrc=%Rhrc\n", this, </xsl:text>
+ <xsl:value-of select="concat('&quot;', $topclass, '::get', $dtraceattrname, '&quot;, ')"/>
+ <xsl:apply-templates select="@type" mode="logparamval">
+ <xsl:with-param name="dir" select="'out'"/>
+ <xsl:with-param name="isref" select="''"/>
+ </xsl:apply-templates>
+ <xsl:text>, hrc));
+ return hrc;</xsl:text>
+ <xsl:if test="$attrbasename = 'MidlDoesNotLikeEmptyInterfaces'">
+ <xsl:text>
+#else /* dummy attribute */
+ NOREF(aMidlDoesNotLikeEmptyInterfaces);
+ return E_FAIL;
+#endif /* dummy attribute */</xsl:text>
+ </xsl:if>
+ <xsl:text>
+}
+</xsl:text>
+ <xsl:if test="not(@readonly) or @readonly!='yes'">
+ <xsl:text>
+</xsl:text>
+ <xsl:value-of select="concat('STDMETHODIMP ', $topclass, 'Wrap::COMSETTER(', $attrbasename, ')(')"/>
+ <xsl:call-template name="emitPublicParameter">
+ <xsl:with-param name="dir">in</xsl:with-param>
+ </xsl:call-template>
+ <!-- @todo check in parameters if possible -->
+ <xsl:text>)
+{
+ LogRelFlow(("{%p} %s: enter </xsl:text>
+ <xsl:apply-templates select="@type" mode="logparamtext">
+ <xsl:with-param name="dir" select="'in'"/>
+ <xsl:with-param name="isref" select="''"/>
+ </xsl:apply-templates>
+ <xsl:text>\n", this, </xsl:text>
+ <xsl:value-of select="concat('&quot;', $topclass, '::set', $attrbasename, '&quot;, ')"/>
+ <xsl:apply-templates select="@type" mode="logparamval">
+ <xsl:with-param name="dir" select="'in'"/>
+ <xsl:with-param name="isref" select="''"/>
+ </xsl:apply-templates>
+ <xsl:text>));
+</xsl:text>
+ <xsl:if test="ancestor::interface[@wrap-gen-hook = 'yes']">
+ <xsl:text>
+ i_callHook(__FUNCTION__);</xsl:text>
+ </xsl:if>
+<xsl:text>
+ // Clear error info, to make in-process calls behave the same as
+ // cross-apartment calls or out-of-process calls.
+ VirtualBoxBase::clearError();
+
+ HRESULT hrc;
+
+ try
+ {
+ </xsl:text>
+ <xsl:apply-templates select="@type" mode="paramvalconversion2tmpvar">
+ <xsl:with-param name="dir" select="'in'"/>
+ </xsl:apply-templates>
+ <xsl:text>
+
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+ </xsl:text>
+ <xsl:value-of select="translate(concat('VBOXAPI_', $topclass, '_SET_', $dtraceattrname, '_ENTER('), $G_lowerCase, $G_upperCase)"/>
+ <xsl:text>this, </xsl:text>
+ <xsl:call-template name="emitDTraceParamValue">
+ <xsl:with-param name="dir">in</xsl:with-param>
+ </xsl:call-template>
+ <xsl:text>);
+#endif
+ </xsl:text>
+ <xsl:choose>
+ <xsl:when test="$limitedAutoCaller = 'true'">
+ <xsl:text>AutoLimitedCaller</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>AutoCaller</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> autoCaller(this);
+ hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+</xsl:text>
+ <xsl:value-of select="concat(' hrc = set', $attrbasename, '(')"/>
+ <xsl:if test="$passAutoCaller = 'true'">
+ <xsl:text>autoCaller, </xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@type" mode="paramvalconversionusingtmp">
+ <xsl:with-param name="dir" select="'in'"/>
+ </xsl:apply-templates>
+ <xsl:text>);
+ }
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+ </xsl:text>
+ <xsl:value-of select="translate(concat('VBOXAPI_', $dtracetopclass, '_SET_', $dtraceattrname, '_RETURN('), $G_lowerCase, $G_upperCase)"/>
+ <xsl:text>this, hrc, 0 /*normal*/,</xsl:text>
+ <xsl:call-template name="emitDTraceParamValue">
+ <xsl:with-param name="dir">in</xsl:with-param>
+ </xsl:call-template>
+ <xsl:text>);
+#endif
+ }
+ catch (HRESULT hrc2)
+ {
+ hrc = hrc2;
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+ </xsl:text>
+ <xsl:value-of select="translate(concat('VBOXAPI_', $dtracetopclass, '_SET_', $dtraceattrname, '_RETURN('), $G_lowerCase, $G_upperCase)"/>
+ <xsl:text>this, hrc, 1 /*hrc exception*/,</xsl:text>
+ <xsl:call-template name="emitDTraceParamValNoTmp-DirIn"/>
+ <xsl:text>);
+#endif
+ }
+ catch (...)
+ {
+ hrc = VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS);
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+ </xsl:text>
+ <xsl:value-of select="translate(concat('VBOXAPI_', $dtracetopclass, '_SET_', $dtraceattrname, '_RETURN('), $G_lowerCase, $G_upperCase)"/>
+ <xsl:text>this, hrc, 9 /*unhandled exception*/,</xsl:text>
+ <xsl:call-template name="emitDTraceParamValNoTmp-DirIn"/>
+ <xsl:text>);
+#endif
+ }
+
+ LogRelFlow(("{%p} %s: leave hrc=%Rhrc\n", this, </xsl:text>
+ <xsl:value-of select="concat('&quot;', $topclass, '::set', $attrbasename, '&quot;, ')"/>
+ <xsl:text>hrc));
+ return hrc;
+}
+</xsl:text>
+ </xsl:if>
+
+ <xsl:call-template name="emitTargetEnd">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:call-template>
+
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ Emit DTrace probes for the given attribute.
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template match="attribute" mode="dtrace-probes">
+ <xsl:param name="topclass"/>
+ <xsl:param name="dtracetopclass"/>
+ <xsl:param name="target"/>
+
+ <xsl:variable name="dtraceattrname">
+ <xsl:choose>
+ <xsl:when test="@dtracename">
+ <xsl:value-of select="@dtracename"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- attrbasename -->
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:if test="@name != 'midlDoesNotLikeEmptyInterfaces'">
+ <xsl:text> probe </xsl:text>
+ <!-- <xsl:value-of select="concat($dtracetopclass, '__get__', $dtraceattrname, '__enter(struct ', $topclass)"/> -->
+ <xsl:value-of select="concat($dtracetopclass, '__get__', $dtraceattrname, '__enter(void')"/>
+ <xsl:text> *a_pThis);
+ probe </xsl:text>
+ <!-- <xsl:value-of select="concat($dtracetopclass, '__get__', $dtraceattrname, '__return(struct ', $topclass, ' *a_pThis')"/> -->
+ <xsl:value-of select="concat($dtracetopclass, '__get__', $dtraceattrname, '__return(void *a_pThis')"/>
+ <xsl:text>, uint32_t a_hrc, int32_t enmWhy, </xsl:text>
+ <xsl:apply-templates select="@type" mode="dtraceparamdecl">
+ <xsl:with-param name="dir">out</xsl:with-param>
+ </xsl:apply-templates>
+ <xsl:text>);
+</xsl:text>
+ </xsl:if>
+ <xsl:if test="(not(@readonly) or @readonly!='yes') and @name != 'midlDoesNotLikeEmptyInterfaces'">
+ <xsl:text> probe </xsl:text>
+ <!-- <xsl:value-of select="concat($topclass, '__set__', $dtraceattrname, '__enter(struct ', $topclass, ' *a_pThis, ')"/>-->
+ <xsl:value-of select="concat($topclass, '__set__', $dtraceattrname, '__enter(void *a_pThis, ')"/>
+ <xsl:apply-templates select="@type" mode="dtraceparamdecl">
+ <xsl:with-param name="dir" select="'in'"/>
+ </xsl:apply-templates>
+ <xsl:text>);
+ probe </xsl:text>
+ <!-- <xsl:value-of select="concat($dtracetopclass, '__set__', $dtraceattrname, '__return(struct ', $topclass, ' *a_pThis')"/> -->
+ <xsl:value-of select="concat($dtracetopclass, '__set__', $dtraceattrname, '__return(void *a_pThis')"/>
+ <xsl:text>, uint32_t a_hrc, int32_t enmWhy, </xsl:text>
+ <xsl:apply-templates select="@type" mode="dtraceparamdecl">
+ <xsl:with-param name="dir">in</xsl:with-param>
+ </xsl:apply-templates>
+ <xsl:text>);
+</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ Emit all attributes of an interface (current node).
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template name="emitAttributes">
+ <xsl:param name="topclass"/>
+ <xsl:param name="dtracetopclass"/>
+ <xsl:param name="pmode"/>
+
+ <xsl:variable name="name" select="@name"/>
+ <!-- first recurse to emit all base interfaces -->
+ <xsl:variable name="extends" select="@extends"/>
+ <xsl:if test="$extends and not($extends='$unknown') and not($extends='$errorinfo')">
+ <xsl:for-each select="key('G_keyInterfacesByName', $extends)">
+ <xsl:call-template name="emitAttributes">
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="pmode" select="$pmode"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="$pmode='code'">
+ <xsl:text>//
+</xsl:text>
+ <xsl:value-of select="concat('// ', $name, ' properties')"/>
+ <xsl:text>
+//
+
+</xsl:text>
+ </xsl:when>
+ <xsl:when test="$pmode != 'dtrace-probes'">
+ <xsl:value-of select="concat($G_sNewLine, ' /** @name ', translate(substring($pmode, 1, 1), $G_lowerCase, $G_upperCase), substring($pmode,2), ' ', $name, ' properties', $G_sNewLine)"/>
+ <xsl:text> * @{ */
+</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ <xsl:choose>
+ <xsl:when test="$pmode='public'">
+ <xsl:apply-templates select="./attribute | ./if" mode="public">
+ <xsl:with-param name="emitmode" select="'attribute'"/>
+ </xsl:apply-templates>
+ <xsl:variable name="reservedAttributes" select="@reservedAttributes"/>
+ <xsl:if test="$reservedAttributes > 0">
+ <!-- tricky way to do a "for" loop without recursion -->
+ <xsl:for-each select="(//*)[position() &lt;= $reservedAttributes]">
+ <xsl:text> STDMETHOD(COMGETTER(InternalAndReservedAttribute</xsl:text>
+ <xsl:value-of select="concat(position(), $name)"/>
+ <xsl:text>))(ULONG *aReserved);&#x0A;</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:when>
+ <xsl:when test="$pmode='wrapped'">
+ <xsl:apply-templates select="./attribute | ./if" mode="wrapped">
+ <xsl:with-param name="emitmode" select="'attribute'"/>
+ </xsl:apply-templates>
+ </xsl:when>
+ <xsl:when test="$pmode='code'">
+ <xsl:apply-templates select="./attribute | ./if" mode="code">
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ <xsl:with-param name="emitmode" select="'attribute'"/>
+ </xsl:apply-templates>
+ <xsl:variable name="reservedAttributes" select="@reservedAttributes"/>
+ <xsl:if test="$reservedAttributes > 0">
+ <!-- tricky way to do a "for" loop without recursion -->
+ <xsl:for-each select="(//*)[position() &lt;= $reservedAttributes]">
+ <xsl:value-of select="concat('STDMETHODIMP ', $topclass, 'Wrap::COMGETTER(InternalAndReservedAttribute', position(), $name, ')(ULONG *aReserved)&#x0A;')"/>
+ <xsl:text>{
+ NOREF(aReserved);
+ return E_NOTIMPL;
+}
+
+</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:when>
+ <xsl:when test="$pmode = 'dtrace-probes'">
+ <xsl:apply-templates select="./attribute | ./if" mode="dtrace-probes">
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ <xsl:with-param name="emitmode" select="'attribute'"/>
+ </xsl:apply-templates>
+ </xsl:when>
+ <xsl:otherwise><xsl:message terminate="yes">Otherwise oops in emitAttributes</xsl:message></xsl:otherwise>
+ </xsl:choose>
+
+ <!-- close doxygen @name -->
+ <xsl:if test="($pmode != 'code') and ($pmode != 'dtrace-probes')" >
+ <xsl:text> /** @} */
+</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+<xsl:template name="emitTargetBegin">
+ <xsl:param name="target"/>
+
+ <xsl:choose>
+ <xsl:when test="$target = ''"/>
+ <xsl:when test="$target = 'xpidl'">
+ <xsl:text>#ifdef VBOX_WITH_XPCOM
+</xsl:text>
+ </xsl:when>
+ <xsl:when test="$target = 'midl'">
+ <xsl:text>#ifndef VBOX_WITH_XPCOM
+</xsl:text>
+ </xsl:when>
+ <xsl:otherwise><xsl:message terminate="yes">Otherwise oops in emitTargetBegin: target=<xsl:value-of select="$target"/></xsl:message></xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="emitTargetEnd">
+ <xsl:param name="target"/>
+
+ <xsl:choose>
+ <xsl:when test="$target = ''"/>
+ <xsl:when test="$target = 'xpidl'">
+ <xsl:text>#endif /* VBOX_WITH_XPCOM */
+</xsl:text>
+ </xsl:when>
+ <xsl:when test="$target = 'midl'">
+ <xsl:text>#endif /* !VBOX_WITH_XPCOM */
+</xsl:text>
+ </xsl:when>
+ <xsl:otherwise><xsl:message terminate="yes">Otherwise oops in emitTargetEnd target=<xsl:value-of select="$target"/></xsl:message></xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ emit method
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="method" mode="public">
+ <xsl:param name="target"/>
+
+ <xsl:call-template name="emitTargetBegin">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:call-template>
+
+ <xsl:variable name="methodindent">
+ <xsl:call-template name="tospace">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:text> STDMETHOD(</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>)(</xsl:text>
+ <xsl:for-each select="param">
+ <xsl:call-template name="emitPublicParameter">
+ <xsl:with-param name="dir" select="@dir"/>
+ </xsl:call-template>
+ <xsl:if test="not(position()=last())">
+ <xsl:text>,
+ </xsl:text>
+ <xsl:value-of select="$methodindent"/>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>);
+</xsl:text>
+
+ <xsl:call-template name="emitTargetEnd">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="method" mode="wrapped">
+ <xsl:param name="target"/>
+
+ <xsl:call-template name="emitTargetBegin">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:call-template>
+
+ <xsl:variable name="methodindent">
+ <xsl:call-template name="tospace">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:text> virtual HRESULT </xsl:text>
+ <xsl:call-template name="uncapitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>(</xsl:text>
+ <xsl:variable name="passAutoCaller">
+ <xsl:call-template name="checkoption">
+ <xsl:with-param name="optionlist" select="@wrap-hint-server"/>
+ <xsl:with-param name="option" select="'passcaller'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:if test="$passAutoCaller = 'true'">
+ <xsl:text>AutoCaller &amp;aAutoCaller</xsl:text>
+ <xsl:if test="count(param) > 0">
+ <xsl:text>,
+ </xsl:text>
+ <xsl:value-of select="$methodindent"/>
+ </xsl:if>
+ </xsl:if>
+ <xsl:for-each select="param">
+ <xsl:apply-templates select="@type" mode="wrapped">
+ <xsl:with-param name="dir" select="@dir"/>
+ </xsl:apply-templates>
+ <xsl:if test="not(position()=last())">
+ <xsl:text>,
+ </xsl:text>
+ <xsl:value-of select="$methodindent"/>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>) = 0;
+</xsl:text>
+
+ <xsl:call-template name="emitTargetEnd">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="method" mode="code">
+ <xsl:param name="topclass"/>
+ <xsl:param name="dtracetopclass"/>
+ <xsl:param name="target"/>
+
+ <xsl:call-template name="emitTargetBegin">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:call-template>
+
+ <xsl:variable name="methodindent">
+ <xsl:call-template name="tospace">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="methodclassindent">
+ <xsl:call-template name="tospace">
+ <xsl:with-param name="str" select="concat($topclass, @name)"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="methodbasename">
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="limitedAutoCaller">
+ <xsl:call-template name="checkoption">
+ <xsl:with-param name="optionlist" select="@wrap-hint-server"/>
+ <xsl:with-param name="option" select="'limitedcaller'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="dtracemethodname">
+ <xsl:choose>
+ <xsl:when test="@dtracename">
+ <xsl:value-of select="@dtracename"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="dtracenamehack"> <!-- Ugly hack to deal with Session::assignMachine and similar. -->
+ <xsl:if test="name(..) = 'if'">
+ <xsl:value-of select="concat('__', ../@target)"/>
+ </xsl:if>
+ </xsl:variable>
+
+ <xsl:value-of select="concat('STDMETHODIMP ', $topclass, 'Wrap::', $methodbasename, '(')"/>
+ <xsl:for-each select="param">
+ <xsl:call-template name="emitPublicParameter">
+ <xsl:with-param name="dir" select="@dir"/>
+ </xsl:call-template>
+ <xsl:if test="not(position()=last())">
+ <xsl:text>,
+ </xsl:text>
+ <xsl:value-of select="$methodclassindent"/>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>)
+{
+ LogRelFlow(("{%p} %s: enter</xsl:text>
+ <xsl:for-each select="param">
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates select="@type" mode="logparamtext">
+ <xsl:with-param name="dir" select="@dir"/>
+ <xsl:with-param name="isref" select="'yes'"/>
+ </xsl:apply-templates>
+ </xsl:for-each>
+ <xsl:text>\n", this</xsl:text>
+ <xsl:value-of select="concat(', &quot;', $topclass, '::', @name, '&quot;')"/>
+ <xsl:for-each select="param">
+ <xsl:text>, </xsl:text>
+ <xsl:apply-templates select="@type" mode="logparamval">
+ <xsl:with-param name="dir" select="@dir"/>
+ <xsl:with-param name="isref" select="'yes'"/>
+ </xsl:apply-templates>
+ </xsl:for-each>
+ <xsl:text>));
+</xsl:text>
+ <xsl:if test="ancestor::interface[@wrap-gen-hook = 'yes']">
+ <xsl:text>
+ i_callHook(__FUNCTION__);</xsl:text>
+ </xsl:if>
+<xsl:text>
+ // Clear error info, to make in-process calls behave the same as
+ // cross-apartment calls or out-of-process calls.
+ VirtualBoxBase::clearError();
+
+ HRESULT hrc;
+
+ try
+ {
+</xsl:text>
+ <!-- @todo check in parameters if possible -->
+ <xsl:for-each select="param">
+ <xsl:if test="@dir!='in'">
+ <xsl:text> CheckComArgOutPointerValidThrow(a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>);
+</xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+<xsl:text>
+</xsl:text>
+ <xsl:for-each select="param">
+ <xsl:text>
+ </xsl:text>
+ <xsl:apply-templates select="@type" mode="paramvalconversion2tmpvar">
+ <xsl:with-param name="dir" select="@dir"/>
+ </xsl:apply-templates>
+ </xsl:for-each>
+ <xsl:text>
+
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+ </xsl:text>
+ <xsl:value-of select="translate(concat('VBOXAPI_', $dtracetopclass, '_', $dtracemethodname, substring($dtracenamehack, 2), '_ENTER('), $G_lowerCase, $G_upperCase)"/>
+ <xsl:text>this</xsl:text>
+ <xsl:for-each select="param[@dir='in']">
+ <xsl:text>, </xsl:text>
+ <xsl:call-template name="emitDTraceParamValue">
+ <xsl:with-param name="dir" select="@dir"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ <xsl:text>);
+#endif
+ </xsl:text>
+ <xsl:choose>
+ <xsl:when test="$limitedAutoCaller = 'true'">
+ <xsl:text>AutoLimitedCaller</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>AutoCaller</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> autoCaller(this);
+ hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+</xsl:text>
+ <xsl:value-of select="concat(' hrc = ', @name, '(')"/>
+ <xsl:variable name="passAutoCaller">
+ <xsl:call-template name="checkoption">
+ <xsl:with-param name="optionlist" select="@wrap-hint-server"/>
+ <xsl:with-param name="option" select="'passcaller'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:if test="$passAutoCaller = 'true'">
+ <xsl:text>autoCaller</xsl:text>
+ <xsl:if test="count(param) > 0">
+ <xsl:text>,
+ </xsl:text>
+ <xsl:value-of select="$methodindent"/>
+ </xsl:if>
+ </xsl:if>
+ <xsl:for-each select="param">
+ <xsl:apply-templates select="@type" mode="paramvalconversionusingtmp">
+ <xsl:with-param name="dir" select="@dir"/>
+ </xsl:apply-templates>
+ <xsl:if test="not(position()=last())">
+ <xsl:text>,
+ </xsl:text>
+ <xsl:value-of select="$methodindent"/>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>);
+ }
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+ </xsl:text>
+ <xsl:value-of select="translate(concat('VBOXAPI_', $dtracetopclass, '_', $dtracemethodname, substring($dtracenamehack, 2), '_RETURN('), $G_lowerCase, $G_upperCase)"/>
+ <xsl:text>this, hrc, 0 /*normal*/</xsl:text>
+ <xsl:for-each select="param">
+ <xsl:text>, </xsl:text>
+ <xsl:call-template name="emitDTraceParamValue">
+ <xsl:with-param name="dir" select="@dir"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ <xsl:text>);
+#endif
+ }
+ catch (HRESULT hrc2)
+ {
+ hrc = hrc2;
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+ </xsl:text>
+ <xsl:value-of select="translate(concat('VBOXAPI_', $dtracetopclass, '_', $dtracemethodname, substring($dtracenamehack, 2), '_RETURN('), $G_lowerCase, $G_upperCase)"/>
+ <xsl:text>this, hrc, 1 /*hrc exception*/</xsl:text>
+ <xsl:for-each select="param">
+ <xsl:text>, </xsl:text>
+ <xsl:call-template name="emitDTraceParamValNoTmp"/>
+ </xsl:for-each>
+ <xsl:text>);
+#endif
+ }
+ catch (...)
+ {
+ hrc = VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS);
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+ </xsl:text>
+ <xsl:value-of select="translate(concat('VBOXAPI_', $dtracetopclass, '_', $dtracemethodname, substring($dtracenamehack, 2), '_RETURN('), $G_lowerCase, $G_upperCase)"/>
+ <xsl:text>this, hrc, 9 /*unhandled exception*/</xsl:text>
+ <xsl:for-each select="param">
+ <xsl:text>, </xsl:text>
+ <xsl:call-template name="emitDTraceParamValNoTmp"/>
+ </xsl:for-each>
+ <xsl:text>);
+#endif
+ }
+
+ LogRelFlow(("{%p} %s: leave</xsl:text>
+ <xsl:for-each select="param">
+ <xsl:if test="@dir!='in'">
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates select="@type" mode="logparamtext">
+ <xsl:with-param name="dir" select="@dir"/>
+ <xsl:with-param name="isref" select="''"/>
+ </xsl:apply-templates>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text> hrc=%Rhrc\n", this</xsl:text>
+ <xsl:value-of select="concat(', &quot;', $topclass, '::', @name, '&quot;')"/>
+ <xsl:for-each select="param">
+ <xsl:if test="@dir!='in'">
+ <xsl:text>, </xsl:text>
+ <xsl:apply-templates select="@type" mode="logparamval">
+ <xsl:with-param name="dir" select="@dir"/>
+ <xsl:with-param name="isref" select="''"/>
+ </xsl:apply-templates>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>, hrc));
+ return hrc;
+}
+</xsl:text>
+
+ <xsl:call-template name="emitTargetEnd">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:call-template>
+
+ <xsl:text>
+</xsl:text>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ Emits the DTrace probes for a method.
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template match="method" mode="dtrace-probes">
+ <xsl:param name="topclass"/>
+ <xsl:param name="dtracetopclass"/>
+ <xsl:param name="target"/>
+
+ <xsl:variable name="dtracemethodname">
+ <xsl:choose>
+ <xsl:when test="@dtracename">
+ <xsl:value-of select="@dtracename"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="dtracenamehack"> <!-- Ugly hack to deal with Session::assignMachine and similar. -->
+ <xsl:if test="name(..) = 'if'">
+ <xsl:value-of select="concat('__', ../@target)"/>
+ </xsl:if>
+ </xsl:variable>
+
+ <xsl:text> probe </xsl:text>
+ <!-- <xsl:value-of select="concat($dtracetopclass, '__', $dtracemethodname, $dtracenamehack, '__enter(struct ', $dtracetopclass, ' *a_pThis')"/> -->
+ <xsl:value-of select="concat($dtracetopclass, '__', $dtracemethodname, $dtracenamehack, '__enter(void *a_pThis')"/>
+ <xsl:for-each select="param[@dir='in']">
+ <xsl:text>, </xsl:text>
+ <xsl:apply-templates select="@type" mode="dtraceparamdecl">
+ <xsl:with-param name="dir" select="'@dir'"/>
+ </xsl:apply-templates>
+ </xsl:for-each>
+ <xsl:text>);
+ probe </xsl:text>
+ <!-- <xsl:value-of select="concat($dtracetopclass, '__', $dtracemethodname, '__return(struct ', $dtracetopclass, ' *a_pThis')"/> -->
+ <xsl:value-of select="concat($dtracetopclass, '__', $dtracemethodname, $dtracenamehack, '__return(void *a_pThis')"/>
+ <xsl:text>, uint32_t a_hrc, int32_t enmWhy</xsl:text>
+ <xsl:for-each select="param">
+ <xsl:text>, </xsl:text>
+ <xsl:apply-templates select="@type" mode="dtraceparamdecl">
+ <xsl:with-param name="dir" select="'@dir'"/>
+ </xsl:apply-templates>
+ </xsl:for-each>
+ <xsl:text>);
+</xsl:text>
+
+</xsl:template>
+
+
+<xsl:template name="emitIf">
+ <xsl:param name="passmode"/>
+ <xsl:param name="target"/>
+ <xsl:param name="topclass"/>
+ <xsl:param name="emitmode"/>
+ <xsl:param name="dtracetopclass"/>
+
+ <xsl:if test="($target = 'xpidl') or ($target = 'midl')">
+ <xsl:choose>
+ <xsl:when test="$passmode='public'">
+ <xsl:choose>
+ <xsl:when test="$emitmode='method'">
+ <xsl:apply-templates select="method" mode="public">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:apply-templates>
+ </xsl:when>
+ <xsl:when test="$emitmode='attribute'">
+ <xsl:apply-templates select="attribute" mode="public">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:apply-templates>
+ </xsl:when>
+ <xsl:otherwise/>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="$passmode='wrapped'">
+ <xsl:choose>
+ <xsl:when test="$emitmode='method'">
+ <xsl:apply-templates select="method" mode="wrapped">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:apply-templates>
+ </xsl:when>
+ <xsl:when test="$emitmode='attribute'">
+ <xsl:apply-templates select="attribute" mode="wrapped">
+ <xsl:with-param name="target" select="$target"/>
+ </xsl:apply-templates>
+ </xsl:when>
+ <xsl:otherwise/>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="$passmode='code'">
+ <xsl:choose>
+ <xsl:when test="$emitmode='method'">
+ <xsl:apply-templates select="method" mode="code">
+ <xsl:with-param name="target" select="$target"/>
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ </xsl:apply-templates>
+ </xsl:when>
+ <xsl:when test="$emitmode='attribute'">
+ <xsl:apply-templates select="attribute" mode="code">
+ <xsl:with-param name="target" select="$target"/>
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ </xsl:apply-templates>
+ </xsl:when>
+ <xsl:otherwise/>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="$passmode = 'dtrace-probes'">
+ <xsl:choose>
+ <xsl:when test="$emitmode = 'method'">
+ <xsl:apply-templates select="method" mode="dtrace-probes">
+ <xsl:with-param name="target" select="$target"/>
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ </xsl:apply-templates>
+ </xsl:when>
+ <xsl:when test="$emitmode = 'attribute'">
+ <xsl:apply-templates select="attribute" mode="dtrace-probes">
+ <xsl:with-param name="target" select="$target"/>
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ </xsl:apply-templates>
+ </xsl:when>
+ <xsl:otherwise/>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise/>
+ </xsl:choose>
+ </xsl:if>
+</xsl:template>
+
+<xsl:template match="if" mode="public">
+ <xsl:param name="emitmode"/>
+
+ <xsl:call-template name="emitIf">
+ <xsl:with-param name="passmode" select="'public'"/>
+ <xsl:with-param name="target" select="@target"/>
+ <xsl:with-param name="emitmode" select="$emitmode"/>
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="if" mode="wrapped">
+ <xsl:param name="emitmode"/>
+
+ <xsl:call-template name="emitIf">
+ <xsl:with-param name="passmode" select="'wrapped'"/>
+ <xsl:with-param name="target" select="@target"/>
+ <xsl:with-param name="emitmode" select="$emitmode"/>
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="if" mode="code">
+ <xsl:param name="topclass"/>
+ <xsl:param name="emitmode"/>
+ <xsl:param name="dtracetopclass"/>
+
+ <xsl:call-template name="emitIf">
+ <xsl:with-param name="passmode" select="'code'"/>
+ <xsl:with-param name="target" select="@target"/>
+ <xsl:with-param name="emitmode" select="$emitmode"/>
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="if" mode="dtrace-probes">
+ <xsl:param name="topclass"/>
+ <xsl:param name="emitmode"/>
+ <xsl:param name="dtracetopclass"/>
+
+ <xsl:call-template name="emitIf">
+ <xsl:with-param name="passmode" select="'dtrace-probes'"/>
+ <xsl:with-param name="target" select="@target"/>
+ <xsl:with-param name="emitmode" select="$emitmode"/>
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ </xsl:call-template>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ emit all methods of the current interface
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template name="emitMethods">
+ <xsl:param name="topclass"/>
+ <xsl:param name="pmode"/>
+ <xsl:param name="dtracetopclass"/>
+
+ <xsl:variable name="name" select="@name"/>
+ <!-- first recurse to emit all base interfaces -->
+ <xsl:variable name="extends" select="@extends"/>
+ <xsl:if test="$extends and not($extends='$unknown') and not($extends='$errorinfo')">
+ <xsl:for-each select="key('G_keyInterfacesByName', $extends)">
+ <xsl:call-template name="emitMethods">
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="pmode" select="$pmode"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:if>
+
+ <xsl:choose>
+ <xsl:when test="$pmode='code'">
+ <xsl:text>//
+</xsl:text>
+ <xsl:value-of select="concat('// ', $name, ' methods')"/>
+ <xsl:text>
+//
+
+</xsl:text>
+ </xsl:when>
+ <xsl:when test="$pmode='dtrace-probes'"/>
+ <xsl:otherwise>
+ <xsl:value-of select="concat($G_sNewLine, ' /** @name ', translate(substring($pmode, 1, 1), $G_lowerCase, $G_upperCase), substring($pmode,2), ' ', $name, ' methods', $G_sNewLine)"/>
+ <xsl:text> * @{ */
+</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:choose>
+ <xsl:when test="$pmode='public'">
+ <xsl:apply-templates select="./method | ./if" mode="public">
+ <xsl:with-param name="emitmode" select="'method'"/>
+ </xsl:apply-templates>
+ <xsl:variable name="reservedMethods" select="@reservedMethods"/>
+ <xsl:if test="$reservedMethods > 0">
+ <!-- tricky way to do a "for" loop without recursion -->
+ <xsl:for-each select="(//*)[position() &lt;= $reservedMethods]">
+ <xsl:text> STDMETHOD(InternalAndReservedMethod</xsl:text>
+ <xsl:value-of select="concat(position(), $name)"/>
+ <xsl:text>)();&#x0A;</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:when>
+ <xsl:when test="$pmode='wrapped'">
+ <xsl:apply-templates select="./method | ./if" mode="wrapped">
+ <xsl:with-param name="emitmode" select="'method'"/>
+ </xsl:apply-templates>
+ </xsl:when>
+ <xsl:when test="$pmode='code'">
+ <xsl:apply-templates select="./method | ./if" mode="code">
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ <xsl:with-param name="emitmode" select="'method'"/>
+ </xsl:apply-templates>
+ <xsl:variable name="reservedMethods" select="@reservedMethods"/>
+ <xsl:if test="$reservedMethods > 0">
+ <!-- tricky way to do a "for" loop without recursion -->
+ <xsl:for-each select="(//*)[position() &lt;= $reservedMethods]">
+ <xsl:value-of select="concat('STDMETHODIMP ', $topclass, 'Wrap::InternalAndReservedMethod', position(), $name, '()&#x0A;')"/>
+ <xsl:text>{
+ return E_NOTIMPL;
+}
+
+</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:when>
+ <xsl:when test="$pmode='dtrace-probes'">
+ <xsl:apply-templates select="./method | ./if" mode="dtrace-probes">
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ <xsl:with-param name="emitmode" select="'method'"/>
+ </xsl:apply-templates>
+ </xsl:when>
+ <xsl:otherwise/>
+ </xsl:choose>
+
+ <!-- close doxygen @name -->
+ <xsl:if test="($pmode != 'code') and ($pmode != 'dtrace-probes')" >
+ <xsl:text> /** @} */
+</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ emit all attributes and methods declarations of the current interface
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template name="emitInterfaceDecls">
+ <xsl:param name="pmode"/>
+
+ <!-- attributes -->
+ <xsl:call-template name="emitAttributes">
+ <xsl:with-param name="pmode" select="$pmode"/>
+ </xsl:call-template>
+
+ <!-- methods -->
+ <xsl:call-template name="emitMethods">
+ <xsl:with-param name="pmode" select="$pmode"/>
+ </xsl:call-template>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ emit auxiliary method declarations of the current interface
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template name="emitAuxMethodDecls">
+ <!-- currently nothing, maybe later some generic FinalConstruct/... helper declaration for ComObjPtr -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ emit the header file of the current interface
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template name="emitHeader">
+ <xsl:param name="addinterfaces"/>
+
+ <xsl:variable name="filename" select="concat(substring(@name, 2), 'Wrap.h')"/>
+
+ <xsl:apply-templates select="." mode="startfile">
+ <xsl:with-param name="file" select="$filename"/>
+ </xsl:apply-templates>
+ <xsl:call-template name="fileheader">
+ <xsl:with-param name="name" select="$filename"/>
+ <xsl:with-param name="class" select="substring(@name, 2)"/>
+ <xsl:with-param name="type" select="'header'"/>
+ </xsl:call-template>
+ <xsl:apply-templates select="." mode="classheader">
+ <xsl:with-param name="addinterfaces" select="$addinterfaces"/>
+ </xsl:apply-templates>
+
+ <!-- interface attributes/methods (public) -->
+ <xsl:call-template name="emitInterfaceDecls">
+ <xsl:with-param name="pmode" select="'public'"/>
+ </xsl:call-template>
+
+ <xsl:for-each select="exsl:node-set($addinterfaces)/token">
+ <!-- This is super tricky, as the for-each switches to the node set,
+ which means the normal document isn't available any more. We get
+ the data we need, uses a for-each to switch document and then a
+ key() to look up the interface by name. -->
+ <xsl:variable name="addifname">
+ <xsl:value-of select="string(.)"/>
+ </xsl:variable>
+ <xsl:for-each select="$G_root">
+ <xsl:for-each select="key('G_keyInterfacesByName', $addifname)">
+ <xsl:call-template name="emitInterfaceDecls">
+ <xsl:with-param name="pmode" select="'public'"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:for-each>
+ </xsl:for-each>
+
+ <!-- auxiliary methods (public) -->
+ <xsl:call-template name="emitAuxMethodDecls"/>
+
+ <!-- switch to private -->
+ <xsl:text>
+private:</xsl:text>
+
+ <!-- wrapped interface attributes/methods (private) -->
+ <xsl:call-template name="emitInterfaceDecls">
+ <xsl:with-param name="pmode" select="'wrapped'"/>
+ </xsl:call-template>
+
+ <xsl:for-each select="exsl:node-set($addinterfaces)/token">
+ <!-- This is super tricky, as the for-each switches to the node set,
+ which means the normal document isn't available any more. We get
+ the data we need, uses a for-each to switch document and then a
+ key() to look up the interface by name. -->
+ <xsl:variable name="addifname">
+ <xsl:value-of select="string(.)"/>
+ </xsl:variable>
+ <xsl:for-each select="$G_root">
+ <xsl:for-each select="key('G_keyInterfacesByName', $addifname)">
+ <xsl:call-template name="emitInterfaceDecls">
+ <xsl:with-param name="pmode" select="'wrapped'"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:for-each>
+ </xsl:for-each>
+
+ <xsl:apply-templates select="." mode="classfooter"/>
+ <xsl:apply-templates select="." mode="endfile">
+ <xsl:with-param name="file" select="$filename"/>
+ </xsl:apply-templates>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ emit all attributes and methods definitions (pmode=code) or probes (pmode=dtrace-probes) of the current interface
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template name="emitInterfaceDefs">
+ <xsl:param name="addinterfaces"/>
+ <xsl:param name="pmode" select="'code'"/>
+
+ <xsl:variable name="topclass" select="substring(@name, 2)"/>
+ <xsl:variable name="dtracetopclass">
+ <xsl:choose>
+ <xsl:when test="@dtracename"><xsl:value-of select="@dtracename"/></xsl:when>
+ <xsl:otherwise><xsl:value-of select="$topclass"/></xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:if test="$pmode = 'code'">
+ <xsl:value-of select="concat('DEFINE_EMPTY_CTOR_DTOR(', $topclass, 'Wrap)', $G_sNewLine, $G_sNewLine)"/>
+ </xsl:if>
+
+ <!-- attributes -->
+ <xsl:call-template name="emitAttributes">
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ <xsl:with-param name="pmode" select="$pmode"/>
+ </xsl:call-template>
+
+ <xsl:for-each select="exsl:node-set($addinterfaces)/token">
+ <!-- This is super tricky, as the for-each switches to the node set,
+ which means the normal document isn't available any more. We get
+ the data we need, uses a for-each to switch document and then a
+ key() to look up the interface by name. -->
+ <xsl:variable name="addifname">
+ <xsl:value-of select="string(.)"/>
+ </xsl:variable>
+ <xsl:for-each select="$G_root">
+ <xsl:for-each select="key('G_keyInterfacesByName', $addifname)">
+ <xsl:call-template name="emitAttributes">
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ <xsl:with-param name="pmode" select="$pmode"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:for-each>
+ </xsl:for-each>
+
+ <!-- methods -->
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+ <xsl:call-template name="emitMethods">
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ <xsl:with-param name="pmode" select="$pmode"/>
+ </xsl:call-template>
+
+ <xsl:for-each select="exsl:node-set($addinterfaces)/token">
+ <!-- This is super tricky, as the for-each switches to the node set,
+ which means the normal document isn't available any more. We get
+ the data we need, uses a for-each to switch document and then a
+ key() to look up the interface by name. -->
+ <xsl:variable name="addifname">
+ <xsl:value-of select="string(.)"/>
+ </xsl:variable>
+ <xsl:for-each select="$G_root">
+ <xsl:for-each select="key('G_keyInterfacesByName', $addifname)">
+ <xsl:call-template name="emitMethods">
+ <xsl:with-param name="topclass" select="$topclass"/>
+ <xsl:with-param name="dtracetopclass" select="$dtracetopclass"/>
+ <xsl:with-param name="pmode" select="$pmode"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:for-each>
+ </xsl:for-each>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ emit auxiliary method declarations of the current interface
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template name="emitAuxMethodDefs">
+ <xsl:param name="pmode" select="'code'"/>
+ <!-- currently nothing, maybe later some generic FinalConstruct/... implementation -->
+</xsl:template>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ emit the code file of the current interface
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template name="emitCode">
+ <xsl:param name="addinterfaces"/>
+
+ <xsl:variable name="filename" select="concat(substring(@name, 2), 'Wrap.cpp')"/>
+
+ <xsl:apply-templates select="." mode="startfile">
+ <xsl:with-param name="file" select="$filename"/>
+ </xsl:apply-templates>
+ <xsl:call-template name="fileheader">
+ <xsl:with-param name="name" select="$filename"/>
+ <xsl:with-param name="class" select="substring(@name, 2)"/>
+ <xsl:with-param name="type" select="'code'"/>
+ </xsl:call-template>
+ <xsl:apply-templates select="." mode="codeheader">
+ <xsl:with-param name="addinterfaces" select="$addinterfaces"/>
+ </xsl:apply-templates>
+
+ <!-- interface attributes/methods (public) -->
+ <xsl:call-template name="emitInterfaceDefs">
+ <xsl:with-param name="addinterfaces" select="$addinterfaces"/>
+ </xsl:call-template>
+
+ <!-- auxiliary methods (public) -->
+ <xsl:call-template name="emitAuxMethodDefs"/>
+
+ <xsl:apply-templates select="." mode="codefooter">
+ <xsl:with-param name="addinterfaces" select="$addinterfaces"/>
+ </xsl:apply-templates>
+ <xsl:apply-templates select="." mode="endfile">
+ <xsl:with-param name="file" select="$filename"/>
+ </xsl:apply-templates>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ emit the DTrace probes for the current interface
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template name="emitDTraceProbes">
+ <xsl:param name="addinterfaces"/>
+
+ <!-- interface attributes/methods (public) -->
+ <xsl:call-template name="emitInterfaceDefs">
+ <xsl:with-param name="addinterfaces" select="$addinterfaces"/>
+ <xsl:with-param name="pmode">dtrace-probes</xsl:with-param>
+ </xsl:call-template>
+
+ <!-- auxiliary methods (public) -->
+ <xsl:call-template name="emitAuxMethodDefs">
+ <xsl:with-param name="pmode">dtrace-probes</xsl:with-param>
+ </xsl:call-template>
+
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ wildcard match, ignore everything which has no explicit match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="*"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ ignore all if tags except those for XPIDL or MIDL target
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="if">
+ <xsl:if test="(@target = 'xpidl') or (@target = 'midl')">
+ <xsl:apply-templates/>
+ </xsl:if>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ interface match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="interface">
+ <xsl:if test="not(@internal='yes') and not(@autogen='VBoxEvent') and not(@supportsErrorInfo='no')">
+ <xsl:call-template name="emitInterface"/>
+ </xsl:if>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ application match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="application">
+ <xsl:apply-templates/>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ library match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="library">
+ <xsl:apply-templates/>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ root match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="/idl">
+ <xsl:choose>
+ <xsl:when test="$generating = 'headers'">
+ <xsl:apply-templates/>
+ </xsl:when>
+ <xsl:when test="$generating = 'sources'">
+ <xsl:apply-templates/>
+ </xsl:when>
+ <xsl:when test="$generating = 'dtrace-probes'">
+ <xsl:apply-templates/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ Unknown string parameter value: generating='<xsl:value-of select="$generating"/>'
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+</xsl:stylesheet>
+<!-- vi: set tabstop=4 shiftwidth=4 expandtab: -->
diff --git a/src/VBox/Main/idl/comimpl.xsl b/src/VBox/Main/idl/comimpl.xsl
new file mode 100644
index 00000000..53a2403d
--- /dev/null
+++ b/src/VBox/Main/idl/comimpl.xsl
@@ -0,0 +1,1182 @@
+<xsl:stylesheet version = '1.0'
+ xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
+ xmlns:vbox="http://www.virtualbox.org/"
+ xmlns:exsl="http://exslt.org/common"
+ extension-element-prefixes="exsl">
+
+<!--
+ comimpl.xsl:
+ XSLT stylesheet that generates COM C++ classes implementing
+ interfaces described in VirtualBox.xidl.
+ For now we generate implementation for events, as they are
+ rather trivial container classes for their read-only attributes.
+ Further extension to other interfaces is possible and anticipated.
+-->
+<!--
+ Copyright (C) 2010-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:output
+ method="text"
+ version="1.0"
+ encoding="utf-8"
+ indent="no"/>
+
+<xsl:include href="typemap-shared.inc.xsl" />
+
+<!-- $G_kind contains what kind of COM class implementation we generate -->
+<xsl:variable name="G_xsltFilename" select="'autogen.xsl'" />
+<xsl:variable name="G_generateBstrVariants" select="'no'" />
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ Keys for more efficiently looking up of types.
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:key name="G_keyEnumsByName" match="//enum[@name]" use="@name"/>
+<xsl:key name="G_keyInterfacesByName" match="//interface[@name]" use="@name"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template name="fileheader">
+ <xsl:param name="name" />
+ <xsl:text>/** @file </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>
+ * DO NOT EDIT! This is a generated file.
+ * Generated from: src/VBox/Main/idl/VirtualBox.xidl (VirtualBox's interface definitions in XML)
+ * Generator: src/VBox/Main/idl/comimpl.xsl
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see &lt;https://www.gnu.org/licenses&gt;.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+</xsl:text>
+</xsl:template>
+
+<xsl:template name="genComEntry">
+ <xsl:param name="name" />
+ <xsl:variable name="extends">
+ <xsl:value-of select="key('G_keyInterfacesByName', $name)/@extends" />
+ </xsl:variable>
+
+ <xsl:value-of select="concat(' COM_INTERFACE_ENTRY(', $name, ')&#10;')" />
+ <xsl:choose>
+ <xsl:when test="$extends='$unknown'">
+ <!-- Reached base -->
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $extends)) > 0">
+ <xsl:call-template name="genComEntry">
+ <xsl:with-param name="name" select="$extends" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('No idea how to process it: ', $extends)" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="typeIdl2Back">
+ <xsl:param name="type" />
+ <xsl:param name="safearray" />
+ <xsl:param name="param" />
+ <xsl:param name="dir" />
+ <xsl:param name="mod" />
+ <xsl:param name="utf8str" select="'no'" />
+
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:variable name="elemtype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$type" />
+ <xsl:with-param name="safearray" select="''" />
+ <xsl:with-param name="dir" select="'in'" />
+ <xsl:with-param name="utf8str" select="$utf8str" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="$param and ($dir='in')">
+ <xsl:value-of select="concat('ComSafeArrayIn(',$elemtype,',', $param,')')"/>
+ </xsl:when>
+ <xsl:when test="$param and ($dir='out')">
+ <xsl:value-of select="concat('ComSafeArrayOut(',$elemtype,', ', $param, ')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('com::SafeArray&lt;',$elemtype,'&gt;')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="$mod='ptr'">
+ <xsl:value-of select="'BYTE *'" />
+ </xsl:when>
+ <xsl:when test="(($type='wstring') or ($type='uuid'))">
+ <xsl:choose>
+ <xsl:when test="$param and ($dir='in') and ($utf8str!='yes')">
+ <xsl:value-of select="'CBSTR'"/>
+ </xsl:when>
+ <xsl:when test="$param and ($dir='in') and ($utf8str='yes')">
+ <xsl:value-of select="'const Utf8Str &amp;'"/>
+ </xsl:when>
+ <xsl:when test="$param and ($dir='out')">
+ <xsl:value-of select="'BSTR'"/>
+ </xsl:when>
+ <xsl:when test="$param and ($dir='out') and ($utf8str='yes')">
+ <xsl:value-of select="'Utf8Str &amp;'"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="'Utf8Str'"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyEnumsByName', $type)) > 0">
+ <xsl:value-of select="concat($type,'_T')"/>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:choose>
+ <xsl:when test="$param">
+ <xsl:value-of select="concat($type,' *')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('ComPtr&lt;',$type,'&gt;')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="$type='boolean'">
+ <xsl:value-of select="'BOOL'" />
+ </xsl:when>
+ <xsl:when test="$type='octet'">
+ <xsl:value-of select="'BYTE'" />
+ </xsl:when>
+ <xsl:when test="$type='unsigned short'">
+ <xsl:value-of select="'USHORT'" />
+ </xsl:when>
+ <xsl:when test="$type='short'">
+ <xsl:value-of select="'SHORT'" />
+ </xsl:when>
+ <xsl:when test="$type='unsigned long'">
+ <xsl:value-of select="'ULONG'" />
+ </xsl:when>
+ <xsl:when test="$type='long'">
+ <xsl:value-of select="'LONG'" />
+ </xsl:when>
+ <xsl:when test="$type='unsigned long long'">
+ <xsl:value-of select="'ULONG64'" />
+ </xsl:when>
+ <xsl:when test="$type='long long'">
+ <xsl:value-of select="'LONG64'" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('Unhandled type: ', $type)" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="$dir='out'">
+ <xsl:value-of select="' *'"/>
+ </xsl:if>
+ <xsl:if test="$param and not($param='_')">
+ <xsl:value-of select="concat(' ', $param)"/>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+
+</xsl:template>
+
+<!-- Checks if interface $name has any string attributes, producing '1' for each string attrib.
+ No output if no string attributes -->
+<xsl:template name="hasStringAttributes">
+ <xsl:param name="name" />
+
+ <!-- Recurse into parent interfaces: -->
+ <xsl:variable name="extends">
+ <xsl:value-of select="key('G_keyInterfacesByName', $name)/@extends" />
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="$extends='IEvent'">
+ </xsl:when>
+ <xsl:when test="$extends='IReusableEvent'">
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $extends)) > 0">
+ <xsl:call-template name="hasStringAttributes">
+ <xsl:with-param name="name" select="$extends" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('No idea how to process it: ', $name)" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <!-- Find immediate string and uuid attributes and output '1' for each one: -->
+ <xsl:for-each select="key('G_keyInterfacesByName', $name)/attribute[(@type = 'wstring' or @type = 'uuid') and (@name != 'midlDoesNotLikeEmptyInterfaces')]">
+ <xsl:text>1</xsl:text>
+ </xsl:for-each>
+</xsl:template>
+
+
+<xsl:template name="genSetParam">
+ <xsl:param name="member"/>
+ <xsl:param name="param"/>
+ <xsl:param name="type"/>
+ <xsl:param name="safearray"/>
+ <xsl:param name="internal"/>
+
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:variable name="elemtype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$type" />
+ <xsl:with-param name="safearray" select="''" />
+ <xsl:with-param name="dir" select="'in'" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:text> SafeArray&lt;</xsl:text><xsl:value-of select="$elemtype"/>
+ <xsl:text>&gt; aArr(ComSafeArrayInArg(</xsl:text><xsl:value-of select="$param"/><xsl:text>));&#10;</xsl:text>
+ <xsl:text> return </xsl:text><xsl:value-of select="$member"/><xsl:text>.initFrom(aArr);&#10;</xsl:text>
+ </xsl:when>
+ <xsl:when test="($type='wstring') or ($type='uuid')">
+ <xsl:text> return </xsl:text><xsl:value-of select="$member"/><xsl:text>.assignEx(</xsl:text>
+ <xsl:value-of select="$param"/><xsl:text>);&#10;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat(' ', $member, ' = ', $param, ';&#10;')"/>
+ <xsl:if test="$internal!='yes'">
+ <xsl:text> return S_OK;&#10;</xsl:text>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="genRetParam">
+ <xsl:param name="member"/>
+ <xsl:param name="param"/>
+ <xsl:param name="type"/>
+ <xsl:param name="safearray"/>
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:variable name="elemtype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="$type" />
+ <xsl:with-param name="safearray" select="''" />
+ <xsl:with-param name="dir" select="'in'" />
+ </xsl:call-template>
+ </xsl:variable>
+<!-- @todo String arrays probably needs work, I doubt we're generating sensible code for them at the moment. -->
+ <xsl:text> SafeArray&lt;</xsl:text><xsl:value-of select="$elemtype"/><xsl:text>&gt; result;&#10;</xsl:text>
+ <xsl:text> HRESULT hrc = </xsl:text><xsl:value-of select="$member"/><xsl:text>.cloneTo(result);&#10;</xsl:text>
+ <xsl:text> if (SUCCEEDED(hrc))&#10;</xsl:text>
+ <xsl:text> result.detachTo(ComSafeArrayOutArg(</xsl:text><xsl:value-of select="$param"/><xsl:text>));&#10;</xsl:text>
+ <xsl:text> return hrc;&#10;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="($type='wstring') or ($type = 'uuid')">
+ <xsl:text> return </xsl:text><xsl:value-of select="$member"/><xsl:text>.cloneToEx(</xsl:text>
+ <xsl:value-of select="$param"/><xsl:text>);&#10;</xsl:text>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:text> return </xsl:text><xsl:value-of select="$member"/><xsl:text>.queryInterfaceTo(</xsl:text>
+ <xsl:value-of select="$param"/><xsl:text>);&#10;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text> *</xsl:text><xsl:value-of select="$param"/><xsl:text> = </xsl:text>
+ <xsl:value-of select="$member"/><xsl:text>;&#10;</xsl:text>
+ <xsl:text> return S_OK;&#10;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="genFormalParams">
+ <xsl:param name="name" />
+ <xsl:param name="utf8str" />
+ <xsl:variable name="extends">
+ <xsl:value-of select="key('G_keyInterfacesByName', $name)/@extends" />
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$extends='IEvent'">
+ </xsl:when>
+ <xsl:when test="$extends='IReusableEvent'">
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $extends)) > 0">
+ <xsl:call-template name="genFormalParams">
+ <xsl:with-param name="name" select="$extends" />
+ <xsl:with-param name="utf8str" select="$utf8str" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('No idea how to process it: ', $name)" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:for-each select="key('G_keyInterfacesByName', $name)/attribute[@name != 'midlDoesNotLikeEmptyInterfaces']">
+ <xsl:variable name="aName" select="concat('a_',@name)"/>
+ <xsl:variable name="aTypeName">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="param" select="$aName" />
+ <xsl:with-param name="dir" select="'in'" />
+ <xsl:with-param name="mod" select="@mod" />
+ <xsl:with-param name="utf8str" select="$utf8str" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(', ',$aTypeName)"/>
+ </xsl:for-each>
+</xsl:template>
+
+<xsl:template name="genCallParams">
+ <xsl:param name="name" />
+ <xsl:variable name="extends">
+ <xsl:value-of select="key('G_keyInterfacesByName', $name)/@extends" />
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$extends='IEvent'">
+ </xsl:when>
+ <xsl:when test="$extends='IReusableEvent'">
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $extends)) > 0">
+ <xsl:call-template name="genCallParams">
+ <xsl:with-param name="name" select="$extends" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('No idea how to process it: ', $name)" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:for-each select="key('G_keyInterfacesByName', $name)/attribute[@name != 'midlDoesNotLikeEmptyInterfaces']">
+ <xsl:variable name="aName" select="concat('a_',@name)"/>
+ <xsl:choose>
+ <xsl:when test="@safearray='yes'">
+ <xsl:value-of select="concat(', ComSafeArrayInArg(',$aName,')')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat(', ',$aName)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+</xsl:template>
+
+<xsl:template name="genAttrInitCode">
+ <xsl:param name="name" />
+ <xsl:param name="obj" />
+ <xsl:variable name="extends">
+ <xsl:value-of select="key('G_keyInterfacesByName', $name)/@extends" />
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$extends='IEvent'">
+ </xsl:when>
+ <xsl:when test="$extends='IReusableEvent'">
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $extends)) > 0">
+ <xsl:call-template name="genAttrInitCode">
+ <xsl:with-param name="name" select="$extends" />
+ <xsl:with-param name="obj" select="$obj" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('No idea how to process it: ', $name)" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:for-each select="key('G_keyInterfacesByName', $name)/attribute[@name != 'midlDoesNotLikeEmptyInterfaces']">
+ <xsl:variable name="aName" select="concat('a_',@name)"/>
+ <xsl:variable name="aTypeName">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="param" select="$aName" />
+ <xsl:with-param name="dir" select="'in'" />
+ <xsl:with-param name="mod" select="@mod" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="aType">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="param" select="'_'" />
+ <xsl:with-param name="dir" select="'in'" />
+ <xsl:with-param name="mod" select="@mod" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="@safearray='yes'">
+ <xsl:variable name="elemtype">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="''" />
+ <xsl:with-param name="dir" select="'in'" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:text> if (SUCCEEDED(hrc))&#10;</xsl:text>
+ <xsl:value-of select="concat(' hrc = ',$obj, '->set_', @name, '(ComSafeArrayInArg(a_', @name, '));&#10;')"/>
+ </xsl:when>
+ <xsl:when test="(@type='wstring') or (@type = 'uuid')">
+ <xsl:text> if (SUCCEEDED(hrc))&#10;</xsl:text>
+ <xsl:value-of select="concat(' hrc = ',$obj, '->set_', @name, '(',$aName, ');&#10;')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat(' ',$obj, '->set_', @name, '(',$aName, ');&#10;')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+</xsl:template>
+
+<xsl:template name="genImplList">
+ <xsl:param name="impl" />
+ <xsl:param name="name" />
+ <xsl:param name="depth" />
+ <xsl:param name="parents" />
+
+ <xsl:variable name="extends">
+ <xsl:value-of select="key('G_keyInterfacesByName', $name)/@extends" />
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$name='IEvent'">
+ <xsl:value-of select=" '#ifdef VBOX_WITH_XPCOM&#10;'" />
+ <xsl:value-of select="concat('NS_DECL_CLASSINFO(', $impl, ')&#10;')" />
+ <xsl:value-of select="concat('NS_IMPL_THREADSAFE_ISUPPORTS',$depth,'_CI(', $impl, $parents, ', IEvent)&#10;')" />
+ <xsl:value-of select=" '#endif&#10;&#10;'"/>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $extends)) > 0">
+ <xsl:call-template name="genImplList">
+ <xsl:with-param name="impl" select="$impl" />
+ <xsl:with-param name="name" select="$extends" />
+ <xsl:with-param name="depth" select="$depth+1" />
+ <xsl:with-param name="parents" select="concat($parents, ', ', $name)" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('No idea how to process it: ', $name)" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="genAttrCode">
+ <xsl:param name="name" />
+ <xsl:param name="depth" />
+ <xsl:param name="parents" />
+ <xsl:param name="fGenBstr" />
+
+ <xsl:variable name="extends">
+ <xsl:value-of select="key('G_keyInterfacesByName', $name)/@extends" />
+ </xsl:variable>
+
+ <xsl:for-each select="key('G_keyInterfacesByName', $name)/attribute">
+ <xsl:variable name="mName">
+ <xsl:value-of select="concat('m_', @name)" />
+ </xsl:variable>
+ <xsl:variable name="mType">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="dir" select="'in'" />
+ <xsl:with-param name="mod" select="@mod" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="pName">
+ <xsl:value-of select="concat('a_', @name)" />
+ </xsl:variable>
+ <xsl:variable name="pTypeNameOut">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="param" select="$pName" />
+ <xsl:with-param name="dir" select="'out'" />
+ <xsl:with-param name="mod" select="@mod" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="pTypeNameIn">
+ <xsl:call-template name="typeIdl2Back">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="param" select="$pName" />
+ <xsl:with-param name="dir" select="'in'" />
+ <xsl:with-param name="mod" select="@mod" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="capsName">
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:value-of select=" '&#10;'" />
+ <xsl:value-of select="concat(' // attribute ', @name,'&#10;')" />
+ <xsl:value-of select=" 'private:&#10;'" />
+ <xsl:value-of select="concat(' ', $mType, ' ', $mName,';&#10;')" />
+ <xsl:value-of select=" 'public:&#10;'" />
+ <xsl:value-of select="concat(' STDMETHOD(COMGETTER(', $capsName,'))(',$pTypeNameOut,') RT_OVERRIDE&#10; {&#10;')" />
+ <xsl:call-template name="genRetParam">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="member" select="$mName" />
+ <xsl:with-param name="param" select="$pName" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ <xsl:value-of select=" ' }&#10;'" />
+
+ <xsl:if test="not(@readonly='yes')">
+ <xsl:value-of select="concat(' STDMETHOD(COMSETTER(', $capsName,'))(',$pTypeNameIn,') RT_OVERRIDE&#10; {&#10;')" />
+ <xsl:call-template name="genSetParam">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="member" select="$mName" />
+ <xsl:with-param name="param" select="$pName" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="internal" select="'no'" />
+ </xsl:call-template>
+ <xsl:value-of select=" ' }&#10;'" />
+ </xsl:if>
+
+ <xsl:text> // purely internal setter&#10;</xsl:text>
+ <xsl:if test="(@type='wstring') or (@type = 'uuid')">
+ <xsl:text> inline HRESULT set_</xsl:text><xsl:value-of select="@name"/><xsl:text>(const Utf8Str &amp;a_rString)&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> return </xsl:text><xsl:value-of select="$mName"/><xsl:text>.assignEx(a_rString);&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ </xsl:if>
+ <xsl:if test="not((@type='wstring') or (@type = 'uuid')) or $fGenBstr">
+ <xsl:text> inline </xsl:text>
+ <xsl:choose>
+ <xsl:when test="(@safearray='yes') or (@type='wstring') or (@type = 'uuid')">
+ <xsl:text>HRESULT</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>void</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:value-of select="concat(' set_', @name,'(',$pTypeNameIn, ')&#10; {&#10;')" />
+ <xsl:call-template name="genSetParam">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="member" select="$mName" />
+ <xsl:with-param name="param" select="$pName" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="internal" select="'yes'" />
+ </xsl:call-template>
+ <xsl:value-of select=" ' }&#10;'" />
+ </xsl:if>
+
+ </xsl:for-each>
+
+ <xsl:choose>
+ <xsl:when test="$extends='IEvent'">
+ <xsl:value-of select=" ' // skipping IEvent attributes &#10;'" />
+ </xsl:when>
+ <xsl:when test="$extends='IReusableEvent'">
+ <xsl:value-of select=" ' // skipping IReusableEvent attributes &#10;'" />
+ </xsl:when>
+ <xsl:when test="$extends='IVetoEvent'">
+ <xsl:value-of select=" ' // skipping IVetoEvent attributes &#10;'" />
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $extends)) > 0">
+ <xsl:call-template name="genAttrCode">
+ <xsl:with-param name="name" select="$extends" />
+ <xsl:with-param name="depth" select="$depth+1" />
+ <xsl:with-param name="parents" select="concat($parents, ', ', @name)" />
+ <xsl:with-param name="fGenBstr" select="$fGenBstr" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('No idea how to process it: ', $extends)" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="genReinitFunction">
+ <xsl:param name="name"/>
+ <xsl:param name="evname"/>
+ <xsl:param name="ifname"/>
+ <xsl:param name="implName"/>
+ <xsl:param name="utf8str"/>
+
+ <xsl:value-of select="concat('DECLHIDDEN(HRESULT) Reinit', $evname, '(IEvent *aEvent')"/>
+ <xsl:call-template name="genFormalParams">
+ <xsl:with-param name="name" select="$ifname" />
+ <xsl:with-param name="utf8str" select="'no'" />
+ </xsl:call-template>
+ <xsl:text>)&#10;</xsl:text>
+ <xsl:text>{&#10;</xsl:text>
+ <xsl:text> </xsl:text><xsl:value-of select="$implName"/><xsl:text> *pEvtImpl = dynamic_cast&lt;</xsl:text>
+ <xsl:value-of select="$implName"/><xsl:text> *&gt;(aEvent);&#10;</xsl:text>
+ <xsl:text> if (pEvtImpl)&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> pEvtImpl->Reuse();&#10;</xsl:text>
+ <xsl:text> HRESULT hrc = S_OK;&#10;</xsl:text>
+ <xsl:call-template name="genAttrInitCode">
+ <xsl:with-param name="name" select="$name" />
+ <xsl:with-param name="obj" select="'pEvtImpl'" />
+ </xsl:call-template>
+ <xsl:text> return hrc;&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:text> return E_INVALIDARG;&#10;</xsl:text>
+ <xsl:text>}&#10;</xsl:text>
+ <xsl:text>&#10;</xsl:text>
+</xsl:template>
+
+<xsl:template name="genCreateFunction">
+ <xsl:param name="name"/>
+ <xsl:param name="evname"/>
+ <xsl:param name="ifname"/>
+ <xsl:param name="implName"/>
+ <xsl:param name="waitable"/>
+ <xsl:param name="evid"/>
+ <xsl:param name="utf8str"/>
+
+ <xsl:value-of select="concat('DECLHIDDEN(HRESULT) Create', $evname, '(IEvent **aEvent, IEventSource *aSource')"/>
+ <xsl:call-template name="genFormalParams">
+ <xsl:with-param name="name" select="$ifname" />
+ <xsl:with-param name="utf8str" select="$utf8str" />
+ </xsl:call-template>
+ <xsl:text>)&#10;</xsl:text>
+ <xsl:text>{&#10;</xsl:text>
+ <xsl:text> ComObjPtr&lt;</xsl:text><xsl:value-of select="$implName"/><xsl:text>&gt; EvtObj;&#10;</xsl:text>
+ <xsl:text> HRESULT hrc = EvtObj.createObject();&#10;</xsl:text>
+ <xsl:text> if (SUCCEEDED(hrc))&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> hrc = EvtObj-&gt;init(aSource, VBoxEventType_</xsl:text><xsl:value-of select="$evid"/>
+ <xsl:text>, </xsl:text><xsl:value-of select="$waitable" /><xsl:text> /*waitable*/);&#10;</xsl:text>
+ <xsl:call-template name="genAttrInitCode">
+ <xsl:with-param name="name" select="$name" />
+ <xsl:with-param name="obj" select="'EvtObj'" />
+ </xsl:call-template>
+ <xsl:text> if (SUCCEEDED(hrc))&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> hrc = EvtObj.queryInterfaceTo(aEvent);&#10;</xsl:text>
+ <xsl:text> if (SUCCEEDED(hrc))&#10;</xsl:text>
+ <xsl:text> return hrc;&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:text> *aEvent = NULL;&#10;</xsl:text>
+ <xsl:text> return hrc;&#10;</xsl:text>
+ <xsl:text>}&#10;</xsl:text>
+ <xsl:text>&#10;</xsl:text>
+</xsl:template>
+
+<xsl:template name="genFireFunction">
+ <xsl:param name="evname"/>
+ <xsl:param name="ifname"/>
+ <xsl:param name="utf8str"/>
+
+ <xsl:value-of select="concat('DECLHIDDEN(HRESULT) Fire', $evname, '(IEventSource *aSource')"/>
+ <xsl:call-template name="genFormalParams">
+ <xsl:with-param name="name" select="$ifname" />
+ <xsl:with-param name="utf8str" select="$utf8str" />
+ </xsl:call-template>
+ <xsl:text>)&#10;</xsl:text>
+ <xsl:text>{&#10;</xsl:text>
+ <xsl:text> AssertReturn(aSource, E_INVALIDARG);&#10;</xsl:text>
+ <xsl:text> ComPtr&lt;IEvent&gt; ptrEvent;&#10;</xsl:text>
+ <xsl:text> HRESULT hrc = </xsl:text>
+ <xsl:value-of select="concat('Create', $evname, '(ptrEvent.asOutParam(), aSource')"/>
+ <xsl:call-template name="genCallParams">
+ <xsl:with-param name="name" select="$ifname" />
+ </xsl:call-template>
+ <xsl:text>);&#10;</xsl:text>
+ <xsl:text> if (SUCCEEDED(hrc))&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> BOOL fDeliveredIgnored = FALSE;&#10;</xsl:text>
+ <xsl:text> hrc = aSource-&gt;FireEvent(ptrEvent, /* do not wait for delivery */ 0, &amp;fDeliveredIgnored);&#10;</xsl:text>
+ <xsl:text> AssertComRC(hrc);&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:text> return hrc;&#10;</xsl:text>
+ <xsl:text>}&#10;</xsl:text>
+ <xsl:text>&#10;</xsl:text>
+</xsl:template>
+
+<xsl:template name="genEventImpl">
+ <xsl:param name="implName" />
+ <xsl:param name="isVeto" />
+ <xsl:param name="isReusable" />
+
+ <xsl:value-of select="concat('class ATL_NO_VTABLE ',$implName,
+ '&#10; : public VirtualBoxBase&#10; , VBOX_SCRIPTABLE_IMPL(',
+ @name, ')&#10;{&#10;')" />
+ <xsl:value-of select="'public:&#10;'" />
+ <xsl:value-of select="concat(' VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(', $implName, ', ', @name, ')&#10;')" />
+ <xsl:value-of select="concat(' DECLARE_NOT_AGGREGATABLE(', $implName, ')&#10;')" />
+ <xsl:value-of select=" ' DECLARE_PROTECT_FINAL_CONSTRUCT()&#10;'" />
+ <xsl:value-of select="concat(' BEGIN_COM_MAP(', $implName, ')&#10;')" />
+ <xsl:value-of select=" ' COM_INTERFACE_ENTRY(ISupportErrorInfo)&#10;'" />
+ <xsl:value-of select="concat(' COM_INTERFACE_ENTRY(', @name, ')&#10;')" />
+ <xsl:value-of select="concat(' COM_INTERFACE_ENTRY2(IDispatch, ', @name, ')&#10;')" />
+ <xsl:value-of select="concat(' VBOX_TWEAK_INTERFACE_ENTRY(', @name, ')&#10;')" />
+
+ <xsl:call-template name="genComEntry">
+ <xsl:with-param name="name" select="@name" />
+ </xsl:call-template>
+ <xsl:value-of select=" ' END_COM_MAP()&#10;'" />
+ <xsl:value-of select="concat(' ',$implName,'() { Log12((&quot;',$implName,' %p\n&quot;, this)); }&#10;')" />
+ <xsl:value-of select="concat(' virtual ~',$implName,'() { Log12((&quot;~',$implName,' %p\n&quot;, this)); uninit(); }&#10;')" />
+ <xsl:text><![CDATA[
+ HRESULT FinalConstruct()
+ {
+ BaseFinalConstruct();
+ return mEvent.createObject();
+ }
+ void FinalRelease()
+ {
+ uninit();
+ BaseFinalRelease();
+ }
+ STDMETHOD(COMGETTER(Type))(VBoxEventType_T *aType) RT_OVERRIDE
+ {
+ return mEvent->COMGETTER(Type)(aType);
+ }
+ STDMETHOD(COMGETTER(Source))(IEventSource * *aSource) RT_OVERRIDE
+ {
+ return mEvent->COMGETTER(Source)(aSource);
+ }
+ STDMETHOD(COMGETTER(Waitable))(BOOL *aWaitable) RT_OVERRIDE
+ {
+ return mEvent->COMGETTER(Waitable)(aWaitable);
+ }
+ STDMETHOD(SetProcessed)() RT_OVERRIDE
+ {
+ return mEvent->SetProcessed();
+ }
+ STDMETHOD(WaitProcessed)(LONG aTimeout, BOOL *aResult) RT_OVERRIDE
+ {
+ return mEvent->WaitProcessed(aTimeout, aResult);
+ }
+ void uninit()
+ {
+ if (!mEvent.isNull())
+ {
+ mEvent->uninit();
+ mEvent.setNull();
+ }
+ }
+]]></xsl:text>
+ <xsl:choose>
+ <xsl:when test="$isVeto='yes'">
+<xsl:text><![CDATA[
+ HRESULT init(IEventSource *aSource, VBoxEventType_T aType, BOOL aWaitable = TRUE)
+ {
+ NOREF(aWaitable);
+ return mEvent->init(aSource, aType);
+ }
+ STDMETHOD(AddVeto)(IN_BSTR aVeto) RT_OVERRIDE
+ {
+ return mEvent->AddVeto(aVeto);
+ }
+ STDMETHOD(IsVetoed)(BOOL *aResult) RT_OVERRIDE
+ {
+ return mEvent->IsVetoed(aResult);
+ }
+ STDMETHOD(GetVetos)(ComSafeArrayOut(BSTR, aVetos)) RT_OVERRIDE
+ {
+ return mEvent->GetVetos(ComSafeArrayOutArg(aVetos));
+ }
+ STDMETHOD(AddApproval)(IN_BSTR aReason) RT_OVERRIDE
+ {
+ return mEvent->AddApproval(aReason);
+ }
+ STDMETHOD(IsApproved)(BOOL *aResult) RT_OVERRIDE
+ {
+ return mEvent->IsApproved(aResult);
+ }
+ STDMETHOD(GetApprovals)(ComSafeArrayOut(BSTR, aReasons)) RT_OVERRIDE
+ {
+ return mEvent->GetApprovals(ComSafeArrayOutArg(aReasons));
+ }
+private:
+ ComObjPtr<VBoxVetoEvent> mEvent;
+]]></xsl:text>
+ </xsl:when>
+ <xsl:when test="$isReusable='yes'">
+ <xsl:text>
+<![CDATA[
+ HRESULT init(IEventSource *aSource, VBoxEventType_T aType, BOOL aWaitable = FALSE)
+ {
+ mGeneration = 1;
+ return mEvent->init(aSource, aType, aWaitable);
+ }
+ STDMETHOD(COMGETTER(Generation))(ULONG *aGeneration) RT_OVERRIDE
+ {
+ *aGeneration = mGeneration;
+ return S_OK;
+ }
+ STDMETHOD(Reuse)() RT_OVERRIDE
+ {
+ ASMAtomicIncU32((volatile uint32_t *)&mGeneration);
+ return S_OK;
+ }
+private:
+ volatile ULONG mGeneration;
+ ComObjPtr<VBoxEvent> mEvent;
+]]></xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+<xsl:text><![CDATA[
+ HRESULT init(IEventSource *aSource, VBoxEventType_T aType, BOOL aWaitable)
+ {
+ return mEvent->init(aSource, aType, aWaitable);
+ }
+private:
+ ComObjPtr<VBoxEvent> mEvent;
+]]></xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <!-- Before we generate attribute code, we check and make sure there are attributes here. -->
+ <xsl:if test="count(attribute) = 0 and @name != 'INATNetworkAlterEvent'">
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg">error: <xsl:value-of select="@name"/> has no attributes</xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <xsl:call-template name="genAttrCode">
+ <xsl:with-param name="name" select="@name" />
+ <xsl:with-param name="fGenBstr" select="($G_generateBstrVariants = 'yes') or (contains(@autogenflags, 'BSTR'))" />
+ </xsl:call-template>
+ <xsl:value-of select="'};&#10;'" />
+
+
+ <xsl:call-template name="genImplList">
+ <xsl:with-param name="impl" select="$implName" />
+ <xsl:with-param name="name" select="@name" />
+ <xsl:with-param name="depth" select="'1'" />
+ <xsl:with-param name="parents" select="''" />
+ </xsl:call-template>
+
+ <!-- Associate public functions. -->
+ <xsl:variable name="evname">
+ <xsl:value-of select="substring(@name, 2)" />
+ </xsl:variable>
+ <xsl:variable name="evid">
+ <xsl:value-of select="concat('On', substring(@name, 2, string-length(@name)-6))" />
+ </xsl:variable>
+ <xsl:variable name="ifname">
+ <xsl:value-of select="@name" />
+ </xsl:variable>
+ <xsl:variable name="waitable">
+ <xsl:choose>
+ <xsl:when test="@waitable='yes'">
+ <xsl:value-of select="'TRUE'"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="'FALSE'"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="hasStringAttribs">
+ <xsl:call-template name="hasStringAttributes">
+ <xsl:with-param name="name" select="@name"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <!-- Generate ReinitXxxxEvent functions if reusable. -->
+ <xsl:if test="$isReusable='yes'">
+ <xsl:call-template name="genReinitFunction">
+ <xsl:with-param name="name" select="@name"/>
+ <xsl:with-param name="evname" select="$evname"/>
+ <xsl:with-param name="ifname" select="$ifname"/>
+ <xsl:with-param name="implName" select="$implName"/>
+ <xsl:with-param name="utf8str" select="'yes'"/>
+ </xsl:call-template>
+
+ <xsl:if test="($hasStringAttribs != '') and (($G_generateBstrVariants = 'yes') or (contains(@autogenflags, 'BSTR')))">
+ <xsl:call-template name="genReinitFunction">
+ <xsl:with-param name="name" select="@name"/>
+ <xsl:with-param name="evname" select="$evname"/>
+ <xsl:with-param name="ifname" select="$ifname"/>
+ <xsl:with-param name="implName" select="$implName"/>
+ <xsl:with-param name="utf8str" select="'no'"/>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:if>
+
+ <!-- Generate the CreateXxxxEvent function. -->
+ <xsl:call-template name="genCreateFunction">
+ <xsl:with-param name="name" select="@name"/>
+ <xsl:with-param name="evname" select="$evname"/>
+ <xsl:with-param name="ifname" select="$ifname"/>
+ <xsl:with-param name="implName" select="$implName"/>
+ <xsl:with-param name="waitable" select="$waitable"/>
+ <xsl:with-param name="evid" select="$evid"/>
+ <xsl:with-param name="utf8str" select="'yes'"/>
+ </xsl:call-template>
+
+ <xsl:if test="($hasStringAttribs != '') and (($G_generateBstrVariants = 'yes') or (contains(@autogenflags, 'BSTR')))">
+ <xsl:call-template name="genCreateFunction">
+ <xsl:with-param name="name" select="@name"/>
+ <xsl:with-param name="evname" select="$evname"/>
+ <xsl:with-param name="ifname" select="$ifname"/>
+ <xsl:with-param name="implName" select="$implName"/>
+ <xsl:with-param name="waitable" select="$waitable"/>
+ <xsl:with-param name="evid" select="$evid"/>
+ <xsl:with-param name="utf8str" select="'no'"/>
+ </xsl:call-template>
+ </xsl:if>
+
+ <!-- Generate the FireXxxxEvent function. -->
+ <xsl:call-template name="genFireFunction">
+ <xsl:with-param name="evname" select="$evname"/>
+ <xsl:with-param name="ifname" select="$ifname"/>
+ <xsl:with-param name="utf8str" select="'yes'"/>
+ </xsl:call-template>
+
+ <xsl:if test="($hasStringAttribs != '') and (($G_generateBstrVariants = 'yes') or (contains(@autogenflags, 'BSTR')))">
+ <xsl:call-template name="genFireFunction">
+ <xsl:with-param name="evname" select="$evname"/>
+ <xsl:with-param name="ifname" select="$ifname"/>
+ <xsl:with-param name="utf8str" select="'no'"/>
+ </xsl:call-template>
+ </xsl:if>
+
+</xsl:template>
+
+
+<!--
+ Produces VBoxEvents.cpp
+ -->
+<xsl:template name="genCommonEventCode">
+ <xsl:call-template name="fileheader">
+ <xsl:with-param name="name" select="'VBoxEvents.cpp'" />
+ </xsl:call-template>
+
+<xsl:text><![CDATA[
+#define LOG_GROUP LOG_GROUP_MAIN_EVENT
+#include <VBox/com/array.h>
+#include <VBox/log.h>
+#include <iprt/asm.h>
+#include "VBoxEvents.h"
+
+]]></xsl:text>
+
+ <!-- Interfaces -->
+ <xsl:for-each select="//interface[@autogen=$G_kind]">
+ <xsl:value-of select="concat('// ', @name, ' implementation code')" />
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+ <xsl:variable name="implName">
+ <xsl:value-of select="substring(@name, 2)" />
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$G_kind='VBoxEvent'">
+ <xsl:variable name="isVeto">
+ <xsl:if test="@extends='IVetoEvent'">
+ <xsl:value-of select="'yes'" />
+ </xsl:if>
+ </xsl:variable>
+ <xsl:variable name="isReusable">
+ <xsl:if test="@extends='IReusableEvent'">
+ <xsl:value-of select="'yes'" />
+ </xsl:if>
+ </xsl:variable>
+ <xsl:call-template name="genEventImpl">
+ <xsl:with-param name="implName" select="$implName" />
+ <xsl:with-param name="isVeto" select="$isVeto" />
+ <xsl:with-param name="isReusable" select="$isReusable" />
+ </xsl:call-template>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:for-each>
+
+</xsl:template>
+
+
+<!--
+ Produces VBoxEvents.h
+ -->
+<xsl:template name="genCommonEventHeader">
+ <xsl:call-template name="fileheader">
+ <xsl:with-param name="name" select="'VBoxEvents.h'" />
+ </xsl:call-template>
+
+ <xsl:text><![CDATA[
+#include "EventImpl.h"
+
+]]></xsl:text>
+
+ <!-- Simple methods for firing off events. -->
+ <xsl:text>/** @name Fire off events&#10;</xsl:text>
+ <xsl:text> * @{ */&#10;</xsl:text>
+ <xsl:for-each select="//interface[@autogen='VBoxEvent']">
+ <xsl:value-of select="concat('/** Fire an ', @name, ' event. */&#10;')" />
+ <xsl:variable name="evname">
+ <xsl:value-of select="substring(@name, 2)" />
+ </xsl:variable>
+ <xsl:variable name="ifname">
+ <xsl:value-of select="@name" />
+ </xsl:variable>
+ <xsl:variable name="hasStringAttribs">
+ <xsl:call-template name="hasStringAttributes">
+ <xsl:with-param name="name" select="@name"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:value-of select="concat('DECLHIDDEN(HRESULT) Fire', $evname, '(IEventSource *aSource')"/>
+ <xsl:call-template name="genFormalParams">
+ <xsl:with-param name="name" select="$ifname" />
+ <xsl:with-param name="utf8str" select="'yes'" />
+ </xsl:call-template>
+ <xsl:text>);&#10;</xsl:text>
+
+ <xsl:if test="($hasStringAttribs != '') and (($G_generateBstrVariants = 'yes') or (contains(@autogenflags, 'BSTR')))">
+ <xsl:value-of select="concat('DECLHIDDEN(HRESULT) Fire', $evname, '(IEventSource *aSource')"/>
+ <xsl:call-template name="genFormalParams">
+ <xsl:with-param name="name" select="$ifname" />
+ <xsl:with-param name="utf8str" select="'no'" />
+ </xsl:call-template>
+ <xsl:text>);&#10;</xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>/** @} */&#10;&#10;</xsl:text>
+
+ <!-- Event instantiation methods. -->
+ <xsl:text>/** @name Instantiate events&#10;</xsl:text>
+ <xsl:text> * @{ */&#10;</xsl:text>
+ <xsl:for-each select="//interface[@autogen='VBoxEvent']">
+ <xsl:value-of select="concat('/** Create an ', @name, ' event. */&#10;')" />
+ <xsl:variable name="evname">
+ <xsl:value-of select="substring(@name, 2)" />
+ </xsl:variable>
+ <xsl:variable name="ifname">
+ <xsl:value-of select="@name" />
+ </xsl:variable>
+ <xsl:variable name="hasStringAttribs">
+ <xsl:call-template name="hasStringAttributes">
+ <xsl:with-param name="name" select="@name"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:value-of select="concat('DECLHIDDEN(HRESULT) Create', $evname, '(IEvent **aEvent, IEventSource *aSource')"/>
+ <xsl:call-template name="genFormalParams">
+ <xsl:with-param name="name" select="$ifname" />
+ <xsl:with-param name="utf8str" select="'yes'" />
+ </xsl:call-template>
+ <xsl:text>);&#10;</xsl:text>
+
+ <xsl:if test="($hasStringAttribs != '') and (($G_generateBstrVariants = 'yes') or (contains(@autogenflags, 'BSTR')))">
+ <xsl:value-of select="concat('DECLHIDDEN(HRESULT) Create', $evname, '(IEvent **aEvent, IEventSource *aSource')"/>
+ <xsl:call-template name="genFormalParams">
+ <xsl:with-param name="name" select="$ifname" />
+ <xsl:with-param name="utf8str" select="'no'" />
+ </xsl:call-template>
+ <xsl:text>);&#10;</xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>/** @} */&#10;</xsl:text>
+ <xsl:text>&#10;</xsl:text>
+
+ <!-- Reinitialization methods for reusable events. -->
+ <xsl:text>/** @name Re-init reusable events&#10;</xsl:text>
+ <xsl:text> * @{ */&#10;</xsl:text>
+ <xsl:for-each select="//interface[@autogen='VBoxEvent']">
+ <xsl:if test="@extends='IReusableEvent'">
+ <xsl:value-of select="concat('/** Re-init an ', @name, ' event. */&#10;')" />
+ <xsl:variable name="evname">
+ <xsl:value-of select="substring(@name, 2)" />
+ </xsl:variable>
+ <xsl:variable name="ifname">
+ <xsl:value-of select="@name" />
+ </xsl:variable>
+ <xsl:variable name="hasStringAttribs">
+ <xsl:call-template name="hasStringAttributes">
+ <xsl:with-param name="name" select="@name"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:value-of select="concat('DECLHIDDEN(HRESULT) Reinit', $evname, '(IEvent *aEvent')"/>
+ <xsl:call-template name="genFormalParams">
+ <xsl:with-param name="name" select="$ifname" />
+ <xsl:with-param name="utf8str" select="'yes'" />
+ </xsl:call-template>
+ <xsl:text>);&#10;</xsl:text>
+
+ <xsl:if test="($hasStringAttribs != '') and (($G_generateBstrVariants = 'yes') or (contains(@autogenflags, 'BSTR')))">
+ <xsl:value-of select="concat('DECLHIDDEN(HRESULT) Reinit', $evname, '(IEvent *aEvent')"/>
+ <xsl:call-template name="genFormalParams">
+ <xsl:with-param name="name" select="$ifname" />
+ <xsl:with-param name="utf8str" select="'no'" />
+ </xsl:call-template>
+ <xsl:text>);&#10;</xsl:text>
+ </xsl:if>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>/** @} */&#10;</xsl:text>
+
+</xsl:template>
+
+<xsl:template match="/">
+ <!-- Global code -->
+ <xsl:choose>
+ <xsl:when test="$G_kind='VBoxEvent'">
+ <xsl:call-template name="genCommonEventCode">
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="$G_kind='VBoxEventHeader'">
+ <xsl:call-template name="genCommonEventHeader">
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('Request unsupported: ', $G_kind)" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/idl/docstrip.xsl b/src/VBox/Main/idl/docstrip.xsl
new file mode 100644
index 00000000..547b620c
--- /dev/null
+++ b/src/VBox/Main/idl/docstrip.xsl
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+
+<!--
+ Copies the source XIDL file to the output, except that all <desc>
+ tags are stripped in the process. This is to generate a copy
+ of VirtualBox.xidl which is then used as a source for generating
+ the COM/XPCOM headers, the webservice files, the Qt bindings and
+ others. The idea is that updating the documentation tags in the
+ original XIDL should not cause a full recompile of nearly all of
+ VirtualBox.
+-->
+<!--
+ Copyright (C) 2009-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="xml" indent="yes" encoding="utf-8" />
+
+<!-- copy everything unless there's a more specific template -->
+<xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()"/>
+ </xsl:copy>
+</xsl:template>
+
+<!-- swallow desc -->
+<xsl:template match="desc" />
+
+<!-- swallow all comments -->
+<xsl:template match="comment()" />
+
+</xsl:stylesheet>
+
diff --git a/src/VBox/Main/idl/doxygen.xsl b/src/VBox/Main/idl/doxygen.xsl
new file mode 100644
index 00000000..2da19dae
--- /dev/null
+++ b/src/VBox/Main/idl/doxygen.xsl
@@ -0,0 +1,703 @@
+<?xml version="1.0"?>
+
+<!--
+ * A template to generate a generic IDL file from the generic interface
+ * definition expressed in XML. The generated file is intended solely to
+ * generate the documentation using Doxygen.
+-->
+<!--
+ Copyright (C) 2006-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:str="http://xsltsl.org/string"
+ exclude-result-prefixes="str"
+>
+<!-- The exclude-result-prefixes attribute is needed since otherwise the <tt>
+ tag added for the ID in interface and enum descriptions would get the
+ namespace, confusing doxygen completely. -->
+
+<xsl:import href="string.xsl"/>
+
+<xsl:output method="html" indent="yes"/>
+
+<xsl:strip-space elements="*"/>
+
+
+<!--
+// Doxygen transformation rules
+/////////////////////////////////////////////////////////////////////////////
+-->
+
+<!--
+ * all text elements that are not explicitly matched are normalized
+ * (all whitespace chars are converted to single spaces)
+-->
+<!--xsl:template match="desc//text()">
+ <xsl:value-of select="concat(' ',normalize-space(.),' ')"/>
+</xsl:template-->
+
+<!--
+ * all elements that are not explicitly matched are considered to be html tags
+ * and copied w/o modifications
+-->
+<xsl:template match="desc//*">
+ <xsl:copy>
+ <xsl:apply-templates/>
+ </xsl:copy>
+</xsl:template>
+
+<!--
+ * special treatment of <tt>, making sure that Doxygen will not interpret its
+ * contents (assuming it is a leaf tag)
+-->
+<xsl:template match="desc//tt/text()">
+ <xsl:variable name="subst1">
+ <xsl:call-template name="str:subst">
+ <xsl:with-param name="text" select="." />
+ <xsl:with-param name="replace" select="'::'" />
+ <xsl:with-param name="with" select="'\::'" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="$subst1"/>
+</xsl:template>
+
+<!--
+ * same like desc//* but place <ol> at start of line otherwise Doxygen will not
+ * find it
+-->
+<xsl:template match="desc//ol">
+ <xsl:text>&#x0A;</xsl:text>
+ <xsl:copy>
+ <xsl:apply-templates/>
+ </xsl:copy>
+</xsl:template>
+
+<!--
+ * same like desc//* but place <ul> at start of line otherwise Doxygen will not
+ * find it
+-->
+<xsl:template match="desc//ul">
+ <xsl:text>&#x0A;</xsl:text>
+ <xsl:copy>
+ <xsl:apply-templates/>
+ </xsl:copy>
+</xsl:template>
+
+<!--
+ * same like desc//* but place <pre> at start of line otherwise Doxygen will not
+ * find it
+-->
+<xsl:template match="desc//pre">
+ <xsl:text>&#x0A;</xsl:text>
+ <xsl:copy>
+ <xsl:apply-templates/>
+ </xsl:copy>
+</xsl:template>
+
+<!--
+ * paragraph
+-->
+<xsl:template match="desc//p">
+ <xsl:text>&#x0A;</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+<!--
+ * link
+-->
+<xsl:template match="desc//link">
+ <xsl:text>@link </xsl:text>
+ <!--
+ * sometimes Doxygen is stupid and cannot resolve global enums properly,
+ * thinking they are members of the current class. Fix it by adding ::
+ * in front of any @to value that doesn't start with #.
+ -->
+ <xsl:choose>
+ <xsl:when test="not(starts-with(@to, '#')) and not(contains(@to, '::'))">
+ <xsl:text>::</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ <!--
+ * Doxygen doesn't understand autolinks like Class::func() if Class
+ * doesn't actually contain a func with no arguments. Fix it.
+ -->
+ <xsl:choose>
+ <xsl:when test="substring(@to, string-length(@to)-1)='()'">
+ <xsl:value-of select="substring-before(@to, '()')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@to"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> </xsl:text>
+ <xsl:choose>
+ <xsl:when test="normalize-space(text())">
+ <xsl:value-of select="normalize-space(text())"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="starts-with(@to, '#')">
+ <xsl:value-of select="substring-after(@to, '#')"/>
+ </xsl:when>
+ <xsl:when test="starts-with(@to, '::')">
+ <xsl:value-of select="substring-after(@to, '::')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@to"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>@endlink</xsl:text>
+ <!--
+ * insert a dummy empty B element to distinctly separate @endlink
+ * from the following text
+ -->
+ <xsl:element name="b"/>
+</xsl:template>
+
+<!--
+ * note
+-->
+<xsl:template match="desc/note">
+ <xsl:if test="not(@internal='yes')">
+ <xsl:text>&#x0A;@note </xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#x0A;</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+<!--
+ * see
+-->
+<xsl:template match="desc/see">
+ <xsl:text>&#x0A;@see </xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * common comment prologue (handles group IDs)
+-->
+<xsl:template match="desc" mode="begin">
+ <xsl:param name="id" select="@group | preceding::descGroup[1]/@id"/>
+ <xsl:text>/**&#x0A;</xsl:text>
+ <xsl:if test="$id">
+ <xsl:value-of select="concat(' @ingroup ',$id,'&#x0A;')"/>
+ </xsl:if>
+</xsl:template>
+
+<!--
+ * common brief comment prologue (handles group IDs)
+-->
+<xsl:template match="desc" mode="begin_brief">
+ <xsl:param name="id" select="@group | preceding::descGroup[1]/@id"/>
+ <xsl:text>/**&#x0A;</xsl:text>
+ <xsl:if test="$id">
+ <xsl:value-of select="concat(' @ingroup ',$id,'&#x0A;')"/>
+ </xsl:if>
+ <xsl:text> @brief&#x0A;</xsl:text>
+</xsl:template>
+
+<!--
+ * common middle part of the comment block
+-->
+<xsl:template match="desc" mode="middle">
+ <xsl:apply-templates select="text() | *[not(self::note or self::see)]"/>
+ <xsl:apply-templates select="note"/>
+ <xsl:apply-templates select="see"/>
+</xsl:template>
+
+<!--
+ * result part of the comment block
+-->
+<xsl:template match="desc" mode="results">
+ <xsl:if test="result">
+ <xsl:text>
+@par Expected result codes:
+ </xsl:text>
+ <table>
+ <xsl:for-each select="result">
+ <tr>
+ <xsl:choose>
+ <xsl:when test="ancestor::library/result[@name=current()/@name]">
+ <td><xsl:value-of select=
+ "concat('@link ::',@name,' ',@name,' @endlink')"/></td>
+ </xsl:when>
+ <xsl:otherwise>
+ <td><xsl:value-of select="@name"/></td>
+ </xsl:otherwise>
+ </xsl:choose>
+ <td>
+ <xsl:apply-templates select="text() | *[not(self::note or self::see or
+ self::result)]"/>
+ </td>
+ </tr>
+ </xsl:for-each>
+ </table>
+ </xsl:if>
+</xsl:template>
+
+
+<!--
+ * comment for interfaces
+-->
+<xsl:template match="interface/desc">
+ <xsl:apply-templates select="." mode="begin"/>
+ <xsl:apply-templates select="." mode="middle"/>
+@par Interface ID:
+<tt>{<xsl:call-template name="str:to-upper">
+ <xsl:with-param name="text" select="../@uuid"/>
+ </xsl:call-template>}</tt>
+ <xsl:text>&#x0A;*/&#x0A;</xsl:text>
+</xsl:template>
+
+<!--
+ * comment for attributes
+-->
+<xsl:template match="attribute/desc">
+ <xsl:apply-templates select="." mode="begin"/>
+ <xsl:apply-templates select="text() | *[not(self::note or self::see or self::result)]"/>
+ <xsl:apply-templates select="." mode="results"/>
+ <xsl:apply-templates select="note"/>
+ <xsl:if test="../@mod='ptr'">
+ <xsl:text>
+
+@warning This attribute is non-scriptable. In particular, this also means that an
+attempt to get or set it from a process other than the process that has created and
+owns the object will most likely fail or crash your application.
+</xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="see"/>
+ <xsl:text>&#x0A;*/&#x0A;</xsl:text>
+</xsl:template>
+
+<!--
+ * comment for methods
+-->
+<xsl:template match="method/desc">
+ <xsl:apply-templates select="." mode="begin"/>
+ <xsl:apply-templates select="text() | *[not(self::note or self::see or self::result)]"/>
+ <xsl:for-each select="../param">
+ <xsl:apply-templates select="desc"/>
+ </xsl:for-each>
+ <xsl:apply-templates select="." mode="results"/>
+ <xsl:apply-templates select="note"/>
+ <xsl:apply-templates select="../param/desc/note"/>
+ <xsl:if test="../param/@mod='ptr'">
+ <xsl:text>
+
+@warning This method is non-scriptable. In particular, this also means that an
+attempt to call it from a process other than the process that has created and
+owns the object will most likely fail or crash your application.
+</xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="see"/>
+ <xsl:text>&#x0A;*/&#x0A;</xsl:text>
+</xsl:template>
+
+<!--
+ * comment for method parameters
+-->
+<xsl:template match="method/param/desc">
+ <xsl:text>&#x0A;@param </xsl:text>
+ <xsl:value-of select="../@name"/>
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates select="text() | *[not(self::note or self::see)]"/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+<!--
+ * comment for enums
+-->
+<xsl:template match="enum/desc">
+ <xsl:apply-templates select="." mode="begin"/>
+ <xsl:apply-templates select="." mode="middle"/>
+@par Interface ID:
+<tt>{<xsl:call-template name="str:to-upper">
+ <xsl:with-param name="text" select="../@uuid"/>
+ </xsl:call-template>}</tt>
+ <xsl:text>&#x0A;*/&#x0A;</xsl:text>
+</xsl:template>
+
+<!--
+ * comment for enum values
+-->
+<xsl:template match="enum/const/desc">
+ <xsl:apply-templates select="." mode="begin_brief"/>
+ <xsl:apply-templates select="." mode="middle"/>
+ <xsl:text>&#x0A;*/&#x0A;</xsl:text>
+</xsl:template>
+
+<!--
+ * comment for result codes
+-->
+<xsl:template match="result/desc">
+ <xsl:apply-templates select="." mode="begin_brief"/>
+ <xsl:apply-templates select="." mode="middle"/>
+ <xsl:text>&#x0A;*/&#x0A;</xsl:text>
+</xsl:template>
+
+<!--
+ * ignore descGroups by default (processed in /idl)
+-->
+<xsl:template match="descGroup"/>
+
+<!--
+// templates
+/////////////////////////////////////////////////////////////////////////////
+-->
+
+
+<!--
+ * header
+-->
+<xsl:template match="/idl">
+/*
+ * DO NOT EDIT! This is a generated file.
+ *
+ * Doxygen IDL definition for VirtualBox Main API (COM interfaces)
+ * generated from XIDL (XML interface definition).
+ *
+ * Source : src/VBox/Main/idl/VirtualBox.xidl
+ * Generator : src/VBox/Main/idl/doxygen.xsl
+ *
+ * This IDL is generated using some generic OMG IDL-like syntax SOLELY
+ * for the purpose of generating the documentation using Doxygen and
+ * is not syntactically valid.
+ *
+ * DO NOT USE THIS HEADER IN ANY OTHER WAY!
+ */
+
+ <!-- general description -->
+ <xsl:text>/** @mainpage &#x0A;</xsl:text>
+ <xsl:apply-templates select="desc" mode="middle"/>
+ <xsl:text>&#x0A;*/&#x0A;</xsl:text>
+
+ <!-- group (module) definitions -->
+ <xsl:for-each select="//descGroup">
+ <xsl:if test="@id and (@title or desc)">
+ <xsl:value-of select="concat('/** @defgroup ',@id,' ',@title)"/>
+ <xsl:apply-templates select="desc" mode="middle"/>
+ <xsl:text>&#x0A;*/&#x0A;</xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+
+ <!-- everything else -->
+ <xsl:apply-templates select="*[not(self::desc)]"/>
+
+</xsl:template>
+
+
+<!--
+ * accept all <if>s
+-->
+<xsl:template match="if">
+ <xsl:apply-templates/>
+</xsl:template>
+
+
+<!--
+ * cpp_quote (ignore)
+-->
+<xsl:template match="cpp">
+</xsl:template>
+
+
+<!--
+ * #ifdef statement (@if attribute)
+-->
+<xsl:template match="@if" mode="begin">
+ <xsl:text>#if </xsl:text>
+ <xsl:value-of select="."/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+<xsl:template match="@if" mode="end">
+ <xsl:text>#endif&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * libraries
+-->
+<xsl:template match="application">
+ <!-- result codes -->
+ <xsl:for-each select="result">
+ <xsl:apply-templates select="."/>
+ </xsl:for-each>
+ <!-- all enums go first -->
+ <xsl:apply-templates select="enum | if/enum"/>
+ <!-- everything else but result codes and enums -->
+ <xsl:apply-templates select="*[not(self::result or self::enum) and
+ not(self::if[result] or self::if[enum])]"/>
+</xsl:template>
+
+
+<!--
+ * result codes
+-->
+<xsl:template match="application//result">
+ <xsl:apply-templates select="@if" mode="begin"/>
+ <xsl:apply-templates select="desc"/>
+ <xsl:value-of select="concat('const HRESULT ',@name,' = ',@value,';')"/>
+ <xsl:text>&#x0A;</xsl:text>
+ <xsl:apply-templates select="@if" mode="end"/>
+</xsl:template>
+
+
+<!--
+ * interfaces
+-->
+<xsl:template match="interface">
+ <xsl:apply-templates select="desc"/>
+ <xsl:text>interface </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> : </xsl:text>
+ <xsl:value-of select="@extends"/>
+ <xsl:text>&#x0A;{&#x0A;</xsl:text>
+ <!-- attributes (properties) -->
+ <xsl:apply-templates select="attribute"/>
+ <!-- methods -->
+ <xsl:apply-templates select="method"/>
+ <!-- 'if' enclosed elements, unsorted -->
+ <xsl:apply-templates select="if"/>
+ <!-- -->
+ <xsl:text>}; /* interface </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> */&#x0A;&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * attributes
+-->
+<xsl:template match="interface//attribute">
+ <xsl:apply-templates select="@if" mode="begin"/>
+ <xsl:apply-templates select="desc"/>
+ <xsl:text> </xsl:text>
+ <xsl:if test="@readonly='yes'">
+ <xsl:text>readonly </xsl:text>
+ </xsl:if>
+ <xsl:text>attribute </xsl:text>
+ <xsl:apply-templates select="@type"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;&#x0A;</xsl:text>
+ <xsl:apply-templates select="@if" mode="end"/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+<!--
+ * methods
+-->
+<xsl:template match="interface//method">
+ <xsl:apply-templates select="@if" mode="begin"/>
+ <xsl:apply-templates select="desc"/>
+ <xsl:text> void </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:if test="param">
+ <xsl:text> (&#x0A;</xsl:text>
+ <xsl:for-each select="param [position() != last()]">
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates select="."/>
+ <xsl:text>,&#x0A;</xsl:text>
+ </xsl:for-each>
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates select="param [last()]"/>
+ <xsl:text>&#x0A; );&#x0A;</xsl:text>
+ </xsl:if>
+ <xsl:if test="not(param)">
+ <xsl:text>();&#x0A;</xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@if" mode="end"/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * co-classes
+-->
+<xsl:template match="module/class">
+ <!-- class and contract id: later -->
+ <!-- CLSID_xxx declarations for XPCOM, for compatibility with Win32: later -->
+</xsl:template>
+
+
+<!--
+ * enums
+-->
+<xsl:template match="enum">
+ <xsl:apply-templates select="desc"/>
+ <xsl:text>enum </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>&#x0A;{&#x0A;</xsl:text>
+ <xsl:for-each select="const">
+ <xsl:apply-templates select="desc"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="../@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:value-of select="@name"/> = <xsl:value-of select="@value"/>
+ <xsl:text>,&#x0A;</xsl:text>
+ </xsl:for-each>
+ <xsl:text>};&#x0A;&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * method parameters
+-->
+<xsl:template match="method/param">
+ <xsl:choose>
+ <xsl:when test="@dir='in'">in </xsl:when>
+ <xsl:when test="@dir='out'">out </xsl:when>
+ <xsl:when test="@dir='return'">[retval] out </xsl:when>
+ <xsl:otherwise>in</xsl:otherwise>
+ </xsl:choose>
+ <xsl:apply-templates select="@type"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="@name"/>
+</xsl:template>
+
+
+<!--
+ * attribute/parameter type conversion
+-->
+<xsl:template match="attribute/@type | param/@type">
+ <xsl:variable name="self_target" select="current()/ancestor::if/@target"/>
+
+ <xsl:choose>
+ <!-- modifiers (ignored for 'enumeration' attributes)-->
+ <xsl:when test="name(current())='type' and ../@mod">
+ <xsl:choose>
+ <xsl:when test="../@mod='ptr'">
+ <xsl:choose>
+ <!-- standard types -->
+ <!--xsl:when test=".='result'">??</xsl:when-->
+ <xsl:when test=".='boolean'">booleanPtr</xsl:when>
+ <xsl:when test=".='octet'">octetPtr</xsl:when>
+ <xsl:when test=".='short'">shortPtr</xsl:when>
+ <xsl:when test=".='unsigned short'">ushortPtr</xsl:when>
+ <xsl:when test=".='long'">longPtr</xsl:when>
+ <xsl:when test=".='long long'">llongPtr</xsl:when>
+ <xsl:when test=".='unsigned long'">ulongPtr</xsl:when>
+ <xsl:when test=".='unsigned long long'">ullongPtr</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:text>attribute 'mod=</xsl:text>
+ <xsl:value-of select="concat('&quot;',../@mod,'&quot;')"/>
+ <xsl:text>' cannot be used with type </xsl:text>
+ <xsl:value-of select="concat('&quot;',current(),'&quot;!')"/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="../@mod='string'">
+ <xsl:choose>
+ <!-- standard types -->
+ <!--xsl:when test=".='result'">??</xsl:when-->
+ <xsl:when test=".='uuid'">wstringUUID</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:text>attribute 'mod=</xsl:text>
+ <xsl:value-of select="concat('&quot;',../@mod,'&quot;')"/>
+ <xsl:text>' cannot be used with type </xsl:text>
+ <xsl:value-of select="concat('&quot;',current(),'&quot;!')"/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:value-of select="concat('value &quot;',../@mod,'&quot; ')"/>
+ <xsl:text>of attribute 'mod' is invalid!</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <!-- no modifiers -->
+ <xsl:otherwise>
+ <xsl:choose>
+ <!-- standard types -->
+ <xsl:when test=".='result'">result</xsl:when>
+ <xsl:when test=".='boolean'">boolean</xsl:when>
+ <xsl:when test=".='octet'">octet</xsl:when>
+ <xsl:when test=".='short'">short</xsl:when>
+ <xsl:when test=".='unsigned short'">unsigned short</xsl:when>
+ <xsl:when test=".='long'">long</xsl:when>
+ <xsl:when test=".='long long'">long long</xsl:when>
+ <xsl:when test=".='unsigned long'">unsigned long</xsl:when>
+ <xsl:when test=".='unsigned long long'">unsigned long long</xsl:when>
+ <xsl:when test=".='char'">char</xsl:when>
+ <xsl:when test=".='wchar'">wchar</xsl:when>
+ <xsl:when test=".='string'">string</xsl:when>
+ <xsl:when test=".='wstring'">wstring</xsl:when>
+ <!-- UUID type -->
+ <xsl:when test=".='uuid'">uuid</xsl:when>
+ <!-- system interface types -->
+ <xsl:when test=".='$unknown'">$unknown</xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <!-- enum types -->
+ <xsl:when test="
+ (ancestor::library/application/enum[@name=current()])
+ or (ancestor::library/if/application/enum[@name=current()])
+ or (ancestor::library/application/if[@target=$self_target]/enum[@name=current()])
+ or (ancestor::library/if/application/if[@target=$self_target]/enum[@name=current()])
+ ">
+ <xsl:value-of select="."/>
+ </xsl:when>
+ <!-- custom interface types -->
+ <xsl:when test="
+ ( (ancestor::library/application/interface[@name=current()])
+ or (ancestor::library/if/application/interface[@name=current()])
+ or (ancestor::library/application/if[@target=$self_target]/interface[@name=current()])
+ or (ancestor::library/if/application/if[@target=$self_target]/interface[@name=current()])
+ )
+ ">
+ <xsl:value-of select="."/>
+ </xsl:when>
+ <!-- other types -->
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:text>Unknown parameter type: </xsl:text>
+ <xsl:value-of select="."/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="../@safearray='yes'">
+ <xsl:text>[]</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+</xsl:stylesheet>
+
diff --git a/src/VBox/Main/idl/midl.xsl b/src/VBox/Main/idl/midl.xsl
new file mode 100644
index 00000000..b88f3218
--- /dev/null
+++ b/src/VBox/Main/idl/midl.xsl
@@ -0,0 +1,924 @@
+<?xml version="1.0"?>
+<!-- $Id: midl.xsl $ -->
+
+<!--
+ * A template to generate a MS IDL compatible interface definition file
+ * from the generic interface definition expressed in XML.
+-->
+<!--
+ Copyright (C) 2006-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="text"/>
+
+<xsl:strip-space elements="*"/>
+
+<!-- Whether to generate proxy code and type library ('yes'), or just the type-library. -->
+<xsl:param name="g_fGenProxy" select="'no'"/>
+
+<!-- Whether to generate coclass and interfaces for VBoxSDS-->
+<xsl:param name="g_fVBoxWithSDS" select="'no'"/>
+
+<xsl:include href="typemap-shared.inc.xsl"/>
+
+
+<!--
+// templates
+/////////////////////////////////////////////////////////////////////////////
+-->
+
+<!--
+ * not explicitly matched elements and attributes
+-->
+<xsl:template match="*"/>
+
+
+<!--
+ * header
+-->
+ <xsl:template match="/idl">
+ <xsl:text>
+/*
+ * DO NOT EDIT! This is a generated file.
+ *
+ * MS IDL (MIDL) definition for VirtualBox Main API (COM interfaces)
+ * generated from XIDL (XML interface definition).
+ *
+ * Source : src/VBox/Main/idl/VirtualBox.xidl
+ * Generator : src/VBox/Main/idl/midl.xsl
+ */
+
+#if (__midl >= 501)
+midl_pragma warning(disable:2039) /* Disable warning MIDL2039 regarding interface not being automation
+ marshaling conformant and requiring NT 4.0 SP4 or greater. */
+midl_pragma warning(disable:2456) /* Disable warning MIDL2456 regarding SAFEARRAY(interface pointer). */
+midl_pragma warning(disable:2111) /* Disable warning MIDL2111 regarding identifier lengths exceeding 31 chars. */
+#endif
+
+import "unknwn.idl";
+
+</xsl:text>
+ <xsl:apply-templates/>
+</xsl:template>
+
+
+<!--
+ * ignore all |if|s except those for MIDL target
+-->
+<xsl:template match="if">
+ <xsl:if test="@target='midl'">
+ <xsl:apply-templates/>
+ </xsl:if>
+</xsl:template>
+<xsl:template match="if" mode="forward">
+ <xsl:if test="@target='midl'">
+ <xsl:apply-templates mode="forward"/>
+ </xsl:if>
+</xsl:template>
+<xsl:template match="if" mode="forwarder">
+ <xsl:param name="nameOnly"/>
+ <xsl:if test="@target='midl'">
+ <xsl:apply-templates mode="forwarder">
+ <xsl:with-param name="nameOnly" select="$nameOnly"/>
+ </xsl:apply-templates>
+ </xsl:if>
+</xsl:template>
+
+
+<!--
+ * cpp_quote
+-->
+<xsl:template match="cpp">
+ <xsl:text>cpp_quote("</xsl:text>
+ <xsl:value-of select="@line"/>
+ <xsl:text>")&#x0A;&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * #if statement (@if attribute)
+-->
+<xsl:template match="@if" mode="begin">
+ <xsl:text>#if </xsl:text>
+ <xsl:value-of select="."/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+<xsl:template match="@if" mode="end">
+ <xsl:text>#endif&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * libraries
+-->
+<xsl:template match="library">
+ <xsl:if test="$g_fGenProxy = 'yes'">
+ <!-- Declare everything outside the library and then reference these
+ from inside the library statement. See:
+ http://msdn.microsoft.com/en-us/library/windows/desktop/aa366841(v=vs.85).aspx -->
+ <xsl:text>&#x0A;</xsl:text>
+ <!-- forward declarations -->
+ <xsl:apply-templates select="descendant::application/if | descendant::application/interface" mode="forward"/>
+ <xsl:text>&#x0A;</xsl:text>
+ <!-- all enums go first -->
+ <xsl:apply-templates select="descendant::application/enum | descendant::application/if[enum]"/>
+ <!-- declare the interfaces -->
+ <xsl:apply-templates select="descendant::application/if | descendant::application/interface"/>
+ </xsl:if>
+
+[
+ uuid(<xsl:value-of select="@uuid"/>),
+ version(<xsl:value-of select="@version"/>),
+ helpstring("<xsl:value-of select="@name"/> Type Library")
+]
+<xsl:text>library </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>&#x0A;{&#x0A;</xsl:text>
+ <xsl:text>&#x0A;importlib("stdole2.tlb");&#x0A;&#x0A;</xsl:text>
+ <!-- result codes -->
+ <xsl:for-each select="application/result">
+ <xsl:apply-templates select="."/>
+ </xsl:for-each>
+ <xsl:text>&#x0A;</xsl:text>
+ <xsl:text>&#x0A;</xsl:text>
+ <xsl:choose>
+ <xsl:when test="$g_fGenProxy = 'yes'">
+ <!-- reference enums and interfaces -->
+ <xsl:apply-templates select="descendant::application/if | descendant::application/interface" mode="forward"/>
+ <xsl:apply-templates select="descendant::application/enum | descendant::application/if[enum]" mode="forward"/>
+ <!-- the modules (i.e. everything else) -->
+ <xsl:apply-templates select="descendant::application/module | descendant::application/if[module]"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- forward declarations -->
+ <xsl:apply-templates select="descendant::application/if | descendant::application/interface" mode="forward"/>
+ <!-- all enums go first -->
+ <xsl:apply-templates select="descendant::application/enum | descendant::application/if[enum]"/>
+ <!-- everything else but result codes and enums -->
+ <xsl:apply-templates select=" descendant::application/interface | descendant::application/if[interface]
+ | descendant::application/module | descendant::application/if[module]"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ <!-- -->
+ <xsl:text>}; /* library </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> */&#x0A;&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * applications
+-->
+<xsl:template match="application">
+ <xsl:apply-templates/>
+</xsl:template>
+<xsl:template match="application" mode="forward">
+ <xsl:apply-templates mode="forward"/>
+</xsl:template>
+
+
+<!--
+ * result codes
+-->
+<xsl:template match="result">
+ <xsl:text>cpp_quote("</xsl:text>
+ <xsl:value-of select="concat('#define ',@name,' ((HRESULT)',@value, ')')"/>
+ <xsl:text>")&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * forward declarations
+-->
+<xsl:template match="interface" mode="forward" name="template_interface_forward">
+ <xsl:text>interface </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;&#x0A;</xsl:text>
+</xsl:template>
+
+
+<xsl:template match="enum" mode="forward">
+ <xsl:text>enum </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;&#x0A;&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * interfaces
+-->
+<xsl:template match="interface" name="template_interface">[
+ uuid(<xsl:value-of select="@uuid"/>),
+ object,
+<xsl:if test="not(@notdual = 'yes')"> dual,</xsl:if>
+ oleautomation
+<xsl:if test="$g_fGenProxy = 'yes'">
+ <!-- Indicates to the typelib that we are using a proxy stub DLL and that interfaces
+ should have any ProxyStubClsid32 or TypeLib keys in the registry. -->
+ <xsl:text> , proxy</xsl:text>
+</xsl:if>
+]
+<xsl:text>interface </xsl:text>
+ <xsl:variable name="name" select="@name"/>
+ <xsl:value-of select="$name"/>
+ <xsl:text> : </xsl:text>
+ <xsl:choose>
+ <xsl:when test="(@extends = '$unknown') and (@notdual = 'yes')">IUnknown</xsl:when>
+ <xsl:when test="@extends='$unknown'">IDispatch</xsl:when>
+ <xsl:when test="@extends='$errorinfo'">IErrorInfo</xsl:when>
+ <!-- TODO/FIXME/BUGBUG: The above $errorinfo value causes the following warning (/W4):
+warning MIDL2460 : dual interface should be derived from IDispatch : IVirtualBoxErrorInfo [ Interface 'IVirtualBoxErrorInfo' ]
+ -->
+ <xsl:otherwise><xsl:value-of select="@extends"/></xsl:otherwise>
+ </xsl:choose>
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+ <xsl:text>{&#x0A;</xsl:text>
+ <!-- attributes (properties) -->
+ <xsl:apply-templates select="attribute"/>
+ <xsl:variable name="reservedAttributes" select="@reservedAttributes"/>
+ <xsl:if test="$reservedAttributes > 0">
+ <!-- tricky way to do a "for" loop without recursion -->
+ <xsl:for-each select="(//*)[position() &lt;= $reservedAttributes]">
+ <xsl:text> [propget] HRESULT InternalAndReservedAttribute</xsl:text>
+ <xsl:value-of select="concat(position(), $name)"/>
+ <xsl:text> ([out, retval] ULONG *aReserved);&#x0A;&#x0A;</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+ <!-- methods -->
+ <xsl:apply-templates select="method"/>
+ <xsl:variable name="reservedMethods" select="@reservedMethods"/>
+ <xsl:if test="$reservedMethods > 0">
+ <!-- tricky way to do a "for" loop without recursion -->
+ <xsl:for-each select="(//*)[position() &lt;= $reservedMethods]">
+ <xsl:text> HRESULT InternalAndReservedMethod</xsl:text>
+ <xsl:value-of select="concat(position(), $name)"/>
+ <xsl:text>();&#x0A;&#x0A;</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+ <!-- 'if' enclosed elements, unsorted -->
+ <xsl:apply-templates select="if"/>
+ <!-- -->
+ <xsl:text>}; /* interface </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text> */&#x0A;&#x0A;</xsl:text>
+ <!-- Interface implementation forwarder macro -->
+ <xsl:text>/* Interface implementation forwarder macro */&#x0A;</xsl:text>
+ <!-- 1) individual methods -->
+ <xsl:apply-templates select="attribute" mode="forwarder"/>
+ <xsl:apply-templates select="method" mode="forwarder"/>
+ <xsl:apply-templates select="if" mode="forwarder"/>
+ <!-- 2) COM_FORWARD_Interface_TO(smth) -->
+ <xsl:text>cpp_quote("#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>_TO(smth) </xsl:text>
+ <xsl:apply-templates select="attribute" mode="forwarder">
+ <xsl:with-param name="nameOnly" select="'yes'"/>
+ </xsl:apply-templates>
+ <xsl:apply-templates select="method" mode="forwarder">
+ <xsl:with-param name="nameOnly" select="'yes'"/>
+ </xsl:apply-templates>
+ <xsl:apply-templates select="if" mode="forwarder">
+ <xsl:with-param name="nameOnly" select="'yes'"/>
+ </xsl:apply-templates>
+ <xsl:text>")&#x0A;</xsl:text>
+ <!-- 3) COM_FORWARD_Interface_TO_OBJ(obj) -->
+ <xsl:text>cpp_quote("#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>_TO_OBJ(obj) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>_TO ((obj)->)")&#x0A;</xsl:text>
+ <!-- 4) COM_FORWARD_Interface_TO_BASE(base) -->
+ <xsl:text>cpp_quote("#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>_TO_BASE(base) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>_TO (base::)")&#x0A;</xsl:text>
+ <!-- end -->
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * attributes
+-->
+<xsl:template match="interface//attribute">
+ <xsl:apply-templates select="@if" mode="begin"/>
+ <!-- getter -->
+ <xsl:text> [propget] HRESULT </xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> ([out, retval] </xsl:text>
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>SAFEARRAY(</xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@type"/>
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>)</xsl:text>
+ </xsl:if>
+ <xsl:text> * a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>);&#x0A;</xsl:text>
+ <!-- setter -->
+ <xsl:if test="not(@readonly='yes')">
+ <xsl:text> [propput] HRESULT </xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> ([in] </xsl:text>
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>SAFEARRAY(</xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@type"/>
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>)</xsl:text>
+ </xsl:if>
+ <xsl:text> a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>);&#x0A;</xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@if" mode="end"/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+<xsl:template match="interface//attribute" mode="forwarder">
+
+ <!-- if nameOnly='yes' then only the macro name is composed
+ followed by a space -->
+ <xsl:param name="nameOnly"/>
+
+ <xsl:variable name="parent" select="ancestor::interface"/>
+
+ <xsl:apply-templates select="@if" mode="begin"/>
+
+ <xsl:choose>
+ <xsl:when test="$nameOnly='yes'">
+ <!-- getter: COM_FORWARD_Interface_GETTER_Name_TO(smth) -->
+ <xsl:text>COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_GETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO (smth) </xsl:text>
+ <!-- setter: COM_FORWARD_Interface_SETTER_Name_TO(smth) -->
+ <xsl:if test="not(@readonly='yes')">
+ <xsl:text>COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_SETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO (smth) </xsl:text>
+ </xsl:if>
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- getter: COM_FORWARD_Interface_GETTER_Name_TO(smth) -->
+ <xsl:text>cpp_quote("#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_GETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO(smth) HRESULT STDMETHODCALLTYPE get_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> (</xsl:text>
+ <xsl:choose>
+ <xsl:when test="@safearray='yes'">
+ <xsl:text>SAFEARRAY *</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="@type"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> * a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>) { return smth get_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> (a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>); }")&#x0A;</xsl:text>
+ <!-- getter: COM_FORWARD_Interface_GETTER_Name_TO_OBJ(obj) -->
+ <xsl:text>cpp_quote("#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_GETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO_OBJ(obj) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_GETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO ((obj)->)")&#x0A;</xsl:text>
+ <!-- getter: COM_FORWARD_Interface_GETTER_Name_TO_BASE(base) -->
+ <xsl:text>cpp_quote("#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_GETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO_BASE(base) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_GETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO (base::)")&#x0A;</xsl:text>
+ <!-- -->
+ <xsl:if test="not(@readonly='yes')">
+ <!-- setter: COM_FORWARD_Interface_SETTER_Name_TO(smth) -->
+ <xsl:text>cpp_quote("#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_SETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO(smth) HRESULT STDMETHODCALLTYPE put_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> (</xsl:text>
+ <xsl:choose>
+ <xsl:when test="@safearray='yes'">
+ <xsl:text>SAFEARRAY *</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="@type"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>) { return smth put_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> (a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>); }")&#x0A;</xsl:text>
+ <!-- setter: COM_FORWARD_Interface_SETTER_Name_TO_OBJ(obj) -->
+ <xsl:text>cpp_quote("#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_SETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO_OBJ(obj) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_SETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO ((obj)->)")&#x0A;</xsl:text>
+ <!-- setter: COM_FORWARD_Interface_SETTER_Name_TO_BASE(base) -->
+ <xsl:text>cpp_quote("#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_SETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO_BASE(base) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_SETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO (base::)")&#x0A;</xsl:text>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:apply-templates select="@if" mode="end"/>
+
+</xsl:template>
+
+
+<!--
+ * methods
+-->
+<xsl:template match="interface//method">
+ <xsl:apply-templates select="@if" mode="begin"/>
+ <xsl:text> HRESULT </xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:choose>
+ <xsl:when test="param">
+ <xsl:text> (&#x0A;</xsl:text>
+ <xsl:for-each select="param [position() != last()]">
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates select="."/>
+ <xsl:text>,&#x0A;</xsl:text>
+ </xsl:for-each>
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates select="param [last()]"/>
+ <xsl:text>&#x0A; );&#x0A;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise test="not(param)">
+ <xsl:text>();&#x0A;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:apply-templates select="@if" mode="end"/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+<xsl:template match="interface//method" mode="forwarder">
+
+ <!-- if nameOnly='yes' then only the macro name is composed followed by \ -->
+ <xsl:param name="nameOnly"/>
+
+ <xsl:variable name="parent" select="ancestor::interface"/>
+
+ <xsl:apply-templates select="@if" mode="begin"/>
+
+ <xsl:choose>
+ <xsl:when test="$nameOnly='yes'">
+ <!-- COM_FORWARD_Interface_Method_TO(smth) -->
+ <xsl:text>COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO (smth) </xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>cpp_quote("#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO(smth) HRESULT STDMETHODCALLTYPE </xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:choose>
+ <xsl:when test="param">
+ <xsl:text> (</xsl:text>
+ <xsl:for-each select="param [position() != last()]">
+ <xsl:apply-templates select="." mode="forwarder"/>
+ <xsl:text>, </xsl:text>
+ </xsl:for-each>
+ <xsl:apply-templates select="param [last()]" mode="forwarder"/>
+ <xsl:text>) { return smth </xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> (</xsl:text>
+ <xsl:for-each select="param [position() != last()]">
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>, </xsl:text>
+ </xsl:for-each>
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="param [last()]/@name"/>
+ </xsl:call-template>
+ <xsl:text>); }</xsl:text>
+ </xsl:when>
+ <xsl:otherwise test="not(param)">
+ <xsl:text>() { return smth </xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>(); }</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>")&#x0A;</xsl:text>
+ <!-- COM_FORWARD_Interface_Method_TO_OBJ(obj) -->
+ <xsl:text>cpp_quote("#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO_OBJ(obj) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO ((obj)->)")&#x0A;</xsl:text>
+ <!-- COM_FORWARD_Interface_Method_TO_BASE(base) -->
+ <xsl:text>cpp_quote("#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO_BASE(base) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO (base::)")&#x0A;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:apply-templates select="@if" mode="end"/>
+
+</xsl:template>
+
+
+<!--
+ * modules
+-->
+<xsl:template match="module">
+ <xsl:apply-templates select="class"/>
+</xsl:template>
+
+
+<!--
+ * co-classes
+-->
+<xsl:template match="module/class" name="template_class">[
+ uuid(<xsl:value-of select="@uuid"/>)
+]
+<xsl:text>coclass </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>&#x0A;{&#x0A;</xsl:text>
+ <xsl:for-each select="interface">
+ <xsl:text> </xsl:text>
+ <xsl:if test="@default='yes'">
+ <xsl:text>[default] </xsl:text>
+ </xsl:if>
+ <xsl:text>interface </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;&#x0A;</xsl:text>
+ </xsl:for-each>
+ <xsl:for-each select="eventsink">
+ <xsl:text> </xsl:text>
+ <xsl:choose>
+ <xsl:when test="@default='yes'"><xsl:text>[default,source]</xsl:text></xsl:when>
+ <xsl:otherwise><xsl:text>[source]</xsl:text></xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> interface </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;&#x0A;</xsl:text>
+ </xsl:for-each>
+ <xsl:text>&#x0A;}; /* coclass </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> */&#x0A;&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * enums
+-->
+<xsl:template match="enum">[
+ uuid(<xsl:value-of select="@uuid"/>),
+ v1_enum
+]
+<xsl:text>typedef enum &#x0A;{&#x0A;</xsl:text>
+ <xsl:for-each select="const">
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="concat(../@name,'_',@name)"/> = <xsl:value-of select="@value"/>
+ <xsl:choose>
+ <xsl:when test="position()!=last()"><xsl:text>,&#x0A;</xsl:text></xsl:when>
+ <xsl:otherwise><xsl:text>&#x0A;</xsl:text></xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ <xsl:text>} </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;&#x0A;&#x0A;</xsl:text>
+ <!-- -->
+ <xsl:value-of select="concat('/* cross-platform type name for ', @name, ' */&#x0A;')"/>
+ <xsl:value-of select="concat('cpp_quote(&quot;#define ', @name, '_T', ' ',
+ @name, '&quot;)&#x0A;&#x0A;')"/>
+ <xsl:text>&#x0A;&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * method parameters
+-->
+<xsl:template match="method/param">
+ <xsl:text>[</xsl:text>
+ <xsl:choose>
+ <xsl:when test="@dir='in'">in</xsl:when>
+ <xsl:when test="@dir='out'">out</xsl:when>
+ <xsl:when test="@dir='return'">out, retval</xsl:when>
+ <xsl:otherwise>in</xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>] </xsl:text>
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>SAFEARRAY(</xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@type"/>
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>)</xsl:text>
+ </xsl:if>
+ <xsl:if test="@dir='out' or @dir='return'">
+ <xsl:text> *</xsl:text>
+ </xsl:if>
+ <xsl:text> a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="method/param" mode="forwarder">
+ <xsl:choose>
+ <xsl:when test="@safearray='yes'">
+ <xsl:text>SAFEARRAY *</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="@type"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="@dir='out' or @dir='return' or @safearray='yes'">
+ <xsl:text> *</xsl:text>
+ </xsl:if>
+ <xsl:text> a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+</xsl:template>
+
+
+<!--
+ * attribute/parameter type conversion
+-->
+<xsl:template match="attribute/@type | param/@type">
+ <xsl:variable name="self_target" select="current()/ancestor::if/@target"/>
+
+ <xsl:choose>
+ <!-- modifiers -->
+ <xsl:when test="name(current())='type' and ../@mod">
+ <xsl:choose>
+ <xsl:when test="../@mod='ptr'">
+ <xsl:choose>
+ <!-- standard types -->
+ <!--xsl:when test=".='result'">??</xsl:when-->
+ <xsl:when test=".='boolean'">BOOL *</xsl:when>
+ <xsl:when test=".='octet'">BYTE *</xsl:when>
+ <xsl:when test=".='short'">SHORT *</xsl:when>
+ <xsl:when test=".='unsigned short'">USHORT *</xsl:when>
+ <xsl:when test=".='long'">LONG *</xsl:when>
+ <xsl:when test=".='long long'">LONG64 *</xsl:when>
+ <xsl:when test=".='unsigned long'">ULONG *</xsl:when>
+ <xsl:when test=".='unsigned long long'">
+ <xsl:message terminate="yes">
+ <xsl:value-of select="'&quot;unsigned long long&quot; no longer supported'" />
+ </xsl:message>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:text>attribute 'mod=</xsl:text>
+ <xsl:value-of select="concat('&quot;',../@mod,'&quot;')"/>
+ <xsl:text>' cannot be used with type </xsl:text>
+ <xsl:value-of select="concat('&quot;',current(),'&quot;!')"/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="../@mod='string'">
+ <xsl:choose>
+ <!-- standard types -->
+ <!--xsl:when test=".='result'">??</xsl:when-->
+ <xsl:when test=".='uuid'">BSTR</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:text>attribute 'mod=</xsl:text>
+ <xsl:value-of select="concat('&quot;',../@mod,'&quot;')"/>
+ <xsl:text>' cannot be used with type </xsl:text>
+ <xsl:value-of select="concat('&quot;',current(),'&quot;!')"/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:value-of select="concat('value &quot;',../@mod,'&quot; ')"/>
+ <xsl:text>of attribute 'mod' is invalid!</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <!-- no modifiers -->
+ <xsl:otherwise>
+ <xsl:choose>
+ <!-- standard types -->
+ <xsl:when test=".='result'">HRESULT</xsl:when>
+ <xsl:when test=".='boolean'">BOOL</xsl:when>
+ <xsl:when test=".='octet'">BYTE</xsl:when>
+ <xsl:when test=".='short'">SHORT</xsl:when>
+ <xsl:when test=".='unsigned short'">USHORT</xsl:when>
+ <xsl:when test=".='long'">LONG</xsl:when>
+ <xsl:when test=".='long long'">LONG64</xsl:when>
+ <xsl:when test=".='unsigned long'">ULONG</xsl:when>
+ <xsl:when test=".='char'">CHAR</xsl:when>
+ <xsl:when test=".='string'">CHAR *</xsl:when>
+ <xsl:when test=".='wchar'">OLECHAR</xsl:when>
+ <xsl:when test=".='wstring'">BSTR</xsl:when>
+ <!-- UUID type -->
+ <xsl:when test=".='uuid'">GUID</xsl:when>
+ <!-- system interface types -->
+ <xsl:when test=".='$unknown'">IUnknown *</xsl:when>
+ <xsl:when test=".='unsigned long long'">
+ <xsl:message terminate="yes">
+ <xsl:value-of select="'&quot;unsigned long long&quot; no longer supported'" />
+ </xsl:message>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <!-- enum types -->
+ <xsl:when test="
+ (ancestor::library/application/enum[@name=current()])
+ or (ancestor::library/if/application/enum[@name=current()])
+ or (ancestor::library/application/if[@target=$self_target]/enum[@name=current()])
+ or (ancestor::library/if/application/if[@target=$self_target]/enum[@name=current()])
+ ">
+ <xsl:value-of select="."/>
+ </xsl:when>
+ <!-- custom interface types -->
+ <xsl:when test="
+ ( (ancestor::library/application/interface[@name=current()])
+ or (ancestor::library/if/application/interface[@name=current()])
+ or (ancestor::library/application/if[@target=$self_target]/interface[@name=current()])
+ or (ancestor::library/if/application/if[@target=$self_target]/interface[@name=current()])
+ )
+ ">
+ <xsl:value-of select="."/><xsl:text> *</xsl:text>
+ </xsl:when>
+ <!-- other types -->
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:text>Unknown parameter type: </xsl:text>
+ <xsl:value-of select="."/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+
+<!-- Filters for switch on/off VBoxSDS definitions -->
+
+<xsl:template match="if[@target='midl']/application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']//module/class" >
+ <xsl:if test="$g_fVBoxWithSDS='yes'" >
+ <xsl:call-template name="template_class" />
+ </xsl:if>
+</xsl:template>
+
+<xsl:template match="if[@target='midl']/application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']/if//interface
+ | application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']//interface" >
+ <xsl:if test="$g_fVBoxWithSDS='yes'" >
+ <xsl:call-template name="template_interface" />
+ </xsl:if>
+</xsl:template>
+
+<xsl:template match="if[@target='midl']/application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']//interface" mode="forward" >
+ <xsl:if test="$g_fVBoxWithSDS='yes'" >
+ <xsl:call-template name="template_interface_forward" />
+ </xsl:if>
+</xsl:template>
+
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/idl/stringify-enums.xsl b/src/VBox/Main/idl/stringify-enums.xsl
new file mode 100644
index 00000000..05d4fd9d
--- /dev/null
+++ b/src/VBox/Main/idl/stringify-enums.xsl
@@ -0,0 +1,251 @@
+<?xml version="1.0"?>
+
+<!--
+ stringify-enums.xsl - Generates stringify functions for all the enums in VirtualBox.xidl.
+-->
+<!--
+ Copyright (C) 2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ extension-element-prefixes="exsl">
+
+<xsl:output method="text"/>
+
+<xsl:strip-space elements="*"/>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ Parameters
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:param name="G_kind">source</xsl:param>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+templates for file headers
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template name="fileheader">
+ <xsl:param name="class"/>
+ <xsl:param name="name"/>
+ <xsl:param name="type"/>
+
+ <xsl:text>/** @file
+ * VirtualBox API Enum Stringifier - </xsl:text>
+ <xsl:choose>
+ <xsl:when test="$G_kind = 'header'">Header</xsl:when>
+ <xsl:when test="$G_kind = 'source'">Definition</xsl:when>
+ </xsl:choose>
+<xsl:text>.
+ *
+ * DO NOT EDIT! This is a generated file.
+ * Generated from: src/VBox/Main/idl/VirtualBox.xidl
+ * Generator: src/VBox/Main/idl/stringify-enums.xsl
+ */
+
+/*
+ * Copyright (C) 2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see &lt;https://www.gnu.org/licenses&gt;.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+</xsl:text>
+</xsl:template>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ Emits a function prototype for the header file.
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template name="emitEnumStringifyPrototype">
+ <xsl:text>const char *stringify</xsl:text><xsl:value-of select="@name"/><xsl:text>(</xsl:text>
+ <xsl:value-of select="@name"/><xsl:text>_T aValue) RT_NOEXCEPT;
+</xsl:text>
+</xsl:template>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ Emits a function definition for the source file.
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template name="emitEnumStringifyFunction">
+ <xsl:text>
+
+const char *stringify</xsl:text><xsl:value-of select="@name"/><xsl:text>(</xsl:text>
+ <xsl:value-of select="@name"/><xsl:text>_T aValue) RT_NOEXCEPT
+{
+ switch (aValue)
+ {
+</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text> default:
+ AssertMsgFailedReturn(("%d / %#x\n", aValue, aValue), formatUnknown("</xsl:text>
+ <xsl:value-of select="@name"/><xsl:text>", (int)aValue));
+ }
+}
+</xsl:text>
+</xsl:template>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ wildcard match, ignore everything which has no explicit match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template match="*"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ ignore all if tags except those for XPIDL or MIDL target
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template match="if">
+ <xsl:if test="(@target = 'xpidl') or (@target = 'midl')">
+ <xsl:apply-templates/>
+ </xsl:if>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ const match - emits case statemtns
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template match="const">
+ <!-- HACK ALERT! There are 4 enums in MachineState that have duplicate values,
+ we exploit the existing @wsmap="suppress" markers on these to
+ avoid generating code which doesn't compile. -->
+ <xsl:if test="not(@wsmap) or @wsmap != 'suppress'">
+ <xsl:text> case </xsl:text><xsl:value-of select="../@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:value-of select="@name"/><xsl:text>:
+ return "</xsl:text><xsl:value-of select="@name"/><xsl:text>";
+</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ enum match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template match="enum">
+ <xsl:choose>
+ <xsl:when test="$G_kind = 'header'">
+ <xsl:call-template name="emitEnumStringifyPrototype"/>
+ </xsl:when>
+ <xsl:when test="$G_kind = 'source'">
+ <xsl:call-template name="emitEnumStringifyFunction"/>
+ </xsl:when>
+ </xsl:choose>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ application match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template match="application">
+ <xsl:apply-templates/>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ library match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template match="library">
+ <xsl:apply-templates/>
+</xsl:template>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ root match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+<xsl:template match="/idl">
+ <xsl:choose>
+ <xsl:when test="$G_kind = 'header'">
+ <xsl:call-template name="fileheader"/>
+ <xsl:text>
+#ifndef INCLUDED_GENERATED_StringifyEnums_h
+#define INCLUDED_GENERATED_StringifyEnums_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBox/com/VirtualBox.h"
+
+</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>
+#endif /* INCLUDED_GENERATED_StringifyEnums_h */
+</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="$G_kind = 'source'">
+ <xsl:call-template name="fileheader"/>
+ <xsl:text>
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "StringifyEnums.h"
+
+#include "iprt/asm.h"
+#include "iprt/assert.h"
+#include "iprt/string.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+typedef char UNKNOWNBUF[64];
+static UNKNOWNBUF s_aszUnknown[16];
+static uint32_t volatile s_iUnknown = 0;
+
+
+static const char *formatUnknown(const char *pszName, int iValue)
+{
+ size_t iUnknown = ASMAtomicIncU32(&amp;s_iUnknown) % RT_ELEMENTS(s_aszUnknown);
+ char *pszBuf = s_aszUnknown[iUnknown];
+ RTStrPrintf(pszBuf, sizeof(UNKNOWNBUF), "Unk-%s-%#x", pszName, iValue);
+ return pszBuf;
+}
+
+</xsl:text>
+ <xsl:apply-templates/>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ Unknown string parameter value: G_kind='<xsl:value-of select="$G_kind"/>'
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+</xsl:stylesheet>
+<!-- vi: set tabstop=4 shiftwidth=4 expandtab: -->
diff --git a/src/VBox/Main/idl/typemap-shared.inc.xsl b/src/VBox/Main/idl/typemap-shared.inc.xsl
new file mode 100644
index 00000000..1397c90e
--- /dev/null
+++ b/src/VBox/Main/idl/typemap-shared.inc.xsl
@@ -0,0 +1,571 @@
+<!--
+ typemap-shared.inc.xsl:
+ this gets included from other XSLT stylesheets including those
+ for the webservice, so we can share some definitions that must
+ be the same for all of them (like method prefixes/suffices).
+-->
+<!--
+ Copyright (C) 2006-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+
+<xsl:stylesheet
+ version="1.0"
+ targetNamespace="http://schemas.xmlsoap.org/wsdl/"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+ xmlns:vbox="http://www.virtualbox.org/">
+
+<xsl:variable name="G_xsltIncludeFilename" select="'typemap-shared.inc.xsl'" />
+
+<xsl:variable name="G_lowerCase" select="'abcdefghijklmnopqrstuvwxyz'" />
+<xsl:variable name="G_upperCase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
+<xsl:variable name="G_sNewLine">
+ <xsl:text>
+</xsl:text>
+</xsl:variable>
+
+<!-- List of white space characters that the strip functions will remove -->
+<xsl:variable name="G_sWhiteSpace" select="' &#10;&#13;&#09;'"/>
+
+<!-- target namespace; this must match the xmlns:vbox in stylesheet opening tags! -->
+<xsl:variable name="G_targetNamespace"
+ select='"http://www.virtualbox.org/"' />
+<xsl:variable name="G_targetNamespaceSeparator"
+ select='""' />
+
+<!-- ENCODING SCHEME
+
+ See: http://www-128.ibm.com/developerworks/webservices/library/ws-whichwsdl/
+
+ Essentially "document" style means that each SOAP message is a complete and
+ self-explanatory document that does not rely on outside information for
+ validation.
+
+ By contrast, the (older) "RPC" style allows for much shorter SOAP messages
+ that do not contain validation info like all types that are used, but then
+ again, caller and receiver must have agreed on a valid format in some other way.
+ With RPC, WSDL typically looks like this:
+
+ <message name="myMethodRequest">
+ <part name="x" type="xsd:int"/>
+ <part name="y" type="xsd:float"/>
+ </message>
+
+ This is why today "document" style is preferred. However, with document style,
+ one _cannot_ use "type" in <part> elements. Instead, one must use "element"
+ attributes that refer to <element> items in the type section. Like this:
+
+ <types>
+ <schema>
+ <element name="xElement" type="xsd:int"/>
+ <element name="yElement" type="xsd:float"/>
+ </schema>
+ </types>
+
+ <message name="myMethodRequest">
+ <part name="x" element="xElement"/>
+ <part name="y" element="yElement"/>
+ </message>
+
+ The "encoded" and "literal" sub-styles then only determine whether the
+ individual types in the soap messages carry additional information in
+ attributes. "Encoded" was only used with RPC styles, really, and even that
+ is not widely supported any more.
+
+-->
+<!-- These are the settings: all the other XSLTs react on this and are supposed
+ to be able to generate both valid RPC and document-style code. The only
+ allowed values are 'rpc' or 'document'. -->
+<xsl:variable name="G_basefmt"
+ select='"document"' />
+<xsl:variable name="G_parmfmt"
+ select='"literal"' />
+<!-- <xsl:variable name="G_basefmt"
+ select='"rpc"' />
+<xsl:variable name="G_parmfmt"
+ select='"encoded"' /> -->
+
+<!-- with document style, this is how we name the request and return element structures -->
+<xsl:variable name="G_requestElementVarName"
+ select='"req"' />
+<xsl:variable name="G_responseElementVarName"
+ select='"resp"' />
+<!-- this is how we name the result parameter in messages -->
+<xsl:variable name="G_result"
+ select='"returnval"' />
+
+<!-- we represent interface attributes by creating "get" and "set" methods; these
+ are the prefixes we use for that -->
+<xsl:variable name="G_attributeGetPrefix"
+ select='"get"' />
+<xsl:variable name="G_attributeSetPrefix"
+ select='"set"' />
+<!-- separator between class name and method/attribute name; would be "::" in C++
+ but i'm unsure whether WSDL appreciates that (WSDL only) -->
+<xsl:variable name="G_classSeparator"
+ select='"_"' />
+<!-- for each interface method, we need to create both a "request" and a "response"
+ message; these are the suffixes we append to the method names for that -->
+<xsl:variable name="G_methodRequest"
+ select='"RequestMsg"' />
+<xsl:variable name="G_methodResponse"
+ select='"ResultMsg"' />
+<!-- suffix for element declarations that describe request message parameters (WSDL only) -->
+<xsl:variable name="G_requestMessageElementSuffix"
+ select='""' />
+<!-- suffix for element declarations that describe request message parameters (WSDL only) -->
+<xsl:variable name="G_responseMessageElementSuffix"
+ select='"Response"' />
+<!-- suffix for portType names (WSDL only) -->
+<xsl:variable name="G_portTypeSuffix"
+ select='"PortType"' />
+<!-- suffix for binding names (WSDL only) -->
+<xsl:variable name="G_bindingSuffix"
+ select='"Binding"' />
+<!-- schema type to use for object references; while it is theoretically
+ possible to use a self-defined type (e.g. some vboxObjRef type that's
+ really an int), gSOAP gets a bit nasty and creates complicated structs
+ for function parameters when these types are used as output parameters.
+ So we just use "int" even though it's not as lucid.
+ One setting is for the WSDL emitter, one for the C++ emitter -->
+<!--
+<xsl:variable name="G_typeObjectRef"
+ select='"xsd:unsignedLong"' />
+<xsl:variable name="G_typeObjectRef_gsoapH"
+ select='"ULONG64"' />
+<xsl:variable name="G_typeObjectRef_CPP"
+ select='"WSDLT_ID"' />
+-->
+<xsl:variable name="G_typeObjectRef"
+ select='"xsd:string"' />
+<xsl:variable name="G_typeObjectRef_gsoapH"
+ select='"std::string"' />
+<xsl:variable name="G_typeObjectRef_CPP"
+ select='"std::string"' />
+<!-- and what to call first the object parameter -->
+<xsl:variable name="G_nameObjectRef"
+ select='"_this"' />
+<!-- gSOAP encodes underscores with USCORE so this is used in our C++ code -->
+<xsl:variable name="G_nameObjectRefEncoded"
+ select='"_USCOREthis"' />
+
+<!-- type to represent enums within C++ COM callers -->
+<xsl:variable name="G_funcPrefixInputEnumConverter"
+ select='"EnumSoap2Com_"' />
+<xsl:variable name="G_funcPrefixOutputEnumConverter"
+ select='"EnumCom2Soap_"' />
+
+<!-- type to represent structs within C++ COM callers -->
+<xsl:variable name="G_funcPrefixOutputStructConverter"
+ select='"StructCom2Soap_"' />
+
+<xsl:variable name="G_aSharedTypes">
+ <type idlname="octet" xmlname="unsignedByte" cname="unsigned char" gluename="BYTE" gluefmt="%RU8" javaname="byte" dtracename="uint8_t" />
+ <type idlname="boolean" xmlname="boolean" cname="bool" gluename="BOOL" gluefmt="%RTbool" javaname="Boolean" dtracename="int8_t" />
+ <type idlname="short" xmlname="short" cname="short" gluename="SHORT" gluefmt="%RI16" javaname="Short" dtracename="int16_t" />
+ <type idlname="unsigned short" xmlname="unsignedShort" cname="unsigned short" gluename="USHORT" gluefmt="%RU16" javaname="Integer" dtracename="uint16_t" />
+ <type idlname="long" xmlname="int" cname="int" gluename="LONG" gluefmt="%RI32" javaname="Integer" dtracename="int32_t" />
+ <type idlname="unsigned long" xmlname="unsignedInt" cname="unsigned int" gluename="ULONG" gluefmt="%RU32" javaname="Long" dtracename="uint32_t" />
+ <type idlname="long long" xmlname="long" cname="LONG64" gluename="LONG64" gluefmt="%RI64" javaname="Long" dtracename="int64_t" />
+ <type idlname="unsigned long long" xmlname="unsignedLong" cname="ULONG64" gluename="ULONG64" gluefmt="%RU64" javaname="BigInteger" dtracename="uint64_t" />
+ <type idlname="double" xmlname="double" cname="double" gluename="DOUBLE" gluefmt="%#RX64" javaname="Double" dtracename="double" />
+ <type idlname="float" xmlname="float" cname="float" gluename="FLOAT" gluefmt="%#RX32" javaname="Float" dtracename="float" />
+ <type idlname="wstring" xmlname="string" cname="std::string" gluename="BSTR" gluefmt="%ls" javaname="String" dtracename="const char *"/>
+ <type idlname="uuid" xmlname="string" cname="std::string" gluename="BSTR" gluefmt="%ls" javaname="String" dtracename="const char *"/>
+ <type idlname="result" xmlname="unsignedInt" cname="unsigned int" gluename="HRESULT" gluefmt="%Rhrc" javaname="Long" dtracename="int32_t" />
+</xsl:variable>
+
+<!--
+ warning:
+ -->
+
+<xsl:template name="warning">
+ <xsl:param name="msg" />
+
+ <xsl:message terminate="no">
+ <xsl:value-of select="concat('[', $G_xsltFilename, '] Warning in ', $msg)" />
+ </xsl:message>
+</xsl:template>
+
+<!--
+ fatalError:
+ -->
+
+<xsl:template name="fatalError">
+ <xsl:param name="msg" />
+
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat('[', $G_xsltFilename, '] Error in ', $msg)" />
+ </xsl:message>
+</xsl:template>
+
+<!--
+ debugMsg
+ -->
+
+<xsl:template name="debugMsg">
+ <xsl:param name="msg" />
+
+ <xsl:if test="$G_argDebug">
+ <xsl:message terminate="no">
+ <xsl:value-of select="concat('[', $G_xsltFilename, '] ', $msg)" />
+ </xsl:message>
+ </xsl:if>
+</xsl:template>
+
+<!--
+ uncapitalize
+ -->
+
+<xsl:template name="uncapitalize">
+ <xsl:param name="str" select="."/>
+ <xsl:value-of select="
+ concat(
+ translate(substring($str,1,1),$G_upperCase,$G_lowerCase),
+ substring($str,2)
+ )
+ "/>
+</xsl:template>
+<!--
+ uncapitalize in the way JAX-WS understands, see #2910
+ -->
+
+<xsl:template name="uncapitalize2">
+ <xsl:param name="str" select="."/>
+ <xsl:variable name="strlen">
+ <xsl:value-of select="string-length($str)"/>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="$strlen>1">
+ <xsl:choose>
+ <xsl:when test="contains($G_upperCase,substring($str,1,1))
+ and
+ contains($G_upperCase,substring($str,2,1))">
+ <xsl:variable name="cdr">
+ <xsl:call-template name="uncapitalize2">
+ <xsl:with-param name="str" select="substring($str,2)"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="
+ concat(
+ translate(substring($str,1,1),
+ $G_upperCase,
+ $G_lowerCase),
+ $cdr
+ )
+ "/>
+ </xsl:when>
+ <xsl:otherwise>
+ <!--<xsl:value-of select="concat(substring($str,1,1),$cdr)"/>-->
+ <xsl:value-of select="$str"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="$strlen=1">
+ <xsl:value-of select="
+ translate($str,
+ $G_upperCase,
+ $G_lowerCase)
+ "/>
+ </xsl:when>
+ <xsl:otherwise>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+<!--
+ capitalize
+ -->
+
+<xsl:template name="capitalize">
+ <xsl:param name="str" select="."/>
+ <xsl:value-of select="
+ concat(
+ translate(substring($str,1,1),$G_lowerCase,$G_upperCase),
+ substring($str,2)
+ )
+ "/>
+</xsl:template>
+
+<!--
+ makeGetterName:
+ -->
+<xsl:template name="makeGetterName">
+ <xsl:param name="attrname" />
+ <xsl:variable name="capsname"><xsl:call-template name="capitalize"><xsl:with-param name="str" select="$attrname" /></xsl:call-template></xsl:variable>
+ <xsl:value-of select="concat($G_attributeGetPrefix, $capsname)" />
+</xsl:template>
+
+<!--
+ makeSetterName:
+ -->
+<xsl:template name="makeSetterName">
+ <xsl:param name="attrname" />
+ <xsl:variable name="capsname"><xsl:call-template name="capitalize"><xsl:with-param name="str" select="$attrname" /></xsl:call-template></xsl:variable>
+ <xsl:value-of select="concat($G_attributeSetPrefix, $capsname)" />
+</xsl:template>
+
+<!--
+ makeJaxwsMethod: compose idevInterfaceMethod out of IDEVInterface::method
+ -->
+<xsl:template name="makeJaxwsMethod">
+ <xsl:param name="ifname" />
+ <xsl:param name="methodname" />
+ <xsl:variable name="uncapsif"><xsl:call-template name="uncapitalize2"><xsl:with-param name="str" select="$ifname" /></xsl:call-template></xsl:variable>
+ <xsl:variable name="capsmethod"><xsl:call-template name="capitalize"><xsl:with-param name="str" select="$methodname" /></xsl:call-template></xsl:variable>
+ <xsl:value-of select="concat($uncapsif, $capsmethod)" />
+</xsl:template>
+
+
+<!--
+ makeJaxwsMethod2: compose iInterfaceMethod out of IInterface::method
+ -->
+<xsl:template name="makeJaxwsMethod2">
+ <xsl:param name="ifname" />
+ <xsl:param name="methodname" />
+ <xsl:variable name="uncapsif"><xsl:call-template name="uncapitalize"><xsl:with-param name="str" select="$ifname" /></xsl:call-template></xsl:variable>
+ <xsl:variable name="capsmethod"><xsl:call-template name="capitalize"><xsl:with-param name="str" select="$methodname" /></xsl:call-template></xsl:variable>
+ <xsl:value-of select="concat($uncapsif, $capsmethod)" />
+</xsl:template>
+
+<!--
+ emitNewline:
+ -->
+<xsl:template name="emitNewline">
+ <xsl:text>
+</xsl:text>
+</xsl:template>
+
+<!--
+ emitNewlineIndent8:
+ -->
+<xsl:template name="emitNewlineIndent8">
+ <xsl:text>
+ </xsl:text>
+</xsl:template>
+
+<!--
+ escapeUnderscores
+ -->
+<xsl:template name="escapeUnderscores">
+ <xsl:param name="string" />
+ <xsl:if test="contains($string, '_')">
+ <xsl:value-of select="substring-before($string, '_')" />_USCORE<xsl:call-template name="escapeUnderscores"><xsl:with-param name="string"><xsl:value-of select="substring-after($string, '_')" /></xsl:with-param></xsl:call-template>
+ </xsl:if>
+ <xsl:if test="not(contains($string, '_'))"><xsl:value-of select="$string" />
+ </xsl:if>
+</xsl:template>
+
+<!--
+ xsltprocNewlineOutputHack - emits a single new line.
+
+ Hack Alert! This template helps xsltproc split up the output text elements
+ and avoid reallocating them into the MB range. Calls to this
+ template is made occationally while generating larger output
+ file. It's not necessary for small stuff like header.
+
+ The trick we're playing on xsltproc has to do with CDATA
+ and/or the escape setting of the xsl:text element. It forces
+ xsltproc to allocate a new output element, thus preventing
+ things from growing out of proportions and slowing us down.
+
+ This was successfully employed to reduce a 18+ seconds run to
+ around one second (possibly less due to kmk overhead).
+ -->
+<xsl:template name="xsltprocNewlineOutputHack">
+ <xsl:text disable-output-escaping="yes"><![CDATA[
+]]></xsl:text>
+</xsl:template>
+
+
+<!--
+ string-to-upper - translates the string to uppercase.
+ -->
+<xsl:template name="string-to-upper">
+ <xsl:param name="str" select="."/>
+ <xsl:value-of select="translate($str, $G_lowerCase, $G_upperCase)"/>
+</xsl:template>
+
+<!--
+ string-to-lower - translates the string to lowercase.
+ -->
+<xsl:template name="string-to-lower">
+ <xsl:param name="str" select="."/>
+ <xsl:value-of select="translate($str, $G_upperCase, $G_lowerCase)"/>
+</xsl:template>
+
+<!--
+ string-replace - Replace all occurencees of needle in haystack.
+ -->
+<xsl:template name="string-replace">
+ <xsl:param name="haystack"/>
+ <xsl:param name="needle"/>
+ <xsl:param name="replacement"/>
+ <xsl:param name="onlyfirst" select="false"/>
+ <xsl:choose>
+ <xsl:when test="contains($haystack, $needle)">
+ <xsl:value-of select="substring-before($haystack, $needle)"/>
+ <xsl:value-of select="$replacement"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="haystack" select="substring-after($haystack, $needle)"/>
+ <xsl:with-param name="needle" select="$needle"/>
+ <xsl:with-param name="replacement" select="$replacement"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$haystack"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+ string-replace-first - Replace the _first_ occurence of needle in haystack.
+ -->
+<xsl:template name="string-replace-first">
+ <xsl:param name="haystack"/>
+ <xsl:param name="needle"/>
+ <xsl:param name="replacement"/>
+ <xsl:choose>
+ <xsl:when test="contains($haystack, $needle)">
+ <xsl:value-of select="substring-before($haystack, $needle)"/>
+ <xsl:value-of select="$replacement"/>
+ <xsl:value-of select="substring-after($haystack, $needle)"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$haystack"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+ strip-string-right - String trailing white space from a string.
+ -->
+<xsl:template name="strip-string-right">
+ <xsl:param name="text"/>
+
+ <!-- Check for trailing whitespace. -->
+ <xsl:choose>
+ <xsl:when test="contains($G_sWhiteSpace, substring($text, string-length($text), 1))">
+ <xsl:call-template name="strip-string-right">
+ <xsl:with-param name="text" select="substring($text, 1, string-length($text) - 1)"/>
+ </xsl:call-template>
+ </xsl:when>
+
+ <!-- No trailing white space. Return the string. -->
+ <xsl:otherwise>
+ <xsl:value-of select="$text"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+ strip-string-left - String leading white space from a string.
+ -->
+<xsl:template name="strip-string-left">
+ <xsl:param name="text"/>
+
+ <!-- Check for leading white space. To optimize for speed, we check a couple
+ of longer space sequences first. -->
+ <xsl:choose>
+ <xsl:when test="starts-with($text, ' ')"> <!-- 8 leading spaces -->
+ <xsl:call-template name="strip-string-left">
+ <xsl:with-param name="text" select="substring($text, 9)"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="starts-with($text, ' ')"> <!-- 4 leading spaces -->
+ <xsl:call-template name="strip-string-left">
+ <xsl:with-param name="text" select="substring($text, 5)"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="starts-with($text, ' ')"> <!-- 2 leading spaces -->
+ <xsl:call-template name="strip-string-left">
+ <xsl:with-param name="text" select="substring($text, 3)"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="contains($G_sWhiteSpace, substring($text, 1, 1))">
+ <xsl:if test="string-length($text) > 0">
+ <xsl:call-template name="strip-string">
+ <xsl:with-param name="text" select="substring($text, 2)"/>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:when>
+
+ <!-- No leading white space. Return the string. -->
+ <xsl:otherwise>
+ <xsl:value-of select="$text"/>
+ </xsl:otherwise>
+ </xsl:choose>
+
+</xsl:template>
+
+<!--
+ strip-string - String leading and trailing white space from a string.
+ -->
+<xsl:template name="strip-string">
+ <xsl:param name="text"/>
+
+ <!-- Check for leading white space. To optimize for speed, we check a couple
+ of longer space sequences first. -->
+ <xsl:choose>
+ <xsl:when test="starts-with($text, ' ')"> <!-- 8 leading spaces -->
+ <xsl:call-template name="strip-string">
+ <xsl:with-param name="text" select="substring($text, 9)"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="starts-with($text, ' ')"> <!-- 4 leading spaces -->
+ <xsl:call-template name="strip-string">
+ <xsl:with-param name="text" select="substring($text, 5)"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="starts-with($text, ' ')"> <!-- 2 leading spaces -->
+ <xsl:call-template name="strip-string">
+ <xsl:with-param name="text" select="substring($text, 3)"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="contains($G_sWhiteSpace, substring($text, 1, 1))">
+ <xsl:if test="string-length($text) > 0">
+ <xsl:call-template name="strip-string">
+ <xsl:with-param name="text" select="substring($text, 2)"/>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:when>
+
+ <!-- Then check for trailing whitespace. -->
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="contains($G_sWhiteSpace, substring($text, string-length($text), 1))">
+ <xsl:call-template name="strip-string-right">
+ <xsl:with-param name="text" select="substring($text, 1, string-length($text) - 1)"/>
+ </xsl:call-template>
+ </xsl:when>
+
+ <!-- No leading or trailing white space. Return the string. -->
+ <xsl:otherwise>
+ <xsl:value-of select="$text"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/idl/xpidl.xsl b/src/VBox/Main/idl/xpidl.xsl
new file mode 100644
index 00000000..ed571185
--- /dev/null
+++ b/src/VBox/Main/idl/xpidl.xsl
@@ -0,0 +1,1124 @@
+<?xml version="1.0"?>
+<!-- $Id: xpidl.xsl $ -->
+
+<!--
+ * A template to generate a XPCOM IDL compatible interface definition file
+ * from the generic interface definition expressed in XML.
+-->
+<!--
+ Copyright (C) 2006-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="text"/>
+
+<xsl:strip-space elements="*"/>
+
+<xsl:include href="typemap-shared.inc.xsl"/>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ XSLT parameters
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<!-- xpidl doesn't support enums. This parameter performs certain hacks that helps
+ us bridge the gap and get similar behaviour as msidl.
+
+ The https://bugzilla.mozilla.org/show_bug.cgi?id=8781 bug discusses why xpidl
+ doesn't do enums. It boils down to the gcc short-enum option and similar
+ portability concerns.
+ -->
+<xsl:param name="g_fHackEnumsOntoCppEnums" select="'yes'"/>
+
+
+<!--
+// templates
+/////////////////////////////////////////////////////////////////////////////
+-->
+
+
+<!--
+ * not explicitly matched elements and attributes
+-->
+<xsl:template match="*"/>
+
+
+<!--
+ * header
+-->
+<xsl:template match="/idl">
+ <xsl:text>
+/*
+ * DO NOT EDIT! This is a generated file.
+ *
+ * XPCOM IDL (XPIDL) definition for VirtualBox Main API (COM interfaces)
+ * generated from XIDL (XML interface definition).
+ *
+ * Source : src/VBox/Main/idl/VirtualBox.xidl
+ * Generator : src/VBox/Main/idl/xpidl.xsl
+ */
+
+#include "nsISupports.idl"
+#include "nsIException.idl"
+
+</xsl:text>
+ <!-- native typedefs for the 'mod="ptr"' attribute -->
+ <xsl:text>
+[ptr] native booleanPtr (PRBool);
+[ptr] native octetPtr (PRUint8);
+[ptr] native shortPtr (PRInt16);
+[ptr] native ushortPtr (PRUint16);
+[ptr] native longPtr (PRInt32);
+[ptr] native llongPtr (PRInt64);
+[ptr] native ulongPtr (PRUint32);
+[ptr] native ullongPtr (PRUint64);
+<!-- charPtr is already defined in nsrootidl.idl -->
+<!-- [ptr] native charPtr (char) -->
+[ptr] native stringPtr (string);
+[ptr] native wcharPtr (wchar);
+[ptr] native wstringPtr (wstring);
+
+</xsl:text>
+ <xsl:apply-templates/>
+</xsl:template>
+
+
+<!--
+ * ignore all |if|s except those for XPIDL target
+-->
+<xsl:template match="if">
+ <xsl:if test="@target='xpidl'">
+ <xsl:apply-templates/>
+ </xsl:if>
+</xsl:template>
+<xsl:template match="if" mode="forward">
+ <xsl:if test="@target='xpidl'">
+ <xsl:apply-templates mode="forward"/>
+ </xsl:if>
+</xsl:template>
+<xsl:template match="if" mode="forwarder">
+ <xsl:if test="@target='midl'">
+ <xsl:apply-templates mode="forwarder"/>
+ </xsl:if>
+</xsl:template>
+
+
+<!--
+ * cpp_quote
+-->
+<xsl:template match="cpp">
+ <xsl:if test="text()">
+ <xsl:text>%{C++</xsl:text>
+ <xsl:value-of select="text()"/>
+ <xsl:text>&#x0A;%}&#x0A;&#x0A;</xsl:text>
+ </xsl:if>
+ <xsl:if test="not(text()) and @line">
+ <xsl:text>%{C++&#x0A;</xsl:text>
+ <xsl:value-of select="@line"/>
+ <xsl:text>&#x0A;%}&#x0A;&#x0A;</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+
+<!--
+ * #if statement (@if attribute)
+ * @note
+ * xpidl doesn't support any preprocessor defines other than #include
+ * (it just ignores them), so the generated IDL will most likely be
+ * invalid. So for now we forbid using @if attributes
+-->
+<xsl:template match="@if" mode="begin">
+ <xsl:message terminate="yes">
+ @if attributes are not currently allowed because xpidl lacks
+ support for #ifdef and stuff.
+ </xsl:message>
+ <xsl:text>#if </xsl:text>
+ <xsl:value-of select="."/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+<xsl:template match="@if" mode="end">
+ <xsl:text>#endif&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * libraries
+-->
+<xsl:template match="library">
+ <xsl:text>%{C++&#x0A;</xsl:text>
+ <xsl:text>#ifndef VBOX_EXTERN_C&#x0A;</xsl:text>
+ <xsl:text># ifdef __cplusplus&#x0A;</xsl:text>
+ <xsl:text># define VBOX_EXTERN_C extern "C"&#x0A;</xsl:text>
+ <xsl:text># else // !__cplusplus&#x0A;</xsl:text>
+ <xsl:text># define VBOX_EXTERN_C extern&#x0A;</xsl:text>
+ <xsl:text># endif // !__cplusplus&#x0A;</xsl:text>
+ <xsl:text>#endif // !VBOX_EXTERN_C&#x0A;</xsl:text>
+ <!-- result codes -->
+ <xsl:text>// result codes declared in API spec&#x0A;</xsl:text>
+ <xsl:for-each select="application/result">
+ <xsl:apply-templates select="."/>
+ </xsl:for-each>
+ <xsl:text>%}&#x0A;&#x0A;</xsl:text>
+ <!-- forward declarations -->
+ <xsl:apply-templates select="application/if | application/interface" mode="forward"/>
+ <xsl:text>&#x0A;</xsl:text>
+ <!-- all enums go first -->
+ <xsl:apply-templates select="application/enum | application/if/enum"/>
+ <!-- everything else but result codes and enums
+ <xsl:apply-templates select="*[not(self::application/result or self::application/enum) and
+ not(self::application[result] or self::application/if[enum])]"/> -->
+ <!-- the modules (i.e. everything else) -->
+ <xsl:apply-templates select="application/interface | application/if[interface]
+ | application/module | application/if[module]"/>
+ <!-- -->
+</xsl:template>
+
+
+ <!--
+ * applications
+-->
+<xsl:template match="application">
+ <xsl:apply-templates/>
+</xsl:template>
+<xsl:template match="application" mode="forward">
+ <xsl:apply-templates mode="forward"/>
+</xsl:template>
+
+<!--
+ * result codes
+-->
+<xsl:template match="result">
+ <xsl:value-of select="concat('#define ',@name,' ((nsresult)',@value, ')')"/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * forward declarations
+-->
+<xsl:template match="interface" mode="forward">
+ <xsl:text>interface </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * interfaces
+-->
+<xsl:template match="interface">[
+ uuid(<xsl:value-of select="@uuid"/>),
+ scriptable
+]
+<xsl:text>interface </xsl:text>
+ <xsl:variable name="name" select="@name"/>
+ <xsl:value-of select="$name"/>
+ <xsl:text> : </xsl:text>
+ <xsl:choose>
+ <xsl:when test="@extends='$unknown'">nsISupports</xsl:when>
+ <xsl:when test="@extends='$errorinfo'">nsIException</xsl:when>
+ <xsl:otherwise><xsl:value-of select="@extends"/></xsl:otherwise>
+ </xsl:choose>
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+ <xsl:text>{&#x0A;</xsl:text>
+ <!-- attributes (properties) -->
+ <xsl:apply-templates select="attribute"/>
+ <xsl:variable name="reservedAttributes" select="@reservedAttributes"/>
+ <xsl:if test="$reservedAttributes > 0">
+ <!-- tricky way to do a "for" loop without recursion -->
+ <xsl:for-each select="(//*)[position() &lt;= $reservedAttributes]">
+ <xsl:text> readonly attribute unsigned long InternalAndReservedAttribute</xsl:text>
+ <xsl:value-of select="concat(position(), $name)"/>
+ <xsl:text>;&#x0A;&#x0A;</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+ <!-- methods -->
+ <xsl:apply-templates select="method"/>
+ <xsl:variable name="reservedMethods" select="@reservedMethods"/>
+ <xsl:if test="$reservedMethods > 0">
+ <!-- tricky way to do a "for" loop without recursion -->
+ <xsl:for-each select="(//*)[position() &lt;= $reservedMethods]">
+ <xsl:text> void InternalAndReservedMethod</xsl:text>
+ <xsl:value-of select="concat(position(), $name)"/>
+ <xsl:text>();&#x0A;&#x0A;</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+ <!-- 'if' enclosed elements, unsorted -->
+ <xsl:apply-templates select="if"/>
+ <!-- -->
+ <xsl:text>}; /* interface </xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text> */&#x0A;&#x0A;</xsl:text>
+ <!-- Interface implementation forwarder macro -->
+ <xsl:text>/* Interface implementation forwarder macro */&#x0A;</xsl:text>
+ <xsl:text>%{C++&#x0A;</xsl:text>
+ <!-- 1) individual methods -->
+ <xsl:apply-templates select="attribute" mode="forwarder"/>
+ <xsl:apply-templates select="method" mode="forwarder"/>
+ <xsl:apply-templates select="if" mode="forwarder"/>
+ <!-- 2) COM_FORWARD_Interface_TO(smth) -->
+ <xsl:text>#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>_TO(smth) NS_FORWARD_</xsl:text>
+ <xsl:call-template name="string-to-upper">
+ <xsl:with-param name="str" select="$name"/>
+ </xsl:call-template>
+ <xsl:text> (smth)&#x0A;</xsl:text>
+ <!-- 3) COM_FORWARD_Interface_TO_OBJ(obj) -->
+ <xsl:text>#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>_TO_OBJ(obj) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>_TO ((obj)->)&#x0A;</xsl:text>
+ <!-- 4) COM_FORWARD_Interface_TO_BASE(base) -->
+ <xsl:text>#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>_TO_BASE(base) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>_TO (base::)&#x0A;&#x0A;</xsl:text>
+ <!-- -->
+ <xsl:text>// for compatibility with Win32&#x0A;</xsl:text>
+ <xsl:text>VBOX_EXTERN_C const nsID IID_</xsl:text>
+ <xsl:value-of select="$name"/>
+ <xsl:text>;&#x0A;</xsl:text>
+ <xsl:text>%}&#x0A;&#x0A;</xsl:text>
+ <!-- end -->
+</xsl:template>
+
+
+<!--
+ * attributes
+-->
+<xsl:template match="interface//attribute">
+ <xsl:apply-templates select="@if" mode="begin"/>
+ <xsl:if test="@mod='ptr'">
+ <!-- attributes using native types must be non-scriptable -->
+ <xsl:text> [noscript]&#x0A;</xsl:text>
+ </xsl:if>
+ <xsl:choose>
+ <!-- safearray pseudo attribute -->
+ <xsl:when test="@safearray='yes'">
+ <!-- getter -->
+ <xsl:text> void get</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> (&#x0A;</xsl:text>
+ <!-- array size -->
+ <xsl:text> out unsigned long </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>Size,&#x0A;</xsl:text>
+ <!-- array pointer -->
+ <xsl:text> [array, size_is(</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>Size), retval] out </xsl:text>
+ <xsl:apply-templates select="@type"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>&#x0A; );&#x0A;</xsl:text>
+ <!-- setter -->
+ <xsl:if test="not(@readonly='yes')">
+ <xsl:text> void set</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> (&#x0A;</xsl:text>
+ <!-- array size -->
+ <xsl:text> in unsigned long </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>Size,&#x0A;</xsl:text>
+ <!-- array pointer -->
+ <xsl:text> [array, size_is(</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>Size)] in </xsl:text>
+ <xsl:apply-templates select="@type"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>&#x0A; );&#x0A;</xsl:text>
+ </xsl:if>
+ </xsl:when>
+ <!-- normal attribute -->
+ <xsl:otherwise>
+ <xsl:text> </xsl:text>
+ <xsl:if test="@readonly='yes'">
+ <xsl:text>readonly </xsl:text>
+ </xsl:if>
+ <xsl:text>attribute </xsl:text>
+ <xsl:apply-templates select="@type"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;&#x0A;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:apply-templates select="@if" mode="end"/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+<xsl:template match="interface//attribute" mode="forwarder">
+
+ <xsl:variable name="parent" select="ancestor::interface"/>
+
+ <xsl:apply-templates select="@if" mode="begin"/>
+
+ <!-- getter: COM_FORWARD_Interface_GETTER_Name_TO(smth) -->
+ <xsl:text>#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_GETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO(smth) NS_IMETHOD Get</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> (</xsl:text>
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>PRUint32 * a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>Size, </xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:if test="@safearray='yes'">
+ <xsl:text> *</xsl:text>
+ </xsl:if>
+ <xsl:text> * a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>) { return smth Get</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> (</xsl:text>
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>Size, </xsl:text>
+ </xsl:if>
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>); }&#x0A;</xsl:text>
+ <!-- getter: COM_FORWARD_Interface_GETTER_Name_TO_OBJ(obj) -->
+ <xsl:text>#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_GETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO_OBJ(obj) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_GETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO ((obj)->)&#x0A;</xsl:text>
+ <!-- getter: COM_FORWARD_Interface_GETTER_Name_TO_BASE(base) -->
+ <xsl:text>#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_GETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO_BASE(base) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_GETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO (base::)&#x0A;</xsl:text>
+ <!-- -->
+ <xsl:if test="not(@readonly='yes')">
+ <!-- setter: COM_FORWARD_Interface_SETTER_Name_TO(smth) -->
+ <xsl:text>#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_SETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO(smth) NS_IMETHOD Set</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> (</xsl:text>
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>PRUint32 a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>Size, </xsl:text>
+ </xsl:if>
+ <xsl:if test="not(@safearray='yes') and (@type='string' or @type='wstring')">
+ <xsl:text>const </xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:if test="@safearray='yes'">
+ <xsl:text> *</xsl:text>
+ </xsl:if>
+ <xsl:text> a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>) { return smth Set</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> (a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>); }&#x0A;</xsl:text>
+ <!-- setter: COM_FORWARD_Interface_SETTER_Name_TO_OBJ(obj) -->
+ <xsl:text>#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_SETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO_OBJ(obj) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_SETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO ((obj)->)&#x0A;</xsl:text>
+ <!-- setter: COM_FORWARD_Interface_SETTER_Name_TO_BASE(base) -->
+ <xsl:text>#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_SETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO_BASE(base) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_SETTER_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO (base::)&#x0A;</xsl:text>
+ </xsl:if>
+
+ <xsl:apply-templates select="@if" mode="end"/>
+
+</xsl:template>
+
+
+<!--
+ * methods
+-->
+<xsl:template match="interface//method">
+ <xsl:apply-templates select="@if" mode="begin"/>
+ <xsl:if test="param/@mod='ptr'">
+ <!-- methods using native types must be non-scriptable -->
+ <xsl:text> [noscript]&#x0A;</xsl:text>
+ </xsl:if>
+ <xsl:text> void </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:if test="param">
+ <xsl:text> (&#x0A;</xsl:text>
+ <xsl:for-each select="param [position() != last()]">
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates select="."/>
+ <xsl:text>,&#x0A;</xsl:text>
+ </xsl:for-each>
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates select="param [last()]"/>
+ <xsl:text>&#x0A; );&#x0A;</xsl:text>
+ </xsl:if>
+ <xsl:if test="not(param)">
+ <xsl:text>();&#x0A;</xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@if" mode="end"/>
+ <xsl:text>&#x0A;</xsl:text>
+</xsl:template>
+
+<xsl:template match="interface//method" mode="forwarder">
+
+ <xsl:variable name="parent" select="ancestor::interface"/>
+
+ <xsl:apply-templates select="@if" mode="begin"/>
+
+ <xsl:text>#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO(smth) NS_IMETHOD </xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:choose>
+ <xsl:when test="param">
+ <xsl:text> (</xsl:text>
+ <xsl:for-each select="param [position() != last()]">
+ <xsl:apply-templates select="." mode="forwarder"/>
+ <xsl:text>, </xsl:text>
+ </xsl:for-each>
+ <xsl:apply-templates select="param [last()]" mode="forwarder"/>
+ <xsl:text>) { return smth </xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text> (</xsl:text>
+ <xsl:for-each select="param [position() != last()]">
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>Size+++, </xsl:text>
+ </xsl:if>
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>, </xsl:text>
+ </xsl:for-each>
+ <xsl:if test="param [last()]/@safearray='yes'">
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="param [last()]/@name"/>
+ </xsl:call-template>
+ <xsl:text>Size, </xsl:text>
+ </xsl:if>
+ <xsl:text>a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="param [last()]/@name"/>
+ </xsl:call-template>
+ <xsl:text>); }</xsl:text>
+ </xsl:when>
+ <xsl:otherwise test="not(param)">
+ <xsl:text>() { return smth </xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>(); }</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>&#x0A;</xsl:text>
+ <!-- COM_FORWARD_Interface_Method_TO_OBJ(obj) -->
+ <xsl:text>#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO_OBJ(obj) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO ((obj)->)&#x0A;</xsl:text>
+ <!-- COM_FORWARD_Interface_Method_TO_BASE(base) -->
+ <xsl:text>#define COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO_BASE(base) COM_FORWARD_</xsl:text>
+ <xsl:value-of select="$parent/@name"/>
+ <xsl:text>_</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_TO (base::)&#x0A;</xsl:text>
+
+ <xsl:apply-templates select="@if" mode="end"/>
+
+</xsl:template>
+
+
+<!--
+ * modules
+-->
+<xsl:template match="module">
+ <xsl:apply-templates select="class"/>
+</xsl:template>
+
+
+<!--
+ * co-classes
+-->
+<xsl:template match="module/class">
+ <!-- class and contract id -->
+ <xsl:text>%{C++&#x0A;</xsl:text>
+ <xsl:text>// Definitions for module </xsl:text>
+ <xsl:value-of select="../@name"/>
+ <xsl:text>, class </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>:&#x0A;</xsl:text>
+ <xsl:text>#define NS_</xsl:text>
+ <xsl:call-template name="string-to-upper">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>_CID { \&#x0A;</xsl:text>
+ <xsl:text> 0x</xsl:text><xsl:value-of select="substring(@uuid,1,8)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,10,4)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,15,4)"/>
+ <xsl:text>, \&#x0A; </xsl:text>
+ <xsl:text>{ 0x</xsl:text><xsl:value-of select="substring(@uuid,20,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,22,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,25,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,27,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,29,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,31,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,33,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,35,2)"/>
+ <xsl:text> } \&#x0A;}&#x0A;</xsl:text>
+ <xsl:text>#define NS_</xsl:text>
+ <xsl:call-template name="string-to-upper">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <!-- Contract ID -->
+ <xsl:text>_CONTRACTID &quot;@</xsl:text>
+ <xsl:value-of select="@namespace"/>
+ <xsl:text>/</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;1&quot;&#x0A;</xsl:text>
+ <!-- CLSID_xxx declarations for XPCOM, for compatibility with Win32 -->
+ <xsl:text>// for compatibility with Win32&#x0A;</xsl:text>
+ <xsl:text>VBOX_EXTERN_C const nsCID CLSID_</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>;&#x0A;</xsl:text>
+ <xsl:text>%}&#x0A;&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * enums
+-->
+<xsl:template match="enum">[
+ uuid(<xsl:value-of select="@uuid"/>),
+ scriptable
+]
+<xsl:text>interface </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>&#x0A;{&#x0A;</xsl:text>
+ <xsl:for-each select="const">
+ <xsl:text> const PRUint32 </xsl:text>
+ <xsl:value-of select="@name"/> = <xsl:value-of select="@value"/>
+ <xsl:text>;&#x0A;</xsl:text>
+ </xsl:for-each>
+ <xsl:text>};&#x0A;&#x0A;</xsl:text>
+ <xsl:choose>
+
+ <xsl:when test="$g_fHackEnumsOntoCppEnums = 'yes'">
+ <xsl:text>
+/* IDL typedef for enum </xsl:text><xsl:value-of select="@name" /><xsl:text> and C++ mappings. */
+%{C++
+#ifndef VBOX_WITH_XPCOM_CPP_ENUM_HACK
+%}
+typedef PRUint32 </xsl:text><xsl:value-of select="concat(@name, '_T')" /><xsl:text>;
+%{C++
+</xsl:text>
+ <xsl:for-each select="const">
+ <xsl:value-of select="concat('# define ', ../@name, '_', @name, ' ', ../@name, '::', @name, '&#x0A;')"/>
+ </xsl:for-each>
+ <xsl:text>#else /* VBOX_WITH_XPCOM_CPP_ENUM_HACK */
+typedef enum </xsl:text>
+ <xsl:value-of select="concat(@name, '_T')" />
+ <xsl:text> {
+</xsl:text>
+ <xsl:for-each select="const">
+ <xsl:value-of select="concat(' ', ../@name, '_', @name, ' = ', ../@name, '::', @name, ',&#x0A;')"/>
+ </xsl:for-each>
+ <xsl:value-of select="concat(' ', @name, '_32BitHack = 0x7fffffff', '&#x0A;')"/>
+ <xsl:text>} </xsl:text><xsl:value-of select="concat(@name, '_T')"/><xsl:text>;
+# ifdef AssertCompileSize
+AssertCompileSize(</xsl:text><xsl:value-of select="concat(@name, '_T')"/><xsl:text>, sizeof(PRUint32));
+# endif
+#endif /* VBOX_WITH_XPCOM_CPP_ENUM_HACK */
+%}
+
+</xsl:text>
+ </xsl:when>
+
+ <xsl:otherwise>
+ <!-- -->
+ <xsl:value-of select="concat('/* cross-platform type name for ', @name, ' */&#x0A;')"/>
+ <xsl:text>%{C++&#x0A;</xsl:text>
+ <xsl:value-of select="concat('#define ', @name, '_T', ' ',
+ 'PRUint32&#x0A;')"/>
+ <xsl:text>%}&#x0A;&#x0A;</xsl:text>
+ <!-- -->
+ <xsl:value-of select="concat('/* cross-platform constants for ', @name, ' */&#x0A;')"/>
+ <xsl:text>%{C++&#x0A;</xsl:text>
+ <xsl:for-each select="const">
+ <xsl:value-of select="concat('#define ', ../@name, '_', @name, ' ',
+ ../@name, '::', @name, '&#x0A;')"/>
+ </xsl:for-each>
+ <xsl:text>%}&#x0A;&#x0A;</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+
+<!--
+ * method parameters
+-->
+<xsl:template match="method/param">
+ <xsl:choose>
+ <!-- safearray parameters -->
+ <xsl:when test="@safearray='yes'">
+ <!-- array size -->
+ <xsl:choose>
+ <xsl:when test="@dir='in'">in </xsl:when>
+ <xsl:when test="@dir='out'">out </xsl:when>
+ <xsl:when test="@dir='return'">out </xsl:when>
+ <xsl:otherwise>in </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>unsigned long </xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>Size,&#x0A;</xsl:text>
+ <!-- array pointer -->
+ <xsl:text> [array, size_is(</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text>Size)</xsl:text>
+ <xsl:choose>
+ <xsl:when test="@dir='in'">] in </xsl:when>
+ <xsl:when test="@dir='out'">] out </xsl:when>
+ <xsl:when test="@dir='return'"> , retval] out </xsl:when>
+ <xsl:otherwise>] in </xsl:otherwise>
+ </xsl:choose>
+ <xsl:apply-templates select="@type"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="@name"/>
+ </xsl:when>
+ <!-- normal and array parameters -->
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="@dir='in'">in </xsl:when>
+ <xsl:when test="@dir='out'">out </xsl:when>
+ <xsl:when test="@dir='return'">[retval] out </xsl:when>
+ <xsl:otherwise>in </xsl:otherwise>
+ </xsl:choose>
+ <xsl:apply-templates select="@type"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template match="method/param" mode="forwarder">
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>PRUint32</xsl:text>
+ <xsl:if test="@dir='out' or @dir='return'">
+ <xsl:text> *</xsl:text>
+ </xsl:if>
+ <xsl:text> a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>Size, </xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="@type" mode="forwarder"/>
+ <xsl:if test="@dir='out' or @dir='return'">
+ <xsl:text> *</xsl:text>
+ </xsl:if>
+ <xsl:if test="@safearray='yes'">
+ <xsl:text> *</xsl:text>
+ </xsl:if>
+ <xsl:text> a</xsl:text>
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="@name"/>
+ </xsl:call-template>
+</xsl:template>
+
+
+<!--
+ * attribute/parameter type conversion
+-->
+<xsl:template match="attribute/@type | param/@type">
+ <xsl:variable name="self_target" select="current()/ancestor::if/@target"/>
+
+ <xsl:choose>
+ <!-- modifiers (ignored for 'enumeration' attributes)-->
+ <xsl:when test="name(current())='type' and ../@mod">
+ <xsl:choose>
+ <xsl:when test="../@mod='ptr'">
+ <xsl:choose>
+ <!-- standard types -->
+ <!--xsl:when test=".='result'">??</xsl:when-->
+ <xsl:when test=".='boolean'">booleanPtr</xsl:when>
+ <xsl:when test=".='octet'">octetPtr</xsl:when>
+ <xsl:when test=".='short'">shortPtr</xsl:when>
+ <xsl:when test=".='unsigned short'">ushortPtr</xsl:when>
+ <xsl:when test=".='long'">longPtr</xsl:when>
+ <xsl:when test=".='long long'">llongPtr</xsl:when>
+ <xsl:when test=".='unsigned long'">ulongPtr</xsl:when>
+ <xsl:when test=".='unsigned long long'">ullongPtr</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:text>attribute 'mod=</xsl:text>
+ <xsl:value-of select="concat('&quot;',../@mod,'&quot;')"/>
+ <xsl:text>' cannot be used with type </xsl:text>
+ <xsl:value-of select="concat('&quot;',current(),'&quot;!')"/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="../@mod='string'">
+ <xsl:choose>
+ <!-- standard types -->
+ <!--xsl:when test=".='result'">??</xsl:when-->
+ <xsl:when test=".='uuid'">wstring</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:text>attribute 'mod=</xsl:text>
+ <xsl:value-of select="concat('&quot;',../@mod,'&quot;')"/>
+ <xsl:text>' cannot be used with type </xsl:text>
+ <xsl:value-of select="concat('&quot;',current(),'&quot;!')"/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:value-of select="concat('value &quot;',../@mod,'&quot; ')"/>
+ <xsl:text>of attribute 'mod' is invalid!</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <!-- no modifiers -->
+ <xsl:otherwise>
+ <xsl:choose>
+ <!-- standard types -->
+ <xsl:when test=".='result'">nsresult</xsl:when>
+ <xsl:when test=".='boolean'">boolean</xsl:when>
+ <xsl:when test=".='octet'">octet</xsl:when>
+ <xsl:when test=".='short'">short</xsl:when>
+ <xsl:when test=".='unsigned short'">unsigned short</xsl:when>
+ <xsl:when test=".='long'">long</xsl:when>
+ <xsl:when test=".='long long'">long long</xsl:when>
+ <xsl:when test=".='unsigned long'">unsigned long</xsl:when>
+ <xsl:when test=".='unsigned long long'">unsigned long long</xsl:when>
+ <xsl:when test=".='char'">char</xsl:when>
+ <xsl:when test=".='wchar'">wchar</xsl:when>
+ <xsl:when test=".='string'">string</xsl:when>
+ <xsl:when test=".='wstring'">wstring</xsl:when>
+ <!-- UUID type -->
+ <xsl:when test=".='uuid'">
+ <xsl:choose>
+ <xsl:when test="name(..)='attribute'">
+ <xsl:choose>
+ <xsl:when test="../@readonly='yes'">
+ <xsl:text>nsIDPtr</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="../@name"/>
+ <xsl:text>: Non-readonly uuid attributes are not supported!</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="name(..)='param'">
+ <xsl:choose>
+ <xsl:when test="../@dir='in' and not(../@safearray='yes')">
+ <xsl:text>nsIDRef</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>nsIDPtr</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+ <!-- system interface types -->
+ <xsl:when test=".='$unknown'">nsISupports</xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <!-- enum types -->
+ <xsl:when test="
+ (ancestor::library/application/enum[@name=current()]) or
+ (ancestor::library/application/if[@target=$self_target]/enum[@name=current()])
+ ">
+ <xsl:choose>
+ <xsl:when test="$g_fHackEnumsOntoCppEnums = 'yes'">
+ <xsl:value-of select="concat(., '_T')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>PRUint32</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <!-- custom interface types -->
+ <xsl:when test="
+ (ancestor::library/application/interface[@name=current()]) or
+ (ancestor::library/application/if[@target=$self_target]/interface[@name=current()])
+ ">
+ <xsl:value-of select="."/>
+ </xsl:when>
+ <!-- other types -->
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:text>Unknown parameter type: </xsl:text>
+ <xsl:value-of select="."/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template match="attribute/@type | param/@type" mode="forwarder">
+
+ <xsl:variable name="self_target" select="current()/ancestor::if/@target"/>
+
+ <xsl:choose>
+ <!-- modifiers (ignored for 'enumeration' attributes)-->
+ <xsl:when test="name(current())='type' and ../@mod">
+ <xsl:choose>
+ <xsl:when test="../@mod='ptr'">
+ <xsl:choose>
+ <!-- standard types -->
+ <!--xsl:when test=".='result'">??</xsl:when-->
+ <xsl:when test=".='boolean'">PRBool *</xsl:when>
+ <xsl:when test=".='octet'">PRUint8 *</xsl:when>
+ <xsl:when test=".='short'">PRInt16 *</xsl:when>
+ <xsl:when test=".='unsigned short'">PRUint16 *</xsl:when>
+ <xsl:when test=".='long'">PRInt32 *</xsl:when>
+ <xsl:when test=".='long long'">PRInt64 *</xsl:when>
+ <xsl:when test=".='unsigned long'">PRUint32 *</xsl:when>
+ <xsl:when test=".='unsigned long long'">PRUint64 *</xsl:when>
+ <xsl:when test=".='char'">char *</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:text>attribute 'mod=</xsl:text>
+ <xsl:value-of select="concat('&quot;',../@mod,'&quot;')"/>
+ <xsl:text>' cannot be used with type </xsl:text>
+ <xsl:value-of select="concat('&quot;',current(),'&quot;!')"/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="../@mod='string'">
+ <xsl:choose>
+ <!-- standard types -->
+ <!--xsl:when test=".='result'">??</xsl:when-->
+ <xsl:when test=".='uuid'">PRUnichar *</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../../@name,'::',../../@name,'::',../@name,': ')"/>
+ <xsl:text>attribute 'mod=</xsl:text>
+ <xsl:value-of select="concat('&quot;',../@mod,'&quot;')"/>
+ <xsl:text>' cannot be used with type </xsl:text>
+ <xsl:value-of select="concat('&quot;',current(),'&quot;!')"/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+ <!-- no modifiers -->
+ <xsl:otherwise>
+ <xsl:choose>
+ <!-- standard types -->
+ <xsl:when test=".='result'">nsresult</xsl:when>
+ <xsl:when test=".='boolean'">PRBool</xsl:when>
+ <xsl:when test=".='octet'">PRUint8</xsl:when>
+ <xsl:when test=".='short'">PRInt16</xsl:when>
+ <xsl:when test=".='unsigned short'">PRUint16</xsl:when>
+ <xsl:when test=".='long'">PRInt32</xsl:when>
+ <xsl:when test=".='long long'">PRInt64</xsl:when>
+ <xsl:when test=".='unsigned long'">PRUint32</xsl:when>
+ <xsl:when test=".='unsigned long long'">PRUint64</xsl:when>
+ <xsl:when test=".='char'">char</xsl:when>
+ <xsl:when test=".='wchar'">PRUnichar</xsl:when>
+ <!-- string types -->
+ <xsl:when test=".='string'">char *</xsl:when>
+ <xsl:when test=".='wstring'">PRUnichar *</xsl:when>
+ <!-- UUID type -->
+ <xsl:when test=".='uuid'">
+ <xsl:choose>
+ <xsl:when test="name(..)='attribute'">
+ <xsl:choose>
+ <xsl:when test="../@readonly='yes'">
+ <xsl:text>nsID *</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="name(..)='param'">
+ <xsl:choose>
+ <xsl:when test="../@dir='in' and not(../@safearray='yes')">
+ <xsl:text>const nsID &amp;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>nsID *</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+ <!-- system interface types -->
+ <xsl:when test=".='$unknown'">nsISupports *</xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <!-- enum types -->
+ <xsl:when test="
+ (ancestor::library/application/enum[@name=current()]) or
+ (ancestor::library/application/if[@target=$self_target]/enum[@name=current()])
+ ">
+ <xsl:text>PRUint32</xsl:text>
+ </xsl:when>
+ <!-- custom interface types -->
+ <xsl:when test="
+ (ancestor::library/application/interface[@name=current()]) or
+ (ancestor::library/application/if[@target=$self_target]/interface[@name=current()])
+ ">
+ <xsl:value-of select="."/>
+ <xsl:text> *</xsl:text>
+ </xsl:when>
+ <!-- other types -->
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!-- Filters for switch off VBoxSDS definitions -->
+
+<xsl:template match="application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']//module/class" />
+
+<xsl:template match="application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']/if//interface
+| application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']//interface" />
+
+<xsl:template match="application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']//interface" mode="forward" />
+
+
+</xsl:stylesheet>
+
diff --git a/src/VBox/Main/idl/xpidl_iid.xsl b/src/VBox/Main/idl/xpidl_iid.xsl
new file mode 100644
index 00000000..82e74514
--- /dev/null
+++ b/src/VBox/Main/idl/xpidl_iid.xsl
@@ -0,0 +1,172 @@
+<?xml version="1.0"?>
+<!-- $Id: xpidl_iid.xsl $ -->
+
+<!--
+ * A template to generate a header file containing IIDs for XPCOM
+ * from the generic interface definition expressed in XML.
+-->
+
+<!--
+ Copyright (C) 2006-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="text"/>
+
+<xsl:strip-space elements="*"/>
+
+<!--
+// templates
+/////////////////////////////////////////////////////////////////////////////
+-->
+
+
+<!--
+ * not explicitly matched elements and attributes
+-->
+<xsl:template match="*"/>
+
+
+<!--
+ * header
+-->
+<xsl:template match="/idl">
+ <xsl:text>
+/*
+ * DO NOT EDIT! This is a generated file.
+ *
+ * XPCOM C definitions for VirtualBox Main API (IIDs for COM interfaces)
+ * generated from XIDL (XML interface definition).
+ *
+ * Source : src/VBox/Main/idl/VirtualBox.xidl
+ * Generator : src/VBox/Main/idl/xpidl_iid.xsl
+ */
+
+#ifndef nsID_h__
+struct nsID
+{
+ unsigned int m0;
+ unsigned short m1;
+ unsigned short m2;
+ unsigned char m3[8];
+};
+
+typedef struct nsID nsID;
+typedef struct nsID nsIID;
+typedef struct nsID nsCID;
+#endif /* nsID_h__ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>
+
+#ifdef __cplusplus
+}
+#endif
+
+</xsl:text>
+</xsl:template>
+
+
+<!--
+ * ignore all |if|s except those for XPIDL target
+-->
+<xsl:template match="if">
+ <xsl:if test="@target='xpidl'">
+ <xsl:apply-templates/>
+ </xsl:if>
+</xsl:template>
+
+
+<!--
+ * libraries
+-->
+<xsl:template match="library">
+ <xsl:apply-templates select="application/if | application/interface"/>
+ <xsl:apply-templates select="application/module"/>
+</xsl:template>
+
+
+<!--
+ * interfaces
+-->
+<xsl:template match="interface">
+ <xsl:text>const nsID IID_</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> = {&#x0A;</xsl:text>
+ <xsl:text> 0x</xsl:text><xsl:value-of select="substring(@uuid,1,8)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,10,4)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,15,4)"/>
+ <xsl:text>, \&#x0A; </xsl:text>
+ <xsl:text>{ 0x</xsl:text><xsl:value-of select="substring(@uuid,20,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,22,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,25,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,27,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,29,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,31,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,33,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,35,2)"/>
+ <xsl:text> } \&#x0A;};&#x0A;&#x0A;</xsl:text>
+</xsl:template>
+
+
+<!--
+ * modules
+-->
+<xsl:template match="module">
+ <xsl:apply-templates select="class"/>
+</xsl:template>
+
+
+<!--
+ * co-classes
+-->
+<xsl:template match="module/class">
+ <xsl:text>const nsCID CLSID_</xsl:text>
+ <xsl:value-of select="@name"/>
+ <xsl:text> = {&#x0A;</xsl:text>
+ <xsl:text> 0x</xsl:text><xsl:value-of select="substring(@uuid,1,8)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,10,4)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,15,4)"/>
+ <xsl:text>, \&#x0A; </xsl:text>
+ <xsl:text>{ 0x</xsl:text><xsl:value-of select="substring(@uuid,20,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,22,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,25,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,27,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,29,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,31,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,33,2)"/>
+ <xsl:text>, 0x</xsl:text><xsl:value-of select="substring(@uuid,35,2)"/>
+ <xsl:text> } \&#x0A;};&#x0A;&#x0A;</xsl:text>
+</xsl:template>
+
+
+<xsl:template match="application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']//module/class" />
+
+<xsl:template match="application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']/if//interface
+| application[@uuid='ec0e78e8-fa43-43e8-ac0a-02c784c4a4fa']//interface" />
+
+</xsl:stylesheet>
+
diff --git a/src/VBox/Main/include/AdditionsFacilityImpl.h b/src/VBox/Main/include/AdditionsFacilityImpl.h
new file mode 100644
index 00000000..c5e0c021
--- /dev/null
+++ b/src/VBox/Main/include/AdditionsFacilityImpl.h
@@ -0,0 +1,110 @@
+/* $Id: AdditionsFacilityImpl.h $ */
+/** @file
+ * VirtualBox Main - Additions facility class.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_AdditionsFacilityImpl_h
+#define MAIN_INCLUDED_AdditionsFacilityImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/time.h>
+#include "AdditionsFacilityWrap.h"
+
+class Guest;
+
+/**
+ * A Guest Additions facility.
+ */
+class ATL_NO_VTABLE AdditionsFacility :
+ public AdditionsFacilityWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(AdditionsFacility)
+
+ /** @name Initializer & uninitializer methods
+ * @{ */
+ HRESULT init(Guest *a_pParent, AdditionsFacilityType_T a_enmFacility, AdditionsFacilityStatus_T a_enmStatus,
+ uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS);
+ void uninit();
+ HRESULT FinalConstruct();
+ void FinalRelease();
+ /** @} */
+
+public:
+ /** @name public internal methods
+ * @{ */
+ LONG64 i_getLastUpdated() const;
+#if 0 /* unused */
+ AdditionsFacilityType_T i_getType() const;
+ AdditionsFacilityClass_T i_getClass() const;
+ const char *i_getName() const;
+#endif
+ AdditionsFacilityStatus_T i_getStatus() const;
+ bool i_update(AdditionsFacilityStatus_T a_enmStatus, uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS);
+ /** @} */
+
+private:
+
+ /** @name Wrapped IAdditionsFacility properties
+ * @{ */
+ HRESULT getClassType(AdditionsFacilityClass_T *aClassType);
+ HRESULT getLastUpdated(LONG64 *aLastUpdated);
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getStatus(AdditionsFacilityStatus_T *aStatus);
+ HRESULT getType(AdditionsFacilityType_T *aType);
+ /** @} */
+
+ struct Data
+ {
+ /** Last update timestamp. */
+ RTTIMESPEC mTimestamp;
+ /** The facilitie's current status. */
+ AdditionsFacilityStatus_T mStatus;
+ /** Flags. */
+ uint32_t mfFlags;
+ /** The facilitie's ID/type (static). */
+ AdditionsFacilityType_T mType;
+ /** Index into s_aFacilityInfo. */
+ size_t midxInfo;
+ } mData;
+
+ /** Facility <-> string mappings. */
+ struct FacilityInfo
+ {
+ /** The facilitie's name. */
+ const char *mName; /* utf-8 */
+ /** The facilitie's type. */
+ AdditionsFacilityType_T mType;
+ /** The facilitie's class. */
+ AdditionsFacilityClass_T mClass;
+ };
+ static const FacilityInfo s_aFacilityInfo[8];
+};
+
+#endif /* !MAIN_INCLUDED_AdditionsFacilityImpl_h */
+
diff --git a/src/VBox/Main/include/ApplianceImpl.h b/src/VBox/Main/include/ApplianceImpl.h
new file mode 100644
index 00000000..9c4884cd
--- /dev/null
+++ b/src/VBox/Main/include/ApplianceImpl.h
@@ -0,0 +1,367 @@
+/* $Id: ApplianceImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ApplianceImpl_h
+#define MAIN_INCLUDED_ApplianceImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/* VBox includes */
+#include "VirtualSystemDescriptionWrap.h"
+#include "ApplianceWrap.h"
+#include "MediumFormatImpl.h"
+
+/** @todo This file needs massive cleanup. Split IAppliance in a public and
+ * private classes. */
+#include <iprt/tar.h>
+#include "ovfreader.h"
+#include <set>
+
+/* VBox forward declarations */
+class Certificate;
+class Progress;
+class VirtualSystemDescription;
+struct VirtualSystemDescriptionEntry;
+struct LocationInfo;
+typedef struct VDINTERFACE *PVDINTERFACE;
+typedef struct VDINTERFACEIO *PVDINTERFACEIO;
+typedef struct SHASTORAGE *PSHASTORAGE;
+
+namespace ovf
+{
+ struct HardDiskController;
+ struct VirtualSystem;
+ class OVFReader;
+ struct DiskImage;
+ struct EnvelopeData;
+}
+
+namespace xml
+{
+ class Document;
+ class ElementNode;
+}
+
+namespace settings
+{
+ class MachineConfigFile;
+}
+
+class ATL_NO_VTABLE Appliance :
+ public ApplianceWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(Appliance)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+
+ HRESULT init(VirtualBox *aVirtualBox);
+ void uninit();
+
+ /* public methods only for internal purposes */
+
+ static HRESULT i_setErrorStatic(HRESULT aResultCode, const char *aText, ...)
+ {
+ va_list va;
+ va_start(va, aText);
+ HRESULT hrc = setErrorInternalV(aResultCode, getStaticClassIID(), getStaticComponentName(), aText, va, false, true);
+ va_end(va);
+ return hrc;
+ }
+
+ /* private instance data */
+private:
+ // wrapped IAppliance properties
+ HRESULT getPath(com::Utf8Str &aPath);
+ HRESULT getDisks(std::vector<com::Utf8Str> &aDisks);
+ HRESULT getCertificate(ComPtr<ICertificate> &aCertificateInfo);
+ HRESULT getVirtualSystemDescriptions(std::vector<ComPtr<IVirtualSystemDescription> > &aVirtualSystemDescriptions);
+ HRESULT getMachines(std::vector<com::Utf8Str> &aMachines);
+
+ // wrapped IAppliance methods
+ HRESULT read(const com::Utf8Str &aFile,
+ ComPtr<IProgress> &aProgress);
+ HRESULT interpret();
+ HRESULT importMachines(const std::vector<ImportOptions_T> &aOptions,
+ ComPtr<IProgress> &aProgress);
+ HRESULT createVFSExplorer(const com::Utf8Str &aURI,
+ ComPtr<IVFSExplorer> &aExplorer);
+ HRESULT write(const com::Utf8Str &aFormat,
+ const std::vector<ExportOptions_T> &aOptions,
+ const com::Utf8Str &aPath,
+ ComPtr<IProgress> &aProgress);
+ HRESULT getWarnings(std::vector<com::Utf8Str> &aWarnings);
+ HRESULT getPasswordIds(std::vector<com::Utf8Str> &aIdentifiers);
+ HRESULT getMediumIdsForPasswordId(const com::Utf8Str &aPasswordId, std::vector<com::Guid> &aIdentifiers);
+ HRESULT addPasswords(const std::vector<com::Utf8Str> &aIdentifiers,
+ const std::vector<com::Utf8Str> &aPasswords);
+ HRESULT createVirtualSystemDescriptions(ULONG aRequested, ULONG *aCreated);
+ /** weak VirtualBox parent */
+ VirtualBox* const mVirtualBox;
+
+ struct ImportStack;
+ class TaskOVF;
+ class TaskOPC;
+ class TaskCloud;
+
+ struct Data; // opaque, defined in ApplianceImpl.cpp
+ Data *m;
+
+ enum SetUpProgressMode { ImportFile, ImportS3, WriteFile, WriteS3, ExportCloud, ImportCloud };
+
+ enum ApplianceState { ApplianceIdle, ApplianceImporting, ApplianceExporting };
+ void i_setApplianceState(const ApplianceState &state);
+ /** @name General stuff
+ * @{
+ */
+ bool i_isApplianceIdle();
+ HRESULT i_searchUniqueVMName(Utf8Str &aName) const;
+ HRESULT i_ensureUniqueImageFilePath(const Utf8Str &aMachineFolder,
+ DeviceType_T aDeviceType,
+ Utf8Str &aName) const;
+ HRESULT i_setUpProgress(ComObjPtr<Progress> &pProgress,
+ const Utf8Str &strDescription,
+ SetUpProgressMode mode);
+ void i_addWarning(const char* aWarning, ...);
+ void i_disksWeight();
+ void i_parseBucket(Utf8Str &aPath, Utf8Str &aBucket);
+
+ static void i_importOrExportThreadTask(TaskOVF *pTask);
+ static void i_exportOPCThreadTask(TaskOPC *pTask);
+ static void i_importOrExportCloudThreadTask(TaskCloud *pTask);
+
+ HRESULT i_initBackendNames();
+
+ Utf8Str i_typeOfVirtualDiskFormatFromURI(Utf8Str type) const;
+
+#if 0 /* unused */
+ std::set<Utf8Str> i_URIFromTypeOfVirtualDiskFormat(Utf8Str type);
+#endif
+
+ HRESULT i_findMediumFormatFromDiskImage(const ovf::DiskImage &di, ComObjPtr<MediumFormat>& mf);
+
+ RTVFSIOSTREAM i_manifestSetupDigestCalculationForGivenIoStream(RTVFSIOSTREAM hVfsIos, const char *pszManifestEntry,
+ bool fRead = true);
+ /** @} */
+
+ /** @name Read stuff
+ * @{
+ */
+ HRESULT i_readImpl(const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress);
+
+ HRESULT i_readFS(TaskOVF *pTask);
+ HRESULT i_readFSOVF(TaskOVF *pTask);
+ HRESULT i_readFSOVA(TaskOVF *pTask);
+ HRESULT i_readOVFFile(TaskOVF *pTask, RTVFSIOSTREAM hIosOvf, const char *pszManifestEntry);
+ HRESULT i_readManifestFile(TaskOVF *pTask, RTVFSIOSTREAM hIosMf, const char *pszSubFileNm);
+ HRESULT i_readSignatureFile(TaskOVF *pTask, RTVFSIOSTREAM hIosCert, const char *pszSubFileNm);
+ HRESULT i_readTailProcessing(TaskOVF *pTask);
+ HRESULT i_readTailProcessingGetManifestData(void **ppvData, size_t *pcbData);
+ HRESULT i_readTailProcessingSignedData(PRTERRINFOSTATIC pErrInfo);
+ HRESULT i_readTailProcessingVerifySelfSignedOvfCert(TaskOVF *pTask, RTCRSTORE hTrustedCerts, PRTERRINFOSTATIC pErrInfo);
+ HRESULT i_readTailProcessingVerifyIssuedOvfCert(TaskOVF *pTask, RTCRSTORE hTrustedStore, PRTERRINFOSTATIC pErrInfo);
+ HRESULT i_readTailProcessingVerifyContentInfoCerts(void const *pvData, size_t cbData,
+ RTCRSTORE hTrustedStore, PRTERRINFOSTATIC pErrInfo);
+ HRESULT i_readTailProcessingVerifyAnalyzeSignerInfo(void const *pvData, size_t cbData, RTCRSTORE hTrustedStore,
+ uint32_t iSigner, PRTTIMESPEC pNow, int vrc,
+ PRTERRINFOSTATIC pErrInfo, PRTCRSTORE phTrustedStore2);
+ HRESULT i_readTailProcessingVerifyContentInfoFailOne(const char *pszSignature, int vrc, PRTERRINFOSTATIC pErrInfo);
+
+ HRESULT i_gettingCloudData(TaskCloud *pTask);
+ /** @} */
+
+ /** @name Import stuff
+ * @{
+ */
+ HRESULT i_importImpl(const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress);
+
+ HRESULT i_importFS(TaskOVF *pTask);
+ HRESULT i_importFSOVF(TaskOVF *pTask, AutoWriteLockBase &rWriteLock);
+ HRESULT i_importFSOVA(TaskOVF *pTask, AutoWriteLockBase &rWriteLock);
+ HRESULT i_importDoIt(TaskOVF *pTask, AutoWriteLockBase &rWriteLock, RTVFSFSSTREAM hVfsFssOva = NIL_RTVFSFSSTREAM);
+
+ HRESULT i_verifyManifestFile(ImportStack &stack);
+
+ void i_convertDiskAttachmentValues(const ovf::HardDiskController &hdc,
+ uint32_t ulAddressOnParent,
+ Utf8Str &controllerName,
+ int32_t &lControllerPort,
+ int32_t &lDevice);
+
+ void i_importOneDiskImage(const ovf::DiskImage &di,
+ const Utf8Str &strDstPath,
+ ComObjPtr<Medium> &pTargetMedium,
+ ImportStack &stack);
+
+ void i_importMachineGeneric(const ovf::VirtualSystem &vsysThis,
+ ComObjPtr<VirtualSystemDescription> &vsdescThis,
+ ComPtr<IMachine> &pNewMachineRet,
+ ImportStack &stack);
+ void i_importVBoxMachine(ComObjPtr<VirtualSystemDescription> &vsdescThis,
+ ComPtr<IMachine> &pNewMachine,
+ ImportStack &stack);
+ void i_importMachines(ImportStack &stack);
+ HRESULT i_verifyStorageControllerPortValid(const StorageControllerType_T aStorageControllerType,
+ const uint32_t aControllerPort,
+ ULONG *ulMaxPorts);
+
+ HRESULT i_preCheckImageAvailability(ImportStack &stack);
+ bool i_importEnsureOvaLookAhead(ImportStack &stack);
+ RTVFSIOSTREAM i_importOpenSourceFile(ImportStack &stack, Utf8Str const &rstrSrcPath, const char *pszManifestEntry);
+ HRESULT i_importCreateAndWriteDestinationFile(Utf8Str const &rstrDstPath,
+ RTVFSIOSTREAM hVfsIosSrc, Utf8Str const &rstrSrcLogNm);
+
+ void i_importCopyFile(ImportStack &stack, Utf8Str const &rstrSrcPath, Utf8Str const &rstrDstPath,
+ const char *pszManifestEntry);
+ void i_importDecompressFile(ImportStack &stack, Utf8Str const &rstrSrcPath, Utf8Str const &rstrDstPath,
+ const char *pszManifestEntry);
+ HRESULT i_importCloudImpl(TaskCloud *pTask);
+ /** @} */
+
+ /** @name Write stuff
+ * @{
+ */
+ HRESULT i_writeImpl(ovf::OVFVersion_T aFormat, const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress);
+ HRESULT i_writeOPCImpl(ovf::OVFVersion_T aFormat, const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress);
+ HRESULT i_writeCloudImpl(const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress);
+
+ HRESULT i_writeFS(TaskOVF *pTask);
+ HRESULT i_writeFSOVF(TaskOVF *pTask, AutoWriteLockBase& writeLock);
+ HRESULT i_writeFSOVA(TaskOVF *pTask, AutoWriteLockBase& writeLock);
+ HRESULT i_writeFSOPC(TaskOPC *pTask);
+ HRESULT i_exportCloudImpl(TaskCloud *pTask);
+ HRESULT i_writeFSImpl(TaskOVF *pTask, AutoWriteLockBase &writeLock, RTVFSFSSTREAM hVfsFssDst);
+ HRESULT i_writeBufferToFile(RTVFSFSSTREAM hVfsFssDst, const char *pszFilename, const void *pvContent, size_t cbContent);
+
+ struct XMLStack;
+
+ void i_buildXML(AutoWriteLockBase& writeLock,
+ xml::Document &doc,
+ XMLStack &stack,
+ const Utf8Str &strPath,
+ ovf::OVFVersion_T enFormat);
+ void i_buildXMLForOneVirtualSystem(AutoWriteLockBase& writeLock,
+ xml::ElementNode &elmToAddVirtualSystemsTo,
+ std::list<xml::ElementNode*> *pllElementsWithUuidAttributes,
+ ComObjPtr<VirtualSystemDescription> &vsdescThis,
+ ovf::OVFVersion_T enFormat,
+ XMLStack &stack);
+ /** @} */
+
+ friend class Machine;
+ friend class Certificate;
+};
+
+void i_parseURI(Utf8Str strUri, LocationInfo &locInfo);
+
+struct VirtualSystemDescriptionEntry
+{
+ uint32_t ulIndex; ///< zero-based index of this entry within array
+ VirtualSystemDescriptionType_T type; ///< type of this entry
+ Utf8Str strRef; ///< reference number (hard disk controllers only)
+ Utf8Str strOvf; ///< original OVF value (type-dependent)
+ Utf8Str strVBoxSuggested; ///< configuration value (type-dependent); original value suggested by interpret()
+ Utf8Str strVBoxCurrent; ///< configuration value (type-dependent); current value, either from interpret() or setFinalValue()
+ Utf8Str strExtraConfigSuggested; ///< extra configuration key=value strings (type-dependent); original value suggested by interpret()
+ Utf8Str strExtraConfigCurrent; ///< extra configuration key=value strings (type-dependent); current value, either from interpret() or setFinalValue()
+
+ uint32_t ulSizeMB; ///< hard disk images only: a copy of ovf::DiskImage::ulSuggestedSizeMB
+ bool skipIt; ///< used during export to skip some parts if it's needed
+};
+
+class ATL_NO_VTABLE VirtualSystemDescription :
+ public VirtualSystemDescriptionWrap
+{
+ friend class Appliance;
+
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(VirtualSystemDescription)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ HRESULT init();
+ void uninit();
+
+ /* public methods only for internal purposes */
+ void i_addEntry(VirtualSystemDescriptionType_T aType,
+ const Utf8Str &strRef,
+ const Utf8Str &aOvfValue,
+ const Utf8Str &aVBoxValue,
+ uint32_t ulSizeMB = 0,
+ const Utf8Str &strExtraConfig = "");
+
+ std::list<VirtualSystemDescriptionEntry*> i_findByType(VirtualSystemDescriptionType_T aType);
+ const VirtualSystemDescriptionEntry* i_findControllerFromID(const Utf8Str &id);
+ const VirtualSystemDescriptionEntry* i_findByIndex(const uint32_t aIndex);
+
+ void i_importVBoxMachineXML(const xml::ElementNode &elmMachine);
+ const settings::MachineConfigFile* i_getMachineConfig() const;
+
+ /* private instance data */
+private:
+
+ // wrapped IVirtualSystemDescription properties
+ HRESULT getCount(ULONG *aCount);
+
+ // wrapped IVirtualSystemDescription methods
+ HRESULT getDescription(std::vector<VirtualSystemDescriptionType_T> &aTypes,
+ std::vector<com::Utf8Str> &aRefs,
+ std::vector<com::Utf8Str> &aOVFValues,
+ std::vector<com::Utf8Str> &aVBoxValues,
+ std::vector<com::Utf8Str> &aExtraConfigValues);
+ HRESULT getDescriptionByType(VirtualSystemDescriptionType_T aType,
+ std::vector<VirtualSystemDescriptionType_T> &aTypes,
+ std::vector<com::Utf8Str> &aRefs,
+ std::vector<com::Utf8Str> &aOVFValues,
+ std::vector<com::Utf8Str> &aVBoxValues,
+ std::vector<com::Utf8Str> &aExtraConfigValues);
+ HRESULT getValuesByType(VirtualSystemDescriptionType_T aType,
+ VirtualSystemDescriptionValueType_T aWhich,
+ std::vector<com::Utf8Str> &aValues);
+ HRESULT setFinalValues(const std::vector<BOOL> &aEnabled,
+ const std::vector<com::Utf8Str> &aVBoxValues,
+ const std::vector<com::Utf8Str> &aExtraConfigValues);
+ HRESULT addDescription(VirtualSystemDescriptionType_T aType,
+ const com::Utf8Str &aVBoxValue,
+ const com::Utf8Str &aExtraConfigValue);
+ HRESULT removeDescriptionByType(VirtualSystemDescriptionType_T aType);
+ void i_removeByType(VirtualSystemDescriptionType_T aType);
+
+ struct Data;
+ Data *m;
+
+ friend class Machine;
+};
+
+#endif /* !MAIN_INCLUDED_ApplianceImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/ApplianceImplPrivate.h b/src/VBox/Main/include/ApplianceImplPrivate.h
new file mode 100644
index 00000000..f3641a8f
--- /dev/null
+++ b/src/VBox/Main/include/ApplianceImplPrivate.h
@@ -0,0 +1,572 @@
+/* $Id: ApplianceImplPrivate.h $ */
+/** @file
+ * VirtualBox Appliance private data definitions
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ApplianceImplPrivate_h
+#define MAIN_INCLUDED_ApplianceImplPrivate_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+class VirtualSystemDescription;
+
+#include "ovfreader.h"
+#include "SecretKeyStore.h"
+#include "ThreadTask.h"
+#include "CertificateImpl.h"
+#include <map>
+#include <vector>
+#include <iprt/manifest.h>
+#include <iprt/vfs.h>
+#include <iprt/crypto/x509.h>
+#include <iprt/crypto/pkcs7.h>
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Appliance data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+
+namespace settings
+{
+ struct AttachedDevice;
+}
+
+typedef std::pair<Utf8Str, Utf8Str> STRPAIR;
+
+typedef std::vector<com::Guid> GUIDVEC;
+
+/* Describe a location for the import/export. The location could be a file on a
+ * local hard disk or a remote target based on the supported inet protocols. */
+struct LocationInfo
+{
+ LocationInfo()
+ : storageType(VFSType_File) {}
+ VFSType_T storageType; /* Which type of storage should be handled */
+ Utf8Str strProvider; /* cloud provider name in case of export/import to Cloud */
+ Utf8Str strPath; /* File path for the import/export */
+ Utf8Str strHostname; /* Hostname on remote storage locations (could be empty) */
+ Utf8Str strUsername; /* Username on remote storage locations (could be empty) */
+ Utf8Str strPassword; /* Password on remote storage locations (could be empty) */
+};
+
+/**
+ * opaque private instance data of Appliance class
+ */
+struct Appliance::Data
+{
+ enum digest_T {SHA1, SHA256};
+
+ Data()
+ : state(Appliance::ApplianceIdle)
+ , fDigestTypes(0)
+ , hOurManifest(NIL_RTMANIFEST)
+ , fManifest(true)
+ , fDeterminedDigestTypes(false)
+ , hTheirManifest(NIL_RTMANIFEST)
+ , hMemFileTheirManifest(NIL_RTVFSFILE)
+ , fSignerCertLoaded(false)
+ , fCertificateIsSelfSigned(false)
+ , fSignatureValid(false)
+ , fCertificateValid(false)
+ , fCertificateMissingPath(true)
+ , fCertificateValidTime(false)
+ , pbSignedDigest(NULL)
+ , cbSignedDigest(0)
+ , enmSignedDigestType(RTDIGESTTYPE_INVALID)
+ , fContentInfoLoaded(false)
+ , fContentInfoOkay(false)
+ , fContentInfoSameCert(false)
+ , fContentInfoValidSignature(false)
+ , fExportISOImages(false)
+ , pReader(NULL)
+ , ulWeightForXmlOperation(0)
+ , ulWeightForManifestOperation(0)
+ , ulTotalDisksMB(0)
+ , cDisks(0)
+ , m_cPwProvided(0)
+ {
+ RT_ZERO(SignerCert);
+ RT_ZERO(ContentInfo);
+ }
+
+ ~Data()
+ {
+ if (pReader)
+ {
+ delete pReader;
+ pReader = NULL;
+ }
+ resetReadData();
+ }
+
+ /**
+ * Resets data used by read.
+ */
+ void resetReadData(void)
+ {
+ strOvfManifestEntry.setNull();
+ if (hOurManifest != NIL_RTMANIFEST)
+ {
+ RTManifestRelease(hOurManifest);
+ hOurManifest = NIL_RTMANIFEST;
+ }
+ if (hTheirManifest != NIL_RTMANIFEST)
+ {
+ RTManifestRelease(hTheirManifest);
+ hTheirManifest = NIL_RTMANIFEST;
+ }
+ if (hMemFileTheirManifest)
+ {
+ RTVfsFileRelease(hMemFileTheirManifest);
+ hMemFileTheirManifest = NIL_RTVFSFILE;
+ }
+ if (pbSignedDigest)
+ {
+ RTMemFree(pbSignedDigest);
+ pbSignedDigest = NULL;
+ cbSignedDigest = 0;
+ }
+ if (fSignerCertLoaded)
+ {
+ RTCrX509Certificate_Delete(&SignerCert);
+ fSignerCertLoaded = false;
+ }
+ RT_ZERO(SignerCert);
+ enmSignedDigestType = RTDIGESTTYPE_INVALID;
+ fCertificateIsSelfSigned = false;
+ fSignatureValid = false;
+ fCertificateValid = false;
+ fCertificateMissingPath = true;
+ fCertificateValidTime = false;
+ fDeterminedDigestTypes = false;
+ fDigestTypes = RTMANIFEST_ATTR_SHA1 | RTMANIFEST_ATTR_SHA256 | RTMANIFEST_ATTR_SHA512;
+ ptrCertificateInfo.setNull();
+ strCertError.setNull();
+ if (fContentInfoLoaded)
+ {
+ RTCrPkcs7ContentInfo_Delete(&ContentInfo);
+ fContentInfoLoaded = false;
+ }
+ RT_ZERO(ContentInfo);
+ }
+
+ Appliance::ApplianceState state;
+
+ LocationInfo locInfo; // location info for the currently processed OVF
+ /** The digests types to calculate (RTMANIFEST_ATTR_XXX) for the manifest.
+ * This will be a single value when exporting. Zero, one or two. */
+ uint32_t fDigestTypes;
+ /** Manifest created while importing or exporting. */
+ RTMANIFEST hOurManifest;
+
+ /** @name Write data
+ * @{ */
+ bool fManifest; // Create a manifest file on export
+ /** @} */
+
+ /** @name Read data
+ * @{ */
+ /** The manifest entry name of the OVF-file. */
+ Utf8Str strOvfManifestEntry;
+
+ /** Set if we've parsed the manifest and determined the digest types. */
+ bool fDeterminedDigestTypes;
+
+ /** Manifest read in during read() and kept around for later verification. */
+ RTMANIFEST hTheirManifest;
+ /** Memorized copy of the manifest file for signature checking purposes. */
+ RTVFSFILE hMemFileTheirManifest;
+
+ /** The signer certificate from the signature file (.cert).
+ * This will be used in the future provide information about the signer via
+ * the API. */
+ RTCRX509CERTIFICATE SignerCert;
+ /** Set if the SignerCert member contains usable data. */
+ bool fSignerCertLoaded;
+ /** Cached RTCrX509Validity_IsValidAtTimeSpec result set by read(). */
+ bool fCertificateIsSelfSigned;
+ /** Set by read() if pbSignedDigest verified correctly against SignerCert. */
+ bool fSignatureValid;
+ /** Set by read() when the SignerCert checked out fine. */
+ bool fCertificateValid;
+ /** Set by read() when the SignerCert certificate path couldn't be built. */
+ bool fCertificateMissingPath;
+ /** Set by read() when the SignerCert (+path) is valid in the temporal sense. */
+ bool fCertificateValidTime;
+ /** For keeping certificate error messages we delay from read() to import(). */
+ Utf8Str strCertError;
+ /** The signed digest of the manifest. */
+ uint8_t *pbSignedDigest;
+ /** The size of the signed digest. */
+ size_t cbSignedDigest;
+ /** The digest type used to sign the manifest. */
+ RTDIGESTTYPE enmSignedDigestType;
+ /** The certificate info object. This is NULL if no signature and
+ * successfully loaded certificate. */
+ ComObjPtr<Certificate> ptrCertificateInfo;
+
+ /** The PKCS\#7/CMS signed data signing manifest, optional VBox extension.
+ * This contains at least one signature using the same certificate as above
+ * (SignerCert), but should preferrably use a different digest. The PKCS\#7/CMS
+ * format is a lot more versatile, allow multiple signatures using different
+ * digests and certificates, optionally with counter signed timestamps.
+ * Additional intermediate certificates can also be shipped, helping to bridge
+ * the gap to a trusted root certificate installed on the recieving system. */
+ RTCRPKCS7CONTENTINFO ContentInfo;
+ /** Set if the ContentInfo member contains usable data. */
+ bool fContentInfoLoaded;
+ /** Set by read() if the ContentInfo member checked out okay (says nothing about
+ * the signature or certificates within it). */
+ bool fContentInfoOkay;
+ /** Set by read() if the ContentInfo member is using the SignerCert too. */
+ bool fContentInfoSameCert;
+ /** Set by read() if the ContentInfo member contains valid signatures (not
+ * saying anything about valid signing certificates). */
+ bool fContentInfoValidSignature;
+ /** Set by read() if we've already verified the signed data signature(s). */
+ bool fContentInfoDoneVerification;
+
+ bool fContentInfoVerifiedOkay;
+ /** @} */
+
+ bool fExportISOImages;// when 1 the ISO images are exported
+
+ RTCList<ImportOptions_T> optListImport;
+ RTCList<ExportOptions_T> optListExport;
+
+ ovf::OVFReader *pReader;
+
+ std::list< ComObjPtr<VirtualSystemDescription> >
+ virtualSystemDescriptions;
+
+ std::list<Utf8Str> llWarnings;
+
+ ULONG ulWeightForXmlOperation;
+ ULONG ulWeightForManifestOperation;
+ ULONG ulTotalDisksMB;
+ ULONG cDisks;
+
+ std::list<Guid> llGuidsMachinesCreated;
+
+ /** Sequence of password identifiers to encrypt disk images during export. */
+ std::vector<com::Utf8Str> m_vecPasswordIdentifiers;
+ /** Map to get all medium identifiers assoicated with a given password identifier. */
+ std::map<com::Utf8Str, GUIDVEC> m_mapPwIdToMediumIds;
+ /** Secret key store used to hold the passwords during export. */
+ SecretKeyStore *m_pSecretKeyStore;
+ /** Number of passwords provided. */
+ uint32_t m_cPwProvided;
+};
+
+struct Appliance::XMLStack
+{
+ std::map<Utf8Str, const VirtualSystemDescriptionEntry*> mapDisks;
+ std::list<Utf8Str> mapDiskSequence;
+ std::list<Utf8Str> mapDiskSequenceForOneVM;//temporary keeps all disks attached to one exported VM
+ std::map<Utf8Str, bool> mapNetworks;
+};
+
+class Appliance::TaskOVF : public ThreadTask
+{
+public:
+ enum TaskType
+ {
+ Read,
+ Import,
+ Write
+ };
+
+ TaskOVF(Appliance *aThat,
+ TaskType aType,
+ LocationInfo aLocInfo,
+ ComObjPtr<Progress> &aProgress)
+ : ThreadTask("TaskOVF"),
+ pAppliance(aThat),
+ taskType(aType),
+ locInfo(aLocInfo),
+ pProgress(aProgress),
+ enFormat(ovf::OVFVersion_unknown),
+ rc(S_OK)
+ {
+ switch (taskType)
+ {
+ case TaskOVF::Read: m_strTaskName = "ApplRead"; break;
+ case TaskOVF::Import: m_strTaskName = "ApplImp"; break;
+ case TaskOVF::Write: m_strTaskName = "ApplWrit"; break;
+ default: m_strTaskName = "ApplTask"; break;
+ }
+ }
+
+ static DECLCALLBACK(int) updateProgress(unsigned uPercent, void *pvUser);
+
+ Appliance *pAppliance;
+ TaskType taskType;
+ const LocationInfo locInfo;
+ ComObjPtr<Progress> pProgress;
+
+ ovf::OVFVersion_T enFormat;
+
+ HRESULT rc;
+
+ void handler()
+ {
+ Appliance::i_importOrExportThreadTask(this);
+ }
+};
+
+class Appliance::TaskOPC : public ThreadTask
+{
+public:
+ enum TaskType
+ {
+ Export
+ };
+
+ TaskOPC(Appliance *aThat,
+ TaskType aType,
+ LocationInfo aLocInfo,
+ ComObjPtr<Progress> &aProgress)
+ : ThreadTask("TaskOPC"),
+ pAppliance(aThat),
+ taskType(aType),
+ locInfo(aLocInfo),
+ pProgress(aProgress),
+ rc(S_OK)
+ {
+ m_strTaskName = "OPCExpt";
+ }
+
+ ~TaskOPC()
+ {
+ }
+
+ static DECLCALLBACK(int) updateProgress(unsigned uPercent, void *pvUser);
+
+ Appliance *pAppliance;
+ TaskType taskType;
+ const LocationInfo locInfo;
+ ComObjPtr<Progress> pProgress;
+
+ HRESULT rc;
+
+ void handler()
+ {
+ Appliance::i_exportOPCThreadTask(this);
+ }
+};
+
+
+class Appliance::TaskCloud : public ThreadTask
+{
+public:
+ enum TaskType
+ {
+ Export,
+ Import,
+ ReadData
+ };
+
+ TaskCloud(Appliance *aThat,
+ TaskType aType,
+ LocationInfo aLocInfo,
+ ComObjPtr<Progress> &aProgress)
+ : ThreadTask("TaskCloud"),
+ pAppliance(aThat),
+ taskType(aType),
+ locInfo(aLocInfo),
+ pProgress(aProgress),
+ rc(S_OK)
+ {
+ switch (taskType)
+ {
+ case TaskCloud::Export: m_strTaskName = "CloudExpt"; break;
+ case TaskCloud::Import: m_strTaskName = "CloudImpt"; break;
+ case TaskCloud::ReadData: m_strTaskName = "CloudRead"; break;
+ default: m_strTaskName = "CloudTask"; break;
+ }
+ }
+
+ ~TaskCloud()
+ {
+ }
+
+ static DECLCALLBACK(int) updateProgress(unsigned uPercent, void *pvUser);
+
+ Appliance *pAppliance;
+ TaskType taskType;
+ const LocationInfo locInfo;
+ ComObjPtr<Progress> pProgress;
+
+ HRESULT rc;
+
+ void handler()
+ {
+ Appliance::i_importOrExportCloudThreadTask(this);
+ }
+};
+
+struct MyHardDiskAttachment
+{
+ ComPtr<IMachine> pMachine;
+ Utf8Str controllerName;
+ int32_t lControllerPort; // 0-29 for SATA
+ int32_t lDevice; // IDE: 0 or 1, otherwise 0 always
+};
+
+/**
+ * Used by Appliance::importMachineGeneric() to store
+ * input parameters and rollback information.
+ */
+struct Appliance::ImportStack
+{
+ // input pointers
+ const LocationInfo &locInfo; // ptr to location info from Appliance::importFS()
+ Utf8Str strSourceDir; // directory where source files reside
+ const ovf::DiskImagesMap &mapDisks; // ptr to disks map in OVF
+ ComObjPtr<Progress> &pProgress; // progress object passed into Appliance::importFS()
+
+ // input parameters from VirtualSystemDescriptions
+ Utf8Str strNameVBox; // VM name
+ Utf8Str strSettingsFilename; // Absolute path to VM config file
+ Utf8Str strMachineFolder; // Absolute path to VM folder (derived from strSettingsFilename)
+ Utf8Str strOsTypeVBox; // VirtualBox guest OS type as string
+ Utf8Str strPrimaryGroup; // VM primary group as string
+ Utf8Str strDescription;
+ uint32_t cCPUs; // CPU count
+ bool fForceHWVirt; // if true, we force enabling hardware virtualization
+ bool fForceIOAPIC; // if true, we force enabling the IOAPIC
+ uint32_t ulMemorySizeMB; // virtual machine RAM in megabytes
+ Utf8Str strFirmwareType; //Firmware - BIOS or EFI
+#ifdef VBOX_WITH_USB
+ bool fUSBEnabled;
+#endif
+ Utf8Str strAudioAdapter; // if not empty, then the guest has audio enabled, and this is the decimal
+ // representation of the audio adapter (should always be "0" for AC97 presently)
+
+ // session (not initially created)
+ ComPtr<ISession> pSession; // session opened in Appliance::importFS() for machine manipulation
+ bool fSessionOpen; // true if the pSession is currently open and needs closing
+
+ /** @name File access related stuff (TAR stream)
+ * @{ */
+ /** OVA file system stream handle. NIL if not OVA. */
+ RTVFSFSSTREAM hVfsFssOva;
+ /** OVA lookahead I/O stream object. */
+ RTVFSIOSTREAM hVfsIosOvaLookAhead;
+ /** OVA lookahead I/O stream object name. */
+ char *pszOvaLookAheadName;
+ /** @} */
+
+ // a list of images that we created/imported; this is initially empty
+ // and will be cleaned up on errors
+ std::list<MyHardDiskAttachment> llHardDiskAttachments; // disks that were attached
+ std::map<Utf8Str , Utf8Str> mapNewUUIDsToOriginalUUIDs;
+
+ ImportStack(const LocationInfo &aLocInfo,
+ const ovf::DiskImagesMap &aMapDisks,
+ ComObjPtr<Progress> &aProgress,
+ RTVFSFSSTREAM aVfsFssOva)
+ : locInfo(aLocInfo),
+ mapDisks(aMapDisks),
+ pProgress(aProgress),
+ cCPUs(1),
+ fForceHWVirt(false),
+ fForceIOAPIC(false),
+ ulMemorySizeMB(0),
+ fSessionOpen(false),
+ hVfsFssOva(aVfsFssOva),
+ hVfsIosOvaLookAhead(NIL_RTVFSIOSTREAM),
+ pszOvaLookAheadName(NULL)
+ {
+ if (hVfsFssOva != NIL_RTVFSFSSTREAM)
+ RTVfsFsStrmRetain(hVfsFssOva);
+
+ // disk images have to be on the same place as the OVF file. So
+ // strip the filename out of the full file path
+ strSourceDir = aLocInfo.strPath;
+ strSourceDir.stripFilename();
+ }
+
+ ~ImportStack()
+ {
+ if (hVfsFssOva != NIL_RTVFSFSSTREAM)
+ {
+ RTVfsFsStrmRelease(hVfsFssOva);
+ hVfsFssOva = NIL_RTVFSFSSTREAM;
+ }
+ if (hVfsIosOvaLookAhead != NIL_RTVFSIOSTREAM)
+ {
+ RTVfsIoStrmRelease(hVfsIosOvaLookAhead);
+ hVfsIosOvaLookAhead = NIL_RTVFSIOSTREAM;
+ }
+ if (pszOvaLookAheadName)
+ {
+ RTStrFree(pszOvaLookAheadName);
+ pszOvaLookAheadName = NULL;
+ }
+ }
+
+ HRESULT restoreOriginalUUIDOfAttachedDevice(settings::MachineConfigFile *config);
+ HRESULT saveOriginalUUIDOfAttachedDevice(settings::AttachedDevice &device,
+ const Utf8Str &newlyUuid);
+ RTVFSIOSTREAM claimOvaLookAHead(void);
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// VirtualSystemDescription data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct VirtualSystemDescription::Data
+{
+ std::vector<VirtualSystemDescriptionEntry>
+ maDescriptions; // item descriptions
+
+ ComPtr<Machine> pMachine; // VirtualBox machine this description was exported from (export only)
+
+ settings::MachineConfigFile
+ *pConfig; // machine config created from <vbox:Machine> element if found (import only)
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Internal helpers
+//
+////////////////////////////////////////////////////////////////////////////////
+
+void convertCIMOSType2VBoxOSType(Utf8Str &strType, ovf::CIMOSType_T c, const Utf8Str &cStr);
+
+ovf::CIMOSType_T convertVBoxOSType2CIMOSType(const char *pcszVBox, BOOL fLongMode);
+
+Utf8Str convertNetworkAttachmentTypeToString(NetworkAttachmentType_T type);
+
+
+#endif /* !MAIN_INCLUDED_ApplianceImplPrivate_h */
+
diff --git a/src/VBox/Main/include/AudioAdapterImpl.h b/src/VBox/Main/include/AudioAdapterImpl.h
new file mode 100644
index 00000000..f5e833ec
--- /dev/null
+++ b/src/VBox/Main/include/AudioAdapterImpl.h
@@ -0,0 +1,94 @@
+/* $Id: AudioAdapterImpl.h $ */
+
+/** @file
+ *
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_AudioAdapterImpl_h
+#define MAIN_INCLUDED_AudioAdapterImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+class AudioSettings;
+
+#include "AudioAdapterWrap.h"
+namespace settings
+{
+ struct AudioAdapter;
+}
+
+class ATL_NO_VTABLE AudioAdapter :
+ public AudioAdapterWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS (AudioAdapter)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(AudioSettings *aParent);
+ HRESULT init(AudioSettings *aParent, AudioAdapter *aThat);
+ HRESULT initCopy(AudioSettings *aParent, AudioAdapter *aThat);
+ void uninit();
+
+ // public methods only for internal purposes
+ HRESULT i_loadSettings(const settings::AudioAdapter &data);
+ HRESULT i_saveSettings(settings::AudioAdapter &data);
+
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(AudioAdapter *aThat);
+
+private:
+
+ // wrapped IAudioAdapter properties
+ HRESULT getEnabled(BOOL *aEnabled);
+ HRESULT setEnabled(BOOL aEnabled);
+ HRESULT getEnabledIn(BOOL *aEnabled);
+ HRESULT setEnabledIn(BOOL aEnabled);
+ HRESULT getEnabledOut(BOOL *aEnabled);
+ HRESULT setEnabledOut(BOOL aEnabled);
+ HRESULT getAudioDriver(AudioDriverType_T *aAudioDriver);
+ HRESULT setAudioDriver(AudioDriverType_T aAudioDriver);
+ HRESULT getAudioController(AudioControllerType_T *aAudioController);
+ HRESULT setAudioController(AudioControllerType_T aAudioController);
+ HRESULT getAudioCodec(AudioCodecType_T *aAudioCodec);
+ HRESULT setAudioCodec(AudioCodecType_T aAudioCodec);
+ HRESULT getPropertiesList(std::vector<com::Utf8Str>& aProperties);
+ HRESULT getProperty(const com::Utf8Str &aKey, com::Utf8Str &aValue);
+ HRESULT setProperty(const com::Utf8Str &aKey, const com::Utf8Str &aValue);
+
+private:
+
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_AudioAdapterImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/AudioDriver.h b/src/VBox/Main/include/AudioDriver.h
new file mode 100644
index 00000000..b1124b66
--- /dev/null
+++ b/src/VBox/Main/include/AudioDriver.h
@@ -0,0 +1,144 @@
+/* $Id: AudioDriver.h $ */
+/** @file
+ * VirtualBox audio base class for Main audio drivers.
+ */
+
+/*
+ * Copyright (C) 2018-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_AudioDriver_h
+#define MAIN_INCLUDED_AudioDriver_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/com/ptr.h>
+#include <VBox/com/string.h>
+#include <VBox/com/AutoLock.h>
+
+using namespace com;
+
+/**
+ * Audio driver configuration for audio drivers implemented
+ * in Main.
+ */
+struct AudioDriverCfg
+{
+ AudioDriverCfg(Utf8Str a_strDev = "", unsigned a_uInst = 0, unsigned a_uLUN = 0, Utf8Str a_strName = "",
+ bool a_fEnabledIn = false, bool a_fEnabledOut = false)
+ : strDev(a_strDev)
+ , uInst(a_uInst)
+ , uLUN(a_uLUN)
+ , strName(a_strName)
+ , fEnabledIn(a_fEnabledIn)
+ , fEnabledOut(a_fEnabledOut)
+ { }
+
+ /** Copy assignment operator. */
+ AudioDriverCfg& operator=(AudioDriverCfg const &a_rThat) RT_NOEXCEPT
+ {
+ this->strDev = a_rThat.strDev;
+ this->uInst = a_rThat.uInst;
+ this->uLUN = a_rThat.uLUN;
+ this->strName = a_rThat.strName;
+ this->fEnabledIn = a_rThat.fEnabledIn;
+ this->fEnabledOut = a_rThat.fEnabledOut;
+
+ return *this;
+ }
+
+ /** The device name. */
+ Utf8Str strDev;
+ /** The device instance. */
+ unsigned uInst;
+ /** The LUN the driver is attached to.
+ * Set the UINT8_MAX if not attached. */
+ unsigned uLUN;
+ /** The driver name. */
+ Utf8Str strName;
+ /** Whether input is enabled. */
+ bool fEnabledIn;
+ /** Whether output is enabled. */
+ bool fEnabledOut;
+};
+
+class Console;
+
+/**
+ * Base class for all audio drivers implemented in Main.
+ */
+class AudioDriver
+{
+
+public:
+ AudioDriver(Console *pConsole);
+ virtual ~AudioDriver();
+
+ /** Copy assignment operator. */
+ AudioDriver &operator=(AudioDriver const &a_rThat) RT_NOEXCEPT;
+
+ Console *GetParent(void) { return mpConsole; }
+
+ AudioDriverCfg *GetConfig(void) { return &mCfg; }
+ int InitializeConfig(AudioDriverCfg *pCfg);
+
+ /** Checks if audio is configured or not. */
+ bool isConfigured() const { return mCfg.strName.isNotEmpty(); }
+
+ bool IsAttached(void) { return mfAttached; }
+
+ int doAttachDriverViaEmt(PUVM pUVM, PCVMMR3VTABLE pVMM, util::AutoWriteLock *pAutoLock);
+ int doDetachDriverViaEmt(PUVM pUVM, PCVMMR3VTABLE pVMM, util::AutoWriteLock *pAutoLock);
+
+protected:
+ static DECLCALLBACK(int) attachDriverOnEmt(AudioDriver *pThis);
+ static DECLCALLBACK(int) detachDriverOnEmt(AudioDriver *pThis);
+
+ int configure(unsigned uLUN, bool fAttach);
+
+ /**
+ * Virtual function for child specific driver configuration.
+ *
+ * This is called at the end of AudioDriver::configure().
+ *
+ * @returns VBox status code.
+ * @param pLunCfg CFGM configuration node of the driver.
+ * @param pVMM The VMM ring-3 vtable.
+ */
+ virtual int configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM)
+ {
+ RT_NOREF(pLunCfg, pVMM);
+ return VINF_SUCCESS;
+ }
+
+protected:
+
+ /** Pointer to parent. */
+ Console *mpConsole;
+ /** The driver's configuration. */
+ AudioDriverCfg mCfg;
+ /** Whether the driver is attached or not. */
+ bool mfAttached;
+};
+
+#endif /* !MAIN_INCLUDED_AudioDriver_h */
+
diff --git a/src/VBox/Main/include/AudioSettingsImpl.h b/src/VBox/Main/include/AudioSettingsImpl.h
new file mode 100644
index 00000000..7786a27e
--- /dev/null
+++ b/src/VBox/Main/include/AudioSettingsImpl.h
@@ -0,0 +1,84 @@
+/* $Id: AudioSettingsImpl.h $ */
+
+/** @file
+ *
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_AudioSettingsImpl_h
+#define MAIN_INCLUDED_AudioSettingsImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "AudioAdapterImpl.h"
+#include "GuestOSTypeImpl.h"
+
+#include "AudioSettingsWrap.h"
+namespace settings
+{
+ struct AudioSettings;
+}
+
+class ATL_NO_VTABLE AudioSettings :
+ public AudioSettingsWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(AudioSettings)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aParent);
+ HRESULT init(Machine *aParent, AudioSettings *aThat);
+ HRESULT initCopy(Machine *aParent, AudioSettings *aThat);
+ void uninit();
+
+ HRESULT getHostAudioDevice(AudioDirection_T aUsage, ComPtr<IHostAudioDevice> &aDevice);
+ HRESULT setHostAudioDevice(const ComPtr<IHostAudioDevice> &aDevice, AudioDirection_T aUsage);
+ HRESULT getAdapter(ComPtr<IAudioAdapter> &aAdapter);
+
+ // public methods only for internal purposes
+ bool i_canChangeSettings(void);
+ void i_onAdapterChanged(IAudioAdapter *pAdapter);
+ void i_onHostDeviceChanged(IHostAudioDevice *pDevice, bool fIsNew, AudioDeviceState_T enmState, IVirtualBoxErrorInfo *pErrInfo);
+ void i_onSettingsChanged(void);
+ HRESULT i_loadSettings(const settings::AudioAdapter &data);
+ HRESULT i_saveSettings(settings::AudioAdapter &data);
+ void i_copyFrom(AudioSettings *aThat);
+ HRESULT i_applyDefaults(ComObjPtr<GuestOSType> &aGuestOsType);
+
+ void i_rollback();
+ void i_commit();
+
+private:
+
+ struct Data;
+ Data *m;
+};
+#endif /* !MAIN_INCLUDED_AudioSettingsImpl_h */
+
diff --git a/src/VBox/Main/include/AuthLibrary.h b/src/VBox/Main/include/AuthLibrary.h
new file mode 100644
index 00000000..f432ff79
--- /dev/null
+++ b/src/VBox/Main/include/AuthLibrary.h
@@ -0,0 +1,54 @@
+/* $Id: AuthLibrary.h $ */
+/** @file
+ * Main - external authentication library interface.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_AuthLibrary_h
+#define MAIN_INCLUDED_AuthLibrary_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/VBoxAuth.h>
+#include <iprt/types.h>
+
+typedef struct AUTHLIBRARYCONTEXT
+{
+ RTLDRMOD hAuthLibrary;
+ PAUTHENTRY pfnAuthEntry;
+ PAUTHENTRY2 pfnAuthEntry2;
+ PAUTHENTRY3 pfnAuthEntry3;
+} AUTHLIBRARYCONTEXT;
+
+int AuthLibLoad(AUTHLIBRARYCONTEXT *pAuthLibCtx, const char *pszLibrary);
+void AuthLibUnload(AUTHLIBRARYCONTEXT *pAuthLibCtx);
+
+AuthResult AuthLibAuthenticate(const AUTHLIBRARYCONTEXT *pAuthLibCtx,
+ PCRTUUID pUuid, AuthGuestJudgement guestJudgement,
+ const char *pszUser, const char *pszPassword, const char *pszDomain,
+ uint32_t u32ClientId);
+void AuthLibDisconnect(const AUTHLIBRARYCONTEXT *pAuthLibCtx, PCRTUUID pUuid, uint32_t u32ClientId);
+
+#endif /* !MAIN_INCLUDED_AuthLibrary_h */
diff --git a/src/VBox/Main/include/AutoCaller.h b/src/VBox/Main/include/AutoCaller.h
new file mode 100644
index 00000000..00ec27d7
--- /dev/null
+++ b/src/VBox/Main/include/AutoCaller.h
@@ -0,0 +1,535 @@
+/* $Id: AutoCaller.h $ */
+/** @file
+ *
+ * VirtualBox object caller handling definitions
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_AutoCaller_h
+#define MAIN_INCLUDED_AutoCaller_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "ObjectState.h"
+
+#include "VBox/com/AutoLock.h"
+
+// Forward declaration needed, but nothing more.
+class VirtualBoxBase;
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AutoCaller* classes
+//
+////////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * Smart class that automatically increases the number of normal (non-limited)
+ * callers of the given VirtualBoxBase object when an instance is constructed
+ * and decreases it back when the created instance goes out of scope (i.e. gets
+ * destroyed).
+ *
+ * If #rc() returns a failure after the instance creation, it means that
+ * the managed VirtualBoxBase object is not Ready, or in any other invalid
+ * state, so that the caller must not use the object and can return this
+ * failed result code to the upper level.
+ *
+ * See ObjectState::addCaller() and ObjectState::releaseCaller() for more
+ * details about object callers.
+ *
+ * A typical usage pattern to declare a normal method of some object (i.e. a
+ * method that is valid only when the object provides its full
+ * functionality) is:
+ * <code>
+ * STDMETHODIMP Component::Foo()
+ * {
+ * AutoCaller autoCaller(this);
+ * HRESULT hrc = autoCaller.rc();
+ * if (SUCCEEDED(hrc))
+ * {
+ * ...
+ * }
+ * return hrc;
+ * }
+ * </code>
+ */
+class AutoCaller
+{
+public:
+ /**
+ * Default constructor. Not terribly useful, but it's valid to create
+ * an instance without associating it with an object. It's a no-op,
+ * like the more useful constructor below when NULL is passed to it.
+ */
+ AutoCaller()
+ {
+ init(NULL, false);
+ }
+
+ /**
+ * Increases the number of callers of the given object by calling
+ * ObjectState::addCaller() for the corresponding member instance.
+ *
+ * @param aObj Object to add a normal caller to. If NULL, this
+ * instance is effectively turned to no-op (where
+ * rc() will return S_OK).
+ */
+ AutoCaller(VirtualBoxBase *aObj)
+ {
+ init(aObj, false);
+ }
+
+ /**
+ * If the number of callers was successfully increased, decreases it
+ * using ObjectState::releaseCaller(), otherwise does nothing.
+ */
+ ~AutoCaller()
+ {
+ if (mObj && SUCCEEDED(mRC))
+ mObj->getObjectState().releaseCaller();
+ }
+
+ /**
+ * Returns the stored result code returned by ObjectState::addCaller()
+ * after instance creation or after the last #add() call. A successful
+ * result code means the number of callers was successfully increased.
+ */
+ HRESULT rc() const { return mRC; }
+
+ /**
+ * Returns |true| if |SUCCEEDED(rc())| is |true|, for convenience.
+ * |true| means the number of callers was successfully increased.
+ */
+ bool isOk() const { return SUCCEEDED(mRC); }
+
+ /**
+ * Returns |true| if |FAILED(rc())| is |true|, for convenience.
+ * |true| means the number of callers was _not_ successfully increased.
+ */
+ bool isNotOk() const { return FAILED(mRC); }
+
+ /**
+ * Temporarily decreases the number of callers of the managed object.
+ * May only be called if #isOk() returns |true|. Note that #rc() will
+ * return E_FAIL after this method succeeds.
+ */
+ void release()
+ {
+ Assert(SUCCEEDED(mRC));
+ if (SUCCEEDED(mRC))
+ {
+ if (mObj)
+ mObj->getObjectState().releaseCaller();
+ mRC = E_FAIL;
+ }
+ }
+
+ /**
+ * Restores the number of callers decreased by #release(). May only be
+ * called after #release().
+ */
+ void add()
+ {
+ Assert(!SUCCEEDED(mRC));
+ if (mObj && !SUCCEEDED(mRC))
+ mRC = mObj->getObjectState().addCaller(mLimited);
+ }
+
+ /**
+ * Attaches another object to this caller instance.
+ * The previous object's caller is released before the new one is added.
+ *
+ * @param aObj New object to attach, may be @c NULL.
+ */
+ void attach(VirtualBoxBase *aObj)
+ {
+ /* detect simple self-reattachment */
+ if (mObj != aObj)
+ {
+ if (mObj && SUCCEEDED(mRC))
+ release();
+ else if (!mObj)
+ {
+ /* Fix up the success state when nothing is attached. Otherwise
+ * there are a couple of assertion which would trigger. */
+ mRC = E_FAIL;
+ }
+ mObj = aObj;
+ add();
+ }
+ }
+
+ /** Verbose equivalent to <tt>attach(NULL)</tt>. */
+ void detach() { attach(NULL); }
+
+protected:
+ /**
+ * Internal constructor: Increases the number of callers of the given
+ * object (either normal or limited variant) by calling
+ * ObjectState::addCaller() for the corresponding member instance.
+ *
+ * @param aObj Object to add a caller to. If NULL, this
+ * instance is effectively turned to no-op (where
+ * rc() will return S_OK).
+ * @param aLimited If |false|, then it's a regular caller, otherwise a
+ * limited caller.
+ */
+ void init(VirtualBoxBase *aObj, bool aLimited)
+ {
+ mObj = aObj;
+ mRC = S_OK;
+ mLimited = aLimited;
+ if (mObj)
+ mRC = mObj->getObjectState().addCaller(mLimited);
+ }
+
+private:
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(AutoCaller);
+ DECLARE_CLS_NEW_DELETE_NOOP(AutoCaller);
+
+ VirtualBoxBase *mObj;
+ HRESULT mRC;
+ bool mLimited;
+};
+
+/**
+ * Smart class that automatically increases the number of limited callers of
+ * the given VirtualBoxBase object when an instance is constructed and
+ * decreases it back when the created instance goes out of scope (i.e. gets
+ * destroyed).
+ *
+ * A typical usage pattern to declare a limited method of some object (i.e.
+ * a method that is valid even if the object doesn't provide its full
+ * functionality) is:
+ * <code>
+ * STDMETHODIMP Component::Bar()
+ * {
+ * AutoLimitedCaller autoCaller(this);
+ * HRESULT hrc = autoCaller.rc();
+ * if (SUCCEEDED(hrc))
+ * {
+ * ...
+ * }
+ * return hrc;
+ * </code>
+ *
+ * See AutoCaller for more information about auto caller functionality.
+ */
+class AutoLimitedCaller : public AutoCaller
+{
+public:
+ /**
+ * Default constructor. Not terribly useful, but it's valid to create
+ * an instance without associating it with an object. It's a no-op,
+ * like the more useful constructor below when NULL is passed to it.
+ */
+ AutoLimitedCaller()
+ {
+ AutoCaller::init(NULL, true);
+ }
+
+ /**
+ * Increases the number of callers of the given object by calling
+ * ObjectState::addCaller() for the corresponding member instance.
+ *
+ * @param aObj Object to add a limited caller to. If NULL, this
+ * instance is effectively turned to no-op (where
+ * rc() will return S_OK).
+ */
+ AutoLimitedCaller(VirtualBoxBase *aObj)
+ {
+ AutoCaller::init(aObj, true);
+ }
+
+private:
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(AutoLimitedCaller); /* Shuts up MSC warning C4625. */
+};
+
+/**
+ * Smart class to enclose the state transition NotReady->InInit->Ready.
+ *
+ * The purpose of this span is to protect object initialization.
+ *
+ * Instances must be created as a stack-based variable taking |this| pointer
+ * as the argument at the beginning of init() methods of VirtualBoxBase
+ * subclasses. When this variable is created it automatically places the
+ * object to the InInit state.
+ *
+ * When the created variable goes out of scope (i.e. gets destroyed) then,
+ * depending on the result status of this initialization span, it either
+ * places the object to Ready or Limited state or calls the object's
+ * VirtualBoxBase::uninit() method which is supposed to place the object
+ * back to the NotReady state using the AutoUninitSpan class.
+ *
+ * The initial result status of the initialization span is determined by the
+ * @a aResult argument of the AutoInitSpan constructor (Result::Failed by
+ * default). Inside the initialization span, the success status can be set
+ * to Result::Succeeded using #setSucceeded(), to to Result::Limited using
+ * #setLimited() or to Result::Failed using #setFailed(). Please don't
+ * forget to set the correct success status before getting the AutoInitSpan
+ * variable destroyed (for example, by performing an early return from
+ * the init() method)!
+ *
+ * Note that if an instance of this class gets constructed when the object
+ * is in the state other than NotReady, #isOk() returns |false| and methods
+ * of this class do nothing: the state transition is not performed.
+ *
+ * A typical usage pattern is:
+ * <code>
+ * HRESULT Component::init()
+ * {
+ * AutoInitSpan autoInitSpan(this);
+ * AssertReturn(autoInitSpan.isOk(), E_FAIL);
+ * ...
+ * if (FAILED(rc))
+ * return rc;
+ * ...
+ * if (SUCCEEDED(rc))
+ * autoInitSpan.setSucceeded();
+ * return rc;
+ * }
+ * </code>
+ *
+ * @note Never create instances of this class outside init() methods of
+ * VirtualBoxBase subclasses and never pass anything other than |this|
+ * as the argument to the constructor!
+ */
+class AutoInitSpan
+{
+public:
+
+ enum Result { Failed = 0x0, Succeeded = 0x1, Limited = 0x2 };
+
+ AutoInitSpan(VirtualBoxBase *aObj, Result aResult = Failed);
+ ~AutoInitSpan();
+
+ /**
+ * Returns |true| if this instance has been created at the right moment
+ * (when the object was in the NotReady state) and |false| otherwise.
+ */
+ bool isOk() const { return mOk; }
+
+ /**
+ * Sets the initialization status to Succeeded to indicates successful
+ * initialization. The AutoInitSpan destructor will place the managed
+ * VirtualBoxBase object to the Ready state.
+ */
+ void setSucceeded() { mResult = Succeeded; }
+
+ /**
+ * Sets the initialization status to Succeeded to indicate limited
+ * (partly successful) initialization. The AutoInitSpan destructor will
+ * place the managed VirtualBoxBase object to the Limited state.
+ */
+ void setLimited() { mResult = Limited; }
+
+ /**
+ * Sets the initialization status to Succeeded to indicate limited
+ * (partly successful) initialization but also adds the initialization
+ * error if required for further reporting. The AutoInitSpan destructor
+ * will place the managed VirtualBoxBase object to the Limited state.
+ */
+ void setLimited(HRESULT rc)
+ {
+ mResult = Limited;
+ mFailedRC = rc;
+ mpFailedEI = new ErrorInfo();
+ }
+
+ /**
+ * Sets the initialization status to Failure to indicates failed
+ * initialization. The AutoInitSpan destructor will place the managed
+ * VirtualBoxBase object to the InitFailed state and will automatically
+ * call its uninit() method which is supposed to place the object back
+ * to the NotReady state using AutoUninitSpan.
+ */
+ void setFailed(HRESULT rc = E_ACCESSDENIED)
+ {
+ mResult = Failed;
+ mFailedRC = rc;
+ mpFailedEI = new ErrorInfo();
+ }
+
+ /** Returns the current initialization result. */
+ Result result() { return mResult; }
+
+private:
+
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(AutoInitSpan);
+ DECLARE_CLS_NEW_DELETE_NOOP(AutoInitSpan);
+
+ VirtualBoxBase *mObj;
+ Result mResult : 3; // must be at least total number of bits + 1 (sign)
+ bool mOk : 1;
+ HRESULT mFailedRC;
+ ErrorInfo *mpFailedEI;
+};
+
+/**
+ * Smart class to enclose the state transition Limited->InInit->Ready.
+ *
+ * The purpose of this span is to protect object re-initialization.
+ *
+ * Instances must be created as a stack-based variable taking |this| pointer
+ * as the argument at the beginning of methods of VirtualBoxBase
+ * subclasses that try to re-initialize the object to bring it to the Ready
+ * state (full functionality) after partial initialization (limited
+ * functionality). When this variable is created, it automatically places
+ * the object to the InInit state.
+ *
+ * When the created variable goes out of scope (i.e. gets destroyed),
+ * depending on the success status of this initialization span, it either
+ * places the object to the Ready state or brings it back to the Limited
+ * state.
+ *
+ * The initial success status of the re-initialization span is |false|. In
+ * order to make it successful, #setSucceeded() must be called before the
+ * instance is destroyed.
+ *
+ * Note that if an instance of this class gets constructed when the object
+ * is in the state other than Limited, #isOk() returns |false| and methods
+ * of this class do nothing: the state transition is not performed.
+ *
+ * A typical usage pattern is:
+ * <code>
+ * HRESULT Component::reinit()
+ * {
+ * AutoReinitSpan autoReinitSpan(this);
+ * AssertReturn(autoReinitSpan.isOk(), E_FAIL);
+ * ...
+ * if (FAILED(rc))
+ * return rc;
+ * ...
+ * if (SUCCEEDED(rc))
+ * autoReinitSpan.setSucceeded();
+ * return rc;
+ * }
+ * </code>
+ *
+ * @note Never create instances of this class outside re-initialization
+ * methods of VirtualBoxBase subclasses and never pass anything other than
+ * |this| as the argument to the constructor!
+ */
+class AutoReinitSpan
+{
+public:
+
+ AutoReinitSpan(VirtualBoxBase *aObj);
+ ~AutoReinitSpan();
+
+ /**
+ * Returns |true| if this instance has been created at the right moment
+ * (when the object was in the Limited state) and |false| otherwise.
+ */
+ bool isOk() const { return mOk; }
+
+ /**
+ * Sets the re-initialization status to Succeeded to indicates
+ * successful re-initialization. The AutoReinitSpan destructor will place
+ * the managed VirtualBoxBase object to the Ready state.
+ */
+ void setSucceeded() { mSucceeded = true; }
+
+private:
+
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(AutoReinitSpan);
+ DECLARE_CLS_NEW_DELETE_NOOP(AutoReinitSpan);
+
+ VirtualBoxBase *mObj;
+ bool mSucceeded : 1;
+ bool mOk : 1;
+};
+
+/**
+ * Smart class to enclose the state transition Ready->InUninit->NotReady,
+ * InitFailed->InUninit->NotReady.
+ *
+ * The purpose of this span is to protect object uninitialization.
+ *
+ * Instances must be created as a stack-based variable taking |this| pointer
+ * as the argument at the beginning of uninit() methods of VirtualBoxBase
+ * subclasses. When this variable is created it automatically places the
+ * object to the InUninit state, unless it is already in the NotReady state
+ * as indicated by #uninitDone() returning |true|. In the latter case, the
+ * uninit() method must immediately return because there should be nothing
+ * to uninitialize.
+ *
+ * When this variable goes out of scope (i.e. gets destroyed), it places the
+ * object to NotReady state.
+ *
+ * A typical usage pattern is:
+ * <code>
+ * void Component::uninit()
+ * {
+ * AutoUninitSpan autoUninitSpan(this);
+ * if (autoUninitSpan.uninitDone())
+ * return;
+ * ...
+ * }
+ * </code>
+ *
+ * @note The constructor of this class blocks the current thread execution
+ * until the number of callers added to the object using
+ * ObjectState::addCaller() or AutoCaller drops to zero. For this reason,
+ * it is forbidden to create instances of this class (or call uninit())
+ * within the AutoCaller or ObjectState::addCaller() scope because it is
+ * a guaranteed deadlock.
+ *
+ * @note Never create instances of this class outside uninit() methods and
+ * never pass anything other than |this| as the argument to the
+ * constructor!
+ */
+class AutoUninitSpan
+{
+public:
+
+ AutoUninitSpan(VirtualBoxBase *aObj, bool fTry = false);
+ ~AutoUninitSpan();
+
+ /** |true| when uninit() is called as a result of init() failure */
+ bool initFailed() { return mInitFailed; }
+
+ /** |true| when uninit() has already been called (so the object is NotReady) */
+ bool uninitDone() { return mUninitDone; }
+
+ /** |true| when uninit() has failed, relevant only if it was a "try uninit" */
+ bool uninitFailed() { return mUninitFailed; }
+
+ void setSucceeded();
+
+private:
+
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(AutoUninitSpan);
+ DECLARE_CLS_NEW_DELETE_NOOP(AutoUninitSpan);
+
+ VirtualBoxBase *mObj;
+ bool mInitFailed : 1;
+ bool mUninitDone : 1;
+ bool mUninitFailed : 1;
+};
+
+#endif /* !MAIN_INCLUDED_AutoCaller_h */
diff --git a/src/VBox/Main/include/AutoStateDep.h b/src/VBox/Main/include/AutoStateDep.h
new file mode 100644
index 00000000..47a6d23f
--- /dev/null
+++ b/src/VBox/Main/include/AutoStateDep.h
@@ -0,0 +1,214 @@
+/* $Id: AutoStateDep.h $ */
+
+#ifndef MAIN_INCLUDED_AutoStateDep_h
+#define MAIN_INCLUDED_AutoStateDep_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/** @file
+ *
+ * AutoStateDep template classes, formerly in MachineImpl.h. Use these if
+ * you need to ensure that the machine state does not change over a certain
+ * period of time.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+ /**
+ * Helper class that safely manages the machine state dependency by
+ * calling Machine::addStateDependency() on construction and
+ * Machine::releaseStateDependency() on destruction. Intended for Machine
+ * children. The usage pattern is:
+ *
+ * @code
+ * AutoCaller autoCaller(this);
+ * if (FAILED(autoCaller.rc())) return autoCaller.rc();
+ *
+ * Machine::AutoStateDependency<MutableStateDep> adep(mParent);
+ * if (FAILED(stateDep.rc())) return stateDep.rc();
+ * ...
+ * // code that depends on the particular machine state
+ * ...
+ * @endcode
+ *
+ * Note that it is more convenient to use the following individual
+ * shortcut classes instead of using this template directly:
+ * AutoAnyStateDependency, AutoMutableStateDependency,
+ * AutoMutableOrSavedStateDependency, AutoMutableOrRunningStateDependency
+ * or AutoMutableOrSavedOrRunningStateDependency. The usage pattern is
+ * exactly the same as above except that there is no need to specify the
+ * template argument because it is already done by the shortcut class.
+ *
+ * @param taDepType Dependency type to manage.
+ */
+ template <Machine::StateDependency taDepType = Machine::AnyStateDep>
+ class AutoStateDependency
+ {
+ public:
+
+ AutoStateDependency(Machine *aThat)
+ : mThat(aThat), mRC(S_OK),
+ mMachineState(MachineState_Null),
+ mRegistered(FALSE)
+ {
+ Assert(aThat);
+ mRC = aThat->i_addStateDependency(taDepType, &mMachineState,
+ &mRegistered);
+ }
+ ~AutoStateDependency()
+ {
+ if (SUCCEEDED(mRC))
+ mThat->i_releaseStateDependency();
+ }
+
+ /** Decreases the number of dependencies before the instance is
+ * destroyed. Note that will reset #rc() to E_FAIL. */
+ void release()
+ {
+ AssertReturnVoid(SUCCEEDED(mRC));
+ mThat->i_releaseStateDependency();
+ mRC = E_FAIL;
+ }
+
+ /** Restores the number of callers after by #release(). #rc() will be
+ * reset to the result of calling addStateDependency() and must be
+ * rechecked to ensure the operation succeeded. */
+ void add()
+ {
+ AssertReturnVoid(!SUCCEEDED(mRC));
+ mRC = mThat->i_addStateDependency(taDepType, &mMachineState,
+ &mRegistered);
+ }
+
+ /** Returns the result of Machine::addStateDependency(). */
+ HRESULT rc() const { return mRC; }
+
+ /** Shortcut to SUCCEEDED(rc()). */
+ bool isOk() const { return SUCCEEDED(mRC); }
+
+ /** Returns the machine state value as returned by
+ * Machine::addStateDependency(). */
+ MachineState_T machineState() const { return mMachineState; }
+
+ /** Returns the machine state value as returned by
+ * Machine::addStateDependency(). */
+ BOOL machineRegistered() const { return mRegistered; }
+
+ protected:
+
+ Machine *mThat;
+ HRESULT mRC;
+ MachineState_T mMachineState;
+ BOOL mRegistered;
+
+ private:
+
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(AutoStateDependency);
+ DECLARE_CLS_NEW_DELETE_NOOP(AutoStateDependency);
+ };
+
+ /**
+ * Shortcut to AutoStateDependency<AnyStateDep>.
+ * See AutoStateDependency to get the usage pattern.
+ *
+ * Accepts any machine state and guarantees the state won't change before
+ * this object is destroyed. If the machine state cannot be protected (as
+ * a result of the state change currently in progress), this instance's
+ * #rc() method will indicate a failure, and the caller is not allowed to
+ * rely on any particular machine state and should return the failed
+ * result code to the upper level.
+ */
+ typedef AutoStateDependency<Machine::AnyStateDep> AutoAnyStateDependency;
+
+ /**
+ * Shortcut to AutoStateDependency<MutableStateDep>.
+ * See AutoStateDependency to get the usage pattern.
+ *
+ * Succeeds only if the machine state is in one of the mutable states, and
+ * guarantees the given mutable state won't change before this object is
+ * destroyed. If the machine is not mutable, this instance's #rc() method
+ * will indicate a failure, and the caller is not allowed to rely on any
+ * particular machine state and should return the failed result code to
+ * the upper level.
+ *
+ * Intended to be used within all setter methods of IMachine
+ * children objects (DVDDrive, NetworkAdapter, AudioAdapter, etc.) to
+ * provide data protection and consistency. There must be no VM process,
+ * i.e. use for settings changes which are valid when the VM is shut down.
+ */
+ typedef AutoStateDependency<Machine::MutableStateDep> AutoMutableStateDependency;
+
+ /**
+ * Shortcut to AutoStateDependency<MutableOrSavedStateDep>.
+ * See AutoStateDependency to get the usage pattern.
+ *
+ * Succeeds only if the machine state is in one of the mutable states, or
+ * if the machine is in the Saved state, and guarantees the given mutable
+ * state won't change before this object is destroyed. If the machine is
+ * not mutable, this instance's #rc() method will indicate a failure, and
+ * the caller is not allowed to rely on any particular machine state and
+ * should return the failed result code to the upper level.
+ *
+ * Intended to be used within setter methods of IMachine
+ * children objects that may operate on shut down or Saved machines.
+ */
+ typedef AutoStateDependency<Machine::MutableOrSavedStateDep> AutoMutableOrSavedStateDependency;
+
+ /**
+ * Shortcut to AutoStateDependency<MutableOrRunningStateDep>.
+ * See AutoStateDependency to get the usage pattern.
+ *
+ * Succeeds only if the machine state is in one of the mutable states, or
+ * if the machine is in the Running or Paused state, and guarantees the
+ * given mutable state won't change before this object is destroyed. If
+ * the machine is not mutable, this instance's #rc() method will indicate
+ * a failure, and the caller is not allowed to rely on any particular
+ * machine state and should return the failed result code to the upper
+ * level.
+ *
+ * Intended to be used within setter methods of IMachine
+ * children objects that may operate on shut down or running machines.
+ */
+ typedef AutoStateDependency<Machine::MutableOrRunningStateDep> AutoMutableOrRunningStateDependency;
+
+ /**
+ * Shortcut to AutoStateDependency<MutableOrSavedOrRunningStateDep>.
+ * See AutoStateDependency to get the usage pattern.
+ *
+ * Succeeds only if the machine state is in one of the mutable states, or
+ * if the machine is in the Running, Paused or Saved state, and guarantees
+ * the given mutable state won't change before this object is destroyed.
+ * If the machine is not mutable, this instance's #rc() method will
+ * indicate a failure, and the caller is not allowed to rely on any
+ * particular machine state and should return the failed result code to
+ * the upper level.
+ *
+ * Intended to be used within setter methods of IMachine
+ * children objects that may operate on shut down, running or saved
+ * machines.
+ */
+ typedef AutoStateDependency<Machine::MutableOrSavedOrRunningStateDep> AutoMutableOrSavedOrRunningStateDependency;
+
+#endif /* !MAIN_INCLUDED_AutoStateDep_h */
+
diff --git a/src/VBox/Main/include/AutostartDb.h b/src/VBox/Main/include/AutostartDb.h
new file mode 100644
index 00000000..01a3c7bd
--- /dev/null
+++ b/src/VBox/Main/include/AutostartDb.h
@@ -0,0 +1,109 @@
+/* $Id: AutostartDb.h $ */
+/** @file
+ * Main - Autostart database Interfaces.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_AutostartDb_h
+#define MAIN_INCLUDED_AutostartDb_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+#include <iprt/types.h>
+#include <iprt/critsect.h>
+
+class AutostartDb
+{
+ public:
+
+ AutostartDb();
+ ~AutostartDb();
+
+ /**
+ * Sets the path to the autostart database.
+ *
+ * @returns VBox status code.
+ * @param pszAutostartDbPathNew Path to the autostart database.
+ */
+ int setAutostartDbPath(const char *pszAutostartDbPathNew);
+
+ /**
+ * Add a autostart VM to the global database.
+ *
+ * @returns VBox status code.
+ * @retval VERR_PATH_NOT_FOUND if the autostart database directory is not set.
+ * @param pszVMId ID of the VM to add.
+ */
+ int addAutostartVM(const char *pszVMId);
+
+ /**
+ * Remove a autostart VM from the global database.
+ *
+ * @returns VBox status code.
+ * @retval VERR_PATH_NOT_FOUND if the autostart database directory is not set.
+ * @param pszVMId ID of the VM to remove.
+ */
+ int removeAutostartVM(const char *pszVMId);
+
+ /**
+ * Add a autostop VM to the global database.
+ *
+ * @returns VBox status code.
+ * @retval VERR_PATH_NOT_FOUND if the autostart database directory is not set.
+ * @param pszVMId ID of the VM to add.
+ */
+ int addAutostopVM(const char *pszVMId);
+
+ /**
+ * Remove a autostop VM from the global database.
+ *
+ * @returns VBox status code.
+ * @retval VERR_PATH_NOT_FOUND if the autostart database directory is not set.
+ * @param pszVMId ID of the VM to remove.
+ */
+ int removeAutostopVM(const char *pszVMId);
+
+ private:
+
+#ifdef RT_OS_LINUX
+ /** Critical section protecting the database against concurrent access. */
+ RTCRITSECT CritSect;
+ /** Path to the autostart database. */
+ char *m_pszAutostartDbPath;
+
+ /**
+ * Autostart database modification worker.
+ *
+ * @returns VBox status code.
+ * @param fAutostart Flag whether the autostart or autostop database is modified.
+ * @param fAddVM Flag whether a VM is added or removed from the database.
+ */
+ int autostartModifyDb(bool fAutostart, bool fAddVM);
+#endif
+};
+
+#endif /* !MAIN_INCLUDED_AutostartDb_h */
+
diff --git a/src/VBox/Main/include/BIOSSettingsImpl.h b/src/VBox/Main/include/BIOSSettingsImpl.h
new file mode 100644
index 00000000..ad6eeded
--- /dev/null
+++ b/src/VBox/Main/include/BIOSSettingsImpl.h
@@ -0,0 +1,102 @@
+/* $Id: BIOSSettingsImpl.h $ */
+
+/** @file
+ *
+ * VirtualBox COM class implementation - Machine BIOS settings.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_BIOSSettingsImpl_h
+#define MAIN_INCLUDED_BIOSSettingsImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "BIOSSettingsWrap.h"
+
+class GuestOSType;
+
+namespace settings
+{
+ struct BIOSSettings;
+}
+
+class ATL_NO_VTABLE BIOSSettings :
+ public BIOSSettingsWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(BIOSSettings)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *parent);
+ HRESULT init(Machine *parent, BIOSSettings *that);
+ HRESULT initCopy(Machine *parent, BIOSSettings *that);
+ void uninit();
+
+ // public methods for internal purposes only
+ HRESULT i_loadSettings(const settings::BIOSSettings &data);
+ HRESULT i_saveSettings(settings::BIOSSettings &data);
+
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(BIOSSettings *aThat);
+ void i_applyDefaults(GuestOSType *aOsType);
+
+private:
+
+ // wrapped IBIOSettings properties
+ HRESULT getLogoFadeIn(BOOL *enabled);
+ HRESULT setLogoFadeIn(BOOL enable);
+ HRESULT getLogoFadeOut(BOOL *enabled);
+ HRESULT setLogoFadeOut(BOOL enable);
+ HRESULT getLogoDisplayTime(ULONG *displayTime);
+ HRESULT setLogoDisplayTime(ULONG displayTime);
+ HRESULT getLogoImagePath(com::Utf8Str &imagePath);
+ HRESULT setLogoImagePath(const com::Utf8Str &imagePath);
+ HRESULT getBootMenuMode(BIOSBootMenuMode_T *bootMenuMode);
+ HRESULT setBootMenuMode(BIOSBootMenuMode_T bootMenuMode);
+ HRESULT getACPIEnabled(BOOL *enabled);
+ HRESULT setACPIEnabled(BOOL enable);
+ HRESULT getIOAPICEnabled(BOOL *aIOAPICEnabled);
+ HRESULT setIOAPICEnabled(BOOL aIOAPICEnabled);
+ HRESULT getAPICMode(APICMode_T *aAPICMode);
+ HRESULT setAPICMode(APICMode_T aAPICMode);
+ HRESULT getTimeOffset(LONG64 *offset);
+ HRESULT setTimeOffset(LONG64 offset);
+ HRESULT getPXEDebugEnabled(BOOL *enabled);
+ HRESULT setPXEDebugEnabled(BOOL enable);
+ HRESULT getSMBIOSUuidLittleEndian(BOOL *enabled);
+ HRESULT setSMBIOSUuidLittleEndian(BOOL enable);
+
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_BIOSSettingsImpl_h */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/BandwidthControlImpl.h b/src/VBox/Main/include/BandwidthControlImpl.h
new file mode 100644
index 00000000..dfafb22e
--- /dev/null
+++ b/src/VBox/Main/include/BandwidthControlImpl.h
@@ -0,0 +1,111 @@
+/* $Id: BandwidthControlImpl.h $ */
+/** @file
+ *
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_BandwidthControlImpl_h
+#define MAIN_INCLUDED_BandwidthControlImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "BandwidthControlWrap.h"
+
+class BandwidthGroup;
+
+namespace settings
+{
+ struct IOSettings;
+}
+
+class ATL_NO_VTABLE BandwidthControl :
+ public BandwidthControlWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(BandwidthControl)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aParent);
+ HRESULT init(Machine *aParent, BandwidthControl *aThat);
+ HRESULT initCopy(Machine *aParent, BandwidthControl *aThat);
+ void uninit();
+
+ // public internal methods
+ HRESULT i_loadSettings(const settings::IOSettings &data);
+ HRESULT i_saveSettings(settings::IOSettings &data);
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(BandwidthControl *aThat);
+ Machine *i_getMachine() const;
+ HRESULT i_getBandwidthGroupByName(const Utf8Str &aName,
+ ComObjPtr<BandwidthGroup> &aBandwidthGroup,
+ bool aSetError /* = false */);
+
+private:
+
+ // wrapped IBandwidthControl properties
+ HRESULT getNumGroups(ULONG *aNumGroups);
+
+ // wrapped IBandwidthControl methods
+ HRESULT createBandwidthGroup(const com::Utf8Str &aName,
+ BandwidthGroupType_T aType,
+ LONG64 aMaxBytesPerSec);
+ HRESULT deleteBandwidthGroup(const com::Utf8Str &aName);
+ HRESULT getBandwidthGroup(const com::Utf8Str &aName,
+ ComPtr<IBandwidthGroup> &aBandwidthGroup);
+ HRESULT getAllBandwidthGroups(std::vector<ComPtr<IBandwidthGroup> > &aBandwidthGroups);
+
+ // Data
+ typedef std::list< ComObjPtr<BandwidthGroup> > BandwidthGroupList;
+
+ struct Data
+ {
+ Data(Machine *pMachine)
+ : pParent(pMachine)
+ { }
+
+ ~Data()
+ {};
+
+ Machine * const pParent;
+
+ // peer machine's bandwidth control
+ const ComObjPtr<BandwidthControl> pPeer;
+
+ // the following fields need special backup/rollback/commit handling,
+ // so they cannot be a part of BackupableData
+ Backupable<BandwidthGroupList> llBandwidthGroups;
+ };
+
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_BandwidthControlImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/BandwidthGroupImpl.h b/src/VBox/Main/include/BandwidthGroupImpl.h
new file mode 100644
index 00000000..6038b1d3
--- /dev/null
+++ b/src/VBox/Main/include/BandwidthGroupImpl.h
@@ -0,0 +1,115 @@
+/* $Id: BandwidthGroupImpl.h $ */
+/** @file
+ *
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_BandwidthGroupImpl_h
+#define MAIN_INCLUDED_BandwidthGroupImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/settings.h>
+#include "BandwidthControlImpl.h"
+#include "BandwidthGroupWrap.h"
+
+
+class ATL_NO_VTABLE BandwidthGroup :
+ public BandwidthGroupWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(BandwidthGroup)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(BandwidthControl *aParent,
+ const com::Utf8Str &aName,
+ BandwidthGroupType_T aType,
+ LONG64 aMaxBytesPerSec);
+ HRESULT init(BandwidthControl *aParent, BandwidthGroup *aThat, bool aReshare = false);
+ HRESULT initCopy(BandwidthControl *aParent, BandwidthGroup *aThat);
+ void uninit();
+
+ // public methods only for internal purposes
+ void i_rollback();
+ void i_commit();
+ void i_unshare();
+ void i_reference();
+ void i_release();
+
+ ComObjPtr<BandwidthGroup> i_getPeer() { return m->pPeer; }
+ const Utf8Str &i_getName() const { return m->bd->mData.strName; }
+ BandwidthGroupType_T i_getType() const { return m->bd->mData.enmType; }
+ LONG64 i_getMaxBytesPerSec() const { return (LONG64)m->bd->mData.cMaxBytesPerSec; }
+ ULONG i_getReferences() const { return m->bd->cReferences; }
+
+private:
+
+ // wrapped IBandwidthGroup properties
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getType(BandwidthGroupType_T *aType);
+ HRESULT getReference(ULONG *aReferences);
+ HRESULT getMaxBytesPerSec(LONG64 *aMaxBytesPerSec);
+ HRESULT setMaxBytesPerSec(LONG64 MaxBytesPerSec);
+
+ ////////////////////////////////////////////////////////////////////////////////
+ ////
+ //// private member data definition
+ ////
+ //////////////////////////////////////////////////////////////////////////////////
+ //
+ struct BackupableBandwidthGroupData
+ {
+ BackupableBandwidthGroupData()
+ : cReferences(0)
+ { }
+
+ settings::BandwidthGroup mData;
+ ULONG cReferences;
+ };
+
+ struct Data
+ {
+ Data(BandwidthControl * const aBandwidthControl)
+ : pParent(aBandwidthControl),
+ pPeer(NULL)
+ { }
+
+ BandwidthControl * const pParent;
+ ComObjPtr<BandwidthGroup> pPeer;
+
+ // use the XML settings structure in the members for simplicity
+ Backupable<BackupableBandwidthGroupData> bd;
+ };
+
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_BandwidthGroupImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/BusAssignmentManager.h b/src/VBox/Main/include/BusAssignmentManager.h
new file mode 100644
index 00000000..13bc0a3d
--- /dev/null
+++ b/src/VBox/Main/include/BusAssignmentManager.h
@@ -0,0 +1,90 @@
+/* $Id: BusAssignmentManager.h $ */
+/** @file
+ * VirtualBox bus slots assignment manager
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_BusAssignmentManager_h
+#define MAIN_INCLUDED_BusAssignmentManager_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBox/types.h"
+#include "VBox/pci.h"
+#include "VirtualBoxBase.h"
+#include <vector>
+
+class BusAssignmentManager
+{
+private:
+ struct State;
+ State *pState;
+
+ BusAssignmentManager();
+ virtual ~BusAssignmentManager();
+
+ HRESULT assignPCIDeviceImpl(const char *pszDevName, PCFGMNODE pCfg, PCIBusAddress& GuestAddress,
+ PCIBusAddress HostAddress, bool fGuestAddressRequired = false);
+
+public:
+ struct PCIDeviceInfo
+ {
+ com::Utf8Str strDeviceName;
+ PCIBusAddress guestAddress;
+ PCIBusAddress hostAddress;
+ };
+
+ static BusAssignmentManager *createInstance(PCVMMR3VTABLE pVMM, ChipsetType_T chipsetType, IommuType_T iommuType);
+ virtual void AddRef();
+ virtual void Release();
+
+ virtual HRESULT assignHostPCIDevice(const char *pszDevName, PCFGMNODE pCfg, PCIBusAddress HostAddress,
+ PCIBusAddress& GuestAddress, bool fAddressRequired = false)
+ {
+ return assignPCIDeviceImpl(pszDevName, pCfg, GuestAddress, HostAddress, fAddressRequired);
+ }
+
+ virtual HRESULT assignPCIDevice(const char *pszDevName, PCFGMNODE pCfg, PCIBusAddress& Address, bool fAddressRequired = false)
+ {
+ PCIBusAddress HostAddress;
+ return assignPCIDeviceImpl(pszDevName, pCfg, Address, HostAddress, fAddressRequired);
+ }
+
+ virtual HRESULT assignPCIDevice(const char *pszDevName, PCFGMNODE pCfg)
+ {
+ PCIBusAddress GuestAddress;
+ PCIBusAddress HostAddress;
+ return assignPCIDeviceImpl(pszDevName, pCfg, GuestAddress, HostAddress, false);
+ }
+ virtual bool findPCIAddress(const char *pszDevName, int iInstance, PCIBusAddress& Address);
+ virtual bool hasPCIDevice(const char *pszDevName, int iInstance)
+ {
+ PCIBusAddress Address;
+ return findPCIAddress(pszDevName, iInstance, Address);
+ }
+ virtual void listAttachedPCIDevices(std::vector<PCIDeviceInfo> &aAttached);
+};
+
+#endif /* !MAIN_INCLUDED_BusAssignmentManager_h */
diff --git a/src/VBox/Main/include/CPUProfileImpl.h b/src/VBox/Main/include/CPUProfileImpl.h
new file mode 100644
index 00000000..7f647073
--- /dev/null
+++ b/src/VBox/Main/include/CPUProfileImpl.h
@@ -0,0 +1,75 @@
+/* $Id: CPUProfileImpl.h $ */
+/** @file
+ * VirtualBox Main - interface for CPU profiles, VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2020-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_CPUProfileImpl_h
+#define MAIN_INCLUDED_CPUProfileImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "CPUProfileWrap.h"
+
+struct CPUMDBENTRY;
+
+/**
+ * A CPU profile.
+ */
+class ATL_NO_VTABLE CPUProfile
+ : public CPUProfileWrap
+{
+public:
+ /** @name COM and internal init/term/mapping cruft
+ * @{ */
+ DECLARE_COMMON_CLASS_METHODS(CPUProfile)
+ HRESULT FinalConstruct();
+ void FinalRelease();
+ HRESULT initFromDbEntry(struct CPUMDBENTRY const *a_pDbEntry) RT_NOEXCEPT;
+ void uninit();
+ /** @} */
+
+ bool i_match(CPUArchitecture_T a_enmArchitecture, CPUArchitecture_T a_enmSecondaryArch,
+ const com::Utf8Str &a_strNamePattern) const RT_NOEXCEPT;
+
+private:
+ /** @name Wrapped ICPUProfile attributes
+ * @{ */
+ HRESULT getName(com::Utf8Str &aName) RT_OVERRIDE;
+ HRESULT getFullName(com::Utf8Str &aFullName) RT_OVERRIDE;
+ HRESULT getArchitecture(CPUArchitecture_T *aArchitecture) RT_OVERRIDE;
+ /** @} */
+
+ /** @name Data
+ * @{ */
+ com::Utf8Str m_strName;
+ com::Utf8Str m_strFullName;
+ CPUArchitecture_T m_enmArchitecture;
+ /** @} */
+};
+
+#endif /* !MAIN_INCLUDED_CPUProfileImpl_h */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/CertificateImpl.h b/src/VBox/Main/include/CertificateImpl.h
new file mode 100644
index 00000000..d17c5b7a
--- /dev/null
+++ b/src/VBox/Main/include/CertificateImpl.h
@@ -0,0 +1,112 @@
+/* $Id: CertificateImpl.h $ */
+/** @file
+ * VirtualBox COM ICertificate implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_CertificateImpl_h
+#define MAIN_INCLUDED_CertificateImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/* VBox includes */
+#include <iprt/crypto/x509.h>
+#include "CertificateWrap.h"
+
+#include <vector>
+
+using namespace std;
+
+/**
+ * Implemenation of ICertificate.
+ *
+ * This implemenation is a very thin wrapper around an immutable
+ * RTCRX509CERTIFICATE and a few caller stated views.
+ *
+ * The views are whether the caller thinks the certificate is trustworthly, and
+ * whether the caller thinks it's expired or not. The caller could be sitting
+ * on more information, like timestamp and intermediate certificates, that helps
+ * inform the caller's view on these two topics.
+ *
+ * @remarks It could be helpful to let the caller also add certificate paths
+ * showing how this certificate ends up being trusted. However, that's
+ * possibly quite some work and will have to wait till required...
+ */
+class ATL_NO_VTABLE Certificate
+ : public CertificateWrap
+{
+
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(Certificate)
+
+ HRESULT initCertificate(PCRTCRX509CERTIFICATE a_pCert, bool a_fTrusted, bool a_fExpired);
+ void uninit();
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+private:
+ // Wrapped ICertificate properties
+ HRESULT getVersionNumber(CertificateVersion_T *aVersionNumber);
+ HRESULT getSerialNumber(com::Utf8Str &aSerialNumber);
+ HRESULT getSignatureAlgorithmOID(com::Utf8Str &aSignatureAlgorithmOID);
+ HRESULT getSignatureAlgorithmName(com::Utf8Str &aSignatureAlgorithmName);
+ HRESULT getPublicKeyAlgorithmOID(com::Utf8Str &aPublicKeyAlgorithmOID);
+ HRESULT getPublicKeyAlgorithm(com::Utf8Str &aPublicKeyAlgorithm);
+ HRESULT getIssuerName(std::vector<com::Utf8Str> &aIssuerName);
+ HRESULT getSubjectName(std::vector<com::Utf8Str> &aSubjectName);
+ HRESULT getFriendlyName(com::Utf8Str &aFriendlyName);
+ HRESULT getValidityPeriodNotBefore(com::Utf8Str &aValidityPeriodNotBefore);
+ HRESULT getValidityPeriodNotAfter(com::Utf8Str &aValidityPeriodNotAfter);
+ HRESULT getSubjectPublicKey(std::vector<BYTE> &aSubjectPublicKey);
+ HRESULT getIssuerUniqueIdentifier(com::Utf8Str &aIssuerUniqueIdentifier);
+ HRESULT getSubjectUniqueIdentifier(com::Utf8Str &aSubjectUniqueIdentifier);
+ HRESULT getCertificateAuthority(BOOL *aCertificateAuthority);
+ HRESULT getKeyUsage(ULONG *aKeyUsage);
+ HRESULT getExtendedKeyUsage(std::vector<com::Utf8Str> &aExtendedKeyUsage);
+ HRESULT getRawCertData(std::vector<BYTE> &aRawCertData);
+ HRESULT getSelfSigned(BOOL *aSelfSigned);
+ HRESULT getTrusted(BOOL *aTrusted);
+ HRESULT getExpired(BOOL *aExpired);
+
+ // Wrapped ICertificate methods
+ HRESULT isCurrentlyExpired(BOOL *aResult);
+ HRESULT queryInfo(LONG aWhat, com::Utf8Str &aResult);
+
+ // Methods extracting COM data from the certificate object
+ HRESULT i_getAlgorithmName(PCRTCRX509ALGORITHMIDENTIFIER a_pAlgId, com::Utf8Str &a_rReturn);
+ HRESULT i_getX509Name(PCRTCRX509NAME a_pName, std::vector<com::Utf8Str> &a_rReturn);
+ HRESULT i_getTime(PCRTASN1TIME a_pTime, com::Utf8Str &a_rReturn);
+ HRESULT i_getUniqueIdentifier(PCRTCRX509UNIQUEIDENTIFIER a_pUniqueId, com::Utf8Str &a_rReturn);
+ HRESULT i_getEncodedBytes(PRTASN1CORE a_pAsn1Obj, std::vector<BYTE> &a_rReturn);
+
+ struct Data;
+ /** Pointer to the private instance data */
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_CertificateImpl_h */
+
diff --git a/src/VBox/Main/include/ClientToken.h b/src/VBox/Main/include/ClientToken.h
new file mode 100644
index 00000000..85681952
--- /dev/null
+++ b/src/VBox/Main/include/ClientToken.h
@@ -0,0 +1,118 @@
+/* $Id: ClientToken.h $ */
+
+/** @file
+ *
+ * VirtualBox API client session token abstraction
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ClientToken_h
+#define MAIN_INCLUDED_ClientToken_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/com/ptr.h>
+#include <VBox/com/AutoLock.h>
+
+#include "MachineImpl.h"
+#ifdef VBOX_WITH_GENERIC_SESSION_WATCHER
+# include "TokenImpl.h"
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+
+#if defined(RT_OS_WINDOWS)
+# define CTTOKENARG NULL
+# define CTTOKENTYPE HANDLE
+#elif defined(RT_OS_OS2)
+# define CTTOKENARG NULLHANDLE
+# define CTTOKENTYPE HMTX
+#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
+# define CTTOKENARG -1
+# define CTTOKENTYPE int
+#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+# define CTTOKENARG NULL
+# define CTTOKENTYPE MachineToken *
+#else
+# error "Port me!"
+#endif
+
+/**
+ * Class which represents a token which can be used to check for client
+ * crashes and similar purposes.
+ */
+class Machine::ClientToken
+{
+public:
+ /**
+ * Constructor which creates a usable instance
+ *
+ * @param pMachine Reference to Machine object
+ * @param pSessionMachine Reference to corresponding SessionMachine object
+ */
+ ClientToken(const ComObjPtr<Machine> &pMachine, SessionMachine *pSessionMachine);
+
+ /**
+ * Default destructor. Cleans everything up.
+ */
+ ~ClientToken();
+
+ /**
+ * Check if object contains a usable token.
+ */
+ bool isReady();
+
+ /**
+ * Query token ID, which is a unique string value for this token. Do not
+ * assume any specific content/format, it is opaque information.
+ */
+ void getId(Utf8Str &strId);
+
+ /**
+ * Query token, which is platform dependent.
+ */
+ CTTOKENTYPE getToken();
+
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ /**
+ * Release token now. Returns information if the client has terminated.
+ */
+ bool release();
+#endif /* !VBOX_WITH_GENERIC_SESSION_WATCHER */
+
+private:
+ /**
+ * Default constructor. Don't use, will not create a sensible instance.
+ */
+ ClientToken();
+
+ Machine *mMachine;
+ CTTOKENTYPE mClientToken;
+ Utf8Str mClientTokenId;
+#ifdef VBOX_WITH_GENERIC_SESSION_WATCHER
+ bool mClientTokenPassed;
+#endif
+};
+
+#endif /* !MAIN_INCLUDED_ClientToken_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/ClientTokenHolder.h b/src/VBox/Main/include/ClientTokenHolder.h
new file mode 100644
index 00000000..01f0e599
--- /dev/null
+++ b/src/VBox/Main/include/ClientTokenHolder.h
@@ -0,0 +1,112 @@
+/* $Id: ClientTokenHolder.h $ */
+
+/** @file
+ *
+ * VirtualBox API client session token holder (in the client process)
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ClientTokenHolder_h
+#define MAIN_INCLUDED_ClientTokenHolder_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "SessionImpl.h"
+
+#if defined(RT_OS_WINDOWS)
+# define CTHSEMARG NULL
+# define CTHSEMTYPE HANDLE
+/* this second semaphore is only used on Windows */
+# define CTHTHREADSEMARG NULL
+# define CTHTHREADSEMTYPE HANDLE
+#elif defined(RT_OS_OS2)
+# define CTHSEMARG NIL_RTSEMEVENT
+# define CTHSEMTYPE RTSEMEVENT
+#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
+# define CTHSEMARG -1
+# define CTHSEMTYPE int
+#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+/* the token object based implementation needs no semaphores */
+#else
+# error "Port me!"
+#endif
+
+
+/**
+ * Class which holds a client token.
+ */
+class Session::ClientTokenHolder
+{
+public:
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ /**
+ * Constructor which creates a usable instance
+ *
+ * @param strTokenId String with identifier of the token
+ */
+ ClientTokenHolder(const Utf8Str &strTokenId);
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ /**
+ * Constructor which creates a usable instance
+ *
+ * @param aToken Reference to token object
+ */
+ ClientTokenHolder(IToken *aToken);
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+
+ /**
+ * Default destructor. Cleans everything up.
+ */
+ ~ClientTokenHolder();
+
+ /**
+ * Check if object contains a usable token.
+ */
+ bool isReady();
+
+private:
+ /**
+ * Default constructor. Don't use, will not create a sensible instance.
+ */
+ ClientTokenHolder();
+
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ Utf8Str mClientTokenId;
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ ComPtr<IToken> mToken;
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+#ifdef CTHSEMTYPE
+ CTHSEMTYPE mSem;
+#endif
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ RTTHREAD mThread;
+#endif
+#ifdef RT_OS_WINDOWS
+ CTHTHREADSEMTYPE mThreadSem;
+#endif
+};
+
+#endif /* !MAIN_INCLUDED_ClientTokenHolder_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/ClientWatcher.h b/src/VBox/Main/include/ClientWatcher.h
new file mode 100644
index 00000000..84be5f7e
--- /dev/null
+++ b/src/VBox/Main/include/ClientWatcher.h
@@ -0,0 +1,146 @@
+/* $Id: ClientWatcher.h $ */
+/** @file
+ * VirtualBox API client session watcher
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ClientWatcher_h
+#define MAIN_INCLUDED_ClientWatcher_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+#include <list>
+#include <VBox/com/ptr.h>
+#include <VBox/com/AutoLock.h>
+
+#include "VirtualBoxImpl.h"
+
+#if defined(RT_OS_WINDOWS)
+# define CWUPDATEREQARG NULL
+# define CWUPDATEREQTYPE HANDLE
+# define CW_MAX_CLIENTS _16K /**< Max number of clients we can watch (windows). */
+# ifndef DEBUG /* The debug version triggers worker thread code much much earlier. */
+# define CW_MAX_CLIENTS_PER_THREAD 63 /**< Max clients per watcher thread (windows). */
+# else
+# define CW_MAX_CLIENTS_PER_THREAD 3 /**< Max clients per watcher thread (windows). */
+# endif
+# define CW_MAX_HANDLES_PER_THREAD (CW_MAX_CLIENTS_PER_THREAD + 1) /**< Max handles per thread. */
+
+#elif defined(RT_OS_OS2)
+# define CWUPDATEREQARG NIL_RTSEMEVENT
+# define CWUPDATEREQTYPE RTSEMEVENT
+
+#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) || defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+# define CWUPDATEREQARG NIL_RTSEMEVENT
+# define CWUPDATEREQTYPE RTSEMEVENT
+
+#else
+# error "Port me!"
+#endif
+
+/**
+ * Class which checks for API clients which have crashed/exited, and takes
+ * the necessary cleanup actions. Singleton.
+ */
+class VirtualBox::ClientWatcher
+{
+public:
+ /**
+ * Constructor which creates a usable instance
+ *
+ * @param pVirtualBox Reference to VirtualBox object
+ */
+ ClientWatcher(const ComObjPtr<VirtualBox> &pVirtualBox);
+
+ /**
+ * Default destructor. Cleans everything up.
+ */
+ ~ClientWatcher();
+
+ bool isReady();
+
+ void update();
+ void addProcess(RTPROCESS pid);
+
+private:
+ /**
+ * Default constructor. Don't use, will not create a sensible instance.
+ */
+ ClientWatcher();
+
+ static DECLCALLBACK(int) worker(RTTHREAD hThreadSelf, void *pvUser);
+ uint32_t reapProcesses(void);
+
+ VirtualBox *mVirtualBox;
+ RTTHREAD mThread;
+ CWUPDATEREQTYPE mUpdateReq;
+ util::RWLockHandle mLock;
+
+ typedef std::list<RTPROCESS> ProcessList;
+ ProcessList mProcesses;
+
+#if defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) || defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+ uint8_t mUpdateAdaptCtr;
+#endif
+#ifdef RT_OS_WINDOWS
+ /** Indicate a real update request is pending.
+ * To avoid race conditions this must be set before mUpdateReq is signalled and
+ * read after resetting mUpdateReq. */
+ volatile bool mfUpdateReq;
+ /** Set when the worker threads are supposed to shut down. */
+ volatile bool mfTerminate;
+ /** Number of active subworkers.
+ * When decremented to 0, subworker zero is signalled. */
+ uint32_t volatile mcActiveSubworkers;
+ /** Number of valid handles in mahWaitHandles. */
+ uint32_t mcWaitHandles;
+ /** The wait interval (usually INFINITE). */
+ uint32_t mcMsWait;
+ /** Per subworker data. Subworker 0 is the main worker and does not have a
+ * pReq pointer since. */
+ struct PerSubworker
+ {
+ /** The wait result. */
+ DWORD dwWait;
+ /** The subworker index. */
+ uint32_t iSubworker;
+ /** The subworker thread handle. */
+ RTTHREAD hThread;
+ /** Self pointer (for worker thread). */
+ VirtualBox::ClientWatcher *pSelf;
+ } maSubworkers[(CW_MAX_CLIENTS + CW_MAX_CLIENTS_PER_THREAD - 1) / CW_MAX_CLIENTS_PER_THREAD];
+ /** Wait handle array. The mUpdateReq manual reset event handle is inserted
+ * every 64 entries, first entry being 0. */
+ HANDLE mahWaitHandles[CW_MAX_CLIENTS + (CW_MAX_CLIENTS + CW_MAX_CLIENTS_PER_THREAD - 1) / CW_MAX_CLIENTS_PER_THREAD];
+
+ void subworkerWait(VirtualBox::ClientWatcher::PerSubworker *pSubworker, uint32_t cMsWait);
+ static DECLCALLBACK(int) subworkerThread(RTTHREAD hThreadSelf, void *pvUser);
+ void winResetHandleArray(uint32_t cProcHandles);
+#endif
+};
+
+#endif /* !MAIN_INCLUDED_ClientWatcher_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/CloudGateway.h b/src/VBox/Main/include/CloudGateway.h
new file mode 100644
index 00000000..9467a9f6
--- /dev/null
+++ b/src/VBox/Main/include/CloudGateway.h
@@ -0,0 +1,103 @@
+/* $Id: CloudGateway.h $ */
+/** @file
+ * Implementation of local and cloud gateway management.
+ */
+
+/*
+ * Copyright (C) 2019-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_CloudGateway_h
+#define MAIN_INCLUDED_CloudGateway_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+struct GatewayInfo
+{
+ Bstr mTargetVM;
+ Utf8Str mGatewayInstanceId;
+ Utf8Str mPublicSshKey;
+ Utf8Str mPrivateSshKey;
+ Bstr mCloudProvider;
+ Bstr mCloudProfile;
+ Utf8Str mCloudPublicIp;
+ Utf8Str mCloudSecondaryPublicIp;
+ RTMAC mCloudMacAddress;
+ RTMAC mLocalMacAddress;
+ int mAdapterSlot;
+
+ HRESULT setCloudMacAddress(const Utf8Str& mac);
+ HRESULT setLocalMacAddress(const Utf8Str& mac);
+
+ GatewayInfo() {}
+
+ GatewayInfo(const GatewayInfo& other)
+ : mGatewayInstanceId(other.mGatewayInstanceId),
+ mPublicSshKey(other.mPublicSshKey),
+ mPrivateSshKey(other.mPrivateSshKey),
+ mCloudProvider(other.mCloudProvider),
+ mCloudProfile(other.mCloudProfile),
+ mCloudPublicIp(other.mCloudPublicIp),
+ mCloudSecondaryPublicIp(other.mCloudSecondaryPublicIp),
+ mCloudMacAddress(other.mCloudMacAddress),
+ mLocalMacAddress(other.mLocalMacAddress),
+ mAdapterSlot(other.mAdapterSlot)
+ {}
+
+ GatewayInfo& operator=(const GatewayInfo& other)
+ {
+ mGatewayInstanceId = other.mGatewayInstanceId;
+ mPublicSshKey = other.mPublicSshKey;
+ mPrivateSshKey = other.mPrivateSshKey;
+ mCloudProvider = other.mCloudProvider;
+ mCloudProfile = other.mCloudProfile;
+ mCloudPublicIp = other.mCloudPublicIp;
+ mCloudSecondaryPublicIp = other.mCloudSecondaryPublicIp;
+ mCloudMacAddress = other.mCloudMacAddress;
+ mLocalMacAddress = other.mLocalMacAddress;
+ mAdapterSlot = other.mAdapterSlot;
+ return *this;
+ }
+
+ void setNull()
+ {
+ mGatewayInstanceId.setNull();
+ mPublicSshKey.setNull();
+ mPrivateSshKey.setNull();
+ mCloudProvider.setNull();
+ mCloudProfile.setNull();
+ mCloudPublicIp.setNull();
+ mCloudSecondaryPublicIp.setNull();
+ memset(&mCloudMacAddress, 0, sizeof(mCloudMacAddress));
+ memset(&mLocalMacAddress, 0, sizeof(mLocalMacAddress));
+ mAdapterSlot = -1;
+ }
+};
+
+class CloudNetwork;
+
+HRESULT startCloudGateway(ComPtr<IVirtualBox> virtualBox, ComPtr<ICloudNetwork> network, GatewayInfo& pGateways);
+HRESULT stopCloudGateway(ComPtr<IVirtualBox> virtualBox, GatewayInfo& gateways);
+HRESULT generateKeys(GatewayInfo& gateways);
+
+#endif /* !MAIN_INCLUDED_CloudGateway_h */
+
diff --git a/src/VBox/Main/include/CloudNetworkImpl.h b/src/VBox/Main/include/CloudNetworkImpl.h
new file mode 100644
index 00000000..ca22f6cf
--- /dev/null
+++ b/src/VBox/Main/include/CloudNetworkImpl.h
@@ -0,0 +1,79 @@
+/* $Id: CloudNetworkImpl.h $ */
+/** @file
+ * ICloudNetwork implementation header, lives in VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2019-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_CloudNetworkImpl_h
+#define MAIN_INCLUDED_CloudNetworkImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "CloudNetworkWrap.h"
+
+namespace settings
+{
+ struct CloudNetwork;
+}
+
+class ATL_NO_VTABLE CloudNetwork :
+ public CloudNetworkWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(CloudNetwork)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ HRESULT init(VirtualBox *aVirtualBox, com::Utf8Str aName);
+ HRESULT i_loadSettings(const settings::CloudNetwork &data);
+ void uninit();
+ HRESULT i_saveSettings(settings::CloudNetwork &data);
+
+ // Internal methods
+ Utf8Str i_getNetworkName();
+ Utf8Str i_getProvider();
+ Utf8Str i_getProfile();
+ Utf8Str i_getNetworkId();
+private:
+
+ // Wrapped ICloudNetwork properties
+ HRESULT getNetworkName(com::Utf8Str &aNetworkName);
+ HRESULT setNetworkName(const com::Utf8Str &aNetworkName);
+ HRESULT getEnabled(BOOL *aEnabled);
+ HRESULT setEnabled(BOOL aEnabled);
+ HRESULT getProvider(com::Utf8Str &aProvider);
+ HRESULT setProvider(const com::Utf8Str &aProvider);
+ HRESULT getProfile(com::Utf8Str &aProfile);
+ HRESULT setProfile(const com::Utf8Str &aProfile);
+ HRESULT getNetworkId(com::Utf8Str &aNetworkId);
+ HRESULT setNetworkId(const com::Utf8Str &aNetworkId);
+
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_CloudNetworkImpl_h */
diff --git a/src/VBox/Main/include/CloudProviderManagerImpl.h b/src/VBox/Main/include/CloudProviderManagerImpl.h
new file mode 100644
index 00000000..43317c1c
--- /dev/null
+++ b/src/VBox/Main/include/CloudProviderManagerImpl.h
@@ -0,0 +1,82 @@
+/* $Id: CloudProviderManagerImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2018-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_CloudProviderManagerImpl_h
+#define MAIN_INCLUDED_CloudProviderManagerImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "CloudProviderManagerWrap.h"
+
+class ATL_NO_VTABLE CloudProviderManager
+ : public CloudProviderManagerWrap
+{
+public:
+ CloudProviderManager();
+ virtual ~CloudProviderManager();
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ HRESULT init(VirtualBox *aVirtualBox);
+ void uninit();
+
+#ifdef VBOX_WITH_EXTPACK
+ // Safe helpers, take care of caller and lock themselves.
+ bool i_canRemoveExtPack(IExtPack *aExtPack);
+ void i_addExtPack(IExtPack *aExtPack);
+#endif
+
+private:
+ // wrapped ICloudProviderManager attributes and methods
+ HRESULT getProviders(std::vector<ComPtr<ICloudProvider> > &aProviders);
+ HRESULT getProviderById(const com::Guid &aProviderId,
+ ComPtr<ICloudProvider> &aProvider);
+ HRESULT getProviderByShortName(const com::Utf8Str &aProviderName,
+ ComPtr<ICloudProvider> &aProvider);
+ HRESULT getProviderByName(const com::Utf8Str &aProviderName,
+ ComPtr<ICloudProvider> &aProvider);
+
+private:
+#ifdef VBOX_WITH_EXTPACK
+ typedef std::map<com::Utf8Str, ComPtr<ICloudProviderManager> > ExtPackNameCloudProviderManagerMap;
+ ExtPackNameCloudProviderManagerMap m_mapCloudProviderManagers;
+
+ typedef std::vector<com::Utf8Str> ExtPackNameVec;
+ ExtPackNameVec m_astrExtPackNames;
+#endif
+
+ typedef std::vector<ComPtr<ICloudProvider> > CloudProviderVec;
+ CloudProviderVec m_apCloudProviders;
+
+ VirtualBox * const m_pVirtualBox;
+};
+
+#endif /* !MAIN_INCLUDED_CloudProviderManagerImpl_h */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/ConsoleImpl.h b/src/VBox/Main/include/ConsoleImpl.h
new file mode 100644
index 00000000..9fb856d5
--- /dev/null
+++ b/src/VBox/Main/include/ConsoleImpl.h
@@ -0,0 +1,1192 @@
+/* $Id: ConsoleImpl.h $ */
+/** @file
+ * VBox Console COM Class definition
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ConsoleImpl_h
+#define MAIN_INCLUDED_ConsoleImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VirtualBoxBase.h"
+#include "VBox/com/array.h"
+#include "EventImpl.h"
+#include "SecretKeyStore.h"
+#include "ConsoleWrap.h"
+#ifdef VBOX_WITH_RECORDING
+# include "Recording.h"
+#endif
+#ifdef VBOX_WITH_CLOUD_NET
+#include "CloudGateway.h"
+#endif /* VBOX_WITH_CLOUD_NET */
+
+class Guest;
+class Keyboard;
+class Mouse;
+class Display;
+class MachineDebugger;
+class TeleporterStateSrc;
+class OUSBDevice;
+class RemoteUSBDevice;
+class SharedFolder;
+class VRDEServerInfo;
+class EmulatedUSB;
+class AudioVRDE;
+#ifdef VBOX_WITH_AUDIO_RECORDING
+class AudioVideoRec;
+#endif
+#ifdef VBOX_WITH_USB_CARDREADER
+class UsbCardReader;
+#endif
+class ConsoleVRDPServer;
+class VMMDev;
+class Progress;
+class BusAssignmentManager;
+COM_STRUCT_OR_CLASS(IEventListener);
+#ifdef VBOX_WITH_EXTPACK
+class ExtPackManager;
+#endif
+class VMMDevMouseInterface;
+class DisplayMouseInterface;
+class VMPowerUpTask;
+class VMPowerDownTask;
+class NvramStore;
+
+#include <iprt/uuid.h>
+#include <iprt/log.h>
+#include <iprt/memsafer.h>
+#include <VBox/RemoteDesktop/VRDE.h>
+#include <VBox/vmm/pdmdrv.h>
+#ifdef VBOX_WITH_GUEST_PROPS
+# include <VBox/HostServices/GuestPropertySvc.h> /* For the property notification callback */
+#endif
+#ifdef VBOX_WITH_USB
+# include <VBox/vrdpusb.h>
+#endif
+#include <VBox/VBoxCryptoIf.h>
+
+#if defined(VBOX_WITH_GUEST_PROPS) || defined(VBOX_WITH_SHARED_CLIPBOARD) \
+ || defined(VBOX_WITH_DRAG_AND_DROP)
+# include "HGCM.h" /** @todo It should be possible to register a service
+ * extension using a VMMDev callback. */
+#endif
+
+struct VUSBIRHCONFIG;
+typedef struct VUSBIRHCONFIG *PVUSBIRHCONFIG;
+
+#include <list>
+#include <vector>
+
+// defines
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Checks the availability of the underlying VM device driver corresponding
+ * to the COM interface (IKeyboard, IMouse, IDisplay, etc.). When the driver is
+ * not available (NULL), sets error info and returns returns E_ACCESSDENIED.
+ * The translatable error message is defined in null context.
+ *
+ * Intended to used only within Console children (i.e. Keyboard, Mouse,
+ * Display, etc.).
+ *
+ * @param drv driver pointer to check (compare it with NULL)
+ */
+#define CHECK_CONSOLE_DRV(drv) \
+ do { \
+ if (!!(drv)) {} \
+ else return setError(E_ACCESSDENIED, Console::tr("The console is not powered up (%Rfn)"), __FUNCTION__); \
+ } while (0)
+
+// Console
+///////////////////////////////////////////////////////////////////////////////
+
+class ConsoleMouseInterface
+{
+public:
+ virtual ~ConsoleMouseInterface() { }
+ virtual VMMDevMouseInterface *i_getVMMDevMouseInterface(){return NULL;}
+ virtual DisplayMouseInterface *i_getDisplayMouseInterface(){return NULL;}
+ virtual void i_onMouseCapabilityChange(BOOL supportsAbsolute,
+ BOOL supportsRelative,
+ BOOL supportsTouchScreen,
+ BOOL supportsTouchPad,
+ BOOL needsHostCursor)
+ {
+ RT_NOREF(supportsAbsolute, supportsRelative, supportsTouchScreen, supportsTouchPad, needsHostCursor);
+ }
+};
+
+/** IConsole implementation class */
+class ATL_NO_VTABLE Console :
+ public ConsoleWrap,
+ public ConsoleMouseInterface
+{
+
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(Console)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializers/uninitializers for internal purposes only
+ HRESULT initWithMachine(IMachine *aMachine, IInternalMachineControl *aControl, LockType_T aLockType);
+ void uninit();
+
+
+ // public methods for internal purposes only
+
+ /*
+ * Note: the following methods do not increase refcount. intended to be
+ * called only by the VM execution thread.
+ */
+
+ PCVMMR3VTABLE i_getVMMVTable() const RT_NOEXCEPT { return mpVMM; }
+ Guest *i_getGuest() const { return mGuest; }
+ Keyboard *i_getKeyboard() const { return mKeyboard; }
+ Mouse *i_getMouse() const { return mMouse; }
+ Display *i_getDisplay() const { return mDisplay; }
+ MachineDebugger *i_getMachineDebugger() const { return mDebugger; }
+#ifdef VBOX_WITH_AUDIO_VRDE
+ AudioVRDE *i_getAudioVRDE() const { return mAudioVRDE; }
+#endif
+#ifdef VBOX_WITH_RECORDING
+ int i_recordingCreate(void);
+ void i_recordingDestroy(void);
+ int i_recordingEnable(BOOL fEnable, util::AutoWriteLock *pAutoLock);
+ int i_recordingGetSettings(settings::RecordingSettings &recording);
+ int i_recordingStart(util::AutoWriteLock *pAutoLock = NULL);
+ int i_recordingStop(util::AutoWriteLock *pAutoLock = NULL);
+# ifdef VBOX_WITH_AUDIO_RECORDING
+ AudioVideoRec *i_recordingGetAudioDrv(void) const { return mRecording.mAudioRec; }
+# endif
+ RecordingContext *i_recordingGetContext(void) { return &mRecording.mCtx; }
+# ifdef VBOX_WITH_AUDIO_RECORDING
+ HRESULT i_recordingSendAudio(const void *pvData, size_t cbData, uint64_t uDurationMs);
+# endif
+#endif
+
+ const ComPtr<IMachine> &i_machine() const { return mMachine; }
+ const Bstr &i_getId() const { return mstrUuid; }
+
+ bool i_useHostClipboard() { return mfUseHostClipboard; }
+
+ /** Method is called only from ConsoleVRDPServer */
+ IVRDEServer *i_getVRDEServer() const { return mVRDEServer; }
+
+ ConsoleVRDPServer *i_consoleVRDPServer() const { return mConsoleVRDPServer; }
+
+ HRESULT i_updateMachineState(MachineState_T aMachineState);
+ HRESULT i_getNominalState(MachineState_T &aNominalState);
+ Utf8Str i_getAudioAdapterDeviceName(IAudioAdapter *aAudioAdapter);
+
+ // events from IInternalSessionControl
+ HRESULT i_onNetworkAdapterChange(INetworkAdapter *aNetworkAdapter, BOOL changeAdapter);
+ HRESULT i_onAudioAdapterChange(IAudioAdapter *aAudioAdapter);
+ HRESULT i_onHostAudioDeviceChange(IHostAudioDevice *aDevice, BOOL aNew, AudioDeviceState_T aState,
+ IVirtualBoxErrorInfo *aErrInfo);
+ HRESULT i_onSerialPortChange(ISerialPort *aSerialPort);
+ HRESULT i_onParallelPortChange(IParallelPort *aParallelPort);
+ HRESULT i_onStorageControllerChange(const com::Guid& aMachineId, const com::Utf8Str& aControllerName);
+ HRESULT i_onMediumChange(IMediumAttachment *aMediumAttachment, BOOL aForce);
+ HRESULT i_onCPUChange(ULONG aCPU, BOOL aRemove);
+ HRESULT i_onCPUExecutionCapChange(ULONG aExecutionCap);
+ HRESULT i_onClipboardModeChange(ClipboardMode_T aClipboardMode);
+ HRESULT i_onClipboardFileTransferModeChange(bool aEnabled);
+ HRESULT i_onDnDModeChange(DnDMode_T aDnDMode);
+ HRESULT i_onVRDEServerChange(BOOL aRestart);
+ HRESULT i_onRecordingChange(BOOL fEnable);
+ HRESULT i_onUSBControllerChange();
+ HRESULT i_onSharedFolderChange(BOOL aGlobal);
+ HRESULT i_onUSBDeviceAttach(IUSBDevice *aDevice, IVirtualBoxErrorInfo *aError, ULONG aMaskedIfs,
+ const Utf8Str &aCaptureFilename);
+ HRESULT i_onUSBDeviceDetach(IN_BSTR aId, IVirtualBoxErrorInfo *aError);
+ HRESULT i_onBandwidthGroupChange(IBandwidthGroup *aBandwidthGroup);
+ HRESULT i_onStorageDeviceChange(IMediumAttachment *aMediumAttachment, BOOL aRemove, BOOL aSilent);
+ HRESULT i_onExtraDataChange(const Bstr &aMachineId, const Bstr &aKey, const Bstr &aVal);
+ HRESULT i_onGuestDebugControlChange(IGuestDebugControl *aGuestDebugControl);
+
+ HRESULT i_getGuestProperty(const Utf8Str &aName, Utf8Str *aValue, LONG64 *aTimestamp, Utf8Str *aFlags);
+ HRESULT i_setGuestProperty(const Utf8Str &aName, const Utf8Str &aValue, const Utf8Str &aFlags);
+ HRESULT i_deleteGuestProperty(const Utf8Str &aName);
+ HRESULT i_enumerateGuestProperties(const Utf8Str &aPatterns,
+ std::vector<Utf8Str> &aNames,
+ std::vector<Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<Utf8Str> &aFlags);
+ HRESULT i_onlineMergeMedium(IMediumAttachment *aMediumAttachment,
+ ULONG aSourceIdx, ULONG aTargetIdx,
+ IProgress *aProgress);
+ HRESULT i_reconfigureMediumAttachments(const std::vector<ComPtr<IMediumAttachment> > &aAttachments);
+ HRESULT i_onVMProcessPriorityChange(VMProcPriority_T priority);
+ int i_hgcmLoadService(const char *pszServiceLibrary, const char *pszServiceName);
+ VMMDev *i_getVMMDev() { return m_pVMMDev; }
+
+#ifdef VBOX_WITH_EXTPACK
+ ExtPackManager *i_getExtPackManager();
+#endif
+ EventSource *i_getEventSource() { return mEventSource; }
+#ifdef VBOX_WITH_USB_CARDREADER
+ UsbCardReader *i_getUsbCardReader() { return mUsbCardReader; }
+#endif
+
+ int i_VRDPClientLogon(uint32_t u32ClientId, const char *pszUser, const char *pszPassword, const char *pszDomain);
+ void i_VRDPClientStatusChange(uint32_t u32ClientId, const char *pszStatus);
+ void i_VRDPClientConnect(uint32_t u32ClientId);
+ void i_VRDPClientDisconnect(uint32_t u32ClientId, uint32_t fu32Intercepted);
+ void i_VRDPInterceptAudio(uint32_t u32ClientId);
+ void i_VRDPInterceptUSB(uint32_t u32ClientId, void **ppvIntercept);
+ void i_VRDPInterceptClipboard(uint32_t u32ClientId);
+
+ void i_processRemoteUSBDevices(uint32_t u32ClientId, VRDEUSBDEVICEDESC *pDevList, uint32_t cbDevList, bool fDescExt);
+ void i_reportVmStatistics(ULONG aValidStats, ULONG aCpuUser,
+ ULONG aCpuKernel, ULONG aCpuIdle,
+ ULONG aMemTotal, ULONG aMemFree,
+ ULONG aMemBalloon, ULONG aMemShared,
+ ULONG aMemCache, ULONG aPageTotal,
+ ULONG aAllocVMM, ULONG aFreeVMM,
+ ULONG aBalloonedVMM, ULONG aSharedVMM,
+ ULONG aVmNetRx, ULONG aVmNetTx)
+ {
+ mControl->ReportVmStatistics(aValidStats, aCpuUser, aCpuKernel, aCpuIdle,
+ aMemTotal, aMemFree, aMemBalloon, aMemShared,
+ aMemCache, aPageTotal, aAllocVMM, aFreeVMM,
+ aBalloonedVMM, aSharedVMM, aVmNetRx, aVmNetTx);
+ }
+ void i_enableVMMStatistics(BOOL aEnable);
+
+ HRESULT i_pause(Reason_T aReason);
+ HRESULT i_resume(Reason_T aReason, AutoWriteLock &alock);
+ HRESULT i_saveState(Reason_T aReason, const ComPtr<IProgress> &aProgress,
+ const ComPtr<ISnapshot> &aSnapshot,
+ const Utf8Str &aStateFilePath, bool fPauseVM, bool &fLeftPaused);
+ HRESULT i_cancelSaveState();
+
+ // callback callers (partly; for some events console callbacks are notified
+ // directly from IInternalSessionControl event handlers declared above)
+ void i_onMousePointerShapeChange(bool fVisible, bool fAlpha,
+ uint32_t xHot, uint32_t yHot,
+ uint32_t width, uint32_t height,
+ const uint8_t *pu8Shape,
+ uint32_t cbShape);
+ void i_onMouseCapabilityChange(BOOL supportsAbsolute, BOOL supportsRelative,
+ BOOL supportsTouchScreen, BOOL supportsTouchPad,
+ BOOL needsHostCursor);
+ void i_onStateChange(MachineState_T aMachineState);
+ void i_onAdditionsStateChange();
+ void i_onAdditionsOutdated();
+ void i_onKeyboardLedsChange(bool fNumLock, bool fCapsLock, bool fScrollLock);
+ void i_onUSBDeviceStateChange(IUSBDevice *aDevice, bool aAttached,
+ IVirtualBoxErrorInfo *aError);
+ void i_onRuntimeError(BOOL aFatal, IN_BSTR aErrorID, IN_BSTR aMessage);
+ HRESULT i_onShowWindow(BOOL aCheck, BOOL *aCanShow, LONG64 *aWinId);
+ void i_onVRDEServerInfoChange();
+ HRESULT i_sendACPIMonitorHotPlugEvent();
+
+ static const PDMDRVREG DrvStatusReg;
+
+ static HRESULT i_setErrorStatic(HRESULT aResultCode, const char *pcsz, ...);
+ static HRESULT i_setErrorStaticBoth(HRESULT aResultCode, int vrc, const char *pcsz, ...);
+ HRESULT i_setInvalidMachineStateError();
+
+ static const char *i_storageControllerTypeToStr(StorageControllerType_T enmCtrlType);
+ static HRESULT i_storageBusPortDeviceToLun(StorageBus_T enmBus, LONG port, LONG device, unsigned &uLun);
+ // Called from event listener
+ HRESULT i_onNATRedirectRuleChanged(ULONG ulInstance, BOOL aNatRuleRemove,
+ NATProtocol_T aProto, IN_BSTR aHostIp, LONG aHostPort, IN_BSTR aGuestIp, LONG aGuestPort);
+ HRESULT i_onNATDnsChanged();
+
+ // Mouse interface
+ VMMDevMouseInterface *i_getVMMDevMouseInterface();
+ DisplayMouseInterface *i_getDisplayMouseInterface();
+
+ EmulatedUSB *i_getEmulatedUSB(void) { return mEmulatedUSB; }
+
+ /**
+ * Sets the disk encryption keys.
+ *
+ * @returns COM status code.
+ * @param strCfg The config for the disks.
+ *
+ * @note One line in the config string contains all required data for one disk.
+ * The format for one disk is some sort of comma separated value using
+ * key=value pairs.
+ * There are two keys defined at the moment:
+ * - uuid: The uuid of the base image the key is for (with or without)
+ * the curly braces.
+ * - dek: The data encryption key in base64 encoding
+ */
+ HRESULT i_setDiskEncryptionKeys(const Utf8Str &strCfg);
+
+ int i_retainCryptoIf(PCVBOXCRYPTOIF *ppCryptoIf);
+ int i_releaseCryptoIf(PCVBOXCRYPTOIF pCryptoIf);
+ HRESULT i_unloadCryptoIfModule(void);
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ // VMMDev needs:
+ HRESULT i_pullGuestProperties(ComSafeArrayOut(BSTR, names), ComSafeArrayOut(BSTR, values),
+ ComSafeArrayOut(LONG64, timestamps), ComSafeArrayOut(BSTR, flags));
+ static DECLCALLBACK(int) i_doGuestPropNotification(void *pvExtension, uint32_t, void *pvParms, uint32_t cbParms);
+#endif
+
+private:
+
+ // wrapped IConsole properties
+ HRESULT getMachine(ComPtr<IMachine> &aMachine);
+ HRESULT getState(MachineState_T *aState);
+ HRESULT getGuest(ComPtr<IGuest> &aGuest);
+ HRESULT getKeyboard(ComPtr<IKeyboard> &aKeyboard);
+ HRESULT getMouse(ComPtr<IMouse> &aMouse);
+ HRESULT getDisplay(ComPtr<IDisplay> &aDisplay);
+ HRESULT getDebugger(ComPtr<IMachineDebugger> &aDebugger);
+ HRESULT getUSBDevices(std::vector<ComPtr<IUSBDevice> > &aUSBDevices);
+ HRESULT getRemoteUSBDevices(std::vector<ComPtr<IHostUSBDevice> > &aRemoteUSBDevices);
+ HRESULT getSharedFolders(std::vector<ComPtr<ISharedFolder> > &aSharedFolders);
+ HRESULT getVRDEServerInfo(ComPtr<IVRDEServerInfo> &aVRDEServerInfo);
+ HRESULT getEventSource(ComPtr<IEventSource> &aEventSource);
+ HRESULT getAttachedPCIDevices(std::vector<ComPtr<IPCIDeviceAttachment> > &aAttachedPCIDevices);
+ HRESULT getUseHostClipboard(BOOL *aUseHostClipboard);
+ HRESULT setUseHostClipboard(BOOL aUseHostClipboard);
+ HRESULT getEmulatedUSB(ComPtr<IEmulatedUSB> &aEmulatedUSB);
+
+ // wrapped IConsole methods
+ HRESULT powerUp(ComPtr<IProgress> &aProgress);
+ HRESULT powerUpPaused(ComPtr<IProgress> &aProgress);
+ HRESULT powerDown(ComPtr<IProgress> &aProgress);
+ HRESULT reset();
+ HRESULT pause();
+ HRESULT resume();
+ HRESULT powerButton();
+ HRESULT sleepButton();
+ HRESULT getPowerButtonHandled(BOOL *aHandled);
+ HRESULT getGuestEnteredACPIMode(BOOL *aEntered);
+ HRESULT getDeviceActivity(const std::vector<DeviceType_T> &aType,
+ std::vector<DeviceActivity_T> &aActivity);
+ HRESULT attachUSBDevice(const com::Guid &aId, const com::Utf8Str &aCaptureFilename);
+ HRESULT detachUSBDevice(const com::Guid &aId,
+ ComPtr<IUSBDevice> &aDevice);
+ HRESULT findUSBDeviceByAddress(const com::Utf8Str &aName,
+ ComPtr<IUSBDevice> &aDevice);
+ HRESULT findUSBDeviceById(const com::Guid &aId,
+ ComPtr<IUSBDevice> &aDevice);
+ HRESULT createSharedFolder(const com::Utf8Str &aName,
+ const com::Utf8Str &aHostPath,
+ BOOL aWritable,
+ BOOL aAutomount,
+ const com::Utf8Str &aAutoMountPoint);
+ HRESULT removeSharedFolder(const com::Utf8Str &aName);
+ HRESULT teleport(const com::Utf8Str &aHostname,
+ ULONG aTcpport,
+ const com::Utf8Str &aPassword,
+ ULONG aMaxDowntime,
+ ComPtr<IProgress> &aProgress);
+ HRESULT addEncryptionPassword(const com::Utf8Str &aId, const com::Utf8Str &aPassword,
+ BOOL aClearOnSuspend);
+ HRESULT addEncryptionPasswords(const std::vector<com::Utf8Str> &aIds, const std::vector<com::Utf8Str> &aPasswords,
+ BOOL aClearOnSuspend);
+ HRESULT removeEncryptionPassword(const com::Utf8Str &aId);
+ HRESULT clearAllEncryptionPasswords();
+
+ void notifyNatDnsChange(PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDevice, ULONG ulInstanceMax);
+ Utf8Str VRDPServerErrorToMsg(int vrc);
+
+ /**
+ * Base template for AutoVMCaller and SafeVMPtr. Template arguments
+ * have the same meaning as arguments of Console::addVMCaller().
+ */
+ template <bool taQuiet = false, bool taAllowNullVM = false>
+ class AutoVMCallerBase
+ {
+ public:
+ AutoVMCallerBase(Console *aThat) : mThat(aThat), mRC(E_FAIL)
+ {
+ Assert(aThat);
+ mRC = aThat->i_addVMCaller(taQuiet, taAllowNullVM);
+ }
+ ~AutoVMCallerBase()
+ {
+ doRelease();
+ }
+ /** Decreases the number of callers before the instance is destroyed. */
+ void releaseCaller()
+ {
+ Assert(SUCCEEDED(mRC));
+ doRelease();
+ }
+ /** Restores the number of callers after by #release(). #rc() must be
+ * rechecked to ensure the operation succeeded. */
+ void addYY()
+ {
+ AssertReturnVoid(!SUCCEEDED(mRC));
+ mRC = mThat->i_addVMCaller(taQuiet, taAllowNullVM);
+ }
+ /** Returns the result of Console::addVMCaller() */
+ HRESULT rc() const { return mRC; }
+ /** Shortcut to SUCCEEDED(rc()) */
+ bool isOk() const { return SUCCEEDED(mRC); }
+ protected:
+ Console *mThat;
+ void doRelease()
+ {
+ if (SUCCEEDED(mRC))
+ {
+ mThat->i_releaseVMCaller();
+ mRC = E_FAIL;
+ }
+ }
+ private:
+ HRESULT mRC; /* Whether the caller was added. */
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(AutoVMCallerBase);
+ };
+
+#if 0
+ /**
+ * Helper class that protects sections of code using the mpUVM pointer by
+ * automatically calling addVMCaller() on construction and
+ * releaseVMCaller() on destruction. Intended for Console methods dealing
+ * with mpUVM. The usage pattern is:
+ * <code>
+ * AutoVMCaller autoVMCaller(this);
+ * if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
+ * ...
+ * VMR3ReqCall (mpUVM, ...
+ * </code>
+ *
+ * @note Temporarily locks the argument for writing.
+ *
+ * @sa SafeVMPtr, SafeVMPtrQuiet
+ * @note Obsolete, use SafeVMPtr
+ */
+ typedef AutoVMCallerBase<false, false> AutoVMCaller;
+#endif
+
+ /**
+ * Same as AutoVMCaller but doesn't set extended error info on failure.
+ *
+ * @note Temporarily locks the argument for writing.
+ * @note Obsolete, use SafeVMPtrQuiet
+ */
+ typedef AutoVMCallerBase<true, false> AutoVMCallerQuiet;
+
+ /**
+ * Same as AutoVMCaller but allows a null VM pointer (to trigger an error
+ * instead of assertion).
+ *
+ * @note Temporarily locks the argument for writing.
+ * @note Obsolete, use SafeVMPtr
+ */
+ typedef AutoVMCallerBase<false, true> AutoVMCallerWeak;
+
+ /**
+ * Same as AutoVMCaller but doesn't set extended error info on failure
+ * and allows a null VM pointer (to trigger an error instead of
+ * assertion).
+ *
+ * @note Temporarily locks the argument for writing.
+ * @note Obsolete, use SafeVMPtrQuiet
+ */
+ typedef AutoVMCallerBase<true, true> AutoVMCallerQuietWeak;
+
+ /**
+ * Base template for SafeVMPtr and SafeVMPtrQuiet.
+ */
+ template<bool taQuiet = false>
+ class SafeVMPtrBase : public AutoVMCallerBase<taQuiet, true>
+ {
+ typedef AutoVMCallerBase<taQuiet, true> Base;
+ public:
+ SafeVMPtrBase(Console *aThat) : Base(aThat), mRC(E_FAIL), mpUVM(NULL), mpVMM(NULL)
+ {
+ if (Base::isOk())
+ mRC = aThat->i_safeVMPtrRetainer(&mpUVM, &mpVMM, taQuiet);
+ }
+ ~SafeVMPtrBase()
+ {
+ doRelease();
+ }
+ /** Direct PUVM access. */
+ PUVM rawUVM() const { return mpUVM; }
+ /** Direct PCVMMR3VTABLE access. */
+ PCVMMR3VTABLE vtable() const { return mpVMM; }
+ /** Release the handles. */
+ void release()
+ {
+ Assert(SUCCEEDED(mRC));
+ doRelease();
+ }
+
+ /** The combined result of Console::addVMCaller() and Console::safeVMPtrRetainer */
+ HRESULT rc() const { return Base::isOk()? mRC: Base::rc(); }
+ /** Shortcut to SUCCEEDED(rc()) */
+ bool isOk() const { return SUCCEEDED(mRC) && Base::isOk(); }
+
+ private:
+ void doRelease()
+ {
+ if (SUCCEEDED(mRC))
+ {
+ Base::mThat->i_safeVMPtrReleaser(&mpUVM);
+ mRC = E_FAIL;
+ }
+ Base::doRelease();
+ }
+ HRESULT mRC; /* Whether the VM ptr was retained. */
+ PUVM mpUVM;
+ PCVMMR3VTABLE mpVMM;
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(SafeVMPtrBase);
+ };
+
+public:
+
+ /*
+ * Helper class that safely manages the Console::mpUVM pointer
+ * by calling addVMCaller() on construction and releaseVMCaller() on
+ * destruction. Intended for Console children. The usage pattern is:
+ * <code>
+ * Console::SafeVMPtr ptrVM(mParent);
+ * if (!ptrVM.isOk())
+ * return ptrVM.rc();
+ * ...
+ * VMR3ReqCall(ptrVM.rawUVM(), ...
+ * ...
+ * printf("%p\n", ptrVM.rawUVM());
+ * </code>
+ *
+ * @note Temporarily locks the argument for writing.
+ *
+ * @sa SafeVMPtrQuiet, AutoVMCaller
+ */
+ typedef SafeVMPtrBase<false> SafeVMPtr;
+
+ /**
+ * A deviation of SafeVMPtr that doesn't set the error info on failure.
+ * Intended for pieces of code that don't need to return the VM access
+ * failure to the caller. The usage pattern is:
+ * <code>
+ * Console::SafeVMPtrQuiet pVM(mParent);
+ * if (pVM.rc())
+ * VMR3ReqCall(pVM, ...
+ * return S_OK;
+ * </code>
+ *
+ * @note Temporarily locks the argument for writing.
+ *
+ * @sa SafeVMPtr, AutoVMCaller
+ */
+ typedef SafeVMPtrBase<true> SafeVMPtrQuiet;
+
+ class SharedFolderData
+ {
+ public:
+ SharedFolderData()
+ { }
+
+ SharedFolderData(const Utf8Str &aHostPath,
+ bool aWritable,
+ bool aAutoMount,
+ const Utf8Str &aAutoMountPoint)
+ : m_strHostPath(aHostPath)
+ , m_fWritable(aWritable)
+ , m_fAutoMount(aAutoMount)
+ , m_strAutoMountPoint(aAutoMountPoint)
+ { }
+
+ /** Copy constructor. */
+ SharedFolderData(const SharedFolderData& aThat)
+ : m_strHostPath(aThat.m_strHostPath)
+ , m_fWritable(aThat.m_fWritable)
+ , m_fAutoMount(aThat.m_fAutoMount)
+ , m_strAutoMountPoint(aThat.m_strAutoMountPoint)
+ { }
+
+ /** Copy assignment operator. */
+ SharedFolderData &operator=(SharedFolderData const &a_rThat) RT_NOEXCEPT
+ {
+ m_strHostPath = a_rThat.m_strHostPath;
+ m_fWritable = a_rThat.m_fWritable;
+ m_fAutoMount = a_rThat.m_fAutoMount;
+ m_strAutoMountPoint = a_rThat.m_strAutoMountPoint;
+
+ return *this;
+ }
+
+ Utf8Str m_strHostPath;
+ bool m_fWritable;
+ bool m_fAutoMount;
+ Utf8Str m_strAutoMountPoint;
+ };
+
+ /**
+ * Class for managing emulated USB MSDs.
+ */
+ class USBStorageDevice
+ {
+ public:
+ USBStorageDevice()
+ { }
+ /** The UUID associated with the USB device. */
+ RTUUID mUuid;
+ /** Port of the storage device. */
+ LONG iPort;
+ };
+
+ typedef std::map<Utf8Str, ComObjPtr<SharedFolder> > SharedFolderMap;
+ typedef std::map<Utf8Str, SharedFolderData> SharedFolderDataMap;
+ typedef std::map<Utf8Str, ComPtr<IMediumAttachment> > MediumAttachmentMap;
+ typedef std::list <USBStorageDevice> USBStorageDeviceList;
+
+ static void i_powerUpThreadTask(VMPowerUpTask *pTask);
+ static void i_powerDownThreadTask(VMPowerDownTask *pTask);
+
+private:
+
+ typedef std::list <ComObjPtr<OUSBDevice> > USBDeviceList;
+ typedef std::list <ComObjPtr<RemoteUSBDevice> > RemoteUSBDeviceList;
+
+ HRESULT i_loadVMM(void) RT_NOEXCEPT;
+ HRESULT i_addVMCaller(bool aQuiet = false, bool aAllowNullVM = false);
+ void i_releaseVMCaller();
+ HRESULT i_safeVMPtrRetainer(PUVM *a_ppUVM, PCVMMR3VTABLE *a_ppVMM, bool aQuiet) RT_NOEXCEPT;
+ void i_safeVMPtrReleaser(PUVM *a_ppUVM);
+
+ HRESULT i_consoleInitReleaseLog(const ComPtr<IMachine> aMachine);
+
+ HRESULT i_powerUp(IProgress **aProgress, bool aPaused);
+ HRESULT i_powerDown(IProgress *aProgress = NULL);
+
+/* Note: FreeBSD needs this whether netflt is used or not. */
+#if ((defined(RT_OS_LINUX) && !defined(VBOX_WITH_NETFLT)) || defined(RT_OS_FREEBSD))
+ HRESULT i_attachToTapInterface(INetworkAdapter *networkAdapter);
+ HRESULT i_detachFromTapInterface(INetworkAdapter *networkAdapter);
+#endif
+ HRESULT i_powerDownHostInterfaces();
+
+ HRESULT i_setMachineState(MachineState_T aMachineState, bool aUpdateServer = true);
+ HRESULT i_setMachineStateLocally(MachineState_T aMachineState)
+ {
+ return i_setMachineState(aMachineState, false /* aUpdateServer */);
+ }
+
+ HRESULT i_findSharedFolder(const Utf8Str &strName,
+ ComObjPtr<SharedFolder> &aSharedFolder,
+ bool aSetError = false);
+
+ HRESULT i_fetchSharedFolders(BOOL aGlobal);
+ bool i_findOtherSharedFolder(const Utf8Str &straName,
+ SharedFolderDataMap::const_iterator &aIt);
+
+ HRESULT i_createSharedFolder(const Utf8Str &strName, const SharedFolderData &aData);
+ HRESULT i_removeSharedFolder(const Utf8Str &strName);
+
+ HRESULT i_suspendBeforeConfigChange(PUVM pUVM, PCVMMR3VTABLE pVMM, AutoWriteLock *pAlock, bool *pfResume);
+ void i_resumeAfterConfigChange(PUVM pUVM, PCVMMR3VTABLE pVMM);
+
+ static DECLCALLBACK(int) i_configConstructor(PUVM pUVM, PVM pVM, PCVMMR3VTABLE pVMM, void *pvConsole);
+ void InsertConfigString(PCFGMNODE pNode, const char *pcszName, const char *pcszValue);
+ void InsertConfigString(PCFGMNODE pNode, const char *pcszName, const Utf8Str &rStrValue);
+ void InsertConfigString(PCFGMNODE pNode, const char *pcszName, const Bstr &rBstrValue);
+ void InsertConfigPassword(PCFGMNODE pNode, const char *pcszName, const Utf8Str &rStrValue);
+ void InsertConfigBytes(PCFGMNODE pNode, const char *pcszName, const void *pvBytes, size_t cbBytes);
+ void InsertConfigInteger(PCFGMNODE pNode, const char *pcszName, uint64_t u64Integer);
+ void InsertConfigNode(PCFGMNODE pNode, const char *pcszName, PCFGMNODE *ppChild);
+ void InsertConfigNodeF(PCFGMNODE pNode, PCFGMNODE *ppChild, const char *pszNameFormat, ...);
+ void RemoveConfigValue(PCFGMNODE pNode, const char *pcszName);
+ int SetBiosDiskInfo(ComPtr<IMachine> pMachine, PCFGMNODE pCfg, PCFGMNODE pBiosCfg,
+ Bstr controllerName, const char * const s_apszBiosConfig[4]);
+ void i_configAudioDriver(IVirtualBox *pVirtualBox, IMachine *pMachine, PCFGMNODE pLUN, const char *pszDriverName,
+ bool fAudioEnabledIn, bool fAudioEnabledOut);
+ int i_configConstructorInner(PUVM pUVM, PVM pVM, PCVMMR3VTABLE pVMM, AutoWriteLock *pAlock);
+ int i_configCfgmOverlay(PCFGMNODE pRoot, IVirtualBox *pVirtualBox, IMachine *pMachine);
+ int i_configDumpAPISettingsTweaks(IVirtualBox *pVirtualBox, IMachine *pMachine);
+
+ int i_configGraphicsController(PCFGMNODE pDevices,
+ const GraphicsControllerType_T graphicsController,
+ BusAssignmentManager *pBusMgr,
+ const ComPtr<IMachine> &ptrMachine,
+ const ComPtr<IGraphicsAdapter> &ptrGraphicsAdapter,
+ const ComPtr<IBIOSSettings> &ptrBiosSettings,
+ bool fHMEnabled);
+ int i_checkMediumLocation(IMedium *pMedium, bool *pfUseHostIOCache);
+ int i_unmountMediumFromGuest(PUVM pUVM, PCVMMR3VTABLE pVMM, StorageBus_T enmBus, DeviceType_T enmDevType,
+ const char *pcszDevice, unsigned uInstance, unsigned uLUN,
+ bool fForceUnmount) RT_NOEXCEPT;
+ int i_removeMediumDriverFromVm(PCFGMNODE pCtlInst,
+ const char *pcszDevice,
+ unsigned uInstance,
+ unsigned uLUN,
+ StorageBus_T enmBus,
+ bool fAttachDetach,
+ bool fHotplug,
+ bool fForceUnmount,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ DeviceType_T enmDevType,
+ PCFGMNODE *ppLunL0);
+ int i_configMediumAttachment(const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ bool fUseHostIOCache,
+ bool fBuiltinIoCache,
+ bool fInsertDiskIntegrityDrv,
+ bool fSetupMerge,
+ unsigned uMergeSource,
+ unsigned uMergeTarget,
+ IMediumAttachment *pMediumAtt,
+ MachineState_T aMachineState,
+ HRESULT *phrc,
+ bool fAttachDetach,
+ bool fForceUnmount,
+ bool fHotplug,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ DeviceType_T *paLedDevType,
+ PCFGMNODE *ppLunL0);
+ int i_configMedium(PCFGMNODE pLunL0,
+ bool fPassthrough,
+ DeviceType_T enmType,
+ bool fUseHostIOCache,
+ bool fBuiltinIoCache,
+ bool fInsertDiskIntegrityDrv,
+ bool fSetupMerge,
+ unsigned uMergeSource,
+ unsigned uMergeTarget,
+ const char *pcszBwGroup,
+ bool fDiscard,
+ bool fNonRotational,
+ ComPtr<IMedium> ptrMedium,
+ MachineState_T aMachineState,
+ HRESULT *phrc);
+ int i_configMediumProperties(PCFGMNODE pCur, IMedium *pMedium, bool *pfHostIP, bool *pfEncrypted);
+ static DECLCALLBACK(int) i_reconfigureMediumAttachment(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ bool fUseHostIOCache,
+ bool fBuiltinIoCache,
+ bool fInsertDiskIntegrityDrv,
+ bool fSetupMerge,
+ unsigned uMergeSource,
+ unsigned uMergeTarget,
+ IMediumAttachment *aMediumAtt,
+ MachineState_T aMachineState,
+ HRESULT *phrc);
+ static DECLCALLBACK(int) i_changeRemovableMedium(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ bool fUseHostIOCache,
+ IMediumAttachment *aMediumAtt,
+ bool fForce);
+
+ HRESULT i_attachRawPCIDevices(PUVM pUVM, BusAssignmentManager *BusMgr, PCFGMNODE pDevices);
+ struct LEDSET;
+ typedef struct LEDSET *PLEDSET;
+ PPDMLED *i_getLedSet(uint32_t iLedSet);
+ uint32_t i_allocateDriverLeds(uint32_t cLeds, DeviceType_T enmType, DeviceType_T **ppSubTypes);
+ void i_attachStatusDriver(PCFGMNODE pCtlInst, DeviceType_T enmType,
+ uint32_t uFirst, uint32_t uLast,
+ DeviceType_T **ppaSubTypes,
+ Console::MediumAttachmentMap *pmapMediumAttachments,
+ const char *pcszDevice, unsigned uInstance);
+
+ int i_configNetwork(const char *pszDevice, unsigned uInstance, unsigned uLun, INetworkAdapter *aNetworkAdapter,
+ PCFGMNODE pCfg, PCFGMNODE pLunL0, PCFGMNODE pInst, bool fAttachDetach, bool fIgnoreConnectFailure,
+ PUVM pUVM, PCVMMR3VTABLE pVMM);
+ int i_configProxy(ComPtr<IVirtualBox> virtualBox, PCFGMNODE pCfg, const char *pcszPrefix, const com::Utf8Str &strIpAddr);
+
+ int i_configSerialPort(PCFGMNODE pInst, PortMode_T ePortMode, const char *pszPath, bool fServer);
+ static DECLCALLBACK(void) i_vmstateChangeCallback(PUVM pUVM, PCVMMR3VTABLE pVMM, VMSTATE enmState,
+ VMSTATE enmOldState, void *pvUser);
+ static DECLCALLBACK(int) i_unplugCpu(Console *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, VMCPUID idCpu);
+ static DECLCALLBACK(int) i_plugCpu(Console *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, VMCPUID idCpu);
+ HRESULT i_doMediumChange(IMediumAttachment *aMediumAttachment, bool fForce, PUVM pUVM, PCVMMR3VTABLE pVMM);
+ HRESULT i_doCPURemove(ULONG aCpu, PUVM pUVM, PCVMMR3VTABLE pVMM);
+ HRESULT i_doCPUAdd(ULONG aCpu, PUVM pUVM, PCVMMR3VTABLE pVMM);
+
+ HRESULT i_doNetworkAdapterChange(PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDevice, unsigned uInstance,
+ unsigned uLun, INetworkAdapter *aNetworkAdapter);
+ static DECLCALLBACK(int) i_changeNetworkAttachment(Console *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDevice,
+ unsigned uInstance, unsigned uLun, INetworkAdapter *aNetworkAdapter);
+ static DECLCALLBACK(int) i_changeSerialPortAttachment(Console *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, ISerialPort *pSerialPort);
+
+ int i_changeClipboardMode(ClipboardMode_T aClipboardMode);
+ int i_changeClipboardFileTransferMode(bool aEnabled);
+ int i_changeDnDMode(DnDMode_T aDnDMode);
+
+#ifdef VBOX_WITH_USB
+ HRESULT i_attachUSBDevice(IUSBDevice *aHostDevice, ULONG aMaskedIfs, const Utf8Str &aCaptureFilename);
+ HRESULT i_detachUSBDevice(const ComObjPtr<OUSBDevice> &aHostDevice);
+
+ static DECLCALLBACK(int) i_usbAttachCallback(Console *that, PUVM pUVM, PCVMMR3VTABLE pVMM, IUSBDevice *aHostDevice,
+ PCRTUUID aUuid, const char *aBackend, const char *aAddress,
+ PCFGMNODE pRemoteCfg, USBConnectionSpeed_T enmSpeed, ULONG aMaskedIfs,
+ const char *pszCaptureFilename);
+ static DECLCALLBACK(int) i_usbDetachCallback(Console *that, PUVM pUVM, PCVMMR3VTABLE pVMM, PCRTUUID aUuid);
+ static DECLCALLBACK(PREMOTEUSBCALLBACK) i_usbQueryRemoteUsbBackend(void *pvUser, PCRTUUID pUuid, uint32_t idClient);
+
+ /** Interface for the VRDP USB proxy backend to query for a device remote callback table. */
+ REMOTEUSBIF mRemoteUsbIf;
+#endif
+
+ static DECLCALLBACK(int) i_attachStorageDevice(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ bool fUseHostIOCache,
+ IMediumAttachment *aMediumAtt,
+ bool fSilent);
+ static DECLCALLBACK(int) i_detachStorageDevice(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ IMediumAttachment *aMediumAtt,
+ bool fSilent);
+ HRESULT i_doStorageDeviceAttach(IMediumAttachment *aMediumAttachment, PUVM pUVM, PCVMMR3VTABLE pVMM, bool fSilent);
+ HRESULT i_doStorageDeviceDetach(IMediumAttachment *aMediumAttachment, PUVM pUVM, PCVMMR3VTABLE pVMM, bool fSilent);
+
+ static DECLCALLBACK(int) i_stateProgressCallback(PUVM pUVM, unsigned uPercent, void *pvUser);
+
+ static DECLCALLBACK(void) i_genericVMSetErrorCallback(PUVM pUVM, void *pvUser, int rc, RT_SRC_POS_DECL,
+ const char *pszErrorFmt, va_list va);
+
+ void i_atVMRuntimeErrorCallbackF(uint32_t fFatal, const char *pszErrorId, const char *pszFormat, ...);
+ static DECLCALLBACK(void) i_atVMRuntimeErrorCallback(PUVM pUVM, void *pvUser, uint32_t fFatal,
+ const char *pszErrorId, const char *pszFormat, va_list va);
+
+ HRESULT i_captureUSBDevices(PUVM pUVM);
+ void i_detachAllUSBDevices(bool aDone);
+
+
+ static DECLCALLBACK(int) i_vmm2User_SaveState(PCVMM2USERMETHODS pThis, PUVM pUVM);
+ static DECLCALLBACK(void) i_vmm2User_NotifyEmtInit(PCVMM2USERMETHODS pThis, PUVM pUVM, PUVMCPU pUVCpu);
+ static DECLCALLBACK(void) i_vmm2User_NotifyEmtTerm(PCVMM2USERMETHODS pThis, PUVM pUVM, PUVMCPU pUVCpu);
+ static DECLCALLBACK(void) i_vmm2User_NotifyPdmtInit(PCVMM2USERMETHODS pThis, PUVM pUVM);
+ static DECLCALLBACK(void) i_vmm2User_NotifyPdmtTerm(PCVMM2USERMETHODS pThis, PUVM pUVM);
+ static DECLCALLBACK(void) i_vmm2User_NotifyResetTurnedIntoPowerOff(PCVMM2USERMETHODS pThis, PUVM pUVM);
+ static DECLCALLBACK(void *) i_vmm2User_QueryGenericObject(PCVMM2USERMETHODS pThis, PUVM pUVM, PCRTUUID pUuid);
+
+ static DECLCALLBACK(void *) i_drvStatus_QueryInterface(PPDMIBASE pInterface, const char *pszIID);
+ static DECLCALLBACK(void) i_drvStatus_UnitChanged(PPDMILEDCONNECTORS pInterface, unsigned iLUN);
+ static DECLCALLBACK(int) i_drvStatus_MediumEjected(PPDMIMEDIANOTIFY pInterface, unsigned iLUN);
+ static DECLCALLBACK(void) i_drvStatus_Destruct(PPDMDRVINS pDrvIns);
+ static DECLCALLBACK(int) i_drvStatus_Construct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags);
+
+ static DECLCALLBACK(int) i_pdmIfSecKey_KeyRetain(PPDMISECKEY pInterface, const char *pszId, const uint8_t **ppbKey,
+ size_t *pcbKey);
+ static DECLCALLBACK(int) i_pdmIfSecKey_KeyRelease(PPDMISECKEY pInterface, const char *pszId);
+ static DECLCALLBACK(int) i_pdmIfSecKey_PasswordRetain(PPDMISECKEY pInterface, const char *pszId, const char **ppszPassword);
+ static DECLCALLBACK(int) i_pdmIfSecKey_PasswordRelease(PPDMISECKEY pInterface, const char *pszId);
+
+ static DECLCALLBACK(int) i_pdmIfSecKeyHlp_KeyMissingNotify(PPDMISECKEYHLP pInterface);
+
+ int mcAudioRefs;
+ volatile uint32_t mcVRDPClients;
+ uint32_t mu32SingleRDPClientId; /* The id of a connected client in the single connection mode. */
+ volatile bool mcGuestCredentialsProvided;
+
+ static const char *sSSMConsoleUnit;
+
+ HRESULT i_loadDataFromSavedState();
+ int i_loadStateFileExecInternal(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t u32Version);
+
+ static DECLCALLBACK(int) i_saveStateFileExec(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser);
+ static DECLCALLBACK(int) i_loadStateFileExec(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser,
+ uint32_t uVersion, uint32_t uPass);
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ HRESULT i_doEnumerateGuestProperties(const Utf8Str &aPatterns,
+ std::vector<Utf8Str> &aNames,
+ std::vector<Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<Utf8Str> &aFlags);
+
+ void i_guestPropertiesHandleVMReset(void);
+ bool i_guestPropertiesVRDPEnabled(void);
+ void i_guestPropertiesVRDPUpdateLogon(uint32_t u32ClientId, const char *pszUser, const char *pszDomain);
+ void i_guestPropertiesVRDPUpdateActiveClient(uint32_t u32ClientId);
+ void i_guestPropertiesVRDPUpdateClientAttach(uint32_t u32ClientId, bool fAttached);
+ void i_guestPropertiesVRDPUpdateNameChange(uint32_t u32ClientId, const char *pszName);
+ void i_guestPropertiesVRDPUpdateIPAddrChange(uint32_t u32ClientId, const char *pszIPAddr);
+ void i_guestPropertiesVRDPUpdateLocationChange(uint32_t u32ClientId, const char *pszLocation);
+ void i_guestPropertiesVRDPUpdateOtherInfoChange(uint32_t u32ClientId, const char *pszOtherInfo);
+ void i_guestPropertiesVRDPUpdateDisconnect(uint32_t u32ClientId);
+#endif
+
+ /** @name Disk encryption support
+ * @{ */
+ HRESULT i_consoleParseDiskEncryption(const char *psz, const char **ppszEnd);
+ HRESULT i_configureEncryptionForDisk(const Utf8Str &strId, unsigned *pcDisksConfigured);
+ HRESULT i_clearDiskEncryptionKeysOnAllAttachmentsWithKeyId(const Utf8Str &strId);
+ HRESULT i_initSecretKeyIfOnAllAttachments(void);
+ int i_consoleParseKeyValue(const char *psz, const char **ppszEnd,
+ char **ppszKey, char **ppszVal);
+ void i_removeSecretKeysOnSuspend();
+ /** @} */
+
+ /** @name Teleporter support
+ * @{ */
+ static DECLCALLBACK(int) i_teleporterSrcThreadWrapper(RTTHREAD hThreadSelf, void *pvUser);
+ HRESULT i_teleporterSrc(TeleporterStateSrc *pState);
+ HRESULT i_teleporterSrcReadACK(TeleporterStateSrc *pState, const char *pszWhich, const char *pszNAckMsg = NULL);
+ HRESULT i_teleporterSrcSubmitCommand(TeleporterStateSrc *pState, const char *pszCommand, bool fWaitForAck = true);
+ HRESULT i_teleporterTrg(PUVM pUVM, PCVMMR3VTABLE pVMM, IMachine *pMachine, Utf8Str *pErrorMsg,
+ bool fStartPaused, Progress *pProgress, bool *pfPowerOffOnFailure);
+ static DECLCALLBACK(int) i_teleporterTrgServeConnection(RTSOCKET Sock, void *pvUser);
+ /** @} */
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ /** @name Encrypted log interface
+ * @{ */
+ static DECLCALLBACK(int) i_logEncryptedOpen(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilename, uint32_t fFlags);
+ static DECLCALLBACK(int) i_logEncryptedClose(PCRTLOGOUTPUTIF pIf, void *pvUser);
+ static DECLCALLBACK(int) i_logEncryptedDelete(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilename);
+ static DECLCALLBACK(int) i_logEncryptedRename(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilenameOld,
+ const char *pszFilenameNew, uint32_t fFlags);
+ static DECLCALLBACK(int) i_logEncryptedQuerySize(PCRTLOGOUTPUTIF pIf, void *pvUser, uint64_t *pcbSize);
+ static DECLCALLBACK(int) i_logEncryptedWrite(PCRTLOGOUTPUTIF pIf, void *pvUser, const void *pvBuf,
+ size_t cbWrite, size_t *pcbWritten);
+ static DECLCALLBACK(int) i_logEncryptedFlush(PCRTLOGOUTPUTIF pIf, void *pvUser);
+ /** @} */
+#endif
+
+ bool mSavedStateDataLoaded : 1;
+
+ const ComPtr<IMachine> mMachine;
+ const ComPtr<IInternalMachineControl> mControl;
+
+ const ComPtr<IVRDEServer> mVRDEServer;
+
+ ConsoleVRDPServer * const mConsoleVRDPServer;
+ bool mfVRDEChangeInProcess;
+ bool mfVRDEChangePending;
+ const ComObjPtr<Guest> mGuest;
+ const ComObjPtr<Keyboard> mKeyboard;
+ const ComObjPtr<Mouse> mMouse;
+ const ComObjPtr<Display> mDisplay;
+ const ComObjPtr<MachineDebugger> mDebugger;
+ const ComObjPtr<VRDEServerInfo> mVRDEServerInfo;
+ /** This can safely be used without holding any locks.
+ * An AutoCaller suffices to prevent it being destroy while in use and
+ * internally there is a lock providing the necessary serialization. */
+ const ComObjPtr<EventSource> mEventSource;
+#ifdef VBOX_WITH_EXTPACK
+ const ComObjPtr<ExtPackManager> mptrExtPackManager;
+#endif
+ const ComObjPtr<EmulatedUSB> mEmulatedUSB;
+ const ComObjPtr<NvramStore> mptrNvramStore;
+
+ USBDeviceList mUSBDevices;
+ RemoteUSBDeviceList mRemoteUSBDevices;
+
+ SharedFolderDataMap m_mapGlobalSharedFolders;
+ SharedFolderDataMap m_mapMachineSharedFolders;
+ SharedFolderMap m_mapSharedFolders; // the console instances
+
+ /** VMM loader handle. */
+ RTLDRMOD mhModVMM;
+ /** The VMM vtable. */
+ PCVMMR3VTABLE mpVMM;
+ /** The user mode VM handle. */
+ PUVM mpUVM;
+ /** Holds the number of "readonly" mpUVM callers (users). */
+ uint32_t mVMCallers;
+ /** Semaphore posted when the number of mpUVM callers drops to zero. */
+ RTSEMEVENT mVMZeroCallersSem;
+ /** true when Console has entered the mpUVM destruction phase. */
+ bool mVMDestroying : 1;
+ /** true when power down is initiated by vmstateChangeCallback (EMT). */
+ bool mVMPoweredOff : 1;
+ /** true when vmstateChangeCallback shouldn't initiate a power down. */
+ bool mVMIsAlreadyPoweringOff : 1;
+ /** true if we already showed the snapshot folder size warning. */
+ bool mfSnapshotFolderSizeWarningShown : 1;
+ /** true if we already showed the snapshot folder ext4/xfs bug warning. */
+ bool mfSnapshotFolderExt4WarningShown : 1;
+ /** true if we already listed the disk type of the snapshot folder. */
+ bool mfSnapshotFolderDiskTypeShown : 1;
+ /** true if a USB controller is available (i.e. USB devices can be attached). */
+ bool mfVMHasUsbController : 1;
+ /** Shadow of the VBoxInternal2/TurnResetIntoPowerOff extra data setting.
+ * This is initialized by Console::i_configConstructorInner(). */
+ bool mfTurnResetIntoPowerOff : 1;
+ /** true if the VM power off was caused by reset. */
+ bool mfPowerOffCausedByReset : 1;
+
+ /** Pointer to the VMM -> User (that's us) callbacks. */
+ struct MYVMM2USERMETHODS : public VMM2USERMETHODS
+ {
+ Console *pConsole;
+ /** The in-progress snapshot. */
+ ISnapshot *pISnapshot;
+ } *mpVmm2UserMethods;
+
+ /** The current network attachment type in the VM.
+ * This doesn't have to match the network attachment type maintained in the
+ * NetworkAdapter. This is needed to change the network attachment
+ * dynamically.
+ */
+ typedef std::vector<NetworkAttachmentType_T> NetworkAttachmentTypeVector;
+ NetworkAttachmentTypeVector meAttachmentType;
+
+ VMMDev * m_pVMMDev;
+ AudioVRDE * const mAudioVRDE;
+#ifdef VBOX_WITH_USB_CARDREADER
+ UsbCardReader * const mUsbCardReader;
+#endif
+ BusAssignmentManager* mBusMgr;
+
+ /** @name LEDs and their management
+ * @{ */
+ /** Number of LED sets in use in maLedSets. */
+ uint32_t mcLedSets;
+ /** LED sets. */
+ struct LEDSET
+ {
+ PPDMLED *papLeds;
+ uint32_t cLeds;
+ DeviceType_T enmType;
+ DeviceType_T *paSubTypes; /**< Optionally, device types for each individual LED. Runs parallel to papLeds. */
+ } maLedSets[32];
+ /** @} */
+
+ MediumAttachmentMap mapMediumAttachments;
+
+ /** List of attached USB storage devices. */
+ USBStorageDeviceList mUSBStorageDevices;
+
+ /** Store for secret keys. */
+ SecretKeyStore * const m_pKeyStore;
+ /** Number of disks configured for encryption. */
+ unsigned m_cDisksEncrypted;
+ /** Number of disks which have the key in the map. */
+ unsigned m_cDisksPwProvided;
+
+ /** Current active port modes of the supported serial ports. */
+ PortMode_T m_aeSerialPortMode[4];
+
+ /** Pointer to the key consumer -> provider (that's us) callbacks. */
+ struct MYPDMISECKEY : public PDMISECKEY
+ {
+ Console *pConsole;
+ } *mpIfSecKey;
+
+ /** Pointer to the key helpers -> provider (that's us) callbacks. */
+ struct MYPDMISECKEYHLP : public PDMISECKEYHLP
+ {
+ Console *pConsole;
+ } *mpIfSecKeyHlp;
+
+/* Note: FreeBSD needs this whether netflt is used or not. */
+#if ((defined(RT_OS_LINUX) && !defined(VBOX_WITH_NETFLT)) || defined(RT_OS_FREEBSD))
+ Utf8Str maTAPDeviceName[8];
+ RTFILE maTapFD[8];
+#endif
+
+ bool mVMStateChangeCallbackDisabled;
+
+ bool mfUseHostClipboard;
+
+ /** Local machine state value. */
+ MachineState_T mMachineState;
+
+ /** Machine uuid string. */
+ Bstr mstrUuid;
+
+ /** @name Members related to the cryptographic support interface.
+ * @{ */
+ /** The loaded module handle if loaded. */
+ RTLDRMOD mhLdrModCrypto;
+ /** Reference counter tracking how many users of the cryptographic support
+ * are there currently. */
+ volatile uint32_t mcRefsCrypto;
+ /** Pointer to the cryptographic support interface. */
+ PCVBOXCRYPTOIF mpCryptoIf;
+ /** @} */
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ /** Flag whether the log is encrypted. */
+ bool m_fEncryptedLog;
+ /** The file handle of the encrypted log. */
+ RTVFSFILE m_hVfsFileLog;
+ /** The logging output interface for encrypted logs. */
+ RTLOGOUTPUTIF m_LogOutputIf;
+ /** The log file key ID. */
+ Utf8Str m_strLogKeyId;
+ /** The log file key store. */
+ Utf8Str m_strLogKeyStore;
+#endif
+
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ HGCMSVCEXTHANDLE m_hHgcmSvcExtDragAndDrop;
+#endif
+
+ /** Pointer to the progress object of a live cancelable task.
+ *
+ * This is currently only used by Console::Teleport(), but is intended to later
+ * be used by the live snapshot code path as well. Actions like
+ * Console::PowerDown, which automatically cancels out the running snapshot /
+ * teleportation operation, will cancel the teleportation / live snapshot
+ * operation before starting. */
+ ComPtr<IProgress> mptrCancelableProgress;
+
+ ComPtr<IEventListener> mVmListener;
+
+#ifdef VBOX_WITH_RECORDING
+ struct Recording
+ {
+ Recording()
+# ifdef VBOX_WITH_AUDIO_RECORDING
+ : mAudioRec(NULL)
+# endif
+ { }
+
+ /** The recording context. */
+ RecordingContext mCtx;
+# ifdef VBOX_WITH_AUDIO_RECORDING
+ /** Pointer to capturing audio backend. */
+ AudioVideoRec * const mAudioRec;
+# endif
+ } mRecording;
+#endif /* VBOX_WITH_RECORDING */
+
+#ifdef VBOX_WITH_CLOUD_NET
+ GatewayInfo mGateway;
+#endif /* VBOX_WITH_CLOUD_NET */
+
+ friend class VMTask;
+ friend class ConsoleVRDPServer;
+};
+
+#endif /* !MAIN_INCLUDED_ConsoleImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/ConsoleVRDPServer.h b/src/VBox/Main/include/ConsoleVRDPServer.h
new file mode 100644
index 00000000..7ff88079
--- /dev/null
+++ b/src/VBox/Main/include/ConsoleVRDPServer.h
@@ -0,0 +1,428 @@
+/* $Id: ConsoleVRDPServer.h $ */
+/** @file
+ * VBox Console VRDE Server Helper class and implementation of IVRDEServerInfo
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ConsoleVRDPServer_h
+#define MAIN_INCLUDED_ConsoleVRDPServer_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VRDEServerInfoWrap.h"
+#include "RemoteUSBBackend.h"
+#include "HGCM.h"
+
+#include "AuthLibrary.h"
+
+#include <VBox/RemoteDesktop/VRDEImage.h>
+#include <VBox/RemoteDesktop/VRDEMousePtr.h>
+#include <VBox/RemoteDesktop/VRDESCard.h>
+#include <VBox/RemoteDesktop/VRDETSMF.h>
+#define VRDE_VIDEOIN_WITH_VRDEINTERFACE /* Get the VRDE interface definitions. */
+#include <VBox/RemoteDesktop/VRDEVideoIn.h>
+#include <VBox/RemoteDesktop/VRDEInput.h>
+
+#include <VBox/HostServices/VBoxClipboardExt.h>
+#include <VBox/HostServices/VBoxHostChannel.h>
+
+#include "SchemaDefs.h"
+
+// ConsoleVRDPServer
+///////////////////////////////////////////////////////////////////////////////
+
+class EmWebcam;
+
+typedef struct _VRDPInputSynch
+{
+ int cGuestNumLockAdaptions;
+ int cGuestCapsLockAdaptions;
+
+ bool fGuestNumLock;
+ bool fGuestCapsLock;
+ bool fGuestScrollLock;
+
+ bool fClientNumLock;
+ bool fClientCapsLock;
+ bool fClientScrollLock;
+} VRDPInputSynch;
+
+/* Member of Console. Helper class for VRDP server management. Not a COM class. */
+class ConsoleVRDPServer
+{
+public:
+ DECLARE_TRANSLATE_METHODS(ConsoleVRDPServer)
+
+ ConsoleVRDPServer (Console *console);
+ ~ConsoleVRDPServer ();
+
+ int Launch (void);
+
+ void NotifyAbsoluteMouse (bool fGuestWantsAbsolute)
+ {
+ m_fGuestWantsAbsolute = fGuestWantsAbsolute;
+ }
+
+ void NotifyKeyboardLedsChange (BOOL fNumLock, BOOL fCapsLock, BOOL fScrollLock)
+ {
+ bool fGuestNumLock = (fNumLock != FALSE);
+ bool fGuestCapsLock = (fCapsLock != FALSE);
+ bool fGuestScrollLock = (fScrollLock != FALSE);
+
+ /* Might need to resync in case the guest itself changed the LED status. */
+ if (m_InputSynch.fClientNumLock != fGuestNumLock)
+ {
+ m_InputSynch.cGuestNumLockAdaptions = 2;
+ }
+
+ if (m_InputSynch.fClientCapsLock != fGuestCapsLock)
+ {
+ m_InputSynch.cGuestCapsLockAdaptions = 2;
+ }
+
+ m_InputSynch.fGuestNumLock = fGuestNumLock;
+ m_InputSynch.fGuestCapsLock = fGuestCapsLock;
+ m_InputSynch.fGuestScrollLock = fGuestScrollLock;
+ }
+
+ void EnableConnections (void);
+ void DisconnectClient (uint32_t u32ClientId, bool fReconnect);
+ int MousePointer(BOOL alpha, ULONG xHot, ULONG yHot, ULONG width, ULONG height, const uint8_t *pu8Shape);
+ void MousePointerUpdate (const VRDECOLORPOINTER *pPointer);
+ void MousePointerHide (void);
+
+ void Stop (void);
+
+ AuthResult Authenticate (const Guid &uuid, AuthGuestJudgement guestJudgement,
+ const char *pszUser, const char *pszPassword, const char *pszDomain,
+ uint32_t u32ClientId);
+
+ void AuthDisconnect (const Guid &uuid, uint32_t u32ClientId);
+
+ void USBBackendCreate (uint32_t u32ClientId, void **ppvIntercept);
+ void USBBackendDelete (uint32_t u32ClientId);
+
+ void *USBBackendRequestPointer (uint32_t u32ClientId, const Guid *pGuid);
+ void USBBackendReleasePointer (const Guid *pGuid);
+
+ /* Private interface for the RemoteUSBBackend destructor. */
+ void usbBackendRemoveFromList (RemoteUSBBackend *pRemoteUSBBackend);
+
+ /* Private methods for the Remote USB thread. */
+ RemoteUSBBackend *usbBackendGetNext (RemoteUSBBackend *pRemoteUSBBackend);
+
+ void notifyRemoteUSBThreadRunning (RTTHREAD thread);
+ bool isRemoteUSBThreadRunning (void);
+ void waitRemoteUSBThreadEvent (RTMSINTERVAL cMillies);
+
+ void ClipboardCreate (uint32_t u32ClientId);
+ void ClipboardDelete (uint32_t u32ClientId);
+
+ /*
+ * Forwarders to VRDP server library.
+ */
+ void SendUpdate (unsigned uScreenId, void *pvUpdate, uint32_t cbUpdate) const;
+ void SendResize (void);
+ void SendUpdateBitmap (unsigned uScreenId, uint32_t x, uint32_t y, uint32_t w, uint32_t h) const;
+
+ void SendAudioSamples (void const *pvSamples, uint32_t cSamples, VRDEAUDIOFORMAT format) const;
+ void SendAudioVolume (uint16_t left, uint16_t right) const;
+ void SendUSBRequest (uint32_t u32ClientId, void *pvParms, uint32_t cbParms) const;
+
+ void QueryInfo (uint32_t index, void *pvBuffer, uint32_t cbBuffer, uint32_t *pcbOut) const;
+
+ int SendAudioInputBegin(void **ppvUserCtx,
+ void *pvContext,
+ uint32_t cSamples,
+ uint32_t iSampleHz,
+ uint32_t cChannels,
+ uint32_t cBits);
+
+ void SendAudioInputEnd(void *pvUserCtx);
+
+ int SCardRequest(void *pvUser, uint32_t u32Function, const void *pvData, uint32_t cbData);
+
+ int VideoInDeviceAttach(const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle, void *pvDeviceCtx);
+ int VideoInDeviceDetach(const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle);
+ int VideoInGetDeviceDesc(void *pvUser, const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle);
+ int VideoInControl(void *pvUser, const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle,
+ const VRDEVIDEOINCTRLHDR *pReq, uint32_t cbReq);
+
+ Console *getConsole(void) { return mConsole; }
+
+ void onMousePointerShapeChange(BOOL visible, BOOL alpha, ULONG xHot, ULONG yHot,
+ ULONG width, ULONG height, ComSafeArrayIn(BYTE,shape));
+
+private:
+ /* Note: This is not a ComObjPtr here, because the ConsoleVRDPServer object
+ * is actually just a part of the Console.
+ */
+ Console *mConsole;
+
+ HVRDESERVER mhServer;
+ int mServerInterfaceVersion;
+
+ int32_t volatile mcInResize; /* Do not Stop the server if this is not 0. */
+
+ static int loadVRDPLibrary (const char *pszLibraryName);
+
+ /** Static because will never load this more than once! */
+ static RTLDRMOD mVRDPLibrary;
+
+ static PFNVRDECREATESERVER mpfnVRDECreateServer;
+
+ static VRDEENTRYPOINTS_4 mEntryPoints;
+ static VRDEENTRYPOINTS_4 *mpEntryPoints;
+ static VRDECALLBACKS_4 mCallbacks;
+
+ static DECLCALLBACK(int) VRDPCallbackQueryProperty (void *pvCallback, uint32_t index, void *pvBuffer, uint32_t cbBuffer, uint32_t *pcbOut);
+ static DECLCALLBACK(int) VRDPCallbackClientLogon (void *pvCallback, uint32_t u32ClientId, const char *pszUser, const char *pszPassword, const char *pszDomain);
+ static DECLCALLBACK(void) VRDPCallbackClientConnect (void *pvCallback, uint32_t u32ClientId);
+ static DECLCALLBACK(void) VRDPCallbackClientDisconnect (void *pvCallback, uint32_t u32ClientId, uint32_t fu32Intercepted);
+ static DECLCALLBACK(int) VRDPCallbackIntercept (void *pvCallback, uint32_t u32ClientId, uint32_t fu32Intercept, void **ppvIntercept);
+ static DECLCALLBACK(int) VRDPCallbackUSB (void *pvCallback, void *pvIntercept, uint32_t u32ClientId, uint8_t u8Code, const void *pvRet, uint32_t cbRet);
+ static DECLCALLBACK(int) VRDPCallbackClipboard (void *pvCallback, void *pvIntercept, uint32_t u32ClientId, uint32_t u32Function, uint32_t u32Format, const void *pvData, uint32_t cbData);
+ static DECLCALLBACK(bool) VRDPCallbackFramebufferQuery (void *pvCallback, unsigned uScreenId, VRDEFRAMEBUFFERINFO *pInfo);
+ static DECLCALLBACK(void) VRDPCallbackFramebufferLock (void *pvCallback, unsigned uScreenId);
+ static DECLCALLBACK(void) VRDPCallbackFramebufferUnlock (void *pvCallback, unsigned uScreenId);
+ static DECLCALLBACK(void) VRDPCallbackInput (void *pvCallback, int type, const void *pvInput, unsigned cbInput);
+ static DECLCALLBACK(void) VRDPCallbackVideoModeHint (void *pvCallback, unsigned cWidth, unsigned cHeight, unsigned cBitsPerPixel, unsigned uScreenId);
+ static DECLCALLBACK(void) VRDECallbackAudioIn (void *pvCallback, void *pvCtx, uint32_t u32ClientId, uint32_t u32Event, const void *pvData, uint32_t cbData);
+
+ void fetchCurrentState(void);
+
+ bool m_fGuestWantsAbsolute;
+ int m_mousex;
+ int m_mousey;
+
+ ComPtr<IDisplaySourceBitmap> maSourceBitmaps[SchemaDefs::MaxGuestMonitors];
+
+ ComPtr<IEventListener> mConsoleListener;
+
+ VRDPInputSynch m_InputSynch;
+
+ int32_t mVRDPBindPort;
+
+ RTCRITSECT mCritSect;
+
+ int lockConsoleVRDPServer (void);
+ void unlockConsoleVRDPServer (void);
+
+ int mcClipboardRefs;
+ HGCMSVCEXTHANDLE mhClipboard;
+ PFNVRDPCLIPBOARDEXTCALLBACK mpfnClipboardCallback;
+
+ static DECLCALLBACK(int) ClipboardCallback (void *pvCallback, uint32_t u32ClientId, uint32_t u32Function, uint32_t u32Format, const void *pvData, uint32_t cbData);
+ static DECLCALLBACK(int) ClipboardServiceExtension(void *pvExtension, uint32_t u32Function, void *pvParms, uint32_t cbParms);
+
+#ifdef VBOX_WITH_USB
+ RemoteUSBBackend *usbBackendFindByUUID (const Guid *pGuid);
+ RemoteUSBBackend *usbBackendFind (uint32_t u32ClientId);
+
+ typedef struct _USBBackends
+ {
+ RemoteUSBBackend *pHead;
+ RemoteUSBBackend *pTail;
+
+ RTTHREAD thread;
+
+ bool fThreadRunning;
+
+ RTSEMEVENT event;
+ } USBBackends;
+
+ USBBackends mUSBBackends;
+
+ void remoteUSBThreadStart (void);
+ void remoteUSBThreadStop (void);
+#endif /* VBOX_WITH_USB */
+
+#ifndef VBOX_WITH_VRDEAUTH_IN_VBOXSVC
+ /* External authentication library context. The library is loaded in the
+ * Authenticate method and unloaded at the object destructor.
+ */
+ AUTHLIBRARYCONTEXT mAuthLibCtx;
+#endif
+
+ uint32_t volatile mu32AudioInputClientId;
+
+ int32_t volatile mcClients;
+
+#if 0 /** @todo Chromium got removed (see @bugref{9529}) and this is not available for VMSVGA yet. */
+ static DECLCALLBACK(void) H3DORBegin(const void *pvContext, void **ppvInstance,
+ const char *pszFormat);
+ static DECLCALLBACK(void) H3DORGeometry(void *pvInstance,
+ int32_t x, int32_t y, uint32_t w, uint32_t h);
+ static DECLCALLBACK(void) H3DORVisibleRegion(void *pvInstance,
+ uint32_t cRects, const RTRECT *paRects);
+ static DECLCALLBACK(void) H3DORFrame(void *pvInstance,
+ void *pvData, uint32_t cbData);
+ static DECLCALLBACK(void) H3DOREnd(void *pvInstance);
+ static DECLCALLBACK(int) H3DORContextProperty(const void *pvContext, uint32_t index,
+ void *pvBuffer, uint32_t cbBuffer, uint32_t *pcbOut);
+#endif
+
+ void remote3DRedirect(bool fEnable);
+
+ /*
+ * VRDE server optional interfaces.
+ */
+
+ /* Image update interface. */
+ bool m_fInterfaceImage;
+ VRDEIMAGECALLBACKS m_interfaceCallbacksImage;
+ VRDEIMAGEINTERFACE m_interfaceImage;
+ static DECLCALLBACK(int) VRDEImageCbNotify (void *pvContext,
+ void *pvUser,
+ HVRDEIMAGE hVideo,
+ uint32_t u32Id,
+ void *pvData,
+ uint32_t cbData);
+ /* Mouse pointer interface. */
+ VRDEMOUSEPTRINTERFACE m_interfaceMousePtr;
+
+ /* Smartcard interface. */
+ VRDESCARDINTERFACE m_interfaceSCard;
+ VRDESCARDCALLBACKS m_interfaceCallbacksSCard;
+ static DECLCALLBACK(int) VRDESCardCbNotify(void *pvContext,
+ uint32_t u32Id,
+ void *pvData,
+ uint32_t cbData);
+ static DECLCALLBACK(int) VRDESCardCbResponse(void *pvContext,
+ int rcRequest,
+ void *pvUser,
+ uint32_t u32Function,
+ void *pvData,
+ uint32_t cbData);
+
+ /* TSMF interface. */
+ VRDETSMFINTERFACE m_interfaceTSMF;
+ VRDETSMFCALLBACKS m_interfaceCallbacksTSMF;
+ static DECLCALLBACK(void) VRDETSMFCbNotify(void *pvContext,
+ uint32_t u32Notification,
+ void *pvChannel,
+ const void *pvParm,
+ uint32_t cbParm);
+ void setupTSMF(void);
+
+ static DECLCALLBACK(int) tsmfHostChannelAttach(void *pvProvider, void **ppvInstance, uint32_t u32Flags,
+ VBOXHOSTCHANNELCALLBACKS *pCallbacks, void *pvCallbacks);
+ static DECLCALLBACK(void) tsmfHostChannelDetach(void *pvInstance);
+ static DECLCALLBACK(int) tsmfHostChannelSend(void *pvInstance, const void *pvData, uint32_t cbData);
+ static DECLCALLBACK(int) tsmfHostChannelRecv(void *pvInstance, void *pvData, uint32_t cbData,
+ uint32_t *pcbReturned, uint32_t *pcbRemaining);
+ static DECLCALLBACK(int) tsmfHostChannelControl(void *pvInstance, uint32_t u32Code,
+ const void *pvParm, uint32_t cbParm,
+ const void *pvData, uint32_t cbData, uint32_t *pcbDataReturned);
+ int tsmfLock(void);
+ void tsmfUnlock(void);
+ RTCRITSECT mTSMFLock;
+
+ /* Video input interface. */
+ VRDEVIDEOININTERFACE m_interfaceVideoIn;
+ VRDEVIDEOINCALLBACKS m_interfaceCallbacksVideoIn;
+ static DECLCALLBACK(void) VRDECallbackVideoInNotify(void *pvCallback,
+ uint32_t u32Id,
+ const void *pvData,
+ uint32_t cbData);
+ static DECLCALLBACK(void) VRDECallbackVideoInDeviceDesc(void *pvCallback,
+ int rcRequest,
+ void *pDeviceCtx,
+ void *pvUser,
+ const VRDEVIDEOINDEVICEDESC *pDeviceDesc,
+ uint32_t cbDevice);
+ static DECLCALLBACK(void) VRDECallbackVideoInControl(void *pvCallback,
+ int rcRequest,
+ void *pDeviceCtx,
+ void *pvUser,
+ const VRDEVIDEOINCTRLHDR *pControl,
+ uint32_t cbControl);
+ static DECLCALLBACK(void) VRDECallbackVideoInFrame(void *pvCallback,
+ int rcRequest,
+ void *pDeviceCtx,
+ const VRDEVIDEOINPAYLOADHDR *pFrame,
+ uint32_t cbFrame);
+ EmWebcam *mEmWebcam;
+
+ /* Input interface. */
+ VRDEINPUTINTERFACE m_interfaceInput;
+ VRDEINPUTCALLBACKS m_interfaceCallbacksInput;
+ static DECLCALLBACK(void) VRDECallbackInputSetup(void *pvCallback,
+ int rcRequest,
+ uint32_t u32Method,
+ const void *pvResult,
+ uint32_t cbResult);
+ static DECLCALLBACK(void) VRDECallbackInputEvent(void *pvCallback,
+ uint32_t u32Method,
+ const void *pvEvent,
+ uint32_t cbEvent);
+ uint64_t mu64TouchInputTimestampMCS;
+};
+
+
+class Console;
+
+class ATL_NO_VTABLE VRDEServerInfo :
+ public VRDEServerInfoWrap
+{
+public:
+ DECLARE_NOT_AGGREGATABLE(VRDEServerInfo)
+
+ DECLARE_COMMON_CLASS_METHODS(VRDEServerInfo)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ /* Public initializer/uninitializer for internal purposes only. */
+ HRESULT init(Console *aParent);
+ void uninit();
+
+private:
+ // wrapped IVRDEServerInfo properties
+#define DECL_GETTER(_aType, _aName) virtual HRESULT get##_aName(_aType *a##_aName)
+#define DECL_GETTER_REF(_aType, _aName) virtual HRESULT get##_aName(_aType &a##_aName)
+ DECL_GETTER(BOOL, Active);
+ DECL_GETTER(LONG, Port);
+ DECL_GETTER(ULONG, NumberOfClients);
+ DECL_GETTER(LONG64, BeginTime);
+ DECL_GETTER(LONG64, EndTime);
+ DECL_GETTER(LONG64, BytesSent);
+ DECL_GETTER(LONG64, BytesSentTotal);
+ DECL_GETTER(LONG64, BytesReceived);
+ DECL_GETTER(LONG64, BytesReceivedTotal);
+ DECL_GETTER_REF(com::Utf8Str, User);
+ DECL_GETTER_REF(com::Utf8Str, Domain);
+ DECL_GETTER_REF(com::Utf8Str, ClientName);
+ DECL_GETTER_REF(com::Utf8Str, ClientIP);
+ DECL_GETTER(ULONG, ClientVersion);
+ DECL_GETTER(ULONG, EncryptionStyle);
+#undef DECL_GETTER_REF
+#undef DECL_GETTER
+
+ Console * const mParent;
+};
+
+#endif /* !MAIN_INCLUDED_ConsoleVRDPServer_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/CryptoUtils.h b/src/VBox/Main/include/CryptoUtils.h
new file mode 100644
index 00000000..7db74b96
--- /dev/null
+++ b/src/VBox/Main/include/CryptoUtils.h
@@ -0,0 +1,141 @@
+/* $Id: CryptoUtils.h $ */
+/** @file
+ * Main - Cryptographic utility functions used by both VBoxSVC and VBoxC.
+ */
+
+/*
+ * Copyright (C) 2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_CryptoUtils_h
+#define MAIN_INCLUDED_CryptoUtils_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+#include <iprt/types.h>
+#include <iprt/vfs.h>
+
+#include <VBox/VBoxCryptoIf.h>
+#include <VBox/com/string.h>
+
+#include <VBox/vmm/ssm.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/vmm/vmapi.h>
+
+#include "SecretKeyStore.h"
+#ifdef VBOX_COM_INPROC
+# include "ConsoleImpl.h"
+#else
+# include "MachineImpl.h"
+# include "VirtualBoxImpl.h"
+#endif
+
+
+/**
+ * Class handling encrypted and non encrypted SSM files.
+ */
+class SsmStream
+{
+ public:
+#ifdef VBOX_COM_INPROC
+ SsmStream(Console *pParent, PCVMMR3VTABLE pVMM, SecretKeyStore *pKeyStore, const Utf8Str &strKeyId, const Utf8Str &strKeyStore);
+#else
+ SsmStream(VirtualBox *pParent, SecretKeyStore *pKeyStore, const Utf8Str &strKeyId, const Utf8Str &strKeyStore);
+#endif
+ ~SsmStream();
+
+ /**
+ * Actually opens the stream for either reading or writing.
+ *
+ * @returns VBox status code.
+ * @param strFilename The filename of the saved state to open or create.
+ * @param fWrite Flag whether the stream should be opened for writing (true) or readonly (false).
+ * @param ppSsmHandle Where to store the SSM handle on success, don't call SSMR3Close() but the provided close() method.
+ */
+ int open(const Utf8Str &strFilename, bool fWrite, PSSMHANDLE *ppSsmHandle);
+
+ /**
+ * Opens the saved state file for reading, doesn't call SSMR3Open().
+ *
+ * @returns VBox status code.
+ * @param strFilename The filename of the saved state to open.
+ */
+ int open(const Utf8Str &strFilename);
+
+ /**
+ * Creates a new saved state file under the given path.
+ *
+ * @returns VBox status code.
+ * @param strFilename The filename of the saved state to create.
+ */
+ int create(const Utf8Str &strFilename);
+
+ /**
+ * Returns the pointer to the stream operations table after a succesful opening/creation.
+ *
+ * @return VBox status code.
+ * @param ppStrmOps Where to store the pointer to the stream operations table on success.
+ * @param ppvStrmOpsUser Where to store the pointer to the opaque user data on success.
+ */
+ int querySsmStrmOps(PCSSMSTRMOPS *ppStrmOps, void **ppvStrmOpsUser);
+
+ /**
+ * Closes an previously opened stream.
+ *
+ * @returns VBox status code.
+ */
+ int close(void);
+
+ private:
+
+ static DECLCALLBACK(int) i_ssmCryptoWrite(void *pvUser, uint64_t offStream, const void *pvBuf, size_t cbToWrite);
+ static DECLCALLBACK(int) i_ssmCryptoRead(void *pvUser, uint64_t offStream, void *pvBuf, size_t cbToRead, size_t *pcbRead);
+ static DECLCALLBACK(int) i_ssmCryptoSeek(void *pvUser, int64_t offSeek, unsigned uMethod, uint64_t *poffActual);
+ static DECLCALLBACK(uint64_t) i_ssmCryptoTell(void *pvUser);
+ static DECLCALLBACK(int) i_ssmCryptoSize(void *pvUser, uint64_t *pcb);
+ static DECLCALLBACK(int) i_ssmCryptoIsOk(void *pvUser);
+ static DECLCALLBACK(int) i_ssmCryptoClose(void *pvUser, bool fCancelled);
+
+#ifdef VBOX_COM_INPROC
+ Console *m_pParent;
+ PCVMMR3VTABLE m_pVMM;
+#else
+ VirtualBox *m_pParent;
+#endif
+ /** The key store for getting at passwords. */
+ SecretKeyStore *m_pKeyStore;
+ /** The key ID holding the password, empty if the saved state is not encrypted. */
+ Utf8Str m_strKeyId;
+ /** The keystore holding the encrypted DEK. */
+ Utf8Str m_strKeyStore;
+ /** The VFS file handle. */
+ RTVFSFILE m_hVfsFile;
+ /** The SSM handle when opened. */
+ PSSMHANDLE m_pSsm;
+ /** The SSM stream callbacks table. */
+ SSMSTRMOPS m_StrmOps;
+ /** The cryptographic interfacer. */
+ PCVBOXCRYPTOIF m_pCryptoIf;
+};
+
+#endif /* !MAIN_INCLUDED_CryptoUtils_h */
diff --git a/src/VBox/Main/include/DHCPConfigImpl.h b/src/VBox/Main/include/DHCPConfigImpl.h
new file mode 100644
index 00000000..c0504716
--- /dev/null
+++ b/src/VBox/Main/include/DHCPConfigImpl.h
@@ -0,0 +1,529 @@
+/* $Id: DHCPConfigImpl.h $ */
+/** @file
+ * VirtualBox Main - IDHCPConfig, IDHCPConfigGlobal, IDHCPConfigGroup, IDHCPConfigIndividual header.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_DHCPConfigImpl_h
+#define MAIN_INCLUDED_DHCPConfigImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "DHCPGlobalConfigWrap.h"
+#include "DHCPGroupConditionWrap.h"
+#include "DHCPGroupConfigWrap.h"
+#include "DHCPIndividualConfigWrap.h"
+#include <VBox/settings.h>
+
+
+class DHCPServer;
+class DHCPGroupConfig;
+
+
+/**
+ * Base class for the a DHCP configration layer.
+ *
+ * This does not inherit from DHCPConfigWrap because its children need to
+ * inherit from children of DHCPConfigWrap, which smells like trouble and thus
+ * wasn't even attempted. Instead, we have a hack for passing a pointer that we
+ * can call setError and such on.
+ */
+class DHCPConfig
+{
+protected:
+ /** Config scope (global, group, vm+nic, mac). */
+ DHCPConfigScope_T const m_enmScope;
+ /** Minimum lease time. */
+ ULONG m_secMinLeaseTime;
+ /** Default lease time. */
+ ULONG m_secDefaultLeaseTime;
+ /** Maximum lease time. */
+ ULONG m_secMaxLeaseTime;
+ /** List of options which are forced upon the client when available, whether
+ * requested by it or not. */
+ std::vector<DHCPOption_T> m_vecForcedOptions;
+ /** List of options which should be suppressed and not returned the the client
+ * when available and requested. */
+ std::vector<DHCPOption_T> m_vecSuppressedOptions;
+ /** DHCP option map. */
+ settings::DhcpOptionMap m_OptionMap;
+ /** The DHCP server parent (weak). */
+ DHCPServer * const m_pParent;
+ /** The DHCP server parent (weak). */
+ VirtualBox * const m_pVirtualBox;
+private:
+ /** For setError and such. */
+ VirtualBoxBase * const m_pHack;
+
+protected:
+ /** @name Constructors and destructors.
+ * @{ */
+ DHCPConfig(DHCPConfigScope_T a_enmScope, VirtualBoxBase *a_pHack)
+ : m_enmScope(a_enmScope), m_secMinLeaseTime(0), m_secDefaultLeaseTime(0), m_secMaxLeaseTime(0), m_OptionMap()
+ , m_pParent(NULL), m_pVirtualBox(NULL), m_pHack(a_pHack)
+ {}
+ DHCPConfig();
+ virtual ~DHCPConfig()
+ {}
+ HRESULT i_initWithDefaults(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent);
+ HRESULT i_initWithSettings(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent, const settings::DHCPConfig &rConfig);
+ /** @} */
+
+ /** @name IDHCPConfig properties
+ * @{ */
+ HRESULT i_getScope(DHCPConfigScope_T *aScope);
+ HRESULT i_getMinLeaseTime(ULONG *aMinLeaseTime);
+ HRESULT i_setMinLeaseTime(ULONG aMinLeaseTime);
+ HRESULT i_getDefaultLeaseTime(ULONG *aDefaultLeaseTime);
+ HRESULT i_setDefaultLeaseTime(ULONG aDefaultLeaseTime);
+ HRESULT i_getMaxLeaseTime(ULONG *aMaxLeaseTime);
+ HRESULT i_setMaxLeaseTime(ULONG aMaxLeaseTime);
+ HRESULT i_getForcedOptions(std::vector<DHCPOption_T> &aOptions);
+ HRESULT i_setForcedOptions(const std::vector<DHCPOption_T> &aOptions);
+ HRESULT i_getSuppressedOptions(std::vector<DHCPOption_T> &aOptions);
+ HRESULT i_setSuppressedOptions(const std::vector<DHCPOption_T> &aOptions);
+ /** @} */
+
+public:
+ DECLARE_TRANSLATE_METHODS(DHCPConfig)
+
+ /** @name IDHCPConfig methods
+ * @note public because the DHCPServer needs them for 6.0 interfaces.
+ * @todo Make protected again when IDHCPServer is cleaned up.
+ * @{ */
+ virtual HRESULT i_setOption(DHCPOption_T aOption, DHCPOptionEncoding_T aEncoding, const com::Utf8Str &aValue);
+
+ virtual HRESULT i_removeOption(DHCPOption_T aOption);
+ virtual HRESULT i_removeAllOptions();
+ HRESULT i_getOption(DHCPOption_T aOption, DHCPOptionEncoding_T *aEncoding, com::Utf8Str &aValue);
+ HRESULT i_getAllOptions(std::vector<DHCPOption_T> &aOptions, std::vector<DHCPOptionEncoding_T> &aEncodings,
+ std::vector<com::Utf8Str> &aValues);
+ virtual HRESULT i_remove();
+ /** @} */
+
+
+public:
+ HRESULT i_doWriteConfig();
+ HRESULT i_saveSettings(settings::DHCPConfig &a_rDst);
+ DHCPConfigScope_T i_getScope() const RT_NOEXCEPT { return m_enmScope; }
+ virtual void i_writeDhcpdConfig(xml::ElementNode *pElm);
+};
+
+
+/**
+ * Global DHCP configuration.
+ */
+class DHCPGlobalConfig : public DHCPGlobalConfigWrap, public DHCPConfig
+{
+public:
+ DECLARE_TRANSLATE_METHODS(DHCPGlobalConfig)
+
+ /** @name Constructors and destructors.
+ * @{ */
+ DHCPGlobalConfig()
+ : DHCPConfig(DHCPConfigScope_Global, this)
+ { }
+ HRESULT FinalConstruct()
+ {
+ return BaseFinalConstruct();
+ }
+ void FinalRelease()
+ {
+ uninit();
+ BaseFinalRelease();
+ }
+ HRESULT initWithDefaults(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent);
+ HRESULT initWithSettings(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent, const settings::DHCPConfig &rConfig);
+ void uninit();
+ /** @} */
+
+ HRESULT i_saveSettings(settings::DHCPConfig &a_rDst);
+ HRESULT i_getNetworkMask(com::Utf8Str &a_rDst);
+ HRESULT i_setNetworkMask(const com::Utf8Str &a_rSrc);
+
+protected:
+ /** @name wrapped IDHCPConfig properties
+ * @{ */
+ HRESULT getScope(DHCPConfigScope_T *aScope) RT_OVERRIDE { return i_getScope(aScope); }
+ HRESULT getMinLeaseTime(ULONG *aMinLeaseTime) RT_OVERRIDE { return i_getMinLeaseTime(aMinLeaseTime); }
+ HRESULT setMinLeaseTime(ULONG aMinLeaseTime) RT_OVERRIDE { return i_setMinLeaseTime(aMinLeaseTime); }
+ HRESULT getDefaultLeaseTime(ULONG *aDefaultLeaseTime) RT_OVERRIDE { return i_getDefaultLeaseTime(aDefaultLeaseTime); }
+ HRESULT setDefaultLeaseTime(ULONG aDefaultLeaseTime) RT_OVERRIDE { return i_setDefaultLeaseTime(aDefaultLeaseTime); }
+ HRESULT getMaxLeaseTime(ULONG *aMaxLeaseTime) RT_OVERRIDE { return i_getMaxLeaseTime(aMaxLeaseTime); }
+ HRESULT setMaxLeaseTime(ULONG aMaxLeaseTime) RT_OVERRIDE { return i_setMaxLeaseTime(aMaxLeaseTime); }
+ HRESULT getForcedOptions(std::vector<DHCPOption_T> &aOptions) RT_OVERRIDE { return i_getForcedOptions(aOptions); }
+ HRESULT setForcedOptions(const std::vector<DHCPOption_T> &aOptions) RT_OVERRIDE { return i_setForcedOptions(aOptions); }
+ HRESULT getSuppressedOptions(std::vector<DHCPOption_T> &aOptions) RT_OVERRIDE { return i_getSuppressedOptions(aOptions); }
+ HRESULT setSuppressedOptions(const std::vector<DHCPOption_T> &aOptions) RT_OVERRIDE { return i_setSuppressedOptions(aOptions); }
+ /** @} */
+
+ /** @name wrapped IDHCPConfig methods
+ * @{ */
+ HRESULT setOption(DHCPOption_T aOption, DHCPOptionEncoding_T aEncoding, const com::Utf8Str &aValue) RT_OVERRIDE
+ {
+ return i_setOption(aOption, aEncoding, aValue);
+ }
+
+ HRESULT removeOption(DHCPOption_T aOption) RT_OVERRIDE
+ {
+ return i_removeOption(aOption);
+ }
+
+ HRESULT removeAllOptions() RT_OVERRIDE
+ {
+ return i_removeAllOptions();
+ }
+
+ HRESULT getOption(DHCPOption_T aOption, DHCPOptionEncoding_T *aEncoding, com::Utf8Str &aValue) RT_OVERRIDE
+ {
+ return i_getOption(aOption, aEncoding, aValue);
+ }
+
+ HRESULT getAllOptions(std::vector<DHCPOption_T> &aOptions, std::vector<DHCPOptionEncoding_T> &aEncodings,
+ std::vector<com::Utf8Str> &aValues) RT_OVERRIDE
+ {
+ return i_getAllOptions(aOptions, aEncodings, aValues);
+ }
+
+ HRESULT remove() RT_OVERRIDE
+ {
+ return i_remove();
+ }
+ /** @} */
+
+public:
+ HRESULT i_setOption(DHCPOption_T aOption, DHCPOptionEncoding_T aEncoding, const com::Utf8Str &aValue) RT_OVERRIDE;
+ HRESULT i_removeOption(DHCPOption_T aOption) RT_OVERRIDE;
+ HRESULT i_removeAllOptions() RT_OVERRIDE;
+ HRESULT i_remove() RT_OVERRIDE;
+};
+
+
+/**
+ * DHCP Group inclusion/exclusion condition.
+ */
+class DHCPGroupCondition : public DHCPGroupConditionWrap
+{
+private:
+ /** Inclusive or exclusive condition. */
+ bool m_fInclusive;
+ /** The condition type (or how m_strValue should be interpreted). */
+ DHCPGroupConditionType_T m_enmType;
+ /** The value. Interpreted according to m_enmType. */
+ com::Utf8Str m_strValue;
+ /** Pointer to the parent (weak). */
+ DHCPGroupConfig *m_pParent;
+
+public:
+ DECLARE_TRANSLATE_METHODS(DHCPGroupCondition)
+
+ /** @name Constructors and destructors.
+ * @{ */
+ DHCPGroupCondition()
+ : m_enmType(DHCPGroupConditionType_MAC)
+ , m_pParent(NULL)
+ {}
+ HRESULT FinalConstruct()
+ {
+ return BaseFinalConstruct();
+ }
+ void FinalRelease()
+ {
+ uninit();
+ BaseFinalRelease();
+ }
+ HRESULT initWithDefaults(DHCPGroupConfig *a_pParent, bool a_fInclusive, DHCPGroupConditionType_T a_enmType,
+ const com::Utf8Str a_strValue);
+ HRESULT initWithSettings(DHCPGroupConfig *a_pParent, const settings::DHCPGroupCondition &a_rSrc);
+ void uninit();
+ /** @} */
+
+ HRESULT i_saveSettings(settings::DHCPGroupCondition &a_rDst);
+ static HRESULT i_validateTypeAndValue(DHCPGroupConditionType_T enmType, com::Utf8Str const &strValue,
+ VirtualBoxBase *pErrorDst);
+
+ /** @name Internal accessors
+ * @{ */
+ bool i_getInclusive() const RT_NOEXCEPT { return m_fInclusive; }
+ DHCPGroupConditionType_T i_getType() const RT_NOEXCEPT { return m_enmType; }
+ com::Utf8Str const &i_getValue() const RT_NOEXCEPT { return m_strValue; }
+ /** @} */
+
+protected:
+ /** @name Wrapped IDHCPGroupCondition properties
+ * @{ */
+ HRESULT getInclusive(BOOL *aInclusive) RT_OVERRIDE;
+ HRESULT setInclusive(BOOL aInclusive) RT_OVERRIDE;
+ HRESULT getType(DHCPGroupConditionType_T *aType) RT_OVERRIDE;
+ HRESULT setType(DHCPGroupConditionType_T aType) RT_OVERRIDE;
+ HRESULT getValue(com::Utf8Str &aValue) RT_OVERRIDE;
+ HRESULT setValue(const com::Utf8Str &aValue) RT_OVERRIDE;
+ /** @} */
+
+ /** @name Wrapped IDHCPGroupCondition methods
+ * @{ */
+ HRESULT remove() RT_OVERRIDE;
+ /** @} */
+};
+
+
+/**
+ * Group configuration.
+ */
+class DHCPGroupConfig : public DHCPGroupConfigWrap, public DHCPConfig
+{
+private:
+ /** Group name. */
+ com::Utf8Str m_strName;
+ /** Group membership conditions. */
+ std::vector<ComObjPtr<DHCPGroupCondition> > m_Conditions;
+ /** Iterator for m_Conditions. */
+ typedef std::vector<ComObjPtr<DHCPGroupCondition> >::iterator ConditionsIterator;
+
+public:
+ DECLARE_TRANSLATE_METHODS(DHCPGroupConfig)
+
+ /** @name Constructors and destructors.
+ * @{ */
+ DHCPGroupConfig()
+ : DHCPConfig(DHCPConfigScope_Group, this)
+ { }
+ HRESULT FinalConstruct()
+ {
+ return BaseFinalConstruct();
+ }
+ void FinalRelease()
+ {
+ uninit();
+ BaseFinalRelease();
+ }
+ HRESULT initWithDefaults(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent, const com::Utf8Str &a_rName);
+ HRESULT initWithSettings(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent, const settings::DHCPGroupConfig &a_rSrc);
+ void uninit();
+ /** @} */
+
+ HRESULT i_saveSettings(settings::DHCPGroupConfig &a_rDst);
+ HRESULT i_removeCondition(DHCPGroupCondition *a_pCondition);
+ void i_writeDhcpdConfig(xml::ElementNode *a_pElmGroup) RT_OVERRIDE;
+
+protected:
+ /** @name Wrapped IDHCPConfig properties
+ * @{ */
+ HRESULT getScope(DHCPConfigScope_T *aScope) RT_OVERRIDE { return i_getScope(aScope); }
+ HRESULT getMinLeaseTime(ULONG *aMinLeaseTime) RT_OVERRIDE { return i_getMinLeaseTime(aMinLeaseTime); }
+ HRESULT setMinLeaseTime(ULONG aMinLeaseTime) RT_OVERRIDE { return i_setMinLeaseTime(aMinLeaseTime); }
+ HRESULT getDefaultLeaseTime(ULONG *aDefaultLeaseTime) RT_OVERRIDE { return i_getDefaultLeaseTime(aDefaultLeaseTime); }
+ HRESULT setDefaultLeaseTime(ULONG aDefaultLeaseTime) RT_OVERRIDE { return i_setDefaultLeaseTime(aDefaultLeaseTime); }
+ HRESULT getMaxLeaseTime(ULONG *aMaxLeaseTime) RT_OVERRIDE { return i_getMaxLeaseTime(aMaxLeaseTime); }
+ HRESULT setMaxLeaseTime(ULONG aMaxLeaseTime) RT_OVERRIDE { return i_setMaxLeaseTime(aMaxLeaseTime); }
+ HRESULT getForcedOptions(std::vector<DHCPOption_T> &aOptions) RT_OVERRIDE { return i_getForcedOptions(aOptions); }
+ HRESULT setForcedOptions(const std::vector<DHCPOption_T> &aOptions) RT_OVERRIDE { return i_setForcedOptions(aOptions); }
+ HRESULT getSuppressedOptions(std::vector<DHCPOption_T> &aOptions) RT_OVERRIDE { return i_getSuppressedOptions(aOptions); }
+ HRESULT setSuppressedOptions(const std::vector<DHCPOption_T> &aOptions) RT_OVERRIDE { return i_setSuppressedOptions(aOptions); }
+ /** @} */
+
+ /** @name Wrapped IDHCPGroupConfig properties
+ * @{ */
+ HRESULT getName(com::Utf8Str &aName) RT_OVERRIDE;
+ HRESULT setName(const com::Utf8Str &aName) RT_OVERRIDE;
+ HRESULT getConditions(std::vector<ComPtr<IDHCPGroupCondition> > &aConditions) RT_OVERRIDE;
+ /** @} */
+
+ /** @name Wrapped IDHCPConfig methods
+ * @{ */
+ HRESULT setOption(DHCPOption_T aOption, DHCPOptionEncoding_T aEncoding, const com::Utf8Str &aValue) RT_OVERRIDE
+ {
+ return i_setOption(aOption, aEncoding, aValue);
+ }
+
+ HRESULT removeOption(DHCPOption_T aOption) RT_OVERRIDE
+ {
+ return i_removeOption(aOption);
+ }
+
+ HRESULT removeAllOptions() RT_OVERRIDE
+ {
+ return i_removeAllOptions();
+ }
+
+ HRESULT getOption(DHCPOption_T aOption, DHCPOptionEncoding_T *aEncoding, com::Utf8Str &aValue) RT_OVERRIDE
+ {
+ return i_getOption(aOption, aEncoding, aValue);
+ }
+
+ HRESULT getAllOptions(std::vector<DHCPOption_T> &aOptions, std::vector<DHCPOptionEncoding_T> &aEncodings,
+ std::vector<com::Utf8Str> &aValues) RT_OVERRIDE
+ {
+ return i_getAllOptions(aOptions, aEncodings, aValues);
+ }
+
+ HRESULT remove() RT_OVERRIDE
+ {
+ return i_remove();
+ }
+ /** @} */
+
+ /** @name Wrapped IDHCPGroupConfig methods
+ * @{ */
+ HRESULT addCondition(BOOL aInclusive, DHCPGroupConditionType_T aType, const com::Utf8Str &aValue,
+ ComPtr<IDHCPGroupCondition> &aCondition) RT_OVERRIDE;
+ HRESULT removeAllConditions() RT_OVERRIDE;
+ /** @} */
+};
+
+
+/**
+ * Individual DHCP configuration.
+ */
+class DHCPIndividualConfig : public DHCPIndividualConfigWrap, public DHCPConfig
+{
+private:
+ /** The MAC address or all zeros. */
+ RTMAC m_MACAddress;
+ /** The VM ID or all zeros. */
+ com::Guid const m_idMachine;
+ /** The VM NIC slot number, or ~(ULONG)0. */
+ ULONG const m_uSlot;
+ /** This is part of a hack to resolve the MAC address for
+ * DHCPConfigScope_MachineNIC instances. If non-zero, we m_MACAddress is valid.
+ * To deal with the impossibly theoretical scenario that the DHCP server is
+ * being started by more than one thread, this is a version number and not just
+ * a boolean indicator. */
+ uint32_t volatile m_uMACAddressResolvedVersion;
+
+ /** The fixed IPv4 address, empty if dynamic. */
+ com::Utf8Str m_strFixedAddress;
+
+public:
+ DECLARE_TRANSLATE_METHODS(DHCPIndividualConfig)
+
+ /** @name Constructors and destructors.
+ * @{ */
+ DHCPIndividualConfig()
+ : DHCPConfig(DHCPConfigScope_MAC, this)
+ , m_uSlot(~(ULONG)0)
+ , m_uMACAddressResolvedVersion(0)
+ {
+ RT_ZERO(m_MACAddress);
+ }
+ HRESULT FinalConstruct()
+ {
+ return BaseFinalConstruct();
+ }
+ void FinalRelease()
+ {
+ uninit();
+ BaseFinalRelease();
+ }
+ HRESULT initWithMachineIdAndSlot(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent, com::Guid const &a_idMachine,
+ ULONG a_uSlot, uint32_t a_uMACAddressVersion);
+ HRESULT initWithMACAddress(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent, PCRTMAC a_pMACAddress);
+ HRESULT initWithSettingsAndMachineIdAndSlot(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent,
+ const settings::DHCPIndividualConfig &rData, com::Guid const &a_idMachine,
+ ULONG a_uSlot, uint32_t a_uMACAddressVersion);
+ HRESULT initWithSettingsAndMACAddress(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent,
+ const settings::DHCPIndividualConfig &rData, PCRTMAC a_pMACAddress);
+ void uninit();
+ /** @} */
+
+ /** @name Internal methods that are public for various reasons
+ * @{ */
+ HRESULT i_saveSettings(settings::DHCPIndividualConfig &a_rDst);
+ RTMAC const &i_getMACAddress() const RT_NOEXCEPT { return m_MACAddress; }
+ com::Guid const &i_getMachineId() const RT_NOEXCEPT { return m_idMachine; }
+ ULONG i_getSlot() const RT_NOEXCEPT { return m_uSlot; }
+ HRESULT i_getMachineMAC(PRTMAC pMACAddress);
+
+ HRESULT i_resolveMACAddress(uint32_t uVersion);
+ /** This is used to avoid producing bogus Dhcpd configuration elements. */
+ bool i_isMACAddressResolved(uint32_t uVersion) const
+ {
+ return m_enmScope != DHCPConfigScope_MachineNIC || (int32_t)(m_uMACAddressResolvedVersion - uVersion) >= 0;
+ }
+ void i_writeDhcpdConfig(xml::ElementNode *pElm) RT_OVERRIDE;
+ /** @} */
+
+protected:
+ /** @name wrapped IDHCPConfig properties
+ * @{ */
+ HRESULT getScope(DHCPConfigScope_T *aScope) RT_OVERRIDE { return i_getScope(aScope); }
+ HRESULT getMinLeaseTime(ULONG *aMinLeaseTime) RT_OVERRIDE { return i_getMinLeaseTime(aMinLeaseTime); }
+ HRESULT setMinLeaseTime(ULONG aMinLeaseTime) RT_OVERRIDE { return i_setMinLeaseTime(aMinLeaseTime); }
+ HRESULT getDefaultLeaseTime(ULONG *aDefaultLeaseTime) RT_OVERRIDE { return i_getDefaultLeaseTime(aDefaultLeaseTime); }
+ HRESULT setDefaultLeaseTime(ULONG aDefaultLeaseTime) RT_OVERRIDE { return i_setDefaultLeaseTime(aDefaultLeaseTime); }
+ HRESULT getMaxLeaseTime(ULONG *aMaxLeaseTime) RT_OVERRIDE { return i_getMaxLeaseTime(aMaxLeaseTime); }
+ HRESULT setMaxLeaseTime(ULONG aMaxLeaseTime) RT_OVERRIDE { return i_setMaxLeaseTime(aMaxLeaseTime); }
+ HRESULT getForcedOptions(std::vector<DHCPOption_T> &aOptions) RT_OVERRIDE { return i_getForcedOptions(aOptions); }
+ HRESULT setForcedOptions(const std::vector<DHCPOption_T> &aOptions) RT_OVERRIDE { return i_setForcedOptions(aOptions); }
+ HRESULT getSuppressedOptions(std::vector<DHCPOption_T> &aOptions) RT_OVERRIDE { return i_getSuppressedOptions(aOptions); }
+ HRESULT setSuppressedOptions(const std::vector<DHCPOption_T> &aOptions) RT_OVERRIDE { return i_setSuppressedOptions(aOptions); }
+ /** @} */
+
+ /** @name wrapped IDHCPConfig methods
+ * @{ */
+ HRESULT setOption(DHCPOption_T aOption, DHCPOptionEncoding_T aEncoding, const com::Utf8Str &aValue) RT_OVERRIDE
+ {
+ return i_setOption(aOption, aEncoding, aValue);
+ }
+
+ HRESULT removeOption(DHCPOption_T aOption) RT_OVERRIDE
+ {
+ return i_removeOption(aOption);
+ }
+
+ HRESULT removeAllOptions() RT_OVERRIDE
+ {
+ return i_removeAllOptions();
+ }
+
+ HRESULT getOption(DHCPOption_T aOption, DHCPOptionEncoding_T *aEncoding, com::Utf8Str &aValue) RT_OVERRIDE
+ {
+ return i_getOption(aOption, aEncoding, aValue);
+ }
+
+ HRESULT getAllOptions(std::vector<DHCPOption_T> &aOptions, std::vector<DHCPOptionEncoding_T> &aEncodings,
+ std::vector<com::Utf8Str> &aValues) RT_OVERRIDE
+ {
+ return i_getAllOptions(aOptions, aEncodings, aValues);
+ }
+
+ HRESULT remove() RT_OVERRIDE
+ {
+ return i_remove();
+ }
+ /** @} */
+
+ /** @name IDHCPIndividualConfig properties
+ * @{ */
+ HRESULT getMACAddress(com::Utf8Str &aMacAddress) RT_OVERRIDE;
+ HRESULT getMachineId(com::Guid &aId) RT_OVERRIDE;
+ HRESULT getSlot(ULONG *aSlot) RT_OVERRIDE;
+ HRESULT getFixedAddress(com::Utf8Str &aFixedAddress) RT_OVERRIDE;
+ HRESULT setFixedAddress(const com::Utf8Str &aFixedAddress) RT_OVERRIDE;
+ /** @} */
+};
+
+#endif /* !MAIN_INCLUDED_DHCPConfigImpl_h */
+
diff --git a/src/VBox/Main/include/DHCPServerImpl.h b/src/VBox/Main/include/DHCPServerImpl.h
new file mode 100644
index 00000000..cac13d18
--- /dev/null
+++ b/src/VBox/Main/include/DHCPServerImpl.h
@@ -0,0 +1,130 @@
+/* $Id: DHCPServerImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_DHCPServerImpl_h
+#define MAIN_INCLUDED_DHCPServerImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "DHCPServerWrap.h"
+#include <map>
+
+namespace settings
+{
+ struct DHCPServer;
+ struct DhcpOptValue;
+ typedef std::map<DHCPOption_T, DhcpOptValue> DhcpOptionMap;
+}
+
+class DHCPConfig;
+class DHCPIndividualConfig;
+
+/**
+ * A DHCP server for internal host-only & NAT networks.
+ *
+ * Old notes:
+ *
+ * for server configuration needs, it's perhaps better to use (VM,slot) pair
+ * (vm-name, slot) <----> (MAC)
+ *
+ * but for client configuration, when server will have MACs at hand, it'd be
+ * easier to requiest options by MAC.
+ * (MAC) <----> (option-list)
+ *
+ * Doubts: What should be done if MAC changed for (vm-name, slot), when syncing should?
+ * XML: serialization of dependecy (DHCP options) - (VM,slot) shouldn't be done via MAC in
+ * the middle.
+ */
+class ATL_NO_VTABLE DHCPServer
+ : public DHCPServerWrap
+{
+public:
+ /** @name Constructors and destructors
+ * @{ */
+ DECLARE_COMMON_CLASS_METHODS(DHCPServer)
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ HRESULT init(VirtualBox *aVirtualBox, const com::Utf8Str &aName);
+ HRESULT init(VirtualBox *aVirtualBox, const settings::DHCPServer &data);
+ void uninit();
+ /** @} */
+
+ /** @name Public internal methods.
+ * @{ */
+ HRESULT i_saveSettings(settings::DHCPServer &data);
+ HRESULT i_removeConfig(DHCPConfig *pConfig, DHCPConfigScope_T enmScope);
+ /** @} */
+
+private:
+ /** @name IDHCPServer Properties
+ * @{ */
+ HRESULT getEventSource(ComPtr<IEventSource> &aEventSource) RT_OVERRIDE;
+ HRESULT getEnabled(BOOL *aEnabled) RT_OVERRIDE;
+ HRESULT setEnabled(BOOL aEnabled) RT_OVERRIDE;
+ HRESULT getIPAddress(com::Utf8Str &aIPAddress) RT_OVERRIDE;
+ HRESULT getNetworkMask(com::Utf8Str &aNetworkMask) RT_OVERRIDE;
+ HRESULT getNetworkName(com::Utf8Str &aName) RT_OVERRIDE;
+ HRESULT getLowerIP(com::Utf8Str &aIPAddress) RT_OVERRIDE;
+ HRESULT getUpperIP(com::Utf8Str &aIPAddress) RT_OVERRIDE;
+ HRESULT setConfiguration(const com::Utf8Str &aIPAddress, const com::Utf8Str &aNetworkMask,
+ const com::Utf8Str &aFromIPAddress, const com::Utf8Str &aToIPAddress) RT_OVERRIDE;
+ HRESULT getGlobalConfig(ComPtr<IDHCPGlobalConfig> &aGlobalConfig) RT_OVERRIDE;
+ HRESULT getGroupConfigs(std::vector<ComPtr<IDHCPGroupConfig> > &aGroupConfigs) RT_OVERRIDE;
+ HRESULT getIndividualConfigs(std::vector<ComPtr<IDHCPIndividualConfig> > &aIndividualConfigs) ;
+ /** @} */
+
+ /** @name IDHCPServer Methods
+ * @{ */
+ HRESULT start(const com::Utf8Str &aTrunkName, const com::Utf8Str &aTrunkType) RT_OVERRIDE;
+ HRESULT stop() RT_OVERRIDE;
+ HRESULT restart() RT_OVERRIDE;
+ HRESULT findLeaseByMAC(const com::Utf8Str &aMac, LONG aType, com::Utf8Str &aAddress, com::Utf8Str &aState,
+ LONG64 *aIssued, LONG64 *aExpire) RT_OVERRIDE;
+ HRESULT getConfig(DHCPConfigScope_T aScope, const com::Utf8Str &aName, ULONG aSlot, BOOL aMayAdd,
+ ComPtr<IDHCPConfig> &aConfig) RT_OVERRIDE;
+ /** @} */
+
+ /** @name Helpers
+ * @{ */
+ HRESULT i_doSaveSettings();
+ HRESULT i_calcLeasesConfigAndLogFilenames(const com::Utf8Str &aNetwork) RT_NOEXCEPT;
+ HRESULT i_writeDhcpdConfig(const char *pszFilename, uint32_t uMACAddressVersion) RT_NOEXCEPT;
+
+ HRESULT i_vmNameToIdAndValidateSlot(const com::Utf8Str &aVmName, ULONG a_uSlot, com::Guid &idMachine);
+ HRESULT i_vmNameAndSlotToConfig(const com::Utf8Str &a_strVmName, ULONG a_uSlot, bool a_fCreateIfNeeded,
+ ComObjPtr<DHCPIndividualConfig> &a_rPtrConfig);
+ /** @} */
+
+ /** Private data */
+ struct Data;
+ /** Private data. */
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_DHCPServerImpl_h */
diff --git a/src/VBox/Main/include/DataStreamImpl.h b/src/VBox/Main/include/DataStreamImpl.h
new file mode 100644
index 00000000..8239dcd7
--- /dev/null
+++ b/src/VBox/Main/include/DataStreamImpl.h
@@ -0,0 +1,78 @@
+/* $Id: DataStreamImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2018-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_DataStreamImpl_h
+#define MAIN_INCLUDED_DataStreamImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "DataStreamWrap.h"
+
+#include <iprt/circbuf.h>
+#include <iprt/semaphore.h>
+
+class ATL_NO_VTABLE DataStream
+ : public DataStreamWrap
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(DataStream)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ HRESULT init(unsigned long aBufferSize);
+ void uninit();
+
+ /// Feed data into the stream, used by the stream source.
+ /// Blocks if the internal buffer cannot take anything, otherwise
+ /// as much as the internal buffer can hold is taken (if smaller
+ /// than @a cbWrite). Modeled after RTStrmWriteEx.
+ int i_write(const void *pvBuf, size_t cbWrite, size_t *pcbWritten);
+
+ /// Marks the end of the stream.
+ int i_close();
+
+private:
+ // wrapped IDataStream attributes and methods
+ HRESULT getReadSize(ULONG *aReadSize);
+ HRESULT read(ULONG aSize, ULONG aTimeoutMS, std::vector<BYTE> &aData);
+
+private:
+ /** The temporary buffer the conversion process writes into and the user reads from. */
+ PRTCIRCBUF m_pBuffer;
+ /** Event semaphore for waiting until data is available. */
+ RTSEMEVENT m_hSemEvtDataAvail;
+ /** Event semaphore for waiting until there is room in the buffer for writing. */
+ RTSEMEVENT m_hSemEvtBufSpcAvail;
+ /** Flag whether the end of stream flag is set. */
+ bool m_fEos;
+};
+
+#endif /* !MAIN_INCLUDED_DataStreamImpl_h */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/DisplayImpl.h b/src/VBox/Main/include/DisplayImpl.h
new file mode 100644
index 00000000..a17b626e
--- /dev/null
+++ b/src/VBox/Main/include/DisplayImpl.h
@@ -0,0 +1,562 @@
+/* $Id: DisplayImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_DisplayImpl_h
+#define MAIN_INCLUDED_DisplayImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "SchemaDefs.h"
+
+#include <iprt/semaphore.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBoxVideo.h>
+#include <VBox/vmm/pdmifs.h>
+#include <VBox/VMMDev.h> /* For struct VMMDevDisplayDef - why is it in that file? */
+#include "DisplayWrap.h"
+
+#include "DisplaySourceBitmapWrap.h"
+#include "GuestScreenInfoWrap.h"
+
+
+class Console;
+
+typedef struct _DISPLAYFBINFO
+{
+ /* The following 3 fields (u32Offset, u32MaxFramebufferSize and u32InformationSize)
+ * are not used by the current HGSMI. They are needed for backward compatibility with
+ * pre-HGSMI additions.
+ */
+ uint32_t u32Offset;
+ uint32_t u32MaxFramebufferSize;
+ uint32_t u32InformationSize;
+
+ ComPtr<IFramebuffer> pFramebuffer;
+ com::Guid framebufferId;
+ ComPtr<IDisplaySourceBitmap> pSourceBitmap;
+ bool fDisabled;
+
+ uint32_t u32Caps;
+
+ struct
+ {
+ ComPtr<IDisplaySourceBitmap> pSourceBitmap;
+ uint8_t *pu8Address;
+ uint32_t cbLine;
+ } updateImage;
+
+ LONG xOrigin;
+ LONG yOrigin;
+
+ ULONG w;
+ ULONG h;
+
+ uint16_t u16BitsPerPixel;
+ uint8_t *pu8FramebufferVRAM;
+ uint32_t u32LineSize;
+
+ uint16_t flags;
+
+ VBOXVIDEOINFOHOSTEVENTS *pHostEvents;
+
+ /** The framebuffer has default format and must be updates immediately. */
+ bool fDefaultFormat;
+
+#ifdef VBOX_WITH_HGSMI
+ bool fVBVAEnabled;
+ bool fVBVAForceResize;
+ VBVAHOSTFLAGS RT_UNTRUSTED_VOLATILE_GUEST *pVBVAHostFlags;
+#endif /* VBOX_WITH_HGSMI */
+
+#ifdef VBOX_WITH_RECORDING
+ struct
+ {
+ ComPtr<IDisplaySourceBitmap> pSourceBitmap;
+ } Recording;
+#endif /* VBOX_WITH_RECORDING */
+
+ /** Description of the currently plugged monitor with preferred mode,
+ * a.k.a the last mode hint sent. */
+ struct VMMDevDisplayDef monitorDesc;
+} DISPLAYFBINFO;
+
+/* The legacy VBVA (VideoAccel) data.
+ *
+ * Backward compatibility with the Guest Additions 3.x or older.
+ */
+typedef struct VIDEOACCEL
+{
+ VBVAMEMORY *pVbvaMemory;
+ bool fVideoAccelEnabled;
+
+ uint8_t *pu8VbvaPartial;
+ uint32_t cbVbvaPartial;
+
+ /* Old Guest Additions (3.x and older) use both VMMDev and DevVGA refresh timer
+ * to process the VBVABUFFER memory. Therefore the legacy VBVA (VideoAccel) host
+ * code can be executed concurrently by VGA refresh timer and the guest VMMDev
+ * request in SMP VMs. The semaphore serialized this.
+ */
+ RTSEMXROADS hXRoadsVideoAccel;
+
+} VIDEOACCEL;
+
+class DisplayMouseInterface
+{
+public:
+ virtual ~DisplayMouseInterface() { }
+ virtual HRESULT i_getScreenResolution(ULONG cScreen, ULONG *pcx,
+ ULONG *pcy, ULONG *pcBPP, LONG *pXOrigin, LONG *pYOrigin) = 0;
+ virtual void i_getFramebufferDimensions(int32_t *px1, int32_t *py1,
+ int32_t *px2, int32_t *py2) = 0;
+ virtual HRESULT i_reportHostCursorCapabilities(uint32_t fCapabilitiesAdded, uint32_t fCapabilitiesRemoved) = 0;
+ virtual HRESULT i_reportHostCursorPosition(int32_t x, int32_t y, bool fOutOfRange) = 0;
+ virtual bool i_isInputMappingSet(void) = 0;
+};
+
+class VMMDev;
+
+class ATL_NO_VTABLE Display :
+ public DisplayWrap,
+ public DisplayMouseInterface
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(Display)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Console *aParent);
+ void uninit();
+ int i_registerSSM(PUVM pUVM);
+
+ // public methods only for internal purposes
+ unsigned i_getMonitorCount() { return mcMonitors; }
+ int i_handleDisplayResize(unsigned uScreenId, uint32_t bpp, void *pvVRAM,
+ uint32_t cbLine, uint32_t w, uint32_t h, uint16_t flags,
+ int32_t xOrigin, int32_t yOrigin, bool fVGAResize);
+ void i_handleDisplayUpdate(unsigned uScreenId, int x, int y, int w, int h);
+ void i_handleUpdateVMMDevSupportsGraphics(bool fSupportsGraphics);
+ void i_handleUpdateGuestVBVACapabilities(uint32_t fNewCapabilities);
+ void i_handleUpdateVBVAInputMapping(int32_t xOrigin, int32_t yOrigin, uint32_t cx, uint32_t cy);
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ int i_handleVHWACommandProcess(int enmCmd, bool fGuestCmd, VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCommand);
+#endif
+ int i_handle3DNotifyProcess(VBOX3DNOTIFY *p3DNotify);
+
+ int i_saveVisibleRegion(uint32_t cRect, PRTRECT pRect);
+ int i_handleSetVisibleRegion(uint32_t cRect, PRTRECT pRect);
+ int i_handleUpdateMonitorPositions(uint32_t cPositions, PCRTPOINT paPositions);
+ int i_handleQueryVisibleRegion(uint32_t *pcRects, PRTRECT paRects);
+
+ void i_VRDPConnectionEvent(bool fConnect);
+ void i_VideoAccelVRDP(bool fEnable, int c);
+
+ /* Legacy video acceleration requests coming from the VGA refresh timer. */
+ int VideoAccelEnableVGA(bool fEnable, VBVAMEMORY *pVbvaMemory);
+
+ /* Legacy video acceleration requests coming from VMMDev. */
+ int VideoAccelEnableVMMDev(bool fEnable, VBVAMEMORY *pVbvaMemory);
+ void VideoAccelFlushVMMDev(void);
+
+ void i_UpdateDeviceCursorCapabilities(void);
+
+#ifdef VBOX_WITH_RECORDING
+ int i_recordingInvalidate(void);
+ void i_recordingScreenChanged(unsigned uScreenId);
+#endif
+
+ void i_notifyPowerDown(void);
+
+ // DisplayMouseInterface methods
+ virtual HRESULT i_getScreenResolution(ULONG cScreen, ULONG *pcx,
+ ULONG *pcy, ULONG *pcBPP, LONG *pXOrigin, LONG *pYOrigin)
+ {
+ return getScreenResolution(cScreen, pcx, pcy, pcBPP, pXOrigin, pYOrigin, NULL);
+ }
+ virtual void i_getFramebufferDimensions(int32_t *px1, int32_t *py1,
+ int32_t *px2, int32_t *py2);
+ virtual HRESULT i_reportHostCursorCapabilities(uint32_t fCapabilitiesAdded, uint32_t fCapabilitiesRemoved);
+ virtual HRESULT i_reportHostCursorPosition(int32_t x, int32_t y, bool fOutOfRange);
+ virtual bool i_isInputMappingSet(void)
+ {
+ return cxInputMapping != 0 && cyInputMapping != 0;
+ }
+
+ static const PDMDRVREG DrvReg;
+
+private:
+ // Wrapped IDisplay properties
+ virtual HRESULT getGuestScreenLayout(std::vector<ComPtr<IGuestScreenInfo> > &aGuestScreenLayout);
+
+ // Wrapped IDisplay methods
+ virtual HRESULT getScreenResolution(ULONG aScreenId,
+ ULONG *aWidth,
+ ULONG *aHeight,
+ ULONG *aBitsPerPixel,
+ LONG *aXOrigin,
+ LONG *aYOrigin,
+ GuestMonitorStatus_T *aGuestMonitorStatus);
+ virtual HRESULT attachFramebuffer(ULONG aScreenId,
+ const ComPtr<IFramebuffer> &aFramebuffer,
+ com::Guid &aId);
+ virtual HRESULT detachFramebuffer(ULONG aScreenId,
+ const com::Guid &aId);
+ virtual HRESULT queryFramebuffer(ULONG aScreenId,
+ ComPtr<IFramebuffer> &aFramebuffer);
+ virtual HRESULT setVideoModeHint(ULONG aDisplay,
+ BOOL aEnabled,
+ BOOL aChangeOrigin,
+ LONG aOriginX,
+ LONG aOriginY,
+ ULONG aWidth,
+ ULONG aHeight,
+ ULONG aBitsPerPixel,
+ BOOL aNotify);
+ virtual HRESULT getVideoModeHint(ULONG aDisplay,
+ BOOL *aEnabled,
+ BOOL *aChangeOrigin,
+ LONG *aOriginX,
+ LONG *aOriginY,
+ ULONG *aWidth,
+ ULONG *aHeight,
+ ULONG *aBitsPerPixel);
+ virtual HRESULT setSeamlessMode(BOOL aEnabled);
+ virtual HRESULT takeScreenShot(ULONG aScreenId,
+ BYTE *aAddress,
+ ULONG aWidth,
+ ULONG aHeight,
+ BitmapFormat_T aBitmapFormat);
+ virtual HRESULT takeScreenShotToArray(ULONG aScreenId,
+ ULONG aWidth,
+ ULONG aHeight,
+ BitmapFormat_T aBitmapFormat,
+ std::vector<BYTE> &aScreenData);
+ virtual HRESULT drawToScreen(ULONG aScreenId,
+ BYTE *aAddress,
+ ULONG aX,
+ ULONG aY,
+ ULONG aWidth,
+ ULONG aHeight);
+ virtual HRESULT invalidateAndUpdate();
+ virtual HRESULT invalidateAndUpdateScreen(ULONG aScreenId);
+ virtual HRESULT completeVHWACommand(BYTE *aCommand);
+ virtual HRESULT viewportChanged(ULONG aScreenId,
+ ULONG aX,
+ ULONG aY,
+ ULONG aWidth,
+ ULONG aHeight);
+ virtual HRESULT querySourceBitmap(ULONG aScreenId,
+ ComPtr<IDisplaySourceBitmap> &aDisplaySourceBitmap);
+ virtual HRESULT notifyScaleFactorChange(ULONG aScreenId,
+ ULONG aScaleFactorWMultiplied,
+ ULONG aScaleFactorHMultiplied);
+ virtual HRESULT notifyHiDPIOutputPolicyChange(BOOL fUnscaledHiDPI);
+ virtual HRESULT setScreenLayout(ScreenLayoutMode_T aScreenLayoutMode,
+ const std::vector<ComPtr<IGuestScreenInfo> > &aGuestScreenInfo);
+ virtual HRESULT detachScreens(const std::vector<LONG> &aScreenIds);
+ virtual HRESULT createGuestScreenInfo(ULONG aDisplay,
+ GuestMonitorStatus_T aStatus,
+ BOOL aPrimary,
+ BOOL aChangeOrigin,
+ LONG aOriginX,
+ LONG aOriginY,
+ ULONG aWidth,
+ ULONG aHeight,
+ ULONG aBitsPerPixel,
+ ComPtr<IGuestScreenInfo> &aGuestScreenInfo);
+
+ // Wrapped IEventListener properties
+
+ // Wrapped IEventListener methods
+ virtual HRESULT handleEvent(const ComPtr<IEvent> &aEvent);
+
+ // other internal methods
+ HRESULT takeScreenShotWorker(ULONG aScreenId,
+ BYTE *aAddress,
+ ULONG aWidth,
+ ULONG aHeight,
+ BitmapFormat_T aBitmapFormat,
+ ULONG *pcbOut);
+ int processVBVAResize(PCVBVAINFOVIEW pView, PCVBVAINFOSCREEN pScreen, void *pvVRAM, bool fResetInputMapping);
+
+ static DECLCALLBACK(void*) i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID);
+ static DECLCALLBACK(int) i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags);
+ static DECLCALLBACK(void) i_drvDestruct(PPDMDRVINS pDrvIns);
+ static DECLCALLBACK(void) i_drvPowerOff(PPDMDRVINS pDrvIns);
+ static DECLCALLBACK(int) i_displayResizeCallback(PPDMIDISPLAYCONNECTOR pInterface, uint32_t bpp, void *pvVRAM,
+ uint32_t cbLine, uint32_t cx, uint32_t cy);
+ static DECLCALLBACK(void) i_displayUpdateCallback(PPDMIDISPLAYCONNECTOR pInterface,
+ uint32_t x, uint32_t y, uint32_t cx, uint32_t cy);
+ static DECLCALLBACK(void) i_displayRefreshCallback(PPDMIDISPLAYCONNECTOR pInterface);
+ static DECLCALLBACK(void) i_displayResetCallback(PPDMIDISPLAYCONNECTOR pInterface);
+ static DECLCALLBACK(void) i_displayLFBModeChangeCallback(PPDMIDISPLAYCONNECTOR pInterface, bool fEnabled);
+ static DECLCALLBACK(void) i_displayProcessAdapterDataCallback(PPDMIDISPLAYCONNECTOR pInterface,
+ void *pvVRAM, uint32_t u32VRAMSize);
+ static DECLCALLBACK(void) i_displayProcessDisplayDataCallback(PPDMIDISPLAYCONNECTOR pInterface,
+ void *pvVRAM, unsigned uScreenId);
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ static DECLCALLBACK(int) i_displayVHWACommandProcess(PPDMIDISPLAYCONNECTOR pInterface, int enmCmd, bool fGuestCmd,
+ VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCommand);
+#endif
+ static DECLCALLBACK(int) i_display3DNotifyProcess(PPDMIDISPLAYCONNECTOR pInterface,
+ VBOX3DNOTIFY *p3DNotify);
+
+#ifdef VBOX_WITH_HGSMI
+ static DECLCALLBACK(int) i_displayVBVAEnable(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId,
+ VBVAHOSTFLAGS RT_UNTRUSTED_VOLATILE_GUEST *pHostFlags);
+ static DECLCALLBACK(void) i_displayVBVADisable(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId);
+ static DECLCALLBACK(void) i_displayVBVAUpdateBegin(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId);
+ static DECLCALLBACK(void) i_displayVBVAUpdateProcess(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId,
+ struct VBVACMDHDR const RT_UNTRUSTED_VOLATILE_GUEST *pCmd, size_t cbCmd);
+ static DECLCALLBACK(void) i_displayVBVAUpdateEnd(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId, int32_t x, int32_t y,
+ uint32_t cx, uint32_t cy);
+ static DECLCALLBACK(int) i_displayVBVAResize(PPDMIDISPLAYCONNECTOR pInterface, PCVBVAINFOVIEW pView,
+ PCVBVAINFOSCREEN pScreen, void *pvVRAM,
+ bool fResetInputMapping);
+ static DECLCALLBACK(int) i_displayVBVAMousePointerShape(PPDMIDISPLAYCONNECTOR pInterface, bool fVisible, bool fAlpha,
+ uint32_t xHot, uint32_t yHot, uint32_t cx, uint32_t cy,
+ const void *pvShape);
+ static DECLCALLBACK(void) i_displayVBVAGuestCapabilityUpdate(PPDMIDISPLAYCONNECTOR pInterface, uint32_t fCapabilities);
+
+ static DECLCALLBACK(void) i_displayVBVAInputMappingUpdate(PPDMIDISPLAYCONNECTOR pInterface, int32_t xOrigin, int32_t yOrigin,
+ uint32_t cx, uint32_t cy);
+ static DECLCALLBACK(void) i_displayVBVAReportCursorPosition(PPDMIDISPLAYCONNECTOR pInterface, uint32_t fFlags, uint32_t uScreen, uint32_t x, uint32_t y);
+#endif
+
+ static DECLCALLBACK(int) i_displaySSMSaveScreenshot(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser);
+ static DECLCALLBACK(int) i_displaySSMLoadScreenshot(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser,
+ uint32_t uVersion, uint32_t uPass);
+ static DECLCALLBACK(int) i_displaySSMSave(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser);
+ static DECLCALLBACK(int) i_displaySSMLoad(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser,
+ uint32_t uVersion, uint32_t uPass);
+
+ Console * const mParent;
+ /** Pointer to the associated display driver. */
+ struct DRVMAINDISPLAY *mpDrv;
+
+ unsigned mcMonitors;
+ /** Input mapping rectangle top left X relative to the first screen. */
+ int32_t xInputMappingOrigin;
+ /** Input mapping rectangle top left Y relative to the first screen. */
+ int32_t yInputMappingOrigin;
+ uint32_t cxInputMapping; /**< Input mapping rectangle width. */
+ uint32_t cyInputMapping; /**< Input mapping rectangle height. */
+ DISPLAYFBINFO maFramebuffers[SchemaDefs::MaxGuestMonitors];
+ /** Does the VMM device have the "supports graphics" capability set?
+ * Does not go into the saved state as it is refreshed on restore. */
+ bool mfVMMDevSupportsGraphics;
+ /** Mirror of the current guest VBVA capabilities. */
+ uint32_t mfGuestVBVACapabilities;
+ /** Mirror of the current host cursor capabilities. */
+ uint32_t mfHostCursorCapabilities;
+
+ bool mfSourceBitmapEnabled;
+ bool volatile fVGAResizing;
+
+ /** Are we in seamless mode? Not saved, as we exit seamless on saving. */
+ bool mfSeamlessEnabled;
+ /** Last set seamless visible region, number of rectangles. */
+ uint32_t mcRectVisibleRegion;
+ /** Last set seamless visible region, data. Freed on final clean-up. */
+ PRTRECT mpRectVisibleRegion;
+
+ bool mfVideoAccelVRDP;
+ uint32_t mfu32SupportedOrders;
+ /** Number of currently connected VRDP clients. */
+ int32_t volatile mcVRDPRefs;
+
+ /* The legacy VBVA data and methods. */
+ VIDEOACCEL mVideoAccelLegacy;
+
+ int i_VideoAccelEnable(bool fEnable, VBVAMEMORY *pVbvaMemory, PPDMIDISPLAYPORT pUpPort);
+ void i_VideoAccelFlush(PPDMIDISPLAYPORT pUpPort);
+ bool i_VideoAccelAllowed(void);
+
+ int i_videoAccelRefreshProcess(PPDMIDISPLAYPORT pUpPort);
+ int i_videoAccelEnable(bool fEnable, VBVAMEMORY *pVbvaMemory, PPDMIDISPLAYPORT pUpPort);
+ int i_videoAccelFlush(PPDMIDISPLAYPORT pUpPort);
+
+ /* Legacy pre-HGSMI handlers. */
+ void processAdapterData(void *pvVRAM, uint32_t u32VRAMSize);
+ void processDisplayData(void *pvVRAM, unsigned uScreenId);
+
+ /** Serializes access to mVideoAccelLegacy and mfVideoAccelVRDP, etc between VRDP and Display. */
+ RTCRITSECT mVideoAccelLock;
+
+#ifdef VBOX_WITH_RECORDING
+ /* Serializes access to video recording source bitmaps. */
+ RTCRITSECT mVideoRecLock;
+ /** Array which defines which screens are being enabled for recording. */
+ bool maRecordingEnabled[SchemaDefs::MaxGuestMonitors];
+#endif
+
+public:
+
+ static DECLCALLBACK(int) i_displayTakeScreenshotEMT(Display *pDisplay, ULONG aScreenId, uint8_t **ppbData, size_t *pcbData,
+ uint32_t *pcx, uint32_t *pcy, bool *pfMemFree);
+
+private:
+ static DECLCALLBACK(int) i_InvalidateAndUpdateEMT(Display *pDisplay, unsigned uId, bool fUpdateAll);
+ static DECLCALLBACK(int) i_drawToScreenEMT(Display *pDisplay, ULONG aScreenId, BYTE *address, ULONG x, ULONG y,
+ ULONG width, ULONG height);
+
+ void i_updateGuestGraphicsFacility(void);
+
+#ifdef VBOX_WITH_HGSMI
+ volatile uint32_t mu32UpdateVBVAFlags;
+#endif
+
+private:
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(Display); /* Shuts up MSC warning C4625. */
+};
+
+/* The legacy VBVA helpers. */
+int videoAccelConstruct(VIDEOACCEL *pVideoAccel);
+void videoAccelDestroy(VIDEOACCEL *pVideoAccel);
+void i_vbvaSetMemoryFlags(VBVAMEMORY *pVbvaMemory,
+ bool fVideoAccelEnabled,
+ bool fVideoAccelVRDP,
+ uint32_t fu32SupportedOrders,
+ DISPLAYFBINFO *paFBInfos,
+ unsigned cFBInfos);
+int videoAccelEnterVGA(VIDEOACCEL *pVideoAccel);
+void videoAccelLeaveVGA(VIDEOACCEL *pVideoAccel);
+int videoAccelEnterVMMDev(VIDEOACCEL *pVideoAccel);
+void videoAccelLeaveVMMDev(VIDEOACCEL *pVideoAccel);
+
+
+/* helper function, code in DisplayResampleImage.cpp */
+void BitmapScale32(uint8_t *dst, int dstW, int dstH,
+ const uint8_t *src, int iDeltaLine, int srcW, int srcH);
+
+/* helper function, code in DisplayPNGUtul.cpp */
+int DisplayMakePNG(uint8_t *pbData, uint32_t cx, uint32_t cy,
+ uint8_t **ppu8PNG, uint32_t *pcbPNG, uint32_t *pcxPNG, uint32_t *pcyPNG,
+ uint8_t fLimitSize);
+
+class ATL_NO_VTABLE DisplaySourceBitmap:
+ public DisplaySourceBitmapWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(DisplaySourceBitmap)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ /* Public initializer/uninitializer for internal purposes only. */
+ HRESULT init(ComObjPtr<Display> pDisplay, unsigned uScreenId, DISPLAYFBINFO *pFBInfo);
+ void uninit();
+
+ bool i_usesVRAM(void) { return m.pu8Allocated == NULL; }
+
+private:
+ // wrapped IDisplaySourceBitmap properties
+ virtual HRESULT getScreenId(ULONG *aScreenId);
+
+ // wrapped IDisplaySourceBitmap methods
+ virtual HRESULT queryBitmapInfo(BYTE **aAddress,
+ ULONG *aWidth,
+ ULONG *aHeight,
+ ULONG *aBitsPerPixel,
+ ULONG *aBytesPerLine,
+ BitmapFormat_T *aBitmapFormat);
+
+ int initSourceBitmap(unsigned aScreenId, DISPLAYFBINFO *pFBInfo);
+
+ struct Data
+ {
+ ComObjPtr<Display> pDisplay;
+ unsigned uScreenId;
+ DISPLAYFBINFO *pFBInfo;
+
+ uint8_t *pu8Allocated;
+
+ uint8_t *pu8Address;
+ ULONG ulWidth;
+ ULONG ulHeight;
+ ULONG ulBitsPerPixel;
+ ULONG ulBytesPerLine;
+ BitmapFormat_T bitmapFormat;
+ };
+
+ Data m;
+};
+
+class ATL_NO_VTABLE GuestScreenInfo:
+ public GuestScreenInfoWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(GuestScreenInfo)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ /* Public initializer/uninitializer for internal purposes only. */
+ HRESULT init(ULONG aDisplay,
+ GuestMonitorStatus_T aGuestMonitorStatus,
+ BOOL aPrimary,
+ BOOL aChangeOrigin,
+ LONG aOriginX,
+ LONG aOriginY,
+ ULONG aWidth,
+ ULONG aHeight,
+ ULONG aBitsPerPixel);
+ void uninit();
+
+private:
+ // wrapped IGuestScreenInfo properties
+ virtual HRESULT getScreenId(ULONG *aScreenId);
+ virtual HRESULT getGuestMonitorStatus(GuestMonitorStatus_T *aGuestMonitorStatus);
+ virtual HRESULT getPrimary(BOOL *aPrimary);
+ virtual HRESULT getOrigin(BOOL *aOrigin);
+ virtual HRESULT getOriginX(LONG *aOriginX);
+ virtual HRESULT getOriginY(LONG *aOriginY);
+ virtual HRESULT getWidth(ULONG *aWidth);
+ virtual HRESULT getHeight(ULONG *aHeight);
+ virtual HRESULT getBitsPerPixel(ULONG *aBitsPerPixel);
+ virtual HRESULT getExtendedInfo(com::Utf8Str &aExtendedInfo);
+
+ ULONG mScreenId;
+ GuestMonitorStatus_T mGuestMonitorStatus;
+ BOOL mPrimary;
+ BOOL mOrigin;
+ LONG mOriginX;
+ LONG mOriginY;
+ ULONG mWidth;
+ ULONG mHeight;
+ ULONG mBitsPerPixel;
+};
+
+#endif /* !MAIN_INCLUDED_DisplayImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/DisplayUtils.h b/src/VBox/Main/include/DisplayUtils.h
new file mode 100644
index 00000000..a86b44b7
--- /dev/null
+++ b/src/VBox/Main/include/DisplayUtils.h
@@ -0,0 +1,57 @@
+/* $Id: DisplayUtils.h $ */
+/** @file
+ * Display helper declarations
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_DisplayUtils_h
+#define MAIN_INCLUDED_DisplayUtils_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBox/com/string.h"
+
+#include "CryptoUtils.h"
+
+using namespace com;
+
+#define sSSMDisplayScreenshotVer 0x00010001
+#define sSSMDisplayVer 0x00010001
+#define sSSMDisplayVer2 0x00010002
+#define sSSMDisplayVer3 0x00010003
+#define sSSMDisplayVer4 0x00010004
+#define sSSMDisplayVer5 0x00010005
+
+int readSavedGuestScreenInfo(SsmStream &ssmStream, const Utf8Str &strStateFilePath,
+ uint32_t u32ScreenId, uint32_t *pu32OriginX, uint32_t *pu32OriginY,
+ uint32_t *pu32Width, uint32_t *pu32Height, uint16_t *pu16Flags);
+
+int readSavedDisplayScreenshot(SsmStream &ssmStream, const Utf8Str &strStateFilePath,
+ uint32_t u32Type, uint8_t **ppu8Data, uint32_t *pcbData,
+ uint32_t *pu32Width, uint32_t *pu32Height);
+void freeSavedDisplayScreenshot(uint8_t *pu8Data);
+
+#endif /* !MAIN_INCLUDED_DisplayUtils_h */
+
diff --git a/src/VBox/Main/include/DrvAudioRec.h b/src/VBox/Main/include/DrvAudioRec.h
new file mode 100644
index 00000000..babb7036
--- /dev/null
+++ b/src/VBox/Main/include/DrvAudioRec.h
@@ -0,0 +1,79 @@
+/* $Id: DrvAudioRec.h $ */
+/** @file
+ * VirtualBox driver interface video recording audio backend.
+ */
+
+/*
+ * Copyright (C) 2017-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_DrvAudioRec_h
+#define MAIN_INCLUDED_DrvAudioRec_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/com/ptr.h>
+#include <VBox/settings.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmifs.h>
+
+#include "AudioDriver.h"
+#include "Recording.h"
+
+using namespace com;
+
+class Console;
+
+class AudioVideoRec : public AudioDriver
+{
+
+public:
+
+ AudioVideoRec(Console *pConsole);
+ virtual ~AudioVideoRec(void);
+
+public:
+
+ static const PDMDRVREG DrvReg;
+
+public:
+
+ int applyConfiguration(const settings::RecordingSettings &Settings);
+
+public:
+
+ static DECLCALLBACK(int) drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags);
+ static DECLCALLBACK(void) drvDestruct(PPDMDRVINS pDrvIns);
+ static DECLCALLBACK(void) drvPowerOff(PPDMDRVINS pDrvIns);
+
+private:
+
+ virtual int configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM) RT_OVERRIDE;
+
+ /** Pointer to the associated video recording audio driver. */
+ struct DRVAUDIORECORDING *mpDrv;
+ /** Recording settings used for configuring the driver. */
+ struct settings::RecordingSettings mSettings;
+};
+
+#endif /* !MAIN_INCLUDED_DrvAudioRec_h */
+
diff --git a/src/VBox/Main/include/DrvAudioVRDE.h b/src/VBox/Main/include/DrvAudioVRDE.h
new file mode 100644
index 00000000..d807395e
--- /dev/null
+++ b/src/VBox/Main/include/DrvAudioVRDE.h
@@ -0,0 +1,87 @@
+/* $Id: DrvAudioVRDE.h $ */
+/** @file
+ * VirtualBox driver interface to VRDE backend.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_DrvAudioVRDE_h
+#define MAIN_INCLUDED_DrvAudioVRDE_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/com/ptr.h>
+#include <VBox/com/string.h>
+
+#include <VBox/RemoteDesktop/VRDE.h>
+
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmifs.h>
+
+#include "AudioDriver.h"
+
+using namespace com;
+
+class Console;
+
+class AudioVRDE : public AudioDriver
+{
+
+public:
+
+ AudioVRDE(Console *pConsole);
+ virtual ~AudioVRDE(void);
+
+public:
+
+ static const PDMDRVREG DrvReg;
+
+public:
+
+ void onVRDEClientConnect(uint32_t uClientID);
+ void onVRDEClientDisconnect(uint32_t uClientID);
+ int onVRDEControl(bool fEnable, uint32_t uFlags);
+ int onVRDEInputBegin(void *pvContext, PVRDEAUDIOINBEGIN pVRDEAudioBegin);
+ int onVRDEInputData(void *pvContext, const void *pvData, uint32_t cbData);
+ int onVRDEInputEnd(void *pvContext);
+ int onVRDEInputIntercept(bool fIntercept);
+
+public:
+
+ static DECLCALLBACK(int) drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags);
+ static DECLCALLBACK(void) drvDestruct(PPDMDRVINS pDrvIns);
+ static DECLCALLBACK(void) drvPowerOff(PPDMDRVINS pDrvIns);
+
+private:
+
+ virtual int configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM) RT_OVERRIDE;
+
+ /** Pointer to the associated VRDE audio driver. */
+ struct DRVAUDIOVRDE *mpDrv;
+ /** Protects accesses to mpDrv from racing driver destruction. */
+ RTCRITSECT mCritSect;
+};
+
+#endif /* !MAIN_INCLUDED_DrvAudioVRDE_h */
+
diff --git a/src/VBox/Main/include/EBMLWriter.h b/src/VBox/Main/include/EBMLWriter.h
new file mode 100644
index 00000000..8234b04b
--- /dev/null
+++ b/src/VBox/Main/include/EBMLWriter.h
@@ -0,0 +1,135 @@
+/* $Id: EBMLWriter.h $ */
+/** @file
+ * EBMLWriter.h - EBML writer.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_EBMLWriter_h
+#define MAIN_INCLUDED_EBMLWriter_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <stack>
+
+#include <iprt/critsect.h>
+#include <iprt/file.h>
+
+#include <VBox/com/string.h>
+
+
+/** No flags set. */
+#define VBOX_EBMLWRITER_FLAG_NONE 0
+/** The file handle was inherited. */
+#define VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED RT_BIT(0)
+
+class EBMLWriter
+{
+public:
+ typedef uint32_t EbmlClassId;
+
+private:
+
+ struct EbmlSubElement
+ {
+ uint64_t offset;
+ EbmlClassId classId;
+ EbmlSubElement(uint64_t offs, EbmlClassId cid) : offset(offs), classId(cid) {}
+ };
+
+ /** Stack of EBML sub elements. */
+ std::stack<EbmlSubElement> m_Elements;
+ /** The file's handle. */
+ RTFILE m_hFile;
+ /** The file's name (path). */
+ com::Utf8Str m_strFile;
+ /** Flags. */
+ uint32_t m_fFlags;
+
+public:
+
+ EBMLWriter(void)
+ : m_hFile(NIL_RTFILE)
+ , m_fFlags(VBOX_EBMLWRITER_FLAG_NONE) { }
+
+ virtual ~EBMLWriter(void) { close(); }
+
+public:
+
+ int createEx(const char *a_pszFile, PRTFILE phFile);
+
+ int create(const char *a_pszFile, uint64_t fOpen);
+
+ void close(void);
+
+ /** Returns the file name. */
+ const com::Utf8Str& getFileName(void) { return m_strFile; }
+
+ /** Returns file size. */
+ uint64_t getFileSize(void) { return RTFileTell(m_hFile); }
+
+ /** Get reference to file descriptor */
+ inline const RTFILE &getFile(void) { return m_hFile; }
+
+ /** Returns available space on storage. */
+ uint64_t getAvailableSpace(void);
+
+ /**
+ * Returns whether the file is open or not.
+ *
+ * @returns True if open, false if not.
+ */
+ bool isOpen(void) { return RTFileIsValid(m_hFile); }
+
+public:
+
+ EBMLWriter &subStart(EbmlClassId classId);
+
+ EBMLWriter &subEnd(EbmlClassId classId);
+
+ EBMLWriter &serializeString(EbmlClassId classId, const char *str);
+
+ EBMLWriter &serializeUnsignedInteger(EbmlClassId classId, uint64_t parm, size_t size = 0);
+
+ EBMLWriter &serializeFloat(EbmlClassId classId, float value);
+
+ EBMLWriter &serializeData(EbmlClassId classId, const void *pvData, size_t cbData);
+
+ int write(const void *data, size_t size);
+
+ void writeUnsignedInteger(uint64_t value, size_t size = sizeof(uint64_t));
+
+ void writeClassId(EbmlClassId parm);
+
+ void writeSize(uint64_t parm);
+
+ static inline size_t getSizeOfUInt(uint64_t arg);
+
+private:
+
+ void operator=(const EBMLWriter &);
+};
+
+#endif /* !MAIN_INCLUDED_EBMLWriter_h */
+
diff --git a/src/VBox/Main/include/EBML_MKV.h b/src/VBox/Main/include/EBML_MKV.h
new file mode 100644
index 00000000..6e437b6b
--- /dev/null
+++ b/src/VBox/Main/include/EBML_MKV.h
@@ -0,0 +1,106 @@
+/* $Id: EBML_MKV.h $ */
+/** @file
+ * EbmlMkvIDs.h - Matroska EBML Class IDs.
+ */
+
+/*
+ * Copyright (C) 2017-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_EBML_MKV_h
+#define MAIN_INCLUDED_EBML_MKV_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+/** Matroska EBML Class IDs supported by WebM.
+ *
+ * Keep the structure clean and group elements where it makes sense
+ * for easier reading / lookup. */
+enum MkvElem
+{
+ MkvElem_EBML = 0x1A45DFA3,
+ MkvElem_EBMLVersion = 0x4286,
+ MkvElem_EBMLReadVersion = 0x42F7,
+ MkvElem_EBMLMaxIDLength = 0x42F2,
+ MkvElem_EBMLMaxSizeLength = 0x42F3,
+
+ MkvElem_DocType = 0x4282,
+ MkvElem_DocTypeVersion = 0x4287,
+ MkvElem_DocTypeReadVersion = 0x4285,
+
+ MkvElem_Segment = 0x18538067,
+ MkvElem_Segment_Duration = 0x4489,
+
+ MkvElem_SeekHead = 0x114D9B74,
+ MkvElem_Seek = 0x4DBB,
+ MkvElem_SeekID = 0x53AB,
+ MkvElem_SeekPosition = 0x53AC,
+
+ MkvElem_Info = 0x1549A966,
+ MkvElem_TimecodeScale = 0x2AD7B1,
+ MkvElem_MuxingApp = 0x4D80,
+ MkvElem_WritingApp = 0x5741,
+
+ MkvElem_Tracks = 0x1654AE6B,
+ MkvElem_TrackEntry = 0xAE,
+ MkvElem_TrackNumber = 0xD7,
+ MkvElem_TrackUID = 0x73C5,
+ MkvElem_TrackType = 0x83,
+
+ MkvElem_Language = 0x22B59C,
+
+ MkvElem_FlagLacing = 0x9C,
+
+ MkvElem_Cluster = 0x1F43B675,
+ MkvElem_Timecode = 0xE7,
+
+ MkvElem_SimpleBlock = 0xA3,
+
+ MkvElem_SeekPreRoll = 0x56BB,
+
+ MkvElem_CodecID = 0x86,
+ MkvElem_CodecDelay = 0x56AA,
+ MkvElem_CodecPrivate = 0x63A2,
+ MkvElem_CodecName = 0x258688,
+
+ MkvElem_Video = 0xE0,
+ MkvElem_PixelWidth = 0xB0,
+ MkvElem_PixelHeight = 0xBA,
+ MkvElem_FrameRate = 0x2383E3,
+
+ MkvElem_Audio = 0xE1,
+ MkvElem_SamplingFrequency = 0xB5,
+ MkvElem_OutputSamplingFrequency = 0x78B5,
+ MkvElem_Channels = 0x9F,
+ MkvElem_BitDepth = 0x6264,
+
+ MkvElem_Cues = 0x1C53BB6B,
+ MkvElem_CuePoint = 0xBB,
+ MkvElem_CueTime = 0xB3,
+ MkvElem_CueTrackPositions = 0xB7,
+ MkvElem_CueTrack = 0xF7,
+ MkvElem_CueClusterPosition = 0xF1
+};
+
+#endif /* !MAIN_INCLUDED_EBML_MKV_h */
+
diff --git a/src/VBox/Main/include/EmulatedUSBImpl.h b/src/VBox/Main/include/EmulatedUSBImpl.h
new file mode 100644
index 00000000..aaf23e40
--- /dev/null
+++ b/src/VBox/Main/include/EmulatedUSBImpl.h
@@ -0,0 +1,103 @@
+/* $Id: EmulatedUSBImpl.h $ */
+/** @file
+ * Emulated USB devices manager.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_EmulatedUSBImpl_h
+#define MAIN_INCLUDED_EmulatedUSBImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "EmulatedUSBWrap.h"
+
+#include <VBox/vrdpusb.h>
+
+class Console;
+class EUSBWEBCAM;
+
+typedef std::map<Utf8Str, EUSBWEBCAM *> WebcamsMap;
+
+class ATL_NO_VTABLE EmulatedUSB :
+ public EmulatedUSBWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(EmulatedUSB)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ /* Public initializer/uninitializer for internal purposes only. */
+ HRESULT init(ComObjPtr<Console> pConsole);
+ void uninit();
+
+ /* Public method for internal use. */
+ static DECLCALLBACK(int) i_eusbCallback(void *pv, const char *pszId, uint32_t iEvent,
+ const void *pvData, uint32_t cbData);
+
+ PEMULATEDUSBIF i_getEmulatedUsbIf();
+
+ HRESULT i_webcamAttachInternal(const com::Utf8Str &aPath,
+ const com::Utf8Str &aSettings,
+ const char *pszDriver,
+ void *pvObject);
+ HRESULT i_webcamDetachInternal(const com::Utf8Str &aPath);
+
+private:
+
+ static DECLCALLBACK(int) eusbCallbackEMT(EmulatedUSB *pThis, char *pszId, uint32_t iEvent,
+ void *pvData, uint32_t cbData);
+
+ static DECLCALLBACK(int) i_QueryEmulatedUsbDataById(void *pvUser, const char *pszId, void **ppvEmUsbCb, void **ppvEmUsbCbData, void **ppvObject);
+
+ HRESULT webcamPathFromId(com::Utf8Str *pPath, const char *pszId);
+
+ // wrapped IEmulatedUSB properties
+ virtual HRESULT getWebcams(std::vector<com::Utf8Str> &aWebcams);
+
+ // wrapped IEmulatedUSB methods
+ virtual HRESULT webcamAttach(const com::Utf8Str &aPath,
+ const com::Utf8Str &aSettings);
+ virtual HRESULT webcamDetach(const com::Utf8Str &aPath);
+
+ /* Data. */
+ struct Data
+ {
+ Data()
+ {
+ }
+
+ ComObjPtr<Console> pConsole;
+ WebcamsMap webcams;
+ };
+
+ Data m;
+ EMULATEDUSBIF mEmUsbIf;
+};
+
+#endif /* !MAIN_INCLUDED_EmulatedUSBImpl_h */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/EventImpl.h b/src/VBox/Main/include/EventImpl.h
new file mode 100644
index 00000000..0a3116a2
--- /dev/null
+++ b/src/VBox/Main/include/EventImpl.h
@@ -0,0 +1,188 @@
+/* $Id: EventImpl.h $ */
+/** @file
+ * VirtualBox COM IEvent implementation
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_EventImpl_h
+#define MAIN_INCLUDED_EventImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "EventWrap.h"
+#include "EventSourceWrap.h"
+#include "VetoEventWrap.h"
+
+
+class ATL_NO_VTABLE VBoxEvent
+ : public EventWrap
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(VBoxEvent)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(IEventSource *aSource, VBoxEventType_T aType, BOOL aWaitable);
+ void uninit();
+
+private:
+ // wrapped IEvent properties
+ HRESULT getType(VBoxEventType_T *aType);
+ HRESULT getSource(ComPtr<IEventSource> &aSource);
+ HRESULT getWaitable(BOOL *aWaitable);
+
+ // wrapped IEvent methods
+ HRESULT setProcessed();
+ HRESULT waitProcessed(LONG aTimeout, BOOL *aResult);
+
+ struct Data;
+ Data* m;
+};
+
+
+class ATL_NO_VTABLE VBoxVetoEvent
+ : public VetoEventWrap
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(VBoxVetoEvent)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(IEventSource *aSource, VBoxEventType_T aType);
+ void uninit();
+
+private:
+ // wrapped IEvent properties
+ HRESULT getType(VBoxEventType_T *aType);
+ HRESULT getSource(ComPtr<IEventSource> &aSource);
+ HRESULT getWaitable(BOOL *aWaitable);
+
+ // wrapped IEvent methods
+ HRESULT setProcessed();
+ HRESULT waitProcessed(LONG aTimeout, BOOL *aResult);
+
+ // wrapped IVetoEvent methods
+ HRESULT addVeto(const com::Utf8Str &aReason);
+ HRESULT isVetoed(BOOL *aResult);
+ HRESULT getVetos(std::vector<com::Utf8Str> &aResult);
+ HRESULT addApproval(const com::Utf8Str &aReason);
+ HRESULT isApproved(BOOL *aResult);
+ HRESULT getApprovals(std::vector<com::Utf8Str> &aResult);
+
+ struct Data;
+ Data* m;
+};
+
+class ATL_NO_VTABLE EventSource :
+ public EventSourceWrap
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(EventSource)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init();
+ void uninit();
+
+private:
+ // wrapped IEventSource methods
+ HRESULT createListener(ComPtr<IEventListener> &aListener);
+ HRESULT createAggregator(const std::vector<ComPtr<IEventSource> > &aSubordinates,
+ ComPtr<IEventSource> &aResult);
+ HRESULT registerListener(const ComPtr<IEventListener> &aListener,
+ const std::vector<VBoxEventType_T> &aInteresting,
+ BOOL aActive);
+ HRESULT unregisterListener(const ComPtr<IEventListener> &aListener);
+ HRESULT fireEvent(const ComPtr<IEvent> &aEvent,
+ LONG aTimeout,
+ BOOL *aResult);
+ HRESULT getEvent(const ComPtr<IEventListener> &aListener,
+ LONG aTimeout,
+ ComPtr<IEvent> &aEvent);
+ HRESULT eventProcessed(const ComPtr<IEventListener> &aListener,
+ const ComPtr<IEvent> &aEvent);
+
+
+ struct Data;
+ Data *m;
+
+ friend class ListenerRecord;
+};
+
+class VBoxEventDesc
+{
+public:
+ VBoxEventDesc() : mEvent(0), mEventSource(0)
+ {}
+
+ VBoxEventDesc(IEvent *aEvent, IEventSource *aSource)
+ : mEvent(aEvent), mEventSource(aSource)
+ {}
+
+ ~VBoxEventDesc()
+ {}
+
+ void init(IEvent *aEvent, IEventSource *aSource)
+ {
+ mEvent = aEvent;
+ mEventSource = aSource;
+ }
+
+ void uninit()
+ {
+ mEvent.setNull();
+ mEventSource.setNull();
+ }
+
+ void getEvent(IEvent **aEvent)
+ {
+ mEvent.queryInterfaceTo(aEvent);
+ }
+
+ BOOL fire(LONG aTimeout)
+ {
+ if (mEventSource && mEvent)
+ {
+ BOOL fDelivered = FALSE;
+ HRESULT hrc = mEventSource->FireEvent(mEvent, aTimeout, &fDelivered);
+ AssertComRCReturn(hrc, FALSE);
+ return fDelivered;
+ }
+ return FALSE;
+ }
+
+private:
+ ComPtr<IEvent> mEvent;
+ ComPtr<IEventSource> mEventSource;
+};
+
+
+#endif /* !MAIN_INCLUDED_EventImpl_h */
diff --git a/src/VBox/Main/include/ExtPackManagerImpl.h b/src/VBox/Main/include/ExtPackManagerImpl.h
new file mode 100644
index 00000000..dc4ce201
--- /dev/null
+++ b/src/VBox/Main/include/ExtPackManagerImpl.h
@@ -0,0 +1,322 @@
+/* $Id: ExtPackManagerImpl.h $ */
+/** @file
+ * VirtualBox Main - interface for Extension Packs, VBoxSVC & VBoxC.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ExtPackManagerImpl_h
+#define MAIN_INCLUDED_ExtPackManagerImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VirtualBoxBase.h"
+#include <VBox/ExtPack/ExtPack.h>
+#include "ExtPackWrap.h"
+#include "ExtPackFileWrap.h"
+#include "ExtPackManagerWrap.h"
+#include <iprt/fs.h>
+
+
+/** The name of the oracle extension back. */
+#define ORACLE_PUEL_EXTPACK_NAME "Oracle VM VirtualBox Extension Pack"
+
+
+#ifndef VBOX_COM_INPROC
+/**
+ * An extension pack file.
+ */
+class ATL_NO_VTABLE ExtPackFile :
+ public ExtPackFileWrap
+{
+public:
+ /** @name COM and internal init/term/mapping cruft.
+ * @{ */
+ DECLARE_COMMON_CLASS_METHODS(ExtPackFile)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+ HRESULT initWithFile(const char *a_pszFile, const char *a_pszDigest, class ExtPackManager *a_pExtPackMgr, VirtualBox *a_pVirtualBox);
+ void uninit();
+ /** @} */
+
+private:
+ /** @name Misc init helpers
+ * @{ */
+ HRESULT initFailed(const char *a_pszWhyFmt, ...);
+ /** @} */
+
+private:
+
+ // wrapped IExtPackFile properties
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getDescription(com::Utf8Str &aDescription);
+ HRESULT getVersion(com::Utf8Str &aVersion);
+ HRESULT getRevision(ULONG *aRevision);
+ HRESULT getEdition(com::Utf8Str &aEdition);
+ HRESULT getVRDEModule(com::Utf8Str &aVRDEModule);
+ HRESULT getCryptoModule(com::Utf8Str &aCryptoModule);
+ HRESULT getPlugIns(std::vector<ComPtr<IExtPackPlugIn> > &aPlugIns);
+ HRESULT getUsable(BOOL *aUsable);
+ HRESULT getWhyUnusable(com::Utf8Str &aWhyUnusable);
+ HRESULT getShowLicense(BOOL *aShowLicense);
+ HRESULT getLicense(com::Utf8Str &aLicense);
+ HRESULT getFilePath(com::Utf8Str &aFilePath);
+
+ // wrapped IExtPackFile methods
+ HRESULT queryLicense(const com::Utf8Str &aPreferredLocale,
+ const com::Utf8Str &aPreferredLanguage,
+ const com::Utf8Str &aFormat,
+ com::Utf8Str &aLicenseText);
+ HRESULT install(BOOL aReplace,
+ const com::Utf8Str &aDisplayInfo,
+ ComPtr<IProgress> &aProgess);
+
+ struct Data;
+ /** Pointer to the private instance. */
+ Data *m;
+
+ friend class ExtPackManager;
+ friend class ExtPackInstallTask;
+};
+#endif /* !VBOX_COM_INPROC */
+
+
+/**
+ * An installed extension pack.
+ */
+class ATL_NO_VTABLE ExtPack :
+ public ExtPackWrap
+{
+public:
+ /** @name COM and internal init/term/mapping cruft.
+ * @{ */
+ DECLARE_COMMON_CLASS_METHODS(ExtPack)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+ HRESULT initWithDir(VirtualBox *a_pVirtualBox, VBOXEXTPACKCTX a_enmContext, const char *a_pszName, const char *a_pszDir);
+ void uninit();
+ RTMEMEF_NEW_AND_DELETE_OPERATORS();
+ /** @} */
+
+ /** @name Internal interfaces used by ExtPackManager.
+ * @{ */
+#ifndef VBOX_COM_INPROC
+ bool i_callInstalledHook(IVirtualBox *a_pVirtualBox, AutoWriteLock *a_pLock, PRTERRINFO pErrInfo);
+ HRESULT i_callUninstallHookAndClose(IVirtualBox *a_pVirtualBox, bool a_fForcedRemoval);
+ bool i_callVirtualBoxReadyHook(IVirtualBox *a_pVirtualBox, AutoWriteLock *a_pLock);
+#endif
+#ifdef VBOX_COM_INPROC
+ bool i_callConsoleReadyHook(IConsole *a_pConsole, AutoWriteLock *a_pLock);
+#endif
+#ifndef VBOX_COM_INPROC
+ bool i_callVmCreatedHook(IVirtualBox *a_pVirtualBox, IMachine *a_pMachine, AutoWriteLock *a_pLock);
+#endif
+#ifdef VBOX_COM_INPROC
+ bool i_callVmConfigureVmmHook(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM,
+ AutoWriteLock *a_pLock, int *a_pvrc);
+ bool i_callVmPowerOnHook(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM, AutoWriteLock *a_pLock, int *a_pvrc);
+ bool i_callVmPowerOffHook(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM, AutoWriteLock *a_pLock);
+#endif
+ HRESULT i_checkVrde(void);
+ HRESULT i_checkCrypto(void);
+ HRESULT i_getVrdpLibraryName(Utf8Str *a_pstrVrdeLibrary);
+ HRESULT i_getCryptoLibraryName(Utf8Str *a_pstrCryptoLibrary);
+ HRESULT i_getLibraryName(const char *a_pszModuleName, Utf8Str *a_pstrLibrary);
+ bool i_wantsToBeDefaultVrde(void) const;
+ bool i_wantsToBeDefaultCrypto(void) const;
+ HRESULT i_refresh(bool *pfCanDelete);
+#ifndef VBOX_COM_INPROC
+ bool i_areThereCloudProviderUninstallVetos();
+ void i_notifyCloudProviderManager();
+#endif
+ /** @} */
+
+protected:
+ /** @name Internal helper methods.
+ * @{ */
+ void i_probeAndLoad(void);
+ bool i_findModule(const char *a_pszName, const char *a_pszExt, VBOXEXTPACKMODKIND a_enmKind,
+ Utf8Str *a_ppStrFound, bool *a_pfNative, PRTFSOBJINFO a_pObjInfo) const;
+ static bool i_objinfoIsEqual(PCRTFSOBJINFO pObjInfo1, PCRTFSOBJINFO pObjInfo2);
+ /** @} */
+
+ /** @name Extension Pack Helpers
+ * @{ */
+ static DECLCALLBACK(int) i_hlpFindModule(PCVBOXEXTPACKHLP pHlp, const char *pszName, const char *pszExt,
+ VBOXEXTPACKMODKIND enmKind, char *pszFound, size_t cbFound, bool *pfNative);
+ static DECLCALLBACK(int) i_hlpGetFilePath(PCVBOXEXTPACKHLP pHlp, const char *pszFilename, char *pszPath, size_t cbPath);
+ static DECLCALLBACK(VBOXEXTPACKCTX) i_hlpGetContext(PCVBOXEXTPACKHLP pHlp);
+ static DECLCALLBACK(int) i_hlpLoadHGCMService(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IConsole) *pConsole, const char *pszServiceLibrary, const char *pszServiceName);
+ static DECLCALLBACK(int) i_hlpLoadVDPlugin(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IVirtualBox) *pVirtualBox, const char *pszPluginLibrary);
+ static DECLCALLBACK(int) i_hlpUnloadVDPlugin(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IVirtualBox) *pVirtualBox, const char *pszPluginLibrary);
+ static DECLCALLBACK(uint32_t) i_hlpCreateProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IUnknown) *pInitiator,
+ const char *pcszDescription, uint32_t cOperations,
+ uint32_t uTotalOperationsWeight, const char *pcszFirstOperationDescription,
+ uint32_t uFirstOperationWeight, VBOXEXTPACK_IF_CS(IProgress) **ppProgressOut);
+ static DECLCALLBACK(uint32_t) i_hlpGetCanceledProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ bool *pfCanceled);
+ static DECLCALLBACK(uint32_t) i_hlpUpdateProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ uint32_t uPercent);
+ static DECLCALLBACK(uint32_t) i_hlpNextOperationProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ const char *pcszNextOperationDescription,
+ uint32_t uNextOperationWeight);
+ static DECLCALLBACK(uint32_t) i_hlpWaitOtherProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ VBOXEXTPACK_IF_CS(IProgress) *pProgressOther,
+ uint32_t cTimeoutMS);
+ static DECLCALLBACK(uint32_t) i_hlpCompleteProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ uint32_t uResultCode);
+ static DECLCALLBACK(uint32_t) i_hlpCreateEvent(PCVBOXEXTPACKHLP pHlp,
+ VBOXEXTPACK_IF_CS(IEventSource) *aSource,
+ /* VBoxEventType_T */ uint32_t aType, bool aWaitable,
+ VBOXEXTPACK_IF_CS(IEvent) **ppEventOut);
+ static DECLCALLBACK(uint32_t) i_hlpCreateVetoEvent(PCVBOXEXTPACKHLP pHlp,
+ VBOXEXTPACK_IF_CS(IEventSource) *aSource,
+ /* VBoxEventType_T */ uint32_t aType,
+ VBOXEXTPACK_IF_CS(IVetoEvent) **ppEventOut);
+ static DECLCALLBACK(const char *) i_hlpTranslate(PCVBOXEXTPACKHLP pHlp,
+ const char *pszComponent,
+ const char *pszSourceText,
+ const char *pszComment = NULL,
+ const size_t aNum = ~(size_t)0);
+ static DECLCALLBACK(int) i_hlpReservedN(PCVBOXEXTPACKHLP pHlp);
+ /** @} */
+
+private:
+
+ // wrapped IExtPack properties
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getDescription(com::Utf8Str &aDescription);
+ HRESULT getVersion(com::Utf8Str &aVersion);
+ HRESULT getRevision(ULONG *aRevision);
+ HRESULT getEdition(com::Utf8Str &aEdition);
+ HRESULT getVRDEModule(com::Utf8Str &aVRDEModule);
+ HRESULT getCryptoModule(com::Utf8Str &aCryptoModule);
+ HRESULT getPlugIns(std::vector<ComPtr<IExtPackPlugIn> > &aPlugIns);
+ HRESULT getUsable(BOOL *aUsable);
+ HRESULT getWhyUnusable(com::Utf8Str &aWhyUnusable);
+ HRESULT getShowLicense(BOOL *aShowLicense);
+ HRESULT getLicense(com::Utf8Str &aLicense);
+
+ // wrapped IExtPack methods
+ HRESULT queryLicense(const com::Utf8Str &aPreferredLocale,
+ const com::Utf8Str &aPreferredLanguage,
+ const com::Utf8Str &aFormat,
+ com::Utf8Str &aLicenseText);
+ HRESULT queryObject(const com::Utf8Str &aObjUuid,
+ ComPtr<IUnknown> &aReturnInterface);
+
+
+ struct Data;
+ /** Pointer to the private instance. */
+ Data *m;
+
+ friend class ExtPackManager;
+};
+
+
+/**
+ * Extension pack manager.
+ */
+class ATL_NO_VTABLE ExtPackManager :
+ public ExtPackManagerWrap
+{
+public:
+ /** @name COM and internal init/term/mapping cruft.
+ * @{ */
+ DECLARE_COMMON_CLASS_METHODS(ExtPackManager)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+ HRESULT initExtPackManager(VirtualBox *a_pVirtualBox, VBOXEXTPACKCTX a_enmContext);
+ void uninit();
+ /** @} */
+
+ /** @name Internal interfaces used by other Main classes.
+ * @{ */
+#ifndef VBOX_COM_INPROC
+ HRESULT i_doInstall(ExtPackFile *a_pExtPackFile, bool a_fReplace, Utf8Str const *a_pstrDisplayInfo);
+ HRESULT i_doUninstall(const Utf8Str *a_pstrName, bool a_fForcedRemoval, const Utf8Str *a_pstrDisplayInfo);
+ void i_callAllVirtualBoxReadyHooks(void);
+ HRESULT i_queryObjects(const com::Utf8Str &aObjUuid, std::vector<ComPtr<IUnknown> > &aObjects, std::vector<com::Utf8Str> *a_pstrExtPackNames);
+#endif
+#ifdef VBOX_COM_INPROC
+ void i_callAllConsoleReadyHooks(IConsole *a_pConsole);
+#endif
+#ifndef VBOX_COM_INPROC
+ void i_callAllVmCreatedHooks(IMachine *a_pMachine);
+#endif
+#ifdef VBOX_COM_INPROC
+ int i_callAllVmConfigureVmmHooks(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM);
+ int i_callAllVmPowerOnHooks(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM);
+ void i_callAllVmPowerOffHooks(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM);
+#endif
+ HRESULT i_checkVrdeExtPack(Utf8Str const *a_pstrExtPack);
+ int i_getVrdeLibraryPathForExtPack(Utf8Str const *a_pstrExtPack, Utf8Str *a_pstrVrdeLibrary);
+ HRESULT i_checkCryptoExtPack(Utf8Str const *a_pstrExtPack);
+ int i_getCryptoLibraryPathForExtPack(Utf8Str const *a_pstrExtPack, Utf8Str *a_pstrVrdeLibrary);
+ HRESULT i_getLibraryPathForExtPack(const char *a_pszModuleName, const char *a_pszExtPack, Utf8Str *a_pstrLibrary);
+ HRESULT i_getDefaultVrdeExtPack(Utf8Str *a_pstrExtPack);
+ HRESULT i_getDefaultCryptoExtPack(Utf8Str *a_pstrExtPack);
+ bool i_isExtPackUsable(const char *a_pszExtPack);
+ void i_dumpAllToReleaseLog(void);
+ uint64_t i_getUpdateCounter(void);
+ /** @} */
+
+private:
+ // wrapped IExtPackManager properties
+ HRESULT getInstalledExtPacks(std::vector<ComPtr<IExtPack> > &aInstalledExtPacks);
+
+ // wrapped IExtPackManager methods
+ HRESULT find(const com::Utf8Str &aName,
+ ComPtr<IExtPack> &aReturnData);
+ HRESULT openExtPackFile(const com::Utf8Str &aPath,
+ ComPtr<IExtPackFile> &aFile);
+ HRESULT uninstall(const com::Utf8Str &aName,
+ BOOL aForcedRemoval,
+ const com::Utf8Str &aDisplayInfo,
+ ComPtr<IProgress> &aProgess);
+ HRESULT cleanup();
+ HRESULT queryAllPlugInsForFrontend(const com::Utf8Str &aFrontendName,
+ std::vector<com::Utf8Str> &aPlugInModules);
+ HRESULT isExtPackUsable(const com::Utf8Str &aName,
+ BOOL *aUsable);
+
+ bool i_areThereAnyRunningVMs(void) const;
+ HRESULT i_runSetUidToRootHelper(Utf8Str const *a_pstrDisplayInfo, const char *a_pszCommand, ...);
+ ExtPack *i_findExtPack(const char *a_pszName);
+ void i_removeExtPack(const char *a_pszName);
+ HRESULT i_refreshExtPack(const char *a_pszName, bool a_fUnsuableIsError, ExtPack **a_ppExtPack);
+
+private:
+ struct Data;
+ /** Pointer to the private instance. */
+ Data *m;
+
+ friend class ExtPackUninstallTask;
+};
+
+#endif /* !MAIN_INCLUDED_ExtPackManagerImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/ExtPackUtil.h b/src/VBox/Main/include/ExtPackUtil.h
new file mode 100644
index 00000000..ec9ca730
--- /dev/null
+++ b/src/VBox/Main/include/ExtPackUtil.h
@@ -0,0 +1,158 @@
+/* $Id: ExtPackUtil.h $ */
+/** @file
+ * VirtualBox Main - Extension Pack Utilities and definitions, VBoxC, VBoxSVC, ++.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ExtPackUtil_h
+#define MAIN_INCLUDED_ExtPackUtil_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#ifdef __cplusplus
+# include <iprt/cpp/ministring.h>
+#endif
+#include <iprt/fs.h>
+#include <iprt/vfs.h>
+
+
+/** @name VBOX_EXTPACK_DESCRIPTION_NAME
+ * The name of the description file in an extension pack. */
+#define VBOX_EXTPACK_DESCRIPTION_NAME "ExtPack.xml"
+/** @name VBOX_EXTPACK_DESCRIPTION_NAME
+ * The name of the manifest file in an extension pack. */
+#define VBOX_EXTPACK_MANIFEST_NAME "ExtPack.manifest"
+/** @name VBOX_EXTPACK_SIGNATURE_NAME
+ * The name of the signature file in an extension pack. */
+#define VBOX_EXTPACK_SIGNATURE_NAME "ExtPack.signature"
+/** @name VBOX_EXTPACK_LICENSE_NAME_PREFIX
+ * The name prefix of a license file in an extension pack. There can be
+ * several license files in a pack, the variations being on locale, language
+ * and format (HTML, RTF, plain text). All extension packages shall include
+ * a */
+#define VBOX_EXTPACK_LICENSE_NAME_PREFIX "ExtPack-license"
+/** @name VBOX_EXTPACK_SUFFIX
+ * The suffix of a extension pack tarball. */
+#define VBOX_EXTPACK_SUFFIX ".vbox-extpack"
+
+/** The minimum length (strlen) of a extension pack name. */
+#define VBOX_EXTPACK_NAME_MIN_LEN 3
+/** The max length (strlen) of a extension pack name. */
+#define VBOX_EXTPACK_NAME_MAX_LEN 64
+
+/** The architecture-dependent application data subdirectory where the
+ * extension packs are installed. Relative to RTPathAppPrivateArch. */
+#define VBOX_EXTPACK_INSTALL_DIR "ExtensionPacks"
+/** The architecture-independent application data subdirectory where the
+ * certificates are installed. Relative to RTPathAppPrivateNoArch. */
+#define VBOX_EXTPACK_CERT_DIR "ExtPackCertificates"
+
+/** The maximum entry name length.
+ * Play short and safe. */
+#define VBOX_EXTPACK_MAX_MEMBER_NAME_LENGTH 128
+
+
+#ifdef __cplusplus
+
+/**
+ * Plug-in descriptor.
+ */
+typedef struct VBOXEXTPACKPLUGINDESC
+{
+ /** The name. */
+ RTCString strName;
+ /** The module name. */
+ RTCString strModule;
+ /** The description. */
+ RTCString strDescription;
+ /** The frontend or component which it plugs into. */
+ RTCString strFrontend;
+} VBOXEXTPACKPLUGINDESC;
+/** Pointer to a plug-in descriptor. */
+typedef VBOXEXTPACKPLUGINDESC *PVBOXEXTPACKPLUGINDESC;
+
+/**
+ * Extension pack descriptor
+ *
+ * This is the internal representation of the ExtPack.xml.
+ */
+typedef struct VBOXEXTPACKDESC
+{
+ /** The name. */
+ RTCString strName;
+ /** The description. */
+ RTCString strDescription;
+ /** The version string. */
+ RTCString strVersion;
+ /** The edition string. */
+ RTCString strEdition;
+ /** The internal revision number. */
+ uint32_t uRevision;
+ /** The name of the main module. */
+ RTCString strMainModule;
+ /** The name of the main VM module, empty if none. */
+ RTCString strMainVMModule;
+ /** The name of the VRDE module, empty if none. */
+ RTCString strVrdeModule;
+ /** The name of the cryptographic module, empty if none. */
+ RTCString strCryptoModule;
+ /** The number of plug-in descriptors. */
+ uint32_t cPlugIns;
+ /** Pointer to an array of plug-in descriptors. */
+ PVBOXEXTPACKPLUGINDESC paPlugIns;
+ /** Whether to show the license prior to installation. */
+ bool fShowLicense;
+} VBOXEXTPACKDESC;
+
+/** Pointer to a extension pack descriptor. */
+typedef VBOXEXTPACKDESC *PVBOXEXTPACKDESC;
+/** Pointer to a const extension pack descriptor. */
+typedef VBOXEXTPACKDESC const *PCVBOXEXTPACKDESC;
+
+
+void VBoxExtPackInitDesc(PVBOXEXTPACKDESC a_pExtPackDesc);
+RTCString *VBoxExtPackLoadDesc(const char *a_pszDir, PVBOXEXTPACKDESC a_pExtPackDesc, PRTFSOBJINFO a_pObjInfo);
+RTCString *VBoxExtPackLoadDescFromVfsFile(RTVFSFILE hVfsFile, PVBOXEXTPACKDESC a_pExtPackDesc, PRTFSOBJINFO a_pObjInfo);
+RTCString *VBoxExtPackExtractNameFromTarballPath(const char *pszTarball);
+void VBoxExtPackFreeDesc(PVBOXEXTPACKDESC a_pExtPackDesc);
+bool VBoxExtPackIsValidName(const char *pszName);
+bool VBoxExtPackIsValidMangledName(const char *pszMangledName, size_t cchMax = RTSTR_MAX);
+RTCString *VBoxExtPackMangleName(const char *pszName);
+RTCString *VBoxExtPackUnmangleName(const char *pszMangledName, size_t cbMax);
+int VBoxExtPackCalcDir(char *pszExtPackDir, size_t cbExtPackDir, const char *pszParentDir, const char *pszName);
+bool VBoxExtPackIsValidVersionString(const char *pszVersion);
+bool VBoxExtPackIsValidEditionString(const char *pszEdition);
+bool VBoxExtPackIsValidModuleString(const char *pszModule);
+
+int VBoxExtPackValidateMember(const char *pszName, RTVFSOBJTYPE enmType, RTVFSOBJ hVfsObj, char *pszError, size_t cbError);
+int VBoxExtPackOpenTarFss(RTFILE hTarballFile, char *pszError, size_t cbError, PRTVFSFSSTREAM phTarFss, PRTMANIFEST phFileManifest);
+int VBoxExtPackValidateTarball(RTFILE hTarballFile, const char *pszExtPackName,
+ const char *pszTarball, const char *pszTarballDigest,
+ char *pszError, size_t cbError,
+ PRTMANIFEST phValidManifest, PRTVFSFILE phXmlFile, RTCString *pStrDigest);
+#endif /* __cplusplus */
+
+#endif /* !MAIN_INCLUDED_ExtPackUtil_h */
+
diff --git a/src/VBox/Main/include/Global.h b/src/VBox/Main/include/Global.h
new file mode 100644
index 00000000..8244ef61
--- /dev/null
+++ b/src/VBox/Main/include/Global.h
@@ -0,0 +1,255 @@
+/* $Id: Global.h $ */
+/** @file
+ * VirtualBox COM API - Global Declarations and Definitions.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_Global_h
+#define MAIN_INCLUDED_Global_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/* interface definitions */
+#include "VBox/com/VirtualBox.h"
+
+#include <VBox/ostypes.h>
+
+#include <iprt/types.h>
+
+#define VBOXOSHINT_NONE 0
+#define VBOXOSHINT_64BIT RT_BIT(0)
+#define VBOXOSHINT_HWVIRTEX RT_BIT(1)
+#define VBOXOSHINT_IOAPIC RT_BIT(2)
+#define VBOXOSHINT_EFI RT_BIT(3)
+#define VBOXOSHINT_PAE RT_BIT(4)
+#define VBOXOSHINT_USBHID RT_BIT(5)
+#define VBOXOSHINT_HPET RT_BIT(6)
+#define VBOXOSHINT_USBTABLET RT_BIT(7)
+#define VBOXOSHINT_RTCUTC RT_BIT(8)
+#define VBOXOSHINT_ACCEL2D RT_BIT(9)
+#define VBOXOSHINT_ACCEL3D RT_BIT(10)
+#define VBOXOSHINT_FLOPPY RT_BIT(11)
+#define VBOXOSHINT_NOUSB RT_BIT(12)
+#define VBOXOSHINT_TFRESET RT_BIT(13)
+#define VBOXOSHINT_USB3 RT_BIT(14)
+#define VBOXOSHINT_X2APIC RT_BIT(15)
+#define VBOXOSHINT_EFI_SECUREBOOT RT_BIT(16)
+#define VBOXOSHINT_TPM RT_BIT(17)
+#define VBOXOSHINT_TPM2 RT_BIT(18)
+#define VBOXOSHINT_WDDM_GRAPHICS RT_BIT(19)
+
+/** The VBoxVRDP kludge extension pack name.
+ *
+ * This is not a valid extension pack name (dashes are not allowed), and
+ * hence will not conflict with real extension packs.
+ */
+#define VBOXVRDP_KLUDGE_EXTPACK_NAME "Built-in-VBoxVRDP"
+
+/** The VBoxPuelCrypto kludge extension pack name.
+ *
+ * This is not a valid extension pack name (dashes are not allowed), and
+ * hence will not conflict with real extension packs.
+ */
+#define VBOXPUELCRYPTO_KLUDGE_EXTPACK_NAME "Built-in-VBoxPuelCrypto"
+
+/**
+ * Contains global static definitions that can be referenced by all COM classes
+ * regardless of the apartment.
+ */
+class Global
+{
+public:
+
+ /** Represents OS Type <-> string mappings. */
+ struct OSType
+ {
+ const char *familyId; /* utf-8 */
+ const char *familyDescription; /* utf-8 */
+ const char *id; /* utf-8, VM config file value */
+ const char *description; /* utf-8 */
+ const VBOXOSTYPE osType;
+ const uint32_t osHint;
+ const uint32_t recommendedCPUCount;
+ const uint32_t recommendedRAM;
+ const uint32_t recommendedVRAM;
+ const uint64_t recommendedHDD;
+ const GraphicsControllerType_T graphicsControllerType;
+ const NetworkAdapterType_T networkAdapterType;
+ const uint32_t numSerialEnabled;
+ const StorageControllerType_T dvdStorageControllerType;
+ const StorageBus_T dvdStorageBusType;
+ const StorageControllerType_T hdStorageControllerType;
+ const StorageBus_T hdStorageBusType;
+ const ChipsetType_T chipsetType;
+ const IommuType_T iommuType;
+ const AudioControllerType_T audioControllerType;
+ const AudioCodecType_T audioCodecType;
+ };
+
+ static const OSType sOSTypes[];
+ static size_t cOSTypes;
+
+ /**
+ * Maps VBOXOSTYPE to the OS type which is used in VM configs.
+ */
+ static const char *OSTypeId(VBOXOSTYPE aOSType);
+
+ /**
+ * Maps an OS type ID string to index into sOSTypes.
+ * @returns index on success, UINT32_MAX if not found.
+ */
+ static uint32_t getOSTypeIndexFromId(const char *pszId);
+
+ /**
+ * Get the network adapter limit for each chipset type.
+ */
+ static uint32_t getMaxNetworkAdapters(ChipsetType_T aChipsetType);
+
+ /**
+ * Returns @c true if the given machine state is an online state. This is a
+ * recommended way to detect if the VM is online (being executed in a
+ * dedicated process) or not. Note that some online states are also
+ * transitional states (see #IsTransient()).
+ */
+ static bool IsOnline(MachineState_T aState)
+ {
+ return aState >= MachineState_FirstOnline &&
+ aState <= MachineState_LastOnline;
+ }
+
+ /**
+ * Returns @c true if the given machine state is a transient state. This is
+ * a recommended way to detect if the VM is performing some potentially
+ * lengthy operation (such as starting, stopping, saving, deleting
+ * snapshot, etc.). Note some (but not all) transitional states are also
+ * online states (see #IsOnline()).
+ */
+ static bool IsTransient(MachineState_T aState)
+ {
+ return aState >= MachineState_FirstTransient &&
+ aState <= MachineState_LastTransient;
+ }
+
+ /**
+ * Shortcut to <tt>IsOnline(aState) || IsTransient(aState)</tt>. When it returns
+ * @c false, the VM is turned off (no VM process) and not busy with
+ * another exclusive operation.
+ */
+ static bool IsOnlineOrTransient(MachineState_T aState)
+ {
+ return IsOnline(aState) || IsTransient(aState);
+ }
+
+ /**
+ * Stringify a machine state - translated.
+ *
+ * Drop the Global:: prefix and include StringifyEnums.h for an untranslated
+ * version of this method.
+ *
+ * @returns Pointer to a read only string.
+ * @param aState Valid machine state.
+ */
+ static const char *stringifyMachineState(MachineState_T aState);
+
+ /**
+ * Stringify a session state - translated.
+ *
+ * Drop the Global:: prefix and include StringifyEnums.h for an untranslated
+ * version of this method.
+ *
+ * @returns Pointer to a read only string.
+ * @param aState Valid session state.
+ */
+ static const char *stringifySessionState(SessionState_T aState);
+
+ /**
+ * Stringify a device type.
+ *
+ * Drop the Global:: prefix and include StringifyEnums.h for an untranslated
+ * version of this method.
+ *
+ * @returns Pointer to a read only string.
+ * @param aType The device type.
+ */
+ static const char *stringifyDeviceType(DeviceType_T aType);
+
+ /**
+ * Stringify a storage controller type.
+ *
+ * Drop the Global:: prefix and include StringifyEnums.h for an untranslated
+ * version of this method.
+ *
+ * @returns Pointer to a read only string.
+ * @param aType The storage controller type.
+ */
+ static const char *stringifyStorageControllerType(StorageControllerType_T aType);
+
+#if 0 /* unused */
+ /**
+ * Stringify a storage bus type.
+ *
+ * Drop the Global:: prefix and include StringifyEnums.h for an untranslated
+ * version of this method.
+ *
+ * @returns Pointer to a read only string.
+ * @param aBus The storage bus type.
+ */
+ static const char *stringifyStorageBus(StorageBus_T aBus);
+
+ /**
+ * Stringify a reason.
+ *
+ * Drop the Global:: prefix and include StringifyEnums.h for an untranslated
+ * version of this method.
+ *
+ * @returns Pointer to a read only string.
+ * @param aReason The reason code.
+ */
+ static const char *stringifyReason(Reason_T aReason);
+#endif
+
+ /**
+ * Try convert a COM status code to a VirtualBox status code (VBox/err.h).
+ *
+ * @returns VBox status code.
+ * @param aComStatus COM status code.
+ */
+ static int vboxStatusCodeFromCOM(HRESULT aComStatus);
+
+ /**
+ * Try convert a VirtualBox status code (VBox/err.h) to a COM status code.
+ *
+ * This is mainly intended for dealing with vboxStatusCodeFromCOM() return
+ * values. If used on anything else, it won't be able to cope with most of the
+ * input!
+ *
+ * @returns COM status code.
+ * @param aVBoxStatus VBox status code.
+ */
+ static HRESULT vboxStatusCodeToCOM(int aVBoxStatus);
+};
+
+#endif /* !MAIN_INCLUDED_Global_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/GraphicsAdapterImpl.h b/src/VBox/Main/include/GraphicsAdapterImpl.h
new file mode 100644
index 00000000..6be3dccf
--- /dev/null
+++ b/src/VBox/Main/include/GraphicsAdapterImpl.h
@@ -0,0 +1,88 @@
+/* $Id: GraphicsAdapterImpl.h $ */
+/** @file
+ * Implementation of IGraphicsAdapter in VBoxSVC - Header.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GraphicsAdapterImpl_h
+#define MAIN_INCLUDED_GraphicsAdapterImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "GraphicsAdapterWrap.h"
+
+
+namespace settings
+{
+ struct GraphicsAdapter;
+};
+
+
+class ATL_NO_VTABLE GraphicsAdapter :
+ public GraphicsAdapterWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(GraphicsAdapter)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aParent);
+ HRESULT init(Machine *aParent, GraphicsAdapter *aThat);
+ HRESULT initCopy(Machine *aParent, GraphicsAdapter *aThat);
+ void uninit();
+
+
+ // public methods only for internal purposes
+ HRESULT i_loadSettings(const settings::GraphicsAdapter &data);
+ HRESULT i_saveSettings(settings::GraphicsAdapter &data);
+
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(GraphicsAdapter *aThat);
+
+private:
+
+ // wrapped IGraphicsAdapter properties
+ HRESULT getGraphicsControllerType(GraphicsControllerType_T *aGraphicsControllerType);
+ HRESULT setGraphicsControllerType(GraphicsControllerType_T aGraphicsControllerType);
+ HRESULT getVRAMSize(ULONG *aVRAMSize);
+ HRESULT setVRAMSize(ULONG aVRAMSize);
+ HRESULT getAccelerate3DEnabled(BOOL *aAccelerate3DEnabled);
+ HRESULT setAccelerate3DEnabled(BOOL aAccelerate3DEnabled);
+ HRESULT getAccelerate2DVideoEnabled(BOOL *aAccelerate2DVideoEnabled);
+ HRESULT setAccelerate2DVideoEnabled(BOOL aAccelerate2DVideoEnabled);
+ HRESULT getMonitorCount(ULONG *aMonitorCount);
+ HRESULT setMonitorCount(ULONG aMonitorCount);
+
+ Machine * const mParent;
+ const ComObjPtr<GraphicsAdapter> mPeer;
+ Backupable<settings::GraphicsAdapter> mData;
+};
+
+#endif /* !MAIN_INCLUDED_GraphicsAdapterImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/GuestCtrlImplPrivate.h b/src/VBox/Main/include/GuestCtrlImplPrivate.h
new file mode 100644
index 00000000..abdaafe0
--- /dev/null
+++ b/src/VBox/Main/include/GuestCtrlImplPrivate.h
@@ -0,0 +1,1422 @@
+/* $Id: GuestCtrlImplPrivate.h $ */
+/** @file
+ * Internal helpers/structures for guest control functionality.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GuestCtrlImplPrivate_h
+#define MAIN_INCLUDED_GuestCtrlImplPrivate_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "ConsoleImpl.h"
+#include "Global.h"
+
+#include <iprt/asm.h>
+#include <iprt/env.h>
+#include <iprt/semaphore.h>
+#include <iprt/cpp/utils.h>
+
+#include <VBox/com/com.h>
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/com/string.h>
+#include <VBox/com/VirtualBox.h>
+#include <VBox/err.h> /* VERR_GSTCTL_GUEST_ERROR */
+
+#include <map>
+#include <vector>
+
+using namespace com;
+
+#ifdef VBOX_WITH_GUEST_CONTROL
+# include <VBox/GuestHost/GuestControl.h>
+# include <VBox/HostServices/GuestControlSvc.h>
+using namespace guestControl;
+#endif
+
+/** Vector holding a process' CPU affinity. */
+typedef std::vector <LONG> ProcessAffinity;
+/** Vector holding process startup arguments. */
+typedef std::vector <Utf8Str> ProcessArguments;
+
+class GuestProcessStreamBlock;
+class GuestSession;
+
+
+/**
+ * Simple structure mantaining guest credentials.
+ */
+struct GuestCredentials
+{
+ Utf8Str mUser;
+ Utf8Str mPassword;
+ Utf8Str mDomain;
+};
+
+
+/**
+ * Wrapper around the RTEnv API, unusable base class.
+ *
+ * @remarks Feel free to elevate this class to iprt/cpp/env.h as RTCEnv.
+ */
+class GuestEnvironmentBase
+{
+public:
+ /**
+ * Default constructor.
+ *
+ * The user must invoke one of the init methods before using the object.
+ */
+ GuestEnvironmentBase(void)
+ : m_hEnv(NIL_RTENV)
+ , m_cRefs(1)
+ , m_fFlags(0)
+ { }
+
+ /**
+ * Destructor.
+ */
+ virtual ~GuestEnvironmentBase(void)
+ {
+ Assert(m_cRefs <= 1);
+ int rc = RTEnvDestroy(m_hEnv); AssertRC(rc);
+ m_hEnv = NIL_RTENV;
+ }
+
+ /**
+ * Retains a reference to this object.
+ * @returns New reference count.
+ * @remarks Sharing an object is currently only safe if no changes are made to
+ * it because RTENV does not yet implement any locking. For the only
+ * purpose we need this, implementing IGuestProcess::environment by
+ * using IGuestSession::environmentBase, that's fine as the session
+ * base environment is immutable.
+ */
+ uint32_t retain(void)
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&m_cRefs);
+ Assert(cRefs > 1); Assert(cRefs < _1M);
+ return cRefs;
+
+ }
+ /** Useful shortcut. */
+ uint32_t retainConst(void) const { return unconst(this)->retain(); }
+
+ /**
+ * Releases a reference to this object, deleting the object when reaching zero.
+ * @returns New reference count.
+ */
+ uint32_t release(void)
+ {
+ uint32_t cRefs = ASMAtomicDecU32(&m_cRefs);
+ Assert(cRefs < _1M);
+ if (cRefs == 0)
+ delete this;
+ return cRefs;
+ }
+
+ /** Useful shortcut. */
+ uint32_t releaseConst(void) const { return unconst(this)->retain(); }
+
+ /**
+ * Checks if the environment has been successfully initialized or not.
+ *
+ * @returns @c true if initialized, @c false if not.
+ */
+ bool isInitialized(void) const
+ {
+ return m_hEnv != NIL_RTENV;
+ }
+
+ /**
+ * Returns the variable count.
+ * @return Number of variables.
+ * @sa RTEnvCountEx
+ */
+ uint32_t count(void) const
+ {
+ return RTEnvCountEx(m_hEnv);
+ }
+
+ /**
+ * Deletes the environment change record entirely.
+ *
+ * The count() method will return zero after this call.
+ *
+ * @sa RTEnvReset
+ */
+ void reset(void)
+ {
+ int rc = RTEnvReset(m_hEnv);
+ AssertRC(rc);
+ }
+
+ /**
+ * Exports the environment change block as an array of putenv style strings.
+ *
+ *
+ * @returns VINF_SUCCESS or VERR_NO_MEMORY.
+ * @param pArray The output array.
+ */
+ int queryPutEnvArray(std::vector<com::Utf8Str> *pArray) const
+ {
+ uint32_t cVars = RTEnvCountEx(m_hEnv);
+ try
+ {
+ pArray->resize(cVars);
+ for (uint32_t iVar = 0; iVar < cVars; iVar++)
+ {
+ const char *psz = RTEnvGetByIndexRawEx(m_hEnv, iVar);
+ AssertReturn(psz, VERR_INTERNAL_ERROR_3); /* someone is racing us! */
+ (*pArray)[iVar] = psz;
+ }
+ return VINF_SUCCESS;
+ }
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+ }
+
+ /**
+ * Applies an array of putenv style strings.
+ *
+ * @returns IPRT status code.
+ * @param rArray The array with the putenv style strings.
+ * @param pidxError Where to return the index causing trouble on
+ * failure. Optional.
+ * @sa RTEnvPutEx
+ */
+ int applyPutEnvArray(const std::vector<com::Utf8Str> &rArray, size_t *pidxError = NULL)
+ {
+ size_t const cArray = rArray.size();
+ for (size_t i = 0; i < cArray; i++)
+ {
+ int rc = RTEnvPutEx(m_hEnv, rArray[i].c_str());
+ if (RT_FAILURE(rc))
+ {
+ if (pidxError)
+ *pidxError = i;
+ return rc;
+ }
+ }
+ return VINF_SUCCESS;
+ }
+
+ /**
+ * Applies the changes from another environment to this.
+ *
+ * @returns IPRT status code.
+ * @param rChanges Reference to an environment which variables will be
+ * imported and, if it's a change record, schedule
+ * variable unsets will be applied.
+ * @sa RTEnvApplyChanges
+ */
+ int applyChanges(const GuestEnvironmentBase &rChanges)
+ {
+ return RTEnvApplyChanges(m_hEnv, rChanges.m_hEnv);
+ }
+
+ /**
+ * See RTEnvQueryUtf8Block for details.
+ * @returns IPRT status code.
+ * @param ppszzBlock Where to return the block pointer.
+ * @param pcbBlock Where to optionally return the block size.
+ * @sa RTEnvQueryUtf8Block
+ */
+ int queryUtf8Block(char **ppszzBlock, size_t *pcbBlock)
+ {
+ return RTEnvQueryUtf8Block(m_hEnv, true /*fSorted*/, ppszzBlock, pcbBlock);
+ }
+
+ /**
+ * Frees what queryUtf8Block returned, NULL ignored.
+ * @sa RTEnvFreeUtf8Block
+ */
+ static void freeUtf8Block(char *pszzBlock)
+ {
+ return RTEnvFreeUtf8Block(pszzBlock);
+ }
+
+ /**
+ * Applies a block on the format returned by queryUtf8Block.
+ *
+ * @returns IPRT status code.
+ * @param pszzBlock Pointer to the block.
+ * @param cbBlock The size of the block.
+ * @param fNoEqualMeansUnset Whether the lack of a '=' (equal) sign in a
+ * string means it should be unset (@c true), or if
+ * it means the variable should be defined with an
+ * empty value (@c false, the default).
+ * @todo move this to RTEnv!
+ */
+ int copyUtf8Block(const char *pszzBlock, size_t cbBlock, bool fNoEqualMeansUnset = false)
+ {
+ int rc = VINF_SUCCESS;
+ while (cbBlock > 0 && *pszzBlock != '\0')
+ {
+ const char *pszEnd = (const char *)memchr(pszzBlock, '\0', cbBlock);
+ if (!pszEnd)
+ return VERR_BUFFER_UNDERFLOW;
+ int rc2;
+ if (fNoEqualMeansUnset || strchr(pszzBlock, '='))
+ rc2 = RTEnvPutEx(m_hEnv, pszzBlock);
+ else
+ rc2 = RTEnvSetEx(m_hEnv, pszzBlock, "");
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+
+ /* Advance. */
+ cbBlock -= pszEnd - pszzBlock;
+ if (cbBlock < 2)
+ return VERR_BUFFER_UNDERFLOW;
+ cbBlock--;
+ pszzBlock = pszEnd + 1;
+ }
+
+ /* The remainder must be zero padded. */
+ if (RT_SUCCESS(rc))
+ {
+ if (ASMMemIsZero(pszzBlock, cbBlock))
+ return VINF_SUCCESS;
+ return VERR_TOO_MUCH_DATA;
+ }
+ return rc;
+ }
+
+ /**
+ * Get an environment variable.
+ *
+ * @returns IPRT status code.
+ * @param rName The variable name.
+ * @param pValue Where to return the value.
+ * @sa RTEnvGetEx
+ */
+ int getVariable(const com::Utf8Str &rName, com::Utf8Str *pValue) const
+ {
+ size_t cchNeeded;
+ int rc = RTEnvGetEx(m_hEnv, rName.c_str(), NULL, 0, &cchNeeded);
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_BUFFER_OVERFLOW)
+ {
+ try
+ {
+ pValue->reserve(cchNeeded + 1);
+ rc = RTEnvGetEx(m_hEnv, rName.c_str(), pValue->mutableRaw(), pValue->capacity(), NULL);
+ pValue->jolt();
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = VERR_NO_STR_MEMORY;
+ }
+ }
+ return rc;
+ }
+
+ /**
+ * Checks if the given variable exists.
+ *
+ * @returns @c true if it exists, @c false if not or if it's an scheduled unset
+ * in a environment change record.
+ * @param rName The variable name.
+ * @sa RTEnvExistEx
+ */
+ bool doesVariableExist(const com::Utf8Str &rName) const
+ {
+ return RTEnvExistEx(m_hEnv, rName.c_str());
+ }
+
+ /**
+ * Set an environment variable.
+ *
+ * @returns IPRT status code.
+ * @param rName The variable name.
+ * @param rValue The value of the variable.
+ * @sa RTEnvSetEx
+ */
+ int setVariable(const com::Utf8Str &rName, const com::Utf8Str &rValue)
+ {
+ return RTEnvSetEx(m_hEnv, rName.c_str(), rValue.c_str());
+ }
+
+ /**
+ * Unset an environment variable.
+ *
+ * @returns IPRT status code.
+ * @param rName The variable name.
+ * @sa RTEnvUnsetEx
+ */
+ int unsetVariable(const com::Utf8Str &rName)
+ {
+ return RTEnvUnsetEx(m_hEnv, rName.c_str());
+ }
+
+protected:
+ /**
+ * Copy constructor.
+ * @throws HRESULT
+ */
+ GuestEnvironmentBase(const GuestEnvironmentBase &rThat, bool fChangeRecord, uint32_t fFlags = 0)
+ : m_hEnv(NIL_RTENV)
+ , m_cRefs(1)
+ , m_fFlags(fFlags)
+ {
+ int rc = cloneCommon(rThat, fChangeRecord);
+ if (RT_FAILURE(rc))
+ throw (Global::vboxStatusCodeToCOM(rc));
+ }
+
+ /**
+ * Common clone/copy method with type conversion abilities.
+ *
+ * @returns IPRT status code.
+ * @param rThat The object to clone.
+ * @param fChangeRecord Whether the this instance is a change record (true)
+ * or normal (false) environment.
+ */
+ int cloneCommon(const GuestEnvironmentBase &rThat, bool fChangeRecord)
+ {
+ int rc = VINF_SUCCESS;
+ RTENV hNewEnv = NIL_RTENV;
+ if (rThat.m_hEnv != NIL_RTENV)
+ {
+ /*
+ * Clone it.
+ */
+ if (RTEnvIsChangeRecord(rThat.m_hEnv) == fChangeRecord)
+ rc = RTEnvClone(&hNewEnv, rThat.m_hEnv);
+ else
+ {
+ /* Need to type convert it. */
+ if (fChangeRecord)
+ rc = RTEnvCreateChangeRecordEx(&hNewEnv, rThat.m_fFlags);
+ else
+ rc = RTEnvCreateEx(&hNewEnv, rThat.m_fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTEnvApplyChanges(hNewEnv, rThat.m_hEnv);
+ if (RT_FAILURE(rc))
+ RTEnvDestroy(hNewEnv);
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Create an empty one so the object works smoothly.
+ * (Relevant for GuestProcessStartupInfo and internal commands.)
+ */
+ if (fChangeRecord)
+ rc = RTEnvCreateChangeRecordEx(&hNewEnv, rThat.m_fFlags);
+ else
+ rc = RTEnvCreateEx(&hNewEnv, rThat.m_fFlags);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ RTEnvDestroy(m_hEnv);
+ m_hEnv = hNewEnv;
+ m_fFlags = rThat.m_fFlags;
+ }
+ return rc;
+ }
+
+
+ /** The environment change record. */
+ RTENV m_hEnv;
+ /** Reference counter. */
+ uint32_t volatile m_cRefs;
+ /** RTENV_CREATE_F_XXX. */
+ uint32_t m_fFlags;
+};
+
+class GuestEnvironmentChanges;
+
+
+/**
+ * Wrapper around the RTEnv API for a normal environment.
+ */
+class GuestEnvironment : public GuestEnvironmentBase
+{
+public:
+ /**
+ * Default constructor.
+ *
+ * The user must invoke one of the init methods before using the object.
+ */
+ GuestEnvironment(void)
+ : GuestEnvironmentBase()
+ { }
+
+ /**
+ * Copy operator.
+ * @param rThat The object to copy.
+ * @throws HRESULT
+ */
+ GuestEnvironment(const GuestEnvironment &rThat)
+ : GuestEnvironmentBase(rThat, false /*fChangeRecord*/)
+ { }
+
+ /**
+ * Copy operator.
+ * @param rThat The object to copy.
+ * @throws HRESULT
+ */
+ GuestEnvironment(const GuestEnvironmentBase &rThat)
+ : GuestEnvironmentBase(rThat, false /*fChangeRecord*/)
+ { }
+
+ /**
+ * Initialize this as a normal environment block.
+ * @returns IPRT status code.
+ * @param fFlags RTENV_CREATE_F_XXX
+ */
+ int initNormal(uint32_t fFlags)
+ {
+ AssertReturn(m_hEnv == NIL_RTENV, VERR_WRONG_ORDER);
+ m_fFlags = fFlags;
+ return RTEnvCreateEx(&m_hEnv, fFlags);
+ }
+
+ /**
+ * Replaces this environemnt with that in @a rThat.
+ *
+ * @returns IPRT status code
+ * @param rThat The environment to copy. If it's a different type
+ * we'll convert the data to a normal environment block.
+ */
+ int copy(const GuestEnvironmentBase &rThat)
+ {
+ return cloneCommon(rThat, false /*fChangeRecord*/);
+ }
+
+ /**
+ * @copydoc copy()
+ */
+ GuestEnvironment &operator=(const GuestEnvironmentBase &rThat)
+ {
+ int rc = copy(rThat);
+ if (RT_FAILURE(rc))
+ throw (Global::vboxStatusCodeToCOM(rc));
+ return *this;
+ }
+
+ /** @copydoc copy() */
+ GuestEnvironment &operator=(const GuestEnvironment &rThat)
+ { return operator=((const GuestEnvironmentBase &)rThat); }
+
+ /** @copydoc copy() */
+ GuestEnvironment &operator=(const GuestEnvironmentChanges &rThat)
+ { return operator=((const GuestEnvironmentBase &)rThat); }
+
+};
+
+
+/**
+ * Wrapper around the RTEnv API for a environment change record.
+ *
+ * This class is used as a record of changes to be applied to a different
+ * environment block (in VBoxService before launching a new process).
+ */
+class GuestEnvironmentChanges : public GuestEnvironmentBase
+{
+public:
+ /**
+ * Default constructor.
+ *
+ * The user must invoke one of the init methods before using the object.
+ */
+ GuestEnvironmentChanges(void)
+ : GuestEnvironmentBase()
+ { }
+
+ /**
+ * Copy operator.
+ * @param rThat The object to copy.
+ * @throws HRESULT
+ */
+ GuestEnvironmentChanges(const GuestEnvironmentChanges &rThat)
+ : GuestEnvironmentBase(rThat, true /*fChangeRecord*/)
+ { }
+
+ /**
+ * Copy operator.
+ * @param rThat The object to copy.
+ * @throws HRESULT
+ */
+ GuestEnvironmentChanges(const GuestEnvironmentBase &rThat)
+ : GuestEnvironmentBase(rThat, true /*fChangeRecord*/)
+ { }
+
+ /**
+ * Initialize this as a environment change record.
+ * @returns IPRT status code.
+ * @param fFlags RTENV_CREATE_F_XXX
+ */
+ int initChangeRecord(uint32_t fFlags)
+ {
+ AssertReturn(m_hEnv == NIL_RTENV, VERR_WRONG_ORDER);
+ m_fFlags = fFlags;
+ return RTEnvCreateChangeRecordEx(&m_hEnv, fFlags);
+ }
+
+ /**
+ * Replaces this environemnt with that in @a rThat.
+ *
+ * @returns IPRT status code
+ * @param rThat The environment to copy. If it's a different type
+ * we'll convert the data to a set of changes.
+ */
+ int copy(const GuestEnvironmentBase &rThat)
+ {
+ return cloneCommon(rThat, true /*fChangeRecord*/);
+ }
+
+ /**
+ * @copydoc copy()
+ */
+ GuestEnvironmentChanges &operator=(const GuestEnvironmentBase &rThat)
+ {
+ int rc = copy(rThat);
+ if (RT_FAILURE(rc))
+ throw (Global::vboxStatusCodeToCOM(rc));
+ return *this;
+ }
+
+ /** @copydoc copy() */
+ GuestEnvironmentChanges &operator=(const GuestEnvironmentChanges &rThat)
+ { return operator=((const GuestEnvironmentBase &)rThat); }
+
+ /** @copydoc copy() */
+ GuestEnvironmentChanges &operator=(const GuestEnvironment &rThat)
+ { return operator=((const GuestEnvironmentBase &)rThat); }
+};
+
+/**
+ * Class for keeping guest error information.
+ */
+class GuestErrorInfo
+{
+public:
+
+ /**
+ * Enumeration for specifying the guest error type.
+ */
+ enum Type
+ {
+ /** Guest error is anonymous. Avoid this. */
+ Type_Anonymous = 0,
+ /** Guest error is from a guest session. */
+ Type_Session,
+ /** Guest error is from a guest process. */
+ Type_Process,
+ /** Guest error is from a guest file object. */
+ Type_File,
+ /** Guest error is from a guest directory object. */
+ Type_Directory,
+ /** Guest error is from a the built-in toolbox "vbox_cat" command. */
+ Type_ToolCat,
+ /** Guest error is from a the built-in toolbox "vbox_ls" command. */
+ Type_ToolLs,
+ /** Guest error is from a the built-in toolbox "vbox_rm" command. */
+ Type_ToolRm,
+ /** Guest error is from a the built-in toolbox "vbox_mkdir" command. */
+ Type_ToolMkDir,
+ /** Guest error is from a the built-in toolbox "vbox_mktemp" command. */
+ Type_ToolMkTemp,
+ /** Guest error is from a the built-in toolbox "vbox_stat" command. */
+ Type_ToolStat,
+ /** The usual 32-bit hack. */
+ Type_32BIT_HACK = 0x7fffffff
+ };
+
+ /**
+ * Initialization constructor.
+ *
+ * @param eType Error type to use.
+ * @param rc IPRT-style rc to use.
+ * @param pcszWhat Subject to use.
+ */
+ GuestErrorInfo(GuestErrorInfo::Type eType, int rc, const char *pcszWhat)
+ {
+ int rc2 = setV(eType, rc, pcszWhat);
+ if (RT_FAILURE(rc2))
+ throw rc2;
+ }
+
+ /**
+ * Returns the (IPRT-style) rc of this error.
+ *
+ * @returns VBox status code.
+ */
+ int getRc(void) const { return mRc; }
+
+ /**
+ * Returns the type of this error.
+ *
+ * @returns Error type.
+ */
+ Type getType(void) const { return mType; }
+
+ /**
+ * Returns the subject of this error.
+ *
+ * @returns Subject as a string.
+ */
+ Utf8Str getWhat(void) const { return mWhat; }
+
+ /**
+ * Sets the error information using a variable arguments list (va_list).
+ *
+ * @returns VBox status code.
+ * @param eType Error type to use.
+ * @param rc IPRT-style rc to use.
+ * @param pcszWhat Subject to use.
+ */
+ int setV(GuestErrorInfo::Type eType, int rc, const char *pcszWhat)
+ {
+ mType = eType;
+ mRc = rc;
+ mWhat = pcszWhat;
+
+ return VINF_SUCCESS;
+ }
+
+protected:
+
+ /** Error type. */
+ Type mType;
+ /** IPRT-style error code. */
+ int mRc;
+ /** Subject string related to this error. */
+ Utf8Str mWhat;
+};
+
+/**
+ * Structure for keeping all the relevant guest directory
+ * information around.
+ */
+struct GuestDirectoryOpenInfo
+{
+ GuestDirectoryOpenInfo(void)
+ : mFlags(0) { }
+
+ /** The directory path. */
+ Utf8Str mPath;
+ /** Then open filter. */
+ Utf8Str mFilter;
+ /** Opening flags. */
+ uint32_t mFlags;
+};
+
+
+/**
+ * Structure for keeping all the relevant guest file
+ * information around.
+ */
+struct GuestFileOpenInfo
+{
+ GuestFileOpenInfo(void)
+ : mAccessMode((FileAccessMode_T)0)
+ , mOpenAction((FileOpenAction_T)0)
+ , mSharingMode((FileSharingMode_T)0)
+ , mCreationMode(0)
+ , mfOpenEx(0) { }
+
+ /**
+ * Validates a file open info.
+ *
+ * @returns \c true if valid, \c false if not.
+ */
+ bool IsValid(void) const
+ {
+ if (mfOpenEx) /** @todo Open flags not implemented yet. */
+ return false;
+
+ switch (mOpenAction)
+ {
+ case FileOpenAction_OpenExisting:
+ break;
+ case FileOpenAction_OpenOrCreate:
+ break;
+ case FileOpenAction_CreateNew:
+ break;
+ case FileOpenAction_CreateOrReplace:
+ break;
+ case FileOpenAction_OpenExistingTruncated:
+ {
+ if ( mAccessMode == FileAccessMode_ReadOnly
+ || mAccessMode == FileAccessMode_AppendOnly
+ || mAccessMode == FileAccessMode_AppendRead)
+ return false;
+ break;
+ }
+ case FileOpenAction_AppendOrCreate: /* Deprecated, do not use. */
+ break;
+ default:
+ AssertFailedReturn(false);
+ break;
+ }
+
+ return true; /** @todo Do we need more checks here? */
+ }
+
+ /** The filename. */
+ Utf8Str mFilename;
+ /** The file access mode. */
+ FileAccessMode_T mAccessMode;
+ /** The file open action. */
+ FileOpenAction_T mOpenAction;
+ /** The file sharing mode. */
+ FileSharingMode_T mSharingMode;
+ /** Octal creation mode. */
+ uint32_t mCreationMode;
+ /** Extended open flags (currently none defined). */
+ uint32_t mfOpenEx;
+};
+
+
+/**
+ * Structure representing information of a
+ * file system object.
+ */
+struct GuestFsObjData
+{
+ GuestFsObjData(void)
+ : mType(FsObjType_Unknown)
+ , mObjectSize(0)
+ , mAllocatedSize(0)
+ , mAccessTime(0)
+ , mBirthTime(0)
+ , mChangeTime(0)
+ , mModificationTime(0)
+ , mUID(0)
+ , mGID(0)
+ , mNodeID(0)
+ , mNodeIDDevice(0)
+ , mNumHardLinks(0)
+ , mDeviceNumber(0)
+ , mGenerationID(0)
+ , mUserFlags(0) { }
+
+ /** @name Helper functions to extract the data from a certin VBoxService tool's guest stream block.
+ * @{ */
+ int FromLs(const GuestProcessStreamBlock &strmBlk, bool fLong);
+ int FromRm(const GuestProcessStreamBlock &strmBlk);
+ int FromStat(const GuestProcessStreamBlock &strmBlk);
+ int FromMkTemp(const GuestProcessStreamBlock &strmBlk);
+ /** @} */
+
+ /** @name Static helper functions to work with time from stream block keys.
+ * @{ */
+ static PRTTIMESPEC TimeSpecFromKey(const GuestProcessStreamBlock &strmBlk, const Utf8Str &strKey, PRTTIMESPEC pTimeSpec);
+ static int64_t UnixEpochNsFromKey(const GuestProcessStreamBlock &strmBlk, const Utf8Str &strKey);
+ /** @} */
+
+ /** @name helper functions to work with IPRT stuff.
+ * @{ */
+ RTFMODE GetFileMode(void) const;
+ /** @} */
+
+ Utf8Str mName;
+ FsObjType_T mType;
+ Utf8Str mFileAttrs;
+ int64_t mObjectSize;
+ int64_t mAllocatedSize;
+ int64_t mAccessTime;
+ int64_t mBirthTime;
+ int64_t mChangeTime;
+ int64_t mModificationTime;
+ Utf8Str mUserName;
+ int32_t mUID;
+ int32_t mGID;
+ Utf8Str mGroupName;
+ Utf8Str mACL;
+ int64_t mNodeID;
+ uint32_t mNodeIDDevice;
+ uint32_t mNumHardLinks;
+ uint32_t mDeviceNumber;
+ uint32_t mGenerationID;
+ uint32_t mUserFlags;
+};
+
+
+/**
+ * Structure for keeping all the relevant guest session
+ * startup parameters around.
+ */
+class GuestSessionStartupInfo
+{
+public:
+
+ GuestSessionStartupInfo(void)
+ : mID(UINT32_MAX)
+ , mIsInternal(false /* Non-internal session */)
+ , mOpenTimeoutMS(30 * 1000 /* 30s opening timeout */)
+ , mOpenFlags(0 /* No opening flags set */) { }
+
+ /** The session's friendly name. Optional. */
+ Utf8Str mName;
+ /** The session's unique ID. Used to encode a context ID.
+ * UINT32_MAX if not initialized. */
+ uint32_t mID;
+ /** Flag indicating if this is an internal session
+ * or not. Internal session are not accessible by
+ * public API clients. */
+ bool mIsInternal;
+ /** Timeout (in ms) used for opening the session. */
+ uint32_t mOpenTimeoutMS;
+ /** Session opening flags. */
+ uint32_t mOpenFlags;
+};
+
+
+/**
+ * Structure for keeping all the relevant guest process
+ * startup parameters around.
+ */
+class GuestProcessStartupInfo
+{
+public:
+
+ GuestProcessStartupInfo(void)
+ : mFlags(ProcessCreateFlag_None)
+ , mTimeoutMS(UINT32_MAX /* No timeout by default */)
+ , mPriority(ProcessPriority_Default)
+ , mAffinity(0) { }
+
+ /** The process' friendly name. */
+ Utf8Str mName;
+ /** The executable. */
+ Utf8Str mExecutable;
+ /** Arguments vector (starting with argument \#0). */
+ ProcessArguments mArguments;
+ /** The process environment change record. */
+ GuestEnvironmentChanges mEnvironmentChanges;
+ /** Process creation flags. */
+ uint32_t mFlags;
+ /** Timeout (in ms) the process is allowed to run.
+ * Specify UINT32_MAX if no timeout (unlimited run time) is given. */
+ ULONG mTimeoutMS;
+ /** Process priority. */
+ ProcessPriority_T mPriority;
+ /** Process affinity. At the moment we
+ * only support 64 VCPUs. API and
+ * guest can do more already! */
+ uint64_t mAffinity;
+};
+
+
+/**
+ * Class representing the "value" side of a "key=value" pair.
+ */
+class GuestProcessStreamValue
+{
+public:
+
+ GuestProcessStreamValue(void) { }
+ GuestProcessStreamValue(const char *pszValue)
+ : mValue(pszValue) {}
+
+ GuestProcessStreamValue(const GuestProcessStreamValue& aThat)
+ : mValue(aThat.mValue) { }
+
+ /** Copy assignment operator. */
+ GuestProcessStreamValue &operator=(GuestProcessStreamValue const &a_rThat) RT_NOEXCEPT
+ {
+ mValue = a_rThat.mValue;
+
+ return *this;
+ }
+
+ Utf8Str mValue;
+};
+
+/** Map containing "key=value" pairs of a guest process stream. */
+typedef std::pair< Utf8Str, GuestProcessStreamValue > GuestCtrlStreamPair;
+typedef std::map < Utf8Str, GuestProcessStreamValue > GuestCtrlStreamPairMap;
+typedef std::map < Utf8Str, GuestProcessStreamValue >::iterator GuestCtrlStreamPairMapIter;
+typedef std::map < Utf8Str, GuestProcessStreamValue >::const_iterator GuestCtrlStreamPairMapIterConst;
+
+/**
+ * Class representing a block of stream pairs (key=value). Each block in a raw guest
+ * output stream is separated by "\0\0", each pair is separated by "\0". The overall
+ * end of a guest stream is marked by "\0\0\0\0".
+ */
+class GuestProcessStreamBlock
+{
+public:
+
+ GuestProcessStreamBlock(void);
+
+ virtual ~GuestProcessStreamBlock(void);
+
+public:
+
+ void Clear(void);
+
+#ifdef DEBUG
+ void DumpToLog(void) const;
+#endif
+
+ const char *GetString(const char *pszKey) const;
+ size_t GetCount(void) const;
+ int GetRc(void) const;
+ int GetInt64Ex(const char *pszKey, int64_t *piVal) const;
+ int64_t GetInt64(const char *pszKey) const;
+ int GetUInt32Ex(const char *pszKey, uint32_t *puVal) const;
+ uint32_t GetUInt32(const char *pszKey, uint32_t uDefault = 0) const;
+ int32_t GetInt32(const char *pszKey, int32_t iDefault = 0) const;
+
+ bool IsEmpty(void) { return mPairs.empty(); }
+
+ int SetValue(const char *pszKey, const char *pszValue);
+
+protected:
+
+ GuestCtrlStreamPairMap mPairs;
+};
+
+/** Vector containing multiple allocated stream pair objects. */
+typedef std::vector< GuestProcessStreamBlock > GuestCtrlStreamObjects;
+typedef std::vector< GuestProcessStreamBlock >::iterator GuestCtrlStreamObjectsIter;
+typedef std::vector< GuestProcessStreamBlock >::const_iterator GuestCtrlStreamObjectsIterConst;
+
+/**
+ * Class for parsing machine-readable guest process output by VBoxService'
+ * toolbox commands ("vbox_ls", "vbox_stat" etc), aka "guest stream".
+ */
+class GuestProcessStream
+{
+
+public:
+
+ GuestProcessStream();
+
+ virtual ~GuestProcessStream();
+
+public:
+
+ int AddData(const BYTE *pbData, size_t cbData);
+
+ void Destroy();
+
+#ifdef DEBUG
+ void Dump(const char *pszFile);
+#endif
+
+ size_t GetOffset() { return m_offBuffer; }
+
+ size_t GetSize() { return m_cbUsed; }
+
+ int ParseBlock(GuestProcessStreamBlock &streamBlock);
+
+protected:
+
+ /** Maximum allowed size the stream buffer can grow to.
+ * Defaults to 32 MB. */
+ size_t m_cbMax;
+ /** Currently allocated size of internal stream buffer. */
+ size_t m_cbAllocated;
+ /** Currently used size at m_offBuffer. */
+ size_t m_cbUsed;
+ /** Current byte offset within the internal stream buffer. */
+ size_t m_offBuffer;
+ /** Internal stream buffer. */
+ BYTE *m_pbBuffer;
+};
+
+class Guest;
+class Progress;
+
+class GuestWaitEventPayload
+{
+
+public:
+
+ GuestWaitEventPayload(void)
+ : uType(0),
+ cbData(0),
+ pvData(NULL) { }
+
+ /**
+ * Initialization constructor. Will throw() VBox status code (rc).
+ *
+ * @param uTypePayload Payload type to set.
+ * @param pvPayload Pointer to payload data to set (deep copy).
+ * @param cbPayload Size (in bytes) of payload data to set.
+ */
+ GuestWaitEventPayload(uint32_t uTypePayload,
+ const void *pvPayload, uint32_t cbPayload)
+ : uType(0),
+ cbData(0),
+ pvData(NULL)
+ {
+ int rc = copyFrom(uTypePayload, pvPayload, cbPayload);
+ if (RT_FAILURE(rc))
+ throw rc;
+ }
+
+ virtual ~GuestWaitEventPayload(void)
+ {
+ Clear();
+ }
+
+ GuestWaitEventPayload& operator=(const GuestWaitEventPayload &that)
+ {
+ CopyFromDeep(that);
+ return *this;
+ }
+
+public:
+
+ void Clear(void)
+ {
+ if (pvData)
+ {
+ Assert(cbData);
+ RTMemFree(pvData);
+ cbData = 0;
+ pvData = NULL;
+ }
+ uType = 0;
+ }
+
+ int CopyFromDeep(const GuestWaitEventPayload &payload)
+ {
+ return copyFrom(payload.uType, payload.pvData, payload.cbData);
+ }
+
+ const void* Raw(void) const { return pvData; }
+
+ size_t Size(void) const { return cbData; }
+
+ uint32_t Type(void) const { return uType; }
+
+ void* MutableRaw(void) { return pvData; }
+
+ Utf8Str ToString(void)
+ {
+ const char *pszStr = (const char *)pvData;
+ size_t cbStr = cbData;
+
+ if (RT_FAILURE(RTStrValidateEncodingEx(pszStr, cbStr,
+ RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED | RTSTR_VALIDATE_ENCODING_EXACT_LENGTH)))
+ {
+ AssertFailed();
+ return "";
+ }
+
+ return Utf8Str(pszStr, cbStr);
+ }
+
+protected:
+
+ int copyFrom(uint32_t uTypePayload, const void *pvPayload, uint32_t cbPayload)
+ {
+ if (cbPayload > _64K) /* Paranoia. */
+ return VERR_TOO_MUCH_DATA;
+
+ Clear();
+
+ int rc = VINF_SUCCESS;
+
+ if (cbPayload)
+ {
+ pvData = RTMemAlloc(cbPayload);
+ if (pvData)
+ {
+ uType = uTypePayload;
+
+ memcpy(pvData, pvPayload, cbPayload);
+ cbData = cbPayload;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ uType = uTypePayload;
+
+ pvData = NULL;
+ cbData = 0;
+ }
+
+ return rc;
+ }
+
+protected:
+
+ /** Type of payload. */
+ uint32_t uType;
+ /** Size (in bytes) of payload. */
+ uint32_t cbData;
+ /** Pointer to actual payload data. */
+ void *pvData;
+};
+
+class GuestWaitEventBase
+{
+
+protected:
+
+ GuestWaitEventBase(void);
+ virtual ~GuestWaitEventBase(void);
+
+public:
+
+ uint32_t ContextID(void) { return mCID; };
+ int GuestResult(void) { return mGuestRc; }
+ int Result(void) { return mRc; }
+ GuestWaitEventPayload & Payload(void) { return mPayload; }
+ int SignalInternal(int rc, int guestRc, const GuestWaitEventPayload *pPayload);
+ int Wait(RTMSINTERVAL uTimeoutMS);
+
+protected:
+
+ int Init(uint32_t uCID);
+
+protected:
+
+ /* Shutdown indicator. */
+ bool mfAborted;
+ /* Associated context ID (CID). */
+ uint32_t mCID;
+ /** The event semaphore for triggering
+ * the actual event. */
+ RTSEMEVENT mEventSem;
+ /** The event's overall result. If
+ * set to VERR_GSTCTL_GUEST_ERROR,
+ * mGuestRc will contain the actual
+ * error code from the guest side. */
+ int mRc;
+ /** The event'S overall result from the
+ * guest side. If used, mRc must be
+ * set to VERR_GSTCTL_GUEST_ERROR. */
+ int mGuestRc;
+ /** The event's payload data. Optional. */
+ GuestWaitEventPayload mPayload;
+};
+
+/** List of public guest event types. */
+typedef std::list < VBoxEventType_T > GuestEventTypes;
+
+class GuestWaitEvent : public GuestWaitEventBase
+{
+
+public:
+
+ GuestWaitEvent(void);
+ virtual ~GuestWaitEvent(void);
+
+public:
+
+ int Init(uint32_t uCID);
+ int Init(uint32_t uCID, const GuestEventTypes &lstEvents);
+ int Cancel(void);
+ const ComPtr<IEvent> Event(void) { return mEvent; }
+ bool HasGuestError(void) const { return mRc == VERR_GSTCTL_GUEST_ERROR; }
+ int GetGuestError(void) const { return mGuestRc; }
+ int SignalExternal(IEvent *pEvent);
+ const GuestEventTypes &Types(void) { return mEventTypes; }
+ size_t TypeCount(void) { return mEventTypes.size(); }
+
+protected:
+
+ /** List of public event types this event should
+ * be signalled on. Optional. */
+ GuestEventTypes mEventTypes;
+ /** Pointer to the actual public event, if any. */
+ ComPtr<IEvent> mEvent;
+};
+/** Map of pointers to guest events. The primary key
+ * contains the context ID. */
+typedef std::map < uint32_t, GuestWaitEvent* > GuestWaitEvents;
+/** Map of wait events per public guest event. Nice for
+ * faster lookups when signalling a whole event group. */
+typedef std::map < VBoxEventType_T, GuestWaitEvents > GuestEventGroup;
+
+class GuestBase
+{
+
+public:
+
+ GuestBase(void);
+ virtual ~GuestBase(void);
+
+public:
+
+ /** Signals a wait event using a public guest event; also used for
+ * for external event listeners. */
+ int signalWaitEvent(VBoxEventType_T aType, IEvent *aEvent);
+ /** Signals a wait event using a guest rc. */
+ int signalWaitEventInternal(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, int guestRc, const GuestWaitEventPayload *pPayload);
+ /** Signals a wait event without letting public guest events know,
+ * extended director's cut version. */
+ int signalWaitEventInternalEx(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, int rc, int guestRc, const GuestWaitEventPayload *pPayload);
+
+public:
+
+ int baseInit(void);
+ void baseUninit(void);
+ int cancelWaitEvents(void);
+ int dispatchGeneric(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb);
+ int generateContextID(uint32_t uSessionID, uint32_t uObjectID, uint32_t *puContextID);
+ int registerWaitEvent(uint32_t uSessionID, uint32_t uObjectID, GuestWaitEvent **ppEvent);
+ int registerWaitEventEx(uint32_t uSessionID, uint32_t uObjectID, const GuestEventTypes &lstEvents, GuestWaitEvent **ppEvent);
+ int unregisterWaitEvent(GuestWaitEvent *pEvent);
+ int waitForEvent(GuestWaitEvent *pEvent, uint32_t uTimeoutMS, VBoxEventType_T *pType, IEvent **ppEvent);
+
+public:
+
+ static FsObjType_T fileModeToFsObjType(RTFMODE fMode);
+ static const char *fsObjTypeToStr(FsObjType_T enmType);
+ static const char *pathStyleToStr(PathStyle_T enmPathStyle);
+ static Utf8Str getErrorAsString(const Utf8Str &strAction, const GuestErrorInfo& guestErrorInfo);
+ static Utf8Str getErrorAsString(const GuestErrorInfo &guestErrorInfo);
+
+protected:
+
+ /** Pointer to the console object. Needed
+ * for HGCM (VMMDev) communication. */
+ Console *mConsole;
+ /** The next context ID counter component for this object. */
+ uint32_t mNextContextID;
+ /** Local listener for handling the waiting events
+ * internally. */
+ ComPtr<IEventListener> mLocalListener;
+ /** Critical section for wait events access. */
+ RTCRITSECT mWaitEventCritSect;
+ /** Map of registered wait events per event group. */
+ GuestEventGroup mWaitEventGroups;
+ /** Map of registered wait events. */
+ GuestWaitEvents mWaitEvents;
+};
+
+/**
+ * Virtual class (interface) for guest objects (processes, files, ...) --
+ * contains all per-object callback management.
+ */
+class GuestObject : public GuestBase
+{
+ friend class GuestSession;
+
+public:
+
+ GuestObject(void);
+ virtual ~GuestObject(void);
+
+public:
+
+ ULONG getObjectID(void) { return mObjectID; }
+
+protected:
+
+ /**
+ * Called by IGuestSession when the session status has been changed.
+ *
+ * @returns VBox status code.
+ * @param enmSessionStatus New session status.
+ */
+ virtual int i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus) = 0;
+
+ /**
+ * Called by IGuestSession right before this object gets
+ * unregistered (removed) from the public object list.
+ */
+ virtual int i_onUnregister(void) = 0;
+
+ /** Callback dispatcher -- must be implemented by the actual object. */
+ virtual int i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb) = 0;
+
+protected:
+
+ int bindToSession(Console *pConsole, GuestSession *pSession, uint32_t uObjectID);
+ int registerWaitEvent(const GuestEventTypes &lstEvents, GuestWaitEvent **ppEvent);
+ int sendMessage(uint32_t uFunction, uint32_t cParms, PVBOXHGCMSVCPARM paParms);
+
+protected:
+
+ /** @name Common parameters for all derived objects. They have their own
+ * mData structure to keep their specific data around.
+ * @{ */
+ /** Pointer to parent session. Per definition
+ * this objects *always* lives shorter than the
+ * parent.
+ * @todo r=bird: When wanting to use mSession in the
+ * IGuestProcess::getEnvironment() implementation I wanted to access
+ * GuestSession::mData::mpBaseEnvironment. Seeing the comment in
+ * GuestProcess::terminate() saying:
+ * "Now only API clients still can hold references to it."
+ * and recalling seeing similar things in VirtualBox.xidl or some such place,
+ * I'm wondering how this "per definition" behavior is enforced. Is there any
+ * GuestProcess:uninit() call or similar magic that invalidates objects that
+ * GuestSession loses track of in place like GuestProcess::terminate() that I've
+ * failed to spot?
+ *
+ * Please enlighten me.
+ */
+ GuestSession *mSession;
+ /** The object ID -- must be unique for each guest
+ * object and is encoded into the context ID. Must
+ * be set manually when initializing the object.
+ *
+ * For guest processes this is the internal PID,
+ * for guest files this is the internal file ID. */
+ uint32_t mObjectID;
+ /** @} */
+};
+
+/** Returns the path separator based on \a a_enmPathStyle as a C-string. */
+#define PATH_STYLE_SEP_STR(a_enmPathStyle) a_enmPathStyle == PathStyle_DOS ? "\\" : "/"
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+# define PATH_STYLE_NATIVE PathStyle_DOS
+#else
+# define PATH_STYLE_NATIVE PathStyle_UNIX
+#endif
+
+/**
+ * Class for handling guest / host path functions.
+ */
+class GuestPath
+{
+private:
+
+ /**
+ * Default constructor.
+ *
+ * Not directly instantiable (yet).
+ */
+ GuestPath(void) { }
+
+public:
+
+ /** @name Static helper functions.
+ * @{ */
+ static int BuildDestinationPath(const Utf8Str &strSrcPath, PathStyle_T enmSrcPathStyle, Utf8Str &strDstPath, PathStyle_T enmDstPathStyle);
+ static int Translate(Utf8Str &strPath, PathStyle_T enmSrcPathStyle, PathStyle_T enmDstPathStyle, bool fForce = false);
+ /** @} */
+};
+#endif /* !MAIN_INCLUDED_GuestCtrlImplPrivate_h */
+
diff --git a/src/VBox/Main/include/GuestDebugControlImpl.h b/src/VBox/Main/include/GuestDebugControlImpl.h
new file mode 100644
index 00000000..23f684c4
--- /dev/null
+++ b/src/VBox/Main/include/GuestDebugControlImpl.h
@@ -0,0 +1,82 @@
+/* $Id: GuestDebugControlImpl.h $ */
+/** @file
+ *
+ * VirtualBox/GuestDebugControl COM class implementation
+ */
+
+/*
+ * Copyright (C) 2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GuestDebugControlImpl_h
+#define MAIN_INCLUDED_GuestDebugControlImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "GuestDebugControlWrap.h"
+
+namespace settings
+{
+ struct Debugging;
+}
+
+class ATL_NO_VTABLE GuestDebugControl :
+ public GuestDebugControlWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(GuestDebugControl)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aParent);
+ HRESULT init(Machine *aParent, GuestDebugControl *aThat);
+ HRESULT initCopy(Machine *aParent, GuestDebugControl *aThat);
+ void uninit();
+
+ // public internal methods
+ HRESULT i_loadSettings(const settings::Debugging &data);
+ HRESULT i_saveSettings(settings::Debugging &data);
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(GuestDebugControl *aThat);
+ Machine *i_getMachine() const;
+
+private:
+
+ // wrapped IGuestDebugControl properties
+ HRESULT getDebugProvider(GuestDebugProvider_T *aDebugProvider);
+ HRESULT setDebugProvider(GuestDebugProvider_T aDebugProvider);
+ HRESULT getDebugIoProvider(GuestDebugIoProvider_T *aDebugIoProvider);
+ HRESULT setDebugIoProvider(GuestDebugIoProvider_T aDebugIoProvider);
+ HRESULT getDebugAddress(com::Utf8Str &aAddress);
+ HRESULT setDebugAddress(const com::Utf8Str &aAddress);
+ HRESULT getDebugPort(ULONG *aPort);
+ HRESULT setDebugPort(ULONG aPort);
+
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_GuestDebugControlImpl_h */
diff --git a/src/VBox/Main/include/GuestDirectoryImpl.h b/src/VBox/Main/include/GuestDirectoryImpl.h
new file mode 100644
index 00000000..c2d6db7f
--- /dev/null
+++ b/src/VBox/Main/include/GuestDirectoryImpl.h
@@ -0,0 +1,108 @@
+/* $Id: GuestDirectoryImpl.h $ */
+/** @file
+ * VirtualBox Main - Guest directory handling implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GuestDirectoryImpl_h
+#define MAIN_INCLUDED_GuestDirectoryImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "GuestDirectoryWrap.h"
+#include "GuestFsObjInfoImpl.h"
+#include "GuestProcessImpl.h"
+
+class GuestSession;
+
+/**
+ * TODO
+ */
+class ATL_NO_VTABLE GuestDirectory :
+ public GuestDirectoryWrap,
+ public GuestObject
+{
+public:
+ /** @name COM and internal init/term/mapping cruft.
+ * @{ */
+ DECLARE_COMMON_CLASS_METHODS(GuestDirectory)
+
+ int init(Console *pConsole, GuestSession *pSession, ULONG aObjectID, const GuestDirectoryOpenInfo &openInfo);
+ void uninit(void);
+
+ HRESULT FinalConstruct(void);
+ void FinalRelease(void);
+ /** @} */
+
+public:
+ /** @name Implemented virtual methods from GuestObject.
+ * @{ */
+ int i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb);
+ int i_onUnregister(void);
+ int i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus);
+ /** @} */
+
+public:
+ /** @name Public internal methods.
+ * @{ */
+ int i_closeInternal(int *pGuestRc);
+ int i_read(ComObjPtr<GuestFsObjInfo> &fsObjInfo, int *pGuestRc);
+ int i_readInternal(GuestFsObjData &objData, int *prcGuest);
+ /** @} */
+
+public:
+ /** @name Public static internal methods.
+ * @{ */
+ static Utf8Str i_guestErrorToString(int rcGuest, const char *pcszWhat);
+ /** @} */
+
+private:
+
+ /** Wrapped @name IGuestDirectory properties
+ * @{ */
+ HRESULT getDirectoryName(com::Utf8Str &aDirectoryName);
+ HRESULT getFilter(com::Utf8Str &aFilter);
+ /** @} */
+
+ /** Wrapped @name IGuestDirectory methods.
+ * @{ */
+ HRESULT close();
+ HRESULT read(ComPtr<IFsObjInfo> &aObjInfo);
+ /** @} */
+
+ struct Data
+ {
+ /** The directory's open info. */
+ GuestDirectoryOpenInfo mOpenInfo;
+ /** The process tool instance to use. */
+ GuestProcessTool mProcessTool;
+ /** Object data cache.
+ * Its mName attribute acts as a beacon if the cache is valid or not. */
+ GuestFsObjData mObjData;
+ } mData;
+};
+
+#endif /* !MAIN_INCLUDED_GuestDirectoryImpl_h */
+
diff --git a/src/VBox/Main/include/GuestDnDPrivate.h b/src/VBox/Main/include/GuestDnDPrivate.h
new file mode 100644
index 00000000..0be48cea
--- /dev/null
+++ b/src/VBox/Main/include/GuestDnDPrivate.h
@@ -0,0 +1,1108 @@
+/* $Id: GuestDnDPrivate.h $ */
+/** @file
+ * Private guest drag and drop code, used by GuestDnDTarget +
+ * GuestDnDSource.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GuestDnDPrivate_h
+#define MAIN_INCLUDED_GuestDnDPrivate_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+
+#include <VBox/hgcmsvc.h> /* For PVBOXHGCMSVCPARM. */
+#include <VBox/GuestHost/DragAndDrop.h>
+#include <VBox/GuestHost/DragAndDropDefs.h>
+#include <VBox/HostServices/DragAndDropSvc.h>
+
+/**
+ * Forward prototype declarations.
+ */
+class Guest;
+class GuestDnDBase;
+class GuestDnDState;
+class GuestDnDSource;
+class GuestDnDTarget;
+class Progress;
+
+/**
+ * Type definitions.
+ */
+
+/** List (vector) of MIME types. */
+typedef std::vector<com::Utf8Str> GuestDnDMIMEList;
+
+/**
+ * Class to handle a guest DnD callback event.
+ */
+class GuestDnDCallbackEvent
+{
+public:
+
+ GuestDnDCallbackEvent(void)
+ : m_SemEvent(NIL_RTSEMEVENT)
+ , m_Rc(VINF_SUCCESS) { }
+
+ virtual ~GuestDnDCallbackEvent(void);
+
+public:
+
+ int Reset(void);
+
+ int Notify(int rc = VINF_SUCCESS);
+
+ int Result(void) const { return m_Rc; }
+
+ int Wait(RTMSINTERVAL msTimeout);
+
+protected:
+
+ /** Event semaphore to notify on error/completion. */
+ RTSEMEVENT m_SemEvent;
+ /** Callback result. */
+ int m_Rc;
+};
+
+/**
+ * Struct for handling the (raw) meta data.
+ */
+struct GuestDnDMetaData
+{
+ GuestDnDMetaData(void)
+ : pvData(NULL)
+ , cbData(0)
+ , cbAllocated(0)
+ , cbAnnounced(0) { }
+
+ virtual ~GuestDnDMetaData(void)
+ {
+ reset();
+ }
+
+ /**
+ * Adds new meta data.
+ *
+ * @returns New (total) meta data size in bytes.
+ * @param pvDataAdd Pointer of data to add.
+ * @param cbDataAdd Size (in bytes) of data to add.
+ */
+ size_t add(const void *pvDataAdd, size_t cbDataAdd)
+ {
+ LogFlowThisFunc(("cbAllocated=%zu, cbAnnounced=%zu, pvDataAdd=%p, cbDataAdd=%zu\n",
+ cbAllocated, cbAnnounced, pvDataAdd, cbDataAdd));
+ if (!cbDataAdd)
+ return 0;
+ AssertPtrReturn(pvDataAdd, 0);
+
+ const size_t cbAllocatedTmp = cbData + cbDataAdd;
+ if (cbAllocatedTmp > cbAllocated)
+ {
+ int rc = resize(cbAllocatedTmp);
+ if (RT_FAILURE(rc))
+ return 0;
+ }
+
+ Assert(cbAllocated >= cbData + cbDataAdd);
+ memcpy((uint8_t *)pvData + cbData, pvDataAdd, cbDataAdd);
+
+ cbData += cbDataAdd;
+ cbAnnounced = cbData;
+
+ return cbData;
+ }
+
+ /**
+ * Adds new meta data.
+ *
+ * @returns New (total) meta data size in bytes.
+ * @param vecAdd Meta data to add.
+ */
+ size_t add(const std::vector<BYTE> &vecAdd)
+ {
+ if (!vecAdd.size())
+ return 0;
+
+ if (vecAdd.size() > UINT32_MAX) /* Paranoia. */
+ return 0;
+
+ return add(&vecAdd.front(), (uint32_t)vecAdd.size());
+ }
+
+ /**
+ * Resets (clears) all data.
+ */
+ void reset(void)
+ {
+ strFmt = "";
+
+ if (pvData)
+ {
+ Assert(cbAllocated);
+ RTMemFree(pvData);
+ pvData = NULL;
+ }
+
+ cbData = 0;
+ cbAllocated = 0;
+ cbAnnounced = 0;
+ }
+
+ /**
+ * Resizes the allocation size.
+ *
+ * @returns VBox status code.
+ * @param cbSize New allocation size (in bytes).
+ */
+ int resize(size_t cbSize)
+ {
+ if (!cbSize)
+ {
+ reset();
+ return VINF_SUCCESS;
+ }
+
+ if (cbSize == cbAllocated)
+ return VINF_SUCCESS;
+
+ cbSize = RT_ALIGN_Z(cbSize, PAGE_SIZE);
+
+ if (cbSize > _32M) /* Meta data can be up to 32MB. */
+ return VERR_BUFFER_OVERFLOW;
+
+ void *pvTmp = NULL;
+ if (!cbAllocated)
+ {
+ Assert(cbData == 0);
+ pvTmp = RTMemAllocZ(cbSize);
+ }
+ else
+ {
+ AssertPtr(pvData);
+ pvTmp = RTMemRealloc(pvData, cbSize);
+ }
+
+ if (pvTmp)
+ {
+ pvData = pvTmp;
+ cbAllocated = cbSize;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_NO_MEMORY;
+ }
+
+ /** Format string of this meta data. */
+ com::Utf8Str strFmt;
+ /** Pointer to allocated meta data. */
+ void *pvData;
+ /** Used bytes of meta data. Must not exceed cbAllocated. */
+ size_t cbData;
+ /** Size (in bytes) of allocated meta data. */
+ size_t cbAllocated;
+ /** Size (in bytes) of announced meta data. */
+ size_t cbAnnounced;
+};
+
+/**
+ * Struct for accounting shared DnD data to be sent/received.
+ */
+struct GuestDnDData
+{
+ GuestDnDData(void)
+ : cbExtra(0)
+ , cbProcessed(0) { }
+
+ virtual ~GuestDnDData(void)
+ {
+ reset();
+ }
+
+ /**
+ * Adds processed data to the internal accounting.
+ *
+ * @returns New processed data size.
+ * @param cbDataAdd Bytes to add as done processing.
+ */
+ size_t addProcessed(size_t cbDataAdd)
+ {
+ const size_t cbTotal = getTotalAnnounced(); RT_NOREF(cbTotal);
+ AssertReturn(cbProcessed + cbDataAdd <= cbTotal, 0);
+ cbProcessed += cbDataAdd;
+ return cbProcessed;
+ }
+
+ /**
+ * Returns whether all data has been processed or not.
+ *
+ * @returns \c true if all data has been processed, \c false if not.
+ */
+ bool isComplete(void) const
+ {
+ const size_t cbTotal = getTotalAnnounced();
+ LogFlowFunc(("cbProcessed=%zu, cbTotal=%zu\n", cbProcessed, cbTotal));
+ AssertReturn(cbProcessed <= cbTotal, true);
+ return (cbProcessed == cbTotal);
+ }
+
+ /**
+ * Returns the percentage (0-100) of the already processed data.
+ *
+ * @returns Percentage (0-100) of the already processed data.
+ */
+ uint8_t getPercentComplete(void) const
+ {
+ const size_t cbTotal = getTotalAnnounced();
+ return (uint8_t)(cbProcessed * 100 / RT_MAX(cbTotal, 1));
+ }
+
+ /**
+ * Returns the remaining (outstanding) data left for processing.
+ *
+ * @returns Remaining (outstanding) data (in bytes) left for processing.
+ */
+ size_t getRemaining(void) const
+ {
+ const size_t cbTotal = getTotalAnnounced();
+ AssertReturn(cbProcessed <= cbTotal, 0);
+ return cbTotal - cbProcessed;
+ }
+
+ /**
+ * Returns the total data size (in bytes) announced.
+ *
+ * @returns Total data size (in bytes) announced.
+ */
+ size_t getTotalAnnounced(void) const
+ {
+ return Meta.cbAnnounced + cbExtra;
+ }
+
+ /**
+ * Returns the total data size (in bytes) available.
+ * For receiving data, this represents the already received data.
+ * For sending data, this represents the data left to send.
+ *
+ * @returns Total data size (in bytes) available.
+ */
+ size_t getTotalAvailable(void) const
+ {
+ return Meta.cbData + cbExtra;
+ }
+
+ /**
+ * Resets all data.
+ */
+ void reset(void)
+ {
+ Meta.reset();
+
+ cbExtra = 0;
+ cbProcessed = 0;
+ }
+
+ /** For storing the actual meta data.
+ * This might be an URI list or just plain raw data,
+ * according to the format being sent. */
+ GuestDnDMetaData Meta;
+ /** Extra data to send/receive (in bytes). Can be 0 for raw data.
+ * For (file) transfers this is the total size for all files. */
+ size_t cbExtra;
+ /** Overall size (in bytes) of processed data. */
+ size_t cbProcessed;
+};
+
+/** Initial object context state / no state set. */
+#define DND_OBJ_STATE_NONE 0
+/** The header was received / sent. */
+#define DND_OBJ_STATE_HAS_HDR RT_BIT(0)
+/** Validation mask for object context state. */
+#define DND_OBJ_STATE_VALID_MASK UINT32_C(0x00000001)
+
+/**
+ * Base class for keeping around DnD (file) transfer data.
+ * Used for sending / receiving transfer data.
+ */
+struct GuestDnDTransferData
+{
+
+public:
+
+ GuestDnDTransferData(void)
+ : cObjToProcess(0)
+ , cObjProcessed(0)
+ , pvScratchBuf(NULL)
+ , cbScratchBuf(0) { }
+
+ virtual ~GuestDnDTransferData(void)
+ {
+ destroy();
+ }
+
+ /**
+ * Initializes a transfer data object.
+ *
+ * @param cbBuf Scratch buffer size (in bytes) to use.
+ * If not specified, DND_DEFAULT_CHUNK_SIZE will be used.
+ */
+ int init(size_t cbBuf = DND_DEFAULT_CHUNK_SIZE)
+ {
+ reset();
+
+ pvScratchBuf = RTMemAlloc(cbBuf);
+ if (!pvScratchBuf)
+ return VERR_NO_MEMORY;
+
+ cbScratchBuf = cbBuf;
+ return VINF_SUCCESS;
+ }
+
+ /**
+ * Destroys a transfer data object.
+ */
+ void destroy(void)
+ {
+ reset();
+
+ if (pvScratchBuf)
+ {
+ Assert(cbScratchBuf);
+ RTMemFree(pvScratchBuf);
+ pvScratchBuf = NULL;
+ }
+ cbScratchBuf = 0;
+ }
+
+ /**
+ * Resets a transfer data object.
+ */
+ void reset(void)
+ {
+ LogFlowFuncEnter();
+
+ cObjToProcess = 0;
+ cObjProcessed = 0;
+ }
+
+ /**
+ * Returns whether this transfer object is complete or not.
+ *
+ * @returns \c true if complete, \c false if not.
+ */
+ bool isComplete(void) const
+ {
+ return (cObjProcessed == cObjToProcess);
+ }
+
+ /** Number of objects to process. */
+ uint64_t cObjToProcess;
+ /** Number of objects already processed. */
+ uint64_t cObjProcessed;
+ /** Pointer to an optional scratch buffer to use for
+ * doing the actual chunk transfers. */
+ void *pvScratchBuf;
+ /** Size (in bytes) of scratch buffer. */
+ size_t cbScratchBuf;
+};
+
+/**
+ * Class for keeping around DnD transfer send data (Host -> Guest).
+ */
+struct GuestDnDTransferSendData : public GuestDnDTransferData
+{
+ GuestDnDTransferSendData()
+ : fObjState(0)
+ {
+ RT_ZERO(List);
+ int rc2 = DnDTransferListInit(&List);
+ AssertRC(rc2);
+ }
+
+ virtual ~GuestDnDTransferSendData()
+ {
+ destroy();
+ }
+
+ /**
+ * Destroys the object.
+ */
+ void destroy(void)
+ {
+ DnDTransferListDestroy(&List);
+ }
+
+ /**
+ * Resets the object.
+ */
+ void reset(void)
+ {
+ DnDTransferListReset(&List);
+ fObjState = 0;
+
+ GuestDnDTransferData::reset();
+ }
+
+ /** Transfer List to handle. */
+ DNDTRANSFERLIST List;
+ /** Current state of object in transfer.
+ * This is needed for keeping compatibility to old(er) DnD HGCM protocols.
+ *
+ * At the moment we only support transferring one object at a time. */
+ uint32_t fObjState;
+};
+
+/**
+ * Context structure for sending data to the guest.
+ */
+struct GuestDnDSendCtx : public GuestDnDData
+{
+ GuestDnDSendCtx(void);
+
+ /**
+ * Resets the object.
+ */
+ void reset(void);
+
+ /** Pointer to guest target class this context belongs to. */
+ GuestDnDTarget *pTarget;
+ /** Pointer to guest state this context belongs to. */
+ GuestDnDState *pState;
+ /** Target (VM) screen ID. */
+ uint32_t uScreenID;
+ /** Transfer data structure. */
+ GuestDnDTransferSendData Transfer;
+ /** Callback event to use. */
+ GuestDnDCallbackEvent EventCallback;
+};
+
+struct GuestDnDTransferRecvData : public GuestDnDTransferData
+{
+ GuestDnDTransferRecvData()
+ {
+ RT_ZERO(DroppedFiles);
+ int rc2 = DnDDroppedFilesInit(&DroppedFiles);
+ AssertRC(rc2);
+
+ RT_ZERO(List);
+ rc2 = DnDTransferListInit(&List);
+ AssertRC(rc2);
+
+ RT_ZERO(ObjCur);
+ rc2 = DnDTransferObjectInit(&ObjCur);
+ AssertRC(rc2);
+ }
+
+ virtual ~GuestDnDTransferRecvData()
+ {
+ destroy();
+ }
+
+ /**
+ * Destroys the object.
+ */
+ void destroy(void)
+ {
+ DnDTransferListDestroy(&List);
+ }
+
+ /**
+ * Resets the object.
+ */
+ void reset(void)
+ {
+ DnDDroppedFilesClose(&DroppedFiles);
+ DnDTransferListReset(&List);
+ DnDTransferObjectReset(&ObjCur);
+
+ GuestDnDTransferData::reset();
+ }
+
+ /** The "VirtualBox Dropped Files" directory on the host we're going
+ * to utilize for transferring files from guest to the host. */
+ DNDDROPPEDFILES DroppedFiles;
+ /** Transfer List to handle.
+ * Currently we only support one transfer list at a time. */
+ DNDTRANSFERLIST List;
+ /** Current transfer object being handled.
+ * Currently we only support one transfer object at a time. */
+ DNDTRANSFEROBJECT ObjCur;
+};
+
+/**
+ * Context structure for receiving data from the guest.
+ */
+struct GuestDnDRecvCtx : public GuestDnDData
+{
+ GuestDnDRecvCtx(void);
+
+ /**
+ * Resets the object.
+ */
+ void reset(void);
+
+ /** Pointer to guest source class this context belongs to. */
+ GuestDnDSource *pSource;
+ /** Pointer to guest state this context belongs to. */
+ GuestDnDState *pState;
+ /** Formats offered by the guest (and supported by the host). */
+ GuestDnDMIMEList lstFmtOffered;
+ /** Original drop format requested to receive from the guest. */
+ com::Utf8Str strFmtReq;
+ /** Intermediate drop format to be received from the guest.
+ * Some original drop formats require a different intermediate
+ * drop format:
+ *
+ * Receiving a file link as "text/plain" requires still to
+ * receive the file from the guest as "text/uri-list" first,
+ * then pointing to the file path on the host with the data
+ * in "text/plain" format returned. */
+ com::Utf8Str strFmtRecv;
+ /** Desired drop action to perform on the host.
+ * Needed to tell the guest if data has to be
+ * deleted e.g. when moving instead of copying. */
+ VBOXDNDACTION enmAction;
+ /** Transfer data structure. */
+ GuestDnDTransferRecvData Transfer;
+ /** Callback event to use. */
+ GuestDnDCallbackEvent EventCallback;
+};
+
+/**
+ * Class for maintainig a (buffered) guest DnD message.
+ */
+class GuestDnDMsg
+{
+public:
+
+ GuestDnDMsg(void)
+ : uMsg(0)
+ , cParms(0)
+ , cParmsAlloc(0)
+ , paParms(NULL) { }
+
+ virtual ~GuestDnDMsg(void)
+ {
+ reset();
+ }
+
+public:
+
+ /**
+ * Appends a new HGCM parameter to the message and returns the pointer to it.
+ */
+ PVBOXHGCMSVCPARM getNextParam(void)
+ {
+ if (cParms >= cParmsAlloc)
+ {
+ if (!paParms)
+ paParms = (PVBOXHGCMSVCPARM)RTMemAlloc(4 * sizeof(VBOXHGCMSVCPARM));
+ else
+ paParms = (PVBOXHGCMSVCPARM)RTMemRealloc(paParms, (cParmsAlloc + 4) * sizeof(VBOXHGCMSVCPARM));
+ if (!paParms)
+ throw VERR_NO_MEMORY;
+ RT_BZERO(&paParms[cParmsAlloc], 4 * sizeof(VBOXHGCMSVCPARM));
+ cParmsAlloc += 4;
+ }
+
+ return &paParms[cParms++];
+ }
+
+ /**
+ * Returns the current parameter count.
+ *
+ * @returns Current parameter count.
+ */
+ uint32_t getCount(void) const { return cParms; }
+
+ /**
+ * Returns the pointer to the beginning of the HGCM parameters array. Use with care.
+ *
+ * @returns Pointer to the beginning of the HGCM parameters array.
+ */
+ PVBOXHGCMSVCPARM getParms(void) const { return paParms; }
+
+ /**
+ * Returns the message type.
+ *
+ * @returns Message type.
+ */
+ uint32_t getType(void) const { return uMsg; }
+
+ /**
+ * Resets the object.
+ */
+ void reset(void)
+ {
+ if (paParms)
+ {
+ /* Remove deep copies. */
+ for (uint32_t i = 0; i < cParms; i++)
+ {
+ if ( paParms[i].type == VBOX_HGCM_SVC_PARM_PTR
+ && paParms[i].u.pointer.size)
+ {
+ AssertPtr(paParms[i].u.pointer.addr);
+ RTMemFree(paParms[i].u.pointer.addr);
+ }
+ }
+
+ RTMemFree(paParms);
+ paParms = NULL;
+ }
+
+ uMsg = cParms = cParmsAlloc = 0;
+ }
+
+ /**
+ * Appends a new message parameter of type pointer.
+ *
+ * @returns VBox status code.
+ * @param pvBuf Pointer to data to use.
+ * @param cbBuf Size (in bytes) of data to use.
+ */
+ int appendPointer(void *pvBuf, uint32_t cbBuf)
+ {
+ PVBOXHGCMSVCPARM pParm = getNextParam();
+ if (!pParm)
+ return VERR_NO_MEMORY;
+
+ void *pvTmp = NULL;
+ if (cbBuf)
+ {
+ AssertPtr(pvBuf);
+ pvTmp = RTMemDup(pvBuf, cbBuf);
+ if (!pvTmp)
+ return VERR_NO_MEMORY;
+ }
+
+ HGCMSvcSetPv(pParm, pvTmp, cbBuf);
+ return VINF_SUCCESS;
+ }
+
+ /**
+ * Appends a new message parameter of type string.
+ *
+ * @returns VBox status code.
+ * @param pszString Pointer to string data to use.
+ */
+ int appendString(const char *pszString)
+ {
+ PVBOXHGCMSVCPARM pParm = getNextParam();
+ if (!pParm)
+ return VERR_NO_MEMORY;
+
+ char *pszTemp = RTStrDup(pszString);
+ if (!pszTemp)
+ return VERR_NO_MEMORY;
+
+ HGCMSvcSetStr(pParm, pszTemp);
+ return VINF_SUCCESS;
+ }
+
+ /**
+ * Appends a new message parameter of type uint32_t.
+ *
+ * @returns VBox status code.
+ * @param u32Val uint32_t value to use.
+ */
+ int appendUInt32(uint32_t u32Val)
+ {
+ PVBOXHGCMSVCPARM pParm = getNextParam();
+ if (!pParm)
+ return VERR_NO_MEMORY;
+
+ HGCMSvcSetU32(pParm, u32Val);
+ return VINF_SUCCESS;
+ }
+
+ /**
+ * Appends a new message parameter of type uint64_t.
+ *
+ * @returns VBox status code.
+ * @param u64Val uint64_t value to use.
+ */
+ int appendUInt64(uint64_t u64Val)
+ {
+ PVBOXHGCMSVCPARM pParm = getNextParam();
+ if (!pParm)
+ return VERR_NO_MEMORY;
+
+ HGCMSvcSetU64(pParm, u64Val);
+ return VINF_SUCCESS;
+ }
+
+ /**
+ * Sets the HGCM message type (function number).
+ *
+ * @param uMsgType Message type to set.
+ */
+ void setType(uint32_t uMsgType) { uMsg = uMsgType; }
+
+protected:
+
+ /** Message type. */
+ uint32_t uMsg;
+ /** Message parameters. */
+ uint32_t cParms;
+ /** Size of array. */
+ uint32_t cParmsAlloc;
+ /** Array of HGCM parameters */
+ PVBOXHGCMSVCPARM paParms;
+};
+
+/** Guest DnD callback function definition. */
+typedef DECLCALLBACKPTR(int, PFNGUESTDNDCALLBACK,(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser));
+
+/**
+ * Structure for keeping a guest DnD callback.
+ * Each callback can handle one HGCM message, however, multiple HGCM messages can be registered
+ * to the same callback (function).
+ */
+typedef struct GuestDnDCallback
+{
+ GuestDnDCallback(void)
+ : uMessgage(0)
+ , pfnCallback(NULL)
+ , pvUser(NULL) { }
+
+ GuestDnDCallback(PFNGUESTDNDCALLBACK pvCB, uint32_t uMsg, void *pvUsr = NULL)
+ : uMessgage(uMsg)
+ , pfnCallback(pvCB)
+ , pvUser(pvUsr) { }
+
+ /** The HGCM message ID to handle. */
+ uint32_t uMessgage;
+ /** Pointer to callback function. */
+ PFNGUESTDNDCALLBACK pfnCallback;
+ /** Pointer to user-supplied data. */
+ void *pvUser;
+} GuestDnDCallback;
+
+/** Contains registered callback pointers for specific HGCM message types. */
+typedef std::map<uint32_t, GuestDnDCallback> GuestDnDCallbackMap;
+
+/**
+ * Class for keeping a DnD guest state around.
+ */
+class GuestDnDState
+{
+
+public:
+ DECLARE_TRANSLATE_METHODS(GuestDnDState)
+
+ GuestDnDState(const ComObjPtr<Guest>& pGuest);
+ virtual ~GuestDnDState(void);
+
+public:
+
+ VBOXDNDSTATE get(void) const { return m_enmState; }
+ int set(VBOXDNDSTATE enmState) { LogRel3(("DnD: State %s -> %s\n", DnDStateToStr(m_enmState), DnDStateToStr(enmState))); m_enmState = enmState; return 0; }
+ void lock() { RTCritSectEnter(&m_CritSect); };
+ void unlock() { RTCritSectLeave(&m_CritSect); };
+
+ /** @name Guest response handling.
+ * @{ */
+ int notifyAboutGuestResponse(int rcGuest = VINF_SUCCESS);
+ int waitForGuestResponseEx(RTMSINTERVAL msTimeout = 3000, int *prcGuest = NULL);
+ int waitForGuestResponse(int *prcGuest = NULL);
+ /** @} */
+
+ void setActionsAllowed(VBOXDNDACTIONLIST a) { m_dndLstActionsAllowed = a; }
+ VBOXDNDACTIONLIST getActionsAllowed(void) const { return m_dndLstActionsAllowed; }
+
+ void setActionDefault(VBOXDNDACTION a) { m_dndActionDefault = a; }
+ VBOXDNDACTION getActionDefault(void) const { return m_dndActionDefault; }
+
+ void setFormats(const GuestDnDMIMEList &lstFormats) { m_lstFormats = lstFormats; }
+ GuestDnDMIMEList formats(void) const { return m_lstFormats; }
+
+ void reset(void);
+
+ /** @name Callback handling.
+ * @{ */
+ static DECLCALLBACK(int) i_defaultCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser);
+ int setCallback(uint32_t uMsg, PFNGUESTDNDCALLBACK pfnCallback, void *pvUser = NULL);
+ /** @} */
+
+ /** @name Progress handling.
+ * @{ */
+ bool isProgressCanceled(void) const;
+ bool isProgressRunning(void) const;
+ int setProgress(unsigned uPercentage, uint32_t uState, int rcOp = VINF_SUCCESS, const Utf8Str &strMsg = "");
+ HRESULT resetProgress(const ComObjPtr<Guest>& pParent, const Utf8Str &strDesc);
+ HRESULT queryProgressTo(IProgress **ppProgress);
+ /** @} */
+
+public:
+
+ /** @name HGCM callback handling.
+ @{ */
+ int onDispatch(uint32_t u32Function, void *pvParms, uint32_t cbParms);
+ /** @} */
+
+public:
+
+ /** Pointer to context this class is tied to. */
+ void *m_pvCtx;
+ RTCRITSECT m_CritSect;
+ /** The current state we're in. */
+ VBOXDNDSTATE m_enmState;
+ /** The DnD protocol version to use, depending on the
+ * installed Guest Additions. See DragAndDropSvc.h for
+ * a protocol changelog. */
+ uint32_t m_uProtocolVersion;
+ /** The guest feature flags reported to the host (VBOX_DND_GF_XXX). */
+ uint64_t m_fGuestFeatures0;
+ /** Event for waiting for response. */
+ RTSEMEVENT m_EventSem;
+ /** Last error reported from guest.
+ * Set to VERR_IPE_UNINITIALIZED_STATUS if not set yet. */
+ int m_rcGuest;
+ /** Default action to perform in case of a
+ * successful drop. */
+ VBOXDNDACTION m_dndActionDefault;
+ /** Actions supported by the guest in case of a successful drop. */
+ VBOXDNDACTIONLIST m_dndLstActionsAllowed;
+ /** Format(s) requested/supported from the guest. */
+ GuestDnDMIMEList m_lstFormats;
+ /** Pointer to IGuest parent object. */
+ ComObjPtr<Guest> m_pParent;
+ /** Pointer to associated progress object. Optional. */
+ ComObjPtr<Progress> m_pProgress;
+ /** Callback map. */
+ GuestDnDCallbackMap m_mapCallbacks;
+};
+
+/**
+ * Private singleton class for the guest's DnD implementation.
+ *
+ * Can't be instanciated directly, only via the factory pattern.
+ * Keeps track of all ongoing DnD transfers.
+ */
+class GuestDnD
+{
+public:
+
+ /**
+ * Creates the Singleton GuestDnD object.
+ *
+ * @returns Newly created Singleton object, or NULL on failure.
+ */
+ static GuestDnD *createInstance(const ComObjPtr<Guest>& pGuest)
+ {
+ Assert(NULL == GuestDnD::s_pInstance);
+ GuestDnD::s_pInstance = new GuestDnD(pGuest);
+ return GuestDnD::s_pInstance;
+ }
+
+ /**
+ * Destroys the Singleton GuestDnD object.
+ */
+ static void destroyInstance(void)
+ {
+ if (GuestDnD::s_pInstance)
+ {
+ delete GuestDnD::s_pInstance;
+ GuestDnD::s_pInstance = NULL;
+ }
+ }
+
+ /**
+ * Returns the Singleton GuestDnD object.
+ *
+ * @returns Pointer to Singleton GuestDnD object, or NULL if not created yet.
+ */
+ static inline GuestDnD *getInstance(void)
+ {
+ AssertPtr(GuestDnD::s_pInstance);
+ return GuestDnD::s_pInstance;
+ }
+
+protected:
+
+ /** List of registered DnD sources. */
+ typedef std::list< ComObjPtr<GuestDnDSource> > GuestDnDSrcList;
+ /** List of registered DnD targets. */
+ typedef std::list< ComObjPtr<GuestDnDTarget> > GuestDnDTgtList;
+
+ /** Constructor; will throw rc on failure. */
+ GuestDnD(const ComObjPtr<Guest>& pGuest);
+ virtual ~GuestDnD(void);
+
+public:
+
+ /** @name Public helper functions.
+ * @{ */
+ HRESULT adjustScreenCoordinates(ULONG uScreenId, ULONG *puX, ULONG *puY) const;
+ GuestDnDState *getState(uint32_t = 0) const;
+ int hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const;
+ GuestDnDMIMEList defaultFormats(void) const { return m_strDefaultFormats; }
+ /** @} */
+
+ /** @name Source / target management.
+ * @{ */
+ int registerSource(const ComObjPtr<GuestDnDSource> &Source);
+ int unregisterSource(const ComObjPtr<GuestDnDSource> &Source);
+ size_t getSourceCount(void);
+
+ int registerTarget(const ComObjPtr<GuestDnDTarget> &Target);
+ int unregisterTarget(const ComObjPtr<GuestDnDTarget> &Target);
+ size_t getTargetCount(void);
+ /** @} */
+
+public:
+
+ /** @name Static low-level HGCM callback handler.
+ * @{ */
+ static DECLCALLBACK(int) notifyDnDDispatcher(void *pvExtension, uint32_t u32Function, void *pvParms, uint32_t cbParms);
+ /** @} */
+
+ /** @name Static helper methods.
+ * @{ */
+ static bool isFormatInFormatList(const com::Utf8Str &strFormat, const GuestDnDMIMEList &lstFormats);
+ static GuestDnDMIMEList toFormatList(const com::Utf8Str &strFormats, const com::Utf8Str &strSep = DND_FORMATS_SEPARATOR_STR);
+ static com::Utf8Str toFormatString(const GuestDnDMIMEList &lstFormats, const com::Utf8Str &strSep = DND_FORMATS_SEPARATOR_STR);
+ static GuestDnDMIMEList toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const GuestDnDMIMEList &lstFormatsWanted);
+ static GuestDnDMIMEList toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const com::Utf8Str &strFormatsWanted);
+ static DnDAction_T toMainAction(VBOXDNDACTION dndAction);
+ static std::vector<DnDAction_T> toMainActions(VBOXDNDACTIONLIST dndActionList);
+ static VBOXDNDACTION toHGCMAction(DnDAction_T enmAction);
+ static void toHGCMActions(DnDAction_T enmDefAction, VBOXDNDACTION *pDefAction, const std::vector<DnDAction_T> vecAllowedActions, VBOXDNDACTIONLIST *pLstAllowedActions);
+ /** @} */
+
+protected:
+
+ /** @name Singleton properties.
+ * @{ */
+ /** List of supported default MIME/Content-type formats. */
+ GuestDnDMIMEList m_strDefaultFormats;
+ /** Pointer to guest implementation. */
+ const ComObjPtr<Guest> m_pGuest;
+ /** The current state from the guest. At the
+ * moment we only support only state a time (ARQ-style). */
+ GuestDnDState *m_pState;
+ /** Critical section to serialize access. */
+ RTCRITSECT m_CritSect;
+ /** Number of active transfers (guest->host or host->guest). */
+ uint32_t m_cTransfersPending;
+ GuestDnDSrcList m_lstSrc;
+ GuestDnDTgtList m_lstTgt;
+ /** @} */
+
+private:
+
+ /** Static pointer to singleton instance. */
+ static GuestDnD *s_pInstance;
+};
+
+/** Access to the GuestDnD's singleton instance. */
+#define GuestDnDInst() GuestDnD::getInstance()
+
+/** List of pointers to guest DnD Messages. */
+typedef std::list<GuestDnDMsg *> GuestDnDMsgList;
+
+/**
+ * IDnDBase class implementation for sharing code between
+ * IGuestDnDSource and IGuestDnDTarget implementation.
+ */
+class GuestDnDBase
+{
+protected:
+
+ GuestDnDBase(VirtualBoxBase *pBase);
+
+ virtual ~GuestDnDBase(void);
+
+protected:
+
+ /** Shared (internal) IDnDBase method implementations.
+ * @{ */
+ bool i_isFormatSupported(const com::Utf8Str &aFormat) const;
+ const GuestDnDMIMEList &i_getFormats(void) const;
+ HRESULT i_addFormats(const GuestDnDMIMEList &aFormats);
+ HRESULT i_removeFormats(const GuestDnDMIMEList &aFormats);
+ /** @} */
+
+ /** @name Error handling.
+ * @{ */
+ HRESULT i_setErrorV(int vrc, const char *pcszMsgFmt, va_list va);
+ HRESULT i_setError(int vrc, const char *pcszMsgFmt, ...);
+ HRESULT i_setErrorAndReset(const char *pcszMsgFmt, ...);
+ HRESULT i_setErrorAndReset(int vrc, const char *pcszMsgFmt, ...);
+ /** @} */
+
+protected:
+
+ /** @name Pure virtual functions needed to be implemented by the actual (derived) implementation.
+ * @{ */
+ virtual void i_reset(void) = 0;
+ /** @} */
+
+protected:
+
+ /** @name Functions for handling a simple host HGCM message queue.
+ * @{ */
+ int msgQueueAdd(GuestDnDMsg *pMsg);
+ GuestDnDMsg *msgQueueGetNext(void);
+ void msgQueueRemoveNext(void);
+ void msgQueueClear(void);
+ /** @} */
+
+ int sendCancel(void);
+ int updateProgress(GuestDnDData *pData, GuestDnDState *pState, size_t cbDataAdd = 0);
+ int waitForEvent(GuestDnDCallbackEvent *pEvent, GuestDnDState *pState, RTMSINTERVAL msTimeout);
+
+protected:
+
+ /** Pointer to base class to use for stuff like error handlng. */
+ VirtualBoxBase *m_pBase;
+ /** @name Public attributes (through getters/setters).
+ * @{ */
+ /** Pointer to guest implementation. */
+ const ComObjPtr<Guest> m_pGuest;
+ /** List of supported MIME types by the source. */
+ GuestDnDMIMEList m_lstFmtSupported;
+ /** List of offered MIME types to the counterpart. */
+ GuestDnDMIMEList m_lstFmtOffered;
+ /** Whether the object still is in pending state. */
+ bool m_fIsPending;
+ /** Pointer to state bound to this object. */
+ GuestDnDState *m_pState;
+ /** @} */
+
+ /**
+ * Internal stuff.
+ */
+ struct
+ {
+ /** Outgoing message queue (FIFO). */
+ GuestDnDMsgList lstMsgOut;
+ } m_DataBase;
+};
+#endif /* !MAIN_INCLUDED_GuestDnDPrivate_h */
+
diff --git a/src/VBox/Main/include/GuestDnDSourceImpl.h b/src/VBox/Main/include/GuestDnDSourceImpl.h
new file mode 100644
index 00000000..c26881bb
--- /dev/null
+++ b/src/VBox/Main/include/GuestDnDSourceImpl.h
@@ -0,0 +1,131 @@
+/* $Id: GuestDnDSourceImpl.h $ */
+/** @file
+ * VBox Console COM Class implementation - Guest drag'n drop source.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GuestDnDSourceImpl_h
+#define MAIN_INCLUDED_GuestDnDSourceImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/GuestHost/DragAndDrop.h>
+#include <VBox/HostServices/DragAndDropSvc.h>
+
+using namespace DragAndDropSvc;
+
+#include "GuestDnDSourceWrap.h"
+#include "GuestDnDPrivate.h"
+
+class GuestDnDRecvDataTask;
+struct GuestDnDRecvCtx;
+
+class ATL_NO_VTABLE GuestDnDSource :
+ public GuestDnDSourceWrap,
+ public GuestDnDBase
+{
+public:
+ GuestDnDSource(void);
+ virtual ~GuestDnDSource(void);
+
+ /** @name COM and internal init/term/mapping cruft.
+ * @{ */
+ DECLARE_TRANSLATE_METHODS(GuestDnDSource);
+
+ HRESULT init(const ComObjPtr<Guest>& pGuest);
+ void uninit(void);
+
+ HRESULT FinalConstruct(void);
+ void FinalRelease(void);
+ /** @} */
+
+private:
+
+ /** Private wrapped @name IDnDBase methods.
+ * @{ */
+ HRESULT isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported);
+ HRESULT getFormats(GuestDnDMIMEList &aFormats);
+ HRESULT addFormats(const GuestDnDMIMEList &aFormats);
+ HRESULT removeFormats(const GuestDnDMIMEList &aFormats);
+ /** @} */
+
+ /** Private wrapped @name IDnDSource methods.
+ * @{ */
+ HRESULT dragIsPending(ULONG uScreenId, GuestDnDMIMEList &aFormats, std::vector<DnDAction_T> &aAllowedActions, DnDAction_T *aDefaultAction);
+ HRESULT drop(const com::Utf8Str &aFormat, DnDAction_T aAction, ComPtr<IProgress> &aProgress);
+ HRESULT receiveData(std::vector<BYTE> &aData);
+ /** @} */
+
+protected:
+
+ /** @name Implemented virtual functions.
+ * @{ */
+ void i_reset(void);
+ /** @} */
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+ /** @name Dispatch handlers for the HGCM callbacks.
+ * @{ */
+ int i_onReceiveDataHdr(GuestDnDRecvCtx *pCtx, PVBOXDNDSNDDATAHDR pDataHdr);
+ int i_onReceiveData(GuestDnDRecvCtx *pCtx, PVBOXDNDSNDDATA pSndData);
+ int i_onReceiveDir(GuestDnDRecvCtx *pCtx, const char *pszPath, uint32_t cbPath, uint32_t fMode);
+ int i_onReceiveFileHdr(GuestDnDRecvCtx *pCtx, const char *pszPath, uint32_t cbPath, uint64_t cbSize, uint32_t fMode, uint32_t fFlags);
+ int i_onReceiveFileData(GuestDnDRecvCtx *pCtx,const void *pvData, uint32_t cbData);
+ /** @} */
+#endif
+
+protected:
+
+ static Utf8Str i_guestErrorToString(int guestRc);
+ static Utf8Str i_hostErrorToString(int hostRc);
+
+ /** @name Callbacks for dispatch handler.
+ * @{ */
+ static DECLCALLBACK(int) i_receiveRawDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser);
+ static DECLCALLBACK(int) i_receiveTransferDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser);
+ /** @} */
+
+protected:
+
+ int i_receiveData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout);
+ int i_receiveRawData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout);
+ int i_receiveTransferData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout);
+
+protected:
+
+ struct
+ {
+ /** Maximum data block size (in bytes) the source can handle. */
+ uint32_t mcbBlockSize;
+ /** The context for receiving data from the guest.
+ * At the moment only one transfer at a time is supported. */
+ GuestDnDRecvCtx mRecvCtx;
+ } mData;
+
+ friend class GuestDnDRecvDataTask;
+};
+
+#endif /* !MAIN_INCLUDED_GuestDnDSourceImpl_h */
+
diff --git a/src/VBox/Main/include/GuestDnDTargetImpl.h b/src/VBox/Main/include/GuestDnDTargetImpl.h
new file mode 100644
index 00000000..0011709c
--- /dev/null
+++ b/src/VBox/Main/include/GuestDnDTargetImpl.h
@@ -0,0 +1,128 @@
+/* $Id: GuestDnDTargetImpl.h $ */
+/** @file
+ * VBox Console COM Class implementation - Guest drag'n drop target.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GuestDnDTargetImpl_h
+#define MAIN_INCLUDED_GuestDnDTargetImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "GuestDnDTargetWrap.h"
+#include "GuestDnDPrivate.h"
+
+#include <VBox/GuestHost/DragAndDrop.h>
+#include <VBox/HostServices/DragAndDropSvc.h>
+
+struct GuestDnDSendCtx;
+class GuestDnDSendDataTask;
+
+class ATL_NO_VTABLE GuestDnDTarget :
+ public GuestDnDTargetWrap,
+ public GuestDnDBase
+{
+public:
+ GuestDnDTarget(void);
+ virtual ~GuestDnDTarget(void);
+
+ /** @name COM and internal init/term/mapping cruft.
+ * @{ */
+ DECLARE_TRANSLATE_METHODS(GuestDnDTarget);
+
+ HRESULT init(const ComObjPtr<Guest>& pGuest);
+ void uninit(void);
+
+ HRESULT FinalConstruct(void);
+ void FinalRelease(void);
+ /** @} */
+
+private:
+
+ /** Private wrapped @name IDnDBase methods.
+ * @{ */
+ HRESULT isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported);
+ HRESULT getFormats(GuestDnDMIMEList &aFormats);
+ HRESULT addFormats(const GuestDnDMIMEList &aFormats);
+ HRESULT removeFormats(const GuestDnDMIMEList &aFormats);
+ /** @} */
+
+ /** Private wrapped @name IDnDTarget methods.
+ * @{ */
+ HRESULT enter(ULONG aScreenId, ULONG ax, ULONG aY, DnDAction_T aDefaultAction, const std::vector<DnDAction_T> &aAllowedActions, const GuestDnDMIMEList &aFormats, DnDAction_T *aResultAction);
+ HRESULT move(ULONG aScreenId, ULONG aX, ULONG aY, DnDAction_T aDefaultAction, const std::vector<DnDAction_T> &aAllowedActions, const GuestDnDMIMEList &aFormats, DnDAction_T *aResultAction);
+ HRESULT leave(ULONG aScreenId);
+ HRESULT drop(ULONG aScreenId, ULONG aX, ULONG aY, DnDAction_T aDefaultAction, const std::vector<DnDAction_T> &aAllowedActions, const GuestDnDMIMEList &aFormats, com::Utf8Str &aFormat, DnDAction_T *aResultAction);
+ HRESULT sendData(ULONG aScreenId, const com::Utf8Str &aFormat, const std::vector<BYTE> &aData, ComPtr<IProgress> &aProgress);
+ HRESULT cancel(BOOL *aVeto);
+ /** @} */
+
+protected:
+
+ static Utf8Str i_guestErrorToString(int guestRc);
+ static Utf8Str i_hostErrorToString(int hostRc);
+
+ /** @name Callbacks for dispatch handler.
+ * @{ */
+ static DECLCALLBACK(int) i_sendTransferDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser);
+ /** @} */
+
+protected:
+
+ /** @name Implemented virtual functions.
+ * @{ */
+ void i_reset(void);
+ /** @} */
+
+ int i_sendData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout);
+
+ int i_sendMetaDataBody(GuestDnDSendCtx *pCtx);
+ int i_sendMetaDataHeader(GuestDnDSendCtx *pCtx);
+
+ int i_sendTransferData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout);
+ int i_sendTransferListObject(GuestDnDSendCtx *pCtx, PDNDTRANSFERLIST pList, GuestDnDMsg *pMsg);
+
+ int i_sendDirectory(GuestDnDSendCtx *pCtx, PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg);
+ int i_sendFile(GuestDnDSendCtx *pCtx, PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg);
+ int i_sendFileData(GuestDnDSendCtx *pCtx, PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg);
+
+ int i_sendRawData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout);
+
+protected:
+
+ struct
+ {
+ /** Maximum data block size (in bytes) the target can handle. */
+ uint32_t mcbBlockSize;
+ /** The context for sending data to the guest.
+ * At the moment only one transfer at a time is supported. */
+ GuestDnDSendCtx mSendCtx;
+ } mData;
+
+ friend class GuestDnDSendDataTask;
+};
+
+#endif /* !MAIN_INCLUDED_GuestDnDTargetImpl_h */
+
diff --git a/src/VBox/Main/include/GuestFileImpl.h b/src/VBox/Main/include/GuestFileImpl.h
new file mode 100644
index 00000000..e1531019
--- /dev/null
+++ b/src/VBox/Main/include/GuestFileImpl.h
@@ -0,0 +1,161 @@
+/* $Id: GuestFileImpl.h $ */
+/** @file
+ * VirtualBox Main - Guest file handling implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GuestFileImpl_h
+#define MAIN_INCLUDED_GuestFileImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VirtualBoxBase.h"
+#include "EventImpl.h"
+
+#include "GuestCtrlImplPrivate.h"
+#include "GuestFileWrap.h"
+
+class Console;
+class GuestSession;
+class GuestProcess;
+
+class ATL_NO_VTABLE GuestFile :
+ public GuestFileWrap,
+ public GuestObject
+{
+public:
+ /** @name COM and internal init/term/mapping cruft.
+ * @{ */
+ DECLARE_COMMON_CLASS_METHODS(GuestFile)
+
+ int init(Console *pConsole, GuestSession *pSession, ULONG uFileID, const GuestFileOpenInfo &openInfo);
+ void uninit(void);
+
+ HRESULT FinalConstruct(void);
+ void FinalRelease(void);
+ /** @} */
+
+public:
+ /** @name Implemented virtual methods from GuestObject.
+ * @{ */
+ int i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb);
+ int i_onUnregister(void);
+ int i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus);
+ /** @} */
+
+public:
+ /** @name Public internal methods.
+ * @{ */
+ int i_closeFile(int *pGuestRc);
+ EventSource *i_getEventSource(void) { return mEventSource; }
+ int i_onFileNotify(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData);
+ int i_onGuestDisconnected(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData);
+ int i_openFile(uint32_t uTimeoutMS, int *pGuestRc);
+ int i_queryInfo(GuestFsObjData &objData, int *prcGuest);
+ int i_readData(uint32_t uSize, uint32_t uTimeoutMS, void* pvData, uint32_t cbData, uint32_t* pcbRead);
+ int i_readDataAt(uint64_t uOffset, uint32_t uSize, uint32_t uTimeoutMS,
+ void* pvData, size_t cbData, size_t* pcbRead);
+ int i_seekAt(int64_t iOffset, GUEST_FILE_SEEKTYPE eSeekType, uint32_t uTimeoutMS, uint64_t *puOffset);
+ int i_setFileStatus(FileStatus_T fileStatus, int fileRc);
+ int i_waitForOffsetChange(GuestWaitEvent *pEvent, uint32_t uTimeoutMS, uint64_t *puOffset);
+ int i_waitForRead(GuestWaitEvent *pEvent, uint32_t uTimeoutMS, void *pvData, size_t cbData, uint32_t *pcbRead);
+ int i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t uTimeoutMS, FileStatus_T *pFileStatus, int *pGuestRc);
+ int i_waitForWrite(GuestWaitEvent *pEvent, uint32_t uTimeoutMS, uint32_t *pcbWritten);
+ int i_writeData(uint32_t uTimeoutMS, const void *pvData, uint32_t cbData, uint32_t *pcbWritten);
+ int i_writeDataAt(uint64_t uOffset, uint32_t uTimeoutMS, const void *pvData, uint32_t cbData, uint32_t *pcbWritten);
+ /** @} */
+
+ /** @name Static helper methods.
+ * @{ */
+ static Utf8Str i_guestErrorToString(int guestRc, const char *pcszWhat);
+ /** @} */
+
+public:
+
+ /** @name Wrapped IGuestFile properties.
+ * @{ */
+ HRESULT getCreationMode(ULONG *aCreationMode);
+ HRESULT getEventSource(ComPtr<IEventSource> &aEventSource);
+ HRESULT getId(ULONG *aId);
+ HRESULT getInitialSize(LONG64 *aInitialSize);
+ HRESULT getOffset(LONG64 *aOffset);
+ HRESULT getStatus(FileStatus_T *aStatus);
+ HRESULT getFilename(com::Utf8Str &aFilename);
+ HRESULT getAccessMode(FileAccessMode_T *aAccessMode);
+ HRESULT getOpenAction(FileOpenAction_T *aOpenAction);
+ /** @} */
+
+ /** @name Wrapped IGuestFile methods.
+ * @{ */
+ HRESULT close();
+ HRESULT queryInfo(ComPtr<IFsObjInfo> &aObjInfo);
+ HRESULT querySize(LONG64 *aSize);
+ HRESULT read(ULONG aToRead,
+ ULONG aTimeoutMS,
+ std::vector<BYTE> &aData);
+ HRESULT readAt(LONG64 aOffset,
+ ULONG aToRead,
+ ULONG aTimeoutMS,
+ std::vector<BYTE> &aData);
+ HRESULT seek(LONG64 aOffset,
+ FileSeekOrigin_T aWhence,
+ LONG64 *aNewOffset);
+ HRESULT setACL(const com::Utf8Str &aAcl,
+ ULONG aMode);
+ HRESULT setSize(LONG64 aSize);
+ HRESULT write(const std::vector<BYTE> &aData,
+ ULONG aTimeoutMS,
+ ULONG *aWritten);
+ HRESULT writeAt(LONG64 aOffset,
+ const std::vector<BYTE> &aData,
+ ULONG aTimeoutMS,
+ ULONG *aWritten);
+ /** @} */
+
+private:
+
+ /** This can safely be used without holding any locks.
+ * An AutoCaller suffices to prevent it being destroy while in use and
+ * internally there is a lock providing the necessary serialization. */
+ const ComObjPtr<EventSource> mEventSource;
+
+ struct Data
+ {
+ /** The file's open info. */
+ GuestFileOpenInfo mOpenInfo;
+ /** The file's initial size on open. */
+ uint64_t mInitialSize;
+ /** The current file status. */
+ FileStatus_T mStatus;
+ /** The last returned process status
+ * returned from the guest side. */
+ int mLastError;
+ /** The file's current offset. */
+ uint64_t mOffCurrent;
+ } mData;
+};
+
+#endif /* !MAIN_INCLUDED_GuestFileImpl_h */
+
diff --git a/src/VBox/Main/include/GuestFsObjInfoImpl.h b/src/VBox/Main/include/GuestFsObjInfoImpl.h
new file mode 100644
index 00000000..b4955f40
--- /dev/null
+++ b/src/VBox/Main/include/GuestFsObjInfoImpl.h
@@ -0,0 +1,86 @@
+/* $Id: GuestFsObjInfoImpl.h $ */
+/** @file
+ * VirtualBox Main - Guest file system object information implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GuestFsObjInfoImpl_h
+#define MAIN_INCLUDED_GuestFsObjInfoImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "GuestFsObjInfoWrap.h"
+#include "GuestCtrlImplPrivate.h"
+
+class ATL_NO_VTABLE GuestFsObjInfo
+ : public GuestFsObjInfoWrap
+{
+public:
+ /** @name COM and internal init/term/mapping cruft.
+ * @{ */
+ DECLARE_COMMON_CLASS_METHODS(GuestFsObjInfo)
+
+ int init(const GuestFsObjData &objData);
+ void uninit(void);
+
+ HRESULT FinalConstruct(void);
+ void FinalRelease(void);
+ /** @} */
+
+ /** @name Internal access helpers.
+ * @{ */
+ const GuestFsObjData &i_getData() const { return mData; }
+ /** @} */
+
+private:
+
+ /** Wrapped @name IGuestFsObjInfo properties.
+ * @{ */
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getType(FsObjType_T *aType);
+ HRESULT getFileAttributes(com::Utf8Str &aFileAttributes);
+ HRESULT getObjectSize(LONG64 *aObjectSize);
+ HRESULT getAllocatedSize(LONG64 *aAllocatedSize);
+ HRESULT getAccessTime(LONG64 *aAccessTime);
+ HRESULT getBirthTime(LONG64 *aBirthTime);
+ HRESULT getChangeTime(LONG64 *aChangeTime);
+ HRESULT getModificationTime(LONG64 *aModificationTime);
+ HRESULT getUID(LONG *aUID);
+ HRESULT getUserName(com::Utf8Str &aUserName);
+ HRESULT getGID(LONG *aGID);
+ HRESULT getGroupName(com::Utf8Str &aGroupName);
+ HRESULT getNodeId(LONG64 *aNodeId);
+ HRESULT getNodeIdDevice(ULONG *aNodeIdDevice);
+ HRESULT getHardLinks(ULONG *aHardLinks);
+ HRESULT getDeviceNumber(ULONG *aDeviceNumber);
+ HRESULT getGenerationId(ULONG *aGenerationId);
+ HRESULT getUserFlags(ULONG *aUserFlags);
+ /** @} */
+
+ GuestFsObjData mData;
+};
+
+#endif /* !MAIN_INCLUDED_GuestFsObjInfoImpl_h */
+
diff --git a/src/VBox/Main/include/GuestImpl.h b/src/VBox/Main/include/GuestImpl.h
new file mode 100644
index 00000000..a315a430
--- /dev/null
+++ b/src/VBox/Main/include/GuestImpl.h
@@ -0,0 +1,282 @@
+/* $Id: GuestImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GuestImpl_h
+#define MAIN_INCLUDED_GuestImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "GuestWrap.h"
+#include "VirtualBoxBase.h"
+#include <iprt/list.h>
+#include <iprt/time.h>
+#include <VBox/ostypes.h>
+#include <VBox/param.h>
+#include <VBox/vmm/stam.h>
+
+#include "AdditionsFacilityImpl.h"
+#ifdef VBOX_WITH_GUEST_CONTROL
+# include "GuestCtrlImplPrivate.h"
+# include "GuestSessionImpl.h"
+#endif
+#ifdef VBOX_WITH_DRAG_AND_DROP
+# include "GuestDnDSourceImpl.h"
+# include "GuestDnDTargetImpl.h"
+#endif
+#include "EventImpl.h"
+#include "HGCM.h"
+
+typedef enum
+{
+ GUESTSTATTYPE_CPUUSER = 0,
+ GUESTSTATTYPE_CPUKERNEL = 1,
+ GUESTSTATTYPE_CPUIDLE = 2,
+ GUESTSTATTYPE_MEMTOTAL = 3,
+ GUESTSTATTYPE_MEMFREE = 4,
+ GUESTSTATTYPE_MEMBALLOON = 5,
+ GUESTSTATTYPE_MEMCACHE = 6,
+ GUESTSTATTYPE_PAGETOTAL = 7,
+ GUESTSTATTYPE_PAGEFREE = 8,
+ GUESTSTATTYPE_MAX = 9
+} GUESTSTATTYPE;
+
+class Console;
+
+class ATL_NO_VTABLE Guest :
+ public GuestWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS (Guest)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // Public initializer/uninitializer for internal purposes only.
+ HRESULT init(Console *aParent);
+ void uninit();
+
+
+public:
+ /** @name Static internal methods.
+ * @{ */
+#ifdef VBOX_WITH_GUEST_CONTROL
+ /** Static callback for handling guest control notifications. */
+ static DECLCALLBACK(int) i_notifyCtrlDispatcher(void *pvExtension, uint32_t u32Function, void *pvData, uint32_t cbData);
+#endif
+ static DECLCALLBACK(void) i_staticUpdateStats(RTTIMERLR hTimerLR, void *pvUser, uint64_t iTick);
+ /** @} */
+
+public:
+ /** @name Public internal methods.
+ * @{ */
+ void i_enableVMMStatistics(BOOL aEnable) { mCollectVMMStats = aEnable; };
+ void i_setAdditionsInfo(const com::Utf8Str &aInterfaceVersion, VBOXOSTYPE aOsType);
+ void i_setAdditionsInfo2(uint32_t a_uFullVersion, const char *a_pszName, uint32_t a_uRevision, uint32_t a_fFeatures);
+ bool i_facilityIsActive(VBoxGuestFacilityType enmFacility);
+ bool i_facilityUpdate(VBoxGuestFacilityType a_enmFacility, VBoxGuestFacilityStatus a_enmStatus,
+ uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS);
+ ComObjPtr<Console> i_getConsole(void) { return mParent; }
+ void i_setAdditionsStatus(VBoxGuestFacilityType a_enmFacility, VBoxGuestFacilityStatus a_enmStatus,
+ uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS);
+ void i_onUserStateChanged(const Utf8Str &aUser, const Utf8Str &aDomain, VBoxGuestUserState enmState,
+ const uint8_t *puDetails, uint32_t cbDetails);
+ void i_setSupportedFeatures(uint32_t aCaps);
+ HRESULT i_setStatistic(ULONG aCpuId, GUESTSTATTYPE enmType, ULONG aVal);
+ BOOL i_isPageFusionEnabled();
+ void i_setCpuCount(uint32_t aCpus) { mCpus = aCpus; }
+ static HRESULT i_setErrorStatic(HRESULT aResultCode, const char *aText, ...)
+ {
+ va_list va;
+ va_start(va, aText);
+ HRESULT hrc = setErrorInternalV(aResultCode, getStaticClassIID(), getStaticComponentName(), aText, va, false, true);
+ va_end(va);
+ return hrc;
+ }
+ uint32_t i_getAdditionsRevision(void) { return mData.mAdditionsRevision; }
+ uint32_t i_getAdditionsVersion(void) { return mData.mAdditionsVersionFull; }
+ VBOXOSTYPE i_getGuestOSType(void) const { return mData.mOSType; }
+ /** Checks if the guest OS type is part of the windows NT family. */
+ bool i_isGuestInWindowsNtFamily(void) const
+ {
+ return mData.mOSType < VBOXOSTYPE_OS2 && mData.mOSType >= VBOXOSTYPE_WinNT;
+ }
+#ifdef VBOX_WITH_GUEST_CONTROL
+ int i_dispatchToSession(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb);
+ int i_sessionCreate(const GuestSessionStartupInfo &ssInfo, const GuestCredentials &guestCreds,
+ ComObjPtr<GuestSession> &pGuestSession);
+ int i_sessionDestroy(uint32_t uSessionID);
+ inline bool i_sessionExists(uint32_t uSessionID);
+ /** Returns the VBOX_GUESTCTRL_GF_0_XXX mask reported by the guest. */
+ uint64_t i_getGuestControlFeatures0() const { return mData.mfGuestFeatures0; }
+ /** Returns the VBOX_GUESTCTRL_GF_1_XXX mask reported by the guest. */
+ uint64_t i_getGuestControlFeatures1() const { return mData.mfGuestFeatures1; }
+#endif
+ /** @} */
+
+private:
+
+ // wrapped IGuest properties
+ HRESULT getOSTypeId(com::Utf8Str &aOSTypeId);
+ HRESULT getAdditionsRunLevel(AdditionsRunLevelType_T *aAdditionsRunLevel);
+ HRESULT getAdditionsVersion(com::Utf8Str &aAdditionsVersion);
+ HRESULT getAdditionsRevision(ULONG *aAdditionsRevision);
+ HRESULT getDnDSource(ComPtr<IGuestDnDSource> &aDnDSource);
+ HRESULT getDnDTarget(ComPtr<IGuestDnDTarget> &aDnDTarget);
+ HRESULT getEventSource(ComPtr<IEventSource> &aEventSource);
+ HRESULT getFacilities(std::vector<ComPtr<IAdditionsFacility> > &aFacilities);
+ HRESULT getSessions(std::vector<ComPtr<IGuestSession> > &aSessions);
+ HRESULT getMemoryBalloonSize(ULONG *aMemoryBalloonSize);
+ HRESULT setMemoryBalloonSize(ULONG aMemoryBalloonSize);
+ HRESULT getStatisticsUpdateInterval(ULONG *aStatisticsUpdateInterval);
+ HRESULT setStatisticsUpdateInterval(ULONG aStatisticsUpdateInterval);
+ HRESULT internalGetStatistics(ULONG *aCpuUser,
+ ULONG *aCpuKernel,
+ ULONG *aCpuIdle,
+ ULONG *aMemTotal,
+ ULONG *aMemFree,
+ ULONG *aMemBalloon,
+ ULONG *aMemShared,
+ ULONG *aMemCache,
+ ULONG *aPagedTotal,
+ ULONG *aMemAllocTotal,
+ ULONG *aMemFreeTotal,
+ ULONG *aMemBalloonTotal,
+ ULONG *aMemSharedTotal);
+ HRESULT getFacilityStatus(AdditionsFacilityType_T aFacility,
+ LONG64 *aTimestamp,
+ AdditionsFacilityStatus_T *aStatus);
+ HRESULT getAdditionsStatus(AdditionsRunLevelType_T aLevel,
+ BOOL *aActive);
+ HRESULT setCredentials(const com::Utf8Str &aUserName,
+ const com::Utf8Str &aPassword,
+ const com::Utf8Str &aDomain,
+ BOOL aAllowInteractiveLogon);
+
+ // wrapped IGuest methods
+ HRESULT createSession(const com::Utf8Str &aUser,
+ const com::Utf8Str &aPassword,
+ const com::Utf8Str &aDomain,
+ const com::Utf8Str &aSessionName,
+ ComPtr<IGuestSession> &aGuestSession);
+
+ HRESULT findSession(const com::Utf8Str &aSessionName,
+ std::vector<ComPtr<IGuestSession> > &aSessions);
+ HRESULT shutdown(const std::vector<GuestShutdownFlag_T> &aFlags);
+ HRESULT updateGuestAdditions(const com::Utf8Str &aSource,
+ const std::vector<com::Utf8Str> &aArguments,
+ const std::vector<AdditionsUpdateFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress);
+
+
+ /** @name Private internal methods.
+ * @{ */
+ void i_updateStats(uint64_t iTick);
+ static DECLCALLBACK(int) i_staticEnumStatsCallback(const char *pszName, STAMTYPE enmType, void *pvSample,
+ STAMUNIT enmUnit, const char *pszUnit, STAMVISIBILITY enmVisiblity,
+ const char *pszDesc, void *pvUser);
+
+ /** @} */
+
+ typedef std::map< AdditionsFacilityType_T, ComObjPtr<AdditionsFacility> > FacilityMap;
+ typedef std::map< AdditionsFacilityType_T, ComObjPtr<AdditionsFacility> >::iterator FacilityMapIter;
+ typedef std::map< AdditionsFacilityType_T, ComObjPtr<AdditionsFacility> >::const_iterator FacilityMapIterConst;
+
+#ifdef VBOX_WITH_GUEST_CONTROL
+ /** Map for keeping the guest sessions. The primary key marks the guest session ID. */
+ typedef std::map <uint32_t, ComObjPtr<GuestSession> > GuestSessions;
+#endif
+
+ struct Data
+ {
+ Data() : mOSType(VBOXOSTYPE_Unknown), mAdditionsRunLevel(AdditionsRunLevelType_None)
+ , mAdditionsVersionFull(0), mAdditionsRevision(0), mAdditionsFeatures(0)
+#ifdef VBOX_WITH_GUEST_CONTROL
+ , mfGuestFeatures0(0), mfGuestFeatures1(0)
+#endif
+ { }
+
+ VBOXOSTYPE mOSType; /**@< For internal used. VBOXOSTYPE_Unknown if not reported. */
+ Utf8Str mOSTypeId;
+ FacilityMap mFacilityMap;
+ AdditionsRunLevelType_T mAdditionsRunLevel;
+ uint32_t mAdditionsVersionFull;
+ Utf8Str mAdditionsVersionNew;
+ uint32_t mAdditionsRevision;
+ uint32_t mAdditionsFeatures;
+ Utf8Str mInterfaceVersion;
+#ifdef VBOX_WITH_GUEST_CONTROL
+ GuestSessions mGuestSessions;
+ /** Guest control features (reported by the guest), VBOX_GUESTCTRL_GF_0_XXX. */
+ uint64_t mfGuestFeatures0;
+ /** Guest control features (reported by the guest), VBOX_GUESTCTRL_GF_1_XXX. */
+ uint64_t mfGuestFeatures1;
+#endif
+ } mData;
+
+ ULONG mMemoryBalloonSize;
+ ULONG mStatUpdateInterval; /**< In seconds. */
+ uint64_t mNetStatRx;
+ uint64_t mNetStatTx;
+ uint64_t mNetStatLastTs;
+ ULONG mCurrentGuestStat[GUESTSTATTYPE_MAX];
+ ULONG mCurrentGuestCpuUserStat[VMM_MAX_CPU_COUNT];
+ ULONG mCurrentGuestCpuKernelStat[VMM_MAX_CPU_COUNT];
+ ULONG mCurrentGuestCpuIdleStat[VMM_MAX_CPU_COUNT];
+ ULONG mVmValidStats;
+ BOOL mCollectVMMStats;
+ BOOL mfPageFusionEnabled;
+ uint32_t mCpus;
+
+ const ComObjPtr<Console> mParent;
+
+ /**
+ * This can safely be used without holding any locks.
+ * An AutoCaller suffices to prevent it being destroy while in use and
+ * internally there is a lock providing the necessary serialization.
+ */
+ const ComObjPtr<EventSource> mEventSource;
+#ifdef VBOX_WITH_GUEST_CONTROL
+ /** General extension callback for guest control. */
+ HGCMSVCEXTHANDLE mhExtCtrl;
+#endif
+
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ /** The guest's DnD source. */
+ const ComObjPtr<GuestDnDSource> mDnDSource;
+ /** The guest's DnD target. */
+ const ComObjPtr<GuestDnDTarget> mDnDTarget;
+#endif
+
+ RTTIMERLR mStatTimer;
+ uint32_t mMagic; /** @todo r=andy Rename this to something more meaningful. */
+};
+#define GUEST_MAGIC 0xCEED2006u /** @todo r=andy Not very well defined!? */
+
+#endif /* !MAIN_INCLUDED_GuestImpl_h */
+
diff --git a/src/VBox/Main/include/GuestOSTypeImpl.h b/src/VBox/Main/include/GuestOSTypeImpl.h
new file mode 100644
index 00000000..8e69bc29
--- /dev/null
+++ b/src/VBox/Main/include/GuestOSTypeImpl.h
@@ -0,0 +1,131 @@
+/* $Id: GuestOSTypeImpl.h $ */
+/** @file
+ *
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GuestOSTypeImpl_h
+#define MAIN_INCLUDED_GuestOSTypeImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "Global.h"
+#include "GuestOSTypeWrap.h"
+
+class ATL_NO_VTABLE GuestOSType :
+ public GuestOSTypeWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(GuestOSType)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(const Global::OSType &ostype);
+ void uninit();
+
+ // public methods only for internal purposes
+ const Utf8Str &i_id() const { return mID; }
+ const Utf8Str &i_familyId() const { return mFamilyID; }
+ bool i_is64Bit() const { return !!(mOSHint & VBOXOSHINT_64BIT); }
+ bool i_recommendedIOAPIC() const { return !!(mOSHint & VBOXOSHINT_IOAPIC); }
+ bool i_recommendedX2APIC() const { return !!(mOSHint & VBOXOSHINT_X2APIC); }
+ bool i_recommendedVirtEx() const { return !!(mOSHint & VBOXOSHINT_HWVIRTEX); }
+ bool i_recommendedEFI() const { return !!(mOSHint & VBOXOSHINT_EFI); }
+ bool i_recommendedEFISecureBoot() const { return !!(mOSHint & VBOXOSHINT_EFI_SECUREBOOT); }
+ bool i_recommendedTpm2() const { return !!(mOSHint & VBOXOSHINT_TPM2); }
+ NetworkAdapterType_T i_networkAdapterType() const { return mNetworkAdapterType; }
+ uint32_t i_numSerialEnabled() const { return mNumSerialEnabled; }
+
+private:
+
+ // Wrapped IGuestOSType properties
+ HRESULT getFamilyId(com::Utf8Str &aFamilyId);
+ HRESULT getFamilyDescription(com::Utf8Str &aFamilyDescription);
+ HRESULT getId(com::Utf8Str &aId);
+ HRESULT getDescription(com::Utf8Str &aDescription);
+ HRESULT getIs64Bit(BOOL *aIs64Bit);
+ HRESULT getRecommendedIOAPIC(BOOL *aRecommendedIOAPIC);
+ HRESULT getRecommendedVirtEx(BOOL *aRecommendedVirtEx);
+ HRESULT getRecommendedRAM(ULONG *RAMSize);
+ HRESULT getRecommendedGraphicsController(GraphicsControllerType_T *aRecommendedGraphicsController);
+ HRESULT getRecommendedVRAM(ULONG *aVRAMSize);
+ HRESULT getRecommended2DVideoAcceleration(BOOL *aRecommended2DVideoAcceleration);
+ HRESULT getRecommended3DAcceleration(BOOL *aRecommended3DAcceleration);
+ HRESULT getRecommendedHDD(LONG64 *aHDDSize);
+ HRESULT getAdapterType(NetworkAdapterType_T *aNetworkAdapterType);
+ HRESULT getRecommendedPAE(BOOL *aRecommendedPAE);
+ HRESULT getRecommendedDVDStorageController(StorageControllerType_T *aStorageControllerType);
+ HRESULT getRecommendedFirmware(FirmwareType_T *aFirmwareType);
+ HRESULT getRecommendedDVDStorageBus(StorageBus_T *aStorageBusType);
+ HRESULT getRecommendedHDStorageController(StorageControllerType_T *aStorageControllerType);
+ HRESULT getRecommendedHDStorageBus(StorageBus_T *aStorageBusType);
+ HRESULT getRecommendedUSBHID(BOOL *aRecommendedUSBHID);
+ HRESULT getRecommendedHPET(BOOL *aRecommendedHPET);
+ HRESULT getRecommendedUSBTablet(BOOL *aRecommendedUSBTablet);
+ HRESULT getRecommendedRTCUseUTC(BOOL *aRecommendedRTCUseUTC);
+ HRESULT getRecommendedChipset(ChipsetType_T *aChipsetType);
+ HRESULT getRecommendedIommuType(IommuType_T *aIommuType);
+ HRESULT getRecommendedAudioController(AudioControllerType_T *aAudioController);
+ HRESULT getRecommendedAudioCodec(AudioCodecType_T *aAudioCodec);
+ HRESULT getRecommendedFloppy(BOOL *aRecommendedFloppy);
+ HRESULT getRecommendedUSB(BOOL *aRecommendedUSB);
+ HRESULT getRecommendedUSB3(BOOL *aRecommendedUSB3);
+ HRESULT getRecommendedTFReset(BOOL *aRecommendedTFReset);
+ HRESULT getRecommendedX2APIC(BOOL *aRecommendedX2APIC);
+ HRESULT getRecommendedCPUCount(ULONG *aRecommendedCPUCount);
+ HRESULT getRecommendedTpmType(TpmType_T *aRecommendedTpmType);
+ HRESULT getRecommendedSecureBoot(BOOL *aRecommendedSecureBoot);
+ HRESULT getRecommendedWDDMGraphics(BOOL *aRecommendedWDDMGraphics);
+
+
+ const Utf8Str mFamilyID;
+ const Utf8Str mFamilyDescription;
+ const Utf8Str mID;
+ const Utf8Str mDescription;
+ const VBOXOSTYPE mOSType;
+ const uint32_t mOSHint;
+ const uint32_t mRAMSize;
+ const uint32_t mCPUCount;
+ const GraphicsControllerType_T mGraphicsControllerType;
+ const uint32_t mVRAMSize;
+ const uint64_t mHDDSize;
+ const NetworkAdapterType_T mNetworkAdapterType;
+ const uint32_t mNumSerialEnabled;
+ const StorageControllerType_T mDVDStorageControllerType;
+ const StorageBus_T mDVDStorageBusType;
+ const StorageControllerType_T mHDStorageControllerType;
+ const StorageBus_T mHDStorageBusType;
+ const ChipsetType_T mChipsetType;
+ const IommuType_T mIommuType;
+ const AudioControllerType_T mAudioControllerType;
+ const AudioCodecType_T mAudioCodecType;
+};
+
+#endif /* !MAIN_INCLUDED_GuestOSTypeImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/GuestProcessImpl.h b/src/VBox/Main/include/GuestProcessImpl.h
new file mode 100644
index 00000000..138529d3
--- /dev/null
+++ b/src/VBox/Main/include/GuestProcessImpl.h
@@ -0,0 +1,310 @@
+/* $Id: GuestProcessImpl.h $ */
+/** @file
+ * VirtualBox Main - Guest process handling implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GuestProcessImpl_h
+#define MAIN_INCLUDED_GuestProcessImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "GuestCtrlImplPrivate.h"
+#include "GuestProcessWrap.h"
+
+#include <iprt/cpp/utils.h>
+
+class Console;
+class GuestSession;
+class GuestProcessStartTask;
+
+/**
+ * Class for handling a guest process.
+ */
+class ATL_NO_VTABLE GuestProcess :
+ public GuestProcessWrap,
+ public GuestObject
+{
+public:
+ /** @name COM and internal init/term/mapping cruft.
+ * @{ */
+ DECLARE_COMMON_CLASS_METHODS(GuestProcess)
+
+ int init(Console *aConsole, GuestSession *aSession, ULONG aObjectID,
+ const GuestProcessStartupInfo &aProcInfo, const GuestEnvironment *pBaseEnv);
+ void uninit(void);
+ HRESULT FinalConstruct(void);
+ void FinalRelease(void);
+ /** @} */
+
+public:
+ /** @name Implemented virtual methods from GuestObject.
+ * @{ */
+ int i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb);
+ int i_onUnregister(void);
+ int i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus);
+ /** @} */
+
+public:
+ /** @name Public internal methods.
+ * @{ */
+ inline int i_checkPID(uint32_t uPID);
+ ProcessStatus_T i_getStatus(void);
+ int i_readData(uint32_t uHandle, uint32_t uSize, uint32_t uTimeoutMS, void *pvData, size_t cbData, uint32_t *pcbRead, int *pGuestRc);
+ int i_startProcess(uint32_t cMsTimeout, int *pGuestRc);
+ int i_startProcessInner(uint32_t cMsTimeout, AutoWriteLock &rLock, GuestWaitEvent *pEvent, int *pGuestRc);
+ int i_startProcessAsync(void);
+ int i_terminateProcess(uint32_t uTimeoutMS, int *pGuestRc);
+ ProcessWaitResult_T i_waitFlagsToResult(uint32_t fWaitFlags);
+ int i_waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS, ProcessWaitResult_T &waitResult, int *pGuestRc);
+ int i_waitForInputNotify(GuestWaitEvent *pEvent, uint32_t uHandle, uint32_t uTimeoutMS, ProcessInputStatus_T *pInputStatus, uint32_t *pcbProcessed);
+ int i_waitForOutput(GuestWaitEvent *pEvent, uint32_t uHandle, uint32_t uTimeoutMS, void* pvData, size_t cbData, uint32_t *pcbRead);
+ int i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t uTimeoutMS, ProcessStatus_T *pProcessStatus, int *pGuestRc);
+ int i_writeData(uint32_t uHandle, uint32_t uFlags, void *pvData, size_t cbData, uint32_t uTimeoutMS, uint32_t *puWritten, int *pGuestRc);
+ /** @} */
+
+ /** @name Static internal methods.
+ * @{ */
+ static Utf8Str i_guestErrorToString(int rcGuest, const char *pcszWhat);
+ static Utf8Str i_statusToString(ProcessStatus_T enmStatus);
+ static bool i_isGuestError(int guestRc);
+ static ProcessWaitResult_T i_waitFlagsToResultEx(uint32_t fWaitFlags, ProcessStatus_T oldStatus, ProcessStatus_T newStatus, uint32_t uProcFlags, uint32_t uProtocol);
+#if 0 /* unused */
+ static bool i_waitResultImpliesEx(ProcessWaitResult_T waitResult, ProcessStatus_T procStatus, uint32_t uProtocol);
+#endif
+ /** @} */
+
+protected:
+ /** @name Protected internal methods.
+ * @{ */
+ inline bool i_isAlive(void);
+ inline bool i_hasEnded(void);
+ int i_onGuestDisconnected(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData);
+ int i_onProcessInputStatus(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData);
+ int i_onProcessNotifyIO(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData);
+ int i_onProcessStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData);
+ int i_onProcessOutput(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData);
+ int i_prepareExecuteEnv(const char *pszEnv, void **ppvList, ULONG *pcbList, ULONG *pcEnvVars);
+ int i_setProcessStatus(ProcessStatus_T procStatus, int procRc);
+ static int i_startProcessThreadTask(GuestProcessStartTask *pTask);
+ /** @} */
+
+private:
+ /** Wrapped @name IProcess properties.
+ * @{ */
+ HRESULT getArguments(std::vector<com::Utf8Str> &aArguments);
+ HRESULT getEnvironment(std::vector<com::Utf8Str> &aEnvironment);
+ HRESULT getEventSource(ComPtr<IEventSource> &aEventSource);
+ HRESULT getExecutablePath(com::Utf8Str &aExecutablePath);
+ HRESULT getExitCode(LONG *aExitCode);
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getPID(ULONG *aPID);
+ HRESULT getStatus(ProcessStatus_T *aStatus);
+ /** @} */
+
+ /** Wrapped @name IProcess methods.
+ * @{ */
+ HRESULT waitFor(ULONG aWaitFor,
+ ULONG aTimeoutMS,
+ ProcessWaitResult_T *aReason);
+ HRESULT waitForArray(const std::vector<ProcessWaitForFlag_T> &aWaitFor,
+ ULONG aTimeoutMS,
+ ProcessWaitResult_T *aReason);
+ HRESULT read(ULONG aHandle,
+ ULONG aToRead,
+ ULONG aTimeoutMS,
+ std::vector<BYTE> &aData);
+ HRESULT write(ULONG aHandle,
+ ULONG aFlags,
+ const std::vector<BYTE> &aData,
+ ULONG aTimeoutMS,
+ ULONG *aWritten);
+ HRESULT writeArray(ULONG aHandle,
+ const std::vector<ProcessInputFlag_T> &aFlags,
+ const std::vector<BYTE> &aData,
+ ULONG aTimeoutMS,
+ ULONG *aWritten);
+ HRESULT terminate(void);
+ /** @} */
+
+ /**
+ * This can safely be used without holding any locks.
+ * An AutoCaller suffices to prevent it being destroy while in use and
+ * internally there is a lock providing the necessary serialization.
+ */
+ const ComObjPtr<EventSource> mEventSource;
+
+ struct Data
+ {
+ /** The process startup information. */
+ GuestProcessStartupInfo mProcess;
+ /** Reference to the immutable session base environment. NULL if the
+ * environment feature isn't supported.
+ * @remarks If there is proof that the uninit order of GuestSession and
+ * this class is what GuestObjectBase claims, then this isn't
+ * strictly necessary. */
+ GuestEnvironment const *mpSessionBaseEnv;
+ /** Exit code if process has been terminated. */
+ LONG mExitCode;
+ /** PID reported from the guest.
+ * Note: This is *not* the internal object ID! */
+ ULONG mPID;
+ /** The current process status. */
+ ProcessStatus_T mStatus;
+ /** The last returned process status
+ * returned from the guest side. */
+ int mLastError;
+
+ Data(void) : mpSessionBaseEnv(NULL)
+ { }
+ ~Data(void)
+ {
+ if (mpSessionBaseEnv)
+ {
+ mpSessionBaseEnv->releaseConst();
+ mpSessionBaseEnv = NULL;
+ }
+ }
+ } mData;
+
+ friend class GuestProcessStartTask;
+};
+
+/**
+ * Guest process tool wait flags.
+ */
+/** No wait flags specified; wait until process terminates.
+ * The maximum waiting time is set in the process' startup
+ * info. */
+#define GUESTPROCESSTOOL_WAIT_FLAG_NONE 0
+/** Wait until next stream block from stdout has been
+ * read in completely, then return.
+ */
+#define GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK RT_BIT(0)
+
+/**
+ * Structure for keeping a VBoxService toolbox tool's error info around.
+ */
+struct GuestProcessToolErrorInfo
+{
+ /** Return code from the guest side for executing the process tool. */
+ int rcGuest;
+ /** The process tool's returned exit code. */
+ int32_t iExitCode;
+};
+
+/**
+ * Internal class for handling the BusyBox-like tools built into VBoxService
+ * on the guest side. It's also called the VBoxService Toolbox (tm).
+ *
+ * Those initially were necessary to guarantee execution of commands (like "ls", "cat")
+ * under the behalf of a certain guest user.
+ *
+ * This class essentially helps to wrap all the gory details like process creation,
+ * information extraction and maintaining the overall status.
+ *
+ * Note! When implementing new functionality / commands, do *not* use this approach anymore!
+ * This class has to be kept to guarantee backwards-compatibility.
+ */
+class GuestProcessTool
+{
+public:
+ DECLARE_TRANSLATE_METHODS(GuestProcessTool)
+
+ GuestProcessTool(void);
+
+ virtual ~GuestProcessTool(void);
+
+public:
+
+ int init(GuestSession *pGuestSession, const GuestProcessStartupInfo &startupInfo, bool fAsync, int *pGuestRc);
+
+ void uninit(void);
+
+ int getCurrentBlock(uint32_t uHandle, GuestProcessStreamBlock &strmBlock);
+
+ int getRc(void) const;
+
+ /** Returns the stdout output from the guest process tool. */
+ GuestProcessStream &getStdOut(void) { return mStdOut; }
+
+ /** Returns the stderr output from the guest process tool. */
+ GuestProcessStream &getStdErr(void) { return mStdErr; }
+
+ int wait(uint32_t fToolWaitFlags, int *pGuestRc);
+
+ int waitEx(uint32_t fToolWaitFlags, GuestProcessStreamBlock *pStreamBlock, int *pGuestRc);
+
+ bool isRunning(void);
+
+ bool isTerminatedOk(void);
+
+ int getTerminationStatus(int32_t *piExitCode = NULL);
+
+ int terminate(uint32_t uTimeoutMS, int *pGuestRc);
+
+public:
+
+ /** Wrapped @name Static run methods.
+ * @{ */
+ static int run(GuestSession *pGuestSession, const GuestProcessStartupInfo &startupInfo, int *pGuestRc);
+
+ static int runErrorInfo(GuestSession *pGuestSession, const GuestProcessStartupInfo &startupInfo, GuestProcessToolErrorInfo &errorInfo);
+
+ static int runEx(GuestSession *pGuestSession, const GuestProcessStartupInfo &startupInfo,
+ GuestCtrlStreamObjects *pStrmOutObjects, uint32_t cStrmOutObjects, int *pGuestRc);
+
+ static int runExErrorInfo(GuestSession *pGuestSession, const GuestProcessStartupInfo &startupInfo,
+ GuestCtrlStreamObjects *pStrmOutObjects, uint32_t cStrmOutObjects, GuestProcessToolErrorInfo &errorInfo);
+ /** @} */
+
+ /** Wrapped @name Static exit code conversion methods.
+ * @{ */
+ static int exitCodeToRc(const GuestProcessStartupInfo &startupInfo, int32_t iExitCode);
+
+ static int exitCodeToRc(const char *pszTool, int32_t iExitCode);
+ /** @} */
+
+ /** Wrapped @name Static guest error conversion methods.
+ * @{ */
+ static Utf8Str guestErrorToString(const char *pszTool, const GuestErrorInfo& guestErrorInfo);
+ /** @} */
+
+protected:
+
+ /** Pointer to session this toolbox object is bound to. */
+ ComObjPtr<GuestSession> pSession;
+ /** Pointer to process object this toolbox object is bound to. */
+ ComObjPtr<GuestProcess> pProcess;
+ /** The toolbox' startup info. */
+ GuestProcessStartupInfo mStartupInfo;
+ /** Stream object for handling the toolbox' stdout data. */
+ GuestProcessStream mStdOut;
+ /** Stream object for handling the toolbox' stderr data. */
+ GuestProcessStream mStdErr;
+};
+
+#endif /* !MAIN_INCLUDED_GuestProcessImpl_h */
+
diff --git a/src/VBox/Main/include/GuestSessionImpl.h b/src/VBox/Main/include/GuestSessionImpl.h
new file mode 100644
index 00000000..8630b32a
--- /dev/null
+++ b/src/VBox/Main/include/GuestSessionImpl.h
@@ -0,0 +1,461 @@
+/* $Id: GuestSessionImpl.h $ */
+/** @file
+ * VirtualBox Main - Guest session handling.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GuestSessionImpl_h
+#define MAIN_INCLUDED_GuestSessionImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "GuestSessionWrap.h"
+#include "EventImpl.h"
+
+#include "GuestCtrlImplPrivate.h"
+#include "GuestProcessImpl.h"
+#include "GuestDirectoryImpl.h"
+#include "GuestFileImpl.h"
+#include "GuestFsObjInfoImpl.h"
+#include "GuestSessionImplTasks.h"
+
+#include <iprt/asm.h> /** @todo r=bird: Needed for ASMBitSet() in GuestSession::Data constructor. Removed when
+ * that is moved into the class implementation file as it should be. */
+#include <deque>
+
+class GuestSessionTaskInternalStart; /* Needed for i_startSessionThreadTask(). */
+
+/**
+ * Guest session implementation.
+ */
+class ATL_NO_VTABLE GuestSession
+ : public GuestSessionWrap
+ , public GuestBase
+{
+public:
+ /** @name COM and internal init/term/mapping cruft.
+ * @{ */
+ DECLARE_COMMON_CLASS_METHODS(GuestSession)
+
+ int init(Guest *pGuest, const GuestSessionStartupInfo &ssInfo, const GuestCredentials &guestCreds);
+ void uninit(void);
+ HRESULT FinalConstruct(void);
+ void FinalRelease(void);
+ /** @} */
+
+private:
+
+ /** Wrapped @name IGuestSession properties.
+ * @{ */
+ HRESULT getUser(com::Utf8Str &aUser);
+ HRESULT getDomain(com::Utf8Str &aDomain);
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getId(ULONG *aId);
+ HRESULT getTimeout(ULONG *aTimeout);
+ HRESULT setTimeout(ULONG aTimeout);
+ HRESULT getProtocolVersion(ULONG *aProtocolVersion);
+ HRESULT getStatus(GuestSessionStatus_T *aStatus);
+ HRESULT getEnvironmentChanges(std::vector<com::Utf8Str> &aEnvironmentChanges);
+ HRESULT setEnvironmentChanges(const std::vector<com::Utf8Str> &aEnvironmentChanges);
+ HRESULT getEnvironmentBase(std::vector<com::Utf8Str> &aEnvironmentBase);
+ HRESULT getProcesses(std::vector<ComPtr<IGuestProcess> > &aProcesses);
+ HRESULT getPathStyle(PathStyle_T *aPathStyle);
+ HRESULT getCurrentDirectory(com::Utf8Str &aCurrentDirectory);
+ HRESULT setCurrentDirectory(const com::Utf8Str &aCurrentDirectory);
+ HRESULT getUserDocuments(com::Utf8Str &aUserDocuments);
+ HRESULT getUserHome(com::Utf8Str &aUserHome);
+ HRESULT getDirectories(std::vector<ComPtr<IGuestDirectory> > &aDirectories);
+ HRESULT getFiles(std::vector<ComPtr<IGuestFile> > &aFiles);
+ HRESULT getEventSource(ComPtr<IEventSource> &aEventSource);
+ /** @} */
+
+ /** Wrapped @name IGuestSession methods.
+ * @{ */
+ HRESULT close();
+
+ HRESULT copyFromGuest(const std::vector<com::Utf8Str> &aSources,
+ const std::vector<com::Utf8Str> &aFilters,
+ const std::vector<com::Utf8Str> &aFlags,
+ const com::Utf8Str &aDestination,
+ ComPtr<IProgress> &aProgress);
+ HRESULT copyToGuest(const std::vector<com::Utf8Str> &aSources,
+ const std::vector<com::Utf8Str> &aFilters,
+ const std::vector<com::Utf8Str> &aFlags,
+ const com::Utf8Str &aDestination,
+ ComPtr<IProgress> &aProgress);
+
+ HRESULT directoryCopy(const com::Utf8Str &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<DirectoryCopyFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress);
+ HRESULT directoryCopyFromGuest(const com::Utf8Str &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<DirectoryCopyFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress);
+ HRESULT directoryCopyToGuest(const com::Utf8Str &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<DirectoryCopyFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress);
+ HRESULT directoryCreate(const com::Utf8Str &aPath,
+ ULONG aMode,
+ const std::vector<DirectoryCreateFlag_T> &aFlags);
+ HRESULT directoryCreateTemp(const com::Utf8Str &aTemplateName,
+ ULONG aMode,
+ const com::Utf8Str &aPath,
+ BOOL aSecure,
+ com::Utf8Str &aDirectory);
+ HRESULT directoryExists(const com::Utf8Str &aPath,
+ BOOL aFollowSymlinks,
+ BOOL *aExists);
+ HRESULT directoryOpen(const com::Utf8Str &aPath,
+ const com::Utf8Str &aFilter,
+ const std::vector<DirectoryOpenFlag_T> &aFlags,
+ ComPtr<IGuestDirectory> &aDirectory);
+ HRESULT directoryRemove(const com::Utf8Str &aPath);
+ HRESULT directoryRemoveRecursive(const com::Utf8Str &aPath,
+ const std::vector<DirectoryRemoveRecFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress);
+ HRESULT environmentScheduleSet(const com::Utf8Str &aName,
+ const com::Utf8Str &aValue);
+ HRESULT environmentScheduleUnset(const com::Utf8Str &aName);
+ HRESULT environmentGetBaseVariable(const com::Utf8Str &aName,
+ com::Utf8Str &aValue);
+ HRESULT environmentDoesBaseVariableExist(const com::Utf8Str &aName,
+ BOOL *aExists);
+
+ HRESULT fileCopy(const com::Utf8Str &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<FileCopyFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress);
+ HRESULT fileCopyToGuest(const com::Utf8Str &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<FileCopyFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress);
+ HRESULT fileCopyFromGuest(const com::Utf8Str &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<FileCopyFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress);
+ HRESULT fileCreateTemp(const com::Utf8Str &aTemplateName,
+ ULONG aMode,
+ const com::Utf8Str &aPath,
+ BOOL aSecure,
+ ComPtr<IGuestFile> &aFile);
+ HRESULT fileExists(const com::Utf8Str &aPath,
+ BOOL aFollowSymlinks,
+ BOOL *aExists);
+ HRESULT fileOpen(const com::Utf8Str &aPath,
+ FileAccessMode_T aAccessMode,
+ FileOpenAction_T aOpenAction,
+ ULONG aCreationMode,
+ ComPtr<IGuestFile> &aFile);
+ HRESULT fileOpenEx(const com::Utf8Str &aPath,
+ FileAccessMode_T aAccessMode,
+ FileOpenAction_T aOpenAction,
+ FileSharingMode_T aSharingMode,
+ ULONG aCreationMode,
+ const std::vector<FileOpenExFlag_T> &aFlags,
+ ComPtr<IGuestFile> &aFile);
+ HRESULT fileQuerySize(const com::Utf8Str &aPath,
+ BOOL aFollowSymlinks,
+ LONG64 *aSize);
+ HRESULT fsQueryFreeSpace(const com::Utf8Str &aPath, LONG64 *aFreeSpace);
+ HRESULT fsQueryInfo(const com::Utf8Str &aPath, ComPtr<IGuestFsInfo> &aInfo);
+ HRESULT fsObjExists(const com::Utf8Str &aPath,
+ BOOL aFollowSymlinks,
+ BOOL *pfExists);
+ HRESULT fsObjQueryInfo(const com::Utf8Str &aPath,
+ BOOL aFollowSymlinks,
+ ComPtr<IGuestFsObjInfo> &aInfo);
+ HRESULT fsObjRemove(const com::Utf8Str &aPath);
+ HRESULT fsObjRemoveArray(const std::vector<com::Utf8Str> &aPaths,
+ ComPtr<IProgress> &aProgress);
+ HRESULT fsObjRename(const com::Utf8Str &aOldPath,
+ const com::Utf8Str &aNewPath,
+ const std::vector<FsObjRenameFlag_T> &aFlags);
+ HRESULT fsObjMove(const com::Utf8Str &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<FsObjMoveFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress);
+ HRESULT fsObjMoveArray(const std::vector<com::Utf8Str> &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<FsObjMoveFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress);
+ HRESULT fsObjCopyArray(const std::vector<com::Utf8Str> &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<FileCopyFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress);
+ HRESULT fsObjSetACL(const com::Utf8Str &aPath,
+ BOOL aFollowSymlinks,
+ const com::Utf8Str &aAcl,
+ ULONG aMode);
+ HRESULT processCreate(const com::Utf8Str &aCommand,
+ const std::vector<com::Utf8Str> &aArguments,
+ const std::vector<com::Utf8Str> &aEnvironment,
+ const std::vector<ProcessCreateFlag_T> &aFlags,
+ ULONG aTimeoutMS,
+ ComPtr<IGuestProcess> &aGuestProcess);
+ HRESULT processCreateEx(const com::Utf8Str &aCommand,
+ const std::vector<com::Utf8Str> &aArguments,
+ const std::vector<com::Utf8Str> &aEnvironment,
+ const std::vector<ProcessCreateFlag_T> &aFlags,
+ ULONG aTimeoutMS,
+ ProcessPriority_T aPriority,
+ const std::vector<LONG> &aAffinity,
+ ComPtr<IGuestProcess> &aGuestProcess);
+ HRESULT processGet(ULONG aPid,
+ ComPtr<IGuestProcess> &aGuestProcess);
+ HRESULT symlinkCreate(const com::Utf8Str &aSource,
+ const com::Utf8Str &aTarget,
+ SymlinkType_T aType);
+ HRESULT symlinkExists(const com::Utf8Str &aSymlink,
+ BOOL *aExists);
+ HRESULT symlinkRead(const com::Utf8Str &aSymlink,
+ const std::vector<SymlinkReadFlag_T> &aFlags,
+ com::Utf8Str &aTarget);
+ HRESULT waitFor(ULONG aWaitFor,
+ ULONG aTimeoutMS,
+ GuestSessionWaitResult_T *aReason);
+ HRESULT waitForArray(const std::vector<GuestSessionWaitForFlag_T> &aWaitFor,
+ ULONG aTimeoutMS,
+ GuestSessionWaitResult_T *aReason);
+ /** @} */
+
+ /** Map of guest directories. The key specifies the internal directory ID. */
+ typedef std::map <uint32_t, ComObjPtr<GuestDirectory> > SessionDirectories;
+ /** Map of guest files. The key specifies the internal file ID. */
+ typedef std::map <uint32_t, ComObjPtr<GuestFile> > SessionFiles;
+ /** Map of guest processes. The key specifies the internal process number.
+ * To retrieve the process' guest PID use the Id() method of the IProcess interface. */
+ typedef std::map <uint32_t, ComObjPtr<GuestProcess> > SessionProcesses;
+
+ /** Guest session object type enumeration. */
+ enum SESSIONOBJECTTYPE
+ {
+ /** Invalid session object type. */
+ SESSIONOBJECTTYPE_INVALID = 0,
+ /** Session object. */
+ SESSIONOBJECTTYPE_SESSION = 1,
+ /** Directory object. */
+ SESSIONOBJECTTYPE_DIRECTORY = 2,
+ /** File object. */
+ SESSIONOBJECTTYPE_FILE = 3,
+ /** Process object. */
+ SESSIONOBJECTTYPE_PROCESS = 4
+ };
+
+ struct SessionObject
+ {
+ /** Creation timestamp (in ms).
+ * @note not used by anyone at the moment. */
+ uint64_t msBirth;
+ /** The object type. */
+ SESSIONOBJECTTYPE enmType;
+ /** Weak pointer to the object itself.
+ * Is NULL for SESSIONOBJECTTYPE_SESSION because GuestSession doesn't
+ * inherit from GuestObject. */
+ GuestObject *pObject;
+ };
+
+ /** Map containing all objects bound to a guest session.
+ * The key specifies the (global) context ID. */
+ typedef std::map<uint32_t, SessionObject> SessionObjects;
+
+public:
+ /** @name Public internal methods.
+ * @todo r=bird: Most of these are public for no real reason...
+ * @{ */
+ HRESULT i_copyFromGuest(const GuestSessionFsSourceSet &SourceSet, const com::Utf8Str &strDestination,
+ ComPtr<IProgress> &pProgress);
+ HRESULT i_copyToGuest(const GuestSessionFsSourceSet &SourceSet, const com::Utf8Str &strDestination,
+ ComPtr<IProgress> &pProgress);
+ int i_closeSession(uint32_t uFlags, uint32_t uTimeoutMS, int *pGuestRc);
+ HRESULT i_directoryCopyFlagFromStr(const com::Utf8Str &strFlags, bool fStrict, DirectoryCopyFlag_T *pfFlags);
+ bool i_directoryExists(const Utf8Str &strPath);
+ inline bool i_directoryExists(uint32_t uDirID, ComObjPtr<GuestDirectory> *pDir);
+ int i_directoryUnregister(GuestDirectory *pDirectory);
+ int i_directoryRemove(const Utf8Str &strPath, uint32_t fFlags, int *pGuestRc);
+ int i_directoryCreate(const Utf8Str &strPath, uint32_t uMode, uint32_t uFlags, int *pGuestRc);
+ int i_directoryOpen(const GuestDirectoryOpenInfo &openInfo,
+ ComObjPtr<GuestDirectory> &pDirectory, int *pGuestRc);
+ int i_directoryQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *pGuestRc);
+ int i_dispatchToObject(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb);
+ int i_dispatchToThis(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb);
+ HRESULT i_fileCopyFlagFromStr(const com::Utf8Str &strFlags, bool fStrict, FileCopyFlag_T *pfFlags);
+ inline bool i_fileExists(uint32_t uFileID, ComObjPtr<GuestFile> *pFile);
+ int i_fileUnregister(GuestFile *pFile);
+ int i_fileRemove(const Utf8Str &strPath, int *pGuestRc);
+ int i_fileOpenEx(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction,
+ FileSharingMode_T aSharingMode, ULONG aCreationMode,
+ const std::vector<FileOpenExFlag_T> &aFlags,
+ ComObjPtr<GuestFile> &pFile, int *prcGuest);
+ int i_fileOpen(const GuestFileOpenInfo &openInfo, ComObjPtr<GuestFile> &pFile, int *pGuestRc);
+ int i_fileQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *pGuestRc);
+ int i_fileQuerySize(const Utf8Str &strPath, bool fFollowSymlinks, int64_t *pllSize, int *pGuestRc);
+ int i_fsCreateTemp(const Utf8Str &strTemplate, const Utf8Str &strPath, bool fDirectory,
+ Utf8Str &strName, uint32_t fMode, bool fSecure, int *pGuestRc);
+ int i_fsQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *pGuestRc);
+ const GuestCredentials &i_getCredentials(void);
+ EventSource *i_getEventSource(void) { return mEventSource; }
+ Utf8Str i_getName(void);
+ ULONG i_getId(void) { return mData.mSession.mID; }
+ bool i_isStarted(void) const;
+ HRESULT i_isStartedExternal(void);
+ bool i_isTerminated(void) const;
+ int i_onRemove(void);
+ int i_onSessionStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData);
+ PathStyle_T i_getGuestPathStyle(void);
+ static PathStyle_T i_getHostPathStyle(void);
+ int i_startSession(int *pGuestRc);
+ int i_startSessionAsync(void);
+ Guest *i_getParent(void) { return mParent; }
+ uint32_t i_getProtocolVersion(void) { return mData.mProtocolVersion; }
+ int i_objectRegister(GuestObject *pObject, SESSIONOBJECTTYPE enmType, uint32_t *pidObject);
+ int i_objectUnregister(uint32_t uObjectID);
+ int i_objectsUnregister(void);
+ int i_objectsNotifyAboutStatusChange(GuestSessionStatus_T enmSessionStatus);
+ int i_pathRename(const Utf8Str &strSource, const Utf8Str &strDest, uint32_t uFlags, int *pGuestRc);
+ int i_pathUserDocuments(Utf8Str &strPath, int *prcGuest);
+ int i_pathUserHome(Utf8Str &strPath, int *prcGuest);
+ int i_processUnregister(GuestProcess *pProcess);
+ int i_processCreateEx(GuestProcessStartupInfo &procInfo, ComObjPtr<GuestProcess> &pProgress);
+ inline bool i_processExists(uint32_t uProcessID, ComObjPtr<GuestProcess> *pProcess);
+ inline int i_processGetByPID(ULONG uPID, ComObjPtr<GuestProcess> *pProcess);
+ int i_sendMessage(uint32_t uFunction, uint32_t uParms, PVBOXHGCMSVCPARM paParms,
+ uint64_t fDst = VBOX_GUESTCTRL_DST_SESSION);
+ int i_setSessionStatus(GuestSessionStatus_T sessionStatus, int sessionRc);
+ int i_signalWaiters(GuestSessionWaitResult_T enmWaitResult, int rc /*= VINF_SUCCESS */);
+ int i_shutdown(uint32_t fFlags, int *prcGuest);
+ int i_determineProtocolVersion(void);
+ int i_waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS, GuestSessionWaitResult_T &waitResult, int *pGuestRc);
+ int i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t fWaitFlags, uint32_t uTimeoutMS,
+ GuestSessionStatus_T *pSessionStatus, int *pGuestRc);
+ /** @} */
+
+public:
+
+ /** @name Static helper methods.
+ * @{ */
+ static Utf8Str i_guestErrorToString(int guestRc);
+ static bool i_isTerminated(GuestSessionStatus_T enmStatus);
+ static int i_startSessionThreadTask(GuestSessionTaskInternalStart *pTask);
+ /** @} */
+
+private:
+
+ /** Pointer to the parent (Guest). */
+ Guest *mParent;
+ /**
+ * The session's event source. This source is used for
+ * serving the internal listener as well as all other
+ * external listeners that may register to it.
+ *
+ * Note: This can safely be used without holding any locks.
+ * An AutoCaller suffices to prevent it being destroy while in use and
+ * internally there is a lock providing the necessary serialization.
+ */
+ const ComObjPtr<EventSource> mEventSource;
+
+ /** @todo r=bird: One of the core points of the DATA sub-structures in Main is
+ * hinding implementation details and stuff that requires including iprt/asm.h.
+ * The way it's used here totally defeats that purpose. You need to make it
+ * a pointer to a anynmous Data struct and define that structure in
+ * GuestSessionImpl.cpp and allocate it in the Init() function.
+ */
+ struct Data
+ {
+ /** The session credentials. */
+ GuestCredentials mCredentials;
+ /** The session's startup info. */
+ GuestSessionStartupInfo mSession;
+ /** The session's object ID.
+ * Needed for registering wait events which are bound directly to this session. */
+ uint32_t mObjectID;
+ /** The session's current status. */
+ GuestSessionStatus_T mStatus;
+ /** The set of environment changes for the session for use when
+ * creating new guest processes. */
+ GuestEnvironmentChanges mEnvironmentChanges;
+ /** Pointer to the immutable base environment for the session.
+ * @note This is not allocated until the guest reports it to the host. It is
+ * also shared with child processes.
+ * @todo This is actually not yet implemented, see
+ * GuestSession::i_onSessionStatusChange. */
+ GuestEnvironment const *mpBaseEnvironment;
+ /** Directory objects bound to this session. */
+ SessionDirectories mDirectories;
+ /** File objects bound to this session. */
+ SessionFiles mFiles;
+ /** Process objects bound to this session. */
+ SessionProcesses mProcesses;
+ /** Map of registered session objects (files, directories, ...). */
+ SessionObjects mObjects;
+ /** Guest control protocol version to be used.
+ * Guest Additions < VBox 4.3 have version 1,
+ * any newer version will have version 2. */
+ uint32_t mProtocolVersion;
+ /** Session timeout (in ms). */
+ uint32_t mTimeout;
+ /** The last returned session status
+ * returned from the guest side. */
+ int mRC;
+ /** Object ID allocation bitmap; clear bits are free, set bits are busy. */
+ uint64_t bmObjectIds[VBOX_GUESTCTRL_MAX_OBJECTS / sizeof(uint64_t) / 8];
+
+ Data(void)
+ : mpBaseEnvironment(NULL)
+ {
+ RT_ZERO(bmObjectIds);
+ ASMBitSet(&bmObjectIds, VBOX_GUESTCTRL_MAX_OBJECTS - 1); /* Reserved for the session itself? */
+ ASMBitSet(&bmObjectIds, 0); /* Let's reserve this too. */
+ }
+ Data(const Data &rThat)
+ : mCredentials(rThat.mCredentials)
+ , mSession(rThat.mSession)
+ , mStatus(rThat.mStatus)
+ , mEnvironmentChanges(rThat.mEnvironmentChanges)
+ , mpBaseEnvironment(NULL)
+ , mDirectories(rThat.mDirectories)
+ , mFiles(rThat.mFiles)
+ , mProcesses(rThat.mProcesses)
+ , mObjects(rThat.mObjects)
+ , mProtocolVersion(rThat.mProtocolVersion)
+ , mTimeout(rThat.mTimeout)
+ , mRC(rThat.mRC)
+ {
+ memcpy(&bmObjectIds, &rThat.bmObjectIds, sizeof(bmObjectIds));
+ }
+ ~Data(void)
+ {
+ if (mpBaseEnvironment)
+ {
+ mpBaseEnvironment->releaseConst();
+ mpBaseEnvironment = NULL;
+ }
+ }
+ } mData;
+};
+
+#endif /* !MAIN_INCLUDED_GuestSessionImpl_h */
+
diff --git a/src/VBox/Main/include/GuestSessionImplTasks.h b/src/VBox/Main/include/GuestSessionImplTasks.h
new file mode 100644
index 00000000..f44f34d2
--- /dev/null
+++ b/src/VBox/Main/include/GuestSessionImplTasks.h
@@ -0,0 +1,435 @@
+/* $Id: GuestSessionImplTasks.h $ */
+/** @file
+ * VirtualBox Main - Guest session tasks header.
+ */
+
+/*
+ * Copyright (C) 2018-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_GuestSessionImplTasks_h
+#define MAIN_INCLUDED_GuestSessionImplTasks_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "GuestSessionWrap.h"
+#include "EventImpl.h"
+#include "ProgressImpl.h"
+
+#include "GuestCtrlImplPrivate.h"
+#include "GuestSessionImpl.h"
+#include "ThreadTask.h"
+
+#include <iprt/vfs.h>
+
+#include <vector>
+
+class Guest;
+class GuestSessionTask;
+class GuestSessionTaskInternalStart;
+
+
+/**
+ * Structure for keeping a file system source specification,
+ * along with options.
+ */
+struct GuestSessionFsSourceSpec
+{
+ GuestSessionFsSourceSpec()
+ : enmType(FsObjType_Unknown)
+ , enmPathStyle(PathStyle_Unknown)
+ , fDryRun(false) { RT_ZERO(Type); }
+
+ /** The (absolute) path to the source to use. */
+ Utf8Str strSource;
+ /** Filter to use. Currently not implemented and thus ignored. */
+ Utf8Str strFilter;
+ /** The root object type of this source (directory, file). */
+ FsObjType_T enmType;
+ /** The path style to use. */
+ PathStyle_T enmPathStyle;
+ /** Whether to do a dry run (e.g. not really touching anything) or not. */
+ bool fDryRun;
+ /** Directory copy flags. */
+ DirectoryCopyFlag_T fDirCopyFlags;
+ /** File copy flags. */
+ FileCopyFlag_T fFileCopyFlags;
+ /** Union to keep type-specific data. Must be a POD type (zero'ing). */
+ union
+ {
+ /** File-specific data. */
+ struct
+ {
+ /** Source file offset to start copying from. */
+ size_t offStart;
+ /** Host file handle to use for reading from / writing to.
+ * Optional and can be NULL if not used. */
+ PRTFILE phFile;
+ /** Source size (in bytes) to copy. */
+ uint64_t cbSize;
+ } File;
+ } Type;
+};
+
+/** A set of GuestSessionFsSourceSpec sources. */
+typedef std::vector<GuestSessionFsSourceSpec> GuestSessionFsSourceSet;
+
+/**
+ * Structure for keeping a file system entry.
+ */
+struct FsEntry
+{
+ /** The entrie's file mode. */
+ RTFMODE fMode;
+ /** The entrie's path, relative to the list's root path. */
+ Utf8Str strPath;
+};
+
+/** A vector of FsEntry entries. */
+typedef std::vector<FsEntry *> FsEntries;
+
+/**
+ * Class for storing and handling file system entries, neeed for doing
+ * internal file / directory operations to / from the guest.
+ */
+class FsList
+{
+public:
+
+ FsList(const GuestSessionTask &Task);
+ virtual ~FsList();
+
+public:
+
+ int Init(const Utf8Str &strSrcRootAbs, const Utf8Str &strDstRootAbs, const GuestSessionFsSourceSpec &SourceSpec);
+ void Destroy(void);
+
+#ifdef DEBUG
+ void DumpToLog(void);
+#endif
+
+ int AddEntryFromGuest(const Utf8Str &strFile, const GuestFsObjData &fsObjData);
+ int AddDirFromGuest(const Utf8Str &strPath, const Utf8Str &strSubDir = "");
+
+ int AddEntryFromHost(const Utf8Str &strFile, PCRTFSOBJINFO pcObjInfo);
+ int AddDirFromHost(const Utf8Str &strPath, const Utf8Str &strSubDir, char *pszPathReal, size_t cbPathReal, PRTDIRENTRYEX pDirEntry);
+
+public:
+
+ /** The guest session task object this list is working on. */
+ const GuestSessionTask &mTask;
+ /** File system filter / options to use for this task. */
+ GuestSessionFsSourceSpec mSourceSpec;
+ /** The source' root path. Always in the source's path style!
+ *
+ * For a single file list this is the full (absolute) path to a file,
+ * for a directory list this is the source root directory. */
+ Utf8Str mSrcRootAbs;
+ /** The destinations's root path. Always in the destination's path style!
+ *
+ * For a single file list this is the full (absolute) path to a file,
+ * for a directory list this is the destination root directory. */
+ Utf8Str mDstRootAbs;
+ /** Total size (in bytes) of all list entries together. */
+ uint64_t mcbTotalSize;
+ /** List of file system entries this list contains. */
+ FsEntries mVecEntries;
+};
+
+/** A set of FsList lists. */
+typedef std::vector<FsList *> FsLists;
+
+/**
+ * Abstract base class for a lenghtly per-session operation which
+ * runs in a Main worker thread.
+ */
+class GuestSessionTask
+ : public ThreadTask
+{
+public:
+ DECLARE_TRANSLATE_METHODS(GuestSessionTask)
+
+ GuestSessionTask(GuestSession *pSession);
+
+ virtual ~GuestSessionTask(void);
+
+public:
+
+ /**
+ * Function which implements the actual task to perform.
+ *
+ * @returns VBox status code.
+ */
+ virtual int Run(void) = 0;
+
+ void handler()
+ {
+ int vrc = Run();
+ if (RT_FAILURE(vrc)) /* Could be VERR_INTERRUPTED if the user manually canceled the task. */
+ {
+ /* Make sure to let users know if there is a buggy task which failed but didn't set the progress object
+ * to a failed state, and if not canceled manually by the user. */
+ BOOL fCanceled;
+ if (SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled))))
+ {
+ if (!fCanceled)
+ {
+ BOOL fCompleted;
+ if (SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted))))
+ {
+ if (!fCompleted)
+ setProgressErrorMsg(E_UNEXPECTED,
+ Utf8StrFmt(tr("Task '%s' failed with %Rrc, but progress is still pending. Please report this bug!\n"),
+ mDesc.c_str(), vrc));
+ }
+ else
+ AssertReleaseMsgFailed(("Guest Control: Unable to retrieve progress completion status for task '%s' (task result is %Rrc)\n",
+ mDesc.c_str(), vrc));
+ }
+ }
+ else
+ AssertReleaseMsgFailed(("Guest Control: Unable to retrieve progress cancellation status for task '%s' (task result is %Rrc)\n",
+ mDesc.c_str(), vrc));
+ }
+ }
+
+ // unused: int RunAsync(const Utf8Str &strDesc, ComObjPtr<Progress> &pProgress);
+
+ virtual HRESULT Init(const Utf8Str &strTaskDesc)
+ {
+ setTaskDesc(strTaskDesc);
+ int rc = createAndSetProgressObject(); /* Single operation by default. */
+ if (RT_FAILURE(rc))
+ return E_FAIL;
+
+ return S_OK;
+ }
+
+ /** Returns the task's progress object. */
+ const ComObjPtr<Progress>& GetProgressObject(void) const { return mProgress; }
+
+ /** Returns the task's guest session object. */
+ const ComObjPtr<GuestSession>& GetSession(void) const { return mSession; }
+
+protected:
+
+ /** @name Directory handling primitives.
+ * @{ */
+ int directoryCreateOnGuest(const com::Utf8Str &strPath,
+ uint32_t fMode, DirectoryCreateFlag_T enmDirectoryCreateFlags,
+ bool fFollowSymlinks, bool fCanExist);
+ int directoryCreateOnHost(const com::Utf8Str &strPath, uint32_t fMode, uint32_t fCreate, bool fCanExist);
+ /** @} */
+
+ /** @name File handling primitives.
+ * @{ */
+ int fileClose(const ComObjPtr<GuestFile> &file);
+ int fileCopyFromGuestInner(const Utf8Str &strSrcFile, ComObjPtr<GuestFile> &srcFile,
+ const Utf8Str &strDstFile, PRTFILE phDstFile,
+ FileCopyFlag_T fFileCopyFlags, uint64_t offCopy, uint64_t cbSize);
+ int fileCopyFromGuest(const Utf8Str &strSource, const Utf8Str &strDest, FileCopyFlag_T fFileCopyFlags);
+ int fileCopyToGuestInner(const Utf8Str &strSrcFile, RTVFSFILE hSrcFile,
+ const Utf8Str &strDstFile, ComObjPtr<GuestFile> &dstFile,
+ FileCopyFlag_T fFileCopyFlags, uint64_t offCopy, uint64_t cbSize);
+
+ int fileCopyToGuest(const Utf8Str &strSource, const Utf8Str &strDest, FileCopyFlag_T fFileCopyFlags);
+ /** @} */
+
+ /** @name Guest property handling primitives.
+ * @{ */
+ int getGuestProperty(const ComObjPtr<Guest> &pGuest, const Utf8Str &strPath, Utf8Str &strValue);
+ /** @} */
+
+ int setProgress(ULONG uPercent);
+ int setProgressSuccess(void);
+ HRESULT setProgressErrorMsg(HRESULT hr, const Utf8Str &strMsg);
+ HRESULT setProgressErrorMsg(HRESULT hr, const Utf8Str &strMsg, const GuestErrorInfo &guestErrorInfo);
+
+ inline void setTaskDesc(const Utf8Str &strTaskDesc) throw()
+ {
+ mDesc = strTaskDesc;
+ }
+
+ int createAndSetProgressObject(ULONG cOperations = 1);
+
+protected:
+
+ Utf8Str mDesc;
+ /** The guest session object this task is working on. */
+ ComObjPtr<GuestSession> mSession;
+ /** Progress object for getting updated when running
+ * asynchronously. Optional. */
+ ComObjPtr<Progress> mProgress;
+ /** The guest's path style as char representation (depending on the guest OS type set). */
+ Utf8Str mstrGuestPathStyle;
+};
+
+/**
+ * Task for opening a guest session.
+ */
+class GuestSessionTaskOpen : public GuestSessionTask
+{
+public:
+
+ GuestSessionTaskOpen(GuestSession *pSession,
+ uint32_t uFlags,
+ uint32_t uTimeoutMS);
+ virtual ~GuestSessionTaskOpen(void);
+ int Run(void);
+
+protected:
+
+ /** Session creation flags. */
+ uint32_t mFlags;
+ /** Session creation timeout (in ms). */
+ uint32_t mTimeoutMS;
+};
+
+class GuestSessionCopyTask : public GuestSessionTask
+{
+public:
+ DECLARE_TRANSLATE_METHODS(GuestSessionCopyTask)
+
+ GuestSessionCopyTask(GuestSession *pSession);
+ virtual ~GuestSessionCopyTask();
+
+protected:
+
+ /** Source set. */
+ GuestSessionFsSourceSet mSources;
+ /** Destination to copy to. */
+ Utf8Str mDest;
+ /** Vector of file system lists to handle.
+ * This either can be from the guest or the host side. */
+ FsLists mVecLists;
+};
+
+/**
+ * Guest session task for copying files / directories from guest to the host.
+ */
+class GuestSessionTaskCopyFrom : public GuestSessionCopyTask
+{
+public:
+ DECLARE_TRANSLATE_METHODS(GuestSessionTaskCopyFrom)
+
+ GuestSessionTaskCopyFrom(GuestSession *pSession, GuestSessionFsSourceSet const &vecSrc, const Utf8Str &strDest);
+ virtual ~GuestSessionTaskCopyFrom(void);
+
+ HRESULT Init(const Utf8Str &strTaskDesc);
+ int Run(void);
+};
+
+/**
+ * Task for copying directories from host to the guest.
+ */
+class GuestSessionTaskCopyTo : public GuestSessionCopyTask
+{
+public:
+ DECLARE_TRANSLATE_METHODS(GuestSessionTaskCopyTo)
+
+ GuestSessionTaskCopyTo(GuestSession *pSession, GuestSessionFsSourceSet const &vecSrc, const Utf8Str &strDest);
+ virtual ~GuestSessionTaskCopyTo(void);
+
+ HRESULT Init(const Utf8Str &strTaskDesc);
+ int Run(void);
+};
+
+/**
+ * Guest session task for automatically updating the Guest Additions on the guest.
+ */
+class GuestSessionTaskUpdateAdditions : public GuestSessionTask
+{
+public:
+ DECLARE_TRANSLATE_METHODS(GuestSessionTaskUpdateAdditions)
+
+ GuestSessionTaskUpdateAdditions(GuestSession *pSession, const Utf8Str &strSource,
+ const ProcessArguments &aArguments, uint32_t fFlags);
+ virtual ~GuestSessionTaskUpdateAdditions(void);
+ int Run(void);
+
+protected:
+
+ /**
+ * Suported OS types for automatic updating.
+ */
+ enum eOSType
+ {
+ eOSType_Unknown = 0,
+ eOSType_Windows = 1,
+ eOSType_Linux = 2,
+ eOSType_Solaris = 3
+ };
+
+ /**
+ * Structure representing a file to
+ * get off the .ISO, copied to the guest.
+ */
+ struct ISOFile
+ {
+ ISOFile(const Utf8Str &aSource,
+ const Utf8Str &aDest,
+ uint32_t aFlags = 0)
+ : strSource(aSource),
+ strDest(aDest),
+ fFlags(aFlags) { }
+
+ ISOFile(const Utf8Str &aSource,
+ const Utf8Str &aDest,
+ uint32_t aFlags,
+ const GuestProcessStartupInfo &aStartupInfo)
+ : strSource(aSource),
+ strDest(aDest),
+ fFlags(aFlags),
+ mProcInfo(aStartupInfo)
+ {
+ mProcInfo.mExecutable = strDest;
+ if (mProcInfo.mName.isEmpty())
+ mProcInfo.mName = strDest;
+ }
+
+ /** Source file on .ISO. */
+ Utf8Str strSource;
+ /** Destination file on the guest. */
+ Utf8Str strDest;
+ /** ISO file flags (see ISOFILE_FLAG_ defines). */
+ uint32_t fFlags;
+ /** Optional arguments if this file needs to be
+ * executed. */
+ GuestProcessStartupInfo mProcInfo;
+ };
+
+ int addProcessArguments(ProcessArguments &aArgumentsDest, const ProcessArguments &aArgumentsSource);
+ int copyFileToGuest(GuestSession *pSession, RTVFS hVfsIso, Utf8Str const &strFileSource, const Utf8Str &strFileDest, bool fOptional);
+ int runFileOnGuest(GuestSession *pSession, GuestProcessStartupInfo &procInfo);
+ int waitForGuestSession(ComObjPtr<Guest> pGuest);
+
+ /** Files to handle. */
+ std::vector<ISOFile> mFiles;
+ /** The (optionally) specified Guest Additions .ISO on the host
+ * which will be used for the updating process. */
+ Utf8Str mSource;
+ /** (Optional) installer command line arguments. */
+ ProcessArguments mArguments;
+ /** Update flags. */
+ uint32_t mFlags;
+};
+#endif /* !MAIN_INCLUDED_GuestSessionImplTasks_h */
diff --git a/src/VBox/Main/include/HGCM.h b/src/VBox/Main/include/HGCM.h
new file mode 100644
index 00000000..9daafe32
--- /dev/null
+++ b/src/VBox/Main/include/HGCM.h
@@ -0,0 +1,69 @@
+/* $Id: HGCM.h $ */
+/** @file
+ * HGCM - Host-Guest Communication Manager.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HGCM_h
+#define MAIN_INCLUDED_HGCM_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/vmm/pdmifs.h>
+
+#include <VBox/hgcmsvc.h>
+
+/* Handle of a HGCM service extension. */
+struct _HGCMSVCEXTHANDLEDATA;
+typedef struct _HGCMSVCEXTHANDLEDATA *HGCMSVCEXTHANDLE;
+
+RT_C_DECLS_BEGIN
+int HGCMHostInit(void);
+int HGCMHostShutdown(bool fUvmIsInvalid = false);
+
+int HGCMHostReset(bool fForShutdown);
+
+int HGCMHostLoad(const char *pszServiceLibrary, const char *pszServiceName,
+ PUVM pUVM, PCVMMR3VTABLE pVMM, PPDMIHGCMPORT pHgcmPort);
+
+int HGCMHostRegisterServiceExtension(HGCMSVCEXTHANDLE *pHandle, const char *pszServiceName, PFNHGCMSVCEXT pfnExtension, void *pvExtension);
+void HGCMHostUnregisterServiceExtension(HGCMSVCEXTHANDLE handle);
+
+int HGCMGuestConnect(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmdPtr, const char *pszServiceName, uint32_t *pClientID);
+int HGCMGuestDisconnect(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmdPtr, uint32_t clientID);
+int HGCMGuestCall(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmdPtr, uint32_t clientID, uint32_t function, uint32_t cParms,
+ VBOXHGCMSVCPARM *paParms, uint64_t tsArrival);
+void HGCMGuestCancelled(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmdPtr, uint32_t idClient);
+
+int HGCMHostCall(const char *pszServiceName, uint32_t function, uint32_t cParms, VBOXHGCMSVCPARM aParms[]);
+int HGCMBroadcastEvent(HGCMNOTIFYEVENT enmEvent);
+
+int HGCMHostSaveState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM);
+int HGCMHostLoadState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion);
+
+RT_C_DECLS_END
+
+#endif /* !MAIN_INCLUDED_HGCM_h */
+
diff --git a/src/VBox/Main/include/HGCMObjects.h b/src/VBox/Main/include/HGCMObjects.h
new file mode 100644
index 00000000..97322d98
--- /dev/null
+++ b/src/VBox/Main/include/HGCMObjects.h
@@ -0,0 +1,138 @@
+/* $Id: HGCMObjects.h $ */
+/** @file
+ * HGCMObjects - Host-Guest Communication Manager objects header.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HGCMObjects_h
+#define MAIN_INCLUDED_HGCMObjects_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/avl.h>
+#include <iprt/critsect.h>
+#include <iprt/asm.h>
+
+class HGCMObject;
+
+typedef struct ObjectAVLCore
+{
+ AVLU32NODECORE AvlCore;
+ HGCMObject *pSelf;
+} ObjectAVLCore;
+
+typedef enum
+{
+ HGCMOBJ_CLIENT,
+ HGCMOBJ_THREAD,
+ HGCMOBJ_MSG,
+ HGCMOBJ_SizeHack = 0x7fffffff
+} HGCMOBJ_TYPE;
+
+
+/**
+ * A referenced object.
+ */
+class HGCMReferencedObject
+{
+ private:
+ int32_t volatile m_cRefs;
+ HGCMOBJ_TYPE m_enmObjType;
+
+ protected:
+ virtual ~HGCMReferencedObject()
+ {}
+
+ public:
+ HGCMReferencedObject(HGCMOBJ_TYPE enmObjType)
+ : m_cRefs(0) /** @todo change to 1! */
+ , m_enmObjType(enmObjType)
+ {}
+
+ void Reference()
+ {
+ int32_t cRefs = ASMAtomicIncS32(&m_cRefs);
+ NOREF(cRefs);
+ Log(("Reference(%p/%d): cRefs = %d\n", this, m_enmObjType, cRefs));
+ }
+
+ void Dereference()
+ {
+ int32_t cRefs = ASMAtomicDecS32(&m_cRefs);
+ Log(("Dereference(%p/%d): cRefs = %d \n", this, m_enmObjType, cRefs));
+ AssertRelease(cRefs >= 0);
+
+ if (cRefs)
+ { /* likely */ }
+ else
+ delete this;
+ }
+
+ HGCMOBJ_TYPE Type()
+ {
+ return m_enmObjType;
+ }
+};
+
+
+class HGCMObject : public HGCMReferencedObject
+{
+ private:
+ friend uint32_t hgcmObjMake(HGCMObject *pObject, uint32_t u32HandleIn);
+
+ ObjectAVLCore m_core;
+
+ protected:
+ virtual ~HGCMObject()
+ {}
+
+ public:
+ HGCMObject(HGCMOBJ_TYPE enmObjType)
+ : HGCMReferencedObject(enmObjType)
+ {}
+
+ uint32_t Handle()
+ {
+ return (uint32_t)m_core.AvlCore.Key;
+ }
+};
+
+int hgcmObjInit();
+void hgcmObjUninit();
+
+uint32_t hgcmObjGenerateHandle(HGCMObject *pObject);
+uint32_t hgcmObjAssignHandle(HGCMObject *pObject, uint32_t u32Handle);
+
+void hgcmObjDeleteHandle(uint32_t handle);
+
+HGCMObject *hgcmObjReference(uint32_t handle, HGCMOBJ_TYPE enmObjType);
+void hgcmObjDereference(HGCMObject *pObject);
+
+uint32_t hgcmObjQueryHandleCount();
+void hgcmObjSetHandleCount(uint32_t u32HandleCount);
+
+
+#endif /* !MAIN_INCLUDED_HGCMObjects_h */
diff --git a/src/VBox/Main/include/HGCMThread.h b/src/VBox/Main/include/HGCMThread.h
new file mode 100644
index 00000000..d547fed4
--- /dev/null
+++ b/src/VBox/Main/include/HGCMThread.h
@@ -0,0 +1,238 @@
+/* $Id: HGCMThread.h $ */
+/** @file
+ * HGCMThread - Host-Guest Communication Manager worker threads header.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HGCMThread_h
+#define MAIN_INCLUDED_HGCMThread_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/types.h>
+
+#include "HGCMObjects.h"
+
+/* Forward declaration of the worker thread class. */
+class HGCMThread;
+
+/** A handle for HGCM message. */
+typedef uint32_t HGCMMSGHANDLE;
+
+/* Forward declaration of message core class. */
+class HGCMMsgCore;
+
+/** @todo comment */
+
+typedef HGCMMsgCore *FNHGCMNEWMSGALLOC(uint32_t u32MsgId);
+typedef FNHGCMNEWMSGALLOC *PFNHGCMNEWMSGALLOC;
+
+/** Function that is called after message processing by worker thread,
+ * or if an error occurred during message handling after successfully
+ * posting (hgcmMsgPost) the message to worker thread.
+ *
+ * @param result Return code either from the service which actually processed the message
+ * or from HGCM.
+ * @param pMsgCore Pointer to just processed message.
+ *
+ * @return Restricted set of VBox status codes when guest call message:
+ * @retval VINF_SUCCESS on success
+ * @retval VERR_CANCELLED if the request was cancelled.
+ * @retval VERR_ALREADY_RESET if the VM is resetting.
+ * @retval VERR_NOT_AVAILABLE if HGCM has been disconnected from the VMMDev
+ * (shouldn't happen).
+ */
+typedef DECLCALLBACKTYPE(int, FNHGCMMSGCALLBACK,(int32_t result, HGCMMsgCore *pMsgCore));
+/** Pointer to a message completeion callback function. */
+typedef FNHGCMMSGCALLBACK *PFNHGCMMSGCALLBACK;
+
+
+/** HGCM core message. */
+class HGCMMsgCore : public HGCMReferencedObject
+{
+ private:
+ friend class HGCMThread;
+
+ /** Version of message header. */
+ uint32_t m_u32Version;
+
+ /** Message number/identifier. */
+ uint32_t m_u32Msg;
+
+ /** Thread the message belongs to, referenced by the message. */
+ HGCMThread *m_pThread;
+
+ /** Callback function pointer. */
+ PFNHGCMMSGCALLBACK m_pfnCallback;
+
+ /** Next element in a message queue. */
+ HGCMMsgCore *m_pNext;
+ /** Previous element in a message queue.
+ * @todo seems not necessary. */
+ HGCMMsgCore *m_pPrev;
+
+ /** Various internal flags. */
+ uint32_t m_fu32Flags;
+
+ /** Result code for a Send */
+ int32_t m_rcSend;
+
+ protected:
+ void InitializeCore(uint32_t u32MsgId, HGCMThread *pThread);
+
+ virtual ~HGCMMsgCore();
+
+ public:
+ HGCMMsgCore() : HGCMReferencedObject(HGCMOBJ_MSG) {};
+
+ uint32_t MsgId(void) { return m_u32Msg; };
+
+ HGCMThread *Thread(void) { return m_pThread; };
+
+ /** Initialize message after it was allocated. */
+ virtual void Initialize(void) {};
+
+ /** Uninitialize message. */
+ virtual void Uninitialize(void) {};
+};
+
+
+/** HGCM worker thread function.
+ *
+ * @param pThread The HGCM thread instance.
+ * @param pvUser User specified thread parameter.
+ */
+typedef DECLCALLBACKTYPE(void, FNHGCMTHREAD,(HGCMThread *pThread, void *pvUser));
+typedef FNHGCMTHREAD *PFNHGCMTHREAD;
+
+
+/**
+ * Thread API.
+ * Based on thread handles. Internals of a thread are not exposed to users.
+ */
+
+/** Initialize threads.
+ *
+ * @return VBox status code.
+ */
+int hgcmThreadInit(void);
+void hgcmThreadUninit(void);
+
+
+/** Create a HGCM worker thread.
+ *
+ * @param ppThread Where to return the pointer to the worker thread.
+ * @param pszThreadName Name of the thread, needed by runtime.
+ * @param pfnThread The worker thread function.
+ * @param pvUser A pointer passed to worker thread.
+ * @param pszStatsSubDir The "sub-directory" under "/HGCM/" where thread
+ * statistics should be registered. The caller,
+ * HGCMService, will deregister them. NULL if no stats.
+ * @param pUVM The user mode VM handle to register statistics with.
+ * NULL if no stats.
+ * @param pVMM The VMM vtable for statistics registration. NULL if
+ * no stats.
+ *
+ * @return VBox status code.
+ */
+int hgcmThreadCreate(HGCMThread **ppThread, const char *pszThreadName, PFNHGCMTHREAD pfnThread, void *pvUser,
+ const char *pszStatsSubDir, PUVM pUVM, PCVMMR3VTABLE pVMM);
+
+/** Wait for termination of a HGCM worker thread.
+ *
+ * @param pThread The HGCM thread. The passed in reference is always
+ * consumed.
+ *
+ * @return VBox status code.
+ */
+int hgcmThreadWait(HGCMThread *pThread);
+
+/** Allocate a message to be posted to HGCM worker thread.
+ *
+ * @param pThread The HGCM worker thread.
+ * @param ppHandle Where to store the pointer to the new message.
+ * @param u32MsgId Message identifier.
+ * @param pfnNewMessage New message allocation callback.
+ *
+ * @return VBox status code.
+ */
+int hgcmMsgAlloc(HGCMThread *pThread, HGCMMsgCore **ppHandle, uint32_t u32MsgId, PFNHGCMNEWMSGALLOC pfnNewMessage);
+
+/** Post a message to HGCM worker thread.
+ *
+ * @param pMsg The message. Reference will be consumed!
+ * @param pfnCallback Message completion callback.
+ *
+ * @return VBox status code.
+ * @retval VINF_HGCM_ASYNC_EXECUTE on success.
+ *
+ * @thread any
+ */
+int hgcmMsgPost(HGCMMsgCore *pMsg, PFNHGCMMSGCALLBACK pfnCallback);
+
+/** Send a message to HGCM worker thread.
+ *
+ * The function will return after message is processed by thread.
+ *
+ * @param pMsg The message. Reference will be consumed!
+ *
+ * @return VBox status code.
+ *
+ * @thread any
+ */
+int hgcmMsgSend(HGCMMsgCore *pMsg);
+
+
+/* Wait for and get a message.
+ *
+ * @param pThread The HGCM worker thread.
+ * @param ppMsg Where to store returned message pointer.
+ *
+ * @return VBox status code.
+ *
+ * @thread worker thread
+ */
+int hgcmMsgGet(HGCMThread *pThread, HGCMMsgCore **ppMsg);
+
+
+/** Worker thread has processed a message previously obtained with hgcmMsgGet.
+ *
+ * @param pMsg Processed message pointer.
+ * @param result Result code, VBox status code.
+ *
+ * @return Restricted set of VBox status codes when guest call message:
+ * @retval VINF_SUCCESS on success
+ * @retval VERR_CANCELLED if the request was cancelled.
+ * @retval VERR_ALREADY_RESET if the VM is resetting.
+ * @retval VERR_NOT_AVAILABLE if HGCM has been disconnected from the VMMDev
+ * (shouldn't happen).
+ *
+ * @thread worker thread
+ */
+int hgcmMsgComplete(HGCMMsgCore *pMsg, int32_t result);
+
+
+#endif /* !MAIN_INCLUDED_HGCMThread_h */
+
diff --git a/src/VBox/Main/include/HashedPw.h b/src/VBox/Main/include/HashedPw.h
new file mode 100644
index 00000000..56583af3
--- /dev/null
+++ b/src/VBox/Main/include/HashedPw.h
@@ -0,0 +1,40 @@
+/* $Id: HashedPw.h $ */
+/** @file
+ * Main - Password Hashing
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HashedPw_h
+#define MAIN_INCLUDED_HashedPw_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cpp/ministring.h>
+
+bool VBoxIsPasswordHashed(RTCString const *a_pstrPassword);
+void VBoxHashPassword(RTCString *a_pstrPassword);
+
+#endif /* !MAIN_INCLUDED_HashedPw_h */
+
diff --git a/src/VBox/Main/include/HostAudioDeviceImpl.h b/src/VBox/Main/include/HostAudioDeviceImpl.h
new file mode 100644
index 00000000..95bae526
--- /dev/null
+++ b/src/VBox/Main/include/HostAudioDeviceImpl.h
@@ -0,0 +1,58 @@
+/* $Id: HostAudioDeviceImpl.h $ */
+
+/** @file
+ * VirtualBox COM class implementation - Host audio device implementation.
+ */
+
+/*
+ * Copyright (C) 2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HostAudioDeviceImpl_h
+#define MAIN_INCLUDED_HostAudioDeviceImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "HostAudioDeviceWrap.h"
+
+class ATL_NO_VTABLE HostAudioDevice :
+ public HostAudioDeviceWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(HostAudioDevice)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init();
+ void uninit();
+
+private:
+
+ // wrapped IHostAudioDevice properties
+
+};
+
+#endif /* !MAIN_INCLUDED_HostAudioDeviceImpl_h */
+
diff --git a/src/VBox/Main/include/HostDriveImpl.h b/src/VBox/Main/include/HostDriveImpl.h
new file mode 100644
index 00000000..9d793bb4
--- /dev/null
+++ b/src/VBox/Main/include/HostDriveImpl.h
@@ -0,0 +1,86 @@
+/* $Id: HostDriveImpl.h $ */
+/** @file
+ * VirtualBox Main - IHostDrive implementation, VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HostDriveImpl_h
+#define MAIN_INCLUDED_HostDriveImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "HostDriveWrap.h"
+
+class ATL_NO_VTABLE HostDrive
+ : public HostDriveWrap
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(HostDrive)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ /** @name Public initializer/uninitializer for internal purposes only.
+ * @{ */
+ HRESULT initFromPathAndModel(const com::Utf8Str &drivePath, const com::Utf8Str &driveModel);
+ void uninit();
+ /** @} */
+
+ com::Utf8Str i_getDrivePath() { return m.drivePath; }
+
+private:
+ /** @name wrapped IHostDrive properties
+ * @{ */
+ virtual HRESULT getPartitioningType(PartitioningType_T *aPartitioningType) RT_OVERRIDE;
+ virtual HRESULT getDrivePath(com::Utf8Str &aDrivePath) RT_OVERRIDE;
+ virtual HRESULT getUuid(com::Guid &aUuid) RT_OVERRIDE;
+ virtual HRESULT getSectorSize(ULONG *aSectorSize) RT_OVERRIDE;
+ virtual HRESULT getSize(LONG64 *aSize) RT_OVERRIDE;
+ virtual HRESULT getModel(com::Utf8Str &aModel) RT_OVERRIDE;
+ virtual HRESULT getPartitions(std::vector<ComPtr<IHostDrivePartition> > &aPartitions) RT_OVERRIDE;
+ /** @} */
+
+ /** Data. */
+ struct Data
+ {
+ Data() : cbSector(0), cbDisk(0)
+ {
+ }
+
+ PartitioningType_T partitioningType;
+ com::Utf8Str drivePath;
+ com::Guid uuid;
+ uint32_t cbSector;
+ uint64_t cbDisk;
+ com::Utf8Str model;
+ std::vector<ComPtr<IHostDrivePartition> > partitions;
+ };
+
+ Data m;
+};
+
+#endif /* !MAIN_INCLUDED_HostDriveImpl_h */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/HostDrivePartitionImpl.h b/src/VBox/Main/include/HostDrivePartitionImpl.h
new file mode 100644
index 00000000..ddd65262
--- /dev/null
+++ b/src/VBox/Main/include/HostDrivePartitionImpl.h
@@ -0,0 +1,126 @@
+/* $Id: HostDrivePartitionImpl.h $ */
+/** @file
+ * VirtualBox Main - IHostDrivePartition implementation, VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HostDrivePartitionImpl_h
+#define MAIN_INCLUDED_HostDrivePartitionImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "HostDrivePartitionWrap.h"
+
+#include <iprt/dvm.h>
+
+class ATL_NO_VTABLE HostDrivePartition
+ : public HostDrivePartitionWrap
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(HostDrivePartition)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ /** @name Public initializer/uninitializer for internal purposes only.
+ * @{ */
+ HRESULT initFromDvmVol(RTDVMVOLUME hVol);
+ void uninit();
+ /** @} */
+
+private:
+ /** @name wrapped IHostDrivePartition properties
+ * @{ */
+ /* Common: */
+ virtual HRESULT getNumber(ULONG *aNumber) RT_OVERRIDE { *aNumber = m.number; return S_OK; }
+ virtual HRESULT getSize(LONG64 *aSize) RT_OVERRIDE { *aSize = m.cbVol; return S_OK; }
+ virtual HRESULT getStart(LONG64 *aStart) RT_OVERRIDE { *aStart = m.offStart; return S_OK; }
+ virtual HRESULT getType(PartitionType_T *aType) RT_OVERRIDE { *aType = m.enmType; return S_OK; }
+ virtual HRESULT getActive(BOOL *aActive) RT_OVERRIDE { *aActive = m.active; return S_OK; }
+ /* MBR: */
+ virtual HRESULT getTypeMBR(ULONG *aTypeMBR) RT_OVERRIDE { *aTypeMBR = m.bMBRType; return S_OK; }
+ virtual HRESULT getStartCylinder(ULONG *aStartCylinder) RT_OVERRIDE { *aStartCylinder = m.firstCylinder; return S_OK; }
+ virtual HRESULT getStartHead(ULONG *aStartHead) RT_OVERRIDE { *aStartHead = m.firstHead; return S_OK; }
+ virtual HRESULT getStartSector(ULONG *aStartSector) RT_OVERRIDE { *aStartSector = m.firstSector; return S_OK; }
+ virtual HRESULT getEndCylinder(ULONG *aEndCylinder) RT_OVERRIDE { *aEndCylinder = m.lastCylinder; return S_OK; }
+ virtual HRESULT getEndHead(ULONG *aEndHead) RT_OVERRIDE { *aEndHead = m.lastHead; return S_OK; }
+ virtual HRESULT getEndSector(ULONG *aEndSector) RT_OVERRIDE { *aEndSector = m.lastSector; return S_OK; }
+ /* GPT: */
+ virtual HRESULT getTypeUuid(com::Guid &aTypeUuid) RT_OVERRIDE { aTypeUuid = m.typeUuid; return S_OK; }
+ virtual HRESULT getUuid(com::Guid &aUuid) RT_OVERRIDE { aUuid = m.uuid; return S_OK; }
+ virtual HRESULT getName(com::Utf8Str &aName) RT_OVERRIDE { return aName.assignEx(m.name); };
+ /** @} */
+
+ /** Data. */
+ struct Data
+ {
+ Data()
+ : number(0)
+ , cbVol(0)
+ , offStart(0)
+ , enmType(PartitionType_Unknown)
+ , active(FALSE)
+ , bMBRType(0)
+ , firstCylinder(0)
+ , firstHead(0)
+ , firstSector(0)
+ , lastCylinder(0)
+ , lastHead(0)
+ , lastSector(0)
+ , typeUuid()
+ , uuid()
+ , name()
+ {
+ }
+
+ ULONG number;
+ LONG64 cbVol;
+ LONG64 offStart;
+ PartitionType_T enmType;
+ BOOL active;
+ /** @name MBR specifics
+ * @{ */
+ uint8_t bMBRType;
+ uint16_t firstCylinder;
+ uint8_t firstHead;
+ uint8_t firstSector;
+ uint16_t lastCylinder;
+ uint8_t lastHead;
+ uint8_t lastSector;
+ /** @} */
+ /** @name GPT specifics
+ * @{ */
+ com::Guid typeUuid;
+ com::Guid uuid;
+ com::Utf8Str name;
+ /** @} */
+ };
+
+ Data m;
+};
+
+#endif /* !MAIN_INCLUDED_HostDrivePartitionImpl_h */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/HostHardwareLinux.h b/src/VBox/Main/include/HostHardwareLinux.h
new file mode 100644
index 00000000..666c43f1
--- /dev/null
+++ b/src/VBox/Main/include/HostHardwareLinux.h
@@ -0,0 +1,209 @@
+/* $Id: HostHardwareLinux.h $ */
+/** @file
+ * VirtualBox Main - Classes for handling hardware detection under Linux.
+ *
+ * Please feel free to expand these to work for other systems (Solaris!) or to
+ * add new ones for other systems.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HostHardwareLinux_h
+#define MAIN_INCLUDED_HostHardwareLinux_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/err.h>
+#include <iprt/cpp/ministring.h>
+#include <vector>
+#include <vector.h>
+
+/**
+ * Class for probing and returning information about host DVD and floppy
+ * drives. To use this class, create an instance, call one of the update
+ * methods to do the actual probing and use the iterator methods to get the
+ * result of the probe.
+ */
+class VBoxMainDriveInfo
+{
+public:
+ /** Structure describing a host drive */
+ struct DriveInfo
+ {
+ /** The device node of the drive. */
+ RTCString mDevice;
+ /** A unique identifier for the device, if available. This should be
+ * kept consistent across different probing methods of a given
+ * platform if at all possible. */
+ RTCString mUdi;
+ /** A textual description of the drive. */
+ RTCString mDescription;
+
+ /** Constructors */
+ DriveInfo(const RTCString &aDevice,
+ const RTCString &aUdi = "",
+ const RTCString &aDescription = "")
+ : mDevice(aDevice),
+ mUdi(aUdi),
+ mDescription(aDescription)
+ { }
+ };
+
+ /** List (resp vector) holding drive information */
+ typedef std::vector<DriveInfo> DriveInfoList;
+
+ /**
+ * Search for host floppy drives and rebuild the list, which remains empty
+ * until the first time this method is called.
+ * @returns iprt status code
+ */
+ int updateFloppies() RT_NOEXCEPT;
+
+ /**
+ * Search for host DVD drives and rebuild the list, which remains empty
+ * until the first time this method is called.
+ * @returns iprt status code
+ */
+ int updateDVDs() RT_NOEXCEPT;
+
+ /**
+ * Search for fixed disks (HDDs) and rebuild the list, which remains empty until
+ * the first time this method is called.
+ * @returns iprt status code
+ */
+ int updateFixedDrives() RT_NOEXCEPT;
+
+ /** Get the first element in the list of floppy drives. */
+ DriveInfoList::const_iterator FloppyBegin()
+ {
+ return mFloppyList.begin();
+ }
+
+ /** Get the last element in the list of floppy drives. */
+ DriveInfoList::const_iterator FloppyEnd()
+ {
+ return mFloppyList.end();
+ }
+
+ /** Get the first element in the list of DVD drives. */
+ DriveInfoList::const_iterator DVDBegin()
+ {
+ return mDVDList.begin();
+ }
+
+ /** Get the last element in the list of DVD drives. */
+ DriveInfoList::const_iterator DVDEnd()
+ {
+ return mDVDList.end();
+ }
+
+ /** Get the first element in the list of fixed drives. */
+ DriveInfoList::const_iterator FixedDriveBegin()
+ {
+ return mFixedDriveList.begin();
+ }
+
+ /** Get the last element in the list of fixed drives. */
+ DriveInfoList::const_iterator FixedDriveEnd()
+ {
+ return mFixedDriveList.end();
+ }
+private:
+ /** The list of currently available floppy drives */
+ DriveInfoList mFloppyList;
+ /** The list of currently available DVD drives */
+ DriveInfoList mDVDList;
+ /** The list of currently available fixed drives */
+ DriveInfoList mFixedDriveList;
+};
+
+/** Convenience typedef. */
+typedef VBoxMainDriveInfo::DriveInfoList DriveInfoList;
+/** Convenience typedef. */
+typedef VBoxMainDriveInfo::DriveInfo DriveInfo;
+
+/** Implementation of the hotplug waiter class below */
+class VBoxMainHotplugWaiterImpl
+{
+public:
+ VBoxMainHotplugWaiterImpl(void) {}
+ virtual ~VBoxMainHotplugWaiterImpl(void) {}
+ /** @copydoc VBoxMainHotplugWaiter::Wait */
+ virtual int Wait(RTMSINTERVAL cMillies) = 0;
+ /** @copydoc VBoxMainHotplugWaiter::Interrupt */
+ virtual void Interrupt(void) = 0;
+ /** @copydoc VBoxMainHotplugWaiter::getStatus */
+ virtual int getStatus(void) = 0;
+};
+
+/**
+ * Class for waiting for a hotplug event. To use this class, create an
+ * instance and call the @a Wait() method, which blocks until an event or a
+ * user-triggered interruption occurs. Call @a Interrupt() to interrupt the
+ * wait before an event occurs.
+ */
+class VBoxMainHotplugWaiter
+{
+ /** Class implementation. */
+ VBoxMainHotplugWaiterImpl *mImpl;
+public:
+ /** Constructor. Responsible for selecting the implementation. */
+ VBoxMainHotplugWaiter(const char *pcszDevicesRoot);
+ /** Destructor. */
+ ~VBoxMainHotplugWaiter (void)
+ {
+ delete mImpl;
+ }
+ /**
+ * Wait for a hotplug event.
+ *
+ * @returns VINF_SUCCESS if an event occurred or if Interrupt() was called.
+ * @returns VERR_TRY_AGAIN if the wait failed but this might (!) be a
+ * temporary failure.
+ * @returns VERR_NOT_SUPPORTED if the wait failed and will definitely not
+ * succeed if retried.
+ * @returns Possibly other iprt status codes otherwise.
+ * @param cMillies How long to wait for at most.
+ */
+ int Wait (RTMSINTERVAL cMillies)
+ {
+ return mImpl->Wait(cMillies);
+ }
+ /**
+ * Interrupts an active wait. In the current implementation, the wait
+ * may not return until up to two seconds after calling this method.
+ */
+ void Interrupt (void)
+ {
+ mImpl->Interrupt();
+ }
+
+ int getStatus(void)
+ {
+ return mImpl ? mImpl->getStatus() : VERR_NO_MEMORY;
+ }
+};
+
+#endif /* !MAIN_INCLUDED_HostHardwareLinux_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/HostImpl.h b/src/VBox/Main/include/HostImpl.h
new file mode 100644
index 00000000..9f231920
--- /dev/null
+++ b/src/VBox/Main/include/HostImpl.h
@@ -0,0 +1,227 @@
+/* $Id: HostImpl.h $ */
+/** @file
+ * Implementation of IHost.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HostImpl_h
+#define MAIN_INCLUDED_HostImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "HostWrap.h"
+
+class HostUSBDeviceFilter;
+class USBProxyService;
+class SessionMachine;
+class Progress;
+class PerformanceCollector;
+class HostDrive;
+class HostDrivePartition;
+
+namespace settings
+{
+ struct Host;
+}
+
+#include <list>
+
+class ATL_NO_VTABLE Host :
+ public HostWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(Host)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(VirtualBox *aParent);
+ void uninit();
+
+ // public methods only for internal purposes
+
+ /**
+ * Override of the default locking class to be used for validating lock
+ * order with the standard member lock handle.
+ */
+ virtual VBoxLockingClass getLockingClass() const
+ {
+ return LOCKCLASS_HOSTOBJECT;
+ }
+
+ HRESULT i_loadSettings(const settings::Host &data);
+ HRESULT i_saveSettings(settings::Host &data);
+
+ void i_updateProcessorFeatures();
+
+ HRESULT i_getDrives(DeviceType_T mediumType, bool fRefresh, MediaList *&pll, AutoWriteLock &treeLock);
+ HRESULT i_findHostDriveById(DeviceType_T mediumType, const Guid &uuid, bool fRefresh, ComObjPtr<Medium> &pMedium);
+ HRESULT i_findHostDriveByName(DeviceType_T mediumType, const Utf8Str &strLocationFull, bool fRefresh, ComObjPtr<Medium> &pMedium);
+
+#ifdef VBOX_WITH_USB
+ typedef std::list< ComObjPtr<HostUSBDeviceFilter> > USBDeviceFilterList;
+
+ /** Must be called from under this object's lock. */
+ USBProxyService* i_usbProxyService();
+
+ HRESULT i_addChild(HostUSBDeviceFilter *pChild);
+ HRESULT i_removeChild(HostUSBDeviceFilter *pChild);
+ VirtualBox* i_parent();
+
+ HRESULT i_onUSBDeviceFilterChange(HostUSBDeviceFilter *aFilter, BOOL aActiveChanged = FALSE);
+ void i_getUSBFilters(USBDeviceFilterList *aGlobalFiltes);
+ HRESULT i_checkUSBProxyService();
+#endif /* !VBOX_WITH_USB */
+
+ static void i_generateMACAddress(Utf8Str &mac);
+
+#ifdef RT_OS_WINDOWS
+ HRESULT i_updatePersistentConfigForHostOnlyAdapters(void);
+ HRESULT i_removePersistentConfig(const Bstr &bstrGuid);
+#endif /* RT_OS_WINDOWS */
+
+
+private:
+
+ // wrapped IHost properties
+ HRESULT getDVDDrives(std::vector<ComPtr<IMedium> > &aDVDDrives);
+ HRESULT getFloppyDrives(std::vector<ComPtr<IMedium> > &aFloppyDrives);
+ HRESULT getAudioDevices(std::vector<ComPtr<IHostAudioDevice> > &aAudioDevices);
+ HRESULT getUSBDevices(std::vector<ComPtr<IHostUSBDevice> > &aUSBDevices);
+ HRESULT getUSBDeviceFilters(std::vector<ComPtr<IHostUSBDeviceFilter> > &aUSBDeviceFilters);
+ HRESULT getNetworkInterfaces(std::vector<ComPtr<IHostNetworkInterface> > &aNetworkInterfaces);
+ HRESULT getNameServers(std::vector<com::Utf8Str> &aNameServers);
+ HRESULT getDomainName(com::Utf8Str &aDomainName);
+ HRESULT getSearchStrings(std::vector<com::Utf8Str> &aSearchStrings);
+ HRESULT getProcessorCount(ULONG *aProcessorCount);
+ HRESULT getProcessorOnlineCount(ULONG *aProcessorOnlineCount);
+ HRESULT getProcessorCoreCount(ULONG *aProcessorCoreCount);
+ HRESULT getProcessorOnlineCoreCount(ULONG *aProcessorOnlineCoreCount);
+ HRESULT getHostDrives(std::vector<ComPtr<IHostDrive> > &aHostDrives);
+ HRESULT getMemorySize(ULONG *aMemorySize);
+ HRESULT getMemoryAvailable(ULONG *aMemoryAvailable);
+ HRESULT getOperatingSystem(com::Utf8Str &aOperatingSystem);
+ HRESULT getOSVersion(com::Utf8Str &aOSVersion);
+ HRESULT getUTCTime(LONG64 *aUTCTime);
+ HRESULT getAcceleration3DAvailable(BOOL *aAcceleration3DAvailable);
+ HRESULT getVideoInputDevices(std::vector<ComPtr<IHostVideoInputDevice> > &aVideoInputDevices);
+ HRESULT getUpdateHost(ComPtr<IUpdateAgent> &aUpdate);
+ HRESULT getUpdateExtPack(ComPtr<IUpdateAgent> &aUpdate);
+ HRESULT getUpdateGuestAdditions(ComPtr<IUpdateAgent> &aUpdate);
+ HRESULT getUpdateResponse(BOOL *aUpdateNeeded);
+ HRESULT getUpdateVersion(com::Utf8Str &aUpdateVersion);
+ HRESULT getUpdateURL(com::Utf8Str &aUpdateURL);
+ HRESULT getUpdateCheckNeeded(BOOL *aUpdateCheckNeeded);
+
+ // wrapped IHost methods
+ HRESULT getProcessorSpeed(ULONG aCpuId,
+ ULONG *aSpeed);
+ HRESULT getProcessorFeature(ProcessorFeature_T aFeature,
+ BOOL *aSupported);
+ HRESULT getProcessorDescription(ULONG aCpuId,
+ com::Utf8Str &aDescription);
+ HRESULT getProcessorCPUIDLeaf(ULONG aCpuId,
+ ULONG aLeaf,
+ ULONG aSubLeaf,
+ ULONG *aValEax,
+ ULONG *aValEbx,
+ ULONG *aValEcx,
+ ULONG *aValEdx);
+ HRESULT createHostOnlyNetworkInterface(ComPtr<IHostNetworkInterface> &aHostInterface,
+ ComPtr<IProgress> &aProgress);
+ HRESULT removeHostOnlyNetworkInterface(const com::Guid &aId,
+ ComPtr<IProgress> &aProgress);
+ HRESULT createUSBDeviceFilter(const com::Utf8Str &aName,
+ ComPtr<IHostUSBDeviceFilter> &aFilter);
+ HRESULT insertUSBDeviceFilter(ULONG aPosition,
+ const ComPtr<IHostUSBDeviceFilter> &aFilter);
+ HRESULT removeUSBDeviceFilter(ULONG aPosition);
+ HRESULT findHostDVDDrive(const com::Utf8Str &aName,
+ ComPtr<IMedium> &aDrive);
+ HRESULT findHostFloppyDrive(const com::Utf8Str &aName,
+ ComPtr<IMedium> &aDrive);
+ HRESULT findHostNetworkInterfaceByName(const com::Utf8Str &aName,
+ ComPtr<IHostNetworkInterface> &aNetworkInterface);
+ HRESULT findHostNetworkInterfaceById(const com::Guid &aId,
+ ComPtr<IHostNetworkInterface> &aNetworkInterface);
+ HRESULT findHostNetworkInterfacesOfType(HostNetworkInterfaceType_T aType,
+ std::vector<ComPtr<IHostNetworkInterface> > &aNetworkInterfaces);
+ HRESULT findUSBDeviceById(const com::Guid &aId,
+ ComPtr<IHostUSBDevice> &aDevice);
+ HRESULT findUSBDeviceByAddress(const com::Utf8Str &aName,
+ ComPtr<IHostUSBDevice> &aDevice);
+ HRESULT generateMACAddress(com::Utf8Str &aAddress);
+
+ HRESULT addUSBDeviceSource(const com::Utf8Str &aBackend, const com::Utf8Str &aId, const com::Utf8Str &aAddress,
+ const std::vector<com::Utf8Str> &aPropertyNames, const std::vector<com::Utf8Str> &aPropertyValues);
+
+ HRESULT removeUSBDeviceSource(const com::Utf8Str &aId);
+
+ // Internal Methods.
+
+ HRESULT i_buildDVDDrivesList(MediaList &list);
+ HRESULT i_buildFloppyDrivesList(MediaList &list);
+ HRESULT i_findHostDriveByNameOrId(DeviceType_T mediumType, const Utf8Str &strNameOrId, ComObjPtr<Medium> &pMedium);
+
+#if defined(RT_OS_SOLARIS) && defined(VBOX_USE_LIBHAL)
+ bool i_getDVDInfoFromHal(std::list< ComObjPtr<Medium> > &list);
+ bool i_getFloppyInfoFromHal(std::list< ComObjPtr<Medium> > &list);
+ HRESULT i_getFixedDrivesFromHal(std::list<std::pair<com::Utf8Str, com::Utf8Str> > &list) RT_NOEXCEPT;
+#endif
+
+#if defined(RT_OS_SOLARIS)
+ void i_getDVDInfoFromDevTree(std::list< ComObjPtr<Medium> > &list);
+ void i_parseMountTable(char *mountTable, std::list< ComObjPtr<Medium> > &list);
+ bool i_validateDevice(const char *deviceNode, bool isCDROM);
+ HRESULT i_getFixedDrivesFromDevTree(std::list<std::pair<com::Utf8Str, com::Utf8Str> > &list) RT_NOEXCEPT;
+#endif
+
+ HRESULT i_updateNetIfList();
+
+#ifndef RT_OS_WINDOWS
+ HRESULT i_parseResolvConf();
+#else
+ HRESULT i_fetchNameResolvingInformation();
+#endif
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ void i_registerMetrics(PerformanceCollector *aCollector);
+ void i_registerDiskMetrics(PerformanceCollector *aCollector);
+ void i_unregisterMetrics(PerformanceCollector *aCollector);
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+
+#ifdef RT_OS_WINDOWS
+ HRESULT i_getFixedDrivesFromGlobalNamespace(std::list<std::pair<com::Utf8Str, com::Utf8Str> > &aDriveList) RT_NOEXCEPT;
+#endif
+ HRESULT i_getDrivesPathsList(std::list<std::pair<com::Utf8Str, com::Utf8Str> > &aDriveList) RT_NOEXCEPT;
+
+ struct Data; // opaque data structure, defined in HostImpl.cpp
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_HostImpl_h */
+
diff --git a/src/VBox/Main/include/HostNetworkInterfaceImpl.h b/src/VBox/Main/include/HostNetworkInterfaceImpl.h
new file mode 100644
index 00000000..262e037f
--- /dev/null
+++ b/src/VBox/Main/include/HostNetworkInterfaceImpl.h
@@ -0,0 +1,145 @@
+/* $Id: HostNetworkInterfaceImpl.h $ */
+
+/** @file
+ *
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HostNetworkInterfaceImpl_h
+#define MAIN_INCLUDED_HostNetworkInterfaceImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "HostNetworkInterfaceWrap.h"
+
+#ifdef VBOX_WITH_HOSTNETIF_API
+struct NETIFINFO;
+#endif
+
+class PerformanceCollector;
+
+class ATL_NO_VTABLE HostNetworkInterface :
+ public HostNetworkInterfaceWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(HostNetworkInterface)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Utf8Str aInterfaceName, Utf8Str aShortName, Guid aGuid, HostNetworkInterfaceType_T ifType);
+#ifdef VBOX_WITH_HOSTNETIF_API
+ HRESULT init(Utf8Str aInterfaceName, HostNetworkInterfaceType_T ifType, struct NETIFINFO *pIfs);
+ HRESULT updateConfig();
+#endif
+
+ HRESULT i_setVirtualBox(VirtualBox *pVirtualBox);
+#ifdef RT_OS_WINDOWS
+ HRESULT i_updatePersistentConfig();
+#endif /* RT_OS_WINDOWS */
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ void i_registerMetrics(PerformanceCollector *aCollector, ComPtr<IUnknown> objptr);
+ void i_unregisterMetrics(PerformanceCollector *aCollector, ComPtr<IUnknown> objptr);
+#endif
+
+private:
+
+ // Wrapped IHostNetworkInterface properties
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getShortName(com::Utf8Str &aShortName);
+ HRESULT getId(com::Guid &aGuiId);
+ HRESULT getDHCPEnabled(BOOL *aDHCPEnabled);
+ HRESULT getIPAddress(com::Utf8Str &aIPAddress);
+ HRESULT getNetworkMask(com::Utf8Str &aNetworkMask);
+ HRESULT getIPV6Supported(BOOL *aIPV6Supported);
+ HRESULT getIPV6Address(com::Utf8Str &aIPV6Address);
+ HRESULT getIPV6NetworkMaskPrefixLength(ULONG *aIPV6NetworkMaskPrefixLength);
+ HRESULT getHardwareAddress(com::Utf8Str &aHardwareAddress);
+ HRESULT getMediumType(HostNetworkInterfaceMediumType_T *aType);
+ HRESULT getStatus(HostNetworkInterfaceStatus_T *aStatus);
+ HRESULT getInterfaceType(HostNetworkInterfaceType_T *aType);
+ HRESULT getNetworkName(com::Utf8Str &aNetworkName);
+ HRESULT getWireless(BOOL *aWireless);
+
+ // Wrapped IHostNetworkInterface methods
+ HRESULT enableStaticIPConfig(const com::Utf8Str &aIPAddress,
+ const com::Utf8Str &aNetworkMask);
+ HRESULT enableStaticIPConfigV6(const com::Utf8Str &aIPV6Address,
+ ULONG aIPV6NetworkMaskPrefixLength);
+ HRESULT enableDynamicIPConfig();
+ HRESULT dHCPRediscover();
+
+ Utf8Str i_composeNetworkName(const Utf8Str szShortName);
+
+#if defined(RT_OS_WINDOWS)
+ HRESULT eraseAdapterConfigParameter(const char *szParamName);
+ HRESULT saveAdapterConfigParameter(const char *szParamName, const Utf8Str& strValue);
+ HRESULT saveAdapterConfigIPv4Dhcp();
+ HRESULT saveAdapterConfigIPv4(ULONG addr, ULONG mask);
+ HRESULT saveAdapterConfigIPv6(const Utf8Str& addr, ULONG prefix);
+ HRESULT saveAdapterConfig();
+ bool isInConfigFile();
+#endif /* defined(RT_OS_WINDOWS) */
+
+ const Utf8Str mInterfaceName;
+ const Guid mGuid;
+ const Utf8Str mNetworkName;
+ const Utf8Str mShortName;
+ HostNetworkInterfaceType_T mIfType;
+
+ VirtualBox * const mVirtualBox;
+
+ struct Data
+ {
+ Data() : IPAddress(0), networkMask(0), dhcpEnabled(FALSE),
+ mediumType(HostNetworkInterfaceMediumType_Unknown),
+ status(HostNetworkInterfaceStatus_Down), wireless(FALSE){}
+
+ ULONG IPAddress;
+ ULONG networkMask;
+ Utf8Str IPV6Address;
+ ULONG IPV6NetworkMaskPrefixLength;
+ ULONG realIPAddress;
+ ULONG realNetworkMask;
+ Utf8Str realIPV6Address;
+ ULONG realIPV6PrefixLength;
+ BOOL dhcpEnabled;
+ Utf8Str hardwareAddress;
+ HostNetworkInterfaceMediumType_T mediumType;
+ HostNetworkInterfaceStatus_T status;
+ ULONG speedMbits;
+ BOOL wireless;
+ } m;
+
+};
+
+typedef std::list<ComObjPtr<HostNetworkInterface> > HostNetworkInterfaceList;
+
+#endif /* !MAIN_INCLUDED_HostNetworkInterfaceImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/HostOnlyNetworkImpl.h b/src/VBox/Main/include/HostOnlyNetworkImpl.h
new file mode 100644
index 00000000..c71338de
--- /dev/null
+++ b/src/VBox/Main/include/HostOnlyNetworkImpl.h
@@ -0,0 +1,80 @@
+/* $Id: HostOnlyNetworkImpl.h $ */
+/** @file
+ * IHostOnlyNetwork implementation header, lives in VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2021-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HostOnlyNetworkImpl_h
+#define MAIN_INCLUDED_HostOnlyNetworkImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "HostOnlyNetworkWrap.h"
+
+namespace settings
+{
+ struct HostOnlyNetwork;
+}
+
+class ATL_NO_VTABLE HostOnlyNetwork :
+ public HostOnlyNetworkWrap
+{
+public:
+
+ DECLARE_EMPTY_CTOR_DTOR(HostOnlyNetwork)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ HRESULT init(VirtualBox *aVirtualBox, com::Utf8Str aName);
+ HRESULT i_loadSettings(const settings::HostOnlyNetwork &data);
+ void uninit();
+ HRESULT i_saveSettings(settings::HostOnlyNetwork &data);
+
+ // Internal methods
+ // Utf8Str i_getNetworkName();
+ // Utf8Str i_getNetworkId();
+private:
+
+ // Wrapped IHostOnlyNetwork properties
+ HRESULT getNetworkName(com::Utf8Str &aNetworkName);
+ HRESULT setNetworkName(const com::Utf8Str &aNetworkName);
+ HRESULT getNetworkMask(com::Utf8Str &aNetworkMask);
+ HRESULT setNetworkMask(const com::Utf8Str &aNetworkMask);
+ HRESULT getEnabled(BOOL *aEnabled);
+ HRESULT setEnabled(BOOL aEnabled);
+ HRESULT getHostIP(com::Utf8Str &aHostIP);
+ HRESULT getLowerIP(com::Utf8Str &aLowerIP);
+ HRESULT setLowerIP(const com::Utf8Str &aLowerIP);
+ HRESULT getUpperIP(com::Utf8Str &aUpperIP);
+ HRESULT setUpperIP(const com::Utf8Str &aUpperIP);
+ HRESULT getId(com::Guid &aId);
+ HRESULT setId(const com::Guid &aId);
+
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_HostOnlyNetworkImpl_h */
diff --git a/src/VBox/Main/include/HostPower.h b/src/VBox/Main/include/HostPower.h
new file mode 100644
index 00000000..e157d6da
--- /dev/null
+++ b/src/VBox/Main/include/HostPower.h
@@ -0,0 +1,136 @@
+/* $Id: HostPower.h $ */
+/** @file
+ *
+ * VirtualBox interface to host's power notification service
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HostPower_h
+#define MAIN_INCLUDED_HostPower_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#ifdef RT_OS_DARWIN /* first, so we can undef pVM in iprt/cdefs.h */
+# include <IOKit/pwr_mgt/IOPMLib.h>
+# include <Carbon/Carbon.h>
+#endif
+
+#include "VirtualBoxBase.h"
+
+#include <vector>
+
+#ifdef RT_OS_LINUX
+# include <VBox/dbus.h>
+#endif
+
+class HostPowerService
+{
+ public:
+ HostPowerService(VirtualBox *aVirtualBox);
+ virtual ~HostPowerService();
+ void notify(Reason_T aReason);
+
+ protected:
+ VirtualBox *mVirtualBox;
+ std::vector<ComPtr<IInternalSessionControl> > mSessionControls;
+};
+
+# if defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING)
+/**
+ * The Windows hosted Power Service.
+ */
+class HostPowerServiceWin : public HostPowerService
+{
+public:
+
+ HostPowerServiceWin(VirtualBox *aVirtualBox);
+ virtual ~HostPowerServiceWin();
+
+private:
+
+ static DECLCALLBACK(int) NotificationThread(RTTHREAD ThreadSelf, void *pInstance);
+ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+ HWND mHwnd;
+ RTTHREAD mThread;
+};
+# endif
+# if defined(RT_OS_LINUX) || defined(DOXYGEN_RUNNING)
+/**
+ * The Linux hosted Power Service.
+ */
+class HostPowerServiceLinux : public HostPowerService
+{
+public:
+
+ HostPowerServiceLinux(VirtualBox *aVirtualBox);
+ virtual ~HostPowerServiceLinux();
+
+private:
+
+ static DECLCALLBACK(int) powerChangeNotificationThread(RTTHREAD ThreadSelf, void *pInstance);
+
+ /* Private member vars */
+ /** Our message thread. */
+ RTTHREAD mThread;
+ /** Our (private) connection to the DBus. Closing this will cause the
+ * message thread to exit. */
+ DBusConnection *mpConnection;
+};
+
+# endif
+# if defined(RT_OS_DARWIN) || defined(DOXYGEN_RUNNING)
+/**
+ * The Darwin hosted Power Service.
+ */
+class HostPowerServiceDarwin : public HostPowerService
+{
+public:
+
+ HostPowerServiceDarwin(VirtualBox *aVirtualBox);
+ virtual ~HostPowerServiceDarwin();
+
+private:
+
+ static DECLCALLBACK(int) powerChangeNotificationThread(RTTHREAD ThreadSelf, void *pInstance);
+ static void powerChangeNotificationHandler(void *pvData, io_service_t service, natural_t messageType, void *pMessageArgument);
+ static void lowPowerHandler(void *pvData);
+
+ void checkBatteryCriticalLevel(bool *pfCriticalChanged = NULL);
+
+ /* Private member vars */
+ RTTHREAD mThread; /* Our message thread. */
+
+ io_connect_t mRootPort; /* A reference to the Root Power Domain IOService */
+ IONotificationPortRef mNotifyPort; /* Notification port allocated by IORegisterForSystemPower */
+ io_object_t mNotifierObject; /* Notifier object, used to deregister later */
+ CFRunLoopRef mRunLoop; /* A reference to the local thread run loop */
+
+ bool mCritical; /* Indicate if the battery was in the critical state last checked */
+};
+# endif
+
+#endif /* !MAIN_INCLUDED_HostPower_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/HostUSBDeviceImpl.h b/src/VBox/Main/include/HostUSBDeviceImpl.h
new file mode 100644
index 00000000..431abb7b
--- /dev/null
+++ b/src/VBox/Main/include/HostUSBDeviceImpl.h
@@ -0,0 +1,327 @@
+/* $Id: HostUSBDeviceImpl.h $ */
+/** @file
+ * VirtualBox IHostUSBDevice COM interface implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HostUSBDeviceImpl_h
+#define MAIN_INCLUDED_HostUSBDeviceImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VirtualBoxBase.h"
+#include "USBDeviceFilterImpl.h"
+#include <VBox/usb.h>
+#include "HostUSBDeviceWrap.h"
+
+class SessionMachine;
+class USBProxyBackend;
+
+/**
+ * The unified state machine of HostUSBDevice.
+ *
+ * This is a super set of USBDEVICESTATE / USBDeviceState_T that
+ * includes additional states for tracking state transitions.
+ *
+ * @remarks
+ * The CapturingForVM and CapturingForProxy states have been merged
+ * into Capturing with a destination state (AttachingToVM or HeldByProxy).
+ *
+ * The DetachingFromVM state is a merge of DetachingFromVMToProxy and
+ * DetachingFromVMToHost and uses the destination state (HeldByProxy
+ * or ReleasingToHost) like Capturing.
+ *
+ * The *AwaitingDetach and *AwaitingReattach substates (optionally used
+ * in Capturing, AttachingToVM, DetachingFromVM and ReleasingToHost) are
+ * implemented via a substate kHostUSBDeviceSubState.
+ */
+typedef enum
+{
+ /** The device is unsupported (HUB).
+ * Next Host: PhysDetached.
+ * Next VBox: No change permitted.
+ */
+ kHostUSBDeviceState_Unsupported = USBDEVICESTATE_UNSUPPORTED,
+ /** The device is used exclusivly by the host or is inaccessible for some other reason.
+ * Next Host: Capturable, Unused, PhysDetached.
+ * Run filters.
+ * Next VBox: No change permitted.
+ */
+ kHostUSBDeviceState_UsedByHost = USBDEVICESTATE_USED_BY_HOST,
+ /** The device is used by the host but can be captured.
+ * Next Host: Unsupported, UsedByHost, Unused, PhysDetached.
+ * Run filters if Unused (for wildcard filters).
+ * Next VBox: CapturingForVM, CapturingForProxy.
+ */
+ kHostUSBDeviceState_Capturable = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE,
+ /** The device is not used by the host and can be captured.
+ * Next Host: UsedByHost, Capturable, PhysDetached
+ * Don't run any filters (done on state entry).
+ * Next VBox: CapturingForVM, CapturingForProxy.
+ */
+ kHostUSBDeviceState_Unused = USBDEVICESTATE_UNUSED,
+ /** The device is held captive by the proxy.
+ * Next Host: PhysDetached
+ * Next VBox: ReleasingHeld, AttachingToVM
+ */
+ kHostUSBDeviceState_HeldByProxy = USBDEVICESTATE_HELD_BY_PROXY,
+ /** The device is in use by a VM.
+ * Next Host: PhysDetachingFromVM
+ * Next VBox: DetachingFromVM
+ */
+ kHostUSBDeviceState_UsedByVM = USBDEVICESTATE_USED_BY_GUEST,
+ /** The device has been detach from both the host and VMs.
+ * This is the final state. */
+ kHostUSBDeviceState_PhysDetached = 9,
+
+
+ /** The start of the transitional states. */
+ kHostUSBDeviceState_FirstTransitional,
+
+ /** The device is being seized from the host, either for HeldByProxy or for AttachToVM.
+ *
+ * On some hosts we will need to re-enumerate the in which case the sub-state
+ * is employed to track this progress. On others, this is synchronous or faked, and
+ * will will then leave the device in this state and poke the service thread to do
+ * the completion state change.
+ *
+ * Next Host: PhysDetached.
+ * Next VBox: HeldByProxy or AttachingToVM on success,
+ * previous state (Unused or Capturable) or UsedByHost on failure.
+ */
+ kHostUSBDeviceState_Capturing = kHostUSBDeviceState_FirstTransitional,
+
+ /** The device is being released back to the host, following VM or Proxy usage.
+ * Most hosts needs to re-enumerate the device and will therefore employ the
+ * sub-state as during capturing. On the others we'll just leave it to the usb
+ * service thread to advance the device state.
+ *
+ * Next Host: Unused, UsedByHost, Capturable.
+ * No filters.
+ * Next VBox: PhysDetached (timeout), HeldByProxy (failure).
+ */
+ kHostUSBDeviceState_ReleasingToHost,
+
+ /** The device is being attached to a VM.
+ *
+ * This requires IPC to the VM and we will not advance the state until
+ * that completes.
+ *
+ * Next Host: PhysDetachingFromVM.
+ * Next VBox: UsedByGuest, HeldByProxy (failure).
+ */
+ kHostUSBDeviceState_AttachingToVM,
+
+ /** The device is being detached from a VM and will be returned to the proxy or host.
+ *
+ * This involves IPC and may or may not also require re-enumeration of the
+ * device. Which means that it might transition directly into the ReleasingToHost state
+ * because the client (VM) will do the actual re-enumeration.
+ *
+ * Next Host: PhysDetachingFromVM (?) or just PhysDetached.
+ * Next VBox: ReleasingToHost, HeldByProxy.
+ */
+ kHostUSBDeviceState_DetachingFromVM,
+
+ /** The device has been physically removed while a VM used it.
+ *
+ * This is the device state while VBoxSVC is doing IPC to the client (VM) telling it
+ * to detach it.
+ *
+ * Next Host: None.
+ * Next VBox: PhysDetached
+ */
+ kHostUSBDeviceState_PhysDetachingFromVM,
+
+ /** Just an invalid state value for use as default for some methods. */
+ kHostUSBDeviceState_Invalid = 0x7fff
+} HostUSBDeviceState;
+
+
+/**
+ * Sub-state for dealing with device re-enumeration.
+ */
+typedef enum
+{
+ /** Not in any sub-state. */
+ kHostUSBDeviceSubState_Default = 0,
+ /** Awaiting a logical device detach following a device re-enumeration. */
+ kHostUSBDeviceSubState_AwaitingDetach,
+ /** Awaiting a logical device re-attach following a device re-enumeration. */
+ kHostUSBDeviceSubState_AwaitingReAttach
+} HostUSBDeviceSubState;
+
+
+/**
+ * Object class used to hold Host USB Device properties.
+ */
+class ATL_NO_VTABLE HostUSBDevice :
+ public HostUSBDeviceWrap
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(HostUSBDevice)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(PUSBDEVICE aUsb, USBProxyBackend *aUSBProxyBackend);
+ void uninit();
+
+ // public methods only for internal purposes
+
+ /** @note Must be called from under the object read lock. */
+ const Guid& i_getId() const { return mId; }
+
+ /** @note Must be called from under the object read lock. */
+ HostUSBDeviceState i_getUnistate() const { return mUniState; }
+
+ /** @note Must be called from under the object read lock. */
+ const char *i_getStateName() { return i_stateName (mUniState, mPendingUniState, mUniSubState); }
+
+ /** @note Must be called from under the object read lock. */
+ bool i_isCapturableOrHeld()
+ {
+ return mUniState == kHostUSBDeviceState_Unused
+ || mUniState == kHostUSBDeviceState_Capturable
+ || mUniState == kHostUSBDeviceState_HeldByProxy;
+ }
+
+ /** @note Must be called from under the object read lock. */
+ ComObjPtr<SessionMachine> &i_getMachine() { return mMachine; }
+
+ /** @note Must be called from under the object read lock. */
+ PCUSBDEVICE i_getUsbData() const { return mUsb; }
+
+ USBProxyBackend *i_getUsbProxyBackend() const { return mUSBProxyBackend; }
+
+ com::Utf8Str i_getName();
+
+ HRESULT i_requestCaptureForVM(SessionMachine *aMachine, bool aSetError,
+ const com::Utf8Str &aCaptureFilename, ULONG aMaskedIfs = 0);
+ HRESULT i_onDetachFromVM(SessionMachine *aMachine, bool aDone, bool *aRunFilters, bool aAbnormal = false);
+ HRESULT i_requestReleaseToHost();
+ HRESULT i_requestHold();
+ bool i_wasActuallyDetached();
+ void i_onPhysicalDetached();
+
+ bool i_isMatch(const USBDeviceFilter::BackupableUSBDeviceFilterData &aData);
+ int i_compare(PCUSBDEVICE aDev2);
+ static int i_compare(PCUSBDEVICE aDev1, PCUSBDEVICE aDev2, bool aIsAwaitingReAttach = false);
+
+ bool i_updateState(PCUSBDEVICE aDev, bool *aRunFilters, SessionMachine **aIgnoreMachine);
+ bool i_updateStateFake(PCUSBDEVICE aDev, bool *aRunFilters, SessionMachine **aIgnoreMachine);
+
+ static const char *i_stateName(HostUSBDeviceState aState,
+ HostUSBDeviceState aPendingState = kHostUSBDeviceState_Invalid,
+ HostUSBDeviceSubState aSubState = kHostUSBDeviceSubState_Default);
+
+ void *i_getBackendUserData() { return m_pvBackendUser; }
+ void i_setBackendUserData(void *pvBackendUser) { m_pvBackendUser = pvBackendUser; }
+
+protected:
+
+ HRESULT i_attachToVM(SessionMachine *aMachine, const com::Utf8Str &aCaptureFilename, ULONG aMaskedIfs = 0);
+ void i_detachFromVM(HostUSBDeviceState aFinalState);
+ void i_onPhysicalDetachedInternal();
+ bool i_hasAsyncOperationTimedOut() const;
+
+ bool i_setState (HostUSBDeviceState aNewState, HostUSBDeviceState aNewPendingState = kHostUSBDeviceState_Invalid,
+ HostUSBDeviceSubState aNewSubState = kHostUSBDeviceSubState_Default);
+ bool i_startTransition (HostUSBDeviceState aNewState, HostUSBDeviceState aFinalState,
+ HostUSBDeviceSubState aNewSubState = kHostUSBDeviceSubState_Default);
+ bool i_advanceTransition(bool aSkipReAttach = false);
+ bool i_failTransition(HostUSBDeviceState a_enmStateHint);
+ USBDeviceState_T i_canonicalState() const;
+
+private:
+
+ // wrapped IUSBDevice properties
+ HRESULT getId(com::Guid &aId);
+ HRESULT getVendorId(USHORT *aVendorId);
+ HRESULT getProductId(USHORT *aProductId);
+ HRESULT getRevision(USHORT *aRevision);
+ HRESULT getManufacturer(com::Utf8Str &aManufacturer);
+ HRESULT getProduct(com::Utf8Str &aProduct);
+ HRESULT getSerialNumber(com::Utf8Str &aSerialNumber);
+ HRESULT getAddress(com::Utf8Str &aAddress);
+ HRESULT getPort(USHORT *aPort);
+ HRESULT getPortPath(com::Utf8Str &aPortPath);
+ HRESULT getVersion(USHORT *aVersion);
+ HRESULT getPortVersion(USHORT *aPortVersion);
+ HRESULT getSpeed(USBConnectionSpeed_T *aSpeed);
+ HRESULT getRemote(BOOL *aRemote);
+ HRESULT getState(USBDeviceState_T *aState);
+ HRESULT getBackend(com::Utf8Str &aBackend);
+ HRESULT getDeviceInfo(std::vector<com::Utf8Str> &aInfo);
+
+
+ const Guid mId;
+
+ /** @name The state machine variables
+ * Only setState(), init() and uninit() will modify these members!
+ * @{ */
+ /** The RTTimeNanoTS() corresponding to the last state change.
+ *
+ * Old state machine: RTTimeNanoTS() of when mIsStatePending was set or mDetaching changed
+ * from kNotDetaching. For operations that cannot be canceled it's 0. */
+ uint64_t mLastStateChangeTS;
+ /** Current state. */
+ HostUSBDeviceState mUniState;
+ /** Sub-state for tracking re-enumeration. */
+ HostUSBDeviceSubState mUniSubState;
+ /** The final state of an pending transition.
+ * This is mainly a measure to reduce the number of HostUSBDeviceState values. */
+ HostUSBDeviceState mPendingUniState;
+ /** Previous state.
+ * This is used for bailing out when a transition like capture fails. */
+ HostUSBDeviceState mPrevUniState;
+ /** Indicator set by onDetachedPhys and check when advancing a transitional state. */
+ bool mIsPhysicallyDetached;
+ /** @} */
+
+ /** The machine the usb device is (being) attached to. */
+ ComObjPtr<SessionMachine> mMachine;
+ /** Pointer to the USB Proxy Backend instance. */
+ USBProxyBackend *mUSBProxyBackend;
+ /** Pointer to the USB Device structure owned by this device.
+ * Only used for host devices. */
+ PUSBDEVICE mUsb;
+ /** The interface mask to be used in the pending capture.
+ * This is a filter property. */
+ ULONG mMaskedIfs;
+ /** The name of this device. */
+ Utf8Str mNameObj;
+ /** The name of this device (for logging purposes).
+ * This points to the string in mNameObj. */
+ const char *mName;
+ /** The filename to capture the USB traffic to. */
+ com::Utf8Str mCaptureFilename;
+ /** Optional opaque user data assigned by the USB proxy backend owning the device. */
+ void *m_pvBackendUser;
+};
+
+#endif /* !MAIN_INCLUDED_HostUSBDeviceImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/HostVideoInputDeviceImpl.h b/src/VBox/Main/include/HostVideoInputDeviceImpl.h
new file mode 100644
index 00000000..7cac928b
--- /dev/null
+++ b/src/VBox/Main/include/HostVideoInputDeviceImpl.h
@@ -0,0 +1,82 @@
+/* $Id: HostVideoInputDeviceImpl.h $ */
+/** @file
+ * A host video capture device description.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_HostVideoInputDeviceImpl_h
+#define MAIN_INCLUDED_HostVideoInputDeviceImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "HostVideoInputDeviceWrap.h"
+
+#include <list>
+
+class HostVideoInputDevice;
+
+typedef std::list<ComObjPtr<HostVideoInputDevice> > HostVideoInputDeviceList;
+
+class ATL_NO_VTABLE HostVideoInputDevice :
+ public HostVideoInputDeviceWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(HostVideoInputDevice)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ /* Public initializer/uninitializer for internal purposes only. */
+ HRESULT init(const com::Utf8Str &name, const com::Utf8Str &path, const com::Utf8Str &alias);
+ void uninit();
+
+ static HRESULT queryHostDevices(VirtualBox *pVirtualBox, HostVideoInputDeviceList *pList);
+
+private:
+
+ // wrapped IHostVideoInputDevice properties
+ virtual HRESULT getName(com::Utf8Str &aName) { aName = m.name; return S_OK; }
+ virtual HRESULT getPath(com::Utf8Str &aPath) { aPath = m.path; return S_OK; }
+ virtual HRESULT getAlias(com::Utf8Str &aAlias) { aAlias = m.alias; return S_OK; }
+
+ /* Data. */
+ struct Data
+ {
+ Data()
+ {
+ }
+
+ com::Utf8Str name;
+ com::Utf8Str path;
+ com::Utf8Str alias;
+ };
+
+ Data m;
+};
+
+#endif /* !MAIN_INCLUDED_HostVideoInputDeviceImpl_h */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/KeyboardImpl.h b/src/VBox/Main/include/KeyboardImpl.h
new file mode 100644
index 00000000..4eda3f6e
--- /dev/null
+++ b/src/VBox/Main/include/KeyboardImpl.h
@@ -0,0 +1,110 @@
+/* $Id: KeyboardImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_KeyboardImpl_h
+#define MAIN_INCLUDED_KeyboardImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "KeyboardWrap.h"
+#include "EventImpl.h"
+
+#include <VBox/vmm/pdmdrv.h>
+
+/** Limit of simultaneously attached devices (just USB and/or PS/2). */
+enum { KEYBOARD_MAX_DEVICES = 2 };
+
+/** Simple keyboard event class. */
+class KeyboardEvent
+{
+public:
+ KeyboardEvent() : scan(-1) {}
+ KeyboardEvent(int _scan) : scan(_scan) {}
+ bool i_isValid()
+ {
+ return (scan & ~0x80) && !(scan & ~0xFF);
+ }
+ int scan;
+};
+class Console;
+
+class ATL_NO_VTABLE Keyboard :
+ public KeyboardWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(Keyboard)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Console *aParent);
+ void uninit();
+
+ static const PDMDRVREG DrvReg;
+
+ Console *i_getParent() const
+ {
+ return mParent;
+ }
+
+private:
+
+ // Wrapped Keyboard properties
+ HRESULT getEventSource(ComPtr<IEventSource> &aEventSource);
+ HRESULT getKeyboardLEDs(std::vector<KeyboardLED_T> &aKeyboardLEDs);
+
+ // Wrapped Keyboard members
+ HRESULT putScancode(LONG aScancode);
+ HRESULT putScancodes(const std::vector<LONG> &aScancodes,
+ ULONG *aCodesStored);
+ HRESULT putCAD();
+ HRESULT releaseKeys();
+ HRESULT putUsageCode(LONG aUsageCode, LONG aUsagePage, BOOL fKeyRelease);
+
+ static DECLCALLBACK(void) i_keyboardLedStatusChange(PPDMIKEYBOARDCONNECTOR pInterface, PDMKEYBLEDS enmLeds);
+ static DECLCALLBACK(void) i_keyboardSetActive(PPDMIKEYBOARDCONNECTOR pInterface, bool fActive);
+ static DECLCALLBACK(void *) i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID);
+ static DECLCALLBACK(int) i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags);
+ static DECLCALLBACK(void) i_drvDestruct(PPDMDRVINS pDrvIns);
+
+ void onKeyboardLedsChange(PDMKEYBLEDS enmLeds);
+
+ Console * const mParent;
+ /** Pointer to the associated keyboard driver(s). */
+ struct DRVMAINKEYBOARD *mpDrv[KEYBOARD_MAX_DEVICES];
+
+ /* The current guest keyboard LED status. */
+ PDMKEYBLEDS menmLeds;
+
+ const ComObjPtr<EventSource> mEventSource;
+};
+
+#endif /* !MAIN_INCLUDED_KeyboardImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/LoggingNew.h b/src/VBox/Main/include/LoggingNew.h
new file mode 100644
index 00000000..b4917cb9
--- /dev/null
+++ b/src/VBox/Main/include/LoggingNew.h
@@ -0,0 +1,60 @@
+/* $Id: LoggingNew.h $ */
+/** @file
+ * VirtualBox COM - logging macros and function definitions, for new code.
+ */
+
+/*
+ * Copyright (C) 2017-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_LoggingNew_h
+#define MAIN_INCLUDED_LoggingNew_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#ifdef MAIN_INCLUDED_Logging_h
+# error "You must include LoggingNew.h as the first include!"
+#endif
+#define MAIN_INCLUDED_Logging_h /* Prevent Logging.h from being included. */
+
+#ifndef LOG_GROUP
+# error "You must define LOG_GROUP immediately before including LoggingNew.h!"
+#endif
+
+#ifdef LOG_GROUP_MAIN_OVERRIDE
+# error "Please, don't define LOG_GROUP_MAIN_OVERRIDE anymore!"
+#endif
+
+#include <VBox/log.h>
+
+
+#ifndef VBOXSVC_LOG_DEFAULT
+# define VBOXSVC_LOG_DEFAULT "all"
+#endif
+
+#ifndef VBOXSDS_LOG_DEFAULT
+# define VBOXSDS_LOG_DEFAULT "all"
+#endif
+
+#endif /* !MAIN_INCLUDED_LoggingNew_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
+
diff --git a/src/VBox/Main/include/MachineDebuggerImpl.h b/src/VBox/Main/include/MachineDebuggerImpl.h
new file mode 100644
index 00000000..1bf40f0b
--- /dev/null
+++ b/src/VBox/Main/include/MachineDebuggerImpl.h
@@ -0,0 +1,176 @@
+/* $Id: MachineDebuggerImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_MachineDebuggerImpl_h
+#define MAIN_INCLUDED_MachineDebuggerImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "MachineDebuggerWrap.h"
+#include <iprt/log.h>
+#include <VBox/vmm/em.h>
+
+class Console;
+class Progress;
+
+class ATL_NO_VTABLE MachineDebugger :
+ public MachineDebuggerWrap
+{
+
+public:
+
+ DECLARE_COMMON_CLASS_METHODS (MachineDebugger)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init (Console *aParent);
+ void uninit();
+
+ // "public-private methods"
+ void i_flushQueuedSettings();
+
+private:
+
+ // wrapped IMachineDeugger properties
+ HRESULT getSingleStep(BOOL *aSingleStep);
+ HRESULT setSingleStep(BOOL aSingleStep);
+ HRESULT getExecuteAllInIEM(BOOL *aExecuteAllInIEM);
+ HRESULT setExecuteAllInIEM(BOOL aExecuteAllInIEM);
+ HRESULT getLogEnabled(BOOL *aLogEnabled);
+ HRESULT setLogEnabled(BOOL aLogEnabled);
+ HRESULT getLogDbgFlags(com::Utf8Str &aLogDbgFlags);
+ HRESULT getLogDbgGroups(com::Utf8Str &aLogDbgGroups);
+ HRESULT getLogDbgDestinations(com::Utf8Str &aLogDbgDestinations);
+ HRESULT getLogRelFlags(com::Utf8Str &aLogRelFlags);
+ HRESULT getLogRelGroups(com::Utf8Str &aLogRelGroups);
+ HRESULT getLogRelDestinations(com::Utf8Str &aLogRelDestinations);
+ HRESULT getExecutionEngine(VMExecutionEngine_T *apenmEngine);
+ HRESULT getHWVirtExNestedPagingEnabled(BOOL *aHWVirtExNestedPagingEnabled);
+ HRESULT getHWVirtExVPIDEnabled(BOOL *aHWVirtExVPIDEnabled);
+ HRESULT getHWVirtExUXEnabled(BOOL *aHWVirtExUXEnabled);
+ HRESULT getOSName(com::Utf8Str &aOSName);
+ HRESULT getOSVersion(com::Utf8Str &aOSVersion);
+ HRESULT getPAEEnabled(BOOL *aPAEEnabled);
+ HRESULT getVirtualTimeRate(ULONG *aVirtualTimeRate);
+ HRESULT setVirtualTimeRate(ULONG aVirtualTimeRate);
+ HRESULT getUptime(LONG64 *aUptime);
+
+ // wrapped IMachineDeugger methods
+ HRESULT dumpGuestCore(const com::Utf8Str &aFilename,
+ const com::Utf8Str &aCompression);
+ HRESULT dumpHostProcessCore(const com::Utf8Str &aFilename,
+ const com::Utf8Str &aCompression);
+ HRESULT info(const com::Utf8Str &aName,
+ const com::Utf8Str &aArgs,
+ com::Utf8Str &aInfo);
+ HRESULT injectNMI();
+ HRESULT modifyLogGroups(const com::Utf8Str &aSettings);
+ HRESULT modifyLogFlags(const com::Utf8Str &aSettings);
+ HRESULT modifyLogDestinations(const com::Utf8Str &aSettings);
+ HRESULT readPhysicalMemory(LONG64 aAddress,
+ ULONG aSize,
+ std::vector<BYTE> &aBytes);
+ HRESULT writePhysicalMemory(LONG64 aAddress,
+ ULONG aSize,
+ const std::vector<BYTE> &aBytes);
+ HRESULT readVirtualMemory(ULONG aCpuId,
+ LONG64 aAddress,
+ ULONG aSize,
+ std::vector<BYTE> &aBytes);
+ HRESULT writeVirtualMemory(ULONG aCpuId,
+ LONG64 aAddress,
+ ULONG aSize,
+ const std::vector<BYTE> &aBytes);
+ HRESULT loadPlugIn(const com::Utf8Str &aName,
+ com::Utf8Str &aPlugInName);
+ HRESULT unloadPlugIn(const com::Utf8Str &aName);
+ HRESULT detectOS(com::Utf8Str &aOs);
+ HRESULT queryOSKernelLog(ULONG aMaxMessages,
+ com::Utf8Str &aDmesg);
+ HRESULT getRegister(ULONG aCpuId,
+ const com::Utf8Str &aName,
+ com::Utf8Str &aValue);
+ HRESULT getRegisters(ULONG aCpuId,
+ std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aValues);
+ HRESULT setRegister(ULONG aCpuId,
+ const com::Utf8Str &aName,
+ const com::Utf8Str &aValue);
+ HRESULT setRegisters(ULONG aCpuId,
+ const std::vector<com::Utf8Str> &aNames,
+ const std::vector<com::Utf8Str> &aValues);
+ HRESULT dumpGuestStack(ULONG aCpuId,
+ com::Utf8Str &aStack);
+ HRESULT resetStats(const com::Utf8Str &aPattern);
+ HRESULT dumpStats(const com::Utf8Str &aPattern);
+ HRESULT getStats(const com::Utf8Str &aPattern,
+ BOOL aWithDescriptions,
+ com::Utf8Str &aStats);
+ HRESULT getCPULoad(ULONG aCpuId, ULONG *aPctExecuting, ULONG *aPctHalted, ULONG *aPctOther, LONG64 *aMsInterval) RT_OVERRIDE;
+ HRESULT takeGuestSample(const com::Utf8Str &aFilename, ULONG aUsInterval, LONG64 aUsSampleTime, ComPtr<IProgress> &pProgress);
+ HRESULT getUVMAndVMMFunctionTable(LONG64 aMagicVersion, LONG64 *aVMMFunctionTable, LONG64 *aUVM);
+
+ // private methods
+ bool i_queueSettings() const;
+ HRESULT i_getEmExecPolicyProperty(EMEXECPOLICY enmPolicy, BOOL *pfEnforced);
+ HRESULT i_setEmExecPolicyProperty(EMEXECPOLICY enmPolicy, BOOL fEnforce);
+
+ /** RTLogGetFlags, RTLogGetGroupSettings and RTLogGetDestinations function. */
+ typedef DECLCALLBACKTYPE(int, FNLOGGETSTR,(PRTLOGGER, char *, size_t));
+ /** Function pointer. */
+ typedef FNLOGGETSTR *PFNLOGGETSTR;
+ HRESULT i_logStringProps(PRTLOGGER pLogger, PFNLOGGETSTR pfnLogGetStr, const char *pszLogGetStr, Utf8Str *pstrSettings);
+
+ static DECLCALLBACK(int) i_dbgfProgressCallback(void *pvUser, unsigned uPercentage);
+
+ Console * const mParent;
+ /** @name Flags whether settings have been queued because they could not be sent
+ * to the VM (not up yet, etc.)
+ * @{ */
+ uint8_t maiQueuedEmExecPolicyParams[EMEXECPOLICY_END];
+ int mSingleStepQueued;
+ int mLogEnabledQueued;
+ uint32_t mVirtualTimeRateQueued;
+ bool mFlushMode;
+ /** @} */
+
+ /** @name Sample report related things.
+ * @{ */
+ /** Sample report handle. */
+ DBGFSAMPLEREPORT m_hSampleReport;
+ /** Progress object for the currently taken guest sample. */
+ ComObjPtr<Progress> m_Progress;
+ /** Filename to dump the report to. */
+ com::Utf8Str m_strFilename;
+ /** @} */
+};
+
+#endif /* !MAIN_INCLUDED_MachineDebuggerImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/MachineImpl.h b/src/VBox/Main/include/MachineImpl.h
new file mode 100644
index 00000000..da911acf
--- /dev/null
+++ b/src/VBox/Main/include/MachineImpl.h
@@ -0,0 +1,1691 @@
+/* $Id: MachineImpl.h $ */
+/** @file
+ * Implementation of IMachine in VBoxSVC - Header.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_MachineImpl_h
+#define MAIN_INCLUDED_MachineImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "AuthLibrary.h"
+#include "VirtualBoxBase.h"
+#include "ProgressImpl.h"
+#include "VRDEServerImpl.h"
+#include "MediumAttachmentImpl.h"
+#include "PCIDeviceAttachmentImpl.h"
+#include "MediumLock.h"
+#include "NetworkAdapterImpl.h"
+#include "AudioSettingsImpl.h"
+#include "SerialPortImpl.h"
+#include "ParallelPortImpl.h"
+#include "BIOSSettingsImpl.h"
+#include "RecordingSettingsImpl.h"
+#include "GraphicsAdapterImpl.h"
+#include "StorageControllerImpl.h" // required for MachineImpl.h to compile on Windows
+#include "USBControllerImpl.h" // required for MachineImpl.h to compile on Windows
+#include "BandwidthControlImpl.h"
+#include "BandwidthGroupImpl.h"
+#include "TrustedPlatformModuleImpl.h"
+#include "NvramStoreImpl.h"
+#include "GuestDebugControlImpl.h"
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+# include "Performance.h"
+# include "PerformanceImpl.h"
+#endif
+#include "ThreadTask.h"
+
+// generated header
+#include "SchemaDefs.h"
+
+#include "VBox/com/ErrorInfo.h"
+
+#include <iprt/time.h>
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+# include <VBox/VBoxCryptoIf.h>
+# include <iprt/vfs.h>
+#endif
+
+#include <list>
+#include <vector>
+
+#include "MachineWrap.h"
+
+/** @todo r=klaus after moving the various Machine settings structs to
+ * MachineImpl.cpp it should be possible to eliminate this include. */
+#include <VBox/settings.h>
+
+// defines
+////////////////////////////////////////////////////////////////////////////////
+
+// helper declarations
+////////////////////////////////////////////////////////////////////////////////
+
+class Progress;
+class ProgressProxy;
+class Keyboard;
+class Mouse;
+class Display;
+class MachineDebugger;
+class USBController;
+class USBDeviceFilters;
+class Snapshot;
+class SharedFolder;
+class HostUSBDevice;
+class StorageController;
+class SessionMachine;
+#ifdef VBOX_WITH_UNATTENDED
+class Unattended;
+#endif
+
+// Machine class
+////////////////////////////////////////////////////////////////////////////////
+//
+class ATL_NO_VTABLE Machine :
+ public MachineWrap
+{
+
+public:
+
+ enum StateDependency
+ {
+ AnyStateDep = 0,
+ MutableStateDep,
+ MutableOrSavedStateDep,
+ MutableOrRunningStateDep,
+ MutableOrSavedOrRunningStateDep,
+ };
+
+ /**
+ * Internal machine data.
+ *
+ * Only one instance of this data exists per every machine -- it is shared
+ * by the Machine, SessionMachine and all SnapshotMachine instances
+ * associated with the given machine using the util::Shareable template
+ * through the mData variable.
+ *
+ * @note |const| members are persistent during lifetime so can be
+ * accessed without locking.
+ *
+ * @note There is no need to lock anything inside init() or uninit()
+ * methods, because they are always serialized (see AutoCaller).
+ */
+ struct Data
+ {
+ /**
+ * Data structure to hold information about sessions opened for the
+ * given machine.
+ */
+ struct Session
+ {
+ /** Type of lock which created this session */
+ LockType_T mLockType;
+
+ /** Control of the direct session opened by lockMachine() */
+ ComPtr<IInternalSessionControl> mDirectControl;
+
+ typedef std::list<ComPtr<IInternalSessionControl> > RemoteControlList;
+
+ /** list of controls of all opened remote sessions */
+ RemoteControlList mRemoteControls;
+
+ /** launchVMProcess() and OnSessionEnd() progress indicator */
+ ComObjPtr<ProgressProxy> mProgress;
+
+ /**
+ * PID of the session object that must be passed to openSession()
+ * to finalize the launchVMProcess() request (i.e., PID of the
+ * process created by launchVMProcess())
+ */
+ RTPROCESS mPID;
+
+ /** Current session state */
+ SessionState_T mState;
+
+ /** Session name string (of the primary session) */
+ Utf8Str mName;
+
+ /** Session machine object */
+ ComObjPtr<SessionMachine> mMachine;
+
+ /** Medium object lock collection. */
+ MediumLockListMap mLockedMedia;
+ };
+
+ Data();
+ ~Data();
+
+ const Guid mUuid;
+ BOOL mRegistered;
+
+ Utf8Str m_strConfigFile;
+ Utf8Str m_strConfigFileFull;
+
+ // machine settings XML file
+ settings::MachineConfigFile *pMachineConfigFile;
+ uint32_t flModifications;
+ bool m_fAllowStateModification;
+
+ BOOL mAccessible;
+ com::ErrorInfo mAccessError;
+
+ MachineState_T mMachineState;
+ RTTIMESPEC mLastStateChange;
+
+ /* Note: These are guarded by VirtualBoxBase::stateLockHandle() */
+ uint32_t mMachineStateDeps;
+ RTSEMEVENTMULTI mMachineStateDepsSem;
+ uint32_t mMachineStateChangePending;
+
+ BOOL mCurrentStateModified;
+ /** Guest properties have been modified and need saving since the
+ * machine was started, or there are transient properties which need
+ * deleting and the machine is being shut down. */
+ BOOL mGuestPropertiesModified;
+
+ Session mSession;
+
+ ComObjPtr<Snapshot> mFirstSnapshot;
+ ComObjPtr<Snapshot> mCurrentSnapshot;
+
+ // list of files to delete in Delete(); this list is filled by Unregister()
+ std::list<Utf8Str> llFilesToDelete;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ /* Store for secret keys. */
+ SecretKeyStore *mpKeyStore;
+ BOOL fEncrypted;
+ /* KeyId of the password encrypting the DEK */
+ com::Utf8Str mstrKeyId;
+ /* Store containing the DEK used for encrypting the VM */
+ com::Utf8Str mstrKeyStore;
+ /* KeyId of the password encrypting the DEK for log files */
+ com::Utf8Str mstrLogKeyId;
+ /* Store containing the DEK used for encrypting the VM's log files */
+ com::Utf8Str mstrLogKeyStore;
+#endif
+ };
+
+ /**
+ * Saved state data.
+ *
+ * It's actually only the state file path string and its encryption
+ * settings, but it needs to be separate from Data, because Machine
+ * and SessionMachine instances share it, while SnapshotMachine does
+ * not.
+ *
+ * The data variable is |mSSData|.
+ */
+ struct SSData
+ {
+ Utf8Str strStateFilePath;
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ /* KeyId of the password encrypting the DEK for saved state */
+ com::Utf8Str strStateKeyId;
+ /* Store containing the DEK used for encrypting saved state */
+ com::Utf8Str strStateKeyStore;
+#endif
+ };
+
+ /**
+ * User changeable machine data.
+ *
+ * This data is common for all machine snapshots, i.e. it is shared
+ * by all SnapshotMachine instances associated with the given machine
+ * using the util::Backupable template through the |mUserData| variable.
+ *
+ * SessionMachine instances can alter this data and discard changes.
+ *
+ * @note There is no need to lock anything inside init() or uninit()
+ * methods, because they are always serialized (see AutoCaller).
+ */
+ struct UserData
+ {
+ settings::MachineUserData s;
+ };
+
+ /**
+ * Hardware data.
+ *
+ * This data is unique for a machine and for every machine snapshot.
+ * Stored using the util::Backupable template in the |mHWData| variable.
+ *
+ * SessionMachine instances can alter this data and discard changes.
+ *
+ * @todo r=klaus move all "pointer" objects out of this struct, as they
+ * need non-obvious handling when creating a new session or when taking
+ * a snapshot. Better do this right straight away, not relying on the
+ * template magic which doesn't work right in this case.
+ */
+ struct HWData
+ {
+ /**
+ * Data structure to hold information about a guest property.
+ */
+ struct GuestProperty {
+ /** Property value */
+ Utf8Str strValue;
+ /** Property timestamp */
+ LONG64 mTimestamp;
+ /** Property flags */
+ ULONG mFlags;
+ };
+
+ HWData();
+ ~HWData();
+
+ Bstr mHWVersion;
+ Guid mHardwareUUID; /**< If Null, use mData.mUuid. */
+ ULONG mMemorySize;
+ ULONG mMemoryBalloonSize;
+ BOOL mPageFusionEnabled;
+ settings::RecordingSettings mRecordSettings;
+ BOOL mHWVirtExEnabled;
+ BOOL mHWVirtExNestedPagingEnabled;
+ BOOL mHWVirtExLargePagesEnabled;
+ BOOL mHWVirtExVPIDEnabled;
+ BOOL mHWVirtExUXEnabled;
+ BOOL mHWVirtExForceEnabled;
+ BOOL mHWVirtExUseNativeApi;
+ BOOL mHWVirtExVirtVmsaveVmload;
+ BOOL mPAEEnabled;
+ settings::Hardware::LongModeType mLongMode;
+ BOOL mTripleFaultReset;
+ BOOL mAPIC;
+ BOOL mX2APIC;
+ BOOL mIBPBOnVMExit;
+ BOOL mIBPBOnVMEntry;
+ BOOL mSpecCtrl;
+ BOOL mSpecCtrlByHost;
+ BOOL mL1DFlushOnSched;
+ BOOL mL1DFlushOnVMEntry;
+ BOOL mMDSClearOnSched;
+ BOOL mMDSClearOnVMEntry;
+ BOOL mNestedHWVirt;
+ ULONG mCPUCount;
+ BOOL mCPUHotPlugEnabled;
+ ULONG mCpuExecutionCap;
+ uint32_t mCpuIdPortabilityLevel;
+ Utf8Str mCpuProfile;
+ BOOL mHPETEnabled;
+
+ BOOL mCPUAttached[SchemaDefs::MaxCPUCount];
+
+ std::list<settings::CpuIdLeaf> mCpuIdLeafList;
+
+ DeviceType_T mBootOrder[SchemaDefs::MaxBootPosition];
+
+ typedef std::list<ComObjPtr<SharedFolder> > SharedFolderList;
+ SharedFolderList mSharedFolders;
+
+ ClipboardMode_T mClipboardMode;
+ BOOL mClipboardFileTransfersEnabled;
+
+ DnDMode_T mDnDMode;
+
+ typedef std::map<Utf8Str, GuestProperty> GuestPropertyMap;
+ GuestPropertyMap mGuestProperties;
+
+ FirmwareType_T mFirmwareType;
+ KeyboardHIDType_T mKeyboardHIDType;
+ PointingHIDType_T mPointingHIDType;
+ ChipsetType_T mChipsetType;
+ IommuType_T mIommuType;
+ ParavirtProvider_T mParavirtProvider;
+ Utf8Str mParavirtDebug;
+ BOOL mEmulatedUSBCardReaderEnabled;
+
+ BOOL mIOCacheEnabled;
+ ULONG mIOCacheSize;
+
+ typedef std::list<ComObjPtr<PCIDeviceAttachment> > PCIDeviceAssignmentList;
+ PCIDeviceAssignmentList mPCIDeviceAssignments;
+
+ settings::Debugging mDebugging;
+ settings::Autostart mAutostart;
+
+ Utf8Str mDefaultFrontend;
+ };
+
+ typedef std::list<ComObjPtr<MediumAttachment> > MediumAttachmentList;
+
+ DECLARE_COMMON_CLASS_METHODS(Machine)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only:
+
+ // initializer for creating a new, empty machine
+ HRESULT init(VirtualBox *aParent,
+ const Utf8Str &strConfigFile,
+ const Utf8Str &strName,
+ const StringsList &llGroups,
+ const Utf8Str &strOsTypeId,
+ GuestOSType *aOsType,
+ const Guid &aId,
+ bool fForceOverwrite,
+ bool fDirectoryIncludesUUID,
+ const com::Utf8Str &aCipher,
+ const com::Utf8Str &aPasswordId,
+ const com::Utf8Str &aPassword);
+
+ // initializer for loading existing machine XML (either registered or not)
+ HRESULT initFromSettings(VirtualBox *aParent,
+ const Utf8Str &strConfigFile,
+ const Guid *aId,
+ const com::Utf8Str &strPassword);
+
+ // initializer for machine config in memory (OVF import)
+ HRESULT init(VirtualBox *aParent,
+ const Utf8Str &strName,
+ const Utf8Str &strSettingsFilename,
+ const settings::MachineConfigFile &config);
+
+ void uninit();
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ // Needed from VirtualBox, for the delayed metrics cleanup.
+ void i_unregisterMetrics(PerformanceCollector *aCollector, Machine *aMachine);
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+
+protected:
+ HRESULT initImpl(VirtualBox *aParent,
+ const Utf8Str &strConfigFile);
+ HRESULT initDataAndChildObjects();
+ HRESULT i_registeredInit();
+ HRESULT i_tryCreateMachineConfigFile(bool fForceOverwrite);
+ void uninitDataAndChildObjects();
+
+public:
+
+
+ // public methods only for internal purposes
+
+ virtual bool i_isSnapshotMachine() const
+ {
+ return false;
+ }
+
+ virtual bool i_isSessionMachine() const
+ {
+ return false;
+ }
+
+ /**
+ * Override of the default locking class to be used for validating lock
+ * order with the standard member lock handle.
+ */
+ virtual VBoxLockingClass getLockingClass() const
+ {
+ return LOCKCLASS_MACHINEOBJECT;
+ }
+
+ /// @todo (dmik) add lock and make non-inlined after revising classes
+ // that use it. Note: they should enter Machine lock to keep the returned
+ // information valid!
+ bool i_isRegistered() { return !!mData->mRegistered; }
+
+ // unsafe inline public methods for internal purposes only (ensure there is
+ // a caller and a read lock before calling them!)
+
+ /**
+ * Returns the VirtualBox object this machine belongs to.
+ *
+ * @note This method doesn't check this object's readiness. Intended to be
+ * used by ready Machine children (whose readiness is bound to the parent's
+ * one) or after doing addCaller() manually.
+ */
+ VirtualBox* i_getVirtualBox() const { return mParent; }
+
+ /**
+ * Checks if this machine is accessible, without attempting to load the
+ * config file.
+ *
+ * @note This method doesn't check this object's readiness. Intended to be
+ * used by ready Machine children (whose readiness is bound to the parent's
+ * one) or after doing addCaller() manually.
+ */
+ bool i_isAccessible() const { return !!mData->mAccessible; }
+
+ /**
+ * Returns this machine ID.
+ *
+ * @note This method doesn't check this object's readiness. Intended to be
+ * used by ready Machine children (whose readiness is bound to the parent's
+ * one) or after adding a caller manually.
+ */
+ const Guid& i_getId() const { return mData->mUuid; }
+
+ /**
+ * Returns the snapshot ID this machine represents or an empty UUID if this
+ * instance is not SnapshotMachine.
+ *
+ * @note This method doesn't check this object's readiness. Intended to be
+ * used by ready Machine children (whose readiness is bound to the parent's
+ * one) or after adding a caller manually.
+ */
+ inline const Guid& i_getSnapshotId() const;
+
+ /**
+ * Returns this machine's full settings file path.
+ *
+ * @note This method doesn't lock this object or check its readiness.
+ * Intended to be used only after doing addCaller() manually and locking it
+ * for reading.
+ */
+ const Utf8Str& i_getSettingsFileFull() const { return mData->m_strConfigFileFull; }
+
+ /**
+ * Returns this machine name.
+ *
+ * @note This method doesn't lock this object or check its readiness.
+ * Intended to be used only after doing addCaller() manually and locking it
+ * for reading.
+ */
+ const Utf8Str& i_getName() const { return mUserData->s.strName; }
+
+ enum
+ {
+ IsModified_MachineData = 0x000001,
+ IsModified_Storage = 0x000002,
+ IsModified_NetworkAdapters = 0x000008,
+ IsModified_SerialPorts = 0x000010,
+ IsModified_ParallelPorts = 0x000020,
+ IsModified_VRDEServer = 0x000040,
+ IsModified_AudioSettings = 0x000080,
+ IsModified_USB = 0x000100,
+ IsModified_BIOS = 0x000200,
+ IsModified_SharedFolders = 0x000400,
+ IsModified_Snapshots = 0x000800,
+ IsModified_BandwidthControl = 0x001000,
+ IsModified_Recording = 0x002000,
+ IsModified_GraphicsAdapter = 0x004000,
+ IsModified_TrustedPlatformModule = 0x008000,
+ IsModified_NvramStore = 0x010000,
+ IsModified_GuestDebugControl = 0x020000,
+ };
+
+ /**
+ * Returns various information about this machine.
+ *
+ * @note This method doesn't lock this object or check its readiness.
+ * Intended to be used only after doing addCaller() manually and locking it
+ * for reading.
+ */
+ Utf8Str i_getOSTypeId() const { return mUserData->s.strOsType; }
+ ChipsetType_T i_getChipsetType() const { return mHWData->mChipsetType; }
+ FirmwareType_T i_getFirmwareType() const { return mHWData->mFirmwareType; }
+ ParavirtProvider_T i_getParavirtProvider() const { return mHWData->mParavirtProvider; }
+ Utf8Str i_getParavirtDebug() const { return mHWData->mParavirtDebug; }
+
+ void i_setModified(uint32_t fl, bool fAllowStateModification = true);
+ void i_setModifiedLock(uint32_t fl, bool fAllowStateModification = true);
+
+ MachineState_T i_getMachineState() const { return mData->mMachineState; }
+
+ bool i_isStateModificationAllowed() const { return mData->m_fAllowStateModification; }
+ void i_allowStateModification() { mData->m_fAllowStateModification = true; }
+ void i_disallowStateModification() { mData->m_fAllowStateModification = false; }
+
+ const StringsList &i_getGroups() const { return mUserData->s.llGroups; }
+
+ // callback handlers
+ virtual HRESULT i_onNetworkAdapterChange(INetworkAdapter * /* networkAdapter */, BOOL /* changeAdapter */) { return S_OK; }
+ virtual HRESULT i_onNATRedirectRuleChanged(ULONG /* slot */, BOOL /* fRemove */ , const Utf8Str & /* name */,
+ NATProtocol_T /* protocol */, const Utf8Str & /* host ip */, LONG /* host port */,
+ const Utf8Str & /* guest port */, LONG /* guest port */ ) { return S_OK; }
+ virtual HRESULT i_onAudioAdapterChange(IAudioAdapter * /* audioAdapter */) { return S_OK; }
+ virtual HRESULT i_onHostAudioDeviceChange(IHostAudioDevice *, BOOL /* new */, AudioDeviceState_T, IVirtualBoxErrorInfo *) { return S_OK; }
+ virtual HRESULT i_onSerialPortChange(ISerialPort * /* serialPort */) { return S_OK; }
+ virtual HRESULT i_onParallelPortChange(IParallelPort * /* parallelPort */) { return S_OK; }
+ virtual HRESULT i_onVRDEServerChange(BOOL /* aRestart */) { return S_OK; }
+ virtual HRESULT i_onUSBControllerChange() { return S_OK; }
+ virtual HRESULT i_onStorageControllerChange(const com::Guid & /* aMachineId */, const com::Utf8Str & /* aControllerName */) { return S_OK; }
+ virtual HRESULT i_onCPUChange(ULONG /* aCPU */, BOOL /* aRemove */) { return S_OK; }
+ virtual HRESULT i_onCPUExecutionCapChange(ULONG /* aExecutionCap */) { return S_OK; }
+ virtual HRESULT i_onMediumChange(IMediumAttachment * /* mediumAttachment */, BOOL /* force */) { return S_OK; }
+ virtual HRESULT i_onSharedFolderChange() { return S_OK; }
+ virtual HRESULT i_onVMProcessPriorityChange(VMProcPriority_T /* aPriority */) { return S_OK; }
+ virtual HRESULT i_onClipboardModeChange(ClipboardMode_T /* aClipboardMode */) { return S_OK; }
+ virtual HRESULT i_onClipboardFileTransferModeChange(BOOL /* aEnable */) { return S_OK; }
+ virtual HRESULT i_onDnDModeChange(DnDMode_T /* aDnDMode */) { return S_OK; }
+ virtual HRESULT i_onBandwidthGroupChange(IBandwidthGroup * /* aBandwidthGroup */) { return S_OK; }
+ virtual HRESULT i_onStorageDeviceChange(IMediumAttachment * /* mediumAttachment */, BOOL /* remove */,
+ BOOL /* silent */) { return S_OK; }
+ virtual HRESULT i_onRecordingChange(BOOL /* aEnable */) { return S_OK; }
+ virtual HRESULT i_onGuestDebugControlChange(IGuestDebugControl * /* guestDebugControl */) { return S_OK; }
+
+
+ HRESULT i_saveRegistryEntry(settings::MachineRegistryEntry &data);
+
+ int i_calculateFullPath(const Utf8Str &strPath, Utf8Str &aResult);
+ void i_copyPathRelativeToMachine(const Utf8Str &strSource, Utf8Str &strTarget);
+
+ void i_getLogFolder(Utf8Str &aLogFolder);
+ Utf8Str i_getLogFilename(ULONG idx);
+ Utf8Str i_getHardeningLogFilename(void);
+ Utf8Str i_getDefaultNVRAMFilename();
+ Utf8Str i_getSnapshotNVRAMFilename();
+ SettingsVersion_T i_getSettingsVersion(void);
+
+ void i_composeSavedStateFilename(Utf8Str &strStateFilePath);
+
+ bool i_isUSBControllerPresent();
+
+ HRESULT i_launchVMProcess(IInternalSessionControl *aControl,
+ const Utf8Str &strType,
+ const std::vector<com::Utf8Str> &aEnvironmentChanges,
+ ProgressProxy *aProgress);
+
+ HRESULT i_getDirectControl(ComPtr<IInternalSessionControl> *directControl)
+ {
+ HRESULT rc;
+ *directControl = mData->mSession.mDirectControl;
+
+ if (!*directControl)
+ rc = E_ACCESSDENIED;
+ else
+ rc = S_OK;
+
+ return rc;
+ }
+
+ bool i_isSessionOpen(ComObjPtr<SessionMachine> &aMachine,
+ ComPtr<IInternalSessionControl> *aControl = NULL,
+ bool aRequireVM = false,
+ bool aAllowClosing = false);
+ bool i_isSessionSpawning();
+
+ bool i_isSessionOpenOrClosing(ComObjPtr<SessionMachine> &aMachine,
+ ComPtr<IInternalSessionControl> *aControl = NULL)
+ { return i_isSessionOpen(aMachine, aControl, false /* aRequireVM */, true /* aAllowClosing */); }
+
+ bool i_isSessionOpenVM(ComObjPtr<SessionMachine> &aMachine,
+ ComPtr<IInternalSessionControl> *aControl = NULL)
+ { return i_isSessionOpen(aMachine, aControl, true /* aRequireVM */, false /* aAllowClosing */); }
+
+ bool i_checkForSpawnFailure();
+
+ HRESULT i_prepareRegister();
+
+ HRESULT i_getSharedFolder(const Utf8Str &aName,
+ ComObjPtr<SharedFolder> &aSharedFolder,
+ bool aSetError = false)
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return i_findSharedFolder(aName, aSharedFolder, aSetError);
+ }
+
+ HRESULT i_addStateDependency(StateDependency aDepType = AnyStateDep,
+ MachineState_T *aState = NULL,
+ BOOL *aRegistered = NULL);
+ void i_releaseStateDependency();
+
+ HRESULT i_getStorageControllerByName(const Utf8Str &aName,
+ ComObjPtr<StorageController> &aStorageController,
+ bool aSetError = false);
+
+ HRESULT i_getMediumAttachmentsOfController(const Utf8Str &aName,
+ MediumAttachmentList &aAttachments);
+
+ HRESULT i_getUSBControllerByName(const Utf8Str &aName,
+ ComObjPtr<USBController> &aUSBController,
+ bool aSetError = false);
+
+ HRESULT i_getBandwidthGroup(const Utf8Str &strBandwidthGroup,
+ ComObjPtr<BandwidthGroup> &pBandwidthGroup,
+ bool fSetError = false)
+ {
+ return mBandwidthControl->i_getBandwidthGroupByName(strBandwidthGroup,
+ pBandwidthGroup,
+ fSetError);
+ }
+
+ static HRESULT i_setErrorStatic(HRESULT aResultCode, const char *pcszMsg, ...);
+
+protected:
+
+ class ClientToken;
+
+ HRESULT i_checkStateDependency(StateDependency aDepType);
+
+ Machine *i_getMachine();
+
+ void i_ensureNoStateDependencies(AutoWriteLock &alock);
+
+ virtual HRESULT i_setMachineState(MachineState_T aMachineState);
+
+ HRESULT i_findSharedFolder(const Utf8Str &aName,
+ ComObjPtr<SharedFolder> &aSharedFolder,
+ bool aSetError = false);
+
+ HRESULT i_loadSettings(bool aRegistered);
+ HRESULT i_loadMachineDataFromSettings(const settings::MachineConfigFile &config,
+ const Guid *puuidRegistry);
+ HRESULT i_loadSnapshot(const settings::Snapshot &data,
+ const Guid &aCurSnapshotId);
+ HRESULT i_loadHardware(const Guid *puuidRegistry,
+ const Guid *puuidSnapshot,
+ const settings::Hardware &data,
+ const settings::Debugging *pDbg,
+ const settings::Autostart *pAutostart,
+ const settings::RecordingSettings &recording);
+ HRESULT i_loadDebugging(const settings::Debugging *pDbg);
+ HRESULT i_loadAutostart(const settings::Autostart *pAutostart);
+ HRESULT i_loadStorageControllers(const settings::Storage &data,
+ const Guid *puuidRegistry,
+ const Guid *puuidSnapshot);
+ HRESULT i_loadStorageDevices(StorageController *aStorageController,
+ const settings::StorageController &data,
+ const Guid *puuidRegistry,
+ const Guid *puuidSnapshot);
+
+ HRESULT i_findSnapshotById(const Guid &aId,
+ ComObjPtr<Snapshot> &aSnapshot,
+ bool aSetError = false);
+ HRESULT i_findSnapshotByName(const Utf8Str &strName,
+ ComObjPtr<Snapshot> &aSnapshot,
+ bool aSetError = false);
+
+ ULONG i_getUSBControllerCountByType(USBControllerType_T enmType);
+
+ enum
+ {
+ /* flags for #saveSettings() */
+ SaveS_ResetCurStateModified = 0x01,
+ SaveS_Force = 0x04,
+ SaveS_RemoveBackup = 0x08,
+ /* flags for #saveStateSettings() */
+ SaveSTS_CurStateModified = 0x20,
+ SaveSTS_StateFilePath = 0x40,
+ SaveSTS_StateTimeStamp = 0x80
+ };
+
+ HRESULT i_prepareSaveSettings(bool *pfNeedsGlobalSaveSettings,
+ bool *pfSettingsFileIsNew);
+ HRESULT i_saveSettings(bool *pfNeedsGlobalSaveSettings, AutoWriteLock &alock, int aFlags = 0);
+
+ void i_copyMachineDataToSettings(settings::MachineConfigFile &config);
+ HRESULT i_saveAllSnapshots(settings::MachineConfigFile &config);
+ HRESULT i_saveHardware(settings::Hardware &data, settings::Debugging *pDbg,
+ settings::Autostart *pAutostart, settings::RecordingSettings &recording);
+ HRESULT i_saveStorageControllers(settings::Storage &data);
+ HRESULT i_saveStorageDevices(ComObjPtr<StorageController> aStorageController,
+ settings::StorageController &data);
+ HRESULT i_saveStateSettings(int aFlags);
+
+ void i_addMediumToRegistry(ComObjPtr<Medium> &pMedium);
+
+ HRESULT i_deleteFile(const Utf8Str &strFile, bool fIgnoreFailures = false, const Utf8Str &strWhat = "", int *prc = NULL);
+
+ HRESULT i_createImplicitDiffs(IProgress *aProgress,
+ ULONG aWeight,
+ bool aOnline);
+ HRESULT i_deleteImplicitDiffs(bool aOnline);
+
+ MediumAttachment* i_findAttachment(const MediumAttachmentList &ll,
+ const Utf8Str &aControllerName,
+ LONG aControllerPort,
+ LONG aDevice);
+ MediumAttachment* i_findAttachment(const MediumAttachmentList &ll,
+ ComObjPtr<Medium> pMedium);
+ MediumAttachment* i_findAttachment(const MediumAttachmentList &ll,
+ Guid &id);
+
+ HRESULT i_detachDevice(MediumAttachment *pAttach,
+ AutoWriteLock &writeLock,
+ Snapshot *pSnapshot);
+
+ HRESULT i_detachAllMedia(AutoWriteLock &writeLock,
+ Snapshot *pSnapshot,
+ CleanupMode_T cleanupMode,
+ MediaList &llMedia);
+
+ void i_commitMedia(bool aOnline = false);
+ void i_rollbackMedia();
+
+ bool i_isInOwnDir(Utf8Str *aSettingsDir = NULL) const;
+
+ void i_rollback(bool aNotify);
+ void i_commit();
+ void i_copyFrom(Machine *aThat);
+ bool i_isControllerHotplugCapable(StorageControllerType_T enmCtrlType);
+
+ Utf8Str i_getExtraData(const Utf8Str &strKey);
+
+ com::Utf8Str i_controllerNameFromBusType(StorageBus_T aBusType);
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ HRESULT i_getGuestPropertyFromService(const com::Utf8Str &aName, com::Utf8Str &aValue,
+ LONG64 *aTimestamp, com::Utf8Str &aFlags) const;
+ HRESULT i_setGuestPropertyToService(const com::Utf8Str &aName, const com::Utf8Str &aValue,
+ const com::Utf8Str &aFlags, bool fDelete);
+ HRESULT i_getGuestPropertyFromVM(const com::Utf8Str &aName, com::Utf8Str &aValue,
+ LONG64 *aTimestamp, com::Utf8Str &aFlags) const;
+ HRESULT i_setGuestPropertyToVM(const com::Utf8Str &aName, const com::Utf8Str &aValue,
+ const com::Utf8Str &aFlags, bool fDelete);
+ HRESULT i_enumerateGuestPropertiesInService(const com::Utf8Str &aPatterns,
+ std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<com::Utf8Str> &aFlags);
+ HRESULT i_enumerateGuestPropertiesOnVM(const com::Utf8Str &aPatterns,
+ std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<com::Utf8Str> &aFlags);
+
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ void i_getDiskList(MediaList &list);
+ void i_registerMetrics(PerformanceCollector *aCollector, Machine *aMachine, RTPROCESS pid);
+
+ pm::CollectorGuest *mCollectorGuest;
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+
+ Machine * const mPeer;
+
+ VirtualBox * const mParent;
+
+ Shareable<Data> mData;
+ Shareable<SSData> mSSData;
+
+ Backupable<UserData> mUserData;
+ Backupable<HWData> mHWData;
+
+ /**
+ * Hard disk and other media data.
+ *
+ * The usage policy is the same as for mHWData, but a separate field
+ * is necessary because hard disk data requires different procedures when
+ * taking or deleting snapshots, etc.
+ *
+ * @todo r=klaus change this to a regular list and use the normal way to
+ * handle the settings when creating a session or taking a snapshot.
+ * Same thing applies to mStorageControllers and mUSBControllers.
+ */
+ Backupable<MediumAttachmentList> mMediumAttachments;
+
+ // the following fields need special backup/rollback/commit handling,
+ // so they cannot be a part of HWData
+
+ const ComObjPtr<VRDEServer> mVRDEServer;
+ const ComObjPtr<SerialPort> mSerialPorts[SchemaDefs::SerialPortCount];
+ const ComObjPtr<ParallelPort> mParallelPorts[SchemaDefs::ParallelPortCount];
+ const ComObjPtr<AudioSettings> mAudioSettings;
+ const ComObjPtr<USBDeviceFilters> mUSBDeviceFilters;
+ const ComObjPtr<BIOSSettings> mBIOSSettings;
+ const ComObjPtr<RecordingSettings> mRecordingSettings;
+ const ComObjPtr<GraphicsAdapter> mGraphicsAdapter;
+ const ComObjPtr<BandwidthControl> mBandwidthControl;
+ const ComObjPtr<GuestDebugControl> mGuestDebugControl;
+
+ const ComObjPtr<TrustedPlatformModule> mTrustedPlatformModule;
+ const ComObjPtr<NvramStore> mNvramStore;
+
+ typedef std::vector<ComObjPtr<NetworkAdapter> > NetworkAdapterVector;
+ NetworkAdapterVector mNetworkAdapters;
+
+ typedef std::list<ComObjPtr<StorageController> > StorageControllerList;
+ Backupable<StorageControllerList> mStorageControllers;
+
+ typedef std::list<ComObjPtr<USBController> > USBControllerList;
+ Backupable<USBControllerList> mUSBControllers;
+
+ uint64_t uRegistryNeedsSaving;
+
+ /**
+ * Abstract base class for all Machine or SessionMachine related
+ * asynchronous tasks. This is necessary since RTThreadCreate cannot call
+ * a (non-static) method as its thread function, so instead we have it call
+ * the static Machine::taskHandler, which then calls the handler() method
+ * in here (implemented by the subclasses).
+ */
+ class Task : public ThreadTask
+ {
+ public:
+ Task(Machine *m, Progress *p, const Utf8Str &t)
+ : ThreadTask(t),
+ m_pMachine(m),
+ m_machineCaller(m),
+ m_pProgress(p),
+ m_machineStateBackup(m->mData->mMachineState) // save the current machine state
+ {}
+ virtual ~Task(){}
+
+ void modifyBackedUpState(MachineState_T s)
+ {
+ *const_cast<MachineState_T *>(&m_machineStateBackup) = s;
+ }
+
+ ComObjPtr<Machine> m_pMachine;
+ AutoCaller m_machineCaller;
+ ComObjPtr<Progress> m_pProgress;
+ const MachineState_T m_machineStateBackup;
+ };
+
+ class DeleteConfigTask;
+ void i_deleteConfigHandler(DeleteConfigTask &task);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ class ChangeEncryptionTask;
+ void i_changeEncryptionHandler(ChangeEncryptionTask &task);
+ HRESULT i_changeEncryptionForComponent(ChangeEncryptionTask &task, const com::Utf8Str strDirectory,
+ const com::Utf8Str strFilePattern, com::Utf8Str &strKeyStore,
+ com::Utf8Str &strKeyId, int iCipherMode);
+ int i_findFiles(std::list<com::Utf8Str> &lstFiles, const com::Utf8Str &strDir,
+ const com::Utf8Str &strPattern);
+ int i_createIoStreamForFile(const char *pszFilename, PCVBOXCRYPTOIF pCryptoIf,
+ const char *pszKeyStore, const char *pszPassword,
+ uint64_t fOpen, PRTVFSIOSTREAM phVfsIos);
+#endif
+
+ friend class Appliance;
+ friend class RecordingSettings;
+ friend class RecordingScreenSettings;
+ friend class SessionMachine;
+ friend class SnapshotMachine;
+ friend class VirtualBox;
+
+ friend class MachineCloneVM;
+ friend class MachineMoveVM;
+private:
+ // wrapped IMachine properties
+ HRESULT getParent(ComPtr<IVirtualBox> &aParent);
+ HRESULT getIcon(std::vector<BYTE> &aIcon);
+ HRESULT setIcon(const std::vector<BYTE> &aIcon);
+ HRESULT getAccessible(BOOL *aAccessible);
+ HRESULT getAccessError(ComPtr<IVirtualBoxErrorInfo> &aAccessError);
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT setName(const com::Utf8Str &aName);
+ HRESULT getDescription(com::Utf8Str &aDescription);
+ HRESULT setDescription(const com::Utf8Str &aDescription);
+ HRESULT getId(com::Guid &aId);
+ HRESULT getGroups(std::vector<com::Utf8Str> &aGroups);
+ HRESULT setGroups(const std::vector<com::Utf8Str> &aGroups);
+ HRESULT getOSTypeId(com::Utf8Str &aOSTypeId);
+ HRESULT setOSTypeId(const com::Utf8Str &aOSTypeId);
+ HRESULT getHardwareVersion(com::Utf8Str &aHardwareVersion);
+ HRESULT setHardwareVersion(const com::Utf8Str &aHardwareVersion);
+ HRESULT getHardwareUUID(com::Guid &aHardwareUUID);
+ HRESULT setHardwareUUID(const com::Guid &aHardwareUUID);
+ HRESULT getCPUCount(ULONG *aCPUCount);
+ HRESULT setCPUCount(ULONG aCPUCount);
+ HRESULT getCPUHotPlugEnabled(BOOL *aCPUHotPlugEnabled);
+ HRESULT setCPUHotPlugEnabled(BOOL aCPUHotPlugEnabled);
+ HRESULT getCPUExecutionCap(ULONG *aCPUExecutionCap);
+ HRESULT setCPUExecutionCap(ULONG aCPUExecutionCap);
+ HRESULT getCPUIDPortabilityLevel(ULONG *aCPUIDPortabilityLevel);
+ HRESULT setCPUIDPortabilityLevel(ULONG aCPUIDPortabilityLevel);
+ HRESULT getCPUProfile(com::Utf8Str &aCPUProfile);
+ HRESULT setCPUProfile(const com::Utf8Str &aCPUProfile);
+ HRESULT getMemorySize(ULONG *aMemorySize);
+ HRESULT setMemorySize(ULONG aMemorySize);
+ HRESULT getMemoryBalloonSize(ULONG *aMemoryBalloonSize);
+ HRESULT setMemoryBalloonSize(ULONG aMemoryBalloonSize);
+ HRESULT getPageFusionEnabled(BOOL *aPageFusionEnabled);
+ HRESULT setPageFusionEnabled(BOOL aPageFusionEnabled);
+ HRESULT getGraphicsAdapter(ComPtr<IGraphicsAdapter> &aGraphicsAdapter);
+ HRESULT getBIOSSettings(ComPtr<IBIOSSettings> &aBIOSSettings);
+ HRESULT getTrustedPlatformModule(ComPtr<ITrustedPlatformModule> &aTrustedPlatformModule);
+ HRESULT getNonVolatileStore(ComPtr<INvramStore> &aNvramStore);
+ HRESULT getRecordingSettings(ComPtr<IRecordingSettings> &aRecordingSettings);
+ HRESULT getFirmwareType(FirmwareType_T *aFirmwareType);
+ HRESULT setFirmwareType(FirmwareType_T aFirmwareType);
+ HRESULT getPointingHIDType(PointingHIDType_T *aPointingHIDType);
+ HRESULT setPointingHIDType(PointingHIDType_T aPointingHIDType);
+ HRESULT getKeyboardHIDType(KeyboardHIDType_T *aKeyboardHIDType);
+ HRESULT setKeyboardHIDType(KeyboardHIDType_T aKeyboardHIDType);
+ HRESULT getHPETEnabled(BOOL *aHPETEnabled);
+ HRESULT setHPETEnabled(BOOL aHPETEnabled);
+ HRESULT getChipsetType(ChipsetType_T *aChipsetType);
+ HRESULT setChipsetType(ChipsetType_T aChipsetType);
+ HRESULT getIommuType(IommuType_T *aIommuType);
+ HRESULT setIommuType(IommuType_T aIommuType);
+ HRESULT getSnapshotFolder(com::Utf8Str &aSnapshotFolder);
+ HRESULT setSnapshotFolder(const com::Utf8Str &aSnapshotFolder);
+ HRESULT getVRDEServer(ComPtr<IVRDEServer> &aVRDEServer);
+ HRESULT getEmulatedUSBCardReaderEnabled(BOOL *aEmulatedUSBCardReaderEnabled);
+ HRESULT setEmulatedUSBCardReaderEnabled(BOOL aEmulatedUSBCardReaderEnabled);
+ HRESULT getMediumAttachments(std::vector<ComPtr<IMediumAttachment> > &aMediumAttachments);
+ HRESULT getUSBControllers(std::vector<ComPtr<IUSBController> > &aUSBControllers);
+ HRESULT getUSBDeviceFilters(ComPtr<IUSBDeviceFilters> &aUSBDeviceFilters);
+ HRESULT getAudioSettings(ComPtr<IAudioSettings> &aAudioSettings);
+ HRESULT getStorageControllers(std::vector<ComPtr<IStorageController> > &aStorageControllers);
+ HRESULT getSettingsFilePath(com::Utf8Str &aSettingsFilePath);
+ HRESULT getSettingsAuxFilePath(com::Utf8Str &aSettingsAuxFilePath);
+ HRESULT getSettingsModified(BOOL *aSettingsModified);
+ HRESULT getSessionState(SessionState_T *aSessionState);
+ HRESULT getSessionType(SessionType_T *aSessionType);
+ HRESULT getSessionName(com::Utf8Str &aSessionType);
+ HRESULT getSessionPID(ULONG *aSessionPID);
+ HRESULT getState(MachineState_T *aState);
+ HRESULT getLastStateChange(LONG64 *aLastStateChange);
+ HRESULT getStateFilePath(com::Utf8Str &aStateFilePath);
+ HRESULT getLogFolder(com::Utf8Str &aLogFolder);
+ HRESULT getCurrentSnapshot(ComPtr<ISnapshot> &aCurrentSnapshot);
+ HRESULT getSnapshotCount(ULONG *aSnapshotCount);
+ HRESULT getCurrentStateModified(BOOL *aCurrentStateModified);
+ HRESULT getSharedFolders(std::vector<ComPtr<ISharedFolder> > &aSharedFolders);
+ HRESULT getClipboardMode(ClipboardMode_T *aClipboardMode);
+ HRESULT setClipboardMode(ClipboardMode_T aClipboardMode);
+ HRESULT getClipboardFileTransfersEnabled(BOOL *aEnabled);
+ HRESULT setClipboardFileTransfersEnabled(BOOL aEnabled);
+ HRESULT getDnDMode(DnDMode_T *aDnDMode);
+ HRESULT setDnDMode(DnDMode_T aDnDMode);
+ HRESULT getTeleporterEnabled(BOOL *aTeleporterEnabled);
+ HRESULT setTeleporterEnabled(BOOL aTeleporterEnabled);
+ HRESULT getTeleporterPort(ULONG *aTeleporterPort);
+ HRESULT setTeleporterPort(ULONG aTeleporterPort);
+ HRESULT getTeleporterAddress(com::Utf8Str &aTeleporterAddress);
+ HRESULT setTeleporterAddress(const com::Utf8Str &aTeleporterAddress);
+ HRESULT getTeleporterPassword(com::Utf8Str &aTeleporterPassword);
+ HRESULT setTeleporterPassword(const com::Utf8Str &aTeleporterPassword);
+ HRESULT getParavirtProvider(ParavirtProvider_T *aParavirtProvider);
+ HRESULT setParavirtProvider(ParavirtProvider_T aParavirtProvider);
+ HRESULT getParavirtDebug(com::Utf8Str &aParavirtDebug);
+ HRESULT setParavirtDebug(const com::Utf8Str &aParavirtDebug);
+ HRESULT getRTCUseUTC(BOOL *aRTCUseUTC);
+ HRESULT setRTCUseUTC(BOOL aRTCUseUTC);
+ HRESULT getIOCacheEnabled(BOOL *aIOCacheEnabled);
+ HRESULT setIOCacheEnabled(BOOL aIOCacheEnabled);
+ HRESULT getIOCacheSize(ULONG *aIOCacheSize);
+ HRESULT setIOCacheSize(ULONG aIOCacheSize);
+ HRESULT getPCIDeviceAssignments(std::vector<ComPtr<IPCIDeviceAttachment> > &aPCIDeviceAssignments);
+ HRESULT getBandwidthControl(ComPtr<IBandwidthControl> &aBandwidthControl);
+ HRESULT getTracingEnabled(BOOL *aTracingEnabled);
+ HRESULT setTracingEnabled(BOOL aTracingEnabled);
+ HRESULT getTracingConfig(com::Utf8Str &aTracingConfig);
+ HRESULT setTracingConfig(const com::Utf8Str &aTracingConfig);
+ HRESULT getAllowTracingToAccessVM(BOOL *aAllowTracingToAccessVM);
+ HRESULT setAllowTracingToAccessVM(BOOL aAllowTracingToAccessVM);
+ HRESULT getAutostartEnabled(BOOL *aAutostartEnabled);
+ HRESULT setAutostartEnabled(BOOL aAutostartEnabled);
+ HRESULT getAutostartDelay(ULONG *aAutostartDelay);
+ HRESULT setAutostartDelay(ULONG aAutostartDelay);
+ HRESULT getAutostopType(AutostopType_T *aAutostopType);
+ HRESULT setAutostopType(AutostopType_T aAutostopType);
+ HRESULT getDefaultFrontend(com::Utf8Str &aDefaultFrontend);
+ HRESULT setDefaultFrontend(const com::Utf8Str &aDefaultFrontend);
+ HRESULT getUSBProxyAvailable(BOOL *aUSBProxyAvailable);
+ HRESULT getVMProcessPriority(VMProcPriority_T *aVMProcessPriority);
+ HRESULT setVMProcessPriority(VMProcPriority_T aVMProcessPriority);
+ HRESULT getStateKeyId(com::Utf8Str &aKeyId);
+ HRESULT getStateKeyStore(com::Utf8Str &aKeyStore);
+ HRESULT getLogKeyId(com::Utf8Str &aKeyId);
+ HRESULT getLogKeyStore(com::Utf8Str &aKeyStore);
+ HRESULT getGuestDebugControl(ComPtr<IGuestDebugControl> &aGuestDebugControl);
+
+ // wrapped IMachine methods
+ HRESULT lockMachine(const ComPtr<ISession> &aSession,
+ LockType_T aLockType);
+ HRESULT launchVMProcess(const ComPtr<ISession> &aSession,
+ const com::Utf8Str &aType,
+ const std::vector<com::Utf8Str> &aEnvironmentChanges,
+ ComPtr<IProgress> &aProgress);
+ HRESULT setBootOrder(ULONG aPosition,
+ DeviceType_T aDevice);
+ HRESULT getBootOrder(ULONG aPosition,
+ DeviceType_T *aDevice);
+ HRESULT attachDevice(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ DeviceType_T aType,
+ const ComPtr<IMedium> &aMedium);
+ HRESULT attachDeviceWithoutMedium(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ DeviceType_T aType);
+ HRESULT detachDevice(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice);
+ HRESULT passthroughDevice(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ BOOL aPassthrough);
+ HRESULT temporaryEjectDevice(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ BOOL aTemporaryEject);
+ HRESULT nonRotationalDevice(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ BOOL aNonRotational);
+ HRESULT setAutoDiscardForDevice(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ BOOL aDiscard);
+ HRESULT setHotPluggableForDevice(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ BOOL aHotPluggable);
+ HRESULT setBandwidthGroupForDevice(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ const ComPtr<IBandwidthGroup> &aBandwidthGroup);
+ HRESULT setNoBandwidthGroupForDevice(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice);
+ HRESULT unmountMedium(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ BOOL aForce);
+ HRESULT mountMedium(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ const ComPtr<IMedium> &aMedium,
+ BOOL aForce);
+ HRESULT getMedium(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ ComPtr<IMedium> &aMedium);
+ HRESULT getMediumAttachmentsOfController(const com::Utf8Str &aName,
+ std::vector<ComPtr<IMediumAttachment> > &aMediumAttachments);
+ HRESULT getMediumAttachment(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ ComPtr<IMediumAttachment> &aAttachment);
+ HRESULT attachHostPCIDevice(LONG aHostAddress,
+ LONG aDesiredGuestAddress,
+ BOOL aTryToUnbind);
+ HRESULT detachHostPCIDevice(LONG aHostAddress);
+ HRESULT getNetworkAdapter(ULONG aSlot,
+ ComPtr<INetworkAdapter> &aAdapter);
+ HRESULT addStorageController(const com::Utf8Str &aName,
+ StorageBus_T aConnectionType,
+ ComPtr<IStorageController> &aController);
+ HRESULT getStorageControllerByName(const com::Utf8Str &aName,
+ ComPtr<IStorageController> &aStorageController);
+ HRESULT getStorageControllerByInstance(StorageBus_T aConnectionType,
+ ULONG aInstance,
+ ComPtr<IStorageController> &aStorageController);
+ HRESULT removeStorageController(const com::Utf8Str &aName);
+ HRESULT setStorageControllerBootable(const com::Utf8Str &aName,
+ BOOL aBootable);
+ HRESULT addUSBController(const com::Utf8Str &aName,
+ USBControllerType_T aType,
+ ComPtr<IUSBController> &aController);
+ HRESULT removeUSBController(const com::Utf8Str &aName);
+ HRESULT getUSBControllerByName(const com::Utf8Str &aName,
+ ComPtr<IUSBController> &aController);
+ HRESULT getUSBControllerCountByType(USBControllerType_T aType,
+ ULONG *aControllers);
+ HRESULT getSerialPort(ULONG aSlot,
+ ComPtr<ISerialPort> &aPort);
+ HRESULT getParallelPort(ULONG aSlot,
+ ComPtr<IParallelPort> &aPort);
+ HRESULT getExtraDataKeys(std::vector<com::Utf8Str> &aKeys);
+ HRESULT getExtraData(const com::Utf8Str &aKey,
+ com::Utf8Str &aValue);
+ HRESULT setExtraData(const com::Utf8Str &aKey,
+ const com::Utf8Str &aValue);
+ HRESULT getCPUProperty(CPUPropertyType_T aProperty,
+ BOOL *aValue);
+ HRESULT setCPUProperty(CPUPropertyType_T aProperty,
+ BOOL aValue);
+ HRESULT getCPUIDLeafByOrdinal(ULONG aOrdinal,
+ ULONG *aIdx,
+ ULONG *aSubIdx,
+ ULONG *aValEax,
+ ULONG *aValEbx,
+ ULONG *aValEcx,
+ ULONG *aValEdx);
+ HRESULT getCPUIDLeaf(ULONG aIdx, ULONG aSubIdx,
+ ULONG *aValEax,
+ ULONG *aValEbx,
+ ULONG *aValEcx,
+ ULONG *aValEdx);
+ HRESULT setCPUIDLeaf(ULONG aIdx, ULONG aSubIdx,
+ ULONG aValEax,
+ ULONG aValEbx,
+ ULONG aValEcx,
+ ULONG aValEdx);
+ HRESULT removeCPUIDLeaf(ULONG aIdx, ULONG aSubIdx);
+ HRESULT removeAllCPUIDLeaves();
+ HRESULT getHWVirtExProperty(HWVirtExPropertyType_T aProperty,
+ BOOL *aValue);
+ HRESULT setHWVirtExProperty(HWVirtExPropertyType_T aProperty,
+ BOOL aValue);
+ HRESULT setSettingsFilePath(const com::Utf8Str &aSettingsFilePath,
+ ComPtr<IProgress> &aProgress);
+ HRESULT saveSettings();
+ HRESULT discardSettings();
+ HRESULT unregister(AutoCaller &aAutoCaller,
+ CleanupMode_T aCleanupMode,
+ std::vector<ComPtr<IMedium> > &aMedia);
+ HRESULT deleteConfig(const std::vector<ComPtr<IMedium> > &aMedia,
+ ComPtr<IProgress> &aProgress);
+ HRESULT exportTo(const ComPtr<IAppliance> &aAppliance,
+ const com::Utf8Str &aLocation,
+ ComPtr<IVirtualSystemDescription> &aDescription);
+ HRESULT findSnapshot(const com::Utf8Str &aNameOrId,
+ ComPtr<ISnapshot> &aSnapshot);
+ HRESULT createSharedFolder(const com::Utf8Str &aName,
+ const com::Utf8Str &aHostPath,
+ BOOL aWritable,
+ BOOL aAutomount,
+ const com::Utf8Str &aAutoMountPoint);
+ HRESULT removeSharedFolder(const com::Utf8Str &aName);
+ HRESULT canShowConsoleWindow(BOOL *aCanShow);
+ HRESULT showConsoleWindow(LONG64 *aWinId);
+ HRESULT getGuestProperty(const com::Utf8Str &aName,
+ com::Utf8Str &aValue,
+ LONG64 *aTimestamp,
+ com::Utf8Str &aFlags);
+ HRESULT getGuestPropertyValue(const com::Utf8Str &aProperty,
+ com::Utf8Str &aValue);
+ HRESULT getGuestPropertyTimestamp(const com::Utf8Str &aProperty,
+ LONG64 *aValue);
+ HRESULT setGuestProperty(const com::Utf8Str &aProperty,
+ const com::Utf8Str &aValue,
+ const com::Utf8Str &aFlags);
+ HRESULT setGuestPropertyValue(const com::Utf8Str &aProperty,
+ const com::Utf8Str &aValue);
+ HRESULT deleteGuestProperty(const com::Utf8Str &aName);
+ HRESULT enumerateGuestProperties(const com::Utf8Str &aPatterns,
+ std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<com::Utf8Str> &aFlags);
+ HRESULT querySavedGuestScreenInfo(ULONG aScreenId,
+ ULONG *aOriginX,
+ ULONG *aOriginY,
+ ULONG *aWidth,
+ ULONG *aHeight,
+ BOOL *aEnabled);
+ HRESULT readSavedThumbnailToArray(ULONG aScreenId,
+ BitmapFormat_T aBitmapFormat,
+ ULONG *aWidth,
+ ULONG *aHeight,
+ std::vector<BYTE> &aData);
+ HRESULT querySavedScreenshotInfo(ULONG aScreenId,
+ ULONG *aWidth,
+ ULONG *aHeight,
+ std::vector<BitmapFormat_T> &aBitmapFormats);
+ HRESULT readSavedScreenshotToArray(ULONG aScreenId,
+ BitmapFormat_T aBitmapFormat,
+ ULONG *aWidth,
+ ULONG *aHeight,
+ std::vector<BYTE> &aData);
+
+ HRESULT hotPlugCPU(ULONG aCpu);
+ HRESULT hotUnplugCPU(ULONG aCpu);
+ HRESULT getCPUStatus(ULONG aCpu,
+ BOOL *aAttached);
+ HRESULT getEffectiveParavirtProvider(ParavirtProvider_T *aParavirtProvider);
+ HRESULT queryLogFilename(ULONG aIdx,
+ com::Utf8Str &aFilename);
+ HRESULT readLog(ULONG aIdx,
+ LONG64 aOffset,
+ LONG64 aSize,
+ std::vector<BYTE> &aData);
+ HRESULT cloneTo(const ComPtr<IMachine> &aTarget,
+ CloneMode_T aMode,
+ const std::vector<CloneOptions_T> &aOptions,
+ ComPtr<IProgress> &aProgress);
+ HRESULT moveTo(const com::Utf8Str &aTargetPath,
+ const com::Utf8Str &aType,
+ ComPtr<IProgress> &aProgress);
+ HRESULT saveState(ComPtr<IProgress> &aProgress);
+ HRESULT adoptSavedState(const com::Utf8Str &aSavedStateFile);
+ HRESULT discardSavedState(BOOL aFRemoveFile);
+ HRESULT takeSnapshot(const com::Utf8Str &aName,
+ const com::Utf8Str &aDescription,
+ BOOL aPause,
+ com::Guid &aId,
+ ComPtr<IProgress> &aProgress);
+ HRESULT deleteSnapshot(const com::Guid &aId,
+ ComPtr<IProgress> &aProgress);
+ HRESULT deleteSnapshotAndAllChildren(const com::Guid &aId,
+ ComPtr<IProgress> &aProgress);
+ HRESULT deleteSnapshotRange(const com::Guid &aStartId,
+ const com::Guid &aEndId,
+ ComPtr<IProgress> &aProgress);
+ HRESULT restoreSnapshot(const ComPtr<ISnapshot> &aSnapshot,
+ ComPtr<IProgress> &aProgress);
+ HRESULT applyDefaults(const com::Utf8Str &aFlags);
+ HRESULT changeEncryption(const com::Utf8Str &aCurrentPassword,
+ const com::Utf8Str &aCipher,
+ const com::Utf8Str &aNewPassword,
+ const com::Utf8Str &aNewPasswordId,
+ BOOL aForce,
+ ComPtr<IProgress> &aProgress);
+ HRESULT getEncryptionSettings(com::Utf8Str &aCipher,
+ com::Utf8Str &aPasswordId);
+ HRESULT checkEncryptionPassword(const com::Utf8Str &aPassword);
+ HRESULT addEncryptionPassword(const com::Utf8Str &aId,
+ const com::Utf8Str &aPassword);
+ HRESULT addEncryptionPasswords(const std::vector<com::Utf8Str> &aIds,
+ const std::vector<com::Utf8Str> &aPasswords);
+ HRESULT removeEncryptionPassword(AutoCaller &autoCaller,
+ const com::Utf8Str &aId);
+ HRESULT clearAllEncryptionPasswords(AutoCaller &autoCaller);
+
+ // wrapped IInternalMachineControl properties
+
+ // wrapped IInternalMachineControl methods
+ HRESULT updateState(MachineState_T aState);
+ HRESULT beginPowerUp(const ComPtr<IProgress> &aProgress);
+ HRESULT endPowerUp(LONG aResult);
+ HRESULT beginPoweringDown(ComPtr<IProgress> &aProgress);
+ HRESULT endPoweringDown(LONG aResult,
+ const com::Utf8Str &aErrMsg);
+ HRESULT runUSBDeviceFilters(const ComPtr<IUSBDevice> &aDevice,
+ BOOL *aMatched,
+ ULONG *aMaskedInterfaces);
+ HRESULT captureUSBDevice(const com::Guid &aId,
+ const com::Utf8Str &aCaptureFilename);
+ HRESULT detachUSBDevice(const com::Guid &aId,
+ BOOL aDone);
+ HRESULT autoCaptureUSBDevices();
+ HRESULT detachAllUSBDevices(BOOL aDone);
+ HRESULT onSessionEnd(const ComPtr<ISession> &aSession,
+ ComPtr<IProgress> &aProgress);
+ HRESULT finishOnlineMergeMedium();
+ HRESULT pullGuestProperties(std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<com::Utf8Str> &aFlags);
+ HRESULT pushGuestProperty(const com::Utf8Str &aName,
+ const com::Utf8Str &aValue,
+ LONG64 aTimestamp,
+ const com::Utf8Str &aFlags,
+ BOOL fWasDeleted);
+ HRESULT lockMedia();
+ HRESULT unlockMedia();
+ HRESULT ejectMedium(const ComPtr<IMediumAttachment> &aAttachment,
+ ComPtr<IMediumAttachment> &aNewAttachment);
+ HRESULT reportVmStatistics(ULONG aValidStats,
+ ULONG aCpuUser,
+ ULONG aCpuKernel,
+ ULONG aCpuIdle,
+ ULONG aMemTotal,
+ ULONG aMemFree,
+ ULONG aMemBalloon,
+ ULONG aMemShared,
+ ULONG aMemCache,
+ ULONG aPagedTotal,
+ ULONG aMemAllocTotal,
+ ULONG aMemFreeTotal,
+ ULONG aMemBalloonTotal,
+ ULONG aMemSharedTotal,
+ ULONG aVmNetRx,
+ ULONG aVmNetTx);
+ HRESULT authenticateExternal(const std::vector<com::Utf8Str> &aAuthParams,
+ com::Utf8Str &aResult);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ HRESULT i_setInaccessible(void);
+#endif
+};
+
+// SessionMachine class
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * @note Notes on locking objects of this class:
+ * SessionMachine shares some data with the primary Machine instance (pointed
+ * to by the |mPeer| member). In order to provide data consistency it also
+ * shares its lock handle. This means that whenever you lock a SessionMachine
+ * instance using Auto[Reader]Lock or AutoMultiLock, the corresponding Machine
+ * instance is also locked in the same lock mode. Keep it in mind.
+ */
+class ATL_NO_VTABLE SessionMachine :
+ public Machine
+{
+public:
+ VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(SessionMachine, IMachine)
+
+ DECLARE_NOT_AGGREGATABLE(SessionMachine)
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ BEGIN_COM_MAP(SessionMachine)
+ COM_INTERFACE_ENTRY(ISupportErrorInfo)
+ COM_INTERFACE_ENTRY(IMachine)
+ COM_INTERFACE_ENTRY2(IDispatch, IMachine)
+ COM_INTERFACE_ENTRY(IInternalMachineControl)
+ VBOX_TWEAK_INTERFACE_ENTRY(IMachine)
+ END_COM_MAP()
+
+ DECLARE_COMMON_CLASS_METHODS(SessionMachine)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ struct Uninit
+ {
+ enum Reason { Unexpected, Abnormal, Normal };
+ };
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aMachine);
+ void uninit() { uninit(Uninit::Unexpected); }
+ void uninit(Uninit::Reason aReason);
+
+
+ // util::Lockable interface
+ RWLockHandle *lockHandle() const;
+
+ // public methods only for internal purposes
+
+ virtual bool i_isSessionMachine() const
+ {
+ return true;
+ }
+
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ bool i_checkForDeath();
+
+ void i_getTokenId(Utf8Str &strTokenId);
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ IToken *i_getToken();
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ // getClientToken must be only used by callers who can guarantee that
+ // the object cannot be deleted in the mean time, i.e. have a caller/lock.
+ ClientToken *i_getClientToken();
+
+ HRESULT i_onNetworkAdapterChange(INetworkAdapter *networkAdapter, BOOL changeAdapter);
+ HRESULT i_onNATRedirectRuleChanged(ULONG ulSlot, BOOL aNatRuleRemove, const Utf8Str &aRuleName,
+ NATProtocol_T aProto, const Utf8Str &aHostIp, LONG aHostPort,
+ const Utf8Str &aGuestIp, LONG aGuestPort) RT_OVERRIDE;
+ HRESULT i_onStorageControllerChange(const com::Guid &aMachineId, const com::Utf8Str &aControllerName);
+ HRESULT i_onMediumChange(IMediumAttachment *aMediumAttachment, BOOL aForce);
+ HRESULT i_onVMProcessPriorityChange(VMProcPriority_T aPriority);
+ HRESULT i_onAudioAdapterChange(IAudioAdapter *audioAdapter);
+ HRESULT i_onHostAudioDeviceChange(IHostAudioDevice *aDevice, BOOL aNew, AudioDeviceState_T aState, IVirtualBoxErrorInfo *aErrInfo);
+ HRESULT i_onSerialPortChange(ISerialPort *serialPort);
+ HRESULT i_onParallelPortChange(IParallelPort *parallelPort);
+ HRESULT i_onCPUChange(ULONG aCPU, BOOL aRemove);
+ HRESULT i_onVRDEServerChange(BOOL aRestart);
+ HRESULT i_onRecordingChange(BOOL aEnable);
+ HRESULT i_onUSBControllerChange();
+ HRESULT i_onUSBDeviceAttach(IUSBDevice *aDevice,
+ IVirtualBoxErrorInfo *aError,
+ ULONG aMaskedIfs,
+ const com::Utf8Str &aCaptureFilename);
+ HRESULT i_onUSBDeviceDetach(IN_BSTR aId,
+ IVirtualBoxErrorInfo *aError);
+ HRESULT i_onSharedFolderChange();
+ HRESULT i_onClipboardModeChange(ClipboardMode_T aClipboardMode);
+ HRESULT i_onClipboardFileTransferModeChange(BOOL aEnable);
+ HRESULT i_onDnDModeChange(DnDMode_T aDnDMode);
+ HRESULT i_onBandwidthGroupChange(IBandwidthGroup *aBandwidthGroup);
+ HRESULT i_onStorageDeviceChange(IMediumAttachment *aMediumAttachment, BOOL aRemove, BOOL aSilent);
+ HRESULT i_onCPUExecutionCapChange(ULONG aCpuExecutionCap);
+ HRESULT i_onGuestDebugControlChange(IGuestDebugControl *guestDebugControl);
+
+ bool i_hasMatchingUSBFilter(const ComObjPtr<HostUSBDevice> &aDevice, ULONG *aMaskedIfs);
+
+ HRESULT i_lockMedia();
+ HRESULT i_unlockMedia();
+
+ HRESULT i_saveStateWithReason(Reason_T aReason, ComPtr<IProgress> &aProgress);
+
+private:
+
+ // wrapped IInternalMachineControl properties
+
+ // wrapped IInternalMachineControl methods
+ HRESULT setRemoveSavedStateFile(BOOL aRemove);
+ HRESULT updateState(MachineState_T aState);
+ HRESULT beginPowerUp(const ComPtr<IProgress> &aProgress);
+ HRESULT endPowerUp(LONG aResult);
+ HRESULT beginPoweringDown(ComPtr<IProgress> &aProgress);
+ HRESULT endPoweringDown(LONG aResult,
+ const com::Utf8Str &aErrMsg);
+ HRESULT runUSBDeviceFilters(const ComPtr<IUSBDevice> &aDevice,
+ BOOL *aMatched,
+ ULONG *aMaskedInterfaces);
+ HRESULT captureUSBDevice(const com::Guid &aId, const com::Utf8Str &aCaptureFilename);
+ HRESULT detachUSBDevice(const com::Guid &aId,
+ BOOL aDone);
+ HRESULT autoCaptureUSBDevices();
+ HRESULT detachAllUSBDevices(BOOL aDone);
+ HRESULT onSessionEnd(const ComPtr<ISession> &aSession,
+ ComPtr<IProgress> &aProgress);
+ HRESULT finishOnlineMergeMedium();
+ HRESULT pullGuestProperties(std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<com::Utf8Str> &aFlags);
+ HRESULT pushGuestProperty(const com::Utf8Str &aName,
+ const com::Utf8Str &aValue,
+ LONG64 aTimestamp,
+ const com::Utf8Str &aFlags,
+ BOOL fWasDeleted);
+ HRESULT lockMedia();
+ HRESULT unlockMedia();
+ HRESULT ejectMedium(const ComPtr<IMediumAttachment> &aAttachment,
+ ComPtr<IMediumAttachment> &aNewAttachment);
+ HRESULT reportVmStatistics(ULONG aValidStats,
+ ULONG aCpuUser,
+ ULONG aCpuKernel,
+ ULONG aCpuIdle,
+ ULONG aMemTotal,
+ ULONG aMemFree,
+ ULONG aMemBalloon,
+ ULONG aMemShared,
+ ULONG aMemCache,
+ ULONG aPagedTotal,
+ ULONG aMemAllocTotal,
+ ULONG aMemFreeTotal,
+ ULONG aMemBalloonTotal,
+ ULONG aMemSharedTotal,
+ ULONG aVmNetRx,
+ ULONG aVmNetTx);
+ HRESULT authenticateExternal(const std::vector<com::Utf8Str> &aAuthParams,
+ com::Utf8Str &aResult);
+
+
+ struct ConsoleTaskData
+ {
+ ConsoleTaskData()
+ : mLastState(MachineState_Null),
+ mDeleteSnapshotInfo(NULL)
+ { }
+
+ MachineState_T mLastState;
+ ComObjPtr<Progress> mProgress;
+
+ // used when deleting online snaphshot
+ void *mDeleteSnapshotInfo;
+ };
+
+ class SaveStateTask;
+ class SnapshotTask;
+ class TakeSnapshotTask;
+ class DeleteSnapshotTask;
+ class RestoreSnapshotTask;
+
+ void i_saveStateHandler(SaveStateTask &aTask);
+
+ // Override some functionality for SessionMachine, this is where the
+ // real action happens (the Machine methods are just dummies).
+ HRESULT saveState(ComPtr<IProgress> &aProgress);
+ HRESULT adoptSavedState(const com::Utf8Str &aSavedStateFile);
+ HRESULT discardSavedState(BOOL aFRemoveFile);
+ HRESULT takeSnapshot(const com::Utf8Str &aName,
+ const com::Utf8Str &aDescription,
+ BOOL aPause,
+ com::Guid &aId,
+ ComPtr<IProgress> &aProgress);
+ HRESULT deleteSnapshot(const com::Guid &aId,
+ ComPtr<IProgress> &aProgress);
+ HRESULT deleteSnapshotAndAllChildren(const com::Guid &aId,
+ ComPtr<IProgress> &aProgress);
+ HRESULT deleteSnapshotRange(const com::Guid &aStartId,
+ const com::Guid &aEndId,
+ ComPtr<IProgress> &aProgress);
+ HRESULT restoreSnapshot(const ComPtr<ISnapshot> &aSnapshot,
+ ComPtr<IProgress> &aProgress);
+
+ void i_releaseSavedStateFile(const Utf8Str &strSavedStateFile, Snapshot *pSnapshotToIgnore);
+
+ void i_takeSnapshotHandler(TakeSnapshotTask &aTask);
+ static void i_takeSnapshotProgressCancelCallback(void *pvUser);
+ HRESULT i_finishTakingSnapshot(TakeSnapshotTask &aTask, AutoWriteLock &alock, bool aSuccess);
+ HRESULT i_deleteSnapshot(const com::Guid &aStartId,
+ const com::Guid &aEndId,
+ BOOL aDeleteAllChildren,
+ ComPtr<IProgress> &aProgress);
+ void i_deleteSnapshotHandler(DeleteSnapshotTask &aTask);
+ void i_restoreSnapshotHandler(RestoreSnapshotTask &aTask);
+
+ HRESULT i_prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
+ const Guid &machineId,
+ const Guid &snapshotId,
+ bool fOnlineMergePossible,
+ MediumLockList *aVMMALockList,
+ ComObjPtr<Medium> &aSource,
+ ComObjPtr<Medium> &aTarget,
+ bool &fMergeForward,
+ ComObjPtr<Medium> &pParentForTarget,
+ MediumLockList * &aChildrenToReparent,
+ bool &fNeedOnlineMerge,
+ MediumLockList * &aMediumLockList,
+ ComPtr<IToken> &aHDLockToken);
+ void i_cancelDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
+ const ComObjPtr<Medium> &aSource,
+ MediumLockList *aChildrenToReparent,
+ bool fNeedsOnlineMerge,
+ MediumLockList *aMediumLockList,
+ const ComPtr<IToken> &aHDLockToken,
+ const Guid &aMediumId,
+ const Guid &aSnapshotId);
+ HRESULT i_onlineMergeMedium(const ComObjPtr<MediumAttachment> &aMediumAttachment,
+ const ComObjPtr<Medium> &aSource,
+ const ComObjPtr<Medium> &aTarget,
+ bool fMergeForward,
+ const ComObjPtr<Medium> &pParentForTarget,
+ MediumLockList *aChildrenToReparent,
+ MediumLockList *aMediumLockList,
+ ComObjPtr<Progress> &aProgress,
+ bool *pfNeedsMachineSaveSettings);
+
+ HRESULT i_setMachineState(MachineState_T aMachineState);
+ HRESULT i_updateMachineStateOnClient();
+
+ bool mRemoveSavedState;
+
+ ConsoleTaskData mConsoleTaskData;
+
+ /** client token for this machine */
+ ClientToken *mClientToken;
+
+ int miNATNetworksStarted;
+
+ AUTHLIBRARYCONTEXT mAuthLibCtx;
+};
+
+// SnapshotMachine class
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * @note Notes on locking objects of this class:
+ * SnapshotMachine shares some data with the primary Machine instance (pointed
+ * to by the |mPeer| member). In order to provide data consistency it also
+ * shares its lock handle. This means that whenever you lock a SessionMachine
+ * instance using Auto[Reader]Lock or AutoMultiLock, the corresponding Machine
+ * instance is also locked in the same lock mode. Keep it in mind.
+ */
+class ATL_NO_VTABLE SnapshotMachine :
+ public Machine
+{
+public:
+ VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(SnapshotMachine, IMachine)
+
+ DECLARE_NOT_AGGREGATABLE(SnapshotMachine)
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ BEGIN_COM_MAP(SnapshotMachine)
+ COM_INTERFACE_ENTRY(ISupportErrorInfo)
+ COM_INTERFACE_ENTRY(IMachine)
+ COM_INTERFACE_ENTRY2(IDispatch, IMachine)
+ VBOX_TWEAK_INTERFACE_ENTRY(IMachine)
+ END_COM_MAP()
+
+ DECLARE_COMMON_CLASS_METHODS(SnapshotMachine)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(SessionMachine *aSessionMachine,
+ IN_GUID aSnapshotId,
+ const Utf8Str &aStateFilePath);
+ HRESULT initFromSettings(Machine *aMachine,
+ const settings::Hardware &hardware,
+ const settings::Debugging *pDbg,
+ const settings::Autostart *pAutostart,
+ const settings::RecordingSettings &recording,
+ IN_GUID aSnapshotId,
+ const Utf8Str &aStateFilePath);
+ void uninit();
+
+ // util::Lockable interface
+ RWLockHandle *lockHandle() const;
+
+ // public methods only for internal purposes
+
+ virtual bool i_isSnapshotMachine() const
+ {
+ return true;
+ }
+
+ HRESULT i_onSnapshotChange(Snapshot *aSnapshot);
+
+ // unsafe inline public methods for internal purposes only (ensure there is
+ // a caller and a read lock before calling them!)
+
+ const Guid& i_getSnapshotId() const { return mSnapshotId; }
+
+private:
+
+ Guid mSnapshotId;
+ /** This field replaces mPeer for SessionMachine instances, as having
+ * a peer reference is plain meaningless and causes many subtle problems
+ * with saving settings and the like. */
+ Machine * const mMachine;
+
+ friend class Snapshot;
+};
+
+// third party methods that depend on SnapshotMachine definition
+
+inline const Guid &Machine::i_getSnapshotId() const
+{
+ return (i_isSnapshotMachine())
+ ? static_cast<const SnapshotMachine*>(this)->i_getSnapshotId()
+ : Guid::Empty;
+}
+
+
+#endif /* !MAIN_INCLUDED_MachineImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/MachineImplCloneVM.h b/src/VBox/Main/include/MachineImplCloneVM.h
new file mode 100644
index 00000000..b8164c57
--- /dev/null
+++ b/src/VBox/Main/include/MachineImplCloneVM.h
@@ -0,0 +1,63 @@
+/* $Id: MachineImplCloneVM.h $ */
+/** @file
+ * Definition of MachineCloneVM
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_MachineImplCloneVM_h
+#define MAIN_INCLUDED_MachineImplCloneVM_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "MachineImpl.h"
+#include "ProgressImpl.h"
+
+/* Forward declaration of the d-pointer. */
+struct MachineCloneVMPrivate;
+
+class MachineCloneVM
+{
+public:
+ DECLARE_TRANSLATE_METHODS(MachineCloneVM)
+
+ MachineCloneVM(ComObjPtr<Machine> pSrcMachine, ComObjPtr<Machine> pTrgMachine, CloneMode_T mode, const RTCList<CloneOptions_T> &opts);
+ ~MachineCloneVM();
+
+ HRESULT start(IProgress **pProgress);
+
+protected:
+ HRESULT run();
+ void destroy();
+
+ /* d-pointer */
+ MachineCloneVM(MachineCloneVMPrivate &d);
+ MachineCloneVMPrivate *d_ptr;
+
+ friend struct MachineCloneVMPrivate;
+};
+
+#endif /* !MAIN_INCLUDED_MachineImplCloneVM_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
+
diff --git a/src/VBox/Main/include/MachineImplMoveVM.h b/src/VBox/Main/include/MachineImplMoveVM.h
new file mode 100644
index 00000000..9b462e43
--- /dev/null
+++ b/src/VBox/Main/include/MachineImplMoveVM.h
@@ -0,0 +1,146 @@
+/* $Id: MachineImplMoveVM.h $ */
+/** @file
+ * Definition of MachineMoveVM
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_MachineImplMoveVM_h
+#define MAIN_INCLUDED_MachineImplMoveVM_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "MachineImpl.h"
+#include "ProgressImpl.h"
+#include "ThreadTask.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+enum VBoxFolder_t
+{
+ VBox_UnknownFolderType = 0,
+ VBox_OutsideVMfolder,
+ VBox_SettingFolder,
+ VBox_LogFolder,
+ VBox_StateFolder,
+ VBox_SnapshotFolder
+};
+
+typedef struct
+{
+ bool fSnapshot;
+ Utf8Str strBaseName;
+ ComPtr<IMedium> pMedium;
+ uint32_t uIdx;
+ ULONG uWeight;
+} MEDIUMTASKMOVE;
+
+typedef struct
+{
+ RTCList<MEDIUMTASKMOVE> chain;
+ DeviceType_T devType;
+ bool fCreateDiffs;
+ bool fAttachLinked;
+} MEDIUMTASKCHAINMOVE;
+
+typedef struct
+{
+ Guid snapshotUuid;
+ Utf8Str strFile;
+ ULONG uWeight;
+} SNAPFILETASKMOVE;
+
+struct fileList_t;
+
+class MachineMoveVM : public ThreadTask
+{
+ std::vector<ComObjPtr<Machine> > machineList;
+ RTCList<MEDIUMTASKCHAINMOVE> m_llMedias;
+ RTCList<SNAPFILETASKMOVE> m_llSaveStateFiles;
+ RTCList<SNAPFILETASKMOVE> m_llNVRAMFiles;
+ std::map<Utf8Str, MEDIUMTASKMOVE> m_finalMediumsMap;
+ std::map<Utf8Str, SNAPFILETASKMOVE> m_finalSaveStateFilesMap;
+ std::map<Utf8Str, SNAPFILETASKMOVE> m_finalNVRAMFilesMap;
+ std::map<VBoxFolder_t, Utf8Str> m_vmFolders;
+
+ ComObjPtr<Machine> m_pMachine;
+ ComObjPtr<Progress> m_pProgress;
+ ComObjPtr<Progress> m_pRollBackProgress;
+ Utf8Str m_targetPath;
+ Utf8Str m_type;
+ HRESULT m_result;
+
+public:
+ DECLARE_TRANSLATE_METHODS(MachineMoveVM)
+
+ MachineMoveVM(ComObjPtr<Machine> aMachine,
+ const com::Utf8Str &aTargetPath,
+ const com::Utf8Str &aType,
+ ComObjPtr<Progress> &aProgress)
+ : ThreadTask("TaskMoveVM")
+ , m_pMachine(aMachine)
+ , m_pProgress(aProgress)
+ , m_targetPath(aTargetPath)
+ , m_type(aType.isEmpty() ? "basic" : aType)
+ , m_result(S_OK)
+ {
+ }
+
+ virtual ~MachineMoveVM()
+ {
+ }
+
+ HRESULT init();
+private:
+ static DECLCALLBACK(int) updateProgress(unsigned uPercent, void *pvUser);
+ static DECLCALLBACK(int) copyFileProgress(unsigned uPercentage, void *pvUser);
+ static void i_MoveVMThreadTask(MachineMoveVM *task);
+
+public:
+ void handler()
+ {
+ i_MoveVMThreadTask(this);
+ }
+
+private:
+ HRESULT createMachineList(const ComPtr<ISnapshot> &pSnapshot);
+ inline HRESULT queryBaseName(const ComPtr<IMedium> &pMedium, Utf8Str &strBaseName) const;
+ HRESULT queryMediasForAllStates();
+ void updateProgressStats(MEDIUMTASKCHAINMOVE &mtc, ULONG &uCount, ULONG &uTotalWeight) const;
+ HRESULT addSaveState(const ComObjPtr<Machine> &machine);
+ HRESULT addNVRAM(const ComObjPtr<Machine> &machine);
+ void printStateFile(settings::SnapshotsList &snl);
+ HRESULT getFilesList(const Utf8Str &strRootFolder, fileList_t &filesList);
+ HRESULT getFolderSize(const Utf8Str &strRootFolder, uint64_t &size);
+ HRESULT deleteFiles(const RTCList<Utf8Str> &listOfFiles);
+ void updatePathsToStateFiles(const Utf8Str &sourcePath, const Utf8Str &targetPath);
+ void updatePathsToNVRAMFiles(const Utf8Str &sourcePath, const Utf8Str &targetPath);
+ HRESULT moveAllDisks(const std::map<Utf8Str, MEDIUMTASKMOVE> &listOfDisks, const Utf8Str &strTargetFolder = Utf8Str::Empty);
+ HRESULT restoreAllDisks(const std::map<Utf8Str, MEDIUMTASKMOVE> &listOfDisks);
+ HRESULT isMediumTypeSupportedForMoving(const ComPtr<IMedium> &pMedium);
+};
+
+#endif /* !MAIN_INCLUDED_MachineImplMoveVM_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
+
diff --git a/src/VBox/Main/include/MachineLaunchVMCommonWorker.h b/src/VBox/Main/include/MachineLaunchVMCommonWorker.h
new file mode 100644
index 00000000..240db496
--- /dev/null
+++ b/src/VBox/Main/include/MachineLaunchVMCommonWorker.h
@@ -0,0 +1,49 @@
+/* $Id: MachineLaunchVMCommonWorker.h $ */
+/** @file
+ * VirtualBox Main - VM process launcher helper for VBoxSVC & VBoxSDS.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_MachineLaunchVMCommonWorker_h
+#define MAIN_INCLUDED_MachineLaunchVMCommonWorker_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <vector>
+#include "VirtualBoxBase.h"
+
+int MachineLaunchVMCommonWorker(const Utf8Str &aNameOrId,
+ const Utf8Str &aComment,
+ const Utf8Str &aFrontend,
+ const std::vector<com::Utf8Str> &aEnvironmentChanges,
+ const Utf8Str &aExtraArg,
+ const Utf8Str &aFilename,
+ uint32_t aFlags,
+ void *aExtraData,
+ RTPROCESS &aPid);
+
+#endif /* !MAIN_INCLUDED_MachineLaunchVMCommonWorker_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
+
diff --git a/src/VBox/Main/include/Makefile.kup b/src/VBox/Main/include/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/include/Makefile.kup
diff --git a/src/VBox/Main/include/Matching.h b/src/VBox/Main/include/Matching.h
new file mode 100644
index 00000000..e80856bb
--- /dev/null
+++ b/src/VBox/Main/include/Matching.h
@@ -0,0 +1,540 @@
+/* $Id: Matching.h $ */
+/** @file
+ * Declaration of template classes that provide simple API to
+ * do matching between values and value filters constructed from strings.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_Matching_h
+#define MAIN_INCLUDED_Matching_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/com/string.h>
+
+#include <list>
+#include <limits>
+#include <algorithm>
+
+// min and max don't allow us to use numeric_limits::min() and max()
+#if defined (_MSC_VER)
+#undef min
+#undef max
+#endif
+
+namespace matching
+{
+
+using namespace std;
+using namespace com;
+
+class ParsedFilter_base
+{
+public:
+
+ ParsedFilter_base() : mValid (false), mNull (true), mErrorPosition (0) {};
+
+ /**
+ * Returns @c true if the filter is valid, @c false otherwise.
+ */
+ bool isValid() const { return mNull || mValid; }
+ bool isNull() const { return mNull; }
+
+ /**
+ * Returns the error position from the beginning of the filter
+ * string if #isValid() is false. Positions are zero-based.
+ */
+ size_t errorPosition() const { return mErrorPosition; }
+
+protected:
+
+ /**
+ * Returns @c true if current isNull() and isValid() values make further
+ * detailed matching meaningful, otherwise returns @c false.
+ * Must be called as a first method of every isMatch() implementation,
+ * so that isMatch() will immediately return @c false if isPreMatch() returns
+ * false.
+ */
+ bool isPreMatch() const
+ {
+ if (isNull() || !isValid())
+ return false;
+ return true;
+ }
+
+ bool mValid : 1;
+ bool mNull : 1;
+ size_t mErrorPosition;
+};
+
+class ParsedIntervalFilter_base : public ParsedFilter_base
+{
+public:
+ virtual ~ParsedIntervalFilter_base() { /* Make VC++ 14.2 happy */ }
+
+protected:
+
+ enum Mode { Single, Start, End };
+
+ union Widest
+ {
+ int64_t ll;
+ uint64_t ull;
+ };
+
+ struct Limits
+ {
+ Widest min;
+ Widest max;
+ };
+
+ ParsedIntervalFilter_base() {}
+
+ /**
+ * Called by #parse when a value token is encountered.
+ * This method can modify mNull, mValid and mErrorPosition when
+ * appropriate. Parsing stops if mValid is false after this method
+ * returns (mErrorPosition most point to the error position in this case).
+ */
+ virtual void parseValue (const char *aFilter, size_t aStart, size_t aEnd,
+ Mode aMode) = 0;
+
+ static void parse (const char *aFilter,
+ ParsedIntervalFilter_base *that);
+
+ static size_t parseValue (const char *aFilter, size_t aStart, size_t aEnd,
+ bool aIsSigned, const Limits &aLimits,
+ Widest &val);
+};
+
+/**
+ * Represents a parsed interval filter.
+ * The string format is:
+ * "int:(\<m\>|([\<m\>]-[\<n\>]))|(\<m\>|([\<m\>]-[\<n\>]))+"
+ * where \<m\> and \<n\> are numbers in the decimal, hex (0xNNN) or octal
+ * (0NNN) form, and \<m\> \< \<n\>. Spaces are allowed around \<m\> and \<n\>.
+ *
+ * @tparam T type of values to match. Must be a fundamental integer type.
+ */
+template <class T>
+class ParsedIntervalFilter : public ParsedIntervalFilter_base
+{
+ typedef ParsedIntervalFilter_base Base;
+ typedef numeric_limits <T> Lim;
+
+ typedef std::list <T> List;
+ typedef std::pair <T, T> Pair;
+ typedef std::list <Pair> PairList;
+
+public:
+
+ ParsedIntervalFilter() {}
+
+ ParsedIntervalFilter (const Bstr &aFilter) { Base::parse (Utf8Str (aFilter), this); }
+
+ ParsedIntervalFilter &operator= (const Bstr &aFilter)
+ {
+ mValues.clear();
+ mIntervals.clear();
+ Base::parse (Utf8Str (aFilter), this);
+ return *this;
+ }
+
+ bool isMatch (const T &aValue) const
+ {
+ if (!isPreMatch())
+ return false;
+
+ {
+ typename List::const_iterator it =
+ std::find (mValues.begin(), mValues.end(), aValue);
+ if (it != mValues.end())
+ return true;
+ }
+
+ for (typename PairList::const_iterator it = mIntervals.begin();
+ it != mIntervals.end(); ++ it)
+ {
+ if ((*it).first <= aValue &&
+ aValue <= (*it).second)
+ return true;
+ }
+
+ return false;
+ }
+
+protected:
+
+ struct Limits : public Base::Limits
+ {
+ Limits()
+ {
+ if (Lim::is_signed)
+ {
+ min.ll = (int64_t) Lim::min();
+ max.ll = (int64_t) Lim::max();
+ }
+ else
+ {
+ min.ull = (uint64_t) Lim::min();
+ max.ull = (uint64_t) Lim::max();
+ }
+ }
+
+ static T toValue (const Widest &aWidest)
+ {
+ if (Lim::is_signed)
+ return (T) aWidest.ll;
+ else
+ return (T) aWidest.ull;
+ }
+ };
+
+ virtual void parseValue (const char *aFilter, size_t aStart, size_t aEnd,
+ Mode aMode)
+ {
+ AssertReturn (Lim::is_integer, (void) 0);
+ AssertReturn (
+ (Lim::is_signed && Lim::digits <= numeric_limits <int64_t>::digits) ||
+ (!Lim::is_signed && Lim::digits <= numeric_limits <uint64_t>::digits),
+ (void) 0);
+
+ Limits limits;
+ Widest val;
+ size_t parsed = aEnd;
+
+ if (aStart != aEnd)
+ parsed = Base::parseValue (aFilter, aStart, aEnd,
+ Lim::is_signed, limits, val);
+
+ if (parsed != aEnd)
+ {
+ mValid = false;
+ mErrorPosition = parsed;
+ return;
+ }
+
+ switch (aMode)
+ {
+ /// @todo (dmik): future optimizations:
+ // 1) join intervals when they overlap
+ // 2) ignore single values that are within any existing interval
+ case Base::Single:
+ {
+ if (aStart == aEnd)
+ {
+ // an empty string (contains only spaces after "int:")
+ mValid = false;
+ mErrorPosition = aEnd;
+ AssertReturn (!mValues.size() && !mIntervals.size(), (void) 0);
+ break;
+ }
+ mValues.push_back (limits.toValue (val));
+ break;
+ }
+ case Base::Start:
+ {
+ // aStart == aEnd means smth. like "-[NNN]"
+ T m = aStart == aEnd ? limits.toValue (limits.min)
+ : limits.toValue (val);
+ mIntervals.push_back (Pair (m, m));
+ break;
+ }
+ case Base::End:
+ {
+ // aStart == aEnd means smth. like "[NNN]-"
+ T n = aStart == aEnd ? limits.toValue (limits.max)
+ : limits.toValue (val);
+ if (n < mIntervals.back().first)
+ {
+ // error at the beginning of N
+ mValid = false;
+ mErrorPosition = aStart;
+ break;
+ }
+ mIntervals.back().second = n;
+ break;
+ }
+ }
+ }
+
+ std::list <T> mValues;
+ std::list <std::pair <T, T> > mIntervals;
+};
+
+/**
+ * Represents a boolean filter.
+ * The string format is: "true|false|yes|no|1|0" or an empty string (any match).
+ */
+
+class ParsedBoolFilter : public ParsedFilter_base
+{
+public:
+
+ ParsedBoolFilter() : mValue (false), mValueAny (false) {}
+
+ ParsedBoolFilter (const Bstr &aFilter) { parse (aFilter); }
+
+ ParsedBoolFilter &operator= (const Bstr &aFilter)
+ {
+ parse (aFilter);
+ return *this;
+ }
+
+ bool isMatch (const bool aValue) const
+ {
+ if (!isPreMatch())
+ return false;
+
+ return mValueAny || mValue == aValue;
+ }
+
+ bool isMatch (const BOOL aValue) const
+ {
+ return isMatch (bool (aValue == TRUE));
+ }
+
+private:
+
+ void parse (const Bstr &aFilter);
+
+ bool mValue : 1;
+ bool mValueAny : 1;
+};
+
+class ParsedRegexpFilter_base : public ParsedFilter_base
+{
+protected:
+
+ ParsedRegexpFilter_base (bool aDefIgnoreCase = false,
+ size_t aMinLen = 0, size_t aMaxLen = 0)
+ : mIgnoreCase (aDefIgnoreCase)
+ , mMinLen (aMinLen)
+ , mMaxLen (aMaxLen)
+ {}
+
+ ParsedRegexpFilter_base (const Bstr &aFilter, bool aDefIgnoreCase = false,
+ size_t aMinLen = 0, size_t aMaxLen = 0)
+ : mIgnoreCase (aDefIgnoreCase)
+ , mMinLen (aMinLen)
+ , mMaxLen (aMaxLen)
+ {
+ parse (aFilter);
+ }
+
+ ParsedRegexpFilter_base &operator= (const Bstr &aFilter)
+ {
+ parse (aFilter);
+ return *this;
+ }
+
+ bool isMatch (const Bstr &aValue) const;
+
+private:
+
+ void parse (const Bstr &aFilter);
+
+ bool mIgnoreCase : 1;
+
+ size_t mMinLen;
+ size_t mMaxLen;
+
+ Bstr mSimple;
+};
+
+/**
+ * Represents a parsed regexp filter.
+ *
+ * The string format is: "rx:\<regexp\>" or "\<string\>"
+ * where \<regexp\> is a valid regexp and \<string\> is the exact match.
+ *
+ * @tparam Conv
+ * class that must define a public static function
+ * <tt>Bstr toBstr (T aValue)</tt>, where T is the
+ * type of values that should be accepted by #isMatch().
+ * This function is used to get the string representation of T
+ * for regexp matching.
+ * @tparam aIgnoreCase
+ * true if the case insensitive comparison should be done by default
+ * and false otherwise
+ * @tparam aMinLen
+ * minimum string length, or 0 if not limited.
+ * Used only when the filter string represents the exact match.
+ * @tparam aMaxLen
+ * maximum string length, or 0 if not limited.
+ * Used only when the filter string represents the exact match.
+ */
+template <class Conv, bool aIgnoreCase, size_t aMinLen = 0, size_t aMaxLen = 0>
+class ParsedRegexpFilter : public ParsedRegexpFilter_base
+{
+public:
+
+ enum { IgnoreCase = aIgnoreCase, MinLen = aMinLen, MaxLen = aMaxLen };
+
+ ParsedRegexpFilter() : ParsedRegexpFilter_base (IgnoreCase, MinLen, MaxLen) {}
+
+ ParsedRegexpFilter (const Bstr &aFilter)
+ : ParsedRegexpFilter_base (aFilter, IgnoreCase, MinLen, MaxLen) {}
+
+ ParsedRegexpFilter &operator= (const Bstr &aFilter)
+ {
+ ParsedRegexpFilter_base::operator= (aFilter);
+ return *this;
+ }
+
+ template <class T>
+ bool isMatch (const T &aValue) const
+ {
+ if (!this->isPreMatch())
+ return false;
+
+ return ParsedRegexpFilter_base::isMatch (Conv::toBstr (aValue));
+ }
+
+protected:
+};
+
+/**
+ * Joins two filters into one.
+ * Only one filter is active (i.e. used for matching or for error reporting)
+ * at any given time. The active filter is chosen every time when a new
+ * filter string is assigned to an instance of this class -- the filter
+ * for which isNull() = false after parsing the string becomes the active
+ * one (F1 is tried first).
+ *
+ * Both filters must have <tt>bool isMatch(const T&)</tt> methods where T is
+ * the same type as used in #isMatch().
+ *
+ * @tparam F1 first filter class
+ * @tparam F2 second filter class
+ */
+template <class F1, class F2>
+class TwoParsedFilters
+{
+public:
+
+ TwoParsedFilters() {}
+
+ TwoParsedFilters (const Bstr &aFilter)
+ {
+ mFilter1 = aFilter;
+ if (mFilter1.isNull())
+ mFilter2 = aFilter;
+ }
+
+ TwoParsedFilters &operator= (const Bstr &aFilter)
+ {
+ mFilter1 = aFilter;
+ if (mFilter1.isNull())
+ mFilter2 = aFilter;
+ else
+ mFilter2 = F2(); // reset to null
+ return *this;
+ }
+
+ template <class T>
+ bool isMatch (const T &aValue) const
+ {
+ return mFilter1.isMatch (aValue) || mFilter2.isMatch (aValue);
+ }
+
+ bool isValid() const { return isNull() || (mFilter1.isValid() && mFilter2.isValid()); }
+
+ bool isNull() const { return mFilter1.isNull() && mFilter2.isNull(); }
+
+ size_t errorPosition() const
+ {
+ return !mFilter1.isValid() ? mFilter1.errorPosition() :
+ !mFilter2.isValid() ? mFilter2.errorPosition() : 0;
+ }
+
+ const F1 &first() const { return mFilter1; }
+ const F2 &second() const { return mFilter2; }
+
+private:
+
+ F1 mFilter1;
+ F2 mFilter2;
+};
+
+/**
+ * Inherits from the given parsed filter class and keeps the string used to
+ * construct the filter as a member.
+ *
+ * @tparam F parsed filter class
+ */
+template <class F>
+class Matchable : public F
+{
+public:
+
+ Matchable() {}
+
+ /**
+ * Creates a new parsed filter from the given filter string.
+ * If the string format is invalid, #isValid() will return false.
+ */
+ Matchable (const Bstr &aString)
+ : F (aString), mString (aString) {}
+
+ Matchable (CBSTR aString)
+ : F (Bstr (aString)), mString (aString) {}
+
+ /**
+ * Assigns a new filter string to this object and recreates the parser.
+ * If the string format is invalid, #isValid() will return false.
+ */
+ Matchable &operator= (const Bstr &aString)
+ {
+ F::operator= (aString);
+ mString = aString;
+ return *this;
+ }
+
+ Matchable &operator= (CBSTR aString)
+ {
+ F::operator= (Bstr (aString));
+ mString = aString;
+ return *this;
+ }
+
+ /**
+ * Returns the filter string allowing to use the instance where
+ * Str can be used.
+ */
+ operator const Bstr&() const { return mString; }
+
+ /** Returns the filter string */
+ const Bstr& string() const { return mString; }
+
+private:
+
+ Bstr mString;
+};
+
+} /* namespace matching */
+
+#endif /* !MAIN_INCLUDED_Matching_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/MediumAttachmentImpl.h b/src/VBox/Main/include/MediumAttachmentImpl.h
new file mode 100644
index 00000000..45249a12
--- /dev/null
+++ b/src/VBox/Main/include/MediumAttachmentImpl.h
@@ -0,0 +1,146 @@
+/* $Id: MediumAttachmentImpl.h $ */
+/** @file
+ *
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_MediumAttachmentImpl_h
+#define MAIN_INCLUDED_MediumAttachmentImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "MediumAttachmentWrap.h"
+
+class ATL_NO_VTABLE MediumAttachment :
+ public MediumAttachmentWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(MediumAttachment)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aParent,
+ Medium *aMedium,
+ const Utf8Str &aControllerName,
+ LONG aPort,
+ LONG aDevice,
+ DeviceType_T aType,
+ bool aImplicit,
+ bool aPassthrough,
+ bool aTempEject,
+ bool aNonRotational,
+ bool aDiscard,
+ bool aHotPluggable,
+ const Utf8Str &strBandwidthGroup);
+ HRESULT initCopy(Machine *aParent, MediumAttachment *aThat);
+ void uninit();
+
+ // public internal methods
+ void i_rollback();
+ void i_commit();
+
+ // unsafe public methods for internal purposes only (ensure there is
+ // a caller and a read lock before calling them!)
+ bool i_isImplicit() const;
+ void i_setImplicit(bool aImplicit);
+
+ const ComObjPtr<Medium>& i_getMedium() const;
+ const Utf8Str &i_getControllerName() const;
+ LONG i_getPort() const;
+ LONG i_getDevice() const;
+ DeviceType_T i_getType() const;
+ bool i_getPassthrough() const;
+ bool i_getTempEject() const;
+ bool i_getNonRotational() const;
+ bool i_getDiscard() const;
+ Utf8Str& i_getBandwidthGroup() const;
+ bool i_getHotPluggable() const;
+
+ bool i_matches(const Utf8Str &aControllerName, LONG aPort, LONG aDevice);
+
+ /** Must be called from under this object's write lock. */
+ void i_updateName(const Utf8Str &aName);
+
+ /** Must be called from under this object's write lock. */
+ void i_updateMedium(const ComObjPtr<Medium> &aMedium);
+
+ /** Must be called from under this object's write lock. */
+ void i_updatePassthrough(bool aPassthrough);
+
+ /** Must be called from under this object's write lock. */
+ void i_updateTempEject(bool aTempEject);
+
+ /** Must be called from under this object's write lock. */
+ void i_updateNonRotational(bool aNonRotational);
+
+ /** Must be called from under this object's write lock. */
+ void i_updateDiscard(bool aDiscard);
+
+ /** Must be called from under this object's write lock. */
+ void i_updateEjected();
+
+ /** Must be called from under this object's write lock. */
+ void i_updateBandwidthGroup(const Utf8Str &aBandwidthGroup);
+
+ void i_updateParentMachine(Machine * const pMachine);
+
+ /** Must be called from under this object's write lock. */
+ void i_updateHotPluggable(bool aHotPluggable);
+
+ /** Construct a unique and somewhat descriptive name for logging. */
+ void i_updateLogName(void);
+
+ /** Get a unique and somewhat descriptive name for logging. */
+ const char *i_getLogName(void) const { return mLogName.c_str(); }
+
+private:
+
+ // Wrapped IMediumAttachment properties
+ HRESULT getMachine(ComPtr<IMachine> &aMachine);
+ HRESULT getMedium(ComPtr<IMedium> &aHardDisk);
+ HRESULT getController(com::Utf8Str &aController);
+ HRESULT getPort(LONG *aPort);
+ HRESULT getDevice(LONG *aDevice);
+ HRESULT getType(DeviceType_T *aType);
+ HRESULT getPassthrough(BOOL *aPassthrough);
+ HRESULT getTemporaryEject(BOOL *aTemporaryEject);
+ HRESULT getIsEjected(BOOL *aEjected);
+ HRESULT getDiscard(BOOL *aDiscard);
+ HRESULT getNonRotational(BOOL *aNonRotational);
+ HRESULT getBandwidthGroup(ComPtr<IBandwidthGroup> &aBandwidthGroup);
+ HRESULT getHotPluggable(BOOL *aHotPluggable);
+
+ struct Data;
+ Data *m;
+
+ Utf8Str mLogName; /**< For logging purposes */
+};
+
+#endif /* !MAIN_INCLUDED_MediumAttachmentImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/MediumFormatImpl.h b/src/VBox/Main/include/MediumFormatImpl.h
new file mode 100644
index 00000000..c23a7ae9
--- /dev/null
+++ b/src/VBox/Main/include/MediumFormatImpl.h
@@ -0,0 +1,125 @@
+/* $Id: MediumFormatImpl.h $ */
+/** @file
+ * MediumFormat COM class implementation
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_MediumFormatImpl_h
+#define MAIN_INCLUDED_MediumFormatImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "MediumFormatWrap.h"
+
+
+struct VDBACKENDINFO;
+
+/**
+ * The MediumFormat class represents the backend used to store medium data
+ * (IMediumFormat interface).
+ *
+ * @note Instances of this class are permanently caller-referenced by Medium
+ * objects (through addCaller()) so that an attempt to uninitialize or delete
+ * them before all Medium objects are uninitialized will produce an endless
+ * wait!
+ */
+class ATL_NO_VTABLE MediumFormat :
+ public MediumFormatWrap
+{
+public:
+
+ struct Property
+ {
+ Utf8Str strName;
+ Utf8Str strDescription;
+ DataType_T type;
+ ULONG flags;
+ Utf8Str strDefaultValue;
+ };
+
+ typedef std::vector<Property> PropertyArray;
+ typedef std::vector<com::Utf8Str> StrArray;
+
+ DECLARE_COMMON_CLASS_METHODS(MediumFormat)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(const VDBACKENDINFO *aVDInfo);
+ void uninit();
+
+ // public methods for internal purposes only
+ // (ensure there is a caller and a read lock before calling them!)
+
+ /** Const, no need to lock */
+ const Utf8Str &i_getId() const { return m.strId; }
+ /** Const, no need to lock */
+ const Utf8Str &i_getName() const { return m.strName; }
+ /** Const, no need to lock */
+ const StrArray &i_getFileExtensions() const { return m.maFileExtensions; }
+ /** Const, no need to lock */
+ MediumFormatCapabilities_T i_getCapabilities() const { return m.capabilities; }
+ /** Const, no need to lock */
+ const PropertyArray &i_getProperties() const { return m.maProperties; }
+
+private:
+
+ // wrapped IMediumFormat properties
+ HRESULT getId(com::Utf8Str &aId);
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getCapabilities(std::vector<MediumFormatCapabilities_T> &aCapabilities);
+
+ // wrapped IMediumFormat methods
+ HRESULT describeFileExtensions(std::vector<com::Utf8Str> &aExtensions,
+ std::vector<DeviceType_T> &aTypes);
+ HRESULT describeProperties(std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aDescriptions,
+ std::vector<DataType_T> &aTypes,
+ std::vector<ULONG> &aFlags,
+ std::vector<com::Utf8Str> &aDefaults);
+
+ // types
+ typedef std::vector<DeviceType_T> DeviceTypeArray;
+
+ // data
+ struct Data
+ {
+ Data() : capabilities((MediumFormatCapabilities_T)0) {}
+
+ const Utf8Str strId;
+ const Utf8Str strName;
+ const StrArray maFileExtensions;
+ const DeviceTypeArray maDeviceTypes;
+ const MediumFormatCapabilities_T capabilities;
+ const PropertyArray maProperties;
+ };
+
+ Data m;
+};
+
+#endif /* !MAIN_INCLUDED_MediumFormatImpl_h */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/MediumIOImpl.h b/src/VBox/Main/include/MediumIOImpl.h
new file mode 100644
index 00000000..220bc4c0
--- /dev/null
+++ b/src/VBox/Main/include/MediumIOImpl.h
@@ -0,0 +1,92 @@
+/* $Id: MediumIOImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation - MediumIO.
+ */
+
+/*
+ * Copyright (C) 2018-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_MediumIOImpl_h
+#define MAIN_INCLUDED_MediumIOImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "MediumIOWrap.h"
+#include "VirtualBoxBase.h"
+#include "AutoCaller.h"
+
+class ATL_NO_VTABLE MediumIO :
+ public MediumIOWrap
+{
+public:
+ /** @name Dummy/standard constructors and destructors.
+ * @{ */
+ DECLARE_COMMON_CLASS_METHODS(MediumIO)
+ HRESULT FinalConstruct();
+ void FinalRelease();
+ /** @} */
+
+ /** @name Initializer & uninitializer.
+ * @{ */
+ HRESULT initForMedium(Medium *pMedium, VirtualBox *pVirtualBox, bool fWritable,
+ com::Utf8Str const &rStrKeyId, com::Utf8Str const &rStrPassword);
+ void uninit();
+ /** @} */
+
+private:
+ /** @name Wrapped IMediumIO properties
+ * @{ */
+ HRESULT getMedium(ComPtr<IMedium> &a_rPtrMedium);
+ HRESULT getWritable(BOOL *a_fWritable);
+ HRESULT getExplorer(ComPtr<IVFSExplorer> &a_rPtrExplorer);
+ /** @} */
+
+ /** @name Wrapped IMediumIO methods
+ * @{ */
+ HRESULT read(LONG64 a_off, ULONG a_cbRead, std::vector<BYTE> &a_rData);
+ HRESULT write(LONG64 a_off, const std::vector<BYTE> &a_rData, ULONG *a_pcbWritten);
+ HRESULT formatFAT(BOOL a_fQuick);
+ HRESULT initializePartitionTable(PartitionTableType_T a_enmFormat, BOOL a_fWholeDiskInOneEntry);
+ HRESULT convertToStream(const com::Utf8Str &aFormat,
+ const std::vector<MediumVariant_T> &aVariant,
+ ULONG aBufferSize,
+ ComPtr<IDataStream> &aStream,
+ ComPtr<IProgress> &aProgress);
+ HRESULT close();
+ /** @} */
+
+ /** @name Internal workers.
+ * @{ */
+ void i_close();
+ /** @} */
+
+ struct Data;
+ Data *m;
+
+ class StreamTask;
+ friend class StreamTask;
+};
+
+#endif /* !MAIN_INCLUDED_MediumIOImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
+
diff --git a/src/VBox/Main/include/MediumImpl.h b/src/VBox/Main/include/MediumImpl.h
new file mode 100644
index 00000000..1b550277
--- /dev/null
+++ b/src/VBox/Main/include/MediumImpl.h
@@ -0,0 +1,473 @@
+/* $Id: MediumImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_MediumImpl_h
+#define MAIN_INCLUDED_MediumImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/vd.h>
+#include "MediumWrap.h"
+#include "VirtualBoxBase.h"
+#include "AutoCaller.h"
+#include "SecretKeyStore.h"
+class Progress;
+class MediumFormat;
+class MediumLockList;
+struct MediumCryptoFilterSettings;
+
+namespace settings
+{
+ struct Medium;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Medium component class for all media types.
+ */
+class ATL_NO_VTABLE Medium :
+ public MediumWrap
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(Medium)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ enum HDDOpenMode { OpenReadWrite, OpenReadOnly };
+ // have to use a special enum for the overloaded init() below;
+ // can't use AccessMode_T from XIDL because that's mapped to an int
+ // and would be ambiguous
+
+ // public initializer/uninitializer for internal purposes only
+
+ // initializer to create empty medium (VirtualBox::CreateMedium())
+ HRESULT init(VirtualBox *aVirtualBox,
+ const Utf8Str &aFormat,
+ const Utf8Str &aLocation,
+ const Guid &uuidMachineRegistry,
+ const DeviceType_T aDeviceType);
+
+ // initializer for opening existing media
+ // (VirtualBox::OpenMedium(); Machine::AttachDevice())
+ HRESULT init(VirtualBox *aVirtualBox,
+ const Utf8Str &aLocation,
+ HDDOpenMode enOpenMode,
+ bool fForceNewUuid,
+ DeviceType_T aDeviceType);
+
+ // initializer used when loading settings
+ HRESULT initOne(Medium *aParent,
+ DeviceType_T aDeviceType,
+ const Guid &uuidMachineRegistry,
+ const Utf8Str &strMachineFolder,
+ const settings::Medium &data);
+ static HRESULT initFromSettings(VirtualBox *aVirtualBox,
+ DeviceType_T aDeviceType,
+ const Guid &uuidMachineRegistry,
+ const Utf8Str &strMachineFolder,
+ const settings::Medium &data,
+ AutoWriteLock &mediaTreeLock,
+ std::list<std::pair<Guid, DeviceType_T> > &uIdsForNotify);
+
+ // initializer for host floppy/DVD
+ HRESULT init(VirtualBox *aVirtualBox,
+ DeviceType_T aDeviceType,
+ const Utf8Str &aLocation,
+ const Utf8Str &aDescription = Utf8Str::Empty);
+
+ void uninit();
+
+ void i_deparent();
+ void i_setParent(const ComObjPtr<Medium> &pParent);
+
+ // unsafe methods for internal purposes only (ensure there is
+ // a caller and a read lock before calling them!)
+ const ComObjPtr<Medium>& i_getParent() const;
+ const MediaList& i_getChildren() const;
+
+ const Guid& i_getId() const;
+ MediumState_T i_getState() const;
+ MediumVariant_T i_getVariant() const;
+ bool i_isHostDrive() const;
+ const Utf8Str& i_getLocationFull() const;
+ const Utf8Str& i_getFormat() const;
+ const ComObjPtr<MediumFormat> & i_getMediumFormat() const;
+ bool i_isMediumFormatFile() const;
+ uint64_t i_getSize() const;
+ uint64_t i_getLogicalSize() const;
+ DeviceType_T i_getDeviceType() const;
+ MediumType_T i_getType() const;
+ Utf8Str i_getName();
+
+ /* handles caller/locking itself */
+ bool i_addRegistry(const Guid &id);
+ bool i_addRegistryNoCallerCheck(const Guid &id);
+ /* handles caller/locking itself, caller is responsible for tree lock */
+ bool i_addRegistryAll(const Guid &id);
+ /* handles caller/locking itself */
+ bool i_removeRegistry(const Guid& id);
+ /* handles caller/locking itself, caller is responsible for tree lock */
+ bool i_removeRegistryAll(const Guid& id);
+ bool i_isInRegistry(const Guid& id);
+ bool i_getFirstRegistryMachineId(Guid &uuid) const;
+ void i_markRegistriesModified();
+
+ HRESULT i_setPropertyDirect(const Utf8Str &aName, const Utf8Str &aValue);
+
+ HRESULT i_addBackReference(const Guid &aMachineId,
+ const Guid &aSnapshotId = Guid::Empty);
+ HRESULT i_removeBackReference(const Guid &aMachineId,
+ const Guid &aSnapshotId = Guid::Empty);
+
+
+ const Guid* i_getFirstMachineBackrefId() const;
+ const Guid* i_getAnyMachineBackref(const Guid &aId) const;
+ const Guid* i_getFirstMachineBackrefSnapshotId() const;
+ size_t i_getMachineBackRefCount() const;
+
+#ifdef DEBUG
+ void i_dumpBackRefs();
+#endif
+
+ HRESULT i_updatePath(const Utf8Str &strOldPath, const Utf8Str &strNewPath);
+
+ /* handles caller/locking itself */
+ ComObjPtr<Medium> i_getBase(uint32_t *aLevel = NULL);
+ /* handles caller/locking itself */
+ uint32_t i_getDepth();
+
+ bool i_isReadOnly();
+ void i_updateId(const Guid &id);
+
+ void i_saveSettingsOne(settings::Medium &data,
+ const Utf8Str &strHardDiskFolder);
+ HRESULT i_saveSettings(settings::Medium &data,
+ const Utf8Str &strHardDiskFolder);
+
+ HRESULT i_createMediumLockList(bool fFailIfInaccessible,
+ Medium *pToLock,
+ bool fMediumLockWriteAll,
+ Medium *pToBeParent,
+ MediumLockList &mediumLockList);
+
+ HRESULT i_createDiffStorage(ComObjPtr<Medium> &aTarget,
+ MediumVariant_T aVariant,
+ MediumLockList *pMediumLockList,
+ ComObjPtr<Progress> *aProgress,
+ bool aWait,
+ bool aNotify);
+ Utf8Str i_getPreferredDiffFormat();
+ MediumVariant_T i_getPreferredDiffVariant();
+
+ HRESULT i_close(AutoCaller &autoCaller);
+ HRESULT i_unlockRead(MediumState_T *aState);
+ HRESULT i_unlockWrite(MediumState_T *aState);
+ HRESULT i_deleteStorage(ComObjPtr<Progress> *aProgress, bool aWait, bool aNotify);
+ HRESULT i_markForDeletion();
+ HRESULT i_unmarkForDeletion();
+ HRESULT i_markLockedForDeletion();
+ HRESULT i_unmarkLockedForDeletion();
+
+ HRESULT i_queryPreferredMergeDirection(const ComObjPtr<Medium> &pOther,
+ bool &fMergeForward);
+
+ HRESULT i_prepareMergeTo(const ComObjPtr<Medium> &pTarget,
+ const Guid *aMachineId,
+ const Guid *aSnapshotId,
+ bool fLockMedia,
+ bool &fMergeForward,
+ ComObjPtr<Medium> &pParentForTarget,
+ MediumLockList * &aChildrenToReparent,
+ MediumLockList * &aMediumLockList);
+ HRESULT i_mergeTo(const ComObjPtr<Medium> &pTarget,
+ bool fMergeForward,
+ const ComObjPtr<Medium> &pParentForTarget,
+ MediumLockList *aChildrenToReparent,
+ MediumLockList *aMediumLockList,
+ ComObjPtr<Progress> *aProgress,
+ bool aWait,
+ bool aNotify);
+ void i_cancelMergeTo(MediumLockList *aChildrenToReparent,
+ MediumLockList *aMediumLockList);
+
+ HRESULT i_resize(uint64_t aLogicalSize,
+ MediumLockList *aMediumLockList,
+ ComObjPtr<Progress> *aProgress,
+ bool aWait,
+ bool aNotify);
+
+ HRESULT i_fixParentUuidOfChildren(MediumLockList *pChildrenToReparent);
+
+ HRESULT i_addRawToFss(const char *aFilename, SecretKeyStore *pKeyStore, RTVFSFSSTREAM hVfsFssDst,
+ const ComObjPtr<Progress> &aProgress, bool fSparse);
+
+ HRESULT i_exportFile(const char *aFilename,
+ const ComObjPtr<MediumFormat> &aFormat,
+ MediumVariant_T aVariant,
+ SecretKeyStore *pKeyStore,
+ RTVFSIOSTREAM hVfsIosDst,
+ const ComObjPtr<Progress> &aProgress);
+ HRESULT i_importFile(const char *aFilename,
+ const ComObjPtr<MediumFormat> &aFormat,
+ MediumVariant_T aVariant,
+ RTVFSIOSTREAM hVfsIosSrc,
+ const ComObjPtr<Medium> &aParent,
+ const ComObjPtr<Progress> &aProgress,
+ bool aNotify);
+
+ HRESULT i_cloneToEx(const ComObjPtr<Medium> &aTarget, MediumVariant_T aVariant,
+ const ComObjPtr<Medium> &aParent, IProgress **aProgress,
+ uint32_t idxSrcImageSame, uint32_t idxDstImageSame, bool aNotify);
+
+ const Utf8Str& i_getKeyId();
+
+ HRESULT i_openForIO(bool fWritable, SecretKeyStore *pKeyStore, PVDISK *ppHdd, MediumLockList *pMediumLockList,
+ struct MediumCryptoFilterSettings *pCryptoSettings);
+
+private:
+
+ // wrapped IMedium properties
+ HRESULT getId(com::Guid &aId);
+ HRESULT getDescription(AutoCaller &autoCaller, com::Utf8Str &aDescription);
+ HRESULT setDescription(AutoCaller &autoCaller, const com::Utf8Str &aDescription);
+ HRESULT getState(MediumState_T *aState);
+ HRESULT getVariant(std::vector<MediumVariant_T> &aVariant);
+ HRESULT getLocation(com::Utf8Str &aLocation);
+ HRESULT setLocation(const com::Utf8Str &aLocation);
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getDeviceType(DeviceType_T *aDeviceType);
+ HRESULT getHostDrive(BOOL *aHostDrive);
+ HRESULT getSize(LONG64 *aSize);
+ HRESULT getFormat(com::Utf8Str &aFormat);
+ HRESULT getMediumFormat(ComPtr<IMediumFormat> &aMediumFormat);
+ HRESULT getType(AutoCaller &autoCaller, MediumType_T *aType);
+ HRESULT setType(AutoCaller &autoCaller, MediumType_T aType);
+ HRESULT getAllowedTypes(std::vector<MediumType_T> &aAllowedTypes);
+ HRESULT getParent(AutoCaller &autoCaller, ComPtr<IMedium> &aParent);
+ HRESULT getChildren(AutoCaller &autoCaller, std::vector<ComPtr<IMedium> > &aChildren);
+ HRESULT getBase(AutoCaller &autoCaller, ComPtr<IMedium> &aBase);
+ HRESULT getReadOnly(AutoCaller &autoCaller, BOOL *aReadOnly);
+ HRESULT getLogicalSize(LONG64 *aLogicalSize);
+ HRESULT getAutoReset(BOOL *aAutoReset);
+ HRESULT setAutoReset(BOOL aAutoReset);
+ HRESULT getLastAccessError(com::Utf8Str &aLastAccessError);
+ HRESULT getMachineIds(std::vector<com::Guid> &aMachineIds);
+
+ // wrapped IMedium methods
+ HRESULT setIds(AutoCaller &aAutoCaller,
+ BOOL aSetImageId,
+ const com::Guid &aImageId,
+ BOOL aSetParentId,
+ const com::Guid &aParentId);
+ HRESULT refreshState(AutoCaller &aAutoCaller,
+ MediumState_T *aState);
+ HRESULT getSnapshotIds(const com::Guid &aMachineId,
+ std::vector<com::Guid> &aSnapshotIds);
+ HRESULT lockRead(ComPtr<IToken> &aToken);
+ HRESULT lockWrite(ComPtr<IToken> &aToken);
+ HRESULT close(AutoCaller &aAutoCaller);
+ HRESULT getProperty(const com::Utf8Str &aName,
+ com::Utf8Str &aValue);
+ HRESULT setProperty(const com::Utf8Str &aName,
+ const com::Utf8Str &aValue);
+ HRESULT getProperties(const com::Utf8Str &aNames,
+ std::vector<com::Utf8Str> &aReturnNames,
+ std::vector<com::Utf8Str> &aReturnValues);
+ HRESULT setProperties(const std::vector<com::Utf8Str> &aNames,
+ const std::vector<com::Utf8Str> &aValues);
+ HRESULT createBaseStorage(LONG64 aLogicalSize,
+ const std::vector<MediumVariant_T> &aVariant,
+ ComPtr<IProgress> &aProgress);
+ HRESULT deleteStorage(ComPtr<IProgress> &aProgress);
+ HRESULT createDiffStorage(AutoCaller &autoCaller,
+ const ComPtr<IMedium> &aTarget,
+ const std::vector<MediumVariant_T> &aVariant,
+ ComPtr<IProgress> &aProgress);
+ HRESULT mergeTo(const ComPtr<IMedium> &aTarget,
+ ComPtr<IProgress> &aProgress);
+ HRESULT cloneTo(const ComPtr<IMedium> &aTarget,
+ const std::vector<MediumVariant_T> &aVariant,
+ const ComPtr<IMedium> &aParent,
+ ComPtr<IProgress> &aProgress);
+ HRESULT resizeAndCloneTo(const ComPtr<IMedium> &aTarget,
+ LONG64 aLogicalSize,
+ const std::vector<MediumVariant_T> &aVariant,
+ const ComPtr<IMedium> &aParent,
+ ComPtr<IProgress> &aProgress);
+ HRESULT cloneToBase(const ComPtr<IMedium> &aTarget,
+ const std::vector<MediumVariant_T> &aVariant,
+ ComPtr<IProgress> &aProgress);
+ HRESULT moveTo(AutoCaller &autoCaller,
+ const com::Utf8Str &aLocation,
+ ComPtr<IProgress> &aProgress);
+ HRESULT compact(ComPtr<IProgress> &aProgress);
+ HRESULT resize(LONG64 aLogicalSize,
+ ComPtr<IProgress> &aProgress);
+ HRESULT reset(AutoCaller &autoCaller, ComPtr<IProgress> &aProgress);
+ HRESULT changeEncryption(const com::Utf8Str &aCurrentPassword, const com::Utf8Str &aCipher,
+ const com::Utf8Str &aNewPassword, const com::Utf8Str &aNewPasswordId,
+ ComPtr<IProgress> &aProgress);
+ HRESULT getEncryptionSettings(AutoCaller &autoCaller, com::Utf8Str &aCipher, com::Utf8Str &aPasswordId);
+ HRESULT checkEncryptionPassword(const com::Utf8Str &aPassword);
+ HRESULT openForIO(BOOL aWritable, com::Utf8Str const &aPassword, ComPtr<IMediumIO> &aMediumIO);
+
+ // Private internal nmethods
+ HRESULT i_queryInfo(bool fSetImageId, bool fSetParentId, AutoCaller &autoCaller);
+ HRESULT i_canClose();
+ HRESULT i_unregisterWithVirtualBox();
+ HRESULT i_setStateError();
+ HRESULT i_setLocation(const Utf8Str &aLocation, const Utf8Str &aFormat = Utf8Str::Empty);
+ HRESULT i_setFormat(const Utf8Str &aFormat);
+ VDTYPE i_convertDeviceType();
+ DeviceType_T i_convertToDeviceType(VDTYPE enmType);
+ Utf8Str i_vdError(int aVRC);
+
+ bool i_isPropertyForFilter(const com::Utf8Str &aName);
+
+ HRESULT i_getFilterProperties(std::vector<com::Utf8Str> &aReturnNames,
+ std::vector<com::Utf8Str> &aReturnValues);
+
+ HRESULT i_preparationForMoving(const Utf8Str &aLocation);
+ bool i_isMoveOperation(const ComObjPtr<Medium> &pTarget) const;
+ bool i_resetMoveOperationData();
+ Utf8Str i_getNewLocationForMoving() const;
+
+ static DECLCALLBACK(void) i_vdErrorCall(void *pvUser, int rc, RT_SRC_POS_DECL,
+ const char *pszFormat, va_list va);
+ static DECLCALLBACK(bool) i_vdConfigAreKeysValid(void *pvUser,
+ const char *pszzValid);
+ static DECLCALLBACK(int) i_vdConfigQuerySize(void *pvUser, const char *pszName,
+ size_t *pcbValue);
+ static DECLCALLBACK(int) i_vdConfigUpdate(void *pvUser, bool fCreate,
+ const char *pszName, const char *pszValue);
+
+ static DECLCALLBACK(int) i_vdConfigQuery(void *pvUser, const char *pszName,
+ char *pszValue, size_t cchValue);
+
+ static DECLCALLBACK(bool) i_vdCryptoConfigAreKeysValid(void *pvUser,
+ const char *pszzValid);
+ static DECLCALLBACK(int) i_vdCryptoConfigQuerySize(void *pvUser, const char *pszName,
+ size_t *pcbValue);
+ static DECLCALLBACK(int) i_vdCryptoConfigQuery(void *pvUser, const char *pszName,
+ char *pszValue, size_t cchValue);
+
+ static DECLCALLBACK(int) i_vdCryptoKeyRetain(void *pvUser, const char *pszId,
+ const uint8_t **ppbKey, size_t *pcbKey);
+ static DECLCALLBACK(int) i_vdCryptoKeyRelease(void *pvUser, const char *pszId);
+ static DECLCALLBACK(int) i_vdCryptoKeyStorePasswordRetain(void *pvUser, const char *pszId, const char **ppszPassword);
+ static DECLCALLBACK(int) i_vdCryptoKeyStorePasswordRelease(void *pvUser, const char *pszId);
+ static DECLCALLBACK(int) i_vdCryptoKeyStoreSave(void *pvUser, const void *pvKeyStore, size_t cbKeyStore);
+ static DECLCALLBACK(int) i_vdCryptoKeyStoreReturnParameters(void *pvUser, const char *pszCipher,
+ const uint8_t *pbDek, size_t cbDek);
+
+ class Task;
+ class CreateBaseTask;
+ class CreateDiffTask;
+ class CloneTask;
+ class MoveTask;
+ class CompactTask;
+ class ResizeTask;
+ class ResetTask;
+ class DeleteTask;
+ class MergeTask;
+ class ImportTask;
+ class EncryptTask;
+ friend class Task;
+ friend class CreateBaseTask;
+ friend class CreateDiffTask;
+ friend class CloneTask;
+ friend class MoveTask;
+ friend class CompactTask;
+ friend class ResizeTask;
+ friend class ResetTask;
+ friend class DeleteTask;
+ friend class MergeTask;
+ friend class ImportTask;
+ friend class EncryptTask;
+
+ HRESULT i_taskCreateBaseHandler(Medium::CreateBaseTask &task);
+ HRESULT i_taskCreateDiffHandler(Medium::CreateDiffTask &task);
+ HRESULT i_taskMergeHandler(Medium::MergeTask &task);
+ HRESULT i_taskCloneHandler(Medium::CloneTask &task);
+ HRESULT i_taskMoveHandler(Medium::MoveTask &task);
+ HRESULT i_taskDeleteHandler(Medium::DeleteTask &task);
+ HRESULT i_taskResetHandler(Medium::ResetTask &task);
+ HRESULT i_taskCompactHandler(Medium::CompactTask &task);
+ HRESULT i_taskResizeHandler(Medium::ResizeTask &task);
+ HRESULT i_taskImportHandler(Medium::ImportTask &task);
+ HRESULT i_taskEncryptHandler(Medium::EncryptTask &task);
+
+ void i_taskEncryptSettingsSetup(struct MediumCryptoFilterSettings *pSettings, const char *pszCipher,
+ const char *pszKeyStore, const char *pszPassword,
+ bool fCreateKeyStore);
+
+ struct Data; // opaque data struct, defined in MediumImpl.cpp
+ Data *m;
+};
+
+
+/**
+ * Settings for a crypto filter instance.
+ */
+struct MediumCryptoFilterSettings
+{
+ MediumCryptoFilterSettings()
+ : fCreateKeyStore(false),
+ pszPassword(NULL),
+ pszKeyStore(NULL),
+ pszKeyStoreLoad(NULL),
+ pbDek(NULL),
+ cbDek(0),
+ pszCipher(NULL),
+ pszCipherReturned(NULL)
+ { }
+
+ bool fCreateKeyStore;
+ const char *pszPassword;
+ char *pszKeyStore;
+ const char *pszKeyStoreLoad;
+
+ const uint8_t *pbDek;
+ size_t cbDek;
+ const char *pszCipher;
+
+ /** The cipher returned by the crypto filter. */
+ char *pszCipherReturned;
+
+ PVDINTERFACE vdFilterIfaces;
+
+ VDINTERFACECONFIG vdIfCfg;
+ VDINTERFACECRYPTO vdIfCrypto;
+};
+
+
+
+#endif /* !MAIN_INCLUDED_MediumImpl_h */
+
diff --git a/src/VBox/Main/include/MediumLock.h b/src/VBox/Main/include/MediumLock.h
new file mode 100644
index 00000000..ad02501a
--- /dev/null
+++ b/src/VBox/Main/include/MediumLock.h
@@ -0,0 +1,351 @@
+/* $Id: MediumLock.h $ */
+
+/** @file
+ *
+ * VirtualBox medium object lock collections
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_MediumLock_h
+#define MAIN_INCLUDED_MediumLock_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/* interface definitions */
+#include "VBox/com/VirtualBox.h"
+#include "VirtualBoxBase.h"
+#include "AutoCaller.h"
+
+#include <iprt/types.h>
+
+#include <list>
+#include <map>
+
+class Medium;
+class MediumAttachment;
+
+/**
+ * Single entry for medium lock lists. Has a medium object reference,
+ * information about what kind of lock should be taken, and if it is
+ * locked right now.
+ */
+class MediumLock
+{
+public:
+ /**
+ * Default medium lock constructor.
+ */
+ MediumLock();
+
+ /**
+ * Default medium lock destructor.
+ */
+ ~MediumLock();
+
+ /**
+ * Create a new medium lock description
+ *
+ * @param aMedium Reference to medium object
+ * @param aLockWrite @c true means a write lock should be taken
+ */
+ MediumLock(const ComObjPtr<Medium> &aMedium, bool aLockWrite);
+
+ /**
+ * Copy constructor. Needed because we contain an AutoCaller
+ * instance which is deliberately not copyable. The copy is not
+ * marked as locked, so be careful.
+ *
+ * @param aMediumLock Reference to source object.
+ */
+ MediumLock(const MediumLock &aMediumLock);
+
+ /**
+ * Update a medium lock description.
+ *
+ * @note May be used in locked state.
+ *
+ * @return COM status code
+ * @param aLockWrite @c true means a write lock should be taken
+ */
+ HRESULT UpdateLock(bool aLockWrite);
+
+ /**
+ * Get medium object reference.
+ */
+ const ComObjPtr<Medium> &GetMedium() const;
+
+ /**
+ * Get medium object lock request type.
+ */
+ bool GetLockRequest() const;
+
+ /**
+ * Check if this medium object has been locked by this MediumLock.
+ */
+ bool IsLocked() const;
+
+ /**
+ * Acquire a medium lock.
+ *
+ * @return COM status code
+ * @param aIgnoreLockedMedia If set ignore all media which is already
+ * locked in an incompatible way.
+ */
+ HRESULT Lock(bool aIgnoreLockedMedia = false);
+
+ /**
+ * Release a medium lock.
+ *
+ * @return COM status code
+ */
+ HRESULT Unlock();
+
+private:
+ ComObjPtr<Medium> mMedium;
+ ComPtr<IToken> mToken;
+ AutoCaller mMediumCaller;
+ bool mLockWrite;
+ bool mIsLocked;
+ /** Flag whether the medium was skipped when taking the locks.
+ * Only existing and accessible media objects need to be locked. */
+ bool mLockSkipped;
+};
+
+
+/**
+ * Medium lock list. Meant for storing the ordered locking information
+ * for a single medium chain.
+ */
+class MediumLockList
+{
+public:
+
+ /* Base list data type. */
+ typedef std::list<MediumLock> Base;
+
+ /**
+ * Default medium lock list constructor.
+ */
+ MediumLockList();
+
+ /**
+ * Default medium lock list destructor.
+ */
+ ~MediumLockList();
+
+ /**
+ * Checks if medium lock declaration list is empty.
+ *
+ * @return true if list is empty.
+ */
+ bool IsEmpty();
+
+ /**
+ * Add a new medium lock declaration to the end of the list.
+ *
+ * @note May be only used in unlocked state.
+ *
+ * @return COM status code
+ * @param aMedium Reference to medium object
+ * @param aLockWrite @c true means a write lock should be taken
+ */
+ HRESULT Append(const ComObjPtr<Medium> &aMedium, bool aLockWrite);
+
+ /**
+ * Add a new medium lock declaration to the beginning of the list.
+ *
+ * @note May be only used in unlocked state.
+ *
+ * @return COM status code
+ * @param aMedium Reference to medium object
+ * @param aLockWrite @c true means a write lock should be taken
+ */
+ HRESULT Prepend(const ComObjPtr<Medium> &aMedium, bool aLockWrite);
+
+ /**
+ * Update a medium lock declaration.
+ *
+ * @note May be used in locked state.
+ *
+ * @return COM status code
+ * @param aMedium Reference to medium object
+ * @param aLockWrite @c true means a write lock should be taken
+ */
+ HRESULT Update(const ComObjPtr<Medium> &aMedium, bool aLockWrite);
+
+ /**
+ * Remove a medium lock declaration and return an updated iterator.
+ *
+ * @note May be used in locked state.
+ *
+ * @return COM status code
+ * @param aIt Iterator for the element to remove
+ */
+ HRESULT RemoveByIterator(Base::iterator &aIt);
+
+ /**
+ * Clear all medium lock declarations.
+ *
+ * @note Implicitly unlocks all locks.
+ *
+ * @return COM status code
+ */
+ HRESULT Clear();
+
+ /**
+ * Get iterator begin() for base list.
+ */
+ Base::iterator GetBegin();
+
+ /**
+ * Get iterator end() for base list.
+ */
+ Base::iterator GetEnd();
+
+ /**
+ * Acquire all medium locks "atomically", i.e. all or nothing.
+ *
+ * @return COM status code
+ * @param aSkipOverLockedMedia If set ignore all media which is already
+ * locked for reading or writing. For callers
+ * which need to know which medium objects
+ * have been locked by this lock list you
+ * can iterate over the list and check the
+ * MediumLock state.
+ */
+ HRESULT Lock(bool aSkipOverLockedMedia = false);
+
+ /**
+ * Release all medium locks.
+ *
+ * @return COM status code
+ */
+ HRESULT Unlock();
+
+private:
+ Base mMediumLocks;
+ bool mIsLocked;
+};
+
+/**
+ * Medium lock list map. Meant for storing a collection of lock lists.
+ * The usual use case is creating such a map when locking all medium chains
+ * belonging to one VM, however that's not the limit. Be creative.
+ */
+class MediumLockListMap
+{
+public:
+
+ /**
+ * Default medium lock list map constructor.
+ */
+ MediumLockListMap();
+
+ /**
+ * Default medium lock list map destructor.
+ */
+ ~MediumLockListMap();
+
+ /**
+ * Checks if medium lock list map is empty.
+ *
+ * @return true if list is empty.
+ */
+ bool IsEmpty();
+
+ /**
+ * Insert a new medium lock list into the map.
+ *
+ * @note May be only used in unlocked state.
+ *
+ * @return COM status code
+ * @param aMediumAttachment Reference to medium attachment object, the key.
+ * @param aMediumLockList Reference to medium lock list object
+ */
+ HRESULT Insert(const ComObjPtr<MediumAttachment> &aMediumAttachment, MediumLockList *aMediumLockList);
+
+ /**
+ * Replace the medium lock list key by a different one.
+ *
+ * @note May be used in locked state.
+ *
+ * @return COM status code
+ * @param aMediumAttachmentOld Reference to medium attachment object.
+ * @param aMediumAttachmentNew Reference to medium attachment object.
+ */
+ HRESULT ReplaceKey(const ComObjPtr<MediumAttachment> &aMediumAttachmentOld, const ComObjPtr<MediumAttachment> &aMediumAttachmentNew);
+
+ /**
+ * Remove a medium lock list from the map. The list will get deleted.
+ *
+ * @note May be only used in unlocked state.
+ *
+ * @return COM status code
+ * @param aMediumAttachment Reference to medium attachment object, the key.
+ */
+ HRESULT Remove(const ComObjPtr<MediumAttachment> &aMediumAttachment);
+
+ /**
+ * Clear all medium lock declarations in this map.
+ *
+ * @note Implicitly unlocks all locks.
+ *
+ * @return COM status code
+ */
+ HRESULT Clear();
+
+ /**
+ * Get the medium lock list identified by the given key.
+ *
+ * @note May be used in locked state.
+ *
+ * @return COM status code
+ * @param aMediumAttachment Key for medium attachment object.
+ * @param aMediumLockList Out: medium attachment object reference.
+ */
+ HRESULT Get(const ComObjPtr<MediumAttachment> &aMediumAttachment, MediumLockList * &aMediumLockList);
+
+ /**
+ * Acquire all medium locks "atomically", i.e. all or nothing.
+ *
+ * @return COM status code
+ */
+ HRESULT Lock();
+
+ /**
+ * Release all medium locks.
+ *
+ * @return COM status code
+ */
+ HRESULT Unlock();
+
+private:
+ typedef std::map<ComObjPtr<MediumAttachment>, MediumLockList *> Base;
+ Base mMediumLocks;
+ bool mIsLocked;
+};
+
+#endif /* !MAIN_INCLUDED_MediumLock_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/MouseImpl.h b/src/VBox/Main/include/MouseImpl.h
new file mode 100644
index 00000000..f2b1439e
--- /dev/null
+++ b/src/VBox/Main/include/MouseImpl.h
@@ -0,0 +1,175 @@
+/* $Id: MouseImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_MouseImpl_h
+#define MAIN_INCLUDED_MouseImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "MouseWrap.h"
+#include "ConsoleImpl.h"
+#include "EventImpl.h"
+#include <VBox/vmm/pdmdrv.h>
+
+/** Maximum number of devices supported */
+enum { MOUSE_MAX_DEVICES = 4 };
+/** Mouse driver instance data. */
+typedef struct DRVMAINMOUSE DRVMAINMOUSE, *PDRVMAINMOUSE;
+
+class ATL_NO_VTABLE Mouse :
+ public MouseWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS (Mouse)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(ConsoleMouseInterface *parent);
+ void uninit();
+
+ static const PDMDRVREG DrvReg;
+
+ ConsoleMouseInterface *i_getParent() const
+ {
+ return mParent;
+ }
+
+ /** notify the front-end of guest capability changes */
+ void i_onVMMDevGuestCapsChange(uint32_t fCaps)
+ {
+ mfVMMDevGuestCaps = fCaps;
+ i_sendMouseCapsNotifications();
+ }
+
+ void updateMousePointerShape(bool fVisible, bool fAlpha,
+ uint32_t hotX, uint32_t hotY,
+ uint32_t width, uint32_t height,
+ const uint8_t *pu8Shape, uint32_t cbShape);
+private:
+
+ // Wrapped IMouse properties
+ HRESULT getAbsoluteSupported(BOOL *aAbsoluteSupported);
+ HRESULT getRelativeSupported(BOOL *aRelativeSupported);
+ HRESULT getTouchScreenSupported(BOOL *aTouchScreenSupported);
+ HRESULT getTouchPadSupported(BOOL *aTouchPadSupported);
+ HRESULT getNeedsHostCursor(BOOL *aNeedsHostCursor);
+ HRESULT getPointerShape(ComPtr<IMousePointerShape> &aPointerShape);
+ HRESULT getEventSource(ComPtr<IEventSource> &aEventSource);
+
+ // Wrapped IMouse methods
+ HRESULT putMouseEvent(LONG aDx,
+ LONG aDy,
+ LONG aDz,
+ LONG aDw,
+ LONG aButtonState);
+ HRESULT putMouseEventAbsolute(LONG aX,
+ LONG aY,
+ LONG aDz,
+ LONG aDw,
+ LONG aButtonState);
+ HRESULT putEventMultiTouch(LONG aCount,
+ const std::vector<LONG64> &aContacts,
+ BOOL isTouchScreen,
+ ULONG aScanTime);
+ HRESULT putEventMultiTouchString(LONG aCount,
+ const com::Utf8Str &aContacts,
+ BOOL isTouchScreen,
+ ULONG aScanTime);
+
+
+ static DECLCALLBACK(void *) i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID);
+ static DECLCALLBACK(void) i_mouseReportModes(PPDMIMOUSECONNECTOR pInterface, bool fRel, bool fAbs, bool fMTAbs, bool fMTRel);
+ static DECLCALLBACK(int) i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags);
+ static DECLCALLBACK(void) i_drvDestruct(PPDMDRVINS pDrvIns);
+
+ HRESULT i_updateVMMDevMouseCaps(uint32_t fCapsAdded, uint32_t fCapsRemoved);
+ HRESULT i_reportRelEventToMouseDev(int32_t dx, int32_t dy, int32_t dz,
+ int32_t dw, uint32_t fButtons);
+ HRESULT i_reportAbsEventToMouseDev(int32_t x, int32_t y, int32_t dz,
+ int32_t dw, uint32_t fButtons);
+ HRESULT i_reportMTEventToMouseDev(int32_t x, int32_t z, uint32_t cContact,
+ uint32_t fContact);
+ HRESULT i_reportMultiTouchEventToDevice(uint8_t cContacts, const uint64_t *pau64Contacts, bool fTouchScreen, uint32_t u32ScanTime);
+ HRESULT i_reportAbsEventToVMMDev(int32_t x, int32_t y, int32_t dz, int32_t dw, uint32_t fButtons);
+ HRESULT i_reportAbsEventToInputDevices(int32_t x, int32_t y, int32_t dz, int32_t dw, uint32_t fButtons,
+ bool fUsesVMMDevEvent);
+ HRESULT i_reportAbsEventToDisplayDevice(int32_t x, int32_t y);
+ HRESULT i_convertDisplayRes(LONG x, LONG y, int32_t *pxAdj, int32_t *pyAdj,
+ bool *pfValid);
+ HRESULT i_putEventMultiTouch(LONG aCount, const LONG64 *paContacts, BOOL isTouchScreen, ULONG aScanTime);
+
+ uint32_t i_getDeviceCaps(void);
+ void i_sendMouseCapsNotifications(void);
+ bool i_guestNeedsHostCursor(void);
+ bool i_vmmdevCanAbs(void);
+ bool i_deviceCanAbs(void);
+ bool i_supportsAbs(uint32_t fCaps) const;
+ bool i_supportsAbs(void);
+ bool i_supportsRel(void);
+ bool i_supportsTS(void);
+ bool i_supportsTP(void);
+
+ ConsoleMouseInterface * const mParent;
+ /** Pointer to the associated mouse driver. */
+ struct DRVMAINMOUSE *mpDrv[MOUSE_MAX_DEVICES];
+
+ uint32_t mfVMMDevGuestCaps; /** We cache this to avoid access races */
+ int32_t mcLastX;
+ int32_t mcLastY;
+ uint32_t mfLastButtons;
+
+ ComPtr<IMousePointerShape> mPointerShape;
+ struct
+ {
+ bool fVisible;
+ bool fAlpha;
+ uint32_t hotX;
+ uint32_t hotY;
+ uint32_t width;
+ uint32_t height;
+ uint8_t *pu8Shape;
+ uint32_t cbShape;
+ } mPointerData;
+
+ const ComObjPtr<EventSource> mEventSource;
+ VBoxEventDesc mMouseEvent;
+
+ void i_fireMouseEvent(bool fAbsolute, LONG x, LONG y, LONG dz, LONG dw,
+ LONG fButtons);
+
+ void i_fireMultiTouchEvent(uint8_t cContacts,
+ const LONG64 *paContacts,
+ bool fTouchScreen,
+ uint32_t u32ScanTime);
+};
+
+#endif /* !MAIN_INCLUDED_MouseImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/NATEngineImpl.h b/src/VBox/Main/include/NATEngineImpl.h
new file mode 100644
index 00000000..1abe61b5
--- /dev/null
+++ b/src/VBox/Main/include/NATEngineImpl.h
@@ -0,0 +1,124 @@
+/* $Id: NATEngineImpl.h $ */
+
+/** @file
+ *
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_NATEngineImpl_h
+#define MAIN_INCLUDED_NATEngineImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "NATEngineWrap.h"
+
+namespace settings
+{
+ struct NAT;
+}
+
+
+class ATL_NO_VTABLE NATEngine :
+ public NATEngineWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(NATEngine)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ HRESULT init(Machine *aParent, INetworkAdapter *aAdapter);
+ HRESULT init(Machine *aParent, INetworkAdapter *aAdapter, NATEngine *aThat);
+ HRESULT initCopy(Machine *aParent, INetworkAdapter *aAdapter, NATEngine *aThat);
+ void uninit();
+
+ bool i_isModified();
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(NATEngine *aThat);
+ void i_applyDefaults();
+ bool i_hasDefaults();
+ HRESULT i_loadSettings(const settings::NAT &data);
+ HRESULT i_saveSettings(settings::NAT &data);
+
+private:
+
+ // wrapped INATEngine properties
+ HRESULT setNetwork(const com::Utf8Str &aNetwork);
+ HRESULT getNetwork(com::Utf8Str &aNetwork);
+ HRESULT setHostIP(const com::Utf8Str &aHostIP);
+ HRESULT getHostIP(com::Utf8Str &aBindIP);
+ HRESULT setLocalhostReachable(BOOL fLocalhostReachable);
+ HRESULT getLocalhostReachable(BOOL *pfLocalhostReachable);
+ /* TFTP properties */
+ HRESULT setTFTPPrefix(const com::Utf8Str &aTFTPPrefix);
+ HRESULT getTFTPPrefix(com::Utf8Str &aTFTPPrefix);
+ HRESULT setTFTPBootFile(const com::Utf8Str &aTFTPBootFile);
+ HRESULT getTFTPBootFile(com::Utf8Str &aTFTPBootFile);
+ HRESULT setTFTPNextServer(const com::Utf8Str &aTFTPNextServer);
+ HRESULT getTFTPNextServer(com::Utf8Str &aTFTPNextServer);
+ /* DNS properties */
+ HRESULT setDNSPassDomain(BOOL aDNSPassDomain);
+ HRESULT getDNSPassDomain(BOOL *aDNSPassDomain);
+ HRESULT setDNSProxy(BOOL aDNSProxy);
+ HRESULT getDNSProxy(BOOL *aDNSProxy);
+ HRESULT getDNSUseHostResolver(BOOL *aDNSUseHostResolver);
+ HRESULT setDNSUseHostResolver(BOOL aDNSUseHostResolver);
+ /* Alias properties */
+ HRESULT setAliasMode(ULONG aAliasMode);
+ HRESULT getAliasMode(ULONG *aAliasMode);
+
+ HRESULT getRedirects(std::vector<com::Utf8Str> &aRedirects);
+
+ HRESULT setNetworkSettings(ULONG aMtu,
+ ULONG aSockSnd,
+ ULONG aSockRcv,
+ ULONG aTcpWndSnd,
+ ULONG aTcpWndRcv);
+
+ HRESULT getNetworkSettings(ULONG *aMtu,
+ ULONG *aSockSnd,
+ ULONG *aSockRcv,
+ ULONG *aTcpWndSnd,
+ ULONG *aTcpWndRcv);
+
+ HRESULT addRedirect(const com::Utf8Str &aName,
+ NATProtocol_T aProto,
+ const com::Utf8Str &aHostIP,
+ USHORT aHostPort,
+ const com::Utf8Str &aGuestIP,
+ USHORT aGuestPort);
+
+ HRESULT removeRedirect(const com::Utf8Str &aName);
+
+ struct Data;
+ Data *mData;
+ const ComObjPtr<NATEngine> mPeer;
+ Machine * const mParent;
+ INetworkAdapter * const mAdapter;
+};
+#endif /* !MAIN_INCLUDED_NATEngineImpl_h */
diff --git a/src/VBox/Main/include/NATNetworkImpl.h b/src/VBox/Main/include/NATNetworkImpl.h
new file mode 100644
index 00000000..c2e1f7ac
--- /dev/null
+++ b/src/VBox/Main/include/NATNetworkImpl.h
@@ -0,0 +1,143 @@
+/* $Id: NATNetworkImpl.h $ */
+/** @file
+ * INATNetwork implementation header, lives in VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_NATNetworkImpl_h
+#define MAIN_INCLUDED_NATNetworkImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBoxEvents.h"
+#include "NATNetworkWrap.h"
+
+#ifdef VBOX_WITH_HOSTNETIF_API
+struct NETIFINFO;
+#endif
+
+namespace settings
+{
+ struct NATNetwork;
+ struct NATRule;
+ typedef std::map<com::Utf8Str, NATRule> NATRulesMap;
+}
+
+#ifdef RT_OS_WINDOWS
+# define NATSR_EXECUTABLE_NAME "VBoxNetNAT.exe"
+#else
+# define NATSR_EXECUTABLE_NAME "VBoxNetNAT"
+#endif
+
+#undef ADDR_ANY ///@todo ADDR_ANY collides with some windows header!
+
+enum ADDRESSLOOKUPTYPE
+{
+ ADDR_GATEWAY,
+ ADDR_DHCP,
+ ADDR_DHCPLOWERIP,
+ ADDR_ANY
+};
+
+class NATNetworkServiceRunner: public NetworkServiceRunner
+{
+public:
+ NATNetworkServiceRunner(): NetworkServiceRunner(NATSR_EXECUTABLE_NAME){}
+ ~NATNetworkServiceRunner(){}
+};
+
+class ATL_NO_VTABLE NATNetwork :
+ public NATNetworkWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(NATNetwork)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ HRESULT init(VirtualBox *aVirtualBox, com::Utf8Str aName);
+ HRESULT i_loadSettings(const settings::NATNetwork &data);
+ void uninit();
+ HRESULT i_saveSettings(settings::NATNetwork &data);
+
+private:
+
+ // Wrapped INATNetwork properties
+ HRESULT getNetworkName(com::Utf8Str &aNetworkName);
+ HRESULT setNetworkName(const com::Utf8Str &aNetworkName);
+ HRESULT getEnabled(BOOL *aEnabled);
+ HRESULT setEnabled(BOOL aEnabled);
+ HRESULT getNetwork(com::Utf8Str &aNetwork);
+ HRESULT setNetwork(const com::Utf8Str &aNetwork);
+ HRESULT getGateway(com::Utf8Str &aGateway);
+ HRESULT getIPv6Enabled(BOOL *aIPv6Enabled);
+ HRESULT setIPv6Enabled(BOOL aIPv6Enabled);
+ HRESULT getIPv6Prefix(com::Utf8Str &aIPv6Prefix);
+ HRESULT setIPv6Prefix(const com::Utf8Str &aIPv6Prefix);
+ HRESULT getAdvertiseDefaultIPv6RouteEnabled(BOOL *aAdvertiseDefaultIPv6RouteEnabled);
+ HRESULT setAdvertiseDefaultIPv6RouteEnabled(BOOL aAdvertiseDefaultIPv6RouteEnabled);
+ HRESULT getNeedDhcpServer(BOOL *aNeedDhcpServer);
+ HRESULT setNeedDhcpServer(BOOL aNeedDhcpServer);
+ HRESULT getEventSource(ComPtr<IEventSource> &aEventSource);
+ HRESULT getPortForwardRules4(std::vector<com::Utf8Str> &aPortForwardRules4);
+ HRESULT getLocalMappings(std::vector<com::Utf8Str> &aLocalMappings);
+ HRESULT getLoopbackIp6(LONG *aLoopbackIp6);
+ HRESULT setLoopbackIp6(LONG aLoopbackIp6);
+ HRESULT getPortForwardRules6(std::vector<com::Utf8Str> &aPortForwardRules6);
+
+ // wrapped INATNetwork methods
+ HRESULT addLocalMapping(const com::Utf8Str &aHostid,
+ LONG aOffset);
+ HRESULT addPortForwardRule(BOOL aIsIpv6,
+ const com::Utf8Str &aRuleName,
+ NATProtocol_T aProto,
+ const com::Utf8Str &aHostIP,
+ USHORT aHostPort,
+ const com::Utf8Str &aGuestIP,
+ USHORT aGuestPort);
+ HRESULT removePortForwardRule(BOOL aISipv6,
+ const com::Utf8Str &aRuleName);
+ HRESULT start();
+ HRESULT stop();
+
+ // Internal methods
+ HRESULT setErrorBusy();
+
+ int i_recalculateIpv4AddressAssignments();
+ int i_findFirstAvailableOffset(ADDRESSLOOKUPTYPE, uint32_t *);
+ int i_recalculateIPv6Prefix();
+
+ void i_getPortForwardRulesFromMap(std::vector<Utf8Str> &aPortForwardRules, settings::NATRulesMap &aRules);
+
+ void i_updateDnsOptions();
+ void i_updateDomainNameOption(ComPtr<IHost> &host);
+ void i_updateDomainNameServerOption(ComPtr<IHost> &host);
+
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_NATNetworkImpl_h */
diff --git a/src/VBox/Main/include/NetworkAdapterImpl.h b/src/VBox/Main/include/NetworkAdapterImpl.h
new file mode 100644
index 00000000..ca03c118
--- /dev/null
+++ b/src/VBox/Main/include/NetworkAdapterImpl.h
@@ -0,0 +1,141 @@
+/* $Id: NetworkAdapterImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_NetworkAdapterImpl_h
+#define MAIN_INCLUDED_NetworkAdapterImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "NetworkAdapterWrap.h"
+
+class GuestOSType;
+class BandwidthControl;
+class BandwidthGroup;
+class NATEngine;
+
+namespace settings
+{
+ struct NetworkAdapter;
+}
+
+class ATL_NO_VTABLE NetworkAdapter :
+ public NetworkAdapterWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(NetworkAdapter)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aParent, ULONG aSlot);
+ HRESULT init(Machine *aParent, NetworkAdapter *aThat, bool aReshare = false);
+ HRESULT initCopy(Machine *aParent, NetworkAdapter *aThat);
+ void uninit();
+
+ // public methods only for internal purposes
+ HRESULT i_loadSettings(BandwidthControl *bwctl, const settings::NetworkAdapter &data);
+ HRESULT i_saveSettings(settings::NetworkAdapter &data);
+
+ bool i_isModified();
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(NetworkAdapter *aThat);
+ void i_applyDefaults(GuestOSType *aOsType);
+ bool i_hasDefaults();
+
+ ComObjPtr<NetworkAdapter> i_getPeer();
+
+private:
+
+ // wrapped INetworkAdapter properties
+ HRESULT getAdapterType(NetworkAdapterType_T *aAdapterType);
+ HRESULT setAdapterType(NetworkAdapterType_T aAdapterType);
+ HRESULT getSlot(ULONG *aSlot);
+ HRESULT getEnabled(BOOL *aEnabled);
+ HRESULT setEnabled(BOOL aEnabled);
+ HRESULT getMACAddress(com::Utf8Str &aMACAddress);
+ HRESULT setMACAddress(const com::Utf8Str &aMACAddress);
+ HRESULT getAttachmentType(NetworkAttachmentType_T *aAttachmentType);
+ HRESULT setAttachmentType(NetworkAttachmentType_T aAttachmentType);
+ HRESULT getBridgedInterface(com::Utf8Str &aBridgedInterface);
+ HRESULT setBridgedInterface(const com::Utf8Str &aBridgedInterface);
+ HRESULT getHostOnlyInterface(com::Utf8Str &aHostOnlyInterface);
+ HRESULT setHostOnlyInterface(const com::Utf8Str &aHostOnlyInterface);
+ HRESULT getHostOnlyNetwork(com::Utf8Str &aHostOnlyNetwork);
+ HRESULT setHostOnlyNetwork(const com::Utf8Str &aHostOnlyNetwork);
+ HRESULT getInternalNetwork(com::Utf8Str &aInternalNetwork);
+ HRESULT setInternalNetwork(const com::Utf8Str &aInternalNetwork);
+ HRESULT getNATNetwork(com::Utf8Str &aNATNetwork);
+ HRESULT setNATNetwork(const com::Utf8Str &aNATNetwork);
+ HRESULT getGenericDriver(com::Utf8Str &aGenericDriver);
+ HRESULT setGenericDriver(const com::Utf8Str &aGenericDriver);
+ HRESULT getCloudNetwork(com::Utf8Str &aCloudNetwork);
+ HRESULT setCloudNetwork(const com::Utf8Str &aCloudNetwork);
+ HRESULT getCableConnected(BOOL *aCableConnected);
+ HRESULT setCableConnected(BOOL aCableConnected);
+ HRESULT getLineSpeed(ULONG *aLineSpeed);
+ HRESULT setLineSpeed(ULONG aLineSpeed);
+ HRESULT getPromiscModePolicy(NetworkAdapterPromiscModePolicy_T *aPromiscModePolicy);
+ HRESULT setPromiscModePolicy(NetworkAdapterPromiscModePolicy_T aPromiscModePolicy);
+ HRESULT getTraceEnabled(BOOL *aTraceEnabled);
+ HRESULT setTraceEnabled(BOOL aTraceEnabled);
+ HRESULT getTraceFile(com::Utf8Str &aTraceFile);
+ HRESULT setTraceFile(const com::Utf8Str &aTraceFile);
+ HRESULT getNATEngine(ComPtr<INATEngine> &aNATEngine);
+ HRESULT getBootPriority(ULONG *aBootPriority);
+ HRESULT setBootPriority(ULONG aBootPriority);
+ HRESULT getBandwidthGroup(ComPtr<IBandwidthGroup> &aBandwidthGroup);
+ HRESULT setBandwidthGroup(const ComPtr<IBandwidthGroup> &aBandwidthGroup);
+
+ // wrapped INetworkAdapter methods
+ HRESULT getProperty(const com::Utf8Str &aKey,
+ com::Utf8Str &aValue);
+ HRESULT setProperty(const com::Utf8Str &aKey,
+ const com::Utf8Str &aValue);
+ HRESULT getProperties(const com::Utf8Str &aNames,
+ std::vector<com::Utf8Str> &aReturnNames,
+ std::vector<com::Utf8Str> &aReturnValues);
+ // Misc.
+ void i_generateMACAddress();
+ HRESULT i_updateMacAddress(Utf8Str aMacAddress);
+ void i_updateBandwidthGroup(BandwidthGroup *aBwGroup);
+ HRESULT i_switchFromNatNetworking(const com::Utf8Str &aNatnetworkName);
+ HRESULT i_switchToNatNetworking(const com::Utf8Str &aNatNetworkName);
+
+
+ Machine * const mParent;
+ const ComObjPtr<NetworkAdapter> mPeer;
+ const ComObjPtr<NATEngine> mNATEngine;
+
+ Backupable<settings::NetworkAdapter> mData;
+};
+
+#endif /* !MAIN_INCLUDED_NetworkAdapterImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/NetworkServiceRunner.h b/src/VBox/Main/include/NetworkServiceRunner.h
new file mode 100644
index 00000000..ea3b840d
--- /dev/null
+++ b/src/VBox/Main/include/NetworkServiceRunner.h
@@ -0,0 +1,87 @@
+/* $Id: NetworkServiceRunner.h $ */
+/** @file
+ * VirtualBox Main - interface for VBox DHCP server.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_NetworkServiceRunner_h
+#define MAIN_INCLUDED_NetworkServiceRunner_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/com/string.h>
+
+
+/** @name Internal networking trunk type option values (NetworkServiceRunner::kpszKeyTrunkType)
+ * @{ */
+#define TRUNKTYPE_WHATEVER "whatever"
+#define TRUNKTYPE_NETFLT "netflt"
+#define TRUNKTYPE_NETADP "netadp"
+#define TRUNKTYPE_SRVNAT "srvnat"
+/** @} */
+
+/**
+ * Network service runner.
+ *
+ * Build arguments, starts and stops network service processes.
+ */
+class NetworkServiceRunner
+{
+public:
+ NetworkServiceRunner(const char *aProcName);
+ virtual ~NetworkServiceRunner();
+
+ /** @name Argument management
+ * @{ */
+ int addArgument(const char *pszArgument);
+ int addArgPair(const char *pszOption, const char *pszValue);
+ void resetArguments();
+ /** @} */
+
+ int start(bool aKillProcessOnStop);
+ int stop();
+ void detachFromServer();
+ bool isRunning();
+
+ RTPROCESS getPid() const;
+
+ /** @name Common options
+ * @{ */
+ static const char * const kpszKeyNetwork;
+ static const char * const kpszKeyTrunkType;
+ static const char * const kpszTrunkName;
+ static const char * const kpszMacAddress;
+ static const char * const kpszIpAddress;
+ static const char * const kpszIpNetmask;
+ static const char * const kpszKeyNeedMain;
+ /** @} */
+
+private:
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_NetworkServiceRunner_h */
+
diff --git a/src/VBox/Main/include/NvramStoreImpl.h b/src/VBox/Main/include/NvramStoreImpl.h
new file mode 100644
index 00000000..bb637689
--- /dev/null
+++ b/src/VBox/Main/include/NvramStoreImpl.h
@@ -0,0 +1,156 @@
+/* $Id: NvramStoreImpl.h $ */
+/** @file
+ * VirtualBox COM NVRAM store class implementation
+ */
+
+/*
+ * Copyright (C) 2021-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_NvramStoreImpl_h
+#define MAIN_INCLUDED_NvramStoreImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "NvramStoreWrap.h"
+#include "SecretKeyStore.h"
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/VBoxCryptoIf.h>
+
+
+#ifdef VBOX_COM_INPROC
+class Console;
+#else
+class GuestOSType;
+
+namespace settings
+{
+ struct NvramSettings;
+}
+#endif
+
+class ATL_NO_VTABLE NvramStore :
+ public NvramStoreWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(NvramStore)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+#ifdef VBOX_COM_INPROC
+ HRESULT init(Console *aParent, const com::Utf8Str &strNonVolatileStorageFile);
+#else
+ HRESULT init(Machine *parent);
+ HRESULT init(Machine *parent, NvramStore *that);
+ HRESULT initCopy(Machine *parent, NvramStore *that);
+#endif
+ void uninit();
+
+ // public methods for internal purposes only
+#ifndef VBOX_COM_INPROC
+ HRESULT i_loadSettings(const settings::NvramSettings &data);
+ HRESULT i_saveSettings(settings::NvramSettings &data);
+#endif
+
+#ifdef VBOX_COM_INPROC
+ static const PDMDRVREG DrvReg;
+#else
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(NvramStore *aThat);
+ HRESULT i_applyDefaults(GuestOSType *aOSType);
+#endif
+
+ com::Utf8Str i_getNonVolatileStorageFile();
+ void i_updateNonVolatileStorageFile(const com::Utf8Str &aNonVolatileStorageFile);
+
+ int i_loadStore(const char *pszPath);
+ int i_saveStore(void);
+
+#ifndef VBOX_COM_INPROC
+ HRESULT i_retainUefiVarStore(PRTVFS phVfs, bool fReadonly);
+ HRESULT i_releaseUefiVarStore(RTVFS hVfs);
+#endif
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ HRESULT i_updateEncryptionSettings(const com::Utf8Str &strKeyId,
+ const com::Utf8Str &strKeyStore);
+ HRESULT i_getEncryptionSettings(com::Utf8Str &strKeyId,
+ com::Utf8Str &strKeyStore);
+
+ int i_addPassword(const Utf8Str &strKeyId, const Utf8Str &strPassword);
+ int i_removePassword(const Utf8Str &strKeyId);
+ int i_removeAllPasswords();
+#endif
+
+private:
+
+ int initImpl(void);
+
+ // Wrapped NVRAM store properties
+ HRESULT getNonVolatileStorageFile(com::Utf8Str &aNonVolatileStorageFile);
+ HRESULT getUefiVariableStore(ComPtr<IUefiVariableStore> &aUefiVarStore);
+ HRESULT getKeyId(com::Utf8Str &aKeyId);
+ HRESULT getKeyStore(com::Utf8Str &aKeyStore);
+
+ // Wrapped NVRAM store members
+ HRESULT initUefiVariableStore(ULONG aSize);
+
+ int i_loadStoreFromTar(RTVFSFSSTREAM hVfsFssTar);
+ int i_saveStoreAsTar(const char *pszPath);
+
+ int i_retainCryptoIf(PCVBOXCRYPTOIF *ppCryptoIf);
+ int i_releaseCryptoIf(PCVBOXCRYPTOIF pCryptoIf);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ int i_setupEncryptionOrDecryption(RTVFSIOSTREAM hVfsIosInOut, bool fEncrypt,
+ PCVBOXCRYPTOIF *ppCryptoIf, SecretKey **ppKey,
+ PRTVFSIOSTREAM phVfsIos);
+ void i_releaseEncryptionOrDecryptionResources(RTVFSIOSTREAM hVfsIos, PCVBOXCRYPTOIF pCryptoIf,
+ SecretKey *pKey);
+#endif
+
+#ifdef VBOX_COM_INPROC
+ static DECLCALLBACK(int) i_SsmSaveExec(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM);
+ static DECLCALLBACK(int) i_SsmLoadExec(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass);
+
+ static DECLCALLBACK(int) i_nvramStoreQuerySize(PPDMIVFSCONNECTOR pInterface, const char *pszNamespace, const char *pszPath,
+ uint64_t *pcb);
+ static DECLCALLBACK(int) i_nvramStoreReadAll(PPDMIVFSCONNECTOR pInterface, const char *pszNamespace, const char *pszPath,
+ void *pvBuf, size_t cbRead);
+ static DECLCALLBACK(int) i_nvramStoreWriteAll(PPDMIVFSCONNECTOR pInterface, const char *pszNamespace, const char *pszPath,
+ const void *pvBuf, size_t cbWrite);
+ static DECLCALLBACK(int) i_nvramStoreDelete(PPDMIVFSCONNECTOR pInterface, const char *pszNamespace, const char *pszPath);
+ static DECLCALLBACK(void *) i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID);
+ static DECLCALLBACK(int) i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags);
+ static DECLCALLBACK(void) i_drvDestruct(PPDMDRVINS pDrvIns);
+#endif
+
+ struct Data; // opaque data struct, defined in NvramStoreImpl.cpp
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_NvramStoreImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/ObjectState.h b/src/VBox/Main/include/ObjectState.h
new file mode 100644
index 00000000..669af905
--- /dev/null
+++ b/src/VBox/Main/include/ObjectState.h
@@ -0,0 +1,150 @@
+/* $Id: ObjectState.h $ */
+/** @file
+ *
+ * VirtualBox object state handling definitions
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ObjectState_h
+#define MAIN_INCLUDED_ObjectState_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBox/com/defs.h"
+#include "VBox/com/AutoLock.h"
+#include "VBox/com/ErrorInfo.h"
+
+// Forward declaration needed, but nothing more.
+class VirtualBoxBase;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ObjectState
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Thec functionality implemented by this class is the primary object state
+ * (used by VirtualBoxBase and thus part of all API classes) that indicates
+ * if the object is ready to serve the calls, and if not, what stage it is
+ * currently at. Here is the primary state diagram:
+ *
+ * +-------------------------------------------------------+
+ * | |
+ * | (InitFailed) -----------------------+ |
+ * | ^ | |
+ * v | v |
+ * [*] ---> NotReady ----> (InInit) -----> Ready -----> (InUninit) ----+
+ * ^ |
+ * | v
+ * | Limited
+ * | |
+ * +-------+
+ *
+ * The object is fully operational only when its state is Ready. The Limited
+ * state means that only some vital part of the object is operational, and it
+ * requires some sort of reinitialization to become fully operational. The
+ * NotReady state means the object is basically dead: it either was not yet
+ * initialized after creation at all, or was uninitialized and is waiting to be
+ * destroyed when the last reference to it is released. All other states are
+ * transitional.
+ *
+ * The NotReady->InInit->Ready, NotReady->InInit->Limited and
+ * NotReady->InInit->InitFailed transition is done by the AutoInitSpan smart
+ * class.
+ *
+ * The Limited->InInit->Ready, Limited->InInit->Limited and
+ * Limited->InInit->InitFailed transition is done by the AutoReinitSpan smart
+ * class.
+ *
+ * The Ready->InUninit->NotReady and InitFailed->InUninit->NotReady
+ * transitions are done by the AutoUninitSpan smart class.
+ *
+ * In order to maintain the primary state integrity and declared functionality
+ * the following rules apply everywhere:
+ *
+ * 1) Use the above Auto*Span classes to perform state transitions. See the
+ * individual class descriptions for details.
+ *
+ * 2) All public methods of subclasses (i.e. all methods that can be called
+ * directly, not only from within other methods of the subclass) must have a
+ * standard prolog as described in the AutoCaller and AutoLimitedCaller
+ * documentation. Alternatively, they must use #addCaller() and
+ * #releaseCaller() directly (and therefore have both the prolog and the
+ * epilog), but this is not recommended because it is easy to forget the
+ * matching release, e.g. returning before reaching the call.
+ */
+class ObjectState
+{
+public:
+ enum State { NotReady, Ready, InInit, InUninit, InitFailed, Limited };
+
+ ObjectState(VirtualBoxBase *aObj);
+ ~ObjectState();
+
+ State getState();
+
+ HRESULT addCaller(bool aLimited = false);
+ void releaseCaller();
+
+ bool autoInitSpanConstructor(State aExpectedState);
+ void autoInitSpanDestructor(State aNewState, HRESULT aFailedRC, com::ErrorInfo *aFailedEI);
+ State autoUninitSpanConstructor(bool fTry);
+ void autoUninitSpanDestructor();
+
+private:
+ ObjectState();
+
+ void setState(State aState);
+
+ /** Pointer to the managed object, mostly for error signalling or debugging
+ * purposes, not used much. Guaranteed to be valid during the lifetime of
+ * this object, no need to mess with refcount. */
+ VirtualBoxBase *mObj;
+ /** Primary state of this object */
+ State mState;
+ /** Thread that caused the last state change */
+ RTTHREAD mStateChangeThread;
+ /** Result code for failed object initialization */
+ HRESULT mFailedRC;
+ /** Error information for failed object initialization */
+ com::ErrorInfo *mpFailedEI;
+ /** Total number of active calls to this object */
+ unsigned mCallers;
+ /** Posted when the number of callers drops to zero */
+ RTSEMEVENT mZeroCallersSem;
+ /** Posted when the object goes from InInit/InUninit to some other state */
+ RTSEMEVENTMULTI mInitUninitSem;
+ /** Number of threads waiting for mInitUninitDoneSem */
+ unsigned mInitUninitWaiters;
+
+ /** Protects access to state related data members */
+ util::RWLockHandle mStateLock;
+
+private:
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(ObjectState); /* Shuts up MSC warning C4625. */
+};
+
+#endif /* !MAIN_INCLUDED_ObjectState_h */
diff --git a/src/VBox/Main/include/PCIDeviceAttachmentImpl.h b/src/VBox/Main/include/PCIDeviceAttachmentImpl.h
new file mode 100644
index 00000000..9f3998c0
--- /dev/null
+++ b/src/VBox/Main/include/PCIDeviceAttachmentImpl.h
@@ -0,0 +1,79 @@
+/* $Id: PCIDeviceAttachmentImpl.h $ */
+
+/** @file
+ *
+ * PCI attachment information implmentation.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_PCIDeviceAttachmentImpl_h
+#define MAIN_INCLUDED_PCIDeviceAttachmentImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "PCIDeviceAttachmentWrap.h"
+
+namespace settings
+{
+ struct HostPCIDeviceAttachment;
+}
+
+class ATL_NO_VTABLE PCIDeviceAttachment :
+ public PCIDeviceAttachmentWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(PCIDeviceAttachment)
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(IMachine * aParent,
+ const Utf8Str &aDevName,
+ LONG aHostAddess,
+ LONG aGuestAddress,
+ BOOL fPhysical);
+ HRESULT initCopy(IMachine *aParent, PCIDeviceAttachment *aThat);
+ void uninit();
+
+ // settings
+ HRESULT i_loadSettings(IMachine * aParent,
+ const settings::HostPCIDeviceAttachment& aHpda);
+ HRESULT i_saveSettings(settings::HostPCIDeviceAttachment &data);
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+private:
+
+ // wrapped IPCIDeviceAttachment properties
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getIsPhysicalDevice(BOOL *aIsPhysicalDevice);
+ HRESULT getHostAddress(LONG *aHostAddress);
+ HRESULT getGuestAddress(LONG *aGuestAddress);
+
+ struct Data;
+ Data* m;
+};
+
+#endif /* !MAIN_INCLUDED_PCIDeviceAttachmentImpl_h */
diff --git a/src/VBox/Main/include/PCIRawDevImpl.h b/src/VBox/Main/include/PCIRawDevImpl.h
new file mode 100644
index 00000000..6d5923b9
--- /dev/null
+++ b/src/VBox/Main/include/PCIRawDevImpl.h
@@ -0,0 +1,64 @@
+/* $Id: PCIRawDevImpl.h $ */
+/** @file
+ * VirtualBox Driver interface to raw PCI device
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_PCIRawDevImpl_h
+#define MAIN_INCLUDED_PCIRawDevImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/vmm/pdmdrv.h>
+
+class Console;
+struct DRVMAINPCIRAWDEV;
+
+class PCIRawDev
+{
+ public:
+ PCIRawDev(Console *console);
+ virtual ~PCIRawDev();
+
+ static const PDMDRVREG DrvReg;
+
+ Console *getParent() const
+ {
+ return mParent;
+ }
+
+ private:
+ static DECLCALLBACK(void *) drvQueryInterface(PPDMIBASE pInterface, const char *pszIID);
+ static DECLCALLBACK(int) drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags);
+ static DECLCALLBACK(void) drvDestruct(PPDMDRVINS pDrvIns);
+ static DECLCALLBACK(int) drvDeviceConstructComplete(PPDMIPCIRAWCONNECTOR pInterface, const char *pcszName,
+ uint32_t uHostPCIAddress, uint32_t uGuestPCIAddress,
+ int rc);
+
+ Console * const mParent;
+ struct DRVMAINPCIRAWDEV *mpDrv;
+};
+
+#endif /* !MAIN_INCLUDED_PCIRawDevImpl_h */
diff --git a/src/VBox/Main/include/ParallelPortImpl.h b/src/VBox/Main/include/ParallelPortImpl.h
new file mode 100644
index 00000000..a3b79143
--- /dev/null
+++ b/src/VBox/Main/include/ParallelPortImpl.h
@@ -0,0 +1,87 @@
+/* $Id: ParallelPortImpl.h $ */
+
+/** @file
+ * VirtualBox COM class implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ParallelPortImpl_h
+#define MAIN_INCLUDED_ParallelPortImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "ParallelPortWrap.h"
+
+namespace settings
+{
+ struct ParallelPort;
+}
+
+class ATL_NO_VTABLE ParallelPort :
+ public ParallelPortWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(ParallelPort)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aParent, ULONG aSlot);
+ HRESULT init(Machine *aParent, ParallelPort *aThat);
+ HRESULT initCopy(Machine *parent, ParallelPort *aThat);
+ void uninit();
+
+ HRESULT i_loadSettings(const settings::ParallelPort &data);
+ HRESULT i_saveSettings(settings::ParallelPort &data);
+
+ // public methods only for internal purposes
+ bool i_isModified();
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(ParallelPort *aThat);
+ void i_applyDefaults();
+ bool i_hasDefaults();
+
+private:
+
+ // Wrapped IParallelPort properties
+ HRESULT getEnabled(BOOL *aEnabled);
+ HRESULT setEnabled(BOOL aEnabled);
+ HRESULT getSlot(ULONG *aSlot);
+ HRESULT getIRQ(ULONG *aIRQ);
+ HRESULT setIRQ(ULONG aIRQ);
+ HRESULT getIOBase(ULONG *aIOBase);
+ HRESULT setIOBase(ULONG aIOBase);
+ HRESULT getPath(com::Utf8Str &aPath);
+ HRESULT setPath(const com::Utf8Str &aPath);
+
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_ParallelPortImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/Performance.h b/src/VBox/Main/include/Performance.h
new file mode 100644
index 00000000..1c092558
--- /dev/null
+++ b/src/VBox/Main/include/Performance.h
@@ -0,0 +1,915 @@
+/* $Id: Performance.h $ */
+/** @file
+ * VirtualBox Main - Performance Classes declaration.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_Performance_h
+#define MAIN_INCLUDED_Performance_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/com/defs.h>
+#include <VBox/com/ptr.h>
+#include <VBox/com/string.h>
+#include <VBox/com/VirtualBox.h>
+
+#include <iprt/types.h>
+#include <iprt/err.h>
+#include <iprt/cpp/lock.h>
+
+#include <algorithm>
+#include <functional> /* For std::fun_ptr in testcase */
+#include <list>
+#include <vector>
+#include <queue>
+
+#include "MediumImpl.h"
+
+/* Forward decl. */
+class Machine;
+
+namespace pm
+{
+ /* CPU load is measured in 1/1000 of per cent. */
+ const uint64_t PM_CPU_LOAD_MULTIPLIER = UINT64_C(100000);
+ /* Network load is measured in 1/1000 of per cent. */
+ const uint64_t PM_NETWORK_LOAD_MULTIPLIER = UINT64_C(100000);
+ /* Disk load is measured in 1/1000 of per cent. */
+ const uint64_t PM_DISK_LOAD_MULTIPLIER = UINT64_C(100000);
+ /* Sampler precision in milliseconds. */
+ const uint64_t PM_SAMPLER_PRECISION_MS = 50;
+
+ /* Sub Metrics **********************************************************/
+ class CircularBuffer
+ {
+ public:
+ CircularBuffer() : mData(0), mLength(0), mEnd(0), mWrapped(false) {};
+ ~CircularBuffer() { if (mData) RTMemFree(mData); };
+ void init(ULONG length);
+ ULONG length();
+ ULONG getSequenceNumber() { return mSequenceNumber; }
+ void put(ULONG value);
+ void copyTo(ULONG *data);
+ private:
+ ULONG *mData;
+ ULONG mLength;
+ ULONG mEnd;
+ ULONG mSequenceNumber;
+ bool mWrapped;
+ };
+
+ class SubMetric : public CircularBuffer
+ {
+ public:
+ SubMetric(com::Utf8Str name, const char *description)
+ : mName(name), mDescription(description) {};
+ void query(ULONG *data);
+ const char *getName() { return mName.c_str(); };
+ const char *getDescription() { return mDescription; };
+ private:
+ const com::Utf8Str mName;
+ const char *mDescription;
+ };
+
+
+ enum {
+ COLLECT_NONE = 0x0,
+ COLLECT_CPU_LOAD = 0x1,
+ COLLECT_RAM_USAGE = 0x2,
+ COLLECT_GUEST_STATS = 0x4
+ };
+ typedef int HintFlags;
+ typedef std::pair<RTPROCESS, HintFlags> ProcessFlagsPair;
+
+ class CollectorHints
+ {
+ public:
+ typedef std::list<ProcessFlagsPair> ProcessList;
+
+ CollectorHints() : mHostFlags(COLLECT_NONE) {}
+ void collectHostCpuLoad()
+ { mHostFlags |= COLLECT_CPU_LOAD; }
+ void collectHostRamUsage()
+ { mHostFlags |= COLLECT_RAM_USAGE; }
+ void collectHostRamVmm()
+ { mHostFlags |= COLLECT_GUEST_STATS; }
+ void collectProcessCpuLoad(RTPROCESS process)
+ { findProcess(process).second |= COLLECT_CPU_LOAD; }
+ void collectProcessRamUsage(RTPROCESS process)
+ { findProcess(process).second |= COLLECT_RAM_USAGE; }
+ void collectGuestStats(RTPROCESS process)
+ { findProcess(process).second |= COLLECT_GUEST_STATS; }
+ bool isHostCpuLoadCollected() const
+ { return (mHostFlags & COLLECT_CPU_LOAD) != 0; }
+ bool isHostRamUsageCollected() const
+ { return (mHostFlags & COLLECT_RAM_USAGE) != 0; }
+ bool isHostRamVmmCollected() const
+ { return (mHostFlags & COLLECT_GUEST_STATS) != 0; }
+ bool isProcessCpuLoadCollected(RTPROCESS process)
+ { return (findProcess(process).second & COLLECT_CPU_LOAD) != 0; }
+ bool isProcessRamUsageCollected(RTPROCESS process)
+ { return (findProcess(process).second & COLLECT_RAM_USAGE) != 0; }
+ bool isGuestStatsCollected(RTPROCESS process)
+ { return (findProcess(process).second & COLLECT_GUEST_STATS) != 0; }
+ void getProcesses(std::vector<RTPROCESS>& processes) const
+ {
+ processes.clear();
+ processes.reserve(mProcesses.size());
+ for (ProcessList::const_iterator it = mProcesses.begin(); it != mProcesses.end(); ++it)
+ processes.push_back(it->first);
+ }
+ const ProcessList& getProcessFlags() const
+ {
+ return mProcesses;
+ }
+ private:
+ HintFlags mHostFlags;
+ ProcessList mProcesses;
+
+ ProcessFlagsPair& findProcess(RTPROCESS process)
+ {
+ ProcessList::iterator it;
+ for (it = mProcesses.begin(); it != mProcesses.end(); ++it)
+ if (it->first == process)
+ return *it;
+
+ /* Not found -- add new */
+ mProcesses.push_back(ProcessFlagsPair(process, COLLECT_NONE));
+ return mProcesses.back();
+ }
+ };
+
+ /* Guest Collector Classes *********************************/
+ /*
+ * WARNING! The bits in the following masks must correspond to parameters
+ * of CollectorGuest::updateStats().
+ */
+ typedef enum
+ {
+ VMSTATMASK_NONE = 0x00000000,
+ VMSTATMASK_GUEST_CPUUSER = 0x00000001,
+ VMSTATMASK_GUEST_CPUKERNEL = 0x00000002,
+ VMSTATMASK_GUEST_CPUIDLE = 0x00000004,
+ VMSTATMASK_GUEST_MEMTOTAL = 0x00000008,
+ VMSTATMASK_GUEST_MEMFREE = 0x00000010,
+ VMSTATMASK_GUEST_MEMBALLOON = 0x00000020,
+ VMSTATMASK_GUEST_MEMSHARED = 0x00000040,
+ VMSTATMASK_GUEST_MEMCACHE = 0x00000080,
+ VMSTATMASK_GUEST_PAGETOTAL = 0x00000100,
+ VMSTATMASK_VMM_ALLOC = 0x00010000,
+ VMSTATMASK_VMM_FREE = 0x00020000,
+ VMSTATMASK_VMM_BALOON = 0x00040000,
+ VMSTATMASK_VMM_SHARED = 0x00080000,
+ VMSTATMASK_NET_RX = 0x01000000,
+ VMSTATMASK_NET_TX = 0x02000000
+ } VMSTATMASK;
+
+ const ULONG VMSTATS_GUEST_CPULOAD =
+ VMSTATMASK_GUEST_CPUUSER | VMSTATMASK_GUEST_CPUKERNEL |
+ VMSTATMASK_GUEST_CPUIDLE;
+ const ULONG VMSTATS_GUEST_RAMUSAGE =
+ VMSTATMASK_GUEST_MEMTOTAL | VMSTATMASK_GUEST_MEMFREE |
+ VMSTATMASK_GUEST_MEMBALLOON | VMSTATMASK_GUEST_MEMSHARED |
+ VMSTATMASK_GUEST_MEMCACHE | VMSTATMASK_GUEST_PAGETOTAL;
+ const ULONG VMSTATS_VMM_RAM =
+ VMSTATMASK_VMM_ALLOC | VMSTATMASK_VMM_FREE|
+ VMSTATMASK_VMM_BALOON | VMSTATMASK_VMM_SHARED;
+ const ULONG VMSTATS_NET_RATE =
+ VMSTATMASK_NET_RX | VMSTATMASK_NET_TX;
+ const ULONG VMSTATS_ALL =
+ VMSTATS_GUEST_CPULOAD | VMSTATS_GUEST_RAMUSAGE |
+ VMSTATS_VMM_RAM | VMSTATS_NET_RATE;
+ class CollectorGuest;
+
+ class CollectorGuestRequest
+ {
+ public:
+ CollectorGuestRequest()
+ : mCGuest(0) {};
+ virtual ~CollectorGuestRequest() {};
+ void setGuest(CollectorGuest *aGuest) { mCGuest = aGuest; };
+ CollectorGuest *getGuest() { return mCGuest; };
+ virtual HRESULT execute() = 0;
+
+ virtual void debugPrint(void *aObject, const char *aFunction, const char *aText) = 0;
+ protected:
+ CollectorGuest *mCGuest;
+ const char *mDebugName;
+ };
+
+ class CGRQEnable : public CollectorGuestRequest
+ {
+ public:
+ CGRQEnable(ULONG aMask)
+ : mMask(aMask) {};
+ HRESULT execute();
+
+ void debugPrint(void *aObject, const char *aFunction, const char *aText);
+ private:
+ ULONG mMask;
+ };
+
+ class CGRQDisable : public CollectorGuestRequest
+ {
+ public:
+ CGRQDisable(ULONG aMask)
+ : mMask(aMask) {};
+ HRESULT execute();
+
+ void debugPrint(void *aObject, const char *aFunction, const char *aText);
+ private:
+ ULONG mMask;
+ };
+
+ class CGRQAbort : public CollectorGuestRequest
+ {
+ public:
+ CGRQAbort() {};
+ HRESULT execute();
+
+ void debugPrint(void *aObject, const char *aFunction, const char *aText);
+ };
+
+ class CollectorGuestQueue
+ {
+ public:
+ CollectorGuestQueue();
+ ~CollectorGuestQueue();
+ void push(CollectorGuestRequest* rq);
+ CollectorGuestRequest* pop();
+ private:
+ RTCLockMtx mLockMtx;
+ RTSEMEVENT mEvent;
+ std::queue<CollectorGuestRequest*> mQueue;
+ };
+
+ class CollectorGuestManager;
+
+ class CollectorGuest
+ {
+ public:
+ CollectorGuest(Machine *machine, RTPROCESS process);
+ ~CollectorGuest();
+
+ void setManager(CollectorGuestManager *aManager)
+ { mManager = aManager; };
+ bool isUnregistered() { return mUnregistered; };
+ bool isEnabled() { return mEnabled != 0; };
+ bool isValid(ULONG mask) { return (mValid & mask) == mask; };
+ void invalidate(ULONG mask) { mValid &= ~mask; };
+ void unregister() { mUnregistered = true; };
+ void updateStats(ULONG aValidStats, ULONG aCpuUser,
+ ULONG aCpuKernel, ULONG aCpuIdle,
+ ULONG aMemTotal, ULONG aMemFree,
+ ULONG aMemBalloon, ULONG aMemShared,
+ ULONG aMemCache, ULONG aPageTotal,
+ ULONG aAllocVMM, ULONG aFreeVMM,
+ ULONG aBalloonedVMM, ULONG aSharedVMM,
+ ULONG aVmNetRx, ULONG aVmNetTx);
+ HRESULT enable(ULONG mask);
+ HRESULT disable(ULONG mask);
+
+ HRESULT enqueueRequest(CollectorGuestRequest *aRequest);
+ HRESULT enableInternal(ULONG mask);
+ HRESULT disableInternal(ULONG mask);
+
+ const com::Utf8Str& getVMName() const { return mMachineName; };
+
+ RTPROCESS getProcess() { return mProcess; };
+ ULONG getCpuUser() { return mCpuUser; };
+ ULONG getCpuKernel() { return mCpuKernel; };
+ ULONG getCpuIdle() { return mCpuIdle; };
+ ULONG getMemTotal() { return mMemTotal; };
+ ULONG getMemFree() { return mMemFree; };
+ ULONG getMemBalloon() { return mMemBalloon; };
+ ULONG getMemShared() { return mMemShared; };
+ ULONG getMemCache() { return mMemCache; };
+ ULONG getPageTotal() { return mPageTotal; };
+ ULONG getAllocVMM() { return mAllocVMM; };
+ ULONG getFreeVMM() { return mFreeVMM; };
+ ULONG getBalloonedVMM() { return mBalloonedVMM; };
+ ULONG getSharedVMM() { return mSharedVMM; };
+ ULONG getVmNetRx() { return mVmNetRx; };
+ ULONG getVmNetTx() { return mVmNetTx; };
+
+ private:
+ HRESULT enableVMMStats(bool mCollectVMMStats);
+
+ CollectorGuestManager *mManager;
+
+ bool mUnregistered;
+ ULONG mEnabled;
+ ULONG mValid;
+ Machine *mMachine;
+ com::Utf8Str mMachineName;
+ RTPROCESS mProcess;
+ ComPtr<IConsole> mConsole;
+ ComPtr<IGuest> mGuest;
+ ULONG mCpuUser;
+ ULONG mCpuKernel;
+ ULONG mCpuIdle;
+ ULONG mMemTotal;
+ ULONG mMemFree;
+ ULONG mMemBalloon;
+ ULONG mMemShared;
+ ULONG mMemCache;
+ ULONG mPageTotal;
+ ULONG mAllocVMM;
+ ULONG mFreeVMM;
+ ULONG mBalloonedVMM;
+ ULONG mSharedVMM;
+ ULONG mVmNetRx;
+ ULONG mVmNetTx;
+ };
+
+ typedef std::list<CollectorGuest*> CollectorGuestList;
+ class CollectorGuestManager
+ {
+ public:
+ CollectorGuestManager();
+ ~CollectorGuestManager();
+ void registerGuest(CollectorGuest* pGuest);
+ void unregisterGuest(CollectorGuest* pGuest);
+ CollectorGuest *getVMMStatsProvider() { return mVMMStatsProvider; };
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void destroyUnregistered();
+ HRESULT enqueueRequest(CollectorGuestRequest *aRequest);
+
+ CollectorGuest *getBlockedGuest() { return mGuestBeingCalled; };
+
+ static DECLCALLBACK(int) requestProcessingThread(RTTHREAD aThread, void *pvUser);
+ private:
+ RTTHREAD mThread;
+ CollectorGuestList mGuests;
+ CollectorGuest *mVMMStatsProvider;
+ CollectorGuestQueue mQueue;
+ CollectorGuest *mGuestBeingCalled;
+ };
+
+ /* Collector Hardware Abstraction Layer *********************************/
+ typedef std::list<RTCString> DiskList;
+
+ class CollectorHAL
+ {
+ public:
+ CollectorHAL() {};
+ virtual ~CollectorHAL() { };
+ virtual int preCollect(const CollectorHints& /* hints */, uint64_t /* iTick */) { return VINF_SUCCESS; }
+ /** Returns averaged CPU usage in 1/1000th per cent across all host's CPUs. */
+ virtual int getHostCpuLoad(ULONG *user, ULONG *kernel, ULONG *idle);
+ /** Returns the average frequency in MHz across all host's CPUs. */
+ virtual int getHostCpuMHz(ULONG *mhz);
+ /** Returns the amount of physical memory in kilobytes. */
+ virtual int getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available);
+ /** Returns file system counters in megabytes. */
+ virtual int getHostFilesystemUsage(const char *name, ULONG *total, ULONG *used, ULONG *available);
+ /** Returns disk size in bytes. */
+ virtual int getHostDiskSize(const char *name, uint64_t *size);
+ /** Returns CPU usage in 1/1000th per cent by a particular process. */
+ virtual int getProcessCpuLoad(RTPROCESS process, ULONG *user, ULONG *kernel);
+ /** Returns the amount of memory used by a process in kilobytes. */
+ virtual int getProcessMemoryUsage(RTPROCESS process, ULONG *used);
+
+ /** Returns CPU usage counters in platform-specific units. */
+ virtual int getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle);
+ /** Returns received and transmitted bytes. */
+ virtual int getRawHostNetworkLoad(const char *name, uint64_t *rx, uint64_t *tx);
+ /** Returns disk usage counters in platform-specific units. */
+ virtual int getRawHostDiskLoad(const char *name, uint64_t *disk_ms, uint64_t *total_ms);
+ /** Returns process' CPU usage counter in platform-specific units. */
+ virtual int getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total);
+
+ /** Returns the lists of disks (aggregate and physical) used by the specified file system. */
+ virtual int getDiskListByFs(const char *name, DiskList& listUsage, DiskList& listLoad);
+ };
+
+ extern CollectorHAL *createHAL();
+
+ /* Base Metrics *********************************************************/
+ class BaseMetric
+ {
+ public:
+ BaseMetric(CollectorHAL *hal, const com::Utf8Str name, ComPtr<IUnknown> object)
+ : mPeriod(0), mLength(0), mHAL(hal), mName(name), mObject(object),
+ mLastSampleTaken(0), mEnabled(false), mUnregistered(false) {};
+ virtual ~BaseMetric() {};
+
+ virtual void init(ULONG period, ULONG length) = 0;
+ virtual void preCollect(CollectorHints& hints, uint64_t iTick) = 0;
+ virtual void collect() = 0;
+ virtual const char *getUnit() = 0;
+ virtual ULONG getMinValue() = 0;
+ virtual ULONG getMaxValue() = 0;
+ virtual ULONG getScale() = 0;
+
+ bool collectorBeat(uint64_t nowAt);
+
+ virtual HRESULT enable() { mEnabled = true; return S_OK; };
+ virtual HRESULT disable() { mEnabled = false; return S_OK; };
+ void unregister() { mUnregistered = true; };
+
+ bool isUnregistered() { return mUnregistered; };
+ bool isEnabled() { return mEnabled; };
+ ULONG getPeriod() { return mPeriod; };
+ ULONG getLength() { return mLength; };
+ const char *getName() { return mName.c_str(); };
+ ComPtr<IUnknown> getObject() { return mObject; };
+ bool associatedWith(ComPtr<IUnknown> object) { return mObject == object; };
+
+ protected:
+ ULONG mPeriod;
+ ULONG mLength;
+ CollectorHAL *mHAL;
+ const com::Utf8Str mName;
+ ComPtr<IUnknown> mObject;
+ uint64_t mLastSampleTaken;
+ bool mEnabled;
+ bool mUnregistered;
+ };
+
+ class BaseGuestMetric : public BaseMetric
+ {
+ public:
+ BaseGuestMetric(CollectorGuest *cguest, const char *name, ComPtr<IUnknown> object)
+ : BaseMetric(NULL, name, object), mCGuest(cguest) {};
+ protected:
+ CollectorGuest *mCGuest;
+ };
+
+ class HostCpuLoad : public BaseMetric
+ {
+ public:
+ HostCpuLoad(CollectorHAL *hal, ComPtr<IUnknown> object, SubMetric *user, SubMetric *kernel, SubMetric *idle)
+ : BaseMetric(hal, "CPU/Load", object), mUser(user), mKernel(kernel), mIdle(idle) {};
+ ~HostCpuLoad() { delete mUser; delete mKernel; delete mIdle; };
+
+ void init(ULONG period, ULONG length);
+
+ void collect();
+ const char *getUnit() { return "%"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return PM_CPU_LOAD_MULTIPLIER; };
+ ULONG getScale() { return PM_CPU_LOAD_MULTIPLIER / 100; }
+
+ protected:
+ SubMetric *mUser;
+ SubMetric *mKernel;
+ SubMetric *mIdle;
+ };
+
+ class HostCpuLoadRaw : public HostCpuLoad
+ {
+ public:
+ HostCpuLoadRaw(CollectorHAL *hal, ComPtr<IUnknown> object, SubMetric *user, SubMetric *kernel, SubMetric *idle)
+ : HostCpuLoad(hal, object, user, kernel, idle), mUserPrev(0), mKernelPrev(0), mIdlePrev(0) {};
+
+ void init(ULONG period, ULONG length);
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void collect();
+ private:
+ uint64_t mUserPrev;
+ uint64_t mKernelPrev;
+ uint64_t mIdlePrev;
+ };
+
+ class HostCpuMhz : public BaseMetric
+ {
+ public:
+ HostCpuMhz(CollectorHAL *hal, ComPtr<IUnknown> object, SubMetric *mhz)
+ : BaseMetric(hal, "CPU/MHz", object), mMHz(mhz) {};
+ ~HostCpuMhz() { delete mMHz; };
+
+ void init(ULONG period, ULONG length);
+ void preCollect(CollectorHints& /* hints */, uint64_t /* iTick */) {}
+ void collect();
+ const char *getUnit() { return "MHz"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return INT32_MAX; };
+ ULONG getScale() { return 1; }
+ private:
+ SubMetric *mMHz;
+ };
+
+ class HostRamUsage : public BaseMetric
+ {
+ public:
+ HostRamUsage(CollectorHAL *hal, ComPtr<IUnknown> object, SubMetric *total, SubMetric *used, SubMetric *available)
+ : BaseMetric(hal, "RAM/Usage", object), mTotal(total), mUsed(used), mAvailable(available) {};
+ ~HostRamUsage() { delete mTotal; delete mUsed; delete mAvailable; };
+
+ void init(ULONG period, ULONG length);
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void collect();
+ const char *getUnit() { return "kB"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return INT32_MAX; };
+ ULONG getScale() { return 1; }
+ private:
+ SubMetric *mTotal;
+ SubMetric *mUsed;
+ SubMetric *mAvailable;
+ };
+
+ class HostNetworkSpeed : public BaseMetric
+ {
+ public:
+ HostNetworkSpeed(CollectorHAL *hal, ComPtr<IUnknown> object, com::Utf8Str name, com::Utf8Str shortname, com::Utf8Str /* ifname */, uint32_t speed, SubMetric *linkspeed)
+ : BaseMetric(hal, name, object), mShortName(shortname), mSpeed(speed), mLinkSpeed(linkspeed) {};
+ ~HostNetworkSpeed() { delete mLinkSpeed; };
+
+ void init(ULONG period, ULONG length);
+
+ void preCollect(CollectorHints& /* hints */, uint64_t /* iTick */) {};
+ void collect() { if (mSpeed) mLinkSpeed->put(mSpeed); };
+ const char *getUnit() { return "mbit/s"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return INT32_MAX; };
+ ULONG getScale() { return 1; }
+ private:
+ com::Utf8Str mShortName;
+ uint32_t mSpeed;
+ SubMetric *mLinkSpeed;
+ };
+
+ class HostNetworkLoadRaw : public BaseMetric
+ {
+ public:
+ HostNetworkLoadRaw(CollectorHAL *hal, ComPtr<IUnknown> object, com::Utf8Str name, com::Utf8Str shortname, com::Utf8Str ifname, uint32_t speed, SubMetric *rx, SubMetric *tx)
+ : BaseMetric(hal, name, object), mShortName(shortname), mInterfaceName(ifname), mRx(rx), mTx(tx), mRxPrev(0), mTxPrev(0), mRc(VINF_SUCCESS) { mSpeed = (uint64_t)speed * (1000000/8); /* Convert to bytes/sec */ };
+ ~HostNetworkLoadRaw() { delete mRx; delete mTx; };
+
+ void init(ULONG period, ULONG length);
+
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void collect();
+ const char *getUnit() { return "%"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return PM_NETWORK_LOAD_MULTIPLIER; };
+ ULONG getScale() { return PM_NETWORK_LOAD_MULTIPLIER / 100; }
+
+ private:
+ com::Utf8Str mShortName;
+ com::Utf8Str mInterfaceName;
+ SubMetric *mRx;
+ SubMetric *mTx;
+ uint64_t mRxPrev;
+ uint64_t mTxPrev;
+ uint64_t mSpeed;
+ int mRc;
+ };
+
+ class HostFilesystemUsage : public BaseMetric
+ {
+ public:
+ HostFilesystemUsage(CollectorHAL *hal, ComPtr<IUnknown> object, com::Utf8Str name, com::Utf8Str fsname, SubMetric *total, SubMetric *used, SubMetric *available)
+ : BaseMetric(hal, name, object), mFsName(fsname), mTotal(total), mUsed(used), mAvailable(available) {};
+ ~HostFilesystemUsage() { delete mTotal; delete mUsed; delete mAvailable; };
+
+ void init(ULONG period, ULONG length);
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void collect();
+ const char *getUnit() { return "MB"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return INT32_MAX; };
+ ULONG getScale() { return 1; }
+ private:
+ com::Utf8Str mFsName;
+ SubMetric *mTotal;
+ SubMetric *mUsed;
+ SubMetric *mAvailable;
+ };
+
+ class HostDiskUsage : public BaseMetric
+ {
+ public:
+ HostDiskUsage(CollectorHAL *hal, ComPtr<IUnknown> object, com::Utf8Str name, com::Utf8Str diskname, SubMetric *total)
+ : BaseMetric(hal, name, object), mDiskName(diskname), mTotal(total) {};
+ ~HostDiskUsage() { delete mTotal; };
+
+ void init(ULONG period, ULONG length);
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void collect();
+ const char *getUnit() { return "MB"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return INT32_MAX; };
+ ULONG getScale() { return 1; }
+ private:
+ com::Utf8Str mDiskName;
+ SubMetric *mTotal;
+ };
+
+ class HostDiskLoadRaw : public BaseMetric
+ {
+ public:
+ HostDiskLoadRaw(CollectorHAL *hal, ComPtr<IUnknown> object, com::Utf8Str name, com::Utf8Str diskname, SubMetric *util)
+ : BaseMetric(hal, name, object), mDiskName(diskname), mUtil(util), mDiskPrev(0), mTotalPrev(0) {};
+ ~HostDiskLoadRaw() { delete mUtil; };
+
+ void init(ULONG period, ULONG length);
+
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void collect();
+ const char *getUnit() { return "%"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return PM_DISK_LOAD_MULTIPLIER; };
+ ULONG getScale() { return PM_DISK_LOAD_MULTIPLIER / 100; }
+
+ private:
+ com::Utf8Str mDiskName;
+ SubMetric *mUtil;
+ uint64_t mDiskPrev;
+ uint64_t mTotalPrev;
+ };
+
+
+#ifndef VBOX_COLLECTOR_TEST_CASE
+ class HostRamVmm : public BaseMetric
+ {
+ public:
+ HostRamVmm(CollectorGuestManager *gm, ComPtr<IUnknown> object, SubMetric *allocVMM, SubMetric *freeVMM, SubMetric *balloonVMM, SubMetric *sharedVMM)
+ : BaseMetric(NULL, "RAM/VMM", object), mCollectorGuestManager(gm),
+ mAllocVMM(allocVMM), mFreeVMM(freeVMM), mBalloonVMM(balloonVMM), mSharedVMM(sharedVMM),
+ mAllocCurrent(0), mFreeCurrent(0), mBalloonedCurrent(0), mSharedCurrent(0) {};
+ ~HostRamVmm() { delete mAllocVMM; delete mFreeVMM; delete mBalloonVMM; delete mSharedVMM; };
+
+ void init(ULONG period, ULONG length);
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void collect();
+ HRESULT enable();
+ HRESULT disable();
+ const char *getUnit() { return "kB"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return INT32_MAX; };
+ ULONG getScale() { return 1; }
+
+ private:
+ CollectorGuestManager *mCollectorGuestManager;
+ SubMetric *mAllocVMM;
+ SubMetric *mFreeVMM;
+ SubMetric *mBalloonVMM;
+ SubMetric *mSharedVMM;
+ ULONG mAllocCurrent;
+ ULONG mFreeCurrent;
+ ULONG mBalloonedCurrent;
+ ULONG mSharedCurrent;
+ };
+#endif /* VBOX_COLLECTOR_TEST_CASE */
+
+ class MachineCpuLoad : public BaseMetric
+ {
+ public:
+ MachineCpuLoad(CollectorHAL *hal, ComPtr<IUnknown> object, RTPROCESS process, SubMetric *user, SubMetric *kernel)
+ : BaseMetric(hal, "CPU/Load", object), mProcess(process), mUser(user), mKernel(kernel) {};
+ ~MachineCpuLoad() { delete mUser; delete mKernel; };
+
+ void init(ULONG period, ULONG length);
+ void collect();
+ const char *getUnit() { return "%"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return PM_CPU_LOAD_MULTIPLIER; };
+ ULONG getScale() { return PM_CPU_LOAD_MULTIPLIER / 100; }
+ protected:
+ RTPROCESS mProcess;
+ SubMetric *mUser;
+ SubMetric *mKernel;
+ };
+
+ class MachineCpuLoadRaw : public MachineCpuLoad
+ {
+ public:
+ MachineCpuLoadRaw(CollectorHAL *hal, ComPtr<IUnknown> object, RTPROCESS process, SubMetric *user, SubMetric *kernel)
+ : MachineCpuLoad(hal, object, process, user, kernel), mHostTotalPrev(0), mProcessUserPrev(0), mProcessKernelPrev(0) {};
+
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void collect();
+ private:
+ uint64_t mHostTotalPrev;
+ uint64_t mProcessUserPrev;
+ uint64_t mProcessKernelPrev;
+ };
+
+ class MachineRamUsage : public BaseMetric
+ {
+ public:
+ MachineRamUsage(CollectorHAL *hal, ComPtr<IUnknown> object, RTPROCESS process, SubMetric *used)
+ : BaseMetric(hal, "RAM/Usage", object), mProcess(process), mUsed(used) {};
+ ~MachineRamUsage() { delete mUsed; };
+
+ void init(ULONG period, ULONG length);
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void collect();
+ const char *getUnit() { return "kB"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return INT32_MAX; };
+ ULONG getScale() { return 1; }
+ private:
+ RTPROCESS mProcess;
+ SubMetric *mUsed;
+ };
+
+
+#ifndef VBOX_COLLECTOR_TEST_CASE
+ typedef std::list<ComObjPtr<Medium> > MediaList;
+ class MachineDiskUsage : public BaseMetric
+ {
+ public:
+ MachineDiskUsage(CollectorHAL *hal, ComPtr<IUnknown> object, MediaList &disks, SubMetric *used)
+ : BaseMetric(hal, "Disk/Usage", object), mDisks(disks), mUsed(used) {};
+ ~MachineDiskUsage() { delete mUsed; };
+
+ void init(ULONG period, ULONG length);
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void collect();
+ const char *getUnit() { return "MB"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return INT32_MAX; };
+ ULONG getScale() { return 1; }
+ private:
+ MediaList mDisks;
+ SubMetric *mUsed;
+ };
+
+ /*
+ * Although MachineNetRate is measured for VM, not for the guest, it is
+ * derived from BaseGuestMetric since it uses the same mechanism for
+ * data collection -- values get pushed by Guest class along with other
+ * guest statistics.
+ */
+ class MachineNetRate : public BaseGuestMetric
+ {
+ public:
+ MachineNetRate(CollectorGuest *cguest, ComPtr<IUnknown> object, SubMetric *rx, SubMetric *tx)
+ : BaseGuestMetric(cguest, "Net/Rate", object), mRx(rx), mTx(tx) {};
+ ~MachineNetRate() { delete mRx; delete mTx; };
+
+ void init(ULONG period, ULONG length);
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void collect();
+ HRESULT enable();
+ HRESULT disable();
+ const char *getUnit() { return "B/s"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return INT32_MAX; };
+ ULONG getScale() { return 1; }
+ private:
+ SubMetric *mRx, *mTx;
+ };
+
+ class GuestCpuLoad : public BaseGuestMetric
+ {
+ public:
+ GuestCpuLoad(CollectorGuest *cguest, ComPtr<IUnknown> object, SubMetric *user, SubMetric *kernel, SubMetric *idle)
+ : BaseGuestMetric(cguest, "Guest/CPU/Load", object), mUser(user), mKernel(kernel), mIdle(idle) {};
+ ~GuestCpuLoad() { delete mUser; delete mKernel; delete mIdle; };
+
+ void init(ULONG period, ULONG length);
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void collect();
+ HRESULT enable();
+ HRESULT disable();
+ const char *getUnit() { return "%"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return PM_CPU_LOAD_MULTIPLIER; };
+ ULONG getScale() { return PM_CPU_LOAD_MULTIPLIER / 100; }
+ protected:
+ SubMetric *mUser;
+ SubMetric *mKernel;
+ SubMetric *mIdle;
+ };
+
+ class GuestRamUsage : public BaseGuestMetric
+ {
+ public:
+ GuestRamUsage(CollectorGuest *cguest, ComPtr<IUnknown> object, SubMetric *total, SubMetric *free, SubMetric *balloon, SubMetric *shared, SubMetric *cache, SubMetric *pagedtotal)
+ : BaseGuestMetric(cguest, "Guest/RAM/Usage", object), mTotal(total), mFree(free), mBallooned(balloon), mCache(cache), mPagedTotal(pagedtotal), mShared(shared) {};
+ ~GuestRamUsage() { delete mTotal; delete mFree; delete mBallooned; delete mShared; delete mCache; delete mPagedTotal; };
+
+ void init(ULONG period, ULONG length);
+ void preCollect(CollectorHints& hints, uint64_t iTick);
+ void collect();
+ HRESULT enable();
+ HRESULT disable();
+ const char *getUnit() { return "kB"; };
+ ULONG getMinValue() { return 0; };
+ ULONG getMaxValue() { return INT32_MAX; };
+ ULONG getScale() { return 1; }
+ private:
+ SubMetric *mTotal, *mFree, *mBallooned, *mCache, *mPagedTotal, *mShared;
+ };
+#endif /* VBOX_COLLECTOR_TEST_CASE */
+
+ /* Aggregate Functions **************************************************/
+ class Aggregate
+ {
+ public:
+ virtual ULONG compute(ULONG *data, ULONG length) = 0;
+ virtual const char *getName() = 0;
+ virtual ~Aggregate() {}
+ };
+
+ class AggregateAvg : public Aggregate
+ {
+ public:
+ virtual ULONG compute(ULONG *data, ULONG length);
+ virtual const char *getName();
+ };
+
+ class AggregateMin : public Aggregate
+ {
+ public:
+ virtual ULONG compute(ULONG *data, ULONG length);
+ virtual const char *getName();
+ };
+
+ class AggregateMax : public Aggregate
+ {
+ public:
+ virtual ULONG compute(ULONG *data, ULONG length);
+ virtual const char *getName();
+ };
+
+ /* Metric Class *********************************************************/
+ class Metric
+ {
+ public:
+ Metric(BaseMetric *baseMetric, SubMetric *subMetric, Aggregate *aggregate) :
+ mName(subMetric->getName()), mBaseMetric(baseMetric), mSubMetric(subMetric), mAggregate(aggregate)
+ {
+ if (mAggregate)
+ {
+ mName.append(":");
+ mName.append(mAggregate->getName());
+ }
+ }
+
+ ~Metric()
+ {
+ delete mAggregate;
+ }
+ bool associatedWith(ComPtr<IUnknown> object) { return getObject() == object; };
+
+ const char *getName() { return mName.c_str(); };
+ ComPtr<IUnknown> getObject() { return mBaseMetric->getObject(); };
+ const char *getDescription()
+ { return mAggregate ? "" : mSubMetric->getDescription(); };
+ const char *getUnit() { return mBaseMetric->getUnit(); };
+ ULONG getMinValue() { return mBaseMetric->getMinValue(); };
+ ULONG getMaxValue() { return mBaseMetric->getMaxValue(); };
+ ULONG getPeriod() { return mBaseMetric->getPeriod(); };
+ ULONG getLength()
+ { return mAggregate ? 1 : mBaseMetric->getLength(); };
+ ULONG getScale() { return mBaseMetric->getScale(); }
+ void query(ULONG **data, ULONG *count, ULONG *sequenceNumber);
+
+ private:
+ RTCString mName;
+ BaseMetric *mBaseMetric;
+ SubMetric *mSubMetric;
+ Aggregate *mAggregate;
+ };
+
+ /* Filter Class *********************************************************/
+
+ class Filter
+ {
+ public:
+ Filter(const std::vector<com::Utf8Str> &metricNames,
+ const std::vector<ComPtr<IUnknown> > &objects);
+ Filter(const com::Utf8Str &name, const ComPtr<IUnknown> &aObject);
+ static bool patternMatch(const char *pszPat, const char *pszName,
+ bool fSeenColon = false);
+ bool match(const ComPtr<IUnknown> object, const RTCString &name) const;
+ private:
+ typedef std::pair<const ComPtr<IUnknown>, const RTCString> FilterElement;
+ typedef std::list<FilterElement> ElementList;
+
+ ElementList mElements;
+
+ void processMetricList(const com::Utf8Str &name, const ComPtr<IUnknown> object);
+ };
+}
+#endif /* !MAIN_INCLUDED_Performance_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/PerformanceImpl.h b/src/VBox/Main/include/PerformanceImpl.h
new file mode 100644
index 00000000..3b8154cc
--- /dev/null
+++ b/src/VBox/Main/include/PerformanceImpl.h
@@ -0,0 +1,206 @@
+/* $Id: PerformanceImpl.h $ */
+
+/** @file
+ *
+ * VBox Performance COM class implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_PerformanceImpl_h
+#define MAIN_INCLUDED_PerformanceImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "PerformanceCollectorWrap.h"
+#include "PerformanceMetricWrap.h"
+
+#include <VBox/com/com.h>
+#include <VBox/com/array.h>
+//#ifdef VBOX_WITH_RESOURCE_USAGE_API
+#include <iprt/timer.h>
+//#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+
+#include <list>
+
+namespace pm
+{
+ class Metric;
+ class BaseMetric;
+ class CollectorHAL;
+ class CollectorGuest;
+ class CollectorGuestManager;
+}
+
+#undef min
+#undef max
+
+/* Each second we obtain new CPU load stats. */
+#define VBOX_USAGE_SAMPLER_MIN_INTERVAL 1000
+
+class ATL_NO_VTABLE PerformanceMetric :
+ public PerformanceMetricWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(PerformanceMetric)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(pm::Metric *aMetric);
+ HRESULT init(pm::BaseMetric *aMetric);
+ void uninit();
+
+private:
+
+ // wrapped IPerformanceMetric properties
+ HRESULT getMetricName(com::Utf8Str &aMetricName);
+ HRESULT getObject(ComPtr<IUnknown> &aObject);
+ HRESULT getDescription(com::Utf8Str &aDescription);
+ HRESULT getPeriod(ULONG *aPeriod);
+ HRESULT getCount(ULONG *aCount);
+ HRESULT getUnit(com::Utf8Str &aUnit);
+ HRESULT getMinimumValue(LONG *aMinimumValue);
+ HRESULT getMaximumValue(LONG *aMaximumValue);
+
+ struct Data
+ {
+ /* Constructor. */
+ Data()
+ : period(0), count(0), min(0), max(0)
+ {
+ }
+
+ Utf8Str name;
+ ComPtr<IUnknown> object;
+ Utf8Str description;
+ ULONG period;
+ ULONG count;
+ Utf8Str unit;
+ LONG min;
+ LONG max;
+ };
+
+ Data m;
+};
+
+
+class ATL_NO_VTABLE PerformanceCollector :
+ public PerformanceCollectorWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(PerformanceCollector)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializers/uninitializers only for internal purposes
+ HRESULT init();
+ void uninit();
+
+ // public methods only for internal purposes
+
+ void registerBaseMetric(pm::BaseMetric *baseMetric);
+ void registerMetric(pm::Metric *metric);
+ void unregisterBaseMetricsFor(const ComPtr<IUnknown> &object, const Utf8Str name = "*");
+ void unregisterMetricsFor(const ComPtr<IUnknown> &object, const Utf8Str name = "*");
+ void registerGuest(pm::CollectorGuest* pGuest);
+ void unregisterGuest(pm::CollectorGuest* pGuest);
+
+ void suspendSampling();
+ void resumeSampling();
+
+ // public methods for internal purposes only
+ // (ensure there is a caller and a read lock before calling them!)
+
+ pm::CollectorHAL *getHAL() { return m.hal; };
+ pm::CollectorGuestManager *getGuestManager() { return m.gm; };
+
+private:
+
+ // wrapped IPerformanceCollector properties
+ HRESULT getMetricNames(std::vector<com::Utf8Str> &aMetricNames);
+
+ // wrapped IPerformanceCollector methods
+ HRESULT getMetrics(const std::vector<com::Utf8Str> &aMetricNames,
+ const std::vector<ComPtr<IUnknown> > &aObjects,
+ std::vector<ComPtr<IPerformanceMetric> > &aMetrics);
+ HRESULT setupMetrics(const std::vector<com::Utf8Str> &aMetricNames,
+ const std::vector<ComPtr<IUnknown> > &aObjects,
+ ULONG aPeriod,
+ ULONG aCount,
+ std::vector<ComPtr<IPerformanceMetric> > &aAffectedMetrics);
+ HRESULT enableMetrics(const std::vector<com::Utf8Str> &aMetricNames,
+ const std::vector<ComPtr<IUnknown> > &aObjects,
+ std::vector<ComPtr<IPerformanceMetric> > &aAffectedMetrics);
+ HRESULT disableMetrics(const std::vector<com::Utf8Str> &aMetricNames,
+ const std::vector<ComPtr<IUnknown> > &aObjects,
+ std::vector<ComPtr<IPerformanceMetric> > &aAffectedMetrics);
+ HRESULT queryMetricsData(const std::vector<com::Utf8Str> &aMetricNames,
+ const std::vector<ComPtr<IUnknown> > &aObjects,
+ std::vector<com::Utf8Str> &aReturnMetricNames,
+ std::vector<ComPtr<IUnknown> > &aReturnObjects,
+ std::vector<com::Utf8Str> &aReturnUnits,
+ std::vector<ULONG> &aReturnScales,
+ std::vector<ULONG> &aReturnSequenceNumbers,
+ std::vector<ULONG> &aReturnDataIndices,
+ std::vector<ULONG> &aReturnDataLengths,
+ std::vector<LONG> &aReturnData);
+
+
+ HRESULT toIPerformanceMetric(pm::Metric *src, ComPtr<IPerformanceMetric> &dst);
+ HRESULT toIPerformanceMetric(pm::BaseMetric *src, ComPtr<IPerformanceMetric> &dst);
+
+ static DECLCALLBACK(void) staticSamplerCallback(RTTIMERLR hTimerLR, void *pvUser, uint64_t iTick);
+ void samplerCallback(uint64_t iTick);
+
+ const Utf8Str& getFailedGuestName();
+
+ typedef std::list<pm::Metric*> MetricList;
+ typedef std::list<pm::BaseMetric*> BaseMetricList;
+
+/** PerformanceMetric::mMagic value. */
+#define PERFORMANCE_METRIC_MAGIC UINT32_C(0xABBA1972)
+ uint32_t mMagic;
+ const Utf8Str mUnknownGuest;
+
+ struct Data
+ {
+ Data() : hal(0) {};
+
+ BaseMetricList baseMetrics;
+ MetricList metrics;
+ RTTIMERLR sampler;
+ pm::CollectorHAL *hal;
+ pm::CollectorGuestManager *gm;
+ };
+
+ Data m;
+};
+
+#endif /* !MAIN_INCLUDED_PerformanceImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/ProgressImpl.h b/src/VBox/Main/include/ProgressImpl.h
new file mode 100644
index 00000000..473f6c8a
--- /dev/null
+++ b/src/VBox/Main/include/ProgressImpl.h
@@ -0,0 +1,259 @@
+/* $Id: ProgressImpl.h $ */
+/** @file
+ *
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ProgressImpl_h
+#define MAIN_INCLUDED_ProgressImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "ProgressWrap.h"
+#include "VirtualBoxBase.h"
+#include "EventImpl.h"
+
+#include <iprt/semaphore.h>
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Class for progress objects.
+ */
+class ATL_NO_VTABLE Progress :
+ public ProgressWrap
+{
+public:
+ DECLARE_NOT_AGGREGATABLE(Progress)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+
+ /**
+ * Simplified constructor for progress objects that have only one
+ * operation as a task.
+ * @param aParent
+ * @param aInitiator
+ * @param aDescription
+ * @param aCancelable
+ * @return
+ */
+ HRESULT init(
+#if !defined(VBOX_COM_INPROC)
+ VirtualBox *aParent,
+#endif
+ IUnknown *aInitiator,
+ const Utf8Str &aDescription,
+ BOOL aCancelable)
+ {
+ return init(
+#if !defined(VBOX_COM_INPROC)
+ aParent,
+#endif
+ aInitiator,
+ aDescription,
+ aCancelable,
+ 1, // cOperations
+ 1, // ulTotalOperationsWeight
+ aDescription, // aFirstOperationDescription
+ 1); // ulFirstOperationWeight
+ }
+
+ /**
+ * Not quite so simplified constructor for progress objects that have
+ * more than one operation, but all sub-operations are weighed the same.
+ * @param aParent
+ * @param aInitiator
+ * @param aDescription
+ * @param aCancelable
+ * @param cOperations
+ * @param aFirstOperationDescription
+ * @return
+ */
+ HRESULT init(
+#if !defined(VBOX_COM_INPROC)
+ VirtualBox *aParent,
+#endif
+ IUnknown *aInitiator,
+ const Utf8Str &aDescription, BOOL aCancelable,
+ ULONG cOperations,
+ const Utf8Str &aFirstOperationDescription)
+ {
+ return init(
+#if !defined(VBOX_COM_INPROC)
+ aParent,
+#endif
+ aInitiator,
+ aDescription,
+ aCancelable,
+ cOperations, // cOperations
+ cOperations, // ulTotalOperationsWeight = cOperations
+ aFirstOperationDescription, // aFirstOperationDescription
+ 1); // ulFirstOperationWeight: weigh them all the same
+ }
+
+ HRESULT init(
+#if !defined(VBOX_COM_INPROC)
+ VirtualBox *aParent,
+#endif
+ IUnknown *aInitiator,
+ const Utf8Str &aDescription,
+ BOOL aCancelable,
+ ULONG cOperations,
+ ULONG ulTotalOperationsWeight,
+ const Utf8Str &aFirstOperationDescription,
+ ULONG ulFirstOperationWeight);
+
+ HRESULT init(BOOL aCancelable,
+ ULONG aOperationCount,
+ const Utf8Str &aOperationDescription);
+
+ void uninit();
+
+
+ // public methods only for internal purposes
+ HRESULT i_notifyComplete(HRESULT aResultCode);
+ HRESULT i_notifyComplete(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const char *aText,
+ ...);
+ HRESULT i_notifyCompleteV(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const char *aText,
+ va_list va);
+ HRESULT i_notifyCompleteBoth(HRESULT aResultCode,
+ int vrc,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const char *aText,
+ ...);
+ HRESULT i_notifyCompleteBothV(HRESULT aResultCode,
+ int vrc,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const char *aText,
+ va_list va);
+
+ bool i_setCancelCallback(void (*pfnCallback)(void *), void *pvUser);
+
+ static DECLCALLBACK(int) i_iprtProgressCallback(unsigned uPercentage, void *pvUser);
+ static DECLCALLBACK(int) i_vdProgressCallback(void *pvUser, unsigned uPercentage);
+
+protected:
+ DECLARE_COMMON_CLASS_METHODS(Progress)
+
+#if !defined(VBOX_COM_INPROC)
+ /** Weak parent. */
+ VirtualBox * const mParent;
+#endif
+ const ComObjPtr<EventSource> pEventSource;
+ const ComPtr<IUnknown> mInitiator;
+
+ const Guid mId;
+ const com::Utf8Str mDescription;
+
+ uint64_t m_ullTimestamp; // progress object creation timestamp, for ETA computation
+
+ void (*m_pfnCancelCallback)(void *);
+ void *m_pvCancelUserArg;
+
+ /* The fields below are to be properly initialized by subclasses */
+
+ BOOL mCompleted;
+ BOOL mCancelable;
+ BOOL mCanceled;
+ HRESULT mResultCode;
+ ComPtr<IVirtualBoxErrorInfo> mErrorInfo;
+
+ ULONG m_cOperations; // number of operations (so that progress dialog can
+ // display something like 1/3)
+ ULONG m_ulTotalOperationsWeight; // sum of weights of all operations, given to constructor
+
+ ULONG m_ulOperationsCompletedWeight; // summed-up weight of operations that have been completed; initially 0
+
+ ULONG m_ulCurrentOperation; // operations counter, incremented with
+ // each setNextOperation()
+ com::Utf8Str m_operationDescription; // name of current operation; initially
+ // from constructor, changed with setNextOperation()
+ ULONG m_ulCurrentOperationWeight; // weight of current operation, given to setNextOperation()
+ ULONG m_ulOperationPercent; // percentage of current operation, set with setCurrentOperationProgress()
+ ULONG m_cMsTimeout; /**< Automatic timeout value. 0 means none. */
+
+private:
+ // wrapped IProgress properties
+ HRESULT getId(com::Guid &aId);
+ HRESULT getDescription(com::Utf8Str &aDescription);
+ HRESULT getInitiator(ComPtr<IUnknown> &aInitiator);
+ HRESULT getCancelable(BOOL *aCancelable);
+ HRESULT getPercent(ULONG *aPercent);
+ HRESULT getTimeRemaining(LONG *aTimeRemaining);
+ HRESULT getCompleted(BOOL *aCompleted);
+ HRESULT getCanceled(BOOL *aCanceled);
+ HRESULT getResultCode(LONG *aResultCode);
+ HRESULT getErrorInfo(ComPtr<IVirtualBoxErrorInfo> &aErrorInfo);
+ HRESULT getOperationCount(ULONG *aOperationCount);
+ HRESULT getOperation(ULONG *aOperation);
+ HRESULT getOperationDescription(com::Utf8Str &aOperationDescription);
+ HRESULT getOperationPercent(ULONG *aOperationPercent);
+ HRESULT getOperationWeight(ULONG *aOperationWeight);
+ HRESULT getTimeout(ULONG *aTimeout);
+ HRESULT setTimeout(ULONG aTimeout);
+ HRESULT getEventSource(ComPtr<IEventSource> &aEventSource);
+
+ // wrapped IProgress methods
+ HRESULT waitForCompletion(LONG aTimeout);
+ HRESULT waitForOperationCompletion(ULONG aOperation,
+ LONG aTimeout);
+ HRESULT cancel();
+
+ // wrapped IInternalProgressControl methods
+ HRESULT setCurrentOperationProgress(ULONG aPercent);
+ HRESULT waitForOtherProgressCompletion(const ComPtr<IProgress> &aProgressOther,
+ ULONG aTimeoutMS);
+ HRESULT setNextOperation(const com::Utf8Str &aNextOperationDescription,
+ ULONG aNextOperationsWeight);
+ HRESULT notifyPointOfNoReturn();
+ HRESULT notifyComplete(LONG aResultCode,
+ const ComPtr<IVirtualBoxErrorInfo> &aErrorInfo);
+
+ // internal helper methods
+ HRESULT i_notifyCompleteWorker(HRESULT aResultCode, const ComPtr<IVirtualBoxErrorInfo> &aErrorInfo);
+ double i_calcTotalPercent();
+ void i_checkForAutomaticTimeout(void);
+
+ RTSEMEVENTMULTI mCompletedSem;
+ ULONG mWaitersCount;
+
+private:
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(Progress); /* Shuts up MSC warning C4625. */
+};
+
+#endif /* !MAIN_INCLUDED_ProgressImpl_h */
+
diff --git a/src/VBox/Main/include/ProgressProxyImpl.h b/src/VBox/Main/include/ProgressProxyImpl.h
new file mode 100644
index 00000000..2e57409c
--- /dev/null
+++ b/src/VBox/Main/include/ProgressProxyImpl.h
@@ -0,0 +1,130 @@
+/* $Id: ProgressProxyImpl.h $ */
+/** @file
+ * IProgress implementation for Machine::LaunchVMProcess in VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ProgressProxyImpl_h
+#define MAIN_INCLUDED_ProgressProxyImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "ProgressImpl.h"
+#include "AutoCaller.h"
+
+
+/**
+ * The ProgressProxy class allows proxying the important Progress calls and
+ * attributes to a different IProgress object for a period of time.
+ */
+class ATL_NO_VTABLE ProgressProxy :
+ public Progress
+{
+public:
+ VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(ProgressProxy, IProgress)
+
+ DECLARE_NOT_AGGREGATABLE(ProgressProxy)
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ BEGIN_COM_MAP(ProgressProxy)
+ COM_INTERFACE_ENTRY(ISupportErrorInfo)
+ COM_INTERFACE_ENTRY(IProgress)
+ COM_INTERFACE_ENTRY2(IDispatch, IProgress)
+ VBOX_TWEAK_INTERFACE_ENTRY(IProgress)
+ END_COM_MAP()
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+ HRESULT init(
+#ifndef VBOX_COM_INPROC
+ VirtualBox *pParent,
+#endif
+ IUnknown *pInitiator,
+ Utf8Str strDescription,
+ BOOL fCancelable);
+ HRESULT init(
+#ifndef VBOX_COM_INPROC
+ VirtualBox *pParent,
+#endif
+ IUnknown *pInitiator,
+ Utf8Str strDescription,
+ BOOL fCancelable,
+ ULONG uTotalOperationsWeight,
+ Utf8Str strFirstOperationDescription,
+ ULONG uFirstOperationWeight,
+ ULONG cOtherProgressObjectOperations);
+ void uninit();
+
+ // IProgress properties
+ STDMETHOD(COMGETTER(Cancelable))(BOOL *aCancelable);
+ STDMETHOD(COMGETTER(Percent))(ULONG *aPercent);
+ STDMETHOD(COMGETTER(TimeRemaining))(LONG *aTimeRemaining);
+ STDMETHOD(COMGETTER(Completed))(BOOL *aCompleted);
+ STDMETHOD(COMGETTER(Canceled))(BOOL *aCanceled);
+ STDMETHOD(COMGETTER(ResultCode))(LONG *aResultCode);
+ STDMETHOD(COMGETTER(ErrorInfo))(IVirtualBoxErrorInfo **aErrorInfo);
+ //STDMETHOD(COMGETTER(OperationCount))(ULONG *aOperationCount); - not necessary
+ STDMETHOD(COMGETTER(Operation))(ULONG *aOperation);
+ STDMETHOD(COMGETTER(OperationDescription))(BSTR *aOperationDescription);
+ STDMETHOD(COMGETTER(OperationPercent))(ULONG *aOperationPercent);
+ STDMETHOD(COMSETTER(Timeout))(ULONG aTimeout);
+ STDMETHOD(COMGETTER(Timeout))(ULONG *aTimeout);
+
+ // IProgress methods
+ STDMETHOD(WaitForCompletion)(LONG aTimeout);
+ STDMETHOD(WaitForOperationCompletion)(ULONG aOperation, LONG aTimeout);
+ STDMETHOD(Cancel)();
+ STDMETHOD(SetCurrentOperationProgress)(ULONG aPercent);
+ STDMETHOD(SetNextOperation)(IN_BSTR bstrNextOperationDescription, ULONG ulNextOperationsWeight);
+
+ // public methods only for internal purposes
+
+ HRESULT notifyComplete(HRESULT aResultCode);
+ HRESULT notifyComplete(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const char *aText, ...);
+ bool setOtherProgressObject(IProgress *pOtherProgress);
+
+protected:
+ void clearOtherProgressObjectInternal(bool fEarly);
+ void copyProgressInfo(IProgress *pOtherProgress, bool fEarly);
+
+private:
+ /** The other progress object. This can be NULL. */
+ ComPtr<IProgress> mptrOtherProgress;
+ /** Set if the other progress object has multiple operations. */
+ bool mfMultiOperation;
+ /** The weight the other progress object started at. */
+ ULONG muOtherProgressStartWeight;
+ /** The weight of other progress object. */
+ ULONG muOtherProgressWeight;
+ /** The operation number the other progress object started at. */
+ ULONG muOtherProgressStartOperation;
+
+};
+
+#endif /* !MAIN_INCLUDED_ProgressProxyImpl_h */
+
diff --git a/src/VBox/Main/include/QMTranslator.h b/src/VBox/Main/include/QMTranslator.h
new file mode 100644
index 00000000..82b9439f
--- /dev/null
+++ b/src/VBox/Main/include/QMTranslator.h
@@ -0,0 +1,79 @@
+/* $Id: QMTranslator.h $ */
+/** @file
+ * VirtualBox API translation handling class
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_QMTranslator_h
+#define MAIN_INCLUDED_QMTranslator_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+class QMTranslator_Impl;
+
+class QMTranslator
+{
+public:
+ QMTranslator();
+ virtual ~QMTranslator();
+
+ /**
+ * Gets translation from loaded QM file
+ *
+ * @param pszContext QM context to look for translation
+ * @param pszSource Source string in one-byte encoding
+ * @param ppszSafeSource Where to return pointer to a safe copy of @a
+ * pszSource for the purpose of reverse translation.
+ * Will be set to NULL if @a pszSource is returned.
+ * @param pszDisamb Disambiguationg comment, empty by default
+ * @param aNum Plural form indicator.
+ *
+ * @returns Pointer to a translation (UTF-8 encoding), source string on failure.
+ */
+ const char *translate(const char *pszContext, const char *pszSource, const char **ppszSafeSource,
+ const char *pszDisamb = NULL, const size_t aNum = ~(size_t)0) const RT_NOEXCEPT;
+
+ /**
+ * Loads and parses QM file
+ *
+ * @param pszFilename The name of the file to load
+ * @param hStrCache The string cache to use for storing strings.
+ *
+ * @returns VBox status code.
+ */
+ int load(const char *pszFilename, RTSTRCACHE hStrCache) RT_NOEXCEPT;
+
+private:
+ /** QMTranslator implementation.
+ * To separate all the code from the interface */
+ QMTranslator_Impl *m_impl;
+
+ /* If copying is required, please define the following operators */
+ void operator=(QMTranslator &);
+ QMTranslator(const QMTranslator &);
+};
+
+#endif /* !MAIN_INCLUDED_QMTranslator_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/Recording.h b/src/VBox/Main/include/Recording.h
new file mode 100644
index 00000000..0c4c9361
--- /dev/null
+++ b/src/VBox/Main/include/Recording.h
@@ -0,0 +1,174 @@
+/* $Id: Recording.h $ */
+/** @file
+ * Recording code header.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_Recording_h
+#define MAIN_INCLUDED_Recording_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/err.h>
+#include <VBox/settings.h>
+
+#include "RecordingStream.h"
+
+class Console;
+
+/**
+ * Class for managing a recording context.
+ */
+class RecordingContext
+{
+public:
+
+ RecordingContext();
+
+ RecordingContext(Console *ptrConsole, const settings::RecordingSettings &Settings);
+
+ virtual ~RecordingContext(void);
+
+public:
+
+ const settings::RecordingSettings &GetConfig(void) const;
+ RecordingStream *GetStream(unsigned uScreen) const;
+ size_t GetStreamCount(void) const;
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ PRECORDINGCODEC GetCodecAudio(void) { return &this->m_CodecAudio; }
+#endif
+
+ int Create(Console *pConsole, const settings::RecordingSettings &Settings);
+ void Destroy(void);
+
+ int Start(void);
+ int Stop(void);
+
+ int SendAudioFrame(const void *pvData, size_t cbData, uint64_t uTimestampMs);
+ int SendVideoFrame(uint32_t uScreen,
+ uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP,
+ uint32_t uBytesPerLine, uint32_t uSrcWidth, uint32_t uSrcHeight,
+ uint8_t *puSrcData, uint64_t msTimestamp);
+public:
+
+ bool IsFeatureEnabled(RecordingFeature_T enmFeature);
+ bool IsReady(void);
+ bool IsReady(uint32_t uScreen, uint64_t msTimestamp);
+ bool IsStarted(void);
+ bool IsLimitReached(void);
+ bool IsLimitReached(uint32_t uScreen, uint64_t msTimestamp);
+ bool NeedsUpdate( uint32_t uScreen, uint64_t msTimestamp);
+
+ DECLCALLBACK(int) OnLimitReached(uint32_t uScreen, int rc);
+
+protected:
+
+ int createInternal(Console *ptrConsole, const settings::RecordingSettings &Settings);
+ int startInternal(void);
+ int stopInternal(void);
+
+ void destroyInternal(void);
+
+ RecordingStream *getStreamInternal(unsigned uScreen) const;
+
+ int processCommonData(RecordingBlockMap &mapCommon, RTMSINTERVAL msTimeout);
+ int writeCommonData(RecordingBlockMap &mapCommon, PRECORDINGCODEC pCodec, const void *pvData, size_t cbData, uint64_t msAbsPTS, uint32_t uFlags);
+
+ int lock(void);
+ int unlock(void);
+
+ static DECLCALLBACK(int) threadMain(RTTHREAD hThreadSelf, void *pvUser);
+
+ int threadNotify(void);
+
+protected:
+
+ int audioInit(const settings::RecordingScreenSettings &screenSettings);
+
+ static DECLCALLBACK(int) audioCodecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData, uint64_t msAbsPTS, uint32_t uFlags, void *pvUser);
+
+protected:
+
+ /**
+ * Enumeration for a recording context state.
+ */
+ enum RECORDINGSTS
+ {
+ /** Context not initialized. */
+ RECORDINGSTS_UNINITIALIZED = 0,
+ /** Context was created. */
+ RECORDINGSTS_CREATED = 1,
+ /** Context was started. */
+ RECORDINGSTS_STARTED = 2,
+ /** The usual 32-bit hack. */
+ RECORDINGSTS_32BIT_HACK = 0x7fffffff
+ };
+
+ /** Pointer to the console object. */
+ Console *m_pConsole;
+ /** Used recording configuration. */
+ settings::RecordingSettings m_Settings;
+ /** The current state. */
+ RECORDINGSTS m_enmState;
+ /** Critical section to serialize access. */
+ RTCRITSECT m_CritSect;
+ /** Semaphore to signal the encoding worker thread. */
+ RTSEMEVENT m_WaitEvent;
+ /** Shutdown indicator. */
+ bool m_fShutdown;
+ /** Encoding worker thread. */
+ RTTHREAD m_Thread;
+ /** Vector of current recording streams.
+ * Per VM screen (display) one recording stream is being used. */
+ RecordingStreams m_vecStreams;
+ /** Number of streams in vecStreams which currently are enabled for recording. */
+ uint16_t m_cStreamsEnabled;
+ /** Timestamp (in ms) of when recording has been started. */
+ uint64_t m_tsStartMs;
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ /** Audio codec to use.
+ *
+ * We multiplex audio data from this recording context to all streams,
+ * to avoid encoding the same audio data for each stream. We ASSUME that
+ * all audio data of a VM will be the same for each stream at a given
+ * point in time. */
+ RECORDINGCODEC m_CodecAudio;
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+ /** Block map of raw common data blocks which need to get encoded first. */
+ RecordingBlockMap m_mapBlocksRaw;
+ /** Block map of encoded common blocks.
+ *
+ * Only do the encoding of common data blocks only once and then multiplex
+ * the encoded data to all affected recording streams.
+ *
+ * This avoids doing the (expensive) encoding + multiplexing work in other
+ * threads like EMT / audio async I/O..
+ *
+ * For now this only affects audio, e.g. all recording streams
+ * need to have the same audio data at a specific point in time. */
+ RecordingBlockMap m_mapBlocksEncoded;
+};
+#endif /* !MAIN_INCLUDED_Recording_h */
+
diff --git a/src/VBox/Main/include/RecordingInternals.h b/src/VBox/Main/include/RecordingInternals.h
new file mode 100644
index 00000000..49e91235
--- /dev/null
+++ b/src/VBox/Main/include/RecordingInternals.h
@@ -0,0 +1,504 @@
+/* $Id: RecordingInternals.h $ */
+/** @file
+ * Recording internals header.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_RecordingInternals_h
+#define MAIN_INCLUDED_RecordingInternals_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <list>
+
+#include <iprt/assert.h>
+#include <iprt/types.h> /* drag in stdint.h before vpx does it. */
+
+#include "VBox/com/string.h"
+#include "VBox/com/VirtualBox.h"
+#include "VBox/settings.h"
+#include <VBox/vmm/pdmaudioifs.h>
+
+#ifdef VBOX_WITH_LIBVPX
+# define VPX_CODEC_DISABLE_COMPAT 1
+# include "vpx/vp8cx.h"
+# include "vpx/vpx_image.h"
+# include "vpx/vpx_encoder.h"
+#endif /* VBOX_WITH_LIBVPX */
+
+#ifdef VBOX_WITH_LIBVORBIS
+# include "vorbis/vorbisenc.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+#define VBOX_RECORDING_VORBIS_HZ_MAX 48000 /**< Maximum sample rate (in Hz) Vorbis can handle. */
+#define VBOX_RECORDING_VORBIS_FRAME_MS_DEFAULT 20 /**< Default Vorbis frame size (in ms). */
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+struct RECORDINGCODEC;
+typedef RECORDINGCODEC *PRECORDINGCODEC;
+
+struct RECORDINGFRAME;
+typedef RECORDINGFRAME *PRECORDINGFRAME;
+
+
+/*********************************************************************************************************************************
+* Internal structures, defines and APIs *
+*********************************************************************************************************************************/
+
+/**
+ * Enumeration for specifying a (generic) codec type.
+ */
+typedef enum RECORDINGCODECTYPE
+{
+ /** Invalid codec type. Do not use. */
+ RECORDINGCODECTYPE_INVALID = 0,
+ /** Video codec. */
+ RECORDINGCODECTYPE_VIDEO,
+ /** Audio codec. */
+ RECORDINGCODECTYPE_AUDIO
+} RECORDINGCODECTYPE;
+
+/**
+ * Structure for keeping a codec operations table.
+ */
+typedef struct RECORDINGCODECOPS
+{
+ /**
+ * Initializes a codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to initialize.
+ */
+ DECLCALLBACKMEMBER(int, pfnInit, (PRECORDINGCODEC pCodec));
+
+ /**
+ * Destroys a codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to destroy.
+ */
+ DECLCALLBACKMEMBER(int, pfnDestroy, (PRECORDINGCODEC pCodec));
+
+ /**
+ * Parses an options string to configure advanced / hidden / experimental features of a recording stream.
+ * Unknown values will be skipped. Optional.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to parse options for.
+ * @param strOptions Options string to parse.
+ */
+ DECLCALLBACKMEMBER(int, pfnParseOptions, (PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions));
+
+ /**
+ * Feeds the codec encoder with data to encode.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to use.
+ * @param pFrame Pointer to frame data to encode.
+ * @param pcEncoded Where to return the number of encoded blocks in \a pvDst on success. Optional.
+ * @param pcbEncoded Where to return the number of encoded bytes in \a pvDst on success. Optional.
+ */
+ DECLCALLBACKMEMBER(int, pfnEncode, (PRECORDINGCODEC pCodec, const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded));
+
+ /**
+ * Tells the codec to finalize the current stream. Optional.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to finalize stream for.
+ */
+ DECLCALLBACKMEMBER(int, pfnFinalize, (PRECORDINGCODEC pCodec));
+} RECORDINGCODECOPS, *PRECORDINGCODECOPS;
+
+/** No encoding flags set. */
+#define RECORDINGCODEC_ENC_F_NONE UINT32_C(0)
+/** Data block is a key block. */
+#define RECORDINGCODEC_ENC_F_BLOCK_IS_KEY RT_BIT_32(0)
+/** Data block is invisible. */
+#define RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE RT_BIT_32(1)
+/** Encoding flags valid mask. */
+#define RECORDINGCODEC_ENC_F_VALID_MASK 0x1
+
+/**
+ * Structure for keeping a codec callback table.
+ */
+typedef struct RECORDINGCODECCALLBACKS
+{
+ /**
+ * Callback for notifying that encoded data has been written.
+ *
+ * @returns VBox status code.
+ * @param pCodec Pointer to codec instance which has written the data.
+ * @param pvData Pointer to written data (encoded).
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param msAbsPTS Absolute PTS (in ms) of the written data.
+ * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
+ * @param pvUser User-supplied pointer.
+ */
+ DECLCALLBACKMEMBER(int, pfnWriteData, (PRECORDINGCODEC pCodec, const void *pvData, size_t cbData, uint64_t msAbsPTS, uint32_t uFlags, void *pvUser));
+ /** User-supplied data pointer. */
+ void *pvUser;
+} RECORDINGCODECCALLBACKS, *PRECORDINGCODECCALLBACKS;
+
+/**
+ * Structure for keeping generic codec parameters.
+ */
+typedef struct RECORDINGCODECPARMS
+{
+ /** The generic codec type. */
+ RECORDINGCODECTYPE enmType;
+ /** The specific codec type, based on \a enmType. */
+ union
+ {
+ /** The container's video codec to use. */
+ RecordingVideoCodec_T enmVideoCodec;
+ /** The container's audio codec to use. */
+ RecordingAudioCodec_T enmAudioCodec;
+ };
+ union
+ {
+ struct
+ {
+ /** Frames per second. */
+ uint8_t uFPS;
+ /** Target width (in pixels) of encoded video image. */
+ uint16_t uWidth;
+ /** Target height (in pixels) of encoded video image. */
+ uint16_t uHeight;
+ /** Minimal delay (in ms) between two video frames.
+ * This value is based on the configured FPS rate. */
+ uint32_t uDelayMs;
+ } Video;
+ struct
+ {
+ /** The codec's used PCM properties. */
+ PDMAUDIOPCMPROPS PCMProps;
+ } Audio;
+ };
+ /** Desired (average) bitrate (in kbps) to use, for codecs which support bitrate management.
+ * Set to 0 to use a variable bit rate (VBR) (if available, otherwise fall back to CBR). */
+ uint32_t uBitrate;
+ /** Time (in ms) the encoder expects us to send data to encode.
+ *
+ * For Vorbis, valid frame sizes are powers of two from 64 to 8192 bytes.
+ */
+ uint32_t msFrame;
+ /** The frame size in bytes (based on \a msFrame). */
+ uint32_t cbFrame;
+ /** The frame size in samples per frame (based on \a msFrame). */
+ uint32_t csFrame;
+} RECORDINGCODECPARMS, *PRECORDINGCODECPARMS;
+
+#ifdef VBOX_WITH_LIBVPX
+/**
+ * VPX encoder state (needs libvpx).
+ */
+typedef struct RECORDINGCODECVPX
+{
+ /** VPX codec context. */
+ vpx_codec_ctx_t Ctx;
+ /** VPX codec configuration. */
+ vpx_codec_enc_cfg_t Cfg;
+ /** VPX image context. */
+ vpx_image_t RawImage;
+ /** Pointer to the codec's internal YUV buffer. */
+ uint8_t *pu8YuvBuf;
+ /** The encoder's deadline (in ms).
+ * The more time the encoder is allowed to spend encoding, the better the encoded
+ * result, in exchange for higher CPU usage and time spent encoding. */
+ unsigned int uEncoderDeadline;
+} RECORDINGCODECVPX;
+/** Pointer to a VPX encoder state. */
+typedef RECORDINGCODECVPX *PRECORDINGCODECVPX;
+#endif /* VBOX_WITH_LIBVPX */
+
+#ifdef VBOX_WITH_LIBVORBIS
+/**
+ * Vorbis encoder state (needs libvorbis + libogg).
+ */
+typedef struct RECORDINGCODECVORBIS
+{
+ /** Basic information about the audio in a Vorbis bitstream. */
+ vorbis_info info;
+ /** Encoder state. */
+ vorbis_dsp_state dsp_state;
+ /** Current block being worked on. */
+ vorbis_block block_cur;
+} RECORDINGCODECVORBIS;
+/** Pointer to a Vorbis encoder state. */
+typedef RECORDINGCODECVORBIS *PRECORDINGCODECVORBIS;
+#endif /* VBOX_WITH_LIBVORBIS */
+
+/**
+ * Structure for keeping a codec's internal state.
+ */
+typedef struct RECORDINGCODECSTATE
+{
+ /** Timestamp Timestamp (PTS, in ms) of the last frame was encoded. */
+ uint64_t tsLastWrittenMs;
+ /** Number of encoding errors. */
+ uint64_t cEncErrors;
+} RECORDINGCODECSTATE;
+/** Pointer to an internal encoder state. */
+typedef RECORDINGCODECSTATE *PRECORDINGCODECSTATE;
+
+/**
+ * Structure for keeping codec-specific data.
+ */
+typedef struct RECORDINGCODEC
+{
+ /** Callback table for codec operations. */
+ RECORDINGCODECOPS Ops;
+ /** Table for user-supplied callbacks. */
+ RECORDINGCODECCALLBACKS Callbacks;
+ /** Generic codec parameters. */
+ RECORDINGCODECPARMS Parms;
+ /** Generic codec parameters. */
+ RECORDINGCODECSTATE State;
+
+#ifdef VBOX_WITH_LIBVPX
+ union
+ {
+ RECORDINGCODECVPX VPX;
+ } Video;
+#endif
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ union
+ {
+# ifdef VBOX_WITH_LIBVORBIS
+ RECORDINGCODECVORBIS Vorbis;
+# endif /* VBOX_WITH_LIBVORBIS */
+ } Audio;
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+
+ /** Internal scratch buffer for en-/decoding steps. */
+ void *pvScratch;
+ /** Size (in bytes) of \a pvScratch. */
+ uint32_t cbScratch;
+
+#ifdef VBOX_WITH_STATISTICS /** @todo Register these values with STAM. */
+ struct
+ {
+ /** Number of frames encoded. */
+ uint64_t cEncBlocks;
+ /** Total time (in ms) of already encoded audio data. */
+ uint64_t msEncTotal;
+ } STAM;
+#endif
+} RECORDINGCODEC, *PRECORDINGCODEC;
+
+/**
+ * Enumeration for supported pixel formats.
+ */
+enum RECORDINGPIXELFMT
+{
+ /** Unknown pixel format. */
+ RECORDINGPIXELFMT_UNKNOWN = 0,
+ /** RGB 24. */
+ RECORDINGPIXELFMT_RGB24 = 1,
+ /** RGB 24. */
+ RECORDINGPIXELFMT_RGB32 = 2,
+ /** RGB 565. */
+ RECORDINGPIXELFMT_RGB565 = 3,
+ /** The usual 32-bit hack. */
+ RECORDINGPIXELFMT_32BIT_HACK = 0x7fffffff
+};
+
+/**
+ * Enumeration for a recording frame type.
+ */
+enum RECORDINGFRAME_TYPE
+{
+ /** Invalid frame type; do not use. */
+ RECORDINGFRAME_TYPE_INVALID = 0,
+ /** Frame is an audio frame. */
+ RECORDINGFRAME_TYPE_AUDIO = 1,
+ /** Frame is an video frame. */
+ RECORDINGFRAME_TYPE_VIDEO = 2,
+ /** Frame contains a video frame pointer. */
+ RECORDINGFRAME_TYPE_VIDEO_PTR = 3
+};
+
+/**
+ * Structure for keeping a single recording video frame.
+ */
+typedef struct RECORDINGVIDEOFRAME
+{
+ /** X origin (in pixel) of this frame. */
+ uint16_t uX;
+ /** X origin (in pixel) of this frame. */
+ uint16_t uY;
+ /** X resolution (in pixel) of this frame. */
+ uint16_t uWidth;
+ /** Y resolution (in pixel) of this frame. */
+ uint16_t uHeight;
+ /** Bits per pixel (BPP). */
+ uint8_t uBPP;
+ /** Pixel format of this frame. */
+ RECORDINGPIXELFMT enmPixelFmt;
+ /** Bytes per scan line. */
+ uint16_t uBytesPerLine;
+ /** RGB buffer containing the unmodified frame buffer data from Main's display. */
+ uint8_t *pu8RGBBuf;
+ /** Size (in bytes) of the RGB buffer. */
+ size_t cbRGBBuf;
+} RECORDINGVIDEOFRAME, *PRECORDINGVIDEOFRAME;
+
+/**
+ * Structure for keeping a single recording audio frame.
+ */
+typedef struct RECORDINGAUDIOFRAME
+{
+ /** Pointer to audio data. */
+ uint8_t *pvBuf;
+ /** Size (in bytes) of audio data. */
+ size_t cbBuf;
+} RECORDINGAUDIOFRAME, *PRECORDINGAUDIOFRAME;
+
+/**
+ * Structure for keeping a single recording audio frame.
+ */
+typedef struct RECORDINGFRAME
+{
+ /** List node. */
+ RTLISTNODE Node;
+ /** Stream index (hint) where this frame should go to.
+ * Specify UINT16_MAX to broadcast to all streams. */
+ uint16_t idStream;
+ /** The frame type. */
+ RECORDINGFRAME_TYPE enmType;
+ /** Timestamp (PTS, in ms). */
+ uint64_t msTimestamp;
+ union
+ {
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ /** Audio frame data. */
+ RECORDINGAUDIOFRAME Audio;
+#endif
+ /** Video frame data. */
+ RECORDINGVIDEOFRAME Video;
+ /** A (weak) pointer to a video frame. */
+ RECORDINGVIDEOFRAME *VideoPtr;
+ };
+} RECORDINGFRAME, *PRECORDINGFRAME;
+
+/**
+ * Enumeration for specifying a video recording block type.
+ */
+typedef enum RECORDINGBLOCKTYPE
+{
+ /** Uknown block type, do not use. */
+ RECORDINGBLOCKTYPE_UNKNOWN = 0,
+ /** The block is a video frame. */
+ RECORDINGBLOCKTYPE_VIDEO,
+ /** The block is an audio frame. */
+ RECORDINGBLOCKTYPE_AUDIO
+} RECORDINGBLOCKTYPE;
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+int RecordingVideoFrameInit(PRECORDINGVIDEOFRAME pFrame, int w, int h, uint8_t uBPP, RECORDINGPIXELFMT enmPixelFmt);
+void RecordingVideoFrameDestroy(PRECORDINGVIDEOFRAME pFrame);
+void RecordingAudioFrameFree(PRECORDINGAUDIOFRAME pFrame);
+#endif
+void RecordingVideoFrameFree(PRECORDINGVIDEOFRAME pFrame);
+void RecordingFrameFree(PRECORDINGFRAME pFrame);
+
+/**
+ * Generic structure for keeping a single video recording (data) block.
+ */
+struct RecordingBlock
+{
+ RecordingBlock()
+ : enmType(RECORDINGBLOCKTYPE_UNKNOWN)
+ , cRefs(0)
+ , uFlags(RECORDINGCODEC_ENC_F_NONE)
+ , pvData(NULL)
+ , cbData(0) { }
+
+ virtual ~RecordingBlock()
+ {
+ Reset();
+ }
+
+ void Reset(void)
+ {
+ switch (enmType)
+ {
+ case RECORDINGBLOCKTYPE_UNKNOWN:
+ break;
+
+ case RECORDINGBLOCKTYPE_VIDEO:
+ RecordingVideoFrameFree((PRECORDINGVIDEOFRAME)pvData);
+ break;
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ case RECORDINGBLOCKTYPE_AUDIO:
+ RecordingAudioFrameFree((PRECORDINGAUDIOFRAME)pvData);
+ break;
+#endif
+ default:
+ AssertFailed();
+ break;
+ }
+
+ enmType = RECORDINGBLOCKTYPE_UNKNOWN;
+ cRefs = 0;
+ pvData = NULL;
+ cbData = 0;
+ }
+
+ /** The block's type. */
+ RECORDINGBLOCKTYPE enmType;
+ /** Number of references held of this block. */
+ uint16_t cRefs;
+ /** Block flags of type RECORDINGCODEC_ENC_F_XXX. */
+ uint64_t uFlags;
+ /** The (absolute) timestamp (in ms, PTS) of this block. */
+ uint64_t msTimestamp;
+ /** Opaque data block to the actual block data, depending on the block's type. */
+ void *pvData;
+ /** Size (in bytes) of the (opaque) data block. */
+ size_t cbData;
+};
+
+/** List for keeping video recording (data) blocks. */
+typedef std::list<RecordingBlock *> RecordingBlockList;
+
+int recordingCodecCreateAudio(PRECORDINGCODEC pCodec, RecordingAudioCodec_T enmAudioCodec);
+int recordingCodecCreateVideo(PRECORDINGCODEC pCodec, RecordingVideoCodec_T enmVideoCodec);
+int recordingCodecInit(const PRECORDINGCODEC pCodec, const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings);
+int recordingCodecDestroy(PRECORDINGCODEC pCodec);
+int recordingCodecEncode(PRECORDINGCODEC pCodec, const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded);
+int recordingCodecFinalize(PRECORDINGCODEC pCodec);
+bool recordingCodecIsInitialized(const PRECORDINGCODEC pCodec);
+uint32_t recordingCodecGetWritable(const PRECORDINGCODEC pCodec, uint64_t msTimestamp);
+#endif /* !MAIN_INCLUDED_RecordingInternals_h */
diff --git a/src/VBox/Main/include/RecordingScreenSettingsImpl.h b/src/VBox/Main/include/RecordingScreenSettingsImpl.h
new file mode 100644
index 00000000..59ff1935
--- /dev/null
+++ b/src/VBox/Main/include/RecordingScreenSettingsImpl.h
@@ -0,0 +1,141 @@
+/* $Id: RecordingScreenSettingsImpl.h $ */
+
+/** @file
+ *
+ * VirtualBox COM class implementation - Recording settings of one virtual screen.
+ */
+
+/*
+ * Copyright (C) 2018-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_RecordingScreenSettingsImpl_h
+#define MAIN_INCLUDED_RecordingScreenSettingsImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "RecordingScreenSettingsWrap.h"
+
+class RecordingSettings;
+
+namespace settings
+{
+ struct RecordingScreenSettings;
+}
+
+class ATL_NO_VTABLE RecordingScreenSettings :
+ public RecordingScreenSettingsWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(RecordingScreenSettings)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(RecordingSettings *aParent, uint32_t uScreenId, const settings::RecordingScreenSettings& aThat);
+ HRESULT init(RecordingSettings *aParent, RecordingScreenSettings *aThat);
+ HRESULT initCopy(RecordingSettings *aParent, RecordingScreenSettings *aThat);
+ void uninit(void);
+
+ // public methods only for internal purposes
+ HRESULT i_loadSettings(const settings::RecordingScreenSettings &data);
+ HRESULT i_saveSettings(settings::RecordingScreenSettings &data);
+
+ void i_rollback(void);
+ void i_commit(void);
+ void i_copyFrom(RecordingScreenSettings *aThat);
+ void i_applyDefaults(void);
+
+ settings::RecordingScreenSettings &i_getData(void);
+
+ int32_t i_reference(void);
+ int32_t i_release(void);
+ int32_t i_getReferences(void);
+
+private:
+
+ // wrapped IRecordingScreenSettings methods
+ HRESULT isFeatureEnabled(RecordingFeature_T aFeature, BOOL *aEnabled);
+
+ // wrapped IRecordingScreenSettings properties
+ HRESULT getId(ULONG *id);
+ HRESULT getEnabled(BOOL *enabled);
+ HRESULT setEnabled(BOOL enabled);
+ HRESULT getFeatures(std::vector<RecordingFeature_T> &aFeatures);
+ HRESULT setFeatures(const std::vector<RecordingFeature_T> &aFeatures);
+ HRESULT getDestination(RecordingDestination_T *aDestination);
+ HRESULT setDestination(RecordingDestination_T aDestination);
+
+ HRESULT getFilename(com::Utf8Str &aFilename);
+ HRESULT setFilename(const com::Utf8Str &aFilename);
+ HRESULT getMaxTime(ULONG *aMaxTimeS);
+ HRESULT setMaxTime(ULONG aMaxTimeS);
+ HRESULT getMaxFileSize(ULONG *aMaxFileSizeMB);
+ HRESULT setMaxFileSize(ULONG aMaxFileSizeMB);
+ HRESULT getOptions(com::Utf8Str &aOptions);
+ HRESULT setOptions(const com::Utf8Str &aOptions);
+
+ HRESULT getAudioCodec(RecordingAudioCodec_T *aCodec);
+ HRESULT setAudioCodec(RecordingAudioCodec_T aCodec);
+ HRESULT getAudioDeadline(RecordingCodecDeadline_T *aDeadline);
+ HRESULT setAudioDeadline(RecordingCodecDeadline_T aDeadline);
+ HRESULT getAudioRateControlMode(RecordingRateControlMode_T *aMode);
+ HRESULT setAudioRateControlMode(RecordingRateControlMode_T aMode);
+ HRESULT getAudioHz(ULONG *aHz);
+ HRESULT setAudioHz(ULONG aHz);
+ HRESULT getAudioBits(ULONG *aBits);
+ HRESULT setAudioBits(ULONG aBits);
+ HRESULT getAudioChannels(ULONG *aChannels);
+ HRESULT setAudioChannels(ULONG aChannels);
+
+ HRESULT getVideoCodec(RecordingVideoCodec_T *aCodec);
+ HRESULT setVideoCodec(RecordingVideoCodec_T aCodec);
+ HRESULT getVideoDeadline(RecordingCodecDeadline_T *aDeadline);
+ HRESULT setVideoDeadline(RecordingCodecDeadline_T aDeadline);
+ HRESULT getVideoWidth(ULONG *aVideoWidth);
+ HRESULT setVideoWidth(ULONG aVideoWidth);
+ HRESULT getVideoHeight(ULONG *aVideoHeight);
+ HRESULT setVideoHeight(ULONG aVideoHeight);
+ HRESULT getVideoRate(ULONG *aVideoRate);
+ HRESULT setVideoRate(ULONG aVideoRate);
+ HRESULT getVideoRateControlMode(RecordingRateControlMode_T *aMode);
+ HRESULT setVideoRateControlMode(RecordingRateControlMode_T aMode);
+ HRESULT getVideoFPS(ULONG *aVideoFPS);
+ HRESULT setVideoFPS(ULONG aVideoFPS);
+ HRESULT getVideoScalingMode(RecordingVideoScalingMode_T *aMode);
+ HRESULT setVideoScalingMode(RecordingVideoScalingMode_T aMode);
+
+private:
+
+ // internal methods
+ int i_initInternal();
+
+private:
+
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_RecordingScreenSettingsImpl_h */
+
diff --git a/src/VBox/Main/include/RecordingSettingsImpl.h b/src/VBox/Main/include/RecordingSettingsImpl.h
new file mode 100644
index 00000000..4fb7bb82
--- /dev/null
+++ b/src/VBox/Main/include/RecordingSettingsImpl.h
@@ -0,0 +1,102 @@
+/* $Id: RecordingSettingsImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation - Machine recording screen settings.
+ */
+
+/*
+ * Copyright (C) 2018-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_RecordingSettingsImpl_h
+#define MAIN_INCLUDED_RecordingSettingsImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "RecordingSettingsWrap.h"
+
+namespace settings
+{
+ struct RecordingSettings;
+ struct RecordingScreenSettings;
+}
+
+class RecordingScreenSettings;
+
+class ATL_NO_VTABLE RecordingSettings
+ : public RecordingSettingsWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(RecordingSettings)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *parent);
+ HRESULT init(Machine *parent, RecordingSettings *aThat);
+ HRESULT initCopy(Machine *parent, RecordingSettings *aThat);
+ void uninit();
+
+ // public methods only for internal purposes
+ HRESULT i_loadSettings(const settings::RecordingSettings &data);
+ HRESULT i_saveSettings(settings::RecordingSettings &data);
+
+ void i_rollback(void);
+ void i_commit(void);
+ HRESULT i_copyFrom(RecordingSettings *aThat);
+ void i_applyDefaults(void);
+
+ int i_getDefaultFilename(Utf8Str &strFile, uint32_t idScreen, bool fWithFileExtension);
+ int i_getFilename(Utf8Str &strFile, uint32_t idScreen, const Utf8Str &strTemplate);
+ bool i_canChangeSettings(void);
+ void i_onSettingsChanged(void);
+
+private:
+
+ /** Map of screen settings objects. The key specifies the screen ID. */
+ typedef std::map <uint32_t, ComObjPtr<RecordingScreenSettings> > RecordingScreenSettingsObjMap;
+
+ void i_reset(void);
+ int i_syncToMachineDisplays(uint32_t cDisplays);
+ int i_createScreenObj(RecordingScreenSettingsObjMap &screenSettingsMap, uint32_t idScreen, const settings::RecordingScreenSettings &data);
+ int i_destroyScreenObj(RecordingScreenSettingsObjMap &screenSettingsMap, uint32_t idScreen);
+ int i_destroyAllScreenObj(RecordingScreenSettingsObjMap &screenSettingsMap);
+
+private:
+
+ // wrapped IRecordingSettings properties
+ HRESULT getEnabled(BOOL *enabled);
+ HRESULT setEnabled(BOOL enable);
+ HRESULT getScreens(std::vector<ComPtr<IRecordingScreenSettings> > &aRecordScreenSettings);
+
+ // wrapped IRecordingSettings methods
+ HRESULT getScreenSettings(ULONG uScreenId, ComPtr<IRecordingScreenSettings> &aRecordScreenSettings);
+
+private:
+
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_RecordingSettingsImpl_h */
+
diff --git a/src/VBox/Main/include/RecordingStream.h b/src/VBox/Main/include/RecordingStream.h
new file mode 100644
index 00000000..ae17a7a7
--- /dev/null
+++ b/src/VBox/Main/include/RecordingStream.h
@@ -0,0 +1,234 @@
+/* $Id: RecordingStream.h $ */
+/** @file
+ * Recording stream code header.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_RecordingStream_h
+#define MAIN_INCLUDED_RecordingStream_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <map>
+#include <vector>
+
+#include <iprt/critsect.h>
+
+#include "RecordingInternals.h"
+
+class WebMWriter;
+class RecordingContext;
+
+/** Structure for queuing all blocks bound to a single timecode.
+ * This can happen if multiple tracks are being involved. */
+struct RecordingBlocks
+{
+ virtual ~RecordingBlocks()
+ {
+ Clear();
+ }
+
+ /**
+ * Resets a recording block list by removing (destroying)
+ * all current elements.
+ */
+ void Clear()
+ {
+ while (!List.empty())
+ {
+ RecordingBlock *pBlock = List.front();
+ List.pop_front();
+ delete pBlock;
+ }
+
+ Assert(List.size() == 0);
+ }
+
+ /** The actual block list for this timecode. */
+ RecordingBlockList List;
+};
+
+/** A block map containing all currently queued blocks.
+ * The key specifies a unique timecode, whereas the value
+ * is a list of blocks which all correlate to the same key (timecode). */
+typedef std::map<uint64_t, RecordingBlocks *> RecordingBlockMap;
+
+/**
+ * Structure for holding a set of recording (data) blocks.
+ */
+struct RecordingBlockSet
+{
+ virtual ~RecordingBlockSet()
+ {
+ Clear();
+ }
+
+ /**
+ * Resets a recording block set by removing (destroying)
+ * all current elements.
+ */
+ void Clear(void)
+ {
+ RecordingBlockMap::iterator it = Map.begin();
+ while (it != Map.end())
+ {
+ it->second->Clear();
+ delete it->second;
+ Map.erase(it);
+ it = Map.begin();
+ }
+
+ Assert(Map.size() == 0);
+ }
+
+ /** Timestamp (in ms) when this set was last processed. */
+ uint64_t tsLastProcessedMs;
+ /** All blocks related to this block set. */
+ RecordingBlockMap Map;
+};
+
+/**
+ * Class for managing a recording stream.
+ *
+ * A recording stream represents one entity to record (e.g. on screen / monitor),
+ * so there is a 1:1 mapping (stream <-> monitors).
+ */
+class RecordingStream
+{
+public:
+
+ RecordingStream(RecordingContext *pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings);
+
+ virtual ~RecordingStream(void);
+
+public:
+
+ int Init(RecordingContext *pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings);
+ int Uninit(void);
+
+ int Process(RecordingBlockMap &mapBlocksCommon);
+ int SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp);
+ int SendVideoFrame(uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
+ uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData, uint64_t msTimestamp);
+
+ const settings::RecordingScreenSettings &GetConfig(void) const;
+ uint16_t GetID(void) const { return this->m_uScreenID; };
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ PRECORDINGCODEC GetAudioCodec(void) { return this->m_pCodecAudio; };
+#endif
+ PRECORDINGCODEC GetVideoCodec(void) { return &this->m_CodecVideo; };
+
+ bool IsLimitReached(uint64_t msTimestamp) const;
+ bool IsReady(void) const;
+ bool NeedsUpdate(uint64_t msTimestamp) const;
+
+public:
+
+ static DECLCALLBACK(int) codecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData, uint64_t msAbsPTS, uint32_t uFlags, void *pvUser);
+
+protected:
+
+ int open(const settings::RecordingScreenSettings &screenSettings);
+ int close(void);
+
+ int initInternal(RecordingContext *pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &screenSettings);
+ int uninitInternal(void);
+
+ int initVideo(const settings::RecordingScreenSettings &screenSettings);
+ int unitVideo(void);
+
+ bool isLimitReachedInternal(uint64_t msTimestamp) const;
+ int iterateInternal(uint64_t msTimestamp);
+
+ int codecWriteToWebM(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData, uint64_t msAbsPTS, uint32_t uFlags);
+
+ void lock(void);
+ void unlock(void);
+
+protected:
+
+ /**
+ * Enumeration for a recording stream state.
+ */
+ enum RECORDINGSTREAMSTATE
+ {
+ /** Stream not initialized. */
+ RECORDINGSTREAMSTATE_UNINITIALIZED = 0,
+ /** Stream was initialized. */
+ RECORDINGSTREAMSTATE_INITIALIZED = 1,
+ /** The usual 32-bit hack. */
+ RECORDINGSTREAMSTATE_32BIT_HACK = 0x7fffffff
+ };
+
+ /** Recording context this stream is associated to. */
+ RecordingContext *m_pCtx;
+ /** The current state. */
+ RECORDINGSTREAMSTATE m_enmState;
+ struct
+ {
+ /** File handle to use for writing. */
+ RTFILE m_hFile;
+ /** Pointer to WebM writer instance being used. */
+ WebMWriter *m_pWEBM;
+ } File;
+ bool m_fEnabled;
+ /** Track number of audio stream.
+ * Set to UINT8_MAX if not being used. */
+ uint8_t m_uTrackAudio;
+ /** Track number of video stream.
+ * Set to UINT8_MAX if not being used. */
+ uint8_t m_uTrackVideo;
+ /** Screen ID. */
+ uint16_t m_uScreenID;
+ /** Critical section to serialize access. */
+ RTCRITSECT m_CritSect;
+ /** Timestamp (in ms) of when recording has been started. */
+ uint64_t m_tsStartMs;
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ /** Pointer to audio codec instance data to use.
+ *
+ * We multiplex audio data from the recording context to all streams,
+ * to avoid encoding the same audio data for each stream. We ASSUME that
+ * all audio data of a VM will be the same for each stream at a given
+ * point in time.
+ *
+ * Might be NULL if not being used. */
+ PRECORDINGCODEC m_pCodecAudio;
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+ /** Video codec instance data to use. */
+ RECORDINGCODEC m_CodecVideo;
+ /** Screen settings to use. */
+ settings::RecordingScreenSettings
+ m_ScreenSettings;
+ /** Common set of recording (data) blocks, needed for
+ * multiplexing to all recording streams. */
+ RecordingBlockSet m_Blocks;
+};
+
+/** Vector of recording streams. */
+typedef std::vector <RecordingStream *> RecordingStreams;
+
+#endif /* !MAIN_INCLUDED_RecordingStream_h */
+
diff --git a/src/VBox/Main/include/RecordingUtils.h b/src/VBox/Main/include/RecordingUtils.h
new file mode 100644
index 00000000..d8c82084
--- /dev/null
+++ b/src/VBox/Main/include/RecordingUtils.h
@@ -0,0 +1,217 @@
+/* $Id: RecordingUtils.h $ */
+/** @file
+ * Recording utility header.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_RecordingUtils_h
+#define MAIN_INCLUDED_RecordingUtils_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "RecordingInternals.h"
+
+
+/**
+ * Iterator class for running through a BGRA32 image buffer and converting
+ * it to RGB.
+ */
+class ColorConvBGRA32Iter
+{
+private:
+ enum { PIX_SIZE = 4 };
+public:
+ ColorConvBGRA32Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
+ {
+ mPos = 0;
+ mSize = aWidth * aHeight * PIX_SIZE;
+ mBuf = aBuf;
+ }
+
+ /**
+ * Convert the next pixel to RGB.
+ *
+ * @returns true on success, false if we have reached the end of the buffer
+ * @param aRed where to store the red value.
+ * @param aGreen where to store the green value.
+ * @param aBlue where to store the blue value.
+ */
+ bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
+ {
+ bool rc = false;
+ if (mPos + PIX_SIZE <= mSize)
+ {
+ *aRed = mBuf[mPos + 2];
+ *aGreen = mBuf[mPos + 1];
+ *aBlue = mBuf[mPos ];
+ mPos += PIX_SIZE;
+ rc = true;
+ }
+ return rc;
+ }
+
+ /**
+ * Skip forward by a certain number of pixels.
+ *
+ * @param aPixels How many pixels to skip.
+ */
+ void skip(unsigned aPixels)
+ {
+ mPos += PIX_SIZE * aPixels;
+ }
+private:
+ /** Size of the picture buffer. */
+ unsigned mSize;
+ /** Current position in the picture buffer. */
+ unsigned mPos;
+ /** Address of the picture buffer. */
+ uint8_t *mBuf;
+};
+
+/**
+ * Iterator class for running through an BGR24 image buffer and converting
+ * it to RGB.
+ */
+class ColorConvBGR24Iter
+{
+private:
+ enum { PIX_SIZE = 3 };
+public:
+ ColorConvBGR24Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
+ {
+ mPos = 0;
+ mSize = aWidth * aHeight * PIX_SIZE;
+ mBuf = aBuf;
+ }
+
+ /**
+ * Convert the next pixel to RGB.
+ *
+ * @returns true on success, false if we have reached the end of the buffer.
+ * @param aRed where to store the red value.
+ * @param aGreen where to store the green value.
+ * @param aBlue where to store the blue value.
+ */
+ bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
+ {
+ bool rc = false;
+ if (mPos + PIX_SIZE <= mSize)
+ {
+ *aRed = mBuf[mPos + 2];
+ *aGreen = mBuf[mPos + 1];
+ *aBlue = mBuf[mPos ];
+ mPos += PIX_SIZE;
+ rc = true;
+ }
+ return rc;
+ }
+
+ /**
+ * Skip forward by a certain number of pixels.
+ *
+ * @param aPixels How many pixels to skip.
+ */
+ void skip(unsigned aPixels)
+ {
+ mPos += PIX_SIZE * aPixels;
+ }
+private:
+ /** Size of the picture buffer. */
+ unsigned mSize;
+ /** Current position in the picture buffer. */
+ unsigned mPos;
+ /** Address of the picture buffer. */
+ uint8_t *mBuf;
+};
+
+/**
+ * Iterator class for running through an BGR565 image buffer and converting
+ * it to RGB.
+ */
+class ColorConvBGR565Iter
+{
+private:
+ enum { PIX_SIZE = 2 };
+public:
+ ColorConvBGR565Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
+ {
+ mPos = 0;
+ mSize = aWidth * aHeight * PIX_SIZE;
+ mBuf = aBuf;
+ }
+
+ /**
+ * Convert the next pixel to RGB.
+ *
+ * @returns true on success, false if we have reached the end of the buffer.
+ * @param aRed Where to store the red value.
+ * @param aGreen where to store the green value.
+ * @param aBlue where to store the blue value.
+ */
+ bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
+ {
+ bool rc = false;
+ if (mPos + PIX_SIZE <= mSize)
+ {
+ unsigned uFull = (((unsigned) mBuf[mPos + 1]) << 8)
+ | ((unsigned) mBuf[mPos]);
+ *aRed = (uFull >> 8) & ~7;
+ *aGreen = (uFull >> 3) & ~3 & 0xff;
+ *aBlue = (uFull << 3) & ~7 & 0xff;
+ mPos += PIX_SIZE;
+ rc = true;
+ }
+ return rc;
+ }
+
+ /**
+ * Skip forward by a certain number of pixels.
+ *
+ * @param aPixels How many pixels to skip.
+ */
+ void skip(unsigned aPixels)
+ {
+ mPos += PIX_SIZE * aPixels;
+ }
+private:
+ /** Size of the picture buffer. */
+ unsigned mSize;
+ /** Current position in the picture buffer. */
+ unsigned mPos;
+ /** Address of the picture buffer. */
+ uint8_t *mBuf;
+};
+
+int RecordingUtilsRGBToYUV(RECORDINGPIXELFMT enmPixelFormat,
+ uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight,
+ uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight);
+
+#ifdef DEBUG
+int RecordingUtilsDbgDumpFrameEx(const uint8_t *pu8RGBBuf, size_t cbRGBBuf, const char *pszPath, const char *pszPrefx, uint16_t uWidth, uint32_t uHeight, uint8_t uBPP);
+int RecordingUtilsDbgDumpFrame(const PRECORDINGFRAME pFrame);
+#endif
+
+#endif /* !MAIN_INCLUDED_RecordingUtils_h */
+
diff --git a/src/VBox/Main/include/RemoteUSBBackend.h b/src/VBox/Main/include/RemoteUSBBackend.h
new file mode 100644
index 00000000..dc59fccc
--- /dev/null
+++ b/src/VBox/Main/include/RemoteUSBBackend.h
@@ -0,0 +1,153 @@
+/* $Id: RemoteUSBBackend.h $ */
+/** @file
+ *
+ * VirtualBox Remote USB backend
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_RemoteUSBBackend_h
+#define MAIN_INCLUDED_RemoteUSBBackend_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "RemoteUSBDeviceImpl.h"
+
+#include <VBox/RemoteDesktop/VRDE.h>
+#include <VBox/vrdpusb.h>
+
+#include <iprt/critsect.h>
+
+//typedef enum
+//{
+// RDLIdle = 0,
+// RDLReqSent,
+// RDLObtained
+//} RDLState;
+
+class Console;
+class ConsoleVRDPServer;
+
+DECLCALLBACK(int) USBClientResponseCallback (void *pv, uint32_t u32ClientId, uint8_t code, const void *pvRet, uint32_t cbRet);
+
+
+/* How many remote devices can be attached to a remote client.
+ * Normally a client computer has 2-8 physical USB ports, so 16 devices
+ * should be usually enough.
+ */
+#define VRDP_MAX_USB_DEVICES_PER_CLIENT (16)
+
+class RemoteUSBBackendListable
+{
+ public:
+ RemoteUSBBackendListable *pNext;
+ RemoteUSBBackendListable *pPrev;
+
+ RemoteUSBBackendListable() : pNext (NULL), pPrev (NULL) {};
+};
+
+class RemoteUSBBackend: public RemoteUSBBackendListable
+{
+ public:
+ RemoteUSBBackend(Console *console, ConsoleVRDPServer *server, uint32_t u32ClientId);
+ ~RemoteUSBBackend();
+
+ uint32_t ClientId (void) { return mu32ClientId; }
+
+ void AddRef (void);
+ void Release (void);
+
+ REMOTEUSBCALLBACK *GetBackendCallbackPointer (void) { return &mCallback; }
+
+ void NotifyDelete (void);
+
+ void PollRemoteDevices (void);
+
+ public: /* Functions for internal use. */
+ ConsoleVRDPServer *VRDPServer (void) { return mServer; };
+
+ bool pollingEnabledURB (void) { return mfPollURB; }
+
+ int saveDeviceList (const void *pvList, uint32_t cbList);
+
+ int negotiateResponse (const VRDEUSBREQNEGOTIATERET *pret, uint32_t cbRet);
+
+ int reapURB (const void *pvBody, uint32_t cbBody);
+
+ void request (void);
+ void release (void);
+
+ PREMOTEUSBDEVICE deviceFromId (VRDEUSBDEVID id);
+
+ void addDevice (PREMOTEUSBDEVICE pDevice);
+ void removeDevice (PREMOTEUSBDEVICE pDevice);
+
+ bool addUUID (const Guid *pUuid);
+ bool findUUID (const Guid *pUuid);
+ void removeUUID (const Guid *pUuid);
+
+ private:
+ Console *mConsole;
+ ConsoleVRDPServer *mServer;
+
+ int cRefs;
+
+ uint32_t mu32ClientId;
+
+ RTCRITSECT mCritsect;
+
+ REMOTEUSBCALLBACK mCallback;
+
+ bool mfHasDeviceList;
+
+ void *mpvDeviceList;
+ uint32_t mcbDeviceList;
+
+ typedef enum {
+ PollRemoteDevicesStatus_Negotiate,
+ PollRemoteDevicesStatus_WaitNegotiateResponse,
+ PollRemoteDevicesStatus_SendRequest,
+ PollRemoteDevicesStatus_WaitResponse,
+ PollRemoteDevicesStatus_Dereferenced
+ } PollRemoteDevicesStatus;
+
+ PollRemoteDevicesStatus menmPollRemoteDevicesStatus;
+
+ bool mfPollURB;
+
+ PREMOTEUSBDEVICE mpDevices;
+
+ bool mfWillBeDeleted;
+
+ Guid aGuids[VRDP_MAX_USB_DEVICES_PER_CLIENT];
+
+ /* VRDP_USB_VERSION_2: the client version. */
+ uint32_t mClientVersion;
+
+ /* VRDP_USB_VERSION_3: the client sends VRDE_USB_REQ_DEVICE_LIST_EXT_RET. */
+ bool mfDescExt;
+};
+
+#endif /* !MAIN_INCLUDED_RemoteUSBBackend_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/RemoteUSBDeviceImpl.h b/src/VBox/Main/include/RemoteUSBDeviceImpl.h
new file mode 100644
index 00000000..2adbee08
--- /dev/null
+++ b/src/VBox/Main/include/RemoteUSBDeviceImpl.h
@@ -0,0 +1,136 @@
+/* $Id: RemoteUSBDeviceImpl.h $ */
+
+/** @file
+ *
+ * VirtualBox IHostUSBDevice COM interface implementation
+ * for remote (VRDP) USB devices
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_RemoteUSBDeviceImpl_h
+#define MAIN_INCLUDED_RemoteUSBDeviceImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "HostUSBDeviceWrap.h"
+
+struct _VRDEUSBDEVICEDESC;
+typedef _VRDEUSBDEVICEDESC VRDEUSBDEVICEDESC;
+
+class ATL_NO_VTABLE RemoteUSBDevice :
+ public HostUSBDeviceWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(RemoteUSBDevice)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(uint32_t u32ClientId, VRDEUSBDEVICEDESC *pDevDesc, bool fDescExt);
+ void uninit();
+
+ // public methods only for internal purposes
+ bool dirty(void) const { return mData.dirty; }
+ void dirty(bool aDirty) { mData.dirty = aDirty; }
+
+ uint16_t devId(void) const { return mData.devId; }
+ uint32_t clientId(void) { return mData.clientId; }
+
+ bool captured(void) const { return mData.state == USBDeviceState_Captured; }
+ void captured(bool aCaptured)
+ {
+ if (aCaptured)
+ {
+ Assert(mData.state == USBDeviceState_Available);
+ mData.state = USBDeviceState_Captured;
+ }
+ else
+ {
+ Assert(mData.state == USBDeviceState_Captured);
+ mData.state = USBDeviceState_Available;
+ }
+ }
+
+private:
+
+ // wrapped IUSBDevice properties
+ HRESULT getId(com::Guid &aId);
+ HRESULT getVendorId(USHORT *aVendorId);
+ HRESULT getProductId(USHORT *aProductId);
+ HRESULT getRevision(USHORT *aRevision);
+ HRESULT getManufacturer(com::Utf8Str &aManufacturer);
+ HRESULT getProduct(com::Utf8Str &aProduct);
+ HRESULT getSerialNumber(com::Utf8Str &aSerialNumber);
+ HRESULT getAddress(com::Utf8Str &aAddress);
+ HRESULT getPort(USHORT *aPort);
+ HRESULT getVersion(USHORT *aVersion);
+ HRESULT getPortPath(com::Utf8Str &aAddress);
+ HRESULT getSpeed(USBConnectionSpeed_T *aSpeed);
+ HRESULT getRemote(BOOL *aRemote);
+ HRESULT getBackend(com::Utf8Str &aBackend);
+ HRESULT getDeviceInfo(std::vector<com::Utf8Str> &aInfo);
+
+ // wrapped IHostUSBDevice properties
+ HRESULT getState(USBDeviceState_T *aState);
+
+
+ struct Data
+ {
+ Data() : vendorId(0), productId(0), revision(0), port(0), version(1),
+ speed(USBConnectionSpeed_Null), dirty(FALSE),
+ devId(0), clientId(0) {}
+
+ const Guid id;
+
+ const uint16_t vendorId;
+ const uint16_t productId;
+ const uint16_t revision;
+
+ const Utf8Str manufacturer;
+ const Utf8Str product;
+ const Utf8Str serialNumber;
+
+ const Utf8Str address;
+ const Utf8Str backend;
+
+ const uint16_t port;
+ const Utf8Str portPath;
+ const uint16_t version;
+ const USBConnectionSpeed_T speed;
+
+ USBDeviceState_T state;
+ bool dirty;
+
+ const uint16_t devId;
+ const uint32_t clientId;
+ };
+
+ Data mData;
+};
+
+#endif /* !MAIN_INCLUDED_RemoteUSBDeviceImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/SecretKeyStore.h b/src/VBox/Main/include/SecretKeyStore.h
new file mode 100644
index 00000000..27d738ec
--- /dev/null
+++ b/src/VBox/Main/include/SecretKeyStore.h
@@ -0,0 +1,216 @@
+/* $Id: SecretKeyStore.h $ */
+/** @file
+ * Main - Secret key interface.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SecretKeyStore_h
+#define MAIN_INCLUDED_SecretKeyStore_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VirtualBoxBase.h"
+#include "VBox/com/array.h"
+
+class SecretKey
+{
+ public:
+
+ /**
+ * Constructor for a secret key.
+ *
+ * @param pbKey The key buffer.
+ * @param cbKey Size of the key.
+ * @param fKeyBufNonPageable Flag whether the key buffer should be non pageable.
+ */
+ SecretKey(const uint8_t *pbKey, size_t cbKey, bool fKeyBufNonPageable);
+
+ /**
+ * Secret key destructor.
+ */
+ ~SecretKey();
+
+ /**
+ * Increments the reference counter of the key.
+ *
+ * @returns The new reference count.
+ */
+ uint32_t retain();
+
+ /**
+ * Releases a reference of the key.
+ * If the reference counter reaches 0 the key buffer might be protected
+ * against further access or the data will become scrambled.
+ *
+ * @returns The new reference count.
+ */
+ uint32_t release();
+
+ /**
+ * Returns the reference count of the secret key.
+ */
+ uint32_t refCount();
+
+ /**
+ * Sets the possible number of users for this key.
+ *
+ * @returns VBox status code.
+ * @param cUsers The possible number of user for this key.
+ */
+ int setUsers(uint32_t cUsers);
+
+ /**
+ * Returns the possible amount of users.
+ *
+ * @returns Possible amount of users.
+ */
+ uint32_t getUsers();
+
+ /**
+ * Sets the remove on suspend flag.
+ *
+ * @returns VBox status code.
+ * @param fRemoveOnSuspend Flag whether to remove the key on host suspend.
+ */
+ int setRemoveOnSuspend(bool fRemoveOnSuspend);
+
+ /**
+ * Returns whether the key should be destroyed on suspend.
+ */
+ bool getRemoveOnSuspend();
+
+ /**
+ * Returns the buffer to the key.
+ */
+ const void *getKeyBuffer();
+
+ /**
+ * Returns the size of the key.
+ */
+ size_t getKeySize();
+
+ private:
+ /** Reference counter of the key. */
+ volatile uint32_t m_cRefs;
+ /** Key material. */
+ uint8_t *m_pbKey;
+ /** Size of the key in bytes. */
+ size_t m_cbKey;
+ /** Flag whether to remove the key on suspend. */
+ bool m_fRemoveOnSuspend;
+ /** Number of entities which will use this key. */
+ uint32_t m_cUsers;
+};
+
+class SecretKeyStore
+{
+ public:
+
+ typedef std::map<com::Utf8Str, SecretKey *> SecretKeyMap;
+
+ /**
+ * Constructor for a secret key store.
+ *
+ * @param fKeyBufNonPageable Flag whether the key buffer is required to
+ * be non pageable.
+ */
+ SecretKeyStore(bool fKeyBufNonPageable);
+
+ /**
+ * Destructor of a secret key store. This will free all stored secret keys
+ * inluding the key buffers. Make sure there no one accesses one of the keys
+ * stored.
+ */
+ ~SecretKeyStore();
+
+ /**
+ * Add a secret key to the store.
+ *
+ * @returns VBox status code.
+ * @param strKeyId The key identifier.
+ * @param pbKey The key to store.
+ * @param cbKey Size of the key.
+ */
+ int addSecretKey(const com::Utf8Str &strKeyId, const uint8_t *pbKey, size_t cbKey);
+
+ /**
+ * Deletes a key from the key store associated with the given identifier.
+ *
+ * @returns VBox status code.
+ * @param strKeyId The key identifier.
+ */
+ int deleteSecretKey(const com::Utf8Str &strKeyId);
+
+ /**
+ * Returns the secret key object associated with the given identifier.
+ * This increments the reference counter of the secret key object.
+ *
+ * @returns VBox status code.
+ * @param strKeyId The key identifier.
+ * @param ppKey Where to store the secret key object on success.
+ */
+ int retainSecretKey(const com::Utf8Str &strKeyId, SecretKey **ppKey);
+
+ /**
+ * Releases a reference to the secret key object.
+ *
+ * @returns VBox status code.
+ * @param strKeyId The key identifier.
+ */
+ int releaseSecretKey(const com::Utf8Str &strKeyId);
+
+ /**
+ * Deletes all secret keys from the key store.
+ *
+ * @returns VBox status code.
+ * @param fSuspend Flag whether to delete only keys which are
+ * marked for deletion during a suspend.
+ * @param fForce Flag whether to force deletion if some keys
+ * are still in use. Otherwise an error is returned.
+ */
+ int deleteAllSecretKeys(bool fSuspend, bool fForce);
+
+ /**
+ * Iterators for enumerating keys
+ */
+ SecretKeyMap::iterator begin()
+ {
+ return m_mapSecretKeys.begin();
+ }
+
+ SecretKeyMap::iterator end()
+ {
+ return m_mapSecretKeys.end();
+ }
+
+ private:
+
+ /** The map to map key identifers to secret keys. */
+ SecretKeyMap m_mapSecretKeys;
+ /** Flag whether key buffers should be non pagable. */
+ bool m_fKeyBufNonPageable;
+};
+
+#endif /* !MAIN_INCLUDED_SecretKeyStore_h */
diff --git a/src/VBox/Main/include/SerialPortImpl.h b/src/VBox/Main/include/SerialPortImpl.h
new file mode 100644
index 00000000..ad9cd827
--- /dev/null
+++ b/src/VBox/Main/include/SerialPortImpl.h
@@ -0,0 +1,102 @@
+/* $Id: SerialPortImpl.h $ */
+
+/** @file
+ *
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SerialPortImpl_h
+#define MAIN_INCLUDED_SerialPortImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "SerialPortWrap.h"
+
+class GuestOSType;
+
+namespace settings
+{
+ struct SerialPort;
+}
+
+class ATL_NO_VTABLE SerialPort :
+ public SerialPortWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(SerialPort)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aParent, ULONG aSlot);
+ HRESULT init(Machine *aParent, SerialPort *aThat);
+ HRESULT initCopy(Machine *parent, SerialPort *aThat);
+ void uninit();
+
+ // public methods only for internal purposes
+ HRESULT i_loadSettings(const settings::SerialPort &data);
+ HRESULT i_saveSettings(settings::SerialPort &data);
+
+ bool i_isModified();
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(SerialPort *aThat);
+
+ void i_applyDefaults(GuestOSType *aOsType);
+ bool i_hasDefaults();
+
+ // public methods for internal purposes only
+ // (ensure there is a caller and a read lock before calling them!)
+
+private:
+
+ HRESULT i_checkSetPath(const Utf8Str &str);
+
+ // Wrapped ISerialPort properties
+ HRESULT getEnabled(BOOL *aEnabled);
+ HRESULT setEnabled(BOOL aEnabled);
+ HRESULT getHostMode(PortMode_T *aHostMode);
+ HRESULT setHostMode(PortMode_T aHostMode);
+ HRESULT getSlot(ULONG *aSlot);
+ HRESULT getIRQ(ULONG *aIRQ);
+ HRESULT setIRQ(ULONG aIRQ);
+ HRESULT getIOBase(ULONG *aIOBase);
+ HRESULT setIOBase(ULONG aIOBase);
+ HRESULT getServer(BOOL *aServer);
+ HRESULT setServer(BOOL aServer);
+ HRESULT getPath(com::Utf8Str &aPath);
+ HRESULT setPath(const com::Utf8Str &aPath);
+ HRESULT getUartType(UartType_T *aUartType);
+ HRESULT setUartType(UartType_T aUartType);
+
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_SerialPortImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/SessionImpl.h b/src/VBox/Main/include/SessionImpl.h
new file mode 100644
index 00000000..f66e122e
--- /dev/null
+++ b/src/VBox/Main/include/SessionImpl.h
@@ -0,0 +1,188 @@
+/* $Id: SessionImpl.h $ */
+/** @file
+ * VBox Client Session COM Class definition
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SessionImpl_h
+#define MAIN_INCLUDED_SessionImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "SessionWrap.h"
+#include "ConsoleImpl.h"
+
+#ifdef RT_OS_WINDOWS
+# include "win/resource.h"
+#endif
+
+#if defined(RT_OS_WINDOWS) && !RT_MSC_PREREQ(RT_MSC_VER_VC140)
+[threading(free)]
+#endif
+class ATL_NO_VTABLE Session :
+ public SessionWrap
+#ifdef RT_OS_WINDOWS
+ , public ATL::CComCoClass<Session, &CLSID_Session>
+#endif
+{
+public:
+
+ DECLARE_CLASSFACTORY()
+
+ // Do not use any ATL registry support.
+ //DECLARE_REGISTRY_RESOURCEID(IDR_VIRTUALBOX)
+
+ DECLARE_NOT_AGGREGATABLE(Session)
+
+ DECLARE_COMMON_CLASS_METHODS(Session)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializers/uninitializers only for internal purposes
+ HRESULT init();
+ void uninit();
+
+private:
+
+ // Wrapped ISession properties
+ HRESULT getState(SessionState_T *aState);
+ HRESULT getType(SessionType_T *aType);
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT setName(const com::Utf8Str &aName);
+ HRESULT getMachine(ComPtr<IMachine> &aMachine);
+ HRESULT getConsole(ComPtr<IConsole> &aConsole);
+
+ // Wrapped ISession methods
+ HRESULT unlockMachine();
+
+ // Wrapped IInternalSessionControl properties
+ HRESULT getPID(ULONG *aPid);
+ HRESULT getRemoteConsole(ComPtr<IConsole> &aRemoteConsole);
+ HRESULT getNominalState(MachineState_T *aNominalState);
+
+ // Wrapped IInternalSessionControl methods
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ HRESULT assignMachine(const ComPtr<IMachine> &aMachine,
+ LockType_T aLockType,
+ const com::Utf8Str &aTokenId);
+#else
+ HRESULT assignMachine(const ComPtr<IMachine> &aMachine,
+ LockType_T aLockType,
+ const ComPtr<IToken> &aToken);
+#endif /* !VBOX_WITH_GENERIC_SESSION_WATCHER */
+ HRESULT assignRemoteMachine(const ComPtr<IMachine> &aMachine,
+ const ComPtr<IConsole> &aConsole);
+ HRESULT updateMachineState(MachineState_T aMachineState);
+ HRESULT uninitialize();
+ HRESULT onNetworkAdapterChange(const ComPtr<INetworkAdapter> &aNetworkAdapter,
+ BOOL aChangeAdapter);
+ HRESULT onAudioAdapterChange(const ComPtr<IAudioAdapter> &aAudioAdapter);
+ HRESULT onHostAudioDeviceChange(const ComPtr<IHostAudioDevice> &aDevice,
+ BOOL aNew, AudioDeviceState_T aState,
+ const ComPtr<IVirtualBoxErrorInfo> &aErrInfo);
+ HRESULT onSerialPortChange(const ComPtr<ISerialPort> &aSerialPort);
+ HRESULT onParallelPortChange(const ComPtr<IParallelPort> &aParallelPort);
+ HRESULT onStorageControllerChange(const Guid &aMachineId, const com::Utf8Str& aControllerName);
+ HRESULT onMediumChange(const ComPtr<IMediumAttachment> &aMediumAttachment,
+ BOOL aForce);
+ HRESULT onStorageDeviceChange(const ComPtr<IMediumAttachment> &aMediumAttachment,
+ BOOL aRemove,
+ BOOL aSilent);
+ HRESULT onVMProcessPriorityChange(VMProcPriority_T priority);
+ HRESULT onClipboardModeChange(ClipboardMode_T aClipboardMode);
+ HRESULT onClipboardFileTransferModeChange(BOOL aEnabled);
+ HRESULT onDnDModeChange(DnDMode_T aDndMode);
+ HRESULT onCPUChange(ULONG aCpu,
+ BOOL aAdd);
+ HRESULT onCPUExecutionCapChange(ULONG aExecutionCap);
+ HRESULT onVRDEServerChange(BOOL aRestart);
+ HRESULT onRecordingChange(BOOL aEnable);
+ HRESULT onUSBControllerChange();
+ HRESULT onSharedFolderChange(BOOL aGlobal);
+ HRESULT onGuestDebugControlChange(const ComPtr<IGuestDebugControl> &aGuestDebugControl);
+ HRESULT onUSBDeviceAttach(const ComPtr<IUSBDevice> &aDevice,
+ const ComPtr<IVirtualBoxErrorInfo> &aError,
+ ULONG aMaskedInterfaces,
+ const com::Utf8Str &aCaptureFilename);
+ HRESULT onUSBDeviceDetach(const com::Guid &aId,
+ const ComPtr<IVirtualBoxErrorInfo> &aError);
+ HRESULT onShowWindow(BOOL aCheck,
+ BOOL *aCanShow,
+ LONG64 *aWinId);
+ HRESULT onBandwidthGroupChange(const ComPtr<IBandwidthGroup> &aBandwidthGroup);
+ HRESULT accessGuestProperty(const com::Utf8Str &aName,
+ const com::Utf8Str &aValue,
+ const com::Utf8Str &aFlags,
+ ULONG aAccessMode,
+ com::Utf8Str &aRetValue,
+ LONG64 *aRetTimestamp,
+ com::Utf8Str &aRetFlags);
+ HRESULT enumerateGuestProperties(const com::Utf8Str &aPatterns,
+ std::vector<com::Utf8Str> &aKeys,
+ std::vector<com::Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<com::Utf8Str> &aFlags);
+ HRESULT onlineMergeMedium(const ComPtr<IMediumAttachment> &aMediumAttachment,
+ ULONG aSourceIdx,
+ ULONG aTargetIdx,
+ const ComPtr<IProgress> &aProgress);
+ HRESULT reconfigureMediumAttachments(const std::vector<ComPtr<IMediumAttachment> > &aAttachments);
+ HRESULT enableVMMStatistics(BOOL aEnable);
+ HRESULT pauseWithReason(Reason_T aReason);
+ HRESULT resumeWithReason(Reason_T aReason);
+ HRESULT saveStateWithReason(Reason_T aReason,
+ const ComPtr<IProgress> &aProgress,
+ const ComPtr<ISnapshot> &aSnapshot,
+ const Utf8Str &aStateFilePath,
+ BOOL aPauseVM,
+ BOOL *aLeftPaused);
+ HRESULT cancelSaveStateWithReason();
+
+
+ HRESULT i_unlockMachine(bool aFinalRelease, bool aFromServer, AutoWriteLock &aLockW);
+
+ SessionState_T mState;
+ SessionType_T mType;
+ Utf8Str mName;
+
+ ComPtr<IInternalMachineControl> mControl;
+
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ ComObjPtr<Console> mConsole;
+#endif
+
+ ComPtr<IMachine> mRemoteMachine;
+ ComPtr<IConsole> mRemoteConsole;
+
+ ComPtr<IVirtualBox> mVirtualBox;
+
+ class ClientTokenHolder;
+
+ ClientTokenHolder *mClientTokenHolder;
+};
+
+#endif /* !MAIN_INCLUDED_SessionImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/SharedFolderImpl.h b/src/VBox/Main/include/SharedFolderImpl.h
new file mode 100644
index 00000000..b26002c6
--- /dev/null
+++ b/src/VBox/Main/include/SharedFolderImpl.h
@@ -0,0 +1,129 @@
+/* $Id: SharedFolderImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SharedFolderImpl_h
+#define MAIN_INCLUDED_SharedFolderImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "SharedFolderWrap.h"
+#include <VBox/shflsvc.h>
+
+class Console;
+
+class ATL_NO_VTABLE SharedFolder :
+ public SharedFolderWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS (SharedFolder)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aMachine, const com::Utf8Str &aName, const com::Utf8Str &aHostPath,
+ bool aWritable, bool aAutoMount, const com::Utf8Str &aAutoMountPoint, bool fFailOnError);
+ HRESULT initCopy(Machine *aMachine, SharedFolder *aThat);
+ HRESULT init(Console *aConsole, const com::Utf8Str &aName, const com::Utf8Str &aHostPath,
+ bool aWritable, bool aAutoMount, const com::Utf8Str &aAutoMountPoint, bool fFailOnError);
+// HRESULT init(VirtualBox *aVirtualBox, const Utf8Str &aName, const Utf8Str &aHostPath,
+// bool aWritable, const com::Utf8Str &aAutoMountPoint, bool aAutoMount, bool fFailOnError);
+ void uninit();
+
+ // public methods for internal purposes only
+ // (ensure there is a caller and a read lock before calling them!)
+
+ /**
+ * Public internal method. Returns the shared folder's name. Needs caller! Locking not necessary.
+ * @return
+ */
+ const Utf8Str &i_getName() const;
+
+ /**
+ * Public internal method. Returns the shared folder's host path. Needs caller! Locking not necessary.
+ * @return
+ */
+ const Utf8Str &i_getHostPath() const;
+
+ /**
+ * Public internal method. Returns true if the shared folder is writable. Needs caller and locking!
+ * @return
+ */
+ bool i_isWritable() const;
+
+ /**
+ * Public internal method. Returns true if the shared folder is auto-mounted. Needs caller and locking!
+ * @return
+ */
+ bool i_isAutoMounted() const;
+
+ /**
+ * Public internal method for getting the auto mount point.
+ */
+ const Utf8Str &i_getAutoMountPoint() const;
+
+protected:
+
+ HRESULT i_protectedInit(VirtualBoxBase *aParent,
+ const Utf8Str &aName,
+ const Utf8Str &aHostPath,
+ bool aWritable,
+ bool aAutoMount,
+ const com::Utf8Str &aAutoMountPoint,
+ bool fFailOnError);
+private:
+
+ // wrapped ISharedFolder properies.
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getHostPath(com::Utf8Str &aHostPath);
+ HRESULT getAccessible(BOOL *aAccessible);
+ HRESULT getWritable(BOOL *aWritable);
+ HRESULT setWritable(BOOL aWritable);
+ HRESULT getAutoMount(BOOL *aAutoMount);
+ HRESULT setAutoMount(BOOL aAutoMount);
+ HRESULT getAutoMountPoint(com::Utf8Str &aAutoMountPoint);
+ HRESULT setAutoMountPoint(com::Utf8Str const &aAutoMountPoint);
+ HRESULT getLastAccessError(com::Utf8Str &aLastAccessError);
+
+ VirtualBoxBase * const mParent;
+
+ /* weak parents (only one of them is not null) */
+#if !defined(VBOX_COM_INPROC)
+ Machine * const mMachine;
+ VirtualBox * const mVirtualBox;
+#else
+ Console * const mConsole;
+#endif
+
+ struct Data; // opaque data struct, defined in SharedFolderImpl.cpp
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_SharedFolderImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/SnapshotImpl.h b/src/VBox/Main/include/SnapshotImpl.h
new file mode 100644
index 00000000..ea6fd309
--- /dev/null
+++ b/src/VBox/Main/include/SnapshotImpl.h
@@ -0,0 +1,142 @@
+/* $Id: SnapshotImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SnapshotImpl_h
+#define MAIN_INCLUDED_SnapshotImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "SnapshotWrap.h"
+
+class SnapshotMachine;
+
+namespace settings
+{
+ struct Snapshot;
+}
+
+class ATL_NO_VTABLE Snapshot :
+ public SnapshotWrap
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(Snapshot)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer only for internal purposes
+ HRESULT init(VirtualBox *aVirtualBox,
+ const Guid &aId,
+ const com::Utf8Str &aName,
+ const com::Utf8Str &aDescription,
+ const RTTIMESPEC &aTimeStamp,
+ SnapshotMachine *aMachine,
+ Snapshot *aParent);
+ void uninit();
+
+ void i_beginSnapshotDelete();
+
+ void i_deparent();
+
+ // public methods only for internal purposes
+
+ /**
+ * Override of the default locking class to be used for validating lock
+ * order with the standard member lock handle.
+ */
+ virtual VBoxLockingClass getLockingClass() const
+ {
+ return LOCKCLASS_SNAPSHOTOBJECT;
+ }
+
+ const ComObjPtr<Snapshot>& i_getParent() const;
+ const ComObjPtr<Snapshot> i_getFirstChild() const;
+
+ const Utf8Str& i_getStateFilePath() const;
+
+ uint32_t i_getDepth();
+
+ ULONG i_getChildrenCount();
+ ULONG i_getAllChildrenCount();
+
+ const ComObjPtr<SnapshotMachine>& i_getSnapshotMachine() const;
+
+ Guid i_getId() const;
+ const Utf8Str& i_getName() const;
+ RTTIMESPEC i_getTimeStamp() const;
+
+ ComObjPtr<Snapshot> i_findChildOrSelf(IN_GUID aId);
+ ComObjPtr<Snapshot> i_findChildOrSelf(const com::Utf8Str &aName);
+
+ void i_updateSavedStatePaths(const Utf8Str &strOldPath,
+ const Utf8Str &strNewPath);
+ void i_updateSavedStatePathsImpl(const Utf8Str &strOldPath,
+ const Utf8Str &strNewPath);
+
+ bool i_sharesSavedStateFile(const Utf8Str &strPath,
+ Snapshot *pSnapshotToIgnore);
+
+ void i_updateNVRAMPaths(const Utf8Str &strOldPath,
+ const Utf8Str &strNewPath);
+ void i_updateNVRAMPathsImpl(const Utf8Str &strOldPath,
+ const Utf8Str &strNewPath);
+
+ HRESULT i_saveSnapshotOne(settings::Snapshot &data) const;
+ HRESULT i_saveSnapshot(settings::Snapshot &data) const;
+
+ HRESULT i_uninitAll(AutoWriteLock &writeLock,
+ CleanupMode_T cleanupMode,
+ MediaList &llMedia,
+ std::list<Utf8Str> &llFilenames);
+
+
+private:
+
+ struct Data; // opaque, defined in SnapshotImpl.cpp
+
+ // wrapped ISnapshot properties
+ HRESULT getId(com::Guid &aId);
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT setName(const com::Utf8Str &aName);
+ HRESULT getDescription(com::Utf8Str &aDescription);
+ HRESULT setDescription(const com::Utf8Str &aDescription);
+ HRESULT getTimeStamp(LONG64 *aTimeStamp);
+ HRESULT getOnline(BOOL *aOnline);
+ HRESULT getMachine(ComPtr<IMachine> &aMachine);
+ HRESULT getParent(ComPtr<ISnapshot> &aParent);
+ HRESULT getChildren(std::vector<ComPtr<ISnapshot> > &aChildren);
+
+ // wrapped ISnapshot methods
+ HRESULT getChildrenCount(ULONG *aChildrenCount);
+
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_SnapshotImpl_h */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/StorageControllerImpl.h b/src/VBox/Main/include/StorageControllerImpl.h
new file mode 100644
index 00000000..8ff7611f
--- /dev/null
+++ b/src/VBox/Main/include/StorageControllerImpl.h
@@ -0,0 +1,107 @@
+/* $Id: StorageControllerImpl.h $ */
+
+/** @file
+ *
+ * VBox StorageController COM Class declaration.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_StorageControllerImpl_h
+#define MAIN_INCLUDED_StorageControllerImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+#include "StorageControllerWrap.h"
+
+class ATL_NO_VTABLE StorageController :
+ public StorageControllerWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(StorageController)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aParent,
+ const com::Utf8Str &aName,
+ StorageBus_T aBus,
+ ULONG aInstance,
+ bool fBootable);
+ HRESULT init(Machine *aParent,
+ StorageController *aThat,
+ bool aReshare = false);
+ HRESULT initCopy(Machine *aParent,
+ StorageController *aThat);
+ void uninit();
+
+ // public methods only for internal purposes
+ const Utf8Str &i_getName() const;
+ StorageControllerType_T i_getControllerType() const;
+ StorageBus_T i_getStorageBus() const;
+ ULONG i_getInstance() const;
+ bool i_getBootable() const;
+ HRESULT i_checkPortAndDeviceValid(LONG aControllerPort,
+ LONG aDevice);
+ void i_setBootable(BOOL fBootable);
+ void i_rollback();
+ void i_commit();
+
+ // public methods for internal purposes only
+ // (ensure there is a caller and a read lock before calling them!)
+
+ void i_unshare();
+
+ /** @note this doesn't require a read lock since mParent is constant. */
+ Machine* i_getMachine();
+ ComObjPtr<StorageController> i_getPeer();
+
+private:
+
+ // Wrapped IStorageController properties
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT setName(const com::Utf8Str &aName);
+ HRESULT getMaxDevicesPerPortCount(ULONG *aMaxDevicesPerPortCount);
+ HRESULT getMinPortCount(ULONG *aMinPortCount);
+ HRESULT getMaxPortCount(ULONG *aMaxPortCount);
+ HRESULT getInstance(ULONG *aInstance);
+ HRESULT setInstance(ULONG aInstance);
+ HRESULT getPortCount(ULONG *aPortCount);
+ HRESULT setPortCount(ULONG aPortCount);
+ HRESULT getBus(StorageBus_T *aBus);
+ HRESULT getControllerType(StorageControllerType_T *aControllerType);
+ HRESULT setControllerType(StorageControllerType_T aControllerType);
+ HRESULT getUseHostIOCache(BOOL *aUseHostIOCache);
+ HRESULT setUseHostIOCache(BOOL aUseHostIOCache);
+ HRESULT getBootable(BOOL *aBootable);
+
+ void i_printList();
+
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_StorageControllerImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/SystemPropertiesImpl.h b/src/VBox/Main/include/SystemPropertiesImpl.h
new file mode 100644
index 00000000..90fe0e65
--- /dev/null
+++ b/src/VBox/Main/include/SystemPropertiesImpl.h
@@ -0,0 +1,220 @@
+/* $Id: SystemPropertiesImpl.h $ */
+
+/** @file
+ *
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SystemPropertiesImpl_h
+#define MAIN_INCLUDED_SystemPropertiesImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "MediumFormatImpl.h"
+#include "SystemPropertiesWrap.h"
+
+class CPUProfile;
+
+namespace settings
+{
+ struct SystemProperties;
+}
+
+class ATL_NO_VTABLE SystemProperties :
+ public SystemPropertiesWrap
+{
+public:
+ typedef std::list<ComObjPtr<MediumFormat> > MediumFormatList;
+ typedef std::list<ComObjPtr<CPUProfile> > CPUProfileList_T;
+
+ DECLARE_COMMON_CLASS_METHODS(SystemProperties)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(VirtualBox *aParent);
+ void uninit();
+
+ // public methods for internal purposes only
+ // (ensure there is a caller and a read lock before calling them!)
+ HRESULT i_loadSettings(const settings::SystemProperties &data);
+ HRESULT i_saveSettings(settings::SystemProperties &data);
+
+ ComObjPtr<MediumFormat> i_mediumFormat(const Utf8Str &aFormat);
+ ComObjPtr<MediumFormat> i_mediumFormatFromExtension(const Utf8Str &aExt);
+
+ int i_loadVDPlugin(const char *pszPluginLibrary);
+ int i_unloadVDPlugin(const char *pszPluginLibrary);
+
+ HRESULT i_getDefaultAdditionsISO(com::Utf8Str &aDefaultAdditionsISO);
+
+private:
+
+ // wrapped ISystemProperties properties
+ HRESULT getMinGuestRAM(ULONG *aMinGuestRAM) RT_OVERRIDE;
+ HRESULT getMaxGuestRAM(ULONG *aMaxGuestRAM) RT_OVERRIDE;
+ HRESULT getMinGuestVRAM(ULONG *aMinGuestVRAM) RT_OVERRIDE;
+ HRESULT getMaxGuestVRAM(ULONG *aMaxGuestVRAM) RT_OVERRIDE;
+ HRESULT getMinGuestCPUCount(ULONG *aMinGuestCPUCount) RT_OVERRIDE;
+ HRESULT getMaxGuestCPUCount(ULONG *aMaxGuestCPUCount) RT_OVERRIDE;
+ HRESULT getMaxGuestMonitors(ULONG *aMaxGuestMonitors) RT_OVERRIDE;
+ HRESULT getInfoVDSize(LONG64 *aInfoVDSize) RT_OVERRIDE;
+ HRESULT getSerialPortCount(ULONG *aSerialPortCount) RT_OVERRIDE;
+ HRESULT getParallelPortCount(ULONG *aParallelPortCount) RT_OVERRIDE;
+ HRESULT getMaxBootPosition(ULONG *aMaxBootPosition) RT_OVERRIDE;
+ HRESULT getRawModeSupported(BOOL *aRawModeSupported) RT_OVERRIDE;
+ HRESULT getExclusiveHwVirt(BOOL *aExclusiveHwVirt) RT_OVERRIDE;
+ HRESULT setExclusiveHwVirt(BOOL aExclusiveHwVirt) RT_OVERRIDE;
+ HRESULT getDefaultMachineFolder(com::Utf8Str &aDefaultMachineFolder) RT_OVERRIDE;
+ HRESULT setDefaultMachineFolder(const com::Utf8Str &aDefaultMachineFolder) RT_OVERRIDE;
+ HRESULT getLoggingLevel(com::Utf8Str &aLoggingLevel) RT_OVERRIDE;
+ HRESULT setLoggingLevel(const com::Utf8Str &aLoggingLevel) RT_OVERRIDE;
+ HRESULT getMediumFormats(std::vector<ComPtr<IMediumFormat> > &aMediumFormats) RT_OVERRIDE;
+ HRESULT getDefaultHardDiskFormat(com::Utf8Str &aDefaultHardDiskFormat) RT_OVERRIDE;
+ HRESULT setDefaultHardDiskFormat(const com::Utf8Str &aDefaultHardDiskFormat) RT_OVERRIDE;
+ HRESULT getFreeDiskSpaceWarning(LONG64 *aFreeDiskSpaceWarning) RT_OVERRIDE;
+ HRESULT setFreeDiskSpaceWarning(LONG64 aFreeDiskSpaceWarning) RT_OVERRIDE;
+ HRESULT getFreeDiskSpacePercentWarning(ULONG *aFreeDiskSpacePercentWarning) RT_OVERRIDE;
+ HRESULT setFreeDiskSpacePercentWarning(ULONG aFreeDiskSpacePercentWarning) RT_OVERRIDE;
+ HRESULT getFreeDiskSpaceError(LONG64 *aFreeDiskSpaceError) RT_OVERRIDE;
+ HRESULT setFreeDiskSpaceError(LONG64 aFreeDiskSpaceError) RT_OVERRIDE;
+ HRESULT getFreeDiskSpacePercentError(ULONG *aFreeDiskSpacePercentError) RT_OVERRIDE;
+ HRESULT setFreeDiskSpacePercentError(ULONG aFreeDiskSpacePercentError) RT_OVERRIDE;
+ HRESULT getVRDEAuthLibrary(com::Utf8Str &aVRDEAuthLibrary) RT_OVERRIDE;
+ HRESULT setVRDEAuthLibrary(const com::Utf8Str &aVRDEAuthLibrary) RT_OVERRIDE;
+ HRESULT getWebServiceAuthLibrary(com::Utf8Str &aWebServiceAuthLibrary) RT_OVERRIDE;
+ HRESULT setWebServiceAuthLibrary(const com::Utf8Str &aWebServiceAuthLibrary) RT_OVERRIDE;
+ HRESULT getDefaultVRDEExtPack(com::Utf8Str &aDefaultVRDEExtPack) RT_OVERRIDE;
+ HRESULT setDefaultVRDEExtPack(const com::Utf8Str &aDefaultVRDEExtPack) RT_OVERRIDE;
+ HRESULT getDefaultCryptoExtPack(com::Utf8Str &aDefaultCryptoExtPack) RT_OVERRIDE;
+ HRESULT setDefaultCryptoExtPack(const com::Utf8Str &aDefaultCryptoExtPack) RT_OVERRIDE;
+ HRESULT getLogHistoryCount(ULONG *aLogHistoryCount) RT_OVERRIDE;
+ HRESULT setLogHistoryCount(ULONG aLogHistoryCount) RT_OVERRIDE;
+ HRESULT getDefaultAudioDriver(AudioDriverType_T *aDefaultAudioDriver) RT_OVERRIDE;
+ HRESULT getAutostartDatabasePath(com::Utf8Str &aAutostartDatabasePath) RT_OVERRIDE;
+ HRESULT setAutostartDatabasePath(const com::Utf8Str &aAutostartDatabasePath) RT_OVERRIDE;
+ HRESULT getDefaultAdditionsISO(com::Utf8Str &aDefaultAdditionsISO) RT_OVERRIDE;
+ HRESULT setDefaultAdditionsISO(const com::Utf8Str &aDefaultAdditionsISO) RT_OVERRIDE;
+ HRESULT getDefaultFrontend(com::Utf8Str &aDefaultFrontend) RT_OVERRIDE;
+ HRESULT setDefaultFrontend(const com::Utf8Str &aDefaultFrontend) RT_OVERRIDE;
+ HRESULT getScreenShotFormats(std::vector<BitmapFormat_T> &aScreenShotFormats) RT_OVERRIDE;
+ HRESULT getProxyMode(ProxyMode_T *pProxyMode) RT_OVERRIDE;
+ HRESULT setProxyMode(ProxyMode_T aProxyMode) RT_OVERRIDE;
+ HRESULT getProxyURL(com::Utf8Str &aProxyURL) RT_OVERRIDE;
+ HRESULT setProxyURL(const com::Utf8Str &aProxyURL) RT_OVERRIDE;
+ HRESULT getSupportedParavirtProviders(std::vector<ParavirtProvider_T> &aSupportedParavirtProviders) RT_OVERRIDE;
+ HRESULT getSupportedClipboardModes(std::vector<ClipboardMode_T> &aSupportedClipboardModes) RT_OVERRIDE;
+ HRESULT getSupportedDnDModes(std::vector<DnDMode_T> &aSupportedDnDModes) RT_OVERRIDE;
+ HRESULT getSupportedFirmwareTypes(std::vector<FirmwareType_T> &aSupportedFirmwareTypes) RT_OVERRIDE;
+ HRESULT getSupportedPointingHIDTypes(std::vector<PointingHIDType_T> &aSupportedPointingHIDTypes) RT_OVERRIDE;
+ HRESULT getSupportedKeyboardHIDTypes(std::vector<KeyboardHIDType_T> &aSupportedKeyboardHIDTypes) RT_OVERRIDE;
+ HRESULT getSupportedVFSTypes(std::vector<VFSType_T> &aSupportedVFSTypes) RT_OVERRIDE;
+ HRESULT getSupportedImportOptions(std::vector<ImportOptions_T> &aSupportedImportOptions) RT_OVERRIDE;
+ HRESULT getSupportedExportOptions(std::vector<ExportOptions_T> &aSupportedExportOptions) RT_OVERRIDE;
+ HRESULT getSupportedRecordingFeatures(std::vector<RecordingFeature_T> &aSupportedRecordingFeatures) RT_OVERRIDE;
+ HRESULT getSupportedRecordingAudioCodecs(std::vector<RecordingAudioCodec_T> &aSupportedRecordingAudioCodecs) RT_OVERRIDE;
+ HRESULT getSupportedRecordingVideoCodecs(std::vector<RecordingVideoCodec_T> &aSupportedRecordingVideoCodecs) RT_OVERRIDE;
+ HRESULT getSupportedRecordingVSModes(std::vector<RecordingVideoScalingMode_T> &aSupportedRecordingVideoScalingModes) RT_OVERRIDE;
+ HRESULT getSupportedRecordingARCModes(std::vector<RecordingRateControlMode_T> &aSupportedRecordingAudioRateControlModes) RT_OVERRIDE;
+ HRESULT getSupportedRecordingVRCModes(std::vector<RecordingRateControlMode_T> &aSupportedRecordingVideoRateControlModes) RT_OVERRIDE;
+ HRESULT getSupportedGraphicsControllerTypes(std::vector<GraphicsControllerType_T> &aSupportedGraphicsControllerTypes) RT_OVERRIDE;
+ HRESULT getSupportedCloneOptions(std::vector<CloneOptions_T> &aSupportedCloneOptions) RT_OVERRIDE;
+ HRESULT getSupportedAutostopTypes(std::vector<AutostopType_T> &aSupportedAutostopTypes) RT_OVERRIDE;
+ HRESULT getSupportedVMProcPriorities(std::vector<VMProcPriority_T> &aSupportedVMProcPriorities) RT_OVERRIDE;
+ HRESULT getSupportedNetworkAttachmentTypes(std::vector<NetworkAttachmentType_T> &aSupportedNetworkAttachmentTypes) RT_OVERRIDE;
+ HRESULT getSupportedNetworkAdapterTypes(std::vector<NetworkAdapterType_T> &aSupportedNetworkAdapterTypes) RT_OVERRIDE;
+ HRESULT getSupportedPortModes(std::vector<PortMode_T> &aSupportedPortModes) RT_OVERRIDE;
+ HRESULT getSupportedUartTypes(std::vector<UartType_T> &aSupportedUartTypes) RT_OVERRIDE;
+ HRESULT getSupportedUSBControllerTypes(std::vector<USBControllerType_T> &aSupportedUSBControllerTypes) RT_OVERRIDE;
+ HRESULT getSupportedAudioDriverTypes(std::vector<AudioDriverType_T> &aSupportedAudioDriverTypes) RT_OVERRIDE;
+ HRESULT getSupportedAudioControllerTypes(std::vector<AudioControllerType_T> &aSupportedAudioControllerTypes) RT_OVERRIDE;
+ HRESULT getSupportedStorageBuses(std::vector<StorageBus_T> &aSupportedStorageBuses) RT_OVERRIDE;
+ HRESULT getSupportedStorageControllerTypes(std::vector<StorageControllerType_T> &aSupportedStorageControllerTypes) RT_OVERRIDE;
+ HRESULT getSupportedChipsetTypes(std::vector<ChipsetType_T> &aSupportedChipsetTypes) RT_OVERRIDE;
+ HRESULT getSupportedIommuTypes(std::vector<IommuType_T> &aSupportedIommuTypes) RT_OVERRIDE;
+ HRESULT getSupportedTpmTypes(std::vector<TpmType_T> &aSupportedTpmTypes) RT_OVERRIDE;
+ HRESULT getLanguageId(com::Utf8Str &aLanguageId) RT_OVERRIDE;
+ HRESULT setLanguageId(const com::Utf8Str &aLanguageId) RT_OVERRIDE;
+
+ // wrapped ISystemProperties methods
+ HRESULT getMaxNetworkAdapters(ChipsetType_T aChipset,
+ ULONG *aMaxNetworkAdapters) RT_OVERRIDE;
+ HRESULT getMaxNetworkAdaptersOfType(ChipsetType_T aChipset,
+ NetworkAttachmentType_T aType,
+ ULONG *aMaxNetworkAdapters) RT_OVERRIDE;
+ HRESULT getMaxDevicesPerPortForStorageBus(StorageBus_T aBus,
+ ULONG *aMaxDevicesPerPort) RT_OVERRIDE;
+ HRESULT getMinPortCountForStorageBus(StorageBus_T aBus,
+ ULONG *aMinPortCount) RT_OVERRIDE;
+ HRESULT getMaxPortCountForStorageBus(StorageBus_T aBus,
+ ULONG *aMaxPortCount) RT_OVERRIDE;
+ HRESULT getMaxInstancesOfStorageBus(ChipsetType_T aChipset,
+ StorageBus_T aBus,
+ ULONG *aMaxInstances) RT_OVERRIDE;
+ HRESULT getDeviceTypesForStorageBus(StorageBus_T aBus,
+ std::vector<DeviceType_T> &aDeviceTypes) RT_OVERRIDE;
+ HRESULT getStorageBusForStorageControllerType(StorageControllerType_T aStorageControllerType,
+ StorageBus_T *aStorageBus) RT_OVERRIDE;
+ HRESULT getStorageControllerTypesForStorageBus(StorageBus_T aStorageBus,
+ std::vector<StorageControllerType_T> &aStorageControllerTypes) RT_OVERRIDE;
+ HRESULT getDefaultIoCacheSettingForStorageController(StorageControllerType_T aControllerType,
+ BOOL *aEnabled) RT_OVERRIDE;
+ HRESULT getStorageControllerHotplugCapable(StorageControllerType_T aControllerType,
+ BOOL *aHotplugCapable) RT_OVERRIDE;
+ HRESULT getMaxInstancesOfUSBControllerType(ChipsetType_T aChipset,
+ USBControllerType_T aType,
+ ULONG *aMaxInstances) RT_OVERRIDE;
+ HRESULT getCPUProfiles(CPUArchitecture_T aArchitecture, const com::Utf8Str &aNamePattern,
+ std::vector<ComPtr<ICPUProfile> > &aProfiles) RT_OVERRIDE;
+
+ HRESULT i_getUserHomeDirectory(Utf8Str &strPath);
+ HRESULT i_setDefaultMachineFolder(const Utf8Str &strPath);
+ HRESULT i_setLoggingLevel(const com::Utf8Str &aLoggingLevel);
+ HRESULT i_setDefaultHardDiskFormat(const com::Utf8Str &aFormat);
+ HRESULT i_setVRDEAuthLibrary(const com::Utf8Str &aPath);
+
+ HRESULT i_setWebServiceAuthLibrary(const com::Utf8Str &aPath);
+ HRESULT i_setDefaultVRDEExtPack(const com::Utf8Str &aExtPack);
+ HRESULT i_setDefaultCryptoExtPack(const com::Utf8Str &aExtPack);
+ HRESULT i_setAutostartDatabasePath(const com::Utf8Str &aPath);
+ HRESULT i_setDefaultAdditionsISO(const com::Utf8Str &aPath);
+ HRESULT i_setDefaultFrontend(const com::Utf8Str &aDefaultFrontend);
+
+ VirtualBox * const mParent;
+
+ settings::SystemProperties *m;
+
+ MediumFormatList m_llMediumFormats;
+
+ bool m_fLoadedX86CPUProfiles; /**< Set if we've loaded the x86 and AMD64 CPU profiles. */
+ CPUProfileList_T m_llCPUProfiles; /**< List of loaded CPU profiles. */
+
+ friend class VirtualBox;
+};
+
+#endif /* !MAIN_INCLUDED_SystemPropertiesImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/TextScript.h b/src/VBox/Main/include/TextScript.h
new file mode 100644
index 00000000..21284df5
--- /dev/null
+++ b/src/VBox/Main/include/TextScript.h
@@ -0,0 +1,251 @@
+/* $Id: TextScript.h $ */
+/** @file
+ * Classes for reading/parsing/saving text scripts (unattended installation, ++).
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_TextScript_h
+#define MAIN_INCLUDED_TextScript_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VirtualBoxBase.h"
+#include <iprt/cpp/utils.h>
+#include <vector>
+
+
+/**
+ * Base class for all the script readers/editors.
+ *
+ * @todo get rid of this silly bugger.
+ */
+class AbstractScript
+ : public RTCNonCopyable
+{
+protected:
+ /** For setting errors.
+ * Yeah, class isn't entirely abstract now. */
+ VirtualBoxBase *mpSetError;
+
+private: /* no default constructors for children. */
+ AbstractScript() {}
+
+public:
+ DECLARE_TRANSLATE_METHODS(AbstractScript)
+
+ AbstractScript(VirtualBoxBase *pSetError) : mpSetError(pSetError) {}
+ virtual ~AbstractScript() {}
+
+ /**
+ * Read a script from a file
+ */
+ virtual HRESULT read(const Utf8Str &rStrFilename) = 0;
+
+ /**
+ * Read a script from a VFS file handle.
+ */
+ virtual HRESULT readFromHandle(RTVFSFILE hVfsFile, const char *pszFilename) = 0;
+
+ /**
+ * Parse the script
+ */
+ virtual HRESULT parse() = 0;
+
+ /**
+ * Save a script to a string.
+ *
+ * This is used by save() and later others to deloy the script.
+ */
+ virtual HRESULT saveToString(Utf8Str &rStrDst) = 0;
+
+ /**
+ * Save a script to a file.
+ * @param rStrPath Where to save the script. This normally points to a
+ * file, but in a number of child use cases it's
+ * actually giving a directory to put the script in
+ * using the default deloyment filename. One day we
+ * might make the caller do this path joining.
+ * @param fOverwrite Whether to overwrite the file or not.
+ */
+ virtual HRESULT save(const Utf8Str &rStrPath, bool fOverwrite) = 0;
+
+ /**
+ * Path where an actual script with user's data is located
+ */
+ virtual const Utf8Str &getActualScriptPath() const = 0;
+};
+
+/**
+ * Base class for text based script readers/editors.
+ *
+ * This deals with reading the file into a string data member, writing it back
+ * out to a file, and remember the filenames.
+ */
+class BaseTextScript : public AbstractScript
+{
+protected:
+ const char * const mpszDefaultTemplateFilename; /**< The default template filename. Can be empty. */
+ const char * const mpszDefaultFilename; /**< Filename to use when someone calls save() with a directory path. Can be NULL. */
+ RTCString mStrScriptFullContent; /**< Raw text file content. Produced by read() and typically only used by parse(). */
+ Utf8Str mStrOriginalPath; /**< Path where an original script is located (set by read()). */
+ Utf8Str mStrSavedPath; /**< Path where an saved script with user's data is located (set by save()). */
+
+public:
+ DECLARE_TRANSLATE_METHODS(BaseTextScript)
+
+ BaseTextScript(VirtualBoxBase *pSetError, const char *pszDefaultTemplateFilename, const char *pszDefaultFilename)
+ : AbstractScript(pSetError)
+ , mpszDefaultTemplateFilename(pszDefaultTemplateFilename)
+ , mpszDefaultFilename(pszDefaultFilename)
+ { }
+ virtual ~BaseTextScript() {}
+
+ HRESULT read(const Utf8Str &rStrFilename);
+ HRESULT readFromHandle(RTVFSFILE hVfsFile, const char *pszFilename);
+ HRESULT save(const Utf8Str &rStrFilename, bool fOverwrite);
+
+ /**
+ * Gets the default filename for this class of scripts (empty if none).
+ *
+ * @note Just the filename, no path.
+ */
+ const char *getDefaultFilename() const
+ {
+ return mpszDefaultFilename;
+ }
+
+ /**
+ * Gets the default template filename for this class of scripts (empty if none).
+ *
+ * @note Just the filename, no path.
+ */
+ const char *getDefaultTemplateFilename() const
+ {
+ return mpszDefaultTemplateFilename;
+ }
+
+ /**
+ * Path to the file we last saved the script as.
+ */
+ const Utf8Str &getActualScriptPath() const
+ {
+ return mStrSavedPath;
+ }
+
+ /**
+ * Path where an original script is located
+ */
+ const Utf8Str &getOriginalScriptPath() const
+ {
+ return mStrOriginalPath;
+ }
+};
+
+
+/**
+ * Generic line based text script editor.
+ *
+ * This is used for editing isolinux configuratin files among other things.
+ */
+class GeneralTextScript : public BaseTextScript
+{
+protected:
+ RTCList<RTCString> mScriptContentByLines; /**< Content index by line. This contains the edited version. */
+ bool mfDataParsed; /**< Indicates whether the script has been parse() yet. */
+
+public:
+ DECLARE_TRANSLATE_METHODS(GeneralTextScript)
+
+ GeneralTextScript(VirtualBoxBase *pSetError, const char *pszDefaultTemplateFilename = NULL, const char *pszDefaultFilename = NULL)
+ : BaseTextScript(pSetError, pszDefaultTemplateFilename, pszDefaultFilename), mfDataParsed(false)
+ {}
+ virtual ~GeneralTextScript() {}
+
+ HRESULT parse();
+ HRESULT saveToString(Utf8Str &rStrDst);
+
+ //////////////////New functions//////////////////////////////
+
+ bool isDataParsed() const
+ {
+ return mfDataParsed;
+ }
+
+ /**
+ * Returns the actual size of script in lines
+ */
+ size_t getLineNumbersOfScript() const
+ {
+ return mScriptContentByLines.size();
+ }
+
+ /**
+ * Gets a read-only reference to the given line, returning Utf8Str::Empty if
+ * idxLine is out of range.
+ *
+ * @returns Line string reference or Utf8Str::Empty.
+ * @param idxLine The line number.
+ *
+ * @todo RTCList doesn't allow this method to be const.
+ */
+ RTCString const &getContentOfLine(size_t idxLine);
+
+ /**
+ * Set new content of line
+ */
+ HRESULT setContentOfLine(size_t idxLine, const Utf8Str &newContent);
+
+ /**
+ * Find a substring in the script
+ * Returns a list with the found lines
+ * @throws std::bad_alloc
+ */
+ std::vector<size_t> findTemplate(const Utf8Str &rStrNeedle, RTCString::CaseSensitivity enmCase = RTCString::CaseSensitive);
+
+ /**
+ * In line @a idxLine replace the first occurence of @a rStrNeedle with
+ * @a rStrRelacement.
+ */
+ HRESULT findAndReplace(size_t idxLine, const Utf8Str &rStrNeedle, const Utf8Str &rStrReplacement);
+
+ /**
+ * Append a string into the end of the given line.
+ */
+ HRESULT appendToLine(size_t idxLine, const Utf8Str &rStrToAppend);
+
+ /**
+ * Prepend a string in the beginning of the given line.
+ */
+ HRESULT prependToLine(size_t idxLine, const Utf8Str &rStrToPrepend);
+
+ /**
+ * Append a new line at the end of the list of line.
+ */
+ HRESULT appendLine(const Utf8Str &rStrLineToAppend);
+ //////////////////New functions//////////////////////////////
+};
+
+
+#endif /* !MAIN_INCLUDED_TextScript_h */
diff --git a/src/VBox/Main/include/ThreadTask.h b/src/VBox/Main/include/ThreadTask.h
new file mode 100644
index 00000000..c743e2cc
--- /dev/null
+++ b/src/VBox/Main/include/ThreadTask.h
@@ -0,0 +1,78 @@
+/** @file
+ * VirtualBox ThreadTask class definition
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ThreadTask_h
+#define MAIN_INCLUDED_ThreadTask_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBox/com/string.h"
+
+/**
+ * The class ThreadVoidData is used as a base class for any data which we want to pass into a thread
+ */
+struct ThreadVoidData
+{
+public:
+ ThreadVoidData() { }
+ virtual ~ThreadVoidData() { }
+};
+
+
+class ThreadTask
+{
+public:
+ ThreadTask(const Utf8Str &t)
+ : m_strTaskName(t)
+ , mAsync(false)
+ { }
+
+ virtual ~ThreadTask()
+ { }
+
+ HRESULT createThread(void);
+ HRESULT createThreadWithType(RTTHREADTYPE enmType);
+
+ inline Utf8Str getTaskName() const { return m_strTaskName; }
+ bool isAsync() { return mAsync; }
+
+protected:
+ HRESULT createThreadInternal(RTTHREADTYPE enmType);
+ static DECLCALLBACK(int) taskHandlerThreadProc(RTTHREAD thread, void *pvUser);
+
+ ThreadTask() : m_strTaskName("GenericTask")
+ { }
+
+ Utf8Str m_strTaskName;
+ bool mAsync;
+
+private:
+ virtual void handler() = 0;
+};
+
+#endif /* !MAIN_INCLUDED_ThreadTask_h */
+
diff --git a/src/VBox/Main/include/TokenImpl.h b/src/VBox/Main/include/TokenImpl.h
new file mode 100644
index 00000000..433f835f
--- /dev/null
+++ b/src/VBox/Main/include/TokenImpl.h
@@ -0,0 +1,118 @@
+/* $Id: TokenImpl.h $ */
+/** @file
+ * Token COM class implementations - MachineToken and MediumLockToken
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_TokenImpl_h
+#define MAIN_INCLUDED_TokenImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "TokenWrap.h"
+#include "MachineImpl.h"
+
+
+/**
+ * The MachineToken class automates cleanup of a SessionMachine object.
+ */
+class ATL_NO_VTABLE MachineToken :
+ public TokenWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(MachineToken)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(const ComObjPtr<SessionMachine> &pSessionMachine);
+ void uninit(bool fAbandon);
+
+private:
+
+ // wrapped IToken methods
+ HRESULT abandon(AutoCaller &aAutoCaller);
+ HRESULT dummy();
+
+ // data
+ struct Data
+ {
+ Data()
+ {
+ }
+
+ ComObjPtr<SessionMachine> pSessionMachine;
+ };
+
+ Data m;
+};
+
+
+class Medium;
+
+/**
+ * The MediumLockToken class automates cleanup of a Medium lock.
+ */
+class ATL_NO_VTABLE MediumLockToken :
+ public TokenWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(MediumLockToken)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(const ComObjPtr<Medium> &pMedium, bool fWrite);
+ void uninit();
+
+private:
+
+ // wrapped IToken methods
+ HRESULT abandon(AutoCaller &aAutoCaller);
+ HRESULT dummy();
+
+ // data
+ struct Data
+ {
+ Data() :
+ fWrite(false)
+ {
+ }
+
+ ComObjPtr<Medium> pMedium;
+ bool fWrite;
+ };
+
+ Data m;
+};
+
+
+#endif /* !MAIN_INCLUDED_TokenImpl_h */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/TrustAnchorsAndCerts.h b/src/VBox/Main/include/TrustAnchorsAndCerts.h
new file mode 100644
index 00000000..120807d0
--- /dev/null
+++ b/src/VBox/Main/include/TrustAnchorsAndCerts.h
@@ -0,0 +1,53 @@
+/* $Id: TrustAnchorsAndCerts.h $ */
+/** @file
+ * Main - Collection of trust anchors and certificates included in VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2021-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_TrustAnchorsAndCerts_h
+#define MAIN_INCLUDED_TrustAnchorsAndCerts_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+
+RT_C_DECLS_BEGIN
+
+extern const unsigned char g_abUefiMicrosoftKek[];
+extern const unsigned g_cbUefiMicrosoftKek;
+
+extern const unsigned char g_abUefiMicrosoftCa[];
+extern const unsigned g_cbUefiMicrosoftCa;
+
+extern const unsigned char g_abUefiMicrosoftProPca[];
+extern const unsigned g_cbUefiMicrosoftProPca;
+
+extern const unsigned char g_abUefiOracleDefPk[];
+extern const unsigned g_cbUefiOracleDefPk;
+
+RT_C_DECLS_END
+
+#endif /* !MAIN_INCLUDED_TrustAnchorsAndCerts_h */
+
diff --git a/src/VBox/Main/include/TrustedPlatformModuleImpl.h b/src/VBox/Main/include/TrustedPlatformModuleImpl.h
new file mode 100644
index 00000000..af359a84
--- /dev/null
+++ b/src/VBox/Main/include/TrustedPlatformModuleImpl.h
@@ -0,0 +1,84 @@
+/* $Id: TrustedPlatformModuleImpl.h $ */
+
+/** @file
+ *
+ * VirtualBox COM class implementation - Machine Trusted Platform Module settings.
+ */
+
+/*
+ * Copyright (C) 2021-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_TrustedPlatformModuleImpl_h
+#define MAIN_INCLUDED_TrustedPlatformModuleImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "TrustedPlatformModuleWrap.h"
+
+class GuestOSType;
+
+namespace settings
+{
+ struct TpmSettings;
+}
+
+class ATL_NO_VTABLE TrustedPlatformModule :
+ public TrustedPlatformModuleWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(TrustedPlatformModule)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *parent);
+ HRESULT init(Machine *parent, TrustedPlatformModule *that);
+ HRESULT initCopy(Machine *parent, TrustedPlatformModule *that);
+ void uninit();
+
+ // public methods for internal purposes only
+ HRESULT i_loadSettings(const settings::TpmSettings &data);
+ HRESULT i_saveSettings(settings::TpmSettings &data);
+
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(TrustedPlatformModule *aThat);
+ void i_applyDefaults(GuestOSType *aOsType);
+
+private:
+
+ // wrapped ITrustedPlatformModule properties
+ HRESULT getType(TpmType_T *aType);
+ HRESULT setType(TpmType_T aType);
+ HRESULT getLocation(com::Utf8Str &location);
+ HRESULT setLocation(const com::Utf8Str &location);
+
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_TrustedPlatformModuleImpl_h */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/USBControllerImpl.h b/src/VBox/Main/include/USBControllerImpl.h
new file mode 100644
index 00000000..16f15688
--- /dev/null
+++ b/src/VBox/Main/include/USBControllerImpl.h
@@ -0,0 +1,88 @@
+/* $Id: USBControllerImpl.h $ */
+
+/** @file
+ *
+ * VBox USBController COM Class declaration.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_USBControllerImpl_h
+#define MAIN_INCLUDED_USBControllerImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "USBControllerWrap.h"
+
+class HostUSBDevice;
+class USBDeviceFilter;
+
+namespace settings
+{
+ struct USBController;
+}
+
+class ATL_NO_VTABLE USBController :
+ public USBControllerWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(USBController)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aParent, const com::Utf8Str &aName, USBControllerType_T enmType);
+ HRESULT init(Machine *aParent, USBController *aThat, bool fReshare = false);
+ HRESULT initCopy(Machine *aParent, USBController *aThat);
+ void uninit();
+
+ // public methods only for internal purposes
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(USBController *aThat);
+ void i_unshare();
+
+ ComObjPtr<USBController> i_getPeer();
+ const Utf8Str &i_getName() const;
+ const USBControllerType_T &i_getControllerType() const;
+
+private:
+
+ // wrapped IUSBController properties
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT setName(const com::Utf8Str &aName);
+ HRESULT getType(USBControllerType_T *aType);
+ HRESULT setType(USBControllerType_T aType);
+ HRESULT getUSBStandard(USHORT *aUSBStandard);
+
+ void printList();
+
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_USBControllerImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/USBDeviceFilterImpl.h b/src/VBox/Main/include/USBDeviceFilterImpl.h
new file mode 100644
index 00000000..69c13116
--- /dev/null
+++ b/src/VBox/Main/include/USBDeviceFilterImpl.h
@@ -0,0 +1,240 @@
+/* $Id: USBDeviceFilterImpl.h $ */
+/** @file
+ * Declaration of USBDeviceFilter and HostUSBDeviceFilter.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_USBDeviceFilterImpl_h
+#define MAIN_INCLUDED_USBDeviceFilterImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/settings.h>
+#include "Matching.h"
+#include <VBox/usbfilter.h>
+#include "USBDeviceFilterWrap.h"
+
+class USBDeviceFilters;
+class Host;
+namespace settings
+{
+ struct USBDeviceFilter;
+}
+
+// USBDeviceFilter
+////////////////////////////////////////////////////////////////////////////////
+
+class ATL_NO_VTABLE USBDeviceFilter :
+ public USBDeviceFilterWrap
+{
+public:
+
+ struct BackupableUSBDeviceFilterData
+ {
+ typedef matching::Matchable <matching::ParsedBoolFilter> BOOLFilter;
+
+ BackupableUSBDeviceFilterData() : mId (NULL) {}
+ BackupableUSBDeviceFilterData(const BackupableUSBDeviceFilterData &aThat) :
+ mRemote(aThat.mRemote), mId(aThat.mId)
+ {
+ mData.strName = aThat.mData.strName;
+ mData.fActive = aThat.mData.fActive;
+ mData.ulMaskedInterfaces = aThat.mData.ulMaskedInterfaces;
+ USBFilterClone(&mUSBFilter, &aThat.mUSBFilter);
+ }
+
+ /** Remote or local matching criterion. */
+ BOOLFilter mRemote;
+
+ /** The filter data blob. */
+ USBFILTER mUSBFilter;
+
+ /** Arbitrary ID field (not used by the class itself) */
+ void *mId;
+
+ settings::USBDeviceFilter mData;
+ };
+
+ DECLARE_COMMON_CLASS_METHODS(USBDeviceFilter)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(USBDeviceFilters *aParent,
+ const settings::USBDeviceFilter &data);
+ HRESULT init(USBDeviceFilters *aParent, IN_BSTR aName);
+ HRESULT init(USBDeviceFilters *aParent, USBDeviceFilter *aThat,
+ bool aReshare = false);
+ HRESULT initCopy(USBDeviceFilters *aParent, USBDeviceFilter *aThat);
+ void uninit();
+
+ // public methods only for internal purposes
+ bool i_isModified();
+ void i_rollback();
+ void i_commit();
+
+ void unshare();
+
+ // public methods for internal purposes only
+ // (ensure there is a caller and a read lock before calling them!)
+ void *& i_getId() { return bd->mId; }
+ const BackupableUSBDeviceFilterData& i_getData() { return *bd.data(); }
+ ComObjPtr<USBDeviceFilter> i_peer() { return mPeer; }
+
+ // tr() wants to belong to a class it seems, thus this one here.
+ static HRESULT i_usbFilterFieldFromString(PUSBFILTER aFilter,
+ USBFILTERIDX aIdx,
+ const Utf8Str &aValue,
+ Utf8Str &aErrStr);
+
+ static const char* i_describeUSBFilterIdx(USBFILTERIDX aIdx);
+
+private:
+
+ // wrapped IUSBDeviceFilter properties
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT setName(const com::Utf8Str &aName);
+ HRESULT getActive(BOOL *aActive);
+ HRESULT setActive(BOOL aActive);
+ HRESULT getVendorId(com::Utf8Str &aVendorId);
+ HRESULT setVendorId(const com::Utf8Str &aVendorId);
+ HRESULT getProductId(com::Utf8Str &aProductId);
+ HRESULT setProductId(const com::Utf8Str &aProductId);
+ HRESULT getRevision(com::Utf8Str &aRevision);
+ HRESULT setRevision(const com::Utf8Str &aRevision);
+ HRESULT getManufacturer(com::Utf8Str &aManufacturer);
+ HRESULT setManufacturer(const com::Utf8Str &aManufacturer);
+ HRESULT getProduct(com::Utf8Str &aProduct);
+ HRESULT setProduct(const com::Utf8Str &aProduct);
+ HRESULT getSerialNumber(com::Utf8Str &aSerialNumber);
+ HRESULT setSerialNumber(const com::Utf8Str &aSerialNumber);
+ HRESULT getPort(com::Utf8Str &aPort);
+ HRESULT setPort(const com::Utf8Str &aPort);
+ HRESULT getRemote(com::Utf8Str &aRemote);
+ HRESULT setRemote(const com::Utf8Str &aRemote);
+ HRESULT getMaskedInterfaces(ULONG *aMaskedInterfaces);
+ HRESULT setMaskedInterfaces(ULONG aMaskedInterfaces);
+
+ // wrapped IUSBDeviceFilter methods
+ HRESULT i_usbFilterFieldGetter(USBFILTERIDX aIdx, com::Utf8Str &aStr);
+ HRESULT i_usbFilterFieldSetter(USBFILTERIDX aIdx, const com::Utf8Str &strNew);
+
+ USBDeviceFilters * const mParent;
+ USBDeviceFilter * const mPeer;
+
+ Backupable<BackupableUSBDeviceFilterData> bd;
+
+ bool m_fModified;
+
+ /** Used externally to indicate this filter is in the list
+ (not touched by the class itself except that in init()/uninit()) */
+ bool mInList;
+
+ friend class USBDeviceFilters;
+};
+#include "HostUSBDeviceFilterWrap.h"
+
+// HostUSBDeviceFilter
+////////////////////////////////////////////////////////////////////////////////
+
+class ATL_NO_VTABLE HostUSBDeviceFilter :
+ public HostUSBDeviceFilterWrap
+{
+public:
+
+ struct BackupableUSBDeviceFilterData : public USBDeviceFilter::BackupableUSBDeviceFilterData
+ {
+ BackupableUSBDeviceFilterData() {}
+ };
+
+ DECLARE_COMMON_CLASS_METHODS (HostUSBDeviceFilter)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Host *aParent,
+ const settings::USBDeviceFilter &data);
+ HRESULT init(Host *aParent, IN_BSTR aName);
+ void uninit();
+
+ // public methods for internal purposes only
+ // (ensure there is a caller and a read lock before calling them!)
+ void i_saveSettings(settings::USBDeviceFilter &data);
+
+ void*& i_getId() { return bd.data()->mId; }
+
+ const BackupableUSBDeviceFilterData& i_getData() { return *bd.data(); }
+
+ // util::Lockable interface
+ RWLockHandle *lockHandle() const;
+
+private:
+
+ // wrapped IHostUSBDeviceFilter properties
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT setName(const com::Utf8Str &aName);
+ HRESULT getActive(BOOL *aActive);
+ HRESULT setActive(BOOL aActive);
+ HRESULT getVendorId(com::Utf8Str &aVendorId);
+ HRESULT setVendorId(const com::Utf8Str &aVendorId);
+ HRESULT getProductId(com::Utf8Str &aProductId);
+ HRESULT setProductId(const com::Utf8Str &aProductId);
+ HRESULT getRevision(com::Utf8Str &aRevision);
+ HRESULT setRevision(const com::Utf8Str &aRevision);
+ HRESULT getManufacturer(com::Utf8Str &aManufacturer);
+ HRESULT setManufacturer(const com::Utf8Str &aManufacturer);
+ HRESULT getProduct(com::Utf8Str &aProduct);
+ HRESULT setProduct(const com::Utf8Str &aProduct);
+ HRESULT getSerialNumber(com::Utf8Str &aSerialNumber);
+ HRESULT setSerialNumber(const com::Utf8Str &aSerialNumber);
+ HRESULT getPort(com::Utf8Str &aPort);
+ HRESULT setPort(const com::Utf8Str &aPort);
+ HRESULT getRemote(com::Utf8Str &aRemote);
+ HRESULT setRemote(const com::Utf8Str &aRemote);
+ HRESULT getMaskedInterfaces(ULONG *aMaskedInterfaces);
+ HRESULT setMaskedInterfaces(ULONG aMaskedInterfaces);
+
+ // wrapped IHostUSBDeviceFilter properties
+ HRESULT getAction(USBDeviceFilterAction_T *aAction);
+ HRESULT setAction(USBDeviceFilterAction_T aAction);
+
+ HRESULT i_usbFilterFieldGetter(USBFILTERIDX aIdx, com::Utf8Str &aStr);
+ HRESULT i_usbFilterFieldSetter(USBFILTERIDX aIdx, const com::Utf8Str &aStr);
+
+ Host * const mParent;
+
+ Backupable<BackupableUSBDeviceFilterData> bd;
+
+ /** Used externally to indicate this filter is in the list
+ (not touched by the class itself except that in init()/uninit()) */
+ bool mInList;
+
+ friend class Host;
+};
+
+#endif /* !MAIN_INCLUDED_USBDeviceFilterImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/USBDeviceFiltersImpl.h b/src/VBox/Main/include/USBDeviceFiltersImpl.h
new file mode 100644
index 00000000..1daf5d53
--- /dev/null
+++ b/src/VBox/Main/include/USBDeviceFiltersImpl.h
@@ -0,0 +1,97 @@
+/* $Id: USBDeviceFiltersImpl.h $ */
+/** @file
+ * VBox USBDeviceFilters COM Class declaration.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_USBDeviceFiltersImpl_h
+#define MAIN_INCLUDED_USBDeviceFiltersImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "USBDeviceFiltersWrap.h"
+
+class HostUSBDevice;
+class USBDeviceFilter;
+
+namespace settings
+{
+ struct USB;
+}
+
+class ATL_NO_VTABLE USBDeviceFilters :
+ public USBDeviceFiltersWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(USBDeviceFilters)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aParent);
+ HRESULT init(Machine *aParent, USBDeviceFilters *aThat);
+ HRESULT initCopy(Machine *aParent, USBDeviceFilters *aThat);
+ void uninit();
+
+ // public methods only for internal purposes
+ HRESULT i_loadSettings(const settings::USB &data);
+ HRESULT i_saveSettings(settings::USB &data);
+
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(USBDeviceFilters *aThat);
+
+#ifdef VBOX_WITH_USB
+ HRESULT i_onDeviceFilterChange(USBDeviceFilter *aFilter,
+ BOOL aActiveChanged = FALSE);
+ bool i_hasMatchingFilter(const ComObjPtr<HostUSBDevice> &aDevice, ULONG *aMaskedIfs);
+ bool i_hasMatchingFilter(IUSBDevice *aUSBDevice, ULONG *aMaskedIfs);
+ HRESULT i_notifyProxy(bool aInsertFilters);
+#endif /* VBOX_WITH_USB */
+
+ // public methods for internal purposes only
+ // (ensure there is a caller and a read lock before calling them!)
+ Machine* i_getMachine();
+
+private:
+
+ // Wrapped IUSBDeviceFilters attributes
+ HRESULT getDeviceFilters(std::vector<ComPtr<IUSBDeviceFilter> > &aDeviceFilters);
+
+ // wrapped IUSBDeviceFilters methods
+ HRESULT createDeviceFilter(const com::Utf8Str &aName,
+ ComPtr<IUSBDeviceFilter> &aFilter);
+ HRESULT insertDeviceFilter(ULONG aPosition,
+ const ComPtr<IUSBDeviceFilter> &aFilter);
+ HRESULT removeDeviceFilter(ULONG aPosition,
+ ComPtr<IUSBDeviceFilter> &aFilter);
+ struct Data;
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_USBDeviceFiltersImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/USBDeviceImpl.h b/src/VBox/Main/include/USBDeviceImpl.h
new file mode 100644
index 00000000..d0985239
--- /dev/null
+++ b/src/VBox/Main/include/USBDeviceImpl.h
@@ -0,0 +1,118 @@
+/* $Id: USBDeviceImpl.h $ */
+/** @file
+ * Header file for the OUSBDevice (IUSBDevice) class, VBoxC.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_USBDeviceImpl_h
+#define MAIN_INCLUDED_USBDeviceImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "USBDeviceWrap.h"
+
+/**
+ * Object class used for maintaining devices attached to a USB controller.
+ * Generally this contains much less information.
+ */
+class ATL_NO_VTABLE OUSBDevice :
+ public USBDeviceWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(OUSBDevice)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(IUSBDevice *a_pUSBDevice);
+ void uninit();
+
+ // public methods only for internal purposes
+ const Guid &i_id() const { return mData.id; }
+
+private:
+
+ // Wrapped IUSBDevice properties
+ HRESULT getId(com::Guid &aId);
+ HRESULT getVendorId(USHORT *aVendorId);
+ HRESULT getProductId(USHORT *aProductId);
+ HRESULT getRevision(USHORT *aRevision);
+ HRESULT getManufacturer(com::Utf8Str &aManufacturer);
+ HRESULT getProduct(com::Utf8Str &aProduct);
+ HRESULT getSerialNumber(com::Utf8Str &aSerialNumber);
+ HRESULT getAddress(com::Utf8Str &aAddress);
+ HRESULT getPort(USHORT *aPort);
+ HRESULT getPortPath(com::Utf8Str &aPortPath);
+ HRESULT getVersion(USHORT *aVersion);
+ HRESULT getSpeed(USBConnectionSpeed_T *aSpeed);
+ HRESULT getRemote(BOOL *aRemote);
+ HRESULT getBackend(com::Utf8Str &aBackend);
+ HRESULT getDeviceInfo(std::vector<com::Utf8Str> &aInfo);
+
+ struct Data
+ {
+ Data() : vendorId(0), productId(0), revision(0), port(0),
+ version(1), speed(USBConnectionSpeed_Null),
+ remote(FALSE) {}
+
+ /** The UUID of this device. */
+ const Guid id;
+
+ /** The vendor id of this USB device. */
+ const USHORT vendorId;
+ /** The product id of this USB device. */
+ const USHORT productId;
+ /** The product revision number of this USB device.
+ * (high byte = integer; low byte = decimal) */
+ const USHORT revision;
+ /** The Manufacturer string. (Quite possibly NULL.) */
+ const com::Utf8Str manufacturer;
+ /** The Product string. (Quite possibly NULL.) */
+ const com::Utf8Str product;
+ /** The SerialNumber string. (Quite possibly NULL.) */
+ const com::Utf8Str serialNumber;
+ /** The host specific address of the device. */
+ const com::Utf8Str address;
+ /** The device specific backend. */
+ const com::Utf8Str backend;
+ /** The host port number. */
+ const USHORT port;
+ /** The host port path. */
+ const com::Utf8Str portPath;
+ /** The major USB version number of the device. */
+ const USHORT version;
+ /** The speed at which the device is communicating. */
+ const USBConnectionSpeed_T speed;
+ /** Remote (VRDP) or local device. */
+ const BOOL remote;
+ };
+
+ Data mData;
+};
+
+#endif /* !MAIN_INCLUDED_USBDeviceImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/USBGetDevices.h b/src/VBox/Main/include/USBGetDevices.h
new file mode 100644
index 00000000..39470b7f
--- /dev/null
+++ b/src/VBox/Main/include/USBGetDevices.h
@@ -0,0 +1,114 @@
+/* $Id: USBGetDevices.h $ */
+/** @file
+ * VirtualBox Linux host USB device enumeration.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_USBGetDevices_h
+#define MAIN_INCLUDED_USBGetDevices_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/usb.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+/**
+ * Free all the members of a USB device created by the Linux enumeration code.
+ *
+ * @note This duplicates a USBProxyService method which we needed access too
+ * without pulling in the rest of the proxy service code.
+ *
+ * @param pDevice Pointer to the device.
+ */
+DECLINLINE(void) deviceFreeMembers(PUSBDEVICE pDevice)
+{
+ RTStrFree((char *)pDevice->pszManufacturer);
+ pDevice->pszManufacturer = NULL;
+ RTStrFree((char *)pDevice->pszProduct);
+ pDevice->pszProduct = NULL;
+ RTStrFree((char *)pDevice->pszSerialNumber);
+ pDevice->pszSerialNumber = NULL;
+
+ RTStrFree((char *)pDevice->pszAddress);
+ pDevice->pszAddress = NULL;
+}
+
+/**
+ * Free one USB device created by the Linux enumeration code.
+ *
+ * @note This duplicates a USBProxyService method which we needed access too
+ * without pulling in the rest of the proxy service code.
+ *
+ * @param pDevice Pointer to the device. NULL is OK.
+ */
+DECLINLINE(void) deviceFree(PUSBDEVICE pDevice)
+{
+ if (pDevice)
+ {
+ deviceFreeMembers(pDevice);
+ RTMemFree(pDevice);
+ }
+}
+
+/**
+ * Free a linked list of USB devices created by the Linux enumeration code.
+ * @param ppHead Pointer to the first device in the linked list
+ */
+DECLINLINE(void) deviceListFree(PUSBDEVICE *ppHead)
+{
+ PUSBDEVICE pHead = *ppHead;
+ while (pHead)
+ {
+ PUSBDEVICE pNext = pHead->pNext;
+ deviceFree(pHead);
+ pHead = pNext;
+ }
+ *ppHead = NULL;
+}
+
+RT_C_DECLS_BEGIN
+
+extern bool USBProxyLinuxCheckDeviceRoot(const char *pcszRoot, bool fIsDeviceNodes);
+
+#ifdef UNIT_TEST
+void TestUSBSetupInit(const char *pcszUsbfsRoot, bool fUsbfsAccessible,
+ const char *pcszDevicesRoot, bool fDevicesAccessible,
+ int rcMethodInitResult);
+void TestUSBSetEnv(const char *pcszEnvUsb, const char *pcszEnvUsbRoot);
+#endif
+
+extern int USBProxyLinuxChooseMethod(bool *pfUsingUsbfsDevices, const char **ppcszDevicesRoot);
+#ifdef UNIT_TEST
+extern void TestUSBSetAvailableUsbfsDevices(const char **pacszDeviceAddresses);
+extern void TestUSBSetAccessibleFiles(const char **pacszAccessibleFiles);
+#endif
+
+extern PUSBDEVICE USBProxyLinuxGetDevices(const char *pcszDevicesRoot, bool fUseSysfs);
+
+RT_C_DECLS_END
+
+#endif /* !MAIN_INCLUDED_USBGetDevices_h */
+
diff --git a/src/VBox/Main/include/USBIdDatabase.h b/src/VBox/Main/include/USBIdDatabase.h
new file mode 100644
index 00000000..66e3cca1
--- /dev/null
+++ b/src/VBox/Main/include/USBIdDatabase.h
@@ -0,0 +1,226 @@
+/* $Id: USBIdDatabase.h $ */
+/** @file
+ * USB device vendor and product ID database.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_USBIdDatabase_h
+#define MAIN_INCLUDED_USBIdDatabase_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/stdint.h>
+#include <iprt/cpp/ministring.h>
+#include <iprt/bldprog-strtab.h>
+
+
+/** Saves a few bytes (~25%) on strings. */
+#define USB_ID_DATABASE_WITH_COMPRESSION
+
+/** Max string length. */
+#define USB_ID_DATABASE_MAX_STRING _1K
+
+
+AssertCompileSize(RTBLDPROGSTRREF, sizeof(uint32_t));
+
+
+/**
+ * Elements of product table.
+ */
+typedef struct USBIDDBPROD
+{
+ /** Product ID. */
+ uint16_t idProduct;
+} USBIDDBPROD;
+AssertCompileSize(USBIDDBPROD, sizeof(uint16_t));
+
+
+/**
+ * Element of vendor table.
+ */
+typedef struct USBIDDBVENDOR
+{
+ /** Vendor ID. */
+ uint16_t idVendor;
+ /** Index of the first product. */
+ uint16_t iProduct;
+ /** Number of products. */
+ uint16_t cProducts;
+} USBIDDBVENDOR;
+AssertCompileSize(USBIDDBVENDOR, sizeof(uint16_t) * 3);
+
+
+/**
+ * Wrapper for static array of Aliases.
+ */
+class USBIdDatabase
+{
+public: // For assertions and statis in the generator.
+ /** The compressed string table. */
+ static RTBLDPROGSTRTAB const s_StrTab;
+
+ /** Number of vendors in the two parallel arrays. */
+ static const size_t s_cVendors;
+ /** Vendor IDs lookup table. */
+ static const USBIDDBVENDOR s_aVendors[];
+ /** Vendor names table running parallel to s_aVendors. */
+ static const RTBLDPROGSTRREF s_aVendorNames[];
+
+ /** Number of products in the two parallel arrays. */
+ static const size_t s_cProducts;
+ /** Vendor+Product keys for lookup purposes. */
+ static const USBIDDBPROD s_aProducts[];
+ /** Product names table running parallel to s_aProducts. */
+ static const RTBLDPROGSTRREF s_aProductNames[];
+
+public:
+ static RTCString returnString(PCRTBLDPROGSTRREF pStr)
+ {
+ char szTmp[USB_ID_DATABASE_MAX_STRING * 2];
+ ssize_t cchTmp = RTBldProgStrTabQueryString(&s_StrTab, pStr->off, pStr->cch, szTmp, sizeof(szTmp));
+ return RTCString(szTmp, (size_t)RT_MAX(cchTmp, 0));
+ }
+
+private:
+ /**
+ * Performs a binary lookup of @a idVendor.
+ *
+ * @returns The index in the vendor tables, UINT32_MAX if not found.
+ * @param idVendor The vendor ID.
+ */
+ static uint32_t lookupVendor(uint16_t idVendor)
+ {
+ size_t iEnd = s_cVendors;
+ if (iEnd)
+ {
+ size_t iStart = 0;
+ for (;;)
+ {
+ size_t idx = iStart + (iEnd - iStart) / 2;
+ if (s_aVendors[idx].idVendor < idVendor)
+ {
+ idx++;
+ if (idx < iEnd)
+ iStart = idx;
+ else
+ break;
+ }
+ else if (s_aVendors[idx].idVendor > idVendor)
+ {
+ if (idx != iStart)
+ iEnd = idx;
+ else
+ break;
+ }
+ else
+ return (uint32_t)idx;
+ }
+ }
+ return UINT32_MAX;
+ }
+
+ /**
+ * Performs a binary lookup of @a idProduct.
+ *
+ * @returns The index in the product tables, UINT32_MAX if not found.
+ * @param idProduct The product ID.
+ * @param iStart The index of the first entry for the vendor.
+ * @param iEnd The index of after the last entry.
+ */
+ static uint32_t lookupProduct(uint16_t idProduct, size_t iStart, size_t iEnd)
+ {
+ if (iStart < iEnd)
+ {
+ for (;;)
+ {
+ size_t idx = iStart + (iEnd - iStart) / 2;
+ if (s_aProducts[idx].idProduct < idProduct)
+ {
+ idx++;
+ if (idx < iEnd)
+ iStart = idx;
+ else
+ break;
+ }
+ else if (s_aProducts[idx].idProduct > idProduct)
+ {
+ if (idx != iStart)
+ iEnd = idx;
+ else
+ break;
+ }
+ else
+ return (uint32_t)idx;
+ }
+ }
+ return UINT32_MAX;
+ }
+
+
+public:
+ static RTCString findProduct(uint16_t idVendor, uint16_t idProduct)
+ {
+ uint32_t idxVendor = lookupVendor(idVendor);
+ if (idxVendor != UINT32_MAX)
+ {
+ uint32_t idxProduct = lookupProduct(idProduct, s_aVendors[idxVendor].iProduct,
+ s_aVendors[idxVendor].iProduct + s_aVendors[idxVendor].cProducts);
+ if (idxProduct != UINT32_MAX)
+ return returnString(&s_aProductNames[idxProduct]);
+ }
+ return RTCString();
+ }
+
+ static RTCString findVendor(uint16_t idVendor)
+ {
+ uint32_t idxVendor = lookupVendor(idVendor);
+ if (idxVendor != UINT32_MAX)
+ return returnString(&s_aVendorNames[idxVendor]);
+ return RTCString();
+ }
+
+ static RTCString findVendorAndProduct(uint16_t idVendor, uint16_t idProduct, RTCString *pstrProduct)
+ {
+ uint32_t idxVendor = lookupVendor(idVendor);
+ if (idxVendor != UINT32_MAX)
+ {
+ uint32_t idxProduct = lookupProduct(idProduct, s_aVendors[idxVendor].iProduct,
+ s_aVendors[idxVendor].iProduct + s_aVendors[idxVendor].cProducts);
+ if (idxProduct != UINT32_MAX)
+ *pstrProduct = returnString(&s_aProductNames[idxProduct]);
+ else
+ pstrProduct->setNull();
+ return returnString(&s_aVendorNames[idxVendor]);
+ }
+ pstrProduct->setNull();
+ return RTCString();
+ }
+
+};
+
+
+#endif /* !MAIN_INCLUDED_USBIdDatabase_h */
+
diff --git a/src/VBox/Main/include/USBProxyBackend.h b/src/VBox/Main/include/USBProxyBackend.h
new file mode 100644
index 00000000..8af51a5b
--- /dev/null
+++ b/src/VBox/Main/include/USBProxyBackend.h
@@ -0,0 +1,448 @@
+/* $Id: USBProxyBackend.h $ */
+/** @file
+ * VirtualBox USB Proxy Backend (base) class.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_USBProxyBackend_h
+#define MAIN_INCLUDED_USBProxyBackend_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/usb.h>
+#include <VBox/usbfilter.h>
+
+#include <iprt/socket.h>
+#include <iprt/poll.h>
+#include <iprt/semaphore.h>
+#include <iprt/cpp/utils.h>
+
+#include "VirtualBoxBase.h"
+#include "VirtualBoxImpl.h"
+#include "HostUSBDeviceImpl.h"
+#include "USBProxyBackendWrap.h"
+class USBProxyService;
+
+/**
+ * Base class for the USB Proxy Backend.
+ */
+class ATL_NO_VTABLE USBProxyBackend
+ : public USBProxyBackendWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(USBProxyBackend)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ virtual int init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings);
+ virtual void uninit();
+
+ bool isActive(void);
+ const com::Utf8Str &i_getId();
+ const com::Utf8Str &i_getAddress();
+ virtual const com::Utf8Str &i_getBackend();
+ uint32_t i_getRefCount();
+
+ virtual bool i_isDevReEnumerationRequired();
+
+ /** @name Interface for the USBController and the Host object.
+ * @{ */
+ virtual void *insertFilter(PCUSBFILTER aFilter);
+ virtual void removeFilter(void *aId);
+ /** @} */
+
+ /** @name Interfaces for the HostUSBDevice
+ * @{ */
+ virtual int captureDevice(HostUSBDevice *aDevice);
+ virtual void captureDeviceCompleted(HostUSBDevice *aDevice, bool aSuccess);
+ virtual int releaseDevice(HostUSBDevice *aDevice);
+ virtual void releaseDeviceCompleted(HostUSBDevice *aDevice, bool aSuccess);
+ /** @} */
+
+ static void freeDevice(PUSBDEVICE pDevice);
+
+protected:
+ int start(void);
+ int stop(void);
+ virtual void serviceThreadInit(void);
+ virtual void serviceThreadTerm(void);
+
+ virtual int wait(RTMSINTERVAL aMillies);
+ virtual int interruptWait(void);
+ virtual PUSBDEVICE getDevices(void);
+ uint32_t incRef();
+ uint32_t decRef();
+
+ static HRESULT setError(HRESULT aResultCode, const char *aText, ...);
+
+ static void initFilterFromDevice(PUSBFILTER aFilter, HostUSBDevice *aDevice);
+ static void freeDeviceMembers(PUSBDEVICE pDevice);
+
+ /**
+ * Backend specific callback when a device was added.
+ * (Currently only Linux uses it to adjust the udev polling).
+ */
+ virtual void deviceAdded(ComObjPtr<HostUSBDevice> &aDevice, PUSBDEVICE pDev);
+ virtual bool isFakeUpdateRequired();
+
+private:
+
+ // wrapped IUSBProxyBackend properties
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getType(com::Utf8Str &aType);
+
+ static DECLCALLBACK(int) serviceThread(RTTHREAD Thread, void *pvUser);
+
+ void updateDeviceList(PUSBDEVICE pDevices);
+
+protected:
+ /** Pointer to the owning USB Proxy Service object. */
+ USBProxyService *m_pUsbProxyService;
+ /** Thread handle of the service thread. */
+ RTTHREAD mThread;
+ /** Flag which stop() sets to cause serviceThread to return. */
+ bool volatile mTerminate;
+ /** Id of the instance. */
+ const com::Utf8Str m_strId;
+ /** Address of the instance. */
+ const com::Utf8Str m_strAddress;
+ /** Backend identifier as used in the settings. */
+ const com::Utf8Str m_strBackend;
+ /** Reference counter which prevents the backend instance from being removed. */
+ uint32_t m_cRefs;
+ /** List of smart HostUSBDevice pointers. */
+ typedef std::list<ComObjPtr<HostUSBDevice> > HostUSBDeviceList;
+ /** List of the known USB devices for this backend. */
+ HostUSBDeviceList m_llDevices;
+};
+
+
+# if defined(RT_OS_DARWIN) || defined(DOXYGEN_RUNNING)
+# include <VBox/param.h>
+# undef PAGE_SHIFT
+# undef PAGE_SIZE
+# define OSType Carbon_OSType
+# include <Carbon/Carbon.h>
+# undef OSType
+# undef PVM
+
+/**
+ * The Darwin hosted USB Proxy Backend.
+ */
+class USBProxyBackendDarwin : public USBProxyBackend
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(USBProxyBackendDarwin)
+
+ int init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings);
+ void uninit();
+
+ virtual int captureDevice(HostUSBDevice *aDevice);
+ virtual int releaseDevice(HostUSBDevice *aDevice);
+
+protected:
+ virtual int wait(RTMSINTERVAL aMillies);
+ virtual int interruptWait (void);
+ virtual PUSBDEVICE getDevices (void);
+ virtual void serviceThreadInit (void);
+ virtual void serviceThreadTerm (void);
+ virtual bool isFakeUpdateRequired();
+
+private:
+ /** Reference to the runloop of the service thread.
+ * This is NULL if the service thread isn't running. */
+ CFRunLoopRef mServiceRunLoopRef;
+ /** The opaque value returned by DarwinSubscribeUSBNotifications. */
+ void *mNotifyOpaque;
+ /** A hack to work around the problem with the usb device enumeration
+ * not including newly attached devices. */
+ bool mWaitABitNextTime;
+};
+# endif /* RT_OS_DARWIN */
+
+
+# if defined(RT_OS_LINUX) || defined(DOXYGEN_RUNNING)
+# include <stdio.h>
+# ifdef VBOX_USB_WITH_SYSFS
+# include <HostHardwareLinux.h>
+# endif
+
+/**
+ * The Linux hosted USB Proxy Backend.
+ */
+class USBProxyBackendLinux: public USBProxyBackend
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(USBProxyBackendLinux)
+
+ int init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings);
+ void uninit();
+
+ virtual int captureDevice(HostUSBDevice *aDevice);
+ virtual int releaseDevice(HostUSBDevice *aDevice);
+
+protected:
+ int initUsbfs(void);
+ int initSysfs(void);
+ void doUsbfsCleanupAsNeeded(void);
+ virtual int wait(RTMSINTERVAL aMillies);
+ virtual int interruptWait(void);
+ virtual PUSBDEVICE getDevices(void);
+ virtual void deviceAdded(ComObjPtr<HostUSBDevice> &aDevice, PUSBDEVICE aUSBDevice);
+ virtual bool isFakeUpdateRequired();
+
+private:
+ int waitUsbfs(RTMSINTERVAL aMillies);
+ int waitSysfs(RTMSINTERVAL aMillies);
+
+private:
+ /** File handle to the '/proc/bus/usb/devices' file. */
+ RTFILE mhFile;
+ /** Pipe used to interrupt wait(), the read end. */
+ RTPIPE mhWakeupPipeR;
+ /** Pipe used to interrupt wait(), the write end. */
+ RTPIPE mhWakeupPipeW;
+ /** The root of usbfs. */
+ Utf8Str mDevicesRoot;
+ /** Whether we're using \<mUsbfsRoot\>/devices or /sys/whatever. */
+ bool mUsingUsbfsDevices;
+ /** Number of 500ms polls left to do. See usbDeterminState for details. */
+ unsigned mUdevPolls;
+# ifdef VBOX_USB_WITH_SYSFS
+ /** Object used for polling for hotplug events from hal. */
+ VBoxMainHotplugWaiter *mpWaiter;
+# endif
+};
+# endif /* RT_OS_LINUX */
+
+
+# if defined(RT_OS_OS2) || defined(DOXYGEN_RUNNING)
+# include <usbcalls.h>
+
+/**
+ * The Linux hosted USB Proxy Backend.
+ */
+class USBProxyBackendOs2 : public USBProxyBackend
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(USBProxyBackendOs2)
+
+ virtual int captureDevice(HostUSBDevice *aDevice);
+ virtual int releaseDevice(HostUSBDevice *aDevice);
+
+protected:
+ virtual int wait(RTMSINTERVAL aMillies);
+ virtual int interruptWait(void);
+ virtual PUSBDEVICE getDevices(void);
+ int addDeviceToChain(PUSBDEVICE pDev, PUSBDEVICE *ppFirst, PUSBDEVICE **pppNext, int rc);
+
+private:
+ /** The notification event semaphore */
+ HEV mhev;
+ /** The notification id. */
+ USBNOTIFY mNotifyId;
+ /** The usbcalls.dll handle. */
+ HMODULE mhmod;
+ /** UsbRegisterChangeNotification */
+ APIRET (APIENTRY *mpfnUsbRegisterChangeNotification)(PUSBNOTIFY, HEV, HEV);
+ /** UsbDeregisterNotification */
+ APIRET (APIENTRY *mpfnUsbDeregisterNotification)(USBNOTIFY);
+ /** UsbQueryNumberDevices */
+ APIRET (APIENTRY *mpfnUsbQueryNumberDevices)(PULONG);
+ /** UsbQueryDeviceReport */
+ APIRET (APIENTRY *mpfnUsbQueryDeviceReport)(ULONG, PULONG, PVOID);
+};
+# endif /* RT_OS_OS2 */
+
+
+# if defined(RT_OS_SOLARIS) || defined(DOXYGEN_RUNNING)
+# include <libdevinfo.h>
+
+/**
+ * The Solaris hosted USB Proxy Backend.
+ */
+class USBProxyBackendSolaris : public USBProxyBackend
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(USBProxyBackendSolaris)
+
+ int init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings);
+ void uninit();
+
+ virtual void *insertFilter (PCUSBFILTER aFilter);
+ virtual void removeFilter (void *aID);
+
+ virtual int captureDevice(HostUSBDevice *aDevice);
+ virtual int releaseDevice(HostUSBDevice *aDevice);
+ virtual void captureDeviceCompleted(HostUSBDevice *aDevice, bool aSuccess);
+ virtual void releaseDeviceCompleted(HostUSBDevice *aDevice, bool aSuccess);
+
+ virtual bool i_isDevReEnumerationRequired();
+
+protected:
+ virtual int wait(RTMSINTERVAL aMillies);
+ virtual int interruptWait(void);
+ virtual PUSBDEVICE getDevices(void);
+
+private:
+ RTSEMEVENT mNotifyEventSem;
+ /** Whether we've successfully initialized the USBLib and should call USBLibTerm in the destructor. */
+ bool mUSBLibInitialized;
+};
+#endif /* RT_OS_SOLARIS */
+
+
+# if defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING)
+/**
+ * The Windows hosted USB Proxy Backend.
+ */
+class USBProxyBackendWindows : public USBProxyBackend
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(USBProxyBackendWindows)
+
+ int init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings);
+ void uninit();
+
+ virtual void *insertFilter (PCUSBFILTER aFilter);
+ virtual void removeFilter (void *aID);
+
+ virtual int captureDevice(HostUSBDevice *aDevice);
+ virtual int releaseDevice(HostUSBDevice *aDevice);
+
+ virtual bool i_isDevReEnumerationRequired();
+
+protected:
+ virtual int wait(RTMSINTERVAL aMillies);
+ virtual int interruptWait(void);
+ virtual PUSBDEVICE getDevices(void);
+
+private:
+
+ HANDLE mhEventInterrupt;
+};
+# endif /* RT_OS_WINDOWS */
+
+# if defined(RT_OS_FREEBSD) || defined(DOXYGEN_RUNNING)
+/**
+ * The FreeBSD hosted USB Proxy Backend.
+ */
+class USBProxyBackendFreeBSD : public USBProxyBackend
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(USBProxyBackendFreeBSD)
+
+ int init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings);
+ void uninit();
+
+ virtual int captureDevice(HostUSBDevice *aDevice);
+ virtual int releaseDevice(HostUSBDevice *aDevice);
+
+protected:
+ int initUsbfs(void);
+ int initSysfs(void);
+ virtual int wait(RTMSINTERVAL aMillies);
+ virtual int interruptWait(void);
+ virtual PUSBDEVICE getDevices(void);
+ int addDeviceToChain(PUSBDEVICE pDev, PUSBDEVICE *ppFirst, PUSBDEVICE **pppNext, int rc);
+ virtual bool isFakeUpdateRequired();
+
+private:
+ RTSEMEVENT mNotifyEventSem;
+};
+# endif /* RT_OS_FREEBSD */
+
+/**
+ * USB/IP Proxy receive state.
+ */
+typedef enum USBIPRECVSTATE
+{
+ /** Invalid state. */
+ kUsbIpRecvState_Invalid = 0,
+ /** There is no request waiting for an answer. */
+ kUsbIpRecvState_None,
+ /** Waiting for the complete reception of UsbIpRetDevList. */
+ kUsbIpRecvState_Hdr,
+ /** Waiting for the complete reception of a UsbIpExportedDevice structure. */
+ kUsbIpRecvState_ExportedDevice,
+ /** Waiting for a complete reception of a UsbIpDeviceInterface structure to skip. */
+ kUsbIpRecvState_DeviceInterface,
+ /** 32bit hack. */
+ kUsbIpRecvState_32Bit_Hack = 0x7fffffff
+} USBIPRECVSTATE;
+/** Pointer to a USB/IP receive state enum. */
+typedef USBIPRECVSTATE *PUSBIPRECVSTATE;
+
+struct UsbIpExportedDevice;
+
+/**
+ * The USB/IP Proxy Backend.
+ */
+class USBProxyBackendUsbIp: public USBProxyBackend
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(USBProxyBackendUsbIp)
+
+ int init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings);
+ void uninit();
+
+ virtual int captureDevice(HostUSBDevice *aDevice);
+ virtual int releaseDevice(HostUSBDevice *aDevice);
+
+protected:
+ virtual int wait(RTMSINTERVAL aMillies);
+ virtual int interruptWait(void);
+ virtual PUSBDEVICE getDevices(void);
+ virtual bool isFakeUpdateRequired();
+
+private:
+ int updateDeviceList(bool *pfDeviceListChanged);
+ bool hasDevListChanged(PUSBDEVICE pDevices);
+ void freeDeviceList(PUSBDEVICE pHead);
+ void resetRecvState();
+ int reconnect();
+ void disconnect();
+ int startListExportedDevicesReq();
+ void advanceState(USBIPRECVSTATE enmRecvState);
+ int receiveData();
+ int processData();
+ int addDeviceToList(UsbIpExportedDevice *pDev);
+
+ struct Data; // opaque data struct, defined in USBProxyBackendUsbIp.cpp
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_USBProxyBackend_h */
+
diff --git a/src/VBox/Main/include/USBProxyService.h b/src/VBox/Main/include/USBProxyService.h
new file mode 100644
index 00000000..ea1149fd
--- /dev/null
+++ b/src/VBox/Main/include/USBProxyService.h
@@ -0,0 +1,147 @@
+/* $Id: USBProxyService.h $ */
+/** @file
+ * VirtualBox USB Proxy Service (base) class.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_USBProxyService_h
+#define MAIN_INCLUDED_USBProxyService_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/usb.h>
+#include <VBox/usbfilter.h>
+
+#include "VirtualBoxBase.h"
+#include "VirtualBoxImpl.h"
+#include "HostUSBDeviceImpl.h"
+#include "USBProxyBackend.h"
+
+class Host;
+
+namespace settings
+{
+ struct USBDeviceSource;
+ typedef std::list<USBDeviceSource> USBDeviceSourcesList;
+}
+
+
+/**
+ * Base class for the USB Proxy service.
+ */
+class USBProxyService
+ : public Lockable
+{
+public:
+ DECLARE_TRANSLATE_METHODS(USBProxyService)
+
+ USBProxyService(Host *aHost);
+ virtual HRESULT init(void);
+ virtual ~USBProxyService();
+
+ /**
+ * Override of the default locking class to be used for validating lock
+ * order with the standard member lock handle.
+ */
+ virtual VBoxLockingClass getLockingClass() const
+ {
+ // the USB proxy service uses the Host object lock, so return the
+ // same locking class as the host
+ return LOCKCLASS_HOSTOBJECT;
+ }
+
+ void uninit(void);
+
+ bool isActive(void);
+ int getLastError(void);
+
+ RWLockHandle *lockHandle() const;
+
+ /** @name Interface for the USBController and the Host object.
+ * @{ */
+ void *insertFilter(PCUSBFILTER aFilter);
+ void removeFilter(void *aId);
+ /** @} */
+
+ /** @name Host Interfaces
+ * @{ */
+ HRESULT getDeviceCollection(std::vector<ComPtr<IHostUSBDevice> > &aUSBDevices);
+ HRESULT addUSBDeviceSource(const com::Utf8Str &aBackend, const com::Utf8Str &aId, const com::Utf8Str &aAddress,
+ const std::vector<com::Utf8Str> &aPropertyNames, const std::vector<com::Utf8Str> &aPropertyValues);
+ HRESULT removeUSBDeviceSource(const com::Utf8Str &aId);
+ /** @} */
+
+ /** @name SessionMachine Interfaces
+ * @{ */
+ HRESULT captureDeviceForVM(SessionMachine *aMachine, IN_GUID aId, const com::Utf8Str &aCaptureFilename);
+ HRESULT detachDeviceFromVM(SessionMachine *aMachine, IN_GUID aId, bool aDone);
+ HRESULT autoCaptureDevicesForVM(SessionMachine *aMachine);
+ HRESULT detachAllDevicesFromVM(SessionMachine *aMachine, bool aDone, bool aAbnormal);
+ /** @} */
+
+ typedef std::list< ComObjPtr<HostUSBDeviceFilter> > USBDeviceFilterList;
+
+ HRESULT i_loadSettings(const settings::USBDeviceSourcesList &llUSBDeviceSources);
+ HRESULT i_saveSettings(settings::USBDeviceSourcesList &llUSBDeviceSources);
+
+ void i_deviceAdded(ComObjPtr<HostUSBDevice> &aDevice, PUSBDEVICE aUSBDevice);
+ void i_deviceRemoved(ComObjPtr<HostUSBDevice> &aDevice);
+ void i_updateDeviceState(ComObjPtr<HostUSBDevice> &aDevice, PUSBDEVICE aUSBDevice, bool fFakeUpdate);
+
+protected:
+ ComObjPtr<HostUSBDevice> findDeviceById(IN_GUID aId);
+
+ static HRESULT setError(HRESULT aResultCode, const char *aText, ...);
+
+ USBProxyBackend *findUsbProxyBackendById(const com::Utf8Str &strId);
+
+ HRESULT createUSBDeviceSource(const com::Utf8Str &aBackend, const com::Utf8Str &aId,
+ const com::Utf8Str &aAddress, const std::vector<com::Utf8Str> &aPropertyNames,
+ const std::vector<com::Utf8Str> &aPropertyValues, bool fLoadingSettings);
+
+private:
+
+ HRESULT runAllFiltersOnDevice(ComObjPtr<HostUSBDevice> &aDevice,
+ SessionMachinesList &llOpenedMachines,
+ SessionMachine *aIgnoreMachine);
+ bool runMachineFilters(SessionMachine *aMachine, ComObjPtr<HostUSBDevice> &aDevice);
+
+ void deviceChanged(ComObjPtr<HostUSBDevice> &aDevice, bool fRunFilters, SessionMachine *aIgnoreMachine);
+
+ /** Pointer to the Host object. */
+ Host *mHost;
+ /** List of smart HostUSBDevice pointers. */
+ typedef std::list<ComObjPtr<HostUSBDevice> > HostUSBDeviceList;
+ /** List of the known USB devices. */
+ HostUSBDeviceList mDevices;
+ /** List of USBProxyBackend pointers. */
+ typedef std::list<ComObjPtr<USBProxyBackend> > USBProxyBackendList;
+ /** List of active USB backends. */
+ USBProxyBackendList mBackends;
+ int mLastError;
+};
+
+#endif /* !MAIN_INCLUDED_USBProxyService_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/UefiVariableStoreImpl.h b/src/VBox/Main/include/UefiVariableStoreImpl.h
new file mode 100644
index 00000000..7a8e9d7d
--- /dev/null
+++ b/src/VBox/Main/include/UefiVariableStoreImpl.h
@@ -0,0 +1,104 @@
+/* $Id: UefiVariableStoreImpl.h $ */
+/** @file
+ * VirtualBox COM UEFI variable store class implementation
+ */
+
+/*
+ * Copyright (C) 2021-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_UefiVariableStoreImpl_h
+#define MAIN_INCLUDED_UefiVariableStoreImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "UefiVariableStoreWrap.h"
+#include <iprt/types.h>
+
+#include <iprt/formats/efi-common.h>
+
+class NvramStore;
+class Machine;
+
+class ATL_NO_VTABLE UefiVariableStore :
+ public UefiVariableStoreWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(UefiVariableStore)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(NvramStore *aParent, Machine *pMachine);
+ void uninit();
+
+ // public methods for internal purposes only
+
+private:
+
+ // Wrapped NVRAM store properties
+ HRESULT getSecureBootEnabled(BOOL *pfEnabled);
+ HRESULT setSecureBootEnabled(BOOL fEnabled);
+
+ // Wrapped NVRAM store members
+ HRESULT addVariable(const com::Utf8Str &aName, const com::Guid &aOwnerUuid, const std::vector<UefiVariableAttributes_T> &aAttributes,
+ const std::vector<BYTE> &aData);
+ HRESULT deleteVariable(const com::Utf8Str &aName, const com::Guid &aOwnerUuid);
+ HRESULT changeVariable(const com::Utf8Str &aName, const std::vector<BYTE> &aData);
+ HRESULT queryVariableByName(const com::Utf8Str &aName, com::Guid &aOwnerUuid, std::vector<UefiVariableAttributes_T> &aAttributes,
+ std::vector<BYTE> &aData);
+ HRESULT queryVariables(std::vector<com::Utf8Str> &aNames, std::vector<com::Guid> &aOwnerUuids);
+ HRESULT enrollOraclePlatformKey(void);
+ HRESULT enrollPlatformKey(const std::vector<BYTE> &aData, const com::Guid &aOwnerUuid);
+ HRESULT addKek(const std::vector<BYTE> &aData, const com::Guid &aOwnerUuid, SignatureType_T enmSignatureType);
+ HRESULT addSignatureToDb(const std::vector<BYTE> &aData, const com::Guid &aOwnerUuid, SignatureType_T enmSignatureType);
+ HRESULT addSignatureToDbx(const std::vector<BYTE> &aData, const com::Guid &aOwnerUuid, SignatureType_T enmSignatureType);
+ HRESULT enrollDefaultMsSignatures(void);
+
+ int i_uefiVarStoreSetVarAttr(const char *pszVar, uint32_t fAttr);
+ int i_uefiVarStoreQueryVarAttr(const char *pszVar, uint32_t *pfAttr);
+ int i_uefiVarStoreQueryVarSz(const char *pszVar, uint64_t *pcbVar);
+ int i_uefiVarStoreQueryVarOwnerUuid(const char *pszVar, PRTUUID pUuid);
+ uint32_t i_uefiVarAttrToMask(const std::vector<UefiVariableAttributes_T> &aAttributes);
+ void i_uefiAttrMaskToVec(uint32_t fAttr, std::vector<UefiVariableAttributes_T> &aAttributes);
+
+ HRESULT i_retainUefiVariableStore(bool fReadonly);
+ HRESULT i_releaseUefiVariableStore(void);
+
+ HRESULT i_uefiVarStoreAddVar(PCEFI_GUID pGuid, const char *pszVar, uint32_t fAttr, PRTVFSFILE phVfsFile);
+ HRESULT i_uefiVarStoreOpenVar(const char *pszVar, PRTVFSFILE phVfsFile);
+ HRESULT i_uefiVarStoreSetVar(PCEFI_GUID pGuid, const char *pszVar, uint32_t fAttr, const void *pvData, size_t cbData);
+ HRESULT i_uefiVarStoreQueryVar(const char *pszVar, void *pvData, size_t cbData);
+ HRESULT i_uefiSigDbAddSig(RTEFISIGDB hEfiSigDb, const void *pvData, size_t cbData, const com::Guid &aOwnerUuid, SignatureType_T enmSignatureType);
+ HRESULT i_uefiVarStoreAddSignatureToDbVec(PCEFI_GUID pGuid, const char *pszDb, const std::vector<BYTE> &aData,
+ const com::Guid &aOwnerUuid, SignatureType_T enmSignatureType);
+ HRESULT i_uefiVarStoreAddSignatureToDb(PCEFI_GUID pGuid, const char *pszDb, const void *pvData, size_t cbData,
+ const com::Guid &aOwnerUuid, SignatureType_T enmSignatureType);
+
+ struct Data; // opaque data struct, defined in UefiVariableStoreImpl.cpp
+ Data *m;
+};
+
+#endif /* !MAIN_INCLUDED_UefiVariableStoreImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/UnattendedImpl.h b/src/VBox/Main/include/UnattendedImpl.h
new file mode 100644
index 00000000..3345e0af
--- /dev/null
+++ b/src/VBox/Main/include/UnattendedImpl.h
@@ -0,0 +1,318 @@
+/* $Id: UnattendedImpl.h $ */
+/** @file
+ * Unattended class header
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_UnattendedImpl_h
+#define MAIN_INCLUDED_UnattendedImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/ostypes.h>
+#include <iprt/time.h>
+#include "UnattendedWrap.h"
+
+/* Forward declarations. */
+class UnattendedInstaller;
+struct UnattendedInstallationDisk;
+struct ControllerSlot;
+
+/**
+ * A data type to store image data which is read from intall.wim file.
+ * Currently relevant only for Windows OSs.
+ */
+struct WIMImage
+{
+ Utf8Str mName;
+ Utf8Str mVersion;
+ Utf8Str mArch;
+ Utf8Str mFlavor;
+ RTCList<RTCString, RTCString *> mLanguages;
+ Utf8Str mDefaultLanguage;
+ uint32_t mImageIndex;
+ VBOXOSTYPE mOSType;
+ WIMImage() : mImageIndex(0), mOSType(VBOXOSTYPE_Unknown) { }
+ const Utf8Str &formatName(Utf8Str &r_strName) const;
+ VBOXOSTYPE mEnmOsType;
+};
+
+/**
+ * Class implementing the IUnattended interface.
+ *
+ * This class is instantiated on the request by IMachine::getUnattended.
+ */
+class ATL_NO_VTABLE Unattended
+ : public UnattendedWrap
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(Unattended)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT initUnattended(VirtualBox *aParent);
+ void uninit();
+
+ // public methods for internal purposes
+ Utf8Str const &i_getIsoPath() const;
+ Utf8Str const &i_getUser() const;
+ Utf8Str const &i_getPassword() const;
+ Utf8Str const &i_getFullUserName() const;
+ Utf8Str const &i_getProductKey() const;
+ Utf8Str const &i_getProxy() const;
+ Utf8Str const &i_getAdditionsIsoPath() const;
+ bool i_getInstallGuestAdditions() const;
+ Utf8Str const &i_getValidationKitIsoPath() const;
+ bool i_getInstallTestExecService() const;
+ Utf8Str const &i_getTimeZone() const;
+ PCRTTIMEZONEINFO i_getTimeZoneInfo() const;
+ Utf8Str const &i_getLocale() const;
+ Utf8Str const &i_getLanguage() const;
+ Utf8Str const &i_getCountry() const;
+ bool i_isMinimalInstallation() const;
+ Utf8Str const &i_getHostname() const;
+ Utf8Str const &i_getAuxiliaryBasePath() const;
+ ULONG i_getImageIndex() const;
+ Utf8Str const &i_getScriptTemplatePath() const;
+ Utf8Str const &i_getPostInstallScriptTemplatePath() const;
+ Utf8Str const &i_getPostInstallCommand() const;
+ /** The directory where the unattended install config and script is
+ * located, from the perspective of the running unattended install. */
+ Utf8Str const &i_getAuxiliaryInstallDir() const;
+ Utf8Str const &i_getExtraInstallKernelParameters() const;
+
+ bool i_isRtcUsingUtc() const;
+ bool i_isGuestOs64Bit() const;
+ bool i_isFirmwareEFI() const;
+ Utf8Str const &i_getDetectedOSVersion();
+ bool i_getAvoidUpdatesOverNetwork() const;
+
+private:
+ ComPtr<VirtualBox> const mParent; /**< Strong reference to the parent object (VirtualBox/IMachine). */
+ ComPtr<Machine> mMachine; /**< Strong reference to the machine object (Machine/IMachine). */
+ Guid mMachineUuid; /**< The machine UUID. */
+ RTNATIVETHREAD mhThreadReconfigureVM; /**< Set when reconfigureVM is running. */
+ Utf8Str mStrGuestOsTypeId; /**< Guest OS type ID (set by prepare). */
+ bool mfRtcUseUtc; /**< Copy of IMachine::RTCUseUTC (locking reasons). */
+ bool mfGuestOs64Bit; /**< 64-bit (true) or 32-bit guest OS (set by prepare). */
+ FirmwareType_T menmFirmwareType; /**< Firmware type BIOS/EFI (set by prepare). */
+ UnattendedInstaller *mpInstaller; /**< The installer instance (set by prepare, deleted by done). */
+
+ /** @name Values of the IUnattended attributes.
+ * @{ */
+ Utf8Str mStrUser;
+ Utf8Str mStrPassword;
+ Utf8Str mStrFullUserName;
+ Utf8Str mStrProductKey;
+ Utf8Str mStrIsoPath;
+ Utf8Str mStrAdditionsIsoPath;
+ bool mfInstallGuestAdditions;
+ bool mfInstallTestExecService;
+ Utf8Str mStrValidationKitIsoPath;
+ Utf8Str mStrTimeZone;
+ PCRTTIMEZONEINFO mpTimeZoneInfo;
+ Utf8Str mStrLocale;
+ Utf8Str mStrLanguage; /**< (only relevant for windows at the moment) */
+ Utf8Str mStrCountry;
+ RTCList<RTCString, RTCString *> mPackageSelectionAdjustments;
+ Utf8Str mStrHostname;
+ Utf8Str mStrAuxiliaryBasePath;
+ bool mfIsDefaultAuxiliaryBasePath;
+ ULONG midxImage;
+ Utf8Str mStrScriptTemplatePath;
+ Utf8Str mStrPostInstallScriptTemplatePath;
+ Utf8Str mStrPostInstallCommand;
+ Utf8Str mStrExtraInstallKernelParameters;
+ Utf8Str mStrProxy;
+
+ bool mfDoneDetectIsoOS; /**< Set by detectIsoOS(), cleared by setIsoPath(). */
+ Utf8Str mStrDetectedOSTypeId;
+ Utf8Str mStrDetectedOSVersion;
+ Utf8Str mStrDetectedOSFlavor;
+ VBOXOSTYPE mEnmOsType;
+ RTCList<RTCString, RTCString *> mDetectedOSLanguages; /**< (only relevant for windows at the moment) */
+ Utf8Str mStrDetectedOSHints;
+ RTCList<WIMImage> mDetectedImages;
+ bool mfAvoidUpdatesOverNetwork;
+ /** @} */
+
+ // wrapped IUnattended functions:
+
+ /**
+ * Checks what mStrIsoPath points to and sets the detectedOS* properties.
+ */
+ HRESULT detectIsoOS();
+
+ /**
+ * Prepare any data, environment, etc.
+ */
+ HRESULT prepare();
+
+ /**
+ * Prepare installation ISO/floppy.
+ */
+ HRESULT constructMedia();
+
+ /**
+ * Prepare a VM to run an unattended installation
+ */
+ HRESULT reconfigureVM();
+
+ /**
+ * Done with all media construction and VM configuration and stuff.
+ */
+ HRESULT done();
+
+ // wrapped IUnattended attributes:
+ HRESULT getIsoPath(com::Utf8Str &isoPath);
+ HRESULT setIsoPath(const com::Utf8Str &isoPath);
+ HRESULT getUser(com::Utf8Str &user);
+ HRESULT setUser(const com::Utf8Str &user);
+ HRESULT getPassword(com::Utf8Str &password);
+ HRESULT setPassword(const com::Utf8Str &password);
+ HRESULT getFullUserName(com::Utf8Str &user);
+ HRESULT setFullUserName(const com::Utf8Str &user);
+ HRESULT getProductKey(com::Utf8Str &productKey);
+ HRESULT setProductKey(const com::Utf8Str &productKey);
+ HRESULT getAdditionsIsoPath(com::Utf8Str &additionsIsoPath);
+ HRESULT setAdditionsIsoPath(const com::Utf8Str &additionsIsoPath);
+ HRESULT getInstallGuestAdditions(BOOL *installGuestAdditions);
+ HRESULT setInstallGuestAdditions(BOOL installGuestAdditions);
+ HRESULT getValidationKitIsoPath(com::Utf8Str &aValidationKitIsoPath);
+ HRESULT setValidationKitIsoPath(const com::Utf8Str &aValidationKitIsoPath);
+ HRESULT getInstallTestExecService(BOOL *aInstallTestExecService);
+ HRESULT setInstallTestExecService(BOOL aInstallTestExecService);
+ HRESULT getTimeZone(com::Utf8Str &aTimezone);
+ HRESULT setTimeZone(const com::Utf8Str &aTimezone);
+ HRESULT getLocale(com::Utf8Str &aLocale);
+ HRESULT setLocale(const com::Utf8Str &aLocale);
+ HRESULT getLanguage(com::Utf8Str &aLanguage);
+ HRESULT setLanguage(const com::Utf8Str &aLanguage);
+ HRESULT getCountry(com::Utf8Str &aCountry);
+ HRESULT setCountry(const com::Utf8Str &aCountry);
+ HRESULT getProxy(com::Utf8Str &aProxy);
+ HRESULT setProxy(const com::Utf8Str &aProxy);
+ HRESULT getPackageSelectionAdjustments(com::Utf8Str &aPackageSelectionAdjustments);
+ HRESULT setPackageSelectionAdjustments(const com::Utf8Str &aPackageSelectionAdjustments);
+ HRESULT getHostname(com::Utf8Str &aHostname);
+ HRESULT setHostname(const com::Utf8Str &aHostname);
+ HRESULT getAuxiliaryBasePath(com::Utf8Str &aAuxiliaryBasePath);
+ HRESULT setAuxiliaryBasePath(const com::Utf8Str &aAuxiliaryBasePath);
+ HRESULT getImageIndex(ULONG *index);
+ HRESULT setImageIndex(ULONG index);
+ HRESULT getMachine(ComPtr<IMachine> &aMachine);
+ HRESULT setMachine(const ComPtr<IMachine> &aMachine);
+ HRESULT getScriptTemplatePath(com::Utf8Str &aScriptTemplatePath);
+ HRESULT setScriptTemplatePath(const com::Utf8Str &aScriptTemplatePath);
+ HRESULT getPostInstallScriptTemplatePath(com::Utf8Str &aPostInstallScriptTemplatePath);
+ HRESULT setPostInstallScriptTemplatePath(const com::Utf8Str &aPostInstallScriptTemplatePath);
+ HRESULT getPostInstallCommand(com::Utf8Str &aPostInstallCommand);
+ HRESULT setPostInstallCommand(const com::Utf8Str &aPostInstallCommand);
+ HRESULT getExtraInstallKernelParameters(com::Utf8Str &aExtraInstallKernelParameters);
+ HRESULT setExtraInstallKernelParameters(const com::Utf8Str &aExtraInstallKernelParameters);
+ HRESULT getDetectedOSTypeId(com::Utf8Str &aDetectedOSTypeId);
+ HRESULT getDetectedOSVersion(com::Utf8Str &aDetectedOSVersion);
+ HRESULT getDetectedOSLanguages(com::Utf8Str &aDetectedOSLanguages);
+ HRESULT getDetectedOSFlavor(com::Utf8Str &aDetectedOSFlavor);
+ HRESULT getDetectedOSHints(com::Utf8Str &aDetectedOSHints);
+ HRESULT getDetectedImageNames(std::vector<com::Utf8Str> &aDetectedImageNames);
+ HRESULT getDetectedImageIndices(std::vector<ULONG> &aDetectedImageIndices);
+ HRESULT getIsUnattendedInstallSupported(BOOL *aIsUnattendedInstallSupported);
+ HRESULT getAvoidUpdatesOverNetwork(BOOL *aAvoidUpdatesOverNetwork);
+ HRESULT setAvoidUpdatesOverNetwork(BOOL aAvoidUpdatesOverNetwork);
+ //internal functions
+
+ /**
+ * Worker for detectIsoOs().
+ *
+ * @returns COM status code.
+ * @retval S_OK if detected.
+ * @retval S_FALSE if not detected.
+ *
+ * @param hVfsIso The ISO file system handle.
+ */
+ HRESULT i_innerDetectIsoOS(RTVFS hVfsIso);
+ typedef union DETECTBUFFER
+ {
+ char sz[4096];
+ char ach[4096];
+ uint8_t ab[4096];
+ uint32_t au32[1024];
+ } DETECTBUFFER;
+ HRESULT i_innerDetectIsoOSWindows(RTVFS hVfsIso, DETECTBUFFER *puBuf);
+ HRESULT i_innerDetectIsoOSLinux(RTVFS hVfsIso, DETECTBUFFER *puBuf);
+ HRESULT i_innerDetectIsoOSLinuxFedora(RTVFS hVfsIso, DETECTBUFFER *puBuf, char *pszVolId);
+ HRESULT i_innerDetectIsoOSOs2(RTVFS hVfsIso, DETECTBUFFER *puBuf);
+ HRESULT i_innerDetectIsoOSFreeBsd(RTVFS hVfsIso, DETECTBUFFER *puBuf);
+
+ /**
+ * Worker for reconfigureVM().
+ * The caller makes sure to close the session whatever happens.
+ */
+ HRESULT i_innerReconfigureVM(AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus,
+ ComPtr<IMachine> const &rPtrSessionMachine);
+ HRESULT i_reconfigureFloppy(com::SafeIfaceArray<IStorageController> &rControllers,
+ std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
+ ComPtr<IMachine> const &rPtrSessionMachine,
+ AutoMultiWriteLock2 &rAutoLock);
+ HRESULT i_reconfigureIsos(com::SafeIfaceArray<IStorageController> &rControllers,
+ std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
+ ComPtr<IMachine> const &rPtrSessionMachine,
+ AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus);
+
+ /**
+ * Adds all free slots on the controller to @a rOutput.
+ */
+ HRESULT i_findOrCreateNeededFreeSlots(const Utf8Str &rStrControllerName, StorageBus_T enmStorageBus,
+ ComPtr<IMachine> const &rPtrSessionMachine, uint32_t cSlotsNeeded,
+ std::list<ControllerSlot> &rDvdSlots);
+
+ /**
+ * Attach to VM a disk
+ */
+ HRESULT i_attachImage(UnattendedInstallationDisk const *pImage, ComPtr<IMachine> const &rPtrSessionMachine,
+ AutoMultiWriteLock2 &rLock);
+
+ /*
+ * Wrapper functions
+ */
+
+ /**
+ * Check whether guest is 64bit platform or not
+ */
+ bool i_isGuestOSArchX64(Utf8Str const &rStrGuestOsTypeId);
+
+ /**
+ * Updates the detected attributes when the image index or image list changes.
+ *
+ * @returns true if we've got all necessary stuff for a successful detection.
+ */
+ bool i_updateDetectedAttributeForImage(WIMImage const &rImage);
+
+};
+
+#endif /* !MAIN_INCLUDED_UnattendedImpl_h */
diff --git a/src/VBox/Main/include/UnattendedInstaller.h b/src/VBox/Main/include/UnattendedInstaller.h
new file mode 100644
index 00000000..2c4689da
--- /dev/null
+++ b/src/VBox/Main/include/UnattendedInstaller.h
@@ -0,0 +1,891 @@
+/* $Id: UnattendedInstaller.h $ */
+/** @file
+ * UnattendedInstaller class header
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_UnattendedInstaller_h
+#define MAIN_INCLUDED_UnattendedInstaller_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "UnattendedScript.h"
+
+/* Forward declarations */
+class Unattended;
+class UnattendedInstaller;
+class BaseTextScript;
+
+
+/**
+ * The abstract UnattendedInstaller class declaration
+ *
+ * The class is intended to service a new VM that this VM will be able to
+ * execute an unattended installation
+ */
+class UnattendedInstaller : public RTCNonCopyable
+{
+/*data*/
+protected:
+ /** Main unattended installation script. */
+ UnattendedScriptTemplate mMainScript;
+ /** Full path to the main template file (set by initInstaller). */
+ Utf8Str mStrMainScriptTemplate;
+
+ /** Post installation (shell) script. */
+ UnattendedScriptTemplate mPostScript;
+ /** Full path to the post template file (set by initInstaller). */
+ Utf8Str mStrPostScriptTemplate;
+
+ /** Pointer to the parent object.
+ * We use this for setting errors and querying attributes. */
+ Unattended *mpParent;
+ /** The path of the extra ISO image we create (set by initInstaller).
+ * This is only valid when isAdditionsIsoNeeded() returns true. */
+ Utf8Str mStrAuxiliaryIsoFilePath;
+ /** The path of the extra floppy image we create (set by initInstaller)
+ * This is only valid when isAdditionsFloppyNeeded() returns true. */
+ Utf8Str mStrAuxiliaryFloppyFilePath;
+ /** The boot device. */
+ DeviceType_T const meBootDevice;
+ /** Default extra install kernel parameters (set by constructor).
+ * This can be overridden by the extraInstallKernelParameters attribute of
+ * IUnattended. */
+ Utf8Str mStrDefaultExtraInstallKernelParameters;
+ /** The directory of the post install script in the unattended install
+ * environment, i.e. when it gets started by the unattended installer
+ * of the respective guest OS. */
+ Utf8Str mStrAuxiliaryInstallDir;
+
+private:
+ UnattendedInstaller(); /* no default constructors */
+
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedInstaller)
+
+ /**
+ * Regular constructor.
+ *
+ * @param pParent The parent object. Used for setting
+ * errors and querying attributes.
+ * @param pszMainScriptTemplateName The name of the template file (no path)
+ * for the main unattended installer
+ * script.
+ * @param pszPostScriptTemplateName The name of the template file (no path)
+ * for the post installation script.
+ * @param pszMainScriptFilename The main unattended installer script
+ * filename (on aux media).
+ * @param pszPostScriptFilename The post installation script filename
+ * (on aux media).
+ * @param enmBootDevice The boot device type.
+ */
+ UnattendedInstaller(Unattended *pParent,
+ const char *pszMainScriptTemplateName, const char *pszPostScriptTemplateName,
+ const char *pszMainScriptFilename, const char *pszPostScriptFilename,
+ DeviceType_T enmBootDevice = DeviceType_DVD);
+ virtual ~UnattendedInstaller();
+
+ /**
+ * Instantiates the appropriate child class.
+ *
+ * @returns Pointer to the new instance, NULL if no appropriate installer.
+ * @param enmDetectedOSType The detected guest OS type value.
+ * @param strDetectedOSType The detected guest OS type string
+ * @param strDetectedOSVersion The detected guest OS version.
+ * @param strDetectedOSFlavor The detected guest OS flavor.
+ * @param strDetectedOSHints Hints about the detected guest OS.
+ * @param pParent The parent object. Used for setting errors
+ * and querying attributes.
+ * @throws std::bad_alloc
+ */
+ static UnattendedInstaller *createInstance(VBOXOSTYPE enmDetectedOSType, const Utf8Str &strDetectedOSType,
+ const Utf8Str &strDetectedOSVersion, const Utf8Str &strDetectedOSFlavor,
+ const Utf8Str &strDetectedOSHints, Unattended *pParent);
+
+ /**
+ * Initialize the installer.
+ *
+ * @note This is called immediately after instantiation and the caller will
+ * always destroy the unattended installer instance on failure, so it
+ * is not necessary to keep track of whether this succeeded or not.
+ */
+ virtual HRESULT initInstaller();
+
+#if 0 /* These are now in the AUX VISO. */
+ /**
+ * Whether the VBox Guest Additions ISO is needed or not.
+ *
+ * The default implementation always returns false when a VISO is used, see
+ * UnattendedInstaller::addFilesToAuxVisoVectors.
+ */
+ virtual bool isAdditionsIsoNeeded() const;
+
+ /**
+ * Whether the VBox validation kit ISO is needed or not.
+ *
+ * The default implementation always returns false when a VISO is used, see
+ * UnattendedInstaller::addFilesToAuxVisoVectors.
+ */
+ virtual bool isValidationKitIsoNeeded() const;
+#endif
+
+ /**
+ * Indicates whether an original installation ISO is needed or not.
+ */
+ virtual bool isOriginalIsoNeeded() const { return true; }
+
+ /**
+ * Indicates whether a floppy image is needed or not.
+ */
+ virtual bool isAuxiliaryFloppyNeeded() const { return false; }
+
+ /**
+ * Indicates whether an additional or replacement ISO image is needed or not.
+ */
+ virtual bool isAuxiliaryIsoNeeded() const;
+
+ /**
+ * Indicates whether we should boot from the auxiliary ISO image.
+ *
+ * Will boot from installation ISO if false.
+ */
+ virtual bool bootFromAuxiliaryIso() const { return isAuxiliaryIsoNeeded(); }
+
+ /**
+ * Indicates whether a the auxiliary ISO is a .viso-file rather than an
+ * .iso-file.
+ *
+ * Different worker methods are used depending on the return value. A
+ * .viso-file is generally only used when the installation media needs to
+ * be remastered with small changes and additions.
+ */
+ virtual bool isAuxiliaryIsoIsVISO() const { return true; }
+
+ /*
+ * Getters
+ */
+ DeviceType_T getBootableDeviceType() const { return meBootDevice; }
+ const Utf8Str &getTemplateFilePath() const { return mStrMainScriptTemplate; }
+ const Utf8Str &getPostTemplateFilePath() const { return mStrPostScriptTemplate; }
+ const Utf8Str &getAuxiliaryIsoFilePath() const { return mStrAuxiliaryIsoFilePath; }
+ const Utf8Str &getAuxiliaryFloppyFilePath() const { return mStrAuxiliaryFloppyFilePath; }
+ const Utf8Str &getDefaultExtraInstallKernelParameters() const { return mStrDefaultExtraInstallKernelParameters; }
+ const Utf8Str &getAuxiliaryInstallDir() const { return mStrAuxiliaryInstallDir; }
+
+ /*
+ * Setters
+ */
+ void setTemplatePath(const Utf8Str& data); /**< @todo r=bird: This is confusing as heck. Dir for a while, then it's a file. Not a comment about it. Brilliant. */
+
+ /**
+ * Prepares the unattended scripts, does all but write them to the installation
+ * media.
+ */
+ HRESULT prepareUnattendedScripts();
+
+ /**
+ * Prepares the media - floppy image, ISO image.
+ *
+ * This method calls prepareAuxFloppyImage() and prepareAuxIsoImage(), child
+ * classes may override these methods or methods they call.
+ *
+ * @returns COM status code.
+ * @param fOverwrite Whether to overwrite media files or fail if they
+ * already exist.
+ */
+ HRESULT prepareMedia(bool fOverwrite = true);
+
+protected:
+ /**
+ * Prepares (creates) the auxiliary floppy image.
+ *
+ * This is called by the base class prepareMedia() when
+ * isAuxiliaryFloppyNeeded() is true. The base class implementation puts the
+ * edited unattended script onto it.
+ */
+ HRESULT prepareAuxFloppyImage(bool fOverwrite);
+
+ /**
+ * Creates and formats (FAT12) a floppy image.
+ *
+ * This can be overridden to do more preparation work or/and create a different
+ * sized floppy.
+ *
+ * @returns COM status code.
+ * @param pszFilename The path to the image file.
+ * @param fOverwrite Whether to overwrite the file.
+ * @param phVfsFile Where to return a read-writable handle to the newly
+ * created image.
+ */
+ virtual HRESULT newAuxFloppyImage(const char *pszFilename, bool fOverwrite, PRTVFSFILE phVfsFile);
+
+ /**
+ * Copies files to the auxiliary floppy image.
+ *
+ * The base class implementation copies the main and post scripts to the root of
+ * the floppy using the default script names. Child classes may override this
+ * to add additional or different files.
+ *
+ * @returns COM status code.
+ * @param hVfs The floppy image VFS handle.
+ */
+ virtual HRESULT copyFilesToAuxFloppyImage(RTVFS hVfs);
+
+ /**
+ * Adds the given script to the root of the floppy image under the default
+ * script filename.
+ *
+ * @returns COM status code.
+ * @param pEditor The script to add.
+ * @param hVfs The VFS to add it to.
+ */
+ HRESULT addScriptToFloppyImage(BaseTextScript *pEditor, RTVFS hVfs);
+
+ /**
+ * Copy an arbritrary file onto the floopy image.
+ *
+ * @returns COM status code.
+ * @param hVfs The VFS to add it to.
+ * @param pszSrc The source filename.
+ * @param pszDst The destination filename (on @a hVfs).
+ */
+ HRESULT addFileToFloppyImage(RTVFS hVfs, const char *pszSrc, const char *pszDst);
+
+ /**
+ * Prepares (creates) the auxiliary ISO image.
+ *
+ * This is called by the base class prepareMedia() when isAuxiliaryIsoNeeded()
+ * is true. The base class implementation puts the edited unattended script
+ * onto it.
+ */
+ virtual HRESULT prepareAuxIsoImage(bool fOverwrite);
+
+ /**
+ * Opens the installation ISO image.
+ *
+ * @returns COM status code.
+ * @param phVfsIso Where to return the VFS handle for the ISO.
+ * @param fFlags RTFSISO9660_F_XXX flags to pass to the
+ * RTFsIso9660VolOpen API.
+ */
+ virtual HRESULT openInstallIsoImage(PRTVFS phVfsIso, uint32_t fFlags = 0);
+
+ /**
+ * Creates and configures the ISO maker instance.
+ *
+ * This can be overridden to set configure options.
+ *
+ * @returns COM status code.
+ * @param phIsoMaker Where to return the ISO maker.
+ */
+ virtual HRESULT newAuxIsoImageMaker(PRTFSISOMAKER phIsoMaker);
+
+ /**
+ * Adds files to the auxiliary ISO image maker.
+ *
+ * The base class implementation copies just the mMainScript and mPostScript
+ * files to root directory using the default filenames.
+ *
+ * @returns COM status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param hVfsOrgIso The VFS handle to the original ISO in case files
+ * needs to be added from it.
+ */
+ virtual HRESULT addFilesToAuxIsoImageMaker(RTFSISOMAKER hIsoMaker, RTVFS hVfsOrgIso);
+
+ /**
+ * Adds the given script to the ISO maker.
+ *
+ * @returns COM status code.
+ * @param pEditor The script to add.
+ * @param hIsoMaker The ISO maker to add it to.
+ * @param pszDstFilename The file name (w/ path) to add it under. If NULL,
+ * the default script filename is used to add it to the
+ * root.
+ */
+ HRESULT addScriptToIsoMaker(BaseTextScript *pEditor, RTFSISOMAKER hIsoMaker, const char *pszDstFilename = NULL);
+
+ /**
+ * Writes the ISO image to disk.
+ *
+ * @returns COM status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param pszFilename The filename.
+ * @param fOverwrite Whether to overwrite the destination file or not.
+ */
+ HRESULT finalizeAuxIsoImage(RTFSISOMAKER hIsoMaker, const char *pszFilename, bool fOverwrite);
+
+ /**
+ * Adds files to the .viso-file vectors.
+ *
+ * The base class implementation adds the script from mAlg, additions ISO
+ * content to '/vboxadditions', and validation kit ISO to '/vboxvalidationkit'.
+ *
+ * @returns COM status code.
+ * @param rVecArgs The ISO maker argument list that will be turned into
+ * a .viso-file.
+ * @param rVecFiles The list of files we've created. This is for
+ * cleaning up at the end.
+ * @param hVfsOrgIso The VFS handle to the original ISO in case files
+ * needs to be added from it.
+ * @param fOverwrite Whether to overwrite files or not.
+ */
+ virtual HRESULT addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
+ RTVFS hVfsOrgIso, bool fOverwrite);
+
+ /**
+ * Saves the given script to disk and adds it to the .viso-file vectors.
+ *
+ * @returns COM status code.
+ * @param pEditor The script to add.
+ * @param rVecArgs The ISO maker argument list that will be turned into
+ * a .viso-file.
+ * @param rVecFiles The list of files we've created. This is for
+ * cleaning up at the end.
+ * @param fOverwrite Whether to overwrite files or not.
+ */
+ HRESULT addScriptToVisoVectors(BaseTextScript *pEditor, RTCList<RTCString> &rVecArgs,
+ RTCList<RTCString> &rVecFiles, bool fOverwrite);
+
+ /**
+ * Writes out the .viso-file to disk.
+ *
+ * @returns COM status code.
+ * @param rVecArgs The ISO maker argument list to write out.
+ * @param pszFilename The filename.
+ * @param fOverwrite Whether to overwrite the destination file or not.
+ */
+ HRESULT finalizeAuxVisoFile(RTCList<RTCString> const &rVecArgs, const char *pszFilename, bool fOverwrite);
+
+ /**
+ * Loads @a pszFilename from @a hVfsOrgIso into @a pEditor and parses it.
+ *
+ * @returns COM status code.
+ * @param hVfsOrgIso The handle to the original installation ISO.
+ * @param pszFilename The filename to open and load from the ISO.
+ * @param pEditor The editor instance to load the file into and
+ * do the parseing with.
+ */
+ HRESULT loadAndParseFileFromIso(RTVFS hVfsOrgIso, const char *pszFilename, AbstractScript *pEditor);
+};
+
+
+/**
+ * Windows installer, for versions up to xp 64 / w2k3.
+ */
+class UnattendedWindowsSifInstaller : public UnattendedInstaller
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedWindowsSifInstaller)
+
+ UnattendedWindowsSifInstaller(Unattended *pParent)
+ : UnattendedInstaller(pParent,
+ "win_nt5_unattended.sif", "win_postinstall.cmd",
+ "WINNT.SIF", "VBOXPOST.CMD")
+ {
+ Assert(isOriginalIsoNeeded()); Assert(isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO()); Assert(!bootFromAuxiliaryIso());
+ mStrAuxiliaryInstallDir = "A:\\";
+ }
+ ~UnattendedWindowsSifInstaller() {}
+
+ bool isAuxiliaryFloppyNeeded() const { return true; }
+ bool bootFromAuxiliaryIso() const { return false; }
+
+};
+
+/**
+ * Windows installer, for versions starting with Vista.
+ */
+class UnattendedWindowsXmlInstaller : public UnattendedInstaller
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedWindowsXmlInstaller)
+
+ UnattendedWindowsXmlInstaller(Unattended *pParent)
+ : UnattendedInstaller(pParent,
+ "win_nt6_unattended.xml", "win_postinstall.cmd",
+ "autounattend.xml", "VBOXPOST.CMD")
+ {
+ Assert(isOriginalIsoNeeded()); Assert(isAuxiliaryFloppyNeeded() || isAuxiliaryIsoNeeded()); Assert(isAuxiliaryIsoIsVISO()); Assert(!bootFromAuxiliaryIso());
+ if (isAuxiliaryFloppyNeeded())
+ mStrAuxiliaryInstallDir = "A:\\";
+ else if (bootFromAuxiliaryIso())
+ mStrAuxiliaryInstallDir = "D:\\";
+ else
+ mStrAuxiliaryInstallDir = "E:\\";
+ }
+ ~UnattendedWindowsXmlInstaller() {}
+
+ bool isAuxiliaryFloppyNeeded() const { return !mpParent->i_isFirmwareEFI(); }
+ bool isAuxiliaryIsoNeeded() const { return UnattendedInstaller::isAuxiliaryIsoNeeded() || mpParent->i_isFirmwareEFI(); }
+ bool isAuxiliaryIsoIsVISO() const { return true; }
+ bool bootFromAuxiliaryIso() const { return false; }
+};
+
+
+/**
+ * OS/2 installer.
+ */
+class UnattendedOs2Installer : public UnattendedInstaller
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedOs2Installer)
+
+ UnattendedOs2Installer(Unattended *pParent, Utf8Str const &rStrHints);
+ ~UnattendedOs2Installer() {}
+
+ /* Remaster original ISO with auxiliary floppy used for el torito floppy emulation: */
+ bool isOriginalIsoNeeded() const RT_OVERRIDE { return false; }
+ bool isAuxiliaryFloppyNeeded() const RT_OVERRIDE { return true; }
+ bool isAuxiliaryIsoNeeded() const RT_OVERRIDE { return true; }
+
+protected:
+ HRESULT replaceAuxFloppyImageBootSector(RTVFSFILE hVfsFile) RT_NOEXCEPT;
+ HRESULT newAuxFloppyImage(const char *pszFilename, bool fOverwrite, PRTVFSFILE phVfsFile) RT_OVERRIDE;
+ HRESULT copyFilesToAuxFloppyImage(RTVFS hVfs) RT_OVERRIDE;
+ HRESULT addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
+ RTVFS hVfsOrgIso, bool fOverwrite) RT_OVERRIDE;
+
+ HRESULT splitResponseFile() RT_NOEXCEPT;
+
+ /**
+ * Splits up the given file into sub-files and writes them out with the auxilary
+ * path base as prefix.
+ *
+ * The source file contains @@VBOX_SPLITTER_START[filename]@@ and
+ * @@VBOX_SPLITTER_END[filename]@@ markup that is used to split it up. Any
+ * text between END and START tags are ignored and can be used for comments.
+ *
+ * @returns COM status code (error info set).
+ * @param pszFileToSplit The name of the file to split.
+ * @param rVecSplitFiles Vector where names of the sub-files are appended
+ * (without any path or prefix).
+ */
+ HRESULT splitFile(const char *pszFileToSplit, RTCList<RTCString> &rVecSplitFiles) RT_NOEXCEPT;
+
+ /**
+ * Splits up the given editor output into sub-files and writes them out with the
+ * auxilary path base as prefix.
+ *
+ * The source file contains @@VBOX_SPLITTER_START[filename]@@ and
+ * @@VBOX_SPLITTER_END[filename]@@ markup that is used to split it up. Any
+ * text between END and START tags are ignored and can be used for comments.
+ *
+ * @returns COM status code (error info set).
+ * @param pEditor The editor which output should be split.
+ * @param rVecSplitFiles Vector where names of the sub-files are appended
+ * (without any path or prefix).
+ */
+ HRESULT splitFile(BaseTextScript *pEditor, RTCList<RTCString> &rVecSplitFiles) RT_NOEXCEPT;
+
+ HRESULT splitFileInner(const char *pszFileToSplit, RTCList<RTCString> &rVecSplitFiles,
+ const char *pszSrc, size_t cbLeft) RT_NOEXCEPT;
+
+ static int patchTestCfg(uint8_t *pbFile, size_t cbFile, const char *pszFilename, UnattendedOs2Installer *pThis);
+ static int patchOs2Ldr(uint8_t *pbFile, size_t cbFile, const char *pszFilename, UnattendedOs2Installer *pThis);
+
+ /** The OS2SE20.SRC path ("\\OS2IMAGES"). */
+ Utf8Str mStrOs2Images;
+ /** Files split out from os2_response_files.rsp (bare filenames, no paths). */
+ RTCList<RTCString> mVecSplitFiles;
+};
+
+
+
+/**
+ * Base class for the unattended linux installers.
+ */
+class UnattendedLinuxInstaller : public UnattendedInstaller
+{
+protected:
+ /** Array of linux parameter patterns that should be removed by editIsoLinuxCfg.
+ * The patterns are proceed by RTStrSimplePatternNMatch. */
+ RTCList<RTCString, RTCString *> mArrStrRemoveInstallKernelParameters;
+
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedLinuxInstaller)
+
+ UnattendedLinuxInstaller(Unattended *pParent,
+ const char *pszMainScriptTemplateName, const char *pszPostScriptTemplateName,
+ const char *pszMainScriptFilename, const char *pszPostScriptFilename = "vboxpostinstall.sh")
+ : UnattendedInstaller(pParent,
+ pszMainScriptTemplateName, pszPostScriptTemplateName,
+ pszMainScriptFilename, pszPostScriptFilename) {}
+ ~UnattendedLinuxInstaller() {}
+
+ bool isAuxiliaryIsoNeeded() const { return true; }
+
+protected:
+ /**
+ * Performs basic edits on a isolinux.cfg file.
+ *
+ * @returns COM status code
+ * @param pEditor Editor with the isolinux.cfg file loaded and parsed.
+ */
+ virtual HRESULT editIsoLinuxCfg(GeneralTextScript *pEditor);
+ /**
+ * Performs basic common edits on a isolinux.cfg and menu configuration file(s) (txt.cfg or menu.cfg etc).
+ *
+ * @returns COM status code
+ * @param pEditor Editor with the isolinux.cfg file loaded and parsed.
+ */
+ virtual HRESULT editIsoLinuxCommon(GeneralTextScript *pEditor);
+};
+
+
+/**
+ * Debian installer.
+ *
+ * This will remaster the orignal ISO and therefore be producing a .viso-file.
+ */
+class UnattendedDebianInstaller : public UnattendedLinuxInstaller
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedDebianInstaller)
+
+ UnattendedDebianInstaller(Unattended *pParent,
+ const char *pszMainScriptTemplateName = "debian_preseed.cfg",
+ const char *pszPostScriptTemplateName = "debian_postinstall.sh",
+ const char *pszMainScriptFilename = "preseed.cfg")
+ : UnattendedLinuxInstaller(pParent, pszMainScriptTemplateName, pszPostScriptTemplateName, pszMainScriptFilename)
+ {
+ Assert(!isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded());
+ Assert(!isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO());
+ mStrDefaultExtraInstallKernelParameters.setNull();
+ mStrDefaultExtraInstallKernelParameters += " auto=true";
+ mStrDefaultExtraInstallKernelParameters.append(" preseed/file=/cdrom/").append(pszMainScriptFilename);
+ mStrDefaultExtraInstallKernelParameters += " priority=critical";
+ mStrDefaultExtraInstallKernelParameters += " quiet";
+ mStrDefaultExtraInstallKernelParameters += " splash";
+ mStrDefaultExtraInstallKernelParameters += " noprompt"; /* no questions about things like CD/DVD ejections */
+ mStrDefaultExtraInstallKernelParameters += " noshell"; /* No shells on VT1-3 (debian, not ubuntu). */
+ mStrDefaultExtraInstallKernelParameters += " automatic-ubiquity"; // ubiquity
+ // the following can probably go into the preseed.cfg:
+ mStrDefaultExtraInstallKernelParameters.append(" debian-installer/locale=").append(pParent->i_getLocale());
+ mStrDefaultExtraInstallKernelParameters += " keyboard-configuration/layoutcode=us";
+ mStrDefaultExtraInstallKernelParameters += " languagechooser/language-name=English"; /** @todo fixme */
+ mStrDefaultExtraInstallKernelParameters.append(" localechooser/supported-locales=").append(pParent->i_getLocale()).append(".UTF-8");
+ mStrDefaultExtraInstallKernelParameters.append(" countrychooser/shortlist=").append(pParent->i_getCountry()); // ubiquity?
+ mStrDefaultExtraInstallKernelParameters += " --";
+ }
+ ~UnattendedDebianInstaller() {}
+
+ bool isOriginalIsoNeeded() const { return false; }
+
+protected:
+ HRESULT addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
+ RTVFS hVfsOrgIso, bool fOverwrite);
+ /**
+ * Performs basic edits on menu configuration file(s) of isolinux (txt.cfg or menu.cfg etc).
+ *
+ * @returns COM status code
+ * @param pEditor Editor with the menu config. file loaded and parsed.
+ */
+ HRESULT editDebianMenuCfg(GeneralTextScript *pEditor);
+ /**
+ * Performs basic edits on grub configuration file (grub.cfg).
+ *
+ * @returns COM status code
+ * @param pEditor Editor with the grub.cfg file loaded and parsed.
+ */
+ HRESULT editDebianGrubCfg(GeneralTextScript *pEditor);
+
+ /**
+ * Performs basic edits on a isolinux.cfg file.
+ *
+ * @returns COM status code
+ * @param pEditor Editor with the isolinux.cfg file loaded and parsed.
+ * @param pszMenuConfigFileName Name of the menu config file to include in isolinux.txt. On Debians (at least)
+ it includes the kernel command line with our preseed file and command line argument.
+ */
+ virtual HRESULT editIsoLinuxCfg(GeneralTextScript *pEditor, const char *pszMenuConfigFileName);
+
+private:
+
+ /**
+ * Tries to set label name of a label line.
+ *
+ * @returns true if label line is found and label name can be set.
+ * @param pEditor Editor with the menu configuration file loaded and parsed.
+ * @param vecLineNumbers Indices of the label lines (within pEditor data).
+ * @param pszKeyWord The keyword searched within the original label name.
+ * @param pszNewLabelName The new name of the label.
+ */
+ bool modifyLabelLine(GeneralTextScript *pEditor, const std::vector<size_t> &vecLineNumbers,
+ const char *pszKeyWord, const char *pszNewLabelName);
+};
+
+
+/**
+ * Ubuntu installer (same as debian, except for the template).
+ */
+class UnattendedUbuntuInstaller : public UnattendedDebianInstaller
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedUbuntuInstaller)
+
+ UnattendedUbuntuInstaller(Unattended *pParent)
+ : UnattendedDebianInstaller(pParent, "ubuntu_preseed.cfg")
+ { Assert(!isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded()); Assert(!isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO()); }
+ ~UnattendedUbuntuInstaller() {}
+};
+
+
+/**
+ * RHEL 6 installer.
+ *
+ * This serves as a base for the kickstart based installers.
+ */
+class UnattendedRhel6Installer : public UnattendedLinuxInstaller
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedRhel6Installer)
+
+ UnattendedRhel6Installer(Unattended *pParent,
+ const char *pszMainScriptTemplateName = "redhat67_ks.cfg",
+ const char *pszPostScriptTemplateName = "redhat_postinstall.sh",
+ const char *pszMainScriptFilename = "ks.cfg")
+ : UnattendedLinuxInstaller(pParent, pszMainScriptTemplateName, pszPostScriptTemplateName, pszMainScriptFilename)
+ {
+ Assert(!isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded());
+ Assert(!isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO());
+ mStrDefaultExtraInstallKernelParameters.assign(" ks=cdrom:/").append(pszMainScriptFilename).append(' ');
+ mArrStrRemoveInstallKernelParameters.append("rd.live.check"); /* Disables the checkisomd5 step. Required for VISO. */
+ }
+ ~UnattendedRhel6Installer() {}
+
+ bool isAuxiliaryIsoIsVISO() { return true; }
+ bool isOriginalIsoNeeded() const { return false; }
+
+protected:
+ HRESULT addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
+ RTVFS hVfsOrgIso, bool fOverwrite);
+};
+
+/**
+ * RHEL 7 installer (same as RHEL 6).
+ * The class was added for better handling any possible subtle difference between RHEL6 and RHEL7.
+ */
+class UnattendedRhel7Installer : public UnattendedRhel6Installer
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedRhel7Installer)
+
+ UnattendedRhel7Installer(Unattended *pParent)
+ : UnattendedRhel6Installer(pParent)
+ { Assert(!isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded()); Assert(!isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO()); }
+
+ UnattendedRhel7Installer(Unattended *pParent,
+ const char *pszMainScriptTemplateName,
+ const char *pszPostScriptTemplateName,
+ const char *pszMainScriptFilename)
+ : UnattendedRhel6Installer(pParent, pszMainScriptTemplateName, pszPostScriptTemplateName, pszMainScriptFilename)
+ { Assert(!isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded()); Assert(!isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO()); }
+ ~UnattendedRhel7Installer() {}
+};
+
+
+/**
+ * RHEL 8 installer (same as RHEL 7).
+ * The class was added for better handling any possible subtle difference between RHEL7 and RHEL8.
+ */
+class UnattendedRhel8Installer : public UnattendedRhel7Installer
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedRhel8Installer)
+
+ UnattendedRhel8Installer(Unattended *pParent)
+ : UnattendedRhel7Installer(pParent)
+ { Assert(!isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded()); Assert(!isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO()); }
+
+ UnattendedRhel8Installer(Unattended *pParent,
+ const char *pszMainScriptTemplateName,
+ const char *pszPostScriptTemplateName,
+ const char *pszMainScriptFilename)
+ : UnattendedRhel7Installer(pParent, pszMainScriptTemplateName, pszPostScriptTemplateName, pszMainScriptFilename)
+ { Assert(!isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded()); Assert(!isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO()); }
+ ~UnattendedRhel8Installer() {}
+};
+
+
+/**
+ * RHEL 5 installer (same as RHEL 6, except for the kickstart template).
+ */
+class UnattendedRhel5Installer : public UnattendedRhel6Installer
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedRhel5Installer)
+
+ UnattendedRhel5Installer(Unattended *pParent) : UnattendedRhel6Installer(pParent, "rhel5_ks.cfg") {}
+ ~UnattendedRhel5Installer() {}
+};
+
+
+/**
+ * RHEL 4 installer (same as RHEL 6, except for the kickstart template).
+ */
+class UnattendedRhel4Installer : public UnattendedRhel6Installer
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedRhel4Installer)
+
+ UnattendedRhel4Installer(Unattended *pParent) : UnattendedRhel6Installer(pParent, "rhel4_ks.cfg") {}
+ ~UnattendedRhel4Installer() {}
+};
+
+
+/**
+ * RHEL 3 installer (same as RHEL 6, except for the kickstart template).
+ */
+class UnattendedRhel3Installer : public UnattendedRhel6Installer
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedRhel3Installer)
+
+ UnattendedRhel3Installer(Unattended *pParent) : UnattendedRhel6Installer(pParent, "rhel3_ks.cfg") {}
+ ~UnattendedRhel3Installer() {}
+};
+
+
+/**
+ * Fedora installer (same as RHEL 6, except for the template).
+ */
+class UnattendedFedoraInstaller : public UnattendedRhel6Installer
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedFedoraInstaller)
+
+ UnattendedFedoraInstaller(Unattended *pParent)
+ : UnattendedRhel6Installer(pParent, "fedora_ks.cfg")
+ { Assert(!isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded()); Assert(!isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO()); }
+ ~UnattendedFedoraInstaller() {}
+};
+
+
+/**
+ * Oracle Linux 6 installer. Same as RHEL 6, except for the templates.
+ * The reason of adding new class is to sepatate the RHEL from OL.
+ */
+class UnattendedOracleLinux6Installer : public UnattendedRhel6Installer
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedOracleLinux6Installer)
+
+ UnattendedOracleLinux6Installer(Unattended *pParent,
+ const char *pszMainScriptTemplateName = "ol_ks.cfg",
+ const char *pszPostScriptTemplateName = "ol_postinstall.sh",
+ const char *pszMainScriptFilename = "ks.cfg")
+ : UnattendedRhel6Installer(pParent, pszMainScriptTemplateName, pszPostScriptTemplateName, pszMainScriptFilename)
+ { Assert(!isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded()); Assert(!isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO()); }
+ ~UnattendedOracleLinux6Installer() {}
+};
+
+
+/**
+ * Oracle Linux 7 installer. Same as OL 6.
+ * The class was added for better handling any possible subtle difference between OL6 and OL7.
+ */
+class UnattendedOracleLinux7Installer : public UnattendedOracleLinux6Installer
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedOracleLinux7Installer)
+
+ UnattendedOracleLinux7Installer(Unattended *pParent)
+ : UnattendedOracleLinux6Installer(pParent)
+ { Assert(!isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded()); Assert(!isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO()); }
+
+ UnattendedOracleLinux7Installer(Unattended *pParent,
+ const char *pszMainScriptTemplateName,
+ const char *pszPostScriptTemplateName,
+ const char *pszMainScriptFilename)
+ : UnattendedOracleLinux6Installer(pParent, pszMainScriptTemplateName, pszPostScriptTemplateName, pszMainScriptFilename)
+ { Assert(!isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded()); Assert(!isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO()); }
+ ~UnattendedOracleLinux7Installer() {}
+};
+
+
+/**
+ * Oracle Linux 8 installer. Same as OL 7.
+ * The class was added for better handling any possible subtle difference between OL7 and OL8.
+ */
+class UnattendedOracleLinux8Installer : public UnattendedOracleLinux7Installer
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedOracleLinux8Installer)
+
+ UnattendedOracleLinux8Installer(Unattended *pParent)
+ : UnattendedOracleLinux7Installer(pParent)
+ { Assert(!isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded()); Assert(!isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO()); }
+
+ UnattendedOracleLinux8Installer(Unattended *pParent,
+ const char *pszMainScriptTemplateName,
+ const char *pszPostScriptTemplateName,
+ const char *pszMainScriptFilename)
+ : UnattendedOracleLinux7Installer(pParent, pszMainScriptTemplateName, pszPostScriptTemplateName, pszMainScriptFilename)
+ { Assert(!isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded()); Assert(!isAuxiliaryFloppyNeeded()); Assert(isAuxiliaryIsoIsVISO()); }
+ ~UnattendedOracleLinux8Installer() {}
+};
+
+#if 0 /* fixme */
+/**
+ * SUSE linux installer.
+ *
+ * @todo needs fixing.
+ */
+class UnattendedSuseInstaller : public UnattendedLinuxInstaller
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedSuseInstaller)
+
+ UnattendedSuseInstaller(BaseTextScript *pAlg, Unattended *pParent)
+ : UnattendedLinuxInstaller(pAlg, pParent, "suse_autoinstall.xml")
+ { Assert(isOriginalIsoNeeded()); Assert(isAuxiliaryIsoNeeded()); Assert(!isAuxiliaryFloppyNeeded()); Assert(!isAuxiliaryIsoIsVISO()); }
+ ~UnattendedSuseInstaller() {}
+
+ HRESULT setupScriptOnAuxiliaryCD(const Utf8Str &path);
+};
+#endif
+
+/**
+ * Base class for the unattended FreeBSD installers.
+ */
+class UnattendedFreeBsdInstaller : public UnattendedInstaller
+{
+public:
+ UnattendedFreeBsdInstaller(Unattended *pParent)
+ : UnattendedInstaller(pParent,
+ "freebsd_installer.cfg", "freebsd_postinstall.sh",
+ "installerconfig", "vboxpostinstall.sh") {}
+ ~UnattendedFreeBsdInstaller() {}
+
+ bool isAuxiliaryIsoNeeded() const { return true; }
+ bool isOriginalIsoNeeded() const { return false; }
+
+protected:
+ HRESULT addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
+ RTVFS hVfsOrgIso, bool fOverwrite);
+};
+
+#endif /* !MAIN_INCLUDED_UnattendedInstaller_h */
diff --git a/src/VBox/Main/include/UnattendedScript.h b/src/VBox/Main/include/UnattendedScript.h
new file mode 100644
index 00000000..970a01af
--- /dev/null
+++ b/src/VBox/Main/include/UnattendedScript.h
@@ -0,0 +1,193 @@
+/* $Id: UnattendedScript.h $ */
+/** @file
+ * Classes for reading/parsing/saving scripts for unattended installation.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_UnattendedScript_h
+#define MAIN_INCLUDED_UnattendedScript_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "TextScript.h"
+#include "iprt/expreval.h"
+
+using namespace xml;
+
+class Unattended;
+
+
+/**
+ * Generic unattended text script template editor.
+ *
+ * This just perform variable replacements, no other editing possible.
+ *
+ * Everything happens during saveToString, parse is a noop.
+ */
+class UnattendedScriptTemplate : public BaseTextScript
+{
+protected:
+ /** Where to get the replacement strings from. */
+ Unattended *mpUnattended;
+
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedScriptTemplate)
+
+ UnattendedScriptTemplate(Unattended *pUnattended, const char *pszDefaultTemplateFilename, const char *pszDefaultFilename);
+ virtual ~UnattendedScriptTemplate() {}
+
+ HRESULT parse() { return S_OK; }
+ HRESULT saveToString(Utf8Str &rStrDst);
+
+protected:
+ typedef enum
+ {
+ kValueEscaping_None,
+ kValueEscaping_Bourne,
+ kValueEscaping_XML_Element,
+ kValueEscaping_XML_Attribute_Double_Quotes
+ } kEvalEscaping_T;
+
+ /**
+ * Gets the replacement value for the given placeholder.
+ *
+ * @returns COM status code.
+ * @param pachPlaceholder The placholder string. Not zero terminated.
+ * @param cchPlaceholder The length of the placeholder.
+ * @param fOutputting Indicates whether we actually need the correct value
+ * or is just syntax checking excluded template parts.
+ * @param rValue Where to return the value.
+ */
+ HRESULT getReplacement(const char *pachPlaceholder, size_t cchPlaceholder, bool fOutputting, RTCString &rValue);
+
+ /**
+ * Gets the replacement value for the given expression placeholder
+ * (@@VBOX_INSERT[expr]@@ and friends).
+ *
+ * @returns COM status code.
+ * @param hEvaluator The evaluator to use for the expression.
+ * @param pachPlaceholder The placholder string. Not zero terminated.
+ * @param cchPlaceholder The length of the placeholder.
+ * @param fOutputting Indicates whether we actually need the correct value
+ * or is just syntax checking excluded template parts.
+ * @param ppszValue Where to return the value. Free by calling
+ * RTStrFree. Set to NULL for empty string.
+ */
+ HRESULT getReplacementForExpr(RTEXPREVAL hEvaluator, const char *pachPlaceholder, size_t cchPlaceholder,
+ bool fOutputting, char **ppszValue) RT_NOEXCEPT;
+
+ /**
+ * Resolves a conditional expression.
+ *
+ * @returns COM status code.
+ * @param hEvaluator The evaluator to use for the expression.
+ * @param pachPlaceholder The placholder string. Not zero terminated.
+ * @param cchPlaceholder The length of the placeholder.
+ * @param pfOutputting Where to return the result of the conditional. This
+ * holds the current outputting state on input in case
+ * someone want to sanity check anything.
+ */
+ HRESULT resolveConditionalExpr(RTEXPREVAL hEvaluator, const char *pachPlaceholder, size_t cchPlaceholder,
+ bool *pfOutputting) RT_NOEXCEPT;
+
+ /** @callback_method_impl{FNRTEXPREVALQUERYVARIABLE} */
+ static DECLCALLBACK(int) queryVariableForExpr(const char *pchName, size_t cchName, void *pvUser,
+ char **ppszValue) RT_NOEXCEPT;
+
+ /**
+ * Gets a variable.
+ *
+ * This is used both for getting replacements (@@VBOX_INSERT_XXX@@) and in
+ * expressions (@@VBOX_INSERT[expr]@@, @@VBOX_COND[expr]@@).
+ *
+ * @returns VBox status code.
+ * @retval VERR_NOT_FOUND if variable does not exist.
+ *
+ * @param pchName The variable name. Not zero terminated.
+ * @param cchName The length of the name.
+ * @param rstrTmp String object that can be used for keeping the
+ * value returned via @a *ppszValue.
+ * @param ppszValue If a value is desired, this is where to return
+ * it. This points to a string that should be
+ * accessible for a little while after the function
+ * returns. Use @a rstrTmp for storage if
+ * necessary.
+ *
+ * This will be NULL when called from the 'defined'
+ * operator. In which case no errors should be
+ * set.
+ * @throws std::bad_alloc
+ * @see FNRTEXPREVALQUERYVARIABLE
+ */
+ virtual int queryVariable(const char *pchName, size_t cchName, Utf8Str &rstrTmp, const char **ppszValue);
+
+ /**
+ * Get the result of a conditional.
+ *
+ * @returns COM status code.
+ * @param pachPlaceholder The placholder string. Not zero terminated.
+ * @param cchPlaceholder The length of the placeholder.
+ * @param pfOutputting Where to return the result of the conditional.
+ * This holds the current outputting state on input
+ * in case someone want to sanity check anything.
+ */
+ virtual HRESULT getConditional(const char *pachPlaceholder, size_t cchPlaceholder, bool *pfOutputting);
+};
+
+#if 0 /* convert when we fix SUSE */
+/**
+ * SUSE unattended XML file editor.
+ */
+class UnattendedSUSEXMLScript : public UnattendedXMLScript
+{
+public:
+ DECLARE_TRANSLATE_METHODS(UnattendedSUSEXMLScript)
+
+ UnattendedSUSEXMLScript(VirtualBoxBase *pSetError, const char *pszDefaultFilename = "autoinst.xml")
+ : UnattendedXMLScript(pSetError, pszDefaultFilename) {}
+ ~UnattendedSUSEXMLScript() {}
+
+ HRESULT parse();
+
+protected:
+ HRESULT setFieldInElement(xml::ElementNode *pElement, const DataId enmDataId, const Utf8Str &rStrValue);
+
+private:
+ //////////////////New functions//////////////////////////////
+ /** @throws std::bad_alloc */
+ HRESULT LoopThruSections(const xml::ElementNode *pelmRoot);
+ /** @throws std::bad_alloc */
+ HRESULT HandleUserAccountsSection(const xml::ElementNode *pelmSection);
+ /** @throws std::bad_alloc */
+ Utf8Str createProbableValue(const DataId enmDataId, const xml::ElementNode *pCurElem);
+ /** @throws std::bad_alloc */
+ Utf8Str createProbableUserHomeDir(const xml::ElementNode *pCurElem);
+ //////////////////New functions//////////////////////////////
+};
+#endif
+
+
+#endif /* !MAIN_INCLUDED_UnattendedScript_h */
+
diff --git a/src/VBox/Main/include/UpdateAgentImpl.h b/src/VBox/Main/include/UpdateAgentImpl.h
new file mode 100644
index 00000000..db70fa43
--- /dev/null
+++ b/src/VBox/Main/include/UpdateAgentImpl.h
@@ -0,0 +1,220 @@
+/* $Id: UpdateAgentImpl.h $ */
+/** @file
+ * Update agent COM class implementation - Header
+ */
+
+/*
+ * Copyright (C) 2020-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_UpdateAgentImpl_h
+#define MAIN_INCLUDED_UpdateAgentImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/http.h>
+
+#include <VBox/settings.h>
+
+#include "EventImpl.h"
+#include "UpdateAgentWrap.h"
+#include "HostUpdateAgentWrap.h"
+
+class UpdateAgentTask;
+struct UpdateAgentTaskParms;
+
+struct UpdateAgentTaskResult
+{
+ Utf8Str strVer;
+ Utf8Str strWebUrl;
+ Utf8Str strDownloadUrl;
+ UpdateSeverity_T enmSeverity;
+ Utf8Str strReleaseNotes;
+};
+
+class UpdateAgentBase
+{
+protected: /* Not directly instancable. */
+
+ UpdateAgentBase()
+ : m_VirtualBox(NULL)
+ , m(new settings::UpdateAgent) { }
+
+ virtual ~UpdateAgentBase() { delete m; }
+
+public:
+
+ /** @name Pure virtual public methods for internal purposes only
+ * (ensure there is a caller and a read lock before calling them!)
+ * @{ */
+ virtual HRESULT i_loadSettings(const settings::UpdateAgent &data) = 0;
+ virtual HRESULT i_saveSettings(settings::UpdateAgent &data) = 0;
+
+ virtual HRESULT i_setCheckCount(ULONG aCount) = 0;
+ virtual HRESULT i_setLastCheckDate(const com::Utf8Str &aDate) = 0;
+ /** @} */
+
+protected:
+
+ /** @name Pure virtual internal task callbacks.
+ * @{ */
+ friend UpdateAgentTask;
+ virtual DECLCALLBACK(HRESULT) i_checkForUpdateTask(UpdateAgentTask *pTask) = 0;
+ /** @} */
+
+ /** @name Static helper methods.
+ * @{ */
+ static Utf8Str i_getPlatformInfo(void);
+ const char *i_proxyModeToStr(ProxyMode_T enmMode);
+ bool i_urlSchemeIsSupported(const Utf8Str &strUrl) const;
+ /** @} */
+
+protected:
+ /** The update agent's event source. */
+ const ComObjPtr<EventSource> m_EventSource;
+ VirtualBox * const m_VirtualBox;
+
+ /** @name Data members.
+ * @{ */
+ settings::UpdateAgent *m;
+
+ struct Data
+ {
+ UpdateAgentTaskResult m_lastResult;
+ Utf8Str m_strName;
+ /** Vector of update channels this agent supports. */
+ const std::vector<UpdateChannel_T> m_enmChannels;
+ bool m_fHidden;
+ UpdateState_T m_enmState;
+ uint32_t m_uOrder;
+
+ Data(void)
+ {
+ m_fHidden = true;
+ m_enmState = UpdateState_Invalid;
+ m_uOrder = UINT32_MAX;
+ }
+ } mData;
+ /** @} */
+};
+
+class ATL_NO_VTABLE UpdateAgent :
+ public UpdateAgentWrap,
+ public UpdateAgentBase
+{
+public:
+ DECLARE_COMMON_CLASS_METHODS(UpdateAgent)
+
+ /** @name COM and internal init/term/mapping cruft.
+ * @{ */
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ HRESULT init(VirtualBox *aVirtualBox);
+ void uninit(void);
+ /** @} */
+
+ /** @name Public methods for internal purposes only
+ * (ensure there is a caller and a read lock before calling them!)
+ * @{ */
+ HRESULT i_loadSettings(const settings::UpdateAgent &data);
+ HRESULT i_saveSettings(settings::UpdateAgent &data);
+
+ virtual HRESULT i_setCheckCount(ULONG aCount);
+ virtual HRESULT i_setLastCheckDate(const com::Utf8Str &aDate);
+ /** @} */
+
+protected:
+
+ /** @name Internal helper methods.
+ * @{ */
+ HRESULT i_getProxyMode(ProxyMode_T *aMode);
+ HRESULT i_getProxyURL(com::Utf8Str &aAddress);
+ HRESULT i_configureProxy(RTHTTP hHttp);
+ HRESULT i_commitSettings(AutoWriteLock &aLock);
+ HRESULT i_reportError(int vrc, const char *pcszMsgFmt, ...);
+ /** @} */
+
+protected:
+ /** @name Wrapped IUpdateAgent attributes and methods.
+ * @{ */
+ HRESULT checkFor(ComPtr<IProgress> &aProgress);
+ HRESULT download(ComPtr<IProgress> &aProgress);
+ HRESULT install(ComPtr<IProgress> &aProgress);
+ HRESULT rollback(void);
+
+ HRESULT getName(com::Utf8Str &aName);
+ HRESULT getEventSource(ComPtr<IEventSource> &aEventSource);
+ HRESULT getOrder(ULONG *aOrder);
+ HRESULT getDependsOn(std::vector<com::Utf8Str> &aDeps);
+ HRESULT getVersion(com::Utf8Str &aVer);
+ HRESULT getDownloadUrl(com::Utf8Str &aUrl);
+ HRESULT getWebUrl(com::Utf8Str &aUrl);
+ HRESULT getReleaseNotes(com::Utf8Str &aRelNotes);
+ HRESULT getEnabled(BOOL *aEnabled);
+ HRESULT setEnabled(BOOL aEnabled);
+ HRESULT getHidden(BOOL *aHidden);
+ HRESULT getState(UpdateState_T *aState);
+ HRESULT getCheckCount(ULONG *aCount);
+ HRESULT getCheckFrequency(ULONG *aFreqSeconds);
+ HRESULT setCheckFrequency(ULONG aFreqSeconds);
+ HRESULT getChannel(UpdateChannel_T *aChannel);
+ HRESULT setChannel(UpdateChannel_T aChannel);
+ HRESULT getRepositoryURL(com::Utf8Str &aRepo);
+ HRESULT setRepositoryURL(const com::Utf8Str &aRepo);
+ HRESULT getLastCheckDate(com::Utf8Str &aData);
+ HRESULT getIsCheckNeeded(BOOL *aCheckNeeded);
+ HRESULT getSupportedChannels(std::vector<UpdateChannel_T> &aSupportedChannels);
+ /** @} */
+};
+
+/** @todo Put this into an own module, e.g. HostUpdateAgentImpl.[cpp|h]. */
+
+class ATL_NO_VTABLE HostUpdateAgent :
+ public UpdateAgent
+{
+public:
+ /** @name COM and internal init/term/mapping cruft.
+ * @{ */
+ DECLARE_COMMON_CLASS_METHODS(HostUpdateAgent)
+
+ HRESULT init(VirtualBox *aVirtualBox);
+ void uninit(void);
+
+ HRESULT FinalConstruct(void);
+ void FinalRelease(void);
+ /** @} */
+
+private:
+ /** @name Implemented (pure) virtual methods from UpdateAgent.
+ * @{ */
+ HRESULT checkFor(ComPtr<IProgress> &aProgress);
+
+ DECLCALLBACK(HRESULT) i_checkForUpdateTask(UpdateAgentTask *pTask);
+ /** @} */
+
+ HRESULT i_checkForUpdate(void);
+ HRESULT i_checkForUpdateInner(RTHTTP hHttp, com::Utf8Str const &strUrl, com::Utf8Str const &strUserAgent);
+};
+
+#endif /* !MAIN_INCLUDED_UpdateAgentImpl_h */
+
diff --git a/src/VBox/Main/include/UsbCardReader.h b/src/VBox/Main/include/UsbCardReader.h
new file mode 100644
index 00000000..1071ecf4
--- /dev/null
+++ b/src/VBox/Main/include/UsbCardReader.h
@@ -0,0 +1,87 @@
+/* $Id: UsbCardReader.h $ */
+
+/** @file
+ * VirtualBox Driver interface to the virtual Usb Card Reader.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_UsbCardReader_h
+#define MAIN_INCLUDED_UsbCardReader_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/vmm/pdmcardreaderinfs.h>
+#include <VBox/vmm/pdmdrv.h>
+
+#define USBCARDREADER_OID "46225eac-10c9-4b57-92b6-e59efd48009f"
+
+class Console;
+typedef struct USBCARDREADER USBCARDREADER;
+typedef struct UCRREMOTE UCRREMOTE;
+
+class UsbCardReader
+{
+ public:
+ UsbCardReader(Console *console);
+ virtual ~UsbCardReader();
+
+ static const PDMDRVREG DrvReg;
+ USBCARDREADER *mpDrv;
+
+ Console *getParent(void) { return mParent; }
+
+ int VRDENotify(uint32_t u32Id, void *pvData, uint32_t cbData);
+ int VRDEResponse(int rcRequest, void *pvUser, uint32_t u32Function, void *pvData, uint32_t cbData);
+
+ int EstablishContext(USBCARDREADER *pDrv);
+ int ReleaseContext(USBCARDREADER *pDrv);
+ int GetStatusChange(USBCARDREADER *pDrv, void *pvUser, uint32_t u32Timeout,
+ PDMICARDREADER_READERSTATE *paReaderStats, uint32_t cReaderStats);
+ int Connect(USBCARDREADER *pDrv, void *pvUser, const char *pszReaderName,
+ uint32_t u32ShareMode, uint32_t u32PreferredProtocols);
+ int Disconnect(USBCARDREADER *pDrv, void *pvUser, uint32_t u32Mode);
+ int Status(USBCARDREADER *pDrv, void *pvUser);
+ int Transmit(USBCARDREADER *pDrv, void *pvUser, PDMICARDREADER_IO_REQUEST *pioSendRequest,
+ uint8_t *pu8SendBuffer, uint32_t cbSendBuffer, uint32_t cbRecvBuffer);
+ int Control(USBCARDREADER *pDrv, void *pvUser, uint32_t u32ControlCode,
+ uint8_t *pu8InBuffer, uint32_t cbInBuffer, uint32_t cbOutBuffer);
+ int GetAttrib(USBCARDREADER *pDrv, void *pvUser, uint32_t u32AttrId, uint32_t cbAttrib);
+ int SetAttrib(USBCARDREADER *pDrv, void *pvUser, uint32_t u32AttrId,
+ uint8_t *pu8Attrib, uint32_t cbAttrib);
+
+ private:
+ static DECLCALLBACK(void *) drvQueryInterface(PPDMIBASE pInterface, const char *pszIID);
+ static DECLCALLBACK(int) drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags);
+ static DECLCALLBACK(void) drvDestruct(PPDMDRVINS pDrvIns);
+
+ int vrdeSCardRequest(void *pvUser, uint32_t u32Function, const void *pvData, uint32_t cbData);
+
+ Console * const mParent;
+
+ UCRREMOTE *m_pRemote;
+};
+
+#endif /* !MAIN_INCLUDED_UsbCardReader_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/UsbWebcamInterface.h b/src/VBox/Main/include/UsbWebcamInterface.h
new file mode 100644
index 00000000..bedb5a2b
--- /dev/null
+++ b/src/VBox/Main/include/UsbWebcamInterface.h
@@ -0,0 +1,79 @@
+/* $Id: UsbWebcamInterface.h $ */
+/** @file
+ * VirtualBox PDM Driver for Emulated USB Webcam
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_UsbWebcamInterface_h
+#define MAIN_INCLUDED_UsbWebcamInterface_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/vmm/pdmdrv.h>
+#define VRDE_VIDEOIN_WITH_VRDEINTERFACE /* Get the VRDE interface definitions. */
+#include <VBox/RemoteDesktop/VRDEVideoIn.h>
+
+class ConsoleVRDPServer;
+typedef struct EMWEBCAMDRV EMWEBCAMDRV;
+typedef struct EMWEBCAMREMOTE EMWEBCAMREMOTE;
+
+class EmWebcam
+{
+ public:
+ EmWebcam(ConsoleVRDPServer *pServer);
+ virtual ~EmWebcam();
+
+ static const PDMDRVREG DrvReg;
+
+ void EmWebcamConstruct(EMWEBCAMDRV *pDrv);
+ void EmWebcamDestruct(EMWEBCAMDRV *pDrv);
+
+ /* Callbacks. */
+ void EmWebcamCbNotify(uint32_t u32Id, const void *pvData, uint32_t cbData);
+ void EmWebcamCbDeviceDesc(int rcRequest, void *pDeviceCtx, void *pvUser,
+ const VRDEVIDEOINDEVICEDESC *pDeviceDesc, uint32_t cbDeviceDesc);
+ void EmWebcamCbControl(int rcRequest, void *pDeviceCtx, void *pvUser,
+ const VRDEVIDEOINCTRLHDR *pControl, uint32_t cbControl);
+ void EmWebcamCbFrame(int rcRequest, void *pDeviceCtx,
+ const VRDEVIDEOINPAYLOADHDR *pFrame, uint32_t cbFrame);
+
+ /* Methods for the PDM driver. */
+ int SendControl(EMWEBCAMDRV *pDrv, void *pvUser, uint64_t u64DeviceId,
+ const VRDEVIDEOINCTRLHDR *pControl, uint32_t cbControl);
+
+ private:
+ static DECLCALLBACK(void *) drvQueryInterface(PPDMIBASE pInterface, const char *pszIID);
+ static DECLCALLBACK(int) drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags);
+ static DECLCALLBACK(void) drvDestruct(PPDMDRVINS pDrvIns);
+
+ ConsoleVRDPServer * const mParent;
+
+ EMWEBCAMDRV *mpDrv;
+ EMWEBCAMREMOTE *mpRemote;
+ uint64_t volatile mu64DeviceIdSrc;
+};
+
+#endif /* !MAIN_INCLUDED_UsbWebcamInterface_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/VBoxNls.h b/src/VBox/Main/include/VBoxNls.h
new file mode 100644
index 00000000..7f8aae2f
--- /dev/null
+++ b/src/VBox/Main/include/VBoxNls.h
@@ -0,0 +1,65 @@
+/* $Id: VBoxNls.h $ */
+/** @file
+ * VBox NLS.
+ */
+
+/*
+ * Copyright (C) 2020-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#ifndef MAIN_INCLUDED_VBoxNls_h
+#define MAIN_INCLUDED_VBoxNls_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#ifdef VBOX_WITH_MAIN_NLS
+
+#include <VBox/com/defs.h>
+#include <VBox/com/ptr.h>
+#include <VBox/com/string.h>
+#include "VirtualBoxTranslator.h"
+
+
+# define DECLARE_TRANSLATION_CONTEXT(ctx) \
+struct ctx \
+{\
+ static const char *tr(const char *pszSource, const char *pszComment = NULL, const size_t aNum = ~(size_t)0) \
+ { \
+ return VirtualBoxTranslator::translate(NULL, #ctx, pszSource, pszComment, aNum); \
+ } \
+}
+#else
+# define DECLARE_TRANSLATION_CONTEXT(ctx) \
+struct ctx \
+{\
+ static const char *tr(const char *pszSource, const char *pszComment = NULL, const size_t aNum = ~(size_t)0) \
+ { \
+ NOREF(pszComment); \
+ NOREF(aNum); \
+ return pszSource; \
+ } \
+}
+#endif
+
+#endif /* !MAIN_INCLUDED_VBoxNls_h */
+
diff --git a/src/VBox/Main/include/VFSExplorerImpl.h b/src/VBox/Main/include/VFSExplorerImpl.h
new file mode 100644
index 00000000..b3b0b5c3
--- /dev/null
+++ b/src/VBox/Main/include/VFSExplorerImpl.h
@@ -0,0 +1,101 @@
+/* $Id: VFSExplorerImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_VFSExplorerImpl_h
+#define MAIN_INCLUDED_VFSExplorerImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VFSExplorerWrap.h"
+
+class ATL_NO_VTABLE VFSExplorer :
+ public VFSExplorerWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(VFSExplorer)
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT FinalConstruct() { return BaseFinalConstruct(); }
+ void FinalRelease() { uninit(); BaseFinalRelease(); }
+
+ HRESULT init(VFSType_T aType, Utf8Str aFilePath, Utf8Str aHostname, Utf8Str aUsername, Utf8Str aPassword, VirtualBox *aVirtualBox);
+ void uninit();
+
+ /* public methods only for internal purposes */
+ static HRESULT setErrorStatic(HRESULT aResultCode, const char *aText, ...)
+ {
+ va_list va;
+ va_start(va, aText);
+ HRESULT hrc = setErrorInternalV(aResultCode, getStaticClassIID(), getStaticComponentName(), aText, va, false, true);
+ va_end(va);
+ return hrc;
+ }
+
+private:
+
+ // wrapped IVFSExplorer properties
+ HRESULT getPath(com::Utf8Str &aPath);
+ HRESULT getType(VFSType_T *aType);
+
+ // wrapped IVFSExplorer methods
+ HRESULT update(ComPtr<IProgress> &aProgress);
+ HRESULT cd(const com::Utf8Str &aDir, ComPtr<IProgress> &aProgress);
+ HRESULT cdUp(ComPtr<IProgress> &aProgress);
+ HRESULT entryList(std::vector<com::Utf8Str> &aNames,
+ std::vector<ULONG> &aTypes,
+ std::vector<LONG64> &aSizes,
+ std::vector<ULONG> &aModes);
+ HRESULT exists(const std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aExists);
+ HRESULT remove(const std::vector<com::Utf8Str> &aNames,
+ ComPtr<IProgress> &aProgress);
+
+ /* Private member vars */
+ VirtualBox * const mVirtualBox;
+
+ ////////////////////////////////////////////////////////////////////////////////
+ ////
+ //// VFSExplorer definitions
+ ////
+ //////////////////////////////////////////////////////////////////////////////////
+ //
+ class TaskVFSExplorer; /* Worker thread helper */
+ struct Data;
+ Data *m;
+
+ /* Private member methods */
+ FsObjType_T i_iprtToVfsObjType(RTFMODE aType) const;
+
+ HRESULT i_updateFS(TaskVFSExplorer *aTask);
+ HRESULT i_deleteFS(TaskVFSExplorer *aTask);
+
+};
+
+#endif /* !MAIN_INCLUDED_VFSExplorerImpl_h */
+
diff --git a/src/VBox/Main/include/VMMDev.h b/src/VBox/Main/include/VMMDev.h
new file mode 100644
index 00000000..864e7c2e
--- /dev/null
+++ b/src/VBox/Main/include/VMMDev.h
@@ -0,0 +1,115 @@
+/* $Id: VMMDev.h $ */
+/** @file
+ * VirtualBox Driver interface to VMM device
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_VMMDev_h
+#define MAIN_INCLUDED_VMMDev_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VirtualBoxBase.h"
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/hgcmsvc.h>
+#include <iprt/asm.h>
+
+class Console;
+
+class VMMDevMouseInterface
+{
+public:
+ virtual ~VMMDevMouseInterface() { /* Make VC++ 19.2 happy. */ }
+ virtual PPDMIVMMDEVPORT getVMMDevPort() = 0;
+};
+
+class VMMDev : public VMMDevMouseInterface
+{
+public:
+ VMMDev(Console *console);
+ virtual ~VMMDev();
+ static const PDMDRVREG DrvReg;
+ /** Pointer to the associated VMMDev driver. */
+ struct DRVMAINVMMDEV *mpDrv;
+
+ bool fSharedFolderActive;
+ bool isShFlActive()
+ {
+ return fSharedFolderActive;
+ }
+
+ Console *getParent()
+ {
+ return mParent;
+ }
+
+ int WaitCredentialsJudgement (uint32_t u32Timeout, uint32_t *pu32GuestFlags);
+ int SetCredentialsJudgementResult (uint32_t u32Flags);
+
+ PPDMIVMMDEVPORT getVMMDevPort();
+
+#ifdef VBOX_WITH_HGCM
+ int hgcmLoadService (const char *pszServiceLibrary, const char *pszServiceName);
+ int hgcmHostCall (const char *pszServiceName, uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms);
+ void hgcmShutdown(bool fUvmIsInvalid = false);
+
+ bool hgcmIsActive (void) { return ASMAtomicReadBool(&m_fHGCMActive); }
+#endif /* VBOX_WITH_HGCM */
+
+private:
+#ifdef VBOX_WITH_HGCM
+# ifdef VBOX_WITH_GUEST_PROPS
+ void i_guestPropSetMultiple(void *names, void *values, void *timestamps, void *flags);
+ void i_guestPropSet(const char *pszName, const char *pszValue, const char *pszFlags);
+ int i_guestPropSetGlobalPropertyFlags(uint32_t fFlags);
+ int i_guestPropLoadAndConfigure();
+# endif
+#endif
+ static DECLCALLBACK(void *) drvQueryInterface(PPDMIBASE pInterface, const char *pszIID);
+ static DECLCALLBACK(int) drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags);
+ static DECLCALLBACK(void) drvDestruct(PPDMDRVINS pDrvIns);
+ static DECLCALLBACK(void) drvReset(PPDMDRVINS pDrvIns);
+ static DECLCALLBACK(void) drvPowerOn(PPDMDRVINS pDrvIns);
+ static DECLCALLBACK(void) drvPowerOff(PPDMDRVINS pDrvIns);
+ static DECLCALLBACK(void) drvSuspend(PPDMDRVINS pDrvIns);
+ static DECLCALLBACK(void) drvResume(PPDMDRVINS pDrvIns);
+ static DECLCALLBACK(int) hgcmSave(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM);
+ static DECLCALLBACK(int) hgcmLoad(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass);
+
+ Console * const mParent;
+
+ RTSEMEVENT mCredentialsEvent;
+ uint32_t mu32CredentialsFlags;
+
+#ifdef VBOX_WITH_HGCM
+ bool volatile m_fHGCMActive;
+#endif /* VBOX_WITH_HGCM */
+};
+
+/** VMMDev object ID used by Console::i_vmm2User_QueryGenericObject and VMMDev::drvConstruct. */
+#define VMMDEV_OID "e2ff0c7b-c02b-46d0-aa90-b9caf0f60561"
+
+#endif /* !MAIN_INCLUDED_VMMDev_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/VRDEServerImpl.h b/src/VBox/Main/include/VRDEServerImpl.h
new file mode 100644
index 00000000..04a6e78f
--- /dev/null
+++ b/src/VBox/Main/include/VRDEServerImpl.h
@@ -0,0 +1,98 @@
+/* $Id: VRDEServerImpl.h $ */
+
+/** @file
+ *
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_VRDEServerImpl_h
+#define MAIN_INCLUDED_VRDEServerImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VRDEServerWrap.h"
+
+namespace settings
+{
+ struct VRDESettings;
+}
+
+class ATL_NO_VTABLE VRDEServer :
+ public VRDEServerWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(VRDEServer)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(Machine *aParent);
+ HRESULT init(Machine *aParent, VRDEServer *aThat);
+ HRESULT initCopy(Machine *aParent, VRDEServer *aThat);
+ void uninit();
+
+ // public methods only for internal purposes
+ HRESULT i_loadSettings(const settings::VRDESettings &data);
+ HRESULT i_saveSettings(settings::VRDESettings &data);
+ void i_rollback();
+ void i_commit();
+ void i_copyFrom(VRDEServer *aThat);
+
+private:
+
+ // wrapped IVRDEServer properties
+ HRESULT getEnabled(BOOL *aEnabled);
+ HRESULT setEnabled(BOOL aEnabled);
+ HRESULT getAuthType(AuthType_T *aAuthType);
+ HRESULT setAuthType(AuthType_T aAuthType);
+ HRESULT getAuthTimeout(ULONG *aAuthTimeout);
+ HRESULT setAuthTimeout(ULONG aAuthTimeout);
+ HRESULT getAllowMultiConnection(BOOL *aAllowMultiConnection);
+ HRESULT setAllowMultiConnection(BOOL aAllowMultiConnection);
+ HRESULT getReuseSingleConnection(BOOL *aReuseSingleConnection);
+ HRESULT setReuseSingleConnection(BOOL aReuseSingleConnection);
+ HRESULT getVRDEExtPack(com::Utf8Str &aVRDEExtPack);
+ HRESULT setVRDEExtPack(const com::Utf8Str &aVRDEExtPack);
+ HRESULT getAuthLibrary(com::Utf8Str &aAuthLibrary);
+ HRESULT setAuthLibrary(const com::Utf8Str &aAuthLibrary);
+ HRESULT getVRDEProperties(std::vector<com::Utf8Str> &aVRDEProperties);
+
+ // wrapped IVRDEServer methods
+ HRESULT setVRDEProperty(const com::Utf8Str &aKey,
+ const com::Utf8Str &aValue);
+ HRESULT getVRDEProperty(const com::Utf8Str &aKey,
+ com::Utf8Str &aValue);
+
+ Machine * const mParent;
+ const ComObjPtr<VRDEServer> mPeer;
+
+ Backupable<settings::VRDESettings> mData;
+};
+
+#endif /* !MAIN_INCLUDED_VRDEServerImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/VirtualBoxBase.h b/src/VBox/Main/include/VirtualBoxBase.h
new file mode 100644
index 00000000..4a92a871
--- /dev/null
+++ b/src/VBox/Main/include/VirtualBoxBase.h
@@ -0,0 +1,1118 @@
+/* $Id: VirtualBoxBase.h $ */
+/** @file
+ * VirtualBox COM base classes definition
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_VirtualBoxBase_h
+#define MAIN_INCLUDED_VirtualBoxBase_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+#include <iprt/thread.h>
+
+#include <list>
+#include <map>
+
+#include "ObjectState.h"
+
+#include "VBox/com/AutoLock.h"
+#include "VBox/com/string.h"
+#include "VBox/com/Guid.h"
+
+#include "VBox/com/VirtualBox.h"
+
+#include "VirtualBoxTranslator.h"
+
+// avoid including VBox/settings.h and VBox/xml.h; only declare the classes
+namespace xml
+{
+class File;
+}
+
+namespace com
+{
+class ErrorInfo;
+}
+
+using namespace com;
+using namespace util;
+
+class VirtualBox;
+class Machine;
+class Medium;
+class Host;
+typedef std::list<ComObjPtr<Medium> > MediaList;
+typedef std::list<Utf8Str> StringsList;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// COM helpers
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#if !defined(VBOX_WITH_XPCOM)
+
+/* use a special version of the singleton class factory,
+ * see KB811591 in msdn for more info. */
+
+#undef DECLARE_CLASSFACTORY_SINGLETON
+#define DECLARE_CLASSFACTORY_SINGLETON(obj) DECLARE_CLASSFACTORY_EX(CMyComClassFactorySingleton<obj>)
+
+/**
+ * @todo r=bird: This CMyComClassFactorySingleton stuff is probably obsoleted by
+ * microatl.h? Right?
+ */
+
+template <class T>
+class CMyComClassFactorySingleton : public ATL::CComClassFactory
+{
+public:
+ CMyComClassFactorySingleton() :
+ m_hrCreate(S_OK), m_spObj(NULL)
+ {
+ }
+ virtual ~CMyComClassFactorySingleton()
+ {
+ if (m_spObj)
+ m_spObj->Release();
+ }
+ // IClassFactory
+ STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid, void** ppvObj)
+ {
+ HRESULT hRes = E_POINTER;
+ if (ppvObj != NULL)
+ {
+ *ppvObj = NULL;
+ // no aggregation for singletons
+ AssertReturn(pUnkOuter == NULL, CLASS_E_NOAGGREGATION);
+ if (m_hrCreate == S_OK && m_spObj == NULL)
+ {
+ Lock();
+ __try
+ {
+ // Fix: The following If statement was moved inside the __try statement.
+ // Did another thread arrive here first?
+ if (m_hrCreate == S_OK && m_spObj == NULL)
+ {
+ // lock the module to indicate activity
+ // (necessary for the monitor shutdown thread to correctly
+ // terminate the module in case when CreateInstance() fails)
+ ATL::_pAtlModule->Lock();
+ ATL::CComObjectCached<T> *p;
+ m_hrCreate = ATL::CComObjectCached<T>::CreateInstance(&p);
+ if (SUCCEEDED(m_hrCreate))
+ {
+ m_hrCreate = p->QueryInterface(IID_IUnknown, (void **)&m_spObj);
+ if (FAILED(m_hrCreate))
+ {
+ delete p;
+ }
+ }
+ ATL::_pAtlModule->Unlock();
+ }
+ }
+ __finally
+ {
+ Unlock();
+ }
+ }
+ if (m_hrCreate == S_OK)
+ {
+ hRes = m_spObj->QueryInterface(riid, ppvObj);
+ }
+ else
+ {
+ hRes = m_hrCreate;
+ }
+ }
+ return hRes;
+ }
+ HRESULT m_hrCreate;
+ IUnknown *m_spObj;
+};
+
+#endif /* !defined(VBOX_WITH_XPCOM) */
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Macros
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Special version of the Assert macro to be used within VirtualBoxBase
+ * subclasses.
+ *
+ * In the debug build, this macro is equivalent to Assert.
+ * In the release build, this macro uses |setError(E_FAIL, ...)| to set the
+ * error info from the asserted expression.
+ *
+ * @see VirtualBoxBase::setError
+ *
+ * @param expr Expression which should be true.
+ */
+#define ComAssert(expr) \
+ do { \
+ if (RT_LIKELY(!!(expr))) \
+ { /* likely */ } \
+ else \
+ { \
+ AssertMsgFailed(("%s\n", #expr)); \
+ setError(E_FAIL, \
+ VirtualBoxBase::tr("Assertion failed: [%s] at '%s' (%d) in %s.\nPlease contact the product vendor!"), \
+ #expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+ } \
+ } while (0)
+
+/**
+ * Special version of the AssertFailed macro to be used within VirtualBoxBase
+ * subclasses.
+ *
+ * In the debug build, this macro is equivalent to AssertFailed.
+ * In the release build, this macro uses |setError(E_FAIL, ...)| to set the
+ * error info from the asserted expression.
+ *
+ * @see VirtualBoxBase::setError
+ *
+ */
+#define ComAssertFailed() \
+ do { \
+ AssertFailed(); \
+ setError(E_FAIL, \
+ VirtualBoxBase::tr("Assertion failed: at '%s' (%d) in %s.\nPlease contact the product vendor!"), \
+ __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+ } while (0)
+
+/**
+ * Special version of the AssertMsg macro to be used within VirtualBoxBase
+ * subclasses.
+ *
+ * See ComAssert for more info.
+ *
+ * @param expr Expression which should be true.
+ * @param a printf argument list (in parenthesis).
+ */
+#define ComAssertMsg(expr, a) \
+ do { \
+ if (RT_LIKELY(!!(expr))) \
+ { /* likely */ } \
+ else \
+ { \
+ Utf8StrFmt MyAssertMsg a; /* may throw bad_alloc */ \
+ AssertMsgFailed(("%s\n", MyAssertMsg.c_str())); \
+ setError(E_FAIL, \
+ VirtualBoxBase::tr("Assertion failed: [%s] at '%s' (%d) in %s.\n%s.\nPlease contact the product vendor!"), \
+ #expr, __FILE__, __LINE__, __PRETTY_FUNCTION__, MyAssertMsg.c_str()); \
+ } \
+ } while (0)
+
+/**
+ * Special version of the AssertMsgFailed macro to be used within VirtualBoxBase
+ * subclasses.
+ *
+ * See ComAssert for more info.
+ *
+ * @param a printf argument list (in parenthesis).
+ */
+#define ComAssertMsgFailed(a) \
+ do { \
+ Utf8StrFmt MyAssertMsg a; /* may throw bad_alloc */ \
+ AssertMsgFailed(("%s\n", MyAssertMsg.c_str())); \
+ setError(E_FAIL, \
+ VirtualBoxBase::tr("Assertion failed: at '%s' (%d) in %s.\n%s.\nPlease contact the product vendor!"), \
+ __FILE__, __LINE__, __PRETTY_FUNCTION__, MyAssertMsg.c_str()); \
+ } while (0)
+
+/**
+ * Special version of the AssertRC macro to be used within VirtualBoxBase
+ * subclasses.
+ *
+ * See ComAssert for more info.
+ *
+ * @param vrc VBox status code.
+ */
+#define ComAssertRC(vrc) ComAssertMsgRC(vrc, ("%Rra", vrc))
+
+/**
+ * Special version of the AssertMsgRC macro to be used within VirtualBoxBase
+ * subclasses.
+ *
+ * See ComAssert for more info.
+ *
+ * @param vrc VBox status code.
+ * @param msg printf argument list (in parenthesis).
+ */
+#define ComAssertMsgRC(vrc, msg) ComAssertMsg(RT_SUCCESS(vrc), msg)
+
+/**
+ * Special version of the AssertComRC macro to be used within VirtualBoxBase
+ * subclasses.
+ *
+ * See ComAssert for more info.
+ *
+ * @param hrc COM result code
+ */
+#define ComAssertComRC(hrc) ComAssertMsg(SUCCEEDED(hrc), ("COM RC=%Rhrc (0x%08X)", (hrc), (hrc)))
+
+
+/** Special version of ComAssert that returns ret if expr fails */
+#define ComAssertRet(expr, ret) \
+ do { ComAssert(expr); if (!(expr)) return (ret); } while (0)
+/** Special version of ComAssertMsg that returns ret if expr fails */
+#define ComAssertMsgRet(expr, a, ret) \
+ do { ComAssertMsg(expr, a); if (!(expr)) return (ret); } while (0)
+/** Special version of ComAssertRC that returns ret if vrc does not succeed */
+#define ComAssertRCRet(vrc, ret) \
+ do { ComAssertRC(vrc); if (!RT_SUCCESS(vrc)) return (ret); } while (0)
+/** Special version of ComAssertComRC that returns ret if rc does not succeed */
+#define ComAssertComRCRet(rc, ret) \
+ do { ComAssertComRC(rc); if (!SUCCEEDED(rc)) return (ret); } while (0)
+/** Special version of ComAssertComRC that returns rc if rc does not succeed */
+#define ComAssertComRCRetRC(rc) \
+ do { ComAssertComRC(rc); if (!SUCCEEDED(rc)) return (rc); } while (0)
+/** Special version of ComAssertFailed that returns ret */
+#define ComAssertFailedRet(ret) \
+ do { ComAssertFailed(); return (ret); } while (0)
+/** Special version of ComAssertMsgFailed that returns ret */
+#define ComAssertMsgFailedRet(msg, ret) \
+ do { ComAssertMsgFailed(msg); return (ret); } while (0)
+
+
+/** Special version of ComAssert that returns void if expr fails */
+#define ComAssertRetVoid(expr) \
+ do { ComAssert(expr); if (!(expr)) return; } while (0)
+/** Special version of ComAssertMsg that returns void if expr fails */
+#define ComAssertMsgRetVoid(expr, a) \
+ do { ComAssertMsg(expr, a); if (!(expr)) return; } while (0)
+/** Special version of ComAssertRC that returns void if vrc does not succeed */
+#define ComAssertRCRetVoid(vrc) \
+ do { ComAssertRC(vrc); if (!RT_SUCCESS(vrc)) return; } while (0)
+/** Special version of ComAssertComRC that returns void if rc does not succeed */
+#define ComAssertComRCRetVoid(rc) \
+ do { ComAssertComRC(rc); if (!SUCCEEDED(rc)) return; } while (0)
+/** Special version of ComAssertFailed that returns void */
+#define ComAssertFailedRetVoid() \
+ do { ComAssertFailed(); return; } while (0)
+/** Special version of ComAssertMsgFailed that returns void */
+#define ComAssertMsgFailedRetVoid(msg) \
+ do { ComAssertMsgFailed(msg); return; } while (0)
+
+
+/** Special version of ComAssert that evaluates eval and breaks if expr fails */
+#define ComAssertBreak(expr, eval) \
+ if (1) { ComAssert(expr); if (!(expr)) { eval; break; } } else do {} while (0)
+/** Special version of ComAssertMsg that evaluates eval and breaks if expr fails */
+#define ComAssertMsgBreak(expr, a, eval) \
+ if (1) { ComAssertMsg(expr, a); if (!(expr)) { eval; break; } } else do {} while (0)
+/** Special version of ComAssertRC that evaluates eval and breaks if vrc does not succeed */
+#define ComAssertRCBreak(vrc, eval) \
+ if (1) { ComAssertRC(vrc); if (!RT_SUCCESS(vrc)) { eval; break; } } else do {} while (0)
+/** Special version of ComAssertFailed that evaluates eval and breaks */
+#define ComAssertFailedBreak(eval) \
+ if (1) { ComAssertFailed(); { eval; break; } } else do {} while (0)
+/** Special version of ComAssertMsgFailed that evaluates eval and breaks */
+#define ComAssertMsgFailedBreak(msg, eval) \
+ if (1) { ComAssertMsgFailed (msg); { eval; break; } } else do {} while (0)
+/** Special version of ComAssertComRC that evaluates eval and breaks if rc does not succeed */
+#define ComAssertComRCBreak(rc, eval) \
+ if (1) { ComAssertComRC(rc); if (!SUCCEEDED(rc)) { eval; break; } } else do {} while (0)
+/** Special version of ComAssertComRC that just breaks if rc does not succeed */
+#define ComAssertComRCBreakRC(rc) \
+ if (1) { ComAssertComRC(rc); if (!SUCCEEDED(rc)) { break; } } else do {} while (0)
+
+
+/** Special version of ComAssert that evaluates eval and throws it if expr fails */
+#define ComAssertThrow(expr, eval) \
+ do { ComAssert(expr); if (!(expr)) { throw (eval); } } while (0)
+/** Special version of ComAssertRC that evaluates eval and throws it if vrc does not succeed */
+#define ComAssertRCThrow(vrc, eval) \
+ do { ComAssertRC(vrc); if (!RT_SUCCESS(vrc)) { throw (eval); } } while (0)
+/** Special version of ComAssertComRC that evaluates eval and throws it if rc does not succeed */
+#define ComAssertComRCThrow(rc, eval) \
+ do { ComAssertComRC(rc); if (!SUCCEEDED(rc)) { throw (eval); } } while (0)
+/** Special version of ComAssertComRC that just throws rc if rc does not succeed */
+#define ComAssertComRCThrowRC(rc) \
+ do { ComAssertComRC(rc); if (!SUCCEEDED(rc)) { throw rc; } } while (0)
+/** Special version of ComAssert that throws eval */
+#define ComAssertFailedThrow(eval) \
+ do { ComAssertFailed(); { throw (eval); } } while (0)
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Checks that the pointer argument is not NULL and returns E_INVALIDARG +
+ * extended error info on failure.
+ * @param arg Input pointer-type argument (strings, interface pointers...)
+ */
+#define CheckComArgNotNull(arg) \
+ do { \
+ if (RT_LIKELY((arg) != NULL)) \
+ { /* likely */ }\
+ else \
+ return setError(E_INVALIDARG, VirtualBoxBase::tr("Argument %s is NULL"), #arg); \
+ } while (0)
+
+/**
+ * Checks that the pointer argument is a valid pointer or NULL and returns
+ * E_INVALIDARG + extended error info on failure.
+ * @param arg Input pointer-type argument (strings, interface pointers...)
+ */
+#define CheckComArgMaybeNull(arg) \
+ do { \
+ if (RT_LIKELY(RT_VALID_PTR(arg) || (arg) == NULL)) \
+ { /* likely */ }\
+ else \
+ return setError(E_INVALIDARG, \
+ VirtualBoxBase::tr("Argument %s is an invalid pointer"), #arg); \
+ } while (0)
+
+/**
+ * Checks that the given pointer to an argument is valid and returns
+ * E_POINTER + extended error info otherwise.
+ * @param arg Pointer argument.
+ */
+#define CheckComArgPointerValid(arg) \
+ do { \
+ if (RT_LIKELY(RT_VALID_PTR(arg))) \
+ { /* likely */ }\
+ else \
+ return setError(E_POINTER, \
+ VirtualBoxBase::tr("Argument %s points to invalid memory location (%p)"), \
+ #arg, (void *)(arg)); \
+ } while (0)
+
+/**
+ * Checks that safe array argument is not NULL and returns E_INVALIDARG +
+ * extended error info on failure.
+ * @param arg Input safe array argument (strings, interface pointers...)
+ */
+#define CheckComArgSafeArrayNotNull(arg) \
+ do { \
+ if (RT_LIKELY(!ComSafeArrayInIsNull(arg))) \
+ { /* likely */ }\
+ else \
+ return setError(E_INVALIDARG, \
+ VirtualBoxBase::tr("Argument %s is NULL"), #arg); \
+ } while (0)
+
+/**
+ * Checks that a string input argument is valid (not NULL or obviously invalid
+ * pointer), returning E_INVALIDARG + extended error info if invalid.
+ * @param a_bstrIn Input string argument (IN_BSTR).
+ */
+#define CheckComArgStr(a_bstrIn) \
+ do { \
+ IN_BSTR const bstrInCheck = (a_bstrIn); /* type check */ \
+ if (RT_LIKELY(RT_VALID_PTR(bstrInCheck))) \
+ { /* likely */ }\
+ else \
+ return setError(E_INVALIDARG, \
+ VirtualBoxBase::tr("Argument %s is an invalid pointer"), #a_bstrIn); \
+ } while (0)
+/**
+ * Checks that the string argument is not a NULL, a invalid pointer or an empty
+ * string, returning E_INVALIDARG + extended error info on failure.
+ * @param a_bstrIn Input string argument (BSTR etc.).
+ */
+#define CheckComArgStrNotEmptyOrNull(a_bstrIn) \
+ do { \
+ IN_BSTR const bstrInCheck = (a_bstrIn); /* type check */ \
+ if (RT_LIKELY(RT_VALID_PTR(bstrInCheck) && *(bstrInCheck) != '\0')) \
+ { /* likely */ }\
+ else \
+ return setError(E_INVALIDARG, \
+ VirtualBoxBase::tr("Argument %s is empty or an invalid pointer"), \
+ #a_bstrIn); \
+ } while (0)
+
+/**
+ * Converts the Guid input argument (string) to a Guid object, returns with
+ * E_INVALIDARG and error message on failure.
+ *
+ * @param a_Arg Argument.
+ * @param a_GuidVar The Guid variable name.
+ */
+#define CheckComArgGuid(a_Arg, a_GuidVar) \
+ do { \
+ Guid tmpGuid(a_Arg); \
+ (a_GuidVar) = tmpGuid; \
+ if (RT_LIKELY((a_GuidVar).isValid())) \
+ { /* likely */ }\
+ else \
+ return setError(E_INVALIDARG, \
+ VirtualBoxBase::tr("GUID argument %s is not valid (\"%ls\")"), \
+ #a_Arg, Bstr(a_Arg).raw()); \
+ } while (0)
+
+/**
+ * Checks that the given expression (that must involve the argument) is true and
+ * returns E_INVALIDARG + extended error info on failure.
+ * @param arg Argument.
+ * @param expr Expression to evaluate.
+ */
+#define CheckComArgExpr(arg, expr) \
+ do { \
+ if (RT_LIKELY(!!(expr))) \
+ { /* likely */ }\
+ else \
+ return setError(E_INVALIDARG, \
+ VirtualBoxBase::tr("Argument %s is invalid (must be %s)"), \
+ #arg, #expr); \
+ } while (0)
+
+/**
+ * Checks that the given expression (that must involve the argument) is true and
+ * returns E_INVALIDARG + extended error info on failure. The error message must
+ * be customized.
+ * @param arg Argument.
+ * @param expr Expression to evaluate.
+ * @param msg Parenthesized printf-like expression (must start with a verb,
+ * like "must be one of...", "is not within...").
+ */
+#define CheckComArgExprMsg(arg, expr, msg) \
+ do { \
+ if (RT_LIKELY(!!(expr))) \
+ { /* likely */ }\
+ else \
+ return setError(E_INVALIDARG, VirtualBoxBase::tr("Argument %s %s"), \
+ #arg, Utf8StrFmt msg .c_str()); \
+ } while (0)
+
+/**
+ * Checks that the given pointer to an output argument is valid and returns
+ * E_POINTER + extended error info otherwise.
+ * @param arg Pointer argument.
+ */
+#define CheckComArgOutPointerValid(arg) \
+ do { \
+ if (RT_LIKELY(RT_VALID_PTR(arg))) \
+ { /* likely */ }\
+ else \
+ return setError(E_POINTER, \
+ VirtualBoxBase::tr("Output argument %s points to invalid memory location (%p)"), \
+ #arg, (void *)(arg)); \
+ } while (0)
+
+/**
+ * Checks that the given pointer to an output safe array argument is valid and
+ * returns E_POINTER + extended error info otherwise.
+ * @param arg Safe array argument.
+ */
+#define CheckComArgOutSafeArrayPointerValid(arg) \
+ do { \
+ if (RT_LIKELY(!ComSafeArrayOutIsNull(arg))) \
+ { /* likely */ }\
+ else \
+ return setError(E_POINTER, \
+ VirtualBoxBase::tr("Output argument %s points to invalid memory location (%p)"), \
+ #arg, (void*)(arg)); \
+ } while (0)
+
+/**
+ * Sets the extended error info and returns E_NOTIMPL.
+ */
+#define ReturnComNotImplemented() \
+ do { \
+ return setError(E_NOTIMPL, VirtualBoxBase::tr("Method %s is not implemented"), __FUNCTION__); \
+ } while (0)
+
+/**
+ * Declares an empty constructor and destructor for the given class.
+ * This is useful to prevent the compiler from generating the default
+ * ctor and dtor, which in turn allows to use forward class statements
+ * (instead of including their header files) when declaring data members of
+ * non-fundamental types with constructors (which are always called implicitly
+ * by constructors and by the destructor of the class).
+ *
+ * This macro is to be placed within (the public section of) the class
+ * declaration. Its counterpart, DEFINE_EMPTY_CTOR_DTOR, must be placed
+ * somewhere in one of the translation units (usually .cpp source files).
+ *
+ * @param cls class to declare a ctor and dtor for
+ */
+#define DECLARE_EMPTY_CTOR_DTOR(cls) cls(); virtual ~cls();
+
+/**
+ * Defines an empty constructor and destructor for the given class.
+ * See DECLARE_EMPTY_CTOR_DTOR for more info.
+ */
+#define DEFINE_EMPTY_CTOR_DTOR(cls) \
+ cls::cls() { /*empty*/ } \
+ cls::~cls() { /*empty*/ }
+
+/**
+ * A variant of 'throw' that hits a debug breakpoint first to make
+ * finding the actual thrower possible.
+ */
+#ifdef DEBUG
+# define DebugBreakThrow(a) \
+ do { \
+ RTAssertDebugBreak(); \
+ throw (a); \
+ } while (0)
+#else
+# define DebugBreakThrow(a) throw (a)
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// VirtualBoxBase
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifdef VBOX_WITH_MAIN_NLS
+# define DECLARE_TRANSLATE_METHODS(cls) \
+ static inline const char *tr(const char *aSourceText, \
+ const char *aComment = NULL, \
+ const size_t aNum = ~(size_t)0) \
+ { \
+ return VirtualBoxTranslator::translate(NULL, #cls, aSourceText, aComment, aNum); \
+ }
+#else
+# define DECLARE_TRANSLATE_METHODS(cls) \
+ static inline const char *tr(const char *aSourceText, \
+ const char *aComment = NULL, \
+ const size_t aNum = ~(size_t)0) \
+ { \
+ RT_NOREF(aComment, aNum); \
+ return aSourceText; \
+ }
+#endif
+
+#define DECLARE_COMMON_CLASS_METHODS(cls) \
+ DECLARE_EMPTY_CTOR_DTOR(cls) \
+ DECLARE_TRANSLATE_METHODS(cls)
+
+#define VIRTUALBOXBASE_ADD_VIRTUAL_COMPONENT_METHODS(cls, iface) \
+ virtual const IID& getClassIID() const \
+ { \
+ return cls::getStaticClassIID(); \
+ } \
+ static const IID& getStaticClassIID() \
+ { \
+ return COM_IIDOF(iface); \
+ } \
+ virtual const char* getComponentName() const \
+ { \
+ return cls::getStaticComponentName(); \
+ } \
+ static const char* getStaticComponentName() \
+ { \
+ return #cls; \
+ }
+
+/**
+ * VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT:
+ * This macro must be used once in the declaration of any class derived
+ * from VirtualBoxBase. It implements the pure virtual getClassIID() and
+ * getComponentName() methods. If this macro is not present, instances
+ * of a class derived from VirtualBoxBase cannot be instantiated.
+ *
+ * @param cls The class name, e.g. "Class".
+ * @param iface The interface name which this class implements, e.g. "IClass".
+ */
+#ifdef VBOX_WITH_XPCOM
+ #define VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(cls, iface) \
+ VIRTUALBOXBASE_ADD_VIRTUAL_COMPONENT_METHODS(cls, iface)
+#else // !VBOX_WITH_XPCOM
+ #define VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(cls, iface) \
+ VIRTUALBOXBASE_ADD_VIRTUAL_COMPONENT_METHODS(cls, iface) \
+ STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid) \
+ { \
+ const ATL::_ATL_INTMAP_ENTRY* pEntries = cls::_GetEntries(); \
+ Assert(pEntries); \
+ if (!pEntries) \
+ return S_FALSE; \
+ BOOL bSupports = FALSE; \
+ BOOL bISupportErrorInfoFound = FALSE; \
+ while (pEntries->pFunc != NULL && !bSupports) \
+ { \
+ if (!bISupportErrorInfoFound) \
+ bISupportErrorInfoFound = InlineIsEqualGUID(*(pEntries->piid), IID_ISupportErrorInfo); \
+ else \
+ bSupports = InlineIsEqualGUID(*(pEntries->piid), riid); \
+ pEntries++; \
+ } \
+ Assert(bISupportErrorInfoFound); \
+ return bSupports ? S_OK : S_FALSE; \
+ }
+#endif // !VBOX_WITH_XPCOM
+
+/**
+ * VBOX_TWEAK_INTERFACE_ENTRY:
+ * Macro for defining magic interface entries needed for all interfaces
+ * implemented by any subclass of VirtualBoxBase.
+ */
+#ifdef VBOX_WITH_XPCOM
+#define VBOX_TWEAK_INTERFACE_ENTRY(iface)
+#else // !VBOX_WITH_XPCOM
+#define VBOX_TWEAK_INTERFACE_ENTRY(iface) \
+ COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pUnkMarshaler.m_p)
+#endif // !VBOX_WITH_XPCOM
+
+
+/**
+ * Abstract base class for all component classes implementing COM
+ * interfaces of the VirtualBox COM library.
+ *
+ * Declares functionality that should be available in all components.
+ *
+ * The object state logic is documented in ObjectState.h.
+ */
+class ATL_NO_VTABLE VirtualBoxBase
+ : public Lockable
+ , public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>
+#if !defined (VBOX_WITH_XPCOM)
+ , public ISupportErrorInfo
+#endif
+{
+protected:
+#ifdef RT_OS_WINDOWS
+ ComPtr<IUnknown> m_pUnkMarshaler;
+#endif
+
+ HRESULT BaseFinalConstruct();
+ void BaseFinalRelease();
+
+public:
+ DECLARE_COMMON_CLASS_METHODS(VirtualBoxBase)
+
+ /**
+ * Uninitialization method.
+ *
+ * Must be called by all final implementations (component classes) when the
+ * last reference to the object is released, before calling the destructor.
+ *
+ * @note Never call this method the AutoCaller scope or after the
+ * ObjectState::addCaller() call not paired by
+ * ObjectState::releaseCaller() because it is a guaranteed deadlock.
+ * See AutoUninitSpan and AutoCaller.h/ObjectState.h for details.
+ */
+ virtual void uninit()
+ { }
+
+ /**
+ */
+ ObjectState &getObjectState()
+ {
+ return mState;
+ }
+
+ /**
+ * Pure virtual method for simple run-time type identification without
+ * having to enable C++ RTTI.
+ *
+ * This *must* be implemented by every subclass deriving from VirtualBoxBase;
+ * use the VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT macro to do that most easily.
+ */
+ virtual const IID& getClassIID() const = 0;
+
+ /**
+ * Pure virtual method for simple run-time type identification without
+ * having to enable C++ RTTI.
+ *
+ * This *must* be implemented by every subclass deriving from VirtualBoxBase;
+ * use the VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT macro to do that most easily.
+ */
+ virtual const char* getComponentName() const = 0;
+
+ /**
+ * Virtual method which determines the locking class to be used for validating
+ * lock order with the standard member lock handle. This method is overridden
+ * in a number of subclasses.
+ */
+ virtual VBoxLockingClass getLockingClass() const
+ {
+ return LOCKCLASS_OTHEROBJECT;
+ }
+
+ virtual RWLockHandle *lockHandle() const;
+
+ static HRESULT handleUnexpectedExceptions(VirtualBoxBase *const aThis, RT_SRC_POS_DECL);
+
+ static HRESULT setErrorInternalF(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *aComponent,
+ bool aWarning,
+ bool aLogIt,
+ LONG aResultDetail,
+ const char *aText, ...);
+ static HRESULT setErrorInternalV(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *aComponent,
+ const char *aText,
+ va_list aArgs,
+ bool aWarning,
+ bool aLogIt,
+ LONG aResultDetail = 0);
+ static void clearError(void);
+
+ HRESULT setError(HRESULT aResultCode);
+ HRESULT setError(HRESULT aResultCode, const char *pcsz, ...);
+ HRESULT setError(const ErrorInfo &ei);
+ HRESULT setErrorVrcV(int vrc, const char *pcszMsgFmt, va_list va_args);
+ HRESULT setErrorVrc(int vrc);
+ HRESULT setErrorVrc(int vrc, const char *pcszMsgFmt, ...);
+ HRESULT setErrorBoth(HRESULT hrc, int vrc);
+ HRESULT setErrorBoth(HRESULT hrc, int vrc, const char *pcszMsgFmt, ...);
+ HRESULT setWarning(HRESULT aResultCode, const char *pcsz, ...);
+ HRESULT setErrorNoLog(HRESULT aResultCode, const char *pcsz, ...);
+
+
+ /** Initialize COM for a new thread. */
+ static HRESULT initializeComForThread(void)
+ {
+#ifndef VBOX_WITH_XPCOM
+ HRESULT hrc = CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE | COINIT_SPEED_OVER_MEMORY);
+ AssertComRCReturn(hrc, hrc);
+#endif
+ return S_OK;
+ }
+
+ /** Uninitializes COM for a dying thread. */
+ static void uninitializeComForThread(void)
+ {
+#ifndef VBOX_WITH_XPCOM
+ CoUninitialize();
+#endif
+ }
+
+
+private:
+ /** Object for representing object state */
+ ObjectState mState;
+
+ /** User-level object lock for subclasses */
+ RWLockHandle *mObjectLock;
+
+ /** Slot of this object in the g_aClassFactoryStats array */
+ uint32_t iFactoryStat;
+
+private:
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(VirtualBoxBase); /* Shuts up MSC warning C4625. */
+};
+
+/** Structure for counting the currently existing and ever created objects
+ * for each component name. */
+typedef struct CLASSFACTORY_STAT
+{
+ const char *psz;
+ uint64_t current;
+ uint64_t overall;
+} CLASSFACTORY_STAT;
+
+/** Maximum number of component names to deal with. There will be debug
+ * assertions if the value is too low. Since the table is global and its
+ * entries are reasonably small, it's not worth squeezing out the last bit. */
+#define CLASSFACTORYSTATS_MAX 128
+
+/* global variables (defined in VirtualBoxBase.cpp) */
+extern CLASSFACTORY_STAT g_aClassFactoryStats[CLASSFACTORYSTATS_MAX];
+extern RWLockHandle *g_pClassFactoryStatsLock;
+
+extern void APIDumpComponentFactoryStats();
+
+/**
+ * Dummy macro that is used to shut down Qt's lupdate tool warnings in some
+ * situations. This macro needs to be present inside (better at the very
+ * beginning) of the declaration of the class that uses translation, to make
+ * lupdate happy.
+ */
+#define Q_OBJECT
+
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * Simple template that manages data structure allocation/deallocation
+ * and supports data pointer sharing (the instance that shares the pointer is
+ * not responsible for memory deallocation as opposed to the instance that
+ * owns it).
+ */
+template <class D>
+class Shareable
+{
+public:
+
+ Shareable() : mData(NULL), mIsShared(FALSE) {}
+ virtual ~Shareable() { free(); }
+
+ void allocate() { attach(new D); }
+
+ virtual void free() {
+ if (mData) {
+ if (!mIsShared)
+ delete mData;
+ mData = NULL;
+ mIsShared = false;
+ }
+ }
+
+ void attach(D *d) {
+ AssertMsg(d, ("new data must not be NULL"));
+ if (d && mData != d) {
+ if (mData && !mIsShared)
+ delete mData;
+ mData = d;
+ mIsShared = false;
+ }
+ }
+
+ void attach(Shareable &d) {
+ AssertMsg(
+ d.mData == mData || !d.mIsShared,
+ ("new data must not be shared")
+ );
+ if (this != &d && !d.mIsShared) {
+ attach(d.mData);
+ d.mIsShared = true;
+ }
+ }
+
+ void share(D *d) {
+ AssertMsg(d, ("new data must not be NULL"));
+ if (mData != d) {
+ if (mData && !mIsShared)
+ delete mData;
+ mData = d;
+ mIsShared = true;
+ }
+ }
+
+ void share(const Shareable &d) { share(d.mData); }
+
+ void attachCopy(const D *d) {
+ AssertMsg(d, ("data to copy must not be NULL"));
+ if (d)
+ attach(new D(*d));
+ }
+
+ void attachCopy(const Shareable &d) {
+ attachCopy(d.mData);
+ }
+
+ virtual D *detach() {
+ D *d = mData;
+ mData = NULL;
+ mIsShared = false;
+ return d;
+ }
+
+ D *data() const {
+ return mData;
+ }
+
+ D *operator->() const {
+ AssertMsg(mData, ("data must not be NULL"));
+ return mData;
+ }
+
+ bool isNull() const { return mData == NULL; }
+ bool operator!() const { return isNull(); }
+
+ bool isShared() const { return mIsShared; }
+
+protected:
+
+ D *mData;
+ bool mIsShared;
+};
+
+/**
+ * Simple template that enhances Shareable<> and supports data
+ * backup/rollback/commit (using the copy constructor of the managed data
+ * structure).
+ */
+template<class D>
+class Backupable : public Shareable<D>
+{
+public:
+
+ Backupable() : Shareable<D>(), mBackupData(NULL) {}
+
+ void free()
+ {
+ AssertMsg(this->mData || !mBackupData, ("backup must be NULL if data is NULL"));
+ rollback();
+ Shareable<D>::free();
+ }
+
+ D *detach()
+ {
+ AssertMsg(this->mData || !mBackupData, ("backup must be NULL if data is NULL"));
+ rollback();
+ return Shareable<D>::detach();
+ }
+
+ void share(const Backupable &d)
+ {
+ AssertMsg(!d.isBackedUp(), ("data to share must not be backed up"));
+ if (!d.isBackedUp())
+ Shareable<D>::share(d.mData);
+ }
+
+ /**
+ * Stores the current data pointer in the backup area, allocates new data
+ * using the copy constructor on current data and makes new data active.
+ *
+ * @deprecated Use backupEx to avoid throwing wild out-of-memory exceptions.
+ */
+ void backup()
+ {
+ AssertMsg(this->mData, ("data must not be NULL"));
+ if (this->mData && !mBackupData)
+ {
+ D *pNewData = new D(*this->mData);
+ mBackupData = this->mData;
+ this->mData = pNewData;
+ }
+ }
+
+ /**
+ * Stores the current data pointer in the backup area, allocates new data
+ * using the copy constructor on current data and makes new data active.
+ *
+ * @returns S_OK, E_OUTOFMEMORY or E_FAIL (internal error).
+ */
+ HRESULT backupEx()
+ {
+ AssertMsgReturn(this->mData, ("data must not be NULL"), E_FAIL);
+ if (this->mData && !mBackupData)
+ {
+ try
+ {
+ D *pNewData = new D(*this->mData);
+ mBackupData = this->mData;
+ this->mData = pNewData;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+ return S_OK;
+ }
+
+ /**
+ * Deletes new data created by #backup() and restores previous data pointer
+ * stored in the backup area, making it active again.
+ */
+ void rollback()
+ {
+ if (this->mData && mBackupData)
+ {
+ delete this->mData;
+ this->mData = mBackupData;
+ mBackupData = NULL;
+ }
+ }
+
+ /**
+ * Commits current changes by deleting backed up data and clearing up the
+ * backup area. The new data pointer created by #backup() remains active
+ * and becomes the only managed pointer.
+ *
+ * This method is much faster than #commitCopy() (just a single pointer
+ * assignment operation), but makes the previous data pointer invalid
+ * (because it is freed). For this reason, this method must not be
+ * used if it's possible that data managed by this instance is shared with
+ * some other Shareable instance. See #commitCopy().
+ */
+ void commit()
+ {
+ if (this->mData && mBackupData)
+ {
+ if (!this->mIsShared)
+ delete mBackupData;
+ mBackupData = NULL;
+ this->mIsShared = false;
+ }
+ }
+
+ /**
+ * Commits current changes by assigning new data to the previous data
+ * pointer stored in the backup area using the assignment operator.
+ * New data is deleted, the backup area is cleared and the previous data
+ * pointer becomes active and the only managed pointer.
+ *
+ * This method is slower than #commit(), but it keeps the previous data
+ * pointer valid (i.e. new data is copied to the same memory location).
+ * For that reason it's safe to use this method on instances that share
+ * managed data with other Shareable instances.
+ */
+ void commitCopy()
+ {
+ if (this->mData && mBackupData)
+ {
+ *mBackupData = *(this->mData);
+ delete this->mData;
+ this->mData = mBackupData;
+ mBackupData = NULL;
+ }
+ }
+
+ void assignCopy(const D *pData)
+ {
+ AssertMsg(this->mData, ("data must not be NULL"));
+ AssertMsg(pData, ("data to copy must not be NULL"));
+ if (this->mData && pData)
+ {
+ if (!mBackupData)
+ {
+ D *pNewData = new D(*pData);
+ mBackupData = this->mData;
+ this->mData = pNewData;
+ }
+ else
+ *this->mData = *pData;
+ }
+ }
+
+ void assignCopy(const Backupable &d)
+ {
+ assignCopy(d.mData);
+ }
+
+ bool isBackedUp() const
+ {
+ return mBackupData != NULL;
+ }
+
+ D *backedUpData() const
+ {
+ return mBackupData;
+ }
+
+protected:
+
+ D *mBackupData;
+};
+
+#endif /* !MAIN_INCLUDED_VirtualBoxBase_h */
+
diff --git a/src/VBox/Main/include/VirtualBoxClientImpl.h b/src/VBox/Main/include/VirtualBoxClientImpl.h
new file mode 100644
index 00000000..ad39811c
--- /dev/null
+++ b/src/VBox/Main/include/VirtualBoxClientImpl.h
@@ -0,0 +1,144 @@
+/* $Id: VirtualBoxClientImpl.h $ */
+/** @file
+ * Header file for the VirtualBoxClient (IVirtualBoxClient) class, VBoxC.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_VirtualBoxClientImpl_h
+#define MAIN_INCLUDED_VirtualBoxClientImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VirtualBoxClientWrap.h"
+#include "EventImpl.h"
+#include "VirtualBoxTranslator.h"
+
+#ifdef RT_OS_WINDOWS
+# include "win/resource.h"
+#endif
+
+class ATL_NO_VTABLE VirtualBoxClient :
+ public VirtualBoxClientWrap
+#ifdef RT_OS_WINDOWS
+ , public ATL::CComCoClass<VirtualBoxClient, &CLSID_VirtualBoxClient>
+#endif
+{
+public:
+ DECLARE_CLASSFACTORY_SINGLETON(VirtualBoxClient)
+
+ // Do not use any ATL registry support.
+ //DECLARE_REGISTRY_RESOURCEID(IDR_VIRTUALBOX)
+
+ DECLARE_NOT_AGGREGATABLE(VirtualBoxClient)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init();
+ void uninit();
+
+#ifdef RT_OS_WINDOWS
+ /* HACK ALERT! Implemented in dllmain.cpp. */
+ ULONG InternalRelease();
+#endif
+
+private:
+ // wrapped IVirtualBoxClient properties
+ virtual HRESULT getVirtualBox(ComPtr<IVirtualBox> &aVirtualBox);
+ virtual HRESULT getSession(ComPtr<ISession> &aSession);
+ virtual HRESULT getEventSource(ComPtr<IEventSource> &aEventSource);
+
+ // wrapped IVirtualBoxClient methods
+ virtual HRESULT checkMachineError(const ComPtr<IMachine> &aMachine);
+
+ /** Instance counter for simulating something similar to a singleton.
+ * Only the first instance will be a usable object, all additional
+ * instances will return a failure at creation time and will not work. */
+ static uint32_t g_cInstances;
+
+#ifdef RT_OS_WINDOWS
+ virtual HRESULT i_investigateVirtualBoxObjectCreationFailure(HRESULT hrc);
+#endif
+
+#ifdef VBOX_WITH_SDS
+ int i_getServiceAccountAndStartType(const wchar_t *pwszServiceName,
+ wchar_t *pwszAccountName, size_t cwcAccountName, uint32_t *puStartType);
+#endif
+
+ static DECLCALLBACK(int) SVCWatcherThread(RTTHREAD ThreadSelf, void *pvUser);
+
+ struct Data
+ {
+ Data()
+ : m_ThreadWatcher(NIL_RTTHREAD)
+ , m_SemEvWatcher(NIL_RTSEMEVENT)
+#ifdef VBOX_WITH_MAIN_NLS
+ , m_pVBoxTranslator(NULL)
+ , m_pTrComponent(NULL)
+#endif
+ {}
+
+ ~Data()
+ {
+ /* HACK ALERT! This is for DllCanUnloadNow(). */
+ if (m_pEventSource.isNotNull())
+ {
+ s_cUnnecessaryAtlModuleLocks--;
+ AssertMsg(s_cUnnecessaryAtlModuleLocks == 0, ("%d\n", s_cUnnecessaryAtlModuleLocks));
+ }
+ }
+
+ ComPtr<IVirtualBox> m_pVirtualBox;
+ ComPtr<IToken> m_pToken;
+ const ComObjPtr<EventSource> m_pEventSource;
+ ComPtr<IEventSource> m_pVBoxEventSource;
+ ComPtr<IEventListener> m_pVBoxEventListener;
+
+ RTTHREAD m_ThreadWatcher;
+ RTSEMEVENT m_SemEvWatcher;
+#ifdef VBOX_WITH_MAIN_NLS
+ VirtualBoxTranslator *m_pVBoxTranslator;
+ PTRCOMPONENT m_pTrComponent;
+#endif
+ };
+
+ Data mData;
+
+public:
+ /** Hack for discounting the AtlModule lock held by Data::m_pEventSource during
+ * DllCanUnloadNow(). This is incremented to 1 when init() initialized
+ * m_pEventSource and is decremented by the Data destructor (above). */
+ static LONG s_cUnnecessaryAtlModuleLocks;
+
+#ifdef VBOX_WITH_MAIN_NLS
+ HRESULT i_reloadApiLanguage();
+ HRESULT i_registerEventListener();
+ void i_unregisterEventListener();
+#endif
+};
+
+#endif /* !MAIN_INCLUDED_VirtualBoxClientImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/VirtualBoxErrorInfoImpl.h b/src/VBox/Main/include/VirtualBoxErrorInfoImpl.h
new file mode 100644
index 00000000..62f98436
--- /dev/null
+++ b/src/VBox/Main/include/VirtualBoxErrorInfoImpl.h
@@ -0,0 +1,175 @@
+/* $Id: VirtualBoxErrorInfoImpl.h $ */
+/** @file
+ * VirtualBoxErrorInfo COM class definition.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_VirtualBoxErrorInfoImpl_h
+#define MAIN_INCLUDED_VirtualBoxErrorInfoImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VirtualBoxBase.h"
+
+using namespace com;
+
+class ATL_NO_VTABLE VirtualBoxErrorInfo
+ : public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>
+ , VBOX_SCRIPTABLE_IMPL(IVirtualBoxErrorInfo)
+#ifndef VBOX_WITH_XPCOM /* IErrorInfo doesn't inherit from IDispatch, ugly 3am hack: */
+ , public IDispatch
+#endif
+{
+public:
+
+ DECLARE_NOT_AGGREGATABLE(VirtualBoxErrorInfo)
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ BEGIN_COM_MAP(VirtualBoxErrorInfo)
+ COM_INTERFACE_ENTRY(IErrorInfo)
+ COM_INTERFACE_ENTRY(IVirtualBoxErrorInfo)
+ COM_INTERFACE_ENTRY(IDispatch)
+ COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pUnkMarshaler)
+ END_COM_MAP()
+
+ DECLARE_TRANSLATE_METHODS(VirtualBoxErrorInfo)
+
+ HRESULT FinalConstruct()
+ {
+#ifndef VBOX_WITH_XPCOM
+ return CoCreateFreeThreadedMarshaler((IUnknown *)(void *)this, &m_pUnkMarshaler);
+#else
+ return S_OK;
+#endif
+ }
+
+ void FinalRelease()
+ {
+#ifndef VBOX_WITH_XPCOM
+ if (m_pUnkMarshaler)
+ {
+ m_pUnkMarshaler->Release();
+ m_pUnkMarshaler = NULL;
+ }
+#endif
+ }
+
+#ifndef VBOX_WITH_XPCOM
+
+ HRESULT init(IErrorInfo *aInfo);
+
+ STDMETHOD(GetGUID)(GUID *guid);
+ STDMETHOD(GetSource)(BSTR *pBstrSource);
+ STDMETHOD(GetDescription)(BSTR *description);
+ STDMETHOD(GetHelpFile)(BSTR *pBstrHelpFile);
+ STDMETHOD(GetHelpContext)(DWORD *pdwHelpContext);
+
+ // IDispatch forwarding - 3am hack.
+ typedef IDispatchImpl<IVirtualBoxErrorInfo, &IID_IVirtualBoxErrorInfo, &LIBID_VirtualBox, kTypeLibraryMajorVersion, kTypeLibraryMinorVersion> idi;
+
+ STDMETHOD(GetTypeInfoCount)(UINT *pcInfo)
+ {
+ return idi::GetTypeInfoCount(pcInfo);
+ }
+
+ STDMETHOD(GetTypeInfo)(UINT iInfo, LCID Lcid, ITypeInfo **ppTypeInfo)
+ {
+ return idi::GetTypeInfo(iInfo, Lcid, ppTypeInfo);
+ }
+
+ STDMETHOD(GetIDsOfNames)(REFIID rIID, LPOLESTR *papwszNames, UINT cNames, LCID Lcid, DISPID *paDispIDs)
+ {
+ return idi::GetIDsOfNames(rIID, papwszNames, cNames, Lcid, paDispIDs);
+ }
+
+ STDMETHOD(Invoke)(DISPID idDispMember, REFIID rIID, LCID Lcid, WORD fw, DISPPARAMS *pDispParams,
+ VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *piErrArg)
+ {
+ return idi::Invoke(idDispMember, rIID, Lcid, fw, pDispParams, pVarResult, pExcepInfo, piErrArg);
+ }
+
+#else // defined(VBOX_WITH_XPCOM)
+
+ HRESULT init(nsIException *aInfo);
+
+ NS_DECL_NSIEXCEPTION
+
+#endif
+
+ VirtualBoxErrorInfo()
+ : m_resultCode(S_OK),
+ m_resultDetail(0)
+ {}
+ virtual ~VirtualBoxErrorInfo() {}
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const Utf8Str &strText,
+ IVirtualBoxErrorInfo *aNext = NULL);
+
+ HRESULT initEx(HRESULT aResultCode,
+ LONG aResultDetail,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const Utf8Str &strText,
+ IVirtualBoxErrorInfo *aNext = NULL);
+
+ HRESULT init(const com::ErrorInfo &ei,
+ IVirtualBoxErrorInfo *aNext = NULL);
+
+ // IVirtualBoxErrorInfo properties
+ STDMETHOD(COMGETTER(ResultCode))(LONG *aResultCode);
+ STDMETHOD(COMGETTER(ResultDetail))(LONG *aResultDetail);
+ STDMETHOD(COMGETTER(InterfaceID))(BSTR *aIID);
+ STDMETHOD(COMGETTER(Component))(BSTR *aComponent);
+ STDMETHOD(COMGETTER(Text))(BSTR *aText);
+ STDMETHOD(COMGETTER(Next))(IVirtualBoxErrorInfo **aNext);
+
+ const char* getComponentName() const { return "VirtualBoxErrorInfo"; }
+
+private:
+ static HRESULT setError(HRESULT rc,
+ const char * /* a */,
+ const char * /* b */,
+ void * /* c */) { return rc; }
+
+ HRESULT m_resultCode;
+ LONG m_resultDetail;
+ Utf8Str m_strText;
+ Guid m_IID;
+ Utf8Str m_strComponent;
+ ComPtr<IVirtualBoxErrorInfo> mNext;
+
+#ifndef VBOX_WITH_XPCOM
+ IUnknown *m_pUnkMarshaler;
+#endif
+};
+
+#endif /* !MAIN_INCLUDED_VirtualBoxErrorInfoImpl_h */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/VirtualBoxImpl.h b/src/VBox/Main/include/VirtualBoxImpl.h
new file mode 100644
index 00000000..b0bc6250
--- /dev/null
+++ b/src/VBox/Main/include/VirtualBoxImpl.h
@@ -0,0 +1,502 @@
+/* $Id: VirtualBoxImpl.h $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_VirtualBoxImpl_h
+#define MAIN_INCLUDED_VirtualBoxImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/VBoxCryptoIf.h>
+
+#include "VirtualBoxBase.h"
+#include "objectslist.h"
+#include "VirtualBoxWrap.h"
+
+#ifdef RT_OS_WINDOWS
+# include "win/resource.h"
+#endif
+
+//#ifdef DEBUG_bird
+//# define VBOXSVC_WITH_CLIENT_WATCHER
+//#endif
+
+namespace com
+{
+ class Event;
+ class EventQueue;
+}
+
+class SessionMachine;
+class GuestOSType;
+class Progress;
+class Host;
+class SystemProperties;
+class DHCPServer;
+class PerformanceCollector;
+class CloudProviderManager;
+#ifdef VBOX_WITH_EXTPACK
+class ExtPackManager;
+#endif
+class AutostartDb;
+class NATNetwork;
+#ifdef VBOX_WITH_CLOUD_NET
+class CloudNetwork;
+#endif /* VBOX_WITH_CLOUD_NET */
+
+
+typedef std::list<ComObjPtr<SessionMachine> > SessionMachinesList;
+
+#ifdef RT_OS_WINDOWS
+class SVCHlpClient;
+#endif
+
+namespace settings
+{
+ class MainConfigFile;
+ struct MediaRegistry;
+}
+
+
+#if defined(VBOX_WITH_SDS) && !defined(VBOX_WITH_XPCOM)
+class VirtualBoxClassFactory; /* See ../src-server/win/svcmain.cpp */
+#endif
+
+class ATL_NO_VTABLE VirtualBox :
+ public VirtualBoxWrap
+#ifdef RT_OS_WINDOWS
+ , public ATL::CComCoClass<VirtualBox, &CLSID_VirtualBox>
+#endif
+{
+
+public:
+
+ typedef std::list<ComPtr<IInternalSessionControl> > InternalControlList;
+ typedef ObjectsList<Machine> MachinesOList;
+
+#if 0 /* obsoleted by AsyncEvent */
+ class CallbackEvent;
+ friend class CallbackEvent;
+#endif
+ class AsyncEvent;
+ friend class AsyncEvent;
+
+#ifndef VBOX_WITH_XPCOM
+# ifdef VBOX_WITH_SDS
+ DECLARE_CLASSFACTORY_EX(VirtualBoxClassFactory)
+# else
+ DECLARE_CLASSFACTORY_SINGLETON(VirtualBox)
+# endif
+#endif
+
+ // Do not use any ATL registry support.
+ //DECLARE_REGISTRY_RESOURCEID(IDR_VIRTUALBOX)
+
+ // Kind of redundant (VirtualBoxWrap declares itself not aggregatable and
+ // CComCoClass<VirtualBox, &CLSID_VirtualBox> as aggregatable, the former
+ // is the first inheritance), but the C++ multiple inheritance rules and
+ // the class factory in svcmain.cpp needs this to disambiguate.
+ DECLARE_NOT_AGGREGATABLE(VirtualBox)
+
+ // to postpone generation of the default ctor/dtor
+ DECLARE_COMMON_CLASS_METHODS(VirtualBox)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // public initializer/uninitializer for internal purposes only
+ HRESULT init();
+ HRESULT initMachines();
+ HRESULT initMedia(const Guid &uuidMachineRegistry,
+ const settings::MediaRegistry &mediaRegistry,
+ const Utf8Str &strMachineFolder);
+ void uninit();
+
+ // public methods only for internal purposes
+
+ /**
+ * Override of the default locking class to be used for validating lock
+ * order with the standard member lock handle.
+ */
+ virtual VBoxLockingClass getLockingClass() const
+ {
+ return LOCKCLASS_VIRTUALBOXOBJECT;
+ }
+
+#ifdef DEBUG
+ void i_dumpAllBackRefs();
+#endif
+
+ HRESULT i_postEvent(Event *event);
+
+ HRESULT i_addProgress(IProgress *aProgress);
+ HRESULT i_removeProgress(IN_GUID aId);
+
+#ifdef RT_OS_WINDOWS
+ typedef HRESULT (*PFN_SVC_HELPER_CLIENT_T)(SVCHlpClient *aClient, Progress *aProgress, void *aUser, int *aVrc);
+ HRESULT i_startSVCHelperClient(bool aPrivileged,
+ PFN_SVC_HELPER_CLIENT_T aFunc,
+ void *aUser, Progress *aProgress);
+#endif
+
+ void i_addProcessToReap(RTPROCESS pid);
+ void i_updateClientWatcher();
+
+ int i_loadVDPlugin(const char *pszPluginLibrary);
+ int i_unloadVDPlugin(const char *pszPluginLibrary);
+
+ void i_onMediumRegistered(const Guid &aMediumId, const DeviceType_T aDevType, BOOL aRegistered);
+ void i_onMediumConfigChanged(IMedium *aMedium);
+ void i_onMediumChanged(IMediumAttachment* aMediumAttachment);
+ void i_onStorageControllerChanged(const Guid &aMachineId, const com::Utf8Str &aControllerName);
+ void i_onStorageDeviceChanged(IMediumAttachment* aStorageDevice, BOOL fRemoved, BOOL fSilent);
+ void i_onMachineStateChanged(const Guid &aId, MachineState_T aState);
+ void i_onMachineDataChanged(const Guid &aId, BOOL aTemporary = FALSE);
+ void i_onMachineGroupsChanged(const Guid &aId);
+ BOOL i_onExtraDataCanChange(const Guid &aId, const Utf8Str &aKey, const Utf8Str &aValue, Bstr &aError);
+ void i_onExtraDataChanged(const Guid &aId, const Utf8Str &aKey, const Utf8Str &aValue);
+ void i_onMachineRegistered(const Guid &aId, BOOL aRegistered);
+ void i_onSessionStateChanged(const Guid &aId, SessionState_T aState);
+
+ void i_onSnapshotTaken(const Guid &aMachineId, const Guid &aSnapshotId);
+ void i_onSnapshotDeleted(const Guid &aMachineId, const Guid &aSnapshotId);
+ void i_onSnapshotRestored(const Guid &aMachineId, const Guid &aSnapshotId);
+ void i_onSnapshotChanged(const Guid &aMachineId, const Guid &aSnapshotId);
+
+ void i_onGuestPropertyChanged(const Guid &aMachineId, const Utf8Str &aName, const Utf8Str &aValue, const Utf8Str &aFlags,
+ const BOOL fWasDeleted);
+ void i_onNatRedirectChanged(const Guid &aMachineId, ULONG ulSlot, bool fRemove, const Utf8Str &aName,
+ NATProtocol_T aProto, const Utf8Str &aHostIp, uint16_t aHostPort,
+ const Utf8Str &aGuestIp, uint16_t aGuestPort);
+ void i_onNATNetworkChanged(const Utf8Str &aNetworkName);
+ void i_onNATNetworkStartStop(const Utf8Str &aNetworkName, BOOL aStart);
+ void i_onNATNetworkSetting(const Utf8Str &aNetworkName, BOOL aEnabled, const Utf8Str &aNetwork,
+ const Utf8Str &aGateway, BOOL aAdvertiseDefaultIpv6RouteEnabled,
+ BOOL fNeedDhcpServer);
+ void i_onNATNetworkPortForward(const Utf8Str &aNetworkName, BOOL create, BOOL fIpv6,
+ const Utf8Str &aRuleName, NATProtocol_T proto,
+ const Utf8Str &aHostIp, LONG aHostPort,
+ const Utf8Str &aGuestIp, LONG aGuestPort);
+ void i_onHostNameResolutionConfigurationChange();
+
+ int i_natNetworkRefInc(const Utf8Str &aNetworkName);
+ int i_natNetworkRefDec(const Utf8Str &aNetworkName);
+
+ RWLockHandle *i_getNatNetLock() const;
+ bool i_isNatNetStarted(const Utf8Str &aNetworkName) const;
+
+ void i_onCloudProviderListChanged(BOOL aRegistered);
+ void i_onCloudProviderRegistered(const Utf8Str &aProviderId, BOOL aRegistered);
+ void i_onCloudProviderUninstall(const Utf8Str &aProviderId);
+
+ void i_onProgressCreated(const Guid &aId, BOOL aCreated);
+
+ void i_onLanguageChanged(const Utf8Str &aLanguageId);
+
+#ifdef VBOX_WITH_UPDATE_AGENT
+ void i_onUpdateAgentAvailable(IUpdateAgent *aAgent,
+ const Utf8Str &aVer, UpdateChannel_T aChannel, UpdateSeverity_T aSev,
+ const Utf8Str &aDownloadURL, const Utf8Str &aWebURL, const Utf8Str &aReleaseNotes);
+ void i_onUpdateAgentError(IUpdateAgent *aAgent, const Utf8Str &aErrMsg, LONG aRc);
+ void i_onUpdateAgentStateChanged(IUpdateAgent *aAgent, UpdateState_T aState);
+ void i_onUpdateAgentSettingsChanged(IUpdateAgent *aAgent, const Utf8Str &aAttributeHint);
+#endif /* VBOX_WITH_UPDATE_AGENT */
+
+#ifdef VBOX_WITH_CLOUD_NET
+ HRESULT i_findCloudNetworkByName(const com::Utf8Str &aNetworkName,
+ ComObjPtr<CloudNetwork> *aNetwork = NULL);
+ HRESULT i_getEventSource(ComPtr<IEventSource>& aSource);
+#endif /* VBOX_WITH_CLOUD_NET */
+
+ ComObjPtr<GuestOSType> i_getUnknownOSType();
+
+ void i_getOpenedMachines(SessionMachinesList &aMachines,
+ InternalControlList *aControls = NULL);
+ MachinesOList &i_getMachinesList();
+
+ HRESULT i_findMachine(const Guid &aId,
+ bool fPermitInaccessible,
+ bool aSetError,
+ ComObjPtr<Machine> *aMachine = NULL);
+
+ HRESULT i_findMachineByName(const Utf8Str &aName,
+ bool aSetError,
+ ComObjPtr<Machine> *aMachine = NULL);
+
+ HRESULT i_validateMachineGroup(const com::Utf8Str &aGroup, bool fPrimary);
+ HRESULT i_convertMachineGroups(const std::vector<com::Utf8Str> aMachineGroups, StringsList *pllMachineGroups);
+
+ HRESULT i_findHardDiskById(const Guid &id,
+ bool aSetError,
+ ComObjPtr<Medium> *aHardDisk = NULL);
+ HRESULT i_findHardDiskByLocation(const Utf8Str &strLocation,
+ bool aSetError,
+ ComObjPtr<Medium> *aHardDisk = NULL);
+ HRESULT i_findDVDOrFloppyImage(DeviceType_T mediumType,
+ const Guid *aId,
+ const Utf8Str &aLocation,
+ bool aSetError,
+ ComObjPtr<Medium> *aImage = NULL);
+ HRESULT i_findRemoveableMedium(DeviceType_T mediumType,
+ const Guid &uuid,
+ bool fRefresh,
+ bool aSetError,
+ ComObjPtr<Medium> &pMedium);
+
+ HRESULT i_findGuestOSType(const Utf8Str &strOSType,
+ ComObjPtr<GuestOSType> &guestOSType);
+
+ const Guid &i_getGlobalRegistryId() const;
+
+ const ComObjPtr<Host> &i_host() const;
+ SystemProperties *i_getSystemProperties() const;
+ CloudProviderManager *i_getCloudProviderManager() const;
+#ifdef VBOX_WITH_EXTPACK
+ ExtPackManager *i_getExtPackManager() const;
+#endif
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ const ComObjPtr<PerformanceCollector> &i_performanceCollector() const;
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+
+ void i_getDefaultMachineFolder(Utf8Str &str) const;
+ void i_getDefaultHardDiskFormat(Utf8Str &str) const;
+
+ /** Returns the VirtualBox home directory */
+ const Utf8Str &i_homeDir() const;
+ int i_calculateFullPath(const Utf8Str &strPath, Utf8Str &aResult);
+ void i_copyPathRelativeToConfig(const Utf8Str &strSource, Utf8Str &strTarget);
+ HRESULT i_registerMedium(const ComObjPtr<Medium> &pMedium, ComObjPtr<Medium> *ppMedium,
+ AutoWriteLock &mediaTreeLock, bool fCalledFromMediumInit = false);
+ HRESULT i_unregisterMedium(Medium *pMedium);
+ HRESULT i_unregisterMachineMedia(const Guid &id);
+ HRESULT i_unregisterMachine(Machine *pMachine, CleanupMode_T aCleanupMode, const Guid &id);
+ void i_rememberMachineNameChangeForMedia(const Utf8Str &strOldConfigDir,
+ const Utf8Str &strNewConfigDir);
+ void i_saveMediaRegistry(settings::MediaRegistry &mediaRegistry,
+ const Guid &uuidRegistry,
+ const Utf8Str &strMachineFolder);
+ HRESULT i_saveSettings();
+ void i_markRegistryModified(const Guid &uuid);
+ void i_unmarkRegistryModified(const Guid &uuid);
+ void i_saveModifiedRegistries();
+ static const com::Utf8Str &i_getVersionNormalized();
+ static HRESULT i_ensureFilePathExists(const Utf8Str &strFileName, bool fCreate);
+ const Utf8Str& i_settingsFilePath();
+ AutostartDb* i_getAutostartDb() const;
+ RWLockHandle& i_getMachinesListLockHandle();
+ RWLockHandle& i_getMediaTreeLockHandle();
+ int i_encryptSetting(const Utf8Str &aPlaintext, Utf8Str *aCiphertext);
+ int i_decryptSetting(Utf8Str *aPlaintext, const Utf8Str &aCiphertext);
+ void i_storeSettingsKey(const Utf8Str &aKey);
+ bool i_isMediaUuidInUse(const Guid &aId, DeviceType_T deviceType);
+ HRESULT i_retainCryptoIf(PCVBOXCRYPTOIF *ppCryptoIf);
+ HRESULT i_releaseCryptoIf(PCVBOXCRYPTOIF pCryptoIf);
+ HRESULT i_unloadCryptoIfModule(void);
+
+
+
+private:
+ class ClientWatcher;
+
+ // wrapped IVirtualBox properties
+ HRESULT getVersion(com::Utf8Str &aVersion);
+ HRESULT getVersionNormalized(com::Utf8Str &aVersionNormalized);
+ HRESULT getRevision(ULONG *aRevision);
+ HRESULT getPackageType(com::Utf8Str &aPackageType);
+ HRESULT getAPIVersion(com::Utf8Str &aAPIVersion);
+ HRESULT getAPIRevision(LONG64 *aAPIRevision);
+ HRESULT getHomeFolder(com::Utf8Str &aHomeFolder);
+ HRESULT getSettingsFilePath(com::Utf8Str &aSettingsFilePath);
+ HRESULT getHost(ComPtr<IHost> &aHost);
+ HRESULT getSystemProperties(ComPtr<ISystemProperties> &aSystemProperties);
+ HRESULT getMachines(std::vector<ComPtr<IMachine> > &aMachines);
+ HRESULT getMachineGroups(std::vector<com::Utf8Str> &aMachineGroups);
+ HRESULT getHardDisks(std::vector<ComPtr<IMedium> > &aHardDisks);
+ HRESULT getDVDImages(std::vector<ComPtr<IMedium> > &aDVDImages);
+ HRESULT getFloppyImages(std::vector<ComPtr<IMedium> > &aFloppyImages);
+ HRESULT getProgressOperations(std::vector<ComPtr<IProgress> > &aProgressOperations);
+ HRESULT getGuestOSTypes(std::vector<ComPtr<IGuestOSType> > &aGuestOSTypes);
+ HRESULT getSharedFolders(std::vector<ComPtr<ISharedFolder> > &aSharedFolders);
+ HRESULT getPerformanceCollector(ComPtr<IPerformanceCollector> &aPerformanceCollector);
+ HRESULT getDHCPServers(std::vector<ComPtr<IDHCPServer> > &aDHCPServers);
+ HRESULT getNATNetworks(std::vector<ComPtr<INATNetwork> > &aNATNetworks);
+ HRESULT getEventSource(ComPtr<IEventSource> &aEventSource);
+ HRESULT getExtensionPackManager(ComPtr<IExtPackManager> &aExtensionPackManager);
+ HRESULT getHostOnlyNetworks(std::vector<ComPtr<IHostOnlyNetwork> > &aHostOnlyNetworks);
+ HRESULT getInternalNetworks(std::vector<com::Utf8Str> &aInternalNetworks);
+ HRESULT getGenericNetworkDrivers(std::vector<com::Utf8Str> &aGenericNetworkDrivers);
+ HRESULT getCloudNetworks(std::vector<ComPtr<ICloudNetwork> > &aCloudNetworks);
+ HRESULT getCloudProviderManager(ComPtr<ICloudProviderManager> &aCloudProviderManager);
+
+ // wrapped IVirtualBox methods
+ HRESULT composeMachineFilename(const com::Utf8Str &aName,
+ const com::Utf8Str &aGroup,
+ const com::Utf8Str &aCreateFlags,
+ const com::Utf8Str &aBaseFolder,
+ com::Utf8Str &aFile);
+ HRESULT createMachine(const com::Utf8Str &aSettingsFile,
+ const com::Utf8Str &aName,
+ const std::vector<com::Utf8Str> &aGroups,
+ const com::Utf8Str &aOsTypeId,
+ const com::Utf8Str &aFlags,
+ const com::Utf8Str &aCipher,
+ const com::Utf8Str &aPasswordId,
+ const com::Utf8Str &aPassword,
+ ComPtr<IMachine> &aMachine);
+ HRESULT openMachine(const com::Utf8Str &aSettingsFile,
+ const com::Utf8Str &aPassword,
+ ComPtr<IMachine> &aMachine);
+ HRESULT registerMachine(const ComPtr<IMachine> &aMachine);
+ HRESULT findMachine(const com::Utf8Str &aNameOrId,
+ ComPtr<IMachine> &aMachine);
+ HRESULT getMachinesByGroups(const std::vector<com::Utf8Str> &aGroups,
+ std::vector<ComPtr<IMachine> > &aMachines);
+ HRESULT getMachineStates(const std::vector<ComPtr<IMachine> > &aMachines,
+ std::vector<MachineState_T> &aStates);
+ HRESULT createAppliance(ComPtr<IAppliance> &aAppliance);
+ HRESULT createUnattendedInstaller(ComPtr<IUnattended> &aUnattended);
+ HRESULT createMedium(const com::Utf8Str &aFormat,
+ const com::Utf8Str &aLocation,
+ AccessMode_T aAccessMode,
+ DeviceType_T aDeviceType,
+ ComPtr<IMedium> &aMedium);
+ HRESULT openMedium(const com::Utf8Str &aLocation,
+ DeviceType_T aDeviceType,
+ AccessMode_T aAccessMode,
+ BOOL aForceNewUuid,
+ ComPtr<IMedium> &aMedium);
+ HRESULT getGuestOSType(const com::Utf8Str &aId,
+ ComPtr<IGuestOSType> &aType);
+ HRESULT createSharedFolder(const com::Utf8Str &aName,
+ const com::Utf8Str &aHostPath,
+ BOOL aWritable,
+ BOOL aAutomount,
+ const com::Utf8Str &aAutoMountPoint);
+ HRESULT removeSharedFolder(const com::Utf8Str &aName);
+ HRESULT getExtraDataKeys(std::vector<com::Utf8Str> &aKeys);
+ HRESULT getExtraData(const com::Utf8Str &aKey,
+ com::Utf8Str &aValue);
+ HRESULT setExtraData(const com::Utf8Str &aKey,
+ const com::Utf8Str &aValue);
+ HRESULT setSettingsSecret(const com::Utf8Str &aPassword);
+ HRESULT createDHCPServer(const com::Utf8Str &aName,
+ ComPtr<IDHCPServer> &aServer);
+ HRESULT findDHCPServerByNetworkName(const com::Utf8Str &aName,
+ ComPtr<IDHCPServer> &aServer);
+ HRESULT removeDHCPServer(const ComPtr<IDHCPServer> &aServer);
+ HRESULT createNATNetwork(const com::Utf8Str &aNetworkName,
+ ComPtr<INATNetwork> &aNetwork);
+ HRESULT findNATNetworkByName(const com::Utf8Str &aNetworkName,
+ ComPtr<INATNetwork> &aNetwork);
+ HRESULT removeNATNetwork(const ComPtr<INATNetwork> &aNetwork);
+ HRESULT createHostOnlyNetwork(const com::Utf8Str &aNetworkName,
+ ComPtr<IHostOnlyNetwork> &aNetwork);
+ HRESULT findHostOnlyNetworkByName(const com::Utf8Str &aNetworkName,
+ ComPtr<IHostOnlyNetwork> &aNetwork);
+ HRESULT findHostOnlyNetworkById(const com::Guid &aId,
+ ComPtr<IHostOnlyNetwork> &aNetwork);
+ HRESULT removeHostOnlyNetwork(const ComPtr<IHostOnlyNetwork> &aNetwork);
+ HRESULT createCloudNetwork(const com::Utf8Str &aNetworkName,
+ ComPtr<ICloudNetwork> &aNetwork);
+ HRESULT findCloudNetworkByName(const com::Utf8Str &aNetworkName,
+ ComPtr<ICloudNetwork> &aNetwork);
+ HRESULT removeCloudNetwork(const ComPtr<ICloudNetwork> &aNetwork);
+ HRESULT checkFirmwarePresent(FirmwareType_T aFirmwareType,
+ const com::Utf8Str &aVersion,
+ com::Utf8Str &aUrl,
+ com::Utf8Str &aFile,
+ BOOL *aResult);
+ HRESULT findProgressById(const com::Guid &aId,
+ ComPtr<IProgress> &aProgressObject);
+
+ static HRESULT i_setErrorStaticBoth(HRESULT aResultCode, int vrc, const char *aText, ...)
+ {
+ va_list va;
+ va_start (va, aText);
+ HRESULT hrc = setErrorInternalV(aResultCode, getStaticClassIID(), getStaticComponentName(), aText, va, false, true, vrc);
+ va_end(va);
+ return hrc;
+ }
+
+ HRESULT i_registerMachine(Machine *aMachine);
+ HRESULT i_registerDHCPServer(DHCPServer *aDHCPServer,
+ bool aSaveRegistry = true);
+ HRESULT i_unregisterDHCPServer(DHCPServer *aDHCPServer);
+ HRESULT i_registerNATNetwork(NATNetwork *aNATNetwork,
+ bool aSaveRegistry = true);
+ HRESULT i_unregisterNATNetwork(NATNetwork *aNATNetwork,
+ bool aSaveRegistry = true);
+ HRESULT i_checkMediaForConflicts(const Guid &aId,
+ const Utf8Str &aLocation,
+ Utf8Str &aConflictType,
+ ComObjPtr<Medium> *pDupMedium);
+ int i_decryptSettings();
+ int i_decryptMediumSettings(Medium *pMedium);
+ int i_decryptSettingBytes(uint8_t *aPlaintext,
+ const uint8_t *aCiphertext,
+ size_t aCiphertextSize) const;
+ int i_encryptSettingBytes(const uint8_t *aPlaintext,
+ uint8_t *aCiphertext,
+ size_t aPlaintextSize,
+ size_t aCiphertextSize) const;
+ void i_reportDriverVersions(void);
+
+ struct Data; // opaque data structure, defined in VirtualBoxImpl.cpp
+
+ Data *m;
+
+ /* static variables (defined in VirtualBoxImpl.cpp) */
+ static com::Utf8Str sVersion;
+ static com::Utf8Str sVersionNormalized;
+ static ULONG sRevision;
+ static com::Utf8Str sPackageType;
+ static com::Utf8Str sAPIVersion;
+ static std::map<com::Utf8Str, int> sNatNetworkNameToRefCount;
+ static RWLockHandle* spMtxNatNetworkNameToRefCountLock;
+
+ static DECLCALLBACK(int) AsyncEventHandler(RTTHREAD thread, void *pvUser);
+
+#ifdef RT_OS_WINDOWS
+ friend class StartSVCHelperClientData;
+ static void i_SVCHelperClientThreadTask(StartSVCHelperClientData *pTask);
+#endif
+
+#if defined(RT_OS_WINDOWS) && defined(VBOXSVC_WITH_CLIENT_WATCHER)
+protected:
+ void i_callHook(const char *a_pszFunction) RT_OVERRIDE;
+ bool i_watchClientProcess(RTPROCESS a_pidClient, const char *a_pszFunction);
+public:
+ static void i_logCaller(const char *a_pszFormat, ...);
+private:
+
+#endif
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#endif /* !MAIN_INCLUDED_VirtualBoxImpl_h */
+
diff --git a/src/VBox/Main/include/VirtualBoxSDSImpl.h b/src/VBox/Main/include/VirtualBoxSDSImpl.h
new file mode 100644
index 00000000..3a495fc2
--- /dev/null
+++ b/src/VBox/Main/include/VirtualBoxSDSImpl.h
@@ -0,0 +1,161 @@
+/* $Id: VirtualBoxSDSImpl.h $ */
+/** @file
+ * VBox Global COM Class definition
+ */
+
+/*
+ * Copyright (C) 2017-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_VirtualBoxSDSImpl_h
+#define MAIN_INCLUDED_VirtualBoxSDSImpl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VirtualBoxBase.h"
+
+/* Enable the watcher code in debug builds. */
+#ifdef DEBUG
+# define WITH_WATCHER
+#endif
+
+
+class VBoxSDSPerUserData; /* See VirtualBoxSDSImpl.cpp. */
+struct VBoxSDSWatcher; /* See VirtualBoxSDSImpl.cpp. */
+
+/**
+ * The IVirtualBoxSDS implementation.
+ *
+ * This class helps different VBoxSVC processes make sure a user only have a
+ * single VirtualBox instance.
+ *
+ * @note This is a simple internal class living in a privileged process. So, we
+ * do not use the API wrappers as they add complexity. In particular,
+ * they add the auto caller logic, which is an excellent tool to create
+ * unkillable processes. If an API method during development or product
+ * for instance triggers an NT exception like STATUS_ACCESS_VIOLATION, the
+ * caller will be unwound without releasing the caller. When uninit is
+ * called during COM shutdown/whatever, the thread gets stuck waiting for
+ * the long gone caller and cannot be killed (Windows 10, build 16299),
+ * requiring a reboot to continue.
+ *
+ * @todo Would be very nice to get rid of the ATL cruft too here.
+ */
+class VirtualBoxSDS
+ : public IVirtualBoxSDS
+ , public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>
+ , public ATL::CComCoClass<VirtualBoxSDS, &CLSID_VirtualBoxSDS>
+{
+private:
+ typedef std::map<com::Utf8Str, VBoxSDSPerUserData *> UserDataMap_T;
+ /** Per user data map (key is SID string).
+ * This is an insert-only map! */
+ UserDataMap_T m_UserDataMap;
+ /** Number of registered+watched VBoxSVC processes. */
+ uint32_t m_cVBoxSvcProcesses;
+#ifdef WITH_WATCHER
+ /** Number of watcher threads. */
+ uint32_t m_cWatchers;
+ /** Pointer to an array of watcher pointers. */
+ VBoxSDSWatcher **m_papWatchers;
+ /** Lock protecting m_papWatchers and associated structures. */
+ RTCRITSECT m_WatcherCritSect;
+#endif
+ /** Lock protecting m_UserDataMap . */
+ RTCRITSECTRW m_MapCritSect;
+
+public:
+ DECLARE_CLASSFACTORY_SINGLETON(VirtualBoxSDS)
+ DECLARE_NOT_AGGREGATABLE(VirtualBoxSDS)
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ BEGIN_COM_MAP(VirtualBoxSDS)
+ COM_INTERFACE_ENTRY(IVirtualBoxSDS)
+ END_COM_MAP()
+
+ DECLARE_COMMON_CLASS_METHODS(VirtualBoxSDS)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+private:
+
+ /** @name IVirtualBoxSDS methods
+ * @{ */
+ STDMETHOD(RegisterVBoxSVC)(IVBoxSVCRegistration *aVBoxSVC, LONG aPid, IUnknown **aExistingVirtualBox);
+ STDMETHOD(DeregisterVBoxSVC)(IVBoxSVCRegistration *aVBoxSVC, LONG aPid);
+ STDMETHOD(LaunchVMProcess)(IN_BSTR aMachine, IN_BSTR aComment, IN_BSTR aFrontend,
+ ComSafeArrayIn(IN_BSTR, aEnvironmentChanges), IN_BSTR aCmdOptions,
+ ULONG aSessionId, ULONG *aPid);
+ /** @} */
+
+
+ /** @name Private methods
+ * @{ */
+ /**
+ * Gets the client user SID of the
+ */
+ static bool i_getClientUserSid(com::Utf8Str *a_pStrSid, com::Utf8Str *a_pStrUsername);
+
+ /**
+ * Returns whether a VBoxSDS feature is enabled or not.
+ *
+ * @returns \c true if enabled, \c false if not.
+ * @param a_pwszFeature Feature to check enabled status for.
+ */
+ static bool i_isFeatureEnabled(wchar_t const *a_pwszFeature);
+
+ /**
+ * Looks up the given user.
+ *
+ * @returns Pointer to the LOCKED per user data. NULL if not found.
+ * @param a_rStrUserSid The user SID.
+ */
+ VBoxSDSPerUserData *i_lookupPerUserData(com::Utf8Str const &a_rStrUserSid);
+
+ /**
+ * Looks up the given user, creating it if not found
+ *
+ * @returns Pointer to the LOCKED per user data. NULL on allocation error.
+ * @param a_rStrUserSid The user SID.
+ * @param a_rStrUsername The user name if available.
+ */
+ VBoxSDSPerUserData *i_lookupOrCreatePerUserData(com::Utf8Str const &a_rStrUserSid, com::Utf8Str const &a_rStrUsername);
+
+#ifdef WITH_WATCHER
+ static DECLCALLBACK(int) i_watcherThreadProc(RTTHREAD hSelf, void *pvUser);
+ bool i_watchIt(VBoxSDSPerUserData *pProcess, HANDLE hProcess, RTPROCESS pid);
+ void i_stopWatching(VBoxSDSPerUserData *pProcess, RTPROCESS pid);
+ void i_shutdownAllWatchers(void);
+
+ void i_decrementClientCount();
+ void i_incrementClientCount();
+#endif
+ /** @} */
+};
+
+#ifdef WITH_WATCHER
+void VBoxSDSNotifyClientCount(uint32_t cClients);
+#endif
+
+#endif /* !MAIN_INCLUDED_VirtualBoxSDSImpl_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/VirtualBoxTranslator.h b/src/VBox/Main/include/VirtualBoxTranslator.h
new file mode 100644
index 00000000..3921bf14
--- /dev/null
+++ b/src/VBox/Main/include/VirtualBoxTranslator.h
@@ -0,0 +1,165 @@
+/* $Id: VirtualBoxTranslator.h $ */
+/** @file
+ * VirtualBox Translator.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_VirtualBoxTranslator_h
+#define MAIN_INCLUDED_VirtualBoxTranslator_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <list>
+
+#include <iprt/cpp/lock.h>
+#include <VBox/com/AutoLock.h>
+
+COM_STRUCT_OR_CLASS(IVirtualBox);
+class QMTranslator;
+
+class VirtualBoxTranslator
+ : public util::RWLockHandle
+{
+public:
+ virtual ~VirtualBoxTranslator();
+
+ static VirtualBoxTranslator *instance();
+ /* Returns instance if exists, returns NULL otherwise. */
+ static VirtualBoxTranslator *tryInstance() RT_NOEXCEPT;
+ void release();
+
+ /* Load language based on settings in the VirtualBox config */
+ HRESULT loadLanguage(ComPtr<IVirtualBox> aVirtualBox);
+
+private:
+ /** Translator component. */
+ struct TranslatorComponent
+ {
+ QMTranslator *pTranslator;
+ /** Path to translation files. It includes file prefix, i.e '/path/to/folder/file_prefix'. */
+ com::Utf8Str strPath;
+
+ TranslatorComponent() : pTranslator(NULL) {}
+ };
+public:
+ /** Pointer to a translator component. */
+ typedef TranslatorComponent *PTRCOMPONENT;
+
+ /**
+ * Registers the translation for a component.
+ *
+ * @returns VBox status code
+ * @param aTranslationPath Path to the translation files, this includes the
+ * base filename prefix.
+ * @param aDefault Use this as the default translation component, i.e.
+ * when translate() is called with NULL for @a
+ * aComponent.
+ * @param aComponent Pointer to where is the component handle should be
+ * returned on success. The return value must be used
+ * for all subsequent calls to translate().
+ */
+ static int registerTranslation(const char *aTranslationPath,
+ bool aDefault,
+ PTRCOMPONENT *aComponent);
+
+ /**
+ * Removes translations for a component.
+ *
+ * @returns VBox status code
+ * @param aComponent The component. NULL is quietly (VWRN_NOT_FOUND)
+ * ignored .
+ */
+ static int unregisterTranslation(PTRCOMPONENT aComponent);
+
+ /**
+ * Translates @a aSourceText to user language.
+ * Uses component marked as default if @a aTranslationComponent is NULL
+ *
+ * @returns Translated string or @a aSourceText. The returned string is
+ * valid only during lifetime of the translator instance.
+ */
+ static const char *translate(PTRCOMPONENT aComponent,
+ const char *aContext,
+ const char *aSourceText,
+ const char *aComment = NULL,
+ const size_t aNum = ~(size_t)0) RT_NOEXCEPT;
+
+ /**
+ * Returns source text stored in the cache if exists.
+ * Otherwise, the pszTranslation itself returned.
+ */
+ static const char *trSource(const char *aTranslation) RT_NOEXCEPT;
+
+ /* Convenience function used by VirtualBox::init */
+ int i_loadLanguage(const char *pszLang);
+
+ static int32_t initCritSect();
+
+ com::Utf8Str language();
+
+private:
+ static RTCRITSECTRW s_instanceRwLock;
+ static VirtualBoxTranslator *s_pInstance;
+
+ uint32_t m_cInstanceRefs;
+
+ typedef std::list<TranslatorComponent> TranslatorList;
+ TranslatorList m_lTranslators;
+ TranslatorComponent *m_pDefaultComponent;
+
+ /* keep the language code for registration */
+ com::Utf8Str m_strLanguage;
+
+ /** String cache that all translation strings are stored in.
+ * This is a add-only cache, which allows translate() to return C-strings w/o
+ * needing to think about racing loadLanguage() wrt string lifetime. */
+ RTSTRCACHE m_hStrCache;
+ /** RTStrCacheCreate status code. */
+ int m_rcCache;
+
+ VirtualBoxTranslator();
+
+ int i_loadLanguageForComponent(TranslatorComponent *aComponent, const char *aLang);
+
+ int i_setLanguageFile(TranslatorComponent *aComponent, const char *aFileName);
+
+ int i_registerTranslation(const char *aTranslationPath,
+ bool aDefault,
+ PTRCOMPONENT *aComponent);
+
+ int i_unregisterTranslation(PTRCOMPONENT aComponent);
+
+ const char *i_translate(PTRCOMPONENT aComponent,
+ const char *aContext,
+ const char *aSourceText,
+ const char *aComment = NULL,
+ const size_t aNum = ~(size_t)0) RT_NOEXCEPT;
+};
+
+/** Pointer to a translator component. */
+typedef VirtualBoxTranslator::PTRCOMPONENT PTRCOMPONENT;
+
+#endif /* !MAIN_INCLUDED_VirtualBoxTranslator_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/WebMWriter.h b/src/VBox/Main/include/WebMWriter.h
new file mode 100644
index 00000000..e03a4798
--- /dev/null
+++ b/src/VBox/Main/include/WebMWriter.h
@@ -0,0 +1,579 @@
+/* $Id: WebMWriter.h $ */
+/** @file
+ * WebMWriter.h - WebM container handling.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_WebMWriter_h
+#define MAIN_INCLUDED_WebMWriter_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/buildconfig.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+
+#include "VBox/com/VirtualBox.h"
+#include <VBox/version.h>
+
+#include "EBMLWriter.h"
+#include "EBML_MKV.h"
+
+#include <queue>
+#include <map>
+#include <list>
+
+#ifdef VBOX_WITH_LIBVPX
+# ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable: 4668) /* vpx_codec.h(64) : warning C4668: '__GNUC__' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */
+# include <vpx/vpx_encoder.h>
+# pragma warning(pop)
+# else
+# include <vpx/vpx_encoder.h>
+# endif
+#endif /* VBOX_WITH_LIBVPX */
+
+/** No flags specified. */
+#define VBOX_WEBM_BLOCK_FLAG_NONE 0
+/** Invisible block which can be skipped. */
+#define VBOX_WEBM_BLOCK_FLAG_INVISIBLE 0x08
+/** The block marks a key frame. */
+#define VBOX_WEBM_BLOCK_FLAG_KEY_FRAME 0x80
+
+/** The default timecode scale factor for WebM -- all timecodes in the segments are expressed in ms.
+ * This allows every cluster to have blocks with positive values up to 32.767 seconds. */
+#define VBOX_WEBM_TIMECODESCALE_FACTOR_MS 1000000
+
+/** Maximum time (in ms) a cluster can store. */
+#define VBOX_WEBM_CLUSTER_MAX_LEN_MS INT16_MAX
+
+/** Maximum time a block can store.
+ * With signed 16-bit timecodes and a default timecode scale of 1ms per unit this makes 65536ms. */
+#define VBOX_WEBM_BLOCK_MAX_LEN_MS UINT16_MAX
+
+#ifdef VBOX_WITH_LIBVORBIS
+# pragma pack(push)
+# pragma pack(1)
+ /** Ogg Vorbis codec private data within the MKV (WEBM) container.
+ * Taken from: https://www.matroska.org/technical/codec_specs.html */
+ typedef struct WEBMOGGVORBISPRIVDATA
+ {
+ WEBMOGGVORBISPRIVDATA(uint32_t a_cbHdrIdent, uint32_t a_cbHdrComments, uint32_t a_cbHdrSetup)
+ : cbHdrIdent(a_cbHdrIdent)
+ , cbHdrComments(a_cbHdrComments)
+ {
+ RT_NOREF(a_cbHdrSetup);
+
+ /* We supply 3 headers total: The "real" header, comments header + setup header. */
+ cHeaders = 3 /* Headers */ - 1; /* Note: Always "minus one" here. */
+
+ Assert(a_cbHdrIdent <= UINT8_MAX);
+ Assert(a_cbHdrComments <= UINT8_MAX);
+ Assert(a_cbHdrSetup <= _8K);
+ Assert(a_cbHdrIdent + a_cbHdrComments + a_cbHdrSetup <= sizeof(abHdr));
+ }
+
+ /** Number of private headers - 1. */
+ uint8_t cHeaders;
+ /** Size of identification header (in bytes). */
+ uint8_t cbHdrIdent;
+ /** < Size of comments header (in bytes). */
+ uint8_t cbHdrComments;
+ /** < Header code area. */
+ uint8_t abHdr[UINT8_MAX /* Header */ + UINT8_MAX /* Comments header */ + _8K /* Setup header */];
+
+ } WEBMOGGVORBISPRIVDATA, *PWEBMOGGVORBISPRIVDATA;
+# pragma pack(pop)
+#endif
+
+class WebMWriter : public EBMLWriter
+{
+
+public:
+
+ /** Defines an absolute WebM timecode (Block + Cluster). */
+ typedef uint64_t WebMTimecodeAbs;
+
+ /** Defines a relative WebM timecode (Block). */
+ typedef uint16_t WebMTimecodeRel;
+
+ /** Defines the WebM block flags data type. */
+ typedef uint8_t WebMBlockFlags;
+
+ /**
+ * Track type enumeration.
+ */
+ enum WebMTrackType
+ {
+ /** Unknown / invalid type. */
+ WebMTrackType_Invalid = 0,
+ /** Only writes audio. */
+ WebMTrackType_Audio = 1,
+ /** Only writes video. */
+ WebMTrackType_Video = 2
+ };
+
+ struct WebMTrack;
+
+ /**
+ * Structure for defining a WebM simple block.
+ */
+ struct WebMSimpleBlock
+ {
+ WebMSimpleBlock(WebMTrack *a_pTrack,
+ WebMTimecodeAbs a_tcAbsPTSMs, const void *a_pvData, size_t a_cbData, WebMBlockFlags a_fFlags)
+ : pTrack(a_pTrack)
+ {
+ Data.tcAbsPTSMs = a_tcAbsPTSMs;
+ Data.cb = a_cbData;
+ Data.fFlags = a_fFlags;
+
+ if (Data.cb)
+ {
+ Data.pv = RTMemDup(a_pvData, a_cbData);
+ if (!Data.pv)
+ throw;
+ }
+ }
+
+ virtual ~WebMSimpleBlock()
+ {
+ if (Data.pv)
+ {
+ Assert(Data.cb);
+ RTMemFree(Data.pv);
+ }
+ }
+
+ WebMTrack *pTrack;
+
+ /** Actual simple block data. */
+ struct
+ {
+ WebMTimecodeAbs tcAbsPTSMs;
+ WebMTimecodeRel tcRelToClusterMs;
+ void *pv;
+ size_t cb;
+ WebMBlockFlags fFlags;
+ } Data;
+ };
+
+ /** A simple block queue.*/
+ typedef std::queue<WebMSimpleBlock *> WebMSimpleBlockQueue;
+
+ /** Structure for queuing all simple blocks bound to a single timecode.
+ * This can happen if multiple tracks are being involved. */
+ struct WebMTimecodeBlocks
+ {
+ WebMTimecodeBlocks(void)
+ : fClusterNeeded(false)
+ , fClusterStarted(false) { }
+
+ /** The actual block queue for this timecode. */
+ WebMSimpleBlockQueue Queue;
+ /** Whether a new cluster is needed for this timecode or not. */
+ bool fClusterNeeded;
+ /** Whether a new cluster already has been started for this timecode or not. */
+ bool fClusterStarted;
+
+ /**
+ * Enqueues a simple block into the internal queue.
+ *
+ * @param a_pBlock Block to enqueue and take ownership of.
+ */
+ void Enqueue(WebMSimpleBlock *a_pBlock)
+ {
+ Queue.push(a_pBlock);
+
+ if (a_pBlock->Data.fFlags & VBOX_WEBM_BLOCK_FLAG_KEY_FRAME)
+ fClusterNeeded = true;
+ }
+ };
+
+ /** A block map containing all currently queued blocks.
+ * The key specifies a unique timecode, whereas the value
+ * is a queue of blocks which all correlate to the key (timecode). */
+ typedef std::map<WebMTimecodeAbs, WebMTimecodeBlocks> WebMBlockMap;
+
+ /**
+ * Structure for defining a WebM (encoding) queue.
+ */
+ struct WebMQueue
+ {
+ WebMQueue(void)
+ : tcAbsLastBlockWrittenMs(0)
+ , tsLastProcessedMs(0) { }
+
+ /** Blocks as FIFO (queue). */
+ WebMBlockMap Map;
+ /** Absolute timecode (in ms) of last written block to queue. */
+ WebMTimecodeAbs tcAbsLastBlockWrittenMs;
+ /** Time stamp (in ms) of when the queue was processed last. */
+ uint64_t tsLastProcessedMs;
+ };
+
+ /**
+ * Structure for keeping a WebM track entry.
+ */
+ struct WebMTrack
+ {
+ WebMTrack(WebMTrackType a_enmType, PRECORDINGCODEC pTheCodec, uint8_t a_uTrack, uint64_t a_offID)
+ : enmType(a_enmType)
+ , pCodec(pTheCodec)
+ , uTrack(a_uTrack)
+ , offUUID(a_offID)
+ , cTotalBlocks(0)
+ , tcAbsLastWrittenMs(0)
+ {
+ uUUID = RTRandU32();
+ }
+
+ /** The type of this track. */
+ WebMTrackType enmType;
+ /** Pointer to codec data to use. */
+ PRECORDINGCODEC pCodec;
+ /** Track parameters. */
+ union
+ {
+ struct
+ {
+ /** Sample rate of input data. */
+ uint32_t uHz;
+ /** Duration of the frame in samples (per channel).
+ * Valid frame size are:
+ *
+ * ms Frame size
+ * 2.5 120
+ * 5 240
+ * 10 480
+ * 20 (Default) 960
+ * 40 1920
+ * 60 2880
+ */
+ uint16_t framesPerBlock;
+ /** How many milliseconds (ms) one written (simple) block represents. */
+ uint16_t msPerBlock;
+ } Audio;
+ };
+ /** This track's track number. Also used as key in track map. */
+ uint8_t uTrack;
+ /** The track's "UUID".
+ * Needed in case this track gets mux'ed with tracks from other files. Not really unique though. */
+ uint32_t uUUID;
+ /** Absolute offset in file of track UUID.
+ * Needed to write the hash sum within the footer. */
+ uint64_t offUUID;
+ /** Total number of blocks. */
+ uint64_t cTotalBlocks;
+ /** Absoute timecode (in ms) of last write. */
+ WebMTimecodeAbs tcAbsLastWrittenMs;
+ };
+
+ /**
+ * Structure for a single cue point track position entry.
+ */
+ struct WebMCueTrackPosEntry
+ {
+ WebMCueTrackPosEntry(uint64_t a_offCluster)
+ : offCluster(a_offCluster) { }
+
+ /** Offset (in bytes) of the related cluster containing the given position. */
+ uint64_t offCluster;
+ };
+
+ /** Map for keeping track position entries for a single cue point.
+ * The key is the track number (*not* UUID!). */
+ typedef std::map<uint8_t, WebMCueTrackPosEntry *> WebMCueTrackPosMap;
+
+ /**
+ * Structure for keeping a cue point.
+ */
+ struct WebMCuePoint
+ {
+ WebMCuePoint(WebMTimecodeAbs a_tcAbs)
+ : tcAbs(a_tcAbs) { }
+
+ virtual ~WebMCuePoint()
+ {
+ Clear();
+ }
+
+ void Clear(void)
+ {
+ WebMCueTrackPosMap::iterator itTrackPos = Pos.begin();
+ while (itTrackPos != Pos.end())
+ {
+ WebMCueTrackPosEntry *pTrackPos = itTrackPos->second;
+ AssertPtr(pTrackPos);
+ delete pTrackPos;
+
+ Pos.erase(itTrackPos);
+ itTrackPos = Pos.begin();
+ }
+
+ Assert(Pos.empty());
+ }
+
+ /** Map containing all track positions for this specific cue point. */
+ WebMCueTrackPosMap Pos;
+ /** Absolute time code according to the segment time base. */
+ WebMTimecodeAbs tcAbs;
+ };
+
+ /** List of cue points. */
+ typedef std::list<WebMCuePoint *> WebMCuePointList;
+
+ /**
+ * Structure for keeping a WebM cluster entry.
+ */
+ struct WebMCluster
+ {
+ WebMCluster(void)
+ : uID(0)
+ , offStart(0)
+ , fOpen(false)
+ , tcAbsStartMs(0)
+ , cBlocks(0) { }
+
+ /** This cluster's ID. */
+ uint64_t uID;
+ /** Absolute offset (in bytes) of this cluster.
+ * Needed for seeking info table. */
+ uint64_t offStart;
+ /** Whether this cluster element is opened currently. */
+ bool fOpen;
+ /** Absolute timecode (in ms) when this cluster starts. */
+ WebMTimecodeAbs tcAbsStartMs;
+ /** Absolute timecode (in ms) of when last written to this cluster. */
+ WebMTimecodeAbs tcAbsLastWrittenMs;
+ /** Number of (simple) blocks in this cluster. */
+ uint64_t cBlocks;
+ };
+
+ /**
+ * Structure for keeping a WebM segment entry.
+ *
+ * Current we're only using one segment.
+ */
+ struct WebMSegment
+ {
+ WebMSegment(void)
+ : m_tcAbsStartMs(0)
+ , m_tcAbsLastWrittenMs(0)
+ , m_offStart(0)
+ , m_offInfo(0)
+ , m_offSeekInfo(0)
+ , m_offTracks(0)
+ , m_offCues(0)
+ , m_cClusters(0)
+ {
+ m_uTimecodeScaleFactor = VBOX_WEBM_TIMECODESCALE_FACTOR_MS;
+
+ LogFunc(("Default timecode scale is: %RU64ns\n", m_uTimecodeScaleFactor));
+ }
+
+ virtual ~WebMSegment()
+ {
+ uninit();
+ }
+
+ /**
+ * Initializes a segment.
+ *
+ * @returns VBox status code.
+ */
+ int init(void)
+ {
+ return RTCritSectInit(&m_CritSect);
+ }
+
+ /**
+ * Uninitializes a segment.
+ */
+ void uninit(void)
+ {
+ clear();
+
+ RTCritSectDelete(&m_CritSect);
+ }
+
+ /**
+ * Clear the segment's data by removing (and freeing) all data.
+ */
+ void clear(void)
+ {
+ WebMCuePointList::iterator itCuePoint = m_lstCuePoints.begin();
+ while (itCuePoint != m_lstCuePoints.end())
+ {
+ WebMCuePoint *pCuePoint = (*itCuePoint);
+ AssertPtr(pCuePoint);
+ delete pCuePoint;
+
+ m_lstCuePoints.erase(itCuePoint);
+ itCuePoint = m_lstCuePoints.begin();
+ }
+
+ Assert(m_lstCuePoints.empty());
+ }
+
+ /** Critical section for serializing access to this segment. */
+ RTCRITSECT m_CritSect;
+
+ /** The timecode scale factor of this segment. */
+ uint64_t m_uTimecodeScaleFactor;
+
+ /** Absolute timecode (in ms) when starting this segment. */
+ WebMTimecodeAbs m_tcAbsStartMs;
+ /** Absolute timecode (in ms) of last write. */
+ WebMTimecodeAbs m_tcAbsLastWrittenMs;
+
+ /** Absolute offset (in bytes) of CurSeg. */
+ uint64_t m_offStart;
+ /** Absolute offset (in bytes) of general info. */
+ uint64_t m_offInfo;
+ /** Absolute offset (in bytes) of seeking info. */
+ uint64_t m_offSeekInfo;
+ /** Absolute offset (in bytes) of tracks. */
+ uint64_t m_offTracks;
+ /** Absolute offset (in bytes) of cues table. */
+ uint64_t m_offCues;
+ /** List of cue points. Needed for seeking table. */
+ WebMCuePointList m_lstCuePoints;
+
+ /** Total number of clusters. */
+ uint64_t m_cClusters;
+
+ /** Map of tracks.
+ * The key marks the track number (*not* the UUID!). */
+ std::map <uint8_t, WebMTrack *> m_mapTracks;
+
+ /** Current cluster which is being handled.
+ *
+ * Note that we don't need (and shouldn't need, as this can be a *lot* of data!) a
+ * list of all clusters. */
+ WebMCluster m_CurCluster;
+
+ WebMQueue m_queueBlocks;
+
+ } m_CurSeg;
+
+ /** Audio codec to use. */
+ RecordingAudioCodec_T m_enmAudioCodec;
+ /** Video codec to use. */
+ RecordingVideoCodec_T m_enmVideoCodec;
+
+ /** Whether we're currently in the tracks section. */
+ bool m_fInTracksSection;
+
+ /** Size of timecodes (in bytes). */
+ size_t m_cbTimecode;
+ /** Maximum value a timecode can have. */
+ uint32_t m_uTimecodeMax;
+
+#ifdef VBOX_WITH_LIBVPX
+ /**
+ * Block data for VP8-encoded video data.
+ */
+ struct BlockData_VP8
+ {
+ const vpx_codec_enc_cfg_t *pCfg;
+ const vpx_codec_cx_pkt_t *pPkt;
+ };
+#endif /* VBOX_WITH_LIBVPX */
+
+ /**
+ * Block data for encoded audio data.
+ */
+ struct BlockData_Audio
+ {
+ /** Pointer to encoded audio data. */
+ const void *pvData;
+ /** Size (in bytes) of encoded audio data. */
+ size_t cbData;
+ /** PTS (in ms) of encoded audio data. */
+ uint64_t uPTSMs;
+ };
+
+public:
+
+ WebMWriter();
+
+ virtual ~WebMWriter();
+
+public:
+
+ int OpenEx(const char *a_pszFilename, PRTFILE a_phFile,
+ RecordingAudioCodec_T a_enmAudioCodec, RecordingVideoCodec_T a_enmVideoCodec);
+
+ int Open(const char *a_pszFilename, uint64_t a_fOpen,
+ RecordingAudioCodec_T a_enmAudioCodec, RecordingVideoCodec_T a_enmVideoCodec);
+
+ int Close(void);
+
+ int AddAudioTrack(PRECORDINGCODEC pCodec, uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack);
+
+ int AddVideoTrack(PRECORDINGCODEC pCodec, uint16_t uWidth, uint16_t uHeight, uint32_t uFPS, uint8_t *puTrack);
+
+ int WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData, WebMTimecodeAbs tcAbsPTSMs, WebMBlockFlags uFlags);
+
+ const com::Utf8Str& GetFileName(void);
+
+ uint64_t GetFileSize(void);
+
+ uint64_t GetAvailableSpace(void);
+
+ /**
+ * Returns the number of written WebM clusters.
+ *
+ * @returns Number of written WebM clusters; 0 when no clusters written (empty file).
+ */
+ uint64_t GetClusters(void) const { return m_CurSeg.m_cClusters; }
+
+protected:
+
+ int init(RecordingAudioCodec_T a_enmAudioCodec, RecordingVideoCodec_T a_enmVideoCodec);
+
+ void destroy(void);
+
+ int writeHeader(void);
+
+ void writeSeekHeader(void);
+
+ int writeFooter(void);
+
+ int writeSimpleBlockEBML(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock);
+
+ int writeSimpleBlockQueued(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock);
+
+ int processQueue(WebMQueue *pQueue, bool fForce);
+
+protected:
+
+ typedef std::map <uint8_t, WebMTrack *> WebMTracks;
+};
+
+#endif /* !MAIN_INCLUDED_WebMWriter_h */
diff --git a/src/VBox/Main/include/Wrapper.h b/src/VBox/Main/include/Wrapper.h
new file mode 100644
index 00000000..8e994f30
--- /dev/null
+++ b/src/VBox/Main/include/Wrapper.h
@@ -0,0 +1,504 @@
+/* $Id: Wrapper.h $ */
+/** @file
+ * VirtualBox COM - API wrapper helpers.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_Wrapper_h
+#define MAIN_INCLUDED_Wrapper_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <vector>
+#include <VBox/com/ptr.h>
+#include <VBox/com/array.h>
+
+#include "AutoCaller.h"
+
+
+/**
+ * Checks that the given pointer to an output argument is valid and throws
+ * E_POINTER + extended error info otherwise.
+ * @param arg Pointer argument.
+ */
+#define CheckComArgOutPointerValidThrow(arg) \
+ do { \
+ if (RT_LIKELY(RT_VALID_PTR(arg))) \
+ { /* likely */ }\
+ /* Have to use use VirtualBoxBase::tr here or lupdate won't be able to figure out the context, \
+ as it is picking it up right here rather than in the places where the macro is actually used. */ \
+ else throw setError(E_POINTER, VirtualBoxBase::tr("Output argument %s points to invalid memory location (%p)"), \
+ #arg, (void *)(arg)); \
+ } while (0)
+
+
+class BSTROutConverter
+{
+public:
+ BSTROutConverter() : mDst(NULL)
+ {
+ }
+
+ BSTROutConverter(BSTR *aDst) : mDst(aDst)
+ {
+ }
+
+ ~BSTROutConverter()
+ {
+ if (mDst)
+ Bstr(mStr).detachTo(mDst);
+ }
+
+ com::Utf8Str &str()
+ {
+ return mStr;
+ }
+
+private:
+ com::Utf8Str mStr;
+ BSTR *mDst;
+};
+
+class BSTRInConverter
+{
+public:
+ BSTRInConverter() : mSrc()
+ {
+ }
+
+ BSTRInConverter(CBSTR aSrc) : mSrc(aSrc)
+ {
+ }
+
+ ~BSTRInConverter()
+ {
+ }
+
+ const com::Utf8Str &str()
+ {
+ return mSrc;
+ }
+
+private:
+ const com::Utf8Str mSrc;
+};
+
+class ArrayBSTROutConverter
+{
+public:
+ ArrayBSTROutConverter() :
+#ifdef VBOX_WITH_XPCOM
+ mDstSize(NULL),
+ mDst(NULL)
+#else // !VBOX_WITH_XPCOM
+ mDst(NULL)
+#endif // !VBOX_WITH_XPCOM
+ {
+ }
+
+ ArrayBSTROutConverter(ComSafeArrayOut(BSTR, aDst)) :
+#ifdef VBOX_WITH_XPCOM
+ mDstSize(aDstSize),
+ mDst(aDst)
+#else // !VBOX_WITH_XPCOM
+ mDst(aDst)
+#endif // !VBOX_WITH_XPCOM
+ {
+ }
+
+ ~ArrayBSTROutConverter()
+ {
+ if (mDst)
+ {
+ com::SafeArray<BSTR> outArray(mArray.size());
+ for (size_t i = 0; i < mArray.size(); i++)
+ Bstr(mArray[i]).detachTo(&outArray[i]);
+ outArray.detachTo(ComSafeArrayOutArg(mDst));
+ }
+ }
+
+ std::vector<com::Utf8Str> &array()
+ {
+ return mArray;
+ }
+
+private:
+ std::vector<com::Utf8Str> mArray;
+#ifdef VBOX_WITH_XPCOM
+ PRUint32 *mDstSize;
+ BSTR **mDst;
+#else // !VBOX_WITH_XPCOM
+ SAFEARRAY **mDst;
+#endif // !VBOX_WITH_XPCOM
+};
+
+class ArrayBSTRInConverter
+{
+public:
+ ArrayBSTRInConverter()
+ {
+ }
+
+ ArrayBSTRInConverter(ComSafeArrayIn(IN_BSTR, aSrc))
+ {
+ if (!ComSafeArrayInIsNull(aSrc))
+ {
+ com::SafeArray<IN_BSTR> inArray(ComSafeArrayInArg(aSrc));
+ mArray.resize(inArray.size());
+ for (size_t i = 0; i < inArray.size(); i++)
+ mArray[i] = inArray[i];
+ }
+ }
+
+ ~ArrayBSTRInConverter()
+ {
+ }
+
+ const std::vector<com::Utf8Str> &array()
+ {
+ return mArray;
+ }
+
+private:
+ std::vector<com::Utf8Str> mArray;
+};
+
+class UuidOutConverter
+{
+public:
+ UuidOutConverter() : mDst(NULL)
+ {
+ }
+
+ UuidOutConverter(BSTR *aDst) : mDst(aDst)
+ {
+ }
+
+ ~UuidOutConverter()
+ {
+ if (mDst)
+ mUuid.toUtf16().detachTo(mDst);
+ }
+
+ com::Guid &uuid()
+ {
+ return mUuid;
+ }
+
+private:
+ com::Guid mUuid;
+ BSTR *mDst;
+};
+
+class UuidInConverter
+{
+public:
+ UuidInConverter() : mSrc()
+ {
+ }
+
+ UuidInConverter(CBSTR aSrc) : mSrc(aSrc)
+ {
+ }
+
+ ~UuidInConverter()
+ {
+ }
+
+ const com::Guid &uuid()
+ {
+ return mSrc;
+ }
+
+private:
+ const com::Guid mSrc;
+};
+
+class ArrayUuidOutConverter
+{
+public:
+ ArrayUuidOutConverter() :
+#ifdef VBOX_WITH_XPCOM
+ mDstSize(NULL),
+ mDst(NULL)
+#else // !VBOX_WITH_XPCOM
+ mDst(NULL)
+#endif // !VBOX_WITH_XPCOM
+ {
+ }
+
+ ArrayUuidOutConverter(ComSafeArrayOut(BSTR, aDst)) :
+#ifdef VBOX_WITH_XPCOM
+ mDstSize(aDstSize),
+ mDst(aDst)
+#else // !VBOX_WITH_XPCOM
+ mDst(aDst)
+#endif // !VBOX_WITH_XPCOM
+ {
+ }
+
+ ~ArrayUuidOutConverter()
+ {
+ if (mDst)
+ {
+ com::SafeArray<BSTR> outArray(mArray.size());
+ for (size_t i = 0; i < mArray.size(); i++)
+ mArray[i].toUtf16().detachTo(&outArray[i]);
+ outArray.detachTo(ComSafeArrayOutArg(mDst));
+ }
+ }
+
+ std::vector<com::Guid> &array()
+ {
+ return mArray;
+ }
+
+private:
+ std::vector<com::Guid> mArray;
+#ifdef VBOX_WITH_XPCOM
+ PRUint32 *mDstSize;
+ BSTR **mDst;
+#else // !VBOX_WITH_XPCOM
+ SAFEARRAY **mDst;
+#endif // !VBOX_WITH_XPCOM
+};
+
+template <class A>
+class ComTypeOutConverter
+{
+public:
+ ComTypeOutConverter() : mDst(NULL)
+ {
+ }
+
+ ComTypeOutConverter(A **aDst) : mDst(aDst)
+ {
+ }
+
+ ~ComTypeOutConverter()
+ {
+ if (mDst)
+ mPtr.queryInterfaceTo(mDst);
+ }
+
+ ComPtr<A> &ptr()
+ {
+ return mPtr;
+ }
+
+private:
+ ComPtr<A> mPtr;
+ A **mDst;
+};
+
+template <class A>
+class ComTypeInConverter
+{
+public:
+ ComTypeInConverter() : mSrc(NULL)
+ {
+ }
+
+ ComTypeInConverter(A *aSrc) : mSrc(aSrc)
+ {
+ }
+
+ ~ComTypeInConverter()
+ {
+ }
+
+ const ComPtr<A> &ptr()
+ {
+ return mSrc;
+ }
+
+private:
+ const ComPtr<A> mSrc;
+};
+
+template <class A>
+class ArrayComTypeOutConverter
+{
+public:
+ ArrayComTypeOutConverter() :
+#ifdef VBOX_WITH_XPCOM
+ mDstSize(NULL),
+ mDst(NULL)
+#else // !VBOX_WITH_XPCOM
+ mDst(NULL)
+#endif // !VBOX_WITH_XPCOM
+ {
+ }
+
+ ArrayComTypeOutConverter(ComSafeArrayOut(A *, aDst)) :
+#ifdef VBOX_WITH_XPCOM
+ mDstSize(aDstSize),
+ mDst(aDst)
+#else // !VBOX_WITH_XPCOM
+ mDst(aDst)
+#endif // !VBOX_WITH_XPCOM
+ {
+ }
+
+ ~ArrayComTypeOutConverter()
+ {
+ if (mDst)
+ {
+ com::SafeIfaceArray<A> outArray(mArray);
+ outArray.detachTo(ComSafeArrayOutArg(mDst));
+ }
+ }
+
+ std::vector<ComPtr<A> > &array()
+ {
+ return mArray;
+ }
+
+private:
+ std::vector<ComPtr<A> > mArray;
+#ifdef VBOX_WITH_XPCOM
+ PRUint32 *mDstSize;
+ A ***mDst;
+#else // !VBOX_WITH_XPCOM
+ SAFEARRAY **mDst;
+#endif // !VBOX_WITH_XPCOM
+};
+
+template <class A>
+class ArrayComTypeInConverter
+{
+public:
+ ArrayComTypeInConverter()
+ {
+ }
+
+ ArrayComTypeInConverter(ComSafeArrayIn(A *, aSrc))
+ {
+ if (!ComSafeArrayInIsNull(aSrc))
+ {
+ com::SafeIfaceArray<A> inArray(ComSafeArrayInArg(aSrc));
+ mArray.resize(inArray.size());
+ for (size_t i = 0; i < inArray.size(); i++)
+ mArray[i] = inArray[i];
+ }
+ }
+
+ ~ArrayComTypeInConverter()
+ {
+ }
+
+ const std::vector<ComPtr<A> > &array()
+ {
+ return mArray;
+ }
+
+private:
+ std::vector<ComPtr<A> > mArray;
+};
+
+template <typename A>
+class ArrayOutConverter
+{
+public:
+ ArrayOutConverter() :
+#ifdef VBOX_WITH_XPCOM
+ mDstSize(NULL),
+ mDst(NULL)
+#else // !VBOX_WITH_XPCOM
+ mDst(NULL)
+#endif // !VBOX_WITH_XPCOM
+ {
+ }
+
+ ArrayOutConverter(ComSafeArrayOut(A, aDst)) :
+#ifdef VBOX_WITH_XPCOM
+ mDstSize(aDstSize),
+ mDst(aDst)
+#else // !VBOX_WITH_XPCOM
+ mDst(aDst)
+#endif // !VBOX_WITH_XPCOM
+ {
+ }
+
+ ~ArrayOutConverter()
+ {
+ if (mDst)
+ {
+ com::SafeArray<A> outArray(mArray.size());
+ for (size_t i = 0; i < mArray.size(); i++)
+ outArray[i] = mArray[i];
+ outArray.detachTo(ComSafeArrayOutArg(mDst));
+ }
+ }
+
+ std::vector<A> &array()
+ {
+ return mArray;
+ }
+
+private:
+ std::vector<A> mArray;
+#ifdef VBOX_WITH_XPCOM
+ PRUint32 *mDstSize;
+ A **mDst;
+#else // !VBOX_WITH_XPCOM
+ SAFEARRAY **mDst;
+#endif // !VBOX_WITH_XPCOM
+};
+
+template <typename A>
+class ArrayInConverter
+{
+public:
+ ArrayInConverter()
+ {
+ }
+
+ ArrayInConverter(ComSafeArrayIn(A, aSrc))
+ {
+ if (!ComSafeArrayInIsNull(aSrc))
+ {
+ com::SafeArray<A> inArray(ComSafeArrayInArg(aSrc));
+ mArray.resize(inArray.size());
+ for (size_t i = 0; i < inArray.size(); i++)
+ mArray[i] = inArray[i];
+ }
+ }
+
+ ~ArrayInConverter()
+ {
+ }
+
+ const std::vector<A> &array()
+ {
+ return mArray;
+ }
+
+private:
+ std::vector<A> mArray;
+};
+
+#endif /* !MAIN_INCLUDED_Wrapper_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/netif.h b/src/VBox/Main/include/netif.h
new file mode 100644
index 00000000..a48b8c92
--- /dev/null
+++ b/src/VBox/Main/include/netif.h
@@ -0,0 +1,145 @@
+/* $Id: netif.h $ */
+/** @file
+ * Main - Network Interfaces.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_netif_h
+#define MAIN_INCLUDED_netif_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+#include <iprt/types.h>
+#include <iprt/net.h>
+/** @todo r=bird: The inlined code below that drags in asm.h here. I doubt
+ * speed is very important here, so move it into a .cpp file, please. */
+#include <iprt/asm.h>
+
+#ifndef RT_OS_WINDOWS
+# include <arpa/inet.h>
+# include <stdio.h>
+#endif /* !RT_OS_WINDOWS */
+
+#define VBOXNET_IPV4ADDR_DEFAULT 0x0138A8C0 /* 192.168.56.1 */
+#define VBOXNET_IPV4MASK_DEFAULT "255.255.255.0"
+
+#define VBOXNET_MAX_SHORT_NAME 50
+
+#if 1
+/**
+ * Encapsulation type.
+ * @note Must match HostNetworkInterfaceMediumType_T exactly.
+ * @todo r=bird: Why are we duplicating HostNetworkInterfaceMediumType_T here?!?
+ */
+typedef enum NETIFTYPE
+{
+ NETIF_T_UNKNOWN,
+ NETIF_T_ETHERNET,
+ NETIF_T_PPP,
+ NETIF_T_SLIP
+} NETIFTYPE;
+
+/**
+ * Current state of the interface.
+ * @note Must match HostNetworkInterfaceStatus_T exactly.
+ * @todo r=bird: Why are we duplicating HostNetworkInterfaceStatus_T here?!?
+ */
+typedef enum NETIFSTATUS
+{
+ NETIF_S_UNKNOWN,
+ NETIF_S_UP,
+ NETIF_S_DOWN
+} NETIFSTATUS;
+
+/**
+ * Host Network Interface Information.
+ */
+typedef struct NETIFINFO
+{
+ NETIFINFO *pNext;
+ RTNETADDRIPV4 IPAddress;
+ RTNETADDRIPV4 IPNetMask;
+ RTNETADDRIPV6 IPv6Address;
+ RTNETADDRIPV6 IPv6NetMask;
+ BOOL fDhcpEnabled;
+ BOOL fIsDefault;
+ BOOL fWireless;
+ RTMAC MACAddress;
+ NETIFTYPE enmMediumType;
+ NETIFSTATUS enmStatus;
+ uint32_t uSpeedMbits;
+ RTUUID Uuid;
+ char szShortName[VBOXNET_MAX_SHORT_NAME];
+ char szName[1];
+} NETIFINFO;
+
+/** Pointer to a network interface info. */
+typedef NETIFINFO *PNETIFINFO;
+/** Pointer to a const network interface info. */
+typedef NETIFINFO const *PCNETIFINFO;
+#endif
+
+int NetIfList(std::list <ComObjPtr<HostNetworkInterface> > &list);
+int NetIfEnableStaticIpConfig(VirtualBox *pVBox, HostNetworkInterface * pIf, ULONG aOldIp, ULONG aNewIp, ULONG aMask);
+int NetIfEnableStaticIpConfigV6(VirtualBox *pVBox, HostNetworkInterface *pIf, const Utf8Str &aOldIPV6Address, const Utf8Str &aIPV6Address, ULONG aIPV6MaskPrefixLength);
+int NetIfEnableDynamicIpConfig(VirtualBox *pVBox, HostNetworkInterface * pIf);
+#ifdef RT_OS_WINDOWS
+int NetIfCreateHostOnlyNetworkInterface(VirtualBox *pVBox, IHostNetworkInterface **aHostNetworkInterface, IProgress **aProgress,
+ IN_BSTR bstrName = NULL);
+#else
+int NetIfCreateHostOnlyNetworkInterface(VirtualBox *pVBox, IHostNetworkInterface **aHostNetworkInterface, IProgress **aProgress,
+ const char *pszName = NULL);
+#endif
+int NetIfRemoveHostOnlyNetworkInterface(VirtualBox *pVBox, const Guid &aId, IProgress **aProgress);
+int NetIfGetConfig(HostNetworkInterface * pIf, NETIFINFO *);
+int NetIfGetConfigByName(PNETIFINFO pInfo);
+int NetIfGetState(const char *pcszIfName, NETIFSTATUS *penmState);
+int NetIfGetLinkSpeed(const char *pcszIfName, uint32_t *puMbits);
+int NetIfDhcpRediscover(VirtualBox *pVBox, HostNetworkInterface * pIf);
+int NetIfAdpCtlOut(const char *pszName, const char *pszCmd, char *pszBuffer, size_t cBufSize);
+
+DECLINLINE(Bstr) getDefaultIPv4Address(Bstr bstrIfName)
+{
+ /* Get the index from the name */
+ Utf8Str strTmp = bstrIfName;
+ const char *pcszIfName = strTmp.c_str();
+ size_t iPos = strcspn(pcszIfName, "0123456789");
+ uint32_t uInstance = 0;
+ if (pcszIfName[iPos])
+ uInstance = RTStrToUInt32(pcszIfName + iPos);
+
+ in_addr tmp;
+#if defined(RT_OS_WINDOWS)
+ tmp.S_un.S_addr = VBOXNET_IPV4ADDR_DEFAULT + (uInstance << 16);
+#else
+ tmp.s_addr = VBOXNET_IPV4ADDR_DEFAULT + (uInstance << 16);
+#endif
+ char *addr = inet_ntoa(tmp);
+ return Bstr(addr);
+}
+
+#endif /* !MAIN_INCLUDED_netif_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/objectslist.h b/src/VBox/Main/include/objectslist.h
new file mode 100644
index 00000000..2dd2f92d
--- /dev/null
+++ b/src/VBox/Main/include/objectslist.h
@@ -0,0 +1,224 @@
+/* $Id: objectslist.h $ */
+/** @file
+ *
+ * List of COM objects
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_objectslist_h
+#define MAIN_INCLUDED_objectslist_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <list>
+#include <VBox/com/ptr.h>
+
+/**
+ * Implements a "flat" objects list with a lock. Since each such list
+ * has its own lock it is not a good idea to implement trees with this.
+ *
+ * ObjectList<T> is designed to behave as if it were a std::list of
+ * COM pointers of class T; in other words,
+ * ObjectList<Medium> behaves like std::list< ComObjPtr<Medium> > but
+ * it's less typing. Iterators, front(), size(), begin() and end()
+ * are implemented.
+ *
+ * In addition it automatically includes an RWLockHandle which can be
+ * accessed with getLockHandle().
+ *
+ * If you need the raw std::list for some reason you can access it with
+ * getList().
+ *
+ * The destructor automatically calls uninit() on every contained
+ * COM object. If this is not desired, clear the member list before
+ * deleting the list object.
+ */
+template<typename T>
+class ObjectsList
+{
+public:
+ typedef ComObjPtr<T> MyType;
+ typedef std::list<MyType> MyList;
+
+ typedef typename MyList::iterator iterator;
+ typedef typename MyList::const_iterator const_iterator;
+ // typename is necessary to disambiguate "::iterator" in templates; see
+ // the "this might hurt your head" part in
+ // http://www.parashift.com/c++-faq-lite/templates.html#faq-35.18
+
+ ObjectsList(RWLockHandle &lockHandle)
+ : m_lock(lockHandle)
+ { }
+
+ ~ObjectsList()
+ {
+ uninitAll();
+ }
+
+private:
+ // prohibit copying and assignment
+ ObjectsList(const ObjectsList &d);
+ ObjectsList& operator=(const ObjectsList &d);
+
+public:
+
+ /**
+ * Returns the lock handle which protects this list, for use with
+ * AutoReadLock or AutoWriteLock.
+ */
+ RWLockHandle& getLockHandle()
+ {
+ return m_lock;
+ }
+
+ /**
+ * Calls m_ll.push_back(p) with locking.
+ * @param p
+ */
+ void addChild(MyType p)
+ {
+ AutoWriteLock al(m_lock COMMA_LOCKVAL_SRC_POS);
+ m_ll.push_back(p);
+ }
+
+ /**
+ * Calls m_ll.remove(p) with locking. Does NOT call uninit()
+ * on the contained object.
+ * @param p
+ */
+ void removeChild(MyType p)
+ {
+ AutoWriteLock al(m_lock COMMA_LOCKVAL_SRC_POS);
+ m_ll.remove(p);
+ }
+
+ /**
+ * Appends all objects from another list to the member list.
+ * Locks the other list for reading but does not lock "this"
+ * (because it might be on the caller's stack and needs no
+ * locking).
+ * @param ll
+ */
+ void appendOtherList(ObjectsList<T> &ll)
+ {
+ AutoReadLock alr(ll.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ for (const_iterator it = ll.begin();
+ it != ll.end();
+ ++it)
+ {
+ m_ll.push_back(*it);
+ }
+ }
+
+ /**
+ * Calls uninit() on every COM object on the list and then
+ * clears the list, with locking.
+ */
+ void uninitAll()
+ {
+ /* The implementation differs from the high level description, because
+ * it isn't safe to hold any locks when invoking uninit() methods. It
+ * leads to incorrect lock order (first lock, then the Caller related
+ * event semaphore) and thus deadlocks. Dropping the lock is vital,
+ * and means we can't rely on iterators while not holding the lock. */
+ AutoWriteLock al(m_lock COMMA_LOCKVAL_SRC_POS);
+ while (!m_ll.empty())
+ {
+ /* Need a copy of the element, have to delete the entry before
+ * dropping the lock, otherwise someone else might mess with the
+ * list in the mean time, leading to erratic behavior. */
+ MyType q = m_ll.front();
+ m_ll.pop_front();
+ al.release();
+ q->uninit();
+ al.acquire();
+ }
+ }
+
+ /**
+ * Returns the no. of objects on the list (std::list compatibility)
+ * with locking.
+ */
+ size_t size()
+ {
+ AutoReadLock al(m_lock COMMA_LOCKVAL_SRC_POS);
+ return m_ll.size();
+ }
+
+ /**
+ * Returns a raw pointer to the member list of objects.
+ * Does not lock!
+ * @return
+ */
+ MyList& getList()
+ {
+ return m_ll;
+ }
+
+ /**
+ * Returns the first object on the list (std::list compatibility)
+ * with locking.
+ */
+ MyType front()
+ {
+ AutoReadLock al(m_lock COMMA_LOCKVAL_SRC_POS);
+ return m_ll.front();
+ }
+
+ /**
+ * Returns the begin iterator from the list (std::list compatibility).
+ * Does not lock!
+ * @return
+ */
+ iterator begin()
+ {
+ return m_ll.begin();
+ }
+
+ /**
+ * Returns the end iterator from the list (std::list compatibility).
+ * Does not lock!
+ */
+ iterator end()
+ {
+ return m_ll.end();
+ }
+
+ void insert(iterator it, MyType &p)
+ {
+ m_ll.insert(it, p);
+ }
+
+ void erase(iterator it)
+ {
+ m_ll.erase(it);
+ }
+
+private:
+ MyList m_ll;
+ RWLockHandle &m_lock;
+};
+
+#endif /* !MAIN_INCLUDED_objectslist_h */
diff --git a/src/VBox/Main/include/ovfreader.h b/src/VBox/Main/include/ovfreader.h
new file mode 100644
index 00000000..848434b6
--- /dev/null
+++ b/src/VBox/Main/include/ovfreader.h
@@ -0,0 +1,732 @@
+/* $Id: ovfreader.h $ */
+/** @file
+ * VirtualBox Main - OVF reader declarations.
+ *
+ * Depends only on IPRT, including the RTCString and IPRT XML classes.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_ovfreader_h
+#define MAIN_INCLUDED_ovfreader_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "iprt/cpp/xml.h"
+#include <map>
+#include <vector>
+
+namespace ovf
+{
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Errors
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Thrown by OVFReader for any kind of error that is not an XML error but
+ * still makes the OVF impossible to parse. Based on xml::LogicError so
+ * that one catch() for all xml::LogicError can handle all possible errors.
+ */
+class OVFLogicError : public xml::LogicError
+{
+public:
+ OVFLogicError(const char *aFormat, ...);
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Enumerations
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * CIM OS values.
+ *
+ * The OVF 1.10 spec refers to some CIM_OperatingSystem.mof doc. Could this be it:
+ * http://cvs.opengroup.org/cgi-bin/cvsweb.cgi/pegasus/Schemas/CIM231/DMTF/System/CIM_OperatingSystem.mof
+ *
+ * @todo r=bird: Why are the values are repeating 'CIMOS'. CIMOSType_T is also
+ * repeating it self, 'Type' and '_T'. Why not call it kCIOMOpSys,
+ * easier to read as well.
+ * Then also apply: s/CIMOSType_CIMOS_/kCIMOpSys_/g
+ */
+enum CIMOSType_T
+{
+ CIMOSType_CIMOS_Unknown = 0,
+ CIMOSType_CIMOS_Other = 1,
+ CIMOSType_CIMOS_MACOS = 2,
+ CIMOSType_CIMOS_ATTUNIX = 3,
+ CIMOSType_CIMOS_DGUX = 4,
+ CIMOSType_CIMOS_DECNT = 5,
+ CIMOSType_CIMOS_Tru64UNIX = 6,
+ CIMOSType_CIMOS_OpenVMS = 7,
+ CIMOSType_CIMOS_HPUX = 8,
+ CIMOSType_CIMOS_AIX = 9,
+ CIMOSType_CIMOS_MVS = 10,
+ CIMOSType_CIMOS_OS400 = 11,
+ CIMOSType_CIMOS_OS2 = 12,
+ CIMOSType_CIMOS_JavaVM = 13,
+ CIMOSType_CIMOS_MSDOS = 14,
+ CIMOSType_CIMOS_WIN3x = 15,
+ CIMOSType_CIMOS_WIN95 = 16,
+ CIMOSType_CIMOS_WIN98 = 17,
+ CIMOSType_CIMOS_WINNT = 18,
+ CIMOSType_CIMOS_WINCE = 19,
+ CIMOSType_CIMOS_NCR3000 = 20,
+ CIMOSType_CIMOS_NetWare = 21,
+ CIMOSType_CIMOS_OSF = 22,
+ CIMOSType_CIMOS_DCOS = 23,
+ CIMOSType_CIMOS_ReliantUNIX = 24,
+ CIMOSType_CIMOS_SCOUnixWare = 25,
+ CIMOSType_CIMOS_SCOOpenServer = 26,
+ CIMOSType_CIMOS_Sequent = 27,
+ CIMOSType_CIMOS_IRIX = 28,
+ CIMOSType_CIMOS_Solaris = 29,
+ CIMOSType_CIMOS_SunOS = 30,
+ CIMOSType_CIMOS_U6000 = 31,
+ CIMOSType_CIMOS_ASERIES = 32,
+ CIMOSType_CIMOS_HPNonStopOS = 33,
+ CIMOSType_CIMOS_HPNonStopOSS = 34,
+ CIMOSType_CIMOS_BS2000 = 35,
+ CIMOSType_CIMOS_LINUX = 36,
+ CIMOSType_CIMOS_Lynx = 37,
+ CIMOSType_CIMOS_XENIX = 38,
+ CIMOSType_CIMOS_VM = 39,
+ CIMOSType_CIMOS_InteractiveUNIX = 40,
+ CIMOSType_CIMOS_BSDUNIX = 41,
+ CIMOSType_CIMOS_FreeBSD = 42,
+ CIMOSType_CIMOS_NetBSD = 43,
+ CIMOSType_CIMOS_GNUHurd = 44,
+ CIMOSType_CIMOS_OS9 = 45,
+ CIMOSType_CIMOS_MACHKernel = 46,
+ CIMOSType_CIMOS_Inferno = 47,
+ CIMOSType_CIMOS_QNX = 48,
+ CIMOSType_CIMOS_EPOC = 49,
+ CIMOSType_CIMOS_IxWorks = 50,
+ CIMOSType_CIMOS_VxWorks = 51,
+ CIMOSType_CIMOS_MiNT = 52,
+ CIMOSType_CIMOS_BeOS = 53,
+ CIMOSType_CIMOS_HPMPE = 54,
+ CIMOSType_CIMOS_NextStep = 55,
+ CIMOSType_CIMOS_PalmPilot = 56,
+ CIMOSType_CIMOS_Rhapsody = 57,
+ CIMOSType_CIMOS_Windows2000 = 58,
+ CIMOSType_CIMOS_Dedicated = 59,
+ CIMOSType_CIMOS_OS390 = 60,
+ CIMOSType_CIMOS_VSE = 61,
+ CIMOSType_CIMOS_TPF = 62,
+ CIMOSType_CIMOS_WindowsMe = 63,
+ CIMOSType_CIMOS_CalderaOpenUNIX = 64,
+ CIMOSType_CIMOS_OpenBSD = 65,
+ CIMOSType_CIMOS_NotApplicable = 66,
+ CIMOSType_CIMOS_WindowsXP = 67,
+ CIMOSType_CIMOS_zOS = 68,
+ CIMOSType_CIMOS_MicrosoftWindowsServer2003 = 69,
+ CIMOSType_CIMOS_MicrosoftWindowsServer2003_64 = 70,
+ CIMOSType_CIMOS_WindowsXP_64 = 71,
+ CIMOSType_CIMOS_WindowsXPEmbedded = 72,
+ CIMOSType_CIMOS_WindowsVista = 73,
+ CIMOSType_CIMOS_WindowsVista_64 = 74,
+ CIMOSType_CIMOS_WindowsEmbeddedforPointofService = 75,
+ CIMOSType_CIMOS_MicrosoftWindowsServer2008 = 76,
+ CIMOSType_CIMOS_MicrosoftWindowsServer2008_64 = 77,
+ CIMOSType_CIMOS_FreeBSD_64 = 78,
+ CIMOSType_CIMOS_RedHatEnterpriseLinux = 79,
+ CIMOSType_CIMOS_RedHatEnterpriseLinux_64 = 80,
+ CIMOSType_CIMOS_Solaris_64 = 81,
+ CIMOSType_CIMOS_SUSE = 82,
+ CIMOSType_CIMOS_SUSE_64 = 83,
+ CIMOSType_CIMOS_SLES = 84,
+ CIMOSType_CIMOS_SLES_64 = 85,
+ CIMOSType_CIMOS_NovellOES = 86,
+ CIMOSType_CIMOS_NovellLinuxDesktop = 87,
+ CIMOSType_CIMOS_SunJavaDesktopSystem = 88,
+ CIMOSType_CIMOS_Mandriva = 89,
+ CIMOSType_CIMOS_Mandriva_64 = 90,
+ CIMOSType_CIMOS_TurboLinux = 91,
+ CIMOSType_CIMOS_TurboLinux_64 = 92,
+ CIMOSType_CIMOS_Ubuntu = 93,
+ CIMOSType_CIMOS_Ubuntu_64 = 94,
+ CIMOSType_CIMOS_Debian = 95,
+ CIMOSType_CIMOS_Debian_64 = 96,
+ CIMOSType_CIMOS_Linux_2_4_x = 97,
+ CIMOSType_CIMOS_Linux_2_4_x_64 = 98,
+ CIMOSType_CIMOS_Linux_2_6_x = 99,
+ CIMOSType_CIMOS_Linux_2_6_x_64 = 100,
+ CIMOSType_CIMOS_Linux_64 = 101,
+ CIMOSType_CIMOS_Other_64 = 102,
+ // types added with CIM 2.25.0 follow:
+ CIMOSType_CIMOS_WindowsServer2008R2 = 103,
+ CIMOSType_CIMOS_VMwareESXi = 104,
+ CIMOSType_CIMOS_Windows7 = 105,
+ CIMOSType_CIMOS_CentOS = 106,
+ CIMOSType_CIMOS_CentOS_64 = 107,
+ CIMOSType_CIMOS_OracleLinux = 108,
+ CIMOSType_CIMOS_OracleLinux_64 = 109,
+ CIMOSType_CIMOS_eComStation = 110,
+ // no new types added with CIM 2.26.0
+ CIMOSType_CIMOS_WindowsServer2011 = 111,
+ CIMOSType_CIMOS_WindowsServer2012 = 112,
+ CIMOSType_CIMOS_Windows8 = 113,
+ CIMOSType_CIMOS_Windows8_64 = 114,
+ CIMOSType_CIMOS_WindowsServer2012R2 = 115,
+ CIMOSType_CIMOS_Windows8_1 = 116,
+ CIMOSType_CIMOS_Windows8_1_64 = 117,
+ CIMOSType_CIMOS_WindowsServer2016 = 118,
+ CIMOSType_CIMOS_Windows10 = 119,
+ CIMOSType_CIMOS_Windows10_64 = 120,
+ // the above covers up to CIM 2.52.0, without checking when it was added
+};
+
+enum OVFVersion_T
+{
+ OVFVersion_unknown,
+ OVFVersion_0_9,
+ OVFVersion_1_0,
+ OVFVersion_2_0
+};
+
+const char* const OVF09_URI_string = "http://www.vmware.com/schema/ovf/1/envelope";
+const char* const OVF10_URI_string = "http://schemas.dmtf.org/ovf/envelope/1";
+const char* const OVF20_URI_string = "http://schemas.dmtf.org/ovf/envelope/2";
+
+const char* const DTMF_SPECS_URI = "http://schemas.dmtf.org/wbem/cim-html/2/";
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Envelope data
+//
+////////////////////////////////////////////////////////////////////////////////
+struct EnvelopeData
+{
+ OVFVersion_T version;//OVF standard version, it is used internally only by VirtualBox
+ RTCString lang;//language
+
+ OVFVersion_T getOVFVersion() const
+ {
+ return version;
+ }
+
+
+ RTCString getStringOVFVersion() const
+ {
+ if (version == OVFVersion_0_9)
+ return "0.9";
+ else if (version == OVFVersion_1_0)
+ return "1.0";
+ else if (version == OVFVersion_2_0)
+ return "2.0";
+ else
+ return "";
+ }
+
+ void setOVFVersion(OVFVersion_T v)
+ {
+ version = v;
+ }
+};
+
+
+struct FileReference
+{
+ RTCString strHref; // value from /References/File/@href (filename)
+ RTCString strDiskId; // value from /References/File/@id ()
+};
+
+typedef std::map<uint32_t, FileReference> FileReferenceMap;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Hardware definition structs
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct DiskImage
+{
+ // fields from /DiskSection/Disk
+ RTCString strDiskId; // value from DiskSection/Disk/@diskId
+ int64_t iCapacity; // value from DiskSection/Disk/@capacity;
+ // (maximum size for dynamic images, I guess; we always translate this to bytes)
+ int64_t iPopulatedSize; // optional value from DiskSection/Disk/@populatedSize
+ // (actual used size of disk, always in bytes; can be an estimate of used disk
+ // space, but cannot be larger than iCapacity; -1 if not set)
+ RTCString strFormat; // value from DiskSection/Disk/@format
+ // typically http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized
+ RTCString uuidVBox; // optional; if the file was exported by VirtualBox >= 3.2,
+ // then this has the UUID with which the disk was registered
+
+ // fields from /References/File; the spec says the file reference from disk can be empty,
+ // so in that case, strFilename will be empty, then a new disk should be created
+ RTCString strHref; // value from /References/File/@href (filename); if empty, then the remaining fields are ignored
+ int64_t iSize; // value from /References/File/@size (optional according to spec; then we set -1 here)
+ int64_t iChunkSize; // value from /References/File/@chunkSize (optional, unsupported)
+ RTCString strCompression; // value from /References/File/@compression (optional, can be "gzip" according to spec)
+
+ // additional field which has a descriptive size in megabytes derived from the above; this can be used for progress reports
+ uint32_t ulSuggestedSizeMB;
+};
+
+enum ResourceType_T
+{
+ ResourceType_Other = 1,
+ ResourceType_ComputerSystem = 2,
+ ResourceType_Processor = 3,
+ ResourceType_Memory = 4,
+ ResourceType_IDEController = 5,
+ ResourceType_ParallelSCSIHBA = 6,
+ ResourceType_FCHBA = 7,
+ ResourceType_iSCSIHBA = 8,
+ ResourceType_IBHCA = 9,
+ ResourceType_EthernetAdapter = 10,
+ ResourceType_OtherNetworkAdapter = 11,
+ ResourceType_IOSlot = 12,
+ ResourceType_IODevice = 13,
+ ResourceType_FloppyDrive = 14,
+ ResourceType_CDDrive = 15,
+ ResourceType_DVDDrive = 16,
+ ResourceType_HardDisk = 17,
+ ResourceType_TapeDrive = 18,
+ ResourceType_StorageExtent = 19,
+ ResourceType_OtherStorageDevice = 20,
+ ResourceType_SerialPort = 21,
+ ResourceType_ParallelPort = 22,
+ ResourceType_USBController = 23,
+ ResourceType_GraphicsController = 24,
+ ResourceType_IEEE1394Controller = 25,
+ ResourceType_PartitionableUnit = 26,
+ ResourceType_BasePartitionableUnit = 27,
+ ResourceType_Power = 28,
+ ResourceType_CoolingCapacity = 29,
+ ResourceType_EthernetSwitchPort = 30,
+ ResourceType_LogicalDisk = 31,
+ ResourceType_StorageVolume = 32,
+ ResourceType_EthernetConnection = 33,
+ ResourceType_SoundCard = 35 /**< @todo r=klaus: Not part of OVF/CIM spec, should use "Other" or some value from 0x8000..0xffff. */
+};
+
+
+enum StorageAccessType_T
+{ StorageAccessType_Unknown = 0,
+ StorageAccessType_Readable = 1,
+ StorageAccessType_Writeable = 2,
+ StorageAccessType_ReadWrite = 3
+};
+
+enum ComplianceType_T
+{ ComplianceType_No = 0,
+ ComplianceType_Soft = 1,
+ ComplianceType_Medium = 2,
+ ComplianceType_Strong = 3
+};
+
+class VirtualHardwareItem
+{
+public:
+ RTCString strDescription;
+ RTCString strCaption;
+ RTCString strElementName;
+
+ RTCString strInstanceID;
+ RTCString strParent;
+
+ ResourceType_T resourceType;
+ RTCString strOtherResourceType;
+ RTCString strResourceSubType;
+ bool fResourceRequired;
+
+ RTCString strHostResource; ///< "Abstractly specifies how a device shall connect to a resource on the deployment platform.
+ /// Not all devices need a backing." Used with disk items, for which this
+ /// references a virtual disk from the Disks section.
+ bool fAutomaticAllocation;
+ bool fAutomaticDeallocation;
+ RTCString strConnection; ///< "All Ethernet adapters that specify the same abstract network connection name within an OVF
+ /// package shall be deployed on the same network. The abstract network connection name shall be
+ /// listed in the NetworkSection at the outermost envelope level." We ignore this and only set up
+ /// a network adapter depending on the network name.
+ RTCString strAddress; ///< "Device-specific. For an Ethernet adapter, this specifies the MAC address."
+ int32_t lAddress; ///< strAddress as an integer, if applicable.
+ RTCString strAddressOnParent;///< "For a device, this specifies its location on the controller."
+ RTCString strAllocationUnits;///< "Specifies the units of allocation used. For example, “byte * 2^20â€."
+ uint64_t ullVirtualQuantity; ///< "Specifies the quantity of resources presented. For example, “256â€."
+ uint64_t ullReservation; ///< "Specifies the minimum quantity of resources guaranteed to be available."
+ uint64_t ullLimit; ///< "Specifies the maximum quantity of resources that will be granted."
+ uint64_t ullWeight; ///< "Specifies a relative priority for this allocation in relation to other allocations."
+
+ RTCString strConsumerVisibility;
+ RTCString strMappingBehavior;
+ RTCString strPoolID;
+ uint32_t ulBusNumber; ///< seen with IDE controllers, but not listed in OVF spec
+
+ int m_iLineNumber; ///< line number of \<Item\> element in XML source; cached for error messages
+
+ VirtualHardwareItem()
+ : fResourceRequired(false)
+ , fAutomaticAllocation(false)
+ , fAutomaticDeallocation(false)
+ , ullVirtualQuantity(0)
+ , ullReservation(0)
+ , ullLimit(0)
+ , ullWeight(0)
+ , ulBusNumber(0)
+ , m_iLineNumber(0)
+ , fDefault(false)
+ {
+ itemName = "Item";
+ }
+
+ virtual ~VirtualHardwareItem() { /* Makes MSC happy. */ }
+
+ void fillItem(const xml::ElementNode *item);
+
+ void setDefaultFlag()
+ {
+ fDefault = true;
+ }
+
+ bool isThereDefaultValues() const
+ {
+ return fDefault;
+ }
+
+ void checkConsistencyAndCompliance() RT_THROW(OVFLogicError)
+ {
+ _checkConsistencyAndCompliance();
+ }
+
+protected:
+ virtual void _checkConsistencyAndCompliance() RT_THROW(OVFLogicError);
+ virtual const RTCString& getItemName()
+ {
+ return _getItemName();
+ }
+
+private:
+ RTCString itemName;
+ bool fDefault;//true means that some fields were absent in the XML and some default values were assigned to.
+
+ virtual const RTCString& _getItemName()
+ {
+ return itemName;
+ }
+};
+
+class StorageItem: public VirtualHardwareItem
+{
+ //see DMTF Schema Documentation http://schemas.dmtf.org/wbem/cim-html/2/
+ StorageAccessType_T accessType;
+ RTCString strHostExtentName;
+#if 0 /* unused */
+ int16_t hostExtentNameFormat;
+ int16_t hostExtentNameNamespace;
+ int64_t hostExtentStartingAddress;
+#endif
+ int64_t hostResourceBlockSize;
+ int64_t limit;
+ RTCString strOtherHostExtentNameFormat;
+ RTCString strOtherHostExtentNameNamespace;
+ int64_t reservation;
+ int64_t virtualQuantity;
+ RTCString strVirtualQuantityUnits;
+ int64_t virtualResourceBlockSize;
+
+public:
+ StorageItem()
+ : VirtualHardwareItem()
+ , accessType(StorageAccessType_Unknown)
+#if 0 /* unused */
+ , hostExtentNameFormat(-1)
+ , hostExtentNameNamespace(-1)
+ , hostExtentStartingAddress(-1)
+#endif
+ , hostResourceBlockSize(-1)
+ , limit(-1)
+ , reservation(-1)
+ , virtualQuantity(-1)
+ , virtualResourceBlockSize(-1)
+ {
+ itemName = "StorageItem";
+ };
+
+ void fillItem(const xml::ElementNode *item);
+
+protected:
+ virtual void _checkConsistencyAndCompliance() RT_THROW(OVFLogicError);
+private:
+ RTCString itemName;
+
+ virtual const RTCString& _getItemName()
+ {
+ return itemName;
+ }
+};
+
+
+class EthernetPortItem: public VirtualHardwareItem
+{
+ //see DMTF Schema Documentation http://schemas.dmtf.org/wbem/cim-html/2/
+#if 0 /* unused */
+ uint16_t DefaultPortVID;
+ uint16_t DefaultPriority;
+ uint16_t DesiredVLANEndpointMode;
+ uint32_t GroupID;
+ uint32_t ManagerID;
+ uint16_t NetworkPortProfileIDType;
+#endif
+ RTCString strNetworkPortProfileID;
+ RTCString strOtherEndpointMode;
+ RTCString strOtherNetworkPortProfileIDTypeInfo;
+ RTCString strPortCorrelationID;
+#if 0 /* unused */
+ uint16_t PortVID;
+ bool Promiscuous;
+ uint64_t ReceiveBandwidthLimit;
+ uint16_t ReceiveBandwidthReservation;
+ bool SourceMACFilteringEnabled;
+ uint32_t VSITypeID;
+ uint8_t VSITypeIDVersion;
+ uint16_t AllowedPriorities[256];
+ uint16_t AllowedToReceiveVLANs[256];
+ uint16_t AllowedToTransmitVLANs[256];
+#endif
+ RTCString strAllowedToReceiveMACAddresses;
+ RTCString strAllowedToTransmitMACAddresses;
+
+public:
+ EthernetPortItem() : VirtualHardwareItem()
+ {
+ itemName = "EthernetPortItem";
+ };
+
+ void fillItem(const xml::ElementNode *item);
+
+protected:
+ virtual void _checkConsistencyAndCompliance() RT_THROW(OVFLogicError);
+private:
+ RTCString itemName;
+
+ virtual const RTCString& _getItemName()
+ {
+ return itemName;
+ }
+};
+
+typedef std::map<RTCString, DiskImage> DiskImagesMap;
+
+struct VirtualSystem;
+
+
+/**
+ * VirtualHardwareItem pointer vector with safe cleanup.
+ *
+ * We need to use object pointers because we also want EthernetPortItem and
+ * StorageItems to go into the container.
+ */
+class HardwareItemVector : public std::vector<VirtualHardwareItem *>
+{
+public:
+ HardwareItemVector() : std::vector<VirtualHardwareItem *>() { }
+ ~HardwareItemVector()
+ {
+ for (iterator it = begin(); it != end(); ++it)
+ delete(*it);
+ clear();
+ }
+
+ /* There is no copying of this vector. We'd need something like shared_ptr for that. */
+private:
+ HardwareItemVector(const VirtualSystem &);
+
+};
+
+struct HardDiskController
+{
+ RTCString strIdController; // instance ID (Item/InstanceId); this gets referenced from VirtualDisk
+
+ enum ControllerSystemType { IDE, SATA, SCSI, VIRTIOSCSI };
+ ControllerSystemType system; // one of IDE, SATA, SCSI, VIRTIOSCSI
+
+ RTCString strControllerType;
+ // controller subtype (Item/ResourceSubType); e.g. "LsiLogic"; can be empty (esp. for IDE)
+ // note that we treat LsiLogicSAS as a SCSI controller (system == SCSI) even though VirtualBox
+ // treats it as a fourth class besides IDE, SATA, SCSI
+
+ int32_t lAddress; // value from OVF "Address" element
+ bool fPrimary; // controller index; this is determined heuristically by the OVF reader and will
+ // be true for the first controller of this type (e.g. IDE primary ctler) or
+ // false for the next (e.g. IDE secondary ctler)
+
+ HardDiskController()
+ : lAddress(0),
+ fPrimary(true)
+ { }
+};
+
+typedef std::map<RTCString, HardDiskController> ControllersMap;
+
+struct VirtualDisk
+{
+ RTCString strIdController; // SCSI (or IDE) controller this disk is connected to;
+ // this must match HardDiskController.strIdController and
+ // points into VirtualSystem.mapControllers
+ uint32_t ulAddressOnParent; // parsed strAddressOnParent of hardware item; will be 0 or 1 for IDE
+ // and possibly higher for disks attached to SCSI controllers (untested)
+ RTCString strDiskId; // if the hard disk has an ovf:/disk/<id> reference,
+ // this receives the <id> component; points to one of the
+ // references in Appliance::Data.mapDisks
+ bool fEmpty; //true - empty disk, e.g. the component <rasd:HostResource>...</rasd:HostResource> is absent.
+};
+
+typedef std::map<RTCString, VirtualDisk> VirtualDisksMap;
+
+/**
+ * A list of EthernetAdapters is contained in VirtualSystem, representing the
+ * ethernet adapters in the virtual system.
+ */
+struct EthernetAdapter
+{
+ RTCString strAdapterType; // "PCNet32" or "E1000" or whatever; from <rasd:ResourceSubType>
+ RTCString strNetworkName; // from <rasd:Connection>
+};
+
+typedef std::list<EthernetAdapter> EthernetAdaptersList;
+
+/**
+ * A list of VirtualSystem structs is created by OVFReader::read(). Each refers to
+ * a \<VirtualSystem\> block in the OVF file.
+ */
+struct VirtualSystem
+{
+ RTCString strName; // copy of VirtualSystem/@id
+
+ RTCString strDescription; // copy of VirtualSystem/AnnotationSection content, if any
+
+ CIMOSType_T cimos;
+ RTCString strCimosDesc; // readable description of the cimos type in the case of cimos = 0/1/102
+ RTCString strTypeVBox; // optional type from @vbox:ostype attribute (VirtualBox 4.0 or higher)
+
+ RTCString strVirtualSystemType; // generic hardware description; OVF says this can be something like "vmx-4" or "xen";
+ // VMware Workstation 6.5 is "vmx-07"
+
+ HardwareItemVector vecHardwareItems; //< vector containing all virtual hardware items in parsing order.
+
+ uint64_t ullMemorySize; // always in bytes, copied from llHardwareItems; default = 0 (unspecified)
+ uint16_t cCPUs; // no. of CPUs, copied from llHardwareItems; default = 1
+
+ EthernetAdaptersList llEthernetAdapters; // (one for each VirtualSystem/Item[@ResourceType=10]element)
+
+ ControllersMap mapControllers;
+ // list of hard disk controllers
+ // (one for each VirtualSystem/Item[@ResourceType=6] element with accumulated data from children)
+
+ VirtualDisksMap mapVirtualDisks;
+ // (one for each VirtualSystem/Item[@ResourceType=17] element with accumulated data from children)
+
+ bool fHasFloppyDrive; // true if there's a floppy item in mapHardwareItems
+ bool fHasCdromDrive; // true if there's a CD-ROM item in mapHardwareItems; ISO images are not yet supported by OVFtool
+ bool fHasUsbController; // true if there's a USB controller item in mapHardwareItems
+
+ RTCString strSoundCardType; // if not empty, then the system wants a soundcard; this then specifies the hardware;
+ // VMware Workstation 6.5 uses "ensoniq1371" for example
+
+ RTCString strLicenseText; // license info if any; receives contents of VirtualSystem/EulaSection/License
+
+ RTCString strProduct; // product info if any; receives contents of VirtualSystem/ProductSection/Product
+ RTCString strVendor; // product info if any; receives contents of VirtualSystem/ProductSection/Vendor
+ RTCString strVersion; // product info if any; receives contents of VirtualSystem/ProductSection/Version
+ RTCString strProductUrl; // product info if any; receives contents of VirtualSystem/ProductSection/ProductUrl
+ RTCString strVendorUrl; // product info if any; receives contents of VirtualSystem/ProductSection/VendorUrl
+
+ const xml::ElementNode *pelmVBoxMachine; // pointer to <vbox:Machine> element under <VirtualSystem> element or NULL if not present
+
+ VirtualSystem()
+ : cimos(CIMOSType_CIMOS_Unknown),
+ ullMemorySize(0),
+ cCPUs(1),
+ fHasFloppyDrive(false),
+ fHasCdromDrive(false),
+ fHasUsbController(false),
+ pelmVBoxMachine(NULL)
+ {
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Class OVFReader
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * OVFReader attempts to open, read in and parse an OVF XML file. This is all done
+ * in the constructor; if there is any kind of error in the file -- filesystem error
+ * from IPRT, XML parsing errors from libxml, or OVF logical errors --, exceptions
+ * are thrown. These are all based on xml::Error.
+ *
+ * Hence, use this class as follows:
+<code>
+ OVFReader *pReader = NULL;
+ try
+ {
+ pReader = new("/path/to/file.ovf");
+ }
+ catch (xml::Error &e)
+ {
+ printf("A terrible thing happened: %s", e.what());
+ }
+ // now go look at pReader->m_llVirtualSystem and what's in there
+ if (pReader)
+ delete pReader;
+</code>
+ */
+class OVFReader
+{
+public:
+ OVFReader();
+ OVFReader(const void *pvBuf, size_t cbSize, const RTCString &path);
+ OVFReader(const RTCString &path);
+
+ // Data fields
+ EnvelopeData m_envelopeData; //data of root element "Envelope"
+ RTCString m_strPath; // file name given to constructor
+ DiskImagesMap m_mapDisks; // map of DiskImage structs, sorted by DiskImage.strDiskId
+ std::list<VirtualSystem> m_llVirtualSystems; // list of virtual systems, created by and valid after read()
+
+private:
+ xml::Document m_doc;
+
+ void parse();
+ void LoopThruSections(const xml::ElementNode *pReferencesElem, const xml::ElementNode *pCurElem);
+ void HandleDiskSection(const xml::ElementNode *pReferencesElem, const xml::ElementNode *pSectionElem);
+ void HandleNetworkSection(const xml::ElementNode *pSectionElem);
+ void HandleVirtualSystemContent(const xml::ElementNode *pContentElem);
+};
+
+} // end namespace ovf
+
+#endif /* !MAIN_INCLUDED_ovfreader_h */
+
diff --git a/src/VBox/Main/include/vbox-libhal.h b/src/VBox/Main/include/vbox-libhal.h
new file mode 100644
index 00000000..09aa113b
--- /dev/null
+++ b/src/VBox/Main/include/vbox-libhal.h
@@ -0,0 +1,80 @@
+/* $Id: vbox-libhal.h $ */
+/** @file
+ *
+ * Module to dynamically load libhal and libdbus and load all symbols
+ * which are needed by VirtualBox.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_vbox_libhal_h
+#define MAIN_INCLUDED_vbox_libhal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <stdint.h>
+
+#define LIB_HAL "libhal.so.1"
+
+/** Types from the dbus and hal header files which we need. These are taken more or less
+ verbatim from the DBus and Hal public interface header files. */
+struct DBusError
+{
+ const char *name;
+ const char *message;
+ unsigned int dummy1 : 1; /**< placeholder */
+ unsigned int dummy2 : 1; /**< placeholder */
+ unsigned int dummy3 : 1; /**< placeholder */
+ unsigned int dummy4 : 1; /**< placeholder */
+ unsigned int dummy5 : 1; /**< placeholder */
+ void *padding1; /**< placeholder */
+};
+struct DBusConnection;
+typedef struct DBusConnection DBusConnection;
+typedef uint32_t dbus_bool_t;
+typedef enum { DBUS_BUS_SESSON, DBUS_BUS_SYSTEM, DBUS_BUS_STARTER } DBusBusType;
+struct LibHalContext_s;
+typedef struct LibHalContext_s LibHalContext;
+
+/** The following are the symbols which we need from libdbus and libhal. */
+extern void (*gDBusErrorInit)(DBusError *);
+extern DBusConnection *(*gDBusBusGet)(DBusBusType, DBusError *);
+extern void (*gDBusErrorFree)(DBusError *);
+extern void (*gDBusConnectionUnref)(DBusConnection *);
+extern LibHalContext *(*gLibHalCtxNew)(void);
+extern dbus_bool_t (*gLibHalCtxSetDBusConnection)(LibHalContext *, DBusConnection *);
+extern dbus_bool_t (*gLibHalCtxInit)(LibHalContext *, DBusError *);
+extern char **(*gLibHalFindDeviceStringMatch)(LibHalContext *, const char *, const char *, int *,
+ DBusError *);
+extern char *(*gLibHalDeviceGetPropertyString)(LibHalContext *, const char *, const char *,
+ DBusError *);
+extern void (*gLibHalFreeString)(char *);
+extern void (*gLibHalFreeStringArray)(char **);
+extern dbus_bool_t (*gLibHalCtxShutdown)(LibHalContext *, DBusError *);
+extern dbus_bool_t (*gLibHalCtxFree)(LibHalContext *);
+
+extern bool gLibHalCheckPresence(void);
+
+#endif /* !MAIN_INCLUDED_vbox_libhal_h */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/include/vector.h b/src/VBox/Main/include/vector.h
new file mode 100644
index 00000000..7c43dc8a
--- /dev/null
+++ b/src/VBox/Main/include/vector.h
@@ -0,0 +1,368 @@
+/* $Id: vector.h $ */
+/** @file
+ * STL-inspired vector implementation in C
+ * @note functions in this file are inline to prevent warnings about
+ * unused static functions. I assume that in this day and age a
+ * compiler makes its own decisions about whether to actually
+ * inline a function.
+ * @note as this header is included in rdesktop-vrdp, we do not include other
+ * required header files here (to wit assert.h, err.h, mem.h and
+ * types.h). These must be included first. If this moves to iprt, we
+ * should write a wrapper around it doing that.
+ * @todo can we do more of the type checking at compile time somehow?
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_vector_h
+#define MAIN_INCLUDED_vector_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <stdlib.h>
+
+
+/*********************************************************************************************************************************
+* Helper macros and definitions *
+*********************************************************************************************************************************/
+
+/** The unit by which the vector capacity is increased */
+#define VECTOR_ALLOC_UNIT 16
+
+/** Calculate a hash of a string of tokens for sanity type checking */
+#define VECTOR_TOKEN_HASH(token) \
+ ((unsigned) \
+ ( VECTOR_TOKEN_HASH4(token, 0) \
+ ^ VECTOR_TOKEN_HASH4(token, 4) \
+ ^ VECTOR_TOKEN_HASH4(token, 8) \
+ ^ VECTOR_TOKEN_HASH4(token, 12)))
+
+/** Helper macro for @a VECTOR_TOKEN_HASH */
+#define VECTOR_TOKEN_HASH_VALUE(token, place, mul) \
+ (sizeof(#token) > place ? #token[place] * mul : 0)
+
+/** Helper macro for @a VECTOR_TOKEN_HASH */
+#define VECTOR_TOKEN_HASH4(token, place) \
+ VECTOR_TOKEN_HASH_VALUE(token, place, 0x1) \
+ ^ VECTOR_TOKEN_HASH_VALUE(token, place + 1, 0x100) \
+ ^ VECTOR_TOKEN_HASH_VALUE(token, place + 2, 0x10000) \
+ ^ VECTOR_TOKEN_HASH_VALUE(token, place + 3, 0x1000000)
+
+/** Generic vector structure, used by @a VECTOR_OBJ and @a VECTOR_PTR */
+#define VECTOR_STRUCT \
+{ \
+ /** The number of elements in the vector */ \
+ size_t mcElements; \
+ /** The current capacity of the vector */ \
+ size_t mcCapacity; \
+ /** The size of an element */ \
+ size_t mcbElement; \
+ /** Hash value of the element type */ \
+ unsigned muTypeHash; \
+ /** The elements themselves */ \
+ void *mpvaElements; \
+ /** Destructor for elements - takes a pointer to an element. */ \
+ void (*mpfnCleanup)(void *); \
+}
+
+/*** Structure definitions ***/
+
+/** A vector of values or objects */
+typedef struct VECTOR_OBJ VECTOR_STRUCT VECTOR_OBJ;
+
+/** A vector of pointer values. (A handy special case.) */
+typedef struct VECTOR_PTR VECTOR_STRUCT VECTOR_PTR;
+
+/** Convenience macro for annotating the type of the vector. Unfortunately the
+ * type name is only cosmetic. */
+/** @todo can we do something useful with the type? */
+#define VECTOR_OBJ(type) VECTOR_OBJ
+
+/** Convenience macro for annotating the type of the vector. Unfortunately the
+ * type name is only cosmetic. */
+#define VECTOR_PTR(type) VECTOR_PTR
+
+/*** Private helper functions and macros ***/
+
+#define VEC_GET_ELEMENT_OBJ(pvaElements, cbElement, cElement) \
+ ((void *)((char *)(pvaElements) + (cElement) * (cbElement)))
+
+#define VEC_GET_ELEMENT_PTR(pvaElements, cElement) \
+ (*(void **)VEC_GET_ELEMENT_OBJ(pvaElements, sizeof(void *), cElement))
+
+/** Default cleanup function that does nothing. */
+DECLINLINE(void) vecNoCleanup(void *pvElement)
+{
+ (void) pvElement;
+}
+
+/** Expand an existing vector, implementation */
+DECLINLINE(int) vecExpand(size_t *pcCapacity, void **ppvaElements,
+ size_t cbElement)
+{
+ size_t cOldCap, cNewCap;
+ void *pRealloc;
+
+ cOldCap = *pcCapacity;
+ cNewCap = cOldCap + VECTOR_ALLOC_UNIT;
+ pRealloc = RTMemRealloc(*ppvaElements, cNewCap * cbElement);
+ if (!pRealloc)
+ return VERR_NO_MEMORY;
+ *pcCapacity = cNewCap;
+ *ppvaElements = pRealloc;
+ return VINF_SUCCESS;
+}
+
+/** Expand an existing vector */
+#define VEC_EXPAND(pvec) vecExpand(&(pvec)->mcCapacity, &(pvec)->mpvaElements, \
+ (pvec)->mcbElement)
+
+/** Reset a vector, cleaning up all its elements. */
+DECLINLINE(void) vecClearObj(VECTOR_OBJ *pvec)
+{
+ unsigned i;
+
+ for (i = 0; i < pvec->mcElements; ++i)
+ pvec->mpfnCleanup(VEC_GET_ELEMENT_OBJ(pvec->mpvaElements,
+ pvec->mcbElement, i));
+ pvec->mcElements = 0;
+}
+
+/** Reset a pointer vector, cleaning up all its elements. */
+DECLINLINE(void) vecClearPtr(VECTOR_PTR *pvec)
+{
+ unsigned i;
+
+ for (i = 0; i < pvec->mcElements; ++i)
+ pvec->mpfnCleanup(VEC_GET_ELEMENT_PTR(pvec->mpvaElements, i));
+ pvec->mcElements = 0;
+}
+
+/** Clean up a vector */
+DECLINLINE(void) vecCleanupObj(VECTOR_OBJ *pvec)
+{
+ vecClearObj(pvec);
+ RTMemFree(pvec->mpvaElements);
+ pvec->mpvaElements = NULL;
+}
+
+/** Clean up a pointer vector */
+DECLINLINE(void) vecCleanupPtr(VECTOR_PTR *pvec)
+{
+ vecClearPtr(pvec);
+ RTMemFree(pvec->mpvaElements);
+ pvec->mpvaElements = NULL;
+}
+
+/** Initialise a vector structure, implementation */
+#define VEC_INIT(pvec, cbElement, uTypeHash, pfnCleanup) \
+ pvec->mcElements = pvec->mcCapacity = 0; \
+ pvec->mcbElement = cbElement; \
+ pvec->muTypeHash = uTypeHash; \
+ pvec->mpfnCleanup = pfnCleanup ? pfnCleanup : vecNoCleanup; \
+ pvec->mpvaElements = NULL;
+
+/** Initialise a vector. */
+DECLINLINE(void) vecInitObj(VECTOR_OBJ *pvec, size_t cbElement,
+ unsigned uTypeHash, void (*pfnCleanup)(void *))
+{
+ VEC_INIT(pvec, cbElement, uTypeHash, pfnCleanup)
+}
+
+/** Initialise a pointer vector. */
+DECLINLINE(void) vecInitPtr(VECTOR_PTR *pvec, size_t cbElement,
+ unsigned uTypeHash, void (*pfnCleanup)(void *))
+{
+ VEC_INIT(pvec, cbElement, uTypeHash, pfnCleanup)
+}
+
+/** Add an element onto the end of a vector */
+DECLINLINE(int) vecPushBackObj(VECTOR_OBJ *pvec, unsigned uTypeHash,
+ void *pvElement)
+{
+ int rc2;
+ AssertReturn(pvec->muTypeHash == uTypeHash, VERR_INVALID_PARAMETER);
+ if ( pvec->mcElements == pvec->mcCapacity
+ && RT_FAILURE((rc2 = VEC_EXPAND(pvec))))
+ return rc2;
+ memcpy(VEC_GET_ELEMENT_OBJ(pvec->mpvaElements, pvec->mcbElement,
+ pvec->mcElements), pvElement, pvec->mcbElement);
+ ++pvec->mcElements;
+ return VINF_SUCCESS;
+}
+
+/** Add a pointer onto the end of a pointer vector */
+DECLINLINE(int) vecPushBackPtr(VECTOR_PTR *pvec, unsigned uTypeHash,
+ void *pv)
+{
+ int rc2;
+ AssertReturn(pvec->muTypeHash == uTypeHash, VERR_INVALID_PARAMETER);
+ if ( pvec->mcElements == pvec->mcCapacity
+ && RT_FAILURE((rc2 = VEC_EXPAND(pvec))))
+ return rc2;
+ VEC_GET_ELEMENT_PTR(pvec->mpvaElements, pvec->mcElements) = pv;
+ ++pvec->mcElements;
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* Public interface macros *
+*********************************************************************************************************************************/
+
+/**
+ * Initialise a vector structure. Always succeeds.
+ * @param pvec pointer to an uninitialised vector structure
+ * @param type the type of the objects in the vector. As this is
+ * hashed by the preprocessor use of space etc is
+ * important.
+ * @param pfnCleanup cleanup function (void (*pfn)(void *)) that is called
+ * on a pointer to a vector element when that element is
+ * dropped
+ */
+#define VEC_INIT_OBJ(pvec, type, pfnCleanup) \
+ vecInitObj(pvec, sizeof(type), VECTOR_TOKEN_HASH(type), \
+ (void (*)(void*)) pfnCleanup)
+
+/**
+ * Initialise a vector-of-pointers structure. Always succeeds.
+ * @param pvec pointer to an uninitialised vector structure
+ * @param type the type of the pointers in the vector, including the
+ * final "*". As this is hashed by the preprocessor use
+ * of space etc is important.
+ * @param pfnCleanup cleanup function (void (*pfn)(void *)) that is called
+ * directly on a vector element when that element is
+ * dropped
+ */
+#define VEC_INIT_PTR(pvec, type, pfnCleanup) \
+ vecInitPtr(pvec, sizeof(type), VECTOR_TOKEN_HASH(type), \
+ (void (*)(void*)) pfnCleanup)
+
+/**
+ * Clean up a vector.
+ * @param pvec pointer to the vector to clean up. The clean up function
+ * specified at initialisation (if any) is called for each element
+ * in the vector. After clean up, the vector structure is invalid
+ * until it is re-initialised
+ */
+#define VEC_CLEANUP_OBJ vecCleanupObj
+
+/**
+ * Clean up a vector-of-pointers.
+ * @param pvec pointer to the vector to clean up. The clean up function
+ * specified at initialisation (if any) is called for each element
+ * in the vector. After clean up, the vector structure is invalid
+ * until it is re-initialised
+ */
+#define VEC_CLEANUP_PTR vecCleanupPtr
+
+/**
+ * Reinitialises a vector structure to empty.
+ * @param pvec pointer to the vector to re-initialise. The clean up function
+ * specified at initialisation (if any) is called for each element
+ * in the vector.
+ */
+#define VEC_CLEAR_OBJ vecClearObj
+
+/**
+ * Reinitialises a vector-of-pointers structure to empty.
+ * @param pvec pointer to the vector to re-initialise. The clean up function
+ * specified at initialisation (if any) is called for each element
+ * in the vector.
+ */
+#define VEC_CLEAR_PTR vecClearPtr
+
+/**
+ * Adds an element to the back of a vector. The element will be byte-copied
+ * and become owned by the vector, to be cleaned up by the vector's clean-up
+ * routine when the element is dropped.
+ * @returns iprt status code (VINF_SUCCESS or VERR_NO_MEMORY)
+ * @returns VERR_INVALID_PARAMETER if the type does not match the type given
+ * when the vector was initialised (asserted)
+ * @param pvec pointer to the vector on to which the element should be
+ * added
+ * @param type the type of the vector as specified at initialisation.
+ * Spacing etc is important.
+ * @param pvElement void pointer to the element to be added
+ */
+#define VEC_PUSH_BACK_OBJ(pvec, type, pvElement) \
+ vecPushBackObj(pvec, VECTOR_TOKEN_HASH(type), \
+ (pvElement) + ((pvElement) - (type *)(pvElement)))
+
+/**
+ * Adds a pointer to the back of a vector-of-pointers. The pointer will become
+ * owned by the vector, to be cleaned up by the vector's clean-up routine when
+ * it is dropped.
+ * @returns iprt status code (VINF_SUCCESS or VERR_NO_MEMORY)
+ * @returns VERR_INVALID_PARAMETER if the type does not match the type given
+ * when the vector was initialised (asserted)
+ * @param pvec pointer to the vector on to which the element should be
+ * added
+ * @param type the type of the vector as specified at initialisation.
+ * Spacing etc is important.
+ * @param pvElement the pointer to be added, typecast to pointer-to-void
+ */
+#define VEC_PUSH_BACK_PTR(pvec, type, pvElement) \
+ vecPushBackPtr(pvec, VECTOR_TOKEN_HASH(type), \
+ (pvElement) + ((pvElement) - (type)(pvElement)))
+
+/**
+ * Returns the count of elements in a vector.
+ * @param pvec pointer to the vector.
+ */
+#define VEC_SIZE_OBJ(pvec) (pvec)->mcElements
+
+/**
+ * Returns the count of elements in a vector-of-pointers.
+ * @param pvec pointer to the vector.
+ */
+#define VEC_SIZE_PTR VEC_SIZE_OBJ
+
+/**
+ * Iterates over a vector.
+ *
+ * Iterates over the vector elements from first to last and execute the
+ * following instruction or block on each iteration with @a pIterator set to
+ * point to the current element (that is, a pointer to the pointer element for
+ * a vector-of-pointers). Use in the same way as a "for" statement.
+ *
+ * @param pvec Pointer to the vector to be iterated over.
+ * @param type The type of the vector, must match the type specified at
+ * vector initialisation (including whitespace etc).
+ * @param pIterator A pointer to @a type which will be set to point to the
+ * current vector element on each iteration.
+ *
+ * @todo can we assert the correctness of the type in some way?
+ */
+#define VEC_FOR_EACH(pvec, type, pIterator) \
+ for (pIterator = (type *) (pvec)->mpvaElements; \
+ (pvec)->muTypeHash == VECTOR_TOKEN_HASH(type) \
+ && pIterator < (type *) (pvec)->mpvaElements + (pvec)->mcElements; \
+ ++pIterator)
+
+#endif /* !MAIN_INCLUDED_vector_h */
diff --git a/src/VBox/Main/include/win/resource.h b/src/VBox/Main/include/win/resource.h
new file mode 100644
index 00000000..d7eea1f1
--- /dev/null
+++ b/src/VBox/Main/include/win/resource.h
@@ -0,0 +1,43 @@
+/* $Id: resource.h $ */
+/** @file
+ *
+ * Resource definitions
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_win_resource_h
+#define MAIN_INCLUDED_win_resource_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+// registry script resource ID
+#define IDR_VIRTUALBOX 101
+
+// service name string resource ID
+#define IDS_SERVICENAME 102
+
+
+#endif /* !MAIN_INCLUDED_win_resource_h */
diff --git a/src/VBox/Main/nls/ApprovedLanguages.kmk b/src/VBox/Main/nls/ApprovedLanguages.kmk
new file mode 100644
index 00000000..78187eec
--- /dev/null
+++ b/src/VBox/Main/nls/ApprovedLanguages.kmk
@@ -0,0 +1,31 @@
+# $Id: ApprovedLanguages.kmk $
+## @file
+# ApprovedLanguages.kmk - List of approved Main API translations.
+#
+
+#
+# Copyright (C) 2007-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# The list of approved Main API languages.
+VBOX_APPROVED_MAIN_LANGUAGES := \
+ ru
+
diff --git a/src/VBox/Main/nls/VirtualBoxAPI_ru.ts b/src/VBox/Main/nls/VirtualBoxAPI_ru.ts
new file mode 100644
index 00000000..75ba6bec
--- /dev/null
+++ b/src/VBox/Main/nls/VirtualBoxAPI_ru.ts
@@ -0,0 +1,9628 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru">
+<context>
+ <name>Appliance</name>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="647"/>
+ <source>The number of VirtualSystemDescription objects must be at least 1 or more.</source>
+ <translation>КоличеÑтво объектов VirtualSystemDescription должно быть не менее одного.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="725"/>
+ <source>The given password identifier is not associated with any medium</source>
+ <translation>Указанный идентификатор Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð½Ðµ ÑвÑзан ни Ñ Ð¾Ð´Ð½Ð¸Ð¼ ноÑителем</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="747"/>
+ <source>A password with the given ID already exists</source>
+ <translation>Пароль Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð½Ñ‹Ð¼ ID уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="760"/>
+ <source>Failed to allocate enough secure memory for the key</source>
+ <translation>Ðевозможно выделить доÑтаточного количеÑтва защищенной памÑти Ð´Ð»Ñ ÐºÐ»ÑŽÑ‡Ð°</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="762"/>
+ <source>Unknown error happened while adding a password (%Rrc)</source>
+ <translation>Произошла неизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° во Ð²Ñ€ÐµÐ¼Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="805"/>
+ <source>Can&apos;t find appropriate medium format for ISO type of a virtual disk.</source>
+ <translation>Ðевозможно найти ÑоответÑтвующий формат ноÑÐ¸Ñ‚ÐµÐ»Ñ Ð´Ð»Ñ Ñ‚Ð¸Ð¿Ð° ISO виртуального диÑка.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="882"/>
+ <location filename="../src-server/ApplianceImpl.cpp" line="902"/>
+ <source>Internal inconsistency looking up medium format for the disk image &apos;%s&apos;</source>
+ <translation>ВнутреннÑÑ Ð½ÐµÑоглаÑованноÑÑ‚ÑŒ при поиÑке формата ноÑÐ¸Ñ‚ÐµÐ»Ñ Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð·Ð° диÑка &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="941"/>
+ <source>RTManifestEntryAddPassthruIoStream failed with rc=%Rrc</source>
+ <translation>RTManifestEntryAddPassthruIoStream вернул ошибку rc=%Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="960"/>
+ <source>The appliance is busy importing files</source>
+ <translation>ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð·Ð°Ð½Ñта импортом файлов</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="962"/>
+ <source>The appliance is busy exporting files</source>
+ <translation>ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð·Ð°Ð½Ñта ÑкÑпортом файлов</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="1202"/>
+ <source>The path &apos;%s&apos; must start with /</source>
+ <translation>Путь &apos;%s&apos; должен начинатьÑÑ Ñ /</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="1212"/>
+ <source>You doesn&apos;t provide a bucket name in the URI &apos;%s&apos;</source>
+ <translation>Ðе указано название корзины в URI &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="1247"/>
+ <source>The manifest signature for &apos;%s&apos; is not valid</source>
+ <translation>ПодпиÑÑŒ манифеÑта Ð´Ð»Ñ &apos;%s&apos; недейÑтвительна</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="1252"/>
+ <source>The certificate used to signed &apos;%s&apos; is not valid: %s</source>
+ <translation>Сертификат, иÑпользуемый Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñи &apos;%s&apos;, недейÑтвителен: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="1255"/>
+ <source>The certificate used to signed &apos;%s&apos; is not valid</source>
+ <translation>Сертификат, иÑпользуемый Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñи &apos;%s&apos;, недейÑтвителен</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="763"/>
+ <source>Invalid format &quot;%s&quot; specified</source>
+ <translation>Указан недопуÑтимый формат &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="770"/>
+ <source>OPC appliance file must have .tar.gz extension</source>
+ <translation>Файл конфигурации OPC должен иметь раÑширение .tar.gz</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="774"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="111"/>
+ <source>Appliance file must have .ovf or .ova extension</source>
+ <translation>Файл конфигурации должен иметь раÑширение .ovf или .ova</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="786"/>
+ <source>Appliance export failed because not all passwords were provided for all encrypted media</source>
+ <translation>ЭкÑпорт конфигурации завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹, потому что пароли указаны не Ð´Ð»Ñ Ð²Ñех ноÑителей Ñ ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸ÐµÐ¼</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="852"/>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="982"/>
+ <source>Export appliance &apos;%s&apos;</source>
+ <translation>ЭкÑпорт конфигурации &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="915"/>
+ <source>There are no images to export to Cloud after preparation steps</source>
+ <translation>ПоÑле подготовительных шагов не оÑталоÑÑŒ ни одно образа Ð´Ð»Ñ ÑкÑпорта в облако</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="923"/>
+ <source>Cloud: More than one profile name was found.</source>
+ <translation>Облако: Ðайдено больше одного имени профилÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="925"/>
+ <source>Cloud: Profile name wasn&apos;t specified.</source>
+ <translation>Облако: Ð˜Ð¼Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð½Ðµ указано.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="928"/>
+ <source>Cloud: Cloud user profile name is empty</source>
+ <translation>Облако: ПользовательÑкое Ð¸Ð¼Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð¾Ð±Ð»Ð°ÐºÐ° пуÑто</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="939"/>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="943"/>
+ <source>Exporting VM to Cloud...</source>
+ <translation>ЭкÑпорт Ð’Ðœ в Облако...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="947"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3598"/>
+ <source>Only &quot;OCI&quot; cloud provider is supported for now. &quot;%s&quot; isn&apos;t supported.</source>
+ <translation>Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÑ‚ÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ провайдер облака &apos;OCI&apos;.&apos;%s&apos; не поддерживаетÑÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1119"/>
+ <source>Cannot export more than one virtual system with OVF 0.9, use OVF 1.0</source>
+ <translation>Ðевозможно ÑкÑпортировать больше одной виртуальной ÑиÑтемы Ñ OVF 0.9, иÑпользуйте OVF 1.0</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1339"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5632"/>
+ <source>Missing VM name</source>
+ <translation>ОтÑутÑтвует Ð¸Ð¼Ñ Ð’Ðœ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1435"/>
+ <source>Missing OS type</source>
+ <translation>ОтÑутÑтвует тип ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1678"/>
+ <source>Invalid config string &quot;%s&quot; in SATA controller</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ñтрока конфигурации &apos;%s&apos; у SATA контроллера</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1718"/>
+ <source>Invalid config string &quot;%s&quot; in SCSI/SAS controller</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ñтрока конфигурации &apos;%s&apos; у SCSI/SAS контроллера</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1802"/>
+ <source>Missing or bad extra config string in hard disk image: &quot;%s&quot;</source>
+ <translation>ОтÑутÑтвует или Ð½ÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð°Ñ Ñтрока конфигурации у образа жеÑткого диÑка: &quot;%s&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1879"/>
+ <source>Missing or bad extra config string in DVD drive medium: &quot;%s&quot;</source>
+ <translation>ОтÑутÑтвует или Ð½ÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð°Ñ Ñтрока конфигурации у ноÑÐ¸Ñ‚ÐµÐ»Ñ DVD: &quot;%s&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2262"/>
+ <source>Failed to open directory &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть директорию &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2320"/>
+ <source>Failed create TAR creator for &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать Ñоздатель TAR Ð´Ð»Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2327"/>
+ <source>Failed to open &apos;%s&apos; for writing (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть &apos;%s&apos; Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2346"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1335"/>
+ <source>%s: Cloud provider manager object wasn&apos;t found</source>
+ <translation>%s: Объект менеджера провайдера облака не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2354"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1343"/>
+ <source>%s: Cloud provider object wasn&apos;t found</source>
+ <translation>%s: Объект провайдера облака не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2375"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1376"/>
+ <source>%s: Cloud user profile name wasn&apos;t found</source>
+ <translation>%s: Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŒÑкого Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð¾Ð±Ð»Ð°ÐºÐ° не найдено</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2379"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1384"/>
+ <source>%s: Cloud profile object wasn&apos;t found</source>
+ <translation>%s: Объект Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð¾Ð±Ð»Ð°ÐºÐ° не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2384"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1389"/>
+ <source>%s: Cloud client object wasn&apos;t found</source>
+ <translation>%s: Объект клиента облака не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2392"/>
+ <source>Export to Cloud isn&apos;t supported for more than one VM instance.</source>
+ <translation>ЭкÑпорт в облако Ð´Ð»Ñ Ð±Ð¾Ð»ÐµÐµ чем одного ÑкземплÑра Ð’Ðœ не поддерживаетÑÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2537"/>
+ <source>Exporting to disk image &apos;%Rbn&apos;</source>
+ <translation>ЭкÑпорт в образ диÑка &apos;%Rbn&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2565"/>
+ <source>Error completing TAR file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° TAR &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2569"/>
+ <source>Failed to TAR creator instance for &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать ÑкземплÑÑ€ ÑÐ¾Ð·Ð´Ð°Ñ‚ÐµÐ»Ñ TAR Ð´Ð»Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2575"/>
+ <source>Failed to create &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2624"/>
+ <source>Could not create OVF file &apos;%s&apos;</source>
+ <translation>Ðевозможно Ñоздать OVF файл &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2646"/>
+ <source>Invalid medium storage format</source>
+ <translation>ÐедопуÑтимый формат образа ноÑителÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2711"/>
+ <source>Exporting to disk image &apos;%s&apos;</source>
+ <translation>ЭкÑпорт в образ диÑка &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2727"/>
+ <source>RTVfsFsStrmPushFile failed for &apos;%s&apos; (%Rrc)</source>
+ <translation>RTVfsFsStrmPushFile завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ Ð´Ð»Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2768"/>
+ <source>Creating manifest file &apos;%s&apos;</source>
+ <translation>Создание файла манифеÑта &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2775"/>
+ <source>RTVfsMemIoStrmCreate failed (%Rrc)</source>
+ <translation>RTVfsMemIoStrmCreate завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2790"/>
+ <source>RTVfsFsStrmAdd failed for the manifest (%Rrc)</source>
+ <translation>RTVfsFsStrmAdd завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ при добавлении манифеÑта (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2793"/>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2796"/>
+ <source>RTManifestWriteStandard failed (%Rrc)</source>
+ <translation>RTManifestWriteStandard завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2854"/>
+ <source>RTVfsFsStrmAdd failed for &apos;%s&apos; (%Rrc)</source>
+ <translation>RTVfsFsStrmAdd завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ при добавлении &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="143"/>
+ <source>Cannot interpret appliance without reading it first (call read() before interpret())</source>
+ <translation>Ðевозможно интерпретировать конфигурацию без предварительного Ñ‡Ñ‚ÐµÐ½Ð¸Ñ (вызовите read() перед interpret())</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="344"/>
+ <source>The virtual system &quot;%s&quot; claims support for %llu MB RAM size, but VirtualBox has support for min %u &amp; max %u MB RAM size only.</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ ÑиÑтема &quot;%s&quot; заÑвлÑет о поддержке %llu MB RAM, но VirtualBox поддерживает только от %u MB и до %u MB RAM.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="317"/>
+ <source>The virtual system &quot;%s&quot; claims support for %u CPU&apos;s, but VirtualBox has support for max %u CPU&apos;s only.</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ ÑиÑтема &quot;%s&quot; заÑвлÑет о поддержке %u ЦПУ, но VirtualBox поддерживает только до %u ЦПУ.</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/ApplianceImplImport.cpp" line="409"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="439"/>
+ <source>The virtual system &quot;%s&quot; claims support for %zu network adapters, but VirtualBox has support for max %u network adapter only.</source>
+ <translation>
+ <numerusform>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ ÑиÑтема &quot;%s&quot; заÑвлÑет о поддержке %zu Ñетевого адаптера, но VirtualBox поддерживает только до %u Ñетевых адаптеров.</numerusform>
+ <numerusform>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ ÑиÑтема &quot;%s&quot; заÑвлÑет о поддержке %zu Ñетевых адаптеров, но VirtualBox поддерживает только до %u Ñетевых адаптеров.</numerusform>
+ <numerusform>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ ÑиÑтема &quot;%s&quot; заÑвлÑет о поддержке %zu Ñетевых адаптеров, но VirtualBox поддерживает только до %u Ñетевых адаптеров.</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="600"/>
+ <source>The virtual &quot;%s&quot; system requests support for more than two IDE controller channels, but VirtualBox supports only two.</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ ÑиÑтема &quot;%s&quot; запрашивает поддержку более чем двух каналов IDE контроллера, но VirtualBox поддерживает только два.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="622"/>
+ <source>The virtual system &quot;%s&quot; requests support for more than one SATA controller, but VirtualBox has support for only one</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ ÑиÑтема &quot;%s&quot; запрашивает поддержку Ð´Ð»Ñ Ð±Ð¾Ð»ÐµÐµ чем одно SATA контроллера, но VirtualBox поддерживает только один</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="650"/>
+ <source>The virtual system &quot;%s&quot; requests support for an additional SCSI controller of type &quot;%s&quot; with ID %s, but VirtualBox presently supports only one SCSI controller.</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ ÑиÑтема &quot;%s&quot; запрашивает поддержку Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð³Ð¾ SCSI контроллера типа &quot;%s&quot; Ñ ID %s, но VirtualBox в наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÑ‚ только один SCSI контроллер.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="672"/>
+ <source>The virtual system &quot;%s&quot; requests support for more than one VirtioSCSI controller, but VirtualBox has support for only one</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ ÑиÑтема &quot;%s&quot; запрашивает поддержку Ð´Ð»Ñ Ð±Ð¾Ð»ÐµÐµ чем одного VirtioSCSI контроллера, но VirtualBox поддерживает только один</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="745"/>
+ <source>Unsupported format for virtual disk image %s in OVF: &quot;%s&quot;</source>
+ <translation>Ðеподдерживаемый формат Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð·Ð° виртуального диÑка %s в OVF: &quot;%s&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="762"/>
+ <source>Cannot find storage controller with OVF instance ID &quot;%s&quot; to which medium &quot;%s&quot; should be attached</source>
+ <translation>Ðевозможно найти контроллер Ñ ID ÑкземплÑра OVF &quot;%s&quot; куда ноÑитель &quot;%s&quot; должен быть подключен</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="835"/>
+ <source>Cannot import machines without reading it first (call read() before i_importMachines())</source>
+ <translation>Ðевозможно импортировать машины без предварительного Ñ‡Ñ‚ÐµÐ½Ð¸Ñ (вызовите read() перед i_importMachines())</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="878"/>
+ <source>Malformed OVA. &apos;%s&apos; is not a regular file (%d).</source>
+ <translation>OVA поврежден. &apos;%s&apos; не ÑвлÑетÑÑ Ð¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ð¼ файлом (%d).</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="883"/>
+ <source>RTVfsFsStrmNext failed (%Rrc)</source>
+ <translation>RTVfsFsStrmNext завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="892"/>
+ <source>Unexpected end of OVA package</source>
+ <translation>Ðеожиданный конец пакета OVA</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="922"/>
+ <source>Unexpected end of OVA / internal error - missing &apos;%s&apos; (skipped %u)</source>
+ <translation>Ðеожиданный конец OVA / внутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ° - отÑутÑтвует &apos;%s&apos; (пропущено %u)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="936"/>
+ <source>Error opening &apos;%s&apos; for reading (%Rrc)</source>
+ <translation>Ошибка при открытии &apos;%s&apos; на чтение (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="988"/>
+ <source>Error occured decompressing &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка при раÑпаковке &apos;%s&apos; в &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="996"/>
+ <source>Error opening destionation image &apos;%s&apos; for writing (%Rrc)</source>
+ <translation>Ошибка Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ð¾Ð±Ñ€Ð°Ð·Ð° Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ &apos;%s&apos; на запиÑÑŒ (%Rc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1026"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1078"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4232"/>
+ <source>Error initializing read ahead thread for &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка инициализации потока упреждающего Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð´Ð»Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1043"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1106"/>
+ <source>RTManifestPtIosAddEntryNow failed with %Rrc</source>
+ <translation>RTManifestPtIosAddEntryNow вернул ошибку c %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1090"/>
+ <source>Error initializing gzip decompression for &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка инциализации раÑпаковки gzip Ð´Ð»Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1155"/>
+ <source>Getting cloud instance information</source>
+ <translation>Получение информации об ÑкземплÑре облака</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1166"/>
+ <source>Reading appliance &apos;%s&apos;</source>
+ <translation>Чтение конфигурации &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1175"/>
+ <source>Download appliance &apos;%s&apos;</source>
+ <translation>Загрузка конфигурации &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1210"/>
+ <source>Failed to create thread for reading appliance data</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать поток Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… конфигурации</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1231"/>
+ <source>%s: The profile name or instance id are absent or contain unsupported characters: %s</source>
+ <translation>%s: Ð˜Ð¼Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð¸Ð»Ð¸ идентификатор ÑкземплÑра отÑутÑтвуют или Ñодержат неподдерживаемые Ñимволы: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1238"/>
+ <source>%s: Cloud provider manager object wasn&apos;t found (%Rhrc)</source>
+ <translation>%s: Объект менеджера провайдера облака не найден (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1246"/>
+ <source>%s: Cloud provider object wasn&apos;t found (%Rhrc)</source>
+ <translation>%s: Объект провайдера облака не найден (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1250"/>
+ <source>%s: Cloud user profile name wasn&apos;t found (%Rhrc)</source>
+ <translation>%s: ПользовательÑкое Ð¸Ð¼Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð¾Ð±Ð»Ð°ÐºÐ° не найдено (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1254"/>
+ <source>%s: Cloud profile object wasn&apos;t found (%Rhrc)</source>
+ <translation>%s: Объект Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð¾Ð±Ð»Ð°ÐºÐ° не найден (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1259"/>
+ <source>%s: Cloud client object wasn&apos;t found (%Rhrc)</source>
+ <translation>%s: Объект клиента облака не найден (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1268"/>
+ <source>%s: Requested (%d) and created (%d) numbers of VSD are differ .</source>
+ <translation>%s: Запрошенное (%d) и Ñозданное (%d) количеÑтво VSD отличаютÑÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1380"/>
+ <source>%s: Cloud user profile name is empty</source>
+ <translation>%s: ПользовательÑкое Ð¸Ð¼Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð¾Ð±Ð»Ð°ÐºÐ° пуÑто</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1505"/>
+ <source>Can&apos;t open folder %s</source>
+ <translation>Ðевозможно открыть папку %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1509"/>
+ <source>The target folder %s has already contained some files (%d items). Clear the folder from the files or choose another folder</source>
+ <translation>Папка Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ %s уже Ñодержит файлы (%d шт.). Удалите вÑе файлы оттуда или выберите другую папку</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1536"/>
+ <source>%s: Cloud import (cloud phase) failed. Used cloud instance is &apos;%s&apos;
+</source>
+ <translation>%s: Ðе удалоÑÑŒ произвеÑти импорт из облака (фаза загрузки из облака). ПользовательÑкий облачный ÑкземплÑÑ€ &apos;%s&apos;
+</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1545"/>
+ <source>Import from Cloud isn&apos;t supported for more than one VM instance.</source>
+ <translation>Импорт из облака не поддерживаетÑÑ Ð´Ð»Ñ Ð±Ð¾Ð»ÐµÐµ чем одного ÑкземплÑра Ð’Ðœ.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1559"/>
+ <source>%s: Cloud cleanup action - the instance wasn&apos;t found</source>
+ <translation>%s: ДейÑтвие очиÑтки облака - ÑкземплÑÑ€ не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1596"/>
+ <source>Rollback action for Import Cloud operation failed. Some leavings may exist on the local disk or in the Cloud.</source>
+ <translation>Ðе удалоÑÑŒ завершить дейÑтвие отката Ð´Ð»Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ð¸ импорта из облака. Локальный диÑк или облако может Ñодержать некоторые оÑтатки.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1705"/>
+ <source>Error opening &apos;%s&apos; for reading (%Rrc)
+</source>
+ <translation>Ошибка при открытии &apos;%s&apos; на чтение (%Rrc)
+</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1710"/>
+ <source>Error reading the downloaded file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð·Ð°Ð³Ñ€ÑƒÐ¶ÐµÐ½Ð½Ð¾Ð³Ð¾ файла &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1841"/>
+ <source>%s: Error reading &apos;%s&apos; (%Rrc)</source>
+ <translation>%s: Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1875"/>
+ <source>Could not read the file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно прочитать файл &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1889"/>
+ <source>Could not create the directory &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать директорию &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1893"/>
+ <source>Error during getting info about the directory &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ð¸ о директории &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1902"/>
+ <source>Could not create the file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать файл &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1907"/>
+ <source>Could not write into the file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно запиÑать в файл&apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1947"/>
+ <source>The hard disk &apos;%s&apos; already exists.</source>
+ <translation>ЖеÑткий диÑк &apos;%s&apos; уже ÑущеÑтвует.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1960"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4152"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4263"/>
+ <source>Importing virtual disk image &apos;%s&apos;</source>
+ <translation>Импортирование образа виртуального диÑка &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1971"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4242"/>
+ <source>Importing medium &apos;%s&apos;</source>
+ <translation>Импортрирование ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2051"/>
+ <source>Creating new VM &apos;%s&apos;</source>
+ <translation>Создание новой ВМ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2312"/>
+ <source>Failed to open OVF file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть OVF файл &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2344"/>
+ <source>Failed to open the signature file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть файл подпиÑи &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2353"/>
+ <source>Failed to open the manifest file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть файл манифеÑта &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2375"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3706"/>
+ <source>Error opening the OVA file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка при открытии OVA файла &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2381"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3712"/>
+ <source>Error reading the OVA file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка при чтении OVA файла &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2405"/>
+ <source>Error reading OVA &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ OVA &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2494"/>
+ <source>OVA &apos;%s&apos; does not contain an .ovf-file</source>
+ <translation>OVA &apos;%s&apos; не Ñодержит .ovf файл</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2544"/>
+ <source>Could not read the OVF file for &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно прочитать OVF файл Ð´Ð»Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2602"/>
+ <source>Error reading the manifest file &apos;%s&apos; for &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° манифеÑта &apos;%s&apos; Ð´Ð»Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2617"/>
+ <source>Failed to parse manifest file &apos;%s&apos; for &apos;%s&apos; (%Rrc): %s</source>
+ <translation>Ðе удалоÑÑŒ разобрать файл манифеÑта &apos;%s&apos; Ð´Ð»Ñ &apos;%s&apos; (%Rrc): %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2673"/>
+ <source>Error reading the signature file &apos;%s&apos; for &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° подпиÑи &apos;%s&apos; Ð´Ð»Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2764"/>
+ <source>Unsupported signed digest type (%#x)</source>
+ <translation>Ðеподдерживаемый тип подпиÑанного дайджеÑта (%#x)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2767"/>
+ <source>Error reading signed manifest digest: %Rrc</source>
+ <translation>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð´Ð°Ð¹Ð´Ð¶ÐµÑта подпиÑанного манифеÑта: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2770"/>
+ <source>Could not locate signed digest for &apos;%s&apos; in the cert-file for &apos;%s&apos;</source>
+ <translation>Ðевозможно определить меÑтоположение подпиÑанного дайджеÑта Ð´Ð»Ñ &apos;%s&apos; в файле Ñертификата Ð´Ð»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2773"/>
+ <source>RTManifestEntryQueryAttr failed unexpectedly: %Rrc</source>
+ <translation>RTManifestEntryQueryAttr неожиданно завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2776"/>
+ <source>Error parsing the .cert-file for &apos;%s&apos;: %s</source>
+ <translation>Ошибка интерпретации файла .cert Ð´Ð»Ñ &apos;%s&apos;: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2798"/>
+ <source>Error reading the PKCS#7/CMS signature from &apos;%s&apos; for &apos;%s&apos; (%Rrc): %s</source>
+ <translation>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñи PKCS#7/CMS из &apos;%s&apos; Ð´Ð»Ñ &apos;%s&apos; (%Rrc): %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2803"/>
+ <source>Malformed .cert-file for &apos;%s&apos;: Signer&apos;s certificate not found (%Rrc)</source>
+ <translation>Поврежденный файл .cert Ð´Ð»Ñ &apos;%s&apos;: Сертификат подпиÑывающего лица не найден (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2806"/>
+ <source>Error reading the signer&apos;s certificate from &apos;%s&apos; for &apos;%s&apos; (%Rrc): %s</source>
+ <translation>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ñертификата подпиÑывающего лица из &apos;%s&apos; Ð´Ð»Ñ &apos;%s&apos; (%Rrc): %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2844"/>
+ <source>Found .cert-file but no .mf-file for &apos;%s&apos;</source>
+ <translation>Ðайден файл .cert, но не найден .mf файл Ð´Ð»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2875"/>
+ <source>The manifest signature does not match</source>
+ <translation>ПодпиÑÑŒ манифеÑта не ÑоответÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2878"/>
+ <source>Error validating the manifest signature (%Rrc, %s)</source>
+ <translation>Ошибка при проверке подпиÑи манифеÑта (%Rrc, %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2881"/>
+ <source>RTCrDigestUpdateFromVfsFile failed: %Rrc</source>
+ <translation>RTCrDigestUpdateFromVfsFile завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2885"/>
+ <source>RTCrDigestCreateByType failed: %Rrc</source>
+ <translation>RTCrDigestCreateByType завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2963"/>
+ <source>Failed to query trusted CAs and Certificates from the system and for the current user (%Rrc%RTeim)</source>
+ <translation>Ðе удалоÑÑŒ запроÑить доверенные УЦ и Ñертификаты у ÑиÑтемы Ð´Ð»Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ³Ð¾ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ (%Rrc%RTeim)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3043"/>
+ <source>Invalid PKCS#7/CMS type: %s, expected %s (signedData)</source>
+ <translation>ÐедопуÑтимый тип PKCS#7/CMS: %s, ожидаетÑÑ %s (signedData)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3046"/>
+ <source>Invalid PKCS#7/CMS inner type: %s, expected %s (data)</source>
+ <translation>ÐедопуÑтимый внутренний тип PKCS#7/CMS: %s, ожидаетÑÑ %s (data)</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3049"/>
+ <source>Invalid PKCS#7/CMS data: embedded (%u bytes), expected external</source>
+ <translation>
+ <numerusform>ÐедопуÑтимые данные PKCS#7/CMS: внедрено (%u байт), ожидаетÑÑ external</numerusform>
+ <numerusform>ÐедопуÑтимые данные PKCS#7/CMS: внедрено (%u байта), ожидаетÑÑ external</numerusform>
+ <numerusform>ÐедопуÑтимые данные PKCS#7/CMS: внедрено (%u байтов), ожидаетÑÑ external</numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3482"/>
+ <source>%u out of %u PKCS#7/CMS signatures verfified okay</source>
+ <translation>
+ <numerusform>%u из of %u PKCS#7/CMS уÑпешно проверенной подпиÑи</numerusform>
+ <numerusform>%u из of %u PKCS#7/CMS уÑпешно проверенных подпиÑей</numerusform>
+ <numerusform>%u из of %u PKCS#7/CMS уÑпешно проверенных подпиÑей</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3053"/>
+ <source>Invalid PKCS#7/CMS: No signers</source>
+ <translation>ÐедопуÑтимый PKCS#7/CMS: Ðет подпиÑывающих лиц</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3067"/>
+ <source>Invalid PKCS#7/CMS: Using a different certificate</source>
+ <translation>ÐедопуÑтимый PKCS#7/CMS: ИÑпользование другого Ñертификата</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3075"/>
+ <source>RTCrStoreCreateInMem failed: %Rrc</source>
+ <translation>RTCrStoreCreateInMem завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3093"/>
+ <source>Failed to validate PKCS#7/CMS signature: %Rrc%RTeim</source>
+ <translation>Ðе удалоÑÑŒ проверить подпиÑÑŒ PKCS#7/CMS: %Rrc%RTeim</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3098"/>
+ <source>RTCrStoreCertAddX509 failed: %Rrc%RTeim</source>
+ <translation>RTCrStoreCertAddX509 завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc%RTeim</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3138"/>
+ <source>A self signed certificate was used to sign &apos;%s&apos;</source>
+ <translation>СамоподпиÑанный Ñертификат иÑпользуетÑÑ Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñи &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3141"/>
+ <source>Self signed certificate used to sign &apos;%s&apos; is not currently valid</source>
+ <translation>СамоподпиÑанный Ñертификат, иÑпользуемый Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñи &apos;%s&apos;, недейÑтвителен в наÑтоÑщий момент</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3146"/>
+ <source>Verification of the self signed certificate failed (%Rrc%#RTeim)</source>
+ <translation>Ðе удалоÑÑŒ проверить ÑамоподпиÑанный Ñертификат (%Rrc%#RTeim)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3148"/>
+ <source>Verification of the self signed certificate used to sign &apos;%s&apos; failed (%Rrc)%RTeim</source>
+ <translation>Ðе удалоÑÑŒ проверить ÑамоподпиÑанный Ñертификат, иÑпользуемый Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñи &apos;%s&apos;, (%Rrc)%RTeim</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3156"/>
+ <source>Self signed certificate used to sign &apos;%s&apos; is not marked as certificate authority (CA)</source>
+ <translation>СамоподпиÑанный Ñертификат, иÑпользуемый Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñи &apos;%s&apos;, не отмечен как Ñертификат удоÑтоверÑющего центра (УЦ)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3181"/>
+ <source>RTCrX509CertPathsSetTrustedStore failed (%Rrc)</source>
+ <translation>RTCrX509CertPathsSetTrustedStore завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3220"/>
+ <source>The certificate used to sign &apos;%s&apos; (or a certificate in the path) is not currently valid (%Rrc)</source>
+ <translation>Сертификат, иÑпользуемый Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñи &apos;%s&apos;, (или Ñертификат указанный в пути) недейÑтвителен в наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3224"/>
+ <source>RTCrX509CertPathsSetValidTimeSpec failed: %Rrc</source>
+ <translation>RTCrX509CertPathsSetValidTimeSpec завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3229"/>
+ <source>No trusted certificate paths</source>
+ <translation>Ðет путей к доверенным Ñертификатам</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3236"/>
+ <source>The certificate used to sign &apos;%s&apos; is not currently valid</source>
+ <translation>Сертификат, иÑпользуемый Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñи &apos;%s&apos;, недейÑтвителен в наÑтоÑщее времÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3240"/>
+ <source>Certificate path validation failed (%Rrc%RTeim)</source>
+ <translation>Ðе удалоÑÑŒ проверить путь Ñертификата (%Rrc%RTeim)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3243"/>
+ <source>Certificate path building failed (%Rrc%RTeim)</source>
+ <translation>Ðе удалоÑÑŒ поÑтроить путь Ñертификата (%Rrc%RTeim)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3248"/>
+ <source>RTCrX509CertPathsCreate failed: %Rrc</source>
+ <translation>RTCrX509CertPathsCreate завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3260"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3262"/>
+ <source>%s verification failed: %Rrc%RTeim</source>
+ <translation>Ðе удалоÑÑŒ проверить %s: %Rrc%RTeim</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3294"/>
+ <source>OVF &amp; PKCS#7/CMS signature</source>
+ <translation>ПодпиÑÑŒ OVF и PKCS#7/CMS</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3296"/>
+ <source>PKCS#7/CMS signature</source>
+ <translation>ПодпиÑÑŒ PKCS#7/CMS</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3322"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3324"/>
+ <source>PKCS#7/CMS signature #%u does not include the signing certificate</source>
+ <translation>PKCS#7/CMS подпиÑÑŒ #%u не включает Ñертификат Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑываниÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3336"/>
+ <source>%s: Untrusted self-signed certificate</source>
+ <translation>%s: Ðедоверенный ÑамоподпиÑанный Ñертификат</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3361"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3416"/>
+ <source>%s: Untrusted timestamp (%s)</source>
+ <translation>%s: Ðедоверенный временной отпечаток (%s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3363"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3418"/>
+ <source>%s: Not valid at current time, but validates fine for untrusted signing time (%s)</source>
+ <translation>%s: ÐедейÑтвителен в наÑтоÑщее времÑ, но проходит проверку Ð´Ð»Ñ Ð½ÐµÐ´Ð¾Ð²ÐµÑ€ÐµÐ½Ð½Ð¾Ð³Ð¾ времени подпиÑÑ‹Ð²Ð°Ð½Ð¸Ñ (%s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3520"/>
+ <source>Importing appliance &apos;%s&apos;</source>
+ <translation>Импорт конфигурации &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3523"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3595"/>
+ <source>Failed to create task for importing appliance into VirtualBox</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать задачу Ð´Ð»Ñ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð° конфигурации в VirtualBox</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3579"/>
+ <source>Importing VM from Cloud...</source>
+ <translation>Импорт ВМ из облака...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3583"/>
+ <source>Start import VM from the Cloud...</source>
+ <translation>Старт импорта ВМ из облака...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3609"/>
+ <source>Failed to start thread for importing appliance into VirtualBox</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить поток Ð´Ð»Ñ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð° конфигурации в VirtualBox</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3845"/>
+ <source>Error fudging missing OVF digest in manifest: %Rrc</source>
+ <translation>Ошибка подделки потерÑнного дайджеÑта OVF в манифеÑте: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3866"/>
+ <source>Digest mismatch (%Rrc): %s</source>
+ <translation>ДайджеÑÑ‚ не ÑоответÑтвует (%Rrc): %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3948"/>
+ <source>Invalid channel %RU32 specified; IDE controllers support only 0, 1 or 2</source>
+ <translation>Указан недопуÑтимый канал %RU32; IDE контроллеры поддерживают только 0, 1 или 2</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4081"/>
+ <source>Unsupported medium format for disk image &apos;%s&apos;</source>
+ <translation>Ðеподдерживаемый формат ноÑÐ¸Ñ‚ÐµÐ»Ñ Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð·Ð° диÑка &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4113"/>
+ <source>Could not find a valid medium format for the target disk &apos;%s&apos;</source>
+ <translation>Ðевозможно найти правильный формат ноÑÐ¸Ñ‚ÐµÐ»Ñ Ð´Ð»Ñ Ð´Ð¸Ñка Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4119"/>
+ <source>The target disk &apos;%s&apos; has no extension </source>
+ <translation>ОтÑутÑтвует раÑширение у диÑка Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ &apos;%s&apos; </translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4183"/>
+ <source>Creating disk image &apos;%s&apos;</source>
+ <translation>Создание образа диÑка &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4195"/>
+ <source>Could not find a valid medium format for the source disk &apos;%s&apos; Check correctness of the image format URL in the OVF description file or extension of the image</source>
+ <translation>Ðевозможно найти правильный формат ноÑÐ¸Ñ‚ÐµÐ»Ñ Ð´Ð»Ñ Ð´Ð¸Ñка иÑточника &apos;%s&apos; Проверьте правильноÑÑ‚ÑŒ URL формата образа в файле опиÑÐ°Ð½Ð¸Ñ OVF или раÑширение файла образа</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4219"/>
+ <source>Error opening decompressed image file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка при открытии раÑпакованного файла образа &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4298"/>
+ <source>Failed to delete the temporary file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ удалить временный файл &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4457"/>
+ <source>Too many network adapters: OVF requests %d network adapters, but VirtualBox only supports %d</source>
+ <translation>
+ <numerusform>Слишком много Ñетевых адаптеров: OVF запрашивает %d Ñетевой адаптер, но VirtualBox поддерживает только до %d</numerusform>
+ <numerusform>Слишком много Ñетевых адаптеров: OVF запрашивает %d Ñетевых адаптера, но VirtualBox поддерживает только до %d</numerusform>
+ <numerusform>Слишком много Ñетевых адаптеров: OVF запрашивает %d Ñетевых адаптеров, но VirtualBox поддерживает только до %d</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4597"/>
+ <source>Too many IDE controllers in OVF; import facility only supports two</source>
+ <translation>Слишком много IDE контроллеров в OVF; СредÑтво импорта поддерживает только два</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4614"/>
+ <source>Invalid IDE controller type &quot;%s&quot;</source>
+ <translation>ÐедопуÑтимый тип IDE контроллера &quot;%s&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4624"/>
+ <source>Too many SATA controllers in OVF; import facility only supports one</source>
+ <translation>Слишком много SATA контроллеров в OVF; СредÑтво импорта поддерживает только один</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4638"/>
+ <source>Invalid SATA controller type &quot;%s&quot;</source>
+ <translation>ÐедопуÑтимый тип SATA контроллера &quot;%s&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4647"/>
+ <source>Too many SCSI controllers in OVF; import facility only supports one</source>
+ <translation>Слишком много SCSI контроллеров в OVF; СредÑтво импорта поддерживает только один</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4668"/>
+ <source>Invalid SCSI controller type &quot;%s&quot;</source>
+ <translation>ÐедопуÑтимый тип SCSI контроллера &quot;%s&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4682"/>
+ <source>Too many SAS controllers in OVF; import facility only supports one</source>
+ <translation>Слишком много SAS контроллеров в OVF; СредÑтво импорта поддерживает только один</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4700"/>
+ <source>Too many VirtioSCSI controllers in OVF; import facility only supports one</source>
+ <translation>Слишком много VirtioSCSI контроллеров в OVF; СредÑтво импорта поддерживает только один</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4718"/>
+ <source>Invalid VirtioSCSI controller type &quot;%s&quot;</source>
+ <translation>ÐедопуÑтимый тип VirtioSCSI контроллера &quot;%s&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4737"/>
+ <source>Too many floppy controllers in OVF; import facility only supports one</source>
+ <translation>Слишком много флоппи контроллеров в OVF; СредÑтво импорта поддерживает только один</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4807"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5076"/>
+ <source>Unknown error during OVF import</source>
+ <translation>ÐеизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° во Ð²Ñ€ÐµÐ¼Ñ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð° OVF</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4887"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4953"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4959"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5441"/>
+ <source>Internal inconsistency looking up disk image &apos;%s&apos;</source>
+ <translation>ВнутреннÑÑ Ð½ÐµÑоглаÑованноÑÑ‚ÑŒ при поиÑке образа диÑка &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5546"/>
+ <source>&lt;vbox:Machine&gt; element in OVF contains a medium attachment for the disk image %s but the OVF describes no such image</source>
+ <translation>Элемент &lt;vbox:Machine&gt; в OVF Ñодержит прикрепление ноÑÐ¸Ñ‚ÐµÐ»Ñ Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð·Ð° диÑка %s, но OVF не опиÑывает Ñтот образ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5683"/>
+ <source>Missing guest OS type</source>
+ <translation>ОтÑутÑтвует тип гоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5696"/>
+ <source>CPU count missing</source>
+ <translation>ОтÑутÑтвует количеÑтво ЦПУ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5709"/>
+ <source>RAM size missing</source>
+ <translation>ОтÑутÑтвует размер RAM</translation>
+ </message>
+</context>
+<context>
+ <name>AudioAdapter</name>
+ <message>
+ <location filename="../src-server/AudioAdapterImpl.cpp" line="436"/>
+ <source>Invalid audio codec type %d</source>
+ <translation>ÐедопуÑтимый тип аудио кодека %d</translation>
+ </message>
+</context>
+<context>
+ <name>AutoCallerCtx</name>
+ <message>
+ <location filename="../src-all/AutoCaller.cpp" line="207"/>
+ <source>The object functionality is limited</source>
+ <translation>ФункциональноÑÑ‚ÑŒ объекта ограничена</translation>
+ </message>
+ <message>
+ <location filename="../src-all/AutoCaller.cpp" line="216"/>
+ <source>The object is not ready</source>
+ <translation>Объект не готов</translation>
+ </message>
+</context>
+<context>
+ <name>BandwidthControl</name>
+ <message>
+ <location filename="../src-server/BandwidthControlImpl.cpp" line="385"/>
+ <source>Could not find a bandwidth group named &apos;%s&apos;</source>
+ <translation>Ðевозможно найти группу полоÑÑ‹ пропуÑÐºÐ°Ð½Ð¸Ñ Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/BandwidthControlImpl.cpp" line="396"/>
+ <source>Bandwidth group limit cannot be negative</source>
+ <translation>Ограничение группы полоÑÑ‹ пропуÑÐºÐ°Ð½Ð¸Ñ Ð½Ðµ может быть отрицательным</translation>
+ </message>
+ <message>
+ <location filename="../src-server/BandwidthControlImpl.cpp" line="410"/>
+ <source>Bandwidth group named &apos;%s&apos; already exists</source>
+ <translation>Группа полоÑÑ‹ пропуÑÐºÐ°Ð½Ð¸Ñ Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos; уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/BandwidthControlImpl.cpp" line="439"/>
+ <source>The bandwidth group &apos;%s&apos; is still in use</source>
+ <translation>Группа полоÑÑ‹ пропуÑÐºÐ°Ð½Ð¸Ñ &apos;%s&apos; вÑе еще иÑпользуетÑÑ</translation>
+ </message>
+</context>
+<context>
+ <name>BandwidthGroup</name>
+ <message>
+ <location filename="../src-server/BandwidthGroupImpl.cpp" line="68"/>
+ <source>Invalid bandwidth group type</source>
+ <translation>ÐедопуÑтимый тип группы полоÑÑ‹ пропуÑканиÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/BandwidthGroupImpl.cpp" line="240"/>
+ <source>Bandwidth group limit cannot be negative</source>
+ <translation>Ограничение группы полоÑÑ‹ пропуÑÐºÐ°Ð½Ð¸Ñ Ð½Ðµ может быть отрицательным</translation>
+ </message>
+</context>
+<context>
+ <name>BaseTextScript</name>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="55"/>
+ <source>Failed to open &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="96"/>
+ <source>&apos;%s&apos; isn&apos;t valid UTF-8: %Rrc</source>
+ <translation>&apos;%s&apos; не ÑвлÑетÑÑ Ð¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ñ‹Ð¼ UTF-8: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="99"/>
+ <source>Error reading &apos;%s&apos;: %Rrc</source>
+ <translation>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/TextScript.cpp" line="103"/>
+ <source>Failed to allocate memory (%&apos;RU64 bytes) for &apos;%s&apos;</source>
+ <translation>
+ <numerusform>Ðе удалоÑÑŒ выделить памÑÑ‚ÑŒ (%&apos;RU64 байт) Ð´Ð»Ñ &apos;%s&apos;</numerusform>
+ <numerusform>Ðе удалоÑÑŒ выделить памÑÑ‚ÑŒ (%&apos;RU64 байта) Ð´Ð»Ñ &apos;%s&apos;</numerusform>
+ <numerusform>Ðе удалоÑÑŒ выделить памÑÑ‚ÑŒ (%&apos;RU64 байтов) Ð´Ð»Ñ &apos;%s&apos;</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="107"/>
+ <source>&apos;%s&apos; is too big (max 16MB): %&apos;RU64</source>
+ <translation>&apos;%s&apos; Ñлишком большой (макÑимально 16MB): %&apos;RU64</translation>
+ </message>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="109"/>
+ <source>RTVfsFileQuerySize failed (%Rrc)</source>
+ <translation>RTVfsFileQuerySize завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="180"/>
+ <source>Error writing to &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка запиÑи в &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="183"/>
+ <source>Error creating/replacing &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка ÑозданиÑ/замены &apos;%s&apos; (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>Certificate</name>
+ <message>
+ <location filename="../src-server/CertificateImpl.cpp" line="419"/>
+ <source>Unknown item %u</source>
+ <translation>ÐеизвеÑтный Ñлемент %u</translation>
+ </message>
+ <message>
+ <location filename="../src-server/CertificateImpl.cpp" line="570"/>
+ <source>RTAsn1EncodeToBuffer failed with %Rrc</source>
+ <translation>RTAsn1EncodeToBuffer завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/CertificateImpl.cpp" line="574"/>
+ <source>RTAsn1EncodePrepare failed with %Rrc</source>
+ <translation>RTAsn1EncodePrepare завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+</context>
+<context>
+ <name>CloudNetwork</name>
+ <message>
+ <location filename="../src-server/CloudNetworkImpl.cpp" line="150"/>
+ <source>Network name cannot be empty</source>
+ <translation>Ð˜Ð¼Ñ Ñети не может быть пуÑтым</translation>
+ </message>
+</context>
+<context>
+ <name>CloudProviderManager</name>
+ <message>
+ <location filename="../src-server/CloudProviderManagerImpl.cpp" line="272"/>
+ <source>Could not find a cloud provider with UUID {%RTuuid}</source>
+ <translation>Ðевозможно найти провайдер облака Ñ UUID {%RTuuid}</translation>
+ </message>
+ <message>
+ <location filename="../src-server/CloudProviderManagerImpl.cpp" line="290"/>
+ <source>Could not find a cloud provider with short name &apos;%s&apos;</source>
+ <translation>Ðевозможно найти провайдер облака Ñ ÐºÑ€Ð°Ñ‚ÐºÐ¸Ð¼ именем &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/CloudProviderManagerImpl.cpp" line="308"/>
+ <source>Could not find a cloud provider with name &apos;%s&apos;</source>
+ <translation>Ðевозможно найти провайдер облака Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos;</translation>
+ </message>
+</context>
+<context>
+ <name>Console</name>
+ <message>
+ <location filename="../include/ConsoleImpl.h" line="105"/>
+ <source>The console is not powered up (%Rfn)</source>
+ <translation>КонÑоль не запущена (%Rfn)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="1612"/>
+ <source>The saved state file &apos;%ls&apos; is invalid (%Rrc). Delete the saved state and try again</source>
+ <translation>Ðекорректный файл Ñохраненного ÑоÑтоÑÐ½Ð¸Ñ &apos;%ls&apos; (%Rrc). Удалите Ñохраненное ÑоÑтоÑние и попытайтеÑÑŒ Ñнова</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="1873"/>
+ <source>Internal application error</source>
+ <translation>ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ° приложениÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="1877"/>
+ <source>Temporary failure due to guest activity, please retry</source>
+ <translation>Ð’Ñ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ Ð½ÐµÐ¸ÑправноÑÑ‚ÑŒ из-за активноÑти гоÑтевой ÑиÑтемы, пожалуйÑта, попробуйте Ñнова</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2149"/>
+ <source>Cannot power down at this point during a save state</source>
+ <translation>Ðевозможно выключить питание на Ñтом Ñтапе во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ ÑоÑтоÑниÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2160"/>
+ <source>Cannot power down at this point in a teleportation</source>
+ <translation>Ðевозможно выключить питание на Ñтом Ñтапе во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ð¾Ñ€Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2170"/>
+ <source>Cannot power down at this point in an online snapshot</source>
+ <translation>Ðевозможно выключить питание на Ñтом Ñтапе во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‡ÐµÐ³Ð¾ Ñнимка</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2180"/>
+ <source>Cannot power down at this point in a live snapshot</source>
+ <translation>Ðевозможно выключить питание на Ñтом Ñтапе во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¶Ð¸Ð²Ð¾Ð³Ð¾ Ñнимка</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2185"/>
+ <source>Cannot power down a saved virtual machine</source>
+ <translation>Ðевозможно выключить питание у Ñохраненной виртуальной машины</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2187"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7474"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7532"/>
+ <source>The virtual machine is being powered down</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð° выключаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2190"/>
+ <source>Invalid machine state: %s (must be Running, Paused or Stuck)</source>
+ <translation>ÐедопуÑтимое ÑоÑтоÑние машины: %s (Ð´Ð¾Ð»Ð¶Ð½Ð°Ñ Ð±Ñ‹Ñ‚ÑŒ Работает, ПриоÑтановлена или ЗавиÑла)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2228"/>
+ <source>Could not create VMPowerDownTask object
+</source>
+ <translation>Ðевозможно Ñоздать объект VMPowerDownTask
+</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2285"/>
+ <source>Could not reset the machine (%Rrc)</source>
+ <translation>Ðевозможно ÑброÑить ÑоÑтоÑние машины (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2332"/>
+ <source>CPU %d is not attached</source>
+ <translation>ЦПУ %d не подключен</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2398"/>
+ <source>Hot-Remove failed (rc=%Rrc)</source>
+ <translation>ГорÑчее удаление не удалоÑÑŒ (rc=%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2402"/>
+ <source>Hot-Remove was aborted because the CPU may still be used by the guest</source>
+ <translation>ГорÑчее удаление прервано, потому что ЦПУ вÑе еще может иÑпользоватьÑÑ Ð³Ð¾Ñтевой ÑиÑтемой</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2476"/>
+ <source>CPU %d is already attached</source>
+ <translation>ЦПУ %d уже подключен</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2508"/>
+ <source>Could not add CPU to the machine (%Rrc)</source>
+ <translation>Ðевозможно добавить ЦПУ в машину (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2534"/>
+ <source>Cannot resume the machine as it is not paused (machine state: %s)</source>
+ <translation>Ðевозможно возобновить выполнение машины, потому что она не приоÑтановлена (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2576"/>
+ <source>Controlled power off failed (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ управлÑемо выключить питание (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2624"/>
+ <source>Checking if the ACPI Power Button event was handled by the guest OS failed (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ проверить, что Ñобытие Ð½Ð°Ð¶Ð°Ñ‚Ð¸Ñ ACPI кнопки Ð¿Ð¸Ñ‚Ð°Ð½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ð±Ð°Ñ‚Ñ‹Ð²Ð°ÐµÑ‚ÑÑ Ð³Ð¾Ñтевой ÑиÑтемой (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2644"/>
+ <source>Invalid machine state %s when checking if the guest entered the ACPI mode</source>
+ <translation>ÐедопуÑтимое ÑоÑтоÑние машины %s при проверке перешел ли гоÑÑ‚ÑŒ в режим ACPI</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2707"/>
+ <source>Sending sleep button event failed (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ отправить Ñобытие Ð½Ð°Ð¶Ð°Ñ‚Ð¸Ñ ÐºÐ½Ð¾Ð¿ÐºÐ¸ &quot;Сон&quot; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2806"/>
+ <source>Cannot attach a USB device to the machine which is not running or paused (machine state: %s)</source>
+ <translation>Ðевозможно подключить USB уÑтройÑтво к машине, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð½Ðµ запущена или приоÑтановлена (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2816"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2827"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2890"/>
+ <source>The virtual machine does not have a USB controller</source>
+ <translation>У виртуальной машины нет USB контроллера</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2852"/>
+ <source>USB device with UUID {%RTuuid} is not attached to this machine</source>
+ <translation>USB уÑтройÑтво Ñ UUID {%RTuuid} не подключено к виртуальной машине</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2919"/>
+ <source>Could not find a USB device with address &apos;%s&apos;</source>
+ <translation>Ðевозможно найти USB уÑтройÑтво Ñ Ð°Ð´Ñ€ÐµÑом &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2952"/>
+ <source>Could not find a USB device with uuid {%RTuuid}</source>
+ <translation>Ðевозможно найти USB уÑтройÑтво Ñ UUID {%RTuuid}</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2970"/>
+ <source>Cannot create a transient shared folder on a machine in a saved state (machine state: %s)</source>
+ <translation>Ðевозможно Ñоздать временную общую папку Ð´Ð»Ñ Ð¼Ð°ÑˆÐ¸Ð½Ñ‹ в Ñохраненном ÑоÑтоÑнии (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2979"/>
+ <source>Cannot create a transient shared folder on the machine while it is changing the state (machine state: %s)</source>
+ <translation>Ðевозможно Ñоздать временную общую папку Ð´Ð»Ñ Ð¼Ð°ÑˆÐ¸Ð½Ñ‹ во Ð²Ñ€ÐµÐ¼Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ ÑоÑтоÑÐ½Ð¸Ñ (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2986"/>
+ <source>Shared folder named &apos;%s&apos; already exists</source>
+ <translation>ÐžÐ±Ñ‰Ð°Ñ Ð¿Ð°Ð¿ÐºÐ° Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos; уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3042"/>
+ <source>Cannot remove a transient shared folder from a machine in a saved state (machine state: %s)</source>
+ <translation>Ðевозможно удалить временную общую папку у машины в Ñохраненном ÑоÑтоÑнии (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3051"/>
+ <source>Cannot remove a transient shared folder from the machine while it is changing the state (machine state: %s)</source>
+ <translation>Ðевозможно удалить временную общую папку у машины во Ð²Ñ€ÐµÐ¼Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ ÑоÑтоÑÐ½Ð¸Ñ (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3098"/>
+ <source>The ID and password must be both valid</source>
+ <translation>ID и пароль должны быть верны</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3135"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6805"/>
+ <source>Could not resume the machine execution (%Rrc)</source>
+ <translation>Ðевозможно возобновить выполнение машины (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3140"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3173"/>
+ <source>A password with the given ID already exists</source>
+ <translation>Пароль Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð½Ñ‹Ð¼ ID уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3142"/>
+ <source>Failed to allocate enough secure memory for the key</source>
+ <translation>Ðевозможно выделить доÑтаточного количеÑтва защищенной памÑти Ð´Ð»Ñ ÐºÐ»ÑŽÑ‡Ð°</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3144"/>
+ <source>Unknown error happened while adding a password (%Rrc)</source>
+ <translation>Произошла неизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° во Ð²Ñ€ÐµÐ¼Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3156"/>
+ <source>IDs and passwords must not be empty</source>
+ <translation>ID и пароли не должны быть пуÑтыми</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3159"/>
+ <source>The number of entries in the id and password arguments must match</source>
+ <translation>КоличеÑтво Ñлементов у маÑÑивов ID и паролей должно Ñовпадать</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3203"/>
+ <source>The ID must be valid</source>
+ <translation>ID должен быть правильным</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3217"/>
+ <source>A password with the ID &quot;%s&quot; does not exist</source>
+ <translation>Пароль Ñ ID &quot;%s&quot; не ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3219"/>
+ <source>Failed to remove password with ID &quot;%s&quot; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ удалить пароль Ñ ID &quot;%s&quot; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3230"/>
+ <source>A password is still in use by the VM</source>
+ <translation>Пароль вÑе еще иÑпользуетÑÑ Ð’Ðœ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3232"/>
+ <source>Deleting all passwords failed (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ удалить вÑе пароли (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3275"/>
+ <source>Invalid machine state: %s</source>
+ <translation>ÐедопуÑтимое ÑоÑтоÑние машины: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3384"/>
+ <source>Could suspend VM for medium change (%Rrc)</source>
+ <translation>Ðевозможно приоÑтановить Ð’Ðœ Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3397"/>
+ <source>Invalid state &apos;%s&apos; for changing medium</source>
+ <translation>ÐедопуÑтимое ÑоÑтоÑние &apos;%s&apos; Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½Ð¾ÑителÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3479"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3652"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3825"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6403"/>
+ <source>Could not find storage controller &apos;%ls&apos;</source>
+ <translation>Ðевозможно найти контроллер &apos;%ls&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3535"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3708"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3878"/>
+ <source>Could not mount the media/drive &apos;%ls&apos; (%Rrc)</source>
+ <translation>Ðевозможно подключить ноÑитель/диÑк &apos;%ls&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3536"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3709"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3879"/>
+ <source>Could not unmount the currently mounted media/drive (%Rrc)</source>
+ <translation>Ðевозможно отключить подключенный ноÑитель/диÑк (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4078"/>
+ <source>The network adapter #%u is not enabled</source>
+ <translation>Сетевой адартер #%u не включен</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4669"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6466"/>
+ <source>could not query medium interface of controller</source>
+ <translation>Ðевозможно запроÑить Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð½Ð¾Ñителей у контроллера</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4674"/>
+ <source>The provided password for ID &quot;%s&quot; is not correct for at least one disk using this ID</source>
+ <translation>Ðекорректный указанный пароль Ð´Ð»Ñ ID &quot;%s&quot; Ð´Ð»Ñ ÐºÐ°Ðº минимум одно диÑка, иÑпользующего Ñтот ID</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4680"/>
+ <source>Failed to set the encryption key (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ уÑтановить ключ ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4688"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6469"/>
+ <source>could not query base interface of controller</source>
+ <translation>Ðевозможно запроÑить базовый Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»Ð»ÐµÑ€Ð°</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4788"/>
+ <source>Failed to decode the key (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ декодировать ключ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4793"/>
+ <source>Failed to allocate secure memory for the key (%Rrc)</source>
+ <translation>Ðевозможно выделить защищенную памÑÑ‚ÑŒ Ð´Ð»Ñ ÐºÐ»ÑŽÑ‡Ð° (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4797"/>
+ <source>The base64 encoding of the passed key is incorrect</source>
+ <translation>Кодировка Base64 переданного ключа не корректна</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4801"/>
+ <source>The encryption configuration is incomplete</source>
+ <translation>ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð½Ðµ полнаÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4886"/>
+ <source>Could not change the network adaptor attachement type (%Rrc)</source>
+ <translation>Ðевозможно изменить тип Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ñетевого адаптера (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="5212"/>
+ <source>Failed to change the serial port attachment (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ изменить подключение поÑледовательного порта (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="5501"/>
+ <source>VirtualBox Remote Desktop Extension server can&apos;t bind to the port(s): %s</source>
+ <translation>Сервер удаленного рабочего Ñтола VirtualBox (VRDE Ñервер) не может привÑзатьÑÑ Ðº порту: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="5510"/>
+ <source>VirtualBox Remote Desktop Extension is not available</source>
+ <translation>Сервер удаленного рабочего Ñтола VirtualBox (VRDE Ñервер) не доÑтупен</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="5518"/>
+ <source>Could not find the VirtualBox Remote Desktop Extension library</source>
+ <translation>Ðевозможно найти библиотеку Ñервера удаленного рабочего Ñтола VirtualBox</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="5521"/>
+ <source>Failed to launch the Remote Desktop Extension server (%Rrc)</source>
+ <translation>Ðе удалÑÑŒ запуÑтить Ñервер удаленного рабочего Ñтола (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="5667"/>
+ <source>Sending monitor hot-plug event failed (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ отправить Ñобытие горÑчего Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð½Ð¸Ñ‚Ð¾Ñ€Ð° (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6192"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6249"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6280"/>
+ <source>The VBoxGuestPropSvc service call failed with the error %Rrc</source>
+ <translation>Вызов к ÑервиÑу VBoxGuestPropSvc завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6475"/>
+ <source>Failed to perform an online medium merge (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ Ñделать онлайн объединение ноÑÐ¸Ñ‚ÐµÐ»Ñ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6618"/>
+ <source>Unsupported priority type (%d)</source>
+ <translation>Ðеподдерживаемый тип приоритета (%d)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6622"/>
+ <source>Could not set the priority of the process (%Rrc). Try to set it when VM is not started.</source>
+ <translation>Ðевозможно уÑтановить приоритет процеÑÑа (%Rrc). ПопытайтеÑÑŒ Ñделать Ñто когда Ð’Ðœ не запущена.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6683"/>
+ <source>Already paused</source>
+ <translation>Уже приоÑтановлена</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6711"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6892"/>
+ <source>Could not suspend the machine execution (%Rrc)</source>
+ <translation>Ðевозможно приоÑтановить выполение машины (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6791"/>
+ <source>VM is paused due to host power management</source>
+ <translation>Ð’Ðœ приоÑтановлена из-за ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¸Ñ‚Ð°Ð½Ð¸ÐµÐ¼ хоÑта</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6846"/>
+ <source>Cannot save the execution state as the machine is not running or paused (machine state: %s)</source>
+ <translation>Ðевозможно Ñохранить ÑоÑтоÑние Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ñ‚Ð°Ðº как машина не запущена или приоÑтановлена (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6855"/>
+ <source>Saving the execution state is disabled for this VM</source>
+ <translation>Сохранение ÑоÑтоÑÐ½Ð¸Ñ Ð¼Ð°ÑˆÐ¸Ð½Ñ‹ выключено Ð´Ð»Ñ Ñтой Ð’Ðœ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6868"/>
+ <source>Could not create a directory &apos;%s&apos; to save the state to (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать директорию &apos;%s&apos; Ð´Ð»Ñ ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ ÑоÑтоÑÐ½Ð¸Ñ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6918"/>
+ <source>Failed to save the machine state to &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ Ñохранить ÑоÑтоÑние машиы в &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7482"/>
+ <source>The virtual machine is not powered up</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð° не запущена</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7537"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7546"/>
+ <source>The virtual machine is powered off</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð° выключена</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7632"/>
+ <source>Failed to open release log (%s, %Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть релизный журнал (%s, %Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7676"/>
+ <source>The virtual machine is already running or busy (machine state: %s)</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð° уже запущена или занÑта (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7711"/>
+ <source>Restoring virtual machine</source>
+ <translation>ВоÑÑтановление ÑоÑтоÑÐ½Ð¸Ñ Ð²Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð¾Ð¹ машины</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7713"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7959"/>
+ <source>Teleporting virtual machine</source>
+ <translation>Портирование виртуальной машины</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7715"/>
+ <source>Starting virtual machine</source>
+ <translation>ЗапуÑк виртуальной машины</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7735"/>
+ <source>VM failed to start because the saved state file &apos;%ls&apos; does not exist.</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить Ð’Ðœ, потому что файл Ñохраненного ÑоÑтоÑÐ½Ð¸Ñ &apos;%ls&apos; не ÑущеÑтвует.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7739"/>
+ <source>VM failed to start because the saved state file &apos;%ls&apos; is invalid (%Rrc). Delete the saved state prior to starting the VM.</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить Ð’Ðœ, потому что некорректен файл Ñохраненного ÑоÑтоÑÐ½Ð¸Ñ &apos;%ls&apos; (%Rrc). Удалите Ñохраненное ÑоÑтоÑние перед запуÑком Ð’Ðœ.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7940"/>
+ <source>Starting Hard Disk operations</source>
+ <translation>ЗапуÑк операций по жеÑткому диÑку</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7986"/>
+ <source>Powerup was canceled</source>
+ <translation>ЗапуÑк отменен</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8022"/>
+ <source>VM cannot start because host interface networking requires a host interface name to be set</source>
+ <translation>Ð’Ðœ не может запуÑтитьÑÑ, потому что ÑÐµÑ‚ÐµÐ²Ð°Ñ Ð¿Ð¾Ð´ÑиÑтема хоÑта требует Ð·Ð°Ð´Ð°Ð½Ð¸Ñ Ð¸Ð¼ÐµÐ½Ð¸ интерфейÑу хоÑта</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8033"/>
+ <source>VM cannot start because the host interface &apos;%ls&apos; does not exist</source>
+ <translation>Ð’Ðœ не может запуÑтитьÑÑ, потому что Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ñ…Ð¾Ñта &apos;%ls&apos; не ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8371"/>
+ <source>Could not destroy the machine. (Error: %Rrc)</source>
+ <translation>Ðевозможно удалить машину (Ошибка: %Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8387"/>
+ <source>Could not power off the machine. (Error: %Rrc)</source>
+ <translation>Ðевозможно выключить машину (Ошибка: %Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8489"/>
+ <source>Could not find a shared folder named &apos;%s&apos;.</source>
+ <translation>Ðевозможно найти общую папку Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8704"/>
+ <source>Invalid shared folder path: &apos;%s&apos; (%Rrc)</source>
+ <translation>ÐедопуÑтимый путь к общей папке: &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8710"/>
+ <source>Shared folder path &apos;%s&apos; is not absolute</source>
+ <translation>Путь к общей папке &apos;%s&apos; не абÑолютный</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/ConsoleImpl.cpp" line="8719"/>
+ <source>Shared folder name is too long: %zu bytes</source>
+ <translation>
+ <numerusform>Ð˜Ð¼Ñ Ð¾Ð±Ñ‰ÐµÐ¹ папки Ñлишком длинное: %zu байт</numerusform>
+ <numerusform>Ð˜Ð¼Ñ Ð¾Ð±Ñ‰ÐµÐ¹ папки Ñлишком длинное: %zu байта</numerusform>
+ <numerusform>Ð˜Ð¼Ñ Ð¾Ð±Ñ‰ÐµÐ¹ папки Ñлишком длинное: %zu байт</numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/ConsoleImpl.cpp" line="8722"/>
+ <source>Shared folder mount point too long: %zu bytes</source>
+ <translation>
+ <numerusform>Точка Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¾Ð±Ñ‰ÐµÐ¹ папки Ñлишком длиннаÑ: %zu байт</numerusform>
+ <numerusform>Точка Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¾Ð±Ñ‰ÐµÐ¹ папки Ñлишком длиннаÑ: %zu байта</numerusform>
+ <numerusform>Точка Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¾Ð±Ñ‰ÐµÐ¹ папки Ñлишком длиннаÑ: %zu байт</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8747"/>
+ <source>Could not create a shared folder &apos;%s&apos; mapped to &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать общую папку &apos;%s&apos; отображенную на &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8752"/>
+ <source>Shared folder path &apos;%s&apos; does not exist on the host</source>
+ <translation>Путь к общей папке &apos;%s&apos; не ÑущеÑтвует у хоÑта</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8790"/>
+ <source>The name is too long</source>
+ <translation>Ð˜Ð¼Ñ Ñлишком длинное</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8807"/>
+ <source>Could not remove the shared folder &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно удалить общую папку &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9376"/>
+ <source>Failed to attach the USB device. (No available ports on the USB controller).</source>
+ <translation>Ðе удалоÑÑŒ подключить USB уÑтройÑтво (Ðет доÑтупных портов у USB контроллера).</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9379"/>
+ <source>Not permitted to open the USB device, check usbfs options</source>
+ <translation>Ðе разрешено открывать USB уÑтройÑтво, проверьте параметры usbfs</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9382"/>
+ <source>Failed to create a proxy device for the USB device. (Error: %Rrc)</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать прокÑи уÑтройÑтво к USB уÑтройÑтву (Ошибка: %Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9568"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9638"/>
+ <source>No TAP device name was supplied for the host networking interface</source>
+ <translation>Ðе предоÑтавлено ни одного имени TAP уÑтройÑтва Ð´Ð»Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñа Ñетевой подÑиÑтемы хоÑта</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9581"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9662"/>
+ <source>Failed to open the host network interface %ls</source>
+ <translation>Ðе удалоÑÑŒ открыть Ñетевой Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ñ…Ð¾Ñта %ls</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9606"/>
+ <source>could not set up the host networking device for non blocking access: %s</source>
+ <translation>Ðевозможно уÑтановить Ñетевое уÑтройÑтво хоÑта в неблокирующий режим: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9621"/>
+ <source>Could not set up the host networking device: %Rrc</source>
+ <translation>Ðевозможно Ñконфигурировать Ñетевое уÑтройÑтво хоÑта: %Rc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9673"/>
+ <source>General failure attaching to host interface</source>
+ <translation>ÐžÐ±Ñ‰Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ðº интерфейÑу хоÑта</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="10173"/>
+ <source>Disk Image Reset Operation - Immutable Image</source>
+ <translation>ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ ÑброÑа образа диÑка - ÐеизменÑемый образ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="10485"/>
+ <source>Failed to start VM execution (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить Ð’Ðœ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl2.cpp" line="277"/>
+ <source>%s failed: rc=%Rrc, pcszName=%s</source>
+ <translation>%s завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: rc=%Rrc, pcszName=%s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="240"/>
+ <source>Failed reading ACK(%s): %Rrc</source>
+ <translation>Ошибка во Ð²Ñ€ÐµÐ¼Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ ACK(%s): %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="283"/>
+ <source>%s: Expected ACK or NACK, got &apos;%s&apos;</source>
+ <translation>%s: ОжидаетÑÑ ACK или NACK, получено &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="302"/>
+ <source>Failed writing command &apos;%s&apos;: %Rrc</source>
+ <translation>Ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¿Ð¸Ñи команды &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="649"/>
+ <source>canceled</source>
+ <translation>отменено</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="657"/>
+ <source>Failed to connect to port %u on &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ подключитьÑÑ Ðº порту %u &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="667"/>
+ <source>Failed to read welcome message: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ прочеÑÑ‚ÑŒ Ñообщение приветÑтвиÑ: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="669"/>
+ <source>Unexpected welcome %.*Rhxs</source>
+ <translation>Ðеожиданное приветÑтвие %.*Rhxs</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="675"/>
+ <source>Failed to send password: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ отправить пароль: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="678"/>
+ <source>Invalid password</source>
+ <translation>Ðеправильный пароль</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="953"/>
+ <source>The specified password resembles a hashed password, expected plain text</source>
+ <translation>Указанный пароль напоминает преобразованный в хеш, ожидаетÑÑ Ð¿Ñ€Ð¾Ñтой текÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="970"/>
+ <source>Invalid machine state: %s (must be Running or Paused)</source>
+ <translation>ÐедопуÑтимое ÑоÑтоÑние машины: %s (должен быть Работает или ПриоÑтановлено)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="985"/>
+ <source>Teleporter</source>
+ <translation>Портирование</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1100"/>
+ <source>RTTcpServerCreateEx failed with status %Rrc</source>
+ <translation>RTTcpServerCreateEx завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ и ÑтатуÑом %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1123"/>
+ <source>Waiting for incoming VM</source>
+ <translation>Ожидание входÑщей Ð’Ðœ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1148"/>
+ <source>Teleporation failed (%Rrc)</source>
+ <translation>Портирование не удалоÑÑŒ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1156"/>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1173"/>
+ <source>Teleporting canceled</source>
+ <translation>Портирование отменено</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1158"/>
+ <source>Teleporter timed out waiting for incoming connection</source>
+ <translation>Превышено Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ Ð²Ñ…Ð¾Ð´Ñщего Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð²Ð¾ Ð²Ñ€ÐµÐ¼Ñ Ð¿Ð¾Ñ€Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1163"/>
+ <source>Unexpected RTTcpServerListen status code %Rrc</source>
+ <translation>Ðеожиданный код ÑтатуÑа RTTcpServerListen %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1312"/>
+ <source>Teleporting VM from %RTnaddr</source>
+ <translation>Портирование ВМ из %RTnaddr</translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1317"/>
+ <source>Teleporting VM</source>
+ <translation>Портирование ВМ</translation>
+ </message>
+</context>
+<context>
+ <name>ConsoleVRDPServer</name>
+ <message>
+ <location filename="../src-client/ConsoleVRDPServer.cpp" line="3202"/>
+ <source>Could not load the external authentication library &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно загрузить внешнюю библиотку аутентификации &apos;%s&apos; (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>DHCPConfig</name>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="258"/>
+ <source>Duplicate option value: %d</source>
+ <translation>Дублированное значение параметра: %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="261"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="330"/>
+ <source>Invalid option value: %d</source>
+ <translation>ÐедопуÑтимое значение параметра: %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="416"/>
+ <source>Unsupported encoding %d (option %d, value %s)</source>
+ <translation>ÐÐµÐ¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²ÐºÐ°: %d (параметр %d, значение %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="419"/>
+ <source>Unsupported option %d (encoding %d, value %s)</source>
+ <translation>Ðеподдерживаемый параметр %d (кодировка %d, значение %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="421"/>
+ <source>Malformed option %d value &apos;%s&apos; (encoding %d, rc=%Rrc)</source>
+ <translation>Поврежденный параметр %d значение &apos;%s&apos; (кодировка %d, rc=%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="434"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="459"/>
+ <source>DHCP option %u was not found</source>
+ <translation>Параметр DHCP %u не найден</translation>
+ </message>
+</context>
+<context>
+ <name>DHCPGlobalConfig</name>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="618"/>
+ <source>DHCP option DHCPOption_SubnetMask is not in a legacy encoding</source>
+ <translation>Параметр DHCP DHCPOption_SubnetMask в кодировке, не ÑвлÑющейÑÑ ÑƒÑтаревшей</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="620"/>
+ <source>DHCP option DHCPOption_SubnetMask was not found</source>
+ <translation>Параметр DHCP DHCPOption_SubnetMask не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="637"/>
+ <source>Invalid IPv4 netmask &apos;%s&apos;: %Rrc</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ ÑÐµÑ‚ÐµÐ²Ð°Ñ Ð¼Ð°Ñка IPv4 &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="650"/>
+ <source>DHCPOption_SubnetMask must use DHCPOptionEncoding_Normal as it is reflected by IDHCPServer::networkMask</source>
+ <translation>DHCPOption_SubnetMask должен иÑпользовать DHCPOptionEncoding_Normal как Ñто отражено в IDHCPServer::networkMask</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="661"/>
+ <source>DHCPOption_SubnetMask cannot be removed as it reflects IDHCPServer::networkMask</source>
+ <translation>DHCPOption_SubnetMask не может быть удален как он отражает IDHCPServer::networkMask</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="691"/>
+ <source>Cannot delete the global config</source>
+ <translation>Ðевозможно удалить глобальную конфигурацию</translation>
+ </message>
+</context>
+<context>
+ <name>DHCPGroupCondition</name>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="764"/>
+ <source>Not a valid MAC address: %s</source>
+ <translation>ÐедопуÑтимый MAC адреÑ: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="793"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="816"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="838"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="866"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="873"/>
+ <source>Trailing chars in MAC wildcard address: %s (offset %zu)</source>
+ <translation>Лишние Ñимволы в конце ÐœÐС адреÑа Ñ Ð¿Ð¾Ð´Ñтановочными знаками: %s (Ñмещение %zu)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="801"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="822"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="845"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="877"/>
+ <source>Malformed MAC wildcard address: %s (offset %zu)</source>
+ <translation>Поврежденный MAC Ð°Ð´Ñ€ÐµÑ Ñ Ð¿Ð¾Ð´Ñтановочными знаками: %s (Ñмещение %zu)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="888"/>
+ <source>Value cannot be empty</source>
+ <translation>Значение не может быть пуÑтое</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="890"/>
+ <source>Value is too long: %zu bytes</source>
+ <translation>
+ <numerusform>Значение Ñлишком длинное: %zu байт</numerusform>
+ <numerusform>Значение Ñлишком длинное: %zu байта</numerusform>
+ <numerusform>Значение Ñлишком длинное: %zu байт</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="895"/>
+ <source>Invalid condition type: %d</source>
+ <translation>ÐедопуÑтимый тип уÑловиÑ: %d</translation>
+ </message>
+</context>
+<context>
+ <name>DHCPIndividualConfig</name>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="1399"/>
+ <source>Invalid IPv4 address &apos;%s&apos;: %Rrc</source>
+ <translation>ÐедопуÑтимый Ð°Ð´Ñ€ÐµÑ IPv4 &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="1448"/>
+ <source>INetworkAdapter returned bogus MAC address &apos;%ls&apos;: %Rrc</source>
+ <translation>INetworkAdapter возвратил поддельный MAC Ð°Ð´Ñ€ÐµÑ &apos;%ls&apos;: %Rrc</translation>
+ </message>
+</context>
+<context>
+ <name>DHCPServer</name>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="506"/>
+ <source>Invalid server address: %s</source>
+ <translation>ÐедопуÑтимый Ð°Ð´Ñ€ÐµÑ Ñервера: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="510"/>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="526"/>
+ <source>Invalid netmask: %s</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ ÑÐµÑ‚ÐµÐ²Ð°Ñ Ð¼Ð°Ñка: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="514"/>
+ <source>Invalid range lower address: %s</source>
+ <translation>ÐедопуÑтимый нижний Ð°Ð´Ñ€ÐµÑ Ð´Ð¸Ð°Ð¿Ð°Ð·Ð¾Ð½Ð°: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="518"/>
+ <source>Invalid range upper address: %s</source>
+ <translation>ÐедопуÑтимый верхний Ð°Ð´Ñ€ÐµÑ Ð´Ð¸Ð°Ð¿Ð°Ð·Ð¾Ð½Ð°: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="540"/>
+ <source>Invalid server address: %s (mask %s)</source>
+ <translation>ÐедопуÑтимый Ð°Ð´Ñ€ÐµÑ Ñервера: %s (маÑка %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="546"/>
+ <source>Invalid range lower address: %s (mask %s)</source>
+ <translation>ÐедопуÑтимый нижний Ð°Ð´Ñ€ÐµÑ Ð´Ð¸Ð°Ð¿Ð°Ð·Ð¾Ð½Ð°: %s (маÑка %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="552"/>
+ <source>Invalid range upper address</source>
+ <translation>ÐедопуÑтимый верхний Ð°Ð´Ñ€ÐµÑ Ð´Ð¸Ð°Ð¿Ð°Ð·Ð¾Ð½Ð°</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="556"/>
+ <source>Lower bound must be less or eqaul than the upper: %s vs %s</source>
+ <translation>ÐижнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° должна быть меньше или равна верхней: %s против %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="603"/>
+ <source>NIC slot number (%d) is out of range (0..32)</source>
+ <translation>Ðомер Ñлота NIC (%d) вне диапазона (0..32)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="766"/>
+ <source>not running</source>
+ <translation>не запущен</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="896"/>
+ <source>Cannot start DHCP server because it is already running (pid %RTproc)</source>
+ <translation>Ðевозможно запуÑтить DHCP Ñервер, потому что он уже работает (pid %RTproc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="929"/>
+ <source>Failed to start DHCP server for &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить DHCP Ñервер Ð´Ð»Ñ &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="932"/>
+ <source>Failed to assemble the command line for DHCP server &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ Ñобрать командную Ñтроку Ð´Ð»Ñ DHCP Ñервера &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="966"/>
+ <source>Invalid MAC address &apos;%s&apos;: %Rrc</source>
+ <translation>ÐедопуÑтимый MAC Ð°Ð´Ñ€ÐµÑ &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="968"/>
+ <source>flags must be zero (not %#x)</source>
+ <translation>флаги должны быть равны нулю (не %#x)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1019"/>
+ <source>Reading &apos;%s&apos; failed: %Rrc - %s</source>
+ <translation>Ðе удалоÑÑŒ прочитать &apos;%s&apos;: %Rrc - %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1025"/>
+ <source>Reading &apos;%s&apos; failed: %s</source>
+ <translation>Ðе удалоÑÑŒ прочитать &apos;%s&apos;: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1026"/>
+ <source>Reading &apos;%s&apos; failed: RTCError</source>
+ <translation>Ðе удалоÑÑŒ прочитать &apos;%s&apos;: RTCError</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1035"/>
+ <source>Reading &apos;%s&apos; failed: Unexpected exception</source>
+ <translation>Ðе удалоÑÑŒ прочитать &apos;%s&apos;: Ðеожиданное иÑключение</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1091"/>
+ <source>Could not find a lease for %RTmac</source>
+ <translation>Ðевозможно найти аренду Ð´Ð»Ñ %RTmac</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1099"/>
+ <source>The &apos;slot&apos; argument must be zero for all but the MachineNIC scope!</source>
+ <translation>Ðргумент &apos;slot&apos; должен быть равен нулю Ð´Ð»Ñ Ð²Ñех кроме облаÑти MachineNIC!</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1105"/>
+ <source>The name must be empty or NULL for the Global scope!</source>
+ <translation>Ð˜Ð¼Ñ Ð´Ð¾Ð»Ð¶Ð½Ð¾ быть пуÑтое или нулевое Ð´Ð»Ñ Ð¾Ð±Ð»Ð°Ñти Global (ГлобальнаÑ)!</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1113"/>
+ <source>A group must have a name!</source>
+ <translation>У группы должно быть имÑ!</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1115"/>
+ <source>Name too long! %zu bytes</source>
+ <translation>
+ <numerusform>Ð˜Ð¼Ñ Ñлишком длинное! %zu байт</numerusform>
+ <numerusform>Ð˜Ð¼Ñ Ñлишком длинное! %zu байта</numerusform>
+ <numerusform>Ð˜Ð¼Ñ Ñлишком длинное! %zu байт</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1126"/>
+ <source>Found no configuration for group %s</source>
+ <translation>Ðе найдено кофигурации Ð´Ð»Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹ %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1210"/>
+ <source>Found no configuration for MAC address %s</source>
+ <translation>Ðе найдено конфигурации Ð´Ð»Ñ MAC адреÑа %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1214"/>
+ <source>Invalid MAC address: %s</source>
+ <translation>ÐедопуÑтимый MAC адреÑ: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1264"/>
+ <source>Failed to construct leases, config and log filenames: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ Ñделать имена файлов Ð´Ð»Ñ Ð°Ñ€ÐµÐ½Ð´Ñ‹, конфигурации и журнала: %Rrc</translation>
+ </message>
+</context>
+<context>
+ <name>DataStream</name>
+ <message>
+ <location filename="../src-server/DataStreamImpl.cpp" line="87"/>
+ <source>Failed to initialize data stream object (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ инициализировать объект потока данных (%Rrc)</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/DataStreamImpl.cpp" line="213"/>
+ <source>Error reading %u bytes: %Rrc</source>
+ <translation>
+ <numerusform>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ %u байт: %Rrc</numerusform>
+ <numerusform>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ %u байта: %Rrc</numerusform>
+ <numerusform>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ %u байт: %Rrc</numerusform>
+ </translation>
+ </message>
+</context>
+<context>
+ <name>Display</name>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="1542"/>
+ <source>AttachFramebuffer: Invalid screen %d (total %d)</source>
+ <translation>AttachFramebuffer: недопуÑтимый Ñкран %d (вÑего %d)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="1547"/>
+ <source>AttachFramebuffer: Framebuffer already attached to %d</source>
+ <translation>AttachFramebuffer: Фреймбуфер уже подключен к %d</translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="1593"/>
+ <source>DetachFramebuffer: Invalid screen %d (total %d)</source>
+ <translation>DetachFramebuffer: недопуÑтимый Ñкран %d (вÑего %d)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="1601"/>
+ <source>DetachFramebuffer: Invalid framebuffer object</source>
+ <translation>DetachFramebuffer: недопуÑтимый объект фреймбуфера</translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="1618"/>
+ <source>QueryFramebuffer: Invalid screen %d (total %d)</source>
+ <translation>QueryFramebuffer: ÐедопуÑтимый Ñкран %d (вÑего %d)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="1963"/>
+ <source>Unsupported screenshot format 0x%08X</source>
+ <translation>Ðеподдерживаемый формат Ñнимка Ñкрана 0x%08X</translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2024"/>
+ <source>PNG is larger than 32bpp bitmap</source>
+ <translation>PNG больше чем 32bpp bitmap</translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2028"/>
+ <source>Could not convert screenshot to PNG (%Rrc)</source>
+ <translation>Ðевозможно преобразовать Ñнимок Ñкрана в PNG (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2033"/>
+ <source>Screenshot is not available at this time</source>
+ <translation>Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ñнимок Ñкрана недоÑтупен</translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2035"/>
+ <source>Could not take a screenshot (%Rrc)</source>
+ <translation>Ðевозможно Ñделать Ñнимок Ñкрана (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2301"/>
+ <source>Could not draw to the screen (%Rrc)</source>
+ <translation>Ðевозможно риÑовать на Ñкране (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2435"/>
+ <source>Could not invalidate and update the screen (%Rrc)</source>
+ <translation>Ðевозможно аннулировать и обновить Ñодержимое Ñкрана (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2454"/>
+ <source>Could not invalidate and update the screen %d (%Rrc)</source>
+ <translation>Ðевозможно аннулировать и обновить Ñодержимое Ñкрана %d (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2499"/>
+ <source>QuerySourceBitmap: Invalid screen %d (total %d)</source>
+ <translation>QuerySourceBitmap: ÐедопуÑтимый Ñкран %d (вÑего %d)</translation>
+ </message>
+</context>
+<context>
+ <name>EmulatedUSB</name>
+ <message>
+ <location filename="../src-client/EmulatedUSBImpl.cpp" line="215"/>
+ <source>Init emulated USB webcam (%Rrc)</source>
+ <translation>Ð˜Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ñмулированной USB вебкамеры (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/EmulatedUSBImpl.cpp" line="314"/>
+ <source>Attach emulated USB webcam (%Rrc)</source>
+ <translation>Подключение Ñмулированной USB вебкамеры (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/EmulatedUSBImpl.cpp" line="332"/>
+ <source>Detach emulated USB webcam (%Rrc)</source>
+ <translation>Отключение Ñмулированной USB вебкамеры (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>EventSource</name>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1065"/>
+ <location filename="../src-all/EventImpl.cpp" line="1122"/>
+ <location filename="../src-all/EventImpl.cpp" line="1193"/>
+ <location filename="../src-all/EventImpl.cpp" line="1221"/>
+ <source>This event source is already shut down</source>
+ <translation>ИÑточник Ñобытий уже оÑтанавливаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1070"/>
+ <source>This listener already registered</source>
+ <translation>ПроÑлушиватель уже зарегиÑтрирован</translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1099"/>
+ <location filename="../src-all/EventImpl.cpp" line="1202"/>
+ <location filename="../src-all/EventImpl.cpp" line="1254"/>
+ <source>Listener was never registered</source>
+ <translation>ПроÑлушиватель никогда не был зарегиÑтрирован</translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1205"/>
+ <source>Listener must be passive</source>
+ <translation>ПроÑлушиватель должен быть паÑÑивного типа</translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1231"/>
+ <source>Only applicable to passive listeners</source>
+ <translation>Применимо только Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñлушивателей паÑÑивного типа</translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1241"/>
+ <source>Unknown event</source>
+ <translation>ÐеизвеÑтное Ñобытие</translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1445"/>
+ <source>Could not create wrapper object (%Rhrc)</source>
+ <translation>Ðевозможно Ñоздать объект обертки (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1457"/>
+ <source>Could not create aggregator (%Rhrc)</source>
+ <translation>Ðевозможно Ñоздать агрегатор (%Rhrc)</translation>
+ </message>
+</context>
+<context>
+ <name>EventSourceAggregator</name>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1476"/>
+ <source>Could not create source (%Rhrc)</source>
+ <translation>Ðевозможно Ñоздать иÑточник (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1479"/>
+ <source>Could not init source (%Rhrc)</source>
+ <translation>Ðевозможно инициализировать иÑточник (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1615"/>
+ <source>Could not create proxy (%Rhrc)</source>
+ <translation>Ðевозможно Ñоздать прокÑи (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1625"/>
+ <source>This listener already registered</source>
+ <translation>ПроÑлушиватель уже зарегиÑтрирован</translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1639"/>
+ <location filename="../src-all/EventImpl.cpp" line="1650"/>
+ <source>This listener never registered</source>
+ <translation>ПроÑлушиватель никогда не был зарегиÑтрирован</translation>
+ </message>
+</context>
+<context>
+ <name>ExtPack</name>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="787"/>
+ <source>ExtPack::init failed</source>
+ <translation>ExtPack::init завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="934"/>
+ <source>pfnUninstall returned %Rrc</source>
+ <translation>pfnUninstall возвратил %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1145"/>
+ <source>The extension pack &apos;%s&apos; does not include a VRDE module</source>
+ <translation>Пакет раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos; не включает модуль VRDE</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1170"/>
+ <source>Failed to locate the VRDE module &apos;%s&apos; in extension pack &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ обнаружить VRDE модуль &apos;%s&apos; в пакете раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1193"/>
+ <source>Failed to locate the module &apos;%s&apos; in extension pack &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ обнаружить модуль &apos;%s&apos; в пакете раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1351"/>
+ <source>RTPathQueryInfoEx on &apos;%s&apos; failed: %Rrc</source>
+ <translation>RTPathQueryInfoEx завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ на &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1357"/>
+ <source>&apos;%s&apos; is a symbolic link, this is not allowed</source>
+ <translation>&apos;%s&apos; - Ñто ÑимволичеÑÐºÐ°Ñ ÑÑылка, такое не допуÑтимо</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1360"/>
+ <source>&apos;%s&apos; is a symbolic file, not a directory</source>
+ <translation>&apos;%s&apos; - Ñто ÑимволичеÑÐºÐ°Ñ ÑÑылка, не директориÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1363"/>
+ <source>&apos;%s&apos; is not a directory (fMode=%#x)</source>
+ <translation>&apos;%s&apos; не Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ (fMode=%#x)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1384"/>
+ <source>Failed to load &apos;%s/%s&apos;: %s</source>
+ <translation>Ðе удалоÑÑŒ загрузить &apos;%s/%s&apos;: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1396"/>
+ <source>The description name (&apos;%s&apos;) and directory name (&apos;%s&apos;) does not match</source>
+ <translation>Ð˜Ð¼Ñ Ð¾Ð¿Ð¸ÑÐ°Ð½Ð¸Ñ (&apos;%s&apos;) и Ð¸Ð¼Ñ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ð¸ (&apos;%s&apos;) не ÑоответÑтвуют друг другу</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1423"/>
+ <source>Failed to locate the main module (&apos;%s&apos;)</source>
+ <translation>Ðе удалоÑÑŒ обнаружить главный модуль (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1440"/>
+ <source>Failed to load the main module (&apos;%s&apos;): %Rrc - %s</source>
+ <translation>Ðе удалоÑÑŒ обнаружить главный модуль (&apos;%s&apos;): %Rrc - %s</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1447"/>
+ <source>Only native main modules are currently supported</source>
+ <translation>Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÑŽÑ‚ÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ ÑобÑтвенные оÑновные модули</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1509"/>
+ <source>The registration structure contains one or more invalid function pointers</source>
+ <translation>Структура региÑтрации Ñодержит один или неÑколько недопуÑтимых указателей на функции</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1512"/>
+ <source>Unsupported registration structure version %u.%u</source>
+ <translation>ÐÐµÐ¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ñтруктуры региÑтрации %u.%u</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1516"/>
+ <source>%s returned %Rrc, pReg=%p ErrInfo=&apos;%s&apos;</source>
+ <translation>%s возвратил %Rrc, pReg=%p ErrInfo=&apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1521"/>
+ <source>Failed to resolve exported symbol &apos;%s&apos; in the main module: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ разрешить ÑкÑпортируемый Ñимвол &apos;%s&apos; в главном модуле: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2138"/>
+ <source>The preferred locale is a two character string or empty.</source>
+ <translation>ÐŸÑ€ÐµÐ´Ð¿Ð¾Ñ‡Ñ‚Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒ - Ñто Ñтрока из 2 Ñимволов или пуÑтаÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2141"/>
+ <source>The preferred language is a two character string or empty.</source>
+ <translation>Предпочтительный Ñзык - Ñто Ñтрока из 2 Ñимволов или пуÑтаÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2146"/>
+ <source>The license format can only have the values &apos;html&apos;, &apos;rtf&apos; and &apos;txt&apos;.</source>
+ <translation>Формат лицензии может иметь Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ &apos;html&apos;, &apos;rtf&apos; и &apos;txt&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2190"/>
+ <source>The license file &apos;%s&apos; is empty or contains invalid UTF-8 encoding</source>
+ <translation>Файл лицензии &apos;%s&apos; пуÑÑ‚ или Ñодержит некорректную кодировку UTF-8</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2195"/>
+ <source>The license file &apos;%s&apos; was not found in extension pack &apos;%s&apos;</source>
+ <translation>Файл лицензии &apos;%s&apos; не найден в пакете раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2198"/>
+ <source>Failed to open the license file &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ открыть файл лицензии &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2201"/>
+ <source>RTPathJoin failed: %Rrc</source>
+ <translation>RTPathJoin завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+</context>
+<context>
+ <name>ExtPackFile</name>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="329"/>
+ <source>ExtPack::init failed</source>
+ <translation>ExtPack::init завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="355"/>
+ <source>&apos;%s&apos; file not found</source>
+ <translation>&apos;%s&apos; файл не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="356"/>
+ <source>RTFileOpen(&apos;%s&apos;,,) failed with %Rrc</source>
+ <translation>RTFileOpen(&apos;%s&apos;,,) завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="362"/>
+ <source>RTFileQueryInfo failed with %Rrc on &apos;%s&apos;</source>
+ <translation>RTFileQueryInfo завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc на &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="364"/>
+ <source>Not a regular file: %s</source>
+ <translation>Ðе обычный файл: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="384"/>
+ <source>Failed to the xml file: %s</source>
+ <translation>Загрузка xml файла завершена Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="396"/>
+ <source>Extension pack name mismatch between the downloaded file and the XML inside it (xml=&apos;%s&apos; file=&apos;%s&apos;)</source>
+ <translation>Ð˜Ð¼Ñ Ð¿Ð°ÐºÐµÑ‚Ð° раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð½Ð¾Ðµ внутри XML не ÑоответÑтвует имени загруженного файла (xml=&apos;%s&apos; file=&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="532"/>
+ <source>The preferred locale is a two character string or empty.</source>
+ <translation>ÐŸÑ€ÐµÐ´Ð¿Ð¾Ñ‡Ñ‚Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒ - Ñто Ñтрока из 2 Ñимволов или пуÑтаÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="535"/>
+ <source>The preferred language is a two character string or empty.</source>
+ <translation>Предпочтительный Ñзык - Ñто Ñтрока из 2 Ñимволов или пуÑтаÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="540"/>
+ <source>The license format can only have the values &apos;html&apos;, &apos;rtf&apos; and &apos;txt&apos;.</source>
+ <translation>Формат лицензии может иметь Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ &apos;html&apos;, &apos;rtf&apos; и &apos;txt&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="592"/>
+ <source>RTVfsFsStrmNext failed: %Rrc</source>
+ <translation>RTVfsFsStrmNext завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="594"/>
+ <source>&apos;%s&apos; was found in the manifest but not in the tarball</source>
+ <translation>&apos;%s&apos; найден в манифеÑте но не в архиве</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="629"/>
+ <source>The license file &apos;%s&apos; is empty or contains invalid UTF-8 encoding</source>
+ <translation>Файл лицензии &apos;%s&apos; пуÑÑ‚ или Ñодержит некорректную кодировку UTF-8</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="633"/>
+ <source>Failed to read &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ прочеÑÑ‚ÑŒ &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="637"/>
+ <source>Failed to allocate %zu bytes for &apos;%s&apos;</source>
+ <translation>
+ <numerusform>Ðе удалоÑÑŒ выделить %zu байт Ð´Ð»Ñ &apos;%s&apos;</numerusform>
+ <numerusform>Ðе удалоÑÑŒ выделить %zu байта Ð´Ð»Ñ &apos;%s&apos;</numerusform>
+ <numerusform>Ðе удалоÑÑŒ выделить %zu байт Ð´Ð»Ñ &apos;%s&apos;</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="641"/>
+ <source>RTVfsIoStrmQueryInfo on &apos;%s&apos;: %Rrc</source>
+ <translation>RTVfsIoStrmQueryInfo на &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="656"/>
+ <source>The license file &apos;%s&apos; was not found in &apos;%s&apos;</source>
+ <translation>Файл лицензии &apos;%s&apos; не найден в &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="688"/>
+ <source>Starting thread for an extension pack installation failed with %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить поток Ð´Ð»Ñ ÑƒÑтановки пакета раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="692"/>
+ <source>Looks like creating a progress object for ExtraPackInstallTask object failed</source>
+ <translation>ВыглÑдит так, как будто не удалоÑÑŒ Ñоздать объект прогреÑÑа Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð° ExtraPackInstallTask</translation>
+ </message>
+</context>
+<context>
+ <name>ExtPackInstallTask</name>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="219"/>
+ <source>Installing extension pack</source>
+ <translation>УÑтановка пакета раÑширениÑ</translation>
+ </message>
+</context>
+<context>
+ <name>ExtPackManager</name>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2467"/>
+ <source>Starting thread for an extension pack uninstallation failed with %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить поток Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¿Ð°ÐºÐµÑ‚Ð° раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2470"/>
+ <source>Looks like creating a progress object for ExtraPackUninstallTask object failed</source>
+ <translation>ВыглÑдит так, как будто не удалоÑÑŒ Ñоздать объект прогреÑÑа Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð° ExtraPackUninstallTask</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2734"/>
+ <source>The installer failed with exit code %d: %s</source>
+ <translation>УÑтановщик завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %d: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2738"/>
+ <source>The installer was killed by signal #d (stderr: %s)</source>
+ <translation>УÑтановщик был принудительно завершен по Ñигналу #d (stderr: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2741"/>
+ <source>The installer aborted abnormally (stderr: %s)</source>
+ <translation>УÑтановщик завершилÑÑ Ð½ÐµÐ½Ð¾Ñ€Ð¼Ð°Ð»ÑŒÐ½Ð¾ (stderr: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2744"/>
+ <source>internal error: enmReason=%d iStatus=%d stderr=&apos;%s&apos;</source>
+ <translation>внутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ°: enmReason=%d iStatus=%d stderr=&apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2750"/>
+ <source>Failed to launch the helper application &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить приложение-помощник &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3026"/>
+ <source>Upgrading extension pack &apos;%s&apos; failed because at least one VM is still running</source>
+ <translation>Ðе удалоÑÑŒ произвеÑти обновление пакета раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos;, потому что как минимум одна Ð’Ðœ вÑе еще работает</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3032"/>
+ <source>Upgrading extension pack &apos;%s&apos; failed because at least one Cloud Provider is still busy</source>
+ <translation>Ðе удалоÑÑŒ произвеÑти обновление пакета раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos;, потому что как минимум один провайдер облака вÑе еще занÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3040"/>
+ <source>Extension pack &apos;%s&apos; is already installed. In case of a reinstallation, please uninstall it first</source>
+ <translation>Пакет раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos; уже уÑтановлен. ЕÑли надо переуÑтановить, пожалуйÑта, Ñначала удалите его</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3085"/>
+ <source>The installation hook failed: %Rrc - %s</source>
+ <translation>Выполнение &quot;ловушки&quot; поÑле инÑталлÑции завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc - %s</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3090"/>
+ <source>Installing extension pack &apos;%s&apos; failed under mysterious circumstances</source>
+ <translation>Ðе удалоÑÑŒ уÑтановить пакет раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos; при загадочных обÑтоÑтельÑтвах</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3191"/>
+ <source>Uninstall extension pack &apos;%s&apos; failed under mysterious circumstances</source>
+ <translation>Ðе удалоÑÑŒ удалить пакет раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos; при загадочных обÑтоÑтельÑтвах</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3209"/>
+ <source>Uninstall extension pack &apos;%s&apos; failed because at least one VM is still running</source>
+ <translation>Ðе удалоÑÑŒ удалить пакет раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos;, потому что как минимум одна Ð’Ðœ вÑе еще работает</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3215"/>
+ <source>Uninstall extension pack &apos;%s&apos; failed because at least one Cloud Provider is still busy</source>
+ <translation>Ðе удалоÑÑŒ удалить пакет раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos;, потому что как минимум один провайдер облака вÑе еще занÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3221"/>
+ <source>Uninstall extension pack &apos;%s&apos; failed for an unknown reason</source>
+ <translation>Ðе удалоÑÑŒ удалить пакет раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos; из-за неизвеÑтной причины</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3465"/>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3494"/>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3522"/>
+ <source>No extension pack by the name &apos;%s&apos; was found</source>
+ <translation>Ðе найдено пакетов раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos;</translation>
+ </message>
+</context>
+<context>
+ <name>ExtPackUninstallTask</name>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="269"/>
+ <source>Uninstalling extension pack</source>
+ <translation>Удаление пакета раÑширениÑ</translation>
+ </message>
+</context>
+<context>
+ <name>ExtPackUtil</name>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="126"/>
+ <source>No VirtualBoxExtensionPack element</source>
+ <translation>Ðет Ñлемента VirtualBoxExtensionPack</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="130"/>
+ <source>Missing format version</source>
+ <translation>ОтÑутÑтвует верÑÐ¸Ñ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚Ð°</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="132"/>
+ <source>Unsupported format version: </source>
+ <translation>ÐÐµÐ¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚Ð°: </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="139"/>
+ <source>The &apos;Name&apos; element is missing</source>
+ <translation>ОтÑутÑтвует Ñлемент &apos;Name&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="142"/>
+ <source>Invalid name: </source>
+ <translation>ÐедопуÑтимое имÑ: </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="146"/>
+ <source>The &apos;Description&apos; element is missing</source>
+ <translation>ОтÑутÑтвует Ñлемент &apos;Description&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="149"/>
+ <source>The &apos;Description&apos; element is empty</source>
+ <translation>Элемент &apos;Description&apos; пуÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="151"/>
+ <source>The &apos;Description&apos; must not contain control characters</source>
+ <translation>&apos;Description&apos; не должен Ñодержать управлÑющие Ñимволы</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="155"/>
+ <source>The &apos;Version&apos; element is missing</source>
+ <translation>ОтÑутÑтвует Ñлемент &apos;Version&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="158"/>
+ <source>The &apos;Version&apos; element is empty</source>
+ <translation>Элемент &apos;Version&apos; пуÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="160"/>
+ <source>Invalid version string: </source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ñтрока верÑии: </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="170"/>
+ <source>Invalid edition string: </source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ñтрока редакции: </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="174"/>
+ <source>The &apos;MainModule&apos; element is missing</source>
+ <translation>ОтÑутÑтвует Ñлемент &apos;MainModule&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="177"/>
+ <source>The &apos;MainModule&apos; element is empty</source>
+ <translation>Элемент &apos;MainModule&apos; пуÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="179"/>
+ <source>Invalid main module string: </source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ñтрока главного модулÑ: </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="193"/>
+ <source>Invalid main VM module string: </source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ñтрока главного Ð¼Ð¾Ð´ÑƒÐ»Ñ Ð’Ðœ: </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="208"/>
+ <source>Invalid VRDE module string: </source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ñтрока VRDE модулÑ: </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="267"/>
+ <source>RTPathJoin failed with %Rrc</source>
+ <translation>RTPathJoin завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="272"/>
+ <source>RTPathQueryInfoEx failed with %Rrc</source>
+ <translation>RTPathQueryInfoEx завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="278"/>
+ <source>The XML file is symlinked, that is not allowed</source>
+ <translation>XML файл указан через ÑимволичеÑкую ÑÑылку, что не допуÑкаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="279"/>
+ <source>The XML file is not a file (fMode=%#x)</source>
+ <translation>XML файл не ÑвлÑетÑÑ Ñ„Ð°Ð¹Ð»Ð¾Ð¼ (fMode=%#x)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="328"/>
+ <source>RTVfsFileQueryInfo failed: %Rrc</source>
+ <translation>RTVfsFileQueryInfo завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/ExtPackUtil.cpp" line="339"/>
+ <source>The XML file is too large (%&apos;RU64 bytes)</source>
+ <translation>
+ <numerusform>XML файл Ñлишком большой (%&apos;RU64 байт)</numerusform>
+ <numerusform>XML файл Ñлишком большой (%&apos;RU64 байта)</numerusform>
+ <numerusform>XML файл Ñлишком большой (%&apos;RU64 байт)</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="346"/>
+ <source>RTVfsFileSeek(,0,BEGIN) failed: %Rrc</source>
+ <translation>RTVfsFileSeek(,0,BEGIN) завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="351"/>
+ <source>RTMemTmpAlloc(%zu) failed</source>
+ <translation>RTMemTmpAlloc(%zu) завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="356"/>
+ <source>RTVfsFileRead failed: %Rrc</source>
+ <translation>RTVfsFileRead завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="757"/>
+ <source>The name of the downloaded file and the name stored inside the extension pack does not match (xml=&apos;%s&apos; file=&apos;%s&apos;)</source>
+ <translation>Ð˜Ð¼Ñ Ð·Ð°Ð³Ñ€ÑƒÐ¶ÐµÐ½Ð½Ð¾Ð³Ð¾ файла не ÑоответÑтвует имени, Ñохраненному внутри пакета раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ (xml=&apos;%s&apos; file=&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="780"/>
+ <source>RTVfsFileSeek failed: %Rrc</source>
+ <translation>RTVfsFileSeek завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="785"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1240"/>
+ <source>RTManifestCreate failed: %Rrc</source>
+ <translation>RTManifestCreate завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="817"/>
+ <source>Manifest mismatch: %s</source>
+ <translation>ÐеÑоответÑтвие манифеÑтов: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="819"/>
+ <source>RTManifestEqualsEx failed: %Rrc</source>
+ <translation>RTManifestEqualsEx завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="830"/>
+ <source>Error parsing &apos;%s&apos;: %Rrc</source>
+ <translation>Ошибка интерпретации &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="878"/>
+ <source>The extension pack file has changed (SHA-256 mismatch)</source>
+ <translation>Пакет раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½Ð¸Ð»ÑÑ (неÑоответÑтвие SHA-256)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="883"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="902"/>
+ <source>Bad SHA-256 &apos;%s&apos;: %Rrc</source>
+ <translation>Плохой SHA-256 &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="936"/>
+ <source>There can only be one &apos;%s&apos;</source>
+ <translation>Может быть только один &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="939"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="948"/>
+ <source>Standard member &apos;%s&apos; is not a file</source>
+ <translation>Стандартный член &apos;%s&apos; не ÑвлÑетÑÑ Ñ„Ð°Ð¹Ð»Ð¾Ð¼</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/ExtPackUtil.cpp" line="951"/>
+ <source>Standard member &apos;%s&apos; is too large: %&apos;RU64 bytes (max 1 MB)</source>
+ <translation>
+ <numerusform>Стандартный член &apos;%s&apos; Ñлишком большой: %&apos;RU64 байт (макÑимально 1 MB)</numerusform>
+ <numerusform>Стандартный член &apos;%s&apos; Ñлишком большой: %&apos;RU64 байта (макÑимально 1 MB)</numerusform>
+ <numerusform>Стандартный член &apos;%s&apos; Ñлишком большой: %&apos;RU64 байт (макÑимально 1 MB)</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="981"/>
+ <source>RTVfsFileSeek failed on &apos;%s&apos;: %Rrc</source>
+ <translation>RTVfsFileSeek завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="991"/>
+ <source>RTVfsMemorizeIoStreamAsFile failed on &apos;%s&apos;: %Rrc</source>
+ <translation>RTVfsMemorizeIoStreamAsFile завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ Ð´Ð»Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="996"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1101"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1131"/>
+ <source>RTVfsObjQueryInfo failed on &apos;%s&apos;: %Rrc</source>
+ <translation>RTVfsObjQueryInfo завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ на &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1017"/>
+ <source>&apos;%s&apos;: starts with root spec</source>
+ <translation>&apos;%s&apos;: начинаетÑÑ Ñ ÐºÐ¾Ñ€Ð½Ñ (не отноÑительный)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1067"/>
+ <source>Bad member name &apos;%s&apos; (pos %zu): %s</source>
+ <translation>Ðеправильное Ð¸Ð¼Ñ Ñ‡Ð»ÐµÐ½Ð° &apos;%s&apos; (pos %zu): %s</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/ExtPackUtil.cpp" line="1093"/>
+ <source>&apos;%s&apos;: too large (%&apos;RU64 bytes)</source>
+ <translation>
+ <numerusform>&apos;%s&apos;: Ñлишком большой (%&apos;RU64 байт)</numerusform>
+ <numerusform>&apos;%s&apos;: Ñлишком большой (%&apos;RU64 байта)</numerusform>
+ <numerusform>&apos;%s&apos;: Ñлишком большой (%&apos;RU64 байт)</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1097"/>
+ <source>The alleged file &apos;%s&apos; has a mode mask stating otherwise (%RTfmode)</source>
+ <translation>Предполагаемый файл &apos;%s&apos; имеет маÑку режима, указывающую иное (%RTfmode)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1127"/>
+ <source>The alleged directory &apos;%s&apos; has a mode mask saying differently (%RTfmode)</source>
+ <translation>Предполагаемый файл &apos;%s&apos; имеет маÑку режима, говорÑщую другое (%RTfmode)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1160"/>
+ <source>&apos;%s&apos; is not a file or directory (enmType=%d)</source>
+ <translation>&apos;%s&apos; не файл или Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ (enmType=%d)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1192"/>
+ <source>Failed seeking to the start of the tarball: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ переÑкочить на начало архива: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1198"/>
+ <source>RTVfsIoStrmFromRTFile failed: %Rrc</source>
+ <translation>RTVfsIoStrmFromRTFile завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1228"/>
+ <source>RTZipTarFsStreamFromIoStream failed: %Rrc</source>
+ <translation>RTZipTarFsStreamFromIoStream завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1232"/>
+ <source>RTZipGzipDecompressIoStream failed: %Rrc</source>
+ <translation>RTZipGzipDecompressIoStream завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1236"/>
+ <source>RTManifestEntryAddPassthruIoStream failed: %Rrc</source>
+ <translation>RTManifestEntryAddPassthruIoStream вернул ошибку: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1321"/>
+ <source>RTVfsFsStrmNext failed: %Rrc</source>
+ <translation>RTVfsFsStrmNext завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1362"/>
+ <source>RTManifestEntryAddIoStream failed on &apos;%s&apos;: %Rrc</source>
+ <translation>RTManifestEntryAddIoStream завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ на &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1392"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1395"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1398"/>
+ <source>Mandator file &apos;%s&apos; is missing</source>
+ <translation>ОбÑзательный файл &apos;%s&apos; отÑутÑтвует</translation>
+ </message>
+</context>
+<context>
+ <name>GeneralTextScript</name>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="217"/>
+ <source>saveToString() called before parse()</source>
+ <translation>saveToString() вызван перед parse()</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/TextScript.cpp" line="267"/>
+ <source>attempting to set line %zu when there are only %zu lines</source>
+ <translation>
+ <numerusform>Попытка уÑтановить Ñтроку %zu когда ÑущеÑтвует только %zu Ñтрока</numerusform>
+ <numerusform>Попытка уÑтановить Ñтроку %zu когда ÑущеÑтвует только %zu Ñтроки</numerusform>
+ <numerusform>Попытка уÑтановить Ñтроку %zu когда ÑущеÑтвует только %zu Ñтрок</numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/TextScript.cpp" line="297"/>
+ <source>attempting search&amp;replace in line %zu when there are only %zu lines</source>
+ <translation>
+ <numerusform>Попытка произвеÑти поиÑк/замену в Ñтроке %zu когда ÑущеÑтвует только %zu Ñтрока</numerusform>
+ <numerusform>Попытка произвеÑти поиÑк/замену в Ñтроке %zu когда ÑущеÑтвует только %zu Ñтроки</numerusform>
+ <numerusform>Попытка произвеÑти поиÑк/замену в Ñтроке %zu когда ÑущеÑтвует только %zu Ñтрок</numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/TextScript.cpp" line="326"/>
+ <source>appending to line %zu when there are only %zu lines</source>
+ <translation>
+ <numerusform>Добавление в Ñтроке %zu когда ÑущеÑтвует только %zu Ñтрока</numerusform>
+ <numerusform>Добавление в Ñтроке %zu когда ÑущеÑтвует только %zu Ñтроки</numerusform>
+ <numerusform>Добавление в Ñтроке %zu когда ÑущеÑтвует только %zu Ñтрок</numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/TextScript.cpp" line="345"/>
+ <source>prepending to line %zu when there are only %zu lines</source>
+ <translation>
+ <numerusform>Ð’Ñтавка в начало Ñтроки %zu когда ÑущеÑтвует только %zu Ñтрока</numerusform>
+ <numerusform>Ð’Ñтавка в начало Ñтроки %zu когда ÑущеÑтвует только %zu Ñтроки</numerusform>
+ <numerusform>Ð’Ñтавка в начало Ñтроки %zu когда ÑущеÑтвует только %zu Ñтрок</numerusform>
+ </translation>
+ </message>
+</context>
+<context>
+ <name>GlobalCtx</name>
+ <message>
+ <location filename="../src-all/Global.cpp" line="587"/>
+ <location filename="../src-all/Global.cpp" line="626"/>
+ <location filename="../src-all/Global.cpp" line="646"/>
+ <source>Null</source>
+ <translation>Null</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="588"/>
+ <source>PoweredOff</source>
+ <translation>Выключена</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="589"/>
+ <source>Saved</source>
+ <translation>Сохранена</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="590"/>
+ <source>Teleported</source>
+ <translation>Портирована</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="591"/>
+ <source>Aborted</source>
+ <translation>Прервана</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="592"/>
+ <source>Aborted-Saved</source>
+ <translation>Прервана-Сохранена</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="593"/>
+ <source>Running</source>
+ <translation>Работает</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="594"/>
+ <source>Paused</source>
+ <translation>ПриоÑтановлена</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="595"/>
+ <source>GuruMeditation</source>
+ <translation>Гуру МедитациÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="596"/>
+ <source>Teleporting</source>
+ <translation>Портирование</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="597"/>
+ <source>LiveSnapshotting</source>
+ <translation>Создание Живого Снимка</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="598"/>
+ <source>Starting</source>
+ <translation>ЗапуÑк</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="599"/>
+ <source>Stopping</source>
+ <translation>Выключение</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="600"/>
+ <source>Saving</source>
+ <translation>Сохранение</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="601"/>
+ <source>Restoring</source>
+ <translation>ВоÑÑтановление</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="602"/>
+ <source>TeleportingPausedVM</source>
+ <translation>Портирование ПриоÑтановленной Ð’Ðœ</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="603"/>
+ <source>TeleportingIn</source>
+ <translation>Портирование (Извне)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="604"/>
+ <source>DeletingSnapshotOnline</source>
+ <translation>Удаление Снимка Работающей ВМ</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="605"/>
+ <source>DeletingSnapshotPaused</source>
+ <translation>Удаление Снимка ПриоÑтановленной Ð’Ðœ</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="606"/>
+ <source>OnlineSnapshotting</source>
+ <translation>Создание Онлайн Снимка</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="607"/>
+ <source>RestoringSnapshot</source>
+ <translation>ВоÑÑтановление Снимка</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="608"/>
+ <source>DeletingSnapshot</source>
+ <translation>Удаление Снимка</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="609"/>
+ <source>SettingUp</source>
+ <translation>УÑтановка</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="610"/>
+ <source>Snapshotting</source>
+ <translation>Создание Снимка</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="615"/>
+ <location filename="../src-all/Global.cpp" line="635"/>
+ <source>InvalidState-0x%08x
+</source>
+ <translation>ÐедопуÑтимое ÑоÑтоÑние-0x%08x
+</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="627"/>
+ <source>Unlocked</source>
+ <translation>Разблокирована</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="628"/>
+ <source>Locked</source>
+ <translation>Заблокирована</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="629"/>
+ <source>Spawning</source>
+ <translation>ОткрываетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="630"/>
+ <source>Unlocking</source>
+ <translation>РазблокируетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="647"/>
+ <source>Floppy</source>
+ <translation>Флоппи</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="648"/>
+ <source>DVD</source>
+ <translation>DVD</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="649"/>
+ <source>HardDisk</source>
+ <translation>ЖеÑткий ДиÑк</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="650"/>
+ <source>Network</source>
+ <translation>Сеть</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="651"/>
+ <source>USB</source>
+ <translation>USB</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="652"/>
+ <source>ShardFolder</source>
+ <translation>ÐžÐ±Ñ‰Ð°Ñ Ð¿Ð°Ð¿ÐºÐ°</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="657"/>
+ <source>InvalidType-0x%08x
+</source>
+ <translation>ÐедопуÑтимый тип-0x%08x
+</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="669"/>
+ <source>unspecified</source>
+ <translation>не указано</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="670"/>
+ <source>host suspend</source>
+ <translation>приоÑтановка хоÑта</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="671"/>
+ <source>host resume</source>
+ <translation>воÑÑтановление хоÑта</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="672"/>
+ <source>host battery low</source>
+ <translation>низкий зарÑд батареи хоÑта</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="673"/>
+ <source>snapshot</source>
+ <translation>Ñнимок</translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="678"/>
+ <source>invalid reason %#010x
+</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð° %#010x
+</translation>
+ </message>
+</context>
+<context>
+ <name>GraphicsAdapter</name>
+ <message>
+ <location filename="../src-server/GraphicsAdapterImpl.cpp" line="193"/>
+ <source>The graphics controller type (%d) is invalid</source>
+ <translation>ÐедопуÑтимый тип графичеÑкого контроллера (%d)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/GraphicsAdapterImpl.cpp" line="223"/>
+ <source>Invalid VRAM size: %lu MB (must be in range [%lu, %lu] MB)</source>
+ <translation>ÐедопуÑтимый размер VRAM: %lu MB (должен быть в диапазоне [%lu, %lu] MB)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/GraphicsAdapterImpl.cpp" line="307"/>
+ <source>Invalid monitor count: %lu (must be in range [%lu, %lu])</source>
+ <translation>ÐедопуÑтимое количеÑтво мониторов: %lu (должен быть в диапазоне [%lu, %lu])</translation>
+ </message>
+</context>
+<context>
+ <name>Guest</name>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="402"/>
+ <source>No user name specified</source>
+ <translation>Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ðµ указано</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="436"/>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="570"/>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="694"/>
+ <source>Maximum number of concurrent guest sessions (%d) reached</source>
+ <translation>ДоÑтигнуто макÑимальное количеÑтво одновременных гоÑтевых ÑеÑÑий (%d)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="443"/>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="577"/>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="701"/>
+ <source>Could not create guest session: %Rrc</source>
+ <translation>Ðевозможно Ñоздать гоÑтевую ÑеÑÑию: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="489"/>
+ <source>Could not find sessions with name &apos;%s&apos;</source>
+ <translation>Ðевозможно найти ÑеÑÑию Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="509"/>
+ <source>Unknown flags: flags value %#x, invalid: %#x</source>
+ <translation>ÐеизвеÑтные флаги: Ð—Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ñ„Ð»Ð°Ð³Ð¾Ð² %#x, недопуÑтимые: %#x</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="513"/>
+ <source>Invalid combination of flags (%#x)</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ ÐºÐ¾Ð¼Ð±Ð¸Ð½Ð°Ñ†Ð¸Ñ Ñ„Ð»Ð°Ð³Ð¾Ð² (%#x)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="515"/>
+ <source>Rebooting</source>
+ <translation>Перезагрузка</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="515"/>
+ <source>Shutting down</source>
+ <translation>Выключение</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="522"/>
+ <source>Rebooting guest</source>
+ <translation>Перезагрузка гоÑтевой ÑиÑтемы</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="522"/>
+ <source>Shutting down guest</source>
+ <translation>Выключение гоÑтевой ÑиÑтемы</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="545"/>
+ <source>%s not supported by installed Guest Additions</source>
+ <translation>%s не поддерживаетÑÑ ÑƒÑтановленными ДополнениÑми ГоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="552"/>
+ <source>Error %s guest: %Rrc</source>
+ <translation>Ошибка гоÑтевой ÑиÑтемы во Ð²Ñ€ÐµÐ¼Ñ %s: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="562"/>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="686"/>
+ <source>Could not open guest session: %Rrc</source>
+ <translation>Ðевозможно открыть гоÑтевую ÑеÑÑию: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="601"/>
+ <source>Unknown flags (%#x)</source>
+ <translation>ÐеизвеÑтные флаги (%#x)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="649"/>
+ <source>Failed to create SessionTaskUpdateAdditions object</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать объект SessionTaskUpdateAdditions</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="655"/>
+ <source>Updating Guest Additions</source>
+ <translation>Обновление Дополнений ГоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="673"/>
+ <source>Starting thread for updating Guest Additions on the guest failed</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить поток Ð´Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð”Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ð¹ ГоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="677"/>
+ <source>Failed to initialize SessionTaskUpdateAdditions object</source>
+ <translation>Ðе удалоÑÑŒ инициализировать объект SessionTaskUpdateAdditions</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestImpl.cpp" line="635"/>
+ <source>Failed to create guest statistics update timer (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать таймер Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÑтатиÑтики гоÑтевой ÑиÑтемы(%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestImpl.cpp" line="640"/>
+ <source>Failed to change guest statistics update timer interval from %u to %u failed (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ изменить интервал таймера Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÑтатиÑтики гоÑтевой ÑиÑтемы из %u в %u (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestImpl.cpp" line="645"/>
+ <source>Failed to start the guest statistics update timer (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить таймер Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÑтатиÑтики гоÑтевой ÑиÑтемы (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestImpl.cpp" line="653"/>
+ <source>Failed to stop the guest statistics update timer (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ оÑтановить таймер Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÑтатиÑтики гоÑтевой ÑиÑтемы (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestImpl.cpp" line="828"/>
+ <source>Invalid status level defined: %u</source>
+ <translation>ÐедопуÑтимый указанный уровень ÑтатуÑа: %u</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestImpl.cpp" line="866"/>
+ <source>VMM device is not available (is the VM running?)</source>
+ <translation>VMM уÑтройÑтво недоÑтупно (Ð’Ðœ работает?)</translation>
+ </message>
+</context>
+<context>
+ <name>GuestDirectory</name>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="85"/>
+ <source>Opening directory &quot;%s&quot;</source>
+ <translation>ОткрываетÑÑ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ &quot;%s&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="249"/>
+ <source>Access to guest directory &quot;%s&quot; is denied</source>
+ <translation>ДоÑтуп к гоÑтевой директории &quot;%s&quot; запрещен</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="250"/>
+ <source>Guest directory &quot;%s&quot; is not empty</source>
+ <translation>ГоÑÑ‚ÐµÐ²Ð°Ñ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ &quot;%s&quot; не пуÑтаÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="252"/>
+ <source>Error %Rrc for guest directory &quot;%s&quot; occurred
+</source>
+ <translation>Произошла ошибка %Rrc в гоÑтевой директории &quot;%s&quot;
+</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="418"/>
+ <source>Closing guest directory failed: %s</source>
+ <translation>Ðе удалоÑÑŒ закрыть гоÑтевую директорию: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="429"/>
+ <source>Closing guest directory &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ закрыть гоÑтевую директорию &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="461"/>
+ <source>Reading guest directory failed: %s</source>
+ <translation>Ðе удалоÑÑŒ прочеÑÑ‚ÑŒ гоÑтевую директорию: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="466"/>
+ <source>Reading guest directory &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ прочеÑÑ‚ÑŒ гоÑтевую директорию &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="471"/>
+ <source>Reading guest directory &quot;%s&quot; failed: Path not found</source>
+ <translation>Ðе удалоÑÑŒ прочеÑÑ‚ÑŒ гоÑтевую директорию &quot;%s&quot;: Путь не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="477"/>
+ <source>Reading guest directory &quot;%s&quot; failed: No more entries</source>
+ <translation>Ðе удалоÑÑŒ прочеÑÑ‚ÑŒ гоÑтевую директорию &quot;%s&quot;: Ðет больше Ñлементов</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="482"/>
+ <source>Reading guest directory &quot;%s&quot; returned error: %Rrc
+</source>
+ <translation>Чтение гоÑтевой директории &quot;%s&quot; возвратило ошибку: %Rrc
+</translation>
+ </message>
+</context>
+<context>
+ <name>GuestDnDSource</name>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="347"/>
+ <source>No drop format specified</source>
+ <translation>Ðе указан формат перетаÑкиваниÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="351"/>
+ <source>Specified format &apos;%s&apos; is not supported</source>
+ <translation>Указанный формат &apos;%s&apos; не поддерживаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="362"/>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="446"/>
+ <source>Current drop operation to host still in progress</source>
+ <translation>Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¿ÐµÑ€ÐµÑ‚Ð°ÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð² хоÑÑ‚ еще в процеÑÑе</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="369"/>
+ <source>Another drag and drop operation to the host already is in progress</source>
+ <translation>Уже производитÑÑ Ð´Ñ€ÑƒÐ³Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¿ÐµÑ€ÐµÑ‚Ð°ÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð² хоÑÑ‚ в наÑтоÑщее времÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="426"/>
+ <source>Starting thread for GuestDnDSource failed (%Rhrc)</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить поток Ð´Ð»Ñ GuestDnDSource (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="519"/>
+ <source>For one or more guest files or directories selected for transferring to the host your guest user does not have the appropriate access rights for. Please make sure that all selected elements can be accessed and that your guest user has the appropriate rights</source>
+ <translation>У Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð³Ð¾Ñтевой ÑиÑтемы нет ÑоответÑтвующих прав доÑтупа длÑ, по крайней мере, одного или неÑкольких файлов или директорий гоÑтевой ÑиÑтемы, выбранных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ на хоÑÑ‚. ПожалуйÑта, убедитеÑÑŒ что вÑе выбранные Ñлементы доÑтупны и пользователь имеет ÑоответÑтвующие права доÑтупа</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="526"/>
+ <source>One or more guest files or directories selected for transferring to the host were notfound on the guest anymore. This can be the case if the guest files were moved and/oraltered while the drag and drop operation was in progress</source>
+ <translation>Один или неÑколько файлов или директорий, выбранных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ на хоÑÑ‚, не найдены в гоÑтевой ÑиÑтеме. Возможно, Ñто из-за того, что файлы в гоÑтевой ÑиÑтеме были перемещены или удалены во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ð¸ перетаÑкиваниÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="532"/>
+ <source>One or more guest files or directories selected for transferring to the host were locked. Please make sure that all selected elements can be accessed and that your guest user has the appropriate rights</source>
+ <translation>Один или неÑколько файлов или директорий, выбранных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ на хоÑÑ‚, заблокированы. ПожалуйÑта, убедитеÑÑŒ что вÑе выбранные Ñлементы доÑтупны и пользователь имеет ÑоответÑтвующие права доÑтупа</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="538"/>
+ <source>The guest was not able to retrieve the drag and drop data within time</source>
+ <translation>ГоÑÑ‚ÐµÐ²Ð°Ñ ÑиÑтема не Ñмогла получить данные перетаÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð² уÑтановленное времÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="542"/>
+ <source>Drag and drop error from guest (%Rrc)</source>
+ <translation>Ошибка перетаÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð¸Ð· гоÑтевой ÑиÑтемы (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="563"/>
+ <source>For one or more host files or directories selected for transferring to the guest your host user does not have the appropriate access rights for. Please make sure that all selected elements can be accessed and that your host user has the appropriate rights.</source>
+ <translation>У Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ñ…Ð¾Ñта нет ÑоответÑтвующих прав доÑтупа длÑ, по крайней мере, одного или неÑкольких файлов или директорий, выбранных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ в гоÑтевую ÑиÑтему. ПожалуйÑта, убедитеÑÑŒ что вÑе выбранные Ñлементы доÑтупны и пользователь имеет ÑоответÑтвующие права доÑтупа.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="569"/>
+ <source>Host disk ran out of space (disk is full).</source>
+ <translation>Ðет Ñвободного меÑта на диÑке хоÑта (диÑк полон).</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="574"/>
+ <source>One or more host files or directories selected for transferring to the host were notfound on the host anymore. This can be the case if the host files were moved and/oraltered while the drag and drop operation was in progress.</source>
+ <translation>Один или неÑколько файлов или директорий, выбранных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ в гоÑтевую ÑиÑтему, не найдены у хоÑта. Возможно, Ñто из-за того, что файлы у хоÑта были перемещены или удалены во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ð¸ перетаÑкиваниÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="580"/>
+ <source>One or more host files or directories selected for transferring to the guest were locked. Please make sure that all selected elements can be accessed and that your host user has the appropriate rights.</source>
+ <translation>Один или неÑколько файлов или директорий, выбранных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ в гоÑтевую ÑиÑтему, заблокированы. ПожалуйÑта, убедитеÑÑŒ что вÑе выбранные Ñлементы доÑтупны и пользователь имеет ÑоответÑтвующие права доÑтупа.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="586"/>
+ <source>Drag and drop error from host (%Rrc)</source>
+ <translation>Ошибка перетаÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð¸Ð· хоÑта (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>GuestDnDState</name>
+ <message>
+ <location filename="../src-client/GuestDnDPrivate.cpp" line="348"/>
+ <source>Dropping data</source>
+ <translation>ПеретаÑкивание данных</translation>
+ </message>
+</context>
+<context>
+ <name>GuestDnDTarget</name>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="268"/>
+ <source>No default action specified</source>
+ <translation>Ðе указано дейÑтвие по умолчанию</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="270"/>
+ <source>Number of allowed actions is empty</source>
+ <translation>СпиÑок допуÑтимых дейÑтвий пуÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="272"/>
+ <source>Number of supported formats is empty</source>
+ <translation>СпиÑок поддерживаемых форматов пуÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="297"/>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="385"/>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="509"/>
+ <source>No or not supported format(s) specified</source>
+ <translation>Ðе указаны поддерживаемые или указаны неподдерживаемые форматы</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="473"/>
+ <source>Invalid default action specified</source>
+ <translation>Указано недопуÑтимое дейÑтвие по умолчанию</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="475"/>
+ <source>Invalid allowed actions specified</source>
+ <translation>СпиÑок допуÑтимых дейÑтвий Ñодержит недопуÑтимые Ñлементы</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="477"/>
+ <source>No drop format(s) specified</source>
+ <translation>Ðе указаны форматы перетаÑкиваниÑ</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="543"/>
+ <source>Guest returned invalid drop formats (%zu formats)</source>
+ <translation>
+ <numerusform>ГоÑÑ‚ÐµÐ²Ð°Ñ ÑиÑтема вернула недопуÑтимые количеÑтво форматов перетаÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ (%zu формат)</numerusform>
+ <numerusform>ГоÑÑ‚ÐµÐ²Ð°Ñ ÑиÑтема вернула недопуÑтимые количеÑтво форматов перетаÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ (%zu формата)</numerusform>
+ <numerusform>ГоÑÑ‚ÐµÐ²Ð°Ñ ÑиÑтема вернула недопуÑтимые количеÑтво форматов перетаÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ (%zu форматов)</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="547"/>
+ <source>Waiting for response of dropped event failed (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ подождать ответа от ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¿ÐµÑ€ÐµÑ‚Ð°ÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="550"/>
+ <source>Sending dropped event to guest failed (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ отправить Ñобытие перетаÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð² гоÑтевую ÑиÑтему (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="553"/>
+ <source>Retrieving drop coordinates failed</source>
+ <translation>Ðе удалоÑÑŒ получить координаты перетаÑкиваниÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="591"/>
+ <source>No data format specified</source>
+ <translation>Ðе указан формат данных</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="593"/>
+ <source>No data to send specified</source>
+ <translation>Ðе указаны данные Ð´Ð»Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²ÐºÐ¸</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="599"/>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="780"/>
+ <source>Current drop operation to guest still in progress</source>
+ <translation>Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¿ÐµÑ€ÐµÑ‚Ð°ÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð² гоÑтевую ÑиÑтему вÑе еще в процеÑÑе</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="606"/>
+ <source>Another drag and drop operation to the guest already is in progress</source>
+ <translation>Уже производитÑÑ Ð´Ñ€ÑƒÐ³Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¿ÐµÑ€ÐµÑ‚Ð°ÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð² гоÑтевую ÑиÑтему в наÑтоÑщее времÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="661"/>
+ <source>Starting thread for GuestDnDTarget failed (%Rhrc)</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить поток Ð´Ð»Ñ GuestDnDTarget (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="682"/>
+ <source>For one or more guest files or directories selected for transferring to the host your guest user does not have the appropriate access rights for. Please make sure that all selected elements can be accessed and that your guest user has the appropriate rights</source>
+ <translation>У Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð³Ð¾Ñтевой ÑиÑтемы нет ÑоответÑтвующих прав доÑтупа длÑ, по крайней мере, одного или неÑкольких файлов или директорий гоÑтевой ÑиÑтемы, выбранных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ на хоÑÑ‚. ПожалуйÑта, убедитеÑÑŒ что вÑе выбранные Ñлементы доÑтупны и пользователь имеет ÑоответÑтвующие права доÑтупа</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="689"/>
+ <source>One or more guest files or directories selected for transferring to the host were notfound on the guest anymore. This can be the case if the guest files were moved and/oraltered while the drag and drop operation was in progress</source>
+ <translation>Один или неÑколько файлов или директорий, выбранных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ на хоÑÑ‚, не найдены в гоÑтевой ÑиÑтеме. Возможно, Ñто из-за того, что файлы в гоÑтевой ÑиÑтеме были перемещены или удалены во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ð¸ перетаÑкиваниÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="695"/>
+ <source>One or more guest files or directories selected for transferring to the host were locked. Please make sure that all selected elements can be accessed and that your guest user has the appropriate rights</source>
+ <translation>Один или неÑколько файлов или директорий, выбранных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ на хоÑÑ‚, заблокированы. ПожалуйÑта, убедитеÑÑŒ что вÑе выбранные Ñлементы доÑтупны и пользователь имеет ÑоответÑтвующие права доÑтупа</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="701"/>
+ <source>The guest was not able to process the drag and drop data within time</source>
+ <translation>ГоÑÑ‚ÐµÐ²Ð°Ñ ÑиÑтема не Ñмогла обработать данные перетаÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð² уÑтановленное времÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="705"/>
+ <source>Drag and drop error from guest (%Rrc)</source>
+ <translation>Ошибка перетаÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð¸Ð· гоÑтевой ÑиÑтемы (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="726"/>
+ <source>For one or more host files or directories selected for transferring to the guest your host user does not have the appropriate access rights for. Please make sure that all selected elements can be accessed and that your host user has the appropriate rights.</source>
+ <translation>У Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ñ…Ð¾Ñта нет ÑоответÑтвующих прав доÑтупа длÑ, по крайней мере, одного или неÑкольких файлов или директорий, выбранных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ в гоÑтевую ÑиÑтему. ПожалуйÑта, убедитеÑÑŒ что вÑе выбранные Ñлементы доÑтупны и пользователь имеет ÑоответÑтвующие права доÑтупа.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="733"/>
+ <source>One or more host files or directories selected for transferring to the host were notfound on the host anymore. This can be the case if the host files were moved and/oraltered while the drag and drop operation was in progress.</source>
+ <translation>Один или неÑколько файлов или директорий, выбранных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ в гоÑтевую ÑиÑтему, не найдены у хоÑта. Возможно Ñто из-за того, что файлы у хоÑта были перемещены или удалены во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ð¸ перетаÑкиваниÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="739"/>
+ <source>One or more host files or directories selected for transferring to the guest were locked. Please make sure that all selected elements can be accessed and that your host user has the appropriate rights.</source>
+ <translation>Один или неÑколько файлов или директорий, выбранных Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ в гоÑтевую ÑиÑтему, заблокированы. ПожалуйÑта, убедитеÑÑŒ что вÑе выбранные Ñлементы доÑтупны и пользователь имеет ÑоответÑтвующие права доÑтупа.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="745"/>
+ <source>Drag and drop error from host (%Rrc)</source>
+ <translation>Ошибка перетаÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð¸Ð· хоÑта (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>GuestFile</name>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="438"/>
+ <source>Access to guest file &quot;%s&quot; denied</source>
+ <translation>ДоÑтуп к файлу гоÑтевой ÑиÑтемы &quot;%s&quot; запрещен</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="439"/>
+ <source>Guest file &quot;%s&quot; already exists</source>
+ <translation>Файл гоÑтевой ÑиÑтемы &quot;%s&quot; уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="440"/>
+ <source>Guest file &quot;%s&quot; not found</source>
+ <translation>Файл гоÑтевой ÑиÑтемы &quot;%s&quot; не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="441"/>
+ <source>Host name &quot;%s&quot;, not found</source>
+ <translation>ХоÑÑ‚ Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &quot;%s&quot; не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="442"/>
+ <source>Sharing violation for guest file &quot;%s&quot;</source>
+ <translation>Ðарушение разделÑемого доÑтупа к файлу гоÑтевой ÑиÑтемы &quot;%s&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="444"/>
+ <source>Error %Rrc for guest file &quot;%s&quot; occurred
+</source>
+ <translation>Произошла ошибка %Rrc Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° гоÑтевой ÑиÑтемы &quot;%s&quot;
+</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1530"/>
+ <source>Closing guest file failed: %s</source>
+ <translation>Ðе удалоÑÑŒ закрыть файл гоÑтевой ÑиÑтемы: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1533"/>
+ <source>Closing guest file &quot;%s&quot; failed with %Rrc
+</source>
+ <translation>Ðе удалоÑÑŒ закрыть файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc
+</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1564"/>
+ <source>Initialization of guest file object for &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ инициализировать объект файла гоÑтевой ÑиÑтемы Ð´Ð»Ñ &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1573"/>
+ <source>Querying guest file information failed: %s</source>
+ <translation>Ðе удалоÑÑŒ запроÑить информацию о файле гоÑтевой ÑиÑтемы: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1578"/>
+ <source>Querying guest file information for &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить информацию о файле гоÑтевой ÑиÑтемы Ð´Ð»Ñ &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1606"/>
+ <source>Querying guest file size failed: %s</source>
+ <translation>Ðе удалоÑÑŒ запроÑить размер файла гоÑтевой ÑиÑтемы: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1610"/>
+ <source>Querying guest file size for &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить размер файла гоÑтевой ÑиÑтемы Ð´Ð»Ñ &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1623"/>
+ <source>The size to read is zero</source>
+ <translation>Размер читаемых данных равен 0</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1648"/>
+ <source>Reading from file &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ прочитать из файла &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1662"/>
+ <source>The size to read for guest file &quot;%s&quot; is zero</source>
+ <translation>Размер читаемых данных из файла гоÑтевой ÑиÑтемы &quot;%s&quot; равен 0</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1686"/>
+ <source>Reading from file &quot;%s&quot; (at offset %RU64) failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ прочитать из файла &quot;%s&quot; (Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ð¸ %RU64): %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1717"/>
+ <source>Invalid seek type for guest file &quot;%s&quot; specified</source>
+ <translation>Указан недопуÑтимый тип Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ ÑƒÐºÐ°Ð·Ð°Ñ‚ÐµÐ»Ñ Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° гоÑтевой ÑиÑтемы &quot;%s&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1729"/>
+ <source>Seeking file &quot;%s&quot; (to offset %RI64) failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ изменить указатель Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° &quot;%s&quot; (на позицию %RI64): %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1750"/>
+ <source>The size (%RI64) for guest file &quot;%s&quot; cannot be a negative value</source>
+ <translation>Размер (%RI64) Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° гоÑтевой ÑиÑтемы &quot;%s&quot; не может быть отрицательным</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestFileImpl.cpp" line="1814"/>
+ <source>Setting the guest file size of &quot;%s&quot; to %RU64 (%#RX64) bytes failed: %Rrc</source>
+ <translation>
+ <numerusform>Ðе удалоÑÑŒ уÑтановить размер Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° гоÑтевой ÑиÑтемы&quot;%s&quot; в %RU64 (%#RX64) байт: %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ уÑтановить размер Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° гоÑтевой ÑиÑтемы&quot;%s&quot; в %RU64 (%#RX64) байта: %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ уÑтановить размер Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° гоÑтевой ÑиÑтемы&quot;%s&quot; в %RU64 (%#RX64) байт: %Rrc</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1826"/>
+ <source>No data to write specified</source>
+ <translation>Ðе указаны данные Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestFileImpl.cpp" line="1836"/>
+ <source>Writing %zu bytes to guest file &quot;%s&quot; failed: %Rrc</source>
+ <translation>
+ <numerusform>Ðе удалоÑÑŒ запиÑать %zu байт в файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ запиÑать %zu байта в файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ запиÑать %zu байт в файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1849"/>
+ <source>No data to write at for guest file &quot;%s&quot; specified</source>
+ <translation>Ðе указаны данные Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи в файл гоÑтевой ÑиÑтемы &quot;%s&quot;</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestFileImpl.cpp" line="1860"/>
+ <source>Writing %zu bytes to file &quot;%s&quot; (at offset %RU64) failed: %Rrc</source>
+ <translation>
+ <numerusform>Ðе удалоÑÑŒ запиÑать %zu байт в файл &quot;%s&quot; (Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ð¸ %RU64): %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ запиÑать %zu байта в файл &quot;%s&quot; (Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ð¸ %RU64): %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ запиÑать %zu байт в файл &quot;%s&quot; (Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ð¸ %RU64): %Rrc</numerusform>
+ </translation>
+ </message>
+</context>
+<context>
+ <name>GuestProcess</name>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="353"/>
+ <source>The base environment feature is not supported by installed Guest Additions</source>
+ <translation>Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð±Ð°Ð·Ð¾Ð²Ð¾Ð³Ð¾ Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð½Ðµ поддерживаетÑÑ ÑƒÑтановленными ДополнениÑми ГоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="535"/>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="536"/>
+ <source>No such file or directory &quot;%s&quot; on guest</source>
+ <translation>Файл или Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ &quot;%s&quot; не ÑущеÑтвует в гоÑтевой ÑиÑтеме</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="537"/>
+ <source>VMM device is not available (is the VM running?)</source>
+ <translation>VMM уÑтройÑтво недоÑтупно (Ð’Ðœ работает?)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="538"/>
+ <source>The guest execution service is not available</source>
+ <translation>ГоÑÑ‚ÐµÐ²Ð°Ñ Ñлужба Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð½Ðµ доÑтупна</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="539"/>
+ <source>The file &quot;%s&quot; is not an executable format on guest</source>
+ <translation>Формат файла &quot;%s&quot; не ÑвлÑетÑÑ Ð¸ÑполнÑемым в гоÑтевой ÑиÑтеме</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="540"/>
+ <source>The user &quot;%s&quot; was not able to logon on guest</source>
+ <translation>Пользователь &quot;%s&quot; не Ñмог войти в гоÑтевую ÑиÑтему</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="541"/>
+ <source>The file &quot;%s&quot; is an invalid name</source>
+ <translation>У файла &quot;%s&quot; недопуÑтимое имÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="542"/>
+ <source>The guest did not respond within time</source>
+ <translation>ГоÑÑ‚ÐµÐ²Ð°Ñ ÑиÑтема не ответила за отведенное времÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="543"/>
+ <source>The execution operation for &quot;%s&quot; was canceled</source>
+ <translation>ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ &quot;%s&quot; отменена</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="544"/>
+ <source>Maximum number of concurrent guest processes has been reached</source>
+ <translation>ДоÑтигнуто макÑимальное количеÑтво одновременных гоÑтевых процеÑÑов</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="545"/>
+ <source>The guest execution service is not ready (yet)</source>
+ <translation>ГоÑÑ‚ÐµÐ²Ð°Ñ Ñлужба Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ (еще) не готова</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="547"/>
+ <source>Error %Rrc for guest process &quot;%s&quot; occurred
+</source>
+ <translation>Произошла ошибка %Rrc у гоÑтевого процеÑÑа &quot;%s&quot;
+</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="1969"/>
+ <source>The size to read is zero</source>
+ <translation>Размер читаемых данных равен 0</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestProcessImpl.cpp" line="1995"/>
+ <source>Reading %RU32 bytes from guest process handle %RU32 failed: %s</source>
+ <translation>
+ <numerusform>Ðе удалоÑÑŒ прочитать %RU32 байт Ñ Ð´ÐµÑкриптора гоÑтевого процеÑÑа %RU32: %s</numerusform>
+ <numerusform>Ðе удалоÑÑŒ прочитать %RU32 байта Ñ Ð´ÐµÑкриптора гоÑтевого процеÑÑа %RU32: %s</numerusform>
+ <numerusform>Ðе удалоÑÑŒ прочитать %RU32 байт Ñ Ð´ÐµÑкриптора гоÑтевого процеÑÑа %RU32: %s</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2000"/>
+ <source>Reading from guest process &quot;%s&quot; (PID %RU32) failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ прочитать Ñ Ð³Ð¾Ñтевого процеÑÑа &quot;%s&quot; (PID %RU32): %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2030"/>
+ <source>Terminating guest process failed: %s</source>
+ <translation>Ðе удалоÑÑŒ завершить гоÑтевой процеÑÑ: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2036"/>
+ <source>Terminating guest process &quot;%s&quot; (PID %RU32) not supported by installed Guest Additions</source>
+ <translation>Завершение гоÑтевого процеÑÑа &quot;%s&quot; (PID %RU32) неподдерживаемого уÑтановленными ДополнениÑми ГоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2041"/>
+ <source>Terminating guest process &quot;%s&quot; (PID %RU32) failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ завершить гоÑтевой процеÑÑ &quot;%s&quot; (PID %RU32): %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2069"/>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2125"/>
+ <source>Flags value %#x, invalid: %#x</source>
+ <translation>Ð—Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ñ„Ð»Ð°Ð³Ð¾Ð² %#x, недопуÑтимые: %#x</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2091"/>
+ <source>Waiting for guest process (flags %#x) failed: %s</source>
+ <translation>Ожидание гоÑтевого процеÑÑа (флаги %#x) завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2100"/>
+ <source>Waiting for guest process &quot;%s&quot; (PID %RU32) failed: %Rrc</source>
+ <translation>Ожидание гоÑтевого процеÑÑа &quot;%s&quot; (PID %RU32) завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rr</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2148"/>
+ <source>Writing %RU32 bytes (flags %#x) to guest process failed: %s</source>
+ <translation>
+ <numerusform>Ðе удалÑÑŒ запиÑать %RU32 байт (флаги %#x) в гоÑтевой процеÑÑ: %s</numerusform>
+ <numerusform>Ðе удалÑÑŒ запиÑать %RU32 байта (флаги %#x) в гоÑтевой процеÑÑ: %s</numerusform>
+ <numerusform>Ðе удалÑÑŒ запиÑать %RU32 байт (флаги %#x) в гоÑтевой процеÑÑ: %s</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2153"/>
+ <source>Writing to guest process &quot;%s&quot; (PID %RU32) failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запиÑать в гоÑтевой процеÑÑ &quot;%s&quot; (PID %RU32): %Rrc</translation>
+ </message>
+</context>
+<context>
+ <name>GuestProcessTool</name>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2892"/>
+ <source>Access to &quot;%s&quot; denied</source>
+ <translation>ДоÑтуп к &quot;%s&quot; запрещен</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2898"/>
+ <source>No such file or directory &quot;%s&quot;</source>
+ <translation>Файл или Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ &quot;%s&quot; не ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2902"/>
+ <source>VMM device is not available (is the VM running?)</source>
+ <translation>VMM уÑтройÑтво недоÑтупно (Ð’Ðœ работает?)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2906"/>
+ <source>The guest execution service is not available</source>
+ <translation>ГоÑтевой ÑÐµÑ€Ð²Ð¸Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð½Ðµ доÑтупен</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2910"/>
+ <source>The file &quot;%s&quot; is not an executable format</source>
+ <translation>Формат файла &quot;%s&quot; не ÑвлÑетÑÑ Ð¸ÑполнÑемым</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2914"/>
+ <source>The user &quot;%s&quot; was not able to logon</source>
+ <translation>Пользователь &quot;%s&quot; не Ñмог войти ÑиÑтему</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2918"/>
+ <source>The file &quot;%s&quot; is an invalid name</source>
+ <translation>У файла &quot;%s&quot; недопуÑтимое имÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2922"/>
+ <source>The guest did not respond within time</source>
+ <translation>ГоÑÑ‚ÐµÐ²Ð°Ñ ÑиÑтема не ответила за отведенное времÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2926"/>
+ <source>The execution operation was canceled</source>
+ <translation>ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð¼ÐµÐ½ÐµÐ½Ð°</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2930"/>
+ <source>Maximum number of concurrent guest processes has been reached</source>
+ <translation>ДоÑтигнуто макÑимальное количеÑтво одновременных гоÑтевых процеÑÑов</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2934"/>
+ <source>The guest execution service is not ready (yet)</source>
+ <translation>ГоÑÑ‚ÐµÐ²Ð°Ñ Ñлужба Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ (еще) не готова</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2938"/>
+ <source>Unhandled error %Rrc for &quot;%s&quot; occurred for tool &quot;%s&quot; on guest -- please file a bug report</source>
+ <translation>Произошла Ð½ÐµÐ¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚Ð°Ð½Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° %Rrc Ð´Ð»Ñ &quot;%s&quot; у инÑтрумента &quot;%s&quot; в гоÑтевой ÑиÑтеме -- пожалуйÑта, Ñообщите об ошибке разработчикам</translation>
+ </message>
+</context>
+<context>
+ <name>GuestSession</name>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="473"/>
+ <source>Invalid environment variable name &apos;%s&apos;, index %zu</source>
+ <translation>ÐедопуÑтимое Ð¸Ð¼Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð¾Ð¹ Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ &apos;%s&apos;, Ð¸Ð½Ð´ÐµÐºÑ %zu</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="475"/>
+ <source>Failed to apply &apos;%s&apos;, index %zu (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ применить &apos;%s&apos;, Ð¸Ð½Ð´ÐµÐºÑ %zu (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="491"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4008"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4029"/>
+ <source>The base environment feature is not supported by the Guest Additions</source>
+ <translation>Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð±Ð°Ð·Ð¾Ð²Ð¾Ð³Ð¾ Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð½Ðµ поддерживаетÑÑ ÑƒÑтановленными ДополнениÑми ГоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="493"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4010"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4031"/>
+ <source>The base environment has not yet been reported by the guest</source>
+ <translation>ГоÑÑ‚ÐµÐ²Ð°Ñ ÑиÑтема еще не Ñообщила о базовом окружении</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="554"/>
+ <source>Getting the user&apos;s home path is not supported by installed Guest Additions</source>
+ <translation>Получение пути к домашней директории Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ðµ поддерживаетÑÑ ÑƒÑтановленными ДополнениÑми ГоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="559"/>
+ <source>Getting the user&apos;s home path failed on the guest: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ получить путь к домашней директории Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ð° гоÑтевой ÑиÑтеме: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="566"/>
+ <source>Getting the user&apos;s home path failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ получить путь к домашней директории пользователÑ: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="592"/>
+ <source>Getting the user&apos;s documents path is not supported by installed Guest Additions</source>
+ <translation>Получение пути к директории пользователÑ, Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ð¼Ð¸ не поддерживаетÑÑ ÑƒÑтановленными ДополнениÑми ГоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="597"/>
+ <source>Getting the user&apos;s documents path failed on the guest: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ получить путь к директории Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ c документами на гоÑтевой ÑиÑтеме: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="604"/>
+ <source>Getting the user&apos;s documents path failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ получить путь к директории Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ð¼Ð¸: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="754"/>
+ <source>No source(s) specified</source>
+ <translation>Ðе указаны пути к иÑточникам</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="756"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3547"/>
+ <source>No destination specified</source>
+ <translation>Ðе указано путь назначениÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="766"/>
+ <source>Failed to create GuestSessionTaskCopyFrom object</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать объект GuestSessionTaskCopyFrom</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="771"/>
+ <source>Copying to &quot;%s&quot; on the host</source>
+ <translation>Копирование в &quot;%s&quot; хоÑта</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="787"/>
+ <source>Starting thread for copying from guest to the host failed</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить поток Ð´Ð»Ñ ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ Ð³Ð¾Ñтевой ÑиÑтемы в хоÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="791"/>
+ <source>Initializing GuestSessionTaskCopyFrom object failed</source>
+ <translation>Ðе удалоÑÑŒ инициализировать объект GuestSessionTaskCopyFrom</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="825"/>
+ <source>Failed to create GuestSessionTaskCopyTo object</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать объект GuestSessionTaskCopyTo</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="830"/>
+ <source>Copying to &quot;%s&quot; on the guest</source>
+ <translation>Копирование в &quot;%s&quot; гоÑтевой ÑиÑтемы</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="846"/>
+ <source>Starting thread for copying from host to the guest failed</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить поток Ð´Ð»Ñ ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ Ñ…Ð¾Ñта в гоÑтевую ÑиÑтему</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="850"/>
+ <source>Initializing GuestSessionTaskCopyTo object failed</source>
+ <translation>Ðе удалоÑÑŒ инициализировать объект GuestSessionTaskCopyTo</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="895"/>
+ <source>Invalid directory copy flag: %.*s</source>
+ <translation>ÐедопуÑтимый флаг ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ð¸: %.*s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1460"/>
+ <source>Invalid file copy flag: %.*s</source>
+ <translation>ÐедопуÑтимый флаг ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð°: %.*s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1846"/>
+ <source>VMM device is not available (is the VM running?)</source>
+ <translation>VMM уÑтройÑтво недоÑтупно (Ð’Ðœ работает?)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1850"/>
+ <source>The guest execution service is not available</source>
+ <translation>ГоÑтевой ÑÐµÑ€Ð²Ð¸Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð½Ðµ доÑтупен</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1854"/>
+ <source>The specified user account on the guest is restricted and can&apos;t be used to logon</source>
+ <translation>Ð£ÐºÐ°Ð·Ð°Ð½Ð½Ð°Ñ ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð³Ð¾Ñтевой ÑиÑтемы ограничена и не может иÑпользоватьÑÑ Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ð° в ÑиÑтему</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1858"/>
+ <source>The specified user was not able to logon on guest</source>
+ <translation>Указанный пользователь не Ñмог войти в гоÑтевую ÑиÑтему</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1862"/>
+ <source>The guest did not respond within time</source>
+ <translation>ГоÑÑ‚ÐµÐ²Ð°Ñ ÑиÑтема не ответила за отведенное времÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1866"/>
+ <source>The session operation was canceled</source>
+ <translation>СеÑÑÐ¸Ð¾Ð½Ð½Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð¼ÐµÐ½ÐµÐ½Ð°</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1870"/>
+ <source>Maximum number of concurrent guest processes has been reached</source>
+ <translation>ДоÑтигнуто макÑимальное количеÑтво одновременных гоÑтевых процеÑÑов</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1874"/>
+ <source>The guest execution service is not ready (yet)</source>
+ <translation>ГоÑтевой ÑÐµÑ€Ð²Ð¸Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ (еще) не готов</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1907"/>
+ <source>Session is not in started state</source>
+ <translation>СеÑÑÐ¸Ñ Ð½Ðµ ÑоÑтоÑнии &quot;запущено&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3322"/>
+ <source>Closing guest session failed: %s</source>
+ <translation>Ðе удалоÑÑŒ закрыть гоÑтевую ÑеÑÑию: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3325"/>
+ <source>Closing guest session &quot;%s&quot; failed with %Rrc</source>
+ <translation>Ðе удалоÑÑŒ закрыть гоÑтевую ÑеÑÑию &quot;%s&quot; из-за %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3351"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3379"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3571"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3600"/>
+ <source>Unknown flags: flags value %#x, invalid: %#x</source>
+ <translation>ÐеизвеÑтные флаги: Ð—Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ñ„Ð»Ð°Ð³Ð¾Ð² %#x, недопуÑтимые: %#x</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3404"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3482"/>
+ <source>Parameter array sizes don&apos;t match to the number of sources specified</source>
+ <translation>Размеры маÑÑивов в параметрах не ÑоответÑтвуют количеÑтву указанных иÑточников</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3427"/>
+ <source>Querying type for guest source failed: %s</source>
+ <translation>Ðе удалоÑÑŒ запроÑить тип иÑточника в гоÑтевой ÑиÑтеме: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3430"/>
+ <source>Querying type for guest source &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить тип иÑточника гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3462"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3532"/>
+ <source>Source type %#x invalid / not supported</source>
+ <translation>Тип иÑточника %#x недопуÑтим / не поддерживаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3500"/>
+ <source>Unable to query type for source &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ запроÑить тип иÑточника &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3543"/>
+ <source>No sources specified</source>
+ <translation>Ðе указаны пути к иÑточникам</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3545"/>
+ <source>First source entry is empty</source>
+ <translation>Первый Ñлемент из ÑпиÑка иÑточников пуÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3621"/>
+ <source>No directory to create specified</source>
+ <translation>Ðе указана Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð»Ñ ÑозданиÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3630"/>
+ <source>Unknown flags (%#x)</source>
+ <translation>ÐеизвеÑтные флаги (%#x)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3646"/>
+ <source>Guest directory creation failed: %s</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать директорию в гоÑтевой ÑиÑтеме: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3652"/>
+ <source>Guest directory creation failed: Invalid parameters given</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать директорию в гоÑтевой ÑиÑтеме: Данные параметры недопуÑтимы</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3656"/>
+ <source>Guest directory creation failed: Unexpectedly aborted</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать директорию в гоÑтевой ÑиÑтеме: Ðеожиданно прерван</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3660"/>
+ <source>Guest directory creation failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать директорию в гоÑтевой ÑиÑтеме: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3672"/>
+ <source>No template specified</source>
+ <translation>Ðе указан шаблон</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3674"/>
+ <source>No directory name specified</source>
+ <translation>Ðе указано Ð¸Ð¼Ñ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ð¸</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3677"/>
+ <source>Mode invalid (must be specified in octal mode)</source>
+ <translation>ÐедопуÑтимый режим (должен быть указан в воÑмеричном формате)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3694"/>
+ <source>Temporary guest directory creation failed: %s</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать временную директорию в гоÑтевой ÑиÑтеме: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3699"/>
+ <source>Temporary guest directory creation &quot;%s&quot; with template &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать временную директорию &quot;%s&quot; по шаблону &quot;%s&quot; в гоÑтевой ÑиÑтеме: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3711"/>
+ <source>Empty path</source>
+ <translation>Путь пуÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3739"/>
+ <source>Querying directory existence failed: %s</source>
+ <translation>Ðе удалоÑÑŒ запроÑить ÑущеÑтвование директории: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3754"/>
+ <source>Querying directory existence &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить ÑущеÑтвование директории &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3767"/>
+ <source>No directory to open specified</source>
+ <translation>Ðе указана Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð»Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3769"/>
+ <source>Directory filters are not implemented yet</source>
+ <translation>Фильтры директории еще не реализованы</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3778"/>
+ <source>Open flags (%#x) not implemented yet</source>
+ <translation>Флаги Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ (%#x) еще не реализованы</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3804"/>
+ <source>Opening guest directory &quot;%s&quot; failed; invalid parameters given</source>
+ <translation>Ðе удалоÑÑŒ открыть гоÑтевую директорию &quot;%s&quot;; даны недопуÑтимые параметры</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3811"/>
+ <source>Opening guest directory failed: %s</source>
+ <translation>Ðе удалоÑÑŒ открыть гоÑтевую директорию: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3816"/>
+ <source>Opening guest directory &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ открыть гоÑтевую директорию &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3827"/>
+ <source>No directory to remove specified</source>
+ <translation>Ðе указана Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3846"/>
+ <source>Handling removing guest directories not supported by installed Guest Additions</source>
+ <translation>Обработка ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ð¹ гоÑтевой ÑиÑтемы не поддерживаетÑÑ ÑƒÑтановленными ДополнениÑми ГоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3852"/>
+ <source>Removing guest directory failed: %s</source>
+ <translation>Ðе удалоÑÑŒ удалить гоÑтевую директорию: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3857"/>
+ <source>Removing guest directory &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ удалить гоÑтевую директорию &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3869"/>
+ <source>No directory to remove recursively specified</source>
+ <translation>Ðе указана Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð»Ñ Ñ€ÐµÐºÑƒÑ€Ñивного удалениÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3891"/>
+ <source>Invalid flags specified</source>
+ <translation>Указаны недопуÑтимые флаги</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3906"/>
+ <source>Removing guest directory</source>
+ <translation>Удаление директории гоÑтевой ÑиÑтемы</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3927"/>
+ <source>Handling removing guest directories recursively not supported by installed Guest Additions</source>
+ <translation>Обработка рекурÑивного ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ð¹ гоÑтевой ÑиÑтемы не поддерживаетÑÑ ÑƒÑтановленными ДополнениÑми ГоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3933"/>
+ <source>Recursively removing guest directory failed: %s</source>
+ <translation>Ðе удалоÑÑŒ рекурÑивно удалить гоÑтевую директорию: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3938"/>
+ <source>Recursively removing guest directory &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ рекурÑивно удалить гоÑтевую директорию &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3963"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3983"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4003"/>
+ <source>Invalid environment variable name &apos;%s&apos;</source>
+ <translation>ÐедопуÑтимое Ð¸Ð¼Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð¾Ð¹ Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3965"/>
+ <source>Failed to schedule setting environment variable &apos;%s&apos; to &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ запланировать уÑтановку переменной Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ &apos;%s&apos; в &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3985"/>
+ <source>Failed to schedule unsetting environment variable &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ запланировать ÑÐ±Ñ€Ð¾Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð¾Ð¹ Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4080"/>
+ <source>Querying guest file existence failed: %s</source>
+ <translation>Ðе удалоÑÑŒ запроÑить ÑущеÑтвование файла гоÑтевой ÑиÑтемы: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4093"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4325"/>
+ <source>Querying guest file information for &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить информацию о файле гоÑтевой ÑиÑтемы Ð´Ð»Ñ &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4115"/>
+ <source>No file to open specified</source>
+ <translation>Ðе указан файл Ð´Ð»Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4135"/>
+ <source>Append access modes are not yet implemented</source>
+ <translation>Добавление режимов доÑтупа еще не реализовано</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4137"/>
+ <source>Unknown FileAccessMode value %u (%#x)</source>
+ <translation>ÐеизвеÑтное значение FileAccessMode %u (%#x)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4156"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4173"/>
+ <source>Unknown FileOpenAction value %u (%#x)</source>
+ <translation>ÐеизвеÑтное значение FileOpenAction %u (%#x)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4170"/>
+ <source>Only FileSharingMode_All is currently implemented</source>
+ <translation>Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ñ€ÐµÐ°Ð»Ð¸Ð·Ð¾Ð²Ð°Ð½ только FileSharingMode_All</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4181"/>
+ <source>Unsupported FileOpenExFlag value(s) in aFlags (%#x)</source>
+ <translation>Ðеподдерживаемое значение FileOpenExFlag в aFlags (%#x)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4195"/>
+ <source>Handling guest files not supported by installed Guest Additions</source>
+ <translation>Обработка файлов гоÑтевой ÑиÑтемы не поддерживаетÑÑ ÑƒÑтановленными ДополнениÑми ГоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4201"/>
+ <source>Opening guest file failed: %s</source>
+ <translation>Ðе удалоÑÑŒ открыть файл гоÑтевой ÑиÑтемы: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4206"/>
+ <source>Opening guest file &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ открыть файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4217"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4248"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4293"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4334"/>
+ <source>No path specified</source>
+ <translation>Ðе указан путь</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4234"/>
+ <source>Querying guest file size failed: %s</source>
+ <translation>Ðе удалоÑÑŒ запроÑить размер файла гоÑтевой ÑиÑтемы: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4238"/>
+ <source>Querying guest file size of &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить размер файла гоÑтевой ÑиÑтемы Ð´Ð»Ñ &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4279"/>
+ <source>Querying guest file existence information failed: %s</source>
+ <translation>Ðе удалоÑÑŒ запроÑить информацию о ÑущеÑтвовании файла гоÑтевой ÑиÑтемы длÑ: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4284"/>
+ <source>Querying guest file existence information for &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить информацию о ÑущеÑтвовании файла гоÑтевой ÑиÑтемы Ð´Ð»Ñ &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4321"/>
+ <source>Querying guest file information failed: %s</source>
+ <translation>Ðе удалоÑÑŒ запроÑить информацию о файле гоÑтевой ÑиÑтемы: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4349"/>
+ <source>Removing guest file failed: %s</source>
+ <translation>Ðе удалоÑÑŒ удалить файл гоÑтевой ÑиÑтемы: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4353"/>
+ <source>Removing guest file &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ удалить файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4370"/>
+ <source>No source path specified</source>
+ <translation>Ðе указаны пути к иÑточникам</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4373"/>
+ <source>No destination path specified</source>
+ <translation>Ðе указано путь назначениÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4384"/>
+ <source>Unknown rename flag: %#x</source>
+ <translation>ÐеизвеÑтный флаг Ð¿ÐµÑ€ÐµÐ¸Ð¼ÐµÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ (%#x)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4405"/>
+ <source>Handling renaming guest paths not supported by installed Guest Additions</source>
+ <translation>Обработка Ð¿ÐµÑ€ÐµÐ¸Ð¼ÐµÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿ÑƒÑ‚ÐµÐ¹ гоÑтевой ÑиÑтемы не поддерживаетÑÑ ÑƒÑтановленными ДополнениÑми ГоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4411"/>
+ <source>Renaming guest path failed: %s</source>
+ <translation>Ðе удалоÑÑŒ переименовать путь гоÑтевой ÑиÑтемы: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4416"/>
+ <source>Renaming guest path &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ переименовать путь гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4489"/>
+ <source>No command to execute specified</source>
+ <translation>Ðе указана команда Ð´Ð»Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4559"/>
+ <source>Failed to start guest process: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить гоÑтевой процеÑÑ: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4563"/>
+ <source>Maximum number of concurrent guest processes per session (%u) reached</source>
+ <translation>ДоÑтигнуто макÑимальное количеÑтво одновременных гоÑтевых процеÑÑов на ÑеÑÑию (%u)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4566"/>
+ <source>Failed to create guest process object: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать объект процеÑÑа гоÑтевой ÑиÑтемы: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4570"/>
+ <source>Failed to apply environment variable &apos;%s&apos;, index %u (%Rrc)&apos;</source>
+ <translation>Ðе удалоÑÑŒ применить переменную Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ &apos;%s&apos;, Ð¸Ð½Ð´ÐµÐºÑ %u (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4574"/>
+ <source>Failed to set up the environment: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ уÑтановить окружение: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4584"/>
+ <source>No valid process ID (PID) specified</source>
+ <translation>Ðе указан допуÑтимый ID процеÑÑа (PID)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4595"/>
+ <source>No process with PID %RU32 found</source>
+ <translation>ПроцеÑÑ Ñ PID %RU32 не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4648"/>
+ <source>Waiting for guest process failed: %s</source>
+ <translation>Ðе удалоÑÑŒ подождать гоÑтевой процеÑÑ: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4660"/>
+ <source>Waiting for guest session &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ подождать гоÑтевую ÑеÑÑию &quot;%s&quot; из-за %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4661"/>
+ <source>Unnamed</source>
+ <translation>БезымÑнный</translation>
+ </message>
+</context>
+<context>
+ <name>GuestSessionTask</name>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="294"/>
+ <source>Guest directory &quot;%s&quot; already exists</source>
+ <translation>Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð³Ð¾Ñтевой ÑиÑтемы &quot;%s&quot; уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="319"/>
+ <source>Guest error creating directory &quot;%s&quot; on the guest: %Rrc</source>
+ <translation>Ошибка гоÑтевой ÑиÑтемы во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð³Ð¾Ñтевой директории &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="326"/>
+ <source>Host error creating directory &quot;%s&quot; on the guest: %Rrc</source>
+ <translation>Ошибка хоÑта во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð³Ð¾Ñтевой директории &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="357"/>
+ <source>Host directory &quot;%s&quot; already exists</source>
+ <translation>Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ñ…Ð¾Ñта &quot;%s&quot; уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="364"/>
+ <source>Could not create host directory &quot;%s&quot;: %Rrc</source>
+ <translation>Ðевозможно Ñоздать директорию у хоÑта &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="405"/>
+ <source>Seeking to offset %RU64 of guest file &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ изменить указатель в позицию %RI64 Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="420"/>
+ <source>Reading %RU32 bytes @ %RU64 from guest &quot;%s&quot; failed: %Rrc</source>
+ <translation>
+ <numerusform>Ðе удалоÑÑŒ прочитать %RU32 байт @ %RU64 Ñ Ð³Ð¾Ñтевой ÑиÑтемы &quot;%s&quot;: %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ прочитать %RU32 байта @ %RU64 Ñ Ð³Ð¾Ñтевой ÑиÑтемы &quot;%s&quot;: %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ прочитать %RU32 байт @ %RU64 Ñ Ð³Ð¾Ñтевой ÑиÑтемы &quot;%s&quot;: %Rrc</numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="429"/>
+ <source>Writing %RU32 bytes to host file &quot;%s&quot; failed: %Rrc</source>
+ <translation>
+ <numerusform>Ðе удалоÑÑŒ запиÑать %RU32 байт @ %RU64 в файл хоÑта &quot;%s&quot;: %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ запиÑать %RU32 байта @ %RU64 в файл хоÑта &quot;%s&quot;: %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ запиÑать %RU32 байт @ %RU64 в файл хоÑта &quot;%s&quot;: %Rrc</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="468"/>
+ <source>Writing guest file &quot;%s&quot; to host file &quot;%s&quot; failed: Access denied</source>
+ <translation>Ðе удалоÑÑŒ запиÑать файл гоÑтевой ÑиÑтемы &quot;%s&quot; в файл хоÑта &quot;%s&quot;: ДоÑтуп запрещен</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="476"/>
+ <source>Copying guest file &quot;%s&quot; to host file &quot;%s&quot; failed (%RU64/%RU64 bytes transfered)</source>
+ <translation>Ðе удалоÑÑŒ Ñкопировать файл гоÑтевой ÑиÑтемы &quot;%s&quot; в файл хоÑта &quot;%s&quot; (%RU64/%RU64 байт передано)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="511"/>
+ <source>Guest file lookup failed</source>
+ <translation>Ðе удалоÑÑŒ найти файл гоÑтевой ÑиÑтемы</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="515"/>
+ <source>Guest file lookup for &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ найти файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="528"/>
+ <source>Guest file &quot;%s&quot; is a symbolic link</source>
+ <translation>Файл гоÑтевой ÑиÑтемы &quot;%s&quot; ÑвлÑетÑÑ ÑимволичеÑкой ÑÑылкой</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="536"/>
+ <source>Guest object &quot;%s&quot; is not a file (is type %#x)</source>
+ <translation>Объект гоÑтевой ÑиÑтемы &quot;%s&quot; не ÑвлÑетÑÑ Ñ„Ð°Ð¹Ð»Ð¾Ð¼ (тип %#x)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="550"/>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="830"/>
+ <source>Guest file could not be opened</source>
+ <translation>Ðе удалоÑÑŒ открыть файл гоÑтевой ÑиÑтемы</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="554"/>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="834"/>
+ <source>Guest file &quot;%s&quot; could not be opened: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ открыть файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="573"/>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="613"/>
+ <source>Host file &quot;%s&quot; already exists</source>
+ <translation>Файл хоÑта &quot;%s&quot; уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="592"/>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="902"/>
+ <source>Host file lookup for &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ найти файл хоÑта &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="636"/>
+ <source>Host file &quot;%s&quot; is a symbolic link</source>
+ <translation>Файл хоÑта &quot;%s&quot; ÑвлÑетÑÑ ÑимволичеÑкой ÑÑылкой</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="657"/>
+ <source>No memory to allocate host file path</source>
+ <translation>Ðе хватает памÑти чтобы выделить буфер под путь к файлу хоÑта</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="678"/>
+ <source>Opening/creating host file &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ открыть/Ñоздать файл хоÑта &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="725"/>
+ <source>Seeking to offset %RU64 of host file &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ изменить указатель в позицию %RI64 Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° хоÑта &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="740"/>
+ <source>Reading %RU32 bytes @ %RU64 from host file &quot;%s&quot; failed: %Rrc</source>
+ <translation>
+ <numerusform>Ðе удалоÑÑŒ прочитать %RU32 байт @ %RU64 Ñ Ñ„Ð°Ð¹Ð»Ð° хоÑта &quot;%s&quot;: %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ прочитать %RU32 байта @ %RU64 Ñ Ñ„Ð°Ð¹Ð»Ð° хоÑта &quot;%s&quot;: %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ прочитать %RU32 байт @ %RU64 Ñ Ñ„Ð°Ð¹Ð»Ð° хоÑта &quot;%s&quot;: %Rrc</numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="749"/>
+ <source>Writing %zu bytes to guest file &quot;%s&quot; failed: %Rrc</source>
+ <translation>
+ <numerusform>Ðе удалоÑÑŒ запиÑать %zu байт в файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ запиÑать %zu байта в файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</numerusform>
+ <numerusform>Ðе удалоÑÑŒ запиÑать %zu байт в файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="784"/>
+ <source>Writing to guest file &quot;%s&quot; failed: Access denied</source>
+ <translation>Ðе удалоÑÑŒ запиÑать в файл гоÑтевой ÑиÑтемы &quot;%s&quot;: ДоÑтуп запрещен</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="792"/>
+ <source>Copying to guest file &quot;%s&quot; failed (%RU64/%RU64 bytes transfered)</source>
+ <translation>Ðе удалоÑÑŒ Ñкопировать в файл гоÑтевой ÑиÑтемы &quot;%s&quot; (%RU64/%RU64 байт передано)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="851"/>
+ <source>Host path lookup for file &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ найти путь к файлу хоÑта &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="887"/>
+ <source>Guest error while determining object data for guest file &quot;%s&quot;: %Rrc</source>
+ <translation>Ошибка гоÑтевой ÑиÑтемы во времы Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… объекта Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="894"/>
+ <source>Host error while determining object data for guest file &quot;%s&quot;: %Rrc</source>
+ <translation>Ошибка хоÑта во времы Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… объекта Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="932"/>
+ <source>Opening host file &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ открыть файл хоÑта &quot;%s&quot;: %Rrc</translation>
+ </message>
+</context>
+<context>
+ <name>GuestSessionTaskCopyFrom</name>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1442"/>
+ <source>Host destination must not be empty</source>
+ <translation>Путь Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ñƒ хоÑта не должен быть пуÑтым</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1457"/>
+ <source>Guest source entry must not be empty</source>
+ <translation>Путь иÑточника в гоÑтевой ÑиÑтеме не должен быть пуÑтым</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1491"/>
+ <source>Guest file lookup failed</source>
+ <translation>Ðе удалоÑÑŒ найти файл гоÑтевой ÑиÑтемы</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1494"/>
+ <source>Guest file lookup for &quot;%s&quot; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ найти файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1503"/>
+ <source>Guest source is not a file: %s</source>
+ <translation>ИÑточник в гоÑтевой ÑиÑтеме не ÑвлÑетÑÑ Ñ„Ð°Ð¹Ð»Ð¾Ð¼: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1512"/>
+ <source>Guest source is not a directory: %s</source>
+ <translation>ИÑточник в гоÑтевой ÑиÑтеме не ÑвлÑетÑÑ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸ÐµÐ¹: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1534"/>
+ <source>Error adding guest source &apos;%s&apos; to list: %Rrc</source>
+ <translation>Ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¸Ñточника в гоÑтевой ÑиÑтеме &apos;%s&apos; в ÑпиÑок: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1570"/>
+ <source>Failed with %Rrc</source>
+ <translation>Ошибка %Rrc</translation>
+ </message>
+</context>
+<context>
+ <name>GuestSessionTaskCopyTo</name>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1745"/>
+ <source>Guest destination must not be empty</source>
+ <translation>Путь Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð² гоÑтевой ÑиÑтеме не должен быть пуÑтым</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1760"/>
+ <source>Host source entry must not be empty</source>
+ <translation>Путь иÑточника у хоÑта не должен быть пуÑтым</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1769"/>
+ <source>No such host file/directory: %s</source>
+ <translation>Файл или Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ñ…Ð¾Ñта не ÑущеÑтвует: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1777"/>
+ <source>Host source is not a file: %s</source>
+ <translation>ИÑточник у хоÑта не ÑвлÑетÑÑ Ñ„Ð°Ð¹Ð»Ð¾Ð¼: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1786"/>
+ <source>Host source is not a directory: %s</source>
+ <translation>ИÑточник у хоÑта не ÑвлÑетÑÑ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸ÐµÐ¹: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1810"/>
+ <source>Error adding host source &apos;%s&apos; to list: %Rrc</source>
+ <translation>Ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¸Ñточника у хоÑта &apos;%s&apos; в ÑпиÑок: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1846"/>
+ <source>Failed with %Rrc</source>
+ <translation>Ошибка %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1960"/>
+ <source>Unknown object type (%#x) on guest for &quot;%s&quot;</source>
+ <translation>ÐеизвеÑтный тип гоÑтевого объекта (%#x) на &quot;%s&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2020"/>
+ <source>Building source host path for entry &quot;%s&quot; failed (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ поÑтроить путь иÑточника в хоÑÑ‚-ÑиÑтеме Ð´Ð»Ñ Ñлемента &quot;%s&quot; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2038"/>
+ <source>Building destination guest path for entry &quot;%s&quot; failed (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ поÑтроить путь Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð² гоÑтевой-ÑиÑтеме Ð´Ð»Ñ Ñлемента &quot;%s&quot; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1895"/>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1903"/>
+ <source>Querying information on guest for &apos;%s&apos; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить информацию о &apos;%s&apos; у гоÑтевой ÑиÑтемы: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1945"/>
+ <source>Guest directory &quot;%s&quot; already exists</source>
+ <translation>Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð³Ð¾Ñтевой ÑиÑтемы &quot;%s&quot; уже ÑущеÑтвует</translation>
+ </message>
+</context>
+<context>
+ <name>GuestSessionTaskUpdateAdditions</name>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2199"/>
+ <source>Guest file &quot;%s&quot; could not be opened: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ открыть файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2251"/>
+ <source>Running update file &quot;%s&quot; on guest failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ обновить файл гоÑтевой ÑиÑтемы &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2256"/>
+ <source>Running update file on guest failed</source>
+ <translation>Ðе удалоÑÑŒ обновить файл гоÑтевой ÑиÑтемы</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2262"/>
+ <source>Update file &quot;%s&quot; reported invalid running state</source>
+ <translation>Файл Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ &quot;%s&quot; Ñообщил о недопуÑтимом рабочем ÑоÑтоÑнии</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2268"/>
+ <source>Error while running update file &quot;%s&quot; on guest: %Rrc</source>
+ <translation>Ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¿ÑƒÑка файла Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ &quot;%s&quot; в гоÑтевой ÑиÑтеме: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2319"/>
+ <source>Guest Additions were not ready within time, giving up</source>
+ <translation>Ð”Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð“Ð¾Ñтевой ОС не Ñтали готовы за отведенное времÑ, ÑдаюÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2332"/>
+ <source>Guest Additions are installed but not fully loaded yet, aborting automatic update</source>
+ <translation>Ð”Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð“Ð¾Ñтевой ОС уÑтановлены, но еще не полноÑтью загружены, прерывание автоматичеÑкого обновлениÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2335"/>
+ <source>Guest Additions not installed or ready, aborting automatic update</source>
+ <translation>Ð”Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð“Ð¾Ñтевой ОС не уÑтановлены или не готовы, прерывание автоматичеÑкого обновлениÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2352"/>
+ <source>Guest has too old Guest Additions (%s) installed for automatic updating, please update manually</source>
+ <translation>Ð’ гоÑтевой ÑиÑтеме уÑтановлена Ñлишком ÑÑ‚Ð°Ñ€Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ð”Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ð¹ ГоÑтевой ОС (%s). ÐвтоматичеÑкое обновление невозможно. ПожалуйÑта, обновите вручную</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2381"/>
+ <source>Unable to detected guest OS version, please update manually</source>
+ <translation>Ðевозможно определить верÑию гоÑтевой ОС. ПожалуйÑта, обновите вручную</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2401"/>
+ <source>Windows 2000 and XP are not supported for automatic updating due to WHQL interaction, please update manually</source>
+ <translation>ÐвтоматичеÑкое обновление не поддерживаетÑÑ Ð² Windows 2000 и XP из-за WHQL взаимодейÑтвиÑ. ПожалуйÑта, обновите вручную</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2409"/>
+ <source>%s (%s) not supported for automatic updating, please update manually</source>
+ <translation>ÐвтоматичеÑкое обновление не поддерживаетÑÑ Ð² %s (%s). ПожалуйÑта, обновите вручную</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2427"/>
+ <source>Detected guest OS (%s) does not support automatic Guest Additions updating, please update manually</source>
+ <translation>ÐвтоматичеÑкое обновление не поддерживаетÑÑ Ð² обнаруженной гоÑтевой OC (%s). ПожалуйÑта, обновите вручную</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2444"/>
+ <source>Unable to open Guest Additions .ISO file &quot;%s&quot;: %Rrc</source>
+ <translation>Ðевозможно открыть .ISO файл Дополнений ГоÑтевой ОС &quot;%s&quot;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2454"/>
+ <source>Unable to open file as ISO 9660 file system volume: %Rrc</source>
+ <translation>Ðевозможнр открыть файл как том Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ð¾Ð¹ ÑиÑтемой ISO 9660: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2522"/>
+ <source>Creating installation directory on guest failed</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать уÑтановочную директорию в гоÑтевой ÑиÑтеме: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2528"/>
+ <source>Creating installation directory &quot;%s&quot; on guest failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать уÑтановочную директорию &quot;%s&quot; в гоÑтевой ÑиÑтеме: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2715"/>
+ <source>Error while copying file &quot;%s&quot; to &quot;%s&quot; on the guest: %Rrc</source>
+ <translation>Ошибка во Ð²Ñ€ÐµÐ¼Ñ ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° &quot;%s&quot; в &quot;%s&quot; в гоÑтевой ÑиÑтеме: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2779"/>
+ <source>Installation was canceled</source>
+ <translation>УÑтановка отменена</translation>
+ </message>
+</context>
+<context>
+ <name>Host</name>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1233"/>
+ <source>The aFeature value %d (%#x) is out of range.</source>
+ <translation>Значение aFeature %d (%#x) выходит за границы диапазона.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1314"/>
+ <source>CPU no.%u is not present</source>
+ <translation>ЦПУ #%u отÑутÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1315"/>
+ <source>CPU no.%u is not online</source>
+ <translation>ЦПУ #%u не подключен</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1482"/>
+ <source>Unable to create a host network interface</source>
+ <translation>Ðевозможно Ñоздать Ñетевой хоÑÑ‚-интерфейÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1549"/>
+ <source>Host network interface with UUID {%RTuuid} does not exist</source>
+ <translation>Сетевой хоÑÑ‚-Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ñ UUID {%RTuuid} не ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1627"/>
+ <source>The given USB device filter is not created within this VirtualBox instance</source>
+ <translation>Данный фильтр USB уÑтройÑтва не Ñоздан в Ñтом ÑкземплÑре VirtualBox</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1631"/>
+ <source>The given USB device filter is already in the list</source>
+ <translation>Данный фильтр USB уÑтройÑтва уже в ÑпиÑке</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1675"/>
+ <source>The USB device filter list is empty</source>
+ <translation>СпиÑок фильтров USB уÑтройÑтв пуÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1679"/>
+ <source>Invalid position: %lu (must be in range [0, %lu])</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ: %lu (должна быть в диапазоне [0, %lu])</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1723"/>
+ <source>The host DVD drive named &apos;%s&apos; could not be found</source>
+ <translation>DVD диÑковод хоÑта Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos; не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1737"/>
+ <source>The host floppy drive named &apos;%s&apos; could not be found</source>
+ <translation>Флоппи диÑковод хоÑта Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos; не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1777"/>
+ <source>The host network interface named &apos;%s&apos; could not be found</source>
+ <translation>Сетевой хоÑÑ‚-Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos; не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1820"/>
+ <source>The host network interface with the given GUID could not be found</source>
+ <translation>Сетевой хоÑÑ‚-Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ñ Ð´Ð°Ð½Ð½Ñ‹Ð¼ GUID не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1895"/>
+ <source>Could not find a USB device with address &apos;%s&apos;</source>
+ <translation>Ðевозможно найти USB уÑтройÑтво Ñ Ð°Ð´Ñ€ÐµÑом &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1929"/>
+ <source>Could not find a USB device with uuid {%RTuuid}</source>
+ <translation>Ðевозможно найти USB уÑтройÑтво Ñ UUID {%RTuuid}</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3549"/>
+ <source>Could not load the Host USB Proxy Service (VERR_FILE_NOT_FOUND). The service might not be installed on the host computer</source>
+ <translation>Ðевозможно запуÑтить Ñлужбу USB прокÑи хоÑта (VERR_FILE_NOT_FOUND). Служба может быть не уÑтановлена на хоÑте</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3552"/>
+ <source>VirtualBox is not currently allowed to access USB devices. You can change this by adding your user to the &apos;vboxusers&apos; group. Please see the user manual for a more detailed explanation</source>
+ <translation>Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ VirtualBox не имеет доÑтупа к USB уÑтройÑтвам. Ð’Ñ‹ можете изменить Ñто путем Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð² группу &apos;vboxusers&apos;. ПожалуйÑта, обратитеÑÑŒ к руководÑтву Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð·Ð° более подробными объÑÑнениÑми</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3555"/>
+ <source>VirtualBox is not currently allowed to access USB devices. You can change this by allowing your user to access the &apos;usbfs&apos; folder and files. Please see the user manual for a more detailed explanation</source>
+ <translation>Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ VirtualBox не имеет доÑтупа к USB уÑтройÑтвам. Ð’Ñ‹ можете изменить Ñто путем Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð´Ð¾Ñтупа к &apos;usbfs&apos; папке и файлам. ПожалуйÑта, обратитеÑÑŒ к руководÑтву Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð·Ð° более подробными объÑÑнениÑми</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3558"/>
+ <source>The USB Proxy Service has not yet been ported to this host</source>
+ <translation>Служба USB прокÑи еще не портирована под Ñтот хоÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3561"/>
+ <source>Could not load the Host USB Proxy service</source>
+ <translation>Ðевозможно запуÑтить Службу USB прокÑи хоÑта</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3908"/>
+ <source>Failed to open NT\GLOBAL?? (error %Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть NT\GLOBAL?? (ошибка %Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3919"/>
+ <location filename="../src-server/HostImpl.cpp" line="3936"/>
+ <source>Out of memory! (direntry buffer)</source>
+ <translation>Ðе хватает памÑти! (буфер direntry)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3939"/>
+ <source>RTVfsDirReadEx failed: %Rrc</source>
+ <translation>RTVfsDirReadEx завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3953"/>
+ <source>Unknown (Access denied)</source>
+ <translation>ÐеизвеÑтно (ДоÑтуп запрещен)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3957"/>
+ <location filename="../src-server/HostImpl.cpp" line="4002"/>
+ <source>Out of memory</source>
+ <translation>Ðе хватает памÑти</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="4060"/>
+ <source>Failed to update fixed drive list (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ обновить ÑпиÑок жеÑтких диÑков (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>HostNetworkInterface</name>
+ <message>
+ <location filename="../src-server/HostNetworkInterfaceImpl.cpp" line="622"/>
+ <source>Invalid IPv6 prefix length</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ð´Ð»Ð¸Ð½Ð° префикÑа IPv6</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostNetworkInterfaceImpl.cpp" line="633"/>
+ <source>Invalid IPv6 address</source>
+ <translation>ÐедопуÑтимый Ð°Ð´Ñ€ÐµÑ IPv6</translation>
+ </message>
+</context>
+<context>
+ <name>HostOnlyNetwork</name>
+ <message>
+ <location filename="../src-server/HostOnlyNetworkImpl.cpp" line="141"/>
+ <source>Network name cannot be empty</source>
+ <translation>Ð˜Ð¼Ñ Ñети не может быть пуÑтым</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostOnlyNetworkImpl.cpp" line="168"/>
+ <source>Network mask cannot be empty</source>
+ <translation>МаÑка Ñети не может быть пуÑтой</translation>
+ </message>
+</context>
+<context>
+ <name>HostUSBDevice</name>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="427"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} cannot be accessed by guest computers</source>
+ <translation>USB уÑтройÑтво &apos;%s&apos; Ñ UUID {%RTuuid} не имеет доÑтупа из гоÑтевой ÑиÑтемы</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="431"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} is being exclusively used by the host computer</source>
+ <translation>USB уÑтройÑтво &apos;%s&apos; Ñ UUID {%RTuuid} ÑкÑклюзивно иÑпользуетÑÑ Ñ…Ð¾Ñтом</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="439"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} is already captured by the virtual machine &apos;%s&apos;</source>
+ <translation>USB уÑтройÑтво &apos;%s&apos; Ñ UUID {%RTuuid} уже захвачено виртуальной машиной &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="444"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} is busy with a previous request. Please try again later</source>
+ <translation>USB уÑтройÑтво &apos;%s&apos; Ñ UUID {%RTuuid} занÑто предыдущим запроÑом. ПожалуйÑта, попробуйте позже</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="450"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} is not in the right state for capturing (%s)</source>
+ <translation>USB уÑтройÑтво &apos;%s&apos; Ñ UUID {%RTuuid} в некорректном Ð´Ð»Ñ Ð·Ð°Ñ…Ð²Ð°Ñ‚Ð° ÑоÑтоÑнии (%s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="495"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} is in use by someone else</source>
+ <translation>USB уÑтройÑтво &apos;%s&apos; Ñ UUID {%RTuuid} иÑпользуетÑÑ ÐºÐµÐ¼-то еще</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="672"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} is busy (state &apos;%s&apos;). Please try again later</source>
+ <translation>USB уÑтройÑтво &apos;%s&apos; Ñ UUID {%RTuuid} занÑто (ÑоÑтоÑние &apos;%s&apos;). ПожалуйÑта, попробуйте позже</translation>
+ </message>
+</context>
+<context>
+ <name>HostUSBDeviceFilter</name>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="1138"/>
+ <source>The remote state filter is not supported by IHostUSBDeviceFilter objects</source>
+ <translation>Фильтр удаленного ÑоÑтоÑÐ½Ð¸Ñ Ð½Ðµ поддерживаетÑÑ Ð¾Ð±ÑŠÐµÐºÑ‚Ð°Ð¼Ð¸ IHostUSBDeviceFilter</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="1153"/>
+ <source>The masked interfaces property is not applicable to IHostUSBDeviceFilter objects</source>
+ <translation>СвойÑтво маÑкированных интерфейÑов не применимо к объектам IHostUSBDeviceFilter</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="1186"/>
+ <source>Action value InvalidUSBDeviceFilterAction is not permitted</source>
+ <translation>Значение дейÑÑ‚Ð²Ð¸Ñ InvalidUSBDeviceFilterAction не разрешено</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="1189"/>
+ <source>Invalid action %d</source>
+ <translation>ÐедопуÑтимое дейÑтвие %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="1197"/>
+ <source>Unexpected error %Rrc</source>
+ <translation>ÐÐµÐ¾Ð¶Ð¸Ð´Ð°Ð½Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° %Rrc</translation>
+ </message>
+</context>
+<context>
+ <name>HostUpdate</name>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="361"/>
+ <source>%s: RTHttpCreate() failed: %Rrc</source>
+ <translation>%s: RTHttpCreate() завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="371"/>
+ <source>%s: RTHttpAddHeader() failed: %Rrc (on User-Agent)</source>
+ <translation>%s: RTHttpAddHeader() завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc (на User-Agent)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="379"/>
+ <source>%s: ISystemProperties::proxyMode() failed: %Rrc</source>
+ <translation>%s: ISystemProperties::proxyMode() завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="386"/>
+ <source>%s: ISystemProperties::proxyURL() failed: %Rrc</source>
+ <translation>%s: ISystemProperties::proxyURL() завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="389"/>
+ <source>%s: RTHttpSetProxyByUrl() failed: %Rrc</source>
+ <translation>%s: RTHttpSetProxyByUrl() завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="395"/>
+ <source>%s: RTHttpUseSystemProxySettings() failed: %Rrc</source>
+ <translation>%s: RTHttpUseSystemProxySettings() завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="407"/>
+ <source>%s: RTHttpGetBinary() failed: %Rrc</source>
+ <translation>%s: RTHttpGetBinary() завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="460"/>
+ <source>Invalid server response: %Rrc (%.*Rhxs -- %.*Rhxs)</source>
+ <translation>ÐедопуÑтимый ответ Ñервера: %Rrc (%.*Rhxs -- %.*Rhxs)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="493"/>
+ <source>Update check type %d is not implemented</source>
+ <translation>Обновление типа проверки %d не реализовано</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="573"/>
+ <source>UpdateCheckType::ExtensionPack is not implemented</source>
+ <translation>UpdateCheckType::ExtensionPack не реализовано</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="575"/>
+ <source>UpdateCheckType::GuestAdditions is not implemented</source>
+ <translation>UpdateCheckType::GuestAdditions не реализовано</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="577"/>
+ <source>Invalid aCheckType value %d</source>
+ <translation>ÐедопуÑтимое значение aCheckType %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="609"/>
+ <source>Checking for software update...</source>
+ <translation>Проверка обновлений ПО...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="626"/>
+ <source>Update checking support was not compiled into this VirtualBox build</source>
+ <translation>Поддержка проверки обновлений не вÑтроена в Ñту Ñборку VirtualBox</translation>
+ </message>
+</context>
+<context>
+ <name>HostVideoInputDevice</name>
+ <message>
+ <location filename="../src-server/HostVideoInputDeviceImpl.cpp" line="227"/>
+ <source>Failed to get webcam list: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ получить ÑпиÑок вебкамер: %Rrc</translation>
+ </message>
+</context>
+<context>
+ <name>Keyboard</name>
+ <message>
+ <location filename="../src-client/KeyboardImpl.cpp" line="219"/>
+ <source>Could not send all scan codes to the virtual keyboard (%Rrc)</source>
+ <translation>Ðевозможно отправить вÑе Ñкан-коды в виртуальную клавиатуру (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/KeyboardImpl.cpp" line="260"/>
+ <source>Could not send usage code to the virtual keyboard (%Rrc)</source>
+ <translation>Ðевозможно отправить код иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð² виртуальную клавиатуру (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>Machine</name>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="409"/>
+ <source>Cannot attach disk &apos;%s&apos; -- file name too long</source>
+ <translation>Ðевозможно подключить диÑк &apos;%s&apos; -- Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° Ñлишком длинное</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="452"/>
+ <source>Internal error adding a medium UUID to the map</source>
+ <translation>ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ° во Ð²Ñ€ÐµÐ¼Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ UUID ноÑÐ¸Ñ‚ÐµÐ»Ñ Ð² карту ноÑителей</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="500"/>
+ <source>Cannot attach image &apos;%s&apos; -- file name too long</source>
+ <translation>Ðевозможно подключить образ &apos;%s&apos; -- Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° Ñлишком длинное</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="540"/>
+ <source>Cannot handle medium attachment: channel is %d, device is %d</source>
+ <translation>Ðевозможно обработать подключение ноÑителÑ: канал %d, уÑтройÑтво %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="566"/>
+ <source>Cannot handle medium attachment: storageBus is %d, channel is %d, device is %d</source>
+ <translation>Ðевозможно обработать подключение ноÑителÑ: шина %d, канал %d, уÑтройÑтво %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="482"/>
+ <source>Trying to open a VM config &apos;%s&apos; which has the same UUID as an existing virtual machine</source>
+ <translation>Попытка открыть конфигурацию Ð’Ðœ &apos;%s&apos; у которой тот же UUID что и у ÑущеÑтвующей виртуальной машины</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="655"/>
+ <location filename="../src-server/MachineImpl.cpp" line="702"/>
+ <source>Invalid machine settings file name &apos;%s&apos; (%Rrc)</source>
+ <translation>ÐедопуÑтимое Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° наÑтроек машины &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="685"/>
+ <source>Machine settings file &apos;%s&apos; already exists</source>
+ <translation>Файл наÑтроек машины &apos;%s&apos; уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="694"/>
+ <source>Could not delete the existing settings file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно удалить ÑущеÑтвующий файл наÑтроек &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="744"/>
+ <source>Machine UUID {%RTuuid} in &apos;%s&apos; doesn&apos;t match its UUID {%s} in the registry file &apos;%s&apos;</source>
+ <translation>UUID машины {%RTuuid} в &apos;%s&apos; не ÑоответÑтвует его UUID {%s} в файле рееÑтра &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1017"/>
+ <source>A machine cannot have a UUID as its name</source>
+ <translation>Машина не может иметь UUID в качеÑтве имени</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1457"/>
+ <source>Invalid hardware version: %s
+</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ð°Ð¿Ð¿Ð°Ñ€Ð°Ñ‚Ð½Ð¾Ð³Ð¾ обеÑпечениÑ: %s
+</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1519"/>
+ <source>Invalid RAM size: %lu MB (must be in range [%lu, %lu] MB)</source>
+ <translation>ÐедопуÑтимый размер RAM: %lu MB (должен быть в диапазоне [%lu, %lu] MB)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1550"/>
+ <source>Invalid virtual CPU count: %lu (must be in range [%lu, %lu])</source>
+ <translation>ÐедопуÑтимое количеÑтво ЦПУ: %lu (должен быть в диапазоне [%lu, %lu])</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1562"/>
+ <source>There is still a CPU attached to socket %lu.Detach the CPU before removing the socket</source>
+ <translation>Ð’Ñе еще ÑущеÑтвует ЦПУ подключенный к Ñокету %lu. Отключите ЦПУ перед удалением Ñокета</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1596"/>
+ <source>Invalid CPU execution cap value: %lu (must be in range [%lu, %lu])</source>
+ <translation>ÐедопуÑтимое значение предельной загрузки ЦПУ: %lu (должно быть в диапазоне [%lu, %lu])</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1667"/>
+ <source>CPU hotplugging can&apos;t be disabled because the maximum number of CPUs is not equal to the amount of CPUs attached</source>
+ <translation>ГорÑчее подключение ЦПУ не может быть выключено, потому что макÑимальное количеÑтво ЦПУ не равно количеÑтву подключенных ЦПУ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1808"/>
+ <source>Invalid memory balloon size: %lu MB (must be in range [%lu, %lu] MB)</source>
+ <translation>ÐедопуÑтимый размер memory balloon: %lu MB (должен быть в диапазоне [%lu, %lu] MB)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1820"/>
+ <source>Memory ballooning is only supported on 64-bit hosts</source>
+ <translation>Memory ballooning поддерживаетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ на 64 битных хоÑтах</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1844"/>
+ <source>Page fusion is only supported on 64-bit hosts</source>
+ <translation>Page fusion поддерживаетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ на 64 битных хоÑтах</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2148"/>
+ <source>Currently only aSubIdx values 0 and 0xffffffff are supported: %#x</source>
+ <translation>Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÑŽÑ‚ÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ aSubIdx Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ 0 и 0xffffffff: %#x</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2152"/>
+ <source>CpuId override leaf %#x is out of range</source>
+ <translation>ЛиÑÑ‚ Ð¿ÐµÑ€ÐµÐ¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ CpuId %#x вышел за границы диапазона</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2162"/>
+ <source>Max of 256 CPUID override leaves reached</source>
+ <translation>ДоÑтигнут макÑимум 256 лиÑтов Ð¿ÐµÑ€ÐµÐ¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ CPUID</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2382"/>
+ <source>The snapshot folder of a machine with snapshots cannot be changed (please delete all snapshots first)</source>
+ <translation>Папка Ð´Ð»Ñ Ñнимков машины не может быть изменена пока там еÑÑ‚ÑŒ Ñники. (пожалуйÑта, Ñначала удалите вÑе Ñнимки)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2391"/>
+ <source>Invalid snapshot folder &apos;%s&apos; (%Rrc)</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ð¿Ð°Ð¿ÐºÐ° Ð´Ð»Ñ Ñнимков &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2771"/>
+ <location filename="../src-server/MachineImpl.cpp" line="2892"/>
+ <source>The machine is not powered off (state is %s)</source>
+ <translation>Машина не выключена (ÑоÑтоÑние %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2793"/>
+ <source>Invalid port number %d</source>
+ <translation>ÐедопуÑтимый номер порта %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2848"/>
+ <source>Cannot set an already hashed password, only plain text password please</source>
+ <translation>Ðевозможно уÑтановить уже прохешированный пароль, пожалуйÑта, иÑпользуйте только проÑтой текÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2962"/>
+ <location filename="../src-server/MachineImpl.cpp" line="3371"/>
+ <source>The given session is busy</source>
+ <translation>Ð”Ð°Ð½Ð½Ð°Ñ ÑеÑÑÐ¸Ñ Ð·Ð°Ð½Ñта</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2966"/>
+ <source>No IInternalSessionControl interface</source>
+ <translation>Ðет интерфейÑа IInternalSessionControl</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2976"/>
+ <location filename="../src-server/MachineImpl.cpp" line="7400"/>
+ <source>The machine &apos;%s&apos; is not registered</source>
+ <translation>Машина &apos;%s&apos; не зарегиÑтрирована</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3031"/>
+ <source>Failed to get a console object from the direct session (%Rhrc)</source>
+ <translation>Ðе удалоÑÑŒ получить объект конÑоли из прÑмой ÑеÑÑии (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3043"/>
+ <location filename="../src-server/MachineImpl.cpp" line="3184"/>
+ <location filename="../src-server/MachineImpl.cpp" line="7675"/>
+ <source>Failed to assign the machine to the session (%Rhrc)</source>
+ <translation>Ðе удалоÑÑŒ назначить машину ÑеÑÑии (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3051"/>
+ <source>The machine &apos;%s&apos; was unlocked unexpectedly while attempting to share its session</source>
+ <translation>Машина &apos;%s&apos; была неожиданно разблокирована во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ð¾Ð¿Ñ‹Ñ‚ÐºÐ¸ поделитьÑÑ Ñвоей ÑеÑÑией</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3064"/>
+ <source>The machine &apos;%s&apos; is already locked for a session (or being unlocked)</source>
+ <translation>Машина &apos;%s&apos; уже заблокирована Ð´Ð»Ñ ÑеÑÑии (или разблокируетÑÑ)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3091"/>
+ <source>The machine &apos;%s&apos; already has a lock request pending</source>
+ <translation>У машины &apos;%s&apos; уже еÑÑ‚ÑŒ ожидающий Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° блокировку</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3119"/>
+ <source>An unexpected process (PID=0x%08X) has tried to lock the machine &apos;%s&apos;, while only the process started by LaunchVMProcess (PID=0x%08X) is allowed</source>
+ <translation>Ðеожиданных процеÑÑ (PID=0x%08X) попыталÑÑ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²Ð°Ñ‚ÑŒ машину &apos;%s&apos;, в то Ð²Ñ€ÐµÐ¼Ñ ÐºÐ°Ðº допуÑкаетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ один процеÑÑ, запущенный LaunchVMProcess (PID=0x%08X)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3226"/>
+ <source>Failed to assign the machine to the remote session (%Rhrc)</source>
+ <translation>Ðе удалоÑÑŒ назначить машину удаленной ÑеÑÑии (%Rhrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3390"/>
+ <source>Starting VM</source>
+ <translation>ЗапуÑк Ð’Ðœ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3393"/>
+ <source>Creating process for virtual machine &quot;%s&quot; (%s)</source>
+ <translation>Создание процеÑÑа Ð´Ð»Ñ Ð²Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð¾Ð¹ машины &quot;%s&quot; (%s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3422"/>
+ <source>The machine &apos;%s&apos; is not locked by a session</source>
+ <translation>Машина &apos;%s&apos; не заблокирована ÑеÑÑией</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3429"/>
+ <source>The machine &apos;%s&apos; does not have a VM process</source>
+ <translation>У машины &apos;%s&apos; нет процеÑÑа Ð’Ðœ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3448"/>
+ <location filename="../src-server/MachineImpl.cpp" line="3471"/>
+ <source>Invalid boot position: %lu (must be in range [1, %lu])</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð¾Ñ‡Ð½Ð°Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ: %lu (должна быть в диапазоне [1, %lu])</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3453"/>
+ <source>Booting from USB device is currently not supported</source>
+ <translation>Загрузка из USB уÑтройÑтва в наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð½Ðµ поддерживаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3502"/>
+ <source>Cannot attach storage devices to an unregistered machine</source>
+ <translation>Ðевозможно подключить уÑтройÑтво Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ðº незарегиÑтрированной машине</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3515"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4093"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4179"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4392"/>
+ <source>Could not get type of controller &apos;%s&apos;</source>
+ <translation>Ðевозможно получить тип контроллера &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3534"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4112"/>
+ <source>Controller &apos;%s&apos; does not support hotplugging</source>
+ <translation>Контроллер &apos;%s&apos; не поддерживает горÑчее подключение</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3553"/>
+ <source>Medium &apos;%s&apos; is already attached to port %d, device %d of controller &apos;%s&apos; of this virtual machine</source>
+ <translation>ÐоÑитель &apos;%s&apos; уже поключен на порт %d, уÑтройÑтво %d контроллера &apos;%s&apos; Ñтой виртуальной машины</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3561"/>
+ <source>Device is already attached to port %d, device %d of controller &apos;%s&apos; of this virtual machine</source>
+ <translation>УÑтройÑтво уже поключено на порт %d, уÑтройÑтво %d контроллера &apos;%s&apos; Ñтой виртуальной машины</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3567"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4535"/>
+ <source>The given medium pointer is invalid</source>
+ <translation>Данный указатель на ноÑитель недейÑтвительный</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3580"/>
+ <source>Medium &apos;%s&apos; is already attached to this virtual machine</source>
+ <translation>ÐоÑитель &apos;%s&apos; уже поключен к Ñтой виртуальной машине</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3607"/>
+ <source>Cannot attach medium &apos;%s&apos;: the media type &apos;MultiAttach&apos; can only be attached to machines that were created with VirtualBox 4.0 or later</source>
+ <translation>Ðевозможно подключить ноÑитель &apos;%s&apos;: ноÑитель Ñ Ñ‚Ð¸Ð¿Ð¾Ð¼ &apos;МножеÑтвенное подключение&apos; может быть подключен к машинам, Ñозданным в VirtualBox 4.0 или позже</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3935"/>
+ <source>Could not lock medium when creating diff &apos;%s&apos;</source>
+ <translation>Ðевозможно заблокировать ноÑитель во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ€Ð°Ð·Ð½Ð¾Ñтного ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4121"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4207"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4252"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4295"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4338"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4380"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4445"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4640"/>
+ <location filename="../src-server/MachineImpl.cpp" line="5849"/>
+ <source>No storage device attached to device slot %d on port %d of controller &apos;%s&apos;</source>
+ <translation>УÑтройÑтва Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð½Ðµ подключены к Ñлоту уÑтройÑтв %d на порту %d контроллера &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4126"/>
+ <source>The device slot %d on port %d of controller &apos;%s&apos; does not support hotplugging</source>
+ <translation>Слот уÑтройÑтв %d на порту %d контроллера &apos;%s&apos; не поддерживает горÑчее подключение</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4198"/>
+ <source>Controller &apos;%s&apos; does not support hotplugging which is required to change the passthrough setting while the VM is running</source>
+ <translation>Контроллер &apos;%s&apos; не поддерживает горÑчее подключение, которое требуетÑÑ Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½Ð°Ñтроек &quot;прÑмого доÑтупа&quot; в то Ð²Ñ€ÐµÐ¼Ñ ÐºÐ°Ðº Ð’Ðœ работает</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4218"/>
+ <source>Setting passthrough rejected as the device attached to device slot %d on port %d of controller &apos;%s&apos; is not a DVD</source>
+ <translation>УÑтановка &quot;прÑмого доÑтупа&quot; отклонена, так как уÑтройÑтво, подключенное к Ñлоту уÑтройÑтв %d на порту %d контроллера &apos;%s&apos; не ÑвлÑетÑÑ DVD</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4263"/>
+ <source>Setting temporary eject flag rejected as the device attached to device slot %d on port %d of controller &apos;%s&apos; is not a DVD</source>
+ <translation>УÑтановка флага временного изьÑÑ‚Ð¸Ñ Ð¾Ñ‚ÐºÐ»Ð¾Ð½ÐµÐ½Ð°, так как уÑтройÑтво, подключенное к Ñлоту уÑтройÑтв %d на порту %d контроллера &apos;%s&apos; не ÑвлÑетÑÑ DVD</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4286"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4329"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4371"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4436"/>
+ <source>Invalid machine state: %s</source>
+ <translation>ÐедопуÑтимое ÑоÑтоÑние машины: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4306"/>
+ <source>Setting the non-rotational medium flag rejected as the device attached to device slot %d on port %d of controller &apos;%s&apos; is not a hard disk</source>
+ <translation>УÑтановка флага &quot;твердотельный ноÑитель&quot; отклонена, так как уÑтройÑтво, подключенное к Ñлоту уÑтройÑтв %d на порту %d контроллера &apos;%s&apos; не ÑвлÑетÑÑ Ð¶ÐµÑтким диÑком</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4349"/>
+ <source>Setting the discard medium flag rejected as the device attached to device slot %d on port %d of controller &apos;%s&apos; is not a hard disk</source>
+ <translation>УÑтановка флага &quot;ÑброÑить (удалить) ноÑитель&quot; отклонена, так как уÑтройÑтво, подключенное к Ñлоту уÑтройÑтв %d на порту %d контроллера &apos;%s&apos; не ÑвлÑетÑÑ Ð¶ÐµÑтким диÑком</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4397"/>
+ <source>Controller &apos;%s&apos; does not support changing the hot-pluggable device flag</source>
+ <translation>Контроллер &apos;%s&apos; не поддерживает Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ„Ð»Ð°Ð³Ð° &quot;возможноÑÑ‚ÑŒ горÑчего подключениÑ&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4407"/>
+ <source>Setting the hot-pluggable device flag rejected as the device attached to device slot %d on port %d of controller &apos;%s&apos; is a floppy drive</source>
+ <translation>УÑтановка флага &quot;возможноÑÑ‚ÑŒ горÑчего подключениÑ&quot; отклонена, так как уÑтройÑтво, подключенное к Ñлоту уÑтройÑтв %d на порту %d контроллера &apos;%s&apos; ÑвлÑетÑÑ Ñ„Ð»Ð¾Ð¿Ð¿Ð¸ диÑком</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4455"/>
+ <source>The given bandwidth group pointer is invalid</source>
+ <translation>Данный указатель на группу полоÑÑ‹ пропуÑÐºÐ°Ð½Ð¸Ñ Ð½ÐµÐ´ÐµÐ¹Ñтвителен</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4524"/>
+ <source>No drive attached to device slot %d on port %d of controller &apos;%s&apos;</source>
+ <translation>ДиÑки не подключены к Ñлоту уÑтройÑтва %d на порту %d контроллера &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4552"/>
+ <source>The device at port %d, device %d of controller &apos;%s&apos; of this virtual machine is not removeable</source>
+ <translation>УÑтройÑтво на порту %d, уÑтройÑтво %d контроллера &apos;%s&apos; Ñтой виртуальной машины не Ñьемное</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4656"/>
+ <source>Serial port slot %RU32 is out of bounds (max %zu)</source>
+ <translation>Слот поÑледовательного порта %RU32 выходит за границы (макимально %zu)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4667"/>
+ <source>Parallel port slot %RU32 is out of bounds (max %zu)</source>
+ <translation>Слот параллельного порта %RU32 выходит за границы (макимально %zu)</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MachineImpl.cpp" line="4677"/>
+ <source>No network adapter in slot %RU32 (total %RU32 adapters)</source>
+ <translation>
+ <numerusform>Ðет Ñетевых адаптеров в Ñлоте %RU32 (вÑего %RU32 адаптер)</numerusform>
+ <numerusform>Ðет Ñетевых адаптеров в Ñлоте %RU32 (вÑего %RU32 адаптера)</numerusform>
+ <numerusform>Ðет Ñетевых адаптеров в Ñлоте %RU32 (вÑего %RU32 адаптеров)</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4756"/>
+ <source>Cannot set extradata for a snapshot</source>
+ <translation>Ðевозможно уÑтановить ÑкÑтра данные Ð´Ð»Ñ Ñнимка</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4761"/>
+ <source>Cannot set extradata for an immutable machine</source>
+ <translation>Ðевозможно уÑтановить ÑкÑтра данные Ð´Ð»Ñ Ð½ÐµÐ¸Ð·Ð¼ÐµÐ½Ñемой машины</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4780"/>
+ <source>Could not set extra data because someone refused the requested change of &apos;%s&apos; to &apos;%s&apos;%s%ls</source>
+ <translation>Ðевозможно уÑтановить ÑкÑтра данные, потому что кто-то отклонил запрошенное изменение &apos;%s&apos; в &apos;%s&apos;%s%ls</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4926"/>
+ <source>Cannot unregister the machine &apos;%s&apos; while it is locked</source>
+ <translation>Ðевозможно отменить региÑтрацию машины &apos;%s&apos; пока она заблокирована</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5090"/>
+ <source>The session has been accidentally closed</source>
+ <translation>СеÑÑÐ¸Ñ Ð±Ñ‹Ð»Ð° Ñлучайно закрыта</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5124"/>
+ <location filename="../src-server/MachineImpl.cpp" line="5159"/>
+ <source>Deleting &apos;%s&apos;</source>
+ <translation>Удаление &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5165"/>
+ <source>Could not delete file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно удалить файл &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5168"/>
+ <source>Cleaning up machine directory</source>
+ <translation>ОчиÑтка директории машины</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5253"/>
+ <source>Cannot delete settings of a registered machine</source>
+ <translation>Ðевозможно удалить наÑтройки зарегиÑтрированной машины</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5272"/>
+ <source>The given medium pointer with index %d is invalid</source>
+ <translation>Данный указатель на ноÑитель Ñ Ð¸Ð½Ð´ÐµÐºÑом %d недейÑтвителен</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5287"/>
+ <source>Deleting files</source>
+ <translation>Удаление файлов</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5290"/>
+ <source>Collecting file inventory</source>
+ <translation>Сбор информации о файлах</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5344"/>
+ <location filename="../src-server/MachineImpl.cpp" line="8999"/>
+ <source>Shared folder named &apos;%s&apos; already exists</source>
+ <translation>ÐžÐ±Ñ‰Ð°Ñ Ð¿Ð°Ð¿ÐºÐ° Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos; уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5401"/>
+ <location filename="../src-server/MachineImpl.cpp" line="5424"/>
+ <source>Machine is not locked for session (session state: %s)</source>
+ <translation>Машина &apos;%s&apos; не заблокирована Ð´Ð»Ñ ÑеÑÑии (ÑоÑтоÑние ÑеÑÑии: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5549"/>
+ <source>Invalid guest property flag values: &apos;%s&apos;</source>
+ <translation>ÐедопуÑтимые Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ñ„Ð»Ð°Ð³Ð° ÑвойÑтва гоÑтевой ÑиÑтемы: &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5571"/>
+ <source>The property &apos;%s&apos; cannot be changed by the host</source>
+ <translation>СвойÑтво &apos;%s&apos; не может быть изменено хоÑтом</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5865"/>
+ <source>Invalid connection type: %d</source>
+ <translation>ÐедопуÑтимый тип ÑоединениÑ: %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5879"/>
+ <location filename="../src-server/MachineImpl.cpp" line="9145"/>
+ <source>Storage controller named &apos;%s&apos; already exists</source>
+ <translation>Контроллер Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos; уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5955"/>
+ <source>Could not find a storage controller with instance number &apos;%lu&apos;</source>
+ <translation>Ðевозможно найти контроллер Ñ Ð½Ð¾Ð¼ÐµÑ€Ð¾Ð¼ ÑкземплÑра &apos;%lu&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6078"/>
+ <location filename="../src-server/MachineImpl.cpp" line="6143"/>
+ <source>Invalid USB controller type: %d</source>
+ <translation>ÐедопуÑтимый тип USB контроллера %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6092"/>
+ <source>USB controller named &apos;%s&apos; already exists</source>
+ <translation>USB контроллер Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos; уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6104"/>
+ <source>Too many USB controllers of this type</source>
+ <translation>Слишком много USB контроллеров Ñтого типа</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6208"/>
+ <source>Saved guest size is not available (%Rrc)</source>
+ <translation>Размер Ñохраненной гоÑтевой ÑиÑтемы недоÑтупен (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6232"/>
+ <source>Unsupported saved thumbnail format 0x%08X</source>
+ <translation>Ðеподдерживаемый формат Ñохраненной миниатюры 0x%08X</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6245"/>
+ <source>Saved thumbnail data is not available (%Rrc)</source>
+ <translation>Данные Ñохраненной миниатюры недоÑтупны (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6303"/>
+ <source>Could not convert saved thumbnail to PNG (%Rrc)</source>
+ <translation>Ðевозможно преобразовать Ñохраненную миниатюру в PNG (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6334"/>
+ <source>Saved screenshot data is not available (%Rrc)</source>
+ <translation>Данные Ñохраненного Ñнимка Ñкрана недоÑтупны (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6370"/>
+ <source>Saved screenshot thumbnail data is not available (%Rrc)</source>
+ <translation>Данные миниатюры Ñохраненного Ñнимка Ñкрана недоÑтупны (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6391"/>
+ <location filename="../src-server/MachineImpl.cpp" line="6422"/>
+ <source>CPU hotplug is not enabled</source>
+ <translation>ГорÑчее подключение ЦПУ не включено</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6394"/>
+ <source>CPU id exceeds number of possible CPUs [0:%lu]</source>
+ <translation>ID ЦПУ превыÑил количеÑтво возможных ЦПУ [0:%lu]</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6397"/>
+ <source>CPU %lu is already attached</source>
+ <translation>ЦПУ %lu уже подключен</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6426"/>
+ <source>CPU index exceeds maximum CPU count (must be in range [0:%lu])</source>
+ <translation>Ð˜Ð½Ð´ÐµÐºÑ Ð¦ÐŸÐ£ превыÑил макÑимальное количеÑтво ЦПУ (должен быть в диапазоне [0:%lu])</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6430"/>
+ <source>CPU %lu is not attached</source>
+ <translation>ЦПУ %lu не подключен</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6434"/>
+ <source>It is not possible to detach CPU 0</source>
+ <translation>Ðевозможно отключить ЦПУ 0</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6488"/>
+ <source>The size argument (%lld) is negative</source>
+ <translation>Параметр size (%lld) отрицательный</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6516"/>
+ <source>Could not read log file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно прочитать файл журнала &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6522"/>
+ <source>Could not open log file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно открыть файл журнала &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6551"/>
+ <source>Host PCI attachment only supported with ICH9 chipset</source>
+ <translation>PCI подключение к хоÑту поддерживаетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ Ñ Ñ‡Ð¸Ð¿Ñетом ICH9</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6566"/>
+ <source>Device with host PCI address already attached to this VM</source>
+ <translation>УÑтройÑтво Ñ PCI адреÑом хоÑта уже подключено к Ñтой Ð’Ðœ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6635"/>
+ <source>No host PCI device %08x attached</source>
+ <translation>PCI уÑтройÑтво хоÑта %08x не подключено</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6772"/>
+ <source>The VM autostart feature is not supported on this platform</source>
+ <translation>Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð°Ð²Ñ‚Ð¾Ñтарта Ð’Ðœ не поддерживаетÑÑ Ð½Ð° Ñтой платформе</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6775"/>
+ <location filename="../src-server/MachineImpl.cpp" line="6849"/>
+ <source>The path to the autostart database is not set</source>
+ <translation>Путь к базе данных Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ñтарта не задан</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6779"/>
+ <source>Adding machine &apos;%s&apos; to the autostart database failed with %Rrc</source>
+ <translation>Ðе удалоÑÑŒ добавить машину &apos;%s&apos; в базу данных автоÑтарта %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6780"/>
+ <source>Removing machine &apos;%s&apos; from the autostart database failed with %Rrc</source>
+ <translation>Ðе удалоÑÑŒ удалить машину &apos;%s&apos; из базы данных автоÑтарта %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6846"/>
+ <source>The VM autostop feature is not supported on this platform</source>
+ <translation>Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð°Ð²Ñ‚Ð¾Ñтопа Ð’Ðœ не поддерживаетÑÑ Ð½Ð° Ñтой платформе</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6853"/>
+ <source>Adding machine &apos;%s&apos; to the autostop database failed with %Rrc</source>
+ <translation>Ðе удалоÑÑŒ добавить машину &apos;%s&apos; в базу данных автоÑтопа %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6854"/>
+ <source>Removing machine &apos;%s&apos; from the autostop database failed with %Rrc</source>
+ <translation>Ðе удалоÑÑŒ удалить машину &apos;%s&apos; из базы данных автоÑтопа %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6970"/>
+ <source>Linked clone can only be created from a snapshot</source>
+ <translation>СвÑÐ·Ð°Ð½Ð½Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ Ð¼Ð¾Ð¶ÐµÑ‚ быть Ñоздана только из Ñнимка</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6973"/>
+ <source>Linked clone can only be created for a single machine state</source>
+ <translation>СвÑÐ·Ð°Ð½Ð½Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ Ð¼Ð¾Ð¶ÐµÑ‚ быть Ñоздана только Ð´Ð»Ñ ÐºÐ¾Ð½ÐºÑ€ÐµÑ‚Ð½Ð¾Ð³Ð¾ ÑоÑтоÑÐ½Ð¸Ñ (Ñнимка)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7022"/>
+ <source>Failed to create a worker thread for the MachineMoveVM task</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать поток иÑÐ¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»Ñ Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸ MachineMoveVM</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7413"/>
+ <source>The machine &apos;%s&apos; is in a state which is incompatible with launching a separate UI process</source>
+ <translation>Машина &apos;%s&apos; в ÑоÑÑ‚Ñнии, неÑовмеÑтимым Ñ Ð·Ð°Ð¿ÑƒÑкаемым отдельным UI процеÑÑом</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7422"/>
+ <source>The machine &apos;%s&apos; is already locked by a session (or being locked or unlocked)</source>
+ <translation>Машина &apos;%s&apos; уже заблокирована ÑеÑÑией (или блокируетÑÑ Ð¸Ð»Ð¸ разблокируетÑÑ)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7493"/>
+ <source>Invalid frontend name: &apos;%s&apos;</source>
+ <translation>ÐедопуÑтимое Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŒÑкого интерфейÑа: &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7603"/>
+ <source>Failed to start the machine &apos;%s&apos;. A connection to VBoxSDS cannot be established</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить машину &apos;%s&apos;. Ðевозможно уÑтановить Ñоединение Ñ VBoxSDS</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7619"/>
+ <source>Failed to start the machine &apos;%s&apos;. CoSetProxyBlanket failed</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить машину &apos;%s&apos;. CoSetProxyBlanket вернул ошибку</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7631"/>
+ <source>Failed to start the machine &apos;%s&apos;. Process creation failed</source>
+ <translation>Ðе удалоÑÑŒ запуÑтить машину &apos;%s&apos;. Ðе удалоÑÑŒ Ñоздать процеÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7641"/>
+ <source>Could not launch the VM process for the machine &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно запуÑтить Ð’Ðœ процеÑÑ Ð´Ð»Ñ Ð¼Ð°ÑˆÐ¸Ð½Ñ‹ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7825"/>
+ <source>. More details may be available in &apos;%s&apos;</source>
+ <translation>. Больше деталей может быть в &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7830"/>
+ <source>The virtual machine &apos;%s&apos; has terminated unexpectedly during startup with exit code %d (%#x)%s</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð° &apos;%s&apos; неожиданно завершилаÑÑŒ во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¿ÑƒÑка Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %d (%#x)%s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7834"/>
+ <source>The virtual machine &apos;%s&apos; has terminated unexpectedly during startup because of signal %d%s</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð° &apos;%s&apos; неожиданно завершилаÑÑŒ во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¿ÑƒÑка из-за Ñигнала %d%s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7838"/>
+ <source>The virtual machine &apos;%s&apos; has terminated abnormally (iStatus=%#x)%s</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð° &apos;%s&apos; ненормально завершилаÑÑŒ (iStatus=%#x)%s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7842"/>
+ <source>The virtual machine &apos;%s&apos; has terminated unexpectedly during startup (%Rrc)%s</source>
+ <translation>Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð° &apos;%s&apos; неожиданно завершилаÑÑŒ во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¿ÑƒÑка (%Rrc)%s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7899"/>
+ <source>The machine &apos;%s&apos; with UUID {%s} is inaccessible and cannot be registered</source>
+ <translation>Машина &apos;%s&apos; c UUID {%s} недоÑтупна и не может быть зарегиÑтрирована</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7907"/>
+ <source>The machine &apos;%s&apos; with UUID {%s} is already registered</source>
+ <translation>Машина &apos;%s&apos; Ñ UUID {%s} уже зарегиÑтрирована</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7986"/>
+ <source>Machine state change is in progress. Please retry the operation later.</source>
+ <translation>Изменение ÑоÑтоÑÐ½Ð¸Ñ Ð¼Ð°ÑˆÐ¸Ð½Ñ‹ в процеÑÑе. ПожалуйÑта, попробуйте позже.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="8105"/>
+ <source>The machine is not mutable (state is %s)</source>
+ <translation>Машина не находитÑÑ Ð² изменÑемом ÑоÑтоÑнии (ÑоÑтоÑние %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="8122"/>
+ <source>The machine is not mutable or saved (state is %s)</source>
+ <translation>Машина не находитÑÑ Ð½Ð¸ в изменÑемом ни в Ñохраненном ÑоÑтоÑниÑÑ… (ÑоÑтоÑние %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="8138"/>
+ <source>The machine is not mutable or running (state is %s)</source>
+ <translation>Машина не находитÑÑ Ð½Ð¸ в изменÑемом ни в работающем ÑоÑтоÑниÑÑ… (ÑоÑтоÑние %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="8156"/>
+ <source>The machine is not mutable, saved or running (state is %s)</source>
+ <translation>Машина не находитÑÑ Ð½Ð¸ в изменÑемом ни в Ñохраненном ни в работающем ÑоÑтоÑниÑÑ… (ÑоÑтоÑние %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="8540"/>
+ <source>Could not find a shared folder named &apos;%s&apos;</source>
+ <translation>Ðевозможно найти общую папку Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="8590"/>
+ <location filename="../src-server/MachineImpl.cpp" line="8705"/>
+ <source>Invalid saved state file path &apos;%s&apos; (%Rrc)</source>
+ <translation>ÐедопуÑтимый путь к файлу Ñохраненного ÑоÑтоÑÐ½Ð¸Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9216"/>
+ <source>Duplicate attachments for storage controller &apos;%s&apos;, port %d, device %d of the virtual machine &apos;%s&apos;</source>
+ <translation>Двойное подключение к контроллеру &apos;%s&apos;, порт %d, уÑтройÑтво %d виртуальной машины &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9264"/>
+ <source>A differencing image of snapshot {%RTuuid} could not be found. %ls</source>
+ <translation>Ðе найден разноÑтный образ Ñнимка {%RTuuid}. %ls</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9278"/>
+ <source>Immutable hard disk &apos;%s&apos; with UUID {%RTuuid} cannot be directly attached to snapshot with UUID {%RTuuid} of the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation>ÐеизменÑемый жеÑткий диÑк &apos;%s&apos; Ñ UUID {%RTuuid} не может быть подключен прÑмо к Ñнимку Ñ UUID {%RTuuid} виртуальной машины &apos;%s&apos; (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9287"/>
+ <source>Immutable hard disk &apos;%s&apos; with UUID {%RTuuid} cannot be directly attached to the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation>ÐеизменÑемый жеÑткий диÑк &apos;%s&apos; Ñ UUID {%RTuuid} не может быть подключен прÑмо к виртуальной машине &apos;%s&apos; (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9298"/>
+ <source>Multi-attach hard disk &apos;%s&apos; with UUID {%RTuuid} cannot be directly attached to snapshot with UUID {%RTuuid} of the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation>ЖеÑткий диÑк Ñ Ð¼Ð½Ð¾Ð¶ÐµÑтвенным подключением&apos;%s&apos; Ñ UUID {%RTuuid} не может быть подключен прÑмо к Ñнимку Ñ UUID {%RTuuid} виртуальной машины &apos;%s&apos; (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9307"/>
+ <source>Multi-attach hard disk &apos;%s&apos; with UUID {%RTuuid} cannot be directly attached to the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation>ЖеÑткий диÑк Ñ Ð¼Ð½Ð¾Ð¶ÐµÑтвенным подключением &apos;%s&apos; Ñ UUID {%RTuuid} не может быть подключен прÑмо к виртуальной машине &apos;%s&apos; (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9318"/>
+ <source>Hard disk &apos;%s&apos; with UUID {%RTuuid} cannot be directly attached to the virtual machine &apos;%s&apos; (&apos;%s&apos;) because it has %d differencing child hard disks</source>
+ <translation>ЖеÑткий диÑк &apos;%s&apos; Ñ UUID {%RTuuid} не может быть подключен прÑмо к виртуальной машине &apos;%s&apos; (&apos;%s&apos;) из-за Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ñ Ð´Ð¾Ñ‡ÐµÑ€Ð½Ð¸Ñ… разноÑтных жеÑтких диÑков</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9329"/>
+ <source>Hard disk &apos;%s&apos; with UUID {%RTuuid} is already attached to the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation>ЖеÑткий диÑк &apos;%s&apos; Ñ UUID {%RTuuid} уже подключен к виртуальной машине &apos;%s&apos; (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9340"/>
+ <source>Device &apos;%s&apos; with unknown type is attached to the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation>УÑтройÑтво &apos;%s&apos; неизвеÑтного типа подключено к виртуальной машине &apos;%s&apos; (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9357"/>
+ <source>Device &apos;%s&apos; with unknown bandwidth group &apos;%s&apos; is attached to the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation>УÑтройÑтво &apos;%s&apos; Ñ Ð½ÐµÐ¸Ð·Ð²ÐµÑтной группой полоÑÑ‹ пропуÑÐºÐ°Ð½Ð¸Ñ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¾ к виртуальной машине &apos;%s&apos; (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9431"/>
+ <location filename="../src-server/MachineImpl.cpp" line="9471"/>
+ <source>This machine does not have any snapshots</source>
+ <translation>У Ñтой машины нет Ñнимков</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9444"/>
+ <source>Could not find a snapshot with UUID {%s}</source>
+ <translation>Ðевозможно найти Ñнимок Ñ UUID {%s}</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9481"/>
+ <source>Could not find a snapshot named &apos;%s&apos;</source>
+ <translation>Ðевозможно найти Ñнимок Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9515"/>
+ <location filename="../src-server/MachineImpl.cpp" line="9547"/>
+ <source>Could not find a storage controller named &apos;%s&apos;</source>
+ <translation>Ðевозможно найти контроллер Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9694"/>
+ <source>Could not rename the directory &apos;%s&apos; to &apos;%s&apos; to save the settings file (%Rrc)</source>
+ <translation>Ðевозможно переименовать директорию &apos;%s&apos; в &apos;%s&apos; Ð´Ð»Ñ ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° наÑтроек (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9732"/>
+ <source>Could not rename the settings file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно переименовать файл наÑтроек &apos;%s&apos; в &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9828"/>
+ <source>Could not create a directory &apos;%s&apos; to save the settings file (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать директорию &apos;%s&apos; Ð´Ð»Ñ ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° наÑтроек (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9841"/>
+ <source>Could not create the settings file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать файл наÑтроек &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9893"/>
+ <source>The machine is not accessible, so cannot save settings</source>
+ <translation>Машина недоÑтупна, поÑтому невозможно Ñохранить наÑтройки</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="10743"/>
+ <source>Collecting locking information for all attached media failed</source>
+ <translation>Ðе удалоÑÑŒ Ñобрать информациию о блокировках Ð´Ð»Ñ Ð²Ñех подключенных ноÑителей</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="10755"/>
+ <source>Locking of attached media failed</source>
+ <translation>Ðе удалоÑÑŒ заблокировать подключенный ноÑитель</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="10790"/>
+ <source>Skipping attachment without medium</source>
+ <translation>ПропуÑк Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð±ÐµÐ· ноÑителÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="10793"/>
+ <source>Skipping medium &apos;%s&apos;</source>
+ <translation>ПропуÑк ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="10803"/>
+ <source>Creating differencing hard disk for &apos;%s&apos;</source>
+ <translation>Создание разноÑтного жеÑткого диÑка Ð´Ð»Ñ &apos;%s&apos;</translation>
+ </message>
+</context>
+<context>
+ <name>Machine::ClientToken</name>
+ <message>
+ <location filename="../src-server/ClientToken.cpp" line="214"/>
+ <source>Cannot create IPC semaphore. Most likely your host kernel lacks support for SysV IPC. Check the host kernel configuration for CONFIG_SYSVIPC=y</source>
+ <translation>Ðевозможно Ñоздать IPC Ñемафор. Ðаиболее вероÑтно Ñдро хоÑта не поддерживает SysV IPC. Проверьте конфигурацию Ñдра хоÑта на наличие CONFIG_SYSVIPC=y</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ClientToken.cpp" line="226"/>
+ <source>Cannot create IPC semaphore because the system limit for the maximum number of semaphore sets (SEMMNI), or the system wide maximum number of semaphores (SEMMNS) would be exceeded. The current set of SysV IPC semaphores can be determined from the file /proc/sysvipc/sem</source>
+ <translation>Ðевозможно Ñоздать IPC Ñемафор, потому что уÑтановлено ограничение ÑиÑтемы на макÑимальное количеÑтво Ñемафоров (SEMMNI) или превышено макÑимальное количеÑтво Ñемафоров вообще в ÑиÑтеме (SEMMNS). Текущее количеÑтво SysV IPC Ñемафоров может быть получено через /proc/sysvipc/sem</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ClientToken.cpp" line="233"/>
+ <source>Cannot create IPC semaphore because the system-imposed limit on the maximum number of allowed semaphores or semaphore identifiers system-wide would be exceeded</source>
+ <translation>Ðевозможно Ñоздать IPC Ñемафор, потому что превышено макÑимальное количеÑтво допуÑтимых Ñемафоров или идентификаторов Ñемафоров, наложенное ÑиÑтемой</translation>
+ </message>
+</context>
+<context>
+ <name>MachineCloneVM</name>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="875"/>
+ <source>The source machine is mutable</source>
+ <translation>ИÑÑ…Ð¾Ð´Ð½Ð°Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð° изменÑемаÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1039"/>
+ <source>Cloning Machine</source>
+ <translation>Клонирование машины</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1043"/>
+ <source>Initialize Cloning</source>
+ <translation>Ð˜Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ ÐºÐ»Ð¾Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1050"/>
+ <source>Could not create machine clone thread (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать поток Ð´Ð»Ñ ÐºÐ»Ð¾Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¼Ð°ÑˆÐ¸Ð½Ñ‹ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1120"/>
+ <source>Could not find data to snapshots &apos;%s&apos;</source>
+ <translation>Ðевозможно найти данные Ñнимков &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1198"/>
+ <source>Cloning Disk &apos;%ls&apos; ...</source>
+ <translation>Клонирование диÑка &apos;%s&apos;...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1492"/>
+ <source>Could not create snapshots folder &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать папку Ñнимков &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1503"/>
+ <source>Copy save state file &apos;%s&apos; ...</source>
+ <translation>Копирование файла Ñохраненного ÑоÑтоÑÐ½Ð¸Ñ &apos;%s&apos;...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1513"/>
+ <source>Could not copy state file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñкопировать файл ÑоÑтоÑÐ½Ð¸Ñ &apos;%s&apos; в &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1538"/>
+ <source>Copy NVRAM file &apos;%s&apos; ...</source>
+ <translation>Копирование NVRAM файла &apos;%s&apos;...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1550"/>
+ <source>Could not copy NVRAM file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñкопировать NVRAM файл &apos;%s&apos; в &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1563"/>
+ <source>Create Machine Clone &apos;%s&apos; ...</source>
+ <translation>Создание клона машины &apos;%s&apos;...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1643"/>
+ <source>Could not delete file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно удалить файл &apos;%s&apos; (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>MachineCloneVMPrivate</name>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="231"/>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="268"/>
+ <source>Could not query file size of &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно запроÑить размер файла &apos;%s&apos; (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>MachineDebugger</name>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="141"/>
+ <source>Writing the sample report to &apos;%s&apos; failed with %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запиÑать образец отчета в &apos;%s&apos; %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="249"/>
+ <source>EMR3SetExecutionPolicy failed with %Rrc</source>
+ <translation>EMR3SetExecutionPolicy завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="348"/>
+ <source>PATM not present</source>
+ <translation>нет PATM</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="375"/>
+ <source>CASM not present</source>
+ <translation>нет CASM</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="466"/>
+ <source>%s returned %Rrc</source>
+ <translation>%s возвратил %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="473"/>
+ <source>%s returns too much data</source>
+ <translation>%s возвращает Ñлишком много данных</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="645"/>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="676"/>
+ <source>DBGFR3OSQueryNameAndVersion failed with %Rrc</source>
+ <translation>DBGFR3OSQueryNameAndVersion завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="734"/>
+ <source>%u is out of range [2..20000]</source>
+ <translation>%u вышел за границы диапазона [2..20000]</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="747"/>
+ <source>TMR3SetWarpDrive(, %u) failed with rc=%Rrc</source>
+ <translation>TMR3SetWarpDrive(, %u) завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ rc=%Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="809"/>
+ <source>The compression parameter must be empty</source>
+ <translation>Параметр ÑÐ¶Ð°Ñ‚Ð¸Ñ Ð´Ð¾Ð»Ð¶ÐµÐ½ быть пуÑтым</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="820"/>
+ <source>DBGFR3CoreWrite failed with %Rrc</source>
+ <translation>DBGFR3CoreWrite завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="987"/>
+ <source>DBGFR3Info failed with %Rrc</source>
+ <translation>DBGFR3Info завершено Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1007"/>
+ <source>DBGFR3InjectNMI failed with %Rrc</source>
+ <translation>DBGFR3InjectNMI завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1024"/>
+ <source>DBGFR3LogModifyFlags failed with %Rrc</source>
+ <translation>DBGFR3LogModifyFlags завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1041"/>
+ <source>DBGFR3LogModifyGroups failed with %Rrc</source>
+ <translation>DBGFR3LogModifyGroups завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1058"/>
+ <source>DBGFR3LogModifyDestinations failed with %Rrc</source>
+ <translation>DBGFR3LogModifyDestinations завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1162"/>
+ <source>Plug-in &apos;%s&apos; was not found</source>
+ <translation>Плагин &apos;%s&apos; не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1164"/>
+ <source>Error unloading &apos;%s&apos;: %Rrc</source>
+ <translation>Ошибка при выгрузке &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1200"/>
+ <source>DBGFR3OSDetect failed with %Rrc</source>
+ <translation>DBGFR3OSDetect завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1239"/>
+ <source>Too much log available, must use the maxMessages parameter to restrict.</source>
+ <translation>ДоÑтупно Ñлишком много журналов. Ðужно иÑпользовать параметр maxMessages Ð´Ð»Ñ Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1247"/>
+ <source>The dmesg interface isn&apos;t implemented by guest OS digger, or detectOS() has not been called.</source>
+ <translation>Ð˜Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ dmesg не реализован отладчиком гоÑтевой ОС или не вызван detectOS().</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1304"/>
+ <source>Register &apos;%s&apos; was not found</source>
+ <translation>РегиÑÑ‚Ñ€ &apos;%s&apos; не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1306"/>
+ <source>Invalid CPU ID: %u</source>
+ <translation>ÐедопуÑтимое ID ЦПУ: %u</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1309"/>
+ <source>DBGFR3RegNmQuery failed with rc=%Rrc querying register &apos;%s&apos; with default cpu set to %u</source>
+ <translation>DBGFR3RegNmQuery завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ rc=%Rrc Ð·Ð°Ð¿Ñ€Ð°ÑˆÐ¸Ð²Ð°Ñ Ñ€ÐµÐ³Ð¸ÑÑ‚Ñ€ &apos;%s&apos; Ñ Ð¦ÐŸÐ£ по умолчанию уÑтановленному в %u</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1363"/>
+ <source>DBGFR3RegNmQueryAll failed with %Rrc</source>
+ <translation>DBGFR3RegNmQueryAll завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1371"/>
+ <source>DBGFR3RegNmQueryAllCount failed with %Rrc</source>
+ <translation>DBGFR3RegNmQueryAllCount завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1513"/>
+ <source>DBGFR3StackWalkBegin failed with %Rrc</source>
+ <translation>DBGFR3StackWalkBegin завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1525"/>
+ <source>Suspending the VM failed with %Rrc
+</source>
+ <translation>Ðе удалоÑÑŒ приоÑтановить Ð’Ðœ %Rrc
+</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1542"/>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1560"/>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1579"/>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1623"/>
+ <source>Machine is not running</source>
+ <translation>Машина не запущена</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1650"/>
+ <source>Creating guest sample report...</source>
+ <translation>Создание образца отчета гоÑтевой ÑиÑтемы...</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1672"/>
+ <source>A sample report is already in progress</source>
+ <translation>Образец отчета уже в процеÑÑе</translation>
+ </message>
+</context>
+<context>
+ <name>MachineMoveVM</name>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="136"/>
+ <source>The destination path exceeds the maximum value.</source>
+ <translation>Размер пути Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¿Ñ€ÐµÐ²Ñ‹Ñил макÑимальное значение.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="166"/>
+ <source>Unable to determine free space at move destination (&apos;%s&apos;): %Rrc</source>
+ <translation>Ðевозможно определить Ñвободное меÑто Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ½Ð¾Ñа в назначение (&apos;%s&apos;): %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="181"/>
+ <source>Can&apos;t create a test file test.txt in the %s. Check the access rights of the destination folder.</source>
+ <translation>Ðевозможно Ñоздать теÑтовый файл test.txt в %s. Проверьте права доÑтупа папки назначениÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="397"/>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="437"/>
+ <source>Failed to get file size for &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ получить размер файла Ð´Ð»Ñ &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="459"/>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="503"/>
+ <source>Insufficient disk space available (%RTfoff needed, %RTfoff free)</source>
+ <translation>ÐедоÑтаточно Ñвободного меÑта на диÑке (%RTfoff нужно, %RTfoff Ñвободно)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="519"/>
+ <source>Moving Machine</source>
+ <translation>Перемещение машины</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="523"/>
+ <source>Initialize Moving</source>
+ <translation>Ð˜Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="527"/>
+ <source>Couldn&apos;t correctly setup the progress object for moving VM operation</source>
+ <translation>Ðевозможно корректно наÑтроить объект прогреÑÑа Ð´Ð»Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ð¸ Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð’Ðœ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="692"/>
+ <source>Could not create snapshots folder &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать папку Ñнимков &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="704"/>
+ <source>Copy the save state file &apos;%s&apos; ...</source>
+ <translation>Копирование файла Ñохраненного ÑоÑтоÑÐ½Ð¸Ñ &apos;%s&apos;...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="714"/>
+ <source>Could not copy state file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñкопировать файл ÑоÑтоÑÐ½Ð¸Ñ &apos;%s&apos; в &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="734"/>
+ <source>Copy the NVRAM file &apos;%s&apos; ...</source>
+ <translation>Копирование NVRAM файла &apos;%s&apos;...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="744"/>
+ <source>Could not copy NVRAM file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñкопировать NVRAM файл &apos;%s&apos; в &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="788"/>
+ <source>Copy Machine settings file &apos;%s&apos; ...</source>
+ <translation>Копирование файла наÑтроек машины &apos;%s&apos;...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="802"/>
+ <source>Could not create a home machine folder &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать домашнюю папку машины &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="827"/>
+ <source>Could not copy the setting file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñкопировать файл наÑтроек &apos;%s&apos; в &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="864"/>
+ <source>Could not create log folder &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать папку журнала &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="882"/>
+ <source>Copying the log file &apos;%s&apos; ...</source>
+ <translation>Копирование файла журнала &apos;%s&apos;...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="892"/>
+ <source>Could not copy the log file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñкопировать файл журнала &apos;%s&apos; в &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1002"/>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1103"/>
+ <source>Skip the empty operation %d...</source>
+ <translation>ÐŸÑ€Ð¾Ð¿ÑƒÑ Ð¿ÑƒÑтой операции %d...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1029"/>
+ <source>Rollback scenario: can&apos;t delete new destination folder.</source>
+ <translation>Сценарий отката: невозможно удалить новую папку назначениÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1165"/>
+ <source>Moving medium &apos;%ls&apos; ...</source>
+ <translation>Перемещение ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%ls&apos;...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1173"/>
+ <source>Moving medium &apos;%ls&apos; back...</source>
+ <translation>Возвращение ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%ls&apos;...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1326"/>
+ <source>Folder &apos;%s&apos; doesn&apos;t exist (%Rrc)</source>
+ <translation>Папка &apos;%s&apos; не ÑущеÑтвует (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1330"/>
+ <source>Could not open folder &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно открыть папку &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1343"/>
+ <source>Deleting file %s...</source>
+ <translation>Удаление файла %s...</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1349"/>
+ <source>Could not delete file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно удалить файл &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1385"/>
+ <source>Could not get the size of file &apos;%s&apos;: %Rrc</source>
+ <translation>Ðевозможно получить размер файла &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1571"/>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1603"/>
+ <source>Could not get file size of &apos;%s&apos;: %Rrc</source>
+ <translation>Ðевозможно получить размер &apos;%s&apos;: %Rrc</translation>
+ </message>
+</context>
+<context>
+ <name>Medium</name>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1140"/>
+ <location filename="../src-server/MediumImpl.cpp" line="1236"/>
+ <source>Accessibility check was not yet performed</source>
+ <translation>Проверка на доÑтупноÑÑ‚ÑŒ еще не проведена</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1695"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3315"/>
+ <source>Failed to create medium lock list for &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать ÑпиÑок блокировок ноÑителей &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1701"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3330"/>
+ <source>Failed to lock media &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ заблокировать ноÑитель &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1880"/>
+ <source>Cannot change the type of DVD medium &apos;%s&apos;</source>
+ <translation>Ðевозможно изменить тип ноÑÐ¸Ñ‚ÐµÐ»Ñ DVD &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1887"/>
+ <source>Cannot change the type of floppy medium &apos;%s&apos;</source>
+ <translation>Ðевозможно изменить тип ноÑÐ¸Ñ‚ÐµÐ»Ñ Ñ„Ð»Ð¾Ð¿Ð¿Ð¸ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1893"/>
+ <source>Cannot change the type of medium &apos;%s&apos; because it is a differencing medium</source>
+ <translation>Ðевозможно изменить тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;, потому что Ñто разноÑтный диÑк</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="1907"/>
+ <source>Cannot change the type of medium &apos;%s&apos; because it is attached to %d virtual machines</source>
+ <translation>
+ <numerusform>Ðевозможно изменить тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;, потому что он подключен к %d виртуальной машине</numerusform>
+ <numerusform>Ðевозможно изменить тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;, потому что он подключен к %d виртуальным машинам</numerusform>
+ <numerusform>Ðевозможно изменить тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;, потому что он подключен к %d виртуальным машинам</numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="1930"/>
+ <source>Cannot change type for medium &apos;%s&apos; since it has %d child media</source>
+ <translation>
+ <numerusform>Ðевозможно изменить тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;, потому что у него еÑÑ‚ÑŒ %d дочерний диÑк</numerusform>
+ <numerusform>Ðевозможно изменить тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;, потому что у него еÑÑ‚ÑŒ %d дочерних диÑка</numerusform>
+ <numerusform>Ðевозможно изменить тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;, потому что у него еÑÑ‚ÑŒ %d дочерних диÑков</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1937"/>
+ <source>Cannot change type for medium &apos;%s&apos; to &apos;Shareable&apos; since it is a dynamic medium storage unit</source>
+ <translation>Ðевозможно изменить тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s в &quot;С общим доÑтупом&apos;, потому что Ñто динамичеÑки раÑширÑющийÑÑ Ð¾Ð±Ñ€Ð°Ð·</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1946"/>
+ <source>Cannot change type for medium &apos;%s&apos; to &apos;Readonly&apos; since it is a hard disk</source>
+ <translation>Ðевозможно изменить тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s в &quot;Только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ&apos;, потому что Ñто жеÑткий диÑк</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1969"/>
+ <source>Cannot change type for medium &apos;%s&apos;: the media type &apos;MultiAttach&apos; can only be used on media registered with a machine that was created with VirtualBox 4.0 or later</source>
+ <translation>Ðевозможно изменить тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s: тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &quot;МножеÑтвенное подключение&quot; может иÑпользоватьÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ Ñ Ð½Ð¾ÑителÑми, зарегиÑтрированных в машинах, Ñозданных VirtualBox-4.0 или позже</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2083"/>
+ <source>Medium &apos;%s&apos; is not differencing</source>
+ <translation>ÐоÑитель &apos;%s&apos; не ÑвлÑетÑÑ Ñ€Ð°Ð·Ð½Ð¾Ñтным диÑком</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2168"/>
+ <source>Argument %s is invalid</source>
+ <translation>ÐедопуÑтимый аргумент %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2310"/>
+ <source>Counter overflow</source>
+ <translation>Переполнение Ñчетчика</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2362"/>
+ <source>Counter underflow</source>
+ <translation>Переполнение Ñчетчика Ñнизу</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2383"/>
+ <source>Medium &apos;%s&apos; is not locked for reading</source>
+ <translation>ÐоÑитель &apos;%s&apos; не заблокирован Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2483"/>
+ <source>Medium &apos;%s&apos; is not locked for writing</source>
+ <translation>ÐоÑитель &apos;%s&apos; не заблокирован Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2523"/>
+ <location filename="../src-server/MediumImpl.cpp" line="2573"/>
+ <location filename="../src-server/MediumImpl.cpp" line="2639"/>
+ <source>Property &apos;%s&apos; does not exist</source>
+ <translation>СвойÑтво &apos;%s&apos; не ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2687"/>
+ <source>The medium size argument (%lld) is negative</source>
+ <translation>Ðргумент размера ноÑÐ¸Ñ‚ÐµÐ»Ñ (%lld) отрицательный</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2710"/>
+ <source>Medium format &apos;%s&apos; does not support dynamic storage creation</source>
+ <translation>Формат ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; не поддерживает Ñоздание динамичеÑкого диÑка</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2716"/>
+ <source>Medium format &apos;%s&apos; does not support fixed storage creation</source>
+ <translation>Формат ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; не поддерживает Ñоздание фикÑированного диÑка</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2722"/>
+ <location filename="../src-server/MediumImpl.cpp" line="2884"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3057"/>
+ <source>Medium variant &apos;formatted&apos; applies to floppy images only</source>
+ <translation>Вариант ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;форматирован&apos; применимо только к образам флоппи-диÑков</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2731"/>
+ <source>Creating fixed medium storage unit &apos;%s&apos;</source>
+ <translation>Создание фикÑированного ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2732"/>
+ <source>Creating dynamic medium storage unit &apos;%s&apos;</source>
+ <translation>Создание динамичеÑки раÑширÑющегоÑÑ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2803"/>
+ <source>Medium type of &apos;%s&apos; is Writethrough</source>
+ <translation>Тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; - Ñквозной</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2807"/>
+ <source>Medium type of &apos;%s&apos; is Shareable</source>
+ <translation>Тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; - Ñ Ð¾Ð±Ñ‰Ð¸Ð¼ доÑтупом</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2811"/>
+ <source>Medium type of &apos;%s&apos; is Readonly</source>
+ <translation>Тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; - только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2848"/>
+ <source>Could not lock medium when creating diff &apos;%s&apos;</source>
+ <translation>Ðевозможно заблокировать ноÑитель во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ€Ð°Ð·Ð½Ð¾Ñтного ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3017"/>
+ <location filename="../src-server/MediumImpl.cpp" line="6935"/>
+ <source>Failed to lock source media &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ заблокировать иÑходный ноÑитель &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3028"/>
+ <location filename="../src-server/MediumImpl.cpp" line="6812"/>
+ <location filename="../src-server/MediumImpl.cpp" line="6946"/>
+ <source>Failed to lock target media &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ заблокировать целевой ноÑитель &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3035"/>
+ <location filename="../src-server/MediumImpl.cpp" line="6953"/>
+ <source>Creating clone medium &apos;%s&apos;</source>
+ <translation>Создание клонированного ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3126"/>
+ <source>Medium &apos;%s&apos; can&apos;t be moved. Destination path is empty.</source>
+ <translation>Ðевозможно перемеÑтить ноÑитель &apos;%s&apos;. Путь Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¿ÑƒÑÑ‚.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3193"/>
+ <source>Medium &apos;%s&apos; has RAW type. &quot;Move&quot; operation isn&apos;t supported for this type.</source>
+ <translation>ÐоÑитель &apos;%s&apos; RAW типа. ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ &quot;перемещение&quot; не поддерживаетÑÑ Ð´Ð»Ñ Ñтого типа.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3215"/>
+ <source>The given path &apos;%s&apos; is an existing file. Delete or rename this file.</source>
+ <translation>Данный путь &apos;%s&apos; указывает на ÑущеÑтвующий файл. Удалите или переименуйте файл.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3223"/>
+ <source>Medium &apos;%s&apos; isn&apos;t a file object. &quot;Move&quot; operation isn&apos;t supported.</source>
+ <translation>ÐоÑитель &apos;%s&apos; не ÑвлÑетÑÑ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð¼ файла. ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ &quot;перемещение&quot; не поддерживаетÑÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3231"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3398"/>
+ <location filename="../src-server/MediumImpl.cpp" line="7860"/>
+ <source>The given path &apos;%s&apos; is not fully qualified</source>
+ <translation>Данный путь &apos;%s&apos; не полноÑтью определен</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3245"/>
+ <source>Medium &apos;%s&apos; is already in the correct location</source>
+ <translation>ÐоÑитель &apos;%s&apos; уже в правильном меÑте</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3288"/>
+ <source>At least the VM &apos;%s&apos; to whom this medium &apos;%s&apos; attached has currently an opened session. Stop all VMs before relocating this medium</source>
+ <translation>Как минимум у Ð’Ðœ &apos;%s&apos;, к которой подключен ноÑитель &apos;%s&apos;, открыта ÑеÑÑÐ¸Ñ Ð² наÑтоÑщее времÑ. ОÑтановите вÑе Ð’Ðœ перед перемещением ноÑителÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3337"/>
+ <source>Moving medium &apos;%s&apos;</source>
+ <translation>Перемещение ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3407"/>
+ <source>The given path &apos;%s&apos; is not an existing file. New location is invalid.</source>
+ <translation>Данный путь &apos;%s&apos; не указывает на ÑущеÑтвующий файл. Ðовое меÑтоположение недейÑтвительно.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3446"/>
+ <source>At least the VM &apos;%s&apos; to whom this medium &apos;%s&apos; attached has currently an opened session. Stop all VMs before set location for this medium</source>
+ <translation>Как минимум у Ð’Ðœ &apos;%s&apos;, к которой подключен ноÑитель &apos;%s&apos;, открыта ÑеÑÑÐ¸Ñ Ð² наÑтоÑщее времÑ. ОÑтановите вÑе Ð’Ðœ перед уÑтновкой нового меÑÑ‚Ð¾Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð½Ð¾ÑителÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3512"/>
+ <source>Failed to lock media when compacting &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ заблокировать ноÑитель во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¶Ð°Ñ‚Ð¸Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3519"/>
+ <source>Compacting medium &apos;%s&apos;</source>
+ <translation>Сжатие ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3570"/>
+ <source>Failed to create medium lock list when resizing &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать ÑпиÑок блокировок ноÑÐ¸Ñ‚ÐµÐ»Ñ Ð²Ð¾ Ð²Ñ€ÐµÐ¼Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€Ð° &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3576"/>
+ <source>Failed to lock media when resizing &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ заблокировать ноÑитель во Ð²Ñ€ÐµÐ¼Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€Ð° &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3590"/>
+ <location filename="../src-server/MediumImpl.cpp" line="6425"/>
+ <source>Resizing medium &apos;%s&apos;</source>
+ <translation>Изменение размера ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3636"/>
+ <source>Medium type of &apos;%s&apos; is not differencing</source>
+ <translation>Тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; не разноÑтный</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3665"/>
+ <source>Failed to lock media when resetting &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ заблокировать ноÑитель во Ð²Ñ€ÐµÐ¼Ñ ÑброÑа &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3672"/>
+ <source>Resetting differencing medium &apos;%s&apos;</source>
+ <translation>Ð¡Ð±Ñ€Ð¾Ñ Ñ€Ð°Ð·Ð½Ð¾Ñтного ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3718"/>
+ <source>Cannot encrypt DVD or Floppy medium &apos;%s&apos;</source>
+ <translation>Ðевозможно зашифровать ноÑитель DVD или флоппи &apos;%s&apos;</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="3724"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3777"/>
+ <source>Cannot encrypt medium &apos;%s&apos; because it is attached to %d virtual machines</source>
+ <translation>
+ <numerusform>Ðевозможно зашифровать ноÑитель &apos;%s&apos;, потому что он подключен к %d виртуальной машине</numerusform>
+ <numerusform>Ðевозможно зашифровать ноÑитель &apos;%s&apos;, потому что он подключен к %d виртуальным машинам</numerusform>
+ <numerusform>Ðевозможно зашифровать ноÑитель &apos;%s&apos;, потому что он подключен к %d виртуальным машинам</numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="3729"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3785"/>
+ <source>Cannot encrypt medium &apos;%s&apos; because it has %d children</source>
+ <translation>
+ <numerusform>Ðевозможно зашифровать ноÑитель &apos;%s&apos;, потому что у него %d дочерний ноÑитель</numerusform>
+ <numerusform>Ðевозможно зашифровать ноÑитель &apos;%s&apos;, потому что у него %d дочерних ноÑителÑ</numerusform>
+ <numerusform>Ðевозможно зашифровать ноÑитель &apos;%s&apos;, потому что у него %d дочерних ноÑителей</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3754"/>
+ <source>Failed to lock media for encryption &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ заблокировать ноÑитель Ð´Ð»Ñ ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3797"/>
+ <source>Encrypting medium</source>
+ <translation>Шифрование ноÑителÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3800"/>
+ <source>Decrypting medium</source>
+ <translation>РаÑшифровка ноÑителÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3869"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3945"/>
+ <location filename="../src-server/MediumImpl.cpp" line="8437"/>
+ <source>Retrieving encryption settings of the image failed because the encryption plugin could not be loaded (%s)</source>
+ <translation>Ðе удалоÑÑŒ получить наÑтройки шифрованиÑ, потому что невозможно загрузить плагин ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ (%s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3874"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3950"/>
+ <location filename="../src-server/MediumImpl.cpp" line="8442"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10671"/>
+ <source>Encryption is not supported because the extension pack &apos;%s&apos; is missing the encryption plugin (old extension pack installed?)</source>
+ <translation>Шифрование не поддерживаетÑÑ, потому что плагин ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¾Ñ‚ÑутÑтвует в пакете раÑширений &apos;%s&apos; (уÑтановлен Ñтарый пакет раÑширений?)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3879"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3955"/>
+ <location filename="../src-server/MediumImpl.cpp" line="8447"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10676"/>
+ <source>Encryption is not supported because the extension pack &apos;%s&apos; is missing</source>
+ <translation>Шифрование не поддерживаетÑÑ, потому что отÑутÑтвует пакет раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3892"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3972"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10744"/>
+ <source>Failed to load the encryption filter: %s</source>
+ <translation>Ðе удалоÑÑŒ загрузить фильтр шифрованиÑ: &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3898"/>
+ <source>Image is configured for encryption but doesn&apos;t has a KeyId set</source>
+ <translation>Образ Ñконфигурирован Ð´Ð»Ñ ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ, но у него не уÑтановлен KeyId</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3907"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3978"/>
+ <location filename="../src-server/MediumImpl.cpp" line="8480"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10837"/>
+ <source>Encryption is not supported because extension pack support is not built in</source>
+ <translation>Шифрование не поддерживаетÑÑ, потому что поддержка пакетов раÑширений не вÑтроена</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3927"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10707"/>
+ <source>The image is not configured for encryption</source>
+ <translation>Образ не Ñконфигурирован Ð´Ð»Ñ ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3931"/>
+ <source>The given password must not be empty</source>
+ <translation>Данный пароль не должен быть пуÑтым</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3969"/>
+ <source>The given password is incorrect</source>
+ <translation>Данный пароль неправильный</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3992"/>
+ <source>Write access denied: read-only</source>
+ <translation>ДоÑтуп на запиÑÑŒ запрещен: только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3996"/>
+ <source>Password given for unencrypted medium</source>
+ <translation>Пароль дан Ð´Ð»Ñ Ð½ÐµÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð½Ð¾Ð³Ð¾ ноÑителÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3998"/>
+ <source>Password needed for encrypted medium</source>
+ <translation>ТребуетÑÑ Ð¿Ð°Ñ€Ð¾Ð»ÑŒ Ð´Ð»Ñ ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð½Ð¾Ð³Ð¾ ноÑителÑ</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="4437"/>
+ <source>Cannot attach medium &apos;%s&apos; {%RTuuid}: %u differencing child media are being created</source>
+ <translation>
+ <numerusform>Ðевозможно подключить ноÑитель &apos;%s&apos; {%RTuuid}: ÑоздаетÑÑ %u разноÑтный дочерний ноÑитель</numerusform>
+ <numerusform>Ðевозможно подключить ноÑитель &apos;%s&apos; {%RTuuid}: ÑоздаетÑÑ %u разноÑтных дочерних ноÑителÑ</numerusform>
+ <numerusform>Ðевозможно подключить ноÑитель &apos;%s&apos; {%RTuuid}: ÑоздаетÑÑ %u разноÑтных дочерних ноÑителей</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="4480"/>
+ <source>Cannot attach medium &apos;%s&apos; {%RTuuid}: medium is already associated with the current state of machine uuid {%RTuuid}!</source>
+ <translation>Ðевозможно подключить ноÑитель &apos;%s&apos; {%RTuuid}: ноÑитель уже ÑвÑзан Ñ Ñ‚ÐµÐºÑƒÑ‰Ð¸Ð¼ ÑоÑтоÑнием машины Ñ uuid {%RTuuid}!</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="4509"/>
+ <source>Cannot attach medium &apos;%s&apos; {%RTuuid} from snapshot &apos;%RTuuid&apos;: medium is already in use by this snapshot!</source>
+ <translation>Ðевозможно подключить ноÑитель &apos;%s&apos; {%RTuuid} из Ñнимка &apos;%RTuuid&apos;: ноÑитель уже иÑпользуетÑÑ Ñтим Ñнимком!</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5159"/>
+ <source>Medium &apos;%s&apos; is attached to a virtual machine with UUID {%RTuuid}. No differencing media based on it may be created until it is detached</source>
+ <translation>ÐоÑитель &apos;%s&apos; подключен к виртуальной машине Ñ UUID {%RTuuid}. Ðевозможно Ñоздать разноÑтные ноÑители на оÑнове Ñтого пока он не отключен</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5177"/>
+ <source>Creating differencing medium storage unit &apos;%s&apos;</source>
+ <translation>Создание разноÑтного ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="5324"/>
+ <source>Medium &apos;%s&apos; cannot be closed because it is still attached to %d virtual machines</source>
+ <translation>
+ <numerusform>ÐоÑитель &apos;%s&apos; не может быть закрыт, потому что он еще подключен к %d виртуальной машине</numerusform>
+ <numerusform>ÐоÑитель &apos;%s&apos; не может быть закрыт, потому что он еще подключен к %d виртуальным машинам</numerusform>
+ <numerusform>ÐоÑитель &apos;%s&apos; не может быть закрыт, потому что он еще подключен к %d виртуальным машинам</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5417"/>
+ <source>Medium format &apos;%s&apos; does not support storage deletion</source>
+ <translation>Формат ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; не поддерживает удаление диÑка</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="5476"/>
+ <source>Cannot delete storage: medium &apos;%s&apos; is still attached to the following %d virtual machine(s): %s</source>
+ <translation>
+ <numerusform>Ðевозможно удалить файл ноÑителÑ: ноÑитель &apos;%s&apos; вÑе еще подключен к %d виртуальной машине: %s</numerusform>
+ <numerusform>Ðевозможно удалить файл ноÑителÑ: ноÑитель &apos;%s&apos; вÑе еще подключен к %d виртуальным машинам: %s</numerusform>
+ <numerusform>Ðевозможно удалить файл ноÑителÑ: ноÑитель &apos;%s&apos; вÑе еще подключен к %d виртуальным машинам: %s</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5527"/>
+ <source>Failed to lock media when deleting &apos;%s&apos;</source>
+ <translation>Ðевозможно заблокировать ноÑитель во Ð²Ñ€ÐµÐ¼Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5555"/>
+ <source>Deleting medium storage unit &apos;%s&apos;</source>
+ <translation>Удаление файла ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5736"/>
+ <location filename="../src-server/MediumImpl.cpp" line="5875"/>
+ <source>Media &apos;%s&apos; and &apos;%s&apos; are unrelated</source>
+ <translation>ÐоÑители &apos;%s&apos; и &apos;%s&apos; не ÑвÑзаны</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5931"/>
+ <location filename="../src-server/MediumImpl.cpp" line="5962"/>
+ <location filename="../src-server/MediumImpl.cpp" line="5985"/>
+ <source>Medium &apos;%s&apos; involved in the merge operation has more than one child medium (%d)</source>
+ <translation>У ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;, иÑпользуемого в операции объединениÑ, еÑÑ‚ÑŒ более одного дочернего ноÑÐ¸Ñ‚ÐµÐ»Ñ (%d)</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="5945"/>
+ <location filename="../src-server/MediumImpl.cpp" line="5991"/>
+ <source>Medium &apos;%s&apos; is attached to %d virtual machines</source>
+ <translation>
+ <numerusform>ÐоÑитель &apos;%s&apos; подключен к %d виртуальной машине</numerusform>
+ <numerusform>ÐоÑитель &apos;%s&apos; подключен к %d виртуальным машинам</numerusform>
+ <numerusform>ÐоÑитель &apos;%s&apos; подключен к %d виртуальным машинам</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5949"/>
+ <location filename="../src-server/MediumImpl.cpp" line="5968"/>
+ <source>Medium &apos;%s&apos; is immutable</source>
+ <translation>ÐоÑитель &apos;%s&apos; неизменÑемый</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5953"/>
+ <location filename="../src-server/MediumImpl.cpp" line="5972"/>
+ <source>Medium &apos;%s&apos; is multi-attach</source>
+ <translation>ÐоÑитель &apos;%s&apos; множеÑтвенного подключениÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6110"/>
+ <source>Failed to lock media when merging to &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ заблокировать ноÑитель во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6256"/>
+ <location filename="../src-server/MediumImpl.cpp" line="9045"/>
+ <source>Merging medium &apos;%s&apos; to &apos;%s&apos;</source>
+ <translation>Объединение ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; в &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6261"/>
+ <source>Resizing medium &apos;%s&apos; before merge</source>
+ <translation>Изменение размера ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; перед объединением</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6544"/>
+ <source>Could not update medium UUID references to parent &apos;%s&apos; (%s)</source>
+ <translation>Ðевозможно обновить UUID ÑÑылки на родителей &apos;%s&apos; (%s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6599"/>
+ <source>Failed to add &apos;%s&apos; to output (%Rrc)</source>
+ <translation>Ðевозможно добавить &apos;%s&apos; на выход (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6603"/>
+ <source>RTVfsCreateProgressForFile failed when processing &apos;%s&apos; (%Rrc)</source>
+ <translation>RTVfsCreateProgressForFile завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6606"/>
+ <source>VDCreateVfsFileFromDisk failed for &apos;%s&apos; (%Rrc)</source>
+ <translation>VDCreateVfsFileFromDisk завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ Ð´Ð»Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6711"/>
+ <source>Could not create the exported medium &apos;%s&apos;%s</source>
+ <translation>Ðевозможно Ñоздать ÑкÑпортированный ноÑитель &apos;%s&apos;%s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7258"/>
+ <source>Could not open the medium &apos;%s&apos;%s</source>
+ <translation>Ðевозможно открыть ноÑитель &apos;%s&apos;%s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7274"/>
+ <source>Could not update the UUID of medium &apos;%s&apos;%s</source>
+ <translation>Ðевозможно обновить UUID ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;%s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7287"/>
+ <source>Could not update the parent UUID of medium &apos;%s&apos;%s</source>
+ <translation>Ðевозможно обновить родительÑкий UUID ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;%s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7319"/>
+ <source>UUID {%RTuuid} of the medium &apos;%s&apos; does not match the value {%RTuuid} stored in the media registry (&apos;%s&apos;)</source>
+ <translation>UUID {%RTuuid} ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; не ÑответÑтвует значению {%RTuuid} Ñохраненному в рееÑтре ноÑителей (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7409"/>
+ <source>Parent medium with UUID {%RTuuid} of the medium &apos;%s&apos; is not found in the media registry (&apos;%s&apos;)</source>
+ <translation>РодительÑкий ноÑитель Ñ UUID {%RTuuid} ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; не найден в рееÑтре ноÑителей (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7432"/>
+ <source>Cannot open differencing image for medium &apos;%s&apos;, because it exceeds the medium tree depth limit. Please merge some images which you no longer need</source>
+ <translation>Ðевозможно открыть разноÑтный образ ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;, потому что превышено ограничение на глубину дерева ноÑителей. ПожалуйÑта, Ñделайте объединение некоторых ненужных образов</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7467"/>
+ <source>Medium type of &apos;%s&apos; is differencing but it is not associated with any parent medium in the media registry (&apos;%s&apos;)</source>
+ <translation>Тип ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; - разноÑтный, но он не ÑвÑзан Ñ ÐºÐ°ÐºÐ¸Ð¼-либо родительÑким ноÑителем в рееÑтре ноÑителей (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7488"/>
+ <source>Parent UUID {%RTuuid} of the medium &apos;%s&apos; does not match UUID {%RTuuid} of its parent medium stored in the media registry (&apos;%s&apos;)</source>
+ <translation>РодительÑкий UUID {%RTuuid} ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; не ÑоответÑтвует UUID {%RTuuid} родительÑкого ноÑителÑ, Ñохраненного в рееÑтре ноÑителей (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7519"/>
+ <source>Could not update and close the medium &apos;%s&apos;%s</source>
+ <translation>Ðевозможно обновить и закрыть ноÑитель &apos;%s&apos;%s</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="7643"/>
+ <source>Cannot close medium &apos;%s&apos; because it has %d child media</source>
+ <translation>
+ <numerusform>Ðевозможно закрыть ноÑитель &apos;%s&apos;, потому что у него еÑÑ‚ÑŒ %d дочерний ноÑитель</numerusform>
+ <numerusform>Ðевозможно закрыть ноÑитель &apos;%s&apos;, потому что у него еÑÑ‚ÑŒ %d дочерних ноÑителÑ</numerusform>
+ <numerusform>Ðевозможно закрыть ноÑитель &apos;%s&apos;, потому что у него еÑÑ‚ÑŒ %d дочерних ноÑителей</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7718"/>
+ <source>Storage for the medium &apos;%s&apos; is not created</source>
+ <translation>Файл Ð´Ð»Ñ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; не Ñоздан</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7725"/>
+ <source>Storage for the medium &apos;%s&apos; is already created</source>
+ <translation>Файл Ð´Ð»Ñ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; уже Ñоздан</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7732"/>
+ <source>Medium &apos;%s&apos; is locked for reading by another task</source>
+ <translation>ÐоÑитель &apos;%s&apos; заблокирован Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð¹ задачей</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7739"/>
+ <source>Medium &apos;%s&apos; is locked for writing by another task</source>
+ <translation>ÐоÑитель &apos;%s&apos; заблокирован Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи другой задачей</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7748"/>
+ <source>Medium &apos;%s&apos; is not accessible. %s</source>
+ <translation>ÐоÑитель &apos;%s&apos; недоÑтупен. %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7752"/>
+ <source>Medium &apos;%s&apos; is not accessible</source>
+ <translation>ÐоÑитель &apos;%s&apos; недоÑтупен</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7759"/>
+ <source>Storage for the medium &apos;%s&apos; is being created</source>
+ <translation>Файл Ð´Ð»Ñ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; ÑоздаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7766"/>
+ <source>Storage for the medium &apos;%s&apos; is being deleted</source>
+ <translation>Файл Ð´Ð»Ñ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; удалÑетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7840"/>
+ <source>Must be at least one extension if it is MediumFormatCapabilities_File
+</source>
+ <translation>Должно быть Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ одно раÑширение еÑли у ноÑÐ¸Ñ‚ÐµÐ»Ñ ÑƒÑтнановлен MediumFormatCapabilities_File
+</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7845"/>
+ <source>Default extension must not be empty
+</source>
+ <translation>РаÑширение по умолчанию не может быть пуÑтым
+</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7894"/>
+ <source>Permission problem accessing the file for the medium &apos;%s&apos; (%Rrc)</source>
+ <translation>Проблемы Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñми доÑтупа к файлу Ð´Ð»Ñ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7898"/>
+ <source>Could not find file for the medium &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно найти файл Ð´Ð»Ñ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7902"/>
+ <source>Could not get the storage format of the medium &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно получить форма Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7918"/>
+ <source>The medium &apos;%s&apos; can&apos;t be used as the requested device type (%s, detected %s)</source>
+ <translation>ÐоÑитель &apos;%s&apos; не может иÑпользоватьÑÑ ÐºÐ°Ðº ноÑитель запрошенного типа уÑтройÑтва (%s, обнаружено %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7971"/>
+ <source>Invalid medium storage format &apos;%s&apos;</source>
+ <translation>ÐедопуÑтимый формат образа ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8452"/>
+ <source>Image &apos;%s&apos; is configured for encryption but doesn&apos;t has a key identifier set</source>
+ <translation>Образ &apos;%s&apos; Ñконфигурирован Ð´Ð»Ñ ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ, но у него не уÑтановлен KeyId</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8458"/>
+ <source>Image &apos;%s&apos; is configured for encryption but there is no key store to retrieve the password from</source>
+ <translation>Образ &apos;%s&apos; Ñконфигурирован Ð´Ð»Ñ ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ, но нет хранилища ключен, откуда можно получить пароль</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8465"/>
+ <source>Failed to retrieve the secret key with ID &quot;%s&quot; from the store (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ получить ключ Ñ ID &quot;%s&quot; из хранилища (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8473"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10714"/>
+ <source>The password to decrypt the image is incorrect</source>
+ <translation>Пароль Ð´Ð»Ñ Ñ€Ð°Ñшифровки неправильный</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8475"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10717"/>
+ <source>Failed to load the decryption filter: %s</source>
+ <translation>Ðе удалоÑÑŒ загрузить фильтр Ð´Ð»Ñ Ñ€Ð°Ñшифровки: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8509"/>
+ <location filename="../src-server/MediumImpl.cpp" line="8803"/>
+ <location filename="../src-server/MediumImpl.cpp" line="9450"/>
+ <location filename="../src-server/MediumImpl.cpp" line="9520"/>
+ <location filename="../src-server/MediumImpl.cpp" line="9777"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10022"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10046"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10155"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10261"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10390"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10458"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10779"/>
+ <source>Could not open the medium storage unit &apos;%s&apos;%s</source>
+ <translation>Ðевозможно открыть файл ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8612"/>
+ <source>Parameters for creating the medium storage unit &apos;%s&apos; are invalid%s</source>
+ <translation>ÐедейÑтвительный параметр Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° ноÑителÑ&apos;%s:&apos; %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8616"/>
+ <source>Could not create the medium storage unit &apos;%s&apos;%s</source>
+ <translation>Ðевозможно Ñоздать файл ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;:%s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8625"/>
+ <source>Opening medium storage unit &apos;%s&apos; failed%s</source>
+ <translation>Ðе удалоÑÑŒ открыть файл ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8635"/>
+ <source>Formatting medium storage unit &apos;%s&apos; failed: %s</source>
+ <translation>Ðе удалоÑÑŒ отформатировать файл ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s:&apos; %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8638"/>
+ <source>Formatting medium storage unit &apos;%s&apos; failed%s</source>
+ <translation>Ðе удалоÑÑŒ отформатировать файл ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;: %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8735"/>
+ <source>Cannot create differencing image for medium &apos;%s&apos;, because it exceeds the medium tree depth limit. Please merge some images which you no longer need</source>
+ <translation>Ðевозможно Ñоздать разноÑтный образ ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;, потому что превышено ограничение на глубину дерева ноÑителей. ПожалуйÑта, Ñделайте объединение некоторых ненужных образов</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8832"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10064"/>
+ <source>Parameters for creating the differencing medium storage unit &apos;%s&apos; are invalid%s</source>
+ <translation>ÐедейÑтвительный параметр Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ€Ð°Ð·Ð½Ð¾Ñтного файла ноÑителÑ&apos;%s:&apos; %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8836"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10068"/>
+ <source>Could not create the differencing medium storage unit &apos;%s&apos;%s</source>
+ <translation>Ðевозможно Ñоздать разноÑтный файл ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;:%s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8962"/>
+ <source>Cannot merge image for medium &apos;%s&apos;, because it exceeds the medium tree depth limit. Please merge some images which you no longer need</source>
+ <translation>Ðевозможно Ñделать объединение образа ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;, потому что превышено ограничение на глубину дерева ноÑителей. ПожалуйÑта, Ñделайте объединение некоторых ненужных образов</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9016"/>
+ <source>Failed to lock the medium &apos;%s&apos; to resize before merge</source>
+ <translation>Ðе удалоÑÑŒ заблокировать ноÑитель &apos;%s&apos;, чтобы изменить размер перед объединением&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9028"/>
+ <source>Failed to set size of &apos;%s&apos; to size of &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ уÑтановить размер &apos;%s&apos; в &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9039"/>
+ <source>Sizes of &apos;%s&apos; and &apos;%s&apos; are different and medium format does not support resing</source>
+ <translation>Размеры &apos;%s&apos; и &apos;%s&apos; отличаютÑÑ Ð¸ формат ноÑÐ¸Ñ‚ÐµÐ»Ñ Ð½Ðµ поддерживает изменение размера</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9175"/>
+ <source>Could not merge the medium &apos;%s&apos; to &apos;%s&apos;%s</source>
+ <translation>Ðевозможно объединить ноÑитель &apos;%s&apos; Ñ &apos;%s&apos;%s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9396"/>
+ <source>Cannot clone image for medium &apos;%s&apos;, because it exceeds the medium tree depth limit. Please merge some images which you no longer need</source>
+ <translation>Ðевозможно Ñклонировать образ ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;, потому что превышено ограничение на глубину дерева ноÑителей. ПожалуйÑта, Ñделайте объединение некоторых ненужных образов</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9562"/>
+ <source>Could not create the clone medium &apos;%s&apos;%s</source>
+ <translation>Ðевозможно Ñоздать клон ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9734"/>
+ <source>Wrong preconditions for moving the medium %s</source>
+ <translation>Ðеверные уÑÐ»Ð¾Ð²Ð¸Ñ Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9823"/>
+ <source>Could not move medium &apos;%s&apos;%s</source>
+ <translation>Ðевозможно перемеÑтить ноÑитель &apos;%s&apos; %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9919"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10035"/>
+ <source>Could not delete the medium storage unit &apos;%s&apos;%s</source>
+ <translation>Ðевозможно удалить файл ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10172"/>
+ <source>Compacting is not yet supported for medium &apos;%s&apos;</source>
+ <translation>Уплотнение еще не поддерживаетÑÑ Ð´Ð»Ñ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10176"/>
+ <source>Compacting is not implemented, medium &apos;%s&apos;</source>
+ <translation>Уплотнение не реализовано, ноÑитель &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10180"/>
+ <source>Could not compact medium &apos;%s&apos;%s</source>
+ <translation>Ðевозможно уплотнить ноÑитель &apos;%s&apos; %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10279"/>
+ <source>Shrinking is not yet supported for medium &apos;%s&apos;</source>
+ <translation>Уменьшение размера не поддерживаетÑÑ Ð´Ð»Ñ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10283"/>
+ <source>Resizing to new size %llu is not yet supported for medium &apos;%s&apos;</source>
+ <translation>Изменение размера к %llu еще не поддерживаетÑÑ Ð´Ð»Ñ Ð½Ð¾ÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10287"/>
+ <source>Resizing is not implemented, medium &apos;%s&apos;</source>
+ <translation>Изменение размера не реализовано, ноÑитель &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10291"/>
+ <source>Could not resize medium &apos;%s&apos;%s</source>
+ <translation>Ðевозможно изменить размер ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10352"/>
+ <source>Cannot import image for medium &apos;%s&apos;, because it exceeds the medium tree depth limit. Please merge some images which you no longer need</source>
+ <translation>Ðевозможно импортировать образ ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos;, потому что превышено ограничение на глубину дерева ноÑителей. ПожалуйÑта, Ñделайте объединение некоторых ненужных образов</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10478"/>
+ <source>Could not create the imported medium &apos;%s&apos;%s</source>
+ <translation>Ðевозможно Ñоздать импортированный ноÑитель &apos;%s&apos;%s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10666"/>
+ <source>Encrypting the image failed because the encryption plugin could not be loaded (%s)</source>
+ <translation>Ðе удалоÑÑŒ зашифровать образ, потому что невозможно загрузить плагин ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ (%s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10700"/>
+ <source>The password given for the encrypted image is incorrect</source>
+ <translation>Пароль Ð´Ð»Ñ ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð½Ð¾Ð³Ð¾ образа неправильный</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10732"/>
+ <source>A password must be given for the image encryption</source>
+ <translation>Должен быть предоÑтавлен пароль Ð´Ð»Ñ ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ð·Ð°</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10735"/>
+ <source>A valid identifier for the password must be given</source>
+ <translation>Должен быть предоÑтавлен дейÑтвительный идентификатор паролÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10749"/>
+ <source>The password and password identifier must be empty if the output should be unencrypted</source>
+ <translation>Пароль и идентификатор Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð´Ð¾Ð»Ð¶Ð½Ñ‹ быть пуÑÑ‚Ñ‹ еÑли нужно раÑшифровать образ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10794"/>
+ <source>Could not prepare disk images for encryption (%Rrc): %s</source>
+ <translation>Ðевозможно подготовить диÑковые образы Ð´Ð»Ñ ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ (%Rrc): %s</translation>
+ </message>
+</context>
+<context>
+ <name>MediumIO</name>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="533"/>
+ <source>Failed to allocate enough secure memory for the key/password</source>
+ <translation>Ðевозможно выделить доÑтаточного количеÑтва защищенной памÑти Ð´Ð»Ñ ÐºÐ»ÑŽÑ‡Ð° и паролÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="535"/>
+ <source>Unknown error happened while adding a password (%Rrc)</source>
+ <translation>Произошла неизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° во Ð²Ñ€ÐµÐ¼Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="549"/>
+ <source>VDCreateVfsFileFromDisk failed: %Rrc</source>
+ <translation>VDCreateVfsFileFromDisk завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ длÑ: (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="631"/>
+ <source>Max read size is 256KB, given: %u</source>
+ <translation>МакÑимальный размер Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ 256KB, указано: %u</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="633"/>
+ <source>Zero byte read is not supported.</source>
+ <translation>Чтение Ð½Ð¾Ð»Ñ Ð±Ð°Ð¹Ñ‚ не поддерживаетÑÑ.</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumIOImpl.cpp" line="671"/>
+ <source>Error reading %u bytes at %RU64: %Rrc</source>
+ <translation>
+ <numerusform>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ %u байт в %RU64: %Rrc</numerusform>
+ <numerusform>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ %u байта в %RU64: %Rrc</numerusform>
+ <numerusform>Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ %u байт в %RU64: %Rrc</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="685"/>
+ <source>Zero byte write is not supported.</source>
+ <translation>ЗапиÑÑŒ Ð½Ð¾Ð»Ñ Ð±Ð°Ð¹Ñ‚ не поддерживаетÑÑ.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="687"/>
+ <location filename="../src-server/MediumIOImpl.cpp" line="721"/>
+ <location filename="../src-server/MediumIOImpl.cpp" line="760"/>
+ <source>Medium not opened for writing.</source>
+ <translation>ÐоÑитель &apos;%s&apos; не открыт Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи.</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumIOImpl.cpp" line="709"/>
+ <source>Error writing %zu bytes at %RU64: %Rrc</source>
+ <translation>
+ <numerusform>Ошибка запиÑи %u байт в %RU64: %Rrc</numerusform>
+ <numerusform>Ошибка запиÑи %u байта в %RU64: %Rrc</numerusform>
+ <numerusform>Ошибка запиÑи %u байт в %RU64: %Rrc</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="740"/>
+ <source>Error formatting (%Rrc): %s</source>
+ <translation>Ошибка Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ (%Rrc): %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="742"/>
+ <source>Error formatting: %Rrc</source>
+ <translation>Ошибка форматированиÑ: (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="758"/>
+ <source>Invalid partition format type: %d</source>
+ <translation>ÐедопуÑтимый тип Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ€Ð°Ð·Ð´ÐµÐ»Ð°: %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="762"/>
+ <source>whole-disk-in-one-entry is not implemented yet, sorry.</source>
+ <translation>&quot;веÑÑŒ диÑк в одном Ñлементе таблицы разделов&quot; еще не реализован, извините.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="784"/>
+ <source>RTDvmMapInitialize failed: %Rrc</source>
+ <translation>RTDvmMapInitialize завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="788"/>
+ <source>RTDvmCreate failed: %Rrc</source>
+ <translation>RTDvmCreate завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="814"/>
+ <source>Converting medium &apos;%s&apos; to data stream</source>
+ <translation>Преобразование ноÑÐ¸Ñ‚ÐµÐ»Ñ &apos;%s&apos; в поток данных</translation>
+ </message>
+</context>
+<context>
+ <name>MediumIO::StreamTask</name>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="457"/>
+ <source>Failed to convert and stream disk image</source>
+ <translation>Ðе удалоÑÑŒ преобразовать и отправить образ диÑка</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="463"/>
+ <source>Failed to create destination disk container</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать диÑковый контейнер назначениÑ</translation>
+ </message>
+</context>
+<context>
+ <name>Mouse</name>
+ <message>
+ <location filename="../src-client/MouseImpl.cpp" line="510"/>
+ <location filename="../src-client/MouseImpl.cpp" line="553"/>
+ <location filename="../src-client/MouseImpl.cpp" line="619"/>
+ <source>Could not send the mouse event to the virtual mouse (%Rrc)</source>
+ <translation>Ðевозможно отправить Ñобытие мыши в виртуальную мышь (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/MouseImpl.cpp" line="588"/>
+ <source>Could not send the multi-touch event to the virtual device (%Rrc)</source>
+ <translation>Ðевозможно отправить Ñобытие мультитач в виртуальное уÑтройÑтво (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>NATEngine</name>
+ <message>
+ <location filename="../src-server/NATEngineImpl.cpp" line="296"/>
+ <source>&apos;%c&apos; - invalid character in NAT rule name</source>
+ <translation>&apos;%c&apos; - недопуÑтимый Ñимвол в имени правила NAT</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATEngineImpl.cpp" line="306"/>
+ <source>A NAT rule of this name already exists</source>
+ <translation>Правило NAT Ñ Ñтим именем уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATEngineImpl.cpp" line="311"/>
+ <source>A NAT rule for this host port and this host IP already exists</source>
+ <translation>Правило NAT Ð´Ð»Ñ Ñтого порта и IP адреÑа хоÑта уже ÑущеÑтвует</translation>
+ </message>
+</context>
+<context>
+ <name>NATNetwork</name>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="154"/>
+ <source>Unable to change settings while NATNetwork instance is running</source>
+ <translation>Ðевозможно изменить наÑтройки пока ÑкземплÑÑ€ NATNetwork работает</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="221"/>
+ <source>Network name cannot be empty</source>
+ <translation>Ð˜Ð¼Ñ Ñети не может быть пуÑтым</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="289"/>
+ <source>%s is not a valid IPv4 CIDR notation</source>
+ <translation>IPv4 CIDR Ð½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ %s недопуÑтима</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="299"/>
+ <source>%s network is too small</source>
+ <translation>Сеть %s Ñлишком маленькаÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="302"/>
+ <source>%s specifies zero prefix</source>
+ <translation>%s задает нулевой префикÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="311"/>
+ <source>%s: the specified address is longer than the specified prefix</source>
+ <translation>%s: указанный Ð°Ð´Ñ€ÐµÑ Ð´Ð»Ð¸Ð½Ð½ÐµÐµ чем указанный префикÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="449"/>
+ <source>%s is not a valid IPv6 prefix</source>
+ <translation>%s - Ñто недопуÑтимый IPv6 префикÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="457"/>
+ <source>Invalid IPv6 prefix length %d, must be 64</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ð´Ð»Ð¸Ð½Ð° префикÑа IPv6 %d, должна быть 64</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="464"/>
+ <source>IPv6 prefix %RTnaipv6 is not unicast</source>
+ <translation>IPv6 Ð¿Ñ€ÐµÑ„Ð¸ÐºÑ %RTnaipv6 не unicast</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="470"/>
+ <source>Non-zero bits in the interface ID part of the IPv6 prefix %RTnaipv6/64</source>
+ <translation>Ðенулевые биты в чаÑти c ID интерфейÑом IPv6 префикÑа %RTnaipv6/64</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="480"/>
+ <source>Internal error</source>
+ <translation>ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ°</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="496"/>
+ <source>Setting an empty IPv6 prefix when IPv6 is enabled</source>
+ <translation>УÑтановлен пуÑтой IPv6 префикÑ, когда включен IPv6</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="740"/>
+ <source>A NAT rule of this name already exists</source>
+ <translation>Правило NAT Ñ Ñтим именем уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="745"/>
+ <source>A NAT rule for this host port and this host IP already exists</source>
+ <translation>Правило NAT Ð´Ð»Ñ Ñтого порта и IP адреÑа хоÑта уже ÑущеÑтвует</translation>
+ </message>
+</context>
+<context>
+ <name>NetworkAdapter</name>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="254"/>
+ <source>Invalid network adapter type &apos;%d&apos;</source>
+ <translation>ÐедопуÑтимый тип Ñетевого адаптера %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="370"/>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="374"/>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="381"/>
+ <source>Invalid MAC address format</source>
+ <translation>ÐедопуÑтимый формат MAC адреÑа</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="525"/>
+ <source>Empty or null bridged interface name is not valid</source>
+ <translation>ПуÑтое или нулевое Ð¸Ð¼Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñа Ñетевого моÑта недопуÑтимо</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="573"/>
+ <source>Empty or null host only interface name is not valid</source>
+ <translation>ПуÑтое или нулевое Ð¸Ð¼Ñ Ð²Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð¾Ð³Ð¾ адаптера хоÑта недопуÑтимо</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="628"/>
+ <source>Empty or null host only Network name is not valid</source>
+ <translation>ПуÑтое или нулевое Ð¸Ð¼Ñ Ñети виртуального адаптера хоÑта недопуÑтимо</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="681"/>
+ <source>Empty or null internal network name is not valid</source>
+ <translation>ПуÑтое или нулевое Ð¸Ð¼Ñ Ð²Ð½ÑƒÑ‚Ñ€ÐµÐ½Ð½ÐµÐ¹ Ñети недопуÑтимо</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="728"/>
+ <source>Empty or null NAT network name is not valid</source>
+ <translation>ПуÑтое или нулевое Ð¸Ð¼Ñ Ñети NAT недопуÑтимо</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="821"/>
+ <source>Empty or null Cloud network name is not valid</source>
+ <translation>ПуÑтое или нулевое Ð¸Ð¼Ñ Ð¾Ð±Ð»Ð°Ñ‡Ð½Ð¾Ð¹ Ñети недопуÑтимо</translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="947"/>
+ <source>Invalid promiscuous mode policy (%d)</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ° неразборчивого режима (%d)</translation>
+ </message>
+</context>
+<context>
+ <name>NvramStore</name>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="334"/>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="392"/>
+ <source>Loading the NVRAM store failed (%Rrc)
+</source>
+ <translation>Ðе удалоÑÑŒ загрузкить NVRAM хранилище (%Rrc)
+</translation>
+ </message>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="729"/>
+ <source>Opening the UEFI variable store failed (%Rrc).</source>
+ <translation>Ðе удалоÑÑŒ открыть хранилище UEFI переменных (%Rrc).</translation>
+ </message>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="346"/>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="732"/>
+ <source>The UEFI NVRAM file is not existing for this machine.</source>
+ <translation>Ðет UEFI NVRAM файла Ð´Ð»Ñ Ñтой машины.</translation>
+ </message>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="370"/>
+ <source>Supporting another NVRAM size apart from the default one is not supported right now</source>
+ <translation>Поддержка другого размера NVRAM, кроме Ñтандартного, ÑÐµÐ¹Ñ‡Ð°Ñ Ð½Ðµ поддерживаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="384"/>
+ <source>The selected firmware type doesn&apos;t support a UEFI variable store</source>
+ <translation>Выбранный тип прошивки не поддерживает хранилище UEFI переменных</translation>
+ </message>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="422"/>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="425"/>
+ <source>Failed to initialize the UEFI variable store (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ инициализировать хранилище UEFI переменных (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="791"/>
+ <source>Failed to save the NVRAM content to disk (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ Ñохранить Ñодержимое NVRAM на диÑк (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>ParallelPort</name>
+ <message>
+ <location filename="../src-server/ParallelPortImpl.cpp" line="260"/>
+ <source>Invalid IRQ number of the parallel port %d: %lu (must be in range [0, %lu])</source>
+ <translation>ÐедопуÑтимый IRQ номер параллельного порта %d: %lu (должен быть в диапазоне [0, %lu])</translation>
+ </message>
+ <message>
+ <location filename="../src-server/ParallelPortImpl.cpp" line="303"/>
+ <source>Invalid I/O port base address of the parallel port %d: %lu (must be in range [0, 0x%X])</source>
+ <translation>ÐедопуÑтимый базовый Ð°Ð´Ñ€ÐµÑ Ð¿Ð¾Ñ€Ñ‚Ð° I/O параллельного порта %d: %lu (должен быть в диапазоне [0, 0x%X])</translation>
+ </message>
+</context>
+<context>
+ <name>PassiveEventListener</name>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1301"/>
+ <source>HandleEvent() of wrapper shall never be called</source>
+ <translation>HandleEvent() обертки не должен вызыватьÑÑ Ð½Ð¸ÐºÐ¾Ð³Ð´Ð°</translation>
+ </message>
+</context>
+<context>
+ <name>PerformanceCollector</name>
+ <message>
+ <location filename="../src-server/PerformanceImpl.cpp" line="383"/>
+ <source>Failed to setup metrics for &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ уÑтановить метрики Ð´Ð»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/PerformanceImpl.cpp" line="419"/>
+ <source>Failed to enable metrics for &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ включить метрики Ð´Ð»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/PerformanceImpl.cpp" line="455"/>
+ <source>Failed to disable metrics for &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ выключить метрики Ð´Ð»Ñ &apos;%s&apos;</translation>
+ </message>
+</context>
+<context>
+ <name>Progress</name>
+ <message>
+ <location filename="../src-all/ProgressImpl.cpp" line="656"/>
+ <source>Result code is not available, operation is still in progress</source>
+ <translation>Результирующий код недоÑтупен, Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ ÐµÑ‰Ðµ в процеÑÑе</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ProgressImpl.cpp" line="668"/>
+ <source>Error info is not available, operation is still in progress</source>
+ <translation>Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð± ошибке недоÑтупна, Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ ÐµÑ‰Ðµ в процеÑÑе</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ProgressImpl.cpp" line="737"/>
+ <location filename="../src-all/ProgressImpl.cpp" line="871"/>
+ <source>Operation cannot be canceled</source>
+ <translation>ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð½Ðµ может быть отменена</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ProgressImpl.cpp" line="786"/>
+ <source>Failed to wait for the task completion (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ подождать Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/ProgressImpl.cpp" line="845"/>
+ <source>Failed to wait for the operation completion (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ подождать Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ð¸ (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>ProgressProxy</name>
+ <message>
+ <location filename="../src-server/ProgressProxyImpl.cpp" line="381"/>
+ <source>No error info</source>
+ <translation>Ðет информации об ошибке</translation>
+ </message>
+</context>
+<context>
+ <name>RecordingScreenSettings</name>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="284"/>
+ <source>Cannot change enabled state of screen while recording is enabled</source>
+ <translation>Ðевозможно изменить ÑоÑтоÑние Ñкрана на &quot;включено&quot; пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="328"/>
+ <source>Cannot change features while recording is enabled</source>
+ <translation>Ðевозможно изменить функции пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="363"/>
+ <source>Cannot change destination type while recording is enabled</source>
+ <translation>Ðевозможно изменить тип Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¿Ð¾ÐºÐ° включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="386"/>
+ <source>Error retrieving default file name</source>
+ <translation>Ошибка при получении имени файла по умолчанию</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="400"/>
+ <source>Cannot change file name while recording is enabled</source>
+ <translation>Ðевозможно изменить Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="404"/>
+ <source>Recording file name &apos;%s&apos; is not absolute</source>
+ <translation>Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи &apos;%s&apos; не абÑолютно</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="432"/>
+ <source>Cannot change maximum time while recording is enabled</source>
+ <translation>Ðевозможно изменить макÑимальное Ð²Ñ€ÐµÐ¼Ñ Ð¿Ð¾ÐºÐ° включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="460"/>
+ <source>Cannot change maximum file size while recording is enabled</source>
+ <translation>Ðевозможно изменить макÑимальный размер файла пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="488"/>
+ <source>Cannot change options while recording is enabled</source>
+ <translation>Ðевозможно изменить параметры пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="497"/>
+ <source>Invalid option specified</source>
+ <translation>Указан недопуÑтимый параметр</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="520"/>
+ <source>Cannot change audio codec while recording is enabled</source>
+ <translation>Ðевозможно изменить аудио кодек пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="548"/>
+ <source>Cannot change audio Hertz rate while recording is enabled</source>
+ <translation>Ðевозможно изменить чаÑтоту оцифровки аудио пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="576"/>
+ <source>Cannot change audio bits while recording is enabled</source>
+ <translation>Ðевозможно изменить битноÑÑ‚ÑŒ аудио пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="604"/>
+ <source>Cannot change audio channels while recording is enabled</source>
+ <translation>Ðевозможно изменить аудио каналы пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="632"/>
+ <source>Cannot change video codec while recording is enabled</source>
+ <translation>Ðевозможно изменить видео кодек пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="660"/>
+ <source>Cannot change video width while recording is enabled</source>
+ <translation>Ðевозможно изменить ширину видео пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="688"/>
+ <source>Cannot change video height while recording is enabled</source>
+ <translation>Ðевозможно изменить выÑоту видео пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="716"/>
+ <source>Cannot change video rate while recording is enabled</source>
+ <translation>Ðевозможно изменить видео битрейт пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="744"/>
+ <source>Cannot change video rate control mode while recording is enabled</source>
+ <translation>Ðевозможно изменить режим ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð±Ð¸Ñ‚Ñ€ÐµÐ¹Ñ‚Ð¾Ð¼ пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="772"/>
+ <source>Cannot change video FPS while recording is enabled</source>
+ <translation>Ðевозможно изменить чаÑтоту кадров видео пока включена запиÑÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="800"/>
+ <source>Cannot change video rate scaling method while recording is enabled</source>
+ <translation>Ðевозможно изменить метод Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð±Ð¸Ñ‚Ñ€ÐµÐ¹Ñ‚Ð° видео пока включена запиÑÑŒ</translation>
+ </message>
+</context>
+<context>
+ <name>RecordingSettings</name>
+ <message>
+ <location filename="../src-server/RecordingSettingsImpl.cpp" line="306"/>
+ <source>Invalid screen ID specified</source>
+ <translation>Указан недопуÑтимый ID Ñкрана</translation>
+ </message>
+</context>
+<context>
+ <name>SerialPort</name>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="264"/>
+ <source>Cannot set the raw file mode of the serial port %d because the file path is empty or null</source>
+ <translation>Ðевозможно уÑтановить файловый режим RAW на поÑледовательный порт %d, потому что путь к файлу пуÑÑ‚ или нулевой</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="271"/>
+ <source>Cannot set the host pipe mode of the serial port %d because the pipe path is empty or null</source>
+ <translation>Ðевозможно уÑтановить режим конвеера хоÑта на поÑледовательный порт %d, потому что путь к файлу пуÑÑ‚ или нулевой</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="278"/>
+ <source>Cannot set the host device mode of the serial port %d because the device path is empty or null</source>
+ <translation>Ðевозможно уÑтановить режим уÑтройÑтва хоÑта на поÑледовательный порт %d, потому что путь к файлу пуÑÑ‚ или нулевой</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="285"/>
+ <source>Cannot set the host device mode of the serial port %d because the server address or TCP port is invalid</source>
+ <translation>Ðевозможно уÑтановить режим уÑтройÑтва хоÑта на поÑледовательный порт %d, потому что Ð°Ð´Ñ€ÐµÑ Ñервера или порт TCP недейÑтвительны</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="340"/>
+ <source>Invalid IRQ number of the serial port %d: %lu (must be in range [0, %lu])</source>
+ <translation>ÐедопуÑтимый IRQ номер поÑледовательного порта %d: %lu (должен быть в диапазоне [0, %lu])</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="384"/>
+ <source>Invalid I/O port base address of the serial port %d: %lu (must be in range [0, 0x%X])</source>
+ <translation>ÐедопуÑтимый базовый Ð°Ð´Ñ€ÐµÑ Ð¿Ð¾Ñ€Ñ‚Ð° I/O параллельного порта %d: %lu (должен быть в диапазоне [0, 0x%X])</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="770"/>
+ <source>Path of the serial port %d may not be empty or null in host pipe, host device or TCP mode</source>
+ <translation>Путь к поÑледовательному порту %d не может быть пуÑтым в режимах конвеер хоÑта, уÑтройÑтво хоÑта или TCP</translation>
+ </message>
+</context>
+<context>
+ <name>Session</name>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="40"/>
+ <source>The session is not locked (session state: %s)</source>
+ <translation>СеÑÑÐ¸Ñ Ð½Ðµ заблокирована (ÑоÑтоÑние ÑеÑÑии: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="170"/>
+ <source>Trying to set name for a session which is not in state &quot;unlocked&quot;</source>
+ <translation>Попытка уÑтановить Ð¸Ð¼Ñ Ð´Ð»Ñ ÑеÑÑии, не находÑщейÑÑ Ð² ÑоÑтоÑнии &quot;разблокировано&quot;</translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="193"/>
+ <source>Failed to query the session machine</source>
+ <translation>Ðе удалоÑÑŒ запроÑить машину ÑеÑÑии</translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="197"/>
+ <location filename="../src-client/SessionImpl.cpp" line="227"/>
+ <source>Peer process crashed</source>
+ <translation>Парный процеÑÑ ÑƒÐ¿Ð°Ð»</translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="199"/>
+ <source>Failed to query the remote session machine</source>
+ <translation>Ðе удалоÑÑŒ запроÑить машину удаленной ÑеÑÑии</translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="223"/>
+ <source>Failed to query the console</source>
+ <translation>Ðе удалоÑÑŒ запроÑить конÑоль</translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="229"/>
+ <source>Failed to query the remote console</source>
+ <translation>Ðе удалоÑÑŒ запроÑить удаленную конÑоль</translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="952"/>
+ <location filename="../src-client/SessionImpl.cpp" line="998"/>
+ <location filename="../src-client/SessionImpl.cpp" line="1020"/>
+ <location filename="../src-client/SessionImpl.cpp" line="1040"/>
+ <source>Machine is not locked by session (session state: %s).</source>
+ <translation>Машина не заблокирована ÑеÑÑией (ÑоÑтоÑние ÑеÑÑии: %s)</translation>
+ </message>
+</context>
+<context>
+ <name>SessionMachine</name>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="12816"/>
+ <location filename="../src-server/MachineImpl.cpp" line="12826"/>
+ <source>The VM session was aborted</source>
+ <translation>СеÑÑÐ¸Ñ Ð’Ðœ прервана</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="12978"/>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1726"/>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2287"/>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2935"/>
+ <source>The session has been accidentally closed</source>
+ <translation>СеÑÑÐ¸Ñ Ð±Ñ‹Ð»Ð° Ñлучайно закрыта</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="12995"/>
+ <source>Trying to save state without a running VM</source>
+ <translation>Попытка Ñохранить ÑоÑтоÑние без запущенной Ð’Ðœ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13053"/>
+ <source>Cannot save the execution state as the machine is not running or paused (machine state: %s)</source>
+ <translation>Ðевозможно Ñохранить ÑоÑтоÑние Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ñ‚Ð°Ðº как машина не запущена или приоÑтановлена (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13060"/>
+ <source>Saving the execution state of the virtual machine</source>
+ <translation>Сохранение ÑоÑтоÑÐ½Ð¸Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð²Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð¾Ð¹ машины</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13099"/>
+ <source>Cannot adopt the saved machine state as the machine is not in Powered Off, Teleported or Aborted state (machine state: %s)</source>
+ <translation>Ðевозможно принÑÑ‚ÑŒ Ñохраненное ÑоÑтоÑние машины, так как машина не находитÑÑ Ð² ÑоÑтоÑниÑÑ… &quot;Выключена&quot;, &quot;Портирована&quot; или &quot;Прервана&quot; (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13106"/>
+ <source>Invalid saved state file path &apos;%s&apos; (%Rrc)</source>
+ <translation>ÐедопуÑтимый путь к файлу Ñохраненного ÑоÑотоÑÐ½Ð¸Ñ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13131"/>
+ <source>Cannot discard the saved state as the machine is not in the Saved or Aborted-Saved state (machine state: %s)</source>
+ <translation>Ðевозможно убрать Ñохраненное ÑоÑтоÑние, так как машина не находитÑÑ Ð² ÑоÑтоÑниÑÑ… &quot;Сохранена&quot;, &quot;Прервана Ñ Ñохранением&quot; (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13259"/>
+ <source>Stopping the virtual machine</source>
+ <translation>ОÑтановка виртуальной машины</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13485"/>
+ <source>The VM session was closed before any attempt to power it on</source>
+ <translation>СеÑÑÐ¸Ñ Ð’Ðœ закрыта перед какой-либо попыткой включить машину</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13499"/>
+ <source>Closing session</source>
+ <translation>Закрытие ÑеÑÑии</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13523"/>
+ <source>The session is not found in the session list!</source>
+ <translation>СеÑÑÐ¸Ñ Ð½Ðµ найдена в ÑпиÑке ÑеÑÑий!</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13761"/>
+ <source>Could not load the external authentication library &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно загрузить внешнюю библиотку аутентификации &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="14602"/>
+ <source>Collecting locking information for all attached media failed</source>
+ <translation>Ðе удалоÑÑŒ Ñобрать информациию о блокировках Ð´Ð»Ñ Ð²Ñех подключенных ноÑителей</translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="14616"/>
+ <source>Locking of attached media failed. A possible reason is that one of the media is attached to a running VM</source>
+ <translation>Ðе удалоÑÑŒ заблокировать поключенный ноÑитель. Ð’Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð°Ñ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð° - один из ноÑителей подключен к запущенной Ð’Ðœ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1603"/>
+ <source>Cannot take a snapshot of the machine while it is changing the state (machine state: %s)</source>
+ <translation>Ðевозможно Ñделать Ñнимок машины пока она менÑет ÑоÑтоÑние (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1646"/>
+ <source>Taking a snapshot of the virtual machine</source>
+ <translation>Получение Ñнимка виртуальной машины</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1650"/>
+ <source>Setting up snapshot operation</source>
+ <translation>ÐаÑтройка операции ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñнимка</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1753"/>
+ <source>Cannot take another snapshot for machine &apos;%s&apos;, because it exceeds the maximum snapshot depth limit. Please delete some earlier snapshot which you no longer need</source>
+ <translation>Ðевозможно получить еще один Ñнимок Ð´Ð»Ñ Ð¼Ð°ÑˆÐ¸Ð½Ñ‹ &apos;%s&apos;, потому что превышено ограничение на макÑимальную глубину Ñнимков. ПожалуйÑта, удалите какой-нибудь Ñтарый ненужный Ñнимок</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1835"/>
+ <source>Saving the machine state</source>
+ <translation>Сохранение ÑоÑтоÑÐ½Ð¸Ñ Ð¼Ð°ÑˆÐ¸Ð½Ñ‹</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1858"/>
+ <source>Canceled</source>
+ <translation>Отменено</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1863"/>
+ <source>Reconfiguring medium attachments</source>
+ <translation>ÐŸÐµÑ€ÐµÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ð¹ ноÑителей</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1891"/>
+ <source>Could not copy NVRAM file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñкопировать NVRAM файл &apos;%s&apos; в &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2195"/>
+ <source>Cannot delete the current state of the running machine (machine state: %s)</source>
+ <translation>Ðевозможно удалить текущее ÑоÑтоÑние запущенной машины (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2232"/>
+ <source>Restoring snapshot &apos;%s&apos;</source>
+ <translation>ВоÑÑтановление из Ñнимка &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2236"/>
+ <source>Restoring machine settings</source>
+ <translation>ВоÑÑтановление наÑтроек машины</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2675"/>
+ <source>Cannot delete a snapshot of the machine while it is changing the state (machine state: %s)</source>
+ <translation>Ðевозможно удалить Ñнимок машины во Ð²Ñ€ÐµÐ¼Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ ÑоÑтоÑÐ½Ð¸Ñ (ÑоÑтоÑние машины: %s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2688"/>
+ <source>Invalid machine state: %s</source>
+ <translation>ÐедопуÑтимое ÑоÑтоÑние машины: %s</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/SnapshotImpl.cpp" line="2706"/>
+ <source>Snapshot &apos;%s&apos; of the machine &apos;%s&apos; cannot be deleted, because it has %d child snapshots, which is more than the one snapshot allowed for deletion</source>
+ <translation>
+ <numerusform>Снимок &apos;%s&apos; машины &apos;%s&apos; не может быть удален, потому что у него еÑÑ‚ÑŒ %d дочерний Ñнимок, что больше одного, доÑтупного Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ</numerusform>
+ <numerusform>Снимок &apos;%s&apos; машины &apos;%s&apos; не может быть удален, потому что у него еÑÑ‚ÑŒ %d дочерних Ñнимка, что больше одного, доÑтупного Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ</numerusform>
+ <numerusform>Снимок &apos;%s&apos; машины &apos;%s&apos; не может быть удален, потому что у него еÑÑ‚ÑŒ %d дочерних Ñнимков, что больше одного, доÑтупного Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2714"/>
+ <source>Snapshot &apos;%s&apos; of the machine &apos;%s&apos; cannot be deleted, because it is the current snapshot and has one child snapshot</source>
+ <translation>Снимок &apos;%s&apos; машины &apos;%s&apos; не может быть удален, потому что Ñто текущий Ñнимок и у него еÑÑ‚ÑŒ один дочерний Ñнимок</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2787"/>
+ <source>Deleting snapshot &apos;%s&apos;</source>
+ <translation>Удаление Ñнимка &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2791"/>
+ <source>Setting up</source>
+ <translation>ÐаÑтройка</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/SnapshotImpl.cpp" line="3069"/>
+ <source>Unable to merge storage &apos;%s&apos;, because it is smaller than the source image. If you resize it to have a capacity of at least %lld bytes you can retry</source>
+ <translation>
+ <numerusform>Ðевозможно объединить ноÑитель &apos;%s&apos;, потому что он меньше чем иÑходный образ. Измените размер чтобы иметь Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ %lld байт и попытайтеÑÑŒ Ñнова</numerusform>
+ <numerusform>Ðевозможно объединить ноÑитель &apos;%s&apos;, потому что он меньше чем иÑходный образ. Измените размер чтобы иметь Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ %lld байта и попытайтеÑÑŒ Ñнова</numerusform>
+ <numerusform>Ðевозможно объединить ноÑитель &apos;%s&apos;, потому что он меньше чем иÑходный образ. Измените размер чтобы иметь Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ %lld байт и попытайтеÑÑŒ Ñнова</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3169"/>
+ <source>Unable to merge storage &apos;%s&apos;. Can&apos;t get storage UID</source>
+ <translation>Ðевозможно объединить ноÑитель &apos;%s&apos;. Ðевозможно получить UID ноÑителÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3211"/>
+ <source>Unable to merge storage &apos;%s&apos;. Path to the storage wasn&apos;t found</source>
+ <translation>Ðевозможно объединить ноÑитель &apos;%s&apos;. Путь к ноÑителю не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3220"/>
+ <source>Unable to merge storage &apos;%s&apos;. Can&apos;t get the storage size</source>
+ <translation>Ðевозможно объединить ноÑитель &apos;%s&apos;. Ðевозможно получить размер ноÑителÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3230"/>
+ <source>Unable to merge storage &apos;%s&apos;. Not enough free storage space</source>
+ <translation>Ðевозможно объединить ноÑитель &apos;%s&apos;. ÐедоÑтаточно Ñвободного меÑта</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3263"/>
+ <source>Deleting the execution state</source>
+ <translation>Удаление ÑоÑтоÑÐ½Ð¸Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3292"/>
+ <source>Merging differencing image &apos;%s&apos;</source>
+ <translation>Объединение разноÑтных образов &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3293"/>
+ <source>Resizing before merge differencing image &apos;%s&apos;</source>
+ <translation>Изменение размера перед объединением разноÑтных образов &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3690"/>
+ <source>Hard disk &apos;%s&apos; has more than one child hard disk (%d)</source>
+ <translation>У жеÑткого диÑка &apos;%s&apos; более одного дочернего жеÑткого диÑка (%d)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3859"/>
+ <source>Cannot lock hard disk &apos;%s&apos; for a live merge</source>
+ <translation>Ðевозможно заблокировать жеÑткий диÑк &apos;%s&apos; Ð´Ð»Ñ Ð¶Ð¸Ð²Ð¾Ð³Ð¾ объединениÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3873"/>
+ <source>Failed to construct lock list for a live merge of hard disk &apos;%s&apos;</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать ÑпиÑок блокировок Ð´Ð»Ñ Ð¶Ð¸Ð²Ð¾Ð³Ð¾ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¶ÐµÑтких диÑков &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3906"/>
+ <source>Cannot lock hard disk &apos;%s&apos; when deleting a snapshot</source>
+ <translation>Ðевозможно заблокировать жеÑткий диÑк &apos;%s&apos; во Ð²Ñ€ÐµÐ¼Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ñнимка</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="4088"/>
+ <source>Machine is not locked by a session (session state: %s)</source>
+ <translation>Машина не заблокирована ÑеÑÑией (ÑоÑтоÑние ÑеÑÑии: %s)</translation>
+ </message>
+</context>
+<context>
+ <name>SharedFolder</name>
+ <message>
+ <location filename="../src-all/SharedFolderImpl.cpp" line="289"/>
+ <source>Invalid shared folder path: &apos;%s&apos; (%Rrc)</source>
+ <translation>ÐедопуÑтимый путь к общей папке: &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/SharedFolderImpl.cpp" line="292"/>
+ <source>Shared folder path &apos;%s&apos; is not absolute</source>
+ <translation>Путь к общей папке &apos;%s&apos; не абÑолютный</translation>
+ </message>
+ <message>
+ <location filename="../src-all/SharedFolderImpl.cpp" line="297"/>
+ <source>RTPathQueryInfo failed on shared folder path &apos;%s&apos;: %Rrc</source>
+ <translation>RTPathQueryInfo завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ на пути к общей папке &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-all/SharedFolderImpl.cpp" line="300"/>
+ <source>Shared folder path &apos;%s&apos; is not a directory</source>
+ <translation>Путь к общей папке &apos;%s&apos; не указывает на директорию</translation>
+ </message>
+ <message>
+ <location filename="../src-all/SharedFolderImpl.cpp" line="372"/>
+ <source>&apos;%s&apos; is not accessible (%Rrc)</source>
+ <translation>&apos;%s&apos; не доÑтупно (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>Snapshot</name>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="338"/>
+ <source>A machine cannot have a UUID as its name</source>
+ <translation>Машина не может иметь UUID в качеÑтве имени</translation>
+ </message>
+</context>
+<context>
+ <name>StorageController</name>
+ <message>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="100"/>
+ <source>Invalid storage connection type</source>
+ <translation>ÐедопуÑтимый тип ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð½Ð¾Ñителей</translation>
+ </message>
+ <message>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="113"/>
+ <source>Too many storage controllers of this type</source>
+ <translation>Слишком много контроллеров Ñтого типа</translation>
+ </message>
+ <message>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="317"/>
+ <source>Storage controller named &apos;%s&apos; already exists</source>
+ <translation>Контроллер Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos; уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="434"/>
+ <source>Invalid controller type %d</source>
+ <translation>ÐедопуÑтимый тип контроллера %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="502"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="516"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="527"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="538"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="547"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="558"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="570"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="581"/>
+ <source>Invalid port count: %lu (must be in range [%lu, %lu])</source>
+ <translation>ÐедопуÑтимое количеÑтво портов: %lu (должно быть в диапазоне [%lu, %lu])</translation>
+ </message>
+ <message>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="733"/>
+ <source>The port and/or device parameter are out of range: port=%d (must be in range [0, %d]), device=%d (must be in range [0, %d])</source>
+ <translation>Параметр порта и/или уÑтройÑтва вышел за границы диапазона: порт=%d (должен быть в диапазоне [0, %d]), уÑтройÑтво=%d (должно быть в диапазоне [0, %d])</translation>
+ </message>
+</context>
+<context>
+ <name>SystemProperties</name>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="587"/>
+ <source>Invalid storage controller type %d
+</source>
+ <translation>ÐедопуÑтимый тип контроллера %d
+</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="637"/>
+ <source>Invalid storage bus %d
+</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ ÑˆÐ¸Ð½Ð° %d
+</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="744"/>
+ <source>Invalid or unsupported architecture value: %d</source>
+ <translation>ÐедопуÑтимое или неподдерживаемое значение архитектуры: %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="854"/>
+ <source>&apos;%s&apos; is missing symbols: CPUMR3DbGetEntries, CPUMR3DbGetEntryByIndex</source>
+ <translation>ОтÑутÑтвуют Ñимволы в &apos;%s&apos;: CPUMR3DbGetEntries, CPUMR3DbGetEntryByIndex</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="858"/>
+ <source>Failed to construct load &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ загрузить &apos;%s&apos;: %Rc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="861"/>
+ <source>Failed to construct path to the VMM DLL/Dylib/SharedObject: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ Ñобрать путь к VMM DLL/Dylib/SharedObject: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1098"/>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1144"/>
+ <source>The extension pack &apos;%s&apos; does not exist</source>
+ <translation>Пакет раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos; не ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1300"/>
+ <source>Invalid ProxyMode value: %d</source>
+ <translation>ÐедопуÑтимое значение ProxyMode: %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1340"/>
+ <source>Failed to parse proxy URL: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ разобрать URL прокÑи: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1343"/>
+ <source>Proxy URL must include a hostname</source>
+ <translation>URL прокÑи должен включать Ð¸Ð¼Ñ Ñ…Ð¾Ñта</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1345"/>
+ <source>Proxy URL must not include a path component (%.*s)</source>
+ <translation>URL прокÑи не должен включать компонент пути (%.*s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1348"/>
+ <source>Proxy URL must not include a query component (?%.*s)</source>
+ <translation>URL прокÑи не должен включать компонент запроÑа (%.*s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1351"/>
+ <source>Proxy URL must not include a fragment component (#%.*s)</source>
+ <translation>URL прокÑи не должен включать компонент фрагмента (%.*s)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2044"/>
+ <source>Cannot determine user home directory (%Rrc)</source>
+ <translation>Ðевозможно определить домашнюю директорию Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2075"/>
+ <source>Given default machine folder &apos;%s&apos; is not fully qualified</source>
+ <translation>Ð”Ð°Ð½Ð½Ð°Ñ Ð¿Ð°Ð¿ÐºÐ° машины по умолчанию &apos;%s&apos; не полноÑтью определена</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2160"/>
+ <source>Cannot set the autostart database path (%Rrc)</source>
+ <translation>Ðевозможно уÑтановить путь к базе данных автоÑтарта (%Rc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2170"/>
+ <source>Deleting the autostart database path failed (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ удалить путь к базе данных автоÑтарта (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2204"/>
+ <source>Cannot determine default Guest Additions ISO location. Most likely they are not available</source>
+ <translation>Ðевозможно определить Ñтандартное раÑположение Дополнений ГоÑтевой ОС. Ðаиболее вероÑтно, они недоÑтупны</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2209"/>
+ <source>Given default machine Guest Additions ISO file &apos;%s&apos; is not fully qualified</source>
+ <translation>Данный файл Дополнений ГоÑтевой ОС &apos;%s&apos; машины не полноÑтью определен</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2214"/>
+ <source>Given default machine Guest Additions ISO file &apos;%s&apos; does not exist</source>
+ <translation>Данный файл Дополнений ГоÑтевой ОС &apos;%s&apos; машины не ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2382"/>
+ <source>Invalid Target value: %d</source>
+ <translation>ÐедопуÑтимое значение Target: %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2411"/>
+ <source>Invalid LastCheckDate value: &apos;%s&apos;. Must be in ISO 8601 format (e.g. 2020-05-11T21:13:39.348416000Z)</source>
+ <translation>ÐедопуÑтимое значение LastCheckDate: &apos;%s&apos;. Должно быть в формате ISO 8601 (например 2020-05-11T21:13:39.348416000Z)</translation>
+ </message>
+</context>
+<context>
+ <name>USBController</name>
+ <message>
+ <location filename="../src-server/USBControllerImpl.cpp" line="96"/>
+ <source>Invalid USB controller type</source>
+ <translation>ÐедопуÑтимый тип USB контроллера</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBControllerImpl.cpp" line="252"/>
+ <source>USB controller named &apos;%s&apos; already exists</source>
+ <translation>USB контроллер Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos; уже ÑущеÑтвует</translation>
+ </message>
+</context>
+<context>
+ <name>USBDeviceFilter</name>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="67"/>
+ <source>Vendor ID</source>
+ <translation>ID поÑтавщика</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="68"/>
+ <source>Product ID</source>
+ <translation>ID продукта</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="69"/>
+ <source>Revision</source>
+ <translation>РевизиÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="70"/>
+ <source>Manufacturer</source>
+ <translation>Производитель</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="71"/>
+ <source>Product</source>
+ <translation>Продукт</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="72"/>
+ <source>Serial number</source>
+ <translation>Серийный номер</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="73"/>
+ <source>Port number</source>
+ <translation>Ðомер порта</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="122"/>
+ <source>The %s value &apos;%s&apos; is too big (max 0xFFFF)</source>
+ <translation>Значение %s &apos;%s&apos; Ñлишком большое (макÑимальное 0xFFFF)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="151"/>
+ <source>The %s filter expression &apos;%s&apos; is not valid</source>
+ <translation>ÐедопуÑтимое выражение фильтра %s &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="156"/>
+ <source>Insufficient expression space for the &apos;%s&apos; filter expression &apos;%s&apos;</source>
+ <translation>ÐедоÑтаточно проÑтраÑтва Ð´Ð»Ñ Ð²Ñ‹Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ñ„Ð¸Ð»ÑŒÑ‚Ñ€Ð° &apos;%s&apos; &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="161"/>
+ <source>Encountered unexpected status %Rrc when setting &apos;%s&apos; to &apos;%s&apos;</source>
+ <translation>Обнаружен неожиданный ÑÑ‚Ð°Ñ‚ÑƒÑ %Rrc во Ð²Ñ€ÐµÐ¼Ñ ÑƒÑтановки &apos;%s&apos; в &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="616"/>
+ <source>Remote state filter string &apos;%s&apos; is not valid (error at position %d)</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ñтрока фильтра удаленного ÑоÑтоÑÐ½Ð¸Ñ &apos;%s&apos; (ошибка в позиции %d)</translation>
+ </message>
+</context>
+<context>
+ <name>USBDeviceFilters</name>
+ <message>
+ <location filename="../src-server/USBDeviceFiltersImpl.cpp" line="364"/>
+ <source>The given USB device pFilter is already in the list</source>
+ <translation>Данный pFilter USB уÑтройÑтва уже в ÑпиÑке</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFiltersImpl.cpp" line="421"/>
+ <source>The USB device pFilter list is empty</source>
+ <translation>СпиÑок pFilter USB уÑтройÑтв пуÑÑ‚</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFiltersImpl.cpp" line="425"/>
+ <source>Invalid position: %lu (must be in range [0, %lu])</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ: %lu (должна быть в диапазоне [0, %lu])</translation>
+ </message>
+</context>
+<context>
+ <name>USBProxyService</name>
+ <message>
+ <location filename="../src-server/USBProxyService.cpp" line="253"/>
+ <source>The USB device source &quot;%s&quot; could not be found</source>
+ <translation>ИÑточник USB уÑтройÑтва &quot;%s&quot; не найден</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBProxyService.cpp" line="286"/>
+ <source>The USB device with UUID {%RTuuid} is not currently attached to the host</source>
+ <translation>USB уÑтройÑтво Ñ UUID {%RTuuid} не подключен к хоÑту в наÑтоÑщее времÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBProxyService.cpp" line="922"/>
+ <source>The USB device source &quot;%s&quot; exists already</source>
+ <translation>ИÑточник USB уÑтройÑтва &quot;%s&quot; уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBProxyService.cpp" line="934"/>
+ <source>Creating the USB device source &quot;%s&quot; using backend &quot;%s&quot; failed with %Rrc</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать иÑточник USB уÑтройÑтва &quot;%s&quot; иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ using бÑкÑнд &quot;%s&quot; %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBProxyService.cpp" line="941"/>
+ <source>The USB backend &quot;%s&quot; is not supported</source>
+ <translation>USB бÑкÑнд &quot;%s&quot; не поддерживаетÑÑ</translation>
+ </message>
+</context>
+<context>
+ <name>UefiVariableStore</name>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="171"/>
+ <source>The &apos;SecureBootEnable&apos; variable size is bogus (expected 1, got %llu)</source>
+ <translation>Размер переменной &apos;SecureBootEnable&apos; неправильный (ожидаетÑÑ 1, получено %llu)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="174"/>
+ <source>Failed to query the &apos;SecureBootEnable&apos; variable size: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить размер переменной &apos;SecureBootEnable&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="179"/>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="212"/>
+ <source>Failed to query the platform key variable size: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить размер переменной ключ платформы: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="210"/>
+ <source>Secure boot is not available because the platform key (PK) is not enrolled</source>
+ <translation>Secure boot недоÑтупен, потому что ключ платформы (PK) не зарегиÑтрирован</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="266"/>
+ <source>Failed to remove variable &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ удалить переменную &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="269"/>
+ <source>Failed to open the variable store root (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть корень хранилища переменных (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="272"/>
+ <source>The variable name is too long</source>
+ <translation>Ð˜Ð¼Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð¾Ð¹ Ñлишком длинное</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="299"/>
+ <source>Failed to data for variable &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ запиÑать данные переменной &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="302"/>
+ <source>Failed to allocate space for the variable &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно выделить проÑтранÑтво Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð¾Ð¹ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="346"/>
+ <source>Failed to query the size of variable &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить размер переменной &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="349"/>
+ <source>Failed to query the owner UUID of variable &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить UUID владельца переменной &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="352"/>
+ <source>Failed to query the attributes of variable &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить аттрибуты переменной &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="402"/>
+ <source>Failed to query the variables: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запроÑить переменные: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="782"/>
+ <source>Opening variable storage root directory failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ открыть корневую директорию хранилища переменных: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="794"/>
+ <source>Creating the variable &apos;%s&apos; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать переменную &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="820"/>
+ <source>The variable &apos;%s&apos; could not be found</source>
+ <translation>Ðевозможно найти переменную &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="822"/>
+ <source>Couldn&apos;t open variable &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно открыть переменную &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="837"/>
+ <source>Setting the variable &apos;%s&apos; failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ уÑтановить переменную &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="862"/>
+ <source>Failed to read data of variable &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ прочитать данные переменной &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="867"/>
+ <source>Failed to open variable &apos;%s&apos; for reading: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ открыть переменную &apos;%s&apos; Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="886"/>
+ <source>The given signature type is not supported</source>
+ <translation>Данный тип подпиÑи не поддерживаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="893"/>
+ <source>Failed to add signature to the database (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ добавить подпиÑÑŒ в базу данных (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="926"/>
+ <source>Writing updated signature database failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ запиÑать обновленную подпиÑÑŒ в базу данных: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="930"/>
+ <source>Loading signature database failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ загрузить базу данных подпиÑей: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="935"/>
+ <source>Creating signature database failed: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать базу данных подпиÑей: %Rrc</translation>
+ </message>
+</context>
+<context>
+ <name>Unattended</name>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="249"/>
+ <source>Failed to open &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="265"/>
+ <source>Failed to open &apos;%s&apos; as ISO FS (%Rrc) - %s</source>
+ <translation>Ðе удалоÑÑŒ открыть &apos;%s&apos; как файловую ÑиÑтему ISO (%Rrc) - %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="268"/>
+ <source>Failed to open &apos;%s&apos; as ISO FS (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть &apos;%s&apos; как файловую ÑиÑтему ISO (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1196"/>
+ <source>No machine associated with this IUnatteded instance</source>
+ <translation>С Ñтим ÑкземплÑром IUnatteded не ÑвÑзана ни одна машина</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1253"/>
+ <source>The prepare method has been called (must call done to restart)</source>
+ <translation>Вызван метод prepare (Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸ нужно вызвать done)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1255"/>
+ <source>The &apos;machine&apos; while we were using it - please don&apos;t do that</source>
+ <translation>Машина изменилаÑÑŒ пока мы ее иÑпользовали - пожалуйÑта, не делайте так</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1261"/>
+ <source>Could not locate the installation ISO file &apos;%s&apos;</source>
+ <translation>Ðевозможно определить меÑтоположение уÑтановочного ISO файла &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1264"/>
+ <source>Could not locate the Guest Additions ISO file &apos;%s&apos;</source>
+ <translation>Ðевозможно определить меÑтоположение ISO файла Дополнений ГоÑтевой ОС &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1267"/>
+ <source>Could not locate the validation kit ISO file &apos;%s&apos;</source>
+ <translation>Ðевозможно определить меÑтоположение ISO файла комплекта Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1270"/>
+ <source>Could not locate unattended installation script template &apos;%s&apos;</source>
+ <translation>Ðевозможно определить меÑтоположение шаблона Ñкрипта unattended уÑтановки &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1398"/>
+ <source>Unattended installation is not supported for guest type &apos;%s&apos;</source>
+ <translation>Unattended уÑтановка не поддерживаетÑÑ Ð´Ð»Ñ Ñ‚Ð¸Ð¿Ð° гоÑтевой ÑиÑтемы &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1514"/>
+ <source>reconfigureVM running on other thread</source>
+ <translation>reconfigureVM запущена на другом потоке</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1523"/>
+ <source>prepare() not yet called</source>
+ <translation>prepare() еще не вызван</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1678"/>
+ <source>Found non-floppy device attached to port 0 device 0 on the floppy controller &apos;%ls&apos;</source>
+ <translation>Ðе найдено флоппи уÑтройÑтв подключенных к порту 0 уÑтройÑтва 0 флоппи контроллера &apos;%ls&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1825"/>
+ <source>Support for recommended storage bus %d not implemented</source>
+ <translation>Поддержка рекомендованной шины %d не реализована</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/UnattendedImpl.cpp" line="1844"/>
+ <source>Not enough free slots on controller &apos;%s&apos; to add %u DVD drive(s)</source>
+ <translation>
+ <numerusform>Ðет доÑтаточно Ñвободных Ñлотов на контроллере &apos;%s&apos; Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ %u DVD диÑка</numerusform>
+ <numerusform>Ðет доÑтаточно Ñвободных Ñлотов на контроллере &apos;%s&apos; Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ %u DVD диÑков</numerusform>
+ <numerusform>Ðет доÑтаточно Ñвободных Ñлотов на контроллере &apos;%s&apos; Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ %u DVD диÑков</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2032"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2049"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2064"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2079"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2094"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2109"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2124"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2139"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2154"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2169"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2184"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2209"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2224"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2246"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2275"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2357"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2377"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2393"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2422"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2447"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2466"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2481"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2500"/>
+ <source>Cannot change after prepare() has been called</source>
+ <translation>Ðевозможно изменить поÑле того, как prepare() вызван</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2196"/>
+ <source>Expected two lower cased letters, an underscore, and two upper cased letters</source>
+ <translation>ОжидаетÑÑ Ð´Ð²Ðµ буквы в нижнем региÑтре, Ñимвол подчеркиваниÑ, и две буквы в верхнем региÑтре</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2233"/>
+ <source>Expected two upper cased letters</source>
+ <translation>ОжидаетÑÑ Ð´Ð²Ðµ букве в верхнем региÑтре</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2286"/>
+ <source>Unknown keyword: %s</source>
+ <translation>ÐеизвеÑтный Ñимвол: %s</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/UnattendedImpl.cpp" line="2307"/>
+ <source>Hostname &apos;%s&apos; is %zu bytes long, max is 253 (excluding trailing dot)</source>
+ <translation>
+ <numerusform>Ð˜Ð¼Ñ Ñ…Ð¾Ñта &apos;%s&apos; имеет длину %zu байт, макÑимально допуÑтимо 253 (иÑÐºÐ»ÑŽÑ‡Ð°Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐ°ÑŽÑ‰ÑƒÑŽ точку)</numerusform>
+ <numerusform>Ð˜Ð¼Ñ Ñ…Ð¾Ñта &apos;%s&apos; имеет длину %zu байта, макÑимально допуÑтимо 253 (иÑÐºÐ»ÑŽÑ‡Ð°Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐ°ÑŽÑ‰ÑƒÑŽ точку)</numerusform>
+ <numerusform>Ð˜Ð¼Ñ Ñ…Ð¾Ñта &apos;%s&apos; имеет длину %zu байт, макÑимально допуÑтимо 253 (иÑÐºÐ»ÑŽÑ‡Ð°Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐ°ÑŽÑ‰ÑƒÑŽ точку)</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2326"/>
+ <source>Invalid hostname &apos;%s&apos; - label %u is too long, max is 63.</source>
+ <translation>ÐедопуÑтимое Ð¸Ð¼Ñ Ñ…Ð¾Ñта &apos;%s&apos; - метка %u Ñлишком длиннаÑ, макÑимально допуÑтимо 63.</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2331"/>
+ <source>Invalid hostname &apos;%s&apos; - illegal char &apos;%c&apos; at position %zu</source>
+ <translation>ÐедопуÑтимое Ð¸Ð¼Ñ Ñ…Ð¾Ñта &apos;%s&apos; - недопуÑтимый Ñимвол &apos;%c&apos; в позиции %zu</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2336"/>
+ <source>Invalid hostname &apos;%s&apos; - the name part must be at least two characters long</source>
+ <translation>ÐедопуÑтимое Ð¸Ð¼Ñ Ñ…Ð¾Ñта &apos;%s&apos; - чаÑÑ‚ÑŒ Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ должна иметь длину Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ два Ñимвола</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2343"/>
+ <source>Invalid hostname &apos;%s&apos; - illegal lead char &apos;%c&apos; at position %zu</source>
+ <translation>ÐедопуÑтимое Ð¸Ð¼Ñ Ñ…Ð¾Ñта &apos;%s&apos; - недопуÑтимый ведущий Ñимвол &apos;%c&apos; в позиции %zu</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2347"/>
+ <source>Invalid hostname &apos;%s&apos; - trailing dot not permitted</source>
+ <translation>ÐедопуÑтимое Ð¸Ð¼Ñ Ñ…Ð¾Ñта &apos;%s&apos; - Ð·Ð°Ð²ÐµÑ€ÑˆÐ°ÑŽÑ‰Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° не допуÑкаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2351"/>
+ <source>Incomplete hostname &apos;%s&apos; - must include both a name and a domain</source>
+ <translation>ÐедопуÑтимое Ð¸Ð¼Ñ Ñ…Ð¾Ñта &apos;%s&apos; - должно включать как Ð¸Ð¼Ñ Ñ‚Ð°Ðº домен</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2372"/>
+ <source>Empty base path is not allowed</source>
+ <translation>ПуÑтой базовый путь не допуÑкаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2374"/>
+ <source>Base path must be absolute</source>
+ <translation>Базовый путь должен быть абÑолютным</translation>
+ </message>
+</context>
+<context>
+ <name>UnattendedInstaller</name>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="167"/>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="185"/>
+ <source>Failed to construct path to the unattended installer script templates (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ Ñобрать путь к шаблонам Ñкриптов unattended уÑтановщика (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="210"/>
+ <source>Cannot proceed with an empty installation ISO path</source>
+ <translation>Ðевозможно продолжать Ñ Ð¿ÑƒÑтым путем к ISO диÑтрибутива</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="212"/>
+ <source>Empty user name is not allowed</source>
+ <translation>ПуÑтое Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ðµ допуÑкаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="214"/>
+ <source>Empty password is not allowed</source>
+ <translation>ПуÑтой пароль не допуÑкаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="374"/>
+ <source>Failed to open newly created floppy image &apos;%s&apos;: %Rrc: %s</source>
+ <translation>Ðе удалоÑÑŒ открыть только что Ñозданный образ флоппи &apos;%s&apos;: %Rrc %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="377"/>
+ <source>Failed to open newly created floppy image &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ открыть только что Ñозданный образ флоппи &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="381"/>
+ <source>Failed to format floppy image &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ отформатировать образ флоппи &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="386"/>
+ <source>Failed to create floppy image &apos;%s&apos;: %Rrc</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать образ флоппи &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/UnattendedInstaller.cpp" line="427"/>
+ <source>Error writing %zu bytes to &apos;%s&apos; in floppy image &apos;%s&apos;: %Rrc</source>
+ <translation>
+ <numerusform>Ощибка запиÑи %zu байт в &apos;%s&apos; в образе флоппи &apos;%s&apos;: %Rrc</numerusform>
+ <numerusform>Ощибка запиÑи %zu байта в &apos;%s&apos; в образе флоппи &apos;%s&apos;: %Rrc</numerusform>
+ <numerusform>Ощибка запиÑи %zu байт в &apos;%s&apos; в образе флоппи &apos;%s&apos;: %Rrc</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="436"/>
+ <source>Error creating &apos;%s&apos; in floppy image &apos;%s&apos;: %Rrc</source>
+ <translation>Ошибка ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ &apos;%s&apos; в образе флоппи &apos;%s&apos;: %Rrc</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="508"/>
+ <source>Failed to open ISO image &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть ISO образ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="517"/>
+ <source>ISO reader fail to open &apos;%s&apos; (%Rrc): %s</source>
+ <translation>Считывателю ISO не удалоÑÑŒ открыть &apos;%s&apos; (%Rrc): %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="519"/>
+ <source>ISO reader fail to open &apos;%s&apos; (%Rrc)</source>
+ <translation>Считывателю ISO не удалоÑÑŒ открыть &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="527"/>
+ <source>RTFsIsoMakerCreate failed (%Rrc)</source>
+ <translation>RTFsIsoMakerCreate завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="586"/>
+ <source>RTFsIsoMakerAddFileWithVfsFile failed on the script &apos;%s&apos; (%Rrc)</source>
+ <translation>RTFsIsoMakerAddFileWithVfsFile завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ при обработке Ñкрипта &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/UnattendedInstaller.cpp" line="591"/>
+ <source>RTVfsFileFromBuffer failed on the %zu byte script &apos;%s&apos; (%Rrc)</source>
+ <translation>
+ <numerusform>RTVfsFileFromBuffer завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ при обработке %zu байт Ñкрипта &apos;%s&apos; (%Rrc)</numerusform>
+ <numerusform>RTVfsFileFromBuffer завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ при обработке %zu байта Ñкрипта &apos;%s&apos; (%Rrc)</numerusform>
+ <numerusform>RTVfsFileFromBuffer завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ при обработке %zu байт Ñкрипта &apos;%s&apos; (%Rrc)</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="604"/>
+ <source>RTFsIsoMakerFinalize failed (%Rrc)</source>
+ <translation>RTFsIsoMakerFinalize завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="619"/>
+ <source>The auxiliary ISO image file &apos;%s&apos; already exists</source>
+ <translation>Ð’Ñпомогательный файл образа ISO &apos;%s&apos; уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="621"/>
+ <source>Failed to open the auxiliary ISO image file &apos;%s&apos; for writing (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть вÑпомогательный файл образа ISO &apos;%s&apos; на запиÑÑŒ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="642"/>
+ <source>Error writing auxiliary ISO image &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка запиÑи вÑпомогательного образа ISO &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="647"/>
+ <source>Internal Error: Failed to case VFS file to VFS I/O stream</source>
+ <translation>ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ°: Ðе удалоÑÑŒ привеÑти VFS файл к VFS I/O потоку</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="652"/>
+ <source>RTFsIsoMakerCreateVfsOutputFile failed (%Rrc)</source>
+ <translation>RTFsIsoMakerCreateVfsOutputFile завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="760"/>
+ <source>RTGetOptArgvToString failed (%Rrc)</source>
+ <translation>RTGetOptArgvToString завершилÑÑ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹ (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="783"/>
+ <source>Error writing &apos;%s&apos; (%Rrc)</source>
+ <translation>Ошибка запиÑи &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="786"/>
+ <source>Failed to create &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="805"/>
+ <source>Failed to open &apos;%s&apos; on the ISO &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть &apos;%s&apos; в ISO &apos;%s&apos; (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>UnattendedSUSEXMLScript</name>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="575"/>
+ <source>XML document root element is &apos;%s&apos; instead of &apos;profile&apos;</source>
+ <translation>Корневым Ñлементом документа XML ÑвлÑетÑÑ &apos;%s&apos; вмеÑто &apos;profile&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="578"/>
+ <source>Missing XML root element</source>
+ <translation>ОтÑутÑтвует корневой Ñлемент XML</translation>
+ </message>
+</context>
+<context>
+ <name>UnattendedScriptTemplate</name>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="133"/>
+ <source>Malformed template placeholder &apos;%.*s&apos;</source>
+ <translation>ÐÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð°Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ð´Ñтановка &apos;%.*s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="181"/>
+ <source>%s without @@VBOX_COND_XXX@@ at offset %zu (%#zx)</source>
+ <translation>%s без @@VBOX_COND_XXX@@ в позиции %zu (%#zx)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="207"/>
+ <source>Too deep conditional nesting at offset %zu (%#zx)</source>
+ <translation>Слишком Ð³Ð»ÑƒÐ±Ð¾ÐºÐ°Ñ Ð²Ð»Ð¾Ð¶ÐµÐ½Ð½Ð¾ÑÑ‚ÑŒ по уÑловию в позиции %zu (%#zx)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="222"/>
+ <source>Missing @@VBOX_COND_END@@</source>
+ <translation>ОтÑутÑтвует @@VBOX_COND_END@@</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="224"/>
+ <source>Missing %u @@VBOX_COND_END@@</source>
+ <translation>ОтÑутÑтвует %u @@VBOX_COND_END@@</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="396"/>
+ <source>Unknown guest OS major version &apos;%s&apos;</source>
+ <translation>ÐеизвеÑÑ‚Ð½Ð°Ñ Ð¼Ð°Ð¶Ð¾Ñ€Ð½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ð³Ð¾Ñтевой ОС &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="445"/>
+ <source>Unknown template placeholder &apos;%.*s&apos;</source>
+ <translation>ÐеизвеÑÑ‚Ð½Ð°Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ð´Ñтановка &apos;%.*s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="535"/>
+ <source>Unknown conditional placeholder &apos;%.*s&apos;</source>
+ <translation>ÐеизвеÑÑ‚Ð½Ð°Ñ ÑƒÑÐ»Ð¾Ð²Ð½Ð°Ñ Ð¿Ð¾Ð´Ñтановка &apos;%.*s&apos;</translation>
+ </message>
+</context>
+<context>
+ <name>VBoxEvent</name>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="131"/>
+ <source>Internal error (%Rrc)</source>
+ <translation>ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ° (%Rrc)</translation>
+ </message>
+</context>
+<context>
+ <name>VFSExplorer</name>
+ <message>
+ <location filename="../src-server/VFSExplorerImpl.cpp" line="315"/>
+ <source>Can&apos;t open directory &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ открыть директорию &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VFSExplorerImpl.cpp" line="358"/>
+ <source>Internal Error (%Rrc)</source>
+ <translation>ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ° (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VFSExplorerImpl.cpp" line="361"/>
+ <source>Can&apos;t delete file &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно удалить файл &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VFSExplorerImpl.cpp" line="391"/>
+ <source>Update directory info for &apos;%s&apos;</source>
+ <translation>Обновление информации о директории Ð´Ð»Ñ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VFSExplorerImpl.cpp" line="512"/>
+ <source>Delete files</source>
+ <translation>Удаление файлов</translation>
+ </message>
+</context>
+<context>
+ <name>VRDEServer</name>
+ <message>
+ <location filename="../src-server/VRDEServerImpl.cpp" line="697"/>
+ <source>failed to query the library setting
+</source>
+ <translation>не удалоÑÑŒ запроÑить наÑтройки библиотеки
+</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VRDEServerImpl.cpp" line="819"/>
+ <location filename="../src-server/VRDEServerImpl.cpp" line="864"/>
+ <source>Extension pack &apos;%s&apos; does not exist</source>
+ <translation>Пакет раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ &apos;%s&apos; не ÑущеÑтвует</translation>
+ </message>
+</context>
+<context>
+ <name>VirtualBox</name>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="545"/>
+ <source>Could not create the VirtualBox home directory &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать домашнюю директорию VirtualBox &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="1396"/>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2609"/>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2615"/>
+ <source>Not yet implemented</source>
+ <translation>Еще не реализован</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="1931"/>
+ <source>Machine name is invalid, must not be empty</source>
+ <translation>ÐедопуÑтимое Ð¸Ð¼Ñ Ð¼Ð°ÑˆÐ¸Ð½Ñ‹, не должно быть пуÑтым</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="1960"/>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2174"/>
+ <source>&apos;%s&apos; is not a valid Guid</source>
+ <translation>%s - Ñто недопуÑтимый Guid</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2461"/>
+ <source>Format must be Valid Type%s</source>
+ <translation>Формат должен быть допуÑтимым типом %s</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2477"/>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2534"/>
+ <source>Device type must be HardDisk, DVD or Floppy %d</source>
+ <translation>Тип уÑтройÑтва должен быть ЖеÑткий диÑк, DVD или Флоппи %d</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2703"/>
+ <source>Could not set extra data because someone refused the requested change of &apos;%s&apos; to &apos;%s&apos;%s%ls</source>
+ <translation>Ðевозможно уÑтановить ÑкÑтра данные, потому что кто-то отклонил запрошенное изменение &apos;%s&apos; в &apos;%s&apos;%s%ls</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3270"/>
+ <source>Could not create the communication channel (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать коммуникационный канал (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3279"/>
+ <source>Cannot get executable name</source>
+ <translation>Ðевозможно получить Ð¸Ð¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½Ñемого файла</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3315"/>
+ <source>Operation canceled by the user</source>
+ <translation>ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð¼ÐµÐ½ÐµÐ½Ð° пользователем</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3317"/>
+ <source>Could not launch a privileged process &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно запуÑтить привелегированный процеÑÑ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3327"/>
+ <source>Could not launch a process &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно запуÑтить процеÑÑ &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3350"/>
+ <source>Could not operate the communication channel (%Rrc)</source>
+ <translation>Ðевозможно работать по коммуникационному каналу (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3901"/>
+ <source>Could not find a registered machine with UUID {%RTuuid}</source>
+ <translation>Ðевозможно найти зарегиÑтрированную машину Ñ UUID {%RTuuid}</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3951"/>
+ <source>Could not find a registered machine named &apos;%s&apos;</source>
+ <translation>Ðевозможно найти зарегиÑтрированную машину Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4029"/>
+ <source>Machine group &apos;%s&apos; conflicts with a virtual machine name</source>
+ <translation>Группа машин &apos;%s&apos; конфликтует Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ виртуальной машины</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4033"/>
+ <source>Invalid machine group &apos;%s&apos;</source>
+ <translation>ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° машин &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4109"/>
+ <source>Could not find an open hard disk with UUID {%RTuuid}</source>
+ <translation>Ðевозможно найти открытый жеÑткий диÑк Ñ UUID {%RTuuid}</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4161"/>
+ <source>Could not find an open hard disk with location &apos;%s&apos;</source>
+ <translation>Ðевозможно найти открытый жеÑткий диÑк Ñ Ð¼ÐµÑтоположением &apos;%s&apos;</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4198"/>
+ <source>Invalid image file location &apos;%s&apos; (%Rrc)</source>
+ <translation>ÐедопуÑтимое меÑтоположение файла образа &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4243"/>
+ <source>Cannot mount DVD medium &apos;%s&apos; as floppy</source>
+ <translation>Ðевозможно подключить DVD ноÑитель &apos;%s&apos; как флоппи</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4246"/>
+ <source>Cannot mount floppy medium &apos;%s&apos; as DVD</source>
+ <translation>Ðевозможно подключить флоппи ноÑитель &apos;%s&apos; как DVD</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4261"/>
+ <source>Could not find an image file with UUID {%RTuuid} in the media registry (&apos;%s&apos;)</source>
+ <translation>Ðевозможно найти файл образа Ñ UUID {%RTuuid} в рееÑтре ноÑителей (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4266"/>
+ <source>Could not find an image file with location &apos;%s&apos; in the media registry (&apos;%s&apos;)</source>
+ <translation>Ðевозможно найти файл образа Ñ Ð¼ÐµÑтоположением &apos;%s&apos; в рееÑтре ноÑителей (&apos;%s&apos;)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4311"/>
+ <source>Guid &apos;%s&apos; is invalid</source>
+ <translation>Guid &apos;%s&apos; недопуÑтим</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4351"/>
+ <source>&apos;%s&apos; is not a valid Guest OS type</source>
+ <translation>&apos;%s&apos; не ÑвлÑетÑÑ Ð´Ð¾Ð¿ÑƒÑтимым типом гоÑтевой ОС</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4538"/>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5091"/>
+ <source>hard disk</source>
+ <translation>жеÑткий диÑк</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4544"/>
+ <source>CD/DVD image</source>
+ <translation>Образ CD/DVD</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4551"/>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5098"/>
+ <source>floppy image</source>
+ <translation>образ флоппи</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4567"/>
+ <source>%s &apos;%s&apos; with UUID {%RTuuid}</source>
+ <translation>%s &apos;%s&apos; Ñ UUID {%RTuuid}</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5018"/>
+ <source>Registered machine with UUID {%RTuuid} (&apos;%s&apos;) already exists</source>
+ <translation>ЗарегиÑÑ‚Ñ€Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð° Ñ UUID {%RTuuid} (&apos;%s&apos;) уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5094"/>
+ <source>DVD image</source>
+ <translation>Образ DVD</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5129"/>
+ <source>Cannot register the %s &apos;%s&apos; {%RTuuid} because a %s already exists</source>
+ <translation>Ðевозможно зарегиÑтрировать %s &apos;%s&apos; {%RTuuid}, потому что %s уже ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5555"/>
+ <source>Could not create the directory &apos;%s&apos; (%Rrc)</source>
+ <translation>Ðевозможно Ñоздать директорию &apos;%s&apos; (%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5561"/>
+ <source>Directory &apos;%s&apos; does not exist</source>
+ <translation>Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ &apos;%s&apos; не ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="6080"/>
+ <source>The provided progress object GUID is invalid</source>
+ <translation>ÐедопуÑтимый предоÑтавленный GUID объекта прогреÑÑа</translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="6092"/>
+ <source>The progress object with the given GUID could not be found</source>
+ <translation>Объект прогреÑÑа Ñ Ð´Ð°Ð½Ð½Ñ‹Ð¼ GUID не найден</translation>
+ </message>
+</context>
+<context>
+ <name>VirtualBoxBase</name>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="176"/>
+ <source>Assertion failed: [%s] at &apos;%s&apos; (%d) in %s.
+Please contact the product vendor!</source>
+ <translation>Assertion failed: [%s] в &apos;%s&apos; (%d) в %s.
+ПожалуйÑта, обратитеÑÑŒ к поÑтавщику продукта!</translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="196"/>
+ <source>Assertion failed: at &apos;%s&apos; (%d) in %s.
+Please contact the product vendor!</source>
+ <translation>Assertion failed: в &apos;%s&apos; (%d) в %s.
+ПожалуйÑта, обратитеÑÑŒ к поÑтавщику продукта!</translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="218"/>
+ <source>Assertion failed: [%s] at &apos;%s&apos; (%d) in %s.
+%s.
+Please contact the product vendor!</source>
+ <translation>Assertion failed: [%s] в &apos;%s&apos; (%d) в %s.
+%s.
+ПожалуйÑта, обратитеÑÑŒ к поÑтавщику продукта!</translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="236"/>
+ <source>Assertion failed: at &apos;%s&apos; (%d) in %s.
+%s.
+Please contact the product vendor!</source>
+ <translation>Assertion failed: в &apos;%s&apos; (%d) в %s.
+%s.
+ПожалуйÑта, обратитеÑÑŒ к поÑтавщику продукта!</translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="366"/>
+ <location filename="../include/VirtualBoxBase.h" line="409"/>
+ <source>Argument %s is NULL</source>
+ <translation>Ðргумент %s измеет значение ноль</translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="380"/>
+ <location filename="../include/VirtualBoxBase.h" line="424"/>
+ <source>Argument %s is an invalid pointer</source>
+ <translation>Ðргумент %s ÑвлÑетÑÑ Ð½ÐµÐ´ÐµÐ¹Ñтвительным указателем</translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="394"/>
+ <source>Argument %s points to invalid memory location (%p)</source>
+ <translation>Ðргумент %s указывает на недопуÑтимый Ð°Ð´Ñ€ÐµÑ Ð² памÑти (%p)</translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="438"/>
+ <source>Argument %s is empty or an invalid pointer</source>
+ <translation>Ðргумент %s пуÑтой или недейÑтвительный указатель</translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="457"/>
+ <source>GUID argument %s is not valid (&quot;%ls&quot;)</source>
+ <translation>GUID аргумент %s недопуÑтим (&quot;%ls&quot;)</translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="473"/>
+ <source>Argument %s is invalid (must be %s)</source>
+ <translation>Ðргумент %s недейÑтвительный (должен быть %s)</translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="491"/>
+ <source>Argument %s %s</source>
+ <translation>Ðргумент %s %s</translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="506"/>
+ <location filename="../include/VirtualBoxBase.h" line="521"/>
+ <location filename="../include/Wrapper.h" line="42"/>
+ <source>Output argument %s points to invalid memory location (%p)</source>
+ <translation>Выходной аргумент %s указывает на недопуÑтимый Ð°Ð´Ñ€ÐµÑ Ð² памÑти (%p)</translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="530"/>
+ <source>Method %s is not implemented</source>
+ <translation>Метод %s не реализован</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="242"/>
+ <source>Unexpected exception: %s [%s]
+%s[%d] (%s)</source>
+ <translation>Ðеожиданное иÑключение: %s [%s]
+%s[%d] (%s)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="251"/>
+ <source>Unknown exception
+%s[%d] (%s)</source>
+ <translation>ÐеизвеÑтное иÑключение
+%s[%d] (%s)</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="348"/>
+ <source>A parameter has an invalid value</source>
+ <translation>У параметра недопуÑтимое значение</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="349"/>
+ <source>A parameter is an invalid pointer</source>
+ <translation>Параметр ÑвлÑетÑÑ Ð½ÐµÐ´Ð¾Ð¿ÑƒÑтимым указателем</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="350"/>
+ <source>The result of the operation is unexpected</source>
+ <translation>Ðеожиданный результат операции</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="351"/>
+ <source>The access to an object is not allowed</source>
+ <translation>ДоÑтуп к объекту не допуÑкаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="352"/>
+ <source>The allocation of new memory failed</source>
+ <translation>Ðе удалоÑÑŒ выделить новую памÑÑ‚ÑŒ</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="353"/>
+ <source>The requested operation is not implemented</source>
+ <translation>Ð—Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð½Ðµ реализована</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="354"/>
+ <source>The requested interface is not implemented</source>
+ <translation>Запрошенный Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð½Ðµ реализован</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="355"/>
+ <source>A general error occurred</source>
+ <translation>Произошла Ð¾Ð±Ñ‰Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="356"/>
+ <source>The operation was canceled</source>
+ <translation>ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð¼ÐµÐ½ÐµÐ½Ð°</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="357"/>
+ <source>Object corresponding to the supplied arguments does not exist</source>
+ <translation>Объект, ÑоответÑтвующий переданным аргументам, не ÑущеÑтвует</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="358"/>
+ <source>Current virtual machine state prevents the operation</source>
+ <translation>Текущее ÑоÑтоÑние виртуальной машины не позволÑет провеÑти операцию</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="359"/>
+ <source>Virtual machine error occurred attempting the operation</source>
+ <translation>Произошла ошибка виртуальной машины при попытке произвеÑти операцию</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="360"/>
+ <source>File not accessible or erroneous file contents</source>
+ <translation>Файл недоÑтупен или ошибочное Ñодержимое файла</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="361"/>
+ <source>Runtime subsystem error</source>
+ <translation>Ошибка подÑиÑтемы runtime</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="362"/>
+ <source>Pluggable Device Manager error</source>
+ <translation>Ошибка подключаемого менеджера уÑтройÑтв</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="363"/>
+ <source>Current object state prohibits operation</source>
+ <translation>Текущее ÑоÑтоÑние объекта запрещает операцию</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="364"/>
+ <source>Host operating system related error</source>
+ <translation>Ошибка отноÑительно операционной ÑиÑтемы хоÑта</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="365"/>
+ <source>Requested operation is not supported</source>
+ <translation>Ð—Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð½Ðµ поддерживаетÑÑ</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="366"/>
+ <source>Invalid XML found</source>
+ <translation>Ðайден недейÑтвительный XML</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="367"/>
+ <source>Current session state prohibits operation</source>
+ <translation>Текущее ÑоÑтоÑние ÑеÑÑии запрещает операцию</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="368"/>
+ <source>Object being in use prohibits operation</source>
+ <translation>Объект иÑпользуетÑÑ Ð¸ запрещает операцию</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="369"/>
+ <source>Incorrect password provided</source>
+ <translation>ПредоÑтавлен неправильный пароль</translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="370"/>
+ <source>Unknown error</source>
+ <translation>ÐеизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°</translation>
+ </message>
+</context>
+<context>
+ <name>VirtualBoxClient</name>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="247"/>
+ <source>Failed to create semaphore (rc=%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать Ñемафор (rc=%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="256"/>
+ <source>Failed to create watcher thread (rc=%Rrc)</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать поток Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ (rc=%Rrc)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="307"/>
+ <source>VBoxSDS is misconfigured to run under the &apos;%ls&apos; account instead of the SYSTEM one.
+Reinstall VirtualBox to fix it. Alternatively you can fix it using the Windows Service Control Manager or by running &apos;sc config VBoxSDS obj=LocalSystem&apos; on a command line.</source>
+ <translation>VBoxSDS Ñконфигурирован, чтобы запуÑкатьÑÑ Ð¿Ð¾Ð´ аккаунтом &apos;%ls&apos; вмеÑто SYSTEM, что не ÑвлÑетÑÑ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ñ‹Ð¼.
+Ð”Ð»Ñ Ð¸ÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¸, переуÑтановите VirtualBox. Также, можно иÑправить Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ Windows Service Control Manager или путем запуÑка &apos;sc config VBoxSDS obj=LocalSystem&apos; в командной Ñтроке.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="312"/>
+ <source>The VBoxSDS windows service is disabled.
+Reinstall VirtualBox to fix it. Alternatively try reenable the service by setting it to &apos;Manual&apos; startup type in the Windows Service management console, or by runing &apos;sc config VBoxSDS start=demand&apos; on the command line.</source>
+ <translation>Windows Ñлужба VBoxSDS отключена.
+Ð”Ð»Ñ Ð¸ÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¸, переуÑтановите VirtualBox. Также, попытайтеÑÑŒ включить Ñлужбу путем уÑтановки типа автозапуÑка в &apos;Вручную&apos; в Windows Service management console, или путем запуÑка &apos;sc config VBoxSDS start=demand&apos; в командной Ñтроке.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="319"/>
+ <source>The VBoxSDS windows service was not found.
+Reinstall VirtualBox to fix it. Alternatively you can try start VirtualBox as Administrator, this should automatically reinstall the service, or you can run &apos;VBoxSDS.exe --regservice&apos; command from an elevated Administrator command line.</source>
+ <translation>Windows Ñлужба VBoxSDS не найдена.
+Ð”Ð»Ñ Ð¸ÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¸, переуÑтановите VirtualBox. Также, вы можете попытатьÑÑ Ð·Ð°Ð¿ÑƒÑтить VirtualBox как ÐдминиÑтратор, Ñто должно автоматичеÑки переуÑтановить Ñлужбу, или вы можете запуÑтить команду &apos;VBoxSDS.exe --regservice&apos; из командной Ñтроки Ñ Ð¿Ð¾Ð²Ñ‹ÑˆÐµÐ½Ð½Ñ‹Ð¼Ð¸ правами.</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="339"/>
+ <source>Completely failed to instantiate CLSID_VirtualBox: %Rhrc</source>
+ <translation>Ðе удалоÑÑŒ вообще Ñоздать ÑкземплÑÑ€ CLSID_VirtualBox: %Rhrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="340"/>
+ <source>Completely failed to instantiate CLSID_VirtualBox: %Rhrc &amp; %Rhrc</source>
+ <translation>Ðе удалоÑÑŒ вообще Ñоздать ÑкземплÑÑ€ CLSID_VirtualBox: %Rhrc &amp; %Rhrc</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="354"/>
+ <source>Failed to instantiate CLSID_VirtualBox the first time, but worked when checking out why ... weird</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать ÑкземплÑÑ€ CLSID_VirtualBox в первый раз, но Ñработало, когда Ñтали проверÑÑ‚ÑŒ почему... Ñтранно</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="441"/>
+ <source>Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.
+PSDispatch looks broken by the &apos;%ls&apos; (%ls) program, suspecting that it features the broken oleaut32.msm module as component %ls.
+
+We suggest you try uninstall &apos;%ls&apos;.
+
+See also https://support.microsoft.com/en-us/kb/316911 </source>
+ <translation>Ðе удалоÑÑŒ Ñоздать ÑкземплÑÑ€ CLSID_VirtualBox через IVirtualBox, но CLSID_VirtualBox через IUnknown работает.
+Похоже PSDispatch Ñломан из-за программы&apos;%ls&apos; (%ls), подозреваÑ, что он Ñодержит Ñломанный модуль oleaut32.msm как компонент %ls.
+
+Мы предлагаем вам переуÑтановить &apos;%ls&apos;.
+
+Смотри также https://support.microsoft.com/en-us/kb/316911 </translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="457"/>
+ <source>Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, CLSID_VirtualBox w/ IUnknown works.
+PSDispatch looks broken by installer %ls featuring the broken oleaut32.msm module as component %ls.
+
+See also https://support.microsoft.com/en-us/kb/316911 </source>
+ <translation>Ðе удалоÑÑŒ Ñоздать ÑкземплÑÑ€ CLSID_VirtualBox через IVirtualBox, но CLSID_VirtualBox через IUnknown работает.
+Похоже, PSDispatch Ñломан инÑталлÑтором %ls, Ñодержащим Ñломанный модуль oleaut32.msm как компонент %ls.
+
+Смотри также https://support.microsoft.com/en-us/kb/316911 </translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="467"/>
+ <source>Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, CLSID_VirtualBox w/ IUnknown works.
+PSDispatch looks broken by some installer featuring the broken oleaut32.msm module as a component.
+
+See also https://support.microsoft.com/en-us/kb/316911 </source>
+ <translation>Ðе удалоÑÑŒ Ñоздать ÑкземплÑÑ€ CLSID_VirtualBox через IVirtualBox, но CLSID_VirtualBox через IUnknown работает.
+Похоже, PSDispatch Ñломан каким-то инÑталлÑтором, Ñодержащим Ñломанный модуль oleaut32.msm как компонент %ls.
+
+Смотри также https://support.microsoft.com/en-us/kb/316911 </translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="473"/>
+ <source>Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.
+PSDispatch looks fine. Weird</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать ÑкземплÑÑ€ CLSID_VirtualBox через IVirtualBox, но CLSID_VirtualBox через IUnknown работает.
+Похоже PSDispatch нормальный. Странно</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="476"/>
+ <source>Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.
+Checking out PSDispatch registration ended with error: %u (%#x)</source>
+ <translation>Ðе удалоÑÑŒ Ñоздать ÑкземплÑÑ€ CLSID_VirtualBox через IVirtualBox, но CLSID_VirtualBox через IUnknown работает.
+Проверка региÑтрации PSDispatch закончилаÑÑŒ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹: %u (%#x)</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="676"/>
+ <source>Could not check the accessibility status of the VM</source>
+ <translation>Ðевозможно проверить ÑÑ‚Ð°Ñ‚ÑƒÑ Ð´Ð¾ÑтупноÑти Ð’Ðœ</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="682"/>
+ <source>Could not get the access error message of the VM</source>
+ <translation>Ðевозможно получить Ñообщение Ð’Ðœ об ошибке доÑтупа</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="783"/>
+ <source>Failed to load user language instance</source>
+ <translation>Ðе удалоÑÑŒ загрузить ÑкземплÑÑ€ пользовательÑкого Ñзыка</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="801"/>
+ <source>Failed to register listener</source>
+ <translation>Ðе удалоÑÑŒ зарегиÑтрировать проÑлушиватель</translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="807"/>
+ <source>Failed to get event source from VirtualBox</source>
+ <translation>Ðе удалоÑÑŒ получить иÑточник Ñобытий из VirtualBox</translation>
+ </message>
+</context>
+</TS>
diff --git a/src/VBox/Main/nls/VirtualBoxAPI_xx_YY.ts b/src/VBox/Main/nls/VirtualBoxAPI_xx_YY.ts
new file mode 100644
index 00000000..dd5e5f7c
--- /dev/null
+++ b/src/VBox/Main/nls/VirtualBoxAPI_xx_YY.ts
@@ -0,0 +1,9493 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>Appliance</name>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="647"/>
+ <source>The number of VirtualSystemDescription objects must be at least 1 or more.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="725"/>
+ <source>The given password identifier is not associated with any medium</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="747"/>
+ <source>A password with the given ID already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="760"/>
+ <source>Failed to allocate enough secure memory for the key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="762"/>
+ <source>Unknown error happened while adding a password (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="805"/>
+ <source>Can&apos;t find appropriate medium format for ISO type of a virtual disk.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="882"/>
+ <location filename="../src-server/ApplianceImpl.cpp" line="902"/>
+ <source>Internal inconsistency looking up medium format for the disk image &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="941"/>
+ <source>RTManifestEntryAddPassthruIoStream failed with rc=%Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="960"/>
+ <source>The appliance is busy importing files</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="962"/>
+ <source>The appliance is busy exporting files</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="1202"/>
+ <source>The path &apos;%s&apos; must start with /</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="1212"/>
+ <source>You doesn&apos;t provide a bucket name in the URI &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="1247"/>
+ <source>The manifest signature for &apos;%s&apos; is not valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="1252"/>
+ <source>The certificate used to signed &apos;%s&apos; is not valid: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImpl.cpp" line="1255"/>
+ <source>The certificate used to signed &apos;%s&apos; is not valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="763"/>
+ <source>Invalid format &quot;%s&quot; specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="770"/>
+ <source>OPC appliance file must have .tar.gz extension</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="774"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="111"/>
+ <source>Appliance file must have .ovf or .ova extension</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="786"/>
+ <source>Appliance export failed because not all passwords were provided for all encrypted media</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="852"/>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="982"/>
+ <source>Export appliance &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="915"/>
+ <source>There are no images to export to Cloud after preparation steps</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="923"/>
+ <source>Cloud: More than one profile name was found.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="925"/>
+ <source>Cloud: Profile name wasn&apos;t specified.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="928"/>
+ <source>Cloud: Cloud user profile name is empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="939"/>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="943"/>
+ <source>Exporting VM to Cloud...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="947"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3598"/>
+ <source>Only &quot;OCI&quot; cloud provider is supported for now. &quot;%s&quot; isn&apos;t supported.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1119"/>
+ <source>Cannot export more than one virtual system with OVF 0.9, use OVF 1.0</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1339"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5632"/>
+ <source>Missing VM name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1435"/>
+ <source>Missing OS type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1678"/>
+ <source>Invalid config string &quot;%s&quot; in SATA controller</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1718"/>
+ <source>Invalid config string &quot;%s&quot; in SCSI/SAS controller</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1802"/>
+ <source>Missing or bad extra config string in hard disk image: &quot;%s&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="1879"/>
+ <source>Missing or bad extra config string in DVD drive medium: &quot;%s&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2262"/>
+ <source>Failed to open directory &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2320"/>
+ <source>Failed create TAR creator for &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2327"/>
+ <source>Failed to open &apos;%s&apos; for writing (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2346"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1335"/>
+ <source>%s: Cloud provider manager object wasn&apos;t found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2354"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1343"/>
+ <source>%s: Cloud provider object wasn&apos;t found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2375"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1376"/>
+ <source>%s: Cloud user profile name wasn&apos;t found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2379"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1384"/>
+ <source>%s: Cloud profile object wasn&apos;t found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2384"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1389"/>
+ <source>%s: Cloud client object wasn&apos;t found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2392"/>
+ <source>Export to Cloud isn&apos;t supported for more than one VM instance.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2537"/>
+ <source>Exporting to disk image &apos;%Rbn&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2565"/>
+ <source>Error completing TAR file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2569"/>
+ <source>Failed to TAR creator instance for &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2575"/>
+ <source>Failed to create &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2624"/>
+ <source>Could not create OVF file &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2646"/>
+ <source>Invalid medium storage format</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2711"/>
+ <source>Exporting to disk image &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2727"/>
+ <source>RTVfsFsStrmPushFile failed for &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2768"/>
+ <source>Creating manifest file &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2775"/>
+ <source>RTVfsMemIoStrmCreate failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2790"/>
+ <source>RTVfsFsStrmAdd failed for the manifest (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2793"/>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2796"/>
+ <source>RTManifestWriteStandard failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="2854"/>
+ <source>RTVfsFsStrmAdd failed for &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="143"/>
+ <source>Cannot interpret appliance without reading it first (call read() before interpret())</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="317"/>
+ <source>The virtual system &quot;%s&quot; claims support for %u CPU&apos;s, but VirtualBox has support for max %u CPU&apos;s only.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="344"/>
+ <source>The virtual system &quot;%s&quot; claims support for %llu MB RAM size, but VirtualBox has support for min %u &amp; max %u MB RAM size only.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/ApplianceImplImport.cpp" line="409"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="439"/>
+ <source>The virtual system &quot;%s&quot; claims support for %zu network adapters, but VirtualBox has support for max %u network adapter only.</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="600"/>
+ <source>The virtual &quot;%s&quot; system requests support for more than two IDE controller channels, but VirtualBox supports only two.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="622"/>
+ <source>The virtual system &quot;%s&quot; requests support for more than one SATA controller, but VirtualBox has support for only one</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="650"/>
+ <source>The virtual system &quot;%s&quot; requests support for an additional SCSI controller of type &quot;%s&quot; with ID %s, but VirtualBox presently supports only one SCSI controller.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="672"/>
+ <source>The virtual system &quot;%s&quot; requests support for more than one VirtioSCSI controller, but VirtualBox has support for only one</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="745"/>
+ <source>Unsupported format for virtual disk image %s in OVF: &quot;%s&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="762"/>
+ <source>Cannot find storage controller with OVF instance ID &quot;%s&quot; to which medium &quot;%s&quot; should be attached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="835"/>
+ <source>Cannot import machines without reading it first (call read() before i_importMachines())</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="878"/>
+ <source>Malformed OVA. &apos;%s&apos; is not a regular file (%d).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="883"/>
+ <source>RTVfsFsStrmNext failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="892"/>
+ <source>Unexpected end of OVA package</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="922"/>
+ <source>Unexpected end of OVA / internal error - missing &apos;%s&apos; (skipped %u)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="936"/>
+ <source>Error opening &apos;%s&apos; for reading (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="988"/>
+ <source>Error occured decompressing &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="996"/>
+ <source>Error opening destionation image &apos;%s&apos; for writing (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1026"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1078"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4232"/>
+ <source>Error initializing read ahead thread for &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1043"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1106"/>
+ <source>RTManifestPtIosAddEntryNow failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1090"/>
+ <source>Error initializing gzip decompression for &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1155"/>
+ <source>Getting cloud instance information</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1166"/>
+ <source>Reading appliance &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1175"/>
+ <source>Download appliance &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1210"/>
+ <source>Failed to create thread for reading appliance data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1231"/>
+ <source>%s: The profile name or instance id are absent or contain unsupported characters: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1238"/>
+ <source>%s: Cloud provider manager object wasn&apos;t found (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1246"/>
+ <source>%s: Cloud provider object wasn&apos;t found (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1250"/>
+ <source>%s: Cloud user profile name wasn&apos;t found (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1254"/>
+ <source>%s: Cloud profile object wasn&apos;t found (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1259"/>
+ <source>%s: Cloud client object wasn&apos;t found (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1268"/>
+ <source>%s: Requested (%d) and created (%d) numbers of VSD are differ .</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1380"/>
+ <source>%s: Cloud user profile name is empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1505"/>
+ <source>Can&apos;t open folder %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1509"/>
+ <source>The target folder %s has already contained some files (%d items). Clear the folder from the files or choose another folder</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1536"/>
+ <source>%s: Cloud import (cloud phase) failed. Used cloud instance is &apos;%s&apos;
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1545"/>
+ <source>Import from Cloud isn&apos;t supported for more than one VM instance.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1559"/>
+ <source>%s: Cloud cleanup action - the instance wasn&apos;t found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1596"/>
+ <source>Rollback action for Import Cloud operation failed. Some leavings may exist on the local disk or in the Cloud.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1705"/>
+ <source>Error opening &apos;%s&apos; for reading (%Rrc)
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1710"/>
+ <source>Error reading the downloaded file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1841"/>
+ <source>%s: Error reading &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1875"/>
+ <source>Could not read the file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1889"/>
+ <source>Could not create the directory &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1893"/>
+ <source>Error during getting info about the directory &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1902"/>
+ <source>Could not create the file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1907"/>
+ <source>Could not write into the file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1947"/>
+ <source>The hard disk &apos;%s&apos; already exists.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1960"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4152"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4263"/>
+ <source>Importing virtual disk image &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="1971"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4242"/>
+ <source>Importing medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2051"/>
+ <source>Creating new VM &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2312"/>
+ <source>Failed to open OVF file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2344"/>
+ <source>Failed to open the signature file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2353"/>
+ <source>Failed to open the manifest file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2375"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3706"/>
+ <source>Error opening the OVA file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2381"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3712"/>
+ <source>Error reading the OVA file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2405"/>
+ <source>Error reading OVA &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2494"/>
+ <source>OVA &apos;%s&apos; does not contain an .ovf-file</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2544"/>
+ <source>Could not read the OVF file for &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2602"/>
+ <source>Error reading the manifest file &apos;%s&apos; for &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2617"/>
+ <source>Failed to parse manifest file &apos;%s&apos; for &apos;%s&apos; (%Rrc): %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2673"/>
+ <source>Error reading the signature file &apos;%s&apos; for &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2764"/>
+ <source>Unsupported signed digest type (%#x)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2767"/>
+ <source>Error reading signed manifest digest: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2770"/>
+ <source>Could not locate signed digest for &apos;%s&apos; in the cert-file for &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2773"/>
+ <source>RTManifestEntryQueryAttr failed unexpectedly: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2776"/>
+ <source>Error parsing the .cert-file for &apos;%s&apos;: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2798"/>
+ <source>Error reading the PKCS#7/CMS signature from &apos;%s&apos; for &apos;%s&apos; (%Rrc): %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2803"/>
+ <source>Malformed .cert-file for &apos;%s&apos;: Signer&apos;s certificate not found (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2806"/>
+ <source>Error reading the signer&apos;s certificate from &apos;%s&apos; for &apos;%s&apos; (%Rrc): %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2844"/>
+ <source>Found .cert-file but no .mf-file for &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2875"/>
+ <source>The manifest signature does not match</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2878"/>
+ <source>Error validating the manifest signature (%Rrc, %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2881"/>
+ <source>RTCrDigestUpdateFromVfsFile failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2885"/>
+ <source>RTCrDigestCreateByType failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="2963"/>
+ <source>Failed to query trusted CAs and Certificates from the system and for the current user (%Rrc%RTeim)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3043"/>
+ <source>Invalid PKCS#7/CMS type: %s, expected %s (signedData)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3046"/>
+ <source>Invalid PKCS#7/CMS inner type: %s, expected %s (data)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3049"/>
+ <source>Invalid PKCS#7/CMS data: embedded (%u bytes), expected external</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3053"/>
+ <source>Invalid PKCS#7/CMS: No signers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3067"/>
+ <source>Invalid PKCS#7/CMS: Using a different certificate</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3075"/>
+ <source>RTCrStoreCreateInMem failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3093"/>
+ <source>Failed to validate PKCS#7/CMS signature: %Rrc%RTeim</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3098"/>
+ <source>RTCrStoreCertAddX509 failed: %Rrc%RTeim</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3138"/>
+ <source>A self signed certificate was used to sign &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3141"/>
+ <source>Self signed certificate used to sign &apos;%s&apos; is not currently valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3146"/>
+ <source>Verification of the self signed certificate failed (%Rrc%#RTeim)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3148"/>
+ <source>Verification of the self signed certificate used to sign &apos;%s&apos; failed (%Rrc)%RTeim</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3156"/>
+ <source>Self signed certificate used to sign &apos;%s&apos; is not marked as certificate authority (CA)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3181"/>
+ <source>RTCrX509CertPathsSetTrustedStore failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3220"/>
+ <source>The certificate used to sign &apos;%s&apos; (or a certificate in the path) is not currently valid (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3224"/>
+ <source>RTCrX509CertPathsSetValidTimeSpec failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3229"/>
+ <source>No trusted certificate paths</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3236"/>
+ <source>The certificate used to sign &apos;%s&apos; is not currently valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3240"/>
+ <source>Certificate path validation failed (%Rrc%RTeim)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3243"/>
+ <source>Certificate path building failed (%Rrc%RTeim)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3248"/>
+ <source>RTCrX509CertPathsCreate failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3260"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3262"/>
+ <source>%s verification failed: %Rrc%RTeim</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3294"/>
+ <source>OVF &amp; PKCS#7/CMS signature</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3296"/>
+ <source>PKCS#7/CMS signature</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3322"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3324"/>
+ <source>PKCS#7/CMS signature #%u does not include the signing certificate</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3336"/>
+ <source>%s: Untrusted self-signed certificate</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3361"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3416"/>
+ <source>%s: Untrusted timestamp (%s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3363"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3418"/>
+ <source>%s: Not valid at current time, but validates fine for untrusted signing time (%s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3482"/>
+ <source>%u out of %u PKCS#7/CMS signatures verfified okay</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3520"/>
+ <source>Importing appliance &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3523"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3595"/>
+ <source>Failed to create task for importing appliance into VirtualBox</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3579"/>
+ <source>Importing VM from Cloud...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3583"/>
+ <source>Start import VM from the Cloud...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3609"/>
+ <source>Failed to start thread for importing appliance into VirtualBox</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3845"/>
+ <source>Error fudging missing OVF digest in manifest: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3866"/>
+ <source>Digest mismatch (%Rrc): %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="3948"/>
+ <source>Invalid channel %RU32 specified; IDE controllers support only 0, 1 or 2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4081"/>
+ <source>Unsupported medium format for disk image &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4113"/>
+ <source>Could not find a valid medium format for the target disk &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4119"/>
+ <source>The target disk &apos;%s&apos; has no extension </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4183"/>
+ <source>Creating disk image &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4195"/>
+ <source>Could not find a valid medium format for the source disk &apos;%s&apos; Check correctness of the image format URL in the OVF description file or extension of the image</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4219"/>
+ <source>Error opening decompressed image file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4298"/>
+ <source>Failed to delete the temporary file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4457"/>
+ <source>Too many network adapters: OVF requests %d network adapters, but VirtualBox only supports %d</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4597"/>
+ <source>Too many IDE controllers in OVF; import facility only supports two</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4614"/>
+ <source>Invalid IDE controller type &quot;%s&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4624"/>
+ <source>Too many SATA controllers in OVF; import facility only supports one</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4638"/>
+ <source>Invalid SATA controller type &quot;%s&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4647"/>
+ <source>Too many SCSI controllers in OVF; import facility only supports one</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4668"/>
+ <source>Invalid SCSI controller type &quot;%s&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4682"/>
+ <source>Too many SAS controllers in OVF; import facility only supports one</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4700"/>
+ <source>Too many VirtioSCSI controllers in OVF; import facility only supports one</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4718"/>
+ <source>Invalid VirtioSCSI controller type &quot;%s&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4737"/>
+ <source>Too many floppy controllers in OVF; import facility only supports one</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4807"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5076"/>
+ <source>Unknown error during OVF import</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4887"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4953"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="4959"/>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5441"/>
+ <source>Internal inconsistency looking up disk image &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5546"/>
+ <source>&lt;vbox:Machine&gt; element in OVF contains a medium attachment for the disk image %s but the OVF describes no such image</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5683"/>
+ <source>Missing guest OS type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5696"/>
+ <source>CPU count missing</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplImport.cpp" line="5709"/>
+ <source>RAM size missing</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>AudioAdapter</name>
+ <message>
+ <location filename="../src-server/AudioAdapterImpl.cpp" line="436"/>
+ <source>Invalid audio codec type %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>AutoCallerCtx</name>
+ <message>
+ <location filename="../src-all/AutoCaller.cpp" line="207"/>
+ <source>The object functionality is limited</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/AutoCaller.cpp" line="216"/>
+ <source>The object is not ready</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>BandwidthControl</name>
+ <message>
+ <location filename="../src-server/BandwidthControlImpl.cpp" line="385"/>
+ <source>Could not find a bandwidth group named &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/BandwidthControlImpl.cpp" line="396"/>
+ <source>Bandwidth group limit cannot be negative</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/BandwidthControlImpl.cpp" line="410"/>
+ <source>Bandwidth group named &apos;%s&apos; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/BandwidthControlImpl.cpp" line="439"/>
+ <source>The bandwidth group &apos;%s&apos; is still in use</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>BandwidthGroup</name>
+ <message>
+ <location filename="../src-server/BandwidthGroupImpl.cpp" line="68"/>
+ <source>Invalid bandwidth group type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/BandwidthGroupImpl.cpp" line="240"/>
+ <source>Bandwidth group limit cannot be negative</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>BaseTextScript</name>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="55"/>
+ <source>Failed to open &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="96"/>
+ <source>&apos;%s&apos; isn&apos;t valid UTF-8: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="99"/>
+ <source>Error reading &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/TextScript.cpp" line="103"/>
+ <source>Failed to allocate memory (%&apos;RU64 bytes) for &apos;%s&apos;</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="107"/>
+ <source>&apos;%s&apos; is too big (max 16MB): %&apos;RU64</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="109"/>
+ <source>RTVfsFileQuerySize failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="180"/>
+ <source>Error writing to &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="183"/>
+ <source>Error creating/replacing &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Certificate</name>
+ <message>
+ <location filename="../src-server/CertificateImpl.cpp" line="419"/>
+ <source>Unknown item %u</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/CertificateImpl.cpp" line="570"/>
+ <source>RTAsn1EncodeToBuffer failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/CertificateImpl.cpp" line="574"/>
+ <source>RTAsn1EncodePrepare failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>CloudNetwork</name>
+ <message>
+ <location filename="../src-server/CloudNetworkImpl.cpp" line="150"/>
+ <source>Network name cannot be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>CloudProviderManager</name>
+ <message>
+ <location filename="../src-server/CloudProviderManagerImpl.cpp" line="272"/>
+ <source>Could not find a cloud provider with UUID {%RTuuid}</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/CloudProviderManagerImpl.cpp" line="290"/>
+ <source>Could not find a cloud provider with short name &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/CloudProviderManagerImpl.cpp" line="308"/>
+ <source>Could not find a cloud provider with name &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Console</name>
+ <message>
+ <location filename="../include/ConsoleImpl.h" line="105"/>
+ <source>The console is not powered up (%Rfn)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="1612"/>
+ <source>The saved state file &apos;%ls&apos; is invalid (%Rrc). Delete the saved state and try again</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="1873"/>
+ <source>Internal application error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="1877"/>
+ <source>Temporary failure due to guest activity, please retry</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2149"/>
+ <source>Cannot power down at this point during a save state</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2160"/>
+ <source>Cannot power down at this point in a teleportation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2170"/>
+ <source>Cannot power down at this point in an online snapshot</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2180"/>
+ <source>Cannot power down at this point in a live snapshot</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2185"/>
+ <source>Cannot power down a saved virtual machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2187"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7474"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7532"/>
+ <source>The virtual machine is being powered down</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2190"/>
+ <source>Invalid machine state: %s (must be Running, Paused or Stuck)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2228"/>
+ <source>Could not create VMPowerDownTask object
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2285"/>
+ <source>Could not reset the machine (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2332"/>
+ <source>CPU %d is not attached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2398"/>
+ <source>Hot-Remove failed (rc=%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2402"/>
+ <source>Hot-Remove was aborted because the CPU may still be used by the guest</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2476"/>
+ <source>CPU %d is already attached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2508"/>
+ <source>Could not add CPU to the machine (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2534"/>
+ <source>Cannot resume the machine as it is not paused (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2576"/>
+ <source>Controlled power off failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2624"/>
+ <source>Checking if the ACPI Power Button event was handled by the guest OS failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2644"/>
+ <source>Invalid machine state %s when checking if the guest entered the ACPI mode</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2707"/>
+ <source>Sending sleep button event failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2806"/>
+ <source>Cannot attach a USB device to the machine which is not running or paused (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2816"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2827"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2890"/>
+ <source>The virtual machine does not have a USB controller</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2852"/>
+ <source>USB device with UUID {%RTuuid} is not attached to this machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2919"/>
+ <source>Could not find a USB device with address &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2952"/>
+ <source>Could not find a USB device with uuid {%RTuuid}</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2970"/>
+ <source>Cannot create a transient shared folder on a machine in a saved state (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2979"/>
+ <source>Cannot create a transient shared folder on the machine while it is changing the state (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="2986"/>
+ <source>Shared folder named &apos;%s&apos; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3042"/>
+ <source>Cannot remove a transient shared folder from a machine in a saved state (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3051"/>
+ <source>Cannot remove a transient shared folder from the machine while it is changing the state (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3098"/>
+ <source>The ID and password must be both valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3135"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6805"/>
+ <source>Could not resume the machine execution (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3140"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3173"/>
+ <source>A password with the given ID already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3142"/>
+ <source>Failed to allocate enough secure memory for the key</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3144"/>
+ <source>Unknown error happened while adding a password (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3156"/>
+ <source>IDs and passwords must not be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3159"/>
+ <source>The number of entries in the id and password arguments must match</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3203"/>
+ <source>The ID must be valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3217"/>
+ <source>A password with the ID &quot;%s&quot; does not exist</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3219"/>
+ <source>Failed to remove password with ID &quot;%s&quot; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3230"/>
+ <source>A password is still in use by the VM</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3232"/>
+ <source>Deleting all passwords failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3275"/>
+ <source>Invalid machine state: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3384"/>
+ <source>Could suspend VM for medium change (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3397"/>
+ <source>Invalid state &apos;%s&apos; for changing medium</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3479"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3652"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3825"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6403"/>
+ <source>Could not find storage controller &apos;%ls&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3535"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3708"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3878"/>
+ <source>Could not mount the media/drive &apos;%ls&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3536"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3709"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="3879"/>
+ <source>Could not unmount the currently mounted media/drive (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4078"/>
+ <source>The network adapter #%u is not enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4669"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6466"/>
+ <source>could not query medium interface of controller</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4674"/>
+ <source>The provided password for ID &quot;%s&quot; is not correct for at least one disk using this ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4680"/>
+ <source>Failed to set the encryption key (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4688"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6469"/>
+ <source>could not query base interface of controller</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4788"/>
+ <source>Failed to decode the key (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4793"/>
+ <source>Failed to allocate secure memory for the key (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4797"/>
+ <source>The base64 encoding of the passed key is incorrect</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4801"/>
+ <source>The encryption configuration is incomplete</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="4886"/>
+ <source>Could not change the network adaptor attachement type (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="5212"/>
+ <source>Failed to change the serial port attachment (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="5501"/>
+ <source>VirtualBox Remote Desktop Extension server can&apos;t bind to the port(s): %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="5510"/>
+ <source>VirtualBox Remote Desktop Extension is not available</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="5518"/>
+ <source>Could not find the VirtualBox Remote Desktop Extension library</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="5521"/>
+ <source>Failed to launch the Remote Desktop Extension server (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="5667"/>
+ <source>Sending monitor hot-plug event failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6192"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6249"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6280"/>
+ <source>The VBoxGuestPropSvc service call failed with the error %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6475"/>
+ <source>Failed to perform an online medium merge (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6618"/>
+ <source>Unsupported priority type (%d)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6622"/>
+ <source>Could not set the priority of the process (%Rrc). Try to set it when VM is not started.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6683"/>
+ <source>Already paused</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6711"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6892"/>
+ <source>Could not suspend the machine execution (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6791"/>
+ <source>VM is paused due to host power management</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6846"/>
+ <source>Cannot save the execution state as the machine is not running or paused (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6855"/>
+ <source>Saving the execution state is disabled for this VM</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6868"/>
+ <source>Could not create a directory &apos;%s&apos; to save the state to (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="6918"/>
+ <source>Failed to save the machine state to &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7482"/>
+ <source>The virtual machine is not powered up</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7537"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7546"/>
+ <source>The virtual machine is powered off</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7632"/>
+ <source>Failed to open release log (%s, %Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7676"/>
+ <source>The virtual machine is already running or busy (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7711"/>
+ <source>Restoring virtual machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7713"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7959"/>
+ <source>Teleporting virtual machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7715"/>
+ <source>Starting virtual machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7735"/>
+ <source>VM failed to start because the saved state file &apos;%ls&apos; does not exist.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7739"/>
+ <source>VM failed to start because the saved state file &apos;%ls&apos; is invalid (%Rrc). Delete the saved state prior to starting the VM.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7940"/>
+ <source>Starting Hard Disk operations</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="7986"/>
+ <source>Powerup was canceled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8022"/>
+ <source>VM cannot start because host interface networking requires a host interface name to be set</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8033"/>
+ <source>VM cannot start because the host interface &apos;%ls&apos; does not exist</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8371"/>
+ <source>Could not destroy the machine. (Error: %Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8387"/>
+ <source>Could not power off the machine. (Error: %Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8489"/>
+ <source>Could not find a shared folder named &apos;%s&apos;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8704"/>
+ <source>Invalid shared folder path: &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8710"/>
+ <source>Shared folder path &apos;%s&apos; is not absolute</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/ConsoleImpl.cpp" line="8719"/>
+ <source>Shared folder name is too long: %zu bytes</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/ConsoleImpl.cpp" line="8722"/>
+ <source>Shared folder mount point too long: %zu bytes</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8747"/>
+ <source>Could not create a shared folder &apos;%s&apos; mapped to &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8752"/>
+ <source>Shared folder path &apos;%s&apos; does not exist on the host</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8790"/>
+ <source>The name is too long</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="8807"/>
+ <source>Could not remove the shared folder &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9376"/>
+ <source>Failed to attach the USB device. (No available ports on the USB controller).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9379"/>
+ <source>Not permitted to open the USB device, check usbfs options</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9382"/>
+ <source>Failed to create a proxy device for the USB device. (Error: %Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9568"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9638"/>
+ <source>No TAP device name was supplied for the host networking interface</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9581"/>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9662"/>
+ <source>Failed to open the host network interface %ls</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9606"/>
+ <source>could not set up the host networking device for non blocking access: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9621"/>
+ <source>Could not set up the host networking device: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="9673"/>
+ <source>General failure attaching to host interface</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="10173"/>
+ <source>Disk Image Reset Operation - Immutable Image</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl.cpp" line="10485"/>
+ <source>Failed to start VM execution (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImpl2.cpp" line="277"/>
+ <source>%s failed: rc=%Rrc, pcszName=%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="240"/>
+ <source>Failed reading ACK(%s): %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="283"/>
+ <source>%s: Expected ACK or NACK, got &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="302"/>
+ <source>Failed writing command &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="649"/>
+ <source>canceled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="657"/>
+ <source>Failed to connect to port %u on &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="667"/>
+ <source>Failed to read welcome message: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="669"/>
+ <source>Unexpected welcome %.*Rhxs</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="675"/>
+ <source>Failed to send password: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="678"/>
+ <source>Invalid password</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="953"/>
+ <source>The specified password resembles a hashed password, expected plain text</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="970"/>
+ <source>Invalid machine state: %s (must be Running or Paused)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="985"/>
+ <source>Teleporter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1100"/>
+ <source>RTTcpServerCreateEx failed with status %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1123"/>
+ <source>Waiting for incoming VM</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1148"/>
+ <source>Teleporation failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1156"/>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1173"/>
+ <source>Teleporting canceled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1158"/>
+ <source>Teleporter timed out waiting for incoming connection</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1163"/>
+ <source>Unexpected RTTcpServerListen status code %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1312"/>
+ <source>Teleporting VM from %RTnaddr</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/ConsoleImplTeleporter.cpp" line="1317"/>
+ <source>Teleporting VM</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ConsoleVRDPServer</name>
+ <message>
+ <location filename="../src-client/ConsoleVRDPServer.cpp" line="3202"/>
+ <source>Could not load the external authentication library &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>DHCPConfig</name>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="258"/>
+ <source>Duplicate option value: %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="261"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="330"/>
+ <source>Invalid option value: %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="416"/>
+ <source>Unsupported encoding %d (option %d, value %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="419"/>
+ <source>Unsupported option %d (encoding %d, value %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="421"/>
+ <source>Malformed option %d value &apos;%s&apos; (encoding %d, rc=%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="434"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="459"/>
+ <source>DHCP option %u was not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>DHCPGlobalConfig</name>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="618"/>
+ <source>DHCP option DHCPOption_SubnetMask is not in a legacy encoding</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="620"/>
+ <source>DHCP option DHCPOption_SubnetMask was not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="637"/>
+ <source>Invalid IPv4 netmask &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="650"/>
+ <source>DHCPOption_SubnetMask must use DHCPOptionEncoding_Normal as it is reflected by IDHCPServer::networkMask</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="661"/>
+ <source>DHCPOption_SubnetMask cannot be removed as it reflects IDHCPServer::networkMask</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="691"/>
+ <source>Cannot delete the global config</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>DHCPGroupCondition</name>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="764"/>
+ <source>Not a valid MAC address: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="793"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="816"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="838"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="866"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="873"/>
+ <source>Trailing chars in MAC wildcard address: %s (offset %zu)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="801"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="822"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="845"/>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="877"/>
+ <source>Malformed MAC wildcard address: %s (offset %zu)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="888"/>
+ <source>Value cannot be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="890"/>
+ <source>Value is too long: %zu bytes</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="895"/>
+ <source>Invalid condition type: %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>DHCPIndividualConfig</name>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="1399"/>
+ <source>Invalid IPv4 address &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPConfigImpl.cpp" line="1448"/>
+ <source>INetworkAdapter returned bogus MAC address &apos;%ls&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>DHCPServer</name>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="506"/>
+ <source>Invalid server address: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="510"/>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="526"/>
+ <source>Invalid netmask: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="514"/>
+ <source>Invalid range lower address: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="518"/>
+ <source>Invalid range upper address: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="540"/>
+ <source>Invalid server address: %s (mask %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="546"/>
+ <source>Invalid range lower address: %s (mask %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="552"/>
+ <source>Invalid range upper address</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="556"/>
+ <source>Lower bound must be less or eqaul than the upper: %s vs %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="603"/>
+ <source>NIC slot number (%d) is out of range (0..32)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="766"/>
+ <source>not running</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="896"/>
+ <source>Cannot start DHCP server because it is already running (pid %RTproc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="929"/>
+ <source>Failed to start DHCP server for &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="932"/>
+ <source>Failed to assemble the command line for DHCP server &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="966"/>
+ <source>Invalid MAC address &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="968"/>
+ <source>flags must be zero (not %#x)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1019"/>
+ <source>Reading &apos;%s&apos; failed: %Rrc - %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1025"/>
+ <source>Reading &apos;%s&apos; failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1026"/>
+ <source>Reading &apos;%s&apos; failed: RTCError</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1035"/>
+ <source>Reading &apos;%s&apos; failed: Unexpected exception</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1091"/>
+ <source>Could not find a lease for %RTmac</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1099"/>
+ <source>The &apos;slot&apos; argument must be zero for all but the MachineNIC scope!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1105"/>
+ <source>The name must be empty or NULL for the Global scope!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1113"/>
+ <source>A group must have a name!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1115"/>
+ <source>Name too long! %zu bytes</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1126"/>
+ <source>Found no configuration for group %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1210"/>
+ <source>Found no configuration for MAC address %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1214"/>
+ <source>Invalid MAC address: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/DHCPServerImpl.cpp" line="1264"/>
+ <source>Failed to construct leases, config and log filenames: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>DataStream</name>
+ <message>
+ <location filename="../src-server/DataStreamImpl.cpp" line="87"/>
+ <source>Failed to initialize data stream object (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/DataStreamImpl.cpp" line="213"/>
+ <source>Error reading %u bytes: %Rrc</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+</context>
+<context>
+ <name>Display</name>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="1542"/>
+ <source>AttachFramebuffer: Invalid screen %d (total %d)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="1547"/>
+ <source>AttachFramebuffer: Framebuffer already attached to %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="1593"/>
+ <source>DetachFramebuffer: Invalid screen %d (total %d)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="1601"/>
+ <source>DetachFramebuffer: Invalid framebuffer object</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="1618"/>
+ <source>QueryFramebuffer: Invalid screen %d (total %d)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="1963"/>
+ <source>Unsupported screenshot format 0x%08X</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2024"/>
+ <source>PNG is larger than 32bpp bitmap</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2028"/>
+ <source>Could not convert screenshot to PNG (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2033"/>
+ <source>Screenshot is not available at this time</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2035"/>
+ <source>Could not take a screenshot (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2301"/>
+ <source>Could not draw to the screen (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2435"/>
+ <source>Could not invalidate and update the screen (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2454"/>
+ <source>Could not invalidate and update the screen %d (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/DisplayImpl.cpp" line="2499"/>
+ <source>QuerySourceBitmap: Invalid screen %d (total %d)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>EmulatedUSB</name>
+ <message>
+ <location filename="../src-client/EmulatedUSBImpl.cpp" line="215"/>
+ <source>Init emulated USB webcam (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/EmulatedUSBImpl.cpp" line="314"/>
+ <source>Attach emulated USB webcam (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/EmulatedUSBImpl.cpp" line="332"/>
+ <source>Detach emulated USB webcam (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>EventSource</name>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1065"/>
+ <location filename="../src-all/EventImpl.cpp" line="1122"/>
+ <location filename="../src-all/EventImpl.cpp" line="1193"/>
+ <location filename="../src-all/EventImpl.cpp" line="1221"/>
+ <source>This event source is already shut down</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1070"/>
+ <source>This listener already registered</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1099"/>
+ <location filename="../src-all/EventImpl.cpp" line="1202"/>
+ <location filename="../src-all/EventImpl.cpp" line="1254"/>
+ <source>Listener was never registered</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1205"/>
+ <source>Listener must be passive</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1231"/>
+ <source>Only applicable to passive listeners</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1241"/>
+ <source>Unknown event</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1445"/>
+ <source>Could not create wrapper object (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1457"/>
+ <source>Could not create aggregator (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>EventSourceAggregator</name>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1476"/>
+ <source>Could not create source (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1479"/>
+ <source>Could not init source (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1615"/>
+ <source>Could not create proxy (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1625"/>
+ <source>This listener already registered</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1639"/>
+ <location filename="../src-all/EventImpl.cpp" line="1650"/>
+ <source>This listener never registered</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ExtPack</name>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="787"/>
+ <source>ExtPack::init failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="934"/>
+ <source>pfnUninstall returned %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1145"/>
+ <source>The extension pack &apos;%s&apos; does not include a VRDE module</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1170"/>
+ <source>Failed to locate the VRDE module &apos;%s&apos; in extension pack &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1193"/>
+ <source>Failed to locate the module &apos;%s&apos; in extension pack &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1351"/>
+ <source>RTPathQueryInfoEx on &apos;%s&apos; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1357"/>
+ <source>&apos;%s&apos; is a symbolic link, this is not allowed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1360"/>
+ <source>&apos;%s&apos; is a symbolic file, not a directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1363"/>
+ <source>&apos;%s&apos; is not a directory (fMode=%#x)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1384"/>
+ <source>Failed to load &apos;%s/%s&apos;: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1396"/>
+ <source>The description name (&apos;%s&apos;) and directory name (&apos;%s&apos;) does not match</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1423"/>
+ <source>Failed to locate the main module (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1440"/>
+ <source>Failed to load the main module (&apos;%s&apos;): %Rrc - %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1447"/>
+ <source>Only native main modules are currently supported</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1509"/>
+ <source>The registration structure contains one or more invalid function pointers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1512"/>
+ <source>Unsupported registration structure version %u.%u</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1516"/>
+ <source>%s returned %Rrc, pReg=%p ErrInfo=&apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="1521"/>
+ <source>Failed to resolve exported symbol &apos;%s&apos; in the main module: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2138"/>
+ <source>The preferred locale is a two character string or empty.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2141"/>
+ <source>The preferred language is a two character string or empty.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2146"/>
+ <source>The license format can only have the values &apos;html&apos;, &apos;rtf&apos; and &apos;txt&apos;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2190"/>
+ <source>The license file &apos;%s&apos; is empty or contains invalid UTF-8 encoding</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2195"/>
+ <source>The license file &apos;%s&apos; was not found in extension pack &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2198"/>
+ <source>Failed to open the license file &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2201"/>
+ <source>RTPathJoin failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ExtPackFile</name>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="329"/>
+ <source>ExtPack::init failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="355"/>
+ <source>&apos;%s&apos; file not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="356"/>
+ <source>RTFileOpen(&apos;%s&apos;,,) failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="362"/>
+ <source>RTFileQueryInfo failed with %Rrc on &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="364"/>
+ <source>Not a regular file: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="384"/>
+ <source>Failed to the xml file: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="396"/>
+ <source>Extension pack name mismatch between the downloaded file and the XML inside it (xml=&apos;%s&apos; file=&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="532"/>
+ <source>The preferred locale is a two character string or empty.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="535"/>
+ <source>The preferred language is a two character string or empty.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="540"/>
+ <source>The license format can only have the values &apos;html&apos;, &apos;rtf&apos; and &apos;txt&apos;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="592"/>
+ <source>RTVfsFsStrmNext failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="594"/>
+ <source>&apos;%s&apos; was found in the manifest but not in the tarball</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="629"/>
+ <source>The license file &apos;%s&apos; is empty or contains invalid UTF-8 encoding</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="633"/>
+ <source>Failed to read &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="637"/>
+ <source>Failed to allocate %zu bytes for &apos;%s&apos;</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="641"/>
+ <source>RTVfsIoStrmQueryInfo on &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="656"/>
+ <source>The license file &apos;%s&apos; was not found in &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="688"/>
+ <source>Starting thread for an extension pack installation failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="692"/>
+ <source>Looks like creating a progress object for ExtraPackInstallTask object failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ExtPackInstallTask</name>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="219"/>
+ <source>Installing extension pack</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ExtPackManager</name>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2467"/>
+ <source>Starting thread for an extension pack uninstallation failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2470"/>
+ <source>Looks like creating a progress object for ExtraPackUninstallTask object failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2734"/>
+ <source>The installer failed with exit code %d: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2738"/>
+ <source>The installer was killed by signal #d (stderr: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2741"/>
+ <source>The installer aborted abnormally (stderr: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2744"/>
+ <source>internal error: enmReason=%d iStatus=%d stderr=&apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="2750"/>
+ <source>Failed to launch the helper application &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3026"/>
+ <source>Upgrading extension pack &apos;%s&apos; failed because at least one VM is still running</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3032"/>
+ <source>Upgrading extension pack &apos;%s&apos; failed because at least one Cloud Provider is still busy</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3040"/>
+ <source>Extension pack &apos;%s&apos; is already installed. In case of a reinstallation, please uninstall it first</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3085"/>
+ <source>The installation hook failed: %Rrc - %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3090"/>
+ <source>Installing extension pack &apos;%s&apos; failed under mysterious circumstances</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3191"/>
+ <source>Uninstall extension pack &apos;%s&apos; failed under mysterious circumstances</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3209"/>
+ <source>Uninstall extension pack &apos;%s&apos; failed because at least one VM is still running</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3215"/>
+ <source>Uninstall extension pack &apos;%s&apos; failed because at least one Cloud Provider is still busy</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3221"/>
+ <source>Uninstall extension pack &apos;%s&apos; failed for an unknown reason</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3465"/>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3494"/>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="3522"/>
+ <source>No extension pack by the name &apos;%s&apos; was found</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ExtPackUninstallTask</name>
+ <message>
+ <location filename="../src-all/ExtPackManagerImpl.cpp" line="269"/>
+ <source>Uninstalling extension pack</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ExtPackUtil</name>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="126"/>
+ <source>No VirtualBoxExtensionPack element</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="130"/>
+ <source>Missing format version</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="132"/>
+ <source>Unsupported format version: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="139"/>
+ <source>The &apos;Name&apos; element is missing</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="142"/>
+ <source>Invalid name: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="146"/>
+ <source>The &apos;Description&apos; element is missing</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="149"/>
+ <source>The &apos;Description&apos; element is empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="151"/>
+ <source>The &apos;Description&apos; must not contain control characters</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="155"/>
+ <source>The &apos;Version&apos; element is missing</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="158"/>
+ <source>The &apos;Version&apos; element is empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="160"/>
+ <source>Invalid version string: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="170"/>
+ <source>Invalid edition string: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="174"/>
+ <source>The &apos;MainModule&apos; element is missing</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="177"/>
+ <source>The &apos;MainModule&apos; element is empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="179"/>
+ <source>Invalid main module string: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="193"/>
+ <source>Invalid main VM module string: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="208"/>
+ <source>Invalid VRDE module string: </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="267"/>
+ <source>RTPathJoin failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="272"/>
+ <source>RTPathQueryInfoEx failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="278"/>
+ <source>The XML file is symlinked, that is not allowed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="279"/>
+ <source>The XML file is not a file (fMode=%#x)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="328"/>
+ <source>RTVfsFileQueryInfo failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/ExtPackUtil.cpp" line="339"/>
+ <source>The XML file is too large (%&apos;RU64 bytes)</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="346"/>
+ <source>RTVfsFileSeek(,0,BEGIN) failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="351"/>
+ <source>RTMemTmpAlloc(%zu) failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="356"/>
+ <source>RTVfsFileRead failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="757"/>
+ <source>The name of the downloaded file and the name stored inside the extension pack does not match (xml=&apos;%s&apos; file=&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="780"/>
+ <source>RTVfsFileSeek failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="785"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1240"/>
+ <source>RTManifestCreate failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="817"/>
+ <source>Manifest mismatch: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="819"/>
+ <source>RTManifestEqualsEx failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="830"/>
+ <source>Error parsing &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="878"/>
+ <source>The extension pack file has changed (SHA-256 mismatch)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="883"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="902"/>
+ <source>Bad SHA-256 &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="936"/>
+ <source>There can only be one &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="939"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="948"/>
+ <source>Standard member &apos;%s&apos; is not a file</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/ExtPackUtil.cpp" line="951"/>
+ <source>Standard member &apos;%s&apos; is too large: %&apos;RU64 bytes (max 1 MB)</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="981"/>
+ <source>RTVfsFileSeek failed on &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="991"/>
+ <source>RTVfsMemorizeIoStreamAsFile failed on &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="996"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1101"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1131"/>
+ <source>RTVfsObjQueryInfo failed on &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1017"/>
+ <source>&apos;%s&apos;: starts with root spec</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1067"/>
+ <source>Bad member name &apos;%s&apos; (pos %zu): %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/ExtPackUtil.cpp" line="1093"/>
+ <source>&apos;%s&apos;: too large (%&apos;RU64 bytes)</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1097"/>
+ <source>The alleged file &apos;%s&apos; has a mode mask stating otherwise (%RTfmode)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1127"/>
+ <source>The alleged directory &apos;%s&apos; has a mode mask saying differently (%RTfmode)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1160"/>
+ <source>&apos;%s&apos; is not a file or directory (enmType=%d)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1192"/>
+ <source>Failed seeking to the start of the tarball: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1198"/>
+ <source>RTVfsIoStrmFromRTFile failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1228"/>
+ <source>RTZipTarFsStreamFromIoStream failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1232"/>
+ <source>RTZipGzipDecompressIoStream failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1236"/>
+ <source>RTManifestEntryAddPassthruIoStream failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1321"/>
+ <source>RTVfsFsStrmNext failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1362"/>
+ <source>RTManifestEntryAddIoStream failed on &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1392"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1395"/>
+ <location filename="../src-all/ExtPackUtil.cpp" line="1398"/>
+ <source>Mandator file &apos;%s&apos; is missing</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>GeneralTextScript</name>
+ <message>
+ <location filename="../src-all/TextScript.cpp" line="217"/>
+ <source>saveToString() called before parse()</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/TextScript.cpp" line="267"/>
+ <source>attempting to set line %zu when there are only %zu lines</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/TextScript.cpp" line="297"/>
+ <source>attempting search&amp;replace in line %zu when there are only %zu lines</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/TextScript.cpp" line="326"/>
+ <source>appending to line %zu when there are only %zu lines</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-all/TextScript.cpp" line="345"/>
+ <source>prepending to line %zu when there are only %zu lines</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+</context>
+<context>
+ <name>GlobalCtx</name>
+ <message>
+ <location filename="../src-all/Global.cpp" line="587"/>
+ <location filename="../src-all/Global.cpp" line="626"/>
+ <location filename="../src-all/Global.cpp" line="646"/>
+ <source>Null</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="588"/>
+ <source>PoweredOff</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="589"/>
+ <source>Saved</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="590"/>
+ <source>Teleported</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="591"/>
+ <source>Aborted</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="592"/>
+ <source>Aborted-Saved</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="593"/>
+ <source>Running</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="594"/>
+ <source>Paused</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="595"/>
+ <source>GuruMeditation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="596"/>
+ <source>Teleporting</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="597"/>
+ <source>LiveSnapshotting</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="598"/>
+ <source>Starting</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="599"/>
+ <source>Stopping</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="600"/>
+ <source>Saving</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="601"/>
+ <source>Restoring</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="602"/>
+ <source>TeleportingPausedVM</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="603"/>
+ <source>TeleportingIn</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="604"/>
+ <source>DeletingSnapshotOnline</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="605"/>
+ <source>DeletingSnapshotPaused</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="606"/>
+ <source>OnlineSnapshotting</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="607"/>
+ <source>RestoringSnapshot</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="608"/>
+ <source>DeletingSnapshot</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="609"/>
+ <source>SettingUp</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="610"/>
+ <source>Snapshotting</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="615"/>
+ <location filename="../src-all/Global.cpp" line="635"/>
+ <source>InvalidState-0x%08x
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="627"/>
+ <source>Unlocked</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="628"/>
+ <source>Locked</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="629"/>
+ <source>Spawning</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="630"/>
+ <source>Unlocking</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="647"/>
+ <source>Floppy</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="648"/>
+ <source>DVD</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="649"/>
+ <source>HardDisk</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="650"/>
+ <source>Network</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="651"/>
+ <source>USB</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="652"/>
+ <source>ShardFolder</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="657"/>
+ <source>InvalidType-0x%08x
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="669"/>
+ <source>unspecified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="670"/>
+ <source>host suspend</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="671"/>
+ <source>host resume</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="672"/>
+ <source>host battery low</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="673"/>
+ <source>snapshot</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/Global.cpp" line="678"/>
+ <source>invalid reason %#010x
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>GraphicsAdapter</name>
+ <message>
+ <location filename="../src-server/GraphicsAdapterImpl.cpp" line="193"/>
+ <source>The graphics controller type (%d) is invalid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/GraphicsAdapterImpl.cpp" line="223"/>
+ <source>Invalid VRAM size: %lu MB (must be in range [%lu, %lu] MB)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/GraphicsAdapterImpl.cpp" line="307"/>
+ <source>Invalid monitor count: %lu (must be in range [%lu, %lu])</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Guest</name>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="402"/>
+ <source>No user name specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="436"/>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="570"/>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="694"/>
+ <source>Maximum number of concurrent guest sessions (%d) reached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="443"/>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="577"/>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="701"/>
+ <source>Could not create guest session: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="489"/>
+ <source>Could not find sessions with name &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="509"/>
+ <source>Unknown flags: flags value %#x, invalid: %#x</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="513"/>
+ <source>Invalid combination of flags (%#x)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="515"/>
+ <source>Rebooting</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="515"/>
+ <source>Shutting down</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="522"/>
+ <source>Rebooting guest</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="522"/>
+ <source>Shutting down guest</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="545"/>
+ <source>%s not supported by installed Guest Additions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="552"/>
+ <source>Error %s guest: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="562"/>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="686"/>
+ <source>Could not open guest session: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="601"/>
+ <source>Unknown flags (%#x)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="649"/>
+ <source>Failed to create SessionTaskUpdateAdditions object</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="655"/>
+ <source>Updating Guest Additions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="673"/>
+ <source>Starting thread for updating Guest Additions on the guest failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestCtrlImpl.cpp" line="677"/>
+ <source>Failed to initialize SessionTaskUpdateAdditions object</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestImpl.cpp" line="635"/>
+ <source>Failed to create guest statistics update timer (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestImpl.cpp" line="640"/>
+ <source>Failed to change guest statistics update timer interval from %u to %u failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestImpl.cpp" line="645"/>
+ <source>Failed to start the guest statistics update timer (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestImpl.cpp" line="653"/>
+ <source>Failed to stop the guest statistics update timer (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestImpl.cpp" line="828"/>
+ <source>Invalid status level defined: %u</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestImpl.cpp" line="866"/>
+ <source>VMM device is not available (is the VM running?)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>GuestDirectory</name>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="85"/>
+ <source>Opening directory &quot;%s&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="249"/>
+ <source>Access to guest directory &quot;%s&quot; is denied</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="250"/>
+ <source>Guest directory &quot;%s&quot; is not empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="252"/>
+ <source>Error %Rrc for guest directory &quot;%s&quot; occurred
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="418"/>
+ <source>Closing guest directory failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="429"/>
+ <source>Closing guest directory &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="461"/>
+ <source>Reading guest directory failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="466"/>
+ <source>Reading guest directory &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="471"/>
+ <source>Reading guest directory &quot;%s&quot; failed: Path not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="477"/>
+ <source>Reading guest directory &quot;%s&quot; failed: No more entries</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDirectoryImpl.cpp" line="482"/>
+ <source>Reading guest directory &quot;%s&quot; returned error: %Rrc
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>GuestDnDSource</name>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="347"/>
+ <source>No drop format specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="351"/>
+ <source>Specified format &apos;%s&apos; is not supported</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="362"/>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="446"/>
+ <source>Current drop operation to host still in progress</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="369"/>
+ <source>Another drag and drop operation to the host already is in progress</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="426"/>
+ <source>Starting thread for GuestDnDSource failed (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="519"/>
+ <source>For one or more guest files or directories selected for transferring to the host your guest user does not have the appropriate access rights for. Please make sure that all selected elements can be accessed and that your guest user has the appropriate rights</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="526"/>
+ <source>One or more guest files or directories selected for transferring to the host were notfound on the guest anymore. This can be the case if the guest files were moved and/oraltered while the drag and drop operation was in progress</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="532"/>
+ <source>One or more guest files or directories selected for transferring to the host were locked. Please make sure that all selected elements can be accessed and that your guest user has the appropriate rights</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="538"/>
+ <source>The guest was not able to retrieve the drag and drop data within time</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="542"/>
+ <source>Drag and drop error from guest (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="563"/>
+ <source>For one or more host files or directories selected for transferring to the guest your host user does not have the appropriate access rights for. Please make sure that all selected elements can be accessed and that your host user has the appropriate rights.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="569"/>
+ <source>Host disk ran out of space (disk is full).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="574"/>
+ <source>One or more host files or directories selected for transferring to the host were notfound on the host anymore. This can be the case if the host files were moved and/oraltered while the drag and drop operation was in progress.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="580"/>
+ <source>One or more host files or directories selected for transferring to the guest were locked. Please make sure that all selected elements can be accessed and that your host user has the appropriate rights.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDSourceImpl.cpp" line="586"/>
+ <source>Drag and drop error from host (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>GuestDnDState</name>
+ <message>
+ <location filename="../src-client/GuestDnDPrivate.cpp" line="348"/>
+ <source>Dropping data</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>GuestDnDTarget</name>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="268"/>
+ <source>No default action specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="270"/>
+ <source>Number of allowed actions is empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="272"/>
+ <source>Number of supported formats is empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="297"/>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="385"/>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="509"/>
+ <source>No or not supported format(s) specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="473"/>
+ <source>Invalid default action specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="475"/>
+ <source>Invalid allowed actions specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="477"/>
+ <source>No drop format(s) specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="543"/>
+ <source>Guest returned invalid drop formats (%zu formats)</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="547"/>
+ <source>Waiting for response of dropped event failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="550"/>
+ <source>Sending dropped event to guest failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="553"/>
+ <source>Retrieving drop coordinates failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="591"/>
+ <source>No data format specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="593"/>
+ <source>No data to send specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="599"/>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="780"/>
+ <source>Current drop operation to guest still in progress</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="606"/>
+ <source>Another drag and drop operation to the guest already is in progress</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="661"/>
+ <source>Starting thread for GuestDnDTarget failed (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="682"/>
+ <source>For one or more guest files or directories selected for transferring to the host your guest user does not have the appropriate access rights for. Please make sure that all selected elements can be accessed and that your guest user has the appropriate rights</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="689"/>
+ <source>One or more guest files or directories selected for transferring to the host were notfound on the guest anymore. This can be the case if the guest files were moved and/oraltered while the drag and drop operation was in progress</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="695"/>
+ <source>One or more guest files or directories selected for transferring to the host were locked. Please make sure that all selected elements can be accessed and that your guest user has the appropriate rights</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="701"/>
+ <source>The guest was not able to process the drag and drop data within time</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="705"/>
+ <source>Drag and drop error from guest (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="726"/>
+ <source>For one or more host files or directories selected for transferring to the guest your host user does not have the appropriate access rights for. Please make sure that all selected elements can be accessed and that your host user has the appropriate rights.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="733"/>
+ <source>One or more host files or directories selected for transferring to the host were notfound on the host anymore. This can be the case if the host files were moved and/oraltered while the drag and drop operation was in progress.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="739"/>
+ <source>One or more host files or directories selected for transferring to the guest were locked. Please make sure that all selected elements can be accessed and that your host user has the appropriate rights.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestDnDTargetImpl.cpp" line="745"/>
+ <source>Drag and drop error from host (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>GuestFile</name>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="438"/>
+ <source>Access to guest file &quot;%s&quot; denied</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="439"/>
+ <source>Guest file &quot;%s&quot; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="440"/>
+ <source>Guest file &quot;%s&quot; not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="441"/>
+ <source>Host name &quot;%s&quot;, not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="442"/>
+ <source>Sharing violation for guest file &quot;%s&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="444"/>
+ <source>Error %Rrc for guest file &quot;%s&quot; occurred
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1530"/>
+ <source>Closing guest file failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1533"/>
+ <source>Closing guest file &quot;%s&quot; failed with %Rrc
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1564"/>
+ <source>Initialization of guest file object for &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1573"/>
+ <source>Querying guest file information failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1578"/>
+ <source>Querying guest file information for &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1606"/>
+ <source>Querying guest file size failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1610"/>
+ <source>Querying guest file size for &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1623"/>
+ <source>The size to read is zero</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1648"/>
+ <source>Reading from file &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1662"/>
+ <source>The size to read for guest file &quot;%s&quot; is zero</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1686"/>
+ <source>Reading from file &quot;%s&quot; (at offset %RU64) failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1717"/>
+ <source>Invalid seek type for guest file &quot;%s&quot; specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1729"/>
+ <source>Seeking file &quot;%s&quot; (to offset %RI64) failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1750"/>
+ <source>The size (%RI64) for guest file &quot;%s&quot; cannot be a negative value</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestFileImpl.cpp" line="1814"/>
+ <source>Setting the guest file size of &quot;%s&quot; to %RU64 (%#RX64) bytes failed: %Rrc</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1826"/>
+ <source>No data to write specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestFileImpl.cpp" line="1836"/>
+ <source>Writing %zu bytes to guest file &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestFileImpl.cpp" line="1849"/>
+ <source>No data to write at for guest file &quot;%s&quot; specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestFileImpl.cpp" line="1860"/>
+ <source>Writing %zu bytes to file &quot;%s&quot; (at offset %RU64) failed: %Rrc</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+</context>
+<context>
+ <name>GuestProcess</name>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="353"/>
+ <source>The base environment feature is not supported by installed Guest Additions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="535"/>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="536"/>
+ <source>No such file or directory &quot;%s&quot; on guest</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="537"/>
+ <source>VMM device is not available (is the VM running?)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="538"/>
+ <source>The guest execution service is not available</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="539"/>
+ <source>The file &quot;%s&quot; is not an executable format on guest</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="540"/>
+ <source>The user &quot;%s&quot; was not able to logon on guest</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="541"/>
+ <source>The file &quot;%s&quot; is an invalid name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="542"/>
+ <source>The guest did not respond within time</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="543"/>
+ <source>The execution operation for &quot;%s&quot; was canceled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="544"/>
+ <source>Maximum number of concurrent guest processes has been reached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="545"/>
+ <source>The guest execution service is not ready (yet)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="547"/>
+ <source>Error %Rrc for guest process &quot;%s&quot; occurred
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="1969"/>
+ <source>The size to read is zero</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestProcessImpl.cpp" line="1995"/>
+ <source>Reading %RU32 bytes from guest process handle %RU32 failed: %s</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2000"/>
+ <source>Reading from guest process &quot;%s&quot; (PID %RU32) failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2030"/>
+ <source>Terminating guest process failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2036"/>
+ <source>Terminating guest process &quot;%s&quot; (PID %RU32) not supported by installed Guest Additions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2041"/>
+ <source>Terminating guest process &quot;%s&quot; (PID %RU32) failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2069"/>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2125"/>
+ <source>Flags value %#x, invalid: %#x</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2091"/>
+ <source>Waiting for guest process (flags %#x) failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2100"/>
+ <source>Waiting for guest process &quot;%s&quot; (PID %RU32) failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2148"/>
+ <source>Writing %RU32 bytes (flags %#x) to guest process failed: %s</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2153"/>
+ <source>Writing to guest process &quot;%s&quot; (PID %RU32) failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>GuestProcessTool</name>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2892"/>
+ <source>Access to &quot;%s&quot; denied</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2898"/>
+ <source>No such file or directory &quot;%s&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2902"/>
+ <source>VMM device is not available (is the VM running?)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2906"/>
+ <source>The guest execution service is not available</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2910"/>
+ <source>The file &quot;%s&quot; is not an executable format</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2914"/>
+ <source>The user &quot;%s&quot; was not able to logon</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2918"/>
+ <source>The file &quot;%s&quot; is an invalid name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2922"/>
+ <source>The guest did not respond within time</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2926"/>
+ <source>The execution operation was canceled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2930"/>
+ <source>Maximum number of concurrent guest processes has been reached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2934"/>
+ <source>The guest execution service is not ready (yet)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestProcessImpl.cpp" line="2938"/>
+ <source>Unhandled error %Rrc for &quot;%s&quot; occurred for tool &quot;%s&quot; on guest -- please file a bug report</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>GuestSession</name>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="473"/>
+ <source>Invalid environment variable name &apos;%s&apos;, index %zu</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="475"/>
+ <source>Failed to apply &apos;%s&apos;, index %zu (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="491"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4008"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4029"/>
+ <source>The base environment feature is not supported by the Guest Additions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="493"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4010"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4031"/>
+ <source>The base environment has not yet been reported by the guest</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="554"/>
+ <source>Getting the user&apos;s home path is not supported by installed Guest Additions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="559"/>
+ <source>Getting the user&apos;s home path failed on the guest: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="566"/>
+ <source>Getting the user&apos;s home path failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="592"/>
+ <source>Getting the user&apos;s documents path is not supported by installed Guest Additions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="597"/>
+ <source>Getting the user&apos;s documents path failed on the guest: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="604"/>
+ <source>Getting the user&apos;s documents path failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="754"/>
+ <source>No source(s) specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="756"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3547"/>
+ <source>No destination specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="766"/>
+ <source>Failed to create GuestSessionTaskCopyFrom object</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="771"/>
+ <source>Copying to &quot;%s&quot; on the host</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="787"/>
+ <source>Starting thread for copying from guest to the host failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="791"/>
+ <source>Initializing GuestSessionTaskCopyFrom object failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="825"/>
+ <source>Failed to create GuestSessionTaskCopyTo object</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="830"/>
+ <source>Copying to &quot;%s&quot; on the guest</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="846"/>
+ <source>Starting thread for copying from host to the guest failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="850"/>
+ <source>Initializing GuestSessionTaskCopyTo object failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="895"/>
+ <source>Invalid directory copy flag: %.*s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1460"/>
+ <source>Invalid file copy flag: %.*s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1846"/>
+ <source>VMM device is not available (is the VM running?)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1850"/>
+ <source>The guest execution service is not available</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1854"/>
+ <source>The specified user account on the guest is restricted and can&apos;t be used to logon</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1858"/>
+ <source>The specified user was not able to logon on guest</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1862"/>
+ <source>The guest did not respond within time</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1866"/>
+ <source>The session operation was canceled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1870"/>
+ <source>Maximum number of concurrent guest processes has been reached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1874"/>
+ <source>The guest execution service is not ready (yet)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="1907"/>
+ <source>Session is not in started state</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3322"/>
+ <source>Closing guest session failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3325"/>
+ <source>Closing guest session &quot;%s&quot; failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3351"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3379"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3571"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3600"/>
+ <source>Unknown flags: flags value %#x, invalid: %#x</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3404"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3482"/>
+ <source>Parameter array sizes don&apos;t match to the number of sources specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3427"/>
+ <source>Querying type for guest source failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3430"/>
+ <source>Querying type for guest source &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3462"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3532"/>
+ <source>Source type %#x invalid / not supported</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3500"/>
+ <source>Unable to query type for source &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3543"/>
+ <source>No sources specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3545"/>
+ <source>First source entry is empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3621"/>
+ <source>No directory to create specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3630"/>
+ <source>Unknown flags (%#x)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3646"/>
+ <source>Guest directory creation failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3652"/>
+ <source>Guest directory creation failed: Invalid parameters given</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3656"/>
+ <source>Guest directory creation failed: Unexpectedly aborted</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3660"/>
+ <source>Guest directory creation failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3672"/>
+ <source>No template specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3674"/>
+ <source>No directory name specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3677"/>
+ <source>Mode invalid (must be specified in octal mode)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3694"/>
+ <source>Temporary guest directory creation failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3699"/>
+ <source>Temporary guest directory creation &quot;%s&quot; with template &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3711"/>
+ <source>Empty path</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3739"/>
+ <source>Querying directory existence failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3754"/>
+ <source>Querying directory existence &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3767"/>
+ <source>No directory to open specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3769"/>
+ <source>Directory filters are not implemented yet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3778"/>
+ <source>Open flags (%#x) not implemented yet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3804"/>
+ <source>Opening guest directory &quot;%s&quot; failed; invalid parameters given</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3811"/>
+ <source>Opening guest directory failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3816"/>
+ <source>Opening guest directory &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3827"/>
+ <source>No directory to remove specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3846"/>
+ <source>Handling removing guest directories not supported by installed Guest Additions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3852"/>
+ <source>Removing guest directory failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3857"/>
+ <source>Removing guest directory &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3869"/>
+ <source>No directory to remove recursively specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3891"/>
+ <source>Invalid flags specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3906"/>
+ <source>Removing guest directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3927"/>
+ <source>Handling removing guest directories recursively not supported by installed Guest Additions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3933"/>
+ <source>Recursively removing guest directory failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3938"/>
+ <source>Recursively removing guest directory &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3963"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3983"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4003"/>
+ <source>Invalid environment variable name &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3965"/>
+ <source>Failed to schedule setting environment variable &apos;%s&apos; to &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="3985"/>
+ <source>Failed to schedule unsetting environment variable &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4080"/>
+ <source>Querying guest file existence failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4093"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4325"/>
+ <source>Querying guest file information for &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4115"/>
+ <source>No file to open specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4135"/>
+ <source>Append access modes are not yet implemented</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4137"/>
+ <source>Unknown FileAccessMode value %u (%#x)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4156"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4173"/>
+ <source>Unknown FileOpenAction value %u (%#x)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4170"/>
+ <source>Only FileSharingMode_All is currently implemented</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4181"/>
+ <source>Unsupported FileOpenExFlag value(s) in aFlags (%#x)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4195"/>
+ <source>Handling guest files not supported by installed Guest Additions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4201"/>
+ <source>Opening guest file failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4206"/>
+ <source>Opening guest file &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4217"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4248"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4293"/>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4334"/>
+ <source>No path specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4234"/>
+ <source>Querying guest file size failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4238"/>
+ <source>Querying guest file size of &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4279"/>
+ <source>Querying guest file existence information failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4284"/>
+ <source>Querying guest file existence information for &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4321"/>
+ <source>Querying guest file information failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4349"/>
+ <source>Removing guest file failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4353"/>
+ <source>Removing guest file &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4370"/>
+ <source>No source path specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4373"/>
+ <source>No destination path specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4384"/>
+ <source>Unknown rename flag: %#x</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4405"/>
+ <source>Handling renaming guest paths not supported by installed Guest Additions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4411"/>
+ <source>Renaming guest path failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4416"/>
+ <source>Renaming guest path &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4489"/>
+ <source>No command to execute specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4559"/>
+ <source>Failed to start guest process: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4563"/>
+ <source>Maximum number of concurrent guest processes per session (%u) reached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4566"/>
+ <source>Failed to create guest process object: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4570"/>
+ <source>Failed to apply environment variable &apos;%s&apos;, index %u (%Rrc)&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4574"/>
+ <source>Failed to set up the environment: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4584"/>
+ <source>No valid process ID (PID) specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4595"/>
+ <source>No process with PID %RU32 found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4648"/>
+ <source>Waiting for guest process failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4660"/>
+ <source>Waiting for guest session &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImpl.cpp" line="4661"/>
+ <source>Unnamed</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>GuestSessionTask</name>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="294"/>
+ <source>Guest directory &quot;%s&quot; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="319"/>
+ <source>Guest error creating directory &quot;%s&quot; on the guest: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="326"/>
+ <source>Host error creating directory &quot;%s&quot; on the guest: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="357"/>
+ <source>Host directory &quot;%s&quot; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="364"/>
+ <source>Could not create host directory &quot;%s&quot;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="405"/>
+ <source>Seeking to offset %RU64 of guest file &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="420"/>
+ <source>Reading %RU32 bytes @ %RU64 from guest &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="429"/>
+ <source>Writing %RU32 bytes to host file &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="468"/>
+ <source>Writing guest file &quot;%s&quot; to host file &quot;%s&quot; failed: Access denied</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="476"/>
+ <source>Copying guest file &quot;%s&quot; to host file &quot;%s&quot; failed (%RU64/%RU64 bytes transfered)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="511"/>
+ <source>Guest file lookup failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="515"/>
+ <source>Guest file lookup for &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="528"/>
+ <source>Guest file &quot;%s&quot; is a symbolic link</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="536"/>
+ <source>Guest object &quot;%s&quot; is not a file (is type %#x)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="550"/>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="830"/>
+ <source>Guest file could not be opened</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="554"/>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="834"/>
+ <source>Guest file &quot;%s&quot; could not be opened: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="573"/>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="613"/>
+ <source>Host file &quot;%s&quot; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="592"/>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="902"/>
+ <source>Host file lookup for &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="636"/>
+ <source>Host file &quot;%s&quot; is a symbolic link</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="657"/>
+ <source>No memory to allocate host file path</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="678"/>
+ <source>Opening/creating host file &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="725"/>
+ <source>Seeking to offset %RU64 of host file &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="740"/>
+ <source>Reading %RU32 bytes @ %RU64 from host file &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="749"/>
+ <source>Writing %zu bytes to guest file &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="784"/>
+ <source>Writing to guest file &quot;%s&quot; failed: Access denied</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="792"/>
+ <source>Copying to guest file &quot;%s&quot; failed (%RU64/%RU64 bytes transfered)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="851"/>
+ <source>Host path lookup for file &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="887"/>
+ <source>Guest error while determining object data for guest file &quot;%s&quot;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="894"/>
+ <source>Host error while determining object data for guest file &quot;%s&quot;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="932"/>
+ <source>Opening host file &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>GuestSessionTaskCopyFrom</name>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1442"/>
+ <source>Host destination must not be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1457"/>
+ <source>Guest source entry must not be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1491"/>
+ <source>Guest file lookup failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1494"/>
+ <source>Guest file lookup for &quot;%s&quot; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1503"/>
+ <source>Guest source is not a file: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1512"/>
+ <source>Guest source is not a directory: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1534"/>
+ <source>Error adding guest source &apos;%s&apos; to list: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1570"/>
+ <source>Failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>GuestSessionTaskCopyTo</name>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1745"/>
+ <source>Guest destination must not be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1760"/>
+ <source>Host source entry must not be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1769"/>
+ <source>No such host file/directory: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1777"/>
+ <source>Host source is not a file: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1786"/>
+ <source>Host source is not a directory: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1810"/>
+ <source>Error adding host source &apos;%s&apos; to list: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1846"/>
+ <source>Failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1895"/>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1903"/>
+ <source>Querying information on guest for &apos;%s&apos; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1945"/>
+ <source>Guest directory &quot;%s&quot; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="1960"/>
+ <source>Unknown object type (%#x) on guest for &quot;%s&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2020"/>
+ <source>Building source host path for entry &quot;%s&quot; failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2038"/>
+ <source>Building destination guest path for entry &quot;%s&quot; failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>GuestSessionTaskUpdateAdditions</name>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2199"/>
+ <source>Guest file &quot;%s&quot; could not be opened: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2251"/>
+ <source>Running update file &quot;%s&quot; on guest failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2256"/>
+ <source>Running update file on guest failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2262"/>
+ <source>Update file &quot;%s&quot; reported invalid running state</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2268"/>
+ <source>Error while running update file &quot;%s&quot; on guest: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2319"/>
+ <source>Guest Additions were not ready within time, giving up</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2332"/>
+ <source>Guest Additions are installed but not fully loaded yet, aborting automatic update</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2335"/>
+ <source>Guest Additions not installed or ready, aborting automatic update</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2352"/>
+ <source>Guest has too old Guest Additions (%s) installed for automatic updating, please update manually</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2381"/>
+ <source>Unable to detected guest OS version, please update manually</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2401"/>
+ <source>Windows 2000 and XP are not supported for automatic updating due to WHQL interaction, please update manually</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2409"/>
+ <source>%s (%s) not supported for automatic updating, please update manually</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2427"/>
+ <source>Detected guest OS (%s) does not support automatic Guest Additions updating, please update manually</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2444"/>
+ <source>Unable to open Guest Additions .ISO file &quot;%s&quot;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2454"/>
+ <source>Unable to open file as ISO 9660 file system volume: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2522"/>
+ <source>Creating installation directory on guest failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2528"/>
+ <source>Creating installation directory &quot;%s&quot; on guest failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2715"/>
+ <source>Error while copying file &quot;%s&quot; to &quot;%s&quot; on the guest: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/GuestSessionImplTasks.cpp" line="2779"/>
+ <source>Installation was canceled</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Host</name>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1233"/>
+ <source>The aFeature value %d (%#x) is out of range.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1314"/>
+ <source>CPU no.%u is not present</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1315"/>
+ <source>CPU no.%u is not online</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1482"/>
+ <source>Unable to create a host network interface</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1549"/>
+ <source>Host network interface with UUID {%RTuuid} does not exist</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1627"/>
+ <source>The given USB device filter is not created within this VirtualBox instance</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1631"/>
+ <source>The given USB device filter is already in the list</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1675"/>
+ <source>The USB device filter list is empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1679"/>
+ <source>Invalid position: %lu (must be in range [0, %lu])</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1723"/>
+ <source>The host DVD drive named &apos;%s&apos; could not be found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1737"/>
+ <source>The host floppy drive named &apos;%s&apos; could not be found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1777"/>
+ <source>The host network interface named &apos;%s&apos; could not be found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1820"/>
+ <source>The host network interface with the given GUID could not be found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1895"/>
+ <source>Could not find a USB device with address &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="1929"/>
+ <source>Could not find a USB device with uuid {%RTuuid}</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3549"/>
+ <source>Could not load the Host USB Proxy Service (VERR_FILE_NOT_FOUND). The service might not be installed on the host computer</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3552"/>
+ <source>VirtualBox is not currently allowed to access USB devices. You can change this by adding your user to the &apos;vboxusers&apos; group. Please see the user manual for a more detailed explanation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3555"/>
+ <source>VirtualBox is not currently allowed to access USB devices. You can change this by allowing your user to access the &apos;usbfs&apos; folder and files. Please see the user manual for a more detailed explanation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3558"/>
+ <source>The USB Proxy Service has not yet been ported to this host</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3561"/>
+ <source>Could not load the Host USB Proxy service</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3908"/>
+ <source>Failed to open NT\GLOBAL?? (error %Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3919"/>
+ <location filename="../src-server/HostImpl.cpp" line="3936"/>
+ <source>Out of memory! (direntry buffer)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3939"/>
+ <source>RTVfsDirReadEx failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3953"/>
+ <source>Unknown (Access denied)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="3957"/>
+ <location filename="../src-server/HostImpl.cpp" line="4002"/>
+ <source>Out of memory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostImpl.cpp" line="4060"/>
+ <source>Failed to update fixed drive list (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>HostNetworkInterface</name>
+ <message>
+ <location filename="../src-server/HostNetworkInterfaceImpl.cpp" line="622"/>
+ <source>Invalid IPv6 prefix length</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostNetworkInterfaceImpl.cpp" line="633"/>
+ <source>Invalid IPv6 address</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>HostOnlyNetwork</name>
+ <message>
+ <location filename="../src-server/HostOnlyNetworkImpl.cpp" line="141"/>
+ <source>Network name cannot be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostOnlyNetworkImpl.cpp" line="168"/>
+ <source>Network mask cannot be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>HostUSBDevice</name>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="427"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} cannot be accessed by guest computers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="431"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} is being exclusively used by the host computer</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="439"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} is already captured by the virtual machine &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="444"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} is busy with a previous request. Please try again later</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="450"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} is not in the right state for capturing (%s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="495"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} is in use by someone else</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUSBDeviceImpl.cpp" line="672"/>
+ <source>USB device &apos;%s&apos; with UUID {%RTuuid} is busy (state &apos;%s&apos;). Please try again later</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>HostUSBDeviceFilter</name>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="1138"/>
+ <source>The remote state filter is not supported by IHostUSBDeviceFilter objects</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="1153"/>
+ <source>The masked interfaces property is not applicable to IHostUSBDeviceFilter objects</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="1186"/>
+ <source>Action value InvalidUSBDeviceFilterAction is not permitted</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="1189"/>
+ <source>Invalid action %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="1197"/>
+ <source>Unexpected error %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>HostUpdate</name>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="361"/>
+ <source>%s: RTHttpCreate() failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="371"/>
+ <source>%s: RTHttpAddHeader() failed: %Rrc (on User-Agent)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="379"/>
+ <source>%s: ISystemProperties::proxyMode() failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="386"/>
+ <source>%s: ISystemProperties::proxyURL() failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="389"/>
+ <source>%s: RTHttpSetProxyByUrl() failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="395"/>
+ <source>%s: RTHttpUseSystemProxySettings() failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="407"/>
+ <source>%s: RTHttpGetBinary() failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="460"/>
+ <source>Invalid server response: %Rrc (%.*Rhxs -- %.*Rhxs)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="493"/>
+ <source>Update check type %d is not implemented</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="573"/>
+ <source>UpdateCheckType::ExtensionPack is not implemented</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="575"/>
+ <source>UpdateCheckType::GuestAdditions is not implemented</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="577"/>
+ <source>Invalid aCheckType value %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="609"/>
+ <source>Checking for software update...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/HostUpdateImpl.cpp" line="626"/>
+ <source>Update checking support was not compiled into this VirtualBox build</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>HostVideoInputDevice</name>
+ <message>
+ <location filename="../src-server/HostVideoInputDeviceImpl.cpp" line="227"/>
+ <source>Failed to get webcam list: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Keyboard</name>
+ <message>
+ <location filename="../src-client/KeyboardImpl.cpp" line="219"/>
+ <source>Could not send all scan codes to the virtual keyboard (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/KeyboardImpl.cpp" line="260"/>
+ <source>Could not send usage code to the virtual keyboard (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Machine</name>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="409"/>
+ <source>Cannot attach disk &apos;%s&apos; -- file name too long</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="452"/>
+ <source>Internal error adding a medium UUID to the map</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="500"/>
+ <source>Cannot attach image &apos;%s&apos; -- file name too long</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="540"/>
+ <source>Cannot handle medium attachment: channel is %d, device is %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ApplianceImplExport.cpp" line="566"/>
+ <source>Cannot handle medium attachment: storageBus is %d, channel is %d, device is %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="482"/>
+ <source>Trying to open a VM config &apos;%s&apos; which has the same UUID as an existing virtual machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="655"/>
+ <location filename="../src-server/MachineImpl.cpp" line="702"/>
+ <source>Invalid machine settings file name &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="685"/>
+ <source>Machine settings file &apos;%s&apos; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="694"/>
+ <source>Could not delete the existing settings file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="744"/>
+ <source>Machine UUID {%RTuuid} in &apos;%s&apos; doesn&apos;t match its UUID {%s} in the registry file &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1017"/>
+ <source>A machine cannot have a UUID as its name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1457"/>
+ <source>Invalid hardware version: %s
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1519"/>
+ <source>Invalid RAM size: %lu MB (must be in range [%lu, %lu] MB)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1550"/>
+ <source>Invalid virtual CPU count: %lu (must be in range [%lu, %lu])</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1562"/>
+ <source>There is still a CPU attached to socket %lu.Detach the CPU before removing the socket</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1596"/>
+ <source>Invalid CPU execution cap value: %lu (must be in range [%lu, %lu])</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1667"/>
+ <source>CPU hotplugging can&apos;t be disabled because the maximum number of CPUs is not equal to the amount of CPUs attached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1808"/>
+ <source>Invalid memory balloon size: %lu MB (must be in range [%lu, %lu] MB)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1820"/>
+ <source>Memory ballooning is only supported on 64-bit hosts</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="1844"/>
+ <source>Page fusion is only supported on 64-bit hosts</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2148"/>
+ <source>Currently only aSubIdx values 0 and 0xffffffff are supported: %#x</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2152"/>
+ <source>CpuId override leaf %#x is out of range</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2162"/>
+ <source>Max of 256 CPUID override leaves reached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2382"/>
+ <source>The snapshot folder of a machine with snapshots cannot be changed (please delete all snapshots first)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2391"/>
+ <source>Invalid snapshot folder &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2771"/>
+ <location filename="../src-server/MachineImpl.cpp" line="2892"/>
+ <source>The machine is not powered off (state is %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2793"/>
+ <source>Invalid port number %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2848"/>
+ <source>Cannot set an already hashed password, only plain text password please</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2962"/>
+ <location filename="../src-server/MachineImpl.cpp" line="3371"/>
+ <source>The given session is busy</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2966"/>
+ <source>No IInternalSessionControl interface</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="2976"/>
+ <location filename="../src-server/MachineImpl.cpp" line="7400"/>
+ <source>The machine &apos;%s&apos; is not registered</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3031"/>
+ <source>Failed to get a console object from the direct session (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3043"/>
+ <location filename="../src-server/MachineImpl.cpp" line="3184"/>
+ <location filename="../src-server/MachineImpl.cpp" line="7675"/>
+ <source>Failed to assign the machine to the session (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3051"/>
+ <source>The machine &apos;%s&apos; was unlocked unexpectedly while attempting to share its session</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3064"/>
+ <source>The machine &apos;%s&apos; is already locked for a session (or being unlocked)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3091"/>
+ <source>The machine &apos;%s&apos; already has a lock request pending</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3119"/>
+ <source>An unexpected process (PID=0x%08X) has tried to lock the machine &apos;%s&apos;, while only the process started by LaunchVMProcess (PID=0x%08X) is allowed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3226"/>
+ <source>Failed to assign the machine to the remote session (%Rhrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3390"/>
+ <source>Starting VM</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3393"/>
+ <source>Creating process for virtual machine &quot;%s&quot; (%s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3422"/>
+ <source>The machine &apos;%s&apos; is not locked by a session</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3429"/>
+ <source>The machine &apos;%s&apos; does not have a VM process</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3448"/>
+ <location filename="../src-server/MachineImpl.cpp" line="3471"/>
+ <source>Invalid boot position: %lu (must be in range [1, %lu])</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3453"/>
+ <source>Booting from USB device is currently not supported</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3502"/>
+ <source>Cannot attach storage devices to an unregistered machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3515"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4093"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4179"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4392"/>
+ <source>Could not get type of controller &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3534"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4112"/>
+ <source>Controller &apos;%s&apos; does not support hotplugging</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3553"/>
+ <source>Medium &apos;%s&apos; is already attached to port %d, device %d of controller &apos;%s&apos; of this virtual machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3561"/>
+ <source>Device is already attached to port %d, device %d of controller &apos;%s&apos; of this virtual machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3567"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4535"/>
+ <source>The given medium pointer is invalid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3580"/>
+ <source>Medium &apos;%s&apos; is already attached to this virtual machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3607"/>
+ <source>Cannot attach medium &apos;%s&apos;: the media type &apos;MultiAttach&apos; can only be attached to machines that were created with VirtualBox 4.0 or later</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="3935"/>
+ <source>Could not lock medium when creating diff &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4121"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4207"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4252"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4295"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4338"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4380"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4445"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4640"/>
+ <location filename="../src-server/MachineImpl.cpp" line="5849"/>
+ <source>No storage device attached to device slot %d on port %d of controller &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4126"/>
+ <source>The device slot %d on port %d of controller &apos;%s&apos; does not support hotplugging</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4198"/>
+ <source>Controller &apos;%s&apos; does not support hotplugging which is required to change the passthrough setting while the VM is running</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4218"/>
+ <source>Setting passthrough rejected as the device attached to device slot %d on port %d of controller &apos;%s&apos; is not a DVD</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4263"/>
+ <source>Setting temporary eject flag rejected as the device attached to device slot %d on port %d of controller &apos;%s&apos; is not a DVD</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4286"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4329"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4371"/>
+ <location filename="../src-server/MachineImpl.cpp" line="4436"/>
+ <source>Invalid machine state: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4306"/>
+ <source>Setting the non-rotational medium flag rejected as the device attached to device slot %d on port %d of controller &apos;%s&apos; is not a hard disk</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4349"/>
+ <source>Setting the discard medium flag rejected as the device attached to device slot %d on port %d of controller &apos;%s&apos; is not a hard disk</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4397"/>
+ <source>Controller &apos;%s&apos; does not support changing the hot-pluggable device flag</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4407"/>
+ <source>Setting the hot-pluggable device flag rejected as the device attached to device slot %d on port %d of controller &apos;%s&apos; is a floppy drive</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4455"/>
+ <source>The given bandwidth group pointer is invalid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4524"/>
+ <source>No drive attached to device slot %d on port %d of controller &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4552"/>
+ <source>The device at port %d, device %d of controller &apos;%s&apos; of this virtual machine is not removeable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4656"/>
+ <source>Serial port slot %RU32 is out of bounds (max %zu)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4667"/>
+ <source>Parallel port slot %RU32 is out of bounds (max %zu)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MachineImpl.cpp" line="4677"/>
+ <source>No network adapter in slot %RU32 (total %RU32 adapters)</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4756"/>
+ <source>Cannot set extradata for a snapshot</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4761"/>
+ <source>Cannot set extradata for an immutable machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4780"/>
+ <source>Could not set extra data because someone refused the requested change of &apos;%s&apos; to &apos;%s&apos;%s%ls</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="4926"/>
+ <source>Cannot unregister the machine &apos;%s&apos; while it is locked</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5090"/>
+ <source>The session has been accidentally closed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5124"/>
+ <location filename="../src-server/MachineImpl.cpp" line="5159"/>
+ <source>Deleting &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5165"/>
+ <source>Could not delete file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5168"/>
+ <source>Cleaning up machine directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5253"/>
+ <source>Cannot delete settings of a registered machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5272"/>
+ <source>The given medium pointer with index %d is invalid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5287"/>
+ <source>Deleting files</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5290"/>
+ <source>Collecting file inventory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5344"/>
+ <location filename="../src-server/MachineImpl.cpp" line="8999"/>
+ <source>Shared folder named &apos;%s&apos; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5401"/>
+ <location filename="../src-server/MachineImpl.cpp" line="5424"/>
+ <source>Machine is not locked for session (session state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5549"/>
+ <source>Invalid guest property flag values: &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5571"/>
+ <source>The property &apos;%s&apos; cannot be changed by the host</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5865"/>
+ <source>Invalid connection type: %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5879"/>
+ <location filename="../src-server/MachineImpl.cpp" line="9145"/>
+ <source>Storage controller named &apos;%s&apos; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="5955"/>
+ <source>Could not find a storage controller with instance number &apos;%lu&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6078"/>
+ <location filename="../src-server/MachineImpl.cpp" line="6143"/>
+ <source>Invalid USB controller type: %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6092"/>
+ <source>USB controller named &apos;%s&apos; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6104"/>
+ <source>Too many USB controllers of this type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6208"/>
+ <source>Saved guest size is not available (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6232"/>
+ <source>Unsupported saved thumbnail format 0x%08X</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6245"/>
+ <source>Saved thumbnail data is not available (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6303"/>
+ <source>Could not convert saved thumbnail to PNG (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6334"/>
+ <source>Saved screenshot data is not available (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6370"/>
+ <source>Saved screenshot thumbnail data is not available (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6391"/>
+ <location filename="../src-server/MachineImpl.cpp" line="6422"/>
+ <source>CPU hotplug is not enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6394"/>
+ <source>CPU id exceeds number of possible CPUs [0:%lu]</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6397"/>
+ <source>CPU %lu is already attached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6426"/>
+ <source>CPU index exceeds maximum CPU count (must be in range [0:%lu])</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6430"/>
+ <source>CPU %lu is not attached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6434"/>
+ <source>It is not possible to detach CPU 0</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6488"/>
+ <source>The size argument (%lld) is negative</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6516"/>
+ <source>Could not read log file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6522"/>
+ <source>Could not open log file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6551"/>
+ <source>Host PCI attachment only supported with ICH9 chipset</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6566"/>
+ <source>Device with host PCI address already attached to this VM</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6635"/>
+ <source>No host PCI device %08x attached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6772"/>
+ <source>The VM autostart feature is not supported on this platform</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6775"/>
+ <location filename="../src-server/MachineImpl.cpp" line="6849"/>
+ <source>The path to the autostart database is not set</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6779"/>
+ <source>Adding machine &apos;%s&apos; to the autostart database failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6780"/>
+ <source>Removing machine &apos;%s&apos; from the autostart database failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6846"/>
+ <source>The VM autostop feature is not supported on this platform</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6853"/>
+ <source>Adding machine &apos;%s&apos; to the autostop database failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6854"/>
+ <source>Removing machine &apos;%s&apos; from the autostop database failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6970"/>
+ <source>Linked clone can only be created from a snapshot</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="6973"/>
+ <source>Linked clone can only be created for a single machine state</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7022"/>
+ <source>Failed to create a worker thread for the MachineMoveVM task</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7413"/>
+ <source>The machine &apos;%s&apos; is in a state which is incompatible with launching a separate UI process</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7422"/>
+ <source>The machine &apos;%s&apos; is already locked by a session (or being locked or unlocked)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7493"/>
+ <source>Invalid frontend name: &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7603"/>
+ <source>Failed to start the machine &apos;%s&apos;. A connection to VBoxSDS cannot be established</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7619"/>
+ <source>Failed to start the machine &apos;%s&apos;. CoSetProxyBlanket failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7631"/>
+ <source>Failed to start the machine &apos;%s&apos;. Process creation failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7641"/>
+ <source>Could not launch the VM process for the machine &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7825"/>
+ <source>. More details may be available in &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7830"/>
+ <source>The virtual machine &apos;%s&apos; has terminated unexpectedly during startup with exit code %d (%#x)%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7834"/>
+ <source>The virtual machine &apos;%s&apos; has terminated unexpectedly during startup because of signal %d%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7838"/>
+ <source>The virtual machine &apos;%s&apos; has terminated abnormally (iStatus=%#x)%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7842"/>
+ <source>The virtual machine &apos;%s&apos; has terminated unexpectedly during startup (%Rrc)%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7899"/>
+ <source>The machine &apos;%s&apos; with UUID {%s} is inaccessible and cannot be registered</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7907"/>
+ <source>The machine &apos;%s&apos; with UUID {%s} is already registered</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="7986"/>
+ <source>Machine state change is in progress. Please retry the operation later.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="8105"/>
+ <source>The machine is not mutable (state is %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="8122"/>
+ <source>The machine is not mutable or saved (state is %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="8138"/>
+ <source>The machine is not mutable or running (state is %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="8156"/>
+ <source>The machine is not mutable, saved or running (state is %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="8540"/>
+ <source>Could not find a shared folder named &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="8590"/>
+ <location filename="../src-server/MachineImpl.cpp" line="8705"/>
+ <source>Invalid saved state file path &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9216"/>
+ <source>Duplicate attachments for storage controller &apos;%s&apos;, port %d, device %d of the virtual machine &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9264"/>
+ <source>A differencing image of snapshot {%RTuuid} could not be found. %ls</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9278"/>
+ <source>Immutable hard disk &apos;%s&apos; with UUID {%RTuuid} cannot be directly attached to snapshot with UUID {%RTuuid} of the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9287"/>
+ <source>Immutable hard disk &apos;%s&apos; with UUID {%RTuuid} cannot be directly attached to the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9298"/>
+ <source>Multi-attach hard disk &apos;%s&apos; with UUID {%RTuuid} cannot be directly attached to snapshot with UUID {%RTuuid} of the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9307"/>
+ <source>Multi-attach hard disk &apos;%s&apos; with UUID {%RTuuid} cannot be directly attached to the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9318"/>
+ <source>Hard disk &apos;%s&apos; with UUID {%RTuuid} cannot be directly attached to the virtual machine &apos;%s&apos; (&apos;%s&apos;) because it has %d differencing child hard disks</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9329"/>
+ <source>Hard disk &apos;%s&apos; with UUID {%RTuuid} is already attached to the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9340"/>
+ <source>Device &apos;%s&apos; with unknown type is attached to the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9357"/>
+ <source>Device &apos;%s&apos; with unknown bandwidth group &apos;%s&apos; is attached to the virtual machine &apos;%s&apos; (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9431"/>
+ <location filename="../src-server/MachineImpl.cpp" line="9471"/>
+ <source>This machine does not have any snapshots</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9444"/>
+ <source>Could not find a snapshot with UUID {%s}</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9481"/>
+ <source>Could not find a snapshot named &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9515"/>
+ <location filename="../src-server/MachineImpl.cpp" line="9547"/>
+ <source>Could not find a storage controller named &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9694"/>
+ <source>Could not rename the directory &apos;%s&apos; to &apos;%s&apos; to save the settings file (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9732"/>
+ <source>Could not rename the settings file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9828"/>
+ <source>Could not create a directory &apos;%s&apos; to save the settings file (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9841"/>
+ <source>Could not create the settings file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="9893"/>
+ <source>The machine is not accessible, so cannot save settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="10743"/>
+ <source>Collecting locking information for all attached media failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="10755"/>
+ <source>Locking of attached media failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="10790"/>
+ <source>Skipping attachment without medium</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="10793"/>
+ <source>Skipping medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="10803"/>
+ <source>Creating differencing hard disk for &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Machine::ClientToken</name>
+ <message>
+ <location filename="../src-server/ClientToken.cpp" line="214"/>
+ <source>Cannot create IPC semaphore. Most likely your host kernel lacks support for SysV IPC. Check the host kernel configuration for CONFIG_SYSVIPC=y</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ClientToken.cpp" line="226"/>
+ <source>Cannot create IPC semaphore because the system limit for the maximum number of semaphore sets (SEMMNI), or the system wide maximum number of semaphores (SEMMNS) would be exceeded. The current set of SysV IPC semaphores can be determined from the file /proc/sysvipc/sem</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ClientToken.cpp" line="233"/>
+ <source>Cannot create IPC semaphore because the system-imposed limit on the maximum number of allowed semaphores or semaphore identifiers system-wide would be exceeded</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MachineCloneVM</name>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="875"/>
+ <source>The source machine is mutable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1039"/>
+ <source>Cloning Machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1043"/>
+ <source>Initialize Cloning</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1050"/>
+ <source>Could not create machine clone thread (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1120"/>
+ <source>Could not find data to snapshots &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1198"/>
+ <source>Cloning Disk &apos;%ls&apos; ...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1492"/>
+ <source>Could not create snapshots folder &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1503"/>
+ <source>Copy save state file &apos;%s&apos; ...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1513"/>
+ <source>Could not copy state file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1538"/>
+ <source>Copy NVRAM file &apos;%s&apos; ...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1550"/>
+ <source>Could not copy NVRAM file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1563"/>
+ <source>Create Machine Clone &apos;%s&apos; ...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="1643"/>
+ <source>Could not delete file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MachineCloneVMPrivate</name>
+ <message>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="231"/>
+ <location filename="../src-server/MachineImplCloneVM.cpp" line="268"/>
+ <source>Could not query file size of &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MachineDebugger</name>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="141"/>
+ <source>Writing the sample report to &apos;%s&apos; failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="249"/>
+ <source>EMR3SetExecutionPolicy failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="348"/>
+ <source>PATM not present</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="375"/>
+ <source>CASM not present</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="466"/>
+ <source>%s returned %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="473"/>
+ <source>%s returns too much data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="645"/>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="676"/>
+ <source>DBGFR3OSQueryNameAndVersion failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="734"/>
+ <source>%u is out of range [2..20000]</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="747"/>
+ <source>TMR3SetWarpDrive(, %u) failed with rc=%Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="809"/>
+ <source>The compression parameter must be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="820"/>
+ <source>DBGFR3CoreWrite failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="987"/>
+ <source>DBGFR3Info failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1007"/>
+ <source>DBGFR3InjectNMI failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1024"/>
+ <source>DBGFR3LogModifyFlags failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1041"/>
+ <source>DBGFR3LogModifyGroups failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1058"/>
+ <source>DBGFR3LogModifyDestinations failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1162"/>
+ <source>Plug-in &apos;%s&apos; was not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1164"/>
+ <source>Error unloading &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1200"/>
+ <source>DBGFR3OSDetect failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1239"/>
+ <source>Too much log available, must use the maxMessages parameter to restrict.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1247"/>
+ <source>The dmesg interface isn&apos;t implemented by guest OS digger, or detectOS() has not been called.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1304"/>
+ <source>Register &apos;%s&apos; was not found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1306"/>
+ <source>Invalid CPU ID: %u</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1309"/>
+ <source>DBGFR3RegNmQuery failed with rc=%Rrc querying register &apos;%s&apos; with default cpu set to %u</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1363"/>
+ <source>DBGFR3RegNmQueryAll failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1371"/>
+ <source>DBGFR3RegNmQueryAllCount failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1513"/>
+ <source>DBGFR3StackWalkBegin failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1525"/>
+ <source>Suspending the VM failed with %Rrc
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1542"/>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1560"/>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1579"/>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1623"/>
+ <source>Machine is not running</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1650"/>
+ <source>Creating guest sample report...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MachineDebuggerImpl.cpp" line="1672"/>
+ <source>A sample report is already in progress</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MachineMoveVM</name>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="136"/>
+ <source>The destination path exceeds the maximum value.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="166"/>
+ <source>Unable to determine free space at move destination (&apos;%s&apos;): %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="181"/>
+ <source>Can&apos;t create a test file test.txt in the %s. Check the access rights of the destination folder.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="397"/>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="437"/>
+ <source>Failed to get file size for &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="459"/>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="503"/>
+ <source>Insufficient disk space available (%RTfoff needed, %RTfoff free)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="519"/>
+ <source>Moving Machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="523"/>
+ <source>Initialize Moving</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="527"/>
+ <source>Couldn&apos;t correctly setup the progress object for moving VM operation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="692"/>
+ <source>Could not create snapshots folder &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="704"/>
+ <source>Copy the save state file &apos;%s&apos; ...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="714"/>
+ <source>Could not copy state file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="734"/>
+ <source>Copy the NVRAM file &apos;%s&apos; ...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="744"/>
+ <source>Could not copy NVRAM file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="788"/>
+ <source>Copy Machine settings file &apos;%s&apos; ...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="802"/>
+ <source>Could not create a home machine folder &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="827"/>
+ <source>Could not copy the setting file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="864"/>
+ <source>Could not create log folder &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="882"/>
+ <source>Copying the log file &apos;%s&apos; ...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="892"/>
+ <source>Could not copy the log file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1002"/>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1103"/>
+ <source>Skip the empty operation %d...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1029"/>
+ <source>Rollback scenario: can&apos;t delete new destination folder.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1165"/>
+ <source>Moving medium &apos;%ls&apos; ...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1173"/>
+ <source>Moving medium &apos;%ls&apos; back...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1326"/>
+ <source>Folder &apos;%s&apos; doesn&apos;t exist (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1330"/>
+ <source>Could not open folder &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1343"/>
+ <source>Deleting file %s...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1349"/>
+ <source>Could not delete file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1385"/>
+ <source>Could not get the size of file &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1571"/>
+ <location filename="../src-server/MachineImplMoveVM.cpp" line="1603"/>
+ <source>Could not get file size of &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Medium</name>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1140"/>
+ <location filename="../src-server/MediumImpl.cpp" line="1236"/>
+ <source>Accessibility check was not yet performed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1695"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3315"/>
+ <source>Failed to create medium lock list for &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1701"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3330"/>
+ <source>Failed to lock media &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1880"/>
+ <source>Cannot change the type of DVD medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1887"/>
+ <source>Cannot change the type of floppy medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1893"/>
+ <source>Cannot change the type of medium &apos;%s&apos; because it is a differencing medium</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="1907"/>
+ <source>Cannot change the type of medium &apos;%s&apos; because it is attached to %d virtual machines</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="1930"/>
+ <source>Cannot change type for medium &apos;%s&apos; since it has %d child media</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1937"/>
+ <source>Cannot change type for medium &apos;%s&apos; to &apos;Shareable&apos; since it is a dynamic medium storage unit</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1946"/>
+ <source>Cannot change type for medium &apos;%s&apos; to &apos;Readonly&apos; since it is a hard disk</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="1969"/>
+ <source>Cannot change type for medium &apos;%s&apos;: the media type &apos;MultiAttach&apos; can only be used on media registered with a machine that was created with VirtualBox 4.0 or later</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2083"/>
+ <source>Medium &apos;%s&apos; is not differencing</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2168"/>
+ <source>Argument %s is invalid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2310"/>
+ <source>Counter overflow</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2362"/>
+ <source>Counter underflow</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2383"/>
+ <source>Medium &apos;%s&apos; is not locked for reading</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2483"/>
+ <source>Medium &apos;%s&apos; is not locked for writing</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2523"/>
+ <location filename="../src-server/MediumImpl.cpp" line="2573"/>
+ <location filename="../src-server/MediumImpl.cpp" line="2639"/>
+ <source>Property &apos;%s&apos; does not exist</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2687"/>
+ <source>The medium size argument (%lld) is negative</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2710"/>
+ <source>Medium format &apos;%s&apos; does not support dynamic storage creation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2716"/>
+ <source>Medium format &apos;%s&apos; does not support fixed storage creation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2722"/>
+ <location filename="../src-server/MediumImpl.cpp" line="2884"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3057"/>
+ <source>Medium variant &apos;formatted&apos; applies to floppy images only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2731"/>
+ <source>Creating fixed medium storage unit &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2732"/>
+ <source>Creating dynamic medium storage unit &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2803"/>
+ <source>Medium type of &apos;%s&apos; is Writethrough</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2807"/>
+ <source>Medium type of &apos;%s&apos; is Shareable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2811"/>
+ <source>Medium type of &apos;%s&apos; is Readonly</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="2848"/>
+ <source>Could not lock medium when creating diff &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3017"/>
+ <location filename="../src-server/MediumImpl.cpp" line="6935"/>
+ <source>Failed to lock source media &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3028"/>
+ <location filename="../src-server/MediumImpl.cpp" line="6812"/>
+ <location filename="../src-server/MediumImpl.cpp" line="6946"/>
+ <source>Failed to lock target media &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3035"/>
+ <location filename="../src-server/MediumImpl.cpp" line="6953"/>
+ <source>Creating clone medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3126"/>
+ <source>Medium &apos;%s&apos; can&apos;t be moved. Destination path is empty.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3193"/>
+ <source>Medium &apos;%s&apos; has RAW type. &quot;Move&quot; operation isn&apos;t supported for this type.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3215"/>
+ <source>The given path &apos;%s&apos; is an existing file. Delete or rename this file.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3223"/>
+ <source>Medium &apos;%s&apos; isn&apos;t a file object. &quot;Move&quot; operation isn&apos;t supported.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3231"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3398"/>
+ <location filename="../src-server/MediumImpl.cpp" line="7860"/>
+ <source>The given path &apos;%s&apos; is not fully qualified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3245"/>
+ <source>Medium &apos;%s&apos; is already in the correct location</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3288"/>
+ <source>At least the VM &apos;%s&apos; to whom this medium &apos;%s&apos; attached has currently an opened session. Stop all VMs before relocating this medium</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3337"/>
+ <source>Moving medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3407"/>
+ <source>The given path &apos;%s&apos; is not an existing file. New location is invalid.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3446"/>
+ <source>At least the VM &apos;%s&apos; to whom this medium &apos;%s&apos; attached has currently an opened session. Stop all VMs before set location for this medium</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3512"/>
+ <source>Failed to lock media when compacting &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3519"/>
+ <source>Compacting medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3570"/>
+ <source>Failed to create medium lock list when resizing &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3576"/>
+ <source>Failed to lock media when resizing &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3590"/>
+ <location filename="../src-server/MediumImpl.cpp" line="6425"/>
+ <source>Resizing medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3636"/>
+ <source>Medium type of &apos;%s&apos; is not differencing</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3665"/>
+ <source>Failed to lock media when resetting &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3672"/>
+ <source>Resetting differencing medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3718"/>
+ <source>Cannot encrypt DVD or Floppy medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="3724"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3777"/>
+ <source>Cannot encrypt medium &apos;%s&apos; because it is attached to %d virtual machines</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="3729"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3785"/>
+ <source>Cannot encrypt medium &apos;%s&apos; because it has %d children</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3754"/>
+ <source>Failed to lock media for encryption &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3797"/>
+ <source>Encrypting medium</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3800"/>
+ <source>Decrypting medium</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3869"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3945"/>
+ <location filename="../src-server/MediumImpl.cpp" line="8437"/>
+ <source>Retrieving encryption settings of the image failed because the encryption plugin could not be loaded (%s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3874"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3950"/>
+ <location filename="../src-server/MediumImpl.cpp" line="8442"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10671"/>
+ <source>Encryption is not supported because the extension pack &apos;%s&apos; is missing the encryption plugin (old extension pack installed?)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3879"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3955"/>
+ <location filename="../src-server/MediumImpl.cpp" line="8447"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10676"/>
+ <source>Encryption is not supported because the extension pack &apos;%s&apos; is missing</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3892"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3972"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10744"/>
+ <source>Failed to load the encryption filter: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3898"/>
+ <source>Image is configured for encryption but doesn&apos;t has a KeyId set</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3907"/>
+ <location filename="../src-server/MediumImpl.cpp" line="3978"/>
+ <location filename="../src-server/MediumImpl.cpp" line="8480"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10837"/>
+ <source>Encryption is not supported because extension pack support is not built in</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3927"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10707"/>
+ <source>The image is not configured for encryption</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3931"/>
+ <source>The given password must not be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3969"/>
+ <source>The given password is incorrect</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3992"/>
+ <source>Write access denied: read-only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3996"/>
+ <source>Password given for unencrypted medium</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="3998"/>
+ <source>Password needed for encrypted medium</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="4437"/>
+ <source>Cannot attach medium &apos;%s&apos; {%RTuuid}: %u differencing child media are being created</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="4480"/>
+ <source>Cannot attach medium &apos;%s&apos; {%RTuuid}: medium is already associated with the current state of machine uuid {%RTuuid}!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="4509"/>
+ <source>Cannot attach medium &apos;%s&apos; {%RTuuid} from snapshot &apos;%RTuuid&apos;: medium is already in use by this snapshot!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5159"/>
+ <source>Medium &apos;%s&apos; is attached to a virtual machine with UUID {%RTuuid}. No differencing media based on it may be created until it is detached</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5177"/>
+ <source>Creating differencing medium storage unit &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="5324"/>
+ <source>Medium &apos;%s&apos; cannot be closed because it is still attached to %d virtual machines</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5417"/>
+ <source>Medium format &apos;%s&apos; does not support storage deletion</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="5476"/>
+ <source>Cannot delete storage: medium &apos;%s&apos; is still attached to the following %d virtual machine(s): %s</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5527"/>
+ <source>Failed to lock media when deleting &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5555"/>
+ <source>Deleting medium storage unit &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5736"/>
+ <location filename="../src-server/MediumImpl.cpp" line="5875"/>
+ <source>Media &apos;%s&apos; and &apos;%s&apos; are unrelated</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5931"/>
+ <location filename="../src-server/MediumImpl.cpp" line="5962"/>
+ <location filename="../src-server/MediumImpl.cpp" line="5985"/>
+ <source>Medium &apos;%s&apos; involved in the merge operation has more than one child medium (%d)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="5945"/>
+ <location filename="../src-server/MediumImpl.cpp" line="5991"/>
+ <source>Medium &apos;%s&apos; is attached to %d virtual machines</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5949"/>
+ <location filename="../src-server/MediumImpl.cpp" line="5968"/>
+ <source>Medium &apos;%s&apos; is immutable</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="5953"/>
+ <location filename="../src-server/MediumImpl.cpp" line="5972"/>
+ <source>Medium &apos;%s&apos; is multi-attach</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6110"/>
+ <source>Failed to lock media when merging to &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6256"/>
+ <location filename="../src-server/MediumImpl.cpp" line="9045"/>
+ <source>Merging medium &apos;%s&apos; to &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6261"/>
+ <source>Resizing medium &apos;%s&apos; before merge</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6544"/>
+ <source>Could not update medium UUID references to parent &apos;%s&apos; (%s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6599"/>
+ <source>Failed to add &apos;%s&apos; to output (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6603"/>
+ <source>RTVfsCreateProgressForFile failed when processing &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6606"/>
+ <source>VDCreateVfsFileFromDisk failed for &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="6711"/>
+ <source>Could not create the exported medium &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7258"/>
+ <source>Could not open the medium &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7274"/>
+ <source>Could not update the UUID of medium &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7287"/>
+ <source>Could not update the parent UUID of medium &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7319"/>
+ <source>UUID {%RTuuid} of the medium &apos;%s&apos; does not match the value {%RTuuid} stored in the media registry (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7409"/>
+ <source>Parent medium with UUID {%RTuuid} of the medium &apos;%s&apos; is not found in the media registry (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7432"/>
+ <source>Cannot open differencing image for medium &apos;%s&apos;, because it exceeds the medium tree depth limit. Please merge some images which you no longer need</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7467"/>
+ <source>Medium type of &apos;%s&apos; is differencing but it is not associated with any parent medium in the media registry (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7488"/>
+ <source>Parent UUID {%RTuuid} of the medium &apos;%s&apos; does not match UUID {%RTuuid} of its parent medium stored in the media registry (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7519"/>
+ <source>Could not update and close the medium &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumImpl.cpp" line="7643"/>
+ <source>Cannot close medium &apos;%s&apos; because it has %d child media</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7718"/>
+ <source>Storage for the medium &apos;%s&apos; is not created</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7725"/>
+ <source>Storage for the medium &apos;%s&apos; is already created</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7732"/>
+ <source>Medium &apos;%s&apos; is locked for reading by another task</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7739"/>
+ <source>Medium &apos;%s&apos; is locked for writing by another task</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7748"/>
+ <source>Medium &apos;%s&apos; is not accessible. %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7752"/>
+ <source>Medium &apos;%s&apos; is not accessible</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7759"/>
+ <source>Storage for the medium &apos;%s&apos; is being created</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7766"/>
+ <source>Storage for the medium &apos;%s&apos; is being deleted</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7840"/>
+ <source>Must be at least one extension if it is MediumFormatCapabilities_File
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7845"/>
+ <source>Default extension must not be empty
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7894"/>
+ <source>Permission problem accessing the file for the medium &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7898"/>
+ <source>Could not find file for the medium &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7902"/>
+ <source>Could not get the storage format of the medium &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7918"/>
+ <source>The medium &apos;%s&apos; can&apos;t be used as the requested device type (%s, detected %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="7971"/>
+ <source>Invalid medium storage format &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8452"/>
+ <source>Image &apos;%s&apos; is configured for encryption but doesn&apos;t has a key identifier set</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8458"/>
+ <source>Image &apos;%s&apos; is configured for encryption but there is no key store to retrieve the password from</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8465"/>
+ <source>Failed to retrieve the secret key with ID &quot;%s&quot; from the store (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8473"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10714"/>
+ <source>The password to decrypt the image is incorrect</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8475"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10717"/>
+ <source>Failed to load the decryption filter: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8509"/>
+ <location filename="../src-server/MediumImpl.cpp" line="8803"/>
+ <location filename="../src-server/MediumImpl.cpp" line="9450"/>
+ <location filename="../src-server/MediumImpl.cpp" line="9520"/>
+ <location filename="../src-server/MediumImpl.cpp" line="9777"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10022"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10046"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10155"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10261"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10390"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10458"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10779"/>
+ <source>Could not open the medium storage unit &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8612"/>
+ <source>Parameters for creating the medium storage unit &apos;%s&apos; are invalid%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8616"/>
+ <source>Could not create the medium storage unit &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8625"/>
+ <source>Opening medium storage unit &apos;%s&apos; failed%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8635"/>
+ <source>Formatting medium storage unit &apos;%s&apos; failed: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8638"/>
+ <source>Formatting medium storage unit &apos;%s&apos; failed%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8735"/>
+ <source>Cannot create differencing image for medium &apos;%s&apos;, because it exceeds the medium tree depth limit. Please merge some images which you no longer need</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8832"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10064"/>
+ <source>Parameters for creating the differencing medium storage unit &apos;%s&apos; are invalid%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8836"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10068"/>
+ <source>Could not create the differencing medium storage unit &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="8962"/>
+ <source>Cannot merge image for medium &apos;%s&apos;, because it exceeds the medium tree depth limit. Please merge some images which you no longer need</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9016"/>
+ <source>Failed to lock the medium &apos;%s&apos; to resize before merge</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9028"/>
+ <source>Failed to set size of &apos;%s&apos; to size of &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9039"/>
+ <source>Sizes of &apos;%s&apos; and &apos;%s&apos; are different and medium format does not support resing</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9175"/>
+ <source>Could not merge the medium &apos;%s&apos; to &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9396"/>
+ <source>Cannot clone image for medium &apos;%s&apos;, because it exceeds the medium tree depth limit. Please merge some images which you no longer need</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9562"/>
+ <source>Could not create the clone medium &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9734"/>
+ <source>Wrong preconditions for moving the medium %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9823"/>
+ <source>Could not move medium &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="9919"/>
+ <location filename="../src-server/MediumImpl.cpp" line="10035"/>
+ <source>Could not delete the medium storage unit &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10172"/>
+ <source>Compacting is not yet supported for medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10176"/>
+ <source>Compacting is not implemented, medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10180"/>
+ <source>Could not compact medium &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10279"/>
+ <source>Shrinking is not yet supported for medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10283"/>
+ <source>Resizing to new size %llu is not yet supported for medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10287"/>
+ <source>Resizing is not implemented, medium &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10291"/>
+ <source>Could not resize medium &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10352"/>
+ <source>Cannot import image for medium &apos;%s&apos;, because it exceeds the medium tree depth limit. Please merge some images which you no longer need</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10478"/>
+ <source>Could not create the imported medium &apos;%s&apos;%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10666"/>
+ <source>Encrypting the image failed because the encryption plugin could not be loaded (%s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10700"/>
+ <source>The password given for the encrypted image is incorrect</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10732"/>
+ <source>A password must be given for the image encryption</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10735"/>
+ <source>A valid identifier for the password must be given</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10749"/>
+ <source>The password and password identifier must be empty if the output should be unencrypted</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumImpl.cpp" line="10794"/>
+ <source>Could not prepare disk images for encryption (%Rrc): %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MediumIO</name>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="533"/>
+ <source>Failed to allocate enough secure memory for the key/password</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="535"/>
+ <source>Unknown error happened while adding a password (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="549"/>
+ <source>VDCreateVfsFileFromDisk failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="631"/>
+ <source>Max read size is 256KB, given: %u</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="633"/>
+ <source>Zero byte read is not supported.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumIOImpl.cpp" line="671"/>
+ <source>Error reading %u bytes at %RU64: %Rrc</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="685"/>
+ <source>Zero byte write is not supported.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="687"/>
+ <location filename="../src-server/MediumIOImpl.cpp" line="721"/>
+ <location filename="../src-server/MediumIOImpl.cpp" line="760"/>
+ <source>Medium not opened for writing.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/MediumIOImpl.cpp" line="709"/>
+ <source>Error writing %zu bytes at %RU64: %Rrc</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="740"/>
+ <source>Error formatting (%Rrc): %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="742"/>
+ <source>Error formatting: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="758"/>
+ <source>Invalid partition format type: %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="762"/>
+ <source>whole-disk-in-one-entry is not implemented yet, sorry.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="784"/>
+ <source>RTDvmMapInitialize failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="788"/>
+ <source>RTDvmCreate failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="814"/>
+ <source>Converting medium &apos;%s&apos; to data stream</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>MediumIO::StreamTask</name>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="457"/>
+ <source>Failed to convert and stream disk image</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MediumIOImpl.cpp" line="463"/>
+ <source>Failed to create destination disk container</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Mouse</name>
+ <message>
+ <location filename="../src-client/MouseImpl.cpp" line="510"/>
+ <location filename="../src-client/MouseImpl.cpp" line="553"/>
+ <location filename="../src-client/MouseImpl.cpp" line="619"/>
+ <source>Could not send the mouse event to the virtual mouse (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/MouseImpl.cpp" line="588"/>
+ <source>Could not send the multi-touch event to the virtual device (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>NATEngine</name>
+ <message>
+ <location filename="../src-server/NATEngineImpl.cpp" line="296"/>
+ <source>&apos;%c&apos; - invalid character in NAT rule name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATEngineImpl.cpp" line="306"/>
+ <source>A NAT rule of this name already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATEngineImpl.cpp" line="311"/>
+ <source>A NAT rule for this host port and this host IP already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>NATNetwork</name>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="154"/>
+ <source>Unable to change settings while NATNetwork instance is running</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="221"/>
+ <source>Network name cannot be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="289"/>
+ <source>%s is not a valid IPv4 CIDR notation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="299"/>
+ <source>%s network is too small</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="302"/>
+ <source>%s specifies zero prefix</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="311"/>
+ <source>%s: the specified address is longer than the specified prefix</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="449"/>
+ <source>%s is not a valid IPv6 prefix</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="457"/>
+ <source>Invalid IPv6 prefix length %d, must be 64</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="464"/>
+ <source>IPv6 prefix %RTnaipv6 is not unicast</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="470"/>
+ <source>Non-zero bits in the interface ID part of the IPv6 prefix %RTnaipv6/64</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="480"/>
+ <source>Internal error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="496"/>
+ <source>Setting an empty IPv6 prefix when IPv6 is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="740"/>
+ <source>A NAT rule of this name already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NATNetworkImpl.cpp" line="745"/>
+ <source>A NAT rule for this host port and this host IP already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>NetworkAdapter</name>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="254"/>
+ <source>Invalid network adapter type &apos;%d&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="370"/>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="374"/>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="381"/>
+ <source>Invalid MAC address format</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="525"/>
+ <source>Empty or null bridged interface name is not valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="573"/>
+ <source>Empty or null host only interface name is not valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="628"/>
+ <source>Empty or null host only Network name is not valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="681"/>
+ <source>Empty or null internal network name is not valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="728"/>
+ <source>Empty or null NAT network name is not valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="821"/>
+ <source>Empty or null Cloud network name is not valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/NetworkAdapterImpl.cpp" line="947"/>
+ <source>Invalid promiscuous mode policy (%d)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>NvramStore</name>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="334"/>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="392"/>
+ <source>Loading the NVRAM store failed (%Rrc)
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="346"/>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="732"/>
+ <source>The UEFI NVRAM file is not existing for this machine.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="370"/>
+ <source>Supporting another NVRAM size apart from the default one is not supported right now</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="384"/>
+ <source>The selected firmware type doesn&apos;t support a UEFI variable store</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="422"/>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="425"/>
+ <source>Failed to initialize the UEFI variable store (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="729"/>
+ <source>Opening the UEFI variable store failed (%Rrc).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/NvramStoreImpl.cpp" line="791"/>
+ <source>Failed to save the NVRAM content to disk (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ParallelPort</name>
+ <message>
+ <location filename="../src-server/ParallelPortImpl.cpp" line="260"/>
+ <source>Invalid IRQ number of the parallel port %d: %lu (must be in range [0, %lu])</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/ParallelPortImpl.cpp" line="303"/>
+ <source>Invalid I/O port base address of the parallel port %d: %lu (must be in range [0, 0x%X])</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>PassiveEventListener</name>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="1301"/>
+ <source>HandleEvent() of wrapper shall never be called</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>PerformanceCollector</name>
+ <message>
+ <location filename="../src-server/PerformanceImpl.cpp" line="383"/>
+ <source>Failed to setup metrics for &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/PerformanceImpl.cpp" line="419"/>
+ <source>Failed to enable metrics for &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/PerformanceImpl.cpp" line="455"/>
+ <source>Failed to disable metrics for &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Progress</name>
+ <message>
+ <location filename="../src-all/ProgressImpl.cpp" line="656"/>
+ <source>Result code is not available, operation is still in progress</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ProgressImpl.cpp" line="668"/>
+ <source>Error info is not available, operation is still in progress</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ProgressImpl.cpp" line="737"/>
+ <location filename="../src-all/ProgressImpl.cpp" line="871"/>
+ <source>Operation cannot be canceled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ProgressImpl.cpp" line="786"/>
+ <source>Failed to wait for the task completion (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/ProgressImpl.cpp" line="845"/>
+ <source>Failed to wait for the operation completion (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>ProgressProxy</name>
+ <message>
+ <location filename="../src-server/ProgressProxyImpl.cpp" line="381"/>
+ <source>No error info</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>RecordingScreenSettings</name>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="284"/>
+ <source>Cannot change enabled state of screen while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="328"/>
+ <source>Cannot change features while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="363"/>
+ <source>Cannot change destination type while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="386"/>
+ <source>Error retrieving default file name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="400"/>
+ <source>Cannot change file name while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="404"/>
+ <source>Recording file name &apos;%s&apos; is not absolute</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="432"/>
+ <source>Cannot change maximum time while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="460"/>
+ <source>Cannot change maximum file size while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="488"/>
+ <source>Cannot change options while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="497"/>
+ <source>Invalid option specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="520"/>
+ <source>Cannot change audio codec while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="548"/>
+ <source>Cannot change audio Hertz rate while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="576"/>
+ <source>Cannot change audio bits while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="604"/>
+ <source>Cannot change audio channels while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="632"/>
+ <source>Cannot change video codec while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="660"/>
+ <source>Cannot change video width while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="688"/>
+ <source>Cannot change video height while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="716"/>
+ <source>Cannot change video rate while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="744"/>
+ <source>Cannot change video rate control mode while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="772"/>
+ <source>Cannot change video FPS while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/RecordingScreenSettingsImpl.cpp" line="800"/>
+ <source>Cannot change video rate scaling method while recording is enabled</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>RecordingSettings</name>
+ <message>
+ <location filename="../src-server/RecordingSettingsImpl.cpp" line="306"/>
+ <source>Invalid screen ID specified</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SerialPort</name>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="264"/>
+ <source>Cannot set the raw file mode of the serial port %d because the file path is empty or null</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="271"/>
+ <source>Cannot set the host pipe mode of the serial port %d because the pipe path is empty or null</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="278"/>
+ <source>Cannot set the host device mode of the serial port %d because the device path is empty or null</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="285"/>
+ <source>Cannot set the host device mode of the serial port %d because the server address or TCP port is invalid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="340"/>
+ <source>Invalid IRQ number of the serial port %d: %lu (must be in range [0, %lu])</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="384"/>
+ <source>Invalid I/O port base address of the serial port %d: %lu (must be in range [0, 0x%X])</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SerialPortImpl.cpp" line="770"/>
+ <source>Path of the serial port %d may not be empty or null in host pipe, host device or TCP mode</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Session</name>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="40"/>
+ <source>The session is not locked (session state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="170"/>
+ <source>Trying to set name for a session which is not in state &quot;unlocked&quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="193"/>
+ <source>Failed to query the session machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="197"/>
+ <location filename="../src-client/SessionImpl.cpp" line="227"/>
+ <source>Peer process crashed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="199"/>
+ <source>Failed to query the remote session machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="223"/>
+ <source>Failed to query the console</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="229"/>
+ <source>Failed to query the remote console</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/SessionImpl.cpp" line="952"/>
+ <location filename="../src-client/SessionImpl.cpp" line="998"/>
+ <location filename="../src-client/SessionImpl.cpp" line="1020"/>
+ <location filename="../src-client/SessionImpl.cpp" line="1040"/>
+ <source>Machine is not locked by session (session state: %s).</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SessionMachine</name>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="12816"/>
+ <location filename="../src-server/MachineImpl.cpp" line="12826"/>
+ <source>The VM session was aborted</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="12978"/>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1726"/>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2287"/>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2935"/>
+ <source>The session has been accidentally closed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="12995"/>
+ <source>Trying to save state without a running VM</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13053"/>
+ <source>Cannot save the execution state as the machine is not running or paused (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13060"/>
+ <source>Saving the execution state of the virtual machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13099"/>
+ <source>Cannot adopt the saved machine state as the machine is not in Powered Off, Teleported or Aborted state (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13106"/>
+ <source>Invalid saved state file path &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13131"/>
+ <source>Cannot discard the saved state as the machine is not in the Saved or Aborted-Saved state (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13259"/>
+ <source>Stopping the virtual machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13485"/>
+ <source>The VM session was closed before any attempt to power it on</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13499"/>
+ <source>Closing session</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13523"/>
+ <source>The session is not found in the session list!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="13761"/>
+ <source>Could not load the external authentication library &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="14602"/>
+ <source>Collecting locking information for all attached media failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/MachineImpl.cpp" line="14616"/>
+ <source>Locking of attached media failed. A possible reason is that one of the media is attached to a running VM</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1603"/>
+ <source>Cannot take a snapshot of the machine while it is changing the state (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1646"/>
+ <source>Taking a snapshot of the virtual machine</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1650"/>
+ <source>Setting up snapshot operation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1753"/>
+ <source>Cannot take another snapshot for machine &apos;%s&apos;, because it exceeds the maximum snapshot depth limit. Please delete some earlier snapshot which you no longer need</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1835"/>
+ <source>Saving the machine state</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1858"/>
+ <source>Canceled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1863"/>
+ <source>Reconfiguring medium attachments</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="1891"/>
+ <source>Could not copy NVRAM file &apos;%s&apos; to &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2195"/>
+ <source>Cannot delete the current state of the running machine (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2232"/>
+ <source>Restoring snapshot &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2236"/>
+ <source>Restoring machine settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2675"/>
+ <source>Cannot delete a snapshot of the machine while it is changing the state (machine state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2688"/>
+ <source>Invalid machine state: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/SnapshotImpl.cpp" line="2706"/>
+ <source>Snapshot &apos;%s&apos; of the machine &apos;%s&apos; cannot be deleted, because it has %d child snapshots, which is more than the one snapshot allowed for deletion</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2714"/>
+ <source>Snapshot &apos;%s&apos; of the machine &apos;%s&apos; cannot be deleted, because it is the current snapshot and has one child snapshot</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2787"/>
+ <source>Deleting snapshot &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="2791"/>
+ <source>Setting up</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/SnapshotImpl.cpp" line="3069"/>
+ <source>Unable to merge storage &apos;%s&apos;, because it is smaller than the source image. If you resize it to have a capacity of at least %lld bytes you can retry</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3169"/>
+ <source>Unable to merge storage &apos;%s&apos;. Can&apos;t get storage UID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3211"/>
+ <source>Unable to merge storage &apos;%s&apos;. Path to the storage wasn&apos;t found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3220"/>
+ <source>Unable to merge storage &apos;%s&apos;. Can&apos;t get the storage size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3230"/>
+ <source>Unable to merge storage &apos;%s&apos;. Not enough free storage space</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3263"/>
+ <source>Deleting the execution state</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3292"/>
+ <source>Merging differencing image &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3293"/>
+ <source>Resizing before merge differencing image &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3690"/>
+ <source>Hard disk &apos;%s&apos; has more than one child hard disk (%d)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3859"/>
+ <source>Cannot lock hard disk &apos;%s&apos; for a live merge</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3873"/>
+ <source>Failed to construct lock list for a live merge of hard disk &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="3906"/>
+ <source>Cannot lock hard disk &apos;%s&apos; when deleting a snapshot</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="4088"/>
+ <source>Machine is not locked by a session (session state: %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SharedFolder</name>
+ <message>
+ <location filename="../src-all/SharedFolderImpl.cpp" line="289"/>
+ <source>Invalid shared folder path: &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/SharedFolderImpl.cpp" line="292"/>
+ <source>Shared folder path &apos;%s&apos; is not absolute</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/SharedFolderImpl.cpp" line="297"/>
+ <source>RTPathQueryInfo failed on shared folder path &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/SharedFolderImpl.cpp" line="300"/>
+ <source>Shared folder path &apos;%s&apos; is not a directory</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/SharedFolderImpl.cpp" line="372"/>
+ <source>&apos;%s&apos; is not accessible (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Snapshot</name>
+ <message>
+ <location filename="../src-server/SnapshotImpl.cpp" line="338"/>
+ <source>A machine cannot have a UUID as its name</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>StorageController</name>
+ <message>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="100"/>
+ <source>Invalid storage connection type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="113"/>
+ <source>Too many storage controllers of this type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="317"/>
+ <source>Storage controller named &apos;%s&apos; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="434"/>
+ <source>Invalid controller type %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="502"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="516"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="527"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="538"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="547"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="558"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="570"/>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="581"/>
+ <source>Invalid port count: %lu (must be in range [%lu, %lu])</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/StorageControllerImpl.cpp" line="733"/>
+ <source>The port and/or device parameter are out of range: port=%d (must be in range [0, %d]), device=%d (must be in range [0, %d])</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SystemProperties</name>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="587"/>
+ <source>Invalid storage controller type %d
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="637"/>
+ <source>Invalid storage bus %d
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="744"/>
+ <source>Invalid or unsupported architecture value: %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="854"/>
+ <source>&apos;%s&apos; is missing symbols: CPUMR3DbGetEntries, CPUMR3DbGetEntryByIndex</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="858"/>
+ <source>Failed to construct load &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="861"/>
+ <source>Failed to construct path to the VMM DLL/Dylib/SharedObject: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1098"/>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1144"/>
+ <source>The extension pack &apos;%s&apos; does not exist</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1300"/>
+ <source>Invalid ProxyMode value: %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1340"/>
+ <source>Failed to parse proxy URL: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1343"/>
+ <source>Proxy URL must include a hostname</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1345"/>
+ <source>Proxy URL must not include a path component (%.*s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1348"/>
+ <source>Proxy URL must not include a query component (?%.*s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="1351"/>
+ <source>Proxy URL must not include a fragment component (#%.*s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2044"/>
+ <source>Cannot determine user home directory (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2075"/>
+ <source>Given default machine folder &apos;%s&apos; is not fully qualified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2160"/>
+ <source>Cannot set the autostart database path (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2170"/>
+ <source>Deleting the autostart database path failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2204"/>
+ <source>Cannot determine default Guest Additions ISO location. Most likely they are not available</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2209"/>
+ <source>Given default machine Guest Additions ISO file &apos;%s&apos; is not fully qualified</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2214"/>
+ <source>Given default machine Guest Additions ISO file &apos;%s&apos; does not exist</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2382"/>
+ <source>Invalid Target value: %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/SystemPropertiesImpl.cpp" line="2411"/>
+ <source>Invalid LastCheckDate value: &apos;%s&apos;. Must be in ISO 8601 format (e.g. 2020-05-11T21:13:39.348416000Z)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>USBController</name>
+ <message>
+ <location filename="../src-server/USBControllerImpl.cpp" line="96"/>
+ <source>Invalid USB controller type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBControllerImpl.cpp" line="252"/>
+ <source>USB controller named &apos;%s&apos; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>USBDeviceFilter</name>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="67"/>
+ <source>Vendor ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="68"/>
+ <source>Product ID</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="69"/>
+ <source>Revision</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="70"/>
+ <source>Manufacturer</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="71"/>
+ <source>Product</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="72"/>
+ <source>Serial number</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="73"/>
+ <source>Port number</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="122"/>
+ <source>The %s value &apos;%s&apos; is too big (max 0xFFFF)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="151"/>
+ <source>The %s filter expression &apos;%s&apos; is not valid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="156"/>
+ <source>Insufficient expression space for the &apos;%s&apos; filter expression &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="161"/>
+ <source>Encountered unexpected status %Rrc when setting &apos;%s&apos; to &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFilterImpl.cpp" line="616"/>
+ <source>Remote state filter string &apos;%s&apos; is not valid (error at position %d)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>USBDeviceFilters</name>
+ <message>
+ <location filename="../src-server/USBDeviceFiltersImpl.cpp" line="364"/>
+ <source>The given USB device pFilter is already in the list</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFiltersImpl.cpp" line="421"/>
+ <source>The USB device pFilter list is empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBDeviceFiltersImpl.cpp" line="425"/>
+ <source>Invalid position: %lu (must be in range [0, %lu])</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>USBProxyService</name>
+ <message>
+ <location filename="../src-server/USBProxyService.cpp" line="253"/>
+ <source>The USB device source &quot;%s&quot; could not be found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBProxyService.cpp" line="286"/>
+ <source>The USB device with UUID {%RTuuid} is not currently attached to the host</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBProxyService.cpp" line="922"/>
+ <source>The USB device source &quot;%s&quot; exists already</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBProxyService.cpp" line="934"/>
+ <source>Creating the USB device source &quot;%s&quot; using backend &quot;%s&quot; failed with %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/USBProxyService.cpp" line="941"/>
+ <source>The USB backend &quot;%s&quot; is not supported</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>UefiVariableStore</name>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="171"/>
+ <source>The &apos;SecureBootEnable&apos; variable size is bogus (expected 1, got %llu)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="174"/>
+ <source>Failed to query the &apos;SecureBootEnable&apos; variable size: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="179"/>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="212"/>
+ <source>Failed to query the platform key variable size: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="210"/>
+ <source>Secure boot is not available because the platform key (PK) is not enrolled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="266"/>
+ <source>Failed to remove variable &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="269"/>
+ <source>Failed to open the variable store root (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="272"/>
+ <source>The variable name is too long</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="299"/>
+ <source>Failed to data for variable &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="302"/>
+ <source>Failed to allocate space for the variable &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="346"/>
+ <source>Failed to query the size of variable &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="349"/>
+ <source>Failed to query the owner UUID of variable &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="352"/>
+ <source>Failed to query the attributes of variable &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="402"/>
+ <source>Failed to query the variables: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="782"/>
+ <source>Opening variable storage root directory failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="794"/>
+ <source>Creating the variable &apos;%s&apos; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="820"/>
+ <source>The variable &apos;%s&apos; could not be found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="822"/>
+ <source>Couldn&apos;t open variable &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="837"/>
+ <source>Setting the variable &apos;%s&apos; failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="862"/>
+ <source>Failed to read data of variable &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="867"/>
+ <source>Failed to open variable &apos;%s&apos; for reading: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="886"/>
+ <source>The given signature type is not supported</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="893"/>
+ <source>Failed to add signature to the database (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="926"/>
+ <source>Writing updated signature database failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="930"/>
+ <source>Loading signature database failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UefiVariableStoreImpl.cpp" line="935"/>
+ <source>Creating signature database failed: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>Unattended</name>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="249"/>
+ <source>Failed to open &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="265"/>
+ <source>Failed to open &apos;%s&apos; as ISO FS (%Rrc) - %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="268"/>
+ <source>Failed to open &apos;%s&apos; as ISO FS (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1196"/>
+ <source>No machine associated with this IUnatteded instance</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1253"/>
+ <source>The prepare method has been called (must call done to restart)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1255"/>
+ <source>The &apos;machine&apos; while we were using it - please don&apos;t do that</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1261"/>
+ <source>Could not locate the installation ISO file &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1264"/>
+ <source>Could not locate the Guest Additions ISO file &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1267"/>
+ <source>Could not locate the validation kit ISO file &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1270"/>
+ <source>Could not locate unattended installation script template &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1398"/>
+ <source>Unattended installation is not supported for guest type &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1514"/>
+ <source>reconfigureVM running on other thread</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1523"/>
+ <source>prepare() not yet called</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1678"/>
+ <source>Found non-floppy device attached to port 0 device 0 on the floppy controller &apos;%ls&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="1825"/>
+ <source>Support for recommended storage bus %d not implemented</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/UnattendedImpl.cpp" line="1844"/>
+ <source>Not enough free slots on controller &apos;%s&apos; to add %u DVD drive(s)</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2032"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2049"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2064"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2079"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2094"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2109"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2124"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2139"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2154"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2169"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2184"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2209"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2224"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2246"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2275"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2357"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2377"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2393"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2422"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2447"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2466"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2481"/>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2500"/>
+ <source>Cannot change after prepare() has been called</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2196"/>
+ <source>Expected two lower cased letters, an underscore, and two upper cased letters</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2233"/>
+ <source>Expected two upper cased letters</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2286"/>
+ <source>Unknown keyword: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/UnattendedImpl.cpp" line="2307"/>
+ <source>Hostname &apos;%s&apos; is %zu bytes long, max is 253 (excluding trailing dot)</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2326"/>
+ <source>Invalid hostname &apos;%s&apos; - label %u is too long, max is 63.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2331"/>
+ <source>Invalid hostname &apos;%s&apos; - illegal char &apos;%c&apos; at position %zu</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2336"/>
+ <source>Invalid hostname &apos;%s&apos; - the name part must be at least two characters long</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2343"/>
+ <source>Invalid hostname &apos;%s&apos; - illegal lead char &apos;%c&apos; at position %zu</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2347"/>
+ <source>Invalid hostname &apos;%s&apos; - trailing dot not permitted</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2351"/>
+ <source>Incomplete hostname &apos;%s&apos; - must include both a name and a domain</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2372"/>
+ <source>Empty base path is not allowed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedImpl.cpp" line="2374"/>
+ <source>Base path must be absolute</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>UnattendedInstaller</name>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="167"/>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="185"/>
+ <source>Failed to construct path to the unattended installer script templates (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="210"/>
+ <source>Cannot proceed with an empty installation ISO path</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="212"/>
+ <source>Empty user name is not allowed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="214"/>
+ <source>Empty password is not allowed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="374"/>
+ <source>Failed to open newly created floppy image &apos;%s&apos;: %Rrc: %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="377"/>
+ <source>Failed to open newly created floppy image &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="381"/>
+ <source>Failed to format floppy image &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="386"/>
+ <source>Failed to create floppy image &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/UnattendedInstaller.cpp" line="427"/>
+ <source>Error writing %zu bytes to &apos;%s&apos; in floppy image &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="436"/>
+ <source>Error creating &apos;%s&apos; in floppy image &apos;%s&apos;: %Rrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="508"/>
+ <source>Failed to open ISO image &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="517"/>
+ <source>ISO reader fail to open &apos;%s&apos; (%Rrc): %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="519"/>
+ <source>ISO reader fail to open &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="527"/>
+ <source>RTFsIsoMakerCreate failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="586"/>
+ <source>RTFsIsoMakerAddFileWithVfsFile failed on the script &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../src-server/UnattendedInstaller.cpp" line="591"/>
+ <source>RTVfsFileFromBuffer failed on the %zu byte script &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="604"/>
+ <source>RTFsIsoMakerFinalize failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="619"/>
+ <source>The auxiliary ISO image file &apos;%s&apos; already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="621"/>
+ <source>Failed to open the auxiliary ISO image file &apos;%s&apos; for writing (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="642"/>
+ <source>Error writing auxiliary ISO image &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="647"/>
+ <source>Internal Error: Failed to case VFS file to VFS I/O stream</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="652"/>
+ <source>RTFsIsoMakerCreateVfsOutputFile failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="760"/>
+ <source>RTGetOptArgvToString failed (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="783"/>
+ <source>Error writing &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="786"/>
+ <source>Failed to create &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedInstaller.cpp" line="805"/>
+ <source>Failed to open &apos;%s&apos; on the ISO &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>UnattendedSUSEXMLScript</name>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="575"/>
+ <source>XML document root element is &apos;%s&apos; instead of &apos;profile&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="578"/>
+ <source>Missing XML root element</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>UnattendedScriptTemplate</name>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="133"/>
+ <source>Malformed template placeholder &apos;%.*s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="181"/>
+ <source>%s without @@VBOX_COND_XXX@@ at offset %zu (%#zx)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="207"/>
+ <source>Too deep conditional nesting at offset %zu (%#zx)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="222"/>
+ <source>Missing @@VBOX_COND_END@@</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="224"/>
+ <source>Missing %u @@VBOX_COND_END@@</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="396"/>
+ <source>Unknown guest OS major version &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="445"/>
+ <source>Unknown template placeholder &apos;%.*s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/UnattendedScript.cpp" line="535"/>
+ <source>Unknown conditional placeholder &apos;%.*s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>VBoxEvent</name>
+ <message>
+ <location filename="../src-all/EventImpl.cpp" line="131"/>
+ <source>Internal error (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>VFSExplorer</name>
+ <message>
+ <location filename="../src-server/VFSExplorerImpl.cpp" line="315"/>
+ <source>Can&apos;t open directory &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VFSExplorerImpl.cpp" line="358"/>
+ <source>Internal Error (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VFSExplorerImpl.cpp" line="361"/>
+ <source>Can&apos;t delete file &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VFSExplorerImpl.cpp" line="391"/>
+ <source>Update directory info for &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VFSExplorerImpl.cpp" line="512"/>
+ <source>Delete files</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>VRDEServer</name>
+ <message>
+ <location filename="../src-server/VRDEServerImpl.cpp" line="697"/>
+ <source>failed to query the library setting
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VRDEServerImpl.cpp" line="819"/>
+ <location filename="../src-server/VRDEServerImpl.cpp" line="864"/>
+ <source>Extension pack &apos;%s&apos; does not exist</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>VirtualBox</name>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="545"/>
+ <source>Could not create the VirtualBox home directory &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="1396"/>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2609"/>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2615"/>
+ <source>Not yet implemented</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="1931"/>
+ <source>Machine name is invalid, must not be empty</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="1960"/>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2174"/>
+ <source>&apos;%s&apos; is not a valid Guid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2461"/>
+ <source>Format must be Valid Type%s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2477"/>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2534"/>
+ <source>Device type must be HardDisk, DVD or Floppy %d</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="2703"/>
+ <source>Could not set extra data because someone refused the requested change of &apos;%s&apos; to &apos;%s&apos;%s%ls</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3270"/>
+ <source>Could not create the communication channel (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3279"/>
+ <source>Cannot get executable name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3315"/>
+ <source>Operation canceled by the user</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3317"/>
+ <source>Could not launch a privileged process &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3327"/>
+ <source>Could not launch a process &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3350"/>
+ <source>Could not operate the communication channel (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3901"/>
+ <source>Could not find a registered machine with UUID {%RTuuid}</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="3951"/>
+ <source>Could not find a registered machine named &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4029"/>
+ <source>Machine group &apos;%s&apos; conflicts with a virtual machine name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4033"/>
+ <source>Invalid machine group &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4109"/>
+ <source>Could not find an open hard disk with UUID {%RTuuid}</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4161"/>
+ <source>Could not find an open hard disk with location &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4198"/>
+ <source>Invalid image file location &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4243"/>
+ <source>Cannot mount DVD medium &apos;%s&apos; as floppy</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4246"/>
+ <source>Cannot mount floppy medium &apos;%s&apos; as DVD</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4261"/>
+ <source>Could not find an image file with UUID {%RTuuid} in the media registry (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4266"/>
+ <source>Could not find an image file with location &apos;%s&apos; in the media registry (&apos;%s&apos;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4311"/>
+ <source>Guid &apos;%s&apos; is invalid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4351"/>
+ <source>&apos;%s&apos; is not a valid Guest OS type</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4538"/>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5091"/>
+ <source>hard disk</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4544"/>
+ <source>CD/DVD image</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4551"/>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5098"/>
+ <source>floppy image</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="4567"/>
+ <source>%s &apos;%s&apos; with UUID {%RTuuid}</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5018"/>
+ <source>Registered machine with UUID {%RTuuid} (&apos;%s&apos;) already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5094"/>
+ <source>DVD image</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5129"/>
+ <source>Cannot register the %s &apos;%s&apos; {%RTuuid} because a %s already exists</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5555"/>
+ <source>Could not create the directory &apos;%s&apos; (%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="5561"/>
+ <source>Directory &apos;%s&apos; does not exist</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="6080"/>
+ <source>The provided progress object GUID is invalid</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-server/VirtualBoxImpl.cpp" line="6092"/>
+ <source>The progress object with the given GUID could not be found</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>VirtualBoxBase</name>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="176"/>
+ <source>Assertion failed: [%s] at &apos;%s&apos; (%d) in %s.
+Please contact the product vendor!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="196"/>
+ <source>Assertion failed: at &apos;%s&apos; (%d) in %s.
+Please contact the product vendor!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="218"/>
+ <source>Assertion failed: [%s] at &apos;%s&apos; (%d) in %s.
+%s.
+Please contact the product vendor!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="236"/>
+ <source>Assertion failed: at &apos;%s&apos; (%d) in %s.
+%s.
+Please contact the product vendor!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="366"/>
+ <location filename="../include/VirtualBoxBase.h" line="409"/>
+ <source>Argument %s is NULL</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="380"/>
+ <location filename="../include/VirtualBoxBase.h" line="424"/>
+ <source>Argument %s is an invalid pointer</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="394"/>
+ <source>Argument %s points to invalid memory location (%p)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="438"/>
+ <source>Argument %s is empty or an invalid pointer</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="457"/>
+ <source>GUID argument %s is not valid (&quot;%ls&quot;)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="473"/>
+ <source>Argument %s is invalid (must be %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="491"/>
+ <source>Argument %s %s</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="506"/>
+ <location filename="../include/VirtualBoxBase.h" line="521"/>
+ <location filename="../include/Wrapper.h" line="42"/>
+ <source>Output argument %s points to invalid memory location (%p)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../include/VirtualBoxBase.h" line="530"/>
+ <source>Method %s is not implemented</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="242"/>
+ <source>Unexpected exception: %s [%s]
+%s[%d] (%s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="251"/>
+ <source>Unknown exception
+%s[%d] (%s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="348"/>
+ <source>A parameter has an invalid value</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="349"/>
+ <source>A parameter is an invalid pointer</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="350"/>
+ <source>The result of the operation is unexpected</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="351"/>
+ <source>The access to an object is not allowed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="352"/>
+ <source>The allocation of new memory failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="353"/>
+ <source>The requested operation is not implemented</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="354"/>
+ <source>The requested interface is not implemented</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="355"/>
+ <source>A general error occurred</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="356"/>
+ <source>The operation was canceled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="357"/>
+ <source>Object corresponding to the supplied arguments does not exist</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="358"/>
+ <source>Current virtual machine state prevents the operation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="359"/>
+ <source>Virtual machine error occurred attempting the operation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="360"/>
+ <source>File not accessible or erroneous file contents</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="361"/>
+ <source>Runtime subsystem error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="362"/>
+ <source>Pluggable Device Manager error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="363"/>
+ <source>Current object state prohibits operation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="364"/>
+ <source>Host operating system related error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="365"/>
+ <source>Requested operation is not supported</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="366"/>
+ <source>Invalid XML found</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="367"/>
+ <source>Current session state prohibits operation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="368"/>
+ <source>Object being in use prohibits operation</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="369"/>
+ <source>Incorrect password provided</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-all/VirtualBoxBase.cpp" line="370"/>
+ <source>Unknown error</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>VirtualBoxClient</name>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="247"/>
+ <source>Failed to create semaphore (rc=%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="256"/>
+ <source>Failed to create watcher thread (rc=%Rrc)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="307"/>
+ <source>VBoxSDS is misconfigured to run under the &apos;%ls&apos; account instead of the SYSTEM one.
+Reinstall VirtualBox to fix it. Alternatively you can fix it using the Windows Service Control Manager or by running &apos;sc config VBoxSDS obj=LocalSystem&apos; on a command line.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="312"/>
+ <source>The VBoxSDS windows service is disabled.
+Reinstall VirtualBox to fix it. Alternatively try reenable the service by setting it to &apos;Manual&apos; startup type in the Windows Service management console, or by runing &apos;sc config VBoxSDS start=demand&apos; on the command line.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="319"/>
+ <source>The VBoxSDS windows service was not found.
+Reinstall VirtualBox to fix it. Alternatively you can try start VirtualBox as Administrator, this should automatically reinstall the service, or you can run &apos;VBoxSDS.exe --regservice&apos; command from an elevated Administrator command line.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="339"/>
+ <source>Completely failed to instantiate CLSID_VirtualBox: %Rhrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="340"/>
+ <source>Completely failed to instantiate CLSID_VirtualBox: %Rhrc &amp; %Rhrc</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="354"/>
+ <source>Failed to instantiate CLSID_VirtualBox the first time, but worked when checking out why ... weird</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="441"/>
+ <source>Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.
+PSDispatch looks broken by the &apos;%ls&apos; (%ls) program, suspecting that it features the broken oleaut32.msm module as component %ls.
+
+We suggest you try uninstall &apos;%ls&apos;.
+
+See also https://support.microsoft.com/en-us/kb/316911 </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="457"/>
+ <source>Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, CLSID_VirtualBox w/ IUnknown works.
+PSDispatch looks broken by installer %ls featuring the broken oleaut32.msm module as component %ls.
+
+See also https://support.microsoft.com/en-us/kb/316911 </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="467"/>
+ <source>Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, CLSID_VirtualBox w/ IUnknown works.
+PSDispatch looks broken by some installer featuring the broken oleaut32.msm module as a component.
+
+See also https://support.microsoft.com/en-us/kb/316911 </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="473"/>
+ <source>Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.
+PSDispatch looks fine. Weird</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="476"/>
+ <source>Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.
+Checking out PSDispatch registration ended with error: %u (%#x)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="676"/>
+ <source>Could not check the accessibility status of the VM</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="682"/>
+ <source>Could not get the access error message of the VM</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="783"/>
+ <source>Failed to load user language instance</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="801"/>
+ <source>Failed to register listener</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../src-client/VirtualBoxClientImpl.cpp" line="807"/>
+ <source>Failed to get event source from VirtualBox</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/src/VBox/Main/nls/dummy.c b/src/VBox/Main/nls/dummy.c
new file mode 100644
index 00000000..2b0dd4c8
--- /dev/null
+++ b/src/VBox/Main/nls/dummy.c
@@ -0,0 +1,32 @@
+/* $Id: dummy.c $ */
+/** @file
+ * Dummy C file for the VirtualBoxAPI NLS target.
+ */
+
+/*
+ * Copyright (C) 2021-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+int main(void)
+{
+ return 1;
+}
+
diff --git a/src/VBox/Main/src-all/AuthLibrary.cpp b/src/VBox/Main/src-all/AuthLibrary.cpp
new file mode 100644
index 00000000..6bce2ef9
--- /dev/null
+++ b/src/VBox/Main/src-all/AuthLibrary.cpp
@@ -0,0 +1,267 @@
+/* $Id: AuthLibrary.cpp $ */
+/** @file
+ * Main - External authentication library interface.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN
+#include "AuthLibrary.h"
+#include "LoggingNew.h"
+
+#include <iprt/err.h>
+#include <iprt/ldr.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+typedef struct AuthCtx
+{
+ AuthResult result;
+
+ PAUTHENTRY3 pfnAuthEntry3;
+ PAUTHENTRY2 pfnAuthEntry2;
+ PAUTHENTRY pfnAuthEntry;
+
+ const char *pszCaller;
+ PAUTHUUID pUuid;
+ AuthGuestJudgement guestJudgement;
+ const char *pszUser;
+ const char *pszPassword;
+ const char *pszDomain;
+ int fLogon;
+ unsigned clientId;
+} AuthCtx;
+
+static DECLCALLBACK(int) authThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+ AuthCtx *pCtx = (AuthCtx *)pvUser;
+
+ if (pCtx->pfnAuthEntry3)
+ {
+ pCtx->result = pCtx->pfnAuthEntry3(pCtx->pszCaller, pCtx->pUuid, pCtx->guestJudgement,
+ pCtx->pszUser, pCtx->pszPassword, pCtx->pszDomain,
+ pCtx->fLogon, pCtx->clientId);
+ }
+ else if (pCtx->pfnAuthEntry2)
+ {
+ pCtx->result = pCtx->pfnAuthEntry2(pCtx->pUuid, pCtx->guestJudgement,
+ pCtx->pszUser, pCtx->pszPassword, pCtx->pszDomain,
+ pCtx->fLogon, pCtx->clientId);
+ }
+ else if (pCtx->pfnAuthEntry)
+ {
+ pCtx->result = pCtx->pfnAuthEntry(pCtx->pUuid, pCtx->guestJudgement,
+ pCtx->pszUser, pCtx->pszPassword, pCtx->pszDomain);
+ }
+ return VINF_SUCCESS;
+}
+
+static AuthResult authCall(AuthCtx *pCtx)
+{
+ AuthResult result = AuthResultAccessDenied;
+
+ /* Use a separate thread because external modules might need a lot of stack space. */
+ RTTHREAD thread = NIL_RTTHREAD;
+ int vrc = RTThreadCreate(&thread, authThread, pCtx, 512*_1K,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "VRDEAuth");
+ LogFlowFunc(("RTThreadCreate %Rrc\n", vrc));
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTThreadWait(thread, RT_INDEFINITE_WAIT, NULL);
+ LogFlowFunc(("RTThreadWait %Rrc\n", vrc));
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Only update the result if the thread finished without errors. */
+ result = pCtx->result;
+ }
+ else
+ {
+ LogRel(("AUTH: Unable to execute the auth thread %Rrc\n", vrc));
+ }
+
+ return result;
+}
+
+int AuthLibLoad(AUTHLIBRARYCONTEXT *pAuthLibCtx, const char *pszLibrary)
+{
+ RT_ZERO(*pAuthLibCtx);
+
+ /* Load the external authentication library. */
+ LogRel(("AUTH: Loading external authentication library '%s'\n", pszLibrary));
+
+ int vrc;
+ if (RTPathHavePath(pszLibrary))
+ vrc = RTLdrLoad(pszLibrary, &pAuthLibCtx->hAuthLibrary);
+ else
+ {
+ vrc = RTLdrLoadAppPriv(pszLibrary, &pAuthLibCtx->hAuthLibrary);
+ if (RT_FAILURE(vrc))
+ {
+ /* Backward compatibility with old default 'VRDPAuth' name.
+ * Try to load new default 'VBoxAuth' instead.
+ */
+ if (RTStrICmp(pszLibrary, "VRDPAuth") == 0)
+ {
+ LogRel(("AUTH: Loading external authentication library 'VBoxAuth'\n"));
+ vrc = RTLdrLoadAppPriv("VBoxAuth", &pAuthLibCtx->hAuthLibrary);
+ }
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("AUTH: Failed to load external authentication library: %Rrc\n", vrc));
+ pAuthLibCtx->hAuthLibrary = NIL_RTLDRMOD;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ typedef struct AuthEntryInfoStruct
+ {
+ const char *pszName;
+ void **ppvAddress;
+ } AuthEntryInfo;
+
+ AuthEntryInfo entries[] =
+ {
+ { AUTHENTRY3_NAME, (void **)&pAuthLibCtx->pfnAuthEntry3 },
+ { AUTHENTRY2_NAME, (void **)&pAuthLibCtx->pfnAuthEntry2 },
+ { AUTHENTRY_NAME, (void **)&pAuthLibCtx->pfnAuthEntry },
+ { NULL, NULL }
+ };
+
+ /* Get the entry point. */
+ AuthEntryInfo *pEntryInfo = &entries[0];
+ while (pEntryInfo->pszName)
+ {
+ *pEntryInfo->ppvAddress = NULL;
+
+ int vrc2 = RTLdrGetSymbol(pAuthLibCtx->hAuthLibrary, pEntryInfo->pszName, pEntryInfo->ppvAddress);
+ if (RT_SUCCESS(vrc2))
+ {
+ /* Found an entry point. */
+ LogRel(("AUTH: Using entry point '%s'\n", pEntryInfo->pszName));
+ vrc = VINF_SUCCESS;
+ break;
+ }
+
+ if (vrc2 != VERR_SYMBOL_NOT_FOUND)
+ LogRel(("AUTH: Could not resolve import '%s': %Rrc\n", pEntryInfo->pszName, vrc2));
+
+ vrc = vrc2;
+
+ pEntryInfo++;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ AuthLibUnload(pAuthLibCtx);
+
+ return vrc;
+}
+
+void AuthLibUnload(AUTHLIBRARYCONTEXT *pAuthLibCtx)
+{
+ if (pAuthLibCtx->hAuthLibrary != NIL_RTLDRMOD)
+ RTLdrClose(pAuthLibCtx->hAuthLibrary);
+
+ RT_ZERO(*pAuthLibCtx);
+ pAuthLibCtx->hAuthLibrary = NIL_RTLDRMOD;
+}
+
+AuthResult AuthLibAuthenticate(const AUTHLIBRARYCONTEXT *pAuthLibCtx,
+ PCRTUUID pUuid, AuthGuestJudgement guestJudgement,
+ const char *pszUser, const char *pszPassword, const char *pszDomain,
+ uint32_t u32ClientId)
+{
+ AuthResult result = AuthResultAccessDenied;
+
+ AUTHUUID rawuuid;
+ memcpy(rawuuid, pUuid, sizeof(rawuuid));
+
+ LogFlowFunc(("pAuthLibCtx = %p, uuid = %RTuuid, guestJudgement = %d, pszUser = %s, pszPassword = %s, pszDomain = %s, u32ClientId = %d\n",
+ pAuthLibCtx, rawuuid, guestJudgement, pszUser, pszPassword, pszDomain, u32ClientId));
+
+ if ( pAuthLibCtx->hAuthLibrary
+ && (pAuthLibCtx->pfnAuthEntry || pAuthLibCtx->pfnAuthEntry2 || pAuthLibCtx->pfnAuthEntry3))
+ {
+ AuthCtx ctx;
+ ctx.result = AuthResultAccessDenied; /* Denied by default. */
+ ctx.pfnAuthEntry3 = pAuthLibCtx->pfnAuthEntry3;
+ ctx.pfnAuthEntry2 = pAuthLibCtx->pfnAuthEntry2;
+ ctx.pfnAuthEntry = pAuthLibCtx->pfnAuthEntry;
+ ctx.pszCaller = "vrde";
+ ctx.pUuid = &rawuuid;
+ ctx.guestJudgement = guestJudgement;
+ ctx.pszUser = pszUser;
+ ctx.pszPassword = pszPassword;
+ ctx.pszDomain = pszDomain;
+ ctx.fLogon = true;
+ ctx.clientId = u32ClientId;
+
+ result = authCall(&ctx);
+ }
+ else
+ {
+ LogRelMax(8, ("AUTH: Invalid authentication module context\n"));
+ AssertFailed();
+ }
+
+ LogFlowFunc(("result = %d\n", result));
+
+ return result;
+}
+
+void AuthLibDisconnect(const AUTHLIBRARYCONTEXT *pAuthLibCtx, PCRTUUID pUuid, uint32_t u32ClientId)
+{
+ AUTHUUID rawuuid;
+ memcpy(rawuuid, pUuid, sizeof(rawuuid));
+
+ LogFlowFunc(("pAuthLibCtx = %p, , uuid = %RTuuid, u32ClientId = %d\n",
+ pAuthLibCtx, rawuuid, u32ClientId));
+
+ if ( pAuthLibCtx->hAuthLibrary
+ && (pAuthLibCtx->pfnAuthEntry || pAuthLibCtx->pfnAuthEntry2 || pAuthLibCtx->pfnAuthEntry3))
+ {
+ AuthCtx ctx;
+ ctx.result = AuthResultAccessDenied; /* Not used. */
+ ctx.pfnAuthEntry3 = pAuthLibCtx->pfnAuthEntry3;
+ ctx.pfnAuthEntry2 = pAuthLibCtx->pfnAuthEntry2;
+ ctx.pfnAuthEntry = NULL; /* Does not use disconnect notification. */
+ ctx.pszCaller = "vrde";
+ ctx.pUuid = &rawuuid;
+ ctx.guestJudgement = AuthGuestNotAsked;
+ ctx.pszUser = NULL;
+ ctx.pszPassword = NULL;
+ ctx.pszDomain = NULL;
+ ctx.fLogon = false;
+ ctx.clientId = u32ClientId;
+
+ authCall(&ctx);
+ }
+}
diff --git a/src/VBox/Main/src-all/AutoCaller.cpp b/src/VBox/Main/src-all/AutoCaller.cpp
new file mode 100644
index 00000000..a91b7946
--- /dev/null
+++ b/src/VBox/Main/src-all/AutoCaller.cpp
@@ -0,0 +1,585 @@
+/* $Id: AutoCaller.cpp $ */
+/** @file
+ * VirtualBox object state implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN
+#include <iprt/semaphore.h>
+
+#include "VirtualBoxBase.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+#include "VBoxNls.h"
+
+
+DECLARE_TRANSLATION_CONTEXT(AutoCallerCtx);
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ObjectState methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+
+ObjectState::ObjectState() : mStateLock(LOCKCLASS_OBJECTSTATE)
+{
+ AssertFailed();
+}
+
+ObjectState::ObjectState(VirtualBoxBase *aObj) :
+ mObj(aObj), mStateLock(LOCKCLASS_OBJECTSTATE)
+{
+ Assert(mObj);
+ mState = NotReady;
+ mStateChangeThread = NIL_RTTHREAD;
+ mCallers = 0;
+ mFailedRC = S_OK;
+ mpFailedEI = NULL;
+ mZeroCallersSem = NIL_RTSEMEVENT;
+ mInitUninitSem = NIL_RTSEMEVENTMULTI;
+ mInitUninitWaiters = 0;
+}
+
+ObjectState::~ObjectState()
+{
+ Assert(mInitUninitWaiters == 0);
+ Assert(mInitUninitSem == NIL_RTSEMEVENTMULTI);
+ if (mZeroCallersSem != NIL_RTSEMEVENT)
+ RTSemEventDestroy(mZeroCallersSem);
+ mCallers = 0;
+ mStateChangeThread = NIL_RTTHREAD;
+ mState = NotReady;
+ mFailedRC = S_OK;
+ if (mpFailedEI)
+ {
+ delete mpFailedEI;
+ mpFailedEI = NULL;
+ }
+ mObj = NULL;
+}
+
+ObjectState::State ObjectState::getState()
+{
+ AutoReadLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+ return mState;
+}
+
+/**
+ * Increments the number of calls to this object by one.
+ *
+ * After this method succeeds, it is guaranteed that the object will remain
+ * in the Ready (or in the Limited) state at least until #releaseCaller() is
+ * called.
+ *
+ * This method is intended to mark the beginning of sections of code within
+ * methods of COM objects that depend on the readiness (Ready) state. The
+ * Ready state is a primary "ready to serve" state. Usually all code that
+ * works with component's data depends on it. On practice, this means that
+ * almost every public method, setter or getter of the object should add
+ * itself as an object's caller at the very beginning, to protect from an
+ * unexpected uninitialization that may happen on a different thread.
+ *
+ * Besides the Ready state denoting that the object is fully functional,
+ * there is a special Limited state. The Limited state means that the object
+ * is still functional, but its functionality is limited to some degree, so
+ * not all operations are possible. The @a aLimited argument to this method
+ * determines whether the caller represents this limited functionality or
+ * not.
+ *
+ * This method succeeds (and increments the number of callers) only if the
+ * current object's state is Ready. Otherwise, it will return E_ACCESSDENIED
+ * to indicate that the object is not operational. There are two exceptions
+ * from this rule:
+ * <ol>
+ * <li>If the @a aLimited argument is |true|, then this method will also
+ * succeed if the object's state is Limited (or Ready, of course).
+ * </li>
+ * <li>If this method is called from the same thread that placed
+ * the object to InInit or InUninit state (i.e. either from within the
+ * AutoInitSpan or AutoUninitSpan scope), it will succeed as well (but
+ * will not increase the number of callers).
+ * </li>
+ * </ol>
+ *
+ * Normally, calling addCaller() never blocks. However, if this method is
+ * called by a thread created from within the AutoInitSpan scope and this
+ * scope is still active (i.e. the object state is InInit), it will block
+ * until the AutoInitSpan destructor signals that it has finished
+ * initialization.
+ *
+ * When this method returns a failure, the caller must not use the object
+ * and should return the failed result code to its own caller.
+ *
+ * @param aLimited |true| to add a limited caller.
+ *
+ * @return S_OK on success or E_ACCESSDENIED on failure.
+ *
+ * @sa #releaseCaller()
+ */
+HRESULT ObjectState::addCaller(bool aLimited /* = false */)
+{
+ AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = E_ACCESSDENIED;
+
+ if (mState == Ready || (aLimited && mState == Limited))
+ {
+ /* if Ready or allows Limited, increase the number of callers */
+ ++mCallers;
+ hrc = S_OK;
+ }
+ else
+ if (mState == InInit || mState == InUninit)
+ {
+ if (mStateChangeThread == RTThreadSelf())
+ {
+ /* Called from the same thread that is doing AutoInitSpan or
+ * AutoUninitSpan, just succeed */
+ hrc = S_OK;
+ }
+ else if (mState == InInit)
+ {
+ /* addCaller() is called by a "child" thread while the "parent"
+ * thread is still doing AutoInitSpan/AutoReinitSpan, so wait for
+ * the state to become either Ready/Limited or InitFailed (in
+ * case of init failure).
+ *
+ * Note that we increase the number of callers anyway -- to
+ * prevent AutoUninitSpan from early completion if we are
+ * still not scheduled to pick up the posted semaphore when
+ * uninit() is called.
+ */
+ ++mCallers;
+
+ /* lazy semaphore creation */
+ if (mInitUninitSem == NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiCreate(&mInitUninitSem);
+ Assert(mInitUninitWaiters == 0);
+ }
+
+ ++mInitUninitWaiters;
+
+ LogFlowThisFunc(("Waiting for AutoInitSpan/AutoReinitSpan to finish...\n"));
+
+ stateLock.release();
+ RTSemEventMultiWait(mInitUninitSem, RT_INDEFINITE_WAIT);
+ stateLock.acquire();
+
+ if (--mInitUninitWaiters == 0)
+ {
+ /* destroy the semaphore since no more necessary */
+ RTSemEventMultiDestroy(mInitUninitSem);
+ mInitUninitSem = NIL_RTSEMEVENTMULTI;
+ }
+
+ if (mState == Ready || (aLimited && mState == Limited))
+ hrc = S_OK;
+ else
+ {
+ Assert(mCallers != 0);
+ --mCallers;
+ if (mCallers == 0 && mState == InUninit)
+ {
+ /* inform AutoUninitSpan ctor there are no more callers */
+ RTSemEventSignal(mZeroCallersSem);
+ }
+ }
+ }
+ }
+
+ if (FAILED(hrc))
+ {
+ if (mState == Limited)
+ hrc = mObj->setError(hrc, AutoCallerCtx::tr("The object functionality is limited"));
+ else if (FAILED(mFailedRC) && mFailedRC != E_ACCESSDENIED)
+ {
+ /* replay recorded error information */
+ if (mpFailedEI)
+ ErrorInfoKeeper eik(*mpFailedEI);
+ hrc = mFailedRC;
+ }
+ else
+ hrc = mObj->setError(hrc, AutoCallerCtx::tr("The object is not ready"));
+ }
+
+ return hrc;
+}
+
+/**
+ * Decreases the number of calls to this object by one.
+ *
+ * Must be called after every #addCaller() when protecting the object
+ * from uninitialization is no more necessary.
+ */
+void ObjectState::releaseCaller()
+{
+ AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+
+ if (mState == Ready || mState == Limited)
+ {
+ /* if Ready or Limited, decrease the number of callers */
+ AssertMsgReturn(mCallers != 0, ("mCallers is ZERO!"), (void) 0);
+ --mCallers;
+
+ return;
+ }
+
+ if (mState == InInit || mState == InUninit)
+ {
+ if (mStateChangeThread == RTThreadSelf())
+ {
+ /* Called from the same thread that is doing AutoInitSpan or
+ * AutoUninitSpan: just succeed */
+ return;
+ }
+
+ if (mState == InUninit)
+ {
+ /* the caller is being released after AutoUninitSpan has begun */
+ AssertMsgReturn(mCallers != 0, ("mCallers is ZERO!"), (void) 0);
+ --mCallers;
+
+ if (mCallers == 0)
+ /* inform the Auto*UninitSpan ctor there are no more callers */
+ RTSemEventSignal(mZeroCallersSem);
+
+ return;
+ }
+ }
+
+ AssertMsgFailed(("mState = %d!", mState));
+}
+
+bool ObjectState::autoInitSpanConstructor(ObjectState::State aExpectedState)
+{
+ AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+
+ mFailedRC = S_OK;
+ if (mpFailedEI)
+ {
+ delete mpFailedEI;
+ mpFailedEI = NULL;
+ }
+
+ if (mState == aExpectedState)
+ {
+ setState(InInit);
+ return true;
+ }
+ else
+ return false;
+}
+
+void ObjectState::autoInitSpanDestructor(State aNewState, HRESULT aFailedRC, com::ErrorInfo *apFailedEI)
+{
+ AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+
+ Assert(mState == InInit);
+
+ if (mCallers > 0 && mInitUninitWaiters > 0)
+ {
+ /* We have some pending addCaller() calls on other threads (created
+ * during InInit), signal that InInit is finished and they may go on. */
+ RTSemEventMultiSignal(mInitUninitSem);
+ }
+
+ if (aNewState == InitFailed || aNewState == Limited)
+ {
+ mFailedRC = aFailedRC;
+ /* apFailedEI may be NULL, when there is no explicit setFailed() or
+ * setLimited() call, which also implies that aFailedRC is S_OK.
+ * This case is used by objects (the majority) which don't want
+ * delayed error signalling. */
+ mpFailedEI = apFailedEI;
+ }
+ else
+ {
+ Assert(SUCCEEDED(aFailedRC));
+ Assert(apFailedEI == NULL);
+ Assert(mpFailedEI == NULL);
+ }
+
+ setState(aNewState);
+}
+
+ObjectState::State ObjectState::autoUninitSpanConstructor(bool fTry)
+{
+ AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+
+ Assert(mState != InInit);
+
+ if (mState == NotReady)
+ {
+ /* do nothing if already uninitialized */
+ return mState;
+ }
+ else if (mState == InUninit)
+ {
+ /* Another thread has already started uninitialization, wait for its
+ * completion. This is necessary to make sure that when this method
+ * returns, the object state is well-defined (NotReady). */
+
+ if (fTry)
+ return Ready;
+
+ /* lazy semaphore creation */
+ if (mInitUninitSem == NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiCreate(&mInitUninitSem);
+ Assert(mInitUninitWaiters == 0);
+ }
+ ++mInitUninitWaiters;
+
+ LogFlowFunc(("{%p}: Waiting for AutoUninitSpan to finish...\n", mObj));
+
+ stateLock.release();
+ RTSemEventMultiWait(mInitUninitSem, RT_INDEFINITE_WAIT);
+ stateLock.acquire();
+
+ if (--mInitUninitWaiters == 0)
+ {
+ /* destroy the semaphore since no more necessary */
+ RTSemEventMultiDestroy(mInitUninitSem);
+ mInitUninitSem = NIL_RTSEMEVENTMULTI;
+ }
+
+ /* the other thread set it to NotReady */
+ return mState;
+ }
+
+ /* go to InUninit to prevent from adding new callers */
+ setState(InUninit);
+
+ /* wait for already existing callers to drop to zero */
+ if (mCallers > 0)
+ {
+ if (fTry)
+ return Ready;
+
+ /* lazy creation */
+ Assert(mZeroCallersSem == NIL_RTSEMEVENT);
+ RTSemEventCreate(&mZeroCallersSem);
+
+ /* wait until remaining callers release the object */
+ LogFlowFunc(("{%p}: Waiting for callers (%d) to drop to zero...\n",
+ mObj, mCallers));
+
+ stateLock.release();
+ RTSemEventWait(mZeroCallersSem, RT_INDEFINITE_WAIT);
+ }
+ return mState;
+}
+
+void ObjectState::autoUninitSpanDestructor()
+{
+ AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+
+ Assert(mState == InUninit);
+
+ setState(NotReady);
+}
+
+
+void ObjectState::setState(ObjectState::State aState)
+{
+ Assert(mState != aState);
+ mState = aState;
+ mStateChangeThread = RTThreadSelf();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AutoInitSpan methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Creates a smart initialization span object that places the object to
+ * InInit state.
+ *
+ * Please see the AutoInitSpan class description for more info.
+ *
+ * @param aObj |this| pointer of the managed VirtualBoxBase object whose
+ * init() method is being called.
+ * @param aResult Default initialization result.
+ */
+AutoInitSpan::AutoInitSpan(VirtualBoxBase *aObj,
+ Result aResult /* = Failed */)
+ : mObj(aObj),
+ mResult(aResult),
+ mOk(false),
+ mFailedRC(S_OK),
+ mpFailedEI(NULL)
+{
+ Assert(mObj);
+ mOk = mObj->getObjectState().autoInitSpanConstructor(ObjectState::NotReady);
+ AssertReturnVoid(mOk);
+}
+
+/**
+ * Places the managed VirtualBoxBase object to Ready/Limited state if the
+ * initialization succeeded or partly succeeded, or places it to InitFailed
+ * state and calls the object's uninit() method.
+ *
+ * Please see the AutoInitSpan class description for more info.
+ */
+AutoInitSpan::~AutoInitSpan()
+{
+ /* if the state was other than NotReady, do nothing */
+ if (!mOk)
+ {
+ Assert(SUCCEEDED(mFailedRC));
+ Assert(mpFailedEI == NULL);
+ return;
+ }
+
+ ObjectState::State newState;
+ if (mResult == Succeeded)
+ newState = ObjectState::Ready;
+ else if (mResult == Limited)
+ newState = ObjectState::Limited;
+ else
+ newState = ObjectState::InitFailed;
+ mObj->getObjectState().autoInitSpanDestructor(newState, mFailedRC, mpFailedEI);
+ mFailedRC = S_OK;
+ mpFailedEI = NULL; /* now owned by ObjectState instance */
+ if (newState == ObjectState::InitFailed)
+ {
+ /* call uninit() to let the object uninit itself after failed init() */
+ mObj->uninit();
+ }
+}
+
+// AutoReinitSpan methods
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Creates a smart re-initialization span object and places the object to
+ * InInit state.
+ *
+ * Please see the AutoInitSpan class description for more info.
+ *
+ * @param aObj |this| pointer of the managed VirtualBoxBase object whose
+ * re-initialization method is being called.
+ */
+AutoReinitSpan::AutoReinitSpan(VirtualBoxBase *aObj)
+ : mObj(aObj),
+ mSucceeded(false),
+ mOk(false)
+{
+ Assert(mObj);
+ mOk = mObj->getObjectState().autoInitSpanConstructor(ObjectState::Limited);
+ AssertReturnVoid(mOk);
+}
+
+/**
+ * Places the managed VirtualBoxBase object to Ready state if the
+ * re-initialization succeeded (i.e. #setSucceeded() has been called) or back to
+ * Limited state otherwise.
+ *
+ * Please see the AutoInitSpan class description for more info.
+ */
+AutoReinitSpan::~AutoReinitSpan()
+{
+ /* if the state was other than Limited, do nothing */
+ if (!mOk)
+ return;
+
+ ObjectState::State newState;
+ if (mSucceeded)
+ newState = ObjectState::Ready;
+ else
+ newState = ObjectState::Limited;
+ mObj->getObjectState().autoInitSpanDestructor(newState, S_OK, NULL);
+ /* If later AutoReinitSpan can truly fail (today there is no way) then
+ * in this place there needs to be an mObj->uninit() call just like in
+ * the AutoInitSpan destructor. In that case it might make sense to
+ * let AutoReinitSpan inherit from AutoInitSpan, as the code can be
+ * made (almost) identical. */
+}
+
+// AutoUninitSpan methods
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Creates a smart uninitialization span object and places this object to
+ * InUninit state.
+ *
+ * Please see the AutoInitSpan class description for more info.
+ *
+ * @note This method blocks the current thread execution until the number of
+ * callers of the managed VirtualBoxBase object drops to zero!
+ *
+ * @param aObj |this| pointer of the VirtualBoxBase object whose uninit()
+ * method is being called.
+ * @param fTry @c true if the wait for other callers should be skipped,
+ * requiring checking if the uninit span is actually operational.
+ */
+AutoUninitSpan::AutoUninitSpan(VirtualBoxBase *aObj, bool fTry /* = false */)
+ : mObj(aObj),
+ mInitFailed(false),
+ mUninitDone(false),
+ mUninitFailed(false)
+{
+ Assert(mObj);
+ ObjectState::State state;
+ state = mObj->getObjectState().autoUninitSpanConstructor(fTry);
+ if (state == ObjectState::InitFailed)
+ mInitFailed = true;
+ else if (state == ObjectState::NotReady)
+ mUninitDone = true;
+ else if (state == ObjectState::Ready)
+ mUninitFailed = true;
+}
+
+/**
+ * Places the managed VirtualBoxBase object to the NotReady state.
+ */
+AutoUninitSpan::~AutoUninitSpan()
+{
+ /* do nothing if already uninitialized */
+ if (mUninitDone || mUninitFailed)
+ return;
+
+ mObj->getObjectState().autoUninitSpanDestructor();
+}
+
+/**
+ * Marks the uninitializion as succeeded.
+ *
+ * Same as the destructor, and makes the destructor do nothing.
+ */
+void AutoUninitSpan::setSucceeded()
+{
+ /* do nothing if already uninitialized */
+ if (mUninitDone || mUninitFailed)
+ return;
+
+ mObj->getObjectState().autoUninitSpanDestructor();
+ mUninitDone = true;
+}
diff --git a/src/VBox/Main/src-all/CryptoUtils.cpp b/src/VBox/Main/src-all/CryptoUtils.cpp
new file mode 100644
index 00000000..3411f3e0
--- /dev/null
+++ b/src/VBox/Main/src-all/CryptoUtils.cpp
@@ -0,0 +1,344 @@
+/* $Id: CryptoUtils.cpp $ */
+/** @file
+ * Main - Cryptographic utility functions used by both VBoxSVC and VBoxC.
+ */
+
+/*
+ * Copyright (C) 2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/vfs.h>
+
+#include "CryptoUtils.h"
+
+
+/*static*/
+DECLCALLBACK(int) SsmStream::i_ssmCryptoWrite(void *pvUser, uint64_t offStream, const void *pvBuf, size_t cbToWrite)
+{
+ SsmStream *pThis = static_cast<SsmStream *>(pvUser);
+
+ return RTVfsFileWriteAt(pThis->m_hVfsFile, (RTFOFF)offStream, pvBuf, cbToWrite, NULL /*pcbWritten*/);
+}
+
+
+/*static*/
+DECLCALLBACK(int) SsmStream::i_ssmCryptoRead(void *pvUser, uint64_t offStream, void *pvBuf, size_t cbToRead, size_t *pcbRead)
+{
+ SsmStream *pThis = static_cast<SsmStream *>(pvUser);
+
+ return RTVfsFileReadAt(pThis->m_hVfsFile, (RTFOFF)offStream, pvBuf, cbToRead, pcbRead);
+}
+
+
+/*static*/
+DECLCALLBACK(int) SsmStream::i_ssmCryptoSeek(void *pvUser, int64_t offSeek, unsigned uMethod, uint64_t *poffActual)
+{
+ SsmStream *pThis = static_cast<SsmStream *>(pvUser);
+
+ return RTVfsFileSeek(pThis->m_hVfsFile, (RTFOFF)offSeek, uMethod, poffActual);
+}
+
+
+/*static*/
+DECLCALLBACK(uint64_t) SsmStream::i_ssmCryptoTell(void *pvUser)
+{
+ SsmStream *pThis = static_cast<SsmStream *>(pvUser);
+
+ return (uint64_t)RTVfsFileTell(pThis->m_hVfsFile);
+}
+
+
+/*static*/
+DECLCALLBACK(int) SsmStream::i_ssmCryptoSize(void *pvUser, uint64_t *pcb)
+{
+ SsmStream *pThis = static_cast<SsmStream *>(pvUser);
+
+ return RTVfsFileQuerySize(pThis->m_hVfsFile, pcb);
+}
+
+
+/*static*/
+DECLCALLBACK(int) SsmStream::i_ssmCryptoIsOk(void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ /** @todo */
+ return VINF_SUCCESS;
+}
+
+
+/*static*/
+DECLCALLBACK(int) SsmStream::i_ssmCryptoClose(void *pvUser, bool fCancelled)
+{
+ SsmStream *pThis = static_cast<SsmStream *>(pvUser);
+
+ RT_NOREF(fCancelled); /** @todo */
+ RTVfsFileRelease(pThis->m_hVfsFile);
+ pThis->m_hVfsFile = NIL_RTVFSFILE;
+ return VINF_SUCCESS;
+}
+
+
+#ifdef VBOX_COM_INPROC
+SsmStream::SsmStream(Console *pParent, PCVMMR3VTABLE pVMM, SecretKeyStore *pKeyStore, const Utf8Str &strKeyId, const Utf8Str &strKeyStore)
+#else
+SsmStream::SsmStream(VirtualBox *pParent, SecretKeyStore *pKeyStore, const Utf8Str &strKeyId, const Utf8Str &strKeyStore)
+#endif
+{
+ m_StrmOps.u32Version = SSMSTRMOPS_VERSION;
+ m_StrmOps.pfnWrite = SsmStream::i_ssmCryptoWrite;
+ m_StrmOps.pfnRead = SsmStream::i_ssmCryptoRead;
+ m_StrmOps.pfnSeek = SsmStream::i_ssmCryptoSeek;
+ m_StrmOps.pfnTell = SsmStream::i_ssmCryptoTell;
+ m_StrmOps.pfnSize = SsmStream::i_ssmCryptoSize;
+ m_StrmOps.pfnIsOk = SsmStream::i_ssmCryptoIsOk;
+ m_StrmOps.pfnClose = SsmStream::i_ssmCryptoClose;
+ m_StrmOps.u32EndVersion = SSMSTRMOPS_VERSION;
+
+ m_pKeyStore = pKeyStore;
+ m_strKeyId = strKeyId;
+ m_strKeyStore = strKeyStore;
+ m_pParent = pParent;
+ m_hVfsFile = NIL_RTVFSFILE;
+ m_pSsm = NULL;
+ m_pCryptoIf = NULL;
+#ifdef VBOX_COM_INPROC
+ m_pVMM = pVMM;
+#endif
+}
+
+
+SsmStream::~SsmStream()
+{
+ close();
+
+ if (m_pCryptoIf)
+ m_pParent->i_releaseCryptoIf(m_pCryptoIf);
+
+ m_pCryptoIf = NULL;
+ m_pKeyStore = NULL;
+}
+
+
+int SsmStream::open(const Utf8Str &strFilename, bool fWrite, PSSMHANDLE *ppSsmHandle)
+{
+ /* Fast path, if the saved state is not encrypted we can skip everything and let SSM handle the file. */
+ if (m_strKeyId.isEmpty())
+ {
+ AssertReturn(!fWrite, VERR_NOT_SUPPORTED);
+
+#ifdef VBOX_COM_INPROC
+ int vrc = m_pVMM->pfnSSMR3Open(strFilename.c_str(), NULL /*pStreamOps*/, NULL /*pvStreamOps*/,
+ 0 /*fFlags*/, &m_pSsm);
+#else
+ int vrc = SSMR3Open(strFilename.c_str(), NULL /*pStreamOps*/, NULL /*pvStreamOps*/,
+ 0 /*fFlags*/, &m_pSsm);
+#endif
+ if ( RT_SUCCESS(vrc)
+ && ppSsmHandle)
+ *ppSsmHandle = m_pSsm;
+
+ return vrc;
+ }
+
+ int vrc = VINF_SUCCESS;
+ if (!m_pCryptoIf)
+ {
+#ifdef VBOX_COM_INPROC
+ vrc = m_pParent->i_retainCryptoIf(&m_pCryptoIf);
+ if (RT_FAILURE(vrc))
+ return vrc;
+#else
+ HRESULT hrc = m_pParent->i_retainCryptoIf(&m_pCryptoIf);
+ if (FAILED(hrc))
+ return VERR_COM_IPRT_ERROR;
+#endif
+ }
+
+ SecretKey *pKey;
+ vrc = m_pKeyStore->retainSecretKey(m_strKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSFILE hVfsFileSsm = NIL_RTVFSFILE;
+ uint32_t fOpen = fWrite
+ ? RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE
+ : RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE;
+
+ vrc = RTVfsFileOpenNormal(strFilename.c_str(), fOpen, &hVfsFileSsm);
+ if (RT_SUCCESS(vrc))
+ {
+ const char *pszPassword = (const char *)pKey->getKeyBuffer();
+
+ vrc = m_pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFileSsm, m_strKeyStore.c_str(), pszPassword, &m_hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+#ifdef VBOX_COM_INPROC
+ vrc = m_pVMM->pfnSSMR3Open(NULL /*pszFilename*/, &m_StrmOps, this, 0 /*fFlags*/, &m_pSsm);
+#else
+ vrc = SSMR3Open(NULL /*pszFilename*/, &m_StrmOps, this, 0 /*fFlags*/, &m_pSsm);
+#endif
+ if ( RT_SUCCESS(vrc)
+ && ppSsmHandle)
+ *ppSsmHandle = m_pSsm;
+
+ if (RT_FAILURE(vrc))
+ {
+ RTVfsFileRelease(m_hVfsFile);
+ m_hVfsFile = NIL_RTVFSFILE;
+ }
+ }
+
+ /* Also release in success case because the encrypted file handle retained a new reference to it. */
+ RTVfsFileRelease(hVfsFileSsm);
+ }
+
+ pKey->release();
+ }
+
+ return vrc;
+}
+
+
+int SsmStream::open(const Utf8Str &strFilename)
+{
+#ifdef VBOX_COM_INPROC
+ RTVFSFILE hVfsFileSsm = NIL_RTVFSFILE;
+ uint32_t fOpen = RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE;
+
+ int vrc = RTVfsFileOpenNormal(strFilename.c_str(), fOpen, &hVfsFileSsm);
+ if (RT_SUCCESS(vrc))
+ {
+ if (m_strKeyId.isNotEmpty())
+ {
+ /* File is encrypted, set up machinery. */
+ if (!m_pCryptoIf)
+ vrc = m_pParent->i_retainCryptoIf(&m_pCryptoIf);
+
+ if (RT_SUCCESS(vrc))
+ {
+ SecretKey *pKey;
+ vrc = m_pKeyStore->retainSecretKey(m_strKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ const char *pszPassword = (const char *)pKey->getKeyBuffer();
+
+ vrc = m_pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFileSsm, m_strKeyStore.c_str(), pszPassword, &m_hVfsFile);
+ pKey->release();
+ }
+
+ /* Also release in success case because the encrypted file handle retained a new reference to it. */
+ RTVfsFileRelease(hVfsFileSsm);
+ }
+ }
+ else /* File is not encrypted. */
+ m_hVfsFile = hVfsFileSsm;
+ }
+
+ return vrc;
+#else
+ RT_NOREF(strFilename);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+
+int SsmStream::create(const Utf8Str &strFilename)
+{
+#ifdef VBOX_COM_INPROC
+ RTVFSFILE hVfsFileSsm = NIL_RTVFSFILE;
+ uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE;
+
+ int vrc = RTVfsFileOpenNormal(strFilename.c_str(), fOpen, &hVfsFileSsm);
+ if (RT_SUCCESS(vrc))
+ {
+ if (m_strKeyId.isNotEmpty())
+ {
+ /* File is encrypted, set up machinery. */
+ if (!m_pCryptoIf)
+ vrc = m_pParent->i_retainCryptoIf(&m_pCryptoIf);
+
+ if (RT_SUCCESS(vrc))
+ {
+ SecretKey *pKey;
+ vrc = m_pKeyStore->retainSecretKey(m_strKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ const char *pszPassword = (const char *)pKey->getKeyBuffer();
+
+ vrc = m_pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFileSsm, m_strKeyStore.c_str(), pszPassword, &m_hVfsFile);
+ pKey->release();
+ }
+
+ /* Also release in success case because the encrypted file handle retained a new reference to it. */
+ RTVfsFileRelease(hVfsFileSsm);
+ if (RT_FAILURE(vrc))
+ RTFileDelete(strFilename.c_str());
+ }
+ }
+ else /* File doesn't need to be encrypted. */
+ m_hVfsFile = hVfsFileSsm;
+ }
+
+ return vrc;
+#else
+ RT_NOREF(strFilename);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+
+int SsmStream::querySsmStrmOps(PCSSMSTRMOPS *ppStrmOps, void **ppvStrmOpsUser)
+{
+ AssertReturn(m_hVfsFile != NIL_RTVFSFILE, VERR_INVALID_STATE);
+
+ *ppStrmOps = &m_StrmOps;
+ *ppvStrmOpsUser = this;
+ return VINF_SUCCESS;
+}
+
+
+int SsmStream::close(void)
+{
+ if (m_pSsm)
+ {
+#ifdef VBOX_COM_INPROC
+ int vrc = m_pVMM->pfnSSMR3Close(m_pSsm);
+#else
+ int vrc = SSMR3Close(m_pSsm);
+#endif
+ AssertRCReturn(vrc, vrc);
+ }
+
+ if (m_hVfsFile != NIL_RTVFSFILE)
+ RTVfsFileRelease(m_hVfsFile);
+
+ m_hVfsFile = NIL_RTVFSFILE;
+ m_pSsm = NULL;
+#ifdef VBOX_COM_INPROC
+ m_pVMM = NULL;
+#endif
+
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/Main/src-all/DisplayPNGUtil.cpp b/src/VBox/Main/src-all/DisplayPNGUtil.cpp
new file mode 100644
index 00000000..6323c85e
--- /dev/null
+++ b/src/VBox/Main/src-all/DisplayPNGUtil.cpp
@@ -0,0 +1,233 @@
+/* $Id: DisplayPNGUtil.cpp $ */
+/** @file
+ * PNG utilities
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY
+#include "DisplayImpl.h"
+
+#include <iprt/alloc.h>
+
+#include <png.h>
+
+#define kMaxSizePNG 1024
+
+typedef struct PNGWriteCtx
+{
+ uint8_t *pu8PNG;
+ uint32_t cbPNG;
+ uint32_t cbAllocated;
+ int vrc;
+} PNGWriteCtx;
+
+static void PNGAPI png_write_data_fn(png_structp png_ptr, png_bytep p, png_size_t cb) RT_NOTHROW_DEF
+{
+ PNGWriteCtx *pCtx = (PNGWriteCtx *)png_get_io_ptr(png_ptr);
+ LogFlowFunc(("png_ptr %p, p %p, cb %d, pCtx %p\n", png_ptr, p, cb, pCtx));
+
+ if (pCtx && RT_SUCCESS(pCtx->vrc))
+ {
+ if (pCtx->cbAllocated - pCtx->cbPNG < cb)
+ {
+ uint32_t cbNew = pCtx->cbPNG + (uint32_t)cb;
+ AssertReturnVoidStmt(cbNew > pCtx->cbPNG && cbNew <= _1G, pCtx->vrc = VERR_TOO_MUCH_DATA);
+ cbNew = RT_ALIGN_32(cbNew, 4096) + 4096;
+
+ void *pNew = RTMemRealloc(pCtx->pu8PNG, cbNew);
+ if (!pNew)
+ {
+ pCtx->vrc = VERR_NO_MEMORY;
+ return;
+ }
+
+ pCtx->pu8PNG = (uint8_t *)pNew;
+ pCtx->cbAllocated = cbNew;
+ }
+
+ memcpy(pCtx->pu8PNG + pCtx->cbPNG, p, cb);
+ pCtx->cbPNG += (uint32_t)cb;
+ }
+}
+
+static void PNGAPI png_output_flush_fn(png_structp png_ptr) RT_NOTHROW_DEF
+{
+ NOREF(png_ptr);
+ /* Do nothing. */
+}
+
+int DisplayMakePNG(uint8_t *pu8Data, uint32_t cx, uint32_t cy,
+ uint8_t **ppu8PNG, uint32_t *pcbPNG, uint32_t *pcxPNG, uint32_t *pcyPNG,
+ uint8_t fLimitSize)
+{
+ int vrc = VINF_SUCCESS;
+
+ uint8_t * volatile pu8Bitmap = NULL; /* gcc setjmp warning */
+ uint32_t volatile cbBitmap = 0; /* gcc setjmp warning */
+ uint32_t volatile cxBitmap = 0; /* gcc setjmp warning */
+ uint32_t volatile cyBitmap = 0; /* gcc setjmp warning */
+
+ if (!fLimitSize || (cx < kMaxSizePNG && cy < kMaxSizePNG))
+ {
+ /* Save unscaled screenshot. */
+ pu8Bitmap = pu8Data;
+ cbBitmap = cx * 4 * cy;
+ cxBitmap = cx;
+ cyBitmap = cy;
+ }
+ else
+ {
+ /* Large screenshot, scale. */
+ if (cx > cy)
+ {
+ cxBitmap = kMaxSizePNG;
+ cyBitmap = (kMaxSizePNG * cy) / cx;
+ }
+ else
+ {
+ cyBitmap = kMaxSizePNG;
+ cxBitmap = (kMaxSizePNG * cx) / cy;
+ }
+
+ cbBitmap = cxBitmap * 4 * cyBitmap;
+
+ pu8Bitmap = (uint8_t *)RTMemAlloc(cbBitmap);
+
+ if (pu8Bitmap)
+ {
+ BitmapScale32(pu8Bitmap /*dst*/,
+ (int)cxBitmap, (int)cyBitmap,
+ pu8Data /*src*/,
+ (int)cx * 4,
+ (int)cx, (int)cy);
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ LogFlowFunc(("%dx%d -> %dx%d\n", cx, cy, cxBitmap, cyBitmap));
+
+ if (RT_SUCCESS(vrc))
+ {
+ png_bytep *row_pointers = (png_bytep *)RTMemAlloc(cyBitmap * sizeof(png_bytep));
+ if (row_pointers)
+ {
+ png_infop info_ptr = NULL;
+ png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+ (png_voidp)NULL, /* error/warning context pointer */
+ (png_error_ptr)NULL, /* error function */
+ (png_error_ptr)NULL /* warning function */);
+ if (png_ptr)
+ {
+ info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr)
+ {
+#if RT_MSC_PREREQ(RT_MSC_VER_VC140)
+#pragma warning(push,3)
+ if (!setjmp(png_jmpbuf(png_ptr)))
+#pragma warning(pop)
+#else
+ if (!setjmp(png_jmpbuf(png_ptr)))
+#endif
+ {
+ PNGWriteCtx ctx;
+ ctx.pu8PNG = NULL;
+ ctx.cbPNG = 0;
+ ctx.cbAllocated = 0;
+ ctx.vrc = VINF_SUCCESS;
+
+ png_set_write_fn(png_ptr,
+ (png_voidp)&ctx,
+ png_write_data_fn,
+ png_output_flush_fn);
+
+ png_set_IHDR(png_ptr, info_ptr,
+ cxBitmap, cyBitmap,
+ 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ png_bytep row_pointer = (png_bytep)pu8Bitmap;
+ unsigned i = 0;
+ for (; i < cyBitmap; i++, row_pointer += cxBitmap * 4)
+ {
+ row_pointers[i] = row_pointer;
+ }
+ png_set_rows(png_ptr, info_ptr, &row_pointers[0]);
+
+ png_write_info(png_ptr, info_ptr);
+ png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
+ png_set_bgr(png_ptr);
+
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_IDAT))
+ png_write_image(png_ptr, png_get_rows(png_ptr, info_ptr));
+
+ png_write_end(png_ptr, info_ptr);
+
+ vrc = ctx.vrc;
+
+ if (RT_SUCCESS(vrc))
+ {
+ *ppu8PNG = ctx.pu8PNG;
+ *pcbPNG = ctx.cbPNG;
+ *pcxPNG = cxBitmap;
+ *pcyPNG = cyBitmap;
+ LogFlowFunc(("PNG %d bytes, bitmap %d bytes\n", ctx.cbPNG, cbBitmap));
+ }
+ }
+ else
+ {
+ vrc = VERR_GENERAL_FAILURE; /* Something within libpng. */
+ }
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr
+ : (png_infopp)NULL);
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ RTMemFree(row_pointers);
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (pu8Bitmap && pu8Bitmap != pu8Data)
+ {
+ RTMemFree(pu8Bitmap);
+ }
+
+ return vrc;
+
+}
diff --git a/src/VBox/Main/src-all/DisplayResampleImage.cpp b/src/VBox/Main/src-all/DisplayResampleImage.cpp
new file mode 100644
index 00000000..04f0cdbe
--- /dev/null
+++ b/src/VBox/Main/src-all/DisplayResampleImage.cpp
@@ -0,0 +1,159 @@
+/* $Id: DisplayResampleImage.cpp $ */
+/** @file
+ * Image resampling code, used for snapshot thumbnails.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <iprt/types.h>
+
+DECLINLINE(void) imageSetPixel (uint8_t *im, int x, int y, int color, int w)
+{
+ *(int32_t *)(im + y * w * 4 + x * 4) = color;
+}
+
+#define trueColorGetAlpha(c) (((c) & 0x7F000000) >> 24)
+#define trueColorGetRed(c) (((c) & 0xFF0000) >> 16)
+#define trueColorGetGreen(c) (((c) & 0x00FF00) >> 8)
+#define trueColorGetBlue(c) ((c) & 0x0000FF)
+
+/* Fast integer implementation for 32 bpp bitmap scaling.
+ * Using fixed point values * 16.
+ */
+typedef int32_t FIXEDPOINT;
+#define INT_TO_FIXEDPOINT(i) (FIXEDPOINT)((i) << 4)
+#define FIXEDPOINT_TO_INT(v) (int)((v) >> 4)
+#define FIXEDPOINT_FLOOR(v) ((v) & ~0xF)
+#define FIXEDPOINT_FRACTION(v) ((v) & 0xF)
+
+/* For 32 bit source only. */
+void BitmapScale32 (uint8_t *dst,
+ int dstW, int dstH,
+ const uint8_t *src,
+ int iDeltaLine,
+ int srcW, int srcH)
+{
+ int x, y;
+
+ for (y = 0; y < dstH; y++)
+ {
+ FIXEDPOINT sy1 = INT_TO_FIXEDPOINT(y * srcH) / dstH;
+ FIXEDPOINT sy2 = INT_TO_FIXEDPOINT((y + 1) * srcH) / dstH;
+
+ for (x = 0; x < dstW; x++)
+ {
+ FIXEDPOINT red = 0, green = 0, blue = 0;
+
+ FIXEDPOINT sx1 = INT_TO_FIXEDPOINT(x * srcW) / dstW;
+ FIXEDPOINT sx2 = INT_TO_FIXEDPOINT((x + 1) * srcW) / dstW;
+
+ FIXEDPOINT spixels = (sx2 - sx1) * (sy2 - sy1);
+
+ FIXEDPOINT sy = sy1;
+
+ do
+ {
+ FIXEDPOINT yportion;
+ if (FIXEDPOINT_FLOOR (sy) == FIXEDPOINT_FLOOR (sy1))
+ {
+ yportion = INT_TO_FIXEDPOINT(1) - FIXEDPOINT_FRACTION(sy);
+ if (yportion > sy2 - sy1)
+ {
+ yportion = sy2 - sy1;
+ }
+ sy = FIXEDPOINT_FLOOR (sy);
+ }
+ else if (sy == FIXEDPOINT_FLOOR (sy2))
+ {
+ yportion = FIXEDPOINT_FRACTION(sy2);
+ }
+ else
+ {
+ yportion = INT_TO_FIXEDPOINT(1);
+ }
+
+ const uint8_t *pu8SrcLine = src + iDeltaLine * FIXEDPOINT_TO_INT(sy);
+ FIXEDPOINT sx = sx1;
+ do
+ {
+ FIXEDPOINT xportion;
+ FIXEDPOINT pcontribution;
+ if (FIXEDPOINT_FLOOR (sx) == FIXEDPOINT_FLOOR (sx1))
+ {
+ xportion = INT_TO_FIXEDPOINT(1) - FIXEDPOINT_FRACTION(sx);
+ if (xportion > sx2 - sx1)
+ {
+ xportion = sx2 - sx1;
+ }
+ pcontribution = xportion * yportion;
+ sx = FIXEDPOINT_FLOOR (sx);
+ }
+ else if (sx == FIXEDPOINT_FLOOR (sx2))
+ {
+ xportion = FIXEDPOINT_FRACTION(sx2);
+ pcontribution = xportion * yportion;
+ }
+ else
+ {
+ xportion = INT_TO_FIXEDPOINT(1);
+ pcontribution = xportion * yportion;
+ }
+ /* Color depth specific code begin */
+ int32_t p = *(int32_t *)(pu8SrcLine + FIXEDPOINT_TO_INT(sx) * 4);
+ /* Color depth specific code end */
+ red += trueColorGetRed (p) * pcontribution;
+ green += trueColorGetGreen (p) * pcontribution;
+ blue += trueColorGetBlue (p) * pcontribution;
+
+ sx += INT_TO_FIXEDPOINT(1);
+ } while (sx < sx2);
+
+ sy += INT_TO_FIXEDPOINT(1);
+ } while (sy < sy2);
+
+ if (spixels != 0)
+ {
+ red /= spixels;
+ green /= spixels;
+ blue /= spixels;
+ }
+ /* Clamping to allow for rounding errors above */
+ if (red > 255)
+ {
+ red = 255;
+ }
+ if (green > 255)
+ {
+ green = 255;
+ }
+ if (blue > 255)
+ {
+ blue = 255;
+ }
+ imageSetPixel (dst,
+ x, y,
+ ( ((int) red) << 16) + (((int) green) << 8) + ((int) blue),
+ dstW);
+ }
+ }
+}
diff --git a/src/VBox/Main/src-all/DisplayUtils.cpp b/src/VBox/Main/src-all/DisplayUtils.cpp
new file mode 100644
index 00000000..a75001d3
--- /dev/null
+++ b/src/VBox/Main/src-all/DisplayUtils.cpp
@@ -0,0 +1,225 @@
+/* $Id: DisplayUtils.cpp $ */
+/** @file
+ * Implementation of IDisplay helpers, currently only used in VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <DisplayUtils.h>
+
+#include <iprt/log.h>
+#include <VBox/err.h>
+#include <VBox/vmm/ssm.h>
+#include <VBoxVideo.h>
+
+int readSavedDisplayScreenshot(SsmStream &ssmStream, const Utf8Str &strStateFilePath,
+ uint32_t u32Type, uint8_t **ppu8Data, uint32_t *pcbData,
+ uint32_t *pu32Width, uint32_t *pu32Height)
+{
+ LogFlowFunc(("u32Type = %d [%s]\n", u32Type, strStateFilePath.c_str()));
+
+ /** @todo cache read data */
+ if (strStateFilePath.isEmpty())
+ {
+ /* No saved state data. */
+ return VERR_NOT_SUPPORTED;
+ }
+
+ uint8_t *pu8Data = NULL;
+ uint32_t cbData = 0;
+ uint32_t u32Width = 0;
+ uint32_t u32Height = 0;
+
+ PSSMHANDLE pSSM;
+ int vrc = ssmStream.open(strStateFilePath.c_str(), false /*fWrite*/, &pSSM);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t uVersion;
+ vrc = SSMR3Seek(pSSM, "DisplayScreenshot", 1100 /*iInstance*/, &uVersion);
+ if (RT_SUCCESS(vrc))
+ {
+ if (uVersion == sSSMDisplayScreenshotVer)
+ {
+ uint32_t cBlocks;
+ vrc = SSMR3GetU32(pSSM, &cBlocks);
+ AssertRCReturn(vrc, vrc);
+
+ for (uint32_t i = 0; i < cBlocks; i++)
+ {
+ uint32_t cbBlock;
+ vrc = SSMR3GetU32(pSSM, &cbBlock);
+ AssertRCBreak(vrc);
+
+ uint32_t typeOfBlock;
+ vrc = SSMR3GetU32(pSSM, &typeOfBlock);
+ AssertRCBreak(vrc);
+
+ LogFlowFunc(("[%d] type %d, size %d bytes\n", i, typeOfBlock, cbBlock));
+
+ if (typeOfBlock == u32Type)
+ {
+ if (cbBlock > 2 * sizeof(uint32_t))
+ {
+ cbData = (uint32_t)(cbBlock - 2 * sizeof(uint32_t));
+ pu8Data = (uint8_t *)RTMemAlloc(cbData);
+ if (pu8Data == NULL)
+ {
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+
+ vrc = SSMR3GetU32(pSSM, &u32Width);
+ AssertRCBreak(vrc);
+ vrc = SSMR3GetU32(pSSM, &u32Height);
+ AssertRCBreak(vrc);
+ vrc = SSMR3GetMem(pSSM, pu8Data, cbData);
+ AssertRCBreak(vrc);
+ }
+ else
+ {
+ /* No saved state data. */
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ break;
+ }
+ else
+ {
+ /* displaySSMSaveScreenshot did not write any data, if
+ * cbBlock was == 2 * sizeof (uint32_t).
+ */
+ if (cbBlock > 2 * sizeof (uint32_t))
+ {
+ vrc = SSMR3Skip(pSSM, cbBlock);
+ AssertRCBreak(vrc);
+ }
+ }
+ }
+ }
+ else
+ {
+ vrc = VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ }
+ }
+
+ ssmStream.close();
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (u32Type == 0 && cbData % 4 != 0)
+ {
+ /* Bitmap is 32bpp, so data is invalid. */
+ vrc = VERR_SSM_UNEXPECTED_DATA;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ *ppu8Data = pu8Data;
+ *pcbData = cbData;
+ *pu32Width = u32Width;
+ *pu32Height = u32Height;
+ LogFlowFunc(("cbData %d, u32Width %d, u32Height %d\n", cbData, u32Width, u32Height));
+ }
+
+ LogFlowFunc(("vrc %Rrc\n", vrc));
+ return vrc;
+}
+
+void freeSavedDisplayScreenshot(uint8_t *pu8Data)
+{
+ /** @todo not necessary when caching is implemented. */
+ RTMemFree(pu8Data);
+}
+
+int readSavedGuestScreenInfo(SsmStream &ssmStream, const Utf8Str &strStateFilePath,
+ uint32_t u32ScreenId, uint32_t *pu32OriginX, uint32_t *pu32OriginY,
+ uint32_t *pu32Width, uint32_t *pu32Height, uint16_t *pu16Flags)
+{
+ LogFlowFunc(("u32ScreenId = %d [%s]\n", u32ScreenId, strStateFilePath.c_str()));
+
+ /** @todo cache read data */
+ if (strStateFilePath.isEmpty())
+ {
+ /* No saved state data. */
+ return VERR_NOT_SUPPORTED;
+ }
+
+ PSSMHANDLE pSSM;
+ int vrc = ssmStream.open(strStateFilePath.c_str(), false /*fWrite*/, &pSSM);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t uVersion;
+ vrc = SSMR3Seek(pSSM, "DisplayData", 0 /*iInstance*/, &uVersion);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Starting from sSSMDisplayVer2 we have pu32Width and pu32Height.
+ * Starting from sSSMDisplayVer3 we have all the rest of parameters we need. */
+ if (uVersion >= sSSMDisplayVer2)
+ {
+ uint32_t cMonitors;
+ SSMR3GetU32(pSSM, &cMonitors);
+ if (u32ScreenId > cMonitors)
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ if (uVersion == sSSMDisplayVer2)
+ {
+ /* Skip all previous monitors, each 5 uint32_t, and the first 3 uint32_t entries. */
+ SSMR3Skip(pSSM, u32ScreenId * 5 * sizeof(uint32_t) + 3 * sizeof(uint32_t));
+ SSMR3GetU32(pSSM, pu32Width);
+ SSMR3GetU32(pSSM, pu32Height);
+ *pu32OriginX = 0;
+ *pu32OriginY = 0;
+ *pu16Flags = VBVA_SCREEN_F_ACTIVE;
+ }
+ else
+ {
+ /* Skip all previous monitors, each 8 uint32_t, and the first 3 uint32_t entries. */
+ SSMR3Skip(pSSM, u32ScreenId * 8 * sizeof(uint32_t) + 3 * sizeof(uint32_t));
+ SSMR3GetU32(pSSM, pu32Width);
+ SSMR3GetU32(pSSM, pu32Height);
+ SSMR3GetU32(pSSM, pu32OriginX);
+ SSMR3GetU32(pSSM, pu32OriginY);
+ uint32_t u32Flags = 0;
+ SSMR3GetU32(pSSM, &u32Flags);
+ *pu16Flags = (uint16_t)u32Flags;
+ }
+ }
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ ssmStream.close();
+ }
+
+ LogFlowFunc(("vrc %Rrc\n", vrc));
+ return vrc;
+}
+
diff --git a/src/VBox/Main/src-all/EventImpl.cpp b/src/VBox/Main/src-all/EventImpl.cpp
new file mode 100644
index 00000000..85953d30
--- /dev/null
+++ b/src/VBox/Main/src-all/EventImpl.cpp
@@ -0,0 +1,1690 @@
+/* $Id: EventImpl.cpp $ */
+/** @file
+ * VirtualBox COM Event class implementation
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @page pg_main_events Events
+ *
+ * Theory of operations.
+ *
+ * This code implements easily extensible event mechanism, letting us
+ * to make any VirtualBox object an event source (by aggregating an EventSource instance).
+ * Another entity could subscribe to the event source for events it is interested in.
+ * If an event is waitable, it's possible to wait until all listeners
+ * registered at the moment of firing event as ones interested in this
+ * event acknowledged that they finished event processing (thus allowing
+ * vetoable events).
+ *
+ * Listeners can be registered as active or passive ones, defining policy of delivery.
+ * For *active* listeners, their HandleEvent() method is invoked when event is fired by
+ * the event source (pretty much callbacks).
+ * For *passive* listeners, it's up to an event consumer to perform GetEvent() operation
+ * with given listener, and then perform desired operation with returned event, if any.
+ * For passive listeners case, listener instance serves as merely a key referring to
+ * particular event consumer, thus HandleEvent() implementation isn't that important.
+ * IEventSource's CreateListener() could be used to create such a listener.
+ * Passive mode is designed for transports not allowing callbacks, such as webservices
+ * running on top of HTTP, and for situations where consumer wants exact control on
+ * context where event handler is executed (such as GUI thread for some toolkits).
+ *
+ * Internal EventSource data structures are optimized for fast event delivery, while
+ * listener registration/unregistration operations are expected being pretty rare.
+ * Passive mode listeners keep an internal event queue for all events they receive,
+ * and all waitable events are added to the pending events map. This map keeps track
+ * of how many listeners are still not acknowledged their event, and once this counter
+ * reach zero, element is removed from pending events map, and event is marked as processed.
+ * Thus if passive listener's user forgets to call IEventSource's EventProcessed()
+ * waiters may never know that event processing finished.
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_EVENT
+#include <list>
+#include <map>
+#include <deque>
+
+#include "EventImpl.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "VBoxEvents.h"
+
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+#include <iprt/errcore.h>
+#include <iprt/semaphore.h>
+#include <iprt/time.h>
+
+#include <VBox/com/array.h>
+
+class ListenerRecord;
+
+struct VBoxEvent::Data
+{
+ Data()
+ : mType(VBoxEventType_Invalid),
+ mWaitEvent(NIL_RTSEMEVENT),
+ mWaitable(FALSE),
+ mProcessed(FALSE)
+ {}
+
+ VBoxEventType_T mType;
+ RTSEMEVENT mWaitEvent;
+ BOOL mWaitable;
+ BOOL mProcessed;
+ ComPtr<IEventSource> mSource;
+};
+
+DEFINE_EMPTY_CTOR_DTOR(VBoxEvent)
+
+HRESULT VBoxEvent::FinalConstruct()
+{
+ m = new Data;
+ return BaseFinalConstruct();
+}
+
+void VBoxEvent::FinalRelease()
+{
+ if (m)
+ {
+ uninit();
+ delete m;
+ m = NULL;
+ }
+ BaseFinalRelease();
+}
+
+HRESULT VBoxEvent::init(IEventSource *aSource, VBoxEventType_T aType, BOOL aWaitable)
+{
+ AssertReturn(aSource != NULL, E_INVALIDARG);
+
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m->mSource = aSource;
+ m->mType = aType;
+ m->mWaitable = aWaitable;
+ m->mProcessed = !aWaitable;
+
+ do
+ {
+ if (aWaitable)
+ {
+ int vrc = ::RTSemEventCreate(&m->mWaitEvent);
+
+ if (RT_FAILURE(vrc))
+ {
+ AssertFailed();
+ return setError(E_FAIL,
+ tr("Internal error (%Rrc)"), vrc);
+ }
+ }
+ } while (0);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+void VBoxEvent::uninit()
+{
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ if (!m)
+ return;
+
+ m->mProcessed = TRUE;
+ m->mType = VBoxEventType_Invalid;
+ m->mSource.setNull();
+
+ if (m->mWaitEvent != NIL_RTSEMEVENT)
+ {
+ Assert(m->mWaitable);
+ ::RTSemEventDestroy(m->mWaitEvent);
+ m->mWaitEvent = NIL_RTSEMEVENT;
+ }
+}
+
+HRESULT VBoxEvent::getType(VBoxEventType_T *aType)
+{
+ // never changes while event alive, no locking
+ *aType = m->mType;
+ return S_OK;
+}
+
+HRESULT VBoxEvent::getSource(ComPtr<IEventSource> &aSource)
+{
+ m->mSource.queryInterfaceTo(aSource.asOutParam());
+ return S_OK;
+}
+
+HRESULT VBoxEvent::getWaitable(BOOL *aWaitable)
+{
+ // never changes while event alive, no locking
+ *aWaitable = m->mWaitable;
+ return S_OK;
+}
+
+HRESULT VBoxEvent::setProcessed()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->mProcessed)
+ return S_OK;
+
+ m->mProcessed = TRUE;
+
+ // notify waiters
+ ::RTSemEventSignal(m->mWaitEvent);
+
+ return S_OK;
+}
+
+HRESULT VBoxEvent::waitProcessed(LONG aTimeout, BOOL *aResult)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->mProcessed)
+ {
+ *aResult = TRUE;
+ return S_OK;
+ }
+
+ if (aTimeout == 0)
+ {
+ *aResult = m->mProcessed;
+ return S_OK;
+ }
+
+ // must drop lock while waiting, because setProcessed() needs synchronization.
+ alock.release();
+ /** @todo maybe while loop for spurious wakeups? */
+ int vrc = ::RTSemEventWait(m->mWaitEvent, aTimeout < 0 ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)aTimeout);
+ AssertMsg(RT_SUCCESS(vrc) || vrc == VERR_TIMEOUT || vrc == VERR_INTERRUPTED,
+ ("RTSemEventWait returned %Rrc\n", vrc));
+ alock.acquire();
+
+ if (RT_SUCCESS(vrc))
+ {
+ AssertMsg(m->mProcessed,
+ ("mProcessed must be set here\n"));
+ *aResult = m->mProcessed;
+ }
+ else
+ {
+ *aResult = FALSE;
+ /*
+ * If we timed out then one or more passive listeners didn't process this event
+ * within the time limit most likely due to the listener no longer being alive (e.g.
+ * the VirtualBox GUI crashed) so we flag this to our caller so it can remove this
+ * event from the list of events the passive listener is interested in. This avoids
+ * incurring this timeout every time the event is fired.
+ */
+ if (vrc == VERR_TIMEOUT)
+ return E_ABORT;
+ }
+
+ return S_OK;
+}
+
+typedef std::list<Utf8Str> VetoList;
+typedef std::list<Utf8Str> ApprovalList;
+struct VBoxVetoEvent::Data
+{
+ Data() :
+ mVetoed(FALSE)
+ {}
+ ComObjPtr<VBoxEvent> mEvent;
+ BOOL mVetoed;
+ VetoList mVetoList;
+ ApprovalList mApprovalList;
+};
+
+HRESULT VBoxVetoEvent::FinalConstruct()
+{
+ m = new Data;
+ HRESULT hrc = m->mEvent.createObject();
+ BaseFinalConstruct();
+ return hrc;
+}
+
+void VBoxVetoEvent::FinalRelease()
+{
+ if (m)
+ {
+ uninit();
+ delete m;
+ m = NULL;
+ }
+ BaseFinalRelease();
+}
+
+DEFINE_EMPTY_CTOR_DTOR(VBoxVetoEvent)
+
+HRESULT VBoxVetoEvent::init(IEventSource *aSource, VBoxEventType_T aType)
+{
+ // all veto events are waitable
+ HRESULT hrc = m->mEvent->init(aSource, aType, TRUE);
+ if (FAILED(hrc))
+ return hrc;
+
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m->mVetoed = FALSE;
+ m->mVetoList.clear();
+ m->mApprovalList.clear();
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+void VBoxVetoEvent::uninit()
+{
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ if (!m)
+ return;
+
+ m->mVetoed = FALSE;
+ if (!m->mEvent.isNull())
+ {
+ m->mEvent->uninit();
+ m->mEvent.setNull();
+ }
+}
+
+HRESULT VBoxVetoEvent::getType(VBoxEventType_T *aType)
+{
+ return m->mEvent->COMGETTER(Type)(aType);
+}
+
+HRESULT VBoxVetoEvent::getSource(ComPtr<IEventSource> &aSource)
+{
+ return m->mEvent->COMGETTER(Source)(aSource.asOutParam());
+}
+
+HRESULT VBoxVetoEvent::getWaitable(BOOL *aWaitable)
+{
+ return m->mEvent->COMGETTER(Waitable)(aWaitable);
+}
+
+HRESULT VBoxVetoEvent::setProcessed()
+{
+ return m->mEvent->SetProcessed();
+}
+
+HRESULT VBoxVetoEvent::waitProcessed(LONG aTimeout, BOOL *aResult)
+{
+ return m->mEvent->WaitProcessed(aTimeout, aResult);
+}
+
+HRESULT VBoxVetoEvent::addVeto(const com::Utf8Str &aReason)
+{
+ // AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aReason.length())
+ m->mVetoList.push_back(aReason);
+
+ m->mVetoed = TRUE;
+
+ return S_OK;
+}
+
+HRESULT VBoxVetoEvent::isVetoed(BOOL *aResult)
+{
+ // AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aResult = m->mVetoed;
+
+ return S_OK;
+}
+
+HRESULT VBoxVetoEvent::getVetos(std::vector<com::Utf8Str> &aResult)
+{
+ // AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aResult.resize(m->mVetoList.size());
+ size_t i = 0;
+ for (VetoList::const_iterator it = m->mVetoList.begin(); it != m->mVetoList.end(); ++it, ++i)
+ aResult[i] = (*it);
+
+ return S_OK;
+
+}
+
+HRESULT VBoxVetoEvent::addApproval(const com::Utf8Str &aReason)
+{
+ // AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->mApprovalList.push_back(aReason);
+ return S_OK;
+}
+
+HRESULT VBoxVetoEvent::isApproved(BOOL *aResult)
+{
+ // AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aResult = !m->mApprovalList.empty();
+ return S_OK;
+}
+
+HRESULT VBoxVetoEvent::getApprovals(std::vector<com::Utf8Str> &aResult)
+{
+ // AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aResult.resize(m->mApprovalList.size());
+ size_t i = 0;
+ for (ApprovalList::const_iterator it = m->mApprovalList.begin(); it != m->mApprovalList.end(); ++it, ++i)
+ aResult[i] = (*it);
+ return S_OK;
+}
+
+static const int FirstEvent = (int)VBoxEventType_LastWildcard + 1;
+static const int LastEvent = (int)VBoxEventType_End;
+static const int NumEvents = LastEvent - FirstEvent;
+
+/**
+ * Class replacing std::list and able to provide required stability
+ * during iteration. It's acheived by delaying structural modifications
+ * to the list till the moment particular element is no longer used by
+ * current iterators.
+ */
+class EventMapRecord
+{
+public:
+ /**
+ * We have to be double linked, as structural modifications in list are delayed
+ * till element removed, so we have to know our previous one to update its next
+ */
+ EventMapRecord *mNext;
+ bool mAlive;
+private:
+ EventMapRecord *mPrev;
+ ListenerRecord *mRef; /* must be weak reference */
+ int32_t mRefCnt;
+
+public:
+ EventMapRecord(ListenerRecord *aRef) :
+ mNext(0), mAlive(true), mPrev(0), mRef(aRef), mRefCnt(1)
+ {}
+
+ EventMapRecord(EventMapRecord &aOther)
+ {
+ mNext = aOther.mNext;
+ mPrev = aOther.mPrev;
+ mRef = aOther.mRef;
+ mRefCnt = aOther.mRefCnt;
+ mAlive = aOther.mAlive;
+ }
+
+ ~EventMapRecord()
+ {
+ if (mNext)
+ mNext->mPrev = mPrev;
+ if (mPrev)
+ mPrev->mNext = mNext;
+ }
+
+ void addRef()
+ {
+ ASMAtomicIncS32(&mRefCnt);
+ }
+
+ void release()
+ {
+ if (ASMAtomicDecS32(&mRefCnt) <= 0)
+ delete this;
+ }
+
+ // Called when an element is no longer needed
+ void kill()
+ {
+ mAlive = false;
+ release();
+ }
+
+ ListenerRecord *ref()
+ {
+ return mAlive ? mRef : 0;
+ }
+
+ friend class EventMapList;
+};
+
+
+class EventMapList
+{
+ EventMapRecord *mHead;
+ uint32_t mSize;
+public:
+ EventMapList()
+ :
+ mHead(0),
+ mSize(0)
+ {}
+ ~EventMapList()
+ {
+ EventMapRecord *pCur = mHead;
+ while (pCur)
+ {
+ EventMapRecord *pNext = pCur->mNext;
+ pCur->release();
+ pCur = pNext;
+ }
+ }
+
+ /*
+ * Elements have to be added to the front of the list, to make sure
+ * that iterators doesn't see newly added listeners, and iteration
+ * will always complete.
+ */
+ void add(ListenerRecord *aRec)
+ {
+ EventMapRecord *pNew = new EventMapRecord(aRec);
+ pNew->mNext = mHead;
+ if (mHead)
+ mHead->mPrev = pNew;
+ mHead = pNew;
+ mSize++;
+ }
+
+ /*
+ * Mark element as removed, actual removal could be delayed until
+ * all consumers release it too. This helps to keep list stable
+ * enough for iterators to allow long and probably intrusive callbacks.
+ */
+ void remove(ListenerRecord *aRec)
+ {
+ EventMapRecord *pCur = mHead;
+ while (pCur)
+ {
+ EventMapRecord *aNext = pCur->mNext;
+ if (pCur->ref() == aRec)
+ {
+ if (pCur == mHead)
+ mHead = aNext;
+ pCur->kill();
+ mSize--;
+ // break?
+ }
+ pCur = aNext;
+ }
+ }
+
+ uint32_t size() const
+ {
+ return mSize;
+ }
+
+ struct iterator
+ {
+ EventMapRecord *mCur;
+
+ iterator() :
+ mCur(0)
+ {}
+
+ explicit
+ iterator(EventMapRecord *aCur) :
+ mCur(aCur)
+ {
+ // Prevent element removal, till we're at it
+ if (mCur)
+ mCur->addRef();
+ }
+
+ ~iterator()
+ {
+ if (mCur)
+ mCur->release();
+ }
+
+ ListenerRecord *
+ operator*() const
+ {
+ return mCur->ref();
+ }
+
+ EventMapList::iterator &
+ operator++()
+ {
+ EventMapRecord *pPrev = mCur;
+ do {
+ mCur = mCur->mNext;
+ } while (mCur && !mCur->mAlive);
+
+ // now we can safely release previous element
+ pPrev->release();
+
+ // And grab the new current
+ if (mCur)
+ mCur->addRef();
+
+ return *this;
+ }
+
+ bool
+ operator==(const EventMapList::iterator &aOther) const
+ {
+ return mCur == aOther.mCur;
+ }
+
+ bool
+ operator!=(const EventMapList::iterator &aOther) const
+ {
+ return mCur != aOther.mCur;
+ }
+ };
+
+ iterator begin()
+ {
+ return iterator(mHead);
+ }
+
+ iterator end()
+ {
+ return iterator(0);
+ }
+};
+
+typedef EventMapList EventMap[NumEvents];
+typedef std::map<IEvent *, int32_t> PendingEventsMap;
+typedef std::deque<ComPtr<IEvent> > PassiveQueue;
+
+class ListenerRecord
+{
+private:
+ ComPtr<IEventListener> mListener;
+ BOOL const mActive;
+ EventSource *mOwner;
+
+ RTSEMEVENT mQEvent;
+ int32_t volatile mQEventBusyCnt;
+ RTCRITSECT mcsQLock;
+ PassiveQueue mQueue;
+ int32_t volatile mRefCnt;
+ uint64_t mLastRead;
+
+public:
+ ListenerRecord(IEventListener *aListener,
+ com::SafeArray<VBoxEventType_T> &aInterested,
+ BOOL aActive,
+ EventSource *aOwner);
+ ~ListenerRecord();
+
+ HRESULT process(IEvent *aEvent, BOOL aWaitable, PendingEventsMap::iterator &pit, AutoLockBase &alock);
+ HRESULT enqueue(IEvent *aEvent);
+ HRESULT dequeue(IEvent **aEvent, LONG aTimeout, AutoLockBase &aAlock);
+ HRESULT eventProcessed(IEvent *aEvent, PendingEventsMap::iterator &pit);
+ void shutdown();
+
+ void addRef()
+ {
+ ASMAtomicIncS32(&mRefCnt);
+ }
+
+ void release()
+ {
+ if (ASMAtomicDecS32(&mRefCnt) <= 0)
+ delete this;
+ }
+
+ BOOL isActive()
+ {
+ return mActive;
+ }
+
+ friend class EventSource;
+};
+
+/* Handy class with semantics close to ComPtr, but for list records */
+template<typename Held>
+class RecordHolder
+{
+public:
+ RecordHolder(Held *lr) :
+ held(lr)
+ {
+ addref();
+ }
+ RecordHolder(const RecordHolder &that) :
+ held(that.held)
+ {
+ addref();
+ }
+ RecordHolder()
+ :
+ held(0)
+ {
+ }
+ ~RecordHolder()
+ {
+ release();
+ }
+
+ Held *obj()
+ {
+ return held;
+ }
+
+ RecordHolder &operator=(const RecordHolder &that)
+ {
+ safe_assign(that.held);
+ return *this;
+ }
+private:
+ Held *held;
+
+ void addref()
+ {
+ if (held)
+ held->addRef();
+ }
+ void release()
+ {
+ if (held)
+ held->release();
+ }
+ void safe_assign(Held *that_p)
+ {
+ if (that_p)
+ that_p->addRef();
+ release();
+ held = that_p;
+ }
+};
+
+typedef std::map<IEventListener *, RecordHolder<ListenerRecord> > Listeners;
+
+struct EventSource::Data
+{
+ Data() : fShutdown(false)
+ {}
+
+ Listeners mListeners;
+ EventMap mEvMap;
+ PendingEventsMap mPendingMap;
+ bool fShutdown;
+};
+
+/**
+ * This function defines what wildcard expands to.
+ */
+static BOOL implies(VBoxEventType_T who, VBoxEventType_T what)
+{
+ switch (who)
+ {
+ case VBoxEventType_Any:
+ return TRUE;
+ case VBoxEventType_Vetoable:
+ return (what == VBoxEventType_OnExtraDataCanChange)
+ || (what == VBoxEventType_OnCanShowWindow);
+ case VBoxEventType_MachineEvent:
+ return (what == VBoxEventType_OnMachineStateChanged)
+ || (what == VBoxEventType_OnMachineDataChanged)
+ || (what == VBoxEventType_OnMachineRegistered)
+ || (what == VBoxEventType_OnSessionStateChanged)
+ || (what == VBoxEventType_OnGuestPropertyChanged);
+ case VBoxEventType_SnapshotEvent:
+ return (what == VBoxEventType_OnSnapshotTaken)
+ || (what == VBoxEventType_OnSnapshotDeleted)
+ || (what == VBoxEventType_OnSnapshotChanged) ;
+ case VBoxEventType_InputEvent:
+ return (what == VBoxEventType_OnKeyboardLedsChanged)
+ || (what == VBoxEventType_OnMousePointerShapeChanged)
+ || (what == VBoxEventType_OnMouseCapabilityChanged);
+ case VBoxEventType_Invalid:
+ return FALSE;
+ default:
+ break;
+ }
+
+ return who == what;
+}
+
+ListenerRecord::ListenerRecord(IEventListener *aListener,
+ com::SafeArray<VBoxEventType_T> &aInterested,
+ BOOL aActive,
+ EventSource *aOwner) :
+ mListener(aListener), mActive(aActive), mOwner(aOwner), mQEventBusyCnt(0), mRefCnt(0)
+{
+ EventMap *aEvMap = &aOwner->m->mEvMap;
+
+ for (size_t i = 0; i < aInterested.size(); ++i)
+ {
+ VBoxEventType_T interested = aInterested[i];
+ for (int j = FirstEvent; j < LastEvent; j++)
+ {
+ VBoxEventType_T candidate = (VBoxEventType_T)j;
+ if (implies(interested, candidate))
+ {
+ (*aEvMap)[j - FirstEvent].add(this);
+ }
+ }
+ }
+
+ if (!mActive)
+ {
+ ::RTCritSectInit(&mcsQLock);
+ ::RTSemEventCreate(&mQEvent);
+ mLastRead = RTTimeMilliTS();
+ }
+ else
+ {
+ mQEvent = NIL_RTSEMEVENT;
+ RT_ZERO(mcsQLock);
+ mLastRead = 0;
+ }
+}
+
+ListenerRecord::~ListenerRecord()
+{
+ /* Remove references to us from the event map */
+ EventMap *aEvMap = &mOwner->m->mEvMap;
+ for (int j = FirstEvent; j < LastEvent; j++)
+ {
+ (*aEvMap)[j - FirstEvent].remove(this);
+ }
+
+ if (!mActive)
+ {
+ // at this moment nobody could add elements to our queue, so we can safely
+ // clean it up, otherwise there will be pending events map elements
+ PendingEventsMap *aPem = &mOwner->m->mPendingMap;
+ while (true)
+ {
+ ComPtr<IEvent> aEvent;
+
+ if (mQueue.empty())
+ break;
+
+ mQueue.front().queryInterfaceTo(aEvent.asOutParam());
+ mQueue.pop_front();
+
+ BOOL fWaitable = FALSE;
+ aEvent->COMGETTER(Waitable)(&fWaitable);
+ if (fWaitable)
+ {
+ PendingEventsMap::iterator pit = aPem->find(aEvent);
+ if (pit != aPem->end())
+ eventProcessed(aEvent, pit);
+ }
+ }
+
+ ::RTCritSectDelete(&mcsQLock);
+ }
+ shutdown();
+}
+
+HRESULT ListenerRecord::process(IEvent *aEvent,
+ BOOL aWaitable,
+ PendingEventsMap::iterator &pit,
+ AutoLockBase &aAlock)
+{
+ if (mActive)
+ {
+ /*
+ * We release lock here to allow modifying ops on EventSource inside callback.
+ */
+ HRESULT hrc = S_OK;
+ if (mListener)
+ {
+ aAlock.release();
+ hrc = mListener->HandleEvent(aEvent);
+#ifdef RT_OS_WINDOWS
+ Assert(hrc != RPC_E_WRONG_THREAD);
+#endif
+ aAlock.acquire();
+ }
+ if (aWaitable)
+ eventProcessed(aEvent, pit);
+ return hrc;
+ }
+ return enqueue(aEvent);
+}
+
+
+HRESULT ListenerRecord::enqueue(IEvent *aEvent)
+{
+ AssertMsg(!mActive, ("must be passive\n"));
+
+ // put an event the queue
+ ::RTCritSectEnter(&mcsQLock);
+
+ // If there was no events reading from the listener for the long time,
+ // and events keep coming, or queue is oversized we shall unregister this listener.
+ uint64_t sinceRead = RTTimeMilliTS() - mLastRead;
+ size_t queueSize = mQueue.size();
+ if (queueSize > 1000 || (queueSize > 500 && sinceRead > 60 * 1000))
+ {
+ ::RTCritSectLeave(&mcsQLock);
+ LogRel(("Event: forcefully unregistering passive event listener %p due to excessive queue size\n", this));
+ return E_ABORT;
+ }
+
+
+ RTSEMEVENT hEvt = mQEvent;
+ if (queueSize != 0 && mQueue.back() == aEvent)
+ /* if same event is being pushed multiple times - it's reusable event and
+ we don't really need multiple instances of it in the queue */
+ hEvt = NIL_RTSEMEVENT;
+ else if (hEvt != NIL_RTSEMEVENT) /* don't bother queuing after shutdown */
+ {
+ mQueue.push_back(aEvent);
+ ASMAtomicIncS32(&mQEventBusyCnt);
+ }
+
+ ::RTCritSectLeave(&mcsQLock);
+
+ // notify waiters unless we've been shut down.
+ if (hEvt != NIL_RTSEMEVENT)
+ {
+ ::RTSemEventSignal(hEvt);
+ ASMAtomicDecS32(&mQEventBusyCnt);
+ }
+
+ return S_OK;
+}
+
+HRESULT ListenerRecord::dequeue(IEvent **aEvent,
+ LONG aTimeout,
+ AutoLockBase &aAlock)
+{
+ if (mActive)
+ return VBOX_E_INVALID_OBJECT_STATE;
+
+ // retain listener record
+ RecordHolder<ListenerRecord> holder(this);
+
+ ::RTCritSectEnter(&mcsQLock);
+
+ mLastRead = RTTimeMilliTS();
+
+ /*
+ * If waiting both desired and necessary, then try grab the event
+ * semaphore and mark it busy. If it's NIL we've been shut down already.
+ */
+ if (aTimeout != 0 && mQueue.empty())
+ {
+ RTSEMEVENT hEvt = mQEvent;
+ if (hEvt != NIL_RTSEMEVENT)
+ {
+ ASMAtomicIncS32(&mQEventBusyCnt);
+ ::RTCritSectLeave(&mcsQLock);
+
+ // release lock while waiting, listener will not go away due to above holder
+ aAlock.release();
+
+ ::RTSemEventWait(hEvt, aTimeout < 0 ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)aTimeout);
+ ASMAtomicDecS32(&mQEventBusyCnt);
+
+ // reacquire lock
+ aAlock.acquire();
+ ::RTCritSectEnter(&mcsQLock);
+ }
+ }
+
+ if (mQueue.empty())
+ *aEvent = NULL;
+ else
+ {
+ mQueue.front().queryInterfaceTo(aEvent);
+ mQueue.pop_front();
+ }
+
+ ::RTCritSectLeave(&mcsQLock);
+ return S_OK;
+}
+
+HRESULT ListenerRecord::eventProcessed(IEvent *aEvent, PendingEventsMap::iterator &pit)
+{
+ if (--pit->second == 0)
+ {
+ Assert(pit->first == aEvent);
+ aEvent->SetProcessed();
+ mOwner->m->mPendingMap.erase(pit);
+ }
+
+ return S_OK;
+}
+
+void ListenerRecord::shutdown()
+{
+ if (mQEvent != NIL_RTSEMEVENT)
+ {
+ /* Grab the event semaphore. Must do this while owning the CS or we'll
+ be racing user wanting to use the handle. */
+ ::RTCritSectEnter(&mcsQLock);
+ RTSEMEVENT hEvt = mQEvent;
+ mQEvent = NIL_RTSEMEVENT;
+ ::RTCritSectLeave(&mcsQLock);
+
+ /*
+ * Signal waiters and wait for them and any other signallers to stop using the sempahore.
+ *
+ * Note! RTSemEventDestroy does not necessarily guarantee that waiting threads are
+ * out of RTSemEventWait or even woken up when it returns. Darwin is (or was?)
+ * an example of this, the result was undesirable freezes on shutdown.
+ */
+ int32_t cBusy = ASMAtomicReadS32(&mQEventBusyCnt);
+ if (cBusy > 0)
+ {
+ Log(("Wait for %d waiters+signalers to release.\n", cBusy));
+ while (cBusy-- > 0)
+ ::RTSemEventSignal(hEvt);
+
+ for (uint32_t cLoops = 0;; cLoops++)
+ {
+ RTThreadSleep(RT_MIN(8, cLoops));
+ if (ASMAtomicReadS32(&mQEventBusyCnt) <= 0)
+ break;
+ ::RTSemEventSignal(hEvt); /* (Technically unnecessary, but just in case.) */
+ }
+ Log(("All waiters+signalers just released the lock.\n"));
+ }
+
+ ::RTSemEventDestroy(hEvt);
+ }
+}
+
+EventSource::EventSource()
+{}
+
+EventSource::~EventSource()
+{}
+
+HRESULT EventSource::FinalConstruct()
+{
+ m = new Data;
+ return BaseFinalConstruct();
+}
+
+void EventSource::FinalRelease()
+{
+ uninit();
+ delete m;
+ BaseFinalRelease();
+}
+
+HRESULT EventSource::init()
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+ return S_OK;
+}
+
+void EventSource::uninit()
+{
+ {
+ // First of all (before even thinking about entering the uninit span):
+ // make sure that all listeners are are shut down (no pending events or
+ // wait calls), because they cannot be alive without the associated
+ // event source. Otherwise API clients which use long-term (or
+ // indefinite) waits will block VBoxSVC termination (just one example)
+ // for a long time or even infinitely long.
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (!m->fShutdown)
+ {
+ m->fShutdown = true;
+ for (Listeners::iterator it = m->mListeners.begin();
+ it != m->mListeners.end();
+ ++it)
+ {
+ it->second.obj()->shutdown();
+ }
+ }
+ }
+
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m->mListeners.clear();
+ // m->mEvMap shall be cleared at this point too by destructors, assert?
+}
+
+HRESULT EventSource::registerListener(const ComPtr<IEventListener> &aListener,
+ const std::vector<VBoxEventType_T> &aInteresting,
+ BOOL aActive)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->fShutdown)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("This event source is already shut down"));
+
+ Listeners::const_iterator it = m->mListeners.find(aListener);
+ if (it != m->mListeners.end())
+ return setError(E_INVALIDARG,
+ tr("This listener already registered"));
+
+ com::SafeArray<VBoxEventType_T> interested(aInteresting);
+ RecordHolder<ListenerRecord> lrh(new ListenerRecord(aListener, interested, aActive, this));
+ m->mListeners.insert(Listeners::value_type((IEventListener *)aListener, lrh));
+
+ ::FireEventSourceChangedEvent(this, (IEventListener *)aListener, TRUE /*add*/);
+
+ return S_OK;
+}
+
+HRESULT EventSource::unregisterListener(const ComPtr<IEventListener> &aListener)
+{
+ HRESULT hrc = S_OK;;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Listeners::iterator it = m->mListeners.find(aListener);
+
+ if (it != m->mListeners.end())
+ {
+ it->second.obj()->shutdown();
+ m->mListeners.erase(it);
+ // destructor removes refs from the event map
+ ::FireEventSourceChangedEvent(this, (IEventListener *)aListener, FALSE /*add*/);
+ hrc = S_OK;
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Listener was never registered"));
+
+ return hrc;
+}
+
+HRESULT EventSource::fireEvent(const ComPtr<IEvent> &aEvent,
+ LONG aTimeout,
+ BOOL *aResult)
+{
+ /* Get event attributes before take the source lock: */
+ BOOL fWaitable = FALSE;
+ HRESULT hrc = aEvent->COMGETTER(Waitable)(&fWaitable);
+ AssertComRC(hrc);
+
+ VBoxEventType_T evType;
+ hrc = aEvent->COMGETTER(Type)(&evType);
+ AssertComRCReturn(hrc, hrc);
+
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->fShutdown)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("This event source is already shut down"));
+
+ EventMapList &listeners = m->mEvMap[(int)evType - FirstEvent];
+
+ /* Anyone interested in this event? */
+ uint32_t cListeners = listeners.size();
+ if (cListeners == 0)
+ {
+ aEvent->SetProcessed();
+ // just leave the lock and update event object state
+ }
+ else
+ {
+ PendingEventsMap::iterator pit;
+ if (fWaitable)
+ {
+ m->mPendingMap.insert(PendingEventsMap::value_type(aEvent, cListeners));
+ // we keep iterator here to allow processing active listeners without
+ // pending events lookup
+ pit = m->mPendingMap.find(aEvent);
+ }
+
+ for (EventMapList::iterator it = listeners.begin();
+ it != listeners.end();
+ ++it)
+ {
+ // keep listener record reference, in case someone will remove it while in callback
+ RecordHolder<ListenerRecord> record(*it);
+
+ /*
+ * We pass lock here to allow modifying ops on EventSource inside callback
+ * in active mode. Note that we expect list iterator stability as 'alock'
+ * could be temporary released when calling event handler.
+ */
+ HRESULT cbRc = record.obj()->process(aEvent, fWaitable, pit, alock);
+
+ /* Note that E_ABORT is used above to signal that a passive
+ * listener was unregistered due to not picking up its event.
+ * This overlaps with XPCOM specific use of E_ABORT to signal
+ * death of an active listener, but that's irrelevant here. */
+ if (FAILED_DEAD_INTERFACE(cbRc) || cbRc == E_ABORT)
+ {
+ Listeners::iterator lit = m->mListeners.find(record.obj()->mListener);
+ if (lit != m->mListeners.end())
+ {
+ lit->second.obj()->shutdown();
+ m->mListeners.erase(lit);
+ }
+ }
+ // anything else to do with cbRc?
+ }
+ }
+ }
+ /* We leave the lock here */
+
+ if (fWaitable)
+ {
+ hrc = aEvent->WaitProcessed(aTimeout, aResult);
+
+ /*
+ * If a passive listener times out without processing a vetoable event then we
+ * remove that event from the list of events this listener is interested in.
+ */
+ if (!*aResult && hrc == E_ABORT && implies(VBoxEventType_Vetoable, evType))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ EventMapList &listeners = m->mEvMap[(int)evType - FirstEvent];
+ for (EventMapList::iterator it = listeners.begin();
+ it != listeners.end();
+ ++it)
+ {
+ RecordHolder<ListenerRecord> record(*it);
+ if (record.obj()->mQueue.size() != 0 && record.obj()->mQueue.back() == aEvent)
+ m->mEvMap[(int)evType - FirstEvent].remove(record.obj());
+ }
+
+ PendingEventsMap::iterator pit = m->mPendingMap.find(aEvent);
+ if (pit != m->mPendingMap.end())
+ m->mPendingMap.erase(pit);
+
+ /*
+ * VBoxEventDesc::fire() requires TRUE to be returned so it can handle
+ * vetoable events.
+ */
+ return S_OK;
+ }
+ }
+ else
+ *aResult = TRUE;
+
+ return hrc;
+}
+
+HRESULT EventSource::getEvent(const ComPtr<IEventListener> &aListener,
+ LONG aTimeout,
+ ComPtr<IEvent> &aEvent)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->fShutdown)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("This event source is already shut down"));
+
+ Listeners::iterator it = m->mListeners.find(aListener);
+ HRESULT hrc = S_OK;
+
+ if (it != m->mListeners.end())
+ hrc = it->second.obj()->dequeue(aEvent.asOutParam(), aTimeout, alock);
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Listener was never registered"));
+
+ if (hrc == VBOX_E_INVALID_OBJECT_STATE)
+ return setError(hrc, tr("Listener must be passive"));
+
+ return hrc;
+}
+
+HRESULT EventSource::eventProcessed(const ComPtr<IEventListener> &aListener,
+ const ComPtr<IEvent> &aEvent)
+{
+ BOOL fWaitable = FALSE;
+ HRESULT hrc = aEvent->COMGETTER(Waitable)(&fWaitable);
+ AssertComRC(hrc);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->fShutdown)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("This event source is already shut down"));
+
+ Listeners::iterator it = m->mListeners.find(aListener);
+
+ if (it != m->mListeners.end())
+ {
+ ListenerRecord *aRecord = it->second.obj();
+
+ if (aRecord->isActive())
+ return setError(E_INVALIDARG,
+ tr("Only applicable to passive listeners"));
+
+ if (fWaitable)
+ {
+ PendingEventsMap::iterator pit = m->mPendingMap.find(aEvent);
+
+ if (pit == m->mPendingMap.end())
+ {
+ AssertFailed();
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Unknown event"));
+ }
+ else
+ hrc = aRecord->eventProcessed(aEvent, pit);
+ }
+ else
+ {
+ // for non-waitable events we're done
+ hrc = S_OK;
+ }
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Listener was never registered"));
+
+ return hrc;
+}
+
+/**
+ * This class serves as feasible listener implementation
+ * which could be used by clients not able to create local
+ * COM objects, but still willing to receive event
+ * notifications in passive mode, such as webservices.
+ */
+class ATL_NO_VTABLE PassiveEventListener :
+ public VirtualBoxBase,
+ VBOX_SCRIPTABLE_IMPL(IEventListener)
+{
+public:
+
+ VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(PassiveEventListener, IEventListener)
+
+ DECLARE_NOT_AGGREGATABLE(PassiveEventListener)
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ BEGIN_COM_MAP(PassiveEventListener)
+ COM_INTERFACE_ENTRY(ISupportErrorInfo)
+ COM_INTERFACE_ENTRY(IEventListener)
+ COM_INTERFACE_ENTRY2(IDispatch, IEventListener)
+ VBOX_TWEAK_INTERFACE_ENTRY(IEventListener)
+ END_COM_MAP()
+
+ PassiveEventListener()
+ {}
+ ~PassiveEventListener()
+ {}
+
+ HRESULT FinalConstruct()
+ {
+ return BaseFinalConstruct();
+ }
+ void FinalRelease()
+ {
+ BaseFinalRelease();
+ }
+
+ // IEventListener methods
+ STDMETHOD(HandleEvent)(IEvent *)
+ {
+ ComAssertMsgRet(false, (tr("HandleEvent() of wrapper shall never be called")),
+ E_FAIL);
+ }
+};
+
+/* Proxy listener class, used to aggregate multiple event sources into one */
+class ATL_NO_VTABLE ProxyEventListener :
+ public VirtualBoxBase,
+ VBOX_SCRIPTABLE_IMPL(IEventListener)
+{
+ ComPtr<IEventSource> mSource;
+public:
+
+ VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(ProxyEventListener, IEventListener)
+
+ DECLARE_NOT_AGGREGATABLE(ProxyEventListener)
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ BEGIN_COM_MAP(ProxyEventListener)
+ COM_INTERFACE_ENTRY(ISupportErrorInfo)
+ COM_INTERFACE_ENTRY(IEventListener)
+ COM_INTERFACE_ENTRY2(IDispatch, IEventListener)
+ VBOX_TWEAK_INTERFACE_ENTRY(IEventListener)
+ END_COM_MAP()
+
+ ProxyEventListener()
+ {}
+ ~ProxyEventListener()
+ {}
+
+ HRESULT FinalConstruct()
+ {
+ return BaseFinalConstruct();
+ }
+ void FinalRelease()
+ {
+ BaseFinalRelease();
+ }
+
+ HRESULT init(IEventSource *aSource)
+ {
+ mSource = aSource;
+ return S_OK;
+ }
+
+ // IEventListener methods
+ STDMETHOD(HandleEvent)(IEvent *aEvent)
+ {
+ BOOL fProcessed = FALSE;
+ if (mSource)
+ return mSource->FireEvent(aEvent, 0, &fProcessed);
+ else
+ return S_OK;
+ }
+};
+
+class ATL_NO_VTABLE EventSourceAggregator :
+ public VirtualBoxBase,
+ VBOX_SCRIPTABLE_IMPL(IEventSource)
+{
+ typedef std::list <ComPtr<IEventSource> > EventSourceList;
+ /* key is weak reference */
+ typedef std::map<IEventListener *, ComPtr<IEventListener> > ProxyListenerMap;
+
+ EventSourceList mEventSources;
+ ProxyListenerMap mListenerProxies;
+ ComObjPtr<EventSource> mSource;
+
+public:
+
+ VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(EventSourceAggregator, IEventSource)
+
+ DECLARE_NOT_AGGREGATABLE(EventSourceAggregator)
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ BEGIN_COM_MAP(EventSourceAggregator)
+ COM_INTERFACE_ENTRY(ISupportErrorInfo)
+ COM_INTERFACE_ENTRY(IEventSource)
+ COM_INTERFACE_ENTRY2(IDispatch, IEventSource)
+ VBOX_TWEAK_INTERFACE_ENTRY(IEventSource)
+ END_COM_MAP()
+
+ EventSourceAggregator()
+ {}
+ ~EventSourceAggregator()
+ {}
+
+ HRESULT FinalConstruct()
+ {
+ return BaseFinalConstruct();
+ }
+ void FinalRelease()
+ {
+ mEventSources.clear();
+ mListenerProxies.clear();
+ mSource->uninit();
+ BaseFinalRelease();
+ }
+
+ // internal public
+ HRESULT init(const std::vector<ComPtr<IEventSource> > aSourcesIn);
+
+ // IEventSource methods
+ STDMETHOD(CreateListener)(IEventListener **aListener);
+ STDMETHOD(CreateAggregator)(ComSafeArrayIn(IEventSource *, aSubordinates),
+ IEventSource **aAggregator);
+ STDMETHOD(RegisterListener)(IEventListener *aListener,
+ ComSafeArrayIn(VBoxEventType_T, aInterested),
+ BOOL aActive);
+ STDMETHOD(UnregisterListener)(IEventListener *aListener);
+ STDMETHOD(FireEvent)(IEvent *aEvent,
+ LONG aTimeout,
+ BOOL *aProcessed);
+ STDMETHOD(GetEvent)(IEventListener *aListener,
+ LONG aTimeout,
+ IEvent **aEvent);
+ STDMETHOD(EventProcessed)(IEventListener *aListener,
+ IEvent *aEvent);
+
+ protected:
+ HRESULT createProxyListener(IEventListener *aListener,
+ IEventListener **aProxy);
+ HRESULT getProxyListener(IEventListener *aListener,
+ IEventListener **aProxy);
+ HRESULT removeProxyListener(IEventListener *aListener);
+};
+
+#ifdef VBOX_WITH_XPCOM
+NS_DECL_CLASSINFO(ProxyEventListener)
+NS_IMPL_THREADSAFE_ISUPPORTS1_CI(ProxyEventListener, IEventListener)
+NS_DECL_CLASSINFO(PassiveEventListener)
+NS_IMPL_THREADSAFE_ISUPPORTS1_CI(PassiveEventListener, IEventListener)
+NS_DECL_CLASSINFO(EventSourceAggregator)
+NS_IMPL_THREADSAFE_ISUPPORTS1_CI(EventSourceAggregator, IEventSource)
+#endif
+
+
+HRESULT EventSource::createListener(ComPtr<IEventListener> &aListener)
+{
+ ComObjPtr<PassiveEventListener> listener;
+
+ HRESULT hrc = listener.createObject();
+ ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not create wrapper object (%Rhrc)"), hrc),
+ E_FAIL);
+ listener.queryInterfaceTo(aListener.asOutParam());
+ return S_OK;
+}
+
+HRESULT EventSource::createAggregator(const std::vector<ComPtr<IEventSource> > &aSubordinates,
+ ComPtr<IEventSource> &aResult)
+{
+ ComObjPtr<EventSourceAggregator> agg;
+
+ HRESULT hrc = agg.createObject();
+ ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not create aggregator (%Rhrc)"), hrc),
+ E_FAIL);
+
+ hrc = agg->init(aSubordinates);
+ if (FAILED(hrc))
+ return hrc;
+
+ agg.queryInterfaceTo(aResult.asOutParam());
+ return S_OK;
+}
+
+HRESULT EventSourceAggregator::init(const std::vector<ComPtr<IEventSource> > aSourcesIn)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT hrc = mSource.createObject();
+ ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not create source (%Rhrc)"), hrc),
+ E_FAIL);
+ hrc = mSource->init();
+ ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not init source (%Rhrc)"), hrc),
+ E_FAIL);
+
+ for (size_t i = 0; i < aSourcesIn.size(); i++)
+ {
+ if (aSourcesIn[i] != NULL)
+ mEventSources.push_back(aSourcesIn[i]);
+ }
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return hrc;
+}
+
+STDMETHODIMP EventSourceAggregator::CreateListener(IEventListener **aListener)
+{
+ return mSource->CreateListener(aListener);
+}
+
+STDMETHODIMP EventSourceAggregator::CreateAggregator(ComSafeArrayIn(IEventSource *, aSubordinates),
+ IEventSource **aResult)
+{
+ return mSource->CreateAggregator(ComSafeArrayInArg(aSubordinates), aResult);
+}
+
+STDMETHODIMP EventSourceAggregator::RegisterListener(IEventListener *aListener,
+ ComSafeArrayIn(VBoxEventType_T, aInterested),
+ BOOL aActive)
+{
+ CheckComArgNotNull(aListener);
+ CheckComArgSafeArrayNotNull(aInterested);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return autoCaller.rc();
+
+ ComPtr<IEventListener> proxy;
+ HRESULT hrc = createProxyListener(aListener, proxy.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ for (EventSourceList::const_iterator it = mEventSources.begin(); it != mEventSources.end();
+ ++it)
+ {
+ ComPtr<IEventSource> es = *it;
+ /* Register active proxy listener on real event source */
+ hrc = es->RegisterListener(proxy, ComSafeArrayInArg(aInterested), TRUE);
+ }
+ /* And add real listener on our event source */
+ hrc = mSource->RegisterListener(aListener, ComSafeArrayInArg(aInterested), aActive);
+
+ return S_OK;
+}
+
+STDMETHODIMP EventSourceAggregator::UnregisterListener(IEventListener *aListener)
+{
+ CheckComArgNotNull(aListener);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComPtr<IEventListener> proxy;
+ HRESULT hrc = getProxyListener(aListener, proxy.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ for (EventSourceList::const_iterator it = mEventSources.begin(); it != mEventSources.end();
+ ++it)
+ {
+ ComPtr<IEventSource> es = *it;
+ hrc = es->UnregisterListener(proxy);
+ }
+ hrc = mSource->UnregisterListener(aListener);
+
+ return removeProxyListener(aListener);
+
+}
+
+STDMETHODIMP EventSourceAggregator::FireEvent(IEvent *aEvent,
+ LONG aTimeout,
+ BOOL *aProcessed)
+{
+ CheckComArgNotNull(aEvent);
+ CheckComArgOutPointerValid(aProcessed);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ /* Aggregator event source shall not have direct event firing, but we may
+ wish to support aggregation chains */
+ for (EventSourceList::const_iterator it = mEventSources.begin(); it != mEventSources.end();
+ ++it)
+ {
+ ComPtr<IEventSource> es = *it;
+ HRESULT hrc = es->FireEvent(aEvent, aTimeout, aProcessed);
+ /* Current behavior is that aggregator's FireEvent() always succeeds,
+ so that multiple event sources don't affect each other. */
+ NOREF(hrc);
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP EventSourceAggregator::GetEvent(IEventListener *aListener,
+ LONG aTimeout,
+ IEvent **aEvent)
+{
+ return mSource->GetEvent(aListener, aTimeout, aEvent);
+}
+
+STDMETHODIMP EventSourceAggregator::EventProcessed(IEventListener *aListener,
+ IEvent *aEvent)
+{
+ return mSource->EventProcessed(aListener, aEvent);
+}
+
+HRESULT EventSourceAggregator::createProxyListener(IEventListener *aListener,
+ IEventListener **aProxy)
+{
+ ComObjPtr<ProxyEventListener> proxy;
+
+ HRESULT hrc = proxy.createObject();
+ ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not create proxy (%Rhrc)"), hrc),
+ E_FAIL);
+
+ hrc = proxy->init(mSource);
+ if (FAILED(hrc))
+ return hrc;
+
+ ProxyListenerMap::const_iterator it = mListenerProxies.find(aListener);
+ if (it != mListenerProxies.end())
+ return setError(E_INVALIDARG,
+ tr("This listener already registered"));
+
+ mListenerProxies.insert(ProxyListenerMap::value_type(aListener, proxy));
+
+ proxy.queryInterfaceTo(aProxy);
+ return S_OK;
+}
+
+HRESULT EventSourceAggregator::getProxyListener(IEventListener *aListener,
+ IEventListener **aProxy)
+{
+ ProxyListenerMap::const_iterator it = mListenerProxies.find(aListener);
+ if (it == mListenerProxies.end())
+ return setError(E_INVALIDARG,
+ tr("This listener never registered"));
+
+ (*it).second.queryInterfaceTo(aProxy);
+ return S_OK;
+}
+
+HRESULT EventSourceAggregator::removeProxyListener(IEventListener *aListener)
+{
+ ProxyListenerMap::iterator it = mListenerProxies.find(aListener);
+ if (it == mListenerProxies.end())
+ return setError(E_INVALIDARG,
+ tr("This listener never registered"));
+
+ mListenerProxies.erase(it);
+ return S_OK;
+}
diff --git a/src/VBox/Main/src-all/ExtPackManagerImpl.cpp b/src/VBox/Main/src-all/ExtPackManagerImpl.cpp
new file mode 100644
index 00000000..a7298213
--- /dev/null
+++ b/src/VBox/Main/src-all/ExtPackManagerImpl.cpp
@@ -0,0 +1,3839 @@
+/* $Id: ExtPackManagerImpl.cpp $ */
+/** @file
+ * VirtualBox Main - interface for Extension Packs, VBoxSVC & VBoxC.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "ExtPackManagerImpl.h"
+#include "CloudProviderManagerImpl.h"
+#include "ExtPackUtil.h"
+#include "ThreadTask.h"
+
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/ldr.h>
+#include <iprt/locale.h>
+#include <iprt/manifest.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/pipe.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+
+#include <VBox/com/array.h>
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <VBox/sup.h>
+#include <VBox/version.h>
+
+#include <algorithm>
+
+#include "AutoCaller.h"
+#include "Global.h"
+#include "ProgressImpl.h"
+#ifdef VBOX_COM_INPROC
+# include "ConsoleImpl.h"
+#else
+# include "VirtualBoxImpl.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def VBOX_EXTPACK_HELPER_NAME
+ * The name of the utility application we employ to install and uninstall the
+ * extension packs. This is a set-uid-to-root binary on unixy platforms, which
+ * is why it has to be a separate application.
+ */
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+# define VBOX_EXTPACK_HELPER_NAME "VBoxExtPackHelperApp.exe"
+#else
+# define VBOX_EXTPACK_HELPER_NAME "VBoxExtPackHelperApp"
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+struct ExtPackBaseData
+{
+public:
+ /** The extension pack descriptor (loaded from the XML, mostly). */
+ VBOXEXTPACKDESC Desc;
+ /** The file system object info of the XML file.
+ * This is for detecting changes and save time in refresh(). */
+ RTFSOBJINFO ObjInfoDesc;
+ /** Whether it's usable or not. */
+ bool fUsable;
+ /** Why it is unusable. */
+ Utf8Str strWhyUnusable;
+};
+
+#ifndef VBOX_COM_INPROC
+/**
+ * Private extension pack data.
+ */
+struct ExtPackFile::Data : public ExtPackBaseData
+{
+public:
+ /** The path to the tarball. */
+ Utf8Str strExtPackFile;
+ /** The SHA-256 hash of the file (as string). */
+ Utf8Str strDigest;
+ /** The file handle of the extension pack file. */
+ RTFILE hExtPackFile;
+ /** Our manifest for the tarball. */
+ RTMANIFEST hOurManifest;
+ /** Pointer to the extension pack manager. */
+ ComObjPtr<ExtPackManager> ptrExtPackMgr;
+ /** Pointer to the VirtualBox object so we can create a progress object. */
+ VirtualBox *pVirtualBox;
+
+ RTMEMEF_NEW_AND_DELETE_OPERATORS();
+};
+#endif
+
+/**
+ * Private extension pack data.
+ */
+struct ExtPack::Data : public ExtPackBaseData
+{
+public:
+ /** Where the extension pack is located. */
+ Utf8Str strExtPackPath;
+ /** The file system object info of the extension pack directory.
+ * This is for detecting changes and save time in refresh(). */
+ RTFSOBJINFO ObjInfoExtPack;
+ /** The full path to the main module. */
+ Utf8Str strMainModPath;
+ /** The file system object info of the main module.
+ * This is used to determin whether to bother try reload it. */
+ RTFSOBJINFO ObjInfoMainMod;
+ /** The module handle of the main extension pack module. */
+ RTLDRMOD hMainMod;
+
+ /** The helper callbacks for the extension pack. */
+ VBOXEXTPACKHLP Hlp;
+ /** Pointer back to the extension pack object (for Hlp methods). */
+ ExtPack *pThis;
+#ifndef VBOX_COM_INPROC
+ /** The extension pack main registration structure. */
+ PCVBOXEXTPACKREG pReg;
+#else
+ /** The extension pack main VM registration structure. */
+ PCVBOXEXTPACKVMREG pReg;
+#endif
+ /** The current context. */
+ VBOXEXTPACKCTX enmContext;
+ /** Set if we've made the pfnVirtualBoxReady or pfnConsoleReady call. */
+ bool fMadeReadyCall;
+#ifndef VBOX_COM_INPROC
+ /** Pointer to the VirtualBox object so we can create a progress object. */
+ VirtualBox *pVirtualBox;
+#endif
+#ifdef VBOX_WITH_MAIN_NLS
+ PTRCOMPONENT pTrComponent;
+#endif
+
+ RTMEMEF_NEW_AND_DELETE_OPERATORS();
+};
+
+/** List of extension packs. */
+typedef std::list< ComObjPtr<ExtPack> > ExtPackList;
+
+/**
+ * Private extension pack manager data.
+ */
+struct ExtPackManager::Data
+{
+ Data()
+ : cUpdate(0)
+ {}
+
+ /** The directory where the extension packs are installed. */
+ Utf8Str strBaseDir;
+ /** The directory where the certificates this installation recognizes are
+ * stored. */
+ Utf8Str strCertificatDirPath;
+ /** The list of installed extension packs. */
+ ExtPackList llInstalledExtPacks;
+#ifndef VBOX_COM_INPROC
+ /** Pointer to the VirtualBox object, our parent. */
+ VirtualBox *pVirtualBox;
+#endif
+ /** The current context. */
+ VBOXEXTPACKCTX enmContext;
+ /** Update counter for the installed extension packs, increased in every list update. */
+ uint64_t cUpdate;
+
+ RTMEMEF_NEW_AND_DELETE_OPERATORS();
+};
+
+#ifndef VBOX_COM_INPROC
+
+/**
+ * Extension pack installation job.
+ */
+class ExtPackInstallTask : public ThreadTask
+{
+public:
+ explicit ExtPackInstallTask() : ThreadTask("ExtPackInst") { }
+ ~ExtPackInstallTask() { }
+
+ DECLARE_TRANSLATE_METHODS(ExtPackInstallTask)
+
+ void handler()
+ {
+ HRESULT hrc = ptrExtPackMgr->i_doInstall(ptrExtPackFile, fReplace, &strDisplayInfo);
+ ptrProgress->i_notifyComplete(hrc);
+ }
+
+ HRESULT Init(const ComPtr<ExtPackFile> &a_strExtPackFile, bool a_fReplace,
+ const Utf8Str &strDispInfo, const ComPtr<ExtPackManager> &a_ptrExtPackMgr)
+ {
+ ptrExtPackFile = a_strExtPackFile;
+ fReplace = a_fReplace;
+ strDisplayInfo = strDispInfo;
+ ptrExtPackMgr = a_ptrExtPackMgr;
+
+ HRESULT hrc = ptrProgress.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ Bstr bstrDescription(tr("Installing extension pack"));
+ hrc = ptrProgress->init(ptrExtPackFile->m->pVirtualBox,
+ static_cast<IExtPackFile *>(ptrExtPackFile),
+ bstrDescription.raw(),
+ FALSE /*aCancelable*/);
+ }
+
+ return hrc;
+ }
+
+ /** Smart pointer to the progress object for this job. */
+ ComObjPtr<Progress> ptrProgress;
+private:
+ /** Smart pointer to the extension pack file. */
+ ComPtr<ExtPackFile> ptrExtPackFile;
+ /** The replace argument. */
+ bool fReplace;
+ /** The display info argument. */
+ Utf8Str strDisplayInfo;
+ /** Smart pointer to the extension manager. */
+ ComPtr<ExtPackManager> ptrExtPackMgr;
+};
+
+/**
+ * Extension pack uninstallation job.
+ */
+class ExtPackUninstallTask : public ThreadTask
+{
+public:
+ explicit ExtPackUninstallTask() : ThreadTask("ExtPackUninst") { }
+ ~ExtPackUninstallTask() { }
+ DECLARE_TRANSLATE_METHODS(ExtPackUninstallTask)
+
+ void handler()
+ {
+ HRESULT hrc = ptrExtPackMgr->i_doUninstall(&strName, fForcedRemoval, &strDisplayInfo);
+ ptrProgress->i_notifyComplete(hrc);
+ }
+
+ HRESULT Init(const ComPtr<ExtPackManager> &a_ptrExtPackMgr, const Utf8Str &a_strName,
+ bool a_fForcedRemoval, const Utf8Str &a_strDisplayInfo)
+ {
+ ptrExtPackMgr = a_ptrExtPackMgr;
+ strName = a_strName;
+ fForcedRemoval = a_fForcedRemoval;
+ strDisplayInfo = a_strDisplayInfo;
+
+ HRESULT hrc = ptrProgress.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ Bstr bstrDescription(tr("Uninstalling extension pack"));
+ hrc = ptrProgress->init(ptrExtPackMgr->m->pVirtualBox,
+ static_cast<IExtPackManager *>(ptrExtPackMgr),
+ bstrDescription.raw(),
+ FALSE /*aCancelable*/);
+ }
+
+ return hrc;
+ }
+
+ /** Smart pointer to the progress object for this job. */
+ ComObjPtr<Progress> ptrProgress;
+private:
+ /** Smart pointer to the extension manager. */
+ ComPtr<ExtPackManager> ptrExtPackMgr;
+ /** The name of the extension pack. */
+ Utf8Str strName;
+ /** The replace argument. */
+ bool fForcedRemoval;
+ /** The display info argument. */
+ Utf8Str strDisplayInfo;
+};
+
+DEFINE_EMPTY_CTOR_DTOR(ExtPackFile)
+
+/**
+ * Called by ComObjPtr::createObject when creating the object.
+ *
+ * Just initialize the basic object state, do the rest in initWithDir().
+ *
+ * @returns S_OK.
+ */
+HRESULT ExtPackFile::FinalConstruct()
+{
+ m = NULL;
+ return BaseFinalConstruct();
+}
+
+/**
+ * Initializes the extension pack by reading its file.
+ *
+ * @returns COM status code.
+ * @param a_pszFile The path to the extension pack file.
+ * @param a_pszDigest The SHA-256 digest of the file. Or an empty string.
+ * @param a_pExtPackMgr Pointer to the extension pack manager.
+ * @param a_pVirtualBox Pointer to the VirtualBox object.
+ */
+HRESULT ExtPackFile::initWithFile(const char *a_pszFile, const char *a_pszDigest, ExtPackManager *a_pExtPackMgr,
+ VirtualBox *a_pVirtualBox)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /*
+ * Allocate + initialize our private data.
+ */
+ m = new ExtPackFile::Data;
+ VBoxExtPackInitDesc(&m->Desc);
+ RT_ZERO(m->ObjInfoDesc);
+ m->fUsable = false;
+ m->strWhyUnusable = tr("ExtPack::init failed");
+ m->strExtPackFile = a_pszFile;
+ m->strDigest = a_pszDigest;
+ m->hExtPackFile = NIL_RTFILE;
+ m->hOurManifest = NIL_RTMANIFEST;
+ m->ptrExtPackMgr = a_pExtPackMgr;
+ m->pVirtualBox = a_pVirtualBox;
+
+ RTCString *pstrTarName = VBoxExtPackExtractNameFromTarballPath(a_pszFile);
+ if (pstrTarName)
+ {
+ m->Desc.strName = *pstrTarName;
+ delete pstrTarName;
+ pstrTarName = NULL;
+ }
+
+ autoInitSpan.setSucceeded();
+
+ /*
+ * Try open the extension pack and check that it is a regular file.
+ */
+ int vrc = RTFileOpen(&m->hExtPackFile, a_pszFile,
+ RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
+ return initFailed(tr("'%s' file not found"), a_pszFile);
+ return initFailed(tr("RTFileOpen('%s',,) failed with %Rrc"), a_pszFile, vrc);
+ }
+
+ RTFSOBJINFO ObjInfo;
+ vrc = RTFileQueryInfo(m->hExtPackFile, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(vrc))
+ return initFailed(tr("RTFileQueryInfo failed with %Rrc on '%s'"), vrc, a_pszFile);
+ if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ return initFailed(tr("Not a regular file: %s"), a_pszFile);
+
+ /*
+ * Validate the tarball and extract the XML file.
+ */
+ char szError[8192];
+ RTVFSFILE hXmlFile;
+ vrc = VBoxExtPackValidateTarball(m->hExtPackFile, NULL /*pszExtPackName*/, a_pszFile, a_pszDigest,
+ szError, sizeof(szError), &m->hOurManifest, &hXmlFile, &m->strDigest);
+ if (RT_FAILURE(vrc))
+ return initFailed("%s", szError);
+
+ /*
+ * Parse the XML.
+ */
+ RTCString strSavedName(m->Desc.strName);
+ RTCString *pStrLoadErr = VBoxExtPackLoadDescFromVfsFile(hXmlFile, &m->Desc, &m->ObjInfoDesc);
+ RTVfsFileRelease(hXmlFile);
+ if (pStrLoadErr != NULL)
+ {
+ m->strWhyUnusable.printf(tr("Failed to the xml file: %s"), pStrLoadErr->c_str());
+ m->Desc.strName = strSavedName;
+ delete pStrLoadErr;
+ return S_OK;
+ }
+
+ /*
+ * Match the tarball name with the name from the XML.
+ */
+ /** @todo drop this restriction after the old install interface is
+ * dropped. */
+ if (!strSavedName.equalsIgnoreCase(m->Desc.strName))
+ return initFailed(tr("Extension pack name mismatch between the downloaded file and the XML inside it (xml='%s' file='%s')"),
+ m->Desc.strName.c_str(), strSavedName.c_str());
+
+
+ m->fUsable = true;
+ m->strWhyUnusable.setNull();
+ return S_OK;
+}
+
+/**
+ * Protected helper that formats the strWhyUnusable value.
+ *
+ * @returns S_OK
+ * @param a_pszWhyFmt Why it failed, format string.
+ * @param ... The format arguments.
+ */
+HRESULT ExtPackFile::initFailed(const char *a_pszWhyFmt, ...)
+{
+ va_list va;
+ va_start(va, a_pszWhyFmt);
+ m->strWhyUnusable.printfV(a_pszWhyFmt, va);
+ va_end(va);
+ return S_OK;
+}
+
+/**
+ * COM cruft.
+ */
+void ExtPackFile::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+/**
+ * Do the actual cleanup.
+ */
+void ExtPackFile::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (!autoUninitSpan.uninitDone() && m != NULL)
+ {
+ VBoxExtPackFreeDesc(&m->Desc);
+ RTFileClose(m->hExtPackFile);
+ m->hExtPackFile = NIL_RTFILE;
+ RTManifestRelease(m->hOurManifest);
+ m->hOurManifest = NIL_RTMANIFEST;
+
+ delete m;
+ m = NULL;
+ }
+}
+
+HRESULT ExtPackFile::getName(com::Utf8Str &aName)
+{
+ aName = m->Desc.strName;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getDescription(com::Utf8Str &aDescription)
+{
+ aDescription = m->Desc.strDescription;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getVersion(com::Utf8Str &aVersion)
+{
+ aVersion = m->Desc.strVersion;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getEdition(com::Utf8Str &aEdition)
+{
+ aEdition = m->Desc.strEdition;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getRevision(ULONG *aRevision)
+{
+ *aRevision = m->Desc.uRevision;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getVRDEModule(com::Utf8Str &aVRDEModule)
+{
+ aVRDEModule = m->Desc.strVrdeModule;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getCryptoModule(com::Utf8Str &aCryptoModule)
+{
+ aCryptoModule = m->Desc.strCryptoModule;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getPlugIns(std::vector<ComPtr<IExtPackPlugIn> > &aPlugIns)
+{
+ /** @todo implement plug-ins. */
+#ifdef VBOX_WITH_XPCOM
+ NOREF(aPlugIns);
+#endif
+ NOREF(aPlugIns);
+ ReturnComNotImplemented();
+}
+
+HRESULT ExtPackFile::getUsable(BOOL *aUsable)
+{
+ *aUsable = m->fUsable;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getWhyUnusable(com::Utf8Str &aWhyUnusable)
+{
+ aWhyUnusable = m->strWhyUnusable;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getShowLicense(BOOL *aShowLicense)
+{
+ *aShowLicense = m->Desc.fShowLicense;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getLicense(com::Utf8Str &aLicense)
+{
+ Utf8Str strHtml("html");
+ Utf8Str str("");
+ return queryLicense(str, str, strHtml, aLicense);
+}
+
+/* Same as ExtPack::QueryLicense, should really explore the subject of base classes here... */
+HRESULT ExtPackFile::queryLicense(const com::Utf8Str &aPreferredLocale, const com::Utf8Str &aPreferredLanguage,
+ const com::Utf8Str &aFormat, com::Utf8Str &aLicenseText)
+{
+ HRESULT hrc = S_OK;
+
+ /*
+ * Validate input.
+ */
+
+ if (aPreferredLocale.length() != 2 && aPreferredLocale.length() != 0)
+ return setError(E_FAIL, tr("The preferred locale is a two character string or empty."));
+
+ if (aPreferredLanguage.length() != 2 && aPreferredLanguage.length() != 0)
+ return setError(E_FAIL, tr("The preferred language is a two character string or empty."));
+
+ if ( !aFormat.equals("html")
+ && !aFormat.equals("rtf")
+ && !aFormat.equals("txt"))
+ return setError(E_FAIL, tr("The license format can only have the values 'html', 'rtf' and 'txt'."));
+
+ /*
+ * Combine the options to form a file name before locking down anything.
+ */
+ char szName[sizeof(VBOX_EXTPACK_LICENSE_NAME_PREFIX "-de_DE.html") + 2];
+ if (aPreferredLocale.isNotEmpty() && aPreferredLanguage.isNotEmpty())
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-%s_%s.%s",
+ aPreferredLocale.c_str(), aPreferredLanguage.c_str(), aFormat.c_str());
+ else if (aPreferredLocale.isNotEmpty())
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-%s.%s",
+ aPreferredLocale.c_str(), aFormat.c_str());
+ else if (aPreferredLanguage.isNotEmpty())
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-_%s.%s",
+ aPreferredLocale.c_str(), aFormat.c_str());
+ else
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX ".%s",
+ aFormat.c_str());
+ /*
+ * Lock the extension pack. We need a write lock here as there must not be
+ * concurrent accesses to the tar file handle.
+ */
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Do not permit this query on a pack that isn't considered usable (could
+ * be marked so because of bad license files).
+ */
+ if (!m->fUsable)
+ hrc = setError(E_FAIL, "%s", m->strWhyUnusable.c_str());
+ else
+ {
+ /*
+ * Look it up in the manifest before scanning the tarball for it
+ */
+ if (RTManifestEntryExists(m->hOurManifest, szName))
+ {
+ RTVFSFSSTREAM hTarFss;
+ char szError[8192];
+ int vrc = VBoxExtPackOpenTarFss(m->hExtPackFile, szError, sizeof(szError), &hTarFss, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ for (;;)
+ {
+ /* Get the first/next. */
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ RTVFSOBJTYPE enmType;
+ vrc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc != VERR_EOF)
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("RTVfsFsStrmNext failed: %Rrc"), vrc);
+ else
+ hrc = setErrorBoth(E_UNEXPECTED, vrc, tr("'%s' was found in the manifest but not in the tarball"), szName);
+ break;
+ }
+
+ /* Is this it? */
+ const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
+ if ( !strcmp(pszAdjName, szName)
+ && ( enmType == RTVFSOBJTYPE_IO_STREAM
+ || enmType == RTVFSOBJTYPE_FILE))
+ {
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+
+ /* Load the file into memory. */
+ RTFSOBJINFO ObjInfo;
+ vrc = RTVfsIoStrmQueryInfo(hVfsIos, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ size_t cbFile = (size_t)ObjInfo.cbObject;
+ void *pvFile = RTMemAllocZ(cbFile + 1);
+ if (pvFile)
+ {
+ vrc = RTVfsIoStrmRead(hVfsIos, pvFile, cbFile, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ /* try translate it into a string we can return. */
+ Bstr bstrLicense((const char *)pvFile, cbFile);
+ if (bstrLicense.isNotEmpty())
+ {
+ aLicenseText = Utf8Str(bstrLicense);
+ hrc = S_OK;
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("The license file '%s' is empty or contains invalid UTF-8 encoding"),
+ szName);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to read '%s': %Rrc"), szName, vrc);
+ RTMemFree(pvFile);
+ }
+ else
+ hrc = setError(E_OUTOFMEMORY, tr("Failed to allocate %zu bytes for '%s'", "", cbFile),
+ cbFile, szName);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("RTVfsIoStrmQueryInfo on '%s': %Rrc"), szName, vrc);
+ RTVfsIoStrmRelease(hVfsIos);
+ break;
+ }
+
+ /* Release current. */
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ }
+ RTVfsFsStrmRelease(hTarFss);
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, "%s", szError);
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("The license file '%s' was not found in '%s'"),
+ szName, m->strExtPackFile.c_str());
+ }
+ return hrc;
+}
+
+HRESULT ExtPackFile::getFilePath(com::Utf8Str &aFilePath)
+{
+
+ aFilePath = m->strExtPackFile;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::install(BOOL aReplace, const com::Utf8Str &aDisplayInfo, ComPtr<IProgress> &aProgress)
+{
+ HRESULT hrc;
+ if (m->fUsable)
+ {
+ ExtPackInstallTask *pTask = NULL;
+ try
+ {
+ pTask = new ExtPackInstallTask();
+ hrc = pTask->Init(this, aReplace != FALSE, aDisplayInfo, m->ptrExtPackMgr);
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<Progress> ptrProgress = pTask->ptrProgress;
+ hrc = pTask->createThreadWithType(RTTHREADTYPE_DEFAULT);
+ pTask = NULL; /* The _completely_ _undocumented_ createThread method always consumes pTask. */
+ if (SUCCEEDED(hrc))
+ hrc = ptrProgress.queryInterfaceTo(aProgress.asOutParam());
+ else
+ hrc = setError(VBOX_E_IPRT_ERROR,
+ tr("Starting thread for an extension pack installation failed with %Rrc"), hrc);
+ }
+ else
+ hrc = setError(VBOX_E_IPRT_ERROR,
+ tr("Looks like creating a progress object for ExtraPackInstallTask object failed"));
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ catch (HRESULT hrcXcpt)
+ {
+ LogFlowThisFunc(("Exception was caught in the function ExtPackFile::install() \n"));
+ hrc = hrcXcpt;
+ }
+ if (pTask)
+ delete pTask;
+ }
+ else
+ hrc = setError(E_FAIL, "%s", m->strWhyUnusable.c_str());
+ return hrc;
+}
+
+#endif /* !VBOX_COM_INPROC */
+
+
+
+
+DEFINE_EMPTY_CTOR_DTOR(ExtPack)
+
+/**
+ * Called by ComObjPtr::createObject when creating the object.
+ *
+ * Just initialize the basic object state, do the rest in initWithDir().
+ *
+ * @returns S_OK.
+ */
+HRESULT ExtPack::FinalConstruct()
+{
+ m = NULL;
+ return BaseFinalConstruct();
+}
+
+/**
+ * Initializes the extension pack by reading its file.
+ *
+ * @returns COM status code.
+ * @param a_pVirtualBox The VirtualBox object.
+ * @param a_enmContext The context we're in.
+ * @param a_pszName The name of the extension pack. This is also the
+ * name of the subdirector under @a a_pszParentDir
+ * where the extension pack is installed.
+ * @param a_pszDir The extension pack directory name.
+ */
+HRESULT ExtPack::initWithDir(VirtualBox *a_pVirtualBox, VBOXEXTPACKCTX a_enmContext, const char *a_pszName, const char *a_pszDir)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ static const VBOXEXTPACKHLP s_HlpTmpl =
+ {
+ /* u32Version = */ VBOXEXTPACKHLP_VERSION,
+ /* uVBoxFullVersion = */ VBOX_FULL_VERSION,
+ /* uVBoxVersionRevision = */ 0,
+ /* u32Padding = */ 0,
+ /* pszVBoxVersion = */ "",
+ /* pfnFindModule = */ ExtPack::i_hlpFindModule,
+ /* pfnGetFilePath = */ ExtPack::i_hlpGetFilePath,
+ /* pfnGetContext = */ ExtPack::i_hlpGetContext,
+ /* pfnLoadHGCMService = */ ExtPack::i_hlpLoadHGCMService,
+ /* pfnLoadVDPlugin = */ ExtPack::i_hlpLoadVDPlugin,
+ /* pfnUnloadVDPlugin = */ ExtPack::i_hlpUnloadVDPlugin,
+ /* pfnCreateProgress = */ ExtPack::i_hlpCreateProgress,
+ /* pfnGetCanceledProgress = */ ExtPack::i_hlpGetCanceledProgress,
+ /* pfnUpdateProgress = */ ExtPack::i_hlpUpdateProgress,
+ /* pfnNextOperationProgress = */ ExtPack::i_hlpNextOperationProgress,
+ /* pfnWaitOtherProgress = */ ExtPack::i_hlpWaitOtherProgress,
+ /* pfnCompleteProgress = */ ExtPack::i_hlpCompleteProgress,
+ /* pfnCreateEvent = */ ExtPack::i_hlpCreateEvent,
+ /* pfnCreateVetoEvent = */ ExtPack::i_hlpCreateVetoEvent,
+ /* pfnTranslate = */ ExtPack::i_hlpTranslate,
+ /* pfnReserved1 = */ ExtPack::i_hlpReservedN,
+ /* pfnReserved2 = */ ExtPack::i_hlpReservedN,
+ /* pfnReserved3 = */ ExtPack::i_hlpReservedN,
+ /* pfnReserved4 = */ ExtPack::i_hlpReservedN,
+ /* pfnReserved5 = */ ExtPack::i_hlpReservedN,
+ /* pfnReserved6 = */ ExtPack::i_hlpReservedN,
+ /* uReserved7 = */ 0,
+ /* u32EndMarker = */ VBOXEXTPACKHLP_VERSION
+ };
+
+ /*
+ * Allocate + initialize our private data.
+ */
+ m = new Data;
+ VBoxExtPackInitDesc(&m->Desc);
+ m->Desc.strName = a_pszName;
+ RT_ZERO(m->ObjInfoDesc);
+ m->fUsable = false;
+ m->strWhyUnusable = tr("ExtPack::init failed");
+ m->strExtPackPath = a_pszDir;
+ RT_ZERO(m->ObjInfoExtPack);
+ m->strMainModPath.setNull();
+ RT_ZERO(m->ObjInfoMainMod);
+ m->hMainMod = NIL_RTLDRMOD;
+ m->Hlp = s_HlpTmpl;
+ m->Hlp.pszVBoxVersion = RTBldCfgVersion();
+ m->Hlp.uVBoxInternalRevision = RTBldCfgRevision();
+ m->pThis = this;
+ m->pReg = NULL;
+ m->enmContext = a_enmContext;
+ m->fMadeReadyCall = false;
+#ifndef VBOX_COM_INPROC
+ m->pVirtualBox = a_pVirtualBox;
+#else
+ RT_NOREF(a_pVirtualBox);
+#endif
+#ifdef VBOX_WITH_MAIN_NLS
+ m->pTrComponent = NULL;
+#endif
+ /*
+ * Make sure the SUPR3Hardened API works (ignoring errors for now).
+ */
+ int vrc = SUPR3HardenedVerifyInit();
+ if (RT_FAILURE(vrc))
+ LogRel(("SUPR3HardenedVerifyInit failed: %Rrc\n", vrc));
+
+ /*
+ * Probe the extension pack (this code is shared with refresh()).
+ */
+ i_probeAndLoad();
+
+#ifdef VBOX_WITH_MAIN_NLS
+ /* register language files if exist */
+ if (m->pReg != NULL && m->pReg->pszNlsBaseName != NULL)
+ {
+ char szPath[RTPATH_MAX];
+ vrc = RTPathJoin(szPath, sizeof(szPath), a_pszDir, "nls");
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathAppend(szPath, sizeof(szPath), m->pReg->pszNlsBaseName);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = VirtualBoxTranslator::registerTranslation(szPath, false, &m->pTrComponent);
+ if (RT_FAILURE(vrc))
+ m->pTrComponent = NULL;
+ }
+ }
+ }
+#endif
+
+ autoInitSpan.setSucceeded();
+ return S_OK;
+}
+
+/**
+ * COM cruft.
+ */
+void ExtPack::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+/**
+ * Do the actual cleanup.
+ */
+void ExtPack::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (!autoUninitSpan.uninitDone() && m != NULL)
+ {
+ if (m->hMainMod != NIL_RTLDRMOD)
+ {
+ AssertPtr(m->pReg);
+ if (m->pReg->pfnUnload != NULL)
+ m->pReg->pfnUnload(m->pReg);
+
+ RTLdrClose(m->hMainMod);
+ m->hMainMod = NIL_RTLDRMOD;
+ m->pReg = NULL;
+ }
+
+ VBoxExtPackFreeDesc(&m->Desc);
+
+#ifdef VBOX_WITH_MAIN_NLS
+ if (m->pTrComponent != NULL)
+ VirtualBoxTranslator::unregisterTranslation(m->pTrComponent);
+#endif
+ delete m;
+ m = NULL;
+ }
+}
+
+
+#ifndef VBOX_COM_INPROC
+/**
+ * Calls the installed hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pVirtualBox The VirtualBox interface.
+ * @param a_pLock The write lock held by the caller.
+ * @param pErrInfo Where to return error information.
+ */
+bool ExtPack::i_callInstalledHook(IVirtualBox *a_pVirtualBox, AutoWriteLock *a_pLock, PRTERRINFO pErrInfo)
+{
+ if ( m != NULL
+ && m->hMainMod != NIL_RTLDRMOD)
+ {
+ if (m->pReg->pfnInstalled)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ pErrInfo->rc = m->pReg->pfnInstalled(m->pReg, a_pVirtualBox, pErrInfo);
+ a_pLock->acquire();
+ return true;
+ }
+ }
+ pErrInfo->rc = VINF_SUCCESS;
+ return false;
+}
+
+/**
+ * Calls the uninstall hook and closes the module.
+ *
+ * @returns S_OK or COM error status with error information.
+ * @param a_pVirtualBox The VirtualBox interface.
+ * @param a_fForcedRemoval When set, we'll ignore complaints from the
+ * uninstall hook.
+ * @remarks The caller holds the manager's write lock, not released.
+ */
+HRESULT ExtPack::i_callUninstallHookAndClose(IVirtualBox *a_pVirtualBox, bool a_fForcedRemoval)
+{
+ HRESULT hrc = S_OK;
+
+ if ( m != NULL
+ && m->hMainMod != NIL_RTLDRMOD)
+ {
+ if (m->pReg->pfnUninstall && !a_fForcedRemoval)
+ {
+ int vrc = m->pReg->pfnUninstall(m->pReg, a_pVirtualBox);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("ExtPack pfnUninstall returned %Rrc for %s\n", vrc, m->Desc.strName.c_str()));
+ if (!a_fForcedRemoval)
+ hrc = setErrorBoth(E_FAIL, vrc, tr("pfnUninstall returned %Rrc"), vrc);
+ }
+ }
+ if (SUCCEEDED(hrc))
+ {
+ RTLdrClose(m->hMainMod);
+ m->hMainMod = NIL_RTLDRMOD;
+ m->pReg = NULL;
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * Calls the pfnVirtualBoxReady hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pVirtualBox The VirtualBox interface.
+ * @param a_pLock The write lock held by the caller.
+ */
+bool ExtPack::i_callVirtualBoxReadyHook(IVirtualBox *a_pVirtualBox, AutoWriteLock *a_pLock)
+{
+ if ( m != NULL
+ && m->fUsable
+ && m->hMainMod != NIL_RTLDRMOD
+ && !m->fMadeReadyCall)
+ {
+ m->fMadeReadyCall = true;
+ if (m->pReg->pfnVirtualBoxReady)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ m->pReg->pfnVirtualBoxReady(m->pReg, a_pVirtualBox);
+ i_notifyCloudProviderManager();
+ a_pLock->acquire();
+ return true;
+ }
+ }
+ return false;
+}
+#endif /* !VBOX_COM_INPROC */
+
+#ifdef VBOX_COM_INPROC
+/**
+ * Calls the pfnConsoleReady hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pConsole The Console interface.
+ * @param a_pLock The write lock held by the caller.
+ */
+bool ExtPack::i_callConsoleReadyHook(IConsole *a_pConsole, AutoWriteLock *a_pLock)
+{
+ if ( m != NULL
+ && m->fUsable
+ && m->hMainMod != NIL_RTLDRMOD
+ && !m->fMadeReadyCall)
+ {
+ m->fMadeReadyCall = true;
+ if (m->pReg->pfnConsoleReady)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ m->pReg->pfnConsoleReady(m->pReg, a_pConsole);
+ a_pLock->acquire();
+ return true;
+ }
+ }
+ return false;
+}
+#endif /* VBOX_COM_INPROC */
+
+#ifndef VBOX_COM_INPROC
+/**
+ * Calls the pfnVMCreate hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pVirtualBox The VirtualBox interface.
+ * @param a_pMachine The machine interface of the new VM.
+ * @param a_pLock The write lock held by the caller.
+ */
+bool ExtPack::i_callVmCreatedHook(IVirtualBox *a_pVirtualBox, IMachine *a_pMachine, AutoWriteLock *a_pLock)
+{
+ if ( m != NULL
+ && m->hMainMod != NIL_RTLDRMOD
+ && m->fUsable)
+ {
+ if (m->pReg->pfnVMCreated)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ m->pReg->pfnVMCreated(m->pReg, a_pVirtualBox, a_pMachine);
+ a_pLock->acquire();
+ return true;
+ }
+ }
+ return false;
+}
+#endif /* !VBOX_COM_INPROC */
+
+#ifdef VBOX_COM_INPROC
+
+/**
+ * Calls the pfnVMConfigureVMM hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pConsole The console interface.
+ * @param a_pVM The VM handle.
+ * @param a_pVMM The VMM function table.
+ * @param a_pLock The write lock held by the caller.
+ * @param a_pvrc Where to return the status code of the callback. This
+ * is always set. LogRel is called on if a failure status
+ * is returned.
+ */
+bool ExtPack::i_callVmConfigureVmmHook(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM, AutoWriteLock *a_pLock, int *a_pvrc)
+{
+ *a_pvrc = VINF_SUCCESS;
+ if ( m != NULL
+ && m->hMainMod != NIL_RTLDRMOD
+ && m->fUsable)
+ {
+ if (m->pReg->pfnVMConfigureVMM)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ int vrc = m->pReg->pfnVMConfigureVMM(m->pReg, a_pConsole, a_pVM, a_pVMM);
+ *a_pvrc = vrc;
+ a_pLock->acquire();
+ if (RT_FAILURE(vrc))
+ LogRel(("ExtPack pfnVMConfigureVMM returned %Rrc for %s\n", vrc, m->Desc.strName.c_str()));
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Calls the pfnVMPowerOn hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pConsole The console interface.
+ * @param a_pVM The VM handle.
+ * @param a_pVMM The VMM function table.
+ * @param a_pLock The write lock held by the caller.
+ * @param a_pvrc Where to return the status code of the callback. This
+ * is always set. LogRel is called on if a failure status
+ * is returned.
+ */
+bool ExtPack::i_callVmPowerOnHook(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM, AutoWriteLock *a_pLock, int *a_pvrc)
+{
+ *a_pvrc = VINF_SUCCESS;
+ if ( m != NULL
+ && m->hMainMod != NIL_RTLDRMOD
+ && m->fUsable)
+ {
+ if (m->pReg->pfnVMPowerOn)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ int vrc = m->pReg->pfnVMPowerOn(m->pReg, a_pConsole, a_pVM, a_pVMM);
+ *a_pvrc = vrc;
+ a_pLock->acquire();
+ if (RT_FAILURE(vrc))
+ LogRel(("ExtPack pfnVMPowerOn returned %Rrc for %s\n", vrc, m->Desc.strName.c_str()));
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Calls the pfnVMPowerOff hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pConsole The console interface.
+ * @param a_pVM The VM handle.
+ * @param a_pVMM The VMM function table.
+ * @param a_pLock The write lock held by the caller.
+ */
+bool ExtPack::i_callVmPowerOffHook(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM, AutoWriteLock *a_pLock)
+{
+ if ( m != NULL
+ && m->hMainMod != NIL_RTLDRMOD
+ && m->fUsable)
+ {
+ if (m->pReg->pfnVMPowerOff)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ m->pReg->pfnVMPowerOff(m->pReg, a_pConsole, a_pVM, a_pVMM);
+ a_pLock->acquire();
+ return true;
+ }
+ }
+ return false;
+}
+
+#endif /* VBOX_COM_INPROC */
+
+/**
+ * Check if the extension pack is usable and has an VRDE module.
+ *
+ * @returns S_OK or COM error status with error information.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+HRESULT ExtPack::i_checkVrde(void)
+{
+ HRESULT hrc;
+ if ( m != NULL
+ && m->fUsable)
+ {
+ if (m->Desc.strVrdeModule.isNotEmpty())
+ hrc = S_OK;
+ else
+ hrc = setError(E_FAIL, tr("The extension pack '%s' does not include a VRDE module"), m->Desc.strName.c_str());
+ }
+ else
+ hrc = setError(E_FAIL, "%s", m->strWhyUnusable.c_str());
+ return hrc;
+}
+
+/**
+ * Check if the extension pack is usable and has a cryptographic module.
+ *
+ * @returns S_OK or COM error status with error information.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+HRESULT ExtPack::i_checkCrypto(void)
+{
+ HRESULT hrc;
+ if ( m != NULL
+ && m->fUsable)
+ {
+ if (m->Desc.strCryptoModule.isNotEmpty())
+ hrc = S_OK;
+ else
+ hrc = setError(E_FAIL, tr("The extension pack '%s' does not include a cryptographic module"), m->Desc.strName.c_str());
+ }
+ else
+ hrc = setError(E_FAIL, "%s", m->strWhyUnusable.c_str());
+ return hrc;
+}
+
+/**
+ * Same as checkVrde(), except that it also resolves the path to the module.
+ *
+ * @returns S_OK or COM error status with error information.
+ * @param a_pstrVrdeLibrary Where to return the path on success.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+HRESULT ExtPack::i_getVrdpLibraryName(Utf8Str *a_pstrVrdeLibrary)
+{
+ HRESULT hrc = i_checkVrde();
+ if (SUCCEEDED(hrc))
+ {
+ if (i_findModule(m->Desc.strVrdeModule.c_str(), NULL, VBOXEXTPACKMODKIND_R3,
+ a_pstrVrdeLibrary, NULL /*a_pfNative*/, NULL /*a_pObjInfo*/))
+ hrc = S_OK;
+ else
+ hrc = setError(E_FAIL, tr("Failed to locate the VRDE module '%s' in extension pack '%s'"),
+ m->Desc.strVrdeModule.c_str(), m->Desc.strName.c_str());
+ }
+ return hrc;
+}
+
+/**
+ * Same as i_checkCrypto(), except that it also resolves the path to the module.
+ *
+ * @returns S_OK or COM error status with error information.
+ * @param a_pstrCryptoLibrary Where to return the path on success.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+HRESULT ExtPack::i_getCryptoLibraryName(Utf8Str *a_pstrCryptoLibrary)
+{
+ HRESULT hrc = i_checkCrypto();
+ if (SUCCEEDED(hrc))
+ {
+ if (i_findModule(m->Desc.strCryptoModule.c_str(), NULL, VBOXEXTPACKMODKIND_R3,
+ a_pstrCryptoLibrary, NULL /*a_pfNative*/, NULL /*a_pObjInfo*/))
+ hrc = S_OK;
+ else
+ hrc = setError(E_FAIL, tr("Failed to locate the cryptographic module '%s' in extension pack '%s'"),
+ m->Desc.strCryptoModule.c_str(), m->Desc.strName.c_str());
+ }
+ return hrc;
+}
+
+/**
+ * Resolves the path to the module.
+ *
+ * @returns S_OK or COM error status with error information.
+ * @param a_pszModuleName The library.
+ * @param a_pstrLibrary Where to return the path on success.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+HRESULT ExtPack::i_getLibraryName(const char *a_pszModuleName, Utf8Str *a_pstrLibrary)
+{
+ HRESULT hrc;
+ if (i_findModule(a_pszModuleName, NULL, VBOXEXTPACKMODKIND_R3,
+ a_pstrLibrary, NULL /*a_pfNative*/, NULL /*a_pObjInfo*/))
+ hrc = S_OK;
+ else
+ hrc = setError(E_FAIL, tr("Failed to locate the module '%s' in extension pack '%s'"),
+ a_pszModuleName, m->Desc.strName.c_str());
+ return hrc;
+}
+
+/**
+ * Check if this extension pack wishes to be the default VRDE provider.
+ *
+ * @returns @c true if it wants to and it is in a usable state, otherwise
+ * @c false.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+bool ExtPack::i_wantsToBeDefaultVrde(void) const
+{
+ return m->fUsable
+ && m->Desc.strVrdeModule.isNotEmpty();
+}
+
+/**
+ * Check if this extension pack wishes to be the default cryptographic provider.
+ *
+ * @returns @c true if it wants to and it is in a usable state, otherwise
+ * @c false.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+bool ExtPack::i_wantsToBeDefaultCrypto(void) const
+{
+ return m->fUsable
+ && m->Desc.strCryptoModule.isNotEmpty();
+}
+
+/**
+ * Refreshes the extension pack state.
+ *
+ * This is called by the manager so that the on disk changes are picked up.
+ *
+ * @returns S_OK or COM error status with error information.
+ *
+ * @param a_pfCanDelete Optional can-delete-this-object output indicator.
+ *
+ * @remarks Caller holds the extension manager lock for writing.
+ * @remarks Only called in VBoxSVC.
+ */
+HRESULT ExtPack::i_refresh(bool *a_pfCanDelete)
+{
+ if (a_pfCanDelete)
+ *a_pfCanDelete = false;
+
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS); /* for the COMGETTERs */
+
+ /*
+ * Has the module been deleted?
+ */
+ RTFSOBJINFO ObjInfoExtPack;
+ int vrc = RTPathQueryInfoEx(m->strExtPackPath.c_str(), &ObjInfoExtPack, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if ( RT_FAILURE(vrc)
+ || !RTFS_IS_DIRECTORY(ObjInfoExtPack.Attr.fMode))
+ {
+ if (a_pfCanDelete)
+ *a_pfCanDelete = true;
+ return S_OK;
+ }
+
+ /*
+ * We've got a directory, so try query file system object info for the
+ * files we are interested in as well.
+ */
+ RTFSOBJINFO ObjInfoDesc;
+ char szDescFilePath[RTPATH_MAX];
+ vrc = RTPathJoin(szDescFilePath, sizeof(szDescFilePath), m->strExtPackPath.c_str(), VBOX_EXTPACK_DESCRIPTION_NAME);
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathQueryInfoEx(szDescFilePath, &ObjInfoDesc, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ RT_ZERO(ObjInfoDesc);
+
+ RTFSOBJINFO ObjInfoMainMod;
+ if (m->strMainModPath.isNotEmpty())
+ vrc = RTPathQueryInfoEx(m->strMainModPath.c_str(), &ObjInfoMainMod, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (m->strMainModPath.isEmpty() || RT_FAILURE(vrc))
+ RT_ZERO(ObjInfoMainMod);
+
+ /*
+ * If we have a usable module already, just verify that things haven't
+ * changed since we loaded it.
+ */
+ if (m->fUsable)
+ {
+ if (m->hMainMod == NIL_RTLDRMOD)
+ i_probeAndLoad();
+ else if ( !i_objinfoIsEqual(&ObjInfoDesc, &m->ObjInfoDesc)
+ || !i_objinfoIsEqual(&ObjInfoMainMod, &m->ObjInfoMainMod)
+ || !i_objinfoIsEqual(&ObjInfoExtPack, &m->ObjInfoExtPack) )
+ {
+ /** @todo not important, so it can wait. */
+ }
+ }
+ /*
+ * Ok, it is currently not usable. If anything has changed since last time
+ * reprobe the extension pack.
+ */
+ else if ( !i_objinfoIsEqual(&ObjInfoDesc, &m->ObjInfoDesc)
+ || !i_objinfoIsEqual(&ObjInfoMainMod, &m->ObjInfoMainMod)
+ || !i_objinfoIsEqual(&ObjInfoExtPack, &m->ObjInfoExtPack) )
+ i_probeAndLoad();
+
+ return S_OK;
+}
+
+#ifndef VBOX_COM_INPROC
+/**
+ * Checks if there are cloud providers vetoing extension pack uninstall.
+ *
+ * This needs going through the cloud provider singleton in VirtualBox,
+ * the job cannot be done purely by using the code in the extension pack).
+ * It cannot be integrated into i_callUninstallHookAndClose, because it
+ * can only do its job when the extpack lock is not held, whereas the
+ * actual uninstall must be done with the lock held all the time for
+ * consistency reasons.
+ *
+ * This is called when uninstalling or replacing an extension pack.
+ *
+ * @returns true / false
+ */
+bool ExtPack::i_areThereCloudProviderUninstallVetos()
+{
+ Assert(m->pVirtualBox != NULL); /* Only called from VBoxSVC. */
+
+ ComObjPtr<CloudProviderManager> cpm(m->pVirtualBox->i_getCloudProviderManager());
+ AssertReturn(!cpm.isNull(), false);
+
+ return !cpm->i_canRemoveExtPack(static_cast<IExtPack *>(this));
+}
+
+/**
+ * Notifies the Cloud Provider Manager that there is a new extension pack.
+ *
+ * This is called when installing an extension pack.
+ */
+void ExtPack::i_notifyCloudProviderManager()
+{
+ Assert(m->pVirtualBox != NULL); /* Only called from VBoxSVC. */
+
+ ComObjPtr<CloudProviderManager> cpm(m->pVirtualBox->i_getCloudProviderManager());
+ AssertReturnVoid(!cpm.isNull());
+
+ cpm->i_addExtPack(static_cast<IExtPack *>(this));
+}
+
+#endif /* !VBOX_COM_INPROC */
+
+/**
+ * Probes the extension pack, loading the main dll and calling its registration
+ * entry point.
+ *
+ * This updates the state accordingly, the strWhyUnusable and fUnusable members
+ * being the most important ones.
+ */
+void ExtPack::i_probeAndLoad(void)
+{
+ m->fUsable = false;
+ m->fMadeReadyCall = false;
+
+ /*
+ * Query the file system info for the extension pack directory. This and
+ * all other file system info we save is for the benefit of refresh().
+ */
+ int vrc = RTPathQueryInfoEx(m->strExtPackPath.c_str(), &m->ObjInfoExtPack, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ {
+ m->strWhyUnusable.printf(tr("RTPathQueryInfoEx on '%s' failed: %Rrc"), m->strExtPackPath.c_str(), vrc);
+ return;
+ }
+ if (!RTFS_IS_DIRECTORY(m->ObjInfoExtPack.Attr.fMode))
+ {
+ if (RTFS_IS_SYMLINK(m->ObjInfoExtPack.Attr.fMode))
+ m->strWhyUnusable.printf(tr("'%s' is a symbolic link, this is not allowed"),
+ m->strExtPackPath.c_str(), vrc);
+ else if (RTFS_IS_FILE(m->ObjInfoExtPack.Attr.fMode))
+ m->strWhyUnusable.printf(tr("'%s' is a symbolic file, not a directory"),
+ m->strExtPackPath.c_str(), vrc);
+ else
+ m->strWhyUnusable.printf(tr("'%s' is not a directory (fMode=%#x)"),
+ m->strExtPackPath.c_str(), m->ObjInfoExtPack.Attr.fMode);
+ return;
+ }
+
+ RTERRINFOSTATIC ErrInfo;
+ RTErrInfoInitStatic(&ErrInfo);
+ vrc = SUPR3HardenedVerifyDir(m->strExtPackPath.c_str(), true /*fRecursive*/, true /*fCheckFiles*/, &ErrInfo.Core);
+ if (RT_FAILURE(vrc))
+ {
+ m->strWhyUnusable.printf("%s (rc=%Rrc)", ErrInfo.Core.pszMsg, vrc);
+ return;
+ }
+
+ /*
+ * Read the description file.
+ */
+ RTCString strSavedName(m->Desc.strName);
+ RTCString *pStrLoadErr = VBoxExtPackLoadDesc(m->strExtPackPath.c_str(), &m->Desc, &m->ObjInfoDesc);
+ if (pStrLoadErr != NULL)
+ {
+ m->strWhyUnusable.printf(tr("Failed to load '%s/%s': %s"),
+ m->strExtPackPath.c_str(), VBOX_EXTPACK_DESCRIPTION_NAME, pStrLoadErr->c_str());
+ m->Desc.strName = strSavedName;
+ delete pStrLoadErr;
+ return;
+ }
+
+ /*
+ * Make sure the XML name and directory matches.
+ */
+ if (!m->Desc.strName.equalsIgnoreCase(strSavedName))
+ {
+ m->strWhyUnusable.printf(tr("The description name ('%s') and directory name ('%s') does not match"),
+ m->Desc.strName.c_str(), strSavedName.c_str());
+ m->Desc.strName = strSavedName;
+ return;
+ }
+
+ /*
+ * Load the main DLL and call the predefined entry point.
+ */
+#ifndef VBOX_COM_INPROC
+ const char *pszMainModule = m->Desc.strMainModule.c_str();
+#else
+ const char *pszMainModule = m->Desc.strMainVMModule.c_str();
+ if (m->Desc.strMainVMModule.isEmpty())
+ {
+ /*
+ * We're good! The main module for VM processes is optional.
+ */
+ m->fUsable = true;
+ m->strWhyUnusable.setNull();
+ return;
+ }
+#endif
+ bool fIsNative;
+ if (!i_findModule(pszMainModule, NULL /* default extension */, VBOXEXTPACKMODKIND_R3,
+ &m->strMainModPath, &fIsNative, &m->ObjInfoMainMod))
+ {
+ m->strWhyUnusable.printf(tr("Failed to locate the main module ('%s')"), pszMainModule);
+ return;
+ }
+
+ vrc = SUPR3HardenedVerifyPlugIn(m->strMainModPath.c_str(), &ErrInfo.Core);
+ if (RT_FAILURE(vrc))
+ {
+ m->strWhyUnusable.printf("%s", ErrInfo.Core.pszMsg);
+ return;
+ }
+
+ if (fIsNative)
+ {
+ vrc = SUPR3HardenedLdrLoadPlugIn(m->strMainModPath.c_str(), &m->hMainMod, &ErrInfo.Core);
+ if (RT_FAILURE(vrc))
+ {
+ m->hMainMod = NIL_RTLDRMOD;
+ m->strWhyUnusable.printf(tr("Failed to load the main module ('%s'): %Rrc - %s"),
+ m->strMainModPath.c_str(), vrc, ErrInfo.Core.pszMsg);
+ return;
+ }
+ }
+ else
+ {
+ m->strWhyUnusable.printf(tr("Only native main modules are currently supported"));
+ return;
+ }
+
+ /*
+ * Resolve the predefined entry point.
+ */
+#ifndef VBOX_COM_INPROC
+ const char *pszMainEntryPoint = VBOX_EXTPACK_MAIN_MOD_ENTRY_POINT;
+ PFNVBOXEXTPACKREGISTER pfnRegistration;
+ uint32_t uVersion = VBOXEXTPACKREG_VERSION;
+#else
+ const char *pszMainEntryPoint = VBOX_EXTPACK_MAIN_VM_MOD_ENTRY_POINT;
+ PFNVBOXEXTPACKVMREGISTER pfnRegistration;
+ uint32_t uVersion = VBOXEXTPACKVMREG_VERSION;
+#endif
+ vrc = RTLdrGetSymbol(m->hMainMod, pszMainEntryPoint, (void **)&pfnRegistration);
+ if (RT_SUCCESS(vrc))
+ {
+ RTErrInfoClear(&ErrInfo.Core);
+ vrc = pfnRegistration(&m->Hlp, &m->pReg, &ErrInfo.Core);
+ if ( RT_SUCCESS(vrc)
+ && !RTErrInfoIsSet(&ErrInfo.Core)
+ && RT_VALID_PTR(m->pReg))
+ {
+ if ( VBOXEXTPACK_IS_MAJOR_VER_EQUAL(m->pReg->u32Version, uVersion)
+ && m->pReg->u32EndMarker == m->pReg->u32Version)
+ {
+#ifndef VBOX_COM_INPROC
+ if ( (!m->pReg->pfnInstalled || RT_VALID_PTR(m->pReg->pfnInstalled))
+ && (!m->pReg->pfnUninstall || RT_VALID_PTR(m->pReg->pfnUninstall))
+ && (!m->pReg->pfnVirtualBoxReady || RT_VALID_PTR(m->pReg->pfnVirtualBoxReady))
+ && (!m->pReg->pfnUnload || RT_VALID_PTR(m->pReg->pfnUnload))
+ && (!m->pReg->pfnVMCreated || RT_VALID_PTR(m->pReg->pfnVMCreated))
+ && (!m->pReg->pfnQueryObject || RT_VALID_PTR(m->pReg->pfnQueryObject))
+ )
+ {
+ /*
+ * We're good!
+ */
+ m->fUsable = true;
+ m->strWhyUnusable.setNull();
+ return;
+ }
+#else
+ if ( (!m->pReg->pfnConsoleReady || RT_VALID_PTR(m->pReg->pfnConsoleReady))
+ && (!m->pReg->pfnUnload || RT_VALID_PTR(m->pReg->pfnUnload))
+ && (!m->pReg->pfnVMConfigureVMM || RT_VALID_PTR(m->pReg->pfnVMConfigureVMM))
+ && (!m->pReg->pfnVMPowerOn || RT_VALID_PTR(m->pReg->pfnVMPowerOn))
+ && (!m->pReg->pfnVMPowerOff || RT_VALID_PTR(m->pReg->pfnVMPowerOff))
+ && (!m->pReg->pfnQueryObject || RT_VALID_PTR(m->pReg->pfnQueryObject))
+ )
+ {
+ /*
+ * We're good!
+ */
+ m->fUsable = true;
+ m->strWhyUnusable.setNull();
+ return;
+ }
+#endif
+
+ m->strWhyUnusable = tr("The registration structure contains one or more invalid function pointers");
+ }
+ else
+ m->strWhyUnusable.printf(tr("Unsupported registration structure version %u.%u"),
+ RT_HIWORD(m->pReg->u32Version), RT_LOWORD(m->pReg->u32Version));
+ }
+ else
+ m->strWhyUnusable.printf(tr("%s returned %Rrc, pReg=%p ErrInfo='%s'"),
+ pszMainEntryPoint, vrc, m->pReg, ErrInfo.Core.pszMsg);
+ m->pReg = NULL;
+ }
+ else
+ m->strWhyUnusable.printf(tr("Failed to resolve exported symbol '%s' in the main module: %Rrc"),
+ pszMainEntryPoint, vrc);
+
+ RTLdrClose(m->hMainMod);
+ m->hMainMod = NIL_RTLDRMOD;
+}
+
+/**
+ * Finds a module.
+ *
+ * @returns true if found, false if not.
+ * @param a_pszName The module base name (no extension).
+ * @param a_pszExt The extension. If NULL we use default
+ * extensions.
+ * @param a_enmKind The kind of module to locate.
+ * @param a_pStrFound Where to return the path to the module we've
+ * found.
+ * @param a_pfNative Where to return whether this is a native module
+ * or an agnostic one. Optional.
+ * @param a_pObjInfo Where to return the file system object info for
+ * the module. Optional.
+ */
+bool ExtPack::i_findModule(const char *a_pszName, const char *a_pszExt, VBOXEXTPACKMODKIND a_enmKind,
+ Utf8Str *a_pStrFound, bool *a_pfNative, PRTFSOBJINFO a_pObjInfo) const
+{
+ /*
+ * Try the native path first.
+ */
+ char szPath[RTPATH_MAX];
+ int vrc = RTPathJoin(szPath, sizeof(szPath), m->strExtPackPath.c_str(), RTBldCfgTargetDotArch());
+ AssertLogRelRCReturn(vrc, false);
+ vrc = RTPathAppend(szPath, sizeof(szPath), a_pszName);
+ AssertLogRelRCReturn(vrc, false);
+ if (!a_pszExt)
+ {
+ const char *pszDefExt;
+ switch (a_enmKind)
+ {
+ case VBOXEXTPACKMODKIND_RC: pszDefExt = ".rc"; break;
+ case VBOXEXTPACKMODKIND_R0: pszDefExt = ".r0"; break;
+ case VBOXEXTPACKMODKIND_R3: pszDefExt = RTLdrGetSuff(); break;
+ default:
+ AssertFailedReturn(false);
+ }
+ vrc = RTStrCat(szPath, sizeof(szPath), pszDefExt);
+ AssertLogRelRCReturn(vrc, false);
+ }
+
+ RTFSOBJINFO ObjInfo;
+ if (!a_pObjInfo)
+ a_pObjInfo = &ObjInfo;
+ vrc = RTPathQueryInfo(szPath, a_pObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(vrc) && RTFS_IS_FILE(a_pObjInfo->Attr.fMode))
+ {
+ if (a_pfNative)
+ *a_pfNative = true;
+ *a_pStrFound = szPath;
+ return true;
+ }
+
+ /*
+ * Try the platform agnostic modules.
+ */
+ /* gcc.x86/module.rel */
+ char szSubDir[32];
+ RTStrPrintf(szSubDir, sizeof(szSubDir), "%s.%s", RTBldCfgCompiler(), RTBldCfgTargetArch());
+ vrc = RTPathJoin(szPath, sizeof(szPath), m->strExtPackPath.c_str(), szSubDir);
+ AssertLogRelRCReturn(vrc, false);
+ vrc = RTPathAppend(szPath, sizeof(szPath), a_pszName);
+ AssertLogRelRCReturn(vrc, false);
+ if (!a_pszExt)
+ {
+ vrc = RTStrCat(szPath, sizeof(szPath), ".rel");
+ AssertLogRelRCReturn(vrc, false);
+ }
+ vrc = RTPathQueryInfo(szPath, a_pObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(vrc) && RTFS_IS_FILE(a_pObjInfo->Attr.fMode))
+ {
+ if (a_pfNative)
+ *a_pfNative = false;
+ *a_pStrFound = szPath;
+ return true;
+ }
+
+ /* x86/module.rel */
+ vrc = RTPathJoin(szPath, sizeof(szPath), m->strExtPackPath.c_str(), RTBldCfgTargetArch());
+ AssertLogRelRCReturn(vrc, false);
+ vrc = RTPathAppend(szPath, sizeof(szPath), a_pszName);
+ AssertLogRelRCReturn(vrc, false);
+ if (!a_pszExt)
+ {
+ vrc = RTStrCat(szPath, sizeof(szPath), ".rel");
+ AssertLogRelRCReturn(vrc, false);
+ }
+ vrc = RTPathQueryInfo(szPath, a_pObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(vrc) && RTFS_IS_FILE(a_pObjInfo->Attr.fMode))
+ {
+ if (a_pfNative)
+ *a_pfNative = false;
+ *a_pStrFound = szPath;
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Compares two file system object info structures.
+ *
+ * @returns true if equal, false if not.
+ * @param pObjInfo1 The first.
+ * @param pObjInfo2 The second.
+ * @todo IPRT should do this, really.
+ */
+/* static */ bool ExtPack::i_objinfoIsEqual(PCRTFSOBJINFO pObjInfo1, PCRTFSOBJINFO pObjInfo2)
+{
+ if (!RTTimeSpecIsEqual(&pObjInfo1->ModificationTime, &pObjInfo2->ModificationTime))
+ return false;
+ if (!RTTimeSpecIsEqual(&pObjInfo1->ChangeTime, &pObjInfo2->ChangeTime))
+ return false;
+ if (!RTTimeSpecIsEqual(&pObjInfo1->BirthTime, &pObjInfo2->BirthTime))
+ return false;
+ if (pObjInfo1->cbObject != pObjInfo2->cbObject)
+ return false;
+ if (pObjInfo1->Attr.fMode != pObjInfo2->Attr.fMode)
+ return false;
+ if (pObjInfo1->Attr.enmAdditional == pObjInfo2->Attr.enmAdditional)
+ {
+ switch (pObjInfo1->Attr.enmAdditional)
+ {
+ case RTFSOBJATTRADD_UNIX:
+ if (pObjInfo1->Attr.u.Unix.uid != pObjInfo2->Attr.u.Unix.uid)
+ return false;
+ if (pObjInfo1->Attr.u.Unix.gid != pObjInfo2->Attr.u.Unix.gid)
+ return false;
+ if (pObjInfo1->Attr.u.Unix.INodeIdDevice != pObjInfo2->Attr.u.Unix.INodeIdDevice)
+ return false;
+ if (pObjInfo1->Attr.u.Unix.INodeId != pObjInfo2->Attr.u.Unix.INodeId)
+ return false;
+ if (pObjInfo1->Attr.u.Unix.GenerationId != pObjInfo2->Attr.u.Unix.GenerationId)
+ return false;
+ break;
+ default:
+ break;
+ }
+ }
+ return true;
+}
+
+
+/**
+ * @interface_method_impl{VBOXEXTPACKHLP,pfnFindModule}
+ */
+/*static*/ DECLCALLBACK(int)
+ExtPack::i_hlpFindModule(PCVBOXEXTPACKHLP pHlp, const char *pszName, const char *pszExt, VBOXEXTPACKMODKIND enmKind,
+ char *pszFound, size_t cbFound, bool *pfNative)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pszExt, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFound, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfNative, VERR_INVALID_POINTER);
+ AssertReturn(enmKind > VBOXEXTPACKMODKIND_INVALID && enmKind < VBOXEXTPACKMODKIND_END, VERR_INVALID_PARAMETER);
+
+ AssertPtrReturn(pHlp, VERR_INVALID_POINTER);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VERR_INVALID_POINTER);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VERR_INVALID_POINTER);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ /*
+ * This is just a wrapper around findModule.
+ */
+ Utf8Str strFound;
+ if (pThis->i_findModule(pszName, pszExt, enmKind, &strFound, pfNative, NULL))
+ return RTStrCopy(pszFound, cbFound, strFound.c_str());
+ return VERR_FILE_NOT_FOUND;
+}
+
+/*static*/ DECLCALLBACK(int)
+ExtPack::i_hlpGetFilePath(PCVBOXEXTPACKHLP pHlp, const char *pszFilename, char *pszPath, size_t cbPath)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(cbPath > 0, VERR_BUFFER_OVERFLOW);
+
+ AssertPtrReturn(pHlp, VERR_INVALID_POINTER);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VERR_INVALID_POINTER);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VERR_INVALID_POINTER);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ /*
+ * This is a simple RTPathJoin, no checking if things exists or anything.
+ */
+ int vrc = RTPathJoin(pszPath, cbPath, pThis->m->strExtPackPath.c_str(), pszFilename);
+ if (RT_FAILURE(vrc))
+ RT_BZERO(pszPath, cbPath);
+ return vrc;
+}
+
+/*static*/ DECLCALLBACK(VBOXEXTPACKCTX)
+ExtPack::i_hlpGetContext(PCVBOXEXTPACKHLP pHlp)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pHlp, VBOXEXTPACKCTX_INVALID);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VBOXEXTPACKCTX_INVALID);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VBOXEXTPACKCTX_INVALID);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VBOXEXTPACKCTX_INVALID);
+
+ return pThis->m->enmContext;
+}
+
+/*static*/ DECLCALLBACK(int)
+ExtPack::i_hlpLoadHGCMService(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IConsole) *pConsole,
+ const char *pszServiceLibrary, const char *pszServiceName)
+{
+#ifdef VBOX_COM_INPROC
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pszServiceLibrary, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszServiceName, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pHlp, VERR_INVALID_POINTER);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VERR_INVALID_POINTER);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VERR_INVALID_POINTER);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pConsole, VERR_INVALID_POINTER);
+
+ Console *pCon = (Console *)pConsole;
+ return pCon->i_hgcmLoadService(pszServiceLibrary, pszServiceName);
+#else
+ NOREF(pHlp); NOREF(pConsole); NOREF(pszServiceLibrary); NOREF(pszServiceName);
+ return VERR_INVALID_STATE;
+#endif
+}
+
+/*static*/ DECLCALLBACK(int)
+ExtPack::i_hlpLoadVDPlugin(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IVirtualBox) *pVirtualBox, const char *pszPluginLibrary)
+{
+#ifndef VBOX_COM_INPROC
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pszPluginLibrary, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pHlp, VERR_INVALID_POINTER);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VERR_INVALID_POINTER);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VERR_INVALID_POINTER);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pVirtualBox, VERR_INVALID_POINTER);
+
+ VirtualBox *pVBox = (VirtualBox *)pVirtualBox;
+ return pVBox->i_loadVDPlugin(pszPluginLibrary);
+#else
+ NOREF(pHlp); NOREF(pVirtualBox); NOREF(pszPluginLibrary);
+ return VERR_INVALID_STATE;
+#endif
+}
+
+/*static*/ DECLCALLBACK(int)
+ExtPack::i_hlpUnloadVDPlugin(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IVirtualBox) *pVirtualBox, const char *pszPluginLibrary)
+{
+#ifndef VBOX_COM_INPROC
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pszPluginLibrary, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pHlp, VERR_INVALID_POINTER);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VERR_INVALID_POINTER);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VERR_INVALID_POINTER);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pVirtualBox, VERR_INVALID_POINTER);
+
+ VirtualBox *pVBox = (VirtualBox *)pVirtualBox;
+ return pVBox->i_unloadVDPlugin(pszPluginLibrary);
+#else
+ NOREF(pHlp); NOREF(pVirtualBox); NOREF(pszPluginLibrary);
+ return VERR_INVALID_STATE;
+#endif
+}
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpCreateProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IUnknown) *pInitiator,
+ const char *pcszDescription, uint32_t cOperations,
+ uint32_t uTotalOperationsWeight, const char *pcszFirstOperationDescription,
+ uint32_t uFirstOperationWeight, VBOXEXTPACK_IF_CS(IProgress) **ppProgressOut)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pcszDescription, (uint32_t)E_INVALIDARG);
+ AssertReturn(cOperations >= 1, (uint32_t)E_INVALIDARG);
+ AssertReturn(uTotalOperationsWeight >= 1, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(pcszFirstOperationDescription, (uint32_t)E_INVALIDARG);
+ AssertReturn(uFirstOperationWeight >= 1, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(ppProgressOut, (uint32_t)E_INVALIDARG);
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+#ifndef VBOX_COM_INPROC
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+#endif
+
+ ComObjPtr<Progress> pProgress;
+ HRESULT hrc = pProgress.createObject();
+ if (FAILED(hrc))
+ return hrc;
+ hrc = pProgress->init(
+#ifndef VBOX_COM_INPROC
+ m->pVirtualBox,
+#endif
+ pInitiator, pcszDescription, TRUE /* aCancelable */,
+ cOperations, uTotalOperationsWeight,
+ pcszFirstOperationDescription, uFirstOperationWeight);
+ if (FAILED(hrc))
+ return hrc;
+
+ return pProgress.queryInterfaceTo(ppProgressOut);
+}
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpGetCanceledProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ bool *pfCanceled)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pProgress, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(pfCanceled, (uint32_t)E_INVALIDARG);
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+
+ BOOL fCanceled = FALSE;
+ HRESULT hrc = pProgress->COMGETTER(Canceled)(&fCanceled);
+ *pfCanceled = !!fCanceled;
+ return hrc;
+}
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpUpdateProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ uint32_t uPercent)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pProgress, (uint32_t)E_INVALIDARG);
+ AssertReturn(uPercent <= 100, (uint32_t)E_INVALIDARG);
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+
+ ComPtr<IInternalProgressControl> pProgressControl(pProgress);
+ AssertReturn(!!pProgressControl, (uint32_t)E_INVALIDARG);
+ return pProgressControl->SetCurrentOperationProgress(uPercent);
+}
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpNextOperationProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ const char *pcszNextOperationDescription,
+ uint32_t uNextOperationWeight)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pProgress, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(pcszNextOperationDescription, (uint32_t)E_INVALIDARG);
+ AssertReturn(uNextOperationWeight >= 1, (uint32_t)E_INVALIDARG);
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+
+ ComPtr<IInternalProgressControl> pProgressControl(pProgress);
+ AssertReturn(!!pProgressControl, (uint32_t)E_INVALIDARG);
+ return pProgressControl->SetNextOperation(Bstr(pcszNextOperationDescription).raw(), uNextOperationWeight);
+}
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpWaitOtherProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ VBOXEXTPACK_IF_CS(IProgress) *pProgressOther, uint32_t cTimeoutMS)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pProgress, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(pProgressOther, (uint32_t)E_INVALIDARG);
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+
+ ComPtr<IInternalProgressControl> pProgressControl(pProgress);
+ AssertReturn(!!pProgressControl, (uint32_t)E_INVALIDARG);
+ return pProgressControl->WaitForOtherProgressCompletion(pProgressOther, cTimeoutMS);
+}
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpCompleteProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ uint32_t uResultCode)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pProgress, (uint32_t)E_INVALIDARG);
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+
+ ComPtr<IInternalProgressControl> pProgressControl(pProgress);
+ AssertReturn(!!pProgressControl, (uint32_t)E_INVALIDARG);
+
+ ComPtr<IVirtualBoxErrorInfo> errorInfo;
+ if (FAILED((HRESULT)uResultCode))
+ {
+ ErrorInfoKeeper eik;
+ eik.getVirtualBoxErrorInfo(errorInfo);
+ }
+ return pProgressControl->NotifyComplete((LONG)uResultCode, errorInfo);
+}
+
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpCreateEvent(PCVBOXEXTPACKHLP pHlp,
+ VBOXEXTPACK_IF_CS(IEventSource) *aSource,
+ /* VBoxEventType_T */ uint32_t aType, bool aWaitable,
+ VBOXEXTPACK_IF_CS(IEvent) **ppEventOut)
+{
+ HRESULT hrc;
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(ppEventOut, (uint32_t)E_INVALIDARG);
+
+ ComObjPtr<VBoxEvent> pEvent;
+
+ hrc = pEvent.createObject();
+ if (FAILED(hrc))
+ return hrc;
+
+ /* default aSource to pVirtualBox? */
+ hrc = pEvent->init(aSource, static_cast<VBoxEventType_T>(aType), aWaitable);
+ if (FAILED(hrc))
+ return hrc;
+
+ return pEvent.queryInterfaceTo(ppEventOut);
+}
+
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpCreateVetoEvent(PCVBOXEXTPACKHLP pHlp,
+ VBOXEXTPACK_IF_CS(IEventSource) *aSource,
+ /* VBoxEventType_T */ uint32_t aType,
+ VBOXEXTPACK_IF_CS(IVetoEvent) **ppEventOut)
+{
+ HRESULT hrc;
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(ppEventOut, (uint32_t)E_INVALIDARG);
+
+ ComObjPtr<VBoxVetoEvent> pEvent;
+
+ hrc = pEvent.createObject();
+ if (FAILED(hrc))
+ return hrc;
+
+ /* default aSource to pVirtualBox? */
+ hrc = pEvent->init(aSource, static_cast<VBoxEventType_T>(aType));
+ if (FAILED(hrc))
+ return hrc;
+
+ return pEvent.queryInterfaceTo(ppEventOut);
+}
+
+
+/*static*/ DECLCALLBACK(const char *)
+ExtPack::i_hlpTranslate(PCVBOXEXTPACKHLP pHlp,
+ const char *pszComponent,
+ const char *pszSourceText,
+ const char *pszComment /* = NULL */,
+ const size_t aNum /* = -1 */)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pHlp, pszSourceText);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, pszSourceText);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, pszSourceText);
+
+#ifdef VBOX_WITH_MAIN_NLS
+ return VirtualBoxTranslator::translate(m->pTrComponent, pszComponent,
+ pszSourceText, pszComment, aNum);
+#else
+ NOREF(pszComponent);
+ NOREF(pszComment);
+ NOREF(aNum);
+ return pszSourceText;
+#endif
+}
+
+
+/*static*/ DECLCALLBACK(int)
+ExtPack::i_hlpReservedN(PCVBOXEXTPACKHLP pHlp)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pHlp, VERR_INVALID_POINTER);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VERR_INVALID_POINTER);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VERR_INVALID_POINTER);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+
+
+HRESULT ExtPack::getName(com::Utf8Str &aName)
+{
+ aName = m->Desc.strName;
+ return S_OK;
+}
+
+HRESULT ExtPack::getDescription(com::Utf8Str &aDescription)
+{
+ aDescription = m->Desc.strDescription;
+ return S_OK;
+}
+
+HRESULT ExtPack::getVersion(com::Utf8Str &aVersion)
+{
+ aVersion = m->Desc.strVersion;
+ return S_OK;
+}
+
+HRESULT ExtPack::getRevision(ULONG *aRevision)
+{
+ *aRevision = m->Desc.uRevision;
+ return S_OK;
+}
+
+HRESULT ExtPack::getEdition(com::Utf8Str &aEdition)
+{
+ aEdition = m->Desc.strEdition;
+ return S_OK;
+}
+
+HRESULT ExtPack::getVRDEModule(com::Utf8Str &aVRDEModule)
+{
+ aVRDEModule = m->Desc.strVrdeModule;
+ return S_OK;
+}
+
+HRESULT ExtPack::getCryptoModule(com::Utf8Str &aCryptoModule)
+{
+ aCryptoModule = m->Desc.strCryptoModule;
+ return S_OK;
+}
+
+HRESULT ExtPack::getPlugIns(std::vector<ComPtr<IExtPackPlugIn> > &aPlugIns)
+{
+ /** @todo implement plug-ins. */
+ NOREF(aPlugIns);
+ ReturnComNotImplemented();
+}
+
+HRESULT ExtPack::getUsable(BOOL *aUsable)
+{
+ *aUsable = m->fUsable;
+ return S_OK;
+}
+
+HRESULT ExtPack::getWhyUnusable(com::Utf8Str &aWhyUnusable)
+{
+ aWhyUnusable = m->strWhyUnusable;
+ return S_OK;
+}
+
+HRESULT ExtPack::getShowLicense(BOOL *aShowLicense)
+{
+ *aShowLicense = m->Desc.fShowLicense;
+ return S_OK;
+}
+
+HRESULT ExtPack::getLicense(com::Utf8Str &aLicense)
+{
+ Utf8Str strHtml("html");
+ Utf8Str str("");
+ return queryLicense(str, str, strHtml, aLicense);
+}
+
+HRESULT ExtPack::queryLicense(const com::Utf8Str &aPreferredLocale, const com::Utf8Str &aPreferredLanguage,
+ const com::Utf8Str &aFormat, com::Utf8Str &aLicenseText)
+{
+ HRESULT hrc = S_OK;
+
+ /*
+ * Validate input.
+ */
+ if (aPreferredLocale.length() != 2 && aPreferredLocale.length() != 0)
+ return setError(E_FAIL, tr("The preferred locale is a two character string or empty."));
+
+ if (aPreferredLanguage.length() != 2 && aPreferredLanguage.length() != 0)
+ return setError(E_FAIL, tr("The preferred language is a two character string or empty."));
+
+ if ( !aFormat.equals("html")
+ && !aFormat.equals("rtf")
+ && !aFormat.equals("txt"))
+ return setError(E_FAIL, tr("The license format can only have the values 'html', 'rtf' and 'txt'."));
+
+ /*
+ * Combine the options to form a file name before locking down anything.
+ */
+ char szName[sizeof(VBOX_EXTPACK_LICENSE_NAME_PREFIX "-de_DE.html") + 2];
+ if (aPreferredLocale.isNotEmpty() && aPreferredLanguage.isNotEmpty())
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-%s_%s.%s",
+ aPreferredLocale.c_str(), aPreferredLanguage.c_str(), aFormat.c_str());
+ else if (aPreferredLocale.isNotEmpty())
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-%s.%s",
+ aPreferredLocale.c_str(), aFormat.c_str());
+ else if (aPreferredLanguage.isNotEmpty())
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-_%s.%s",
+ aPreferredLocale.c_str(), aFormat.c_str());
+ else
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX ".%s",
+ aFormat.c_str());
+
+ /*
+ * Effectuate the query.
+ */
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS); /* paranoia */
+
+ if (!m->fUsable)
+ hrc = setError(E_FAIL, "%s", m->strWhyUnusable.c_str());
+ else
+ {
+ char szPath[RTPATH_MAX];
+ int vrc = RTPathJoin(szPath, sizeof(szPath), m->strExtPackPath.c_str(), szName);
+ if (RT_SUCCESS(vrc))
+ {
+ void *pvFile;
+ size_t cbFile;
+ vrc = RTFileReadAllEx(szPath, 0, RTFOFF_MAX, RTFILE_RDALL_O_DENY_READ, &pvFile, &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ Bstr bstrLicense((const char *)pvFile, cbFile);
+ if (bstrLicense.isNotEmpty())
+ {
+ aLicenseText = Utf8Str(bstrLicense);
+ hrc = S_OK;
+ }
+ else
+ hrc = setError(VBOX_E_IPRT_ERROR, tr("The license file '%s' is empty or contains invalid UTF-8 encoding"),
+ szPath);
+ RTFileReadAllFree(pvFile, cbFile);
+ }
+ else if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
+ hrc = setErrorBoth(VBOX_E_OBJECT_NOT_FOUND, vrc, tr("The license file '%s' was not found in extension pack '%s'"),
+ szName, m->Desc.strName.c_str());
+ else
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to open the license file '%s': %Rrc"), szPath, vrc);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("RTPathJoin failed: %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT ExtPack::queryObject(const com::Utf8Str &aObjUuid, ComPtr<IUnknown> &aReturnInterface)
+{
+ com::Guid ObjectId;
+ CheckComArgGuid(aObjUuid, ObjectId);
+
+ HRESULT hrc = S_OK;
+
+ if ( m->pReg
+ && m->pReg->pfnQueryObject)
+ {
+ void *pvUnknown = m->pReg->pfnQueryObject(m->pReg, ObjectId.raw());
+ if (pvUnknown)
+ {
+ aReturnInterface = (IUnknown *)pvUnknown;
+ /* The above assignment increased the refcount. Since pvUnknown
+ * is a dumb pointer we have to do the release ourselves. */
+ ((IUnknown *)pvUnknown)->Release();
+ }
+ else
+ hrc = E_NOINTERFACE;
+ }
+ else
+ hrc = E_NOINTERFACE;
+ return hrc;
+}
+
+DEFINE_EMPTY_CTOR_DTOR(ExtPackManager)
+
+/**
+ * Called by ComObjPtr::createObject when creating the object.
+ *
+ * Just initialize the basic object state, do the rest in init().
+ *
+ * @returns S_OK.
+ */
+HRESULT ExtPackManager::FinalConstruct()
+{
+ m = NULL;
+ return BaseFinalConstruct();
+}
+
+/**
+ * Initializes the extension pack manager.
+ *
+ * @returns COM status code.
+ * @param a_pVirtualBox Pointer to the VirtualBox object.
+ * @param a_enmContext The context we're in.
+ */
+HRESULT ExtPackManager::initExtPackManager(VirtualBox *a_pVirtualBox, VBOXEXTPACKCTX a_enmContext)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /*
+ * Figure some stuff out before creating the instance data.
+ */
+ char szBaseDir[RTPATH_MAX];
+ int vrc = RTPathAppPrivateArchTop(szBaseDir, sizeof(szBaseDir));
+ AssertLogRelRCReturn(vrc, E_FAIL);
+ vrc = RTPathAppend(szBaseDir, sizeof(szBaseDir), VBOX_EXTPACK_INSTALL_DIR);
+ AssertLogRelRCReturn(vrc, E_FAIL);
+
+ char szCertificatDir[RTPATH_MAX];
+ vrc = RTPathAppPrivateNoArch(szCertificatDir, sizeof(szCertificatDir));
+ AssertLogRelRCReturn(vrc, E_FAIL);
+ vrc = RTPathAppend(szCertificatDir, sizeof(szCertificatDir), VBOX_EXTPACK_CERT_DIR);
+ AssertLogRelRCReturn(vrc, E_FAIL);
+
+ /*
+ * Allocate and initialize the instance data.
+ */
+ m = new Data;
+ m->strBaseDir = szBaseDir;
+ m->strCertificatDirPath = szCertificatDir;
+ m->enmContext = a_enmContext;
+#ifndef VBOX_COM_INPROC
+ m->pVirtualBox = a_pVirtualBox;
+#else
+ RT_NOREF_PV(a_pVirtualBox);
+#endif
+
+ /*
+ * Go looking for extensions. The RTDirOpen may fail if nothing has been
+ * installed yet, or if root is paranoid and has revoked our access to them.
+ *
+ * We ASSUME that there are no files, directories or stuff in the directory
+ * that exceed the max name length in RTDIRENTRYEX.
+ */
+ HRESULT hrc = S_OK;
+ RTDIR hDir;
+ vrc = RTDirOpen(&hDir, szBaseDir);
+ if (RT_SUCCESS(vrc))
+ {
+ for (;;)
+ {
+ RTDIRENTRYEX Entry;
+ vrc = RTDirReadEx(hDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ {
+ AssertLogRelMsg(vrc == VERR_NO_MORE_FILES, ("%Rrc\n", vrc));
+ break;
+ }
+ if ( RTFS_IS_DIRECTORY(Entry.Info.Attr.fMode)
+ && strcmp(Entry.szName, ".") != 0
+ && strcmp(Entry.szName, "..") != 0
+ && VBoxExtPackIsValidMangledName(Entry.szName) )
+ {
+ /*
+ * All directories are extensions, the shall be nothing but
+ * extensions in this subdirectory.
+ */
+ char szExtPackDir[RTPATH_MAX];
+ vrc = RTPathJoin(szExtPackDir, sizeof(szExtPackDir), m->strBaseDir.c_str(), Entry.szName);
+ AssertLogRelRC(vrc);
+ if (RT_SUCCESS(vrc))
+ {
+ RTCString *pstrName = VBoxExtPackUnmangleName(Entry.szName, RTSTR_MAX);
+ AssertLogRel(pstrName);
+ if (pstrName)
+ {
+ ComObjPtr<ExtPack> NewExtPack;
+ HRESULT hrc2 = NewExtPack.createObject();
+ if (SUCCEEDED(hrc2))
+ hrc2 = NewExtPack->initWithDir(a_pVirtualBox, a_enmContext, pstrName->c_str(), szExtPackDir);
+ delete pstrName;
+ if (SUCCEEDED(hrc2))
+ {
+ m->llInstalledExtPacks.push_back(NewExtPack);
+ /* Paranoia, there should be no API clients before this method is finished. */
+
+ m->cUpdate++;
+ }
+ else if (SUCCEEDED(hrc))
+ hrc = hrc2;
+ }
+ else
+ hrc = E_UNEXPECTED;
+ }
+ else
+ hrc = E_UNEXPECTED;
+ }
+ }
+ RTDirClose(hDir);
+ }
+ /* else: ignore, the directory probably does not exist or something. */
+
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ return hrc;
+}
+
+/**
+ * COM cruft.
+ */
+void ExtPackManager::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+/**
+ * Do the actual cleanup.
+ */
+void ExtPackManager::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (!autoUninitSpan.uninitDone() && m != NULL)
+ {
+ delete m;
+ m = NULL;
+ }
+}
+
+HRESULT ExtPackManager::getInstalledExtPacks(std::vector<ComPtr<IExtPack> > &aInstalledExtPacks)
+{
+ Assert(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON);
+
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ aInstalledExtPacks.resize(m->llInstalledExtPacks.size());
+ std::copy(m->llInstalledExtPacks.begin(), m->llInstalledExtPacks.end(), aInstalledExtPacks.begin());
+
+ return S_OK;
+}
+
+HRESULT ExtPackManager::find(const com::Utf8Str &aName, ComPtr<IExtPack> &aReturnData)
+{
+ HRESULT hrc = S_OK;
+
+ Assert(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON);
+
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComPtr<ExtPack> ptrExtPack = i_findExtPack(aName.c_str());
+ if (!ptrExtPack.isNull())
+ ptrExtPack.queryInterfaceTo(aReturnData.asOutParam());
+ else
+ hrc = VBOX_E_OBJECT_NOT_FOUND;
+
+ return hrc;
+}
+
+HRESULT ExtPackManager::openExtPackFile(const com::Utf8Str &aPath, ComPtr<IExtPackFile> &aFile)
+{
+ AssertReturn(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON, E_UNEXPECTED);
+
+#ifndef VBOX_COM_INPROC
+ /* The API can optionally take a ::SHA-256=<hex-digest> attribute at the
+ end of the file name. This is just a temporary measure for
+ backporting, in 4.2 we'll add another parameter to the method. */
+ Utf8Str strTarball;
+ Utf8Str strDigest;
+ size_t offSha256 = aPath.find("::SHA-256=");
+ if (offSha256 == Utf8Str::npos)
+ strTarball = aPath;
+ else
+ {
+ strTarball = aPath.substr(0, offSha256);
+ strDigest = aPath.substr(offSha256 + sizeof("::SHA-256=") - 1);
+ }
+
+ ComObjPtr<ExtPackFile> NewExtPackFile;
+ HRESULT hrc = NewExtPackFile.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = NewExtPackFile->initWithFile(strTarball.c_str(), strDigest.c_str(), this, m->pVirtualBox);
+ if (SUCCEEDED(hrc))
+ NewExtPackFile.queryInterfaceTo(aFile.asOutParam());
+
+ return hrc;
+#else
+ RT_NOREF(aPath, aFile);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT ExtPackManager::uninstall(const com::Utf8Str &aName, BOOL aForcedRemoval,
+ const com::Utf8Str &aDisplayInfo, ComPtr<IProgress> &aProgress)
+{
+ Assert(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON);
+
+#ifndef VBOX_COM_INPROC
+
+ HRESULT hrc;
+ ExtPackUninstallTask *pTask = NULL;
+ try
+ {
+ pTask = new ExtPackUninstallTask();
+ hrc = pTask->Init(this, aName, aForcedRemoval != FALSE, aDisplayInfo);
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<Progress> ptrProgress = pTask->ptrProgress;
+ hrc = pTask->createThreadWithType(RTTHREADTYPE_DEFAULT);
+ pTask = NULL; /* always consumed by createThread */
+ if (SUCCEEDED(hrc))
+ hrc = ptrProgress.queryInterfaceTo(aProgress.asOutParam());
+ else
+ hrc = setError(VBOX_E_IPRT_ERROR,
+ tr("Starting thread for an extension pack uninstallation failed with %Rrc"), hrc);
+ }
+ else
+ hrc = setError(hrc, tr("Looks like creating a progress object for ExtraPackUninstallTask object failed"));
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ catch (HRESULT hrcXcpt)
+ {
+ LogFlowThisFunc(("Exception was caught in the function ExtPackManager::uninstall()\n"));
+ hrc = hrcXcpt;
+ }
+ if (pTask)
+ delete pTask;
+ return hrc;
+#else
+ RT_NOREF(aName, aForcedRemoval, aDisplayInfo, aProgress);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT ExtPackManager::cleanup(void)
+{
+ Assert(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Run the set-uid-to-root binary that performs the cleanup.
+ *
+ * Take the write lock to prevent conflicts with other calls to this
+ * VBoxSVC instance.
+ */
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ hrc = i_runSetUidToRootHelper(NULL,
+ "cleanup",
+ "--base-dir", m->strBaseDir.c_str(),
+ (const char *)NULL);
+ }
+
+ return hrc;
+}
+
+HRESULT ExtPackManager::queryAllPlugInsForFrontend(const com::Utf8Str &aFrontendName, std::vector<com::Utf8Str> &aPlugInModules)
+{
+ NOREF(aFrontendName);
+ aPlugInModules.resize(0);
+ return S_OK;
+}
+
+HRESULT ExtPackManager::isExtPackUsable(const com::Utf8Str &aName, BOOL *aUsable)
+{
+ *aUsable = i_isExtPackUsable(aName.c_str());
+ return S_OK;
+}
+
+/**
+ * Finds the success indicator string in the stderr output ofr hte helper app.
+ *
+ * @returns Pointer to the indicator.
+ * @param psz The stderr output string. Can be NULL.
+ * @param cch The size of the string.
+ */
+static char *findSuccessIndicator(char *psz, size_t cch)
+{
+ static const char s_szSuccessInd[] = "rcExit=RTEXITCODE_SUCCESS";
+ Assert(!cch || strlen(psz) == cch);
+ if (cch < sizeof(s_szSuccessInd) - 1)
+ return NULL;
+ char *pszInd = &psz[cch - sizeof(s_szSuccessInd) + 1];
+ if (strcmp(s_szSuccessInd, pszInd))
+ return NULL;
+ return pszInd;
+}
+
+/**
+ * Runs the helper application that does the privileged operations.
+ *
+ * @returns S_OK or a failure status with error information set.
+ * @param a_pstrDisplayInfo Platform specific display info hacks.
+ * @param a_pszCommand The command to execute.
+ * @param ... The argument strings that goes along with the
+ * command. Maximum is about 16. Terminated by a
+ * NULL.
+ */
+HRESULT ExtPackManager::i_runSetUidToRootHelper(Utf8Str const *a_pstrDisplayInfo, const char *a_pszCommand, ...)
+{
+ /*
+ * Calculate the path to the helper application.
+ */
+ char szExecName[RTPATH_MAX];
+ int vrc = RTPathAppPrivateArch(szExecName, sizeof(szExecName));
+ AssertLogRelRCReturn(vrc, E_UNEXPECTED);
+
+ vrc = RTPathAppend(szExecName, sizeof(szExecName), VBOX_EXTPACK_HELPER_NAME);
+ AssertLogRelRCReturn(vrc, E_UNEXPECTED);
+
+ /*
+ * Convert the variable argument list to a RTProcCreate argument vector.
+ */
+ const char *apszArgs[20];
+ unsigned cArgs = 0;
+
+ LogRel(("ExtPack: Executing '%s'", szExecName));
+ apszArgs[cArgs++] = &szExecName[0];
+
+ if ( a_pstrDisplayInfo
+ && a_pstrDisplayInfo->isNotEmpty())
+ {
+ LogRel((" '--display-info-hack' '%s'", a_pstrDisplayInfo->c_str()));
+ apszArgs[cArgs++] = "--display-info-hack";
+ apszArgs[cArgs++] = a_pstrDisplayInfo->c_str();
+ }
+
+ LogRel((" '%s'", a_pszCommand));
+ apszArgs[cArgs++] = a_pszCommand;
+
+ va_list va;
+ va_start(va, a_pszCommand);
+ const char *pszLastArg;
+ for (;;)
+ {
+ AssertReturn(cArgs < RT_ELEMENTS(apszArgs) - 1, E_UNEXPECTED);
+ pszLastArg = va_arg(va, const char *);
+ if (!pszLastArg)
+ break;
+ LogRel((" '%s'", pszLastArg));
+ apszArgs[cArgs++] = pszLastArg;
+ };
+ va_end(va);
+
+ LogRel(("\n"));
+ apszArgs[cArgs] = NULL;
+
+ /*
+ * Create a PIPE which we attach to stderr so that we can read the error
+ * message on failure and report it back to the caller.
+ */
+ RTPIPE hPipeR;
+ RTHANDLE hStdErrPipe;
+ hStdErrPipe.enmType = RTHANDLETYPE_PIPE;
+ vrc = RTPipeCreate(&hPipeR, &hStdErrPipe.u.hPipe, RTPIPE_C_INHERIT_WRITE);
+ AssertLogRelRCReturn(vrc, E_UNEXPECTED);
+
+ /*
+ * Spawn the process.
+ */
+ HRESULT hrc;
+ RTPROCESS hProcess;
+ vrc = RTProcCreateEx(szExecName,
+ apszArgs,
+ RTENV_DEFAULT,
+ 0 /*fFlags*/,
+ NULL /*phStdIn*/,
+ NULL /*phStdOut*/,
+ &hStdErrPipe,
+ NULL /*pszAsUser*/,
+ NULL /*pszPassword*/,
+ NULL /*pvExtraData*/,
+ &hProcess);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPipeClose(hStdErrPipe.u.hPipe);
+ hStdErrPipe.u.hPipe = NIL_RTPIPE;
+
+ /*
+ * Read the pipe output until the process completes.
+ */
+ RTPROCSTATUS ProcStatus = { -42, RTPROCEXITREASON_ABEND };
+ size_t cbStdErrBuf = 0;
+ size_t offStdErrBuf = 0;
+ char *pszStdErrBuf = NULL;
+ do
+ {
+ /*
+ * Service the pipe. Block waiting for output or the pipe breaking
+ * when the process terminates.
+ */
+ if (hPipeR != NIL_RTPIPE)
+ {
+ char achBuf[1024];
+ size_t cbRead;
+ vrc = RTPipeReadBlocking(hPipeR, achBuf, sizeof(achBuf), &cbRead);
+ if (RT_SUCCESS(vrc))
+ {
+ /* grow the buffer? */
+ size_t cbBufReq = offStdErrBuf + cbRead + 1;
+ if ( cbBufReq > cbStdErrBuf
+ && cbBufReq < _256K)
+ {
+ size_t cbNew = RT_ALIGN_Z(cbBufReq, 16); // 1024
+ void *pvNew = RTMemRealloc(pszStdErrBuf, cbNew);
+ if (pvNew)
+ {
+ pszStdErrBuf = (char *)pvNew;
+ cbStdErrBuf = cbNew;
+ }
+ }
+
+ /* append if we've got room. */
+ if (cbBufReq <= cbStdErrBuf)
+ {
+ memcpy(&pszStdErrBuf[offStdErrBuf], achBuf, cbRead);
+ offStdErrBuf = offStdErrBuf + cbRead;
+ pszStdErrBuf[offStdErrBuf] = '\0';
+ }
+ }
+ else
+ {
+ AssertLogRelMsg(vrc == VERR_BROKEN_PIPE, ("%Rrc\n", vrc));
+ RTPipeClose(hPipeR);
+ hPipeR = NIL_RTPIPE;
+ }
+ }
+
+ /*
+ * Service the process. Block if we have no pipe.
+ */
+ if (hProcess != NIL_RTPROCESS)
+ {
+ vrc = RTProcWait(hProcess,
+ hPipeR == NIL_RTPIPE ? RTPROCWAIT_FLAGS_BLOCK : RTPROCWAIT_FLAGS_NOBLOCK,
+ &ProcStatus);
+ if (RT_SUCCESS(vrc))
+ hProcess = NIL_RTPROCESS;
+ else
+ AssertLogRelMsgStmt(vrc == VERR_PROCESS_RUNNING, ("%Rrc\n", vrc), hProcess = NIL_RTPROCESS);
+ }
+ } while ( hPipeR != NIL_RTPIPE
+ || hProcess != NIL_RTPROCESS);
+
+ LogRel(("ExtPack: enmReason=%d iStatus=%d stderr='%s'\n",
+ ProcStatus.enmReason, ProcStatus.iStatus, offStdErrBuf ? pszStdErrBuf : ""));
+
+ /*
+ * Look for rcExit=RTEXITCODE_SUCCESS at the end of the error output,
+ * cut it as it is only there to attest the success.
+ */
+ if (offStdErrBuf > 0)
+ {
+ RTStrStripR(pszStdErrBuf);
+ offStdErrBuf = strlen(pszStdErrBuf);
+ }
+
+ char *pszSuccessInd = findSuccessIndicator(pszStdErrBuf, offStdErrBuf);
+ if (pszSuccessInd)
+ {
+ *pszSuccessInd = '\0';
+ offStdErrBuf = (size_t)(pszSuccessInd - pszStdErrBuf);
+ }
+ else if ( ProcStatus.enmReason == RTPROCEXITREASON_NORMAL
+ && ProcStatus.iStatus == 0)
+ ProcStatus.iStatus = offStdErrBuf ? 667 : 666;
+
+ /*
+ * Compose the status code and, on failure, error message.
+ */
+ if ( ProcStatus.enmReason == RTPROCEXITREASON_NORMAL
+ && ProcStatus.iStatus == 0)
+ hrc = S_OK;
+ else if (ProcStatus.enmReason == RTPROCEXITREASON_NORMAL)
+ {
+ AssertMsg(ProcStatus.iStatus != 0, ("%s\n", pszStdErrBuf));
+ hrc = setError(E_FAIL, tr("The installer failed with exit code %d: %s"),
+ ProcStatus.iStatus, offStdErrBuf ? pszStdErrBuf : "");
+ }
+ else if (ProcStatus.enmReason == RTPROCEXITREASON_SIGNAL)
+ hrc = setError(E_UNEXPECTED, tr("The installer was killed by signal #d (stderr: %s)"),
+ ProcStatus.iStatus, offStdErrBuf ? pszStdErrBuf : "");
+ else if (ProcStatus.enmReason == RTPROCEXITREASON_ABEND)
+ hrc = setError(E_UNEXPECTED, tr("The installer aborted abnormally (stderr: %s)"),
+ offStdErrBuf ? pszStdErrBuf : "");
+ else
+ hrc = setError(E_UNEXPECTED, tr("internal error: enmReason=%d iStatus=%d stderr='%s'"),
+ ProcStatus.enmReason, ProcStatus.iStatus, offStdErrBuf ? pszStdErrBuf : "");
+
+ RTMemFree(pszStdErrBuf);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to launch the helper application '%s' (%Rrc)"), szExecName, vrc);
+
+ RTPipeClose(hPipeR);
+ RTPipeClose(hStdErrPipe.u.hPipe);
+
+ return hrc;
+}
+
+/**
+ * Finds an installed extension pack.
+ *
+ * @returns Pointer to the extension pack if found, NULL if not. (No reference
+ * counting problem here since the caller must be holding the lock.)
+ * @param a_pszName The name of the extension pack.
+ */
+ExtPack *ExtPackManager::i_findExtPack(const char *a_pszName)
+{
+ size_t cchName = strlen(a_pszName);
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ ++it)
+ {
+ ExtPack::Data *pExtPackData = (*it)->m;
+ if ( pExtPackData
+ && pExtPackData->Desc.strName.length() == cchName
+ && pExtPackData->Desc.strName.equalsIgnoreCase(a_pszName))
+ return (*it);
+ }
+ return NULL;
+}
+
+/**
+ * Removes an installed extension pack from the internal list.
+ *
+ * The package is expected to exist!
+ *
+ * @param a_pszName The name of the extension pack.
+ */
+void ExtPackManager::i_removeExtPack(const char *a_pszName)
+{
+ size_t cchName = strlen(a_pszName);
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ ++it)
+ {
+ ExtPack::Data *pExtPackData = (*it)->m;
+ if ( pExtPackData
+ && pExtPackData->Desc.strName.length() == cchName
+ && pExtPackData->Desc.strName.equalsIgnoreCase(a_pszName))
+ {
+ m->llInstalledExtPacks.erase(it);
+ m->cUpdate++;
+ return;
+ }
+ }
+ AssertMsgFailed(("%s\n", a_pszName));
+}
+
+#ifndef VBOX_COM_INPROC
+
+/**
+ * Refreshes the specified extension pack.
+ *
+ * This may remove the extension pack from the list, so any non-smart pointers
+ * to the extension pack object may become invalid.
+ *
+ * @returns S_OK and *a_ppExtPack on success, COM status code and error
+ * message on failure. Note that *a_ppExtPack can be NULL.
+ *
+ * @param a_pszName The extension to update..
+ * @param a_fUnusableIsError If @c true, report an unusable extension pack
+ * as an error.
+ * @param a_ppExtPack Where to store the pointer to the extension
+ * pack of it is still around after the refresh.
+ * This is optional.
+ *
+ * @remarks Caller holds the extension manager lock.
+ * @remarks Only called in VBoxSVC.
+ */
+HRESULT ExtPackManager::i_refreshExtPack(const char *a_pszName, bool a_fUnusableIsError, ExtPack **a_ppExtPack)
+{
+ Assert(m->pVirtualBox != NULL); /* Only called from VBoxSVC. */
+
+ HRESULT hrc;
+ ExtPack *pExtPack = i_findExtPack(a_pszName);
+ if (pExtPack)
+ {
+ /*
+ * Refresh existing object.
+ */
+ bool fCanDelete;
+ hrc = pExtPack->i_refresh(&fCanDelete);
+ if (SUCCEEDED(hrc))
+ {
+ if (fCanDelete)
+ {
+ i_removeExtPack(a_pszName);
+ pExtPack = NULL;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Do this check here, otherwise VBoxExtPackCalcDir() will fail with a strange
+ * error.
+ */
+ bool fValid = VBoxExtPackIsValidName(a_pszName);
+ if (!fValid)
+ return setError(E_FAIL, "Invalid extension pack name specified");
+
+ /*
+ * Does the dir exist? Make some special effort to deal with case
+ * sensitivie file systems (a_pszName is case insensitive and mangled).
+ */
+ char szDir[RTPATH_MAX];
+ int vrc = VBoxExtPackCalcDir(szDir, sizeof(szDir), m->strBaseDir.c_str(), a_pszName);
+ AssertLogRelRCReturn(vrc, E_FAIL);
+
+ RTDIRENTRYEX Entry;
+ RTFSOBJINFO ObjInfo;
+ vrc = RTPathQueryInfoEx(szDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ bool fExists = RT_SUCCESS(vrc) && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode);
+ if (!fExists)
+ {
+ RTDIR hDir;
+ vrc = RTDirOpen(&hDir, m->strBaseDir.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ const char *pszMangledName = RTPathFilename(szDir);
+ for (;;)
+ {
+ vrc = RTDirReadEx(hDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ {
+ AssertLogRelMsg(vrc == VERR_NO_MORE_FILES, ("%Rrc\n", vrc));
+ break;
+ }
+ if ( RTFS_IS_DIRECTORY(Entry.Info.Attr.fMode)
+ && !RTStrICmp(Entry.szName, pszMangledName))
+ {
+ /*
+ * The installed extension pack has a uses different case.
+ * Update the name and directory variables.
+ */
+ vrc = RTPathJoin(szDir, sizeof(szDir), m->strBaseDir.c_str(), Entry.szName); /* not really necessary */
+ AssertLogRelRCReturnStmt(vrc, RTDirClose(hDir), E_UNEXPECTED);
+ a_pszName = Entry.szName;
+ fExists = true;
+ break;
+ }
+ }
+ RTDirClose(hDir);
+ }
+ }
+ if (fExists)
+ {
+ /*
+ * We've got something, create a new extension pack object for it.
+ */
+ ComObjPtr<ExtPack> ptrNewExtPack;
+ hrc = ptrNewExtPack.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = ptrNewExtPack->initWithDir(m->pVirtualBox, m->enmContext, a_pszName, szDir);
+ if (SUCCEEDED(hrc))
+ {
+ m->llInstalledExtPacks.push_back(ptrNewExtPack);
+ m->cUpdate++;
+ if (ptrNewExtPack->m->fUsable)
+ LogRel(("ExtPackManager: Found extension pack '%s'.\n", a_pszName));
+ else
+ LogRel(("ExtPackManager: Found bad extension pack '%s': %s\n",
+ a_pszName, ptrNewExtPack->m->strWhyUnusable.c_str() ));
+ pExtPack = ptrNewExtPack;
+ }
+ }
+ else
+ hrc = S_OK;
+ }
+
+ /*
+ * Report error if not usable, if that is desired.
+ */
+ if ( SUCCEEDED(hrc)
+ && pExtPack
+ && a_fUnusableIsError
+ && !pExtPack->m->fUsable)
+ hrc = setError(E_FAIL, "%s", pExtPack->m->strWhyUnusable.c_str());
+
+ if (a_ppExtPack)
+ *a_ppExtPack = pExtPack;
+ return hrc;
+}
+
+/**
+ * Checks if there are any running VMs.
+ *
+ * This is called when uninstalling or replacing an extension pack.
+ *
+ * @returns true / false
+ */
+bool ExtPackManager::i_areThereAnyRunningVMs(void) const
+{
+ Assert(m->pVirtualBox != NULL); /* Only called from VBoxSVC. */
+
+ /*
+ * Get list of machines and their states.
+ */
+ com::SafeIfaceArray<IMachine> SaMachines;
+ HRESULT hrc = m->pVirtualBox->COMGETTER(Machines)(ComSafeArrayAsOutParam(SaMachines));
+ if (SUCCEEDED(hrc))
+ {
+ com::SafeArray<MachineState_T> SaStates;
+ hrc = m->pVirtualBox->GetMachineStates(ComSafeArrayAsInParam(SaMachines), ComSafeArrayAsOutParam(SaStates));
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Scan the two parallel arrays for machines in the running state.
+ */
+ Assert(SaStates.size() == SaMachines.size());
+ for (size_t i = 0; i < SaMachines.size(); ++i)
+ if (SaMachines[i] && Global::IsOnline(SaStates[i]))
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Worker for IExtPackFile::Install.
+ *
+ * Called on a worker thread via doInstallThreadProc.
+ *
+ * @returns COM status code.
+ * @param a_pExtPackFile The extension pack file, caller checks that
+ * it's usable.
+ * @param a_fReplace Whether to replace any existing extpack or just
+ * fail.
+ * @param a_pstrDisplayInfo Host specific display information hacks.
+ * be NULL.
+ */
+HRESULT ExtPackManager::i_doInstall(ExtPackFile *a_pExtPackFile, bool a_fReplace, Utf8Str const *a_pstrDisplayInfo)
+{
+ AssertReturn(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON, E_UNEXPECTED);
+ RTCString const * const pStrName = &a_pExtPackFile->m->Desc.strName;
+ RTCString const * const pStrTarball = &a_pExtPackFile->m->strExtPackFile;
+ RTCString const * const pStrTarballDigest = &a_pExtPackFile->m->strDigest;
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Refresh the data we have on the extension pack as it
+ * may be made stale by direct meddling or some other user.
+ */
+ ExtPack *pExtPack;
+ hrc = i_refreshExtPack(pStrName->c_str(), false /*a_fUnusableIsError*/, &pExtPack);
+ if (SUCCEEDED(hrc))
+ {
+ if (pExtPack && a_fReplace)
+ {
+ /* We must leave the lock when calling i_areThereAnyRunningVMs,
+ which means we have to redo the refresh call afterwards. */
+ autoLock.release();
+ bool fRunningVMs = i_areThereAnyRunningVMs();
+ bool fVetoingCP = pExtPack->i_areThereCloudProviderUninstallVetos();
+ bool fUnloadedCryptoMod = m->pVirtualBox->i_unloadCryptoIfModule() == S_OK;
+ autoLock.acquire();
+ hrc = i_refreshExtPack(pStrName->c_str(), false /*a_fUnusableIsError*/, &pExtPack);
+ if (fRunningVMs)
+ {
+ LogRel(("Upgrading extension pack '%s' failed because at least one VM is still running.", pStrName->c_str()));
+ hrc = setError(E_FAIL, tr("Upgrading extension pack '%s' failed because at least one VM is still running"),
+ pStrName->c_str());
+ }
+ else if (fVetoingCP)
+ {
+ LogRel(("Upgrading extension pack '%s' failed because at least one Cloud Provider is still busy.", pStrName->c_str()));
+ hrc = setError(E_FAIL, tr("Upgrading extension pack '%s' failed because at least one Cloud Provider is still busy"),
+ pStrName->c_str());
+ }
+ else if (!fUnloadedCryptoMod)
+ {
+ LogRel(("Upgrading extension pack '%s' failed because the cryptographic support module is still in use.", pStrName->c_str()));
+ hrc = setError(E_FAIL, tr("Upgrading extension pack '%s' failed because the cryptographic support module is still in use"),
+ pStrName->c_str());
+ }
+ else if (SUCCEEDED(hrc) && pExtPack)
+ hrc = pExtPack->i_callUninstallHookAndClose(m->pVirtualBox, false /*a_ForcedRemoval*/);
+ }
+ else if (pExtPack)
+ hrc = setError(E_FAIL,
+ tr("Extension pack '%s' is already installed."
+ " In case of a reinstallation, please uninstall it first"),
+ pStrName->c_str());
+ }
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Run the privileged helper binary that performs the actual
+ * installation. Then create an object for the packet (we do this
+ * even on failure, to be on the safe side).
+ */
+ hrc = i_runSetUidToRootHelper(a_pstrDisplayInfo,
+ "install",
+ "--base-dir", m->strBaseDir.c_str(),
+ "--cert-dir", m->strCertificatDirPath.c_str(),
+ "--name", pStrName->c_str(),
+ "--tarball", pStrTarball->c_str(),
+ "--sha-256", pStrTarballDigest->c_str(),
+ pExtPack ? "--replace" : (const char *)NULL,
+ (const char *)NULL);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = i_refreshExtPack(pStrName->c_str(), true /*a_fUnusableIsError*/, &pExtPack);
+ if (SUCCEEDED(hrc) && pExtPack)
+ {
+ RTERRINFOSTATIC ErrInfo;
+ RTErrInfoInitStatic(&ErrInfo);
+ pExtPack->i_callInstalledHook(m->pVirtualBox, &autoLock, &ErrInfo.Core);
+ if (RT_SUCCESS(ErrInfo.Core.rc))
+ LogRel(("ExtPackManager: Successfully installed extension pack '%s'.\n", pStrName->c_str()));
+ else
+ {
+ LogRel(("ExtPackManager: Installed hook for '%s' failed: %Rrc - %s\n",
+ pStrName->c_str(), ErrInfo.Core.rc, ErrInfo.Core.pszMsg));
+
+ /*
+ * Uninstall the extpack if the error indicates that.
+ */
+ if (ErrInfo.Core.rc == VERR_EXTPACK_UNSUPPORTED_HOST_UNINSTALL)
+ i_runSetUidToRootHelper(a_pstrDisplayInfo,
+ "uninstall",
+ "--base-dir", m->strBaseDir.c_str(),
+ "--name", pStrName->c_str(),
+ "--forced",
+ (const char *)NULL);
+ hrc = setErrorBoth(E_FAIL, ErrInfo.Core.rc, tr("The installation hook failed: %Rrc - %s"),
+ ErrInfo.Core.rc, ErrInfo.Core.pszMsg);
+ }
+ }
+ else if (SUCCEEDED(hrc))
+ hrc = setError(E_FAIL, tr("Installing extension pack '%s' failed under mysterious circumstances"),
+ pStrName->c_str());
+ }
+ else
+ {
+ ErrorInfoKeeper Eik;
+ i_refreshExtPack(pStrName->c_str(), false /*a_fUnusableIsError*/, NULL);
+ }
+ }
+
+ /*
+ * Do VirtualBoxReady callbacks now for any freshly installed
+ * extension pack (old ones will not be called).
+ */
+ if (m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON)
+ {
+ autoLock.release();
+ i_callAllVirtualBoxReadyHooks();
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * Worker for IExtPackManager::Uninstall.
+ *
+ * Called on a worker thread via doUninstallThreadProc.
+ *
+ * @returns COM status code.
+ * @param a_pstrName The name of the extension pack to uninstall.
+ * @param a_fForcedRemoval Whether to be skip and ignore certain bits of
+ * the extpack feedback. To deal with misbehaving
+ * extension pack hooks.
+ * @param a_pstrDisplayInfo Host specific display information hacks.
+ */
+HRESULT ExtPackManager::i_doUninstall(Utf8Str const *a_pstrName, bool a_fForcedRemoval, Utf8Str const *a_pstrDisplayInfo)
+{
+ Assert(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Refresh the data we have on the extension pack as it
+ * may be made stale by direct meddling or some other user.
+ */
+ ExtPack *pExtPack;
+ hrc = i_refreshExtPack(a_pstrName->c_str(), false /*a_fUnusableIsError*/, &pExtPack);
+ if (SUCCEEDED(hrc) && pExtPack)
+ {
+ /* We must leave the lock when calling i_areThereAnyRunningVMs,
+ which means we have to redo the refresh call afterwards. */
+ autoLock.release();
+ bool fRunningVMs = i_areThereAnyRunningVMs();
+ bool fVetoingCP = pExtPack->i_areThereCloudProviderUninstallVetos();
+ bool fUnloadedCryptoMod = m->pVirtualBox->i_unloadCryptoIfModule() == S_OK;
+ autoLock.acquire();
+ if (a_fForcedRemoval || (!fRunningVMs && !fVetoingCP && fUnloadedCryptoMod))
+ {
+ hrc = i_refreshExtPack(a_pstrName->c_str(), false /*a_fUnusableIsError*/, &pExtPack);
+ if (SUCCEEDED(hrc))
+ {
+ if (!pExtPack)
+ {
+ LogRel(("ExtPackManager: Extension pack '%s' is not installed, so nothing to uninstall.\n", a_pstrName->c_str()));
+ hrc = S_OK; /* nothing to uninstall */
+ }
+ else
+ {
+ /*
+ * Call the uninstall hook and unload the main dll.
+ */
+ hrc = pExtPack->i_callUninstallHookAndClose(m->pVirtualBox, a_fForcedRemoval);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Run the set-uid-to-root binary that performs the
+ * uninstallation. Then refresh the object.
+ *
+ * This refresh is theorically subject to races, but it's of
+ * the don't-do-that variety.
+ */
+ const char *pszForcedOpt = a_fForcedRemoval ? "--forced" : NULL;
+ hrc = i_runSetUidToRootHelper(a_pstrDisplayInfo,
+ "uninstall",
+ "--base-dir", m->strBaseDir.c_str(),
+ "--name", a_pstrName->c_str(),
+ pszForcedOpt, /* Last as it may be NULL. */
+ (const char *)NULL);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = i_refreshExtPack(a_pstrName->c_str(), false /*a_fUnusableIsError*/, &pExtPack);
+ if (SUCCEEDED(hrc))
+ {
+ if (!pExtPack)
+ LogRel(("ExtPackManager: Successfully uninstalled extension pack '%s'.\n", a_pstrName->c_str()));
+ else
+ hrc = setError(E_FAIL,
+ tr("Uninstall extension pack '%s' failed under mysterious circumstances"),
+ a_pstrName->c_str());
+ }
+ }
+ else
+ {
+ ErrorInfoKeeper Eik;
+ i_refreshExtPack(a_pstrName->c_str(), false /*a_fUnusableIsError*/, NULL);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ if (fRunningVMs)
+ {
+ LogRel(("Uninstall extension pack '%s' failed because at least one VM is still running.", a_pstrName->c_str()));
+ hrc = setError(E_FAIL, tr("Uninstall extension pack '%s' failed because at least one VM is still running"),
+ a_pstrName->c_str());
+ }
+ else if (fVetoingCP)
+ {
+ LogRel(("Uninstall extension pack '%s' failed because at least one Cloud Provider is still busy.", a_pstrName->c_str()));
+ hrc = setError(E_FAIL, tr("Uninstall extension pack '%s' failed because at least one Cloud Provider is still busy"),
+ a_pstrName->c_str());
+ }
+ else if (!fUnloadedCryptoMod)
+ {
+ LogRel(("Uninstall extension pack '%s' failed because the cryptographic support module is still in use.", a_pstrName->c_str()));
+ hrc = setError(E_FAIL, tr("Uninstall extension pack '%s' failed because the cryptographic support module is still in use"),
+ a_pstrName->c_str());
+ }
+ else
+ {
+ LogRel(("Uninstall extension pack '%s' failed for an unknown reason.", a_pstrName->c_str()));
+ hrc = setError(E_FAIL, tr("Uninstall extension pack '%s' failed for an unknown reason"),
+ a_pstrName->c_str());
+
+ }
+ }
+ }
+ else if (SUCCEEDED(hrc) && !pExtPack)
+ {
+ hrc = setError(E_FAIL, tr("Extension pack '%s' is not installed.\n"), a_pstrName->c_str());
+ }
+
+ /*
+ * Do VirtualBoxReady callbacks now for any freshly installed
+ * extension pack (old ones will not be called).
+ */
+ if (m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON)
+ {
+ autoLock.release();
+ i_callAllVirtualBoxReadyHooks();
+ }
+ }
+
+ return hrc;
+}
+
+
+/**
+ * Calls the pfnVirtualBoxReady hook for all working extension packs.
+ *
+ * @remarks The caller must not hold any locks.
+ */
+void ExtPackManager::i_callAllVirtualBoxReadyHooks(void)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (FAILED(hrc))
+ return;
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this;
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ /* advancing below */)
+ {
+ if ((*it)->i_callVirtualBoxReadyHook(m->pVirtualBox, &autoLock))
+ it = m->llInstalledExtPacks.begin();
+ else
+ ++it;
+ }
+}
+
+
+/**
+ * Queries objects of type @a aObjUuid from all the extension packs.
+ *
+ * @returns COM status code.
+ * @param aObjUuid The UUID of the kind of objects we're querying.
+ * @param aObjects Where to return the objects.
+ * @param a_pstrExtPackNames Where to return the corresponding extpack names (may be NULL).
+ *
+ * @remarks The caller must not hold any locks.
+ */
+HRESULT ExtPackManager::i_queryObjects(const com::Utf8Str &aObjUuid, std::vector<ComPtr<IUnknown> > &aObjects, std::vector<com::Utf8Str> *a_pstrExtPackNames)
+{
+ aObjects.clear();
+ if (a_pstrExtPackNames)
+ a_pstrExtPackNames->clear();
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this;
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ ++it)
+ {
+ ComPtr<IUnknown> ptrIf;
+ HRESULT hrc2 = (*it)->queryObject(aObjUuid, ptrIf);
+ if (SUCCEEDED(hrc2))
+ {
+ aObjects.push_back(ptrIf);
+ if (a_pstrExtPackNames)
+ a_pstrExtPackNames->push_back((*it)->m->Desc.strName);
+ }
+ else if (hrc2 != E_NOINTERFACE)
+ hrc = hrc2;
+ }
+
+ if (aObjects.size() > 0)
+ hrc = S_OK;
+ }
+ return hrc;
+}
+
+#endif /* !VBOX_COM_INPROC */
+
+#ifdef VBOX_COM_INPROC
+/**
+ * Calls the pfnConsoleReady hook for all working extension packs.
+ *
+ * @param a_pConsole The console interface.
+ * @remarks The caller must not hold any locks.
+ */
+void ExtPackManager::i_callAllConsoleReadyHooks(IConsole *a_pConsole)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (FAILED(hrc))
+ return;
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this;
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ /* advancing below */)
+ {
+ if ((*it)->i_callConsoleReadyHook(a_pConsole, &autoLock))
+ it = m->llInstalledExtPacks.begin();
+ else
+ ++it;
+ }
+}
+#endif
+
+#ifndef VBOX_COM_INPROC
+/**
+ * Calls the pfnVMCreated hook for all working extension packs.
+ *
+ * @param a_pMachine The machine interface of the new VM.
+ */
+void ExtPackManager::i_callAllVmCreatedHooks(IMachine *a_pMachine)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (FAILED(hrc))
+ return;
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this; /* paranoia */
+ ExtPackList llExtPacks = m->llInstalledExtPacks;
+
+ for (ExtPackList::iterator it = llExtPacks.begin(); it != llExtPacks.end(); ++it)
+ (*it)->i_callVmCreatedHook(m->pVirtualBox, a_pMachine, &autoLock);
+}
+#endif
+
+#ifdef VBOX_COM_INPROC
+
+/**
+ * Calls the pfnVMConfigureVMM hook for all working extension packs.
+ *
+ * @returns VBox status code. Stops on the first failure, expecting the caller
+ * to signal this to the caller of the CFGM constructor.
+ * @param a_pConsole The console interface for the VM.
+ * @param a_pVM The VM handle.
+ * @param a_pVMM The VMM function table.
+ */
+int ExtPackManager::i_callAllVmConfigureVmmHooks(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (FAILED(hrc))
+ return Global::vboxStatusCodeFromCOM(hrc);
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this; /* paranoia */
+ ExtPackList llExtPacks = m->llInstalledExtPacks;
+
+ for (ExtPackList::iterator it = llExtPacks.begin(); it != llExtPacks.end(); ++it)
+ {
+ int vrc;
+ (*it)->i_callVmConfigureVmmHook(a_pConsole, a_pVM, a_pVMM, &autoLock, &vrc);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Calls the pfnVMPowerOn hook for all working extension packs.
+ *
+ * @returns VBox status code. Stops on the first failure, expecting the caller
+ * to not power on the VM.
+ * @param a_pConsole The console interface for the VM.
+ * @param a_pVM The VM handle.
+ * @param a_pVMM The VMM function table.
+ */
+int ExtPackManager::i_callAllVmPowerOnHooks(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (FAILED(hrc))
+ return Global::vboxStatusCodeFromCOM(hrc);
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this; /* paranoia */
+ ExtPackList llExtPacks = m->llInstalledExtPacks;
+
+ for (ExtPackList::iterator it = llExtPacks.begin(); it != llExtPacks.end(); ++it)
+ {
+ int vrc;
+ (*it)->i_callVmPowerOnHook(a_pConsole, a_pVM, a_pVMM, &autoLock, &vrc);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Calls the pfnVMPowerOff hook for all working extension packs.
+ *
+ * @param a_pConsole The console interface for the VM.
+ * @param a_pVM The VM handle. Can be NULL.
+ * @param a_pVMM The VMM function table.
+ */
+void ExtPackManager::i_callAllVmPowerOffHooks(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (FAILED(hrc))
+ return;
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this; /* paranoia */
+ ExtPackList llExtPacks = m->llInstalledExtPacks;
+
+ for (ExtPackList::iterator it = llExtPacks.begin(); it != llExtPacks.end(); ++it)
+ (*it)->i_callVmPowerOffHook(a_pConsole, a_pVM, a_pVMM, &autoLock);
+}
+
+#endif /* VBOX_COM_INPROC */
+
+/**
+ * Checks that the specified extension pack contains a VRDE module and that it
+ * is shipshape.
+ *
+ * @returns S_OK if ok, appropriate failure status code with details.
+ * @param a_pstrExtPack The name of the extension pack.
+ */
+HRESULT ExtPackManager::i_checkVrdeExtPack(Utf8Str const *a_pstrExtPack)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ExtPack *pExtPack = i_findExtPack(a_pstrExtPack->c_str());
+ if (pExtPack)
+ hrc = pExtPack->i_checkVrde();
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("No extension pack by the name '%s' was found"), a_pstrExtPack->c_str());
+ }
+
+ return hrc;
+}
+
+/**
+ * Gets the full path to the VRDE library of the specified extension pack.
+ *
+ * This will do extacly the same as checkVrdeExtPack and then resolve the
+ * library path.
+ *
+ * @returns VINF_SUCCESS if a path is returned, VBox error status and message
+ * return if not.
+ * @param a_pstrExtPack The extension pack.
+ * @param a_pstrVrdeLibrary Where to return the path.
+ */
+int ExtPackManager::i_getVrdeLibraryPathForExtPack(Utf8Str const *a_pstrExtPack, Utf8Str *a_pstrVrdeLibrary)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ExtPack *pExtPack = i_findExtPack(a_pstrExtPack->c_str());
+ if (pExtPack)
+ hrc = pExtPack->i_getVrdpLibraryName(a_pstrVrdeLibrary);
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("No extension pack by the name '%s' was found"),
+ a_pstrExtPack->c_str());
+ }
+
+ return Global::vboxStatusCodeFromCOM(hrc);
+}
+
+/**
+ * Checks that the specified extension pack contains a cryptographic module and that it
+ * is shipshape.
+ *
+ * @returns S_OK if ok, appropriate failure status code with details.
+ * @param a_pstrExtPack The name of the extension pack.
+ */
+HRESULT ExtPackManager::i_checkCryptoExtPack(Utf8Str const *a_pstrExtPack)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ExtPack *pExtPack = i_findExtPack(a_pstrExtPack->c_str());
+ if (pExtPack)
+ hrc = pExtPack->i_checkCrypto();
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("No extension pack by the name '%s' was found"), a_pstrExtPack->c_str());
+ }
+
+ return hrc;
+}
+
+/**
+ * Gets the full path to the cryptographic library of the specified extension pack.
+ *
+ * This will do extacly the same as checkCryptoExtPack and then resolve the
+ * library path.
+ *
+ * @returns VINF_SUCCESS if a path is returned, VBox error status and message
+ * return if not.
+ * @param a_pstrExtPack The extension pack.
+ * @param a_pstrCryptoLibrary Where to return the path.
+ */
+int ExtPackManager::i_getCryptoLibraryPathForExtPack(Utf8Str const *a_pstrExtPack, Utf8Str *a_pstrCryptoLibrary)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ExtPack *pExtPack = i_findExtPack(a_pstrExtPack->c_str());
+ if (pExtPack)
+ hrc = pExtPack->i_getCryptoLibraryName(a_pstrCryptoLibrary);
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("No extension pack by the name '%s' was found"),
+ a_pstrExtPack->c_str());
+ }
+
+ return Global::vboxStatusCodeFromCOM(hrc);
+}
+
+
+/**
+ * Gets the full path to the specified library of the specified extension pack.
+ *
+ * @returns S_OK if a path is returned, COM error status and message return if
+ * not.
+ * @param a_pszModuleName The library.
+ * @param a_pszExtPack The extension pack.
+ * @param a_pstrLibrary Where to return the path.
+ */
+HRESULT ExtPackManager::i_getLibraryPathForExtPack(const char *a_pszModuleName, const char *a_pszExtPack, Utf8Str *a_pstrLibrary)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ExtPack *pExtPack = i_findExtPack(a_pszExtPack);
+ if (pExtPack)
+ hrc = pExtPack->i_getLibraryName(a_pszModuleName, a_pstrLibrary);
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("No extension pack by the name '%s' was found"), a_pszExtPack);
+ }
+
+ return hrc;
+}
+
+/**
+ * Gets the name of the default VRDE extension pack.
+ *
+ * @returns S_OK or some COM error status on red tape failure.
+ * @param a_pstrExtPack Where to return the extension pack name. Returns
+ * empty if no extension pack wishes to be the default
+ * VRDP provider.
+ */
+HRESULT ExtPackManager::i_getDefaultVrdeExtPack(Utf8Str *a_pstrExtPack)
+{
+ a_pstrExtPack->setNull();
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ ++it)
+ {
+ if ((*it)->i_wantsToBeDefaultVrde())
+ {
+ *a_pstrExtPack = (*it)->m->Desc.strName;
+ break;
+ }
+ }
+ }
+ return hrc;
+}
+
+/**
+ * Gets the name of the default cryptographic extension pack.
+ *
+ * @returns S_OK or some COM error status on red tape failure.
+ * @param a_pstrExtPack Where to return the extension pack name. Returns
+ * empty if no extension pack wishes to be the default
+ * VRDP provider.
+ */
+HRESULT ExtPackManager::i_getDefaultCryptoExtPack(Utf8Str *a_pstrExtPack)
+{
+ a_pstrExtPack->setNull();
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ ++it)
+ {
+ if ((*it)->i_wantsToBeDefaultCrypto())
+ {
+ *a_pstrExtPack = (*it)->m->Desc.strName;
+ break;
+ }
+ }
+ }
+ return hrc;
+}
+
+/**
+ * Checks if an extension pack is (present and) usable.
+ *
+ * @returns @c true if it is, otherwise @c false.
+ * @param a_pszExtPack The name of the extension pack.
+ */
+bool ExtPackManager::i_isExtPackUsable(const char *a_pszExtPack)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (FAILED(hrc))
+ return false;
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ExtPack *pExtPack = i_findExtPack(a_pszExtPack);
+ return pExtPack != NULL
+ && pExtPack->m->fUsable;
+}
+
+/**
+ * Dumps all extension packs to the release log.
+ */
+void ExtPackManager::i_dumpAllToReleaseLog(void)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (FAILED(hrc))
+ return;
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogRel(("Installed Extension Packs:\n"));
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ ++it)
+ {
+ ExtPack::Data *pExtPackData = (*it)->m;
+ if (pExtPackData)
+ {
+ if (pExtPackData->fUsable)
+ LogRel((" %s (Version: %s r%u%s%s; VRDE Module: %s; Crypto Module: %s)\n",
+ pExtPackData->Desc.strName.c_str(),
+ pExtPackData->Desc.strVersion.c_str(),
+ pExtPackData->Desc.uRevision,
+ pExtPackData->Desc.strEdition.isEmpty() ? "" : " ",
+ pExtPackData->Desc.strEdition.c_str(),
+ pExtPackData->Desc.strVrdeModule.c_str(),
+ pExtPackData->Desc.strCryptoModule.c_str() ));
+ else
+ LogRel((" %s (Version: %s r%u%s%s; VRDE Module: %s; Crypto Module: %s unusable because of '%s')\n",
+ pExtPackData->Desc.strName.c_str(),
+ pExtPackData->Desc.strVersion.c_str(),
+ pExtPackData->Desc.uRevision,
+ pExtPackData->Desc.strEdition.isEmpty() ? "" : " ",
+ pExtPackData->Desc.strEdition.c_str(),
+ pExtPackData->Desc.strVrdeModule.c_str(),
+ pExtPackData->Desc.strCryptoModule.c_str(),
+ pExtPackData->strWhyUnusable.c_str() ));
+ }
+ else
+ LogRel((" pExtPackData is NULL\n"));
+ }
+
+ if (!m->llInstalledExtPacks.size())
+ LogRel((" None installed!\n"));
+}
+
+/**
+ * Gets the update counter (reflecting extpack list updates).
+ */
+uint64_t ExtPackManager::i_getUpdateCounter(void)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (FAILED(hrc))
+ return 0;
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ return m->cUpdate;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/ExtPackUtil.cpp b/src/VBox/Main/src-all/ExtPackUtil.cpp
new file mode 100644
index 00000000..045f02bf
--- /dev/null
+++ b/src/VBox/Main/src-all/ExtPackUtil.cpp
@@ -0,0 +1,1474 @@
+/* $Id: ExtPackUtil.cpp $ */
+/** @file
+ * VirtualBox Main - Extension Pack Utilities and definitions, VBoxC, VBoxSVC, ++.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "../include/ExtPackUtil.h"
+
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/manifest.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/tar.h>
+#include <iprt/zip.h>
+#include <iprt/cpp/xml.h>
+
+#include <VBox/log.h>
+
+#include "../include/VBoxNls.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+DECLARE_TRANSLATION_CONTEXT(ExtPackUtil);
+
+
+/*********************************************************************************************************************************
+* Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Worker for VBoxExtPackLoadDesc that loads the plug-in descriptors.
+ *
+ * @returns Same as VBoxExtPackLoadDesc.
+ * @param pVBoxExtPackElm
+ * @param pcPlugIns Where to return the number of plug-ins in the
+ * array.
+ * @param paPlugIns Where to return the plug-in descriptor array.
+ * (RTMemFree it even on failure)
+ */
+static RTCString *
+vboxExtPackLoadPlugInDescs(const xml::ElementNode *pVBoxExtPackElm,
+ uint32_t *pcPlugIns, PVBOXEXTPACKPLUGINDESC *paPlugIns)
+{
+ *pcPlugIns = 0;
+ *paPlugIns = NULL;
+
+ /** @todo plug-ins */
+ NOREF(pVBoxExtPackElm);
+
+ return NULL;
+}
+
+
+/**
+ * Clears the extension pack descriptor.
+ *
+ * @param a_pExtPackDesc The descriptor to clear.
+ */
+static void vboxExtPackClearDesc(PVBOXEXTPACKDESC a_pExtPackDesc)
+{
+ a_pExtPackDesc->strName.setNull();
+ a_pExtPackDesc->strDescription.setNull();
+ a_pExtPackDesc->strVersion.setNull();
+ a_pExtPackDesc->strEdition.setNull();
+ a_pExtPackDesc->uRevision = 0;
+ a_pExtPackDesc->strMainModule.setNull();
+ a_pExtPackDesc->strMainVMModule.setNull();
+ a_pExtPackDesc->strVrdeModule.setNull();
+ a_pExtPackDesc->strCryptoModule.setNull();
+ a_pExtPackDesc->cPlugIns = 0;
+ a_pExtPackDesc->paPlugIns = NULL;
+ a_pExtPackDesc->fShowLicense = false;
+}
+
+
+/**
+ * Initializes an extension pack descriptor so that it's safe to call free on
+ * it whatever happens later on.
+ *
+ * @param a_pExtPackDesc The descirptor to initialize.
+ */
+void VBoxExtPackInitDesc(PVBOXEXTPACKDESC a_pExtPackDesc)
+{
+ vboxExtPackClearDesc(a_pExtPackDesc);
+}
+
+
+/**
+ * Load the extension pack descriptor from an XML document.
+ *
+ * @returns NULL on success, pointer to an error message on failure (caller
+ * deletes it).
+ * @param a_pDoc Pointer to the XML document.
+ * @param a_pExtPackDesc Where to store the extension pack descriptor.
+ */
+static RTCString *vboxExtPackLoadDescFromDoc(xml::Document *a_pDoc, PVBOXEXTPACKDESC a_pExtPackDesc)
+{
+ /*
+ * Get the main element and check its version.
+ */
+ const xml::ElementNode *pVBoxExtPackElm = a_pDoc->getRootElement();
+ if ( !pVBoxExtPackElm
+ || strcmp(pVBoxExtPackElm->getName(), "VirtualBoxExtensionPack") != 0)
+ return new RTCString(ExtPackUtil::tr("No VirtualBoxExtensionPack element"));
+
+ RTCString strFormatVersion;
+ if (!pVBoxExtPackElm->getAttributeValueN("version", strFormatVersion, RT_XML_ATTR_TINY))
+ return new RTCString(ExtPackUtil::tr("Missing format version"));
+ if (!strFormatVersion.equals("1.0"))
+ return &(new RTCString(ExtPackUtil::tr("Unsupported format version: ")))->append(strFormatVersion);
+
+ /*
+ * Read and validate mandatory bits.
+ */
+ const xml::ElementNode *pNameElm = pVBoxExtPackElm->findChildElement("Name");
+ if (!pNameElm)
+ return new RTCString(ExtPackUtil::tr("The 'Name' element is missing"));
+ const char *pszName = pNameElm->getValueN(RT_XML_CONTENT_SMALL);
+ if (!VBoxExtPackIsValidName(pszName))
+ return &(new RTCString(ExtPackUtil::tr("Invalid name: ")))->append(pszName);
+
+ const xml::ElementNode *pDescElm = pVBoxExtPackElm->findChildElement("Description");
+ if (!pDescElm)
+ return new RTCString(ExtPackUtil::tr("The 'Description' element is missing"));
+ const char *pszDesc = pDescElm->getValueN(RT_XML_CONTENT_LARGE);
+ if (!pszDesc || *pszDesc == '\0')
+ return new RTCString(ExtPackUtil::tr("The 'Description' element is empty"));
+ if (strpbrk(pszDesc, "\n\r\t\v\b") != NULL)
+ return new RTCString(ExtPackUtil::tr("The 'Description' must not contain control characters"));
+
+ const xml::ElementNode *pVersionElm = pVBoxExtPackElm->findChildElement("Version");
+ if (!pVersionElm)
+ return new RTCString(ExtPackUtil::tr("The 'Version' element is missing"));
+ const char *pszVersion = pVersionElm->getValueN(RT_XML_CONTENT_SMALL);
+ if (!pszVersion || *pszVersion == '\0')
+ return new RTCString(ExtPackUtil::tr("The 'Version' element is empty"));
+ if (!VBoxExtPackIsValidVersionString(pszVersion))
+ return &(new RTCString(ExtPackUtil::tr("Invalid version string: ")))->append(pszVersion);
+
+ uint32_t uRevision;
+ if (!pVersionElm->getAttributeValue("revision", uRevision))
+ uRevision = 0;
+
+ const char *pszEdition;
+ if (!pVersionElm->getAttributeValueN("edition", pszEdition, RT_XML_ATTR_TINY))
+ pszEdition = "";
+ if (!VBoxExtPackIsValidEditionString(pszEdition))
+ return &(new RTCString(ExtPackUtil::tr("Invalid edition string: ")))->append(pszEdition);
+
+ const xml::ElementNode *pMainModuleElm = pVBoxExtPackElm->findChildElement("MainModule");
+ if (!pMainModuleElm)
+ return new RTCString(ExtPackUtil::tr("The 'MainModule' element is missing"));
+ const char *pszMainModule = pMainModuleElm->getValueN(RT_XML_CONTENT_SMALL);
+ if (!pszMainModule || *pszMainModule == '\0')
+ return new RTCString(ExtPackUtil::tr("The 'MainModule' element is empty"));
+ if (!VBoxExtPackIsValidModuleString(pszMainModule))
+ return &(new RTCString(ExtPackUtil::tr("Invalid main module string: ")))->append(pszMainModule);
+
+ /*
+ * The main VM module, optional.
+ * Accept both none and empty as tokens of no main VM module.
+ */
+ const char *pszMainVMModule = NULL;
+ const xml::ElementNode *pMainVMModuleElm = pVBoxExtPackElm->findChildElement("MainVMModule");
+ if (pMainVMModuleElm)
+ {
+ pszMainVMModule = pMainVMModuleElm->getValueN(RT_XML_CONTENT_SMALL);
+ if (!pszMainVMModule || *pszMainVMModule == '\0')
+ pszMainVMModule = NULL;
+ else if (!VBoxExtPackIsValidModuleString(pszMainVMModule))
+ return &(new RTCString(ExtPackUtil::tr("Invalid main VM module string: ")))->append(pszMainVMModule);
+ }
+
+ /*
+ * The VRDE module, optional.
+ * Accept both none and empty as tokens of no VRDE module.
+ */
+ const char *pszVrdeModule = NULL;
+ const xml::ElementNode *pVrdeModuleElm = pVBoxExtPackElm->findChildElement("VRDEModule");
+ if (pVrdeModuleElm)
+ {
+ pszVrdeModule = pVrdeModuleElm->getValueN(RT_XML_CONTENT_SMALL);
+ if (!pszVrdeModule || *pszVrdeModule == '\0')
+ pszVrdeModule = NULL;
+ else if (!VBoxExtPackIsValidModuleString(pszVrdeModule))
+ return &(new RTCString(ExtPackUtil::tr("Invalid VRDE module string: ")))->append(pszVrdeModule);
+ }
+
+ /*
+ * The cryptographic module, optional.
+ * Accept both none and empty as tokens of no cryptographic module.
+ */
+ const char *pszCryptoModule = NULL;
+ const xml::ElementNode *pCryptoModuleElm = pVBoxExtPackElm->findChildElement("CryptoModule");
+ if (pCryptoModuleElm)
+ {
+ pszCryptoModule = pCryptoModuleElm->getValueN(RT_XML_CONTENT_SMALL);
+ if (!pszCryptoModule || *pszCryptoModule == '\0')
+ pszCryptoModule = NULL;
+ else if (!VBoxExtPackIsValidModuleString(pszCryptoModule))
+ return &(new RTCString(ExtPackUtil::tr("Invalid cryptographic module string: ")))->append(pszCryptoModule);
+ }
+
+ /*
+ * Whether to show the license, optional. (presense is enough here)
+ */
+ const xml::ElementNode *pShowLicenseElm = pVBoxExtPackElm->findChildElement("ShowLicense");
+ bool fShowLicense = pShowLicenseElm != NULL;
+
+ /*
+ * Parse plug-in descriptions (last because of the manual memory management).
+ */
+ uint32_t cPlugIns = 0;
+ PVBOXEXTPACKPLUGINDESC paPlugIns = NULL;
+ RTCString *pstrRet = vboxExtPackLoadPlugInDescs(pVBoxExtPackElm, &cPlugIns, &paPlugIns);
+ if (pstrRet)
+ {
+ RTMemFree(paPlugIns);
+ return pstrRet;
+ }
+
+ /*
+ * Everything seems fine, fill in the return values and return successfully.
+ */
+ a_pExtPackDesc->strName = pszName;
+ a_pExtPackDesc->strDescription = pszDesc;
+ a_pExtPackDesc->strVersion = pszVersion;
+ a_pExtPackDesc->strEdition = pszEdition;
+ a_pExtPackDesc->uRevision = uRevision;
+ a_pExtPackDesc->strMainModule = pszMainModule;
+ a_pExtPackDesc->strMainVMModule = pszMainVMModule;
+ a_pExtPackDesc->strVrdeModule = pszVrdeModule;
+ a_pExtPackDesc->strCryptoModule = pszCryptoModule;
+ a_pExtPackDesc->cPlugIns = cPlugIns;
+ a_pExtPackDesc->paPlugIns = paPlugIns;
+ a_pExtPackDesc->fShowLicense = fShowLicense;
+
+ return NULL;
+}
+
+/**
+ * Reads the extension pack descriptor.
+ *
+ * @returns NULL on success, pointer to an error message on failure (caller
+ * deletes it).
+ * @param a_pszDir The directory containing the description file.
+ * @param a_pExtPackDesc Where to store the extension pack descriptor.
+ * @param a_pObjInfo Where to store the object info for the file (unix
+ * attribs). Optional.
+ */
+RTCString *VBoxExtPackLoadDesc(const char *a_pszDir, PVBOXEXTPACKDESC a_pExtPackDesc, PRTFSOBJINFO a_pObjInfo)
+{
+ vboxExtPackClearDesc(a_pExtPackDesc);
+
+ /*
+ * Validate, open and parse the XML file.
+ */
+ char szFilePath[RTPATH_MAX];
+ int vrc = RTPathJoin(szFilePath, sizeof(szFilePath), a_pszDir, VBOX_EXTPACK_DESCRIPTION_NAME);
+ if (RT_FAILURE(vrc))
+ return new RTCStringFmt(ExtPackUtil::tr("RTPathJoin failed with %Rrc"), vrc);
+
+ RTFSOBJINFO ObjInfo;
+ vrc = RTPathQueryInfoEx(szFilePath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ return new RTCStringFmt(ExtPackUtil::tr("RTPathQueryInfoEx failed with %Rrc"), vrc);
+ if (a_pObjInfo)
+ *a_pObjInfo = ObjInfo;
+ if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ {
+ if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
+ return new RTCString(ExtPackUtil::tr("The XML file is symlinked, that is not allowed"));
+ return new RTCStringFmt(ExtPackUtil::tr("The XML file is not a file (fMode=%#x)"), ObjInfo.Attr.fMode);
+ }
+
+ xml::Document Doc;
+ {
+ xml::XmlFileParser Parser;
+ try
+ {
+ Parser.read(szFilePath, Doc);
+ }
+ catch (xml::XmlError &rErr)
+ {
+ return new RTCString(rErr.what());
+ }
+ }
+
+ /*
+ * Hand the xml doc over to the common code.
+ */
+ try
+ {
+ return vboxExtPackLoadDescFromDoc(&Doc, a_pExtPackDesc);
+ }
+ catch (RTCError &rXcpt) // includes all XML exceptions
+ {
+ return new RTCString(rXcpt.what());
+ }
+}
+
+/**
+ * Reads the extension pack descriptor.
+ *
+ * @returns NULL on success, pointer to an error message on failure (caller
+ * deletes it).
+ * @param hVfsFile The file handle of the description file.
+ * @param a_pExtPackDesc Where to store the extension pack descriptor.
+ * @param a_pObjInfo Where to store the object info for the file (unix
+ * attribs). Optional.
+ */
+RTCString *VBoxExtPackLoadDescFromVfsFile(RTVFSFILE hVfsFile, PVBOXEXTPACKDESC a_pExtPackDesc, PRTFSOBJINFO a_pObjInfo)
+{
+ vboxExtPackClearDesc(a_pExtPackDesc);
+
+ /*
+ * Query the object info.
+ */
+ RTFSOBJINFO ObjInfo;
+ int vrc = RTVfsFileQueryInfo(hVfsFile, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(vrc))
+ return &(new RTCString)->printf(ExtPackUtil::tr("RTVfsFileQueryInfo failed: %Rrc"), vrc);
+ if (a_pObjInfo)
+ *a_pObjInfo = ObjInfo;
+
+ /*
+ * The simple approach, read the whole thing into memory and pass this to
+ * the XML parser.
+ */
+
+ /* Check the file size. */
+ if (ObjInfo.cbObject > _1M || ObjInfo.cbObject < 0)
+ return &(new RTCString)->printf(ExtPackUtil::tr("The XML file is too large (%'RU64 bytes)", "", (size_t)ObjInfo.cbObject),
+ ObjInfo.cbObject);
+ size_t const cbFile = (size_t)ObjInfo.cbObject;
+
+ /* Rewind to the start of the file. */
+ vrc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(vrc))
+ return &(new RTCString)->printf(ExtPackUtil::tr("RTVfsFileSeek(,0,BEGIN) failed: %Rrc"), vrc);
+
+ /* Allocate memory and read the file content into it. */
+ void *pvFile = RTMemTmpAlloc(cbFile);
+ if (!pvFile)
+ return &(new RTCString)->printf(ExtPackUtil::tr("RTMemTmpAlloc(%zu) failed"), cbFile);
+
+ RTCString *pstrErr = NULL;
+ vrc = RTVfsFileRead(hVfsFile, pvFile, cbFile, NULL);
+ if (RT_FAILURE(vrc))
+ pstrErr = &(new RTCString)->printf(ExtPackUtil::tr("RTVfsFileRead failed: %Rrc"), vrc);
+
+ /*
+ * Parse the file.
+ */
+ xml::Document Doc;
+ if (RT_SUCCESS(vrc))
+ {
+ xml::XmlMemParser Parser;
+ RTCString strFileName = VBOX_EXTPACK_DESCRIPTION_NAME;
+ try
+ {
+ Parser.read(pvFile, cbFile, strFileName, Doc);
+ }
+ catch (xml::XmlError &rErr)
+ {
+ pstrErr = new RTCString(rErr.what());
+ vrc = VERR_PARSE_ERROR;
+ }
+ }
+ RTMemTmpFree(pvFile);
+
+ /*
+ * Hand the xml doc over to the common code.
+ */
+ if (RT_SUCCESS(vrc))
+ try
+ {
+ pstrErr = vboxExtPackLoadDescFromDoc(&Doc, a_pExtPackDesc);
+ }
+ catch (RTCError &rXcpt) // includes all XML exceptions
+ {
+ return new RTCString(rXcpt.what());
+ }
+
+ return pstrErr;
+}
+
+/**
+ * Frees all resources associated with a extension pack descriptor.
+ *
+ * @param a_pExtPackDesc The extension pack descriptor which members
+ * should be freed.
+ */
+void VBoxExtPackFreeDesc(PVBOXEXTPACKDESC a_pExtPackDesc)
+{
+ if (!a_pExtPackDesc)
+ return;
+
+ a_pExtPackDesc->strName.setNull();
+ a_pExtPackDesc->strDescription.setNull();
+ a_pExtPackDesc->strVersion.setNull();
+ a_pExtPackDesc->strEdition.setNull();
+ a_pExtPackDesc->uRevision = 0;
+ a_pExtPackDesc->strMainModule.setNull();
+ a_pExtPackDesc->strMainVMModule.setNull();
+ a_pExtPackDesc->strVrdeModule.setNull();
+ a_pExtPackDesc->strCryptoModule.setNull();
+ a_pExtPackDesc->cPlugIns = 0;
+ RTMemFree(a_pExtPackDesc->paPlugIns);
+ a_pExtPackDesc->paPlugIns = NULL;
+ a_pExtPackDesc->fShowLicense = false;
+}
+
+/**
+ * Extract the extension pack name from the tarball path.
+ *
+ * @returns String containing the name on success, the caller must delete it.
+ * NULL if no valid name was found or if we ran out of memory.
+ * @param pszTarball The path to the tarball.
+ */
+RTCString *VBoxExtPackExtractNameFromTarballPath(const char *pszTarball)
+{
+ /*
+ * Skip ahead to the filename part and count the number of characters
+ * that matches the criteria for a mangled extension pack name.
+ */
+ const char *pszSrc = RTPathFilename(pszTarball);
+ if (!pszSrc)
+ return NULL;
+
+ size_t off = 0;
+ while (RT_C_IS_ALNUM(pszSrc[off]) || pszSrc[off] == '_')
+ off++;
+
+ /*
+ * Check min and max name limits.
+ */
+ if ( off > VBOX_EXTPACK_NAME_MAX_LEN
+ || off < VBOX_EXTPACK_NAME_MIN_LEN)
+ return NULL;
+
+ /*
+ * Return the unmangled name.
+ */
+ return VBoxExtPackUnmangleName(pszSrc, off);
+}
+
+/**
+ * Validates the extension pack name.
+ *
+ * @returns true if valid, false if not.
+ * @param pszName The name to validate.
+ * @sa VBoxExtPackExtractNameFromTarballPath
+ */
+bool VBoxExtPackIsValidName(const char *pszName)
+{
+ if (!pszName)
+ return false;
+
+ /*
+ * Check the characters making up the name, only english alphabet
+ * characters, decimal digits and spaces are allowed.
+ */
+ size_t off = 0;
+ while (pszName[off])
+ {
+ if (!RT_C_IS_ALNUM(pszName[off]) && pszName[off] != ' ')
+ return false;
+ off++;
+ }
+
+ /*
+ * Check min and max name limits.
+ */
+ if ( off > VBOX_EXTPACK_NAME_MAX_LEN
+ || off < VBOX_EXTPACK_NAME_MIN_LEN)
+ return false;
+
+ return true;
+}
+
+/**
+ * Checks if an alledged manged extension pack name.
+ *
+ * @returns true if valid, false if not.
+ * @param pszMangledName The mangled name to validate.
+ * @param cchMax The max number of chars to test.
+ * @sa VBoxExtPackMangleName
+ */
+bool VBoxExtPackIsValidMangledName(const char *pszMangledName, size_t cchMax /*= RTSTR_MAX*/)
+{
+ if (!pszMangledName)
+ return false;
+
+ /*
+ * Check the characters making up the name, only english alphabet
+ * characters, decimal digits and underscores (=space) are allowed.
+ */
+ size_t off = 0;
+ while (off < cchMax && pszMangledName[off])
+ {
+ if (!RT_C_IS_ALNUM(pszMangledName[off]) && pszMangledName[off] != '_')
+ return false;
+ off++;
+ }
+
+ /*
+ * Check min and max name limits.
+ */
+ if ( off > VBOX_EXTPACK_NAME_MAX_LEN
+ || off < VBOX_EXTPACK_NAME_MIN_LEN)
+ return false;
+
+ return true;
+}
+
+/**
+ * Mangle an extension pack name so it can be used by a directory or file name.
+ *
+ * @returns String containing the mangled name on success, the caller must
+ * delete it. NULL on failure.
+ * @param pszName The unmangled name.
+ * @sa VBoxExtPackUnmangleName, VBoxExtPackIsValidMangledName
+ */
+RTCString *VBoxExtPackMangleName(const char *pszName)
+{
+ AssertReturn(VBoxExtPackIsValidName(pszName), NULL);
+
+ char szTmp[VBOX_EXTPACK_NAME_MAX_LEN + 1];
+ size_t off = 0;
+ char ch;
+ while ((ch = pszName[off]) != '\0')
+ {
+ if (ch == ' ')
+ ch = '_';
+ szTmp[off++] = ch;
+ }
+ szTmp[off] = '\0';
+ Assert(VBoxExtPackIsValidMangledName(szTmp));
+
+ return new RTCString(szTmp, off);
+}
+
+/**
+ * Unmangle an extension pack name (reverses VBoxExtPackMangleName).
+ *
+ * @returns String containing the mangled name on success, the caller must
+ * delete it. NULL on failure.
+ * @param pszMangledName The mangled name.
+ * @param cchMax The max name length. RTSTR_MAX is fine.
+ * @sa VBoxExtPackMangleName, VBoxExtPackIsValidMangledName
+ */
+RTCString *VBoxExtPackUnmangleName(const char *pszMangledName, size_t cchMax)
+{
+ AssertReturn(VBoxExtPackIsValidMangledName(pszMangledName, cchMax), NULL);
+
+ char szTmp[VBOX_EXTPACK_NAME_MAX_LEN + 1];
+ size_t off = 0;
+ char ch;
+ while ( off < cchMax
+ && (ch = pszMangledName[off]) != '\0')
+ {
+ if (ch == '_')
+ ch = ' ';
+ else
+ AssertReturn(RT_C_IS_ALNUM(ch) || ch == ' ', NULL);
+ szTmp[off++] = ch;
+ }
+ szTmp[off] = '\0';
+ AssertReturn(VBoxExtPackIsValidName(szTmp), NULL);
+
+ return new RTCString(szTmp, off);
+}
+
+/**
+ * Constructs the extension pack directory path.
+ *
+ * A combination of RTPathJoin and VBoxExtPackMangleName.
+ *
+ * @returns IPRT status code like RTPathJoin.
+ * @param pszExtPackDir Where to return the directory path.
+ * @param cbExtPackDir The size of the return buffer.
+ * @param pszParentDir The parent directory (".../Extensions").
+ * @param pszName The extension pack name, unmangled.
+ */
+int VBoxExtPackCalcDir(char *pszExtPackDir, size_t cbExtPackDir, const char *pszParentDir, const char *pszName)
+{
+ AssertReturn(VBoxExtPackIsValidName(pszName), VERR_INTERNAL_ERROR_5);
+
+ RTCString *pstrMangledName = VBoxExtPackMangleName(pszName);
+ if (!pstrMangledName)
+ return VERR_INTERNAL_ERROR_4;
+
+ int vrc = RTPathJoin(pszExtPackDir, cbExtPackDir, pszParentDir, pstrMangledName->c_str());
+ delete pstrMangledName;
+
+ return vrc;
+}
+
+
+/**
+ * Validates the extension pack version string.
+ *
+ * @returns true if valid, false if not.
+ * @param pszVersion The version string to validate.
+ */
+bool VBoxExtPackIsValidVersionString(const char *pszVersion)
+{
+ if (!pszVersion || *pszVersion == '\0')
+ return false;
+
+ /* 1.x.y.z... */
+ for (;;)
+ {
+ if (!RT_C_IS_DIGIT(*pszVersion))
+ return false;
+ do
+ pszVersion++;
+ while (RT_C_IS_DIGIT(*pszVersion));
+ if (*pszVersion != '.')
+ break;
+ pszVersion++;
+ }
+
+ /* upper case string + numbers indicating the build type */
+ if (*pszVersion == '-' || *pszVersion == '_')
+ {
+ /** @todo Should probably restrict this to known build types (alpha,
+ * beta, release candidate, ++). */
+ do
+ pszVersion++;
+ while ( RT_C_IS_DIGIT(*pszVersion)
+ || RT_C_IS_UPPER(*pszVersion)
+ || *pszVersion == '-'
+ || *pszVersion == '_');
+ }
+
+ return *pszVersion == '\0';
+}
+
+/**
+ * Validates the extension pack edition string.
+ *
+ * @returns true if valid, false if not.
+ * @param pszEdition The edition string to validate.
+ */
+bool VBoxExtPackIsValidEditionString(const char *pszEdition)
+{
+ if (*pszEdition)
+ {
+ if (!RT_C_IS_UPPER(*pszEdition))
+ return false;
+
+ do
+ pszEdition++;
+ while ( RT_C_IS_UPPER(*pszEdition)
+ || RT_C_IS_DIGIT(*pszEdition)
+ || *pszEdition == '-'
+ || *pszEdition == '_');
+ }
+ return *pszEdition == '\0';
+}
+
+/**
+ * Validates an extension pack module string.
+ *
+ * @returns true if valid, false if not.
+ * @param pszModule The module string to validate.
+ */
+bool VBoxExtPackIsValidModuleString(const char *pszModule)
+{
+ if (!pszModule || *pszModule == '\0')
+ return false;
+
+ /* Restricted charset, no extensions (dots). */
+ while ( RT_C_IS_ALNUM(*pszModule)
+ || *pszModule == '-'
+ || *pszModule == '_')
+ pszModule++;
+
+ return *pszModule == '\0';
+}
+
+/**
+ * RTStrPrintfv wrapper.
+ *
+ * @returns @a vrc
+ * @param vrc The status code to return.
+ * @param pszError The error buffer.
+ * @param cbError The size of the buffer.
+ * @param pszFormat The error message format string.
+ * @param ... Format arguments.
+ */
+static int vboxExtPackReturnError(int vrc, char *pszError, size_t cbError, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTStrPrintfV(pszError, cbError, pszFormat, va);
+ va_end(va);
+ return vrc;
+}
+
+/**
+ * RTStrPrintfv wrapper.
+ *
+ * @param pszError The error buffer.
+ * @param cbError The size of the buffer.
+ * @param pszFormat The error message format string.
+ * @param ... Format arguments.
+ */
+static void vboxExtPackSetError(char *pszError, size_t cbError, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTStrPrintfV(pszError, cbError, pszFormat, va);
+ va_end(va);
+}
+
+/**
+ * Verifies the manifest and its signature.
+ *
+ * @returns VBox status code, failures with message.
+ * @param hXmlFile The xml from the extension pack.
+ * @param pszExtPackName The expected extension pack name. This can be
+ * NULL, in which we don't have any expectations.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ */
+static int vboxExtPackVerifyXml(RTVFSFILE hXmlFile, const char *pszExtPackName, char *pszError, size_t cbError)
+{
+ /*
+ * Load the XML.
+ */
+ VBOXEXTPACKDESC ExtPackDesc;
+ RTCString *pstrErr = VBoxExtPackLoadDescFromVfsFile(hXmlFile, &ExtPackDesc, NULL);
+ if (pstrErr)
+ {
+ RTStrCopy(pszError, cbError, pstrErr->c_str());
+ delete pstrErr;
+ return VERR_PARSE_ERROR;
+ }
+
+ /*
+ * Check the name.
+ */
+ /** @todo drop this restriction after the old install interface is
+ * dropped. */
+ int vrc = VINF_SUCCESS;
+ if ( pszExtPackName
+ && !ExtPackDesc.strName.equalsIgnoreCase(pszExtPackName))
+ vrc = vboxExtPackReturnError(VERR_NOT_EQUAL, pszError, cbError,
+ ExtPackUtil::tr("The name of the downloaded file and the name stored inside the extension pack does not match"
+ " (xml='%s' file='%s')"), ExtPackDesc.strName.c_str(), pszExtPackName);
+ return vrc;
+}
+
+/**
+ * Verifies the manifest and its signature.
+ *
+ * @returns VBox status code, failures with message.
+ * @param hOurManifest The manifest we compiled.
+ * @param hManifestFile The manifest file in the extension pack.
+ * @param hSignatureFile The manifest signature file.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ */
+static int vboxExtPackVerifyManifestAndSignature(RTMANIFEST hOurManifest, RTVFSFILE hManifestFile, RTVFSFILE hSignatureFile,
+ char *pszError, size_t cbError)
+{
+ /*
+ * Read the manifest from the extension pack.
+ */
+ int vrc = RTVfsFileSeek(hManifestFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(vrc))
+ return vboxExtPackReturnError(vrc, pszError, cbError, ExtPackUtil::tr("RTVfsFileSeek failed: %Rrc"), vrc);
+
+ RTMANIFEST hTheirManifest;
+ vrc = RTManifestCreate(0 /*fFlags*/, &hTheirManifest);
+ if (RT_FAILURE(vrc))
+ return vboxExtPackReturnError(vrc, pszError, cbError, ExtPackUtil::tr("RTManifestCreate failed: %Rrc"), vrc);
+
+ RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hManifestFile);
+ vrc = RTManifestReadStandard(hTheirManifest, hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Compare the manifests.
+ */
+ static const char *s_apszIgnoreEntries[] =
+ {
+ VBOX_EXTPACK_MANIFEST_NAME,
+ VBOX_EXTPACK_SIGNATURE_NAME,
+ "./" VBOX_EXTPACK_MANIFEST_NAME,
+ "./" VBOX_EXTPACK_SIGNATURE_NAME,
+ NULL
+ };
+ char szError[RTPATH_MAX];
+ vrc = RTManifestEqualsEx(hOurManifest, hTheirManifest, &s_apszIgnoreEntries[0], NULL,
+ RTMANIFEST_EQUALS_IGN_MISSING_ATTRS /*fFlags*/,
+ szError, sizeof(szError));
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Validate the manifest file signature.
+ */
+ /** @todo implement signature stuff */
+ NOREF(hSignatureFile);
+
+ }
+ else if (vrc == VERR_NOT_EQUAL && szError[0])
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("Manifest mismatch: %s"), szError);
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTManifestEqualsEx failed: %Rrc"), vrc);
+#if 0
+ RTVFSIOSTREAM hVfsIosStdOut = NIL_RTVFSIOSTREAM;
+ RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, true, &hVfsIosStdOut);
+ RTVfsIoStrmWrite(hVfsIosStdOut, "Our:\n", sizeof("Our:\n") - 1, true, NULL);
+ RTManifestWriteStandard(hOurManifest, hVfsIosStdOut);
+ RTVfsIoStrmWrite(hVfsIosStdOut, "Their:\n", sizeof("Their:\n") - 1, true, NULL);
+ RTManifestWriteStandard(hTheirManifest, hVfsIosStdOut);
+#endif
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("Error parsing '%s': %Rrc"), VBOX_EXTPACK_MANIFEST_NAME, vrc);
+
+ RTManifestRelease(hTheirManifest);
+ return vrc;
+}
+
+
+/**
+ * Verifies the file digest (if specified) and returns the SHA-256 of the file.
+ *
+ * @returns
+ * @param hFileManifest Manifest containing a SHA-256 digest of the file
+ * that was calculated as the file was processed.
+ * @param pszFileDigest SHA-256 digest of the file.
+ * @param pStrDigest Where to return the SHA-256 digest. Optional.
+ * @param pszError Where to write an error message on failure.
+ * @param cbError The size of the @a pszError buffer.
+ */
+static int vboxExtPackVerifyFileDigest(RTMANIFEST hFileManifest, const char *pszFileDigest,
+ RTCString *pStrDigest, char *pszError, size_t cbError)
+{
+ /*
+ * Extract the SHA-256 entry for the extpack file.
+ */
+ char szCalculatedDigest[RTSHA256_DIGEST_LEN + 1];
+ int vrc = RTManifestEntryQueryAttr(hFileManifest, "extpack", NULL /*no name*/, RTMANIFEST_ATTR_SHA256,
+ szCalculatedDigest, sizeof(szCalculatedDigest), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Convert the two strings to binary form before comparing.
+ * We convert the calculated hash even if we don't have anything to
+ * compare with, just to validate it.
+ */
+ uint8_t abCalculatedHash[RTSHA256_HASH_SIZE];
+ vrc = RTSha256FromString(szCalculatedDigest, abCalculatedHash);
+ if (RT_SUCCESS(vrc))
+ {
+ if ( pszFileDigest
+ && *pszFileDigest != '\0')
+ {
+ uint8_t abFileHash[RTSHA256_HASH_SIZE];
+ vrc = RTSha256FromString(pszFileDigest, abFileHash);
+ if (RT_SUCCESS(vrc))
+ {
+ if (memcmp(abFileHash, abCalculatedHash, sizeof(abFileHash)))
+ {
+ vboxExtPackSetError(pszError, cbError,
+ ExtPackUtil::tr("The extension pack file has changed (SHA-256 mismatch)"));
+ vrc = VERR_NOT_EQUAL;
+ }
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("Bad SHA-256 '%s': %Rrc"), szCalculatedDigest, vrc);
+ }
+
+ /*
+ * Set the output hash on success.
+ */
+ if (pStrDigest && RT_SUCCESS(vrc))
+ {
+ try
+ {
+ *pStrDigest = szCalculatedDigest;
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("Bad SHA-256 '%s': %Rrc"), szCalculatedDigest, vrc);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, "RTManifestEntryGetAttr: %Rrc", vrc);
+ return vrc;
+}
+
+
+
+/**
+ * Validates a standard file.
+ *
+ * Generally all files are
+ *
+ * @returns VBox status code, failure message in @a pszError.
+ * @param pszAdjName The adjusted member name.
+ * @param enmType The VFS object type.
+ * @param phVfsObj The pointer to the VFS object handle variable.
+ * This is both input and output.
+ * @param phVfsFile Where to store the handle to the memorized
+ * file. This is NULL for license files.
+ * @param pszError Where to write an error message on failure.
+ * @param cbError The size of the @a pszError buffer.
+ */
+static int VBoxExtPackValidateStandardFile(const char *pszAdjName, RTVFSOBJTYPE enmType,
+ PRTVFSOBJ phVfsObj, PRTVFSFILE phVfsFile, char *pszError, size_t cbError)
+{
+ int vrc;
+
+ /*
+ * Make sure it's a file and that it isn't too large.
+ */
+ if (phVfsFile && *phVfsFile != NIL_RTVFSFILE)
+ vrc = vboxExtPackReturnError(VERR_DUPLICATE, pszError, cbError,
+ ExtPackUtil::tr("There can only be one '%s'"), pszAdjName);
+ else if (enmType != RTVFSOBJTYPE_IO_STREAM && enmType != RTVFSOBJTYPE_FILE)
+ vrc = vboxExtPackReturnError(VERR_NOT_A_FILE, pszError, cbError,
+ ExtPackUtil::tr("Standard member '%s' is not a file"), pszAdjName);
+ else
+ {
+ RTFSOBJINFO ObjInfo;
+ vrc = RTVfsObjQueryInfo(*phVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ vrc = vboxExtPackReturnError(VERR_NOT_A_FILE, pszError, cbError,
+ ExtPackUtil::tr("Standard member '%s' is not a file"), pszAdjName);
+ else if (ObjInfo.cbObject >= _1M)
+ vrc = vboxExtPackReturnError(VERR_OUT_OF_RANGE, pszError, cbError,
+ ExtPackUtil::tr("Standard member '%s' is too large: %'RU64 bytes (max 1 MB)", "",
+ (size_t)ObjInfo.cbObject),
+ pszAdjName, (uint64_t)ObjInfo.cbObject);
+ else
+ {
+ /*
+ * Make an in memory copy of the stream and check that the file
+ * is UTF-8 clean.
+ */
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(*phVfsObj);
+ RTVFSFILE hVfsFile;
+ vrc = RTVfsMemorizeIoStreamAsFile(hVfsIos, RTFILE_O_READ, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsIoStrmValidateUtf8Encoding(hVfsIos,
+ RTVFS_VALIDATE_UTF8_BY_RTC_3629 | RTVFS_VALIDATE_UTF8_NO_NULL,
+ NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Replace *phVfsObj with the memorized file.
+ */
+ vrc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVfsObjRelease(*phVfsObj);
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError,
+ ExtPackUtil::tr("RTVfsFileSeek failed on '%s': %Rrc"), pszAdjName, vrc);
+ }
+
+ if (phVfsFile && RT_SUCCESS(vrc))
+ *phVfsFile = hVfsFile;
+ else
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError,
+ ExtPackUtil::tr("RTVfsMemorizeIoStreamAsFile failed on '%s': %Rrc"), pszAdjName, vrc);
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTVfsObjQueryInfo failed on '%s': %Rrc"), pszAdjName, vrc);
+ }
+ return vrc;
+}
+
+
+/**
+ * Validates a name in an extension pack.
+ *
+ * We restrict the charset to try make sure the extension pack can be unpacked
+ * on all file systems.
+ *
+ * @returns VBox status code, failures with message.
+ * @param pszName The name to validate.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ */
+static int vboxExtPackValidateMemberName(const char *pszName, char *pszError, size_t cbError)
+{
+ if (RTPathStartsWithRoot(pszName))
+ return vboxExtPackReturnError(VERR_PATH_IS_NOT_RELATIVE, pszError, cbError,
+ ExtPackUtil::tr("'%s': starts with root spec"), pszName);
+
+ const char *pszErr = NULL;
+ const char *psz = pszName;
+ int ch;
+ while ((ch = *psz) != '\0')
+ {
+ /* Character set restrictions. */
+ if (ch < 0 || ch >= 128)
+ {
+ pszErr = "Only 7-bit ASCII allowed";
+ break;
+ }
+ if (ch <= 31 || ch == 127)
+ {
+ pszErr = "No control characters are not allowed";
+ break;
+ }
+ if (ch == '\\')
+ {
+ pszErr = "Only backward slashes are not allowed";
+ break;
+ }
+ if (strchr("'\":;*?|[]<>(){}", ch))
+ {
+ pszErr = "The characters ', \", :, ;, *, ?, |, [, ], <, >, (, ), { and } are not allowed";
+ break;
+ }
+
+ /* Take the simple way out and ban all ".." sequences. */
+ if ( ch == '.'
+ && psz[1] == '.')
+ {
+ pszErr = "Double dot sequence are not allowed";
+ break;
+ }
+
+ /* Keep the tree shallow or the hardening checks will fail. */
+ if (psz - pszName > VBOX_EXTPACK_MAX_MEMBER_NAME_LENGTH)
+ {
+ pszErr = "Too long";
+ break;
+ }
+
+ /* advance */
+ psz++;
+ }
+
+ if (pszErr)
+ return vboxExtPackReturnError(VERR_INVALID_NAME, pszError, cbError,
+ ExtPackUtil::tr("Bad member name '%s' (pos %zu): %s"),
+ pszName, (size_t)(psz - pszName), pszErr);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Validates a file in an extension pack.
+ *
+ * @returns VBox status code, failures with message.
+ * @param pszName The name of the file.
+ * @param hVfsObj The VFS object.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ */
+static int vboxExtPackValidateMemberFile(const char *pszName, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
+{
+ int vrc = vboxExtPackValidateMemberName(pszName, pszError, cbError);
+ if (RT_SUCCESS(vrc))
+ {
+ RTFSOBJINFO ObjInfo;
+ vrc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ if (ObjInfo.cbObject >= 9*_1G64)
+ vrc = vboxExtPackReturnError(VERR_OUT_OF_RANGE, pszError, cbError,
+ ExtPackUtil::tr("'%s': too large (%'RU64 bytes)", "", (size_t)ObjInfo.cbObject),
+ pszName, (uint64_t)ObjInfo.cbObject);
+ if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ vrc = vboxExtPackReturnError(VERR_NOT_A_FILE, pszError, cbError,
+ ExtPackUtil::tr("The alleged file '%s' has a mode mask stating otherwise (%RTfmode)"),
+ pszName, ObjInfo.Attr.fMode);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTVfsObjQueryInfo failed on '%s': %Rrc"), pszName, vrc);
+ }
+ return vrc;
+}
+
+
+/**
+ * Validates a directory in an extension pack.
+ *
+ * @returns VBox status code, failures with message.
+ * @param pszName The name of the directory.
+ * @param hVfsObj The VFS object.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ */
+static int vboxExtPackValidateMemberDir(const char *pszName, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
+{
+ int vrc = vboxExtPackValidateMemberName(pszName, pszError, cbError);
+ if (RT_SUCCESS(vrc))
+ {
+ RTFSOBJINFO ObjInfo;
+ vrc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ vrc = vboxExtPackReturnError(VERR_NOT_A_DIRECTORY, pszError, cbError,
+ ExtPackUtil::tr("The alleged directory '%s' has a mode mask saying differently (%RTfmode)"),
+ pszName, ObjInfo.Attr.fMode);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTVfsObjQueryInfo failed on '%s': %Rrc"), pszName, vrc);
+ }
+ return vrc;
+}
+
+/**
+ * Validates a member of an extension pack.
+ *
+ * @returns VBox status code, failures with message.
+ * @param pszName The name of the directory.
+ * @param enmType The object type.
+ * @param hVfsObj The VFS object.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ */
+int VBoxExtPackValidateMember(const char *pszName, RTVFSOBJTYPE enmType, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
+{
+ Assert(cbError > 0);
+ *pszError = '\0';
+
+ int vrc;
+ if ( enmType == RTVFSOBJTYPE_FILE
+ || enmType == RTVFSOBJTYPE_IO_STREAM)
+ vrc = vboxExtPackValidateMemberFile(pszName, hVfsObj, pszError, cbError);
+ else if ( enmType == RTVFSOBJTYPE_DIR
+ || enmType == RTVFSOBJTYPE_BASE)
+ vrc = vboxExtPackValidateMemberDir(pszName, hVfsObj, pszError, cbError);
+ else
+ vrc = vboxExtPackReturnError(VERR_UNEXPECTED_FS_OBJ_TYPE, pszError, cbError,
+ ExtPackUtil::tr("'%s' is not a file or directory (enmType=%d)"), pszName, enmType);
+ return vrc;
+}
+
+
+/**
+ * Rewinds the tarball file handle and creates a gunzip | tar chain that
+ * results in a filesystem stream.
+ *
+ * @returns VBox status code, failures with message.
+ * @param hTarballFile The handle to the tarball file.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ * @param phTarFss Where to return the filesystem stream handle.
+ * @param phFileManifest Where to return a manifest where the tarball is
+ * gettting hashed. The entry will be called
+ * "extpack" and be ready when the file system
+ * stream is at an end. Optional.
+ */
+int VBoxExtPackOpenTarFss(RTFILE hTarballFile, char *pszError, size_t cbError, PRTVFSFSSTREAM phTarFss,
+ PRTMANIFEST phFileManifest)
+{
+ Assert(cbError > 0);
+ *pszError = '\0';
+ *phTarFss = NIL_RTVFSFSSTREAM;
+
+ /*
+ * Rewind the file and set up a VFS chain for it.
+ */
+ int vrc = RTFileSeek(hTarballFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(vrc))
+ return vboxExtPackReturnError(vrc, pszError, cbError,
+ ExtPackUtil::tr("Failed seeking to the start of the tarball: %Rrc"), vrc);
+
+ RTVFSIOSTREAM hTarballIos;
+ vrc = RTVfsIoStrmFromRTFile(hTarballFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN, true /*fLeaveOpen*/,
+ &hTarballIos);
+ if (RT_FAILURE(vrc))
+ return vboxExtPackReturnError(vrc, pszError, cbError, ExtPackUtil::tr("RTVfsIoStrmFromRTFile failed: %Rrc"), vrc);
+
+ RTMANIFEST hFileManifest = NIL_RTMANIFEST;
+ vrc = RTManifestCreate(0 /*fFlags*/, &hFileManifest);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hPtIos;
+ vrc = RTManifestEntryAddPassthruIoStream(hFileManifest, hTarballIos, "extpack", RTMANIFEST_ATTR_SHA256,
+ true /*read*/, &hPtIos);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hGunzipIos;
+ vrc = RTZipGzipDecompressIoStream(hPtIos, 0 /*fFlags*/, &hGunzipIos);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSFSSTREAM hTarFss;
+ vrc = RTZipTarFsStreamFromIoStream(hGunzipIos, 0 /*fFlags*/, &hTarFss);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVfsIoStrmRelease(hPtIos);
+ RTVfsIoStrmRelease(hGunzipIos);
+ RTVfsIoStrmRelease(hTarballIos);
+ *phTarFss = hTarFss;
+ if (phFileManifest)
+ *phFileManifest = hFileManifest;
+ else
+ RTManifestRelease(hFileManifest);
+ return VINF_SUCCESS;
+ }
+
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTZipTarFsStreamFromIoStream failed: %Rrc"), vrc);
+ RTVfsIoStrmRelease(hGunzipIos);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTZipGzipDecompressIoStream failed: %Rrc"), vrc);
+ RTVfsIoStrmRelease(hPtIos);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTManifestEntryAddPassthruIoStream failed: %Rrc"), vrc);
+ RTManifestRelease(hFileManifest);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTManifestCreate failed: %Rrc"), vrc);
+
+ RTVfsIoStrmRelease(hTarballIos);
+ return vrc;
+}
+
+
+/**
+ * Validates the extension pack tarball prior to unpacking.
+ *
+ * Operations performed:
+ * - Mandatory files.
+ * - Manifest check.
+ * - Manifest seal check.
+ * - XML check, match name.
+ *
+ * @returns VBox status code, failures with message.
+ * @param hTarballFile The handle to open the @a pszTarball file.
+ * @param pszExtPackName The name of the extension pack name. NULL if
+ * the name is not fixed.
+ * @param pszTarball The name of the tarball in case we have to
+ * complain about something.
+ * @param pszTarballDigest The SHA-256 digest of the tarball. Empty string
+ * if no digest available.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ * @param phValidManifest Where to optionally return the handle to fully
+ * validated the manifest for the extension pack.
+ * This includes all files.
+ * @param phXmlFile Where to optionally return the memorized XML
+ * file.
+ * @param pStrDigest Where to return the digest of the file.
+ * Optional.
+ */
+int VBoxExtPackValidateTarball(RTFILE hTarballFile, const char *pszExtPackName,
+ const char *pszTarball, const char *pszTarballDigest,
+ char *pszError, size_t cbError,
+ PRTMANIFEST phValidManifest, PRTVFSFILE phXmlFile, RTCString *pStrDigest)
+{
+ /*
+ * Clear return values.
+ */
+ if (phValidManifest)
+ *phValidManifest = NIL_RTMANIFEST;
+ if (phXmlFile)
+ *phXmlFile = NIL_RTVFSFILE;
+ Assert(cbError > 1);
+ *pszError = '\0';
+ NOREF(pszTarball);
+
+ /*
+ * Open the tar.gz filesystem stream and set up an manifest in-memory file.
+ */
+ RTMANIFEST hFileManifest;
+ RTVFSFSSTREAM hTarFss;
+ int vrc = VBoxExtPackOpenTarFss(hTarballFile, pszError, cbError, &hTarFss, &hFileManifest);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ RTMANIFEST hOurManifest;
+ vrc = RTManifestCreate(0 /*fFlags*/, &hOurManifest);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Process the tarball (would be nice to move this to a function).
+ */
+ RTVFSFILE hXmlFile = NIL_RTVFSFILE;
+ RTVFSFILE hManifestFile = NIL_RTVFSFILE;
+ RTVFSFILE hSignatureFile = NIL_RTVFSFILE;
+ for (;;)
+ {
+ /*
+ * Get the next stream object.
+ */
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ RTVFSOBJTYPE enmType;
+ vrc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc != VERR_EOF)
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTVfsFsStrmNext failed: %Rrc"), vrc);
+ else
+ vrc = VINF_SUCCESS;
+ break;
+ }
+ const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
+
+ /*
+ * Check the type & name validity, performing special tests on
+ * standard extension pack member files.
+ *
+ * N.B. We will always reach the end of the loop before breaking on
+ * failure - cleanup reasons.
+ */
+ vrc = VBoxExtPackValidateMember(pszName, enmType, hVfsObj, pszError, cbError);
+ if (RT_SUCCESS(vrc))
+ {
+ PRTVFSFILE phVfsFile = NULL;
+ if (!strcmp(pszAdjName, VBOX_EXTPACK_DESCRIPTION_NAME))
+ phVfsFile = &hXmlFile;
+ else if (!strcmp(pszAdjName, VBOX_EXTPACK_MANIFEST_NAME))
+ phVfsFile = &hManifestFile;
+ else if (!strcmp(pszAdjName, VBOX_EXTPACK_SIGNATURE_NAME))
+ phVfsFile = &hSignatureFile;
+ else if (!strncmp(pszAdjName, VBOX_EXTPACK_LICENSE_NAME_PREFIX, sizeof(VBOX_EXTPACK_LICENSE_NAME_PREFIX) - 1))
+ vrc = VBoxExtPackValidateStandardFile(pszAdjName, enmType, &hVfsObj, NULL, pszError, cbError);
+ if (phVfsFile)
+ vrc = VBoxExtPackValidateStandardFile(pszAdjName, enmType, &hVfsObj, phVfsFile, pszError, cbError);
+ }
+
+ /*
+ * Add any I/O stream to the manifest
+ */
+ if ( RT_SUCCESS(vrc)
+ && ( enmType == RTVFSOBJTYPE_FILE
+ || enmType == RTVFSOBJTYPE_IO_STREAM))
+ {
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ vrc = RTManifestEntryAddIoStream(hOurManifest, hVfsIos, pszAdjName, RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256);
+ if (RT_FAILURE(vrc))
+ vboxExtPackSetError(pszError, cbError,
+ ExtPackUtil::tr("RTManifestEntryAddIoStream failed on '%s': %Rrc"), pszAdjName, vrc);
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+
+ /*
+ * Clean up and break out on failure.
+ */
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ /*
+ * Check the integrity of the tarball file.
+ */
+ if (RT_SUCCESS(vrc))
+ {
+ RTVfsFsStrmRelease(hTarFss);
+ hTarFss = NIL_RTVFSFSSTREAM;
+ vrc = vboxExtPackVerifyFileDigest(hFileManifest, pszTarballDigest, pStrDigest, pszError, cbError);
+ }
+
+ /*
+ * If we've successfully processed the tarball, verify that the
+ * mandatory files are present.
+ */
+ if (RT_SUCCESS(vrc))
+ {
+ if (hXmlFile == NIL_RTVFSFILE)
+ vrc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, ExtPackUtil::tr("Mandator file '%s' is missing"),
+ VBOX_EXTPACK_DESCRIPTION_NAME);
+ if (hManifestFile == NIL_RTVFSFILE)
+ vrc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, ExtPackUtil::tr("Mandator file '%s' is missing"),
+ VBOX_EXTPACK_MANIFEST_NAME);
+ if (hSignatureFile == NIL_RTVFSFILE)
+ vrc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, ExtPackUtil::tr("Mandator file '%s' is missing"),
+ VBOX_EXTPACK_SIGNATURE_NAME);
+ }
+
+ /*
+ * Check the manifest and it's signature.
+ */
+ if (RT_SUCCESS(vrc))
+ vrc = vboxExtPackVerifyManifestAndSignature(hOurManifest, hManifestFile, hSignatureFile, pszError, cbError);
+
+ /*
+ * Check the XML.
+ */
+ if (RT_SUCCESS(vrc))
+ vrc = vboxExtPackVerifyXml(hXmlFile, pszExtPackName, pszError, cbError);
+
+ /*
+ * Returns objects.
+ */
+ if (RT_SUCCESS(vrc))
+ {
+ if (phValidManifest)
+ {
+ RTManifestRetain(hOurManifest);
+ *phValidManifest = hOurManifest;
+ }
+ if (phXmlFile)
+ {
+ RTVfsFileRetain(hXmlFile);
+ *phXmlFile = hXmlFile;
+ }
+ }
+
+ /*
+ * Release our object references.
+ */
+ RTManifestRelease(hOurManifest);
+ RTVfsFileRelease(hXmlFile);
+ RTVfsFileRelease(hManifestFile);
+ RTVfsFileRelease(hSignatureFile);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, "RTManifestCreate failed: %Rrc", vrc);
+ RTVfsFsStrmRelease(hTarFss);
+ RTManifestRelease(hFileManifest);
+
+ return vrc;
+}
+
diff --git a/src/VBox/Main/src-all/Global.cpp b/src/VBox/Main/src-all/Global.cpp
new file mode 100644
index 00000000..6e0b2116
--- /dev/null
+++ b/src/VBox/Main/src-all/Global.cpp
@@ -0,0 +1,787 @@
+/* $Id: Global.cpp $ */
+/** @file
+ * VirtualBox COM global definitions
+ *
+ * NOTE: This file is part of both VBoxC.dll and VBoxSVC.exe.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "Global.h"
+#include "StringifyEnums.h"
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/errcore.h>
+
+#include "VBoxNls.h"
+
+DECLARE_TRANSLATION_CONTEXT(GlobalCtx);
+
+
+/* static */
+const Global::OSType Global::sOSTypes[] =
+{
+ /* NOTE1: we assume that unknown is always the first two entries!
+ * NOTE2: please use powers of 2 when specifying the size of harddisks since
+ * '2GB' looks better than '1.95GB' (= 2000MB)
+ * NOTE3: if you add new guest OS types please check if the code in
+ * Machine::getEffectiveParavirtProvider and Console::i_configConstructorInner
+ * are still covering the relevant cases. */
+ { "Other", "Other", "Other", "Other/Unknown",
+ VBOXOSTYPE_Unknown, VBOXOSHINT_NONE,
+ 1, 64, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Other", "Other", "Other_64", "Other/Unknown (64-bit)",
+ VBOXOSTYPE_Unknown_x64, VBOXOSHINT_64BIT | VBOXOSHINT_PAE | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC,
+ 1, 64, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "Windows31", "Windows 3.1",
+ VBOXOSTYPE_Win31, VBOXOSHINT_FLOPPY,
+ 1, 32, 4, 1 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "Windows", "Microsoft Windows", "Windows95", "Windows 95",
+ VBOXOSTYPE_Win95, VBOXOSHINT_FLOPPY,
+ 1, 64, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "Windows", "Microsoft Windows", "Windows98", "Windows 98",
+ VBOXOSTYPE_Win98, VBOXOSHINT_FLOPPY,
+ 1, 64, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "Windows", "Microsoft Windows", "WindowsMe", "Windows ME",
+ VBOXOSTYPE_WinMe, VBOXOSHINT_FLOPPY | VBOXOSHINT_USBTABLET,
+ 1, 128, 4, 4 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "WindowsNT3x", "Windows NT 3.x",
+ VBOXOSTYPE_WinNT3x, VBOXOSHINT_NOUSB | VBOXOSHINT_FLOPPY,
+ 1, 64, 8, _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_BusLogic, StorageBus_SCSI,
+ StorageControllerType_BusLogic, StorageBus_SCSI, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "Windows", "Microsoft Windows", "WindowsNT4", "Windows NT 4",
+ VBOXOSTYPE_WinNT4, VBOXOSHINT_NOUSB,
+ 1, 128, 16, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "Windows", "Microsoft Windows", "Windows2000", "Windows 2000",
+ VBOXOSTYPE_Win2k, VBOXOSHINT_USBTABLET,
+ 1, 168, 16, 4 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "WindowsXP", "Windows XP (32-bit)",
+ VBOXOSTYPE_WinXP, VBOXOSHINT_USBTABLET,
+ 1, 192, 16, 10 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82543GC, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "WindowsXP_64", "Windows XP (64-bit)",
+ VBOXOSTYPE_WinXP_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET,
+ 1, 512, 16, 10 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "Windows2003", "Windows 2003 (32-bit)",
+ VBOXOSTYPE_Win2k3, VBOXOSHINT_USBTABLET,
+ 1, 512, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82543GC, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "Windows2003_64", "Windows 2003 (64-bit)",
+ VBOXOSTYPE_Win2k3_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET,
+ 1, 512, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "WindowsVista", "Windows Vista (32-bit)",
+ VBOXOSTYPE_WinVista, VBOXOSHINT_USBTABLET | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 512, 16, 25 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "WindowsVista_64", "Windows Vista (64-bit)",
+ VBOXOSTYPE_WinVista_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 512, 16, 25 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows2008", "Windows 2008 (32-bit)",
+ VBOXOSTYPE_Win2k8, VBOXOSHINT_USBTABLET | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 1024, 16, 32 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows2008_64", "Windows 2008 (64-bit)",
+ VBOXOSTYPE_Win2k8_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 16, 32 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows7", "Windows 7 (32-bit)",
+ VBOXOSTYPE_Win7, VBOXOSHINT_USBTABLET | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 1024, 16, 32 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows7_64", "Windows 7 (64-bit)",
+ VBOXOSTYPE_Win7_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 16, 32 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows8", "Windows 8 (32-bit)",
+ VBOXOSTYPE_Win8, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_PAE | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 1024, 128, 40 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows8_64", "Windows 8 (64-bit)",
+ VBOXOSTYPE_Win8_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 40 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows81", "Windows 8.1 (32-bit)",
+ VBOXOSTYPE_Win81, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_PAE | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 1024, 128, 40 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows81_64", "Windows 8.1 (64-bit)",
+ VBOXOSTYPE_Win81_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 40 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows2012_64", "Windows 2012 (64-bit)",
+ VBOXOSTYPE_Win2k12_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 50 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows10", "Windows 10 (32-bit)",
+ VBOXOSTYPE_Win10, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_PAE | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 1024, 128, 50 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows10_64", "Windows 10 (64-bit)",
+ VBOXOSTYPE_Win10_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 50 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows2016_64", "Windows 2016 (64-bit)",
+ VBOXOSTYPE_Win2k16_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 50 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows2019_64", "Windows 2019 (64-bit)",
+ VBOXOSTYPE_Win2k19_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 50 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows11_64", "Windows 11 (64-bit)",
+ VBOXOSTYPE_Win11_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_EFI_SECUREBOOT | VBOXOSHINT_TPM2 | VBOXOSHINT_WDDM_GRAPHICS,
+ 2, 4096, 128, 80 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows2022_64", "Windows 2022 (64-bit)",
+ VBOXOSTYPE_Win2k22_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 50 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "WindowsNT", "Other Windows (32-bit)",
+ VBOXOSTYPE_WinNT, VBOXOSHINT_NONE,
+ 1, 512, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "WindowsNT_64", "Other Windows (64-bit)",
+ VBOXOSTYPE_WinNT_x64, VBOXOSHINT_64BIT | VBOXOSHINT_PAE | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET,
+ 1, 512, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+#define VBOX_LINUX_OSHINTS_A_32 (VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET | VBOXOSHINT_X2APIC | VBOXOSHINT_PAE)
+#define VBOX_LINUX_OSHINTS_A_64 (VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET | VBOXOSHINT_X2APIC | VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC)
+
+#define VBOX_LINUX_OSHINTS_B_32 (VBOXOSHINT_RTCUTC | VBOXOSHINT_PAE | VBOXOSHINT_X2APIC)
+#define VBOX_LINUX_OSHINTS_B_64 (VBOXOSHINT_RTCUTC | VBOXOSHINT_PAE | VBOXOSHINT_X2APIC | VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC)
+
+#define VBOX_LINUX_OSHINTS_C_32 (VBOXOSHINT_RTCUTC | VBOXOSHINT_X2APIC | VBOXOSHINT_PAE)
+#define VBOX_LINUX_OSHINTS_C_64 (VBOXOSHINT_RTCUTC | VBOXOSHINT_X2APIC | VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC)
+
+#define VBOX_LINUX_OSHINTS_D_32 (VBOXOSHINT_RTCUTC | VBOXOSHINT_PAE)
+#define VBOX_LINUX_OSHINTS_D_64 (VBOXOSHINT_RTCUTC | VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC)
+
+#define VBOX_LINUX_OSTYPE_32(a_OStype) (VBOXOSTYPE_ ## a_OStype)
+#define VBOX_LINUX_OSTYPE_64(a_OStype) (VBOXOSTYPE_ ## a_OStype ## _x64)
+
+#define VBOX_LINUX_OSID_STR(a_OSid) (# a_OSid)
+#define VBOX_LINUX_OSID_STR_64(a_OSid) VBOX_LINUX_OSID_STR(a_OSid ## _64)
+
+#define VBOX_LINUX_SUBTYPE_TEMPLATE_32(a_Id, a_Description, a_OStype, a_OSHint, a_Memory, a_Vram, a_Diskspace, \
+ a_NetworkAdapter, a_HDStorageController, a_HDStorageBusType) \
+ { "Linux", "Linux", VBOX_LINUX_OSID_STR(a_Id), a_Description, VBOX_LINUX_OSTYPE_32(a_OStype), a_OSHint, \
+ 1, a_Memory, a_Vram, a_Diskspace * _1G64, GraphicsControllerType_VMSVGA, a_NetworkAdapter, 0, StorageControllerType_PIIX4, StorageBus_IDE, \
+ a_HDStorageController, a_HDStorageBusType, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_AD1980 }
+
+#define VBOX_LINUX_SUBTYPE_TEMPLATE_64(a_Id, a_Description, a_OStype, a_OSHint, a_Memory, a_Vram, a_Diskspace, \
+ a_NetworkAdapter, a_HDStorageController, a_HDStorageBusType) \
+ { "Linux", "Linux", VBOX_LINUX_OSID_STR_64(a_Id), a_Description, VBOX_LINUX_OSTYPE_64(a_OStype), a_OSHint, \
+ 1, a_Memory, a_Vram, a_Diskspace * _1G64, GraphicsControllerType_VMSVGA, a_NetworkAdapter, 0, StorageControllerType_PIIX4, StorageBus_IDE, \
+ a_HDStorageController, a_HDStorageBusType, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_AD1980 }
+
+/* Linux 32-bit sub-type template defaulting to 1 CPU with USB-tablet-mouse/VMSVGA/Intel-Pro1000/PIIX4+IDE DVD/AHCI+SATA disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_A_32(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_32(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_A_32, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+/* Linux 64-bit sub-type template defaulting to 1 CPU with USB-tablet-mouse/VMSVGA/Intel-Pro1000/PIIX4+IDE DVD/AHCI+SATA disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_A_64(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_64(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_A_64, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+#define VBOX_LINUX_SUBTYPE_A_WITH_OSTYPE_32(a_Id, a_Description, a_OStype, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_32(a_Id, a_Description, a_OStype, VBOX_LINUX_OSHINTS_A_32, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+#define VBOX_LINUX_SUBTYPE_A_WITH_OSTYPE_64(a_Id, a_Description, a_OStype, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_64(a_Id, a_Description, a_OStype, VBOX_LINUX_OSHINTS_A_64, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+/* Linux 32-bit sub-type template defaulting to 1 CPU with PS/2-mouse/PAE-NX/VMSVGA/Intel-Pro1000/PIIX4+IDE DVD/AHCI+SATA disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_B_32(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_32(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_B_32, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+/* Linux 64-bit sub-type template defaulting to 1 CPU with PS/2-mouse/PAE-NX/VMSVGA/Intel-Pro1000/PIIX4+IDE DVD/AHCI+SATA disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_B_64(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_64(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_B_64, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+/* Linux 32-bit sub-type template defaulting to 1 CPU with PS/2-mouse/VMSVGA/Intel-Pro1000/PIIX4+IDE DVD/AHCI+SATA disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_C_32(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_32(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_C_32, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+/* Linux 64-bit sub-type template defaulting to 1 CPU with PS/2-mouse/VMSVGA/Intel-Pro1000/PIIX4+IDE DVD/AHCI+SATA disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_C_64(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_64(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_C_64, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+/* Linux 32-bit sub-type template defaulting to 1 CPU with PS/2-mouse/VMSVGA/PCnet-FASTIII/PIIX4+IDE DVD/PIIX4+IDE disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_D_32(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_32(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_D_32, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_Am79C973, StorageControllerType_PIIX4, StorageBus_IDE)
+
+/* Linux 64-bit sub-type template defaulting to 1 CPU with PS/2-mouse/VMSVGA/PCnet-FASTIII/PIIX4+IDE DVD/PIIX4+IDE disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_D_64(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_64(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_D_64, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_PIIX4, StorageBus_IDE)
+
+ VBOX_LINUX_SUBTYPE_D_32(Linux22, "Linux 2.2 (32-bit)", 64, 4, 2),
+ VBOX_LINUX_SUBTYPE_D_32(Linux24, "Linux 2.4 (32-bit)", 128, 16, 2),
+ VBOX_LINUX_SUBTYPE_D_64(Linux24, "Linux 2.4 (64-bit)", 1024, 16, 4),
+ VBOX_LINUX_SUBTYPE_A_32(Linux26, "Linux 2.6 / 3.x / 4.x / 5.x (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Linux26, "Linux 2.6 / 3.x / 4.x / 5.x (64-bit)", 1024, 16, 8),
+
+ VBOX_LINUX_SUBTYPE_A_32(ArchLinux, "Arch Linux (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(ArchLinux, "Arch Linux (64-bit)", 1024, 16, 8),
+
+ VBOX_LINUX_SUBTYPE_A_32(Debian, "Debian (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_64(Debian, "Debian (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_32(Debian31, "Debian 3.1 Sarge (32-bit)", 1024, 16, 8), // 32-bit only
+ VBOX_LINUX_SUBTYPE_A_32(Debian4, "Debian 4.0 Etch (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Debian4, "Debian 4.0 Etch (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_32(Debian5, "Debian 5.0 Lenny (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Debian5, "Debian 5.0 Lenny (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_32(Debian6, "Debian 6.0 Squeeze (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Debian6, "Debian 6.0 Squeeze (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_32(Debian7, "Debian 7 Wheezy (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_64(Debian7, "Debian 7 Wheezy (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_32(Debian8, "Debian 8 Jessie (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_64(Debian8, "Debian 8 Jessie (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_32(Debian9, "Debian 9 Stretch (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_64(Debian9, "Debian 9 Stretch (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_32(Debian10, "Debian 10 Buster (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_64(Debian10, "Debian 10 Buster (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_32(Debian11, "Debian 11 Bullseye (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_64(Debian11, "Debian 11 Bullseye (64-bit)", 2048, 16, 20),
+
+ VBOX_LINUX_SUBTYPE_A_WITH_OSTYPE_32(Fedora, "Fedora (32-bit)", FedoraCore, 2048, 16, 15),
+ VBOX_LINUX_SUBTYPE_A_WITH_OSTYPE_64(Fedora, "Fedora (64-bit)", FedoraCore, 2048, 16, 15),
+
+ VBOX_LINUX_SUBTYPE_A_32(Gentoo, "Gentoo (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Gentoo, "Gentoo (64-bit)", 1024, 16, 8),
+
+ VBOX_LINUX_SUBTYPE_A_32(Mandriva, "Mandriva (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Mandriva, "Mandriva (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_32(OpenMandriva_Lx, "OpenMandriva Lx (32-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(OpenMandriva_Lx, "OpenMandriva Lx (64-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_32(PCLinuxOS, "PCLinuxOS / PCLOS (32-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(PCLinuxOS, "PCLinuxOS / PCLOS (64-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_32(Mageia, "Mageia (32-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(Mageia, "Mageia (64-bit)", 2048, 16, 10),
+
+ VBOX_LINUX_SUBTYPE_B_32(Oracle, "Oracle Linux (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_B_64(Oracle, "Oracle Linux (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_B_32(Oracle4, "Oracle Linux 4.x (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_64(Oracle4, "Oracle Linux 4.x (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_32(Oracle5, "Oracle Linux 5.x (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_64(Oracle5, "Oracle Linux 5.x (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_32(Oracle6, "Oracle Linux 6.x (32-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_B_64(Oracle6, "Oracle Linux 6.x (64-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_B_64(Oracle7, "Oracle Linux 7.x (64-bit)", 2048, 16, 20), // 64-bit only
+ VBOX_LINUX_SUBTYPE_B_64(Oracle8, "Oracle Linux 8.x (64-bit)", 2048, 16, 20), // 64-bit only
+ VBOX_LINUX_SUBTYPE_B_64(Oracle9, "Oracle Linux 9.x (64-bit)", 2048, 16, 20), // 64-bit only
+
+ VBOX_LINUX_SUBTYPE_B_32(RedHat, "Red Hat (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_B_64(RedHat, "Red Hat (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_B_32(RedHat3, "Red Hat 3.x (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_64(RedHat3, "Red Hat 3.x (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_32(RedHat4, "Red Hat 4.x (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_64(RedHat4, "Red Hat 4.x (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_32(RedHat5, "Red Hat 5.x (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_64(RedHat5, "Red Hat 5.x (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_32(RedHat6, "Red Hat 6.x (32-bit)", 1024, 16, 10),
+ VBOX_LINUX_SUBTYPE_B_64(RedHat6, "Red Hat 6.x (64-bit)", 1024, 16, 10),
+ VBOX_LINUX_SUBTYPE_B_64(RedHat7, "Red Hat 7.x (64-bit)", 2048, 16, 20), // 64-bit only
+ VBOX_LINUX_SUBTYPE_B_64(RedHat8, "Red Hat 8.x (64-bit)", 2048, 16, 20), // 64-bit only
+ VBOX_LINUX_SUBTYPE_B_64(RedHat9, "Red Hat 9.x (64-bit)", 2048, 16, 20), // 64-bit only
+
+ VBOX_LINUX_SUBTYPE_A_32(OpenSUSE, "openSUSE (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(OpenSUSE, "openSUSE (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(OpenSUSE_Leap, "openSUSE Leap (64-bit)", 2048, 16, 8), // 64-bit only
+ VBOX_LINUX_SUBTYPE_A_32(OpenSUSE_Tumbleweed, "openSUSE Tumbleweed (32-bit)", 2048, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(OpenSUSE_Tumbleweed, "openSUSE Tumbleweed (64-bit)", 2048, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_32(SUSE_LE, "SUSE Linux Enterprise (32-bit)", 2048, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(SUSE_LE, "SUSE Linux Enterprise (64-bit)", 2048, 16, 8),
+
+ VBOX_LINUX_SUBTYPE_A_32(Turbolinux, "Turbolinux (32-bit)", 384, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Turbolinux, "Turbolinux (64-bit)", 384, 16, 8),
+
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu, "Ubuntu (32-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu, "Ubuntu (64-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu10_LTS, "Ubuntu 10.04 LTS (Lucid Lynx) (32-bit)", 256, 16, 3),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu10_LTS, "Ubuntu 10.04 LTS (Lucid Lynx) (64-bit)", 256, 16, 3),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu10, "Ubuntu 10.10 (Maverick Meerkat) (32-bit)", 256, 16, 3),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu10, "Ubuntu 10.10 (Maverick Meerkat) (64-bit)", 256, 16, 3),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu11, "Ubuntu 11.04 (Natty Narwhal) / 11.10 (Oneiric Ocelot) (32-bit)", 384, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu11, "Ubuntu 11.04 (Natty Narwhal) / 11.10 (Oneiric Ocelot) (64-bit)", 384, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu12_LTS, "Ubuntu 12.04 LTS (Precise Pangolin) (32-bit)", 768, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu12_LTS, "Ubuntu 12.04 LTS (Precise Pangolin) (64-bit)", 768, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu12, "Ubuntu 12.10 (Quantal Quetzal) (32-bit)", 768, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu12, "Ubuntu 12.10 (Quantal Quetzal) (64-bit)", 768, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu13, "Ubuntu 13.04 (Raring Ringtail) / 13.10 (Saucy Salamander) (32-bit)", 768, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu13, "Ubuntu 13.04 (Raring Ringtail) / 13.10 (Saucy Salamander) (64-bit)", 768, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu14_LTS, "Ubuntu 14.04 LTS (Trusty Tahr) (32-bit)", 1536, 16, 7),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu14_LTS, "Ubuntu 14.04 LTS (Trusty Tahr) (64-bit)", 1536, 16, 7),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu14, "Ubuntu 14.10 (Utopic Unicorn) (32-bit)", 1536, 16, 7),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu14, "Ubuntu 14.10 (Utopic Unicorn) (64-bit)", 1536, 16, 7),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu15, "Ubuntu 15.04 (Vivid Vervet) / 15.10 (Wily Werewolf) (32-bit)", 1536, 16, 7),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu15, "Ubuntu 15.04 (Vivid Vervet) / 15.10 (Wily Werewolf) (64-bit)", 1536, 16, 7),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu16_LTS, "Ubuntu 16.04 LTS (Xenial Xerus) (32-bit)", 1536, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu16_LTS, "Ubuntu 16.04 LTS (Xenial Xerus) (64-bit)", 1536, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu16, "Ubuntu 16.10 (Yakkety Yak) (32-bit)", 1536, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu16, "Ubuntu 16.10 (Yakkety Yak) (64-bit)", 1536, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu17, "Ubuntu 17.04 (Zesty Zapus) / 17.10 (Artful Aardvark) (32-bit)", 1536, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu17, "Ubuntu 17.04 (Zesty Zapus) / 17.10 (Artful Aardvark) (64-bit)", 1536, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu18_LTS, "Ubuntu 18.04 LTS (Bionic Beaver) (32-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu18_LTS, "Ubuntu 18.04 LTS (Bionic Beaver) (64-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu18, "Ubuntu 18.10 (Cosmic Cuttlefish) (32-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu18, "Ubuntu 18.10 (Cosmic Cuttlefish) (64-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu19, "Ubuntu 19.04 (Disco Dingo) / 19.10 (Eoan Ermine) (32-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu19, "Ubuntu 19.04 (Disco Dingo) / 19.10 (Eoan Ermine) (64-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu20_LTS, "Ubuntu 20.04 LTS (Focal Fossa) (64-bit)", 2048, 16, 25), // 64-bit only
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu20, "Ubuntu 20.10 (Groovy Gorilla) (64-bit)", 2048, 16, 25), // 64-bit only
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu21, "Ubuntu 21.04 (Hirsute Hippo) / 21.10 (Impish Indri) (64-bit)", 2048, 16, 25), // 64-bit only
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu22_LTS, "Ubuntu 22.04 LTS (Jammy Jellyfish) (64-bit)", 2048, 16, 25), // 64-bit only
+ VBOX_LINUX_SUBTYPE_A_32(Lubuntu, "Lubuntu (32-bit)", 1024, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(Lubuntu, "Lubuntu (64-bit)", 1024, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_32(Xubuntu, "Xubuntu (32-bit)", 1024, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(Xubuntu, "Xubuntu (64-bit)", 1024, 16, 10),
+
+ VBOX_LINUX_SUBTYPE_C_32(Xandros, "Xandros (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_C_64(Xandros, "Xandros (64-bit)", 1024, 16, 8),
+
+ VBOX_LINUX_SUBTYPE_A_32(Linux, "Other Linux (32-bit)", 256, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_64(Linux, "Other Linux (64-bit)", 512, 16, 8),
+
+ { "Solaris", "Solaris", "Solaris", "Oracle Solaris 10 5/09 and earlier (32-bit)",
+ VBOXOSTYPE_Solaris, VBOXOSHINT_NONE,
+ 1, 1024, 16, 32 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Solaris", "Solaris", "Solaris_64", "Oracle Solaris 10 5/09 and earlier (64-bit)",
+ VBOXOSTYPE_Solaris_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC,
+ 1, 2048, 16, 32 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Solaris", "Solaris", "Solaris10U8_or_later", "Oracle Solaris 10 10/09 and later (32-bit)",
+ VBOXOSTYPE_Solaris10U8_or_later, VBOXOSHINT_USBTABLET,
+ 1, 1024, 16, 32 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Solaris", "Solaris", "Solaris10U8_or_later_64", "Oracle Solaris 10 10/09 and later (64-bit)",
+ VBOXOSTYPE_Solaris10U8_or_later_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 32 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Solaris", "Solaris", "Solaris11_64", "Oracle Solaris 11 (64-bit)",
+ VBOXOSTYPE_Solaris11_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_RTCUTC,
+ 1, 4096, 16, 32 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Solaris", "Solaris", "OpenSolaris", "OpenSolaris / Illumos / OpenIndiana (32-bit)",
+ VBOXOSTYPE_OpenSolaris, VBOXOSHINT_USBTABLET,
+ 1, 1024, 16, 32 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Solaris", "Solaris", "OpenSolaris_64", "OpenSolaris / Illumos / OpenIndiana (64-bit)",
+ VBOXOSTYPE_OpenSolaris_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 32 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "BSD", "BSD", "FreeBSD", "FreeBSD (32-bit)",
+ VBOXOSTYPE_FreeBSD, VBOXOSHINT_NONE,
+ 1, 1024, 16, 2 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "BSD", "BSD", "FreeBSD_64", "FreeBSD (64-bit)",
+ VBOXOSTYPE_FreeBSD_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC,
+ 1, 1024, 16, 16 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "BSD", "BSD", "OpenBSD", "OpenBSD (32-bit)",
+ VBOXOSTYPE_OpenBSD, VBOXOSHINT_HWVIRTEX,
+ 1, 1024, 16, 16 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "BSD", "BSD", "OpenBSD_64", "OpenBSD (64-bit)",
+ VBOXOSTYPE_OpenBSD_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC,
+ 1, 1024, 16, 16 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "BSD", "BSD", "NetBSD", "NetBSD (32-bit)",
+ VBOXOSTYPE_NetBSD, VBOXOSHINT_RTCUTC,
+ 1, 1024, 16, 16 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "BSD", "BSD", "NetBSD_64", "NetBSD (64-bit)",
+ VBOXOSTYPE_NetBSD_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_RTCUTC,
+ 1, 1024, 16, 16 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "OS2", "IBM OS/2", "OS21x", "OS/2 1.x",
+ VBOXOSTYPE_OS21x, VBOXOSHINT_FLOPPY | VBOXOSHINT_NOUSB | VBOXOSHINT_TFRESET,
+ 1, 8, 4, 500 * _1M, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "OS2", "IBM OS/2", "OS2Warp3", "OS/2 Warp 3",
+ VBOXOSTYPE_OS2Warp3, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY,
+ 1, 48, 4, 1 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "OS2", "IBM OS/2", "OS2Warp4", "OS/2 Warp 4",
+ VBOXOSTYPE_OS2Warp4, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY,
+ 1, 64, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "OS2", "IBM OS/2", "OS2Warp45", "OS/2 Warp 4.5",
+ VBOXOSTYPE_OS2Warp45, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY,
+ 1, 128, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "OS2", "IBM OS/2", "OS2eCS", "eComStation",
+ VBOXOSTYPE_ECS, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY,
+ 1, 256, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "OS2", "IBM OS/2", "OS2ArcaOS", "ArcaOS",
+ VBOXOSTYPE_ArcaOS, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY,
+ 1, 1024, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "OS2", "IBM OS/2", "OS2", "Other OS/2",
+ VBOXOSTYPE_OS2, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY | VBOXOSHINT_NOUSB,
+ 1, 96, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "MacOS", "Mac OS X", "MacOS", "Mac OS X (32-bit)",
+ VBOXOSTYPE_MacOS, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS_64", "Mac OS X (64-bit)",
+ VBOXOSTYPE_MacOS_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS106", "Mac OS X 10.6 Snow Leopard (32-bit)",
+ VBOXOSTYPE_MacOS106, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS106_64", "Mac OS X 10.6 Snow Leopard (64-bit)",
+ VBOXOSTYPE_MacOS106_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS107_64", "Mac OS X 10.7 Lion (64-bit)",
+ VBOXOSTYPE_MacOS107_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS108_64", "Mac OS X 10.8 Mountain Lion (64-bit)", /* Aka "Mountain Kitten". */
+ VBOXOSTYPE_MacOS108_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS109_64", "Mac OS X 10.9 Mavericks (64-bit)", /* Not to be confused with McCain. */
+ VBOXOSTYPE_MacOS109_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 25 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS1010_64", "Mac OS X 10.10 Yosemite (64-bit)",
+ VBOXOSTYPE_MacOS1010_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 25 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS1011_64", "Mac OS X 10.11 El Capitan (64-bit)",
+ VBOXOSTYPE_MacOS1011_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 30 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS1012_64", "macOS 10.12 Sierra (64-bit)",
+ VBOXOSTYPE_MacOS1012_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 30 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS1013_64", "macOS 10.13 High Sierra (64-bit)",
+ VBOXOSTYPE_MacOS1013_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 30 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Other", "Other", "DOS", "DOS",
+ VBOXOSTYPE_DOS, VBOXOSHINT_FLOPPY | VBOXOSHINT_NOUSB,
+ 1, 32, 4, 500 * _1M, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "Other", "Other", "Netware", "Netware",
+ VBOXOSTYPE_Netware, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY | VBOXOSHINT_NOUSB,
+ 1, 512, 4, 4 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Other", "Other", "L4", "L4",
+ VBOXOSTYPE_L4, VBOXOSHINT_NONE,
+ 1, 64, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Other", "Other", "QNX", "QNX",
+ VBOXOSTYPE_QNX, VBOXOSHINT_HWVIRTEX,
+ 1, 512, 4, 4 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Other", "Other", "JRockitVE", "JRockitVE",
+ VBOXOSTYPE_JRockitVE, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_PAE,
+ 1, 1024, 4, 8 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_BusLogic, StorageBus_SCSI, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Other", "Other", "VBoxBS_64", "VirtualBox Bootsector Test (64-bit)",
+ VBOXOSTYPE_VBoxBS_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY | VBOXOSHINT_IOAPIC | VBOXOSHINT_PAE | VBOXOSHINT_64BIT,
+ 1, 128, 4, 0, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+};
+
+size_t Global::cOSTypes = RT_ELEMENTS(Global::sOSTypes);
+
+/**
+ * Returns an OS Type ID for the given VBOXOSTYPE value.
+ *
+ * The returned ID will correspond to the IGuestOSType::id value of one of the
+ * objects stored in the IVirtualBox::guestOSTypes
+ * (VirtualBoxImpl::COMGETTER(GuestOSTypes)) collection.
+ */
+/* static */
+const char *Global::OSTypeId(VBOXOSTYPE aOSType)
+{
+ for (size_t i = 0; i < RT_ELEMENTS(sOSTypes); ++i)
+ {
+ if (sOSTypes[i].osType == aOSType)
+ return sOSTypes[i].id;
+ }
+
+ return sOSTypes[0].id;
+}
+
+/**
+ * Maps an OS type ID string to index into sOSTypes.
+ *
+ * @returns index on success, UINT32_MAX if not found.
+ * @param pszId The OS type ID string.
+ */
+/* static */ uint32_t Global::getOSTypeIndexFromId(const char *pszId)
+{
+ for (size_t i = 0; i < RT_ELEMENTS(sOSTypes); ++i)
+ if (!RTStrICmp(pszId, Global::sOSTypes[i].id))
+ return (uint32_t)i;
+ return UINT32_MAX;
+}
+
+/*static*/ uint32_t Global::getMaxNetworkAdapters(ChipsetType_T aChipsetType)
+{
+ switch (aChipsetType)
+ {
+ case ChipsetType_PIIX3:
+ return 8;
+ case ChipsetType_ICH9:
+ return 36;
+ default:
+ return 0;
+ }
+}
+
+/*static*/ const char *
+Global::stringifyMachineState(MachineState_T aState)
+{
+ switch (aState)
+ {
+ case MachineState_Null: return GlobalCtx::tr("Null");
+ case MachineState_PoweredOff: return GlobalCtx::tr("PoweredOff");
+ case MachineState_Saved: return GlobalCtx::tr("Saved");
+ case MachineState_Teleported: return GlobalCtx::tr("Teleported");
+ case MachineState_Aborted: return GlobalCtx::tr("Aborted");
+ case MachineState_AbortedSaved: return GlobalCtx::tr("Aborted-Saved");
+ case MachineState_Running: return GlobalCtx::tr("Running");
+ case MachineState_Paused: return GlobalCtx::tr("Paused");
+ case MachineState_Stuck: return GlobalCtx::tr("GuruMeditation");
+ case MachineState_Teleporting: return GlobalCtx::tr("Teleporting");
+ case MachineState_LiveSnapshotting: return GlobalCtx::tr("LiveSnapshotting");
+ case MachineState_Starting: return GlobalCtx::tr("Starting");
+ case MachineState_Stopping: return GlobalCtx::tr("Stopping");
+ case MachineState_Saving: return GlobalCtx::tr("Saving");
+ case MachineState_Restoring: return GlobalCtx::tr("Restoring");
+ case MachineState_TeleportingPausedVM: return GlobalCtx::tr("TeleportingPausedVM");
+ case MachineState_TeleportingIn: return GlobalCtx::tr("TeleportingIn");
+ case MachineState_DeletingSnapshotOnline: return GlobalCtx::tr("DeletingSnapshotOnline");
+ case MachineState_DeletingSnapshotPaused: return GlobalCtx::tr("DeletingSnapshotPaused");
+ case MachineState_OnlineSnapshotting: return GlobalCtx::tr("OnlineSnapshotting");
+ case MachineState_RestoringSnapshot: return GlobalCtx::tr("RestoringSnapshot");
+ case MachineState_DeletingSnapshot: return GlobalCtx::tr("DeletingSnapshot");
+ case MachineState_SettingUp: return GlobalCtx::tr("SettingUp");
+ case MachineState_Snapshotting: return GlobalCtx::tr("Snapshotting");
+ default:
+ AssertMsgFailedReturn(("%d (%#x)\n", aState, aState), ::stringifyMachineState(aState));
+ }
+}
+
+/*static*/ const char *
+Global::stringifySessionState(SessionState_T aState)
+{
+ switch (aState)
+ {
+ case SessionState_Null: return GlobalCtx::tr("Null");
+ case SessionState_Unlocked: return GlobalCtx::tr("Unlocked");
+ case SessionState_Locked: return GlobalCtx::tr("Locked");
+ case SessionState_Spawning: return GlobalCtx::tr("Spawning");
+ case SessionState_Unlocking: return GlobalCtx::tr("Unlocking");
+ default:
+ AssertMsgFailedReturn(("%d (%#x)\n", aState, aState), ::stringifySessionState(aState));
+ }
+}
+
+/*static*/ const char *
+Global::stringifyStorageControllerType(StorageControllerType_T aType)
+{
+ switch (aType)
+ {
+ case StorageControllerType_Null: return GlobalCtx::tr("Null");
+ case StorageControllerType_LsiLogic: return GlobalCtx::tr("LsiLogic");
+ case StorageControllerType_BusLogic: return GlobalCtx::tr("BusLogic");
+ case StorageControllerType_IntelAhci: return GlobalCtx::tr("AHCI");
+ case StorageControllerType_PIIX3: return GlobalCtx::tr("PIIX3");
+ case StorageControllerType_PIIX4 : return GlobalCtx::tr("PIIX4");
+ case StorageControllerType_ICH6: return GlobalCtx::tr("ICH6");
+ case StorageControllerType_I82078: return GlobalCtx::tr("I82078");
+ case StorageControllerType_LsiLogicSas: return GlobalCtx::tr("LsiLogicSas");
+ case StorageControllerType_USB: return GlobalCtx::tr("USB");
+ case StorageControllerType_NVMe: return GlobalCtx::tr("NVMe");
+ case StorageControllerType_VirtioSCSI: return GlobalCtx::tr("VirtioSCSI");
+ default:
+ AssertMsgFailedReturn(("%d (%#x)\n", aType, aType), ::stringifyStorageControllerType(aType));
+ }
+}
+
+/*static*/ const char *
+Global::stringifyDeviceType(DeviceType_T aType)
+{
+ switch (aType)
+ {
+ case DeviceType_Null: return GlobalCtx::tr("Null");
+ case DeviceType_Floppy: return GlobalCtx::tr("Floppy");
+ case DeviceType_DVD: return GlobalCtx::tr("DVD");
+ case DeviceType_HardDisk: return GlobalCtx::tr("HardDisk");
+ case DeviceType_Network: return GlobalCtx::tr("Network");
+ case DeviceType_USB: return GlobalCtx::tr("USB");
+ case DeviceType_SharedFolder: return GlobalCtx::tr("ShardFolder");
+ default:
+ AssertMsgFailedReturn(("%d (%#x)\n", aType, aType), ::stringifyDeviceType(aType));
+ }
+}
+
+#if 0 /* unused */
+
+/*static*/ const char *
+Global::stringifyStorageBus(StorageBus_T aBus)
+{
+ switch (aBus)
+ {
+ case StorageBus_Null: return GlobalCtx::tr("Null");
+ case StorageBus_IDE: return GlobalCtx::tr("IDE");
+ case StorageBus_SATA: return GlobalCtx::tr("SATA");
+ case StorageBus_Floppy: return GlobalCtx::tr("Floppy");
+ case StorageBus_SAS: return GlobalCtx::tr("SAS");
+ case StorageBus_USB: return GlobalCtx::tr("USB");
+ case StorageBus_PCIe: return GlobalCtx::tr("PCIe");
+ case StorageBus_VirtioSCSI: return GlobalCtx::tr("VirtioSCSI");
+ default:
+ AssertMsgFailedReturn(("%d (%#x)\n", aBus, aBus), ::stringifyStorageBus(aBus));
+ }
+}
+
+/*static*/ const char *
+Global::stringifyReason(Reason_T aReason)
+{
+ switch (aReason)
+ {
+ case Reason_Unspecified: return GlobalCtx::tr("unspecified");
+ case Reason_HostSuspend: return GlobalCtx::tr("host suspend");
+ case Reason_HostResume: return GlobalCtx::tr("host resume");
+ case Reason_HostBatteryLow: return GlobalCtx::tr("host battery low");
+ case Reason_Snapshot: return GlobalCtx::tr("snapshot");
+ default:
+ AssertMsgFailedReturn(("%d (%#x)\n", aReason, aReason), ::stringifyReason(aReason));
+ }
+}
+
+#endif /* unused */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/GlobalStatusConversion.cpp b/src/VBox/Main/src-all/GlobalStatusConversion.cpp
new file mode 100644
index 00000000..f2cf0493
--- /dev/null
+++ b/src/VBox/Main/src-all/GlobalStatusConversion.cpp
@@ -0,0 +1,148 @@
+/* $Id: GlobalStatusConversion.cpp $ */
+/** @file
+ * VirtualBox COM global definitions - status code conversion.
+ *
+ * NOTE: This file is part of both VBoxC.dll and VBoxSVC.exe.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "Global.h"
+
+#include <iprt/assert.h>
+#include <VBox/err.h>
+
+
+/*static*/ int
+Global::vboxStatusCodeFromCOM(HRESULT aComStatus)
+{
+ switch (aComStatus)
+ {
+ case S_OK: return VINF_SUCCESS;
+
+ /* Standard COM status codes. See also RTErrConvertFromDarwinCOM */
+ case E_UNEXPECTED: return VERR_COM_UNEXPECTED;
+ case E_NOTIMPL: return VERR_NOT_IMPLEMENTED;
+ case E_OUTOFMEMORY: return VERR_NO_MEMORY;
+ case E_INVALIDARG: return VERR_INVALID_PARAMETER;
+ case E_NOINTERFACE: return VERR_NOT_SUPPORTED;
+ case E_POINTER: return VERR_INVALID_POINTER;
+#ifdef E_HANDLE
+ case E_HANDLE: return VERR_INVALID_HANDLE;
+#endif
+ case E_ABORT: return VERR_CANCELLED;
+ case E_FAIL: return VERR_GENERAL_FAILURE;
+ case E_ACCESSDENIED: return VERR_ACCESS_DENIED;
+
+ /* VirtualBox status codes */
+ case VBOX_E_OBJECT_NOT_FOUND: return VERR_COM_OBJECT_NOT_FOUND;
+ case VBOX_E_INVALID_VM_STATE: return VERR_COM_INVALID_VM_STATE;
+ case VBOX_E_VM_ERROR: return VERR_COM_VM_ERROR;
+ case VBOX_E_FILE_ERROR: return VERR_COM_FILE_ERROR;
+ case VBOX_E_IPRT_ERROR: return VERR_COM_IPRT_ERROR;
+ case VBOX_E_PDM_ERROR: return VERR_COM_PDM_ERROR;
+ case VBOX_E_INVALID_OBJECT_STATE: return VERR_COM_INVALID_OBJECT_STATE;
+ case VBOX_E_HOST_ERROR: return VERR_COM_HOST_ERROR;
+ case VBOX_E_NOT_SUPPORTED: return VERR_COM_NOT_SUPPORTED;
+ case VBOX_E_XML_ERROR: return VERR_COM_XML_ERROR;
+ case VBOX_E_INVALID_SESSION_STATE: return VERR_COM_INVALID_SESSION_STATE;
+ case VBOX_E_OBJECT_IN_USE: return VERR_COM_OBJECT_IN_USE;
+
+ default:
+ if (SUCCEEDED(aComStatus))
+ return VINF_SUCCESS;
+ /** @todo Check for the win32 facility and use the
+ * RTErrConvertFromWin32 function on windows. */
+ return VERR_UNRESOLVED_ERROR;
+ }
+}
+
+
+/*static*/ HRESULT
+Global::vboxStatusCodeToCOM(int aVBoxStatus)
+{
+ switch (aVBoxStatus)
+ {
+ case VINF_SUCCESS: return S_OK;
+
+ /* Standard COM status codes. */
+ case VERR_COM_UNEXPECTED: return E_UNEXPECTED;
+ case VERR_NOT_IMPLEMENTED: return E_NOTIMPL;
+ case VERR_NO_MEMORY: return E_OUTOFMEMORY;
+ case VERR_INVALID_PARAMETER: return E_INVALIDARG;
+ case VERR_NOT_SUPPORTED: return E_NOINTERFACE;
+ case VERR_INVALID_POINTER: return E_POINTER;
+#ifdef E_HANDLE
+ case VERR_INVALID_HANDLE: return E_HANDLE;
+#endif
+ case VERR_CANCELLED: return E_ABORT;
+ case VERR_GENERAL_FAILURE: return E_FAIL;
+ case VERR_ACCESS_DENIED: return E_ACCESSDENIED;
+
+ /* VirtualBox COM status codes. */
+ case VERR_COM_OBJECT_NOT_FOUND: return VBOX_E_OBJECT_NOT_FOUND;
+ case VERR_COM_INVALID_VM_STATE: return VBOX_E_INVALID_VM_STATE;
+ case VERR_COM_VM_ERROR: return VBOX_E_VM_ERROR;
+ case VERR_COM_FILE_ERROR: return VBOX_E_FILE_ERROR;
+ case VERR_COM_IPRT_ERROR: return VBOX_E_IPRT_ERROR;
+ case VERR_COM_PDM_ERROR: return VBOX_E_PDM_ERROR;
+ case VERR_COM_INVALID_OBJECT_STATE: return VBOX_E_INVALID_OBJECT_STATE;
+ case VERR_COM_HOST_ERROR: return VBOX_E_HOST_ERROR;
+ case VERR_COM_NOT_SUPPORTED: return VBOX_E_NOT_SUPPORTED;
+ case VERR_COM_XML_ERROR: return VBOX_E_XML_ERROR;
+ case VERR_COM_INVALID_SESSION_STATE: return VBOX_E_INVALID_SESSION_STATE;
+ case VERR_COM_OBJECT_IN_USE: return VBOX_E_OBJECT_IN_USE;
+
+ /* Other errors. */
+ case VERR_UNRESOLVED_ERROR: return E_FAIL;
+ case VERR_NOT_EQUAL: return VBOX_E_FILE_ERROR;
+ case VERR_FILE_NOT_FOUND: return VBOX_E_OBJECT_NOT_FOUND;
+ case VERR_IO_NOT_READY: return VBOX_E_INVALID_OBJECT_STATE;
+
+ /* Guest Control errors. */
+ case VERR_GSTCTL_MAX_CID_OBJECTS_REACHED: return VBOX_E_MAXIMUM_REACHED;
+ case VERR_GSTCTL_GUEST_ERROR: return VBOX_E_GSTCTL_GUEST_ERROR;
+
+ default:
+ if (RT_SUCCESS(aVBoxStatus))
+ return S_OK;
+
+ /* try categorize it */
+ if ( aVBoxStatus < 0
+ && ( aVBoxStatus > -1000
+ || (aVBoxStatus < -22000 && aVBoxStatus > -32766) )
+ )
+ return VBOX_E_IPRT_ERROR;
+ if ( aVBoxStatus < VERR_PDM_NO_SUCH_LUN / 100 * 10
+ && aVBoxStatus > VERR_PDM_NO_SUCH_LUN / 100 * 10 - 100)
+ return VBOX_E_PDM_ERROR;
+ if ( aVBoxStatus <= -1000
+ && aVBoxStatus > -5000 /* wrong, but so what... */)
+ return VBOX_E_VM_ERROR;
+
+ AssertMsgFailed(("%Rrc\n", aVBoxStatus));
+ return E_FAIL;
+ }
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/HashedPw.cpp b/src/VBox/Main/src-all/HashedPw.cpp
new file mode 100644
index 00000000..d3912989
--- /dev/null
+++ b/src/VBox/Main/src-all/HashedPw.cpp
@@ -0,0 +1,114 @@
+/* $Id: HashedPw.cpp $ */
+/** @file
+ * Main - Password Hashing
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "HashedPw.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * The prefix of a hashed password.
+ */
+static const char s_szHashedPwPrefix[] = "#SHA-512#";
+
+
+/**
+ * Checks if the password is a hashed one or not.
+ *
+ * Empty password are not considered hashed.
+ *
+ * @returns true if hashed, false if not.
+ * @param a_pstrPassword Password to inspect.
+ */
+bool VBoxIsPasswordHashed(RTCString const *a_pstrPassword)
+{
+ /* prefix */
+ if (!a_pstrPassword->startsWith(s_szHashedPwPrefix))
+ return false;
+
+ /* salt (optional) */
+ const char *pszSalt = a_pstrPassword->c_str() + sizeof(s_szHashedPwPrefix) - 1;
+ const char *pszSaltEnd = strchr(pszSalt, '#');
+ if (!pszSaltEnd)
+ return false;
+ while (pszSalt != pszSaltEnd)
+ {
+ if (!RT_C_IS_XDIGIT(*pszSalt))
+ return false;
+ pszSalt++;
+ }
+
+ /* hash */
+ uint8_t abHash[RTSHA512_HASH_SIZE];
+ int vrc = RTSha512FromString(pszSaltEnd + 1, abHash);
+ return RT_SUCCESS(vrc);
+}
+
+
+/**
+ * Hashes a plain text password.
+ *
+ * @param a_pstrPassword Plain text password to hash. This is both
+ * input and output.
+ */
+void VBoxHashPassword(RTCString *a_pstrPassword)
+{
+ AssertReturnVoid(!VBoxIsPasswordHashed(a_pstrPassword));
+
+ char szHashedPw[sizeof(s_szHashedPwPrefix) + 1 + RTSHA512_DIGEST_LEN];
+ if (a_pstrPassword->isEmpty())
+ szHashedPw[0] = '\0';
+ else
+ {
+ /* prefix */
+ char *pszHashedPw = szHashedPw;
+ strcpy(pszHashedPw, s_szHashedPwPrefix);
+ pszHashedPw += sizeof(s_szHashedPwPrefix) - 1;
+
+ /* salt */
+ *pszHashedPw++ = '#'; /* no salt yet */
+
+ /* hash */
+ uint8_t abHash[RTSHA512_HASH_SIZE];
+ RTSha512(a_pstrPassword->c_str(), a_pstrPassword->length(), abHash);
+ int vrc = RTSha512ToString(abHash, pszHashedPw, sizeof(szHashedPw) - (size_t)(pszHashedPw - &szHashedPw[0]));
+ AssertReleaseRC(vrc);
+ }
+
+ *a_pstrPassword = szHashedPw;
+}
+
diff --git a/src/VBox/Main/src-all/Logging.cpp b/src/VBox/Main/src-all/Logging.cpp
new file mode 100644
index 00000000..1a426a2d
--- /dev/null
+++ b/src/VBox/Main/src-all/Logging.cpp
@@ -0,0 +1,33 @@
+/* $Id: Logging.cpp $ */
+/** @file
+ * VirtualBox Main Logging
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*
+ * Main now always uses the defaults for logging, so nothing to do
+ * here for now.
+ */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/MachineLaunchVMCommonWorker.cpp b/src/VBox/Main/src-all/MachineLaunchVMCommonWorker.cpp
new file mode 100644
index 00000000..c03b38ff
--- /dev/null
+++ b/src/VBox/Main/src-all/MachineLaunchVMCommonWorker.cpp
@@ -0,0 +1,289 @@
+/* $Id: MachineLaunchVMCommonWorker.cpp $ */
+/** @file
+ * VirtualBox Main - VM process launcher helper for VBoxSVC & VBoxSDS.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include "MachineLaunchVMCommonWorker.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+# define HOSTSUFF_EXE ".exe"
+#else
+# define HOSTSUFF_EXE ""
+#endif
+
+
+/**
+ * Launch a VM process.
+ *
+ * The function starts the new VM process. It is a caller's responsibility
+ * to make any checks before and after calling the function.
+ * The function is a part of both VBoxSVC and VBoxSDS, so any calls to IVirtualBox
+ * and IMachine interfaces are performed using the client API.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS when new VM process started.
+ * @retval VERR_INVALID_PARAMETER when either aMachine is not Machine interface
+ * or invalid aFrontend is specified. Hmm. Come to think of it, it
+ * could also be returned in some other cases, especially if the code
+ * is buggy, so I wouldn't rely on any exact meaning here!
+ * @retval VERR_INTERNAL_ERROR when something wrong.
+ *
+ * @param aNameOrId The Machine name or id interface the VM will start for.
+ * @param aComment The comment for new VM process.
+ * @param aFrontend The desired frontend for started VM.
+ * @param aEnvironmentChanges Additional environment variables in the putenv style
+ * (VAR=VAL for setting, VAR for unsetting) for new VM process.
+ * @param aExtraArg Extra argument for the VM process. Ignored if
+ * empty string.
+ * @param aFilename Start new VM using specified filename. Only filename
+ * without path is allowed. Default filename is used if
+ * empty.
+ * @param aFlags Flags for RTProcCreateEx functions family if
+ * required (RTPROC_FLAGS_XXX).
+ * @param aExtraData Additional data for RTProcCreateX functions family
+ * if required. Content is defined by the flags.
+ * @param aPid The PID of created process is returned here
+ */
+int MachineLaunchVMCommonWorker(const Utf8Str &aNameOrId,
+ const Utf8Str &aComment,
+ const Utf8Str &aFrontend,
+ const std::vector<com::Utf8Str> &aEnvironmentChanges,
+ const Utf8Str &aExtraArg,
+ const Utf8Str &aFilename,
+ uint32_t aFlags,
+ void *aExtraData,
+ RTPROCESS &aPid)
+{
+ NOREF(aNameOrId);
+ NOREF(aComment);
+ NOREF(aFlags);
+ NOREF(aExtraData);
+ NOREF(aExtraArg);
+ NOREF(aFilename);
+
+ /* Get the path to the executable directory w/ trailing slash: */
+ char szPath[RTPATH_MAX];
+ int vrc = RTPathAppPrivateArch(szPath, sizeof(szPath));
+ AssertRCReturn(vrc, vrc);
+ size_t cbBufLeft = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath));
+ AssertReturn(cbBufLeft > 0, VERR_FILENAME_TOO_LONG);
+ char *pszNamePart = &szPath[cbBufLeft]; NOREF(pszNamePart);
+ cbBufLeft = sizeof(szPath) - cbBufLeft;
+
+ /* The process started when launching a VM with separate UI/VM processes is always
+ * the UI process, i.e. needs special handling as it won't claim the session. */
+ bool fSeparate = aFrontend.endsWith("separate", Utf8Str::CaseInsensitive); NOREF(fSeparate);
+
+ aPid = NIL_RTPROCESS;
+
+ RTENV hEnv = RTENV_DEFAULT;
+ if (!aEnvironmentChanges.empty())
+ {
+#ifdef IN_VBOXSVC
+ /* VBoxSVC: clone the current environment */
+ vrc = RTEnvClone(&hEnv, RTENV_DEFAULT);
+#else
+ /* VBoxSDS: Create a change record environment since RTProcCreateEx has to
+ build the final environment from the profile of the VBoxSDS caller. */
+ aFlags |= RTPROC_FLAGS_ENV_CHANGE_RECORD;
+ vrc = RTEnvCreateChangeRecord(&hEnv);
+#endif
+ AssertRCReturn(vrc, vrc);
+
+ /* Apply the specified environment changes. */
+ for (std::vector<com::Utf8Str>::const_iterator itEnv = aEnvironmentChanges.begin();
+ itEnv != aEnvironmentChanges.end();
+ ++itEnv)
+ {
+ vrc = RTEnvPutEx(hEnv, itEnv->c_str());
+ AssertRCReturnStmt(vrc, RTEnvDestroy(hEnv), vrc);
+ }
+ }
+
+#ifdef VBOX_WITH_QTGUI
+ if ( !aFrontend.compare("gui", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("GUI/Qt", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("separate", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("gui/separate", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("GUI/Qt/separate", Utf8Str::CaseInsensitive))
+ {
+# ifdef RT_OS_DARWIN /* Avoid Launch Services confusing this with the selector by using a helper app. */
+
+# define OSX_APP_NAME "VirtualBoxVM"
+# define OSX_APP_PATH_FMT "/Resources/%s.app/Contents/MacOS/VirtualBoxVM"
+# define OSX_APP_PATH_WITH_NAME "/Resources/VirtualBoxVM.app/Contents/MacOS/VirtualBoxVM"
+
+ /* Modify the base path so that we don't need to use ".." below. */
+ RTPathStripTrailingSlash(szPath);
+ RTPathStripFilename(szPath);
+ cbBufLeft = strlen(szPath);
+ pszNamePart = &szPath[cbBufLeft]; Assert(!*pszNamePart);
+ cbBufLeft = sizeof(szPath) - cbBufLeft;
+
+ if (aFilename.isNotEmpty() && strpbrk(aFilename.c_str(), "./\\:") == NULL)
+ {
+ ssize_t cch = RTStrPrintf2(pszNamePart, cbBufLeft, OSX_APP_PATH_FMT, aFilename.c_str());
+ AssertReturn(cch > 0, VERR_FILENAME_TOO_LONG);
+ /* there is a race, but people using this deserve the failure */
+ if (!RTFileExists(szPath))
+ *pszNamePart = '\0';
+ }
+ if (!*pszNamePart)
+ {
+ vrc = RTStrCopy(pszNamePart, cbBufLeft, OSX_APP_PATH_WITH_NAME);
+ AssertRCReturn(vrc, vrc);
+ }
+# else
+ static const char s_szVirtualBox_exe[] = "VirtualBoxVM" HOSTSUFF_EXE;
+ vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVirtualBox_exe);
+ AssertRCReturn(vrc, vrc);
+# endif
+
+ const char *apszArgs[] =
+ {
+ szPath,
+ "--comment", aComment.c_str(),
+ "--startvm", aNameOrId.c_str(),
+ "--no-startvm-errormsgbox",
+ NULL, /* For "--separate". */
+ NULL, /* For "--sup-startup-log". */
+ NULL
+ };
+ unsigned iArg = 6;
+ if (fSeparate)
+ apszArgs[iArg++] = "--separate";
+ if (aExtraArg.isNotEmpty())
+ apszArgs[iArg++] = aExtraArg.c_str();
+
+ vrc = RTProcCreateEx(szPath, apszArgs, hEnv, aFlags, NULL, NULL, NULL, NULL, NULL, aExtraData, &aPid);
+ }
+#else /* !VBOX_WITH_QTGUI */
+ if (0)
+ ;
+#endif /* VBOX_WITH_QTGUI */
+
+ else
+
+#ifdef VBOX_WITH_VBOXSDL
+ if ( !aFrontend.compare("sdl", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("GUI/SDL", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("sdl/separate", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("GUI/SDL/separate", Utf8Str::CaseInsensitive))
+ {
+ static const char s_szVBoxSDL_exe[] = "VBoxSDL" HOSTSUFF_EXE;
+ vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVBoxSDL_exe);
+ AssertRCReturn(vrc, vrc);
+
+ const char *apszArgs[] =
+ {
+ szPath,
+ "--comment", aComment.c_str(),
+ "--startvm", aNameOrId.c_str(),
+ NULL, /* For "--separate". */
+ NULL, /* For "--sup-startup-log". */
+ NULL
+ };
+ unsigned iArg = 5;
+ if (fSeparate)
+ apszArgs[iArg++] = "--separate";
+ if (aExtraArg.isNotEmpty())
+ apszArgs[iArg++] = aExtraArg.c_str();
+
+ vrc = RTProcCreateEx(szPath, apszArgs, hEnv, aFlags, NULL, NULL, NULL, NULL, NULL, aExtraData, &aPid);
+ }
+#else /* !VBOX_WITH_VBOXSDL */
+ if (0)
+ ;
+#endif /* !VBOX_WITH_VBOXSDL */
+
+ else
+
+#ifdef VBOX_WITH_HEADLESS
+ if ( !aFrontend.compare("headless", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("capture", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("vrdp", Utf8Str::CaseInsensitive) /* Deprecated. Same as headless. */
+ )
+ {
+ /* On pre-4.0 the "headless" type was used for passing "--vrdp off" to VBoxHeadless to let it work in OSE,
+ * which did not contain VRDP server. In VBox 4.0 the remote desktop server (VRDE) is optional,
+ * and a VM works even if the server has not been installed.
+ * So in 4.0 the "headless" behavior remains the same for default VBox installations.
+ * Only if a VRDE has been installed and the VM enables it, the "headless" will work
+ * differently in 4.0 and 3.x.
+ */
+ static const char s_szVBoxHeadless_exe[] = "VBoxHeadless" HOSTSUFF_EXE;
+ vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVBoxHeadless_exe);
+ AssertRCReturn(vrc, vrc);
+
+ const char *apszArgs[] =
+ {
+ szPath,
+ "--comment", aComment.c_str(),
+ "--startvm", aNameOrId.c_str(),
+ "--vrde", "config",
+ NULL, /* For "--capture". */
+ NULL, /* For "--sup-startup-log". */
+ NULL
+ };
+ unsigned iArg = 7;
+ if (!aFrontend.compare("capture", Utf8Str::CaseInsensitive))
+ apszArgs[iArg++] = "--capture";
+ if (aExtraArg.isNotEmpty())
+ apszArgs[iArg++] = aExtraArg.c_str();
+
+# ifdef RT_OS_WINDOWS
+ aFlags |= RTPROC_FLAGS_NO_WINDOW;
+# endif
+ vrc = RTProcCreateEx(szPath, apszArgs, hEnv, aFlags, NULL, NULL, NULL, NULL, NULL, aExtraData, &aPid);
+ }
+#else /* !VBOX_WITH_HEADLESS */
+ if (0)
+ ;
+#endif /* !VBOX_WITH_HEADLESS */
+ else
+ vrc = VERR_INVALID_PARAMETER;
+
+ RTEnvDestroy(hEnv);
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/Main/src-all/Makefile.kup b/src/VBox/Main/src-all/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-all/Makefile.kup
diff --git a/src/VBox/Main/src-all/NvramStoreImpl.cpp b/src/VBox/Main/src-all/NvramStoreImpl.cpp
new file mode 100644
index 00000000..d81c9651
--- /dev/null
+++ b/src/VBox/Main/src-all/NvramStoreImpl.cpp
@@ -0,0 +1,1645 @@
+/* $Id: NvramStoreImpl.cpp $ */
+/** @file
+ * VirtualBox COM NVRAM store class implementation
+ */
+
+/*
+ * Copyright (C) 2021-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_NVRAMSTORE
+#include "LoggingNew.h"
+
+#include "NvramStoreImpl.h"
+#ifdef VBOX_COM_INPROC
+# include "ConsoleImpl.h"
+#else
+# include "MachineImpl.h"
+# include "GuestOSTypeImpl.h"
+# include "AutoStateDep.h"
+#endif
+#include "UefiVariableStoreImpl.h"
+#include "VirtualBoxImpl.h"
+
+#include "AutoCaller.h"
+
+#include <VBox/com/array.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/err.h>
+
+#include <iprt/cpp/utils.h>
+#include <iprt/efi.h>
+#include <iprt/file.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+
+
+// defines
+////////////////////////////////////////////////////////////////////////////////
+
+/** Version of the NVRAM saved state unit. */
+#define NVRAM_STORE_SAVED_STATE_VERSION 1
+
+
+// globals
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * NVRAM store driver instance data.
+ */
+typedef struct DRVMAINNVRAMSTORE
+{
+ /** Pointer to the keyboard object. */
+ NvramStore *pNvramStore;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Our VFS connector interface. */
+ PDMIVFSCONNECTOR IVfs;
+} DRVMAINNVRAMSTORE, *PDRVMAINNVRAMSTORE;
+
+/** The NVRAM store map keyed by namespace/entity. */
+typedef std::map<Utf8Str, RTVFSFILE> NvramStoreMap;
+/** The NVRAM store map iterator. */
+typedef std::map<Utf8Str, RTVFSFILE>::iterator NvramStoreIter;
+
+struct BackupableNvramStoreData
+{
+ BackupableNvramStoreData()
+ { }
+
+ /** The NVRAM file path. */
+ com::Utf8Str strNvramPath;
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ /** The key id used for encrypting the NVRAM file */
+ com::Utf8Str strKeyId;
+ /** The key store containing the encrypting DEK */
+ com::Utf8Str strKeyStore;
+#endif
+ /** The NVRAM store. */
+ NvramStoreMap mapNvram;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+// NvramStore::Data structure
+/////////////////////////////////////////////////////////////////////////////
+
+struct NvramStore::Data
+{
+ Data()
+ : pParent(NULL)
+#ifdef VBOX_COM_INPROC
+ , cRefs(0)
+ , fSsmSaved(false)
+#endif
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ , mpKeyStore(NULL)
+#endif
+ { }
+
+#ifdef VBOX_COM_INPROC
+ /** The Console owning this NVRAM store. */
+ Console * const pParent;
+ /** Number of references held to this NVRAM store from the various devices/drivers. */
+ volatile uint32_t cRefs;
+ /** Flag whether the NVRAM data was saved during a save state operation
+ * preventing it from getting written to the backing file. */
+ bool fSsmSaved;
+#else
+ /** The Machine object owning this NVRAM store. */
+ Machine * const pParent;
+ /** The peer NVRAM store object. */
+ ComObjPtr<NvramStore> pPeer;
+ /** The UEFI variable store. */
+ const ComObjPtr<UefiVariableStore> pUefiVarStore;
+#endif
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ /* Store for secret keys. */
+ SecretKeyStore *mpKeyStore;
+#endif
+
+ Backupable<BackupableNvramStoreData> bd;
+};
+
+// constructor / destructor
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(NvramStore)
+
+HRESULT NvramStore::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void NvramStore::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initialization stuff shared across the different methods.
+ *
+ * @returns COM result indicator
+ */
+int NvramStore::initImpl()
+{
+ m = new Data();
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+# ifdef VBOX_COM_INPROC
+ bool fNonPageable = true;
+# else
+ /* Non-pageable memory is not accessible for non-VM process */
+ bool fNonPageable = false;
+# endif
+
+ m->mpKeyStore = new SecretKeyStore(fNonPageable /* fKeyBufNonPageable */);
+ AssertReturn(m->mpKeyStore, VERR_NO_MEMORY);
+#endif
+
+ return VINF_SUCCESS;
+}
+
+
+#if !defined(VBOX_COM_INPROC)
+/**
+ * Initializes the NVRAM store object.
+ *
+ * @returns COM result indicator
+ */
+HRESULT NvramStore::init(Machine *aParent)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ int vrc = initImpl();
+ if (RT_FAILURE(vrc))
+ return E_FAIL;
+
+ /* share the parent weakly */
+ unconst(m->pParent) = aParent;
+
+ m->bd.allocate();
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Initializes the NVRAM store object given another NVRAM store object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ */
+HRESULT NvramStore::init(Machine *aParent, NvramStore *that)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p, that: %p\n", aParent, that));
+
+ ComAssertRet(aParent && that, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ initImpl();
+
+ unconst(m->pParent) = aParent;
+ m->pPeer = that;
+
+ AutoWriteLock thatlock(that COMMA_LOCKVAL_SRC_POS);
+ m->bd.share(that->m->bd);
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Initializes the guest object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ */
+HRESULT NvramStore::initCopy(Machine *aParent, NvramStore *that)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p, that: %p\n", aParent, that));
+
+ ComAssertRet(aParent && that, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ initImpl();
+
+ unconst(m->pParent) = aParent;
+ // mPeer is left null
+
+ AutoWriteLock thatlock(that COMMA_LOCKVAL_SRC_POS);
+ m->bd.attachCopy(that->m->bd);
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+#else
+
+/**
+ * Initializes the NVRAM store object.
+ *
+ * @returns COM result indicator
+ * @param aParent Handle of our parent object
+ * @param strNonVolatileStorageFile The NVRAM file path.
+ */
+HRESULT NvramStore::init(Console *aParent, const com::Utf8Str &strNonVolatileStorageFile)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ initImpl();
+
+ unconst(m->pParent) = aParent;
+
+ m->bd.allocate();
+ m->bd->strNvramPath = strNonVolatileStorageFile;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+#endif /* VBOX_COM_INPROC */
+
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void NvramStore::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(m->pParent) = NULL;
+#ifndef VBOX_COM_INPROC
+ unconst(m->pUefiVarStore) = NULL;
+#endif
+
+ /* Delete the NVRAM content. */
+ NvramStoreIter it = m->bd->mapNvram.begin();
+ while (it != m->bd->mapNvram.end())
+ {
+ RTVfsFileRelease(it->second);
+ it++;
+ }
+
+ m->bd->mapNvram.clear();
+ m->bd.free();
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (m->mpKeyStore != NULL)
+ delete m->mpKeyStore;
+#endif
+
+ delete m;
+ m = NULL;
+
+ LogFlowThisFuncLeave();
+}
+
+
+HRESULT NvramStore::getNonVolatileStorageFile(com::Utf8Str &aNonVolatileStorageFile)
+{
+#ifndef VBOX_COM_INPROC
+ Utf8Str strTmp;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ strTmp = m->bd->strNvramPath;
+ }
+
+ AutoReadLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ if (strTmp.isEmpty())
+ strTmp = m->pParent->i_getDefaultNVRAMFilename();
+ if (strTmp.isNotEmpty())
+ m->pParent->i_calculateFullPath(strTmp, aNonVolatileStorageFile);
+#else
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aNonVolatileStorageFile = m->bd->strNvramPath;
+#endif
+
+ return S_OK;
+}
+
+
+HRESULT NvramStore::getUefiVariableStore(ComPtr<IUefiVariableStore> &aUefiVarStore)
+{
+#ifndef VBOX_COM_INPROC
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ Utf8Str strPath;
+ NvramStore::getNonVolatileStorageFile(strPath);
+
+ /* We need a write lock because of the lazy initialization. */
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Check if we have to create the UEFI variable store object */
+ HRESULT hrc = S_OK;
+ if (!m->pUefiVarStore)
+ {
+ /* Load the NVRAM file first if it isn't already. */
+ if (!m->bd->mapNvram.size())
+ {
+ int vrc = i_loadStore(strPath.c_str());
+ if (RT_FAILURE(vrc))
+ hrc = setError(E_FAIL, tr("Loading the NVRAM store failed (%Rrc)\n"), vrc);
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ NvramStoreIter it = m->bd->mapNvram.find("efi/nvram");
+ if (it != m->bd->mapNvram.end())
+ {
+ unconst(m->pUefiVarStore).createObject();
+ m->pUefiVarStore->init(this, m->pParent);
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("The UEFI NVRAM file is not existing for this machine."));
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ m->pUefiVarStore.queryInterfaceTo(aUefiVarStore.asOutParam());
+
+ /* Mark the NVRAM store as potentially modified. */
+ m->pParent->i_setModified(Machine::IsModified_NvramStore);
+ }
+
+ return hrc;
+#else
+ NOREF(aUefiVarStore);
+ return E_NOTIMPL;
+#endif
+}
+
+
+HRESULT NvramStore::getKeyId(com::Utf8Str &aKeyId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ aKeyId = m->bd->strKeyId;
+#else
+ aKeyId = com::Utf8Str::Empty;
+#endif
+
+ return S_OK;
+}
+
+
+HRESULT NvramStore::getKeyStore(com::Utf8Str &aKeyStore)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ aKeyStore = m->bd->strKeyStore;
+#else
+ aKeyStore = com::Utf8Str::Empty;
+#endif
+
+ return S_OK;
+}
+
+
+HRESULT NvramStore::initUefiVariableStore(ULONG aSize)
+{
+#ifndef VBOX_COM_INPROC
+ if (aSize != 0)
+ return setError(E_NOTIMPL, tr("Supporting another NVRAM size apart from the default one is not supported right now"));
+
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ Utf8Str strPath;
+ NvramStore::getNonVolatileStorageFile(strPath);
+
+ /* We need a write lock because of the lazy initialization. */
+ AutoReadLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->pParent->i_getFirmwareType() == FirmwareType_BIOS)
+ return setError(VBOX_E_NOT_SUPPORTED, tr("The selected firmware type doesn't support a UEFI variable store"));
+
+ /* Load the NVRAM file first if it isn't already. */
+ HRESULT hrc = S_OK;
+ if (!m->bd->mapNvram.size())
+ {
+ int vrc = i_loadStore(strPath.c_str());
+ if (RT_FAILURE(vrc))
+ hrc = setError(E_FAIL, tr("Loading the NVRAM store failed (%Rrc)\n"), vrc);
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = VINF_SUCCESS;
+ RTVFSFILE hVfsUefiVarStore = NIL_RTVFSFILE;
+ NvramStoreIter it = m->bd->mapNvram.find("efi/nvram");
+ if (it != m->bd->mapNvram.end())
+ hVfsUefiVarStore = it->second;
+ else
+ {
+ /* Create a new file. */
+ vrc = RTVfsMemFileCreate(NIL_RTVFSIOSTREAM, 0 /*cbEstimate*/, &hVfsUefiVarStore);
+ if (RT_SUCCESS(vrc))
+ {
+ /** @todo The size is hardcoded to match what the firmware image uses right now which is a gross hack... */
+ vrc = RTVfsFileSetSize(hVfsUefiVarStore, 540672, RTVFSFILE_SIZE_F_NORMAL);
+ if (RT_SUCCESS(vrc))
+ m->bd->mapNvram["efi/nvram"] = hVfsUefiVarStore;
+ else
+ RTVfsFileRelease(hVfsUefiVarStore);
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTEfiVarStoreCreate(hVfsUefiVarStore, 0 /*offStore*/, 0 /*cbStore*/, RTEFIVARSTORE_CREATE_F_DEFAULT, 0 /*cbBlock*/,
+ NULL /*pErrInfo*/);
+ if (RT_FAILURE(vrc))
+ return setError(E_FAIL, tr("Failed to initialize the UEFI variable store (%Rrc)"), vrc);
+ }
+ else
+ return setError(E_FAIL, tr("Failed to initialize the UEFI variable store (%Rrc)"), vrc);
+
+ m->pParent->i_setModified(Machine::IsModified_NvramStore);
+ }
+
+ return hrc;
+#else
+ NOREF(aSize);
+ return E_NOTIMPL;
+#endif
+}
+
+
+Utf8Str NvramStore::i_getNonVolatileStorageFile()
+{
+ AutoCaller autoCaller(this);
+ AssertReturn(autoCaller.isOk(), Utf8Str::Empty);
+
+ Utf8Str strTmp;
+ NvramStore::getNonVolatileStorageFile(strTmp);
+ return strTmp;
+}
+
+
+/**
+ * Loads the NVRAM store from the given TAR filesystem stream.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFssTar Handle to the tar filesystem stream.
+ */
+int NvramStore::i_loadStoreFromTar(RTVFSFSSTREAM hVfsFssTar)
+{
+ int vrc = VINF_SUCCESS;
+
+ /*
+ * Process the stream.
+ */
+ for (;;)
+ {
+ /*
+ * Retrieve the next object.
+ */
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ vrc = RTVfsFsStrmNext(hVfsFssTar, &pszName, NULL, &hVfsObj);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_EOF)
+ vrc = VINF_SUCCESS;
+ break;
+ }
+
+ RTFSOBJINFO UnixInfo;
+ vrc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(vrc))
+ {
+ switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FILE:
+ {
+ LogRel(("NvramStore: Loading '%s' from archive\n", pszName));
+ RTVFSIOSTREAM hVfsIosEntry = RTVfsObjToIoStream(hVfsObj);
+ Assert(hVfsIosEntry != NIL_RTVFSIOSTREAM);
+
+ RTVFSFILE hVfsFileEntry;
+ vrc = RTVfsMemorizeIoStreamAsFile(hVfsIosEntry, RTFILE_O_READ | RTFILE_O_WRITE, &hVfsFileEntry);
+ if (RT_FAILURE(vrc))
+ break;
+ RTVfsIoStrmRelease(hVfsIosEntry);
+
+ m->bd->mapNvram[Utf8Str(pszName)] = hVfsFileEntry;
+ break;
+ }
+ case RTFS_TYPE_DIRECTORY:
+ break;
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+
+ /*
+ * Release the current object and string.
+ */
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ return vrc;
+}
+
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+/**
+ * Sets up the encryption or decryption machinery.
+ *
+ * @returns VBox status code.
+ * @param hVfsIosInOut Handle to the input stream to be decrypted or the destination to the encrypted
+ * output is written to.
+ * @param fEncrypt Flag whether to setup encryption or decryption.
+ * @param ppCryptoIf Where to store the pointer to the cryptographic interface which needs to be released
+ * when done.
+ * @param ppKey Where to store the pointer to the secret key buffer which needs to be released when done.
+ * @param phVfsIos Where to store the handle to the plaintext I/O stream (either input or output) on success.
+ */
+int NvramStore::i_setupEncryptionOrDecryption(RTVFSIOSTREAM hVfsIosInOut, bool fEncrypt,
+ PCVBOXCRYPTOIF *ppCryptoIf, SecretKey **ppKey,
+ PRTVFSIOSTREAM phVfsIos)
+{
+ int vrc = VINF_SUCCESS;
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ SecretKey *pKey = NULL;
+ const char *pszPassword = NULL;
+
+ vrc = i_retainCryptoIf(&pCryptoIf);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = m->mpKeyStore->retainSecretKey(m->bd->strKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ pszPassword = (const char *)pKey->getKeyBuffer();
+ if (fEncrypt)
+ vrc = pCryptoIf->pfnCryptoIoStrmFromVfsIoStrmEncrypt(hVfsIosInOut, m->bd->strKeyStore.c_str(), pszPassword,
+ phVfsIos);
+ else
+ vrc = pCryptoIf->pfnCryptoIoStrmFromVfsIoStrmDecrypt(hVfsIosInOut, m->bd->strKeyStore.c_str(), pszPassword,
+ phVfsIos);
+ if (RT_SUCCESS(vrc))
+ {
+ *ppCryptoIf = pCryptoIf;
+ *ppKey = pKey;
+ return VINF_SUCCESS;
+ }
+ else
+ LogRelMax(10, ("Failed to decrypt the NVRAM store using secret key ID '%s' with %Rrc\n",
+ m->bd->strKeyId.c_str(), vrc));
+
+ m->mpKeyStore->releaseSecretKey(m->bd->strKeyId);
+ }
+ else
+ LogRelMax(10, ("Failed to retain the secret key ID '%s' with %Rrc\n",
+ m->bd->strKeyId.c_str(), vrc));
+
+ i_releaseCryptoIf(pCryptoIf);
+ }
+ else
+ LogRelMax(10, ("Failed to retain the cryptographic interface with %Rrc\n", vrc));
+
+ return vrc;
+}
+
+/**
+ * Releases all resources acquired in NvramStore::i_setupEncryptionOrDecryption().
+ *
+ * @returns nothing.
+ * @param hVfsIos Handle to the I/O stream previously created.
+ * @param pCryptoIf Pointer to the cryptographic interface being released.
+ * @param pKey Pointer to the key buffer being released.
+ */
+void NvramStore::i_releaseEncryptionOrDecryptionResources(RTVFSIOSTREAM hVfsIos, PCVBOXCRYPTOIF pCryptoIf,
+ SecretKey *pKey)
+{
+ Assert(hVfsIos != NIL_RTVFSIOSTREAM);
+ AssertPtr(pCryptoIf);
+ AssertPtr(pKey);
+
+ i_releaseCryptoIf(pCryptoIf);
+ pKey->release();
+ RTVfsIoStrmRelease(hVfsIos);
+}
+#endif
+
+
+/**
+ * Loads the NVRAM store.
+ *
+ * @returns IPRT status code.
+ */
+int NvramStore::i_loadStore(const char *pszPath)
+{
+ uint64_t cbStore = 0;
+ int vrc = RTFileQuerySizeByPath(pszPath, &cbStore);
+ if (RT_SUCCESS(vrc))
+ {
+ if (cbStore <= _1M) /* Arbitrary limit to fend off bogus files because the file will be read into memory completely. */
+ {
+ /*
+ * Old NVRAM files just consist of the EFI variable store whereas starting
+ * with VirtualBox 7.0 and the introduction of the TPM the need to handle multiple
+ * independent NVRAM files came up. For those scenarios all NVRAM states are collected
+ * in a tar archive.
+ *
+ * Here we detect whether the file is the new tar archive format or whether it is just
+ * the plain EFI variable store file.
+ */
+ RTVFSIOSTREAM hVfsIosNvram;
+ vrc = RTVfsIoStrmOpenNormal(pszPath, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE,
+ &hVfsIosNvram);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hVfsIosDecrypted = NIL_RTVFSIOSTREAM;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ SecretKey *pKey = NULL;
+
+ if ( m->bd->strKeyId.isNotEmpty()
+ && m->bd->strKeyStore.isNotEmpty())
+ vrc = i_setupEncryptionOrDecryption(hVfsIosNvram, false /*fEncrypt*/,
+ &pCryptoIf, &pKey, &hVfsIosDecrypted);
+#endif
+ if (RT_SUCCESS(vrc))
+ {
+ /* Read the content. */
+ RTVFSFILE hVfsFileNvram;
+ vrc = RTVfsMemorizeIoStreamAsFile( hVfsIosDecrypted != NIL_RTVFSIOSTREAM
+ ? hVfsIosDecrypted
+ : hVfsIosNvram,
+ RTFILE_O_READ, &hVfsFileNvram);
+ if (RT_SUCCESS(vrc))
+ {
+ if (RT_SUCCESS(vrc))
+ {
+ /* Try to parse it as an EFI variable store. */
+ RTVFS hVfsEfiVarStore;
+ vrc = RTEfiVarStoreOpenAsVfs(hVfsFileNvram, RTVFSMNT_F_READ_ONLY, 0 /*fVarStoreFlags*/, &hVfsEfiVarStore,
+ NULL /*pErrInfo*/);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsFileSeek(hVfsFileNvram, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(vrc);
+
+ RTVfsFileRetain(hVfsFileNvram); /* Retain a new reference for the map. */
+ m->bd->mapNvram[Utf8Str("efi/nvram")] = hVfsFileNvram;
+
+ RTVfsRelease(hVfsEfiVarStore);
+ }
+ else if (vrc == VERR_VFS_UNKNOWN_FORMAT)
+ {
+ /* Check for the new style tar archive. */
+ vrc = RTVfsFileSeek(hVfsFileNvram, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(vrc);
+
+ RTVFSIOSTREAM hVfsIosTar = RTVfsFileToIoStream(hVfsFileNvram);
+ Assert(hVfsIosTar != NIL_RTVFSIOSTREAM);
+
+ RTVFSFSSTREAM hVfsFssTar;
+ vrc = RTZipTarFsStreamFromIoStream(hVfsIosTar, 0 /*fFlags*/, &hVfsFssTar);
+ RTVfsIoStrmRelease(hVfsIosTar);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = i_loadStoreFromTar(hVfsFssTar);
+ RTVfsFsStrmRelease(hVfsFssTar);
+ }
+ else
+ LogRel(("The given NVRAM file is neither a raw UEFI variable store nor a tar archive (opening failed with %Rrc)\n", vrc));
+ }
+ else
+ LogRel(("Opening the UEFI variable store '%s' failed with %Rrc\n", pszPath, vrc));
+
+ RTVfsFileRelease(hVfsFileNvram);
+ }
+ else
+ LogRel(("Failed to memorize NVRAM store '%s' with %Rrc\n", pszPath, vrc));
+ }
+ }
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (hVfsIosDecrypted != NIL_RTVFSIOSTREAM)
+ i_releaseEncryptionOrDecryptionResources(hVfsIosDecrypted, pCryptoIf, pKey);
+#endif
+
+ RTVfsIoStrmRelease(hVfsIosNvram);
+ }
+ else
+ LogRelMax(10, ("NVRAM store '%s' couldn't be opened with %Rrc\n", pszPath, vrc));
+ }
+ else
+ LogRelMax(10, ("NVRAM store '%s' exceeds limit of %u bytes, actual size is %u\n",
+ pszPath, _1M, cbStore));
+ }
+ else if (vrc == VERR_FILE_NOT_FOUND) /* Valid for the first run where no NVRAM file is there. */
+ vrc = VINF_SUCCESS;
+
+ return vrc;
+}
+
+
+/**
+ * Saves the NVRAM store as a tar archive.
+ */
+int NvramStore::i_saveStoreAsTar(const char *pszPath)
+{
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ RTVFSIOSTREAM hVfsIos;
+
+ int vrc = RTVfsChainOpenIoStream(pszPath, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hVfsIosEncrypted = NIL_RTVFSIOSTREAM;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ SecretKey *pKey = NULL;
+
+ if ( m->bd->strKeyId.isNotEmpty()
+ && m->bd->strKeyStore.isNotEmpty())
+ vrc = i_setupEncryptionOrDecryption(hVfsIos, true /*fEncrypt*/,
+ &pCryptoIf, &pKey, &hVfsIosEncrypted);
+#endif
+
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSFSSTREAM hVfsFss;
+ vrc = RTZipTarFsStreamToIoStream( hVfsIosEncrypted != NIL_RTVFSIOSTREAM
+ ? hVfsIosEncrypted
+ : hVfsIos,
+ RTZIPTARFORMAT_GNU, 0 /*fFlags*/, &hVfsFss);
+ if (RT_SUCCESS(vrc))
+ {
+ NvramStoreIter it = m->bd->mapNvram.begin();
+
+ while (it != m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFile = it->second;
+
+ vrc = RTVfsFileSeek(hVfsFile, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(vrc);
+
+ RTVFSOBJ hVfsObj = RTVfsObjFromFile(hVfsFile);
+ vrc = RTVfsFsStrmAdd(hVfsFss, it->first.c_str(), hVfsObj, 0 /*fFlags*/);
+ RTVfsObjRelease(hVfsObj);
+ if (RT_FAILURE(vrc))
+ break;
+
+ it++;
+ }
+
+ RTVfsFsStrmRelease(hVfsFss);
+ }
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (hVfsIosEncrypted != NIL_RTVFSIOSTREAM)
+ i_releaseEncryptionOrDecryptionResources(hVfsIosEncrypted, pCryptoIf, pKey);
+#endif
+ }
+
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+
+ return vrc;
+}
+
+
+int NvramStore::i_retainCryptoIf(PCVBOXCRYPTOIF *ppCryptoIf)
+{
+#ifdef VBOX_COM_INPROC
+ return m->pParent->i_retainCryptoIf(ppCryptoIf);
+#else
+ HRESULT hrc = m->pParent->i_getVirtualBox()->i_retainCryptoIf(ppCryptoIf);
+ if (SUCCEEDED(hrc))
+ return VINF_SUCCESS;
+
+ return VERR_COM_IPRT_ERROR;
+#endif
+}
+
+
+int NvramStore::i_releaseCryptoIf(PCVBOXCRYPTOIF pCryptoIf)
+{
+#ifdef VBOX_COM_INPROC
+ return m->pParent->i_releaseCryptoIf(pCryptoIf);
+#else
+ HRESULT hrc = m->pParent->i_getVirtualBox()->i_releaseCryptoIf(pCryptoIf);
+ if (SUCCEEDED(hrc))
+ return VINF_SUCCESS;
+
+ return VERR_COM_IPRT_ERROR;
+#endif
+}
+
+
+/**
+ * Saves the NVRAM store.
+ *
+ * @returns IPRT status code.
+ */
+int NvramStore::i_saveStore(void)
+{
+ int vrc = VINF_SUCCESS;
+
+ Utf8Str strTmp;
+ NvramStore::getNonVolatileStorageFile(strTmp);
+
+ /*
+ * Only store the NVRAM content if the path is not empty, if it is
+ * this means the VM was just created and the store was nnot saved yet,
+ * see @bugref{10191}.
+ */
+ if (strTmp.isNotEmpty())
+ {
+ /*
+ * Skip creating the tar archive if only the UEFI NVRAM content is available in order
+ * to maintain backwards compatibility. As soon as there is more than one entry or
+ * it doesn't belong to the UEFI the tar archive will be created.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if ( m->bd->mapNvram.size() == 1
+ && m->bd->mapNvram.find(Utf8Str("efi/nvram")) != m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFileNvram = m->bd->mapNvram[Utf8Str("efi/nvram")];
+
+ vrc = RTVfsFileSeek(hVfsFileNvram, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(vrc); RT_NOREF(vrc);
+
+ RTVFSIOSTREAM hVfsIosDst;
+ vrc = RTVfsIoStrmOpenNormal(strTmp.c_str(), RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
+ &hVfsIosDst);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hVfsIosSrc = RTVfsFileToIoStream(hVfsFileNvram);
+ Assert(hVfsIosSrc != NIL_RTVFSIOSTREAM);
+
+ RTVFSIOSTREAM hVfsIosEncrypted = NIL_RTVFSIOSTREAM;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ SecretKey *pKey = NULL;
+
+ if ( m->bd->strKeyId.isNotEmpty()
+ && m->bd->strKeyStore.isNotEmpty())
+ vrc = i_setupEncryptionOrDecryption(hVfsIosDst, true /*fEncrypt*/,
+ &pCryptoIf, &pKey, &hVfsIosEncrypted);
+#endif
+
+ vrc = RTVfsUtilPumpIoStreams(hVfsIosSrc,
+ hVfsIosEncrypted != NIL_RTVFSIOSTREAM
+ ? hVfsIosEncrypted
+ : hVfsIosDst
+ , 0 /*cbBufHint*/);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (hVfsIosEncrypted != NIL_RTVFSIOSTREAM)
+ i_releaseEncryptionOrDecryptionResources(hVfsIosEncrypted, pCryptoIf, pKey);
+#endif
+
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ }
+ }
+ else if (m->bd->mapNvram.size())
+ vrc = i_saveStoreAsTar(strTmp.c_str());
+ /* else: No NVRAM content to store so we are done here. */
+ }
+
+ return vrc;
+}
+
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+HRESULT NvramStore::i_updateEncryptionSettings(const com::Utf8Str &strKeyId,
+ const com::Utf8Str &strKeyStore)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->strKeyId = strKeyId;
+ m->bd->strKeyStore = strKeyStore;
+
+ /* clear all passwords because they are invalid now */
+ m->mpKeyStore->deleteAllSecretKeys(false, true);
+
+ alock.release();
+ AutoWriteLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS);
+#ifndef VBOX_COM_INPROC
+ m->pParent->i_setModified(Machine::IsModified_NvramStore);
+#endif
+ return S_OK;
+}
+
+
+HRESULT NvramStore::i_getEncryptionSettings(com::Utf8Str &strKeyId,
+ com::Utf8Str &strKeyStore)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ strKeyId = m->bd->strKeyId;
+ strKeyStore = m->bd->strKeyStore;
+
+ return S_OK;
+}
+
+
+int NvramStore::i_addPassword(const Utf8Str &strKeyId, const Utf8Str &strPassword)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), VERR_INVALID_STATE);
+
+ /* keep only required password */
+ if (strKeyId != m->bd->strKeyId)
+ return VINF_SUCCESS;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return m->mpKeyStore->addSecretKey(strKeyId, (const uint8_t *)strPassword.c_str(), strPassword.length() + 1);
+}
+
+
+int NvramStore::i_removePassword(const Utf8Str &strKeyId)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), VERR_INVALID_STATE);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return m->mpKeyStore->deleteSecretKey(strKeyId);
+}
+
+
+int NvramStore::i_removeAllPasswords()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), VERR_INVALID_STATE);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->mpKeyStore->deleteAllSecretKeys(false, true);
+ return VINF_SUCCESS;
+}
+#endif
+
+
+#ifndef VBOX_COM_INPROC
+HRESULT NvramStore::i_retainUefiVarStore(PRTVFS phVfs, bool fReadonly)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+ NvramStoreIter it = m->bd->mapNvram.find("efi/nvram");
+ if (it != m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFileNvram = it->second;
+ RTVFS hVfsEfiVarStore;
+ uint32_t fMntFlags = fReadonly ? RTVFSMNT_F_READ_ONLY : 0;
+
+ int vrc = RTEfiVarStoreOpenAsVfs(hVfsFileNvram, fMntFlags, 0 /*fVarStoreFlags*/, &hVfsEfiVarStore,
+ NULL /*pErrInfo*/);
+ if (RT_SUCCESS(vrc))
+ {
+ *phVfs = hVfsEfiVarStore;
+ if (!fReadonly)
+ m->pParent->i_setModified(Machine::IsModified_NvramStore);
+ }
+ else
+ hrc = setError(E_FAIL, tr("Opening the UEFI variable store failed (%Rrc)."), vrc);
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("The UEFI NVRAM file is not existing for this machine."));
+
+ return hrc;
+}
+
+
+HRESULT NvramStore::i_releaseUefiVarStore(RTVFS hVfs)
+{
+ RTVfsRelease(hVfs);
+ return S_OK;
+}
+
+
+/**
+ * Loads settings from the given machine node.
+ * May be called once right after this object creation.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT NvramStore::i_loadSettings(const settings::NvramSettings &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd->strNvramPath = data.strNvramPath;
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ m->bd->strKeyId = data.strKeyId;
+ m->bd->strKeyStore = data.strKeyStore;
+#endif
+
+ Utf8Str strTmp(m->bd->strNvramPath);
+ if (strTmp.isNotEmpty())
+ m->pParent->i_copyPathRelativeToMachine(strTmp, m->bd->strNvramPath);
+ if ( m->pParent->i_getFirmwareType() == FirmwareType_BIOS
+ || m->bd->strNvramPath == m->pParent->i_getDefaultNVRAMFilename())
+ m->bd->strNvramPath.setNull();
+
+ return S_OK;
+}
+
+/**
+ * Saves settings to the given machine node.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT NvramStore::i_saveSettings(settings::NvramSettings &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ data.strNvramPath = m->bd->strNvramPath;
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ data.strKeyId = m->bd->strKeyId;
+ data.strKeyStore = m->bd->strKeyStore;
+#endif
+
+ int vrc = i_saveStore();
+ if (RT_FAILURE(vrc))
+ return setError(E_FAIL, tr("Failed to save the NVRAM content to disk (%Rrc)"), vrc);
+
+ return S_OK;
+}
+
+void NvramStore::i_rollback()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->bd.rollback();
+}
+
+void NvramStore::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertReturnVoid(autoCaller.isOk());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertReturnVoid(peerCaller.isOk());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ {
+ m->bd.commit();
+ if (m->pPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ AutoWriteLock peerlock(m->pPeer COMMA_LOCKVAL_SRC_POS);
+ m->pPeer->m->bd.attach(m->bd);
+ }
+ }
+}
+
+void NvramStore::i_copyFrom(NvramStore *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertReturnVoid(autoCaller.isOk());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertReturnVoid(thatCaller.isOk());
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ m->bd.assignCopy(aThat->m->bd);
+
+ // Intentionally "forget" the NVRAM file since it must be unique and set
+ // to the correct value before the copy of the settings makes sense.
+ m->bd->strNvramPath.setNull();
+}
+
+HRESULT NvramStore::i_applyDefaults(GuestOSType *aOSType)
+{
+ HRESULT hrc = S_OK;
+
+ if (aOSType->i_recommendedEFISecureBoot())
+ {
+ /* Initialize the UEFI variable store and enroll default keys. */
+ hrc = initUefiVariableStore(0 /*aSize*/);
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<IUefiVariableStore> pVarStore;
+
+ hrc = getUefiVariableStore(pVarStore);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = pVarStore->EnrollOraclePlatformKey();
+ if (SUCCEEDED(hrc))
+ hrc = pVarStore->EnrollDefaultMsSignatures();
+ }
+ }
+ }
+
+ return hrc;
+}
+
+void NvramStore::i_updateNonVolatileStorageFile(const Utf8Str &aNonVolatileStorageFile)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoReadLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Utf8Str strTmp(aNonVolatileStorageFile);
+ if (strTmp == m->pParent->i_getDefaultNVRAMFilename())
+ strTmp.setNull();
+
+ if (strTmp == m->bd->strNvramPath)
+ return;
+
+ m->bd.backup();
+ m->bd->strNvramPath = strTmp;
+}
+
+#else
+//
+// private methods
+//
+/*static*/
+DECLCALLBACK(int) NvramStore::i_nvramStoreQuerySize(PPDMIVFSCONNECTOR pInterface, const char *pszNamespace, const char *pszPath,
+ uint64_t *pcb)
+{
+ PDRVMAINNVRAMSTORE pThis = RT_FROM_MEMBER(pInterface, DRVMAINNVRAMSTORE, IVfs);
+
+ AutoReadLock rlock(pThis->pNvramStore COMMA_LOCKVAL_SRC_POS);
+ NvramStoreIter it = pThis->pNvramStore->m->bd->mapNvram.find(Utf8StrFmt("%s/%s", pszNamespace, pszPath));
+ if (it != pThis->pNvramStore->m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFile = it->second;
+ return RTVfsFileQuerySize(hVfsFile, pcb);
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+
+/*static*/
+DECLCALLBACK(int) NvramStore::i_nvramStoreReadAll(PPDMIVFSCONNECTOR pInterface, const char *pszNamespace, const char *pszPath,
+ void *pvBuf, size_t cbRead)
+{
+ PDRVMAINNVRAMSTORE pThis = RT_FROM_MEMBER(pInterface, DRVMAINNVRAMSTORE, IVfs);
+
+ AutoReadLock rlock(pThis->pNvramStore COMMA_LOCKVAL_SRC_POS);
+ NvramStoreIter it = pThis->pNvramStore->m->bd->mapNvram.find(Utf8StrFmt("%s/%s", pszNamespace, pszPath));
+ if (it != pThis->pNvramStore->m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFile = it->second;
+
+ int vrc = RTVfsFileSeek(hVfsFile, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(vrc); RT_NOREF(vrc);
+
+ return RTVfsFileRead(hVfsFile, pvBuf, cbRead, NULL /*pcbRead*/);
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+
+/*static*/
+DECLCALLBACK(int) NvramStore::i_nvramStoreWriteAll(PPDMIVFSCONNECTOR pInterface, const char *pszNamespace, const char *pszPath,
+ const void *pvBuf, size_t cbWrite)
+{
+ PDRVMAINNVRAMSTORE pThis = RT_FROM_MEMBER(pInterface, DRVMAINNVRAMSTORE, IVfs);
+
+ AutoWriteLock wlock(pThis->pNvramStore COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+ NvramStoreIter it = pThis->pNvramStore->m->bd->mapNvram.find(Utf8StrFmt("%s/%s", pszNamespace, pszPath));
+ if (it != pThis->pNvramStore->m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFile = it->second;
+
+ vrc = RTVfsFileSeek(hVfsFile, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(vrc);
+ vrc = RTVfsFileSetSize(hVfsFile, cbWrite, RTVFSFILE_SIZE_F_NORMAL);
+ if (RT_SUCCESS(vrc))
+ vrc = RTVfsFileWrite(hVfsFile, pvBuf, cbWrite, NULL /*pcbWritten*/);
+ }
+ else
+ {
+ /* Create a new entry. */
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ vrc = RTVfsFileFromBuffer(RTFILE_O_READ | RTFILE_O_WRITE, pvBuf, cbWrite, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ pThis->pNvramStore->m->bd->mapNvram[Utf8StrFmt("%s/%s", pszNamespace, pszPath)] = hVfsFile;
+ }
+
+ return vrc;
+}
+
+
+/*static*/
+DECLCALLBACK(int) NvramStore::i_nvramStoreDelete(PPDMIVFSCONNECTOR pInterface, const char *pszNamespace, const char *pszPath)
+{
+ PDRVMAINNVRAMSTORE pThis = RT_FROM_MEMBER(pInterface, DRVMAINNVRAMSTORE, IVfs);
+
+ AutoWriteLock wlock(pThis->pNvramStore COMMA_LOCKVAL_SRC_POS);
+ NvramStoreIter it = pThis->pNvramStore->m->bd->mapNvram.find(Utf8StrFmt("%s/%s", pszNamespace, pszPath));
+ if (it != pThis->pNvramStore->m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFile = it->second;
+ pThis->pNvramStore->m->bd->mapNvram.erase(it);
+ RTVfsFileRelease(hVfsFile);
+ return VINF_SUCCESS;
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+
+/*static*/
+DECLCALLBACK(int) NvramStore::i_SsmSaveExec(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVMAINNVRAMSTORE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINNVRAMSTORE);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ AutoWriteLock wlock(pThis->pNvramStore COMMA_LOCKVAL_SRC_POS);
+
+ size_t cEntries = pThis->pNvramStore->m->bd->mapNvram.size();
+ AssertReturn(cEntries < 32, VERR_OUT_OF_RANGE); /* Some sanity checking. */
+ pHlp->pfnSSMPutU32(pSSM, (uint32_t)cEntries);
+
+ void *pvData = NULL;
+ size_t cbDataMax = 0;
+ NvramStoreIter it = pThis->pNvramStore->m->bd->mapNvram.begin();
+
+ while (it != pThis->pNvramStore->m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFile = it->second;
+ uint64_t cbFile;
+
+ int vrc = RTVfsFileQuerySize(hVfsFile, &cbFile);
+ AssertRCReturn(vrc, vrc);
+ AssertReturn(cbFile < _1M, VERR_OUT_OF_RANGE);
+
+ if (cbDataMax < cbFile)
+ {
+ pvData = RTMemRealloc(pvData, cbFile);
+ AssertPtrReturn(pvData, VERR_NO_MEMORY);
+ cbDataMax = cbFile;
+ }
+
+ vrc = RTVfsFileReadAt(hVfsFile, 0 /*off*/, pvData, cbFile, NULL /*pcbRead*/);
+ AssertRCReturn(vrc, vrc);
+
+ pHlp->pfnSSMPutStrZ(pSSM, it->first.c_str());
+ pHlp->pfnSSMPutU64(pSSM, cbFile);
+ pHlp->pfnSSMPutMem(pSSM, pvData, cbFile);
+ it++;
+ }
+
+ if (pvData)
+ RTMemFree(pvData);
+
+ pThis->pNvramStore->m->fSsmSaved = true;
+ return pHlp->pfnSSMPutU32(pSSM, UINT32_MAX); /* sanity/terminator */
+}
+
+
+/*static*/
+DECLCALLBACK(int) NvramStore::i_SsmLoadExec(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVMAINNVRAMSTORE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINNVRAMSTORE);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ AssertMsgReturn(uVersion >= NVRAM_STORE_SAVED_STATE_VERSION, ("%d\n", uVersion),
+ VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
+
+ if (uPass == SSM_PASS_FINAL)
+ {
+ AutoWriteLock wlock(pThis->pNvramStore COMMA_LOCKVAL_SRC_POS);
+
+ /* Clear any content first. */
+ NvramStoreIter it = pThis->pNvramStore->m->bd->mapNvram.begin();
+ while (it != pThis->pNvramStore->m->bd->mapNvram.end())
+ {
+ RTVfsFileRelease(it->second);
+ it++;
+ }
+
+ pThis->pNvramStore->m->bd->mapNvram.clear();
+
+ uint32_t cEntries = 0;
+ int vrc = pHlp->pfnSSMGetU32(pSSM, &cEntries);
+ AssertRCReturn(vrc, vrc);
+ AssertReturn(cEntries < 32, VERR_OUT_OF_RANGE);
+
+ void *pvData = NULL;
+ size_t cbDataMax = 0;
+ while (cEntries--)
+ {
+ char szId[_1K]; /* Lazy developer */
+ uint64_t cbFile = 0;
+
+ vrc = pHlp->pfnSSMGetStrZ(pSSM, &szId[0], sizeof(szId));
+ AssertRCReturn(vrc, vrc);
+
+ vrc = pHlp->pfnSSMGetU64(pSSM, &cbFile);
+ AssertRCReturn(vrc, vrc);
+ AssertReturn(cbFile < _1M, VERR_OUT_OF_RANGE);
+
+ if (cbDataMax < cbFile)
+ {
+ pvData = RTMemRealloc(pvData, cbFile);
+ AssertPtrReturn(pvData, VERR_NO_MEMORY);
+ cbDataMax = cbFile;
+ }
+
+ vrc = pHlp->pfnSSMGetMem(pSSM, pvData, cbFile);
+ AssertRCReturn(vrc, vrc);
+
+ RTVFSFILE hVfsFile;
+ vrc = RTVfsFileFromBuffer(RTFILE_O_READWRITE, pvData, cbFile, &hVfsFile);
+ AssertRCReturn(vrc, vrc);
+
+ pThis->pNvramStore->m->bd->mapNvram[Utf8Str(szId)] = hVfsFile;
+ }
+
+ if (pvData)
+ RTMemFree(pvData);
+
+ /* The marker. */
+ uint32_t u32;
+ vrc = pHlp->pfnSSMGetU32(pSSM, &u32);
+ AssertRCReturn(vrc, vrc);
+ AssertMsgReturn(u32 == UINT32_MAX, ("%#x\n", u32), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) NvramStore::i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINNVRAMSTORE pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINNVRAMSTORE);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIVFSCONNECTOR, &pDrv->IVfs);
+ return NULL;
+}
+
+
+/**
+ * Destruct a NVRAM store driver instance.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The driver instance data.
+ */
+DECLCALLBACK(void) NvramStore::i_drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVMAINNVRAMSTORE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINNVRAMSTORE);
+ LogFlow(("NvramStore::drvDestruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ if (pThis->pNvramStore)
+ {
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->pNvramStore->m->cRefs);
+ if ( !cRefs
+ && !pThis->pNvramStore->m->fSsmSaved)
+ {
+ int vrc = pThis->pNvramStore->i_saveStore();
+ AssertRC(vrc); /** @todo Disk full error? */
+ }
+ }
+}
+
+
+/**
+ * Construct a NVRAM store driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+DECLCALLBACK(int) NvramStore::i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ RT_NOREF(fFlags, pCfg);
+ PDRVMAINNVRAMSTORE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINNVRAMSTORE);
+ LogFlow(("NvramStore::drvConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", "");
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * IBase.
+ */
+ pDrvIns->IBase.pfnQueryInterface = NvramStore::i_drvQueryInterface;
+
+ pThis->IVfs.pfnQuerySize = NvramStore::i_nvramStoreQuerySize;
+ pThis->IVfs.pfnReadAll = NvramStore::i_nvramStoreReadAll;
+ pThis->IVfs.pfnWriteAll = NvramStore::i_nvramStoreWriteAll;
+ pThis->IVfs.pfnDelete = NvramStore::i_nvramStoreDelete;
+
+ /*
+ * Get the NVRAM store object pointer.
+ */
+ com::Guid uuid(COM_IIDOF(INvramStore));
+ pThis->pNvramStore = (NvramStore *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw());
+ if (!pThis->pNvramStore)
+ {
+ AssertMsgFailed(("Configuration error: No/bad NVRAM store object!\n"));
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Only the first instance will register the SSM handlers and will do the work on behalf
+ * of all other NVRAM store driver instances when it comes to SSM.
+ */
+ if (pDrvIns->iInstance == 0)
+ {
+ int vrc = PDMDrvHlpSSMRegister(pDrvIns, NVRAM_STORE_SAVED_STATE_VERSION, 0 /*cbGuess*/,
+ NvramStore::i_SsmSaveExec, NvramStore::i_SsmLoadExec);
+ if (RT_FAILURE(vrc))
+ return PDMDrvHlpVMSetError(pDrvIns, vrc, RT_SRC_POS,
+ N_("Failed to register the saved state unit for the NVRAM store"));
+ }
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->pNvramStore->m->cRefs);
+ if (cRefs == 1)
+ {
+ int vrc = pThis->pNvramStore->i_loadStore(pThis->pNvramStore->m->bd->strNvramPath.c_str());
+ if (RT_FAILURE(vrc))
+ {
+ ASMAtomicDecU32(&pThis->pNvramStore->m->cRefs);
+ return PDMDrvHlpVMSetError(pDrvIns, vrc, RT_SRC_POS,
+ N_("Failed to load the NVRAM store from the file"));
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * NVRAM store driver registration record.
+ */
+const PDMDRVREG NvramStore::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "NvramStore",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Main NVRAM store driver (Main as in the API).",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_STATUS,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMAINNVRAMSTORE),
+ /* pfnConstruct */
+ NvramStore::i_drvConstruct,
+ /* pfnDestruct */
+ NvramStore::i_drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+#endif /* !VBOX_COM_INPROC */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/PCIDeviceAttachmentImpl.cpp b/src/VBox/Main/src-all/PCIDeviceAttachmentImpl.cpp
new file mode 100644
index 00000000..cde23d6a
--- /dev/null
+++ b/src/VBox/Main/src-all/PCIDeviceAttachmentImpl.cpp
@@ -0,0 +1,160 @@
+/* $Id: PCIDeviceAttachmentImpl.cpp $ */
+/** @file
+ * PCI attachment information implmentation.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_PCIDEVICEATTACHMENT
+#include "PCIDeviceAttachmentImpl.h"
+#include "AutoCaller.h"
+#include "Global.h"
+#include "LoggingNew.h"
+
+#include <VBox/settings.h>
+
+struct PCIDeviceAttachment::Data
+{
+ Data(const Utf8Str &aDevName,
+ LONG aHostAddress,
+ LONG aGuestAddress,
+ BOOL afPhysical) :
+ DevName(aDevName),
+ HostAddress(aHostAddress),
+ GuestAddress(aGuestAddress),
+ fPhysical(afPhysical)
+ {
+ }
+
+ Utf8Str DevName;
+ LONG HostAddress;
+ LONG GuestAddress;
+ BOOL fPhysical;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+DEFINE_EMPTY_CTOR_DTOR(PCIDeviceAttachment)
+
+HRESULT PCIDeviceAttachment::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void PCIDeviceAttachment::FinalRelease()
+{
+ LogFlowThisFunc(("\n"));
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+HRESULT PCIDeviceAttachment::init(IMachine *aParent,
+ const Utf8Str &aDevName,
+ LONG aHostAddress,
+ LONG aGuestAddress,
+ BOOL fPhysical)
+{
+ NOREF(aParent);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aDevName, aHostAddress, aGuestAddress, fPhysical);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+HRESULT PCIDeviceAttachment::initCopy(IMachine *aParent, PCIDeviceAttachment *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ return init(aParent, aThat->m->DevName, aThat->m->HostAddress, aThat->m->GuestAddress, aThat->m->fPhysical);
+}
+
+HRESULT PCIDeviceAttachment::i_loadSettings(IMachine *aParent,
+ const settings::HostPCIDeviceAttachment &hpda)
+{
+ /** @todo r=bird: Inconsistent signed/unsigned crap. */
+ return init(aParent, hpda.strDeviceName, (LONG)hpda.uHostAddress, (LONG)hpda.uGuestAddress, TRUE);
+}
+
+
+HRESULT PCIDeviceAttachment::i_saveSettings(settings::HostPCIDeviceAttachment &data)
+{
+ Assert(m);
+ /** @todo r=bird: Inconsistent signed/unsigned crap. */
+ data.uHostAddress = (uint32_t)m->HostAddress;
+ data.uGuestAddress = (uint32_t)m->GuestAddress;
+ data.strDeviceName = m->DevName;
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void PCIDeviceAttachment::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ delete m;
+ m = NULL;
+}
+
+// IPCIDeviceAttachment properties
+/////////////////////////////////////////////////////////////////////////////
+HRESULT PCIDeviceAttachment::getName(com::Utf8Str &aName)
+{
+ aName = m->DevName;
+ return S_OK;
+}
+
+HRESULT PCIDeviceAttachment::getIsPhysicalDevice(BOOL *aIsPhysicalDevice)
+{
+ *aIsPhysicalDevice = m->fPhysical;
+ return S_OK;
+}
+
+HRESULT PCIDeviceAttachment::getHostAddress(LONG *aHostAddress)
+{
+ *aHostAddress = m->HostAddress;
+ return S_OK;
+}
+HRESULT PCIDeviceAttachment::getGuestAddress(LONG *aGuestAddress)
+{
+ *aGuestAddress = m->GuestAddress;
+ return S_OK;
+}
diff --git a/src/VBox/Main/src-all/ProgressImpl.cpp b/src/VBox/Main/src-all/ProgressImpl.cpp
new file mode 100644
index 00000000..a4fc20f6
--- /dev/null
+++ b/src/VBox/Main/src-all/ProgressImpl.cpp
@@ -0,0 +1,1215 @@
+/* $Id: ProgressImpl.cpp $ */
+/** @file
+ * VirtualBox Progress COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_PROGRESS
+#include <iprt/types.h>
+
+#if defined(VBOX_WITH_XPCOM)
+#include <nsIServiceManager.h>
+#include <nsIExceptionService.h>
+#include <nsCOMPtr.h>
+#endif /* defined(VBOX_WITH_XPCOM) */
+
+#include "ProgressImpl.h"
+
+#if !defined(VBOX_COM_INPROC)
+# include "VirtualBoxImpl.h"
+#endif
+#include "VirtualBoxErrorInfoImpl.h"
+
+#include <iprt/time.h>
+#include <iprt/semaphore.h>
+#include <iprt/cpp/utils.h>
+
+#include <iprt/errcore.h>
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "VBoxEvents.h"
+
+
+Progress::Progress()
+#if !defined(VBOX_COM_INPROC)
+ : mParent(NULL)
+#endif
+{
+}
+
+Progress::~Progress()
+{
+}
+
+
+HRESULT Progress::FinalConstruct()
+{
+ mCancelable = FALSE;
+ mCompleted = FALSE;
+ mCanceled = FALSE;
+ mResultCode = S_OK;
+
+ m_cOperations
+ = m_ulTotalOperationsWeight
+ = m_ulOperationsCompletedWeight
+ = m_ulCurrentOperation
+ = m_ulCurrentOperationWeight
+ = m_ulOperationPercent
+ = m_cMsTimeout
+ = 0;
+
+ // get creation timestamp
+ m_ullTimestamp = RTTimeMilliTS();
+
+ m_pfnCancelCallback = NULL;
+ m_pvCancelUserArg = NULL;
+
+ mCompletedSem = NIL_RTSEMEVENTMULTI;
+ mWaitersCount = 0;
+
+ return Progress::BaseFinalConstruct();
+}
+
+void Progress::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the normal progress object. With this variant, one can have
+ * an arbitrary number of sub-operation which IProgress can analyze to
+ * have a weighted progress computed.
+ *
+ * For example, say that one IProgress is supposed to track the cloning
+ * of two hard disk images, which are 100 MB and 1000 MB in size, respectively,
+ * and each of these hard disks should be one sub-operation of the IProgress.
+ *
+ * Obviously the progress would be misleading if the progress displayed 50%
+ * after the smaller image was cloned and would then take much longer for
+ * the second half.
+ *
+ * With weighted progress, one can invoke the following calls:
+ *
+ * 1) create progress object with cOperations = 2 and ulTotalOperationsWeight =
+ * 1100 (100 MB plus 1100, but really the weights can be any ULONG); pass
+ * in ulFirstOperationWeight = 100 for the first sub-operation
+ *
+ * 2) Then keep calling setCurrentOperationProgress() with a percentage
+ * for the first image; the total progress will increase up to a value
+ * of 9% (100MB / 1100MB * 100%).
+ *
+ * 3) Then call setNextOperation with the second weight (1000 for the megabytes
+ * of the second disk).
+ *
+ * 4) Then keep calling setCurrentOperationProgress() with a percentage for
+ * the second image, where 100% of the operation will then yield a 100%
+ * progress of the entire task.
+ *
+ * Weighting is optional; you can simply assign a weight of 1 to each operation
+ * and pass ulTotalOperationsWeight == cOperations to this constructor (but
+ * for that variant and for backwards-compatibility a simpler constructor exists
+ * in ProgressImpl.h as well).
+ *
+ * Even simpler, if you need no sub-operations at all, pass in cOperations =
+ * ulTotalOperationsWeight = ulFirstOperationWeight = 1.
+ *
+ * @param aParent Parent object (only for server-side Progress objects).
+ * @param aInitiator Initiator of the task (for server-side objects. Can be
+ * NULL which means initiator = parent, otherwise must not
+ * be NULL).
+ * @param aDescription Overall task description.
+ * @param aCancelable Flag whether the task maybe canceled.
+ * @param cOperations Number of operations within this task (at least 1).
+ * @param ulTotalOperationsWeight Total weight of operations; must be the sum of ulFirstOperationWeight and
+ * what is later passed with each subsequent setNextOperation() call.
+ * @param aFirstOperationDescription Description of the first operation.
+ * @param ulFirstOperationWeight Weight of first sub-operation.
+ */
+HRESULT Progress::init(
+#if !defined(VBOX_COM_INPROC)
+ VirtualBox *aParent,
+#endif
+ IUnknown *aInitiator,
+ const Utf8Str &aDescription,
+ BOOL aCancelable,
+ ULONG cOperations,
+ ULONG ulTotalOperationsWeight,
+ const Utf8Str &aFirstOperationDescription,
+ ULONG ulFirstOperationWeight)
+{
+ LogFlowThisFunc(("aDescription=\"%s\", cOperations=%d, ulTotalOperationsWeight=%d, aFirstOperationDescription=\"%s\", ulFirstOperationWeight=%d\n",
+ aDescription.c_str(),
+ cOperations,
+ ulTotalOperationsWeight,
+ aFirstOperationDescription.c_str(),
+ ulFirstOperationWeight));
+
+ AssertReturn(ulTotalOperationsWeight >= 1, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT hrc = unconst(pEventSource).createObject();
+ if (FAILED(hrc))
+ return hrc;
+
+ hrc = pEventSource->init();
+ if (FAILED(hrc))
+ return hrc;
+
+#if !defined(VBOX_COM_INPROC)
+ AssertReturn(aParent, E_INVALIDARG);
+#else
+ AssertReturn(aInitiator, E_INVALIDARG);
+#endif
+
+#if !defined(VBOX_COM_INPROC)
+ /* share parent weakly */
+ unconst(mParent) = aParent;
+#endif
+
+#if !defined(VBOX_COM_INPROC)
+ /* assign (and therefore addref) initiator only if it is not VirtualBox
+ * (to avoid cycling); otherwise mInitiator will remain null which means
+ * that it is the same as the parent */
+ if (aInitiator)
+ {
+ ComObjPtr<VirtualBox> pVirtualBox(mParent);
+ if (!(pVirtualBox == aInitiator))
+ unconst(mInitiator) = aInitiator;
+ }
+#else
+ unconst(mInitiator) = aInitiator;
+#endif
+
+ unconst(mId).create();
+
+#if !defined(VBOX_COM_INPROC)
+ /* add to the global collection of progress operations (note: after
+ * creating mId) */
+ mParent->i_addProgress(this);
+#endif
+
+ unconst(mDescription) = aDescription;
+
+ mCancelable = aCancelable;
+
+ m_cOperations = cOperations;
+ m_ulTotalOperationsWeight = ulTotalOperationsWeight;
+ m_ulOperationsCompletedWeight = 0;
+ m_ulCurrentOperation = 0;
+ m_operationDescription = aFirstOperationDescription;
+ m_ulCurrentOperationWeight = ulFirstOperationWeight;
+ m_ulOperationPercent = 0;
+
+ int vrc = RTSemEventMultiCreate(&mCompletedSem);
+ ComAssertRCRet(vrc, E_FAIL);
+
+ RTSemEventMultiReset(mCompletedSem);
+
+ /* Confirm a successful initialization. */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the sub-progress object that represents a specific operation of
+ * the whole task.
+ *
+ * Objects initialized with this method are then combined together into the
+ * single task using a Progress instance, so it doesn't require the
+ * parent, initiator, description and doesn't create an ID. Note that calling
+ * respective getter methods on an object initialized with this method is
+ * useless. Such objects are used only to provide a separate wait semaphore and
+ * store individual operation descriptions.
+ *
+ * @param aCancelable Flag whether the task maybe canceled.
+ * @param aOperationCount Number of sub-operations within this task (at least 1).
+ * @param aOperationDescription Description of the individual operation.
+ */
+HRESULT Progress::init(BOOL aCancelable,
+ ULONG aOperationCount,
+ const Utf8Str &aOperationDescription)
+{
+ LogFlowThisFunc(("aOperationDescription=\"%s\"\n", aOperationDescription.c_str()));
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ mCancelable = aCancelable;
+
+ // for this variant we assume for now that all operations are weighed "1"
+ // and equal total weight = operation count
+ m_cOperations = aOperationCount;
+ m_ulTotalOperationsWeight = aOperationCount;
+ m_ulOperationsCompletedWeight = 0;
+ m_ulCurrentOperation = 0;
+ m_operationDescription = aOperationDescription;
+ m_ulCurrentOperationWeight = 1;
+ m_ulOperationPercent = 0;
+
+ int vrc = RTSemEventMultiCreate(&mCompletedSem);
+ ComAssertRCRet(vrc, E_FAIL);
+
+ RTSemEventMultiReset(mCompletedSem);
+
+ /* Confirm a successful initialization. */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ *
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void Progress::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ /* wake up all threads still waiting on occasion */
+ if (mWaitersCount > 0)
+ {
+ LogFlow(("WARNING: There are still %d threads waiting for '%s' completion!\n",
+ mWaitersCount, mDescription.c_str()));
+ RTSemEventMultiSignal(mCompletedSem);
+ }
+
+ RTSemEventMultiDestroy(mCompletedSem);
+
+ /* release initiator (effective only if mInitiator has been assigned in init()) */
+ unconst(mInitiator).setNull();
+
+#if !defined(VBOX_COM_INPROC)
+ if (mParent)
+ {
+ /* remove the added progress on failure to complete the initialization */
+ if (autoUninitSpan.initFailed() && mId.isValid() && !mId.isZero())
+ mParent->i_removeProgress(mId.ref());
+
+ unconst(mParent) = NULL;
+ }
+#endif
+}
+
+
+// public methods only for internal purposes
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Marks the whole task as complete and sets the result code.
+ *
+ * If the result code indicates a failure (|FAILED(@a aResultCode)|) then this
+ * method will import the error info from the current thread and assign it to
+ * the errorInfo attribute (it will return an error if no info is available in
+ * such case).
+ *
+ * If the result code indicates a success (|SUCCEEDED(@a aResultCode)|) then
+ * the current operation is set to the last.
+ *
+ * Note that this method may be called only once for the given Progress object.
+ * Subsequent calls will assert.
+ *
+ * @param aResultCode Operation result code.
+ */
+HRESULT Progress::i_notifyComplete(HRESULT aResultCode)
+{
+ HRESULT hrc;
+ ComPtr<IVirtualBoxErrorInfo> errorInfo;
+ if (FAILED(aResultCode))
+ {
+ /* try to import error info from the current thread */
+#if !defined(VBOX_WITH_XPCOM)
+ ComPtr<IErrorInfo> err;
+ hrc = ::GetErrorInfo(0, err.asOutParam());
+ if (hrc == S_OK && err)
+ hrc = err.queryInterfaceTo(errorInfo.asOutParam());
+#else /* !defined(VBOX_WITH_XPCOM) */
+ nsCOMPtr<nsIExceptionService> es;
+ es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &hrc);
+ if (NS_SUCCEEDED(hrc))
+ {
+ nsCOMPtr <nsIExceptionManager> em;
+ hrc = es->GetCurrentExceptionManager(getter_AddRefs(em));
+ if (NS_SUCCEEDED(hrc))
+ {
+ ComPtr<nsIException> ex;
+ hrc = em->GetCurrentException(ex.asOutParam());
+ if (NS_SUCCEEDED(hrc) && ex)
+ hrc = ex.queryInterfaceTo(errorInfo.asOutParam());
+ }
+ }
+#endif /* !defined(VBOX_WITH_XPCOM) */
+ }
+
+ return i_notifyCompleteWorker(aResultCode, errorInfo);
+}
+
+/**
+ * Wrapper around Progress:notifyCompleteV.
+ */
+HRESULT Progress::i_notifyComplete(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const char *aText,
+ ...)
+{
+ va_list va;
+ va_start(va, aText);
+ HRESULT hrc = i_notifyCompleteV(aResultCode, aIID, pcszComponent, aText, va);
+ va_end(va);
+ return hrc;
+}
+
+/**
+ * Marks the operation as complete and attaches full error info.
+ *
+ * @param aResultCode Operation result (error) code, must not be S_OK.
+ * @param aIID IID of the interface that defines the error.
+ * @param pcszComponent Name of the component that generates the error.
+ * @param aText Error message (must not be null), an RTStrPrintf-like
+ * format string in UTF-8 encoding.
+ * @param va List of arguments for the format string.
+ */
+HRESULT Progress::i_notifyCompleteV(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const char *aText,
+ va_list va)
+{
+ /* expected to be used only in case of error */
+ Assert(FAILED(aResultCode));
+
+ Utf8Str text(aText, va);
+ ComObjPtr<VirtualBoxErrorInfo> errorInfo;
+ HRESULT hrc = errorInfo.createObject();
+ AssertComRCReturnRC(hrc);
+ errorInfo->init(aResultCode, aIID, pcszComponent, text);
+
+ return i_notifyCompleteWorker(aResultCode, errorInfo);
+}
+
+/**
+ * Wrapper around Progress:notifyCompleteBothV.
+ */
+HRESULT Progress::i_notifyCompleteBoth(HRESULT aResultCode,
+ int vrc,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const char *aText,
+ ...)
+{
+ va_list va;
+ va_start(va, aText);
+ HRESULT hrc = i_notifyCompleteBothV(aResultCode, vrc, aIID, pcszComponent, aText, va);
+ va_end(va);
+ return hrc;
+}
+
+/**
+ * Marks the operation as complete and attaches full error info.
+ *
+ * @param aResultCode Operation result (error) code, must not be S_OK.
+ * @param vrc VBox status code to associate with the error.
+ * @param aIID IID of the interface that defines the error.
+ * @param pszComponent Name of the component that generates the error.
+ * @param pszFormat Error message (must not be null), an RTStrPrintf-like
+ * format string in UTF-8 encoding.
+ * @param va List of arguments for the format string.
+ */
+HRESULT Progress::i_notifyCompleteBothV(HRESULT aResultCode,
+ int vrc,
+ const GUID &aIID,
+ const char *pszComponent,
+ const char *pszFormat,
+ va_list va)
+{
+ /* expected to be used only in case of error */
+ Assert(FAILED(aResultCode));
+
+ Utf8Str text(pszFormat, va);
+ ComObjPtr<VirtualBoxErrorInfo> errorInfo;
+ HRESULT hrc = errorInfo.createObject();
+ AssertComRCReturnRC(hrc);
+ errorInfo->initEx(aResultCode, vrc, aIID, pszComponent, text);
+
+ return i_notifyCompleteWorker(aResultCode, errorInfo);
+}
+
+/**
+ * Sets the cancelation callback, checking for cancelation first.
+ *
+ * @returns Success indicator.
+ * @retval true on success.
+ * @retval false if the progress object has already been canceled or is in an
+ * invalid state
+ *
+ * @param pfnCallback The function to be called upon cancelation.
+ * @param pvUser The callback argument.
+ */
+bool Progress::i_setCancelCallback(void (*pfnCallback)(void *), void *pvUser)
+{
+ AutoCaller autoCaller(this);
+ AssertReturn(autoCaller.isOk(), false);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ i_checkForAutomaticTimeout();
+ if (mCanceled)
+ return false;
+
+ m_pvCancelUserArg = pvUser;
+ m_pfnCancelCallback = pfnCallback;
+ return true;
+}
+
+/**
+ * @callback_method_impl{FNRTPROGRESS,
+ * Works the progress of the current operation.}
+ */
+/*static*/ DECLCALLBACK(int) Progress::i_iprtProgressCallback(unsigned uPercentage, void *pvUser)
+{
+ Progress *pThis = (Progress *)pvUser;
+
+ /*
+ * Same as setCurrentOperationProgress, except we don't fail on mCompleted.
+ */
+ AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
+ int vrc = VINF_SUCCESS;
+ if (!pThis->mCompleted)
+ {
+ pThis->i_checkForAutomaticTimeout();
+ if (!pThis->mCanceled)
+ {
+ if (uPercentage > pThis->m_ulOperationPercent)
+ pThis->setCurrentOperationProgress(uPercentage);
+ }
+ else
+ {
+ Assert(pThis->mCancelable);
+ vrc = VERR_CANCELLED;
+ }
+ }
+ /* else ignored */
+ return vrc;
+}
+
+/**
+ * @callback_method_impl{FNVDPROGRESS,
+ * Progress::i_iprtProgressCallback with parameters switched around.}
+ */
+/*static*/ DECLCALLBACK(int) Progress::i_vdProgressCallback(void *pvUser, unsigned uPercentage)
+{
+ return i_iprtProgressCallback(uPercentage, pvUser);
+}
+
+
+// IProgress properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT Progress::getId(com::Guid &aId)
+{
+ /* mId is constant during life time, no need to lock */
+ aId = mId;
+
+ return S_OK;
+}
+
+HRESULT Progress::getDescription(com::Utf8Str &aDescription)
+{
+ /* mDescription is constant during life time, no need to lock */
+ aDescription = mDescription;
+
+ return S_OK;
+}
+HRESULT Progress::getInitiator(ComPtr<IUnknown> &aInitiator)
+{
+ /* mInitiator/mParent are constant during life time, no need to lock */
+#if !defined(VBOX_COM_INPROC)
+ if (mInitiator)
+ mInitiator.queryInterfaceTo(aInitiator.asOutParam());
+ else
+ {
+ ComObjPtr<VirtualBox> pVirtualBox(mParent);
+ pVirtualBox.queryInterfaceTo(aInitiator.asOutParam());
+ }
+#else
+ mInitiator.queryInterfaceTo(aInitiator.asOutParam());
+#endif
+
+ return S_OK;
+}
+
+HRESULT Progress::getCancelable(BOOL *aCancelable)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCancelable = mCancelable;
+
+ return S_OK;
+}
+
+HRESULT Progress::getPercent(ULONG *aPercent)
+{
+ /* i_checkForAutomaticTimeout requires a write lock. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mCompleted && SUCCEEDED(mResultCode))
+ *aPercent = 100;
+ else
+ {
+ ULONG ulPercent = (ULONG)i_calcTotalPercent();
+ // do not report 100% until we're really really done with everything
+ // as the Qt GUI dismisses progress dialogs in that case
+ if ( ulPercent == 100
+ && ( m_ulOperationPercent < 100
+ || (m_ulCurrentOperation < m_cOperations -1)
+ )
+ )
+ *aPercent = 99;
+ else
+ *aPercent = ulPercent;
+ }
+
+ i_checkForAutomaticTimeout();
+
+ return S_OK;
+}
+
+HRESULT Progress::getTimeRemaining(LONG *aTimeRemaining)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mCompleted)
+ *aTimeRemaining = 0;
+ else
+ {
+ double dPercentDone = i_calcTotalPercent();
+ if (dPercentDone < 1)
+ *aTimeRemaining = -1; // unreliable, or avoid division by 0 below
+ else
+ {
+ uint64_t ullTimeNow = RTTimeMilliTS();
+ uint64_t ullTimeElapsed = ullTimeNow - m_ullTimestamp;
+ uint64_t ullTimeTotal = (uint64_t)((double)ullTimeElapsed * 100 / dPercentDone);
+ uint64_t ullTimeRemaining = (ullTimeTotal < ullTimeElapsed) ? 0 : ullTimeTotal - ullTimeElapsed;
+
+// LogFunc(("dPercentDone = %RI32, ullTimeNow = %RI64, ullTimeElapsed = %RI64, ullTimeTotal = %RI64, ullTimeRemaining = %RI64\n",
+// (uint32_t)dPercentDone, ullTimeNow, ullTimeElapsed, ullTimeTotal, ullTimeRemaining));
+
+ *aTimeRemaining = (LONG)(RT_MIN(ullTimeRemaining, RT_MS_1HOUR_64*24*365) / 1000);
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT Progress::getCompleted(BOOL *aCompleted)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCompleted = mCompleted;
+
+ return S_OK;
+}
+
+HRESULT Progress::getCanceled(BOOL *aCanceled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCanceled = mCanceled;
+
+ return S_OK;
+}
+
+HRESULT Progress::getResultCode(LONG *aResultCode)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mCompleted)
+ return setError(E_FAIL, tr("Result code is not available, operation is still in progress"));
+
+ *aResultCode = (LONG)mResultCode;
+
+ return S_OK;
+}
+
+HRESULT Progress::getErrorInfo(ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mCompleted)
+ return setError(E_FAIL, tr("Error info is not available, operation is still in progress"));
+
+ mErrorInfo.queryInterfaceTo(aErrorInfo.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Progress::getOperationCount(ULONG *aOperationCount)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aOperationCount = m_cOperations;
+
+ return S_OK;
+}
+
+HRESULT Progress::getOperation(ULONG *aOperation)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aOperation = m_ulCurrentOperation;
+
+ return S_OK;
+}
+
+HRESULT Progress::getOperationDescription(com::Utf8Str &aOperationDescription)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aOperationDescription = m_operationDescription;
+
+ return S_OK;
+}
+
+HRESULT Progress::getOperationPercent(ULONG *aOperationPercent)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mCompleted && SUCCEEDED(mResultCode))
+ *aOperationPercent = 100;
+ else
+ *aOperationPercent = m_ulOperationPercent;
+
+ return S_OK;
+}
+
+HRESULT Progress::getOperationWeight(ULONG *aOperationWeight)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aOperationWeight = m_ulCurrentOperationWeight;
+
+ return S_OK;
+}
+
+HRESULT Progress::getTimeout(ULONG *aTimeout)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aTimeout = m_cMsTimeout;
+
+ return S_OK;
+}
+
+HRESULT Progress::setTimeout(ULONG aTimeout)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mCancelable)
+ return setError(VBOX_E_INVALID_OBJECT_STATE, tr("Operation cannot be canceled"));
+ m_cMsTimeout = aTimeout;
+
+ return S_OK;
+}
+
+HRESULT Progress::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ /* event source is const, no need to lock */
+ pEventSource.queryInterfaceTo(aEventSource.asOutParam());
+ return S_OK;
+}
+
+
+// IProgress methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * @note XPCOM: when this method is not called on the main XPCOM thread, it
+ * simply blocks the thread until mCompletedSem is signalled. If the
+ * thread has its own event queue (hmm, what for?) that it must run, then
+ * calling this method will definitely freeze event processing.
+ */
+HRESULT Progress::waitForCompletion(LONG aTimeout)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aTimeout=%d\n", aTimeout));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* if we're already completed, take a shortcut */
+ if (!mCompleted && aTimeout != 0)
+ {
+ RTMSINTERVAL cMsWait = aTimeout < 0 ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)aTimeout;
+ uint64_t msLast = aTimeout < 0 ? 0 : RTTimeMilliTS();
+
+ for (;;)
+ {
+ mWaitersCount++;
+ alock.release();
+ int vrc = RTSemEventMultiWait(mCompletedSem, cMsWait);
+ alock.acquire();
+ mWaitersCount--;
+
+ /* the last waiter resets the semaphore */
+ if (mWaitersCount == 0)
+ RTSemEventMultiReset(mCompletedSem);
+
+ if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to wait for the task completion (%Rrc)"), vrc);
+
+ if (mCompleted)
+ break;
+
+ if (aTimeout >= 0)
+ {
+ uint64_t msNow = RTTimeMilliTS();
+ uint64_t cMsElapsed = msNow - msLast;
+ if (cMsWait <= cMsElapsed)
+ break;
+ cMsWait -= (RTMSINTERVAL)cMsElapsed;
+ msLast = msNow;
+ }
+ }
+ }
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * @note XPCOM: when this method is not called on the main XPCOM thread, it
+ * simply blocks the thread until mCompletedSem is signalled. If the
+ * thread has its own event queue (hmm, what for?) that it must run, then
+ * calling this method will definitely freeze event processing.
+ */
+HRESULT Progress::waitForOperationCompletion(ULONG aOperation, LONG aTimeout)
+
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aOperation=%d, aTimeout=%d\n", aOperation, aTimeout));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CheckComArgExpr(aOperation, aOperation < m_cOperations);
+
+ /* if we're already completed or if the given operation is already done,
+ * then take a shortcut */
+ if ( !mCompleted
+ && aOperation >= m_ulCurrentOperation
+ && aTimeout != 0)
+ {
+ RTMSINTERVAL cMsWait = aTimeout < 0 ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)aTimeout;
+ uint64_t msLast = aTimeout < 0 ? 0 : RTTimeMilliTS();
+
+ for (;;)
+ {
+ mWaitersCount ++;
+ alock.release();
+ int vrc = RTSemEventMultiWait(mCompletedSem, cMsWait);
+ alock.acquire();
+ mWaitersCount--;
+
+ /* the last waiter resets the semaphore */
+ if (mWaitersCount == 0)
+ RTSemEventMultiReset(mCompletedSem);
+
+ if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to wait for the operation completion (%Rrc)"), vrc);
+
+ if (mCompleted || aOperation >= m_ulCurrentOperation)
+ break;
+
+ if (aTimeout >= 0)
+ {
+ uint64_t msNow = RTTimeMilliTS();
+ uint64_t cMsElapsed = msNow - msLast;
+ if (cMsWait <= cMsElapsed)
+ break;
+ cMsWait -= (RTMSINTERVAL)cMsElapsed;
+ msLast = msNow;
+ }
+ }
+ }
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT Progress::cancel()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mCancelable)
+ return setError(VBOX_E_INVALID_OBJECT_STATE, tr("Operation cannot be canceled"));
+
+ if (!mCanceled)
+ {
+ LogThisFunc(("Canceling\n"));
+ mCanceled = TRUE;
+ if (m_pfnCancelCallback)
+ m_pfnCancelCallback(m_pvCancelUserArg);
+
+ }
+ else
+ LogThisFunc(("Already canceled\n"));
+
+ return S_OK;
+}
+
+
+// IInternalProgressControl methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Updates the percentage value of the current operation.
+ *
+ * @param aPercent New percentage value of the operation in progress
+ * (in range [0, 100]).
+ */
+HRESULT Progress::setCurrentOperationProgress(ULONG aPercent)
+{
+ AssertMsgReturn(aPercent <= 100, ("%u\n", aPercent), E_INVALIDARG);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ i_checkForAutomaticTimeout();
+ if (mCancelable && mCanceled)
+ AssertReturn(!mCompleted, E_FAIL);
+ AssertReturn(!mCompleted && !mCanceled, E_FAIL);
+
+ if (m_ulOperationPercent != aPercent)
+ {
+ m_ulOperationPercent = aPercent;
+ ULONG actualPercent = 0;
+ getPercent(&actualPercent);
+ ::FireProgressPercentageChangedEvent(pEventSource, mId.toString(), (LONG)actualPercent);
+ }
+
+ return S_OK;
+}
+
+HRESULT Progress::waitForOtherProgressCompletion(const ComPtr<IProgress> &aProgressOther,
+ ULONG aTimeoutMS)
+{
+ LogFlowThisFuncEnter();
+
+ /* Note: no locking needed, because we just use public methods. */
+
+ BOOL fCancelable = FALSE;
+ BOOL fCompleted = FALSE;
+ BOOL fCanceled = FALSE;
+ ULONG prevPercent = UINT32_MAX;
+ ULONG currentPercent = 0;
+ ULONG cOp = 0;
+ /* Is the async process cancelable? */
+ HRESULT hrc = aProgressOther->COMGETTER(Cancelable)(&fCancelable);
+ if (FAILED(hrc)) return hrc;
+
+ uint64_t u64StopTime = UINT64_MAX;
+ if (aTimeoutMS > 0)
+ u64StopTime = RTTimeMilliTS() + aTimeoutMS;
+ /* Loop as long as the sync process isn't completed. */
+ while (SUCCEEDED(aProgressOther->COMGETTER(Completed(&fCompleted))))
+ {
+ /* We can forward any cancel request to the async process only when
+ * it is cancelable. */
+ if (fCancelable)
+ {
+ hrc = COMGETTER(Canceled)(&fCanceled);
+ if (FAILED(hrc)) return hrc;
+ if (fCanceled)
+ {
+ hrc = aProgressOther->Cancel();
+ if (FAILED(hrc)) return hrc;
+ }
+ }
+ /* Even if the user canceled the process, we have to wait until the
+ async task has finished his work (cleanup and such). Otherwise there
+ will be sync trouble (still wrong state, dead locks, ...) on the
+ used objects. So just do nothing, but wait for the complete
+ notification. */
+ if (!fCanceled)
+ {
+ /* Check if the current operation has changed. It is also possible that
+ * in the meantime more than one async operation was finished. So we
+ * have to loop as long as we reached the same operation count. */
+ ULONG curOp;
+ for (;;)
+ {
+ hrc = aProgressOther->COMGETTER(Operation(&curOp));
+ if (FAILED(hrc)) return hrc;
+ if (cOp != curOp)
+ {
+ Bstr bstr;
+ ULONG currentWeight;
+ hrc = aProgressOther->COMGETTER(OperationDescription(bstr.asOutParam()));
+ if (FAILED(hrc)) return hrc;
+ hrc = aProgressOther->COMGETTER(OperationWeight(&currentWeight));
+ if (FAILED(hrc)) return hrc;
+ hrc = SetNextOperation(bstr.raw(), currentWeight);
+ if (FAILED(hrc)) return hrc;
+ ++cOp;
+ }
+ else
+ break;
+ }
+
+ hrc = aProgressOther->COMGETTER(OperationPercent(&currentPercent));
+ if (FAILED(hrc)) return hrc;
+ if (currentPercent != prevPercent)
+ {
+ prevPercent = currentPercent;
+ hrc = SetCurrentOperationProgress(currentPercent);
+ if (FAILED(hrc)) return hrc;
+ }
+ }
+ if (fCompleted)
+ break;
+
+ if (aTimeoutMS != 0)
+ {
+ /* Make sure the loop is not too tight */
+ uint64_t u64Now = RTTimeMilliTS();
+ uint64_t u64RemainingMS = u64StopTime - u64Now;
+ if (u64RemainingMS < 10)
+ u64RemainingMS = 10;
+ else if (u64RemainingMS > 200)
+ u64RemainingMS = 200;
+ hrc = aProgressOther->WaitForCompletion((LONG)u64RemainingMS);
+ if (FAILED(hrc)) return hrc;
+
+ if (RTTimeMilliTS() >= u64StopTime)
+ return VBOX_E_TIMEOUT;
+ }
+ else
+ {
+ /* Make sure the loop is not too tight */
+ hrc = aProgressOther->WaitForCompletion(200);
+ if (FAILED(hrc)) return hrc;
+ }
+ }
+
+ /* Transfer error information if applicable and report the error status
+ * back to the caller to make this as easy as possible. */
+ LONG iRc;
+ hrc = aProgressOther->COMGETTER(ResultCode)(&iRc);
+ if (FAILED(hrc)) return hrc;
+ if (FAILED((HRESULT)iRc))
+ {
+ setError(ProgressErrorInfo(aProgressOther));
+ hrc = (HRESULT)iRc;
+ }
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * Signals that the current operation is successfully completed and advances to
+ * the next operation. The operation percentage is reset to 0.
+ *
+ * @param aNextOperationDescription Description of the next operation.
+ * @param aNextOperationsWeight Weight of the next operation.
+ *
+ * @note The current operation must not be the last one.
+ */
+HRESULT Progress::setNextOperation(const com::Utf8Str &aNextOperationDescription, ULONG aNextOperationsWeight)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mCanceled)
+ return E_FAIL;
+ AssertReturn(!mCompleted, E_FAIL);
+ AssertReturn(m_ulCurrentOperation + 1 < m_cOperations, E_FAIL);
+
+ ++m_ulCurrentOperation;
+ m_ulOperationsCompletedWeight += m_ulCurrentOperationWeight;
+
+ m_operationDescription = aNextOperationDescription;
+ m_ulCurrentOperationWeight = aNextOperationsWeight;
+ m_ulOperationPercent = 0;
+
+ LogThisFunc(("%s: aNextOperationsWeight = %d; m_ulCurrentOperation is now %d, m_ulOperationsCompletedWeight is now %d\n",
+ m_operationDescription.c_str(), aNextOperationsWeight, m_ulCurrentOperation, m_ulOperationsCompletedWeight));
+
+ /* wake up all waiting threads */
+ if (mWaitersCount > 0)
+ RTSemEventMultiSignal(mCompletedSem);
+
+ ULONG actualPercent = 0;
+ getPercent(&actualPercent);
+ ::FireProgressPercentageChangedEvent(pEventSource, mId.toString(), (LONG)actualPercent);
+
+ return S_OK;
+}
+
+/**
+ * Notify the progress object that we're almost at the point of no return.
+ *
+ * This atomically checks for and disables cancelation. Calls to
+ * IProgress::Cancel() made after a successful call to this method will fail
+ * and the user can be told. While this isn't entirely clean behavior, it
+ * prevents issues with an irreversible actually operation succeeding while the
+ * user believe it was rolled back.
+ *
+ * @returns COM error status.
+ * @retval S_OK on success.
+ * @retval E_FAIL if the progress object has already been canceled or is in an
+ * invalid state
+ */
+HRESULT Progress::notifyPointOfNoReturn(void)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mCanceled)
+ {
+ LogThisFunc(("returns failure\n"));
+ return E_FAIL;
+ }
+
+ mCancelable = FALSE;
+ LogThisFunc(("returns success\n"));
+ return S_OK;
+}
+
+/**
+ * Marks the operation as complete and attaches full error info.
+ *
+ * This is where the actual work is done, the related methods all end up here.
+ *
+ * @param aResultCode Operation result (error) code, must not be S_OK.
+ * @param aErrorInfo List of arguments for the format string.
+ */
+HRESULT Progress::notifyComplete(LONG aResultCode, const ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
+{
+ return i_notifyCompleteWorker((HRESULT)aResultCode, aErrorInfo);
+}
+
+
+// private internal helpers
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Marks the operation as complete and attaches full error info.
+ *
+ * This is where the actual work is done, the related methods all end up here.
+ *
+ * @param aResultCode Operation result (error) code, must not be S_OK.
+ * @param aErrorInfo List of arguments for the format string.
+ *
+ * @note This is just notifyComplete with the correct aResultCode type.
+ */
+HRESULT Progress::i_notifyCompleteWorker(HRESULT aResultCode, const ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
+{
+ LogThisFunc(("aResultCode=%Rhrc\n", aResultCode));
+ /* on failure we expect error info, on success there must be none */
+ AssertMsg(FAILED(aResultCode) ^ aErrorInfo.isNull(),
+ ("No error info but trying to set a failed result (%08X/%Rhrc)!\n", aResultCode, aResultCode));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(mCompleted == FALSE, E_FAIL);
+
+ if (mCanceled && SUCCEEDED(aResultCode))
+ aResultCode = E_FAIL;
+
+ mCompleted = TRUE;
+ mResultCode = aResultCode;
+ if (SUCCEEDED(aResultCode))
+ {
+ m_ulCurrentOperation = m_cOperations - 1; /* last operation */
+ m_ulOperationPercent = 100;
+ }
+ mErrorInfo = aErrorInfo;
+
+#if !defined(VBOX_COM_INPROC)
+ /* remove from the global collection of pending progress operations */
+ if (mParent)
+ mParent->i_removeProgress(mId.ref());
+#endif
+
+ /* wake up all waiting threads */
+ if (mWaitersCount > 0)
+ RTSemEventMultiSignal(mCompletedSem);
+
+ ::FireProgressTaskCompletedEvent(pEventSource, mId.toString());
+
+ return S_OK;
+}
+
+/**
+ * Internal helper to compute the total percent value based on the member values and
+ * returns it as a "double". This is used both by GetPercent (which returns it as a
+ * rounded ULONG) and GetTimeRemaining().
+ *
+ * Requires locking by the caller!
+ *
+ * @return fractional percentage as a double value.
+ */
+double Progress::i_calcTotalPercent()
+{
+ // avoid division by zero
+ if (m_ulTotalOperationsWeight == 0)
+ return 0.0;
+
+ double dPercent = ( (double)m_ulOperationsCompletedWeight // weight of operations that have been completed
+ + ((double)m_ulOperationPercent *
+ (double)m_ulCurrentOperationWeight / 100.0) // plus partial weight of the current operation
+ ) * 100.0 / (double)m_ulTotalOperationsWeight;
+
+ return dPercent;
+}
+
+/**
+ * Internal helper for automatically timing out the operation.
+ *
+ * The caller must hold the object write lock.
+ */
+void Progress::i_checkForAutomaticTimeout(void)
+{
+ AssertReturnVoid(isWriteLockOnCurrentThread());
+
+ if ( m_cMsTimeout
+ && mCancelable
+ && !mCanceled
+ && RTTimeMilliTS() - m_ullTimestamp > m_cMsTimeout)
+ Cancel();
+}
diff --git a/src/VBox/Main/src-all/QMTranslatorImpl.cpp b/src/VBox/Main/src-all/QMTranslatorImpl.cpp
new file mode 100644
index 00000000..f1d19aab
--- /dev/null
+++ b/src/VBox/Main/src-all/QMTranslatorImpl.cpp
@@ -0,0 +1,671 @@
+/* $Id: QMTranslatorImpl.cpp $ */
+/** @file
+ * VirtualBox API translation handling class
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <vector>
+#include <set>
+#include <algorithm>
+#include <iprt/sanitized/iterator>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/strcache.h>
+#include <VBox/com/string.h>
+#include <VBox/log.h>
+#include <QMTranslator.h>
+
+/* QM File Magic Number */
+static const size_t g_cbMagic = 16;
+static const uint8_t g_abMagic[g_cbMagic] =
+{
+ 0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
+ 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
+};
+
+/* Used internally */
+class QMException : public std::exception
+{
+ const char *m_str;
+public:
+ QMException(const char *str) : m_str(str) {}
+ virtual const char *what() const throw() { return m_str; }
+};
+
+/* Bytes stream. Used by the parser to iterate through the data */
+class QMBytesStream
+{
+ size_t m_cbSize;
+ const uint8_t * const m_dataStart;
+ const uint8_t *m_iter;
+ const uint8_t *m_end;
+
+public:
+
+ QMBytesStream(const uint8_t *const dataStart, size_t cbSize)
+ : m_cbSize(dataStart ? cbSize : 0)
+ , m_dataStart(dataStart)
+ , m_iter(dataStart)
+ {
+ setEnd();
+ }
+
+ /** Sets end pointer.
+ * Used in message reader to detect the end of message block */
+ inline void setEnd(size_t pos = 0)
+ {
+ m_end = m_dataStart + (pos && pos < m_cbSize ? pos : m_cbSize);
+ }
+
+ inline uint8_t read8()
+ {
+ checkSize(1);
+ return *m_iter++;
+ }
+
+ inline uint32_t read32()
+ {
+ checkSize(4);
+ uint32_t result = *reinterpret_cast<const uint32_t *>(m_iter);
+ m_iter += 4;
+ return RT_BE2H_U32(result);
+ }
+
+ /** Reads string in UTF16 and converts it into a UTF8 string */
+ inline com::Utf8Str readUtf16String()
+ {
+ uint32_t size = read32();
+ checkSize(size);
+ if (size & 1)
+ throw QMException("Incorrect string size");
+
+ /* UTF-16 can encode up to codepoint U+10ffff, which UTF-8 needs 4 bytes
+ to encode, so reserve twice the size plus a terminator for the result. */
+ com::Utf8Str result;
+ result.reserve(size * 2 + 1);
+ char *pszStr = result.mutableRaw();
+ int vrc = RTUtf16BigToUtf8Ex((PCRTUTF16)m_iter, size >> 1, &pszStr, result.capacity(), NULL);
+ if (RT_SUCCESS(vrc))
+ result.jolt();
+ else
+ throw QMException("Translation from UTF-16 to UTF-8 failed");
+
+ m_iter += size;
+ return result;
+ }
+
+ /**
+ * Reads a string, forcing UTF-8 encoding.
+ */
+ inline com::Utf8Str readString()
+ {
+ uint32_t size = read32();
+ checkSize(size);
+
+ com::Utf8Str result(reinterpret_cast<const char *>(m_iter), size);
+ if (size > 0)
+ {
+ RTStrPurgeEncoding(result.mutableRaw());
+ result.jolt();
+ }
+
+ m_iter += size;
+ return result;
+ }
+
+ /**
+ * Reads memory block
+ * Returns number of bytes read
+ */
+ inline uint32_t read(char *bBuf, uint32_t cbSize)
+ {
+ if (!bBuf || !cbSize)
+ return 0;
+ cbSize = RT_MIN(cbSize, (uint32_t)(m_end - m_iter));
+ memcpy(bBuf, m_iter, cbSize);
+ m_iter += cbSize;
+ return cbSize;
+ }
+
+ /** Checks the magic number.
+ * Should be called when in the beginning of the data
+ * @throws exception on mismatch */
+ inline void checkMagic()
+ {
+ checkSize(g_cbMagic);
+ if (RT_LIKELY(memcmp(&(*m_iter), g_abMagic, g_cbMagic) == 0))
+ m_iter += g_cbMagic;
+ else
+ throw QMException("Wrong magic number");
+ }
+
+ /** Has we reached the end pointer? */
+ inline bool hasFinished()
+ {
+ return m_iter == m_end;
+ }
+
+ /** Returns current stream position */
+ inline size_t tellPos()
+ {
+ return (size_t)(m_iter - m_dataStart);
+ }
+
+ /** Moves current pointer to a desired position */
+ inline void seek(uint32_t offSkip)
+ {
+ size_t cbLeft = (size_t)(m_end - m_iter);
+ if (cbLeft >= offSkip)
+ m_iter += offSkip;
+ else
+ m_iter = m_end; /** @todo r=bird: Or throw exception via checkSize? */
+ }
+
+ /** Checks whether stream has enough data to read size bytes */
+ inline void checkSize(size_t size)
+ {
+ if (RT_LIKELY((size_t)(m_end - m_iter) >= size))
+ return;
+ throw QMException("Incorrect item size");
+ }
+};
+
+/* Internal QMTranslator implementation */
+class QMTranslator_Impl
+{
+ /** Used while parsing */
+ struct QMMessageParse
+ {
+ /* Everything is in UTF-8 */
+ std::vector<com::Utf8Str> astrTranslations;
+ com::Utf8Str strContext;
+ com::Utf8Str strComment;
+ com::Utf8Str strSource;
+
+ QMMessageParse() {}
+ };
+
+ struct QMMessage
+ {
+ const char *pszContext;
+ const char *pszSource;
+ const char *pszComment;
+ std::vector<const char *> vecTranslations;
+ uint32_t hash;
+
+ QMMessage() : pszContext(NULL), pszSource(NULL), pszComment(NULL), hash(0)
+ {}
+
+ QMMessage(RTSTRCACHE hStrCache, const QMMessageParse &rSrc)
+ : pszContext(addStr(hStrCache, rSrc.strContext))
+ , pszSource(addStr(hStrCache, rSrc.strSource))
+ , pszComment(addStr(hStrCache, rSrc.strComment))
+ , hash(RTStrHash1(pszSource))
+ {
+ for (size_t i = 0; i < rSrc.astrTranslations.size(); i++)
+ vecTranslations.push_back(addStr(hStrCache, rSrc.astrTranslations[i]));
+ }
+
+ /** Helper. */
+ static const char *addStr(RTSTRCACHE hStrCache, const com::Utf8Str &rSrc)
+ {
+ if (rSrc.isNotEmpty())
+ {
+ const char *psz = RTStrCacheEnterN(hStrCache, rSrc.c_str(), rSrc.length());
+ if (RT_LIKELY(psz))
+ return psz;
+ throw std::bad_alloc();
+ }
+ return NULL;
+ }
+
+ };
+
+ struct HashOffset
+ {
+ uint32_t hash;
+ uint32_t offset;
+
+ HashOffset(uint32_t a_hash = 0, uint32_t a_offs = 0) : hash(a_hash), offset(a_offs) {}
+
+ bool operator<(const HashOffset &obj) const
+ {
+ return (hash != obj.hash ? hash < obj.hash : offset < obj.offset);
+ }
+
+ };
+
+ typedef std::set<HashOffset> QMHashSet;
+ typedef QMHashSet::const_iterator QMHashSetConstIter;
+ typedef std::vector<QMMessage> QMMessageArray;
+ typedef std::vector<uint8_t> QMByteArray;
+
+ QMHashSet m_hashSet;
+ QMMessageArray m_messageArray;
+ QMByteArray m_pluralRules;
+
+public:
+
+ QMTranslator_Impl() {}
+
+ enum PluralOpCodes
+ {
+ Pl_Eq = 0x01,
+ Pl_Lt = 0x02,
+ Pl_Leq = 0x03,
+ Pl_Between = 0x04,
+
+ Pl_OpMask = 0x07,
+
+ Pl_Not = 0x08,
+ Pl_Mod10 = 0x10,
+ Pl_Mod100 = 0x20,
+ Pl_Lead1000 = 0x40,
+
+ Pl_And = 0xFD,
+ Pl_Or = 0xFE,
+ Pl_NewRule = 0xFF,
+
+ Pl_LMask = 0x80,
+ };
+
+ /*
+ * Rules format:
+ * <O><2>[<3>][<&&><O><2>[<3>]]...[<||><O><2>[<3>][<&&><O><2>[<3>]]...]...[<New><O>...]...
+ * where:
+ * <O> - OpCode
+ * <2> - Second operand
+ * <3> - Third operand
+ * <&&> - 'And' operation
+ * <||> - 'Or' operation
+ * <New> - Start of rule for next plural form
+ * Rules are ordered by plural forms, i.e:
+ * <rule for first form (i.e. single)><New><rule for next form>...
+ */
+ bool checkPlural(const QMByteArray &aRules) const
+ {
+ if (aRules.empty())
+ return true;
+
+ uint32_t iPos = 0;
+ do {
+ uint8_t bOpCode = aRules[iPos];
+
+ /* Invalid place of And/Or/NewRule */
+ if (bOpCode & Pl_LMask)
+ return false;
+
+ /* 2nd operand */
+ iPos++;
+
+ /* 2nd operand missing */
+ if (iPos == aRules.size())
+ return false;
+
+ /* Invalid OpCode */
+ if ((bOpCode & Pl_OpMask) == 0)
+ return false;
+
+ if ((bOpCode & Pl_OpMask) == Pl_Between)
+ {
+ /* 3rd operand */
+ iPos++;
+
+ /* 3rd operand missing */
+ if (iPos == aRules.size())
+ return false;
+ }
+
+ /* And/Or/NewRule */
+ iPos++;
+
+ /* All rules checked */
+ if (iPos == aRules.size())
+ return true;
+
+ } while ( ( (aRules[iPos] == Pl_And)
+ || (aRules[iPos] == Pl_Or)
+ || (aRules[iPos] == Pl_NewRule))
+ && ++iPos != aRules.size());
+
+ return false;
+ }
+
+ size_t plural(size_t aNum) const
+ {
+ if (aNum == ~(size_t)0 || m_pluralRules.empty())
+ return 0;
+
+ size_t uPluralNumber = 0;
+ uint32_t iPos = 0;
+
+ /* Rules loop */
+ for (;;)
+ {
+ bool fOr = false;
+ /* 'Or' loop */
+ for (;;)
+ {
+ bool fAnd = true;
+ /* 'And' loop */
+ for (;;)
+ {
+ int iOpCode = m_pluralRules[iPos++];
+ size_t iOpLeft = aNum;
+ if (iOpCode & Pl_Mod10)
+ iOpLeft %= 10;
+ else if (iOpCode & Pl_Mod100)
+ iOpLeft %= 100;
+ else if (iOpCode & Pl_Lead1000)
+ {
+ while (iOpLeft >= 1000)
+ iOpLeft /= 1000;
+ }
+ size_t iOpRight = m_pluralRules[iPos++];
+ int iOp = iOpCode & Pl_OpMask;
+ size_t iOpRight1 = 0;
+ if (iOp == Pl_Between)
+ iOpRight1 = m_pluralRules[iPos++];
+
+ bool fResult = (iOp == Pl_Eq && iOpLeft == iOpRight)
+ || (iOp == Pl_Lt && iOpLeft < iOpRight)
+ || (iOp == Pl_Leq && iOpLeft <= iOpRight)
+ || (iOp == Pl_Between && iOpLeft >= iOpRight && iOpLeft <= iOpRight1);
+ if (iOpCode & Pl_Not)
+ fResult = !fResult;
+
+ fAnd = fAnd && fResult;
+ if (iPos == m_pluralRules.size() || m_pluralRules[iPos] != Pl_And)
+ break;
+ iPos++;
+ }
+ fOr = fOr || fAnd;
+ if (iPos == m_pluralRules.size() || m_pluralRules[iPos] != Pl_Or)
+ break;
+ iPos++;
+ }
+ if (fOr)
+ return uPluralNumber;
+
+ /* Qt returns last plural number if none of rules are match. */
+ uPluralNumber++;
+
+ if (iPos >= m_pluralRules.size())
+ return uPluralNumber;
+
+ iPos++; // Skip Pl_NewRule
+ }
+ }
+
+ const char *translate(const char *pszContext,
+ const char *pszSource,
+ const char *pszDisamb,
+ const size_t aNum,
+ const char **ppszSafeSource) const RT_NOEXCEPT
+ {
+ QMHashSetConstIter lowerIter, upperIter;
+
+ /* As turned out, comments (pszDisamb) are not kept always in result qm file
+ * Therefore, exclude them from the hash */
+ uint32_t hash = RTStrHash1(pszSource);
+ lowerIter = m_hashSet.lower_bound(HashOffset(hash, 0));
+ upperIter = m_hashSet.upper_bound(HashOffset(hash, UINT32_MAX));
+
+ /*
+ * Check different combinations with and without context and
+ * disambiguation. This can help us to find the translation even
+ * if context or disambiguation are not know or properly defined.
+ */
+ const char *apszCtx[] = {pszContext, pszContext, NULL, NULL};
+ const char *apszDisabm[] = {pszDisamb, NULL, pszDisamb, NULL};
+ AssertCompile(RT_ELEMENTS(apszCtx) == RT_ELEMENTS(apszDisabm));
+
+ for (size_t i = 0; i < RT_ELEMENTS(apszCtx); ++i)
+ {
+ for (QMHashSetConstIter iter = lowerIter; iter != upperIter; ++iter)
+ {
+ const QMMessage &message = m_messageArray[iter->offset];
+ if ( RTStrCmp(message.pszSource, pszSource) == 0
+ && (!apszCtx[i] || !*apszCtx[i] || RTStrCmp(message.pszContext, apszCtx[i]) == 0)
+ && (!apszDisabm[i] || !*apszDisabm[i] || RTStrCmp(message.pszComment, apszDisabm[i]) == 0 ))
+ {
+ *ppszSafeSource = message.pszSource;
+ const std::vector<const char *> &vecTranslations = m_messageArray[iter->offset].vecTranslations;
+ size_t const idxPlural = plural(aNum);
+ return vecTranslations[RT_MIN(idxPlural, vecTranslations.size() - 1)];
+ }
+ }
+ }
+
+ *ppszSafeSource = NULL;
+ return pszSource;
+ }
+
+ void load(QMBytesStream &stream, RTSTRCACHE hStrCache)
+ {
+ /* Load into local variables. If we failed during the load,
+ * it would allow us to keep the object in a valid (previous) state. */
+ QMHashSet hashSet;
+ QMMessageArray messageArray;
+ QMByteArray pluralRules;
+
+ stream.checkMagic();
+
+ while (!stream.hasFinished())
+ {
+ uint32_t sectionCode = stream.read8();
+ uint32_t sLen = stream.read32();
+
+ /* Hashes and Context sections are ignored. They contain hash tables
+ * to speed-up search which is not useful since we recalculate all hashes
+ * and don't perform context search by hash */
+ switch (sectionCode)
+ {
+ case Messages:
+ parseMessages(stream, hStrCache, &hashSet, &messageArray, sLen);
+ break;
+ case Hashes:
+ /* Only get size information to speed-up vector filling
+ * if Hashes section goes in the file before Message section */
+ if (messageArray.empty())
+ messageArray.reserve(sLen >> 3);
+ stream.seek(sLen);
+ break;
+ case NumerusRules:
+ {
+ pluralRules.resize(sLen);
+ uint32_t cbSize = stream.read((char *)&pluralRules[0], sLen);
+ if (cbSize < sLen)
+ throw QMException("Incorrect section size");
+ if (!checkPlural(pluralRules))
+ pluralRules.erase(pluralRules.begin(), pluralRules.end());
+ break;
+ }
+ case Contexts:
+ case Dependencies:
+ case Language:
+ stream.seek(sLen);
+ break;
+ default:
+ throw QMException("Unkown section");
+ }
+ }
+
+ /* Store the data into member variables.
+ * The following functions never generate exceptions */
+ m_hashSet.swap(hashSet);
+ m_messageArray.swap(messageArray);
+ m_pluralRules.swap(pluralRules);
+ }
+
+private:
+
+ /* Some QM stuff */
+ enum SectionType
+ {
+ Contexts = 0x2f,
+ Hashes = 0x42,
+ Messages = 0x69,
+ NumerusRules = 0x88,
+ Dependencies = 0x96,
+ Language = 0xa7
+ };
+
+ enum MessageType
+ {
+ End = 1,
+ SourceText16 = 2,
+ Translation = 3,
+ Context16 = 4,
+ Obsolete1 = 5, /**< was Hash */
+ SourceText = 6,
+ Context = 7,
+ Comment = 8
+ };
+
+ /* Read messages from the stream. */
+ static void parseMessages(QMBytesStream &stream, RTSTRCACHE hStrCache, QMHashSet * const hashSet,
+ QMMessageArray * const messageArray, size_t cbSize)
+ {
+ stream.setEnd(stream.tellPos() + cbSize);
+ uint32_t cMessage = 0;
+ while (!stream.hasFinished())
+ {
+ /* Process the record. Skip anything that doesn't have a source
+ string or any valid translations. Using C++ strings for temporary
+ storage here, as we don't want to pollute the cache we bogus strings
+ in case of duplicate sub-records or invalid records. */
+ QMMessageParse ParsedMsg;
+ parseMessageRecord(stream, &ParsedMsg);
+ if ( ParsedMsg.astrTranslations.size() > 0
+ && ParsedMsg.strSource.isNotEmpty())
+ {
+ /* Copy the strings over into the string cache and a hashed QMMessage,
+ before adding it to the result. */
+ QMMessage HashedMsg(hStrCache, ParsedMsg);
+ hashSet->insert(HashOffset(HashedMsg.hash, cMessage++));
+ messageArray->push_back(HashedMsg);
+
+ }
+ /*else: wtf? */
+ }
+ stream.setEnd();
+ }
+
+ /* Parse one message from the stream */
+ static void parseMessageRecord(QMBytesStream &stream, QMMessageParse * const message)
+ {
+ while (!stream.hasFinished())
+ {
+ uint8_t type = stream.read8();
+ switch (type)
+ {
+ case End:
+ return;
+ /* Ignored as obsolete */
+ case Context16:
+ case SourceText16:
+ stream.seek(stream.read32());
+ break;
+ case Translation:
+ message->astrTranslations.push_back(stream.readUtf16String());
+ break;
+
+ case SourceText:
+ message->strSource = stream.readString();
+ break;
+
+ case Context:
+ message->strContext = stream.readString();
+ break;
+
+ case Comment:
+ message->strComment = stream.readString();
+ break;
+
+ default:
+ /* Ignore unknown/obsolete block */
+ LogRel(("QMTranslator::parseMessageRecord(): Unknown/obsolete message block %x\n", type));
+ break;
+ }
+ }
+ }
+};
+
+/* Inteface functions implementation */
+QMTranslator::QMTranslator() : m_impl(new QMTranslator_Impl) {}
+
+QMTranslator::~QMTranslator() { delete m_impl; }
+
+const char *QMTranslator::translate(const char *pszContext, const char *pszSource, const char **ppszSafeSource,
+ const char *pszDisamb /*= NULL*/, const size_t aNum /*= ~(size_t)0*/) const RT_NOEXCEPT
+
+{
+ return m_impl->translate(pszContext, pszSource, pszDisamb, aNum, ppszSafeSource);
+}
+
+int QMTranslator::load(const char *pszFilename, RTSTRCACHE hStrCache) RT_NOEXCEPT
+{
+ /* To free safely the file in case of exception */
+ struct FileLoader
+ {
+ uint8_t *data;
+ size_t cbSize;
+ int vrc;
+ FileLoader(const char *pszFname)
+ {
+ vrc = RTFileReadAll(pszFname, (void**) &data, &cbSize);
+ }
+
+ ~FileLoader()
+ {
+ if (isSuccess())
+ RTFileReadAllFree(data, cbSize);
+ }
+ bool isSuccess() { return RT_SUCCESS(vrc); }
+ };
+
+ try
+ {
+ FileLoader loader(pszFilename);
+ if (loader.isSuccess())
+ {
+ QMBytesStream stream(loader.data, loader.cbSize);
+ m_impl->load(stream, hStrCache);
+ }
+ return loader.vrc;
+ }
+ catch(std::exception &e)
+ {
+ LogRel(("QMTranslator::load() failed to load file '%s', reason: %s\n", pszFilename, e.what()));
+ return VERR_INTERNAL_ERROR;
+ }
+ catch(...)
+ {
+ LogRel(("QMTranslator::load() failed to load file '%s'\n", pszFilename));
+ return VERR_GENERAL_FAILURE;
+ }
+}
diff --git a/src/VBox/Main/src-all/SecretKeyStore.cpp b/src/VBox/Main/src-all/SecretKeyStore.cpp
new file mode 100644
index 00000000..ca2b4396
--- /dev/null
+++ b/src/VBox/Main/src-all/SecretKeyStore.cpp
@@ -0,0 +1,248 @@
+/* $Id: SecretKeyStore.cpp $ */
+/** @file
+ * Main - Secret key interface.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/memsafer.h>
+
+#include "SecretKeyStore.h"
+
+SecretKey::SecretKey(const uint8_t *pbKey, size_t cbKey, bool fKeyBufNonPageable)
+{
+ m_cRefs = 0;
+ m_fRemoveOnSuspend = false;
+ m_cUsers = 0;
+ m_cbKey = cbKey;
+
+ int vrc = RTMemSaferAllocZEx((void **)&this->m_pbKey, cbKey,
+ fKeyBufNonPageable ? RTMEMSAFER_F_REQUIRE_NOT_PAGABLE : 0);
+ if (RT_SUCCESS(vrc))
+ {
+ memcpy(this->m_pbKey, pbKey, cbKey);
+
+ /* Scramble content to make retrieving the key more difficult. */
+ vrc = RTMemSaferScramble(this->m_pbKey, cbKey);
+ }
+ else
+ throw vrc;
+}
+
+SecretKey::~SecretKey()
+{
+ Assert(!m_cRefs);
+
+ RTMemSaferFree(m_pbKey, m_cbKey);
+ m_cRefs = 0;
+ m_pbKey = NULL;
+ m_cbKey = 0;
+ m_fRemoveOnSuspend = false;
+ m_cUsers = 0;
+}
+
+uint32_t SecretKey::retain()
+{
+ uint32_t cRefs = ASMAtomicIncU32(&m_cRefs);
+ if (cRefs == 1)
+ {
+ int vrc = RTMemSaferUnscramble(m_pbKey, m_cbKey);
+ AssertRC(vrc);
+ }
+
+ return cRefs;
+}
+
+uint32_t SecretKey::release()
+{
+ uint32_t cRefs = ASMAtomicDecU32(&m_cRefs);
+ if (!cRefs)
+ {
+ int vrc = RTMemSaferScramble(m_pbKey, m_cbKey);
+ AssertRC(vrc);
+ }
+
+ return cRefs;
+}
+
+uint32_t SecretKey::refCount()
+{
+ return m_cRefs;
+}
+
+int SecretKey::setUsers(uint32_t cUsers)
+{
+ m_cUsers = cUsers;
+ return VINF_SUCCESS;
+}
+
+uint32_t SecretKey::getUsers()
+{
+ return m_cUsers;
+}
+
+int SecretKey::setRemoveOnSuspend(bool fRemoveOnSuspend)
+{
+ m_fRemoveOnSuspend = fRemoveOnSuspend;
+ return VINF_SUCCESS;
+}
+
+bool SecretKey::getRemoveOnSuspend()
+{
+ return m_fRemoveOnSuspend;
+}
+
+const void *SecretKey::getKeyBuffer()
+{
+ AssertReturn(m_cRefs > 0, NULL);
+ return m_pbKey;
+}
+
+size_t SecretKey::getKeySize()
+{
+ return m_cbKey;
+}
+
+SecretKeyStore::SecretKeyStore(bool fKeyBufNonPageable)
+{
+ m_fKeyBufNonPageable = fKeyBufNonPageable;
+}
+
+SecretKeyStore::~SecretKeyStore()
+{
+ int vrc = deleteAllSecretKeys(false /* fSuspend */, true /* fForce */);
+ AssertRC(vrc);
+}
+
+int SecretKeyStore::addSecretKey(const com::Utf8Str &strKeyId, const uint8_t *pbKey, size_t cbKey)
+{
+ /* Check that the ID is not existing already. */
+ SecretKeyMap::const_iterator it = m_mapSecretKeys.find(strKeyId);
+ if (it != m_mapSecretKeys.end())
+ return VERR_ALREADY_EXISTS;
+
+ SecretKey *pKey = NULL;
+ try
+ {
+ pKey = new SecretKey(pbKey, cbKey, m_fKeyBufNonPageable);
+
+ m_mapSecretKeys.insert(std::make_pair(strKeyId, pKey));
+ }
+ catch (int vrc)
+ {
+ return vrc;
+ }
+ catch (std::bad_alloc &)
+ {
+ if (pKey)
+ delete pKey;
+ return VERR_NO_MEMORY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+int SecretKeyStore::deleteSecretKey(const com::Utf8Str &strKeyId)
+{
+ SecretKeyMap::iterator it = m_mapSecretKeys.find(strKeyId);
+ if (it == m_mapSecretKeys.end())
+ return VERR_NOT_FOUND;
+
+ SecretKey *pKey = it->second;
+ if (pKey->refCount() != 0)
+ return VERR_RESOURCE_IN_USE;
+
+ m_mapSecretKeys.erase(it);
+ delete pKey;
+
+ return VINF_SUCCESS;
+}
+
+int SecretKeyStore::retainSecretKey(const com::Utf8Str &strKeyId, SecretKey **ppKey)
+{
+ SecretKeyMap::const_iterator it = m_mapSecretKeys.find(strKeyId);
+ if (it == m_mapSecretKeys.end())
+ return VERR_NOT_FOUND;
+
+ SecretKey *pKey = it->second;
+ pKey->retain();
+
+ *ppKey = pKey;
+
+ return VINF_SUCCESS;
+}
+
+int SecretKeyStore::releaseSecretKey(const com::Utf8Str &strKeyId)
+{
+ SecretKeyMap::const_iterator it = m_mapSecretKeys.find(strKeyId);
+ if (it == m_mapSecretKeys.end())
+ return VERR_NOT_FOUND;
+
+ SecretKey *pKey = it->second;
+ pKey->release();
+ return VINF_SUCCESS;
+}
+
+int SecretKeyStore::deleteAllSecretKeys(bool fSuspend, bool fForce)
+{
+ /* First check whether a key is still in use. */
+ if (!fForce)
+ {
+ for (SecretKeyMap::iterator it = m_mapSecretKeys.begin();
+ it != m_mapSecretKeys.end();
+ ++it)
+ {
+ SecretKey *pKey = it->second;
+ if ( pKey->refCount()
+ && ( ( pKey->getRemoveOnSuspend()
+ && fSuspend)
+ || !fSuspend))
+ return VERR_RESOURCE_IN_USE;
+ }
+ }
+
+ SecretKeyMap::iterator it = m_mapSecretKeys.begin();
+ while (it != m_mapSecretKeys.end())
+ {
+ SecretKey *pKey = it->second;
+ if ( pKey->getRemoveOnSuspend()
+ || !fSuspend)
+ {
+ AssertMsg(!pKey->refCount(), ("No one should access the stored key at this point anymore!\n"));
+ delete pKey;
+ SecretKeyMap::iterator itNext = it;
+ ++itNext;
+ m_mapSecretKeys.erase(it);
+ it = itNext;
+ }
+ else
+ ++it;
+ }
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Main/src-all/SharedFolderImpl.cpp b/src/VBox/Main/src-all/SharedFolderImpl.cpp
new file mode 100644
index 00000000..6e531c2b
--- /dev/null
+++ b/src/VBox/Main/src-all/SharedFolderImpl.cpp
@@ -0,0 +1,465 @@
+/* $Id: SharedFolderImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_SHAREDFOLDER
+#include "SharedFolderImpl.h"
+#if !defined(VBOX_COM_INPROC)
+# include "VirtualBoxImpl.h"
+# include "MachineImpl.h"
+#endif
+#include "ConsoleImpl.h"
+
+#include "AutoCaller.h"
+
+#include <iprt/param.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/path.h>
+
+/////////////////////////////////////////////////////////////////////////////
+// SharedFolder::Data structure
+/////////////////////////////////////////////////////////////////////////////
+
+struct SharedFolder::Data
+{
+ Data()
+ : fWritable(false),
+ fAutoMount(false)
+ { }
+
+ const Utf8Str strName;
+ const Utf8Str strHostPath;
+ bool fWritable;
+ bool fAutoMount;
+ const Utf8Str strAutoMountPoint;
+ Utf8Str strLastAccessError;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+SharedFolder::SharedFolder()
+ : mParent(NULL),
+#if !defined(VBOX_COM_INPROC)
+ mMachine(NULL),
+ mVirtualBox(NULL)
+#else
+ mConsole(NULL)
+#endif
+{
+ m = new Data;
+}
+
+SharedFolder::~SharedFolder()
+{
+ delete m;
+ m = NULL;
+}
+
+HRESULT SharedFolder::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void SharedFolder::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+#if !defined(VBOX_COM_INPROC)
+/**
+ * Initializes the shared folder object.
+ *
+ * This variant initializes a machine instance that lives in the server address space.
+ *
+ * @param aMachine parent Machine object
+ * @param aName logical name of the shared folder
+ * @param aHostPath full path to the shared folder on the host
+ * @param aWritable writable if true, readonly otherwise
+ * @param aAutoMount if auto mounted by guest true, false otherwise
+ * @param aAutoMountPoint Where the guest should try auto mount it.
+ * @param fFailOnError Whether to fail with an error if the shared folder path is bad.
+ *
+ * @return COM result indicator
+ */
+HRESULT SharedFolder::init(Machine *aMachine,
+ const Utf8Str &aName,
+ const Utf8Str &aHostPath,
+ bool aWritable,
+ bool aAutoMount,
+ const Utf8Str &aAutoMountPoint,
+ bool fFailOnError)
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mMachine) = aMachine;
+
+ HRESULT hrc = i_protectedInit(aMachine, aName, aHostPath, aWritable, aAutoMount, aAutoMountPoint, fFailOnError);
+
+ /* Confirm a successful initialization when it's the case */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+
+ return hrc;
+}
+
+/**
+ * Initializes the shared folder object given another object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ *
+ * @param aMachine parent Machine object
+ * @param aThat shared folder object to copy
+ *
+ * @return COM result indicator
+ */
+HRESULT SharedFolder::initCopy(Machine *aMachine, SharedFolder *aThat)
+{
+ ComAssertRet(aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mMachine) = aMachine;
+
+ HRESULT hrc = i_protectedInit(aMachine,
+ aThat->m->strName,
+ aThat->m->strHostPath,
+ aThat->m->fWritable,
+ aThat->m->fAutoMount,
+ aThat->m->strAutoMountPoint,
+ false /* fFailOnError */ );
+
+ /* Confirm a successful initialization when it's the case */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+
+ return hrc;
+}
+
+# if 0
+
+/**
+ * Initializes the shared folder object.
+ *
+ * This variant initializes a global instance that lives in the server address space. It is not presently used.
+ *
+ * @param aVirtualBox VirtualBox parent object
+ * @param aName logical name of the shared folder
+ * @param aHostPath full path to the shared folder on the host
+ * @param aWritable writable if true, readonly otherwise
+ * @param aAutoMountPoint Where the guest should try auto mount it.
+ * @param fFailOnError Whether to fail with an error if the shared folder path is bad.
+ *
+ * @return COM result indicator
+ */
+HRESULT SharedFolder::init(VirtualBox *aVirtualBox,
+ const Utf8Str &aName,
+ const Utf8Str &aHostPath,
+ bool aWritable,
+ bool aAutoMount,
+ const Utf8Str &aAutoMountPoint
+ bool fFailOnError)
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mVirtualBox) = aVirtualBox;
+
+ HRESULT hrc = protectedInit(aVirtualBox, aName, aHostPath, aWritable, aAutoMount, aAutoMountPoint, fFailOnError);
+
+ /* Confirm a successful initialization when it's the case */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+
+ return hrc;
+}
+
+# endif
+
+#else
+
+/**
+ * Initializes the shared folder object.
+ *
+ * This variant initializes an instance that lives in the console address space.
+ *
+ * @param aConsole Console parent object
+ * @param aName logical name of the shared folder
+ * @param aHostPath full path to the shared folder on the host
+ * @param aWritable writable if true, readonly otherwise
+ * @param aAutoMountPoint Where the guest should try auto mount it.
+ * @param fFailOnError Whether to fail with an error if the shared folder path is bad.
+ *
+ * @return COM result indicator
+ */
+HRESULT SharedFolder::init(Console *aConsole,
+ const Utf8Str &aName,
+ const Utf8Str &aHostPath,
+ bool aWritable,
+ bool aAutoMount,
+ const Utf8Str &aAutoMountPoint,
+ bool fFailOnError)
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mConsole) = aConsole;
+
+ HRESULT hrc = i_protectedInit(aConsole, aName, aHostPath, aWritable, aAutoMount, aAutoMountPoint, fFailOnError);
+
+ /* Confirm a successful initialization when it's the case */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+
+ return hrc;
+}
+#endif
+
+/**
+ * Shared initialization code. Called from the other constructors.
+ *
+ * @note
+ * Must be called from under the object's lock!
+ */
+HRESULT SharedFolder::i_protectedInit(VirtualBoxBase *aParent,
+ const Utf8Str &aName,
+ const Utf8Str &aHostPath,
+ bool aWritable,
+ bool aAutoMount,
+ const Utf8Str &aAutoMountPoint,
+ bool fFailOnError)
+{
+ LogFlowThisFunc(("aName={%s}, aHostPath={%s}, aWritable={%d}, aAutoMount={%d}\n",
+ aName.c_str(), aHostPath.c_str(), aWritable, aAutoMount));
+
+ ComAssertRet(aParent && aName.isNotEmpty() && aHostPath.isNotEmpty(), E_INVALIDARG);
+
+ Utf8Str hostPath = aHostPath;
+ size_t hostPathLen = hostPath.length();
+
+ /* Remove the trailing slash unless it's a root directory
+ * (otherwise the comparison with the RTPathAbs() result will fail at least
+ * on Linux). Note that this isn't really necessary for the shared folder
+ * itself, since adding a mapping eventually results into a
+ * RTDirOpenFiltered() call (see HostServices/SharedFolders) that seems to
+ * accept both the slashified paths and not. */
+#if defined (RT_OS_OS2) || defined (RT_OS_WINDOWS)
+ if ( hostPathLen > 2
+ && RTPATH_IS_SEP(hostPath.c_str()[hostPathLen - 1])
+ && RTPATH_IS_VOLSEP(hostPath.c_str()[hostPathLen - 2]))
+ ;
+#else
+ if (hostPathLen == 1 && RTPATH_IS_SEP(hostPath[0]))
+ ;
+#endif
+ else
+ hostPath.stripTrailingSlash();
+
+ if (fFailOnError)
+ {
+ /* Check whether the path is full (absolute) */
+ char hostPathFull[RTPATH_MAX];
+ int vrc = RTPathAbs(hostPath.c_str(),
+ hostPathFull,
+ sizeof(hostPathFull));
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid shared folder path: '%s' (%Rrc)"), hostPath.c_str(), vrc);
+
+ if (RTPathCompare(hostPath.c_str(), hostPathFull) != 0)
+ return setError(E_INVALIDARG, tr("Shared folder path '%s' is not absolute"), hostPath.c_str());
+
+ RTFSOBJINFO ObjInfo;
+ vrc = RTPathQueryInfoEx(hostPathFull, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_FAILURE(vrc))
+ return setError(E_INVALIDARG, tr("RTPathQueryInfo failed on shared folder path '%s': %Rrc"), hostPathFull, vrc);
+
+ if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ return setError(E_INVALIDARG, tr("Shared folder path '%s' is not a directory"), hostPathFull);
+ }
+
+ unconst(mParent) = aParent;
+
+ unconst(m->strName) = aName;
+ unconst(m->strHostPath) = hostPath;
+ m->fWritable = aWritable;
+ m->fAutoMount = aAutoMount;
+ unconst(m->strAutoMountPoint) = aAutoMountPoint;
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void SharedFolder::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(mParent) = NULL;
+
+#if !defined(VBOX_COM_INPROC)
+ unconst(mMachine) = NULL;
+ unconst(mVirtualBox) = NULL;
+#else
+ unconst(mConsole) = NULL;
+#endif
+}
+
+// wrapped ISharedFolder properties
+/////////////////////////////////////////////////////////////////////////////
+HRESULT SharedFolder::getName(com::Utf8Str &aName)
+{
+ /* mName is constant during life time, no need to lock */
+ aName = m->strName;
+ return S_OK;
+}
+
+HRESULT SharedFolder::getHostPath(com::Utf8Str &aHostPath)
+{
+ /* mHostPath is constant during life time, no need to lock */
+ aHostPath = m->strHostPath;
+ return S_OK;
+}
+
+HRESULT SharedFolder::getAccessible(BOOL *aAccessible)
+{
+ /* mName and mHostPath are constant during life time, no need to lock */
+
+ /* check whether the host path exists */
+ Utf8Str hostPath = m->strHostPath;
+ char hostPathFull[RTPATH_MAX];
+ int vrc = RTPathExists(hostPath.c_str()) ? RTPathReal(hostPath.c_str(),
+ hostPathFull,
+ sizeof(hostPathFull))
+ : VERR_PATH_NOT_FOUND;
+ if (RT_SUCCESS(vrc))
+ {
+ *aAccessible = TRUE;
+ return S_OK;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->strLastAccessError = Utf8StrFmt(tr("'%s' is not accessible (%Rrc)"),
+ m->strHostPath.c_str(),
+ vrc);
+
+ Log1WarningThisFunc(("m.lastAccessError=\"%s\"\n", m->strLastAccessError.c_str()));
+
+ *aAccessible = FALSE;
+
+ return S_OK;
+}
+
+HRESULT SharedFolder::getWritable(BOOL *aWritable)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aWritable = m->fWritable;
+ return S_OK;
+}
+
+HRESULT SharedFolder::setWritable(BOOL aWritable)
+{
+ RT_NOREF(aWritable);
+ return E_NOTIMPL;
+}
+
+HRESULT SharedFolder::getAutoMount(BOOL *aAutoMount)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aAutoMount = m->fAutoMount;
+ return S_OK;
+}
+
+HRESULT SharedFolder::setAutoMount(BOOL aAutoMount)
+{
+ RT_NOREF(aAutoMount);
+ return E_NOTIMPL;
+}
+
+HRESULT SharedFolder::getAutoMountPoint(com::Utf8Str &aAutoMountPoint)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aAutoMountPoint = m->strAutoMountPoint;
+ return S_OK;
+}
+
+HRESULT SharedFolder::setAutoMountPoint(com::Utf8Str const &aAutoMountPoint)
+{
+ RT_NOREF(aAutoMountPoint);
+ return E_NOTIMPL;
+}
+
+HRESULT SharedFolder::getLastAccessError(com::Utf8Str &aLastAccessError)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aLastAccessError = m->strLastAccessError;
+ return S_OK;
+}
+
+
+const Utf8Str& SharedFolder::i_getName() const
+{
+ return m->strName;
+}
+
+const Utf8Str& SharedFolder::i_getHostPath() const
+{
+ return m->strHostPath;
+}
+
+bool SharedFolder::i_isWritable() const
+{
+ return m->fWritable;
+}
+
+bool SharedFolder::i_isAutoMounted() const
+{
+ return m->fAutoMount;
+}
+
+const Utf8Str &SharedFolder::i_getAutoMountPoint() const
+{
+ return m->strAutoMountPoint;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/TextScript.cpp b/src/VBox/Main/src-all/TextScript.cpp
new file mode 100644
index 00000000..94811057
--- /dev/null
+++ b/src/VBox/Main/src-all/TextScript.cpp
@@ -0,0 +1,388 @@
+/* $Id: TextScript.cpp $ */
+/** @file
+ * Classes for reading/parsing/saving text scripts (unattended installation, ++).
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
+#include "LoggingNew.h"
+#include "TextScript.h"
+
+#include <VBox/err.h>
+
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/vfs.h>
+#include <iprt/path.h>
+
+using namespace std;
+
+
+/*********************************************************************************************************************************
+* BaseTextScript Implementation *
+*********************************************************************************************************************************/
+
+HRESULT BaseTextScript::read(const Utf8Str &rStrFilename)
+{
+ /*
+ * Open the file for reading and figure it's size. Capping the size
+ * at 16MB so we don't exaust the heap on bad input.
+ */
+ HRESULT hrc;
+ RTVFSFILE hVfsFile;
+ int vrc = RTVfsFileOpenNormal(rStrFilename.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = readFromHandle(hVfsFile, rStrFilename.c_str());
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("Failed to open '%s' (%Rrc)"), rStrFilename.c_str(), vrc);
+ return hrc;
+}
+
+HRESULT BaseTextScript::readFromHandle(RTVFSFILE hVfsFile, const char *pszFilename)
+{
+ /*
+ * Open the file for reading and figure it's size. Capping the size
+ * at 16MB so we don't exaust the heap on bad input.
+ */
+ HRESULT hrc;
+ uint64_t cbFile = 0;
+ int vrc = RTVfsFileQuerySize(hVfsFile, &cbFile);
+ if ( RT_SUCCESS(vrc)
+ && cbFile < _16M)
+ {
+ /*
+ * Exploint the jolt() feature of RTCString and read the content directly into
+ * its storage buffer.
+ */
+ vrc = mStrScriptFullContent.reserveNoThrow((size_t)cbFile + 1);
+ if (RT_SUCCESS(vrc))
+ {
+ char *pszDst = mStrScriptFullContent.mutableRaw();
+ vrc = RTVfsFileReadAt(hVfsFile, 0 /*off*/, pszDst, (size_t)cbFile, NULL);
+ pszDst[(size_t)cbFile] = '\0';
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * We must validate the encoding or we'll be subject to potential security trouble.
+ * If this turns out to be problematic, we will need to implement codeset
+ * conversion coping mechanisms.
+ */
+ vrc = RTStrValidateEncodingEx(pszDst, (size_t)cbFile + 1,
+ RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED | RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(vrc))
+ {
+ mStrScriptFullContent.jolt();
+ return S_OK;
+ }
+
+ hrc = mpSetError->setErrorVrc(vrc, tr("'%s' isn't valid UTF-8: %Rrc"), pszFilename, vrc);
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("Error reading '%s': %Rrc"), pszFilename, vrc);
+ mStrScriptFullContent.setNull();
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("Failed to allocate memory (%'RU64 bytes) for '%s'", "", cbFile),
+ cbFile, pszFilename);
+ }
+ else if (RT_SUCCESS(vrc))
+ hrc = mpSetError->setErrorVrc(VERR_FILE_TOO_BIG, tr("'%s' is too big (max 16MB): %'RU64"), pszFilename, cbFile);
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("RTVfsFileQuerySize failed (%Rrc)"), vrc);
+ return hrc;
+}
+
+HRESULT BaseTextScript::save(const Utf8Str &rStrFilename, bool fOverwrite)
+{
+ /*
+ * We may have to append the default filename to the
+ */
+ const char *pszFilename = rStrFilename.c_str();
+ Utf8Str strWithDefaultFilename;
+ if ( getDefaultFilename() != NULL
+ && *getDefaultFilename() != '\0'
+ && RTDirExists(rStrFilename.c_str()) )
+ {
+ try
+ {
+ strWithDefaultFilename = rStrFilename;
+ strWithDefaultFilename.append(RTPATH_SLASH);
+ strWithDefaultFilename.append(getDefaultFilename());
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ pszFilename = strWithDefaultFilename.c_str();
+ }
+
+ /*
+ * Save the filename for later use.
+ */
+ try
+ {
+ mStrSavedPath = pszFilename;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Use the saveToString method to produce the content.
+ */
+ Utf8Str strDst;
+ HRESULT hrc = saveToString(strDst);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Write the content.
+ */
+ RTFILE hFile;
+ uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_ALL;
+ if (fOverwrite)
+ fOpen |= RTFILE_O_CREATE_REPLACE;
+ else
+ fOpen |= RTFILE_O_CREATE;
+ int vrc = RTFileOpen(&hFile, pszFilename, fOpen);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTFileWrite(hFile, strDst.c_str(), strDst.length(), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTFileClose(hFile);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRelFlow(("GeneralTextScript::save(): saved %zu bytes to '%s'\n", strDst.length(), pszFilename));
+ return S_OK;
+ }
+ }
+ RTFileClose(hFile);
+ RTFileDelete(pszFilename);
+ hrc = mpSetError->setErrorVrc(vrc, tr("Error writing to '%s' (%Rrc)"), pszFilename, vrc);
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("Error creating/replacing '%s' (%Rrc)"), pszFilename, vrc);
+ }
+ return hrc;
+}
+
+
+
+/*********************************************************************************************************************************
+* GeneralTextScript Implementation *
+*********************************************************************************************************************************/
+
+HRESULT GeneralTextScript::parse()
+{
+ AssertReturn(!mfDataParsed, mpSetError->setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("parse called more than once")));
+
+ /*
+ * Split the raw context into an array of lines.
+ */
+ try
+ {
+ mScriptContentByLines = mStrScriptFullContent.split("\n");
+ }
+ catch (std::bad_alloc &)
+ {
+ mScriptContentByLines.clear();
+ return E_OUTOFMEMORY;
+ }
+
+ mfDataParsed = true;
+ return S_OK;
+}
+
+HRESULT GeneralTextScript::saveToString(Utf8Str &rStrDst)
+{
+ AssertReturn(mfDataParsed, mpSetError->setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("saveToString() called before parse()")));
+
+ /*
+ * Calc the required size first.
+ */
+ size_t const cLines = mScriptContentByLines.size();
+ size_t cbTotal = 1;
+ for (size_t iLine = 0; iLine < cLines; iLine++)
+ cbTotal = mScriptContentByLines[iLine].length() + 1;
+
+ /*
+ * Clear the output and try reserve sufficient space.
+ */
+ rStrDst.setNull();
+
+ int vrc = rStrDst.reserveNoThrow(cbTotal);
+ if (RT_FAILURE(vrc))
+ return E_OUTOFMEMORY;
+
+ /*
+ * Assemble the output.
+ */
+ for (size_t iLine = 0; iLine < cLines; iLine++)
+ {
+ try
+ {
+ rStrDst.append(mScriptContentByLines[iLine]);
+ rStrDst.append('\n');
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ return S_OK;
+}
+
+const RTCString &GeneralTextScript::getContentOfLine(size_t idxLine)
+{
+ if (idxLine < mScriptContentByLines.size())
+ return mScriptContentByLines[idxLine];
+ return Utf8Str::Empty;
+}
+
+
+HRESULT GeneralTextScript::setContentOfLine(size_t idxLine, const Utf8Str &rStrNewLine)
+{
+ AssertReturn(idxLine < mScriptContentByLines.size(),
+ mpSetError->setErrorBoth(E_FAIL, VERR_OUT_OF_RANGE,
+ tr("attempting to set line %zu when there are only %zu lines", "",
+ mScriptContentByLines.size()),
+ idxLine, mScriptContentByLines.size()));
+ try
+ {
+ mScriptContentByLines[idxLine] = rStrNewLine;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+vector<size_t> GeneralTextScript::findTemplate(const Utf8Str &rStrNeedle,
+ RTCString::CaseSensitivity enmCase /*= RTCString::CaseSensitive*/)
+{
+ vector<size_t> vecHitLineNumbers;
+ size_t const cLines = mScriptContentByLines.size();
+ for (size_t iLine = 0; iLine < cLines; iLine++)
+ if (mScriptContentByLines[iLine].contains(rStrNeedle, enmCase))
+ vecHitLineNumbers.push_back(iLine);
+
+ return vecHitLineNumbers;
+}
+
+HRESULT GeneralTextScript::findAndReplace(size_t idxLine, const Utf8Str &rStrNeedle, const Utf8Str &rStrReplacement)
+{
+ AssertReturn(idxLine < mScriptContentByLines.size(),
+ mpSetError->setErrorBoth(E_FAIL, VERR_OUT_OF_RANGE,
+ tr("attempting search&replace in line %zu when there are only %zu lines", "",
+ mScriptContentByLines.size()),
+ idxLine, mScriptContentByLines.size()));
+
+ RTCString &rDstString = mScriptContentByLines[idxLine];
+ size_t const offNeedle = rDstString.find(&rStrNeedle);
+ if (offNeedle != RTCString::npos)
+ {
+ try
+ {
+ RTCString strBefore(rDstString, 0, offNeedle);
+ RTCString strAfter(rDstString, offNeedle + rStrNeedle.length());
+ rDstString = strBefore;
+ strBefore.setNull();
+ rDstString.append(rStrReplacement);
+ rDstString.append(strAfter);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+ return S_OK;
+}
+
+HRESULT GeneralTextScript::appendToLine(size_t idxLine, const Utf8Str &rStrToAppend)
+{
+ AssertReturn(idxLine < mScriptContentByLines.size(),
+ mpSetError->setErrorBoth(E_FAIL, VERR_OUT_OF_RANGE,
+ tr("appending to line %zu when there are only %zu lines", "",
+ mScriptContentByLines.size()),
+ idxLine, mScriptContentByLines.size()));
+
+ try
+ {
+ mScriptContentByLines[idxLine].append(rStrToAppend);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+HRESULT GeneralTextScript::prependToLine(size_t idxLine, const Utf8Str &rStrToPrepend)
+{
+ AssertReturn(idxLine < mScriptContentByLines.size(),
+ mpSetError->setErrorBoth(E_FAIL, VERR_OUT_OF_RANGE,
+ tr("prepending to line %zu when there are only %zu lines", "",
+ mScriptContentByLines.size()),
+ idxLine, mScriptContentByLines.size()));
+
+ RTCString &rDstString = mScriptContentByLines[idxLine];
+ try
+ {
+ RTCString strCopy;
+ rDstString.swap(strCopy);
+ rDstString.reserve(strCopy.length() + rStrToPrepend.length() + 1);
+ rDstString = rStrToPrepend;
+ rDstString.append(strCopy);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+HRESULT GeneralTextScript::appendLine(const Utf8Str &rStrLineToAppend)
+{
+ AssertReturn(mfDataParsed, mpSetError->setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("appendLine() called before parse()")));
+
+ try
+ {
+ mScriptContentByLines.append(rStrLineToAppend);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
diff --git a/src/VBox/Main/src-all/ThreadTask.cpp b/src/VBox/Main/src-all/ThreadTask.cpp
new file mode 100644
index 00000000..3fd702e8
--- /dev/null
+++ b/src/VBox/Main/src-all/ThreadTask.cpp
@@ -0,0 +1,136 @@
+/* $Id: ThreadTask.cpp $ */
+/** @file
+ * Implementation of ThreadTask
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <iprt/errcore.h>
+#include <iprt/thread.h>
+
+#include "VirtualBoxBase.h"
+#include "ThreadTask.h"
+
+#define LOG_GROUP LOG_GROUP_MAIN_THREAD_TASK
+#include "LoggingNew.h"
+
+/**
+ * Starts the task (on separate thread), consuming @a this.
+ *
+ * The function takes ownership of "this" instance (object instance which calls
+ * this function). And the function is responsible for deletion of "this"
+ * pointer in all cases.
+ *
+ * Possible way of usage:
+ * @code{.cpp}
+ HRESULT hr;
+ SomeTaskInheritedFromThreadTask *pTask = NULL;
+ try
+ {
+ pTask = new SomeTaskInheritedFromThreadTask(this);
+ if (!pTask->Init()) // some init procedure
+ throw E_FAIL;
+ }
+ catch (...)
+ {
+ if (pTask);
+ delete pTask;
+ return E_FAIL;
+ }
+ return pTask->createThread(); // pTask is always consumed
+ @endcode
+ *
+ * @sa createThreadWithType
+ *
+ * @note Always consumes @a this!
+ */
+HRESULT ThreadTask::createThread(void)
+{
+ return createThreadInternal(RTTHREADTYPE_MAIN_WORKER);
+}
+
+
+/**
+ * Same ThreadTask::createThread(), except it takes a thread type parameter.
+ *
+ * @param enmType The thread type.
+ *
+ * @note Always consumes @a this!
+ */
+HRESULT ThreadTask::createThreadWithType(RTTHREADTYPE enmType)
+{
+ return createThreadInternal(enmType);
+}
+
+
+/**
+ * Internal worker for ThreadTask::createThread,
+ * ThreadTask::createThreadWithType.
+ *
+ * @note Always consumes @a this!
+ */
+HRESULT ThreadTask::createThreadInternal(RTTHREADTYPE enmType)
+{
+ LogThisFunc(("Created \"%s\"\n", m_strTaskName.c_str()));
+
+ mAsync = true;
+ int vrc = RTThreadCreate(NULL,
+ taskHandlerThreadProc,
+ (void *)this,
+ 0,
+ enmType,
+ 0,
+ m_strTaskName.c_str());
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+
+ mAsync = false;
+ delete this;
+ return E_FAIL;
+}
+
+
+/**
+ * Static method that can get passed to RTThreadCreate to have a
+ * thread started for a Task.
+ */
+/* static */ DECLCALLBACK(int) ThreadTask::taskHandlerThreadProc(RTTHREAD /* thread */, void *pvUser)
+{
+ if (pvUser == NULL)
+ return VERR_INVALID_POINTER; /* nobody cares */
+
+ ThreadTask *pTask = static_cast<ThreadTask *>(pvUser);
+
+ LogFunc(("Started \"%s\"\n", pTask->m_strTaskName.c_str()));
+
+ /*
+ * handler shall catch and process all possible cases as errors and exceptions.
+ */
+ pTask->handler();
+
+ LogFunc(("Ended \"%s\"\n", pTask->m_strTaskName.c_str()));
+
+ delete pTask;
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Main/src-all/VBoxAPI-end-alternative.d b/src/VBox/Main/src-all/VBoxAPI-end-alternative.d
new file mode 100644
index 00000000..71912691
--- /dev/null
+++ b/src/VBox/Main/src-all/VBoxAPI-end-alternative.d
@@ -0,0 +1,3 @@
+
+};
+
diff --git a/src/VBox/Main/src-all/VBoxAPI-end.d b/src/VBox/Main/src-all/VBoxAPI-end.d
new file mode 100644
index 00000000..5f88a01b
--- /dev/null
+++ b/src/VBox/Main/src-all/VBoxAPI-end.d
@@ -0,0 +1,9 @@
+
+};
+
+#pragma D attributes Evolving/Evolving/Common provider vboxapi provider
+#pragma D attributes Private/Private/Unknown provider vboxapi module
+#pragma D attributes Private/Private/Unknown provider vboxapi function
+#pragma D attributes Evolving/Evolving/Common provider vboxapi name
+#pragma D attributes Evolving/Evolving/Common provider vboxapi args
+
diff --git a/src/VBox/Main/src-all/VBoxAPI-start-alternative.d b/src/VBox/Main/src-all/VBoxAPI-start-alternative.d
new file mode 100644
index 00000000..c60fb51e
--- /dev/null
+++ b/src/VBox/Main/src-all/VBoxAPI-start-alternative.d
@@ -0,0 +1,39 @@
+/* $Id: VBoxAPI-start-alternative.d $ */
+/** @file
+ * VBoxAPI - Static dtrace probes.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*#pragma D attributes Evolving/Evolving/Common provider vboxapi provider
+#pragma D attributes Private/Private/Unknown provider vboxapi module
+#pragma D attributes Private/Private/Unknown provider vboxapi function
+#pragma D attributes Evolving/Evolving/Common provider vboxapi name
+#pragma D attributes Evolving/Evolving/Common provider vboxapi args*/
+
+provider vboxapi
+{
+ /* Manually defined probes: */
+ probe machine__state__changed(void *a_pMachine, int a_enmNewState, int a_enmOldState, const char *pszMachineUuid);
+
+ /* The following probes are automatically generated and changes with the API: */
diff --git a/src/VBox/Main/src-all/VBoxAPI-start.d b/src/VBox/Main/src-all/VBoxAPI-start.d
new file mode 100644
index 00000000..72c01782
--- /dev/null
+++ b/src/VBox/Main/src-all/VBoxAPI-start.d
@@ -0,0 +1,33 @@
+/* $Id: VBoxAPI-start.d $ */
+/** @file
+ * VBoxAPI - Static dtrace probes.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+provider vboxapi
+{
+ /* Manually defined probes: */
+ probe machine__state__changed(void *a_pMachine, int a_enmNewState, int a_enmOldState, const char *pszMachineUuid);
+
+ /* The following probes are automatically generated and changes with the API: */
diff --git a/src/VBox/Main/src-all/VBoxLibSsh.def b/src/VBox/Main/src-all/VBoxLibSsh.def
new file mode 100644
index 00000000..4f85d707
--- /dev/null
+++ b/src/VBox/Main/src-all/VBoxLibSsh.def
@@ -0,0 +1,35 @@
+; $Id: VBoxLibSsh.def $
+;; @file
+; VBoxLibSsh - Definition file for lazy import generation for VBoxC.
+;
+
+;
+; Copyright (C) 2022 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; SPDX-License-Identifier: GPL-3.0-only
+;
+
+LIBRARY VBoxLibSsh
+EXPORTS
+ ssh_key_free
+ ssh_pki_generate
+ ssh_pki_export_privkey_base64
+ ssh_pki_export_privkey_file
+ ssh_pki_export_pubkey_base64
+ ssh_string_free_char
diff --git a/src/VBox/Main/src-all/VirtualBoxBase.cpp b/src/VBox/Main/src-all/VirtualBoxBase.cpp
new file mode 100644
index 00000000..17bb6a2f
--- /dev/null
+++ b/src/VBox/Main/src-all/VirtualBoxBase.cpp
@@ -0,0 +1,884 @@
+/* $Id: VirtualBoxBase.cpp $ */
+/** @file
+ * VirtualBox COM base classes implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN
+#include <iprt/semaphore.h>
+#include <iprt/asm.h>
+#include <iprt/cpp/exception.h>
+
+#include <typeinfo>
+
+#if !defined(VBOX_WITH_XPCOM)
+# include <iprt/win/windows.h>
+#else /* !defined(VBOX_WITH_XPCOM) */
+/// @todo remove when VirtualBoxErrorInfo goes away from here
+# include <nsIServiceManager.h>
+# include <nsIExceptionService.h>
+#endif /* !defined(VBOX_WITH_XPCOM) */
+
+#include "VirtualBoxBase.h"
+#include "AutoCaller.h"
+#include "VirtualBoxErrorInfoImpl.h"
+#include "VirtualBoxTranslator.h"
+#include "Global.h"
+#include "LoggingNew.h"
+
+#include "VBox/com/ErrorInfo.h"
+#include "VBox/com/MultiResult.h"
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// VirtualBoxBase
+//
+////////////////////////////////////////////////////////////////////////////////
+
+CLASSFACTORY_STAT g_aClassFactoryStats[CLASSFACTORYSTATS_MAX] =
+{
+ { "--- totals ---", 0 },
+ { NULL, 0 }
+};
+
+RWLockHandle *g_pClassFactoryStatsLock = NULL;
+
+
+VirtualBoxBase::VirtualBoxBase() :
+ mState(this),
+ iFactoryStat(~0U)
+{
+ mObjectLock = NULL;
+
+ if (!g_pClassFactoryStatsLock)
+ {
+ RWLockHandle *lock = new RWLockHandle(LOCKCLASS_OBJECTSTATE);
+ if (!ASMAtomicCmpXchgPtr(&g_pClassFactoryStatsLock, lock, NULL))
+ delete lock;
+ }
+ Assert(g_pClassFactoryStatsLock);
+}
+
+VirtualBoxBase::~VirtualBoxBase()
+{
+ Assert(iFactoryStat == ~0U);
+ if (mObjectLock)
+ delete mObjectLock;
+}
+
+HRESULT VirtualBoxBase::BaseFinalConstruct()
+{
+ Assert(iFactoryStat == ~0U);
+ if (g_pClassFactoryStatsLock)
+ {
+ AutoWriteLock alock(g_pClassFactoryStatsLock COMMA_LOCKVAL_SRC_POS);
+ g_aClassFactoryStats[0].current++;
+ g_aClassFactoryStats[0].overall++;
+ const char *pszName = getComponentName();
+ uint32_t i = 1;
+ while (i < CLASSFACTORYSTATS_MAX && g_aClassFactoryStats[i].psz)
+ {
+ if (g_aClassFactoryStats[i].psz == pszName)
+ break;
+ i++;
+ }
+ if (i < CLASSFACTORYSTATS_MAX)
+ {
+ if (!g_aClassFactoryStats[i].psz)
+ {
+ g_aClassFactoryStats[i].psz = pszName;
+ g_aClassFactoryStats[i].current = 0;
+ g_aClassFactoryStats[i].overall = 0;
+ }
+ iFactoryStat = i;
+ g_aClassFactoryStats[i].current++;
+ g_aClassFactoryStats[i].overall++;
+ }
+ else
+ AssertMsg(i < CLASSFACTORYSTATS_MAX, ("%u exhausts size of factory housekeeping array\n", i));
+ }
+ else
+ Assert(g_pClassFactoryStatsLock);
+
+#ifdef RT_OS_WINDOWS
+ return CoCreateFreeThreadedMarshaler(this, //GetControllingUnknown(),
+ m_pUnkMarshaler.asOutParam());
+#else
+ return S_OK;
+#endif
+}
+
+void VirtualBoxBase::BaseFinalRelease()
+{
+ if (g_pClassFactoryStatsLock)
+ {
+ AutoWriteLock alock(g_pClassFactoryStatsLock COMMA_LOCKVAL_SRC_POS);
+ g_aClassFactoryStats[0].current--;
+ const char *pszName = getComponentName();
+ if (iFactoryStat < CLASSFACTORYSTATS_MAX)
+ {
+ if (g_aClassFactoryStats[iFactoryStat].psz == pszName)
+ {
+ g_aClassFactoryStats[iFactoryStat].current--;
+ iFactoryStat = ~0U;
+ }
+ else
+ AssertMsgFailed(("could not find factory housekeeping array entry for %s (index %u contains %s)\n", pszName, iFactoryStat, g_aClassFactoryStats[iFactoryStat].psz));
+ }
+ else
+ AssertMsgFailed(("factory housekeeping array corruption, index %u is too large\n", iFactoryStat));
+ }
+ else
+ Assert(g_pClassFactoryStatsLock);
+
+#ifdef RT_OS_WINDOWS
+ m_pUnkMarshaler.setNull();
+#endif
+}
+
+void APIDumpComponentFactoryStats()
+{
+ if (g_pClassFactoryStatsLock)
+ {
+ AutoReadLock alock(g_pClassFactoryStatsLock COMMA_LOCKVAL_SRC_POS);
+ for (uint32_t i = 0; i < CLASSFACTORYSTATS_MAX && g_aClassFactoryStats[i].psz; i++)
+ LogRel(("CFS: component %-30s current %-10u total %-10u\n",
+ g_aClassFactoryStats[i].psz, g_aClassFactoryStats[i].current,
+ g_aClassFactoryStats[i].overall));
+ }
+ else
+ Assert(g_pClassFactoryStatsLock);
+}
+
+/**
+ * This virtual method returns an RWLockHandle that can be used to
+ * protect instance data. This RWLockHandle is generally referred to
+ * as the "object lock"; its locking class (for lock order validation)
+ * must be returned by another virtual method, getLockingClass(), which
+ * by default returns LOCKCLASS_OTHEROBJECT but is overridden by several
+ * subclasses such as VirtualBox, Host, Machine and others.
+ *
+ * On the first call this method lazily creates the RWLockHandle.
+ *
+ * @return
+ */
+/* virtual */
+RWLockHandle *VirtualBoxBase::lockHandle() const
+{
+ /* lazy initialization */
+ if (RT_LIKELY(mObjectLock))
+ return mObjectLock;
+
+ AssertCompile(sizeof(RWLockHandle *) == sizeof(void *));
+
+ // getLockingClass() is overridden by many subclasses to return
+ // one of the locking classes listed at the top of AutoLock.h
+ RWLockHandle *objLock = new RWLockHandle(getLockingClass());
+ if (!ASMAtomicCmpXchgPtr(&mObjectLock, objLock, NULL))
+ {
+ delete objLock;
+ objLock = ASMAtomicReadPtrT(&mObjectLock, RWLockHandle *);
+ }
+ return objLock;
+}
+
+/**
+ * Handles unexpected exceptions by turning them into COM errors in release
+ * builds or by hitting a breakpoint in the release builds.
+ *
+ * Usage pattern:
+ * @code
+ try
+ {
+ // ...
+ }
+ catch (LaLalA)
+ {
+ // ...
+ }
+ catch (...)
+ {
+ hrc = VirtualBox::handleUnexpectedExceptions(this, RT_SRC_POS);
+ }
+ * @endcode
+ *
+ * @param aThis object where the exception happened
+ * @param SRC_POS "RT_SRC_POS" macro instantiation.
+ * */
+/* static */
+HRESULT VirtualBoxBase::handleUnexpectedExceptions(VirtualBoxBase *const aThis, RT_SRC_POS_DECL)
+{
+ try
+ {
+ /* re-throw the current exception */
+ throw;
+ }
+ catch (const RTCError &err) // includes all XML exceptions
+ {
+ return setErrorInternalF(E_FAIL, aThis->getClassIID(), aThis->getComponentName(),
+ false /* aWarning */,
+ true /* aLogIt */,
+ 0 /* aResultDetail */,
+ "%s.\n%s[%d] (%s)",
+ err.what(), pszFile, iLine, pszFunction);
+ }
+ catch (const std::exception &err)
+ {
+ return setErrorInternalF(E_FAIL, aThis->getClassIID(), aThis->getComponentName(),
+ false /* aWarning */,
+ true /* aLogIt */,
+ 0 /* aResultDetail */,
+ tr("Unexpected exception: %s [%s]\n%s[%d] (%s)"),
+ err.what(), typeid(err).name(), pszFile, iLine, pszFunction);
+ }
+ catch (...)
+ {
+ return setErrorInternalF(E_FAIL, aThis->getClassIID(), aThis->getComponentName(),
+ false /* aWarning */,
+ true /* aLogIt */,
+ 0 /* aResultDetail */,
+ tr("Unknown exception\n%s[%d] (%s)"),
+ pszFile, iLine, pszFunction);
+ }
+
+#ifndef _MSC_VER /* (unreachable) */
+ /* should not get here */
+ AssertFailed();
+ return E_FAIL;
+#endif
+}
+
+
+/**
+ * Sets error info for the current thread. This is an internal function that
+ * gets eventually called by all public variants. If @a aWarning is
+ * @c true, then the highest (31) bit in the @a aResultCode value which
+ * indicates the error severity is reset to zero to make sure the receiver will
+ * recognize that the created error info object represents a warning rather
+ * than an error.
+ */
+/* static */
+HRESULT VirtualBoxBase::setErrorInternalF(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *aComponent,
+ bool aWarning,
+ bool aLogIt,
+ LONG aResultDetail,
+ const char *aText, ...)
+{
+ va_list va;
+ va_start(va, aText);
+ HRESULT hres = setErrorInternalV(aResultCode, aIID, aComponent, aText, va,
+ aWarning, aLogIt, aResultDetail);
+ va_end(va);
+ return hres;
+}
+
+/**
+ * Sets error info for the current thread. This is an internal function that
+ * gets eventually called by all public variants. If @a aWarning is
+ * @c true, then the highest (31) bit in the @a aResultCode value which
+ * indicates the error severity is reset to zero to make sure the receiver will
+ * recognize that the created error info object represents a warning rather
+ * than an error.
+ */
+/* static */
+HRESULT VirtualBoxBase::setErrorInternalV(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *aComponent,
+ const char *aText,
+ va_list aArgs,
+ bool aWarning,
+ bool aLogIt,
+ LONG aResultDetail /* = 0*/)
+{
+ /* whether multi-error mode is turned on */
+ bool preserve = MultiResult::isMultiEnabled();
+
+ com::Utf8Str strText;
+ if (aLogIt)
+ {
+#ifdef VBOX_WITH_MAIN_NLS
+ strText = VirtualBoxTranslator::trSource(aText);
+#else
+ strText = aText;
+#endif
+ va_list va2;
+ va_copy(va2, aArgs);
+ LogRel(("%s [COM]: aRC=%Rhrc (%#08x) aIID={%RTuuid} aComponent={%s} aText={%N}, preserve=%RTbool aResultDetail=%d\n",
+ aWarning ? "WARNING" : "ERROR",
+ aResultCode,
+ aResultCode,
+ &aIID,
+ aComponent,
+ strText.c_str(),
+ &va2,
+ preserve,
+ aResultDetail));
+ va_end(va2);
+ }
+
+ /* these are mandatory, others -- not */
+ AssertReturn((!aWarning && FAILED(aResultCode)) ||
+ (aWarning && aResultCode != S_OK),
+ E_FAIL);
+
+ /* reset the error severity bit if it's a warning */
+ if (aWarning)
+ aResultCode &= ~0x80000000;
+
+ HRESULT hrc = S_OK;
+
+ if (aText == NULL || aText[0] == '\0')
+ {
+ /* Some default info */
+ switch (aResultCode)
+ {
+ case E_INVALIDARG: strText = tr("A parameter has an invalid value"); break;
+ case E_POINTER: strText = tr("A parameter is an invalid pointer"); break;
+ case E_UNEXPECTED: strText = tr("The result of the operation is unexpected"); break;
+ case E_ACCESSDENIED: strText = tr("The access to an object is not allowed"); break;
+ case E_OUTOFMEMORY: strText = tr("The allocation of new memory failed"); break;
+ case E_NOTIMPL: strText = tr("The requested operation is not implemented"); break;
+ case E_NOINTERFACE: strText = tr("The requested interface is not implemented"); break;
+ case E_FAIL: strText = tr("A general error occurred"); break;
+ case E_ABORT: strText = tr("The operation was canceled"); break;
+ case VBOX_E_OBJECT_NOT_FOUND: strText = tr("Object corresponding to the supplied arguments does not exist"); break;
+ case VBOX_E_INVALID_VM_STATE: strText = tr("Current virtual machine state prevents the operation"); break;
+ case VBOX_E_VM_ERROR: strText = tr("Virtual machine error occurred attempting the operation"); break;
+ case VBOX_E_FILE_ERROR: strText = tr("File not accessible or erroneous file contents"); break;
+ case VBOX_E_IPRT_ERROR: strText = tr("Runtime subsystem error"); break;
+ case VBOX_E_PDM_ERROR: strText = tr("Pluggable Device Manager error"); break;
+ case VBOX_E_INVALID_OBJECT_STATE: strText = tr("Current object state prohibits operation"); break;
+ case VBOX_E_HOST_ERROR: strText = tr("Host operating system related error"); break;
+ case VBOX_E_NOT_SUPPORTED: strText = tr("Requested operation is not supported"); break;
+ case VBOX_E_XML_ERROR: strText = tr("Invalid XML found"); break;
+ case VBOX_E_INVALID_SESSION_STATE: strText = tr("Current session state prohibits operation"); break;
+ case VBOX_E_OBJECT_IN_USE: strText = tr("Object being in use prohibits operation"); break;
+ case VBOX_E_PASSWORD_INCORRECT: strText = tr("Incorrect password provided"); break;
+ default: strText = tr("Unknown error"); break;
+ }
+ }
+ else
+ {
+ va_list va2;
+ va_copy(va2, aArgs);
+ strText = com::Utf8StrFmt("%N", aText, &va2);
+ va_end(va2);
+ }
+
+ do
+ {
+ ComObjPtr<VirtualBoxErrorInfo> info;
+ hrc = info.createObject();
+ if (FAILED(hrc)) break;
+
+#if !defined(VBOX_WITH_XPCOM)
+
+ ComPtr<IVirtualBoxErrorInfo> curInfo;
+ if (preserve)
+ {
+ /* get the current error info if any */
+ ComPtr<IErrorInfo> err;
+ hrc = ::GetErrorInfo(0, err.asOutParam());
+ if (FAILED(hrc)) break;
+ hrc = err.queryInterfaceTo(curInfo.asOutParam());
+ if (FAILED(hrc))
+ {
+ /* create a IVirtualBoxErrorInfo wrapper for the native
+ * IErrorInfo object */
+ ComObjPtr<VirtualBoxErrorInfo> wrapper;
+ hrc = wrapper.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = wrapper->init(err);
+ if (SUCCEEDED(hrc))
+ curInfo = wrapper;
+ }
+ }
+ }
+ /* On failure, curInfo will stay null */
+ Assert(SUCCEEDED(hrc) || curInfo.isNull());
+
+ /* set the current error info and preserve the previous one if any */
+ hrc = info->initEx(aResultCode, aResultDetail, aIID, aComponent, strText, curInfo);
+ if (FAILED(hrc)) break;
+
+ ComPtr<IErrorInfo> err;
+ hrc = info.queryInterfaceTo(err.asOutParam());
+ if (SUCCEEDED(hrc))
+ hrc = ::SetErrorInfo(0, err);
+
+#else // !defined(VBOX_WITH_XPCOM)
+
+ nsCOMPtr <nsIExceptionService> es;
+ es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &hrc);
+ if (NS_SUCCEEDED(hrc))
+ {
+ nsCOMPtr <nsIExceptionManager> em;
+ hrc = es->GetCurrentExceptionManager(getter_AddRefs(em));
+ if (FAILED(hrc)) break;
+
+ ComPtr<IVirtualBoxErrorInfo> curInfo;
+ if (preserve)
+ {
+ /* get the current error info if any */
+ ComPtr<nsIException> ex;
+ hrc = em->GetCurrentException(ex.asOutParam());
+ if (FAILED(hrc)) break;
+ hrc = ex.queryInterfaceTo(curInfo.asOutParam());
+ if (FAILED(hrc))
+ {
+ /* create a IVirtualBoxErrorInfo wrapper for the native
+ * nsIException object */
+ ComObjPtr<VirtualBoxErrorInfo> wrapper;
+ hrc = wrapper.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = wrapper->init(ex);
+ if (SUCCEEDED(hrc))
+ curInfo = wrapper;
+ }
+ }
+ }
+ /* On failure, curInfo will stay null */
+ Assert(SUCCEEDED(hrc) || curInfo.isNull());
+
+ /* set the current error info and preserve the previous one if any */
+ hrc = info->initEx(aResultCode, aResultDetail, aIID, aComponent, Bstr(strText), curInfo);
+ if (FAILED(hrc)) break;
+
+ ComPtr<nsIException> ex;
+ hrc = info.queryInterfaceTo(ex.asOutParam());
+ if (SUCCEEDED(hrc))
+ hrc = em->SetCurrentException(ex);
+ }
+ else if (hrc == NS_ERROR_UNEXPECTED)
+ {
+ /*
+ * It is possible that setError() is being called by the object
+ * after the XPCOM shutdown sequence has been initiated
+ * (for example, when XPCOM releases all instances it internally
+ * references, which can cause object's FinalConstruct() and then
+ * uninit()). In this case, do_GetService() above will return
+ * NS_ERROR_UNEXPECTED and it doesn't actually make sense to
+ * set the exception (nobody will be able to read it).
+ */
+ Log1WarningFunc(("Will not set an exception because nsIExceptionService is not available (NS_ERROR_UNEXPECTED). XPCOM is being shutdown?\n"));
+ hrc = NS_OK;
+ }
+
+#endif // !defined(VBOX_WITH_XPCOM)
+ }
+ while (0);
+
+ AssertComRC(hrc);
+
+ return SUCCEEDED(hrc) ? aResultCode : hrc;
+}
+
+/**
+ * Shortcut instance method to calling the static setErrorInternal with the
+ * class interface ID and component name inserted correctly. This uses the
+ * virtual getClassIID() and getComponentName() methods which are automatically
+ * defined by the VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT macro.
+ * @param aResultCode
+ * @return
+ */
+HRESULT VirtualBoxBase::setError(HRESULT aResultCode)
+{
+ return setErrorInternalF(aResultCode,
+ this->getClassIID(),
+ this->getComponentName(),
+ false /* aWarning */,
+ true /* aLogIt */,
+ 0 /* aResultDetail */,
+ NULL);
+}
+
+/**
+ * Shortcut instance method to calling the static setErrorInternal with the
+ * class interface ID and component name inserted correctly. This uses the
+ * virtual getClassIID() and getComponentName() methods which are automatically
+ * defined by the VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT macro.
+ * @param aResultCode
+ * @param pcsz
+ * @return
+ */
+HRESULT VirtualBoxBase::setError(HRESULT aResultCode, const char *pcsz, ...)
+{
+ va_list args;
+ va_start(args, pcsz);
+ HRESULT hrc = setErrorInternalV(aResultCode,
+ this->getClassIID(),
+ this->getComponentName(),
+ pcsz, args,
+ false /* aWarning */,
+ true /* aLogIt */);
+ va_end(args);
+ return hrc;
+}
+
+/**
+ * Shortcut instance method to calling the static setErrorInternal with the
+ * class interface ID and component name inserted correctly. This uses the
+ * virtual getClassIID() and getComponentName() methods which are automatically
+ * defined by the VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT macro.
+ * @param ei
+ * @return
+ */
+HRESULT VirtualBoxBase::setError(const com::ErrorInfo &ei)
+{
+ /* whether multi-error mode is turned on */
+ bool preserve = MultiResult::isMultiEnabled();
+
+ HRESULT hrc = S_OK;
+
+ do
+ {
+ ComObjPtr<VirtualBoxErrorInfo> info;
+ hrc = info.createObject();
+ if (FAILED(hrc)) break;
+
+#if !defined(VBOX_WITH_XPCOM)
+
+ ComPtr<IVirtualBoxErrorInfo> curInfo;
+ if (preserve)
+ {
+ /* get the current error info if any */
+ ComPtr<IErrorInfo> err;
+ hrc = ::GetErrorInfo(0, err.asOutParam());
+ if (FAILED(hrc)) break;
+ hrc = err.queryInterfaceTo(curInfo.asOutParam());
+ if (FAILED(hrc))
+ {
+ /* create a IVirtualBoxErrorInfo wrapper for the native
+ * IErrorInfo object */
+ ComObjPtr<VirtualBoxErrorInfo> wrapper;
+ hrc = wrapper.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = wrapper->init(err);
+ if (SUCCEEDED(hrc))
+ curInfo = wrapper;
+ }
+ }
+ }
+ /* On failure, curInfo will stay null */
+ Assert(SUCCEEDED(hrc) || curInfo.isNull());
+
+ /* set the current error info and preserve the previous one if any */
+ hrc = info->init(ei, curInfo);
+ if (FAILED(hrc)) break;
+
+ ComPtr<IErrorInfo> err;
+ hrc = info.queryInterfaceTo(err.asOutParam());
+ if (SUCCEEDED(hrc))
+ hrc = ::SetErrorInfo(0, err);
+
+#else // !defined(VBOX_WITH_XPCOM)
+
+ nsCOMPtr <nsIExceptionService> es;
+ es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &hrc);
+ if (NS_SUCCEEDED(hrc))
+ {
+ nsCOMPtr <nsIExceptionManager> em;
+ hrc = es->GetCurrentExceptionManager(getter_AddRefs(em));
+ if (FAILED(hrc)) break;
+
+ ComPtr<IVirtualBoxErrorInfo> curInfo;
+ if (preserve)
+ {
+ /* get the current error info if any */
+ ComPtr<nsIException> ex;
+ hrc = em->GetCurrentException(ex.asOutParam());
+ if (FAILED(hrc)) break;
+ hrc = ex.queryInterfaceTo(curInfo.asOutParam());
+ if (FAILED(hrc))
+ {
+ /* create a IVirtualBoxErrorInfo wrapper for the native
+ * nsIException object */
+ ComObjPtr<VirtualBoxErrorInfo> wrapper;
+ hrc = wrapper.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = wrapper->init(ex);
+ if (SUCCEEDED(hrc))
+ curInfo = wrapper;
+ }
+ }
+ }
+ /* On failure, curInfo will stay null */
+ Assert(SUCCEEDED(hrc) || curInfo.isNull());
+
+ /* set the current error info and preserve the previous one if any */
+ hrc = info->init(ei, curInfo);
+ if (FAILED(hrc)) break;
+
+ ComPtr<nsIException> ex;
+ hrc = info.queryInterfaceTo(ex.asOutParam());
+ if (SUCCEEDED(hrc))
+ hrc = em->SetCurrentException(ex);
+ }
+ else if (hrc == NS_ERROR_UNEXPECTED)
+ {
+ /*
+ * It is possible that setError() is being called by the object
+ * after the XPCOM shutdown sequence has been initiated
+ * (for example, when XPCOM releases all instances it internally
+ * references, which can cause object's FinalConstruct() and then
+ * uninit()). In this case, do_GetService() above will return
+ * NS_ERROR_UNEXPECTED and it doesn't actually make sense to
+ * set the exception (nobody will be able to read it).
+ */
+ Log1WarningFunc(("Will not set an exception because nsIExceptionService is not available (NS_ERROR_UNEXPECTED). XPCOM is being shutdown?\n"));
+ hrc = NS_OK;
+ }
+
+#endif // !defined(VBOX_WITH_XPCOM)
+ }
+ while (0);
+
+ AssertComRC(hrc);
+
+ return SUCCEEDED(hrc) ? ei.getResultCode() : hrc;
+}
+
+/**
+ * Converts the VBox status code a COM one and sets the error info.
+ *
+ * The VBox status code is made available to the API user via
+ * IVirtualBoxErrorInfo::resultDetail attribute.
+ *
+ * @param vrc The VBox status code.
+ * @return COM status code appropriate for @a vrc.
+ *
+ * @sa VirtualBoxBase::setError(HRESULT)
+ */
+HRESULT VirtualBoxBase::setErrorVrc(int vrc)
+{
+ return setErrorInternalF(Global::vboxStatusCodeToCOM(vrc),
+ this->getClassIID(),
+ this->getComponentName(),
+ false /* aWarning */,
+ true /* aLogIt */,
+ vrc /* aResultDetail */,
+ Utf8StrFmt("%Rrc", vrc).c_str());
+}
+
+/**
+ * Converts the VBox status code a COM one and sets the error info.
+ *
+ * @param vrc The VBox status code.
+ * @param pcszMsgFmt Error message format string.
+ * @param va_args Error message format string.
+ * @return COM status code appropriate for @a vrc.
+ *
+ * @sa VirtualBoxBase::setError(HRESULT, const char *, ...)
+ */
+HRESULT VirtualBoxBase::setErrorVrcV(int vrc, const char *pcszMsgFmt, va_list va_args)
+{
+ return setErrorInternalV(Global::vboxStatusCodeToCOM(vrc),
+ this->getClassIID(),
+ this->getComponentName(),
+ pcszMsgFmt, va_args,
+ false /* aWarning */,
+ true /* aLogIt */,
+ vrc /* aResultDetail */);
+}
+
+/**
+ * Converts the VBox status code a COM one and sets the error info.
+ *
+ * @param vrc The VBox status code.
+ * @param pcszMsgFmt Error message format string.
+ * @param ... Argument specified in the @a pcszMsgFmt
+ * @return COM status code appropriate for @a vrc.
+ *
+ * @sa VirtualBoxBase::setError(HRESULT, const char *, ...)
+ */
+HRESULT VirtualBoxBase::setErrorVrc(int vrc, const char *pcszMsgFmt, ...)
+{
+ va_list va;
+ va_start(va, pcszMsgFmt);
+ HRESULT hrc = setErrorVrcV(vrc, pcszMsgFmt, va);
+ va_end(va);
+ return hrc;
+}
+
+/**
+ * Sets error info with both a COM status and an VBox status code.
+ *
+ * The VBox status code is made available to the API user via
+ * IVirtualBoxErrorInfo::resultDetail attribute.
+ *
+ * @param hrc The COM status code to return.
+ * @param vrc The VBox status code.
+ * @return Most likely @a hrc, see setErrorInternal.
+ *
+ * @sa VirtualBoxBase::setError(HRESULT)
+ */
+HRESULT VirtualBoxBase::setErrorBoth(HRESULT hrc, int vrc)
+{
+ return setErrorInternalF(hrc,
+ this->getClassIID(),
+ this->getComponentName(),
+ false /* aWarning */,
+ true /* aLogIt */,
+ vrc /* aResultDetail */,
+ Utf8StrFmt("%Rrc", vrc).c_str());
+}
+
+/**
+ * Sets error info with a message and both a COM status and an VBox status code.
+ *
+ * The VBox status code is made available to the API user via
+ * IVirtualBoxErrorInfo::resultDetail attribute.
+ *
+ * @param hrc The COM status code to return.
+ * @param vrc The VBox status code.
+ * @param pcszMsgFmt Error message format string.
+ * @param ... Argument specified in the @a pcszMsgFmt
+ * @return Most likely @a hrc, see setErrorInternal.
+ *
+ * @sa VirtualBoxBase::setError(HRESULT, const char *, ...)
+ */
+HRESULT VirtualBoxBase::setErrorBoth(HRESULT hrc, int vrc, const char *pcszMsgFmt, ...)
+{
+ va_list va;
+ va_start(va, pcszMsgFmt);
+ hrc = setErrorInternalV(hrc,
+ this->getClassIID(),
+ this->getComponentName(),
+ pcszMsgFmt, va,
+ false /* aWarning */,
+ true /* aLogIt */,
+ vrc /* aResultDetail */);
+ va_end(va);
+ return hrc;
+}
+
+/**
+ * Like setError(), but sets the "warning" bit in the call to setErrorInternal().
+ * @param aResultCode
+ * @param pcsz
+ * @return
+ */
+HRESULT VirtualBoxBase::setWarning(HRESULT aResultCode, const char *pcsz, ...)
+{
+ va_list args;
+ va_start(args, pcsz);
+ HRESULT hrc = setErrorInternalV(aResultCode,
+ this->getClassIID(),
+ this->getComponentName(),
+ pcsz, args,
+ true /* aWarning */,
+ true /* aLogIt */);
+ va_end(args);
+ return hrc;
+}
+
+/**
+ * Like setError(), but disables the "log" flag in the call to setErrorInternal().
+ * @param aResultCode
+ * @param pcsz
+ * @return
+ */
+HRESULT VirtualBoxBase::setErrorNoLog(HRESULT aResultCode, const char *pcsz, ...)
+{
+ va_list args;
+ va_start(args, pcsz);
+ HRESULT hrc = setErrorInternalV(aResultCode,
+ this->getClassIID(),
+ this->getComponentName(),
+ pcsz, args,
+ false /* aWarning */,
+ false /* aLogIt */);
+ va_end(args);
+ return hrc;
+}
+
+/**
+ * Clear the current error information.
+ */
+/*static*/
+void VirtualBoxBase::clearError(void)
+{
+#if !defined(VBOX_WITH_XPCOM)
+ ::SetErrorInfo(0, NULL);
+#else
+ HRESULT hrc = S_OK;
+ nsCOMPtr <nsIExceptionService> es;
+ es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &hrc);
+ if (NS_SUCCEEDED(hrc))
+ {
+ nsCOMPtr <nsIExceptionManager> em;
+ hrc = es->GetCurrentExceptionManager(getter_AddRefs(em));
+ if (SUCCEEDED(hrc))
+ em->SetCurrentException(NULL);
+ }
+#endif
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MultiResult methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+RTTLS MultiResult::sCounter = NIL_RTTLS;
+
+/*static*/
+void MultiResult::incCounter()
+{
+ if (sCounter == NIL_RTTLS)
+ {
+ sCounter = RTTlsAlloc();
+ AssertReturnVoid(sCounter != NIL_RTTLS);
+ }
+
+ uintptr_t counter = (uintptr_t)RTTlsGet(sCounter);
+ ++counter;
+ RTTlsSet(sCounter, (void*)counter);
+}
+
+/*static*/
+void MultiResult::decCounter()
+{
+ uintptr_t counter = (uintptr_t)RTTlsGet(sCounter);
+ AssertReturnVoid(counter != 0);
+ --counter;
+ RTTlsSet(sCounter, (void*)counter);
+}
+
+/*static*/
+bool MultiResult::isMultiEnabled()
+{
+ if (sCounter == NIL_RTTLS)
+ return false;
+
+ return ((uintptr_t)RTTlsGet(MultiResult::sCounter)) > 0;
+}
+
diff --git a/src/VBox/Main/src-all/VirtualBoxErrorInfoImpl.cpp b/src/VBox/Main/src-all/VirtualBoxErrorInfoImpl.cpp
new file mode 100644
index 00000000..eb7fbc92
--- /dev/null
+++ b/src/VBox/Main/src-all/VirtualBoxErrorInfoImpl.cpp
@@ -0,0 +1,317 @@
+/* $Id: VirtualBoxErrorInfoImpl.cpp $ */
+/** @file
+ * VirtualBoxErrorInfo COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN
+#include "VirtualBoxErrorInfoImpl.h"
+
+#include <VBox/com/ErrorInfo.h>
+
+// public initializer/uninitializer for internal purposes only
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT VirtualBoxErrorInfo::init(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const Utf8Str &strText,
+ IVirtualBoxErrorInfo *aNext)
+{
+ m_resultCode = aResultCode;
+ m_resultDetail = 0; /* Not being used. */
+ m_IID = aIID;
+ m_strComponent = pcszComponent;
+ m_strText = strText;
+ mNext = aNext;
+
+ return S_OK;
+}
+
+HRESULT VirtualBoxErrorInfo::initEx(HRESULT aResultCode,
+ LONG aResultDetail,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const Utf8Str &strText,
+ IVirtualBoxErrorInfo *aNext)
+{
+ HRESULT hrc = init(aResultCode, aIID, pcszComponent, strText, aNext);
+ m_resultDetail = aResultDetail;
+
+ return hrc;
+}
+
+HRESULT VirtualBoxErrorInfo::init(const com::ErrorInfo &info,
+ IVirtualBoxErrorInfo *aNext)
+{
+ m_resultCode = info.getResultCode();
+ m_resultDetail = info.getResultDetail();
+ m_IID = info.getInterfaceID();
+ m_strComponent = info.getComponent();
+ m_strText = info.getText();
+
+ /* Recursively create VirtualBoxErrorInfo instances for the next objects. */
+ const com::ErrorInfo *pInfo = info.getNext();
+ if (pInfo)
+ {
+ ComObjPtr<VirtualBoxErrorInfo> nextEI;
+ HRESULT hrc = nextEI.createObject();
+ if (FAILED(hrc)) return hrc;
+ hrc = nextEI->init(*pInfo, aNext);
+ if (FAILED(hrc)) return hrc;
+ mNext = nextEI;
+ }
+ else
+ mNext = aNext;
+
+ return S_OK;
+}
+
+// IVirtualBoxErrorInfo properties
+////////////////////////////////////////////////////////////////////////////////
+
+STDMETHODIMP VirtualBoxErrorInfo::COMGETTER(ResultCode)(LONG *aResultCode)
+{
+ CheckComArgOutPointerValid(aResultCode);
+
+ *aResultCode = (LONG)m_resultCode;
+ return S_OK;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::COMGETTER(ResultDetail)(LONG *aResultDetail)
+{
+ CheckComArgOutPointerValid(aResultDetail);
+
+ *aResultDetail = m_resultDetail;
+ return S_OK;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::COMGETTER(InterfaceID)(BSTR *aIID)
+{
+ CheckComArgOutPointerValid(aIID);
+
+ m_IID.toUtf16().cloneTo(aIID);
+ return S_OK;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::COMGETTER(Component)(BSTR *aComponent)
+{
+ CheckComArgOutPointerValid(aComponent);
+
+ m_strComponent.cloneTo(aComponent);
+ return S_OK;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::COMGETTER(Text)(BSTR *aText)
+{
+ CheckComArgOutPointerValid(aText);
+
+ m_strText.cloneTo(aText);
+ return S_OK;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::COMGETTER(Next)(IVirtualBoxErrorInfo **aNext)
+{
+ CheckComArgOutPointerValid(aNext);
+
+ /* this will set aNext to NULL if mNext is null */
+ return mNext.queryInterfaceTo(aNext);
+}
+
+#if !defined(VBOX_WITH_XPCOM)
+
+/**
+ * Initializes itself by fetching error information from the given error info
+ * object.
+ */
+HRESULT VirtualBoxErrorInfo::init(IErrorInfo *aInfo)
+{
+ AssertReturn(aInfo, E_FAIL);
+
+ /* We don't return a failure if talking to IErrorInfo fails below to
+ * protect ourselves from bad IErrorInfo implementations (the
+ * corresponding fields will simply remain null in this case). */
+
+ m_resultCode = S_OK;
+ m_resultDetail = 0;
+ HRESULT hrc = aInfo->GetGUID(m_IID.asOutParam());
+ AssertComRC(hrc);
+ Bstr bstrComponent;
+ hrc = aInfo->GetSource(bstrComponent.asOutParam());
+ AssertComRC(hrc);
+ m_strComponent = bstrComponent;
+ Bstr bstrText;
+ hrc = aInfo->GetDescription(bstrText.asOutParam());
+ AssertComRC(hrc);
+ m_strText = bstrText;
+
+ return S_OK;
+}
+
+// IErrorInfo methods
+////////////////////////////////////////////////////////////////////////////////
+
+STDMETHODIMP VirtualBoxErrorInfo::GetDescription(BSTR *description)
+{
+ return COMGETTER(Text)(description);
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::GetGUID(GUID *guid)
+{
+ Bstr iid;
+ HRESULT hrc = COMGETTER(InterfaceID)(iid.asOutParam());
+ if (SUCCEEDED(hrc))
+ *guid = Guid(iid).ref();
+ return hrc;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::GetHelpContext(DWORD *pdwHelpContext)
+{
+ RT_NOREF(pdwHelpContext);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::GetHelpFile(BSTR *pBstrHelpFile)
+{
+ RT_NOREF(pBstrHelpFile);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::GetSource(BSTR *pBstrSource)
+{
+ return COMGETTER(Component)(pBstrSource);
+}
+
+#else // defined(VBOX_WITH_XPCOM)
+
+/**
+ * Initializes itself by fetching error information from the given error info
+ * object.
+ */
+HRESULT VirtualBoxErrorInfo::init(nsIException *aInfo)
+{
+ AssertReturn(aInfo, E_FAIL);
+
+ /* We don't return a failure if talking to nsIException fails below to
+ * protect ourselves from bad nsIException implementations (the
+ * corresponding fields will simply remain null in this case). */
+
+ HRESULT hrc = aInfo->GetResult(&m_resultCode);
+ AssertComRC(hrc);
+ m_resultDetail = 0; /* Not being used. */
+
+ char *pszMsg; /* No Utf8Str.asOutParam, different allocator! */
+ hrc = aInfo->GetMessage(&pszMsg);
+ AssertComRC(hrc);
+ if (NS_SUCCEEDED(hrc))
+ {
+ m_strText = pszMsg;
+ nsMemory::Free(pszMsg);
+ }
+ else
+ m_strText.setNull();
+
+ return S_OK;
+}
+
+// nsIException methods
+////////////////////////////////////////////////////////////////////////////////
+
+/* readonly attribute string message; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetMessage(char **aMessage)
+{
+ CheckComArgOutPointerValid(aMessage);
+
+ m_strText.cloneTo(aMessage);
+ return S_OK;
+}
+
+/* readonly attribute nsresult result; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetResult(nsresult *aResult)
+{
+ AssertReturn(aResult, NS_ERROR_INVALID_POINTER);
+
+ PRInt32 lrc;
+ nsresult hrc = COMGETTER(ResultCode)(&lrc);
+ if (SUCCEEDED(hrc))
+ *aResult = (nsresult)lrc;
+ return hrc;
+}
+
+/* readonly attribute string name; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetName(char ** /* aName */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute string filename; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetFilename(char ** /* aFilename */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute PRUint32 lineNumber; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetLineNumber(PRUint32 * /* aLineNumber */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute PRUint32 columnNumber; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetColumnNumber(PRUint32 * /*aColumnNumber */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute nsIStackFrame location; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetLocation(nsIStackFrame ** /* aLocation */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute nsIException inner; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetInner(nsIException **aInner)
+{
+ ComPtr<IVirtualBoxErrorInfo> info;
+ nsresult rv = COMGETTER(Next)(info.asOutParam());
+ if (FAILED(rv)) return rv;
+ return info.queryInterfaceTo(aInner);
+}
+
+/* readonly attribute nsISupports data; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetData(nsISupports ** /* aData */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* string toString(); */
+NS_IMETHODIMP VirtualBoxErrorInfo::ToString(char ** /* retval */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(VirtualBoxErrorInfo,
+ nsIException, IVirtualBoxErrorInfo)
+
+#endif // defined(VBOX_WITH_XPCOM)
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/VirtualBoxTranslator.cpp b/src/VBox/Main/src-all/VirtualBoxTranslator.cpp
new file mode 100644
index 00000000..34ce27d2
--- /dev/null
+++ b/src/VBox/Main/src-all/VirtualBoxTranslator.cpp
@@ -0,0 +1,593 @@
+/* $Id: VirtualBoxTranslator.cpp $ */
+/** @file
+ * VirtualBox Translator class.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_VIRTUALBOXCLIENT /** @todo add separate logging group! */
+#include "LoggingNew.h"
+
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/locale.h>
+#include <iprt/once.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/strcache.h>
+
+#ifdef RT_OS_DARWIN
+#include <CoreFoundation/CFLocale.h>
+#include <CoreFoundation/CFString.h>
+#endif
+
+#include "Global.h"
+#include "VirtualBoxBase.h"
+#include "QMTranslator.h"
+#include "VirtualBoxTranslator.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define TRANSLATOR_CACHE_SIZE 32
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Init once for the critical section. */
+static RTONCE g_Once = RTONCE_INITIALIZER;
+RTCRITSECTRW VirtualBoxTranslator::s_instanceRwLock;
+VirtualBoxTranslator *VirtualBoxTranslator::s_pInstance = NULL;
+/** TLS index that points to the translated text. */
+static RTTLS g_idxTlsTr = NIL_RTTLS;
+/** TLS index that points to the original text. */
+static RTTLS g_idxTlsSrc = NIL_RTTLS;
+
+
+/**
+ * @callback_method_impl{FNRTONCE}
+ */
+static DECLCALLBACK(int32_t) initLock(void *pvUser)
+{
+ RT_NOREF(pvUser);
+ return VirtualBoxTranslator::initCritSect();
+}
+
+
+/**
+ * Obtains the user language code in ll_CC form depending on platform
+ *
+ * @returns VBox status code
+ * @param pszName The buffer for storing user language code
+ * @param cbName Size of the pszName buffer
+ */
+static int vboxGetDefaultUserLanguage(char *pszName, size_t cbName)
+{
+ AssertReturn(pszName, VERR_INVALID_PARAMETER);
+ AssertReturn(cbName >= 6, VERR_INVALID_PARAMETER); /* 5 chars for language + null termination */
+
+#ifdef RT_OS_WINDOWS
+ if ( GetLocaleInfoA(GetUserDefaultLCID(), LOCALE_SISO639LANGNAME, pszName, (int)cbName) == 3
+ && GetLocaleInfoA(GetUserDefaultLCID(), LOCALE_SISO3166CTRYNAME, &pszName[3], (int)cbName - 4) == 3)
+ {
+ pszName[2] = '_';
+ Assert(RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(pszName));
+ return VINF_SUCCESS;
+ }
+#elif RT_OS_DARWIN
+ CFLocaleRef locale = CFLocaleCopyCurrent();
+ CFTypeRef localeId = CFLocaleGetValue (locale, kCFLocaleIdentifier);
+ char szLocale[256] = { 0 };
+ if (CFGetTypeID(localeId) == CFStringGetTypeID())
+ CFStringGetCString((CFStringRef)localeId, szLocale, sizeof(szLocale), kCFStringEncodingUTF8);
+ /* Some cleanup */
+ CFRelease(locale);
+ if (szLocale[0] == '\0')
+ {
+ pszName[0] = 'C';
+ pszName[1] = 0;
+ return VINF_SUCCESS;
+ }
+ else
+ return RTStrCopy(pszName, cbName, szLocale);
+
+#elif defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) || defined(RT_OS_SOLARIS)
+ const char *pszValue = RTEnvGet("LC_ALL");
+ if (pszValue == 0)
+ pszValue = RTEnvGet("LC_MESSAGES");
+ if (pszValue == 0)
+ pszValue = RTEnvGet("LANG");
+ if (pszValue != 0)
+ {
+ /* ignore codepage part, i.e. ignore ".UTF-8" in "ru_RU.UTF-8" */
+ const char *pszDot = strchr(pszValue, '.');
+ size_t cbValue = strlen(pszValue);
+ if (pszDot != NULL)
+ cbValue = RT_MIN(cbValue, (size_t)(pszDot - pszValue));
+
+ if ( ( cbValue == 2
+ && RT_C_IS_LOWER(pszValue[0])
+ && RT_C_IS_LOWER(pszValue[1]))
+ || ( cbValue == 5
+ && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(pszValue)))
+ return RTStrCopyEx(pszName, cbName, pszValue, cbValue);
+ }
+#endif
+ return RTLocaleQueryNormalizedBaseLocaleName(pszName, cbName);
+}
+
+VirtualBoxTranslator::VirtualBoxTranslator()
+ : util::RWLockHandle(util::LOCKCLASS_TRANSLATOR)
+ , m_cInstanceRefs(0)
+ , m_pDefaultComponent(NULL)
+ , m_strLanguage("C")
+ , m_hStrCache(NIL_RTSTRCACHE)
+{
+ g_idxTlsTr = RTTlsAlloc();
+ g_idxTlsSrc = RTTlsAlloc();
+ int vrc = RTStrCacheCreate(&m_hStrCache, "API Translation");
+ m_rcCache = vrc;
+ if (RT_FAILURE(vrc))
+ m_hStrCache = NIL_RTSTRCACHE; /* (loadLanguage will fail) */
+ LogFlowFunc(("m_rcCache=%Rrc g_idxTlsTr=%#x g_idxTlsSrc=%#x\n", m_rcCache, g_idxTlsTr, g_idxTlsSrc));
+}
+
+
+VirtualBoxTranslator::~VirtualBoxTranslator()
+{
+ LogFlowFunc(("enter\n"));
+
+ /* Write-lock the object as we could be racing language change
+ notifications processing during XPCOM shutdown. (risky?) */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ RTTlsFree(g_idxTlsTr);
+ g_idxTlsTr = NIL_RTTLS;
+ RTTlsFree(g_idxTlsSrc);
+ g_idxTlsSrc = NIL_RTTLS;
+
+ m_pDefaultComponent = NULL;
+
+ for (TranslatorList::iterator it = m_lTranslators.begin();
+ it != m_lTranslators.end();
+ ++it)
+ {
+ if (it->pTranslator != NULL)
+ delete it->pTranslator;
+ it->pTranslator = NULL;
+ }
+ if (m_hStrCache != NIL_RTSTRCACHE)
+ {
+ RTStrCacheDestroy(m_hStrCache);
+ m_hStrCache = NIL_RTSTRCACHE;
+ m_rcCache = VERR_WRONG_ORDER;
+ }
+ LogFlowFunc(("returns\n"));
+}
+
+
+/**
+ * Get or create a translator instance (singelton), referenced.
+ *
+ * The main reference is held by the main VBox singelton objects (VirtualBox,
+ * VirtualBoxClient) tying it's lifetime to theirs.
+ */
+/* static */
+VirtualBoxTranslator *VirtualBoxTranslator::instance()
+{
+ int vrc = RTOnce(&g_Once, initLock, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ RTCritSectRwEnterShared(&s_instanceRwLock);
+ VirtualBoxTranslator *pInstance = s_pInstance;
+ if (RT_LIKELY(pInstance != NULL))
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&pInstance->m_cInstanceRefs);
+ Assert(cRefs > 1); Assert(cRefs < _8K); RT_NOREF(cRefs);
+ RTCritSectRwLeaveShared(&s_instanceRwLock);
+ return pInstance;
+ }
+
+ /* Maybe create the instance: */
+ RTCritSectRwLeaveShared(&s_instanceRwLock);
+ RTCritSectRwEnterExcl(&s_instanceRwLock);
+ pInstance = s_pInstance;
+ if (pInstance == NULL)
+ s_pInstance = pInstance = new VirtualBoxTranslator();
+ ASMAtomicIncU32(&pInstance->m_cInstanceRefs);
+ RTCritSectRwLeaveExcl(&s_instanceRwLock);
+ return pInstance;
+ }
+ return NULL;
+}
+
+
+/* static */
+VirtualBoxTranslator *VirtualBoxTranslator::tryInstance() RT_NOEXCEPT
+{
+ int vrc = RTOnce(&g_Once, initLock, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ RTCritSectRwEnterShared(&s_instanceRwLock);
+ VirtualBoxTranslator *pInstance = s_pInstance;
+ if (RT_LIKELY(pInstance != NULL))
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&pInstance->m_cInstanceRefs);
+ Assert(cRefs > 1); Assert(cRefs < _8K); RT_NOREF(cRefs);
+ }
+ RTCritSectRwLeaveShared(&s_instanceRwLock);
+ return pInstance;
+ }
+ return NULL;
+}
+
+
+/**
+ * Release translator reference previous obtained via instance() or
+ * tryinstance().
+ */
+void VirtualBoxTranslator::release()
+{
+ RTCritSectRwEnterShared(&s_instanceRwLock);
+ uint32_t cRefs = ASMAtomicDecU32(&m_cInstanceRefs);
+ Assert(cRefs < _8K);
+ if (RT_LIKELY(cRefs > 0))
+ RTCritSectRwLeaveShared(&s_instanceRwLock);
+ else
+ {
+ /* Looks like we've got the last reference. Must switch to exclusive
+ mode for safe cleanup. */
+ ASMAtomicIncU32(&m_cInstanceRefs);
+ RTCritSectRwLeaveShared(&s_instanceRwLock);
+ RTCritSectRwEnterExcl(&s_instanceRwLock);
+ cRefs = ASMAtomicDecU32(&m_cInstanceRefs);
+ Assert(cRefs < _8K);
+ if (cRefs == 0)
+ {
+ s_pInstance = NULL;
+ delete this;
+ }
+ RTCritSectRwLeaveExcl(&s_instanceRwLock);
+ }
+}
+
+
+HRESULT VirtualBoxTranslator::loadLanguage(ComPtr<IVirtualBox> aVirtualBox)
+{
+ AssertReturn(aVirtualBox, E_INVALIDARG);
+
+ ComPtr<ISystemProperties> pSystemProperties;
+ HRESULT hrc = aVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ com::Bstr bstrLocale;
+ hrc = pSystemProperties->COMGETTER(LanguageId)(bstrLocale.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = i_loadLanguage(com::Utf8Str(bstrLocale).c_str());
+ if (RT_FAILURE(vrc))
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+ }
+ }
+ return hrc;
+}
+
+
+com::Utf8Str VirtualBoxTranslator::language()
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return m_strLanguage;
+}
+
+
+int VirtualBoxTranslator::i_loadLanguage(const char *pszLang)
+{
+ LogFlowFunc(("pszLang=%s\n", pszLang));
+ int vrc = VINF_SUCCESS;
+ char szLocale[256];
+ if (pszLang == NULL || *pszLang == '\0')
+ {
+ vrc = vboxGetDefaultUserLanguage(szLocale, sizeof(szLocale));
+ if (RT_SUCCESS(vrc))
+ pszLang = szLocale;
+ }
+ else
+ {
+ /* check the pszLang looks like language code, i.e. {ll} or {ll}_{CC} */
+ size_t cbLang = strlen(pszLang);
+ if ( !(cbLang == 1 && pszLang[0] == 'C')
+ && !(cbLang == 2 && RT_C_IS_LOWER(pszLang[0]) && RT_C_IS_LOWER(pszLang[1]))
+ && !(cbLang == 5 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(pszLang)))
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m_strLanguage = pszLang;
+
+ for (TranslatorList::iterator it = m_lTranslators.begin();
+ it != m_lTranslators.end();
+ ++it)
+ {
+ /* ignore errors from particular translator allowing the use of others */
+ i_loadLanguageForComponent(&(*it), pszLang);
+ }
+ }
+ return vrc;
+}
+
+
+int VirtualBoxTranslator::i_loadLanguageForComponent(TranslatorComponent *aComponent, const char *aLang)
+{
+ AssertReturn(aComponent, VERR_INVALID_PARAMETER);
+ LogFlow(("aComponent=%s aLang=%s\n", aComponent->strPath.c_str(), aLang));
+
+ int vrc;
+ if (strcmp(aLang, "C") != 0)
+ {
+ /* Construct the base filename for the translations: */
+ char szNlsPath[RTPATH_MAX];
+ /* Try load language file on form 'VirtualBoxAPI_ll_CC.qm' if it exists
+ where 'll_CC' could for example be 'en_US' or 'de_CH': */
+ ssize_t cchOkay = RTStrPrintf2(szNlsPath, sizeof(szNlsPath), "%s_%s.qm",
+ aComponent->strPath.c_str(), aLang);
+ if (cchOkay > 0)
+ vrc = i_setLanguageFile(aComponent, szNlsPath);
+ else
+ vrc = VERR_FILENAME_TOO_LONG;
+ if (RT_FAILURE(vrc))
+ {
+ /* No luck, drop the country part, i.e. 'VirtualBoxAPI_de.qm' or 'VirtualBoxAPI_en.qm': */
+ const char *pszDash = strchr(aLang, '_');
+ if (pszDash && pszDash != aLang)
+ {
+ cchOkay = RTStrPrintf2(szNlsPath, sizeof(szNlsPath), "%s_%.*s.qm",
+ aComponent->strPath.c_str(), pszDash - aLang, aLang);
+ if (cchOkay > 0)
+ vrc = i_setLanguageFile(aComponent, szNlsPath);
+ }
+ }
+ }
+ else
+ {
+ /* No translator needed for 'C' */
+ delete aComponent->pTranslator;
+ aComponent->pTranslator = NULL;
+ vrc = VINF_SUCCESS;
+ }
+ return vrc;
+}
+
+
+int VirtualBoxTranslator::i_setLanguageFile(TranslatorComponent *aComponent, const char *aFileName)
+{
+ AssertReturn(aComponent, VERR_INVALID_PARAMETER);
+
+ int vrc = m_rcCache;
+ if (m_hStrCache != NIL_RTSTRCACHE)
+ {
+ QMTranslator *pNewTranslator;
+ try { pNewTranslator = new QMTranslator(); }
+ catch (std::bad_alloc &) { pNewTranslator = NULL; }
+ if (pNewTranslator)
+ {
+ vrc = pNewTranslator->load(aFileName, m_hStrCache);
+ if (RT_SUCCESS(vrc))
+ {
+ if (aComponent->pTranslator)
+ delete aComponent->pTranslator;
+ aComponent->pTranslator = pNewTranslator;
+ }
+ else
+ delete pNewTranslator;
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ Assert(RT_FAILURE_NP(vrc));
+ return vrc;
+}
+
+
+int VirtualBoxTranslator::registerTranslation(const char *aTranslationPath,
+ bool aDefault,
+ PTRCOMPONENT *aComponent)
+{
+ VirtualBoxTranslator *pCurrInstance = VirtualBoxTranslator::tryInstance();
+ int vrc = VERR_GENERAL_FAILURE;
+ if (pCurrInstance != NULL)
+ {
+ vrc = pCurrInstance->i_registerTranslation(aTranslationPath, aDefault, aComponent);
+ pCurrInstance->release();
+ }
+ return vrc;
+}
+
+
+int VirtualBoxTranslator::i_registerTranslation(const char *aTranslationPath,
+ bool aDefault,
+ PTRCOMPONENT *aComponent)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ TranslatorComponent *pComponent;
+ for (TranslatorList::iterator it = m_lTranslators.begin();
+ it != m_lTranslators.end();
+ ++it)
+ {
+ if (it->strPath == aTranslationPath)
+ {
+ pComponent = &(*it);
+ if (aDefault)
+ m_pDefaultComponent = pComponent;
+ *aComponent = (PTRCOMPONENT)pComponent;
+ return VINF_SUCCESS;
+ }
+ }
+
+ try
+ {
+ m_lTranslators.push_back(TranslatorComponent());
+ pComponent = &m_lTranslators.back();
+ }
+ catch(std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+
+ pComponent->strPath = aTranslationPath;
+ if (aDefault)
+ m_pDefaultComponent = pComponent;
+ *aComponent = (PTRCOMPONENT)pComponent;
+ /* ignore the error during loading because path
+ * could contain no translation for current language */
+ i_loadLanguageForComponent(pComponent, m_strLanguage.c_str());
+ return VINF_SUCCESS;
+}
+
+
+int VirtualBoxTranslator::unregisterTranslation(PTRCOMPONENT aComponent)
+{
+ int vrc;
+ if (aComponent != NULL)
+ {
+ VirtualBoxTranslator *pCurrInstance = VirtualBoxTranslator::tryInstance();
+ if (pCurrInstance != NULL)
+ {
+ vrc = pCurrInstance->i_unregisterTranslation(aComponent);
+ pCurrInstance->release();
+ }
+ else
+ vrc = VERR_GENERAL_FAILURE;
+ }
+ else
+ vrc = VWRN_NOT_FOUND;
+ return vrc;
+}
+
+
+int VirtualBoxTranslator::i_unregisterTranslation(PTRCOMPONENT aComponent)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aComponent == m_pDefaultComponent)
+ m_pDefaultComponent = NULL;
+
+ for (TranslatorList::iterator it = m_lTranslators.begin();
+ it != m_lTranslators.end();
+ ++it)
+ {
+ if (&(*it) == aComponent)
+ {
+ delete aComponent->pTranslator;
+ m_lTranslators.erase(it);
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+
+const char *VirtualBoxTranslator::translate(PTRCOMPONENT aComponent,
+ const char *aContext,
+ const char *aSourceText,
+ const char *aComment,
+ const size_t aNum) RT_NOEXCEPT
+{
+ VirtualBoxTranslator *pCurrInstance = VirtualBoxTranslator::tryInstance();
+ const char *pszTranslation = aSourceText;
+ if (pCurrInstance != NULL)
+ {
+ pszTranslation = pCurrInstance->i_translate(aComponent, aContext, aSourceText, aComment, aNum);
+ pCurrInstance->release();
+ }
+ return pszTranslation;
+}
+
+
+const char *VirtualBoxTranslator::i_translate(PTRCOMPONENT aComponent,
+ const char *aContext,
+ const char *aSourceText,
+ const char *aComment,
+ const size_t aNum) RT_NOEXCEPT
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aComponent == NULL)
+ aComponent = m_pDefaultComponent;
+
+ if ( aComponent == NULL
+ || aComponent->pTranslator == NULL)
+ return aSourceText;
+
+ const char *pszSafeSource = NULL;
+ const char *pszTranslation = aComponent->pTranslator->translate(aContext, aSourceText, &pszSafeSource, aComment, aNum);
+ if (pszSafeSource && g_idxTlsSrc != NIL_RTTLS && g_idxTlsTr != NIL_RTTLS)
+ {
+ RTTlsSet(g_idxTlsTr, (void *)pszTranslation);
+ RTTlsSet(g_idxTlsSrc, (void *)pszSafeSource);
+ }
+
+ return pszTranslation;
+}
+
+
+const char *VirtualBoxTranslator::trSource(const char *aTranslation) RT_NOEXCEPT
+{
+ const char *pszSource = aTranslation;
+ VirtualBoxTranslator *pCurInstance = VirtualBoxTranslator::tryInstance(); /* paranoia */
+ if (pCurInstance != NULL)
+ {
+ if (g_idxTlsSrc != NIL_RTTLS && g_idxTlsTr != NIL_RTTLS)
+ {
+ const char * const pszTranslationTls = (const char *)RTTlsGet(g_idxTlsTr);
+ const char * const pszSourceTls = (const char *)RTTlsGet(g_idxTlsSrc);
+ if ( pszSourceTls != NULL
+ && pszTranslationTls != NULL
+ && ( pszTranslationTls == aTranslation
+ || strcmp(pszTranslationTls, aTranslation) == 0))
+ pszSource = pszSourceTls;
+ }
+ pCurInstance->release();
+ }
+ return pszSource;
+}
+
+
+int32_t VirtualBoxTranslator::initCritSect()
+{
+ return RTCritSectRwInit(&s_instanceRwLock);
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/win/Makefile.kup b/src/VBox/Main/src-all/win/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-all/win/Makefile.kup
diff --git a/src/VBox/Main/src-all/win/VBoxAPIWrap-precomp_vcc.h b/src/VBox/Main/src-all/win/VBoxAPIWrap-precomp_vcc.h
new file mode 100644
index 00000000..5c5c2f62
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VBoxAPIWrap-precomp_vcc.h
@@ -0,0 +1,50 @@
+/* $Id: VBoxAPIWrap-precomp_vcc.h $ */
+/** @file
+ * VirtualBox COM - Visual C++ precompiled header for the API wrappers.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#include <iprt/cdefs.h>
+#include <iprt/win/windows.h>
+#include <VBox/cdefs.h>
+#include <iprt/types.h>
+#include <iprt/cpp/list.h>
+#include <iprt/cpp/meta.h>
+#include <iprt/cpp/ministring.h>
+#include <VBox/com/microatl.h>
+#include <VBox/com/com.h>
+#include <VBox/com/array.h>
+#include <VBox/com/Guid.h>
+#include <VBox/com/string.h>
+
+#include "VBox/com/VirtualBox.h"
+
+#include "VirtualBoxBase.h"
+#include "Wrapper.h"
+
+#if defined(Log) || defined(LogIsEnabled)
+# error "Log() from iprt/log.h cannot be defined in the precompiled header!"
+#endif
+
diff --git a/src/VBox/Main/src-all/win/VBoxProxyStub-x86.rc b/src/VBox/Main/src-all/win/VBoxProxyStub-x86.rc
new file mode 100644
index 00000000..3aceef6e
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VBoxProxyStub-x86.rc
@@ -0,0 +1,34 @@
+/* $Id: VBoxProxyStub-x86.rc $ */
+/** @file
+ * VBoxProxyStub - Resource file containing version info, icon and typelib.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define IN_FILE_DESCRIPTION "VirtualBox COM Proxy Stub and Typelib"
+#define IN_FILE_BASENAME "VBoxProxyStub-x86"
+
+#include "../../../Artwork/win/TemplateDll.rc"
+
+1 TYPELIB "VirtualBox-x86.tlb"
+
diff --git a/src/VBox/Main/src-all/win/VBoxProxyStub.c b/src/VBox/Main/src-all/win/VBoxProxyStub.c
new file mode 100644
index 00000000..44b9cd84
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VBoxProxyStub.c
@@ -0,0 +1,2599 @@
+/* $Id: VBoxProxyStub.c $ */
+/** @file
+ * VBoxProxyStub - Proxy Stub and Typelib, COM DLL exports and DLL init/term.
+ *
+ * @remarks This is a C file and not C++ because rpcproxy.h isn't C++ clean,
+ * at least not in SDK v7.1.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN
+#define PROXY_DELEGATION /* see generated dlldata.c */
+#include <iprt/nt/nt-and-windows.h>
+#include <rpcproxy.h>
+#include <iprt/win/shlwapi.h>
+#include <stdio.h>
+
+#include "VirtualBox.h"
+#include <VBox/cdefs.h> /* for VBOX_STRICT */
+#include <VBox/log.h>
+#include <iprt/alloca.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/initterm.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/utf16.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifdef DEBUG_bird
+# define VBSP_LOG_ENABLED
+#endif
+
+#ifdef VBSP_LOG_ENABLED
+# define VBSP_LOG_VALUE_CHANGE(a) RTAssertMsg2 a
+#else
+# define VBSP_LOG_VALUE_CHANGE(a) do { } while (0)
+#endif
+
+#ifdef VBSP_LOG_ENABLED
+# define VBSP_LOG_SET_VALUE(a) RTAssertMsg2 a
+#else
+# define VBSP_LOG_SET_VALUE(a) do { } while (0)
+#endif
+
+#ifdef VBSP_LOG_ENABLED
+# define VBSP_LOG_NEW_KEY(a) RTAssertMsg2 a
+#else
+# define VBSP_LOG_NEW_KEY(a) do { } while (0)
+#endif
+
+#ifdef VBSP_LOG_ENABLED
+# define VBSP_LOG_DEL_KEY(a) RTAssertMsg2 a
+#else
+# define VBSP_LOG_DEL_KEY(a) do { } while (0)
+#endif
+
+/**
+ * Selects the proxy stub DLL based on 32-on-64-bit and host OS version.
+ *
+ * The legacy DLL covers 64-bit pre Windows 7 versions of Windows. W2K3-amd64
+ * has trouble parsing the result when MIDL /target NT51 or higher. Vista and
+ * windows server 2008 seems to have trouble with newer IDL compilers.
+ */
+#if ARCH_BITS == 64 || defined(VBOX_IN_32_ON_64_MAIN_API)
+# define VBPS_PROXY_STUB_FILE(a_fIs32On64) ( (a_fIs32On64) ? "x86\\VBoxProxyStub-x86.dll" : VBPS_PROXY_STUB_FILE_SUB() )
+#else
+# define VBPS_PROXY_STUB_FILE(a_fIs32On64) VBPS_PROXY_STUB_FILE_SUB()
+#endif
+#define VBPS_PROXY_STUB_FILE_SUB() \
+ ( RT_MAKE_U64(((PKUSER_SHARED_DATA)MM_SHARED_USER_DATA_VA)->NtMinorVersion, \
+ ((PKUSER_SHARED_DATA)MM_SHARED_USER_DATA_VA)->NtMajorVersion) >= RT_MAKE_U64(1/*Lo*/,6/*Hi*/) \
+ ? "VBoxProxyStub.dll" : "VBoxProxyStubLegacy.dll" )
+
+/** For use with AssertLogRel except a_Expr1 from assertions but not LogRel. */
+#ifdef RT_STRICT
+# define VBPS_LOGREL_NO_ASSERT(a_Expr) (a_Expr)
+#else
+# define VBPS_LOGREL_NO_ASSERT(a_Expr) false
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** For NdrXxx. */
+CStdPSFactoryBuffer g_ProxyStubFactory = /* see generated dlldata.c */
+{
+ NULL,
+ 0,
+ NULL,
+ 0
+};
+/** Reference to VirtualBox_p.c structure. */
+EXTERN_PROXY_FILE(VirtualBox) /* see generated dlldata.c */
+/** For NdrXxx and for returning. */
+static const ProxyFileInfo *g_apProxyFiles[] =
+{
+ REFERENCE_PROXY_FILE(VirtualBox),
+ NULL /* terminator */
+};
+/** The class ID for this proxy stub factory (see Makefile). */
+static const CLSID g_ProxyClsId = PROXY_CLSID_IS;
+/** The instance handle of this DLL. For use in registration routines. */
+static HINSTANCE g_hDllSelf;
+
+
+/** Type library GUIDs to clean up manually.
+ * Must be upper case! */
+static PCRTUTF16 const g_apwszTypeLibIds[] =
+{
+ L"{46137EEC-703B-4FE5-AFD4-7C9BBBBA0259}",
+ L"{D7569351-1750-46F0-936E-BD127D5BC264}",
+};
+
+/** Type library version to clean up manually. */
+static PCRTUTF16 const g_apwszTypelibVersions[] =
+{
+ L"1.0",
+ L"1.3",
+};
+
+/** Proxy stub class IDs we wish to clean up manually.
+ * Must be upper case! */
+static PCRTUTF16 const g_apwszProxyStubClsIds[] =
+{
+ L"{0BB3B78C-1807-4249-5BA5-EA42D66AF0BF}",
+ L"{327E3C00-EE61-462F-AED3-0DFF6CBF9904}",
+};
+
+
+/**
+ * DLL main function.
+ *
+ * @returns TRUE (/ FALSE).
+ * @param hInstance The DLL handle.
+ * @param dwReason The rason for the call (DLL_XXX).
+ * @param lpReserved Reserved.
+ */
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
+{
+ switch (dwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Save the DLL handle so we can get the path to this DLL during
+ registration and updating. */
+ g_hDllSelf = hInstance;
+
+ /* We don't need callbacks for thread creation and destruction. */
+ DisableThreadLibraryCalls(hInstance);
+
+ /* Init IPRT. */
+ RTR3InitDll(RTR3INIT_FLAGS_UNOBTRUSIVE);
+ Log12(("VBoxProxyStub[%u]/DllMain: DLL_PROCESS_ATTACH\n", GetCurrentProcessId()));
+
+#ifdef VBOX_STRICT
+ {
+ /*
+ * Check that no interface has more than 256 methods in the stub vtable.
+ */
+ const ProxyFileInfo **ppProxyFile = &g_apProxyFiles[0];
+ const ProxyFileInfo *pProxyFile;
+ while ((pProxyFile = *ppProxyFile++) != NULL)
+ {
+ const PCInterfaceStubVtblList * const papStubVtbls = pProxyFile->pStubVtblList;
+ const char * const *papszNames = pProxyFile->pNamesArray;
+ unsigned iIf = pProxyFile->TableSize;
+ AssertStmt(iIf < 1024, iIf = 0);
+ Assert(pProxyFile->TableVersion == 2);
+
+ while (iIf-- > 0)
+ AssertMsg(papStubVtbls[iIf]->header.DispatchTableCount <= 256,
+ ("%s: DispatchTableCount=%d\n", papszNames[iIf], papStubVtbls[iIf]->header.DispatchTableCount));
+ }
+ }
+#endif
+ break;
+
+ case DLL_PROCESS_DETACH:
+ Log12(("VBoxProxyStub[%u]/DllMain: DLL_PROCESS_DETACH\n", GetCurrentProcessId()));
+ break;
+ }
+
+ NOREF(lpReserved);
+ return TRUE;
+}
+
+
+/**
+ * RPC entry point returning info about the proxy.
+ */
+void RPC_ENTRY GetProxyDllInfo(const ProxyFileInfo ***ppapInfo, const CLSID **ppClsid)
+{
+ *ppapInfo = &g_apProxyFiles[0];
+ *ppClsid = &g_ProxyClsId;
+ Log12(("VBoxProxyStub[%u]/GetProxyDllInfo:\n", GetCurrentProcessId()));
+}
+
+
+/**
+ * Instantiate the proxy stub class object.
+ *
+ * @returns COM status code
+ * @param rclsid Reference to the ID of the call to instantiate (our
+ * g_ProxyClsId).
+ * @param riid The interface ID to return (IID_IPSFactoryBuffer).
+ * @param ppv Where to return the interface pointer on success.
+ */
+HRESULT STDAPICALLTYPE DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
+{
+ HRESULT hrc;
+ Assert(memcmp(rclsid, &g_ProxyClsId, sizeof(g_ProxyClsId)) == 0);
+
+ hrc = NdrDllGetClassObject(rclsid, riid, ppv, /* see DLLGETCLASSOBJECTROUTINE in RpcProxy.h */
+ g_apProxyFiles, &g_ProxyClsId, &g_ProxyStubFactory);
+
+ /*
+ * This may fail if the IDL compiler generates code that is incompatible
+ * with older windows releases. Like for instance 64-bit W2K8 SP1 not
+ * liking the output of MIDL 7.00.0555 (from the v7.1 SDK), despite
+ * /target being set to NT51.
+ */
+ AssertLogRelMsg(hrc == S_OK, ("%Rhrc\n", hrc));
+ Log12(("VBoxProxyStub[%u]/DllGetClassObject(%RTuuid, %RTuuid, %p): %#x + *ppv=%p\n",
+ GetCurrentProcessId(), rclsid, riid, ppv, hrc, ppv ? *ppv : NULL));
+ return hrc;
+}
+
+
+/**
+ * Checks whether the DLL can be unloaded or not.
+ *
+ * @returns S_OK if it can be unloaded, S_FALSE if not.
+ */
+HRESULT STDAPICALLTYPE DllCanUnloadNow(void)
+{
+ HRESULT hrc = NdrDllCanUnloadNow(&g_ProxyStubFactory); /* see DLLCANUNLOADNOW in RpcProxy.h */
+ Log12(("VBoxProxyStub[%u]/DllCanUnloadNow: %Rhrc\n", GetCurrentProcessId(), hrc));
+ return hrc;
+}
+
+
+
+/**
+ * Release call that could be referenced by VirtualBox_p.c via
+ * CStdStubBuffer_METHODS.
+ *
+ * @returns New reference count.
+ * @param pThis Buffer to release.
+ */
+ULONG STDMETHODCALLTYPE CStdStubBuffer_Release(IRpcStubBuffer *pThis) /* see CSTDSTUBBUFFERRELEASE in RpcProxy.h */
+{
+ ULONG cRefs = NdrCStdStubBuffer_Release(pThis, (IPSFactoryBuffer *)&g_ProxyStubFactory);
+ Log12(("VBoxProxyStub[%u]/CStdStubBuffer_Release: %p -> %#x\n", GetCurrentProcessId(), pThis, cRefs));
+ return cRefs;
+}
+
+
+/**
+ * Release call referenced by VirtualBox_p.c via
+ * CStdStubBuffer_DELEGATING_METHODS.
+ *
+ * @returns New reference count.
+ * @param pThis Buffer to release.
+ */
+ULONG WINAPI CStdStubBuffer2_Release(IRpcStubBuffer *pThis) /* see CSTDSTUBBUFFER2RELEASE in RpcProxy.h */
+{
+ ULONG cRefs = NdrCStdStubBuffer2_Release(pThis, (IPSFactoryBuffer *)&g_ProxyStubFactory);
+ Log12(("VBoxProxyStub[%u]/CStdStubBuffer2_Release: %p -> %#x\n", GetCurrentProcessId(), pThis, cRefs));
+ return cRefs;
+}
+
+
+/**
+ * Pure virtual method implementation referenced by VirtualBox_p.c
+ */
+void __cdecl _purecall(void) /* see DLLDUMMYPURECALL in RpcProxy.h */
+{
+ AssertFailed();
+}
+
+
+#ifdef VBSP_LOG_ENABLED
+# include <iprt/asm.h>
+
+/** For logging full key names. */
+static PCRTUTF16 vbpsDebugKeyToWSZ(HKEY hKey)
+{
+ static union
+ {
+ KEY_NAME_INFORMATION NameInfo;
+ WCHAR awchPadding[260];
+ } s_aBufs[4];
+ static uint32_t volatile iNext = 0;
+ uint32_t i = ASMAtomicIncU32(&iNext) % RT_ELEMENTS(s_aBufs);
+ ULONG cbRet = 0;
+ NTSTATUS rcNt;
+
+ memset(&s_aBufs[i], 0, sizeof(s_aBufs[i]));
+ rcNt = NtQueryKey(hKey, KeyNameInformation, &s_aBufs[i], sizeof(s_aBufs[i]) - sizeof(WCHAR), &cbRet);
+ if (!NT_SUCCESS(rcNt))
+ s_aBufs[i].NameInfo.NameLength = 0;
+ s_aBufs[i].NameInfo.Name[s_aBufs[i].NameInfo.NameLength] = '\0';
+ return s_aBufs[i].NameInfo.Name;
+}
+#endif
+
+/**
+ * Registry modifier state.
+ */
+typedef struct VBPSREGSTATE
+{
+ /** Where the classes and stuff are to be registered. */
+ HKEY hkeyClassesRootDst;
+ /** The handle to the CLSID key under hkeyClassesRootDst. */
+ HKEY hkeyClsidRootDst;
+ /** The handle to the Interface key under hkeyClassesRootDst. */
+ HKEY hkeyInterfaceRootDst;
+
+ /** Alternative locations where data needs to be deleted, but never updated. */
+ struct
+ {
+ /** The classes root key handle. */
+ HKEY hkeyClasses;
+ /** The classes/CLSID key handle. */
+ HKEY hkeyClsid;
+ /** The classes/Interface key handle. */
+ HKEY hkeyInterface;
+ } aAltDeletes[3];
+ /** Alternative delete locations. */
+ uint32_t cAltDeletes;
+
+ /** The current total result. */
+ LSTATUS rc;
+
+ /** KEY_WOW64_32KEY, KEY_WOW64_64KEY or 0 (for default). Allows doing all
+ * almost the work from one process (at least W7+ due to aliases). */
+ DWORD fSamWow;
+ /** Desired key access when only deleting. */
+ DWORD fSamDelete;
+ /** Desired key access when only doing updates. */
+ DWORD fSamUpdate;
+ /** Desired key access when both deleting and updating. */
+ DWORD fSamBoth;
+ /** Whether to delete registrations first. */
+ bool fDelete;
+ /** Whether to update registry value and keys. */
+ bool fUpdate;
+
+} VBPSREGSTATE;
+
+
+/**
+ * Initializes a registry modification job state.
+ *
+ * Always call vbpsRegTerm!
+ *
+ * @returns Windows error code (ERROR_SUCCESS on success).
+ * @param pState The state to init.
+ * @param hkeyRoot The registry root tree constant.
+ * @param pszSubRoot The path to the where the classes are registered,
+ * NULL if @a hkeyRoot.
+ * @param fDelete Whether to delete registrations first.
+ * @param fUpdate Whether to update registrations.
+ * @param fSamWow KEY_WOW64_32KEY or 0.
+ */
+static LSTATUS vbpsRegInit(VBPSREGSTATE *pState, HKEY hkeyRoot, const char *pszSubRoot, bool fDelete, bool fUpdate, DWORD fSamWow)
+{
+ LSTATUS rc;
+ unsigned i = 0;
+
+ /*
+ * Initialize the whole structure first so we can safely call vbpsRegTerm on failure.
+ */
+ pState->hkeyClassesRootDst = NULL;
+ pState->hkeyClsidRootDst = NULL;
+ pState->hkeyInterfaceRootDst = NULL;
+ for (i = 0; i < RT_ELEMENTS(pState->aAltDeletes); i++)
+ {
+ pState->aAltDeletes[i].hkeyClasses = NULL;
+ pState->aAltDeletes[i].hkeyClsid = NULL;
+ pState->aAltDeletes[i].hkeyInterface = NULL;
+ }
+ pState->cAltDeletes = 0;
+ pState->rc = ERROR_SUCCESS;
+ pState->fDelete = fDelete;
+ pState->fUpdate = fUpdate;
+ pState->fSamWow = fSamWow;
+ pState->fSamDelete = 0;
+ if (fDelete)
+ pState->fSamDelete = pState->fSamWow | DELETE | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE
+ | STANDARD_RIGHTS_READ | STANDARD_RIGHTS_WRITE;
+ pState->fSamUpdate = 0;
+ if (fUpdate)
+ pState->fSamUpdate = pState->fSamWow | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY
+ | STANDARD_RIGHTS_READ | STANDARD_RIGHTS_WRITE;
+ pState->fSamBoth = pState->fSamDelete | pState->fSamUpdate;
+
+ /*
+ * Open the root keys.
+ */
+ rc = RegOpenKeyExA(hkeyRoot, pszSubRoot, 0 /*fOptions*/, pState->fSamBoth, &pState->hkeyClassesRootDst);
+ if (rc == ERROR_SUCCESS)
+ {
+ rc = RegCreateKeyExW(pState->hkeyClassesRootDst, L"CLSID", 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/,
+ pState->fSamBoth, NULL /*pSecAttr*/, &pState->hkeyClsidRootDst, NULL /*pdwDisposition*/);
+ if (rc == ERROR_SUCCESS)
+ return ERROR_SUCCESS;
+
+ /* Ignore access denied errors as these may easily happen for
+ non-admin users. Just give up when this happens */
+ AssertLogRelMsgReturn(rc == ERROR_ACCESS_DENIED, ("%u\n", rc), pState->rc = rc);
+ }
+ else
+ AssertLogRelMsgReturn(rc == ERROR_ACCESS_DENIED, ("%u\n", rc), pState->rc = rc);
+ return pState->rc = rc;
+}
+
+
+/**
+ * Terminates the state, closing all open keys.
+ *
+ * @param pState The state to clean up.
+ */
+static void vbpsRegTerm(VBPSREGSTATE *pState)
+{
+ LSTATUS rc;
+ if (pState->hkeyClassesRootDst)
+ {
+ rc = RegCloseKey(pState->hkeyClassesRootDst);
+ Assert(rc == ERROR_SUCCESS);
+ pState->hkeyClassesRootDst = NULL;
+ }
+ if (pState->hkeyClsidRootDst)
+ {
+ rc = RegCloseKey(pState->hkeyClsidRootDst);
+ Assert(rc == ERROR_SUCCESS);
+ pState->hkeyClsidRootDst = NULL;
+ }
+ if (pState->hkeyInterfaceRootDst)
+ {
+ rc = RegCloseKey(pState->hkeyInterfaceRootDst);
+ Assert(rc == ERROR_SUCCESS);
+ pState->hkeyInterfaceRootDst = NULL;
+ }
+
+ while (pState->cAltDeletes > 0 && pState->cAltDeletes <= RT_ELEMENTS(pState->aAltDeletes))
+ {
+ unsigned i = --pState->cAltDeletes;
+ if (pState->aAltDeletes[i].hkeyClasses)
+ {
+ rc = RegCloseKey(pState->aAltDeletes[i].hkeyClasses);
+ Assert(rc == ERROR_SUCCESS);
+ pState->aAltDeletes[i].hkeyClasses = NULL;
+ }
+ if (pState->aAltDeletes[i].hkeyClsid)
+ {
+ rc = RegCloseKey(pState->aAltDeletes[i].hkeyClsid);
+ Assert(rc == ERROR_SUCCESS);
+ pState->aAltDeletes[i].hkeyClsid = NULL;
+ }
+ if (pState->aAltDeletes[i].hkeyInterface)
+ {
+ rc = RegCloseKey(pState->aAltDeletes[i].hkeyInterface);
+ Assert(rc == ERROR_SUCCESS);
+ pState->aAltDeletes[i].hkeyInterface = NULL;
+ }
+ }
+}
+
+
+/**
+ * Add an alternative registry classes tree from which to remove keys.
+ *
+ * @returns ERROR_SUCCESS if we successfully opened the destination root, other
+ * wise windows error code (remebered).
+ * @param pState The registry modifier state.
+ * @param hkeyAltRoot The root of the alternate registry classes
+ * location.
+ * @param pszAltSubRoot The path to the 'classes' sub-key, or NULL if
+ * hkeyAltRoot is it.
+ */
+static LSTATUS vbpsRegAddAltDelete(VBPSREGSTATE *pState, HKEY hkeyAltRoot, const char *pszAltSubRoot)
+{
+ unsigned i;
+ LSTATUS rc;
+
+ /* Ignore call if not in delete mode. */
+ if (!pState->fDelete)
+ return ERROR_SUCCESS;
+
+ /* Check that there is space in the state. */
+ i = pState->cAltDeletes;
+ AssertReturn(i < RT_ELEMENTS(pState->aAltDeletes), pState->rc = ERROR_TOO_MANY_NAMES);
+
+
+ /* Open the root. */
+ rc = RegOpenKeyExA(hkeyAltRoot, pszAltSubRoot, 0 /*fOptions*/, pState->fSamDelete,
+ &pState->aAltDeletes[i].hkeyClasses);
+ if (rc == ERROR_SUCCESS)
+ {
+ /* Try open the CLSID subkey, it's fine if it doesn't exists. */
+ rc = RegOpenKeyExW(pState->aAltDeletes[i].hkeyClasses, L"CLSID", 0 /*fOptions*/, pState->fSamDelete,
+ &pState->aAltDeletes[i].hkeyClsid);
+ if (rc == ERROR_SUCCESS || rc == ERROR_FILE_NOT_FOUND)
+ {
+ if (rc == ERROR_FILE_NOT_FOUND)
+ pState->aAltDeletes[i].hkeyClsid = NULL;
+ pState->cAltDeletes = i + 1;
+ return ERROR_SUCCESS;
+ }
+ AssertLogRelMsgFailed(("%u\n", rc));
+ RegCloseKey(pState->aAltDeletes[i].hkeyClasses);
+ }
+ /* No need to add non-existing alternative roots, nothing to delete in the void. */
+ else if (rc == ERROR_FILE_NOT_FOUND)
+ rc = ERROR_SUCCESS;
+ else
+ {
+ AssertLogRelMsgFailed(("%u (%#x %s)\n", rc));
+ pState->rc = rc;
+ }
+
+ pState->aAltDeletes[i].hkeyClasses = NULL;
+ pState->aAltDeletes[i].hkeyClsid = NULL;
+ return rc;
+}
+
+
+/**
+ * Open the 'Interface' keys under the current classes roots.
+ *
+ * We don't do this during vbpsRegInit as it's only needed for updating.
+ *
+ * @returns ERROR_SUCCESS if we successfully opened the destination root, other
+ * wise windows error code (remebered).
+ * @param pState The registry modifier state.
+ */
+static LSTATUS vbpsRegOpenInterfaceKeys(VBPSREGSTATE *pState)
+{
+ unsigned i;
+ LSTATUS rc;
+
+ /*
+ * Under the root destination.
+ */
+ if (pState->hkeyInterfaceRootDst == NULL)
+ {
+ if (pState->fSamUpdate)
+ rc = RegCreateKeyExW(pState->hkeyClassesRootDst, L"Interface", 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/,
+ pState->fSamBoth, NULL /*pSecAttr*/, &pState->hkeyInterfaceRootDst, NULL /*pdwDisposition*/);
+ else
+ rc = RegOpenKeyExW(pState->hkeyClassesRootDst, L"Interface", 0 /*fOptions*/, pState->fSamBoth,
+ &pState->hkeyClsidRootDst);
+ if (rc == ERROR_ACCESS_DENIED)
+ {
+ pState->hkeyInterfaceRootDst = NULL;
+ return pState->rc = rc;
+ }
+ AssertLogRelMsgReturnStmt(rc == ERROR_SUCCESS, ("%u\n", rc), pState->hkeyInterfaceRootDst = NULL, pState->rc = rc);
+ }
+
+ /*
+ * Under the alternative delete locations.
+ */
+ i = pState->cAltDeletes;
+ while (i-- > 0)
+ if (pState->aAltDeletes[i].hkeyInterface == NULL)
+ {
+ rc = RegOpenKeyExW(pState->aAltDeletes[i].hkeyClasses, L"Interface", 0 /*fOptions*/, pState->fSamDelete,
+ &pState->aAltDeletes[i].hkeyInterface);
+ if (rc != ERROR_SUCCESS)
+ {
+ AssertMsgStmt(rc == ERROR_FILE_NOT_FOUND || rc == ERROR_ACCESS_DENIED, ("%u\n", rc), pState->rc = rc);
+ pState->aAltDeletes[i].hkeyInterface = NULL;
+ }
+ }
+
+ return ERROR_SUCCESS;
+}
+
+
+/** The destination buffer size required by vbpsFormatUuidInCurly. */
+#define CURLY_UUID_STR_BUF_SIZE 40
+
+/**
+ * Formats a UUID to a string, inside curly braces.
+ *
+ * @returns @a pszString
+ * @param pszString Output buffer of size CURLY_UUID_STR_BUF_SIZE.
+ * @param pUuidIn The UUID to format.
+ */
+static const char *vbpsFormatUuidInCurly(char pszString[CURLY_UUID_STR_BUF_SIZE], const CLSID *pUuidIn)
+{
+ static const char s_achDigits[17] = "0123456789abcdef";
+ PCRTUUID pUuid = (PCRTUUID)pUuidIn;
+ uint32_t u32TimeLow;
+ unsigned u;
+
+ pszString[ 0] = '{';
+ u32TimeLow = RT_H2LE_U32(pUuid->Gen.u32TimeLow);
+ pszString[ 1] = s_achDigits[(u32TimeLow >> 28)/*& 0xf*/];
+ pszString[ 2] = s_achDigits[(u32TimeLow >> 24) & 0xf];
+ pszString[ 3] = s_achDigits[(u32TimeLow >> 20) & 0xf];
+ pszString[ 4] = s_achDigits[(u32TimeLow >> 16) & 0xf];
+ pszString[ 5] = s_achDigits[(u32TimeLow >> 12) & 0xf];
+ pszString[ 6] = s_achDigits[(u32TimeLow >> 8) & 0xf];
+ pszString[ 7] = s_achDigits[(u32TimeLow >> 4) & 0xf];
+ pszString[ 8] = s_achDigits[(u32TimeLow/*>>0*/)& 0xf];
+ pszString[ 9] = '-';
+ u = RT_H2LE_U16(pUuid->Gen.u16TimeMid);
+ pszString[10] = s_achDigits[(u >> 12)/*& 0xf*/];
+ pszString[11] = s_achDigits[(u >> 8) & 0xf];
+ pszString[12] = s_achDigits[(u >> 4) & 0xf];
+ pszString[13] = s_achDigits[(u/*>>0*/)& 0xf];
+ pszString[14] = '-';
+ u = RT_H2LE_U16(pUuid->Gen.u16TimeHiAndVersion);
+ pszString[15] = s_achDigits[(u >> 12)/*& 0xf*/];
+ pszString[16] = s_achDigits[(u >> 8) & 0xf];
+ pszString[17] = s_achDigits[(u >> 4) & 0xf];
+ pszString[18] = s_achDigits[(u/*>>0*/)& 0xf];
+ pszString[19] = '-';
+ pszString[20] = s_achDigits[pUuid->Gen.u8ClockSeqHiAndReserved >> 4];
+ pszString[21] = s_achDigits[pUuid->Gen.u8ClockSeqHiAndReserved & 0xf];
+ pszString[22] = s_achDigits[pUuid->Gen.u8ClockSeqLow >> 4];
+ pszString[23] = s_achDigits[pUuid->Gen.u8ClockSeqLow & 0xf];
+ pszString[24] = '-';
+ pszString[25] = s_achDigits[pUuid->Gen.au8Node[0] >> 4];
+ pszString[26] = s_achDigits[pUuid->Gen.au8Node[0] & 0xf];
+ pszString[27] = s_achDigits[pUuid->Gen.au8Node[1] >> 4];
+ pszString[28] = s_achDigits[pUuid->Gen.au8Node[1] & 0xf];
+ pszString[29] = s_achDigits[pUuid->Gen.au8Node[2] >> 4];
+ pszString[30] = s_achDigits[pUuid->Gen.au8Node[2] & 0xf];
+ pszString[31] = s_achDigits[pUuid->Gen.au8Node[3] >> 4];
+ pszString[32] = s_achDigits[pUuid->Gen.au8Node[3] & 0xf];
+ pszString[33] = s_achDigits[pUuid->Gen.au8Node[4] >> 4];
+ pszString[34] = s_achDigits[pUuid->Gen.au8Node[4] & 0xf];
+ pszString[35] = s_achDigits[pUuid->Gen.au8Node[5] >> 4];
+ pszString[36] = s_achDigits[pUuid->Gen.au8Node[5] & 0xf];
+ pszString[37] = '}';
+ pszString[38] = '\0';
+
+ return pszString;
+
+}
+
+
+/**
+ * Sets a registry string value, wide char variant.
+ *
+ * @returns See RegSetValueExA (errors are remembered in the state).
+ * @param pState The registry modifier state.
+ * @param hkey The key to add the value to.
+ * @param pwszValueNm The value name. NULL for setting the default.
+ * @param pwszValue The value string.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsSetRegValueWW(VBPSREGSTATE *pState, HKEY hkey, PCRTUTF16 pwszValueNm, PCRTUTF16 pwszValue, unsigned uLine)
+{
+ DWORD const cbValue = (DWORD)((RTUtf16Len(pwszValue) + 1) * sizeof(RTUTF16));
+ LSTATUS rc;
+ Assert(pState->fUpdate);
+
+ /*
+ * If we're not deleting the key prior to updating, we're in gentle update
+ * mode where we will query if the existing value matches the incoming one.
+ */
+ if (!pState->fDelete)
+ {
+ DWORD cbExistingData = cbValue + 128;
+ PRTUTF16 pwszExistingData = (PRTUTF16)alloca(cbExistingData);
+ DWORD dwExistingType;
+ rc = RegQueryValueExW(hkey, pwszValueNm, 0 /*Reserved*/, &dwExistingType, (BYTE *)pwszExistingData, &cbExistingData);
+ if (rc == ERROR_SUCCESS)
+ {
+ if ( dwExistingType == REG_SZ
+ && cbExistingData == cbValue)
+ {
+ if (memcmp(pwszValue, pwszExistingData, cbValue) == 0)
+ return ERROR_SUCCESS;
+ }
+ VBSP_LOG_VALUE_CHANGE(("vbpsSetRegValueWW: Value difference: dwExistingType=%d cbExistingData=%#x cbValue=%#x\n"
+ " hkey=%#x %ls; value name=%ls\n"
+ "existing: %.*Rhxs (%.*ls)\n"
+ " new: %.*Rhxs (%ls)\n",
+ dwExistingType, cbExistingData, cbValue,
+ hkey, vbpsDebugKeyToWSZ(hkey), pwszValueNm ? pwszValueNm : L"(default)",
+ cbExistingData, pwszExistingData, cbExistingData / sizeof(RTUTF16), pwszExistingData,
+ cbValue, pwszValue, pwszValue));
+ }
+ else
+ Assert(rc == ERROR_FILE_NOT_FOUND || rc == ERROR_MORE_DATA);
+ }
+
+ /*
+ * Set the value.
+ */
+ rc = RegSetValueExW(hkey, pwszValueNm, 0 /*Reserved*/, REG_SZ, (const BYTE *)pwszValue, cbValue);
+ if (rc == ERROR_SUCCESS)
+ {
+ VBSP_LOG_SET_VALUE(("vbpsSetRegValueWW: %ls/%ls=%ls (at %d)\n",
+ vbpsDebugKeyToWSZ(hkey), pwszValueNm ? pwszValueNm : L"(Default)", pwszValue, uLine));
+ return ERROR_SUCCESS;
+ }
+
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(rc == ERROR_ACCESS_DENIED),
+ ("%d: '%ls'='%ls' -> %u\n", uLine, pwszValueNm, pwszValue, rc));
+ pState->rc = rc;
+ return rc;
+}
+
+
+/**
+ * Sets a registry string value.
+ *
+ * @returns See RegSetValueExA (errors are remembered in the state).
+ * @param pState The registry modifier state.
+ * @param hkey The key to add the value to.
+ * @param pszValueNm The value name. NULL for setting the default.
+ * @param pszValue The value string.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsSetRegValueAA(VBPSREGSTATE *pState, HKEY hkey, const char *pszValueNm, const char *pszValue, unsigned uLine)
+{
+ DWORD const cbValue = (DWORD)strlen(pszValue) + 1;
+ LSTATUS rc;
+ Assert(pState->fUpdate);
+
+ /*
+ * If we're not deleting the key prior to updating, we're in gentle update
+ * mode where we will query if the existing value matches the incoming one.
+ */
+ if (!pState->fDelete)
+ {
+ DWORD cbExistingData = cbValue + 128;
+ char *pszExistingData = alloca(cbExistingData);
+ DWORD dwExistingType;
+ rc = RegQueryValueExA(hkey, pszValueNm, 0 /*Reserved*/, &dwExistingType, (PBYTE)pszExistingData, &cbExistingData);
+ if (rc == ERROR_SUCCESS)
+ {
+ if ( dwExistingType == REG_SZ
+ && cbExistingData == cbValue)
+ {
+ if (memcmp(pszValue, pszExistingData, cbValue) == 0)
+ return ERROR_SUCCESS;
+ if (memicmp(pszValue, pszExistingData, cbValue) == 0)
+ return ERROR_SUCCESS;
+ }
+ VBSP_LOG_VALUE_CHANGE(("vbpsSetRegValueAA: Value difference: dwExistingType=%d cbExistingData=%#x cbValue=%#x\n"
+ " hkey=%#x %ls; value name=%s\n"
+ "existing: %.*Rhxs (%.*s)\n"
+ " new: %.*Rhxs (%s)\n",
+ dwExistingType, cbExistingData, cbValue,
+ hkey, vbpsDebugKeyToWSZ(hkey), pszValueNm ? pszValueNm : "(default)",
+ cbExistingData, pszExistingData, cbExistingData, pszExistingData,
+ cbValue, pszValue, pszValue));
+ }
+ else
+ Assert(rc == ERROR_FILE_NOT_FOUND || rc == ERROR_MORE_DATA);
+ }
+
+ /*
+ * Set the value.
+ */
+ rc = RegSetValueExA(hkey, pszValueNm, 0 /*Reserved*/, REG_SZ, (PBYTE)pszValue, cbValue);
+ if (rc == ERROR_SUCCESS)
+ {
+ VBSP_LOG_SET_VALUE(("vbpsSetRegValueAA: %ls/%s=%s (at %d)\n",
+ vbpsDebugKeyToWSZ(hkey), pszValueNm ? pszValueNm : "(Default)", pszValue, uLine));
+ return ERROR_SUCCESS;
+ }
+
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(rc == ERROR_ACCESS_DENIED),
+ ("%d: '%s'='%s' -> %u\n", uLine, pszValueNm, pszValue, rc));
+ pState->rc = rc;
+ return rc;
+}
+
+
+/**
+ * Closes a registry key.
+ *
+ * @returns See RegCloseKey (errors are remembered in the state).
+ * @param pState The registry modifier state.
+ * @param hkey The key to close.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsCloseKey(VBPSREGSTATE *pState, HKEY hkey, unsigned uLine)
+{
+ LSTATUS rc = RegCloseKey(hkey);
+ if (rc == ERROR_SUCCESS)
+ return ERROR_SUCCESS;
+
+ AssertLogRelMsgFailed(("%d: close key -> %u\n", uLine, rc));
+ pState->rc = rc;
+ return rc;
+}
+
+
+/**
+ * Creates a registry key.
+ *
+ * @returns See RegCreateKeyA and RegSetValueExA (errors are remembered in the
+ * state).
+ * @param pState The registry modifier state.
+ * @param hkeyParent The parent key.
+ * @param pszKey The new key under @a hkeyParent.
+ * @param phkey Where to return the handle to the new key.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsCreateRegKeyA(VBPSREGSTATE *pState, HKEY hkeyParent, const char *pszKey, PHKEY phkey, unsigned uLine)
+{
+ /*
+ * This will open if it exists and create if new, which is exactly what we want.
+ */
+ HKEY hNewKey;
+ DWORD dwDisposition = 0;
+ LSTATUS rc = RegCreateKeyExA(hkeyParent, pszKey, 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/,
+ pState->fSamBoth, NULL /*pSecAttr*/, &hNewKey, &dwDisposition);
+ if (rc == ERROR_SUCCESS)
+ {
+ *phkey = hNewKey;
+ if (dwDisposition == REG_CREATED_NEW_KEY)
+ VBSP_LOG_NEW_KEY(("vbpsCreateRegKeyA: %ls/%s (at %d)\n", vbpsDebugKeyToWSZ(hkeyParent), pszKey, uLine));
+ }
+ else
+ {
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(rc == ERROR_ACCESS_DENIED),
+ ("%d: create key '%s' -> %u\n", uLine, pszKey, rc));
+ pState->rc = rc;
+ *phkey = NULL;
+ }
+ return rc;
+}
+
+
+/**
+ * Creates a registry key with a default string value.
+ *
+ * @returns See RegCreateKeyA and RegSetValueExA (errors are remembered in the
+ * state).
+ * @param pState The registry modifier state.
+ * @param hkeyParent The parent key.
+ * @param pszKey The new key under @a hkeyParent.
+ * @param pszValue The value string.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsCreateRegKeyWithDefaultValueAA(VBPSREGSTATE *pState, HKEY hkeyParent, const char *pszKey,
+ const char *pszValue, unsigned uLine)
+{
+ HKEY hNewKey;
+ LSTATUS rc = vbpsCreateRegKeyA(pState, hkeyParent, pszKey, &hNewKey, uLine);
+ if (rc == ERROR_SUCCESS)
+ {
+ rc = vbpsSetRegValueAA(pState, hNewKey, NULL /*pszValueNm*/, pszValue, uLine);
+ vbpsCloseKey(pState, hNewKey, uLine);
+ }
+ else
+ {
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(rc == ERROR_ACCESS_DENIED),
+ ("%d: create key '%s'(/Default='%s') -> %u\n", uLine, pszKey, pszValue, rc));
+ pState->rc = rc;
+ }
+ return rc;
+}
+
+
+/**
+ * Creates a registry key with a default wide string value.
+ *
+ * @returns See RegCreateKeyA and RegSetValueExA (errors are remembered in the
+ * state).
+ * @param pState The registry modifier state.
+ * @param hkeyParent The parent key.
+ * @param pszKey The new key under @a hkeyParent.
+ * @param pwszValue The value string.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsCreateRegKeyWithDefaultValueAW(VBPSREGSTATE *pState, HKEY hkeyParent, const char *pszKey,
+ PCRTUTF16 pwszValue, unsigned uLine)
+{
+ HKEY hNewKey;
+ LSTATUS rc = vbpsCreateRegKeyA(pState, hkeyParent, pszKey, &hNewKey, uLine);
+ if (rc == ERROR_SUCCESS)
+ {
+ rc = vbpsSetRegValueWW(pState, hNewKey, NULL /*pwszValueNm*/, pwszValue, uLine);
+ vbpsCloseKey(pState, hNewKey, uLine);
+ }
+ else
+ {
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(rc == ERROR_ACCESS_DENIED),
+ ("%d: create key '%s'(/Default='%ls') -> %u\n", uLine, pszKey, pwszValue, rc));
+ pState->rc = rc;
+ }
+ return rc;
+}
+
+
+/**
+ * Creates a registry key with a default string value, return the key.
+ *
+ * @returns See RegCreateKeyA and RegSetValueExA (errors are remembered in the
+ * state).
+ * @param pState The registry modifier state.
+ * @param hkeyParent The parent key.
+ * @param pszKey The new key under @a hkeyParent.
+ * @param pszValue The value string.
+ * @param phkey Where to return the handle to the new key.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsCreateRegKeyWithDefaultValueAAEx(VBPSREGSTATE *pState, HKEY hkeyParent, const char *pszKey,
+ const char *pszValue, PHKEY phkey, unsigned uLine)
+{
+ HKEY hNewKey;
+ LSTATUS rc = vbpsCreateRegKeyA(pState, hkeyParent, pszKey, &hNewKey, uLine);
+ if (rc == ERROR_SUCCESS)
+ {
+ rc = vbpsSetRegValueAA(pState, hNewKey, NULL /*pszValueNm*/, pszValue, uLine);
+ *phkey = hNewKey;
+ }
+ else
+ {
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(rc == ERROR_ACCESS_DENIED),
+ ("%d: create key '%s'(/Default='%s') -> %u\n", uLine, pszKey, pszValue, rc));
+ pState->rc = rc;
+ *phkey = NULL;
+ }
+ return rc;
+}
+
+
+/**
+ * Recursively deletes a registry key.
+ *
+ * @returns See SHDeleteKeyA (errors are remembered in the state).
+ * @param pState The registry modifier state.
+ * @param hkeyParent The parent key.
+ * @param pszKey The key under @a hkeyParent that should be
+ * deleted.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsDeleteKeyRecursiveA(VBPSREGSTATE *pState, HKEY hkeyParent, const char *pszKey, unsigned uLine)
+{
+ LSTATUS rc;
+
+ Assert(pState->fDelete);
+ Assert(pszKey);
+ AssertReturn(*pszKey != '\0', pState->rc = ERROR_INVALID_PARAMETER);
+
+#ifdef VBSP_LOG_ENABLED
+ {
+ HKEY hkeyLog;
+ rc = RegOpenKeyExA(hkeyParent, pszKey, 0 /*fOptions*/, pState->fSamDelete, &hkeyLog);
+ if (rc != ERROR_FILE_NOT_FOUND)
+ VBSP_LOG_DEL_KEY(("vbpsDeleteKeyRecursiveA: %ls/%s (at %d)\n", vbpsDebugKeyToWSZ(hkeyParent), pszKey, uLine));
+ if (rc == ERROR_SUCCESS)
+ RegCloseKey(hkeyLog);
+ }
+#endif
+
+ rc = SHDeleteKeyA(hkeyParent, pszKey);
+ if (rc == ERROR_SUCCESS || rc == ERROR_FILE_NOT_FOUND)
+ return ERROR_SUCCESS;
+
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(rc == ERROR_ACCESS_DENIED),
+ ("%d: delete key '%s' -> %u\n", uLine, pszKey, rc));
+ pState->rc = rc;
+ return rc;
+}
+
+
+/**
+ * Recursively deletes a registry key, wide char version.
+ *
+ * @returns See SHDeleteKeyW (errors are remembered in the state).
+ * @param pState The registry modifier state.
+ * @param hkeyParent The parent key.
+ * @param pwszKey The key under @a hkeyParent that should be
+ * deleted.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsDeleteKeyRecursiveW(VBPSREGSTATE *pState, HKEY hkeyParent, PCRTUTF16 pwszKey, unsigned uLine)
+{
+ LSTATUS rc;
+
+ Assert(pState->fDelete);
+ Assert(pwszKey);
+ AssertReturn(*pwszKey != '\0', pState->rc = ERROR_INVALID_PARAMETER);
+
+#ifdef VBSP_LOG_ENABLED
+ {
+ HKEY hkeyLog;
+ rc = RegOpenKeyExW(hkeyParent, pwszKey, 0 /*fOptions*/, pState->fSamDelete, &hkeyLog);
+ if (rc != ERROR_FILE_NOT_FOUND)
+ VBSP_LOG_DEL_KEY(("vbpsDeleteKeyRecursiveW: %ls/%ls (at %d)\n", vbpsDebugKeyToWSZ(hkeyParent), pwszKey, uLine));
+ if (rc == ERROR_SUCCESS)
+ RegCloseKey(hkeyLog);
+ }
+#endif
+
+ rc = SHDeleteKeyW(hkeyParent, pwszKey);
+ if (rc == ERROR_SUCCESS || rc == ERROR_FILE_NOT_FOUND)
+ return ERROR_SUCCESS;
+
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(rc == ERROR_ACCESS_DENIED),
+ ("%d: delete key '%ls' -> %u\n", uLine, pwszKey, rc));
+ pState->rc = rc;
+ return rc;
+}
+
+
+/**
+ * Register an application id.
+ *
+ * @returns Windows error code (errors are rememberd in the state).
+ * @param pState The registry modifier state.
+ * @param pszModuleName The module name.
+ * @param pszAppId The application UUID string.
+ * @param pszDescription The description string.
+ * @param pszServiceName The window service name if the application is a
+ * service, otherwise this must be NULL.
+ */
+LSTATUS VbpsRegisterAppId(VBPSREGSTATE *pState, const char *pszModuleName, const char *pszAppId,
+ const char *pszDescription, const char *pszServiceName)
+{
+ LSTATUS rc;
+ HKEY hkeyAppIds;
+ Assert(*pszAppId == '{');
+
+ /*
+ * Delete.
+ */
+ if (pState->fDelete)
+ {
+ unsigned i = pState->cAltDeletes;
+ while (i-- > 0)
+ {
+ rc = RegOpenKeyExW(pState->aAltDeletes[i].hkeyClasses, L"AppID", 0 /*fOptions*/, pState->fSamDelete, &hkeyAppIds);
+ AssertLogRelMsgStmt(rc == ERROR_SUCCESS || rc == ERROR_FILE_NOT_FOUND, ("%u\n", rc), pState->rc = rc);
+ if (rc == ERROR_SUCCESS)
+ {
+ vbpsDeleteKeyRecursiveA(pState, hkeyAppIds, pszAppId, __LINE__);
+ vbpsCloseKey(pState, hkeyAppIds, __LINE__);
+ }
+ }
+ }
+
+ if (pState->fUpdate)
+ {
+ rc = RegCreateKeyExW(pState->hkeyClassesRootDst, L"AppID", 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/,
+ pState->fSamBoth, NULL /*pSecAttr*/, &hkeyAppIds, NULL /*pdwDisposition*/);
+ if (rc == ERROR_ACCESS_DENIED)
+ return ERROR_SUCCESS;
+ }
+ else
+ {
+ rc = RegOpenKeyExW(pState->hkeyClassesRootDst, L"AppID", 0 /*fOptions*/, pState->fSamBoth, &hkeyAppIds);
+ if (rc == ERROR_FILE_NOT_FOUND || rc == ERROR_ACCESS_DENIED)
+ return ERROR_SUCCESS;
+ }
+ if (rc == ERROR_ACCESS_DENIED)
+ return pState->rc = rc;
+ AssertLogRelMsgReturn(rc == ERROR_SUCCESS, ("%u\n", rc), pState->rc = rc);
+
+ if (pState->fDelete)
+ {
+ vbpsDeleteKeyRecursiveA(pState, hkeyAppIds, pszAppId, __LINE__);
+ vbpsDeleteKeyRecursiveA(pState, hkeyAppIds, pszModuleName, __LINE__);
+ }
+
+ /*
+ * Register / update.
+ */
+ if (pState->fUpdate)
+ {
+ HKEY hkey;
+ rc = vbpsCreateRegKeyA(pState, hkeyAppIds, pszAppId, &hkey, __LINE__);
+ if (rc == ERROR_SUCCESS)
+ {
+ vbpsSetRegValueAA(pState, hkey, NULL /*pszValueNm*/, pszDescription, __LINE__);
+ if (pszServiceName)
+ vbpsSetRegValueAA(pState, hkey, "LocalService", pszServiceName, __LINE__);
+ vbpsCloseKey(pState, hkey, __LINE__);
+ }
+
+ rc = vbpsCreateRegKeyA(pState, hkeyAppIds, pszModuleName, &hkey, __LINE__);
+ if (rc == ERROR_SUCCESS)
+ {
+ vbpsSetRegValueAA(pState, hkey, NULL /*pszValueNm*/, "", __LINE__);
+ vbpsSetRegValueAA(pState, hkey, "AppID", pszAppId, __LINE__);
+ vbpsCloseKey(pState, hkey, __LINE__);
+ }
+ }
+
+ vbpsCloseKey(pState, hkeyAppIds, __LINE__);
+
+ return pState->rc;
+}
+
+
+/**
+ * Register an class name.
+ *
+ * @returns Windows error code (errors are rememberd in the state).
+ * @param pState The registry modifier state.
+ * @param pszClassName The name of the class.
+ * @param pszDescription The description string
+ * @param pClsId The UUID for the class.
+ * @param pszCurVerSuffIfRootName This is the current version suffix to
+ * append to @a pszClassName when
+ * registering the version idependent name.
+ */
+LSTATUS VbpsRegisterClassName(VBPSREGSTATE *pState, const char *pszClassName, const char *pszDescription,
+ const CLSID *pClsId, const char *pszCurVerSuffIfRootName)
+{
+ LSTATUS rc;
+
+ /*
+ * Delete.
+ */
+ if (pState->fDelete)
+ {
+ unsigned i = pState->cAltDeletes;
+ while (i-- > 0)
+ vbpsDeleteKeyRecursiveA(pState, pState->aAltDeletes[i].hkeyClasses, pszClassName, __LINE__);
+ vbpsDeleteKeyRecursiveA(pState, pState->hkeyClassesRootDst, pszClassName, __LINE__);
+ }
+
+ /*
+ * Update.
+ */
+ if (pState->fUpdate)
+ {
+ /* pszClassName/Default = description. */
+ HKEY hkeyClass;
+ rc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, pState->hkeyClassesRootDst, pszClassName, pszDescription,
+ &hkeyClass, __LINE__);
+ if (rc == ERROR_SUCCESS)
+ {
+ char szClsId[CURLY_UUID_STR_BUF_SIZE];
+
+ /* CLSID/Default = pClsId. */
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyClass, "CLSID", vbpsFormatUuidInCurly(szClsId, pClsId), __LINE__);
+
+ /* CurVer/Default = pszClassName+Suffix. */
+ if (pszCurVerSuffIfRootName != NULL)
+ {
+ char szCurClassNameVer[128];
+ rc = RTStrCopy(szCurClassNameVer, sizeof(szCurClassNameVer), pszClassName);
+ if (RT_SUCCESS(rc))
+ rc = RTStrCat(szCurClassNameVer, sizeof(szCurClassNameVer), pszCurVerSuffIfRootName);
+ AssertStmt(RT_SUCCESS(rc), pState->rc = rc = ERROR_INVALID_DATA);
+ if (rc == ERROR_SUCCESS)
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyClass, "CurVer", szCurClassNameVer, __LINE__);
+ }
+
+ vbpsCloseKey(pState, hkeyClass, __LINE__);
+ }
+ }
+
+ return pState->rc;
+}
+
+
+/**
+ * Registers a class ID.
+ *
+ * @returns Windows error code (errors are rememberd in the state).
+ * @param pState The registry modifier state.
+ * @param pClsId The UUID for the class.
+ * @param pszDescription The description string.
+ * @param pszAppId The application ID.
+ * @param pszClassName The version idependent class name.
+ * @param pszCurClassNameVerSuffix The suffix to add to @a pszClassName for
+ * the current version.
+ * @param pTypeLibId The UUID for the typelib this class
+ * belongs to.
+ * @param pszServerType The server type (InprocServer32 or
+ * LocalServer32).
+ * @param pwszVBoxDir The VirtualBox install directory
+ * (unicode), trailing slash.
+ * @param pszServerSubPath What to append to @a pwszVBoxDir to
+ * construct the server module name.
+ * @param pszThreadingModel The threading model for inproc servers,
+ * NULL for local servers.
+ */
+LSTATUS VbpsRegisterClassId(VBPSREGSTATE *pState, const CLSID *pClsId, const char *pszDescription, const char *pszAppId,
+ const char *pszClassName, const char *pszCurClassNameVerSuffix, const CLSID *pTypeLibId,
+ const char *pszServerType, PCRTUTF16 pwszVBoxDir, const char *pszServerSubPath,
+ const char *pszThreadingModel)
+{
+ LSTATUS rc;
+ char szClsId[CURLY_UUID_STR_BUF_SIZE];
+ RT_NOREF(pszAppId);
+
+ Assert(!pszAppId || *pszAppId == '{');
+ Assert((pwszVBoxDir == NULL && !pState->fUpdate) || pwszVBoxDir[RTUtf16Len(pwszVBoxDir) - 1] == '\\');
+
+ /*
+ * We need this, whatever we end up having to do.
+ */
+ vbpsFormatUuidInCurly(szClsId, pClsId);
+
+ /*
+ * Delete.
+ */
+ if (pState->fDelete)
+ {
+ unsigned i = pState->cAltDeletes;
+ while (i-- > 0)
+ if (pState->aAltDeletes[i].hkeyClsid != NULL)
+ vbpsDeleteKeyRecursiveA(pState, pState->aAltDeletes[i].hkeyClsid, szClsId, __LINE__);
+ vbpsDeleteKeyRecursiveA(pState, pState->hkeyClsidRootDst, szClsId, __LINE__);
+ }
+
+ /*
+ * Update.
+ */
+ if (pState->fUpdate)
+ {
+ HKEY hkeyClass;
+ rc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, pState->hkeyClsidRootDst, szClsId, pszDescription,
+ &hkeyClass, __LINE__);
+ if (rc == ERROR_SUCCESS)
+ {
+ bool const fIsLocalServer32 = strcmp(pszServerType, "LocalServer32") == 0;
+ HKEY hkeyServerType;
+ char szCurClassNameVer[128];
+
+ /* pszServerType/Default = module. */
+ rc = vbpsCreateRegKeyA(pState, hkeyClass, pszServerType, &hkeyServerType, __LINE__);
+ if (rc == ERROR_SUCCESS)
+ {
+ RTUTF16 wszModule[MAX_PATH * 2];
+ PRTUTF16 pwszCur = wszModule;
+ if (fIsLocalServer32)
+ *pwszCur++ = '"';
+
+ rc = RTUtf16Copy(pwszCur, MAX_PATH, pwszVBoxDir); AssertRC(rc);
+ pwszCur += RTUtf16Len(pwszCur);
+ rc = RTUtf16CopyAscii(pwszCur, MAX_PATH - 3, pszServerSubPath); AssertRC(rc);
+ pwszCur += RTUtf16Len(pwszCur);
+
+ if (fIsLocalServer32)
+ *pwszCur++ = '"';
+ *pwszCur++ = '\0'; /* included, so ++. */
+
+ vbpsSetRegValueWW(pState, hkeyServerType, NULL /*pszValueNm*/, wszModule, __LINE__);
+
+ /* pszServerType/ThreadingModel = pszThreading Model. */
+ if (pszThreadingModel)
+ vbpsSetRegValueAA(pState, hkeyServerType, "ThreadingModel", pszThreadingModel, __LINE__);
+
+ vbpsCloseKey(pState, hkeyServerType, __LINE__);
+ }
+
+ /* ProgId/Default = pszClassName + pszCurClassNameVerSuffix. */
+ if (pszClassName)
+ {
+ rc = RTStrCopy(szCurClassNameVer, sizeof(szCurClassNameVer), pszClassName);
+ if (RT_SUCCESS(rc))
+ rc = RTStrCat(szCurClassNameVer, sizeof(szCurClassNameVer), pszCurClassNameVerSuffix);
+ AssertStmt(RT_SUCCESS(rc), pState->rc = rc = ERROR_INVALID_DATA);
+ if (rc == ERROR_SUCCESS)
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyClass, "ProgId", szCurClassNameVer, __LINE__);
+
+ /* VersionIndependentProgID/Default = pszClassName. */
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyClass, "VersionIndependentProgID", pszClassName, __LINE__);
+ }
+
+ /* TypeLib/Default = pTypeLibId. */
+ if (pTypeLibId)
+ {
+ char szTypeLibId[CURLY_UUID_STR_BUF_SIZE];
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyClass, "TypeLib",
+ vbpsFormatUuidInCurly(szTypeLibId, pTypeLibId), __LINE__);
+ }
+
+ /* AppID = pszAppId */
+ if (pszAppId && fIsLocalServer32)
+ vbpsSetRegValueAA(pState, hkeyClass, "AppID", pszAppId, __LINE__);
+
+ vbpsCloseKey(pState, hkeyClass, __LINE__);
+ }
+ }
+
+ return pState->rc;
+}
+
+
+/**
+ * Register modules and classes from the VirtualBox.xidl file.
+ *
+ * @returns COM status code.
+ * @param pState
+ * @param pwszVBoxDir The VirtualBox application directory.
+ * @param fIs32On64 Set if this is the 32-bit on 64-bit component.
+ *
+ * @todo convert to XSLT.
+ */
+void RegisterXidlModulesAndClassesGenerated(VBPSREGSTATE *pState, PCRTUTF16 pwszVBoxDir, bool fIs32On64)
+{
+ const char *pszAppId = "{819B4D85-9CEE-493C-B6FC-64FFE759B3C9}";
+ const char *pszInprocDll = !fIs32On64 ? "VBoxC.dll" : "x86\\VBoxClient-x86.dll";
+ const char *pszLocalServer = "VBoxSVC.exe";
+#ifdef VBOX_WITH_SDS
+ const char *pszSdsAppId = "{EC0E78E8-FA43-43E8-AC0A-02C784C4A4FA}";
+ const char *pszSdsExe = "VBoxSDS.exe";
+ const char *pszSdsServiceName = "VBoxSDS";
+#endif
+
+ /* VBoxSVC */
+ VbpsRegisterAppId(pState, pszLocalServer, pszAppId, "VirtualBox Application", NULL);
+ VbpsRegisterClassName(pState, "VirtualBox.VirtualBox.1", "VirtualBox Class", &CLSID_VirtualBox, NULL);
+ VbpsRegisterClassName(pState, "VirtualBox.VirtualBox", "VirtualBox Class", &CLSID_VirtualBox, ".1");
+ VbpsRegisterClassId(pState, &CLSID_VirtualBox, "VirtualBox Class", pszAppId, "VirtualBox.VirtualBox", ".1",
+ &LIBID_VirtualBox, "LocalServer32", pwszVBoxDir, pszLocalServer, NULL /*N/A*/);
+ /* VBoxC */
+ VbpsRegisterClassName(pState, "VirtualBox.Session.1", "Session Class", &CLSID_Session, NULL);
+ VbpsRegisterClassName(pState, "VirtualBox.Session", "Session Class", &CLSID_Session, ".1");
+ VbpsRegisterClassId(pState, &CLSID_Session, "Session Class", pszAppId, "VirtualBox.Session", ".1",
+ &LIBID_VirtualBox, "InprocServer32", pwszVBoxDir, pszInprocDll, "Free");
+
+ VbpsRegisterClassName(pState, "VirtualBox.VirtualBoxClient.1", "VirtualBoxClient Class", &CLSID_VirtualBoxClient, NULL);
+ VbpsRegisterClassName(pState, "VirtualBox.VirtualBoxClient", "VirtualBoxClient Class", &CLSID_VirtualBoxClient, ".1");
+ VbpsRegisterClassId(pState, &CLSID_VirtualBoxClient, "VirtualBoxClient Class", pszAppId,
+ "VirtualBox.VirtualBoxClient", ".1",
+ &LIBID_VirtualBox, "InprocServer32", pwszVBoxDir, pszInprocDll, "Free");
+
+#ifdef VBOX_WITH_SDS
+ /* VBoxSDS */
+ VbpsRegisterAppId(pState, pszSdsExe, pszSdsAppId, "VirtualBox System Service", pszSdsServiceName);
+ VbpsRegisterClassName(pState, "VirtualBox.VirtualBoxSDS.1", "VirtualBoxSDS Class", &CLSID_VirtualBoxSDS, NULL);
+ VbpsRegisterClassName(pState, "VirtualBox.VirtualBoxSDS", "VirtualBoxSDS Class", &CLSID_VirtualBoxSDS, ".1");
+ VbpsRegisterClassId(pState, &CLSID_VirtualBoxSDS, "VirtualBoxSDS Class", pszSdsAppId, "VirtualBox.VirtualBoxSDS", ".1",
+ &LIBID_VirtualBox, "LocalServer32", pwszVBoxDir, pszSdsExe, NULL /*N/A*/);
+#endif
+}
+
+
+/**
+ * Updates the VBox type lib registration.
+ *
+ * This is only used when updating COM registrations during com::Initialize.
+ * For normal registration and unregistrations we use the RegisterTypeLib and
+ * UnRegisterTypeLib APIs.
+ *
+ * @param pState The registry modifier state.
+ * @param pwszVBoxDir The VirtualBox install directory (unicode),
+ * trailing slash.
+ * @param fIs32On64 Set if we're registering the 32-bit proxy stub
+ * on a 64-bit system.
+ */
+static void vbpsUpdateTypeLibRegistration(VBPSREGSTATE *pState, PCRTUTF16 pwszVBoxDir, bool fIs32On64)
+{
+ const char * const pszTypeLibDll = VBPS_PROXY_STUB_FILE(fIs32On64);
+#if ARCH_BITS == 32 && !defined(VBOX_IN_32_ON_64_MAIN_API)
+ const char * const pszWinXx = "win32";
+#else
+ const char * const pszWinXx = !fIs32On64 ? "win64" : "win32";
+#endif
+ const char * const pszDescription = "VirtualBox Type Library";
+
+ char szTypeLibId[CURLY_UUID_STR_BUF_SIZE];
+ HKEY hkeyTypeLibs;
+ HKEY hkeyTypeLibId;
+ LSTATUS rc;
+ RT_NOREF(fIs32On64);
+
+ Assert(pState->fUpdate && !pState->fDelete);
+
+ /*
+ * Type library registration (w/o interfaces).
+ */
+
+ /* Open Classes/TypeLib/. */
+ rc = vbpsCreateRegKeyA(pState, pState->hkeyClassesRootDst, "TypeLib", &hkeyTypeLibs, __LINE__);
+ if (rc != ERROR_SUCCESS)
+ return;
+
+ /* Create TypeLib/{UUID}. */
+ rc = vbpsCreateRegKeyA(pState, hkeyTypeLibs, vbpsFormatUuidInCurly(szTypeLibId, &LIBID_VirtualBox), &hkeyTypeLibId, __LINE__);
+ if (rc == ERROR_SUCCESS)
+ {
+ /* {UUID}/Major.Minor/Default = pszDescription. */
+ HKEY hkeyMajMin;
+ char szMajMin[64];
+ sprintf(szMajMin, "%u.%u", kTypeLibraryMajorVersion, kTypeLibraryMinorVersion);
+ rc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, hkeyTypeLibId, szMajMin, pszDescription, &hkeyMajMin, __LINE__);
+ if (rc == ERROR_SUCCESS)
+ {
+ RTUTF16 wszBuf[MAX_PATH * 2];
+
+ /* {UUID}/Major.Minor/0. */
+ HKEY hkey0;
+ rc = vbpsCreateRegKeyA(pState, hkeyMajMin, "0", &hkey0, __LINE__);
+ if (rc == ERROR_SUCCESS)
+ {
+ /* {UUID}/Major.Minor/0/winXX/Default = VBoxProxyStub. */
+ rc = RTUtf16Copy(wszBuf, MAX_PATH, pwszVBoxDir); AssertRC(rc);
+ rc = RTUtf16CatAscii(wszBuf, MAX_PATH * 2, pszTypeLibDll); AssertRC(rc);
+
+ vbpsCreateRegKeyWithDefaultValueAW(pState, hkey0, pszWinXx, wszBuf, __LINE__);
+ vbpsCloseKey(pState, hkey0, __LINE__);
+ }
+
+ /* {UUID}/Major.Minor/FLAGS */
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyMajMin, "FLAGS", "0", __LINE__);
+
+ /* {UUID}/Major.Minor/HELPDIR */
+ rc = RTUtf16Copy(wszBuf, MAX_PATH, pwszVBoxDir); AssertRC(rc);
+#if 0 /* MSI: trailing slash; regsvr32/comregister: strip unnecessary trailing slash. Go with MSI to avoid user issues. */
+ {
+ size_t off = RTUtf16Len(wszBuf);
+ while (off > 2 && wszBuf[off - 2] != ':' && RTPATH_IS_SLASH(wszBuf[off - 1]))
+ off--;
+ wszBuf[off] = '\0';
+ }
+#endif
+ vbpsCreateRegKeyWithDefaultValueAW(pState, hkeyMajMin, "HELPDIR", wszBuf, __LINE__);
+
+ vbpsCloseKey(pState, hkeyMajMin, __LINE__);
+ }
+ vbpsCloseKey(pState, hkeyTypeLibId, __LINE__);
+ }
+ vbpsCloseKey(pState, hkeyTypeLibs, __LINE__);
+}
+
+
+/**
+ * Update the VBox proxy stub registration.
+ *
+ * This is only used when updating COM registrations during com::Initialize.
+ * For normal registration and unregistrations we use the NdrDllRegisterProxy
+ * and NdrDllUnregisterProxy.
+ *
+ * @param pState The registry modifier state.
+ * @param pwszVBoxDir The VirtualBox install directory (unicode),
+ * trailing slash.
+ * @param fIs32On64 Set if we're registering the 32-bit proxy stub
+ * on a 64-bit system.
+ */
+static void vbpsUpdateProxyStubRegistration(VBPSREGSTATE *pState, PCRTUTF16 pwszVBoxDir, bool fIs32On64)
+{
+ /*
+ * Register the proxy stub factory class ID.
+ * It's simple compared to the VBox classes, thus all the NULL parameters.
+ */
+ const char *pszPsDll = VBPS_PROXY_STUB_FILE(fIs32On64);
+ RT_NOREF(fIs32On64);
+ Assert(pState->fUpdate && !pState->fDelete);
+ VbpsRegisterClassId(pState, &g_ProxyClsId, "PSFactoryBuffer", NULL /*pszAppId*/,
+ NULL /*pszClassName*/, NULL /*pszCurClassNameVerSuffix*/, NULL /*pTypeLibId*/,
+ "InprocServer32", pwszVBoxDir, pszPsDll, "Both");
+}
+
+
+/**
+ * Updates the VBox interface registrations.
+ *
+ * This is only used when updating COM registrations during com::Initialize.
+ * For normal registration and unregistrations we use the NdrDllRegisterProxy
+ * and NdrDllUnregisterProxy.
+ *
+ * @param pState The registry modifier state.
+ */
+static void vbpsUpdateInterfaceRegistrations(VBPSREGSTATE *pState)
+{
+ const ProxyFileInfo **ppProxyFile = &g_apProxyFiles[0];
+ const ProxyFileInfo *pProxyFile;
+ LSTATUS rc;
+ char szProxyClsId[CURLY_UUID_STR_BUF_SIZE];
+ char szTypeLibId[CURLY_UUID_STR_BUF_SIZE];
+ char szTypeLibVersion[64];
+
+ vbpsFormatUuidInCurly(szProxyClsId, &g_ProxyClsId);
+ vbpsFormatUuidInCurly(szTypeLibId, &LIBID_VirtualBox);
+ sprintf(szTypeLibVersion, "%u.%u", kTypeLibraryMajorVersion, kTypeLibraryMinorVersion);
+
+ Assert(pState->fUpdate && !pState->fDelete);
+ rc = vbpsRegOpenInterfaceKeys(pState);
+ if (rc != ERROR_SUCCESS)
+ return;
+
+ /*
+ * We walk the proxy file list (even if we only have one).
+ */
+ while ((pProxyFile = *ppProxyFile++) != NULL)
+ {
+ const PCInterfaceStubVtblList * const papStubVtbls = pProxyFile->pStubVtblList;
+ const char * const *papszNames = pProxyFile->pNamesArray;
+ unsigned iIf = pProxyFile->TableSize;
+ AssertStmt(iIf < 1024, iIf = 0);
+ Assert(pProxyFile->TableVersion == 2);
+
+ /*
+ * Walk the interfaces in that file, picking data from the various tables.
+ */
+ while (iIf-- > 0)
+ {
+ char szIfId[CURLY_UUID_STR_BUF_SIZE];
+ const char * const pszIfNm = papszNames[iIf];
+ size_t const cchIfNm = RT_VALID_PTR(pszIfNm) ? strlen(pszIfNm) : 0;
+ char szMethods[32];
+ uint32_t const cMethods = papStubVtbls[iIf]->header.DispatchTableCount;
+ HKEY hkeyIfId;
+
+ AssertReturnVoidStmt(cchIfNm >= 3 && cchIfNm <= 72, pState->rc = ERROR_INVALID_DATA);
+
+ AssertReturnVoidStmt(cMethods >= 3 && cMethods < 1024, pState->rc = ERROR_INVALID_DATA);
+ sprintf(szMethods, "%u", cMethods);
+
+ rc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, pState->hkeyInterfaceRootDst,
+ vbpsFormatUuidInCurly(szIfId, papStubVtbls[iIf]->header.piid),
+ pszIfNm, &hkeyIfId, __LINE__);
+ if (rc == ERROR_SUCCESS)
+ {
+ HKEY hkeyTypeLib;
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyIfId, "ProxyStubClsid32", szProxyClsId, __LINE__);
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyIfId, "NumMethods", szMethods, __LINE__);
+
+ /* The MSI seems to still be putting TypeLib keys here. So, let's do that too. */
+ rc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, hkeyIfId, "TypeLib", szTypeLibId, &hkeyTypeLib, __LINE__);
+ if (rc == ERROR_SUCCESS)
+ {
+ vbpsSetRegValueAA(pState, hkeyTypeLib, "Version", szTypeLibVersion, __LINE__);
+ vbpsCloseKey(pState, hkeyTypeLib, __LINE__);
+ }
+
+ vbpsCloseKey(pState, hkeyIfId, __LINE__);
+ }
+ }
+ }
+}
+
+
+static bool vbpsIsUpToDate(VBPSREGSTATE *pState)
+{
+ /** @todo read some registry key and */
+ NOREF(pState);
+ return false;
+}
+
+static bool vbpsMarkUpToDate(VBPSREGSTATE *pState)
+{
+ /** @todo write the key vbpsIsUpToDate uses, if pState indicates success. */
+ NOREF(pState);
+ return false;
+}
+
+
+
+/**
+ * Strips the stub dll name and any x86 subdir off the full DLL path to get a
+ * path to the VirtualBox application directory.
+ *
+ * @param pwszDllPath The path to strip, returns will end with a slash.
+ */
+static void vbpsDllPathToVBoxDir(PRTUTF16 pwszDllPath)
+{
+ RTUTF16 wc;
+ size_t off = RTUtf16Len(pwszDllPath);
+ while ( off > 0
+ && ( (wc = pwszDllPath[off - 1]) >= 127U
+ || !RTPATH_IS_SEP((unsigned char)wc)))
+ off--;
+
+#ifdef VBOX_IN_32_ON_64_MAIN_API
+ /*
+ * The -x86 variant is in a x86 subdirectory, drop it.
+ */
+ while ( off > 0
+ && ( (wc = pwszDllPath[off - 1]) < 127U
+ && RTPATH_IS_SEP((unsigned char)wc)))
+ off--;
+ while ( off > 0
+ && ( (wc = pwszDllPath[off - 1]) >= 127U
+ || !RTPATH_IS_SEP((unsigned char)wc)))
+ off--;
+#endif
+ pwszDllPath[off] = '\0';
+}
+
+
+/**
+ * Wrapper around RegisterXidlModulesAndClassesGenerated for the convenience of
+ * the standard registration entry points.
+ *
+ * @returns COM status code.
+ * @param pwszVBoxDir The VirtualBox install directory (unicode),
+ * trailing slash.
+ * @param fDelete Whether to delete registration keys and values.
+ * @param fUpdate Whether to update registration keys and values.
+ */
+HRESULT RegisterXidlModulesAndClasses(PRTUTF16 pwszVBoxDir, bool fDelete, bool fUpdate)
+{
+#ifdef VBOX_IN_32_ON_64_MAIN_API
+ bool const fIs32On64 = true;
+#else
+ bool const fIs32On64 = false;
+#endif
+ VBPSREGSTATE State;
+ LSTATUS rc;
+
+ /*
+ * Do registration for the current execution mode of the DLL.
+ */
+ rc = vbpsRegInit(&State, HKEY_CLASSES_ROOT, NULL /* Alt: HKEY_LOCAL_MACHINE, "Software\\Classes", */, fDelete, fUpdate, 0);
+ if (rc == ERROR_SUCCESS)
+ {
+ if (!fUpdate)
+ {
+ /* When only unregistering, really purge everything twice or trice. :-) */
+ vbpsRegAddAltDelete(&State, HKEY_LOCAL_MACHINE, "Software\\Classes");
+ vbpsRegAddAltDelete(&State, HKEY_CURRENT_USER, "Software\\Classes");
+ vbpsRegAddAltDelete(&State, HKEY_CLASSES_ROOT, NULL);
+ }
+
+ RegisterXidlModulesAndClassesGenerated(&State, pwszVBoxDir, fIs32On64);
+ rc = State.rc;
+ }
+ vbpsRegTerm(&State);
+
+ /*
+ * Translate error code? Return.
+ */
+ if (rc == ERROR_SUCCESS)
+ return S_OK;
+ return E_FAIL;
+}
+
+
+/**
+ * Checks if the string matches any of our type library versions.
+ *
+ * @returns true on match, false on mismatch.
+ * @param pwszTypeLibVersion The type library version string.
+ */
+DECLINLINE(bool) vbpsIsTypeLibVersionToRemove(PCRTUTF16 pwszTypeLibVersion)
+{
+ AssertCompile(RT_ELEMENTS(g_apwszTypelibVersions) == 2);
+
+ /* ASSUMES: 1.x version strings and that the input buffer is at least 3 wchars long. */
+ if ( g_apwszTypelibVersions[0][3] == pwszTypeLibVersion[3]
+ && RTUtf16Cmp(g_apwszTypelibVersions[0], pwszTypeLibVersion) == 0)
+ return true;
+ if ( g_apwszTypelibVersions[1][3] == pwszTypeLibVersion[3]
+ && RTUtf16Cmp(g_apwszTypelibVersions[1], pwszTypeLibVersion) == 0)
+ return true;
+
+ return false;
+}
+
+
+/**
+ * Quick check whether the given string looks like a UUID in braces.
+ *
+ * This does not check the whole string, just do a quick sweep.
+ *
+ * @returns true if possible UUID, false if definitely not.
+ * @param pwszUuid Alleged UUID in braces.
+ */
+DECLINLINE(bool) vbpsIsUuidInBracesQuickW(PCRTUTF16 pwszUuid)
+{
+ return pwszUuid[ 0] == '{'
+ && pwszUuid[ 9] == '-'
+ && pwszUuid[14] == '-'
+ && pwszUuid[19] == '-'
+ && pwszUuid[24] == '-'
+ && pwszUuid[37] == '}'
+ && pwszUuid[38] == '\0'
+ && RT_C_IS_XDIGIT(pwszUuid[1]);
+}
+
+
+/**
+ * Compares two UUIDs (in braces).
+ *
+ * @returns true on match, false if no match.
+ * @param pwszUuid1 The first UUID.
+ * @param pwszUuid2 The second UUID.
+ */
+static bool vbpsCompareUuidW(PCRTUTF16 pwszUuid1, PCRTUTF16 pwszUuid2)
+{
+#define COMPARE_EXACT_RET(a_wch1, a_wch2) \
+ if ((a_wch1) == (a_wch2)) { } else return false
+
+#define COMPARE_XDIGITS_RET(a_wch1, a_wch2) \
+ if ((a_wch1) == (a_wch2)) { } \
+ else if (RT_C_TO_UPPER(a_wch1) != RT_C_TO_UPPER(a_wch2) || (a_wch1) >= 127U || (a_wch2) >= 127U) \
+ return false
+ COMPARE_EXACT_RET( pwszUuid1[ 0], pwszUuid2[ 0]); /* { */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 1], pwszUuid2[ 1]); /* 5 */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 2], pwszUuid2[ 2]); /* e */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 3], pwszUuid2[ 3]); /* 5 */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 4], pwszUuid2[ 4]); /* e */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 5], pwszUuid2[ 5]); /* 3 */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 6], pwszUuid2[ 6]); /* 6 */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 7], pwszUuid2[ 7]); /* 4 */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 8], pwszUuid2[ 8]); /* 0 */
+ COMPARE_EXACT_RET( pwszUuid1[ 9], pwszUuid2[ 9]); /* - */
+ COMPARE_XDIGITS_RET(pwszUuid1[10], pwszUuid2[10]); /* 7 */
+ COMPARE_XDIGITS_RET(pwszUuid1[11], pwszUuid2[11]); /* 4 */
+ COMPARE_XDIGITS_RET(pwszUuid1[12], pwszUuid2[12]); /* f */
+ COMPARE_XDIGITS_RET(pwszUuid1[13], pwszUuid2[13]); /* 3 */
+ COMPARE_EXACT_RET( pwszUuid1[14], pwszUuid2[14]); /* - */
+ COMPARE_XDIGITS_RET(pwszUuid1[15], pwszUuid2[15]); /* 4 */
+ COMPARE_XDIGITS_RET(pwszUuid1[16], pwszUuid2[16]); /* 6 */
+ COMPARE_XDIGITS_RET(pwszUuid1[17], pwszUuid2[17]); /* 8 */
+ COMPARE_XDIGITS_RET(pwszUuid1[18], pwszUuid2[18]); /* 9 */
+ COMPARE_EXACT_RET( pwszUuid1[19], pwszUuid2[19]); /* - */
+ COMPARE_XDIGITS_RET(pwszUuid1[20], pwszUuid2[20]); /* 9 */
+ COMPARE_XDIGITS_RET(pwszUuid1[21], pwszUuid2[21]); /* 7 */
+ COMPARE_XDIGITS_RET(pwszUuid1[22], pwszUuid2[22]); /* 9 */
+ COMPARE_XDIGITS_RET(pwszUuid1[23], pwszUuid2[23]); /* f */
+ COMPARE_EXACT_RET( pwszUuid1[24], pwszUuid2[24]); /* - */
+ COMPARE_XDIGITS_RET(pwszUuid1[25], pwszUuid2[25]); /* 6 */
+ COMPARE_XDIGITS_RET(pwszUuid1[26], pwszUuid2[26]); /* b */
+ COMPARE_XDIGITS_RET(pwszUuid1[27], pwszUuid2[27]); /* 1 */
+ COMPARE_XDIGITS_RET(pwszUuid1[28], pwszUuid2[28]); /* b */
+ COMPARE_XDIGITS_RET(pwszUuid1[29], pwszUuid2[29]); /* 8 */
+ COMPARE_XDIGITS_RET(pwszUuid1[30], pwszUuid2[30]); /* d */
+ COMPARE_XDIGITS_RET(pwszUuid1[31], pwszUuid2[31]); /* 7 */
+ COMPARE_XDIGITS_RET(pwszUuid1[32], pwszUuid2[32]); /* 6 */
+ COMPARE_XDIGITS_RET(pwszUuid1[33], pwszUuid2[33]); /* 0 */
+ COMPARE_XDIGITS_RET(pwszUuid1[34], pwszUuid2[34]); /* 9 */
+ COMPARE_XDIGITS_RET(pwszUuid1[35], pwszUuid2[35]); /* a */
+ COMPARE_XDIGITS_RET(pwszUuid1[36], pwszUuid2[36]); /* 5 */
+ COMPARE_EXACT_RET( pwszUuid1[37], pwszUuid2[37]); /* } */
+ COMPARE_EXACT_RET( pwszUuid1[38], pwszUuid2[38]); /* \0 */
+#undef COMPARE_EXACT_RET
+#undef COMPARE_XDIGITS_RET
+ return true;
+}
+
+
+/**
+ * Checks if the type library ID is one of the ones we wish to clean up.
+ *
+ * @returns true if it should be cleaned up, false if not.
+ * @param pwszTypeLibId The type library ID as a bracketed string.
+ */
+DECLINLINE(bool) vbpsIsTypeLibIdToRemove(PRTUTF16 pwszTypeLibId)
+{
+ AssertCompile(RT_ELEMENTS(g_apwszTypeLibIds) == 2);
+#ifdef VBOX_STRICT
+ static bool s_fDoneStrict = false;
+ if (s_fDoneStrict) { }
+ else
+ {
+ Assert(RT_ELEMENTS(g_apwszTypeLibIds) == 2);
+ Assert(g_apwszTypeLibIds[0][0] == '{');
+ Assert(g_apwszTypeLibIds[1][0] == '{');
+ Assert(RT_C_IS_XDIGIT(g_apwszTypeLibIds[0][1]));
+ Assert(RT_C_IS_XDIGIT(g_apwszTypeLibIds[1][1]));
+ Assert(RT_C_IS_UPPER(g_apwszTypeLibIds[0][1]) || RT_C_IS_DIGIT(g_apwszTypeLibIds[0][1]));
+ Assert(RT_C_IS_UPPER(g_apwszTypeLibIds[1][1]) || RT_C_IS_DIGIT(g_apwszTypeLibIds[1][1]));
+ s_fDoneStrict = true;
+ }
+#endif
+
+ /*
+ * Rolled out matching with inlined check of the opening braces
+ * and first two digits.
+ *
+ * ASSUMES input buffer is at least 3 wchars big and uppercased UUID in
+ * our matching array.
+ */
+ if (pwszTypeLibId[0] == '{')
+ {
+ RTUTF16 const wcFirstDigit = RT_C_TO_UPPER(pwszTypeLibId[1]);
+ RTUTF16 const wcSecondDigit = RT_C_TO_UPPER(pwszTypeLibId[2]);
+ PCRTUTF16 pwsz2 = g_apwszTypeLibIds[0];
+ if ( wcFirstDigit == pwsz2[1]
+ && wcSecondDigit == pwsz2[2]
+ && vbpsCompareUuidW(pwszTypeLibId, pwsz2))
+ return true;
+ pwsz2 = g_apwszTypeLibIds[1];
+ if ( wcFirstDigit == pwsz2[1]
+ && wcSecondDigit == pwsz2[2]
+ && vbpsCompareUuidW(pwszTypeLibId, pwsz2))
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Checks if the proxy stub class ID is one of the ones we wish to clean up.
+ *
+ * @returns true if it should be cleaned up, false if not.
+ * @param pwszProxyStubId The proxy stub class ID.
+ */
+DECLINLINE(bool) vbpsIsProxyStubClsIdToRemove(PRTUTF16 pwszProxyStubId)
+{
+ AssertCompile(RT_ELEMENTS(g_apwszProxyStubClsIds) == 2);
+#ifdef VBOX_STRICT
+ static bool s_fDoneStrict = false;
+ if (s_fDoneStrict) { }
+ else
+ {
+ Assert(RT_ELEMENTS(g_apwszProxyStubClsIds) == 2);
+ Assert(g_apwszProxyStubClsIds[0][0] == '{');
+ Assert(g_apwszProxyStubClsIds[1][0] == '{');
+ Assert(RT_C_IS_XDIGIT(g_apwszProxyStubClsIds[0][1]));
+ Assert(RT_C_IS_XDIGIT(g_apwszProxyStubClsIds[1][1]));
+ Assert(RT_C_IS_UPPER(g_apwszProxyStubClsIds[0][1]) || RT_C_IS_DIGIT(g_apwszProxyStubClsIds[0][1]));
+ Assert(RT_C_IS_UPPER(g_apwszProxyStubClsIds[1][1]) || RT_C_IS_DIGIT(g_apwszProxyStubClsIds[1][1]));
+ s_fDoneStrict = true;
+ }
+#endif
+
+ /*
+ * Rolled out matching with inlined check of the opening braces
+ * and first two digits.
+ *
+ * ASSUMES input buffer is at least 3 wchars big and uppercased UUID in
+ * our matching array.
+ */
+ if (pwszProxyStubId[0] == '{')
+ {
+ RTUTF16 const wcFirstDigit = RT_C_TO_UPPER(pwszProxyStubId[1]);
+ RTUTF16 const wcSecondDigit = RT_C_TO_UPPER(pwszProxyStubId[2]);
+ PCRTUTF16 pwsz2 = g_apwszProxyStubClsIds[0];
+ if ( wcFirstDigit == pwsz2[1]
+ && wcSecondDigit == pwsz2[2]
+ && vbpsCompareUuidW(pwszProxyStubId, pwsz2))
+ return true;
+ pwsz2 = g_apwszProxyStubClsIds[1];
+ if ( wcFirstDigit == pwsz2[1]
+ && wcSecondDigit == pwsz2[2]
+ && vbpsCompareUuidW(pwszProxyStubId, pwsz2))
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Hack to clean out the interfaces belonging to obsolete typelibs on
+ * development boxes and such likes.
+ */
+static void vbpsRemoveOldInterfaces(VBPSREGSTATE *pState)
+{
+ unsigned iAlt = pState->cAltDeletes;
+ while (iAlt-- > 0)
+ {
+ /*
+ * Open the interface root key. Not using the vbpsRegOpenInterfaceKeys feature
+ * here in case it messes things up by keeping the special HKEY_CLASSES_ROOT key
+ * open with possibly pending deletes in parent views or other weird stuff.
+ */
+ HKEY hkeyInterfaces;
+ LRESULT rc = RegOpenKeyExW(pState->aAltDeletes[iAlt].hkeyClasses, L"Interface",
+ 0 /*fOptions*/, pState->fSamDelete, &hkeyInterfaces);
+ if (rc == ERROR_SUCCESS)
+ {
+ /*
+ * This is kind of expensive, but we have to check all registered interfaces.
+ * Only use wide APIs to avoid wasting time on string conversion.
+ */
+ DWORD idxKey;
+ for (idxKey = 0;; idxKey++)
+ {
+ RTUTF16 wszCurNm[128 + 48];
+ DWORD cwcCurNm = 128;
+ rc = RegEnumKeyExW(hkeyInterfaces, idxKey, wszCurNm, &cwcCurNm,
+ NULL /*pdwReserved*/, NULL /*pwszClass*/, NULL /*pcwcClass*/, NULL /*pLastWriteTime*/);
+ if (rc == ERROR_SUCCESS)
+ {
+ /*
+ * We match the interface by type library ID or proxy stub class ID.
+ *
+ * We have to check the proxy ID last, as it is almost always there
+ * and we can safely skip it if there is a mismatching type lib
+ * associated with the interface.
+ */
+ static RTUTF16 const s_wszTypeLib[] = L"\\TypeLib";
+ bool fDeleteMe = false;
+ HKEY hkeySub;
+ RTUTF16 wszValue[128];
+ DWORD cbValue;
+ DWORD dwType;
+
+ /* Skip this entry if it doesn't look like a braced UUID. */
+ wszCurNm[cwcCurNm] = '\0'; /* paranoia */
+ if (vbpsIsUuidInBracesQuickW(wszCurNm)) { }
+ else continue;
+
+ /* Try the TypeLib sub-key. */
+ memcpy(&wszCurNm[cwcCurNm], s_wszTypeLib, sizeof(s_wszTypeLib));
+ rc = RegOpenKeyExW(hkeyInterfaces, wszCurNm, 0 /*fOptions*/, KEY_QUERY_VALUE, &hkeySub);
+ if (rc == ERROR_SUCCESS)
+ {
+ cbValue = sizeof(wszValue) - sizeof(RTUTF16);
+ rc = RegQueryValueExW(hkeySub, NULL /*pszValueNm*/, NULL /*pdwReserved*/,
+ &dwType, (PBYTE)&wszValue[0], &cbValue);
+ if (rc != ERROR_SUCCESS || dwType != REG_SZ)
+ cbValue = 0;
+ wszValue[cbValue / sizeof(RTUTF16)] = '\0';
+
+ if ( rc == ERROR_SUCCESS
+ && vbpsIsTypeLibIdToRemove(wszValue))
+ {
+ /* Check the TypeLib/Version value to make sure. */
+ cbValue = sizeof(wszValue) - sizeof(RTUTF16);
+ rc = RegQueryValueExW(hkeySub, L"Version", 0 /*pdwReserved*/, &dwType, (PBYTE)&wszValue[0], &cbValue);
+ if (rc != ERROR_SUCCESS)
+ cbValue = 0;
+ wszValue[cbValue] = '\0';
+
+ if ( rc == ERROR_SUCCESS
+ && vbpsIsTypeLibVersionToRemove(wszValue))
+ fDeleteMe = true;
+ }
+ vbpsCloseKey(pState, hkeySub, __LINE__);
+ }
+ else if (rc == ERROR_FILE_NOT_FOUND)
+ {
+ /* No TypeLib, try the ProxyStubClsid32 sub-key next. */
+ static RTUTF16 const s_wszProxyStubClsid32[] = L"\\ProxyStubClsid32";
+ memcpy(&wszCurNm[cwcCurNm], s_wszProxyStubClsid32, sizeof(s_wszProxyStubClsid32));
+ rc = RegOpenKeyExW(hkeyInterfaces, wszCurNm, 0 /*fOptions*/, KEY_QUERY_VALUE, &hkeySub);
+ if (rc == ERROR_SUCCESS)
+ {
+ cbValue = sizeof(wszValue) - sizeof(RTUTF16);
+ rc = RegQueryValueExW(hkeySub, NULL /*pszValueNm*/, NULL /*pdwReserved*/,
+ &dwType, (PBYTE)&wszValue[0], &cbValue);
+ if (rc != ERROR_SUCCESS || dwType != REG_SZ)
+ cbValue = 0;
+ wszValue[cbValue / sizeof(RTUTF16)] = '\0';
+
+ if ( rc == ERROR_SUCCESS
+ && vbpsIsProxyStubClsIdToRemove(wszValue))
+ fDeleteMe = true;
+
+ vbpsCloseKey(pState, hkeySub, __LINE__);
+ }
+ }
+
+ if (fDeleteMe)
+ {
+ /*
+ * Ok, it's an orphaned VirtualBox interface. Delete it.
+ */
+ wszCurNm[cwcCurNm] = '\0';
+ vbpsDeleteKeyRecursiveW(pState, hkeyInterfaces, wszCurNm, __LINE__);
+ }
+ }
+ else
+ {
+ Assert(rc == ERROR_NO_MORE_ITEMS);
+ break;
+ }
+ }
+
+ vbpsCloseKey(pState, hkeyInterfaces, __LINE__);
+ }
+ }
+}
+
+
+/**
+ * Hack to clean out the class IDs belonging to obsolete typelibs on development
+ * boxes and such likes.
+ */
+static void vbpsRemoveOldClassIDs(VBPSREGSTATE *pState)
+{
+ unsigned iAlt = pState->cAltDeletes;
+ while (iAlt-- > 0)
+ {
+ /*
+ * Open the CLSID key if it exists.
+ * We don't use the hKeyClsid member for the same paranoid reasons as
+ * already stated in vbpsRemoveOldInterfaces.
+ */
+ HKEY hkeyClsIds;
+ LRESULT rc;
+ rc = RegOpenKeyExW(pState->aAltDeletes[iAlt].hkeyClasses, L"CLSID", 0 /*fOptions*/, pState->fSamDelete, &hkeyClsIds);
+ if (rc == ERROR_SUCCESS)
+ {
+ /*
+ * This is kind of expensive, but we have to check all registered interfaces.
+ * Only use wide APIs to avoid wasting time on string conversion.
+ */
+ DWORD idxKey;
+ for (idxKey = 0;; idxKey++)
+ {
+ RTUTF16 wszCurNm[128 + 48];
+ DWORD cwcCurNm = 128;
+ rc = RegEnumKeyExW(hkeyClsIds, idxKey, wszCurNm, &cwcCurNm,
+ NULL /*pdwReserved*/, NULL /*pwszClass*/, NULL /*pcwcClass*/, NULL /*pLastWriteTime*/);
+ if (rc == ERROR_SUCCESS)
+ {
+ /*
+ * Match both the type library ID and the program ID.
+ */
+ static RTUTF16 const s_wszTypeLib[] = L"\\TypeLib";
+ HKEY hkeySub;
+ RTUTF16 wszValue[128];
+ DWORD cbValue;
+ DWORD dwType;
+
+
+ /* Skip this entry if it doesn't look like a braced UUID. (Microsoft
+ has one two malformed ones plus a hack.) */
+ wszCurNm[cwcCurNm] = '\0'; /* paranoia */
+ if (vbpsIsUuidInBracesQuickW(wszCurNm)) { }
+ else continue;
+
+ /* The TypeLib sub-key. */
+ memcpy(&wszCurNm[cwcCurNm], s_wszTypeLib, sizeof(s_wszTypeLib));
+ rc = RegOpenKeyExW(hkeyClsIds, wszCurNm, 0 /*fOptions*/, KEY_QUERY_VALUE, &hkeySub);
+ if (rc == ERROR_SUCCESS)
+ {
+ bool fDeleteMe = false;
+
+ cbValue = sizeof(wszValue) - sizeof(RTUTF16);
+ rc = RegQueryValueExW(hkeySub, NULL /*pszValueNm*/, NULL /*pdwReserved*/,
+ &dwType, (PBYTE)&wszValue[0], &cbValue);
+ if (rc != ERROR_SUCCESS || dwType != REG_SZ)
+ cbValue = 0;
+ wszValue[cbValue / sizeof(RTUTF16)] = '\0';
+
+ if ( rc == ERROR_SUCCESS
+ && vbpsIsTypeLibIdToRemove(wszValue))
+ fDeleteMe = true;
+
+ vbpsCloseKey(pState, hkeySub, __LINE__);
+
+ if (fDeleteMe)
+ {
+ /* The ProgId sub-key. */
+ static RTUTF16 const s_wszProgId[] = L"\\ProgId";
+ memcpy(&wszCurNm[cwcCurNm], s_wszProgId, sizeof(s_wszProgId));
+ rc = RegOpenKeyExW(hkeyClsIds, wszCurNm, 0 /*fOptions*/, KEY_QUERY_VALUE, &hkeySub);
+ if (rc == ERROR_SUCCESS)
+ {
+ static RTUTF16 const s_wszProgIdPrefix[] = L"VirtualBox.";
+
+ cbValue = sizeof(wszValue) - sizeof(RTUTF16);
+ rc = RegQueryValueExW(hkeySub, NULL /*pszValueNm*/, NULL /*pdwReserved*/,
+ &dwType, (PBYTE)&wszValue[0], &cbValue);
+ if (rc != ERROR_SUCCESS || dwType != REG_SZ)
+ cbValue = 0;
+ wszValue[cbValue / sizeof(RTUTF16)] = '\0';
+
+ if ( cbValue < sizeof(s_wszProgIdPrefix)
+ || memcmp(wszValue, s_wszProgIdPrefix, sizeof(s_wszProgIdPrefix) - sizeof(RTUTF16)) != 0)
+ fDeleteMe = false;
+
+ vbpsCloseKey(pState, hkeySub, __LINE__);
+ }
+ else
+ AssertStmt(rc == ERROR_FILE_NOT_FOUND, fDeleteMe = false);
+
+ if (fDeleteMe)
+ {
+ /*
+ * Ok, it's an orphaned VirtualBox interface. Delete it.
+ */
+ wszCurNm[cwcCurNm] = '\0';
+ vbpsDeleteKeyRecursiveW(pState, hkeyClsIds, wszCurNm, __LINE__);
+ }
+ }
+ }
+ else
+ Assert(rc == ERROR_FILE_NOT_FOUND);
+ }
+ else
+ {
+ Assert(rc == ERROR_NO_MORE_ITEMS);
+ break;
+ }
+ }
+
+ vbpsCloseKey(pState, hkeyClsIds, __LINE__);
+ }
+ else
+ Assert(rc == ERROR_FILE_NOT_FOUND);
+ }
+}
+
+
+/**
+ * Hack to clean obsolete typelibs on development boxes and such.
+ */
+static void vbpsRemoveOldTypeLibs(VBPSREGSTATE *pState)
+{
+ unsigned iAlt = pState->cAltDeletes;
+ while (iAlt-- > 0)
+ {
+ /*
+ * Open the TypeLib key, if it exists.
+ */
+ HKEY hkeyTypeLibs;
+ LSTATUS rc;
+ rc = RegOpenKeyExW(pState->aAltDeletes[iAlt].hkeyClasses, L"TypeLib", 0 /*fOptions*/, pState->fSamDelete, &hkeyTypeLibs);
+ if (rc == ERROR_SUCCESS)
+ {
+ /*
+ * Look for our type library IDs.
+ */
+ unsigned iTlb = RT_ELEMENTS(g_apwszTypeLibIds);
+ while (iTlb-- > 0)
+ {
+ HKEY hkeyTypeLibId;
+ rc = RegOpenKeyExW(hkeyTypeLibs, g_apwszTypeLibIds[iTlb], 0 /*fOptions*/, pState->fSamDelete, &hkeyTypeLibId);
+ if (rc == ERROR_SUCCESS)
+ {
+ unsigned iVer = RT_ELEMENTS(g_apwszTypelibVersions);
+ while (iVer-- > 0)
+ {
+ HKEY hkeyVer;
+ rc = RegOpenKeyExW(hkeyTypeLibId, g_apwszTypelibVersions[iVer], 0, KEY_READ, &hkeyVer);
+ if (rc == ERROR_SUCCESS)
+ {
+ char szValue[128];
+ DWORD cbValue = sizeof(szValue) - 1;
+ rc = RegQueryValueExA(hkeyVer, NULL, NULL, NULL, (PBYTE)&szValue[0], &cbValue);
+ vbpsCloseKey(pState, hkeyVer, __LINE__);
+ if (rc == ERROR_SUCCESS)
+ {
+ szValue[cbValue] = '\0';
+ if (!strcmp(szValue, "VirtualBox Type Library"))
+ {
+ /*
+ * Delete the type library version.
+ * We do not delete the whole type library ID, just this version of it.
+ */
+ vbpsDeleteKeyRecursiveW(pState, hkeyTypeLibId, g_apwszTypelibVersions[iVer], __LINE__);
+ }
+ }
+ }
+ }
+ vbpsCloseKey(pState, hkeyTypeLibId, __LINE__);
+
+ /*
+ * The type library ID key should be empty now, so we can try remove it (non-recursively).
+ */
+ rc = RegDeleteKeyW(hkeyTypeLibs, g_apwszTypeLibIds[iTlb]);
+ Assert(rc == ERROR_SUCCESS);
+ }
+ }
+ }
+ else
+ Assert(rc == ERROR_FILE_NOT_FOUND);
+ }
+}
+
+
+/**
+ * Hack to clean out obsolete typelibs on development boxes and such.
+ */
+static void vbpsRemoveOldMessSub(REGSAM fSamWow)
+{
+ /*
+ * Note! The worker procedures does not use the default destination,
+ * because it's much much simpler to enumerate alternative locations.
+ */
+ VBPSREGSTATE State;
+ LRESULT rc = vbpsRegInit(&State, HKEY_CLASSES_ROOT, NULL, true /*fDelete*/, false /*fUpdate*/, fSamWow);
+ if (rc == ERROR_SUCCESS)
+ {
+ vbpsRegAddAltDelete(&State, HKEY_CURRENT_USER, "Software\\Classes");
+ vbpsRegAddAltDelete(&State, HKEY_LOCAL_MACHINE, "Software\\Classes");
+ vbpsRegAddAltDelete(&State, HKEY_CLASSES_ROOT, NULL);
+
+ vbpsRemoveOldInterfaces(&State);
+ vbpsRemoveOldClassIDs(&State);
+ vbpsRemoveOldTypeLibs(&State);
+ }
+ vbpsRegTerm(&State);
+}
+
+
+/**
+ * Hack to clean out obsolete typelibs on development boxes and such.
+ */
+static void removeOldMess(void)
+{
+ vbpsRemoveOldMessSub(0 /*fSamWow*/);
+#if ARCH_BITS == 64 || defined(VBOX_IN_32_ON_64_MAIN_API)
+ vbpsRemoveOldMessSub(KEY_WOW64_32KEY);
+#endif
+}
+
+
+
+/**
+ * Register the interfaces proxied by this DLL, and to avoid duplication and
+ * minimize work the VBox type library, classes and servers are also registered.
+ *
+ * This is normally only used by developers via comregister.cmd and the heat.exe
+ * tool during MSI creation. The only situation where users may end up here is
+ * if they're playing around or we recommend it as a solution to COM problems.
+ * So, no problem if this approach is less gentle, though we leave the cleaning
+ * up of orphaned interfaces to DllUnregisterServer.
+ *
+ * @returns COM status code.
+ */
+HRESULT STDAPICALLTYPE DllRegisterServer(void)
+{
+ HRESULT hrc;
+
+ /*
+ * Register the type library first.
+ */
+ ITypeLib *pITypeLib;
+ WCHAR wszDllName[MAX_PATH];
+ DWORD cwcRet = GetModuleFileNameW(g_hDllSelf, wszDllName, RT_ELEMENTS(wszDllName));
+ AssertReturn(cwcRet > 0 && cwcRet < RT_ELEMENTS(wszDllName), CO_E_PATHTOOLONG);
+
+ hrc = LoadTypeLib(wszDllName, &pITypeLib);
+ AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), hrc);
+ hrc = RegisterTypeLib(pITypeLib, wszDllName, NULL /*pszHelpDir*/);
+ pITypeLib->lpVtbl->Release(pITypeLib);
+ AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), hrc);
+
+ /*
+ * Register proxy stub.
+ */
+ hrc = NdrDllRegisterProxy(g_hDllSelf, &g_apProxyFiles[0], &g_ProxyClsId); /* see DLLREGISTRY_ROUTINES in RpcProxy.h */
+ AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), hrc);
+
+ /*
+ * Register the VBox modules and classes.
+ */
+ vbpsDllPathToVBoxDir(wszDllName);
+ hrc = RegisterXidlModulesAndClasses(wszDllName, true /*fDelete*/, true /*fUpdate*/);
+ AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), hrc);
+
+ return S_OK;
+}
+
+
+/**
+ * Reverse of DllRegisterServer.
+ *
+ * This is normally only used by developers via comregister.cmd. Users may be
+ * asked to perform it in order to fix some COM issue. So, it's OK if we spend
+ * some extra time and clean up orphaned interfaces, because developer boxes
+ * will end up with a bunch of those as interface UUIDs changes.
+ *
+ * @returns COM status code.
+ */
+HRESULT STDAPICALLTYPE DllUnregisterServer(void)
+{
+ HRESULT hrc = S_OK;
+ HRESULT hrc2;
+
+ /*
+ * Unregister the type library.
+ *
+ * We ignore TYPE_E_REGISTRYACCESS as that is what is returned if the
+ * type lib hasn't been registered (W10).
+ */
+ hrc2 = UnRegisterTypeLib(&LIBID_VirtualBox, kTypeLibraryMajorVersion, kTypeLibraryMinorVersion,
+ 0 /*LCid*/, RT_CONCAT(SYS_WIN, ARCH_BITS));
+ AssertMsgStmt(SUCCEEDED(hrc2) || hrc2 == TYPE_E_REGISTRYACCESS, ("%Rhrc\n", hrc2), if (SUCCEEDED(hrc)) hrc = hrc2);
+
+ /*
+ * Unregister the proxy stub.
+ *
+ * We ignore ERROR_FILE_NOT_FOUND as that is returned if not registered (W10).
+ */
+ hrc2 = NdrDllUnregisterProxy(g_hDllSelf, &g_apProxyFiles[0], &g_ProxyClsId); /* see DLLREGISTRY_ROUTINES in RpcProxy.h */
+ AssertMsgStmt( SUCCEEDED(hrc2)
+ || hrc2 == MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_FILE_NOT_FOUND)
+ || hrc2 == REGDB_E_INVALIDVALUE,
+ ("%Rhrc\n", hrc2), if (SUCCEEDED(hrc)) hrc = hrc2);
+
+ /*
+ * Register the VBox modules and classes.
+ */
+ hrc2 = RegisterXidlModulesAndClasses(NULL, true /*fDelete*/, false /*fUpdate*/);
+ AssertMsgStmt(SUCCEEDED(hrc2), ("%Rhrc\n", hrc2), if (SUCCEEDED(hrc)) hrc = hrc2);
+
+ /*
+ * Purge old mess.
+ */
+ removeOldMess();
+
+ return hrc;
+}
+
+
+#ifdef VBOX_WITH_SDS
+/**
+ * Update a SCM service.
+ *
+ * @param pState The state.
+ * @param pwszVBoxDir The VirtualBox install directory (unicode),
+ * trailing slash.
+ * @param pwszModule The service module.
+ * @param pwszServiceName The service name.
+ * @param pwszDisplayName The service display name.
+ * @param pwszDescription The service description.
+ */
+static void vbpsUpdateWindowsService(VBPSREGSTATE *pState, const WCHAR *pwszVBoxDir, const WCHAR *pwszModule,
+ const WCHAR *pwszServiceName, const WCHAR *pwszDisplayName, const WCHAR *pwszDescription)
+{
+ SC_HANDLE hSCM;
+
+ /* Configuration options that are currently standard. */
+ uint32_t const uServiceType = SERVICE_WIN32_OWN_PROCESS;
+ uint32_t const uStartType = SERVICE_DEMAND_START;
+ uint32_t const uErrorControl = SERVICE_ERROR_NORMAL;
+ WCHAR const * const pwszServiceStartName = L"LocalSystem";
+ static WCHAR const wszzDependencies[] = L"RPCSS\0";
+
+ /*
+ * Make double quoted executable file path. ASSUMES pwszVBoxDir ends with a slash!
+ */
+ WCHAR wszFilePath[MAX_PATH + 2];
+ int rc = RTUtf16CopyAscii(wszFilePath, RT_ELEMENTS(wszFilePath), "\"");
+ if (RT_SUCCESS(rc))
+ rc = RTUtf16Cat(wszFilePath, RT_ELEMENTS(wszFilePath), pwszVBoxDir);
+ if (RT_SUCCESS(rc))
+ rc = RTUtf16Cat(wszFilePath, RT_ELEMENTS(wszFilePath), pwszModule);
+ if (RT_SUCCESS(rc))
+ rc = RTUtf16CatAscii(wszFilePath, RT_ELEMENTS(wszFilePath), "\"");
+ AssertLogRelRCReturnVoid(rc);
+
+ /*
+ * Open the service manager for the purpose of checking the configuration.
+ */
+ hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
+ if (hSCM != NULL)
+ {
+ union
+ {
+ QUERY_SERVICE_CONFIGW Config;
+ SERVICE_STATUS Status;
+ SERVICE_DESCRIPTIONW Desc;
+ uint8_t abPadding[sizeof(QUERY_SERVICE_CONFIGW) + 5 * _1K];
+ } uBuf;
+ SC_HANDLE hService;
+ bool fCreateIt = pState->fUpdate;
+ bool fDeleteIt = true;
+
+ /*
+ * Step #1: Open the service and validate the configuration.
+ */
+ if (pState->fUpdate)
+ {
+ hService = OpenServiceW(hSCM, pwszServiceName, SERVICE_QUERY_CONFIG);
+ if (hService != NULL)
+ {
+ DWORD cbNeeded = 0;
+ if (QueryServiceConfigW(hService, &uBuf.Config, sizeof(uBuf), &cbNeeded))
+ {
+ if (uBuf.Config.dwErrorControl)
+ {
+ uint32_t cErrors = 0;
+ if (uBuf.Config.dwServiceType != uServiceType)
+ {
+ LogRel(("update service '%ls': dwServiceType %u, expected %u\n",
+ pwszServiceName, uBuf.Config.dwServiceType, uServiceType));
+ cErrors++;
+ }
+ if (uBuf.Config.dwStartType != uStartType)
+ {
+ LogRel(("update service '%ls': dwStartType %u, expected %u\n",
+ pwszServiceName, uBuf.Config.dwStartType, uStartType));
+ cErrors++;
+ }
+ if (uBuf.Config.dwErrorControl != uErrorControl)
+ {
+ LogRel(("update service '%ls': dwErrorControl %u, expected %u\n",
+ pwszServiceName, uBuf.Config.dwErrorControl, uErrorControl));
+ cErrors++;
+ }
+ if (RTUtf16ICmp(uBuf.Config.lpBinaryPathName, wszFilePath) != 0)
+ {
+ LogRel(("update service '%ls': lpBinaryPathName '%ls', expected '%ls'\n",
+ pwszServiceName, uBuf.Config.lpBinaryPathName, wszFilePath));
+ cErrors++;
+ }
+ if ( uBuf.Config.lpServiceStartName != NULL
+ && *uBuf.Config.lpServiceStartName != L'\0'
+ && RTUtf16ICmp(uBuf.Config.lpServiceStartName, pwszServiceStartName) != 0)
+ {
+ LogRel(("update service '%ls': lpServiceStartName '%ls', expected '%ls'\n",
+ pwszServiceName, uBuf.Config.lpBinaryPathName, pwszServiceStartName));
+ cErrors++;
+ }
+
+ fDeleteIt = fCreateIt = cErrors > 0;
+ }
+ }
+ else
+ AssertLogRelMsgFailed(("QueryServiceConfigW returned %u (cbNeeded=%u vs %zu)\n",
+ GetLastError(), cbNeeded, sizeof(uBuf)));
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ fDeleteIt = dwErr != ERROR_SERVICE_DOES_NOT_EXIST;
+ AssertLogRelMsg(dwErr == ERROR_SERVICE_DOES_NOT_EXIST, ("OpenServiceW('%ls') -> %u\n", pwszServiceName, dwErr));
+ }
+ CloseServiceHandle(hService);
+ }
+
+ /*
+ * Step #2: Stop and delete the service if needed.
+ * We can do this without reopening the service manager.
+ */
+ if (fDeleteIt)
+ {
+ hService = OpenServiceW(hSCM, pwszServiceName, SERVICE_STOP | DELETE);
+ if (hService)
+ {
+ BOOL fRet;
+ DWORD dwErr;
+ RT_ZERO(uBuf.Status);
+ SetLastError(ERROR_SERVICE_NOT_ACTIVE);
+ fRet = ControlService(hService, SERVICE_CONTROL_STOP, &uBuf.Status);
+ dwErr = GetLastError();
+ if ( fRet
+ || dwErr == ERROR_SERVICE_NOT_ACTIVE
+ || ( dwErr == ERROR_SERVICE_CANNOT_ACCEPT_CTRL
+ && uBuf.Status.dwCurrentState == SERVICE_STOP_PENDING) )
+ {
+ if (DeleteService(hService))
+ LogRel(("update service '%ls': deleted\n", pwszServiceName));
+ else
+ AssertLogRelMsgFailed(("Failed to not delete service %ls: %u\n", pwszServiceName, GetLastError()));
+ }
+ else
+ AssertMsg(dwErr == ERROR_ACCESS_DENIED,
+ ("Failed to stop service %ls: %u (state=%u)\n", pwszServiceName, dwErr, uBuf.Status.dwCurrentState));
+ CloseServiceHandle(hService);
+ }
+ else
+ {
+ pState->rc = GetLastError();
+ LogRel(("Failed to not open service %ls for stop+delete: %u\n", pwszServiceName, pState->rc));
+ hService = OpenServiceW(hSCM, pwszServiceName, SERVICE_CHANGE_CONFIG);
+ }
+ CloseServiceHandle(hService);
+ }
+
+ CloseServiceHandle(hSCM);
+
+ /*
+ * Step #3: Create the service (if requested).
+ * Need to have the SC_MANAGER_CREATE_SERVICE access right for this.
+ */
+ if (fCreateIt)
+ {
+ Assert(pState->fUpdate);
+ hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
+ if (hSCM)
+ {
+ hService = CreateServiceW(hSCM,
+ pwszServiceName,
+ pwszDisplayName,
+ SERVICE_CHANGE_CONFIG /* dwDesiredAccess */,
+ uServiceType,
+ uStartType,
+ uErrorControl,
+ wszFilePath,
+ NULL /* pwszLoadOrderGroup */,
+ NULL /* pdwTagId */,
+ wszzDependencies,
+ NULL /* pwszServiceStartName */,
+ NULL /* pwszPassword */);
+ if (hService != NULL)
+ {
+ uBuf.Desc.lpDescription = (WCHAR *)pwszDescription;
+ if (ChangeServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, &uBuf.Desc))
+ LogRel(("update service '%ls': created\n", pwszServiceName));
+ else
+ AssertMsgFailed(("Failed to set service description for %ls: %u\n", pwszServiceName, GetLastError()));
+ CloseServiceHandle(hService);
+ }
+ else
+ {
+ pState->rc = GetLastError();
+ AssertMsgFailed(("Failed to create service '%ls': %u\n", pwszServiceName, pState->rc));
+ }
+ CloseServiceHandle(hSCM);
+ }
+ else
+ {
+ pState->rc = GetLastError();
+ LogRel(("Failed to open service manager with create service access: %u\n", pState->rc));
+ }
+ }
+ }
+ else
+ AssertLogRelMsgFailed(("OpenSCManagerW failed: %u\n", GetLastError()));
+}
+#endif /* VBOX_WITH_SDS */
+
+
+
+/**
+ * Gently update the COM registrations for VirtualBox.
+ *
+ * API that com::Initialize (VBoxCOM/initterm.cpp) calls the first time COM is
+ * initialized in a process. ASSUMES that the caller has initialized IPRT.
+ *
+ * @returns Windows error code.
+ */
+DECLEXPORT(uint32_t) VbpsUpdateRegistrations(void)
+{
+ LSTATUS rc;
+ VBPSREGSTATE State;
+#ifdef VBOX_IN_32_ON_64_MAIN_API
+ bool const fIs32On64 = true;
+#else
+ bool const fIs32On64 = false;
+#endif
+
+ /** @todo Should probably skip this when VBoxSVC is already running... Use
+ * some mutex or something for checking. */
+
+ /*
+ * Find the VirtualBox application directory first.
+ */
+ WCHAR wszVBoxDir[MAX_PATH];
+ DWORD cwcRet = GetModuleFileNameW(g_hDllSelf, wszVBoxDir, RT_ELEMENTS(wszVBoxDir));
+ AssertReturn(cwcRet > 0 && cwcRet < RT_ELEMENTS(wszVBoxDir), ERROR_BUFFER_OVERFLOW);
+ vbpsDllPathToVBoxDir(wszVBoxDir);
+
+ /*
+ * Update registry entries for the current CPU bitness.
+ */
+ rc = vbpsRegInit(&State, HKEY_CLASSES_ROOT, NULL, false /*fDelete*/, true /*fUpdate*/, 0);
+ if (rc == ERROR_SUCCESS && !vbpsIsUpToDate(&State))
+ {
+
+#ifdef VBOX_WITH_SDS
+ vbpsUpdateWindowsService(&State, wszVBoxDir, L"VBoxSDS.exe", L"VBoxSDS",
+ L"VirtualBox system service", L"Used as a COM server for VirtualBox API.");
+#endif
+ vbpsUpdateTypeLibRegistration(&State, wszVBoxDir, fIs32On64);
+ vbpsUpdateProxyStubRegistration(&State, wszVBoxDir, fIs32On64);
+ vbpsUpdateInterfaceRegistrations(&State);
+ RegisterXidlModulesAndClassesGenerated(&State, wszVBoxDir, fIs32On64);
+ vbpsMarkUpToDate(&State);
+ rc = State.rc;
+ }
+ vbpsRegTerm(&State);
+
+
+#if (ARCH_BITS == 64 && defined(VBOX_WITH_32_ON_64_MAIN_API)) /*|| defined(VBOX_IN_32_ON_64_MAIN_API) ??*/
+ /*
+ * Update registry entries for the other CPU bitness.
+ */
+ if (rc == ERROR_SUCCESS)
+ {
+ rc = vbpsRegInit(&State, HKEY_CLASSES_ROOT, NULL, false /*fDelete*/, true /*fUpdate*/,
+ !fIs32On64 ? KEY_WOW64_32KEY : KEY_WOW64_64KEY);
+ if (rc == ERROR_SUCCESS && !vbpsIsUpToDate(&State))
+ {
+ vbpsUpdateTypeLibRegistration(&State, wszVBoxDir, !fIs32On64);
+ vbpsUpdateProxyStubRegistration(&State, wszVBoxDir, !fIs32On64);
+ vbpsUpdateInterfaceRegistrations(&State);
+ RegisterXidlModulesAndClassesGenerated(&State, wszVBoxDir, !fIs32On64);
+ vbpsMarkUpToDate(&State);
+ rc = State.rc;
+ }
+ vbpsRegTerm(&State);
+ }
+#endif
+
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/Main/src-all/win/VBoxProxyStub.def b/src/VBox/Main/src-all/win/VBoxProxyStub.def
new file mode 100644
index 00000000..f21ac77d
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VBoxProxyStub.def
@@ -0,0 +1,36 @@
+; $Id: VBoxProxyStub.def $
+;; @file
+; VBoxProxyStub DLL Definition File.
+;
+
+;
+; Copyright (C) 2006-2022 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; SPDX-License-Identifier: GPL-3.0-only
+;
+
+LIBRARY VBoxProxyStub.dll
+
+EXPORTS
+ DllGetClassObject PRIVATE
+ DllCanUnloadNow PRIVATE
+ DllRegisterServer PRIVATE
+ DllUnregisterServer PRIVATE
+ GetProxyDllInfo PRIVATE
+
diff --git a/src/VBox/Main/src-all/win/VBoxProxyStub.rc b/src/VBox/Main/src-all/win/VBoxProxyStub.rc
new file mode 100644
index 00000000..81e0d5da
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VBoxProxyStub.rc
@@ -0,0 +1,34 @@
+/* $Id: VBoxProxyStub.rc $ */
+/** @file
+ * VBoxProxyStub - Resource file containing version info, icon and typelib.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define IN_FILE_DESCRIPTION "VirtualBox COM Proxy Stub and Typelib"
+#define IN_FILE_BASENAME "VBoxProxyStub"
+
+#include "../../../Artwork/win/TemplateDll.rc"
+
+1 TYPELIB "VirtualBox.tlb"
+
diff --git a/src/VBox/Main/src-all/win/VBoxProxyStubLegacy.rc b/src/VBox/Main/src-all/win/VBoxProxyStubLegacy.rc
new file mode 100644
index 00000000..34815f80
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VBoxProxyStubLegacy.rc
@@ -0,0 +1,34 @@
+/* $Id: VBoxProxyStubLegacy.rc $ */
+/** @file
+ * VBoxProxyStub - Resource file containing version info, icon and typelib.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define IN_FILE_DESCRIPTION "VirtualBox COM Proxy Stub and Type Library (pre Windows 7)"
+#define IN_FILE_BASENAME "VBoxProxyStubLegacy"
+
+#include "../../../Artwork/win/TemplateDll.rc"
+
+1 TYPELIB "VirtualBox.tlb"
+
diff --git a/src/VBox/Main/src-all/win/VirtualBox_rgs.xsl b/src/VBox/Main/src-all/win/VirtualBox_rgs.xsl
new file mode 100644
index 00000000..96b95f41
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VirtualBox_rgs.xsl
@@ -0,0 +1,196 @@
+<?xml version="1.0"?>
+
+<!--
+ * A template to generate a RGS resource script that contains
+ * registry definitions necessary to properly register
+ * VirtualBox Main API COM components.
+-->
+<!--
+ Copyright (C) 2007-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+<xsl:output method="text"/>
+
+<xsl:strip-space elements="*"/>
+
+<!--
+// parameters
+/////////////////////////////////////////////////////////////////////////////
+-->
+
+<!-- Name of the application to generate the RGS script for -->
+<xsl:param name="Application"/>
+<!-- Name of the module to generate the RGS script for -->
+<xsl:param name="Module"/>
+
+
+<!--
+// templates
+/////////////////////////////////////////////////////////////////////////////
+-->
+
+<!--
+ * header
+-->
+<xsl:template match="/idl">
+ <xsl:apply-templates/>
+</xsl:template>
+
+
+<!--
+ * libraries
+-->
+<xsl:template match="library">
+ <xsl:apply-templates/>
+</xsl:template>
+
+
+<!--
+ * applications
+-->
+<xsl:template match="application">
+ <xsl:if test="@name=$Application">
+ <xsl:variable name="context" select="//module[@name=$Module]/@context"/>
+<xsl:text>HKCR
+{
+ NoRemove AppID
+ {
+ ForceRemove {</xsl:text><xsl:value-of select="@uuid"/>} = s '<xsl:value-of select="@name"/><xsl:text> </xsl:text>
+ <xsl:choose>
+ <xsl:when test="$context='LocalService'">
+ <xsl:text>Service</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>Application</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>'
+</xsl:text>
+ <xsl:if test="$context='LocalService'">
+ <xsl:text> {
+ val LocalService = s '</xsl:text><xsl:value-of select="$Module"/><xsl:text>'
+ }
+</xsl:text>
+ </xsl:if>
+ <xsl:text> '</xsl:text><xsl:value-of select="$Module"/>
+ <xsl:choose>
+ <xsl:when test="$context='InprocServer'">
+ <xsl:text>.dll</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>.exe</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>'
+ {
+ val AppID = s '{</xsl:text><xsl:value-of select="//library/application[@name=$Application]/@uuid"/><xsl:text>}'
+ }
+ }
+
+</xsl:text>
+ <xsl:apply-templates select="module[@name=$Module]/class"/>
+<xsl:text>}
+</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+
+<!--
+ * classes
+-->
+<xsl:template match="library//module/class">
+ <xsl:variable name="cname" select="concat(//library/application/@name,'.',@name)"/>
+ <xsl:variable name="desc" select="concat(@name,' Class')"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="concat($cname,'.1')"/> = s '<xsl:value-of select="$desc"/>'
+ {
+ CLSID = s '{<xsl:value-of select="@uuid"/>}'
+ }
+ <xsl:value-of select="$cname"/> = s '<xsl:value-of select="$desc"/>'
+ {
+ CLSID = s '{<xsl:value-of select="@uuid"/>}'
+ CurVer = s '<xsl:value-of select="concat($cname,'.1')"/>'
+ }
+ NoRemove CLSID
+ {
+ ForceRemove {<xsl:value-of select="@uuid"/>} = s '<xsl:value-of select="$desc"/>'
+ {
+ val AppID = s '{<xsl:value-of select="//library/application[@name=$Application]/@uuid"/><xsl:text>}'
+</xsl:text>
+ <xsl:if test="../@context!='LocalService'">
+ <xsl:text> ProgID = s '</xsl:text><xsl:value-of select="concat($cname,'.1')"/><xsl:text>'
+ VersionIndependentProgID = s '</xsl:text><xsl:value-of select="$cname"/><xsl:text>'
+ </xsl:text>
+ <xsl:choose>
+ <xsl:when test="../@context='InprocServer'">InprocServer32</xsl:when>
+ <xsl:when test="../@context='LocalServer'">LocalServer32</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../@name,'::',../@name,': ')"/>
+ <xsl:text>module context </xsl:text>
+ <xsl:value-of select="concat('&quot;',../@context,'&quot;')"/>
+ <xsl:text> is invalid!</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose><xsl:text> = s '%MODULE%'
+</xsl:text>
+ <xsl:if test="../@context='InprocServer'">
+ <xsl:variable name="tmodel" select="(./@threadingModel | ../@threadingModel)[last()]"/><xsl:text> {
+ val ThreadingModel = s '</xsl:text>
+ <xsl:choose>
+ <xsl:when test="$tmodel='Apartment'">Apartment</xsl:when>
+ <xsl:when test="$tmodel='Free'">Free</xsl:when>
+ <xsl:when test="$tmodel='Both'">Both</xsl:when>
+ <xsl:when test="$tmodel='Neutral'">Neutral</xsl:when>
+ <xsl:when test="$tmodel='Single'">Single</xsl:when>
+ <xsl:when test="$tmodel='Rental'">Rental</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../@name,'::',@name,': ')"/>
+ <xsl:text>class (or module) threading model </xsl:text>
+ <xsl:value-of select="concat('&quot;',$tmodel,'&quot;')"/>
+ <xsl:text> is invalid!</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose><xsl:text>'
+ }
+</xsl:text>
+ </xsl:if>
+ <xsl:text> TypeLib = s '{</xsl:text><xsl:value-of select="//library/@uuid"/><xsl:text>}'
+</xsl:text>
+ </xsl:if>
+ <xsl:text> }
+ }
+</xsl:text>
+</xsl:template>
+
+
+<!--
+ * eat everything else not explicitly matched
+-->
+<xsl:template match="*">
+</xsl:template>
+
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/src-all/win/comregister.cmd b/src/VBox/Main/src-all/win/comregister.cmd
new file mode 100644
index 00000000..17340f77
--- /dev/null
+++ b/src/VBox/Main/src-all/win/comregister.cmd
@@ -0,0 +1,212 @@
+@echo off
+REM $Id: comregister.cmd $
+REM
+REM Script to register the VirtualBox COM classes
+REM (both inproc and out-of-process)
+REM
+
+REM
+REM Copyright (C) 2006-2022 Oracle and/or its affiliates.
+REM
+REM This file is part of VirtualBox base platform packages, as
+REM available from https://www.virtualbox.org.
+REM
+REM This program is free software; you can redistribute it and/or
+REM modify it under the terms of the GNU General Public License
+REM as published by the Free Software Foundation, in version 3 of the
+REM License.
+REM
+REM This program is distributed in the hope that it will be useful, but
+REM WITHOUT ANY WARRANTY; without even the implied warranty of
+REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+REM General Public License for more details.
+REM
+REM You should have received a copy of the GNU General Public License
+REM along with this program; if not, see <https://www.gnu.org/licenses>.
+REM
+REM SPDX-License-Identifier: GPL-3.0-only
+REM
+
+setlocal
+
+REM Check if the current user is an administrator. Otherwise
+REM all the COM registration will fail silently.
+NET FILE 1>NUL 2>NUL & IF ERRORLEVEL 1 (ECHO Must be run as Administrator. Exiting.) & GOTO end
+
+REM
+REM Figure out where the script lives first, so that we can invoke the
+REM correct VBoxSVC and register the right VBoxC.dll.
+REM
+
+REM Determine the current directory.
+set _SCRIPT_CURDIR=%CD%
+for /f "tokens=*" %%d in ('cd') do set _SCRIPT_CURDIR=%%d
+
+REM Determine a correct self - by %0.
+set _SCRIPT_SELF=%0
+if exist "%_SCRIPT_SELF%" goto found_self
+set _SCRIPT_SELF=%_SCRIPT_SELF%.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+
+REM Determine a correct self - by current working directory.
+set _SCRIPT_SELF=%_SCRIPT_CURDIR%\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+
+REM Determine a correct self - by the PATH
+REM This is very verbose because nested for loops didn't work out.
+for /f "tokens=1 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=2 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=3 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=4 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=5 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=6 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=7 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=8 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=9 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=10 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=11 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=12 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=13 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=14 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=15 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=16 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=17 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=18 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=19 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=20 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+echo Warning: Not able to determin the comregister.cmd location.
+set _VBOX_DIR=
+goto register
+
+:found_self
+set _VBOX_DIR=
+cd "%_SCRIPT_SELF%\.."
+for /f "tokens=*" %%d in ('cd') do set _VBOX_DIR=%%d\
+cd "%_SCRIPT_CURDIR%"
+
+REM
+REM Check for 64-bitness.
+REM
+set fIs64BitWindows=0
+if not "%ProgramW6432%x" == "x" set fIs64BitWindows=1
+if exist "%windir\syswow64\kernel32.dll" set fIs64BitWindows=1
+
+REM
+REM Figure out the Windows version as the proxy stub requires 6.0 or later (at least for 64-bit).
+REM
+set WinVer=Version 4.0.1381
+set WinVerMajor=4
+set WinVerMinor=0
+set WinVerBuild=1381
+for /f "tokens=2 delims=[]" %%a in ('ver') do set WinVer=%%a
+for /f "tokens=2,3,4 delims=. " %%a in ("%WinVer%") do (
+ set WinVerMajor=%%a
+ set WinVerMinor=%%b
+ set WinVerBuild=%%c
+)
+REM echo WinVerMajor=%WinVerMajor% WinVerMinor=%WinVerMinor% WinVerBuild=%WinVerBuild% WinVer=%WinVer%
+
+REM
+REM Parse arguments.
+REM
+set fNoProxy=0
+set fUninstallOnly=0
+
+:arg_loop
+if "%1x" == "x" goto arg_done
+
+if "%1" == "-u" goto arg_uninstall
+if "%1" == "--uninstall" goto arg_uninstall
+if "%1" == "--proxy" goto arg_proxy
+if "%1" == "--no-proxy" goto arg_no_proxy
+echo syntax error: Unknown option %1
+echo usage: comregister.cmd [-u,--uninstall] [--no-proxy] [--proxy]
+goto end
+
+:arg_uninstall
+set fUninstallOnly=1
+goto arg_next
+
+:arg_proxy
+set fNoProxy=0
+goto arg_next
+
+:arg_no_proxy
+set fNoProxy=1
+goto arg_next
+
+:arg_next
+shift
+goto arg_loop
+:arg_done
+
+REM
+REM Do the registrations.
+REM
+@if %fIs64BitWindows% == 1 goto register_amd64
+
+:register_x86
+@echo on
+"%_VBOX_DIR%VBoxSVC.exe" /UnregServer
+regsvr32 /s /u "%_VBOX_DIR%VBoxC.dll"
+%windir%\system32\regsvr32 /s /u "%_VBOX_DIR%VBoxProxyStub.dll"
+@if %fUninstallOnly% == 1 goto end
+"%_VBOX_DIR%VBoxSVC.exe" /RegServer
+"%_VBOX_DIR%VBoxSDS.exe" /RegService
+regsvr32 /s "%_VBOX_DIR%VBoxC.dll"
+@if %fNoProxy% == 1 goto end
+if exist "%_VBOX_DIR%VBoxProxyStub.dll" %windir%\system32\regsvr32 /s "%_VBOX_DIR%VBoxProxyStub.dll"
+@echo off
+goto end
+
+REM Unregister all first, then register them. The order matters here.
+:register_amd64
+if "%WinVerMajor%" == "5" goto register_amd64_legacy
+if not "%WinVerMajor%" == "6" goto register_amd64_not_legacy
+if not "%WinVerMinor%" == "0" goto register_amd64_not_legacy
+:register_amd64_legacy
+set s64BitProxyStub=VBoxProxyStubLegacy.dll
+goto register_amd64_begin
+:register_amd64_not_legacy
+set s64BitProxyStub=VBoxProxyStub.dll
+:register_amd64_begin
+echo s64BitProxyStub=%s64BitProxyStub%
+@echo on
+"%_VBOX_DIR%VBoxSVC.exe" /UnregServer
+"%_VBOX_DIR%VBoxSDS.exe" /UnregService
+%windir%\system32\regsvr32 /s /u "%_VBOX_DIR%VBoxC.dll"
+%windir%\syswow64\regsvr32 /s /u "%_VBOX_DIR%x86\VBoxClient-x86.dll"
+%windir%\system32\regsvr32 /s /u "%_VBOX_DIR%%s64BitProxyStub%"
+%windir%\syswow64\regsvr32 /s /u "%_VBOX_DIR%x86\VBoxProxyStub-x86.dll"
+if %fUninstallOnly% == 1 goto end
+"%_VBOX_DIR%VBoxSVC.exe" /RegServer
+"%_VBOX_DIR%VBoxSDS.exe" /RegService
+%windir%\system32\regsvr32 /s "%_VBOX_DIR%VBoxC.dll"
+%windir%\syswow64\regsvr32 /s "%_VBOX_DIR%x86\VBoxClient-x86.dll"
+if %fNoProxy% == 1 goto end
+if exist "%_VBOX_DIR%%s64BitProxyStub%" %windir%\system32\regsvr32 /s "%_VBOX_DIR%%s64BitProxyStub%"
+if exist "%_VBOX_DIR%x86\VBoxProxyStub-x86.dll" %windir%\syswow64\regsvr32 /s "%_VBOX_DIR%x86\VBoxProxyStub-x86.dll"
+@echo off
+
+:end
+@endlocal
diff --git a/src/VBox/Main/src-all/xpcom/VBoxAPIWrap-precomp_gcc.h b/src/VBox/Main/src-all/xpcom/VBoxAPIWrap-precomp_gcc.h
new file mode 100644
index 00000000..c3460917
--- /dev/null
+++ b/src/VBox/Main/src-all/xpcom/VBoxAPIWrap-precomp_gcc.h
@@ -0,0 +1,52 @@
+/* $Id: VBoxAPIWrap-precomp_gcc.h $ */
+/** @file
+ * VirtualBox COM - GCC precompiled header for the API wrappers.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <iprt/cdefs.h>
+#include <VBox/cdefs.h>
+#include <iprt/types.h>
+#include <iprt/stdarg.h>
+#include <iprt/cpp/list.h>
+#include <iprt/cpp/meta.h>
+#include <iprt/cpp/ministring.h>
+#include <VBox/com/com.h>
+#include <VBox/com/array.h>
+#include <VBox/com/Guid.h>
+#include <VBox/com/string.h>
+
+#include "VBox/com/VirtualBox.h"
+
+#include "VirtualBoxBase.h"
+#include "Wrapper.h"
+
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+# include "dtrace/VBoxAPI.h"
+#endif
+
+#if defined(Log) || defined(LogIsEnabled)
+# error "Log() from iprt/log.h cannot be defined in the precompiled header!"
+#endif
+
diff --git a/src/VBox/Main/src-client/AdditionsFacilityImpl.cpp b/src/VBox/Main/src-client/AdditionsFacilityImpl.cpp
new file mode 100644
index 00000000..65b593bd
--- /dev/null
+++ b/src/VBox/Main/src-client/AdditionsFacilityImpl.cpp
@@ -0,0 +1,231 @@
+/* $Id: AdditionsFacilityImpl.cpp $ */
+/** @file
+ * VirtualBox Main - Additions facility class.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_ADDITIONSFACILITY
+#include "LoggingNew.h"
+
+#include "AdditionsFacilityImpl.h"
+#include "Global.h"
+
+#include "AutoCaller.h"
+
+
+/**
+ * @note We ASSUME that unknown is the first entry!
+ */
+/* static */
+const AdditionsFacility::FacilityInfo AdditionsFacility::s_aFacilityInfo[8] =
+{
+ { "Unknown", AdditionsFacilityType_None, AdditionsFacilityClass_None },
+ { "VirtualBox Base Driver", AdditionsFacilityType_VBoxGuestDriver, AdditionsFacilityClass_Driver },
+ { "Auto Logon", AdditionsFacilityType_AutoLogon, AdditionsFacilityClass_Feature },
+ { "VirtualBox System Service", AdditionsFacilityType_VBoxService, AdditionsFacilityClass_Service },
+ { "VirtualBox Desktop Integration", AdditionsFacilityType_VBoxTrayClient, AdditionsFacilityClass_Program },
+ { "Seamless Mode", AdditionsFacilityType_Seamless, AdditionsFacilityClass_Feature },
+ { "Graphics Mode", AdditionsFacilityType_Graphics, AdditionsFacilityClass_Feature },
+ { "Guest Monitor Attach", AdditionsFacilityType_MonitorAttach, AdditionsFacilityClass_Feature },
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(AdditionsFacility)
+
+HRESULT AdditionsFacility::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void AdditionsFacility::FinalRelease()
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT AdditionsFacility::init(Guest *a_pParent, AdditionsFacilityType_T a_enmFacility, AdditionsFacilityStatus_T a_enmStatus,
+ uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS)
+{
+ RT_NOREF(a_pParent); /** @todo r=bird: For locking perhaps? */
+ LogFlowThisFunc(("a_pParent=%p\n", a_pParent));
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Initialize the data: */
+ mData.mType = a_enmFacility;
+ mData.mStatus = a_enmStatus;
+ mData.mTimestamp = *a_pTimeSpecTS;
+ mData.mfFlags = a_fFlags;
+ mData.midxInfo = 0;
+ for (size_t i = 0; i < RT_ELEMENTS(s_aFacilityInfo); ++i)
+ if (s_aFacilityInfo[i].mType == a_enmFacility)
+ {
+ mData.midxInfo = i;
+ break;
+ }
+
+ /* Confirm a successful initialization when it's the case. */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance.
+ *
+ * Called from FinalRelease().
+ */
+void AdditionsFacility::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+}
+
+HRESULT AdditionsFacility::getClassType(AdditionsFacilityClass_T *aClassType)
+{
+ LogFlowThisFuncEnter();
+
+ /* midxInfo is static, so no need to lock anything. */
+ size_t idxInfo = mData.midxInfo;
+ AssertStmt(idxInfo < RT_ELEMENTS(s_aFacilityInfo), idxInfo = 0);
+ *aClassType = s_aFacilityInfo[idxInfo].mClass;
+ return S_OK;
+}
+
+HRESULT AdditionsFacility::getName(com::Utf8Str &aName)
+{
+ LogFlowThisFuncEnter();
+
+ /* midxInfo is static, so no need to lock anything. */
+ size_t idxInfo = mData.midxInfo;
+ AssertStmt(idxInfo < RT_ELEMENTS(s_aFacilityInfo), idxInfo = 0);
+ int vrc = aName.assignNoThrow(s_aFacilityInfo[idxInfo].mName);
+ return RT_SUCCESS(vrc) ? S_OK : E_OUTOFMEMORY;
+}
+
+HRESULT AdditionsFacility::getLastUpdated(LONG64 *aLastUpdated)
+{
+ LogFlowThisFuncEnter();
+
+ /** @todo r=bird: Should take parent (Guest) lock here, see i_update(). */
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aLastUpdated = RTTimeSpecGetMilli(&mData.mTimestamp);
+ return S_OK;
+}
+
+HRESULT AdditionsFacility::getStatus(AdditionsFacilityStatus_T *aStatus)
+{
+ LogFlowThisFuncEnter();
+
+ /** @todo r=bird: Should take parent (Guest) lock here, see i_update(). */
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aStatus = mData.mStatus;
+ return S_OK;
+}
+
+HRESULT AdditionsFacility::getType(AdditionsFacilityType_T *aType)
+{
+ LogFlowThisFuncEnter();
+
+ /* mType is static, so no need to lock anything. */
+ *aType = mData.mType;
+ return S_OK;
+}
+
+#if 0 /* unused */
+
+AdditionsFacilityType_T AdditionsFacility::i_getType() const
+{
+ return mData.mType;
+}
+
+AdditionsFacilityClass_T AdditionsFacility::i_getClass() const
+{
+ size_t idxInfo = mData.midxInfo;
+ AssertStmt(idxInfo < RT_ELEMENTS(s_aFacilityInfo), idxInfo = 0);
+ return s_aFacilityInfo[idxInfo].mClass;
+}
+
+const char *AdditionsFacility::i_getName() const
+{
+ size_t idxInfo = mData.midxInfo;
+ AssertStmt(idxInfo < RT_ELEMENTS(s_aFacilityInfo), idxInfo = 0);
+ return s_aFacilityInfo[idxInfo].mName;
+}
+
+#endif /* unused */
+
+/**
+ * @note Caller should read lock the Guest object.
+ */
+LONG64 AdditionsFacility::i_getLastUpdated() const
+{
+ return RTTimeSpecGetMilli(&mData.mTimestamp);
+}
+
+/**
+ * @note Caller should read lock the Guest object.
+ */
+AdditionsFacilityStatus_T AdditionsFacility::i_getStatus() const
+{
+ return mData.mStatus;
+}
+
+/**
+ * Method used by IGuest::facilityUpdate to make updates.
+ *
+ * @returns change indicator.
+ *
+ * @todo r=bird: Locking here isn't quite sane. While updating is serialized
+ * by the caller holding down the Guest object lock, this code doesn't
+ * serialize with this object. So, the read locking done in the getter
+ * methods is utterly pointless. OTOH, the getter methods only get
+ * single values, so there isn't really much to be worried about here,
+ * especially with 32-bit hosts no longer being supported.
+ */
+bool AdditionsFacility::i_update(AdditionsFacilityStatus_T a_enmStatus, uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS)
+{
+ bool const fChanged = mData.mStatus != a_enmStatus;
+
+ mData.mTimestamp = *a_pTimeSpecTS;
+ mData.mStatus = a_enmStatus;
+ mData.mfFlags = a_fFlags;
+
+ return fChanged;
+}
+
diff --git a/src/VBox/Main/src-client/AudioDriver.cpp b/src/VBox/Main/src-client/AudioDriver.cpp
new file mode 100644
index 00000000..c4876114
--- /dev/null
+++ b/src/VBox/Main/src-client/AudioDriver.cpp
@@ -0,0 +1,337 @@
+/* $Id: AudioDriver.cpp $ */
+/** @file
+ * VirtualBox audio base class for Main audio drivers.
+ */
+
+/*
+ * Copyright (C) 2018-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include "LoggingNew.h"
+
+#include <VBox/log.h>
+#include <VBox/vmm/cfgm.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmapi.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/vmmr3vtable.h>
+
+#include "AudioDriver.h"
+#include "ConsoleImpl.h"
+
+AudioDriver::AudioDriver(Console *pConsole)
+ : mpConsole(pConsole)
+ , mfAttached(false)
+{
+}
+
+
+AudioDriver::~AudioDriver(void)
+{
+}
+
+
+AudioDriver &AudioDriver::operator=(AudioDriver const &a_rThat) RT_NOEXCEPT
+{
+ mpConsole = a_rThat.mpConsole;
+ mCfg = a_rThat.mCfg;
+ mfAttached = a_rThat.mfAttached;
+
+ return *this;
+}
+
+
+/**
+ * Initializes the audio driver with a certain (device) configuration.
+ *
+ * @returns VBox status code.
+ * @param pCfg Audio driver configuration to use.
+ */
+int AudioDriver::InitializeConfig(AudioDriverCfg *pCfg)
+{
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ /* Sanity. */
+ AssertReturn(pCfg->strDev.isNotEmpty(), VERR_INVALID_PARAMETER);
+ AssertReturn(pCfg->uLUN != UINT8_MAX, VERR_INVALID_PARAMETER);
+ AssertReturn(pCfg->strName.isNotEmpty(), VERR_INVALID_PARAMETER);
+
+ /* Apply configuration. */
+ mCfg = *pCfg;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Attaches the driver via EMT, if configured.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle for talking to EMT.
+ * @param pVMM The VMM ring-3 vtable.
+ * @param pAutoLock The callers auto lock instance. Can be NULL if not
+ * locked.
+ */
+int AudioDriver::doAttachDriverViaEmt(PUVM pUVM, PCVMMR3VTABLE pVMM, util::AutoWriteLock *pAutoLock)
+{
+ if (!isConfigured())
+ return VINF_SUCCESS;
+
+ PVMREQ pReq;
+ int vrc = pVMM->pfnVMR3ReqCallU(pUVM, VMCPUID_ANY, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)attachDriverOnEmt, 1, this);
+ if (vrc == VERR_TIMEOUT)
+ {
+ /* Release the lock before a blocking VMR3* call (EMT might wait for it, @bugref{7648})! */
+ if (pAutoLock)
+ pAutoLock->release();
+
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+
+ if (pAutoLock)
+ pAutoLock->acquire();
+ }
+
+ AssertRC(vrc);
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ return vrc;
+}
+
+
+/**
+ * Configures the audio driver (to CFGM) and attaches it to the audio chain.
+ * Does nothing if the audio driver already is attached.
+ *
+ * @returns VBox status code.
+ * @param pThis Audio driver to detach.
+ */
+/* static */
+DECLCALLBACK(int) AudioDriver::attachDriverOnEmt(AudioDriver *pThis)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ Console::SafeVMPtrQuiet ptrVM(pThis->mpConsole);
+ Assert(ptrVM.isOk());
+
+ if (pThis->mfAttached) /* Already attached? Bail out. */
+ {
+ LogFunc(("%s: Already attached\n", pThis->mCfg.strName.c_str()));
+ return VINF_SUCCESS;
+ }
+
+ AudioDriverCfg *pCfg = &pThis->mCfg;
+
+ LogFunc(("strName=%s, strDevice=%s, uInst=%u, uLUN=%u\n",
+ pCfg->strName.c_str(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN));
+
+ /* Detach the driver chain from the audio device first. */
+ int vrc = ptrVM.vtable()->pfnPDMR3DeviceDetach(ptrVM.rawUVM(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN, 0 /* fFlags */);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pThis->configure(pCfg->uLUN, true /* Attach */);
+ if (RT_SUCCESS(vrc))
+ vrc = ptrVM.vtable()->pfnPDMR3DriverAttach(ptrVM.rawUVM(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN,
+ 0 /* fFlags */, NULL /* ppBase */);
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pThis->mfAttached = true;
+ LogRel2(("%s: Driver attached (LUN #%u)\n", pCfg->strName.c_str(), pCfg->uLUN));
+ }
+ else
+ LogRel(("%s: Failed to attach audio driver, rc=%Rrc\n", pCfg->strName.c_str(), vrc));
+
+ LogFunc(("Returning %Rrc\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * Detatches the driver via EMT, if configured.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle for talking to EMT.
+ * @param pVMM The VMM ring-3 vtable.
+ * @param pAutoLock The callers auto lock instance. Can be NULL if not
+ * locked.
+ */
+int AudioDriver::doDetachDriverViaEmt(PUVM pUVM, PCVMMR3VTABLE pVMM, util::AutoWriteLock *pAutoLock)
+{
+ if (!isConfigured())
+ return VINF_SUCCESS;
+
+ PVMREQ pReq;
+ int vrc = pVMM->pfnVMR3ReqCallU(pUVM, VMCPUID_ANY, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)detachDriverOnEmt, 1, this);
+ if (vrc == VERR_TIMEOUT)
+ {
+ /* Release the lock before a blocking VMR3* call (EMT might wait for it, @bugref{7648})! */
+ if (pAutoLock)
+ pAutoLock->release();
+
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+
+ if (pAutoLock)
+ pAutoLock->acquire();
+ }
+
+ AssertRC(vrc);
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ return vrc;
+}
+
+
+/**
+ * Detaches an already attached audio driver from the audio chain.
+ * Does nothing if the audio driver already is detached or not attached.
+ *
+ * @returns VBox status code.
+ * @param pThis Audio driver to detach.
+ */
+/* static */
+DECLCALLBACK(int) AudioDriver::detachDriverOnEmt(AudioDriver *pThis)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ if (!pThis->mfAttached) /* Not attached? Bail out. */
+ {
+ LogFunc(("%s: Not attached\n", pThis->mCfg.strName.c_str()));
+ return VINF_SUCCESS;
+ }
+
+ Console::SafeVMPtrQuiet ptrVM(pThis->mpConsole);
+ Assert(ptrVM.isOk());
+
+ AudioDriverCfg *pCfg = &pThis->mCfg;
+
+ Assert(pCfg->uLUN != UINT8_MAX);
+
+ LogFunc(("strName=%s, strDevice=%s, uInst=%u, uLUN=%u\n",
+ pCfg->strName.c_str(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN));
+
+ /* Destroy the entire driver chain for the specified LUN.
+ *
+ * Start with the "AUDIO" driver, as this driver serves as the audio connector between
+ * the device emulation and the select backend(s). */
+ int vrc = ptrVM.vtable()->pfnPDMR3DriverDetach(ptrVM.rawUVM(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN,
+ "AUDIO", 0 /* iOccurrence */, 0 /* fFlags */);
+ if (RT_SUCCESS(vrc))
+ vrc = pThis->configure(pCfg->uLUN, false /* Detach */);/** @todo r=bird: Illogical and from what I can tell pointless! */
+
+ if (RT_SUCCESS(vrc))
+ {
+ pThis->mfAttached = false;
+ LogRel2(("%s: Driver detached\n", pCfg->strName.c_str()));
+ }
+ else
+ LogRel(("%s: Failed to detach audio driver, vrc=%Rrc\n", pCfg->strName.c_str(), vrc));
+
+ LogFunc(("Returning %Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Configures the audio driver via CFGM.
+ *
+ * @returns VBox status code.
+ * @param uLUN LUN to attach driver to.
+ * @param fAttach Whether to attach or detach the driver configuration to CFGM.
+ *
+ * @thread EMT
+ */
+int AudioDriver::configure(unsigned uLUN, bool fAttach)
+{
+ Console::SafeVMPtrQuiet ptrVM(mpConsole);
+ AssertReturn(ptrVM.isOk(), VERR_INVALID_STATE);
+
+ PCFGMNODE pRoot = ptrVM.vtable()->pfnCFGMR3GetRootU(ptrVM.rawUVM());
+ AssertPtr(pRoot);
+ PCFGMNODE pDev0 = ptrVM.vtable()->pfnCFGMR3GetChildF(pRoot, "Devices/%s/%u/", mCfg.strDev.c_str(), mCfg.uInst);
+
+ if (!pDev0) /* No audio device configured? Bail out. */
+ {
+ LogRel2(("%s: No audio device configured, skipping to attach driver\n", mCfg.strName.c_str()));
+ return VINF_SUCCESS;
+ }
+
+ int vrc = VINF_SUCCESS;
+
+ PCFGMNODE pDevLun = ptrVM.vtable()->pfnCFGMR3GetChildF(pDev0, "LUN#%u/", uLUN);
+
+ if (fAttach)
+ {
+ do /* break "loop" */
+ {
+ AssertMsgBreakStmt(pDevLun, ("%s: Device LUN #%u not found\n", mCfg.strName.c_str(), uLUN), vrc = VERR_NOT_FOUND);
+
+ LogRel2(("%s: Configuring audio driver (to LUN #%u)\n", mCfg.strName.c_str(), uLUN));
+
+ ptrVM.vtable()->pfnCFGMR3RemoveNode(pDevLun); /* Remove LUN completely first. */
+
+ /* Insert new LUN configuration and build up the new driver chain. */
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertNodeF(pDev0, &pDevLun, "LUN#%u/", uLUN); AssertRCBreak(vrc);
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertString(pDevLun, "Driver", "AUDIO"); AssertRCBreak(vrc);
+
+ PCFGMNODE pLunCfg;
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertNode(pDevLun, "Config", &pLunCfg); AssertRCBreak(vrc);
+
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertStringF(pLunCfg, "DriverName", "%s", mCfg.strName.c_str()); AssertRCBreak(vrc);
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertInteger(pLunCfg, "InputEnabled", mCfg.fEnabledIn); AssertRCBreak(vrc);
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertInteger(pLunCfg, "OutputEnabled", mCfg.fEnabledOut); AssertRCBreak(vrc);
+
+ PCFGMNODE pAttachedDriver;
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertNode(pDevLun, "AttachedDriver", &pAttachedDriver); AssertRCBreak(vrc);
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertStringF(pAttachedDriver, "Driver", "%s", mCfg.strName.c_str()); AssertRCBreak(vrc);
+ PCFGMNODE pAttachedDriverCfg;
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertNode(pAttachedDriver, "Config", &pAttachedDriverCfg); AssertRCBreak(vrc);
+
+ /* Call the (virtual) method for driver-specific configuration. */
+ vrc = configureDriver(pAttachedDriverCfg, ptrVM.vtable()); AssertRCBreak(vrc);
+
+ } while (0);
+ }
+ else /* Detach */
+ {
+ LogRel2(("%s: Unconfiguring audio driver\n", mCfg.strName.c_str()));
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+#ifdef LOG_ENABLED
+ LogFunc(("%s: fAttach=%RTbool\n", mCfg.strName.c_str(), fAttach));
+ ptrVM.vtable()->pfnCFGMR3Dump(pDevLun);
+#endif
+ }
+ else
+ LogRel(("%s: %s audio driver failed with vrc=%Rrc\n", mCfg.strName.c_str(), fAttach ? "Configuring" : "Unconfiguring", vrc));
+
+ LogFunc(("Returning %Rrc\n", vrc));
+ return vrc;
+}
+
diff --git a/src/VBox/Main/src-client/BusAssignmentManager.cpp b/src/VBox/Main/src-client/BusAssignmentManager.cpp
new file mode 100644
index 00000000..c21f2f53
--- /dev/null
+++ b/src/VBox/Main/src-client/BusAssignmentManager.cpp
@@ -0,0 +1,711 @@
+/* $Id: BusAssignmentManager.cpp $ */
+/** @file
+ * VirtualBox bus slots assignment manager
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN
+#include "LoggingNew.h"
+
+#include "BusAssignmentManager.h"
+
+#include <iprt/asm.h>
+#include <iprt/string.h>
+
+#include <VBox/vmm/cfgm.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/com/array.h>
+
+#include <map>
+#include <vector>
+#include <algorithm>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+struct DeviceAssignmentRule
+{
+ const char *pszName;
+ int iBus;
+ int iDevice;
+ int iFn;
+ int iPriority;
+};
+
+struct DeviceAliasRule
+{
+ const char *pszDevName;
+ const char *pszDevAlias;
+};
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/* Those rules define PCI slots assignment */
+/** @note
+ * The EFI takes assumptions about PCI slot assignments which are different
+ * from the following tables in certain cases, for example the IDE device
+ * is assumed to be 00:01.1! */
+
+/* Device Bus Device Function Priority */
+
+/* Generic rules */
+static const DeviceAssignmentRule g_aGenericRules[] =
+{
+ /* VGA controller */
+ {"vga", 0, 2, 0, 0},
+
+ /* VMM device */
+ {"VMMDev", 0, 4, 0, 0},
+
+ /* Audio controllers */
+ {"ichac97", 0, 5, 0, 0},
+ {"hda", 0, 5, 0, 0},
+
+ /* Storage controllers */
+ {"buslogic", 0, 21, 0, 1},
+ {"lsilogicsas", 0, 22, 0, 1},
+ {"nvme", 0, 14, 0, 1},
+ {"virtio-scsi", 0, 15, 0, 1},
+
+ /* USB controllers */
+ {"usb-ohci", 0, 6, 0, 0},
+ {"usb-ehci", 0, 11, 0, 0},
+ {"usb-xhci", 0, 12, 0, 0},
+
+ /* ACPI controller */
+#if 0
+ // It really should be this for 440FX chipset (part of PIIX4 actually)
+ {"acpi", 0, 1, 3, 0},
+#else
+ {"acpi", 0, 7, 0, 0},
+#endif
+
+ /* Network controllers */
+ /* the first network card gets the PCI ID 3, the next 3 gets 8..10,
+ * next 4 get 16..19. In "VMWare compatibility" mode the IDs 3 and 17
+ * swap places, i.e. the first card goes to ID 17=0x11. */
+ {"nic", 0, 3, 0, 1},
+ {"nic", 0, 8, 0, 1},
+ {"nic", 0, 9, 0, 1},
+ {"nic", 0, 10, 0, 1},
+ {"nic", 0, 16, 0, 1},
+ {"nic", 0, 17, 0, 1},
+ {"nic", 0, 18, 0, 1},
+ {"nic", 0, 19, 0, 1},
+
+ /* ISA/LPC controller */
+ {"lpc", 0, 31, 0, 0},
+
+ { NULL, -1, -1, -1, 0}
+};
+
+/* PIIX3 chipset rules */
+static const DeviceAssignmentRule g_aPiix3Rules[] =
+{
+ {"piix3ide", 0, 1, 1, 0},
+ {"ahci", 0, 13, 0, 1},
+ {"lsilogic", 0, 20, 0, 1},
+ {"pcibridge", 0, 24, 0, 0},
+ {"pcibridge", 0, 25, 0, 0},
+ { NULL, -1, -1, -1, 0}
+};
+
+
+/* ICH9 chipset rules */
+static const DeviceAssignmentRule g_aIch9Rules[] =
+{
+ /* Host Controller */
+ {"i82801", 0, 30, 0, 0},
+
+ /* Those are functions of LPC at 00:1e:00 */
+ /**
+ * Please note, that for devices being functions, like we do here, device 0
+ * must be multifunction, i.e. have header type 0x80. Our LPC device is.
+ * Alternative approach is to assign separate slot to each device.
+ */
+ {"piix3ide", 0, 31, 1, 2},
+ {"ahci", 0, 31, 2, 2},
+ {"smbus", 0, 31, 3, 2},
+ {"usb-ohci", 0, 31, 4, 2},
+ {"usb-ehci", 0, 31, 5, 2},
+ {"thermal", 0, 31, 6, 2},
+
+ /* to make sure rule never used before rules assigning devices on it */
+ {"ich9pcibridge", 0, 24, 0, 10},
+ {"ich9pcibridge", 0, 25, 0, 10},
+ {"ich9pcibridge", 2, 24, 0, 9}, /* Bridges must be instantiated depth */
+ {"ich9pcibridge", 2, 25, 0, 9}, /* first (assumption in PDM and other */
+ {"ich9pcibridge", 4, 24, 0, 8}, /* places), so make sure that nested */
+ {"ich9pcibridge", 4, 25, 0, 8}, /* bridges are added to the last bridge */
+ {"ich9pcibridge", 6, 24, 0, 7}, /* only, avoiding the need to re-sort */
+ {"ich9pcibridge", 6, 25, 0, 7}, /* everything before starting the VM. */
+ {"ich9pcibridge", 8, 24, 0, 6},
+ {"ich9pcibridge", 8, 25, 0, 6},
+ {"ich9pcibridge", 10, 24, 0, 5},
+ {"ich9pcibridge", 10, 25, 0, 5},
+
+ /* Storage controllers */
+ {"ahci", 1, 0, 0, 0},
+ {"ahci", 1, 1, 0, 0},
+ {"ahci", 1, 2, 0, 0},
+ {"ahci", 1, 3, 0, 0},
+ {"ahci", 1, 4, 0, 0},
+ {"ahci", 1, 5, 0, 0},
+ {"ahci", 1, 6, 0, 0},
+ {"lsilogic", 1, 7, 0, 0},
+ {"lsilogic", 1, 8, 0, 0},
+ {"lsilogic", 1, 9, 0, 0},
+ {"lsilogic", 1, 10, 0, 0},
+ {"lsilogic", 1, 11, 0, 0},
+ {"lsilogic", 1, 12, 0, 0},
+ {"lsilogic", 1, 13, 0, 0},
+ {"buslogic", 1, 14, 0, 0},
+ {"buslogic", 1, 15, 0, 0},
+ {"buslogic", 1, 16, 0, 0},
+ {"buslogic", 1, 17, 0, 0},
+ {"buslogic", 1, 18, 0, 0},
+ {"buslogic", 1, 19, 0, 0},
+ {"buslogic", 1, 20, 0, 0},
+ {"lsilogicsas", 1, 21, 0, 0},
+ {"lsilogicsas", 1, 26, 0, 0},
+ {"lsilogicsas", 1, 27, 0, 0},
+ {"lsilogicsas", 1, 28, 0, 0},
+ {"lsilogicsas", 1, 29, 0, 0},
+ {"lsilogicsas", 1, 30, 0, 0},
+ {"lsilogicsas", 1, 31, 0, 0},
+
+ /* NICs */
+ {"nic", 2, 0, 0, 0},
+ {"nic", 2, 1, 0, 0},
+ {"nic", 2, 2, 0, 0},
+ {"nic", 2, 3, 0, 0},
+ {"nic", 2, 4, 0, 0},
+ {"nic", 2, 5, 0, 0},
+ {"nic", 2, 6, 0, 0},
+ {"nic", 2, 7, 0, 0},
+ {"nic", 2, 8, 0, 0},
+ {"nic", 2, 9, 0, 0},
+ {"nic", 2, 10, 0, 0},
+ {"nic", 2, 11, 0, 0},
+ {"nic", 2, 12, 0, 0},
+ {"nic", 2, 13, 0, 0},
+ {"nic", 2, 14, 0, 0},
+ {"nic", 2, 15, 0, 0},
+ {"nic", 2, 16, 0, 0},
+ {"nic", 2, 17, 0, 0},
+ {"nic", 2, 18, 0, 0},
+ {"nic", 2, 19, 0, 0},
+ {"nic", 2, 20, 0, 0},
+ {"nic", 2, 21, 0, 0},
+ {"nic", 2, 26, 0, 0},
+ {"nic", 2, 27, 0, 0},
+ {"nic", 2, 28, 0, 0},
+ {"nic", 2, 29, 0, 0},
+ {"nic", 2, 30, 0, 0},
+ {"nic", 2, 31, 0, 0},
+
+ /* Storage controller #2 (NVMe, virtio-scsi) */
+ {"nvme", 3, 0, 0, 0},
+ {"nvme", 3, 1, 0, 0},
+ {"nvme", 3, 2, 0, 0},
+ {"nvme", 3, 3, 0, 0},
+ {"nvme", 3, 4, 0, 0},
+ {"nvme", 3, 5, 0, 0},
+ {"nvme", 3, 6, 0, 0},
+ {"virtio-scsi", 3, 7, 0, 0},
+ {"virtio-scsi", 3, 8, 0, 0},
+ {"virtio-scsi", 3, 9, 0, 0},
+ {"virtio-scsi", 3, 10, 0, 0},
+ {"virtio-scsi", 3, 11, 0, 0},
+ {"virtio-scsi", 3, 12, 0, 0},
+ {"virtio-scsi", 3, 13, 0, 0},
+
+ { NULL, -1, -1, -1, 0}
+};
+
+
+#ifdef VBOX_WITH_IOMMU_AMD
+/*
+ * AMD IOMMU and LSI Logic controller rules.
+ *
+ * Since the PCI slot (BDF=00:20.0) of the LSI Logic controller
+ * conflicts with the SB I/O APIC, we assign the LSI Logic controller
+ * to device number 23 when the VM is configured for an AMD IOMMU.
+ */
+static const DeviceAssignmentRule g_aIch9IommuAmdRules[] =
+{
+ /* AMD IOMMU. */
+ {"iommu-amd", 0, 0, 0, 0},
+ /* AMD IOMMU: Reserved for southbridge I/O APIC. */
+ {"sb-ioapic", 0, 20, 0, 0},
+
+ /* Storage controller */
+ {"lsilogic", 0, 23, 0, 1},
+ { NULL, -1, -1, -1, 0}
+};
+#endif
+
+#ifdef VBOX_WITH_IOMMU_INTEL
+/*
+ * Intel IOMMU.
+ * The VT-d misc, address remapping, system management device is
+ * located at BDF 0:5:0 on real hardware but we use 0:1:0 since that
+ * slot isn't used for anything else.
+ *
+ * While we could place the I/O APIC anywhere, we keep it consistent
+ * with the AMD IOMMU and we assign the LSI Logic controller to
+ * device number 23 (and I/O APIC at device 20).
+ */
+static const DeviceAssignmentRule g_aIch9IommuIntelRules[] =
+{
+ /* Intel IOMMU. */
+ {"iommu-intel", 0, 1, 0, 0},
+ /* Intel IOMMU: Reserved for I/O APIC. */
+ {"sb-ioapic", 0, 20, 0, 0},
+
+ /* Storage controller */
+ {"lsilogic", 0, 23, 0, 1},
+ { NULL, -1, -1, -1, 0}
+};
+#endif
+
+/* LSI Logic Controller. */
+static const DeviceAssignmentRule g_aIch9LsiRules[] =
+{
+ /* Storage controller */
+ {"lsilogic", 0, 20, 0, 1},
+ { NULL, -1, -1, -1, 0}
+};
+
+/* Aliasing rules */
+static const DeviceAliasRule g_aDeviceAliases[] =
+{
+ {"e1000", "nic"},
+ {"pcnet", "nic"},
+ {"virtio-net", "nic"},
+ {"ahci", "storage"},
+ {"lsilogic", "storage"},
+ {"buslogic", "storage"},
+ {"lsilogicsas", "storage"},
+ {"nvme", "storage"},
+ {"virtio-scsi", "storage"}
+};
+
+
+
+/**
+ * Bus assignment manage state data.
+ * @internal
+ */
+struct BusAssignmentManager::State
+{
+ struct PCIDeviceRecord
+ {
+ char szDevName[32];
+ PCIBusAddress HostAddress;
+
+ PCIDeviceRecord(const char *pszName, PCIBusAddress aHostAddress)
+ {
+ RTStrCopy(this->szDevName, sizeof(szDevName), pszName);
+ this->HostAddress = aHostAddress;
+ }
+
+ PCIDeviceRecord(const char *pszName)
+ {
+ RTStrCopy(this->szDevName, sizeof(szDevName), pszName);
+ }
+
+ bool operator<(const PCIDeviceRecord &a) const
+ {
+ return RTStrNCmp(szDevName, a.szDevName, sizeof(szDevName)) < 0;
+ }
+
+ bool operator==(const PCIDeviceRecord &a) const
+ {
+ return RTStrNCmp(szDevName, a.szDevName, sizeof(szDevName)) == 0;
+ }
+ };
+
+ typedef std::map<PCIBusAddress,PCIDeviceRecord> PCIMap;
+ typedef std::vector<PCIBusAddress> PCIAddrList;
+ typedef std::vector<const DeviceAssignmentRule *> PCIRulesList;
+ typedef std::map<PCIDeviceRecord,PCIAddrList> ReversePCIMap;
+
+ volatile int32_t cRefCnt;
+ ChipsetType_T mChipsetType;
+ const char * mpszBridgeName;
+ IommuType_T mIommuType;
+ PCIMap mPCIMap;
+ ReversePCIMap mReversePCIMap;
+ PCVMMR3VTABLE mpVMM;
+
+ State()
+ : cRefCnt(1), mChipsetType(ChipsetType_Null), mpszBridgeName("unknownbridge"), mpVMM(NULL)
+ {}
+ ~State()
+ {}
+
+ HRESULT init(PCVMMR3VTABLE pVMM, ChipsetType_T chipsetType, IommuType_T iommuType);
+
+ HRESULT record(const char *pszName, PCIBusAddress& GuestAddress, PCIBusAddress HostAddress);
+ HRESULT autoAssign(const char *pszName, PCIBusAddress& Address);
+ bool checkAvailable(PCIBusAddress& Address);
+ bool findPCIAddress(const char *pszDevName, int iInstance, PCIBusAddress& Address);
+
+ const char *findAlias(const char *pszName);
+ void addMatchingRules(const char *pszName, PCIRulesList& aList);
+ void listAttachedPCIDevices(std::vector<PCIDeviceInfo> &aAttached);
+};
+
+
+HRESULT BusAssignmentManager::State::init(PCVMMR3VTABLE pVMM, ChipsetType_T chipsetType, IommuType_T iommuType)
+{
+ mpVMM = pVMM;
+
+ if (iommuType != IommuType_None)
+ {
+#if defined(VBOX_WITH_IOMMU_AMD) && defined(VBOX_WITH_IOMMU_INTEL)
+ Assert(iommuType == IommuType_AMD || iommuType == IommuType_Intel);
+#elif defined(VBOX_WITH_IOMMU_AMD)
+ Assert(iommuType == IommuType_AMD);
+#elif defined(VBOX_WITH_IOMMU_INTEL)
+ Assert(iommuType == IommuType_Intel);
+#endif
+ }
+
+ mChipsetType = chipsetType;
+ mIommuType = iommuType;
+ switch (chipsetType)
+ {
+ case ChipsetType_PIIX3:
+ mpszBridgeName = "pcibridge";
+ break;
+ case ChipsetType_ICH9:
+ mpszBridgeName = "ich9pcibridge";
+ break;
+ default:
+ mpszBridgeName = "unknownbridge";
+ AssertFailed();
+ break;
+ }
+ return S_OK;
+}
+
+HRESULT BusAssignmentManager::State::record(const char *pszName, PCIBusAddress& Address, PCIBusAddress HostAddress)
+{
+ PCIDeviceRecord devRec(pszName, HostAddress);
+
+ /* Remember address -> device mapping */
+ mPCIMap.insert(PCIMap::value_type(Address, devRec));
+
+ ReversePCIMap::iterator it = mReversePCIMap.find(devRec);
+ if (it == mReversePCIMap.end())
+ {
+ mReversePCIMap.insert(ReversePCIMap::value_type(devRec, PCIAddrList()));
+ it = mReversePCIMap.find(devRec);
+ }
+
+ /* Remember device name -> addresses mapping */
+ it->second.push_back(Address);
+
+ return S_OK;
+}
+
+bool BusAssignmentManager::State::findPCIAddress(const char *pszDevName, int iInstance, PCIBusAddress& Address)
+{
+ PCIDeviceRecord devRec(pszDevName);
+
+ ReversePCIMap::iterator it = mReversePCIMap.find(devRec);
+ if (it == mReversePCIMap.end())
+ return false;
+
+ if (iInstance >= (int)it->second.size())
+ return false;
+
+ Address = it->second[iInstance];
+ return true;
+}
+
+void BusAssignmentManager::State::addMatchingRules(const char *pszName, PCIRulesList& aList)
+{
+ size_t iRuleset, iRule;
+ const DeviceAssignmentRule *aArrays[3] = {g_aGenericRules, NULL, NULL};
+
+ switch (mChipsetType)
+ {
+ case ChipsetType_PIIX3:
+ aArrays[1] = g_aPiix3Rules;
+ break;
+ case ChipsetType_ICH9:
+ {
+ aArrays[1] = g_aIch9Rules;
+#ifdef VBOX_WITH_IOMMU_AMD
+ if (mIommuType == IommuType_AMD)
+ aArrays[2] = g_aIch9IommuAmdRules;
+ else
+#endif
+#ifdef VBOX_WITH_IOMMU_INTEL
+ if (mIommuType == IommuType_Intel)
+ aArrays[2] = g_aIch9IommuIntelRules;
+ else
+#endif
+ {
+ aArrays[2] = g_aIch9LsiRules;
+ }
+ break;
+ }
+ default:
+ AssertFailed();
+ break;
+ }
+
+ for (iRuleset = 0; iRuleset < RT_ELEMENTS(aArrays); iRuleset++)
+ {
+ if (aArrays[iRuleset] == NULL)
+ continue;
+
+ for (iRule = 0; aArrays[iRuleset][iRule].pszName != NULL; iRule++)
+ {
+ if (RTStrCmp(pszName, aArrays[iRuleset][iRule].pszName) == 0)
+ aList.push_back(&aArrays[iRuleset][iRule]);
+ }
+ }
+}
+
+const char *BusAssignmentManager::State::findAlias(const char *pszDev)
+{
+ for (size_t iAlias = 0; iAlias < RT_ELEMENTS(g_aDeviceAliases); iAlias++)
+ {
+ if (strcmp(pszDev, g_aDeviceAliases[iAlias].pszDevName) == 0)
+ return g_aDeviceAliases[iAlias].pszDevAlias;
+ }
+ return NULL;
+}
+
+static bool RuleComparator(const DeviceAssignmentRule *r1, const DeviceAssignmentRule *r2)
+{
+ return (r1->iPriority > r2->iPriority);
+}
+
+HRESULT BusAssignmentManager::State::autoAssign(const char *pszName, PCIBusAddress& Address)
+{
+ PCIRulesList matchingRules;
+
+ addMatchingRules(pszName, matchingRules);
+ const char *pszAlias = findAlias(pszName);
+ if (pszAlias)
+ addMatchingRules(pszAlias, matchingRules);
+
+ AssertMsg(matchingRules.size() > 0, ("No rule for %s(%s)\n", pszName, pszAlias));
+
+ stable_sort(matchingRules.begin(), matchingRules.end(), RuleComparator);
+
+ for (size_t iRule = 0; iRule < matchingRules.size(); iRule++)
+ {
+ const DeviceAssignmentRule *rule = matchingRules[iRule];
+
+ Address.miBus = rule->iBus;
+ Address.miDevice = rule->iDevice;
+ Address.miFn = rule->iFn;
+
+ if (checkAvailable(Address))
+ return S_OK;
+ }
+ AssertLogRelMsgFailed(("BusAssignmentManager: All possible candidate positions for %s exhausted\n", pszName));
+
+ return E_INVALIDARG;
+}
+
+bool BusAssignmentManager::State::checkAvailable(PCIBusAddress& Address)
+{
+ PCIMap::const_iterator it = mPCIMap.find(Address);
+
+ return (it == mPCIMap.end());
+}
+
+void BusAssignmentManager::State::listAttachedPCIDevices(std::vector<PCIDeviceInfo> &aAttached)
+{
+ aAttached.resize(mPCIMap.size());
+
+ size_t i = 0;
+ PCIDeviceInfo dev;
+ for (PCIMap::const_iterator it = mPCIMap.begin(); it != mPCIMap.end(); ++it, ++i)
+ {
+ dev.strDeviceName = it->second.szDevName;
+ dev.guestAddress = it->first;
+ dev.hostAddress = it->second.HostAddress;
+ aAttached[i] = dev;
+ }
+}
+
+BusAssignmentManager::BusAssignmentManager()
+ : pState(NULL)
+{
+ pState = new State();
+ Assert(pState);
+}
+
+BusAssignmentManager::~BusAssignmentManager()
+{
+ if (pState)
+ {
+ delete pState;
+ pState = NULL;
+ }
+}
+
+BusAssignmentManager *BusAssignmentManager::createInstance(PCVMMR3VTABLE pVMM, ChipsetType_T chipsetType, IommuType_T iommuType)
+{
+ BusAssignmentManager *pInstance = new BusAssignmentManager();
+ pInstance->pState->init(pVMM, chipsetType, iommuType);
+ Assert(pInstance);
+ return pInstance;
+}
+
+void BusAssignmentManager::AddRef()
+{
+ ASMAtomicIncS32(&pState->cRefCnt);
+}
+
+void BusAssignmentManager::Release()
+{
+ if (ASMAtomicDecS32(&pState->cRefCnt) == 0)
+ delete this;
+}
+
+DECLINLINE(HRESULT) InsertConfigInteger(PCVMMR3VTABLE pVMM, PCFGMNODE pCfg, const char *pszName, uint64_t u64)
+{
+ int vrc = pVMM->pfnCFGMR3InsertInteger(pCfg, pszName, u64);
+ if (RT_FAILURE(vrc))
+ return E_INVALIDARG;
+
+ return S_OK;
+}
+
+DECLINLINE(HRESULT) InsertConfigNode(PCVMMR3VTABLE pVMM, PCFGMNODE pNode, const char *pcszName, PCFGMNODE *ppChild)
+{
+ int vrc = pVMM->pfnCFGMR3InsertNode(pNode, pcszName, ppChild);
+ if (RT_FAILURE(vrc))
+ return E_INVALIDARG;
+
+ return S_OK;
+}
+
+
+HRESULT BusAssignmentManager::assignPCIDeviceImpl(const char *pszDevName,
+ PCFGMNODE pCfg,
+ PCIBusAddress& GuestAddress,
+ PCIBusAddress HostAddress,
+ bool fGuestAddressRequired)
+{
+ HRESULT hrc = S_OK;
+
+ if (!GuestAddress.valid())
+ hrc = pState->autoAssign(pszDevName, GuestAddress);
+ else
+ {
+ bool fAvailable = pState->checkAvailable(GuestAddress);
+
+ if (!fAvailable)
+ {
+ if (fGuestAddressRequired)
+ hrc = E_ACCESSDENIED;
+ else
+ hrc = pState->autoAssign(pszDevName, GuestAddress);
+ }
+ }
+
+ if (FAILED(hrc))
+ return hrc;
+
+ Assert(GuestAddress.valid() && pState->checkAvailable(GuestAddress));
+
+ hrc = pState->record(pszDevName, GuestAddress, HostAddress);
+ if (FAILED(hrc))
+ return hrc;
+
+ PCVMMR3VTABLE const pVMM = pState->mpVMM;
+ if (pCfg)
+ {
+ hrc = InsertConfigInteger(pVMM, pCfg, "PCIBusNo", GuestAddress.miBus);
+ if (FAILED(hrc))
+ return hrc;
+ hrc = InsertConfigInteger(pVMM, pCfg, "PCIDeviceNo", GuestAddress.miDevice);
+ if (FAILED(hrc))
+ return hrc;
+ hrc = InsertConfigInteger(pVMM, pCfg, "PCIFunctionNo", GuestAddress.miFn);
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /* Check if the bus is still unknown, i.e. the bridge to it is missing */
+ if ( GuestAddress.miBus > 0
+ && !hasPCIDevice(pState->mpszBridgeName, GuestAddress.miBus - 1))
+ {
+ PCFGMNODE pDevices = pVMM->pfnCFGMR3GetParent(pVMM->pfnCFGMR3GetParent(pCfg));
+ AssertLogRelMsgReturn(pDevices, ("BusAssignmentManager: cannot find base device configuration\n"), E_UNEXPECTED);
+ PCFGMNODE pBridges = pVMM->pfnCFGMR3GetChild(pDevices, "ich9pcibridge");
+ AssertLogRelMsgReturn(pBridges, ("BusAssignmentManager: cannot find bridge configuration base\n"), E_UNEXPECTED);
+
+ /* Device should be on a not yet existing bus, add it automatically */
+ for (int iBridge = 0; iBridge <= GuestAddress.miBus - 1; iBridge++)
+ {
+ if (!hasPCIDevice(pState->mpszBridgeName, iBridge))
+ {
+ PCIBusAddress BridgeGuestAddress;
+ hrc = pState->autoAssign(pState->mpszBridgeName, BridgeGuestAddress);
+ if (FAILED(hrc))
+ return hrc;
+ if (BridgeGuestAddress.miBus > iBridge)
+ AssertLogRelMsgFailedReturn(("BusAssignmentManager: cannot create bridge for bus %i because the possible parent bus positions are exhausted\n", iBridge + 1), E_UNEXPECTED);
+
+ PCFGMNODE pInst;
+ InsertConfigNode(pVMM, pBridges, Utf8StrFmt("%d", iBridge).c_str(), &pInst);
+ InsertConfigInteger(pVMM, pInst, "Trusted", 1);
+ hrc = assignPCIDevice(pState->mpszBridgeName, pInst);
+ if (FAILED(hrc))
+ return hrc;
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+
+bool BusAssignmentManager::findPCIAddress(const char *pszDevName, int iInstance, PCIBusAddress& Address)
+{
+ return pState->findPCIAddress(pszDevName, iInstance, Address);
+}
+void BusAssignmentManager::listAttachedPCIDevices(std::vector<PCIDeviceInfo> &aAttached)
+{
+ pState->listAttachedPCIDevices(aAttached);
+}
diff --git a/src/VBox/Main/src-client/ClientTokenHolder.cpp b/src/VBox/Main/src-client/ClientTokenHolder.cpp
new file mode 100644
index 00000000..7a85c90c
--- /dev/null
+++ b/src/VBox/Main/src-client/ClientTokenHolder.cpp
@@ -0,0 +1,347 @@
+/* $Id: ClientTokenHolder.cpp $ */
+/** @file
+ *
+ * VirtualBox API client session token holder (in the client process)
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_SESSION
+#include "LoggingNew.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/log.h>
+#include <iprt/semaphore.h>
+#include <iprt/process.h>
+
+#ifdef VBOX_WITH_SYS_V_IPC_SESSION_WATCHER
+# include <errno.h>
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <sys/ipc.h>
+# include <sys/sem.h>
+#endif
+
+#include <VBox/com/defs.h>
+
+#include "ClientTokenHolder.h"
+#include "SessionImpl.h"
+
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+/** client token holder thread */
+static DECLCALLBACK(int) ClientTokenHolderThread(RTTHREAD hThreadSelf, void *pvUser);
+#endif
+
+
+Session::ClientTokenHolder::ClientTokenHolder()
+{
+ AssertReleaseFailed();
+}
+
+Session::ClientTokenHolder::~ClientTokenHolder()
+{
+ /* release the client token */
+#if defined(RT_OS_WINDOWS)
+
+ if (mSem && mThreadSem)
+ {
+ /*
+ * tell the thread holding the token to release it;
+ * it will close mSem handle
+ */
+ ::SetEvent(mSem);
+ /* wait for the thread to finish */
+ ::WaitForSingleObject(mThreadSem, INFINITE);
+ ::CloseHandle(mThreadSem);
+
+ mThreadSem = NULL;
+ mSem = NULL;
+ mThread = NIL_RTTHREAD;
+ }
+
+#elif defined(RT_OS_OS2)
+
+ if (mThread != NIL_RTTHREAD)
+ {
+ Assert(mSem != NIL_RTSEMEVENT);
+
+ /* tell the thread holding the token to release it */
+ int vrc = RTSemEventSignal(mSem);
+ AssertRC(vrc == NO_ERROR);
+
+ /* wait for the thread to finish */
+ vrc = RTThreadUserWait(mThread, RT_INDEFINITE_WAIT);
+ Assert(RT_SUCCESS(vrc) || vrc == VERR_INTERRUPTED);
+
+ mThread = NIL_RTTHREAD;
+ }
+
+ if (mSem != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(mSem);
+ mSem = NIL_RTSEMEVENT;
+ }
+
+#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
+
+ if (mSem >= 0)
+ {
+ ::sembuf sop = { 0, 1, SEM_UNDO };
+ ::semop(mSem, &sop, 1);
+
+ mSem = -1;
+ }
+
+#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+
+ if (!mToken.isNull())
+ {
+ mToken->Abandon();
+ mToken.setNull();
+ }
+
+#else
+# error "Port me!"
+#endif
+}
+
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+Session::ClientTokenHolder::ClientTokenHolder(const Utf8Str &strTokenId) :
+ mClientTokenId(strTokenId)
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+Session::ClientTokenHolder::ClientTokenHolder(IToken *aToken) :
+ mToken(aToken)
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+{
+#ifdef CTHSEMTYPE
+ mSem = CTHSEMARG;
+#endif
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ mThread = NIL_RTTHREAD;
+#endif
+
+#if defined(RT_OS_WINDOWS)
+ mThreadSem = CTHTHREADSEMARG;
+
+ /*
+ * Since there is no guarantee that the constructor and destructor will be
+ * called in the same thread, we need a separate thread to hold the token.
+ */
+
+ mThreadSem = ::CreateEvent(NULL, FALSE, FALSE, NULL);
+ AssertMsgReturnVoid(mThreadSem,
+ ("Cannot create an event sem, err=%d", ::GetLastError()));
+
+ void *data[3];
+ data[0] = (void*)strTokenId.c_str();
+ data[1] = (void*)mThreadSem;
+ data[2] = 0; /* will get an output from the thread */
+
+ /* create a thread to hold the token until signalled to release it */
+ int vrc = RTThreadCreate(&mThread, ClientTokenHolderThread, (void*)data, 0, RTTHREADTYPE_MAIN_WORKER, 0, "IPCHolder");
+ AssertRCReturnVoid(vrc);
+
+ /* wait until thread init is completed */
+ DWORD wrc = ::WaitForSingleObject(mThreadSem, INFINITE);
+ AssertMsg(wrc == WAIT_OBJECT_0, ("Wait failed, err=%d\n", ::GetLastError()));
+ Assert(data[2]);
+
+ if (wrc == WAIT_OBJECT_0 && data[2])
+ {
+ /* memorize the event sem we should signal in close() */
+ mSem = (HANDLE)data[2];
+ }
+ else
+ {
+ ::CloseHandle(mThreadSem);
+ mThreadSem = NULL;
+ }
+#elif defined(RT_OS_OS2)
+ /*
+ * Since there is no guarantee that the constructor and destructor will be
+ * called in the same thread, we need a separate thread to hold the token.
+ */
+
+ int vrc = RTSemEventCreate(&mSem);
+ AssertRCReturnVoid(vrc);
+
+ void *data[3];
+ data[0] = (void*)strTokenId.c_str();
+ data[1] = (void*)mSem;
+ data[2] = (void*)false; /* will get the thread result here */
+
+ /* create a thread to hold the token until signalled to release it */
+ vrc = RTThreadCreate(&mThread, ClientTokenHolderThread, (void *) data,
+ 0, RTTHREADTYPE_MAIN_WORKER, 0, "IPCHolder");
+ AssertRCReturnVoid(vrc);
+ /* wait until thread init is completed */
+ vrc = RTThreadUserWait(mThread, RT_INDEFINITE_WAIT);
+ AssertReturnVoid(RT_SUCCESS(vrc) || vrc == VERR_INTERRUPTED);
+
+ /* the thread must succeed */
+ AssertReturnVoid((bool)data[2]);
+
+#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
+
+# ifdef VBOX_WITH_NEW_SYS_V_KEYGEN
+ key_t key = RTStrToUInt32(strTokenId.c_str());
+ AssertMsgReturnVoid(key != 0,
+ ("Key value of 0 is not valid for client token"));
+# else /* !VBOX_WITH_NEW_SYS_V_KEYGEN */
+ char *pszSemName = NULL;
+ RTStrUtf8ToCurrentCP(&pszSemName, strTokenId);
+ key_t key = ::ftok(pszSemName, 'V');
+ RTStrFree(pszSemName);
+# endif /* !VBOX_WITH_NEW_SYS_V_KEYGEN */
+ int s = ::semget(key, 0, 0);
+ AssertMsgReturnVoid(s >= 0,
+ ("Cannot open semaphore, errno=%d", errno));
+
+ /* grab the semaphore */
+ ::sembuf sop = { 0, -1, SEM_UNDO };
+ int rv = ::semop(s, &sop, 1);
+ AssertMsgReturnVoid(rv == 0,
+ ("Cannot grab semaphore, errno=%d", errno));
+ mSem = s;
+
+#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+
+ /* nothing to do */
+
+#else
+# error "Port me!"
+#endif
+}
+
+bool Session::ClientTokenHolder::isReady()
+{
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ return mSem != CTHSEMARG;
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ return !mToken.isNull();
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+}
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+/** client token holder thread */
+DECLCALLBACK(int) ClientTokenHolderThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+ LogFlowFuncEnter();
+
+ Assert(pvUser);
+
+ void **data = (void **)pvUser;
+
+# if defined(RT_OS_WINDOWS)
+ Utf8Str strSessionId = (const char *)data[0];
+ HANDLE initDoneSem = (HANDLE)data[1];
+
+ Bstr bstrSessionId(strSessionId);
+ HANDLE mutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, bstrSessionId.raw());
+
+ //AssertMsg(mutex, ("cannot open token, err=%u\n", ::GetLastError()));
+ AssertLogRelMsg(mutex, ("cannot open token %ls, err=%u\n", bstrSessionId.raw(), ::GetLastError()));
+ if (mutex)
+ {
+ /* grab the token */
+ DWORD wrc = ::WaitForSingleObject(mutex, 0);
+ AssertMsg(wrc == WAIT_OBJECT_0, ("cannot grab token, err=%d\n", wrc));
+ if (wrc == WAIT_OBJECT_0)
+ {
+ HANDLE finishSem = ::CreateEvent(NULL, FALSE, FALSE, NULL);
+ AssertMsg(finishSem, ("cannot create event sem, err=%d\n", ::GetLastError()));
+ if (finishSem)
+ {
+ data[2] = (void*)finishSem;
+ /* signal we're done with init */
+ ::SetEvent(initDoneSem);
+ /* wait until we're signaled to release the token */
+ ::WaitForSingleObject(finishSem, INFINITE);
+ /* release the token */
+ LogFlow(("ClientTokenHolderThread(): releasing token...\n"));
+ BOOL fRc = ::ReleaseMutex(mutex);
+ AssertMsg(fRc, ("cannot release token, err=%d\n", ::GetLastError())); NOREF(fRc);
+ ::CloseHandle(mutex);
+ ::CloseHandle(finishSem);
+ }
+ }
+ }
+
+ /* signal we're done */
+ ::SetEvent(initDoneSem);
+# elif defined(RT_OS_OS2)
+ Utf8Str strSessionId = (const char *)data[0];
+ RTSEMEVENT finishSem = (RTSEMEVENT)data[1];
+
+ LogFlowFunc(("strSessionId='%s', finishSem=%p\n", strSessionId.c_str(), finishSem));
+
+ HMTX mutex = NULLHANDLE;
+ APIRET arc = ::DosOpenMutexSem((PSZ)strSessionId.c_str(), &mutex);
+ AssertMsg(arc == NO_ERROR, ("cannot open token, arc=%ld\n", arc));
+
+ if (arc == NO_ERROR)
+ {
+ /* grab the token */
+ LogFlowFunc(("grabbing token...\n"));
+ arc = ::DosRequestMutexSem(mutex, SEM_IMMEDIATE_RETURN);
+ AssertMsg(arc == NO_ERROR, ("cannot grab token, arc=%ld\n", arc));
+ if (arc == NO_ERROR)
+ {
+ /* store the answer */
+ data[2] = (void*)true;
+ /* signal we're done */
+ int vrc = RTThreadUserSignal(Thread);
+ AssertRC(vrc);
+
+ /* wait until we're signaled to release the token */
+ LogFlowFunc(("waiting for termination signal..\n"));
+ vrc = RTSemEventWait(finishSem, RT_INDEFINITE_WAIT);
+ Assert(arc == ERROR_INTERRUPT || ERROR_TIMEOUT);
+
+ /* release the token */
+ LogFlowFunc(("releasing token...\n"));
+ arc = ::DosReleaseMutexSem(mutex);
+ AssertMsg(arc == NO_ERROR, ("cannot release token, arc=%ld\n", arc));
+ }
+ ::DosCloseMutexSem(mutex);
+ }
+
+ /* store the answer */
+ data[1] = (void*)false;
+ /* signal we're done */
+ int vrc = RTThreadUserSignal(Thread);
+ AssertRC(vrc);
+# else
+# error "Port me!"
+# endif
+
+ LogFlowFuncLeave();
+
+ return 0;
+}
+#endif
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/CloudGateway.cpp b/src/VBox/Main/src-client/CloudGateway.cpp
new file mode 100644
index 00000000..60a468b1
--- /dev/null
+++ b/src/VBox/Main/src-client/CloudGateway.cpp
@@ -0,0 +1,309 @@
+/* $Id: CloudGateway.cpp $ */
+/** @file
+ * Implementation of local and cloud gateway management.
+ */
+
+/*
+ * Copyright (C) 2019-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE
+
+/* Make sure all the stdint.h macros are included - must come first! */
+#ifndef __STDC_LIMIT_MACROS
+# define __STDC_LIMIT_MACROS
+#endif
+#ifndef __STDC_CONSTANT_MACROS
+# define __STDC_CONSTANT_MACROS
+#endif
+
+#include "LoggingNew.h"
+#include "ApplianceImpl.h"
+#include "CloudNetworkImpl.h"
+#include "CloudGateway.h"
+
+#include <iprt/http.h>
+#include <iprt/inifile.h>
+#include <iprt/net.h>
+#include <iprt/path.h>
+#include <iprt/vfs.h>
+#include <iprt/uri.h>
+#ifdef DEBUG
+#include <iprt/file.h>
+#include <VBox/com/utils.h>
+#endif
+
+#ifdef VBOX_WITH_LIBSSH
+/* Prevent inclusion of Winsock2.h */
+#define _WINSOCK2API_
+#include <libssh/libssh.h>
+#endif /* VBOX_WITH_LIBSSH */
+
+
+static HRESULT setMacAddress(const Utf8Str& str, RTMAC& mac)
+{
+ int rc = RTNetStrToMacAddr(str.c_str(), &mac);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("CLOUD-NET: Invalid MAC address '%s'\n", str.c_str()));
+ return E_INVALIDARG;
+ }
+ return S_OK;
+}
+
+
+HRESULT GatewayInfo::setCloudMacAddress(const Utf8Str& mac)
+{
+ return setMacAddress(mac, mCloudMacAddress);
+}
+
+
+HRESULT GatewayInfo::setLocalMacAddress(const Utf8Str& mac)
+{
+ return setMacAddress(mac, mLocalMacAddress);
+}
+
+
+class CloudError
+{
+public:
+ CloudError(HRESULT hrc, const Utf8Str& strText) : mHrc(hrc), mText(strText) {};
+ HRESULT getRc() { return mHrc; };
+ Utf8Str getText() { return mText; };
+
+private:
+ HRESULT mHrc;
+ Utf8Str mText;
+};
+
+
+static void handleErrors(HRESULT hrc, const char *pszFormat, ...)
+{
+ if (FAILED(hrc))
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ Utf8Str strError(pszFormat, va);
+ va_end(va);
+ LogRel(("CLOUD-NET: %s (rc=%x)\n", strError.c_str(), hrc));
+ throw CloudError(hrc, strError);
+ }
+
+}
+
+
+class CloudClient
+{
+public:
+ CloudClient(ComPtr<IVirtualBox> virtualBox, const Bstr& strProvider, const Bstr& strProfile);
+ ~CloudClient() {};
+
+ void startCloudGateway(const ComPtr<ICloudNetwork> &network, GatewayInfo& gateway);
+ void stopCloudGateway(const GatewayInfo& gateway);
+
+private:
+ ComPtr<ICloudProviderManager> mManager;
+ ComPtr<ICloudProvider> mProvider;
+ ComPtr<ICloudProfile> mProfile;
+ ComPtr<ICloudClient> mClient;
+};
+
+
+CloudClient::CloudClient(ComPtr<IVirtualBox> virtualBox, const Bstr& strProvider, const Bstr& strProfile)
+{
+ HRESULT hrc = virtualBox->COMGETTER(CloudProviderManager)(mManager.asOutParam());
+ handleErrors(hrc, "Failed to obtain cloud provider manager object");
+ hrc = mManager->GetProviderByShortName(strProvider.raw(), mProvider.asOutParam());
+ handleErrors(hrc, "Failed to obtain cloud provider '%ls'", strProvider.raw());
+ hrc = mProvider->GetProfileByName(strProfile.raw(), mProfile.asOutParam());
+ handleErrors(hrc, "Failed to obtain cloud profile '%ls'", strProfile.raw());
+ hrc = mProfile->CreateCloudClient(mClient.asOutParam());
+ handleErrors(hrc, "Failed to create cloud client");
+}
+
+
+void CloudClient::startCloudGateway(const ComPtr<ICloudNetwork> &network, GatewayInfo& gateway)
+{
+ ComPtr<IProgress> progress;
+ ComPtr<ICloudNetworkGatewayInfo> gatewayInfo;
+ HRESULT hrc = mClient->StartCloudNetworkGateway(network, Bstr(gateway.mPublicSshKey).raw(),
+ gatewayInfo.asOutParam(), progress.asOutParam());
+ handleErrors(hrc, "Failed to launch compute instance");
+ hrc = progress->WaitForCompletion(-1);
+ handleErrors(hrc, "Failed to launch compute instance (wait)");
+
+ Bstr instanceId;
+ hrc = gatewayInfo->COMGETTER(InstanceId)(instanceId.asOutParam());
+ handleErrors(hrc, "Failed to get launched compute instance id");
+ gateway.mGatewayInstanceId = instanceId;
+
+ Bstr publicIP;
+ hrc = gatewayInfo->COMGETTER(PublicIP)(publicIP.asOutParam());
+ handleErrors(hrc, "Failed to get cloud gateway public IP address");
+ gateway.mCloudPublicIp = publicIP;
+
+ Bstr secondaryPublicIP;
+ hrc = gatewayInfo->COMGETTER(SecondaryPublicIP)(secondaryPublicIP.asOutParam());
+ handleErrors(hrc, "Failed to get cloud gateway secondary public IP address");
+ gateway.mCloudSecondaryPublicIp = secondaryPublicIP;
+
+ Bstr macAddress;
+ hrc = gatewayInfo->COMGETTER(MacAddress)(macAddress.asOutParam());
+ handleErrors(hrc, "Failed to get cloud gateway public IP address");
+ gateway.setCloudMacAddress(macAddress);
+}
+
+
+void CloudClient::stopCloudGateway(const GatewayInfo& gateway)
+{
+ ComPtr<IProgress> progress;
+ HRESULT hrc = mClient->TerminateInstance(Bstr(gateway.mGatewayInstanceId).raw(), progress.asOutParam());
+ handleErrors(hrc, "Failed to terminate compute instance");
+#if 0
+ /* Someday we may want to wait until the cloud gateway has terminated. */
+ hrc = progress->WaitForCompletion(-1);
+ handleErrors(hrc, "Failed to terminate compute instance (wait)");
+#endif
+}
+
+
+HRESULT startCloudGateway(ComPtr<IVirtualBox> virtualBox, ComPtr<ICloudNetwork> network, GatewayInfo& gateway)
+{
+ HRESULT hrc = S_OK;
+
+ try {
+ hrc = network->COMGETTER(Provider)(gateway.mCloudProvider.asOutParam());
+ hrc = network->COMGETTER(Profile)(gateway.mCloudProfile.asOutParam());
+ CloudClient client(virtualBox, gateway.mCloudProvider, gateway.mCloudProfile);
+ client.startCloudGateway(network, gateway);
+ }
+ catch (CloudError e)
+ {
+ hrc = e.getRc();
+ }
+
+ return hrc;
+}
+
+
+HRESULT stopCloudGateway(ComPtr<IVirtualBox> virtualBox, GatewayInfo& gateway)
+{
+ if (gateway.mGatewayInstanceId.isEmpty())
+ return S_OK;
+
+ LogRel(("CLOUD-NET: Terminating cloud gateway instance '%s'...\n", gateway.mGatewayInstanceId.c_str()));
+
+ HRESULT hrc = S_OK;
+ try {
+ CloudClient client(virtualBox, gateway.mCloudProvider, gateway.mCloudProfile);
+ client.stopCloudGateway(gateway);
+#if 0
+# ifdef DEBUG
+ char szKeyPath[RTPATH_MAX];
+
+ int rc = GetVBoxUserHomeDirectory(szKeyPath, sizeof(szKeyPath), false /* fCreateDir */);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPathAppend(szKeyPath, sizeof(szKeyPath), "gateway-key.pem");
+ AssertRCReturn(rc, rc);
+ rc = RTFileDelete(szKeyPath);
+ if (RT_FAILURE(rc))
+ LogRel(("WARNING! Failed to delete private key %s with rc=%d\n", szKeyPath, rc));
+ }
+ else
+ LogRel(("WARNING! Failed to get VirtualBox user home directory with '%Rrc'\n", rc));
+# endif /* DEBUG */
+#endif
+ }
+ catch (CloudError e)
+ {
+ hrc = e.getRc();
+ LogRel(("CLOUD-NET: Failed to terminate cloud gateway instance (rc=%x).\n", hrc));
+ }
+ gateway.mGatewayInstanceId.setNull();
+ return hrc;
+}
+
+
+HRESULT generateKeys(GatewayInfo& gateway)
+{
+#ifndef VBOX_WITH_LIBSSH
+ RT_NOREF(gateway);
+ return E_NOTIMPL;
+#else /* VBOX_WITH_LIBSSH */
+ ssh_key single_use_key;
+ int rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &single_use_key);
+ if (rc != SSH_OK)
+ {
+ LogRel(("Failed to generate a key pair. rc = %d\n", rc));
+ return E_FAIL;
+ }
+
+ char *pstrKey = NULL;
+ rc = ssh_pki_export_privkey_base64(single_use_key, NULL, NULL, NULL, &pstrKey);
+ if (rc != SSH_OK)
+ {
+ LogRel(("Failed to export private key. rc = %d\n", rc));
+ return E_FAIL;
+ }
+ gateway.mPrivateSshKey = pstrKey;
+#if 0
+# ifdef DEBUG
+ char szConfigPath[RTPATH_MAX];
+
+ rc = GetVBoxUserHomeDirectory(szConfigPath, sizeof(szConfigPath), false /* fCreateDir */);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPathAppend(szConfigPath, sizeof(szConfigPath), "gateway-key.pem");
+ AssertRCReturn(rc, rc);
+ rc = ssh_pki_export_privkey_file(single_use_key, NULL, NULL, NULL, szConfigPath);
+ if (rc != SSH_OK)
+ {
+ LogRel(("Failed to export private key to %s with rc=%d\n", szConfigPath, rc));
+ return E_FAIL;
+ }
+# ifndef RT_OS_WINDOWS
+ rc = RTPathSetMode(szConfigPath, RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR); /* Satisfy ssh client */
+ AssertRCReturn(rc, rc);
+# endif
+ }
+ else
+ {
+ LogRel(("Failed to get VirtualBox user home directory with '%Rrc'\n", rc));
+ return E_FAIL;
+ }
+# endif /* DEBUG */
+#endif
+ ssh_string_free_char(pstrKey);
+ pstrKey = NULL;
+ rc = ssh_pki_export_pubkey_base64(single_use_key, &pstrKey);
+ if (rc != SSH_OK)
+ {
+ LogRel(("Failed to export public key. rc = %d\n", rc));
+ return E_FAIL;
+ }
+ gateway.mPublicSshKey = Utf8StrFmt("ssh-rsa %s single-use-key", pstrKey);
+ ssh_string_free_char(pstrKey);
+ ssh_key_free(single_use_key);
+
+ return S_OK;
+#endif /* VBOX_WITH_LIBSSH */
+}
diff --git a/src/VBox/Main/src-client/ConsoleImpl.cpp b/src/VBox/Main/src-client/ConsoleImpl.cpp
new file mode 100644
index 00000000..dbde726f
--- /dev/null
+++ b/src/VBox/Main/src-client/ConsoleImpl.cpp
@@ -0,0 +1,11964 @@
+/* $Id: ConsoleImpl.cpp $ */
+/** @file
+ * VBox Console COM Class implementation
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE
+#include "LoggingNew.h"
+
+/** @todo Move the TAP mess back into the driver! */
+#if defined(RT_OS_WINDOWS)
+#elif defined(RT_OS_LINUX)
+# include <errno.h>
+# include <sys/ioctl.h>
+# include <sys/poll.h>
+# include <sys/fcntl.h>
+# include <sys/types.h>
+# include <sys/wait.h>
+# include <net/if.h>
+# include <linux/if_tun.h>
+# include <stdio.h>
+# include <stdlib.h>
+# include <string.h>
+#elif defined(RT_OS_FREEBSD)
+# include <errno.h>
+# include <sys/ioctl.h>
+# include <sys/poll.h>
+# include <sys/fcntl.h>
+# include <sys/types.h>
+# include <sys/wait.h>
+# include <stdio.h>
+# include <stdlib.h>
+# include <string.h>
+#elif defined(RT_OS_SOLARIS)
+# include <iprt/coredumper.h>
+#endif
+
+#include "ConsoleImpl.h"
+
+#include "Global.h"
+#include "VirtualBoxErrorInfoImpl.h"
+#include "GuestImpl.h"
+#include "KeyboardImpl.h"
+#include "MouseImpl.h"
+#include "DisplayImpl.h"
+#include "MachineDebuggerImpl.h"
+#include "USBDeviceImpl.h"
+#include "RemoteUSBDeviceImpl.h"
+#include "SharedFolderImpl.h"
+#ifdef VBOX_WITH_AUDIO_VRDE
+# include "DrvAudioVRDE.h"
+#endif
+#ifdef VBOX_WITH_AUDIO_RECORDING
+# include "DrvAudioRec.h"
+#endif
+#ifdef VBOX_WITH_USB_CARDREADER
+# include "UsbCardReader.h"
+#endif
+#include "ProgressImpl.h"
+#include "ConsoleVRDPServer.h"
+#include "VMMDev.h"
+#ifdef VBOX_WITH_EXTPACK
+# include "ExtPackManagerImpl.h"
+#endif
+#include "BusAssignmentManager.h"
+#include "PCIDeviceAttachmentImpl.h"
+#include "EmulatedUSBImpl.h"
+#include "NvramStoreImpl.h"
+#include "StringifyEnums.h"
+
+#include "VBoxEvents.h"
+#include "AutoCaller.h"
+#include "ThreadTask.h"
+
+#ifdef VBOX_WITH_RECORDING
+# include "Recording.h"
+#endif
+
+#include "CryptoUtils.h"
+
+#include <VBox/com/array.h>
+#include "VBox/com/ErrorInfo.h"
+#include <VBox/com/listeners.h>
+
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/ldr.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/base64.h>
+#include <iprt/memsafer.h>
+
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/vmm/vmapi.h>
+#include <VBox/vmm/vmm.h>
+#include <VBox/vmm/pdmapi.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmasynccompletion.h>
+#include <VBox/vmm/pdmnetifs.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#ifdef VBOX_WITH_USB
+# include <VBox/vmm/pdmusb.h>
+#endif
+#ifdef VBOX_WITH_NETSHAPER
+# include <VBox/vmm/pdmnetshaper.h>
+#endif /* VBOX_WITH_NETSHAPER */
+#include <VBox/vmm/mm.h>
+#include <VBox/vmm/ssm.h>
+#include <VBox/err.h>
+#include <VBox/param.h>
+#include <VBox/vusb.h>
+
+#include <VBox/VMMDev.h>
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+# include <VBox/HostServices/VBoxClipboardSvc.h>
+#endif
+#include <VBox/HostServices/DragAndDropSvc.h>
+#ifdef VBOX_WITH_GUEST_PROPS
+# include <VBox/HostServices/GuestPropertySvc.h>
+# include <VBox/com/array.h>
+#endif
+
+#ifdef VBOX_OPENSSL_FIPS
+# include <openssl/crypto.h>
+#endif
+
+#include <set>
+#include <algorithm>
+#include <memory> // for auto_ptr
+#include <vector>
+#include <exception>// std::exception
+
+// VMTask and friends
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Task structure for asynchronous VM operations.
+ *
+ * Once created, the task structure adds itself as a Console caller. This means:
+ *
+ * 1. The user must check for #rc() before using the created structure
+ * (e.g. passing it as a thread function argument). If #rc() returns a
+ * failure, the Console object may not be used by the task.
+ * 2. On successful initialization, the structure keeps the Console caller
+ * until destruction (to ensure Console remains in the Ready state and won't
+ * be accidentally uninitialized). Forgetting to delete the created task
+ * will lead to Console::uninit() stuck waiting for releasing all added
+ * callers.
+ *
+ * If \a aUsesVMPtr parameter is true, the task structure will also add itself
+ * as a Console::mpUVM caller with the same meaning as above. See
+ * Console::addVMCaller() for more info.
+ */
+class VMTask: public ThreadTask
+{
+public:
+ VMTask(Console *aConsole,
+ Progress *aProgress,
+ const ComPtr<IProgress> &aServerProgress,
+ bool aUsesVMPtr)
+ : ThreadTask("GenericVMTask"),
+ mConsole(aConsole),
+ mConsoleCaller(aConsole),
+ mProgress(aProgress),
+ mServerProgress(aServerProgress),
+ mRC(E_FAIL),
+ mpSafeVMPtr(NULL)
+ {
+ AssertReturnVoid(aConsole);
+ mRC = mConsoleCaller.rc();
+ if (FAILED(mRC))
+ return;
+ if (aUsesVMPtr)
+ {
+ mpSafeVMPtr = new Console::SafeVMPtr(aConsole);
+ if (!mpSafeVMPtr->isOk())
+ mRC = mpSafeVMPtr->rc();
+ }
+ }
+
+ virtual ~VMTask()
+ {
+ releaseVMCaller();
+ }
+
+ HRESULT rc() const { return mRC; }
+ bool isOk() const { return SUCCEEDED(rc()); }
+
+ /** Releases the VM caller before destruction. Not normally necessary. */
+ void releaseVMCaller()
+ {
+ if (mpSafeVMPtr)
+ {
+ delete mpSafeVMPtr;
+ mpSafeVMPtr = NULL;
+ }
+ }
+
+ const ComObjPtr<Console> mConsole;
+ AutoCaller mConsoleCaller;
+ const ComObjPtr<Progress> mProgress;
+ Utf8Str mErrorMsg;
+ const ComPtr<IProgress> mServerProgress;
+
+private:
+ HRESULT mRC;
+ Console::SafeVMPtr *mpSafeVMPtr;
+};
+
+
+class VMPowerUpTask : public VMTask
+{
+public:
+ VMPowerUpTask(Console *aConsole,
+ Progress *aProgress)
+ : VMTask(aConsole, aProgress, NULL /* aServerProgress */, false /* aUsesVMPtr */)
+ , mpfnConfigConstructor(NULL)
+ , mStartPaused(false)
+ , mTeleporterEnabled(FALSE)
+ , m_pKeyStore(NULL)
+ {
+ m_strTaskName = "VMPwrUp";
+ }
+
+ PFNCFGMCONSTRUCTOR mpfnConfigConstructor;
+ Utf8Str mSavedStateFile;
+ Utf8Str mKeyStore;
+ Utf8Str mKeyId;
+ Console::SharedFolderDataMap mSharedFolders;
+ bool mStartPaused;
+ BOOL mTeleporterEnabled;
+ SecretKeyStore *m_pKeyStore;
+
+ /* array of progress objects for hard disk reset operations */
+ typedef std::list<ComPtr<IProgress> > ProgressList;
+ ProgressList hardDiskProgresses;
+
+ void handler()
+ {
+ Console::i_powerUpThreadTask(this);
+ }
+
+};
+
+class VMPowerDownTask : public VMTask
+{
+public:
+ VMPowerDownTask(Console *aConsole,
+ const ComPtr<IProgress> &aServerProgress)
+ : VMTask(aConsole, NULL /* aProgress */, aServerProgress,
+ true /* aUsesVMPtr */)
+ {
+ m_strTaskName = "VMPwrDwn";
+ }
+
+ void handler()
+ {
+ Console::i_powerDownThreadTask(this);
+ }
+};
+
+// Handler for global events
+////////////////////////////////////////////////////////////////////////////////
+inline static const char *networkAdapterTypeToName(NetworkAdapterType_T adapterType);
+
+class VmEventListener
+{
+public:
+ VmEventListener()
+ {}
+
+
+ HRESULT init(Console *aConsole)
+ {
+ mConsole = aConsole;
+ return S_OK;
+ }
+
+ void uninit()
+ {
+ }
+
+ virtual ~VmEventListener()
+ {
+ }
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
+ {
+ switch(aType)
+ {
+ case VBoxEventType_OnNATRedirect:
+ {
+ ComPtr<IMachine> pMachine = mConsole->i_machine();
+ ComPtr<INATRedirectEvent> pNREv = aEvent;
+ HRESULT rc = E_FAIL;
+ Assert(pNREv);
+
+ Bstr id;
+ rc = pNREv->COMGETTER(MachineId)(id.asOutParam());
+ AssertComRC(rc);
+ if (id != mConsole->i_getId())
+ break;
+
+ /* now we can operate with redirects */
+ NATProtocol_T proto = (NATProtocol_T)0;
+ pNREv->COMGETTER(Proto)(&proto);
+ BOOL fRemove;
+ pNREv->COMGETTER(Remove)(&fRemove);
+ Bstr hostIp;
+ pNREv->COMGETTER(HostIP)(hostIp.asOutParam());
+ LONG hostPort = 0;
+ pNREv->COMGETTER(HostPort)(&hostPort);
+ Bstr guestIp;
+ pNREv->COMGETTER(GuestIP)(guestIp.asOutParam());
+ LONG guestPort = 0;
+ pNREv->COMGETTER(GuestPort)(&guestPort);
+ ULONG ulSlot;
+ rc = pNREv->COMGETTER(Slot)(&ulSlot);
+ AssertComRC(rc);
+ if (FAILED(rc))
+ break;
+ mConsole->i_onNATRedirectRuleChanged(ulSlot, fRemove, proto, hostIp.raw(), hostPort, guestIp.raw(), guestPort);
+ break;
+ }
+
+ case VBoxEventType_OnHostNameResolutionConfigurationChange:
+ {
+ mConsole->i_onNATDnsChanged();
+ break;
+ }
+
+ case VBoxEventType_OnHostPCIDevicePlug:
+ {
+ // handle if needed
+ break;
+ }
+
+ case VBoxEventType_OnExtraDataChanged:
+ {
+ ComPtr<IExtraDataChangedEvent> pEDCEv = aEvent;
+ Bstr strMachineId;
+ HRESULT hrc = pEDCEv->COMGETTER(MachineId)(strMachineId.asOutParam());
+ if (FAILED(hrc)) break;
+
+ Bstr strKey;
+ hrc = pEDCEv->COMGETTER(Key)(strKey.asOutParam());
+ if (FAILED(hrc)) break;
+
+ Bstr strVal;
+ hrc = pEDCEv->COMGETTER(Value)(strVal.asOutParam());
+ if (FAILED(hrc)) break;
+
+ mConsole->i_onExtraDataChange(strMachineId.raw(), strKey.raw(), strVal.raw());
+ break;
+ }
+
+ default:
+ AssertFailed();
+ }
+
+ return S_OK;
+ }
+private:
+ ComObjPtr<Console> mConsole;
+};
+
+typedef ListenerImpl<VmEventListener, Console*> VmEventListenerImpl;
+
+
+VBOX_LISTENER_DECLARE(VmEventListenerImpl)
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+Console::Console()
+ : mSavedStateDataLoaded(false)
+ , mConsoleVRDPServer(NULL)
+ , mfVRDEChangeInProcess(false)
+ , mfVRDEChangePending(false)
+ , mhModVMM(NIL_RTLDRMOD)
+ , mpVMM(NULL)
+ , mpUVM(NULL)
+ , mVMCallers(0)
+ , mVMZeroCallersSem(NIL_RTSEMEVENT)
+ , mVMDestroying(false)
+ , mVMPoweredOff(false)
+ , mVMIsAlreadyPoweringOff(false)
+ , mfSnapshotFolderSizeWarningShown(false)
+ , mfSnapshotFolderExt4WarningShown(false)
+ , mfSnapshotFolderDiskTypeShown(false)
+ , mfVMHasUsbController(false)
+ , mfTurnResetIntoPowerOff(false)
+ , mfPowerOffCausedByReset(false)
+ , mpVmm2UserMethods(NULL)
+ , m_pVMMDev(NULL)
+ , mAudioVRDE(NULL)
+#ifdef VBOX_WITH_USB_CARDREADER
+ , mUsbCardReader(NULL)
+#endif
+ , mBusMgr(NULL)
+ , m_pKeyStore(NULL)
+ , mpIfSecKey(NULL)
+ , mpIfSecKeyHlp(NULL)
+ , mVMStateChangeCallbackDisabled(false)
+ , mfUseHostClipboard(true)
+ , mMachineState(MachineState_PoweredOff)
+ , mhLdrModCrypto(NIL_RTLDRMOD)
+ , mcRefsCrypto(0)
+ , mpCryptoIf(NULL)
+{
+}
+
+Console::~Console()
+{}
+
+HRESULT Console::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+
+ mcLedSets = 0;
+ RT_ZERO(maLedSets);
+
+ MYVMM2USERMETHODS *pVmm2UserMethods = (MYVMM2USERMETHODS *)RTMemAllocZ(sizeof(*mpVmm2UserMethods) + sizeof(Console *));
+ if (!pVmm2UserMethods)
+ return E_OUTOFMEMORY;
+ pVmm2UserMethods->u32Magic = VMM2USERMETHODS_MAGIC;
+ pVmm2UserMethods->u32Version = VMM2USERMETHODS_VERSION;
+ pVmm2UserMethods->pfnSaveState = Console::i_vmm2User_SaveState;
+ pVmm2UserMethods->pfnNotifyEmtInit = Console::i_vmm2User_NotifyEmtInit;
+ pVmm2UserMethods->pfnNotifyEmtTerm = Console::i_vmm2User_NotifyEmtTerm;
+ pVmm2UserMethods->pfnNotifyPdmtInit = Console::i_vmm2User_NotifyPdmtInit;
+ pVmm2UserMethods->pfnNotifyPdmtTerm = Console::i_vmm2User_NotifyPdmtTerm;
+ pVmm2UserMethods->pfnNotifyResetTurnedIntoPowerOff = Console::i_vmm2User_NotifyResetTurnedIntoPowerOff;
+ pVmm2UserMethods->pfnQueryGenericObject = Console::i_vmm2User_QueryGenericObject;
+ pVmm2UserMethods->u32EndMagic = VMM2USERMETHODS_MAGIC;
+ pVmm2UserMethods->pConsole = this;
+ mpVmm2UserMethods = pVmm2UserMethods;
+
+ MYPDMISECKEY *pIfSecKey = (MYPDMISECKEY *)RTMemAllocZ(sizeof(*mpIfSecKey) + sizeof(Console *));
+ if (!pIfSecKey)
+ return E_OUTOFMEMORY;
+ pIfSecKey->pfnKeyRetain = Console::i_pdmIfSecKey_KeyRetain;
+ pIfSecKey->pfnKeyRelease = Console::i_pdmIfSecKey_KeyRelease;
+ pIfSecKey->pfnPasswordRetain = Console::i_pdmIfSecKey_PasswordRetain;
+ pIfSecKey->pfnPasswordRelease = Console::i_pdmIfSecKey_PasswordRelease;
+ pIfSecKey->pConsole = this;
+ mpIfSecKey = pIfSecKey;
+
+ MYPDMISECKEYHLP *pIfSecKeyHlp = (MYPDMISECKEYHLP *)RTMemAllocZ(sizeof(*mpIfSecKeyHlp) + sizeof(Console *));
+ if (!pIfSecKeyHlp)
+ return E_OUTOFMEMORY;
+ pIfSecKeyHlp->pfnKeyMissingNotify = Console::i_pdmIfSecKeyHlp_KeyMissingNotify;
+ pIfSecKeyHlp->pConsole = this;
+ mpIfSecKeyHlp = pIfSecKeyHlp;
+
+ mRemoteUsbIf.pvUser = this;
+ mRemoteUsbIf.pfnQueryRemoteUsbBackend = Console::i_usbQueryRemoteUsbBackend;
+
+ return BaseFinalConstruct();
+}
+
+void Console::FinalRelease()
+{
+ LogFlowThisFunc(("\n"));
+
+ uninit();
+
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/** @todo r=bird: aLockType is always LockType_VM. */
+HRESULT Console::initWithMachine(IMachine *aMachine, IInternalMachineControl *aControl, LockType_T aLockType)
+{
+ AssertReturn(aMachine && aControl, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aMachine=%p, aControl=%p\n", aMachine, aControl));
+
+ HRESULT rc = E_FAIL;
+
+ unconst(mMachine) = aMachine;
+ unconst(mControl) = aControl;
+
+ /* Cache essential properties and objects, and create child objects */
+
+ rc = mMachine->COMGETTER(State)(&mMachineState);
+ AssertComRCReturnRC(rc);
+
+ rc = mMachine->COMGETTER(Id)(mstrUuid.asOutParam());
+ AssertComRCReturnRC(rc);
+
+#ifdef VBOX_WITH_EXTPACK
+ unconst(mptrExtPackManager).createObject();
+ rc = mptrExtPackManager->initExtPackManager(NULL, VBOXEXTPACKCTX_VM_PROCESS);
+ AssertComRCReturnRC(rc);
+#endif
+
+ // Event source may be needed by other children
+ unconst(mEventSource).createObject();
+ rc = mEventSource->init();
+ AssertComRCReturnRC(rc);
+
+ mcAudioRefs = 0;
+ mcVRDPClients = 0;
+ mu32SingleRDPClientId = 0;
+ mcGuestCredentialsProvided = false;
+
+ /* Now the VM specific parts */
+ /** @todo r=bird: aLockType is always LockType_VM. */
+ if (aLockType == LockType_VM)
+ {
+ /* Load the VMM. We won't continue without it being successfully loaded here. */
+ rc = i_loadVMM();
+ AssertComRCReturnRC(rc);
+
+ rc = mMachine->COMGETTER(VRDEServer)(unconst(mVRDEServer).asOutParam());
+ AssertComRCReturnRC(rc);
+
+ unconst(mGuest).createObject();
+ rc = mGuest->init(this);
+ AssertComRCReturnRC(rc);
+
+ ULONG cCpus = 1;
+ rc = mMachine->COMGETTER(CPUCount)(&cCpus);
+ mGuest->i_setCpuCount(cCpus);
+
+ unconst(mKeyboard).createObject();
+ rc = mKeyboard->init(this);
+ AssertComRCReturnRC(rc);
+
+ unconst(mMouse).createObject();
+ rc = mMouse->init(this);
+ AssertComRCReturnRC(rc);
+
+ unconst(mDisplay).createObject();
+ rc = mDisplay->init(this);
+ AssertComRCReturnRC(rc);
+
+ unconst(mVRDEServerInfo).createObject();
+ rc = mVRDEServerInfo->init(this);
+ AssertComRCReturnRC(rc);
+
+ unconst(mEmulatedUSB).createObject();
+ rc = mEmulatedUSB->init(this);
+ AssertComRCReturnRC(rc);
+
+ /* Init the NVRAM store. */
+ ComPtr<INvramStore> pNvramStore;
+ rc = aMachine->COMGETTER(NonVolatileStore)(pNvramStore.asOutParam());
+ AssertComRCReturnRC(rc);
+
+ Bstr strNonVolatilePath;
+ pNvramStore->COMGETTER(NonVolatileStorageFile)(strNonVolatilePath.asOutParam());
+
+ unconst(mptrNvramStore).createObject();
+ rc = mptrNvramStore->init(this, strNonVolatilePath);
+ AssertComRCReturnRC(rc);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ Bstr bstrNvramKeyId;
+ Bstr bstrNvramKeyStore;
+ rc = pNvramStore->COMGETTER(KeyId)(bstrNvramKeyId.asOutParam());
+ AssertComRCReturnRC(rc);
+ rc = pNvramStore->COMGETTER(KeyStore)(bstrNvramKeyStore.asOutParam());
+ AssertComRCReturnRC(rc);
+ const Utf8Str strNvramKeyId(bstrNvramKeyId);
+ const Utf8Str strNvramKeyStore(bstrNvramKeyStore);
+ mptrNvramStore->i_updateEncryptionSettings(strNvramKeyId, strNvramKeyStore);
+#endif
+
+ /* Grab global and machine shared folder lists */
+
+ rc = i_fetchSharedFolders(true /* aGlobal */);
+ AssertComRCReturnRC(rc);
+ rc = i_fetchSharedFolders(false /* aGlobal */);
+ AssertComRCReturnRC(rc);
+
+ /* Create other child objects */
+
+ unconst(mConsoleVRDPServer) = new ConsoleVRDPServer(this);
+ AssertReturn(mConsoleVRDPServer, E_FAIL);
+
+ /* Figure out size of meAttachmentType vector */
+ ComPtr<IVirtualBox> pVirtualBox;
+ rc = aMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ AssertComRC(rc);
+ ComPtr<ISystemProperties> pSystemProperties;
+ if (pVirtualBox)
+ pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ ChipsetType_T chipsetType = ChipsetType_PIIX3;
+ aMachine->COMGETTER(ChipsetType)(&chipsetType);
+ ULONG maxNetworkAdapters = 0;
+ if (pSystemProperties)
+ pSystemProperties->GetMaxNetworkAdapters(chipsetType, &maxNetworkAdapters);
+ meAttachmentType.resize(maxNetworkAdapters);
+ for (ULONG slot = 0; slot < maxNetworkAdapters; ++slot)
+ meAttachmentType[slot] = NetworkAttachmentType_Null;
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ unconst(mAudioVRDE) = new AudioVRDE(this);
+ AssertReturn(mAudioVRDE, E_FAIL);
+#endif
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ unconst(mRecording.mAudioRec) = new AudioVideoRec(this);
+ AssertReturn(mRecording.mAudioRec, E_FAIL);
+#endif
+
+#ifdef VBOX_WITH_USB_CARDREADER
+ unconst(mUsbCardReader) = new UsbCardReader(this);
+ AssertReturn(mUsbCardReader, E_FAIL);
+#endif
+
+ m_cDisksPwProvided = 0;
+ m_cDisksEncrypted = 0;
+
+ unconst(m_pKeyStore) = new SecretKeyStore(true /* fKeyBufNonPageable */);
+ AssertReturn(m_pKeyStore, E_FAIL);
+
+ /* VirtualBox events registration. */
+ {
+ ComPtr<IEventSource> pES;
+ rc = pVirtualBox->COMGETTER(EventSource)(pES.asOutParam());
+ AssertComRC(rc);
+ ComObjPtr<VmEventListenerImpl> aVmListener;
+ aVmListener.createObject();
+ aVmListener->init(new VmEventListener(), this);
+ mVmListener = aVmListener;
+ com::SafeArray<VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnNATRedirect);
+ eventTypes.push_back(VBoxEventType_OnHostNameResolutionConfigurationChange);
+ eventTypes.push_back(VBoxEventType_OnHostPCIDevicePlug);
+ eventTypes.push_back(VBoxEventType_OnExtraDataChanged);
+ rc = pES->RegisterListener(aVmListener, ComSafeArrayAsInParam(eventTypes), true);
+ AssertComRC(rc);
+ }
+ }
+
+ /* Confirm a successful initialization when it's the case */
+ autoInitSpan.setSucceeded();
+
+#ifdef VBOX_WITH_EXTPACK
+ /* Let the extension packs have a go at things (hold no locks). */
+ if (SUCCEEDED(rc))
+ mptrExtPackManager->i_callAllConsoleReadyHooks(this);
+#endif
+
+ LogFlowThisFuncLeave();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the Console object.
+ */
+void Console::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ {
+ LogFlowThisFunc(("Already uninitialized.\n"));
+ LogFlowThisFuncLeave();
+ return;
+ }
+
+ LogFlowThisFunc(("initFailed()=%d\n", autoUninitSpan.initFailed()));
+ if (mVmListener)
+ {
+ ComPtr<IEventSource> pES;
+ ComPtr<IVirtualBox> pVirtualBox;
+ HRESULT rc = mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ AssertComRC(rc);
+ if (SUCCEEDED(rc) && !pVirtualBox.isNull())
+ {
+ rc = pVirtualBox->COMGETTER(EventSource)(pES.asOutParam());
+ AssertComRC(rc);
+ if (!pES.isNull())
+ {
+ rc = pES->UnregisterListener(mVmListener);
+ AssertComRC(rc);
+ }
+ }
+ mVmListener.setNull();
+ }
+
+ /* power down the VM if necessary */
+ if (mpUVM)
+ {
+ i_powerDown();
+ Assert(mpUVM == NULL);
+ }
+
+ if (mVMZeroCallersSem != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(mVMZeroCallersSem);
+ mVMZeroCallersSem = NIL_RTSEMEVENT;
+ }
+
+ if (mpVmm2UserMethods)
+ {
+ RTMemFree((void *)mpVmm2UserMethods);
+ mpVmm2UserMethods = NULL;
+ }
+
+ if (mpIfSecKey)
+ {
+ RTMemFree((void *)mpIfSecKey);
+ mpIfSecKey = NULL;
+ }
+
+ if (mpIfSecKeyHlp)
+ {
+ RTMemFree((void *)mpIfSecKeyHlp);
+ mpIfSecKeyHlp = NULL;
+ }
+
+#ifdef VBOX_WITH_USB_CARDREADER
+ if (mUsbCardReader)
+ {
+ delete mUsbCardReader;
+ unconst(mUsbCardReader) = NULL;
+ }
+#endif
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ if (mAudioVRDE)
+ {
+ delete mAudioVRDE;
+ unconst(mAudioVRDE) = NULL;
+ }
+#endif
+
+#ifdef VBOX_WITH_RECORDING
+ i_recordingDestroy();
+# ifdef VBOX_WITH_AUDIO_RECORDING
+ if (mRecording.mAudioRec)
+ {
+ delete mRecording.mAudioRec;
+ unconst(mRecording.mAudioRec) = NULL;
+ }
+# endif
+#endif /* VBOX_WITH_RECORDING */
+
+ // if the VM had a VMMDev with an HGCM thread, then remove that here
+ if (m_pVMMDev)
+ {
+ delete m_pVMMDev;
+ unconst(m_pVMMDev) = NULL;
+ }
+
+ if (mBusMgr)
+ {
+ mBusMgr->Release();
+ mBusMgr = NULL;
+ }
+
+ if (m_pKeyStore)
+ {
+ delete m_pKeyStore;
+ unconst(m_pKeyStore) = NULL;
+ }
+
+ m_mapGlobalSharedFolders.clear();
+ m_mapMachineSharedFolders.clear();
+ m_mapSharedFolders.clear(); // console instances
+
+ mRemoteUSBDevices.clear();
+ mUSBDevices.clear();
+
+ if (mVRDEServerInfo)
+ {
+ mVRDEServerInfo->uninit();
+ unconst(mVRDEServerInfo).setNull();
+ }
+
+ if (mEmulatedUSB)
+ {
+ mEmulatedUSB->uninit();
+ unconst(mEmulatedUSB).setNull();
+ }
+
+ if (mDebugger)
+ {
+ mDebugger->uninit();
+ unconst(mDebugger).setNull();
+ }
+
+ if (mDisplay)
+ {
+ mDisplay->uninit();
+ unconst(mDisplay).setNull();
+ }
+
+ if (mMouse)
+ {
+ mMouse->uninit();
+ unconst(mMouse).setNull();
+ }
+
+ if (mKeyboard)
+ {
+ mKeyboard->uninit();
+ unconst(mKeyboard).setNull();
+ }
+
+ if (mGuest)
+ {
+ mGuest->uninit();
+ unconst(mGuest).setNull();
+ }
+
+ if (mConsoleVRDPServer)
+ {
+ delete mConsoleVRDPServer;
+ unconst(mConsoleVRDPServer) = NULL;
+ }
+
+ if (mptrNvramStore)
+ {
+ mptrNvramStore->uninit();
+ unconst(mptrNvramStore).setNull();
+ }
+
+ unconst(mVRDEServer).setNull();
+
+ unconst(mControl).setNull();
+ unconst(mMachine).setNull();
+
+ // we don't perform uninit() as it's possible that some pending event refers to this source
+ unconst(mEventSource).setNull();
+
+#ifdef VBOX_WITH_EXTPACK
+ unconst(mptrExtPackManager).setNull();
+#endif
+
+ /* Unload the VMM. */
+ mpVMM = NULL;
+ if (mhModVMM != NIL_RTLDRMOD)
+ {
+ RTLdrClose(mhModVMM);
+ mhModVMM = NIL_RTLDRMOD;
+ }
+
+ /* Release memory held by the LED sets. */
+ for (size_t idxSet = 0; idxSet < mcLedSets; idxSet++)
+ {
+ RTMemFree(maLedSets[idxSet].papLeds);
+ RTMemFree(maLedSets[idxSet].paSubTypes);
+ maLedSets[idxSet].papLeds = NULL;
+ maLedSets[idxSet].paSubTypes = NULL;
+ }
+ mcLedSets = 0;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ /* Close the release log before unloading the cryptographic module. */
+ if (m_fEncryptedLog)
+ {
+ PRTLOGGER pLogEnc = RTLogRelSetDefaultInstance(NULL);
+ int vrc = RTLogDestroy(pLogEnc);
+ AssertRC(vrc);
+ }
+#endif
+
+ HRESULT rc = i_unloadCryptoIfModule();
+ AssertComRC(rc);
+
+ LogFlowThisFuncLeave();
+}
+
+#ifdef VBOX_WITH_GUEST_PROPS
+
+/**
+ * Wrapper for VMMDev::i_guestPropertiesHandleVMReset
+ */
+HRESULT Console::i_pullGuestProperties(ComSafeArrayOut(BSTR, names), ComSafeArrayOut(BSTR, values),
+ ComSafeArrayOut(LONG64, timestamps), ComSafeArrayOut(BSTR, flags))
+{
+ AssertReturn(mControl.isNotNull(), VERR_INVALID_POINTER);
+ return mControl->PullGuestProperties(ComSafeArrayOutArg(names), ComSafeArrayOutArg(values),
+ ComSafeArrayOutArg(timestamps), ComSafeArrayOutArg(flags));
+}
+
+/**
+ * Handles guest properties on a VM reset.
+ *
+ * We must delete properties that are flagged TRANSRESET.
+ *
+ * @todo r=bird: Would be more efficient if we added a request to the HGCM
+ * service to do this instead of detouring thru VBoxSVC.
+ * (IMachine::SetGuestProperty ends up in VBoxSVC, which in turns calls
+ * back into the VM process and the HGCM service.)
+ */
+void Console::i_guestPropertiesHandleVMReset(void)
+{
+ std::vector<Utf8Str> names;
+ std::vector<Utf8Str> values;
+ std::vector<LONG64> timestamps;
+ std::vector<Utf8Str> flags;
+ HRESULT hrc = i_enumerateGuestProperties("*", names, values, timestamps, flags);
+ if (SUCCEEDED(hrc))
+ {
+ for (size_t i = 0; i < flags.size(); i++)
+ {
+ /* Delete all properties which have the flag "TRANSRESET". */
+ if (flags[i].contains("TRANSRESET", Utf8Str::CaseInsensitive))
+ {
+ hrc = mMachine->DeleteGuestProperty(Bstr(names[i]).raw());
+ if (FAILED(hrc))
+ LogRel(("RESET: Could not delete transient property \"%s\", rc=%Rhrc\n",
+ names[i].c_str(), hrc));
+ }
+ }
+ }
+ else
+ LogRel(("RESET: Unable to enumerate guest properties, rc=%Rhrc\n", hrc));
+}
+
+bool Console::i_guestPropertiesVRDPEnabled(void)
+{
+ Bstr value;
+ HRESULT hrc = mMachine->GetExtraData(Bstr("VBoxInternal2/EnableGuestPropertiesVRDP").raw(),
+ value.asOutParam());
+ if ( hrc == S_OK
+ && value == "1")
+ return true;
+ return false;
+}
+
+void Console::i_guestPropertiesVRDPUpdateLogon(uint32_t u32ClientId, const char *pszUser, const char *pszDomain)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ char szPropNm[256];
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Name", u32ClientId);
+ Bstr clientName;
+ mVRDEServerInfo->COMGETTER(ClientName)(clientName.asOutParam());
+
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ clientName.raw(),
+ bstrReadOnlyGuest.raw());
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/User", u32ClientId);
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ Bstr(pszUser).raw(),
+ bstrReadOnlyGuest.raw());
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Domain", u32ClientId);
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ Bstr(pszDomain).raw(),
+ bstrReadOnlyGuest.raw());
+
+ char szClientId[64];
+ RTStrPrintf(szClientId, sizeof(szClientId), "%u", u32ClientId);
+ mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VRDP/LastConnectedClient").raw(),
+ Bstr(szClientId).raw(),
+ bstrReadOnlyGuest.raw());
+
+ return;
+}
+
+void Console::i_guestPropertiesVRDPUpdateActiveClient(uint32_t u32ClientId)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("%d\n", u32ClientId));
+
+ Bstr bstrFlags(L"RDONLYGUEST,TRANSIENT");
+
+ char szClientId[64];
+ RTStrPrintf(szClientId, sizeof(szClientId), "%u", u32ClientId);
+
+ mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VRDP/ActiveClient").raw(),
+ Bstr(szClientId).raw(),
+ bstrFlags.raw());
+
+ return;
+}
+
+void Console::i_guestPropertiesVRDPUpdateNameChange(uint32_t u32ClientId, const char *pszName)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ char szPropNm[256];
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Name", u32ClientId);
+ Bstr clientName(pszName);
+
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ clientName.raw(),
+ bstrReadOnlyGuest.raw());
+
+}
+
+void Console::i_guestPropertiesVRDPUpdateIPAddrChange(uint32_t u32ClientId, const char *pszIPAddr)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ char szPropNm[256];
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/IPAddr", u32ClientId);
+ Bstr clientIPAddr(pszIPAddr);
+
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ clientIPAddr.raw(),
+ bstrReadOnlyGuest.raw());
+
+}
+
+void Console::i_guestPropertiesVRDPUpdateLocationChange(uint32_t u32ClientId, const char *pszLocation)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ char szPropNm[256];
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Location", u32ClientId);
+ Bstr clientLocation(pszLocation);
+
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ clientLocation.raw(),
+ bstrReadOnlyGuest.raw());
+
+}
+
+void Console::i_guestPropertiesVRDPUpdateOtherInfoChange(uint32_t u32ClientId, const char *pszOtherInfo)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ char szPropNm[256];
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/OtherInfo", u32ClientId);
+ Bstr clientOtherInfo(pszOtherInfo);
+
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ clientOtherInfo.raw(),
+ bstrReadOnlyGuest.raw());
+
+}
+
+void Console::i_guestPropertiesVRDPUpdateClientAttach(uint32_t u32ClientId, bool fAttached)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ char szPropNm[256];
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Attach", u32ClientId);
+
+ Bstr bstrValue = fAttached? "1": "0";
+
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ bstrValue.raw(),
+ bstrReadOnlyGuest.raw());
+}
+
+void Console::i_guestPropertiesVRDPUpdateDisconnect(uint32_t u32ClientId)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ char szPropNm[256];
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Name", u32ClientId);
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(), NULL,
+ bstrReadOnlyGuest.raw());
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/User", u32ClientId);
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(), NULL,
+ bstrReadOnlyGuest.raw());
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Domain", u32ClientId);
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(), NULL,
+ bstrReadOnlyGuest.raw());
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Attach", u32ClientId);
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(), NULL,
+ bstrReadOnlyGuest.raw());
+
+ char szClientId[64];
+ RTStrPrintf(szClientId, sizeof(szClientId), "%d", u32ClientId);
+ mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VRDP/LastDisconnectedClient").raw(),
+ Bstr(szClientId).raw(),
+ bstrReadOnlyGuest.raw());
+
+ return;
+}
+
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+#ifdef VBOX_WITH_EXTPACK
+/**
+ * Used by VRDEServer and others to talke to the extension pack manager.
+ *
+ * @returns The extension pack manager.
+ */
+ExtPackManager *Console::i_getExtPackManager()
+{
+ return mptrExtPackManager;
+}
+#endif
+
+
+int Console::i_VRDPClientLogon(uint32_t u32ClientId, const char *pszUser, const char *pszPassword, const char *pszDomain)
+{
+ LogFlowFuncEnter();
+ LogFlowFunc(("%d, %s, %s, %s\n", u32ClientId, pszUser, pszPassword, pszDomain));
+
+ AutoCaller autoCaller(this);
+ if (!autoCaller.isOk())
+ {
+ /* Console has been already uninitialized, deny request */
+ LogRel(("AUTH: Access denied (Console uninitialized).\n"));
+ LogFlowFuncLeave();
+ return VERR_ACCESS_DENIED;
+ }
+
+ Guid uuid = Guid(i_getId());
+
+ AuthType_T authType = AuthType_Null;
+ HRESULT hrc = mVRDEServer->COMGETTER(AuthType)(&authType);
+ AssertComRCReturn(hrc, VERR_ACCESS_DENIED);
+
+ ULONG authTimeout = 0;
+ hrc = mVRDEServer->COMGETTER(AuthTimeout)(&authTimeout);
+ AssertComRCReturn(hrc, VERR_ACCESS_DENIED);
+
+ AuthResult result = AuthResultAccessDenied;
+ AuthGuestJudgement guestJudgement = AuthGuestNotAsked;
+
+ LogFlowFunc(("Auth type %d\n", authType));
+
+ LogRel(("AUTH: User: [%s]. Domain: [%s]. Authentication type: [%s]\n",
+ pszUser, pszDomain,
+ authType == AuthType_Null?
+ "Null":
+ (authType == AuthType_External?
+ "External":
+ (authType == AuthType_Guest?
+ "Guest":
+ "INVALID"
+ )
+ )
+ ));
+
+ switch (authType)
+ {
+ case AuthType_Null:
+ {
+ result = AuthResultAccessGranted;
+ break;
+ }
+
+ case AuthType_External:
+ {
+ /* Call the external library. */
+ result = mConsoleVRDPServer->Authenticate(uuid, guestJudgement, pszUser, pszPassword, pszDomain, u32ClientId);
+
+ if (result != AuthResultDelegateToGuest)
+ {
+ break;
+ }
+
+ LogRel(("AUTH: Delegated to guest.\n"));
+
+ LogFlowFunc(("External auth asked for guest judgement\n"));
+ }
+ RT_FALL_THRU();
+
+ case AuthType_Guest:
+ {
+ guestJudgement = AuthGuestNotReacted;
+
+ /** @todo r=dj locking required here for m_pVMMDev? */
+ PPDMIVMMDEVPORT pDevPort;
+ if ( m_pVMMDev
+ && ((pDevPort = m_pVMMDev->getVMMDevPort()))
+ )
+ {
+ /* Issue the request to guest. Assume that the call does not require EMT. It should not. */
+
+ /* Ask the guest to judge these credentials. */
+ uint32_t u32GuestFlags = VMMDEV_SETCREDENTIALS_JUDGE;
+
+ int rc = pDevPort->pfnSetCredentials(pDevPort, pszUser, pszPassword, pszDomain, u32GuestFlags);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Wait for guest. */
+ rc = m_pVMMDev->WaitCredentialsJudgement(authTimeout, &u32GuestFlags);
+
+ if (RT_SUCCESS(rc))
+ {
+ switch (u32GuestFlags & ( VMMDEV_CREDENTIALS_JUDGE_OK
+ | VMMDEV_CREDENTIALS_JUDGE_DENY
+ | VMMDEV_CREDENTIALS_JUDGE_NOJUDGEMENT))
+ {
+ case VMMDEV_CREDENTIALS_JUDGE_DENY: guestJudgement = AuthGuestAccessDenied; break;
+ case VMMDEV_CREDENTIALS_JUDGE_NOJUDGEMENT: guestJudgement = AuthGuestNoJudgement; break;
+ case VMMDEV_CREDENTIALS_JUDGE_OK: guestJudgement = AuthGuestAccessGranted; break;
+ default:
+ LogFlowFunc(("Invalid guest flags %#08x!!!\n", u32GuestFlags)); break;
+ }
+ }
+ else
+ {
+ LogFlowFunc(("Wait for credentials judgement rc = %Rrc!!!\n", rc));
+ }
+
+ LogFlowFunc(("Guest judgement %d\n", guestJudgement));
+ }
+ else
+ {
+ LogFlowFunc(("Could not set credentials rc = %Rrc!!!\n", rc));
+ }
+ }
+
+ if (authType == AuthType_External)
+ {
+ LogRel(("AUTH: Guest judgement %d.\n", guestJudgement));
+ LogFlowFunc(("External auth called again with guest judgement = %d\n", guestJudgement));
+ result = mConsoleVRDPServer->Authenticate(uuid, guestJudgement, pszUser, pszPassword, pszDomain, u32ClientId);
+ }
+ else
+ {
+ switch (guestJudgement)
+ {
+ case AuthGuestAccessGranted:
+ result = AuthResultAccessGranted;
+ break;
+ default:
+ result = AuthResultAccessDenied;
+ break;
+ }
+ }
+ } break;
+
+ default:
+ AssertFailed();
+ }
+
+ LogFlowFunc(("Result = %d\n", result));
+ LogFlowFuncLeave();
+
+ if (result != AuthResultAccessGranted)
+ {
+ /* Reject. */
+ LogRel(("AUTH: Access denied.\n"));
+ return VERR_ACCESS_DENIED;
+ }
+
+ LogRel(("AUTH: Access granted.\n"));
+
+ /* Multiconnection check must be made after authentication, so bad clients would not interfere with a good one. */
+ BOOL allowMultiConnection = FALSE;
+ hrc = mVRDEServer->COMGETTER(AllowMultiConnection)(&allowMultiConnection);
+ AssertComRCReturn(hrc, VERR_ACCESS_DENIED);
+
+ BOOL reuseSingleConnection = FALSE;
+ hrc = mVRDEServer->COMGETTER(ReuseSingleConnection)(&reuseSingleConnection);
+ AssertComRCReturn(hrc, VERR_ACCESS_DENIED);
+
+ LogFlowFunc(("allowMultiConnection %d, reuseSingleConnection = %d, mcVRDPClients = %d, mu32SingleRDPClientId = %d\n",
+ allowMultiConnection, reuseSingleConnection, mcVRDPClients, mu32SingleRDPClientId));
+
+ if (allowMultiConnection == FALSE)
+ {
+ /* Note: the 'mcVRDPClients' variable is incremented in ClientConnect callback, which is called when the client
+ * is successfully connected, that is after the ClientLogon callback. Therefore the mcVRDPClients
+ * value is 0 for first client.
+ */
+ if (mcVRDPClients != 0)
+ {
+ Assert(mcVRDPClients == 1);
+ /* There is a client already.
+ * If required drop the existing client connection and let the connecting one in.
+ */
+ if (reuseSingleConnection)
+ {
+ LogRel(("AUTH: Multiple connections are not enabled. Disconnecting existing client.\n"));
+ mConsoleVRDPServer->DisconnectClient(mu32SingleRDPClientId, false);
+ }
+ else
+ {
+ /* Reject. */
+ LogRel(("AUTH: Multiple connections are not enabled. Access denied.\n"));
+ return VERR_ACCESS_DENIED;
+ }
+ }
+
+ /* Save the connected client id. From now on it will be necessary to disconnect this one. */
+ mu32SingleRDPClientId = u32ClientId;
+ }
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ i_guestPropertiesVRDPUpdateLogon(u32ClientId, pszUser, pszDomain);
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+ /* Check if the successfully verified credentials are to be sent to the guest. */
+ BOOL fProvideGuestCredentials = FALSE;
+
+ Bstr value;
+ hrc = mMachine->GetExtraData(Bstr("VRDP/ProvideGuestCredentials").raw(),
+ value.asOutParam());
+ if (SUCCEEDED(hrc) && value == "1")
+ {
+ /* Provide credentials only if there are no logged in users. */
+ Utf8Str noLoggedInUsersValue;
+ LONG64 ul64Timestamp = 0;
+ Utf8Str flags;
+
+ hrc = i_getGuestProperty("/VirtualBox/GuestInfo/OS/NoLoggedInUsers",
+ &noLoggedInUsersValue, &ul64Timestamp, &flags);
+
+ if (SUCCEEDED(hrc) && noLoggedInUsersValue != "false")
+ {
+ /* And only if there are no connected clients. */
+ if (ASMAtomicCmpXchgBool(&mcGuestCredentialsProvided, true, false))
+ {
+ fProvideGuestCredentials = TRUE;
+ }
+ }
+ }
+
+ /** @todo r=dj locking required here for m_pVMMDev? */
+ if ( fProvideGuestCredentials
+ && m_pVMMDev)
+ {
+ uint32_t u32GuestFlags = VMMDEV_SETCREDENTIALS_GUESTLOGON;
+
+ PPDMIVMMDEVPORT pDevPort = m_pVMMDev->getVMMDevPort();
+ if (pDevPort)
+ {
+ int rc = pDevPort->pfnSetCredentials(m_pVMMDev->getVMMDevPort(),
+ pszUser, pszPassword, pszDomain, u32GuestFlags);
+ AssertRC(rc);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+void Console::i_VRDPClientStatusChange(uint32_t u32ClientId, const char *pszStatus)
+{
+ LogFlowFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ LogFlowFunc(("%s\n", pszStatus));
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ /* Parse the status string. */
+ if (RTStrICmp(pszStatus, "ATTACH") == 0)
+ {
+ i_guestPropertiesVRDPUpdateClientAttach(u32ClientId, true);
+ }
+ else if (RTStrICmp(pszStatus, "DETACH") == 0)
+ {
+ i_guestPropertiesVRDPUpdateClientAttach(u32ClientId, false);
+ }
+ else if (RTStrNICmp(pszStatus, "NAME=", strlen("NAME=")) == 0)
+ {
+ i_guestPropertiesVRDPUpdateNameChange(u32ClientId, pszStatus + strlen("NAME="));
+ }
+ else if (RTStrNICmp(pszStatus, "CIPA=", strlen("CIPA=")) == 0)
+ {
+ i_guestPropertiesVRDPUpdateIPAddrChange(u32ClientId, pszStatus + strlen("CIPA="));
+ }
+ else if (RTStrNICmp(pszStatus, "CLOCATION=", strlen("CLOCATION=")) == 0)
+ {
+ i_guestPropertiesVRDPUpdateLocationChange(u32ClientId, pszStatus + strlen("CLOCATION="));
+ }
+ else if (RTStrNICmp(pszStatus, "COINFO=", strlen("COINFO=")) == 0)
+ {
+ i_guestPropertiesVRDPUpdateOtherInfoChange(u32ClientId, pszStatus + strlen("COINFO="));
+ }
+#endif
+
+ LogFlowFuncLeave();
+}
+
+void Console::i_VRDPClientConnect(uint32_t u32ClientId)
+{
+ LogFlowFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ uint32_t u32Clients = ASMAtomicIncU32(&mcVRDPClients);
+ VMMDev *pDev;
+ PPDMIVMMDEVPORT pPort;
+ if ( (u32Clients == 1)
+ && ((pDev = i_getVMMDev()))
+ && ((pPort = pDev->getVMMDevPort()))
+ )
+ {
+ pPort->pfnVRDPChange(pPort,
+ true,
+ VRDP_EXPERIENCE_LEVEL_FULL); /** @todo configurable */
+ }
+
+ NOREF(u32ClientId);
+ mDisplay->i_VRDPConnectionEvent(true);
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ i_guestPropertiesVRDPUpdateActiveClient(u32ClientId);
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+ LogFlowFuncLeave();
+ return;
+}
+
+void Console::i_VRDPClientDisconnect(uint32_t u32ClientId,
+ uint32_t fu32Intercepted)
+{
+ LogFlowFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AssertReturnVoid(mConsoleVRDPServer);
+
+ uint32_t u32Clients = ASMAtomicDecU32(&mcVRDPClients);
+ VMMDev *pDev;
+ PPDMIVMMDEVPORT pPort;
+
+ if ( (u32Clients == 0)
+ && ((pDev = i_getVMMDev()))
+ && ((pPort = pDev->getVMMDevPort()))
+ )
+ {
+ pPort->pfnVRDPChange(pPort,
+ false,
+ 0);
+ }
+
+ mDisplay->i_VRDPConnectionEvent(false);
+
+ if (fu32Intercepted & VRDE_CLIENT_INTERCEPT_USB)
+ {
+ mConsoleVRDPServer->USBBackendDelete(u32ClientId);
+ }
+
+ if (fu32Intercepted & VRDE_CLIENT_INTERCEPT_CLIPBOARD)
+ {
+ mConsoleVRDPServer->ClipboardDelete(u32ClientId);
+ }
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ if (fu32Intercepted & VRDE_CLIENT_INTERCEPT_AUDIO)
+ {
+ if (mAudioVRDE)
+ mAudioVRDE->onVRDEControl(false /* fEnable */, 0 /* uFlags */);
+ }
+#endif
+
+ AuthType_T authType = AuthType_Null;
+ HRESULT hrc = mVRDEServer->COMGETTER(AuthType)(&authType);
+ AssertComRC(hrc);
+
+ if (authType == AuthType_External)
+ mConsoleVRDPServer->AuthDisconnect(i_getId(), u32ClientId);
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ i_guestPropertiesVRDPUpdateDisconnect(u32ClientId);
+ if (u32Clients == 0)
+ i_guestPropertiesVRDPUpdateActiveClient(0);
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+ if (u32Clients == 0)
+ mcGuestCredentialsProvided = false;
+
+ LogFlowFuncLeave();
+ return;
+}
+
+void Console::i_VRDPInterceptAudio(uint32_t u32ClientId)
+{
+ RT_NOREF(u32ClientId);
+ LogFlowFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ LogFlowFunc(("u32ClientId=%RU32\n", u32ClientId));
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ if (mAudioVRDE)
+ mAudioVRDE->onVRDEControl(true /* fEnable */, 0 /* uFlags */);
+#endif
+
+ LogFlowFuncLeave();
+ return;
+}
+
+void Console::i_VRDPInterceptUSB(uint32_t u32ClientId, void **ppvIntercept)
+{
+ LogFlowFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AssertReturnVoid(mConsoleVRDPServer);
+
+ mConsoleVRDPServer->USBBackendCreate(u32ClientId, ppvIntercept);
+
+ LogFlowFuncLeave();
+ return;
+}
+
+void Console::i_VRDPInterceptClipboard(uint32_t u32ClientId)
+{
+ LogFlowFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AssertReturnVoid(mConsoleVRDPServer);
+
+ mConsoleVRDPServer->ClipboardCreate(u32ClientId);
+
+ LogFlowFuncLeave();
+ return;
+}
+
+
+//static
+const char *Console::sSSMConsoleUnit = "ConsoleData";
+/** The saved state version. */
+#define CONSOLE_SAVED_STATE_VERSION UINT32_C(0x00010002)
+/** The saved state version, pre shared folder autoMountPoint. */
+#define CONSOLE_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT UINT32_C(0x00010001)
+
+inline static const char *networkAdapterTypeToName(NetworkAdapterType_T adapterType)
+{
+ switch (adapterType)
+ {
+ case NetworkAdapterType_Am79C970A:
+ case NetworkAdapterType_Am79C973:
+ case NetworkAdapterType_Am79C960:
+ return "pcnet";
+#ifdef VBOX_WITH_E1000
+ case NetworkAdapterType_I82540EM:
+ case NetworkAdapterType_I82543GC:
+ case NetworkAdapterType_I82545EM:
+ return "e1000";
+#endif
+#ifdef VBOX_WITH_VIRTIO
+ case NetworkAdapterType_Virtio:
+ return "virtio-net";
+#endif
+ case NetworkAdapterType_NE1000:
+ case NetworkAdapterType_NE2000:
+ case NetworkAdapterType_WD8003:
+ case NetworkAdapterType_WD8013:
+ case NetworkAdapterType_ELNK2:
+ return "dp8390";
+ case NetworkAdapterType_ELNK1:
+ return "3c501";
+ default:
+ AssertFailed();
+ return "unknown";
+ }
+ /* not reached */
+}
+
+/**
+ * Loads various console data stored in the saved state file.
+ *
+ * This method does validation of the state file and returns an error info
+ * when appropriate.
+ *
+ * The method does nothing if the machine is not in the Saved file or if
+ * console data from it has already been loaded.
+ *
+ * @note The caller must lock this object for writing.
+ */
+HRESULT Console::i_loadDataFromSavedState()
+{
+ if ( ( mMachineState != MachineState_Saved
+ && mMachineState != MachineState_AbortedSaved)
+ || mSavedStateDataLoaded)
+ return S_OK;
+
+ Bstr bstrSavedStateFile;
+ HRESULT hrc = mMachine->COMGETTER(StateFilePath)(bstrSavedStateFile.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Bstr bstrStateKeyId;
+ hrc = mMachine->COMGETTER(StateKeyId)(bstrStateKeyId.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Bstr bstrStateKeyStore;
+ hrc = mMachine->COMGETTER(StateKeyStore)(bstrStateKeyStore.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Utf8Str const strSavedStateFile(bstrSavedStateFile);
+
+ PCVMMR3VTABLE pVMM = mpVMM;
+ AssertPtrReturn(pVMM, E_UNEXPECTED);
+
+ PSSMHANDLE pSSM;
+ SsmStream ssmStream(this, pVMM, m_pKeyStore, bstrStateKeyId, bstrStateKeyStore);
+
+ int vrc = ssmStream.open(strSavedStateFile.c_str(), false /*fWrite*/, &pSSM);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t uVersion = 0;
+ vrc = pVMM->pfnSSMR3Seek(pSSM, sSSMConsoleUnit, 0 /* iInstance */, &uVersion);
+ /** @todo r=bird: This version check is premature, so the logic here is
+ * buggered as we won't ignore VERR_SSM_UNIT_NOT_FOUND as seems to be
+ * intended. Sigh. */
+ if (SSM_VERSION_MAJOR(uVersion) == SSM_VERSION_MAJOR(CONSOLE_SAVED_STATE_VERSION))
+ {
+ if (RT_SUCCESS(vrc))
+ try
+ {
+ vrc = i_loadStateFileExecInternal(pSSM, pVMM, uVersion);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ else if (vrc == VERR_SSM_UNIT_NOT_FOUND)
+ vrc = VINF_SUCCESS;
+ }
+ else
+ vrc = VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+ ssmStream.close();
+ }
+
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("The saved state file '%s' is invalid (%Rrc). Delete the saved state and try again"),
+ strSavedStateFile.c_str(), vrc);
+
+ mSavedStateDataLoaded = true;
+ }
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * Callback handler to save various console data to the state file,
+ * called when the user saves the VM state.
+ *
+ * @returns VBox status code.
+ * @param pSSM SSM handle.
+ * @param pVMM The VMM ring-3 vtable.
+ * @param pvUser Pointer to Console
+ *
+ * @note Locks the Console object for reading.
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_saveStateFileExec(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser)
+{
+ LogFlowFunc(("\n"));
+
+ Console *pThat = static_cast<Console *>(pvUser);
+ AssertReturn(pThat, VERR_INVALID_POINTER);
+
+ AutoCaller autoCaller(pThat);
+ AssertComRCReturn(autoCaller.rc(), VERR_INVALID_STATE);
+
+ AutoReadLock alock(pThat COMMA_LOCKVAL_SRC_POS);
+
+ pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)pThat->m_mapSharedFolders.size());
+
+ for (SharedFolderMap::const_iterator it = pThat->m_mapSharedFolders.begin();
+ it != pThat->m_mapSharedFolders.end();
+ ++it)
+ {
+ SharedFolder *pSF = (*it).second;
+ AutoCaller sfCaller(pSF);
+ AutoReadLock sfLock(pSF COMMA_LOCKVAL_SRC_POS);
+
+ const Utf8Str &name = pSF->i_getName();
+ pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)name.length() + 1 /* term. 0 */);
+ pVMM->pfnSSMR3PutStrZ(pSSM, name.c_str());
+
+ const Utf8Str &hostPath = pSF->i_getHostPath();
+ pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)hostPath.length() + 1 /* term. 0 */);
+ pVMM->pfnSSMR3PutStrZ(pSSM, hostPath.c_str());
+
+ pVMM->pfnSSMR3PutBool(pSSM, !!pSF->i_isWritable());
+ pVMM->pfnSSMR3PutBool(pSSM, !!pSF->i_isAutoMounted());
+
+ const Utf8Str &rStrAutoMountPoint = pSF->i_getAutoMountPoint();
+ pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)rStrAutoMountPoint.length() + 1 /* term. 0 */);
+ pVMM->pfnSSMR3PutStrZ(pSSM, rStrAutoMountPoint.c_str());
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Callback handler to load various console data from the state file.
+ *
+ * Called when the VM is being restored from the saved state.
+ *
+ * @returns VBox status code.
+ * @param pSSM SSM handle.
+ * @param pVMM The VMM ring-3 vtable.
+ * @param pvUser pointer to Console
+ * @param uVersion Console unit version. Should match sSSMConsoleVer.
+ * @param uPass The data pass.
+ */
+//static
+DECLCALLBACK(int)
+Console::i_loadStateFileExec(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser, uint32_t uVersion, uint32_t uPass)
+{
+ LogFlowFunc(("uVersion=%#x uPass=%#x\n", uVersion, uPass));
+ Assert(uPass == SSM_PASS_FINAL); RT_NOREF_PV(uPass);
+
+ if (SSM_VERSION_MAJOR_CHANGED(uVersion, CONSOLE_SAVED_STATE_VERSION))
+ return VERR_VERSION_MISMATCH;
+
+ Console *pThat = static_cast<Console *>(pvUser);
+ AssertReturn(pThat, VERR_INVALID_PARAMETER);
+
+ /* Currently, nothing to do when we've been called from VMR3Load*. */
+ return pVMM->pfnSSMR3SkipToEndOfUnit(pSSM);
+}
+
+/**
+ * Method to load various console data from the state file.
+ *
+ * Called from #i_loadDataFromSavedState.
+ *
+ * @param pSSM SSM handle.
+ * @param pVMM The VMM vtable.
+ * @param u32Version Console unit version.
+ * Should match sSSMConsoleVer.
+ *
+ * @note Locks the Console object for writing.
+ */
+int Console::i_loadStateFileExecInternal(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t u32Version)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(m_mapSharedFolders.empty(), VERR_INTERNAL_ERROR);
+
+ uint32_t size = 0;
+ int vrc = pVMM->pfnSSMR3GetU32(pSSM, &size);
+ AssertRCReturn(vrc, vrc);
+
+ for (uint32_t i = 0; i < size; ++i)
+ {
+ Utf8Str strName;
+ Utf8Str strHostPath;
+ bool writable = true;
+ bool autoMount = false;
+
+ uint32_t cbStr = 0;
+ char *buf = NULL;
+
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &cbStr);
+ AssertRCReturn(vrc, vrc);
+ buf = new char[cbStr];
+ vrc = pVMM->pfnSSMR3GetStrZ(pSSM, buf, cbStr);
+ AssertRC(vrc);
+ strName = buf;
+ delete[] buf;
+
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &cbStr);
+ AssertRCReturn(vrc, vrc);
+ buf = new char[cbStr];
+ vrc = pVMM->pfnSSMR3GetStrZ(pSSM, buf, cbStr);
+ AssertRC(vrc);
+ strHostPath = buf;
+ delete[] buf;
+
+ if (u32Version >= CONSOLE_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT)
+ pVMM->pfnSSMR3GetBool(pSSM, &writable);
+
+ if ( u32Version >= CONSOLE_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT
+#ifndef VBOX_OSE /* This broke saved state when introduced in r63916 (4.0). */
+ && pVMM->pfnSSMR3HandleRevision(pSSM) >= 63916
+#endif
+ )
+ pVMM->pfnSSMR3GetBool(pSSM, &autoMount);
+
+ Utf8Str strAutoMountPoint;
+ if (u32Version >= CONSOLE_SAVED_STATE_VERSION)
+ {
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &cbStr);
+ AssertRCReturn(vrc, vrc);
+ vrc = strAutoMountPoint.reserveNoThrow(cbStr);
+ AssertRCReturn(vrc, vrc);
+ vrc = pVMM->pfnSSMR3GetStrZ(pSSM, strAutoMountPoint.mutableRaw(), cbStr);
+ AssertRCReturn(vrc, vrc);
+ strAutoMountPoint.jolt();
+ }
+
+ ComObjPtr<SharedFolder> pSharedFolder;
+ pSharedFolder.createObject();
+ HRESULT rc = pSharedFolder->init(this,
+ strName,
+ strHostPath,
+ writable,
+ autoMount,
+ strAutoMountPoint,
+ false /* fFailOnError */);
+ AssertComRCReturn(rc, VERR_INTERNAL_ERROR);
+
+ m_mapSharedFolders.insert(std::make_pair(strName, pSharedFolder));
+ }
+
+ return VINF_SUCCESS;
+}
+
+#ifdef VBOX_WITH_GUEST_PROPS
+
+// static
+DECLCALLBACK(int) Console::i_doGuestPropNotification(void *pvExtension,
+ uint32_t u32Function,
+ void *pvParms,
+ uint32_t cbParms)
+{
+ Assert(u32Function == 0); NOREF(u32Function);
+
+ /*
+ * No locking, as this is purely a notification which does not make any
+ * changes to the object state.
+ */
+ PGUESTPROPHOSTCALLBACKDATA pCBData = reinterpret_cast<PGUESTPROPHOSTCALLBACKDATA>(pvParms);
+ AssertReturn(sizeof(GUESTPROPHOSTCALLBACKDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(pCBData->u32Magic == GUESTPROPHOSTCALLBACKDATA_MAGIC, VERR_INVALID_PARAMETER);
+ LogFlow(("Console::doGuestPropNotification: pCBData={.pcszName=%s, .pcszValue=%s, .pcszFlags=%s}\n",
+ pCBData->pcszName, pCBData->pcszValue, pCBData->pcszFlags));
+
+ int rc;
+ Bstr name(pCBData->pcszName);
+ Bstr value(pCBData->pcszValue);
+ Bstr flags(pCBData->pcszFlags);
+ BOOL fWasDeleted = !pCBData->pcszValue;
+ ComObjPtr<Console> pConsole = reinterpret_cast<Console *>(pvExtension);
+ HRESULT hrc = pConsole->mControl->PushGuestProperty(name.raw(),
+ value.raw(),
+ pCBData->u64Timestamp,
+ flags.raw(),
+ fWasDeleted);
+ if (SUCCEEDED(hrc))
+ {
+ ::FireGuestPropertyChangedEvent(pConsole->mEventSource, pConsole->i_getId().raw(), name.raw(), value.raw(), flags.raw(),
+ fWasDeleted);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ LogFlow(("Console::doGuestPropNotification: hrc=%Rhrc pCBData={.pcszName=%s, .pcszValue=%s, .pcszFlags=%s}\n",
+ hrc, pCBData->pcszName, pCBData->pcszValue, pCBData->pcszFlags));
+ rc = Global::vboxStatusCodeFromCOM(hrc);
+ }
+ return rc;
+}
+
+HRESULT Console::i_doEnumerateGuestProperties(const Utf8Str &aPatterns,
+ std::vector<Utf8Str> &aNames,
+ std::vector<Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<Utf8Str> &aFlags)
+{
+ AssertReturn(m_pVMMDev, E_FAIL);
+
+ VBOXHGCMSVCPARM parm[3];
+ parm[0].type = VBOX_HGCM_SVC_PARM_PTR;
+ parm[0].u.pointer.addr = (void*)aPatterns.c_str();
+ parm[0].u.pointer.size = (uint32_t)aPatterns.length() + 1;
+
+ /*
+ * Now things get slightly complicated. Due to a race with the guest adding
+ * properties, there is no good way to know how much to enlarge a buffer for
+ * the service to enumerate into. We choose a decent starting size and loop a
+ * few times, each time retrying with the size suggested by the service plus
+ * one Kb.
+ */
+ size_t cchBuf = 4096;
+ Utf8Str Utf8Buf;
+ int vrc = VERR_BUFFER_OVERFLOW;
+ for (unsigned i = 0; i < 10 && (VERR_BUFFER_OVERFLOW == vrc); ++i)
+ {
+ try
+ {
+ Utf8Buf.reserve(cchBuf + 1024);
+ }
+ catch(...)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ parm[1].type = VBOX_HGCM_SVC_PARM_PTR;
+ parm[1].u.pointer.addr = Utf8Buf.mutableRaw();
+ parm[1].u.pointer.size = (uint32_t)cchBuf + 1024;
+
+ parm[2].type = VBOX_HGCM_SVC_PARM_32BIT;
+ parm[2].u.uint32 = 0;
+
+ vrc = m_pVMMDev->hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_ENUM_PROPS, 3, &parm[0]);
+ Utf8Buf.jolt();
+ if (parm[2].type != VBOX_HGCM_SVC_PARM_32BIT)
+ return setErrorBoth(E_FAIL, vrc, tr("Internal application error"));
+ cchBuf = parm[2].u.uint32;
+ }
+ if (vrc == VERR_BUFFER_OVERFLOW)
+ return setError(E_UNEXPECTED, tr("Temporary failure due to guest activity, please retry"));
+
+ /*
+ * Finally we have to unpack the data returned by the service into the safe
+ * arrays supplied by the caller. We start by counting the number of entries.
+ */
+ const char *pszBuf
+ = reinterpret_cast<const char *>(parm[1].u.pointer.addr);
+ unsigned cEntries = 0;
+ /* The list is terminated by a zero-length string at the end of a set
+ * of four strings. */
+ for (size_t i = 0; strlen(pszBuf + i) != 0; )
+ {
+ /* We are counting sets of four strings. */
+ for (unsigned j = 0; j < 4; ++j)
+ i += strlen(pszBuf + i) + 1;
+ ++cEntries;
+ }
+
+ aNames.resize(cEntries);
+ aValues.resize(cEntries);
+ aTimestamps.resize(cEntries);
+ aFlags.resize(cEntries);
+
+ size_t iBuf = 0;
+ /* Rely on the service to have formated the data correctly. */
+ for (unsigned i = 0; i < cEntries; ++i)
+ {
+ size_t cchName = strlen(pszBuf + iBuf);
+ aNames[i] = &pszBuf[iBuf];
+ iBuf += cchName + 1;
+
+ size_t cchValue = strlen(pszBuf + iBuf);
+ aValues[i] = &pszBuf[iBuf];
+ iBuf += cchValue + 1;
+
+ size_t cchTimestamp = strlen(pszBuf + iBuf);
+ aTimestamps[i] = RTStrToUInt64(&pszBuf[iBuf]);
+ iBuf += cchTimestamp + 1;
+
+ size_t cchFlags = strlen(pszBuf + iBuf);
+ aFlags[i] = &pszBuf[iBuf];
+ iBuf += cchFlags + 1;
+ }
+
+ return S_OK;
+}
+
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+
+// IConsole properties
+/////////////////////////////////////////////////////////////////////////////
+HRESULT Console::getMachine(ComPtr<IMachine> &aMachine)
+{
+ /* mMachine is constant during life time, no need to lock */
+ mMachine.queryInterfaceTo(aMachine.asOutParam());
+
+ /* callers expect to get a valid reference, better fail than crash them */
+ if (mMachine.isNull())
+ return E_FAIL;
+
+ return S_OK;
+}
+
+HRESULT Console::getState(MachineState_T *aState)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* we return our local state (since it's always the same as on the server) */
+ *aState = mMachineState;
+
+ return S_OK;
+}
+
+HRESULT Console::getGuest(ComPtr<IGuest> &aGuest)
+{
+ /* mGuest is constant during life time, no need to lock */
+ mGuest.queryInterfaceTo(aGuest.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Console::getKeyboard(ComPtr<IKeyboard> &aKeyboard)
+{
+ /* mKeyboard is constant during life time, no need to lock */
+ mKeyboard.queryInterfaceTo(aKeyboard.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Console::getMouse(ComPtr<IMouse> &aMouse)
+{
+ /* mMouse is constant during life time, no need to lock */
+ mMouse.queryInterfaceTo(aMouse.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Console::getDisplay(ComPtr<IDisplay> &aDisplay)
+{
+ /* mDisplay is constant during life time, no need to lock */
+ mDisplay.queryInterfaceTo(aDisplay.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Console::getDebugger(ComPtr<IMachineDebugger> &aDebugger)
+{
+ /* we need a write lock because of the lazy mDebugger initialization*/
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* check if we have to create the debugger object */
+ if (!mDebugger)
+ {
+ unconst(mDebugger).createObject();
+ mDebugger->init(this);
+ }
+
+ mDebugger.queryInterfaceTo(aDebugger.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Console::getUSBDevices(std::vector<ComPtr<IUSBDevice> > &aUSBDevices)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ size_t i = 0;
+ aUSBDevices.resize(mUSBDevices.size());
+ for (USBDeviceList::const_iterator it = mUSBDevices.begin(); it != mUSBDevices.end(); ++i, ++it)
+ (*it).queryInterfaceTo(aUSBDevices[i].asOutParam());
+
+ return S_OK;
+}
+
+
+HRESULT Console::getRemoteUSBDevices(std::vector<ComPtr<IHostUSBDevice> > &aRemoteUSBDevices)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ size_t i = 0;
+ aRemoteUSBDevices.resize(mRemoteUSBDevices.size());
+ for (RemoteUSBDeviceList::const_iterator it = mRemoteUSBDevices.begin(); it != mRemoteUSBDevices.end(); ++i, ++it)
+ (*it).queryInterfaceTo(aRemoteUSBDevices[i].asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Console::getVRDEServerInfo(ComPtr<IVRDEServerInfo> &aVRDEServerInfo)
+{
+ /* mVRDEServerInfo is constant during life time, no need to lock */
+ mVRDEServerInfo.queryInterfaceTo(aVRDEServerInfo.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Console::getEmulatedUSB(ComPtr<IEmulatedUSB> &aEmulatedUSB)
+{
+ /* mEmulatedUSB is constant during life time, no need to lock */
+ mEmulatedUSB.queryInterfaceTo(aEmulatedUSB.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Console::getSharedFolders(std::vector<ComPtr<ISharedFolder> > &aSharedFolders)
+{
+ /* loadDataFromSavedState() needs a write lock */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Read console data stored in the saved state file (if not yet done) */
+ HRESULT rc = i_loadDataFromSavedState();
+ if (FAILED(rc)) return rc;
+
+ size_t i = 0;
+ aSharedFolders.resize(m_mapSharedFolders.size());
+ for (SharedFolderMap::const_iterator it = m_mapSharedFolders.begin(); it != m_mapSharedFolders.end(); ++i, ++it)
+ (it)->second.queryInterfaceTo(aSharedFolders[i].asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Console::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ // no need to lock - lifetime constant
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Console::getAttachedPCIDevices(std::vector<ComPtr<IPCIDeviceAttachment> > &aAttachedPCIDevices)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mBusMgr)
+ {
+ std::vector<BusAssignmentManager::PCIDeviceInfo> devInfos;
+ mBusMgr->listAttachedPCIDevices(devInfos);
+ ComObjPtr<PCIDeviceAttachment> dev;
+ aAttachedPCIDevices.resize(devInfos.size());
+ for (size_t i = 0; i < devInfos.size(); i++)
+ {
+ const BusAssignmentManager::PCIDeviceInfo &devInfo = devInfos[i];
+ dev.createObject();
+ dev->init(NULL, devInfo.strDeviceName,
+ devInfo.hostAddress.valid() ? devInfo.hostAddress.asLong() : -1,
+ devInfo.guestAddress.asLong(),
+ devInfo.hostAddress.valid());
+ dev.queryInterfaceTo(aAttachedPCIDevices[i].asOutParam());
+ }
+ }
+ else
+ aAttachedPCIDevices.resize(0);
+
+ return S_OK;
+}
+
+HRESULT Console::getUseHostClipboard(BOOL *aUseHostClipboard)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aUseHostClipboard = mfUseHostClipboard;
+
+ return S_OK;
+}
+
+HRESULT Console::setUseHostClipboard(BOOL aUseHostClipboard)
+{
+ if (mfUseHostClipboard != RT_BOOL(aUseHostClipboard))
+ {
+ mfUseHostClipboard = RT_BOOL(aUseHostClipboard);
+ LogRel(("Shared Clipboard: %s using host clipboard\n", mfUseHostClipboard ? "Enabled" : "Disabled"));
+ }
+
+ return S_OK;
+}
+
+// IConsole methods
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT Console::powerUp(ComPtr<IProgress> &aProgress)
+{
+ return i_powerUp(aProgress.asOutParam(), false /* aPaused */);
+}
+
+HRESULT Console::powerUpPaused(ComPtr<IProgress> &aProgress)
+{
+ return i_powerUp(aProgress.asOutParam(), true /* aPaused */);
+}
+
+HRESULT Console::powerDown(ComPtr<IProgress> &aProgress)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
+ switch (mMachineState)
+ {
+ case MachineState_Running:
+ case MachineState_Paused:
+ case MachineState_Stuck:
+ break;
+
+ /* Try cancel the save state. */
+ case MachineState_Saving:
+ if (!mptrCancelableProgress.isNull())
+ {
+ HRESULT hrc = mptrCancelableProgress->Cancel();
+ if (SUCCEEDED(hrc))
+ break;
+ }
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down at this point during a save state"));
+
+ /* Try cancel the teleportation. */
+ case MachineState_Teleporting:
+ case MachineState_TeleportingPausedVM:
+ if (!mptrCancelableProgress.isNull())
+ {
+ HRESULT hrc = mptrCancelableProgress->Cancel();
+ if (SUCCEEDED(hrc))
+ break;
+ }
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down at this point in a teleportation"));
+
+ /* Try cancel the online snapshot. */
+ case MachineState_OnlineSnapshotting:
+ if (!mptrCancelableProgress.isNull())
+ {
+ HRESULT hrc = mptrCancelableProgress->Cancel();
+ if (SUCCEEDED(hrc))
+ break;
+ }
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down at this point in an online snapshot"));
+
+ /* Try cancel the live snapshot. */
+ case MachineState_LiveSnapshotting:
+ if (!mptrCancelableProgress.isNull())
+ {
+ HRESULT hrc = mptrCancelableProgress->Cancel();
+ if (SUCCEEDED(hrc))
+ break;
+ }
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down at this point in a live snapshot"));
+
+ /* extra nice error message for a common case */
+ case MachineState_Saved:
+ case MachineState_AbortedSaved:
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down a saved virtual machine"));
+ case MachineState_Stopping:
+ return setError(VBOX_E_INVALID_VM_STATE, tr("The virtual machine is being powered down"));
+ default:
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Invalid machine state: %s (must be Running, Paused or Stuck)"),
+ Global::stringifyMachineState(mMachineState));
+ }
+ LogFlowThisFunc(("Initiating SHUTDOWN request...\n"));
+
+ /* memorize the current machine state */
+ MachineState_T lastMachineState = mMachineState;
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ if (mfTurnResetIntoPowerOff)
+ {
+ alock.release(); /** @todo r=bird: This code introduces a race condition wrt to the state. This must be done elsewhere! */
+ mMachine->DeleteGuestProperty(Bstr("/VirtualBox/HostInfo/VMPowerOffReason").raw());
+ mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VMPowerOffReason").raw(),
+ Bstr("PowerOff").raw(), Bstr("RDONLYGUEST").raw());
+ mMachine->SaveSettings();
+ alock.acquire();
+ }
+#endif
+
+ /*
+ * Request a progress object from the server (this will set the machine state
+ * to Stopping on the server to block others from accessing this machine).
+ */
+ ComPtr<IProgress> ptrProgress;
+ HRESULT hrc = mControl->BeginPoweringDown(ptrProgress.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ /* Sync the state with the server: */
+ i_setMachineStateLocally(MachineState_Stopping);
+
+ /* Create the power down task: */
+ VMPowerDownTask *pTask = NULL;
+ try
+ {
+ pTask = new VMPowerDownTask(this, ptrProgress);
+ if (!pTask->isOk())
+ {
+ hrc = setError(FAILED(pTask->rc()) ? pTask->rc() : E_FAIL, tr("Could not create VMPowerDownTask object\n"));
+ delete(pTask);
+ pTask = NULL;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ hrc = pTask->createThread();
+ if (SUCCEEDED(hrc))
+ {
+ ptrProgress.queryInterfaceTo(aProgress.asOutParam());
+ LogFlowThisFunc(("LEAVE: hrc=%Rhrc\n", hrc));
+ return hrc;
+ }
+ }
+
+ /*
+ * Cancel the requested power down procedure.
+ * This will reset the machine state to the state it had right
+ * before calling mControl->BeginPoweringDown().
+ */
+ ErrorInfoKeeper eik;
+ mControl->EndPoweringDown(eik.getResultCode(), eik.getText().raw());
+ i_setMachineStateLocally(lastMachineState);
+ }
+ LogFlowThisFunc(("LEAVE: hrc=%Rhrc\n", hrc));
+ return hrc;
+}
+
+HRESULT Console::reset()
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_LiveSnapshotting
+ /** @todo r=bird: This should be allowed on paused VMs as well. Later. */
+ )
+ return i_setInvalidMachineStateError();
+
+ /* protect mpUVM */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ int vrc = ptrVM.vtable()->pfnVMR3Reset(ptrVM.rawUVM());
+
+ hrc = RT_SUCCESS(vrc) ? S_OK : setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not reset the machine (%Rrc)"), vrc);
+ }
+
+ LogFlowThisFunc(("mMachineState=%d, hrc=%Rhrc\n", mMachineState, hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/*static*/ DECLCALLBACK(int) Console::i_unplugCpu(Console *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, VMCPUID idCpu)
+{
+ LogFlowFunc(("pThis=%p pVM=%p idCpu=%u\n", pThis, pUVM, idCpu));
+
+ AssertReturn(pThis, VERR_INVALID_PARAMETER);
+
+ int vrc = pVMM->pfnPDMR3DeviceDetach(pUVM, "acpi", 0, idCpu, 0);
+ Log(("UnplugCpu: rc=%Rrc\n", vrc));
+
+ return vrc;
+}
+
+HRESULT Console::i_doCPURemove(ULONG aCpu, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ HRESULT rc = S_OK;
+
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
+ AssertReturn(m_pVMMDev, E_FAIL);
+ PPDMIVMMDEVPORT pVmmDevPort = m_pVMMDev->getVMMDevPort();
+ AssertReturn(pVmmDevPort, E_FAIL);
+
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_LiveSnapshotting
+ )
+ return i_setInvalidMachineStateError();
+
+ /* Check if the CPU is present */
+ BOOL fCpuAttached;
+ rc = mMachine->GetCPUStatus(aCpu, &fCpuAttached);
+ if (FAILED(rc))
+ return rc;
+ if (!fCpuAttached)
+ return setError(E_FAIL, tr("CPU %d is not attached"), aCpu);
+
+ /* Leave the lock before any EMT/VMMDev call. */
+ alock.release();
+ bool fLocked = true;
+
+ /* Check if the CPU is unlocked */
+ PPDMIBASE pBase;
+ int vrc = pVMM->pfnPDMR3QueryDeviceLun(pUVM, "acpi", 0, aCpu, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMIACPIPORT pApicPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT);
+
+ /* Notify the guest if possible. */
+ uint32_t idCpuCore, idCpuPackage;
+ vrc = pVMM->pfnVMR3GetCpuCoreAndPackageIdFromCpuId(pUVM, aCpu, &idCpuCore, &idCpuPackage); AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pVmmDevPort->pfnCpuHotUnplug(pVmmDevPort, idCpuCore, idCpuPackage);
+ if (RT_SUCCESS(vrc))
+ {
+ unsigned cTries = 100;
+ do
+ {
+ /* It will take some time until the event is processed in the guest. Wait... */
+ vrc = pApicPort ? pApicPort->pfnGetCpuStatus(pApicPort, aCpu, &fLocked) : VERR_INVALID_POINTER;
+ if (RT_SUCCESS(vrc) && !fLocked)
+ break;
+
+ /* Sleep a bit */
+ RTThreadSleep(100);
+ } while (cTries-- > 0);
+ }
+ else if (vrc == VERR_VMMDEV_CPU_HOTPLUG_NOT_MONITORED_BY_GUEST)
+ {
+ /* Query one time. It is possible that the user ejected the CPU. */
+ vrc = pApicPort ? pApicPort->pfnGetCpuStatus(pApicPort, aCpu, &fLocked) : VERR_INVALID_POINTER;
+ }
+ }
+
+ /* If the CPU was unlocked we can detach it now. */
+ if (RT_SUCCESS(vrc) && !fLocked)
+ {
+ /*
+ * Call worker on EMT #0, that's faster and safer than doing everything
+ * using VMR3ReqCall.
+ */
+ PVMREQ pReq;
+ vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)i_unplugCpu, 4,
+ this, pUVM, pVMM, (VMCPUID)aCpu);
+
+ if (vrc == VERR_TIMEOUT)
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+ AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pReq->iStatus;
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Detach it from the VM */
+ vrc = pVMM->pfnVMR3HotUnplugCpu(pUVM, aCpu);
+ AssertRC(vrc);
+ }
+ else
+ rc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Hot-Remove failed (rc=%Rrc)"), vrc);
+ }
+ else
+ rc = setErrorBoth(VBOX_E_VM_ERROR, VERR_RESOURCE_BUSY,
+ tr("Hot-Remove was aborted because the CPU may still be used by the guest"), VERR_RESOURCE_BUSY);
+
+ LogFlowThisFunc(("mMachineState=%d, rc=%Rhrc\n", mMachineState, rc));
+ LogFlowThisFuncLeave();
+ return rc;
+}
+
+/*static*/ DECLCALLBACK(int) Console::i_plugCpu(Console *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, VMCPUID idCpu)
+{
+ LogFlowFunc(("pThis=%p uCpu=%u\n", pThis, idCpu));
+ RT_NOREF(pThis);
+
+ int rc = pVMM->pfnVMR3HotPlugCpu(pUVM, idCpu);
+ AssertRC(rc);
+
+ PCFGMNODE pInst = pVMM->pfnCFGMR3GetChild(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/acpi/0/");
+ AssertRelease(pInst);
+ /* nuke anything which might have been left behind. */
+ pVMM->pfnCFGMR3RemoveNode(pVMM->pfnCFGMR3GetChildF(pInst, "LUN#%u", idCpu));
+
+#define RC_CHECK() do { if (RT_FAILURE(rc)) { AssertReleaseRC(rc); break; } } while (0)
+
+ PCFGMNODE pLunL0;
+ PCFGMNODE pCfg;
+ rc = pVMM->pfnCFGMR3InsertNodeF(pInst, &pLunL0, "LUN#%u", idCpu); RC_CHECK();
+ rc = pVMM->pfnCFGMR3InsertString(pLunL0, "Driver", "ACPICpu"); RC_CHECK();
+ rc = pVMM->pfnCFGMR3InsertNode(pLunL0, "Config", &pCfg); RC_CHECK();
+
+ /*
+ * Attach the driver.
+ */
+ PPDMIBASE pBase;
+ rc = pVMM->pfnPDMR3DeviceAttach(pUVM, "acpi", 0, idCpu, 0, &pBase); RC_CHECK();
+
+ Log(("PlugCpu: rc=%Rrc\n", rc));
+
+ pVMM->pfnCFGMR3Dump(pInst);
+
+#undef RC_CHECK
+
+ return VINF_SUCCESS;
+}
+
+HRESULT Console::i_doCPUAdd(ULONG aCpu, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ HRESULT rc = S_OK;
+
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_LiveSnapshotting
+ /** @todo r=bird: This should be allowed on paused VMs as well. Later. */
+ )
+ return i_setInvalidMachineStateError();
+
+ AssertReturn(m_pVMMDev, E_FAIL);
+ PPDMIVMMDEVPORT pDevPort = m_pVMMDev->getVMMDevPort();
+ AssertReturn(pDevPort, E_FAIL);
+
+ /* Check if the CPU is present */
+ BOOL fCpuAttached;
+ rc = mMachine->GetCPUStatus(aCpu, &fCpuAttached);
+ if (FAILED(rc)) return rc;
+
+ if (fCpuAttached)
+ return setError(E_FAIL,
+ tr("CPU %d is already attached"), aCpu);
+
+ /*
+ * Call worker on EMT #0, that's faster and safer than doing everything
+ * using VMR3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait
+ * here to make requests from under the lock in order to serialize them.
+ */
+ PVMREQ pReq;
+ int vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)i_plugCpu, 4,
+ this, pUVM, pVMM, aCpu);
+
+ /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ if (vrc == VERR_TIMEOUT)
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+ AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pReq->iStatus;
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Notify the guest if possible. */
+ uint32_t idCpuCore, idCpuPackage;
+ vrc = pVMM->pfnVMR3GetCpuCoreAndPackageIdFromCpuId(pUVM, aCpu, &idCpuCore, &idCpuPackage); AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pDevPort->pfnCpuHotPlug(pDevPort, idCpuCore, idCpuPackage);
+ /** @todo warning if the guest doesn't support it */
+ }
+ else
+ rc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not add CPU to the machine (%Rrc)"), vrc);
+
+ LogFlowThisFunc(("mMachineState=%d, rc=%Rhrc\n", mMachineState, rc));
+ LogFlowThisFuncLeave();
+ return rc;
+}
+
+HRESULT Console::pause()
+{
+ LogFlowThisFuncEnter();
+
+ HRESULT rc = i_pause(Reason_Unspecified);
+
+ LogFlowThisFunc(("rc=%Rhrc\n", rc));
+ LogFlowThisFuncLeave();
+ return rc;
+}
+
+HRESULT Console::resume()
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mMachineState != MachineState_Paused)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot resume the machine as it is not paused (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));
+
+ HRESULT rc = i_resume(Reason_Unspecified, alock);
+
+ LogFlowThisFunc(("rc=%Rhrc\n", rc));
+ LogFlowThisFuncLeave();
+ return rc;
+}
+
+HRESULT Console::powerButton()
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_LiveSnapshotting
+ )
+ return i_setInvalidMachineStateError();
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ // no need to release lock, as there are no cross-thread callbacks
+
+ /* get the acpi device interface and press the button. */
+ PPDMIBASE pBase = NULL;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT);
+ if (pPort)
+ vrc = pPort->pfnPowerButtonPress(pPort);
+ else
+ vrc = VERR_PDM_MISSING_INTERFACE;
+ }
+
+ hrc = RT_SUCCESS(vrc) ? S_OK : setErrorBoth(VBOX_E_PDM_ERROR, vrc, tr("Controlled power off failed (%Rrc)"), vrc);
+ }
+
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT Console::getPowerButtonHandled(BOOL *aHandled)
+{
+ LogFlowThisFuncEnter();
+
+ *aHandled = FALSE;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_LiveSnapshotting
+ )
+ return i_setInvalidMachineStateError();
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ // no need to release lock, as there are no cross-thread callbacks
+
+ /* get the acpi device interface and check if the button press was handled. */
+ PPDMIBASE pBase;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT);
+ if (pPort)
+ {
+ bool fHandled = false;
+ vrc = pPort->pfnGetPowerButtonHandled(pPort, &fHandled);
+ if (RT_SUCCESS(vrc))
+ *aHandled = fHandled;
+ }
+ else
+ vrc = VERR_PDM_MISSING_INTERFACE;
+ }
+
+ hrc = RT_SUCCESS(vrc) ? S_OK
+ : setErrorBoth(VBOX_E_PDM_ERROR, vrc,
+ tr("Checking if the ACPI Power Button event was handled by the guest OS failed (%Rrc)"), vrc);
+
+ }
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT Console::getGuestEnteredACPIMode(BOOL *aEntered)
+{
+ LogFlowThisFuncEnter();
+
+ *aEntered = FALSE;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_LiveSnapshotting
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Invalid machine state %s when checking if the guest entered the ACPI mode"),
+ Global::stringifyMachineState(mMachineState));
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ // no need to release lock, as there are no cross-thread callbacks
+
+ /* get the acpi device interface and query the information. */
+ PPDMIBASE pBase;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT);
+ if (pPort)
+ {
+ bool fEntered = false;
+ vrc = pPort->pfnGetGuestEnteredACPIMode(pPort, &fEntered);
+ if (RT_SUCCESS(vrc))
+ *aEntered = fEntered;
+ }
+ else
+ vrc = VERR_PDM_MISSING_INTERFACE;
+ }
+ }
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT Console::sleepButton()
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_LiveSnapshotting)
+ return i_setInvalidMachineStateError();
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ // no need to release lock, as there are no cross-thread callbacks
+
+ /* get the acpi device interface and press the sleep button. */
+ PPDMIBASE pBase = NULL;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT);
+ if (pPort)
+ vrc = pPort->pfnSleepButtonPress(pPort);
+ else
+ vrc = VERR_PDM_MISSING_INTERFACE;
+ }
+
+ hrc = RT_SUCCESS(vrc) ? S_OK : setErrorBoth(VBOX_E_PDM_ERROR, vrc, tr("Sending sleep button event failed (%Rrc)"), vrc);
+ }
+
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/** read the value of a LED. */
+DECLINLINE(uint32_t) readAndClearLed(PPDMLED pLed)
+{
+ if (!pLed)
+ return 0;
+ uint32_t u32 = pLed->Actual.u32 | pLed->Asserted.u32;
+ pLed->Asserted.u32 = 0;
+ return u32;
+}
+
+HRESULT Console::getDeviceActivity(const std::vector<DeviceType_T> &aType, std::vector<DeviceActivity_T> &aActivity)
+{
+ /*
+ * Note: we don't lock the console object here because
+ * readAndClearLed() should be thread safe.
+ */
+
+ std::vector<bool> aWanted;
+ std::vector<PDMLEDCORE> aLED;
+ DeviceType_T maxWanted = (DeviceType_T) 0;
+ DeviceType_T enmType;
+
+ /* Make a roadmap of which DeviceType_T LED types are wanted */
+ for (size_t iType = 0; iType < aType.size(); ++iType)
+ {
+ enmType = aType[iType];
+ if (enmType > maxWanted)
+ {
+ maxWanted = enmType;
+ aWanted.resize(maxWanted + 1);
+ }
+ aWanted[enmType] = true;
+ }
+ aLED.resize(maxWanted + 1);
+
+ /* Collect all the LEDs in a single sweep through all drivers' sets */
+ for (uint32_t idxSet = 0; idxSet < mcLedSets; ++idxSet)
+ {
+ /* Look inside this driver's set of LEDs */
+ PLEDSET pLS = &maLedSets[idxSet];
+
+ /* Multi-type drivers (e.g. SCSI) have a subtype array which must be matched. */
+ if (pLS->paSubTypes)
+ {
+ for (uint32_t inSet = 0; inSet < pLS->cLeds; ++inSet)
+ {
+ enmType = pLS->paSubTypes[inSet];
+ if (enmType < maxWanted && aWanted[enmType])
+ aLED[enmType].u32 |= readAndClearLed(pLS->papLeds[inSet]);
+ }
+ }
+ /* Single-type drivers (e.g. floppy) have the type in ->enmType */
+ else
+ {
+ enmType = pLS->enmType;
+ if (enmType < maxWanted && aWanted[enmType])
+ for (uint32_t inSet = 0; inSet < pLS->cLeds; ++inSet)
+ aLED[enmType].u32 |= readAndClearLed(pLS->papLeds[inSet]);
+ }
+ }
+
+ aActivity.resize(aType.size());
+ for (size_t iType = 0; iType < aActivity.size(); ++iType)
+ {
+ /* Compose the result */
+ switch (aLED[aType[iType]].u32 & (PDMLED_READING | PDMLED_WRITING))
+ {
+ case 0:
+ aActivity[iType] = DeviceActivity_Idle;
+ break;
+ case PDMLED_READING:
+ aActivity[iType] = DeviceActivity_Reading;
+ break;
+ case PDMLED_WRITING:
+ case PDMLED_READING | PDMLED_WRITING:
+ aActivity[iType] = DeviceActivity_Writing;
+ break;
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT Console::attachUSBDevice(const com::Guid &aId, const com::Utf8Str &aCaptureFilename)
+{
+#ifdef VBOX_WITH_USB
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Paused)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot attach a USB device to the machine which is not running or paused (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));
+
+ /* Get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /* Don't proceed unless we have a USB controller. */
+ if (mfVMHasUsbController)
+ {
+ /* release the lock because the USB Proxy service may call us back
+ * (via onUSBDeviceAttach()) */
+ alock.release();
+
+ /* Request the device capture */
+ hrc = mControl->CaptureUSBDevice(Bstr(aId.toString()).raw(), Bstr(aCaptureFilename).raw());
+ }
+ else
+ hrc = setError(VBOX_E_PDM_ERROR, tr("The virtual machine does not have a USB controller"));
+ }
+ return hrc;
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aId, aCaptureFilename);
+ return setError(VBOX_E_PDM_ERROR, tr("The virtual machine does not have a USB controller"));
+#endif /* !VBOX_WITH_USB */
+}
+
+HRESULT Console::detachUSBDevice(const com::Guid &aId, ComPtr<IUSBDevice> &aDevice)
+{
+ RT_NOREF(aDevice);
+#ifdef VBOX_WITH_USB
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Find it. */
+ for (USBDeviceList::iterator it = mUSBDevices.begin(); it != mUSBDevices.end(); ++it)
+ if ((*it)->i_id() == aId)
+ {
+ /* Found it! */
+ ComObjPtr<OUSBDevice> pUSBDevice(*it);
+
+ /* Remove the device from the collection, it is re-added below for failures */
+ mUSBDevices.erase(it);
+
+ /*
+ * Inform the USB device and USB proxy about what's cooking.
+ */
+ alock.release();
+ HRESULT hrc = mControl->DetachUSBDevice(Bstr(aId.toString()).raw(), false /* aDone */);
+ if (SUCCEEDED(hrc))
+ {
+ /* Request the PDM to detach the USB device. */
+ hrc = i_detachUSBDevice(pUSBDevice);
+ if (SUCCEEDED(hrc))
+ {
+ /* Request the device release. Even if it fails, the device will
+ * remain as held by proxy, which is OK for us (the VM process). */
+ return mControl->DetachUSBDevice(Bstr(aId.toString()).raw(), true /* aDone */);
+ }
+ }
+
+ /* Re-add the device to the collection */
+ alock.acquire();
+ mUSBDevices.push_back(pUSBDevice);
+ return hrc;
+ }
+
+ return setError(E_INVALIDARG, tr("USB device with UUID {%RTuuid} is not attached to this machine"), aId.raw());
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aId, aDevice);
+ return setError(VBOX_E_PDM_ERROR, tr("The virtual machine does not have a USB controller"));
+#endif /* !VBOX_WITH_USB */
+}
+
+
+HRESULT Console::findUSBDeviceByAddress(const com::Utf8Str &aName, ComPtr<IUSBDevice> &aDevice)
+{
+#ifdef VBOX_WITH_USB
+
+ aDevice = NULL;
+
+ SafeIfaceArray<IUSBDevice> devsvec;
+ HRESULT rc = COMGETTER(USBDevices)(ComSafeArrayAsOutParam(devsvec));
+ if (FAILED(rc)) return rc;
+
+ for (size_t i = 0; i < devsvec.size(); ++i)
+ {
+ Bstr bstrAddress;
+ rc = devsvec[i]->COMGETTER(Address)(bstrAddress.asOutParam());
+ if (FAILED(rc)) return rc;
+ if (bstrAddress == aName)
+ {
+ ComObjPtr<OUSBDevice> pUSBDevice;
+ pUSBDevice.createObject();
+ pUSBDevice->init(devsvec[i]);
+ return pUSBDevice.queryInterfaceTo(aDevice.asOutParam());
+ }
+ }
+
+ return setErrorNoLog(VBOX_E_OBJECT_NOT_FOUND, tr("Could not find a USB device with address '%s'"), aName.c_str());
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aName, aDevice);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_USB */
+}
+
+HRESULT Console::findUSBDeviceById(const com::Guid &aId, ComPtr<IUSBDevice> &aDevice)
+{
+#ifdef VBOX_WITH_USB
+
+ aDevice = NULL;
+
+ SafeIfaceArray<IUSBDevice> devsvec;
+ HRESULT rc = COMGETTER(USBDevices)(ComSafeArrayAsOutParam(devsvec));
+ if (FAILED(rc)) return rc;
+
+ Utf8Str const strId = aId.toString();
+ for (size_t i = 0; i < devsvec.size(); ++i)
+ {
+ Bstr id;
+ rc = devsvec[i]->COMGETTER(Id)(id.asOutParam());
+ if (FAILED(rc)) return rc;
+ if (id == strId)
+ {
+ ComObjPtr<OUSBDevice> pUSBDevice;
+ pUSBDevice.createObject();
+ pUSBDevice->init(devsvec[i]);
+ ComObjPtr<IUSBDevice> iUSBDevice = static_cast <ComObjPtr<IUSBDevice> > (pUSBDevice);
+ return iUSBDevice.queryInterfaceTo(aDevice.asOutParam());
+ }
+ }
+
+ return setErrorNoLog(VBOX_E_OBJECT_NOT_FOUND, tr("Could not find a USB device with uuid {%RTuuid}"), aId.raw());
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aId, aDevice);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_USB */
+}
+
+HRESULT Console::createSharedFolder(const com::Utf8Str &aName, const com::Utf8Str &aHostPath, BOOL aWritable,
+ BOOL aAutomount, const com::Utf8Str &aAutoMountPoint)
+{
+ LogFlowThisFunc(("Entering for '%s' -> '%s'\n", aName.c_str(), aHostPath.c_str()));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /// @todo see @todo in AttachUSBDevice() about the Paused state
+ if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot create a transient shared folder on a machine in a saved state (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));
+ if ( mMachineState != MachineState_PoweredOff
+ && mMachineState != MachineState_Teleported
+ && mMachineState != MachineState_Aborted
+ && mMachineState != MachineState_Running
+ && mMachineState != MachineState_Paused
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot create a transient shared folder on the machine while it is changing the state (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));
+
+ ComObjPtr<SharedFolder> pSharedFolder;
+ HRESULT rc = i_findSharedFolder(aName, pSharedFolder, false /* aSetError */);
+ if (SUCCEEDED(rc))
+ return setError(VBOX_E_FILE_ERROR,
+ tr("Shared folder named '%s' already exists"),
+ aName.c_str());
+
+ pSharedFolder.createObject();
+ rc = pSharedFolder->init(this,
+ aName,
+ aHostPath,
+ !!aWritable,
+ !!aAutomount,
+ aAutoMountPoint,
+ true /* fFailOnError */);
+ if (FAILED(rc)) return rc;
+
+ /* If the VM is online and supports shared folders, share this folder
+ * under the specified name. (Ignore any failure to obtain the VM handle.) */
+ SafeVMPtrQuiet ptrVM(this);
+ if ( ptrVM.isOk()
+ && m_pVMMDev
+ && m_pVMMDev->isShFlActive()
+ )
+ {
+ /* first, remove the machine or the global folder if there is any */
+ SharedFolderDataMap::const_iterator it;
+ if (i_findOtherSharedFolder(aName, it))
+ {
+ rc = i_removeSharedFolder(aName);
+ if (FAILED(rc))
+ return rc;
+ }
+
+ /* second, create the given folder */
+ rc = i_createSharedFolder(aName, SharedFolderData(aHostPath, !!aWritable, !!aAutomount, aAutoMountPoint));
+ if (FAILED(rc))
+ return rc;
+ }
+
+ m_mapSharedFolders.insert(std::make_pair(aName, pSharedFolder));
+
+ /* Notify console callbacks after the folder is added to the list. */
+ alock.release();
+ ::FireSharedFolderChangedEvent(mEventSource, Scope_Session);
+
+ LogFlowThisFunc(("Leaving for '%s' -> '%s'\n", aName.c_str(), aHostPath.c_str()));
+
+ return rc;
+}
+
+HRESULT Console::removeSharedFolder(const com::Utf8Str &aName)
+{
+ LogFlowThisFunc(("Entering for '%s'\n", aName.c_str()));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /// @todo see @todo in AttachUSBDevice() about the Paused state
+ if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot remove a transient shared folder from a machine in a saved state (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));;
+ if ( mMachineState != MachineState_PoweredOff
+ && mMachineState != MachineState_Teleported
+ && mMachineState != MachineState_Aborted
+ && mMachineState != MachineState_Running
+ && mMachineState != MachineState_Paused
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot remove a transient shared folder from the machine while it is changing the state (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));
+
+ ComObjPtr<SharedFolder> pSharedFolder;
+ HRESULT rc = i_findSharedFolder(aName, pSharedFolder, true /* aSetError */);
+ if (FAILED(rc)) return rc;
+
+ /* protect the VM handle (if not NULL) */
+ SafeVMPtrQuiet ptrVM(this);
+ if ( ptrVM.isOk()
+ && m_pVMMDev
+ && m_pVMMDev->isShFlActive()
+ )
+ {
+ /* if the VM is online and supports shared folders, UNshare this folder. */
+
+ /* first, remove the given folder */
+ rc = i_removeSharedFolder(aName);
+ if (FAILED(rc)) return rc;
+
+ /* first, remove the machine or the global folder if there is any */
+ SharedFolderDataMap::const_iterator it;
+ if (i_findOtherSharedFolder(aName, it))
+ {
+ rc = i_createSharedFolder(aName, it->second);
+ /* don't check rc here because we need to remove the console
+ * folder from the collection even on failure */
+ }
+ }
+
+ m_mapSharedFolders.erase(aName);
+
+ /* Notify console callbacks after the folder is removed from the list. */
+ alock.release();
+ ::FireSharedFolderChangedEvent(mEventSource, Scope_Session);
+
+ LogFlowThisFunc(("Leaving for '%s'\n", aName.c_str()));
+
+ return rc;
+}
+
+HRESULT Console::addEncryptionPassword(const com::Utf8Str &aId, const com::Utf8Str &aPassword,
+ BOOL aClearOnSuspend)
+{
+ if ( aId.isEmpty()
+ || aPassword.isEmpty())
+ return setError(E_FAIL, tr("The ID and password must be both valid"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+ size_t cbKey = aPassword.length() + 1; /* Include terminator */
+ const uint8_t *pbKey = (const uint8_t *)aPassword.c_str();
+
+ int vrc = m_pKeyStore->addSecretKey(aId, pbKey, cbKey);
+ if ( RT_SUCCESS(vrc)
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ || vrc == VERR_ALREADY_EXISTS /* Allow setting an existing key for encrypted VMs. */
+#endif
+ )
+ {
+ unsigned cDisksConfigured = 0;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (mptrNvramStore.isNotNull())
+ mptrNvramStore->i_addPassword(aId, aPassword);
+
+ SecretKey *pKey = NULL;
+ vrc = m_pKeyStore->retainSecretKey(aId, &pKey);
+ AssertRCReturn(vrc, E_FAIL);
+ pKey->setRemoveOnSuspend(!!aClearOnSuspend);
+ pKey->release();
+#endif
+
+ hrc = i_configureEncryptionForDisk(aId, &cDisksConfigured);
+ if (SUCCEEDED(hrc))
+ {
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ SecretKey *pKey = NULL;
+#endif
+ vrc = m_pKeyStore->retainSecretKey(aId, &pKey);
+ AssertRCReturn(vrc, E_FAIL);
+
+ pKey->setUsers(cDisksConfigured);
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ pKey->setRemoveOnSuspend(!!aClearOnSuspend);
+ m_pKeyStore->releaseSecretKey(aId);
+#endif
+ m_cDisksPwProvided += cDisksConfigured;
+
+ if ( m_cDisksPwProvided == m_cDisksEncrypted
+ && mMachineState == MachineState_Paused)
+ {
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ alock.release();
+ vrc = ptrVM.vtable()->pfnVMR3Resume(ptrVM.rawUVM(), VMRESUMEREASON_RECONFIG);
+
+ hrc = RT_SUCCESS(vrc) ? S_OK
+ : setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not resume the machine execution (%Rrc)"), vrc);
+ }
+ }
+ }
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ else if (vrc == VERR_ALREADY_EXISTS)
+ hrc = setErrorBoth(VBOX_E_OBJECT_IN_USE, vrc, tr("A password with the given ID already exists"));
+#endif
+ else if (vrc == VERR_NO_MEMORY)
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to allocate enough secure memory for the key"));
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Unknown error happened while adding a password (%Rrc)"), vrc);
+
+ return hrc;
+}
+
+HRESULT Console::addEncryptionPasswords(const std::vector<com::Utf8Str> &aIds, const std::vector<com::Utf8Str> &aPasswords,
+ BOOL aClearOnSuspend)
+{
+ HRESULT hrc = S_OK;
+
+ if ( aIds.empty()
+ || aPasswords.empty())
+ return setError(E_FAIL, tr("IDs and passwords must not be empty"));
+
+ if (aIds.size() != aPasswords.size())
+ return setError(E_FAIL, tr("The number of entries in the id and password arguments must match"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ /* Check that the IDs do not exist already before changing anything. */
+ for (unsigned i = 0; i < aIds.size(); i++)
+ {
+ SecretKey *pKey = NULL;
+ int vrc = m_pKeyStore->retainSecretKey(aIds[i], &pKey);
+ if (vrc != VERR_NOT_FOUND)
+ {
+ AssertPtr(pKey);
+ if (pKey)
+ pKey->release();
+ return setError(VBOX_E_OBJECT_IN_USE, tr("A password with the given ID already exists"));
+ }
+ }
+#else
+ /*
+ * Passwords for the same ID can be added in different ways because
+ * of encrypted VMs now. Just add them instead of generating an error.
+ */
+ /** @todo Check that passwords with the same ID match. */
+#endif
+
+ for (unsigned i = 0; i < aIds.size(); i++)
+ {
+ hrc = addEncryptionPassword(aIds[i], aPasswords[i], aClearOnSuspend);
+ if (FAILED(hrc))
+ {
+ /*
+ * Try to remove already successfully added passwords from the map to not
+ * change the state of the Console object.
+ */
+ ErrorInfoKeeper eik; /* Keep current error info or it gets deestroyed in the IPC methods below. */
+ for (unsigned ii = 0; ii < i; ii++)
+ {
+ i_clearDiskEncryptionKeysOnAllAttachmentsWithKeyId(aIds[ii]);
+ removeEncryptionPassword(aIds[ii]);
+ }
+
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT Console::removeEncryptionPassword(const com::Utf8Str &aId)
+{
+ if (aId.isEmpty())
+ return setError(E_FAIL, tr("The ID must be valid"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ SecretKey *pKey = NULL;
+ int vrc = m_pKeyStore->retainSecretKey(aId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ m_cDisksPwProvided -= pKey->getUsers();
+ m_pKeyStore->releaseSecretKey(aId);
+ vrc = m_pKeyStore->deleteSecretKey(aId);
+ AssertRCReturn(vrc, E_FAIL);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (mptrNvramStore.isNotNull())
+ mptrNvramStore->i_removePassword(aId);
+#endif
+ }
+ else if (vrc == VERR_NOT_FOUND)
+ return setErrorBoth(VBOX_E_OBJECT_NOT_FOUND, vrc, tr("A password with the ID \"%s\" does not exist"), aId.c_str());
+ else
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to remove password with ID \"%s\" (%Rrc)"), aId.c_str(), vrc);
+
+ return S_OK;
+}
+
+HRESULT Console::clearAllEncryptionPasswords()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (mptrNvramStore.isNotNull())
+ mptrNvramStore->i_removeAllPasswords();
+#endif
+
+ int vrc = m_pKeyStore->deleteAllSecretKeys(false /* fSuspend */, false /* fForce */);
+ if (vrc == VERR_RESOURCE_IN_USE)
+ return setErrorBoth(VBOX_E_OBJECT_IN_USE, vrc, tr("A password is still in use by the VM"));
+ else if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Deleting all passwords failed (%Rrc)"));
+
+ m_cDisksPwProvided = 0;
+ return S_OK;
+}
+
+// Non-interface public methods
+/////////////////////////////////////////////////////////////////////////////
+
+/*static*/
+HRESULT Console::i_setErrorStatic(HRESULT aResultCode, const char *pcsz, ...)
+{
+ va_list args;
+ va_start(args, pcsz);
+ HRESULT rc = setErrorInternalV(aResultCode,
+ getStaticClassIID(),
+ getStaticComponentName(),
+ pcsz, args,
+ false /* aWarning */,
+ true /* aLogIt */);
+ va_end(args);
+ return rc;
+}
+
+/*static*/
+HRESULT Console::i_setErrorStaticBoth(HRESULT aResultCode, int vrc, const char *pcsz, ...)
+{
+ va_list args;
+ va_start(args, pcsz);
+ HRESULT rc = setErrorInternalV(aResultCode,
+ getStaticClassIID(),
+ getStaticComponentName(),
+ pcsz, args,
+ false /* aWarning */,
+ true /* aLogIt */,
+ vrc);
+ va_end(args);
+ return rc;
+}
+
+HRESULT Console::i_setInvalidMachineStateError()
+{
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Invalid machine state: %s"),
+ Global::stringifyMachineState(mMachineState));
+}
+
+
+/**
+ * Converts to PDM device names.
+ */
+/* static */ const char *Console::i_storageControllerTypeToStr(StorageControllerType_T enmCtrlType)
+{
+ switch (enmCtrlType)
+ {
+ case StorageControllerType_LsiLogic:
+ return "lsilogicscsi";
+ case StorageControllerType_BusLogic:
+ return "buslogic";
+ case StorageControllerType_LsiLogicSas:
+ return "lsilogicsas";
+ case StorageControllerType_IntelAhci:
+ return "ahci";
+ case StorageControllerType_PIIX3:
+ case StorageControllerType_PIIX4:
+ case StorageControllerType_ICH6:
+ return "piix3ide";
+ case StorageControllerType_I82078:
+ return "i82078";
+ case StorageControllerType_USB:
+ return "Msd";
+ case StorageControllerType_NVMe:
+ return "nvme";
+ case StorageControllerType_VirtioSCSI:
+ return "virtio-scsi";
+ default:
+ return NULL;
+ }
+}
+
+HRESULT Console::i_storageBusPortDeviceToLun(StorageBus_T enmBus, LONG port, LONG device, unsigned &uLun)
+{
+ switch (enmBus)
+ {
+ case StorageBus_IDE:
+ case StorageBus_Floppy:
+ {
+ AssertMsgReturn(port < 2 && port >= 0, ("%d\n", port), E_INVALIDARG);
+ AssertMsgReturn(device < 2 && device >= 0, ("%d\n", device), E_INVALIDARG);
+ uLun = 2 * port + device;
+ return S_OK;
+ }
+ case StorageBus_SATA:
+ case StorageBus_SCSI:
+ case StorageBus_SAS:
+ case StorageBus_PCIe:
+ case StorageBus_VirtioSCSI:
+ {
+ uLun = port;
+ return S_OK;
+ }
+ case StorageBus_USB:
+ {
+ /*
+ * It is always the first lun, the port denotes the device instance
+ * for the Msd device.
+ */
+ uLun = 0;
+ return S_OK;
+ }
+ default:
+ uLun = 0;
+ AssertMsgFailedReturn(("%d\n", enmBus), E_INVALIDARG);
+ }
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Suspend the VM before we do any medium or network attachment change.
+ *
+ * @param pUVM Safe VM handle.
+ * @param pVMM Safe VMM vtable.
+ * @param pAlock The automatic lock instance. This is for when we have
+ * to leave it in order to avoid deadlocks.
+ * @param pfResume where to store the information if we need to resume
+ * afterwards.
+ */
+HRESULT Console::i_suspendBeforeConfigChange(PUVM pUVM, PCVMMR3VTABLE pVMM, AutoWriteLock *pAlock, bool *pfResume)
+{
+ *pfResume = false;
+
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ switch (enmVMState)
+ {
+ case VMSTATE_RUNNING:
+ case VMSTATE_RESETTING:
+ case VMSTATE_SOFT_RESETTING:
+ {
+ LogFlowFunc(("Suspending the VM...\n"));
+ /* disable the callback to prevent Console-level state change */
+ mVMStateChangeCallbackDisabled = true;
+ if (pAlock)
+ pAlock->release();
+ int vrc = pVMM->pfnVMR3Suspend(pUVM, VMSUSPENDREASON_RECONFIG);
+ if (pAlock)
+ pAlock->acquire();
+ mVMStateChangeCallbackDisabled = false;
+ if (RT_FAILURE(vrc))
+ return setErrorInternalF(VBOX_E_INVALID_VM_STATE,
+ COM_IIDOF(IConsole),
+ getStaticComponentName(),
+ false /*aWarning*/,
+ true /*aLogIt*/,
+ vrc,
+ tr("Could suspend VM for medium change (%Rrc)"), vrc);
+ *pfResume = true;
+ break;
+ }
+ case VMSTATE_SUSPENDED:
+ break;
+ default:
+ return setErrorInternalF(VBOX_E_INVALID_VM_STATE,
+ COM_IIDOF(IConsole),
+ getStaticComponentName(),
+ false /*aWarning*/,
+ true /*aLogIt*/,
+ 0 /* aResultDetail */,
+ tr("Invalid state '%s' for changing medium"),
+ pVMM->pfnVMR3GetStateName(enmVMState));
+ }
+
+ return S_OK;
+}
+
+/**
+ * Resume the VM after we did any medium or network attachment change.
+ * This is the counterpart to Console::suspendBeforeConfigChange().
+ *
+ * @param pUVM Safe VM handle.
+ * @param pVMM Safe VMM vtable.
+ */
+void Console::i_resumeAfterConfigChange(PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ LogFlowFunc(("Resuming the VM...\n"));
+
+ /* disable the callback to prevent Console-level state change */
+ mVMStateChangeCallbackDisabled = true;
+ int rc = pVMM->pfnVMR3Resume(pUVM, VMRESUMEREASON_RECONFIG);
+ mVMStateChangeCallbackDisabled = false;
+ AssertRC(rc);
+ if (RT_FAILURE(rc))
+ {
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ if (enmVMState == VMSTATE_SUSPENDED)
+ {
+ /* too bad, we failed. try to sync the console state with the VMM state */
+ i_vmstateChangeCallback(pUVM, pVMM, VMSTATE_SUSPENDED, enmVMState, this);
+ }
+ }
+}
+
+/**
+ * Process a medium change.
+ *
+ * @param aMediumAttachment The medium attachment with the new medium state.
+ * @param fForce Force medium chance, if it is locked or not.
+ * @param pUVM Safe VM handle.
+ * @param pVMM Safe VMM vtable.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_doMediumChange(IMediumAttachment *aMediumAttachment, bool fForce, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /* We will need to release the write lock before calling EMT */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+ const char *pszDevice = NULL;
+
+ SafeIfaceArray<IStorageController> ctrls;
+ rc = mMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls));
+ AssertComRC(rc);
+ IMedium *pMedium;
+ rc = aMediumAttachment->COMGETTER(Medium)(&pMedium);
+ AssertComRC(rc);
+ Bstr mediumLocation;
+ if (pMedium)
+ {
+ rc = pMedium->COMGETTER(Location)(mediumLocation.asOutParam());
+ AssertComRC(rc);
+ }
+
+ Bstr attCtrlName;
+ rc = aMediumAttachment->COMGETTER(Controller)(attCtrlName.asOutParam());
+ AssertComRC(rc);
+ ComPtr<IStorageController> pStorageController;
+ for (size_t i = 0; i < ctrls.size(); ++i)
+ {
+ Bstr ctrlName;
+ rc = ctrls[i]->COMGETTER(Name)(ctrlName.asOutParam());
+ AssertComRC(rc);
+ if (attCtrlName == ctrlName)
+ {
+ pStorageController = ctrls[i];
+ break;
+ }
+ }
+ if (pStorageController.isNull())
+ return setError(E_FAIL,
+ tr("Could not find storage controller '%ls'"), attCtrlName.raw());
+
+ StorageControllerType_T enmCtrlType;
+ rc = pStorageController->COMGETTER(ControllerType)(&enmCtrlType);
+ AssertComRC(rc);
+ pszDevice = i_storageControllerTypeToStr(enmCtrlType);
+
+ StorageBus_T enmBus;
+ rc = pStorageController->COMGETTER(Bus)(&enmBus);
+ AssertComRC(rc);
+ ULONG uInstance;
+ rc = pStorageController->COMGETTER(Instance)(&uInstance);
+ AssertComRC(rc);
+ BOOL fUseHostIOCache;
+ rc = pStorageController->COMGETTER(UseHostIOCache)(&fUseHostIOCache);
+ AssertComRC(rc);
+
+ /*
+ * Suspend the VM first. The VM must not be running since it might have
+ * pending I/O to the drive which is being changed.
+ */
+ bool fResume = false;
+ rc = i_suspendBeforeConfigChange(pUVM, pVMM, &alock, &fResume);
+ if (FAILED(rc))
+ return rc;
+
+ /*
+ * Call worker on EMT #0, that's faster and safer than doing everything
+ * using VMR3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait
+ * here to make requests from under the lock in order to serialize them.
+ */
+ PVMREQ pReq;
+ int vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)i_changeRemovableMedium, 9,
+ this, pUVM, pVMM, pszDevice, uInstance, enmBus, fUseHostIOCache, aMediumAttachment, fForce);
+
+ /* release the lock before waiting for a result (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ if (vrc == VERR_TIMEOUT)
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+ AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pReq->iStatus;
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ if (fResume)
+ i_resumeAfterConfigChange(pUVM, pVMM);
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("Returns S_OK\n"));
+ return S_OK;
+ }
+
+ if (pMedium)
+ return setErrorBoth(E_FAIL, vrc, tr("Could not mount the media/drive '%ls' (%Rrc)"), mediumLocation.raw(), vrc);
+ return setErrorBoth(E_FAIL, vrc, tr("Could not unmount the currently mounted media/drive (%Rrc)"), vrc);
+}
+
+/**
+ * Performs the medium change in EMT.
+ *
+ * @returns VBox status code.
+ *
+ * @param pThis Pointer to the Console object.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pcszDevice The PDM device name.
+ * @param uInstance The PDM device instance.
+ * @param enmBus The storage bus type of the controller.
+ * @param fUseHostIOCache Whether to use the host I/O cache (disable async I/O).
+ * @param aMediumAtt The medium attachment.
+ * @param fForce Force unmounting.
+ *
+ * @thread EMT
+ * @note The VM must not be running since it might have pending I/O to the drive which is being changed.
+ */
+DECLCALLBACK(int) Console::i_changeRemovableMedium(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ bool fUseHostIOCache,
+ IMediumAttachment *aMediumAtt,
+ bool fForce)
+{
+ LogFlowFunc(("pThis=%p uInstance=%u pszDevice=%p:{%s} enmBus=%u, aMediumAtt=%p, fForce=%d\n",
+ pThis, uInstance, pcszDevice, pcszDevice, enmBus, aMediumAtt, fForce));
+
+ AssertReturn(pThis, VERR_INVALID_PARAMETER);
+
+ AutoCaller autoCaller(pThis);
+ AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED);
+
+ /*
+ * Check the VM for correct state.
+ */
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE);
+
+ int rc = pThis->i_configMediumAttachment(pcszDevice,
+ uInstance,
+ enmBus,
+ fUseHostIOCache,
+ false /* fSetupMerge */,
+ false /* fBuiltinIOCache */,
+ false /* fInsertDiskIntegrityDrv. */,
+ 0 /* uMergeSource */,
+ 0 /* uMergeTarget */,
+ aMediumAtt,
+ pThis->mMachineState,
+ NULL /* phrc */,
+ true /* fAttachDetach */,
+ fForce /* fForceUnmount */,
+ false /* fHotplug */,
+ pUVM,
+ pVMM,
+ NULL /* paLedDevType */,
+ NULL /* ppLunL0 */);
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Attach a new storage device to the VM.
+ *
+ * @param aMediumAttachment The medium attachment which is added.
+ * @param pUVM Safe VM handle.
+ * @param pVMM Safe VMM vtable.
+ * @param fSilent Flag whether to notify the guest about the attached device.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_doStorageDeviceAttach(IMediumAttachment *aMediumAttachment, PUVM pUVM, PCVMMR3VTABLE pVMM, bool fSilent)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /* We will need to release the write lock before calling EMT */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+ const char *pszDevice = NULL;
+
+ SafeIfaceArray<IStorageController> ctrls;
+ rc = mMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls));
+ AssertComRC(rc);
+ IMedium *pMedium;
+ rc = aMediumAttachment->COMGETTER(Medium)(&pMedium);
+ AssertComRC(rc);
+ Bstr mediumLocation;
+ if (pMedium)
+ {
+ rc = pMedium->COMGETTER(Location)(mediumLocation.asOutParam());
+ AssertComRC(rc);
+ }
+
+ Bstr attCtrlName;
+ rc = aMediumAttachment->COMGETTER(Controller)(attCtrlName.asOutParam());
+ AssertComRC(rc);
+ ComPtr<IStorageController> pStorageController;
+ for (size_t i = 0; i < ctrls.size(); ++i)
+ {
+ Bstr ctrlName;
+ rc = ctrls[i]->COMGETTER(Name)(ctrlName.asOutParam());
+ AssertComRC(rc);
+ if (attCtrlName == ctrlName)
+ {
+ pStorageController = ctrls[i];
+ break;
+ }
+ }
+ if (pStorageController.isNull())
+ return setError(E_FAIL, tr("Could not find storage controller '%ls'"), attCtrlName.raw());
+
+ StorageControllerType_T enmCtrlType;
+ rc = pStorageController->COMGETTER(ControllerType)(&enmCtrlType);
+ AssertComRC(rc);
+ pszDevice = i_storageControllerTypeToStr(enmCtrlType);
+
+ StorageBus_T enmBus;
+ rc = pStorageController->COMGETTER(Bus)(&enmBus);
+ AssertComRC(rc);
+ ULONG uInstance;
+ rc = pStorageController->COMGETTER(Instance)(&uInstance);
+ AssertComRC(rc);
+ BOOL fUseHostIOCache;
+ rc = pStorageController->COMGETTER(UseHostIOCache)(&fUseHostIOCache);
+ AssertComRC(rc);
+
+ /*
+ * Suspend the VM first. The VM must not be running since it might have
+ * pending I/O to the drive which is being changed.
+ */
+ bool fResume = false;
+ rc = i_suspendBeforeConfigChange(pUVM, pVMM, &alock, &fResume);
+ if (FAILED(rc))
+ return rc;
+
+ /*
+ * Call worker on EMT #0, that's faster and safer than doing everything
+ * using VMR3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait
+ * here to make requests from under the lock in order to serialize them.
+ */
+ PVMREQ pReq;
+ int vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)i_attachStorageDevice, 9,
+ this, pUVM, pVMM, pszDevice, uInstance, enmBus, fUseHostIOCache, aMediumAttachment, fSilent);
+
+ /* release the lock before waiting for a result (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ if (vrc == VERR_TIMEOUT)
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+ AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pReq->iStatus;
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ if (fResume)
+ i_resumeAfterConfigChange(pUVM, pVMM);
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("Returns S_OK\n"));
+ return S_OK;
+ }
+
+ if (!pMedium)
+ return setErrorBoth(E_FAIL, vrc, tr("Could not mount the media/drive '%ls' (%Rrc)"), mediumLocation.raw(), vrc);
+ return setErrorBoth(E_FAIL, vrc, tr("Could not unmount the currently mounted media/drive (%Rrc)"), vrc);
+}
+
+
+/**
+ * Performs the storage attach operation in EMT.
+ *
+ * @returns VBox status code.
+ *
+ * @param pThis Pointer to the Console object.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pcszDevice The PDM device name.
+ * @param uInstance The PDM device instance.
+ * @param enmBus The storage bus type of the controller.
+ * @param fUseHostIOCache Whether to use the host I/O cache (disable async I/O).
+ * @param aMediumAtt The medium attachment.
+ * @param fSilent Flag whether to inform the guest about the attached device.
+ *
+ * @thread EMT
+ * @note The VM must not be running since it might have pending I/O to the drive which is being changed.
+ */
+DECLCALLBACK(int) Console::i_attachStorageDevice(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ bool fUseHostIOCache,
+ IMediumAttachment *aMediumAtt,
+ bool fSilent)
+{
+ LogFlowFunc(("pThis=%p uInstance=%u pszDevice=%p:{%s} enmBus=%u, aMediumAtt=%p\n",
+ pThis, uInstance, pcszDevice, pcszDevice, enmBus, aMediumAtt));
+
+ AssertReturn(pThis, VERR_INVALID_PARAMETER);
+
+ AutoCaller autoCaller(pThis);
+ AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED);
+
+ /*
+ * Check the VM for correct state.
+ */
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE);
+
+ int rc = pThis->i_configMediumAttachment(pcszDevice,
+ uInstance,
+ enmBus,
+ fUseHostIOCache,
+ false /* fSetupMerge */,
+ false /* fBuiltinIOCache */,
+ false /* fInsertDiskIntegrityDrv. */,
+ 0 /* uMergeSource */,
+ 0 /* uMergeTarget */,
+ aMediumAtt,
+ pThis->mMachineState,
+ NULL /* phrc */,
+ true /* fAttachDetach */,
+ false /* fForceUnmount */,
+ !fSilent /* fHotplug */,
+ pUVM,
+ pVMM,
+ NULL /* paLedDevType */,
+ NULL);
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Attach a new storage device to the VM.
+ *
+ * @param aMediumAttachment The medium attachment which is added.
+ * @param pUVM Safe VM handle.
+ * @param pVMM Safe VMM vtable.
+ * @param fSilent Flag whether to notify the guest about the detached device.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_doStorageDeviceDetach(IMediumAttachment *aMediumAttachment, PUVM pUVM, PCVMMR3VTABLE pVMM, bool fSilent)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /* We will need to release the write lock before calling EMT */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+ const char *pszDevice = NULL;
+
+ SafeIfaceArray<IStorageController> ctrls;
+ rc = mMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls));
+ AssertComRC(rc);
+ IMedium *pMedium;
+ rc = aMediumAttachment->COMGETTER(Medium)(&pMedium);
+ AssertComRC(rc);
+ Bstr mediumLocation;
+ if (pMedium)
+ {
+ rc = pMedium->COMGETTER(Location)(mediumLocation.asOutParam());
+ AssertComRC(rc);
+ }
+
+ Bstr attCtrlName;
+ rc = aMediumAttachment->COMGETTER(Controller)(attCtrlName.asOutParam());
+ AssertComRC(rc);
+ ComPtr<IStorageController> pStorageController;
+ for (size_t i = 0; i < ctrls.size(); ++i)
+ {
+ Bstr ctrlName;
+ rc = ctrls[i]->COMGETTER(Name)(ctrlName.asOutParam());
+ AssertComRC(rc);
+ if (attCtrlName == ctrlName)
+ {
+ pStorageController = ctrls[i];
+ break;
+ }
+ }
+ if (pStorageController.isNull())
+ return setError(E_FAIL, tr("Could not find storage controller '%ls'"), attCtrlName.raw());
+
+ StorageControllerType_T enmCtrlType;
+ rc = pStorageController->COMGETTER(ControllerType)(&enmCtrlType);
+ AssertComRC(rc);
+ pszDevice = i_storageControllerTypeToStr(enmCtrlType);
+
+ StorageBus_T enmBus;
+ rc = pStorageController->COMGETTER(Bus)(&enmBus);
+ AssertComRC(rc);
+ ULONG uInstance;
+ rc = pStorageController->COMGETTER(Instance)(&uInstance);
+ AssertComRC(rc);
+
+ /*
+ * Suspend the VM first. The VM must not be running since it might have
+ * pending I/O to the drive which is being changed.
+ */
+ bool fResume = false;
+ rc = i_suspendBeforeConfigChange(pUVM, pVMM, &alock, &fResume);
+ if (FAILED(rc))
+ return rc;
+
+ /*
+ * Call worker on EMT #0, that's faster and safer than doing everything
+ * using VMR3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait
+ * here to make requests from under the lock in order to serialize them.
+ */
+ PVMREQ pReq;
+ int vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)i_detachStorageDevice, 8,
+ this, pUVM, pVMM, pszDevice, uInstance, enmBus, aMediumAttachment, fSilent);
+
+ /* release the lock before waiting for a result (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ if (vrc == VERR_TIMEOUT)
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+ AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pReq->iStatus;
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ if (fResume)
+ i_resumeAfterConfigChange(pUVM, pVMM);
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("Returns S_OK\n"));
+ return S_OK;
+ }
+
+ if (!pMedium)
+ return setErrorBoth(E_FAIL, vrc, tr("Could not mount the media/drive '%ls' (%Rrc)"), mediumLocation.raw(), vrc);
+ return setErrorBoth(E_FAIL, vrc, tr("Could not unmount the currently mounted media/drive (%Rrc)"), vrc);
+}
+
+/**
+ * Performs the storage detach operation in EMT.
+ *
+ * @returns VBox status code.
+ *
+ * @param pThis Pointer to the Console object.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pcszDevice The PDM device name.
+ * @param uInstance The PDM device instance.
+ * @param enmBus The storage bus type of the controller.
+ * @param pMediumAtt Pointer to the medium attachment.
+ * @param fSilent Flag whether to notify the guest about the detached device.
+ *
+ * @thread EMT
+ * @note The VM must not be running since it might have pending I/O to the drive which is being changed.
+ */
+DECLCALLBACK(int) Console::i_detachStorageDevice(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ IMediumAttachment *pMediumAtt,
+ bool fSilent)
+{
+ LogFlowFunc(("pThis=%p uInstance=%u pszDevice=%p:{%s} enmBus=%u, pMediumAtt=%p\n",
+ pThis, uInstance, pcszDevice, pcszDevice, enmBus, pMediumAtt));
+
+ AssertReturn(pThis, VERR_INVALID_PARAMETER);
+
+ AutoCaller autoCaller(pThis);
+ AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED);
+
+ /*
+ * Check the VM for correct state.
+ */
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE);
+
+ /* Determine the base path for the device instance. */
+ PCFGMNODE pCtlInst;
+ pCtlInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/%s/%u/", pcszDevice, uInstance);
+ AssertReturn(pCtlInst || enmBus == StorageBus_USB, VERR_INTERNAL_ERROR);
+
+#define H() AssertMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_GENERAL_FAILURE)
+
+ HRESULT hrc;
+ int rc = VINF_SUCCESS;
+ int rcRet = VINF_SUCCESS;
+ unsigned uLUN;
+ LONG lDev;
+ LONG lPort;
+ DeviceType_T lType;
+ PCFGMNODE pLunL0 = NULL;
+
+ hrc = pMediumAtt->COMGETTER(Device)(&lDev); H();
+ hrc = pMediumAtt->COMGETTER(Port)(&lPort); H();
+ hrc = pMediumAtt->COMGETTER(Type)(&lType); H();
+ hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN); H();
+
+#undef H
+
+ if (enmBus != StorageBus_USB)
+ {
+ /* First check if the LUN really exists. */
+ pLunL0 = pVMM->pfnCFGMR3GetChildF(pCtlInst, "LUN#%u", uLUN);
+ if (pLunL0)
+ {
+ uint32_t fFlags = 0;
+
+ if (fSilent)
+ fFlags |= PDM_TACH_FLAGS_NOT_HOT_PLUG;
+
+ rc = pVMM->pfnPDMR3DeviceDetach(pUVM, pcszDevice, uInstance, uLUN, fFlags);
+ if (rc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN)
+ rc = VINF_SUCCESS;
+ AssertRCReturn(rc, rc);
+ pVMM->pfnCFGMR3RemoveNode(pLunL0);
+
+ Utf8StrFmt devicePath("%s/%u/LUN#%u", pcszDevice, uInstance, uLUN);
+ pThis->mapMediumAttachments.erase(devicePath);
+
+ }
+ else
+ AssertFailedReturn(VERR_INTERNAL_ERROR);
+
+ pVMM->pfnCFGMR3Dump(pCtlInst);
+ }
+#ifdef VBOX_WITH_USB
+ else
+ {
+ /* Find the correct USB device in the list. */
+ USBStorageDeviceList::iterator it;
+ for (it = pThis->mUSBStorageDevices.begin(); it != pThis->mUSBStorageDevices.end(); ++it)
+ {
+ if (it->iPort == lPort)
+ break;
+ }
+
+ AssertReturn(it != pThis->mUSBStorageDevices.end(), VERR_INTERNAL_ERROR);
+ rc = pVMM->pfnPDMR3UsbDetachDevice(pUVM, &it->mUuid);
+ AssertRCReturn(rc, rc);
+ pThis->mUSBStorageDevices.erase(it);
+ }
+#endif
+
+ LogFlowFunc(("Returning %Rrc\n", rcRet));
+ return rcRet;
+}
+
+/**
+ * Called by IInternalSessionControl::OnNetworkAdapterChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onNetworkAdapterChange(INetworkAdapter *aNetworkAdapter, BOOL changeAdapter)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ /* don't trigger network changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ /* Get the properties we need from the adapter */
+ BOOL fCableConnected, fTraceEnabled;
+ rc = aNetworkAdapter->COMGETTER(CableConnected)(&fCableConnected);
+ AssertComRC(rc);
+ if (SUCCEEDED(rc))
+ {
+ rc = aNetworkAdapter->COMGETTER(TraceEnabled)(&fTraceEnabled);
+ AssertComRC(rc);
+ if (SUCCEEDED(rc))
+ {
+ ULONG ulInstance;
+ rc = aNetworkAdapter->COMGETTER(Slot)(&ulInstance);
+ AssertComRC(rc);
+ if (SUCCEEDED(rc))
+ {
+ /*
+ * Find the adapter instance, get the config interface and update
+ * the link state.
+ */
+ NetworkAdapterType_T adapterType;
+ rc = aNetworkAdapter->COMGETTER(AdapterType)(&adapterType);
+ AssertComRC(rc);
+ const char *pszAdapterName = networkAdapterTypeToName(adapterType);
+
+ // prevent cross-thread deadlocks, don't need the lock any more
+ alock.release();
+
+ PPDMIBASE pBase = NULL;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), pszAdapterName, ulInstance, 0, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMINETWORKCONFIG pINetCfg;
+ pINetCfg = PDMIBASE_QUERY_INTERFACE(pBase, PDMINETWORKCONFIG);
+ if (pINetCfg)
+ {
+ Log(("Console::onNetworkAdapterChange: setting link state to %d\n",
+ fCableConnected));
+ vrc = pINetCfg->pfnSetLinkState(pINetCfg,
+ fCableConnected ? PDMNETWORKLINKSTATE_UP
+ : PDMNETWORKLINKSTATE_DOWN);
+ ComAssertRC(vrc);
+ }
+ if (RT_SUCCESS(vrc) && changeAdapter)
+ {
+ VMSTATE enmVMState = mpVMM->pfnVMR3GetStateU(ptrVM.rawUVM());
+ if ( enmVMState == VMSTATE_RUNNING /** @todo LiveMigration: Forbid or deal
+ correctly with the _LS variants */
+ || enmVMState == VMSTATE_SUSPENDED)
+ {
+ if (fTraceEnabled && fCableConnected && pINetCfg)
+ {
+ vrc = pINetCfg->pfnSetLinkState(pINetCfg, PDMNETWORKLINKSTATE_DOWN);
+ ComAssertRC(vrc);
+ }
+
+ rc = i_doNetworkAdapterChange(ptrVM.rawUVM(), ptrVM.vtable(), pszAdapterName,
+ ulInstance, 0, aNetworkAdapter);
+
+ if (fTraceEnabled && fCableConnected && pINetCfg)
+ {
+ vrc = pINetCfg->pfnSetLinkState(pINetCfg, PDMNETWORKLINKSTATE_UP);
+ ComAssertRC(vrc);
+ }
+ }
+ }
+ }
+ else if (vrc == VERR_PDM_DEVICE_INSTANCE_NOT_FOUND)
+ return setErrorBoth(E_FAIL, vrc, tr("The network adapter #%u is not enabled"), ulInstance);
+ else
+ ComAssertRC(vrc);
+
+ if (RT_FAILURE(vrc))
+ rc = E_FAIL;
+
+ alock.acquire();
+ }
+ }
+ }
+ ptrVM.release();
+ }
+
+ // definitely don't need the lock any more
+ alock.release();
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(rc))
+ ::FireNetworkAdapterChangedEvent(mEventSource, aNetworkAdapter);
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", rc));
+ return rc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnNATEngineChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onNATRedirectRuleChanged(ULONG ulInstance, BOOL aNatRuleRemove, NATProtocol_T aProto, IN_BSTR aHostIP,
+ LONG aHostPort, IN_BSTR aGuestIP, LONG aGuestPort)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ /* don't trigger NAT engine changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ do
+ {
+ ComPtr<INetworkAdapter> pNetworkAdapter;
+ rc = i_machine()->GetNetworkAdapter(ulInstance, pNetworkAdapter.asOutParam());
+ if ( FAILED(rc)
+ || pNetworkAdapter.isNull())
+ break;
+
+ /*
+ * Find the adapter instance, get the config interface and update
+ * the link state.
+ */
+ NetworkAdapterType_T adapterType;
+ rc = pNetworkAdapter->COMGETTER(AdapterType)(&adapterType);
+ if (FAILED(rc))
+ {
+ AssertComRC(rc);
+ rc = E_FAIL;
+ break;
+ }
+
+ const char *pszAdapterName = networkAdapterTypeToName(adapterType);
+ PPDMIBASE pBase;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryLun(ptrVM.rawUVM(), pszAdapterName, ulInstance, 0, &pBase);
+ if (RT_FAILURE(vrc))
+ {
+ /* This may happen if the NAT network adapter is currently not attached.
+ * This is a valid condition. */
+ if (vrc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN)
+ break;
+ ComAssertRC(vrc);
+ rc = E_FAIL;
+ break;
+ }
+
+ NetworkAttachmentType_T attachmentType;
+ rc = pNetworkAdapter->COMGETTER(AttachmentType)(&attachmentType);
+ if ( FAILED(rc)
+ || attachmentType != NetworkAttachmentType_NAT)
+ {
+ rc = E_FAIL;
+ break;
+ }
+
+ /* look down for PDMINETWORKNATCONFIG interface */
+ PPDMINETWORKNATCONFIG pNetNatCfg = NULL;
+ while (pBase)
+ {
+ pNetNatCfg = (PPDMINETWORKNATCONFIG)pBase->pfnQueryInterface(pBase, PDMINETWORKNATCONFIG_IID);
+ if (pNetNatCfg)
+ break;
+ /** @todo r=bird: This stinks! */
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pBase);
+ pBase = pDrvIns->pDownBase;
+ }
+ if (!pNetNatCfg)
+ break;
+
+ bool fUdp = aProto == NATProtocol_UDP;
+ vrc = pNetNatCfg->pfnRedirectRuleCommand(pNetNatCfg, !!aNatRuleRemove, fUdp,
+ Utf8Str(aHostIP).c_str(), (uint16_t)aHostPort, Utf8Str(aGuestIP).c_str(),
+ (uint16_t)aGuestPort);
+ if (RT_FAILURE(vrc))
+ rc = E_FAIL;
+ } while (0); /* break loop */
+ ptrVM.release();
+ }
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", rc));
+ return rc;
+}
+
+
+/*
+ * IHostNameResolutionConfigurationChangeEvent
+ *
+ * Currently this event doesn't carry actual resolver configuration,
+ * so we have to go back to VBoxSVC and ask... This is not ideal.
+ */
+HRESULT Console::i_onNATDnsChanged()
+{
+ HRESULT hrc;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#if 0 /* XXX: We don't yet pass this down to pfnNotifyDnsChanged */
+ ComPtr<IVirtualBox> pVirtualBox;
+ hrc = mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ if (FAILED(hrc))
+ return S_OK;
+
+ ComPtr<IHost> pHost;
+ hrc = pVirtualBox->COMGETTER(Host)(pHost.asOutParam());
+ if (FAILED(hrc))
+ return S_OK;
+
+ SafeArray<BSTR> aNameServers;
+ hrc = pHost->COMGETTER(NameServers)(ComSafeArrayAsOutParam(aNameServers));
+ if (FAILED(hrc))
+ return S_OK;
+
+ const size_t cNameServers = aNameServers.size();
+ Log(("DNS change - %zu nameservers\n", cNameServers));
+
+ for (size_t i = 0; i < cNameServers; ++i)
+ {
+ com::Utf8Str strNameServer(aNameServers[i]);
+ Log(("- nameserver[%zu] = \"%s\"\n", i, strNameServer.c_str()));
+ }
+
+ com::Bstr domain;
+ pHost->COMGETTER(DomainName)(domain.asOutParam());
+ Log(("domain name = \"%s\"\n", com::Utf8Str(domain).c_str()));
+#endif /* 0 */
+
+ ChipsetType_T enmChipsetType;
+ hrc = mMachine->COMGETTER(ChipsetType)(&enmChipsetType);
+ if (!FAILED(hrc))
+ {
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ ULONG ulInstanceMax = (ULONG)Global::getMaxNetworkAdapters(enmChipsetType);
+
+ notifyNatDnsChange(ptrVM.rawUVM(), ptrVM.vtable(), "pcnet", ulInstanceMax);
+ notifyNatDnsChange(ptrVM.rawUVM(), ptrVM.vtable(), "e1000", ulInstanceMax);
+ notifyNatDnsChange(ptrVM.rawUVM(), ptrVM.vtable(), "virtio-net", ulInstanceMax);
+ }
+ }
+
+ return S_OK;
+}
+
+
+/*
+ * This routine walks over all network device instances, checking if
+ * device instance has DrvNAT attachment and triggering DrvNAT DNS
+ * change callback.
+ */
+void Console::notifyNatDnsChange(PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDevice, ULONG ulInstanceMax)
+{
+ Log(("notifyNatDnsChange: looking for DrvNAT attachment on %s device instances\n", pszDevice));
+ for (ULONG ulInstance = 0; ulInstance < ulInstanceMax; ulInstance++)
+ {
+ PPDMIBASE pBase;
+ int rc = pVMM->pfnPDMR3QueryDriverOnLun(pUVM, pszDevice, ulInstance, 0 /* iLun */, "NAT", &pBase);
+ if (RT_FAILURE(rc))
+ continue;
+
+ Log(("Instance %s#%d has DrvNAT attachment; do actual notify\n", pszDevice, ulInstance));
+ if (pBase)
+ {
+ PPDMINETWORKNATCONFIG pNetNatCfg = NULL;
+ pNetNatCfg = (PPDMINETWORKNATCONFIG)pBase->pfnQueryInterface(pBase, PDMINETWORKNATCONFIG_IID);
+ if (pNetNatCfg && pNetNatCfg->pfnNotifyDnsChanged)
+ pNetNatCfg->pfnNotifyDnsChanged(pNetNatCfg);
+ }
+ }
+}
+
+
+VMMDevMouseInterface *Console::i_getVMMDevMouseInterface()
+{
+ return m_pVMMDev;
+}
+
+DisplayMouseInterface *Console::i_getDisplayMouseInterface()
+{
+ return mDisplay;
+}
+
+/**
+ * Parses one key value pair.
+ *
+ * @returns VBox status code.
+ * @param psz Configuration string.
+ * @param ppszEnd Where to store the pointer to the string following the key value pair.
+ * @param ppszKey Where to store the key on success.
+ * @param ppszVal Where to store the value on success.
+ */
+int Console::i_consoleParseKeyValue(const char *psz, const char **ppszEnd,
+ char **ppszKey, char **ppszVal)
+{
+ int rc = VINF_SUCCESS;
+ const char *pszKeyStart = psz;
+ const char *pszValStart = NULL;
+ size_t cchKey = 0;
+ size_t cchVal = 0;
+
+ while ( *psz != '='
+ && *psz)
+ psz++;
+
+ /* End of string at this point is invalid. */
+ if (*psz == '\0')
+ return VERR_INVALID_PARAMETER;
+
+ cchKey = psz - pszKeyStart;
+ psz++; /* Skip = character */
+ pszValStart = psz;
+
+ while ( *psz != ','
+ && *psz != '\n'
+ && *psz != '\r'
+ && *psz)
+ psz++;
+
+ cchVal = psz - pszValStart;
+
+ if (cchKey && cchVal)
+ {
+ *ppszKey = RTStrDupN(pszKeyStart, cchKey);
+ if (*ppszKey)
+ {
+ *ppszVal = RTStrDupN(pszValStart, cchVal);
+ if (!*ppszVal)
+ {
+ RTStrFree(*ppszKey);
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ if (RT_SUCCESS(rc))
+ *ppszEnd = psz;
+
+ return rc;
+}
+
+/**
+ * Initializes the secret key interface on all configured attachments.
+ *
+ * @returns COM status code.
+ */
+HRESULT Console::i_initSecretKeyIfOnAllAttachments(void)
+{
+ HRESULT hrc = S_OK;
+ SafeIfaceArray<IMediumAttachment> sfaAttachments;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /* Get the VM - must be done before the read-locking. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ hrc = mMachine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
+ AssertComRCReturnRC(hrc);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ m_cDisksPwProvided = 0;
+#endif
+
+ /* Find the correct attachment. */
+ for (unsigned i = 0; i < sfaAttachments.size(); i++)
+ {
+ const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[i];
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ ComPtr<IMedium> pMedium;
+ ComPtr<IMedium> pBase;
+
+ hrc = pAtt->COMGETTER(Medium)(pMedium.asOutParam());
+ AssertComRC(hrc);
+
+ bool fKeepSecIf = false;
+ /* Skip non hard disk attachments. */
+ if (pMedium.isNotNull())
+ {
+ /* Get the UUID of the base medium and compare. */
+ hrc = pMedium->COMGETTER(Base)(pBase.asOutParam());
+ AssertComRC(hrc);
+
+ Bstr bstrKeyId;
+ hrc = pBase->GetProperty(Bstr("CRYPT/KeyId").raw(), bstrKeyId.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Utf8Str strKeyId(bstrKeyId);
+ SecretKey *pKey = NULL;
+ int vrc = m_pKeyStore->retainSecretKey(strKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ fKeepSecIf = true;
+ m_pKeyStore->releaseSecretKey(strKeyId);
+ }
+ }
+ }
+#endif
+
+ /*
+ * Query storage controller, port and device
+ * to identify the correct driver.
+ */
+ ComPtr<IStorageController> pStorageCtrl;
+ Bstr storageCtrlName;
+ LONG lPort, lDev;
+ ULONG ulStorageCtrlInst;
+
+ hrc = pAtt->COMGETTER(Controller)(storageCtrlName.asOutParam());
+ AssertComRC(hrc);
+
+ hrc = pAtt->COMGETTER(Port)(&lPort);
+ AssertComRC(hrc);
+
+ hrc = pAtt->COMGETTER(Device)(&lDev);
+ AssertComRC(hrc);
+
+ hrc = mMachine->GetStorageControllerByName(storageCtrlName.raw(), pStorageCtrl.asOutParam());
+ AssertComRC(hrc);
+
+ hrc = pStorageCtrl->COMGETTER(Instance)(&ulStorageCtrlInst);
+ AssertComRC(hrc);
+
+ StorageControllerType_T enmCtrlType;
+ hrc = pStorageCtrl->COMGETTER(ControllerType)(&enmCtrlType);
+ AssertComRC(hrc);
+ const char *pcszDevice = i_storageControllerTypeToStr(enmCtrlType);
+
+ StorageBus_T enmBus;
+ hrc = pStorageCtrl->COMGETTER(Bus)(&enmBus);
+ AssertComRC(hrc);
+
+ unsigned uLUN;
+ hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN);
+ AssertComRC(hrc);
+
+ PPDMIBASE pIBase = NULL;
+ PPDMIMEDIA pIMedium = NULL;
+ int rc = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(), pcszDevice, ulStorageCtrlInst, uLUN, "VD", &pIBase);
+ if (RT_SUCCESS(rc))
+ {
+ if (pIBase)
+ {
+ pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID);
+ if (pIMedium)
+ {
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ rc = pIMedium->pfnSetSecKeyIf(pIMedium, fKeepSecIf ? mpIfSecKey : NULL, mpIfSecKeyHlp);
+ Assert(RT_SUCCESS(rc) || rc == VERR_NOT_SUPPORTED);
+ if (fKeepSecIf)
+ m_cDisksPwProvided++;
+#else
+ rc = pIMedium->pfnSetSecKeyIf(pIMedium, NULL, mpIfSecKeyHlp);
+ Assert(RT_SUCCESS(rc) || rc == VERR_NOT_SUPPORTED);
+#endif
+ }
+ }
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * Removes the key interfaces from all disk attachments with the given key ID.
+ * Useful when changing the key store or dropping it.
+ *
+ * @returns COM status code.
+ * @param strId The ID to look for.
+ */
+HRESULT Console::i_clearDiskEncryptionKeysOnAllAttachmentsWithKeyId(const Utf8Str &strId)
+{
+ HRESULT hrc = S_OK;
+ SafeIfaceArray<IMediumAttachment> sfaAttachments;
+
+ /* Get the VM - must be done before the read-locking. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ hrc = mMachine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
+ AssertComRCReturnRC(hrc);
+
+ /* Find the correct attachment. */
+ for (unsigned i = 0; i < sfaAttachments.size(); i++)
+ {
+ const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[i];
+ ComPtr<IMedium> pMedium;
+ ComPtr<IMedium> pBase;
+ Bstr bstrKeyId;
+
+ hrc = pAtt->COMGETTER(Medium)(pMedium.asOutParam());
+ if (FAILED(hrc))
+ break;
+
+ /* Skip non hard disk attachments. */
+ if (pMedium.isNull())
+ continue;
+
+ /* Get the UUID of the base medium and compare. */
+ hrc = pMedium->COMGETTER(Base)(pBase.asOutParam());
+ if (FAILED(hrc))
+ break;
+
+ hrc = pBase->GetProperty(Bstr("CRYPT/KeyId").raw(), bstrKeyId.asOutParam());
+ if (hrc == VBOX_E_OBJECT_NOT_FOUND)
+ {
+ hrc = S_OK;
+ continue;
+ }
+ else if (FAILED(hrc))
+ break;
+
+ if (strId.equals(Utf8Str(bstrKeyId)))
+ {
+
+ /*
+ * Query storage controller, port and device
+ * to identify the correct driver.
+ */
+ ComPtr<IStorageController> pStorageCtrl;
+ Bstr storageCtrlName;
+ LONG lPort, lDev;
+ ULONG ulStorageCtrlInst;
+
+ hrc = pAtt->COMGETTER(Controller)(storageCtrlName.asOutParam());
+ AssertComRC(hrc);
+
+ hrc = pAtt->COMGETTER(Port)(&lPort);
+ AssertComRC(hrc);
+
+ hrc = pAtt->COMGETTER(Device)(&lDev);
+ AssertComRC(hrc);
+
+ hrc = mMachine->GetStorageControllerByName(storageCtrlName.raw(), pStorageCtrl.asOutParam());
+ AssertComRC(hrc);
+
+ hrc = pStorageCtrl->COMGETTER(Instance)(&ulStorageCtrlInst);
+ AssertComRC(hrc);
+
+ StorageControllerType_T enmCtrlType;
+ hrc = pStorageCtrl->COMGETTER(ControllerType)(&enmCtrlType);
+ AssertComRC(hrc);
+ const char *pcszDevice = i_storageControllerTypeToStr(enmCtrlType);
+
+ StorageBus_T enmBus;
+ hrc = pStorageCtrl->COMGETTER(Bus)(&enmBus);
+ AssertComRC(hrc);
+
+ unsigned uLUN;
+ hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN);
+ AssertComRC(hrc);
+
+ PPDMIBASE pIBase = NULL;
+ PPDMIMEDIA pIMedium = NULL;
+ int rc = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(), pcszDevice, ulStorageCtrlInst, uLUN, "VD", &pIBase);
+ if (RT_SUCCESS(rc))
+ {
+ if (pIBase)
+ {
+ pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID);
+ if (pIMedium)
+ {
+ rc = pIMedium->pfnSetSecKeyIf(pIMedium, NULL, mpIfSecKeyHlp);
+ Assert(RT_SUCCESS(rc) || rc == VERR_NOT_SUPPORTED);
+ }
+ }
+ }
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * Configures the encryption support for the disk which have encryption conigured
+ * with the configured key.
+ *
+ * @returns COM status code.
+ * @param strId The ID of the password.
+ * @param pcDisksConfigured Where to store the number of disks configured for the given ID.
+ */
+HRESULT Console::i_configureEncryptionForDisk(const com::Utf8Str &strId, unsigned *pcDisksConfigured)
+{
+ unsigned cDisksConfigured = 0;
+ HRESULT hrc = S_OK;
+ SafeIfaceArray<IMediumAttachment> sfaAttachments;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /* Get the VM - must be done before the read-locking. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ hrc = mMachine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
+ if (FAILED(hrc))
+ return hrc;
+
+ /* Find the correct attachment. */
+ for (unsigned i = 0; i < sfaAttachments.size(); i++)
+ {
+ const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[i];
+ ComPtr<IMedium> pMedium;
+ ComPtr<IMedium> pBase;
+ Bstr bstrKeyId;
+
+ hrc = pAtt->COMGETTER(Medium)(pMedium.asOutParam());
+ if (FAILED(hrc))
+ break;
+
+ /* Skip non hard disk attachments. */
+ if (pMedium.isNull())
+ continue;
+
+ /* Get the UUID of the base medium and compare. */
+ hrc = pMedium->COMGETTER(Base)(pBase.asOutParam());
+ if (FAILED(hrc))
+ break;
+
+ hrc = pBase->GetProperty(Bstr("CRYPT/KeyId").raw(), bstrKeyId.asOutParam());
+ if (hrc == VBOX_E_OBJECT_NOT_FOUND)
+ {
+ hrc = S_OK;
+ continue;
+ }
+ else if (FAILED(hrc))
+ break;
+
+ if (strId.equals(Utf8Str(bstrKeyId)))
+ {
+ /*
+ * Found the matching medium, query storage controller, port and device
+ * to identify the correct driver.
+ */
+ ComPtr<IStorageController> pStorageCtrl;
+ Bstr storageCtrlName;
+ LONG lPort, lDev;
+ ULONG ulStorageCtrlInst;
+
+ hrc = pAtt->COMGETTER(Controller)(storageCtrlName.asOutParam());
+ if (FAILED(hrc))
+ break;
+
+ hrc = pAtt->COMGETTER(Port)(&lPort);
+ if (FAILED(hrc))
+ break;
+
+ hrc = pAtt->COMGETTER(Device)(&lDev);
+ if (FAILED(hrc))
+ break;
+
+ hrc = mMachine->GetStorageControllerByName(storageCtrlName.raw(), pStorageCtrl.asOutParam());
+ if (FAILED(hrc))
+ break;
+
+ hrc = pStorageCtrl->COMGETTER(Instance)(&ulStorageCtrlInst);
+ if (FAILED(hrc))
+ break;
+
+ StorageControllerType_T enmCtrlType;
+ hrc = pStorageCtrl->COMGETTER(ControllerType)(&enmCtrlType);
+ AssertComRC(hrc);
+ const char *pcszDevice = i_storageControllerTypeToStr(enmCtrlType);
+
+ StorageBus_T enmBus;
+ hrc = pStorageCtrl->COMGETTER(Bus)(&enmBus);
+ AssertComRC(hrc);
+
+ unsigned uLUN;
+ hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN);
+ AssertComRCReturnRC(hrc);
+
+ PPDMIBASE pIBase = NULL;
+ PPDMIMEDIA pIMedium = NULL;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(), pcszDevice, ulStorageCtrlInst, uLUN, "VD", &pIBase);
+ if (RT_SUCCESS(vrc))
+ {
+ if (pIBase)
+ {
+ pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID);
+ if (!pIMedium)
+ return setError(E_FAIL, tr("could not query medium interface of controller"));
+ vrc = pIMedium->pfnSetSecKeyIf(pIMedium, mpIfSecKey, mpIfSecKeyHlp);
+ if (vrc == VERR_VD_PASSWORD_INCORRECT)
+ {
+ hrc = setError(VBOX_E_PASSWORD_INCORRECT,
+ tr("The provided password for ID \"%s\" is not correct for at least one disk using this ID"),
+ strId.c_str());
+ break;
+ }
+ else if (RT_FAILURE(vrc))
+ {
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to set the encryption key (%Rrc)"), vrc);
+ break;
+ }
+
+ if (RT_SUCCESS(vrc))
+ cDisksConfigured++;
+ }
+ else
+ return setError(E_FAIL, tr("could not query base interface of controller"));
+ }
+ }
+ }
+
+ if ( SUCCEEDED(hrc)
+ && pcDisksConfigured)
+ *pcDisksConfigured = cDisksConfigured;
+ else if (FAILED(hrc))
+ {
+ /* Clear disk encryption setup on successfully configured attachments. */
+ ErrorInfoKeeper eik; /* Keep current error info or it gets deestroyed in the IPC methods below. */
+ i_clearDiskEncryptionKeysOnAllAttachmentsWithKeyId(strId);
+ }
+
+ return hrc;
+}
+
+/**
+ * Parses the encryption configuration for one disk.
+ *
+ * @returns COM status code.
+ * @param psz Pointer to the configuration for the encryption of one disk.
+ * @param ppszEnd Pointer to the string following encrpytion configuration.
+ */
+HRESULT Console::i_consoleParseDiskEncryption(const char *psz, const char **ppszEnd)
+{
+ char *pszUuid = NULL;
+ char *pszKeyEnc = NULL;
+ int rc = VINF_SUCCESS;
+ HRESULT hrc = S_OK;
+
+ while ( *psz
+ && RT_SUCCESS(rc))
+ {
+ char *pszKey = NULL;
+ char *pszVal = NULL;
+ const char *pszEnd = NULL;
+
+ rc = i_consoleParseKeyValue(psz, &pszEnd, &pszKey, &pszVal);
+ if (RT_SUCCESS(rc))
+ {
+ if (!RTStrCmp(pszKey, "uuid"))
+ pszUuid = pszVal;
+ else if (!RTStrCmp(pszKey, "dek"))
+ pszKeyEnc = pszVal;
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ RTStrFree(pszKey);
+
+ if (*pszEnd == ',')
+ psz = pszEnd + 1;
+ else
+ {
+ /*
+ * End of the configuration for the current disk, skip linefeed and
+ * carriage returns.
+ */
+ while ( *pszEnd == '\n'
+ || *pszEnd == '\r')
+ pszEnd++;
+
+ psz = pszEnd;
+ break; /* Stop parsing */
+ }
+
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pszUuid
+ && pszKeyEnc)
+ {
+ ssize_t cbKey = 0;
+
+ /* Decode the key. */
+ cbKey = RTBase64DecodedSize(pszKeyEnc, NULL);
+ if (cbKey != -1)
+ {
+ uint8_t *pbKey;
+ rc = RTMemSaferAllocZEx((void **)&pbKey, cbKey, RTMEMSAFER_F_REQUIRE_NOT_PAGABLE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTBase64Decode(pszKeyEnc, pbKey, cbKey, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = m_pKeyStore->addSecretKey(Utf8Str(pszUuid), pbKey, cbKey);
+ if (RT_SUCCESS(rc))
+ {
+ hrc = i_configureEncryptionForDisk(Utf8Str(pszUuid), NULL);
+ if (FAILED(hrc))
+ {
+ /* Delete the key from the map. */
+ rc = m_pKeyStore->deleteSecretKey(Utf8Str(pszUuid));
+ AssertRC(rc);
+ }
+ }
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, rc, tr("Failed to decode the key (%Rrc)"), rc);
+
+ RTMemSaferFree(pbKey, cbKey);
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, rc, tr("Failed to allocate secure memory for the key (%Rrc)"), rc);
+ }
+ else
+ hrc = setError(E_FAIL,
+ tr("The base64 encoding of the passed key is incorrect"));
+ }
+ else if (RT_SUCCESS(rc))
+ hrc = setError(E_FAIL,
+ tr("The encryption configuration is incomplete"));
+
+ if (pszUuid)
+ RTStrFree(pszUuid);
+ if (pszKeyEnc)
+ {
+ RTMemWipeThoroughly(pszKeyEnc, strlen(pszKeyEnc), 10 /* cMinPasses */);
+ RTStrFree(pszKeyEnc);
+ }
+
+ if (ppszEnd)
+ *ppszEnd = psz;
+
+ return hrc;
+}
+
+HRESULT Console::i_setDiskEncryptionKeys(const Utf8Str &strCfg)
+{
+ HRESULT hrc = S_OK;
+ const char *pszCfg = strCfg.c_str();
+
+ while ( *pszCfg
+ && SUCCEEDED(hrc))
+ {
+ const char *pszNext = NULL;
+ hrc = i_consoleParseDiskEncryption(pszCfg, &pszNext);
+ pszCfg = pszNext;
+ }
+
+ return hrc;
+}
+
+void Console::i_removeSecretKeysOnSuspend()
+{
+ /* Remove keys which are supposed to be removed on a suspend. */
+ int rc = m_pKeyStore->deleteAllSecretKeys(true /* fSuspend */, true /* fForce */);
+ AssertRC(rc); NOREF(rc);
+}
+
+/**
+ * Process a network adaptor change.
+ *
+ * @returns COM status code.
+ *
+ * @param pUVM The VM handle (caller hold this safely).
+ * @param pVMM The VMM vtable.
+ * @param pszDevice The PDM device name.
+ * @param uInstance The PDM device instance.
+ * @param uLun The PDM LUN number of the drive.
+ * @param aNetworkAdapter The network adapter whose attachment needs to be changed
+ */
+HRESULT Console::i_doNetworkAdapterChange(PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDevice,
+ unsigned uInstance, unsigned uLun, INetworkAdapter *aNetworkAdapter)
+{
+ LogFlowThisFunc(("pszDevice=%p:{%s} uInstance=%u uLun=%u aNetworkAdapter=%p\n",
+ pszDevice, pszDevice, uInstance, uLun, aNetworkAdapter));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /*
+ * Suspend the VM first.
+ */
+ bool fResume = false;
+ HRESULT hr = i_suspendBeforeConfigChange(pUVM, pVMM, NULL, &fResume);
+ if (FAILED(hr))
+ return hr;
+
+ /*
+ * Call worker in EMT, that's faster and safer than doing everything
+ * using VM3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait
+ * here to make requests from under the lock in order to serialize them.
+ */
+ int rc = pVMM->pfnVMR3ReqCallWaitU(pUVM, 0 /*idDstCpu*/,
+ (PFNRT)i_changeNetworkAttachment, 7,
+ this, pUVM, pVMM, pszDevice, uInstance, uLun, aNetworkAdapter);
+
+ if (fResume)
+ i_resumeAfterConfigChange(pUVM, pVMM);
+
+ if (RT_SUCCESS(rc))
+ return S_OK;
+
+ return setErrorBoth(E_FAIL, rc, tr("Could not change the network adaptor attachement type (%Rrc)"), rc);
+}
+
+
+/**
+ * Performs the Network Adaptor change in EMT.
+ *
+ * @returns VBox status code.
+ *
+ * @param pThis Pointer to the Console object.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pszDevice The PDM device name.
+ * @param uInstance The PDM device instance.
+ * @param uLun The PDM LUN number of the drive.
+ * @param aNetworkAdapter The network adapter whose attachment needs to be changed
+ *
+ * @thread EMT
+ * @note Locks the Console object for writing.
+ * @note The VM must not be running.
+ */
+DECLCALLBACK(int) Console::i_changeNetworkAttachment(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pszDevice,
+ unsigned uInstance,
+ unsigned uLun,
+ INetworkAdapter *aNetworkAdapter)
+{
+ LogFlowFunc(("pThis=%p pszDevice=%p:{%s} uInstance=%u uLun=%u aNetworkAdapter=%p\n",
+ pThis, pszDevice, pszDevice, uInstance, uLun, aNetworkAdapter));
+
+ AssertReturn(pThis, VERR_INVALID_PARAMETER);
+
+ AutoCaller autoCaller(pThis);
+ AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED);
+
+ ComPtr<IVirtualBox> pVirtualBox;
+ pThis->mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ ComPtr<ISystemProperties> pSystemProperties;
+ if (pVirtualBox)
+ pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ ChipsetType_T chipsetType = ChipsetType_PIIX3;
+ pThis->mMachine->COMGETTER(ChipsetType)(&chipsetType);
+ ULONG maxNetworkAdapters = 0;
+ if (pSystemProperties)
+ pSystemProperties->GetMaxNetworkAdapters(chipsetType, &maxNetworkAdapters);
+ AssertMsg( ( !strcmp(pszDevice, "pcnet")
+ || !strcmp(pszDevice, "e1000")
+ || !strcmp(pszDevice, "virtio-net"))
+ && uLun == 0
+ && uInstance < maxNetworkAdapters,
+ ("pszDevice=%s uLun=%d uInstance=%d\n", pszDevice, uLun, uInstance));
+ Log(("pszDevice=%s uLun=%d uInstance=%d\n", pszDevice, uLun, uInstance));
+
+ /*
+ * Check the VM for correct state.
+ */
+ PCFGMNODE pCfg = NULL; /* /Devices/Dev/.../Config/ */
+ PCFGMNODE pLunL0 = NULL; /* /Devices/Dev/0/LUN#0/ */
+ PCFGMNODE pInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/%s/%d/", pszDevice, uInstance);
+ AssertRelease(pInst);
+
+ int rc = pThis->i_configNetwork(pszDevice, uInstance, uLun, aNetworkAdapter, pCfg, pLunL0, pInst,
+ true /*fAttachDetach*/, false /*fIgnoreConnectFailure*/, pUVM, pVMM);
+
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Returns the device name of a given audio adapter.
+ *
+ * @returns Device name, or an empty string if no device is configured.
+ * @param aAudioAdapter Audio adapter to return device name for.
+ */
+Utf8Str Console::i_getAudioAdapterDeviceName(IAudioAdapter *aAudioAdapter)
+{
+ Utf8Str strDevice;
+
+ AudioControllerType_T audioController;
+ HRESULT hrc = aAudioAdapter->COMGETTER(AudioController)(&audioController);
+ AssertComRC(hrc);
+ if (SUCCEEDED(hrc))
+ {
+ switch (audioController)
+ {
+ case AudioControllerType_HDA: strDevice = "hda"; break;
+ case AudioControllerType_AC97: strDevice = "ichac97"; break;
+ case AudioControllerType_SB16: strDevice = "sb16"; break;
+ default: break; /* None. */
+ }
+ }
+
+ return strDevice;
+}
+
+/**
+ * Called by IInternalSessionControl::OnAudioAdapterChange().
+ */
+HRESULT Console::i_onAudioAdapterChange(IAudioAdapter *aAudioAdapter)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ /* don't trigger audio changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ BOOL fEnabledIn, fEnabledOut;
+ hrc = aAudioAdapter->COMGETTER(EnabledIn)(&fEnabledIn);
+ AssertComRC(hrc);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = aAudioAdapter->COMGETTER(EnabledOut)(&fEnabledOut);
+ AssertComRC(hrc);
+ if (SUCCEEDED(hrc))
+ {
+ int rc = VINF_SUCCESS;
+
+ for (ULONG ulLUN = 0; ulLUN < 16 /** @todo Use a define */; ulLUN++)
+ {
+ PPDMIBASE pBase;
+ int rc2 = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(),
+ i_getAudioAdapterDeviceName(aAudioAdapter).c_str(),
+ 0 /* iInstance */, ulLUN, "AUDIO", &pBase);
+ if (RT_FAILURE(rc2))
+ continue;
+
+ if (pBase)
+ {
+ PPDMIAUDIOCONNECTOR pAudioCon = (PPDMIAUDIOCONNECTOR)pBase->pfnQueryInterface(pBase,
+ PDMIAUDIOCONNECTOR_IID);
+ if ( pAudioCon
+ && pAudioCon->pfnEnable)
+ {
+ int rcIn = pAudioCon->pfnEnable(pAudioCon, PDMAUDIODIR_IN, RT_BOOL(fEnabledIn));
+ if (RT_FAILURE(rcIn))
+ LogRel(("Audio: Failed to %s input of LUN#%RU32, rc=%Rrc\n",
+ fEnabledIn ? "enable" : "disable", ulLUN, rcIn));
+
+ if (RT_SUCCESS(rc))
+ rc = rcIn;
+
+ int rcOut = pAudioCon->pfnEnable(pAudioCon, PDMAUDIODIR_OUT, RT_BOOL(fEnabledOut));
+ if (RT_FAILURE(rcOut))
+ LogRel(("Audio: Failed to %s output of LUN#%RU32, rc=%Rrc\n",
+ fEnabledIn ? "enable" : "disable", ulLUN, rcOut));
+
+ if (RT_SUCCESS(rc))
+ rc = rcOut;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ LogRel(("Audio: Status has changed (input is %s, output is %s)\n",
+ fEnabledIn ? "enabled" : "disabled", fEnabledOut ? "enabled" : "disabled"));
+ }
+ }
+
+ ptrVM.release();
+ }
+
+ alock.release();
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ ::FireAudioAdapterChangedEvent(mEventSource, aAudioAdapter);
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", S_OK));
+ return S_OK;
+}
+
+/**
+ * Called by IInternalSessionControl::OnHostAudioDeviceChange().
+ */
+HRESULT Console::i_onHostAudioDeviceChange(IHostAudioDevice *aDevice, BOOL aNew, AudioDeviceState_T aState,
+ IVirtualBoxErrorInfo *aErrInfo)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ /** @todo Implement logic here. */
+
+ alock.release();
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ ::FireHostAudioDeviceChangedEvent(mEventSource, aDevice, aNew, aState, aErrInfo);
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", S_OK));
+ return S_OK;
+}
+
+/**
+ * Performs the Serial Port attachment change in EMT.
+ *
+ * @returns VBox status code.
+ *
+ * @param pThis Pointer to the Console object.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pSerialPort The serial port whose attachment needs to be changed
+ *
+ * @thread EMT
+ * @note Locks the Console object for writing.
+ * @note The VM must not be running.
+ */
+DECLCALLBACK(int) Console::i_changeSerialPortAttachment(Console *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, ISerialPort *pSerialPort)
+{
+ LogFlowFunc(("pThis=%p pUVM=%p pSerialPort=%p\n", pThis, pUVM, pSerialPort));
+
+ AssertReturn(pThis, VERR_INVALID_PARAMETER);
+
+ AutoCaller autoCaller(pThis);
+ AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED);
+
+ AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Check the VM for correct state.
+ */
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE);
+
+ HRESULT hrc = S_OK;
+ int rc = VINF_SUCCESS;
+ ULONG ulSlot;
+ hrc = pSerialPort->COMGETTER(Slot)(&ulSlot);
+ if (SUCCEEDED(hrc))
+ {
+ /* Check whether the port mode changed and act accordingly. */
+ Assert(ulSlot < 4);
+
+ PortMode_T eHostMode;
+ hrc = pSerialPort->COMGETTER(HostMode)(&eHostMode);
+ if (SUCCEEDED(hrc))
+ {
+ PCFGMNODE pInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/serial/%d/", ulSlot);
+ AssertRelease(pInst);
+
+ /* Remove old driver. */
+ if (pThis->m_aeSerialPortMode[ulSlot] != PortMode_Disconnected)
+ {
+ rc = pVMM->pfnPDMR3DeviceDetach(pUVM, "serial", ulSlot, 0, 0);
+ PCFGMNODE pLunL0 = pVMM->pfnCFGMR3GetChildF(pInst, "LUN#0");
+ pVMM->pfnCFGMR3RemoveNode(pLunL0);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ BOOL fServer;
+ Bstr bstrPath;
+ hrc = pSerialPort->COMGETTER(Server)(&fServer);
+ if (SUCCEEDED(hrc))
+ hrc = pSerialPort->COMGETTER(Path)(bstrPath.asOutParam());
+
+ /* Configure new driver. */
+ if ( SUCCEEDED(hrc)
+ && eHostMode != PortMode_Disconnected)
+ {
+ rc = pThis->i_configSerialPort(pInst, eHostMode, Utf8Str(bstrPath).c_str(), RT_BOOL(fServer));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Attach the driver.
+ */
+ PPDMIBASE pBase;
+ rc = pVMM->pfnPDMR3DeviceAttach(pUVM, "serial", ulSlot, 0, 0, &pBase);
+
+ pVMM->pfnCFGMR3Dump(pInst);
+ }
+ }
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc) && FAILED(hrc))
+ rc = VERR_INTERNAL_ERROR;
+
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Called by IInternalSessionControl::OnSerialPortChange().
+ */
+HRESULT Console::i_onSerialPortChange(ISerialPort *aSerialPort)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ HRESULT hrc = S_OK;
+
+ /* don't trigger audio changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ ULONG ulSlot;
+ BOOL fEnabled = FALSE;
+ hrc = aSerialPort->COMGETTER(Slot)(&ulSlot);
+ if (SUCCEEDED(hrc))
+ hrc = aSerialPort->COMGETTER(Enabled)(&fEnabled);
+ if (SUCCEEDED(hrc) && fEnabled)
+ {
+ /* Check whether the port mode changed and act accordingly. */
+ Assert(ulSlot < 4);
+
+ PortMode_T eHostMode;
+ hrc = aSerialPort->COMGETTER(HostMode)(&eHostMode);
+ if (m_aeSerialPortMode[ulSlot] != eHostMode)
+ {
+ /*
+ * Suspend the VM first.
+ */
+ bool fResume = false;
+ HRESULT hr = i_suspendBeforeConfigChange(ptrVM.rawUVM(), ptrVM.vtable(), NULL, &fResume);
+ if (FAILED(hr))
+ return hr;
+
+ /*
+ * Call worker in EMT, that's faster and safer than doing everything
+ * using VM3ReqCallWait.
+ */
+ int rc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), 0 /*idDstCpu*/,
+ (PFNRT)i_changeSerialPortAttachment, 4,
+ this, ptrVM.rawUVM(), ptrVM.vtable(), aSerialPort);
+
+ if (fResume)
+ i_resumeAfterConfigChange(ptrVM.rawUVM(), ptrVM.vtable());
+ if (RT_SUCCESS(rc))
+ m_aeSerialPortMode[ulSlot] = eHostMode;
+ else
+ hrc = setErrorBoth(E_FAIL, rc, tr("Failed to change the serial port attachment (%Rrc)"), rc);
+ }
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ ::FireSerialPortChangedEvent(mEventSource, aSerialPort);
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", S_OK));
+ return hrc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnParallelPortChange().
+ */
+HRESULT Console::i_onParallelPortChange(IParallelPort *aParallelPort)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ ::FireParallelPortChangedEvent(mEventSource, aParallelPort);
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", S_OK));
+ return S_OK;
+}
+
+/**
+ * Called by IInternalSessionControl::OnStorageControllerChange().
+ */
+HRESULT Console::i_onStorageControllerChange(const Guid &aMachineId, const Utf8Str &aControllerName)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ ::FireStorageControllerChangedEvent(mEventSource, aMachineId.toString(), aControllerName);
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", S_OK));
+ return S_OK;
+}
+
+/**
+ * Called by IInternalSessionControl::OnMediumChange().
+ */
+HRESULT Console::i_onMediumChange(IMediumAttachment *aMediumAttachment, BOOL aForce)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ HRESULT rc = S_OK;
+
+ /* don't trigger medium changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ rc = i_doMediumChange(aMediumAttachment, !!aForce, ptrVM.rawUVM(), ptrVM.vtable());
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(rc))
+ ::FireMediumChangedEvent(mEventSource, aMediumAttachment);
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", rc));
+ return rc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnCPUChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onCPUChange(ULONG aCPU, BOOL aRemove)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ HRESULT rc = S_OK;
+
+ /* don't trigger CPU changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if (aRemove)
+ rc = i_doCPURemove(aCPU, ptrVM.rawUVM(), ptrVM.vtable());
+ else
+ rc = i_doCPUAdd(aCPU, ptrVM.rawUVM(), ptrVM.vtable());
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(rc))
+ ::FireCPUChangedEvent(mEventSource, aCPU, aRemove);
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", rc));
+ return rc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnCpuExecutionCapChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onCPUExecutionCapChange(ULONG aExecutionCap)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ /* don't trigger the CPU priority change if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if ( mMachineState == MachineState_Running
+ || mMachineState == MachineState_Teleporting
+ || mMachineState == MachineState_LiveSnapshotting
+ )
+ {
+ /* No need to call in the EMT thread. */
+ rc = ptrVM.vtable()->pfnVMR3SetCpuExecutionCap(ptrVM.rawUVM(), aExecutionCap);
+ }
+ else
+ rc = i_setInvalidMachineStateError();
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(rc))
+ {
+ alock.release();
+ ::FireCPUExecutionCapChangedEvent(mEventSource, aExecutionCap);
+ }
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", rc));
+ return rc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnClipboardModeChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onClipboardModeChange(ClipboardMode_T aClipboardMode)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ /* don't trigger the clipboard mode change if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if ( mMachineState == MachineState_Running
+ || mMachineState == MachineState_Teleporting
+ || mMachineState == MachineState_LiveSnapshotting)
+ {
+ int vrc = i_changeClipboardMode(aClipboardMode);
+ if (RT_FAILURE(vrc))
+ rc = E_FAIL; /** @todo r=andy Set error info here? */
+ }
+ else
+ rc = i_setInvalidMachineStateError();
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(rc))
+ {
+ alock.release();
+ ::FireClipboardModeChangedEvent(mEventSource, aClipboardMode);
+ }
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", rc));
+ return rc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnClipboardFileTransferModeChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onClipboardFileTransferModeChange(bool aEnabled)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ /* don't trigger the change if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if ( mMachineState == MachineState_Running
+ || mMachineState == MachineState_Teleporting
+ || mMachineState == MachineState_LiveSnapshotting)
+ {
+ int vrc = i_changeClipboardFileTransferMode(aEnabled);
+ if (RT_FAILURE(vrc))
+ rc = E_FAIL; /** @todo r=andy Set error info here? */
+ }
+ else
+ rc = i_setInvalidMachineStateError();
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(rc))
+ {
+ alock.release();
+ ::FireClipboardFileTransferModeChangedEvent(mEventSource, aEnabled ? TRUE : FALSE);
+ }
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", rc));
+ return rc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnDnDModeChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onDnDModeChange(DnDMode_T aDnDMode)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ /* don't trigger the drag and drop mode change if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if ( mMachineState == MachineState_Running
+ || mMachineState == MachineState_Teleporting
+ || mMachineState == MachineState_LiveSnapshotting)
+ i_changeDnDMode(aDnDMode);
+ else
+ rc = i_setInvalidMachineStateError();
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(rc))
+ {
+ alock.release();
+ ::FireDnDModeChangedEvent(mEventSource, aDnDMode);
+ }
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", rc));
+ return rc;
+}
+
+/**
+ * Check the return code of mConsoleVRDPServer->Launch. LogRel() the error reason and
+ * return an error message appropriate for setError().
+ */
+Utf8Str Console::VRDPServerErrorToMsg(int vrc)
+{
+ Utf8Str errMsg;
+ if (vrc == VERR_NET_ADDRESS_IN_USE)
+ {
+ /* Not fatal if we start the VM, fatal if the VM is already running. */
+ Bstr bstr;
+ mVRDEServer->GetVRDEProperty(Bstr("TCP/Ports").raw(), bstr.asOutParam());
+ errMsg = Utf8StrFmt(tr("VirtualBox Remote Desktop Extension server can't bind to the port(s): %s"),
+ Utf8Str(bstr).c_str());
+ LogRel(("VRDE: Warning: failed to launch VRDE server (%Rrc): %s\n", vrc, errMsg.c_str()));
+ }
+ else if (vrc == VINF_NOT_SUPPORTED)
+ {
+ /* This means that the VRDE is not installed.
+ * Not fatal if we start the VM, fatal if the VM is already running. */
+ LogRel(("VRDE: VirtualBox Remote Desktop Extension is not available.\n"));
+ errMsg = Utf8Str(tr("VirtualBox Remote Desktop Extension is not available"));
+ }
+ else if (RT_FAILURE(vrc))
+ {
+ /* Fail if the server is installed but can't start. Always fatal. */
+ switch (vrc)
+ {
+ case VERR_FILE_NOT_FOUND:
+ errMsg = Utf8StrFmt(tr("Could not find the VirtualBox Remote Desktop Extension library"));
+ break;
+ default:
+ errMsg = Utf8StrFmt(tr("Failed to launch the Remote Desktop Extension server (%Rrc)"), vrc);
+ break;
+ }
+ LogRel(("VRDE: Failed: (%Rrc): %s\n", vrc, errMsg.c_str()));
+ }
+
+ return errMsg;
+}
+
+/**
+ * Called by IInternalSessionControl::OnVRDEServerChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onVRDEServerChange(BOOL aRestart)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ /* don't trigger VRDE server changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ /* Serialize. */
+ if (mfVRDEChangeInProcess)
+ mfVRDEChangePending = true;
+ else
+ {
+ do {
+ mfVRDEChangeInProcess = true;
+ mfVRDEChangePending = false;
+
+ if ( mVRDEServer
+ && ( mMachineState == MachineState_Running
+ || mMachineState == MachineState_Teleporting
+ || mMachineState == MachineState_LiveSnapshotting
+ || mMachineState == MachineState_Paused
+ )
+ )
+ {
+ BOOL vrdpEnabled = FALSE;
+
+ rc = mVRDEServer->COMGETTER(Enabled)(&vrdpEnabled);
+ ComAssertComRCRetRC(rc);
+
+ if (aRestart)
+ {
+ /* VRDP server may call this Console object back from other threads (VRDP INPUT or OUTPUT). */
+ alock.release();
+
+ if (vrdpEnabled)
+ {
+ // If there was no VRDP server started the 'stop' will do nothing.
+ // However if a server was started and this notification was called,
+ // we have to restart the server.
+ mConsoleVRDPServer->Stop();
+
+ int vrc = mConsoleVRDPServer->Launch();
+ if (vrc != VINF_SUCCESS)
+ {
+ Utf8Str errMsg = VRDPServerErrorToMsg(vrc);
+ rc = setErrorBoth(E_FAIL, vrc, errMsg.c_str());
+ }
+ else
+ {
+#ifdef VBOX_WITH_AUDIO_VRDE
+ mAudioVRDE->doAttachDriverViaEmt(ptrVM.rawUVM(), ptrVM.vtable(), NULL /*alock is not held*/);
+#endif
+ mConsoleVRDPServer->EnableConnections();
+ }
+ }
+ else
+ {
+ mConsoleVRDPServer->Stop();
+#ifdef VBOX_WITH_AUDIO_VRDE
+ mAudioVRDE->doDetachDriverViaEmt(ptrVM.rawUVM(), ptrVM.vtable(), NULL /*alock is not held*/);
+#endif
+ }
+
+ alock.acquire();
+ }
+ }
+ else
+ rc = i_setInvalidMachineStateError();
+
+ mfVRDEChangeInProcess = false;
+ } while (mfVRDEChangePending && SUCCEEDED(rc));
+ }
+
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(rc))
+ {
+ alock.release();
+ ::FireVRDEServerChangedEvent(mEventSource);
+ }
+
+ return rc;
+}
+
+void Console::i_onVRDEServerInfoChange()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ ::FireVRDEServerInfoChangedEvent(mEventSource);
+}
+
+HRESULT Console::i_sendACPIMonitorHotPlugEvent()
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_LiveSnapshotting)
+ return i_setInvalidMachineStateError();
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ // no need to release lock, as there are no cross-thread callbacks
+
+ /* get the acpi device interface and press the sleep button. */
+ PPDMIBASE pBase;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT);
+ if (pPort)
+ vrc = pPort->pfnMonitorHotPlugEvent(pPort);
+ else
+ vrc = VERR_PDM_MISSING_INTERFACE;
+ }
+
+ HRESULT rc = RT_SUCCESS(vrc) ? S_OK
+ : setErrorBoth(VBOX_E_PDM_ERROR, vrc, tr("Sending monitor hot-plug event failed (%Rrc)"), vrc);
+
+ LogFlowThisFunc(("rc=%Rhrc\n", rc));
+ LogFlowThisFuncLeave();
+ return rc;
+}
+
+#ifdef VBOX_WITH_RECORDING
+/**
+ * Enables or disables recording of a VM.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NO_CHANGE if the recording state has not been changed.
+ * @param fEnable Whether to enable or disable the recording.
+ * @param pAutoLock Pointer to auto write lock to use for attaching/detaching required driver(s) at runtime.
+ */
+int Console::i_recordingEnable(BOOL fEnable, util::AutoWriteLock *pAutoLock)
+{
+ AssertPtrReturn(pAutoLock, VERR_INVALID_POINTER);
+
+ int vrc = VINF_SUCCESS;
+
+ Display *pDisplay = i_getDisplay();
+ if (pDisplay)
+ {
+ bool const fIsEnabled = mRecording.mCtx.IsStarted();
+
+ if (RT_BOOL(fEnable) != fIsEnabled)
+ {
+ LogRel(("Recording: %s\n", fEnable ? "Enabling" : "Disabling"));
+
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if (fEnable)
+ {
+ vrc = i_recordingCreate();
+ if (RT_SUCCESS(vrc))
+ {
+# ifdef VBOX_WITH_AUDIO_RECORDING
+ /* Attach the video recording audio driver if required. */
+ if ( mRecording.mCtx.IsFeatureEnabled(RecordingFeature_Audio)
+ && mRecording.mAudioRec)
+ {
+ vrc = mRecording.mAudioRec->applyConfiguration(mRecording.mCtx.GetConfig());
+ if (RT_SUCCESS(vrc))
+ vrc = mRecording.mAudioRec->doAttachDriverViaEmt(ptrVM.rawUVM(), ptrVM.vtable(), pAutoLock);
+
+ if (RT_FAILURE(vrc))
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Attaching to audio recording driver failed (%Rrc) -- please consult log file for details"), vrc);
+ }
+# endif
+ if ( RT_SUCCESS(vrc)
+ && mRecording.mCtx.IsReady()) /* Any video recording (audio and/or video) feature enabled? */
+ {
+ vrc = pDisplay->i_recordingInvalidate();
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = i_recordingStart(pAutoLock);
+ if (RT_FAILURE(vrc))
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recording start failed (%Rrc) -- please consult log file for details"), vrc);
+ }
+ }
+ }
+ else
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recording initialization failed (%Rrc) -- please consult log file for details"), vrc);
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Recording: Failed to enable with %Rrc\n", vrc));
+ }
+ else
+ {
+ vrc = i_recordingStop(pAutoLock);
+ if (RT_SUCCESS(vrc))
+ {
+# ifdef VBOX_WITH_AUDIO_RECORDING
+ if (mRecording.mAudioRec)
+ mRecording.mAudioRec->doDetachDriverViaEmt(ptrVM.rawUVM(), ptrVM.vtable(), pAutoLock);
+# endif
+ i_recordingDestroy();
+ }
+ else
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recording stop failed (%Rrc) -- please consult log file for details"), vrc);
+ }
+ }
+ else
+ vrc = VERR_VM_INVALID_VM_STATE;
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Recording: %s failed with %Rrc\n", fEnable ? "Enabling" : "Disabling", vrc));
+ }
+ else /* Should not happen. */
+ {
+ vrc = VERR_NO_CHANGE;
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recording already %s"), fIsEnabled ? tr("enabled") : tr("disabled"));
+ }
+ }
+
+ return vrc;
+}
+#endif /* VBOX_WITH_RECORDING */
+
+/**
+ * Called by IInternalSessionControl::OnRecordingChange().
+ */
+HRESULT Console::i_onRecordingChange(BOOL fEnabled)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+#ifdef VBOX_WITH_RECORDING
+ /* Don't trigger recording changes if the VM isn't running. */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ LogFlowThisFunc(("fEnabled=%RTbool\n", RT_BOOL(fEnabled)));
+
+ int vrc = i_recordingEnable(fEnabled, &alock);
+ if (RT_SUCCESS(vrc))
+ {
+ alock.release();
+ ::FireRecordingChangedEvent(mEventSource);
+ }
+ else /* Error set via ErrorInfo within i_recordingEnable() already. */
+ rc = VBOX_E_IPRT_ERROR;
+ ptrVM.release();
+ }
+#else
+ RT_NOREF(fEnabled);
+#endif /* VBOX_WITH_RECORDING */
+ return rc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnUSBControllerChange().
+ */
+HRESULT Console::i_onUSBControllerChange()
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ ::FireUSBControllerChangedEvent(mEventSource);
+
+ return S_OK;
+}
+
+/**
+ * Called by IInternalSessionControl::OnSharedFolderChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onSharedFolderChange(BOOL aGlobal)
+{
+ LogFlowThisFunc(("aGlobal=%RTbool\n", aGlobal));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_fetchSharedFolders(aGlobal);
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(rc))
+ {
+ alock.release();
+ ::FireSharedFolderChangedEvent(mEventSource, aGlobal ? Scope_Global : Scope_Machine);
+ }
+
+ return rc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnGuestDebugControlChange().
+ */
+HRESULT Console::i_onGuestDebugControlChange(IGuestDebugControl *aGuestDebugControl)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ HRESULT hrc = S_OK;
+
+ /* don't trigger changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ /// @todo
+ }
+
+ if (SUCCEEDED(hrc))
+ ::FireGuestDebugControlChangedEvent(mEventSource, aGuestDebugControl);
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", S_OK));
+ return hrc;
+}
+
+
+/**
+ * Called by IInternalSessionControl::OnUSBDeviceAttach() or locally by
+ * processRemoteUSBDevices() after IInternalMachineControl::RunUSBDeviceFilters()
+ * returns TRUE for a given remote USB device.
+ *
+ * @return S_OK if the device was attached to the VM.
+ * @return failure if not attached.
+ *
+ * @param aDevice The device in question.
+ * @param aError Error information.
+ * @param aMaskedIfs The interfaces to hide from the guest.
+ * @param aCaptureFilename File name where to store the USB traffic.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onUSBDeviceAttach(IUSBDevice *aDevice, IVirtualBoxErrorInfo *aError, ULONG aMaskedIfs,
+ const Utf8Str &aCaptureFilename)
+{
+#ifdef VBOX_WITH_USB
+ LogFlowThisFunc(("aDevice=%p aError=%p\n", aDevice, aError));
+
+ AutoCaller autoCaller(this);
+ ComAssertComRCRetRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Get the VM pointer (we don't need error info, since it's a callback). */
+ SafeVMPtrQuiet ptrVM(this);
+ if (!ptrVM.isOk())
+ {
+ /* The VM may be no more operational when this message arrives
+ * (e.g. it may be Saving or Stopping or just PoweredOff) --
+ * autoVMCaller.rc() will return a failure in this case. */
+ LogFlowThisFunc(("Attach request ignored (mMachineState=%d).\n", mMachineState));
+ return ptrVM.rc();
+ }
+
+ if (aError != NULL)
+ {
+ /* notify callbacks about the error */
+ alock.release();
+ i_onUSBDeviceStateChange(aDevice, true /* aAttached */, aError);
+ return S_OK;
+ }
+
+ /* Don't proceed unless there's at least one USB hub. */
+ if (!ptrVM.vtable()->pfnPDMR3UsbHasHub(ptrVM.rawUVM()))
+ {
+ LogFlowThisFunc(("Attach request ignored (no USB controller).\n"));
+ return E_FAIL;
+ }
+
+ alock.release();
+ HRESULT rc = i_attachUSBDevice(aDevice, aMaskedIfs, aCaptureFilename);
+ if (FAILED(rc))
+ {
+ /* take the current error info */
+ com::ErrorInfoKeeper eik;
+ /* the error must be a VirtualBoxErrorInfo instance */
+ ComPtr<IVirtualBoxErrorInfo> pError = eik.takeError();
+ Assert(!pError.isNull());
+ if (!pError.isNull())
+ {
+ /* notify callbacks about the error */
+ i_onUSBDeviceStateChange(aDevice, true /* aAttached */, pError);
+ }
+ }
+
+ return rc;
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aDevice, aError, aMaskedIfs, aCaptureFilename);
+ return E_FAIL;
+#endif /* !VBOX_WITH_USB */
+}
+
+/**
+ * Called by IInternalSessionControl::OnUSBDeviceDetach() and locally by
+ * processRemoteUSBDevices().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onUSBDeviceDetach(IN_BSTR aId,
+ IVirtualBoxErrorInfo *aError)
+{
+#ifdef VBOX_WITH_USB
+ Guid Uuid(aId);
+ LogFlowThisFunc(("aId={%RTuuid} aError=%p\n", Uuid.raw(), aError));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Find the device. */
+ ComObjPtr<OUSBDevice> pUSBDevice;
+ USBDeviceList::iterator it = mUSBDevices.begin();
+ while (it != mUSBDevices.end())
+ {
+ LogFlowThisFunc(("it={%RTuuid}\n", (*it)->i_id().raw()));
+ if ((*it)->i_id() == Uuid)
+ {
+ pUSBDevice = *it;
+ break;
+ }
+ ++it;
+ }
+
+
+ if (pUSBDevice.isNull())
+ {
+ LogFlowThisFunc(("USB device not found.\n"));
+
+ /* The VM may be no more operational when this message arrives
+ * (e.g. it may be Saving or Stopping or just PoweredOff). Use
+ * AutoVMCaller to detect it -- AutoVMCaller::rc() will return a
+ * failure in this case. */
+
+ AutoVMCallerQuiet autoVMCaller(this);
+ if (FAILED(autoVMCaller.rc()))
+ {
+ LogFlowThisFunc(("Detach request ignored (mMachineState=%d).\n",
+ mMachineState));
+ return autoVMCaller.rc();
+ }
+
+ /* the device must be in the list otherwise */
+ AssertFailedReturn(E_FAIL);
+ }
+
+ if (aError != NULL)
+ {
+ /* notify callback about an error */
+ alock.release();
+ i_onUSBDeviceStateChange(pUSBDevice, false /* aAttached */, aError);
+ return S_OK;
+ }
+
+ /* Remove the device from the collection, it is re-added below for failures */
+ mUSBDevices.erase(it);
+
+ alock.release();
+ HRESULT rc = i_detachUSBDevice(pUSBDevice);
+ if (FAILED(rc))
+ {
+ /* Re-add the device to the collection */
+ alock.acquire();
+ mUSBDevices.push_back(pUSBDevice);
+ alock.release();
+ /* take the current error info */
+ com::ErrorInfoKeeper eik;
+ /* the error must be a VirtualBoxErrorInfo instance */
+ ComPtr<IVirtualBoxErrorInfo> pError = eik.takeError();
+ Assert(!pError.isNull());
+ if (!pError.isNull())
+ {
+ /* notify callbacks about the error */
+ i_onUSBDeviceStateChange(pUSBDevice, false /* aAttached */, pError);
+ }
+ }
+
+ return rc;
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aId, aError);
+ return E_FAIL;
+#endif /* !VBOX_WITH_USB */
+}
+
+/**
+ * Called by IInternalSessionControl::OnBandwidthGroupChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onBandwidthGroupChange(IBandwidthGroup *aBandwidthGroup)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ /* don't trigger bandwidth group changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if ( mMachineState == MachineState_Running
+ || mMachineState == MachineState_Teleporting
+ || mMachineState == MachineState_LiveSnapshotting
+ )
+ {
+ /* No need to call in the EMT thread. */
+ Bstr bstrName;
+ rc = aBandwidthGroup->COMGETTER(Name)(bstrName.asOutParam());
+ if (SUCCEEDED(rc))
+ {
+ Utf8Str const strName(bstrName);
+ LONG64 cMax;
+ rc = aBandwidthGroup->COMGETTER(MaxBytesPerSec)(&cMax);
+ if (SUCCEEDED(rc))
+ {
+ BandwidthGroupType_T enmType;
+ rc = aBandwidthGroup->COMGETTER(Type)(&enmType);
+ if (SUCCEEDED(rc))
+ {
+ int vrc = VINF_SUCCESS;
+ if (enmType == BandwidthGroupType_Disk)
+ vrc = ptrVM.vtable()->pfnPDMR3AsyncCompletionBwMgrSetMaxForFile(ptrVM.rawUVM(), strName.c_str(),
+ (uint32_t)cMax);
+#ifdef VBOX_WITH_NETSHAPER
+ else if (enmType == BandwidthGroupType_Network)
+ vrc = ptrVM.vtable()->pfnPDMR3NsBwGroupSetLimit(ptrVM.rawUVM(), strName.c_str(), cMax);
+ else
+ rc = E_NOTIMPL;
+#endif
+ AssertRC(vrc);
+ }
+ }
+ }
+ }
+ else
+ rc = i_setInvalidMachineStateError();
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(rc))
+ {
+ alock.release();
+ ::FireBandwidthGroupChangedEvent(mEventSource, aBandwidthGroup);
+ }
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", rc));
+ return rc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnStorageDeviceChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onStorageDeviceChange(IMediumAttachment *aMediumAttachment, BOOL aRemove, BOOL aSilent)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ HRESULT rc = S_OK;
+
+ /* don't trigger medium changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if (aRemove)
+ rc = i_doStorageDeviceDetach(aMediumAttachment, ptrVM.rawUVM(), ptrVM.vtable(), RT_BOOL(aSilent));
+ else
+ rc = i_doStorageDeviceAttach(aMediumAttachment, ptrVM.rawUVM(), ptrVM.vtable(), RT_BOOL(aSilent));
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(rc))
+ ::FireStorageDeviceChangedEvent(mEventSource, aMediumAttachment, aRemove, aSilent);
+
+ LogFlowThisFunc(("Leaving rc=%#x\n", rc));
+ return rc;
+}
+
+HRESULT Console::i_onExtraDataChange(const Bstr &aMachineId, const Bstr &aKey, const Bstr &aVal)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return autoCaller.rc();
+
+ if (aMachineId != i_getId())
+ return S_OK;
+
+ /* don't do anything if the VM isn't running */
+ if (aKey == "VBoxInternal2/TurnResetIntoPowerOff")
+ {
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ mfTurnResetIntoPowerOff = aVal == "1";
+ int vrc = ptrVM.vtable()->pfnVMR3SetPowerOffInsteadOfReset(ptrVM.rawUVM(), mfTurnResetIntoPowerOff);
+ AssertRC(vrc);
+
+ ptrVM.release();
+ }
+ }
+
+ /* notify console callbacks on success */
+ ::FireExtraDataChangedEvent(mEventSource, aMachineId.raw(), aKey.raw(), aVal.raw());
+
+ LogFlowThisFunc(("Leaving S_OK\n"));
+ return S_OK;
+}
+
+/**
+ * @note Temporarily locks this object for writing.
+ */
+HRESULT Console::i_getGuestProperty(const Utf8Str &aName, Utf8Str *aValue, LONG64 *aTimestamp, Utf8Str *aFlags)
+{
+#ifndef VBOX_WITH_GUEST_PROPS
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_GUEST_PROPS */
+ if (!RT_VALID_PTR(aValue))
+ return E_POINTER;
+ if (aTimestamp != NULL && !RT_VALID_PTR(aTimestamp))
+ return E_POINTER;
+ if (aFlags != NULL && !RT_VALID_PTR(aFlags))
+ return E_POINTER;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /* protect mpUVM (if not NULL) */
+ SafeVMPtrQuiet ptrVM(this);
+ if (FAILED(ptrVM.rc()))
+ return ptrVM.rc();
+
+ /* Note: validity of mVMMDev which is bound to uninit() is guaranteed by
+ * ptrVM, so there is no need to hold a lock of this */
+
+ HRESULT rc = E_UNEXPECTED;
+ try
+ {
+ VBOXHGCMSVCPARM parm[4];
+ char szBuffer[GUEST_PROP_MAX_VALUE_LEN + GUEST_PROP_MAX_FLAGS_LEN];
+
+ parm[0].type = VBOX_HGCM_SVC_PARM_PTR;
+ parm[0].u.pointer.addr = (void *)aName.c_str();
+ parm[0].u.pointer.size = (uint32_t)aName.length() + 1; /* The + 1 is the null terminator */
+
+ parm[1].type = VBOX_HGCM_SVC_PARM_PTR;
+ parm[1].u.pointer.addr = szBuffer;
+ parm[1].u.pointer.size = sizeof(szBuffer);
+
+ parm[2].type = VBOX_HGCM_SVC_PARM_64BIT;
+ parm[2].u.uint64 = 0;
+
+ parm[3].type = VBOX_HGCM_SVC_PARM_32BIT;
+ parm[3].u.uint32 = 0;
+
+ int vrc = m_pVMMDev->hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_GET_PROP,
+ 4, &parm[0]);
+ /* The returned string should never be able to be greater than our buffer */
+ AssertLogRel(vrc != VERR_BUFFER_OVERFLOW);
+ AssertLogRel(RT_FAILURE(vrc) || parm[2].type == VBOX_HGCM_SVC_PARM_64BIT);
+ if (RT_SUCCESS(vrc))
+ {
+ *aValue = szBuffer;
+
+ if (aTimestamp)
+ *aTimestamp = parm[2].u.uint64;
+
+ if (aFlags)
+ *aFlags = &szBuffer[strlen(szBuffer) + 1];
+
+ rc = S_OK;
+ }
+ else if (vrc == VERR_NOT_FOUND)
+ {
+ *aValue = "";
+ rc = S_OK;
+ }
+ else
+ rc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("The VBoxGuestPropSvc service call failed with the error %Rrc"), vrc);
+ }
+ catch(std::bad_alloc & /*e*/)
+ {
+ rc = E_OUTOFMEMORY;
+ }
+
+ return rc;
+#endif /* VBOX_WITH_GUEST_PROPS */
+}
+
+/**
+ * @note Temporarily locks this object for writing.
+ */
+HRESULT Console::i_setGuestProperty(const Utf8Str &aName, const Utf8Str &aValue, const Utf8Str &aFlags)
+{
+#ifndef VBOX_WITH_GUEST_PROPS
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_GUEST_PROPS */
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /* protect mpUVM (if not NULL) */
+ SafeVMPtrQuiet ptrVM(this);
+ if (FAILED(ptrVM.rc()))
+ return ptrVM.rc();
+
+ /* Note: validity of mVMMDev which is bound to uninit() is guaranteed by
+ * ptrVM, so there is no need to hold a lock of this */
+
+ VBOXHGCMSVCPARM parm[3];
+
+ parm[0].type = VBOX_HGCM_SVC_PARM_PTR;
+ parm[0].u.pointer.addr = (void*)aName.c_str();
+ parm[0].u.pointer.size = (uint32_t)aName.length() + 1; /* The + 1 is the null terminator */
+
+ parm[1].type = VBOX_HGCM_SVC_PARM_PTR;
+ parm[1].u.pointer.addr = (void *)aValue.c_str();
+ parm[1].u.pointer.size = (uint32_t)aValue.length() + 1; /* The + 1 is the null terminator */
+
+ int vrc;
+ if (aFlags.isEmpty())
+ {
+ vrc = m_pVMMDev->hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_PROP_VALUE, 2, &parm[0]);
+ }
+ else
+ {
+ parm[2].type = VBOX_HGCM_SVC_PARM_PTR;
+ parm[2].u.pointer.addr = (void*)aFlags.c_str();
+ parm[2].u.pointer.size = (uint32_t)aFlags.length() + 1; /* The + 1 is the null terminator */
+
+ vrc = m_pVMMDev->hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_PROP, 3, &parm[0]);
+ }
+
+ HRESULT hrc = S_OK;
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("The VBoxGuestPropSvc service call failed with the error %Rrc"), vrc);
+ return hrc;
+#endif /* VBOX_WITH_GUEST_PROPS */
+}
+
+HRESULT Console::i_deleteGuestProperty(const Utf8Str &aName)
+{
+#ifndef VBOX_WITH_GUEST_PROPS
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_GUEST_PROPS */
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /* protect mpUVM (if not NULL) */
+ SafeVMPtrQuiet ptrVM(this);
+ if (FAILED(ptrVM.rc()))
+ return ptrVM.rc();
+
+ /* Note: validity of mVMMDev which is bound to uninit() is guaranteed by
+ * ptrVM, so there is no need to hold a lock of this */
+
+ VBOXHGCMSVCPARM parm[1];
+ parm[0].type = VBOX_HGCM_SVC_PARM_PTR;
+ parm[0].u.pointer.addr = (void*)aName.c_str();
+ parm[0].u.pointer.size = (uint32_t)aName.length() + 1; /* The + 1 is the null terminator */
+
+ int vrc = m_pVMMDev->hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_DEL_PROP, 1, &parm[0]);
+
+ HRESULT hrc = S_OK;
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("The VBoxGuestPropSvc service call failed with the error %Rrc"), vrc);
+ return hrc;
+#endif /* VBOX_WITH_GUEST_PROPS */
+}
+
+/**
+ * @note Temporarily locks this object for writing.
+ */
+HRESULT Console::i_enumerateGuestProperties(const Utf8Str &aPatterns,
+ std::vector<Utf8Str> &aNames,
+ std::vector<Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<Utf8Str> &aFlags)
+{
+#ifndef VBOX_WITH_GUEST_PROPS
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_GUEST_PROPS */
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /* protect mpUVM (if not NULL) */
+ AutoVMCallerWeak autoVMCaller(this);
+ if (FAILED(autoVMCaller.rc()))
+ return autoVMCaller.rc();
+
+ /* Note: validity of mVMMDev which is bound to uninit() is guaranteed by
+ * autoVMCaller, so there is no need to hold a lock of this */
+
+ return i_doEnumerateGuestProperties(aPatterns, aNames, aValues, aTimestamps, aFlags);
+#endif /* VBOX_WITH_GUEST_PROPS */
+}
+
+
+/*
+ * Internal: helper function for connecting progress reporting
+ */
+static DECLCALLBACK(int) onlineMergeMediumProgress(void *pvUser, unsigned uPercentage)
+{
+ HRESULT rc = S_OK;
+ IProgress *pProgress = static_cast<IProgress *>(pvUser);
+ if (pProgress)
+ {
+ ComPtr<IInternalProgressControl> pProgressControl(pProgress);
+ AssertReturn(!!pProgressControl, VERR_INVALID_PARAMETER);
+ rc = pProgressControl->SetCurrentOperationProgress(uPercentage);
+ }
+ return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_GENERAL_FAILURE;
+}
+
+/**
+ * @note Temporarily locks this object for writing. bird: And/or reading?
+ */
+HRESULT Console::i_onlineMergeMedium(IMediumAttachment *aMediumAttachment,
+ ULONG aSourceIdx, ULONG aTargetIdx,
+ IProgress *aProgress)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ HRESULT rc = S_OK;
+ int vrc = VINF_SUCCESS;
+
+ /* Get the VM - must be done before the read-locking. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ /* We will need to release the lock before doing the actual merge */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* paranoia - we don't want merges to happen while teleporting etc. */
+ switch (mMachineState)
+ {
+ case MachineState_DeletingSnapshotOnline:
+ case MachineState_DeletingSnapshotPaused:
+ break;
+
+ default:
+ return i_setInvalidMachineStateError();
+ }
+
+ /** @todo AssertComRC -> AssertComRCReturn! Could potentially end up
+ * using uninitialized variables here. */
+ BOOL fBuiltinIOCache;
+ rc = mMachine->COMGETTER(IOCacheEnabled)(&fBuiltinIOCache);
+ AssertComRC(rc);
+ SafeIfaceArray<IStorageController> ctrls;
+ rc = mMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls));
+ AssertComRC(rc);
+ LONG lDev;
+ rc = aMediumAttachment->COMGETTER(Device)(&lDev);
+ AssertComRC(rc);
+ LONG lPort;
+ rc = aMediumAttachment->COMGETTER(Port)(&lPort);
+ AssertComRC(rc);
+ IMedium *pMedium;
+ rc = aMediumAttachment->COMGETTER(Medium)(&pMedium);
+ AssertComRC(rc);
+ Bstr mediumLocation;
+ if (pMedium)
+ {
+ rc = pMedium->COMGETTER(Location)(mediumLocation.asOutParam());
+ AssertComRC(rc);
+ }
+
+ Bstr attCtrlName;
+ rc = aMediumAttachment->COMGETTER(Controller)(attCtrlName.asOutParam());
+ AssertComRC(rc);
+ ComPtr<IStorageController> pStorageController;
+ for (size_t i = 0; i < ctrls.size(); ++i)
+ {
+ Bstr ctrlName;
+ rc = ctrls[i]->COMGETTER(Name)(ctrlName.asOutParam());
+ AssertComRC(rc);
+ if (attCtrlName == ctrlName)
+ {
+ pStorageController = ctrls[i];
+ break;
+ }
+ }
+ if (pStorageController.isNull())
+ return setError(E_FAIL,
+ tr("Could not find storage controller '%ls'"),
+ attCtrlName.raw());
+
+ StorageControllerType_T enmCtrlType;
+ rc = pStorageController->COMGETTER(ControllerType)(&enmCtrlType);
+ AssertComRC(rc);
+ const char *pcszDevice = i_storageControllerTypeToStr(enmCtrlType);
+
+ StorageBus_T enmBus;
+ rc = pStorageController->COMGETTER(Bus)(&enmBus);
+ AssertComRC(rc);
+ ULONG uInstance;
+ rc = pStorageController->COMGETTER(Instance)(&uInstance);
+ AssertComRC(rc);
+ BOOL fUseHostIOCache;
+ rc = pStorageController->COMGETTER(UseHostIOCache)(&fUseHostIOCache);
+ AssertComRC(rc);
+
+ unsigned uLUN;
+ rc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN);
+ AssertComRCReturnRC(rc);
+
+ Assert(mMachineState == MachineState_DeletingSnapshotOnline);
+
+ /* Pause the VM, as it might have pending IO on this drive */
+ bool fResume = false;
+ rc = i_suspendBeforeConfigChange(ptrVM.rawUVM(), ptrVM.vtable(), &alock, &fResume);
+ if (FAILED(rc))
+ return rc;
+
+ bool fInsertDiskIntegrityDrv = false;
+ Bstr strDiskIntegrityFlag;
+ rc = mMachine->GetExtraData(Bstr("VBoxInternal2/EnableDiskIntegrityDriver").raw(),
+ strDiskIntegrityFlag.asOutParam());
+ if ( rc == S_OK
+ && strDiskIntegrityFlag == "1")
+ fInsertDiskIntegrityDrv = true;
+
+ alock.release();
+ vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY,
+ (PFNRT)i_reconfigureMediumAttachment, 15,
+ this, ptrVM.rawUVM(), ptrVM.vtable(), pcszDevice, uInstance, enmBus,
+ fUseHostIOCache, fBuiltinIOCache, fInsertDiskIntegrityDrv, true /* fSetupMerge */,
+ aSourceIdx, aTargetIdx, aMediumAttachment, mMachineState, &rc);
+ /* error handling is after resuming the VM */
+
+ if (fResume)
+ i_resumeAfterConfigChange(ptrVM.rawUVM(), ptrVM.vtable());
+
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, "%Rrc", vrc);
+ if (FAILED(rc))
+ return rc;
+
+ PPDMIBASE pIBase = NULL;
+ PPDMIMEDIA pIMedium = NULL;
+ vrc = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(), pcszDevice, uInstance, uLUN, "VD", &pIBase);
+ if (RT_SUCCESS(vrc))
+ {
+ if (pIBase)
+ {
+ pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID);
+ if (!pIMedium)
+ return setError(E_FAIL, tr("could not query medium interface of controller"));
+ }
+ else
+ return setError(E_FAIL, tr("could not query base interface of controller"));
+ }
+
+ /* Finally trigger the merge. */
+ vrc = pIMedium->pfnMerge(pIMedium, onlineMergeMediumProgress, aProgress);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to perform an online medium merge (%Rrc)"), vrc);
+
+ alock.acquire();
+ /* Pause the VM, as it might have pending IO on this drive */
+ rc = i_suspendBeforeConfigChange(ptrVM.rawUVM(), ptrVM.vtable(), &alock, &fResume);
+ if (FAILED(rc))
+ return rc;
+ alock.release();
+
+ /* Update medium chain and state now, so that the VM can continue. */
+ rc = mControl->FinishOnlineMergeMedium();
+
+ vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY,
+ (PFNRT)i_reconfigureMediumAttachment, 15,
+ this, ptrVM.rawUVM(), ptrVM.vtable(), pcszDevice, uInstance, enmBus,
+ fUseHostIOCache, fBuiltinIOCache, fInsertDiskIntegrityDrv, false /* fSetupMerge */,
+ 0 /* uMergeSource */, 0 /* uMergeTarget */, aMediumAttachment, mMachineState, &rc);
+ /* error handling is after resuming the VM */
+
+ if (fResume)
+ i_resumeAfterConfigChange(ptrVM.rawUVM(), ptrVM.vtable());
+
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, "%Rrc", vrc);
+ if (FAILED(rc))
+ return rc;
+
+ return rc;
+}
+
+HRESULT Console::i_reconfigureMediumAttachments(const std::vector<ComPtr<IMediumAttachment> > &aAttachments)
+{
+ HRESULT rc = S_OK;
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (size_t i = 0; i < aAttachments.size(); ++i)
+ {
+ ComPtr<IStorageController> pStorageController;
+ Bstr controllerName;
+ ULONG lInstance;
+ StorageControllerType_T enmController;
+ StorageBus_T enmBus;
+ BOOL fUseHostIOCache;
+
+ /*
+ * We could pass the objects, but then EMT would have to do lots of
+ * IPC (to VBoxSVC) which takes a significant amount of time.
+ * Better query needed values here and pass them.
+ */
+ rc = aAttachments[i]->COMGETTER(Controller)(controllerName.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+
+ rc = mMachine->GetStorageControllerByName(controllerName.raw(), pStorageController.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+
+ rc = pStorageController->COMGETTER(ControllerType)(&enmController);
+ if (FAILED(rc))
+ throw rc;
+ rc = pStorageController->COMGETTER(Instance)(&lInstance);
+ if (FAILED(rc))
+ throw rc;
+ rc = pStorageController->COMGETTER(Bus)(&enmBus);
+ if (FAILED(rc))
+ throw rc;
+ rc = pStorageController->COMGETTER(UseHostIOCache)(&fUseHostIOCache);
+ if (FAILED(rc))
+ throw rc;
+
+ const char *pcszDevice = i_storageControllerTypeToStr(enmController);
+
+ BOOL fBuiltinIOCache;
+ rc = mMachine->COMGETTER(IOCacheEnabled)(&fBuiltinIOCache);
+ if (FAILED(rc))
+ throw rc;
+
+ bool fInsertDiskIntegrityDrv = false;
+ Bstr strDiskIntegrityFlag;
+ rc = mMachine->GetExtraData(Bstr("VBoxInternal2/EnableDiskIntegrityDriver").raw(),
+ strDiskIntegrityFlag.asOutParam());
+ if ( rc == S_OK
+ && strDiskIntegrityFlag == "1")
+ fInsertDiskIntegrityDrv = true;
+
+ alock.release();
+
+ IMediumAttachment *pAttachment = aAttachments[i];
+ int vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY,
+ (PFNRT)i_reconfigureMediumAttachment, 15,
+ this, ptrVM.rawUVM(), ptrVM.vtable(), pcszDevice, lInstance, enmBus,
+ fUseHostIOCache, fBuiltinIOCache, fInsertDiskIntegrityDrv,
+ false /* fSetupMerge */, 0 /* uMergeSource */, 0 /* uMergeTarget */,
+ pAttachment, mMachineState, &rc);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(E_FAIL, vrc, "%Rrc", vrc);
+ if (FAILED(rc))
+ throw rc;
+
+ alock.acquire();
+ }
+
+ return rc;
+}
+
+HRESULT Console::i_onVMProcessPriorityChange(VMProcPriority_T priority)
+{
+ HRESULT rc = S_OK;
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return autoCaller.rc();
+
+ RTPROCPRIORITY enmProcPriority = RTPROCPRIORITY_DEFAULT;
+ switch (priority)
+ {
+ case VMProcPriority_Default:
+ enmProcPriority = RTPROCPRIORITY_DEFAULT;
+ break;
+ case VMProcPriority_Flat:
+ enmProcPriority = RTPROCPRIORITY_FLAT;
+ break;
+ case VMProcPriority_Low:
+ enmProcPriority = RTPROCPRIORITY_LOW;
+ break;
+ case VMProcPriority_Normal:
+ enmProcPriority = RTPROCPRIORITY_NORMAL;
+ break;
+ case VMProcPriority_High:
+ enmProcPriority = RTPROCPRIORITY_HIGH;
+ break;
+ default:
+ return setError(E_INVALIDARG, tr("Unsupported priority type (%d)"), priority);
+ }
+ int vrc = RTProcSetPriority(enmProcPriority);
+ if (RT_FAILURE(vrc))
+ rc = setErrorBoth(VBOX_E_VM_ERROR, vrc,
+ tr("Could not set the priority of the process (%Rrc). Try to set it when VM is not started."), vrc);
+
+ return rc;
+}
+
+/**
+ * Load an HGCM service.
+ *
+ * Main purpose of this method is to allow extension packs to load HGCM
+ * service modules, which they can't, because the HGCM functionality lives
+ * in module VBoxC (and ConsoleImpl.cpp is part of it and thus can call it).
+ * Extension modules must not link directly against VBoxC, (XP)COM is
+ * handling this.
+ */
+int Console::i_hgcmLoadService(const char *pszServiceLibrary, const char *pszServiceName)
+{
+ /* Everyone seems to delegate all HGCM calls to VMMDev, so stick to this
+ * convention. Adds one level of indirection for no obvious reason. */
+ AssertPtrReturn(m_pVMMDev, VERR_INVALID_STATE);
+ return m_pVMMDev->hgcmLoadService(pszServiceLibrary, pszServiceName);
+}
+
+/**
+ * Merely passes the call to Guest::enableVMMStatistics().
+ */
+void Console::i_enableVMMStatistics(BOOL aEnable)
+{
+ if (mGuest)
+ mGuest->i_enableVMMStatistics(aEnable);
+}
+
+/**
+ * Worker for Console::Pause and internal entry point for pausing a VM for
+ * a specific reason.
+ */
+HRESULT Console::i_pause(Reason_T aReason)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ switch (mMachineState)
+ {
+ case MachineState_Running:
+ case MachineState_Teleporting:
+ case MachineState_LiveSnapshotting:
+ break;
+
+ case MachineState_Paused:
+ case MachineState_TeleportingPausedVM:
+ case MachineState_OnlineSnapshotting:
+ /* Remove any keys which are supposed to be removed on a suspend. */
+ if ( aReason == Reason_HostSuspend
+ || aReason == Reason_HostBatteryLow)
+ {
+ i_removeSecretKeysOnSuspend();
+ return S_OK;
+ }
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Already paused"));
+
+ default:
+ return i_setInvalidMachineStateError();
+ }
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ LogFlowThisFunc(("Sending PAUSE request...\n"));
+ if (aReason != Reason_Unspecified)
+ LogRel(("Pausing VM execution, reason '%s'\n", ::stringifyReason(aReason)));
+
+ /** @todo r=klaus make use of aReason */
+ VMSUSPENDREASON enmReason = VMSUSPENDREASON_USER;
+ if (aReason == Reason_HostSuspend)
+ enmReason = VMSUSPENDREASON_HOST_SUSPEND;
+ else if (aReason == Reason_HostBatteryLow)
+ enmReason = VMSUSPENDREASON_HOST_BATTERY_LOW;
+
+ int vrc = ptrVM.vtable()->pfnVMR3Suspend(ptrVM.rawUVM(), enmReason);
+
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not suspend the machine execution (%Rrc)"), vrc);
+ else if ( aReason == Reason_HostSuspend
+ || aReason == Reason_HostBatteryLow)
+ {
+ alock.acquire();
+ i_removeSecretKeysOnSuspend();
+ }
+ }
+
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * Worker for Console::Resume and internal entry point for resuming a VM for
+ * a specific reason.
+ */
+HRESULT Console::i_resume(Reason_T aReason, AutoWriteLock &alock)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ LogFlowThisFunc(("Sending RESUME request...\n"));
+ if (aReason != Reason_Unspecified)
+ LogRel(("Resuming VM execution, reason '%s'\n", ::stringifyReason(aReason)));
+
+ int vrc;
+ VMSTATE const enmVMState = mpVMM->pfnVMR3GetStateU(ptrVM.rawUVM());
+ if (enmVMState == VMSTATE_CREATED)
+ {
+#ifdef VBOX_WITH_EXTPACK
+ vrc = mptrExtPackManager->i_callAllVmPowerOnHooks(this, ptrVM.vtable()->pfnVMR3GetVM(ptrVM.rawUVM()), ptrVM.vtable());
+#else
+ vrc = VINF_SUCCESS;
+#endif
+ if (RT_SUCCESS(vrc))
+ vrc = ptrVM.vtable()->pfnVMR3PowerOn(ptrVM.rawUVM()); /* (PowerUpPaused) */
+ }
+ else
+ {
+ VMRESUMEREASON enmReason;
+ if (aReason == Reason_HostResume)
+ {
+ /*
+ * Host resume may be called multiple times successively. We don't want to VMR3Resume->vmR3Resume->vmR3TrySetState()
+ * to assert on us, hence check for the VM state here and bail if it's not in the 'suspended' state.
+ * See @bugref{3495}.
+ *
+ * Also, don't resume the VM through a host-resume unless it was suspended due to a host-suspend.
+ */
+ if (enmVMState != VMSTATE_SUSPENDED)
+ {
+ LogRel(("Ignoring VM resume request, VM is currently not suspended (%d)\n", enmVMState));
+ return S_OK;
+ }
+ VMSUSPENDREASON const enmSuspendReason = ptrVM.vtable()->pfnVMR3GetSuspendReason(ptrVM.rawUVM());
+ if (enmSuspendReason != VMSUSPENDREASON_HOST_SUSPEND)
+ {
+ LogRel(("Ignoring VM resume request, VM was not suspended due to host-suspend (%d)\n", enmSuspendReason));
+ return S_OK;
+ }
+
+ enmReason = VMRESUMEREASON_HOST_RESUME;
+ }
+ else
+ {
+ /*
+ * Any other reason to resume the VM throws an error when the VM was suspended due to a host suspend.
+ * See @bugref{7836}.
+ */
+ if ( enmVMState == VMSTATE_SUSPENDED
+ && ptrVM.vtable()->pfnVMR3GetSuspendReason(ptrVM.rawUVM()) == VMSUSPENDREASON_HOST_SUSPEND)
+ return setError(VBOX_E_INVALID_VM_STATE, tr("VM is paused due to host power management"));
+
+ enmReason = aReason == Reason_Snapshot ? VMRESUMEREASON_STATE_SAVED : VMRESUMEREASON_USER;
+ }
+
+ // for snapshots: no state change callback, VBoxSVC does everything
+ if (aReason == Reason_Snapshot)
+ mVMStateChangeCallbackDisabled = true;
+
+ vrc = ptrVM.vtable()->pfnVMR3Resume(ptrVM.rawUVM(), enmReason);
+
+ if (aReason == Reason_Snapshot)
+ mVMStateChangeCallbackDisabled = false;
+ }
+
+ HRESULT hrc = RT_SUCCESS(vrc) ? S_OK
+ : setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not resume the machine execution (%Rrc)"), vrc);
+
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * Internal entry point for saving state of a VM for a specific reason. This
+ * method is completely synchronous.
+ *
+ * The machine state is already set appropriately. It is only changed when
+ * saving state actually paused the VM (happens with live snapshots and
+ * teleportation), and in this case reflects the now paused variant.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_saveState(Reason_T aReason, const ComPtr<IProgress> &aProgress, const ComPtr<ISnapshot> &aSnapshot,
+ const Utf8Str &aStateFilePath, bool aPauseVM, bool &aLeftPaused)
+{
+ LogFlowThisFuncEnter();
+ aLeftPaused = false;
+
+ AssertReturn(!aProgress.isNull(), E_INVALIDARG);
+ AssertReturn(!aStateFilePath.isEmpty(), E_INVALIDARG);
+ Assert(aSnapshot.isNull() || aReason == Reason_Snapshot);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
+ if ( mMachineState != MachineState_Saving
+ && mMachineState != MachineState_LiveSnapshotting
+ && mMachineState != MachineState_OnlineSnapshotting
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_TeleportingPausedVM)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot save the execution state as the machine is not running or paused (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));
+ bool fContinueAfterwards = mMachineState != MachineState_Saving;
+
+ Bstr strDisableSaveState;
+ mMachine->GetExtraData(Bstr("VBoxInternal2/DisableSaveState").raw(), strDisableSaveState.asOutParam());
+ if (strDisableSaveState == "1")
+ return setError(VBOX_E_VM_ERROR,
+ tr("Saving the execution state is disabled for this VM"));
+
+ if (aReason != Reason_Unspecified)
+ LogRel(("Saving state of VM, reason '%s'\n", ::stringifyReason(aReason)));
+
+ /* ensure the directory for the saved state file exists */
+ {
+ Utf8Str dir = aStateFilePath;
+ dir.stripFilename();
+ if (!RTDirExists(dir.c_str()))
+ {
+ int vrc = RTDirCreateFullPath(dir.c_str(), 0700);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Could not create a directory '%s' to save the state to (%Rrc)"),
+ dir.c_str(), vrc);
+ }
+ }
+
+ /* Get the VM handle early, we need it in several places. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ bool fPaused = false;
+ if (aPauseVM)
+ {
+ /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+ VMSUSPENDREASON enmReason = VMSUSPENDREASON_USER;
+ if (aReason == Reason_HostSuspend)
+ enmReason = VMSUSPENDREASON_HOST_SUSPEND;
+ else if (aReason == Reason_HostBatteryLow)
+ enmReason = VMSUSPENDREASON_HOST_BATTERY_LOW;
+ int vrc = ptrVM.vtable()->pfnVMR3Suspend(ptrVM.rawUVM(), enmReason);
+ alock.acquire();
+
+ if (RT_SUCCESS(vrc))
+ fPaused = true;
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not suspend the machine execution (%Rrc)"), vrc);
+ }
+
+ Bstr bstrStateKeyId;
+ Bstr bstrStateKeyStore;
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mMachine->COMGETTER(StateKeyId)(bstrStateKeyId.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mMachine->COMGETTER(StateKeyStore)(bstrStateKeyStore.asOutParam());
+ if (FAILED(hrc))
+ hrc = setError(hrc, tr("Could not get key store for state file(%Rhrc (0x%08X))"), hrc, hrc);
+ }
+ else
+ hrc = setError(hrc, tr("Could not get key id for state file(%Rhrc (0x%08X))"), hrc, hrc);
+ }
+#endif
+
+ if (SUCCEEDED(hrc))
+ {
+ LogFlowFunc(("Saving the state to '%s'...\n", aStateFilePath.c_str()));
+
+ mpVmm2UserMethods->pISnapshot = aSnapshot;
+ mptrCancelableProgress = aProgress;
+
+ SsmStream ssmStream(this, ptrVM.vtable(), m_pKeyStore, bstrStateKeyId, bstrStateKeyStore);
+ int vrc = ssmStream.create(aStateFilePath.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ PCSSMSTRMOPS pStreamOps = NULL;
+ void *pvStreamOpsUser = NULL;
+ vrc = ssmStream.querySsmStrmOps(&pStreamOps, &pvStreamOpsUser);
+ if (RT_SUCCESS(vrc))
+ {
+ alock.release();
+
+ vrc = ptrVM.vtable()->pfnVMR3Save(ptrVM.rawUVM(),
+ NULL /*pszFilename*/,
+ pStreamOps,
+ pvStreamOpsUser,
+ fContinueAfterwards,
+ Console::i_stateProgressCallback,
+ static_cast<IProgress *>(aProgress),
+ &aLeftPaused);
+
+ alock.acquire();
+ }
+
+ ssmStream.close();
+ if (RT_FAILURE(vrc))
+ {
+ int vrc2 = RTFileDelete(aStateFilePath.c_str());
+ AssertRC(vrc2);
+ }
+ }
+
+ mpVmm2UserMethods->pISnapshot = NULL;
+ mptrCancelableProgress.setNull();
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(fContinueAfterwards || !aLeftPaused);
+
+ if (!fContinueAfterwards)
+ {
+ /*
+ * The machine has been successfully saved, so power it down
+ * (vmstateChangeCallback() will set state to Saved on success).
+ * Note: we release the VM caller, otherwise it will deadlock.
+ */
+ ptrVM.release();
+ alock.release();
+ autoCaller.release();
+
+ HRESULT rc = i_powerDown();
+ AssertComRC(rc);
+
+ autoCaller.add();
+ alock.acquire();
+ }
+ else if (fPaused)
+ aLeftPaused = true;
+ }
+ else
+ {
+ if (fPaused)
+ {
+ alock.release();
+ ptrVM.vtable()->pfnVMR3Resume(ptrVM.rawUVM(), VMRESUMEREASON_STATE_RESTORED);
+ alock.acquire();
+ }
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to save the machine state to '%s' (%Rrc)"),
+ aStateFilePath.c_str(), vrc);
+ }
+ }
+ }
+
+ LogFlowFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Internal entry point for cancelling a VM save state.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_cancelSaveState()
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ ptrVM.vtable()->pfnSSMR3Cancel(ptrVM.rawUVM());
+
+ LogFlowFuncLeave();
+ return hrc;
+}
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+/**
+ * Sends audio (frame) data to the recording routines.
+ *
+ * @returns HRESULT
+ * @param pvData Audio data to send.
+ * @param cbData Size (in bytes) of audio data to send.
+ * @param uTimestampMs Timestamp (in ms) of audio data.
+ */
+HRESULT Console::i_recordingSendAudio(const void *pvData, size_t cbData, uint64_t uTimestampMs)
+{
+ if ( mRecording.mCtx.IsStarted()
+ && mRecording.mCtx.IsFeatureEnabled(RecordingFeature_Audio))
+ return mRecording.mCtx.SendAudioFrame(pvData, cbData, uTimestampMs);
+
+ return S_OK;
+}
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+
+#ifdef VBOX_WITH_RECORDING
+
+int Console::i_recordingGetSettings(settings::RecordingSettings &recording)
+{
+ Assert(mMachine.isNotNull());
+
+ recording.applyDefaults();
+
+ ComPtr<IRecordingSettings> pRecordSettings;
+ HRESULT hrc = mMachine->COMGETTER(RecordingSettings)(pRecordSettings.asOutParam());
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+
+ BOOL fTemp;
+ hrc = pRecordSettings->COMGETTER(Enabled)(&fTemp);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ recording.common.fEnabled = RT_BOOL(fTemp);
+
+ SafeIfaceArray<IRecordingScreenSettings> paRecScreens;
+ hrc = pRecordSettings->COMGETTER(Screens)(ComSafeArrayAsOutParam(paRecScreens));
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+
+ for (unsigned long i = 0; i < (unsigned long)paRecScreens.size(); ++i)
+ {
+ settings::RecordingScreenSettings recScreenSettings;
+ ComPtr<IRecordingScreenSettings> pRecScreenSettings = paRecScreens[i];
+
+ hrc = pRecScreenSettings->COMGETTER(Enabled)(&fTemp);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ recScreenSettings.fEnabled = RT_BOOL(fTemp);
+ com::SafeArray<RecordingFeature_T> vecFeatures;
+ hrc = pRecScreenSettings->COMGETTER(Features)(ComSafeArrayAsOutParam(vecFeatures));
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ /* Make sure to clear map first, as we want to (re-)set enabled features. */
+ recScreenSettings.featureMap.clear();
+ for (size_t f = 0; f < vecFeatures.size(); ++f)
+ {
+ if (vecFeatures[f] == RecordingFeature_Audio)
+ recScreenSettings.featureMap[RecordingFeature_Audio] = true;
+ else if (vecFeatures[f] == RecordingFeature_Video)
+ recScreenSettings.featureMap[RecordingFeature_Video] = true;
+ }
+ hrc = pRecScreenSettings->COMGETTER(MaxTime)((ULONG *)&recScreenSettings.ulMaxTimeS);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(MaxFileSize)((ULONG *)&recScreenSettings.File.ulMaxSizeMB);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ Bstr bstrTemp;
+ hrc = pRecScreenSettings->COMGETTER(Filename)(bstrTemp.asOutParam());
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ recScreenSettings.File.strName = bstrTemp;
+ hrc = pRecScreenSettings->COMGETTER(Options)(bstrTemp.asOutParam());
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ recScreenSettings.strOptions = bstrTemp;
+ hrc = pRecScreenSettings->COMGETTER(AudioCodec)(&recScreenSettings.Audio.enmCodec);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(AudioDeadline)(&recScreenSettings.Audio.enmDeadline);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(AudioRateControlMode)(&recScreenSettings.Audio.enmRateCtlMode);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(AudioHz)((ULONG *)&recScreenSettings.Audio.uHz);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(AudioBits)((ULONG *)&recScreenSettings.Audio.cBits);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(AudioChannels)((ULONG *)&recScreenSettings.Audio.cChannels);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoCodec)(&recScreenSettings.Video.enmCodec);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoWidth)((ULONG *)&recScreenSettings.Video.ulWidth);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoHeight)((ULONG *)&recScreenSettings.Video.ulHeight);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoDeadline)(&recScreenSettings.Video.enmDeadline);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoRateControlMode)(&recScreenSettings.Video.enmRateCtlMode);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoScalingMode)(&recScreenSettings.Video.enmScalingMode);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoRate)((ULONG *)&recScreenSettings.Video.ulRate);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoFPS)((ULONG *)&recScreenSettings.Video.ulFPS);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+
+ recording.mapScreens[i] = recScreenSettings;
+ }
+
+ Assert(recording.mapScreens.size() == paRecScreens.size());
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Creates the recording context.
+ *
+ * @returns VBox status code.
+ */
+int Console::i_recordingCreate(void)
+{
+ settings::RecordingSettings recordingSettings;
+ int vrc = i_recordingGetSettings(recordingSettings);
+ if (RT_SUCCESS(vrc))
+ vrc = mRecording.mCtx.Create(this, recordingSettings);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Destroys the recording context.
+ */
+void Console::i_recordingDestroy(void)
+{
+ mRecording.mCtx.Destroy();
+}
+
+/**
+ * Starts recording. Does nothing if recording is already active.
+ *
+ * @returns VBox status code.
+ */
+int Console::i_recordingStart(util::AutoWriteLock *pAutoLock /* = NULL */)
+{
+ RT_NOREF(pAutoLock);
+
+ if (mRecording.mCtx.IsStarted())
+ return VINF_SUCCESS;
+
+ LogRel(("Recording: Starting ...\n"));
+
+ int vrc = mRecording.mCtx.Start();
+ if (RT_SUCCESS(vrc))
+ {
+ for (unsigned uScreen = 0; uScreen < mRecording.mCtx.GetStreamCount(); uScreen++)
+ mDisplay->i_recordingScreenChanged(uScreen);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Stops recording. Does nothing if recording is not active.
+ */
+int Console::i_recordingStop(util::AutoWriteLock *pAutoLock /* = NULL */)
+{
+ if (!mRecording.mCtx.IsStarted())
+ return VINF_SUCCESS;
+
+ LogRel(("Recording: Stopping ...\n"));
+
+ int vrc = mRecording.mCtx.Stop();
+ if (RT_SUCCESS(vrc))
+ {
+ const size_t cStreams = mRecording.mCtx.GetStreamCount();
+ for (unsigned uScreen = 0; uScreen < cStreams; ++uScreen)
+ mDisplay->i_recordingScreenChanged(uScreen);
+
+ if (pAutoLock)
+ pAutoLock->release();
+
+ ComPtr<IRecordingSettings> pRecordSettings;
+ HRESULT hrc = mMachine->COMGETTER(RecordingSettings)(pRecordSettings.asOutParam());
+ ComAssertComRC(hrc);
+ hrc = pRecordSettings->COMSETTER(Enabled)(FALSE);
+ ComAssertComRC(hrc);
+
+ if (pAutoLock)
+ pAutoLock->acquire();
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+#endif /* VBOX_WITH_RECORDING */
+
+/**
+ * Gets called by Session::UpdateMachineState()
+ * (IInternalSessionControl::updateMachineState()).
+ *
+ * Must be called only in certain cases (see the implementation).
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_updateMachineState(MachineState_T aMachineState)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn( mMachineState == MachineState_Saving
+ || mMachineState == MachineState_OnlineSnapshotting
+ || mMachineState == MachineState_LiveSnapshotting
+ || mMachineState == MachineState_DeletingSnapshotOnline
+ || mMachineState == MachineState_DeletingSnapshotPaused
+ || aMachineState == MachineState_Saving
+ || aMachineState == MachineState_OnlineSnapshotting
+ || aMachineState == MachineState_LiveSnapshotting
+ || aMachineState == MachineState_DeletingSnapshotOnline
+ || aMachineState == MachineState_DeletingSnapshotPaused
+ , E_FAIL);
+
+ return i_setMachineStateLocally(aMachineState);
+}
+
+/**
+ * Gets called by Session::COMGETTER(NominalState)()
+ * (IInternalSessionControl::getNominalState()).
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT Console::i_getNominalState(MachineState_T &aNominalState)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /* Get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ MachineState_T enmMachineState = MachineState_Null;
+ VMSTATE enmVMState = ptrVM.vtable()->pfnVMR3GetStateU(ptrVM.rawUVM());
+ switch (enmVMState)
+ {
+ case VMSTATE_CREATING:
+ case VMSTATE_CREATED:
+ case VMSTATE_POWERING_ON:
+ enmMachineState = MachineState_Starting;
+ break;
+ case VMSTATE_LOADING:
+ enmMachineState = MachineState_Restoring;
+ break;
+ case VMSTATE_RESUMING:
+ case VMSTATE_SUSPENDING:
+ case VMSTATE_SUSPENDING_LS:
+ case VMSTATE_SUSPENDING_EXT_LS:
+ case VMSTATE_SUSPENDED:
+ case VMSTATE_SUSPENDED_LS:
+ case VMSTATE_SUSPENDED_EXT_LS:
+ enmMachineState = MachineState_Paused;
+ break;
+ case VMSTATE_RUNNING:
+ case VMSTATE_RUNNING_LS:
+ case VMSTATE_RESETTING:
+ case VMSTATE_RESETTING_LS:
+ case VMSTATE_SOFT_RESETTING:
+ case VMSTATE_SOFT_RESETTING_LS:
+ case VMSTATE_DEBUGGING:
+ case VMSTATE_DEBUGGING_LS:
+ enmMachineState = MachineState_Running;
+ break;
+ case VMSTATE_SAVING:
+ enmMachineState = MachineState_Saving;
+ break;
+ case VMSTATE_POWERING_OFF:
+ case VMSTATE_POWERING_OFF_LS:
+ case VMSTATE_DESTROYING:
+ enmMachineState = MachineState_Stopping;
+ break;
+ case VMSTATE_OFF:
+ case VMSTATE_OFF_LS:
+ case VMSTATE_FATAL_ERROR:
+ case VMSTATE_FATAL_ERROR_LS:
+ case VMSTATE_LOAD_FAILURE:
+ case VMSTATE_TERMINATED:
+ enmMachineState = MachineState_PoweredOff;
+ break;
+ case VMSTATE_GURU_MEDITATION:
+ case VMSTATE_GURU_MEDITATION_LS:
+ enmMachineState = MachineState_Stuck;
+ break;
+ default:
+ AssertMsgFailed(("%s\n", ptrVM.vtable()->pfnVMR3GetStateName(enmVMState)));
+ enmMachineState = MachineState_PoweredOff;
+ }
+ aNominalState = enmMachineState;
+
+ LogFlowFuncLeave();
+ return S_OK;
+}
+
+void Console::i_onMousePointerShapeChange(bool fVisible, bool fAlpha,
+ uint32_t xHot, uint32_t yHot,
+ uint32_t width, uint32_t height,
+ const uint8_t *pu8Shape,
+ uint32_t cbShape)
+{
+#if 0
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("fVisible=%d, fAlpha=%d, xHot = %d, yHot = %d, width=%d, height=%d, shape=%p\n",
+ fVisible, fAlpha, xHot, yHot, width, height, pShape));
+#endif
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ if (!mMouse.isNull())
+ mMouse->updateMousePointerShape(fVisible, fAlpha, xHot, yHot, width, height, pu8Shape, cbShape);
+
+ com::SafeArray<BYTE> shape(cbShape);
+ if (pu8Shape)
+ memcpy(shape.raw(), pu8Shape, cbShape);
+ ::FireMousePointerShapeChangedEvent(mEventSource, fVisible, fAlpha, xHot, yHot, width, height, ComSafeArrayAsInParam(shape));
+
+#if 0
+ LogFlowThisFuncLeave();
+#endif
+}
+
+void Console::i_onMouseCapabilityChange(BOOL supportsAbsolute, BOOL supportsRelative,
+ BOOL supportsTouchScreen, BOOL supportsTouchPad, BOOL needsHostCursor)
+{
+ LogFlowThisFunc(("supportsAbsolute=%d supportsRelative=%d supportsTouchScreen=%d supportsTouchPad=%d needsHostCursor=%d\n",
+ supportsAbsolute, supportsRelative, supportsTouchScreen, supportsTouchPad, needsHostCursor));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ ::FireMouseCapabilityChangedEvent(mEventSource, supportsAbsolute, supportsRelative, supportsTouchScreen, supportsTouchPad, needsHostCursor);
+}
+
+void Console::i_onStateChange(MachineState_T machineState)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+ ::FireStateChangedEvent(mEventSource, machineState);
+}
+
+void Console::i_onAdditionsStateChange()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ ::FireAdditionsStateChangedEvent(mEventSource);
+}
+
+/**
+ * @remarks This notification only is for reporting an incompatible
+ * Guest Additions interface, *not* the Guest Additions version!
+ *
+ * The user will be notified inside the guest if new Guest
+ * Additions are available (via VBoxTray/VBoxClient).
+ */
+void Console::i_onAdditionsOutdated()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /** @todo implement this */
+}
+
+void Console::i_onKeyboardLedsChange(bool fNumLock, bool fCapsLock, bool fScrollLock)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ ::FireKeyboardLedsChangedEvent(mEventSource, fNumLock, fCapsLock, fScrollLock);
+}
+
+void Console::i_onUSBDeviceStateChange(IUSBDevice *aDevice, bool aAttached,
+ IVirtualBoxErrorInfo *aError)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ ::FireUSBDeviceStateChangedEvent(mEventSource, aDevice, aAttached, aError);
+}
+
+void Console::i_onRuntimeError(BOOL aFatal, IN_BSTR aErrorID, IN_BSTR aMessage)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ ::FireRuntimeErrorEvent(mEventSource, aFatal, aErrorID, aMessage);
+}
+
+HRESULT Console::i_onShowWindow(BOOL aCheck, BOOL *aCanShow, LONG64 *aWinId)
+{
+ AssertReturn(aCanShow, E_POINTER);
+ AssertReturn(aWinId, E_POINTER);
+
+ *aCanShow = FALSE;
+ *aWinId = 0;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ ComPtr<IEvent> ptrEvent;
+ if (aCheck)
+ {
+ *aCanShow = TRUE;
+ HRESULT hrc = ::CreateCanShowWindowEvent(ptrEvent.asOutParam(), mEventSource);
+ if (SUCCEEDED(hrc))
+ {
+ VBoxEventDesc EvtDesc(ptrEvent, mEventSource);
+ BOOL fDelivered = EvtDesc.fire(5000); /* Wait up to 5 secs for delivery */
+ //Assert(fDelivered);
+ if (fDelivered)
+ {
+ // bit clumsy
+ ComPtr<ICanShowWindowEvent> ptrCanShowEvent = ptrEvent;
+ if (ptrCanShowEvent)
+ {
+ BOOL fVetoed = FALSE;
+ BOOL fApproved = FALSE;
+ ptrCanShowEvent->IsVetoed(&fVetoed);
+ ptrCanShowEvent->IsApproved(&fApproved);
+ *aCanShow = fApproved || !fVetoed;
+ }
+ else
+ AssertFailed();
+ }
+ }
+ }
+ else
+ {
+ HRESULT hrc = ::CreateShowWindowEvent(ptrEvent.asOutParam(), mEventSource, 0);
+ if (SUCCEEDED(hrc))
+ {
+ VBoxEventDesc EvtDesc(ptrEvent, mEventSource);
+ BOOL fDelivered = EvtDesc.fire(5000); /* Wait up to 5 secs for delivery */
+ //Assert(fDelivered);
+ if (fDelivered)
+ {
+ ComPtr<IShowWindowEvent> ptrShowEvent = ptrEvent;
+ if (ptrShowEvent)
+ {
+ LONG64 idWindow = 0;
+ ptrShowEvent->COMGETTER(WinId)(&idWindow);
+ if (idWindow != 0 && *aWinId == 0)
+ *aWinId = idWindow;
+ }
+ else
+ AssertFailed();
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+// private methods
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Loads the VMM if needed.
+ *
+ * @returns COM status.
+ * @remarks Caller must write lock the console object.
+ */
+HRESULT Console::i_loadVMM(void) RT_NOEXCEPT
+{
+ if ( mhModVMM == NIL_RTLDRMOD
+ || mpVMM == NULL)
+ {
+ Assert(!mpVMM);
+
+ HRESULT hrc;
+ RTERRINFOSTATIC ErrInfo;
+ RTLDRMOD hModVMM = NIL_RTLDRMOD;
+ int vrc = SUPR3HardenedLdrLoadAppPriv("VBoxVMM", &hModVMM, RTLDRLOAD_FLAGS_LOCAL, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ PFNVMMGETVTABLE pfnGetVTable = NULL;
+ vrc = RTLdrGetSymbol(hModVMM, VMMR3VTABLE_GETTER_NAME, (void **)&pfnGetVTable);
+ if (pfnGetVTable)
+ {
+ PCVMMR3VTABLE pVMM = pfnGetVTable();
+ if (pVMM)
+ {
+ if (VMMR3VTABLE_IS_COMPATIBLE(pVMM->uMagicVersion))
+ {
+ if (pVMM->uMagicVersion == pVMM->uMagicVersionEnd)
+ {
+ mhModVMM = hModVMM;
+ mpVMM = pVMM;
+ LogFunc(("mhLdrVMM=%p phVMM=%p uMagicVersion=%#RX64\n", hModVMM, pVMM, pVMM->uMagicVersion));
+ return S_OK;
+ }
+
+ hrc = setErrorVrc(vrc, "Bogus VMM vtable: uMagicVersion=%#RX64 uMagicVersionEnd=%#RX64",
+ pVMM->uMagicVersion, pVMM->uMagicVersionEnd);
+ }
+ else
+ hrc = setErrorVrc(vrc, "Incompatible of bogus VMM version magic: %#RX64", pVMM->uMagicVersion);
+ }
+ else
+ hrc = setErrorVrc(vrc, "pfnGetVTable return NULL!");
+ }
+ else
+ hrc = setErrorVrc(vrc, "Failed to locate symbol '%s' in VBoxVMM: %Rrc", VMMR3VTABLE_GETTER_NAME, vrc);
+ RTLdrClose(hModVMM);
+ }
+ else
+ hrc = setErrorVrc(vrc, "Failed to load VBoxVMM: %#RTeic", &ErrInfo.Core);
+ return hrc;
+ }
+
+ return S_OK;
+}
+
+/**
+ * Increases the usage counter of the mpUVM pointer.
+ *
+ * Guarantees that VMR3Destroy() will not be called on it at least until
+ * releaseVMCaller() is called.
+ *
+ * If this method returns a failure, the caller is not allowed to use mpUVM and
+ * may return the failed result code to the upper level. This method sets the
+ * extended error info on failure if \a aQuiet is false.
+ *
+ * Setting \a aQuiet to true is useful for methods that don't want to return
+ * the failed result code to the caller when this method fails (e.g. need to
+ * silently check for the mpUVM availability).
+ *
+ * When mpUVM is NULL but \a aAllowNullVM is true, a corresponding error will be
+ * returned instead of asserting. Having it false is intended as a sanity check
+ * for methods that have checked mMachineState and expect mpUVM *NOT* to be
+ * NULL.
+ *
+ * @param aQuiet true to suppress setting error info
+ * @param aAllowNullVM true to accept mpUVM being NULL and return a failure
+ * (otherwise this method will assert if mpUVM is NULL)
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_addVMCaller(bool aQuiet /* = false */,
+ bool aAllowNullVM /* = false */)
+{
+ RT_NOREF(aAllowNullVM);
+ AutoCaller autoCaller(this);
+ /** @todo Fix race during console/VM reference destruction, refer @bugref{6318}
+ * comment 25. */
+ if (FAILED(autoCaller.rc()))
+ return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mVMDestroying)
+ {
+ /* powerDown() is waiting for all callers to finish */
+ return aQuiet ? E_ACCESSDENIED : setError(E_ACCESSDENIED, tr("The virtual machine is being powered down"));
+ }
+
+ if (mpUVM == NULL)
+ {
+ Assert(aAllowNullVM == true);
+
+ /* The machine is not powered up */
+ return aQuiet ? E_ACCESSDENIED : setError(E_ACCESSDENIED, tr("The virtual machine is not powered up"));
+ }
+
+ ++mVMCallers;
+
+ return S_OK;
+}
+
+/**
+ * Decreases the usage counter of the mpUVM pointer.
+ *
+ * Must always complete the addVMCaller() call after the mpUVM pointer is no
+ * more necessary.
+ *
+ * @note Locks this object for writing.
+ */
+void Console::i_releaseVMCaller()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturnVoid(mpUVM != NULL);
+
+ Assert(mVMCallers > 0);
+ --mVMCallers;
+
+ if (mVMCallers == 0 && mVMDestroying)
+ {
+ /* inform powerDown() there are no more callers */
+ RTSemEventSignal(mVMZeroCallersSem);
+ }
+}
+
+
+/**
+ * Helper for SafeVMPtrBase.
+ */
+HRESULT Console::i_safeVMPtrRetainer(PUVM *a_ppUVM, PCVMMR3VTABLE *a_ppVMM, bool a_Quiet) RT_NOEXCEPT
+{
+ *a_ppUVM = NULL;
+ *a_ppVMM = NULL;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Repeat the checks done by addVMCaller.
+ */
+ if (mVMDestroying) /* powerDown() is waiting for all callers to finish */
+ return a_Quiet
+ ? E_ACCESSDENIED
+ : setError(E_ACCESSDENIED, tr("The virtual machine is being powered down"));
+ PUVM const pUVM = mpUVM;
+ if (!pUVM)
+ return a_Quiet
+ ? E_ACCESSDENIED
+ : setError(E_ACCESSDENIED, tr("The virtual machine is powered off"));
+ PCVMMR3VTABLE const pVMM = mpVMM;
+ if (!pVMM)
+ return a_Quiet
+ ? E_ACCESSDENIED
+ : setError(E_ACCESSDENIED, tr("No VMM loaded!"));
+
+ /*
+ * Retain a reference to the user mode VM handle and get the global handle.
+ */
+ uint32_t cRefs = pVMM->pfnVMR3RetainUVM(pUVM);
+ if (cRefs == UINT32_MAX)
+ return a_Quiet
+ ? E_ACCESSDENIED
+ : setError(E_ACCESSDENIED, tr("The virtual machine is powered off"));
+
+ /* done */
+ *a_ppUVM = pUVM;
+ *a_ppVMM = pVMM;
+ return S_OK;
+}
+
+void Console::i_safeVMPtrReleaser(PUVM *a_ppUVM)
+{
+ PUVM const pUVM = *a_ppUVM;
+ *a_ppUVM = NULL;
+ if (pUVM)
+ {
+ PCVMMR3VTABLE const pVMM = mpVMM;
+ if (pVMM)
+ pVMM->pfnVMR3ReleaseUVM(pUVM);
+ }
+}
+
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedOpen(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilename, uint32_t fFlags)
+{
+ RT_NOREF(pIf);
+ Console *pConsole = static_cast<Console *>(pvUser);
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+
+ int vrc = RTVfsFileOpenNormal(pszFilename, fFlags, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ vrc = pConsole->i_retainCryptoIf(&pCryptoIf);
+ if (RT_SUCCESS(vrc))
+ {
+ SecretKey *pKey = NULL;
+
+ vrc = pConsole->m_pKeyStore->retainSecretKey(pConsole->m_strLogKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ const char *pszPassword = (const char *)pKey->getKeyBuffer();
+
+ vrc = pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFile, pConsole->m_strLogKeyStore.c_str(), pszPassword,
+ &pConsole->m_hVfsFileLog);
+ pKey->release();
+ }
+
+ /* On success we keep the reference to keep the cryptographic module loaded. */
+ if (RT_FAILURE(vrc))
+ pConsole->i_releaseCryptoIf(pCryptoIf);
+ }
+
+ /* Always do this because the encrypted log has retained a reference to the underlying file. */
+ RTVfsFileRelease(hVfsFile);
+ if (RT_FAILURE(vrc))
+ RTFileDelete(pszFilename);
+ }
+
+ return vrc;
+}
+
+
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedClose(PCRTLOGOUTPUTIF pIf, void *pvUser)
+{
+ RT_NOREF(pIf);
+ Console *pConsole = static_cast<Console *>(pvUser);
+
+ RTVfsFileRelease(pConsole->m_hVfsFileLog);
+ pConsole->m_hVfsFileLog = NIL_RTVFSFILE;
+ return VINF_SUCCESS;
+}
+
+
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedDelete(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilename)
+{
+ RT_NOREF(pIf, pvUser);
+ return RTFileDelete(pszFilename);
+}
+
+
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedRename(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilenameOld,
+ const char *pszFilenameNew, uint32_t fFlags)
+{
+ RT_NOREF(pIf, pvUser);
+ return RTFileRename(pszFilenameOld, pszFilenameNew, fFlags);
+}
+
+
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedQuerySize(PCRTLOGOUTPUTIF pIf, void *pvUser, uint64_t *pcbSize)
+{
+ RT_NOREF(pIf);
+ Console *pConsole = static_cast<Console *>(pvUser);
+
+ return RTVfsFileQuerySize(pConsole->m_hVfsFileLog, pcbSize);
+}
+
+
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedWrite(PCRTLOGOUTPUTIF pIf, void *pvUser, const void *pvBuf,
+ size_t cbWrite, size_t *pcbWritten)
+{
+ RT_NOREF(pIf);
+ Console *pConsole = static_cast<Console *>(pvUser);
+
+ return RTVfsFileWrite(pConsole->m_hVfsFileLog, pvBuf, cbWrite, pcbWritten);
+}
+
+
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedFlush(PCRTLOGOUTPUTIF pIf, void *pvUser)
+{
+ RT_NOREF(pIf);
+ Console *pConsole = static_cast<Console *>(pvUser);
+
+ return RTVfsFileFlush(pConsole->m_hVfsFileLog);
+}
+#endif
+
+
+/**
+ * Initialize the release logging facility. In case something
+ * goes wrong, there will be no release logging. Maybe in the future
+ * we can add some logic to use different file names in this case.
+ * Note that the logic must be in sync with Machine::DeleteSettings().
+ */
+HRESULT Console::i_consoleInitReleaseLog(const ComPtr<IMachine> aMachine)
+{
+ Bstr bstrLogFolder;
+ HRESULT hrc = aMachine->COMGETTER(LogFolder)(bstrLogFolder.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ Utf8Str strLogDir = bstrLogFolder;
+
+ /* make sure the Logs folder exists */
+ Assert(strLogDir.length());
+ if (!RTDirExists(strLogDir.c_str()))
+ RTDirCreateFullPath(strLogDir.c_str(), 0700);
+
+ Utf8StrFmt logFile("%s%cVBox.log", strLogDir.c_str(), RTPATH_DELIMITER);
+ Utf8StrFmt pngFile("%s%cVBox.png", strLogDir.c_str(), RTPATH_DELIMITER);
+
+ /*
+ * Age the old log files.
+ * Rename .(n-1) to .(n), .(n-2) to .(n-1), ..., and the last log file to .1
+ * Overwrite target files in case they exist.
+ */
+ ComPtr<IVirtualBox> pVirtualBox;
+ aMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ ComPtr<ISystemProperties> pSystemProperties;
+ pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ ULONG cHistoryFiles = 3;
+ pSystemProperties->COMGETTER(LogHistoryCount)(&cHistoryFiles);
+ if (cHistoryFiles)
+ {
+ for (int i = cHistoryFiles - 1; i >= 0; i--)
+ {
+ Utf8Str *files[] = { &logFile, &pngFile };
+ Utf8Str oldName, newName;
+
+ for (unsigned int j = 0; j < RT_ELEMENTS(files); ++j)
+ {
+ if (i > 0)
+ oldName.printf("%s.%d", files[j]->c_str(), i);
+ else
+ oldName = *files[j];
+ newName.printf("%s.%d", files[j]->c_str(), i + 1);
+
+ /* If the old file doesn't exist, delete the new file (if it
+ * exists) to provide correct rotation even if the sequence is
+ * broken */
+ if (RTFileRename(oldName.c_str(), newName.c_str(), RTFILEMOVE_FLAGS_REPLACE) == VERR_FILE_NOT_FOUND)
+ RTFileDelete(newName.c_str());
+ }
+ }
+ }
+
+ Bstr bstrLogKeyId;
+ Bstr bstrLogKeyStore;
+ PCRTLOGOUTPUTIF pLogOutputIf = NULL;
+ void *pvLogOutputUser = NULL;
+ int vrc = VINF_SUCCESS;
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ hrc = aMachine->COMGETTER(LogKeyId)(bstrLogKeyId.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ hrc = aMachine->COMGETTER(LogKeyStore)(bstrLogKeyStore.asOutParam());
+ if ( SUCCEEDED(hrc)
+ && bstrLogKeyId.isNotEmpty()
+ && bstrLogKeyStore.isNotEmpty())
+ {
+ m_LogOutputIf.pfnOpen = Console::i_logEncryptedOpen;
+ m_LogOutputIf.pfnClose = Console::i_logEncryptedClose;
+ m_LogOutputIf.pfnDelete = Console::i_logEncryptedDelete;
+ m_LogOutputIf.pfnRename = Console::i_logEncryptedRename;
+ m_LogOutputIf.pfnQuerySize = Console::i_logEncryptedQuerySize;
+ m_LogOutputIf.pfnWrite = Console::i_logEncryptedWrite;
+ m_LogOutputIf.pfnFlush = Console::i_logEncryptedFlush;
+
+ m_strLogKeyId = Utf8Str(bstrLogKeyId);
+ m_strLogKeyStore = Utf8Str(bstrLogKeyStore);
+
+ pLogOutputIf = &m_LogOutputIf;
+ pvLogOutputUser = this;
+ m_fEncryptedLog = true;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to set encryption for release log (%Rrc)"), vrc);
+ else
+#endif
+ {
+ RTERRINFOSTATIC ErrInfo;
+ vrc = com::VBoxLogRelCreateEx("VM", logFile.c_str(),
+ RTLOGFLAGS_PREFIX_TIME_PROG | RTLOGFLAGS_RESTRICT_GROUPS,
+ "all all.restrict -default.restrict",
+ "VBOX_RELEASE_LOG", RTLOGDEST_FILE,
+ 32768 /* cMaxEntriesPerGroup */,
+ 0 /* cHistory */, 0 /* uHistoryFileTime */,
+ 0 /* uHistoryFileSize */,
+ pLogOutputIf, pvLogOutputUser,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to open release log (%s, %Rrc)"), ErrInfo.Core.pszMsg, vrc);
+ }
+
+ /* If we've made any directory changes, flush the directory to increase
+ the likelihood that the log file will be usable after a system panic.
+
+ Tip: Try 'export VBOX_RELEASE_LOG_FLAGS=flush' if the last bits of the log
+ is missing. Just don't have too high hopes for this to help. */
+ if (SUCCEEDED(hrc) || cHistoryFiles)
+ RTDirFlush(strLogDir.c_str());
+
+ return hrc;
+}
+
+/**
+ * Common worker for PowerUp and PowerUpPaused.
+ *
+ * @returns COM status code.
+ *
+ * @param aProgress Where to return the progress object.
+ * @param aPaused true if PowerUpPaused called.
+ */
+HRESULT Console::i_powerUp(IProgress **aProgress, bool aPaused)
+{
+ LogFlowThisFuncEnter();
+
+ CheckComArgOutPointerValid(aProgress);
+
+ AutoCaller autoCaller(this);
+ HRESULT rc = autoCaller.rc();
+ if (FAILED(rc)) return rc;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
+
+ if (Global::IsOnlineOrTransient(mMachineState))
+ return setError(VBOX_E_INVALID_VM_STATE, tr("The virtual machine is already running or busy (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));
+
+
+ /* Set up release logging as early as possible after the check if
+ * there is already a running VM which we shouldn't disturb. */
+ rc = i_consoleInitReleaseLog(mMachine);
+ if (FAILED(rc))
+ return rc;
+
+#ifdef VBOX_OPENSSL_FIPS
+ LogRel(("crypto: FIPS mode %s\n", FIPS_mode() ? "enabled" : "FAILED"));
+#endif
+
+ /* test and clear the TeleporterEnabled property */
+ BOOL fTeleporterEnabled;
+ rc = mMachine->COMGETTER(TeleporterEnabled)(&fTeleporterEnabled);
+ if (FAILED(rc))
+ return rc;
+
+#if 0 /** @todo we should save it afterwards, but that isn't necessarily a good idea. Find a better place for this (VBoxSVC). */
+ if (fTeleporterEnabled)
+ {
+ rc = mMachine->COMSETTER(TeleporterEnabled)(FALSE);
+ if (FAILED(rc))
+ return rc;
+ }
+#endif
+
+ PCVMMR3VTABLE const pVMM = mpVMM;
+ AssertPtrReturn(pVMM, E_UNEXPECTED);
+
+ ComObjPtr<Progress> pPowerupProgress;
+ bool fBeganPoweringUp = false;
+
+ LONG cOperations = 1;
+ LONG ulTotalOperationsWeight = 1;
+ VMPowerUpTask *task = NULL;
+
+ try
+ {
+ /* Create a progress object to track progress of this operation. Must
+ * be done as early as possible (together with BeginPowerUp()) as this
+ * is vital for communicating as much as possible early powerup
+ * failure information to the API caller */
+ pPowerupProgress.createObject();
+ Bstr progressDesc;
+ if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved)
+ progressDesc = tr("Restoring virtual machine");
+ else if (fTeleporterEnabled)
+ progressDesc = tr("Teleporting virtual machine");
+ else
+ progressDesc = tr("Starting virtual machine");
+
+ /*
+ * Saved VMs will have to prove that their saved states seem kosher.
+ */
+ Utf8Str strSavedStateFile;
+ Bstr bstrStateKeyId;
+ Bstr bstrStateKeyStore;
+
+ if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved)
+ {
+ Bstr bstrSavedStateFile;
+ rc = mMachine->COMGETTER(StateFilePath)(bstrSavedStateFile.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+ strSavedStateFile = bstrSavedStateFile;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ rc = mMachine->COMGETTER(StateKeyId)(bstrStateKeyId.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+ rc = mMachine->COMGETTER(StateKeyStore)(bstrStateKeyStore.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+#endif
+
+ ComAssertRet(bstrSavedStateFile.isNotEmpty(), E_FAIL);
+ SsmStream ssmStream(this, pVMM, m_pKeyStore, bstrStateKeyId, bstrStateKeyStore);
+ int vrc = ssmStream.open(strSavedStateFile.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ PCSSMSTRMOPS pStreamOps;
+ void *pvStreamOpsUser;
+
+ vrc = ssmStream.querySsmStrmOps(&pStreamOps, &pvStreamOpsUser);
+ if (RT_SUCCESS(vrc))
+ vrc = pVMM->pfnSSMR3ValidateFile(NULL /*pszFilename*/, pStreamOps, pvStreamOpsUser,
+ false /* fChecksumIt */);
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ Utf8Str errMsg;
+ switch (vrc)
+ {
+ case VERR_FILE_NOT_FOUND:
+ errMsg.printf(tr("VM failed to start because the saved state file '%s' does not exist."),
+ strSavedStateFile.c_str());
+ break;
+ default:
+ errMsg.printf(tr("VM failed to start because the saved state file '%s' is invalid (%Rrc). "
+ "Delete the saved state prior to starting the VM."), strSavedStateFile.c_str(), vrc);
+ break;
+ }
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, errMsg.c_str());
+ }
+
+ }
+
+ /* Read console data, including console shared folders, stored in the
+ * saved state file (if not yet done).
+ */
+ rc = i_loadDataFromSavedState();
+ if (FAILED(rc))
+ throw rc;
+
+ /* Check all types of shared folders and compose a single list */
+ SharedFolderDataMap sharedFolders;
+ {
+ /* first, insert global folders */
+ for (SharedFolderDataMap::const_iterator it = m_mapGlobalSharedFolders.begin();
+ it != m_mapGlobalSharedFolders.end();
+ ++it)
+ {
+ const SharedFolderData &d = it->second;
+ sharedFolders[it->first] = d;
+ }
+
+ /* second, insert machine folders */
+ for (SharedFolderDataMap::const_iterator it = m_mapMachineSharedFolders.begin();
+ it != m_mapMachineSharedFolders.end();
+ ++it)
+ {
+ const SharedFolderData &d = it->second;
+ sharedFolders[it->first] = d;
+ }
+
+ /* third, insert console folders */
+ for (SharedFolderMap::const_iterator it = m_mapSharedFolders.begin();
+ it != m_mapSharedFolders.end();
+ ++it)
+ {
+ SharedFolder *pSF = it->second;
+ AutoCaller sfCaller(pSF);
+ AutoReadLock sfLock(pSF COMMA_LOCKVAL_SRC_POS);
+ sharedFolders[it->first] = SharedFolderData(pSF->i_getHostPath(),
+ pSF->i_isWritable(),
+ pSF->i_isAutoMounted(),
+ pSF->i_getAutoMountPoint());
+ }
+ }
+
+
+ /* Setup task object and thread to carry out the operation
+ * asynchronously */
+ try { task = new VMPowerUpTask(this, pPowerupProgress); }
+ catch (std::bad_alloc &) { throw rc = E_OUTOFMEMORY; }
+ if (!task->isOk())
+ throw task->rc();
+
+ task->mpfnConfigConstructor = i_configConstructor;
+ task->mSharedFolders = sharedFolders;
+ task->mStartPaused = aPaused;
+ if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved)
+ try { task->mSavedStateFile = strSavedStateFile; }
+ catch (std::bad_alloc &) { throw rc = E_OUTOFMEMORY; }
+ task->mTeleporterEnabled = fTeleporterEnabled;
+
+ /* Reset differencing hard disks for which autoReset is true,
+ * but only if the machine has no snapshots OR the current snapshot
+ * is an OFFLINE snapshot; otherwise we would reset the current
+ * differencing image of an ONLINE snapshot which contains the disk
+ * state of the machine while it was previously running, but without
+ * the corresponding machine state, which is equivalent to powering
+ * off a running machine and not good idea
+ */
+ ComPtr<ISnapshot> pCurrentSnapshot;
+ rc = mMachine->COMGETTER(CurrentSnapshot)(pCurrentSnapshot.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+
+ BOOL fCurrentSnapshotIsOnline = false;
+ if (pCurrentSnapshot)
+ {
+ rc = pCurrentSnapshot->COMGETTER(Online)(&fCurrentSnapshotIsOnline);
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ if (strSavedStateFile.isEmpty() && !fCurrentSnapshotIsOnline)
+ {
+ LogFlowThisFunc(("Looking for immutable images to reset\n"));
+
+ com::SafeIfaceArray<IMediumAttachment> atts;
+ rc = mMachine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(atts));
+ if (FAILED(rc))
+ throw rc;
+
+ for (size_t i = 0;
+ i < atts.size();
+ ++i)
+ {
+ DeviceType_T devType;
+ rc = atts[i]->COMGETTER(Type)(&devType);
+ /** @todo later applies to floppies as well */
+ if (devType == DeviceType_HardDisk)
+ {
+ ComPtr<IMedium> pMedium;
+ rc = atts[i]->COMGETTER(Medium)(pMedium.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+
+ /* needs autoreset? */
+ BOOL autoReset = FALSE;
+ rc = pMedium->COMGETTER(AutoReset)(&autoReset);
+ if (FAILED(rc))
+ throw rc;
+
+ if (autoReset)
+ {
+ ComPtr<IProgress> pResetProgress;
+ rc = pMedium->Reset(pResetProgress.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+
+ /* save for later use on the powerup thread */
+ task->hardDiskProgresses.push_back(pResetProgress);
+ }
+ }
+ }
+ }
+ else
+ LogFlowThisFunc(("Machine has a current snapshot which is online, skipping immutable images reset\n"));
+
+ /* setup task object and thread to carry out the operation
+ * asynchronously */
+
+#ifdef VBOX_WITH_EXTPACK
+ mptrExtPackManager->i_dumpAllToReleaseLog();
+#endif
+
+#ifdef RT_OS_SOLARIS
+ /* setup host core dumper for the VM */
+ Bstr value;
+ HRESULT hrc = mMachine->GetExtraData(Bstr("VBoxInternal2/CoreDumpEnabled").raw(), value.asOutParam());
+ if (SUCCEEDED(hrc) && value == "1")
+ {
+ Bstr coreDumpDir, coreDumpReplaceSys, coreDumpLive;
+ mMachine->GetExtraData(Bstr("VBoxInternal2/CoreDumpDir").raw(), coreDumpDir.asOutParam());
+ mMachine->GetExtraData(Bstr("VBoxInternal2/CoreDumpReplaceSystemDump").raw(), coreDumpReplaceSys.asOutParam());
+ mMachine->GetExtraData(Bstr("VBoxInternal2/CoreDumpLive").raw(), coreDumpLive.asOutParam());
+
+ uint32_t fCoreFlags = 0;
+ if ( coreDumpReplaceSys.isEmpty() == false
+ && Utf8Str(coreDumpReplaceSys).toUInt32() == 1)
+ fCoreFlags |= RTCOREDUMPER_FLAGS_REPLACE_SYSTEM_DUMP;
+
+ if ( coreDumpLive.isEmpty() == false
+ && Utf8Str(coreDumpLive).toUInt32() == 1)
+ fCoreFlags |= RTCOREDUMPER_FLAGS_LIVE_CORE;
+
+ Utf8Str strDumpDir(coreDumpDir);
+ const char *pszDumpDir = strDumpDir.c_str();
+ if ( pszDumpDir
+ && *pszDumpDir == '\0')
+ pszDumpDir = NULL;
+
+ int vrc;
+ if ( pszDumpDir
+ && !RTDirExists(pszDumpDir))
+ {
+ /*
+ * Try create the directory.
+ */
+ vrc = RTDirCreateFullPath(pszDumpDir, 0700);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(E_FAIL, vrc, tr("Failed to setup CoreDumper. Couldn't create dump directory '%s' (%Rrc)"),
+ pszDumpDir, vrc);
+ }
+
+ vrc = RTCoreDumperSetup(pszDumpDir, fCoreFlags);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(E_FAIL, vrc, tr("Failed to setup CoreDumper (%Rrc)"), vrc);
+ LogRel(("CoreDumper setup successful. pszDumpDir=%s fFlags=%#x\n", pszDumpDir ? pszDumpDir : ".", fCoreFlags));
+ }
+#endif
+
+
+ // If there is immutable drive the process that.
+ VMPowerUpTask::ProgressList progresses(task->hardDiskProgresses);
+ if (aProgress && !progresses.empty())
+ {
+ for (VMPowerUpTask::ProgressList::const_iterator it = progresses.begin(); it != progresses.end(); ++it)
+ {
+ ++cOperations;
+ ulTotalOperationsWeight += 1;
+ }
+ rc = pPowerupProgress->init(static_cast<IConsole *>(this),
+ progressDesc.raw(),
+ TRUE, // Cancelable
+ cOperations,
+ ulTotalOperationsWeight,
+ tr("Starting Hard Disk operations"),
+ 1);
+ AssertComRCReturnRC(rc);
+ }
+ else if ( mMachineState == MachineState_Saved
+ || mMachineState == MachineState_AbortedSaved
+ || !fTeleporterEnabled)
+ rc = pPowerupProgress->init(static_cast<IConsole *>(this),
+ progressDesc.raw(),
+ FALSE /* aCancelable */);
+ else if (fTeleporterEnabled)
+ rc = pPowerupProgress->init(static_cast<IConsole *>(this),
+ progressDesc.raw(),
+ TRUE /* aCancelable */,
+ 3 /* cOperations */,
+ 10 /* ulTotalOperationsWeight */,
+ tr("Teleporting virtual machine"),
+ 1 /* ulFirstOperationWeight */);
+
+ if (FAILED(rc))
+ throw rc;
+
+ /* Tell VBoxSVC and Machine about the progress object so they can
+ combine/proxy it to any openRemoteSession caller. */
+ LogFlowThisFunc(("Calling BeginPowerUp...\n"));
+ rc = mControl->BeginPowerUp(pPowerupProgress);
+ if (FAILED(rc))
+ {
+ LogFlowThisFunc(("BeginPowerUp failed\n"));
+ throw rc;
+ }
+ fBeganPoweringUp = true;
+
+ LogFlowThisFunc(("Checking if canceled...\n"));
+ BOOL fCanceled;
+ rc = pPowerupProgress->COMGETTER(Canceled)(&fCanceled);
+ if (FAILED(rc))
+ throw rc;
+
+ if (fCanceled)
+ {
+ LogFlowThisFunc(("Canceled in BeginPowerUp\n"));
+ throw setError(E_FAIL, tr("Powerup was canceled"));
+ }
+ LogFlowThisFunc(("Not canceled yet.\n"));
+
+ /** @todo this code prevents starting a VM with unavailable bridged
+ * networking interface. The only benefit is a slightly better error
+ * message, which should be moved to the driver code. This is the
+ * only reason why I left the code in for now. The driver allows
+ * unavailable bridged networking interfaces in certain circumstances,
+ * and this is sabotaged by this check. The VM will initially have no
+ * network connectivity, but the user can fix this at runtime. */
+#if 0
+ /* the network cards will undergo a quick consistency check */
+ for (ULONG slot = 0;
+ slot < maxNetworkAdapters;
+ ++slot)
+ {
+ ComPtr<INetworkAdapter> pNetworkAdapter;
+ mMachine->GetNetworkAdapter(slot, pNetworkAdapter.asOutParam());
+ BOOL enabled = FALSE;
+ pNetworkAdapter->COMGETTER(Enabled)(&enabled);
+ if (!enabled)
+ continue;
+
+ NetworkAttachmentType_T netattach;
+ pNetworkAdapter->COMGETTER(AttachmentType)(&netattach);
+ switch (netattach)
+ {
+ case NetworkAttachmentType_Bridged:
+ {
+ /* a valid host interface must have been set */
+ Bstr hostif;
+ pNetworkAdapter->COMGETTER(HostInterface)(hostif.asOutParam());
+ if (hostif.isEmpty())
+ {
+ throw setError(VBOX_E_HOST_ERROR,
+ tr("VM cannot start because host interface networking requires a host interface name to be set"));
+ }
+ ComPtr<IVirtualBox> pVirtualBox;
+ mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ ComPtr<IHost> pHost;
+ pVirtualBox->COMGETTER(Host)(pHost.asOutParam());
+ ComPtr<IHostNetworkInterface> pHostInterface;
+ if (!SUCCEEDED(pHost->FindHostNetworkInterfaceByName(hostif.raw(), pHostInterface.asOutParam())))
+ throw setError(VBOX_E_HOST_ERROR,
+ tr("VM cannot start because the host interface '%ls' does not exist"), hostif.raw());
+ break;
+ }
+ default:
+ break;
+ }
+ }
+#endif // 0
+
+
+ /* setup task object and thread to carry out the operation
+ * asynchronously */
+ if (aProgress)
+ {
+ rc = pPowerupProgress.queryInterfaceTo(aProgress);
+ AssertComRCReturnRC(rc);
+ }
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ task->mKeyStore = Utf8Str(bstrStateKeyStore);
+ task->mKeyId = Utf8Str(bstrStateKeyId);
+ task->m_pKeyStore = m_pKeyStore;
+#endif
+
+ rc = task->createThread();
+ task = NULL;
+ if (FAILED(rc))
+ throw rc;
+
+ /* finally, set the state: no right to fail in this method afterwards
+ * since we've already started the thread and it is now responsible for
+ * any error reporting and appropriate state change! */
+ if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved)
+ i_setMachineState(MachineState_Restoring);
+ else if (fTeleporterEnabled)
+ i_setMachineState(MachineState_TeleportingIn);
+ else
+ i_setMachineState(MachineState_Starting);
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ if (FAILED(rc) && fBeganPoweringUp)
+ {
+
+ /* The progress object will fetch the current error info */
+ if (!pPowerupProgress.isNull())
+ pPowerupProgress->i_notifyComplete(rc);
+
+ /* Save the error info across the IPC below. Can't be done before the
+ * progress notification above, as saving the error info deletes it
+ * from the current context, and thus the progress object wouldn't be
+ * updated correctly. */
+ ErrorInfoKeeper eik;
+
+ /* signal end of operation */
+ mControl->EndPowerUp(rc);
+ }
+
+ if (task)
+ {
+ ErrorInfoKeeper eik;
+ delete task;
+ }
+
+ LogFlowThisFunc(("mMachineState=%d, rc=%Rhrc\n", mMachineState, rc));
+ LogFlowThisFuncLeave();
+ return rc;
+}
+
+/**
+ * Internal power off worker routine.
+ *
+ * This method may be called only at certain places with the following meaning
+ * as shown below:
+ *
+ * - if the machine state is either Running or Paused, a normal
+ * Console-initiated powerdown takes place (e.g. PowerDown());
+ * - if the machine state is Saving, saveStateThread() has successfully done its
+ * job;
+ * - if the machine state is Starting or Restoring, powerUpThread() has failed
+ * to start/load the VM;
+ * - if the machine state is Stopping, the VM has powered itself off (i.e. not
+ * as a result of the powerDown() call).
+ *
+ * Calling it in situations other than the above will cause unexpected behavior.
+ *
+ * Note that this method should be the only one that destroys mpUVM and sets it
+ * to NULL.
+ *
+ * @param aProgress Progress object to run (may be NULL).
+ *
+ * @note Locks this object for writing.
+ *
+ * @note Never call this method from a thread that called addVMCaller() or
+ * instantiated an AutoVMCaller object; first call releaseVMCaller() or
+ * release(). Otherwise it will deadlock.
+ */
+HRESULT Console::i_powerDown(IProgress *aProgress /*= NULL*/)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ ComPtr<IInternalProgressControl> pProgressControl(aProgress);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Total # of steps for the progress object. Must correspond to the
+ * number of "advance percent count" comments in this method! */
+ enum { StepCount = 7 };
+ /* current step */
+ ULONG step = 0;
+
+ HRESULT rc = S_OK;
+ int vrc = VINF_SUCCESS;
+
+ /* sanity */
+ Assert(mVMDestroying == false);
+
+ PCVMMR3VTABLE const pVMM = mpVMM;
+ AssertPtrReturn(pVMM, E_UNEXPECTED);
+ PUVM pUVM = mpUVM;
+ AssertPtrReturn(pUVM, E_UNEXPECTED);
+
+ uint32_t cRefs = pVMM->pfnVMR3RetainUVM(pUVM);
+ Assert(cRefs != UINT32_MAX); NOREF(cRefs);
+
+ AssertMsg( mMachineState == MachineState_Running
+ || mMachineState == MachineState_Paused
+ || mMachineState == MachineState_Stuck
+ || mMachineState == MachineState_Starting
+ || mMachineState == MachineState_Stopping
+ || mMachineState == MachineState_Saving
+ || mMachineState == MachineState_Restoring
+ || mMachineState == MachineState_TeleportingPausedVM
+ || mMachineState == MachineState_TeleportingIn
+ , ("Invalid machine state: %s\n", ::stringifyMachineState(mMachineState)));
+
+ LogRel(("Console::powerDown(): A request to power off the VM has been issued (mMachineState=%s, InUninit=%d)\n",
+ ::stringifyMachineState(mMachineState), getObjectState().getState() == ObjectState::InUninit));
+
+ /* Check if we need to power off the VM. In case of mVMPoweredOff=true, the
+ * VM has already powered itself off in vmstateChangeCallback() and is just
+ * notifying Console about that. In case of Starting or Restoring,
+ * powerUpThread() is calling us on failure, so the VM is already off at
+ * that point. */
+ if ( !mVMPoweredOff
+ && ( mMachineState == MachineState_Starting
+ || mMachineState == MachineState_Restoring
+ || mMachineState == MachineState_TeleportingIn)
+ )
+ mVMPoweredOff = true;
+
+ /*
+ * Go to Stopping state if not already there.
+ *
+ * Note that we don't go from Saving/Restoring to Stopping because
+ * vmstateChangeCallback() needs it to set the state to Saved on
+ * VMSTATE_TERMINATED. In terms of protecting from inappropriate operations
+ * while leaving the lock below, Saving or Restoring should be fine too.
+ * Ditto for TeleportingPausedVM -> Teleported.
+ */
+ if ( mMachineState != MachineState_Saving
+ && mMachineState != MachineState_Restoring
+ && mMachineState != MachineState_Stopping
+ && mMachineState != MachineState_TeleportingIn
+ && mMachineState != MachineState_TeleportingPausedVM
+ )
+ i_setMachineState(MachineState_Stopping);
+
+ /* ----------------------------------------------------------------------
+ * DONE with necessary state changes, perform the power down actions (it's
+ * safe to release the object lock now if needed)
+ * ---------------------------------------------------------------------- */
+
+ if (mDisplay)
+ {
+ alock.release();
+
+ mDisplay->i_notifyPowerDown();
+
+ alock.acquire();
+ }
+
+ /* Stop the VRDP server to prevent new clients connection while VM is being
+ * powered off. */
+ if (mConsoleVRDPServer)
+ {
+ LogFlowThisFunc(("Stopping VRDP server...\n"));
+
+ /* Leave the lock since EMT could call us back as addVMCaller() */
+ alock.release();
+
+ mConsoleVRDPServer->Stop();
+
+ alock.acquire();
+ }
+
+ /* advance percent count */
+ if (pProgressControl)
+ pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount);
+
+
+ /* ----------------------------------------------------------------------
+ * Now, wait for all mpUVM callers to finish their work if there are still
+ * some on other threads. NO methods that need mpUVM (or initiate other calls
+ * that need it) may be called after this point
+ * ---------------------------------------------------------------------- */
+
+ /* go to the destroying state to prevent from adding new callers */
+ mVMDestroying = true;
+
+ if (mVMCallers > 0)
+ {
+ /* lazy creation */
+ if (mVMZeroCallersSem == NIL_RTSEMEVENT)
+ RTSemEventCreate(&mVMZeroCallersSem);
+
+ LogFlowThisFunc(("Waiting for mpUVM callers (%d) to drop to zero...\n", mVMCallers));
+
+ alock.release();
+
+ RTSemEventWait(mVMZeroCallersSem, RT_INDEFINITE_WAIT);
+
+ alock.acquire();
+ }
+
+ /* advance percent count */
+ if (pProgressControl)
+ pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount);
+
+ vrc = VINF_SUCCESS;
+
+ /*
+ * Power off the VM if not already done that.
+ * Leave the lock since EMT will call vmstateChangeCallback.
+ *
+ * Note that VMR3PowerOff() may fail here (invalid VMSTATE) if the
+ * VM-(guest-)initiated power off happened in parallel a ms before this
+ * call. So far, we let this error pop up on the user's side.
+ */
+ if (!mVMPoweredOff)
+ {
+ LogFlowThisFunc(("Powering off the VM...\n"));
+ alock.release();
+ vrc = pVMM->pfnVMR3PowerOff(pUVM);
+#ifdef VBOX_WITH_EXTPACK
+ mptrExtPackManager->i_callAllVmPowerOffHooks(this, pVMM->pfnVMR3GetVM(pUVM), pVMM);
+#endif
+ alock.acquire();
+ }
+
+ /* advance percent count */
+ if (pProgressControl)
+ pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount);
+
+#ifdef VBOX_WITH_HGCM
+ /* Shutdown HGCM services before destroying the VM. */
+ if (m_pVMMDev)
+ {
+ LogFlowThisFunc(("Shutdown HGCM...\n"));
+
+ /* Leave the lock since EMT might wait for it and will call us back as addVMCaller() */
+ alock.release();
+
+# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ /** @todo Deregister area callbacks? */
+# endif
+# ifdef VBOX_WITH_DRAG_AND_DROP
+ if (m_hHgcmSvcExtDragAndDrop)
+ {
+ HGCMHostUnregisterServiceExtension(m_hHgcmSvcExtDragAndDrop);
+ m_hHgcmSvcExtDragAndDrop = NULL;
+ }
+# endif
+
+ m_pVMMDev->hgcmShutdown();
+
+ alock.acquire();
+ }
+
+ /* advance percent count */
+ if (pProgressControl)
+ pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount);
+
+#endif /* VBOX_WITH_HGCM */
+
+ LogFlowThisFunc(("Ready for VM destruction.\n"));
+
+ /* If we are called from Console::uninit(), then try to destroy the VM even
+ * on failure (this will most likely fail too, but what to do?..) */
+ if (RT_SUCCESS(vrc) || getObjectState().getState() == ObjectState::InUninit)
+ {
+ /* If the machine has a USB controller, release all USB devices
+ * (symmetric to the code in captureUSBDevices()) */
+ if (mfVMHasUsbController)
+ {
+ alock.release();
+ i_detachAllUSBDevices(false /* aDone */);
+ alock.acquire();
+ }
+
+ /* Now we've got to destroy the VM as well. (mpUVM is not valid beyond
+ * this point). We release the lock before calling VMR3Destroy() because
+ * it will result into calling destructors of drivers associated with
+ * Console children which may in turn try to lock Console (e.g. by
+ * instantiating SafeVMPtr to access mpUVM). It's safe here because
+ * mVMDestroying is set which should prevent any activity. */
+
+ /* Set mpUVM to NULL early just in case if some old code is not using
+ * addVMCaller()/releaseVMCaller(). (We have our own ref on pUVM.) */
+ pVMM->pfnVMR3ReleaseUVM(mpUVM);
+ mpUVM = NULL;
+
+ LogFlowThisFunc(("Destroying the VM...\n"));
+
+ alock.release();
+
+ vrc = pVMM->pfnVMR3Destroy(pUVM);
+
+ /* take the lock again */
+ alock.acquire();
+
+ /* advance percent count */
+ if (pProgressControl)
+ pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount);
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("Machine has been destroyed (mMachineState=%d)\n",
+ mMachineState));
+ /* Note: the Console-level machine state change happens on the
+ * VMSTATE_TERMINATE state change in vmstateChangeCallback(). If
+ * powerDown() is called from EMT (i.e. from vmstateChangeCallback()
+ * on receiving VM-initiated VMSTATE_OFF), VMSTATE_TERMINATE hasn't
+ * occurred yet. This is okay, because mMachineState is already
+ * Stopping in this case, so any other attempt to call PowerDown()
+ * will be rejected. */
+ }
+ else
+ {
+ /* bad bad bad, but what to do? (Give Console our UVM ref.) */
+ mpUVM = pUVM;
+ pUVM = NULL;
+ rc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not destroy the machine. (Error: %Rrc)"), vrc);
+ }
+
+ /* Complete the detaching of the USB devices. */
+ if (mfVMHasUsbController)
+ {
+ alock.release();
+ i_detachAllUSBDevices(true /* aDone */);
+ alock.acquire();
+ }
+
+ /* advance percent count */
+ if (pProgressControl)
+ pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount);
+ }
+ else
+ rc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not power off the machine. (Error: %Rrc)"), vrc);
+
+ /*
+ * Finished with the destruction.
+ *
+ * Note that if something impossible happened and we've failed to destroy
+ * the VM, mVMDestroying will remain true and mMachineState will be
+ * something like Stopping, so most Console methods will return an error
+ * to the caller.
+ */
+ if (pUVM != NULL)
+ pVMM->pfnVMR3ReleaseUVM(pUVM);
+ else
+ mVMDestroying = false;
+
+ LogFlowThisFuncLeave();
+ return rc;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_setMachineState(MachineState_T aMachineState, bool aUpdateServer /* = true */)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ if (mMachineState != aMachineState)
+ {
+ LogThisFunc(("machineState=%s -> %s aUpdateServer=%RTbool\n",
+ ::stringifyMachineState(mMachineState), ::stringifyMachineState(aMachineState), aUpdateServer));
+ LogRel(("Console: Machine state changed to '%s'\n", ::stringifyMachineState(aMachineState)));
+ mMachineState = aMachineState;
+
+ /// @todo (dmik)
+ // possibly, we need to redo onStateChange() using the dedicated
+ // Event thread, like it is done in VirtualBox. This will make it
+ // much safer (no deadlocks possible if someone tries to use the
+ // console from the callback), however, listeners will lose the
+ // ability to synchronously react to state changes (is it really
+ // necessary??)
+ LogFlowThisFunc(("Doing onStateChange()...\n"));
+ i_onStateChange(aMachineState);
+ LogFlowThisFunc(("Done onStateChange()\n"));
+
+ if (aUpdateServer)
+ {
+ /* Server notification MUST be done from under the lock; otherwise
+ * the machine state here and on the server might go out of sync
+ * which can lead to various unexpected results (like the machine
+ * state being >= MachineState_Running on the server, while the
+ * session state is already SessionState_Unlocked at the same time
+ * there).
+ *
+ * Cross-lock conditions should be carefully watched out: calling
+ * UpdateState we will require Machine and SessionMachine locks
+ * (remember that here we're holding the Console lock here, and also
+ * all locks that have been acquire by the thread before calling
+ * this method).
+ */
+ LogFlowThisFunc(("Doing mControl->UpdateState()...\n"));
+ rc = mControl->UpdateState(aMachineState);
+ LogFlowThisFunc(("mControl->UpdateState()=%Rhrc\n", rc));
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Searches for a shared folder with the given logical name
+ * in the collection of shared folders.
+ *
+ * @param strName logical name of the shared folder
+ * @param aSharedFolder where to return the found object
+ * @param aSetError whether to set the error info if the folder is
+ * not found
+ * @return
+ * S_OK when found or E_INVALIDARG when not found
+ *
+ * @note The caller must lock this object for writing.
+ */
+HRESULT Console::i_findSharedFolder(const Utf8Str &strName, ComObjPtr<SharedFolder> &aSharedFolder, bool aSetError /* = false */)
+{
+ /* sanity check */
+ AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
+
+ SharedFolderMap::const_iterator it = m_mapSharedFolders.find(strName);
+ if (it != m_mapSharedFolders.end())
+ {
+ aSharedFolder = it->second;
+ return S_OK;
+ }
+
+ if (aSetError)
+ setError(VBOX_E_FILE_ERROR, tr("Could not find a shared folder named '%s'."), strName.c_str());
+ return VBOX_E_FILE_ERROR;
+}
+
+/**
+ * Fetches the list of global or machine shared folders from the server.
+ *
+ * @param aGlobal true to fetch global folders.
+ *
+ * @note The caller must lock this object for writing.
+ */
+HRESULT Console::i_fetchSharedFolders(BOOL aGlobal)
+{
+ /* sanity check */
+ AssertReturn( getObjectState().getState() == ObjectState::InInit
+ || isWriteLockOnCurrentThread(), E_FAIL);
+
+ LogFlowThisFunc(("Entering\n"));
+
+ /* Check if we're online and keep it that way. */
+ SafeVMPtrQuiet ptrVM(this);
+ AutoVMCallerQuietWeak autoVMCaller(this);
+ bool const online = ptrVM.isOk()
+ && m_pVMMDev
+ && m_pVMMDev->isShFlActive();
+
+ HRESULT rc = S_OK;
+
+ try
+ {
+ if (aGlobal)
+ {
+ /// @todo grab & process global folders when they are done
+ }
+ else
+ {
+ SharedFolderDataMap oldFolders;
+ if (online)
+ oldFolders = m_mapMachineSharedFolders;
+
+ m_mapMachineSharedFolders.clear();
+
+ SafeIfaceArray<ISharedFolder> folders;
+ rc = mMachine->COMGETTER(SharedFolders)(ComSafeArrayAsOutParam(folders));
+ if (FAILED(rc)) throw rc;
+
+ for (size_t i = 0; i < folders.size(); ++i)
+ {
+ ComPtr<ISharedFolder> pSharedFolder = folders[i];
+
+ Bstr bstr;
+ rc = pSharedFolder->COMGETTER(Name)(bstr.asOutParam());
+ if (FAILED(rc)) throw rc;
+ Utf8Str strName(bstr);
+
+ rc = pSharedFolder->COMGETTER(HostPath)(bstr.asOutParam());
+ if (FAILED(rc)) throw rc;
+ Utf8Str strHostPath(bstr);
+
+ BOOL writable;
+ rc = pSharedFolder->COMGETTER(Writable)(&writable);
+ if (FAILED(rc)) throw rc;
+
+ BOOL autoMount;
+ rc = pSharedFolder->COMGETTER(AutoMount)(&autoMount);
+ if (FAILED(rc)) throw rc;
+
+ rc = pSharedFolder->COMGETTER(AutoMountPoint)(bstr.asOutParam());
+ if (FAILED(rc)) throw rc;
+ Utf8Str strAutoMountPoint(bstr);
+
+ m_mapMachineSharedFolders.insert(std::make_pair(strName,
+ SharedFolderData(strHostPath, !!writable,
+ !!autoMount, strAutoMountPoint)));
+
+ /* send changes to HGCM if the VM is running */
+ if (online)
+ {
+ SharedFolderDataMap::iterator it = oldFolders.find(strName);
+ if ( it == oldFolders.end()
+ || it->second.m_strHostPath != strHostPath)
+ {
+ /* a new machine folder is added or
+ * the existing machine folder is changed */
+ if (m_mapSharedFolders.find(strName) != m_mapSharedFolders.end())
+ ; /* the console folder exists, nothing to do */
+ else
+ {
+ /* remove the old machine folder (when changed)
+ * or the global folder if any (when new) */
+ if ( it != oldFolders.end()
+ || m_mapGlobalSharedFolders.find(strName) != m_mapGlobalSharedFolders.end()
+ )
+ {
+ rc = i_removeSharedFolder(strName);
+ if (FAILED(rc)) throw rc;
+ }
+
+ /* create the new machine folder */
+ rc = i_createSharedFolder(strName,
+ SharedFolderData(strHostPath, !!writable, !!autoMount, strAutoMountPoint));
+ if (FAILED(rc)) throw rc;
+ }
+ }
+ /* forget the processed (or identical) folder */
+ if (it != oldFolders.end())
+ oldFolders.erase(it);
+ }
+ }
+
+ /* process outdated (removed) folders */
+ if (online)
+ {
+ for (SharedFolderDataMap::const_iterator it = oldFolders.begin();
+ it != oldFolders.end(); ++it)
+ {
+ if (m_mapSharedFolders.find(it->first) != m_mapSharedFolders.end())
+ ; /* the console folder exists, nothing to do */
+ else
+ {
+ /* remove the outdated machine folder */
+ rc = i_removeSharedFolder(it->first);
+ if (FAILED(rc)) throw rc;
+
+ /* create the global folder if there is any */
+ SharedFolderDataMap::const_iterator git =
+ m_mapGlobalSharedFolders.find(it->first);
+ if (git != m_mapGlobalSharedFolders.end())
+ {
+ rc = i_createSharedFolder(git->first, git->second);
+ if (FAILED(rc)) throw rc;
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (HRESULT rc2)
+ {
+ rc = rc2;
+ if (online)
+ i_atVMRuntimeErrorCallbackF(0, "BrokenSharedFolder", N_("Broken shared folder!"));
+ }
+
+ LogFlowThisFunc(("Leaving\n"));
+
+ return rc;
+}
+
+/**
+ * Searches for a shared folder with the given name in the list of machine
+ * shared folders and then in the list of the global shared folders.
+ *
+ * @param strName Name of the folder to search for.
+ * @param aIt Where to store the pointer to the found folder.
+ * @return @c true if the folder was found and @c false otherwise.
+ *
+ * @note The caller must lock this object for reading.
+ */
+bool Console::i_findOtherSharedFolder(const Utf8Str &strName,
+ SharedFolderDataMap::const_iterator &aIt)
+{
+ /* sanity check */
+ AssertReturn(isWriteLockOnCurrentThread(), false);
+
+ /* first, search machine folders */
+ aIt = m_mapMachineSharedFolders.find(strName);
+ if (aIt != m_mapMachineSharedFolders.end())
+ return true;
+
+ /* second, search machine folders */
+ aIt = m_mapGlobalSharedFolders.find(strName);
+ if (aIt != m_mapGlobalSharedFolders.end())
+ return true;
+
+ return false;
+}
+
+/**
+ * Calls the HGCM service to add a shared folder definition.
+ *
+ * @param strName Shared folder name.
+ * @param aData Shared folder data.
+ *
+ * @note Must be called from under AutoVMCaller and when mpUVM != NULL!
+ * @note Doesn't lock anything.
+ */
+HRESULT Console::i_createSharedFolder(const Utf8Str &strName, const SharedFolderData &aData)
+{
+ Log(("Adding shared folder '%s' -> '%s'\n", strName.c_str(), aData.m_strHostPath.c_str()));
+
+ /*
+ * Sanity checks
+ */
+ ComAssertRet(strName.isNotEmpty(), E_FAIL);
+ ComAssertRet(aData.m_strHostPath.isNotEmpty(), E_FAIL);
+
+ AssertReturn(mpUVM, E_FAIL);
+ AssertReturn(m_pVMMDev && m_pVMMDev->isShFlActive(), E_FAIL);
+
+ /*
+ * Find out whether we should allow symbolic link creation.
+ */
+ Bstr bstrValue;
+ HRESULT hrc = mMachine->GetExtraData(BstrFmt("VBoxInternal2/SharedFoldersEnableSymlinksCreate/%s", strName.c_str()).raw(),
+ bstrValue.asOutParam());
+ bool fSymlinksCreate = hrc == S_OK && bstrValue == "1";
+
+ /*
+ * Check whether the path is valid and exists.
+ */
+ char szAbsHostPath[RTPATH_MAX];
+ int vrc = RTPathAbs(aData.m_strHostPath.c_str(), szAbsHostPath, sizeof(szAbsHostPath));
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid shared folder path: '%s' (%Rrc)"), aData.m_strHostPath.c_str(), vrc);
+
+ /* Check whether the path is full (absolute). ASSUMING a RTPATH_MAX of ~4K
+ this also checks that the length is within bounds of a SHFLSTRING. */
+ if (RTPathCompare(aData.m_strHostPath.c_str(), szAbsHostPath) != 0)
+ return setError(E_INVALIDARG, tr("Shared folder path '%s' is not absolute"), aData.m_strHostPath.c_str());
+
+ bool const fMissing = !RTPathExists(szAbsHostPath);
+
+ /*
+ * Check the other two string lengths before converting them all to SHFLSTRINGS.
+ */
+ if (strName.length() >= _2K)
+ return setError(E_INVALIDARG, tr("Shared folder name is too long: %zu bytes", "", strName.length()), strName.length());
+ if (aData.m_strAutoMountPoint.length() >= RTPATH_MAX)
+ return setError(E_INVALIDARG, tr("Shared folder mount point too long: %zu bytes", "",
+ (int)aData.m_strAutoMountPoint.length()),
+ aData.m_strAutoMountPoint.length());
+
+ PSHFLSTRING pHostPath = ShflStringDupUtf8AsUtf16(aData.m_strHostPath.c_str());
+ PSHFLSTRING pName = ShflStringDupUtf8AsUtf16(strName.c_str());
+ PSHFLSTRING pAutoMountPoint = ShflStringDupUtf8AsUtf16(aData.m_strAutoMountPoint.c_str());
+ if (pHostPath && pName && pAutoMountPoint)
+ {
+ /*
+ * Make a SHFL_FN_ADD_MAPPING call to tell the service about folder.
+ */
+ VBOXHGCMSVCPARM aParams[SHFL_CPARMS_ADD_MAPPING];
+ SHFLSTRING_TO_HGMC_PARAM(&aParams[0], pHostPath);
+ SHFLSTRING_TO_HGMC_PARAM(&aParams[1], pName);
+ HGCMSvcSetU32(&aParams[2],
+ (aData.m_fWritable ? SHFL_ADD_MAPPING_F_WRITABLE : 0)
+ | (aData.m_fAutoMount ? SHFL_ADD_MAPPING_F_AUTOMOUNT : 0)
+ | (fSymlinksCreate ? SHFL_ADD_MAPPING_F_CREATE_SYMLINKS : 0)
+ | (fMissing ? SHFL_ADD_MAPPING_F_MISSING : 0));
+ SHFLSTRING_TO_HGMC_PARAM(&aParams[3], pAutoMountPoint);
+ AssertCompile(SHFL_CPARMS_ADD_MAPPING == 4);
+
+ vrc = m_pVMMDev->hgcmHostCall("VBoxSharedFolders", SHFL_FN_ADD_MAPPING, SHFL_CPARMS_ADD_MAPPING, aParams);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Could not create a shared folder '%s' mapped to '%s' (%Rrc)"),
+ strName.c_str(), aData.m_strHostPath.c_str(), vrc);
+
+ else if (fMissing)
+ hrc = setError(E_INVALIDARG, tr("Shared folder path '%s' does not exist on the host"), aData.m_strHostPath.c_str());
+ else
+ hrc = S_OK;
+ }
+ else
+ hrc = E_OUTOFMEMORY;
+ RTMemFree(pAutoMountPoint);
+ RTMemFree(pName);
+ RTMemFree(pHostPath);
+ return hrc;
+}
+
+/**
+ * Calls the HGCM service to remove the shared folder definition.
+ *
+ * @param strName Shared folder name.
+ *
+ * @note Must be called from under AutoVMCaller and when mpUVM != NULL!
+ * @note Doesn't lock anything.
+ */
+HRESULT Console::i_removeSharedFolder(const Utf8Str &strName)
+{
+ ComAssertRet(strName.isNotEmpty(), E_FAIL);
+
+ /* sanity checks */
+ AssertReturn(mpUVM, E_FAIL);
+ AssertReturn(m_pVMMDev && m_pVMMDev->isShFlActive(), E_FAIL);
+
+ VBOXHGCMSVCPARM parms;
+ SHFLSTRING *pMapName;
+ size_t cbString;
+
+ Log(("Removing shared folder '%s'\n", strName.c_str()));
+
+ Bstr bstrName(strName);
+ cbString = (bstrName.length() + 1) * sizeof(RTUTF16);
+ if (cbString >= UINT16_MAX)
+ return setError(E_INVALIDARG, tr("The name is too long"));
+ pMapName = (SHFLSTRING *) RTMemAllocZ(SHFLSTRING_HEADER_SIZE + cbString);
+ Assert(pMapName);
+ memcpy(pMapName->String.ucs2, bstrName.raw(), cbString);
+
+ pMapName->u16Size = (uint16_t)cbString;
+ pMapName->u16Length = (uint16_t)(cbString - sizeof(RTUTF16));
+
+ parms.type = VBOX_HGCM_SVC_PARM_PTR;
+ parms.u.pointer.addr = pMapName;
+ parms.u.pointer.size = ShflStringSizeOfBuffer(pMapName);
+
+ int vrc = m_pVMMDev->hgcmHostCall("VBoxSharedFolders", SHFL_FN_REMOVE_MAPPING, 1, &parms);
+ RTMemFree(pMapName);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Could not remove the shared folder '%s' (%Rrc)"), strName.c_str(), vrc);
+
+ return S_OK;
+}
+
+/**
+ * Retains a reference to the default cryptographic interface.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NOT_SUPPORTED if the VM is not configured for encryption.
+ * @param ppCryptoIf Where to store the pointer to the cryptographic interface on success.
+ *
+ * @note Locks this object for writing.
+ */
+int Console::i_retainCryptoIf(PCVBOXCRYPTOIF *ppCryptoIf)
+{
+ AssertReturn(ppCryptoIf != NULL, VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+ if (mhLdrModCrypto == NIL_RTLDRMOD)
+ {
+#ifdef VBOX_WITH_EXTPACK
+ /*
+ * Check that a crypto extension pack name is set and resolve it into a
+ * library path.
+ */
+ HRESULT hrc = S_OK;
+ Bstr bstrExtPack;
+
+ ComPtr<IVirtualBox> pVirtualBox;
+ mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ ComPtr<ISystemProperties> pSystemProperties;
+ if (pVirtualBox)
+ pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ if (pSystemProperties)
+ pSystemProperties->COMGETTER(DefaultCryptoExtPack)(bstrExtPack.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ Utf8Str strExtPack(bstrExtPack);
+ if (strExtPack.isEmpty())
+ {
+ setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Ńo extension pack providing a cryptographic support module could be found"));
+ return VERR_NOT_FOUND;
+ }
+
+ Utf8Str strCryptoLibrary;
+ vrc = mptrExtPackManager->i_getCryptoLibraryPathForExtPack(&strExtPack, &strCryptoLibrary);
+ if (RT_SUCCESS(vrc))
+ {
+ RTERRINFOSTATIC ErrInfo;
+ vrc = SUPR3HardenedLdrLoadPlugIn(strCryptoLibrary.c_str(), &mhLdrModCrypto, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ /* Resolve the entry point and query the pointer to the cryptographic interface. */
+ PFNVBOXCRYPTOENTRY pfnCryptoEntry = NULL;
+ vrc = RTLdrGetSymbol(mhLdrModCrypto, VBOX_CRYPTO_MOD_ENTRY_POINT, (void **)&pfnCryptoEntry);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pfnCryptoEntry(&mpCryptoIf);
+ if (RT_FAILURE(vrc))
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Failed to query the interface callback table from the cryptographic support module '%s' from extension pack '%s'"),
+ strCryptoLibrary.c_str(), strExtPack.c_str());
+ }
+ else
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Failed to resolve the entry point for the cryptographic support module '%s' from extension pack '%s'"),
+ strCryptoLibrary.c_str(), strExtPack.c_str());
+
+ if (RT_FAILURE(vrc))
+ {
+ RTLdrClose(mhLdrModCrypto);
+ mhLdrModCrypto = NIL_RTLDRMOD;
+ }
+ }
+ else
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Couldn't load the cryptographic support module '%s' from extension pack '%s' (error: '%s')"),
+ strCryptoLibrary.c_str(), strExtPack.c_str(), ErrInfo.Core.pszMsg);
+ }
+ else
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Couldn't resolve the library path of the crpytographic support module for extension pack '%s'"),
+ strExtPack.c_str());
+#else
+ setError(VBOX_E_NOT_SUPPORTED,
+ tr("The cryptographic support module is not supported in this build because extension packs are not supported"));
+ vrc = VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ ASMAtomicIncU32(&mcRefsCrypto);
+ *ppCryptoIf = mpCryptoIf;
+ }
+
+ return vrc;
+}
+
+/**
+ * Releases the reference of the given cryptographic interface.
+ *
+ * @returns VBox status code.
+ * @param pCryptoIf Pointer to the cryptographic interface to release.
+ *
+ * @note Locks this object for writing.
+ */
+int Console::i_releaseCryptoIf(PCVBOXCRYPTOIF pCryptoIf)
+{
+ AssertReturn(pCryptoIf == mpCryptoIf, VERR_INVALID_PARAMETER);
+
+ ASMAtomicDecU32(&mcRefsCrypto);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Tries to unload any loaded cryptographic support module if it is not in use currently.
+ *
+ * @returns COM status code.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_unloadCryptoIfModule(void)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mcRefsCrypto)
+ return setError(E_ACCESSDENIED,
+ tr("The cryptographic support module is in use and can't be unloaded"));
+
+ if (mhLdrModCrypto != NIL_RTLDRMOD)
+ {
+ int vrc = RTLdrClose(mhLdrModCrypto);
+ AssertRC(vrc);
+ mhLdrModCrypto = NIL_RTLDRMOD;
+ }
+
+ return S_OK;
+}
+
+/** @callback_method_impl{FNVMATSTATE}
+ *
+ * @note Locks the Console object for writing.
+ * @remarks The @a pUVM parameter can be NULL in one case where powerUpThread()
+ * calls after the VM was destroyed.
+ */
+/*static*/ DECLCALLBACK(void)
+Console::i_vmstateChangeCallback(PUVM pUVM, PCVMMR3VTABLE pVMM, VMSTATE enmState, VMSTATE enmOldState, void *pvUser)
+{
+ LogFlowFunc(("Changing state from %s to %s (pUVM=%p)\n",
+ pVMM->pfnVMR3GetStateName(enmOldState), pVMM->pfnVMR3GetStateName(enmState), pUVM));
+ RT_NOREF(pVMM);
+
+ Console *that = static_cast<Console *>(pvUser);
+ AssertReturnVoid(that);
+
+ AutoCaller autoCaller(that);
+
+ /* Note that we must let this method proceed even if Console::uninit() has
+ * been already called. In such case this VMSTATE change is a result of:
+ * 1) powerDown() called from uninit() itself, or
+ * 2) VM-(guest-)initiated power off. */
+ AssertReturnVoid( autoCaller.isOk()
+ || that->getObjectState().getState() == ObjectState::InUninit);
+
+ switch (enmState)
+ {
+ /*
+ * The VM has terminated
+ */
+ case VMSTATE_OFF:
+ {
+#ifdef VBOX_WITH_GUEST_PROPS
+ if (that->mfTurnResetIntoPowerOff)
+ {
+ Bstr strPowerOffReason;
+
+ if (that->mfPowerOffCausedByReset)
+ strPowerOffReason = Bstr("Reset");
+ else
+ strPowerOffReason = Bstr("PowerOff");
+
+ that->mMachine->DeleteGuestProperty(Bstr("/VirtualBox/HostInfo/VMPowerOffReason").raw());
+ that->mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VMPowerOffReason").raw(),
+ strPowerOffReason.raw(), Bstr("RDONLYGUEST").raw());
+ that->mMachine->SaveSettings();
+ }
+#endif
+
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+
+ if (that->mVMStateChangeCallbackDisabled)
+ return;
+
+ /* Do we still think that it is running? It may happen if this is a
+ * VM-(guest-)initiated shutdown/poweroff.
+ */
+ if ( that->mMachineState != MachineState_Stopping
+ && that->mMachineState != MachineState_Saving
+ && that->mMachineState != MachineState_Restoring
+ && that->mMachineState != MachineState_TeleportingIn
+ && that->mMachineState != MachineState_TeleportingPausedVM
+ && !that->mVMIsAlreadyPoweringOff
+ )
+ {
+ LogFlowFunc(("VM has powered itself off but Console still thinks it is running. Notifying.\n"));
+
+ /*
+ * Prevent powerDown() from calling VMR3PowerOff() again if this was called from
+ * the power off state change.
+ * When called from the Reset state make sure to call VMR3PowerOff() first.
+ */
+ Assert(that->mVMPoweredOff == false);
+ that->mVMPoweredOff = true;
+
+ /*
+ * request a progress object from the server
+ * (this will set the machine state to Stopping on the server
+ * to block others from accessing this machine)
+ */
+ ComPtr<IProgress> pProgress;
+ HRESULT rc = that->mControl->BeginPoweringDown(pProgress.asOutParam());
+ AssertComRC(rc);
+
+ /* sync the state with the server */
+ that->i_setMachineStateLocally(MachineState_Stopping);
+
+ /*
+ * Setup task object and thread to carry out the operation
+ * asynchronously (if we call powerDown() right here but there
+ * is one or more mpUVM callers (added with addVMCaller()) we'll
+ * deadlock).
+ */
+ VMPowerDownTask *pTask = NULL;
+ try
+ {
+ pTask = new VMPowerDownTask(that, pProgress);
+ }
+ catch (std::bad_alloc &)
+ {
+ LogRelFunc(("E_OUTOFMEMORY creating VMPowerDownTask"));
+ rc = E_OUTOFMEMORY;
+ break;
+ }
+
+ /*
+ * If creating a task failed, this can currently mean one of
+ * two: either Console::uninit() has been called just a ms
+ * before (so a powerDown() call is already on the way), or
+ * powerDown() itself is being already executed. Just do
+ * nothing.
+ */
+ if (pTask->isOk())
+ {
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (FAILED(rc))
+ LogRelFunc(("Problem with creating thread for VMPowerDownTask.\n"));
+ }
+ else
+ {
+ LogFlowFunc(("Console is already being uninitialized. (%Rhrc)\n", pTask->rc()));
+ delete pTask;
+ pTask = NULL;
+ rc = E_FAIL;
+ }
+ }
+ break;
+ }
+
+ /* The VM has been completely destroyed.
+ *
+ * Note: This state change can happen at two points:
+ * 1) At the end of VMR3Destroy() if it was not called from EMT.
+ * 2) At the end of vmR3EmulationThread if VMR3Destroy() was
+ * called by EMT.
+ */
+ case VMSTATE_TERMINATED:
+ {
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+
+ if (that->mVMStateChangeCallbackDisabled)
+ break;
+
+#ifdef VBOX_WITH_CLOUD_NET
+ /*
+ * We stop cloud gateway here because we may have failed to connect to it,
+ * configure it, or establish a tunnel. We definitely do not want an orphaned
+ * instance running in the cloud.
+ */
+ if (!that->mGateway.mGatewayInstanceId.isEmpty())
+ {
+ ComPtr<IVirtualBox> pVirtualBox;
+ HRESULT rc = that->mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ AssertComRC(rc);
+ if (SUCCEEDED(rc) && !pVirtualBox.isNull())
+ stopCloudGateway(pVirtualBox, that->mGateway);
+ }
+#endif /* VBOX_WITH_CLOUD_NET */
+ /* Terminate host interface networking. If pUVM is NULL, we've been
+ * manually called from powerUpThread() either before calling
+ * VMR3Create() or after VMR3Create() failed, so no need to touch
+ * networking.
+ */
+ if (pUVM)
+ that->i_powerDownHostInterfaces();
+
+ /* From now on the machine is officially powered down or remains in
+ * the Saved state.
+ */
+ switch (that->mMachineState)
+ {
+ default:
+ AssertFailed();
+ RT_FALL_THRU();
+ case MachineState_Stopping:
+ /* successfully powered down */
+ that->i_setMachineState(MachineState_PoweredOff);
+ break;
+ case MachineState_Saving:
+ /* successfully saved */
+ that->i_setMachineState(MachineState_Saved);
+ break;
+ case MachineState_Starting:
+ /* failed to start, but be patient: set back to PoweredOff
+ * (for similarity with the below) */
+ that->i_setMachineState(MachineState_PoweredOff);
+ break;
+ case MachineState_Restoring:
+ /* failed to load the saved state file, but be patient: set
+ * to AbortedSaved (to preserve the saved state file) */
+ that->i_setMachineState(MachineState_AbortedSaved);
+ break;
+ case MachineState_TeleportingIn:
+ /* Teleportation failed or was canceled. Back to powered off. */
+ that->i_setMachineState(MachineState_PoweredOff);
+ break;
+ case MachineState_TeleportingPausedVM:
+ /* Successfully teleported the VM. */
+ that->i_setMachineState(MachineState_Teleported);
+ break;
+ }
+ break;
+ }
+
+ case VMSTATE_RESETTING:
+ /** @todo shouldn't VMSTATE_RESETTING_LS be here? */
+ {
+#ifdef VBOX_WITH_GUEST_PROPS
+ /* Do not take any read/write locks here! */
+ that->i_guestPropertiesHandleVMReset();
+#endif
+ break;
+ }
+
+ case VMSTATE_SOFT_RESETTING:
+ case VMSTATE_SOFT_RESETTING_LS:
+ /* Shouldn't do anything here! */
+ break;
+
+ case VMSTATE_SUSPENDED:
+ {
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+
+ if (that->mVMStateChangeCallbackDisabled)
+ break;
+
+ switch (that->mMachineState)
+ {
+ case MachineState_Teleporting:
+ that->i_setMachineState(MachineState_TeleportingPausedVM);
+ break;
+
+ case MachineState_LiveSnapshotting:
+ that->i_setMachineState(MachineState_OnlineSnapshotting);
+ break;
+
+ case MachineState_TeleportingPausedVM:
+ case MachineState_Saving:
+ case MachineState_Restoring:
+ case MachineState_Stopping:
+ case MachineState_TeleportingIn:
+ case MachineState_OnlineSnapshotting:
+ /* The worker thread handles the transition. */
+ break;
+
+ case MachineState_Running:
+ that->i_setMachineState(MachineState_Paused);
+ break;
+
+ case MachineState_Paused:
+ /* Nothing to do. */
+ break;
+
+ default:
+ AssertMsgFailed(("%s\n", ::stringifyMachineState(that->mMachineState)));
+ }
+ break;
+ }
+
+ case VMSTATE_SUSPENDED_LS:
+ case VMSTATE_SUSPENDED_EXT_LS:
+ {
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+ if (that->mVMStateChangeCallbackDisabled)
+ break;
+ switch (that->mMachineState)
+ {
+ case MachineState_Teleporting:
+ that->i_setMachineState(MachineState_TeleportingPausedVM);
+ break;
+
+ case MachineState_LiveSnapshotting:
+ that->i_setMachineState(MachineState_OnlineSnapshotting);
+ break;
+
+ case MachineState_TeleportingPausedVM:
+ case MachineState_Saving:
+ /* ignore */
+ break;
+
+ default:
+ AssertMsgFailed(("%s/%s -> %s\n", ::stringifyMachineState(that->mMachineState),
+ pVMM->pfnVMR3GetStateName(enmOldState), pVMM->pfnVMR3GetStateName(enmState) ));
+ that->i_setMachineState(MachineState_Paused);
+ break;
+ }
+ break;
+ }
+
+ case VMSTATE_RUNNING:
+ {
+ if ( enmOldState == VMSTATE_POWERING_ON
+ || enmOldState == VMSTATE_RESUMING)
+ {
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+
+ if (that->mVMStateChangeCallbackDisabled)
+ break;
+
+ Assert( ( ( that->mMachineState == MachineState_Starting
+ || that->mMachineState == MachineState_Paused)
+ && enmOldState == VMSTATE_POWERING_ON)
+ || ( ( that->mMachineState == MachineState_Restoring
+ || that->mMachineState == MachineState_TeleportingIn
+ || that->mMachineState == MachineState_Paused
+ || that->mMachineState == MachineState_Saving
+ )
+ && enmOldState == VMSTATE_RESUMING));
+
+ that->i_setMachineState(MachineState_Running);
+ }
+
+ break;
+ }
+
+ case VMSTATE_RUNNING_LS:
+ AssertMsg( that->mMachineState == MachineState_LiveSnapshotting
+ || that->mMachineState == MachineState_Teleporting,
+ ("%s/%s -> %s\n", ::stringifyMachineState(that->mMachineState),
+ pVMM->pfnVMR3GetStateName(enmOldState), pVMM->pfnVMR3GetStateName(enmState) ));
+ break;
+
+ case VMSTATE_FATAL_ERROR:
+ {
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+
+ if (that->mVMStateChangeCallbackDisabled)
+ break;
+
+ /* Fatal errors are only for running VMs. */
+ Assert(Global::IsOnline(that->mMachineState));
+
+ /* Note! 'Pause' is used here in want of something better. There
+ * are currently only two places where fatal errors might be
+ * raised, so it is not worth adding a new externally
+ * visible state for this yet. */
+ that->i_setMachineState(MachineState_Paused);
+ break;
+ }
+
+ case VMSTATE_GURU_MEDITATION:
+ {
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+
+ if (that->mVMStateChangeCallbackDisabled)
+ break;
+
+ /* Guru are only for running VMs */
+ Assert(Global::IsOnline(that->mMachineState));
+
+ that->i_setMachineState(MachineState_Stuck);
+ break;
+ }
+
+ case VMSTATE_CREATED:
+ {
+ /*
+ * We have to set the secret key helper interface for the VD drivers to
+ * get notified about missing keys.
+ */
+ that->i_initSecretKeyIfOnAllAttachments();
+ break;
+ }
+
+ default: /* shut up gcc */
+ break;
+ }
+}
+
+/**
+ * Changes the clipboard mode.
+ *
+ * @returns VBox status code.
+ * @param aClipboardMode new clipboard mode.
+ */
+int Console::i_changeClipboardMode(ClipboardMode_T aClipboardMode)
+{
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+ VMMDev *pVMMDev = m_pVMMDev;
+ AssertPtrReturn(pVMMDev, VERR_INVALID_POINTER);
+
+ VBOXHGCMSVCPARM parm;
+ parm.type = VBOX_HGCM_SVC_PARM_32BIT;
+
+ switch (aClipboardMode)
+ {
+ default:
+ case ClipboardMode_Disabled:
+ LogRel(("Shared Clipboard: Mode: Off\n"));
+ parm.u.uint32 = VBOX_SHCL_MODE_OFF;
+ break;
+ case ClipboardMode_GuestToHost:
+ LogRel(("Shared Clipboard: Mode: Guest to Host\n"));
+ parm.u.uint32 = VBOX_SHCL_MODE_GUEST_TO_HOST;
+ break;
+ case ClipboardMode_HostToGuest:
+ LogRel(("Shared Clipboard: Mode: Host to Guest\n"));
+ parm.u.uint32 = VBOX_SHCL_MODE_HOST_TO_GUEST;
+ break;
+ case ClipboardMode_Bidirectional:
+ LogRel(("Shared Clipboard: Mode: Bidirectional\n"));
+ parm.u.uint32 = VBOX_SHCL_MODE_BIDIRECTIONAL;
+ break;
+ }
+
+ int vrc = pVMMDev->hgcmHostCall("VBoxSharedClipboard", VBOX_SHCL_HOST_FN_SET_MODE, 1, &parm);
+ if (RT_FAILURE(vrc))
+ LogRel(("Shared Clipboard: Error changing mode: %Rrc\n", vrc));
+
+ return vrc;
+#else
+ RT_NOREF(aClipboardMode);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+/**
+ * Changes the clipboard file transfer mode.
+ *
+ * @returns VBox status code.
+ * @param aEnabled Whether clipboard file transfers are enabled or not.
+ */
+int Console::i_changeClipboardFileTransferMode(bool aEnabled)
+{
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ VMMDev *pVMMDev = m_pVMMDev;
+ AssertPtrReturn(pVMMDev, VERR_INVALID_POINTER);
+
+ VBOXHGCMSVCPARM parm;
+ RT_ZERO(parm);
+
+ parm.type = VBOX_HGCM_SVC_PARM_32BIT;
+ parm.u.uint32 = aEnabled ? VBOX_SHCL_TRANSFER_MODE_ENABLED : VBOX_SHCL_TRANSFER_MODE_DISABLED;
+
+ int vrc = pVMMDev->hgcmHostCall("VBoxSharedClipboard", VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1 /* cParms */, &parm);
+ if (RT_FAILURE(vrc))
+ LogRel(("Shared Clipboard: Error changing file transfer mode: %Rrc\n", vrc));
+
+ return vrc;
+#else
+ RT_NOREF(aEnabled);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+/**
+ * Changes the drag and drop mode.
+ *
+ * @param aDnDMode new drag and drop mode.
+ */
+int Console::i_changeDnDMode(DnDMode_T aDnDMode)
+{
+ VMMDev *pVMMDev = m_pVMMDev;
+ AssertPtrReturn(pVMMDev, VERR_INVALID_POINTER);
+
+ VBOXHGCMSVCPARM parm;
+ RT_ZERO(parm);
+ parm.type = VBOX_HGCM_SVC_PARM_32BIT;
+
+ switch (aDnDMode)
+ {
+ default:
+ case DnDMode_Disabled:
+ LogRel(("Drag and drop mode: Off\n"));
+ parm.u.uint32 = VBOX_DRAG_AND_DROP_MODE_OFF;
+ break;
+ case DnDMode_GuestToHost:
+ LogRel(("Drag and drop mode: Guest to Host\n"));
+ parm.u.uint32 = VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST;
+ break;
+ case DnDMode_HostToGuest:
+ LogRel(("Drag and drop mode: Host to Guest\n"));
+ parm.u.uint32 = VBOX_DRAG_AND_DROP_MODE_HOST_TO_GUEST;
+ break;
+ case DnDMode_Bidirectional:
+ LogRel(("Drag and drop mode: Bidirectional\n"));
+ parm.u.uint32 = VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL;
+ break;
+ }
+
+ int rc = pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", DragAndDropSvc::HOST_DND_FN_SET_MODE, 1 /* cParms */, &parm);
+ if (RT_FAILURE(rc))
+ LogRel(("Error changing drag and drop mode: %Rrc\n", rc));
+
+ return rc;
+}
+
+#ifdef VBOX_WITH_USB
+/**
+ * @interface_method_impl{REMOTEUSBIF,pfnQueryRemoteUsbBackend}
+ */
+/*static*/ DECLCALLBACK(PREMOTEUSBCALLBACK)
+Console::i_usbQueryRemoteUsbBackend(void *pvUser, PCRTUUID pUuid, uint32_t idClient)
+{
+ Console *pConsole = (Console *)pvUser;
+
+ AutoReadLock thatLock(pConsole COMMA_LOCKVAL_SRC_POS);
+
+ Guid const uuid(*pUuid);
+ return (PREMOTEUSBCALLBACK)pConsole->i_consoleVRDPServer()->USBBackendRequestPointer(idClient, &uuid);
+}
+
+
+/**
+ * Sends a request to VMM to attach the given host device.
+ * After this method succeeds, the attached device will appear in the
+ * mUSBDevices collection.
+ *
+ * @param aHostDevice device to attach
+ *
+ * @note Synchronously calls EMT.
+ */
+HRESULT Console::i_attachUSBDevice(IUSBDevice *aHostDevice, ULONG aMaskedIfs, const Utf8Str &aCaptureFilename)
+{
+ AssertReturn(aHostDevice, E_FAIL);
+ AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL);
+
+ HRESULT hrc;
+
+ /*
+ * Get the address and the Uuid, and call the pfnCreateProxyDevice roothub
+ * method in EMT (using usbAttachCallback()).
+ */
+ Bstr bstrAddress;
+ hrc = aHostDevice->COMGETTER(Address)(bstrAddress.asOutParam());
+ ComAssertComRCRetRC(hrc);
+ Utf8Str const Address(bstrAddress);
+
+ Bstr id;
+ hrc = aHostDevice->COMGETTER(Id)(id.asOutParam());
+ ComAssertComRCRetRC(hrc);
+ Guid const uuid(id);
+
+ BOOL fRemote = FALSE;
+ hrc = aHostDevice->COMGETTER(Remote)(&fRemote);
+ ComAssertComRCRetRC(hrc);
+
+ Bstr bstrBackend;
+ hrc = aHostDevice->COMGETTER(Backend)(bstrBackend.asOutParam());
+ ComAssertComRCRetRC(hrc);
+ Utf8Str const strBackend(bstrBackend);
+
+ /* Get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ LogFlowThisFunc(("Proxying USB device '%s' {%RTuuid}...\n", Address.c_str(), uuid.raw()));
+
+ PCFGMNODE pRemoteCfg = NULL;
+ if (fRemote)
+ {
+ RemoteUSBDevice *pRemoteUSBDevice = static_cast<RemoteUSBDevice *>(aHostDevice);
+
+ pRemoteCfg = mpVMM->pfnCFGMR3CreateTree(ptrVM.rawUVM());
+ if (pRemoteCfg)
+ {
+ int vrc = mpVMM->pfnCFGMR3InsertInteger(pRemoteCfg, "ClientId", pRemoteUSBDevice->clientId());
+ if (RT_FAILURE(vrc))
+ {
+ mpVMM->pfnCFGMR3DestroyTree(pRemoteCfg);
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to create configuration for USB device."));
+ }
+ }
+ else
+ return setErrorBoth(E_OUTOFMEMORY, VERR_NO_MEMORY, tr("Failed to allocate config tree for USB device."));
+ }
+
+ USBConnectionSpeed_T enmSpeed;
+ hrc = aHostDevice->COMGETTER(Speed)(&enmSpeed);
+ AssertComRCReturnRC(hrc);
+
+ int vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), 0 /* idDstCpu (saved state, see #6232) */,
+ (PFNRT)i_usbAttachCallback, 11,
+ this, ptrVM.rawUVM(), ptrVM.vtable(), aHostDevice, uuid.raw(),
+ strBackend.c_str(), Address.c_str(), pRemoteCfg, enmSpeed, aMaskedIfs,
+ aCaptureFilename.isEmpty() ? NULL : aCaptureFilename.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ /* Create a OUSBDevice and add it to the device list */
+ ComObjPtr<OUSBDevice> pUSBDevice;
+ pUSBDevice.createObject();
+ hrc = pUSBDevice->init(aHostDevice);
+ AssertComRC(hrc);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mUSBDevices.push_back(pUSBDevice);
+ LogFlowFunc(("Attached device {%RTuuid}\n", pUSBDevice->i_id().raw()));
+
+ /* notify callbacks */
+ alock.release();
+ i_onUSBDeviceStateChange(pUSBDevice, true /* aAttached */, NULL);
+ }
+ else
+ {
+ Log1WarningThisFunc(("Failed to create proxy device for '%s' {%RTuuid} (%Rrc)\n", Address.c_str(), uuid.raw(), vrc));
+ switch (vrc)
+ {
+ case VERR_VUSB_NO_PORTS:
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to attach the USB device. (No available ports on the USB controller)."));
+ break;
+ case VERR_VUSB_USBFS_PERMISSION:
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Not permitted to open the USB device, check usbfs options"));
+ break;
+ default:
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to create a proxy device for the USB device. (Error: %Rrc)"), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * USB device attach callback used by AttachUSBDevice().
+ * Note that AttachUSBDevice() doesn't return until this callback is executed,
+ * so we don't use AutoCaller and don't care about reference counters of
+ * interface pointers passed in.
+ *
+ * @thread EMT
+ * @note Locks the console object for writing.
+ */
+//static
+DECLCALLBACK(int)
+Console::i_usbAttachCallback(Console *that, PUVM pUVM, PCVMMR3VTABLE pVMM, IUSBDevice *aHostDevice, PCRTUUID aUuid,
+ const char *pszBackend, const char *aAddress, PCFGMNODE pRemoteCfg, USBConnectionSpeed_T aEnmSpeed,
+ ULONG aMaskedIfs, const char *pszCaptureFilename)
+{
+ RT_NOREF(aHostDevice);
+ LogFlowFuncEnter();
+ LogFlowFunc(("that={%p} aUuid={%RTuuid}\n", that, aUuid));
+
+ AssertReturn(that && aUuid, VERR_INVALID_PARAMETER);
+ AssertReturn(!that->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ VUSBSPEED enmSpeed = VUSB_SPEED_UNKNOWN;
+ switch (aEnmSpeed)
+ {
+ case USBConnectionSpeed_Low: enmSpeed = VUSB_SPEED_LOW; break;
+ case USBConnectionSpeed_Full: enmSpeed = VUSB_SPEED_FULL; break;
+ case USBConnectionSpeed_High: enmSpeed = VUSB_SPEED_HIGH; break;
+ case USBConnectionSpeed_Super: enmSpeed = VUSB_SPEED_SUPER; break;
+ case USBConnectionSpeed_SuperPlus: enmSpeed = VUSB_SPEED_SUPERPLUS; break;
+ default: AssertFailed(); break;
+ }
+
+ int vrc = pVMM->pfnPDMR3UsbCreateProxyDevice(pUVM, aUuid, pszBackend, aAddress, pRemoteCfg,
+ enmSpeed, aMaskedIfs, pszCaptureFilename);
+ LogFlowFunc(("vrc=%Rrc\n", vrc));
+ LogFlowFuncLeave();
+ return vrc;
+}
+
+/**
+ * Sends a request to VMM to detach the given host device. After this method
+ * succeeds, the detached device will disappear from the mUSBDevices
+ * collection.
+ *
+ * @param aHostDevice device to attach
+ *
+ * @note Synchronously calls EMT.
+ */
+HRESULT Console::i_detachUSBDevice(const ComObjPtr<OUSBDevice> &aHostDevice)
+{
+ AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL);
+
+ /* Get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ /* if the device is attached, then there must at least one USB hub. */
+ AssertReturn(ptrVM.vtable()->pfnPDMR3UsbHasHub(ptrVM.rawUVM()), E_FAIL);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("Detaching USB proxy device {%RTuuid}...\n", aHostDevice->i_id().raw()));
+
+ /*
+ * If this was a remote device, release the backend pointer.
+ * The pointer was requested in usbAttachCallback.
+ */
+ BOOL fRemote = FALSE;
+
+ HRESULT hrc2 = aHostDevice->COMGETTER(Remote)(&fRemote);
+ if (FAILED(hrc2))
+ i_setErrorStatic(hrc2, "GetRemote() failed");
+
+ PCRTUUID pUuid = aHostDevice->i_id().raw();
+ if (fRemote)
+ {
+ Guid guid(*pUuid);
+ i_consoleVRDPServer()->USBBackendReleasePointer(&guid);
+ }
+
+ alock.release();
+ int vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), 0 /* idDstCpu (saved state, see #6232) */,
+ (PFNRT)i_usbDetachCallback, 4,
+ this, ptrVM.rawUVM(), ptrVM.vtable(), pUuid);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowFunc(("Detached device {%RTuuid}\n", pUuid));
+
+ /* notify callbacks */
+ i_onUSBDeviceStateChange(aHostDevice, false /* aAttached */, NULL);
+ }
+
+ ComAssertRCRet(vrc, E_FAIL);
+
+ return S_OK;
+}
+
+/**
+ * USB device detach callback used by DetachUSBDevice().
+ *
+ * Note that DetachUSBDevice() doesn't return until this callback is executed,
+ * so we don't use AutoCaller and don't care about reference counters of
+ * interface pointers passed in.
+ *
+ * @thread EMT
+ */
+//static
+DECLCALLBACK(int)
+Console::i_usbDetachCallback(Console *that, PUVM pUVM, PCVMMR3VTABLE pVMM, PCRTUUID aUuid)
+{
+ LogFlowFuncEnter();
+ LogFlowFunc(("that={%p} aUuid={%RTuuid}\n", that, aUuid));
+
+ AssertReturn(that && aUuid, VERR_INVALID_PARAMETER);
+ AssertReturn(!that->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ int vrc = pVMM->pfnPDMR3UsbDetachDevice(pUVM, aUuid);
+
+ LogFlowFunc(("vrc=%Rrc\n", vrc));
+ LogFlowFuncLeave();
+ return vrc;
+}
+#endif /* VBOX_WITH_USB */
+
+/* Note: FreeBSD needs this whether netflt is used or not. */
+#if ((defined(RT_OS_LINUX) && !defined(VBOX_WITH_NETFLT)) || defined(RT_OS_FREEBSD))
+
+/**
+ * Helper function to handle host interface device creation and attachment.
+ *
+ * @param networkAdapter the network adapter which attachment should be reset
+ * @return COM status code
+ *
+ * @note The caller must lock this object for writing.
+ *
+ * @todo Move this back into the driver!
+ */
+HRESULT Console::i_attachToTapInterface(INetworkAdapter *networkAdapter)
+{
+ LogFlowThisFunc(("\n"));
+ /* sanity check */
+ AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
+
+# ifdef VBOX_STRICT
+ /* paranoia */
+ NetworkAttachmentType_T attachment;
+ networkAdapter->COMGETTER(AttachmentType)(&attachment);
+ Assert(attachment == NetworkAttachmentType_Bridged);
+# endif /* VBOX_STRICT */
+
+ HRESULT rc = S_OK;
+
+ ULONG slot = 0;
+ rc = networkAdapter->COMGETTER(Slot)(&slot);
+ AssertComRC(rc);
+
+# ifdef RT_OS_LINUX
+ /*
+ * Allocate a host interface device
+ */
+ int vrc = RTFileOpen(&maTapFD[slot], "/dev/net/tun",
+ RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_INHERIT);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Set/obtain the tap interface.
+ */
+ struct ifreq IfReq;
+ RT_ZERO(IfReq);
+ /* The name of the TAP interface we are using */
+ Bstr tapDeviceName;
+ rc = networkAdapter->COMGETTER(BridgedInterface)(tapDeviceName.asOutParam());
+ if (FAILED(rc))
+ tapDeviceName.setNull(); /* Is this necessary? */
+ if (tapDeviceName.isEmpty())
+ {
+ LogRel(("No TAP device name was supplied.\n"));
+ rc = setError(E_FAIL, tr("No TAP device name was supplied for the host networking interface"));
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ /* If we are using a static TAP device then try to open it. */
+ Utf8Str str(tapDeviceName);
+ RTStrCopy(IfReq.ifr_name, sizeof(IfReq.ifr_name), str.c_str()); /** @todo bitch about names which are too long... */
+ IfReq.ifr_flags = IFF_TAP | IFF_NO_PI;
+ vrc = ioctl(RTFileToNative(maTapFD[slot]), TUNSETIFF, &IfReq);
+ if (vrc != 0)
+ {
+ LogRel(("Failed to open the host network interface %ls\n", tapDeviceName.raw()));
+ rc = setErrorBoth(E_FAIL, vrc, tr("Failed to open the host network interface %ls"), tapDeviceName.raw());
+ }
+ }
+ if (SUCCEEDED(rc))
+ {
+ /*
+ * Make it pollable.
+ */
+ if (fcntl(RTFileToNative(maTapFD[slot]), F_SETFL, O_NONBLOCK) != -1)
+ {
+ Log(("i_attachToTapInterface: %RTfile %ls\n", maTapFD[slot], tapDeviceName.raw()));
+ /*
+ * Here is the right place to communicate the TAP file descriptor and
+ * the host interface name to the server if/when it becomes really
+ * necessary.
+ */
+ maTAPDeviceName[slot] = tapDeviceName;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ int iErr = errno;
+
+ LogRel(("Configuration error: Failed to configure /dev/net/tun non blocking. Error: %s\n", strerror(iErr)));
+ vrc = VERR_HOSTIF_BLOCKING;
+ rc = setErrorBoth(E_FAIL, vrc, tr("could not set up the host networking device for non blocking access: %s"),
+ strerror(errno));
+ }
+ }
+ }
+ else
+ {
+ LogRel(("Configuration error: Failed to open /dev/net/tun rc=%Rrc\n", vrc));
+ switch (vrc)
+ {
+ case VERR_ACCESS_DENIED:
+ /* will be handled by our caller */
+ rc = E_ACCESSDENIED;
+ break;
+ default:
+ rc = setErrorBoth(E_FAIL, vrc, tr("Could not set up the host networking device: %Rrc"), vrc);
+ break;
+ }
+ }
+
+# elif defined(RT_OS_FREEBSD)
+ /*
+ * Set/obtain the tap interface.
+ */
+ /* The name of the TAP interface we are using */
+ Bstr tapDeviceName;
+ rc = networkAdapter->COMGETTER(BridgedInterface)(tapDeviceName.asOutParam());
+ if (FAILED(rc))
+ tapDeviceName.setNull(); /* Is this necessary? */
+ if (tapDeviceName.isEmpty())
+ {
+ LogRel(("No TAP device name was supplied.\n"));
+ rc = setError(E_FAIL, tr("No TAP device name was supplied for the host networking interface"));
+ }
+ char szTapdev[1024] = "/dev/";
+ /* If we are using a static TAP device then try to open it. */
+ Utf8Str str(tapDeviceName);
+ if (str.length() + strlen(szTapdev) <= sizeof(szTapdev))
+ strcat(szTapdev, str.c_str());
+ else
+ memcpy(szTapdev + strlen(szTapdev), str.c_str(),
+ sizeof(szTapdev) - strlen(szTapdev) - 1); /** @todo bitch about names which are too long... */
+ int vrc = RTFileOpen(&maTapFD[slot], szTapdev,
+ RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_INHERIT | RTFILE_O_NON_BLOCK);
+
+ if (RT_SUCCESS(vrc))
+ maTAPDeviceName[slot] = tapDeviceName;
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_ACCESS_DENIED:
+ /* will be handled by our caller */
+ rc = E_ACCESSDENIED;
+ break;
+ default:
+ rc = setErrorBoth(E_FAIL, vrc, tr("Failed to open the host network interface %ls"), tapDeviceName.raw());
+ break;
+ }
+ }
+# else
+# error "huh?"
+# endif
+ /* in case of failure, cleanup. */
+ if (RT_FAILURE(vrc) && SUCCEEDED(rc))
+ {
+ LogRel(("General failure attaching to host interface\n"));
+ rc = setErrorBoth(E_FAIL, vrc, tr("General failure attaching to host interface"));
+ }
+ LogFlowThisFunc(("rc=%Rhrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Helper function to handle detachment from a host interface
+ *
+ * @param networkAdapter the network adapter which attachment should be reset
+ * @return COM status code
+ *
+ * @note The caller must lock this object for writing.
+ *
+ * @todo Move this back into the driver!
+ */
+HRESULT Console::i_detachFromTapInterface(INetworkAdapter *networkAdapter)
+{
+ /* sanity check */
+ LogFlowThisFunc(("\n"));
+ AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
+
+ HRESULT rc = S_OK;
+# ifdef VBOX_STRICT
+ /* paranoia */
+ NetworkAttachmentType_T attachment;
+ networkAdapter->COMGETTER(AttachmentType)(&attachment);
+ Assert(attachment == NetworkAttachmentType_Bridged);
+# endif /* VBOX_STRICT */
+
+ ULONG slot = 0;
+ rc = networkAdapter->COMGETTER(Slot)(&slot);
+ AssertComRC(rc);
+
+ /* is there an open TAP device? */
+ if (maTapFD[slot] != NIL_RTFILE)
+ {
+ /*
+ * Close the file handle.
+ */
+ Bstr tapDeviceName, tapTerminateApplication;
+ bool isStatic = true;
+ rc = networkAdapter->COMGETTER(BridgedInterface)(tapDeviceName.asOutParam());
+ if (FAILED(rc) || tapDeviceName.isEmpty())
+ {
+ /* If the name is empty, this is a dynamic TAP device, so close it now,
+ so that the termination script can remove the interface. Otherwise we still
+ need the FD to pass to the termination script. */
+ isStatic = false;
+ int rcVBox = RTFileClose(maTapFD[slot]);
+ AssertRC(rcVBox);
+ maTapFD[slot] = NIL_RTFILE;
+ }
+ if (isStatic)
+ {
+ /* If we are using a static TAP device, we close it now, after having called the
+ termination script. */
+ int rcVBox = RTFileClose(maTapFD[slot]);
+ AssertRC(rcVBox);
+ }
+ /* the TAP device name and handle are no longer valid */
+ maTapFD[slot] = NIL_RTFILE;
+ maTAPDeviceName[slot] = "";
+ }
+ LogFlowThisFunc(("returning %d\n", rc));
+ return rc;
+}
+
+#endif /* (RT_OS_LINUX || RT_OS_FREEBSD) && !VBOX_WITH_NETFLT */
+
+/**
+ * Called at power down to terminate host interface networking.
+ *
+ * @note The caller must lock this object for writing.
+ */
+HRESULT Console::i_powerDownHostInterfaces()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* sanity check */
+ AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
+
+ /*
+ * host interface termination handling
+ */
+ HRESULT rc = S_OK;
+ ComPtr<IVirtualBox> pVirtualBox;
+ mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ ComPtr<ISystemProperties> pSystemProperties;
+ if (pVirtualBox)
+ pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ ChipsetType_T chipsetType = ChipsetType_PIIX3;
+ mMachine->COMGETTER(ChipsetType)(&chipsetType);
+ ULONG maxNetworkAdapters = 0;
+ if (pSystemProperties)
+ pSystemProperties->GetMaxNetworkAdapters(chipsetType, &maxNetworkAdapters);
+
+ for (ULONG slot = 0; slot < maxNetworkAdapters; slot++)
+ {
+ ComPtr<INetworkAdapter> pNetworkAdapter;
+ rc = mMachine->GetNetworkAdapter(slot, pNetworkAdapter.asOutParam());
+ if (FAILED(rc)) break;
+
+ BOOL enabled = FALSE;
+ pNetworkAdapter->COMGETTER(Enabled)(&enabled);
+ if (!enabled)
+ continue;
+
+ NetworkAttachmentType_T attachment;
+ pNetworkAdapter->COMGETTER(AttachmentType)(&attachment);
+ if (attachment == NetworkAttachmentType_Bridged)
+ {
+#if ((defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)) && !defined(VBOX_WITH_NETFLT))
+ HRESULT rc2 = i_detachFromTapInterface(pNetworkAdapter);
+ if (FAILED(rc2) && SUCCEEDED(rc))
+ rc = rc2;
+#endif /* (RT_OS_LINUX || RT_OS_FREEBSD) && !VBOX_WITH_NETFLT */
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Process callback handler for VMR3LoadFromFile, VMR3LoadFromStream, VMR3Save
+ * and VMR3Teleport.
+ *
+ * @param pUVM The user mode VM handle.
+ * @param uPercent Completion percentage (0-100).
+ * @param pvUser Pointer to an IProgress instance.
+ * @return VINF_SUCCESS.
+ */
+/*static*/
+DECLCALLBACK(int) Console::i_stateProgressCallback(PUVM pUVM, unsigned uPercent, void *pvUser)
+{
+ IProgress *pProgress = static_cast<IProgress *>(pvUser);
+
+ /* update the progress object */
+ if (pProgress)
+ {
+ ComPtr<IInternalProgressControl> pProgressControl(pProgress);
+ AssertReturn(!!pProgressControl, VERR_INVALID_PARAMETER);
+ pProgressControl->SetCurrentOperationProgress(uPercent);
+ }
+
+ NOREF(pUVM);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @copydoc FNVMATERROR
+ *
+ * @remarks Might be some tiny serialization concerns with access to the string
+ * object here...
+ */
+/*static*/ DECLCALLBACK(void)
+Console::i_genericVMSetErrorCallback(PUVM pUVM, void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list args)
+{
+ RT_SRC_POS_NOREF();
+ Utf8Str *pErrorText = (Utf8Str *)pvUser;
+ AssertPtr(pErrorText);
+
+ /* We ignore RT_SRC_POS_DECL arguments to avoid confusion of end-users. */
+ va_list va2;
+ va_copy(va2, args);
+
+ /* Append to any the existing error message. */
+ try
+ {
+ if (pErrorText->length())
+ pErrorText->appendPrintf(".\n%N (%Rrc)", pszFormat, &va2, rc, rc);
+ else
+ pErrorText->printf("%N (%Rrc)", pszFormat, &va2, rc, rc);
+ }
+ catch (std::bad_alloc &)
+ {
+ }
+
+ va_end(va2);
+
+ NOREF(pUVM);
+}
+
+/**
+ * VM runtime error callback function (FNVMATRUNTIMEERROR).
+ *
+ * See VMSetRuntimeError for the detailed description of parameters.
+ *
+ * @param pUVM The user mode VM handle. Ignored, so passing NULL
+ * is fine.
+ * @param pvUser The user argument, pointer to the Console instance.
+ * @param fFlags The action flags. See VMSETRTERR_FLAGS_*.
+ * @param pszErrorId Error ID string.
+ * @param pszFormat Error message format string.
+ * @param va Error message arguments.
+ * @thread EMT.
+ */
+/* static */ DECLCALLBACK(void)
+Console::i_atVMRuntimeErrorCallback(PUVM pUVM, void *pvUser, uint32_t fFlags,
+ const char *pszErrorId, const char *pszFormat, va_list va)
+{
+ bool const fFatal = !!(fFlags & VMSETRTERR_FLAGS_FATAL);
+ LogFlowFuncEnter();
+
+ Console *that = static_cast<Console *>(pvUser);
+ AssertReturnVoid(that);
+
+ Utf8Str message(pszFormat, va);
+
+ LogRel(("Console: VM runtime error: fatal=%RTbool, errorID=%s message=\"%s\"\n", fFatal, pszErrorId, message.c_str()));
+ try
+ {
+ that->i_onRuntimeError(BOOL(fFatal), Bstr(pszErrorId).raw(), Bstr(message).raw());
+ }
+ catch (std::bad_alloc &)
+ {
+ }
+ LogFlowFuncLeave(); NOREF(pUVM);
+}
+
+/**
+ * Captures USB devices that match filters of the VM.
+ * Called at VM startup.
+ *
+ * @param pUVM The VM handle.
+ */
+HRESULT Console::i_captureUSBDevices(PUVM pUVM)
+{
+ RT_NOREF(pUVM);
+ LogFlowThisFunc(("\n"));
+
+ /* sanity check */
+ AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* If the machine has a USB controller, ask the USB proxy service to
+ * capture devices */
+ if (mfVMHasUsbController)
+ {
+ /* release the lock before calling Host in VBoxSVC since Host may call
+ * us back from under its lock (e.g. onUSBDeviceAttach()) which would
+ * produce an inter-process dead-lock otherwise. */
+ alock.release();
+
+ HRESULT hrc = mControl->AutoCaptureUSBDevices();
+ ComAssertComRCRetRC(hrc);
+ }
+
+ return S_OK;
+}
+
+
+/**
+ * Detach all USB device which are attached to the VM for the
+ * purpose of clean up and such like.
+ */
+void Console::i_detachAllUSBDevices(bool aDone)
+{
+ LogFlowThisFunc(("aDone=%RTbool\n", aDone));
+
+ /* sanity check */
+ AssertReturnVoid(!isWriteLockOnCurrentThread());
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mUSBDevices.clear();
+
+ /* release the lock before calling Host in VBoxSVC since Host may call
+ * us back from under its lock (e.g. onUSBDeviceAttach()) which would
+ * produce an inter-process dead-lock otherwise. */
+ alock.release();
+
+ mControl->DetachAllUSBDevices(aDone);
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+void Console::i_processRemoteUSBDevices(uint32_t u32ClientId, VRDEUSBDEVICEDESC *pDevList, uint32_t cbDevList, bool fDescExt)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("u32ClientId = %d, pDevList=%p, cbDevList = %d, fDescExt = %d\n",
+ u32ClientId, pDevList, cbDevList, fDescExt));
+
+ AutoCaller autoCaller(this);
+ if (!autoCaller.isOk())
+ {
+ /* Console has been already uninitialized, deny request */
+ AssertMsgFailed(("Console is already uninitialized\n"));
+ LogFlowThisFunc(("Console is already uninitialized\n"));
+ LogFlowThisFuncLeave();
+ return;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Mark all existing remote USB devices as dirty.
+ */
+ for (RemoteUSBDeviceList::iterator it = mRemoteUSBDevices.begin();
+ it != mRemoteUSBDevices.end();
+ ++it)
+ {
+ (*it)->dirty(true);
+ }
+
+ /*
+ * Process the pDevList and add devices those are not already in the mRemoteUSBDevices list.
+ */
+ /** @todo (sunlover) REMOTE_USB Strict validation of the pDevList. */
+ VRDEUSBDEVICEDESC *e = pDevList;
+
+ /* The cbDevList condition must be checked first, because the function can
+ * receive pDevList = NULL and cbDevList = 0 on client disconnect.
+ */
+ while (cbDevList >= 2 && e->oNext)
+ {
+ /* Sanitize incoming strings in case they aren't valid UTF-8. */
+ if (e->oManufacturer)
+ RTStrPurgeEncoding((char *)e + e->oManufacturer);
+ if (e->oProduct)
+ RTStrPurgeEncoding((char *)e + e->oProduct);
+ if (e->oSerialNumber)
+ RTStrPurgeEncoding((char *)e + e->oSerialNumber);
+
+ LogFlowThisFunc(("vendor %04x, product %04x, name = %s\n",
+ e->idVendor, e->idProduct, e->oProduct ? (char *)e + e->oProduct : ""));
+
+ bool fNewDevice = true;
+
+ for (RemoteUSBDeviceList::iterator it = mRemoteUSBDevices.begin();
+ it != mRemoteUSBDevices.end();
+ ++it)
+ {
+ if ( (*it)->devId() == e->id
+ && (*it)->clientId() == u32ClientId)
+ {
+ /* The device is already in the list. */
+ (*it)->dirty(false);
+ fNewDevice = false;
+ break;
+ }
+ }
+
+ if (fNewDevice)
+ {
+ LogRel(("Remote USB: ++++ Vendor %04X. Product %04X. Name = [%s].\n",
+ e->idVendor, e->idProduct, e->oProduct? (char *)e + e->oProduct: ""));
+
+ /* Create the device object and add the new device to list. */
+ ComObjPtr<RemoteUSBDevice> pUSBDevice;
+ pUSBDevice.createObject();
+ pUSBDevice->init(u32ClientId, e, fDescExt);
+
+ mRemoteUSBDevices.push_back(pUSBDevice);
+
+ /* Check if the device is ok for current USB filters. */
+ BOOL fMatched = FALSE;
+ ULONG fMaskedIfs = 0;
+ HRESULT hrc = mControl->RunUSBDeviceFilters(pUSBDevice, &fMatched, &fMaskedIfs);
+
+ AssertComRC(hrc);
+
+ LogFlowThisFunc(("USB filters return %d %#x\n", fMatched, fMaskedIfs));
+
+ if (fMatched)
+ {
+ alock.release();
+ hrc = i_onUSBDeviceAttach(pUSBDevice, NULL, fMaskedIfs, Utf8Str());
+ alock.acquire();
+
+ /// @todo (r=dmik) warning reporting subsystem
+
+ if (hrc == S_OK)
+ {
+ LogFlowThisFunc(("Device attached\n"));
+ pUSBDevice->captured(true);
+ }
+ }
+ }
+
+ if (cbDevList < e->oNext)
+ {
+ Log1WarningThisFunc(("cbDevList %d > oNext %d\n", cbDevList, e->oNext));
+ break;
+ }
+
+ cbDevList -= e->oNext;
+
+ e = (VRDEUSBDEVICEDESC *)((uint8_t *)e + e->oNext);
+ }
+
+ /*
+ * Remove dirty devices, that is those which are not reported by the server anymore.
+ */
+ for (;;)
+ {
+ ComObjPtr<RemoteUSBDevice> pUSBDevice;
+
+ RemoteUSBDeviceList::iterator it = mRemoteUSBDevices.begin();
+ while (it != mRemoteUSBDevices.end())
+ {
+ if ((*it)->dirty())
+ {
+ pUSBDevice = *it;
+ break;
+ }
+
+ ++it;
+ }
+
+ if (!pUSBDevice)
+ {
+ break;
+ }
+
+ USHORT vendorId = 0;
+ pUSBDevice->COMGETTER(VendorId)(&vendorId);
+
+ USHORT productId = 0;
+ pUSBDevice->COMGETTER(ProductId)(&productId);
+
+ Bstr product;
+ pUSBDevice->COMGETTER(Product)(product.asOutParam());
+
+ LogRel(("Remote USB: ---- Vendor %04x. Product %04x. Name = [%ls].\n", vendorId, productId, product.raw()));
+
+ /* Detach the device from VM. */
+ if (pUSBDevice->captured())
+ {
+ Bstr uuid;
+ pUSBDevice->COMGETTER(Id)(uuid.asOutParam());
+ alock.release();
+ i_onUSBDeviceDetach(uuid.raw(), NULL);
+ alock.acquire();
+ }
+
+ /* And remove it from the list. */
+ mRemoteUSBDevices.erase(it);
+ }
+
+ LogFlowThisFuncLeave();
+}
+
+
+/**
+ * Worker called by VMPowerUpTask::handler to start the VM (also from saved
+ * state) and track progress.
+ *
+ * @param pTask The power up task.
+ *
+ * @note Locks the Console object for writing.
+ */
+/*static*/
+void Console::i_powerUpThreadTask(VMPowerUpTask *pTask)
+{
+ LogFlowFuncEnter();
+
+ AssertReturnVoid(pTask);
+ AssertReturnVoid(!pTask->mConsole.isNull());
+ AssertReturnVoid(!pTask->mProgress.isNull());
+
+ VirtualBoxBase::initializeComForThread();
+
+ HRESULT rc = S_OK;
+ int vrc = VINF_SUCCESS;
+
+ /* Set up a build identifier so that it can be seen from core dumps what
+ * exact build was used to produce the core. */
+ static char s_szBuildID[48];
+ RTStrPrintf(s_szBuildID, sizeof(s_szBuildID), "%s%s%s%s VirtualBox %s r%u %s%s%s%s",
+ "BU", "IL", "DI", "D", RTBldCfgVersion(), RTBldCfgRevision(), "BU", "IL", "DI", "D");
+
+ ComObjPtr<Console> pConsole = pTask->mConsole;
+
+ /* Note: no need to use AutoCaller because VMPowerUpTask does that */
+
+ /* The lock is also used as a signal from the task initiator (which
+ * releases it only after RTThreadCreate()) that we can start the job */
+ AutoWriteLock alock(pConsole COMMA_LOCKVAL_SRC_POS);
+
+ /* sanity */
+ Assert(pConsole->mpUVM == NULL);
+
+ try
+ {
+ // Create the VMM device object, which starts the HGCM thread; do this only
+ // once for the console, for the pathological case that the same console
+ // object is used to power up a VM twice.
+ if (!pConsole->m_pVMMDev)
+ {
+ pConsole->m_pVMMDev = new VMMDev(pConsole);
+ AssertReturnVoid(pConsole->m_pVMMDev);
+ }
+
+ /* wait for auto reset ops to complete so that we can successfully lock
+ * the attached hard disks by calling LockMedia() below */
+ for (VMPowerUpTask::ProgressList::const_iterator
+ it = pTask->hardDiskProgresses.begin();
+ it != pTask->hardDiskProgresses.end(); ++it)
+ {
+ HRESULT rc2 = (*it)->WaitForCompletion(-1);
+ AssertComRC(rc2);
+
+ rc = pTask->mProgress->SetNextOperation(BstrFmt(tr("Disk Image Reset Operation - Immutable Image")).raw(), 1);
+ AssertComRCReturnVoid(rc);
+ }
+
+ /*
+ * Lock attached media. This method will also check their accessibility.
+ * If we're a teleporter, we'll have to postpone this action so we can
+ * migrate between local processes.
+ *
+ * Note! The media will be unlocked automatically by
+ * SessionMachine::i_setMachineState() when the VM is powered down.
+ */
+ if (!pTask->mTeleporterEnabled)
+ {
+ rc = pConsole->mControl->LockMedia();
+ if (FAILED(rc)) throw rc;
+ }
+
+ /* Create the VRDP server. In case of headless operation, this will
+ * also create the framebuffer, required at VM creation.
+ */
+ ConsoleVRDPServer *server = pConsole->i_consoleVRDPServer();
+ Assert(server);
+
+ /* Does VRDP server call Console from the other thread?
+ * Not sure (and can change), so release the lock just in case.
+ */
+ alock.release();
+ vrc = server->Launch();
+ alock.acquire();
+
+ if (vrc != VINF_SUCCESS)
+ {
+ Utf8Str errMsg = pConsole->VRDPServerErrorToMsg(vrc);
+ if ( RT_FAILURE(vrc)
+ && vrc != VERR_NET_ADDRESS_IN_USE) /* not fatal */
+ throw i_setErrorStaticBoth(E_FAIL, vrc, errMsg.c_str());
+ }
+
+ ComPtr<IMachine> pMachine = pConsole->i_machine();
+ ULONG cCpus = 1;
+ pMachine->COMGETTER(CPUCount)(&cCpus);
+
+ VMProcPriority_T enmVMPriority = VMProcPriority_Default;
+ pMachine->COMGETTER(VMProcessPriority)(&enmVMPriority);
+
+ /*
+ * Create the VM
+ *
+ * Note! Release the lock since EMT will call Console. It's safe because
+ * mMachineState is either Starting or Restoring state here.
+ */
+ alock.release();
+
+ if (enmVMPriority != VMProcPriority_Default)
+ pConsole->i_onVMProcessPriorityChange(enmVMPriority);
+
+ PCVMMR3VTABLE pVMM = pConsole->mpVMM;
+ PVM pVM = NULL;
+ vrc = pVMM->pfnVMR3Create(cCpus,
+ pConsole->mpVmm2UserMethods,
+ Console::i_genericVMSetErrorCallback,
+ &pTask->mErrorMsg,
+ pTask->mpfnConfigConstructor,
+ static_cast<Console *>(pConsole),
+ &pVM, NULL);
+ alock.acquire();
+ if (RT_SUCCESS(vrc))
+ {
+ do /* break "loop" */
+ {
+ /*
+ * Register our load/save state file handlers
+ */
+ vrc = pVMM->pfnSSMR3RegisterExternal(pConsole->mpUVM, sSSMConsoleUnit, 0 /*iInstance*/,
+ CONSOLE_SAVED_STATE_VERSION, 0 /* cbGuess */,
+ NULL, NULL, NULL,
+ NULL, i_saveStateFileExec, NULL,
+ NULL, i_loadStateFileExec, NULL,
+ static_cast<Console *>(pConsole));
+ AssertRCBreak(vrc);
+
+ vrc = static_cast<Console *>(pConsole)->i_getDisplay()->i_registerSSM(pConsole->mpUVM);
+ AssertRC(vrc);
+ if (RT_FAILURE(vrc))
+ break;
+
+ /*
+ * Synchronize debugger settings
+ */
+ MachineDebugger *machineDebugger = pConsole->i_getMachineDebugger();
+ if (machineDebugger)
+ machineDebugger->i_flushQueuedSettings();
+
+ /*
+ * Shared Folders
+ */
+ if (pConsole->m_pVMMDev->isShFlActive())
+ {
+ /* Does the code below call Console from the other thread?
+ * Not sure, so release the lock just in case. */
+ alock.release();
+
+ for (SharedFolderDataMap::const_iterator it = pTask->mSharedFolders.begin();
+ it != pTask->mSharedFolders.end();
+ ++it)
+ {
+ const SharedFolderData &d = it->second;
+ rc = pConsole->i_createSharedFolder(it->first, d);
+ if (FAILED(rc))
+ {
+ ErrorInfoKeeper eik;
+ pConsole->i_atVMRuntimeErrorCallbackF(0, "BrokenSharedFolder",
+ N_("The shared folder '%s' could not be set up: %ls.\n"
+ "The shared folder setup will not be complete. It is recommended to power down the virtual "
+ "machine and fix the shared folder settings while the machine is not running"),
+ it->first.c_str(), eik.getText().raw());
+ }
+ }
+ if (FAILED(rc))
+ rc = S_OK; // do not fail with broken shared folders
+
+ /* acquire the lock again */
+ alock.acquire();
+ }
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ /*
+ * Attach the VRDE audio driver.
+ */
+ if (pConsole->i_getVRDEServer())
+ {
+ BOOL fVRDEEnabled = FALSE;
+ rc = pConsole->i_getVRDEServer()->COMGETTER(Enabled)(&fVRDEEnabled);
+ AssertComRCBreak(rc, RT_NOTHING);
+
+ if ( fVRDEEnabled
+ && pConsole->mAudioVRDE)
+ pConsole->mAudioVRDE->doAttachDriverViaEmt(pConsole->mpUVM, pVMM, &alock);
+ }
+#endif
+
+ /*
+ * Enable client connections to the VRDP server.
+ */
+ pConsole->i_consoleVRDPServer()->EnableConnections();
+
+#ifdef VBOX_WITH_RECORDING
+ /*
+ * Enable recording if configured.
+ */
+ BOOL fRecordingEnabled = FALSE;
+ {
+ ComPtr<IRecordingSettings> ptrRecordingSettings;
+ rc = pConsole->mMachine->COMGETTER(RecordingSettings)(ptrRecordingSettings.asOutParam());
+ AssertComRCBreak(rc, RT_NOTHING);
+
+ rc = ptrRecordingSettings->COMGETTER(Enabled)(&fRecordingEnabled);
+ AssertComRCBreak(rc, RT_NOTHING);
+ }
+ if (fRecordingEnabled)
+ {
+ vrc = pConsole->i_recordingEnable(fRecordingEnabled, &alock);
+ if (RT_SUCCESS(vrc))
+ ::FireRecordingChangedEvent(pConsole->mEventSource);
+ else
+ {
+ LogRel(("Recording: Failed with %Rrc on VM power up\n", vrc));
+ vrc = VINF_SUCCESS; /* do not fail with broken recording */
+ }
+ }
+#endif
+
+ /* release the lock before a lengthy operation */
+ alock.release();
+
+ /*
+ * Capture USB devices.
+ */
+ rc = pConsole->i_captureUSBDevices(pConsole->mpUVM);
+ if (FAILED(rc))
+ {
+ alock.acquire();
+ break;
+ }
+
+ /*
+ * Load saved state?
+ */
+ if (pTask->mSavedStateFile.length())
+ {
+ LogFlowFunc(("Restoring saved state from '%s'...\n", pTask->mSavedStateFile.c_str()));
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ SsmStream ssmStream(pConsole, pVMM, pTask->m_pKeyStore, pTask->mKeyId, pTask->mKeyStore);
+
+ vrc = ssmStream.open(pTask->mSavedStateFile.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ PCSSMSTRMOPS pStreamOps;
+ void *pvStreamOpsUser;
+
+ vrc = ssmStream.querySsmStrmOps(&pStreamOps, &pvStreamOpsUser);
+ if (RT_SUCCESS(vrc))
+ vrc = pVMM->pfnVMR3LoadFromStream(pConsole->mpUVM,
+ pStreamOps, pvStreamOpsUser,
+ Console::i_stateProgressCallback,
+ static_cast<IProgress *>(pTask->mProgress),
+ false /*fTeleporting*/);
+ }
+#else
+ vrc = pVMM->pfnVMR3LoadFromFile(pConsole->mpUVM,
+ pTask->mSavedStateFile.c_str(),
+ Console::i_stateProgressCallback,
+ static_cast<IProgress *>(pTask->mProgress));
+#endif
+ if (RT_SUCCESS(vrc))
+ {
+ if (pTask->mStartPaused)
+ /* done */
+ pConsole->i_setMachineState(MachineState_Paused);
+ else
+ {
+ /* Start/Resume the VM execution */
+#ifdef VBOX_WITH_EXTPACK
+ vrc = pConsole->mptrExtPackManager->i_callAllVmPowerOnHooks(pConsole, pVM, pVMM);
+#endif
+ if (RT_SUCCESS(vrc))
+ vrc = pVMM->pfnVMR3Resume(pConsole->mpUVM, VMRESUMEREASON_STATE_RESTORED);
+ AssertLogRelRC(vrc);
+ }
+ }
+
+ /* Power off in case we failed loading or resuming the VM */
+ if (RT_FAILURE(vrc))
+ {
+ int vrc2 = pVMM->pfnVMR3PowerOff(pConsole->mpUVM); AssertLogRelRC(vrc2);
+#ifdef VBOX_WITH_EXTPACK
+ pConsole->mptrExtPackManager->i_callAllVmPowerOffHooks(pConsole, pVM, pVMM);
+#endif
+ }
+ }
+ else if (pTask->mTeleporterEnabled)
+ {
+ /* -> ConsoleImplTeleporter.cpp */
+ bool fPowerOffOnFailure;
+ rc = pConsole->i_teleporterTrg(pConsole->mpUVM, pConsole->mpVMM, pMachine, &pTask->mErrorMsg,
+ pTask->mStartPaused, pTask->mProgress, &fPowerOffOnFailure);
+ if (FAILED(rc) && fPowerOffOnFailure)
+ {
+ ErrorInfoKeeper eik;
+ int vrc2 = pVMM->pfnVMR3PowerOff(pConsole->mpUVM); AssertLogRelRC(vrc2);
+#ifdef VBOX_WITH_EXTPACK
+ pConsole->mptrExtPackManager->i_callAllVmPowerOffHooks(pConsole, pVM, pVMM);
+#endif
+ }
+ }
+ else if (pTask->mStartPaused)
+ /* done */
+ pConsole->i_setMachineState(MachineState_Paused);
+ else
+ {
+ /* Power on the VM (i.e. start executing) */
+#ifdef VBOX_WITH_EXTPACK
+ vrc = pConsole->mptrExtPackManager->i_callAllVmPowerOnHooks(pConsole, pVM, pVMM);
+#endif
+ if (RT_SUCCESS(vrc))
+ vrc = pVMM->pfnVMR3PowerOn(pConsole->mpUVM);
+ AssertLogRelRC(vrc);
+ }
+
+ /* acquire the lock again */
+ alock.acquire();
+ }
+ while (0);
+
+ /* On failure, destroy the VM */
+ if (FAILED(rc) || RT_FAILURE(vrc))
+ {
+ /* preserve existing error info */
+ ErrorInfoKeeper eik;
+
+ /* powerDown() will call VMR3Destroy() and do all necessary
+ * cleanup (VRDP, USB devices) */
+ alock.release();
+ HRESULT rc2 = pConsole->i_powerDown();
+ alock.acquire();
+ AssertComRC(rc2);
+ }
+ else
+ {
+ /*
+ * Deregister the VMSetError callback. This is necessary as the
+ * pfnVMAtError() function passed to VMR3Create() is supposed to
+ * be sticky but our error callback isn't.
+ */
+ alock.release();
+ pVMM->pfnVMR3AtErrorDeregister(pConsole->mpUVM, Console::i_genericVMSetErrorCallback, &pTask->mErrorMsg);
+ /** @todo register another VMSetError callback? */
+ alock.acquire();
+ }
+ }
+ else
+ {
+ /*
+ * If VMR3Create() failed it has released the VM memory.
+ */
+ if (pConsole->m_pVMMDev)
+ {
+ alock.release(); /* just to be on the safe side... */
+ pConsole->m_pVMMDev->hgcmShutdown(true /*fUvmIsInvalid*/);
+ alock.acquire();
+ }
+ pVMM->pfnVMR3ReleaseUVM(pConsole->mpUVM);
+ pConsole->mpUVM = NULL;
+ }
+
+ if (SUCCEEDED(rc) && RT_FAILURE(vrc))
+ {
+ /* If VMR3Create() or one of the other calls in this function fail,
+ * an appropriate error message has been set in pTask->mErrorMsg.
+ * However since that happens via a callback, the rc status code in
+ * this function is not updated.
+ */
+ if (!pTask->mErrorMsg.length())
+ {
+ /* If the error message is not set but we've got a failure,
+ * convert the VBox status code into a meaningful error message.
+ * This becomes unused once all the sources of errors set the
+ * appropriate error message themselves.
+ */
+ AssertMsgFailed(("Missing error message during powerup for status code %Rrc\n", vrc));
+ pTask->mErrorMsg = Utf8StrFmt(tr("Failed to start VM execution (%Rrc)"), vrc);
+ }
+
+ /* Set the error message as the COM error.
+ * Progress::notifyComplete() will pick it up later. */
+ throw i_setErrorStaticBoth(E_FAIL, vrc, pTask->mErrorMsg.c_str());
+ }
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if ( pConsole->mMachineState == MachineState_Starting
+ || pConsole->mMachineState == MachineState_Restoring
+ || pConsole->mMachineState == MachineState_TeleportingIn
+ )
+ {
+ /* We are still in the Starting/Restoring state. This means one of:
+ *
+ * 1) we failed before VMR3Create() was called;
+ * 2) VMR3Create() failed.
+ *
+ * In both cases, there is no need to call powerDown(), but we still
+ * need to go back to the PoweredOff/Saved state. Reuse
+ * vmstateChangeCallback() for that purpose.
+ */
+
+ /* preserve existing error info */
+ ErrorInfoKeeper eik;
+
+ Assert(pConsole->mpUVM == NULL);
+ i_vmstateChangeCallback(NULL, pConsole->mpVMM, VMSTATE_TERMINATED, VMSTATE_CREATING, pConsole);
+ }
+
+ /*
+ * Evaluate the final result. Note that the appropriate mMachineState value
+ * is already set by vmstateChangeCallback() in all cases.
+ */
+
+ /* release the lock, don't need it any more */
+ alock.release();
+
+ if (SUCCEEDED(rc))
+ {
+ /* Notify the progress object of the success */
+ pTask->mProgress->i_notifyComplete(S_OK);
+ }
+ else
+ {
+ /* The progress object will fetch the current error info */
+ pTask->mProgress->i_notifyComplete(rc);
+ LogRel(("Power up failed (vrc=%Rrc, rc=%Rhrc (%#08X))\n", vrc, rc, rc));
+ }
+
+ /* Notify VBoxSVC and any waiting openRemoteSession progress object. */
+ pConsole->mControl->EndPowerUp(rc);
+
+#if defined(RT_OS_WINDOWS)
+ /* uninitialize COM */
+ CoUninitialize();
+#endif
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * Reconfigures a medium attachment (part of taking or deleting an online snapshot).
+ *
+ * @param pThis Reference to the console object.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pcszDevice The name of the controller type.
+ * @param uInstance The instance of the controller.
+ * @param enmBus The storage bus type of the controller.
+ * @param fUseHostIOCache Use the host I/O cache (disable async I/O).
+ * @param fBuiltinIOCache Use the builtin I/O cache.
+ * @param fInsertDiskIntegrityDrv Flag whether to insert the disk integrity driver into the chain
+ * for additionalk debugging aids.
+ * @param fSetupMerge Whether to set up a medium merge
+ * @param uMergeSource Merge source image index
+ * @param uMergeTarget Merge target image index
+ * @param aMediumAtt The medium attachment.
+ * @param aMachineState The current machine state.
+ * @param phrc Where to store com error - only valid if we return VERR_GENERAL_FAILURE.
+ * @return VBox status code.
+ */
+/* static */
+DECLCALLBACK(int) Console::i_reconfigureMediumAttachment(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ bool fUseHostIOCache,
+ bool fBuiltinIOCache,
+ bool fInsertDiskIntegrityDrv,
+ bool fSetupMerge,
+ unsigned uMergeSource,
+ unsigned uMergeTarget,
+ IMediumAttachment *aMediumAtt,
+ MachineState_T aMachineState,
+ HRESULT *phrc)
+{
+ LogFlowFunc(("pUVM=%p aMediumAtt=%p phrc=%p\n", pUVM, aMediumAtt, phrc));
+
+ HRESULT hrc;
+ Bstr bstr;
+ *phrc = S_OK;
+#define H() do { if (FAILED(hrc)) { AssertMsgFailed(("hrc=%Rhrc (%#x)\n", hrc, hrc)); *phrc = hrc; return VERR_GENERAL_FAILURE; } } while (0)
+
+ /* Ignore attachments other than hard disks, since at the moment they are
+ * not subject to snapshotting in general. */
+ DeviceType_T lType;
+ hrc = aMediumAtt->COMGETTER(Type)(&lType); H();
+ if (lType != DeviceType_HardDisk)
+ return VINF_SUCCESS;
+
+ /* Update the device instance configuration. */
+ int rc = pThis->i_configMediumAttachment(pcszDevice,
+ uInstance,
+ enmBus,
+ fUseHostIOCache,
+ fBuiltinIOCache,
+ fInsertDiskIntegrityDrv,
+ fSetupMerge,
+ uMergeSource,
+ uMergeTarget,
+ aMediumAtt,
+ aMachineState,
+ phrc,
+ true /* fAttachDetach */,
+ false /* fForceUnmount */,
+ false /* fHotplug */,
+ pUVM,
+ pVMM,
+ NULL /* paLedDevType */,
+ NULL /* ppLunL0)*/);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("rc=%Rrc\n", rc));
+ return rc;
+ }
+
+#undef H
+
+ LogFlowFunc(("Returns success\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Thread for powering down the Console.
+ *
+ * @param pTask The power down task.
+ *
+ * @note Locks the Console object for writing.
+ */
+/*static*/
+void Console::i_powerDownThreadTask(VMPowerDownTask *pTask)
+{
+ int rc = VINF_SUCCESS; /* only used in assertion */
+ LogFlowFuncEnter();
+ try
+ {
+ if (pTask->isOk() == false)
+ rc = VERR_GENERAL_FAILURE;
+
+ const ComObjPtr<Console> &that = pTask->mConsole;
+
+ /* Note: no need to use AutoCaller to protect Console because VMTask does
+ * that */
+
+ /* wait until the method tat started us returns */
+ AutoWriteLock thatLock(that COMMA_LOCKVAL_SRC_POS);
+
+ /* release VM caller to avoid the powerDown() deadlock */
+ pTask->releaseVMCaller();
+
+ thatLock.release();
+
+ that->i_powerDown(pTask->mServerProgress);
+
+ /* complete the operation */
+ that->mControl->EndPoweringDown(S_OK, Bstr().raw());
+
+ }
+ catch (const std::exception &e)
+ {
+ AssertMsgFailed(("Exception %s was caught, rc=%Rrc\n", e.what(), rc));
+ NOREF(e); NOREF(rc);
+ }
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnSaveState}
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_vmm2User_SaveState(PCVMM2USERMETHODS pThis, PUVM pUVM)
+{
+ Console *pConsole = ((MYVMM2USERMETHODS *)pThis)->pConsole;
+ NOREF(pUVM);
+
+ /*
+ * For now, just call SaveState. We should probably try notify the GUI so
+ * it can pop up a progress object and stuff. The progress object created
+ * by the call isn't returned to anyone and thus gets updated without
+ * anyone noticing it.
+ */
+ ComPtr<IProgress> pProgress;
+ HRESULT hrc = pConsole->mMachine->SaveState(pProgress.asOutParam());
+ return SUCCEEDED(hrc) ? VINF_SUCCESS : Global::vboxStatusCodeFromCOM(hrc);
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnNotifyEmtInit}
+ */
+/*static*/ DECLCALLBACK(void)
+Console::i_vmm2User_NotifyEmtInit(PCVMM2USERMETHODS pThis, PUVM pUVM, PUVMCPU pUVCpu)
+{
+ NOREF(pThis); NOREF(pUVM); NOREF(pUVCpu);
+ VirtualBoxBase::initializeComForThread();
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnNotifyEmtTerm}
+ */
+/*static*/ DECLCALLBACK(void)
+Console::i_vmm2User_NotifyEmtTerm(PCVMM2USERMETHODS pThis, PUVM pUVM, PUVMCPU pUVCpu)
+{
+ NOREF(pThis); NOREF(pUVM); NOREF(pUVCpu);
+ VirtualBoxBase::uninitializeComForThread();
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnNotifyPdmtInit}
+ */
+/*static*/ DECLCALLBACK(void)
+Console::i_vmm2User_NotifyPdmtInit(PCVMM2USERMETHODS pThis, PUVM pUVM)
+{
+ NOREF(pThis); NOREF(pUVM);
+ VirtualBoxBase::initializeComForThread();
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnNotifyPdmtTerm}
+ */
+/*static*/ DECLCALLBACK(void)
+Console::i_vmm2User_NotifyPdmtTerm(PCVMM2USERMETHODS pThis, PUVM pUVM)
+{
+ NOREF(pThis); NOREF(pUVM);
+ VirtualBoxBase::uninitializeComForThread();
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnNotifyResetTurnedIntoPowerOff}
+ */
+/*static*/ DECLCALLBACK(void)
+Console::i_vmm2User_NotifyResetTurnedIntoPowerOff(PCVMM2USERMETHODS pThis, PUVM pUVM)
+{
+ Console *pConsole = ((MYVMM2USERMETHODS *)pThis)->pConsole;
+ NOREF(pUVM);
+
+ pConsole->mfPowerOffCausedByReset = true;
+}
+
+/**
+ * Internal function to get LED set off of Console instance
+ *
+ * @returns pointer to PDMLED object
+ *
+ * @param iLedSet Index of LED set to fetch
+ */
+PPDMLED *
+Console::i_getLedSet(uint32_t iLedSet)
+{
+ AssertReturn(iLedSet < RT_ELEMENTS(maLedSets), NULL);
+ return maLedSets[iLedSet].papLeds;
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnQueryGenericObject}
+ */
+/*static*/ DECLCALLBACK(void *)
+Console::i_vmm2User_QueryGenericObject(PCVMM2USERMETHODS pThis, PUVM pUVM, PCRTUUID pUuid)
+{
+ Console *pConsole = ((MYVMM2USERMETHODS *)pThis)->pConsole;
+ NOREF(pUVM);
+
+ /* To simplify comparison we copy the UUID into a com::Guid object. */
+ com::Guid const UuidCopy(*pUuid);
+
+ if (UuidCopy == COM_IIDOF(IConsole))
+ {
+ IConsole *pIConsole = static_cast<IConsole *>(pConsole);
+ return pIConsole;
+ }
+
+ if (UuidCopy == COM_IIDOF(IMachine))
+ {
+ IMachine *pIMachine = pConsole->mMachine;
+ return pIMachine;
+ }
+
+ if (UuidCopy == COM_IIDOF(IKeyboard))
+ {
+ IKeyboard *pIKeyboard = pConsole->mKeyboard;
+ return pIKeyboard;
+ }
+
+ if (UuidCopy == COM_IIDOF(IMouse))
+ {
+ IMouse *pIMouse = pConsole->mMouse;
+ return pIMouse;
+ }
+
+ if (UuidCopy == COM_IIDOF(IDisplay))
+ {
+ IDisplay *pIDisplay = pConsole->mDisplay;
+ return pIDisplay;
+ }
+
+ if (UuidCopy == COM_IIDOF(INvramStore))
+ {
+ NvramStore *pNvramStore = static_cast<NvramStore *>(pConsole->mptrNvramStore);
+ return pNvramStore;
+ }
+
+ if (UuidCopy == VMMDEV_OID)
+ return pConsole->m_pVMMDev;
+
+ if (UuidCopy == USBCARDREADER_OID)
+ return pConsole->mUsbCardReader;
+
+ if (UuidCopy == COM_IIDOF(ISnapshot))
+ return ((MYVMM2USERMETHODS *)pThis)->pISnapshot;
+
+ if (UuidCopy == REMOTEUSBIF_OID)
+ return &pConsole->mRemoteUsbIf;
+
+ if (UuidCopy == EMULATEDUSBIF_OID)
+ return pConsole->mEmulatedUSB->i_getEmulatedUsbIf();
+
+ return NULL;
+}
+
+
+/**
+ * @interface_method_impl{PDMISECKEY,pfnKeyRetain}
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_pdmIfSecKey_KeyRetain(PPDMISECKEY pInterface, const char *pszId, const uint8_t **ppbKey, size_t *pcbKey)
+{
+ Console *pConsole = ((MYPDMISECKEY *)pInterface)->pConsole;
+
+ AutoReadLock thatLock(pConsole COMMA_LOCKVAL_SRC_POS);
+ SecretKey *pKey = NULL;
+
+ int rc = pConsole->m_pKeyStore->retainSecretKey(Utf8Str(pszId), &pKey);
+ if (RT_SUCCESS(rc))
+ {
+ *ppbKey = (const uint8_t *)pKey->getKeyBuffer();
+ *pcbKey = pKey->getKeySize();
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMISECKEY,pfnKeyRelease}
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_pdmIfSecKey_KeyRelease(PPDMISECKEY pInterface, const char *pszId)
+{
+ Console *pConsole = ((MYPDMISECKEY *)pInterface)->pConsole;
+
+ AutoReadLock thatLock(pConsole COMMA_LOCKVAL_SRC_POS);
+ return pConsole->m_pKeyStore->releaseSecretKey(Utf8Str(pszId));
+}
+
+/**
+ * @interface_method_impl{PDMISECKEY,pfnPasswordRetain}
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_pdmIfSecKey_PasswordRetain(PPDMISECKEY pInterface, const char *pszId, const char **ppszPassword)
+{
+ Console *pConsole = ((MYPDMISECKEY *)pInterface)->pConsole;
+
+ AutoReadLock thatLock(pConsole COMMA_LOCKVAL_SRC_POS);
+ SecretKey *pKey = NULL;
+
+ int rc = pConsole->m_pKeyStore->retainSecretKey(Utf8Str(pszId), &pKey);
+ if (RT_SUCCESS(rc))
+ *ppszPassword = (const char *)pKey->getKeyBuffer();
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMISECKEY,pfnPasswordRelease}
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_pdmIfSecKey_PasswordRelease(PPDMISECKEY pInterface, const char *pszId)
+{
+ Console *pConsole = ((MYPDMISECKEY *)pInterface)->pConsole;
+
+ AutoReadLock thatLock(pConsole COMMA_LOCKVAL_SRC_POS);
+ return pConsole->m_pKeyStore->releaseSecretKey(Utf8Str(pszId));
+}
+
+/**
+ * @interface_method_impl{PDMISECKEYHLP,pfnKeyMissingNotify}
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_pdmIfSecKeyHlp_KeyMissingNotify(PPDMISECKEYHLP pInterface)
+{
+ Console *pConsole = ((MYPDMISECKEYHLP *)pInterface)->pConsole;
+
+ /* Set guest property only, the VM is paused in the media driver calling us. */
+ pConsole->mMachine->DeleteGuestProperty(Bstr("/VirtualBox/HostInfo/DekMissing").raw());
+ pConsole->mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/DekMissing").raw(),
+ Bstr("1").raw(), Bstr("RDONLYGUEST").raw());
+ pConsole->mMachine->SaveSettings();
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * The Main status driver instance data.
+ */
+typedef struct DRVMAINSTATUS
+{
+ /** The LED connectors. */
+ PDMILEDCONNECTORS ILedConnectors;
+ /** Pointer to the LED ports interface above us. */
+ PPDMILEDPORTS pLedPorts;
+ /** Pointer to the array of LED pointers. */
+ PPDMLED *papLeds;
+ /** The unit number corresponding to the first entry in the LED array. */
+ uint32_t iFirstLUN;
+ /** The unit number corresponding to the last entry in the LED array.
+ * (The size of the LED array is iLastLUN - iFirstLUN + 1.) */
+ uint32_t iLastLUN;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** The Media Notify interface. */
+ PDMIMEDIANOTIFY IMediaNotify;
+ /** Set if there potentially are medium attachments. */
+ bool fHasMediumAttachments;
+ /** Device name+instance for mapping */
+ char *pszDeviceInstance;
+ /** Pointer to the Console object, for driver triggered activities. */
+ Console *pConsole;
+} DRVMAINSTATUS, *PDRVMAINSTATUS;
+
+
+/**
+ * Notification about a unit which have been changed.
+ *
+ * The driver must discard any pointers to data owned by
+ * the unit and requery it.
+ *
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param iLUN The unit number.
+ */
+DECLCALLBACK(void) Console::i_drvStatus_UnitChanged(PPDMILEDCONNECTORS pInterface, unsigned iLUN)
+{
+ PDRVMAINSTATUS pThis = RT_FROM_MEMBER(pInterface, DRVMAINSTATUS, ILedConnectors);
+ if (iLUN >= pThis->iFirstLUN && iLUN <= pThis->iLastLUN)
+ {
+ PPDMLED pLed;
+ int rc = pThis->pLedPorts->pfnQueryStatusLed(pThis->pLedPorts, iLUN, &pLed);
+ /*
+ * pLed now points directly to the per-unit struct PDMLED field
+ * inside the target device struct owned by the hardware driver.
+ */
+ if (RT_FAILURE(rc))
+ pLed = NULL;
+ ASMAtomicWritePtr(&pThis->papLeds[iLUN - pThis->iFirstLUN], pLed);
+ /*
+ * papLeds[] points to the struct PDMLED of each of this driver's
+ * units. The entries are initialized here, called out of a loop
+ * in Console::i_drvStatus_Construct(), which previously called
+ * Console::i_attachStatusDriver() to allocate the array itself.
+ *
+ * The arrays (and thus individual LEDs) are eventually read out
+ * by Console::getDeviceActivity(), which is itself called from
+ * src/VBox/Frontends/VirtualBox/src/runtime/UIIndicatorsPool.cpp
+ */
+ Log(("drvStatus_UnitChanged: iLUN=%d pLed=%p\n", iLUN, pLed));
+ }
+}
+
+
+/**
+ * Notification about a medium eject.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param uLUN The unit number.
+ */
+DECLCALLBACK(int) Console::i_drvStatus_MediumEjected(PPDMIMEDIANOTIFY pInterface, unsigned uLUN)
+{
+ PDRVMAINSTATUS pThis = RT_FROM_MEMBER(pInterface, DRVMAINSTATUS, IMediaNotify);
+ LogFunc(("uLUN=%d\n", uLUN));
+ if (pThis->fHasMediumAttachments)
+ {
+ Console * const pConsole = pThis->pConsole;
+ AutoWriteLock alock(pConsole COMMA_LOCKVAL_SRC_POS);
+
+ ComPtr<IMediumAttachment> pMediumAtt;
+ Utf8Str devicePath = Utf8StrFmt("%s/LUN#%u", pThis->pszDeviceInstance, uLUN);
+ Console::MediumAttachmentMap::const_iterator end = pConsole->mapMediumAttachments.end();
+ Console::MediumAttachmentMap::const_iterator it = pConsole->mapMediumAttachments.find(devicePath);
+ if (it != end)
+ pMediumAtt = it->second;
+ Assert(!pMediumAtt.isNull());
+ if (!pMediumAtt.isNull())
+ {
+ IMedium *pMedium = NULL;
+ HRESULT rc = pMediumAtt->COMGETTER(Medium)(&pMedium);
+ AssertComRC(rc);
+ if (SUCCEEDED(rc) && pMedium)
+ {
+ BOOL fHostDrive = FALSE;
+ rc = pMedium->COMGETTER(HostDrive)(&fHostDrive);
+ AssertComRC(rc);
+ if (!fHostDrive)
+ {
+ alock.release();
+
+ ComPtr<IMediumAttachment> pNewMediumAtt;
+ rc = pThis->pConsole->mControl->EjectMedium(pMediumAtt, pNewMediumAtt.asOutParam());
+ if (SUCCEEDED(rc))
+ {
+ pThis->pConsole->mMachine->SaveSettings();
+ ::FireMediumChangedEvent(pThis->pConsole->mEventSource, pNewMediumAtt);
+ }
+
+ alock.acquire();
+ if (pNewMediumAtt != pMediumAtt)
+ {
+ pConsole->mapMediumAttachments.erase(devicePath);
+ pConsole->mapMediumAttachments.insert(std::make_pair(devicePath, pNewMediumAtt));
+ }
+ }
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) Console::i_drvStatus_QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINSTATUS pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINSTATUS);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDCONNECTORS, &pThis->ILedConnectors);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIANOTIFY, &pThis->IMediaNotify);
+ return NULL;
+}
+
+
+/**
+ * Destruct a status driver instance.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The driver instance data.
+ */
+DECLCALLBACK(void) Console::i_drvStatus_Destruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVMAINSTATUS pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINSTATUS);
+ LogFlowFunc(("iInstance=%d\n", pDrvIns->iInstance));
+
+ if (pThis->papLeds)
+ {
+ unsigned iLed = pThis->iLastLUN - pThis->iFirstLUN + 1;
+ while (iLed-- > 0)
+ ASMAtomicWriteNullPtr(&pThis->papLeds[iLed]);
+ }
+}
+
+
+/**
+ * Construct a status driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+DECLCALLBACK(int) Console::i_drvStatus_Construct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVMAINSTATUS pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINSTATUS);
+ LogFlowFunc(("iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Initialize data.
+ */
+ com::Guid ConsoleUuid(COM_IIDOF(IConsole));
+ IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw());
+ AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3);
+ Console *pConsole = static_cast<Console *>(pIConsole);
+ AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3);
+
+ pDrvIns->IBase.pfnQueryInterface = Console::i_drvStatus_QueryInterface;
+ pThis->ILedConnectors.pfnUnitChanged = Console::i_drvStatus_UnitChanged;
+ pThis->IMediaNotify.pfnEjected = Console::i_drvStatus_MediumEjected;
+ pThis->pDrvIns = pDrvIns;
+ pThis->pConsole = pConsole;
+ pThis->fHasMediumAttachments = false;
+ pThis->papLeds = NULL;
+ pThis->pszDeviceInstance = NULL;
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns,
+ "DeviceInstance|"
+ "iLedSet|"
+ "HasMediumAttachments|"
+ "First|"
+ "Last",
+ "");
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * Read config.
+ */
+ PCPDMDRVHLPR3 const pHlp = pDrvIns->pHlpR3;
+
+ uint32_t iLedSet;
+ int rc = pHlp->pfnCFGMQueryU32(pCfg, "iLedSet", &iLedSet);
+ AssertLogRelMsgRCReturn(rc, ("Configuration error: Failed to query the \"iLedSet\" value! rc=%Rrc\n", rc), rc);
+ pThis->papLeds = pConsole->i_getLedSet(iLedSet);
+
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "HasMediumAttachments", &pThis->fHasMediumAttachments, false);
+ AssertLogRelMsgRCReturn(rc, ("Configuration error: Failed to query the \"HasMediumAttachments\" value! rc=%Rrc\n", rc), rc);
+
+ if (pThis->fHasMediumAttachments)
+ {
+ rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "DeviceInstance", &pThis->pszDeviceInstance);
+ AssertLogRelMsgRCReturn(rc, ("Configuration error: Failed to query the \"DeviceInstance\" value! rc=%Rrc\n", rc), rc);
+ }
+
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "First", &pThis->iFirstLUN, 0);
+ AssertLogRelMsgRCReturn(rc, ("Configuration error: Failed to query the \"First\" value! rc=%Rrc\n", rc), rc);
+
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "Last", &pThis->iLastLUN, 0);
+ AssertLogRelMsgRCReturn(rc, ("Configuration error: Failed to query the \"Last\" value! rc=%Rrc\n", rc), rc);
+
+ AssertLogRelMsgReturn(pThis->iFirstLUN <= pThis->iLastLUN,
+ ("Configuration error: Invalid unit range %u-%u\n", pThis->iFirstLUN, pThis->iLastLUN),
+ VERR_INVALID_PARAMETER);
+
+ /*
+ * Get the ILedPorts interface of the above driver/device and
+ * query the LEDs we want.
+ */
+ pThis->pLedPorts = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMILEDPORTS);
+ AssertMsgReturn(pThis->pLedPorts, ("Configuration error: No led ports interface above!\n"),
+ VERR_PDM_MISSING_INTERFACE_ABOVE);
+
+ for (unsigned i = pThis->iFirstLUN; i <= pThis->iLastLUN; ++i)
+ Console::i_drvStatus_UnitChanged(&pThis->ILedConnectors, i);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Console status driver (LED) registration record.
+ */
+const PDMDRVREG Console::DrvStatusReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "MainStatus",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Main status driver (Main as in the API).",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_STATUS,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMAINSTATUS),
+ /* pfnConstruct */
+ Console::i_drvStatus_Construct,
+ /* pfnDestruct */
+ Console::i_drvStatus_Destruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
+
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/ConsoleImpl2.cpp b/src/VBox/Main/src-client/ConsoleImpl2.cpp
new file mode 100644
index 00000000..71e1bda2
--- /dev/null
+++ b/src/VBox/Main/src-client/ConsoleImpl2.cpp
@@ -0,0 +1,6915 @@
+/* $Id: ConsoleImpl2.cpp $ */
+/** @file
+ * VBox Console COM Class implementation - VM Configuration Bits.
+ *
+ * @remark We've split out the code that the 64-bit VC++ v8 compiler finds
+ * problematic to optimize so we can disable optimizations and later,
+ * perhaps, find a real solution for it (like rewriting the code and
+ * to stop resemble a tonne of spaghetti).
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE
+#include "LoggingNew.h"
+
+// VBoxNetCfg-win.h needs winsock2.h and thus MUST be included before any other
+// header file includes Windows.h.
+#if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_NETFLT)
+# include <VBox/VBoxNetCfg-win.h>
+#endif
+
+#include "ConsoleImpl.h"
+#include "DisplayImpl.h"
+#include "NvramStoreImpl.h"
+#ifdef VBOX_WITH_DRAG_AND_DROP
+# include "GuestImpl.h"
+# include "GuestDnDPrivate.h"
+#endif
+#include "VMMDev.h"
+#include "Global.h"
+#ifdef VBOX_WITH_PCI_PASSTHROUGH
+# include "PCIRawDevImpl.h"
+#endif
+
+// generated header
+#include "SchemaDefs.h"
+
+#include "AutoCaller.h"
+
+#include <iprt/base64.h>
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/cpp/exception.h>
+#if 0 /* enable to play with lots of memory. */
+# include <iprt/env.h>
+#endif
+#include <iprt/stream.h>
+
+#include <iprt/http.h>
+#include <iprt/socket.h>
+#include <iprt/uri.h>
+
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/vmm/vmapi.h>
+#include <VBox/err.h>
+#include <VBox/param.h>
+#include <VBox/settings.h> /* For MachineConfigFile::getHostDefaultAudioDriver(). */
+#include <VBox/vmm/pdmapi.h> /* For PDMR3DriverAttach/PDMR3DriverDetach. */
+#include <VBox/vmm/pdmusb.h> /* For PDMR3UsbCreateEmulatedDevice. */
+#include <VBox/vmm/pdmdev.h> /* For PDMAPICMODE enum. */
+#include <VBox/vmm/pdmstorageifs.h>
+#include <VBox/vmm/gcm.h>
+#include <VBox/version.h>
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+# include <VBox/HostServices/VBoxClipboardSvc.h>
+#endif
+#ifdef VBOX_WITH_GUEST_PROPS
+# include <VBox/HostServices/GuestPropertySvc.h>
+# include <VBox/com/defs.h>
+# include <VBox/com/array.h>
+# include <vector>
+#endif /* VBOX_WITH_GUEST_PROPS */
+#include <VBox/intnet.h>
+
+#include <VBox/com/com.h>
+#include <VBox/com/string.h>
+#include <VBox/com/array.h>
+
+#ifdef VBOX_WITH_NETFLT
+# if defined(RT_OS_SOLARIS)
+# include <zone.h>
+# elif defined(RT_OS_LINUX)
+# include <unistd.h>
+# include <sys/ioctl.h>
+# include <sys/socket.h>
+# include <linux/types.h>
+# include <linux/if.h>
+# elif defined(RT_OS_FREEBSD)
+# include <unistd.h>
+# include <sys/types.h>
+# include <sys/ioctl.h>
+# include <sys/socket.h>
+# include <net/if.h>
+# include <net80211/ieee80211_ioctl.h>
+# endif
+# if defined(RT_OS_WINDOWS)
+# include <iprt/win/ntddndis.h>
+# include <devguid.h>
+# else
+# include <HostNetworkInterfaceImpl.h>
+# include <netif.h>
+# include <stdlib.h>
+# endif
+#endif /* VBOX_WITH_NETFLT */
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+# include "DrvAudioVRDE.h"
+#endif
+#ifdef VBOX_WITH_AUDIO_RECORDING
+# include "DrvAudioRec.h"
+#endif
+#include "NetworkServiceRunner.h"
+#include "BusAssignmentManager.h"
+#ifdef VBOX_WITH_EXTPACK
+# include "ExtPackManagerImpl.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static Utf8Str *GetExtraDataBoth(IVirtualBox *pVirtualBox, IMachine *pMachine, const char *pszName, Utf8Str *pStrValue);
+
+
+/* Darwin compile kludge */
+#undef PVM
+
+/* Comment out the following line to remove VMWare compatibility hack. */
+#define VMWARE_NET_IN_SLOT_11
+
+/**
+ * Translate IDE StorageControllerType_T to string representation.
+ */
+static const char* controllerString(StorageControllerType_T enmType)
+{
+ switch (enmType)
+ {
+ case StorageControllerType_PIIX3:
+ return "PIIX3";
+ case StorageControllerType_PIIX4:
+ return "PIIX4";
+ case StorageControllerType_ICH6:
+ return "ICH6";
+ default:
+ return "Unknown";
+ }
+}
+
+/**
+ * Simple class for storing network boot information.
+ */
+struct BootNic
+{
+ ULONG mInstance;
+ PCIBusAddress mPCIAddress;
+
+ ULONG mBootPrio;
+ bool operator < (const BootNic &rhs) const
+ {
+ ULONG lval = mBootPrio - 1; /* 0 will wrap around and get the lowest priority. */
+ ULONG rval = rhs.mBootPrio - 1;
+ return lval < rval; /* Zero compares as highest number (lowest prio). */
+ }
+};
+
+#ifndef VBOX_WITH_EFI_IN_DD2
+static int findEfiRom(IVirtualBox* vbox, FirmwareType_T aFirmwareType, Utf8Str *pEfiRomFile)
+{
+ Bstr aFilePath, empty;
+ BOOL fPresent = FALSE;
+ HRESULT hrc = vbox->CheckFirmwarePresent(aFirmwareType, empty.raw(),
+ empty.asOutParam(), aFilePath.asOutParam(), &fPresent);
+ AssertComRCReturn(hrc, Global::vboxStatusCodeFromCOM(hrc));
+
+ if (!fPresent)
+ {
+ LogRel(("Failed to find an EFI ROM file.\n"));
+ return VERR_FILE_NOT_FOUND;
+ }
+
+ *pEfiRomFile = Utf8Str(aFilePath);
+
+ return VINF_SUCCESS;
+}
+#endif
+
+/**
+ * @throws HRESULT on extra data retrival error.
+ */
+static int getSmcDeviceKey(IVirtualBox *pVirtualBox, IMachine *pMachine, Utf8Str *pStrKey, bool *pfGetKeyFromRealSMC)
+{
+ *pfGetKeyFromRealSMC = false;
+
+ /*
+ * The extra data takes precedence (if non-zero).
+ */
+ GetExtraDataBoth(pVirtualBox, pMachine, "VBoxInternal2/SmcDeviceKey", pStrKey);
+ if (pStrKey->isNotEmpty())
+ return VINF_SUCCESS;
+
+#ifdef RT_OS_DARWIN
+
+ /*
+ * Work done in EFI/DevSmc
+ */
+ *pfGetKeyFromRealSMC = true;
+ int vrc = VINF_SUCCESS;
+
+#else
+ /*
+ * Is it apple hardware in bootcamp?
+ */
+ /** @todo implement + test RTSYSDMISTR_MANUFACTURER on all hosts.
+ * Currently falling back on the product name. */
+ char szManufacturer[256];
+ szManufacturer[0] = '\0';
+ RTSystemQueryDmiString(RTSYSDMISTR_MANUFACTURER, szManufacturer, sizeof(szManufacturer));
+ if (szManufacturer[0] != '\0')
+ {
+ if ( !strcmp(szManufacturer, "Apple Computer, Inc.")
+ || !strcmp(szManufacturer, "Apple Inc.")
+ )
+ *pfGetKeyFromRealSMC = true;
+ }
+ else
+ {
+ char szProdName[256];
+ szProdName[0] = '\0';
+ RTSystemQueryDmiString(RTSYSDMISTR_PRODUCT_NAME, szProdName, sizeof(szProdName));
+ if ( ( !strncmp(szProdName, RT_STR_TUPLE("Mac"))
+ || !strncmp(szProdName, RT_STR_TUPLE("iMac"))
+ || !strncmp(szProdName, RT_STR_TUPLE("Xserve"))
+ )
+ && !strchr(szProdName, ' ') /* no spaces */
+ && RT_C_IS_DIGIT(szProdName[strlen(szProdName) - 1]) /* version number */
+ )
+ *pfGetKeyFromRealSMC = true;
+ }
+
+ int vrc = VINF_SUCCESS;
+#endif
+
+ return vrc;
+}
+
+
+/*
+ * VC++ 8 / amd64 has some serious trouble with the next functions.
+ * As a temporary measure, we'll drop global optimizations.
+ */
+#if defined(_MSC_VER) && defined(RT_ARCH_AMD64)
+# if _MSC_VER >= RT_MSC_VER_VC80 && _MSC_VER < RT_MSC_VER_VC100
+# pragma optimize("g", off)
+# endif
+#endif
+
+class ConfigError : public RTCError
+{
+public:
+
+ ConfigError(const char *pcszFunction,
+ int vrc,
+ const char *pcszName)
+ : RTCError(Utf8StrFmt(Console::tr("%s failed: rc=%Rrc, pcszName=%s"), pcszFunction, vrc, pcszName)),
+ m_vrc(vrc)
+ {
+ AssertMsgFailed(("%s\n", what())); // in strict mode, hit a breakpoint here
+ }
+
+ int m_vrc;
+};
+
+
+/**
+ * Helper that calls CFGMR3InsertString and throws an RTCError if that
+ * fails (C-string variant).
+ * @param pNode See CFGMR3InsertStringN.
+ * @param pcszName See CFGMR3InsertStringN.
+ * @param pcszValue The string value.
+ */
+void Console::InsertConfigString(PCFGMNODE pNode, const char *pcszName, const char *pcszValue)
+{
+ int vrc = mpVMM->pfnCFGMR3InsertString(pNode, pcszName, pcszValue);
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertString", vrc, pcszName);
+}
+
+/**
+ * Helper that calls CFGMR3InsertString and throws an RTCError if that
+ * fails (Utf8Str variant).
+ * @param pNode See CFGMR3InsertStringN.
+ * @param pcszName See CFGMR3InsertStringN.
+ * @param rStrValue The string value.
+ */
+void Console::InsertConfigString(PCFGMNODE pNode, const char *pcszName, const Utf8Str &rStrValue)
+{
+ int vrc = mpVMM->pfnCFGMR3InsertStringN(pNode, pcszName, rStrValue.c_str(), rStrValue.length());
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertStringLengthKnown", vrc, pcszName);
+}
+
+/**
+ * Helper that calls CFGMR3InsertString and throws an RTCError if that
+ * fails (Bstr variant).
+ *
+ * @param pNode See CFGMR3InsertStringN.
+ * @param pcszName See CFGMR3InsertStringN.
+ * @param rBstrValue The string value.
+ */
+void Console::InsertConfigString(PCFGMNODE pNode, const char *pcszName, const Bstr &rBstrValue)
+{
+ InsertConfigString(pNode, pcszName, Utf8Str(rBstrValue));
+}
+
+/**
+ * Helper that calls CFGMR3InsertPassword and throws an RTCError if that
+ * fails (Utf8Str variant).
+ * @param pNode See CFGMR3InsertPasswordN.
+ * @param pcszName See CFGMR3InsertPasswordN.
+ * @param rStrValue The string value.
+ */
+void Console::InsertConfigPassword(PCFGMNODE pNode, const char *pcszName, const Utf8Str &rStrValue)
+{
+ int vrc = mpVMM->pfnCFGMR3InsertPasswordN(pNode, pcszName, rStrValue.c_str(), rStrValue.length());
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertPasswordLengthKnown", vrc, pcszName);
+}
+
+/**
+ * Helper that calls CFGMR3InsertBytes and throws an RTCError if that fails.
+ *
+ * @param pNode See CFGMR3InsertBytes.
+ * @param pcszName See CFGMR3InsertBytes.
+ * @param pvBytes See CFGMR3InsertBytes.
+ * @param cbBytes See CFGMR3InsertBytes.
+ */
+void Console::InsertConfigBytes(PCFGMNODE pNode, const char *pcszName, const void *pvBytes, size_t cbBytes)
+{
+ int vrc = mpVMM->pfnCFGMR3InsertBytes(pNode, pcszName, pvBytes, cbBytes);
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertBytes", vrc, pcszName);
+}
+
+/**
+ * Helper that calls CFGMR3InsertInteger and throws an RTCError if that
+ * fails.
+ *
+ * @param pNode See CFGMR3InsertInteger.
+ * @param pcszName See CFGMR3InsertInteger.
+ * @param u64Integer See CFGMR3InsertInteger.
+ */
+void Console::InsertConfigInteger(PCFGMNODE pNode, const char *pcszName, uint64_t u64Integer)
+{
+ int vrc = mpVMM->pfnCFGMR3InsertInteger(pNode, pcszName, u64Integer);
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertInteger", vrc, pcszName);
+}
+
+/**
+ * Helper that calls CFGMR3InsertNode and throws an RTCError if that fails.
+ *
+ * @param pNode See CFGMR3InsertNode.
+ * @param pcszName See CFGMR3InsertNode.
+ * @param ppChild See CFGMR3InsertNode.
+ */
+void Console::InsertConfigNode(PCFGMNODE pNode, const char *pcszName, PCFGMNODE *ppChild)
+{
+ int vrc = mpVMM->pfnCFGMR3InsertNode(pNode, pcszName, ppChild);
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertNode", vrc, pcszName);
+}
+
+/**
+ * Helper that calls CFGMR3InsertNodeF and throws an RTCError if that fails.
+ *
+ * @param pNode See CFGMR3InsertNodeF.
+ * @param ppChild See CFGMR3InsertNodeF.
+ * @param pszNameFormat Name format string, see CFGMR3InsertNodeF.
+ * @param ... Format arguments.
+ */
+void Console::InsertConfigNodeF(PCFGMNODE pNode, PCFGMNODE *ppChild, const char *pszNameFormat, ...)
+{
+ va_list va;
+ va_start(va, pszNameFormat);
+ int vrc = mpVMM->pfnCFGMR3InsertNodeF(pNode, ppChild, "%N", pszNameFormat, &va);
+ va_end(va);
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertNodeF", vrc, pszNameFormat);
+}
+
+/**
+ * Helper that calls CFGMR3RemoveValue and throws an RTCError if that fails.
+ *
+ * @param pNode See CFGMR3RemoveValue.
+ * @param pcszName See CFGMR3RemoveValue.
+ */
+void Console::RemoveConfigValue(PCFGMNODE pNode, const char *pcszName)
+{
+ int vrc = mpVMM->pfnCFGMR3RemoveValue(pNode, pcszName);
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3RemoveValue", vrc, pcszName);
+}
+
+/**
+ * Gets an extra data value, consulting both machine and global extra data.
+ *
+ * @throws HRESULT on failure
+ * @returns pStrValue for the callers convenience.
+ * @param pVirtualBox Pointer to the IVirtualBox interface.
+ * @param pMachine Pointer to the IMachine interface.
+ * @param pszName The value to get.
+ * @param pStrValue Where to return it's value (empty string if not
+ * found).
+ */
+static Utf8Str *GetExtraDataBoth(IVirtualBox *pVirtualBox, IMachine *pMachine, const char *pszName, Utf8Str *pStrValue)
+{
+ pStrValue->setNull();
+
+ Bstr bstrName(pszName);
+ Bstr bstrValue;
+ HRESULT hrc = pMachine->GetExtraData(bstrName.raw(), bstrValue.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ if (bstrValue.isEmpty())
+ {
+ hrc = pVirtualBox->GetExtraData(bstrName.raw(), bstrValue.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ }
+
+ if (bstrValue.isNotEmpty())
+ *pStrValue = bstrValue;
+ return pStrValue;
+}
+
+
+/** Helper that finds out the next HBA port used
+ */
+static LONG GetNextUsedPort(LONG aPortUsed[30], LONG lBaseVal, uint32_t u32Size)
+{
+ LONG lNextPortUsed = 30;
+ for (size_t j = 0; j < u32Size; ++j)
+ {
+ if ( aPortUsed[j] > lBaseVal
+ && aPortUsed[j] <= lNextPortUsed)
+ lNextPortUsed = aPortUsed[j];
+ }
+ return lNextPortUsed;
+}
+
+#define MAX_BIOS_LUN_COUNT 4
+
+int Console::SetBiosDiskInfo(ComPtr<IMachine> pMachine, PCFGMNODE pCfg, PCFGMNODE pBiosCfg,
+ Bstr controllerName, const char * const s_apszBiosConfig[4])
+{
+ RT_NOREF(pCfg);
+ HRESULT hrc;
+#define MAX_DEVICES 30
+#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR)
+
+ LONG lPortLUN[MAX_BIOS_LUN_COUNT];
+ LONG lPortUsed[MAX_DEVICES];
+ uint32_t u32HDCount = 0;
+
+ /* init to max value */
+ lPortLUN[0] = MAX_DEVICES;
+
+ com::SafeIfaceArray<IMediumAttachment> atts;
+ hrc = pMachine->GetMediumAttachmentsOfController(controllerName.raw(),
+ ComSafeArrayAsOutParam(atts)); H();
+ size_t uNumAttachments = atts.size();
+ if (uNumAttachments > MAX_DEVICES)
+ {
+ LogRel(("Number of Attachments > Max=%d.\n", uNumAttachments));
+ uNumAttachments = MAX_DEVICES;
+ }
+
+ /* Find the relevant ports/IDs, i.e the ones to which a HD is attached. */
+ for (size_t j = 0; j < uNumAttachments; ++j)
+ {
+ IMediumAttachment *pMediumAtt = atts[j];
+ LONG lPortNum = 0;
+ hrc = pMediumAtt->COMGETTER(Port)(&lPortNum); H();
+ if (SUCCEEDED(hrc))
+ {
+ DeviceType_T lType;
+ hrc = pMediumAtt->COMGETTER(Type)(&lType); H();
+ if (SUCCEEDED(hrc) && lType == DeviceType_HardDisk)
+ {
+ /* find min port number used for HD */
+ if (lPortNum < lPortLUN[0])
+ lPortLUN[0] = lPortNum;
+ lPortUsed[u32HDCount++] = lPortNum;
+ LogFlowFunc(("HD port Count=%d\n", u32HDCount));
+ }
+ }
+ }
+
+
+ /* Pick only the top 4 used HD Ports as CMOS doesn't have space
+ * to save details for all 30 ports
+ */
+ uint32_t u32MaxPortCount = MAX_BIOS_LUN_COUNT;
+ if (u32HDCount < MAX_BIOS_LUN_COUNT)
+ u32MaxPortCount = u32HDCount;
+ for (size_t j = 1; j < u32MaxPortCount; j++)
+ lPortLUN[j] = GetNextUsedPort(lPortUsed, lPortLUN[j-1], u32HDCount);
+ if (pBiosCfg)
+ {
+ for (size_t j = 0; j < u32MaxPortCount; j++)
+ {
+ InsertConfigInteger(pBiosCfg, s_apszBiosConfig[j], lPortLUN[j]);
+ LogFlowFunc(("Top %d HBA ports = %s, %d\n", j, s_apszBiosConfig[j], lPortLUN[j]));
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+#ifdef VBOX_WITH_PCI_PASSTHROUGH
+HRESULT Console::i_attachRawPCIDevices(PUVM pUVM, BusAssignmentManager *pBusMgr, PCFGMNODE pDevices)
+{
+# ifndef VBOX_WITH_EXTPACK
+ RT_NOREF(pUVM);
+# endif
+ HRESULT hrc = S_OK;
+ PCFGMNODE pInst, pCfg, pLunL0, pLunL1;
+
+ SafeIfaceArray<IPCIDeviceAttachment> assignments;
+ ComPtr<IMachine> aMachine = i_machine();
+
+ hrc = aMachine->COMGETTER(PCIDeviceAssignments)(ComSafeArrayAsOutParam(assignments));
+ if ( hrc != S_OK
+ || assignments.size() < 1)
+ return hrc;
+
+ /*
+ * PCI passthrough is only available if the proper ExtPack is installed.
+ *
+ * Note. Configuring PCI passthrough here and providing messages about
+ * the missing extpack isn't exactly clean, but it is a necessary evil
+ * to patch over legacy compatability issues introduced by the new
+ * distribution model.
+ */
+# ifdef VBOX_WITH_EXTPACK
+ static const char *s_pszPCIRawExtPackName = "Oracle VM VirtualBox Extension Pack";
+ if (!mptrExtPackManager->i_isExtPackUsable(s_pszPCIRawExtPackName))
+ /* Always fatal! */
+ return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS,
+ N_("Implementation of the PCI passthrough framework not found!\n"
+ "The VM cannot be started. To fix this problem, either "
+ "install the '%s' or disable PCI passthrough via VBoxManage"),
+ s_pszPCIRawExtPackName);
+# endif
+
+ /* Now actually add devices */
+ PCFGMNODE pPCIDevs = NULL;
+
+ if (assignments.size() > 0)
+ {
+ InsertConfigNode(pDevices, "pciraw", &pPCIDevs);
+
+ PCFGMNODE pRoot = CFGMR3GetParent(pDevices); Assert(pRoot);
+
+ /* Tell PGM to tell GPCIRaw about guest mappings. */
+ CFGMR3InsertNode(pRoot, "PGM", NULL);
+ InsertConfigInteger(CFGMR3GetChild(pRoot, "PGM"), "PciPassThrough", 1);
+
+ /*
+ * Currently, using IOMMU needed for PCI passthrough
+ * requires RAM preallocation.
+ */
+ /** @todo check if we can lift this requirement */
+ CFGMR3RemoveValue(pRoot, "RamPreAlloc");
+ InsertConfigInteger(pRoot, "RamPreAlloc", 1);
+ }
+
+ for (size_t iDev = 0; iDev < assignments.size(); iDev++)
+ {
+ PCIBusAddress HostPCIAddress, GuestPCIAddress;
+ ComPtr<IPCIDeviceAttachment> assignment = assignments[iDev];
+ LONG host, guest;
+ Bstr aDevName;
+
+ hrc = assignment->COMGETTER(HostAddress)(&host); H();
+ hrc = assignment->COMGETTER(GuestAddress)(&guest); H();
+ hrc = assignment->COMGETTER(Name)(aDevName.asOutParam()); H();
+
+ InsertConfigNode(pPCIDevs, Utf8StrFmt("%d", iDev).c_str(), &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1);
+
+ HostPCIAddress.fromLong(host);
+ Assert(HostPCIAddress.valid());
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigString(pCfg, "DeviceName", aDevName);
+
+ InsertConfigInteger(pCfg, "DetachHostDriver", 1);
+ InsertConfigInteger(pCfg, "HostPCIBusNo", HostPCIAddress.miBus);
+ InsertConfigInteger(pCfg, "HostPCIDeviceNo", HostPCIAddress.miDevice);
+ InsertConfigInteger(pCfg, "HostPCIFunctionNo", HostPCIAddress.miFn);
+
+ GuestPCIAddress.fromLong(guest);
+ Assert(GuestPCIAddress.valid());
+ hrc = pBusMgr->assignHostPCIDevice("pciraw", pInst, HostPCIAddress, GuestPCIAddress, true);
+ if (hrc != S_OK)
+ return hrc;
+
+ InsertConfigInteger(pCfg, "GuestPCIBusNo", GuestPCIAddress.miBus);
+ InsertConfigInteger(pCfg, "GuestPCIDeviceNo", GuestPCIAddress.miDevice);
+ InsertConfigInteger(pCfg, "GuestPCIFunctionNo", GuestPCIAddress.miFn);
+
+ /* the driver */
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "pciraw");
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+
+ /* the Main driver */
+ InsertConfigString(pLunL1, "Driver", "MainPciRaw");
+ InsertConfigNode(pLunL1, "Config", &pCfg);
+ PCIRawDev* pMainDev = new PCIRawDev(this);
+ InsertConfigInteger(pCfg, "Object", (uintptr_t)pMainDev);
+ }
+
+ return hrc;
+}
+#endif
+
+
+/**
+ * Allocate a set of LEDs.
+ *
+ * This grabs a maLedSets entry and populates it with @a cLeds.
+ *
+ * @returns Index into maLedSets.
+ * @param cLeds The number of LEDs in the set.
+ * @param enmType The device type.
+ * @param ppaSubTypes When not NULL, subtypes for each LED and return the
+ * array pointer here.
+ */
+uint32_t Console::i_allocateDriverLeds(uint32_t cLeds, DeviceType_T enmType, DeviceType_T **ppaSubTypes)
+{
+ Assert(cLeds > 0);
+ Assert(cLeds < 1024); /* Adjust if any driver supports >=1024 units! */
+
+ /* Grab a LED set entry before we start allocating anything so the destructor can do the cleanups. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Caller should have this already. Need protect mcLedSets check and update. */
+ AssertStmt(mcLedSets < RT_ELEMENTS(maLedSets),
+ throw ConfigError("AllocateDriverPapLeds", VERR_OUT_OF_RANGE, "Too many LED sets"));
+ uint32_t const idxLedSet = mcLedSets++;
+ PLEDSET pLS = &maLedSets[idxLedSet];
+ pLS->papLeds = (PPDMLED *)RTMemAllocZ(sizeof(PPDMLED) * cLeds);
+ AssertStmt(pLS->papLeds, throw E_OUTOFMEMORY);
+ pLS->cLeds = cLeds;
+ pLS->enmType = enmType;
+ pLS->paSubTypes = NULL;
+
+ if (ppaSubTypes)
+ {
+ *ppaSubTypes = pLS->paSubTypes = (DeviceType_T *)RTMemAlloc(sizeof(DeviceType_T) * cLeds);
+ AssertStmt(pLS->paSubTypes, throw E_OUTOFMEMORY);
+ for (size_t idxSub = 0; idxSub < cLeds; ++idxSub)
+ pLS->paSubTypes[idxSub] = DeviceType_Null;
+ }
+
+ LogRel2(("mcLedSets = %d, RT_ELEMENTS(maLedSets) = %d\n", mcLedSets, RT_ELEMENTS(maLedSets)));
+ return idxLedSet;
+}
+
+
+/** @todo r=bird: Drop uFirst as it's always zero? Then s/uLast/cLeds/g. */
+void Console::i_attachStatusDriver(PCFGMNODE pCtlInst, DeviceType_T enmType,
+ uint32_t uFirst, uint32_t uLast,
+ DeviceType_T **ppaSubTypes,
+ Console::MediumAttachmentMap *pmapMediumAttachments,
+ const char *pcszDevice, unsigned uInstance)
+{
+ Assert(uFirst <= uLast);
+ PCFGMNODE pLunL0;
+ InsertConfigNode(pCtlInst, "LUN#999", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "MainStatus");
+ PCFGMNODE pCfg;
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ uint32_t const iLedSet = i_allocateDriverLeds(uLast - uFirst + 1, enmType, ppaSubTypes);
+ InsertConfigInteger(pCfg, "iLedSet", iLedSet);
+
+ InsertConfigInteger(pCfg, "HasMediumAttachments", pmapMediumAttachments != NULL);
+ if (pmapMediumAttachments)
+ {
+ AssertPtr(pcszDevice);
+ Utf8StrFmt deviceInstance("%s/%u", pcszDevice, uInstance);
+ InsertConfigString(pCfg, "DeviceInstance", deviceInstance.c_str());
+ }
+ InsertConfigInteger(pCfg, "First", uFirst);
+ InsertConfigInteger(pCfg, "Last", uLast);
+}
+
+
+/**
+ * Construct the VM configuration tree (CFGM).
+ *
+ * This is a callback for VMR3Create() call. It is called from CFGMR3Init() in
+ * the emulation thread (EMT). Any per thread COM/XPCOM initialization is done
+ * here.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param pVM The cross context VM handle.
+ * @param pVMM The VMM ring-3 vtable.
+ * @param pvConsole Pointer to the VMPowerUpTask object.
+ *
+ * @note Locks the Console object for writing.
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_configConstructor(PUVM pUVM, PVM pVM, PCVMMR3VTABLE pVMM, void *pvConsole)
+{
+ LogFlowFuncEnter();
+
+ AssertReturn(pvConsole, VERR_INVALID_POINTER);
+ ComObjPtr<Console> pConsole = static_cast<Console *>(pvConsole);
+
+ AutoCaller autoCaller(pConsole);
+ AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED);
+
+ /* lock the console because we widely use internal fields and methods */
+ AutoWriteLock alock(pConsole COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Set the VM handle and do the rest of the job in an worker method so we
+ * can easily reset the VM handle on failure.
+ */
+ pConsole->mpUVM = pUVM;
+ pVMM->pfnVMR3RetainUVM(pUVM);
+ int vrc;
+ try
+ {
+ vrc = pConsole->i_configConstructorInner(pUVM, pVM, pVMM, &alock);
+ }
+ catch (...)
+ {
+ vrc = VERR_UNEXPECTED_EXCEPTION;
+ }
+ if (RT_FAILURE(vrc))
+ {
+ pConsole->mpUVM = NULL;
+ pVMM->pfnVMR3ReleaseUVM(pUVM);
+ }
+
+ return vrc;
+}
+
+
+/**
+ * Worker for configConstructor.
+ *
+ * @return VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param pVM The cross context VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pAlock The automatic lock instance. This is for when we have
+ * to leave it in order to avoid deadlocks (ext packs and
+ * more).
+ */
+int Console::i_configConstructorInner(PUVM pUVM, PVM pVM, PCVMMR3VTABLE pVMM, AutoWriteLock *pAlock)
+{
+ RT_NOREF(pVM /* when everything is disabled */);
+ VMMDev *pVMMDev = m_pVMMDev; Assert(pVMMDev);
+ ComPtr<IMachine> pMachine = i_machine();
+
+ int vrc;
+ HRESULT hrc;
+ Utf8Str strTmp;
+ Bstr bstr;
+
+#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR)
+
+ /*
+ * Get necessary objects and frequently used parameters.
+ */
+ ComPtr<IVirtualBox> virtualBox;
+ hrc = pMachine->COMGETTER(Parent)(virtualBox.asOutParam()); H();
+
+ ComPtr<IHost> host;
+ hrc = virtualBox->COMGETTER(Host)(host.asOutParam()); H();
+
+ ComPtr<ISystemProperties> systemProperties;
+ hrc = virtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam()); H();
+
+ ComPtr<IBIOSSettings> biosSettings;
+ hrc = pMachine->COMGETTER(BIOSSettings)(biosSettings.asOutParam()); H();
+
+ ComPtr<INvramStore> nvramStore;
+ hrc = pMachine->COMGETTER(NonVolatileStore)(nvramStore.asOutParam()); H();
+
+ hrc = pMachine->COMGETTER(HardwareUUID)(bstr.asOutParam()); H();
+ RTUUID HardwareUuid;
+ vrc = RTUuidFromUtf16(&HardwareUuid, bstr.raw());
+ AssertRCReturn(vrc, vrc);
+
+ ULONG cRamMBs;
+ hrc = pMachine->COMGETTER(MemorySize)(&cRamMBs); H();
+#if 0 /* enable to play with lots of memory. */
+ if (RTEnvExist("VBOX_RAM_SIZE"))
+ cRamMBs = RTStrToUInt64(RTEnvGet("VBOX_RAM_SIZE"));
+#endif
+ uint64_t const cbRam = cRamMBs * (uint64_t)_1M;
+ uint32_t cbRamHole = MM_RAM_HOLE_SIZE_DEFAULT;
+ uint64_t uMcfgBase = 0;
+ uint32_t cbMcfgLength = 0;
+
+ ParavirtProvider_T enmParavirtProvider;
+ hrc = pMachine->GetEffectiveParavirtProvider(&enmParavirtProvider); H();
+
+ Bstr strParavirtDebug;
+ hrc = pMachine->COMGETTER(ParavirtDebug)(strParavirtDebug.asOutParam()); H();
+
+ BOOL fIOAPIC;
+ uint32_t uIoApicPciAddress = NIL_PCIBDF;
+ hrc = biosSettings->COMGETTER(IOAPICEnabled)(&fIOAPIC); H();
+
+ ChipsetType_T chipsetType;
+ hrc = pMachine->COMGETTER(ChipsetType)(&chipsetType); H();
+ if (chipsetType == ChipsetType_ICH9)
+ {
+ /* We'd better have 0x10000000 region, to cover 256 buses but this put
+ * too much load on hypervisor heap. Linux 4.8 currently complains with
+ * ``acpi PNP0A03:00: [Firmware Info]: MMCONFIG for domain 0000 [bus 00-3f]
+ * only partially covers this bridge'' */
+ cbMcfgLength = 0x4000000; //0x10000000;
+ cbRamHole += cbMcfgLength;
+ uMcfgBase = _4G - cbRamHole;
+ }
+
+ /* Get the CPU profile name. */
+ Bstr bstrCpuProfile;
+ hrc = pMachine->COMGETTER(CPUProfile)(bstrCpuProfile.asOutParam()); H();
+
+ /* Check if long mode is enabled. */
+ BOOL fIsGuest64Bit;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_LongMode, &fIsGuest64Bit); H();
+
+ /*
+ * Figure out the IOMMU config.
+ */
+#if defined(VBOX_WITH_IOMMU_AMD) || defined(VBOX_WITH_IOMMU_INTEL)
+ IommuType_T enmIommuType;
+ hrc = pMachine->COMGETTER(IommuType)(&enmIommuType); H();
+
+ /* Resolve 'automatic' type to an Intel or AMD IOMMU based on the host CPU. */
+ if (enmIommuType == IommuType_Automatic)
+ {
+ if ( bstrCpuProfile.startsWith("AMD")
+ || bstrCpuProfile.startsWith("Quad-Core AMD")
+ || bstrCpuProfile.startsWith("Hygon"))
+ enmIommuType = IommuType_AMD;
+ else if (bstrCpuProfile.startsWith("Intel"))
+ {
+ if ( bstrCpuProfile.equals("Intel 8086")
+ || bstrCpuProfile.equals("Intel 80186")
+ || bstrCpuProfile.equals("Intel 80286")
+ || bstrCpuProfile.equals("Intel 80386")
+ || bstrCpuProfile.equals("Intel 80486"))
+ enmIommuType = IommuType_None;
+ else
+ enmIommuType = IommuType_Intel;
+ }
+# if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+ else if (ASMIsAmdCpu())
+ enmIommuType = IommuType_AMD;
+ else if (ASMIsIntelCpu())
+ enmIommuType = IommuType_Intel;
+# endif
+ else
+ {
+ /** @todo Should we handle other CPUs like Shanghai, VIA etc. here? */
+ LogRel(("WARNING! Unrecognized CPU type, IOMMU disabled.\n"));
+ enmIommuType = IommuType_None;
+ }
+ }
+
+ if (enmIommuType == IommuType_AMD)
+ {
+# ifdef VBOX_WITH_IOMMU_AMD
+ /*
+ * Reserve the specific PCI address of the "SB I/O APIC" when using
+ * an AMD IOMMU. Required by Linux guests, see @bugref{9654#c23}.
+ */
+ uIoApicPciAddress = VBOX_PCI_BDF_SB_IOAPIC;
+# else
+ LogRel(("WARNING! AMD IOMMU not supported, IOMMU disabled.\n"));
+ enmIommuType = IommuType_None;
+# endif
+ }
+
+ if (enmIommuType == IommuType_Intel)
+ {
+# ifdef VBOX_WITH_IOMMU_INTEL
+ /*
+ * Reserve a unique PCI address for the I/O APIC when using
+ * an Intel IOMMU. For convenience we use the same address as
+ * we do on AMD, see @bugref{9967#c13}.
+ */
+ uIoApicPciAddress = VBOX_PCI_BDF_SB_IOAPIC;
+# else
+ LogRel(("WARNING! Intel IOMMU not supported, IOMMU disabled.\n"));
+ enmIommuType = IommuType_None;
+# endif
+ }
+
+ if ( enmIommuType == IommuType_AMD
+ || enmIommuType == IommuType_Intel)
+ {
+ if (chipsetType != ChipsetType_ICH9)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("IOMMU uses MSIs which requires the ICH9 chipset implementation."));
+ if (!fIOAPIC)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("IOMMU requires an I/O APIC for remapping interrupts."));
+ }
+#else
+ IommuType_T const enmIommuType = IommuType_None;
+#endif
+
+ /* Instantiate the bus assignment manager. */
+ Assert(enmIommuType != IommuType_Automatic);
+ BusAssignmentManager *pBusMgr = mBusMgr = BusAssignmentManager::createInstance(pVMM, chipsetType, enmIommuType);
+
+ ULONG cCpus = 1;
+ hrc = pMachine->COMGETTER(CPUCount)(&cCpus); H();
+
+ ULONG ulCpuExecutionCap = 100;
+ hrc = pMachine->COMGETTER(CPUExecutionCap)(&ulCpuExecutionCap); H();
+
+ Bstr osTypeId;
+ hrc = pMachine->COMGETTER(OSTypeId)(osTypeId.asOutParam()); H();
+ LogRel(("Guest OS type: '%s'\n", Utf8Str(osTypeId).c_str()));
+
+ APICMode_T apicMode;
+ hrc = biosSettings->COMGETTER(APICMode)(&apicMode); H();
+ uint32_t uFwAPIC;
+ switch (apicMode)
+ {
+ case APICMode_Disabled:
+ uFwAPIC = 0;
+ break;
+ case APICMode_APIC:
+ uFwAPIC = 1;
+ break;
+ case APICMode_X2APIC:
+ uFwAPIC = 2;
+ break;
+ default:
+ AssertMsgFailed(("Invalid APICMode=%d\n", apicMode));
+ uFwAPIC = 1;
+ break;
+ }
+
+ ComPtr<IGuestOSType> pGuestOSType;
+ virtualBox->GetGuestOSType(osTypeId.raw(), pGuestOSType.asOutParam());
+
+ BOOL fOsXGuest = FALSE;
+ BOOL fWinGuest = FALSE;
+ BOOL fOs2Guest = FALSE;
+ BOOL fW9xGuest = FALSE;
+ BOOL fDosGuest = FALSE;
+ if (!pGuestOSType.isNull())
+ {
+ Bstr guestTypeFamilyId;
+ hrc = pGuestOSType->COMGETTER(FamilyId)(guestTypeFamilyId.asOutParam()); H();
+ fOsXGuest = guestTypeFamilyId == Bstr("MacOS");
+ fWinGuest = guestTypeFamilyId == Bstr("Windows");
+ fOs2Guest = osTypeId.startsWith("OS2");
+ fW9xGuest = osTypeId.startsWith("Windows9"); /* Does not include Windows Me. */
+ fDosGuest = osTypeId.startsWith("DOS") || osTypeId.startsWith("Windows31");
+ }
+
+ ULONG maxNetworkAdapters;
+ hrc = systemProperties->GetMaxNetworkAdapters(chipsetType, &maxNetworkAdapters); H();
+
+ /*
+ * Get root node first.
+ * This is the only node in the tree.
+ */
+ PCFGMNODE pRoot = pVMM->pfnCFGMR3GetRootU(pUVM);
+ Assert(pRoot);
+
+ // InsertConfigString throws
+ try
+ {
+
+ /*
+ * Set the root (and VMM) level values.
+ */
+ hrc = pMachine->COMGETTER(Name)(bstr.asOutParam()); H();
+ InsertConfigString(pRoot, "Name", bstr);
+ InsertConfigBytes(pRoot, "UUID", &HardwareUuid, sizeof(HardwareUuid));
+ InsertConfigInteger(pRoot, "RamSize", cbRam);
+ InsertConfigInteger(pRoot, "RamHoleSize", cbRamHole);
+ InsertConfigInteger(pRoot, "NumCPUs", cCpus);
+ InsertConfigInteger(pRoot, "CpuExecutionCap", ulCpuExecutionCap);
+ InsertConfigInteger(pRoot, "TimerMillies", 10);
+
+ BOOL fPageFusion = FALSE;
+ hrc = pMachine->COMGETTER(PageFusionEnabled)(&fPageFusion); H();
+ InsertConfigInteger(pRoot, "PageFusionAllowed", fPageFusion); /* boolean */
+
+ /* Not necessary, but makes sure this setting ends up in the release log. */
+ ULONG ulBalloonSize = 0;
+ hrc = pMachine->COMGETTER(MemoryBalloonSize)(&ulBalloonSize); H();
+ InsertConfigInteger(pRoot, "MemBalloonSize", ulBalloonSize);
+
+ /*
+ * EM values (before CPUM as it may need to set IemExecutesAll).
+ */
+ PCFGMNODE pEM;
+ InsertConfigNode(pRoot, "EM", &pEM);
+
+ /* Triple fault behavior. */
+ BOOL fTripleFaultReset = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_TripleFaultReset, &fTripleFaultReset); H();
+ InsertConfigInteger(pEM, "TripleFaultReset", fTripleFaultReset);
+
+ /*
+ * CPUM values.
+ */
+ PCFGMNODE pCPUM;
+ InsertConfigNode(pRoot, "CPUM", &pCPUM);
+ PCFGMNODE pIsaExts;
+ InsertConfigNode(pCPUM, "IsaExts", &pIsaExts);
+
+ /* Host CPUID leaf overrides. */
+ for (uint32_t iOrdinal = 0; iOrdinal < _4K; iOrdinal++)
+ {
+ ULONG uLeaf, uSubLeaf, uEax, uEbx, uEcx, uEdx;
+ hrc = pMachine->GetCPUIDLeafByOrdinal(iOrdinal, &uLeaf, &uSubLeaf, &uEax, &uEbx, &uEcx, &uEdx);
+ if (hrc == E_INVALIDARG)
+ break;
+ H();
+ PCFGMNODE pLeaf;
+ InsertConfigNode(pCPUM, Utf8StrFmt("HostCPUID/%RX32", uLeaf).c_str(), &pLeaf);
+ /** @todo Figure out how to tell the VMM about uSubLeaf */
+ InsertConfigInteger(pLeaf, "eax", uEax);
+ InsertConfigInteger(pLeaf, "ebx", uEbx);
+ InsertConfigInteger(pLeaf, "ecx", uEcx);
+ InsertConfigInteger(pLeaf, "edx", uEdx);
+ }
+
+ /* We must limit CPUID count for Windows NT 4, as otherwise it stops
+ with error 0x3e (MULTIPROCESSOR_CONFIGURATION_NOT_SUPPORTED). */
+ if (osTypeId == "WindowsNT4")
+ {
+ LogRel(("Limiting CPUID leaf count for NT4 guests\n"));
+ InsertConfigInteger(pCPUM, "NT4LeafLimit", true);
+ }
+
+ if (fOsXGuest)
+ {
+ /* Expose extended MWAIT features to Mac OS X guests. */
+ LogRel(("Using MWAIT extensions\n"));
+ InsertConfigInteger(pIsaExts, "MWaitExtensions", true);
+
+ /* Fake the CPU family/model so the guest works. This is partly
+ because older mac releases really doesn't work on newer cpus,
+ and partly because mac os x expects more from systems with newer
+ cpus (MSRs, power features, whatever). */
+ uint32_t uMaxIntelFamilyModelStep = UINT32_MAX;
+ if ( osTypeId == "MacOS"
+ || osTypeId == "MacOS_64")
+ uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482. */
+ else if ( osTypeId == "MacOS106"
+ || osTypeId == "MacOS106_64")
+ uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482 */
+ else if ( osTypeId == "MacOS107"
+ || osTypeId == "MacOS107_64")
+ uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482 */ /** @todo figure out
+ what is required here. */
+ else if ( osTypeId == "MacOS108"
+ || osTypeId == "MacOS108_64")
+ uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482 */ /** @todo figure out
+ what is required here. */
+ else if ( osTypeId == "MacOS109"
+ || osTypeId == "MacOS109_64")
+ uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482 */ /** @todo figure
+ out what is required here. */
+ if (uMaxIntelFamilyModelStep != UINT32_MAX)
+ InsertConfigInteger(pCPUM, "MaxIntelFamilyModelStep", uMaxIntelFamilyModelStep);
+ }
+
+ /* CPU Portability level, */
+ ULONG uCpuIdPortabilityLevel = 0;
+ hrc = pMachine->COMGETTER(CPUIDPortabilityLevel)(&uCpuIdPortabilityLevel); H();
+ InsertConfigInteger(pCPUM, "PortableCpuIdLevel", uCpuIdPortabilityLevel);
+
+ /* Physical Address Extension (PAE) */
+ BOOL fEnablePAE = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_PAE, &fEnablePAE); H();
+ fEnablePAE |= fIsGuest64Bit;
+ InsertConfigInteger(pRoot, "EnablePAE", fEnablePAE);
+
+ /* 64-bit guests (long mode) */
+ InsertConfigInteger(pCPUM, "Enable64bit", fIsGuest64Bit);
+
+ /* APIC/X2APIC configuration */
+ BOOL fEnableAPIC = true;
+ BOOL fEnableX2APIC = true;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_APIC, &fEnableAPIC); H();
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_X2APIC, &fEnableX2APIC); H();
+ if (fEnableX2APIC)
+ Assert(fEnableAPIC);
+
+ /* CPUM profile name. */
+ InsertConfigString(pCPUM, "GuestCpuName", bstrCpuProfile);
+
+ /*
+ * Temporary(?) hack to make sure we emulate the ancient 16-bit CPUs
+ * correctly. There are way too many #UDs we'll miss using VT-x,
+ * raw-mode or qemu for the 186 and 286, while we'll get undefined opcodes
+ * dead wrong on 8086 (see http://www.os2museum.com/wp/undocumented-8086-opcodes/).
+ */
+ if ( bstrCpuProfile.equals("Intel 80386") /* just for now */
+ || bstrCpuProfile.equals("Intel 80286")
+ || bstrCpuProfile.equals("Intel 80186")
+ || bstrCpuProfile.equals("Nec V20")
+ || bstrCpuProfile.equals("Intel 8086") )
+ {
+ InsertConfigInteger(pEM, "IemExecutesAll", true);
+ if (!bstrCpuProfile.equals("Intel 80386"))
+ {
+ fEnableAPIC = false;
+ fIOAPIC = false;
+ }
+ fEnableX2APIC = false;
+ }
+
+ /* Adjust firmware APIC handling to stay within the VCPU limits. */
+ if (uFwAPIC == 2 && !fEnableX2APIC)
+ {
+ if (fEnableAPIC)
+ uFwAPIC = 1;
+ else
+ uFwAPIC = 0;
+ LogRel(("Limiting the firmware APIC level from x2APIC to %s\n", fEnableAPIC ? "APIC" : "Disabled"));
+ }
+ else if (uFwAPIC == 1 && !fEnableAPIC)
+ {
+ uFwAPIC = 0;
+ LogRel(("Limiting the firmware APIC level from APIC to Disabled\n"));
+ }
+
+ /* Speculation Control. */
+ BOOL fSpecCtrl = FALSE;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_SpecCtrl, &fSpecCtrl); H();
+ InsertConfigInteger(pCPUM, "SpecCtrl", fSpecCtrl);
+
+ /* Nested VT-x / AMD-V. */
+ BOOL fNestedHWVirt = FALSE;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_HWVirt, &fNestedHWVirt); H();
+ InsertConfigInteger(pCPUM, "NestedHWVirt", fNestedHWVirt ? true : false);
+
+ /*
+ * Hardware virtualization extensions.
+ */
+ /* Sanitize valid/useful APIC combinations, see @bugref{8868}. */
+ if (!fEnableAPIC)
+ {
+ if (fIsGuest64Bit)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Cannot disable the APIC for a 64-bit guest."));
+ if (cCpus > 1)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Cannot disable the APIC for an SMP guest."));
+ if (fIOAPIC)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Cannot disable the APIC when the I/O APIC is present."));
+ }
+
+ BOOL fHMEnabled;
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_Enabled, &fHMEnabled); H();
+ if (cCpus > 1 && !fHMEnabled)
+ {
+ LogRel(("Forced fHMEnabled to TRUE by SMP guest.\n"));
+ fHMEnabled = TRUE;
+ }
+
+ BOOL fHMForced;
+ fHMEnabled = fHMForced = TRUE;
+ LogRel(("fHMForced=true - No raw-mode support in this build!\n"));
+ if (!fHMForced) /* No need to query if already forced above. */
+ {
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_Force, &fHMForced); H();
+ if (fHMForced)
+ LogRel(("fHMForced=true - HWVirtExPropertyType_Force\n"));
+ }
+ InsertConfigInteger(pRoot, "HMEnabled", fHMEnabled);
+
+ /* /HM/xyz */
+ PCFGMNODE pHM;
+ InsertConfigNode(pRoot, "HM", &pHM);
+ InsertConfigInteger(pHM, "HMForced", fHMForced);
+ if (fHMEnabled)
+ {
+ /* Indicate whether 64-bit guests are supported or not. */
+ InsertConfigInteger(pHM, "64bitEnabled", fIsGuest64Bit);
+
+ /** @todo Not exactly pretty to check strings; VBOXOSTYPE would be better,
+ but that requires quite a bit of API change in Main. */
+ if ( fIOAPIC
+ && ( osTypeId == "WindowsNT4"
+ || osTypeId == "Windows2000"
+ || osTypeId == "WindowsXP"
+ || osTypeId == "Windows2003"))
+ {
+ /* Only allow TPR patching for NT, Win2k, XP and Windows Server 2003. (32 bits mode)
+ * We may want to consider adding more guest OSes (Solaris) later on.
+ */
+ InsertConfigInteger(pHM, "TPRPatchingEnabled", 1);
+ }
+ }
+
+ /* HWVirtEx exclusive mode */
+ BOOL fHMExclusive = true;
+ hrc = systemProperties->COMGETTER(ExclusiveHwVirt)(&fHMExclusive); H();
+ InsertConfigInteger(pHM, "Exclusive", fHMExclusive);
+
+ /* Nested paging (VT-x/AMD-V) */
+ BOOL fEnableNestedPaging = false;
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_NestedPaging, &fEnableNestedPaging); H();
+ InsertConfigInteger(pHM, "EnableNestedPaging", fEnableNestedPaging);
+
+ /* Large pages; requires nested paging */
+ BOOL fEnableLargePages = false;
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_LargePages, &fEnableLargePages); H();
+ InsertConfigInteger(pHM, "EnableLargePages", fEnableLargePages);
+
+ /* VPID (VT-x) */
+ BOOL fEnableVPID = false;
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_VPID, &fEnableVPID); H();
+ InsertConfigInteger(pHM, "EnableVPID", fEnableVPID);
+
+ /* Unrestricted execution aka UX (VT-x) */
+ BOOL fEnableUX = false;
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_UnrestrictedExecution, &fEnableUX); H();
+ InsertConfigInteger(pHM, "EnableUX", fEnableUX);
+
+ /* Virtualized VMSAVE/VMLOAD (AMD-V) */
+ BOOL fVirtVmsaveVmload = true;
+ hrc = host->GetProcessorFeature(ProcessorFeature_VirtVmsaveVmload, &fVirtVmsaveVmload); H();
+ InsertConfigInteger(pHM, "SvmVirtVmsaveVmload", fVirtVmsaveVmload);
+
+ /* Indirect branch prediction boundraries. */
+ BOOL fIBPBOnVMExit = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_IBPBOnVMExit, &fIBPBOnVMExit); H();
+ InsertConfigInteger(pHM, "IBPBOnVMExit", fIBPBOnVMExit);
+
+ BOOL fIBPBOnVMEntry = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_IBPBOnVMEntry, &fIBPBOnVMEntry); H();
+ InsertConfigInteger(pHM, "IBPBOnVMEntry", fIBPBOnVMEntry);
+
+ BOOL fSpecCtrlByHost = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_SpecCtrlByHost, &fSpecCtrlByHost); H();
+ InsertConfigInteger(pHM, "SpecCtrlByHost", fSpecCtrlByHost);
+
+ BOOL fL1DFlushOnSched = true;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_L1DFlushOnEMTScheduling, &fL1DFlushOnSched); H();
+ InsertConfigInteger(pHM, "L1DFlushOnSched", fL1DFlushOnSched);
+
+ BOOL fL1DFlushOnVMEntry = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_L1DFlushOnVMEntry, &fL1DFlushOnVMEntry); H();
+ InsertConfigInteger(pHM, "L1DFlushOnVMEntry", fL1DFlushOnVMEntry);
+
+ BOOL fMDSClearOnSched = true;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_MDSClearOnEMTScheduling, &fMDSClearOnSched); H();
+ InsertConfigInteger(pHM, "MDSClearOnSched", fMDSClearOnSched);
+
+ BOOL fMDSClearOnVMEntry = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_MDSClearOnVMEntry, &fMDSClearOnVMEntry); H();
+ InsertConfigInteger(pHM, "MDSClearOnVMEntry", fMDSClearOnVMEntry);
+
+ /* Reset overwrite. */
+ mfTurnResetIntoPowerOff = GetExtraDataBoth(virtualBox, pMachine,
+ "VBoxInternal2/TurnResetIntoPowerOff", &strTmp)->equals("1");
+ if (mfTurnResetIntoPowerOff)
+ InsertConfigInteger(pRoot, "PowerOffInsteadOfReset", 1);
+
+ /* Use NEM rather than HM. */
+ BOOL fUseNativeApi = false;
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_UseNativeApi, &fUseNativeApi); H();
+ InsertConfigInteger(pHM, "UseNEMInstead", fUseNativeApi);
+
+ /* Enable workaround for missing TLB flush for OS/2 guests, see ticketref:20625. */
+ if (osTypeId.startsWith("OS2"))
+ InsertConfigInteger(pHM, "MissingOS2TlbFlushWorkaround", 1);
+
+ /*
+ * NEM
+ */
+ PCFGMNODE pNEM;
+ InsertConfigNode(pRoot, "NEM", &pNEM);
+ InsertConfigInteger(pNEM, "Allow64BitGuests", fIsGuest64Bit);
+
+ /*
+ * Paravirt. provider.
+ */
+ PCFGMNODE pParavirtNode;
+ InsertConfigNode(pRoot, "GIM", &pParavirtNode);
+ const char *pcszParavirtProvider;
+ bool fGimDeviceNeeded = true;
+ switch (enmParavirtProvider)
+ {
+ case ParavirtProvider_None:
+ pcszParavirtProvider = "None";
+ fGimDeviceNeeded = false;
+ break;
+
+ case ParavirtProvider_Minimal:
+ pcszParavirtProvider = "Minimal";
+ break;
+
+ case ParavirtProvider_HyperV:
+ pcszParavirtProvider = "HyperV";
+ break;
+
+ case ParavirtProvider_KVM:
+ pcszParavirtProvider = "KVM";
+ break;
+
+ default:
+ AssertMsgFailed(("Invalid enmParavirtProvider=%d\n", enmParavirtProvider));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, N_("Invalid paravirt. provider '%d'"),
+ enmParavirtProvider);
+ }
+ InsertConfigString(pParavirtNode, "Provider", pcszParavirtProvider);
+
+ /*
+ * Parse paravirt. debug options.
+ */
+ bool fGimDebug = false;
+ com::Utf8Str strGimDebugAddress = "127.0.0.1";
+ uint32_t uGimDebugPort = 50000;
+ if (strParavirtDebug.isNotEmpty())
+ {
+ /* Hyper-V debug options. */
+ if (enmParavirtProvider == ParavirtProvider_HyperV)
+ {
+ bool fGimHvDebug = false;
+ com::Utf8Str strGimHvVendor;
+ bool fGimHvVsIf = false;
+ bool fGimHvHypercallIf = false;
+
+ size_t uPos = 0;
+ com::Utf8Str strDebugOptions = strParavirtDebug;
+ com::Utf8Str strKey;
+ com::Utf8Str strVal;
+ while ((uPos = strDebugOptions.parseKeyValue(strKey, strVal, uPos)) != com::Utf8Str::npos)
+ {
+ if (strKey == "enabled")
+ {
+ if (strVal.toUInt32() == 1)
+ {
+ /* Apply defaults.
+ The defaults are documented in the user manual,
+ changes need to be reflected accordingly. */
+ fGimHvDebug = true;
+ strGimHvVendor = "Microsoft Hv";
+ fGimHvVsIf = true;
+ fGimHvHypercallIf = false;
+ }
+ /* else: ignore, i.e. don't assert below with 'enabled=0'. */
+ }
+ else if (strKey == "address")
+ strGimDebugAddress = strVal;
+ else if (strKey == "port")
+ uGimDebugPort = strVal.toUInt32();
+ else if (strKey == "vendor")
+ strGimHvVendor = strVal;
+ else if (strKey == "vsinterface")
+ fGimHvVsIf = RT_BOOL(strVal.toUInt32());
+ else if (strKey == "hypercallinterface")
+ fGimHvHypercallIf = RT_BOOL(strVal.toUInt32());
+ else
+ {
+ AssertMsgFailed(("Unrecognized Hyper-V debug option '%s'\n", strKey.c_str()));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Unrecognized Hyper-V debug option '%s' in '%s'"), strKey.c_str(),
+ strDebugOptions.c_str());
+ }
+ }
+
+ /* Update HyperV CFGM node with active debug options. */
+ if (fGimHvDebug)
+ {
+ PCFGMNODE pHvNode;
+ InsertConfigNode(pParavirtNode, "HyperV", &pHvNode);
+ InsertConfigString(pHvNode, "VendorID", strGimHvVendor);
+ InsertConfigInteger(pHvNode, "VSInterface", fGimHvVsIf ? 1 : 0);
+ InsertConfigInteger(pHvNode, "HypercallDebugInterface", fGimHvHypercallIf ? 1 : 0);
+ fGimDebug = true;
+ }
+ }
+ }
+
+ /*
+ * Guest Compatibility Manager.
+ */
+ PCFGMNODE pGcmNode;
+ uint32_t u32FixerSet = 0;
+ InsertConfigNode(pRoot, "GCM", &pGcmNode);
+ /* OS/2 and Win9x guests can run DOS apps so they get
+ * the DOS specific fixes as well.
+ */
+ if (fOs2Guest)
+ u32FixerSet = GCMFIXER_DBZ_DOS | GCMFIXER_DBZ_OS2;
+ else if (fW9xGuest)
+ u32FixerSet = GCMFIXER_DBZ_DOS | GCMFIXER_DBZ_WIN9X;
+ else if (fDosGuest)
+ u32FixerSet = GCMFIXER_DBZ_DOS;
+ InsertConfigInteger(pGcmNode, "FixerSet", u32FixerSet);
+
+
+ /*
+ * MM values.
+ */
+ PCFGMNODE pMM;
+ InsertConfigNode(pRoot, "MM", &pMM);
+ InsertConfigInteger(pMM, "CanUseLargerHeap", chipsetType == ChipsetType_ICH9);
+
+ /*
+ * PDM config.
+ * Load drivers in VBoxC.[so|dll]
+ */
+ PCFGMNODE pPDM;
+ PCFGMNODE pNode;
+ PCFGMNODE pMod;
+ InsertConfigNode(pRoot, "PDM", &pPDM);
+ InsertConfigNode(pPDM, "Devices", &pNode);
+ InsertConfigNode(pPDM, "Drivers", &pNode);
+ InsertConfigNode(pNode, "VBoxC", &pMod);
+#ifdef VBOX_WITH_XPCOM
+ // VBoxC is located in the components subdirectory
+ char szPathVBoxC[RTPATH_MAX];
+ vrc = RTPathAppPrivateArch(szPathVBoxC, RTPATH_MAX - sizeof("/components/VBoxC")); AssertRC(vrc);
+ strcat(szPathVBoxC, "/components/VBoxC");
+ InsertConfigString(pMod, "Path", szPathVBoxC);
+#else
+ InsertConfigString(pMod, "Path", "VBoxC");
+#endif
+
+
+ /*
+ * Block cache settings.
+ */
+ PCFGMNODE pPDMBlkCache;
+ InsertConfigNode(pPDM, "BlkCache", &pPDMBlkCache);
+
+ /* I/O cache size */
+ ULONG ioCacheSize = 5;
+ hrc = pMachine->COMGETTER(IOCacheSize)(&ioCacheSize); H();
+ InsertConfigInteger(pPDMBlkCache, "CacheSize", ioCacheSize * _1M);
+
+ /*
+ * Bandwidth groups.
+ */
+ ComPtr<IBandwidthControl> bwCtrl;
+
+ hrc = pMachine->COMGETTER(BandwidthControl)(bwCtrl.asOutParam()); H();
+
+ com::SafeIfaceArray<IBandwidthGroup> bwGroups;
+ hrc = bwCtrl->GetAllBandwidthGroups(ComSafeArrayAsOutParam(bwGroups)); H();
+
+ PCFGMNODE pAc;
+ InsertConfigNode(pPDM, "AsyncCompletion", &pAc);
+ PCFGMNODE pAcFile;
+ InsertConfigNode(pAc, "File", &pAcFile);
+ PCFGMNODE pAcFileBwGroups;
+ InsertConfigNode(pAcFile, "BwGroups", &pAcFileBwGroups);
+#ifdef VBOX_WITH_NETSHAPER
+ PCFGMNODE pNetworkShaper;
+ InsertConfigNode(pPDM, "NetworkShaper", &pNetworkShaper);
+ PCFGMNODE pNetworkBwGroups;
+ InsertConfigNode(pNetworkShaper, "BwGroups", &pNetworkBwGroups);
+#endif /* VBOX_WITH_NETSHAPER */
+
+ for (size_t i = 0; i < bwGroups.size(); i++)
+ {
+ Bstr strName;
+ hrc = bwGroups[i]->COMGETTER(Name)(strName.asOutParam()); H();
+ if (strName.isEmpty())
+ return pVMM->pfnVMR3SetError(pUVM, VERR_CFGM_NO_NODE, RT_SRC_POS, N_("No bandwidth group name specified"));
+
+ BandwidthGroupType_T enmType = BandwidthGroupType_Null;
+ hrc = bwGroups[i]->COMGETTER(Type)(&enmType); H();
+ LONG64 cMaxBytesPerSec = 0;
+ hrc = bwGroups[i]->COMGETTER(MaxBytesPerSec)(&cMaxBytesPerSec); H();
+
+ if (enmType == BandwidthGroupType_Disk)
+ {
+ PCFGMNODE pBwGroup;
+ InsertConfigNode(pAcFileBwGroups, Utf8Str(strName).c_str(), &pBwGroup);
+ InsertConfigInteger(pBwGroup, "Max", cMaxBytesPerSec);
+ InsertConfigInteger(pBwGroup, "Start", cMaxBytesPerSec);
+ InsertConfigInteger(pBwGroup, "Step", 0);
+ }
+#ifdef VBOX_WITH_NETSHAPER
+ else if (enmType == BandwidthGroupType_Network)
+ {
+ /* Network bandwidth groups. */
+ PCFGMNODE pBwGroup;
+ InsertConfigNode(pNetworkBwGroups, Utf8Str(strName).c_str(), &pBwGroup);
+ InsertConfigInteger(pBwGroup, "Max", cMaxBytesPerSec);
+ }
+#endif /* VBOX_WITH_NETSHAPER */
+ }
+
+ /*
+ * Devices
+ */
+ PCFGMNODE pDevices = NULL; /* /Devices */
+ PCFGMNODE pDev = NULL; /* /Devices/Dev/ */
+ PCFGMNODE pInst = NULL; /* /Devices/Dev/0/ */
+ PCFGMNODE pCfg = NULL; /* /Devices/Dev/.../Config/ */
+ PCFGMNODE pLunL0 = NULL; /* /Devices/Dev/0/LUN#0/ */
+ PCFGMNODE pLunL1 = NULL; /* /Devices/Dev/0/LUN#0/AttachedDriver/ */
+ PCFGMNODE pBiosCfg = NULL; /* /Devices/pcbios/0/Config/ */
+ PCFGMNODE pNetBootCfg = NULL; /* /Devices/pcbios/0/Config/NetBoot/ */
+
+ InsertConfigNode(pRoot, "Devices", &pDevices);
+
+ /*
+ * GIM Device
+ */
+ if (fGimDeviceNeeded)
+ {
+ InsertConfigNode(pDevices, "GIMDev", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ //InsertConfigNode(pInst, "Config", &pCfg);
+
+ if (fGimDebug)
+ {
+ InsertConfigNode(pInst, "LUN#998", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "UDP");
+ InsertConfigNode(pLunL0, "Config", &pLunL1);
+ InsertConfigString(pLunL1, "ServerAddress", strGimDebugAddress);
+ InsertConfigInteger(pLunL1, "ServerPort", uGimDebugPort);
+ }
+ }
+
+ /*
+ * PC Arch.
+ */
+ InsertConfigNode(pDevices, "pcarch", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ /*
+ * The time offset
+ */
+ LONG64 timeOffset;
+ hrc = biosSettings->COMGETTER(TimeOffset)(&timeOffset); H();
+ PCFGMNODE pTMNode;
+ InsertConfigNode(pRoot, "TM", &pTMNode);
+ InsertConfigInteger(pTMNode, "UTCOffset", timeOffset * 1000000);
+
+ /*
+ * DMA
+ */
+ InsertConfigNode(pDevices, "8237A", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+
+ /*
+ * PCI buses.
+ */
+ uint32_t uIocPCIAddress, uHbcPCIAddress;
+ switch (chipsetType)
+ {
+ default:
+ AssertFailed();
+ RT_FALL_THRU();
+ case ChipsetType_PIIX3:
+ /* Create the base for adding bridges on demand */
+ InsertConfigNode(pDevices, "pcibridge", NULL);
+
+ InsertConfigNode(pDevices, "pci", &pDev);
+ uHbcPCIAddress = (0x0 << 16) | 0;
+ uIocPCIAddress = (0x1 << 16) | 0; // ISA controller
+ break;
+ case ChipsetType_ICH9:
+ /* Create the base for adding bridges on demand */
+ InsertConfigNode(pDevices, "ich9pcibridge", NULL);
+
+ InsertConfigNode(pDevices, "ich9pci", &pDev);
+ uHbcPCIAddress = (0x1e << 16) | 0;
+ uIocPCIAddress = (0x1f << 16) | 0; // LPC controller
+ break;
+ }
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "IOAPIC", fIOAPIC);
+ if (chipsetType == ChipsetType_ICH9)
+ {
+ /* Provide MCFG info */
+ InsertConfigInteger(pCfg, "McfgBase", uMcfgBase);
+ InsertConfigInteger(pCfg, "McfgLength", cbMcfgLength);
+
+#ifdef VBOX_WITH_PCI_PASSTHROUGH
+ /* Add PCI passthrough devices */
+ hrc = i_attachRawPCIDevices(pUVM, pBusMgr, pDevices); H();
+#endif
+
+ if (enmIommuType == IommuType_AMD)
+ {
+ /* AMD IOMMU. */
+ InsertConfigNode(pDevices, "iommu-amd", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ hrc = pBusMgr->assignPCIDevice("iommu-amd", pInst); H();
+
+ /* The AMD IOMMU device needs to know which PCI slot it's in, see @bugref{9654#c104}. */
+ {
+ PCIBusAddress Address;
+ if (pBusMgr->findPCIAddress("iommu-amd", 0, Address))
+ {
+ uint32_t const u32IommuAddress = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "PCIAddress", u32IommuAddress);
+ }
+ else
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Failed to find PCI address of the assigned IOMMU device!"));
+ }
+
+ PCIBusAddress PCIAddr = PCIBusAddress((int32_t)uIoApicPciAddress);
+ hrc = pBusMgr->assignPCIDevice("sb-ioapic", NULL /* pCfg */, PCIAddr, true /*fGuestAddressRequired*/); H();
+ }
+ else if (enmIommuType == IommuType_Intel)
+ {
+ /* Intel IOMMU. */
+ InsertConfigNode(pDevices, "iommu-intel", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ hrc = pBusMgr->assignPCIDevice("iommu-intel", pInst); H();
+
+ PCIBusAddress PCIAddr = PCIBusAddress((int32_t)uIoApicPciAddress);
+ hrc = pBusMgr->assignPCIDevice("sb-ioapic", NULL /* pCfg */, PCIAddr, true /*fGuestAddressRequired*/); H();
+ }
+ }
+
+ /*
+ * Enable the following devices: HPET, SMC and LPC on MacOS X guests or on ICH9 chipset
+ */
+
+ /*
+ * High Precision Event Timer (HPET)
+ */
+ BOOL fHPETEnabled;
+ /* Other guests may wish to use HPET too, but MacOS X not functional without it */
+ hrc = pMachine->COMGETTER(HPETEnabled)(&fHPETEnabled); H();
+ /* so always enable HPET in extended profile */
+ fHPETEnabled |= fOsXGuest;
+ /* HPET is always present on ICH9 */
+ fHPETEnabled |= (chipsetType == ChipsetType_ICH9);
+ if (fHPETEnabled)
+ {
+ InsertConfigNode(pDevices, "hpet", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "ICH9", (chipsetType == ChipsetType_ICH9) ? 1 : 0); /* boolean */
+ }
+
+ /*
+ * System Management Controller (SMC)
+ */
+ BOOL fSmcEnabled;
+ fSmcEnabled = fOsXGuest;
+ if (fSmcEnabled)
+ {
+ InsertConfigNode(pDevices, "smc", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ bool fGetKeyFromRealSMC;
+ Utf8Str strKey;
+ vrc = getSmcDeviceKey(virtualBox, pMachine, &strKey, &fGetKeyFromRealSMC);
+ AssertRCReturn(vrc, vrc);
+
+ if (!fGetKeyFromRealSMC)
+ InsertConfigString(pCfg, "DeviceKey", strKey);
+ InsertConfigInteger(pCfg, "GetKeyFromRealSMC", fGetKeyFromRealSMC);
+ }
+
+ /*
+ * Low Pin Count (LPC) bus
+ */
+ BOOL fLpcEnabled;
+ /** @todo implement appropriate getter */
+ fLpcEnabled = fOsXGuest || (chipsetType == ChipsetType_ICH9);
+ if (fLpcEnabled)
+ {
+ InsertConfigNode(pDevices, "lpc", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ hrc = pBusMgr->assignPCIDevice("lpc", pInst); H();
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ }
+
+ BOOL fShowRtc;
+ fShowRtc = fOsXGuest || (chipsetType == ChipsetType_ICH9);
+
+ /*
+ * PS/2 keyboard & mouse.
+ */
+ InsertConfigNode(pDevices, "pckbd", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ KeyboardHIDType_T aKbdHID;
+ hrc = pMachine->COMGETTER(KeyboardHIDType)(&aKbdHID); H();
+ if (aKbdHID != KeyboardHIDType_None)
+ {
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "KeyboardQueue");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "QueueSize", 64);
+
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "MainKeyboard");
+ }
+
+ PointingHIDType_T aPointingHID;
+ hrc = pMachine->COMGETTER(PointingHIDType)(&aPointingHID); H();
+ if (aPointingHID != PointingHIDType_None)
+ {
+ InsertConfigNode(pInst, "LUN#1", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "MouseQueue");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "QueueSize", 128);
+
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "MainMouse");
+ }
+
+ /*
+ * i8254 Programmable Interval Timer And Dummy Speaker
+ */
+ InsertConfigNode(pDevices, "i8254", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+#ifdef DEBUG
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+#endif
+
+ /*
+ * i8259 Programmable Interrupt Controller.
+ */
+ InsertConfigNode(pDevices, "i8259", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ /*
+ * Advanced Programmable Interrupt Controller.
+ * SMP: Each CPU has a LAPIC, but we have a single device representing all LAPICs states,
+ * thus only single insert
+ */
+ if (fEnableAPIC)
+ {
+ InsertConfigNode(pDevices, "apic", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "IOAPIC", fIOAPIC);
+ PDMAPICMODE enmAPICMode = PDMAPICMODE_APIC;
+ if (fEnableX2APIC)
+ enmAPICMode = PDMAPICMODE_X2APIC;
+ else if (!fEnableAPIC)
+ enmAPICMode = PDMAPICMODE_NONE;
+ InsertConfigInteger(pCfg, "Mode", enmAPICMode);
+ InsertConfigInteger(pCfg, "NumCPUs", cCpus);
+
+ if (fIOAPIC)
+ {
+ /*
+ * I/O Advanced Programmable Interrupt Controller.
+ */
+ InsertConfigNode(pDevices, "ioapic", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "NumCPUs", cCpus);
+ if (enmIommuType == IommuType_AMD)
+ InsertConfigInteger(pCfg, "PCIAddress", uIoApicPciAddress);
+ else if (enmIommuType == IommuType_Intel)
+ {
+ InsertConfigString(pCfg, "ChipType", "DMAR");
+ InsertConfigInteger(pCfg, "PCIAddress", uIoApicPciAddress);
+ }
+ }
+ }
+
+ /*
+ * RTC MC146818.
+ */
+ InsertConfigNode(pDevices, "mc146818", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+ BOOL fRTCUseUTC;
+ hrc = pMachine->COMGETTER(RTCUseUTC)(&fRTCUseUTC); H();
+ InsertConfigInteger(pCfg, "UseUTC", fRTCUseUTC ? 1 : 0);
+
+ /*
+ * VGA.
+ */
+ ComPtr<IGraphicsAdapter> pGraphicsAdapter;
+ hrc = pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam()); H();
+ GraphicsControllerType_T enmGraphicsController;
+ hrc = pGraphicsAdapter->COMGETTER(GraphicsControllerType)(&enmGraphicsController); H();
+ switch (enmGraphicsController)
+ {
+ case GraphicsControllerType_Null:
+ break;
+#ifdef VBOX_WITH_VMSVGA
+ case GraphicsControllerType_VMSVGA:
+ InsertConfigInteger(pHM, "LovelyMesaDrvWorkaround", 1); /* hits someone else logging backdoor. */
+ InsertConfigInteger(pNEM, "LovelyMesaDrvWorkaround", 1); /* hits someone else logging backdoor. */
+ RT_FALL_THROUGH();
+ case GraphicsControllerType_VBoxSVGA:
+#endif
+ case GraphicsControllerType_VBoxVGA:
+ vrc = i_configGraphicsController(pDevices, enmGraphicsController, pBusMgr, pMachine, pGraphicsAdapter, biosSettings,
+ RT_BOOL(fHMEnabled));
+ if (FAILED(vrc))
+ return vrc;
+ break;
+ default:
+ AssertMsgFailed(("Invalid graphicsController=%d\n", enmGraphicsController));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Invalid graphics controller type '%d'"), enmGraphicsController);
+ }
+
+ /*
+ * Firmware.
+ */
+ FirmwareType_T eFwType = FirmwareType_BIOS;
+ hrc = pMachine->COMGETTER(FirmwareType)(&eFwType); H();
+
+#ifdef VBOX_WITH_EFI
+ BOOL fEfiEnabled = (eFwType >= FirmwareType_EFI) && (eFwType <= FirmwareType_EFIDUAL);
+#else
+ BOOL fEfiEnabled = false;
+#endif
+ if (!fEfiEnabled)
+ {
+ /*
+ * PC Bios.
+ */
+ InsertConfigNode(pDevices, "pcbios", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pBiosCfg);
+ InsertConfigInteger(pBiosCfg, "NumCPUs", cCpus);
+ InsertConfigString(pBiosCfg, "HardDiskDevice", "piix3ide");
+ InsertConfigString(pBiosCfg, "FloppyDevice", "i82078");
+ InsertConfigInteger(pBiosCfg, "IOAPIC", fIOAPIC);
+ InsertConfigInteger(pBiosCfg, "APIC", uFwAPIC);
+ BOOL fPXEDebug;
+ hrc = biosSettings->COMGETTER(PXEDebugEnabled)(&fPXEDebug); H();
+ InsertConfigInteger(pBiosCfg, "PXEDebug", fPXEDebug);
+ InsertConfigBytes(pBiosCfg, "UUID", &HardwareUuid,sizeof(HardwareUuid));
+ BOOL fUuidLe;
+ hrc = biosSettings->COMGETTER(SMBIOSUuidLittleEndian)(&fUuidLe); H();
+ InsertConfigInteger(pBiosCfg, "UuidLe", fUuidLe);
+ InsertConfigNode(pBiosCfg, "NetBoot", &pNetBootCfg);
+ InsertConfigInteger(pBiosCfg, "McfgBase", uMcfgBase);
+ InsertConfigInteger(pBiosCfg, "McfgLength", cbMcfgLength);
+
+ AssertMsgReturn(SchemaDefs::MaxBootPosition <= 9, ("Too many boot devices %d\n", SchemaDefs::MaxBootPosition),
+ VERR_INVALID_PARAMETER);
+
+ for (ULONG pos = 1; pos <= SchemaDefs::MaxBootPosition; ++pos)
+ {
+ DeviceType_T enmBootDevice;
+ hrc = pMachine->GetBootOrder(pos, &enmBootDevice); H();
+
+ char szParamName[] = "BootDeviceX";
+ szParamName[sizeof(szParamName) - 2] = (char)(pos - 1 + '0');
+
+ const char *pszBootDevice;
+ switch (enmBootDevice)
+ {
+ case DeviceType_Null:
+ pszBootDevice = "NONE";
+ break;
+ case DeviceType_HardDisk:
+ pszBootDevice = "IDE";
+ break;
+ case DeviceType_DVD:
+ pszBootDevice = "DVD";
+ break;
+ case DeviceType_Floppy:
+ pszBootDevice = "FLOPPY";
+ break;
+ case DeviceType_Network:
+ pszBootDevice = "LAN";
+ break;
+ default:
+ AssertMsgFailed(("Invalid enmBootDevice=%d\n", enmBootDevice));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Invalid boot device '%d'"), enmBootDevice);
+ }
+ InsertConfigString(pBiosCfg, szParamName, pszBootDevice);
+ }
+
+ /** @todo @bugref{7145}: We might want to enable this by default for new VMs. For now,
+ * this is required for Windows 2012 guests. */
+ if (osTypeId == "Windows2012_64")
+ InsertConfigInteger(pBiosCfg, "DmiExposeMemoryTable", 1); /* boolean */
+ }
+ else
+ {
+ /* Autodetect firmware type, basing on guest type */
+ if (eFwType == FirmwareType_EFI)
+ eFwType = fIsGuest64Bit ? FirmwareType_EFI64 : FirmwareType_EFI32;
+ bool const f64BitEntry = eFwType == FirmwareType_EFI64;
+
+ Assert(eFwType == FirmwareType_EFI64 || eFwType == FirmwareType_EFI32 || eFwType == FirmwareType_EFIDUAL);
+#ifdef VBOX_WITH_EFI_IN_DD2
+ const char *pszEfiRomFile = eFwType == FirmwareType_EFIDUAL ? "VBoxEFIDual.fd"
+ : eFwType == FirmwareType_EFI32 ? "VBoxEFI32.fd"
+ : "VBoxEFI64.fd";
+#else
+ Utf8Str efiRomFile;
+ vrc = findEfiRom(virtualBox, eFwType, &efiRomFile);
+ AssertRCReturn(vrc, vrc);
+ const char *pszEfiRomFile = efiRomFile.c_str();
+#endif
+
+ /* Get boot args */
+ Utf8Str bootArgs;
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiBootArgs", &bootArgs);
+
+ /* Get device props */
+ Utf8Str deviceProps;
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiDeviceProps", &deviceProps);
+
+ /* Get NVRAM file name */
+ Utf8Str strNvram = mptrNvramStore->i_getNonVolatileStorageFile();
+
+ BOOL fUuidLe;
+ hrc = biosSettings->COMGETTER(SMBIOSUuidLittleEndian)(&fUuidLe); H();
+
+ /* Get graphics mode settings */
+ uint32_t u32GraphicsMode = UINT32_MAX;
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiGraphicsMode", &strTmp);
+ if (strTmp.isEmpty())
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiGopMode", &strTmp);
+ if (!strTmp.isEmpty())
+ u32GraphicsMode = strTmp.toUInt32();
+
+ /* Get graphics resolution settings, with some sanity checking */
+ Utf8Str strResolution;
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiGraphicsResolution", &strResolution);
+ if (!strResolution.isEmpty())
+ {
+ size_t pos = strResolution.find("x");
+ if (pos != strResolution.npos)
+ {
+ Utf8Str strH, strV;
+ strH.assignEx(strResolution, 0, pos);
+ strV.assignEx(strResolution, pos+1, strResolution.length()-pos-1);
+ uint32_t u32H = strH.toUInt32();
+ uint32_t u32V = strV.toUInt32();
+ if (u32H == 0 || u32V == 0)
+ strResolution.setNull();
+ }
+ else
+ strResolution.setNull();
+ }
+ else
+ {
+ uint32_t u32H = 0;
+ uint32_t u32V = 0;
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiHorizontalResolution", &strTmp);
+ if (strTmp.isEmpty())
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiUgaHorizontalResolution", &strTmp);
+ if (!strTmp.isEmpty())
+ u32H = strTmp.toUInt32();
+
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiVerticalResolution", &strTmp);
+ if (strTmp.isEmpty())
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiUgaVerticalResolution", &strTmp);
+ if (!strTmp.isEmpty())
+ u32V = strTmp.toUInt32();
+ if (u32H != 0 && u32V != 0)
+ strResolution = Utf8StrFmt("%ux%u", u32H, u32V);
+ }
+
+ /*
+ * EFI subtree.
+ */
+ InsertConfigNode(pDevices, "efi", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "NumCPUs", cCpus);
+ InsertConfigInteger(pCfg, "McfgBase", uMcfgBase);
+ InsertConfigInteger(pCfg, "McfgLength", cbMcfgLength);
+ InsertConfigString(pCfg, "EfiRom", pszEfiRomFile);
+ InsertConfigString(pCfg, "BootArgs", bootArgs);
+ InsertConfigString(pCfg, "DeviceProps", deviceProps);
+ InsertConfigInteger(pCfg, "IOAPIC", fIOAPIC);
+ InsertConfigInteger(pCfg, "APIC", uFwAPIC);
+ InsertConfigBytes(pCfg, "UUID", &HardwareUuid,sizeof(HardwareUuid));
+ InsertConfigInteger(pCfg, "UuidLe", fUuidLe);
+ InsertConfigInteger(pCfg, "64BitEntry", f64BitEntry); /* boolean */
+ InsertConfigString(pCfg, "NvramFile", strNvram);
+ if (u32GraphicsMode != UINT32_MAX)
+ InsertConfigInteger(pCfg, "GraphicsMode", u32GraphicsMode);
+ if (!strResolution.isEmpty())
+ InsertConfigString(pCfg, "GraphicsResolution", strResolution);
+
+ /* For OS X guests we'll force passing host's DMI info to the guest */
+ if (fOsXGuest)
+ {
+ InsertConfigInteger(pCfg, "DmiUseHostInfo", 1);
+ InsertConfigInteger(pCfg, "DmiExposeMemoryTable", 1);
+ }
+
+ /* Attach the NVRAM storage driver. */
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "NvramStore");
+ }
+
+ /*
+ * The USB Controllers.
+ */
+ com::SafeIfaceArray<IUSBController> usbCtrls;
+ hrc = pMachine->COMGETTER(USBControllers)(ComSafeArrayAsOutParam(usbCtrls));
+ bool fOhciPresent = false; /**< Flag whether at least one OHCI controller is present. */
+ bool fXhciPresent = false; /**< Flag whether at least one XHCI controller is present. */
+
+ if (SUCCEEDED(hrc))
+ {
+ for (size_t i = 0; i < usbCtrls.size(); ++i)
+ {
+ USBControllerType_T enmCtrlType;
+ vrc = usbCtrls[i]->COMGETTER(Type)(&enmCtrlType); H();
+ if (enmCtrlType == USBControllerType_OHCI)
+ {
+ fOhciPresent = true;
+ break;
+ }
+ else if (enmCtrlType == USBControllerType_XHCI)
+ {
+ fXhciPresent = true;
+ break;
+ }
+ }
+ }
+ else if (hrc != E_NOTIMPL)
+ {
+ H();
+ }
+
+ /*
+ * Currently EHCI is only enabled when an OHCI or XHCI controller is present as well.
+ */
+ if (fOhciPresent || fXhciPresent)
+ mfVMHasUsbController = true;
+
+ PCFGMNODE pUsbDevices = NULL; /**< Required for USB storage controller later. */
+ if (mfVMHasUsbController)
+ {
+ for (size_t i = 0; i < usbCtrls.size(); ++i)
+ {
+ USBControllerType_T enmCtrlType;
+ vrc = usbCtrls[i]->COMGETTER(Type)(&enmCtrlType); H();
+
+ if (enmCtrlType == USBControllerType_OHCI)
+ {
+ InsertConfigNode(pDevices, "usb-ohci", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ hrc = pBusMgr->assignPCIDevice("usb-ohci", pInst); H();
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "VUSBRootHub");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ /*
+ * Attach the status driver.
+ */
+ i_attachStatusDriver(pInst, DeviceType_USB, 0, 0, NULL, NULL, NULL, 0);
+ }
+#ifdef VBOX_WITH_EHCI
+ else if (enmCtrlType == USBControllerType_EHCI)
+ {
+ InsertConfigNode(pDevices, "usb-ehci", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ hrc = pBusMgr->assignPCIDevice("usb-ehci", pInst); H();
+
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "VUSBRootHub");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ /*
+ * Attach the status driver.
+ */
+ i_attachStatusDriver(pInst, DeviceType_USB, 0, 0, NULL, NULL, NULL, 0);
+ }
+#endif
+ else if (enmCtrlType == USBControllerType_XHCI)
+ {
+ InsertConfigNode(pDevices, "usb-xhci", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ hrc = pBusMgr->assignPCIDevice("usb-xhci", pInst); H();
+
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "VUSBRootHub");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ InsertConfigNode(pInst, "LUN#1", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "VUSBRootHub");
+ InsertConfigNode(pLunL1, "Config", &pCfg);
+
+ /*
+ * Attach the status driver.
+ */
+ i_attachStatusDriver(pInst, DeviceType_USB, 0, 1, NULL, NULL, NULL, 0);
+ }
+ } /* for every USB controller. */
+
+
+ /*
+ * Virtual USB Devices.
+ */
+ InsertConfigNode(pRoot, "USB", &pUsbDevices);
+
+#ifdef VBOX_WITH_USB
+ {
+ /*
+ * Global USB options, currently unused as we'll apply the 2.0 -> 1.1 morphing
+ * on a per device level now.
+ */
+ InsertConfigNode(pUsbDevices, "USBProxy", &pCfg);
+ InsertConfigNode(pCfg, "GlobalConfig", &pCfg);
+ // This globally enables the 2.0 -> 1.1 device morphing of proxied devices to keep windows quiet.
+ //InsertConfigInteger(pCfg, "Force11Device", true);
+ // The following breaks stuff, but it makes MSDs work in vista. (I include it here so
+ // that it's documented somewhere.) Users needing it can use:
+ // VBoxManage setextradata "myvm" "VBoxInternal/USB/USBProxy/GlobalConfig/Force11PacketSize" 1
+ //InsertConfigInteger(pCfg, "Force11PacketSize", true);
+ }
+#endif
+
+#ifdef VBOX_WITH_USB_CARDREADER
+ BOOL aEmulatedUSBCardReaderEnabled = FALSE;
+ hrc = pMachine->COMGETTER(EmulatedUSBCardReaderEnabled)(&aEmulatedUSBCardReaderEnabled); H();
+ if (aEmulatedUSBCardReaderEnabled)
+ {
+ InsertConfigNode(pUsbDevices, "CardReader", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+# ifdef VBOX_WITH_USB_CARDREADER_TEST
+ InsertConfigString(pLunL0, "Driver", "DrvDirectCardReader");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+# else
+ InsertConfigString(pLunL0, "Driver", "UsbCardReader");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+# endif
+ }
+#endif
+
+ /* Virtual USB Mouse/Tablet */
+ if ( aPointingHID == PointingHIDType_USBMouse
+ || aPointingHID == PointingHIDType_USBTablet
+ || aPointingHID == PointingHIDType_USBMultiTouch
+ || aPointingHID == PointingHIDType_USBMultiTouchScreenPlusPad)
+ {
+ InsertConfigNode(pUsbDevices, "HidMouse", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ if (aPointingHID == PointingHIDType_USBMouse)
+ InsertConfigString(pCfg, "Mode", "relative");
+ else
+ InsertConfigString(pCfg, "Mode", "absolute");
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "MouseQueue");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "QueueSize", 128);
+
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "MainMouse");
+ }
+ if ( aPointingHID == PointingHIDType_USBMultiTouch
+ || aPointingHID == PointingHIDType_USBMultiTouchScreenPlusPad)
+ {
+ InsertConfigNode(pDev, "1", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ InsertConfigString(pCfg, "Mode", "multitouch");
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "MouseQueue");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "QueueSize", 128);
+
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "MainMouse");
+ }
+ if (aPointingHID == PointingHIDType_USBMultiTouchScreenPlusPad)
+ {
+ InsertConfigNode(pDev, "2", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ InsertConfigString(pCfg, "Mode", "touchpad");
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "MouseQueue");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "QueueSize", 128);
+
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "MainMouse");
+ }
+
+ /* Virtual USB Keyboard */
+ if (aKbdHID == KeyboardHIDType_USBKeyboard)
+ {
+ InsertConfigNode(pUsbDevices, "HidKeyboard", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "KeyboardQueue");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "QueueSize", 64);
+
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "MainKeyboard");
+ }
+ }
+
+ /*
+ * Storage controllers.
+ */
+ com::SafeIfaceArray<IStorageController> ctrls;
+ PCFGMNODE aCtrlNodes[StorageControllerType_VirtioSCSI + 1] = {};
+ hrc = pMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls)); H();
+
+ bool fFdcEnabled = false;
+ for (size_t i = 0; i < ctrls.size(); ++i)
+ {
+ DeviceType_T *paLedDevType = NULL;
+
+ StorageControllerType_T enmCtrlType;
+ hrc = ctrls[i]->COMGETTER(ControllerType)(&enmCtrlType); H();
+ AssertRelease((unsigned)enmCtrlType < RT_ELEMENTS(aCtrlNodes)
+ || enmCtrlType == StorageControllerType_USB);
+
+ StorageBus_T enmBus;
+ hrc = ctrls[i]->COMGETTER(Bus)(&enmBus); H();
+
+ Bstr controllerName;
+ hrc = ctrls[i]->COMGETTER(Name)(controllerName.asOutParam()); H();
+
+ ULONG ulInstance = 999;
+ hrc = ctrls[i]->COMGETTER(Instance)(&ulInstance); H();
+
+ BOOL fUseHostIOCache;
+ hrc = ctrls[i]->COMGETTER(UseHostIOCache)(&fUseHostIOCache); H();
+
+ BOOL fBootable;
+ hrc = ctrls[i]->COMGETTER(Bootable)(&fBootable); H();
+
+ PCFGMNODE pCtlInst = NULL;
+ const char *pszCtrlDev = i_storageControllerTypeToStr(enmCtrlType);
+ if (enmCtrlType != StorageControllerType_USB)
+ {
+ /* /Devices/<ctrldev>/ */
+ pDev = aCtrlNodes[enmCtrlType];
+ if (!pDev)
+ {
+ InsertConfigNode(pDevices, pszCtrlDev, &pDev);
+ aCtrlNodes[enmCtrlType] = pDev; /* IDE variants are handled in the switch */
+ }
+
+ /* /Devices/<ctrldev>/<instance>/ */
+ InsertConfigNode(pDev, Utf8StrFmt("%u", ulInstance).c_str(), &pCtlInst);
+
+ /* Device config: /Devices/<ctrldev>/<instance>/<values> & /ditto/Config/<values> */
+ InsertConfigInteger(pCtlInst, "Trusted", 1);
+ InsertConfigNode(pCtlInst, "Config", &pCfg);
+ }
+
+ static const char * const apszBiosConfigScsi[MAX_BIOS_LUN_COUNT] =
+ { "ScsiLUN1", "ScsiLUN2", "ScsiLUN3", "ScsiLUN4" };
+
+ static const char * const apszBiosConfigSata[MAX_BIOS_LUN_COUNT] =
+ { "SataLUN1", "SataLUN2", "SataLUN3", "SataLUN4" };
+
+ switch (enmCtrlType)
+ {
+ case StorageControllerType_LsiLogic:
+ {
+ hrc = pBusMgr->assignPCIDevice("lsilogic", pCtlInst); H();
+
+ InsertConfigInteger(pCfg, "Bootable", fBootable);
+
+ /* BIOS configuration values, first SCSI controller only. */
+ if ( !pBusMgr->hasPCIDevice("lsilogic", 1)
+ && !pBusMgr->hasPCIDevice("buslogic", 0)
+ && !pBusMgr->hasPCIDevice("lsilogicsas", 0)
+ && pBiosCfg)
+ {
+ InsertConfigString(pBiosCfg, "ScsiHardDiskDevice", "lsilogicscsi");
+ hrc = SetBiosDiskInfo(pMachine, pCfg, pBiosCfg, controllerName, apszBiosConfigScsi); H();
+ }
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, 15, &paLedDevType,
+ &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ case StorageControllerType_BusLogic:
+ {
+ hrc = pBusMgr->assignPCIDevice("buslogic", pCtlInst); H();
+
+ InsertConfigInteger(pCfg, "Bootable", fBootable);
+
+ /* BIOS configuration values, first SCSI controller only. */
+ if ( !pBusMgr->hasPCIDevice("lsilogic", 0)
+ && !pBusMgr->hasPCIDevice("buslogic", 1)
+ && !pBusMgr->hasPCIDevice("lsilogicsas", 0)
+ && pBiosCfg)
+ {
+ InsertConfigString(pBiosCfg, "ScsiHardDiskDevice", "buslogic");
+ hrc = SetBiosDiskInfo(pMachine, pCfg, pBiosCfg, controllerName, apszBiosConfigScsi); H();
+ }
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, 15, &paLedDevType,
+ &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ case StorageControllerType_IntelAhci:
+ {
+ hrc = pBusMgr->assignPCIDevice("ahci", pCtlInst); H();
+
+ ULONG cPorts = 0;
+ hrc = ctrls[i]->COMGETTER(PortCount)(&cPorts); H();
+ InsertConfigInteger(pCfg, "PortCount", cPorts);
+ InsertConfigInteger(pCfg, "Bootable", fBootable);
+
+ com::SafeIfaceArray<IMediumAttachment> atts;
+ hrc = pMachine->GetMediumAttachmentsOfController(controllerName.raw(),
+ ComSafeArrayAsOutParam(atts)); H();
+
+ /* Configure the hotpluggable flag for the port. */
+ for (unsigned idxAtt = 0; idxAtt < atts.size(); ++idxAtt)
+ {
+ IMediumAttachment *pMediumAtt = atts[idxAtt];
+
+ LONG lPortNum = 0;
+ hrc = pMediumAtt->COMGETTER(Port)(&lPortNum); H();
+
+ BOOL fHotPluggable = FALSE;
+ hrc = pMediumAtt->COMGETTER(HotPluggable)(&fHotPluggable); H();
+ if (SUCCEEDED(hrc))
+ {
+ PCFGMNODE pPortCfg;
+ char szName[24];
+ RTStrPrintf(szName, sizeof(szName), "Port%d", lPortNum);
+
+ InsertConfigNode(pCfg, szName, &pPortCfg);
+ InsertConfigInteger(pPortCfg, "Hotpluggable", fHotPluggable ? 1 : 0);
+ }
+ }
+
+ /* BIOS configuration values, first AHCI controller only. */
+ if ( !pBusMgr->hasPCIDevice("ahci", 1)
+ && pBiosCfg)
+ {
+ InsertConfigString(pBiosCfg, "SataHardDiskDevice", "ahci");
+ hrc = SetBiosDiskInfo(pMachine, pCfg, pBiosCfg, controllerName, apszBiosConfigSata); H();
+ }
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, cPorts - 1, &paLedDevType,
+ &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ case StorageControllerType_PIIX3:
+ case StorageControllerType_PIIX4:
+ case StorageControllerType_ICH6:
+ {
+ /*
+ * IDE (update this when the main interface changes)
+ */
+ hrc = pBusMgr->assignPCIDevice("piix3ide", pCtlInst); H();
+ InsertConfigString(pCfg, "Type", controllerString(enmCtrlType));
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, 3, &paLedDevType,
+ &mapMediumAttachments, pszCtrlDev, ulInstance);
+
+ /* IDE flavors */
+ aCtrlNodes[StorageControllerType_PIIX3] = pDev;
+ aCtrlNodes[StorageControllerType_PIIX4] = pDev;
+ aCtrlNodes[StorageControllerType_ICH6] = pDev;
+ break;
+ }
+
+ case StorageControllerType_I82078:
+ {
+ /*
+ * i82078 Floppy drive controller
+ */
+ fFdcEnabled = true;
+ InsertConfigInteger(pCfg, "IRQ", 6);
+ InsertConfigInteger(pCfg, "DMA", 2);
+ InsertConfigInteger(pCfg, "MemMapped", 0 );
+ InsertConfigInteger(pCfg, "IOBase", 0x3f0);
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, DeviceType_Floppy, 0, 1, NULL,
+ &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ case StorageControllerType_LsiLogicSas:
+ {
+ hrc = pBusMgr->assignPCIDevice("lsilogicsas", pCtlInst); H();
+
+ InsertConfigString(pCfg, "ControllerType", "SAS1068");
+ InsertConfigInteger(pCfg, "Bootable", fBootable);
+
+ /* BIOS configuration values, first SCSI controller only. */
+ if ( !pBusMgr->hasPCIDevice("lsilogic", 0)
+ && !pBusMgr->hasPCIDevice("buslogic", 0)
+ && !pBusMgr->hasPCIDevice("lsilogicsas", 1)
+ && pBiosCfg)
+ {
+ InsertConfigString(pBiosCfg, "ScsiHardDiskDevice", "lsilogicsas");
+ hrc = SetBiosDiskInfo(pMachine, pCfg, pBiosCfg, controllerName, apszBiosConfigScsi); H();
+ }
+
+ ULONG cPorts = 0;
+ hrc = ctrls[i]->COMGETTER(PortCount)(&cPorts); H();
+ InsertConfigInteger(pCfg, "NumPorts", cPorts);
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, 7, &paLedDevType,
+ &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ case StorageControllerType_USB:
+ {
+ if (pUsbDevices)
+ {
+ /*
+ * USB MSDs are handled a bit different as the device instance
+ * doesn't match the storage controller instance but the port.
+ */
+ InsertConfigNode(pUsbDevices, "Msd", &pDev);
+ pCtlInst = pDev;
+ }
+ else
+ return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS,
+ N_("There is no USB controller enabled but there\n"
+ "is at least one USB storage device configured for this VM.\n"
+ "To fix this problem either enable the USB controller or remove\n"
+ "the storage device from the VM"));
+ break;
+ }
+
+ case StorageControllerType_NVMe:
+ {
+ hrc = pBusMgr->assignPCIDevice("nvme", pCtlInst); H();
+
+ ULONG cPorts = 0;
+ hrc = ctrls[i]->COMGETTER(PortCount)(&cPorts); H();
+ InsertConfigInteger(pCfg, "NamespacesMax", cPorts);
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, cPorts - 1, NULL,
+ &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ case StorageControllerType_VirtioSCSI:
+ {
+ hrc = pBusMgr->assignPCIDevice("virtio-scsi", pCtlInst); H();
+
+ ULONG cPorts = 0;
+ hrc = ctrls[i]->COMGETTER(PortCount)(&cPorts); H();
+ InsertConfigInteger(pCfg, "NumTargets", cPorts);
+ InsertConfigInteger(pCfg, "Bootable", fBootable);
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, cPorts - 1, &paLedDevType,
+ &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ default:
+ AssertLogRelMsgFailedReturn(("invalid storage controller type: %d\n", enmCtrlType), VERR_MAIN_CONFIG_CONSTRUCTOR_IPE);
+ }
+
+ /* Attach the media to the storage controllers. */
+ com::SafeIfaceArray<IMediumAttachment> atts;
+ hrc = pMachine->GetMediumAttachmentsOfController(controllerName.raw(),
+ ComSafeArrayAsOutParam(atts)); H();
+
+ /* Builtin I/O cache - per device setting. */
+ BOOL fBuiltinIOCache = true;
+ hrc = pMachine->COMGETTER(IOCacheEnabled)(&fBuiltinIOCache); H();
+
+ bool fInsertDiskIntegrityDrv = false;
+ Bstr strDiskIntegrityFlag;
+ hrc = pMachine->GetExtraData(Bstr("VBoxInternal2/EnableDiskIntegrityDriver").raw(),
+ strDiskIntegrityFlag.asOutParam());
+ if ( hrc == S_OK
+ && strDiskIntegrityFlag == "1")
+ fInsertDiskIntegrityDrv = true;
+
+ for (size_t j = 0; j < atts.size(); ++j)
+ {
+ IMediumAttachment *pMediumAtt = atts[j];
+ vrc = i_configMediumAttachment(pszCtrlDev,
+ ulInstance,
+ enmBus,
+ !!fUseHostIOCache,
+ enmCtrlType == StorageControllerType_NVMe ? false : !!fBuiltinIOCache,
+ fInsertDiskIntegrityDrv,
+ false /* fSetupMerge */,
+ 0 /* uMergeSource */,
+ 0 /* uMergeTarget */,
+ pMediumAtt,
+ mMachineState,
+ NULL /* phrc */,
+ false /* fAttachDetach */,
+ false /* fForceUnmount */,
+ false /* fHotplug */,
+ pUVM,
+ pVMM,
+ paLedDevType,
+ NULL /* ppLunL0 */);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+ H();
+ }
+ H();
+
+ /*
+ * Network adapters
+ */
+#ifdef VMWARE_NET_IN_SLOT_11
+ bool fSwapSlots3and11 = false;
+#endif
+ PCFGMNODE pDevPCNet = NULL; /* PCNet-type devices */
+ InsertConfigNode(pDevices, "pcnet", &pDevPCNet);
+#ifdef VBOX_WITH_E1000
+ PCFGMNODE pDevE1000 = NULL; /* E1000-type devices */
+ InsertConfigNode(pDevices, "e1000", &pDevE1000);
+#endif
+#ifdef VBOX_WITH_VIRTIO
+ PCFGMNODE pDevVirtioNet = NULL; /* Virtio network devices */
+ InsertConfigNode(pDevices, "virtio-net", &pDevVirtioNet);
+#endif /* VBOX_WITH_VIRTIO */
+ PCFGMNODE pDevDP8390 = NULL; /* DP8390-type devices */
+ InsertConfigNode(pDevices, "dp8390", &pDevDP8390);
+ PCFGMNODE pDev3C501 = NULL; /* EtherLink-type devices */
+ InsertConfigNode(pDevices, "3c501", &pDev3C501);
+
+ std::list<BootNic> llBootNics;
+ for (ULONG uInstance = 0; uInstance < maxNetworkAdapters; ++uInstance)
+ {
+ ComPtr<INetworkAdapter> networkAdapter;
+ hrc = pMachine->GetNetworkAdapter(uInstance, networkAdapter.asOutParam()); H();
+ BOOL fEnabledNetAdapter = FALSE;
+ hrc = networkAdapter->COMGETTER(Enabled)(&fEnabledNetAdapter); H();
+ if (!fEnabledNetAdapter)
+ continue;
+
+ /*
+ * The virtual hardware type. Create appropriate device first.
+ */
+ const char *pszAdapterName = "pcnet";
+ NetworkAdapterType_T adapterType;
+ hrc = networkAdapter->COMGETTER(AdapterType)(&adapterType); H();
+ switch (adapterType)
+ {
+ case NetworkAdapterType_Am79C970A:
+ case NetworkAdapterType_Am79C973:
+ case NetworkAdapterType_Am79C960:
+ pDev = pDevPCNet;
+ break;
+#ifdef VBOX_WITH_E1000
+ case NetworkAdapterType_I82540EM:
+ case NetworkAdapterType_I82543GC:
+ case NetworkAdapterType_I82545EM:
+ pDev = pDevE1000;
+ pszAdapterName = "e1000";
+ break;
+#endif
+#ifdef VBOX_WITH_VIRTIO
+ case NetworkAdapterType_Virtio:
+ pDev = pDevVirtioNet;
+ pszAdapterName = "virtio-net";
+ break;
+#endif /* VBOX_WITH_VIRTIO */
+ case NetworkAdapterType_NE1000:
+ case NetworkAdapterType_NE2000:
+ case NetworkAdapterType_WD8003:
+ case NetworkAdapterType_WD8013:
+ case NetworkAdapterType_ELNK2:
+ pDev = pDevDP8390;
+ break;
+ case NetworkAdapterType_ELNK1:
+ pDev = pDev3C501;
+ break;
+ default:
+ AssertMsgFailed(("Invalid network adapter type '%d' for slot '%d'", adapterType, uInstance));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Invalid network adapter type '%d' for slot '%d'"), adapterType, uInstance);
+ }
+
+ InsertConfigNode(pDev, Utf8StrFmt("%u", uInstance).c_str(), &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ /* the first network card gets the PCI ID 3, the next 3 gets 8..10,
+ * next 4 get 16..19. */
+ int iPCIDeviceNo;
+ switch (uInstance)
+ {
+ case 0:
+ iPCIDeviceNo = 3;
+ break;
+ case 1: case 2: case 3:
+ iPCIDeviceNo = uInstance - 1 + 8;
+ break;
+ case 4: case 5: case 6: case 7:
+ iPCIDeviceNo = uInstance - 4 + 16;
+ break;
+ default:
+ /* auto assignment */
+ iPCIDeviceNo = -1;
+ break;
+ }
+#ifdef VMWARE_NET_IN_SLOT_11
+ /*
+ * Dirty hack for PCI slot compatibility with VMWare,
+ * it assigns slot 0x11 to the first network controller.
+ */
+ if (iPCIDeviceNo == 3 && adapterType == NetworkAdapterType_I82545EM)
+ {
+ iPCIDeviceNo = 0x11;
+ fSwapSlots3and11 = true;
+ }
+ else if (iPCIDeviceNo == 0x11 && fSwapSlots3and11)
+ iPCIDeviceNo = 3;
+#endif
+ PCIBusAddress PCIAddr = PCIBusAddress(0, iPCIDeviceNo, 0);
+ hrc = pBusMgr->assignPCIDevice(pszAdapterName, pInst, PCIAddr); H();
+
+ InsertConfigNode(pInst, "Config", &pCfg);
+#ifdef VBOX_WITH_2X_4GB_ADDR_SPACE /* not safe here yet. */ /** @todo Make PCNet ring-0 safe on 32-bit mac kernels! */
+ if (pDev == pDevPCNet)
+ InsertConfigInteger(pCfg, "R0Enabled", false);
+#endif
+ /*
+ * Collect information needed for network booting and add it to the list.
+ */
+ BootNic nic;
+
+ nic.mInstance = uInstance;
+ /* Could be updated by reference, if auto assigned */
+ nic.mPCIAddress = PCIAddr;
+
+ hrc = networkAdapter->COMGETTER(BootPriority)(&nic.mBootPrio); H();
+
+ llBootNics.push_back(nic);
+
+ /*
+ * The virtual hardware type. PCNet supports three types, E1000 three,
+ * but VirtIO only one.
+ */
+ switch (adapterType)
+ {
+ case NetworkAdapterType_Am79C970A:
+ InsertConfigString(pCfg, "ChipType", "Am79C970A");
+ break;
+ case NetworkAdapterType_Am79C973:
+ InsertConfigString(pCfg, "ChipType", "Am79C973");
+ break;
+ case NetworkAdapterType_Am79C960:
+ InsertConfigString(pCfg, "ChipType", "Am79C960");
+ break;
+ case NetworkAdapterType_I82540EM:
+ InsertConfigInteger(pCfg, "AdapterType", 0);
+ break;
+ case NetworkAdapterType_I82543GC:
+ InsertConfigInteger(pCfg, "AdapterType", 1);
+ break;
+ case NetworkAdapterType_I82545EM:
+ InsertConfigInteger(pCfg, "AdapterType", 2);
+ break;
+ case NetworkAdapterType_Virtio:
+ break;
+ case NetworkAdapterType_NE1000:
+ InsertConfigString(pCfg, "DeviceType", "NE1000");
+ break;
+ case NetworkAdapterType_NE2000:
+ InsertConfigString(pCfg, "DeviceType", "NE2000");
+ break;
+ case NetworkAdapterType_WD8003:
+ InsertConfigString(pCfg, "DeviceType", "WD8003");
+ break;
+ case NetworkAdapterType_WD8013:
+ InsertConfigString(pCfg, "DeviceType", "WD8013");
+ break;
+ case NetworkAdapterType_ELNK2:
+ InsertConfigString(pCfg, "DeviceType", "3C503");
+ break;
+ case NetworkAdapterType_ELNK1:
+ break;
+ case NetworkAdapterType_Null: AssertFailedBreak(); /* (compiler warnings) */
+#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
+ case NetworkAdapterType_32BitHack: AssertFailedBreak(); /* (compiler warnings) */
+#endif
+ }
+
+ /*
+ * Get the MAC address and convert it to binary representation
+ */
+ Bstr macAddr;
+ hrc = networkAdapter->COMGETTER(MACAddress)(macAddr.asOutParam()); H();
+ Assert(!macAddr.isEmpty());
+ Utf8Str macAddrUtf8 = macAddr;
+#ifdef VBOX_WITH_CLOUD_NET
+ NetworkAttachmentType_T eAttachmentType;
+ hrc = networkAdapter->COMGETTER(AttachmentType)(&eAttachmentType); H();
+ if (eAttachmentType == NetworkAttachmentType_Cloud)
+ {
+ mGateway.setLocalMacAddress(macAddrUtf8);
+ /* We'll insert cloud MAC later, when it becomes known. */
+ }
+ else
+ {
+#endif
+ char *macStr = (char*)macAddrUtf8.c_str();
+ Assert(strlen(macStr) == 12);
+ RTMAC Mac;
+ RT_ZERO(Mac);
+ char *pMac = (char*)&Mac;
+ for (uint32_t i = 0; i < 6; ++i)
+ {
+ int c1 = *macStr++ - '0';
+ if (c1 > 9)
+ c1 -= 7;
+ int c2 = *macStr++ - '0';
+ if (c2 > 9)
+ c2 -= 7;
+ *pMac++ = (char)(((c1 & 0x0f) << 4) | (c2 & 0x0f));
+ }
+ InsertConfigBytes(pCfg, "MAC", &Mac, sizeof(Mac));
+#ifdef VBOX_WITH_CLOUD_NET
+ }
+#endif
+ /*
+ * Check if the cable is supposed to be unplugged
+ */
+ BOOL fCableConnected;
+ hrc = networkAdapter->COMGETTER(CableConnected)(&fCableConnected); H();
+ InsertConfigInteger(pCfg, "CableConnected", fCableConnected ? 1 : 0);
+
+ /*
+ * Line speed to report from custom drivers
+ */
+ ULONG ulLineSpeed;
+ hrc = networkAdapter->COMGETTER(LineSpeed)(&ulLineSpeed); H();
+ InsertConfigInteger(pCfg, "LineSpeed", ulLineSpeed);
+
+ /*
+ * Attach the status driver.
+ */
+ i_attachStatusDriver(pInst, DeviceType_Network, 0, 0, NULL, NULL, NULL, 0);
+
+ /*
+ * Configure the network card now
+ */
+ bool fIgnoreConnectFailure = mMachineState == MachineState_Restoring;
+ vrc = i_configNetwork(pszAdapterName,
+ uInstance,
+ 0,
+ networkAdapter,
+ pCfg,
+ pLunL0,
+ pInst,
+ false /*fAttachDetach*/,
+ fIgnoreConnectFailure,
+ pUVM,
+ pVMM);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ /*
+ * Build network boot information and transfer it to the BIOS.
+ */
+ if (pNetBootCfg && !llBootNics.empty()) /* NetBoot node doesn't exist for EFI! */
+ {
+ llBootNics.sort(); /* Sort the list by boot priority. */
+
+ char achBootIdx[] = "0";
+ unsigned uBootIdx = 0;
+
+ for (std::list<BootNic>::iterator it = llBootNics.begin(); it != llBootNics.end(); ++it)
+ {
+ /* A NIC with priority 0 is only used if it's first in the list. */
+ if (it->mBootPrio == 0 && uBootIdx != 0)
+ break;
+
+ PCFGMNODE pNetBtDevCfg;
+ achBootIdx[0] = (char)('0' + uBootIdx++); /* Boot device order. */
+ InsertConfigNode(pNetBootCfg, achBootIdx, &pNetBtDevCfg);
+ InsertConfigInteger(pNetBtDevCfg, "NIC", it->mInstance);
+ InsertConfigInteger(pNetBtDevCfg, "PCIBusNo", it->mPCIAddress.miBus);
+ InsertConfigInteger(pNetBtDevCfg, "PCIDeviceNo", it->mPCIAddress.miDevice);
+ InsertConfigInteger(pNetBtDevCfg, "PCIFunctionNo", it->mPCIAddress.miFn);
+ }
+ }
+
+ /*
+ * Serial (UART) Ports
+ */
+ /* serial enabled mask to be passed to dev ACPI */
+ uint16_t auSerialIoPortBase[SchemaDefs::SerialPortCount] = {0};
+ uint8_t auSerialIrq[SchemaDefs::SerialPortCount] = {0};
+ InsertConfigNode(pDevices, "serial", &pDev);
+ for (ULONG ulInstance = 0; ulInstance < SchemaDefs::SerialPortCount; ++ulInstance)
+ {
+ ComPtr<ISerialPort> serialPort;
+ hrc = pMachine->GetSerialPort(ulInstance, serialPort.asOutParam()); H();
+ BOOL fEnabledSerPort = FALSE;
+ if (serialPort)
+ {
+ hrc = serialPort->COMGETTER(Enabled)(&fEnabledSerPort); H();
+ }
+ if (!fEnabledSerPort)
+ {
+ m_aeSerialPortMode[ulInstance] = PortMode_Disconnected;
+ continue;
+ }
+
+ InsertConfigNode(pDev, Utf8StrFmt("%u", ulInstance).c_str(), &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ ULONG ulIRQ;
+ hrc = serialPort->COMGETTER(IRQ)(&ulIRQ); H();
+ InsertConfigInteger(pCfg, "IRQ", ulIRQ);
+ auSerialIrq[ulInstance] = (uint8_t)ulIRQ;
+
+ ULONG ulIOBase;
+ hrc = serialPort->COMGETTER(IOBase)(&ulIOBase); H();
+ InsertConfigInteger(pCfg, "IOBase", ulIOBase);
+ auSerialIoPortBase[ulInstance] = (uint16_t)ulIOBase;
+
+ BOOL fServer;
+ hrc = serialPort->COMGETTER(Server)(&fServer); H();
+ hrc = serialPort->COMGETTER(Path)(bstr.asOutParam()); H();
+ UartType_T eUartType;
+ const char *pszUartType;
+ hrc = serialPort->COMGETTER(UartType)(&eUartType); H();
+ switch (eUartType)
+ {
+ case UartType_U16450: pszUartType = "16450"; break;
+ case UartType_U16750: pszUartType = "16750"; break;
+ default: AssertFailed(); RT_FALL_THRU();
+ case UartType_U16550A: pszUartType = "16550A"; break;
+ }
+ InsertConfigString(pCfg, "UartType", pszUartType);
+
+ PortMode_T eHostMode;
+ hrc = serialPort->COMGETTER(HostMode)(&eHostMode); H();
+
+ m_aeSerialPortMode[ulInstance] = eHostMode;
+ if (eHostMode != PortMode_Disconnected)
+ {
+ vrc = i_configSerialPort(pInst, eHostMode, Utf8Str(bstr).c_str(), RT_BOOL(fServer));
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+ }
+
+ /*
+ * Parallel (LPT) Ports
+ */
+ /* parallel enabled mask to be passed to dev ACPI */
+ uint16_t auParallelIoPortBase[SchemaDefs::ParallelPortCount] = {0};
+ uint8_t auParallelIrq[SchemaDefs::ParallelPortCount] = {0};
+ InsertConfigNode(pDevices, "parallel", &pDev);
+ for (ULONG ulInstance = 0; ulInstance < SchemaDefs::ParallelPortCount; ++ulInstance)
+ {
+ ComPtr<IParallelPort> parallelPort;
+ hrc = pMachine->GetParallelPort(ulInstance, parallelPort.asOutParam()); H();
+ BOOL fEnabledParPort = FALSE;
+ if (parallelPort)
+ {
+ hrc = parallelPort->COMGETTER(Enabled)(&fEnabledParPort); H();
+ }
+ if (!fEnabledParPort)
+ continue;
+
+ InsertConfigNode(pDev, Utf8StrFmt("%u", ulInstance).c_str(), &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ ULONG ulIRQ;
+ hrc = parallelPort->COMGETTER(IRQ)(&ulIRQ); H();
+ InsertConfigInteger(pCfg, "IRQ", ulIRQ);
+ auParallelIrq[ulInstance] = (uint8_t)ulIRQ;
+ ULONG ulIOBase;
+ hrc = parallelPort->COMGETTER(IOBase)(&ulIOBase); H();
+ InsertConfigInteger(pCfg, "IOBase", ulIOBase);
+ auParallelIoPortBase[ulInstance] = (uint16_t)ulIOBase;
+
+ hrc = parallelPort->COMGETTER(Path)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ {
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "HostParallel");
+ InsertConfigNode(pLunL0, "Config", &pLunL1);
+ InsertConfigString(pLunL1, "DevicePath", bstr);
+ }
+ }
+
+ /*
+ * VMM Device
+ */
+ InsertConfigNode(pDevices, "VMMDev", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ hrc = pBusMgr->assignPCIDevice("VMMDev", pInst); H();
+
+ Bstr hwVersion;
+ hrc = pMachine->COMGETTER(HardwareVersion)(hwVersion.asOutParam()); H();
+ if (hwVersion.compare(Bstr("1").raw()) == 0) /* <= 2.0.x */
+ InsertConfigInteger(pCfg, "HeapEnabled", 0);
+ Bstr snapshotFolder;
+ hrc = pMachine->COMGETTER(SnapshotFolder)(snapshotFolder.asOutParam()); H();
+ InsertConfigString(pCfg, "GuestCoreDumpDir", snapshotFolder);
+
+ /* the VMM device's Main driver */
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "HGCM");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ /*
+ * Attach the status driver.
+ */
+ i_attachStatusDriver(pInst, DeviceType_SharedFolder, 0, 0, NULL, NULL, NULL, 0);
+
+ /*
+ * Audio configuration.
+ */
+
+ /*
+ * AC'97 ICH / SoundBlaster16 audio / Intel HD Audio.
+ */
+ ComPtr<IAudioSettings> audioSettings;
+ hrc = pMachine->COMGETTER(AudioSettings)(audioSettings.asOutParam()); H();
+
+ BOOL fAudioEnabled = FALSE;
+ ComPtr<IAudioAdapter> audioAdapter;
+ hrc = audioSettings->COMGETTER(Adapter)(audioAdapter.asOutParam()); H();
+ if (audioAdapter)
+ {
+ hrc = audioAdapter->COMGETTER(Enabled)(&fAudioEnabled); H();
+ }
+
+ if (fAudioEnabled)
+ {
+ AudioControllerType_T enmAudioController;
+ hrc = audioAdapter->COMGETTER(AudioController)(&enmAudioController); H();
+ AudioCodecType_T enmAudioCodec;
+ hrc = audioAdapter->COMGETTER(AudioCodec)(&enmAudioCodec); H();
+
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Device/TimerHz", &strTmp);
+ const uint64_t uTimerHz = strTmp.toUInt64();
+
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Device/BufSizeInMs", &strTmp);
+ const uint64_t uBufSizeInMs = strTmp.toUInt64();
+
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Device/BufSizeOutMs", &strTmp);
+ const uint64_t uBufSizeOutMs = strTmp.toUInt64();
+
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Debug/Enabled", &strTmp);
+ const bool fDebugEnabled = strTmp.equalsIgnoreCase("true") || strTmp.equalsIgnoreCase("1");
+
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Debug/Level", &strTmp);
+ const uint32_t uDebugLevel = strTmp.toUInt32();
+
+ Utf8Str strDebugPathOut;
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Debug/PathOut", &strDebugPathOut);
+
+#ifdef VBOX_WITH_AUDIO_VALIDATIONKIT
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/VaKit/Enabled", &strTmp); /* Deprecated; do not use! */
+ if (strTmp.isEmpty())
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/ValKit/Enabled", &strTmp);
+ /* Whether the Validation Kit audio backend runs as the primary backend.
+ * Can also be used with VBox release builds. */
+ const bool fValKitEnabled = strTmp.equalsIgnoreCase("true") || strTmp.equalsIgnoreCase("1");
+#endif
+ /** @todo Implement an audio device class, similar to the audio backend class, to construct the common stuff
+ * without duplicating (more) code. */
+
+ const char *pszAudioDevice;
+ switch (enmAudioController)
+ {
+ case AudioControllerType_AC97:
+ {
+ /* ICH AC'97. */
+ pszAudioDevice = "ichac97";
+
+ InsertConfigNode(pDevices, pszAudioDevice, &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ hrc = pBusMgr->assignPCIDevice(pszAudioDevice, pInst); H();
+ InsertConfigNode(pInst, "Config", &pCfg);
+ switch (enmAudioCodec)
+ {
+ case AudioCodecType_STAC9700:
+ InsertConfigString(pCfg, "Codec", "STAC9700");
+ break;
+ case AudioCodecType_AD1980:
+ InsertConfigString(pCfg, "Codec", "AD1980");
+ break;
+ default: AssertFailedBreak();
+ }
+ if (uTimerHz)
+ InsertConfigInteger(pCfg, "TimerHz", uTimerHz);
+ if (uBufSizeInMs)
+ InsertConfigInteger(pCfg, "BufSizeInMs", uBufSizeInMs);
+ if (uBufSizeOutMs)
+ InsertConfigInteger(pCfg, "BufSizeOutMs", uBufSizeOutMs);
+ InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled);
+ if (strDebugPathOut.isNotEmpty())
+ InsertConfigString(pCfg, "DebugPathOut", strDebugPathOut);
+ break;
+ }
+ case AudioControllerType_SB16:
+ {
+ /* Legacy SoundBlaster16. */
+ pszAudioDevice = "sb16";
+
+ InsertConfigNode(pDevices, pszAudioDevice, &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "IRQ", 5);
+ InsertConfigInteger(pCfg, "DMA", 1);
+ InsertConfigInteger(pCfg, "DMA16", 5);
+ InsertConfigInteger(pCfg, "Port", 0x220);
+ InsertConfigInteger(pCfg, "Version", 0x0405);
+ if (uTimerHz)
+ InsertConfigInteger(pCfg, "TimerHz", uTimerHz);
+ InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled);
+ if (strDebugPathOut.isNotEmpty())
+ InsertConfigString(pCfg, "DebugPathOut", strDebugPathOut);
+ break;
+ }
+ case AudioControllerType_HDA:
+ {
+ /* Intel HD Audio. */
+ pszAudioDevice = "hda";
+
+ InsertConfigNode(pDevices, pszAudioDevice, &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ hrc = pBusMgr->assignPCIDevice(pszAudioDevice, pInst); H();
+ InsertConfigNode(pInst, "Config", &pCfg);
+ if (uBufSizeInMs)
+ InsertConfigInteger(pCfg, "BufSizeInMs", uBufSizeInMs);
+ if (uBufSizeOutMs)
+ InsertConfigInteger(pCfg, "BufSizeOutMs", uBufSizeOutMs);
+ InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled);
+ if (strDebugPathOut.isNotEmpty())
+ InsertConfigString(pCfg, "DebugPathOut", strDebugPathOut);
+
+ /* macOS guests uses a different HDA variant to make 10.14+ (or maybe 10.13?) recognize the device. */
+ if (fOsXGuest)
+ InsertConfigString(pCfg, "DeviceName", "Intel Sunrise Point");
+ break;
+ }
+ default:
+ pszAudioDevice = "oops";
+ AssertFailedBreak();
+ }
+
+ PCFGMNODE pCfgAudioAdapter = NULL;
+ InsertConfigNode(pInst, "AudioConfig", &pCfgAudioAdapter);
+ SafeArray<BSTR> audioProps;
+ hrc = audioAdapter->COMGETTER(PropertiesList)(ComSafeArrayAsOutParam(audioProps)); H();
+
+ std::list<Utf8Str> audioPropertyNamesList;
+ for (size_t i = 0; i < audioProps.size(); ++i)
+ {
+ Bstr bstrValue;
+ audioPropertyNamesList.push_back(Utf8Str(audioProps[i]));
+ hrc = audioAdapter->GetProperty(audioProps[i], bstrValue.asOutParam());
+ Utf8Str strKey(audioProps[i]);
+ InsertConfigString(pCfgAudioAdapter, strKey.c_str(), bstrValue);
+ }
+
+ /*
+ * The audio driver.
+ */
+ const char *pszAudioDriver = NULL;
+#ifdef VBOX_WITH_AUDIO_VALIDATIONKIT
+ if (fValKitEnabled)
+ {
+ pszAudioDriver = "ValidationKitAudio";
+ LogRel(("Audio: ValidationKit driver active\n"));
+ }
+#endif
+ /* If nothing else was selected before, ask the API. */
+ if (pszAudioDriver == NULL)
+ {
+ AudioDriverType_T enmAudioDriver;
+ hrc = audioAdapter->COMGETTER(AudioDriver)(&enmAudioDriver); H();
+
+ /* The "Default" audio driver needs special treatment, as we need to figure out which driver to use
+ * by default on the current platform. */
+ bool const fUseDefaultDrv = enmAudioDriver == AudioDriverType_Default;
+
+ AudioDriverType_T const enmDefaultAudioDriver = settings::MachineConfigFile::getHostDefaultAudioDriver();
+
+ if (fUseDefaultDrv)
+ {
+ enmAudioDriver = enmDefaultAudioDriver;
+ if (enmAudioDriver == AudioDriverType_Null)
+ LogRel(("Audio: Warning: No default driver detected for current platform -- defaulting to Null audio backend\n"));
+ }
+
+ switch (enmAudioDriver)
+ {
+ case AudioDriverType_Default: /* Can't happen, but handle it anyway. */
+ RT_FALL_THROUGH();
+ case AudioDriverType_Null:
+ pszAudioDriver = "NullAudio";
+ break;
+#ifdef RT_OS_WINDOWS
+# ifdef VBOX_WITH_WINMM
+ case AudioDriverType_WinMM:
+# error "Port WinMM audio backend!" /** @todo Still needed? */
+ break;
+# endif
+ case AudioDriverType_DirectSound:
+ /* Use the Windows Audio Session (WAS) API rather than Direct Sound on Windows
+ versions we've tested it on (currently W7+). Since Vista, Direct Sound has
+ been emulated on top of WAS according to the docs, so better use WAS directly.
+
+ Set extradata value "VBoxInternal2/Audio/WindowsDrv" "dsound" to no use WasAPI.
+
+ Keep this hack for backwards compatibility (introduced < 7.0).
+ */
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/WindowsDrv", &strTmp); H();
+ if ( enmDefaultAudioDriver == AudioDriverType_WAS
+ && ( strTmp.isEmpty()
+ || strTmp.equalsIgnoreCase("was")
+ || strTmp.equalsIgnoreCase("wasapi")) )
+ {
+ /* Nothing to do here, fall through to WAS driver. */
+ }
+ else
+ {
+ pszAudioDriver = "DSoundAudio";
+ break;
+ }
+ RT_FALL_THROUGH();
+ case AudioDriverType_WAS:
+ if (enmDefaultAudioDriver == AudioDriverType_WAS) /* WAS supported? */
+ pszAudioDriver = "HostAudioWas";
+ else if (enmDefaultAudioDriver == AudioDriverType_DirectSound)
+ {
+ LogRel(("Audio: Warning: Windows Audio Session (WAS) not supported, defaulting to DirectSound backend\n"));
+ pszAudioDriver = "DSoundAudio";
+ }
+ break;
+#endif /* RT_OS_WINDOWS */
+#ifdef RT_OS_SOLARIS
+ case AudioDriverType_SolAudio:
+ /* Should not happen, as the Solaris Audio backend is not around anymore.
+ * Remove this sometime later. */
+ LogRel(("Audio: Warning: Solaris Audio is deprecated, please switch to OSS!\n"));
+ LogRel(("Audio: Automatically setting host audio backend to OSS\n"));
+
+ /* Manually set backend to OSS for now. */
+ pszAudioDriver = "OSSAudio";
+ break;
+#endif
+#ifdef VBOX_WITH_AUDIO_OSS
+ case AudioDriverType_OSS:
+ pszAudioDriver = "OSSAudio";
+ break;
+#endif
+#ifdef VBOX_WITH_AUDIO_ALSA
+ case AudioDriverType_ALSA:
+ pszAudioDriver = "ALSAAudio";
+ break;
+#endif
+#ifdef VBOX_WITH_AUDIO_PULSE
+ case AudioDriverType_Pulse:
+ pszAudioDriver = "PulseAudio";
+ break;
+#endif
+#ifdef RT_OS_DARWIN
+ case AudioDriverType_CoreAudio:
+ pszAudioDriver = "CoreAudio";
+ break;
+#endif
+ default:
+ pszAudioDriver = "oops";
+ AssertFailedBreak();
+ }
+
+ if (fUseDefaultDrv)
+ LogRel(("Audio: Detected default audio driver type is '%s'\n", pszAudioDriver));
+ }
+
+ BOOL fAudioEnabledIn = FALSE;
+ hrc = audioAdapter->COMGETTER(EnabledIn)(&fAudioEnabledIn); H();
+ BOOL fAudioEnabledOut = FALSE;
+ hrc = audioAdapter->COMGETTER(EnabledOut)(&fAudioEnabledOut); H();
+
+ unsigned idxAudioLun = 0;
+
+ InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun);
+ i_configAudioDriver(virtualBox, pMachine, pLunL0, pszAudioDriver, !!fAudioEnabledIn, !!fAudioEnabledOut);
+ idxAudioLun++;
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ /* Insert dummy audio driver to have the LUN configured. */
+ InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun);
+ InsertConfigString(pLunL0, "Driver", "AUDIO");
+ AudioDriverCfg DrvCfgVRDE(pszAudioDevice, 0 /* Instance */, idxAudioLun, "AudioVRDE",
+ !!fAudioEnabledIn, !!fAudioEnabledOut);
+ vrc = mAudioVRDE->InitializeConfig(&DrvCfgVRDE);
+ AssertRCStmt(vrc, throw ConfigError(__FUNCTION__, vrc, "mAudioVRDE->InitializeConfig failed"));
+ idxAudioLun++;
+#endif
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ /* Insert dummy audio driver to have the LUN configured. */
+ InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun);
+ InsertConfigString(pLunL0, "Driver", "AUDIO");
+ AudioDriverCfg DrvCfgVideoRec(pszAudioDevice, 0 /* Instance */, idxAudioLun, "AudioVideoRec",
+ false /*a_fEnabledIn*/, true /*a_fEnabledOut*/);
+ vrc = mRecording.mAudioRec->InitializeConfig(&DrvCfgVideoRec);
+ AssertRCStmt(vrc, throw ConfigError(__FUNCTION__, vrc, "Recording.mAudioRec->InitializeConfig failed"));
+ idxAudioLun++;
+#endif
+
+ if (fDebugEnabled)
+ {
+#ifdef VBOX_WITH_AUDIO_DEBUG
+# ifdef VBOX_WITH_AUDIO_VALIDATIONKIT
+ /*
+ * When both, ValidationKit and Debug mode (for audio) are enabled,
+ * skip configuring the Debug audio driver, as both modes can
+ * mess with the audio data and would lead to side effects.
+ *
+ * The ValidationKit audio driver has precedence over the Debug audio driver.
+ *
+ * This also can (and will) be used in VBox release builds.
+ */
+ if (fValKitEnabled)
+ {
+ LogRel(("Audio: Warning: ValidationKit running and Debug mode enabled -- disabling Debug driver\n"));
+ }
+ else /* Debug mode active -- run both (nice for catching errors / doing development). */
+ {
+ /*
+ * The ValidationKit backend.
+ */
+ InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun);
+ i_configAudioDriver(virtualBox, pMachine, pLunL0, "ValidationKitAudio",
+ !!fAudioEnabledIn, !!fAudioEnabledOut);
+ idxAudioLun++;
+# endif /* VBOX_WITH_AUDIO_VALIDATIONKIT */
+ /*
+ * The Debug audio backend.
+ */
+ InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun);
+ i_configAudioDriver(virtualBox, pMachine, pLunL0, "DebugAudio",
+ !!fAudioEnabledIn, !!fAudioEnabledOut);
+ idxAudioLun++;
+# ifdef VBOX_WITH_AUDIO_VALIDATIONKIT
+ }
+# endif /* VBOX_WITH_AUDIO_VALIDATIONKIT */
+#endif /* VBOX_WITH_AUDIO_DEBUG */
+
+ /*
+ * Tweak the logging groups.
+ */
+ Utf8Str strGroups("drv_audio.e.l.l2.l3.f"
+ " audio_mixer.e.l.l2.l3.f"
+ " dev_hda_codec.e.l.l2.l3.f"
+ " dev_hda.e.l.l2.l3.f"
+ " dev_ac97.e.l.l2.l3.f"
+ " dev_sb16.e.l.l2.l3.f");
+
+ LogRel(("Audio: Debug level set to %RU32\n", uDebugLevel));
+
+ switch (uDebugLevel)
+ {
+ case 0:
+ strGroups += " drv_host_audio.e.l.l2.l3.f";
+ break;
+ case 1:
+ RT_FALL_THROUGH();
+ case 2:
+ RT_FALL_THROUGH();
+ case 3:
+ strGroups += " drv_host_audio.e.l.l2.l3.f+audio_test.e.l.l2.l3.f";
+ break;
+ case 4:
+ RT_FALL_THROUGH();
+ default:
+ strGroups += " drv_host_audio.e.l.l2.l3.l4.f+audio_test.e.l.l2.l3.l4.f";
+ break;
+ }
+
+ vrc = RTLogGroupSettings(RTLogRelGetDefaultInstance(), strGroups.c_str());
+ if (RT_FAILURE(vrc))
+ LogRel(("Audio: Setting debug logging failed, vrc=%Rrc\n", vrc));
+ }
+ }
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+ /*
+ * Shared Clipboard.
+ */
+ {
+ ClipboardMode_T enmClipboardMode = ClipboardMode_Disabled;
+ hrc = pMachine->COMGETTER(ClipboardMode)(&enmClipboardMode); H();
+# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ BOOL fFileTransfersEnabled;
+ hrc = pMachine->COMGETTER(ClipboardFileTransfersEnabled)(&fFileTransfersEnabled); H();
+#endif
+
+ /* Load the service */
+ vrc = pVMMDev->hgcmLoadService("VBoxSharedClipboard", "VBoxSharedClipboard");
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Shared Clipboard: Service loaded\n"));
+
+ /* Set initial clipboard mode. */
+ vrc = i_changeClipboardMode(enmClipboardMode);
+ AssertLogRelMsg(RT_SUCCESS(vrc), ("Shared Clipboard: Failed to set initial clipboard mode (%d): vrc=%Rrc\n",
+ enmClipboardMode, vrc));
+
+ /* Setup the service. */
+ VBOXHGCMSVCPARM parm;
+ HGCMSvcSetU32(&parm, !i_useHostClipboard());
+ vrc = pVMMDev->hgcmHostCall("VBoxSharedClipboard", VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, &parm);
+ AssertLogRelMsg(RT_SUCCESS(vrc), ("Shared Clipboard: Failed to set initial headless mode (%RTbool): vrc=%Rrc\n",
+ !i_useHostClipboard(), vrc));
+
+# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ vrc = i_changeClipboardFileTransferMode(RT_BOOL(fFileTransfersEnabled));
+ AssertLogRelMsg(RT_SUCCESS(vrc), ("Shared Clipboard: Failed to set initial file transfers mode (%u): vrc=%Rrc\n",
+ fFileTransfersEnabled, vrc));
+
+ /** @todo Register area callbacks? (See also deregistration todo in Console::i_powerDown.) */
+# endif
+ }
+ else
+ LogRel(("Shared Clipboard: Not available, vrc=%Rrc\n", vrc));
+ vrc = VINF_SUCCESS; /* None of the potential failures above are fatal. */
+ }
+#endif /* VBOX_WITH_SHARED_CLIPBOARD */
+
+ /*
+ * HGCM HostChannel.
+ */
+ {
+ Bstr value;
+ hrc = pMachine->GetExtraData(Bstr("HGCM/HostChannel").raw(),
+ value.asOutParam());
+
+ if ( hrc == S_OK
+ && value == "1")
+ {
+ vrc = pVMMDev->hgcmLoadService("VBoxHostChannel", "VBoxHostChannel");
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("VBoxHostChannel is not available, vrc=%Rrc\n", vrc));
+ /* That is not a fatal failure. */
+ vrc = VINF_SUCCESS;
+ }
+ }
+ }
+
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ /*
+ * Drag and Drop.
+ */
+ {
+ DnDMode_T enmMode = DnDMode_Disabled;
+ hrc = pMachine->COMGETTER(DnDMode)(&enmMode); H();
+
+ /* Load the service */
+ vrc = pVMMDev->hgcmLoadService("VBoxDragAndDropSvc", "VBoxDragAndDropSvc");
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Drag and drop service is not available, vrc=%Rrc\n", vrc));
+ /* That is not a fatal failure. */
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = HGCMHostRegisterServiceExtension(&m_hHgcmSvcExtDragAndDrop, "VBoxDragAndDropSvc",
+ &GuestDnD::notifyDnDDispatcher,
+ GuestDnDInst());
+ if (RT_FAILURE(vrc))
+ Log(("Cannot register VBoxDragAndDropSvc extension, vrc=%Rrc\n", vrc));
+ else
+ {
+ LogRel(("Drag and drop service loaded\n"));
+ vrc = i_changeDnDMode(enmMode);
+ }
+ }
+ }
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+
+#if defined(VBOX_WITH_TPM)
+ /*
+ * Configure the Trusted Platform Module.
+ */
+ ComObjPtr<ITrustedPlatformModule> ptrTpm;
+ TpmType_T enmTpmType = TpmType_None;
+
+ hrc = pMachine->COMGETTER(TrustedPlatformModule)(ptrTpm.asOutParam()); H();
+ hrc = ptrTpm->COMGETTER(Type)(&enmTpmType); H();
+ if (enmTpmType != TpmType_None)
+ {
+ InsertConfigNode(pDevices, "tpm", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+
+ switch (enmTpmType)
+ {
+ case TpmType_v1_2:
+ case TpmType_v2_0:
+ {
+ InsertConfigString(pLunL0, "Driver", "TpmEmuTpms");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "TpmVersion", enmTpmType == TpmType_v1_2 ? 1 : 2);
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "NvramStore");
+ break;
+ }
+ case TpmType_Host:
+ {
+#if defined(RT_OS_LINUX) || defined(RT_OS_WINDOWS)
+ InsertConfigString(pLunL0, "Driver", "TpmHost");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+#endif
+ break;
+ }
+ case TpmType_Swtpm:
+ {
+ Bstr location;
+ hrc = ptrTpm->COMGETTER(Location)(location.asOutParam()); H();
+
+ InsertConfigString(pLunL0, "Driver", "TpmEmu");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigString(pCfg, "Location", location);
+ break;
+ }
+ default:
+ AssertFailedBreak();
+ }
+ }
+#endif
+
+ /*
+ * ACPI
+ */
+ BOOL fACPI;
+ hrc = biosSettings->COMGETTER(ACPIEnabled)(&fACPI); H();
+ if (fACPI)
+ {
+ /* Always show the CPU leafs when we have multiple VCPUs or when the IO-APIC is enabled.
+ * The Windows SMP kernel needs a CPU leaf or else its idle loop will burn cpu cycles; the
+ * intelppm driver refuses to register an idle state handler.
+ * Always show CPU leafs for OS X guests. */
+ BOOL fShowCpu = fOsXGuest;
+ if (cCpus > 1 || fIOAPIC)
+ fShowCpu = true;
+
+ BOOL fCpuHotPlug;
+ hrc = pMachine->COMGETTER(CPUHotPlugEnabled)(&fCpuHotPlug); H();
+
+ InsertConfigNode(pDevices, "acpi", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ hrc = pBusMgr->assignPCIDevice("acpi", pInst); H();
+
+ InsertConfigInteger(pCfg, "NumCPUs", cCpus);
+
+ InsertConfigInteger(pCfg, "IOAPIC", fIOAPIC);
+ InsertConfigInteger(pCfg, "FdcEnabled", fFdcEnabled);
+ InsertConfigInteger(pCfg, "HpetEnabled", fHPETEnabled);
+ InsertConfigInteger(pCfg, "SmcEnabled", fSmcEnabled);
+ InsertConfigInteger(pCfg, "ShowRtc", fShowRtc);
+ if (fOsXGuest && !llBootNics.empty())
+ {
+ BootNic aNic = llBootNics.front();
+ uint32_t u32NicPCIAddr = (aNic.mPCIAddress.miDevice << 16) | aNic.mPCIAddress.miFn;
+ InsertConfigInteger(pCfg, "NicPciAddress", u32NicPCIAddr);
+ }
+ if (fOsXGuest && fAudioEnabled)
+ {
+ PCIBusAddress Address;
+ if (pBusMgr->findPCIAddress("hda", 0, Address))
+ {
+ uint32_t u32AudioPCIAddr = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "AudioPciAddress", u32AudioPCIAddr);
+ }
+ }
+ if (fOsXGuest)
+ {
+ PCIBusAddress Address;
+ if (pBusMgr->findPCIAddress("nvme", 0, Address))
+ {
+ uint32_t u32NvmePCIAddr = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "NvmePciAddress", u32NvmePCIAddr);
+ }
+ }
+ if (enmIommuType == IommuType_AMD)
+ {
+ PCIBusAddress Address;
+ if (pBusMgr->findPCIAddress("iommu-amd", 0, Address))
+ {
+ uint32_t u32IommuAddress = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "IommuAmdEnabled", true);
+ InsertConfigInteger(pCfg, "IommuPciAddress", u32IommuAddress);
+ if (pBusMgr->findPCIAddress("sb-ioapic", 0, Address))
+ {
+ uint32_t const u32SbIoapicAddress = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "SbIoApicPciAddress", u32SbIoapicAddress);
+ }
+ else
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("AMD IOMMU is enabled, but the I/O APIC is not assigned a PCI address!"));
+ }
+ }
+ else if (enmIommuType == IommuType_Intel)
+ {
+ PCIBusAddress Address;
+ if (pBusMgr->findPCIAddress("iommu-intel", 0, Address))
+ {
+ uint32_t u32IommuAddress = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "IommuIntelEnabled", true);
+ InsertConfigInteger(pCfg, "IommuPciAddress", u32IommuAddress);
+ if (pBusMgr->findPCIAddress("sb-ioapic", 0, Address))
+ {
+ uint32_t const u32SbIoapicAddress = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "SbIoApicPciAddress", u32SbIoapicAddress);
+ }
+ else
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Intel IOMMU is enabled, but the I/O APIC is not assigned a PCI address!"));
+ }
+ }
+
+ InsertConfigInteger(pCfg, "IocPciAddress", uIocPCIAddress);
+ if (chipsetType == ChipsetType_ICH9)
+ {
+ InsertConfigInteger(pCfg, "McfgBase", uMcfgBase);
+ InsertConfigInteger(pCfg, "McfgLength", cbMcfgLength);
+ /* 64-bit prefetch window root resource: Only for ICH9 and if PAE or Long Mode is enabled (@bugref{5454}). */
+ if (fIsGuest64Bit || fEnablePAE)
+ InsertConfigInteger(pCfg, "PciPref64Enabled", 1);
+ }
+ InsertConfigInteger(pCfg, "HostBusPciAddress", uHbcPCIAddress);
+ InsertConfigInteger(pCfg, "ShowCpu", fShowCpu);
+ InsertConfigInteger(pCfg, "CpuHotPlug", fCpuHotPlug);
+
+ InsertConfigInteger(pCfg, "Serial0IoPortBase", auSerialIoPortBase[0]);
+ InsertConfigInteger(pCfg, "Serial0Irq", auSerialIrq[0]);
+
+ InsertConfigInteger(pCfg, "Serial1IoPortBase", auSerialIoPortBase[1]);
+ InsertConfigInteger(pCfg, "Serial1Irq", auSerialIrq[1]);
+
+ if (auSerialIoPortBase[2])
+ {
+ InsertConfigInteger(pCfg, "Serial2IoPortBase", auSerialIoPortBase[2]);
+ InsertConfigInteger(pCfg, "Serial2Irq", auSerialIrq[2]);
+ }
+
+ if (auSerialIoPortBase[3])
+ {
+ InsertConfigInteger(pCfg, "Serial3IoPortBase", auSerialIoPortBase[3]);
+ InsertConfigInteger(pCfg, "Serial3Irq", auSerialIrq[3]);
+ }
+
+ InsertConfigInteger(pCfg, "Parallel0IoPortBase", auParallelIoPortBase[0]);
+ InsertConfigInteger(pCfg, "Parallel0Irq", auParallelIrq[0]);
+
+ InsertConfigInteger(pCfg, "Parallel1IoPortBase", auParallelIoPortBase[1]);
+ InsertConfigInteger(pCfg, "Parallel1Irq", auParallelIrq[1]);
+
+#if defined(VBOX_WITH_TPM)
+ switch (enmTpmType)
+ {
+ case TpmType_v1_2:
+ InsertConfigString(pCfg, "TpmMode", "tis1.2");
+ break;
+ case TpmType_v2_0:
+ InsertConfigString(pCfg, "TpmMode", "fifo2.0");
+ break;
+ /** @todo Host and swtpm. */
+ default:
+ break;
+ }
+#endif
+
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "ACPIHost");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ /* Attach the dummy CPU drivers */
+ for (ULONG iCpuCurr = 1; iCpuCurr < cCpus; iCpuCurr++)
+ {
+ BOOL fCpuAttached = true;
+
+ if (fCpuHotPlug)
+ {
+ hrc = pMachine->GetCPUStatus(iCpuCurr, &fCpuAttached); H();
+ }
+
+ if (fCpuAttached)
+ {
+ InsertConfigNode(pInst, Utf8StrFmt("LUN#%u", iCpuCurr).c_str(), &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "ACPICpu");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ }
+ }
+ }
+
+ /*
+ * Configure DBGF (Debug(ger) Facility) and DBGC (Debugger Console).
+ */
+ {
+ PCFGMNODE pDbgf;
+ InsertConfigNode(pRoot, "DBGF", &pDbgf);
+
+ /* Paths to search for debug info and such things. */
+ hrc = pMachine->COMGETTER(SettingsFilePath)(bstr.asOutParam()); H();
+ Utf8Str strSettingsPath(bstr);
+ bstr.setNull();
+ strSettingsPath.stripFilename();
+ strSettingsPath.append("/");
+
+ char szHomeDir[RTPATH_MAX + 1];
+ int vrc2 = RTPathUserHome(szHomeDir, sizeof(szHomeDir) - 1);
+ if (RT_FAILURE(vrc2))
+ szHomeDir[0] = '\0';
+ RTPathEnsureTrailingSeparator(szHomeDir, sizeof(szHomeDir));
+
+
+ Utf8Str strPath;
+ strPath.append(strSettingsPath).append("debug/;");
+ strPath.append(strSettingsPath).append(";");
+ strPath.append("cache*").append(strSettingsPath).append("dbgcache/;"); /* handy for symlinking to actual cache */
+ strPath.append(szHomeDir);
+
+ InsertConfigString(pDbgf, "Path", strPath.c_str());
+
+ /* Tracing configuration. */
+ BOOL fTracingEnabled;
+ hrc = pMachine->COMGETTER(TracingEnabled)(&fTracingEnabled); H();
+ if (fTracingEnabled)
+ InsertConfigInteger(pDbgf, "TracingEnabled", 1);
+
+ hrc = pMachine->COMGETTER(TracingConfig)(bstr.asOutParam()); H();
+ if (fTracingEnabled)
+ InsertConfigString(pDbgf, "TracingConfig", bstr);
+
+ BOOL fAllowTracingToAccessVM;
+ hrc = pMachine->COMGETTER(AllowTracingToAccessVM)(&fAllowTracingToAccessVM); H();
+ if (fAllowTracingToAccessVM)
+ InsertConfigInteger(pPDM, "AllowTracingToAccessVM", 1);
+
+ /* Debugger console config. */
+ PCFGMNODE pDbgc;
+ InsertConfigNode(pRoot, "DBGC", &pDbgc);
+
+ hrc = virtualBox->COMGETTER(HomeFolder)(bstr.asOutParam()); H();
+ Utf8Str strVBoxHome = bstr;
+ bstr.setNull();
+ if (strVBoxHome.isNotEmpty())
+ strVBoxHome.append("/");
+ else
+ {
+ strVBoxHome = szHomeDir;
+ strVBoxHome.append("/.vbox");
+ }
+
+ Utf8Str strFile(strVBoxHome);
+ strFile.append("dbgc-history");
+ InsertConfigString(pDbgc, "HistoryFile", strFile);
+
+ strFile = strSettingsPath;
+ strFile.append("dbgc-init");
+ InsertConfigString(pDbgc, "LocalInitScript", strFile);
+
+ strFile = strVBoxHome;
+ strFile.append("dbgc-init");
+ InsertConfigString(pDbgc, "GlobalInitScript", strFile);
+
+ /*
+ * Configure guest debug settings.
+ */
+ ComObjPtr<IGuestDebugControl> ptrGstDbgCtrl;
+ GuestDebugProvider_T enmGstDbgProvider = GuestDebugProvider_None;
+
+ hrc = pMachine->COMGETTER(GuestDebugControl)(ptrGstDbgCtrl.asOutParam()); H();
+ hrc = ptrGstDbgCtrl->COMGETTER(DebugProvider)(&enmGstDbgProvider); H();
+ if (enmGstDbgProvider != GuestDebugProvider_None)
+ {
+ GuestDebugIoProvider_T enmGstDbgIoProvider = GuestDebugIoProvider_None;
+ hrc = ptrGstDbgCtrl->COMGETTER(DebugIoProvider)(&enmGstDbgIoProvider); H();
+ hrc = ptrGstDbgCtrl->COMGETTER(DebugAddress)(bstr.asOutParam()); H();
+ Utf8Str strAddress = bstr;
+ bstr.setNull();
+
+ ULONG ulPort = 0;
+ hrc = ptrGstDbgCtrl->COMGETTER(DebugPort)(&ulPort); H();
+
+ PCFGMNODE pDbgSettings;
+ InsertConfigNode(pDbgc, "Dbg", &pDbgSettings);
+ InsertConfigString(pDbgSettings, "Address", strAddress);
+ InsertConfigInteger(pDbgSettings, "Port", ulPort);
+
+ switch (enmGstDbgProvider)
+ {
+ case GuestDebugProvider_Native:
+ InsertConfigString(pDbgSettings, "StubType", "Native");
+ break;
+ case GuestDebugProvider_GDB:
+ InsertConfigString(pDbgSettings, "StubType", "Gdb");
+ break;
+ case GuestDebugProvider_KD:
+ InsertConfigString(pDbgSettings, "StubType", "Kd");
+ break;
+ default:
+ AssertFailed();
+ break;
+ }
+
+ switch (enmGstDbgIoProvider)
+ {
+ case GuestDebugIoProvider_TCP:
+ InsertConfigString(pDbgSettings, "Provider", "tcp");
+ break;
+ case GuestDebugIoProvider_UDP:
+ InsertConfigString(pDbgSettings, "Provider", "udp");
+ break;
+ case GuestDebugIoProvider_IPC:
+ InsertConfigString(pDbgSettings, "Provider", "ipc");
+ break;
+ default:
+ AssertFailed();
+ break;
+ }
+ }
+ }
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ pVMM->pfnVMR3SetError(pUVM, x.m_vrc, RT_SRC_POS, "Caught ConfigError: %Rrc - %s", x.m_vrc, x.what());
+ return x.m_vrc;
+ }
+ catch (HRESULT hrcXcpt)
+ {
+ AssertLogRelMsgFailedReturn(("hrc=%Rhrc\n", hrcXcpt), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR);
+ }
+
+#ifdef VBOX_WITH_EXTPACK
+ /*
+ * Call the extension pack hooks if everything went well thus far.
+ */
+ if (RT_SUCCESS(vrc))
+ {
+ pAlock->release();
+ vrc = mptrExtPackManager->i_callAllVmConfigureVmmHooks(this, pVM, pVMM);
+ pAlock->acquire();
+ }
+#endif
+
+ /*
+ * Apply the CFGM overlay.
+ */
+ if (RT_SUCCESS(vrc))
+ vrc = i_configCfgmOverlay(pRoot, virtualBox, pMachine);
+
+ /*
+ * Dump all extradata API settings tweaks, both global and per VM.
+ */
+ if (RT_SUCCESS(vrc))
+ vrc = i_configDumpAPISettingsTweaks(virtualBox, pMachine);
+
+#undef H
+
+ pAlock->release(); /* Avoid triggering the lock order inversion check. */
+
+ /*
+ * Register VM state change handler.
+ */
+ int vrc2 = pVMM->pfnVMR3AtStateRegister(pUVM, Console::i_vmstateChangeCallback, this);
+ AssertRC(vrc2);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ /*
+ * Register VM runtime error handler.
+ */
+ vrc2 = pVMM->pfnVMR3AtRuntimeErrorRegister(pUVM, Console::i_atVMRuntimeErrorCallback, this);
+ AssertRC(vrc2);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ pAlock->acquire();
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ LogFlowFuncLeave();
+
+ return vrc;
+}
+
+/**
+ * Configures an audio driver via CFGM by getting (optional) values from extra data.
+ *
+ * @param pVirtualBox Pointer to IVirtualBox instance.
+ * @param pMachine Pointer to IMachine instance.
+ * @param pLUN Pointer to CFGM node of LUN (the driver) to configure.
+ * @param pszDrvName Name of the driver to configure.
+ * @param fAudioEnabledIn IAudioAdapter::enabledIn value.
+ * @param fAudioEnabledOut IAudioAdapter::enabledOut value.
+ *
+ * @throws ConfigError or HRESULT on if there is trouble.
+ */
+void Console::i_configAudioDriver(IVirtualBox *pVirtualBox, IMachine *pMachine, PCFGMNODE pLUN, const char *pszDrvName,
+ bool fAudioEnabledIn, bool fAudioEnabledOut)
+{
+#define H() AssertLogRelMsgStmt(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), \
+ throw ConfigError(__FUNCTION__, VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR, "line: " RT_XSTR(__LINE__)))
+
+ InsertConfigString(pLUN, "Driver", "AUDIO");
+
+ PCFGMNODE pCfg;
+ InsertConfigNode(pLUN, "Config", &pCfg);
+ InsertConfigString(pCfg, "DriverName", pszDrvName);
+ InsertConfigInteger(pCfg, "InputEnabled", fAudioEnabledIn);
+ InsertConfigInteger(pCfg, "OutputEnabled", fAudioEnabledOut);
+
+ Utf8Str strTmp;
+ GetExtraDataBoth(pVirtualBox, pMachine, "VBoxInternal2/Audio/Debug/Enabled", &strTmp);
+ const uint64_t fDebugEnabled = strTmp.equalsIgnoreCase("true") || strTmp.equalsIgnoreCase("1");
+ if (fDebugEnabled)
+ {
+ InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled);
+
+ Utf8Str strDebugPathOut;
+ GetExtraDataBoth(pVirtualBox, pMachine, "VBoxInternal2/Audio/Debug/PathOut", &strDebugPathOut);
+ InsertConfigString(pCfg, "DebugPathOut", strDebugPathOut.c_str());
+ }
+
+ /*
+ * PCM input parameters (playback + recording).
+ * We have host driver specific ones as: VBoxInternal2/Audio/<DrvName>/<Value>
+ * And global ones for all host drivers: VBoxInternal2/Audio/<Value>
+ */
+ for (unsigned iDir = 0; iDir < 2; iDir++)
+ {
+ static const struct
+ {
+ const char *pszExtraName;
+ const char *pszCfgmName;
+ } s_aToCopy[] =
+ { /* PCM parameters: */
+ { "PCMSampleBit", "PCMSampleBit" },
+ { "PCMSampleHz", "PCMSampleHz" },
+ { "PCMSampleSigned", "PCMSampleSigned" },
+ { "PCMSampleSwapEndian", "PCMSampleSwapEndian" },
+ { "PCMSampleChannels", "PCMSampleChannels" },
+ /* Buffering stuff: */
+ { "PeriodSizeMs", "PeriodSizeMs" },
+ { "BufferSizeMs", "BufferSizeMs" },
+ { "PreBufferSizeMs", "PreBufferSizeMs" },
+ };
+
+ PCFGMNODE pDirNode = NULL;
+ const char *pszDir = iDir == 0 ? "In" : "Out";
+ for (size_t i = 0; i < RT_ELEMENTS(s_aToCopy); i++)
+ {
+ char szExtra[128];
+ RTStrPrintf(szExtra, sizeof(szExtra), "VBoxInternal2/Audio/%s/%s%s", pszDrvName, s_aToCopy[i].pszExtraName, pszDir);
+ GetExtraDataBoth(pVirtualBox, pMachine, szExtra, &strTmp); /* throws hrc */
+ if (strTmp.isEmpty())
+ {
+ RTStrPrintf(szExtra, sizeof(szExtra), "VBoxInternal2/Audio/%s%s", s_aToCopy[i].pszExtraName, pszDir);
+ GetExtraDataBoth(pVirtualBox, pMachine, szExtra, &strTmp);
+ if (strTmp.isEmpty())
+ continue;
+ }
+
+ uint32_t uValue;
+ int vrc = RTStrToUInt32Full(strTmp.c_str(), 0, &uValue);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!pDirNode)
+ InsertConfigNode(pCfg, pszDir, &pDirNode);
+ InsertConfigInteger(pDirNode, s_aToCopy[i].pszCfgmName, uValue);
+ }
+ else
+ LogRel(("Ignoring malformed 32-bit unsigned integer config value '%s' = '%s': %Rrc\n", szExtra, strTmp.c_str(), vrc));
+ }
+ }
+
+ PCFGMNODE pLunL1;
+ InsertConfigNode(pLUN, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", pszDrvName);
+ InsertConfigNode(pLunL1, "Config", &pCfg);
+
+#ifdef RT_OS_WINDOWS
+ if (strcmp(pszDrvName, "HostAudioWas") == 0)
+ {
+ Bstr bstrTmp;
+ HRESULT hrc = pMachine->COMGETTER(Id)(bstrTmp.asOutParam()); H();
+ InsertConfigString(pCfg, "VmUuid", bstrTmp);
+ }
+#endif
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX)
+ if ( strcmp(pszDrvName, "HostAudioWas") == 0
+ || strcmp(pszDrvName, "PulseAudio") == 0)
+ {
+ Bstr bstrTmp;
+ HRESULT hrc = pMachine->COMGETTER(Name)(bstrTmp.asOutParam()); H();
+ InsertConfigString(pCfg, "VmName", bstrTmp);
+ }
+#endif
+
+ LogFlowFunc(("szDrivName=%s\n", pszDrvName));
+
+#undef H
+}
+
+/**
+ * Applies the CFGM overlay as specified by VBoxInternal/XXX extra data
+ * values.
+ *
+ * @returns VBox status code.
+ * @param pRoot The root of the configuration tree.
+ * @param pVirtualBox Pointer to the IVirtualBox interface.
+ * @param pMachine Pointer to the IMachine interface.
+ */
+/* static */
+int Console::i_configCfgmOverlay(PCFGMNODE pRoot, IVirtualBox *pVirtualBox, IMachine *pMachine)
+{
+ /*
+ * CFGM overlay handling.
+ *
+ * Here we check the extra data entries for CFGM values
+ * and create the nodes and insert the values on the fly. Existing
+ * values will be removed and reinserted. CFGM is typed, so by default
+ * we will guess whether it's a string or an integer (byte arrays are
+ * not currently supported). It's possible to override this autodetection
+ * by adding "string:", "integer:" or "bytes:" (future).
+ *
+ * We first perform a run on global extra data, then on the machine
+ * extra data to support global settings with local overrides.
+ */
+ int vrc = VINF_SUCCESS;
+ bool fFirst = true;
+ try
+ {
+ /** @todo add support for removing nodes and byte blobs. */
+ /*
+ * Get the next key
+ */
+ SafeArray<BSTR> aGlobalExtraDataKeys;
+ SafeArray<BSTR> aMachineExtraDataKeys;
+ HRESULT hrc = pVirtualBox->GetExtraDataKeys(ComSafeArrayAsOutParam(aGlobalExtraDataKeys));
+ AssertMsg(SUCCEEDED(hrc), ("VirtualBox::GetExtraDataKeys failed with %Rhrc\n", hrc));
+
+ // remember the no. of global values so we can call the correct method below
+ size_t cGlobalValues = aGlobalExtraDataKeys.size();
+
+ hrc = pMachine->GetExtraDataKeys(ComSafeArrayAsOutParam(aMachineExtraDataKeys));
+ AssertMsg(SUCCEEDED(hrc), ("Machine::GetExtraDataKeys failed with %Rhrc\n", hrc));
+
+ // build a combined list from global keys...
+ std::list<Utf8Str> llExtraDataKeys;
+
+ for (size_t i = 0; i < aGlobalExtraDataKeys.size(); ++i)
+ llExtraDataKeys.push_back(Utf8Str(aGlobalExtraDataKeys[i]));
+ // ... and machine keys
+ for (size_t i = 0; i < aMachineExtraDataKeys.size(); ++i)
+ llExtraDataKeys.push_back(Utf8Str(aMachineExtraDataKeys[i]));
+
+ size_t i2 = 0;
+ for (std::list<Utf8Str>::const_iterator it = llExtraDataKeys.begin();
+ it != llExtraDataKeys.end();
+ ++it, ++i2)
+ {
+ const Utf8Str &strKey = *it;
+
+ /*
+ * We only care about keys starting with "VBoxInternal/" (skip "G:" or "M:")
+ */
+ if (!strKey.startsWith("VBoxInternal/"))
+ continue;
+
+ const char *pszExtraDataKey = strKey.c_str() + sizeof("VBoxInternal/") - 1;
+
+ // get the value
+ Bstr bstrExtraDataValue;
+ if (i2 < cGlobalValues)
+ // this is still one of the global values:
+ hrc = pVirtualBox->GetExtraData(Bstr(strKey).raw(), bstrExtraDataValue.asOutParam());
+ else
+ hrc = pMachine->GetExtraData(Bstr(strKey).raw(), bstrExtraDataValue.asOutParam());
+ if (FAILED(hrc))
+ LogRel(("Warning: Cannot get extra data key %s, rc = %Rhrc\n", strKey.c_str(), hrc));
+
+ if (fFirst)
+ {
+ fFirst = false;
+ LogRel(("Extradata overrides:\n"));
+ }
+ LogRel((" %s=\"%ls\"%s\n", strKey.c_str(), bstrExtraDataValue.raw(), i2 < cGlobalValues ? " (global)" : ""));
+
+ /*
+ * The key will be in the format "Node1/Node2/Value" or simply "Value".
+ * Split the two and get the node, delete the value and create the node
+ * if necessary.
+ */
+ PCFGMNODE pNode;
+ const char *pszCFGMValueName = strrchr(pszExtraDataKey, '/');
+ if (pszCFGMValueName)
+ {
+ /* terminate the node and advance to the value (Utf8Str might not
+ offically like this but wtf) */
+ *(char *)pszCFGMValueName = '\0';
+ ++pszCFGMValueName;
+
+ /* does the node already exist? */
+ pNode = mpVMM->pfnCFGMR3GetChild(pRoot, pszExtraDataKey);
+ if (pNode)
+ mpVMM->pfnCFGMR3RemoveValue(pNode, pszCFGMValueName);
+ else
+ {
+ /* create the node */
+ vrc = mpVMM->pfnCFGMR3InsertNode(pRoot, pszExtraDataKey, &pNode);
+ if (RT_FAILURE(vrc))
+ {
+ AssertLogRelMsgRC(vrc, ("failed to insert node '%s'\n", pszExtraDataKey));
+ continue;
+ }
+ Assert(pNode);
+ }
+ }
+ else
+ {
+ /* root value (no node path). */
+ pNode = pRoot;
+ pszCFGMValueName = pszExtraDataKey;
+ pszExtraDataKey--;
+ mpVMM->pfnCFGMR3RemoveValue(pNode, pszCFGMValueName);
+ }
+
+ /*
+ * Now let's have a look at the value.
+ * Empty strings means that we should remove the value, which we've
+ * already done above.
+ */
+ Utf8Str strCFGMValueUtf8(bstrExtraDataValue);
+ if (strCFGMValueUtf8.isNotEmpty())
+ {
+ uint64_t u64Value;
+
+ /* check for type prefix first. */
+ if (!strncmp(strCFGMValueUtf8.c_str(), RT_STR_TUPLE("string:")))
+ vrc = mpVMM->pfnCFGMR3InsertString(pNode, pszCFGMValueName, strCFGMValueUtf8.c_str() + sizeof("string:") - 1);
+ else if (!strncmp(strCFGMValueUtf8.c_str(), RT_STR_TUPLE("integer:")))
+ {
+ vrc = RTStrToUInt64Full(strCFGMValueUtf8.c_str() + sizeof("integer:") - 1, 0, &u64Value);
+ if (RT_SUCCESS(vrc))
+ vrc = mpVMM->pfnCFGMR3InsertInteger(pNode, pszCFGMValueName, u64Value);
+ }
+ else if (!strncmp(strCFGMValueUtf8.c_str(), RT_STR_TUPLE("bytes:")))
+ {
+ char const *pszBase64 = strCFGMValueUtf8.c_str() + sizeof("bytes:") - 1;
+ ssize_t cbValue = RTBase64DecodedSize(pszBase64, NULL);
+ if (cbValue > 0)
+ {
+ void *pvBytes = RTMemTmpAlloc(cbValue);
+ if (pvBytes)
+ {
+ vrc = RTBase64Decode(pszBase64, pvBytes, cbValue, NULL, NULL);
+ if (RT_SUCCESS(vrc))
+ vrc = mpVMM->pfnCFGMR3InsertBytes(pNode, pszCFGMValueName, pvBytes, cbValue);
+ RTMemTmpFree(pvBytes);
+ }
+ else
+ vrc = VERR_NO_TMP_MEMORY;
+ }
+ else if (cbValue == 0)
+ vrc = mpVMM->pfnCFGMR3InsertBytes(pNode, pszCFGMValueName, NULL, 0);
+ else
+ vrc = VERR_INVALID_BASE64_ENCODING;
+ }
+ /* auto detect type. */
+ else if (RT_SUCCESS(RTStrToUInt64Full(strCFGMValueUtf8.c_str(), 0, &u64Value)))
+ vrc = mpVMM->pfnCFGMR3InsertInteger(pNode, pszCFGMValueName, u64Value);
+ else
+ vrc = mpVMM->pfnCFGMR3InsertString(pNode, pszCFGMValueName, strCFGMValueUtf8.c_str());
+ AssertLogRelMsgRCBreak(vrc, ("failed to insert CFGM value '%s' to key '%s'\n",
+ strCFGMValueUtf8.c_str(), pszExtraDataKey));
+ }
+ }
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+ return vrc;
+}
+
+/**
+ * Dumps the API settings tweaks as specified by VBoxInternal2/XXX extra data
+ * values.
+ *
+ * @returns VBox status code.
+ * @param pVirtualBox Pointer to the IVirtualBox interface.
+ * @param pMachine Pointer to the IMachine interface.
+ */
+/* static */
+int Console::i_configDumpAPISettingsTweaks(IVirtualBox *pVirtualBox, IMachine *pMachine)
+{
+ {
+ SafeArray<BSTR> aGlobalExtraDataKeys;
+ HRESULT hrc = pVirtualBox->GetExtraDataKeys(ComSafeArrayAsOutParam(aGlobalExtraDataKeys));
+ AssertMsg(SUCCEEDED(hrc), ("VirtualBox::GetExtraDataKeys failed with %Rhrc\n", hrc));
+ bool hasKey = false;
+ for (size_t i = 0; i < aGlobalExtraDataKeys.size(); i++)
+ {
+ Utf8Str strKey(aGlobalExtraDataKeys[i]);
+ if (!strKey.startsWith("VBoxInternal2/"))
+ continue;
+
+ Bstr bstrValue;
+ hrc = pVirtualBox->GetExtraData(Bstr(strKey).raw(),
+ bstrValue.asOutParam());
+ if (FAILED(hrc))
+ continue;
+ if (!hasKey)
+ LogRel(("Global extradata API settings:\n"));
+ LogRel((" %s=\"%ls\"\n", strKey.c_str(), bstrValue.raw()));
+ hasKey = true;
+ }
+ }
+
+ {
+ SafeArray<BSTR> aMachineExtraDataKeys;
+ HRESULT hrc = pMachine->GetExtraDataKeys(ComSafeArrayAsOutParam(aMachineExtraDataKeys));
+ AssertMsg(SUCCEEDED(hrc), ("Machine::GetExtraDataKeys failed with %Rhrc\n", hrc));
+ bool hasKey = false;
+ for (size_t i = 0; i < aMachineExtraDataKeys.size(); i++)
+ {
+ Utf8Str strKey(aMachineExtraDataKeys[i]);
+ if (!strKey.startsWith("VBoxInternal2/"))
+ continue;
+
+ Bstr bstrValue;
+ hrc = pMachine->GetExtraData(Bstr(strKey).raw(),
+ bstrValue.asOutParam());
+ if (FAILED(hrc))
+ continue;
+ if (!hasKey)
+ LogRel(("Per-VM extradata API settings:\n"));
+ LogRel((" %s=\"%ls\"\n", strKey.c_str(), bstrValue.raw()));
+ hasKey = true;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+int Console::i_configGraphicsController(PCFGMNODE pDevices,
+ const GraphicsControllerType_T enmGraphicsController,
+ BusAssignmentManager *pBusMgr,
+ const ComPtr<IMachine> &ptrMachine,
+ const ComPtr<IGraphicsAdapter> &ptrGraphicsAdapter,
+ const ComPtr<IBIOSSettings> &ptrBiosSettings,
+ bool fHMEnabled)
+{
+ // InsertConfig* throws
+ try
+ {
+ PCFGMNODE pDev, pInst, pCfg, pLunL0;
+ HRESULT hrc;
+ Bstr bstr;
+ const char *pcszDevice = "vga";
+
+#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR)
+ InsertConfigNode(pDevices, pcszDevice, &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+
+ hrc = pBusMgr->assignPCIDevice(pcszDevice, pInst); H();
+ InsertConfigNode(pInst, "Config", &pCfg);
+ ULONG cVRamMBs;
+ hrc = ptrGraphicsAdapter->COMGETTER(VRAMSize)(&cVRamMBs); H();
+ InsertConfigInteger(pCfg, "VRamSize", cVRamMBs * _1M);
+ ULONG cMonitorCount;
+ hrc = ptrGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitorCount); H();
+ InsertConfigInteger(pCfg, "MonitorCount", cMonitorCount);
+#ifdef VBOX_WITH_2X_4GB_ADDR_SPACE
+ InsertConfigInteger(pCfg, "R0Enabled", fHMEnabled);
+#else
+ NOREF(fHMEnabled);
+#endif
+ BOOL f3DEnabled;
+ hrc = ptrGraphicsAdapter->COMGETTER(Accelerate3DEnabled)(&f3DEnabled); H();
+ InsertConfigInteger(pCfg, "3DEnabled", f3DEnabled);
+
+ i_attachStatusDriver(pInst, DeviceType_Graphics3D, 0, 0, NULL, NULL, NULL, 0);
+
+#ifdef VBOX_WITH_VMSVGA
+ if ( enmGraphicsController == GraphicsControllerType_VMSVGA
+ || enmGraphicsController == GraphicsControllerType_VBoxSVGA)
+ {
+ InsertConfigInteger(pCfg, "VMSVGAEnabled", true);
+ if (enmGraphicsController == GraphicsControllerType_VMSVGA)
+ {
+ InsertConfigInteger(pCfg, "VMSVGAPciBarLayout", true);
+ InsertConfigInteger(pCfg, "VMSVGAPciId", true);
+ }
+# ifdef VBOX_WITH_VMSVGA3D
+ InsertConfigInteger(pCfg, "VMSVGA3dEnabled", f3DEnabled);
+# else
+ LogRel(("VMSVGA3d not available in this build!\n"));
+# endif /* VBOX_WITH_VMSVGA3D */
+ }
+#else
+ RT_NOREF(enmGraphicsController);
+#endif /* VBOX_WITH_VMSVGA */
+
+ /* Custom VESA mode list */
+ unsigned cModes = 0;
+ for (unsigned iMode = 1; iMode <= 16; ++iMode)
+ {
+ char szExtraDataKey[sizeof("CustomVideoModeXX")];
+ RTStrPrintf(szExtraDataKey, sizeof(szExtraDataKey), "CustomVideoMode%u", iMode);
+ hrc = ptrMachine->GetExtraData(Bstr(szExtraDataKey).raw(), bstr.asOutParam()); H();
+ if (bstr.isEmpty())
+ break;
+ InsertConfigString(pCfg, szExtraDataKey, bstr);
+ ++cModes;
+ }
+ InsertConfigInteger(pCfg, "CustomVideoModes", cModes);
+
+ /* VESA height reduction */
+ ULONG ulHeightReduction;
+ IFramebuffer *pFramebuffer = NULL;
+ hrc = i_getDisplay()->QueryFramebuffer(0, &pFramebuffer);
+ if (SUCCEEDED(hrc) && pFramebuffer)
+ {
+ hrc = pFramebuffer->COMGETTER(HeightReduction)(&ulHeightReduction); H();
+ pFramebuffer->Release();
+ pFramebuffer = NULL;
+ }
+ else
+ {
+ /* If framebuffer is not available, there is no height reduction. */
+ ulHeightReduction = 0;
+ }
+ InsertConfigInteger(pCfg, "HeightReduction", ulHeightReduction);
+
+ /*
+ * BIOS logo
+ */
+ BOOL fFadeIn;
+ hrc = ptrBiosSettings->COMGETTER(LogoFadeIn)(&fFadeIn); H();
+ InsertConfigInteger(pCfg, "FadeIn", fFadeIn ? 1 : 0);
+ BOOL fFadeOut;
+ hrc = ptrBiosSettings->COMGETTER(LogoFadeOut)(&fFadeOut); H();
+ InsertConfigInteger(pCfg, "FadeOut", fFadeOut ? 1: 0);
+ ULONG logoDisplayTime;
+ hrc = ptrBiosSettings->COMGETTER(LogoDisplayTime)(&logoDisplayTime); H();
+ InsertConfigInteger(pCfg, "LogoTime", logoDisplayTime);
+ Bstr logoImagePath;
+ hrc = ptrBiosSettings->COMGETTER(LogoImagePath)(logoImagePath.asOutParam()); H();
+ InsertConfigString(pCfg, "LogoFile", Utf8Str(!logoImagePath.isEmpty() ? logoImagePath : "") );
+
+ /*
+ * Boot menu
+ */
+ BIOSBootMenuMode_T eBootMenuMode;
+ int iShowBootMenu;
+ hrc = ptrBiosSettings->COMGETTER(BootMenuMode)(&eBootMenuMode); H();
+ switch (eBootMenuMode)
+ {
+ case BIOSBootMenuMode_Disabled: iShowBootMenu = 0; break;
+ case BIOSBootMenuMode_MenuOnly: iShowBootMenu = 1; break;
+ default: iShowBootMenu = 2; break;
+ }
+ InsertConfigInteger(pCfg, "ShowBootMenu", iShowBootMenu);
+
+ /* Attach the display. */
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "MainDisplay");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+
+#undef H
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Ellipsis to va_list wrapper for calling setVMRuntimeErrorCallback.
+ */
+void Console::i_atVMRuntimeErrorCallbackF(uint32_t fFlags, const char *pszErrorId, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ i_atVMRuntimeErrorCallback(NULL, this, fFlags, pszErrorId, pszFormat, va);
+ va_end(va);
+}
+
+/* XXX introduce RT format specifier */
+static uint64_t formatDiskSize(uint64_t u64Size, const char **pszUnit)
+{
+ if (u64Size > INT64_C(5000)*_1G)
+ {
+ *pszUnit = "TB";
+ return u64Size / _1T;
+ }
+ else if (u64Size > INT64_C(5000)*_1M)
+ {
+ *pszUnit = "GB";
+ return u64Size / _1G;
+ }
+ else
+ {
+ *pszUnit = "MB";
+ return u64Size / _1M;
+ }
+}
+
+/**
+ * Checks the location of the given medium for known bugs affecting the usage
+ * of the host I/O cache setting.
+ *
+ * @returns VBox status code.
+ * @param pMedium The medium to check.
+ * @param pfUseHostIOCache Where to store the suggested host I/O cache setting.
+ */
+int Console::i_checkMediumLocation(IMedium *pMedium, bool *pfUseHostIOCache)
+{
+#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR)
+ /*
+ * Some sanity checks.
+ */
+ RT_NOREF(pfUseHostIOCache);
+ ComPtr<IMediumFormat> pMediumFormat;
+ HRESULT hrc = pMedium->COMGETTER(MediumFormat)(pMediumFormat.asOutParam()); H();
+ ULONG uCaps = 0;
+ com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap;
+ hrc = pMediumFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap)); H();
+
+ for (ULONG j = 0; j < mediumFormatCap.size(); j++)
+ uCaps |= mediumFormatCap[j];
+
+ if (uCaps & MediumFormatCapabilities_File)
+ {
+ Bstr bstrFile;
+ hrc = pMedium->COMGETTER(Location)(bstrFile.asOutParam()); H();
+ Utf8Str const strFile(bstrFile);
+
+ Bstr bstrSnap;
+ ComPtr<IMachine> pMachine = i_machine();
+ hrc = pMachine->COMGETTER(SnapshotFolder)(bstrSnap.asOutParam()); H();
+ Utf8Str const strSnap(bstrSnap);
+
+ RTFSTYPE enmFsTypeFile = RTFSTYPE_UNKNOWN;
+ int vrc2 = RTFsQueryType(strFile.c_str(), &enmFsTypeFile);
+ AssertMsgRCReturn(vrc2, ("Querying the file type of '%s' failed!\n", strFile.c_str()), vrc2);
+
+ /* Any VM which hasn't created a snapshot or saved the current state of the VM
+ * won't have a Snapshot folder yet so no need to log anything about the file system
+ * type of the non-existent directory in such cases. */
+ RTFSTYPE enmFsTypeSnap = RTFSTYPE_UNKNOWN;
+ vrc2 = RTFsQueryType(strSnap.c_str(), &enmFsTypeSnap);
+ if (RT_SUCCESS(vrc2) && !mfSnapshotFolderDiskTypeShown)
+ {
+ LogRel(("File system of '%s' (snapshots) is %s\n", strSnap.c_str(), RTFsTypeName(enmFsTypeSnap)));
+ mfSnapshotFolderDiskTypeShown = true;
+ }
+ LogRel(("File system of '%s' is %s\n", strFile.c_str(), RTFsTypeName(enmFsTypeFile)));
+ LONG64 i64Size;
+ hrc = pMedium->COMGETTER(LogicalSize)(&i64Size); H();
+#ifdef RT_OS_WINDOWS
+ if ( enmFsTypeFile == RTFSTYPE_FAT
+ && i64Size >= _4G)
+ {
+ const char *pszUnit;
+ uint64_t u64Print = formatDiskSize((uint64_t)i64Size, &pszUnit);
+ i_atVMRuntimeErrorCallbackF(0, "FatPartitionDetected",
+ N_("The medium '%s' has a logical size of %RU64%s "
+ "but the file system the medium is located on seems "
+ "to be FAT(32) which cannot handle files bigger than 4GB.\n"
+ "We strongly recommend to put all your virtual disk images and "
+ "the snapshot folder onto an NTFS partition"),
+ strFile.c_str(), u64Print, pszUnit);
+ }
+#else /* !RT_OS_WINDOWS */
+ if ( enmFsTypeFile == RTFSTYPE_FAT
+ || enmFsTypeFile == RTFSTYPE_EXT
+ || enmFsTypeFile == RTFSTYPE_EXT2
+ || enmFsTypeFile == RTFSTYPE_EXT3
+ || enmFsTypeFile == RTFSTYPE_EXT4)
+ {
+ RTFILE file;
+ int vrc = RTFileOpen(&file, strFile.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(vrc))
+ {
+ RTFOFF maxSize;
+ /* Careful: This function will work only on selected local file systems! */
+ vrc = RTFileQueryMaxSizeEx(file, &maxSize);
+ RTFileClose(file);
+ if ( RT_SUCCESS(vrc)
+ && maxSize > 0
+ && i64Size > (LONG64)maxSize)
+ {
+ const char *pszUnitSiz;
+ const char *pszUnitMax;
+ uint64_t u64PrintSiz = formatDiskSize((LONG64)i64Size, &pszUnitSiz);
+ uint64_t u64PrintMax = formatDiskSize(maxSize, &pszUnitMax);
+ i_atVMRuntimeErrorCallbackF(0, "FatPartitionDetected", /* <= not exact but ... */
+ N_("The medium '%s' has a logical size of %RU64%s "
+ "but the file system the medium is located on can "
+ "only handle files up to %RU64%s in theory.\n"
+ "We strongly recommend to put all your virtual disk "
+ "images and the snapshot folder onto a proper "
+ "file system (e.g. ext3) with a sufficient size"),
+ strFile.c_str(), u64PrintSiz, pszUnitSiz, u64PrintMax, pszUnitMax);
+ }
+ }
+ }
+#endif /* !RT_OS_WINDOWS */
+
+ /*
+ * Snapshot folder:
+ * Here we test only for a FAT partition as we had to create a dummy file otherwise
+ */
+ if ( enmFsTypeSnap == RTFSTYPE_FAT
+ && i64Size >= _4G
+ && !mfSnapshotFolderSizeWarningShown)
+ {
+ const char *pszUnit;
+ uint64_t u64Print = formatDiskSize(i64Size, &pszUnit);
+ i_atVMRuntimeErrorCallbackF(0, "FatPartitionDetected",
+#ifdef RT_OS_WINDOWS
+ N_("The snapshot folder of this VM '%s' seems to be located on "
+ "a FAT(32) file system. The logical size of the medium '%s' "
+ "(%RU64%s) is bigger than the maximum file size this file "
+ "system can handle (4GB).\n"
+ "We strongly recommend to put all your virtual disk images and "
+ "the snapshot folder onto an NTFS partition"),
+#else
+ N_("The snapshot folder of this VM '%s' seems to be located on "
+ "a FAT(32) file system. The logical size of the medium '%s' "
+ "(%RU64%s) is bigger than the maximum file size this file "
+ "system can handle (4GB).\n"
+ "We strongly recommend to put all your virtual disk images and "
+ "the snapshot folder onto a proper file system (e.g. ext3)"),
+#endif
+ strSnap.c_str(), strFile.c_str(), u64Print, pszUnit);
+ /* Show this particular warning only once */
+ mfSnapshotFolderSizeWarningShown = true;
+ }
+
+#ifdef RT_OS_LINUX
+ /*
+ * Ext4 bug: Check if the host I/O cache is disabled and the disk image is located
+ * on an ext4 partition.
+ * This bug apparently applies to the XFS file system as well.
+ * Linux 2.6.36 is known to be fixed (tested with 2.6.36-rc4).
+ */
+
+ char szOsRelease[128];
+ int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOsRelease, sizeof(szOsRelease));
+ bool fKernelHasODirectBug = RT_FAILURE(vrc)
+ || (RTStrVersionCompare(szOsRelease, "2.6.36-rc4") < 0);
+
+ if ( (uCaps & MediumFormatCapabilities_Asynchronous)
+ && !*pfUseHostIOCache
+ && fKernelHasODirectBug)
+ {
+ if ( enmFsTypeFile == RTFSTYPE_EXT4
+ || enmFsTypeFile == RTFSTYPE_XFS)
+ {
+ i_atVMRuntimeErrorCallbackF(0, "Ext4PartitionDetected",
+ N_("The host I/O cache for at least one controller is disabled "
+ "and the medium '%s' for this VM "
+ "is located on an %s partition. There is a known Linux "
+ "kernel bug which can lead to the corruption of the virtual "
+ "disk image under these conditions.\n"
+ "Either enable the host I/O cache permanently in the VM "
+ "settings or put the disk image and the snapshot folder "
+ "onto a different file system.\n"
+ "The host I/O cache will now be enabled for this medium"),
+ strFile.c_str(), enmFsTypeFile == RTFSTYPE_EXT4 ? "ext4" : "xfs");
+ *pfUseHostIOCache = true;
+ }
+ else if ( ( enmFsTypeSnap == RTFSTYPE_EXT4
+ || enmFsTypeSnap == RTFSTYPE_XFS)
+ && !mfSnapshotFolderExt4WarningShown)
+ {
+ i_atVMRuntimeErrorCallbackF(0, "Ext4PartitionDetected",
+ N_("The host I/O cache for at least one controller is disabled "
+ "and the snapshot folder for this VM "
+ "is located on an %s partition. There is a known Linux "
+ "kernel bug which can lead to the corruption of the virtual "
+ "disk image under these conditions.\n"
+ "Either enable the host I/O cache permanently in the VM "
+ "settings or put the disk image and the snapshot folder "
+ "onto a different file system.\n"
+ "The host I/O cache will now be enabled for this medium"),
+ enmFsTypeSnap == RTFSTYPE_EXT4 ? "ext4" : "xfs");
+ *pfUseHostIOCache = true;
+ mfSnapshotFolderExt4WarningShown = true;
+ }
+ }
+
+ /*
+ * 2.6.18 bug: Check if the host I/O cache is disabled and the host is running
+ * Linux 2.6.18. See @bugref{8690}. Apparently the same problem as
+ * documented in https://lkml.org/lkml/2007/2/1/14. We saw such
+ * kernel oopses on Linux 2.6.18-416.el5. We don't know when this
+ * was fixed but we _know_ that 2.6.18 EL5 kernels are affected.
+ */
+ bool fKernelAsyncUnreliable = RT_FAILURE(vrc)
+ || (RTStrVersionCompare(szOsRelease, "2.6.19") < 0);
+ if ( (uCaps & MediumFormatCapabilities_Asynchronous)
+ && !*pfUseHostIOCache
+ && fKernelAsyncUnreliable)
+ {
+ i_atVMRuntimeErrorCallbackF(0, "Linux2618TooOld",
+ N_("The host I/O cache for at least one controller is disabled. "
+ "There is a known Linux kernel bug which can lead to kernel "
+ "oopses under heavy load. To our knowledge this bug affects "
+ "all 2.6.18 kernels.\n"
+ "Either enable the host I/O cache permanently in the VM "
+ "settings or switch to a newer host kernel.\n"
+ "The host I/O cache will now be enabled for this medium"));
+ *pfUseHostIOCache = true;
+ }
+#endif
+ }
+#undef H
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unmounts the specified medium from the specified device.
+ *
+ * @returns VBox status code.
+ * @param pUVM The usermode VM handle.
+ * @param pVMM The VMM vtable.
+ * @param enmBus The storage bus.
+ * @param enmDevType The device type.
+ * @param pcszDevice The device emulation.
+ * @param uInstance Instance of the device.
+ * @param uLUN The LUN on the device.
+ * @param fForceUnmount Whether to force unmounting.
+ */
+int Console::i_unmountMediumFromGuest(PUVM pUVM, PCVMMR3VTABLE pVMM, StorageBus_T enmBus, DeviceType_T enmDevType,
+ const char *pcszDevice, unsigned uInstance, unsigned uLUN,
+ bool fForceUnmount) RT_NOEXCEPT
+{
+ /* Unmount existing media only for floppy and DVD drives. */
+ int vrc = VINF_SUCCESS;
+ PPDMIBASE pBase;
+ if (enmBus == StorageBus_USB)
+ vrc = pVMM->pfnPDMR3UsbQueryDriverOnLun(pUVM, pcszDevice, uInstance, uLUN, "SCSI", &pBase);
+ else if ( (enmBus == StorageBus_SAS || enmBus == StorageBus_SCSI || enmBus == StorageBus_VirtioSCSI)
+ || (enmBus == StorageBus_SATA && enmDevType == DeviceType_DVD))
+ vrc = pVMM->pfnPDMR3QueryDriverOnLun(pUVM, pcszDevice, uInstance, uLUN, "SCSI", &pBase);
+ else /* IDE or Floppy */
+ vrc = pVMM->pfnPDMR3QueryLun(pUVM, pcszDevice, uInstance, uLUN, &pBase);
+
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_PDM_LUN_NOT_FOUND || vrc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN)
+ vrc = VINF_SUCCESS;
+ AssertRC(vrc);
+ }
+ else
+ {
+ PPDMIMOUNT pIMount = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMOUNT);
+ AssertReturn(pIMount, VERR_INVALID_POINTER);
+
+ /* Unmount the media (but do not eject the medium!) */
+ vrc = pIMount->pfnUnmount(pIMount, fForceUnmount, false /*=fEject*/);
+ if (vrc == VERR_PDM_MEDIA_NOT_MOUNTED)
+ vrc = VINF_SUCCESS;
+ /* for example if the medium is locked */
+ else if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ return vrc;
+}
+
+/**
+ * Removes the currently attached medium driver form the specified device
+ * taking care of the controlelr specific configs wrt. to the attached driver chain.
+ *
+ * @returns VBox status code.
+ * @param pCtlInst The controler instance node in the CFGM tree.
+ * @param pcszDevice The device name.
+ * @param uInstance The device instance.
+ * @param uLUN The device LUN.
+ * @param enmBus The storage bus.
+ * @param fAttachDetach Flag whether this is a change while the VM is running
+ * @param fHotplug Flag whether the guest should be notified about the device change.
+ * @param fForceUnmount Flag whether to force unmounting the medium even if it is locked.
+ * @param pUVM The usermode VM handle.
+ * @param pVMM The VMM vtable.
+ * @param enmDevType The device type.
+ * @param ppLunL0 Where to store the node to attach the new config to on success.
+ */
+int Console::i_removeMediumDriverFromVm(PCFGMNODE pCtlInst,
+ const char *pcszDevice,
+ unsigned uInstance,
+ unsigned uLUN,
+ StorageBus_T enmBus,
+ bool fAttachDetach,
+ bool fHotplug,
+ bool fForceUnmount,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ DeviceType_T enmDevType,
+ PCFGMNODE *ppLunL0)
+{
+ int vrc = VINF_SUCCESS;
+ bool fAddLun = false;
+
+ /* First check if the LUN already exists. */
+ PCFGMNODE pLunL0 = pVMM->pfnCFGMR3GetChildF(pCtlInst, "LUN#%u", uLUN);
+ AssertReturn(!RT_VALID_PTR(pLunL0) || fAttachDetach, VERR_INTERNAL_ERROR);
+
+ if (pLunL0)
+ {
+ /*
+ * Unmount the currently mounted medium if we don't just hot remove the
+ * complete device (SATA) and it supports unmounting (DVD).
+ */
+ if ( (enmDevType != DeviceType_HardDisk)
+ && !fHotplug)
+ {
+ vrc = i_unmountMediumFromGuest(pUVM, pVMM, enmBus, enmDevType, pcszDevice, uInstance, uLUN, fForceUnmount);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ /*
+ * Don't detach the SCSI driver when unmounting the current medium
+ * (we are not ripping out the device but only eject the medium).
+ */
+ char *pszDriverDetach = NULL;
+ if ( !fHotplug
+ && ( (enmBus == StorageBus_SATA && enmDevType == DeviceType_DVD)
+ || enmBus == StorageBus_SAS
+ || enmBus == StorageBus_SCSI
+ || enmBus == StorageBus_VirtioSCSI
+ || enmBus == StorageBus_USB))
+ {
+ /* Get the current attached driver we have to detach. */
+ PCFGMNODE pDrvLun = pVMM->pfnCFGMR3GetChildF(pCtlInst, "LUN#%u/AttachedDriver/", uLUN);
+ if (pDrvLun)
+ {
+ char szDriver[128];
+ RT_ZERO(szDriver);
+ vrc = pVMM->pfnCFGMR3QueryString(pDrvLun, "Driver", &szDriver[0], sizeof(szDriver));
+ if (RT_SUCCESS(vrc))
+ pszDriverDetach = RTStrDup(&szDriver[0]);
+
+ pLunL0 = pDrvLun;
+ }
+ }
+
+ if (enmBus == StorageBus_USB)
+ vrc = pVMM->pfnPDMR3UsbDriverDetach(pUVM, pcszDevice, uInstance, uLUN, pszDriverDetach,
+ 0 /* iOccurence */, fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG);
+ else
+ vrc = pVMM->pfnPDMR3DriverDetach(pUVM, pcszDevice, uInstance, uLUN, pszDriverDetach,
+ 0 /* iOccurence */, fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG);
+
+ if (pszDriverDetach)
+ {
+ RTStrFree(pszDriverDetach);
+ /* Remove the complete node and create new for the new config. */
+ pVMM->pfnCFGMR3RemoveNode(pLunL0);
+ pLunL0 = pVMM->pfnCFGMR3GetChildF(pCtlInst, "LUN#%u", uLUN);
+ if (pLunL0)
+ {
+ try
+ {
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0);
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+ }
+ }
+ if (vrc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN)
+ vrc = VINF_SUCCESS;
+ AssertRCReturn(vrc, vrc);
+
+ /*
+ * Don't remove the LUN except for IDE/floppy/NVMe (which connects directly to the medium driver
+ * even for DVD devices) or if there is a hotplug event which rips out the complete device.
+ */
+ if ( fHotplug
+ || enmBus == StorageBus_IDE
+ || enmBus == StorageBus_Floppy
+ || enmBus == StorageBus_PCIe
+ || (enmBus == StorageBus_SATA && enmDevType != DeviceType_DVD))
+ {
+ fAddLun = true;
+ pVMM->pfnCFGMR3RemoveNode(pLunL0);
+ }
+ }
+ else
+ fAddLun = true;
+
+ try
+ {
+ if (fAddLun)
+ InsertConfigNodeF(pCtlInst, &pLunL0, "LUN#%u", uLUN);
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+
+ if (ppLunL0)
+ *ppLunL0 = pLunL0;
+
+ return vrc;
+}
+
+int Console::i_configMediumAttachment(const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ bool fUseHostIOCache,
+ bool fBuiltinIOCache,
+ bool fInsertDiskIntegrityDrv,
+ bool fSetupMerge,
+ unsigned uMergeSource,
+ unsigned uMergeTarget,
+ IMediumAttachment *pMediumAtt,
+ MachineState_T aMachineState,
+ HRESULT *phrc,
+ bool fAttachDetach,
+ bool fForceUnmount,
+ bool fHotplug,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ DeviceType_T *paLedDevType,
+ PCFGMNODE *ppLunL0)
+{
+ // InsertConfig* throws
+ try
+ {
+ int vrc = VINF_SUCCESS;
+ HRESULT hrc;
+ Bstr bstr;
+ PCFGMNODE pCtlInst = NULL;
+
+// #define RC_CHECK() AssertMsgReturn(RT_SUCCESS(rc), ("rc=%Rrc\n", rc), rc)
+#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR)
+
+ LONG lDev;
+ hrc = pMediumAtt->COMGETTER(Device)(&lDev); H();
+ LONG lPort;
+ hrc = pMediumAtt->COMGETTER(Port)(&lPort); H();
+ DeviceType_T lType;
+ hrc = pMediumAtt->COMGETTER(Type)(&lType); H();
+ BOOL fNonRotational;
+ hrc = pMediumAtt->COMGETTER(NonRotational)(&fNonRotational); H();
+ BOOL fDiscard;
+ hrc = pMediumAtt->COMGETTER(Discard)(&fDiscard); H();
+
+ if (lType == DeviceType_DVD)
+ fInsertDiskIntegrityDrv = false;
+
+ unsigned uLUN;
+ PCFGMNODE pLunL0 = NULL;
+ hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN); H();
+
+ /* Determine the base path for the device instance. */
+ if (enmBus != StorageBus_USB)
+ pCtlInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/%s/%u/", pcszDevice, uInstance);
+ else
+ {
+ /* If we hotplug a USB device create a new CFGM tree. */
+ if (!fHotplug)
+ pCtlInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "USB/%s/", pcszDevice);
+ else
+ pCtlInst = pVMM->pfnCFGMR3CreateTree(pUVM);
+ }
+ AssertReturn(pCtlInst, VERR_INTERNAL_ERROR);
+
+ if (enmBus == StorageBus_USB)
+ {
+ PCFGMNODE pCfg = NULL;
+
+ /* Create correct instance. */
+ if (!fHotplug)
+ {
+ if (!fAttachDetach)
+ InsertConfigNodeF(pCtlInst, &pCtlInst, "%d", lPort);
+ else
+ pCtlInst = pVMM->pfnCFGMR3GetChildF(pCtlInst, "%d/", lPort);
+ }
+
+ if (!fAttachDetach)
+ InsertConfigNode(pCtlInst, "Config", &pCfg);
+
+ uInstance = lPort; /* Overwrite uInstance with the correct one. */
+
+ if (!fHotplug && !fAttachDetach)
+ {
+ char aszUuid[RTUUID_STR_LENGTH + 1];
+ USBStorageDevice UsbMsd = USBStorageDevice();
+
+ memset(aszUuid, 0, sizeof(aszUuid));
+ vrc = RTUuidCreate(&UsbMsd.mUuid);
+ AssertRCReturn(vrc, vrc);
+ vrc = RTUuidToStr(&UsbMsd.mUuid, aszUuid, sizeof(aszUuid));
+ AssertRCReturn(vrc, vrc);
+
+ UsbMsd.iPort = uInstance;
+
+ InsertConfigString(pCtlInst, "UUID", aszUuid);
+ mUSBStorageDevices.push_back(UsbMsd);
+
+ /** @todo No LED after hotplugging. */
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, 7, &paLedDevType,
+ &mapMediumAttachments, pcszDevice, 0);
+ }
+ }
+
+ vrc = i_removeMediumDriverFromVm(pCtlInst, pcszDevice, uInstance, uLUN, enmBus, fAttachDetach,
+ fHotplug, fForceUnmount, pUVM, pVMM, lType, &pLunL0);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ if (ppLunL0)
+ *ppLunL0 = pLunL0;
+
+ Utf8StrFmt devicePath("%s/%u/LUN#%u", pcszDevice, uInstance, uLUN);
+ mapMediumAttachments[devicePath] = pMediumAtt;
+
+ ComPtr<IMedium> ptrMedium;
+ hrc = pMediumAtt->COMGETTER(Medium)(ptrMedium.asOutParam()); H();
+
+ /*
+ * 1. Only check this for hard disk images.
+ * 2. Only check during VM creation and not later, especially not during
+ * taking an online snapshot!
+ */
+ if ( lType == DeviceType_HardDisk
+ && ( aMachineState == MachineState_Starting
+ || aMachineState == MachineState_Restoring))
+ {
+ vrc = i_checkMediumLocation(ptrMedium, &fUseHostIOCache);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ BOOL fPassthrough = FALSE;
+ if (ptrMedium.isNotNull())
+ {
+ BOOL fHostDrive;
+ hrc = ptrMedium->COMGETTER(HostDrive)(&fHostDrive); H();
+ if ( ( lType == DeviceType_DVD
+ || lType == DeviceType_Floppy)
+ && !fHostDrive)
+ {
+ /*
+ * Informative logging.
+ */
+ Bstr bstrFile;
+ hrc = ptrMedium->COMGETTER(Location)(bstrFile.asOutParam()); H();
+ Utf8Str strFile(bstrFile);
+ RTFSTYPE enmFsTypeFile = RTFSTYPE_UNKNOWN;
+ (void)RTFsQueryType(strFile.c_str(), &enmFsTypeFile);
+ LogRel(("File system of '%s' (%s) is %s\n",
+ strFile.c_str(), lType == DeviceType_DVD ? "DVD" : "Floppy", RTFsTypeName(enmFsTypeFile)));
+ }
+
+ if (fHostDrive)
+ {
+ hrc = pMediumAtt->COMGETTER(Passthrough)(&fPassthrough); H();
+ }
+ }
+
+ ComObjPtr<IBandwidthGroup> pBwGroup;
+ Bstr bstrBwGroup;
+ hrc = pMediumAtt->COMGETTER(BandwidthGroup)(pBwGroup.asOutParam()); H();
+
+ if (!pBwGroup.isNull())
+ {
+ hrc = pBwGroup->COMGETTER(Name)(bstrBwGroup.asOutParam()); H();
+ }
+
+ /*
+ * Insert the SCSI driver for hotplug events on the SCSI/USB based storage controllers
+ * or for SATA if the new device is a CD/DVD drive.
+ */
+ if ( (fHotplug || !fAttachDetach)
+ && ( (enmBus == StorageBus_SCSI || enmBus == StorageBus_SAS || enmBus == StorageBus_USB || enmBus == StorageBus_VirtioSCSI)
+ || (enmBus == StorageBus_SATA && lType == DeviceType_DVD && !fPassthrough)))
+ {
+ InsertConfigString(pLunL0, "Driver", "SCSI");
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0);
+ }
+
+ vrc = i_configMedium(pLunL0,
+ !!fPassthrough,
+ lType,
+ fUseHostIOCache,
+ fBuiltinIOCache,
+ fInsertDiskIntegrityDrv,
+ fSetupMerge,
+ uMergeSource,
+ uMergeTarget,
+ bstrBwGroup.isEmpty() ? NULL : Utf8Str(bstrBwGroup).c_str(),
+ !!fDiscard,
+ !!fNonRotational,
+ ptrMedium,
+ aMachineState,
+ phrc);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ if (fAttachDetach)
+ {
+ /* Attach the new driver. */
+ if (enmBus == StorageBus_USB)
+ {
+ if (fHotplug)
+ {
+ USBStorageDevice UsbMsd = USBStorageDevice();
+ RTUuidCreate(&UsbMsd.mUuid);
+ UsbMsd.iPort = uInstance;
+ vrc = pVMM->pfnPDMR3UsbCreateEmulatedDevice(pUVM, pcszDevice, pCtlInst, &UsbMsd.mUuid, NULL);
+ if (RT_SUCCESS(vrc))
+ mUSBStorageDevices.push_back(UsbMsd);
+ }
+ else
+ vrc = pVMM->pfnPDMR3UsbDriverAttach(pUVM, pcszDevice, uInstance, uLUN,
+ fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG, NULL /*ppBase*/);
+ }
+ else if ( !fHotplug
+ && ( (enmBus == StorageBus_SAS || enmBus == StorageBus_SCSI || enmBus == StorageBus_VirtioSCSI)
+ || (enmBus == StorageBus_SATA && lType == DeviceType_DVD)))
+ vrc = pVMM->pfnPDMR3DriverAttach(pUVM, pcszDevice, uInstance, uLUN,
+ fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG, NULL /*ppBase*/);
+ else
+ vrc = pVMM->pfnPDMR3DeviceAttach(pUVM, pcszDevice, uInstance, uLUN,
+ fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG, NULL /*ppBase*/);
+ AssertRCReturn(vrc, vrc);
+
+ /*
+ * Make the secret key helper interface known to the VD driver if it is attached,
+ * so we can get notified about missing keys.
+ */
+ PPDMIBASE pIBase = NULL;
+ vrc = pVMM->pfnPDMR3QueryDriverOnLun(pUVM, pcszDevice, uInstance, uLUN, "VD", &pIBase);
+ if (RT_SUCCESS(vrc) && pIBase)
+ {
+ PPDMIMEDIA pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID);
+ if (pIMedium)
+ {
+ vrc = pIMedium->pfnSetSecKeyIf(pIMedium, mpIfSecKey, mpIfSecKeyHlp);
+ Assert(RT_SUCCESS(vrc) || vrc == VERR_NOT_SUPPORTED);
+ }
+ }
+
+ /* There is no need to handle removable medium mounting, as we
+ * unconditionally replace everthing including the block driver level.
+ * This means the new medium will be picked up automatically. */
+ }
+
+ if (paLedDevType)
+ paLedDevType[uLUN] = lType;
+
+ /* Dump the changed LUN if possible, dump the complete device otherwise */
+ if ( aMachineState != MachineState_Starting
+ && aMachineState != MachineState_Restoring)
+ pVMM->pfnCFGMR3Dump(pLunL0 ? pLunL0 : pCtlInst);
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+
+#undef H
+
+ return VINF_SUCCESS;
+}
+
+int Console::i_configMedium(PCFGMNODE pLunL0,
+ bool fPassthrough,
+ DeviceType_T enmType,
+ bool fUseHostIOCache,
+ bool fBuiltinIOCache,
+ bool fInsertDiskIntegrityDrv,
+ bool fSetupMerge,
+ unsigned uMergeSource,
+ unsigned uMergeTarget,
+ const char *pcszBwGroup,
+ bool fDiscard,
+ bool fNonRotational,
+ ComPtr<IMedium> ptrMedium,
+ MachineState_T aMachineState,
+ HRESULT *phrc)
+{
+ // InsertConfig* throws
+ try
+ {
+ HRESULT hrc;
+ Bstr bstr;
+ PCFGMNODE pCfg = NULL;
+
+#define H() \
+ AssertMsgReturnStmt(SUCCEEDED(hrc), ("hrc=%Rhrc\n", hrc), if (phrc) *phrc = hrc, Global::vboxStatusCodeFromCOM(hrc))
+
+
+ BOOL fHostDrive = FALSE;
+ MediumType_T mediumType = MediumType_Normal;
+ if (ptrMedium.isNotNull())
+ {
+ hrc = ptrMedium->COMGETTER(HostDrive)(&fHostDrive); H();
+ hrc = ptrMedium->COMGETTER(Type)(&mediumType); H();
+ }
+
+ if (fHostDrive)
+ {
+ Assert(ptrMedium.isNotNull());
+ if (enmType == DeviceType_DVD)
+ {
+ InsertConfigString(pLunL0, "Driver", "HostDVD");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ hrc = ptrMedium->COMGETTER(Location)(bstr.asOutParam()); H();
+ InsertConfigString(pCfg, "Path", bstr);
+
+ InsertConfigInteger(pCfg, "Passthrough", fPassthrough);
+ }
+ else if (enmType == DeviceType_Floppy)
+ {
+ InsertConfigString(pLunL0, "Driver", "HostFloppy");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ hrc = ptrMedium->COMGETTER(Location)(bstr.asOutParam()); H();
+ InsertConfigString(pCfg, "Path", bstr);
+ }
+ }
+ else
+ {
+ if (fInsertDiskIntegrityDrv)
+ {
+ /*
+ * The actual configuration is done through CFGM extra data
+ * for each inserted driver separately.
+ */
+ InsertConfigString(pLunL0, "Driver", "DiskIntegrity");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0);
+ }
+
+ InsertConfigString(pLunL0, "Driver", "VD");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ switch (enmType)
+ {
+ case DeviceType_DVD:
+ InsertConfigString(pCfg, "Type", "DVD");
+ InsertConfigInteger(pCfg, "Mountable", 1);
+ break;
+ case DeviceType_Floppy:
+ InsertConfigString(pCfg, "Type", "Floppy 1.44");
+ InsertConfigInteger(pCfg, "Mountable", 1);
+ break;
+ case DeviceType_HardDisk:
+ default:
+ InsertConfigString(pCfg, "Type", "HardDisk");
+ InsertConfigInteger(pCfg, "Mountable", 0);
+ }
+
+ if ( ptrMedium.isNotNull()
+ && ( enmType == DeviceType_DVD
+ || enmType == DeviceType_Floppy)
+ )
+ {
+ // if this medium represents an ISO image and this image is inaccessible,
+ // the ignore it instead of causing a failure; this can happen when we
+ // restore a VM state and the ISO has disappeared, e.g. because the Guest
+ // Additions were mounted and the user upgraded VirtualBox. Previously
+ // we failed on startup, but that's not good because the only way out then
+ // would be to discard the VM state...
+ MediumState_T mediumState;
+ hrc = ptrMedium->RefreshState(&mediumState); H();
+ if (mediumState == MediumState_Inaccessible)
+ {
+ Bstr loc;
+ hrc = ptrMedium->COMGETTER(Location)(loc.asOutParam()); H();
+ i_atVMRuntimeErrorCallbackF(0, "DvdOrFloppyImageInaccessible",
+ N_("The image file '%ls' is inaccessible and is being ignored. "
+ "Please select a different image file for the virtual %s drive."),
+ loc.raw(),
+ enmType == DeviceType_DVD ? "DVD" : "floppy");
+ ptrMedium.setNull();
+ }
+ }
+
+ if (ptrMedium.isNotNull())
+ {
+ /* Start with length of parent chain, as the list is reversed */
+ unsigned uImage = 0;
+ ComPtr<IMedium> ptrTmp = ptrMedium;
+ while (ptrTmp.isNotNull())
+ {
+ uImage++;
+ ComPtr<IMedium> ptrParent;
+ hrc = ptrTmp->COMGETTER(Parent)(ptrParent.asOutParam()); H();
+ ptrTmp = ptrParent;
+ }
+ /* Index of last image */
+ uImage--;
+
+# ifdef VBOX_WITH_EXTPACK
+ if (mptrExtPackManager->i_isExtPackUsable(ORACLE_PUEL_EXTPACK_NAME))
+ {
+ /* Configure loading the VDPlugin. */
+ static const char s_szVDPlugin[] = "VDPluginCrypt";
+ PCFGMNODE pCfgPlugins = NULL;
+ PCFGMNODE pCfgPlugin = NULL;
+ Utf8Str strPlugin;
+ hrc = mptrExtPackManager->i_getLibraryPathForExtPack(s_szVDPlugin, ORACLE_PUEL_EXTPACK_NAME, &strPlugin);
+ // Don't fail, this is optional!
+ if (SUCCEEDED(hrc))
+ {
+ InsertConfigNode(pCfg, "Plugins", &pCfgPlugins);
+ InsertConfigNode(pCfgPlugins, s_szVDPlugin, &pCfgPlugin);
+ InsertConfigString(pCfgPlugin, "Path", strPlugin.c_str());
+ }
+ }
+# endif
+
+ hrc = ptrMedium->COMGETTER(Location)(bstr.asOutParam()); H();
+ InsertConfigString(pCfg, "Path", bstr);
+
+ hrc = ptrMedium->COMGETTER(Format)(bstr.asOutParam()); H();
+ InsertConfigString(pCfg, "Format", bstr);
+
+ if (mediumType == MediumType_Readonly)
+ InsertConfigInteger(pCfg, "ReadOnly", 1);
+ else if (enmType == DeviceType_Floppy)
+ InsertConfigInteger(pCfg, "MaybeReadOnly", 1);
+
+ /* Start without exclusive write access to the images. */
+ /** @todo Live Migration: I don't quite like this, we risk screwing up when
+ * we're resuming the VM if some 3rd dude have any of the VDIs open
+ * with write sharing denied. However, if the two VMs are sharing a
+ * image it really is necessary....
+ *
+ * So, on the "lock-media" command, the target teleporter should also
+ * make DrvVD undo TempReadOnly. It gets interesting if we fail after
+ * that. Grumble. */
+ if ( enmType == DeviceType_HardDisk
+ && aMachineState == MachineState_TeleportingIn)
+ InsertConfigInteger(pCfg, "TempReadOnly", 1);
+
+ /* Flag for opening the medium for sharing between VMs. This
+ * is done at the moment only for the first (and only) medium
+ * in the chain, as shared media can have no diffs. */
+ if (mediumType == MediumType_Shareable)
+ InsertConfigInteger(pCfg, "Shareable", 1);
+
+ if (!fUseHostIOCache)
+ {
+ InsertConfigInteger(pCfg, "UseNewIo", 1);
+ /*
+ * Activate the builtin I/O cache for harddisks only.
+ * It caches writes only which doesn't make sense for DVD drives
+ * and just increases the overhead.
+ */
+ if ( fBuiltinIOCache
+ && (enmType == DeviceType_HardDisk))
+ InsertConfigInteger(pCfg, "BlockCache", 1);
+ }
+
+ if (fSetupMerge)
+ {
+ InsertConfigInteger(pCfg, "SetupMerge", 1);
+ if (uImage == uMergeSource)
+ InsertConfigInteger(pCfg, "MergeSource", 1);
+ else if (uImage == uMergeTarget)
+ InsertConfigInteger(pCfg, "MergeTarget", 1);
+ }
+
+ if (pcszBwGroup)
+ InsertConfigString(pCfg, "BwGroup", pcszBwGroup);
+
+ if (fDiscard)
+ InsertConfigInteger(pCfg, "Discard", 1);
+
+ if (fNonRotational)
+ InsertConfigInteger(pCfg, "NonRotationalMedium", 1);
+
+ /* Pass all custom parameters. */
+ bool fHostIP = true;
+ bool fEncrypted = false;
+ hrc = i_configMediumProperties(pCfg, ptrMedium, &fHostIP, &fEncrypted); H();
+
+ /* Create an inverted list of parents. */
+ uImage--;
+ ComPtr<IMedium> ptrParentMedium = ptrMedium;
+ for (PCFGMNODE pParent = pCfg;; uImage--)
+ {
+ ComPtr<IMedium> ptrCurMedium;
+ hrc = ptrParentMedium->COMGETTER(Parent)(ptrCurMedium.asOutParam()); H();
+ if (ptrCurMedium.isNull())
+ break;
+
+ PCFGMNODE pCur;
+ InsertConfigNode(pParent, "Parent", &pCur);
+ hrc = ptrCurMedium->COMGETTER(Location)(bstr.asOutParam()); H();
+ InsertConfigString(pCur, "Path", bstr);
+
+ hrc = ptrCurMedium->COMGETTER(Format)(bstr.asOutParam()); H();
+ InsertConfigString(pCur, "Format", bstr);
+
+ if (fSetupMerge)
+ {
+ if (uImage == uMergeSource)
+ InsertConfigInteger(pCur, "MergeSource", 1);
+ else if (uImage == uMergeTarget)
+ InsertConfigInteger(pCur, "MergeTarget", 1);
+ }
+
+ /* Configure medium properties. */
+ hrc = i_configMediumProperties(pCur, ptrCurMedium, &fHostIP, &fEncrypted); H();
+
+ /* next */
+ pParent = pCur;
+ ptrParentMedium = ptrCurMedium;
+ }
+
+ /* Custom code: put marker to not use host IP stack to driver
+ * configuration node. Simplifies life of DrvVD a bit. */
+ if (!fHostIP)
+ InsertConfigInteger(pCfg, "HostIPStack", 0);
+
+ if (fEncrypted)
+ m_cDisksEncrypted++;
+ }
+ else
+ {
+ /* Set empty drive flag for DVD or floppy without media. */
+ if ( enmType == DeviceType_DVD
+ || enmType == DeviceType_Floppy)
+ InsertConfigInteger(pCfg, "EmptyDrive", 1);
+ }
+ }
+#undef H
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Adds the medium properties to the CFGM tree.
+ *
+ * @returns VBox status code.
+ * @param pCur The current CFGM node.
+ * @param pMedium The medium object to configure.
+ * @param pfHostIP Where to return the value of the \"HostIPStack\" property if found.
+ * @param pfEncrypted Where to return whether the medium is encrypted.
+ */
+int Console::i_configMediumProperties(PCFGMNODE pCur, IMedium *pMedium, bool *pfHostIP, bool *pfEncrypted)
+{
+ /* Pass all custom parameters. */
+ SafeArray<BSTR> aNames;
+ SafeArray<BSTR> aValues;
+ HRESULT hrc = pMedium->GetProperties(NULL, ComSafeArrayAsOutParam(aNames),
+ ComSafeArrayAsOutParam(aValues));
+
+ if ( SUCCEEDED(hrc)
+ && aNames.size() != 0)
+ {
+ PCFGMNODE pVDC;
+ InsertConfigNode(pCur, "VDConfig", &pVDC);
+ for (size_t ii = 0; ii < aNames.size(); ++ii)
+ {
+ if (aValues[ii] && *aValues[ii])
+ {
+ Utf8Str name = aNames[ii];
+ Utf8Str value = aValues[ii];
+ size_t offSlash = name.find("/", 0);
+ if ( offSlash != name.npos
+ && !name.startsWith("Special/"))
+ {
+ com::Utf8Str strFilter;
+ com::Utf8Str strKey;
+
+ hrc = strFilter.assignEx(name, 0, offSlash);
+ if (FAILED(hrc))
+ break;
+
+ hrc = strKey.assignEx(name, offSlash + 1, name.length() - offSlash - 1); /* Skip slash */
+ if (FAILED(hrc))
+ break;
+
+ PCFGMNODE pCfgFilterConfig = mpVMM->pfnCFGMR3GetChild(pVDC, strFilter.c_str());
+ if (!pCfgFilterConfig)
+ InsertConfigNode(pVDC, strFilter.c_str(), &pCfgFilterConfig);
+
+ InsertConfigString(pCfgFilterConfig, strKey.c_str(), value);
+ }
+ else
+ {
+ InsertConfigString(pVDC, name.c_str(), value);
+ if ( name.compare("HostIPStack") == 0
+ && value.compare("0") == 0)
+ *pfHostIP = false;
+ }
+
+ if ( name.compare("CRYPT/KeyId") == 0
+ && pfEncrypted)
+ *pfEncrypted = true;
+ }
+ }
+ }
+
+ return hrc;
+}
+
+
+/**
+ * Configure proxy parameters the Network configuration tree.
+ * Parameters may differ depending on the IP address being accessed.
+ *
+ * @returns VBox status code.
+ *
+ * @param virtualBox The VirtualBox object.
+ * @param pCfg Configuration node for the driver.
+ * @param pcszPrefix The prefix for CFGM parameters: "Primary" or "Secondary".
+ * @param strIpAddr The public IP address to be accessed via a proxy.
+ *
+ * @thread EMT
+ */
+int Console::i_configProxy(ComPtr<IVirtualBox> virtualBox, PCFGMNODE pCfg, const char *pcszPrefix, const com::Utf8Str &strIpAddr)
+{
+ RTHTTPPROXYINFO ProxyInfo;
+ ComPtr<ISystemProperties> systemProperties;
+ ProxyMode_T enmProxyMode;
+ HRESULT hrc = virtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("CLOUD-NET: Failed to obtain system properties. hrc=%x\n", hrc));
+ return false;
+ }
+ hrc = systemProperties->COMGETTER(ProxyMode)(&enmProxyMode);
+ if (FAILED(hrc))
+ {
+ LogRel(("CLOUD-NET: Failed to obtain default machine folder. hrc=%x\n", hrc));
+ return VERR_INTERNAL_ERROR;
+ }
+
+ RTHTTP hHttp;
+ int vrc = RTHttpCreate(&hHttp);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("CLOUD-NET: Failed to create HTTP context (rc=%Rrc)\n", vrc));
+ return vrc;
+ }
+
+ char *pszProxyType = NULL;
+
+ if (enmProxyMode == ProxyMode_Manual)
+ {
+ /*
+ * Unfortunately we cannot simply call RTHttpSetProxyByUrl because it never
+ * exposes proxy settings. Calling RTHttpQueryProxyInfoForUrl afterward
+ * won't help either as it uses system-wide proxy settings instead of
+ * parameters we would have set with RTHttpSetProxyByUrl. Hence we parse
+ * proxy URL ourselves here.
+ */
+ Bstr proxyUrl;
+ hrc = systemProperties->COMGETTER(ProxyURL)(proxyUrl.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("CLOUD-NET: Failed to obtain proxy URL. hrc=%x\n", hrc));
+ return false;
+ }
+ Utf8Str strProxyUrl = proxyUrl;
+ if (!strProxyUrl.contains("://"))
+ strProxyUrl = "http://" + strProxyUrl;
+ const char *pcszProxyUrl = strProxyUrl.c_str();
+ RTURIPARSED Parsed;
+ vrc = RTUriParse(pcszProxyUrl, &Parsed);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("CLOUD-NET: Failed to parse proxy URL: %ls (vrc=%Rrc)\n", proxyUrl.raw(), vrc));
+ return false;
+ }
+
+ pszProxyType = RTUriParsedScheme(pcszProxyUrl, &Parsed);
+ if (!pszProxyType)
+ {
+ LogRel(("CLOUD-NET: Failed to get proxy scheme from proxy URL: %s\n", pcszProxyUrl));
+ return false;
+ }
+ RTStrToUpper(pszProxyType);
+
+ ProxyInfo.pszProxyHost = RTUriParsedAuthorityHost(pcszProxyUrl, &Parsed);
+ if (!ProxyInfo.pszProxyHost)
+ {
+ LogRel(("CLOUD-NET: Failed to get proxy host name from proxy URL: %s\n", pcszProxyUrl));
+ return false;
+ }
+ ProxyInfo.uProxyPort = RTUriParsedAuthorityPort(pcszProxyUrl, &Parsed);
+ if (ProxyInfo.uProxyPort == UINT32_MAX)
+ {
+ LogRel(("CLOUD-NET: Failed to get proxy port from proxy URL: %s\n", pcszProxyUrl));
+ return false;
+ }
+ ProxyInfo.pszProxyUsername = RTUriParsedAuthorityUsername(pcszProxyUrl, &Parsed);
+ ProxyInfo.pszProxyPassword = RTUriParsedAuthorityPassword(pcszProxyUrl, &Parsed);
+ }
+ else if (enmProxyMode == ProxyMode_System)
+ {
+ vrc = RTHttpUseSystemProxySettings(hHttp);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("%s: RTHttpUseSystemProxySettings() failed: %Rrc", __FUNCTION__, vrc));
+ RTHttpDestroy(hHttp);
+ return vrc;
+ }
+ vrc = RTHttpQueryProxyInfoForUrl(hHttp, ("http://" + strIpAddr).c_str(), &ProxyInfo);
+ RTHttpDestroy(hHttp);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("CLOUD-NET: Failed to get proxy for %s (rc=%Rrc)\n", strIpAddr.c_str(), vrc));
+ return vrc;
+ }
+
+ switch (ProxyInfo.enmProxyType)
+ {
+ case RTHTTPPROXYTYPE_NOPROXY:
+ /* Nothing to do */
+ return VINF_SUCCESS;
+ case RTHTTPPROXYTYPE_HTTP:
+ pszProxyType = RTStrDup("HTTP");
+ break;
+ case RTHTTPPROXYTYPE_HTTPS:
+ case RTHTTPPROXYTYPE_SOCKS4:
+ case RTHTTPPROXYTYPE_SOCKS5:
+ /* break; -- Fall through until support is implemented */
+ case RTHTTPPROXYTYPE_UNKNOWN:
+ case RTHTTPPROXYTYPE_INVALID:
+ case RTHTTPPROXYTYPE_END:
+ case RTHTTPPROXYTYPE_32BIT_HACK:
+ LogRel(("CLOUD-NET: Unsupported proxy type %u\n", ProxyInfo.enmProxyType));
+ RTHttpFreeProxyInfo(&ProxyInfo);
+ return VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ Assert(enmProxyMode == ProxyMode_NoProxy);
+ return VINF_SUCCESS;
+ }
+
+ /* Resolve proxy host name to IP address if necessary */
+ RTNETADDR addr;
+ RTSocketParseInetAddress(ProxyInfo.pszProxyHost, ProxyInfo.uProxyPort, &addr);
+ if (addr.enmType != RTNETADDRTYPE_IPV4)
+ {
+ LogRel(("CLOUD-NET: Unsupported address type %u\n", addr.enmType));
+ RTHttpFreeProxyInfo(&ProxyInfo);
+ return VERR_INVALID_PARAMETER;
+ }
+
+ InsertConfigString(pCfg, Utf8StrFmt("%sProxyType", pcszPrefix).c_str(), pszProxyType);
+ InsertConfigInteger(pCfg, Utf8StrFmt("%sProxyPort", pcszPrefix).c_str(), ProxyInfo.uProxyPort);
+ if (ProxyInfo.pszProxyHost)
+ InsertConfigString(pCfg, Utf8StrFmt("%sProxyHost", pcszPrefix).c_str(), Utf8StrFmt("%RTnaipv4", addr.uAddr.IPv4));
+ if (ProxyInfo.pszProxyUsername)
+ InsertConfigString(pCfg, Utf8StrFmt("%sProxyUser", pcszPrefix).c_str(), ProxyInfo.pszProxyUsername);
+ if (ProxyInfo.pszProxyPassword)
+ InsertConfigPassword(pCfg, Utf8StrFmt("%sProxyPassword", pcszPrefix).c_str(), ProxyInfo.pszProxyPassword);
+
+ RTHttpFreeProxyInfo(&ProxyInfo);
+ RTStrFree(pszProxyType);
+ return vrc;
+}
+
+
+/**
+ * Construct the Network configuration tree
+ *
+ * @returns VBox status code.
+ *
+ * @param pszDevice The PDM device name.
+ * @param uInstance The PDM device instance.
+ * @param uLun The PDM LUN number of the drive.
+ * @param aNetworkAdapter The network adapter whose attachment needs to be changed
+ * @param pCfg Configuration node for the device
+ * @param pLunL0 To store the pointer to the LUN#0.
+ * @param pInst The instance CFGM node
+ * @param fAttachDetach To determine if the network attachment should
+ * be attached/detached after/before
+ * configuration.
+ * @param fIgnoreConnectFailure
+ * True if connection failures should be ignored
+ * (makes only sense for bridged/host-only networks).
+ * @param pUVM The usermode VM handle.
+ * @param pVMM The VMM vtable.
+ *
+ * @note Locks this object for writing.
+ * @thread EMT
+ */
+int Console::i_configNetwork(const char *pszDevice,
+ unsigned uInstance,
+ unsigned uLun,
+ INetworkAdapter *aNetworkAdapter,
+ PCFGMNODE pCfg,
+ PCFGMNODE pLunL0,
+ PCFGMNODE pInst,
+ bool fAttachDetach,
+ bool fIgnoreConnectFailure,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM)
+{
+ RT_NOREF(fIgnoreConnectFailure);
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED);
+
+ // InsertConfig* throws
+ try
+ {
+ int vrc = VINF_SUCCESS;
+ HRESULT hrc;
+ Bstr bstr;
+
+#ifdef VBOX_WITH_CLOUD_NET
+ /* We'll need device's pCfg for cloud attachments */
+ PCFGMNODE pDevCfg = pCfg;
+#endif /* VBOX_WITH_CLOUD_NET */
+
+#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR)
+
+ /*
+ * Locking the object before doing VMR3* calls is quite safe here, since
+ * we're on EMT. Write lock is necessary because we indirectly modify the
+ * meAttachmentType member.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComPtr<IMachine> pMachine = i_machine();
+
+ ComPtr<IVirtualBox> virtualBox;
+ hrc = pMachine->COMGETTER(Parent)(virtualBox.asOutParam()); H();
+
+ ComPtr<IHost> host;
+ hrc = virtualBox->COMGETTER(Host)(host.asOutParam()); H();
+
+ BOOL fSniffer;
+ hrc = aNetworkAdapter->COMGETTER(TraceEnabled)(&fSniffer); H();
+
+ NetworkAdapterPromiscModePolicy_T enmPromiscModePolicy;
+ hrc = aNetworkAdapter->COMGETTER(PromiscModePolicy)(&enmPromiscModePolicy); H();
+ const char *pszPromiscuousGuestPolicy;
+ switch (enmPromiscModePolicy)
+ {
+ case NetworkAdapterPromiscModePolicy_Deny: pszPromiscuousGuestPolicy = "deny"; break;
+ case NetworkAdapterPromiscModePolicy_AllowNetwork: pszPromiscuousGuestPolicy = "allow-network"; break;
+ case NetworkAdapterPromiscModePolicy_AllowAll: pszPromiscuousGuestPolicy = "allow-all"; break;
+ default: AssertFailedReturn(VERR_INTERNAL_ERROR_4);
+ }
+
+ if (fAttachDetach)
+ {
+ vrc = pVMM->pfnPDMR3DeviceDetach(pUVM, pszDevice, uInstance, uLun, 0 /*fFlags*/);
+ if (vrc == VINF_PDM_NO_DRIVER_ATTACHED_TO_LUN)
+ vrc = VINF_SUCCESS;
+ AssertLogRelRCReturn(vrc, vrc);
+
+ /* Nuke anything which might have been left behind. */
+ pVMM->pfnCFGMR3RemoveNode(pVMM->pfnCFGMR3GetChildF(pInst, "LUN#%u", uLun));
+ }
+
+ Bstr networkName, trunkName, trunkType;
+ NetworkAttachmentType_T eAttachmentType;
+ hrc = aNetworkAdapter->COMGETTER(AttachmentType)(&eAttachmentType); H();
+
+#ifdef VBOX_WITH_NETSHAPER
+ ComObjPtr<IBandwidthGroup> pBwGroup;
+ Bstr bstrBwGroup;
+ hrc = aNetworkAdapter->COMGETTER(BandwidthGroup)(pBwGroup.asOutParam()); H();
+
+ if (!pBwGroup.isNull())
+ {
+ hrc = pBwGroup->COMGETTER(Name)(bstrBwGroup.asOutParam()); H();
+ }
+#endif /* VBOX_WITH_NETSHAPER */
+
+ AssertMsg(uLun == 0, ("Network attachments with LUN > 0 are not supported yet\n"));
+ InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", uLun);
+
+ /*
+ * Do not insert neither a shaper nor a sniffer if we are not attached to anything.
+ * This way we can easily detect if we are attached to anything at the device level.
+ */
+#ifdef VBOX_WITH_NETSHAPER
+ if (bstrBwGroup.isNotEmpty() && eAttachmentType != NetworkAttachmentType_Null)
+ {
+ InsertConfigString(pLunL0, "Driver", "NetShaper");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigString(pCfg, "BwGroup", bstrBwGroup);
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0);
+ }
+#endif /* VBOX_WITH_NETSHAPER */
+
+ if (fSniffer && eAttachmentType != NetworkAttachmentType_Null)
+ {
+ InsertConfigString(pLunL0, "Driver", "NetSniffer");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ hrc = aNetworkAdapter->COMGETTER(TraceFile)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty()) /* check convention for indicating default file. */
+ InsertConfigString(pCfg, "File", bstr);
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0);
+ }
+
+ switch (eAttachmentType)
+ {
+ case NetworkAttachmentType_Null:
+ break;
+
+ case NetworkAttachmentType_NAT:
+ {
+ ComPtr<INATEngine> natEngine;
+ hrc = aNetworkAdapter->COMGETTER(NATEngine)(natEngine.asOutParam()); H();
+ InsertConfigString(pLunL0, "Driver", "NAT");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ /* Configure TFTP prefix and boot filename. */
+ hrc = virtualBox->COMGETTER(HomeFolder)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ InsertConfigString(pCfg, "TFTPPrefix", Utf8StrFmt("%ls%c%s", bstr.raw(), RTPATH_DELIMITER, "TFTP"));
+ hrc = pMachine->COMGETTER(Name)(bstr.asOutParam()); H();
+ InsertConfigString(pCfg, "BootFile", Utf8StrFmt("%ls.pxe", bstr.raw()));
+
+ hrc = natEngine->COMGETTER(Network)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ InsertConfigString(pCfg, "Network", bstr);
+ else
+ {
+ ULONG uSlot;
+ hrc = aNetworkAdapter->COMGETTER(Slot)(&uSlot); H();
+ InsertConfigString(pCfg, "Network", Utf8StrFmt("10.0.%d.0/24", uSlot+2));
+ }
+ hrc = natEngine->COMGETTER(HostIP)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ InsertConfigString(pCfg, "BindIP", bstr);
+ ULONG mtu = 0;
+ ULONG sockSnd = 0;
+ ULONG sockRcv = 0;
+ ULONG tcpSnd = 0;
+ ULONG tcpRcv = 0;
+ hrc = natEngine->GetNetworkSettings(&mtu, &sockSnd, &sockRcv, &tcpSnd, &tcpRcv); H();
+ if (mtu)
+ InsertConfigInteger(pCfg, "SlirpMTU", mtu);
+ if (sockRcv)
+ InsertConfigInteger(pCfg, "SockRcv", sockRcv);
+ if (sockSnd)
+ InsertConfigInteger(pCfg, "SockSnd", sockSnd);
+ if (tcpRcv)
+ InsertConfigInteger(pCfg, "TcpRcv", tcpRcv);
+ if (tcpSnd)
+ InsertConfigInteger(pCfg, "TcpSnd", tcpSnd);
+ hrc = natEngine->COMGETTER(TFTPPrefix)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ {
+ RemoveConfigValue(pCfg, "TFTPPrefix");
+ InsertConfigString(pCfg, "TFTPPrefix", bstr);
+ }
+ hrc = natEngine->COMGETTER(TFTPBootFile)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ {
+ RemoveConfigValue(pCfg, "BootFile");
+ InsertConfigString(pCfg, "BootFile", bstr);
+ }
+ hrc = natEngine->COMGETTER(TFTPNextServer)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ InsertConfigString(pCfg, "NextServer", bstr);
+ BOOL fDNSFlag;
+ hrc = natEngine->COMGETTER(DNSPassDomain)(&fDNSFlag); H();
+ InsertConfigInteger(pCfg, "PassDomain", fDNSFlag);
+ hrc = natEngine->COMGETTER(DNSProxy)(&fDNSFlag); H();
+ InsertConfigInteger(pCfg, "DNSProxy", fDNSFlag);
+ hrc = natEngine->COMGETTER(DNSUseHostResolver)(&fDNSFlag); H();
+ InsertConfigInteger(pCfg, "UseHostResolver", fDNSFlag);
+
+ ULONG aliasMode;
+ hrc = natEngine->COMGETTER(AliasMode)(&aliasMode); H();
+ InsertConfigInteger(pCfg, "AliasMode", aliasMode);
+
+ BOOL fLocalhostReachable;
+ hrc = natEngine->COMGETTER(LocalhostReachable)(&fLocalhostReachable); H();
+ InsertConfigInteger(pCfg, "LocalhostReachable", fLocalhostReachable);
+
+ /* port-forwarding */
+ SafeArray<BSTR> pfs;
+ hrc = natEngine->COMGETTER(Redirects)(ComSafeArrayAsOutParam(pfs)); H();
+
+ PCFGMNODE pPFTree = NULL;
+ if (pfs.size() > 0)
+ InsertConfigNode(pCfg, "PortForwarding", &pPFTree);
+
+ for (unsigned int i = 0; i < pfs.size(); ++i)
+ {
+ PCFGMNODE pPF = NULL; /* /Devices/Dev/.../Config/PortForwarding/$n/ */
+
+ uint16_t port = 0;
+ Utf8Str utf = pfs[i];
+ Utf8Str strName;
+ Utf8Str strProto;
+ Utf8Str strHostPort;
+ Utf8Str strHostIP;
+ Utf8Str strGuestPort;
+ Utf8Str strGuestIP;
+ size_t pos, ppos;
+ pos = ppos = 0;
+#define ITERATE_TO_NEXT_TERM(res, str, pos, ppos) \
+ { \
+ pos = str.find(",", ppos); \
+ if (pos == Utf8Str::npos) \
+ { \
+ Log(( #res " extracting from %s is failed\n", str.c_str())); \
+ continue; \
+ } \
+ res = str.substr(ppos, pos - ppos); \
+ Log2((#res " %s pos:%d, ppos:%d\n", res.c_str(), pos, ppos)); \
+ ppos = pos + 1; \
+ } /* no do { ... } while because of 'continue' */
+ ITERATE_TO_NEXT_TERM(strName, utf, pos, ppos);
+ ITERATE_TO_NEXT_TERM(strProto, utf, pos, ppos);
+ ITERATE_TO_NEXT_TERM(strHostIP, utf, pos, ppos);
+ ITERATE_TO_NEXT_TERM(strHostPort, utf, pos, ppos);
+ ITERATE_TO_NEXT_TERM(strGuestIP, utf, pos, ppos);
+ strGuestPort = utf.substr(ppos, utf.length() - ppos);
+#undef ITERATE_TO_NEXT_TERM
+
+ uint32_t proto = strProto.toUInt32();
+ bool fValid = true;
+ switch (proto)
+ {
+ case NATProtocol_UDP:
+ strProto = "UDP";
+ break;
+ case NATProtocol_TCP:
+ strProto = "TCP";
+ break;
+ default:
+ fValid = false;
+ }
+ /* continue with next rule if no valid proto was passed */
+ if (!fValid)
+ continue;
+
+ InsertConfigNode(pPFTree, Utf8StrFmt("%u", i).c_str(), &pPF);
+
+ if (!strName.isEmpty())
+ InsertConfigString(pPF, "Name", strName);
+
+ InsertConfigString(pPF, "Protocol", strProto);
+
+ if (!strHostIP.isEmpty())
+ InsertConfigString(pPF, "BindIP", strHostIP);
+
+ if (!strGuestIP.isEmpty())
+ InsertConfigString(pPF, "GuestIP", strGuestIP);
+
+ port = RTStrToUInt16(strHostPort.c_str());
+ if (port)
+ InsertConfigInteger(pPF, "HostPort", port);
+
+ port = RTStrToUInt16(strGuestPort.c_str());
+ if (port)
+ InsertConfigInteger(pPF, "GuestPort", port);
+ }
+ break;
+ }
+
+ case NetworkAttachmentType_Bridged:
+ {
+#if (defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)) && !defined(VBOX_WITH_NETFLT)
+ hrc = i_attachToTapInterface(aNetworkAdapter);
+ if (FAILED(hrc))
+ {
+ switch (hrc)
+ {
+ case E_ACCESSDENIED:
+ return pVMM->pfnVMR3SetError(pUVM, VERR_HOSTIF_INIT_FAILED, RT_SRC_POS, N_(
+ "Failed to open '/dev/net/tun' for read/write access. Please check the "
+ "permissions of that node. Either run 'chmod 0666 /dev/net/tun' or "
+ "change the group of that node and make yourself a member of that group. "
+ "Make sure that these changes are permanent, especially if you are "
+ "using udev"));
+ default:
+ AssertMsgFailed(("Could not attach to host interface! Bad!\n"));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_HOSTIF_INIT_FAILED, RT_SRC_POS,
+ N_("Failed to initialize Host Interface Networking"));
+ }
+ }
+
+ Assert((intptr_t)maTapFD[uInstance] >= 0);
+ if ((intptr_t)maTapFD[uInstance] >= 0)
+ {
+ InsertConfigString(pLunL0, "Driver", "HostInterface");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "FileHandle", (intptr_t)maTapFD[uInstance]);
+ }
+
+#elif defined(VBOX_WITH_NETFLT)
+ /*
+ * This is the new VBoxNetFlt+IntNet stuff.
+ */
+ Bstr BridgedIfName;
+ hrc = aNetworkAdapter->COMGETTER(BridgedInterface)(BridgedIfName.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_Bridged: COMGETTER(BridgedInterface) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+
+ Utf8Str BridgedIfNameUtf8(BridgedIfName);
+ const char *pszBridgedIfName = BridgedIfNameUtf8.c_str();
+
+ ComPtr<IHostNetworkInterface> hostInterface;
+ hrc = host->FindHostNetworkInterfaceByName(BridgedIfName.raw(),
+ hostInterface.asOutParam());
+ if (!SUCCEEDED(hrc))
+ {
+ AssertLogRelMsgFailed(("NetworkAttachmentType_Bridged: FindByName failed, rc=%Rhrc (0x%x)\n", hrc, hrc));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS,
+ N_("Nonexistent host networking interface, name '%ls'"),
+ BridgedIfName.raw());
+ }
+
+# if defined(RT_OS_DARWIN)
+ /* The name is in the format 'ifX: long name', chop it off at the colon. */
+ char szTrunk[INTNET_MAX_TRUNK_NAME];
+ RTStrCopy(szTrunk, sizeof(szTrunk), pszBridgedIfName);
+ char *pszColon = (char *)memchr(szTrunk, ':', sizeof(szTrunk));
+// Quick fix for @bugref{5633}
+// if (!pszColon)
+// {
+// /*
+// * Dynamic changing of attachment causes an attempt to configure
+// * network with invalid host adapter (as it is must be changed before
+// * the attachment), calling Detach here will cause a deadlock.
+// * See @bugref{4750}.
+// * hrc = aNetworkAdapter->Detach(); H();
+// */
+// return VMSetError(VMR3GetVM(mpUVM), VERR_INTERNAL_ERROR, RT_SRC_POS,
+// N_("Malformed host interface networking name '%ls'"),
+// BridgedIfName.raw());
+// }
+ if (pszColon)
+ *pszColon = '\0';
+ const char *pszTrunk = szTrunk;
+
+# elif defined(RT_OS_SOLARIS)
+ /* The name is in the format 'ifX[:1] - long name, chop it off at space. */
+ char szTrunk[256];
+ strlcpy(szTrunk, pszBridgedIfName, sizeof(szTrunk));
+ char *pszSpace = (char *)memchr(szTrunk, ' ', sizeof(szTrunk));
+
+ /*
+ * Currently don't bother about malformed names here for the sake of people using
+ * VBoxManage and setting only the NIC name from there. If there is a space we
+ * chop it off and proceed, otherwise just use whatever we've got.
+ */
+ if (pszSpace)
+ *pszSpace = '\0';
+
+ /* Chop it off at the colon (zone naming eg: e1000g:1 we need only the e1000g) */
+ char *pszColon = (char *)memchr(szTrunk, ':', sizeof(szTrunk));
+ if (pszColon)
+ *pszColon = '\0';
+
+ const char *pszTrunk = szTrunk;
+
+# elif defined(RT_OS_WINDOWS)
+ HostNetworkInterfaceType_T eIfType;
+ hrc = hostInterface->COMGETTER(InterfaceType)(&eIfType);
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_Bridged: COMGETTER(InterfaceType) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+
+ if (eIfType != HostNetworkInterfaceType_Bridged)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS,
+ N_("Interface ('%ls') is not a Bridged Adapter interface"),
+ BridgedIfName.raw());
+
+ hrc = hostInterface->COMGETTER(Id)(bstr.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_Bridged: COMGETTER(Id) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ Guid hostIFGuid(bstr);
+
+ INetCfg *pNc;
+ ComPtr<INetCfgComponent> pAdaptorComponent;
+ LPWSTR pszApp;
+
+ hrc = VBoxNetCfgWinQueryINetCfg(&pNc, FALSE, L"VirtualBox", 10, &pszApp);
+ Assert(hrc == S_OK);
+ if (hrc != S_OK)
+ {
+ LogRel(("NetworkAttachmentType_Bridged: Failed to get NetCfg, hrc=%Rhrc (0x%x)\n", hrc, hrc));
+ H();
+ }
+
+ /* get the adapter's INetCfgComponent*/
+ hrc = VBoxNetCfgWinGetComponentByGuid(pNc, &GUID_DEVCLASS_NET, (GUID*)hostIFGuid.raw(),
+ pAdaptorComponent.asOutParam());
+ if (hrc != S_OK)
+ {
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+ LogRel(("NetworkAttachmentType_Bridged: VBoxNetCfgWinGetComponentByGuid failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+# define VBOX_WIN_BINDNAME_PREFIX "\\DEVICE\\"
+ char szTrunkName[INTNET_MAX_TRUNK_NAME];
+ char *pszTrunkName = szTrunkName;
+ wchar_t * pswzBindName;
+ hrc = pAdaptorComponent->GetBindName(&pswzBindName);
+ Assert(hrc == S_OK);
+ if (hrc == S_OK)
+ {
+ int cwBindName = (int)wcslen(pswzBindName) + 1;
+ int cbFullBindNamePrefix = sizeof(VBOX_WIN_BINDNAME_PREFIX);
+ if (sizeof(szTrunkName) > cbFullBindNamePrefix + cwBindName)
+ {
+ strcpy(szTrunkName, VBOX_WIN_BINDNAME_PREFIX);
+ pszTrunkName += cbFullBindNamePrefix-1;
+ if (!WideCharToMultiByte(CP_ACP, 0, pswzBindName, cwBindName, pszTrunkName,
+ sizeof(szTrunkName) - cbFullBindNamePrefix + 1, NULL, NULL))
+ {
+ DWORD err = GetLastError();
+ hrc = HRESULT_FROM_WIN32(err);
+ AssertMsgFailed(("hrc=%Rhrc %#x\n", hrc, hrc));
+ AssertLogRelMsgFailed(("NetworkAttachmentType_Bridged: WideCharToMultiByte failed, hr=%Rhrc (0x%x) err=%u\n",
+ hrc, hrc, err));
+ }
+ }
+ else
+ {
+ AssertLogRelMsgFailed(("NetworkAttachmentType_Bridged: insufficient szTrunkName buffer space\n"));
+ /** @todo set appropriate error code */
+ hrc = E_FAIL;
+ }
+
+ if (hrc != S_OK)
+ {
+ AssertFailed();
+ CoTaskMemFree(pswzBindName);
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+ H();
+ }
+
+ /* we're not freeing the bind name since we'll use it later for detecting wireless*/
+ }
+ else
+ {
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+ AssertLogRelMsgFailed(("NetworkAttachmentType_Bridged: VBoxNetCfgWinGetComponentByGuid failed, hrc (0x%x)",
+ hrc));
+ H();
+ }
+
+ const char *pszTrunk = szTrunkName;
+ /* we're not releasing the INetCfg stuff here since we use it later to figure out whether it is wireless */
+
+# elif defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)
+# if defined(RT_OS_FREEBSD)
+ /*
+ * If we bridge to a tap interface open it the `old' direct way.
+ * This works and performs better than bridging a physical
+ * interface via the current FreeBSD vboxnetflt implementation.
+ */
+ if (!strncmp(pszBridgedIfName, RT_STR_TUPLE("tap"))) {
+ hrc = i_attachToTapInterface(aNetworkAdapter);
+ if (FAILED(hrc))
+ {
+ switch (hrc)
+ {
+ case E_ACCESSDENIED:
+ return pVMM->pfnVMR3SetError(pUVM, VERR_HOSTIF_INIT_FAILED, RT_SRC_POS, N_(
+ "Failed to open '/dev/%s' for read/write access. Please check the "
+ "permissions of that node, and that the net.link.tap.user_open "
+ "sysctl is set. Either run 'chmod 0666 /dev/%s' or change the "
+ "group of that node to vboxusers and make yourself a member of "
+ "that group. Make sure that these changes are permanent."),
+ pszBridgedIfName, pszBridgedIfName);
+ default:
+ AssertMsgFailed(("Could not attach to tap interface! Bad!\n"));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_HOSTIF_INIT_FAILED, RT_SRC_POS,
+ N_("Failed to initialize Host Interface Networking"));
+ }
+ }
+
+ Assert((intptr_t)maTapFD[uInstance] >= 0);
+ if ((intptr_t)maTapFD[uInstance] >= 0)
+ {
+ InsertConfigString(pLunL0, "Driver", "HostInterface");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "FileHandle", (intptr_t)maTapFD[uInstance]);
+ }
+ break;
+ }
+# endif
+ /** @todo Check for malformed names. */
+ const char *pszTrunk = pszBridgedIfName;
+
+ /* Issue a warning if the interface is down */
+ {
+ int iSock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (iSock >= 0)
+ {
+ struct ifreq Req;
+ RT_ZERO(Req);
+ RTStrCopy(Req.ifr_name, sizeof(Req.ifr_name), pszBridgedIfName);
+ if (ioctl(iSock, SIOCGIFFLAGS, &Req) >= 0)
+ if ((Req.ifr_flags & IFF_UP) == 0)
+ i_atVMRuntimeErrorCallbackF(0, "BridgedInterfaceDown",
+ N_("Bridged interface %s is down. Guest will not be able to use this interface"),
+ pszBridgedIfName);
+
+ close(iSock);
+ }
+ }
+
+# else
+# error "PORTME (VBOX_WITH_NETFLT)"
+# endif
+
+# if defined(RT_OS_DARWIN) && defined(VBOX_WITH_VMNET)
+ InsertConfigString(pLunL0, "Driver", "VMNet");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigString(pCfg, "Trunk", pszTrunk);
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetFlt);
+# else
+ InsertConfigString(pLunL0, "Driver", "IntNet");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigString(pCfg, "Trunk", pszTrunk);
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetFlt);
+ InsertConfigInteger(pCfg, "IgnoreConnectFailure", (uint64_t)fIgnoreConnectFailure);
+ InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy);
+ char szNetwork[INTNET_MAX_NETWORK_NAME];
+
+# if defined(RT_OS_SOLARIS) || defined(RT_OS_DARWIN)
+ /*
+ * 'pszTrunk' contains just the interface name required in ring-0, while 'pszBridgedIfName' contains
+ * interface name + optional description. We must not pass any description to the VM as it can differ
+ * for the same interface name, eg: "nge0 - ethernet" (GUI) vs "nge0" (VBoxManage).
+ */
+ RTStrPrintf(szNetwork, sizeof(szNetwork), "HostInterfaceNetworking-%s", pszTrunk);
+# else
+ RTStrPrintf(szNetwork, sizeof(szNetwork), "HostInterfaceNetworking-%s", pszBridgedIfName);
+# endif
+ InsertConfigString(pCfg, "Network", szNetwork);
+ networkName = Bstr(szNetwork);
+ trunkName = Bstr(pszTrunk);
+ trunkType = Bstr(TRUNKTYPE_NETFLT);
+
+ BOOL fSharedMacOnWire = false;
+ hrc = hostInterface->COMGETTER(Wireless)(&fSharedMacOnWire);
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_Bridged: COMGETTER(Wireless) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ else if (fSharedMacOnWire)
+ {
+ InsertConfigInteger(pCfg, "SharedMacOnWire", true);
+ Log(("Set SharedMacOnWire\n"));
+ }
+
+# if defined(RT_OS_SOLARIS)
+# if 0 /* bird: this is a bit questionable and might cause more trouble than its worth. */
+ /* Zone access restriction, don't allow snooping the global zone. */
+ zoneid_t ZoneId = getzoneid();
+ if (ZoneId != GLOBAL_ZONEID)
+ {
+ InsertConfigInteger(pCfg, "IgnoreAllPromisc", true);
+ }
+# endif
+# endif
+# endif
+
+#elif defined(RT_OS_WINDOWS) /* not defined NetFlt */
+ /* NOTHING TO DO HERE */
+#elif defined(RT_OS_LINUX)
+/// @todo aleksey: is there anything to be done here?
+#elif defined(RT_OS_FREEBSD)
+/** @todo FreeBSD: Check out this later (HIF networking). */
+#else
+# error "Port me"
+#endif
+ break;
+ }
+
+ case NetworkAttachmentType_Internal:
+ {
+ hrc = aNetworkAdapter->COMGETTER(InternalNetwork)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ {
+ InsertConfigString(pLunL0, "Driver", "IntNet");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigString(pCfg, "Network", bstr);
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_WhateverNone);
+ InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy);
+ networkName = bstr;
+ trunkType = Bstr(TRUNKTYPE_WHATEVER);
+ }
+ break;
+ }
+
+ case NetworkAttachmentType_HostOnly:
+ {
+ InsertConfigString(pLunL0, "Driver", "IntNet");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ Bstr HostOnlyName;
+ hrc = aNetworkAdapter->COMGETTER(HostOnlyInterface)(HostOnlyName.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(HostOnlyInterface) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+
+ Utf8Str HostOnlyNameUtf8(HostOnlyName);
+ const char *pszHostOnlyName = HostOnlyNameUtf8.c_str();
+#ifdef VBOX_WITH_VMNET
+ /* Check if the matching host-only network has already been created. */
+ Bstr bstrLowerIP, bstrUpperIP, bstrNetworkMask;
+ BstrFmt bstrNetworkName("Legacy %s Network", pszHostOnlyName);
+ ComPtr<IHostOnlyNetwork> hostOnlyNetwork;
+ hrc = virtualBox->FindHostOnlyNetworkByName(bstrNetworkName.raw(), hostOnlyNetwork.asOutParam());
+ if (FAILED(hrc))
+ {
+ /*
+ * With VMNET there is no VBoxNetAdp to create vboxnetX adapters,
+ * which means that the Host object won't be able to re-create
+ * them from extra data. Go through existing DHCP/adapter config
+ * to derive the parameters for the new network.
+ */
+ BstrFmt bstrOldNetworkName("HostInterfaceNetworking-%s", pszHostOnlyName);
+ ComPtr<IDHCPServer> dhcpServer;
+ hrc = virtualBox->FindDHCPServerByNetworkName(bstrOldNetworkName.raw(),
+ dhcpServer.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ /* There is a DHCP server available for this network. */
+ hrc = dhcpServer->COMGETTER(LowerIP)(bstrLowerIP.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("Console::i_configNetwork: COMGETTER(LowerIP) failed, hrc (%Rhrc)\n", hrc));
+ H();
+ }
+ hrc = dhcpServer->COMGETTER(UpperIP)(bstrUpperIP.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("Console::i_configNetwork: COMGETTER(UpperIP) failed, hrc (%Rhrc)\n", hrc));
+ H();
+ }
+ hrc = dhcpServer->COMGETTER(NetworkMask)(bstrNetworkMask.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("Console::i_configNetwork: COMGETTER(NetworkMask) failed, hrc (%Rhrc)\n", hrc));
+ H();
+ }
+ }
+ else
+ {
+ /* No DHCP server for this hostonly interface, let's look at extra data */
+ hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPAddress",
+ pszHostOnlyName).raw(),
+ bstrLowerIP.asOutParam());
+ if (SUCCEEDED(hrc) && !bstrLowerIP.isEmpty())
+ hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPNetMask",
+ pszHostOnlyName).raw(),
+ bstrNetworkMask.asOutParam());
+
+ }
+ RTNETADDRIPV4 ipAddr, ipMask;
+ vrc = bstrLowerIP.isEmpty() ? VERR_MISSING : RTNetStrToIPv4Addr(Utf8Str(bstrLowerIP).c_str(), &ipAddr);
+ if (RT_FAILURE(vrc))
+ {
+ /* We failed to locate any valid config of this vboxnetX interface, assume defaults. */
+ LogRel(("NetworkAttachmentType_HostOnly: Invalid or missing lower IP '%ls', using '%ls' instead.\n",
+ bstrLowerIP.raw(), getDefaultIPv4Address(Bstr(pszHostOnlyName)).raw()));
+ bstrLowerIP = getDefaultIPv4Address(Bstr(pszHostOnlyName));
+ bstrNetworkMask.setNull();
+ bstrUpperIP.setNull();
+ vrc = RTNetStrToIPv4Addr(Utf8Str(bstrLowerIP).c_str(), &ipAddr);
+ AssertLogRelMsgReturn(RT_SUCCESS(vrc), ("RTNetStrToIPv4Addr(%ls) failed, vrc=%Rrc\n", bstrLowerIP.raw(), vrc),
+ VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR);
+ }
+ vrc = bstrNetworkMask.isEmpty() ? VERR_MISSING : RTNetStrToIPv4Addr(Utf8Str(bstrNetworkMask).c_str(), &ipMask);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: Invalid or missing network mask '%ls', using '%s' instead.\n",
+ bstrNetworkMask.raw(), VBOXNET_IPV4MASK_DEFAULT));
+ bstrNetworkMask = VBOXNET_IPV4MASK_DEFAULT;
+ vrc = RTNetStrToIPv4Addr(Utf8Str(bstrNetworkMask).c_str(), &ipMask);
+ AssertLogRelMsgReturn(RT_SUCCESS(vrc), ("RTNetStrToIPv4Addr(%ls) failed, vrc=%Rrc\n", bstrNetworkMask.raw(), vrc),
+ VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR);
+ }
+ vrc = bstrUpperIP.isEmpty() ? VERR_MISSING : RTNetStrToIPv4Addr(Utf8Str(bstrUpperIP).c_str(), &ipAddr);
+ if (RT_FAILURE(vrc))
+ {
+ ipAddr.au32[0] = RT_H2N_U32((RT_N2H_U32(ipAddr.au32[0]) | ~RT_N2H_U32(ipMask.au32[0])) - 1); /* Do we need to exlude the last IP? */
+ LogRel(("NetworkAttachmentType_HostOnly: Invalid or missing upper IP '%ls', using '%RTnaipv4' instead.\n",
+ bstrUpperIP.raw(), ipAddr));
+ bstrUpperIP = BstrFmt("%RTnaipv4", ipAddr);
+ }
+
+ /* All parameters are set, create the new network. */
+ hrc = virtualBox->CreateHostOnlyNetwork(bstrNetworkName.raw(), hostOnlyNetwork.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: failed to create host-only network, hrc (0x%x)\n", hrc));
+ H();
+ }
+ hrc = hostOnlyNetwork->COMSETTER(NetworkMask)(bstrNetworkMask.raw());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMSETTER(NetworkMask) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ hrc = hostOnlyNetwork->COMSETTER(LowerIP)(bstrLowerIP.raw());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMSETTER(LowerIP) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ hrc = hostOnlyNetwork->COMSETTER(UpperIP)(bstrUpperIP.raw());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMSETTER(UpperIP) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ LogRel(("Console: created host-only network '%ls' with mask '%ls' and range '%ls'-'%ls'\n",
+ bstrNetworkName.raw(), bstrNetworkMask.raw(), bstrLowerIP.raw(), bstrUpperIP.raw()));
+ }
+ else
+ {
+ /* The matching host-only network already exists. Tell the user to switch to it. */
+ hrc = hostOnlyNetwork->COMGETTER(NetworkMask)(bstrNetworkMask.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(NetworkMask) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ hrc = hostOnlyNetwork->COMGETTER(LowerIP)(bstrLowerIP.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(LowerIP) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ hrc = hostOnlyNetwork->COMGETTER(UpperIP)(bstrUpperIP.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(UpperIP) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ }
+ return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS,
+ N_("Host-only adapters are no longer supported!\n"
+ "For your convenience a host-only network named '%ls' has been "
+ "created with network mask '%ls' and IP address range '%ls' - '%ls'.\n"
+ "To fix this problem, switch to 'Host-only Network' "
+ "attachment type in the VM settings.\n"),
+ bstrNetworkName.raw(), bstrNetworkMask.raw(),
+ bstrLowerIP.raw(), bstrUpperIP.raw());
+#endif /* VBOX_WITH_VMNET */
+ ComPtr<IHostNetworkInterface> hostInterface;
+ hrc = host->FindHostNetworkInterfaceByName(HostOnlyName.raw(),
+ hostInterface.asOutParam());
+ if (!SUCCEEDED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: FindByName failed, vrc=%Rrc\n", vrc));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS,
+ N_("Nonexistent host networking interface, name '%ls'"), HostOnlyName.raw());
+ }
+
+ char szNetwork[INTNET_MAX_NETWORK_NAME];
+ RTStrPrintf(szNetwork, sizeof(szNetwork), "HostInterfaceNetworking-%s", pszHostOnlyName);
+
+#if defined(RT_OS_WINDOWS)
+# ifndef VBOX_WITH_NETFLT
+ hrc = E_NOTIMPL;
+ LogRel(("NetworkAttachmentType_HostOnly: Not Implemented\n"));
+ H();
+# else /* defined VBOX_WITH_NETFLT*/
+ /** @todo r=bird: Put this in a function. */
+
+ HostNetworkInterfaceType_T eIfType;
+ hrc = hostInterface->COMGETTER(InterfaceType)(&eIfType);
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(InterfaceType) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+
+ if (eIfType != HostNetworkInterfaceType_HostOnly)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS,
+ N_("Interface ('%ls') is not a Host-Only Adapter interface"),
+ HostOnlyName.raw());
+
+ hrc = hostInterface->COMGETTER(Id)(bstr.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(Id) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ Guid hostIFGuid(bstr);
+
+ INetCfg *pNc;
+ ComPtr<INetCfgComponent> pAdaptorComponent;
+ LPWSTR pszApp;
+ hrc = VBoxNetCfgWinQueryINetCfg(&pNc, FALSE, L"VirtualBox", 10, &pszApp);
+ Assert(hrc == S_OK);
+ if (hrc != S_OK)
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: Failed to get NetCfg, hrc=%Rhrc (0x%x)\n", hrc, hrc));
+ H();
+ }
+
+ /* get the adapter's INetCfgComponent*/
+ hrc = VBoxNetCfgWinGetComponentByGuid(pNc, &GUID_DEVCLASS_NET, (GUID*)hostIFGuid.raw(),
+ pAdaptorComponent.asOutParam());
+ if (hrc != S_OK)
+ {
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+ LogRel(("NetworkAttachmentType_HostOnly: VBoxNetCfgWinGetComponentByGuid failed, hrc=%Rhrc (0x%x)\n", hrc, hrc));
+ H();
+ }
+# define VBOX_WIN_BINDNAME_PREFIX "\\DEVICE\\"
+ char szTrunkName[INTNET_MAX_TRUNK_NAME];
+ bool fNdis6 = false;
+ wchar_t * pwszHelpText;
+ hrc = pAdaptorComponent->GetHelpText(&pwszHelpText);
+ Assert(hrc == S_OK);
+ if (hrc == S_OK)
+ {
+ Log(("help-text=%ls\n", pwszHelpText));
+ if (!wcscmp(pwszHelpText, L"VirtualBox NDIS 6.0 Miniport Driver"))
+ fNdis6 = true;
+ CoTaskMemFree(pwszHelpText);
+ }
+ if (fNdis6)
+ {
+ strncpy(szTrunkName, pszHostOnlyName, sizeof(szTrunkName) - 1);
+ Log(("trunk=%s\n", szTrunkName));
+ }
+ else
+ {
+ char *pszTrunkName = szTrunkName;
+ wchar_t * pswzBindName;
+ hrc = pAdaptorComponent->GetBindName(&pswzBindName);
+ Assert(hrc == S_OK);
+ if (hrc == S_OK)
+ {
+ int cwBindName = (int)wcslen(pswzBindName) + 1;
+ int cbFullBindNamePrefix = sizeof(VBOX_WIN_BINDNAME_PREFIX);
+ if (sizeof(szTrunkName) > cbFullBindNamePrefix + cwBindName)
+ {
+ strcpy(szTrunkName, VBOX_WIN_BINDNAME_PREFIX);
+ pszTrunkName += cbFullBindNamePrefix-1;
+ if (!WideCharToMultiByte(CP_ACP, 0, pswzBindName, cwBindName, pszTrunkName,
+ sizeof(szTrunkName) - cbFullBindNamePrefix + 1, NULL, NULL))
+ {
+ DWORD err = GetLastError();
+ hrc = HRESULT_FROM_WIN32(err);
+ AssertLogRelMsgFailed(("NetworkAttachmentType_HostOnly: WideCharToMultiByte failed, hr=%Rhrc (0x%x) err=%u\n",
+ hrc, hrc, err));
+ }
+ }
+ else
+ {
+ AssertLogRelMsgFailed(("NetworkAttachmentType_HostOnly: insufficient szTrunkName buffer space\n"));
+ /** @todo set appropriate error code */
+ hrc = E_FAIL;
+ }
+
+ if (hrc != S_OK)
+ {
+ AssertFailed();
+ CoTaskMemFree(pswzBindName);
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+ H();
+ }
+ }
+ else
+ {
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+ AssertLogRelMsgFailed(("NetworkAttachmentType_HostOnly: VBoxNetCfgWinGetComponentByGuid failed, hrc=%Rhrc (0x%x)\n",
+ hrc, hrc));
+ H();
+ }
+
+
+ CoTaskMemFree(pswzBindName);
+ }
+
+ trunkType = TRUNKTYPE_NETADP;
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetAdp);
+
+ pAdaptorComponent.setNull();
+ /* release the pNc finally */
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+
+ const char *pszTrunk = szTrunkName;
+
+ InsertConfigString(pCfg, "Trunk", pszTrunk);
+ InsertConfigString(pCfg, "Network", szNetwork);
+ InsertConfigInteger(pCfg, "IgnoreConnectFailure", (uint64_t)fIgnoreConnectFailure); /** @todo why is this
+ windows only?? */
+ networkName = Bstr(szNetwork);
+ trunkName = Bstr(pszTrunk);
+# endif /* defined VBOX_WITH_NETFLT*/
+#elif defined(RT_OS_DARWIN)
+ InsertConfigString(pCfg, "Trunk", pszHostOnlyName);
+ InsertConfigString(pCfg, "Network", szNetwork);
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetAdp);
+ networkName = Bstr(szNetwork);
+ trunkName = Bstr(pszHostOnlyName);
+ trunkType = TRUNKTYPE_NETADP;
+#else
+ InsertConfigString(pCfg, "Trunk", pszHostOnlyName);
+ InsertConfigString(pCfg, "Network", szNetwork);
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetFlt);
+ networkName = Bstr(szNetwork);
+ trunkName = Bstr(pszHostOnlyName);
+ trunkType = TRUNKTYPE_NETFLT;
+#endif
+ InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy);
+
+#if !defined(RT_OS_WINDOWS) && defined(VBOX_WITH_NETFLT)
+
+ Bstr tmpAddr, tmpMask;
+
+ hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPAddress",
+ pszHostOnlyName).raw(),
+ tmpAddr.asOutParam());
+ if (SUCCEEDED(hrc) && !tmpAddr.isEmpty())
+ {
+ hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPNetMask",
+ pszHostOnlyName).raw(),
+ tmpMask.asOutParam());
+ if (SUCCEEDED(hrc) && !tmpMask.isEmpty())
+ hrc = hostInterface->EnableStaticIPConfig(tmpAddr.raw(),
+ tmpMask.raw());
+ else
+ hrc = hostInterface->EnableStaticIPConfig(tmpAddr.raw(),
+ Bstr(VBOXNET_IPV4MASK_DEFAULT).raw());
+ }
+ else
+ {
+ /* Grab the IP number from the 'vboxnetX' instance number (see netif.h) */
+ hrc = hostInterface->EnableStaticIPConfig(getDefaultIPv4Address(Bstr(pszHostOnlyName)).raw(),
+ Bstr(VBOXNET_IPV4MASK_DEFAULT).raw());
+ }
+
+ ComAssertComRC(hrc); /** @todo r=bird: Why this isn't fatal? (H()) */
+
+ hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPV6Address",
+ pszHostOnlyName).raw(),
+ tmpAddr.asOutParam());
+ if (SUCCEEDED(hrc))
+ hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPV6NetMask", pszHostOnlyName).raw(),
+ tmpMask.asOutParam());
+ if (SUCCEEDED(hrc) && !tmpAddr.isEmpty() && !tmpMask.isEmpty())
+ {
+ hrc = hostInterface->EnableStaticIPConfigV6(tmpAddr.raw(),
+ Utf8Str(tmpMask).toUInt32());
+ ComAssertComRC(hrc); /** @todo r=bird: Why this isn't fatal? (H()) */
+ }
+#endif
+ break;
+ }
+
+ case NetworkAttachmentType_Generic:
+ {
+ hrc = aNetworkAdapter->COMGETTER(GenericDriver)(bstr.asOutParam()); H();
+ SafeArray<BSTR> names;
+ SafeArray<BSTR> values;
+ hrc = aNetworkAdapter->GetProperties(Bstr().raw(),
+ ComSafeArrayAsOutParam(names),
+ ComSafeArrayAsOutParam(values)); H();
+
+ InsertConfigString(pLunL0, "Driver", bstr);
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ for (size_t ii = 0; ii < names.size(); ++ii)
+ {
+ if (values[ii] && *values[ii])
+ {
+ Utf8Str name = names[ii];
+ Utf8Str value = values[ii];
+ InsertConfigString(pCfg, name.c_str(), value);
+ }
+ }
+ break;
+ }
+
+ case NetworkAttachmentType_NATNetwork:
+ {
+ hrc = aNetworkAdapter->COMGETTER(NATNetwork)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ {
+ /** @todo add intnet prefix to separate namespaces, and add trunk if dealing with vboxnatX */
+ InsertConfigString(pLunL0, "Driver", "IntNet");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigString(pCfg, "Network", bstr);
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_WhateverNone);
+ InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy);
+ networkName = bstr;
+ trunkType = Bstr(TRUNKTYPE_WHATEVER);
+ }
+ break;
+ }
+
+#ifdef VBOX_WITH_CLOUD_NET
+ case NetworkAttachmentType_Cloud:
+ {
+ static const char *s_pszCloudExtPackName = "Oracle VM VirtualBox Extension Pack";
+ /*
+ * Cloud network attachments do not work wihout installed extpack.
+ * Without extpack support they won't work either.
+ */
+# ifdef VBOX_WITH_EXTPACK
+ if (!mptrExtPackManager->i_isExtPackUsable(s_pszCloudExtPackName))
+# endif
+ {
+ return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS,
+ N_("Implementation of the cloud network attachment not found!\n"
+ "To fix this problem, either install the '%s' or switch to "
+ "another network attachment type in the VM settings."),
+ s_pszCloudExtPackName);
+ }
+
+ ComPtr<ICloudNetwork> network;
+ hrc = aNetworkAdapter->COMGETTER(CloudNetwork)(bstr.asOutParam()); H();
+ hrc = pMachine->COMGETTER(Name)(mGateway.mTargetVM.asOutParam()); H();
+ hrc = virtualBox->FindCloudNetworkByName(bstr.raw(), network.asOutParam()); H();
+ hrc = generateKeys(mGateway);
+ if (FAILED(hrc))
+ {
+ if (hrc == E_NOTIMPL)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS,
+ N_("Failed to generate a key pair due to missing libssh\n"
+ "To fix this problem, either build VirtualBox with libssh "
+ "support or switch to another network attachment type in "
+ "the VM settings."));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS,
+ N_("Failed to generate a key pair due to libssh error!"));
+ }
+ hrc = startCloudGateway(virtualBox, network, mGateway);
+ if (FAILED(hrc))
+ {
+ if (hrc == VBOX_E_OBJECT_NOT_FOUND)
+ return pVMM->pfnVMR3SetError(pUVM, hrc, RT_SRC_POS,
+ N_("Failed to start cloud gateway instance.\nCould not find suitable "
+ "standard cloud images. Make sure you ran 'VBoxManage cloud network setup' "
+ "with correct '--gateway-os-name' and '--gateway-os-version' parameters. "
+ "Check VBoxSVC.log for actual values used to look up cloud images."));
+ return pVMM->pfnVMR3SetError(pUVM, hrc, RT_SRC_POS,
+ N_("Failed to start cloud gateway instance.\nMake sure you set up "
+ "cloud networking properly with 'VBoxManage cloud network setup'. "
+ "Check VBoxSVC.log for details."));
+ }
+ InsertConfigBytes(pDevCfg, "MAC", &mGateway.mCloudMacAddress, sizeof(mGateway.mCloudMacAddress));
+ if (!bstr.isEmpty())
+ {
+ InsertConfigString(pLunL0, "Driver", "CloudTunnel");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigPassword(pCfg, "SshKey", mGateway.mPrivateSshKey);
+ InsertConfigString(pCfg, "PrimaryIP", mGateway.mCloudPublicIp);
+ InsertConfigString(pCfg, "SecondaryIP", mGateway.mCloudSecondaryPublicIp);
+ InsertConfigBytes(pCfg, "TargetMAC", &mGateway.mLocalMacAddress, sizeof(mGateway.mLocalMacAddress));
+ hrc = i_configProxy(virtualBox, pCfg, "Primary", mGateway.mCloudPublicIp);
+ if (FAILED(hrc))
+ {
+ return pVMM->pfnVMR3SetError(pUVM, hrc, RT_SRC_POS,
+ N_("Failed to configure proxy for accessing cloud gateway instance via primary VNIC.\n"
+ "Check VirtualBox.log for details."));
+ }
+ hrc = i_configProxy(virtualBox, pCfg, "Secondary", mGateway.mCloudSecondaryPublicIp);
+ if (FAILED(hrc))
+ {
+ return pVMM->pfnVMR3SetError(pUVM, hrc, RT_SRC_POS,
+ N_("Failed to configure proxy for accessing cloud gateway instance via secondary VNIC.\n"
+ "Check VirtualBox.log for details."));
+ }
+ networkName = bstr;
+ trunkType = Bstr(TRUNKTYPE_WHATEVER);
+ }
+ break;
+ }
+#endif /* VBOX_WITH_CLOUD_NET */
+
+#ifdef VBOX_WITH_VMNET
+ case NetworkAttachmentType_HostOnlyNetwork:
+ {
+ Bstr bstrId, bstrNetMask, bstrLowerIP, bstrUpperIP;
+ ComPtr<IHostOnlyNetwork> network;
+ hrc = aNetworkAdapter->COMGETTER(HostOnlyNetwork)(bstr.asOutParam()); H();
+ hrc = virtualBox->FindHostOnlyNetworkByName(bstr.raw(), network.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnlyNetwork: FindByName failed, hrc (0x%x)\n", hrc));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS,
+ N_("Nonexistent host-only network '%ls'"), bstr.raw());
+ }
+ hrc = network->COMGETTER(Id)(bstrId.asOutParam()); H();
+ hrc = network->COMGETTER(NetworkMask)(bstrNetMask.asOutParam()); H();
+ hrc = network->COMGETTER(LowerIP)(bstrLowerIP.asOutParam()); H();
+ hrc = network->COMGETTER(UpperIP)(bstrUpperIP.asOutParam()); H();
+ if (!bstr.isEmpty())
+ {
+ InsertConfigString(pLunL0, "Driver", "VMNet");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ // InsertConfigString(pCfg, "Trunk", Utf8Str(bstr).c_str());
+ // InsertConfigString(pCfg, "Network", BstrFmt("HostOnlyNetworking-%ls", bstr.raw()));
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetAdp);
+ InsertConfigString(pCfg, "Id", Utf8Str(bstrId).c_str());
+ InsertConfigString(pCfg, "NetworkMask", Utf8Str(bstrNetMask).c_str());
+ InsertConfigString(pCfg, "LowerIP", Utf8Str(bstrLowerIP).c_str());
+ InsertConfigString(pCfg, "UpperIP", Utf8Str(bstrUpperIP).c_str());
+ // InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy);
+ networkName.setNull(); // We do not want DHCP server on our network!
+ // trunkType = Bstr(TRUNKTYPE_WHATEVER);
+ }
+ break;
+ }
+#endif /* VBOX_WITH_VMNET */
+
+ default:
+ AssertMsgFailed(("should not get here!\n"));
+ break;
+ }
+
+ /*
+ * Attempt to attach the driver.
+ */
+ switch (eAttachmentType)
+ {
+ case NetworkAttachmentType_Null:
+ break;
+
+ case NetworkAttachmentType_Bridged:
+ case NetworkAttachmentType_Internal:
+ case NetworkAttachmentType_HostOnly:
+#ifdef VBOX_WITH_VMNET
+ case NetworkAttachmentType_HostOnlyNetwork:
+#endif /* VBOX_WITH_VMNET */
+ case NetworkAttachmentType_NAT:
+ case NetworkAttachmentType_Generic:
+ case NetworkAttachmentType_NATNetwork:
+#ifdef VBOX_WITH_CLOUD_NET
+ case NetworkAttachmentType_Cloud:
+#endif /* VBOX_WITH_CLOUD_NET */
+ {
+ if (SUCCEEDED(hrc) && RT_SUCCESS(vrc))
+ {
+ if (fAttachDetach)
+ {
+ vrc = pVMM->pfnPDMR3DriverAttach(mpUVM, pszDevice, uInstance, uLun, 0 /*fFlags*/, NULL /* ppBase */);
+ //AssertRC(vrc);
+ }
+
+ {
+ /** @todo pritesh: get the dhcp server name from the
+ * previous network configuration and then stop the server
+ * else it may conflict with the dhcp server running with
+ * the current attachment type
+ */
+ /* Stop the hostonly DHCP Server */
+ }
+
+ /*
+ * NAT networks start their DHCP server theirself, see NATNetwork::Start()
+ */
+ if ( !networkName.isEmpty()
+ && eAttachmentType != NetworkAttachmentType_NATNetwork)
+ {
+ /*
+ * Until we implement service reference counters DHCP Server will be stopped
+ * by DHCPServerRunner destructor.
+ */
+ ComPtr<IDHCPServer> dhcpServer;
+ hrc = virtualBox->FindDHCPServerByNetworkName(networkName.raw(), dhcpServer.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ /* there is a DHCP server available for this network */
+ BOOL fEnabledDhcp;
+ hrc = dhcpServer->COMGETTER(Enabled)(&fEnabledDhcp);
+ if (FAILED(hrc))
+ {
+ LogRel(("DHCP svr: COMGETTER(Enabled) failed, hrc (%Rhrc)\n", hrc));
+ H();
+ }
+
+ if (fEnabledDhcp)
+ hrc = dhcpServer->Start(trunkName.raw(), trunkType.raw());
+ }
+ else
+ hrc = S_OK;
+ }
+ }
+
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("should not get here!\n"));
+ break;
+ }
+
+ meAttachmentType[uInstance] = eAttachmentType;
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+
+#undef H
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Configures the serial port at the given CFGM node with the supplied parameters.
+ *
+ * @returns VBox status code.
+ * @param pInst The instance CFGM node.
+ * @param ePortMode The port mode to sue.
+ * @param pszPath The serial port path.
+ * @param fServer Flag whether the port should act as a server
+ * for the pipe and TCP mode or connect as a client.
+ */
+int Console::i_configSerialPort(PCFGMNODE pInst, PortMode_T ePortMode, const char *pszPath, bool fServer)
+{
+ PCFGMNODE pLunL0 = NULL; /* /Devices/Dev/0/LUN#0/ */
+ PCFGMNODE pLunL1 = NULL; /* /Devices/Dev/0/LUN#0/AttachedDriver/ */
+ PCFGMNODE pLunL1Cfg = NULL; /* /Devices/Dev/0/LUN#0/AttachedDriver/Config */
+
+ try
+ {
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ if (ePortMode == PortMode_HostPipe)
+ {
+ InsertConfigString(pLunL0, "Driver", "Char");
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "NamedPipe");
+ InsertConfigNode(pLunL1, "Config", &pLunL1Cfg);
+ InsertConfigString(pLunL1Cfg, "Location", pszPath);
+ InsertConfigInteger(pLunL1Cfg, "IsServer", fServer);
+ }
+ else if (ePortMode == PortMode_HostDevice)
+ {
+ InsertConfigString(pLunL0, "Driver", "Host Serial");
+ InsertConfigNode(pLunL0, "Config", &pLunL1);
+ InsertConfigString(pLunL1, "DevicePath", pszPath);
+ }
+ else if (ePortMode == PortMode_TCP)
+ {
+ InsertConfigString(pLunL0, "Driver", "Char");
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "TCP");
+ InsertConfigNode(pLunL1, "Config", &pLunL1Cfg);
+ InsertConfigString(pLunL1Cfg, "Location", pszPath);
+ InsertConfigInteger(pLunL1Cfg, "IsServer", fServer);
+ }
+ else if (ePortMode == PortMode_RawFile)
+ {
+ InsertConfigString(pLunL0, "Driver", "Char");
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "RawFile");
+ InsertConfigNode(pLunL1, "Config", &pLunL1Cfg);
+ InsertConfigString(pLunL1Cfg, "Location", pszPath);
+ }
+ }
+ catch (ConfigError &x)
+ {
+ /* InsertConfig threw something */
+ return x.m_vrc;
+ }
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Main/src-client/ConsoleImplTeleporter.cpp b/src/VBox/Main/src-client/ConsoleImplTeleporter.cpp
new file mode 100644
index 00000000..e8c6fc54
--- /dev/null
+++ b/src/VBox/Main/src-client/ConsoleImplTeleporter.cpp
@@ -0,0 +1,1483 @@
+/* $Id: ConsoleImplTeleporter.cpp $ */
+/** @file
+ * VBox Console COM Class implementation, The Teleporter Part.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE
+#include "LoggingNew.h"
+
+#include "ConsoleImpl.h"
+#include "ProgressImpl.h"
+#include "Global.h"
+#include "StringifyEnums.h"
+
+#include "AutoCaller.h"
+#include "HashedPw.h"
+
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/rand.h>
+#include <iprt/socket.h>
+#include <iprt/tcp.h>
+#include <iprt/timer.h>
+
+#include <VBox/vmm/vmapi.h>
+#include <VBox/vmm/ssm.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/err.h>
+#include <VBox/version.h>
+#include <VBox/com/string.h>
+#include "VBox/com/ErrorInfo.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Base class for the teleporter state.
+ *
+ * These classes are used as advanced structs, not as proper classes.
+ */
+class TeleporterState
+{
+public:
+ ComPtr<Console> mptrConsole;
+ PUVM mpUVM;
+ PCVMMR3VTABLE mpVMM;
+ ComObjPtr<Progress> mptrProgress;
+ Utf8Str mstrPassword;
+ bool const mfIsSource;
+
+ /** @name stream stuff
+ * @{ */
+ RTSOCKET mhSocket;
+ uint64_t moffStream;
+ uint32_t mcbReadBlock;
+ bool volatile mfStopReading;
+ bool volatile mfEndOfStream;
+ bool volatile mfIOError;
+ /** @} */
+
+ TeleporterState(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, Progress *pProgress, bool fIsSource)
+ : mptrConsole(pConsole)
+ , mpUVM(pUVM)
+ , mpVMM(pVMM)
+ , mptrProgress(pProgress)
+ , mfIsSource(fIsSource)
+ , mhSocket(NIL_RTSOCKET)
+ , moffStream(UINT64_MAX / 2)
+ , mcbReadBlock(0)
+ , mfStopReading(false)
+ , mfEndOfStream(false)
+ , mfIOError(false)
+ {
+ pVMM->pfnVMR3RetainUVM(mpUVM);
+ }
+
+ ~TeleporterState()
+ {
+ if (mpVMM)
+ mpVMM->pfnVMR3ReleaseUVM(mpUVM);
+ mpUVM = NULL;
+ }
+};
+
+
+/**
+ * Teleporter state used by the source side.
+ */
+class TeleporterStateSrc : public TeleporterState
+{
+public:
+ Utf8Str mstrHostname;
+ uint32_t muPort;
+ uint32_t mcMsMaxDowntime;
+ MachineState_T menmOldMachineState;
+ bool mfSuspendedByUs;
+ bool mfUnlockedMedia;
+
+ TeleporterStateSrc(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, Progress *pProgress, MachineState_T enmOldMachineState)
+ : TeleporterState(pConsole, pUVM, pVMM, pProgress, true /*fIsSource*/)
+ , muPort(UINT32_MAX)
+ , mcMsMaxDowntime(250)
+ , menmOldMachineState(enmOldMachineState)
+ , mfSuspendedByUs(false)
+ , mfUnlockedMedia(false)
+ {
+ }
+};
+
+
+/**
+ * Teleporter state used by the destination side.
+ */
+class TeleporterStateTrg : public TeleporterState
+{
+public:
+ IMachine *mpMachine;
+ IInternalMachineControl *mpControl;
+ PRTTCPSERVER mhServer;
+ PRTTIMERLR mphTimerLR;
+ bool mfLockedMedia;
+ int mRc;
+ Utf8Str mErrorText;
+
+ TeleporterStateTrg(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, Progress *pProgress,
+ IMachine *pMachine, IInternalMachineControl *pControl,
+ PRTTIMERLR phTimerLR, bool fStartPaused)
+ : TeleporterState(pConsole, pUVM, pVMM, pProgress, false /*fIsSource*/)
+ , mpMachine(pMachine)
+ , mpControl(pControl)
+ , mhServer(NULL)
+ , mphTimerLR(phTimerLR)
+ , mfLockedMedia(false)
+ , mRc(VINF_SUCCESS)
+ , mErrorText()
+ {
+ RT_NOREF(fStartPaused); /** @todo figure out why fStartPaused isn't used */
+ }
+};
+
+
+/**
+ * TCP stream header.
+ *
+ * This is an extra layer for fixing the problem with figuring out when the SSM
+ * stream ends.
+ */
+typedef struct TELEPORTERTCPHDR
+{
+ /** Magic value. */
+ uint32_t u32Magic;
+ /** The size of the data block following this header.
+ * 0 indicates the end of the stream, while UINT32_MAX indicates
+ * cancelation. */
+ uint32_t cb;
+} TELEPORTERTCPHDR;
+/** Magic value for TELEPORTERTCPHDR::u32Magic. (Egberto Gismonti Amin) */
+#define TELEPORTERTCPHDR_MAGIC UINT32_C(0x19471205)
+/** The max block size. */
+#define TELEPORTERTCPHDR_MAX_SIZE UINT32_C(0x00fffff8)
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const char g_szWelcome[] = "VirtualBox-Teleporter-1.0\n";
+
+
+/**
+ * Reads a string from the socket.
+ *
+ * @returns VBox status code.
+ *
+ * @param pState The teleporter state structure.
+ * @param pszBuf The output buffer.
+ * @param cchBuf The size of the output buffer.
+ *
+ */
+static int teleporterTcpReadLine(TeleporterState *pState, char *pszBuf, size_t cchBuf)
+{
+ char *pszStart = pszBuf;
+ RTSOCKET hSocket = pState->mhSocket;
+
+ AssertReturn(cchBuf > 1, VERR_INTERNAL_ERROR);
+ *pszBuf = '\0';
+
+ /* dead simple approach. */
+ for (;;)
+ {
+ char ch;
+ int vrc = RTTcpRead(hSocket, &ch, sizeof(ch), NULL);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Teleporter: RTTcpRead -> %Rrc while reading string ('%s')\n", vrc, pszStart));
+ return vrc;
+ }
+ if ( ch == '\n'
+ || ch == '\0')
+ return VINF_SUCCESS;
+ if (cchBuf <= 1)
+ {
+ LogRel(("Teleporter: String buffer overflow: '%s'\n", pszStart));
+ return VERR_BUFFER_OVERFLOW;
+ }
+ *pszBuf++ = ch;
+ *pszBuf = '\0';
+ cchBuf--;
+ }
+}
+
+
+/**
+ * Reads an ACK or NACK.
+ *
+ * @returns S_OK on ACK, E_FAIL+setError() on failure or NACK.
+ * @param pState The teleporter source state.
+ * @param pszWhich Which ACK is this this?
+ * @param pszNAckMsg Optional NACK message.
+ *
+ * @remarks the setError laziness forces this to be a Console member.
+ */
+HRESULT
+Console::i_teleporterSrcReadACK(TeleporterStateSrc *pState, const char *pszWhich, const char *pszNAckMsg /*= NULL*/)
+{
+ char szMsg[256];
+ int vrc = teleporterTcpReadLine(pState, szMsg, sizeof(szMsg));
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Failed reading ACK(%s): %Rrc"), pszWhich, vrc);
+
+ if (!strcmp(szMsg, "ACK"))
+ return S_OK;
+
+ if (!strncmp(szMsg, RT_STR_TUPLE("NACK=")))
+ {
+ char *pszMsgText = strchr(szMsg, ';');
+ if (pszMsgText)
+ *pszMsgText++ = '\0';
+
+ int32_t vrc2;
+ vrc = RTStrToInt32Full(&szMsg[sizeof("NACK=") - 1], 10, &vrc2);
+ if (vrc == VINF_SUCCESS)
+ {
+ /*
+ * Well formed NACK, transform it into an error.
+ */
+ if (pszNAckMsg)
+ {
+ LogRel(("Teleporter: %s: NACK=%Rrc (%d)\n", pszWhich, vrc2, vrc2));
+ return setError(E_FAIL, pszNAckMsg);
+ }
+
+ if (pszMsgText)
+ {
+ pszMsgText = RTStrStrip(pszMsgText);
+ for (size_t off = 0; pszMsgText[off]; off++)
+ if (pszMsgText[off] == '\r')
+ pszMsgText[off] = '\n';
+
+ LogRel(("Teleporter: %s: NACK=%Rrc (%d) - '%s'\n", pszWhich, vrc2, vrc2, pszMsgText));
+ if (strlen(pszMsgText) > 4)
+ return setError(E_FAIL, "%s", pszMsgText);
+ return setError(E_FAIL, "NACK(%s) - %Rrc (%d) '%s'", pszWhich, vrc2, vrc2, pszMsgText);
+ }
+
+ return setError(E_FAIL, "NACK(%s) - %Rrc (%d)", pszWhich, vrc2, vrc2);
+ }
+
+ if (pszMsgText)
+ pszMsgText[-1] = ';';
+ }
+ return setError(E_FAIL, tr("%s: Expected ACK or NACK, got '%s'"), pszWhich, szMsg);
+}
+
+
+/**
+ * Submitts a command to the destination and waits for the ACK.
+ *
+ * @returns S_OK on ACKed command, E_FAIL+setError() on failure.
+ *
+ * @param pState The teleporter source state.
+ * @param pszCommand The command.
+ * @param fWaitForAck Whether to wait for the ACK.
+ *
+ * @remarks the setError laziness forces this to be a Console member.
+ */
+HRESULT Console::i_teleporterSrcSubmitCommand(TeleporterStateSrc *pState, const char *pszCommand, bool fWaitForAck /*= true*/)
+{
+ int vrc = RTTcpSgWriteL(pState->mhSocket, 2, pszCommand, strlen(pszCommand), "\n", sizeof("\n") - 1);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Failed writing command '%s': %Rrc"), pszCommand, vrc);
+ if (!fWaitForAck)
+ return S_OK;
+ return i_teleporterSrcReadACK(pState, pszCommand);
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnWrite
+ */
+static DECLCALLBACK(int) teleporterTcpOpWrite(void *pvUser, uint64_t offStream, const void *pvBuf, size_t cbToWrite)
+{
+ RT_NOREF(offStream);
+ TeleporterState *pState = (TeleporterState *)pvUser;
+
+ AssertReturn(cbToWrite > 0, VINF_SUCCESS);
+ AssertReturn(cbToWrite < UINT32_MAX, VERR_OUT_OF_RANGE);
+ AssertReturn(pState->mfIsSource, VERR_INVALID_HANDLE);
+
+ for (;;)
+ {
+ TELEPORTERTCPHDR Hdr;
+ Hdr.u32Magic = TELEPORTERTCPHDR_MAGIC;
+ Hdr.cb = RT_MIN((uint32_t)cbToWrite, TELEPORTERTCPHDR_MAX_SIZE);
+ int vrc = RTTcpSgWriteL(pState->mhSocket, 2, &Hdr, sizeof(Hdr), pvBuf, (size_t)Hdr.cb);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Teleporter/TCP: Write error: %Rrc (cb=%#x)\n", vrc, Hdr.cb));
+ return vrc;
+ }
+ pState->moffStream += Hdr.cb;
+ if (Hdr.cb == cbToWrite)
+ return VINF_SUCCESS;
+
+ /* advance */
+ cbToWrite -= Hdr.cb;
+ pvBuf = (uint8_t const *)pvBuf + Hdr.cb;
+ }
+}
+
+
+/**
+ * Selects and poll for close condition.
+ *
+ * We can use a relatively high poll timeout here since it's only used to get
+ * us out of error paths. In the normal cause of events, we'll get a
+ * end-of-stream header.
+ *
+ * @returns VBox status code.
+ *
+ * @param pState The teleporter state data.
+ */
+static int teleporterTcpReadSelect(TeleporterState *pState)
+{
+ int vrc;
+ do
+ {
+ vrc = RTTcpSelectOne(pState->mhSocket, 1000);
+ if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
+ {
+ pState->mfIOError = true;
+ LogRel(("Teleporter/TCP: Header select error: %Rrc\n", vrc));
+ break;
+ }
+ if (pState->mfStopReading)
+ {
+ vrc = VERR_EOF;
+ break;
+ }
+ } while (vrc == VERR_TIMEOUT);
+ return vrc;
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnRead
+ */
+static DECLCALLBACK(int) teleporterTcpOpRead(void *pvUser, uint64_t offStream, void *pvBuf, size_t cbToRead, size_t *pcbRead)
+{
+ RT_NOREF(offStream);
+ TeleporterState *pState = (TeleporterState *)pvUser;
+ AssertReturn(!pState->mfIsSource, VERR_INVALID_HANDLE);
+
+ for (;;)
+ {
+ int vrc;
+
+ /*
+ * Check for various conditions and may have been signalled.
+ */
+ if (pState->mfEndOfStream)
+ return VERR_EOF;
+ if (pState->mfStopReading)
+ return VERR_EOF;
+ if (pState->mfIOError)
+ return VERR_IO_GEN_FAILURE;
+
+ /*
+ * If there is no more data in the current block, read the next
+ * block header.
+ */
+ if (!pState->mcbReadBlock)
+ {
+ vrc = teleporterTcpReadSelect(pState);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ TELEPORTERTCPHDR Hdr;
+ vrc = RTTcpRead(pState->mhSocket, &Hdr, sizeof(Hdr), NULL);
+ if (RT_FAILURE(vrc))
+ {
+ pState->mfIOError = true;
+ LogRel(("Teleporter/TCP: Header read error: %Rrc\n", vrc));
+ return vrc;
+ }
+
+ if (RT_UNLIKELY( Hdr.u32Magic != TELEPORTERTCPHDR_MAGIC
+ || Hdr.cb > TELEPORTERTCPHDR_MAX_SIZE
+ || Hdr.cb == 0))
+ {
+ if ( Hdr.u32Magic == TELEPORTERTCPHDR_MAGIC
+ && ( Hdr.cb == 0
+ || Hdr.cb == UINT32_MAX)
+ )
+ {
+ pState->mfEndOfStream = true;
+ pState->mcbReadBlock = 0;
+ return Hdr.cb ? VERR_SSM_CANCELLED : VERR_EOF;
+ }
+ pState->mfIOError = true;
+ LogRel(("Teleporter/TCP: Invalid block: u32Magic=%#x cb=%#x\n", Hdr.u32Magic, Hdr.cb));
+ return VERR_IO_GEN_FAILURE;
+ }
+
+ pState->mcbReadBlock = Hdr.cb;
+ if (pState->mfStopReading)
+ return VERR_EOF;
+ }
+
+ /*
+ * Read more data.
+ */
+ vrc = teleporterTcpReadSelect(pState);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ uint32_t cb = (uint32_t)RT_MIN(pState->mcbReadBlock, cbToRead);
+ vrc = RTTcpRead(pState->mhSocket, pvBuf, cb, pcbRead);
+ if (RT_FAILURE(vrc))
+ {
+ pState->mfIOError = true;
+ LogRel(("Teleporter/TCP: Data read error: %Rrc (cb=%#x)\n", vrc, cb));
+ return vrc;
+ }
+ if (pcbRead)
+ {
+ cb = (uint32_t)*pcbRead;
+ pState->moffStream += cb;
+ pState->mcbReadBlock -= cb;
+ return VINF_SUCCESS;
+ }
+ pState->moffStream += cb;
+ pState->mcbReadBlock -= cb;
+ if (cbToRead == cb)
+ return VINF_SUCCESS;
+
+ /* Advance to the next block. */
+ cbToRead -= cb;
+ pvBuf = (uint8_t *)pvBuf + cb;
+ }
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnSeek
+ */
+static DECLCALLBACK(int) teleporterTcpOpSeek(void *pvUser, int64_t offSeek, unsigned uMethod, uint64_t *poffActual)
+{
+ RT_NOREF(pvUser, offSeek, uMethod, poffActual);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnTell
+ */
+static DECLCALLBACK(uint64_t) teleporterTcpOpTell(void *pvUser)
+{
+ TeleporterState *pState = (TeleporterState *)pvUser;
+ return pState->moffStream;
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnSize
+ */
+static DECLCALLBACK(int) teleporterTcpOpSize(void *pvUser, uint64_t *pcb)
+{
+ RT_NOREF(pvUser, pcb);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnIsOk
+ */
+static DECLCALLBACK(int) teleporterTcpOpIsOk(void *pvUser)
+{
+ TeleporterState *pState = (TeleporterState *)pvUser;
+
+ if (pState->mfIsSource)
+ {
+ /* Poll for incoming NACKs and errors from the other side */
+ int vrc = RTTcpSelectOne(pState->mhSocket, 0);
+ if (vrc != VERR_TIMEOUT)
+ {
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Teleporter/TCP: Incoming data detect by IsOk, assuming it is a cancellation NACK.\n"));
+ vrc = VERR_SSM_CANCELLED;
+ }
+ else
+ LogRel(("Teleporter/TCP: RTTcpSelectOne -> %Rrc (IsOk).\n", vrc));
+ return vrc;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnClose
+ */
+static DECLCALLBACK(int) teleporterTcpOpClose(void *pvUser, bool fCancelled)
+{
+ TeleporterState *pState = (TeleporterState *)pvUser;
+
+ if (pState->mfIsSource)
+ {
+ TELEPORTERTCPHDR EofHdr;
+ EofHdr.u32Magic = TELEPORTERTCPHDR_MAGIC;
+ EofHdr.cb = fCancelled ? UINT32_MAX : 0;
+ int vrc = RTTcpWrite(pState->mhSocket, &EofHdr, sizeof(EofHdr));
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Teleporter/TCP: EOF Header write error: %Rrc\n", vrc));
+ return vrc;
+ }
+ }
+ else
+ {
+ ASMAtomicWriteBool(&pState->mfStopReading, true);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Method table for a TCP based stream.
+ */
+static SSMSTRMOPS const g_teleporterTcpOps =
+{
+ SSMSTRMOPS_VERSION,
+ teleporterTcpOpWrite,
+ teleporterTcpOpRead,
+ teleporterTcpOpSeek,
+ teleporterTcpOpTell,
+ teleporterTcpOpSize,
+ teleporterTcpOpIsOk,
+ teleporterTcpOpClose,
+ SSMSTRMOPS_VERSION
+};
+
+
+/**
+ * Progress cancelation callback.
+ */
+static void teleporterProgressCancelCallback(void *pvUser)
+{
+ TeleporterState *pState = (TeleporterState *)pvUser;
+ pState->mpVMM->pfnSSMR3Cancel(pState->mpUVM);
+ if (!pState->mfIsSource)
+ {
+ TeleporterStateTrg *pStateTrg = (TeleporterStateTrg *)pState;
+ RTTcpServerShutdown(pStateTrg->mhServer);
+ }
+}
+
+/**
+ * @copydoc PFNVMPROGRESS
+ */
+static DECLCALLBACK(int) teleporterProgressCallback(PUVM pUVM, unsigned uPercent, void *pvUser)
+{
+ TeleporterState *pState = (TeleporterState *)pvUser;
+ if (pState->mptrProgress)
+ {
+ HRESULT hrc = pState->mptrProgress->SetCurrentOperationProgress(uPercent);
+ if (FAILED(hrc))
+ {
+ /* check if the failure was caused by cancellation. */
+ BOOL fCanceled;
+ hrc = pState->mptrProgress->COMGETTER(Canceled)(&fCanceled);
+ if (SUCCEEDED(hrc) && fCanceled)
+ {
+ pState->mpVMM->pfnSSMR3Cancel(pState->mpUVM);
+ return VERR_SSM_CANCELLED;
+ }
+ }
+ }
+
+ NOREF(pUVM);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc FNRTTIMERLR
+ */
+static DECLCALLBACK(void) teleporterDstTimeout(RTTIMERLR hTimerLR, void *pvUser, uint64_t iTick)
+{
+ RT_NOREF(hTimerLR, iTick);
+ /* This is harmless for any open connections. */
+ RTTcpServerShutdown((PRTTCPSERVER)pvUser);
+}
+
+
+/**
+ * Do the teleporter.
+ *
+ * @returns VBox status code.
+ * @param pState The teleporter state.
+ */
+HRESULT Console::i_teleporterSrc(TeleporterStateSrc *pState)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ /*
+ * Wait for Console::Teleport to change the state.
+ */
+ { AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS); }
+
+ BOOL fCanceled = TRUE;
+ HRESULT hrc = pState->mptrProgress->COMGETTER(Canceled)(&fCanceled);
+ if (FAILED(hrc))
+ return hrc;
+ if (fCanceled)
+ return setError(E_FAIL, tr("canceled"));
+
+ /*
+ * Try connect to the destination machine, disable Nagle.
+ * (Note. The caller cleans up mhSocket, so we can return without worries.)
+ */
+ int vrc = RTTcpClientConnect(pState->mstrHostname.c_str(), pState->muPort, &pState->mhSocket);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to connect to port %u on '%s': %Rrc"),
+ pState->muPort, pState->mstrHostname.c_str(), vrc);
+ vrc = RTTcpSetSendCoalescing(pState->mhSocket, false /*fEnable*/);
+ AssertRC(vrc);
+
+ /* Read and check the welcome message. */
+ char szLine[RT_MAX(128, sizeof(g_szWelcome))];
+ RT_ZERO(szLine);
+ vrc = RTTcpRead(pState->mhSocket, szLine, sizeof(g_szWelcome) - 1, NULL);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to read welcome message: %Rrc"), vrc);
+ if (strcmp(szLine, g_szWelcome))
+ return setError(E_FAIL, tr("Unexpected welcome %.*Rhxs"), sizeof(g_szWelcome) - 1, szLine);
+
+ /* password */
+ pState->mstrPassword.append('\n');
+ vrc = RTTcpWrite(pState->mhSocket, pState->mstrPassword.c_str(), pState->mstrPassword.length());
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to send password: %Rrc"), vrc);
+
+ /* ACK */
+ hrc = i_teleporterSrcReadACK(pState, "password", tr("Invalid password"));
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Start loading the state.
+ *
+ * Note! The saved state includes vital configuration data which will be
+ * verified against the VM config on the other end. This is all done
+ * in the first pass, so we should fail pretty promptly on misconfig.
+ */
+ hrc = i_teleporterSrcSubmitCommand(pState, "load");
+ if (FAILED(hrc))
+ return hrc;
+
+ RTSocketRetain(pState->mhSocket);
+ void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(pState));
+ vrc = pState->mpVMM->pfnVMR3Teleport(pState->mpUVM,
+ pState->mcMsMaxDowntime,
+ &g_teleporterTcpOps, pvUser,
+ teleporterProgressCallback, pvUser,
+ &pState->mfSuspendedByUs);
+ RTSocketRelease(pState->mhSocket);
+ if (RT_FAILURE(vrc))
+ {
+ if ( vrc == VERR_SSM_CANCELLED
+ && RT_SUCCESS(RTTcpSelectOne(pState->mhSocket, 1)))
+ {
+ hrc = i_teleporterSrcReadACK(pState, "load-complete");
+ if (FAILED(hrc))
+ return hrc;
+ }
+ return setErrorBoth(E_FAIL, vrc, "VMR3Teleport -> %Rrc", vrc);
+ }
+
+ hrc = i_teleporterSrcReadACK(pState, "load-complete");
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * We're at the point of no return.
+ */
+ if (FAILED(pState->mptrProgress->NotifyPointOfNoReturn()))
+ {
+ i_teleporterSrcSubmitCommand(pState, "cancel", false /*fWaitForAck*/);
+ return E_FAIL;
+ }
+
+ /*
+ * Hand over any media which we might be sharing.
+ *
+ * Note! This is only important on localhost teleportations.
+ */
+ /** @todo Maybe we should only do this if it's a local teleportation... */
+ hrc = mControl->UnlockMedia();
+ if (FAILED(hrc))
+ return hrc;
+ pState->mfUnlockedMedia = true;
+
+ hrc = i_teleporterSrcSubmitCommand(pState, "lock-media");
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * The FINAL step is giving the target instructions how to proceed with the VM.
+ */
+ if ( vrc == VINF_SSM_LIVE_SUSPENDED
+ || pState->menmOldMachineState == MachineState_Paused)
+ hrc = i_teleporterSrcSubmitCommand(pState, "hand-over-paused");
+ else
+ hrc = i_teleporterSrcSubmitCommand(pState, "hand-over-resume");
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * teleporterSrcThreadWrapper will do the automatic power off because it
+ * has to release the AutoVMCaller.
+ */
+ return S_OK;
+}
+
+
+/**
+ * Static thread method wrapper.
+ *
+ * @returns VINF_SUCCESS (ignored).
+ * @param hThreadSelf The thread.
+ * @param pvUser Pointer to a TeleporterStateSrc instance.
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_teleporterSrcThreadWrapper(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+ TeleporterStateSrc *pState = (TeleporterStateSrc *)pvUser;
+
+ /*
+ * Console::teleporterSrc does the work, we just grab onto the VM handle
+ * and do the cleanups afterwards.
+ */
+ SafeVMPtr ptrVM(pState->mptrConsole);
+ HRESULT hrc = ptrVM.rc();
+
+ if (SUCCEEDED(hrc))
+ hrc = pState->mptrConsole->i_teleporterSrc(pState);
+
+ /* Close the connection ASAP on so that the other side can complete. */
+ if (pState->mhSocket != NIL_RTSOCKET)
+ {
+ RTTcpClientClose(pState->mhSocket);
+ pState->mhSocket = NIL_RTSOCKET;
+ }
+
+ /* Aaarg! setMachineState trashes error info on Windows, so we have to
+ complete things here on failure instead of right before cleanup. */
+ if (FAILED(hrc))
+ pState->mptrProgress->i_notifyComplete(hrc);
+
+ /* We can no longer be canceled (success), or it doesn't matter any longer (failure). */
+ pState->mptrProgress->i_setCancelCallback(NULL, NULL);
+
+ /*
+ * Write lock the console before resetting mptrCancelableProgress and
+ * fixing the state.
+ */
+ AutoWriteLock autoLock(pState->mptrConsole COMMA_LOCKVAL_SRC_POS);
+ pState->mptrConsole->mptrCancelableProgress.setNull();
+
+ VMSTATE const enmVMState = pState->mpVMM->pfnVMR3GetStateU(pState->mpUVM);
+ MachineState_T const enmMachineState = pState->mptrConsole->mMachineState;
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Automatically shut down the VM on success.
+ *
+ * Note! We have to release the VM caller object or we'll deadlock in
+ * powerDown.
+ */
+ AssertLogRelMsg(enmVMState == VMSTATE_SUSPENDED, ("%s\n", pState->mpVMM->pfnVMR3GetStateName(enmVMState)));
+ AssertLogRelMsg(enmMachineState == MachineState_TeleportingPausedVM, ("%s\n", ::stringifyMachineState(enmMachineState)));
+
+ ptrVM.release();
+
+ pState->mptrConsole->mVMIsAlreadyPoweringOff = true; /* (Make sure we stick in the TeleportingPausedVM state.) */
+ autoLock.release();
+
+ hrc = pState->mptrConsole->i_powerDown();
+
+ autoLock.acquire();
+ pState->mptrConsole->mVMIsAlreadyPoweringOff = false;
+
+ pState->mptrProgress->i_notifyComplete(hrc);
+ }
+ else
+ {
+ /*
+ * Work the state machinery on failure.
+ *
+ * If the state is no longer 'Teleporting*', some other operation has
+ * canceled us and there is nothing we need to do here. In all other
+ * cases, we've failed one way or another.
+ */
+ if ( enmMachineState == MachineState_Teleporting
+ || enmMachineState == MachineState_TeleportingPausedVM
+ )
+ {
+ if (pState->mfUnlockedMedia)
+ {
+ ErrorInfoKeeper Oak;
+ HRESULT hrc2 = pState->mptrConsole->mControl->LockMedia();
+ if (FAILED(hrc2))
+ {
+ uint64_t StartMS = RTTimeMilliTS();
+ do
+ {
+ RTThreadSleep(2);
+ hrc2 = pState->mptrConsole->mControl->LockMedia();
+ } while ( FAILED(hrc2)
+ && RTTimeMilliTS() - StartMS < 2000);
+ }
+ if (SUCCEEDED(hrc2))
+ pState->mfUnlockedMedia = true;
+ else
+ LogRel(("FATAL ERROR: Failed to re-take the media locks. hrc2=%Rhrc\n", hrc2));
+ }
+
+ switch (enmVMState)
+ {
+ case VMSTATE_RUNNING:
+ case VMSTATE_RUNNING_LS:
+ case VMSTATE_DEBUGGING:
+ case VMSTATE_DEBUGGING_LS:
+ case VMSTATE_POWERING_OFF:
+ case VMSTATE_POWERING_OFF_LS:
+ case VMSTATE_RESETTING:
+ case VMSTATE_RESETTING_LS:
+ case VMSTATE_SOFT_RESETTING:
+ case VMSTATE_SOFT_RESETTING_LS:
+ Assert(!pState->mfSuspendedByUs);
+ Assert(!pState->mfUnlockedMedia);
+ pState->mptrConsole->i_setMachineState(MachineState_Running);
+ break;
+
+ case VMSTATE_GURU_MEDITATION:
+ case VMSTATE_GURU_MEDITATION_LS:
+ pState->mptrConsole->i_setMachineState(MachineState_Stuck);
+ break;
+
+ case VMSTATE_FATAL_ERROR:
+ case VMSTATE_FATAL_ERROR_LS:
+ pState->mptrConsole->i_setMachineState(MachineState_Paused);
+ break;
+
+ default:
+ AssertMsgFailed(("%s\n", pState->mpVMM->pfnVMR3GetStateName(enmVMState)));
+ RT_FALL_THRU();
+ case VMSTATE_SUSPENDED:
+ case VMSTATE_SUSPENDED_LS:
+ case VMSTATE_SUSPENDING:
+ case VMSTATE_SUSPENDING_LS:
+ case VMSTATE_SUSPENDING_EXT_LS:
+ if (!pState->mfUnlockedMedia)
+ {
+ pState->mptrConsole->i_setMachineState(MachineState_Paused);
+ if (pState->mfSuspendedByUs)
+ {
+ autoLock.release();
+ int vrc = pState->mpVMM->pfnVMR3Resume(pState->mpUVM, VMRESUMEREASON_TELEPORT_FAILED);
+ AssertLogRelMsgRC(vrc, ("VMR3Resume -> %Rrc\n", vrc));
+ autoLock.acquire();
+ }
+ }
+ else
+ {
+ /* Faking a guru meditation is the best I can think of doing here... */
+ pState->mptrConsole->i_setMachineState(MachineState_Stuck);
+ }
+ break;
+ }
+ }
+ }
+ autoLock.release();
+
+ /*
+ * Cleanup.
+ */
+ Assert(pState->mhSocket == NIL_RTSOCKET);
+ delete pState;
+
+ return VINF_SUCCESS; /* ignored */
+}
+
+
+/**
+ * Start teleporter to the specified target.
+ *
+ * @returns COM status code.
+ *
+ * @param aHostname The name of the target host.
+ * @param aTcpport The TCP port number.
+ * @param aPassword The password.
+ * @param aMaxDowntime Max allowed "downtime" in milliseconds.
+ * @param aProgress Where to return the progress object.
+ */
+HRESULT Console::teleport(const com::Utf8Str &aHostname, ULONG aTcpport, const com::Utf8Str &aPassword,
+ ULONG aMaxDowntime, ComPtr<IProgress> &aProgress)
+{
+ /*
+ * Validate parameters, check+hold object status, write lock the object
+ * and validate the state.
+ */
+ Utf8Str strPassword(aPassword);
+ if (!strPassword.isEmpty())
+ {
+ if (VBoxIsPasswordHashed(&strPassword))
+ return setError(E_INVALIDARG, tr("The specified password resembles a hashed password, expected plain text"));
+ VBoxHashPassword(&strPassword);
+ }
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
+
+ switch (mMachineState)
+ {
+ case MachineState_Running:
+ case MachineState_Paused:
+ break;
+
+ default:
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Invalid machine state: %s (must be Running or Paused)"),
+ Global::stringifyMachineState(mMachineState));
+ }
+
+
+ /*
+ * Create a progress object, spawn a worker thread and change the state.
+ * Note! The thread won't start working until we release the lock.
+ */
+ LogFlowThisFunc(("Initiating TELEPORT request...\n"));
+
+ ComObjPtr<Progress> ptrProgress;
+ HRESULT hrc = ptrProgress.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = ptrProgress->init(static_cast<IConsole *>(this),
+ Bstr(tr("Teleporter")).raw(),
+ TRUE /*aCancelable*/);
+ if (FAILED(hrc))
+ return hrc;
+
+ TeleporterStateSrc *pState = new TeleporterStateSrc(this, mpUVM, mpVMM, ptrProgress, mMachineState);
+ pState->mstrPassword = strPassword;
+ pState->mstrHostname = aHostname;
+ pState->muPort = aTcpport;
+ pState->mcMsMaxDowntime = aMaxDowntime;
+
+ void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(pState));
+ ptrProgress->i_setCancelCallback(teleporterProgressCancelCallback, pvUser);
+
+ int vrc = RTThreadCreate(NULL, Console::i_teleporterSrcThreadWrapper, (void *)pState, 0 /*cbStack*/,
+ RTTHREADTYPE_EMULATION, 0 /*fFlags*/, "Teleport");
+ if (RT_SUCCESS(vrc))
+ {
+ if (mMachineState == MachineState_Running)
+ hrc = i_setMachineState(MachineState_Teleporting);
+ else
+ hrc = i_setMachineState(MachineState_TeleportingPausedVM);
+ if (SUCCEEDED(hrc))
+ {
+ ptrProgress.queryInterfaceTo(aProgress.asOutParam());
+ mptrCancelableProgress = aProgress;
+ }
+ else
+ ptrProgress->Cancel();
+ }
+ else
+ {
+ ptrProgress->i_setCancelCallback(NULL, NULL);
+ delete pState;
+ hrc = setErrorBoth(E_FAIL, vrc, "RTThreadCreate -> %Rrc", vrc);
+ }
+
+ return hrc;
+}
+
+
+/**
+ * Creates a TCP server that listens for the source machine and passes control
+ * over to Console::teleporterTrgServeConnection().
+ *
+ * @returns VBox status code.
+ * @param pUVM The user-mode VM handle
+ * @param pVMM The VMM vtable.
+ * @param pMachine The IMachine for the virtual machine.
+ * @param pErrorMsg Pointer to the error string for VMSetError.
+ * @param fStartPaused Whether to start it in the Paused (true) or
+ * Running (false) state,
+ * @param pProgress Pointer to the progress object.
+ * @param pfPowerOffOnFailure Whether the caller should power off
+ * the VM on failure.
+ *
+ * @remarks The caller expects error information to be set on failure.
+ * @todo Check that all the possible failure paths sets error info...
+ */
+HRESULT Console::i_teleporterTrg(PUVM pUVM, PCVMMR3VTABLE pVMM, IMachine *pMachine, Utf8Str *pErrorMsg, bool fStartPaused,
+ Progress *pProgress, bool *pfPowerOffOnFailure)
+{
+ LogThisFunc(("pUVM=%p pVMM=%p pMachine=%p fStartPaused=%RTbool pProgress=%p\n", pUVM, pVMM, pMachine, fStartPaused, pProgress));
+
+ *pfPowerOffOnFailure = true;
+
+ /*
+ * Get the config.
+ */
+ ULONG uPort;
+ HRESULT hrc = pMachine->COMGETTER(TeleporterPort)(&uPort);
+ if (FAILED(hrc))
+ return hrc;
+ ULONG const uPortOrg = uPort;
+
+ Bstr bstrAddress;
+ hrc = pMachine->COMGETTER(TeleporterAddress)(bstrAddress.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ Utf8Str strAddress(bstrAddress);
+ const char *pszAddress = strAddress.isEmpty() ? NULL : strAddress.c_str();
+
+ Bstr bstrPassword;
+ hrc = pMachine->COMGETTER(TeleporterPassword)(bstrPassword.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ Utf8Str strPassword(bstrPassword);
+ strPassword.append('\n'); /* To simplify password checking. */
+
+ /*
+ * Create the TCP server.
+ */
+ int vrc = VINF_SUCCESS; /* Shut up MSC */
+ PRTTCPSERVER hServer = NULL; /* ditto */
+ if (uPort)
+ vrc = RTTcpServerCreateEx(pszAddress, uPort, &hServer);
+ else
+ {
+ for (int cTries = 10240; cTries > 0; cTries--)
+ {
+ uPort = RTRandU32Ex(cTries >= 8192 ? 49152 : 1024, 65534);
+ vrc = RTTcpServerCreateEx(pszAddress, uPort, &hServer);
+ if (vrc != VERR_NET_ADDRESS_IN_USE)
+ break;
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = pMachine->COMSETTER(TeleporterPort)(uPort);
+ if (FAILED(hrc))
+ {
+ RTTcpServerDestroy(hServer);
+ return hrc;
+ }
+ }
+ }
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("RTTcpServerCreateEx failed with status %Rrc"), vrc);
+
+ /*
+ * Create a one-shot timer for timing out after 5 mins.
+ */
+ RTTIMERLR hTimerLR;
+ vrc = RTTimerLRCreateEx(&hTimerLR, 0 /*ns*/, RTTIMER_FLAGS_CPU_ANY, teleporterDstTimeout, hServer);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTTimerLRStart(hTimerLR, 5*60*UINT64_C(1000000000) /*ns*/);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Do the job, when it returns we're done.
+ */
+ TeleporterStateTrg theState(this, pUVM, pVMM, pProgress, pMachine, mControl, &hTimerLR, fStartPaused);
+ theState.mstrPassword = strPassword;
+ theState.mhServer = hServer;
+
+ void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(&theState));
+ if (pProgress->i_setCancelCallback(teleporterProgressCancelCallback, pvUser))
+ {
+ LogRel(("Teleporter: Waiting for incoming VM...\n"));
+ hrc = pProgress->SetNextOperation(Bstr(tr("Waiting for incoming VM")).raw(), 1);
+ if (SUCCEEDED(hrc))
+ {
+ vrc = RTTcpServerListen(hServer, Console::i_teleporterTrgServeConnection, &theState);
+ pProgress->i_setCancelCallback(NULL, NULL);
+
+ if (vrc == VERR_TCP_SERVER_STOP)
+ {
+ vrc = theState.mRc;
+ /* Power off the VM on failure unless the state callback
+ already did that. */
+ *pfPowerOffOnFailure = false;
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ {
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ if ( enmVMState != VMSTATE_OFF
+ && enmVMState != VMSTATE_POWERING_OFF)
+ *pfPowerOffOnFailure = true;
+
+ /* Set error. */
+ if (pErrorMsg->length())
+ hrc = setError(E_FAIL, "%s", pErrorMsg->c_str());
+ else
+ hrc = setError(E_FAIL, tr("Teleporation failed (%Rrc)"), vrc);
+ }
+ }
+ else if (vrc == VERR_TCP_SERVER_SHUTDOWN)
+ {
+ BOOL fCanceled = TRUE;
+ hrc = pProgress->COMGETTER(Canceled)(&fCanceled);
+ if (FAILED(hrc) || fCanceled)
+ hrc = setError(E_FAIL, tr("Teleporting canceled"));
+ else
+ hrc = setError(E_FAIL, tr("Teleporter timed out waiting for incoming connection"));
+ LogRel(("Teleporter: RTTcpServerListen aborted - %Rrc\n", vrc));
+ }
+ else
+ {
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Unexpected RTTcpServerListen status code %Rrc"), vrc);
+ LogRel(("Teleporter: Unexpected RTTcpServerListen rc: %Rrc\n", vrc));
+ }
+ }
+ else
+ LogThisFunc(("SetNextOperation failed, %Rhrc\n", hrc));
+ }
+ else
+ {
+ LogThisFunc(("Canceled - check point #1\n"));
+ hrc = setError(E_FAIL, tr("Teleporting canceled"));
+ }
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, "RTTimerLRStart -> %Rrc", vrc);
+
+ RTTimerLRDestroy(hTimerLR);
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, "RTTimerLRCreate -> %Rrc", vrc);
+ RTTcpServerDestroy(hServer);
+
+ /*
+ * If we change TeleporterPort above, set it back to it's original
+ * value before returning.
+ */
+ if (uPortOrg != uPort)
+ {
+ ErrorInfoKeeper Eik;
+ pMachine->COMSETTER(TeleporterPort)(uPortOrg);
+ }
+
+ return hrc;
+}
+
+
+/**
+ * Unlock the media.
+ *
+ * This is used in error paths.
+ *
+ * @param pState The teleporter state.
+ */
+static void teleporterTrgUnlockMedia(TeleporterStateTrg *pState)
+{
+ if (pState->mfLockedMedia)
+ {
+ pState->mpControl->UnlockMedia();
+ pState->mfLockedMedia = false;
+ }
+}
+
+
+static int teleporterTcpWriteACK(TeleporterStateTrg *pState, bool fAutomaticUnlock = true)
+{
+ int vrc = RTTcpWrite(pState->mhSocket, "ACK\n", sizeof("ACK\n") - 1);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Teleporter: RTTcpWrite(,ACK,) -> %Rrc\n", vrc));
+ if (fAutomaticUnlock)
+ teleporterTrgUnlockMedia(pState);
+ }
+ return vrc;
+}
+
+
+static int teleporterTcpWriteNACK(TeleporterStateTrg *pState, int32_t rc2, const char *pszMsgText = NULL)
+{
+ /*
+ * Unlock media sending the NACK. That way the other doesn't have to spin
+ * waiting to regain the locks.
+ */
+ teleporterTrgUnlockMedia(pState);
+
+ char szMsg[256];
+ size_t cch;
+ if (pszMsgText && *pszMsgText)
+ {
+ cch = RTStrPrintf(szMsg, sizeof(szMsg), "NACK=%d;%s\n", rc2, pszMsgText);
+ for (size_t off = 6; off + 1 < cch; off++)
+ if (szMsg[off] == '\n')
+ szMsg[off] = '\r';
+ }
+ else
+ cch = RTStrPrintf(szMsg, sizeof(szMsg), "NACK=%d\n", rc2);
+ int vrc = RTTcpWrite(pState->mhSocket, szMsg, cch);
+ if (RT_FAILURE(vrc))
+ LogRel(("Teleporter: RTTcpWrite(,%s,%zu) -> %Rrc\n", szMsg, cch, vrc));
+ return vrc;
+}
+
+
+/**
+ * @copydoc FNRTTCPSERVE
+ *
+ * @returns VINF_SUCCESS or VERR_TCP_SERVER_STOP.
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_teleporterTrgServeConnection(RTSOCKET hSocket, void *pvUser)
+{
+ TeleporterStateTrg *pState = (TeleporterStateTrg *)pvUser;
+ pState->mhSocket = hSocket;
+
+ /*
+ * Disable Nagle and say hello.
+ */
+ int vrc = RTTcpSetSendCoalescing(pState->mhSocket, false /*fEnable*/);
+ AssertRC(vrc);
+ vrc = RTTcpWrite(hSocket, g_szWelcome, sizeof(g_szWelcome) - 1);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Teleporter: Failed to write welcome message: %Rrc\n", vrc));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Password (includes '\n', see i_teleporterTrg).
+ */
+ const char *pszPassword = pState->mstrPassword.c_str();
+ unsigned off = 0;
+ while (pszPassword[off])
+ {
+ char ch;
+ vrc = RTTcpRead(hSocket, &ch, sizeof(ch), NULL);
+ if ( RT_FAILURE(vrc)
+ || pszPassword[off] != ch)
+ {
+ if (RT_FAILURE(vrc))
+ LogRel(("Teleporter: Password read failure (off=%u): %Rrc\n", off, vrc));
+ else
+ {
+ /* Must read the whole password before NACK'ing it. */
+ size_t const cchMaxRead = RT_ALIGN_Z(pState->mstrPassword.length() * 3, _1K);
+ while (off < cchMaxRead && RT_SUCCESS(vrc) && ch != '\n')
+ {
+ vrc = RTTcpRead(hSocket, &ch, sizeof(ch), NULL);
+ off++;
+ }
+ LogRel(("Teleporter: Invalid password\n"));
+ }
+ RTThreadSleep(RTRandU32Ex(64, 1024)); /* stagger retries */
+ teleporterTcpWriteNACK(pState, VERR_AUTHENTICATION_FAILURE);
+ return VINF_SUCCESS;
+ }
+ off++;
+ }
+ vrc = teleporterTcpWriteACK(pState);
+ if (RT_FAILURE(vrc))
+ return VINF_SUCCESS;
+
+ /*
+ * Update the progress bar, with peer name if available.
+ */
+ HRESULT hrc;
+ RTNETADDR Addr;
+ vrc = RTTcpGetPeerAddress(hSocket, &Addr);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Teleporter: Incoming VM from %RTnaddr!\n", &Addr));
+ hrc = pState->mptrProgress->SetNextOperation(BstrFmt(tr("Teleporting VM from %RTnaddr"), &Addr).raw(), 8);
+ }
+ else
+ {
+ LogRel(("Teleporter: Incoming VM!\n"));
+ hrc = pState->mptrProgress->SetNextOperation(Bstr(tr("Teleporting VM")).raw(), 8);
+ }
+ AssertMsg(SUCCEEDED(hrc) || hrc == E_FAIL, ("%Rhrc\n", hrc));
+
+ /*
+ * Stop the server and cancel the timeout timer.
+ *
+ * Note! After this point we must return VERR_TCP_SERVER_STOP, while prior
+ * to it we must not return that value!
+ */
+ RTTcpServerShutdown(pState->mhServer);
+ RTTimerLRDestroy(*pState->mphTimerLR);
+ *pState->mphTimerLR = NIL_RTTIMERLR;
+
+ /*
+ * Command processing loop.
+ */
+ bool fDone = false;
+ for (;;)
+ {
+ char szCmd[128];
+ vrc = teleporterTcpReadLine(pState, szCmd, sizeof(szCmd));
+ if (RT_FAILURE(vrc))
+ break;
+
+ if (!strcmp(szCmd, "load"))
+ {
+ vrc = teleporterTcpWriteACK(pState);
+ if (RT_FAILURE(vrc))
+ break;
+
+ int vrc2 = pState->mpVMM->pfnVMR3AtErrorRegister(pState->mpUVM, Console::i_genericVMSetErrorCallback,
+ &pState->mErrorText);
+ AssertRC(vrc2);
+ RTSocketRetain(pState->mhSocket); /* For concurrent access by I/O thread and EMT. */
+ pState->moffStream = 0;
+
+ void *pvUser2 = static_cast<void *>(static_cast<TeleporterState *>(pState));
+ vrc = pState->mpVMM->pfnVMR3LoadFromStream(pState->mpUVM,
+ &g_teleporterTcpOps, pvUser2,
+ teleporterProgressCallback, pvUser2,
+ true /*fTeleporting*/);
+
+ RTSocketRelease(pState->mhSocket);
+ vrc2 = pState->mpVMM->pfnVMR3AtErrorDeregister(pState->mpUVM, Console::i_genericVMSetErrorCallback, &pState->mErrorText);
+ AssertRC(vrc2);
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Teleporter: VMR3LoadFromStream -> %Rrc\n", vrc));
+ teleporterTcpWriteNACK(pState, vrc, pState->mErrorText.c_str());
+ break;
+ }
+
+ /* The EOS might not have been read, make sure it is. */
+ pState->mfStopReading = false;
+ size_t cbRead;
+ vrc = teleporterTcpOpRead(pvUser2, pState->moffStream, szCmd, 1, &cbRead);
+ if (vrc != VERR_EOF)
+ {
+ LogRel(("Teleporter: Draining teleporterTcpOpRead -> %Rrc\n", vrc));
+ teleporterTcpWriteNACK(pState, vrc);
+ break;
+ }
+
+ vrc = teleporterTcpWriteACK(pState);
+ }
+ else if (!strcmp(szCmd, "cancel"))
+ {
+ /* Don't ACK this. */
+ LogRel(("Teleporter: Received cancel command.\n"));
+ vrc = VERR_SSM_CANCELLED;
+ }
+ else if (!strcmp(szCmd, "lock-media"))
+ {
+ hrc = pState->mpControl->LockMedia();
+ if (SUCCEEDED(hrc))
+ {
+ pState->mfLockedMedia = true;
+ vrc = teleporterTcpWriteACK(pState);
+ }
+ else
+ {
+ vrc = VERR_FILE_LOCK_FAILED;
+ teleporterTcpWriteNACK(pState, vrc);
+ }
+ }
+ else if ( !strcmp(szCmd, "hand-over-resume")
+ || !strcmp(szCmd, "hand-over-paused"))
+ {
+ /*
+ * Point of no return.
+ *
+ * Note! Since we cannot tell whether a VMR3Resume failure is
+ * destructive for the source or not, we have little choice
+ * but to ACK it first and take any failures locally.
+ *
+ * Ideally, we should try resume it first and then ACK (or
+ * NACK) the request since this would reduce latency and
+ * make it possible to recover from some VMR3Resume failures.
+ */
+ if ( SUCCEEDED(pState->mptrProgress->NotifyPointOfNoReturn())
+ && pState->mfLockedMedia)
+ {
+ vrc = teleporterTcpWriteACK(pState);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!strcmp(szCmd, "hand-over-resume"))
+ vrc = pState->mpVMM->pfnVMR3Resume(pState->mpUVM, VMRESUMEREASON_TELEPORTED);
+ else
+ pState->mptrConsole->i_setMachineState(MachineState_Paused);
+ fDone = true;
+ break;
+ }
+ }
+ else
+ {
+ vrc = pState->mfLockedMedia ? VERR_WRONG_ORDER : VERR_SSM_CANCELLED;
+ teleporterTcpWriteNACK(pState, vrc);
+ }
+ }
+ else
+ {
+ LogRel(("Teleporter: Unknown command '%s' (%.*Rhxs)\n", szCmd, strlen(szCmd), szCmd));
+ vrc = VERR_NOT_IMPLEMENTED;
+ teleporterTcpWriteNACK(pState, vrc);
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ if (RT_SUCCESS(vrc) && !fDone)
+ vrc = VERR_WRONG_ORDER;
+ if (RT_FAILURE(vrc))
+ teleporterTrgUnlockMedia(pState);
+
+ pState->mRc = vrc;
+ pState->mhSocket = NIL_RTSOCKET;
+ LogFlowFunc(("returns mRc=%Rrc\n", vrc));
+ return VERR_TCP_SERVER_STOP;
+}
+
diff --git a/src/VBox/Main/src-client/ConsoleVRDPServer.cpp b/src/VBox/Main/src-client/ConsoleVRDPServer.cpp
new file mode 100644
index 00000000..bee4968e
--- /dev/null
+++ b/src/VBox/Main/src-client/ConsoleVRDPServer.cpp
@@ -0,0 +1,4059 @@
+/* $Id: ConsoleVRDPServer.cpp $ */
+/** @file
+ * VBox Console VRDP helper class.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE
+#include "LoggingNew.h"
+
+#include "ConsoleVRDPServer.h"
+#include "ConsoleImpl.h"
+#include "DisplayImpl.h"
+#include "KeyboardImpl.h"
+#include "MouseImpl.h"
+#ifdef VBOX_WITH_AUDIO_VRDE
+#include "DrvAudioVRDE.h"
+#endif
+#ifdef VBOX_WITH_EXTPACK
+# include "ExtPackManagerImpl.h"
+#endif
+#include "VMMDev.h"
+#ifdef VBOX_WITH_USB_CARDREADER
+# include "UsbCardReader.h"
+#endif
+#include "UsbWebcamInterface.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+
+#include <iprt/asm.h>
+#include <iprt/alloca.h>
+#include <iprt/ldr.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/cpp/utils.h>
+
+#include <VBox/err.h>
+#include <VBox/RemoteDesktop/VRDEOrders.h>
+#include <VBox/com/listeners.h>
+
+
+class VRDPConsoleListener
+{
+public:
+ VRDPConsoleListener()
+ {
+ }
+
+ virtual ~VRDPConsoleListener()
+ {
+ }
+
+ HRESULT init(ConsoleVRDPServer *server)
+ {
+ m_server = server;
+ return S_OK;
+ }
+
+ void uninit()
+ {
+ }
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent * aEvent)
+ {
+ switch (aType)
+ {
+ case VBoxEventType_OnMousePointerShapeChanged:
+ {
+ ComPtr<IMousePointerShapeChangedEvent> mpscev = aEvent;
+ Assert(mpscev);
+ BOOL visible, alpha;
+ ULONG xHot, yHot, width, height;
+ com::SafeArray <BYTE> shape;
+
+ mpscev->COMGETTER(Visible)(&visible);
+ mpscev->COMGETTER(Alpha)(&alpha);
+ mpscev->COMGETTER(Xhot)(&xHot);
+ mpscev->COMGETTER(Yhot)(&yHot);
+ mpscev->COMGETTER(Width)(&width);
+ mpscev->COMGETTER(Height)(&height);
+ mpscev->COMGETTER(Shape)(ComSafeArrayAsOutParam(shape));
+
+ m_server->onMousePointerShapeChange(visible, alpha, xHot, yHot, width, height, ComSafeArrayAsInParam(shape));
+ break;
+ }
+ case VBoxEventType_OnMouseCapabilityChanged:
+ {
+ ComPtr<IMouseCapabilityChangedEvent> mccev = aEvent;
+ Assert(mccev);
+ if (m_server)
+ {
+ BOOL fAbsoluteMouse;
+ mccev->COMGETTER(SupportsAbsolute)(&fAbsoluteMouse);
+ m_server->NotifyAbsoluteMouse(!!fAbsoluteMouse);
+ }
+ break;
+ }
+ case VBoxEventType_OnKeyboardLedsChanged:
+ {
+ ComPtr<IKeyboardLedsChangedEvent> klcev = aEvent;
+ Assert(klcev);
+
+ if (m_server)
+ {
+ BOOL fNumLock, fCapsLock, fScrollLock;
+ klcev->COMGETTER(NumLock)(&fNumLock);
+ klcev->COMGETTER(CapsLock)(&fCapsLock);
+ klcev->COMGETTER(ScrollLock)(&fScrollLock);
+ m_server->NotifyKeyboardLedsChange(fNumLock, fCapsLock, fScrollLock);
+ }
+ break;
+ }
+
+ default:
+ AssertFailed();
+ }
+
+ return S_OK;
+ }
+
+private:
+ ConsoleVRDPServer *m_server;
+};
+
+typedef ListenerImpl<VRDPConsoleListener, ConsoleVRDPServer*> VRDPConsoleListenerImpl;
+
+VBOX_LISTENER_DECLARE(VRDPConsoleListenerImpl)
+
+#ifdef DEBUG_sunlover
+#define LOGDUMPPTR Log
+void dumpPointer(const uint8_t *pu8Shape, uint32_t width, uint32_t height, bool fXorMaskRGB32)
+{
+ unsigned i;
+
+ const uint8_t *pu8And = pu8Shape;
+
+ for (i = 0; i < height; i++)
+ {
+ unsigned j;
+ LOGDUMPPTR(("%p: ", pu8And));
+ for (j = 0; j < (width + 7) / 8; j++)
+ {
+ unsigned k;
+ for (k = 0; k < 8; k++)
+ {
+ LOGDUMPPTR(("%d", ((*pu8And) & (1 << (7 - k)))? 1: 0));
+ }
+
+ pu8And++;
+ }
+ LOGDUMPPTR(("\n"));
+ }
+
+ if (fXorMaskRGB32)
+ {
+ uint32_t *pu32Xor = (uint32_t*)(pu8Shape + ((((width + 7) / 8) * height + 3) & ~3));
+
+ for (i = 0; i < height; i++)
+ {
+ unsigned j;
+ LOGDUMPPTR(("%p: ", pu32Xor));
+ for (j = 0; j < width; j++)
+ {
+ LOGDUMPPTR(("%08X", *pu32Xor++));
+ }
+ LOGDUMPPTR(("\n"));
+ }
+ }
+ else
+ {
+ /* RDP 24 bit RGB mask. */
+ uint8_t *pu8Xor = (uint8_t*)(pu8Shape + ((((width + 7) / 8) * height + 3) & ~3));
+ for (i = 0; i < height; i++)
+ {
+ unsigned j;
+ LOGDUMPPTR(("%p: ", pu8Xor));
+ for (j = 0; j < width; j++)
+ {
+ LOGDUMPPTR(("%02X%02X%02X", pu8Xor[2], pu8Xor[1], pu8Xor[0]));
+ pu8Xor += 3;
+ }
+ LOGDUMPPTR(("\n"));
+ }
+ }
+}
+#else
+#define dumpPointer(a, b, c, d) do {} while (0)
+#endif /* DEBUG_sunlover */
+
+static void findTopLeftBorder(const uint8_t *pu8AndMask, const uint8_t *pu8XorMask, uint32_t width,
+ uint32_t height, uint32_t *pxSkip, uint32_t *pySkip)
+{
+ /*
+ * Find the top border of the AND mask. First assign to special value.
+ */
+ uint32_t ySkipAnd = UINT32_MAX;
+
+ const uint8_t *pu8And = pu8AndMask;
+ const uint32_t cbAndRow = (width + 7) / 8;
+ const uint8_t maskLastByte = (uint8_t)( 0xFF << (cbAndRow * 8 - width) );
+
+ Assert(cbAndRow > 0);
+
+ unsigned y;
+ unsigned x;
+
+ for (y = 0; y < height && ySkipAnd == ~(uint32_t)0; y++, pu8And += cbAndRow)
+ {
+ /* For each complete byte in the row. */
+ for (x = 0; x < cbAndRow - 1; x++)
+ {
+ if (pu8And[x] != 0xFF)
+ {
+ ySkipAnd = y;
+ break;
+ }
+ }
+
+ if (ySkipAnd == ~(uint32_t)0)
+ {
+ /* Last byte. */
+ if ((pu8And[cbAndRow - 1] & maskLastByte) != maskLastByte)
+ {
+ ySkipAnd = y;
+ }
+ }
+ }
+
+ if (ySkipAnd == ~(uint32_t)0)
+ {
+ ySkipAnd = 0;
+ }
+
+ /*
+ * Find the left border of the AND mask.
+ */
+ uint32_t xSkipAnd = UINT32_MAX;
+
+ /* For all bit columns. */
+ for (x = 0; x < width && xSkipAnd == ~(uint32_t)0; x++)
+ {
+ pu8And = pu8AndMask + x/8; /* Currently checking byte. */
+ uint8_t mask = 1 << (7 - x%8); /* Currently checking bit in the byte. */
+
+ for (y = ySkipAnd; y < height; y++, pu8And += cbAndRow)
+ {
+ if ((*pu8And & mask) == 0)
+ {
+ xSkipAnd = x;
+ break;
+ }
+ }
+ }
+
+ if (xSkipAnd == ~(uint32_t)0)
+ {
+ xSkipAnd = 0;
+ }
+
+ /*
+ * Find the XOR mask top border.
+ */
+ uint32_t ySkipXor = UINT32_MAX;
+
+ uint32_t *pu32XorStart = (uint32_t *)pu8XorMask;
+
+ uint32_t *pu32Xor = pu32XorStart;
+
+ for (y = 0; y < height && ySkipXor == ~(uint32_t)0; y++, pu32Xor += width)
+ {
+ for (x = 0; x < width; x++)
+ {
+ if (pu32Xor[x] != 0)
+ {
+ ySkipXor = y;
+ break;
+ }
+ }
+ }
+
+ if (ySkipXor == ~(uint32_t)0)
+ {
+ ySkipXor = 0;
+ }
+
+ /*
+ * Find the left border of the XOR mask.
+ */
+ uint32_t xSkipXor = ~(uint32_t)0;
+
+ /* For all columns. */
+ for (x = 0; x < width && xSkipXor == ~(uint32_t)0; x++)
+ {
+ pu32Xor = pu32XorStart + x; /* Currently checking dword. */
+
+ for (y = ySkipXor; y < height; y++, pu32Xor += width)
+ {
+ if (*pu32Xor != 0)
+ {
+ xSkipXor = x;
+ break;
+ }
+ }
+ }
+
+ if (xSkipXor == ~(uint32_t)0)
+ {
+ xSkipXor = 0;
+ }
+
+ *pxSkip = RT_MIN(xSkipAnd, xSkipXor);
+ *pySkip = RT_MIN(ySkipAnd, ySkipXor);
+}
+
+/* Generate an AND mask for alpha pointers here, because
+ * guest driver does not do that correctly for Vista pointers.
+ * Similar fix, changing the alpha threshold, could be applied
+ * for the guest driver, but then additions reinstall would be
+ * necessary, which we try to avoid.
+ */
+static void mousePointerGenerateANDMask(uint8_t *pu8DstAndMask, int cbDstAndMask, const uint8_t *pu8SrcAlpha, int w, int h)
+{
+ memset(pu8DstAndMask, 0xFF, cbDstAndMask);
+
+ int y;
+ for (y = 0; y < h; y++)
+ {
+ uint8_t bitmask = 0x80;
+
+ int x;
+ for (x = 0; x < w; x++, bitmask >>= 1)
+ {
+ if (bitmask == 0)
+ {
+ bitmask = 0x80;
+ }
+
+ /* Whether alpha channel value is not transparent enough for the pixel to be seen. */
+ if (pu8SrcAlpha[x * 4 + 3] > 0x7f)
+ {
+ pu8DstAndMask[x / 8] &= ~bitmask;
+ }
+ }
+
+ /* Point to next source and dest scans. */
+ pu8SrcAlpha += w * 4;
+ pu8DstAndMask += (w + 7) / 8;
+ }
+}
+
+void ConsoleVRDPServer::onMousePointerShapeChange(BOOL visible,
+ BOOL alpha,
+ ULONG xHot,
+ ULONG yHot,
+ ULONG width,
+ ULONG height,
+ ComSafeArrayIn(BYTE,inShape))
+{
+ Log9(("VRDPConsoleListener::OnMousePointerShapeChange: %d, %d, %lux%lu, @%lu,%lu\n",
+ visible, alpha, width, height, xHot, yHot));
+
+ com::SafeArray <BYTE> aShape(ComSafeArrayInArg(inShape));
+ if (aShape.size() == 0)
+ {
+ if (!visible)
+ {
+ MousePointerHide();
+ }
+ }
+ else if (width != 0 && height != 0)
+ {
+ uint8_t* shape = aShape.raw();
+
+ dumpPointer(shape, width, height, true);
+
+ /* Try the new interface. */
+ if (MousePointer(alpha, xHot, yHot, width, height, shape) == VINF_SUCCESS)
+ {
+ return;
+ }
+
+ /* Continue with the old interface. */
+
+ /* Pointer consists of 1 bpp AND and 24 BPP XOR masks.
+ * 'shape' AND mask followed by XOR mask.
+ * XOR mask contains 32 bit (lsb)BGR0(msb) values.
+ *
+ * We convert this to RDP color format which consist of
+ * one bpp AND mask and 24 BPP (BGR) color XOR image.
+ *
+ * RDP clients expect 8 aligned width and height of
+ * pointer (preferably 32x32).
+ *
+ * They even contain bugs which do not appear for
+ * 32x32 pointers but would appear for a 41x32 one.
+ *
+ * So set pointer size to 32x32. This can be done safely
+ * because most pointers are 32x32.
+ */
+
+ int cbDstAndMask = (((width + 7) / 8) * height + 3) & ~3;
+
+ uint8_t *pu8AndMask = shape;
+ uint8_t *pu8XorMask = shape + cbDstAndMask;
+
+ if (alpha)
+ {
+ pu8AndMask = (uint8_t*)alloca(cbDstAndMask);
+
+ mousePointerGenerateANDMask(pu8AndMask, cbDstAndMask, pu8XorMask, width, height);
+ }
+
+ /* Windows guest alpha pointers are wider than 32 pixels.
+ * Try to find out the top-left border of the pointer and
+ * then copy only meaningful bits. All complete top rows
+ * and all complete left columns where (AND == 1 && XOR == 0)
+ * are skipped. Hot spot is adjusted.
+ */
+ uint32_t ySkip = 0; /* How many rows to skip at the top. */
+ uint32_t xSkip = 0; /* How many columns to skip at the left. */
+
+ findTopLeftBorder(pu8AndMask, pu8XorMask, width, height, &xSkip, &ySkip);
+
+ /* Must not skip the hot spot. */
+ xSkip = RT_MIN(xSkip, xHot);
+ ySkip = RT_MIN(ySkip, yHot);
+
+ /*
+ * Compute size and allocate memory for the pointer.
+ */
+ const uint32_t dstwidth = 32;
+ const uint32_t dstheight = 32;
+
+ VRDECOLORPOINTER *pointer = NULL;
+
+ uint32_t dstmaskwidth = (dstwidth + 7) / 8;
+
+ uint32_t rdpmaskwidth = dstmaskwidth;
+ uint32_t rdpmasklen = dstheight * rdpmaskwidth;
+
+ uint32_t rdpdatawidth = dstwidth * 3;
+ uint32_t rdpdatalen = dstheight * rdpdatawidth;
+
+ pointer = (VRDECOLORPOINTER *)RTMemTmpAlloc(sizeof(VRDECOLORPOINTER) + rdpmasklen + rdpdatalen);
+
+ if (pointer)
+ {
+ uint8_t *maskarray = (uint8_t*)pointer + sizeof(VRDECOLORPOINTER);
+ uint8_t *dataarray = maskarray + rdpmasklen;
+
+ memset(maskarray, 0xFF, rdpmasklen);
+ memset(dataarray, 0x00, rdpdatalen);
+
+ uint32_t srcmaskwidth = (width + 7) / 8;
+ uint32_t srcdatawidth = width * 4;
+
+ /* Copy AND mask. */
+ uint8_t *src = pu8AndMask + ySkip * srcmaskwidth;
+ uint8_t *dst = maskarray + (dstheight - 1) * rdpmaskwidth;
+
+ uint32_t minheight = RT_MIN(height - ySkip, dstheight);
+ uint32_t minwidth = RT_MIN(width - xSkip, dstwidth);
+
+ unsigned x, y;
+
+ for (y = 0; y < minheight; y++)
+ {
+ for (x = 0; x < minwidth; x++)
+ {
+ uint32_t byteIndex = (x + xSkip) / 8;
+ uint32_t bitIndex = (x + xSkip) % 8;
+
+ bool bit = (src[byteIndex] & (1 << (7 - bitIndex))) != 0;
+
+ if (!bit)
+ {
+ byteIndex = x / 8;
+ bitIndex = x % 8;
+
+ dst[byteIndex] &= ~(1 << (7 - bitIndex));
+ }
+ }
+
+ src += srcmaskwidth;
+ dst -= rdpmaskwidth;
+ }
+
+ /* Point src to XOR mask */
+ src = pu8XorMask + ySkip * srcdatawidth;
+ dst = dataarray + (dstheight - 1) * rdpdatawidth;
+
+ for (y = 0; y < minheight ; y++)
+ {
+ for (x = 0; x < minwidth; x++)
+ {
+ memcpy(dst + x * 3, &src[4 * (x + xSkip)], 3);
+ }
+
+ src += srcdatawidth;
+ dst -= rdpdatawidth;
+ }
+
+ pointer->u16HotX = (uint16_t)(xHot - xSkip);
+ pointer->u16HotY = (uint16_t)(yHot - ySkip);
+
+ pointer->u16Width = (uint16_t)dstwidth;
+ pointer->u16Height = (uint16_t)dstheight;
+
+ pointer->u16MaskLen = (uint16_t)rdpmasklen;
+ pointer->u16DataLen = (uint16_t)rdpdatalen;
+
+ dumpPointer((uint8_t*)pointer + sizeof(*pointer), dstwidth, dstheight, false);
+
+ MousePointerUpdate(pointer);
+
+ RTMemTmpFree(pointer);
+ }
+ }
+}
+
+
+// ConsoleVRDPServer
+////////////////////////////////////////////////////////////////////////////////
+
+RTLDRMOD ConsoleVRDPServer::mVRDPLibrary = NIL_RTLDRMOD;
+
+PFNVRDECREATESERVER ConsoleVRDPServer::mpfnVRDECreateServer = NULL;
+
+VRDEENTRYPOINTS_4 ConsoleVRDPServer::mEntryPoints; /* A copy of the server entry points. */
+VRDEENTRYPOINTS_4 *ConsoleVRDPServer::mpEntryPoints = NULL;
+
+VRDECALLBACKS_4 ConsoleVRDPServer::mCallbacks =
+{
+ { VRDE_INTERFACE_VERSION_4, sizeof(VRDECALLBACKS_4) },
+ ConsoleVRDPServer::VRDPCallbackQueryProperty,
+ ConsoleVRDPServer::VRDPCallbackClientLogon,
+ ConsoleVRDPServer::VRDPCallbackClientConnect,
+ ConsoleVRDPServer::VRDPCallbackClientDisconnect,
+ ConsoleVRDPServer::VRDPCallbackIntercept,
+ ConsoleVRDPServer::VRDPCallbackUSB,
+ ConsoleVRDPServer::VRDPCallbackClipboard,
+ ConsoleVRDPServer::VRDPCallbackFramebufferQuery,
+ ConsoleVRDPServer::VRDPCallbackFramebufferLock,
+ ConsoleVRDPServer::VRDPCallbackFramebufferUnlock,
+ ConsoleVRDPServer::VRDPCallbackInput,
+ ConsoleVRDPServer::VRDPCallbackVideoModeHint,
+ ConsoleVRDPServer::VRDECallbackAudioIn
+};
+
+DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackQueryProperty(void *pvCallback, uint32_t index, void *pvBuffer,
+ uint32_t cbBuffer, uint32_t *pcbOut)
+{
+ ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback);
+
+ int vrc = VERR_NOT_SUPPORTED;
+
+ switch (index)
+ {
+ case VRDE_QP_NETWORK_PORT:
+ {
+ /* This is obsolete, the VRDE server uses VRDE_QP_NETWORK_PORT_RANGE instead. */
+ ULONG port = 0;
+
+ if (cbBuffer >= sizeof(uint32_t))
+ {
+ *(uint32_t *)pvBuffer = (uint32_t)port;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = sizeof(uint32_t);
+ } break;
+
+ case VRDE_QP_NETWORK_ADDRESS:
+ {
+ com::Bstr bstr;
+ server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("TCP/Address").raw(), bstr.asOutParam());
+
+ /* The server expects UTF8. */
+ com::Utf8Str address = bstr;
+
+ size_t cbAddress = address.length() + 1;
+
+ if (cbAddress >= 0x10000)
+ {
+ /* More than 64K seems to be an invalid address. */
+ vrc = VERR_TOO_MUCH_DATA;
+ break;
+ }
+
+ if ((size_t)cbBuffer >= cbAddress)
+ {
+ memcpy(pvBuffer, address.c_str(), cbAddress);
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = (uint32_t)cbAddress;
+ } break;
+
+ case VRDE_QP_NUMBER_MONITORS:
+ {
+ uint32_t cMonitors = server->mConsole->i_getDisplay()->i_getMonitorCount();
+
+ if (cbBuffer >= sizeof(uint32_t))
+ {
+ *(uint32_t *)pvBuffer = (uint32_t)cMonitors;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = sizeof(uint32_t);
+ } break;
+
+ case VRDE_QP_NETWORK_PORT_RANGE:
+ {
+ com::Bstr bstr;
+ HRESULT hrc = server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("TCP/Ports").raw(), bstr.asOutParam());
+
+ if (hrc != S_OK)
+ {
+ bstr = "";
+ }
+
+ if (bstr == "0")
+ {
+ bstr = "3389";
+ }
+
+ /* The server expects UTF8. */
+ com::Utf8Str portRange = bstr;
+
+ size_t cbPortRange = portRange.length() + 1;
+
+ if (cbPortRange >= _64K)
+ {
+ /* More than 64K seems to be an invalid port range string. */
+ vrc = VERR_TOO_MUCH_DATA;
+ break;
+ }
+
+ if ((size_t)cbBuffer >= cbPortRange)
+ {
+ memcpy(pvBuffer, portRange.c_str(), cbPortRange);
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = (uint32_t)cbPortRange;
+ } break;
+
+ case VRDE_QP_VIDEO_CHANNEL:
+ {
+ com::Bstr bstr;
+ HRESULT hrc = server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("VideoChannel/Enabled").raw(),
+ bstr.asOutParam());
+
+ if (hrc != S_OK)
+ {
+ bstr = "";
+ }
+
+ com::Utf8Str value = bstr;
+
+ BOOL fVideoEnabled = RTStrICmp(value.c_str(), "true") == 0
+ || RTStrICmp(value.c_str(), "1") == 0;
+
+ if (cbBuffer >= sizeof(uint32_t))
+ {
+ *(uint32_t *)pvBuffer = (uint32_t)fVideoEnabled;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = sizeof(uint32_t);
+ } break;
+
+ case VRDE_QP_VIDEO_CHANNEL_QUALITY:
+ {
+ com::Bstr bstr;
+ HRESULT hrc = server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("VideoChannel/Quality").raw(),
+ bstr.asOutParam());
+
+ if (hrc != S_OK)
+ {
+ bstr = "";
+ }
+
+ com::Utf8Str value = bstr;
+
+ ULONG ulQuality = RTStrToUInt32(value.c_str()); /* This returns 0 on invalid string which is ok. */
+
+ if (cbBuffer >= sizeof(uint32_t))
+ {
+ *(uint32_t *)pvBuffer = (uint32_t)ulQuality;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = sizeof(uint32_t);
+ } break;
+
+ case VRDE_QP_VIDEO_CHANNEL_SUNFLSH:
+ {
+ ULONG ulSunFlsh = 1;
+
+ com::Bstr bstr;
+ HRESULT hrc = server->mConsole->i_machine()->GetExtraData(Bstr("VRDP/SunFlsh").raw(),
+ bstr.asOutParam());
+ if (hrc == S_OK && !bstr.isEmpty())
+ {
+ com::Utf8Str sunFlsh = bstr;
+ if (!sunFlsh.isEmpty())
+ {
+ ulSunFlsh = sunFlsh.toUInt32();
+ }
+ }
+
+ if (cbBuffer >= sizeof(uint32_t))
+ {
+ *(uint32_t *)pvBuffer = (uint32_t)ulSunFlsh;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = sizeof(uint32_t);
+ } break;
+
+ case VRDE_QP_FEATURE:
+ {
+ if (cbBuffer < sizeof(VRDEFEATURE))
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ size_t cbInfo = cbBuffer - RT_UOFFSETOF(VRDEFEATURE, achInfo);
+
+ VRDEFEATURE *pFeature = (VRDEFEATURE *)pvBuffer;
+
+ size_t cchInfo = 0;
+ vrc = RTStrNLenEx(pFeature->achInfo, cbInfo, &cchInfo);
+
+ if (RT_FAILURE(vrc))
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ Log(("VRDE_QP_FEATURE [%s]\n", pFeature->achInfo));
+
+ com::Bstr bstrValue;
+
+ if ( RTStrICmp(pFeature->achInfo, "Client/DisableDisplay") == 0
+ || RTStrICmp(pFeature->achInfo, "Client/DisableInput") == 0
+ || RTStrICmp(pFeature->achInfo, "Client/DisableAudio") == 0
+ || RTStrICmp(pFeature->achInfo, "Client/DisableUSB") == 0
+ || RTStrICmp(pFeature->achInfo, "Client/DisableClipboard") == 0
+ )
+ {
+ /** @todo these features should be per client. */
+ NOREF(pFeature->u32ClientId);
+
+ /* These features are mapped to "VRDE/Feature/NAME" extra data. */
+ com::Utf8Str extraData("VRDE/Feature/");
+ extraData += pFeature->achInfo;
+
+ HRESULT hrc = server->mConsole->i_machine()->GetExtraData(com::Bstr(extraData).raw(),
+ bstrValue.asOutParam());
+ if (FAILED(hrc) || bstrValue.isEmpty())
+ {
+ /* Also try the old "VRDP/Feature/NAME" */
+ extraData = "VRDP/Feature/";
+ extraData += pFeature->achInfo;
+
+ hrc = server->mConsole->i_machine()->GetExtraData(com::Bstr(extraData).raw(),
+ bstrValue.asOutParam());
+ if (FAILED(hrc))
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ }
+ else if (RTStrNCmp(pFeature->achInfo, "Property/", 9) == 0)
+ {
+ /* Generic properties. */
+ const char *pszPropertyName = &pFeature->achInfo[9];
+ HRESULT hrc = server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr(pszPropertyName).raw(),
+ bstrValue.asOutParam());
+ if (FAILED(hrc))
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ /* Copy the value string to the callers buffer. */
+ if (vrc == VINF_SUCCESS)
+ {
+ com::Utf8Str value = bstrValue;
+
+ size_t cb = value.length() + 1;
+
+ if ((size_t)cbInfo >= cb)
+ {
+ memcpy(pFeature->achInfo, value.c_str(), cb);
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = (uint32_t)cb;
+ }
+ } break;
+
+ case VRDE_SP_NETWORK_BIND_PORT:
+ {
+ if (cbBuffer != sizeof(uint32_t))
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ ULONG port = *(uint32_t *)pvBuffer;
+
+ server->mVRDPBindPort = port;
+
+ vrc = VINF_SUCCESS;
+
+ if (pcbOut)
+ {
+ *pcbOut = sizeof(uint32_t);
+ }
+
+ server->mConsole->i_onVRDEServerInfoChange();
+ } break;
+
+ case VRDE_SP_CLIENT_STATUS:
+ {
+ if (cbBuffer < sizeof(VRDECLIENTSTATUS))
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ size_t cbStatus = cbBuffer - RT_UOFFSETOF(VRDECLIENTSTATUS, achStatus);
+
+ VRDECLIENTSTATUS *pStatus = (VRDECLIENTSTATUS *)pvBuffer;
+
+ if (cbBuffer < RT_UOFFSETOF(VRDECLIENTSTATUS, achStatus) + pStatus->cbStatus)
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ size_t cchStatus = 0;
+ vrc = RTStrNLenEx(pStatus->achStatus, cbStatus, &cchStatus);
+
+ if (RT_FAILURE(vrc))
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ Log(("VRDE_SP_CLIENT_STATUS [%s]\n", pStatus->achStatus));
+
+ server->mConsole->i_VRDPClientStatusChange(pStatus->u32ClientId, pStatus->achStatus);
+
+ vrc = VINF_SUCCESS;
+
+ if (pcbOut)
+ {
+ *pcbOut = cbBuffer;
+ }
+
+ server->mConsole->i_onVRDEServerInfoChange();
+ } break;
+
+ default:
+ break;
+ }
+
+ return vrc;
+}
+
+DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackClientLogon(void *pvCallback, uint32_t u32ClientId, const char *pszUser,
+ const char *pszPassword, const char *pszDomain)
+{
+ ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback);
+
+ return server->mConsole->i_VRDPClientLogon(u32ClientId, pszUser, pszPassword, pszDomain);
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackClientConnect(void *pvCallback, uint32_t u32ClientId)
+{
+ ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvCallback);
+
+ pServer->mConsole->i_VRDPClientConnect(u32ClientId);
+
+ /* Should the server report usage of an interface for each client?
+ * Similar to Intercept.
+ */
+ int c = ASMAtomicIncS32(&pServer->mcClients);
+ if (c == 1)
+ {
+ /* Features which should be enabled only if there is a client. */
+ pServer->remote3DRedirect(true);
+ }
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ AudioVRDE *pVRDE = pServer->mConsole->i_getAudioVRDE();
+ if (pVRDE)
+ pVRDE->onVRDEClientConnect(u32ClientId);
+#endif
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackClientDisconnect(void *pvCallback, uint32_t u32ClientId,
+ uint32_t fu32Intercepted)
+{
+ ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvCallback);
+ AssertPtrReturnVoid(pServer);
+
+ pServer->mConsole->i_VRDPClientDisconnect(u32ClientId, fu32Intercepted);
+
+ if (ASMAtomicReadU32(&pServer->mu32AudioInputClientId) == u32ClientId)
+ {
+ LogFunc(("Disconnected client %u\n", u32ClientId));
+ ASMAtomicWriteU32(&pServer->mu32AudioInputClientId, 0);
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ AudioVRDE *pVRDE = pServer->mConsole->i_getAudioVRDE();
+ if (pVRDE)
+ {
+ pVRDE->onVRDEInputIntercept(false /* fIntercept */);
+ pVRDE->onVRDEClientDisconnect(u32ClientId);
+ }
+#endif
+ }
+
+ int32_t cClients = ASMAtomicDecS32(&pServer->mcClients);
+ if (cClients == 0)
+ {
+ /* Features which should be enabled only if there is a client. */
+ pServer->remote3DRedirect(false);
+ }
+}
+
+DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackIntercept(void *pvCallback, uint32_t u32ClientId, uint32_t fu32Intercept,
+ void **ppvIntercept)
+{
+ ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvCallback);
+ AssertPtrReturn(pServer, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("%x\n", fu32Intercept));
+
+ int vrc = VERR_NOT_SUPPORTED;
+
+ switch (fu32Intercept)
+ {
+ case VRDE_CLIENT_INTERCEPT_AUDIO:
+ {
+ pServer->mConsole->i_VRDPInterceptAudio(u32ClientId);
+ if (ppvIntercept)
+ {
+ *ppvIntercept = pServer;
+ }
+ vrc = VINF_SUCCESS;
+ } break;
+
+ case VRDE_CLIENT_INTERCEPT_USB:
+ {
+ pServer->mConsole->i_VRDPInterceptUSB(u32ClientId, ppvIntercept);
+ vrc = VINF_SUCCESS;
+ } break;
+
+ case VRDE_CLIENT_INTERCEPT_CLIPBOARD:
+ {
+ pServer->mConsole->i_VRDPInterceptClipboard(u32ClientId);
+ if (ppvIntercept)
+ {
+ *ppvIntercept = pServer;
+ }
+ vrc = VINF_SUCCESS;
+ } break;
+
+ case VRDE_CLIENT_INTERCEPT_AUDIO_INPUT:
+ {
+ /*
+ * This request is processed internally by the ConsoleVRDPServer.
+ * Only one client is allowed to intercept audio input.
+ */
+ if (ASMAtomicCmpXchgU32(&pServer->mu32AudioInputClientId, u32ClientId, 0) == true)
+ {
+ LogFunc(("Intercepting audio input by client %RU32\n", u32ClientId));
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ AudioVRDE *pVRDE = pServer->mConsole->i_getAudioVRDE();
+ if (pVRDE)
+ pVRDE->onVRDEInputIntercept(true /* fIntercept */);
+#endif
+ }
+ else
+ {
+ Log(("AUDIOIN: ignored client %RU32, active client %RU32\n", u32ClientId, pServer->mu32AudioInputClientId));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ } break;
+
+ default:
+ break;
+ }
+
+ return vrc;
+}
+
+DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackUSB(void *pvCallback, void *pvIntercept, uint32_t u32ClientId,
+ uint8_t u8Code, const void *pvRet, uint32_t cbRet)
+{
+ RT_NOREF(pvCallback);
+#ifdef VBOX_WITH_USB
+ return USBClientResponseCallback(pvIntercept, u32ClientId, u8Code, pvRet, cbRet);
+#else
+ RT_NOREF(pvCallback, pvIntercept, u32ClientId, u8Code, pvRet, cbRet);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackClipboard(void *pvCallback, void *pvIntercept, uint32_t u32ClientId,
+ uint32_t u32Function, uint32_t u32Format,
+ const void *pvData, uint32_t cbData)
+{
+ RT_NOREF(pvCallback);
+ return ClipboardCallback(pvIntercept, u32ClientId, u32Function, u32Format, pvData, cbData);
+}
+
+DECLCALLBACK(bool) ConsoleVRDPServer::VRDPCallbackFramebufferQuery(void *pvCallback, unsigned uScreenId,
+ VRDEFRAMEBUFFERINFO *pInfo)
+{
+ ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback);
+
+ bool fAvailable = false;
+
+ /* Obtain the new screen bitmap. */
+ HRESULT hr = server->mConsole->i_getDisplay()->QuerySourceBitmap(uScreenId, server->maSourceBitmaps[uScreenId].asOutParam());
+ if (SUCCEEDED(hr))
+ {
+ LONG xOrigin = 0;
+ LONG yOrigin = 0;
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ hr = server->maSourceBitmaps[uScreenId]->QueryBitmapInfo(&pAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+
+ if (SUCCEEDED(hr))
+ {
+ ULONG dummy;
+ GuestMonitorStatus_T monitorStatus;
+ hr = server->mConsole->i_getDisplay()->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy,
+ &xOrigin, &yOrigin, &monitorStatus);
+
+ if (SUCCEEDED(hr))
+ {
+ /* Now fill the information as requested by the caller. */
+ pInfo->pu8Bits = pAddress;
+ pInfo->xOrigin = xOrigin;
+ pInfo->yOrigin = yOrigin;
+ pInfo->cWidth = ulWidth;
+ pInfo->cHeight = ulHeight;
+ pInfo->cBitsPerPixel = ulBitsPerPixel;
+ pInfo->cbLine = ulBytesPerLine;
+
+ fAvailable = true;
+ }
+ }
+ }
+
+ return fAvailable;
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackFramebufferLock(void *pvCallback, unsigned uScreenId)
+{
+ NOREF(pvCallback);
+ NOREF(uScreenId);
+ /* Do nothing */
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackFramebufferUnlock(void *pvCallback, unsigned uScreenId)
+{
+ NOREF(pvCallback);
+ NOREF(uScreenId);
+ /* Do nothing */
+}
+
+static void fixKbdLockStatus(VRDPInputSynch *pInputSynch, IKeyboard *pKeyboard)
+{
+ if ( pInputSynch->cGuestNumLockAdaptions
+ && (pInputSynch->fGuestNumLock != pInputSynch->fClientNumLock))
+ {
+ pInputSynch->cGuestNumLockAdaptions--;
+ pKeyboard->PutScancode(0x45);
+ pKeyboard->PutScancode(0x45 | 0x80);
+ }
+ if ( pInputSynch->cGuestCapsLockAdaptions
+ && (pInputSynch->fGuestCapsLock != pInputSynch->fClientCapsLock))
+ {
+ pInputSynch->cGuestCapsLockAdaptions--;
+ pKeyboard->PutScancode(0x3a);
+ pKeyboard->PutScancode(0x3a | 0x80);
+ }
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackInput(void *pvCallback, int type, const void *pvInput, unsigned cbInput)
+{
+ ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback);
+ Console *pConsole = server->mConsole;
+
+ switch (type)
+ {
+ case VRDE_INPUT_SCANCODE:
+ {
+ if (cbInput == sizeof(VRDEINPUTSCANCODE))
+ {
+ IKeyboard *pKeyboard = pConsole->i_getKeyboard();
+
+ const VRDEINPUTSCANCODE *pInputScancode = (VRDEINPUTSCANCODE *)pvInput;
+
+ /* Track lock keys. */
+ if (pInputScancode->uScancode == 0x45)
+ {
+ server->m_InputSynch.fClientNumLock = !server->m_InputSynch.fClientNumLock;
+ }
+ else if (pInputScancode->uScancode == 0x3a)
+ {
+ server->m_InputSynch.fClientCapsLock = !server->m_InputSynch.fClientCapsLock;
+ }
+ else if (pInputScancode->uScancode == 0x46)
+ {
+ server->m_InputSynch.fClientScrollLock = !server->m_InputSynch.fClientScrollLock;
+ }
+ else if ((pInputScancode->uScancode & 0x80) == 0)
+ {
+ /* Key pressed. */
+ fixKbdLockStatus(&server->m_InputSynch, pKeyboard);
+ }
+
+ pKeyboard->PutScancode((LONG)pInputScancode->uScancode);
+ }
+ } break;
+
+ case VRDE_INPUT_POINT:
+ {
+ if (cbInput == sizeof(VRDEINPUTPOINT))
+ {
+ const VRDEINPUTPOINT *pInputPoint = (VRDEINPUTPOINT *)pvInput;
+
+ int mouseButtons = 0;
+ int iWheel = 0;
+
+ if (pInputPoint->uButtons & VRDE_INPUT_POINT_BUTTON1)
+ {
+ mouseButtons |= MouseButtonState_LeftButton;
+ }
+ if (pInputPoint->uButtons & VRDE_INPUT_POINT_BUTTON2)
+ {
+ mouseButtons |= MouseButtonState_RightButton;
+ }
+ if (pInputPoint->uButtons & VRDE_INPUT_POINT_BUTTON3)
+ {
+ mouseButtons |= MouseButtonState_MiddleButton;
+ }
+ if (pInputPoint->uButtons & VRDE_INPUT_POINT_WHEEL_UP)
+ {
+ mouseButtons |= MouseButtonState_WheelUp;
+ iWheel = -1;
+ }
+ if (pInputPoint->uButtons & VRDE_INPUT_POINT_WHEEL_DOWN)
+ {
+ mouseButtons |= MouseButtonState_WheelDown;
+ iWheel = 1;
+ }
+
+ if (server->m_fGuestWantsAbsolute)
+ {
+ pConsole->i_getMouse()->PutMouseEventAbsolute(pInputPoint->x + 1, pInputPoint->y + 1, iWheel,
+ 0 /* Horizontal wheel */, mouseButtons);
+ } else
+ {
+ pConsole->i_getMouse()->PutMouseEvent(pInputPoint->x - server->m_mousex,
+ pInputPoint->y - server->m_mousey,
+ iWheel, 0 /* Horizontal wheel */, mouseButtons);
+ server->m_mousex = pInputPoint->x;
+ server->m_mousey = pInputPoint->y;
+ }
+ }
+ } break;
+
+ case VRDE_INPUT_CAD:
+ {
+ pConsole->i_getKeyboard()->PutCAD();
+ } break;
+
+ case VRDE_INPUT_RESET:
+ {
+ pConsole->Reset();
+ } break;
+
+ case VRDE_INPUT_SYNCH:
+ {
+ if (cbInput == sizeof(VRDEINPUTSYNCH))
+ {
+ IKeyboard *pKeyboard = pConsole->i_getKeyboard();
+
+ const VRDEINPUTSYNCH *pInputSynch = (VRDEINPUTSYNCH *)pvInput;
+
+ server->m_InputSynch.fClientNumLock = (pInputSynch->uLockStatus & VRDE_INPUT_SYNCH_NUMLOCK) != 0;
+ server->m_InputSynch.fClientCapsLock = (pInputSynch->uLockStatus & VRDE_INPUT_SYNCH_CAPITAL) != 0;
+ server->m_InputSynch.fClientScrollLock = (pInputSynch->uLockStatus & VRDE_INPUT_SYNCH_SCROLL) != 0;
+
+ /* The client initiated synchronization. Always make the guest to reflect the client state.
+ * Than means, when the guest changes the state itself, it is forced to return to the client
+ * state.
+ */
+ if (server->m_InputSynch.fClientNumLock != server->m_InputSynch.fGuestNumLock)
+ {
+ server->m_InputSynch.cGuestNumLockAdaptions = 2;
+ }
+
+ if (server->m_InputSynch.fClientCapsLock != server->m_InputSynch.fGuestCapsLock)
+ {
+ server->m_InputSynch.cGuestCapsLockAdaptions = 2;
+ }
+
+ fixKbdLockStatus(&server->m_InputSynch, pKeyboard);
+ }
+ } break;
+
+ default:
+ break;
+ }
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackVideoModeHint(void *pvCallback, unsigned cWidth, unsigned cHeight,
+ unsigned cBitsPerPixel, unsigned uScreenId)
+{
+ ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback);
+
+ server->mConsole->i_getDisplay()->SetVideoModeHint(uScreenId, TRUE /*=enabled*/,
+ FALSE /*=changeOrigin*/, 0/*=OriginX*/, 0/*=OriginY*/,
+ cWidth, cHeight, cBitsPerPixel, TRUE /*=notify*/);
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackAudioIn(void *pvCallback,
+ void *pvCtx,
+ uint32_t u32ClientId,
+ uint32_t u32Event,
+ const void *pvData,
+ uint32_t cbData)
+{
+ RT_NOREF(u32ClientId);
+ ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvCallback);
+ AssertPtrReturnVoid(pServer);
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ AudioVRDE *pVRDE = pServer->mConsole->i_getAudioVRDE();
+ if (!pVRDE) /* Nothing to do, bail out early. */
+ return;
+
+ switch (u32Event)
+ {
+ case VRDE_AUDIOIN_BEGIN:
+ {
+ pVRDE->onVRDEInputBegin(pvCtx, (PVRDEAUDIOINBEGIN)pvData);
+ break;
+ }
+
+ case VRDE_AUDIOIN_DATA:
+ pVRDE->onVRDEInputData(pvCtx, pvData, cbData);
+ break;
+
+ case VRDE_AUDIOIN_END:
+ pVRDE->onVRDEInputEnd(pvCtx);
+ break;
+
+ default:
+ break;
+ }
+#else
+ RT_NOREF(pvCtx, u32Event, pvData, cbData);
+#endif /* VBOX_WITH_AUDIO_VRDE */
+}
+
+ConsoleVRDPServer::ConsoleVRDPServer(Console *console)
+ : mhClipboard(NULL)
+{
+ mConsole = console;
+
+ int vrc = RTCritSectInit(&mCritSect);
+ AssertRC(vrc);
+
+ mcClipboardRefs = 0;
+ mpfnClipboardCallback = NULL;
+#ifdef VBOX_WITH_USB
+ mUSBBackends.pHead = NULL;
+ mUSBBackends.pTail = NULL;
+
+ mUSBBackends.thread = NIL_RTTHREAD;
+ mUSBBackends.fThreadRunning = false;
+ mUSBBackends.event = 0;
+#endif
+
+ mhServer = 0;
+ mServerInterfaceVersion = 0;
+
+ mcInResize = 0;
+
+ m_fGuestWantsAbsolute = false;
+ m_mousex = 0;
+ m_mousey = 0;
+
+ m_InputSynch.cGuestNumLockAdaptions = 2;
+ m_InputSynch.cGuestCapsLockAdaptions = 2;
+
+ m_InputSynch.fGuestNumLock = false;
+ m_InputSynch.fGuestCapsLock = false;
+ m_InputSynch.fGuestScrollLock = false;
+
+ m_InputSynch.fClientNumLock = false;
+ m_InputSynch.fClientCapsLock = false;
+ m_InputSynch.fClientScrollLock = false;
+
+ {
+ ComPtr<IEventSource> es;
+ console->COMGETTER(EventSource)(es.asOutParam());
+ ComObjPtr<VRDPConsoleListenerImpl> aConsoleListener;
+ aConsoleListener.createObject();
+ aConsoleListener->init(new VRDPConsoleListener(), this);
+ mConsoleListener = aConsoleListener;
+ com::SafeArray <VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnMousePointerShapeChanged);
+ eventTypes.push_back(VBoxEventType_OnMouseCapabilityChanged);
+ eventTypes.push_back(VBoxEventType_OnKeyboardLedsChanged);
+ es->RegisterListener(mConsoleListener, ComSafeArrayAsInParam(eventTypes), true);
+ }
+
+ mVRDPBindPort = -1;
+
+#ifndef VBOX_WITH_VRDEAUTH_IN_VBOXSVC
+ RT_ZERO(mAuthLibCtx);
+#endif
+
+ mu32AudioInputClientId = 0;
+ mcClients = 0;
+
+ /*
+ * Optional interfaces.
+ */
+ m_fInterfaceImage = false;
+ RT_ZERO(m_interfaceImage);
+ RT_ZERO(m_interfaceCallbacksImage);
+ RT_ZERO(m_interfaceMousePtr);
+ RT_ZERO(m_interfaceSCard);
+ RT_ZERO(m_interfaceCallbacksSCard);
+ RT_ZERO(m_interfaceTSMF);
+ RT_ZERO(m_interfaceCallbacksTSMF);
+ RT_ZERO(m_interfaceVideoIn);
+ RT_ZERO(m_interfaceCallbacksVideoIn);
+ RT_ZERO(m_interfaceInput);
+ RT_ZERO(m_interfaceCallbacksInput);
+
+ vrc = RTCritSectInit(&mTSMFLock);
+ AssertRC(vrc);
+
+ mEmWebcam = new EmWebcam(this);
+ AssertPtr(mEmWebcam);
+}
+
+ConsoleVRDPServer::~ConsoleVRDPServer()
+{
+ Stop();
+
+ if (mConsoleListener)
+ {
+ ComPtr<IEventSource> es;
+ mConsole->COMGETTER(EventSource)(es.asOutParam());
+ es->UnregisterListener(mConsoleListener);
+ mConsoleListener.setNull();
+ }
+
+ unsigned i;
+ for (i = 0; i < RT_ELEMENTS(maSourceBitmaps); i++)
+ {
+ maSourceBitmaps[i].setNull();
+ }
+
+ if (mEmWebcam)
+ {
+ delete mEmWebcam;
+ mEmWebcam = NULL;
+ }
+
+ if (RTCritSectIsInitialized(&mCritSect))
+ {
+ RTCritSectDelete(&mCritSect);
+ RT_ZERO(mCritSect);
+ }
+
+ if (RTCritSectIsInitialized(&mTSMFLock))
+ {
+ RTCritSectDelete(&mTSMFLock);
+ RT_ZERO(mTSMFLock);
+ }
+}
+
+int ConsoleVRDPServer::Launch(void)
+{
+ LogFlowThisFunc(("\n"));
+
+ IVRDEServer *server = mConsole->i_getVRDEServer();
+ AssertReturn(server, VERR_INTERNAL_ERROR_2);
+
+ /*
+ * Check if VRDE is enabled.
+ */
+ BOOL fEnabled;
+ HRESULT hrc = server->COMGETTER(Enabled)(&fEnabled);
+ AssertComRCReturn(hrc, Global::vboxStatusCodeFromCOM(hrc));
+ if (!fEnabled)
+ return VINF_SUCCESS;
+
+ /*
+ * Check that a VRDE extension pack name is set and resolve it into a
+ * library path.
+ */
+ Bstr bstrExtPack;
+ hrc = server->COMGETTER(VRDEExtPack)(bstrExtPack.asOutParam());
+ if (FAILED(hrc))
+ return Global::vboxStatusCodeFromCOM(hrc);
+ if (bstrExtPack.isEmpty())
+ return VINF_NOT_SUPPORTED;
+
+ Utf8Str strExtPack(bstrExtPack);
+ Utf8Str strVrdeLibrary;
+ int vrc = VINF_SUCCESS;
+ if (strExtPack.equals(VBOXVRDP_KLUDGE_EXTPACK_NAME))
+ strVrdeLibrary = "VBoxVRDP";
+ else
+ {
+#ifdef VBOX_WITH_EXTPACK
+ ExtPackManager *pExtPackMgr = mConsole->i_getExtPackManager();
+ vrc = pExtPackMgr->i_getVrdeLibraryPathForExtPack(&strExtPack, &strVrdeLibrary);
+#else
+ vrc = VERR_FILE_NOT_FOUND;
+#endif
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Load the VRDE library and start the server, if it is enabled.
+ */
+ vrc = loadVRDPLibrary(strVrdeLibrary.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ VRDEENTRYPOINTS_4 *pEntryPoints4;
+ vrc = mpfnVRDECreateServer(&mCallbacks.header, this, (VRDEINTERFACEHDR **)&pEntryPoints4, &mhServer);
+
+ if (RT_SUCCESS(vrc))
+ {
+ mServerInterfaceVersion = 4;
+ mEntryPoints = *pEntryPoints4;
+ mpEntryPoints = &mEntryPoints;
+ }
+ else if (vrc == VERR_VERSION_MISMATCH)
+ {
+ /* An older version of VRDE is installed, try version 3. */
+ VRDEENTRYPOINTS_3 *pEntryPoints3;
+
+ static VRDECALLBACKS_3 sCallbacks3 =
+ {
+ { VRDE_INTERFACE_VERSION_3, sizeof(VRDECALLBACKS_3) },
+ ConsoleVRDPServer::VRDPCallbackQueryProperty,
+ ConsoleVRDPServer::VRDPCallbackClientLogon,
+ ConsoleVRDPServer::VRDPCallbackClientConnect,
+ ConsoleVRDPServer::VRDPCallbackClientDisconnect,
+ ConsoleVRDPServer::VRDPCallbackIntercept,
+ ConsoleVRDPServer::VRDPCallbackUSB,
+ ConsoleVRDPServer::VRDPCallbackClipboard,
+ ConsoleVRDPServer::VRDPCallbackFramebufferQuery,
+ ConsoleVRDPServer::VRDPCallbackFramebufferLock,
+ ConsoleVRDPServer::VRDPCallbackFramebufferUnlock,
+ ConsoleVRDPServer::VRDPCallbackInput,
+ ConsoleVRDPServer::VRDPCallbackVideoModeHint,
+ ConsoleVRDPServer::VRDECallbackAudioIn
+ };
+
+ vrc = mpfnVRDECreateServer(&sCallbacks3.header, this, (VRDEINTERFACEHDR **)&pEntryPoints3, &mhServer);
+ if (RT_SUCCESS(vrc))
+ {
+ mServerInterfaceVersion = 3;
+ mEntryPoints.header = pEntryPoints3->header;
+ mEntryPoints.VRDEDestroy = pEntryPoints3->VRDEDestroy;
+ mEntryPoints.VRDEEnableConnections = pEntryPoints3->VRDEEnableConnections;
+ mEntryPoints.VRDEDisconnect = pEntryPoints3->VRDEDisconnect;
+ mEntryPoints.VRDEResize = pEntryPoints3->VRDEResize;
+ mEntryPoints.VRDEUpdate = pEntryPoints3->VRDEUpdate;
+ mEntryPoints.VRDEColorPointer = pEntryPoints3->VRDEColorPointer;
+ mEntryPoints.VRDEHidePointer = pEntryPoints3->VRDEHidePointer;
+ mEntryPoints.VRDEAudioSamples = pEntryPoints3->VRDEAudioSamples;
+ mEntryPoints.VRDEAudioVolume = pEntryPoints3->VRDEAudioVolume;
+ mEntryPoints.VRDEUSBRequest = pEntryPoints3->VRDEUSBRequest;
+ mEntryPoints.VRDEClipboard = pEntryPoints3->VRDEClipboard;
+ mEntryPoints.VRDEQueryInfo = pEntryPoints3->VRDEQueryInfo;
+ mEntryPoints.VRDERedirect = pEntryPoints3->VRDERedirect;
+ mEntryPoints.VRDEAudioInOpen = pEntryPoints3->VRDEAudioInOpen;
+ mEntryPoints.VRDEAudioInClose = pEntryPoints3->VRDEAudioInClose;
+ mEntryPoints.VRDEGetInterface = NULL;
+ mpEntryPoints = &mEntryPoints;
+ }
+ else if (vrc == VERR_VERSION_MISMATCH)
+ {
+ /* An older version of VRDE is installed, try version 1. */
+ VRDEENTRYPOINTS_1 *pEntryPoints1;
+
+ static VRDECALLBACKS_1 sCallbacks1 =
+ {
+ { VRDE_INTERFACE_VERSION_1, sizeof(VRDECALLBACKS_1) },
+ ConsoleVRDPServer::VRDPCallbackQueryProperty,
+ ConsoleVRDPServer::VRDPCallbackClientLogon,
+ ConsoleVRDPServer::VRDPCallbackClientConnect,
+ ConsoleVRDPServer::VRDPCallbackClientDisconnect,
+ ConsoleVRDPServer::VRDPCallbackIntercept,
+ ConsoleVRDPServer::VRDPCallbackUSB,
+ ConsoleVRDPServer::VRDPCallbackClipboard,
+ ConsoleVRDPServer::VRDPCallbackFramebufferQuery,
+ ConsoleVRDPServer::VRDPCallbackFramebufferLock,
+ ConsoleVRDPServer::VRDPCallbackFramebufferUnlock,
+ ConsoleVRDPServer::VRDPCallbackInput,
+ ConsoleVRDPServer::VRDPCallbackVideoModeHint
+ };
+
+ vrc = mpfnVRDECreateServer(&sCallbacks1.header, this, (VRDEINTERFACEHDR **)&pEntryPoints1, &mhServer);
+ if (RT_SUCCESS(vrc))
+ {
+ mServerInterfaceVersion = 1;
+ mEntryPoints.header = pEntryPoints1->header;
+ mEntryPoints.VRDEDestroy = pEntryPoints1->VRDEDestroy;
+ mEntryPoints.VRDEEnableConnections = pEntryPoints1->VRDEEnableConnections;
+ mEntryPoints.VRDEDisconnect = pEntryPoints1->VRDEDisconnect;
+ mEntryPoints.VRDEResize = pEntryPoints1->VRDEResize;
+ mEntryPoints.VRDEUpdate = pEntryPoints1->VRDEUpdate;
+ mEntryPoints.VRDEColorPointer = pEntryPoints1->VRDEColorPointer;
+ mEntryPoints.VRDEHidePointer = pEntryPoints1->VRDEHidePointer;
+ mEntryPoints.VRDEAudioSamples = pEntryPoints1->VRDEAudioSamples;
+ mEntryPoints.VRDEAudioVolume = pEntryPoints1->VRDEAudioVolume;
+ mEntryPoints.VRDEUSBRequest = pEntryPoints1->VRDEUSBRequest;
+ mEntryPoints.VRDEClipboard = pEntryPoints1->VRDEClipboard;
+ mEntryPoints.VRDEQueryInfo = pEntryPoints1->VRDEQueryInfo;
+ mEntryPoints.VRDERedirect = NULL;
+ mEntryPoints.VRDEAudioInOpen = NULL;
+ mEntryPoints.VRDEAudioInClose = NULL;
+ mEntryPoints.VRDEGetInterface = NULL;
+ mpEntryPoints = &mEntryPoints;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: loaded version %d of the server.\n", mServerInterfaceVersion));
+
+ if (mServerInterfaceVersion >= 4)
+ {
+ /* The server supports optional interfaces. */
+ Assert(mpEntryPoints->VRDEGetInterface != NULL);
+
+ /* Image interface. */
+ m_interfaceImage.header.u64Version = 1;
+ m_interfaceImage.header.u64Size = sizeof(m_interfaceImage);
+
+ m_interfaceCallbacksImage.header.u64Version = 1;
+ m_interfaceCallbacksImage.header.u64Size = sizeof(m_interfaceCallbacksImage);
+ m_interfaceCallbacksImage.VRDEImageCbNotify = VRDEImageCbNotify;
+
+ vrc = mpEntryPoints->VRDEGetInterface(mhServer,
+ VRDE_IMAGE_INTERFACE_NAME,
+ &m_interfaceImage.header,
+ &m_interfaceCallbacksImage.header,
+ this);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: [%s]\n", VRDE_IMAGE_INTERFACE_NAME));
+ m_fInterfaceImage = true;
+ }
+
+ /* Mouse pointer interface. */
+ m_interfaceMousePtr.header.u64Version = 1;
+ m_interfaceMousePtr.header.u64Size = sizeof(m_interfaceMousePtr);
+
+ vrc = mpEntryPoints->VRDEGetInterface(mhServer,
+ VRDE_MOUSEPTR_INTERFACE_NAME,
+ &m_interfaceMousePtr.header,
+ NULL,
+ this);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: [%s]\n", VRDE_MOUSEPTR_INTERFACE_NAME));
+ }
+ else
+ {
+ RT_ZERO(m_interfaceMousePtr);
+ }
+
+ /* Smartcard interface. */
+ m_interfaceSCard.header.u64Version = 1;
+ m_interfaceSCard.header.u64Size = sizeof(m_interfaceSCard);
+
+ m_interfaceCallbacksSCard.header.u64Version = 1;
+ m_interfaceCallbacksSCard.header.u64Size = sizeof(m_interfaceCallbacksSCard);
+ m_interfaceCallbacksSCard.VRDESCardCbNotify = VRDESCardCbNotify;
+ m_interfaceCallbacksSCard.VRDESCardCbResponse = VRDESCardCbResponse;
+
+ vrc = mpEntryPoints->VRDEGetInterface(mhServer,
+ VRDE_SCARD_INTERFACE_NAME,
+ &m_interfaceSCard.header,
+ &m_interfaceCallbacksSCard.header,
+ this);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: [%s]\n", VRDE_SCARD_INTERFACE_NAME));
+ }
+ else
+ {
+ RT_ZERO(m_interfaceSCard);
+ }
+
+ /* Raw TSMF interface. */
+ m_interfaceTSMF.header.u64Version = 1;
+ m_interfaceTSMF.header.u64Size = sizeof(m_interfaceTSMF);
+
+ m_interfaceCallbacksTSMF.header.u64Version = 1;
+ m_interfaceCallbacksTSMF.header.u64Size = sizeof(m_interfaceCallbacksTSMF);
+ m_interfaceCallbacksTSMF.VRDETSMFCbNotify = VRDETSMFCbNotify;
+
+ vrc = mpEntryPoints->VRDEGetInterface(mhServer,
+ VRDE_TSMF_INTERFACE_NAME,
+ &m_interfaceTSMF.header,
+ &m_interfaceCallbacksTSMF.header,
+ this);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: [%s]\n", VRDE_TSMF_INTERFACE_NAME));
+ }
+ else
+ {
+ RT_ZERO(m_interfaceTSMF);
+ }
+
+ /* VideoIn interface. */
+ m_interfaceVideoIn.header.u64Version = 1;
+ m_interfaceVideoIn.header.u64Size = sizeof(m_interfaceVideoIn);
+
+ m_interfaceCallbacksVideoIn.header.u64Version = 1;
+ m_interfaceCallbacksVideoIn.header.u64Size = sizeof(m_interfaceCallbacksVideoIn);
+ m_interfaceCallbacksVideoIn.VRDECallbackVideoInNotify = VRDECallbackVideoInNotify;
+ m_interfaceCallbacksVideoIn.VRDECallbackVideoInDeviceDesc = VRDECallbackVideoInDeviceDesc;
+ m_interfaceCallbacksVideoIn.VRDECallbackVideoInControl = VRDECallbackVideoInControl;
+ m_interfaceCallbacksVideoIn.VRDECallbackVideoInFrame = VRDECallbackVideoInFrame;
+
+ vrc = mpEntryPoints->VRDEGetInterface(mhServer,
+ VRDE_VIDEOIN_INTERFACE_NAME,
+ &m_interfaceVideoIn.header,
+ &m_interfaceCallbacksVideoIn.header,
+ this);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: [%s]\n", VRDE_VIDEOIN_INTERFACE_NAME));
+ }
+ else
+ {
+ RT_ZERO(m_interfaceVideoIn);
+ }
+
+ /* Input interface. */
+ m_interfaceInput.header.u64Version = 1;
+ m_interfaceInput.header.u64Size = sizeof(m_interfaceInput);
+
+ m_interfaceCallbacksInput.header.u64Version = 1;
+ m_interfaceCallbacksInput.header.u64Size = sizeof(m_interfaceCallbacksInput);
+ m_interfaceCallbacksInput.VRDECallbackInputSetup = VRDECallbackInputSetup;
+ m_interfaceCallbacksInput.VRDECallbackInputEvent = VRDECallbackInputEvent;
+
+ vrc = mpEntryPoints->VRDEGetInterface(mhServer,
+ VRDE_INPUT_INTERFACE_NAME,
+ &m_interfaceInput.header,
+ &m_interfaceCallbacksInput.header,
+ this);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: [%s]\n", VRDE_INPUT_INTERFACE_NAME));
+ }
+ else
+ {
+ RT_ZERO(m_interfaceInput);
+ }
+
+ /* Since these interfaces are optional, it is always a success here. */
+ vrc = VINF_SUCCESS;
+ }
+#ifdef VBOX_WITH_USB
+ remoteUSBThreadStart();
+#endif
+
+ /*
+ * Re-init the server current state, which is usually obtained from events.
+ */
+ fetchCurrentState();
+ }
+ else
+ {
+ if (vrc != VERR_NET_ADDRESS_IN_USE)
+ LogRel(("VRDE: Could not start the server rc = %Rrc\n", vrc));
+ /* Don't unload the lib, because it prevents us trying again or
+ because there may be other users? */
+ }
+ }
+ }
+
+ return vrc;
+}
+
+void ConsoleVRDPServer::fetchCurrentState(void)
+{
+ ComPtr<IMousePointerShape> mps;
+ mConsole->i_getMouse()->COMGETTER(PointerShape)(mps.asOutParam());
+ if (!mps.isNull())
+ {
+ BOOL visible, alpha;
+ ULONG hotX, hotY, width, height;
+ com::SafeArray <BYTE> shape;
+
+ mps->COMGETTER(Visible)(&visible);
+ mps->COMGETTER(Alpha)(&alpha);
+ mps->COMGETTER(HotX)(&hotX);
+ mps->COMGETTER(HotY)(&hotY);
+ mps->COMGETTER(Width)(&width);
+ mps->COMGETTER(Height)(&height);
+ mps->COMGETTER(Shape)(ComSafeArrayAsOutParam(shape));
+
+ onMousePointerShapeChange(visible, alpha, hotX, hotY, width, height, ComSafeArrayAsInParam(shape));
+ }
+}
+
+#if 0 /** @todo Chromium got removed (see @bugref{9529}) and this is not available for VMSVGA yet. */
+typedef struct H3DORInstance
+{
+ ConsoleVRDPServer *pThis;
+ HVRDEIMAGE hImageBitmap;
+ int32_t x;
+ int32_t y;
+ uint32_t w;
+ uint32_t h;
+ bool fCreated;
+ bool fFallback;
+ bool fTopDown;
+} H3DORInstance;
+
+#define H3DORLOG Log
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DORBegin(const void *pvContext, void **ppvInstance,
+ const char *pszFormat)
+{
+ H3DORLOG(("H3DORBegin: ctx %p [%s]\n", pvContext, pszFormat));
+
+ H3DORInstance *p = (H3DORInstance *)RTMemAlloc(sizeof(H3DORInstance));
+
+ if (p)
+ {
+ p->pThis = (ConsoleVRDPServer *)pvContext;
+ p->hImageBitmap = NULL;
+ p->x = 0;
+ p->y = 0;
+ p->w = 0;
+ p->h = 0;
+ p->fCreated = false;
+ p->fFallback = false;
+
+ /* Host 3D service passes the actual format of data in this redirect instance.
+ * That is what will be in the H3DORFrame's parameters pvData and cbData.
+ */
+ if (RTStrICmp(pszFormat, H3DOR_FMT_RGBA_TOPDOWN) == 0)
+ {
+ /* Accept it. */
+ p->fTopDown = true;
+ }
+ else if (RTStrICmp(pszFormat, H3DOR_FMT_RGBA) == 0)
+ {
+ /* Accept it. */
+ p->fTopDown = false;
+ }
+ else
+ {
+ RTMemFree(p);
+ p = NULL;
+ }
+ }
+
+ H3DORLOG(("H3DORBegin: ins %p\n", p));
+
+ /* Caller checks this for NULL. */
+ *ppvInstance = p;
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DORGeometry(void *pvInstance,
+ int32_t x, int32_t y, uint32_t w, uint32_t h)
+{
+ H3DORLOG(("H3DORGeometry: ins %p %d,%d %dx%d\n", pvInstance, x, y, w, h));
+
+ H3DORInstance *p = (H3DORInstance *)pvInstance;
+ AssertPtrReturnVoid(p);
+ AssertPtrReturnVoid(p->pThis);
+
+ /** @todo find out what to do if size changes to 0x0 from non zero */
+ if (w == 0 || h == 0)
+ {
+ /* Do nothing. */
+ return;
+ }
+
+ RTRECT rect;
+ rect.xLeft = x;
+ rect.yTop = y;
+ rect.xRight = x + w;
+ rect.yBottom = y + h;
+
+ if (p->hImageBitmap)
+ {
+ /* An image handle has been already created,
+ * check if it has the same size as the reported geometry.
+ */
+ if ( p->x == x
+ && p->y == y
+ && p->w == w
+ && p->h == h)
+ {
+ H3DORLOG(("H3DORGeometry: geometry not changed\n"));
+ /* Do nothing. Continue using the existing handle. */
+ }
+ else
+ {
+ int vrc = p->fFallback?
+ VERR_NOT_SUPPORTED: /* Try to go out of fallback mode. */
+ p->pThis->m_interfaceImage.VRDEImageGeometrySet(p->hImageBitmap, &rect);
+ if (RT_SUCCESS(rc))
+ {
+ p->x = x;
+ p->y = y;
+ p->w = w;
+ p->h = h;
+ }
+ else
+ {
+ /* The handle must be recreated. Delete existing handle here. */
+ p->pThis->m_interfaceImage.VRDEImageHandleClose(p->hImageBitmap);
+ p->hImageBitmap = NULL;
+ }
+ }
+ }
+
+ if (!p->hImageBitmap)
+ {
+ /* Create a new bitmap handle. */
+ uint32_t u32ScreenId = 0; /** @todo clip to corresponding screens.
+ * Clipping can be done here or in VRDP server.
+ * If VRDP does clipping, then uScreenId parameter
+ * is not necessary and coords must be global.
+ * (have to check which coords are used in opengl service).
+ * Since all VRDE API uses a ScreenId,
+ * the clipping must be done here in ConsoleVRDPServer
+ */
+ uint32_t fu32CompletionFlags = 0;
+ p->fFallback = false;
+ int vrc = p->pThis->m_interfaceImage.VRDEImageHandleCreate(p->pThis->mhServer,
+ &p->hImageBitmap,
+ p,
+ u32ScreenId,
+ VRDE_IMAGE_F_CREATE_CONTENT_3D
+ | VRDE_IMAGE_F_CREATE_WINDOW,
+ &rect,
+ VRDE_IMAGE_FMT_ID_BITMAP_BGRA8,
+ NULL,
+ 0,
+ &fu32CompletionFlags);
+ if (RT_FAILURE(rc))
+ {
+ /* No support for a 3D + WINDOW. Try bitmap updates. */
+ H3DORLOG(("H3DORGeometry: Fallback to bitmaps\n"));
+ fu32CompletionFlags = 0;
+ p->fFallback = true;
+ vrc = p->pThis->m_interfaceImage.VRDEImageHandleCreate(p->pThis->mhServer,
+ &p->hImageBitmap,
+ p,
+ u32ScreenId,
+ 0,
+ &rect,
+ VRDE_IMAGE_FMT_ID_BITMAP_BGRA8,
+ NULL,
+ 0,
+ &fu32CompletionFlags);
+ }
+
+ H3DORLOG(("H3DORGeometry: Image handle create %Rrc, flags 0x%RX32\n", rc, fu32CompletionFlags));
+
+ if (RT_SUCCESS(vrc))
+ {
+ p->x = x;
+ p->y = y;
+ p->w = w;
+ p->h = h;
+
+ if ((fu32CompletionFlags & VRDE_IMAGE_F_COMPLETE_ASYNC) == 0)
+ {
+ p->fCreated = true;
+ }
+ }
+ else
+ {
+ p->hImageBitmap = NULL;
+ p->w = 0;
+ p->h = 0;
+ }
+ }
+
+ H3DORLOG(("H3DORGeometry: ins %p completed\n", pvInstance));
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DORVisibleRegion(void *pvInstance,
+ uint32_t cRects, const RTRECT *paRects)
+{
+ H3DORLOG(("H3DORVisibleRegion: ins %p %d\n", pvInstance, cRects));
+
+ H3DORInstance *p = (H3DORInstance *)pvInstance;
+ AssertPtrReturnVoid(p);
+ AssertPtrReturnVoid(p->pThis);
+
+ if (cRects == 0)
+ {
+ /* Complete image is visible. */
+ RTRECT rect;
+ rect.xLeft = p->x;
+ rect.yTop = p->y;
+ rect.xRight = p->x + p->w;
+ rect.yBottom = p->y + p->h;
+ p->pThis->m_interfaceImage.VRDEImageRegionSet (p->hImageBitmap,
+ 1,
+ &rect);
+ }
+ else
+ {
+ p->pThis->m_interfaceImage.VRDEImageRegionSet (p->hImageBitmap,
+ cRects,
+ paRects);
+ }
+
+ H3DORLOG(("H3DORVisibleRegion: ins %p completed\n", pvInstance));
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DORFrame(void *pvInstance,
+ void *pvData, uint32_t cbData)
+{
+ H3DORLOG(("H3DORFrame: ins %p %p %d\n", pvInstance, pvData, cbData));
+
+ H3DORInstance *p = (H3DORInstance *)pvInstance;
+ AssertPtrReturnVoid(p);
+ AssertPtrReturnVoid(p->pThis);
+
+ /* Currently only a topdown BGR0 bitmap format is supported. */
+ VRDEIMAGEBITMAP image;
+
+ image.cWidth = p->w;
+ image.cHeight = p->h;
+ image.pvData = pvData;
+ image.cbData = cbData;
+ image.pvScanLine0 = (uint8_t *)pvData + (p->h - 1) * p->w * 4;
+ image.iScanDelta = 4 * p->w;
+ if (p->fTopDown)
+ {
+ image.iScanDelta = -image.iScanDelta;
+ }
+
+ p->pThis->m_interfaceImage.VRDEImageUpdate (p->hImageBitmap,
+ p->x,
+ p->y,
+ p->w,
+ p->h,
+ &image,
+ sizeof(VRDEIMAGEBITMAP));
+
+ H3DORLOG(("H3DORFrame: ins %p completed\n", pvInstance));
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DOREnd(void *pvInstance)
+{
+ H3DORLOG(("H3DOREnd: ins %p\n", pvInstance));
+
+ H3DORInstance *p = (H3DORInstance *)pvInstance;
+ AssertPtrReturnVoid(p);
+ AssertPtrReturnVoid(p->pThis);
+
+ p->pThis->m_interfaceImage.VRDEImageHandleClose(p->hImageBitmap);
+
+ RT_ZERO(*p);
+ RTMemFree(p);
+
+ H3DORLOG(("H3DOREnd: ins %p completed\n", pvInstance));
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::H3DORContextProperty(const void *pvContext, uint32_t index,
+ void *pvBuffer, uint32_t cbBuffer, uint32_t *pcbOut)
+{
+ RT_NOREF(pvContext, pvBuffer);
+ int vrc = VINF_SUCCESS;
+
+ H3DORLOG(("H3DORContextProperty: index %d\n", index));
+
+ if (index == H3DOR_PROP_FORMATS)
+ {
+ /* Return a comma separated list of supported formats. */
+ uint32_t cbOut = (uint32_t)strlen(H3DOR_FMT_RGBA_TOPDOWN) + 1
+ + (uint32_t)strlen(H3DOR_FMT_RGBA) + 1;
+ if (cbOut <= cbBuffer)
+ {
+ char *pch = (char *)pvBuffer;
+ memcpy(pch, H3DOR_FMT_RGBA_TOPDOWN, strlen(H3DOR_FMT_RGBA_TOPDOWN));
+ pch += strlen(H3DOR_FMT_RGBA_TOPDOWN);
+ *pch++ = ',';
+ memcpy(pch, H3DOR_FMT_RGBA, strlen(H3DOR_FMT_RGBA));
+ pch += strlen(H3DOR_FMT_RGBA);
+ *pch++ = '\0';
+ }
+ else
+ {
+ vrc = VERR_BUFFER_OVERFLOW;
+ }
+ *pcbOut = cbOut;
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ H3DORLOG(("H3DORContextProperty: %Rrc\n", vrc));
+ return vrc;
+}
+#endif
+
+void ConsoleVRDPServer::remote3DRedirect(bool fEnable)
+{
+ if (!m_fInterfaceImage)
+ {
+ /* No redirect without corresponding interface. */
+ return;
+ }
+
+ /* Check if 3D redirection has been enabled. It is enabled by default. */
+ com::Bstr bstr;
+ HRESULT hrc = mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("H3DRedirect/Enabled").raw(), bstr.asOutParam());
+
+ com::Utf8Str value = hrc == S_OK? bstr: "";
+
+ bool fAllowed = RTStrICmp(value.c_str(), "true") == 0
+ || RTStrICmp(value.c_str(), "1") == 0
+ || value.c_str()[0] == 0;
+
+ if (!fAllowed && fEnable)
+ {
+ return;
+ }
+
+#if 0 /** @todo Implement again for VMSVGA. */
+ /* Tell the host 3D service to redirect output using the ConsoleVRDPServer callbacks. */
+ H3DOUTPUTREDIRECT outputRedirect =
+ {
+ this,
+ H3DORBegin,
+ H3DORGeometry,
+ H3DORVisibleRegion,
+ H3DORFrame,
+ H3DOREnd,
+ H3DORContextProperty
+ };
+
+ if (!fEnable)
+ {
+ /* This will tell the service to disable rediection. */
+ RT_ZERO(outputRedirect);
+ }
+#endif
+
+ return;
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::VRDEImageCbNotify (void *pvContext,
+ void *pvUser,
+ HVRDEIMAGE hVideo,
+ uint32_t u32Id,
+ void *pvData,
+ uint32_t cbData)
+{
+ RT_NOREF(hVideo);
+ Log(("H3DOR: VRDEImageCbNotify: pvContext %p, pvUser %p, hVideo %p, u32Id %u, pvData %p, cbData %d\n",
+ pvContext, pvUser, hVideo, u32Id, pvData, cbData));
+
+ ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvContext); NOREF(pServer);
+
+#if 0 /** @todo Implement again for VMSVGA. */
+ H3DORInstance *p = (H3DORInstance *)pvUser;
+ Assert(p);
+ Assert(p->pThis);
+ Assert(p->pThis == pServer);
+
+ if (u32Id == VRDE_IMAGE_NOTIFY_HANDLE_CREATE)
+ {
+ if (cbData != sizeof(uint32_t))
+ {
+ AssertFailed();
+ return VERR_INVALID_PARAMETER;
+ }
+
+ uint32_t u32StreamId = *(uint32_t *)pvData;
+ Log(("H3DOR: VRDE_IMAGE_NOTIFY_HANDLE_CREATE u32StreamId %d\n",
+ u32StreamId));
+
+ if (u32StreamId != 0)
+ {
+ p->fCreated = true; /// @todo not needed?
+ }
+ else
+ {
+ /* The stream has not been created. */
+ }
+ }
+#else
+ RT_NOREF(pvUser, u32Id, pvData, cbData);
+#endif
+
+ return VINF_SUCCESS;
+}
+
+#undef H3DORLOG
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::VRDESCardCbNotify(void *pvContext,
+ uint32_t u32Id,
+ void *pvData,
+ uint32_t cbData)
+{
+#ifdef VBOX_WITH_USB_CARDREADER
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvContext);
+ UsbCardReader *pReader = pThis->mConsole->i_getUsbCardReader();
+ return pReader->VRDENotify(u32Id, pvData, cbData);
+#else
+ NOREF(pvContext);
+ NOREF(u32Id);
+ NOREF(pvData);
+ NOREF(cbData);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::VRDESCardCbResponse(void *pvContext,
+ int vrcRequest,
+ void *pvUser,
+ uint32_t u32Function,
+ void *pvData,
+ uint32_t cbData)
+{
+#ifdef VBOX_WITH_USB_CARDREADER
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvContext);
+ UsbCardReader *pReader = pThis->mConsole->i_getUsbCardReader();
+ return pReader->VRDEResponse(vrcRequest, pvUser, u32Function, pvData, cbData);
+#else
+ NOREF(pvContext);
+ NOREF(vrcRequest);
+ NOREF(pvUser);
+ NOREF(u32Function);
+ NOREF(pvData);
+ NOREF(cbData);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+int ConsoleVRDPServer::SCardRequest(void *pvUser, uint32_t u32Function, const void *pvData, uint32_t cbData)
+{
+ int vrc = VINF_SUCCESS;
+
+ if (mhServer && mpEntryPoints && m_interfaceSCard.VRDESCardRequest)
+ {
+ vrc = m_interfaceSCard.VRDESCardRequest(mhServer, pvUser, u32Function, pvData, cbData);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+
+struct TSMFHOSTCHCTX;
+struct TSMFVRDPCTX;
+
+typedef struct TSMFHOSTCHCTX
+{
+ ConsoleVRDPServer *pThis;
+
+ struct TSMFVRDPCTX *pVRDPCtx; /* NULL if no corresponding host channel context. */
+
+ void *pvDataReceived;
+ uint32_t cbDataReceived;
+ uint32_t cbDataAllocated;
+} TSMFHOSTCHCTX;
+
+typedef struct TSMFVRDPCTX
+{
+ ConsoleVRDPServer *pThis;
+
+ VBOXHOSTCHANNELCALLBACKS *pCallbacks;
+ void *pvCallbacks;
+
+ TSMFHOSTCHCTX *pHostChCtx; /* NULL if no corresponding host channel context. */
+
+ uint32_t u32ChannelHandle;
+} TSMFVRDPCTX;
+
+static int tsmfContextsAlloc(TSMFHOSTCHCTX **ppHostChCtx, TSMFVRDPCTX **ppVRDPCtx)
+{
+ TSMFHOSTCHCTX *pHostChCtx = (TSMFHOSTCHCTX *)RTMemAllocZ(sizeof(TSMFHOSTCHCTX));
+ if (!pHostChCtx)
+ {
+ return VERR_NO_MEMORY;
+ }
+
+ TSMFVRDPCTX *pVRDPCtx = (TSMFVRDPCTX *)RTMemAllocZ(sizeof(TSMFVRDPCTX));
+ if (!pVRDPCtx)
+ {
+ RTMemFree(pHostChCtx);
+ return VERR_NO_MEMORY;
+ }
+
+ *ppHostChCtx = pHostChCtx;
+ *ppVRDPCtx = pVRDPCtx;
+ return VINF_SUCCESS;
+}
+
+int ConsoleVRDPServer::tsmfLock(void)
+{
+ int vrc = RTCritSectEnter(&mTSMFLock);
+ AssertRC(vrc);
+ return vrc;
+}
+
+void ConsoleVRDPServer::tsmfUnlock(void)
+{
+ RTCritSectLeave(&mTSMFLock);
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::tsmfHostChannelAttach(void *pvProvider,
+ void **ppvChannel,
+ uint32_t u32Flags,
+ VBOXHOSTCHANNELCALLBACKS *pCallbacks,
+ void *pvCallbacks)
+{
+ LogFlowFunc(("\n"));
+
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvProvider);
+
+ /* Create 2 context structures: for the VRDP server and for the host service. */
+ TSMFHOSTCHCTX *pHostChCtx = NULL;
+ TSMFVRDPCTX *pVRDPCtx = NULL;
+
+ int vrc = tsmfContextsAlloc(&pHostChCtx, &pVRDPCtx);
+ if (RT_FAILURE(vrc))
+ {
+ return vrc;
+ }
+
+ pHostChCtx->pThis = pThis;
+ pHostChCtx->pVRDPCtx = pVRDPCtx;
+
+ pVRDPCtx->pThis = pThis;
+ pVRDPCtx->pCallbacks = pCallbacks;
+ pVRDPCtx->pvCallbacks = pvCallbacks;
+ pVRDPCtx->pHostChCtx = pHostChCtx;
+
+ vrc = pThis->m_interfaceTSMF.VRDETSMFChannelCreate(pThis->mhServer, pVRDPCtx, u32Flags);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /** @todo contexts should be in a list for accounting. */
+ *ppvChannel = pHostChCtx;
+ }
+ else
+ {
+ RTMemFree(pHostChCtx);
+ RTMemFree(pVRDPCtx);
+ }
+
+ return vrc;
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::tsmfHostChannelDetach(void *pvChannel)
+{
+ LogFlowFunc(("\n"));
+
+ TSMFHOSTCHCTX *pHostChCtx = (TSMFHOSTCHCTX *)pvChannel;
+ ConsoleVRDPServer *pThis = pHostChCtx->pThis;
+
+ int vrc = pThis->tsmfLock();
+ if (RT_SUCCESS(vrc))
+ {
+ bool fClose = false;
+ uint32_t u32ChannelHandle = 0;
+
+ if (pHostChCtx->pVRDPCtx)
+ {
+ /* There is still a VRDP context for this channel. */
+ pHostChCtx->pVRDPCtx->pHostChCtx = NULL;
+ u32ChannelHandle = pHostChCtx->pVRDPCtx->u32ChannelHandle;
+ fClose = true;
+ }
+
+ pThis->tsmfUnlock();
+
+ RTMemFree(pHostChCtx);
+
+ if (fClose)
+ {
+ LogFlowFunc(("Closing VRDE channel %d.\n", u32ChannelHandle));
+ pThis->m_interfaceTSMF.VRDETSMFChannelClose(pThis->mhServer, u32ChannelHandle);
+ }
+ else
+ {
+ LogFlowFunc(("No VRDE channel.\n"));
+ }
+ }
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::tsmfHostChannelSend(void *pvChannel,
+ const void *pvData,
+ uint32_t cbData)
+{
+ LogFlowFunc(("cbData %d\n", cbData));
+
+ TSMFHOSTCHCTX *pHostChCtx = (TSMFHOSTCHCTX *)pvChannel;
+ ConsoleVRDPServer *pThis = pHostChCtx->pThis;
+
+ int vrc = pThis->tsmfLock();
+ if (RT_SUCCESS(vrc))
+ {
+ bool fSend = false;
+ uint32_t u32ChannelHandle = 0;
+
+ if (pHostChCtx->pVRDPCtx)
+ {
+ u32ChannelHandle = pHostChCtx->pVRDPCtx->u32ChannelHandle;
+ fSend = true;
+ }
+
+ pThis->tsmfUnlock();
+
+ if (fSend)
+ {
+ LogFlowFunc(("Send to VRDE channel %d.\n", u32ChannelHandle));
+ vrc = pThis->m_interfaceTSMF.VRDETSMFChannelSend(pThis->mhServer, u32ChannelHandle,
+ pvData, cbData);
+ }
+ }
+
+ return vrc;
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::tsmfHostChannelRecv(void *pvChannel,
+ void *pvData,
+ uint32_t cbData,
+ uint32_t *pcbReceived,
+ uint32_t *pcbRemaining)
+{
+ LogFlowFunc(("cbData %d\n", cbData));
+
+ TSMFHOSTCHCTX *pHostChCtx = (TSMFHOSTCHCTX *)pvChannel;
+ ConsoleVRDPServer *pThis = pHostChCtx->pThis;
+
+ int vrc = pThis->tsmfLock();
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t cbToCopy = RT_MIN(cbData, pHostChCtx->cbDataReceived);
+ uint32_t cbRemaining = pHostChCtx->cbDataReceived - cbToCopy;
+
+ LogFlowFunc(("cbToCopy %d, cbRemaining %d\n", cbToCopy, cbRemaining));
+
+ if (cbToCopy != 0)
+ {
+ memcpy(pvData, pHostChCtx->pvDataReceived, cbToCopy);
+
+ if (cbRemaining != 0)
+ {
+ memmove(pHostChCtx->pvDataReceived, (uint8_t *)pHostChCtx->pvDataReceived + cbToCopy, cbRemaining);
+ }
+
+ pHostChCtx->cbDataReceived = cbRemaining;
+ }
+
+ pThis->tsmfUnlock();
+
+ *pcbRemaining = cbRemaining;
+ *pcbReceived = cbToCopy;
+ }
+
+ return vrc;
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::tsmfHostChannelControl(void *pvChannel,
+ uint32_t u32Code,
+ const void *pvParm,
+ uint32_t cbParm,
+ const void *pvData,
+ uint32_t cbData,
+ uint32_t *pcbDataReturned)
+{
+ RT_NOREF(pvParm, cbParm, pvData, cbData);
+ LogFlowFunc(("u32Code %u\n", u32Code));
+
+ if (!pvChannel)
+ {
+ /* Special case, the provider must answer rather than a channel instance. */
+ if (u32Code == VBOX_HOST_CHANNEL_CTRL_EXISTS)
+ {
+ *pcbDataReturned = 0;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_NOT_IMPLEMENTED;
+ }
+
+ /* Channels do not support this. */
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+void ConsoleVRDPServer::setupTSMF(void)
+{
+ if (m_interfaceTSMF.header.u64Size == 0)
+ {
+ return;
+ }
+
+ /* Register with the host channel service. */
+ VBOXHOSTCHANNELINTERFACE hostChannelInterface =
+ {
+ this,
+ tsmfHostChannelAttach,
+ tsmfHostChannelDetach,
+ tsmfHostChannelSend,
+ tsmfHostChannelRecv,
+ tsmfHostChannelControl
+ };
+
+ VBoxHostChannelHostRegister parms;
+
+ static char szProviderName[] = "/vrde/tsmf";
+
+ parms.name.type = VBOX_HGCM_SVC_PARM_PTR;
+ parms.name.u.pointer.addr = &szProviderName[0];
+ parms.name.u.pointer.size = sizeof(szProviderName);
+
+ parms.iface.type = VBOX_HGCM_SVC_PARM_PTR;
+ parms.iface.u.pointer.addr = &hostChannelInterface;
+ parms.iface.u.pointer.size = sizeof(hostChannelInterface);
+
+ VMMDev *pVMMDev = mConsole->i_getVMMDev();
+
+ if (!pVMMDev)
+ {
+ AssertMsgFailed(("setupTSMF no vmmdev\n"));
+ return;
+ }
+
+ int vrc = pVMMDev->hgcmHostCall("VBoxHostChannel",
+ VBOX_HOST_CHANNEL_HOST_FN_REGISTER,
+ 2,
+ &parms.name);
+
+ if (!RT_SUCCESS(vrc))
+ {
+ Log(("VBOX_HOST_CHANNEL_HOST_FN_REGISTER failed with %Rrc\n", vrc));
+ return;
+ }
+
+ LogRel(("VRDE: Enabled TSMF channel.\n"));
+
+ return;
+}
+
+/** @todo these defines must be in a header, which is used by guest component as well. */
+#define VBOX_TSMF_HCH_CREATE_ACCEPTED (VBOX_HOST_CHANNEL_EVENT_USER + 0)
+#define VBOX_TSMF_HCH_CREATE_DECLINED (VBOX_HOST_CHANNEL_EVENT_USER + 1)
+#define VBOX_TSMF_HCH_DISCONNECTED (VBOX_HOST_CHANNEL_EVENT_USER + 2)
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDETSMFCbNotify(void *pvContext,
+ uint32_t u32Notification,
+ void *pvChannel,
+ const void *pvParm,
+ uint32_t cbParm)
+{
+ RT_NOREF(cbParm);
+ int vrc = VINF_SUCCESS;
+
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvContext);
+
+ TSMFVRDPCTX *pVRDPCtx = (TSMFVRDPCTX *)pvChannel;
+
+ Assert(pVRDPCtx->pThis == pThis);
+
+ if (pVRDPCtx->pCallbacks == NULL)
+ {
+ LogFlowFunc(("tsmfHostChannel: Channel disconnected. Skipping.\n"));
+ return;
+ }
+
+ switch (u32Notification)
+ {
+ case VRDE_TSMF_N_CREATE_ACCEPTED:
+ {
+ VRDETSMFNOTIFYCREATEACCEPTED *p = (VRDETSMFNOTIFYCREATEACCEPTED *)pvParm;
+ Assert(cbParm == sizeof(VRDETSMFNOTIFYCREATEACCEPTED));
+
+ LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_CREATE_ACCEPTED(%p): p->u32ChannelHandle %d\n",
+ pVRDPCtx, p->u32ChannelHandle));
+
+ pVRDPCtx->u32ChannelHandle = p->u32ChannelHandle;
+
+ pVRDPCtx->pCallbacks->HostChannelCallbackEvent(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx,
+ VBOX_TSMF_HCH_CREATE_ACCEPTED,
+ NULL, 0);
+ } break;
+
+ case VRDE_TSMF_N_CREATE_DECLINED:
+ {
+ LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_CREATE_DECLINED(%p)\n", pVRDPCtx));
+
+ pVRDPCtx->pCallbacks->HostChannelCallbackEvent(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx,
+ VBOX_TSMF_HCH_CREATE_DECLINED,
+ NULL, 0);
+ } break;
+
+ case VRDE_TSMF_N_DATA:
+ {
+ /* Save the data in the intermediate buffer and send the event. */
+ VRDETSMFNOTIFYDATA *p = (VRDETSMFNOTIFYDATA *)pvParm;
+ Assert(cbParm == sizeof(VRDETSMFNOTIFYDATA));
+
+ LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_DATA(%p): p->cbData %d\n", pVRDPCtx, p->cbData));
+
+ VBOXHOSTCHANNELEVENTRECV ev;
+ ev.u32SizeAvailable = 0;
+
+ vrc = pThis->tsmfLock();
+
+ if (RT_SUCCESS(vrc))
+ {
+ TSMFHOSTCHCTX *pHostChCtx = pVRDPCtx->pHostChCtx;
+
+ if (pHostChCtx)
+ {
+ if (pHostChCtx->pvDataReceived)
+ {
+ uint32_t cbAlloc = p->cbData + pHostChCtx->cbDataReceived;
+ pHostChCtx->pvDataReceived = RTMemRealloc(pHostChCtx->pvDataReceived, cbAlloc);
+ memcpy((uint8_t *)pHostChCtx->pvDataReceived + pHostChCtx->cbDataReceived, p->pvData, p->cbData);
+
+ pHostChCtx->cbDataReceived += p->cbData;
+ pHostChCtx->cbDataAllocated = cbAlloc;
+ }
+ else
+ {
+ pHostChCtx->pvDataReceived = RTMemAlloc(p->cbData);
+ memcpy(pHostChCtx->pvDataReceived, p->pvData, p->cbData);
+
+ pHostChCtx->cbDataReceived = p->cbData;
+ pHostChCtx->cbDataAllocated = p->cbData;
+ }
+
+ ev.u32SizeAvailable = p->cbData;
+ }
+ else
+ {
+ LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_DATA: no host channel. Skipping\n"));
+ }
+
+ pThis->tsmfUnlock();
+ }
+
+ pVRDPCtx->pCallbacks->HostChannelCallbackEvent(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx,
+ VBOX_HOST_CHANNEL_EVENT_RECV,
+ &ev, sizeof(ev));
+ } break;
+
+ case VRDE_TSMF_N_DISCONNECTED:
+ {
+ LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_DISCONNECTED(%p)\n", pVRDPCtx));
+
+ pVRDPCtx->pCallbacks->HostChannelCallbackEvent(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx,
+ VBOX_TSMF_HCH_DISCONNECTED,
+ NULL, 0);
+
+ /* The callback context will not be used anymore. */
+ pVRDPCtx->pCallbacks->HostChannelCallbackDeleted(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx);
+ pVRDPCtx->pCallbacks = NULL;
+ pVRDPCtx->pvCallbacks = NULL;
+
+ vrc = pThis->tsmfLock();
+ if (RT_SUCCESS(vrc))
+ {
+ if (pVRDPCtx->pHostChCtx)
+ {
+ /* There is still a host channel context for this channel. */
+ pVRDPCtx->pHostChCtx->pVRDPCtx = NULL;
+ }
+
+ pThis->tsmfUnlock();
+
+ RT_ZERO(*pVRDPCtx);
+ RTMemFree(pVRDPCtx);
+ }
+ } break;
+
+ default:
+ {
+ AssertFailed();
+ } break;
+ }
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInNotify(void *pvCallback,
+ uint32_t u32Id,
+ const void *pvData,
+ uint32_t cbData)
+{
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback);
+ if (pThis->mEmWebcam)
+ {
+ pThis->mEmWebcam->EmWebcamCbNotify(u32Id, pvData, cbData);
+ }
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInDeviceDesc(void *pvCallback,
+ int vrcRequest,
+ void *pDeviceCtx,
+ void *pvUser,
+ const VRDEVIDEOINDEVICEDESC *pDeviceDesc,
+ uint32_t cbDevice)
+{
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback);
+ if (pThis->mEmWebcam)
+ {
+ pThis->mEmWebcam->EmWebcamCbDeviceDesc(vrcRequest, pDeviceCtx, pvUser, pDeviceDesc, cbDevice);
+ }
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInControl(void *pvCallback,
+ int vrcRequest,
+ void *pDeviceCtx,
+ void *pvUser,
+ const VRDEVIDEOINCTRLHDR *pControl,
+ uint32_t cbControl)
+{
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback);
+ if (pThis->mEmWebcam)
+ {
+ pThis->mEmWebcam->EmWebcamCbControl(vrcRequest, pDeviceCtx, pvUser, pControl, cbControl);
+ }
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInFrame(void *pvCallback,
+ int vrcRequest,
+ void *pDeviceCtx,
+ const VRDEVIDEOINPAYLOADHDR *pFrame,
+ uint32_t cbFrame)
+{
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback);
+ if (pThis->mEmWebcam)
+ {
+ pThis->mEmWebcam->EmWebcamCbFrame(vrcRequest, pDeviceCtx, pFrame, cbFrame);
+ }
+}
+
+int ConsoleVRDPServer::VideoInDeviceAttach(const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle, void *pvDeviceCtx)
+{
+ int vrc;
+
+ if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInDeviceAttach)
+ {
+ vrc = m_interfaceVideoIn.VRDEVideoInDeviceAttach(mhServer, pDeviceHandle, pvDeviceCtx);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+int ConsoleVRDPServer::VideoInDeviceDetach(const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle)
+{
+ int vrc;
+
+ if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInDeviceDetach)
+ {
+ vrc = m_interfaceVideoIn.VRDEVideoInDeviceDetach(mhServer, pDeviceHandle);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+int ConsoleVRDPServer::VideoInGetDeviceDesc(void *pvUser, const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle)
+{
+ int vrc;
+
+ if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInGetDeviceDesc)
+ {
+ vrc = m_interfaceVideoIn.VRDEVideoInGetDeviceDesc(mhServer, pvUser, pDeviceHandle);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+int ConsoleVRDPServer::VideoInControl(void *pvUser, const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle,
+ const VRDEVIDEOINCTRLHDR *pReq, uint32_t cbReq)
+{
+ int vrc;
+
+ if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInControl)
+ {
+ vrc = m_interfaceVideoIn.VRDEVideoInControl(mhServer, pvUser, pDeviceHandle, pReq, cbReq);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackInputSetup(void *pvCallback,
+ int vrcRequest,
+ uint32_t u32Method,
+ const void *pvResult,
+ uint32_t cbResult)
+{
+ NOREF(pvCallback);
+ NOREF(vrcRequest);
+ NOREF(u32Method);
+ NOREF(pvResult);
+ NOREF(cbResult);
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackInputEvent(void *pvCallback,
+ uint32_t u32Method,
+ const void *pvEvent,
+ uint32_t cbEvent)
+{
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback);
+
+ if (u32Method == VRDE_INPUT_METHOD_TOUCH)
+ {
+ if (cbEvent >= sizeof(VRDEINPUTHEADER))
+ {
+ VRDEINPUTHEADER *pHeader = (VRDEINPUTHEADER *)pvEvent;
+
+ if (pHeader->u16EventId == VRDEINPUT_EVENTID_TOUCH)
+ {
+ IMouse *pMouse = pThis->mConsole->i_getMouse();
+
+ VRDEINPUT_TOUCH_EVENT_PDU *p = (VRDEINPUT_TOUCH_EVENT_PDU *)pHeader;
+
+ uint16_t iFrame;
+ for (iFrame = 0; iFrame < p->u16FrameCount; iFrame++)
+ {
+ VRDEINPUT_TOUCH_FRAME *pFrame = &p->aFrames[iFrame];
+
+ com::SafeArray<LONG64> aContacts(pFrame->u16ContactCount);
+
+ uint16_t iContact;
+ for (iContact = 0; iContact < pFrame->u16ContactCount; iContact++)
+ {
+ VRDEINPUT_CONTACT_DATA *pContact = &pFrame->aContacts[iContact];
+
+ int16_t x = (int16_t)(pContact->i32X + 1);
+ int16_t y = (int16_t)(pContact->i32Y + 1);
+ uint8_t contactId = pContact->u8ContactId;
+ uint8_t contactState = TouchContactState_None;
+
+ if (pContact->u32ContactFlags & VRDEINPUT_CONTACT_FLAG_INRANGE)
+ {
+ contactState |= TouchContactState_InRange;
+ }
+ if (pContact->u32ContactFlags & VRDEINPUT_CONTACT_FLAG_INCONTACT)
+ {
+ contactState |= TouchContactState_InContact;
+ }
+
+ aContacts[iContact] = RT_MAKE_U64_FROM_U16((uint16_t)x,
+ (uint16_t)y,
+ RT_MAKE_U16(contactId, contactState),
+ 0);
+ }
+
+ if (pFrame->u64FrameOffset == 0)
+ {
+ pThis->mu64TouchInputTimestampMCS = 0;
+ }
+ else
+ {
+ pThis->mu64TouchInputTimestampMCS += pFrame->u64FrameOffset;
+ }
+
+ pMouse->PutEventMultiTouch(pFrame->u16ContactCount,
+ ComSafeArrayAsInParam(aContacts),
+ true /* isTouchScreen */,
+ (ULONG)(pThis->mu64TouchInputTimestampMCS / 1000)); /* Micro->milliseconds. */
+ }
+ }
+ else if (pHeader->u16EventId == VRDEINPUT_EVENTID_DISMISS_HOVERING_CONTACT)
+ {
+ /** @todo */
+ }
+ else
+ {
+ AssertMsgFailed(("EventId %d\n", pHeader->u16EventId));
+ }
+ }
+ }
+}
+
+
+void ConsoleVRDPServer::EnableConnections(void)
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEEnableConnections(mhServer, true);
+
+ /* Setup the generic TSMF channel. */
+ setupTSMF();
+ }
+}
+
+void ConsoleVRDPServer::DisconnectClient(uint32_t u32ClientId, bool fReconnect)
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEDisconnect(mhServer, u32ClientId, fReconnect);
+ }
+}
+
+int ConsoleVRDPServer::MousePointer(BOOL alpha,
+ ULONG xHot,
+ ULONG yHot,
+ ULONG width,
+ ULONG height,
+ const uint8_t *pu8Shape)
+{
+ int vrc = VINF_SUCCESS;
+
+ if (mhServer && mpEntryPoints && m_interfaceMousePtr.VRDEMousePtr)
+ {
+ size_t cbMask = (((width + 7) / 8) * height + 3) & ~3;
+ size_t cbData = width * height * 4;
+
+ size_t cbDstMask = alpha? 0: cbMask;
+
+ size_t cbPointer = sizeof(VRDEMOUSEPTRDATA) + cbDstMask + cbData;
+ uint8_t *pu8Pointer = (uint8_t *)RTMemAlloc(cbPointer);
+ if (pu8Pointer != NULL)
+ {
+ VRDEMOUSEPTRDATA *pPointer = (VRDEMOUSEPTRDATA *)pu8Pointer;
+
+ pPointer->u16HotX = (uint16_t)xHot;
+ pPointer->u16HotY = (uint16_t)yHot;
+ pPointer->u16Width = (uint16_t)width;
+ pPointer->u16Height = (uint16_t)height;
+ pPointer->u16MaskLen = (uint16_t)cbDstMask;
+ pPointer->u32DataLen = (uint32_t)cbData;
+
+ /* AND mask. */
+ uint8_t *pu8Mask = pu8Pointer + sizeof(VRDEMOUSEPTRDATA);
+ if (cbDstMask)
+ {
+ memcpy(pu8Mask, pu8Shape, cbDstMask);
+ }
+
+ /* XOR mask */
+ uint8_t *pu8Data = pu8Mask + pPointer->u16MaskLen;
+ memcpy(pu8Data, pu8Shape + cbMask, cbData);
+
+ m_interfaceMousePtr.VRDEMousePtr(mhServer, pPointer);
+
+ RTMemFree(pu8Pointer);
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+void ConsoleVRDPServer::MousePointerUpdate(const VRDECOLORPOINTER *pPointer)
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEColorPointer(mhServer, pPointer);
+ }
+}
+
+void ConsoleVRDPServer::MousePointerHide(void)
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEHidePointer(mhServer);
+ }
+}
+
+void ConsoleVRDPServer::Stop(void)
+{
+ AssertPtr(this); /** @todo r=bird: there are(/was) some odd cases where this buster was invalid on
+ * linux. Just remove this when it's 100% sure that problem has been fixed. */
+
+#ifdef VBOX_WITH_USB
+ remoteUSBThreadStop();
+#endif /* VBOX_WITH_USB */
+
+ if (mhServer)
+ {
+ HVRDESERVER hServer = mhServer;
+
+ /* Reset the handle to avoid further calls to the server. */
+ mhServer = 0;
+
+ /* Workaround for VM process hangs on termination.
+ *
+ * Make sure that the server is not currently processing a resize.
+ * mhServer 0 will not allow to enter the server again.
+ * Wait until any current resize returns from the server.
+ */
+ if (mcInResize)
+ {
+ LogRel(("VRDP: waiting for resize %d\n", mcInResize));
+
+ int i = 0;
+ while (mcInResize && ++i < 100)
+ {
+ RTThreadSleep(10);
+ }
+ }
+
+ if (mpEntryPoints && hServer)
+ {
+ mpEntryPoints->VRDEDestroy(hServer);
+ }
+ }
+
+#ifndef VBOX_WITH_VRDEAUTH_IN_VBOXSVC
+ AuthLibUnload(&mAuthLibCtx);
+#endif
+}
+
+/* Worker thread for Remote USB. The thread polls the clients for
+ * the list of attached USB devices.
+ * The thread is also responsible for attaching/detaching devices
+ * to/from the VM.
+ *
+ * It is expected that attaching/detaching is not a frequent operation.
+ *
+ * The thread is always running when the VRDP server is active.
+ *
+ * The thread scans backends and requests the device list every 2 seconds.
+ *
+ * When device list is available, the thread calls the Console to process it.
+ *
+ */
+#define VRDP_DEVICE_LIST_PERIOD_MS (2000)
+
+#ifdef VBOX_WITH_USB
+static DECLCALLBACK(int) threadRemoteUSB(RTTHREAD self, void *pvUser)
+{
+ ConsoleVRDPServer *pOwner = (ConsoleVRDPServer *)pvUser;
+
+ LogFlow(("Console::threadRemoteUSB: start. owner = %p.\n", pOwner));
+
+ pOwner->notifyRemoteUSBThreadRunning(self);
+
+ while (pOwner->isRemoteUSBThreadRunning())
+ {
+ RemoteUSBBackend *pRemoteUSBBackend = NULL;
+
+ while ((pRemoteUSBBackend = pOwner->usbBackendGetNext(pRemoteUSBBackend)) != NULL)
+ {
+ pRemoteUSBBackend->PollRemoteDevices();
+ }
+
+ pOwner->waitRemoteUSBThreadEvent(VRDP_DEVICE_LIST_PERIOD_MS);
+
+ LogFlow(("Console::threadRemoteUSB: iteration. owner = %p.\n", pOwner));
+ }
+
+ return VINF_SUCCESS;
+}
+
+void ConsoleVRDPServer::notifyRemoteUSBThreadRunning(RTTHREAD thread)
+{
+ mUSBBackends.thread = thread;
+ mUSBBackends.fThreadRunning = true;
+ int vrc = RTThreadUserSignal(thread);
+ AssertRC(vrc);
+}
+
+bool ConsoleVRDPServer::isRemoteUSBThreadRunning(void)
+{
+ return mUSBBackends.fThreadRunning;
+}
+
+void ConsoleVRDPServer::waitRemoteUSBThreadEvent(RTMSINTERVAL cMillies)
+{
+ int vrc = RTSemEventWait(mUSBBackends.event, cMillies);
+ Assert(RT_SUCCESS(vrc) || vrc == VERR_TIMEOUT);
+ NOREF(vrc);
+}
+
+void ConsoleVRDPServer::remoteUSBThreadStart(void)
+{
+ int vrc = RTSemEventCreate(&mUSBBackends.event);
+
+ if (RT_FAILURE(vrc))
+ {
+ AssertFailed();
+ mUSBBackends.event = 0;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTThreadCreate(&mUSBBackends.thread, threadRemoteUSB, this, 65536,
+ RTTHREADTYPE_VRDP_IO, RTTHREADFLAGS_WAITABLE, "remote usb");
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Warning: could not start the remote USB thread, vrc = %Rrc!!!\n", vrc));
+ mUSBBackends.thread = NIL_RTTHREAD;
+ }
+ else
+ {
+ /* Wait until the thread is ready. */
+ vrc = RTThreadUserWait(mUSBBackends.thread, 60000);
+ AssertRC(vrc);
+ Assert (mUSBBackends.fThreadRunning || RT_FAILURE(vrc));
+ }
+}
+
+void ConsoleVRDPServer::remoteUSBThreadStop(void)
+{
+ mUSBBackends.fThreadRunning = false;
+
+ if (mUSBBackends.thread != NIL_RTTHREAD)
+ {
+ Assert (mUSBBackends.event != 0);
+
+ RTSemEventSignal(mUSBBackends.event);
+
+ int vrc = RTThreadWait(mUSBBackends.thread, 60000, NULL);
+ AssertRC(vrc);
+
+ mUSBBackends.thread = NIL_RTTHREAD;
+ }
+
+ if (mUSBBackends.event)
+ {
+ RTSemEventDestroy(mUSBBackends.event);
+ mUSBBackends.event = 0;
+ }
+}
+#endif /* VBOX_WITH_USB */
+
+AuthResult ConsoleVRDPServer::Authenticate(const Guid &uuid, AuthGuestJudgement guestJudgement,
+ const char *pszUser, const char *pszPassword, const char *pszDomain,
+ uint32_t u32ClientId)
+{
+ LogFlowFunc(("uuid = %RTuuid, guestJudgement = %d, pszUser = %s, pszPassword = %s, pszDomain = %s, u32ClientId = %d\n",
+ uuid.raw(), guestJudgement, pszUser, pszPassword, pszDomain, u32ClientId));
+
+ AuthResult result = AuthResultAccessDenied;
+
+#ifdef VBOX_WITH_VRDEAUTH_IN_VBOXSVC
+ try
+ {
+ /* Init auth parameters. Order is important. */
+ SafeArray<BSTR> authParams;
+ Bstr("VRDEAUTH" ).detachTo(authParams.appendedRaw());
+ Bstr(uuid.toUtf16() ).detachTo(authParams.appendedRaw());
+ BstrFmt("%u", guestJudgement).detachTo(authParams.appendedRaw());
+ Bstr(pszUser ).detachTo(authParams.appendedRaw());
+ Bstr(pszPassword ).detachTo(authParams.appendedRaw());
+ Bstr(pszDomain ).detachTo(authParams.appendedRaw());
+ BstrFmt("%u", u32ClientId).detachTo(authParams.appendedRaw());
+
+ Bstr authResult;
+ HRESULT hr = mConsole->mControl->AuthenticateExternal(ComSafeArrayAsInParam(authParams),
+ authResult.asOutParam());
+ LogFlowFunc(("%Rhrc [%ls]\n", hr, authResult.raw()));
+
+ size_t cbPassword = RTUtf16Len((PRTUTF16)authParams[4]) * sizeof(RTUTF16);
+ if (cbPassword)
+ RTMemWipeThoroughly(authParams[4], cbPassword, 10 /* cPasses */);
+
+ if (SUCCEEDED(hr) && authResult == "granted")
+ result = AuthResultAccessGranted;
+ }
+ catch (std::bad_alloc &)
+ {
+ }
+#else
+ /*
+ * Called only from VRDP input thread. So thread safety is not required.
+ */
+
+ if (!mAuthLibCtx.hAuthLibrary)
+ {
+ /* Load the external authentication library. */
+ Bstr authLibrary;
+ mConsole->i_getVRDEServer()->COMGETTER(AuthLibrary)(authLibrary.asOutParam());
+
+ Utf8Str filename = authLibrary;
+
+ int vrc = AuthLibLoad(&mAuthLibCtx, filename.c_str());
+ if (RT_FAILURE(vrc))
+ {
+ mConsole->setErrorBoth(E_FAIL, vrc, tr("Could not load the external authentication library '%s' (%Rrc)"),
+ filename.c_str(), vrc);
+ return AuthResultAccessDenied;
+ }
+ }
+
+ result = AuthLibAuthenticate(&mAuthLibCtx,
+ uuid.raw(), guestJudgement,
+ pszUser, pszPassword, pszDomain,
+ u32ClientId);
+#endif /* !VBOX_WITH_VRDEAUTH_IN_VBOXSVC */
+
+ switch (result)
+ {
+ case AuthResultAccessDenied:
+ LogRel(("AUTH: external authentication module returned 'access denied'\n"));
+ break;
+ case AuthResultAccessGranted:
+ LogRel(("AUTH: external authentication module returned 'access granted'\n"));
+ break;
+ case AuthResultDelegateToGuest:
+ LogRel(("AUTH: external authentication module returned 'delegate request to guest'\n"));
+ break;
+ default:
+ LogRel(("AUTH: external authentication module returned incorrect return code %d\n", result));
+ result = AuthResultAccessDenied;
+ }
+
+ LogFlowFunc(("result = %d\n", result));
+
+ return result;
+}
+
+void ConsoleVRDPServer::AuthDisconnect(const Guid &uuid, uint32_t u32ClientId)
+{
+ LogFlow(("ConsoleVRDPServer::AuthDisconnect: uuid = %RTuuid, u32ClientId = %d\n",
+ uuid.raw(), u32ClientId));
+
+#ifdef VBOX_WITH_VRDEAUTH_IN_VBOXSVC
+ try
+ {
+ /* Init auth parameters. Order is important. */
+ SafeArray<BSTR> authParams;
+ Bstr("VRDEAUTHDISCONNECT").detachTo(authParams.appendedRaw());
+ Bstr(uuid.toUtf16() ).detachTo(authParams.appendedRaw());
+ BstrFmt("%u", u32ClientId).detachTo(authParams.appendedRaw());
+
+ Bstr authResult;
+ HRESULT hrc = mConsole->mControl->AuthenticateExternal(ComSafeArrayAsInParam(authParams),
+ authResult.asOutParam());
+ LogFlowFunc(("%Rhrc [%ls]\n", hrc, authResult.raw())); NOREF(hrc);
+ }
+ catch (std::bad_alloc &)
+ {
+ }
+#else
+ AuthLibDisconnect(&mAuthLibCtx, uuid.raw(), u32ClientId);
+#endif /* !VBOX_WITH_VRDEAUTH_IN_VBOXSVC */
+}
+
+int ConsoleVRDPServer::lockConsoleVRDPServer(void)
+{
+ int vrc = RTCritSectEnter(&mCritSect);
+ AssertRC(vrc);
+ return vrc;
+}
+
+void ConsoleVRDPServer::unlockConsoleVRDPServer(void)
+{
+ RTCritSectLeave(&mCritSect);
+}
+
+DECLCALLBACK(int) ConsoleVRDPServer::ClipboardCallback(void *pvCallback,
+ uint32_t u32ClientId,
+ uint32_t u32Function,
+ uint32_t u32Format,
+ const void *pvData,
+ uint32_t cbData)
+{
+ LogFlowFunc(("pvCallback = %p, u32ClientId = %d, u32Function = %d, u32Format = 0x%08X, pvData = %p, cbData = %d\n",
+ pvCallback, u32ClientId, u32Function, u32Format, pvData, cbData));
+
+ int vrc = VINF_SUCCESS;
+
+ ConsoleVRDPServer *pServer = static_cast <ConsoleVRDPServer *>(pvCallback);
+
+ RT_NOREF(u32ClientId);
+
+ switch (u32Function)
+ {
+ case VRDE_CLIPBOARD_FUNCTION_FORMAT_ANNOUNCE:
+ {
+ if (pServer->mpfnClipboardCallback)
+ {
+ vrc = pServer->mpfnClipboardCallback(VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE,
+ u32Format,
+ (void *)pvData,
+ cbData);
+ }
+ } break;
+
+ case VRDE_CLIPBOARD_FUNCTION_DATA_READ:
+ {
+ if (pServer->mpfnClipboardCallback)
+ {
+ vrc = pServer->mpfnClipboardCallback(VBOX_CLIPBOARD_EXT_FN_DATA_READ,
+ u32Format,
+ (void *)pvData,
+ cbData);
+ }
+ } break;
+
+ default:
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ } break;
+ }
+
+ return vrc;
+}
+
+/*static*/ DECLCALLBACK(int)
+ConsoleVRDPServer::ClipboardServiceExtension(void *pvExtension, uint32_t u32Function, void *pvParms, uint32_t cbParms)
+{
+ RT_NOREF(cbParms);
+ LogFlowFunc(("pvExtension = %p, u32Function = %d, pvParms = %p, cbParms = %d\n",
+ pvExtension, u32Function, pvParms, cbParms));
+
+ int vrc = VINF_SUCCESS;
+
+ ConsoleVRDPServer *pServer = static_cast <ConsoleVRDPServer *>(pvExtension);
+
+ SHCLEXTPARMS *pParms = (SHCLEXTPARMS *)pvParms;
+
+ switch (u32Function)
+ {
+ case VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK:
+ {
+ pServer->mpfnClipboardCallback = pParms->u.pfnCallback;
+ } break;
+
+ case VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE:
+ {
+ /* The guest announces clipboard formats. This must be delivered to all clients. */
+ if (mpEntryPoints && pServer->mhServer)
+ {
+ mpEntryPoints->VRDEClipboard(pServer->mhServer,
+ VRDE_CLIPBOARD_FUNCTION_FORMAT_ANNOUNCE,
+ pParms->uFormat,
+ NULL,
+ 0,
+ NULL);
+ }
+ } break;
+
+ case VBOX_CLIPBOARD_EXT_FN_DATA_READ:
+ {
+ /* The clipboard service expects that the pvData buffer will be filled
+ * with clipboard data. The server returns the data from the client that
+ * announced the requested format most recently.
+ */
+ if (mpEntryPoints && pServer->mhServer)
+ {
+ mpEntryPoints->VRDEClipboard(pServer->mhServer,
+ VRDE_CLIPBOARD_FUNCTION_DATA_READ,
+ pParms->uFormat,
+ pParms->u.pvData,
+ pParms->cbData,
+ &pParms->cbData);
+ }
+ } break;
+
+ case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE:
+ {
+ if (mpEntryPoints && pServer->mhServer)
+ {
+ mpEntryPoints->VRDEClipboard(pServer->mhServer,
+ VRDE_CLIPBOARD_FUNCTION_DATA_WRITE,
+ pParms->uFormat,
+ pParms->u.pvData,
+ pParms->cbData,
+ NULL);
+ }
+ } break;
+
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+void ConsoleVRDPServer::ClipboardCreate(uint32_t u32ClientId)
+{
+ RT_NOREF(u32ClientId);
+
+ int vrc = lockConsoleVRDPServer();
+ if (RT_SUCCESS(vrc))
+ {
+ if (mcClipboardRefs == 0)
+ {
+ vrc = HGCMHostRegisterServiceExtension(&mhClipboard, "VBoxSharedClipboard", ClipboardServiceExtension, this);
+ AssertRC(vrc);
+ }
+
+ mcClipboardRefs++;
+ unlockConsoleVRDPServer();
+ }
+}
+
+void ConsoleVRDPServer::ClipboardDelete(uint32_t u32ClientId)
+{
+ RT_NOREF(u32ClientId);
+
+ int vrc = lockConsoleVRDPServer();
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(mcClipboardRefs);
+ if (mcClipboardRefs > 0)
+ {
+ mcClipboardRefs--;
+
+ if (mcClipboardRefs == 0 && mhClipboard)
+ {
+ HGCMHostUnregisterServiceExtension(mhClipboard);
+ mhClipboard = NULL;
+ }
+ }
+
+ unlockConsoleVRDPServer();
+ }
+}
+
+/* That is called on INPUT thread of the VRDP server.
+ * The ConsoleVRDPServer keeps a list of created backend instances.
+ */
+void ConsoleVRDPServer::USBBackendCreate(uint32_t u32ClientId, void **ppvIntercept)
+{
+#ifdef VBOX_WITH_USB
+ LogFlow(("ConsoleVRDPServer::USBBackendCreate: u32ClientId = %d\n", u32ClientId));
+
+ /* Create a new instance of the USB backend for the new client. */
+ RemoteUSBBackend *pRemoteUSBBackend = new RemoteUSBBackend(mConsole, this, u32ClientId);
+
+ if (pRemoteUSBBackend)
+ {
+ pRemoteUSBBackend->AddRef(); /* 'Release' called in USBBackendDelete. */
+
+ /* Append the new instance in the list. */
+ int vrc = lockConsoleVRDPServer();
+
+ if (RT_SUCCESS(vrc))
+ {
+ pRemoteUSBBackend->pNext = mUSBBackends.pHead;
+ if (mUSBBackends.pHead)
+ {
+ mUSBBackends.pHead->pPrev = pRemoteUSBBackend;
+ }
+ else
+ {
+ mUSBBackends.pTail = pRemoteUSBBackend;
+ }
+
+ mUSBBackends.pHead = pRemoteUSBBackend;
+
+ unlockConsoleVRDPServer();
+
+ if (ppvIntercept)
+ {
+ *ppvIntercept = pRemoteUSBBackend;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ pRemoteUSBBackend->Release();
+ }
+ }
+#else
+ RT_NOREF(u32ClientId, ppvIntercept);
+#endif /* VBOX_WITH_USB */
+}
+
+void ConsoleVRDPServer::USBBackendDelete(uint32_t u32ClientId)
+{
+#ifdef VBOX_WITH_USB
+ LogFlow(("ConsoleVRDPServer::USBBackendDelete: u32ClientId = %d\n", u32ClientId));
+
+ RemoteUSBBackend *pRemoteUSBBackend = NULL;
+
+ /* Find the instance. */
+ int vrc = lockConsoleVRDPServer();
+
+ if (RT_SUCCESS(vrc))
+ {
+ pRemoteUSBBackend = usbBackendFind(u32ClientId);
+
+ if (pRemoteUSBBackend)
+ {
+ /* Notify that it will be deleted. */
+ pRemoteUSBBackend->NotifyDelete();
+ }
+
+ unlockConsoleVRDPServer();
+ }
+
+ if (pRemoteUSBBackend)
+ {
+ /* Here the instance has been excluded from the list and can be dereferenced. */
+ pRemoteUSBBackend->Release();
+ }
+#else
+ RT_NOREF(u32ClientId);
+#endif
+}
+
+void *ConsoleVRDPServer::USBBackendRequestPointer(uint32_t u32ClientId, const Guid *pGuid)
+{
+#ifdef VBOX_WITH_USB
+ RemoteUSBBackend *pRemoteUSBBackend = NULL;
+
+ /* Find the instance. */
+ int vrc = lockConsoleVRDPServer();
+
+ if (RT_SUCCESS(vrc))
+ {
+ pRemoteUSBBackend = usbBackendFind(u32ClientId);
+
+ if (pRemoteUSBBackend)
+ {
+ /* Inform the backend instance that it is referenced by the Guid. */
+ bool fAdded = pRemoteUSBBackend->addUUID(pGuid);
+
+ if (fAdded)
+ {
+ /* Reference the instance because its pointer is being taken. */
+ pRemoteUSBBackend->AddRef(); /* 'Release' is called in USBBackendReleasePointer. */
+ }
+ else
+ {
+ pRemoteUSBBackend = NULL;
+ }
+ }
+
+ unlockConsoleVRDPServer();
+ }
+
+ if (pRemoteUSBBackend)
+ {
+ return pRemoteUSBBackend->GetBackendCallbackPointer();
+ }
+#else
+ RT_NOREF(u32ClientId, pGuid);
+#endif
+ return NULL;
+}
+
+void ConsoleVRDPServer::USBBackendReleasePointer(const Guid *pGuid)
+{
+#ifdef VBOX_WITH_USB
+ RemoteUSBBackend *pRemoteUSBBackend = NULL;
+
+ /* Find the instance. */
+ int vrc = lockConsoleVRDPServer();
+
+ if (RT_SUCCESS(vrc))
+ {
+ pRemoteUSBBackend = usbBackendFindByUUID(pGuid);
+
+ if (pRemoteUSBBackend)
+ {
+ pRemoteUSBBackend->removeUUID(pGuid);
+ }
+
+ unlockConsoleVRDPServer();
+
+ if (pRemoteUSBBackend)
+ {
+ pRemoteUSBBackend->Release();
+ }
+ }
+#else
+ RT_NOREF(pGuid);
+#endif
+}
+
+RemoteUSBBackend *ConsoleVRDPServer::usbBackendGetNext(RemoteUSBBackend *pRemoteUSBBackend)
+{
+ LogFlow(("ConsoleVRDPServer::usbBackendGetNext: pBackend = %p\n", pRemoteUSBBackend));
+
+ RemoteUSBBackend *pNextRemoteUSBBackend = NULL;
+#ifdef VBOX_WITH_USB
+
+ int vrc = lockConsoleVRDPServer();
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (pRemoteUSBBackend == NULL)
+ {
+ /* The first backend in the list is requested. */
+ pNextRemoteUSBBackend = mUSBBackends.pHead;
+ }
+ else
+ {
+ /* Get pointer to the next backend. */
+ pNextRemoteUSBBackend = (RemoteUSBBackend *)pRemoteUSBBackend->pNext;
+ }
+
+ if (pNextRemoteUSBBackend)
+ {
+ pNextRemoteUSBBackend->AddRef();
+ }
+
+ unlockConsoleVRDPServer();
+
+ if (pRemoteUSBBackend)
+ {
+ pRemoteUSBBackend->Release();
+ }
+ }
+#endif
+
+ return pNextRemoteUSBBackend;
+}
+
+#ifdef VBOX_WITH_USB
+/* Internal method. Called under the ConsoleVRDPServerLock. */
+RemoteUSBBackend *ConsoleVRDPServer::usbBackendFind(uint32_t u32ClientId)
+{
+ RemoteUSBBackend *pRemoteUSBBackend = mUSBBackends.pHead;
+
+ while (pRemoteUSBBackend)
+ {
+ if (pRemoteUSBBackend->ClientId() == u32ClientId)
+ {
+ break;
+ }
+
+ pRemoteUSBBackend = (RemoteUSBBackend *)pRemoteUSBBackend->pNext;
+ }
+
+ return pRemoteUSBBackend;
+}
+
+/* Internal method. Called under the ConsoleVRDPServerLock. */
+RemoteUSBBackend *ConsoleVRDPServer::usbBackendFindByUUID(const Guid *pGuid)
+{
+ RemoteUSBBackend *pRemoteUSBBackend = mUSBBackends.pHead;
+
+ while (pRemoteUSBBackend)
+ {
+ if (pRemoteUSBBackend->findUUID(pGuid))
+ {
+ break;
+ }
+
+ pRemoteUSBBackend = (RemoteUSBBackend *)pRemoteUSBBackend->pNext;
+ }
+
+ return pRemoteUSBBackend;
+}
+#endif
+
+/* Internal method. Called by the backend destructor. */
+void ConsoleVRDPServer::usbBackendRemoveFromList(RemoteUSBBackend *pRemoteUSBBackend)
+{
+#ifdef VBOX_WITH_USB
+ int vrc = lockConsoleVRDPServer();
+ AssertRC(vrc);
+
+ /* Exclude the found instance from the list. */
+ if (pRemoteUSBBackend->pNext)
+ {
+ pRemoteUSBBackend->pNext->pPrev = pRemoteUSBBackend->pPrev;
+ }
+ else
+ {
+ mUSBBackends.pTail = (RemoteUSBBackend *)pRemoteUSBBackend->pPrev;
+ }
+
+ if (pRemoteUSBBackend->pPrev)
+ {
+ pRemoteUSBBackend->pPrev->pNext = pRemoteUSBBackend->pNext;
+ }
+ else
+ {
+ mUSBBackends.pHead = (RemoteUSBBackend *)pRemoteUSBBackend->pNext;
+ }
+
+ pRemoteUSBBackend->pNext = pRemoteUSBBackend->pPrev = NULL;
+
+ unlockConsoleVRDPServer();
+#else
+ RT_NOREF(pRemoteUSBBackend);
+#endif
+}
+
+
+void ConsoleVRDPServer::SendUpdate(unsigned uScreenId, void *pvUpdate, uint32_t cbUpdate) const
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEUpdate(mhServer, uScreenId, pvUpdate, cbUpdate);
+ }
+}
+
+void ConsoleVRDPServer::SendResize(void)
+{
+ if (mpEntryPoints && mhServer)
+ {
+ ++mcInResize;
+ mpEntryPoints->VRDEResize(mhServer);
+ --mcInResize;
+ }
+}
+
+void ConsoleVRDPServer::SendUpdateBitmap(unsigned uScreenId, uint32_t x, uint32_t y, uint32_t w, uint32_t h) const
+{
+ VRDEORDERHDR update;
+ update.x = (uint16_t)x;
+ update.y = (uint16_t)y;
+ update.w = (uint16_t)w;
+ update.h = (uint16_t)h;
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEUpdate(mhServer, uScreenId, &update, sizeof(update));
+ }
+}
+
+void ConsoleVRDPServer::SendAudioSamples(void const *pvSamples, uint32_t cSamples, VRDEAUDIOFORMAT format) const
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEAudioSamples(mhServer, pvSamples, cSamples, format);
+ }
+}
+
+void ConsoleVRDPServer::SendAudioVolume(uint16_t left, uint16_t right) const
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEAudioVolume(mhServer, left, right);
+ }
+}
+
+void ConsoleVRDPServer::SendUSBRequest(uint32_t u32ClientId, void *pvParms, uint32_t cbParms) const
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEUSBRequest(mhServer, u32ClientId, pvParms, cbParms);
+ }
+}
+
+int ConsoleVRDPServer::SendAudioInputBegin(void **ppvUserCtx,
+ void *pvContext,
+ uint32_t cSamples,
+ uint32_t iSampleHz,
+ uint32_t cChannels,
+ uint32_t cBits)
+{
+ if ( mhServer
+ && mpEntryPoints && mpEntryPoints->VRDEAudioInOpen)
+ {
+ uint32_t u32ClientId = ASMAtomicReadU32(&mu32AudioInputClientId);
+ if (u32ClientId != 0) /* 0 would mean broadcast to all clients. */
+ {
+ VRDEAUDIOFORMAT audioFormat = VRDE_AUDIO_FMT_MAKE(iSampleHz, cChannels, cBits, 0);
+ mpEntryPoints->VRDEAudioInOpen(mhServer,
+ pvContext,
+ u32ClientId,
+ audioFormat,
+ cSamples);
+ if (ppvUserCtx)
+ *ppvUserCtx = NULL; /* This is the ConsoleVRDPServer context.
+ * Currently not used because only one client is allowed to
+ * do audio input and the client ID is saved by the ConsoleVRDPServer.
+ */
+ return VINF_SUCCESS;
+ }
+ }
+
+ /*
+ * Not supported or no client connected.
+ */
+ return VERR_NOT_SUPPORTED;
+}
+
+void ConsoleVRDPServer::SendAudioInputEnd(void *pvUserCtx)
+{
+ RT_NOREF(pvUserCtx);
+ if (mpEntryPoints && mhServer && mpEntryPoints->VRDEAudioInClose)
+ {
+ uint32_t u32ClientId = ASMAtomicReadU32(&mu32AudioInputClientId);
+ if (u32ClientId != 0) /* 0 would mean broadcast to all clients. */
+ {
+ mpEntryPoints->VRDEAudioInClose(mhServer, u32ClientId);
+ }
+ }
+}
+
+void ConsoleVRDPServer::QueryInfo(uint32_t index, void *pvBuffer, uint32_t cbBuffer, uint32_t *pcbOut) const
+{
+ if (index == VRDE_QI_PORT)
+ {
+ uint32_t cbOut = sizeof(int32_t);
+
+ if (cbBuffer >= cbOut)
+ {
+ *pcbOut = cbOut;
+ *(int32_t *)pvBuffer = (int32_t)mVRDPBindPort;
+ }
+ }
+ else if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEQueryInfo(mhServer, index, pvBuffer, cbBuffer, pcbOut);
+ }
+}
+
+/* static */ int ConsoleVRDPServer::loadVRDPLibrary(const char *pszLibraryName)
+{
+ int vrc = VINF_SUCCESS;
+
+ if (mVRDPLibrary == NIL_RTLDRMOD)
+ {
+ RTERRINFOSTATIC ErrInfo;
+ RTErrInfoInitStatic(&ErrInfo);
+
+ if (RTPathHavePath(pszLibraryName))
+ vrc = SUPR3HardenedLdrLoadPlugIn(pszLibraryName, &mVRDPLibrary, &ErrInfo.Core);
+ else
+ vrc = SUPR3HardenedLdrLoadAppPriv(pszLibraryName, &mVRDPLibrary, RTLDRLOAD_FLAGS_LOCAL, &ErrInfo.Core);
+ if (RT_SUCCESS(vrc))
+ {
+ struct SymbolEntry
+ {
+ const char *name;
+ void **ppfn;
+ };
+
+ #define DEFSYMENTRY(a) { #a, (void**)&mpfn##a }
+
+ static const struct SymbolEntry s_aSymbols[] =
+ {
+ DEFSYMENTRY(VRDECreateServer)
+ };
+
+ #undef DEFSYMENTRY
+
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aSymbols); i++)
+ {
+ vrc = RTLdrGetSymbol(mVRDPLibrary, s_aSymbols[i].name, s_aSymbols[i].ppfn);
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("VRDE: Error resolving symbol '%s', vrc %Rrc.\n", s_aSymbols[i].name, vrc));
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (RTErrInfoIsSet(&ErrInfo.Core))
+ LogRel(("VRDE: Error loading the library '%s': %s (%Rrc)\n", pszLibraryName, ErrInfo.Core.pszMsg, vrc));
+ else
+ LogRel(("VRDE: Error loading the library '%s' vrc = %Rrc.\n", pszLibraryName, vrc));
+
+ mVRDPLibrary = NIL_RTLDRMOD;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ if (mVRDPLibrary != NIL_RTLDRMOD)
+ {
+ RTLdrClose(mVRDPLibrary);
+ mVRDPLibrary = NIL_RTLDRMOD;
+ }
+ }
+
+ return vrc;
+}
+
+/*
+ * IVRDEServerInfo implementation.
+ */
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+VRDEServerInfo::VRDEServerInfo()
+ : mParent(NULL)
+{
+}
+
+VRDEServerInfo::~VRDEServerInfo()
+{
+}
+
+
+HRESULT VRDEServerInfo::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void VRDEServerInfo::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the guest object.
+ */
+HRESULT VRDEServerInfo::init(Console *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void VRDEServerInfo::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(mParent) = NULL;
+}
+
+// IVRDEServerInfo properties
+/////////////////////////////////////////////////////////////////////////////
+
+#define IMPL_GETTER_BOOL(_aType, _aName, _aIndex) \
+ HRESULT VRDEServerInfo::get##_aName(_aType *a##_aName) \
+ { \
+ /** @todo Not sure if a AutoReadLock would be sufficient. */ \
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); \
+ \
+ uint32_t value; \
+ uint32_t cbOut = 0; \
+ \
+ mParent->i_consoleVRDPServer()->QueryInfo \
+ (_aIndex, &value, sizeof(value), &cbOut); \
+ \
+ *a##_aName = cbOut? !!value: FALSE; \
+ \
+ return S_OK; \
+ } \
+ extern void IMPL_GETTER_BOOL_DUMMY(void)
+
+#define IMPL_GETTER_SCALAR(_aType, _aName, _aIndex, _aValueMask) \
+ HRESULT VRDEServerInfo::get##_aName(_aType *a##_aName) \
+ { \
+ /** @todo Not sure if a AutoReadLock would be sufficient. */ \
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); \
+ \
+ _aType value; \
+ uint32_t cbOut = 0; \
+ \
+ mParent->i_consoleVRDPServer()->QueryInfo \
+ (_aIndex, &value, sizeof(value), &cbOut); \
+ \
+ if (_aValueMask) value &= (_aValueMask); \
+ *a##_aName = cbOut? value: 0; \
+ \
+ return S_OK; \
+ } \
+ extern void IMPL_GETTER_SCALAR_DUMMY(void)
+
+#define IMPL_GETTER_UTF8STR(_aType, _aName, _aIndex) \
+ HRESULT VRDEServerInfo::get##_aName(_aType &a##_aName) \
+ { \
+ /** @todo Not sure if a AutoReadLock would be sufficient. */ \
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); \
+ \
+ uint32_t cbOut = 0; \
+ \
+ mParent->i_consoleVRDPServer()->QueryInfo \
+ (_aIndex, NULL, 0, &cbOut); \
+ \
+ if (cbOut == 0) \
+ { \
+ a##_aName = Utf8Str::Empty; \
+ return S_OK; \
+ } \
+ \
+ char *pchBuffer = (char *)RTMemTmpAlloc(cbOut); \
+ \
+ if (!pchBuffer) \
+ { \
+ Log(("VRDEServerInfo::" \
+ #_aName \
+ ": Failed to allocate memory %d bytes\n", cbOut)); \
+ return E_OUTOFMEMORY; \
+ } \
+ \
+ mParent->i_consoleVRDPServer()->QueryInfo \
+ (_aIndex, pchBuffer, cbOut, &cbOut); \
+ \
+ a##_aName = pchBuffer; \
+ \
+ RTMemTmpFree(pchBuffer); \
+ \
+ return S_OK; \
+ } \
+ extern void IMPL_GETTER_BSTR_DUMMY(void)
+
+IMPL_GETTER_BOOL (BOOL, Active, VRDE_QI_ACTIVE);
+IMPL_GETTER_SCALAR (LONG, Port, VRDE_QI_PORT, 0);
+IMPL_GETTER_SCALAR (ULONG, NumberOfClients, VRDE_QI_NUMBER_OF_CLIENTS, 0);
+IMPL_GETTER_SCALAR (LONG64, BeginTime, VRDE_QI_BEGIN_TIME, 0);
+IMPL_GETTER_SCALAR (LONG64, EndTime, VRDE_QI_END_TIME, 0);
+IMPL_GETTER_SCALAR (LONG64, BytesSent, VRDE_QI_BYTES_SENT, INT64_MAX);
+IMPL_GETTER_SCALAR (LONG64, BytesSentTotal, VRDE_QI_BYTES_SENT_TOTAL, INT64_MAX);
+IMPL_GETTER_SCALAR (LONG64, BytesReceived, VRDE_QI_BYTES_RECEIVED, INT64_MAX);
+IMPL_GETTER_SCALAR (LONG64, BytesReceivedTotal, VRDE_QI_BYTES_RECEIVED_TOTAL, INT64_MAX);
+IMPL_GETTER_UTF8STR(Utf8Str, User, VRDE_QI_USER);
+IMPL_GETTER_UTF8STR(Utf8Str, Domain, VRDE_QI_DOMAIN);
+IMPL_GETTER_UTF8STR(Utf8Str, ClientName, VRDE_QI_CLIENT_NAME);
+IMPL_GETTER_UTF8STR(Utf8Str, ClientIP, VRDE_QI_CLIENT_IP);
+IMPL_GETTER_SCALAR (ULONG, ClientVersion, VRDE_QI_CLIENT_VERSION, 0);
+IMPL_GETTER_SCALAR (ULONG, EncryptionStyle, VRDE_QI_ENCRYPTION_STYLE, 0);
+
+#undef IMPL_GETTER_UTF8STR
+#undef IMPL_GETTER_SCALAR
+#undef IMPL_GETTER_BOOL
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/DisplayImpl.cpp b/src/VBox/Main/src-client/DisplayImpl.cpp
new file mode 100644
index 00000000..03dfd137
--- /dev/null
+++ b/src/VBox/Main/src-client/DisplayImpl.cpp
@@ -0,0 +1,3872 @@
+/* $Id: DisplayImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY
+#include "LoggingNew.h"
+
+#include "DisplayImpl.h"
+#include "DisplayUtils.h"
+#include "ConsoleImpl.h"
+#include "ConsoleVRDPServer.h"
+#include "GuestImpl.h"
+#include "VMMDev.h"
+
+#include "AutoCaller.h"
+
+/* generated header */
+#include "VBoxEvents.h"
+
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/asm.h>
+#include <iprt/time.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/alloca.h>
+
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/vmm/pdmdrv.h>
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+# include <VBoxVideo.h>
+#endif
+#include <VBoxVideo3D.h>
+
+#include <VBox/com/array.h>
+
+#ifdef VBOX_WITH_RECORDING
+# include <iprt/path.h>
+# include "Recording.h"
+
+# include <VBox/vmm/pdmapi.h>
+# include <VBox/vmm/pdmaudioifs.h>
+#endif
+
+/**
+ * Display driver instance data.
+ *
+ * @implements PDMIDISPLAYCONNECTOR
+ */
+typedef struct DRVMAINDISPLAY
+{
+ /** Pointer to the display object. */
+ Display *pDisplay;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the display port interface of the driver/device above us. */
+ PPDMIDISPLAYPORT pUpPort;
+ /** Our display connector interface. */
+ PDMIDISPLAYCONNECTOR IConnector;
+#if defined(VBOX_WITH_VIDEOHWACCEL)
+ /** VBVA callbacks */
+ PPDMIDISPLAYVBVACALLBACKS pVBVACallbacks;
+#endif
+} DRVMAINDISPLAY, *PDRVMAINDISPLAY;
+
+/** Converts PDMIDISPLAYCONNECTOR pointer to a DRVMAINDISPLAY pointer. */
+#define PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface) RT_FROM_MEMBER(pInterface, DRVMAINDISPLAY, IConnector)
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+Display::Display()
+ : mParent(NULL)
+{
+}
+
+Display::~Display()
+{
+}
+
+
+HRESULT Display::FinalConstruct()
+{
+ int vrc = videoAccelConstruct(&mVideoAccelLegacy);
+ AssertRC(vrc);
+
+ mfVideoAccelVRDP = false;
+ mfu32SupportedOrders = 0;
+ mcVRDPRefs = 0;
+
+ mfSeamlessEnabled = false;
+ mpRectVisibleRegion = NULL;
+ mcRectVisibleRegion = 0;
+
+ mpDrv = NULL;
+
+ vrc = RTCritSectInit(&mVideoAccelLock);
+ AssertRC(vrc);
+
+#ifdef VBOX_WITH_HGSMI
+ mu32UpdateVBVAFlags = 0;
+ mfVMMDevSupportsGraphics = false;
+ mfGuestVBVACapabilities = 0;
+ mfHostCursorCapabilities = 0;
+#endif
+
+#ifdef VBOX_WITH_RECORDING
+ vrc = RTCritSectInit(&mVideoRecLock);
+ AssertRC(vrc);
+
+ for (unsigned i = 0; i < RT_ELEMENTS(maRecordingEnabled); i++)
+ maRecordingEnabled[i] = true;
+#endif
+
+ return BaseFinalConstruct();
+}
+
+void Display::FinalRelease()
+{
+ uninit();
+
+#ifdef VBOX_WITH_RECORDING
+ if (RTCritSectIsInitialized(&mVideoRecLock))
+ {
+ RTCritSectDelete(&mVideoRecLock);
+ RT_ZERO(mVideoRecLock);
+ }
+#endif
+
+ videoAccelDestroy(&mVideoAccelLegacy);
+ i_saveVisibleRegion(0, NULL);
+
+ if (RTCritSectIsInitialized(&mVideoAccelLock))
+ {
+ RTCritSectDelete(&mVideoAccelLock);
+ RT_ZERO(mVideoAccelLock);
+ }
+
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+#define kMaxSizeThumbnail 64
+
+/**
+ * Save thumbnail and screenshot of the guest screen.
+ */
+static int displayMakeThumbnail(uint8_t *pbData, uint32_t cx, uint32_t cy,
+ uint8_t **ppu8Thumbnail, uint32_t *pcbThumbnail, uint32_t *pcxThumbnail, uint32_t *pcyThumbnail)
+{
+ int vrc = VINF_SUCCESS;
+
+ uint8_t *pu8Thumbnail = NULL;
+ uint32_t cbThumbnail = 0;
+ uint32_t cxThumbnail = 0;
+ uint32_t cyThumbnail = 0;
+
+ if (cx > cy)
+ {
+ cxThumbnail = kMaxSizeThumbnail;
+ cyThumbnail = (kMaxSizeThumbnail * cy) / cx;
+ }
+ else
+ {
+ cyThumbnail = kMaxSizeThumbnail;
+ cxThumbnail = (kMaxSizeThumbnail * cx) / cy;
+ }
+
+ LogRelFlowFunc(("%dx%d -> %dx%d\n", cx, cy, cxThumbnail, cyThumbnail));
+
+ cbThumbnail = cxThumbnail * 4 * cyThumbnail;
+ pu8Thumbnail = (uint8_t *)RTMemAlloc(cbThumbnail);
+
+ if (pu8Thumbnail)
+ {
+ uint8_t *dst = pu8Thumbnail;
+ uint8_t *src = pbData;
+ int dstW = cxThumbnail;
+ int dstH = cyThumbnail;
+ int srcW = cx;
+ int srcH = cy;
+ int iDeltaLine = cx * 4;
+
+ BitmapScale32(dst,
+ dstW, dstH,
+ src,
+ iDeltaLine,
+ srcW, srcH);
+
+ *ppu8Thumbnail = pu8Thumbnail;
+ *pcbThumbnail = cbThumbnail;
+ *pcxThumbnail = cxThumbnail;
+ *pcyThumbnail = cyThumbnail;
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ return vrc;
+}
+
+/**
+ * @callback_method_impl{FNSSMEXTSAVEEXEC}
+ */
+DECLCALLBACK(int) Display::i_displaySSMSaveScreenshot(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser)
+{
+ Display * const pThat = static_cast<Display *>(pvUser);
+ AssertPtrReturn(pThat, VERR_INVALID_POINTER);
+
+ /* 32bpp small RGB image. */
+ uint8_t *pu8Thumbnail = NULL;
+ uint32_t cbThumbnail = 0;
+ uint32_t cxThumbnail = 0;
+ uint32_t cyThumbnail = 0;
+
+ /* PNG screenshot. */
+ uint8_t *pu8PNG = NULL;
+ uint32_t cbPNG = 0;
+ uint32_t cxPNG = 0;
+ uint32_t cyPNG = 0;
+
+ Console::SafeVMPtr ptrVM(pThat->mParent);
+ if (ptrVM.isOk())
+ {
+ /* Query RGB bitmap. */
+ /* SSM code is executed on EMT(0), therefore no need to use VMR3ReqCallWait. */
+ uint8_t *pbData = NULL;
+ size_t cbData = 0;
+ uint32_t cx = 0;
+ uint32_t cy = 0;
+ bool fFreeMem = false;
+ int vrc = Display::i_displayTakeScreenshotEMT(pThat, VBOX_VIDEO_PRIMARY_SCREEN, &pbData, &cbData, &cx, &cy, &fFreeMem);
+
+ /*
+ * It is possible that success is returned but everything is 0 or NULL.
+ * (no display attached if a VM is running with VBoxHeadless on OSE for example)
+ */
+ if (RT_SUCCESS(vrc) && pbData)
+ {
+ Assert(cx && cy);
+
+ /* Prepare a small thumbnail and a PNG screenshot. */
+ displayMakeThumbnail(pbData, cx, cy, &pu8Thumbnail, &cbThumbnail, &cxThumbnail, &cyThumbnail);
+ vrc = DisplayMakePNG(pbData, cx, cy, &pu8PNG, &cbPNG, &cxPNG, &cyPNG, 1);
+ if (RT_FAILURE(vrc))
+ {
+ if (pu8PNG)
+ {
+ RTMemFree(pu8PNG);
+ pu8PNG = NULL;
+ }
+ cbPNG = 0;
+ cxPNG = 0;
+ cyPNG = 0;
+ }
+
+ if (fFreeMem)
+ RTMemFree(pbData);
+ else
+ pThat->mpDrv->pUpPort->pfnFreeScreenshot(pThat->mpDrv->pUpPort, pbData);
+ }
+ }
+ else
+ {
+ LogFunc(("Failed to get VM pointer 0x%x\n", ptrVM.rc()));
+ }
+
+ /* Regardless of rc, save what is available:
+ * Data format:
+ * uint32_t cBlocks;
+ * [blocks]
+ *
+ * Each block is:
+ * uint32_t cbBlock; if 0 - no 'block data'.
+ * uint32_t typeOfBlock; 0 - 32bpp RGB bitmap, 1 - PNG, ignored if 'cbBlock' is 0.
+ * [block data]
+ *
+ * Block data for bitmap and PNG:
+ * uint32_t cx;
+ * uint32_t cy;
+ * [image data]
+ */
+ pVMM->pfnSSMR3PutU32(pSSM, 2); /* Write thumbnail and PNG screenshot. */
+
+ /* First block. */
+ pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)(cbThumbnail + 2 * sizeof(uint32_t)));
+ pVMM->pfnSSMR3PutU32(pSSM, 0); /* Block type: thumbnail. */
+
+ if (cbThumbnail)
+ {
+ pVMM->pfnSSMR3PutU32(pSSM, cxThumbnail);
+ pVMM->pfnSSMR3PutU32(pSSM, cyThumbnail);
+ pVMM->pfnSSMR3PutMem(pSSM, pu8Thumbnail, cbThumbnail);
+ }
+
+ /* Second block. */
+ pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)(cbPNG + 2 * sizeof(uint32_t)));
+ pVMM->pfnSSMR3PutU32(pSSM, 1); /* Block type: png. */
+
+ if (cbPNG)
+ {
+ pVMM->pfnSSMR3PutU32(pSSM, cxPNG);
+ pVMM->pfnSSMR3PutU32(pSSM, cyPNG);
+ pVMM->pfnSSMR3PutMem(pSSM, pu8PNG, cbPNG);
+ }
+
+ RTMemFree(pu8PNG);
+ RTMemFree(pu8Thumbnail);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMEXTLOADEXEC}
+ */
+DECLCALLBACK(int)
+Display::i_displaySSMLoadScreenshot(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser, uint32_t uVersion, uint32_t uPass)
+{
+ Display * const pThat = static_cast<Display *>(pvUser);
+ AssertPtrReturn(pThat, VERR_INVALID_POINTER);
+ Assert(uPass == SSM_PASS_FINAL); RT_NOREF_PV(uPass);
+
+ if (uVersion != sSSMDisplayScreenshotVer)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+ /* Skip data. */
+ uint32_t cBlocks;
+ int vrc = pVMM->pfnSSMR3GetU32(pSSM, &cBlocks);
+ AssertRCReturn(vrc, vrc);
+
+ for (uint32_t i = 0; i < cBlocks; i++)
+ {
+ uint32_t cbBlock;
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &cbBlock);
+ AssertRCReturn(vrc, vrc);
+
+ uint32_t typeOfBlock;
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &typeOfBlock);
+ AssertRCReturn(vrc, vrc);
+
+ LogRelFlowFunc(("[%d] type %d, size %d bytes\n", i, typeOfBlock, cbBlock));
+
+ /* Note: displaySSMSaveScreenshot writes size of a block = 8 and
+ * do not write any data if the image size was 0.
+ */
+ /** @todo Fix and increase saved state version. */
+ if (cbBlock > 2 * sizeof(uint32_t))
+ {
+ vrc = pVMM->pfnSSMR3Skip(pSSM, cbBlock);
+ AssertRCReturn(vrc, vrc);
+ }
+ }
+
+ return vrc;
+}
+
+/**
+ * @callback_method_impl{FNSSMEXTSAVEEXEC, Save some important guest state}
+ */
+/*static*/ DECLCALLBACK(int)
+Display::i_displaySSMSave(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser)
+{
+ Display * const pThat = static_cast<Display *>(pvUser);
+ AssertPtrReturn(pThat, VERR_INVALID_POINTER);
+
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->mcMonitors);
+ for (unsigned i = 0; i < pThat->mcMonitors; i++)
+ {
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].u32Offset);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].u32MaxFramebufferSize);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].u32InformationSize);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].w);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].h);
+ pVMM->pfnSSMR3PutS32(pSSM, pThat->maFramebuffers[i].xOrigin);
+ pVMM->pfnSSMR3PutS32(pSSM, pThat->maFramebuffers[i].yOrigin);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].flags);
+ }
+ pVMM->pfnSSMR3PutS32(pSSM, pThat->xInputMappingOrigin);
+ pVMM->pfnSSMR3PutS32(pSSM, pThat->yInputMappingOrigin);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->cxInputMapping);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->cyInputMapping);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->mfGuestVBVACapabilities);
+ return pVMM->pfnSSMR3PutU32(pSSM, pThat->mfHostCursorCapabilities);
+}
+
+/**
+ * @callback_method_impl{FNSSMEXTLOADEXEC, Load some important guest state}
+ */
+/*static*/ DECLCALLBACK(int)
+Display::i_displaySSMLoad(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser, uint32_t uVersion, uint32_t uPass)
+{
+ Display * const pThat = static_cast<Display *>(pvUser);
+ AssertPtrReturn(pThat, VERR_INVALID_POINTER);
+
+ if ( uVersion != sSSMDisplayVer
+ && uVersion != sSSMDisplayVer2
+ && uVersion != sSSMDisplayVer3
+ && uVersion != sSSMDisplayVer4
+ && uVersion != sSSMDisplayVer5)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ uint32_t cMonitors;
+ int vrc = pVMM->pfnSSMR3GetU32(pSSM, &cMonitors);
+ AssertRCReturn(vrc, vrc);
+ if (cMonitors != pThat->mcMonitors)
+ return pVMM->pfnSSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Number of monitors changed (%d->%d)!"), cMonitors, pThat->mcMonitors);
+
+ for (uint32_t i = 0; i < cMonitors; i++)
+ {
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->maFramebuffers[i].u32Offset);
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->maFramebuffers[i].u32MaxFramebufferSize);
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->maFramebuffers[i].u32InformationSize);
+ if ( uVersion == sSSMDisplayVer2
+ || uVersion == sSSMDisplayVer3
+ || uVersion == sSSMDisplayVer4
+ || uVersion == sSSMDisplayVer5)
+ {
+ uint32_t w;
+ uint32_t h;
+ pVMM->pfnSSMR3GetU32(pSSM, &w);
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &h);
+ AssertRCReturn(vrc, vrc);
+ pThat->maFramebuffers[i].w = w;
+ pThat->maFramebuffers[i].h = h;
+ }
+ if ( uVersion == sSSMDisplayVer3
+ || uVersion == sSSMDisplayVer4
+ || uVersion == sSSMDisplayVer5)
+ {
+ int32_t xOrigin;
+ int32_t yOrigin;
+ uint32_t flags;
+ pVMM->pfnSSMR3GetS32(pSSM, &xOrigin);
+ pVMM->pfnSSMR3GetS32(pSSM, &yOrigin);
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &flags);
+ AssertRCReturn(vrc, vrc);
+ pThat->maFramebuffers[i].xOrigin = xOrigin;
+ pThat->maFramebuffers[i].yOrigin = yOrigin;
+ pThat->maFramebuffers[i].flags = (uint16_t)flags;
+ pThat->maFramebuffers[i].fDisabled = (pThat->maFramebuffers[i].flags & VBVA_SCREEN_F_DISABLED) != 0;
+ }
+ }
+ if ( uVersion == sSSMDisplayVer4
+ || uVersion == sSSMDisplayVer5)
+ {
+ pVMM->pfnSSMR3GetS32(pSSM, &pThat->xInputMappingOrigin);
+ pVMM->pfnSSMR3GetS32(pSSM, &pThat->yInputMappingOrigin);
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->cxInputMapping);
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->cyInputMapping);
+ }
+ if (uVersion == sSSMDisplayVer5)
+ {
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->mfGuestVBVACapabilities);
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->mfHostCursorCapabilities);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Initializes the display object.
+ *
+ * @returns COM result indicator
+ * @param aParent handle of our parent object
+ */
+HRESULT Display::init(Console *aParent)
+{
+ ComAssertRet(aParent, E_INVALIDARG);
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ mfSourceBitmapEnabled = true;
+ fVGAResizing = false;
+
+ ComPtr<IGraphicsAdapter> pGraphicsAdapter;
+ HRESULT hrc = mParent->i_machine()->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
+ AssertComRCReturnRC(hrc);
+ AssertReturn(!pGraphicsAdapter.isNull(), E_FAIL);
+
+ ULONG ul;
+ pGraphicsAdapter->COMGETTER(MonitorCount)(&ul);
+ mcMonitors = ul;
+ xInputMappingOrigin = 0;
+ yInputMappingOrigin = 0;
+ cxInputMapping = 0;
+ cyInputMapping = 0;
+
+ for (ul = 0; ul < mcMonitors; ul++)
+ {
+ maFramebuffers[ul].u32Offset = 0;
+ maFramebuffers[ul].u32MaxFramebufferSize = 0;
+ maFramebuffers[ul].u32InformationSize = 0;
+
+ maFramebuffers[ul].pFramebuffer = NULL;
+ /* All secondary monitors are disabled at startup. */
+ maFramebuffers[ul].fDisabled = ul > 0;
+
+ maFramebuffers[ul].u32Caps = 0;
+
+ maFramebuffers[ul].updateImage.pu8Address = NULL;
+ maFramebuffers[ul].updateImage.cbLine = 0;
+
+ maFramebuffers[ul].xOrigin = 0;
+ maFramebuffers[ul].yOrigin = 0;
+
+ maFramebuffers[ul].w = 0;
+ maFramebuffers[ul].h = 0;
+
+ maFramebuffers[ul].flags = maFramebuffers[ul].fDisabled? VBVA_SCREEN_F_DISABLED: 0;
+
+ maFramebuffers[ul].u16BitsPerPixel = 0;
+ maFramebuffers[ul].pu8FramebufferVRAM = NULL;
+ maFramebuffers[ul].u32LineSize = 0;
+
+ maFramebuffers[ul].pHostEvents = NULL;
+
+ maFramebuffers[ul].fDefaultFormat = false;
+
+#ifdef VBOX_WITH_HGSMI
+ maFramebuffers[ul].fVBVAEnabled = false;
+ maFramebuffers[ul].fVBVAForceResize = false;
+ maFramebuffers[ul].pVBVAHostFlags = NULL;
+#endif /* VBOX_WITH_HGSMI */
+ }
+
+ {
+ // register listener for state change events
+ ComPtr<IEventSource> es;
+ mParent->COMGETTER(EventSource)(es.asOutParam());
+ com::SafeArray<VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnStateChanged);
+ es->RegisterListener(this, ComSafeArrayAsInParam(eventTypes), true);
+ }
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void Display::uninit()
+{
+ LogRelFlowFunc(("this=%p\n", this));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unsigned uScreenId;
+ for (uScreenId = 0; uScreenId < mcMonitors; uScreenId++)
+ {
+ maFramebuffers[uScreenId].pSourceBitmap.setNull();
+ maFramebuffers[uScreenId].updateImage.pSourceBitmap.setNull();
+ maFramebuffers[uScreenId].updateImage.pu8Address = NULL;
+ maFramebuffers[uScreenId].updateImage.cbLine = 0;
+ maFramebuffers[uScreenId].pFramebuffer.setNull();
+#ifdef VBOX_WITH_RECORDING
+ maFramebuffers[uScreenId].Recording.pSourceBitmap.setNull();
+#endif
+ }
+
+ if (mParent)
+ {
+ ComPtr<IEventSource> es;
+ mParent->COMGETTER(EventSource)(es.asOutParam());
+ es->UnregisterListener(this);
+ }
+
+ unconst(mParent) = NULL;
+
+ if (mpDrv)
+ mpDrv->pDisplay = NULL;
+
+ mpDrv = NULL;
+}
+
+/**
+ * Register the SSM methods. Called by the power up thread to be able to
+ * pass pVM
+ */
+int Display::i_registerSSM(PUVM pUVM)
+{
+ PCVMMR3VTABLE const pVMM = mParent->i_getVMMVTable();
+ AssertPtrReturn(pVMM, VERR_INTERNAL_ERROR_3);
+
+ /* Version 2 adds width and height of the framebuffer; version 3 adds
+ * the framebuffer offset in the virtual desktop and the framebuffer flags;
+ * version 4 adds guest to host input event mapping and version 5 adds
+ * guest VBVA and host cursor capabilities.
+ */
+ int vrc = pVMM->pfnSSMR3RegisterExternal(pUVM, "DisplayData", 0, sSSMDisplayVer5,
+ mcMonitors * sizeof(uint32_t) * 8 + sizeof(uint32_t),
+ NULL, NULL, NULL,
+ NULL, i_displaySSMSave, NULL,
+ NULL, i_displaySSMLoad, NULL, this);
+ AssertRCReturn(vrc, vrc);
+
+ /*
+ * Register loaders for old saved states where iInstance was
+ * 3 * sizeof(uint32_t *) due to a code mistake.
+ */
+ vrc = pVMM->pfnSSMR3RegisterExternal(pUVM, "DisplayData", 12 /*uInstance*/, sSSMDisplayVer, 0 /*cbGuess*/,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, i_displaySSMLoad, NULL, this);
+ AssertRCReturn(vrc, vrc);
+
+ vrc = pVMM->pfnSSMR3RegisterExternal(pUVM, "DisplayData", 24 /*uInstance*/, sSSMDisplayVer, 0 /*cbGuess*/,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, i_displaySSMLoad, NULL, this);
+ AssertRCReturn(vrc, vrc);
+
+ /* uInstance is an arbitrary value greater than 1024. Such a value will ensure a quick seek in saved state file. */
+ vrc = pVMM->pfnSSMR3RegisterExternal(pUVM, "DisplayScreenshot", 1100 /*uInstance*/, sSSMDisplayScreenshotVer, 0 /*cbGuess*/,
+ NULL, NULL, NULL,
+ NULL, i_displaySSMSaveScreenshot, NULL,
+ NULL, i_displaySSMLoadScreenshot, NULL, this);
+
+ AssertRCReturn(vrc, vrc);
+
+ return VINF_SUCCESS;
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Handles display resize event.
+ *
+ * @param uScreenId Screen ID
+ * @param bpp New bits per pixel.
+ * @param pvVRAM VRAM pointer.
+ * @param cbLine New bytes per line.
+ * @param w New display width.
+ * @param h New display height.
+ * @param flags Flags of the new video mode.
+ * @param xOrigin New display origin X.
+ * @param yOrigin New display origin Y.
+ * @param fVGAResize Whether the resize is originated from the VGA device (DevVGA).
+ */
+int Display::i_handleDisplayResize(unsigned uScreenId, uint32_t bpp, void *pvVRAM,
+ uint32_t cbLine, uint32_t w, uint32_t h, uint16_t flags,
+ int32_t xOrigin, int32_t yOrigin, bool fVGAResize)
+{
+ LogRel2(("Display::i_handleDisplayResize: uScreenId=%d pvVRAM=%p w=%d h=%d bpp=%d cbLine=0x%X flags=0x%X\n", uScreenId,
+ pvVRAM, w, h, bpp, cbLine, flags));
+
+ /* Caller must not hold the object lock. */
+ AssertReturn(!isWriteLockOnCurrentThread(), VERR_INVALID_STATE);
+
+ /* Note: the old code checked if the video mode was actually changed and
+ * did not invalidate the source bitmap if the mode did not change.
+ * The new code always invalidates the source bitmap, i.e. it will
+ * notify the frontend even if nothing actually changed.
+ *
+ * Implementing the filtering is possible but might lead to pfnSetRenderVRAM races
+ * between this method and QuerySourceBitmap. Such races can be avoided by implementing
+ * the @todo below.
+ */
+
+ /* Make sure that the VGA device does not access the source bitmap. */
+ if (uScreenId == VBOX_VIDEO_PRIMARY_SCREEN && mpDrv)
+ {
+ /// @todo It is probably more convenient to implement
+ // mpDrv->pUpPort->pfnSetOutputBitmap(pvVRAM, cbScanline, cBits, cx, cy, bool fSet);
+ // and remove IConnector.pbData, cbScanline, cBits, cx, cy.
+ // fSet = false disables rendering and VGA can check
+ // if it is already rendering to a different bitmap, avoiding
+ // enable/disable rendering races.
+ mpDrv->pUpPort->pfnSetRenderVRAM(mpDrv->pUpPort, false);
+
+ mpDrv->IConnector.pbData = NULL;
+ mpDrv->IConnector.cbScanline = 0;
+ mpDrv->IConnector.cBits = 32; /* DevVGA does not work with cBits == 0. */
+ mpDrv->IConnector.cx = 0;
+ mpDrv->IConnector.cy = 0;
+ }
+
+ /* Update maFramebuffers[uScreenId] under lock. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (uScreenId >= mcMonitors)
+ {
+ LogRel(("Display::i_handleDisplayResize: mcMonitors=%u < uScreenId=%u (pvVRAM=%p w=%u h=%u bpp=%d cbLine=0x%X flags=0x%X)\n",
+ mcMonitors, uScreenId, pvVRAM, w, h, bpp, cbLine, flags));
+ return VINF_SUCCESS;
+ }
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId];
+
+ /* Whether the monitor position has changed.
+ * A resize initiated by the VGA device does not change the monitor position.
+ */
+ const bool fNewOrigin = !fVGAResize
+ && ( pFBInfo->xOrigin != xOrigin
+ || pFBInfo->yOrigin != yOrigin);
+
+ /* The event for disabled->enabled transition.
+ * VGA resizes also come when the guest uses VBVA mode. They do not affect pFBInfo->fDisabled.
+ * The primary screen is re-enabled when the guest leaves the VBVA mode in i_displayVBVADisable.
+ */
+ const bool fGuestMonitorChangedEvent = !fVGAResize
+ && (pFBInfo->fDisabled != RT_BOOL(flags & VBVA_SCREEN_F_DISABLED));
+
+ /* Reset the update mode. */
+ pFBInfo->updateImage.pSourceBitmap.setNull();
+ pFBInfo->updateImage.pu8Address = NULL;
+ pFBInfo->updateImage.cbLine = 0;
+
+ /* Release the current source bitmap. */
+ pFBInfo->pSourceBitmap.setNull();
+
+ /* VGA blanking is signaled as w=0, h=0, bpp=0 and cbLine=0, and it's
+ * best to keep the old resolution, as otherwise the window size would
+ * change before the new resolution is known. */
+ const bool fVGABlank = fVGAResize && uScreenId == VBOX_VIDEO_PRIMARY_SCREEN
+ && w == 0 && h == 0 && bpp == 0 && cbLine == 0;
+ if (fVGABlank)
+ {
+ w = pFBInfo->w;
+ h = pFBInfo->h;
+ }
+
+ /* Log changes. */
+ if ( pFBInfo->w != w
+ || pFBInfo->h != h
+ || pFBInfo->u32LineSize != cbLine
+ /*|| pFBInfo->pu8FramebufferVRAM != (uint8_t *)pvVRAM - too noisy */
+ || ( !fVGAResize
+ && ( pFBInfo->xOrigin != xOrigin
+ || pFBInfo->yOrigin != yOrigin
+ || pFBInfo->flags != flags)))
+ LogRel(("Display::i_handleDisplayResize: uScreenId=%d pvVRAM=%p w=%d h=%d bpp=%d cbLine=0x%X flags=0x%X origin=%d,%d\n",
+ uScreenId, pvVRAM, w, h, bpp, cbLine, flags, xOrigin, yOrigin));
+
+ /* Update the video mode information. */
+ pFBInfo->w = w;
+ pFBInfo->h = h;
+ pFBInfo->u16BitsPerPixel = (uint16_t)bpp;
+ pFBInfo->pu8FramebufferVRAM = (uint8_t *)pvVRAM;
+ pFBInfo->u32LineSize = cbLine;
+ if (!fVGAResize)
+ {
+ /* Fields which are not used in not VBVA modes and not affected by a VGA resize. */
+ pFBInfo->flags = flags;
+ pFBInfo->xOrigin = xOrigin;
+ pFBInfo->yOrigin = yOrigin;
+ pFBInfo->fDisabled = RT_BOOL(flags & VBVA_SCREEN_F_DISABLED);
+ pFBInfo->fVBVAForceResize = false;
+ }
+ else
+ {
+ pFBInfo->flags = VBVA_SCREEN_F_ACTIVE;
+ if (fVGABlank)
+ pFBInfo->flags |= VBVA_SCREEN_F_BLANK;
+ pFBInfo->fDisabled = false;
+ }
+
+ /* Prepare local vars for the notification code below. */
+ ComPtr<IFramebuffer> pFramebuffer = pFBInfo->pFramebuffer;
+ const bool fDisabled = pFBInfo->fDisabled;
+
+ alock.release();
+
+ if (!pFramebuffer.isNull())
+ {
+ HRESULT hr = pFramebuffer->NotifyChange(uScreenId, 0, 0, w, h); /** @todo origin */
+ LogFunc(("NotifyChange hr %08X\n", hr));
+ NOREF(hr);
+ }
+
+ if (fGuestMonitorChangedEvent)
+ {
+ if (fDisabled)
+ ::FireGuestMonitorChangedEvent(mParent->i_getEventSource(),
+ GuestMonitorChangedEventType_Disabled, uScreenId, 0, 0, 0, 0);
+ else
+ ::FireGuestMonitorChangedEvent(mParent->i_getEventSource(),
+ GuestMonitorChangedEventType_Enabled, uScreenId, xOrigin, yOrigin, w, h);
+ }
+
+ if (fNewOrigin)
+ ::FireGuestMonitorChangedEvent(mParent->i_getEventSource(),
+ GuestMonitorChangedEventType_NewOrigin, uScreenId, xOrigin, yOrigin, 0, 0);
+
+ /* Inform the VRDP server about the change of display parameters. */
+ LogRelFlowFunc(("Calling VRDP\n"));
+ mParent->i_consoleVRDPServer()->SendResize();
+
+ /* And re-send the seamless rectangles if necessary. */
+ if (mfSeamlessEnabled)
+ i_handleSetVisibleRegion(mcRectVisibleRegion, mpRectVisibleRegion);
+
+#ifdef VBOX_WITH_RECORDING
+ i_recordingScreenChanged(uScreenId);
+#endif
+
+ LogRelFlowFunc(("[%d]: default format %d\n", uScreenId, pFBInfo->fDefaultFormat));
+
+ return VINF_SUCCESS;
+}
+
+static void i_checkCoordBounds(int *px, int *py, int *pw, int *ph, int cx, int cy)
+{
+ /* Correct negative x and y coordinates. */
+ if (*px < 0)
+ {
+ *px += *pw; /* Compute xRight which is also the new width. */
+
+ *pw = (*px < 0)? 0: *px;
+
+ *px = 0;
+ }
+
+ if (*py < 0)
+ {
+ *py += *ph; /* Compute xBottom, which is also the new height. */
+
+ *ph = (*py < 0)? 0: *py;
+
+ *py = 0;
+ }
+
+ /* Also check if coords are greater than the display resolution. */
+ if (*px + *pw > cx)
+ {
+ *pw = cx > *px? cx - *px: 0;
+ }
+
+ if (*py + *ph > cy)
+ {
+ *ph = cy > *py? cy - *py: 0;
+ }
+}
+
+void Display::i_handleDisplayUpdate(unsigned uScreenId, int x, int y, int w, int h)
+{
+ /*
+ * Always runs under either VBVA lock or, for HGSMI, DevVGA lock.
+ * Safe to use VBVA vars and take the framebuffer lock.
+ */
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("[%d] %d,%d %dx%d\n",
+ uScreenId, x, y, w, h));
+#endif /* DEBUG_sunlover */
+
+ /* No updates for a disabled guest screen. */
+ if (maFramebuffers[uScreenId].fDisabled)
+ return;
+
+ /* No updates for a blank guest screen. */
+ /** @note Disabled for now, as the GUI does not update the picture when we
+ * first blank. */
+ /* if (maFramebuffers[uScreenId].flags & VBVA_SCREEN_F_BLANK)
+ return; */
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId];
+ AutoReadLock alockr(this COMMA_LOCKVAL_SRC_POS);
+
+ ComPtr<IFramebuffer> pFramebuffer = pFBInfo->pFramebuffer;
+ ComPtr<IDisplaySourceBitmap> pSourceBitmap = pFBInfo->updateImage.pSourceBitmap;
+
+ alockr.release();
+
+ if (RT_LIKELY(!pFramebuffer.isNull()))
+ {
+ if (RT_LIKELY(!RT_BOOL(pFBInfo->u32Caps & FramebufferCapabilities_UpdateImage)))
+ {
+ i_checkCoordBounds(&x, &y, &w, &h, pFBInfo->w, pFBInfo->h);
+
+ if (w != 0 && h != 0)
+ {
+ pFramebuffer->NotifyUpdate(x, y, w, h);
+ }
+ }
+ else
+ {
+ if (RT_LIKELY(!pSourceBitmap.isNull()))
+ { /* likely */ }
+ else
+ {
+ /* Create a source bitmap if UpdateImage mode is used. */
+ HRESULT hr = QuerySourceBitmap(uScreenId, pSourceBitmap.asOutParam());
+ if (SUCCEEDED(hr))
+ {
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ hr = pSourceBitmap->QueryBitmapInfo(&pAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+ if (SUCCEEDED(hr))
+ {
+ AutoWriteLock alockw(this COMMA_LOCKVAL_SRC_POS);
+
+ if (pFBInfo->updateImage.pSourceBitmap.isNull())
+ {
+ pFBInfo->updateImage.pSourceBitmap = pSourceBitmap;
+ pFBInfo->updateImage.pu8Address = pAddress;
+ pFBInfo->updateImage.cbLine = ulBytesPerLine;
+ }
+
+ pSourceBitmap = pFBInfo->updateImage.pSourceBitmap;
+
+ alockw.release();
+ }
+ }
+ }
+
+ if (RT_LIKELY(!pSourceBitmap.isNull()))
+ {
+ BYTE *pbAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ HRESULT hr = pSourceBitmap->QueryBitmapInfo(&pbAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+ if (SUCCEEDED(hr))
+ {
+ /* Make sure that the requested update is within the source bitmap dimensions. */
+ i_checkCoordBounds(&x, &y, &w, &h, ulWidth, ulHeight);
+
+ if (w != 0 && h != 0)
+ {
+ const size_t cbData = w * h * 4;
+ com::SafeArray<BYTE> image(cbData);
+
+ uint8_t *pu8Dst = image.raw();
+ const uint8_t *pu8Src = pbAddress + ulBytesPerLine * y + x * 4;
+
+ int i;
+ for (i = y; i < y + h; ++i)
+ {
+ memcpy(pu8Dst, pu8Src, w * 4);
+ pu8Dst += w * 4;
+ pu8Src += ulBytesPerLine;
+ }
+
+ pFramebuffer->NotifyUpdateImage(x, y, w, h, ComSafeArrayAsInParam(image));
+ }
+ }
+ }
+ }
+ }
+
+#ifndef VBOX_WITH_HGSMI
+ if (!mVideoAccelLegacy.fVideoAccelEnabled)
+#else
+ if (!mVideoAccelLegacy.fVideoAccelEnabled && !maFramebuffers[uScreenId].fVBVAEnabled)
+#endif
+ {
+ /* When VBVA is enabled, the VRDP server is informed
+ * either in VideoAccelFlush or displayVBVAUpdateProcess.
+ * Inform the server here only if VBVA is disabled.
+ */
+ mParent->i_consoleVRDPServer()->SendUpdateBitmap(uScreenId, x, y, w, h);
+ }
+}
+
+void Display::i_updateGuestGraphicsFacility(void)
+{
+ Guest* pGuest = mParent->i_getGuest();
+ AssertPtrReturnVoid(pGuest);
+ /* The following is from GuestImpl.cpp. */
+ /** @todo A nit: The timestamp is wrong on saved state restore. Would be better
+ * to move the graphics and seamless capability -> facility translation to
+ * VMMDev so this could be saved. */
+ RTTIMESPEC TimeSpecTS;
+ RTTimeNow(&TimeSpecTS);
+
+ if ( mfVMMDevSupportsGraphics
+ || (mfGuestVBVACapabilities & VBVACAPS_VIDEO_MODE_HINTS) != 0)
+ pGuest->i_setAdditionsStatus(VBoxGuestFacilityType_Graphics,
+ VBoxGuestFacilityStatus_Active,
+ 0 /*fFlags*/, &TimeSpecTS);
+ else
+ pGuest->i_setAdditionsStatus(VBoxGuestFacilityType_Graphics,
+ VBoxGuestFacilityStatus_Inactive,
+ 0 /*fFlags*/, &TimeSpecTS);
+}
+
+void Display::i_handleUpdateVMMDevSupportsGraphics(bool fSupportsGraphics)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mfVMMDevSupportsGraphics == fSupportsGraphics)
+ return;
+ mfVMMDevSupportsGraphics = fSupportsGraphics;
+ i_updateGuestGraphicsFacility();
+ /* The VMMDev interface notifies the console. */
+}
+
+void Display::i_handleUpdateGuestVBVACapabilities(uint32_t fNewCapabilities)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ bool fNotify = (fNewCapabilities & VBVACAPS_VIDEO_MODE_HINTS) != (mfGuestVBVACapabilities & VBVACAPS_VIDEO_MODE_HINTS);
+
+ mfGuestVBVACapabilities = fNewCapabilities;
+ if (!fNotify)
+ return;
+ i_updateGuestGraphicsFacility();
+ /* Tell the console about it */
+ mParent->i_onAdditionsStateChange();
+}
+
+void Display::i_handleUpdateVBVAInputMapping(int32_t xOrigin, int32_t yOrigin, uint32_t cx, uint32_t cy)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ xInputMappingOrigin = xOrigin;
+ yInputMappingOrigin = yOrigin;
+ cxInputMapping = cx;
+ cyInputMapping = cy;
+
+ /* Re-send the seamless rectangles if necessary. */
+ if (mfSeamlessEnabled)
+ i_handleSetVisibleRegion(mcRectVisibleRegion, mpRectVisibleRegion);
+}
+
+/**
+ * Returns the upper left and lower right corners of the virtual framebuffer.
+ * The lower right is "exclusive" (i.e. first pixel beyond the framebuffer),
+ * and the origin is (0, 0), not (1, 1) like the GUI returns.
+ */
+void Display::i_getFramebufferDimensions(int32_t *px1, int32_t *py1,
+ int32_t *px2, int32_t *py2)
+{
+ int32_t x1 = 0, y1 = 0, x2 = 0, y2 = 0;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertPtrReturnVoid(px1);
+ AssertPtrReturnVoid(py1);
+ AssertPtrReturnVoid(px2);
+ AssertPtrReturnVoid(py2);
+ LogRelFlowFunc(("\n"));
+
+ if (!mpDrv)
+ return;
+
+ if (maFramebuffers[0].fVBVAEnabled && cxInputMapping && cyInputMapping)
+ {
+ /* Guest uses VBVA with explicit mouse mapping dimensions. */
+ x1 = xInputMappingOrigin;
+ y1 = yInputMappingOrigin;
+ x2 = xInputMappingOrigin + cxInputMapping;
+ y2 = yInputMappingOrigin + cyInputMapping;
+ }
+ else
+ {
+ /* If VBVA is not in use then this flag will not be set and this
+ * will still work as it should. */
+ if (!maFramebuffers[0].fDisabled)
+ {
+ x1 = (int32_t)maFramebuffers[0].xOrigin;
+ y1 = (int32_t)maFramebuffers[0].yOrigin;
+ x2 = (int32_t)maFramebuffers[0].w + (int32_t)maFramebuffers[0].xOrigin;
+ y2 = (int32_t)maFramebuffers[0].h + (int32_t)maFramebuffers[0].yOrigin;
+ }
+
+ for (unsigned i = 1; i < mcMonitors; ++i)
+ {
+ if (!maFramebuffers[i].fDisabled)
+ {
+ x1 = RT_MIN(x1, maFramebuffers[i].xOrigin);
+ y1 = RT_MIN(y1, maFramebuffers[i].yOrigin);
+ x2 = RT_MAX(x2, maFramebuffers[i].xOrigin + (int32_t)maFramebuffers[i].w);
+ y2 = RT_MAX(y2, maFramebuffers[i].yOrigin + (int32_t)maFramebuffers[i].h);
+ }
+ }
+ }
+
+ *px1 = x1;
+ *py1 = y1;
+ *px2 = x2;
+ *py2 = y2;
+}
+
+/** Updates the device's view of the host cursor handling capabilities.
+ * Calls into mpDrv->pUpPort. */
+void Display::i_UpdateDeviceCursorCapabilities(void)
+{
+ bool fRenderCursor = true;
+ bool fMoveCursor = mcVRDPRefs == 0;
+#ifdef VBOX_WITH_RECORDING
+ RecordingContext *pCtx = mParent->i_recordingGetContext();
+
+ if ( pCtx
+ && pCtx->IsStarted()
+ && pCtx->IsFeatureEnabled(RecordingFeature_Video))
+ fRenderCursor = fMoveCursor = false;
+ else
+#endif /* VBOX_WITH_RECORDING */
+ {
+ for (unsigned uScreenId = 0; uScreenId < mcMonitors; uScreenId++)
+ {
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId];
+ if (!(pFBInfo->u32Caps & FramebufferCapabilities_RenderCursor))
+ fRenderCursor = false;
+ if (!(pFBInfo->u32Caps & FramebufferCapabilities_MoveCursor))
+ fMoveCursor = false;
+ }
+ }
+
+ if (mpDrv)
+ mpDrv->pUpPort->pfnReportHostCursorCapabilities(mpDrv->pUpPort, fRenderCursor, fMoveCursor);
+}
+
+HRESULT Display::i_reportHostCursorCapabilities(uint32_t fCapabilitiesAdded, uint32_t fCapabilitiesRemoved)
+{
+ /* Do we need this to access mParent? I presume that the safe VM pointer
+ * ensures that mpDrv will remain valid. */
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ uint32_t fHostCursorCapabilities = (mfHostCursorCapabilities | fCapabilitiesAdded)
+ & ~fCapabilitiesRemoved;
+
+ Console::SafeVMPtr ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+ if (mfHostCursorCapabilities == fHostCursorCapabilities)
+ return S_OK;
+ CHECK_CONSOLE_DRV(mpDrv);
+ alock.release(); /* Release before calling up for lock order reasons. */
+ mfHostCursorCapabilities = fHostCursorCapabilities;
+ i_UpdateDeviceCursorCapabilities();
+ return S_OK;
+}
+
+HRESULT Display::i_reportHostCursorPosition(int32_t x, int32_t y, bool fOutOfRange)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ uint32_t xAdj = (uint32_t)RT_MAX(x - xInputMappingOrigin, 0);
+ uint32_t yAdj = (uint32_t)RT_MAX(y - yInputMappingOrigin, 0);
+ xAdj = RT_MIN(xAdj, cxInputMapping);
+ yAdj = RT_MIN(yAdj, cyInputMapping);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+ CHECK_CONSOLE_DRV(mpDrv);
+ alock.release(); /* Release before calling up for lock order reasons. */
+ if (fOutOfRange)
+ mpDrv->pUpPort->pfnReportHostCursorPosition(mpDrv->pUpPort, 0, 0, true);
+ else
+ mpDrv->pUpPort->pfnReportHostCursorPosition(mpDrv->pUpPort, xAdj, yAdj, false);
+ return S_OK;
+}
+
+static bool displayIntersectRect(RTRECT *prectResult,
+ const RTRECT *prect1,
+ const RTRECT *prect2)
+{
+ /* Initialize result to an empty record. */
+ memset(prectResult, 0, sizeof(RTRECT));
+
+ int xLeftResult = RT_MAX(prect1->xLeft, prect2->xLeft);
+ int xRightResult = RT_MIN(prect1->xRight, prect2->xRight);
+
+ if (xLeftResult < xRightResult)
+ {
+ /* There is intersection by X. */
+
+ int yTopResult = RT_MAX(prect1->yTop, prect2->yTop);
+ int yBottomResult = RT_MIN(prect1->yBottom, prect2->yBottom);
+
+ if (yTopResult < yBottomResult)
+ {
+ /* There is intersection by Y. */
+
+ prectResult->xLeft = xLeftResult;
+ prectResult->yTop = yTopResult;
+ prectResult->xRight = xRightResult;
+ prectResult->yBottom = yBottomResult;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int Display::i_saveVisibleRegion(uint32_t cRect, PRTRECT pRect)
+{
+ RTRECT *pRectVisibleRegion = NULL;
+
+ if (pRect == mpRectVisibleRegion)
+ return VINF_SUCCESS;
+ if (cRect != 0)
+ {
+ pRectVisibleRegion = (RTRECT *)RTMemAlloc(cRect * sizeof(RTRECT));
+ if (!pRectVisibleRegion)
+ {
+ return VERR_NO_MEMORY;
+ }
+ memcpy(pRectVisibleRegion, pRect, cRect * sizeof(RTRECT));
+ }
+ if (mpRectVisibleRegion)
+ RTMemFree(mpRectVisibleRegion);
+ mcRectVisibleRegion = cRect;
+ mpRectVisibleRegion = pRectVisibleRegion;
+ return VINF_SUCCESS;
+}
+
+int Display::i_handleSetVisibleRegion(uint32_t cRect, PRTRECT pRect)
+{
+ RTRECT *pVisibleRegion = (RTRECT *)RTMemTmpAlloc( RT_MAX(cRect, 1)
+ * sizeof(RTRECT));
+ LogRel2(("%s: cRect=%u\n", __PRETTY_FUNCTION__, cRect));
+ if (!pVisibleRegion)
+ {
+ return VERR_NO_TMP_MEMORY;
+ }
+ int vrc = i_saveVisibleRegion(cRect, pRect);
+ if (RT_FAILURE(vrc))
+ {
+ RTMemTmpFree(pVisibleRegion);
+ return vrc;
+ }
+
+ unsigned uScreenId;
+ for (uScreenId = 0; uScreenId < mcMonitors; uScreenId++)
+ {
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId];
+
+ if ( !pFBInfo->pFramebuffer.isNull()
+ && RT_BOOL(pFBInfo->u32Caps & FramebufferCapabilities_VisibleRegion))
+ {
+ /* Prepare a new array of rectangles which intersect with the framebuffer.
+ */
+ RTRECT rectFramebuffer;
+ rectFramebuffer.xLeft = pFBInfo->xOrigin - xInputMappingOrigin;
+ rectFramebuffer.yTop = pFBInfo->yOrigin - yInputMappingOrigin;
+ rectFramebuffer.xRight = rectFramebuffer.xLeft + pFBInfo->w;
+ rectFramebuffer.yBottom = rectFramebuffer.yTop + pFBInfo->h;
+
+ uint32_t cRectVisibleRegion = 0;
+
+ uint32_t i;
+ for (i = 0; i < cRect; i++)
+ {
+ if (displayIntersectRect(&pVisibleRegion[cRectVisibleRegion], &pRect[i], &rectFramebuffer))
+ {
+ pVisibleRegion[cRectVisibleRegion].xLeft -= rectFramebuffer.xLeft;
+ pVisibleRegion[cRectVisibleRegion].yTop -= rectFramebuffer.yTop;
+ pVisibleRegion[cRectVisibleRegion].xRight -= rectFramebuffer.xLeft;
+ pVisibleRegion[cRectVisibleRegion].yBottom -= rectFramebuffer.yTop;
+
+ cRectVisibleRegion++;
+ }
+ }
+ pFBInfo->pFramebuffer->SetVisibleRegion((BYTE *)pVisibleRegion, cRectVisibleRegion);
+ }
+ }
+
+ RTMemTmpFree(pVisibleRegion);
+
+ return VINF_SUCCESS;
+}
+
+int Display::i_handleUpdateMonitorPositions(uint32_t cPositions, PCRTPOINT paPositions)
+{
+ AssertMsgReturn(paPositions, ("Empty monitor position array\n"), E_INVALIDARG);
+ for (unsigned i = 0; i < cPositions; ++i)
+ LogRel2(("Display::i_handleUpdateMonitorPositions: uScreenId=%d xOrigin=%d yOrigin=%dX\n",
+ i, paPositions[i].x, paPositions[i].y));
+
+ if (mpDrv && mpDrv->pUpPort->pfnReportMonitorPositions)
+ mpDrv->pUpPort->pfnReportMonitorPositions(mpDrv->pUpPort, cPositions, paPositions);
+ return VINF_SUCCESS;
+}
+
+int Display::i_handleQueryVisibleRegion(uint32_t *pcRects, PRTRECT paRects)
+{
+ /// @todo Currently not used by the guest and is not implemented in
+ /// framebuffers. Remove?
+ RT_NOREF(pcRects, paRects);
+ return VERR_NOT_SUPPORTED;
+}
+
+#ifdef VBOX_WITH_HGSMI
+static void vbvaSetMemoryFlagsHGSMI(unsigned uScreenId,
+ uint32_t fu32SupportedOrders,
+ bool fVideoAccelVRDP,
+ DISPLAYFBINFO *pFBInfo)
+{
+ LogRelFlowFunc(("HGSMI[%d]: %p\n", uScreenId, pFBInfo->pVBVAHostFlags));
+
+ if (pFBInfo->pVBVAHostFlags)
+ {
+ uint32_t fu32HostEvents = VBOX_VIDEO_INFO_HOST_EVENTS_F_VRDP_RESET;
+
+ if (pFBInfo->fVBVAEnabled)
+ {
+ fu32HostEvents |= VBVA_F_MODE_ENABLED;
+
+ if (fVideoAccelVRDP)
+ {
+ fu32HostEvents |= VBVA_F_MODE_VRDP;
+ }
+ }
+
+ ASMAtomicWriteU32(&pFBInfo->pVBVAHostFlags->u32HostEvents, fu32HostEvents);
+ ASMAtomicWriteU32(&pFBInfo->pVBVAHostFlags->u32SupportedOrders, fu32SupportedOrders);
+
+ LogRelFlowFunc((" fu32HostEvents = 0x%08X, fu32SupportedOrders = 0x%08X\n", fu32HostEvents, fu32SupportedOrders));
+ }
+}
+
+static void vbvaSetMemoryFlagsAllHGSMI(uint32_t fu32SupportedOrders,
+ bool fVideoAccelVRDP,
+ DISPLAYFBINFO *paFBInfos,
+ unsigned cFBInfos)
+{
+ unsigned uScreenId;
+
+ for (uScreenId = 0; uScreenId < cFBInfos; uScreenId++)
+ {
+ vbvaSetMemoryFlagsHGSMI(uScreenId, fu32SupportedOrders, fVideoAccelVRDP, &paFBInfos[uScreenId]);
+ }
+}
+#endif /* VBOX_WITH_HGSMI */
+
+int Display::VideoAccelEnableVMMDev(bool fEnable, VBVAMEMORY *pVbvaMemory)
+{
+ LogFlowFunc(("%d %p\n", fEnable, pVbvaMemory));
+ int vrc = videoAccelEnterVMMDev(&mVideoAccelLegacy);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = i_VideoAccelEnable(fEnable, pVbvaMemory, mpDrv->pUpPort);
+ videoAccelLeaveVMMDev(&mVideoAccelLegacy);
+ }
+ LogFlowFunc(("leave %Rrc\n", vrc));
+ return vrc;
+}
+
+int Display::VideoAccelEnableVGA(bool fEnable, VBVAMEMORY *pVbvaMemory)
+{
+ LogFlowFunc(("%d %p\n", fEnable, pVbvaMemory));
+ int vrc = videoAccelEnterVGA(&mVideoAccelLegacy);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = i_VideoAccelEnable(fEnable, pVbvaMemory, mpDrv->pUpPort);
+ videoAccelLeaveVGA(&mVideoAccelLegacy);
+ }
+ LogFlowFunc(("leave %Rrc\n", vrc));
+ return vrc;
+}
+
+void Display::VideoAccelFlushVMMDev(void)
+{
+ LogFlowFunc(("enter\n"));
+ int vrc = videoAccelEnterVMMDev(&mVideoAccelLegacy);
+ if (RT_SUCCESS(vrc))
+ {
+ i_VideoAccelFlush(mpDrv->pUpPort);
+ videoAccelLeaveVMMDev(&mVideoAccelLegacy);
+ }
+ LogFlowFunc(("leave\n"));
+}
+
+/* Called always by one VRDP server thread. Can be thread-unsafe.
+ */
+void Display::i_VRDPConnectionEvent(bool fConnect)
+{
+ LogRelFlowFunc(("fConnect = %d\n", fConnect));
+
+ int c = fConnect?
+ ASMAtomicIncS32(&mcVRDPRefs):
+ ASMAtomicDecS32(&mcVRDPRefs);
+
+ i_VideoAccelVRDP(fConnect, c);
+ i_UpdateDeviceCursorCapabilities();
+}
+
+
+void Display::i_VideoAccelVRDP(bool fEnable, int c)
+{
+ VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy;
+
+ Assert (c >= 0);
+ RT_NOREF(fEnable);
+
+ /* This can run concurrently with Display videoaccel state change. */
+ RTCritSectEnter(&mVideoAccelLock);
+
+ if (c == 0)
+ {
+ /* The last client has disconnected, and the accel can be
+ * disabled.
+ */
+ Assert(fEnable == false);
+
+ mfVideoAccelVRDP = false;
+ mfu32SupportedOrders = 0;
+
+ i_vbvaSetMemoryFlags(pVideoAccel->pVbvaMemory, pVideoAccel->fVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders,
+ maFramebuffers, mcMonitors);
+#ifdef VBOX_WITH_HGSMI
+ /* Here is VRDP-IN thread. Process the request in vbvaUpdateBegin under DevVGA lock on an EMT. */
+ ASMAtomicIncU32(&mu32UpdateVBVAFlags);
+#endif /* VBOX_WITH_HGSMI */
+
+ LogRel(("VBVA: VRDP acceleration has been disabled.\n"));
+ }
+ else if ( c == 1
+ && !mfVideoAccelVRDP)
+ {
+ /* The first client has connected. Enable the accel.
+ */
+ Assert(fEnable == true);
+
+ mfVideoAccelVRDP = true;
+ /* Supporting all orders. */
+ mfu32SupportedOrders = UINT32_MAX;
+
+ i_vbvaSetMemoryFlags(pVideoAccel->pVbvaMemory, pVideoAccel->fVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders,
+ maFramebuffers, mcMonitors);
+#ifdef VBOX_WITH_HGSMI
+ /* Here is VRDP-IN thread. Process the request in vbvaUpdateBegin under DevVGA lock on an EMT. */
+ ASMAtomicIncU32(&mu32UpdateVBVAFlags);
+#endif /* VBOX_WITH_HGSMI */
+
+ LogRel(("VBVA: VRDP acceleration has been requested.\n"));
+ }
+ else
+ {
+ /* A client is connected or disconnected but there is no change in the
+ * accel state. It remains enabled.
+ */
+ Assert(mfVideoAccelVRDP == true);
+ }
+
+ RTCritSectLeave(&mVideoAccelLock);
+}
+
+void Display::i_notifyPowerDown(void)
+{
+ LogRelFlowFunc(("\n"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Source bitmaps are not available anymore. */
+ mfSourceBitmapEnabled = false;
+
+ alock.release();
+
+ /* Resize all displays to tell framebuffers to forget current source bitmap. */
+ unsigned uScreenId = mcMonitors;
+ while (uScreenId > 0)
+ {
+ --uScreenId;
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId];
+ if (!pFBInfo->fDisabled)
+ {
+ i_handleDisplayResize(uScreenId, 32,
+ pFBInfo->pu8FramebufferVRAM,
+ pFBInfo->u32LineSize,
+ pFBInfo->w,
+ pFBInfo->h,
+ pFBInfo->flags,
+ pFBInfo->xOrigin,
+ pFBInfo->yOrigin,
+ false);
+ }
+ }
+}
+
+// Wrapped IDisplay methods
+/////////////////////////////////////////////////////////////////////////////
+HRESULT Display::getScreenResolution(ULONG aScreenId, ULONG *aWidth, ULONG *aHeight, ULONG *aBitsPerPixel,
+ LONG *aXOrigin, LONG *aYOrigin, GuestMonitorStatus_T *aGuestMonitorStatus)
+{
+ LogRelFlowFunc(("aScreenId=%RU32\n", aScreenId));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aScreenId >= mcMonitors)
+ return E_INVALIDARG;
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId];
+
+ GuestMonitorStatus_T guestMonitorStatus = GuestMonitorStatus_Enabled;
+
+ if (pFBInfo->flags & VBVA_SCREEN_F_DISABLED)
+ guestMonitorStatus = GuestMonitorStatus_Disabled;
+ else if (pFBInfo->flags & (VBVA_SCREEN_F_BLANK | VBVA_SCREEN_F_BLANK2))
+ guestMonitorStatus = GuestMonitorStatus_Blank;
+
+ if (aWidth)
+ *aWidth = pFBInfo->w;
+ if (aHeight)
+ *aHeight = pFBInfo->h;
+ if (aBitsPerPixel)
+ *aBitsPerPixel = pFBInfo->u16BitsPerPixel;
+ if (aXOrigin)
+ *aXOrigin = pFBInfo->xOrigin;
+ if (aYOrigin)
+ *aYOrigin = pFBInfo->yOrigin;
+ if (aGuestMonitorStatus)
+ *aGuestMonitorStatus = guestMonitorStatus;
+
+ return S_OK;
+}
+
+
+HRESULT Display::attachFramebuffer(ULONG aScreenId, const ComPtr<IFramebuffer> &aFramebuffer, com::Guid &aId)
+{
+ LogRelFlowFunc(("aScreenId = %d\n", aScreenId));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aScreenId >= mcMonitors)
+ return setError(E_INVALIDARG, tr("AttachFramebuffer: Invalid screen %d (total %d)"),
+ aScreenId, mcMonitors);
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId];
+ if (!pFBInfo->pFramebuffer.isNull())
+ return setError(E_FAIL, tr("AttachFramebuffer: Framebuffer already attached to %d"),
+ aScreenId);
+
+ pFBInfo->pFramebuffer = aFramebuffer;
+ pFBInfo->framebufferId.create();
+ aId = pFBInfo->framebufferId;
+
+ SafeArray<FramebufferCapabilities_T> caps;
+ pFBInfo->pFramebuffer->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(caps));
+ pFBInfo->u32Caps = 0;
+ size_t i;
+ for (i = 0; i < caps.size(); ++i)
+ pFBInfo->u32Caps |= caps[i];
+
+ alock.release();
+
+ /* The driver might not have been constructed yet */
+ if (mpDrv)
+ {
+ /* Inform the framebuffer about the actual screen size. */
+ HRESULT hr = aFramebuffer->NotifyChange(aScreenId, 0, 0, pFBInfo->w, pFBInfo->h); /** @todo origin */
+ LogFunc(("NotifyChange hr %08X\n", hr)); NOREF(hr);
+
+ /* Re-send the seamless rectangles if necessary. */
+ if (mfSeamlessEnabled)
+ i_handleSetVisibleRegion(mcRectVisibleRegion, mpRectVisibleRegion);
+ }
+
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ ptrVM.vtable()->pfnVMR3ReqCallNoWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT,
+ 3, this, aScreenId, false);
+
+ LogRelFlowFunc(("Attached to %d %RTuuid\n", aScreenId, aId.raw()));
+ return S_OK;
+}
+
+HRESULT Display::detachFramebuffer(ULONG aScreenId, const com::Guid &aId)
+{
+ LogRelFlowFunc(("aScreenId = %d %RTuuid\n", aScreenId, aId.raw()));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aScreenId >= mcMonitors)
+ return setError(E_INVALIDARG, tr("DetachFramebuffer: Invalid screen %d (total %d)"),
+ aScreenId, mcMonitors);
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId];
+
+ if (pFBInfo->framebufferId != aId)
+ {
+ LogRelFlowFunc(("Invalid framebuffer aScreenId = %d, attached %p\n", aScreenId, pFBInfo->framebufferId.raw()));
+ return setError(E_FAIL, tr("DetachFramebuffer: Invalid framebuffer object"));
+ }
+
+ pFBInfo->pFramebuffer.setNull();
+ pFBInfo->framebufferId.clear();
+
+ alock.release();
+ return S_OK;
+}
+
+HRESULT Display::queryFramebuffer(ULONG aScreenId, ComPtr<IFramebuffer> &aFramebuffer)
+{
+ LogRelFlowFunc(("aScreenId = %d\n", aScreenId));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aScreenId >= mcMonitors)
+ return setError(E_INVALIDARG, tr("QueryFramebuffer: Invalid screen %d (total %d)"),
+ aScreenId, mcMonitors);
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId];
+
+ pFBInfo->pFramebuffer.queryInterfaceTo(aFramebuffer.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Display::setVideoModeHint(ULONG aDisplay, BOOL aEnabled,
+ BOOL aChangeOrigin, LONG aOriginX, LONG aOriginY,
+ ULONG aWidth, ULONG aHeight, ULONG aBitsPerPixel,
+ BOOL aNotify)
+{
+ if (aWidth == 0 || aHeight == 0 || aBitsPerPixel == 0)
+ {
+ /* Some of parameters must not change. Query current mode. */
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ HRESULT hr = getScreenResolution(aDisplay, &ulWidth, &ulHeight, &ulBitsPerPixel, NULL, NULL, NULL);
+ if (FAILED(hr))
+ return hr;
+
+ /* Assign current values to not changing parameters. */
+ if (aWidth == 0)
+ aWidth = ulWidth;
+ if (aHeight == 0)
+ aHeight = ulHeight;
+ if (aBitsPerPixel == 0)
+ aBitsPerPixel = ulBitsPerPixel;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aDisplay >= mcMonitors)
+ return E_INVALIDARG;
+
+ VMMDevDisplayDef d;
+ d.idDisplay = aDisplay;
+ d.xOrigin = aOriginX;
+ d.yOrigin = aOriginY;
+ d.cx = aWidth;
+ d.cy = aHeight;
+ d.cBitsPerPixel = aBitsPerPixel;
+ d.fDisplayFlags = VMMDEV_DISPLAY_CX | VMMDEV_DISPLAY_CY | VMMDEV_DISPLAY_BPP;
+ if (!aEnabled)
+ d.fDisplayFlags |= VMMDEV_DISPLAY_DISABLED;
+ if (aChangeOrigin)
+ d.fDisplayFlags |= VMMDEV_DISPLAY_ORIGIN;
+ if (aDisplay == 0)
+ d.fDisplayFlags |= VMMDEV_DISPLAY_PRIMARY;
+
+ /* Remember the monitor information. */
+ maFramebuffers[aDisplay].monitorDesc = d;
+
+ CHECK_CONSOLE_DRV(mpDrv);
+
+ /*
+ * It is up to the guest to decide whether the hint is
+ * valid. Therefore don't do any VRAM sanity checks here.
+ */
+
+ /* Have to release the lock because the pfnRequestDisplayChange
+ * will call EMT. */
+ alock.release();
+
+ /* We always send the hint to the graphics card in case the guest enables
+ * support later. For now we notify exactly when support is enabled. */
+ mpDrv->pUpPort->pfnSendModeHint(mpDrv->pUpPort, aWidth, aHeight,
+ aBitsPerPixel, aDisplay,
+ aChangeOrigin ? aOriginX : ~0,
+ aChangeOrigin ? aOriginY : ~0,
+ RT_BOOL(aEnabled),
+ (mfGuestVBVACapabilities & VBVACAPS_VIDEO_MODE_HINTS)
+ && aNotify);
+ if ( mfGuestVBVACapabilities & VBVACAPS_VIDEO_MODE_HINTS
+ && !(mfGuestVBVACapabilities & VBVACAPS_IRQ)
+ && aNotify)
+ mParent->i_sendACPIMonitorHotPlugEvent();
+
+ /* We currently never suppress the VMMDev hint if the guest has requested
+ * it. Specifically the video graphics driver may not be responsible for
+ * screen positioning in the guest virtual desktop, and the component
+ * responsible may want to get the hint from VMMDev. */
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ pVMMDevPort->pfnRequestDisplayChange(pVMMDevPort, 1, &d, false, RT_BOOL(aNotify));
+ }
+ /* Notify listeners. */
+ ::FireGuestMonitorInfoChangedEvent(mParent->i_getEventSource(), aDisplay);
+ return S_OK;
+}
+
+HRESULT Display::getVideoModeHint(ULONG cDisplay, BOOL *pfEnabled,
+ BOOL *pfChangeOrigin, LONG *pxOrigin, LONG *pyOrigin,
+ ULONG *pcx, ULONG *pcy, ULONG *pcBitsPerPixel)
+{
+ if (cDisplay >= mcMonitors)
+ return E_INVALIDARG;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (pfEnabled)
+ *pfEnabled = !( maFramebuffers[cDisplay].monitorDesc.fDisplayFlags
+ & VMMDEV_DISPLAY_DISABLED);
+ if (pfChangeOrigin)
+ *pfChangeOrigin = RT_BOOL( maFramebuffers[cDisplay].monitorDesc.fDisplayFlags
+ & VMMDEV_DISPLAY_ORIGIN);
+ if (pxOrigin)
+ *pxOrigin = maFramebuffers[cDisplay].monitorDesc.xOrigin;
+ if (pyOrigin)
+ *pyOrigin = maFramebuffers[cDisplay].monitorDesc.yOrigin;
+ if (pcx)
+ *pcx = maFramebuffers[cDisplay].monitorDesc.cx;
+ if (pcy)
+ *pcy = maFramebuffers[cDisplay].monitorDesc.cy;
+ if (pcBitsPerPixel)
+ *pcBitsPerPixel = maFramebuffers[cDisplay].monitorDesc.cBitsPerPixel;
+ return S_OK;
+}
+
+HRESULT Display::setSeamlessMode(BOOL enabled)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Have to release the lock because the pfnRequestSeamlessChange will call EMT. */
+ alock.release();
+
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ pVMMDevPort->pfnRequestSeamlessChange(pVMMDevPort, !!enabled);
+ }
+ mfSeamlessEnabled = RT_BOOL(enabled);
+ return S_OK;
+}
+
+/*static*/ DECLCALLBACK(int)
+Display::i_displayTakeScreenshotEMT(Display *pDisplay, ULONG aScreenId, uint8_t **ppbData, size_t *pcbData,
+ uint32_t *pcx, uint32_t *pcy, bool *pfMemFree)
+{
+ int vrc;
+ if ( aScreenId == VBOX_VIDEO_PRIMARY_SCREEN
+ && pDisplay->maFramebuffers[aScreenId].fVBVAEnabled == false) /* A non-VBVA mode. */
+ {
+ if (pDisplay->mpDrv)
+ {
+ vrc = pDisplay->mpDrv->pUpPort->pfnTakeScreenshot(pDisplay->mpDrv->pUpPort, ppbData, pcbData, pcx, pcy);
+ *pfMemFree = false;
+ }
+ else
+ {
+ /* No image. */
+ *ppbData = NULL;
+ *pcbData = 0;
+ *pcx = 0;
+ *pcy = 0;
+ *pfMemFree = true;
+ vrc = VINF_SUCCESS;
+ }
+ }
+ else if (aScreenId < pDisplay->mcMonitors)
+ {
+ DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[aScreenId];
+
+ uint32_t width = pFBInfo->w;
+ uint32_t height = pFBInfo->h;
+
+ /* Allocate 32 bit per pixel bitmap. */
+ size_t cbRequired = width * 4 * height;
+
+ if (cbRequired)
+ {
+ uint8_t *pbDst = (uint8_t *)RTMemAlloc(cbRequired);
+ if (pbDst != NULL)
+ {
+ if (pFBInfo->flags & VBVA_SCREEN_F_ACTIVE)
+ {
+ /* Copy guest VRAM to the allocated 32bpp buffer. */
+ const uint8_t *pu8Src = pFBInfo->pu8FramebufferVRAM;
+ int32_t xSrc = 0;
+ int32_t ySrc = 0;
+ uint32_t u32SrcWidth = width;
+ uint32_t u32SrcHeight = height;
+ uint32_t u32SrcLineSize = pFBInfo->u32LineSize;
+ uint32_t u32SrcBitsPerPixel = pFBInfo->u16BitsPerPixel;
+
+ int32_t xDst = 0;
+ int32_t yDst = 0;
+ uint32_t u32DstWidth = u32SrcWidth;
+ uint32_t u32DstHeight = u32SrcHeight;
+ uint32_t u32DstLineSize = u32DstWidth * 4;
+ uint32_t u32DstBitsPerPixel = 32;
+
+ vrc = pDisplay->mpDrv->pUpPort->pfnCopyRect(pDisplay->mpDrv->pUpPort,
+ width, height,
+ pu8Src,
+ xSrc, ySrc,
+ u32SrcWidth, u32SrcHeight,
+ u32SrcLineSize, u32SrcBitsPerPixel,
+ pbDst,
+ xDst, yDst,
+ u32DstWidth, u32DstHeight,
+ u32DstLineSize, u32DstBitsPerPixel);
+ }
+ else
+ {
+ memset(pbDst, 0, cbRequired);
+ vrc = VINF_SUCCESS;
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ *ppbData = pbDst;
+ *pcbData = cbRequired;
+ *pcx = width;
+ *pcy = height;
+ *pfMemFree = true;
+ }
+ else
+ {
+ RTMemFree(pbDst);
+
+ /* CopyRect can fail if VBVA was paused in VGA device, retry using the generic method. */
+ if ( vrc == VERR_INVALID_STATE
+ && aScreenId == VBOX_VIDEO_PRIMARY_SCREEN)
+ {
+ vrc = pDisplay->mpDrv->pUpPort->pfnTakeScreenshot(pDisplay->mpDrv->pUpPort, ppbData, pcbData, pcx, pcy);
+ *pfMemFree = false;
+ }
+ }
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* No image. */
+ *ppbData = NULL;
+ *pcbData = 0;
+ *pcx = 0;
+ *pcy = 0;
+ *pfMemFree = true;
+ vrc = VINF_SUCCESS;
+ }
+ }
+ else
+ vrc = VERR_INVALID_PARAMETER;
+ return vrc;
+}
+
+static int i_displayTakeScreenshot(PUVM pUVM, PCVMMR3VTABLE pVMM, Display *pDisplay, struct DRVMAINDISPLAY *pDrv,
+ ULONG aScreenId, BYTE *address, ULONG width, ULONG height)
+{
+ uint8_t *pbData = NULL;
+ size_t cbData = 0;
+ uint32_t cx = 0;
+ uint32_t cy = 0;
+ bool fFreeMem = false;
+ int vrc = VINF_SUCCESS;
+
+ int cRetries = 5;
+ while (cRetries-- > 0)
+ {
+ /* Note! Not sure if the priority call is such a good idea here, but
+ it would be nice to have an accurate screenshot for the bug
+ report if the VM deadlocks. */
+ vrc = pVMM->pfnVMR3ReqPriorityCallWaitU(pUVM, VMCPUID_ANY, (PFNRT)Display::i_displayTakeScreenshotEMT, 7,
+ pDisplay, aScreenId, &pbData, &cbData, &cx, &cy, &fFreeMem);
+ if (vrc != VERR_TRY_AGAIN)
+ {
+ break;
+ }
+
+ RTThreadSleep(10);
+ }
+
+ if (RT_SUCCESS(vrc) && pbData)
+ {
+ if (cx == width && cy == height)
+ {
+ /* No scaling required. */
+ memcpy(address, pbData, cbData);
+ }
+ else
+ {
+ /* Scale. */
+ LogRelFlowFunc(("SCALE: %dx%d -> %dx%d\n", cx, cy, width, height));
+
+ uint8_t *dst = address;
+ uint8_t *src = pbData;
+ int dstW = width;
+ int dstH = height;
+ int srcW = cx;
+ int srcH = cy;
+ int iDeltaLine = cx * 4;
+
+ BitmapScale32(dst,
+ dstW, dstH,
+ src,
+ iDeltaLine,
+ srcW, srcH);
+ }
+
+ if (fFreeMem)
+ RTMemFree(pbData);
+ else
+ {
+ /* This can be called from any thread. */
+ pDrv->pUpPort->pfnFreeScreenshot(pDrv->pUpPort, pbData);
+ }
+ }
+
+ return vrc;
+}
+
+HRESULT Display::takeScreenShotWorker(ULONG aScreenId,
+ BYTE *aAddress,
+ ULONG aWidth,
+ ULONG aHeight,
+ BitmapFormat_T aBitmapFormat,
+ ULONG *pcbOut)
+{
+ HRESULT hrc = S_OK;
+
+ /* Do not allow too small and too large screenshots. This also filters out negative
+ * values passed as either 'aWidth' or 'aHeight'.
+ */
+ CheckComArgExpr(aWidth, aWidth != 0 && aWidth <= 32767);
+ CheckComArgExpr(aHeight, aHeight != 0 && aHeight <= 32767);
+
+ if ( aBitmapFormat != BitmapFormat_BGR0
+ && aBitmapFormat != BitmapFormat_BGRA
+ && aBitmapFormat != BitmapFormat_RGBA
+ && aBitmapFormat != BitmapFormat_PNG)
+ {
+ return setError(E_NOTIMPL,
+ tr("Unsupported screenshot format 0x%08X"), aBitmapFormat);
+ }
+
+ Console::SafeVMPtr ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ int vrc = i_displayTakeScreenshot(ptrVM.rawUVM(), ptrVM.vtable(), this, mpDrv, aScreenId, aAddress, aWidth, aHeight);
+ if (RT_SUCCESS(vrc))
+ {
+ const size_t cbData = aWidth * 4 * aHeight;
+
+ /* Most of uncompressed formats. */
+ *pcbOut = (ULONG)cbData;
+
+ if (aBitmapFormat == BitmapFormat_BGR0)
+ {
+ /* Do nothing. */
+ }
+ else if (aBitmapFormat == BitmapFormat_BGRA)
+ {
+ uint32_t *pu32 = (uint32_t *)aAddress;
+ size_t cPixels = aWidth * aHeight;
+ while (cPixels--)
+ *pu32++ |= UINT32_C(0xFF000000);
+ }
+ else if (aBitmapFormat == BitmapFormat_RGBA)
+ {
+ uint8_t *pu8 = aAddress;
+ size_t cPixels = aWidth * aHeight;
+ while (cPixels--)
+ {
+ uint8_t u8 = pu8[0];
+ pu8[0] = pu8[2];
+ pu8[2] = u8;
+ pu8[3] = 0xFF;
+
+ pu8 += 4;
+ }
+ }
+ else if (aBitmapFormat == BitmapFormat_PNG)
+ {
+ uint8_t *pu8PNG = NULL;
+ uint32_t cbPNG = 0;
+ uint32_t cxPNG = 0;
+ uint32_t cyPNG = 0;
+
+ vrc = DisplayMakePNG(aAddress, aWidth, aHeight, &pu8PNG, &cbPNG, &cxPNG, &cyPNG, 0);
+ if (RT_SUCCESS(vrc))
+ {
+ if (cbPNG <= cbData)
+ {
+ memcpy(aAddress, pu8PNG, cbPNG);
+ *pcbOut = cbPNG;
+ }
+ else
+ hrc = setError(E_FAIL, tr("PNG is larger than 32bpp bitmap"));
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not convert screenshot to PNG (%Rrc)"), vrc);
+ RTMemFree(pu8PNG);
+ }
+ }
+ else if (vrc == VERR_TRY_AGAIN)
+ hrc = setErrorBoth(E_UNEXPECTED, vrc, tr("Screenshot is not available at this time"));
+ else if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not take a screenshot (%Rrc)"), vrc);
+
+ return hrc;
+}
+
+HRESULT Display::takeScreenShot(ULONG aScreenId,
+ BYTE *aAddress,
+ ULONG aWidth,
+ ULONG aHeight,
+ BitmapFormat_T aBitmapFormat)
+{
+ LogRelFlowFunc(("[%d] address=%p, width=%d, height=%d, format 0x%08X\n",
+ aScreenId, aAddress, aWidth, aHeight, aBitmapFormat));
+
+ ULONG cbOut = 0;
+ HRESULT hrc = takeScreenShotWorker(aScreenId, aAddress, aWidth, aHeight, aBitmapFormat, &cbOut);
+ NOREF(cbOut);
+
+ LogRelFlowFunc(("%Rhrc\n", hrc));
+ return hrc;
+}
+
+HRESULT Display::takeScreenShotToArray(ULONG aScreenId,
+ ULONG aWidth,
+ ULONG aHeight,
+ BitmapFormat_T aBitmapFormat,
+ std::vector<BYTE> &aScreenData)
+{
+ LogRelFlowFunc(("[%d] width=%d, height=%d, format 0x%08X\n",
+ aScreenId, aWidth, aHeight, aBitmapFormat));
+
+ /* Do not allow too small and too large screenshots. This also filters out negative
+ * values passed as either 'aWidth' or 'aHeight'.
+ */
+ CheckComArgExpr(aWidth, aWidth != 0 && aWidth <= 32767);
+ CheckComArgExpr(aHeight, aHeight != 0 && aHeight <= 32767);
+
+ const size_t cbData = aWidth * 4 * aHeight;
+ aScreenData.resize(cbData);
+
+ ULONG cbOut = 0;
+ HRESULT hrc = takeScreenShotWorker(aScreenId, &aScreenData.front(), aWidth, aHeight, aBitmapFormat, &cbOut);
+ if (FAILED(hrc))
+ cbOut = 0;
+
+ aScreenData.resize(cbOut);
+
+ LogRelFlowFunc(("%Rhrc\n", hrc));
+ return hrc;
+}
+
+#ifdef VBOX_WITH_RECORDING
+/**
+ * Invalidates the recording configuration.
+ *
+ * @returns IPRT status code.
+ */
+int Display::i_recordingInvalidate(void)
+{
+ RecordingContext *pCtx = mParent->i_recordingGetContext();
+ if (!pCtx || !pCtx->IsStarted())
+ return VINF_SUCCESS;
+
+ /*
+ * Invalidate screens.
+ */
+ for (unsigned uScreen = 0; uScreen < mcMonitors; uScreen++)
+ {
+ RecordingStream *pRecordingStream = pCtx->GetStream(uScreen);
+
+ const bool fStreamEnabled = pRecordingStream->IsReady();
+ bool fChanged = maRecordingEnabled[uScreen] != fStreamEnabled;
+
+ maRecordingEnabled[uScreen] = fStreamEnabled;
+
+ if (fChanged && uScreen < mcMonitors)
+ i_recordingScreenChanged(uScreen);
+ }
+
+ return VINF_SUCCESS;
+}
+
+void Display::i_recordingScreenChanged(unsigned uScreenId)
+{
+ RecordingContext *pCtx = mParent->i_recordingGetContext();
+
+ i_UpdateDeviceCursorCapabilities();
+ if ( RT_LIKELY(!maRecordingEnabled[uScreenId])
+ || !pCtx || !pCtx->IsStarted())
+ {
+ /* Skip recording this screen. */
+ return;
+ }
+
+ /* Get a new source bitmap which will be used by video recording code. */
+ ComPtr<IDisplaySourceBitmap> pSourceBitmap;
+ QuerySourceBitmap(uScreenId, pSourceBitmap.asOutParam());
+
+ int vrc2 = RTCritSectEnter(&mVideoRecLock);
+ if (RT_SUCCESS(vrc2))
+ {
+ maFramebuffers[uScreenId].Recording.pSourceBitmap = pSourceBitmap;
+
+ vrc2 = RTCritSectLeave(&mVideoRecLock);
+ AssertRC(vrc2);
+ }
+}
+#endif /* VBOX_WITH_RECORDING */
+
+/*static*/ DECLCALLBACK(int)
+Display::i_drawToScreenEMT(Display *pDisplay, ULONG aScreenId, BYTE *address, ULONG x, ULONG y, ULONG width, ULONG height)
+{
+ int vrc = VINF_SUCCESS;
+
+ DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[aScreenId];
+
+ if (aScreenId == VBOX_VIDEO_PRIMARY_SCREEN)
+ {
+ vrc = pDisplay->mpDrv->pUpPort->pfnDisplayBlt(pDisplay->mpDrv->pUpPort, address, x, y, width, height);
+ }
+ else if (aScreenId < pDisplay->mcMonitors)
+ {
+ /* Copy the bitmap to the guest VRAM. */
+ const uint8_t *pu8Src = address;
+ int32_t xSrc = 0;
+ int32_t ySrc = 0;
+ uint32_t u32SrcWidth = width;
+ uint32_t u32SrcHeight = height;
+ uint32_t u32SrcLineSize = width * 4;
+ uint32_t u32SrcBitsPerPixel = 32;
+
+ uint8_t *pu8Dst = pFBInfo->pu8FramebufferVRAM;
+ int32_t xDst = x;
+ int32_t yDst = y;
+ uint32_t u32DstWidth = pFBInfo->w;
+ uint32_t u32DstHeight = pFBInfo->h;
+ uint32_t u32DstLineSize = pFBInfo->u32LineSize;
+ uint32_t u32DstBitsPerPixel = pFBInfo->u16BitsPerPixel;
+
+ vrc = pDisplay->mpDrv->pUpPort->pfnCopyRect(pDisplay->mpDrv->pUpPort,
+ width, height,
+ pu8Src,
+ xSrc, ySrc,
+ u32SrcWidth, u32SrcHeight,
+ u32SrcLineSize, u32SrcBitsPerPixel,
+ pu8Dst,
+ xDst, yDst,
+ u32DstWidth, u32DstHeight,
+ u32DstLineSize, u32DstBitsPerPixel);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!pFBInfo->pSourceBitmap.isNull())
+ {
+ /* Update the changed screen area. When source bitmap uses VRAM directly, just notify
+ * frontend to update. And for default format, render the guest VRAM to the source bitmap.
+ */
+ if ( pFBInfo->fDefaultFormat
+ && !pFBInfo->fDisabled)
+ {
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ HRESULT hrc = pFBInfo->pSourceBitmap->QueryBitmapInfo(&pAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+ if (SUCCEEDED(hrc))
+ {
+ pu8Src = pFBInfo->pu8FramebufferVRAM;
+ xSrc = x;
+ ySrc = y;
+ u32SrcWidth = pFBInfo->w;
+ u32SrcHeight = pFBInfo->h;
+ u32SrcLineSize = pFBInfo->u32LineSize;
+ u32SrcBitsPerPixel = pFBInfo->u16BitsPerPixel;
+
+ /* Default format is 32 bpp. */
+ pu8Dst = pAddress;
+ xDst = xSrc;
+ yDst = ySrc;
+ u32DstWidth = u32SrcWidth;
+ u32DstHeight = u32SrcHeight;
+ u32DstLineSize = u32DstWidth * 4;
+ u32DstBitsPerPixel = 32;
+
+ pDisplay->mpDrv->pUpPort->pfnCopyRect(pDisplay->mpDrv->pUpPort,
+ width, height,
+ pu8Src,
+ xSrc, ySrc,
+ u32SrcWidth, u32SrcHeight,
+ u32SrcLineSize, u32SrcBitsPerPixel,
+ pu8Dst,
+ xDst, yDst,
+ u32DstWidth, u32DstHeight,
+ u32DstLineSize, u32DstBitsPerPixel);
+ }
+ }
+ }
+
+ pDisplay->i_handleDisplayUpdate(aScreenId, x, y, width, height);
+ }
+ }
+ else
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_SUCCESS(vrc))
+ pDisplay->mParent->i_consoleVRDPServer()->SendUpdateBitmap(aScreenId, x, y, width, height);
+
+ return vrc;
+}
+
+HRESULT Display::drawToScreen(ULONG aScreenId, BYTE *aAddress, ULONG aX, ULONG aY, ULONG aWidth, ULONG aHeight)
+{
+ /// @todo (r=dmik) this function may take too long to complete if the VM
+ // is doing something like saving state right now. Which, in case if it
+ // is called on the GUI thread, will make it unresponsive. We should
+ // check the machine state here (by enclosing the check and VMRequCall
+ // within the Console lock to make it atomic).
+
+ LogRelFlowFunc(("aAddress=%p, x=%d, y=%d, width=%d, height=%d\n",
+ (void *)aAddress, aX, aY, aWidth, aHeight));
+
+ CheckComArgExpr(aWidth, aWidth != 0);
+ CheckComArgExpr(aHeight, aHeight != 0);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_CONSOLE_DRV(mpDrv);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ /* Release lock because the call scheduled on EMT may also try to take it. */
+ alock.release();
+
+ /*
+ * Again we're lazy and make the graphics device do all the
+ * dirty conversion work.
+ */
+ int vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_drawToScreenEMT, 7,
+ this, aScreenId, aAddress, aX, aY, aWidth, aHeight);
+
+ /*
+ * If the function returns not supported, we'll have to do all the
+ * work ourselves using the framebuffer.
+ */
+ HRESULT hrc = S_OK;
+ if (vrc == VERR_NOT_SUPPORTED || vrc == VERR_NOT_IMPLEMENTED)
+ {
+ /** @todo implement generic fallback for screen blitting. */
+ hrc = E_NOTIMPL;
+ }
+ else if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not draw to the screen (%Rrc)"), vrc);
+/// @todo
+// else
+// {
+// /* All ok. Redraw the screen. */
+// handleDisplayUpdate(x, y, width, height);
+// }
+
+ LogRelFlowFunc(("hrc=%Rhrc\n", hrc));
+ return hrc;
+}
+
+/** @todo r=bird: cannot quite see why this would be required to run on an
+ * EMT any more. It's not an issue in the COM methods, but for the
+ * VGA device interface it is an issue, see querySourceBitmap. */
+/*static*/ DECLCALLBACK(int) Display::i_InvalidateAndUpdateEMT(Display *pDisplay, unsigned uId, bool fUpdateAll)
+{
+ LogRelFlowFunc(("uId=%d, fUpdateAll %d\n", uId, fUpdateAll));
+
+ unsigned uScreenId;
+ for (uScreenId = (fUpdateAll ? 0 : uId); uScreenId < pDisplay->mcMonitors; uScreenId++)
+ {
+ DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[uScreenId];
+
+ if ( !pFBInfo->fVBVAEnabled
+ && uScreenId == VBOX_VIDEO_PRIMARY_SCREEN)
+ pDisplay->mpDrv->pUpPort->pfnUpdateDisplayAll(pDisplay->mpDrv->pUpPort, /* fFailOnResize = */ true);
+ else
+ {
+ if (!pFBInfo->fDisabled)
+ {
+ /* Render complete VRAM screen to the framebuffer.
+ * When framebuffer uses VRAM directly, just notify it to update.
+ */
+ if (pFBInfo->fDefaultFormat && !pFBInfo->pSourceBitmap.isNull())
+ {
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ HRESULT hrc = pFBInfo->pSourceBitmap->QueryBitmapInfo(&pAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+ if (SUCCEEDED(hrc))
+ {
+ uint32_t width = pFBInfo->w;
+ uint32_t height = pFBInfo->h;
+
+ const uint8_t *pu8Src = pFBInfo->pu8FramebufferVRAM;
+ int32_t xSrc = 0;
+ int32_t ySrc = 0;
+ uint32_t u32SrcWidth = pFBInfo->w;
+ uint32_t u32SrcHeight = pFBInfo->h;
+ uint32_t u32SrcLineSize = pFBInfo->u32LineSize;
+ uint32_t u32SrcBitsPerPixel = pFBInfo->u16BitsPerPixel;
+
+ /* Default format is 32 bpp. */
+ uint8_t *pu8Dst = pAddress;
+ int32_t xDst = xSrc;
+ int32_t yDst = ySrc;
+ uint32_t u32DstWidth = u32SrcWidth;
+ uint32_t u32DstHeight = u32SrcHeight;
+ uint32_t u32DstLineSize = u32DstWidth * 4;
+ uint32_t u32DstBitsPerPixel = 32;
+
+ /* if uWidth != pFBInfo->w and uHeight != pFBInfo->h
+ * implies resize of Framebuffer is in progress and
+ * copyrect should not be called.
+ */
+ if (ulWidth == pFBInfo->w && ulHeight == pFBInfo->h)
+ pDisplay->mpDrv->pUpPort->pfnCopyRect(pDisplay->mpDrv->pUpPort,
+ width, height,
+ pu8Src,
+ xSrc, ySrc,
+ u32SrcWidth, u32SrcHeight,
+ u32SrcLineSize, u32SrcBitsPerPixel,
+ pu8Dst,
+ xDst, yDst,
+ u32DstWidth, u32DstHeight,
+ u32DstLineSize, u32DstBitsPerPixel);
+ }
+ }
+
+ pDisplay->i_handleDisplayUpdate(uScreenId, 0, 0, pFBInfo->w, pFBInfo->h);
+ }
+ }
+ if (!fUpdateAll)
+ break;
+ }
+ LogRelFlowFunc(("done\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Does a full invalidation of the VM display and instructs the VM
+ * to update it immediately.
+ *
+ * @returns COM status code
+ */
+
+HRESULT Display::invalidateAndUpdate()
+{
+ LogRelFlowFunc(("\n"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_CONSOLE_DRV(mpDrv);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ LogRelFlowFunc(("Sending DPYUPDATE request\n"));
+
+ /* Have to release the lock when calling EMT. */
+ alock.release();
+
+ int vrc = ptrVM.vtable()->pfnVMR3ReqCallNoWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT,
+ 3, this, 0, true);
+ alock.acquire();
+
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not invalidate and update the screen (%Rrc)"), vrc);
+ }
+
+ LogRelFlowFunc(("hrc=%Rhrc\n", hrc));
+ return hrc;
+}
+
+HRESULT Display::invalidateAndUpdateScreen(ULONG aScreenId)
+{
+ LogRelFlowFunc(("\n"));
+
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnVMR3ReqCallNoWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT,
+ 3, this, aScreenId, false);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not invalidate and update the screen %d (%Rrc)"), aScreenId, vrc);
+ }
+
+ LogRelFlowFunc(("hrc=%Rhrc\n", hrc));
+ return hrc;
+}
+
+HRESULT Display::completeVHWACommand(BYTE *aCommand)
+{
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ mpDrv->pVBVACallbacks->pfnVHWACommandCompleteAsync(mpDrv->pVBVACallbacks, (VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *)aCommand);
+ return S_OK;
+#else
+ RT_NOREF(aCommand);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Display::viewportChanged(ULONG aScreenId, ULONG aX, ULONG aY, ULONG aWidth, ULONG aHeight)
+{
+ AssertMsgReturn(aScreenId < mcMonitors, ("aScreendId=%d mcMonitors=%d\n", aScreenId, mcMonitors), E_INVALIDARG);
+
+ /* The driver might not have been constructed yet */
+ if (mpDrv && mpDrv->pUpPort->pfnSetViewport)
+ mpDrv->pUpPort->pfnSetViewport(mpDrv->pUpPort, aScreenId, aX, aY, aWidth, aHeight);
+
+ return S_OK;
+}
+
+HRESULT Display::querySourceBitmap(ULONG aScreenId,
+ ComPtr<IDisplaySourceBitmap> &aDisplaySourceBitmap)
+{
+ LogRelFlowFunc(("aScreenId = %d\n", aScreenId));
+
+ Console::SafeVMPtr ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return ptrVM.rc();
+
+ CHECK_CONSOLE_DRV(mpDrv);
+
+ bool fSetRenderVRAM = false;
+ bool fInvalidate = false;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aScreenId >= mcMonitors)
+ return setError(E_INVALIDARG, tr("QuerySourceBitmap: Invalid screen %d (total %d)"), aScreenId, mcMonitors);
+
+ if (!mfSourceBitmapEnabled)
+ {
+ aDisplaySourceBitmap = NULL;
+ return E_FAIL;
+ }
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId];
+
+ /* No source bitmap for a blank guest screen. */
+ if (pFBInfo->flags & VBVA_SCREEN_F_BLANK)
+ {
+ aDisplaySourceBitmap = NULL;
+ return E_FAIL;
+ }
+
+ HRESULT hr = S_OK;
+
+ if (pFBInfo->pSourceBitmap.isNull())
+ {
+ /* Create a new object. */
+ ComObjPtr<DisplaySourceBitmap> obj;
+ hr = obj.createObject();
+ if (SUCCEEDED(hr))
+ hr = obj->init(this, aScreenId, pFBInfo);
+
+ if (SUCCEEDED(hr))
+ {
+ pFBInfo->pSourceBitmap = obj;
+ pFBInfo->fDefaultFormat = !obj->i_usesVRAM();
+
+ if (aScreenId == VBOX_VIDEO_PRIMARY_SCREEN)
+ {
+ /* Start buffer updates. */
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ pFBInfo->pSourceBitmap->QueryBitmapInfo(&pAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+
+ mpDrv->IConnector.pbData = pAddress;
+ mpDrv->IConnector.cbScanline = ulBytesPerLine;
+ mpDrv->IConnector.cBits = ulBitsPerPixel;
+ mpDrv->IConnector.cx = ulWidth;
+ mpDrv->IConnector.cy = ulHeight;
+
+ fSetRenderVRAM = pFBInfo->fDefaultFormat;
+ }
+
+ /* Make sure that the bitmap contains the latest image. */
+ fInvalidate = pFBInfo->fDefaultFormat;
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ pFBInfo->pSourceBitmap.queryInterfaceTo(aDisplaySourceBitmap.asOutParam());
+ }
+
+ /* Leave the IDisplay lock because the VGA device must not be called under it. */
+ alock.release();
+
+ if (SUCCEEDED(hr))
+ {
+ if (fSetRenderVRAM)
+ mpDrv->pUpPort->pfnSetRenderVRAM(mpDrv->pUpPort, true);
+
+ if (fInvalidate)
+#if 1 /* bird: Cannot see why this needs to run on an EMT. It deadlocks now with timer callback moving to non-EMT worker threads. */
+ Display::i_InvalidateAndUpdateEMT(this, aScreenId, false /*fUpdateAll*/);
+#else
+ VMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT,
+ 3, this, aScreenId, false);
+#endif
+ }
+
+ LogRelFlowFunc(("%Rhrc\n", hr));
+ return hr;
+}
+
+HRESULT Display::getGuestScreenLayout(std::vector<ComPtr<IGuestScreenInfo> > &aGuestScreenLayout)
+{
+ NOREF(aGuestScreenLayout);
+ return E_NOTIMPL;
+}
+
+HRESULT Display::setScreenLayout(ScreenLayoutMode_T aScreenLayoutMode,
+ const std::vector<ComPtr<IGuestScreenInfo> > &aGuestScreenInfo)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aGuestScreenInfo.size() != mcMonitors)
+ return E_INVALIDARG;
+
+ CHECK_CONSOLE_DRV(mpDrv);
+
+ /*
+ * It is up to the guest to decide whether the hint is
+ * valid. Therefore don't do any VRAM sanity checks here.
+ */
+
+ /* Have to release the lock because the pfnRequestDisplayChange
+ * will call EMT. */
+ alock.release();
+
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ {
+ uint32_t const cDisplays = (uint32_t)aGuestScreenInfo.size();
+
+ size_t const cbAlloc = cDisplays * sizeof(VMMDevDisplayDef);
+ VMMDevDisplayDef *paDisplayDefs = (VMMDevDisplayDef *)RTMemAlloc(cbAlloc);
+ if (paDisplayDefs)
+ {
+ for (uint32_t i = 0; i < cDisplays; ++i)
+ {
+ VMMDevDisplayDef *p = &paDisplayDefs[i];
+ ComPtr<IGuestScreenInfo> pScreenInfo = aGuestScreenInfo[i];
+
+ ULONG screenId = 0;
+ GuestMonitorStatus_T guestMonitorStatus = GuestMonitorStatus_Enabled;
+ BOOL origin = FALSE;
+ BOOL primary = FALSE;
+ LONG originX = 0;
+ LONG originY = 0;
+ ULONG width = 0;
+ ULONG height = 0;
+ ULONG bitsPerPixel = 0;
+
+ pScreenInfo->COMGETTER(ScreenId) (&screenId);
+ pScreenInfo->COMGETTER(GuestMonitorStatus)(&guestMonitorStatus);
+ pScreenInfo->COMGETTER(Primary) (&primary);
+ pScreenInfo->COMGETTER(Origin) (&origin);
+ pScreenInfo->COMGETTER(OriginX) (&originX);
+ pScreenInfo->COMGETTER(OriginY) (&originY);
+ pScreenInfo->COMGETTER(Width) (&width);
+ pScreenInfo->COMGETTER(Height) (&height);
+ pScreenInfo->COMGETTER(BitsPerPixel)(&bitsPerPixel);
+
+ LogFlowFunc(("%d %d,%d %dx%d\n", screenId, originX, originY, width, height));
+
+ p->idDisplay = screenId;
+ p->xOrigin = originX;
+ p->yOrigin = originY;
+ p->cx = width;
+ p->cy = height;
+ p->cBitsPerPixel = bitsPerPixel;
+ p->fDisplayFlags = VMMDEV_DISPLAY_CX | VMMDEV_DISPLAY_CY | VMMDEV_DISPLAY_BPP;
+ if (guestMonitorStatus == GuestMonitorStatus_Disabled)
+ p->fDisplayFlags |= VMMDEV_DISPLAY_DISABLED;
+ if (origin)
+ p->fDisplayFlags |= VMMDEV_DISPLAY_ORIGIN;
+ if (primary)
+ p->fDisplayFlags |= VMMDEV_DISPLAY_PRIMARY;
+ }
+
+ bool const fForce = aScreenLayoutMode == ScreenLayoutMode_Reset
+ || aScreenLayoutMode == ScreenLayoutMode_Apply;
+ bool const fNotify = aScreenLayoutMode != ScreenLayoutMode_Silent;
+ pVMMDevPort->pfnRequestDisplayChange(pVMMDevPort, cDisplays, paDisplayDefs, fForce, fNotify);
+
+ RTMemFree(paDisplayDefs);
+ }
+ }
+ }
+ return S_OK;
+}
+
+HRESULT Display::detachScreens(const std::vector<LONG> &aScreenIds)
+{
+ NOREF(aScreenIds);
+ return E_NOTIMPL;
+}
+
+HRESULT Display::createGuestScreenInfo(ULONG aDisplay,
+ GuestMonitorStatus_T aStatus,
+ BOOL aPrimary,
+ BOOL aChangeOrigin,
+ LONG aOriginX,
+ LONG aOriginY,
+ ULONG aWidth,
+ ULONG aHeight,
+ ULONG aBitsPerPixel,
+ ComPtr<IGuestScreenInfo> &aGuestScreenInfo)
+{
+ /* Create a new object. */
+ ComObjPtr<GuestScreenInfo> obj;
+ HRESULT hr = obj.createObject();
+ if (SUCCEEDED(hr))
+ hr = obj->init(aDisplay, aStatus, aPrimary, aChangeOrigin, aOriginX, aOriginY,
+ aWidth, aHeight, aBitsPerPixel);
+ if (SUCCEEDED(hr))
+ obj.queryInterfaceTo(aGuestScreenInfo.asOutParam());
+
+ return hr;
+}
+
+
+/*
+ * GuestScreenInfo implementation.
+ */
+DEFINE_EMPTY_CTOR_DTOR(GuestScreenInfo)
+
+HRESULT GuestScreenInfo::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void GuestScreenInfo::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+HRESULT GuestScreenInfo::init(ULONG aDisplay,
+ GuestMonitorStatus_T aGuestMonitorStatus,
+ BOOL aPrimary,
+ BOOL aChangeOrigin,
+ LONG aOriginX,
+ LONG aOriginY,
+ ULONG aWidth,
+ ULONG aHeight,
+ ULONG aBitsPerPixel)
+{
+ LogFlowThisFunc(("[%u]\n", aDisplay));
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ mScreenId = aDisplay;
+ mGuestMonitorStatus = aGuestMonitorStatus;
+ mPrimary = aPrimary;
+ mOrigin = aChangeOrigin;
+ mOriginX = aOriginX;
+ mOriginY = aOriginY;
+ mWidth = aWidth;
+ mHeight = aHeight;
+ mBitsPerPixel = aBitsPerPixel;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+void GuestScreenInfo::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFunc(("[%u]\n", mScreenId));
+}
+
+HRESULT GuestScreenInfo::getScreenId(ULONG *aScreenId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aScreenId = mScreenId;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getGuestMonitorStatus(GuestMonitorStatus_T *aGuestMonitorStatus)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aGuestMonitorStatus = mGuestMonitorStatus;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getPrimary(BOOL *aPrimary)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aPrimary = mPrimary;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getOrigin(BOOL *aOrigin)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aOrigin = mOrigin;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getOriginX(LONG *aOriginX)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aOriginX = mOriginX;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getOriginY(LONG *aOriginY)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aOriginY = mOriginY;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getWidth(ULONG *aWidth)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aWidth = mWidth;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getHeight(ULONG *aHeight)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aHeight = mHeight;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getBitsPerPixel(ULONG *aBitsPerPixel)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aBitsPerPixel = mBitsPerPixel;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getExtendedInfo(com::Utf8Str &aExtendedInfo)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aExtendedInfo = com::Utf8Str();
+ return S_OK;
+}
+
+// wrapped IEventListener method
+HRESULT Display::handleEvent(const ComPtr<IEvent> &aEvent)
+{
+ VBoxEventType_T aType = VBoxEventType_Invalid;
+
+ aEvent->COMGETTER(Type)(&aType);
+ switch (aType)
+ {
+ case VBoxEventType_OnStateChanged:
+ {
+ ComPtr<IStateChangedEvent> scev = aEvent;
+ Assert(scev);
+ MachineState_T machineState;
+ scev->COMGETTER(State)(&machineState);
+ if ( machineState == MachineState_Running
+ || machineState == MachineState_Teleporting
+ || machineState == MachineState_LiveSnapshotting
+ || machineState == MachineState_DeletingSnapshotOnline
+ )
+ {
+ LogRelFlowFunc(("Machine is running.\n"));
+
+ }
+ break;
+ }
+ default:
+ AssertFailed();
+ }
+
+ return S_OK;
+}
+
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Handle display resize event issued by the VGA device for the primary screen.
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnResize
+ */
+DECLCALLBACK(int) Display::i_displayResizeCallback(PPDMIDISPLAYCONNECTOR pInterface,
+ uint32_t bpp, void *pvVRAM, uint32_t cbLine, uint32_t cx, uint32_t cy)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+
+ LogRelFlowFunc(("bpp %d, pvVRAM %p, cbLine %d, cx %d, cy %d\n",
+ bpp, pvVRAM, cbLine, cx, cy));
+
+ bool f = ASMAtomicCmpXchgBool(&pThis->fVGAResizing, true, false);
+ if (!f)
+ {
+ /* This is a result of recursive call when the source bitmap is being updated
+ * during a VGA resize. Tell the VGA device to ignore the call.
+ *
+ * @todo It is a workaround, actually pfnUpdateDisplayAll must
+ * fail on resize.
+ */
+ LogRel(("displayResizeCallback: already processing\n"));
+ return VINF_VGA_RESIZE_IN_PROGRESS;
+ }
+
+ int vrc = pThis->i_handleDisplayResize(VBOX_VIDEO_PRIMARY_SCREEN, bpp, pvVRAM, cbLine, cx, cy, 0, 0, 0, true);
+
+ /* Restore the flag. */
+ f = ASMAtomicCmpXchgBool(&pThis->fVGAResizing, false, true);
+ AssertRelease(f);
+
+ return vrc;
+}
+
+/**
+ * Handle display update.
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnUpdateRect
+ */
+DECLCALLBACK(void) Display::i_displayUpdateCallback(PPDMIDISPLAYCONNECTOR pInterface,
+ uint32_t x, uint32_t y, uint32_t cx, uint32_t cy)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("fVideoAccelEnabled = %d, %d,%d %dx%d\n",
+ pDrv->pDisplay->mVideoAccelLegacy.fVideoAccelEnabled, x, y, cx, cy));
+#endif /* DEBUG_sunlover */
+
+ /* This call does update regardless of VBVA status.
+ * But in VBVA mode this is called only as result of
+ * pfnUpdateDisplayAll in the VGA device.
+ */
+
+ pDrv->pDisplay->i_handleDisplayUpdate(VBOX_VIDEO_PRIMARY_SCREEN, x, y, cx, cy);
+}
+
+/**
+ * Periodic display refresh callback.
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnRefresh
+ * @thread EMT
+ */
+/*static*/ DECLCALLBACK(void) Display::i_displayRefreshCallback(PPDMIDISPLAYCONNECTOR pInterface)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+
+#ifdef DEBUG_sunlover_2
+ LogFlowFunc(("pDrv->pDisplay->mfVideoAccelEnabled = %d\n",
+ pDrv->pDisplay->mfVideoAccelEnabled));
+#endif /* DEBUG_sunlover_2 */
+
+ Display *pDisplay = pDrv->pDisplay;
+ unsigned uScreenId;
+
+ int vrc = pDisplay->i_videoAccelRefreshProcess(pDrv->pUpPort);
+ if (vrc != VINF_TRY_AGAIN) /* Means 'do nothing' here. */
+ {
+ if (vrc == VWRN_INVALID_STATE)
+ {
+ /* No VBVA do a display update. */
+ pDrv->pUpPort->pfnUpdateDisplay(pDrv->pUpPort);
+ }
+
+ /* Inform the VRDP server that the current display update sequence is
+ * completed. At this moment the framebuffer memory contains a definite
+ * image, that is synchronized with the orders already sent to VRDP client.
+ * The server can now process redraw requests from clients or initial
+ * fullscreen updates for new clients.
+ */
+ for (uScreenId = 0; uScreenId < pDisplay->mcMonitors; uScreenId++)
+ {
+ Assert(pDisplay->mParent && pDisplay->mParent->i_consoleVRDPServer());
+ pDisplay->mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, NULL, 0);
+ }
+ }
+
+#ifdef VBOX_WITH_RECORDING
+ AssertPtr(pDisplay->mParent);
+ RecordingContext *pCtx = pDisplay->mParent->i_recordingGetContext();
+
+ if ( pCtx
+ && pCtx->IsStarted()
+ && pCtx->IsFeatureEnabled(RecordingFeature_Video))
+ {
+ do
+ {
+ /* If the recording context has reached the configured recording
+ * limit, disable recording. */
+ if (pCtx->IsLimitReached())
+ {
+ pDisplay->mParent->i_onRecordingChange(FALSE /* Disable */);
+ break;
+ }
+
+ uint64_t tsNowMs = RTTimeProgramMilliTS();
+ for (uScreenId = 0; uScreenId < pDisplay->mcMonitors; uScreenId++)
+ {
+ if (!pDisplay->maRecordingEnabled[uScreenId])
+ continue;
+
+ if (!pCtx->NeedsUpdate(uScreenId, tsNowMs))
+ continue;
+
+ DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[uScreenId];
+ if (!pFBInfo->fDisabled)
+ {
+ ComPtr<IDisplaySourceBitmap> pSourceBitmap;
+ int vrc2 = RTCritSectEnter(&pDisplay->mVideoRecLock);
+ if (RT_SUCCESS(vrc2))
+ {
+ pSourceBitmap = pFBInfo->Recording.pSourceBitmap;
+ RTCritSectLeave(&pDisplay->mVideoRecLock);
+ }
+
+ if (!pSourceBitmap.isNull())
+ {
+ BYTE *pbAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+ HRESULT hrc = pSourceBitmap->QueryBitmapInfo(&pbAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+ if (SUCCEEDED(hrc) && pbAddress)
+ vrc = pCtx->SendVideoFrame(uScreenId, 0, 0, BitmapFormat_BGR,
+ ulBitsPerPixel, ulBytesPerLine, ulWidth, ulHeight,
+ pbAddress, tsNowMs);
+ else
+ vrc = VERR_NOT_SUPPORTED;
+
+ pSourceBitmap.setNull();
+ }
+ else
+ vrc = VERR_NOT_SUPPORTED;
+
+ if (vrc == VINF_TRY_AGAIN)
+ break;
+ }
+ }
+ } while (0);
+ }
+#endif /* VBOX_WITH_RECORDING */
+
+#ifdef DEBUG_sunlover_2
+ LogFlowFunc(("leave\n"));
+#endif /* DEBUG_sunlover_2 */
+}
+
+/**
+ * Reset notification
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnReset
+ */
+DECLCALLBACK(void) Display::i_displayResetCallback(PPDMIDISPLAYCONNECTOR pInterface)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+
+ LogRelFlowFunc(("\n"));
+
+ /* Disable VBVA mode. */
+ pDrv->pDisplay->VideoAccelEnableVGA(false, NULL);
+}
+
+/**
+ * LFBModeChange notification
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnLFBModeChange
+ */
+DECLCALLBACK(void) Display::i_displayLFBModeChangeCallback(PPDMIDISPLAYCONNECTOR pInterface, bool fEnabled)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+
+ LogRelFlowFunc(("fEnabled=%d\n", fEnabled));
+
+ NOREF(fEnabled);
+
+ /* Disable VBVA mode in any case. The guest driver reenables VBVA mode if necessary. */
+ pDrv->pDisplay->VideoAccelEnableVGA(false, NULL);
+}
+
+/**
+ * Adapter information change notification.
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnProcessAdapterData
+ */
+DECLCALLBACK(void) Display::i_displayProcessAdapterDataCallback(PPDMIDISPLAYCONNECTOR pInterface, void *pvVRAM,
+ uint32_t u32VRAMSize)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ pDrv->pDisplay->processAdapterData(pvVRAM, u32VRAMSize);
+}
+
+/**
+ * Display information change notification.
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnProcessDisplayData
+ */
+DECLCALLBACK(void) Display::i_displayProcessDisplayDataCallback(PPDMIDISPLAYCONNECTOR pInterface,
+ void *pvVRAM, unsigned uScreenId)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ pDrv->pDisplay->processDisplayData(pvVRAM, uScreenId);
+}
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+
+int Display::i_handleVHWACommandProcess(int enmCmd, bool fGuestCmd, VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCommand)
+{
+ /* bugref:9691 Disable the legacy VHWA interface.
+ * Keep the host commands enabled because they are needed when an old saved state is loaded.
+ */
+ if (fGuestCmd)
+ return VERR_NOT_IMPLEMENTED;
+
+ unsigned id = (unsigned)pCommand->iDisplay;
+ if (id >= mcMonitors)
+ return VERR_INVALID_PARAMETER;
+
+ ComPtr<IFramebuffer> pFramebuffer;
+ AutoReadLock arlock(this COMMA_LOCKVAL_SRC_POS);
+ pFramebuffer = maFramebuffers[id].pFramebuffer;
+ bool fVHWASupported = RT_BOOL(maFramebuffers[id].u32Caps & FramebufferCapabilities_VHWA);
+ arlock.release();
+
+ if (pFramebuffer == NULL || !fVHWASupported)
+ return VERR_NOT_IMPLEMENTED; /* Implementation is not available. */
+
+ HRESULT hr = pFramebuffer->ProcessVHWACommand((BYTE *)pCommand, enmCmd, fGuestCmd);
+ if (hr == S_FALSE)
+ return VINF_SUCCESS;
+ if (SUCCEEDED(hr))
+ return VINF_CALLBACK_RETURN;
+ if (hr == E_ACCESSDENIED)
+ return VERR_INVALID_STATE; /* notify we can not handle request atm */
+ if (hr == E_NOTIMPL)
+ return VERR_NOT_IMPLEMENTED;
+ return VERR_GENERAL_FAILURE;
+}
+
+DECLCALLBACK(int) Display::i_displayVHWACommandProcess(PPDMIDISPLAYCONNECTOR pInterface, int enmCmd, bool fGuestCmd,
+ VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCommand)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+
+ return pDrv->pDisplay->i_handleVHWACommandProcess(enmCmd, fGuestCmd, pCommand);
+}
+
+#endif /* VBOX_WITH_VIDEOHWACCEL */
+
+int Display::i_handle3DNotifyProcess(VBOX3DNOTIFY *p3DNotify)
+{
+ unsigned const id = (unsigned)p3DNotify->iDisplay;
+ if (id >= mcMonitors)
+ return VERR_INVALID_PARAMETER;
+
+ ComPtr<IFramebuffer> pFramebuffer;
+ AutoReadLock arlock(this COMMA_LOCKVAL_SRC_POS);
+ pFramebuffer = maFramebuffers[id].pFramebuffer;
+ arlock.release();
+
+ int vrc = VINF_SUCCESS;
+
+ if (!pFramebuffer.isNull())
+ {
+ if (p3DNotify->enmNotification == VBOX3D_NOTIFY_TYPE_HW_OVERLAY_GET_ID)
+ {
+ LONG64 winId = 0;
+ HRESULT hrc = pFramebuffer->COMGETTER(WinId)(&winId);
+ if (SUCCEEDED(hrc))
+ {
+ *(uint64_t *)&p3DNotify->au8Data[0] = winId;
+ }
+ else
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ else
+ {
+ com::SafeArray<BYTE> data;
+ data.initFrom((BYTE *)&p3DNotify->au8Data[0], p3DNotify->cbData);
+
+ HRESULT hrc = pFramebuffer->Notify3DEvent((ULONG)p3DNotify->enmNotification, ComSafeArrayAsInParam(data));
+ if (FAILED(hrc))
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ vrc = VERR_NOT_IMPLEMENTED;
+
+ return vrc;
+}
+
+DECLCALLBACK(int) Display::i_display3DNotifyProcess(PPDMIDISPLAYCONNECTOR pInterface,
+ VBOX3DNOTIFY *p3DNotify)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ return pDrv->pDisplay->i_handle3DNotifyProcess(p3DNotify);
+}
+
+HRESULT Display::notifyScaleFactorChange(ULONG aScreenId, ULONG aScaleFactorWMultiplied, ULONG aScaleFactorHMultiplied)
+{
+ RT_NOREF(aScreenId, aScaleFactorWMultiplied, aScaleFactorHMultiplied);
+# if 0 /** @todo Thank you so very much from anyone using VMSVGA3d! */
+ AssertMsgFailed(("Attempt to specify OpenGL content scale factor while 3D acceleration is disabled in VM config. Ignored.\n"));
+# else
+ /* Need an interface like this here (and the #ifdefs needs adjusting):
+ PPDMIDISPLAYPORT pUpPort = mpDrv ? mpDrv->pUpPort : NULL;
+ if (pUpPort && pUpPort->pfnSetScaleFactor)
+ pUpPort->pfnSetScaleFactor(pUpPort, aScreeId, aScaleFactorWMultiplied, aScaleFactorHMultiplied); */
+# endif
+ return S_OK;
+}
+
+HRESULT Display::notifyHiDPIOutputPolicyChange(BOOL fUnscaledHiDPI)
+{
+ RT_NOREF(fUnscaledHiDPI);
+
+ /* Need an interface like this here (and the #ifdefs needs adjusting):
+ PPDMIDISPLAYPORT pUpPort = mpDrv ? mpDrv->pUpPort : NULL;
+ if (pUpPort && pUpPort->pfnSetScaleFactor)
+ pUpPort->pfnSetScaleFactor(pUpPort, aScreeId, aScaleFactorWMultiplied, aScaleFactorHMultiplied); */
+
+ return S_OK;
+}
+
+#ifdef VBOX_WITH_HGSMI
+/**
+ * @interface_method_impl{PDMIDISPLAYCONNECTOR,pfnVBVAEnable}
+ */
+DECLCALLBACK(int) Display::i_displayVBVAEnable(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId,
+ VBVAHOSTFLAGS RT_UNTRUSTED_VOLATILE_GUEST *pHostFlags)
+{
+ LogRelFlowFunc(("uScreenId %d\n", uScreenId));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+ AssertReturn(uScreenId < pThis->mcMonitors, VERR_INVALID_PARAMETER);
+
+ if (pThis->maFramebuffers[uScreenId].fVBVAEnabled)
+ {
+ LogRel(("Enabling different vbva mode\n"));
+#ifdef DEBUG_misha
+ AssertMsgFailed(("enabling different vbva mode\n"));
+#endif
+ return VERR_INVALID_STATE;
+ }
+
+ pThis->maFramebuffers[uScreenId].fVBVAEnabled = true;
+ pThis->maFramebuffers[uScreenId].pVBVAHostFlags = pHostFlags;
+ pThis->maFramebuffers[uScreenId].fVBVAForceResize = true;
+
+ vbvaSetMemoryFlagsHGSMI(uScreenId, pThis->mfu32SupportedOrders, pThis->mfVideoAccelVRDP, &pThis->maFramebuffers[uScreenId]);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIDISPLAYCONNECTOR,pfnVBVADisable}
+ */
+DECLCALLBACK(void) Display::i_displayVBVADisable(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId)
+{
+ LogRelFlowFunc(("uScreenId %d\n", uScreenId));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+ AssertReturnVoid(uScreenId < pThis->mcMonitors);
+
+ DISPLAYFBINFO *pFBInfo = &pThis->maFramebuffers[uScreenId];
+
+ if (uScreenId == VBOX_VIDEO_PRIMARY_SCREEN)
+ {
+ /* Make sure that the primary screen is visible now.
+ * The guest can't use VBVA anymore, so only only the VGA device output works.
+ */
+ pFBInfo->flags = 0;
+ if (pFBInfo->fDisabled)
+ {
+ pFBInfo->fDisabled = false;
+ ::FireGuestMonitorChangedEvent(pThis->mParent->i_getEventSource(), GuestMonitorChangedEventType_Enabled, uScreenId,
+ pFBInfo->xOrigin, pFBInfo->yOrigin, pFBInfo->w, pFBInfo->h);
+ }
+ }
+
+ pFBInfo->fVBVAEnabled = false;
+ pFBInfo->fVBVAForceResize = false;
+
+ vbvaSetMemoryFlagsHGSMI(uScreenId, 0, false, pFBInfo);
+
+ pFBInfo->pVBVAHostFlags = NULL;
+
+ if (uScreenId == VBOX_VIDEO_PRIMARY_SCREEN)
+ {
+ /* Force full screen update, because VGA device must take control, do resize, etc. */
+ pThis->mpDrv->pUpPort->pfnUpdateDisplayAll(pThis->mpDrv->pUpPort, /* fFailOnResize = */ false);
+ }
+}
+
+DECLCALLBACK(void) Display::i_displayVBVAUpdateBegin(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId)
+{
+ RT_NOREF(uScreenId);
+ LogFlowFunc(("uScreenId %d\n", uScreenId));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+
+ if (ASMAtomicReadU32(&pThis->mu32UpdateVBVAFlags) > 0)
+ {
+ vbvaSetMemoryFlagsAllHGSMI(pThis->mfu32SupportedOrders, pThis->mfVideoAccelVRDP, pThis->maFramebuffers,
+ pThis->mcMonitors);
+ ASMAtomicDecU32(&pThis->mu32UpdateVBVAFlags);
+ }
+}
+
+/**
+ * @interface_method_impl{PDMIDISPLAYCONNECTOR,pfnVBVAUpdateProcess}
+ */
+DECLCALLBACK(void) Display::i_displayVBVAUpdateProcess(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId,
+ struct VBVACMDHDR const RT_UNTRUSTED_VOLATILE_GUEST *pCmd, size_t cbCmd)
+{
+ LogFlowFunc(("uScreenId %d pCmd %p cbCmd %d, @%d,%d %dx%d\n", uScreenId, pCmd, cbCmd, pCmd->x, pCmd->y, pCmd->w, pCmd->h));
+ VBVACMDHDR hdrSaved;
+ RT_COPY_VOLATILE(hdrSaved, *pCmd);
+ RT_UNTRUSTED_NONVOLATILE_COPY_FENCE();
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+ DISPLAYFBINFO *pFBInfo;
+ AssertReturnVoid(uScreenId < pThis->mcMonitors);
+
+ pFBInfo = &pThis->maFramebuffers[uScreenId];
+
+ if (pFBInfo->fDefaultFormat)
+ {
+ /* Make sure that framebuffer contains the same image as the guest VRAM. */
+ if ( uScreenId == VBOX_VIDEO_PRIMARY_SCREEN
+ && !pFBInfo->fDisabled)
+ {
+ pDrv->pUpPort->pfnUpdateDisplayRect(pDrv->pUpPort, hdrSaved.x, hdrSaved.y, hdrSaved.w, hdrSaved.h);
+ }
+ else if ( !pFBInfo->pSourceBitmap.isNull()
+ && !pFBInfo->fDisabled)
+ {
+ /* Render VRAM content to the framebuffer. */
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ HRESULT hrc = pFBInfo->pSourceBitmap->QueryBitmapInfo(&pAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+ if (SUCCEEDED(hrc))
+ {
+ uint32_t width = hdrSaved.w;
+ uint32_t height = hdrSaved.h;
+
+ const uint8_t *pu8Src = pFBInfo->pu8FramebufferVRAM;
+ int32_t xSrc = hdrSaved.x - pFBInfo->xOrigin;
+ int32_t ySrc = hdrSaved.y - pFBInfo->yOrigin;
+ uint32_t u32SrcWidth = pFBInfo->w;
+ uint32_t u32SrcHeight = pFBInfo->h;
+ uint32_t u32SrcLineSize = pFBInfo->u32LineSize;
+ uint32_t u32SrcBitsPerPixel = pFBInfo->u16BitsPerPixel;
+
+ uint8_t *pu8Dst = pAddress;
+ int32_t xDst = xSrc;
+ int32_t yDst = ySrc;
+ uint32_t u32DstWidth = u32SrcWidth;
+ uint32_t u32DstHeight = u32SrcHeight;
+ uint32_t u32DstLineSize = u32DstWidth * 4;
+ uint32_t u32DstBitsPerPixel = 32;
+
+ pDrv->pUpPort->pfnCopyRect(pDrv->pUpPort,
+ width, height,
+ pu8Src,
+ xSrc, ySrc,
+ u32SrcWidth, u32SrcHeight,
+ u32SrcLineSize, u32SrcBitsPerPixel,
+ pu8Dst,
+ xDst, yDst,
+ u32DstWidth, u32DstHeight,
+ u32DstLineSize, u32DstBitsPerPixel);
+ }
+ }
+ }
+
+ /*
+ * Here is your classic 'temporary' solution.
+ */
+ /** @todo New SendUpdate entry which can get a separate cmd header or coords. */
+ VBVACMDHDR *pHdrUnconst = (VBVACMDHDR *)pCmd;
+
+ pHdrUnconst->x -= (int16_t)pFBInfo->xOrigin;
+ pHdrUnconst->y -= (int16_t)pFBInfo->yOrigin;
+
+ pThis->mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, pHdrUnconst, (uint32_t)cbCmd);
+
+ *pHdrUnconst = hdrSaved;
+}
+
+DECLCALLBACK(void) Display::i_displayVBVAUpdateEnd(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId, int32_t x, int32_t y,
+ uint32_t cx, uint32_t cy)
+{
+ LogFlowFunc(("uScreenId %d %d,%d %dx%d\n", uScreenId, x, y, cx, cy));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+ DISPLAYFBINFO *pFBInfo;
+ AssertReturnVoid(uScreenId < pThis->mcMonitors);
+
+ pFBInfo = &pThis->maFramebuffers[uScreenId];
+
+ /** @todo handleFramebufferUpdate (uScreenId,
+ * x - pThis->maFramebuffers[uScreenId].xOrigin,
+ * y - pThis->maFramebuffers[uScreenId].yOrigin,
+ * cx, cy);
+ */
+ pThis->i_handleDisplayUpdate(uScreenId, x - pFBInfo->xOrigin, y - pFBInfo->yOrigin, cx, cy);
+}
+
+#ifdef DEBUG_sunlover
+static void logVBVAResize(PCVBVAINFOVIEW pView, PCVBVAINFOSCREEN pScreen, const DISPLAYFBINFO *pFBInfo)
+{
+ LogRel(("displayVBVAResize: [%d] %s\n"
+ " pView->u32ViewIndex %d\n"
+ " pView->u32ViewOffset 0x%08X\n"
+ " pView->u32ViewSize 0x%08X\n"
+ " pView->u32MaxScreenSize 0x%08X\n"
+ " pScreen->i32OriginX %d\n"
+ " pScreen->i32OriginY %d\n"
+ " pScreen->u32StartOffset 0x%08X\n"
+ " pScreen->u32LineSize 0x%08X\n"
+ " pScreen->u32Width %d\n"
+ " pScreen->u32Height %d\n"
+ " pScreen->u16BitsPerPixel %d\n"
+ " pScreen->u16Flags 0x%04X\n"
+ " pFBInfo->u32Offset 0x%08X\n"
+ " pFBInfo->u32MaxFramebufferSize 0x%08X\n"
+ " pFBInfo->u32InformationSize 0x%08X\n"
+ " pFBInfo->fDisabled %d\n"
+ " xOrigin, yOrigin, w, h: %d,%d %dx%d\n"
+ " pFBInfo->u16BitsPerPixel %d\n"
+ " pFBInfo->pu8FramebufferVRAM %p\n"
+ " pFBInfo->u32LineSize 0x%08X\n"
+ " pFBInfo->flags 0x%04X\n"
+ " pFBInfo->pHostEvents %p\n"
+ " pFBInfo->fDefaultFormat %d\n"
+ " pFBInfo->fVBVAEnabled %d\n"
+ " pFBInfo->fVBVAForceResize %d\n"
+ " pFBInfo->pVBVAHostFlags %p\n"
+ "",
+ pScreen->u32ViewIndex,
+ (pScreen->u16Flags & VBVA_SCREEN_F_DISABLED)? "DISABLED": "ENABLED",
+ pView->u32ViewIndex,
+ pView->u32ViewOffset,
+ pView->u32ViewSize,
+ pView->u32MaxScreenSize,
+ pScreen->i32OriginX,
+ pScreen->i32OriginY,
+ pScreen->u32StartOffset,
+ pScreen->u32LineSize,
+ pScreen->u32Width,
+ pScreen->u32Height,
+ pScreen->u16BitsPerPixel,
+ pScreen->u16Flags,
+ pFBInfo->u32Offset,
+ pFBInfo->u32MaxFramebufferSize,
+ pFBInfo->u32InformationSize,
+ pFBInfo->fDisabled,
+ pFBInfo->xOrigin,
+ pFBInfo->yOrigin,
+ pFBInfo->w,
+ pFBInfo->h,
+ pFBInfo->u16BitsPerPixel,
+ pFBInfo->pu8FramebufferVRAM,
+ pFBInfo->u32LineSize,
+ pFBInfo->flags,
+ pFBInfo->pHostEvents,
+ pFBInfo->fDefaultFormat,
+ pFBInfo->fVBVAEnabled,
+ pFBInfo->fVBVAForceResize,
+ pFBInfo->pVBVAHostFlags
+ ));
+}
+#endif /* DEBUG_sunlover */
+
+DECLCALLBACK(int) Display::i_displayVBVAResize(PPDMIDISPLAYCONNECTOR pInterface, PCVBVAINFOVIEW pView,
+ PCVBVAINFOSCREEN pScreen, void *pvVRAM, bool fResetInputMapping)
+{
+ LogRelFlowFunc(("pScreen %p, pvVRAM %p\n", pScreen, pvVRAM));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+
+ return pThis->processVBVAResize(pView, pScreen, pvVRAM, fResetInputMapping);
+}
+
+int Display::processVBVAResize(PCVBVAINFOVIEW pView, PCVBVAINFOSCREEN pScreen, void *pvVRAM, bool fResetInputMapping)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ RT_NOREF(pView);
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[pScreen->u32ViewIndex];
+
+#ifdef DEBUG_sunlover
+ logVBVAResize(pView, pScreen, pFBInfo);
+#endif
+
+ if (pScreen->u16Flags & VBVA_SCREEN_F_DISABLED)
+ {
+ /* Ask the framebuffer to resize using a default format. The framebuffer will be black.
+ * So if the frontend does not support GuestMonitorChangedEventType_Disabled event,
+ * the VM window will be black. */
+ uint32_t u32Width = pFBInfo->w ? pFBInfo->w : 640;
+ uint32_t u32Height = pFBInfo->h ? pFBInfo->h : 480;
+ int32_t xOrigin = pFBInfo->xOrigin;
+ int32_t yOrigin = pFBInfo->yOrigin;
+
+ alock.release();
+
+ i_handleDisplayResize(pScreen->u32ViewIndex, 0, (uint8_t *)NULL, 0,
+ u32Width, u32Height, pScreen->u16Flags, xOrigin, yOrigin, false);
+
+ return VINF_SUCCESS;
+ }
+
+ VBVAINFOSCREEN screenInfo;
+ RT_ZERO(screenInfo);
+
+ if (pScreen->u16Flags & VBVA_SCREEN_F_BLANK2)
+ {
+ /* Init a local VBVAINFOSCREEN structure, which will be used instead of
+ * the original pScreen. Set VBVA_SCREEN_F_BLANK, which will force
+ * the code below to choose the "blanking" branches.
+ */
+ screenInfo.u32ViewIndex = pScreen->u32ViewIndex;
+ screenInfo.i32OriginX = pFBInfo->xOrigin;
+ screenInfo.i32OriginY = pFBInfo->yOrigin;
+ screenInfo.u32StartOffset = 0; /* Irrelevant */
+ screenInfo.u32LineSize = pFBInfo->u32LineSize;
+ screenInfo.u32Width = pFBInfo->w;
+ screenInfo.u32Height = pFBInfo->h;
+ screenInfo.u16BitsPerPixel = pFBInfo->u16BitsPerPixel;
+ screenInfo.u16Flags = pScreen->u16Flags | VBVA_SCREEN_F_BLANK;
+
+ pScreen = &screenInfo;
+ }
+
+ if (fResetInputMapping)
+ {
+ /// @todo Rename to m* and verify whether some kind of lock is required.
+ xInputMappingOrigin = 0;
+ yInputMappingOrigin = 0;
+ cxInputMapping = 0;
+ cyInputMapping = 0;
+ }
+
+ alock.release();
+
+ return i_handleDisplayResize(pScreen->u32ViewIndex, pScreen->u16BitsPerPixel,
+ (uint8_t *)pvVRAM + pScreen->u32StartOffset,
+ pScreen->u32LineSize, pScreen->u32Width, pScreen->u32Height, pScreen->u16Flags,
+ pScreen->i32OriginX, pScreen->i32OriginY, false);
+}
+
+DECLCALLBACK(int) Display::i_displayVBVAMousePointerShape(PPDMIDISPLAYCONNECTOR pInterface, bool fVisible, bool fAlpha,
+ uint32_t xHot, uint32_t yHot,
+ uint32_t cx, uint32_t cy,
+ const void *pvShape)
+{
+ LogFlowFunc(("\n"));
+ LogRel2(("%s: fVisible=%RTbool\n", __PRETTY_FUNCTION__, fVisible));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+
+ uint32_t cbShape = 0;
+ if (pvShape)
+ {
+ cbShape = (cx + 7) / 8 * cy; /* size of the AND mask */
+ cbShape = ((cbShape + 3) & ~3) + cx * 4 * cy; /* + gap + size of the XOR mask */
+ }
+
+ /* Tell the console about it */
+ pDrv->pDisplay->mParent->i_onMousePointerShapeChange(fVisible, fAlpha,
+ xHot, yHot, cx, cy, (uint8_t *)pvShape, cbShape);
+
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(void) Display::i_displayVBVAGuestCapabilityUpdate(PPDMIDISPLAYCONNECTOR pInterface, uint32_t fCapabilities)
+{
+ LogFlowFunc(("\n"));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+
+ pThis->i_handleUpdateGuestVBVACapabilities(fCapabilities);
+}
+
+DECLCALLBACK(void) Display::i_displayVBVAInputMappingUpdate(PPDMIDISPLAYCONNECTOR pInterface, int32_t xOrigin, int32_t yOrigin,
+ uint32_t cx, uint32_t cy)
+{
+ LogFlowFunc(("\n"));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+
+ pThis->i_handleUpdateVBVAInputMapping(xOrigin, yOrigin, cx, cy);
+}
+
+DECLCALLBACK(void) Display::i_displayVBVAReportCursorPosition(PPDMIDISPLAYCONNECTOR pInterface, uint32_t fFlags, uint32_t aScreenId, uint32_t x, uint32_t y)
+{
+ LogFlowFunc(("\n"));
+ LogRel2(("%s: fFlags=%RU32, aScreenId=%RU32, x=%RU32, y=%RU32\n",
+ __PRETTY_FUNCTION__, fFlags, aScreenId, x, y));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+
+ if (fFlags & VBVA_CURSOR_SCREEN_RELATIVE)
+ {
+ AssertReturnVoid(aScreenId < pThis->mcMonitors);
+
+ x += pThis->maFramebuffers[aScreenId].xOrigin;
+ y += pThis->maFramebuffers[aScreenId].yOrigin;
+ }
+ ::FireCursorPositionChangedEvent(pThis->mParent->i_getEventSource(), RT_BOOL(fFlags & VBVA_CURSOR_VALID_DATA), x, y);
+}
+
+#endif /* VBOX_WITH_HGSMI */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) Display::i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINDISPLAY pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINDISPLAY);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIDISPLAYCONNECTOR, &pDrv->IConnector);
+ return NULL;
+}
+
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnPowerOff,
+ * Tries to ensure no client calls gets to HGCM or the VGA device from here on.}
+ */
+DECLCALLBACK(void) Display::i_drvPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVMAINDISPLAY pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINDISPLAY);
+ LogRelFlowFunc(("iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Do much of the work that i_drvDestruct does.
+ */
+ if (pThis->pUpPort)
+ pThis->pUpPort->pfnSetRenderVRAM(pThis->pUpPort, false);
+
+ pThis->IConnector.pbData = NULL;
+ pThis->IConnector.cbScanline = 0;
+ pThis->IConnector.cBits = 32;
+ pThis->IConnector.cx = 0;
+ pThis->IConnector.cy = 0;
+
+ if (pThis->pDisplay)
+ {
+ AutoWriteLock displayLock(pThis->pDisplay COMMA_LOCKVAL_SRC_POS);
+#ifdef VBOX_WITH_RECORDING
+ pThis->pDisplay->mParent->i_recordingStop();
+#endif
+#if defined(VBOX_WITH_VIDEOHWACCEL)
+ pThis->pVBVACallbacks = NULL;
+#endif
+ }
+}
+
+
+/**
+ * Destruct a display driver instance.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The driver instance data.
+ */
+DECLCALLBACK(void) Display::i_drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVMAINDISPLAY pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINDISPLAY);
+ LogRelFlowFunc(("iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * We repeat much of what i_drvPowerOff does in case it wasn't called.
+ * In addition we sever the connection between us and the display.
+ */
+ if (pThis->pUpPort)
+ pThis->pUpPort->pfnSetRenderVRAM(pThis->pUpPort, false);
+
+ pThis->IConnector.pbData = NULL;
+ pThis->IConnector.cbScanline = 0;
+ pThis->IConnector.cBits = 32;
+ pThis->IConnector.cx = 0;
+ pThis->IConnector.cy = 0;
+
+ if (pThis->pDisplay)
+ {
+ AutoWriteLock displayLock(pThis->pDisplay COMMA_LOCKVAL_SRC_POS);
+#ifdef VBOX_WITH_RECORDING
+ pThis->pDisplay->mParent->i_recordingStop();
+#endif
+#if defined(VBOX_WITH_VIDEOHWACCEL)
+ pThis->pVBVACallbacks = NULL;
+#endif
+
+ pThis->pDisplay->mpDrv = NULL;
+ pThis->pDisplay = NULL;
+ }
+#if defined(VBOX_WITH_VIDEOHWACCEL)
+ pThis->pVBVACallbacks = NULL;
+#endif
+}
+
+
+/**
+ * Construct a display driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+DECLCALLBACK(int) Display::i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ RT_NOREF(fFlags, pCfg);
+ PDRVMAINDISPLAY pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINDISPLAY);
+ LogRelFlowFunc(("iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", "");
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * Init Interfaces.
+ */
+ pDrvIns->IBase.pfnQueryInterface = Display::i_drvQueryInterface;
+
+ pThis->IConnector.pfnResize = Display::i_displayResizeCallback;
+ pThis->IConnector.pfnUpdateRect = Display::i_displayUpdateCallback;
+ pThis->IConnector.pfnRefresh = Display::i_displayRefreshCallback;
+ pThis->IConnector.pfnReset = Display::i_displayResetCallback;
+ pThis->IConnector.pfnLFBModeChange = Display::i_displayLFBModeChangeCallback;
+ pThis->IConnector.pfnProcessAdapterData = Display::i_displayProcessAdapterDataCallback;
+ pThis->IConnector.pfnProcessDisplayData = Display::i_displayProcessDisplayDataCallback;
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ pThis->IConnector.pfnVHWACommandProcess = Display::i_displayVHWACommandProcess;
+#endif
+#ifdef VBOX_WITH_HGSMI
+ pThis->IConnector.pfnVBVAEnable = Display::i_displayVBVAEnable;
+ pThis->IConnector.pfnVBVADisable = Display::i_displayVBVADisable;
+ pThis->IConnector.pfnVBVAUpdateBegin = Display::i_displayVBVAUpdateBegin;
+ pThis->IConnector.pfnVBVAUpdateProcess = Display::i_displayVBVAUpdateProcess;
+ pThis->IConnector.pfnVBVAUpdateEnd = Display::i_displayVBVAUpdateEnd;
+ pThis->IConnector.pfnVBVAResize = Display::i_displayVBVAResize;
+ pThis->IConnector.pfnVBVAMousePointerShape = Display::i_displayVBVAMousePointerShape;
+ pThis->IConnector.pfnVBVAGuestCapabilityUpdate = Display::i_displayVBVAGuestCapabilityUpdate;
+ pThis->IConnector.pfnVBVAInputMappingUpdate = Display::i_displayVBVAInputMappingUpdate;
+ pThis->IConnector.pfnVBVAReportCursorPosition = Display::i_displayVBVAReportCursorPosition;
+#endif
+ pThis->IConnector.pfn3DNotifyProcess = Display::i_display3DNotifyProcess;
+
+ /*
+ * Get the IDisplayPort interface of the above driver/device.
+ */
+ pThis->pUpPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIDISPLAYPORT);
+ if (!pThis->pUpPort)
+ {
+ AssertMsgFailed(("Configuration error: No display port interface above!\n"));
+ return VERR_PDM_MISSING_INTERFACE_ABOVE;
+ }
+#if defined(VBOX_WITH_VIDEOHWACCEL)
+ pThis->pVBVACallbacks = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIDISPLAYVBVACALLBACKS);
+ if (!pThis->pVBVACallbacks)
+ {
+ AssertMsgFailed(("Configuration error: No VBVA callback interface above!\n"));
+ return VERR_PDM_MISSING_INTERFACE_ABOVE;
+ }
+#endif
+ /*
+ * Get the Display object pointer and update the mpDrv member.
+ */
+ com::Guid uuid(COM_IIDOF(IDisplay));
+ IDisplay *pIDisplay = (IDisplay *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw());
+ if (!pIDisplay)
+ {
+ AssertMsgFailed(("Configuration error: No/bad Keyboard object!\n"));
+ return VERR_NOT_FOUND;
+ }
+ pThis->pDisplay = static_cast<Display *>(pIDisplay);
+ pThis->pDisplay->mpDrv = pThis;
+
+ /* Disable VRAM to a buffer copy initially. */
+ pThis->pUpPort->pfnSetRenderVRAM(pThis->pUpPort, false);
+ pThis->IConnector.cBits = 32; /* DevVGA does nothing otherwise. */
+
+ /*
+ * Start periodic screen refreshes
+ */
+ pThis->pUpPort->pfnSetRefreshRate(pThis->pUpPort, 20);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Display driver registration record.
+ */
+const PDMDRVREG Display::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "MainDisplay",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Main display driver (Main as in the API).",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_DISPLAY,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMAINDISPLAY),
+ /* pfnConstruct */
+ Display::i_drvConstruct,
+ /* pfnDestruct */
+ Display::i_drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ Display::i_drvPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/DisplayImplLegacy.cpp b/src/VBox/Main/src-client/DisplayImplLegacy.cpp
new file mode 100644
index 00000000..7f00a4d0
--- /dev/null
+++ b/src/VBox/Main/src-client/DisplayImplLegacy.cpp
@@ -0,0 +1,1018 @@
+/* $Id: DisplayImplLegacy.cpp $ */
+/** @file
+ * VirtualBox IDisplay implementation, helpers for legacy GAs.
+ *
+ * Methods and helpers to support old Guest Additions 3.x or older.
+ * This is not used by the current Guest Additions.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY
+#include "LoggingNew.h"
+
+#include "DisplayImpl.h"
+#include "ConsoleImpl.h"
+#include "ConsoleVRDPServer.h"
+#include "VMMDev.h"
+#include <VBox/VMMDev.h>
+
+/* generated header */
+#include "VBoxEvents.h"
+
+
+int videoAccelConstruct(VIDEOACCEL *pVideoAccel)
+{
+ pVideoAccel->pVbvaMemory = NULL;
+ pVideoAccel->fVideoAccelEnabled = false;
+
+ pVideoAccel->pu8VbvaPartial = NULL;
+ pVideoAccel->cbVbvaPartial = 0;
+
+ pVideoAccel->hXRoadsVideoAccel = NIL_RTSEMXROADS;
+ int vrc = RTSemXRoadsCreate(&pVideoAccel->hXRoadsVideoAccel);
+ AssertRC(vrc);
+
+ return vrc;
+}
+
+void videoAccelDestroy(VIDEOACCEL *pVideoAccel)
+{
+ RTSemXRoadsDestroy(pVideoAccel->hXRoadsVideoAccel);
+ RT_ZERO(*pVideoAccel);
+}
+
+static unsigned mapCoordsToScreen(DISPLAYFBINFO *pInfos, unsigned cInfos, int *px, int *py, int *pw, int *ph)
+{
+ RT_NOREF(pw, ph);
+
+ DISPLAYFBINFO *pInfo = pInfos;
+ unsigned uScreenId;
+ Log9(("mapCoordsToScreen: %d,%d %dx%d\n", *px, *py, *pw, *ph));
+ for (uScreenId = 0; uScreenId < cInfos; uScreenId++, pInfo++)
+ {
+ Log9((" [%d] %d,%d %dx%d\n", uScreenId, pInfo->xOrigin, pInfo->yOrigin, pInfo->w, pInfo->h));
+ if ( (pInfo->xOrigin <= *px && *px < pInfo->xOrigin + (int)pInfo->w)
+ && (pInfo->yOrigin <= *py && *py < pInfo->yOrigin + (int)pInfo->h))
+ {
+ /* The rectangle belongs to the screen. Correct coordinates. */
+ *px -= pInfo->xOrigin;
+ *py -= pInfo->yOrigin;
+ Log9((" -> %d,%d", *px, *py));
+ break;
+ }
+ }
+ if (uScreenId == cInfos)
+ {
+ /* Map to primary screen. */
+ uScreenId = 0;
+ }
+ Log9((" scr %d\n", uScreenId));
+ return uScreenId;
+}
+
+
+typedef struct _VBVADIRTYREGION
+{
+ /* Copies of object's pointers used by vbvaRgn functions. */
+ DISPLAYFBINFO *paFramebuffers;
+ unsigned cMonitors;
+ Display *pDisplay;
+ PPDMIDISPLAYPORT pPort;
+
+ /* The rectangle that includes all dirty rectangles. */
+ RTRECT aDirtyRects[SchemaDefs::MaxGuestMonitors];
+
+} VBVADIRTYREGION;
+
+static void vbvaRgnInit(VBVADIRTYREGION *prgn, DISPLAYFBINFO *paFramebuffers, unsigned cMonitors,
+ Display *pd, PPDMIDISPLAYPORT pp)
+{
+ prgn->paFramebuffers = paFramebuffers;
+ prgn->cMonitors = cMonitors;
+ prgn->pDisplay = pd;
+ prgn->pPort = pp;
+
+ RT_ZERO(prgn->aDirtyRects);
+}
+
+static void vbvaRgnDirtyRect(VBVADIRTYREGION *prgn, unsigned uScreenId, VBVACMDHDR *phdr)
+{
+ Log9(("x = %d, y = %d, w = %d, h = %d\n", phdr->x, phdr->y, phdr->w, phdr->h));
+
+ /*
+ * Here update rectangles are accumulated to form an update area.
+ */
+ /** @todo
+ * Now the simplest method is used which builds one rectangle that
+ * includes all update areas. A bit more advanced method can be
+ * employed here. The method should be fast however.
+ */
+ if (phdr->w == 0 || phdr->h == 0)
+ {
+ /* Empty rectangle. */
+ return;
+ }
+
+ int32_t xRight = phdr->x + phdr->w;
+ int32_t yBottom = phdr->y + phdr->h;
+
+ RTRECT *pDirtyRect = &prgn->aDirtyRects[uScreenId];
+ DISPLAYFBINFO *pFBInfo = &prgn->paFramebuffers[uScreenId];
+
+ if (pDirtyRect->xRight == 0)
+ {
+ /* This is the first rectangle to be added. */
+ pDirtyRect->xLeft = phdr->x;
+ pDirtyRect->yTop = phdr->y;
+ pDirtyRect->xRight = xRight;
+ pDirtyRect->yBottom = yBottom;
+ }
+ else
+ {
+ /* Adjust region coordinates. */
+ if (pDirtyRect->xLeft > phdr->x)
+ {
+ pDirtyRect->xLeft = phdr->x;
+ }
+
+ if (pDirtyRect->yTop > phdr->y)
+ {
+ pDirtyRect->yTop = phdr->y;
+ }
+
+ if (pDirtyRect->xRight < xRight)
+ {
+ pDirtyRect->xRight = xRight;
+ }
+
+ if (pDirtyRect->yBottom < yBottom)
+ {
+ pDirtyRect->yBottom = yBottom;
+ }
+ }
+
+ if (pFBInfo->fDefaultFormat)
+ {
+ /// @todo pfnUpdateDisplayRect must take the vram offset parameter for the framebuffer
+ prgn->pPort->pfnUpdateDisplayRect(prgn->pPort, phdr->x, phdr->y, phdr->w, phdr->h);
+ prgn->pDisplay->i_handleDisplayUpdate(uScreenId, phdr->x, phdr->y, phdr->w, phdr->h);
+ }
+
+ return;
+}
+
+static void vbvaRgnUpdateFramebuffer(VBVADIRTYREGION *prgn, unsigned uScreenId)
+{
+ RTRECT *pDirtyRect = &prgn->aDirtyRects[uScreenId];
+ DISPLAYFBINFO *pFBInfo = &prgn->paFramebuffers[uScreenId];
+
+ uint32_t w = pDirtyRect->xRight - pDirtyRect->xLeft;
+ uint32_t h = pDirtyRect->yBottom - pDirtyRect->yTop;
+
+ if (!pFBInfo->fDefaultFormat && w != 0 && h != 0)
+ {
+ /// @todo pfnUpdateDisplayRect must take the vram offset parameter for the framebuffer
+ prgn->pPort->pfnUpdateDisplayRect(prgn->pPort, pDirtyRect->xLeft, pDirtyRect->yTop, w, h);
+ prgn->pDisplay->i_handleDisplayUpdate(uScreenId, pDirtyRect->xLeft, pDirtyRect->yTop, w, h);
+ }
+}
+
+void i_vbvaSetMemoryFlags(VBVAMEMORY *pVbvaMemory,
+ bool fVideoAccelEnabled,
+ bool fVideoAccelVRDP,
+ uint32_t fu32SupportedOrders,
+ DISPLAYFBINFO *paFBInfos,
+ unsigned cFBInfos)
+{
+ if (pVbvaMemory)
+ {
+ /* This called only on changes in mode. So reset VRDP always. */
+ uint32_t fu32Flags = VBVA_F_MODE_VRDP_RESET;
+
+ if (fVideoAccelEnabled)
+ {
+ fu32Flags |= VBVA_F_MODE_ENABLED;
+
+ if (fVideoAccelVRDP)
+ {
+ fu32Flags |= VBVA_F_MODE_VRDP | VBVA_F_MODE_VRDP_ORDER_MASK;
+
+ pVbvaMemory->fu32SupportedOrders = fu32SupportedOrders;
+ }
+ }
+
+ pVbvaMemory->fu32ModeFlags = fu32Flags;
+ }
+
+ unsigned uScreenId;
+ for (uScreenId = 0; uScreenId < cFBInfos; uScreenId++)
+ {
+ if (paFBInfos[uScreenId].pHostEvents)
+ {
+ paFBInfos[uScreenId].pHostEvents->fu32Events |= VBOX_VIDEO_INFO_HOST_EVENTS_F_VRDP_RESET;
+ }
+ }
+}
+
+bool Display::i_VideoAccelAllowed(void)
+{
+ return true;
+}
+
+int videoAccelEnterVGA(VIDEOACCEL *pVideoAccel)
+{
+ return RTSemXRoadsNSEnter(pVideoAccel->hXRoadsVideoAccel);
+}
+
+void videoAccelLeaveVGA(VIDEOACCEL *pVideoAccel)
+{
+ RTSemXRoadsNSLeave(pVideoAccel->hXRoadsVideoAccel);
+}
+
+int videoAccelEnterVMMDev(VIDEOACCEL *pVideoAccel)
+{
+ return RTSemXRoadsEWEnter(pVideoAccel->hXRoadsVideoAccel);
+}
+
+void videoAccelLeaveVMMDev(VIDEOACCEL *pVideoAccel)
+{
+ RTSemXRoadsEWLeave(pVideoAccel->hXRoadsVideoAccel);
+}
+
+/**
+ * @thread EMT
+ */
+int Display::i_VideoAccelEnable(bool fEnable, VBVAMEMORY *pVbvaMemory, PPDMIDISPLAYPORT pUpPort)
+{
+ LogRelFlowFunc(("fEnable = %d\n", fEnable));
+
+ int vrc = i_videoAccelEnable(fEnable, pVbvaMemory, pUpPort);
+
+ LogRelFlowFunc(("%Rrc.\n", vrc));
+ return vrc;
+}
+
+int Display::i_videoAccelEnable(bool fEnable, VBVAMEMORY *pVbvaMemory, PPDMIDISPLAYPORT pUpPort)
+{
+ VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy;
+
+ /* Called each time the guest wants to use acceleration,
+ * or when the VGA device disables acceleration,
+ * or when restoring the saved state with accel enabled.
+ *
+ * VGA device disables acceleration on each video mode change
+ * and on reset.
+ *
+ * Guest enabled acceleration at will. And it has to enable
+ * acceleration after a mode change.
+ */
+ LogRelFlowFunc(("mfVideoAccelEnabled = %d, fEnable = %d, pVbvaMemory = %p\n",
+ pVideoAccel->fVideoAccelEnabled, fEnable, pVbvaMemory));
+
+ /* Strictly check parameters. Callers must not pass anything in the case. */
+ Assert((fEnable && pVbvaMemory) || (!fEnable && pVbvaMemory == NULL));
+
+ if (!i_VideoAccelAllowed ())
+ return VERR_NOT_SUPPORTED;
+
+ /* Check that current status is not being changed */
+ if (pVideoAccel->fVideoAccelEnabled == fEnable)
+ return VINF_SUCCESS;
+
+ if (pVideoAccel->fVideoAccelEnabled)
+ {
+ /* Process any pending orders and empty the VBVA ring buffer. */
+ i_videoAccelFlush (pUpPort);
+ }
+
+ if (!fEnable && pVideoAccel->pVbvaMemory)
+ pVideoAccel->pVbvaMemory->fu32ModeFlags &= ~VBVA_F_MODE_ENABLED;
+
+ if (fEnable)
+ {
+ /* Process any pending VGA device changes, resize. */
+ pUpPort->pfnUpdateDisplayAll(pUpPort, /* fFailOnResize = */ false);
+ }
+
+ /* Protect the videoaccel state transition. */
+ RTCritSectEnter(&mVideoAccelLock);
+
+ if (fEnable)
+ {
+ /* Initialize the hardware memory. */
+ i_vbvaSetMemoryFlags(pVbvaMemory, true, mfVideoAccelVRDP,
+ mfu32SupportedOrders, maFramebuffers, mcMonitors);
+ pVbvaMemory->off32Data = 0;
+ pVbvaMemory->off32Free = 0;
+
+ memset(pVbvaMemory->aRecords, 0, sizeof(pVbvaMemory->aRecords));
+ pVbvaMemory->indexRecordFirst = 0;
+ pVbvaMemory->indexRecordFree = 0;
+
+ pVideoAccel->pVbvaMemory = pVbvaMemory;
+ pVideoAccel->fVideoAccelEnabled = true;
+
+ LogRel(("VBVA: Enabled.\n"));
+ }
+ else
+ {
+ pVideoAccel->pVbvaMemory = NULL;
+ pVideoAccel->fVideoAccelEnabled = false;
+
+ LogRel(("VBVA: Disabled.\n"));
+ }
+
+ RTCritSectLeave(&mVideoAccelLock);
+
+ if (!fEnable)
+ {
+ pUpPort->pfnUpdateDisplayAll(pUpPort, /* fFailOnResize = */ false);
+ }
+
+ /* Notify the VMMDev, which saves VBVA status in the saved state,
+ * and needs to know current status.
+ */
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ pVMMDevPort->pfnVBVAChange(pVMMDevPort, fEnable);
+ }
+
+ LogRelFlowFunc(("VINF_SUCCESS.\n"));
+ return VINF_SUCCESS;
+}
+
+static bool i_vbvaVerifyRingBuffer(VBVAMEMORY *pVbvaMemory)
+{
+ RT_NOREF(pVbvaMemory);
+ return true;
+}
+
+static void i_vbvaFetchBytes(VBVAMEMORY *pVbvaMemory, uint8_t *pu8Dst, uint32_t cbDst)
+{
+ if (cbDst >= VBVA_RING_BUFFER_SIZE)
+ {
+ AssertMsgFailed(("cbDst = 0x%08X, ring buffer size 0x%08X\n", cbDst, VBVA_RING_BUFFER_SIZE));
+ return;
+ }
+
+ uint32_t u32BytesTillBoundary = VBVA_RING_BUFFER_SIZE - pVbvaMemory->off32Data;
+ uint8_t *src = &pVbvaMemory->au8RingBuffer[pVbvaMemory->off32Data];
+ int32_t i32Diff = cbDst - u32BytesTillBoundary;
+
+ if (i32Diff <= 0)
+ {
+ /* Chunk will not cross buffer boundary. */
+ memcpy (pu8Dst, src, cbDst);
+ }
+ else
+ {
+ /* Chunk crosses buffer boundary. */
+ memcpy(pu8Dst, src, u32BytesTillBoundary);
+ memcpy(pu8Dst + u32BytesTillBoundary, &pVbvaMemory->au8RingBuffer[0], i32Diff);
+ }
+
+ /* Advance data offset. */
+ pVbvaMemory->off32Data = (pVbvaMemory->off32Data + cbDst) % VBVA_RING_BUFFER_SIZE;
+
+ return;
+}
+
+
+static bool i_vbvaPartialRead(uint8_t **ppu8, uint32_t *pcb, uint32_t cbRecord, VBVAMEMORY *pVbvaMemory)
+{
+ uint8_t *pu8New;
+
+ LogFlow(("MAIN::DisplayImpl::vbvaPartialRead: p = %p, cb = %d, cbRecord 0x%08X\n",
+ *ppu8, *pcb, cbRecord));
+
+ if (*ppu8)
+ {
+ Assert (*pcb);
+ pu8New = (uint8_t *)RTMemRealloc(*ppu8, cbRecord);
+ }
+ else
+ {
+ Assert (!*pcb);
+ pu8New = (uint8_t *)RTMemAlloc(cbRecord);
+ }
+
+ if (!pu8New)
+ {
+ /* Memory allocation failed, fail the function. */
+ Log(("MAIN::vbvaPartialRead: failed to (re)alocate memory for partial record!!! cbRecord 0x%08X\n",
+ cbRecord));
+
+ if (*ppu8)
+ {
+ RTMemFree(*ppu8);
+ }
+
+ *ppu8 = NULL;
+ *pcb = 0;
+
+ return false;
+ }
+
+ /* Fetch data from the ring buffer. */
+ i_vbvaFetchBytes(pVbvaMemory, pu8New + *pcb, cbRecord - *pcb);
+
+ *ppu8 = pu8New;
+ *pcb = cbRecord;
+
+ return true;
+}
+
+/* For contiguous chunks just return the address in the buffer.
+ * For crossing boundary - allocate a buffer from heap.
+ */
+static bool i_vbvaFetchCmd(VIDEOACCEL *pVideoAccel, VBVACMDHDR **ppHdr, uint32_t *pcbCmd)
+{
+ VBVAMEMORY *pVbvaMemory = pVideoAccel->pVbvaMemory;
+
+ uint32_t indexRecordFirst = pVbvaMemory->indexRecordFirst;
+ uint32_t indexRecordFree = pVbvaMemory->indexRecordFree;
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("first = %d, free = %d\n",
+ indexRecordFirst, indexRecordFree));
+#endif /* DEBUG_sunlover */
+
+ if (!i_vbvaVerifyRingBuffer(pVbvaMemory))
+ {
+ return false;
+ }
+
+ if (indexRecordFirst == indexRecordFree)
+ {
+ /* No records to process. Return without assigning output variables. */
+ return true;
+ }
+
+ uint32_t cbRecordCurrent = ASMAtomicReadU32(&pVbvaMemory->aRecords[indexRecordFirst].cbRecord);
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("cbRecord = 0x%08X\n", cbRecordCurrent));
+#endif /* DEBUG_sunlover */
+
+ uint32_t cbRecord = cbRecordCurrent & ~VBVA_F_RECORD_PARTIAL;
+
+ if (pVideoAccel->cbVbvaPartial)
+ {
+ /* There is a partial read in process. Continue with it. */
+
+ Assert(pVideoAccel->pu8VbvaPartial);
+
+ LogFlowFunc(("continue partial record cbVbvaPartial = %d cbRecord 0x%08X, first = %d, free = %d\n",
+ pVideoAccel->cbVbvaPartial, cbRecordCurrent, indexRecordFirst, indexRecordFree));
+
+ if (cbRecord > pVideoAccel->cbVbvaPartial)
+ {
+ /* New data has been added to the record. */
+ if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, pVbvaMemory))
+ {
+ return false;
+ }
+ }
+
+ if (!(cbRecordCurrent & VBVA_F_RECORD_PARTIAL))
+ {
+ /* The record is completed by guest. Return it to the caller. */
+ *ppHdr = (VBVACMDHDR *)pVideoAccel->pu8VbvaPartial;
+ *pcbCmd = pVideoAccel->cbVbvaPartial;
+
+ pVideoAccel->pu8VbvaPartial = NULL;
+ pVideoAccel->cbVbvaPartial = 0;
+
+ /* Advance the record index. */
+ pVbvaMemory->indexRecordFirst = (indexRecordFirst + 1) % VBVA_MAX_RECORDS;
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("partial done ok, data = %d, free = %d\n",
+ pVbvaMemory->off32Data, pVbvaMemory->off32Free));
+#endif /* DEBUG_sunlover */
+ }
+
+ return true;
+ }
+
+ /* A new record need to be processed. */
+ if (cbRecordCurrent & VBVA_F_RECORD_PARTIAL)
+ {
+ /* Current record is being written by guest. '=' is important here. */
+ if (cbRecord >= VBVA_RING_BUFFER_SIZE - VBVA_RING_BUFFER_THRESHOLD)
+ {
+ /* Partial read must be started. */
+ if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, pVbvaMemory))
+ {
+ return false;
+ }
+
+ LogFlowFunc(("started partial record cbVbvaPartial = 0x%08X cbRecord 0x%08X, first = %d, free = %d\n",
+ pVideoAccel->cbVbvaPartial, cbRecordCurrent, indexRecordFirst, indexRecordFree));
+ }
+
+ return true;
+ }
+
+ /* Current record is complete. If it is not empty, process it. */
+ if (cbRecord)
+ {
+ /* The size of largest contiguous chunk in the ring biffer. */
+ uint32_t u32BytesTillBoundary = VBVA_RING_BUFFER_SIZE - pVbvaMemory->off32Data;
+
+ /* The ring buffer pointer. */
+ uint8_t *au8RingBuffer = &pVbvaMemory->au8RingBuffer[0];
+
+ /* The pointer to data in the ring buffer. */
+ uint8_t *src = &au8RingBuffer[pVbvaMemory->off32Data];
+
+ /* Fetch or point the data. */
+ if (u32BytesTillBoundary >= cbRecord)
+ {
+ /* The command does not cross buffer boundary. Return address in the buffer. */
+ *ppHdr = (VBVACMDHDR *)src;
+
+ /* Advance data offset. */
+ pVbvaMemory->off32Data = (pVbvaMemory->off32Data + cbRecord) % VBVA_RING_BUFFER_SIZE;
+ }
+ else
+ {
+ /* The command crosses buffer boundary. Rare case, so not optimized. */
+ uint8_t *dst = (uint8_t *)RTMemAlloc(cbRecord);
+
+ if (!dst)
+ {
+ LogRelFlowFunc(("could not allocate %d bytes from heap!!!\n", cbRecord));
+ pVbvaMemory->off32Data = (pVbvaMemory->off32Data + cbRecord) % VBVA_RING_BUFFER_SIZE;
+ return false;
+ }
+
+ i_vbvaFetchBytes(pVbvaMemory, dst, cbRecord);
+
+ *ppHdr = (VBVACMDHDR *)dst;
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("Allocated from heap %p\n", dst));
+#endif /* DEBUG_sunlover */
+ }
+ }
+
+ *pcbCmd = cbRecord;
+
+ /* Advance the record index. */
+ pVbvaMemory->indexRecordFirst = (indexRecordFirst + 1) % VBVA_MAX_RECORDS;
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("done ok, data = %d, free = %d\n",
+ pVbvaMemory->off32Data, pVbvaMemory->off32Free));
+#endif /* DEBUG_sunlover */
+
+ return true;
+}
+
+static void i_vbvaReleaseCmd(VIDEOACCEL *pVideoAccel, VBVACMDHDR *pHdr, int32_t cbCmd)
+{
+ RT_NOREF(cbCmd);
+ uint8_t *au8RingBuffer = pVideoAccel->pVbvaMemory->au8RingBuffer;
+
+ if ( (uint8_t *)pHdr >= au8RingBuffer
+ && (uint8_t *)pHdr < &au8RingBuffer[VBVA_RING_BUFFER_SIZE])
+ {
+ /* The pointer is inside ring buffer. Must be continuous chunk. */
+ Assert(VBVA_RING_BUFFER_SIZE - ((uint8_t *)pHdr - au8RingBuffer) >= cbCmd);
+
+ /* Do nothing. */
+
+ Assert(!pVideoAccel->pu8VbvaPartial && pVideoAccel->cbVbvaPartial == 0);
+ }
+ else
+ {
+ /* The pointer is outside. It is then an allocated copy. */
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("Free heap %p\n", pHdr));
+#endif /* DEBUG_sunlover */
+
+ if ((uint8_t *)pHdr == pVideoAccel->pu8VbvaPartial)
+ {
+ pVideoAccel->pu8VbvaPartial = NULL;
+ pVideoAccel->cbVbvaPartial = 0;
+ }
+ else
+ {
+ Assert(!pVideoAccel->pu8VbvaPartial && pVideoAccel->cbVbvaPartial == 0);
+ }
+
+ RTMemFree(pHdr);
+ }
+
+ return;
+}
+
+
+/**
+ * Called regularly on the DisplayRefresh timer.
+ * Also on behalf of guest, when the ring buffer is full.
+ *
+ * @thread EMT
+ */
+void Display::i_VideoAccelFlush(PPDMIDISPLAYPORT pUpPort)
+{
+ int vrc = i_videoAccelFlush(pUpPort);
+ if (RT_FAILURE(vrc))
+ {
+ /* Disable on errors. */
+ i_videoAccelEnable(false, NULL, pUpPort);
+ }
+}
+
+int Display::i_videoAccelFlush(PPDMIDISPLAYPORT pUpPort)
+{
+ VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy;
+ VBVAMEMORY *pVbvaMemory = pVideoAccel->pVbvaMemory;
+
+#ifdef DEBUG_sunlover_2
+ LogFlowFunc(("fVideoAccelEnabled = %d\n", pVideoAccel->fVideoAccelEnabled));
+#endif /* DEBUG_sunlover_2 */
+
+ if (!pVideoAccel->fVideoAccelEnabled)
+ {
+ Log(("Display::VideoAccelFlush: called with disabled VBVA!!! Ignoring.\n"));
+ return VINF_SUCCESS;
+ }
+
+ /* Here VBVA is enabled and we have the accelerator memory pointer. */
+ Assert(pVbvaMemory);
+
+#ifdef DEBUG_sunlover_2
+ LogFlowFunc(("indexRecordFirst = %d, indexRecordFree = %d, off32Data = %d, off32Free = %d\n",
+ pVbvaMemory->indexRecordFirst, pVbvaMemory->indexRecordFree,
+ pVbvaMemory->off32Data, pVbvaMemory->off32Free));
+#endif /* DEBUG_sunlover_2 */
+
+ /* Quick check for "nothing to update" case. */
+ if (pVbvaMemory->indexRecordFirst == pVbvaMemory->indexRecordFree)
+ {
+ return VINF_SUCCESS;
+ }
+
+ /* Process the ring buffer */
+ unsigned uScreenId;
+
+ /* Initialize dirty rectangles accumulator. */
+ VBVADIRTYREGION rgn;
+ vbvaRgnInit(&rgn, maFramebuffers, mcMonitors, this, pUpPort);
+
+ for (;;)
+ {
+ VBVACMDHDR *phdr = NULL;
+ uint32_t cbCmd = UINT32_MAX;
+
+ /* Fetch the command data. */
+ if (!i_vbvaFetchCmd(pVideoAccel, &phdr, &cbCmd))
+ {
+ Log(("Display::VideoAccelFlush: unable to fetch command. off32Data = %d, off32Free = %d. Disabling VBVA!!!\n",
+ pVbvaMemory->off32Data, pVbvaMemory->off32Free));
+ return VERR_INVALID_STATE;
+ }
+
+ if (cbCmd == uint32_t(~0))
+ {
+ /* No more commands yet in the queue. */
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("no command\n"));
+#endif /* DEBUG_sunlover */
+ break;
+ }
+
+ if (cbCmd != 0)
+ {
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("hdr: cbCmd = %d, x=%d, y=%d, w=%d, h=%d\n",
+ cbCmd, phdr->x, phdr->y, phdr->w, phdr->h));
+#endif /* DEBUG_sunlover */
+
+ VBVACMDHDR hdrSaved = *phdr;
+
+ int x = phdr->x;
+ int y = phdr->y;
+ int w = phdr->w;
+ int h = phdr->h;
+
+ uScreenId = mapCoordsToScreen(maFramebuffers, mcMonitors, &x, &y, &w, &h);
+
+ phdr->x = (int16_t)x;
+ phdr->y = (int16_t)y;
+ phdr->w = (uint16_t)w;
+ phdr->h = (uint16_t)h;
+
+ /* Handle the command.
+ *
+ * Guest is responsible for updating the guest video memory.
+ * The Windows guest does all drawing using Eng*.
+ *
+ * For local output, only dirty rectangle information is used
+ * to update changed areas.
+ *
+ * Dirty rectangles are accumulated to exclude overlapping updates and
+ * group small updates to a larger one.
+ */
+
+ /* Accumulate the update. */
+ vbvaRgnDirtyRect(&rgn, uScreenId, phdr);
+
+ /* Forward the command to VRDP server. */
+ mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, phdr, cbCmd);
+
+ *phdr = hdrSaved;
+ }
+
+ i_vbvaReleaseCmd(pVideoAccel, phdr, cbCmd);
+ }
+
+ for (uScreenId = 0; uScreenId < mcMonitors; uScreenId++)
+ {
+ /* Draw the framebuffer. */
+ vbvaRgnUpdateFramebuffer(&rgn, uScreenId);
+ }
+ return VINF_SUCCESS;
+}
+
+int Display::i_videoAccelRefreshProcess(PPDMIDISPLAYPORT pUpPort)
+{
+ int vrc = VWRN_INVALID_STATE; /* Default is to do a display update in VGA device. */
+
+ VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy;
+
+ videoAccelEnterVGA(pVideoAccel);
+
+ if (pVideoAccel->fVideoAccelEnabled)
+ {
+ Assert(pVideoAccel->pVbvaMemory);
+ vrc = i_videoAccelFlush(pUpPort);
+ if (RT_FAILURE(vrc))
+ {
+ /* Disable on errors. */
+ i_videoAccelEnable(false, NULL, pUpPort);
+ vrc = VWRN_INVALID_STATE; /* Do a display update in VGA device. */
+ }
+ else
+ {
+ vrc = VINF_SUCCESS;
+ }
+ }
+
+ videoAccelLeaveVGA(pVideoAccel);
+
+ return vrc;
+}
+
+void Display::processAdapterData(void *pvVRAM, uint32_t u32VRAMSize)
+{
+ RT_NOREF(u32VRAMSize);
+ if (pvVRAM == NULL)
+ {
+ unsigned i;
+ for (i = 0; i < mcMonitors; i++)
+ {
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[i];
+
+ pFBInfo->u32Offset = 0;
+ pFBInfo->u32MaxFramebufferSize = 0;
+ pFBInfo->u32InformationSize = 0;
+ }
+ }
+#ifndef VBOX_WITH_HGSMI
+ else
+ {
+ uint8_t *pu8 = (uint8_t *)pvVRAM;
+ pu8 += u32VRAMSize - VBOX_VIDEO_ADAPTER_INFORMATION_SIZE;
+
+ /// @todo
+ uint8_t *pu8End = pu8 + VBOX_VIDEO_ADAPTER_INFORMATION_SIZE;
+
+ VBOXVIDEOINFOHDR *pHdr;
+
+ for (;;)
+ {
+ pHdr = (VBOXVIDEOINFOHDR *)pu8;
+ pu8 += sizeof(VBOXVIDEOINFOHDR);
+
+ if (pu8 >= pu8End)
+ {
+ LogRel(("VBoxVideo: Guest adapter information overflow!!!\n"));
+ break;
+ }
+
+ if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_DISPLAY)
+ {
+ if (pHdr->u16Length != sizeof(VBOXVIDEOINFODISPLAY))
+ {
+ LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "DISPLAY", pHdr->u16Length));
+ break;
+ }
+
+ VBOXVIDEOINFODISPLAY *pDisplay = (VBOXVIDEOINFODISPLAY *)pu8;
+
+ if (pDisplay->u32Index >= mcMonitors)
+ {
+ LogRel(("VBoxVideo: Guest adapter information invalid display index %d!!!\n", pDisplay->u32Index));
+ break;
+ }
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[pDisplay->u32Index];
+
+ pFBInfo->u32Offset = pDisplay->u32Offset;
+ pFBInfo->u32MaxFramebufferSize = pDisplay->u32FramebufferSize;
+ pFBInfo->u32InformationSize = pDisplay->u32InformationSize;
+
+ LogRelFlow(("VBOX_VIDEO_INFO_TYPE_DISPLAY: %d: at 0x%08X, size 0x%08X, info 0x%08X\n", pDisplay->u32Index,
+ pDisplay->u32Offset, pDisplay->u32FramebufferSize, pDisplay->u32InformationSize));
+ }
+ else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_QUERY_CONF32)
+ {
+ if (pHdr->u16Length != sizeof(VBOXVIDEOINFOQUERYCONF32))
+ {
+ LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "CONF32", pHdr->u16Length));
+ break;
+ }
+
+ VBOXVIDEOINFOQUERYCONF32 *pConf32 = (VBOXVIDEOINFOQUERYCONF32 *)pu8;
+
+ switch (pConf32->u32Index)
+ {
+ case VBOX_VIDEO_QCI32_MONITOR_COUNT:
+ {
+ pConf32->u32Value = mcMonitors;
+ } break;
+
+ case VBOX_VIDEO_QCI32_OFFSCREEN_HEAP_SIZE:
+ {
+ /** @todo make configurable. */
+ pConf32->u32Value = _1M;
+ } break;
+
+ default:
+ LogRel(("VBoxVideo: CONF32 %d not supported!!! Skipping.\n", pConf32->u32Index));
+ }
+ }
+ else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_END)
+ {
+ if (pHdr->u16Length != 0)
+ {
+ LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "END", pHdr->u16Length));
+ break;
+ }
+
+ break;
+ }
+ else if (pHdr->u8Type != VBOX_VIDEO_INFO_TYPE_NV_HEAP)
+ {
+ /** @todo why is Additions/WINNT/Graphics/Miniport/VBoxVideo. cpp pushing this to us? */
+ LogRel(("Guest adapter information contains unsupported type %d. The block has been skipped.\n", pHdr->u8Type));
+ }
+
+ pu8 += pHdr->u16Length;
+ }
+ }
+#endif /* !VBOX_WITH_HGSMI */
+}
+
+void Display::processDisplayData(void *pvVRAM, unsigned uScreenId)
+{
+ if (uScreenId >= mcMonitors)
+ {
+ LogRel(("VBoxVideo: Guest display information invalid display index %d!!!\n", uScreenId));
+ return;
+ }
+
+ /* Get the display information structure. */
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId];
+
+ uint8_t *pu8 = (uint8_t *)pvVRAM;
+ pu8 += pFBInfo->u32Offset + pFBInfo->u32MaxFramebufferSize;
+
+ /// @todo
+ uint8_t *pu8End = pu8 + pFBInfo->u32InformationSize;
+
+ VBOXVIDEOINFOHDR *pHdr;
+
+ for (;;)
+ {
+ pHdr = (VBOXVIDEOINFOHDR *)pu8;
+ pu8 += sizeof(VBOXVIDEOINFOHDR);
+
+ if (pu8 >= pu8End)
+ {
+ LogRel(("VBoxVideo: Guest display information overflow!!!\n"));
+ break;
+ }
+
+ if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_SCREEN)
+ {
+ if (pHdr->u16Length != sizeof(VBOXVIDEOINFOSCREEN))
+ {
+ LogRel(("VBoxVideo: Guest display information %s invalid length %d!!!\n", "SCREEN", pHdr->u16Length));
+ break;
+ }
+
+ VBOXVIDEOINFOSCREEN *pScreen = (VBOXVIDEOINFOSCREEN *)pu8;
+
+ pFBInfo->xOrigin = pScreen->xOrigin;
+ pFBInfo->yOrigin = pScreen->yOrigin;
+
+ pFBInfo->w = pScreen->u16Width;
+ pFBInfo->h = pScreen->u16Height;
+
+ LogRelFlow(("VBOX_VIDEO_INFO_TYPE_SCREEN: (%p) %d: at %d,%d, linesize 0x%X, size %dx%d, bpp %d, flags 0x%02X\n",
+ pHdr, uScreenId, pScreen->xOrigin, pScreen->yOrigin, pScreen->u32LineSize, pScreen->u16Width,
+ pScreen->u16Height, pScreen->bitsPerPixel, pScreen->u8Flags));
+
+ if (uScreenId != VBOX_VIDEO_PRIMARY_SCREEN)
+ {
+ /* Primary screen resize is eeeeeeeee by the VGA device. */
+ if (pFBInfo->fDisabled)
+ {
+ pFBInfo->fDisabled = false;
+ ::FireGuestMonitorChangedEvent(mParent->i_getEventSource(), GuestMonitorChangedEventType_Enabled, uScreenId,
+ pFBInfo->xOrigin, pFBInfo->yOrigin, pFBInfo->w, pFBInfo->h);
+ }
+
+ i_handleDisplayResize(uScreenId, pScreen->bitsPerPixel,
+ (uint8_t *)pvVRAM + pFBInfo->u32Offset,
+ pScreen->u32LineSize,
+ pScreen->u16Width, pScreen->u16Height,
+ VBVA_SCREEN_F_ACTIVE,
+ pScreen->xOrigin, pScreen->yOrigin, false);
+ }
+ }
+ else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_END)
+ {
+ if (pHdr->u16Length != 0)
+ {
+ LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "END", pHdr->u16Length));
+ break;
+ }
+
+ break;
+ }
+ else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_HOST_EVENTS)
+ {
+ if (pHdr->u16Length != sizeof(VBOXVIDEOINFOHOSTEVENTS))
+ {
+ LogRel(("VBoxVideo: Guest display information %s invalid length %d!!!\n", "HOST_EVENTS", pHdr->u16Length));
+ break;
+ }
+
+ VBOXVIDEOINFOHOSTEVENTS *pHostEvents = (VBOXVIDEOINFOHOSTEVENTS *)pu8;
+
+ pFBInfo->pHostEvents = pHostEvents;
+
+ LogFlow(("VBOX_VIDEO_INFO_TYPE_HOSTEVENTS: (%p)\n",
+ pHostEvents));
+ }
+ else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_LINK)
+ {
+ if (pHdr->u16Length != sizeof(VBOXVIDEOINFOLINK))
+ {
+ LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "LINK", pHdr->u16Length));
+ break;
+ }
+
+ VBOXVIDEOINFOLINK *pLink = (VBOXVIDEOINFOLINK *)pu8;
+ pu8 += pLink->i32Offset;
+ }
+ else
+ {
+ LogRel(("Guest display information contains unsupported type %d\n", pHdr->u8Type));
+ }
+
+ pu8 += pHdr->u16Length;
+ }
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/DisplaySourceBitmapImpl.cpp b/src/VBox/Main/src-client/DisplaySourceBitmapImpl.cpp
new file mode 100644
index 00000000..7f190307
--- /dev/null
+++ b/src/VBox/Main/src-client/DisplaySourceBitmapImpl.cpp
@@ -0,0 +1,196 @@
+/* $Id: DisplaySourceBitmapImpl.cpp $ */
+/** @file
+ * Bitmap of a guest screen implementation.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_DISPLAYSOURCEBITMAP
+#include "LoggingNew.h"
+
+#include "DisplayImpl.h"
+
+/*
+ * DisplaySourceBitmap implementation.
+ */
+DEFINE_EMPTY_CTOR_DTOR(DisplaySourceBitmap)
+
+HRESULT DisplaySourceBitmap::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void DisplaySourceBitmap::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+HRESULT DisplaySourceBitmap::init(ComObjPtr<Display> pDisplay, unsigned uScreenId, DISPLAYFBINFO *pFBInfo)
+{
+ LogFlowThisFunc(("[%u]\n", uScreenId));
+
+ ComAssertRet(!pDisplay.isNull(), E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m.pDisplay = pDisplay;
+ m.uScreenId = uScreenId;
+ m.pFBInfo = pFBInfo;
+
+ m.pu8Allocated = NULL;
+
+ m.pu8Address = NULL;
+ m.ulWidth = 0;
+ m.ulHeight = 0;
+ m.ulBitsPerPixel = 0;
+ m.ulBytesPerLine = 0;
+ m.bitmapFormat = BitmapFormat_Opaque;
+
+ int vrc = initSourceBitmap(uScreenId, pFBInfo);
+ if (RT_FAILURE(vrc))
+ return E_FAIL;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+void DisplaySourceBitmap::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFunc(("[%u]\n", m.uScreenId));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m.pDisplay.setNull();
+ RTMemFree(m.pu8Allocated);
+}
+
+HRESULT DisplaySourceBitmap::getScreenId(ULONG *aScreenId)
+{
+ HRESULT hrc = S_OK;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aScreenId = m.uScreenId;
+ return hrc;
+}
+
+HRESULT DisplaySourceBitmap::queryBitmapInfo(BYTE **aAddress,
+ ULONG *aWidth,
+ ULONG *aHeight,
+ ULONG *aBitsPerPixel,
+ ULONG *aBytesPerLine,
+ BitmapFormat_T *aBitmapFormat)
+{
+ HRESULT hrc = S_OK;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAddress = m.pu8Address;
+ *aWidth = m.ulWidth;
+ *aHeight = m.ulHeight;
+ *aBitsPerPixel = m.ulBitsPerPixel;
+ *aBytesPerLine = m.ulBytesPerLine;
+ *aBitmapFormat = m.bitmapFormat;
+
+ return hrc;
+}
+
+int DisplaySourceBitmap::initSourceBitmap(unsigned aScreenId,
+ DISPLAYFBINFO *pFBInfo)
+{
+ RT_NOREF(aScreenId);
+ int vrc = VINF_SUCCESS;
+
+ if (pFBInfo->w == 0 || pFBInfo->h == 0)
+ {
+ return VERR_NOT_SUPPORTED;
+ }
+
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ if (pFBInfo->pu8FramebufferVRAM && pFBInfo->u16BitsPerPixel == 32 && !pFBInfo->fDisabled)
+ {
+ /* From VRAM. */
+ LogFunc(("%d from VRAM\n", aScreenId));
+ pAddress = pFBInfo->pu8FramebufferVRAM;
+ ulWidth = pFBInfo->w;
+ ulHeight = pFBInfo->h;
+ ulBitsPerPixel = pFBInfo->u16BitsPerPixel;
+ ulBytesPerLine = pFBInfo->u32LineSize;
+ bitmapFormat = BitmapFormat_BGR;
+ m.pu8Allocated = NULL;
+ }
+ else
+ {
+ /* Allocated byffer */
+ LogFunc(("%d allocated\n", aScreenId));
+ pAddress = NULL;
+ ulWidth = pFBInfo->w;
+ ulHeight = pFBInfo->h;
+ ulBitsPerPixel = 32;
+ ulBytesPerLine = ulWidth * 4;
+ bitmapFormat = BitmapFormat_BGR;
+
+ m.pu8Allocated = (uint8_t *)RTMemAlloc(ulBytesPerLine * ulHeight);
+ if (m.pu8Allocated == NULL)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ pAddress = m.pu8Allocated;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ m.pu8Address = pAddress;
+ m.ulWidth = ulWidth;
+ m.ulHeight = ulHeight;
+ m.ulBitsPerPixel = ulBitsPerPixel;
+ m.ulBytesPerLine = ulBytesPerLine;
+ m.bitmapFormat = bitmapFormat;
+ if (pFBInfo->fDisabled)
+ {
+ RT_BZERO(pAddress, ulBytesPerLine * ulHeight);
+ }
+ }
+
+ return vrc;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/DrvAudioRec.cpp b/src/VBox/Main/src-client/DrvAudioRec.cpp
new file mode 100644
index 00000000..b30b4f87
--- /dev/null
+++ b/src/VBox/Main/src-client/DrvAudioRec.cpp
@@ -0,0 +1,972 @@
+/* $Id: DrvAudioRec.cpp $ */
+/** @file
+ * Video recording audio backend for Main.
+ *
+ * This driver is part of Main and is responsible for providing audio
+ * data to Main's video capturing feature.
+ *
+ * The driver itself implements a PDM host audio backend, which in turn
+ * provides the driver with the required audio data and audio events.
+ *
+ * For now there is support for the following destinations (called "sinks"):
+ *
+ * - Direct writing of .webm files to the host.
+ * - Communicating with Main via the Console object to send the encoded audio data to.
+ * The Console object in turn then will route the data to the Display / video capturing interface then.
+ */
+
+/*
+ * Copyright (C) 2016-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_RECORDING
+#include "LoggingNew.h"
+
+#include "DrvAudioRec.h"
+#include "ConsoleImpl.h"
+
+#include "WebMWriter.h"
+
+#include <iprt/mem.h>
+#include <iprt/cdefs.h>
+
+#include "VBox/com/VirtualBox.h"
+#include <VBox/vmm/cfgm.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/err.h>
+#include "VBox/settings.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Enumeration for specifying the recording container type.
+ */
+typedef enum AVRECCONTAINERTYPE
+{
+ /** Unknown / invalid container type. */
+ AVRECCONTAINERTYPE_UNKNOWN = 0,
+ /** Recorded data goes to Main / Console. */
+ AVRECCONTAINERTYPE_MAIN_CONSOLE = 1,
+ /** Recorded data will be written to a .webm file. */
+ AVRECCONTAINERTYPE_WEBM = 2
+} AVRECCONTAINERTYPE;
+
+/**
+ * Structure for keeping generic container parameters.
+ */
+typedef struct AVRECCONTAINERPARMS
+{
+ /** Stream index (hint). */
+ uint32_t idxStream;
+ /** The container's type. */
+ AVRECCONTAINERTYPE enmType;
+ union
+ {
+ /** WebM file specifics. */
+ struct
+ {
+ /** Allocated file name to write .webm file to. Must be free'd. */
+ char *pszFile;
+ } WebM;
+ };
+
+} AVRECCONTAINERPARMS, *PAVRECCONTAINERPARMS;
+
+/**
+ * Structure for keeping container-specific data.
+ */
+typedef struct AVRECCONTAINER
+{
+ /** Generic container parameters. */
+ AVRECCONTAINERPARMS Parms;
+
+ union
+ {
+ struct
+ {
+ /** Pointer to Console. */
+ Console *pConsole;
+ } Main;
+
+ struct
+ {
+ /** Pointer to WebM container to write recorded audio data to.
+ * See the AVRECMODE enumeration for more information. */
+ WebMWriter *pWebM;
+ /** Assigned track number from WebM container. */
+ uint8_t uTrack;
+ } WebM;
+ };
+} AVRECCONTAINER, *PAVRECCONTAINER;
+
+/**
+ * Audio video recording sink.
+ */
+typedef struct AVRECSINK
+{
+ /** Pointer (weak) to recording stream to bind to. */
+ RecordingStream *pRecStream;
+ /** Container data to use for data processing. */
+ AVRECCONTAINER Con;
+ /** Timestamp (in ms) of when the sink was created. */
+ uint64_t tsStartMs;
+} AVRECSINK, *PAVRECSINK;
+
+/**
+ * Audio video recording (output) stream.
+ */
+typedef struct AVRECSTREAM
+{
+ /** Common part. */
+ PDMAUDIOBACKENDSTREAM Core;
+ /** The stream's acquired configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+ /** (Audio) frame buffer. */
+ PRTCIRCBUF pCircBuf;
+ /** Pointer to sink to use for writing. */
+ PAVRECSINK pSink;
+ /** Last encoded PTS (in ms). */
+ uint64_t uLastPTSMs;
+ /** Temporary buffer for the input (source) data to encode. */
+ void *pvSrcBuf;
+ /** Size (in bytes) of the temporary buffer holding the input (source) data to encode. */
+ size_t cbSrcBuf;
+} AVRECSTREAM, *PAVRECSTREAM;
+
+/**
+ * Video recording audio driver instance data.
+ */
+typedef struct DRVAUDIORECORDING
+{
+ /** Pointer to audio video recording object. */
+ AudioVideoRec *pAudioVideoRec;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+ /** Pointer to the console object. */
+ ComPtr<Console> pConsole;
+ /** Pointer to the DrvAudio port interface that is above us. */
+ AVRECCONTAINERPARMS ContainerParms;
+ /** Weak pointer to recording context to use. */
+ RecordingContext *pRecCtx;
+ /** The driver's sink for writing output to. */
+ AVRECSINK Sink;
+} DRVAUDIORECORDING, *PDRVAUDIORECORDING;
+
+
+AudioVideoRec::AudioVideoRec(Console *pConsole)
+ : AudioDriver(pConsole)
+ , mpDrv(NULL)
+{
+}
+
+
+AudioVideoRec::~AudioVideoRec(void)
+{
+ if (mpDrv)
+ {
+ mpDrv->pAudioVideoRec = NULL;
+ mpDrv = NULL;
+ }
+}
+
+
+/**
+ * Applies recording settings to this driver instance.
+ *
+ * @returns VBox status code.
+ * @param Settings Recording settings to apply.
+ */
+int AudioVideoRec::applyConfiguration(const settings::RecordingSettings &Settings)
+{
+ /** @todo Do some validation here. */
+ mSettings = Settings; /* Note: Does have an own copy operator. */
+ return VINF_SUCCESS;
+}
+
+
+int AudioVideoRec::configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM)
+{
+ /** @todo For now we're using the configuration of the first screen (screen 0) here audio-wise. */
+ unsigned const idxScreen = 0;
+
+ AssertReturn(mSettings.mapScreens.size() >= 1, VERR_INVALID_PARAMETER);
+ const settings::RecordingScreenSettings &screenSettings = mSettings.mapScreens[idxScreen];
+
+ int vrc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "ContainerType", (uint64_t)screenSettings.enmDest);
+ AssertRCReturn(vrc, vrc);
+ if (screenSettings.enmDest == RecordingDestination_File)
+ {
+ vrc = pVMM->pfnCFGMR3InsertString(pLunCfg, "ContainerFileName", Utf8Str(screenSettings.File.strName).c_str());
+ AssertRCReturn(vrc, vrc);
+ }
+
+ vrc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "StreamIndex", (uint32_t)idxScreen);
+ AssertRCReturn(vrc, vrc);
+
+ return AudioDriver::configureDriver(pLunCfg, pVMM);
+}
+
+
+/*********************************************************************************************************************************
+* PDMIHOSTAUDIO *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ /*
+ * Fill in the config structure.
+ */
+ RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VideoRec");
+ pBackendCfg->cbStream = sizeof(AVRECSTREAM);
+ pBackendCfg->fFlags = 0;
+ pBackendCfg->cMaxStreamsIn = 0;
+ pBackendCfg->cMaxStreamsOut = UINT32_MAX;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(pInterface, enmDir);
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * Creates an audio output stream and associates it with the specified recording sink.
+ *
+ * @returns VBox status code.
+ * @param pThis Driver instance.
+ * @param pStreamAV Audio output stream to create.
+ * @param pSink Recording sink to associate audio output stream to.
+ * @param pCfgReq Requested configuration by the audio backend.
+ * @param pCfgAcq Acquired configuration by the audio output stream.
+ */
+static int avRecCreateStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV,
+ PAVRECSINK pSink, PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ if (pCfgReq->enmPath != PDMAUDIOPATH_OUT_FRONT)
+ {
+ LogRel(("Recording: Support for surround audio not implemented yet\n"));
+ AssertFailed();
+ return VERR_NOT_SUPPORTED;
+ }
+
+ PRECORDINGCODEC pCodec = pSink->pRecStream->GetAudioCodec();
+
+ /* Stuff which has to be set by now. */
+ Assert(pCodec->Parms.cbFrame);
+ Assert(pCodec->Parms.msFrame);
+
+ int vrc = RTCircBufCreate(&pStreamAV->pCircBuf, pCodec->Parms.cbFrame * 2 /* Use "double buffering" */);
+ if (RT_SUCCESS(vrc))
+ {
+ size_t cbScratchBuf = pCodec->Parms.cbFrame;
+ pStreamAV->pvSrcBuf = RTMemAlloc(cbScratchBuf);
+ if (pStreamAV->pvSrcBuf)
+ {
+ pStreamAV->cbSrcBuf = cbScratchBuf;
+
+ pStreamAV->pSink = pSink; /* Assign sink to stream. */
+ pStreamAV->uLastPTSMs = 0;
+
+ /* Make sure to let the driver backend know that we need the audio data in
+ * a specific sampling rate the codec is optimized for. */
+ pCfgAcq->Props = pCodec->Parms.Audio.PCMProps;
+
+ /* Every codec frame marks a period for now. Optimize this later. */
+ pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pCodec->Parms.msFrame);
+ pCfgAcq->Backend.cFramesBufferSize = pCfgAcq->Backend.cFramesPeriod * 2;
+ pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod;
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
+ PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
+ AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ return VERR_NOT_SUPPORTED;
+
+ /* For now we only have one sink, namely the driver's one.
+ * Later each stream could have its own one, to e.g. router different stream to different sinks .*/
+ PAVRECSINK pSink = &pThis->Sink;
+
+ int vrc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq);
+ PDMAudioStrmCfgCopy(&pStreamAV->Cfg, pCfgAcq);
+
+ return vrc;
+}
+
+
+/**
+ * Destroys (closes) an audio output stream.
+ *
+ * @returns VBox status code.
+ * @param pThis Driver instance.
+ * @param pStreamAV Audio output stream to destroy.
+ */
+static int avRecDestroyStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV)
+{
+ RT_NOREF(pThis);
+
+ if (pStreamAV->pCircBuf)
+ {
+ RTCircBufDestroy(pStreamAV->pCircBuf);
+ pStreamAV->pCircBuf = NULL;
+ }
+
+ if (pStreamAV->pvSrcBuf)
+ {
+ Assert(pStreamAV->cbSrcBuf);
+ RTMemFree(pStreamAV->pvSrcBuf);
+ pStreamAV->pvSrcBuf = NULL;
+ pStreamAV->cbSrcBuf = 0;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ bool fImmediate)
+{
+ PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
+ PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ RT_NOREF(fImmediate);
+
+ int vrc = VINF_SUCCESS;
+ if (pStreamAV->Cfg.enmDir == PDMAUDIODIR_OUT)
+ vrc = avRecDestroyStreamOut(pThis, pStreamAV);
+
+ return vrc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVideoRecHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+ return PDMHOSTAUDIOSTREAMSTATE_OKAY;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
+
+ RecordingStream *pRecStream = pStreamAV->pSink->pRecStream;
+ PRECORDINGCODEC pCodec = pRecStream->GetAudioCodec();
+
+ return pCodec->Parms.cbFrame;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ RT_NOREF(pInterface);
+ PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
+ AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
+ if (cbBuf)
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(pcbWritten, VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+
+ uint32_t cbWrittenTotal = 0;
+
+ PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf;
+ AssertPtr(pCircBuf);
+
+ uint32_t cbToWrite = RT_MIN(cbBuf, (uint32_t)RTCircBufFree(pCircBuf));
+ AssertReturn(cbToWrite, VERR_BUFFER_OVERFLOW);
+
+ /*
+ * Write as much as we can into our internal ring buffer.
+ */
+ while (cbToWrite)
+ {
+ void *pvCircBuf = NULL;
+ size_t cbCircBuf = 0;
+ RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf);
+
+ Log3Func(("cbToWrite=%RU32, cbCircBuf=%zu\n", cbToWrite, cbCircBuf));
+
+ memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf),
+ cbWrittenTotal += (uint32_t)cbCircBuf;
+ Assert(cbWrittenTotal <= cbBuf);
+ Assert(cbToWrite >= cbCircBuf);
+ cbToWrite -= (uint32_t)cbCircBuf;
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf);
+ }
+
+ RecordingStream *pRecStream = pStreamAV->pSink->pRecStream;
+ PRECORDINGCODEC pCodec = pRecStream->GetAudioCodec();
+
+ /*
+ * Process our internal ring buffer and send the obtained audio data to our encoding thread.
+ */
+ cbToWrite = (uint32_t)RTCircBufUsed(pCircBuf);
+
+ /** @todo Can we encode more than a frame at a time? Optimize this! */
+ uint32_t const cbFrame = pCodec->Parms.cbFrame;
+
+ /* Only encode data if we have data for at least one full codec frame. */
+ while (cbToWrite >= cbFrame)
+ {
+ uint32_t cbSrc = 0;
+ do
+ {
+ void *pvCircBuf = NULL;
+ size_t cbCircBuf = 0;
+ RTCircBufAcquireReadBlock(pCircBuf, cbFrame - cbSrc, &pvCircBuf, &cbCircBuf);
+
+ Log3Func(("cbSrc=%RU32, cbCircBuf=%zu\n", cbSrc, cbCircBuf));
+
+ memcpy((uint8_t *)pStreamAV->pvSrcBuf + cbSrc, pvCircBuf, cbCircBuf);
+
+ cbSrc += (uint32_t)cbCircBuf;
+ Assert(cbSrc <= pStreamAV->cbSrcBuf);
+ Assert(cbSrc <= cbFrame);
+
+ RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf);
+
+ if (cbSrc == cbFrame) /* Only send full codec frames. */
+ {
+ vrc = pRecStream->SendAudioFrame(pStreamAV->pvSrcBuf, cbSrc, RTTimeProgramMilliTS());
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ } while (cbSrc < cbFrame);
+
+ Assert(cbToWrite >= cbFrame);
+ cbToWrite -= cbFrame;
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ } /* while */
+
+ *pcbWritten = cbWrittenTotal;
+
+ LogFlowFunc(("cbBuf=%RU32, cbWrittenTotal=%RU32, vrc=%Rrc\n", cbBuf, cbWrittenTotal, vrc));
+ return VINF_SUCCESS; /* Don't propagate encoding errors to the caller. */
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return 0; /* Video capturing does not provide any input. */
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ RT_NOREF(pInterface, pStream, pvBuf, cbBuf);
+ *pcbRead = 0;
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* PDMIBASE *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDRVREG *
+*********************************************************************************************************************************/
+
+/**
+ * Shuts down (closes) a recording sink,
+ *
+ * @returns VBox status code.
+ * @param pSink Recording sink to shut down.
+ */
+static void avRecSinkShutdown(PAVRECSINK pSink)
+{
+ AssertPtrReturnVoid(pSink);
+
+ pSink->pRecStream = NULL;
+
+ switch (pSink->Con.Parms.enmType)
+ {
+ case AVRECCONTAINERTYPE_WEBM:
+ {
+ if (pSink->Con.WebM.pWebM)
+ {
+ LogRel2(("Recording: Finished recording audio to file '%s' (%zu bytes)\n",
+ pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize()));
+
+ int vrc2 = pSink->Con.WebM.pWebM->Close();
+ AssertRC(vrc2);
+
+ delete pSink->Con.WebM.pWebM;
+ pSink->Con.WebM.pWebM = NULL;
+ }
+ break;
+ }
+
+ case AVRECCONTAINERTYPE_MAIN_CONSOLE:
+ RT_FALL_THROUGH();
+ default:
+ break;
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnPowerOff}
+ */
+/*static*/ DECLCALLBACK(void) AudioVideoRec::drvPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
+ LogFlowFuncEnter();
+ avRecSinkShutdown(&pThis->Sink);
+}
+
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnDestruct}
+ */
+/*static*/ DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
+
+ LogFlowFuncEnter();
+
+ switch (pThis->ContainerParms.enmType)
+ {
+ case AVRECCONTAINERTYPE_WEBM:
+ {
+ avRecSinkShutdown(&pThis->Sink);
+ RTStrFree(pThis->ContainerParms.WebM.pszFile);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ /*
+ * If the AudioVideoRec object is still alive, we must clear it's reference to
+ * us since we'll be invalid when we return from this method.
+ */
+ if (pThis->pAudioVideoRec)
+ {
+ pThis->pAudioVideoRec->mpDrv = NULL;
+ pThis->pAudioVideoRec = NULL;
+ }
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * Initializes a recording sink.
+ *
+ * @returns VBox status code.
+ * @param pThis Driver instance.
+ * @param pSink Sink to initialize.
+ * @param pConParms Container parameters to set.
+ * @param pStream Recording stream to asssign sink to.
+ */
+static int avRecSinkInit(PDRVAUDIORECORDING pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, RecordingStream *pStream)
+{
+ pSink->pRecStream = pStream;
+
+ int vrc = VINF_SUCCESS;
+
+ /*
+ * Container setup.
+ */
+ try
+ {
+ switch (pConParms->enmType)
+ {
+ case AVRECCONTAINERTYPE_MAIN_CONSOLE:
+ {
+ if (pThis->pConsole)
+ {
+ pSink->Con.Main.pConsole = pThis->pConsole;
+ }
+ else
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ case AVRECCONTAINERTYPE_WEBM:
+ {
+ #if 0
+ /* If we only record audio, create our own WebM writer instance here. */
+ if (!pSink->Con.WebM.pWebM) /* Do we already have our WebM writer instance? */
+ {
+ /** @todo Add sink name / number to file name. */
+ const char *pszFile = pSink->Con.Parms.WebM.pszFile;
+ AssertPtr(pszFile);
+
+ pSink->Con.WebM.pWebM = new WebMWriter();
+ vrc = pSink->Con.WebM.pWebM->Open(pszFile,
+ /** @todo Add option to add some suffix if file exists instead of overwriting? */
+ RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
+ pSink->pCodec->Parms.enmAudioCodec, RecordingVideoCodec_None);
+ if (RT_SUCCESS(vrc))
+ {
+ const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+
+ vrc = pSink->Con.WebM.pWebM->AddAudioTrack(pSink->pCodec,
+ PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps),
+ PDMAudioPropsSampleBits(pPCMProps), &pSink->Con.WebM.uTrack);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Recording: Recording audio to audio file '%s'\n", pszFile));
+ }
+ else
+ LogRel(("Recording: Error creating audio track for audio file '%s' (%Rrc)\n", pszFile, vrc));
+ }
+ else
+ LogRel(("Recording: Error creating audio file '%s' (%Rrc)\n", pszFile, vrc));
+ }
+ break;
+ #endif
+ }
+
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pSink->Con.Parms.enmType = pConParms->enmType;
+ pSink->tsStartMs = RTTimeMilliTS();
+
+ return VINF_SUCCESS;
+ }
+
+ LogRel(("Recording: Error creating sink (%Rrc)\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * Construct a audio video recording driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+/*static*/ DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
+ RT_NOREF(fFlags);
+
+ LogRel(("Audio: Initializing video recording audio driver\n"));
+ LogFlowFunc(("fFlags=0x%x\n", fFlags));
+
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
+ /* IHostAudio */
+ pThis->IHostAudio.pfnGetConfig = drvAudioVideoRecHA_GetConfig;
+ pThis->IHostAudio.pfnGetDevices = NULL;
+ pThis->IHostAudio.pfnSetDevice = NULL;
+ pThis->IHostAudio.pfnGetStatus = drvAudioVideoRecHA_GetStatus;
+ pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
+ pThis->IHostAudio.pfnStreamConfigHint = NULL;
+ pThis->IHostAudio.pfnStreamCreate = drvAudioVideoRecHA_StreamCreate;
+ pThis->IHostAudio.pfnStreamInitAsync = NULL;
+ pThis->IHostAudio.pfnStreamDestroy = drvAudioVideoRecHA_StreamDestroy;
+ pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
+ pThis->IHostAudio.pfnStreamEnable = drvAudioVideoRecHA_StreamEnable;
+ pThis->IHostAudio.pfnStreamDisable = drvAudioVideoRecHA_StreamDisable;
+ pThis->IHostAudio.pfnStreamPause = drvAudioVideoRecHA_StreamPause;
+ pThis->IHostAudio.pfnStreamResume = drvAudioVideoRecHA_StreamResume;
+ pThis->IHostAudio.pfnStreamDrain = drvAudioVideoRecHA_StreamDrain;
+ pThis->IHostAudio.pfnStreamGetState = drvAudioVideoRecHA_StreamGetState;
+ pThis->IHostAudio.pfnStreamGetPending = NULL;
+ pThis->IHostAudio.pfnStreamGetWritable = drvAudioVideoRecHA_StreamGetWritable;
+ pThis->IHostAudio.pfnStreamPlay = drvAudioVideoRecHA_StreamPlay;
+ pThis->IHostAudio.pfnStreamGetReadable = drvAudioVideoRecHA_StreamGetReadable;
+ pThis->IHostAudio.pfnStreamCapture = drvAudioVideoRecHA_StreamCapture;
+
+ /*
+ * Read configuration.
+ */
+ PCPDMDRVHLPR3 const pHlp = pDrvIns->pHlpR3;
+ /** @todo validate it. */
+
+ /*
+ * Get the Console object pointer.
+ */
+ com::Guid ConsoleUuid(COM_IIDOF(IConsole));
+ IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw());
+ AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3);
+ Console *pConsole = static_cast<Console *>(pIConsole);
+ AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3);
+
+ pThis->pConsole = pConsole;
+ AssertReturn(!pThis->pConsole.isNull(), VERR_INVALID_POINTER);
+ pThis->pAudioVideoRec = pConsole->i_recordingGetAudioDrv();
+ AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER);
+
+ pThis->pAudioVideoRec->mpDrv = pThis;
+
+ /*
+ * Get the recording container parameters from the audio driver instance.
+ */
+ RT_ZERO(pThis->ContainerParms);
+ PAVRECCONTAINERPARMS pConParams = &pThis->ContainerParms;
+
+ int vrc = pHlp->pfnCFGMQueryU32(pCfg, "StreamIndex", (uint32_t *)&pConParams->idxStream);
+ AssertRCReturn(vrc, vrc);
+
+ vrc = pHlp->pfnCFGMQueryU32(pCfg, "ContainerType", (uint32_t *)&pConParams->enmType);
+ AssertRCReturn(vrc, vrc);
+
+ switch (pConParams->enmType)
+ {
+ case AVRECCONTAINERTYPE_WEBM:
+ vrc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "ContainerFileName", &pConParams->WebM.pszFile);
+ AssertRCReturn(vrc, vrc);
+ break;
+
+ default:
+ break;
+ }
+
+ /*
+ * Obtain the recording context.
+ */
+ pThis->pRecCtx = pConsole->i_recordingGetContext();
+ AssertPtrReturn(pThis->pRecCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Get the codec configuration.
+ */
+ RecordingStream *pStream = pThis->pRecCtx->GetStream(pConParams->idxStream);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ /*
+ * Init the recording sink.
+ */
+ vrc = avRecSinkInit(pThis, &pThis->Sink, &pThis->ContainerParms, pStream);
+ if (RT_SUCCESS(vrc))
+ LogRel2(("Recording: Audio recording driver initialized\n"));
+ else
+ LogRel(("Recording: Audio recording driver initialization failed: %Rrc\n", vrc));
+
+ return vrc;
+}
+
+
+/**
+ * Video recording audio driver registration record.
+ */
+const PDMDRVREG AudioVideoRec::DrvReg =
+{
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "AudioVideoRec",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Audio driver for video recording",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVAUDIORECORDING),
+ /* pfnConstruct */
+ AudioVideoRec::drvConstruct,
+ /* pfnDestruct */
+ AudioVideoRec::drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ AudioVideoRec::drvPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
diff --git a/src/VBox/Main/src-client/DrvAudioVRDE.cpp b/src/VBox/Main/src-client/DrvAudioVRDE.cpp
new file mode 100644
index 00000000..62615bff
--- /dev/null
+++ b/src/VBox/Main/src-client/DrvAudioVRDE.cpp
@@ -0,0 +1,823 @@
+/* $Id: DrvAudioVRDE.cpp $ */
+/** @file
+ * VRDE audio backend for Main.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include "LoggingNew.h"
+
+#include <VBox/log.h>
+#include "DrvAudioVRDE.h"
+#include "ConsoleImpl.h"
+#include "ConsoleVRDPServer.h"
+
+#include <iprt/mem.h>
+#include <iprt/cdefs.h>
+#include <iprt/circbuf.h>
+
+#include <VBox/vmm/cfgm.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/RemoteDesktop/VRDE.h>
+#include <VBox/err.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * VRDE stream.
+ */
+typedef struct VRDESTREAM
+{
+ /** Common part. */
+ PDMAUDIOBACKENDSTREAM Core;
+ /** The stream's acquired configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+ union
+ {
+ struct
+ {
+ /** Circular buffer for holding the recorded audio frames from the host. */
+ PRTCIRCBUF pCircBuf;
+ } In;
+ };
+} VRDESTREAM;
+/** Pointer to a VRDE stream. */
+typedef VRDESTREAM *PVRDESTREAM;
+
+/**
+ * VRDE (host) audio driver instance data.
+ */
+typedef struct DRVAUDIOVRDE
+{
+ /** Pointer to audio VRDE object. */
+ AudioVRDE *pAudioVRDE;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the VRDP's console object. */
+ ConsoleVRDPServer *pConsoleVRDPServer;
+ /** Number of connected clients to this VRDE instance. */
+ uint32_t cClients;
+ /** Interface to the driver above us (DrvAudio). */
+ PDMIHOSTAUDIOPORT *pIHostAudioPort;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+} DRVAUDIOVRDE;
+/** Pointer to the instance data for an VRDE audio driver. */
+typedef DRVAUDIOVRDE *PDRVAUDIOVRDE;
+
+
+/*********************************************************************************************************************************
+* Class AudioVRDE *
+*********************************************************************************************************************************/
+
+AudioVRDE::AudioVRDE(Console *pConsole)
+ : AudioDriver(pConsole)
+ , mpDrv(NULL)
+{
+ RTCritSectInit(&mCritSect);
+}
+
+
+AudioVRDE::~AudioVRDE(void)
+{
+ RTCritSectEnter(&mCritSect);
+ if (mpDrv)
+ {
+ mpDrv->pAudioVRDE = NULL;
+ mpDrv = NULL;
+ }
+ RTCritSectLeave(&mCritSect);
+ RTCritSectDelete(&mCritSect);
+}
+
+
+int AudioVRDE::configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM)
+{
+ return AudioDriver::configureDriver(pLunCfg, pVMM);
+}
+
+
+void AudioVRDE::onVRDEClientConnect(uint32_t uClientID)
+{
+ RT_NOREF(uClientID);
+
+ RTCritSectEnter(&mCritSect);
+ if (mpDrv)
+ {
+ mpDrv->cClients++;
+ LogRel2(("Audio: VRDE client connected (#%u)\n", mpDrv->cClients));
+
+#if 0 /* later, maybe */
+ /*
+ * The first client triggers a device change event in both directions
+ * so that can start talking to the audio device.
+ *
+ * Note! Should be okay to stay in the critical section here, as it's only
+ * used at construction and destruction time.
+ */
+ if (mpDrv->cClients == 1)
+ {
+ VMSTATE enmState = PDMDrvHlpVMState(mpDrv->pDrvIns);
+ if (enmState <= VMSTATE_POWERING_OFF)
+ {
+ PDMIHOSTAUDIOPORT *pIHostAudioPort = mpDrv->pIHostAudioPort;
+ AssertPtr(pIHostAudioPort);
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/);
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/);
+ }
+ }
+#endif
+ }
+ RTCritSectLeave(&mCritSect);
+}
+
+
+void AudioVRDE::onVRDEClientDisconnect(uint32_t uClientID)
+{
+ RT_NOREF(uClientID);
+
+ RTCritSectEnter(&mCritSect);
+ if (mpDrv)
+ {
+ Assert(mpDrv->cClients > 0);
+ mpDrv->cClients--;
+ LogRel2(("Audio: VRDE client disconnected (%u left)\n", mpDrv->cClients));
+#if 0 /* later maybe */
+ /*
+ * The last client leaving triggers a device change event in both
+ * directions so the audio devices can stop wasting time trying to
+ * talk to us. (There is an additional safeguard in
+ * drvAudioVrdeHA_StreamGetStatus.)
+ */
+ if (mpDrv->cClients == 0)
+ {
+ VMSTATE enmState = PDMDrvHlpVMState(mpDrv->pDrvIns);
+ if (enmState <= VMSTATE_POWERING_OFF)
+ {
+ PDMIHOSTAUDIOPORT *pIHostAudioPort = mpDrv->pIHostAudioPort;
+ AssertPtr(pIHostAudioPort);
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/);
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/);
+ }
+ }
+#endif
+ }
+ RTCritSectLeave(&mCritSect);
+}
+
+
+int AudioVRDE::onVRDEControl(bool fEnable, uint32_t uFlags)
+{
+ RT_NOREF(fEnable, uFlags);
+ LogFlowThisFunc(("fEnable=%RTbool, uFlags=0x%x\n", fEnable, uFlags));
+
+ if (mpDrv == NULL)
+ return VERR_INVALID_STATE;
+
+ return VINF_SUCCESS; /* Never veto. */
+}
+
+
+/**
+ * Marks the beginning of sending captured audio data from a connected
+ * RDP client.
+ *
+ * @returns VBox status code.
+ * @param pvContext The context; in this case a pointer to a
+ * VRDESTREAMIN structure.
+ * @param pVRDEAudioBegin Pointer to a VRDEAUDIOINBEGIN structure.
+ */
+int AudioVRDE::onVRDEInputBegin(void *pvContext, PVRDEAUDIOINBEGIN pVRDEAudioBegin)
+{
+ AssertPtrReturn(pvContext, VERR_INVALID_POINTER);
+ AssertPtrReturn(pVRDEAudioBegin, VERR_INVALID_POINTER);
+ PVRDESTREAM pVRDEStrmIn = (PVRDESTREAM)pvContext;
+ AssertPtrReturn(pVRDEStrmIn, VERR_INVALID_POINTER);
+
+#ifdef LOG_ENABLED
+ VRDEAUDIOFORMAT const audioFmt = pVRDEAudioBegin->fmt;
+ LogFlowFunc(("cbSample=%RU32, iSampleHz=%d, cChannels=%d, cBits=%d, fUnsigned=%RTbool\n",
+ VRDE_AUDIO_FMT_BYTES_PER_SAMPLE(audioFmt), VRDE_AUDIO_FMT_SAMPLE_FREQ(audioFmt),
+ VRDE_AUDIO_FMT_CHANNELS(audioFmt), VRDE_AUDIO_FMT_BITS_PER_SAMPLE(audioFmt), VRDE_AUDIO_FMT_SIGNED(audioFmt)));
+#endif
+
+ return VINF_SUCCESS;
+}
+
+
+int AudioVRDE::onVRDEInputData(void *pvContext, const void *pvData, uint32_t cbData)
+{
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pvContext;
+ AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
+ LogFlowFunc(("cbData=%#x\n", cbData));
+
+ void *pvBuf = NULL;
+ size_t cbBuf = 0;
+ RTCircBufAcquireWriteBlock(pStreamVRDE->In.pCircBuf, cbData, &pvBuf, &cbBuf);
+
+ if (cbBuf)
+ memcpy(pvBuf, pvData, cbBuf);
+
+ RTCircBufReleaseWriteBlock(pStreamVRDE->In.pCircBuf, cbBuf);
+
+ if (cbBuf < cbData)
+ LogRelMax(999, ("VRDE: Capturing audio data lost %zu bytes\n", cbData - cbBuf)); /** @todo Use an error counter. */
+
+ return VINF_SUCCESS; /** @todo r=andy How to tell the caller if we were not able to handle *all* input data? */
+}
+
+
+int AudioVRDE::onVRDEInputEnd(void *pvContext)
+{
+ RT_NOREF(pvContext);
+ return VINF_SUCCESS;
+}
+
+
+int AudioVRDE::onVRDEInputIntercept(bool fEnabled)
+{
+ RT_NOREF(fEnabled);
+ return VINF_SUCCESS; /* Never veto. */
+}
+
+
+
+/*********************************************************************************************************************************
+* PDMIHOSTAUDIO *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VRDE");
+ pBackendCfg->cbStream = sizeof(VRDESTREAM);
+ pBackendCfg->fFlags = 0;
+ pBackendCfg->cMaxStreamsIn = UINT32_MAX;
+ pBackendCfg->cMaxStreamsOut = UINT32_MAX;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVrdeHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(pInterface, enmDir);
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ PDRVAUDIOVRDE pThis = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+ AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ /*
+ * Only create a stream if we have clients.
+ */
+ int vrc;
+ NOREF(pThis);
+#if 0 /* later maybe */
+ if (pThis->cClients == 0)
+ {
+ LogFunc(("No clients, failing with VERR_AUDIO_STREAM_COULD_NOT_CREATE.\n"));
+ vrc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+ else
+#endif
+ {
+ /*
+ * The VRDP server does its own mixing and resampling because it may be
+ * sending the audio to any number of different clients all with different
+ * formats (including clients which hasn't yet connected). So, it desires
+ * the raw data from the mixer (somewhat akind to stereo signed 64-bit,
+ * see st_sample_t and PDMAUDIOFRAME).
+ */
+ PDMAudioPropsInitEx(&pCfgAcq->Props, 8 /*64-bit*/, true /*fSigned*/, 2 /*stereo*/,
+ 22050 /*Hz - VRDP_AUDIO_CHUNK_INTERNAL_FREQ_HZ*/,
+ true /*fLittleEndian*/, true /*fRaw*/);
+
+ /* According to the VRDP docs (VRDP_AUDIO_CHUNK_TIME_MS), the VRDP server
+ stores audio in 200ms chunks. */
+ const uint32_t cFramesVrdpServer = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 200 /*ms*/);
+
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ {
+ pCfgAcq->Backend.cFramesBufferSize = cFramesVrdpServer;
+ pCfgAcq->Backend.cFramesPeriod = cFramesVrdpServer / 4; /* This is utter non-sense, but whatever. */
+ pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * cFramesVrdpServer
+ / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
+
+ vrc = RTCircBufCreate(&pStreamVRDE->In.pCircBuf, PDMAudioPropsFramesToBytes(&pCfgAcq->Props, cFramesVrdpServer));
+ }
+ else
+ {
+ /** @todo r=bird: So, if VRDP does 200ms chunks, why do we report 100ms
+ * buffer and 20ms period? How does these parameters at all correlate
+ * with the above comment?!? */
+ pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 20 /*ms*/);
+ pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 100 /*ms*/);
+ pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod * 2;
+ vrc = VINF_SUCCESS;
+ }
+
+ PDMAudioStrmCfgCopy(&pStreamVRDE->Cfg, pCfgAcq);
+ }
+ return vrc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ bool fImmediate)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+ AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
+ RT_NOREF(fImmediate);
+
+ if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ LogFlowFunc(("Calling SendAudioInputEnd\n"));
+ if (pDrv->pConsoleVRDPServer)
+ pDrv->pConsoleVRDPServer->SendAudioInputEnd(NULL);
+
+ if (pStreamVRDE->In.pCircBuf)
+ {
+ RTCircBufDestroy(pStreamVRDE->In.pCircBuf);
+ pStreamVRDE->In.pCircBuf = NULL;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+
+ int vrc;
+ if (!pDrv->pConsoleVRDPServer)
+ {
+ LogRelMax(32, ("Audio: VRDP console not ready (enable)\n"));
+ vrc = VERR_AUDIO_STREAM_NOT_READY;
+ }
+ else if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ vrc = pDrv->pConsoleVRDPServer->SendAudioInputBegin(NULL, pStreamVRDE,
+ PDMAudioPropsMilliToFrames(&pStreamVRDE->Cfg.Props, 200 /*ms*/),
+ PDMAudioPropsHz(&pStreamVRDE->Cfg.Props),
+ PDMAudioPropsChannels(&pStreamVRDE->Cfg.Props),
+ PDMAudioPropsSampleBits(&pStreamVRDE->Cfg.Props));
+ LogFlowFunc(("SendAudioInputBegin returns %Rrc\n", vrc));
+ if (vrc == VERR_NOT_SUPPORTED)
+ {
+ LogRelMax(64, ("Audio: No VRDE client connected, so no input recording available\n"));
+ vrc = VERR_AUDIO_STREAM_NOT_READY;
+ }
+ }
+ else
+ vrc = VINF_SUCCESS;
+ LogFlowFunc(("returns %Rrc\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+
+ int vrc;
+ if (!pDrv->pConsoleVRDPServer)
+ {
+ LogRelMax(32, ("Audio: VRDP console not ready (disable)\n"));
+ vrc = VERR_AUDIO_STREAM_NOT_READY;
+ }
+ else if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ LogFlowFunc(("Calling SendAudioInputEnd\n"));
+ pDrv->pConsoleVRDPServer->SendAudioInputEnd(NULL /* pvUserCtx */);
+ vrc = VINF_SUCCESS;
+ }
+ else
+ vrc = VINF_SUCCESS;
+ LogFlowFunc(("returns %Rrc\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ RT_NOREF(pStream);
+
+ if (!pDrv->pConsoleVRDPServer)
+ {
+ LogRelMax(32, ("Audio: VRDP console not ready (pause)\n"));
+ return VERR_AUDIO_STREAM_NOT_READY;
+ }
+ LogFlowFunc(("returns VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ RT_NOREF(pStream);
+
+ if (!pDrv->pConsoleVRDPServer)
+ {
+ LogRelMax(32, ("Audio: VRDP console not ready (resume)\n"));
+ return VERR_AUDIO_STREAM_NOT_READY;
+ }
+ LogFlowFunc(("returns VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ LogFlowFunc(("returns VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVrdeHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+
+ return pDrv->cClients > 0 ? PDMHOSTAUDIOSTREAMSTATE_OKAY : PDMHOSTAUDIOSTREAMSTATE_INACTIVE;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioVrdeHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+
+ /** @todo Find some sane value here. We probably need a VRDE API VRDE to specify this. */
+ if (pDrv->cClients)
+ return PDMAudioPropsFramesToBytes(&pStreamVRDE->Cfg.Props, pStreamVRDE->Cfg.Backend.cFramesBufferSize);
+ return 0;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ AssertPtr(pDrv);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+ if (cbBuf)
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
+
+ if (!pDrv->pConsoleVRDPServer)
+ return VERR_NOT_AVAILABLE;
+
+ /* Prepate the format. */
+ PPDMAUDIOPCMPROPS pProps = &pStreamVRDE->Cfg.Props;
+ VRDEAUDIOFORMAT const uVrdpFormat = VRDE_AUDIO_FMT_MAKE(PDMAudioPropsHz(pProps),
+ PDMAudioPropsChannels(pProps),
+ PDMAudioPropsSampleBits(pProps),
+ pProps->fSigned);
+ Assert(uVrdpFormat == VRDE_AUDIO_FMT_MAKE(PDMAudioPropsHz(pProps), 2, 64, true));
+
+ /** @todo r=bird: there was some incoherent mumbling about "using the
+ * internal counter to track if we (still) can write to the VRDP
+ * server or if need to wait another round (time slot)". However it
+ * wasn't accessing any internal counter nor doing anything else
+ * sensible, so I've removed it. */
+
+ uint32_t cFrames = PDMAudioPropsBytesToFrames(&pStream->pStream->Cfg.Props, cbBuf);
+ Assert(cFrames == cbBuf / (sizeof(uint64_t) * 2));
+ pDrv->pConsoleVRDPServer->SendAudioSamples(pvBuf, cFrames, uVrdpFormat);
+
+ Log3Func(("cFramesWritten=%RU32\n", cFrames));
+ *pcbWritten = PDMAudioPropsFramesToBytes(&pStream->pStream->Cfg.Props, cFrames);
+ Assert(*pcbWritten == cbBuf);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioVrdeHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+
+ AssertReturn(pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN, 0);
+ uint32_t cbRet = (uint32_t)RTCircBufUsed(pStreamVRDE->In.pCircBuf);
+ Log4Func(("returns %#x\n", cbRet));
+ return cbRet;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ RT_NOREF(pInterface);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+ AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcbRead, VERR_INVALID_PARAMETER);
+
+ *pcbRead = 0;
+ while (cbBuf > 0 && RTCircBufUsed(pStreamVRDE->In.pCircBuf) > 0)
+ {
+ size_t cbData = 0;
+ void *pvData = NULL;
+ RTCircBufAcquireReadBlock(pStreamVRDE->In.pCircBuf, cbBuf, &pvData, &cbData);
+
+ memcpy(pvBuf, pvData, cbData);
+
+ RTCircBufReleaseReadBlock(pStreamVRDE->In.pCircBuf, cbData);
+
+ *pcbRead += (uint32_t)cbData;
+ cbBuf -= (uint32_t)cbData;
+ pvData = (uint8_t *)pvData + cbData;
+ }
+
+ LogFlowFunc(("returns %#x bytes\n", *pcbRead));
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* PDMIBASE *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvAudioVrdeQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDRVREG *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnPowerOff}
+ */
+/*static*/ DECLCALLBACK(void) AudioVRDE::drvPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
+ LogFlowFuncEnter();
+
+ if (pThis->pConsoleVRDPServer)
+ pThis->pConsoleVRDPServer->SendAudioInputEnd(NULL);
+}
+
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnDestruct}
+ */
+/*static*/ DECLCALLBACK(void) AudioVRDE::drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
+ LogFlowFuncEnter();
+
+ /** @todo For runtime detach maybe:
+ if (pThis->pConsoleVRDPServer)
+ pThis->pConsoleVRDPServer->SendAudioInputEnd(NULL); */
+
+ /*
+ * If the AudioVRDE object is still alive, we must clear it's reference to
+ * us since we'll be invalid when we return from this method.
+ */
+ AudioVRDE *pAudioVRDE = pThis->pAudioVRDE;
+ if (pAudioVRDE)
+ {
+ RTCritSectEnter(&pAudioVRDE->mCritSect);
+ pAudioVRDE->mpDrv = NULL;
+ pThis->pAudioVRDE = NULL;
+ RTCritSectLeave(&pAudioVRDE->mCritSect);
+ }
+}
+
+
+/**
+ * Construct a VRDE audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+/* static */
+DECLCALLBACK(int) AudioVRDE::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
+ RT_NOREF(fFlags);
+
+ AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ LogRel(("Audio: Initializing VRDE driver\n"));
+ LogFlowFunc(("fFlags=0x%x\n", fFlags));
+
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->cClients = 0;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvAudioVrdeQueryInterface;
+ /* IHostAudio */
+ pThis->IHostAudio.pfnGetConfig = drvAudioVrdeHA_GetConfig;
+ pThis->IHostAudio.pfnGetDevices = NULL;
+ pThis->IHostAudio.pfnSetDevice = NULL;
+ pThis->IHostAudio.pfnGetStatus = drvAudioVrdeHA_GetStatus;
+ pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
+ pThis->IHostAudio.pfnStreamConfigHint = NULL;
+ pThis->IHostAudio.pfnStreamCreate = drvAudioVrdeHA_StreamCreate;
+ pThis->IHostAudio.pfnStreamInitAsync = NULL;
+ pThis->IHostAudio.pfnStreamDestroy = drvAudioVrdeHA_StreamDestroy;
+ pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
+ pThis->IHostAudio.pfnStreamEnable = drvAudioVrdeHA_StreamEnable;
+ pThis->IHostAudio.pfnStreamDisable = drvAudioVrdeHA_StreamDisable;
+ pThis->IHostAudio.pfnStreamPause = drvAudioVrdeHA_StreamPause;
+ pThis->IHostAudio.pfnStreamResume = drvAudioVrdeHA_StreamResume;
+ pThis->IHostAudio.pfnStreamDrain = drvAudioVrdeHA_StreamDrain;
+ pThis->IHostAudio.pfnStreamGetState = drvAudioVrdeHA_StreamGetState;
+ pThis->IHostAudio.pfnStreamGetPending = NULL;
+ pThis->IHostAudio.pfnStreamGetWritable = drvAudioVrdeHA_StreamGetWritable;
+ pThis->IHostAudio.pfnStreamPlay = drvAudioVrdeHA_StreamPlay;
+ pThis->IHostAudio.pfnStreamGetReadable = drvAudioVrdeHA_StreamGetReadable;
+ pThis->IHostAudio.pfnStreamCapture = drvAudioVrdeHA_StreamCapture;
+
+ /*
+ * Resolve the interface to the driver above us.
+ */
+ pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
+ AssertPtrReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
+
+ /* Get the Console object pointer. */
+ com::Guid ConsoleUuid(COM_IIDOF(IConsole));
+ IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw());
+ AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3);
+ Console *pConsole = static_cast<Console *>(pIConsole);
+ AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3);
+
+ /* Get the console VRDP object pointer. */
+ pThis->pConsoleVRDPServer = pConsole->i_consoleVRDPServer();
+ AssertLogRelMsgReturn(RT_VALID_PTR(pThis->pConsoleVRDPServer) || !pThis->pConsoleVRDPServer,
+ ("pConsoleVRDPServer=%p\n", pThis->pConsoleVRDPServer), VERR_INVALID_POINTER);
+
+ /* Get the AudioVRDE object pointer. */
+ pThis->pAudioVRDE = pConsole->i_getAudioVRDE();
+ AssertLogRelMsgReturn(RT_VALID_PTR(pThis->pAudioVRDE), ("pAudioVRDE=%p\n", pThis->pAudioVRDE), VERR_INVALID_POINTER);
+ RTCritSectEnter(&pThis->pAudioVRDE->mCritSect);
+ pThis->pAudioVRDE->mpDrv = pThis;
+ RTCritSectLeave(&pThis->pAudioVRDE->mCritSect);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * VRDE audio driver registration record.
+ */
+const PDMDRVREG AudioVRDE::DrvReg =
+{
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "AudioVRDE",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Audio driver for VRDE backend",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVAUDIOVRDE),
+ /* pfnConstruct */
+ AudioVRDE::drvConstruct,
+ /* pfnDestruct */
+ AudioVRDE::drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ AudioVRDE::drvPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Main/src-client/EBMLWriter.cpp b/src/VBox/Main/src-client/EBMLWriter.cpp
new file mode 100644
index 00000000..270b35f6
--- /dev/null
+++ b/src/VBox/Main/src-client/EBMLWriter.cpp
@@ -0,0 +1,275 @@
+/* $Id: EBMLWriter.cpp $ */
+/** @file
+ * EBMLWriter.cpp - EBML writer implementation.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**
+ * For more information, see:
+ * - https://w3c.github.io/media-source/webm-byte-stream-format.html
+ * - https://www.webmproject.org/docs/container/#muxer-guidelines
+ */
+
+#ifdef LOG_GROUP
+# undef LOG_GROUP
+#endif
+#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY
+#include "LoggingNew.h"
+
+#include <list>
+#include <map>
+#include <queue>
+#include <stack>
+
+#include <math.h> /* For lround.h. */
+
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/cdefs.h>
+#include <iprt/critsect.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+
+#include <VBox/log.h>
+#include <VBox/version.h>
+
+#include "EBMLWriter.h"
+#include "EBML_MKV.h"
+
+/** No flags set. */
+#define VBOX_EBMLWRITER_FLAG_NONE 0
+/** The file handle was inherited. */
+#define VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED RT_BIT(0)
+
+/** Creates an EBML output file using an existing, open file handle. */
+int EBMLWriter::createEx(const char *a_pszFile, PRTFILE phFile)
+{
+ AssertPtrReturn(phFile, VERR_INVALID_POINTER);
+
+ m_hFile = *phFile;
+ m_fFlags |= VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED;
+ m_strFile = a_pszFile;
+
+ return VINF_SUCCESS;
+}
+
+/** Creates an EBML output file using a file name. */
+int EBMLWriter::create(const char *a_pszFile, uint64_t fOpen)
+{
+ int vrc = RTFileOpen(&m_hFile, a_pszFile, fOpen);
+ if (RT_SUCCESS(vrc))
+ m_strFile = a_pszFile;
+
+ return vrc;
+}
+
+/** Returns available space on storage. */
+uint64_t EBMLWriter::getAvailableSpace(void)
+{
+ RTFOFF pcbFree;
+ int vrc = RTFileQueryFsSizes(m_hFile, NULL, &pcbFree, 0, 0);
+ return (RT_SUCCESS(vrc)? (uint64_t)pcbFree : UINT64_MAX);
+}
+
+/** Closes the file. */
+void EBMLWriter::close(void)
+{
+ if (!isOpen())
+ return;
+
+ AssertMsg(m_Elements.size() == 0,
+ ("%zu elements are not closed yet (next element to close is 0x%x)\n",
+ m_Elements.size(), m_Elements.top().classId));
+
+ if (!(m_fFlags & VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED))
+ {
+ RTFileClose(m_hFile);
+ m_hFile = NIL_RTFILE;
+ }
+
+ m_fFlags = VBOX_EBMLWRITER_FLAG_NONE;
+ m_strFile = "";
+}
+
+/** Starts an EBML sub-element. */
+EBMLWriter& EBMLWriter::subStart(EbmlClassId classId)
+{
+ writeClassId(classId);
+ /* store the current file offset. */
+ m_Elements.push(EbmlSubElement(RTFileTell(m_hFile), classId));
+ /* Indicates that size of the element
+ * is unkown (as according to EBML specs).
+ */
+ writeUnsignedInteger(UINT64_C(0x01FFFFFFFFFFFFFF));
+ return *this;
+}
+
+/** Ends an EBML sub-element. */
+EBMLWriter& EBMLWriter::subEnd(EbmlClassId classId)
+{
+#ifdef VBOX_STRICT
+ /* Class ID on the top of the stack should match the class ID passed
+ * to the function. Otherwise it may mean that we have a bug in the code.
+ */
+ AssertMsg(!m_Elements.empty(), ("No elements to close anymore\n"));
+ AssertMsg(m_Elements.top().classId == classId,
+ ("Ending sub element 0x%x is in wrong order (next to close is 0x%x)\n", classId, m_Elements.top().classId));
+#else
+ RT_NOREF(classId);
+#endif
+
+ uint64_t uPos = RTFileTell(m_hFile);
+ uint64_t uSize = uPos - m_Elements.top().offset - 8;
+ RTFileSeek(m_hFile, m_Elements.top().offset, RTFILE_SEEK_BEGIN, NULL);
+
+ /* Make sure that size will be serialized as uint64_t. */
+ writeUnsignedInteger(uSize | UINT64_C(0x0100000000000000));
+ RTFileSeek(m_hFile, uPos, RTFILE_SEEK_BEGIN, NULL);
+ m_Elements.pop();
+ return *this;
+}
+
+/** Serializes a null-terminated string. */
+EBMLWriter& EBMLWriter::serializeString(EbmlClassId classId, const char *str)
+{
+ writeClassId(classId);
+ uint64_t size = strlen(str);
+ writeSize(size);
+ write(str, size);
+ return *this;
+}
+
+/** Serializes an UNSIGNED integer.
+ * If size is zero then it will be detected automatically. */
+EBMLWriter& EBMLWriter::serializeUnsignedInteger(EbmlClassId classId, uint64_t parm, size_t size /* = 0 */)
+{
+ writeClassId(classId);
+ if (!size) size = getSizeOfUInt(parm);
+ writeSize(size);
+ writeUnsignedInteger(parm, size);
+ return *this;
+}
+
+/** Serializes a floating point value.
+ *
+ * Only 8-bytes double precision values are supported
+ * by this function.
+ */
+EBMLWriter& EBMLWriter::serializeFloat(EbmlClassId classId, float value)
+{
+ writeClassId(classId);
+ Assert(sizeof(uint32_t) == sizeof(float));
+ writeSize(sizeof(float));
+
+ union
+ {
+ float f;
+ uint8_t u8[4];
+ } u;
+
+ u.f = value;
+
+ for (int i = 3; i >= 0; i--) /* Converts values to big endian. */
+ write(&u.u8[i], 1);
+
+ return *this;
+}
+
+/** Serializes binary data. */
+EBMLWriter& EBMLWriter::serializeData(EbmlClassId classId, const void *pvData, size_t cbData)
+{
+ writeClassId(classId);
+ writeSize(cbData);
+ write(pvData, cbData);
+ return *this;
+}
+
+/** Writes raw data to file. */
+int EBMLWriter::write(const void *data, size_t size)
+{
+ return RTFileWrite(m_hFile, data, size, NULL);
+}
+
+/** Writes an unsigned integer of variable of fixed size. */
+void EBMLWriter::writeUnsignedInteger(uint64_t value, size_t size /* = sizeof(uint64_t) */)
+{
+ /* convert to big-endian */
+ value = RT_H2BE_U64(value);
+ write(reinterpret_cast<uint8_t*>(&value) + sizeof(value) - size, size);
+}
+
+/** Writes EBML class ID to file.
+ *
+ * EBML ID already has a UTF8-like represenation
+ * so getSizeOfUInt is used to determine
+ * the number of its bytes.
+ */
+void EBMLWriter::writeClassId(EbmlClassId parm)
+{
+ writeUnsignedInteger(parm, getSizeOfUInt(parm));
+}
+
+/** Writes data size value. */
+void EBMLWriter::writeSize(uint64_t parm)
+{
+ /* The following expression defines the size of the value that will be serialized
+ * as an EBML UTF-8 like integer (with trailing bits represeting its size):
+ 1xxx xxxx - value 0 to 2^7-2
+ 01xx xxxx xxxx xxxx - value 0 to 2^14-2
+ 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2
+ 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2
+ 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2
+ 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2
+ 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2
+ 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2
+ */
+ size_t size = 8 - ! (parm & (UINT64_MAX << 49)) - ! (parm & (UINT64_MAX << 42)) -
+ ! (parm & (UINT64_MAX << 35)) - ! (parm & (UINT64_MAX << 28)) -
+ ! (parm & (UINT64_MAX << 21)) - ! (parm & (UINT64_MAX << 14)) -
+ ! (parm & (UINT64_MAX << 7));
+ /* One is subtracted in order to avoid loosing significant bit when size = 8. */
+ uint64_t mask = RT_BIT_64(size * 8 - 1);
+ writeUnsignedInteger((parm & (((mask << 1) - 1) >> size)) | (mask >> (size - 1)), size);
+}
+
+/** Size calculation for variable size UNSIGNED integer.
+ *
+ * The function defines the size of the number by trimming
+ * consequent trailing zero bytes starting from the most significant.
+ * The following statement is always true:
+ * 1 <= getSizeOfUInt(arg) <= 8.
+ *
+ * Every !(arg & (UINT64_MAX << X)) expression gives one
+ * if an only if all the bits from X to 63 are set to zero.
+ */
+size_t EBMLWriter::getSizeOfUInt(uint64_t arg)
+{
+ return 8 - ! (arg & (UINT64_MAX << 56)) - ! (arg & (UINT64_MAX << 48)) -
+ ! (arg & (UINT64_MAX << 40)) - ! (arg & (UINT64_MAX << 32)) -
+ ! (arg & (UINT64_MAX << 24)) - ! (arg & (UINT64_MAX << 16)) -
+ ! (arg & (UINT64_MAX << 8));
+}
+
diff --git a/src/VBox/Main/src-client/EmulatedUSBImpl.cpp b/src/VBox/Main/src-client/EmulatedUSBImpl.cpp
new file mode 100644
index 00000000..cdfc6eb2
--- /dev/null
+++ b/src/VBox/Main/src-client/EmulatedUSBImpl.cpp
@@ -0,0 +1,678 @@
+/* $Id: EmulatedUSBImpl.cpp $ */
+/** @file
+ * Emulated USB manager implementation.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_EMULATEDUSB
+#include "LoggingNew.h"
+
+#include "EmulatedUSBImpl.h"
+#include "ConsoleImpl.h"
+
+#include <VBox/vmm/pdmusb.h>
+#include <VBox/vmm/vmmr3vtable.h>
+
+
+/*
+ * Emulated USB webcam device instance.
+ */
+typedef std::map <Utf8Str, Utf8Str> EUSBSettingsMap;
+
+typedef enum EUSBDEVICESTATUS
+{
+ EUSBDEVICE_CREATED,
+ EUSBDEVICE_ATTACHING,
+ EUSBDEVICE_ATTACHED
+} EUSBDEVICESTATUS;
+
+class EUSBWEBCAM /* : public EUSBDEVICE */
+{
+private:
+ int32_t volatile mcRefs;
+
+ EmulatedUSB *mpEmulatedUSB;
+
+ RTUUID mUuid;
+ char mszUuid[RTUUID_STR_LENGTH];
+
+ Utf8Str mPath;
+ Utf8Str mSettings;
+
+ EUSBSettingsMap mDevSettings;
+ EUSBSettingsMap mDrvSettings;
+
+ void *mpvObject;
+
+ static DECLCALLBACK(int) emulatedWebcamAttach(PUVM pUVM, PCVMMR3VTABLE pVMM, EUSBWEBCAM *pThis, const char *pszDriver);
+ static DECLCALLBACK(int) emulatedWebcamDetach(PUVM pUVM, PCVMMR3VTABLE pVMM, EUSBWEBCAM *pThis);
+
+ HRESULT settingsParse(void);
+
+ ~EUSBWEBCAM()
+ {
+ }
+
+public:
+ EUSBWEBCAM()
+ :
+ mcRefs(1),
+ mpEmulatedUSB(NULL),
+ mpvObject(NULL),
+ enmStatus(EUSBDEVICE_CREATED)
+ {
+ RT_ZERO(mUuid);
+ RT_ZERO(mszUuid);
+ }
+
+ int32_t AddRef(void)
+ {
+ return ASMAtomicIncS32(&mcRefs);
+ }
+
+ void Release(void)
+ {
+ int32_t c = ASMAtomicDecS32(&mcRefs);
+ if (c == 0)
+ {
+ delete this;
+ }
+ }
+
+ HRESULT Initialize(Console *pConsole,
+ EmulatedUSB *pEmulatedUSB,
+ const com::Utf8Str *aPath,
+ const com::Utf8Str *aSettings,
+ void *pvObject);
+ HRESULT Attach(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDriver);
+ HRESULT Detach(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM);
+
+ bool HasId(const char *pszId) { return RTStrCmp(pszId, mszUuid) == 0;}
+
+ void *getObjectPtr() { return mpvObject; }
+
+ EUSBDEVICESTATUS enmStatus;
+};
+
+
+static int emulatedWebcamInsertSettings(PCFGMNODE pConfig, PCVMMR3VTABLE pVMM, EUSBSettingsMap *pSettings)
+{
+ for (EUSBSettingsMap::const_iterator it = pSettings->begin(); it != pSettings->end(); ++it)
+ {
+ /* Convert some well known settings for backward compatibility. */
+ int vrc;
+ if ( RTStrCmp(it->first.c_str(), "MaxPayloadTransferSize") == 0
+ || RTStrCmp(it->first.c_str(), "MaxFramerate") == 0)
+ {
+ uint32_t u32 = 0;
+ vrc = RTStrToUInt32Full(it->second.c_str(), 10, &u32);
+ if (vrc == VINF_SUCCESS)
+ vrc = pVMM->pfnCFGMR3InsertInteger(pConfig, it->first.c_str(), u32);
+ else if (RT_SUCCESS(vrc)) /* VWRN_* */
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else
+ vrc = pVMM->pfnCFGMR3InsertString(pConfig, it->first.c_str(), it->second.c_str());
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/*static*/ DECLCALLBACK(int)
+EUSBWEBCAM::emulatedWebcamAttach(PUVM pUVM, PCVMMR3VTABLE pVMM, EUSBWEBCAM *pThis, const char *pszDriver)
+{
+ PCFGMNODE pInstance = pVMM->pfnCFGMR3CreateTree(pUVM);
+ PCFGMNODE pConfig;
+ int vrc = pVMM->pfnCFGMR3InsertNode(pInstance, "Config", &pConfig);
+ AssertRCReturn(vrc, vrc);
+ vrc = emulatedWebcamInsertSettings(pConfig, pVMM, &pThis->mDevSettings);
+ AssertRCReturn(vrc, vrc);
+
+ PCFGMNODE pEUSB;
+ vrc = pVMM->pfnCFGMR3InsertNode(pConfig, "EmulatedUSB", &pEUSB);
+ AssertRCReturn(vrc, vrc);
+ vrc = pVMM->pfnCFGMR3InsertString(pEUSB, "Id", pThis->mszUuid);
+ AssertRCReturn(vrc, vrc);
+
+ PCFGMNODE pLunL0;
+ vrc = pVMM->pfnCFGMR3InsertNode(pInstance, "LUN#0", &pLunL0);
+ AssertRCReturn(vrc, vrc);
+ vrc = pVMM->pfnCFGMR3InsertString(pLunL0, "Driver", pszDriver);
+ AssertRCReturn(vrc, vrc);
+ vrc = pVMM->pfnCFGMR3InsertNode(pLunL0, "Config", &pConfig);
+ AssertRCReturn(vrc, vrc);
+ vrc = pVMM->pfnCFGMR3InsertString(pConfig, "DevicePath", pThis->mPath.c_str());
+ AssertRCReturn(vrc, vrc);
+ vrc = pVMM->pfnCFGMR3InsertString(pConfig, "Id", pThis->mszUuid);
+ AssertRCReturn(vrc, vrc);
+ vrc = emulatedWebcamInsertSettings(pConfig, pVMM, &pThis->mDrvSettings);
+ AssertRCReturn(vrc, vrc);
+
+ /* pInstance will be used by PDM and deallocated on error. */
+ vrc = pVMM->pfnPDMR3UsbCreateEmulatedDevice(pUVM, "Webcam", pInstance, &pThis->mUuid, NULL);
+ LogRelFlowFunc(("PDMR3UsbCreateEmulatedDevice %Rrc\n", vrc));
+ return vrc;
+}
+
+/*static*/ DECLCALLBACK(int)
+EUSBWEBCAM::emulatedWebcamDetach(PUVM pUVM, PCVMMR3VTABLE pVMM, EUSBWEBCAM *pThis)
+{
+ return pVMM->pfnPDMR3UsbDetachDevice(pUVM, &pThis->mUuid);
+}
+
+HRESULT EUSBWEBCAM::Initialize(Console *pConsole,
+ EmulatedUSB *pEmulatedUSB,
+ const com::Utf8Str *aPath,
+ const com::Utf8Str *aSettings,
+ void *pvObject)
+{
+ HRESULT hrc = S_OK;
+
+ int vrc = RTUuidCreate(&mUuid);
+ AssertRCReturn(vrc, pConsole->setError(vrc, EmulatedUSB::tr("Init emulated USB webcam (RTUuidCreate -> %Rrc)"), vrc));
+
+ RTStrPrintf(mszUuid, sizeof(mszUuid), "%RTuuid", &mUuid);
+ hrc = mPath.assignEx(*aPath);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mSettings.assignEx(*aSettings);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = settingsParse();
+ if (SUCCEEDED(hrc))
+ {
+ mpEmulatedUSB = pEmulatedUSB;
+ mpvObject = pvObject;
+ }
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT EUSBWEBCAM::settingsParse(void)
+{
+ HRESULT hr = S_OK;
+
+ /* Parse mSettings string:
+ * "[dev:|drv:]Name1=Value1;[dev:|drv:]Name2=Value2"
+ */
+ char *pszSrc = mSettings.mutableRaw();
+
+ if (pszSrc)
+ {
+ while (*pszSrc)
+ {
+ /* Does the setting belong to device of driver. Default is both. */
+ bool fDev = true;
+ bool fDrv = true;
+ if (RTStrNICmp(pszSrc, RT_STR_TUPLE("drv:")) == 0)
+ {
+ pszSrc += sizeof("drv:")-1;
+ fDev = false;
+ }
+ else if (RTStrNICmp(pszSrc, RT_STR_TUPLE("dev:")) == 0)
+ {
+ pszSrc += sizeof("dev:")-1;
+ fDrv = false;
+ }
+
+ char *pszEq = strchr(pszSrc, '=');
+ if (!pszEq)
+ {
+ hr = E_INVALIDARG;
+ break;
+ }
+
+ char *pszEnd = strchr(pszEq, ';');
+ if (!pszEnd)
+ pszEnd = pszEq + strlen(pszEq);
+
+ *pszEq = 0;
+ char chEnd = *pszEnd;
+ *pszEnd = 0;
+
+ /* Empty strings not allowed. */
+ if (*pszSrc != 0 && pszEq[1] != 0)
+ {
+ if (fDev)
+ mDevSettings[pszSrc] = &pszEq[1];
+ if (fDrv)
+ mDrvSettings[pszSrc] = &pszEq[1];
+ }
+
+ *pszEq = '=';
+ *pszEnd = chEnd;
+
+ pszSrc = pszEnd;
+ if (*pszSrc == ';')
+ pszSrc++;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ EUSBSettingsMap::const_iterator it;
+ for (it = mDevSettings.begin(); it != mDevSettings.end(); ++it)
+ LogRelFlowFunc(("[dev:%s] = [%s]\n", it->first.c_str(), it->second.c_str()));
+ for (it = mDrvSettings.begin(); it != mDrvSettings.end(); ++it)
+ LogRelFlowFunc(("[drv:%s] = [%s]\n", it->first.c_str(), it->second.c_str()));
+ }
+ }
+
+ return hr;
+}
+
+HRESULT EUSBWEBCAM::Attach(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDriver)
+{
+ int vrc = pVMM->pfnVMR3ReqCallWaitU(pUVM, 0 /* idDstCpu (saved state, see #6232) */,
+ (PFNRT)emulatedWebcamAttach, 4,
+ pUVM, pVMM, this, pszDriver);
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+ LogFlowThisFunc(("%Rrc\n", vrc));
+ return pConsole->setErrorBoth(VBOX_E_VM_ERROR, vrc, EmulatedUSB::tr("Attach emulated USB webcam (%Rrc)"), vrc);
+}
+
+HRESULT EUSBWEBCAM::Detach(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ int vrc = pVMM->pfnVMR3ReqCallWaitU(pUVM, 0 /* idDstCpu (saved state, see #6232) */,
+ (PFNRT)emulatedWebcamDetach, 3,
+ pUVM, pVMM, this);
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+ LogFlowThisFunc(("%Rrc\n", vrc));
+ return pConsole->setErrorBoth(VBOX_E_VM_ERROR, vrc, EmulatedUSB::tr("Detach emulated USB webcam (%Rrc)"), vrc);
+}
+
+
+/*
+ * EmulatedUSB implementation.
+ */
+DEFINE_EMPTY_CTOR_DTOR(EmulatedUSB)
+
+HRESULT EmulatedUSB::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void EmulatedUSB::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+/*
+ * Initializes the instance.
+ *
+ * @param pConsole The owner.
+ */
+HRESULT EmulatedUSB::init(ComObjPtr<Console> pConsole)
+{
+ LogFlowThisFunc(("\n"));
+
+ ComAssertRet(!pConsole.isNull(), E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m.pConsole = pConsole;
+
+ mEmUsbIf.pvUser = this;
+ mEmUsbIf.pfnQueryEmulatedUsbDataById = EmulatedUSB::i_QueryEmulatedUsbDataById;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/*
+ * Uninitializes the instance.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void EmulatedUSB::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ m.pConsole.setNull();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ for (WebcamsMap::iterator it = m.webcams.begin(); it != m.webcams.end(); ++it)
+ {
+ EUSBWEBCAM *p = it->second;
+ if (p)
+ {
+ it->second = NULL;
+ p->Release();
+ }
+ }
+ m.webcams.clear();
+ alock.release();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+}
+
+HRESULT EmulatedUSB::getWebcams(std::vector<com::Utf8Str> &aWebcams)
+{
+ HRESULT hrc = S_OK;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ try
+ {
+ aWebcams.resize(m.webcams.size());
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ catch (...)
+ {
+ hrc = E_FAIL;
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ size_t i;
+ WebcamsMap::const_iterator it;
+ for (i = 0, it = m.webcams.begin(); it != m.webcams.end(); ++it)
+ aWebcams[i++] = it->first;
+ }
+
+ return hrc;
+}
+
+PEMULATEDUSBIF EmulatedUSB::i_getEmulatedUsbIf()
+{
+ return &mEmUsbIf;
+}
+
+static const Utf8Str s_pathDefault(".0");
+
+HRESULT EmulatedUSB::webcamAttach(const com::Utf8Str &aPath,
+ const com::Utf8Str &aSettings)
+{
+ return i_webcamAttachInternal(aPath, aSettings, "HostWebcam", NULL);
+}
+
+HRESULT EmulatedUSB::i_webcamAttachInternal(const com::Utf8Str &aPath,
+ const com::Utf8Str &aSettings,
+ const char *pszDriver,
+ void *pvObject)
+{
+ HRESULT hrc = S_OK;
+
+ const Utf8Str &path = aPath.isEmpty() || aPath == "."? s_pathDefault: aPath;
+
+ Console::SafeVMPtr ptrVM(m.pConsole);
+ if (ptrVM.isOk())
+ {
+ EUSBWEBCAM *p = new EUSBWEBCAM();
+ if (p)
+ {
+ hrc = p->Initialize(m.pConsole, this, &path, &aSettings, pvObject);
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ WebcamsMap::const_iterator it = m.webcams.find(path);
+ if (it == m.webcams.end())
+ {
+ p->AddRef();
+ try
+ {
+ m.webcams[path] = p;
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ catch (...)
+ {
+ hrc = E_FAIL;
+ }
+ p->enmStatus = EUSBDEVICE_ATTACHING;
+ }
+ else
+ {
+ hrc = E_FAIL;
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ hrc = p->Attach(m.pConsole, ptrVM.rawUVM(), ptrVM.vtable(), pszDriver);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (SUCCEEDED(hrc))
+ p->enmStatus = EUSBDEVICE_ATTACHED;
+ else if (p->enmStatus != EUSBDEVICE_CREATED)
+ m.webcams.erase(path);
+ alock.release();
+
+ p->Release();
+ }
+ else
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ else
+ {
+ hrc = VBOX_E_INVALID_VM_STATE;
+ }
+
+ return hrc;
+}
+
+HRESULT EmulatedUSB::webcamDetach(const com::Utf8Str &aPath)
+{
+ return i_webcamDetachInternal(aPath);
+}
+
+HRESULT EmulatedUSB::i_webcamDetachInternal(const com::Utf8Str &aPath)
+{
+ HRESULT hrc = S_OK;
+
+ const Utf8Str &path = aPath.isEmpty() || aPath == "."? s_pathDefault: aPath;
+
+ Console::SafeVMPtr ptrVM(m.pConsole);
+ if (ptrVM.isOk())
+ {
+ EUSBWEBCAM *p = NULL;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ WebcamsMap::iterator it = m.webcams.find(path);
+ if (it != m.webcams.end())
+ {
+ if (it->second->enmStatus == EUSBDEVICE_ATTACHED)
+ {
+ p = it->second;
+ m.webcams.erase(it);
+ }
+ }
+ alock.release();
+
+ if (p)
+ {
+ hrc = p->Detach(m.pConsole, ptrVM.rawUVM(), ptrVM.vtable());
+ p->Release();
+ }
+ else
+ {
+ hrc = E_INVALIDARG;
+ }
+ }
+ else
+ {
+ hrc = VBOX_E_INVALID_VM_STATE;
+ }
+
+ return hrc;
+}
+
+/*static*/ DECLCALLBACK(int)
+EmulatedUSB::eusbCallbackEMT(EmulatedUSB *pThis, char *pszId, uint32_t iEvent, void *pvData, uint32_t cbData)
+{
+ LogRelFlowFunc(("id %s event %d, data %p %d\n", pszId, iEvent, pvData, cbData));
+
+ NOREF(cbData);
+
+ int vrc = VINF_SUCCESS;
+ if (iEvent == 0)
+ {
+ com::Utf8Str path;
+ HRESULT hrc = pThis->webcamPathFromId(&path, pszId);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = pThis->webcamDetach(path);
+ if (FAILED(hrc))
+ {
+ vrc = VERR_INVALID_STATE;
+ }
+ }
+ else
+ {
+ vrc = VERR_NOT_FOUND;
+ }
+ }
+ else
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ }
+
+ RTMemFree(pszId);
+ RTMemFree(pvData);
+
+ LogRelFlowFunc(("rc %Rrc\n", vrc));
+ return vrc;
+}
+
+/* static */ DECLCALLBACK(int)
+EmulatedUSB::i_eusbCallback(void *pv, const char *pszId, uint32_t iEvent, const void *pvData, uint32_t cbData)
+{
+ /* Make a copy of parameters, forward to EMT and leave the callback to not hold any lock in the device. */
+ int vrc = VINF_SUCCESS;
+ void *pvDataCopy = NULL;
+ if (cbData > 0)
+ {
+ pvDataCopy = RTMemDup(pvData, cbData);
+ if (!pvDataCopy)
+ vrc = VERR_NO_MEMORY;
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ void *pvIdCopy = RTMemDup(pszId, strlen(pszId) + 1);
+ if (pvIdCopy)
+ {
+ if (RT_SUCCESS(vrc))
+ {
+ EmulatedUSB *pThis = (EmulatedUSB *)pv;
+ Console::SafeVMPtr ptrVM(pThis->m.pConsole);
+ if (ptrVM.isOk())
+ {
+ /* No wait. */
+ vrc = ptrVM.vtable()->pfnVMR3ReqCallNoWaitU(ptrVM.rawUVM(), 0 /* idDstCpu */,
+ (PFNRT)EmulatedUSB::eusbCallbackEMT, 5,
+ pThis, pvIdCopy, iEvent, pvDataCopy, cbData);
+ if (RT_SUCCESS(vrc))
+ return vrc;
+ }
+ else
+ vrc = VERR_INVALID_STATE;
+ }
+ RTMemFree(pvIdCopy);
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ RTMemFree(pvDataCopy);
+ }
+ return vrc;
+}
+
+/*static*/
+DECLCALLBACK(int) EmulatedUSB::i_QueryEmulatedUsbDataById(void *pvUser, const char *pszId, void **ppvEmUsbCb, void **ppvEmUsbCbData, void **ppvObject)
+{
+ EmulatedUSB *pEmUsb = (EmulatedUSB *)pvUser;
+
+ AutoReadLock alock(pEmUsb COMMA_LOCKVAL_SRC_POS);
+ WebcamsMap::const_iterator it;
+ for (it = pEmUsb->m.webcams.begin(); it != pEmUsb->m.webcams.end(); ++it)
+ {
+ EUSBWEBCAM *p = it->second;
+ if (p->HasId(pszId))
+ {
+ if (ppvEmUsbCb)
+ *ppvEmUsbCb = (void *)EmulatedUSB::i_eusbCallback;
+ if (ppvEmUsbCbData)
+ *ppvEmUsbCbData = pEmUsb;
+ if (ppvObject)
+ *ppvObject = p->getObjectPtr();
+
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+HRESULT EmulatedUSB::webcamPathFromId(com::Utf8Str *pPath, const char *pszId)
+{
+ HRESULT hrc = S_OK;
+
+ Console::SafeVMPtr ptrVM(m.pConsole);
+ if (ptrVM.isOk())
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ WebcamsMap::const_iterator it;
+ for (it = m.webcams.begin(); it != m.webcams.end(); ++it)
+ {
+ EUSBWEBCAM *p = it->second;
+ if (p->HasId(pszId))
+ {
+ *pPath = it->first;
+ break;
+ }
+ }
+
+ if (it == m.webcams.end())
+ {
+ hrc = E_FAIL;
+ }
+ alock.release();
+ }
+ else
+ {
+ hrc = VBOX_E_INVALID_VM_STATE;
+ }
+
+ return hrc;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/GuestCtrlImpl.cpp b/src/VBox/Main/src-client/GuestCtrlImpl.cpp
new file mode 100644
index 00000000..d99bab29
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestCtrlImpl.cpp
@@ -0,0 +1,726 @@
+/* $Id: GuestCtrlImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation: Guest
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_GUEST_CONTROL
+#include "LoggingNew.h"
+
+#include "GuestImpl.h"
+#ifdef VBOX_WITH_GUEST_CONTROL
+# include "GuestSessionImpl.h"
+# include "GuestSessionImplTasks.h"
+# include "GuestCtrlImplPrivate.h"
+#endif
+
+#include "Global.h"
+#include "ConsoleImpl.h"
+#include "ProgressImpl.h"
+#include "VBoxEvents.h"
+#include "VMMDev.h"
+
+#include "AutoCaller.h"
+
+#include <VBox/VMMDev.h>
+#ifdef VBOX_WITH_GUEST_CONTROL
+# include <VBox/com/array.h>
+# include <VBox/com/ErrorInfo.h>
+#endif
+#include <iprt/cpp/utils.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/list.h>
+#include <iprt/path.h>
+#include <VBox/vmm/pgm.h>
+#include <VBox/AssertGuest.h>
+
+#include <memory>
+
+
+/*
+ * This #ifdef goes almost to the end of the file where there are a couple of
+ * IGuest method implementations.
+ */
+#ifdef VBOX_WITH_GUEST_CONTROL
+
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Static callback function for receiving updates on guest control messages
+ * from the guest. Acts as a dispatcher for the actual class instance.
+ *
+ * @returns VBox status code.
+ * @param pvExtension Pointer to HGCM service extension.
+ * @param idMessage HGCM message ID the callback was called for.
+ * @param pvData Pointer to user-supplied callback data.
+ * @param cbData Size (in bytes) of user-supplied callback data.
+ */
+/* static */
+DECLCALLBACK(int) Guest::i_notifyCtrlDispatcher(void *pvExtension,
+ uint32_t idMessage,
+ void *pvData,
+ uint32_t cbData)
+{
+ using namespace guestControl;
+
+ /*
+ * No locking, as this is purely a notification which does not make any
+ * changes to the object state.
+ */
+ Log2Func(("pvExtension=%p, idMessage=%RU32, pvParms=%p, cbParms=%RU32\n", pvExtension, idMessage, pvData, cbData));
+
+ ComObjPtr<Guest> pGuest = reinterpret_cast<Guest *>(pvExtension);
+ AssertReturn(pGuest.isNotNull(), VERR_WRONG_ORDER);
+
+ /*
+ * The data packet should ever be a problem, but check to be sure.
+ */
+ AssertMsgReturn(cbData == sizeof(VBOXGUESTCTRLHOSTCALLBACK),
+ ("Guest control host callback data has wrong size (expected %zu, got %zu) - buggy host service!\n",
+ sizeof(VBOXGUESTCTRLHOSTCALLBACK), cbData), VERR_INVALID_PARAMETER);
+ PVBOXGUESTCTRLHOSTCALLBACK pSvcCb = (PVBOXGUESTCTRLHOSTCALLBACK)pvData;
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+
+ /*
+ * Deal with GUEST_MSG_REPORT_FEATURES here as it shouldn't be handed
+ * i_dispatchToSession() and has different parameters.
+ */
+ if (idMessage == GUEST_MSG_REPORT_FEATURES)
+ {
+ Assert(pSvcCb->mParms == 2);
+ Assert(pSvcCb->mpaParms[0].type == VBOX_HGCM_SVC_PARM_64BIT);
+ Assert(pSvcCb->mpaParms[1].type == VBOX_HGCM_SVC_PARM_64BIT);
+ Assert(pSvcCb->mpaParms[1].u.uint64 & VBOX_GUESTCTRL_GF_1_MUST_BE_ONE);
+ pGuest->mData.mfGuestFeatures0 = pSvcCb->mpaParms[0].u.uint64;
+ pGuest->mData.mfGuestFeatures1 = pSvcCb->mpaParms[1].u.uint64;
+ LogRel(("Guest Control: GUEST_MSG_REPORT_FEATURES: %#RX64, %#RX64\n",
+ pGuest->mData.mfGuestFeatures0, pGuest->mData.mfGuestFeatures1));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * For guest control 2.0 using the legacy messages we need to do the following here:
+ * - Get the callback header to access the context ID
+ * - Get the context ID of the callback
+ * - Extract the session ID out of the context ID
+ * - Dispatch the whole stuff to the appropriate session (if still exists)
+ *
+ * At least context ID parameter must always be present.
+ */
+ ASSERT_GUEST_RETURN(pSvcCb->mParms > 0, VERR_WRONG_PARAMETER_COUNT);
+ ASSERT_GUEST_MSG_RETURN(pSvcCb->mpaParms[0].type == VBOX_HGCM_SVC_PARM_32BIT,
+ ("type=%d\n", pSvcCb->mpaParms[0].type), VERR_WRONG_PARAMETER_TYPE);
+ uint32_t const idContext = pSvcCb->mpaParms[0].u.uint32;
+
+ VBOXGUESTCTRLHOSTCBCTX CtxCb = { idMessage, idContext };
+ int vrc = pGuest->i_dispatchToSession(&CtxCb, pSvcCb);
+
+ Log2Func(("CID=%#x, idSession=%RU32, uObject=%RU32, uCount=%RU32, vrc=%Rrc\n",
+ idContext, VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(idContext), VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(idContext),
+ VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(idContext), vrc));
+ return vrc;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Dispatches a host service callback to the appropriate guest control session object.
+ *
+ * @returns VBox status code.
+ * @param pCtxCb Pointer to host callback context.
+ * @param pSvcCb Pointer to callback parameters.
+ */
+int Guest::i_dispatchToSession(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
+{
+ LogFlowFunc(("pCtxCb=%p, pSvcCb=%p\n", pCtxCb, pSvcCb));
+
+ AssertPtrReturn(pCtxCb, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+
+ Log2Func(("uMessage=%RU32, uContextID=%RU32, uProtocol=%RU32\n", pCtxCb->uMessage, pCtxCb->uContextID, pCtxCb->uProtocol));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const uint32_t uSessionID = VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(pCtxCb->uContextID);
+
+ Log2Func(("uSessionID=%RU32 (%zu total)\n", uSessionID, mData.mGuestSessions.size()));
+
+ GuestSessions::const_iterator itSession = mData.mGuestSessions.find(uSessionID);
+
+ int vrc;
+ if (itSession != mData.mGuestSessions.end())
+ {
+ ComObjPtr<GuestSession> pSession(itSession->second);
+ Assert(!pSession.isNull());
+
+ alock.release();
+
+#ifdef DEBUG
+ /*
+ * Pre-check: If we got a status message with an error and VERR_TOO_MUCH_DATA
+ * it means that that guest could not handle the entire message
+ * because of its exceeding size. This should not happen on daily
+ * use but testcases might try this. It then makes no sense to dispatch
+ * this further because we don't have a valid context ID.
+ */
+ bool fDispatch = true;
+ vrc = VERR_INVALID_FUNCTION;
+ if ( pCtxCb->uMessage == GUEST_MSG_EXEC_STATUS
+ && pSvcCb->mParms >= 5)
+ {
+ CALLBACKDATA_PROC_STATUS dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ HGCMSvcGetU32(&pSvcCb->mpaParms[1], &dataCb.uPID);
+ HGCMSvcGetU32(&pSvcCb->mpaParms[2], &dataCb.uStatus);
+ HGCMSvcGetU32(&pSvcCb->mpaParms[3], &dataCb.uFlags);
+ HGCMSvcGetPv(&pSvcCb->mpaParms[4], &dataCb.pvData, &dataCb.cbData);
+
+ if ( dataCb.uStatus == PROC_STS_ERROR
+ && (int32_t)dataCb.uFlags == VERR_TOO_MUCH_DATA)
+ {
+ LogFlowFunc(("Requested message with too much data, skipping dispatching ...\n"));
+ Assert(dataCb.uPID == 0);
+ fDispatch = false;
+ }
+ }
+ if (fDispatch)
+#endif
+ {
+ switch (pCtxCb->uMessage)
+ {
+ case GUEST_MSG_DISCONNECTED:
+ vrc = pSession->i_dispatchToThis(pCtxCb, pSvcCb);
+ break;
+
+ /* Process stuff. */
+ case GUEST_MSG_EXEC_STATUS:
+ case GUEST_MSG_EXEC_OUTPUT:
+ case GUEST_MSG_EXEC_INPUT_STATUS:
+ case GUEST_MSG_EXEC_IO_NOTIFY:
+ vrc = pSession->i_dispatchToObject(pCtxCb, pSvcCb);
+ break;
+
+ /* File stuff. */
+ case GUEST_MSG_FILE_NOTIFY:
+ vrc = pSession->i_dispatchToObject(pCtxCb, pSvcCb);
+ break;
+
+ /* Session stuff. */
+ case GUEST_MSG_SESSION_NOTIFY:
+ vrc = pSession->i_dispatchToThis(pCtxCb, pSvcCb);
+ break;
+
+ default:
+ vrc = pSession->i_dispatchToObject(pCtxCb, pSvcCb);
+ break;
+ }
+ }
+ }
+ else
+ vrc = VERR_INVALID_SESSION_ID;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Creates a new guest session.
+ * This will invoke VBoxService running on the guest creating a new (dedicated) guest session
+ * On older Guest Additions this call has no effect on the guest, and only the credentials will be
+ * used for starting/impersonating guest processes.
+ *
+ * @returns VBox status code.
+ * @param ssInfo Guest session startup information.
+ * @param guestCreds Guest OS (user) credentials to use on the guest for creating the session.
+ * The specified user must be able to logon to the guest and able to start new processes.
+ * @param pGuestSession Where to store the created guest session on success.
+ *
+ * @note Takes the write lock.
+ */
+int Guest::i_sessionCreate(const GuestSessionStartupInfo &ssInfo,
+ const GuestCredentials &guestCreds, ComObjPtr<GuestSession> &pGuestSession)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VERR_MAX_PROCS_REACHED;
+ if (mData.mGuestSessions.size() >= VBOX_GUESTCTRL_MAX_SESSIONS)
+ return vrc;
+
+ try
+ {
+ /* Create a new session ID and assign it. */
+ uint32_t uNewSessionID = VBOX_GUESTCTRL_SESSION_ID_BASE;
+ uint32_t uTries = 0;
+
+ for (;;)
+ {
+ /* Is the context ID already used? */
+ if (!i_sessionExists(uNewSessionID))
+ {
+ vrc = VINF_SUCCESS;
+ break;
+ }
+ uNewSessionID++;
+ if (uNewSessionID >= VBOX_GUESTCTRL_MAX_SESSIONS)
+ uNewSessionID = VBOX_GUESTCTRL_SESSION_ID_BASE;
+
+ if (++uTries == VBOX_GUESTCTRL_MAX_SESSIONS)
+ break; /* Don't try too hard. */
+ }
+ if (RT_FAILURE(vrc)) throw vrc;
+
+ /* Create the session object. */
+ HRESULT hrc = pGuestSession.createObject();
+ if (FAILED(hrc)) throw VERR_COM_UNEXPECTED;
+
+ /** @todo Use an overloaded copy operator. Later. */
+ GuestSessionStartupInfo startupInfo;
+ startupInfo.mID = uNewSessionID; /* Assign new session ID. */
+ startupInfo.mName = ssInfo.mName;
+ startupInfo.mOpenFlags = ssInfo.mOpenFlags;
+ startupInfo.mOpenTimeoutMS = ssInfo.mOpenTimeoutMS;
+
+ GuestCredentials guestCredentials;
+ if (!guestCreds.mUser.isEmpty())
+ {
+ /** @todo Use an overloaded copy operator. Later. */
+ guestCredentials.mUser = guestCreds.mUser;
+ guestCredentials.mPassword = guestCreds.mPassword;
+ guestCredentials.mDomain = guestCreds.mDomain;
+ }
+ else
+ {
+ /* Internal (annonymous) session. */
+ startupInfo.mIsInternal = true;
+ }
+
+ vrc = pGuestSession->init(this, startupInfo, guestCredentials);
+ if (RT_FAILURE(vrc)) throw vrc;
+
+ /*
+ * Add session object to our session map. This is necessary
+ * before calling openSession because the guest calls back
+ * with the creation result of this session.
+ */
+ mData.mGuestSessions[uNewSessionID] = pGuestSession;
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestSessionRegisteredEvent(mEventSource, pGuestSession, true /* Registered */);
+ }
+ catch (int vrc2)
+ {
+ vrc = vrc2;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Destroys a given guest session and removes it from the internal list.
+ *
+ * @returns VBox status code.
+ * @param uSessionID ID of the guest control session to destroy.
+ *
+ * @note Takes the write lock.
+ */
+int Guest::i_sessionDestroy(uint32_t uSessionID)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VERR_NOT_FOUND;
+
+ LogFlowThisFunc(("Destroying session (ID=%RU32) ...\n", uSessionID));
+
+ GuestSessions::iterator itSessions = mData.mGuestSessions.find(uSessionID);
+ if (itSessions == mData.mGuestSessions.end())
+ return VERR_NOT_FOUND;
+
+ /* Make sure to consume the pointer before the one of the
+ * iterator gets released. */
+ ComObjPtr<GuestSession> pSession = itSessions->second;
+
+ LogFlowThisFunc(("Removing session %RU32 (now total %ld sessions)\n",
+ uSessionID, mData.mGuestSessions.size() ? mData.mGuestSessions.size() - 1 : 0));
+
+ vrc = pSession->i_onRemove();
+ mData.mGuestSessions.erase(itSessions);
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestSessionRegisteredEvent(mEventSource, pSession, false /* Unregistered */);
+ pSession.setNull();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Returns whether a guest control session with a specific ID exists or not.
+ *
+ * @returns Returns \c true if the session exists, \c false if not.
+ * @param uSessionID ID to check for.
+ *
+ * @note No locking done, as inline function!
+ */
+inline bool Guest::i_sessionExists(uint32_t uSessionID)
+{
+ GuestSessions::const_iterator itSessions = mData.mGuestSessions.find(uSessionID);
+ return (itSessions == mData.mGuestSessions.end()) ? false : true;
+}
+
+#endif /* VBOX_WITH_GUEST_CONTROL */
+
+
+// implementation of public methods
+/////////////////////////////////////////////////////////////////////////////
+HRESULT Guest::createSession(const com::Utf8Str &aUser, const com::Utf8Str &aPassword, const com::Utf8Str &aDomain,
+ const com::Utf8Str &aSessionName, ComPtr<IGuestSession> &aGuestSession)
+
+{
+#ifndef VBOX_WITH_GUEST_CONTROL
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_GUEST_CONTROL */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ /* Do not allow anonymous sessions (with system rights) with public API. */
+ if (RT_UNLIKELY(!aUser.length()))
+ return setError(E_INVALIDARG, tr("No user name specified"));
+
+ LogFlowFuncEnter();
+
+ GuestSessionStartupInfo startupInfo;
+ startupInfo.mName = aSessionName;
+
+ GuestCredentials guestCreds;
+ guestCreds.mUser = aUser;
+ guestCreds.mPassword = aPassword;
+ guestCreds.mDomain = aDomain;
+
+ ComObjPtr<GuestSession> pSession;
+ int vrc = i_sessionCreate(startupInfo, guestCreds, pSession);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Return guest session to the caller. */
+ HRESULT hr2 = pSession.queryInterfaceTo(aGuestSession.asOutParam());
+ if (FAILED(hr2))
+ vrc = VERR_COM_OBJECT_NOT_FOUND;
+ }
+
+ if (RT_SUCCESS(vrc))
+ /* Start (fork) the session asynchronously
+ * on the guest. */
+ vrc = pSession->i_startSessionAsync();
+
+ HRESULT hr = S_OK;
+
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_MAX_PROCS_REACHED:
+ hr = setErrorBoth(VBOX_E_MAXIMUM_REACHED, vrc, tr("Maximum number of concurrent guest sessions (%d) reached"),
+ VBOX_GUESTCTRL_MAX_SESSIONS);
+ break;
+
+ /** @todo Add more errors here. */
+
+ default:
+ hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not create guest session: %Rrc"), vrc);
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("Returning rc=%Rhrc\n", hr));
+ return hr;
+#endif /* VBOX_WITH_GUEST_CONTROL */
+}
+
+HRESULT Guest::findSession(const com::Utf8Str &aSessionName, std::vector<ComPtr<IGuestSession> > &aSessions)
+{
+#ifndef VBOX_WITH_GUEST_CONTROL
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_GUEST_CONTROL */
+
+ LogFlowFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Utf8Str strName(aSessionName);
+ std::list < ComObjPtr<GuestSession> > listSessions;
+
+ GuestSessions::const_iterator itSessions = mData.mGuestSessions.begin();
+ while (itSessions != mData.mGuestSessions.end())
+ {
+ if (strName.contains(itSessions->second->i_getName())) /** @todo Use a (simple) pattern match (IPRT?). */
+ listSessions.push_back(itSessions->second);
+ ++itSessions;
+ }
+
+ LogFlowFunc(("Sessions with \"%s\" = %RU32\n",
+ aSessionName.c_str(), listSessions.size()));
+
+ aSessions.resize(listSessions.size());
+ if (!listSessions.empty())
+ {
+ size_t i = 0;
+ for (std::list < ComObjPtr<GuestSession> >::const_iterator it = listSessions.begin(); it != listSessions.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aSessions[i].asOutParam());
+
+ return S_OK;
+
+ }
+
+ return setErrorNoLog(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Could not find sessions with name '%s'"),
+ aSessionName.c_str());
+#endif /* VBOX_WITH_GUEST_CONTROL */
+}
+
+HRESULT Guest::shutdown(const std::vector<GuestShutdownFlag_T> &aFlags)
+{
+#ifndef VBOX_WITH_GUEST_CONTROL
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_GUEST_CONTROL */
+
+ /* Validate flags. */
+ uint32_t fFlags = GuestShutdownFlag_None;
+ if (aFlags.size())
+ for (size_t i = 0; i < aFlags.size(); ++i)
+ fFlags |= aFlags[i];
+
+ const uint32_t fValidFlags = GuestShutdownFlag_None
+ | GuestShutdownFlag_PowerOff | GuestShutdownFlag_Reboot | GuestShutdownFlag_Force;
+ if (fFlags & ~fValidFlags)
+ return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags);
+
+ if ( (fFlags & GuestShutdownFlag_PowerOff)
+ && (fFlags & GuestShutdownFlag_Reboot))
+ return setError(E_INVALIDARG, tr("Invalid combination of flags (%#x)"), fFlags);
+
+ Utf8Str strAction = (fFlags & GuestShutdownFlag_Reboot) ? tr("Rebooting") : tr("Shutting down");
+
+ /*
+ * Create an anonymous session. This is required to run shutting down / rebooting
+ * the guest with administrative rights.
+ */
+ GuestSessionStartupInfo startupInfo;
+ startupInfo.mName = (fFlags & GuestShutdownFlag_Reboot) ? tr("Rebooting guest") : tr("Shutting down guest");
+
+ GuestCredentials guestCreds;
+
+ HRESULT hrc = S_OK;
+
+ ComObjPtr<GuestSession> pSession;
+ int vrc = i_sessionCreate(startupInfo, guestCreds, pSession);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(!pSession.isNull());
+
+ int vrcGuest = VERR_GSTCTL_GUEST_ERROR;
+ vrc = pSession->i_startSession(&vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pSession->i_shutdown(fFlags, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_NOT_SUPPORTED:
+ hrc = setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc,
+ tr("%s not supported by installed Guest Additions"), strAction.c_str());
+ break;
+
+ default:
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ vrc = vrcGuest;
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Error %s guest: %Rrc"), strAction.c_str(), vrc);
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ vrc = vrcGuest;
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not open guest session: %Rrc"), vrc);
+ }
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_MAX_PROCS_REACHED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Maximum number of concurrent guest sessions (%d) reached"),
+ VBOX_GUESTCTRL_MAX_SESSIONS);
+ break;
+
+ /** @todo Add more errors here. */
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not create guest session: %Rrc"), vrc);
+ break;
+ }
+ }
+
+ LogFlowFunc(("Returning hrc=%Rhrc\n", hrc));
+ return hrc;
+#endif /* VBOX_WITH_GUEST_CONTROL */
+}
+
+HRESULT Guest::updateGuestAdditions(const com::Utf8Str &aSource, const std::vector<com::Utf8Str> &aArguments,
+ const std::vector<AdditionsUpdateFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+#ifndef VBOX_WITH_GUEST_CONTROL
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_GUEST_CONTROL */
+
+ /* Validate flags. */
+ uint32_t fFlags = AdditionsUpdateFlag_None;
+ if (aFlags.size())
+ for (size_t i = 0; i < aFlags.size(); ++i)
+ fFlags |= aFlags[i];
+
+ if (fFlags && !(fFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly))
+ return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), fFlags);
+
+
+ /* Copy arguments into aArgs: */
+ ProcessArguments aArgs;
+ try
+ {
+ aArgs.resize(0);
+ for (size_t i = 0; i < aArguments.size(); ++i)
+ aArgs.push_back(aArguments[i]);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+
+ /*
+ * Create an anonymous session. This is required to run the Guest Additions
+ * update process with administrative rights.
+ */
+ GuestSessionStartupInfo startupInfo;
+ startupInfo.mName = "Updating Guest Additions";
+
+ GuestCredentials guestCreds;
+
+ HRESULT hrc;
+ ComObjPtr<GuestSession> pSession;
+ int vrc = i_sessionCreate(startupInfo, guestCreds, pSession);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(!pSession.isNull());
+
+ int vrcGuest = VERR_GSTCTL_GUEST_ERROR;
+ vrc = pSession->i_startSession(&vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Create the update task.
+ */
+ GuestSessionTaskUpdateAdditions *pTask = NULL;
+ try
+ {
+ pTask = new GuestSessionTaskUpdateAdditions(pSession /* GuestSession */, aSource, aArgs, fFlags);
+ hrc = S_OK;
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = setError(E_OUTOFMEMORY, tr("Failed to create SessionTaskUpdateAdditions object"));
+ }
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ hrc = pTask->Init(Utf8StrFmt(tr("Updating Guest Additions")));
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<Progress> ptrProgress = pTask->GetProgressObject();
+
+ /*
+ * Kick off the thread. Note! consumes pTask!
+ */
+ hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER);
+ pTask = NULL;
+ if (SUCCEEDED(hrc))
+ hrc = ptrProgress.queryInterfaceTo(aProgress.asOutParam());
+ else
+ hrc = setError(hrc, tr("Starting thread for updating Guest Additions on the guest failed"));
+ }
+ else
+ {
+ hrc = setError(hrc, tr("Failed to initialize SessionTaskUpdateAdditions object"));
+ delete pTask;
+ }
+ }
+ }
+ else
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ vrc = vrcGuest;
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not open guest session: %Rrc"), vrc);
+ }
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_MAX_PROCS_REACHED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Maximum number of concurrent guest sessions (%d) reached"),
+ VBOX_GUESTCTRL_MAX_SESSIONS);
+ break;
+
+ /** @todo Add more errors here. */
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not create guest session: %Rrc"), vrc);
+ break;
+ }
+ }
+
+ LogFlowFunc(("Returning hrc=%Rhrc\n", hrc));
+ return hrc;
+#endif /* VBOX_WITH_GUEST_CONTROL */
+}
+
diff --git a/src/VBox/Main/src-client/GuestCtrlPrivate.cpp b/src/VBox/Main/src-client/GuestCtrlPrivate.cpp
new file mode 100644
index 00000000..a9dc9897
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestCtrlPrivate.cpp
@@ -0,0 +1,1900 @@
+/* $Id: GuestCtrlPrivate.cpp $ */
+/** @file
+ * Internal helpers/structures for guest control functionality.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_GUEST_CONTROL
+#include "LoggingNew.h"
+
+#ifndef VBOX_WITH_GUEST_CONTROL
+# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
+#endif
+#include "GuestCtrlImplPrivate.h"
+#include "GuestSessionImpl.h"
+#include "VMMDev.h"
+
+#include <iprt/asm.h>
+#include <iprt/cpp/utils.h> /* For unconst(). */
+#include <iprt/ctype.h>
+#ifdef DEBUG
+# include <iprt/file.h>
+#endif
+#include <iprt/fs.h>
+#include <iprt/path.h>
+#include <iprt/rand.h>
+#include <iprt/time.h>
+#include <VBox/AssertGuest.h>
+
+
+/**
+ * Extracts the timespec from a given stream block key.
+ *
+ * @return Pointer to handed-in timespec, or NULL if invalid / not found.
+ * @param strmBlk Stream block to extract timespec from.
+ * @param strKey Key to get timespec for.
+ * @param pTimeSpec Where to store the extracted timespec.
+ */
+/* static */
+PRTTIMESPEC GuestFsObjData::TimeSpecFromKey(const GuestProcessStreamBlock &strmBlk, const Utf8Str &strKey, PRTTIMESPEC pTimeSpec)
+{
+ AssertPtrReturn(pTimeSpec, NULL);
+
+ Utf8Str strTime = strmBlk.GetString(strKey.c_str());
+ if (strTime.isEmpty())
+ return NULL;
+
+ if (!RTTimeSpecFromString(pTimeSpec, strTime.c_str()))
+ return NULL;
+
+ return pTimeSpec;
+}
+
+/**
+ * Extracts the nanoseconds relative from Unix epoch for a given stream block key.
+ *
+ * @return Nanoseconds relative from Unix epoch, or 0 if invalid / not found.
+ * @param strmBlk Stream block to extract nanoseconds from.
+ * @param strKey Key to get nanoseconds for.
+ */
+/* static */
+int64_t GuestFsObjData::UnixEpochNsFromKey(const GuestProcessStreamBlock &strmBlk, const Utf8Str &strKey)
+{
+ RTTIMESPEC TimeSpec;
+ if (!GuestFsObjData::TimeSpecFromKey(strmBlk, strKey, &TimeSpec))
+ return 0;
+
+ return TimeSpec.i64NanosecondsRelativeToUnixEpoch;
+}
+
+/**
+ * Initializes this object data with a stream block from VBOXSERVICE_TOOL_LS.
+ *
+ * This is also used by FromStat since the output should be identical given that
+ * they use the same output function on the guest side when fLong is true.
+ *
+ * @return VBox status code.
+ * @param strmBlk Stream block to use for initialization.
+ * @param fLong Whether the stream block contains long (detailed) information or not.
+ */
+int GuestFsObjData::FromLs(const GuestProcessStreamBlock &strmBlk, bool fLong)
+{
+ LogFlowFunc(("\n"));
+#ifdef DEBUG
+ strmBlk.DumpToLog();
+#endif
+
+ /* Object name. */
+ mName = strmBlk.GetString("name");
+ ASSERT_GUEST_RETURN(mName.isNotEmpty(), VERR_NOT_FOUND);
+
+ /* Type & attributes. */
+ bool fHaveAttribs = false;
+ char szAttribs[32];
+ memset(szAttribs, '?', sizeof(szAttribs) - 1);
+ mType = FsObjType_Unknown;
+ const char *psz = strmBlk.GetString("ftype");
+ if (psz)
+ {
+ fHaveAttribs = true;
+ szAttribs[0] = *psz;
+ switch (*psz)
+ {
+ case '-': mType = FsObjType_File; break;
+ case 'd': mType = FsObjType_Directory; break;
+ case 'l': mType = FsObjType_Symlink; break;
+ case 'c': mType = FsObjType_DevChar; break;
+ case 'b': mType = FsObjType_DevBlock; break;
+ case 'f': mType = FsObjType_Fifo; break;
+ case 's': mType = FsObjType_Socket; break;
+ case 'w': mType = FsObjType_WhiteOut; break;
+ default:
+ AssertMsgFailed(("%s\n", psz));
+ szAttribs[0] = '?';
+ fHaveAttribs = false;
+ break;
+ }
+ }
+ psz = strmBlk.GetString("owner_mask");
+ if ( psz
+ && (psz[0] == '-' || psz[0] == 'r')
+ && (psz[1] == '-' || psz[1] == 'w')
+ && (psz[2] == '-' || psz[2] == 'x'))
+ {
+ szAttribs[1] = psz[0];
+ szAttribs[2] = psz[1];
+ szAttribs[3] = psz[2];
+ fHaveAttribs = true;
+ }
+ psz = strmBlk.GetString("group_mask");
+ if ( psz
+ && (psz[0] == '-' || psz[0] == 'r')
+ && (psz[1] == '-' || psz[1] == 'w')
+ && (psz[2] == '-' || psz[2] == 'x'))
+ {
+ szAttribs[4] = psz[0];
+ szAttribs[5] = psz[1];
+ szAttribs[6] = psz[2];
+ fHaveAttribs = true;
+ }
+ psz = strmBlk.GetString("other_mask");
+ if ( psz
+ && (psz[0] == '-' || psz[0] == 'r')
+ && (psz[1] == '-' || psz[1] == 'w')
+ && (psz[2] == '-' || psz[2] == 'x'))
+ {
+ szAttribs[7] = psz[0];
+ szAttribs[8] = psz[1];
+ szAttribs[9] = psz[2];
+ fHaveAttribs = true;
+ }
+ szAttribs[10] = ' '; /* Reserve three chars for sticky bits. */
+ szAttribs[11] = ' ';
+ szAttribs[12] = ' ';
+ szAttribs[13] = ' '; /* Separator. */
+ psz = strmBlk.GetString("dos_mask");
+ if ( psz
+ && (psz[ 0] == '-' || psz[ 0] == 'R')
+ && (psz[ 1] == '-' || psz[ 1] == 'H')
+ && (psz[ 2] == '-' || psz[ 2] == 'S')
+ && (psz[ 3] == '-' || psz[ 3] == 'D')
+ && (psz[ 4] == '-' || psz[ 4] == 'A')
+ && (psz[ 5] == '-' || psz[ 5] == 'd')
+ && (psz[ 6] == '-' || psz[ 6] == 'N')
+ && (psz[ 7] == '-' || psz[ 7] == 'T')
+ && (psz[ 8] == '-' || psz[ 8] == 'P')
+ && (psz[ 9] == '-' || psz[ 9] == 'J')
+ && (psz[10] == '-' || psz[10] == 'C')
+ && (psz[11] == '-' || psz[11] == 'O')
+ && (psz[12] == '-' || psz[12] == 'I')
+ && (psz[13] == '-' || psz[13] == 'E'))
+ {
+ memcpy(&szAttribs[14], psz, 14);
+ fHaveAttribs = true;
+ }
+ szAttribs[28] = '\0';
+ if (fHaveAttribs)
+ mFileAttrs = szAttribs;
+
+ /* Object size. */
+ int rc = strmBlk.GetInt64Ex("st_size", &mObjectSize);
+ ASSERT_GUEST_RC_RETURN(rc, rc);
+ strmBlk.GetInt64Ex("alloc", &mAllocatedSize);
+
+ /* INode number and device. */
+ psz = strmBlk.GetString("node_id");
+ if (!psz)
+ psz = strmBlk.GetString("cnode_id"); /* copy & past error fixed in 6.0 RC1 */
+ if (psz)
+ mNodeID = RTStrToInt64(psz);
+ mNodeIDDevice = strmBlk.GetUInt32("inode_dev"); /* (Produced by GAs prior to 6.0 RC1.) */
+
+ if (fLong)
+ {
+ /* Dates. */
+ mAccessTime = GuestFsObjData::UnixEpochNsFromKey(strmBlk, "st_atime");
+ mBirthTime = GuestFsObjData::UnixEpochNsFromKey(strmBlk, "st_birthtime");
+ mChangeTime = GuestFsObjData::UnixEpochNsFromKey(strmBlk, "st_ctime");
+ mModificationTime = GuestFsObjData::UnixEpochNsFromKey(strmBlk, "st_mtime");
+
+ /* Owner & group. */
+ mUID = strmBlk.GetInt32("uid");
+ psz = strmBlk.GetString("username");
+ if (psz)
+ mUserName = psz;
+ mGID = strmBlk.GetInt32("gid");
+ psz = strmBlk.GetString("groupname");
+ if (psz)
+ mGroupName = psz;
+
+ /* Misc attributes: */
+ mNumHardLinks = strmBlk.GetUInt32("hlinks", 1);
+ mDeviceNumber = strmBlk.GetUInt32("st_rdev");
+ mGenerationID = strmBlk.GetUInt32("st_gen");
+ mUserFlags = strmBlk.GetUInt32("st_flags");
+
+ /** @todo ACL */
+ }
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Parses stream block output data which came from the 'rm' (vbox_rm)
+ * VBoxService toolbox command. The result will be stored in this object.
+ *
+ * @returns VBox status code.
+ * @param strmBlk Stream block output data to parse.
+ */
+int GuestFsObjData::FromRm(const GuestProcessStreamBlock &strmBlk)
+{
+#ifdef DEBUG
+ strmBlk.DumpToLog();
+#endif
+ /* Object name. */
+ mName = strmBlk.GetString("fname");
+
+ /* Return the stream block's rc. */
+ return strmBlk.GetRc();
+}
+
+/**
+ * Parses stream block output data which came from the 'stat' (vbox_stat)
+ * VBoxService toolbox command. The result will be stored in this object.
+ *
+ * @returns VBox status code.
+ * @param strmBlk Stream block output data to parse.
+ */
+int GuestFsObjData::FromStat(const GuestProcessStreamBlock &strmBlk)
+{
+ /* Should be identical output. */
+ return GuestFsObjData::FromLs(strmBlk, true /*fLong*/);
+}
+
+/**
+ * Parses stream block output data which came from the 'mktemp' (vbox_mktemp)
+ * VBoxService toolbox command. The result will be stored in this object.
+ *
+ * @returns VBox status code.
+ * @param strmBlk Stream block output data to parse.
+ */
+int GuestFsObjData::FromMkTemp(const GuestProcessStreamBlock &strmBlk)
+{
+ LogFlowFunc(("\n"));
+
+#ifdef DEBUG
+ strmBlk.DumpToLog();
+#endif
+ /* Object name. */
+ mName = strmBlk.GetString("name");
+ ASSERT_GUEST_RETURN(mName.isNotEmpty(), VERR_NOT_FOUND);
+
+ /* Assign the stream block's rc. */
+ int rc = strmBlk.GetRc();
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns the IPRT-compatible file mode.
+ * Note: Only handling RTFS_TYPE_ flags are implemented for now.
+ *
+ * @return IPRT file mode.
+ */
+RTFMODE GuestFsObjData::GetFileMode(void) const
+{
+ RTFMODE fMode = 0;
+
+ switch (mType)
+ {
+ case FsObjType_Directory:
+ fMode |= RTFS_TYPE_DIRECTORY;
+ break;
+
+ case FsObjType_File:
+ fMode |= RTFS_TYPE_FILE;
+ break;
+
+ case FsObjType_Symlink:
+ fMode |= RTFS_TYPE_SYMLINK;
+ break;
+
+ default:
+ break;
+ }
+
+ /** @todo Implement more stuff. */
+
+ return fMode;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/** @todo *NOT* thread safe yet! */
+/** @todo Add exception handling for STL stuff! */
+
+GuestProcessStreamBlock::GuestProcessStreamBlock(void)
+{
+
+}
+
+GuestProcessStreamBlock::~GuestProcessStreamBlock()
+{
+ Clear();
+}
+
+/**
+ * Clears (destroys) the currently stored stream pairs.
+ */
+void GuestProcessStreamBlock::Clear(void)
+{
+ mPairs.clear();
+}
+
+#ifdef DEBUG
+/**
+ * Dumps the currently stored stream pairs to the (debug) log.
+ */
+void GuestProcessStreamBlock::DumpToLog(void) const
+{
+ LogFlowFunc(("Dumping contents of stream block=0x%p (%ld items):\n",
+ this, mPairs.size()));
+
+ for (GuestCtrlStreamPairMapIterConst it = mPairs.begin();
+ it != mPairs.end(); ++it)
+ {
+ LogFlowFunc(("\t%s=%s\n", it->first.c_str(), it->second.mValue.c_str()));
+ }
+}
+#endif
+
+/**
+ * Returns a 64-bit signed integer of a specified key.
+ *
+ * @return VBox status code. VERR_NOT_FOUND if key was not found.
+ * @param pszKey Name of key to get the value for.
+ * @param piVal Pointer to value to return.
+ */
+int GuestProcessStreamBlock::GetInt64Ex(const char *pszKey, int64_t *piVal) const
+{
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+ AssertPtrReturn(piVal, VERR_INVALID_POINTER);
+ const char *pszValue = GetString(pszKey);
+ if (pszValue)
+ {
+ *piVal = RTStrToInt64(pszValue);
+ return VINF_SUCCESS;
+ }
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * Returns a 64-bit integer of a specified key.
+ *
+ * @return int64_t Value to return, 0 if not found / on failure.
+ * @param pszKey Name of key to get the value for.
+ */
+int64_t GuestProcessStreamBlock::GetInt64(const char *pszKey) const
+{
+ int64_t iVal;
+ if (RT_SUCCESS(GetInt64Ex(pszKey, &iVal)))
+ return iVal;
+ return 0;
+}
+
+/**
+ * Returns the current number of stream pairs.
+ *
+ * @return uint32_t Current number of stream pairs.
+ */
+size_t GuestProcessStreamBlock::GetCount(void) const
+{
+ return mPairs.size();
+}
+
+/**
+ * Gets the return code (name = "rc") of this stream block.
+ *
+ * @return VBox status code.
+ * @retval VERR_NOT_FOUND if the return code string ("rc") was not found.
+ */
+int GuestProcessStreamBlock::GetRc(void) const
+{
+ const char *pszValue = GetString("rc");
+ if (pszValue)
+ {
+ return RTStrToInt16(pszValue);
+ }
+ /** @todo We probably should have a dedicated error for that, VERR_GSTCTL_GUEST_TOOLBOX_whatever. */
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * Returns a string value of a specified key.
+ *
+ * @return uint32_t Pointer to string to return, NULL if not found / on failure.
+ * @param pszKey Name of key to get the value for.
+ */
+const char *GuestProcessStreamBlock::GetString(const char *pszKey) const
+{
+ AssertPtrReturn(pszKey, NULL);
+
+ try
+ {
+ GuestCtrlStreamPairMapIterConst itPairs = mPairs.find(pszKey);
+ if (itPairs != mPairs.end())
+ return itPairs->second.mValue.c_str();
+ }
+ catch (const std::exception &ex)
+ {
+ RT_NOREF(ex);
+ }
+ return NULL;
+}
+
+/**
+ * Returns a 32-bit unsigned integer of a specified key.
+ *
+ * @return VBox status code. VERR_NOT_FOUND if key was not found.
+ * @param pszKey Name of key to get the value for.
+ * @param puVal Pointer to value to return.
+ */
+int GuestProcessStreamBlock::GetUInt32Ex(const char *pszKey, uint32_t *puVal) const
+{
+ const char *pszValue = GetString(pszKey);
+ if (pszValue)
+ {
+ *puVal = RTStrToUInt32(pszValue);
+ return VINF_SUCCESS;
+ }
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * Returns a 32-bit signed integer of a specified key.
+ *
+ * @returns 32-bit signed value
+ * @param pszKey Name of key to get the value for.
+ * @param iDefault The default to return on error if not found.
+ */
+int32_t GuestProcessStreamBlock::GetInt32(const char *pszKey, int32_t iDefault) const
+{
+ const char *pszValue = GetString(pszKey);
+ if (pszValue)
+ {
+ int32_t iRet;
+ int rc = RTStrToInt32Full(pszValue, 0, &iRet);
+ if (RT_SUCCESS(rc))
+ return iRet;
+ ASSERT_GUEST_MSG_FAILED(("%s=%s\n", pszKey, pszValue));
+ }
+ return iDefault;
+}
+
+/**
+ * Returns a 32-bit unsigned integer of a specified key.
+ *
+ * @return uint32_t Value to return, 0 if not found / on failure.
+ * @param pszKey Name of key to get the value for.
+ * @param uDefault The default value to return.
+ */
+uint32_t GuestProcessStreamBlock::GetUInt32(const char *pszKey, uint32_t uDefault /*= 0*/) const
+{
+ uint32_t uVal;
+ if (RT_SUCCESS(GetUInt32Ex(pszKey, &uVal)))
+ return uVal;
+ return uDefault;
+}
+
+/**
+ * Sets a value to a key or deletes a key by setting a NULL value.
+ *
+ * @return VBox status code.
+ * @param pszKey Key name to process.
+ * @param pszValue Value to set. Set NULL for deleting the key.
+ */
+int GuestProcessStreamBlock::SetValue(const char *pszKey, const char *pszValue)
+{
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ try
+ {
+ Utf8Str Utf8Key(pszKey);
+
+ /* Take a shortcut and prevent crashes on some funny versions
+ * of STL if map is empty initially. */
+ if (!mPairs.empty())
+ {
+ GuestCtrlStreamPairMapIter it = mPairs.find(Utf8Key);
+ if (it != mPairs.end())
+ mPairs.erase(it);
+ }
+
+ if (pszValue)
+ {
+ GuestProcessStreamValue val(pszValue);
+ mPairs[Utf8Key] = val;
+ }
+ }
+ catch (const std::exception &ex)
+ {
+ RT_NOREF(ex);
+ }
+ return rc;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GuestProcessStream::GuestProcessStream(void)
+ : m_cbMax(_32M)
+ , m_cbAllocated(0)
+ , m_cbUsed(0)
+ , m_offBuffer(0)
+ , m_pbBuffer(NULL) { }
+
+GuestProcessStream::~GuestProcessStream(void)
+{
+ Destroy();
+}
+
+/**
+ * Adds data to the internal parser buffer. Useful if there
+ * are multiple rounds of adding data needed.
+ *
+ * @return VBox status code. Will return VERR_TOO_MUCH_DATA if the buffer's maximum (limit) has been reached.
+ * @param pbData Pointer to data to add.
+ * @param cbData Size (in bytes) of data to add.
+ */
+int GuestProcessStream::AddData(const BYTE *pbData, size_t cbData)
+{
+ AssertPtrReturn(pbData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+
+ /* Rewind the buffer if it's empty. */
+ size_t cbInBuf = m_cbUsed - m_offBuffer;
+ bool const fAddToSet = cbInBuf == 0;
+ if (fAddToSet)
+ m_cbUsed = m_offBuffer = 0;
+
+ /* Try and see if we can simply append the data. */
+ if (cbData + m_cbUsed <= m_cbAllocated)
+ {
+ memcpy(&m_pbBuffer[m_cbUsed], pbData, cbData);
+ m_cbUsed += cbData;
+ }
+ else
+ {
+ /* Move any buffered data to the front. */
+ cbInBuf = m_cbUsed - m_offBuffer;
+ if (cbInBuf == 0)
+ m_cbUsed = m_offBuffer = 0;
+ else if (m_offBuffer) /* Do we have something to move? */
+ {
+ memmove(m_pbBuffer, &m_pbBuffer[m_offBuffer], cbInBuf);
+ m_cbUsed = cbInBuf;
+ m_offBuffer = 0;
+ }
+
+ /* Do we need to grow the buffer? */
+ if (cbData + m_cbUsed > m_cbAllocated)
+ {
+ size_t cbAlloc = m_cbUsed + cbData;
+ if (cbAlloc <= m_cbMax)
+ {
+ cbAlloc = RT_ALIGN_Z(cbAlloc, _64K);
+ void *pvNew = RTMemRealloc(m_pbBuffer, cbAlloc);
+ if (pvNew)
+ {
+ m_pbBuffer = (uint8_t *)pvNew;
+ m_cbAllocated = cbAlloc;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_TOO_MUCH_DATA;
+ }
+
+ /* Finally, copy the data. */
+ if (RT_SUCCESS(rc))
+ {
+ if (cbData + m_cbUsed <= m_cbAllocated)
+ {
+ memcpy(&m_pbBuffer[m_cbUsed], pbData, cbData);
+ m_cbUsed += cbData;
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Destroys the internal data buffer.
+ */
+void GuestProcessStream::Destroy(void)
+{
+ if (m_pbBuffer)
+ {
+ RTMemFree(m_pbBuffer);
+ m_pbBuffer = NULL;
+ }
+
+ m_cbAllocated = 0;
+ m_cbUsed = 0;
+ m_offBuffer = 0;
+}
+
+#ifdef DEBUG
+/**
+ * Dumps the raw guest process output to a file on the host.
+ * If the file on the host already exists, it will be overwritten.
+ *
+ * @param pszFile Absolute path to host file to dump the output to.
+ */
+void GuestProcessStream::Dump(const char *pszFile)
+{
+ LogFlowFunc(("Dumping contents of stream=0x%p (cbAlloc=%u, cbSize=%u, cbOff=%u) to %s\n",
+ m_pbBuffer, m_cbAllocated, m_cbUsed, m_offBuffer, pszFile));
+
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszFile, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(hFile, m_pbBuffer, m_cbUsed, NULL /* pcbWritten */);
+ RTFileClose(hFile);
+ }
+}
+#endif
+
+/**
+ * Tries to parse the next upcoming pair block within the internal
+ * buffer.
+ *
+ * Returns VERR_NO_DATA is no data is in internal buffer or buffer has been
+ * completely parsed already.
+ *
+ * Returns VERR_MORE_DATA if current block was parsed (with zero or more pairs
+ * stored in stream block) but still contains incomplete (unterminated)
+ * data.
+ *
+ * Returns VINF_SUCCESS if current block was parsed until the next upcoming
+ * block (with zero or more pairs stored in stream block).
+ *
+ * @return VBox status code.
+ * @param streamBlock Reference to guest stream block to fill.
+ */
+int GuestProcessStream::ParseBlock(GuestProcessStreamBlock &streamBlock)
+{
+ if ( !m_pbBuffer
+ || !m_cbUsed)
+ return VERR_NO_DATA;
+
+ AssertReturn(m_offBuffer <= m_cbUsed, VERR_INVALID_PARAMETER);
+ if (m_offBuffer == m_cbUsed)
+ return VERR_NO_DATA;
+
+ int rc = VINF_SUCCESS;
+ char * const pszOff = (char *)&m_pbBuffer[m_offBuffer];
+ size_t cbLeft = m_offBuffer < m_cbUsed ? m_cbUsed - m_offBuffer : 0;
+ char *pszStart = pszOff;
+ while (cbLeft > 0 && *pszStart != '\0')
+ {
+ char * const pszPairEnd = RTStrEnd(pszStart, cbLeft);
+ if (!pszPairEnd)
+ {
+ rc = VERR_MORE_DATA;
+ break;
+ }
+ size_t const cchPair = (size_t)(pszPairEnd - pszStart);
+ char *pszSep = (char *)memchr(pszStart, '=', cchPair);
+ if (pszSep)
+ *pszSep = '\0'; /* Terminate the separator so that we can use pszStart as our key from now on. */
+ else
+ {
+ rc = VERR_MORE_DATA; /** @todo r=bird: This is BOGUS because we'll be stuck here if the guest feeds us bad data! */
+ break;
+ }
+ char const * const pszVal = pszSep + 1;
+
+ rc = streamBlock.SetValue(pszStart, pszVal);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Next pair. */
+ pszStart = pszPairEnd + 1;
+ cbLeft -= cchPair + 1;
+ }
+
+ /* If we did not do any movement but we have stuff left
+ * in our buffer just skip the current termination so that
+ * we can try next time. */
+ size_t cbDistance = (pszStart - pszOff);
+ if ( !cbDistance
+ && cbLeft > 0
+ && *pszStart == '\0'
+ && m_offBuffer < m_cbUsed)
+ cbDistance++;
+ m_offBuffer += cbDistance;
+
+ return rc;
+}
+
+GuestBase::GuestBase(void)
+ : mConsole(NULL)
+ , mNextContextID(RTRandU32() % VBOX_GUESTCTRL_MAX_CONTEXTS)
+{
+}
+
+GuestBase::~GuestBase(void)
+{
+}
+
+/**
+ * Separate initialization function for the base class.
+ *
+ * @returns VBox status code.
+ */
+int GuestBase::baseInit(void)
+{
+ int rc = RTCritSectInit(&mWaitEventCritSect);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Separate uninitialization function for the base class.
+ */
+void GuestBase::baseUninit(void)
+{
+ LogFlowThisFuncEnter();
+
+ /* Make sure to cancel any outstanding wait events. */
+ int rc2 = cancelWaitEvents();
+ AssertRC(rc2);
+
+ rc2 = RTCritSectDelete(&mWaitEventCritSect);
+ AssertRC(rc2);
+
+ LogFlowFuncLeaveRC(rc2);
+ /* No return value. */
+}
+
+/**
+ * Cancels all outstanding wait events.
+ *
+ * @returns VBox status code.
+ */
+int GuestBase::cancelWaitEvents(void)
+{
+ LogFlowThisFuncEnter();
+
+ int rc = RTCritSectEnter(&mWaitEventCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ GuestEventGroup::iterator itEventGroups = mWaitEventGroups.begin();
+ while (itEventGroups != mWaitEventGroups.end())
+ {
+ GuestWaitEvents::iterator itEvents = itEventGroups->second.begin();
+ while (itEvents != itEventGroups->second.end())
+ {
+ GuestWaitEvent *pEvent = itEvents->second;
+ AssertPtr(pEvent);
+
+ /*
+ * Just cancel the event, but don't remove it from the
+ * wait events map. Don't delete it though, this (hopefully)
+ * is done by the caller using unregisterWaitEvent().
+ */
+ int rc2 = pEvent->Cancel();
+ AssertRC(rc2);
+
+ ++itEvents;
+ }
+
+ ++itEventGroups;
+ }
+
+ int rc2 = RTCritSectLeave(&mWaitEventCritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Handles generic messages not bound to a specific object type.
+ *
+ * @return VBox status code. VERR_NOT_FOUND if no handler has been found or VERR_NOT_SUPPORTED
+ * if this class does not support the specified callback.
+ * @param pCtxCb Host callback context.
+ * @param pSvcCb Service callback data.
+ */
+int GuestBase::dispatchGeneric(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
+{
+ LogFlowFunc(("pCtxCb=%p, pSvcCb=%p\n", pCtxCb, pSvcCb));
+
+ AssertPtrReturn(pCtxCb, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+
+ int vrc;
+
+ try
+ {
+ Log2Func(("uFunc=%RU32, cParms=%RU32\n", pCtxCb->uMessage, pSvcCb->mParms));
+
+ switch (pCtxCb->uMessage)
+ {
+ case GUEST_MSG_PROGRESS_UPDATE:
+ vrc = VINF_SUCCESS;
+ break;
+
+ case GUEST_MSG_REPLY:
+ {
+ if (pSvcCb->mParms >= 4)
+ {
+ int idx = 1; /* Current parameter index. */
+ CALLBACKDATA_MSG_REPLY dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ vrc = HGCMSvcGetU32(&pSvcCb->mpaParms[idx++], &dataCb.uType);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCb->mpaParms[idx++], &dataCb.rc);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetPv(&pSvcCb->mpaParms[idx++], &dataCb.pvPayload, &dataCb.cbPayload);
+ AssertRCReturn(vrc, vrc);
+
+ try
+ {
+ GuestWaitEventPayload evPayload(dataCb.uType, dataCb.pvPayload, dataCb.cbPayload);
+ vrc = signalWaitEventInternal(pCtxCb, dataCb.rc, &evPayload);
+ }
+ catch (int rcEx) /* Thrown by GuestWaitEventPayload constructor. */
+ {
+ vrc = rcEx;
+ }
+ }
+ else
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ catch (int rc)
+ {
+ vrc = rc;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Generates a context ID (CID) by incrementing the object's count.
+ * A CID consists of a session ID, an object ID and a count.
+ *
+ * Note: This function does not guarantee that the returned CID is unique;
+ * the caller has to take care of that and eventually retry.
+ *
+ * @returns VBox status code.
+ * @param uSessionID Session ID to use for CID generation.
+ * @param uObjectID Object ID to use for CID generation.
+ * @param puContextID Where to store the generated CID on success.
+ */
+int GuestBase::generateContextID(uint32_t uSessionID, uint32_t uObjectID, uint32_t *puContextID)
+{
+ AssertPtrReturn(puContextID, VERR_INVALID_POINTER);
+
+ if ( uSessionID >= VBOX_GUESTCTRL_MAX_SESSIONS
+ || uObjectID >= VBOX_GUESTCTRL_MAX_OBJECTS)
+ return VERR_INVALID_PARAMETER;
+
+ uint32_t uCount = ASMAtomicIncU32(&mNextContextID);
+ uCount %= VBOX_GUESTCTRL_MAX_CONTEXTS;
+
+ uint32_t uNewContextID = VBOX_GUESTCTRL_CONTEXTID_MAKE(uSessionID, uObjectID, uCount);
+
+ *puContextID = uNewContextID;
+
+#if 0
+ LogFlowThisFunc(("mNextContextID=%RU32, uSessionID=%RU32, uObjectID=%RU32, uCount=%RU32, uNewContextID=%RU32\n",
+ mNextContextID, uSessionID, uObjectID, uCount, uNewContextID));
+#endif
+ return VINF_SUCCESS;
+}
+
+/**
+ * Registers (creates) a new wait event based on a given session and object ID.
+ *
+ * From those IDs an unique context ID (CID) will be built, which only can be
+ * around once at a time.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_MAX_CID_COUNT_REACHED if unable to generate a free context ID (CID, the count part (bits 15:0)).
+ * @param uSessionID Session ID to register wait event for.
+ * @param uObjectID Object ID to register wait event for.
+ * @param ppEvent Pointer to registered (created) wait event on success.
+ * Must be destroyed with unregisterWaitEvent().
+ */
+int GuestBase::registerWaitEvent(uint32_t uSessionID, uint32_t uObjectID, GuestWaitEvent **ppEvent)
+{
+ GuestEventTypes eventTypesEmpty;
+ return registerWaitEventEx(uSessionID, uObjectID, eventTypesEmpty, ppEvent);
+}
+
+/**
+ * Creates and registers a new wait event object that waits on a set of events
+ * related to a given object within the session.
+ *
+ * From the session ID and object ID a one-time unique context ID (CID) is built
+ * for this wait object. Normally the CID is then passed to the guest along
+ * with a request, and the guest passed the CID back with the reply. The
+ * handler for the reply then emits a signal on the event type associated with
+ * the reply, which includes signalling the object returned by this method and
+ * the waking up the thread waiting on it.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_MAX_CID_COUNT_REACHED if unable to generate a free context ID (CID, the count part (bits 15:0)).
+ * @param uSessionID Session ID to register wait event for.
+ * @param uObjectID Object ID to register wait event for.
+ * @param lstEvents List of events to register the wait event for.
+ * @param ppEvent Pointer to registered (created) wait event on success.
+ * Must be destroyed with unregisterWaitEvent().
+ */
+int GuestBase::registerWaitEventEx(uint32_t uSessionID, uint32_t uObjectID, const GuestEventTypes &lstEvents,
+ GuestWaitEvent **ppEvent)
+{
+ AssertPtrReturn(ppEvent, VERR_INVALID_POINTER);
+
+ uint32_t idContext;
+ int rc = generateContextID(uSessionID, uObjectID, &idContext);
+ AssertRCReturn(rc, rc);
+
+ GuestWaitEvent *pEvent = new GuestWaitEvent();
+ AssertPtrReturn(pEvent, VERR_NO_MEMORY);
+
+ rc = pEvent->Init(idContext, lstEvents);
+ AssertRCReturn(rc, rc);
+
+ LogFlowThisFunc(("New event=%p, CID=%RU32\n", pEvent, idContext));
+
+ rc = RTCritSectEnter(&mWaitEventCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check that we don't have any context ID collisions (should be very unlikely).
+ *
+ * The ASSUMPTION here is that mWaitEvents has all the same events as
+ * mWaitEventGroups, so it suffices to check one of the two.
+ */
+ if (mWaitEvents.find(idContext) != mWaitEvents.end())
+ {
+ uint32_t cTries = 0;
+ do
+ {
+ rc = generateContextID(uSessionID, uObjectID, &idContext);
+ AssertRCBreak(rc);
+ LogFunc(("Found context ID duplicate; trying a different context ID: %#x\n", idContext));
+ if (mWaitEvents.find(idContext) != mWaitEvents.end())
+ rc = VERR_GSTCTL_MAX_CID_COUNT_REACHED;
+ } while (RT_FAILURE_NP(rc) && cTries++ < 10);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Insert event into matching event group. This is for faster per-group lookup of all events later.
+ */
+ uint32_t cInserts = 0;
+ for (GuestEventTypes::const_iterator ItType = lstEvents.begin(); ItType != lstEvents.end(); ++ItType)
+ {
+ GuestWaitEvents &eventGroup = mWaitEventGroups[*ItType];
+ if (eventGroup.find(idContext) == eventGroup.end())
+ {
+ try
+ {
+ eventGroup.insert(std::pair<uint32_t, GuestWaitEvent *>(idContext, pEvent));
+ cInserts++;
+ }
+ catch (std::bad_alloc &)
+ {
+ while (ItType != lstEvents.begin())
+ {
+ --ItType;
+ mWaitEventGroups[*ItType].erase(idContext);
+ }
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+ else
+ Assert(cInserts > 0); /* else: lstEvents has duplicate entries. */
+ }
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cInserts > 0 || lstEvents.size() == 0);
+ RT_NOREF(cInserts);
+
+ /*
+ * Register event in the regular event list.
+ */
+ try
+ {
+ mWaitEvents[idContext] = pEvent;
+ }
+ catch (std::bad_alloc &)
+ {
+ for (GuestEventTypes::const_iterator ItType = lstEvents.begin(); ItType != lstEvents.end(); ++ItType)
+ mWaitEventGroups[*ItType].erase(idContext);
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+
+ RTCritSectLeave(&mWaitEventCritSect);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ *ppEvent = pEvent;
+ return rc;
+ }
+
+ if (pEvent)
+ delete pEvent;
+
+ return rc;
+}
+
+/**
+ * Signals all wait events of a specific type (if found)
+ * and notifies external events accordingly.
+ *
+ * @returns VBox status code.
+ * @param aType Event type to signal.
+ * @param aEvent Which external event to notify.
+ */
+int GuestBase::signalWaitEvent(VBoxEventType_T aType, IEvent *aEvent)
+{
+ int rc = RTCritSectEnter(&mWaitEventCritSect);
+#ifdef DEBUG
+ uint32_t cEvents = 0;
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ GuestEventGroup::iterator itGroup = mWaitEventGroups.find(aType);
+ if (itGroup != mWaitEventGroups.end())
+ {
+ /* Signal all events in the group, leaving the group empty afterwards. */
+ GuestWaitEvents::iterator ItWaitEvt;
+ while ((ItWaitEvt = itGroup->second.begin()) != itGroup->second.end())
+ {
+ LogFlowThisFunc(("Signalling event=%p, type=%ld (CID %#x: Session=%RU32, Object=%RU32, Count=%RU32) ...\n",
+ ItWaitEvt->second, aType, ItWaitEvt->first, VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(ItWaitEvt->first),
+ VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(ItWaitEvt->first), VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(ItWaitEvt->first)));
+
+ int rc2 = ItWaitEvt->second->SignalExternal(aEvent);
+ AssertRC(rc2);
+
+ /* Take down the wait event object details before we erase it from this list and invalid ItGrpEvt. */
+ const GuestEventTypes &EvtTypes = ItWaitEvt->second->Types();
+ uint32_t idContext = ItWaitEvt->first;
+ itGroup->second.erase(ItWaitEvt);
+
+ for (GuestEventTypes::const_iterator ItType = EvtTypes.begin(); ItType != EvtTypes.end(); ++ItType)
+ {
+ GuestEventGroup::iterator EvtTypeGrp = mWaitEventGroups.find(*ItType);
+ if (EvtTypeGrp != mWaitEventGroups.end())
+ {
+ ItWaitEvt = EvtTypeGrp->second.find(idContext);
+ if (ItWaitEvt != EvtTypeGrp->second.end())
+ {
+ LogFlowThisFunc(("Removing event %p (CID %#x) from type %d group\n", ItWaitEvt->second, idContext, *ItType));
+ EvtTypeGrp->second.erase(ItWaitEvt);
+ LogFlowThisFunc(("%zu events left for type %d\n", EvtTypeGrp->second.size(), *ItType));
+ Assert(EvtTypeGrp->second.find(idContext) == EvtTypeGrp->second.end()); /* no duplicates */
+ }
+ }
+ }
+ }
+ }
+
+ int rc2 = RTCritSectLeave(&mWaitEventCritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+#ifdef DEBUG
+ LogFlowThisFunc(("Signalled %RU32 events, rc=%Rrc\n", cEvents, rc));
+#endif
+ return rc;
+}
+
+/**
+ * Signals a wait event which is registered to a specific callback (bound to a context ID (CID)).
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Pointer to host service callback context.
+ * @param rcGuest Guest return code (rc) to set additionally, if rc is set to VERR_GSTCTL_GUEST_ERROR.
+ * @param pPayload Additional wait event payload data set set on return. Optional.
+ */
+int GuestBase::signalWaitEventInternal(PVBOXGUESTCTRLHOSTCBCTX pCbCtx,
+ int rcGuest, const GuestWaitEventPayload *pPayload)
+{
+ if (RT_SUCCESS(rcGuest))
+ return signalWaitEventInternalEx(pCbCtx, VINF_SUCCESS,
+ 0 /* Guest rc */, pPayload);
+
+ return signalWaitEventInternalEx(pCbCtx, VERR_GSTCTL_GUEST_ERROR,
+ rcGuest, pPayload);
+}
+
+/**
+ * Signals a wait event which is registered to a specific callback (bound to a context ID (CID)).
+ * Extended version.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Pointer to host service callback context.
+ * @param rc Return code (rc) to set as wait result.
+ * @param rcGuest Guest return code (rc) to set additionally, if rc is set to VERR_GSTCTL_GUEST_ERROR.
+ * @param pPayload Additional wait event payload data set set on return. Optional.
+ */
+int GuestBase::signalWaitEventInternalEx(PVBOXGUESTCTRLHOSTCBCTX pCbCtx,
+ int rc, int rcGuest,
+ const GuestWaitEventPayload *pPayload)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ /* pPayload is optional. */
+
+ int rc2 = RTCritSectEnter(&mWaitEventCritSect);
+ if (RT_SUCCESS(rc2))
+ {
+ GuestWaitEvents::iterator itEvent = mWaitEvents.find(pCbCtx->uContextID);
+ if (itEvent != mWaitEvents.end())
+ {
+ LogFlowThisFunc(("Signalling event=%p (CID %RU32, rc=%Rrc, rcGuest=%Rrc, pPayload=%p) ...\n",
+ itEvent->second, itEvent->first, rc, rcGuest, pPayload));
+ GuestWaitEvent *pEvent = itEvent->second;
+ AssertPtr(pEvent);
+ rc2 = pEvent->SignalInternal(rc, rcGuest, pPayload);
+ }
+ else
+ rc2 = VERR_NOT_FOUND;
+
+ int rc3 = RTCritSectLeave(&mWaitEventCritSect);
+ if (RT_SUCCESS(rc2))
+ rc2 = rc3;
+ }
+
+ return rc2;
+}
+
+/**
+ * Unregisters (deletes) a wait event.
+ *
+ * After successful unregistration the event will not be valid anymore.
+ *
+ * @returns VBox status code.
+ * @param pWaitEvt Wait event to unregister (delete).
+ */
+int GuestBase::unregisterWaitEvent(GuestWaitEvent *pWaitEvt)
+{
+ if (!pWaitEvt) /* Nothing to unregister. */
+ return VINF_SUCCESS;
+
+ int rc = RTCritSectEnter(&mWaitEventCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowThisFunc(("pWaitEvt=%p\n", pWaitEvt));
+
+/** @todo r=bird: One way of optimizing this would be to use the pointer
+ * instead of the context ID as index into the groups, i.e. revert the value
+ * pair for the GuestWaitEvents type.
+ *
+ * An even more efficent way, would be to not use sexy std::xxx containers for
+ * the types, but iprt/list.h, as that would just be a RTListNodeRemove call for
+ * each type w/o needing to iterate much at all. I.e. add a struct {
+ * RTLISTNODE, GuestWaitEvent *pSelf} array to GuestWaitEvent, and change
+ * GuestEventGroup to std::map<VBoxEventType_T, RTListAnchorClass>
+ * (RTListAnchorClass == RTLISTANCHOR wrapper with a constructor)).
+ *
+ * P.S. the try/catch is now longer needed after I changed pWaitEvt->Types() to
+ * return a const reference rather than a copy of the type list (and it think it
+ * is safe to assume iterators are not hitting the heap). Copy vs reference is
+ * an easy mistake to make in C++.
+ *
+ * P.P.S. The mWaitEventGroups optimization is probably just a lot of extra work
+ * with little payoff.
+ */
+ try
+ {
+ /* Remove the event from all event type groups. */
+ const GuestEventTypes &lstTypes = pWaitEvt->Types();
+ for (GuestEventTypes::const_iterator itType = lstTypes.begin();
+ itType != lstTypes.end(); ++itType)
+ {
+ /** @todo Slow O(n) lookup. Optimize this. */
+ GuestWaitEvents::iterator itCurEvent = mWaitEventGroups[(*itType)].begin();
+ while (itCurEvent != mWaitEventGroups[(*itType)].end())
+ {
+ if (itCurEvent->second == pWaitEvt)
+ {
+ mWaitEventGroups[(*itType)].erase(itCurEvent);
+ break;
+ }
+ ++itCurEvent;
+ }
+ }
+
+ /* Remove the event from the general event list as well. */
+ GuestWaitEvents::iterator itEvent = mWaitEvents.find(pWaitEvt->ContextID());
+
+ Assert(itEvent != mWaitEvents.end());
+ Assert(itEvent->second == pWaitEvt);
+
+ mWaitEvents.erase(itEvent);
+
+ delete pWaitEvt;
+ pWaitEvt = NULL;
+ }
+ catch (const std::exception &ex)
+ {
+ RT_NOREF(ex);
+ AssertFailedStmt(rc = VERR_NOT_FOUND);
+ }
+
+ int rc2 = RTCritSectLeave(&mWaitEventCritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+/**
+ * Waits for an already registered guest wait event.
+ *
+ * @return VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR may be returned, call GuestResult() to get
+ * the actual result.
+ *
+ * @param pWaitEvt Pointer to event to wait for.
+ * @param msTimeout Timeout (in ms) for waiting.
+ * @param pType Event type of following IEvent. Optional.
+ * @param ppEvent Pointer to IEvent which got triggered for this event. Optional.
+ */
+int GuestBase::waitForEvent(GuestWaitEvent *pWaitEvt, uint32_t msTimeout, VBoxEventType_T *pType, IEvent **ppEvent)
+{
+ AssertPtrReturn(pWaitEvt, VERR_INVALID_POINTER);
+ /* pType is optional. */
+ /* ppEvent is optional. */
+
+ int vrc = pWaitEvt->Wait(msTimeout);
+ if (RT_SUCCESS(vrc))
+ {
+ const ComPtr<IEvent> pThisEvent = pWaitEvt->Event();
+ if (pThisEvent.isNotNull()) /* Make sure that we actually have an event associated. */
+ {
+ if (pType)
+ {
+ HRESULT hr = pThisEvent->COMGETTER(Type)(pType);
+ if (FAILED(hr))
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ if ( RT_SUCCESS(vrc)
+ && ppEvent)
+ pThisEvent.queryInterfaceTo(ppEvent);
+
+ unconst(pThisEvent).setNull();
+ }
+ }
+
+ return vrc;
+}
+
+#ifndef VBOX_GUESTCTRL_TEST_CASE
+/**
+ * Convenience function to return a pre-formatted string using an action description and a guest error information.
+ *
+ * @returns Pre-formatted string with a user-friendly error string.
+ * @param strAction Action of when the error occurred.
+ * @param guestErrorInfo Related guest error information to use.
+ */
+/* static */ Utf8Str GuestBase::getErrorAsString(const Utf8Str& strAction, const GuestErrorInfo& guestErrorInfo)
+{
+ Assert(strAction.isNotEmpty());
+ return Utf8StrFmt("%s: %s", strAction.c_str(), getErrorAsString(guestErrorInfo).c_str());
+}
+
+/**
+ * Returns a user-friendly error message from a given GuestErrorInfo object.
+ *
+ * @returns Error message string.
+ * @param guestErrorInfo Guest error info to return error message for.
+ */
+/* static */ Utf8Str GuestBase::getErrorAsString(const GuestErrorInfo& guestErrorInfo)
+{
+ AssertMsg(RT_FAILURE(guestErrorInfo.getRc()), ("Guest rc does not indicate a failure\n"));
+
+ Utf8Str strErr;
+
+#define CASE_TOOL_ERROR(a_eType, a_strTool) \
+ case a_eType: \
+ { \
+ strErr = GuestProcessTool::guestErrorToString(a_strTool, guestErrorInfo); \
+ break; \
+ }
+
+ switch (guestErrorInfo.getType())
+ {
+ case GuestErrorInfo::Type_Session:
+ strErr = GuestSession::i_guestErrorToString(guestErrorInfo.getRc());
+ break;
+
+ case GuestErrorInfo::Type_Process:
+ strErr = GuestProcess::i_guestErrorToString(guestErrorInfo.getRc(), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case GuestErrorInfo::Type_File:
+ strErr = GuestFile::i_guestErrorToString(guestErrorInfo.getRc(), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case GuestErrorInfo::Type_Directory:
+ strErr = GuestDirectory::i_guestErrorToString(guestErrorInfo.getRc(), guestErrorInfo.getWhat().c_str());
+ break;
+
+ CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolCat, VBOXSERVICE_TOOL_CAT);
+ CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolLs, VBOXSERVICE_TOOL_LS);
+ CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolMkDir, VBOXSERVICE_TOOL_MKDIR);
+ CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolMkTemp, VBOXSERVICE_TOOL_MKTEMP);
+ CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolRm, VBOXSERVICE_TOOL_RM);
+ CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolStat, VBOXSERVICE_TOOL_STAT);
+
+ default:
+ AssertMsgFailed(("Type not implemented (type=%RU32, rc=%Rrc)\n", guestErrorInfo.getType(), guestErrorInfo.getRc()));
+ strErr = Utf8StrFmt("Unknown / Not implemented -- Please file a bug report (type=%RU32, rc=%Rrc)\n",
+ guestErrorInfo.getType(), guestErrorInfo.getRc());
+ break;
+ }
+
+ return strErr;
+}
+
+#endif /* VBOX_GUESTCTRL_TEST_CASE */
+
+/**
+ * Converts RTFMODE to FsObjType_T.
+ *
+ * @return Converted FsObjType_T type.
+ * @param fMode RTFMODE to convert.
+ */
+/* static */
+FsObjType_T GuestBase::fileModeToFsObjType(RTFMODE fMode)
+{
+ if (RTFS_IS_FILE(fMode)) return FsObjType_File;
+ else if (RTFS_IS_DIRECTORY(fMode)) return FsObjType_Directory;
+ else if (RTFS_IS_SYMLINK(fMode)) return FsObjType_Symlink;
+
+ return FsObjType_Unknown;
+}
+
+/**
+ * Converts a FsObjType_T to a human-readable string.
+ *
+ * @returns Human-readable string of FsObjType_T.
+ * @param enmType FsObjType_T to convert.
+ */
+/* static */
+const char *GuestBase::fsObjTypeToStr(FsObjType_T enmType)
+{
+ switch (enmType)
+ {
+ case FsObjType_Directory: return "directory";
+ case FsObjType_Symlink: return "symbolic link";
+ case FsObjType_File: return "file";
+ default: break;
+ }
+
+ return "unknown";
+}
+
+/**
+ * Converts a PathStyle_T to a human-readable string.
+ *
+ * @returns Human-readable string of PathStyle_T.
+ * @param enmPathStyle PathStyle_T to convert.
+ */
+/* static */
+const char *GuestBase::pathStyleToStr(PathStyle_T enmPathStyle)
+{
+ switch (enmPathStyle)
+ {
+ case PathStyle_DOS: return "DOS";
+ case PathStyle_UNIX: return "UNIX";
+ case PathStyle_Unknown: return "Unknown";
+ default: break;
+ }
+
+ return "<invalid>";
+}
+
+GuestObject::GuestObject(void)
+ : mSession(NULL),
+ mObjectID(0)
+{
+}
+
+GuestObject::~GuestObject(void)
+{
+}
+
+/**
+ * Binds this guest (control) object to a specific guest (control) session.
+ *
+ * @returns VBox status code.
+ * @param pConsole Pointer to console object to use.
+ * @param pSession Pointer to session to bind this object to.
+ * @param uObjectID Object ID for this object to use within that specific session.
+ * Each object ID must be unique per session.
+ */
+int GuestObject::bindToSession(Console *pConsole, GuestSession *pSession, uint32_t uObjectID)
+{
+ AssertPtrReturn(pConsole, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ mConsole = pConsole;
+ mSession = pSession;
+ mObjectID = uObjectID;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Registers (creates) a new wait event.
+ *
+ * @returns VBox status code.
+ * @param lstEvents List of events which the new wait event gets triggered at.
+ * @param ppEvent Returns the new wait event on success.
+ */
+int GuestObject::registerWaitEvent(const GuestEventTypes &lstEvents,
+ GuestWaitEvent **ppEvent)
+{
+ AssertPtr(mSession);
+ return GuestBase::registerWaitEventEx(mSession->i_getId(), mObjectID, lstEvents, ppEvent);
+}
+
+/**
+ * Sends a HGCM message to the guest (via the guest control host service).
+ *
+ * @returns VBox status code.
+ * @param uMessage Message ID of message to send.
+ * @param cParms Number of HGCM message parameters to send.
+ * @param paParms Array of HGCM message parameters to send.
+ */
+int GuestObject::sendMessage(uint32_t uMessage, uint32_t cParms, PVBOXHGCMSVCPARM paParms)
+{
+#ifndef VBOX_GUESTCTRL_TEST_CASE
+ ComObjPtr<Console> pConsole = mConsole;
+ Assert(!pConsole.isNull());
+
+ int vrc = VERR_HGCM_SERVICE_NOT_FOUND;
+
+ /* Forward the information to the VMM device. */
+ VMMDev *pVMMDev = pConsole->i_getVMMDev();
+ if (pVMMDev)
+ {
+ /* HACK ALERT! We extend the first parameter to 64-bit and use the
+ two topmost bits for call destination information. */
+ Assert(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT);
+ paParms[0].type = VBOX_HGCM_SVC_PARM_64BIT;
+ paParms[0].u.uint64 = (uint64_t)paParms[0].u.uint32 | VBOX_GUESTCTRL_DST_SESSION;
+
+ /* Make the call. */
+ LogFlowThisFunc(("uMessage=%RU32, cParms=%RU32\n", uMessage, cParms));
+ vrc = pVMMDev->hgcmHostCall(HGCMSERVICE_NAME, uMessage, cParms, paParms);
+ if (RT_FAILURE(vrc))
+ {
+ /** @todo What to do here? */
+ }
+ }
+#else
+ LogFlowThisFuncEnter();
+
+ /* Not needed within testcases. */
+ RT_NOREF(uMessage, cParms, paParms);
+ int vrc = VINF_SUCCESS;
+#endif
+ return vrc;
+}
+
+GuestWaitEventBase::GuestWaitEventBase(void)
+ : mfAborted(false),
+ mCID(0),
+ mEventSem(NIL_RTSEMEVENT),
+ mRc(VINF_SUCCESS),
+ mGuestRc(VINF_SUCCESS)
+{
+}
+
+GuestWaitEventBase::~GuestWaitEventBase(void)
+{
+ if (mEventSem != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(mEventSem);
+ mEventSem = NIL_RTSEMEVENT;
+ }
+}
+
+/**
+ * Initializes a wait event with a specific context ID (CID).
+ *
+ * @returns VBox status code.
+ * @param uCID Context ID (CID) to initialize wait event with.
+ */
+int GuestWaitEventBase::Init(uint32_t uCID)
+{
+ mCID = uCID;
+
+ return RTSemEventCreate(&mEventSem);
+}
+
+/**
+ * Signals a wait event.
+ *
+ * @returns VBox status code.
+ * @param rc Return code (rc) to set as wait result.
+ * @param rcGuest Guest return code (rc) to set additionally, if rc is set to VERR_GSTCTL_GUEST_ERROR.
+ * @param pPayload Additional wait event payload data set set on return. Optional.
+ */
+int GuestWaitEventBase::SignalInternal(int rc, int rcGuest,
+ const GuestWaitEventPayload *pPayload)
+{
+ if (mfAborted)
+ return VERR_CANCELLED;
+
+#ifdef VBOX_STRICT
+ if (rc == VERR_GSTCTL_GUEST_ERROR)
+ AssertMsg(RT_FAILURE(rcGuest), ("Guest error indicated but no actual guest error set (%Rrc)\n", rcGuest));
+ else
+ AssertMsg(RT_SUCCESS(rcGuest), ("No guest error indicated but actual guest error set (%Rrc)\n", rcGuest));
+#endif
+
+ int rc2;
+ if (pPayload)
+ rc2 = mPayload.CopyFromDeep(*pPayload);
+ else
+ rc2 = VINF_SUCCESS;
+ if (RT_SUCCESS(rc2))
+ {
+ mRc = rc;
+ mGuestRc = rcGuest;
+
+ rc2 = RTSemEventSignal(mEventSem);
+ }
+
+ return rc2;
+}
+
+/**
+ * Waits for the event to get triggered. Will return success if the
+ * wait was successufl (e.g. was being triggered), otherwise an error will be returned.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR may be returned, call GuestResult() to get
+ * the actual result.
+ *
+ * @param msTimeout Timeout (in ms) to wait.
+ * Specifiy 0 to wait indefinitely.
+ */
+int GuestWaitEventBase::Wait(RTMSINTERVAL msTimeout)
+{
+ int rc = VINF_SUCCESS;
+
+ if (mfAborted)
+ rc = VERR_CANCELLED;
+
+ if (RT_SUCCESS(rc))
+ {
+ AssertReturn(mEventSem != NIL_RTSEMEVENT, VERR_CANCELLED);
+
+ rc = RTSemEventWait(mEventSem, msTimeout ? msTimeout : RT_INDEFINITE_WAIT);
+ if ( RT_SUCCESS(rc)
+ && mfAborted)
+ {
+ rc = VERR_CANCELLED;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* If waiting succeeded, return the overall
+ * result code. */
+ rc = mRc;
+ }
+ }
+
+ return rc;
+}
+
+GuestWaitEvent::GuestWaitEvent(void)
+{
+}
+
+GuestWaitEvent::~GuestWaitEvent(void)
+{
+
+}
+
+/**
+ * Cancels the event.
+ */
+int GuestWaitEvent::Cancel(void)
+{
+ if (mfAborted) /* Already aborted? */
+ return VINF_SUCCESS;
+
+ mfAborted = true;
+
+#ifdef DEBUG_andy
+ LogFlowThisFunc(("Cancelling %p ...\n"));
+#endif
+ return RTSemEventSignal(mEventSem);
+}
+
+/**
+ * Initializes a wait event with a given context ID (CID).
+ *
+ * @returns VBox status code.
+ * @param uCID Context ID to initialize wait event with.
+ */
+int GuestWaitEvent::Init(uint32_t uCID)
+{
+ return GuestWaitEventBase::Init(uCID);
+}
+
+/**
+ * Initializes a wait event with a given context ID (CID) and a list of event types to wait for.
+ *
+ * @returns VBox status code.
+ * @param uCID Context ID to initialize wait event with.
+ * @param lstEvents List of event types to wait for this wait event to get signalled.
+ */
+int GuestWaitEvent::Init(uint32_t uCID, const GuestEventTypes &lstEvents)
+{
+ int rc = GuestWaitEventBase::Init(uCID);
+ if (RT_SUCCESS(rc))
+ {
+ mEventTypes = lstEvents;
+ }
+
+ return rc;
+}
+
+/**
+ * Signals the event.
+ *
+ * @return VBox status code.
+ * @param pEvent Public IEvent to associate.
+ * Optional.
+ */
+int GuestWaitEvent::SignalExternal(IEvent *pEvent)
+{
+ if (pEvent)
+ mEvent = pEvent;
+
+ return RTSemEventSignal(mEventSem);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// GuestPath
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Builds a (final) destination path from a given source + destination path.
+ *
+ * This does not utilize any file system access whatsoever. Used for guest and host paths.
+ *
+ * @returns VBox status code.
+ * @param strSrcPath Source path to build destination path for.
+ * @param enmSrcPathStyle Path style the source path is in.
+ * @param strDstPath Destination path to use for building the (final) destination path.
+ * @param enmDstPathStyle Path style the destination path is in.
+ *
+ * @note See rules within the function.
+ */
+/* static */
+int GuestPath::BuildDestinationPath(const Utf8Str &strSrcPath, PathStyle_T enmSrcPathStyle,
+ Utf8Str &strDstPath, PathStyle_T enmDstPathStyle)
+{
+ /*
+ * Rules:
+ *
+ * # source dest final dest remarks
+ *
+ * 1 /src/path1/ /dst/path2/ /dst/path2/<contents of path1> Just copies contents of <contents of path1>, not the path1 itself.
+ * 2 /src/path1 /dst/path2/ /dst/path2/path1 Copies path1 into path2.
+ * 3 /src/path1 /dst/path2 /dst/path2 Overwrites stuff from path2 with stuff from path1.
+ * 4 Dotdot ("..") directories are forbidden for security reasons.
+ */
+ const char *pszSrcName = RTPathFilenameEx(strSrcPath.c_str(),
+ enmSrcPathStyle == PathStyle_DOS
+ ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX);
+
+ const char *pszDstName = RTPathFilenameEx(strDstPath.c_str(),
+ enmDstPathStyle == PathStyle_DOS
+ ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX);
+
+ if ( (!pszSrcName && !pszDstName) /* #1 */
+ || ( pszSrcName && pszDstName)) /* #3 */
+ {
+ /* Note: Must have DirectoryFlag_CopyIntoExisting + FileFlag_NoReplace *not* set. */
+ }
+ else if (pszSrcName && !pszDstName) /* #2 */
+ {
+ if (!strDstPath.endsWith(PATH_STYLE_SEP_STR(enmDstPathStyle)))
+ strDstPath += PATH_STYLE_SEP_STR(enmDstPathStyle);
+ strDstPath += pszSrcName;
+ }
+
+ /* Translate the built destination path to a path compatible with the destination. */
+ int vrc = GuestPath::Translate(strDstPath, enmSrcPathStyle, enmDstPathStyle);
+ if (RT_SUCCESS(vrc))
+ {
+ union
+ {
+ RTPATHPARSED Parsed;
+ RTPATHSPLIT Split;
+ uint8_t ab[4096];
+ } u;
+ vrc = RTPathParse(strDstPath.c_str(), &u.Parsed, sizeof(u), enmDstPathStyle == PathStyle_DOS
+ ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX);
+ if (RT_SUCCESS(vrc))
+ {
+ if (u.Parsed.fProps & RTPATH_PROP_DOTDOT_REFS) /* #4 */
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ }
+
+ LogRel2(("Guest Control: Building destination path for '%s' (%s) -> '%s' (%s): %Rrc\n",
+ strSrcPath.c_str(), GuestBase::pathStyleToStr(enmSrcPathStyle),
+ strDstPath.c_str(), GuestBase::pathStyleToStr(enmDstPathStyle), vrc));
+
+ return vrc;
+}
+
+/**
+ * Translates a path from a specific path style into another.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NOT_SUPPORTED if a conversion is not supported.
+ * @retval VERR_NOT_IMPLEMENTED if path style conversion is not implemented yet.
+ * @param strPath Path to translate. Will contain the translated path on success. UTF-8 only.
+ * @param enmSrcPathStyle Source path style \a strPath is expected in.
+ * @param enmDstPathStyle Destination path style to convert to.
+ * @param fForce Whether to force the translation to the destination path style or not.
+ *
+ * @note This does NOT remove any trailing slashes and/or perform file system lookups!
+ */
+/* static */
+int GuestPath::Translate(Utf8Str &strPath, PathStyle_T enmSrcPathStyle, PathStyle_T enmDstPathStyle, bool fForce /* = false */)
+{
+ if (strPath.isEmpty())
+ return VINF_SUCCESS;
+
+ AssertReturn(RTStrIsValidEncoding(strPath.c_str()), VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+
+ Utf8Str strTranslated;
+
+ if ( ( enmSrcPathStyle == PathStyle_DOS
+ && enmDstPathStyle == PathStyle_UNIX)
+ || (fForce && enmDstPathStyle == PathStyle_UNIX))
+ {
+ strTranslated = strPath;
+ RTPathChangeToUnixSlashes(strTranslated.mutableRaw(), true /* fForce */);
+ }
+ else if ( ( enmSrcPathStyle == PathStyle_UNIX
+ && enmDstPathStyle == PathStyle_DOS)
+ || (fForce && enmDstPathStyle == PathStyle_DOS))
+
+ {
+ strTranslated = strPath;
+ RTPathChangeToDosSlashes(strTranslated.mutableRaw(), true /* fForce */);
+ }
+
+ if ( strTranslated.isEmpty() /* Not forced. */
+ && enmSrcPathStyle == enmDstPathStyle)
+ {
+ strTranslated = strPath;
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Guest Control: Translating path '%s' (%s) -> '%s' (%s) failed, vrc=%Rrc\n",
+ strPath.c_str(), GuestBase::pathStyleToStr(enmSrcPathStyle),
+ strTranslated.c_str(), GuestBase::pathStyleToStr(enmDstPathStyle), vrc));
+ return vrc;
+ }
+
+ /* Cleanup. */
+ const char *psz = strTranslated.mutableRaw();
+ size_t const cch = strTranslated.length();
+ size_t off = 0;
+ while (off < cch)
+ {
+ if (off + 1 > cch)
+ break;
+ /* Remove double back slashes (DOS only). */
+ if ( enmDstPathStyle == PathStyle_DOS
+ && psz[off] == '\\'
+ && psz[off + 1] == '\\')
+ {
+ strTranslated.erase(off + 1, 1);
+ off++;
+ }
+ /* Remove double forward slashes (UNIX only). */
+ if ( enmDstPathStyle == PathStyle_UNIX
+ && psz[off] == '/'
+ && psz[off + 1] == '/')
+ {
+ strTranslated.erase(off + 1, 1);
+ off++;
+ }
+ off++;
+ }
+
+ /* Note: Do not trim() paths here, as technically it's possible to create paths with trailing spaces. */
+
+ strTranslated.jolt();
+
+ LogRel2(("Guest Control: Translating '%s' (%s) -> '%s' (%s): %Rrc\n",
+ strPath.c_str(), GuestBase::pathStyleToStr(enmSrcPathStyle),
+ strTranslated.c_str(), GuestBase::pathStyleToStr(enmDstPathStyle), vrc));
+
+ if (RT_SUCCESS(vrc))
+ strPath = strTranslated;
+
+ return vrc;
+}
+
diff --git a/src/VBox/Main/src-client/GuestDirectoryImpl.cpp b/src/VBox/Main/src-client/GuestDirectoryImpl.cpp
new file mode 100644
index 00000000..77554ae1
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestDirectoryImpl.cpp
@@ -0,0 +1,501 @@
+/* $Id: GuestDirectoryImpl.cpp $ */
+/** @file
+ * VirtualBox Main - Guest directory handling.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_GUESTDIRECTORY
+#include "LoggingNew.h"
+
+#ifndef VBOX_WITH_GUEST_CONTROL
+# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
+#endif
+#include "GuestDirectoryImpl.h"
+#include "GuestSessionImpl.h"
+#include "GuestCtrlImplPrivate.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+
+#include <VBox/com/array.h>
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(GuestDirectory)
+
+HRESULT GuestDirectory::FinalConstruct(void)
+{
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void GuestDirectory::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+int GuestDirectory::init(Console *pConsole, GuestSession *pSession, ULONG aObjectID, const GuestDirectoryOpenInfo &openInfo)
+{
+ LogFlowThisFunc(("pConsole=%p, pSession=%p, aObjectID=%RU32, strPath=%s, strFilter=%s, uFlags=%x\n",
+ pConsole, pSession, aObjectID, openInfo.mPath.c_str(), openInfo.mFilter.c_str(), openInfo.mFlags));
+
+ AssertPtrReturn(pConsole, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED);
+
+ int vrc = bindToSession(pConsole, pSession, aObjectID);
+ if (RT_SUCCESS(vrc))
+ {
+ mSession = pSession;
+ mObjectID = aObjectID;
+
+ mData.mOpenInfo = openInfo;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Start the directory process on the guest. */
+ GuestProcessStartupInfo procInfo;
+ procInfo.mName.printf(tr("Opening directory \"%s\""), openInfo.mPath.c_str());
+ procInfo.mTimeoutMS = 5 * 60 * 1000; /* 5 minutes timeout. */
+ procInfo.mFlags = ProcessCreateFlag_WaitForStdOut;
+ procInfo.mExecutable= Utf8Str(VBOXSERVICE_TOOL_LS);
+
+ procInfo.mArguments.push_back(procInfo.mExecutable);
+ procInfo.mArguments.push_back(Utf8Str("--machinereadable"));
+ /* We want the long output format which contains all the object details. */
+ procInfo.mArguments.push_back(Utf8Str("-l"));
+#if 0 /* Flags are not supported yet. */
+ if (uFlags & DirectoryOpenFlag_NoSymlinks)
+ procInfo.mArguments.push_back(Utf8Str("--nosymlinks")); /** @todo What does GNU here? */
+#endif
+ /** @todo Recursion support? */
+ procInfo.mArguments.push_back(openInfo.mPath); /* The directory we want to open. */
+
+ /*
+ * Start the process synchronously and keep it around so that we can use
+ * it later in subsequent read() calls.
+ */
+ vrc = mData.mProcessTool.init(mSession, procInfo, false /* Async */, NULL /* Guest rc */);
+ if (RT_SUCCESS(vrc))
+ {
+ /* As we need to know if the directory we were about to open exists and and is accessible,
+ * do the first read here in order to return a meaningful status here. */
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ vrc = i_readInternal(mData.mObjData, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ /*
+ * We need to actively terminate our process tool in case of an error here,
+ * as this otherwise would be done on (directory) object destruction implicitly.
+ * This in turn then will run into a timeout, as the directory object won't be
+ * around anymore at that time. Ugly, but that's how it is for the moment.
+ */
+ int vrcTerm = mData.mProcessTool.terminate(30 * RT_MS_1SEC, NULL /* prcGuest */);
+ AssertRC(vrcTerm);
+
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ vrc = vrcGuest;
+ }
+ }
+ }
+
+ /* Confirm a successful initialization when it's the case. */
+ if (RT_SUCCESS(vrc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void GuestDirectory::uninit(void)
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFuncLeave();
+}
+
+// implementation of private wrapped getters/setters for attributes
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDirectory::getDirectoryName(com::Utf8Str &aDirectoryName)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aDirectoryName = mData.mOpenInfo.mPath;
+
+ return S_OK;
+}
+
+HRESULT GuestDirectory::getFilter(com::Utf8Str &aFilter)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aFilter = mData.mOpenInfo.mFilter;
+
+ return S_OK;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Entry point for guest side directory callbacks.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCb Host callback data.
+ */
+int GuestDirectory::i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("strPath=%s, uContextID=%RU32, uFunction=%RU32, pSvcCb=%p\n",
+ mData.mOpenInfo.mPath.c_str(), pCbCtx->uContextID, pCbCtx->uMessage, pSvcCb));
+
+ int vrc;
+ switch (pCbCtx->uMessage)
+ {
+ case GUEST_MSG_DIR_NOTIFY:
+ {
+ int idx = 1; /* Current parameter index. */
+ CALLBACKDATA_DIR_NOTIFY dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ HGCMSvcGetU32(&pSvcCb->mpaParms[idx++], &dataCb.uType);
+ HGCMSvcGetU32(&pSvcCb->mpaParms[idx++], &dataCb.rc);
+
+ LogFlowFunc(("uType=%RU32, vrcGguest=%Rrc\n", dataCb.uType, (int)dataCb.rc));
+
+ switch (dataCb.uType)
+ {
+ /* Nothing here yet, nothing to dispatch further. */
+
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ break;
+ }
+
+ default:
+ /* Silently ignore not implemented functions. */
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Converts a given guest directory error to a string.
+ *
+ * @returns Error string.
+ * @param rcGuest Guest file error to return string for.
+ * @param pcszWhat Hint of what was involved when the error occurred.
+ */
+/* static */
+Utf8Str GuestDirectory::i_guestErrorToString(int rcGuest, const char *pcszWhat)
+{
+ AssertPtrReturn(pcszWhat, "");
+
+ Utf8Str strErr;
+ switch (rcGuest)
+ {
+#define CASE_MSG(a_iRc, ...) \
+ case a_iRc: strErr.printf(__VA_ARGS__); break;
+ CASE_MSG(VERR_CANT_CREATE , tr("Access to guest directory \"%s\" is denied"), pcszWhat);
+ CASE_MSG(VERR_DIR_NOT_EMPTY, tr("Guest directory \"%s\" is not empty"), pcszWhat);
+ default:
+ strErr.printf(tr("Error %Rrc for guest directory \"%s\" occurred\n"), rcGuest, pcszWhat);
+ break;
+ }
+
+#undef CASE_MSG
+
+ return strErr;
+}
+
+/**
+ * @copydoc GuestObject::i_onUnregister
+ */
+int GuestDirectory::i_onUnregister(void)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc = VINF_SUCCESS;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * @copydoc GuestObject::i_onSessionStatusChange
+ */
+int GuestDirectory::i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus)
+{
+ RT_NOREF(enmSessionStatus);
+
+ LogFlowThisFuncEnter();
+
+ int vrc = VINF_SUCCESS;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Closes this guest directory and removes it from the
+ * guest session's directory list.
+ *
+ * @return VBox status code.
+ * @param prcGuest Where to store the guest result code in case VERR_GSTCTL_GUEST_ERROR is returned.
+ */
+int GuestDirectory::i_closeInternal(int *prcGuest)
+{
+ AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
+
+ int vrc = mData.mProcessTool.terminate(30 * 1000 /* 30s timeout */, prcGuest);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ AssertPtr(mSession);
+ int vrc2 = mSession->i_directoryUnregister(this);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ LogFlowThisFunc(("Returning vrc=%Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Reads the next directory entry, internal version.
+ *
+ * @return VBox status code. Will return VERR_NO_MORE_FILES if no more entries are available.
+ * @param objData Where to store the read directory entry as internal object data.
+ * @param prcGuest Where to store the guest result code in case VERR_GSTCTL_GUEST_ERROR is returned.
+ */
+int GuestDirectory::i_readInternal(GuestFsObjData &objData, int *prcGuest)
+{
+ AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
+
+ GuestProcessStreamBlock curBlock;
+ int vrc = mData.mProcessTool.waitEx(GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK, &curBlock, prcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Note: The guest process can still be around to serve the next
+ * upcoming stream block next time.
+ */
+ if (!mData.mProcessTool.isRunning())
+ vrc = mData.mProcessTool.getTerminationStatus(); /* Tool process is not running (anymore). Check termination status. */
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (curBlock.GetCount()) /* Did we get content? */
+ {
+ if (curBlock.GetString("name"))
+ {
+ vrc = objData.FromLs(curBlock, true /* fLong */);
+ }
+ else
+ vrc = VERR_PATH_NOT_FOUND;
+ }
+ else
+ {
+ /* Nothing to read anymore. Tell the caller. */
+ vrc = VERR_NO_MORE_FILES;
+ }
+ }
+ }
+
+ LogFlowThisFunc(("Returning vrc=%Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Reads the next directory entry.
+ *
+ * @return VBox status code. Will return VERR_NO_MORE_FILES if no more entries are available.
+ * @param fsObjInfo Where to store the read directory entry.
+ * @param prcGuest Where to store the guest result code in case VERR_GSTCTL_GUEST_ERROR is returned.
+ */
+int GuestDirectory::i_read(ComObjPtr<GuestFsObjInfo> &fsObjInfo, int *prcGuest)
+{
+ AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
+
+ /* Create the FS info object. */
+ HRESULT hr = fsObjInfo.createObject();
+ if (FAILED(hr))
+ return VERR_COM_UNEXPECTED;
+
+ int vrc;
+
+ /* If we have a valid object data cache, read from it. */
+ if (mData.mObjData.mName.isNotEmpty())
+ {
+ vrc = fsObjInfo->init(mData.mObjData);
+ if (RT_SUCCESS(vrc))
+ {
+ mData.mObjData.mName = ""; /* Mark the object data as being empty (beacon). */
+ }
+ }
+ else /* Otherwise ask the guest for the next object data (block). */
+ {
+
+ GuestFsObjData objData;
+ vrc = i_readInternal(objData, prcGuest);
+ if (RT_SUCCESS(vrc))
+ vrc = fsObjInfo->init(objData);
+ }
+
+ LogFlowThisFunc(("Returning vrc=%Rrc\n", vrc));
+ return vrc;
+}
+
+// implementation of public methods
+/////////////////////////////////////////////////////////////////////////////
+HRESULT GuestDirectory::close()
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_closeInternal(&vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Directory, vrcGuest, mData.mOpenInfo.mPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Closing guest directory failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ case VERR_NOT_SUPPORTED:
+ /* Silently skip old Guest Additions which do not support killing the
+ * the guest directory handling process. */
+ break;
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Closing guest directory \"%s\" failed: %Rrc"), mData.mOpenInfo.mPath.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestDirectory::read(ComPtr<IFsObjInfo> &aObjInfo)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ ComObjPtr<GuestFsObjInfo> fsObjInfo;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_read(fsObjInfo, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Return info object to the caller. */
+ hrc = fsObjInfo.queryInterfaceTo(aObjInfo.asOutParam());
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolLs, vrcGuest, mData.mOpenInfo.mPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Reading guest directory failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ case VERR_GSTCTL_PROCESS_EXIT_CODE:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading guest directory \"%s\" failed: %Rrc"),
+ mData.mOpenInfo.mPath.c_str(), mData.mProcessTool.getRc());
+ break;
+
+ case VERR_PATH_NOT_FOUND:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading guest directory \"%s\" failed: Path not found"),
+ mData.mOpenInfo.mPath.c_str());
+ break;
+
+ case VERR_NO_MORE_FILES:
+ /* See SDK reference. */
+ hrc = setErrorBoth(VBOX_E_OBJECT_NOT_FOUND, vrc, tr("Reading guest directory \"%s\" failed: No more entries"),
+ mData.mOpenInfo.mPath.c_str());
+ break;
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading guest directory \"%s\" returned error: %Rrc\n"),
+ mData.mOpenInfo.mPath.c_str(), vrc);
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("Returning hrc=%Rhrc / vrc=%Rrc\n", hrc, vrc));
+ return hrc;
+}
+
diff --git a/src/VBox/Main/src-client/GuestDnDPrivate.cpp b/src/VBox/Main/src-client/GuestDnDPrivate.cpp
new file mode 100644
index 00000000..12715fac
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestDnDPrivate.cpp
@@ -0,0 +1,1642 @@
+/* $Id: GuestDnDPrivate.cpp $ */
+/** @file
+ * Private guest drag and drop code, used by GuestDnDTarget + GuestDnDSource.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_GUEST_DND
+#include "LoggingNew.h"
+
+#include "GuestImpl.h"
+#include "AutoCaller.h"
+
+#ifdef VBOX_WITH_DRAG_AND_DROP
+# include "ConsoleImpl.h"
+# include "ProgressImpl.h"
+# include "GuestDnDPrivate.h"
+
+# include <algorithm>
+
+# include <iprt/dir.h>
+# include <iprt/path.h>
+# include <iprt/stream.h>
+# include <iprt/semaphore.h>
+# include <iprt/cpp/utils.h>
+
+# include <VMMDev.h>
+
+# include <VBox/GuestHost/DragAndDrop.h>
+# include <VBox/HostServices/DragAndDropSvc.h>
+# include <VBox/version.h>
+
+/** @page pg_main_dnd Dungeons & Dragons - Overview
+ * Overview:
+ *
+ * Drag and Drop is handled over the internal HGCM service for the host <->
+ * guest communication. Beside that we need to map the Drag and Drop protocols
+ * of the various OS's we support to our internal channels, this is also highly
+ * communicative in both directions. Unfortunately HGCM isn't really designed
+ * for that. Next we have to foul some of the components. This includes to
+ * trick X11 on the guest side, but also Qt needs to be tricked on the host
+ * side a little bit.
+ *
+ * The following components are involved:
+ *
+ * 1. GUI: Uses the Qt classes for Drag and Drop and mainly forward the content
+ * of it to the Main IGuest / IGuestDnDSource / IGuestDnDTarget interfaces.
+ * 2. Main: Public interface for doing Drag and Drop. Also manage the IProgress
+ * interfaces for blocking the caller by showing a progress dialog (see
+ * this file).
+ * 3. HGCM service: Handle all messages from the host to the guest at once and
+ * encapsulate the internal communication details (see dndmanager.cpp and
+ * friends).
+ * 4. Guest Additions: Split into the platform neutral part (see
+ * VBoxGuestR3LibDragAndDrop.cpp) and the guest OS specific parts.
+ * Receive/send message from/to the HGCM service and does all guest specific
+ * operations. For Windows guests VBoxTray is in charge, whereas on UNIX-y guests
+ * VBoxClient will be used.
+ *
+ * Terminology:
+ *
+ * All transfers contain a MIME format and according meta data. This meta data then can
+ * be interpreted either as raw meta data or something else. When raw meta data is
+ * being handled, this gets passed through to the destination (guest / host) without
+ * modification. Other meta data (like URI lists) can and will be modified by the
+ * receiving side before passing to OS. How and when modifications will be applied
+ * depends on the MIME format.
+ *
+ * Host -> Guest:
+ * 1. There are DnD Enter, Move, Leave events which are send exactly like this
+ * to the guest. The info includes the position, MIME types and allowed actions.
+ * The guest has to respond with an action it would accept, so the GUI could
+ * change the cursor accordingly.
+ * 2. On drop, first a drop event is sent. If this is accepted a drop data
+ * event follows. This blocks the GUI and shows some progress indicator.
+ *
+ * Guest -> Host:
+ * 1. The GUI is asking the guest if a DnD event is pending when the user moves
+ * the cursor out of the view window. If so, this returns the mimetypes and
+ * allowed actions.
+ * (2. On every mouse move this is asked again, to make sure the DnD event is
+ * still valid.)
+ * 3. On drop the host request the data from the guest. This blocks the GUI and
+ * shows some progress indicator.
+ *
+ * Implementation hints:
+ * m_strSupportedFormats here in this file defines the allowed mime-types.
+ * This is necessary because we need special handling for some of the
+ * mime-types. E.g. for URI lists we need to transfer the actual dirs and
+ * files. Text EOL may to be changed. Also unknown mime-types may need special
+ * handling as well, which may lead to undefined behavior in the host/guest, if
+ * not done.
+ *
+ * Dropping of a directory, means recursively transferring _all_ the content.
+ *
+ * Directories and files are placed into the user's temporary directory on the
+ * guest (e.g. /tmp/VirtualBox Dropped Files). We can't delete them after the
+ * DnD operation, because we didn't know what the DnD target does with it. E.g.
+ * it could just be opened in place. This could lead ofc to filling up the disk
+ * within the guest. To inform the user about this, a small app could be
+ * developed which scans this directory regularly and inform the user with a
+ * tray icon hint (and maybe the possibility to clean this up instantly). The
+ * same has to be done in the G->H direction when it is implemented.
+ *
+ * Only regular files are supported; symlinks are not allowed.
+ *
+ * Transfers currently are an all-succeed or all-fail operation (see todos).
+ *
+ * On MacOS hosts we had to implement own DnD "promises" support for file transfers,
+ * as Qt does not support this out-of-the-box.
+ *
+ * The code tries to preserve the file modes of the transfered directories / files.
+ * This is useful (and maybe necessary) for two things:
+ * 1. If a file is executable, it should be also after the transfer, so the
+ * user can just execute it, without manually tweaking the modes first.
+ * 2. If a dir/file is not accessible by group/others in the host, it shouldn't
+ * be in the guest.
+ * In any case, the user mode is always set to rwx (so that we can access it
+ * ourself, in e.g. for a cleanup case after cancel).
+ *
+ * ACEs / ACLs currently are not supported.
+ *
+ * Cancelling ongoing transfers is supported in both directions by the guest
+ * and/or host side and cleans up all previous steps. This also involves
+ * removing partially transferred directories / files in the temporary directory.
+ *
+ ** @todo
+ * - ESC doesn't really work (on Windows guests it's already implemented)
+ * ... in any case it seems a little bit difficult to handle from the Qt side.
+ * - Transfers currently do not have any interactive (UI) callbacks / hooks which
+ * e.g. would allow to skip / replace / rename and entry, or abort the operation on failure.
+ * - Add support for more MIME types (especially images, csv)
+ * - Test unusual behavior:
+ * - DnD service crash in the guest during a DnD op (e.g. crash of VBoxClient or X11)
+ * - Not expected order of the events between HGCM and the guest
+ * - Security considerations: We transfer a lot of memory between the guest and
+ * the host and even allow the creation of dirs/files. Maybe there should be
+ * limits introduced to preventing DoS attacks or filling up all the memory
+ * (both in the host and the guest).
+ */
+
+
+/*********************************************************************************************************************************
+ * Internal macros. *
+ ********************************************************************************************************************************/
+
+/** Tries locking the GuestDnD object and returns on failure. */
+#define GUESTDND_LOCK() \
+ { \
+ int rcLock = RTCritSectEnter(&m_CritSect); \
+ if (RT_FAILURE(rcLock)) \
+ return rcLock; \
+ }
+
+/** Tries locking the GuestDnD object and returns a_Ret failure. */
+#define GUESTDND_LOCK_RET(a_Ret) \
+ { \
+ int rcLock = RTCritSectEnter(&m_CritSect); \
+ if (RT_FAILURE(rcLock)) \
+ return a_Ret; \
+ }
+
+/** Unlocks a formerly locked GuestDnD object. */
+#define GUESTDND_UNLOCK() \
+ { \
+ int rcUnlock = RTCritSectLeave(&m_CritSect); RT_NOREF(rcUnlock); \
+ AssertRC(rcUnlock); \
+ }
+
+/*********************************************************************************************************************************
+ * GuestDnDSendCtx implementation. *
+ ********************************************************************************************************************************/
+
+GuestDnDSendCtx::GuestDnDSendCtx(void)
+ : pTarget(NULL)
+ , pState(NULL)
+{
+ reset();
+}
+
+/**
+ * Resets a GuestDnDSendCtx object.
+ */
+void GuestDnDSendCtx::reset(void)
+{
+ uScreenID = 0;
+
+ Transfer.reset();
+
+ int rc2 = EventCallback.Reset();
+ AssertRC(rc2);
+
+ GuestDnDData::reset();
+}
+
+/*********************************************************************************************************************************
+ * GuestDnDRecvCtx implementation. *
+ ********************************************************************************************************************************/
+
+GuestDnDRecvCtx::GuestDnDRecvCtx(void)
+ : pSource(NULL)
+ , pState(NULL)
+{
+ reset();
+}
+
+/**
+ * Resets a GuestDnDRecvCtx object.
+ */
+void GuestDnDRecvCtx::reset(void)
+{
+ lstFmtOffered.clear();
+ strFmtReq = "";
+ strFmtRecv = "";
+ enmAction = 0;
+
+ Transfer.reset();
+
+ int rc2 = EventCallback.Reset();
+ AssertRC(rc2);
+
+ GuestDnDData::reset();
+}
+
+/*********************************************************************************************************************************
+ * GuestDnDCallbackEvent implementation. *
+ ********************************************************************************************************************************/
+
+GuestDnDCallbackEvent::~GuestDnDCallbackEvent(void)
+{
+ if (NIL_RTSEMEVENT != m_SemEvent)
+ RTSemEventDestroy(m_SemEvent);
+}
+
+/**
+ * Resets a GuestDnDCallbackEvent object.
+ */
+int GuestDnDCallbackEvent::Reset(void)
+{
+ int rc = VINF_SUCCESS;
+
+ if (NIL_RTSEMEVENT == m_SemEvent)
+ rc = RTSemEventCreate(&m_SemEvent);
+
+ m_Rc = VINF_SUCCESS;
+ return rc;
+}
+
+/**
+ * Completes a callback event by notifying the waiting side.
+ *
+ * @returns VBox status code.
+ * @param rc Result code to use for the event completion.
+ */
+int GuestDnDCallbackEvent::Notify(int rc /* = VINF_SUCCESS */)
+{
+ m_Rc = rc;
+ return RTSemEventSignal(m_SemEvent);
+}
+
+/**
+ * Waits on a callback event for being notified.
+ *
+ * @returns VBox status code.
+ * @param msTimeout Timeout (in ms) to wait for callback event.
+ */
+int GuestDnDCallbackEvent::Wait(RTMSINTERVAL msTimeout)
+{
+ return RTSemEventWait(m_SemEvent, msTimeout);
+}
+
+/********************************************************************************************************************************
+ *
+ ********************************************************************************************************************************/
+
+GuestDnDState::GuestDnDState(const ComObjPtr<Guest>& pGuest)
+ : m_uProtocolVersion(0)
+ , m_fGuestFeatures0(VBOX_DND_GF_NONE)
+ , m_EventSem(NIL_RTSEMEVENT)
+ , m_pParent(pGuest)
+{
+ reset();
+
+ int rc = RTCritSectInit(&m_CritSect);
+ if (RT_FAILURE(rc))
+ throw rc;
+ rc = RTSemEventCreate(&m_EventSem);
+ if (RT_FAILURE(rc))
+ throw rc;
+}
+
+GuestDnDState::~GuestDnDState(void)
+{
+ int rc = RTSemEventDestroy(m_EventSem);
+ AssertRC(rc);
+ rc = RTCritSectDelete(&m_CritSect);
+ AssertRC(rc);
+}
+
+/**
+ * Notifies the waiting side about a guest notification response.
+ *
+ * @returns VBox status code.
+ * @param rcGuest Guest rc to set for the response.
+ * Defaults to VINF_SUCCESS (for success).
+ */
+int GuestDnDState::notifyAboutGuestResponse(int rcGuest /* = VINF_SUCCESS */)
+{
+ m_rcGuest = rcGuest;
+ return RTSemEventSignal(m_EventSem);
+}
+
+/**
+ * Resets a guest drag'n drop state.
+ */
+void GuestDnDState::reset(void)
+{
+ LogRel2(("DnD: Reset\n"));
+
+ m_enmState = VBOXDNDSTATE_UNKNOWN;
+
+ m_dndActionDefault = VBOX_DND_ACTION_IGNORE;
+ m_dndLstActionsAllowed = VBOX_DND_ACTION_IGNORE;
+
+ m_lstFormats.clear();
+ m_mapCallbacks.clear();
+
+ m_rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+}
+
+/**
+ * Default callback handler for guest callbacks.
+ *
+ * This handler acts as a fallback in case important callback messages are not being handled
+ * by the specific callers.
+ *
+ * @returns VBox status code. Will get sent back to the host service.
+ * @retval VERR_NO_DATA if no new messages from the host side are available at the moment.
+ * @retval VERR_CANCELLED for indicating that the current operation was cancelled.
+ * @param uMsg HGCM message ID (function number).
+ * @param pvParms Pointer to additional message data. Optional and can be NULL.
+ * @param cbParms Size (in bytes) additional message data. Optional and can be 0.
+ * @param pvUser User-supplied pointer on callback registration.
+ */
+/* static */
+DECLCALLBACK(int) GuestDnDState::i_defaultCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
+{
+ GuestDnDState *pThis = (GuestDnDState *)pvUser;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("uMsg=%RU32 (%#x)\n", uMsg, uMsg));
+
+ int vrc = VERR_IPE_UNINITIALIZED_STATUS;
+
+ switch (uMsg)
+ {
+ case GUEST_DND_FN_EVT_ERROR:
+ {
+ PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ if (RT_SUCCESS(pCBData->rc))
+ {
+ AssertMsgFailed(("Guest has sent an error event but did not specify an actual error code\n"));
+ pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
+ }
+
+ vrc = pThis->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
+ Utf8StrFmt("Received error from guest: %Rrc", pCBData->rc));
+ AssertRCBreak(vrc);
+ vrc = pThis->notifyAboutGuestResponse(pCBData->rc);
+ AssertRCBreak(vrc);
+ break;
+ }
+
+ case GUEST_DND_FN_GET_NEXT_HOST_MSG:
+ vrc = VERR_NO_DATA; /* Indicate back to the host service that there are no new messages. */
+ break;
+
+ default:
+ AssertMsgBreakStmt(pThis->isProgressRunning() == false,
+ ("Progress object not completed / canceld yet! State is '%s' (%#x)\n",
+ DnDStateToStr(pThis->m_enmState), pThis->m_enmState),
+ vrc = VERR_INVALID_STATE); /* Please report this! */
+ vrc = VERR_CANCELLED;
+ break;
+ }
+
+ LogFlowFunc(("Returning rc=%Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Resets the progress object.
+ *
+ * @returns HRESULT
+ * @param pParent Parent to set for the progress object.
+ * @param strDesc Description of the progress.
+ */
+HRESULT GuestDnDState::resetProgress(const ComObjPtr<Guest>& pParent, const Utf8Str &strDesc)
+{
+ AssertReturn(strDesc.isNotEmpty(), E_INVALIDARG);
+
+ m_pProgress.setNull();
+
+ HRESULT hr = m_pProgress.createObject();
+ if (SUCCEEDED(hr))
+ {
+ hr = m_pProgress->init(static_cast<IGuest *>(pParent),
+ Bstr(strDesc).raw(),
+ TRUE /* aCancelable */);
+ }
+
+ return hr;
+}
+
+/**
+ * Returns whether the progress object has been canceled or not.
+ *
+ * @returns \c true if canceled or progress does not exist, \c false if not.
+ */
+bool GuestDnDState::isProgressCanceled(void) const
+{
+ if (m_pProgress.isNull())
+ return true;
+
+ BOOL fCanceled;
+ HRESULT hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
+ AssertComRCReturn(hr, false);
+ return RT_BOOL(fCanceled);
+}
+
+/**
+ * Returns whether the progress object still is in a running state or not.
+ *
+ * @returns \c true if running, \c false if not.
+ */
+bool GuestDnDState::isProgressRunning(void) const
+{
+ if (m_pProgress.isNull())
+ return false;
+
+ BOOL fCompleted;
+ HRESULT const hr = m_pProgress->COMGETTER(Completed)(&fCompleted);
+ AssertComRCReturn(hr, false);
+ return !RT_BOOL(fCompleted);
+}
+
+/**
+ * Sets (registers or unregisters) a callback for a specific HGCM message.
+ *
+ * @returns VBox status code.
+ * @param uMsg HGCM message ID to set callback for.
+ * @param pfnCallback Callback function pointer to use. Pass NULL to unregister.
+ * @param pvUser User-provided arguments for the callback function. Optional and can be NULL.
+ */
+int GuestDnDState::setCallback(uint32_t uMsg, PFNGUESTDNDCALLBACK pfnCallback, void *pvUser /* = NULL */)
+{
+ GuestDnDCallbackMap::iterator it = m_mapCallbacks.find(uMsg);
+
+ /* Register. */
+ if (pfnCallback)
+ {
+ try
+ {
+ m_mapCallbacks[uMsg] = GuestDnDCallback(pfnCallback, uMsg, pvUser);
+ }
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+ return VINF_SUCCESS;
+ }
+
+ /* Unregister. */
+ if (it != m_mapCallbacks.end())
+ m_mapCallbacks.erase(it);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sets the progress object to a new state.
+ *
+ * @returns VBox status code.
+ * @param uPercentage Percentage (0-100) to set.
+ * @param uStatus Status (of type DND_PROGRESS_XXX) to set.
+ * @param rcOp IPRT-style result code to set. Optional.
+ * @param strMsg Message to set. Optional.
+ */
+int GuestDnDState::setProgress(unsigned uPercentage, uint32_t uStatus,
+ int rcOp /* = VINF_SUCCESS */, const Utf8Str &strMsg /* = "" */)
+{
+ LogFlowFunc(("uPercentage=%u, uStatus=%RU32, , rcOp=%Rrc, strMsg=%s\n",
+ uPercentage, uStatus, rcOp, strMsg.c_str()));
+
+ HRESULT hr = S_OK;
+
+ if (m_pProgress.isNull())
+ return VINF_SUCCESS;
+
+ BOOL fCompleted = FALSE;
+ hr = m_pProgress->COMGETTER(Completed)(&fCompleted);
+ AssertComRCReturn(hr, VERR_COM_UNEXPECTED);
+
+ BOOL fCanceled = FALSE;
+ hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
+ AssertComRCReturn(hr, VERR_COM_UNEXPECTED);
+
+ LogFlowFunc(("Progress fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
+
+ int rc = VINF_SUCCESS;
+
+ switch (uStatus)
+ {
+ case DragAndDropSvc::DND_PROGRESS_ERROR:
+ {
+ LogRel(("DnD: Guest reported error %Rrc\n", rcOp));
+
+ if (!fCompleted)
+ hr = m_pProgress->i_notifyComplete(VBOX_E_DND_ERROR,
+ COM_IIDOF(IGuest),
+ m_pParent->getComponentName(), strMsg.c_str());
+ break;
+ }
+
+ case DragAndDropSvc::DND_PROGRESS_CANCELLED:
+ {
+ LogRel2(("DnD: Guest cancelled operation\n"));
+
+ if (!fCanceled)
+ {
+ hr = m_pProgress->Cancel();
+ AssertComRC(hr);
+ }
+
+ if (!fCompleted)
+ {
+ hr = m_pProgress->i_notifyComplete(S_OK);
+ AssertComRC(hr);
+ }
+ break;
+ }
+
+ case DragAndDropSvc::DND_PROGRESS_RUNNING:
+ RT_FALL_THROUGH();
+ case DragAndDropSvc::DND_PROGRESS_COMPLETE:
+ {
+ LogRel2(("DnD: Guest reporting running/completion status with %u%%\n", uPercentage));
+
+ if ( !fCompleted
+ && !fCanceled)
+ {
+ hr = m_pProgress->SetCurrentOperationProgress(uPercentage);
+ AssertComRCReturn(hr, VERR_COM_UNEXPECTED);
+ if ( uStatus == DragAndDropSvc::DND_PROGRESS_COMPLETE
+ || uPercentage >= 100)
+ {
+ hr = m_pProgress->i_notifyComplete(S_OK);
+ AssertComRCReturn(hr, VERR_COM_UNEXPECTED);
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Dispatching function for handling the host service service callback.
+ *
+ * @returns VBox status code.
+ * @param u32Function HGCM message ID to handle.
+ * @param pvParms Pointer to optional data provided for a particular message. Optional.
+ * @param cbParms Size (in bytes) of \a pvParms.
+ */
+int GuestDnDState::onDispatch(uint32_t u32Function, void *pvParms, uint32_t cbParms)
+{
+ LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms));
+
+ int rc = VERR_WRONG_ORDER; /* Play safe. */
+
+ /* Whether or not to try calling host-installed callbacks after successfully processing the message. */
+ bool fTryCallbacks = false;
+
+ switch (u32Function)
+ {
+ case DragAndDropSvc::GUEST_DND_FN_CONNECT:
+ {
+ DragAndDropSvc::PVBOXDNDCBCONNECTDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBCONNECTDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBCONNECTDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(DragAndDropSvc::CB_MAGIC_DND_CONNECT == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ m_uProtocolVersion = pCBData->uProtocolVersion;
+ /** @todo Handle flags. */
+
+ LogThisFunc(("Client connected, using protocol v%RU32\n", m_uProtocolVersion));
+
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ case DragAndDropSvc::GUEST_DND_FN_REPORT_FEATURES:
+ {
+ DragAndDropSvc::PVBOXDNDCBREPORTFEATURESDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBREPORTFEATURESDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBREPORTFEATURESDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(DragAndDropSvc::CB_MAGIC_DND_REPORT_FEATURES == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ m_fGuestFeatures0 = pCBData->fGuestFeatures0;
+
+ LogThisFunc(("Client reported features: %#RX64\n", m_fGuestFeatures0));
+
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ /* Note: GUEST_DND_FN_EVT_ERROR is handled in either the state's default callback or in specific
+ * (overriden) callbacks (e.g. GuestDnDSendCtx / GuestDnDRecvCtx). */
+
+ case DragAndDropSvc::GUEST_DND_FN_DISCONNECT:
+ {
+ LogThisFunc(("Client disconnected\n"));
+ rc = setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
+ break;
+ }
+
+ case DragAndDropSvc::GUEST_DND_FN_HG_ACK_OP:
+ {
+ DragAndDropSvc::PVBOXDNDCBHGACKOPDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGACKOPDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGACKOPDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_ACK_OP == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ LogRel2(("DnD: Guest responded with action '%s' for host->guest drag event\n", DnDActionToStr(pCBData->uAction)));
+
+ setActionDefault(pCBData->uAction);
+ rc = notifyAboutGuestResponse();
+ break;
+ }
+
+ case DragAndDropSvc::GUEST_DND_FN_HG_REQ_DATA:
+ {
+ DragAndDropSvc::PVBOXDNDCBHGREQDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGREQDATADATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGREQDATADATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ if ( pCBData->cbFormat == 0
+ || pCBData->cbFormat > _64K /** @todo Make this configurable? */
+ || pCBData->pszFormat == NULL)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else if (!RTStrIsValidEncoding(pCBData->pszFormat))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ setFormats(GuestDnD::toFormatList(pCBData->pszFormat));
+ rc = VINF_SUCCESS;
+ }
+
+ int rc2 = notifyAboutGuestResponse();
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ break;
+ }
+
+ case DragAndDropSvc::GUEST_DND_FN_HG_EVT_PROGRESS:
+ {
+ DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA pCBData =
+ reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGEVTPROGRESSDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ rc = setProgress(pCBData->uPercentage, pCBData->uStatus, pCBData->rc);
+ if (RT_SUCCESS(rc))
+ rc = notifyAboutGuestResponse(pCBData->rc);
+ break;
+ }
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+ case DragAndDropSvc::GUEST_DND_FN_GH_ACK_PENDING:
+ {
+ DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA pCBData =
+ reinterpret_cast<DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBGHACKPENDINGDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ LogRel2(("DnD: Guest responded with pending action '%s' (%RU32 bytes format data) to guest->host drag event\n",
+ DnDActionToStr(pCBData->uDefAction), pCBData->cbFormat));
+
+ if ( pCBData->cbFormat == 0
+ || pCBData->cbFormat > _64K /** @todo Make the maximum size configurable? */
+ || pCBData->pszFormat == NULL)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else if (!RTStrIsValidEncoding(pCBData->pszFormat))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ setFormats (GuestDnD::toFormatList(pCBData->pszFormat));
+ setActionDefault (pCBData->uDefAction);
+ setActionsAllowed(pCBData->uAllActions);
+
+ rc = VINF_SUCCESS;
+ }
+
+ int rc2 = notifyAboutGuestResponse();
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ break;
+ }
+#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
+ default:
+ /* * Try if the event is covered by a registered callback. */
+ fTryCallbacks = true;
+ break;
+ }
+
+ /*
+ * Try the host's installed callbacks (if any).
+ */
+ if (fTryCallbacks)
+ {
+ GuestDnDCallbackMap::const_iterator it = m_mapCallbacks.find(u32Function);
+ if (it != m_mapCallbacks.end())
+ {
+ AssertPtr(it->second.pfnCallback);
+ rc = it->second.pfnCallback(u32Function, pvParms, cbParms, it->second.pvUser);
+ }
+ else
+ {
+ /* Invoke the default callback handler in case we don't have any registered callback above. */
+ rc = i_defaultCallback(u32Function, pvParms, cbParms, this /* pvUser */);
+ }
+ }
+
+ LogFlowFunc(("Returning rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Helper function to query the internal progress object to an IProgress interface.
+ *
+ * @returns HRESULT
+ * @param ppProgress Where to query the progress object to.
+ */
+HRESULT GuestDnDState::queryProgressTo(IProgress **ppProgress)
+{
+ return m_pProgress.queryInterfaceTo(ppProgress);
+}
+
+/**
+ * Waits for a guest response to happen, extended version.
+ *
+ * @returns VBox status code.
+ * @retval VERR_TIMEOUT when waiting has timed out.
+ * @retval VERR_DND_GUEST_ERROR on an error reported back from the guest.
+ * @param msTimeout Timeout (in ms) for waiting. Optional, waits 3000 ms if not specified.
+ * @param prcGuest Where to return the guest error when VERR_DND_GUEST_ERROR is returned. Optional.
+ */
+int GuestDnDState::waitForGuestResponseEx(RTMSINTERVAL msTimeout /* = 3000 */, int *prcGuest /* = NULL */)
+{
+ int vrc = RTSemEventWait(m_EventSem, msTimeout);
+ if (RT_SUCCESS(vrc))
+ {
+ if (RT_FAILURE(m_rcGuest))
+ vrc = VERR_DND_GUEST_ERROR;
+ if (prcGuest)
+ *prcGuest = m_rcGuest;
+ }
+ return vrc;
+}
+
+/**
+ * Waits for a guest response to happen.
+ *
+ * @returns VBox status code.
+ * @retval VERR_TIMEOUT when waiting has timed out.
+ * @retval VERR_DND_GUEST_ERROR on an error reported back from the guest.
+ * @param prcGuest Where to return the guest error when VERR_DND_GUEST_ERROR is returned. Optional.
+ *
+ * @note Uses the default timeout of 3000 ms.
+ */
+int GuestDnDState::waitForGuestResponse(int *prcGuest /* = NULL */)
+{
+ return waitForGuestResponseEx(3000 /* ms */, prcGuest);
+}
+
+/*********************************************************************************************************************************
+ * GuestDnD implementation. *
+ ********************************************************************************************************************************/
+
+/** Static (Singleton) instance of the GuestDnD object. */
+GuestDnD* GuestDnD::s_pInstance = NULL;
+
+GuestDnD::GuestDnD(const ComObjPtr<Guest> &pGuest)
+ : m_pGuest(pGuest)
+ , m_cTransfersPending(0)
+{
+ LogFlowFuncEnter();
+
+ try
+ {
+ m_pState = new GuestDnDState(pGuest);
+ }
+ catch (std::bad_alloc &)
+ {
+ throw VERR_NO_MEMORY;
+ }
+
+ int rc = RTCritSectInit(&m_CritSect);
+ if (RT_FAILURE(rc))
+ throw rc;
+
+ /* List of supported default MIME types. */
+ LogRel2(("DnD: Supported default host formats:\n"));
+ const com::Utf8Str arrEntries[] = { VBOX_DND_FORMATS_DEFAULT };
+ for (size_t i = 0; i < RT_ELEMENTS(arrEntries); i++)
+ {
+ m_strDefaultFormats.push_back(arrEntries[i]);
+ LogRel2(("DnD: \t%s\n", arrEntries[i].c_str()));
+ }
+}
+
+GuestDnD::~GuestDnD(void)
+{
+ LogFlowFuncEnter();
+
+ Assert(m_cTransfersPending == 0); /* Sanity. */
+
+ RTCritSectDelete(&m_CritSect);
+
+ if (m_pState)
+ delete m_pState;
+}
+
+/**
+ * Adjusts coordinations to a given screen.
+ *
+ * @returns HRESULT
+ * @param uScreenId ID of screen to adjust coordinates to.
+ * @param puX Pointer to X coordinate to adjust. Will return the adjusted value on success.
+ * @param puY Pointer to Y coordinate to adjust. Will return the adjusted value on success.
+ */
+HRESULT GuestDnD::adjustScreenCoordinates(ULONG uScreenId, ULONG *puX, ULONG *puY) const
+{
+ /** @todo r=andy Save the current screen's shifting coordinates to speed things up.
+ * Only query for new offsets when the screen ID or the screen's resolution has changed. */
+
+ /* For multi-monitor support we need to add shift values to the coordinates
+ * (depending on the screen number). */
+ ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
+ ComPtr<IDisplay> pDisplay;
+ HRESULT hr = pConsole->COMGETTER(Display)(pDisplay.asOutParam());
+ if (FAILED(hr))
+ return hr;
+
+ ULONG dummy;
+ LONG xShift, yShift;
+ GuestMonitorStatus_T monitorStatus;
+ hr = pDisplay->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy,
+ &xShift, &yShift, &monitorStatus);
+ if (FAILED(hr))
+ return hr;
+
+ if (puX)
+ *puX += xShift;
+ if (puY)
+ *puY += yShift;
+
+ LogFlowFunc(("uScreenId=%RU32, x=%RU32, y=%RU32\n", uScreenId, puX ? *puX : 0, puY ? *puY : 0));
+ return S_OK;
+}
+
+/**
+ * Returns a DnD guest state.
+ *
+ * @returns Pointer to DnD guest state, or NULL if not found / invalid.
+ * @param uID ID of DnD guest state to return.
+ */
+GuestDnDState *GuestDnD::getState(uint32_t uID /* = 0 */) const
+{
+ AssertMsgReturn(uID == 0, ("Only one state (0) is supported at the moment\n"), NULL);
+
+ return m_pState;
+}
+
+/**
+ * Sends a (blocking) message to the host side of the host service.
+ *
+ * @returns VBox status code.
+ * @param u32Function HGCM message ID to send.
+ * @param cParms Number of parameters to send.
+ * @param paParms Array of parameters to send. Must match \c cParms.
+ */
+int GuestDnD::hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const
+{
+ Assert(!m_pGuest.isNull());
+ ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
+
+ /* Forward the information to the VMM device. */
+ Assert(!pConsole.isNull());
+ VMMDev *pVMMDev = pConsole->i_getVMMDev();
+ if (!pVMMDev)
+ return VERR_COM_OBJECT_NOT_FOUND;
+
+ return pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", u32Function, cParms, paParms);
+}
+
+/**
+ * Registers a GuestDnDSource object with the GuestDnD manager.
+ *
+ * Currently only one source is supported at a time.
+ *
+ * @returns VBox status code.
+ * @param Source Source to register.
+ */
+int GuestDnD::registerSource(const ComObjPtr<GuestDnDSource> &Source)
+{
+ GUESTDND_LOCK();
+
+ Assert(m_lstSrc.size() == 0); /* We only support one source at a time at the moment. */
+ m_lstSrc.push_back(Source);
+
+ GUESTDND_UNLOCK();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unregisters a GuestDnDSource object from the GuestDnD manager.
+ *
+ * @returns VBox status code.
+ * @param Source Source to unregister.
+ */
+int GuestDnD::unregisterSource(const ComObjPtr<GuestDnDSource> &Source)
+{
+ GUESTDND_LOCK();
+
+ GuestDnDSrcList::iterator itSrc = std::find(m_lstSrc.begin(), m_lstSrc.end(), Source);
+ if (itSrc != m_lstSrc.end())
+ m_lstSrc.erase(itSrc);
+
+ GUESTDND_UNLOCK();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Returns the current number of registered sources.
+ *
+ * @returns Current number of registered sources.
+ */
+size_t GuestDnD::getSourceCount(void)
+{
+ GUESTDND_LOCK_RET(0);
+
+ size_t cSources = m_lstSrc.size();
+
+ GUESTDND_UNLOCK();
+ return cSources;
+}
+
+/**
+ * Registers a GuestDnDTarget object with the GuestDnD manager.
+ *
+ * Currently only one target is supported at a time.
+ *
+ * @returns VBox status code.
+ * @param Target Target to register.
+ */
+int GuestDnD::registerTarget(const ComObjPtr<GuestDnDTarget> &Target)
+{
+ GUESTDND_LOCK();
+
+ Assert(m_lstTgt.size() == 0); /* We only support one target at a time at the moment. */
+ m_lstTgt.push_back(Target);
+
+ GUESTDND_UNLOCK();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unregisters a GuestDnDTarget object from the GuestDnD manager.
+ *
+ * @returns VBox status code.
+ * @param Target Target to unregister.
+ */
+int GuestDnD::unregisterTarget(const ComObjPtr<GuestDnDTarget> &Target)
+{
+ GUESTDND_LOCK();
+
+ GuestDnDTgtList::iterator itTgt = std::find(m_lstTgt.begin(), m_lstTgt.end(), Target);
+ if (itTgt != m_lstTgt.end())
+ m_lstTgt.erase(itTgt);
+
+ GUESTDND_UNLOCK();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Returns the current number of registered targets.
+ *
+ * @returns Current number of registered targets.
+ */
+size_t GuestDnD::getTargetCount(void)
+{
+ GUESTDND_LOCK_RET(0);
+
+ size_t cTargets = m_lstTgt.size();
+
+ GUESTDND_UNLOCK();
+ return cTargets;
+}
+
+/**
+ * Static main dispatcher function to handle callbacks from the DnD host service.
+ *
+ * @returns VBox status code.
+ * @param pvExtension Pointer to service extension.
+ * @param u32Function Callback HGCM message ID.
+ * @param pvParms Pointer to optional data provided for a particular message. Optional.
+ * @param cbParms Size (in bytes) of \a pvParms.
+ */
+/* static */
+DECLCALLBACK(int) GuestDnD::notifyDnDDispatcher(void *pvExtension, uint32_t u32Function,
+ void *pvParms, uint32_t cbParms)
+{
+ LogFlowFunc(("pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n",
+ pvExtension, u32Function, pvParms, cbParms));
+
+ GuestDnD *pGuestDnD = reinterpret_cast<GuestDnD*>(pvExtension);
+ AssertPtrReturn(pGuestDnD, VERR_INVALID_POINTER);
+
+ /** @todo In case we need to handle multiple guest DnD responses at a time this
+ * would be the place to lookup and dispatch to those. For the moment we
+ * only have one response -- simple. */
+ if (pGuestDnD->m_pState)
+ return pGuestDnD->m_pState->onDispatch(u32Function, pvParms, cbParms);
+
+ return VERR_NOT_SUPPORTED;
+}
+
+/**
+ * Static helper function to determine whether a format is part of a given MIME list.
+ *
+ * @returns \c true if found, \c false if not.
+ * @param strFormat Format to search for.
+ * @param lstFormats MIME list to search in.
+ */
+/* static */
+bool GuestDnD::isFormatInFormatList(const com::Utf8Str &strFormat, const GuestDnDMIMEList &lstFormats)
+{
+ return std::find(lstFormats.begin(), lstFormats.end(), strFormat) != lstFormats.end();
+}
+
+/**
+ * Static helper function to create a GuestDnDMIMEList out of a format list string.
+ *
+ * @returns MIME list object.
+ * @param strFormats List of formats to convert.
+ * @param strSep Separator to use. If not specified, DND_FORMATS_SEPARATOR_STR will be used.
+ */
+/* static */
+GuestDnDMIMEList GuestDnD::toFormatList(const com::Utf8Str &strFormats, const com::Utf8Str &strSep /* = DND_FORMATS_SEPARATOR_STR */)
+{
+ GuestDnDMIMEList lstFormats;
+ RTCList<RTCString> lstFormatsTmp = strFormats.split(strSep);
+
+ for (size_t i = 0; i < lstFormatsTmp.size(); i++)
+ lstFormats.push_back(com::Utf8Str(lstFormatsTmp.at(i)));
+
+ return lstFormats;
+}
+
+/**
+ * Static helper function to create a format list string from a given GuestDnDMIMEList object.
+ *
+ * @returns Format list string.
+ * @param lstFormats GuestDnDMIMEList to convert.
+ * @param strSep Separator to use between formats.
+ * Uses DND_FORMATS_SEPARATOR_STR as default.
+ */
+/* static */
+com::Utf8Str GuestDnD::toFormatString(const GuestDnDMIMEList &lstFormats, const com::Utf8Str &strSep /* = DND_FORMATS_SEPARATOR_STR */)
+{
+ com::Utf8Str strFormat;
+ for (size_t i = 0; i < lstFormats.size(); i++)
+ {
+ const com::Utf8Str &f = lstFormats.at(i);
+ strFormat += f + strSep;
+ }
+
+ return strFormat;
+}
+
+/**
+ * Static helper function to create a filtered GuestDnDMIMEList object from supported and wanted formats.
+ *
+ * @returns Filtered MIME list object.
+ * @param lstFormatsSupported MIME list of supported formats.
+ * @param lstFormatsWanted MIME list of wanted formats in returned object.
+ */
+/* static */
+GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const GuestDnDMIMEList &lstFormatsWanted)
+{
+ GuestDnDMIMEList lstFormats;
+
+ for (size_t i = 0; i < lstFormatsWanted.size(); i++)
+ {
+ /* Only keep supported format types. */
+ if (std::find(lstFormatsSupported.begin(),
+ lstFormatsSupported.end(), lstFormatsWanted.at(i)) != lstFormatsSupported.end())
+ {
+ lstFormats.push_back(lstFormatsWanted[i]);
+ }
+ }
+
+ return lstFormats;
+}
+
+/**
+ * Static helper function to create a filtered GuestDnDMIMEList object from supported and wanted formats.
+ *
+ * @returns Filtered MIME list object.
+ * @param lstFormatsSupported MIME list of supported formats.
+ * @param strFormatsWanted Format list string of wanted formats in returned object.
+ */
+/* static */
+GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const com::Utf8Str &strFormatsWanted)
+{
+ GuestDnDMIMEList lstFmt;
+
+ RTCList<RTCString> lstFormats = strFormatsWanted.split(DND_FORMATS_SEPARATOR_STR);
+ size_t i = 0;
+ while (i < lstFormats.size())
+ {
+ /* Only keep allowed format types. */
+ if (std::find(lstFormatsSupported.begin(),
+ lstFormatsSupported.end(), lstFormats.at(i)) != lstFormatsSupported.end())
+ {
+ lstFmt.push_back(lstFormats[i]);
+ }
+ i++;
+ }
+
+ return lstFmt;
+}
+
+/**
+ * Static helper function to convert a Main DnD action an internal DnD action.
+ *
+ * @returns Internal DnD action, or VBOX_DND_ACTION_IGNORE if not found / supported.
+ * @param enmAction Main DnD action to convert.
+ */
+/* static */
+VBOXDNDACTION GuestDnD::toHGCMAction(DnDAction_T enmAction)
+{
+ VBOXDNDACTION dndAction = VBOX_DND_ACTION_IGNORE;
+ switch (enmAction)
+ {
+ case DnDAction_Copy:
+ dndAction = VBOX_DND_ACTION_COPY;
+ break;
+ case DnDAction_Move:
+ dndAction = VBOX_DND_ACTION_MOVE;
+ break;
+ case DnDAction_Link:
+ /* For now it doesn't seems useful to allow a link
+ action between host & guest. Later? */
+ case DnDAction_Ignore:
+ /* Ignored. */
+ break;
+ default:
+ AssertMsgFailed(("Action %RU32 not recognized!\n", enmAction));
+ break;
+ }
+
+ return dndAction;
+}
+
+/**
+ * Static helper function to convert a Main DnD default action and allowed Main actions to their
+ * corresponding internal representations.
+ *
+ * @param enmDnDActionDefault Default Main action to convert.
+ * @param pDnDActionDefault Where to store the converted default action.
+ * @param vecDnDActionsAllowed Allowed Main actions to convert.
+ * @param pDnDLstActionsAllowed Where to store the converted allowed actions.
+ */
+/* static */
+void GuestDnD::toHGCMActions(DnDAction_T enmDnDActionDefault,
+ VBOXDNDACTION *pDnDActionDefault,
+ const std::vector<DnDAction_T> vecDnDActionsAllowed,
+ VBOXDNDACTIONLIST *pDnDLstActionsAllowed)
+{
+ VBOXDNDACTIONLIST dndLstActionsAllowed = VBOX_DND_ACTION_IGNORE;
+ VBOXDNDACTION dndActionDefault = toHGCMAction(enmDnDActionDefault);
+
+ if (!vecDnDActionsAllowed.empty())
+ {
+ /* First convert the allowed actions to a bit array. */
+ for (size_t i = 0; i < vecDnDActionsAllowed.size(); i++)
+ dndLstActionsAllowed |= toHGCMAction(vecDnDActionsAllowed[i]);
+
+ /*
+ * If no default action is set (ignoring), try one of the
+ * set allowed actions, preferring copy, move (in that order).
+ */
+ if (isDnDIgnoreAction(dndActionDefault))
+ {
+ if (hasDnDCopyAction(dndLstActionsAllowed))
+ dndActionDefault = VBOX_DND_ACTION_COPY;
+ else if (hasDnDMoveAction(dndLstActionsAllowed))
+ dndActionDefault = VBOX_DND_ACTION_MOVE;
+ }
+ }
+
+ if (pDnDActionDefault)
+ *pDnDActionDefault = dndActionDefault;
+ if (pDnDLstActionsAllowed)
+ *pDnDLstActionsAllowed = dndLstActionsAllowed;
+}
+
+/**
+ * Static helper function to convert an internal DnD action to its Main representation.
+ *
+ * @returns Converted Main DnD action.
+ * @param dndAction DnD action to convert.
+ */
+/* static */
+DnDAction_T GuestDnD::toMainAction(VBOXDNDACTION dndAction)
+{
+ /* For now it doesn't seems useful to allow a
+ * link action between host & guest. Maybe later! */
+ return isDnDCopyAction(dndAction) ? DnDAction_Copy
+ : isDnDMoveAction(dndAction) ? DnDAction_Move
+ : DnDAction_Ignore;
+}
+
+/**
+ * Static helper function to convert an internal DnD action list to its Main representation.
+ *
+ * @returns Converted Main DnD action list.
+ * @param dndActionList DnD action list to convert.
+ */
+/* static */
+std::vector<DnDAction_T> GuestDnD::toMainActions(VBOXDNDACTIONLIST dndActionList)
+{
+ std::vector<DnDAction_T> vecActions;
+
+ /* For now it doesn't seems useful to allow a
+ * link action between host & guest. Maybe later! */
+ RTCList<DnDAction_T> lstActions;
+ if (hasDnDCopyAction(dndActionList))
+ lstActions.append(DnDAction_Copy);
+ if (hasDnDMoveAction(dndActionList))
+ lstActions.append(DnDAction_Move);
+
+ for (size_t i = 0; i < lstActions.size(); ++i)
+ vecActions.push_back(lstActions.at(i));
+
+ return vecActions;
+}
+
+/*********************************************************************************************************************************
+ * GuestDnDBase implementation. *
+ ********************************************************************************************************************************/
+
+GuestDnDBase::GuestDnDBase(VirtualBoxBase *pBase)
+ : m_pBase(pBase)
+ , m_fIsPending(false)
+{
+ /* Initialize public attributes. */
+ m_lstFmtSupported = GuestDnDInst()->defaultFormats();
+}
+
+GuestDnDBase::~GuestDnDBase(void)
+{
+}
+
+/**
+ * Checks whether a given DnD format is supported or not.
+ *
+ * @returns \c true if supported, \c false if not.
+ * @param aFormat DnD format to check.
+ */
+bool GuestDnDBase::i_isFormatSupported(const com::Utf8Str &aFormat) const
+{
+ return std::find(m_lstFmtSupported.begin(), m_lstFmtSupported.end(), aFormat) != m_lstFmtSupported.end();
+}
+
+/**
+ * Returns the currently supported DnD formats.
+ *
+ * @returns List of the supported DnD formats.
+ */
+const GuestDnDMIMEList &GuestDnDBase::i_getFormats(void) const
+{
+ return m_lstFmtSupported;
+}
+
+/**
+ * Adds DnD formats to the supported formats list.
+ *
+ * @returns HRESULT
+ * @param aFormats List of DnD formats to add.
+ */
+HRESULT GuestDnDBase::i_addFormats(const GuestDnDMIMEList &aFormats)
+{
+ for (size_t i = 0; i < aFormats.size(); ++i)
+ {
+ Utf8Str strFormat = aFormats.at(i);
+ if (std::find(m_lstFmtSupported.begin(),
+ m_lstFmtSupported.end(), strFormat) == m_lstFmtSupported.end())
+ {
+ m_lstFmtSupported.push_back(strFormat);
+ }
+ }
+
+ return S_OK;
+}
+
+/**
+ * Removes DnD formats from tehh supported formats list.
+ *
+ * @returns HRESULT
+ * @param aFormats List of DnD formats to remove.
+ */
+HRESULT GuestDnDBase::i_removeFormats(const GuestDnDMIMEList &aFormats)
+{
+ for (size_t i = 0; i < aFormats.size(); ++i)
+ {
+ Utf8Str strFormat = aFormats.at(i);
+ GuestDnDMIMEList::iterator itFormat = std::find(m_lstFmtSupported.begin(),
+ m_lstFmtSupported.end(), strFormat);
+ if (itFormat != m_lstFmtSupported.end())
+ m_lstFmtSupported.erase(itFormat);
+ }
+
+ return S_OK;
+}
+
+/**
+ * Prints an error in the release log and sets the COM error info.
+ *
+ * @returns HRESULT
+ * @param vrc IPRT-style error code to print in addition.
+ * Will not be printed if set to a non-error (e.g. VINF _ / VWRN_) code.
+ * @param pcszMsgFmt Format string.
+ * @param va Format arguments.
+ * @note
+ */
+HRESULT GuestDnDBase::i_setErrorV(int vrc, const char *pcszMsgFmt, va_list va)
+{
+ char *psz = NULL;
+ if (RTStrAPrintfV(&psz, pcszMsgFmt, va) < 0)
+ return E_OUTOFMEMORY;
+ AssertPtrReturn(psz, E_OUTOFMEMORY);
+
+ HRESULT hrc;
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("DnD: Error: %s (%Rrc)\n", psz, vrc));
+ hrc = m_pBase->setErrorBoth(VBOX_E_DND_ERROR, vrc, "DnD: Error: %s (%Rrc)", psz, vrc);
+ }
+ else
+ {
+ LogRel(("DnD: Error: %s\n", psz));
+ hrc = m_pBase->setErrorBoth(VBOX_E_DND_ERROR, vrc, "DnD: Error: %s", psz);
+ }
+
+ RTStrFree(psz);
+ return hrc;
+}
+
+/**
+ * Prints an error in the release log and sets the COM error info.
+ *
+ * @returns HRESULT
+ * @param vrc IPRT-style error code to print in addition.
+ * Will not be printed if set to a non-error (e.g. VINF _ / VWRN_) code.
+ * @param pcszMsgFmt Format string.
+ * @param ... Format arguments.
+ * @note
+ */
+HRESULT GuestDnDBase::i_setError(int vrc, const char *pcszMsgFmt, ...)
+{
+ va_list va;
+ va_start(va, pcszMsgFmt);
+ HRESULT const hrc = i_setErrorV(vrc, pcszMsgFmt, va);
+ va_end(va);
+
+ return hrc;
+}
+
+/**
+ * Prints an error in the release log, sets the COM error info and calls the object's reset function.
+ *
+ * @returns HRESULT
+ * @param pcszMsgFmt Format string.
+ * @param va Format arguments.
+ * @note
+ */
+HRESULT GuestDnDBase::i_setErrorAndReset(const char *pcszMsgFmt, ...)
+{
+ va_list va;
+ va_start(va, pcszMsgFmt);
+ HRESULT const hrc = i_setErrorV(VINF_SUCCESS, pcszMsgFmt, va);
+ va_end(va);
+
+ i_reset();
+
+ return hrc;
+}
+
+/**
+ * Prints an error in the release log, sets the COM error info and calls the object's reset function.
+ *
+ * @returns HRESULT
+ * @param vrc IPRT-style error code to print in addition.
+ * Will not be printed if set to a non-error (e.g. VINF _ / VWRN_) code.
+ * @param pcszMsgFmt Format string.
+ * @param ... Format arguments.
+ * @note
+ */
+HRESULT GuestDnDBase::i_setErrorAndReset(int vrc, const char *pcszMsgFmt, ...)
+{
+ va_list va;
+ va_start(va, pcszMsgFmt);
+ HRESULT const hrc = i_setErrorV(vrc, pcszMsgFmt, va);
+ va_end(va);
+
+ i_reset();
+
+ return hrc;
+}
+
+/**
+ * Adds a new guest DnD message to the internal message queue.
+ *
+ * @returns VBox status code.
+ * @param pMsg Pointer to message to add.
+ */
+int GuestDnDBase::msgQueueAdd(GuestDnDMsg *pMsg)
+{
+ m_DataBase.lstMsgOut.push_back(pMsg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Returns the next guest DnD message in the internal message queue (FIFO).
+ *
+ * @returns Pointer to guest DnD message, or NULL if none found.
+ */
+GuestDnDMsg *GuestDnDBase::msgQueueGetNext(void)
+{
+ if (m_DataBase.lstMsgOut.empty())
+ return NULL;
+ return m_DataBase.lstMsgOut.front();
+}
+
+/**
+ * Removes the next guest DnD message from the internal message queue.
+ */
+void GuestDnDBase::msgQueueRemoveNext(void)
+{
+ if (!m_DataBase.lstMsgOut.empty())
+ {
+ GuestDnDMsg *pMsg = m_DataBase.lstMsgOut.front();
+ if (pMsg)
+ delete pMsg;
+ m_DataBase.lstMsgOut.pop_front();
+ }
+}
+
+/**
+ * Clears the internal message queue.
+ */
+void GuestDnDBase::msgQueueClear(void)
+{
+ LogFlowFunc(("cMsg=%zu\n", m_DataBase.lstMsgOut.size()));
+
+ GuestDnDMsgList::iterator itMsg = m_DataBase.lstMsgOut.begin();
+ while (itMsg != m_DataBase.lstMsgOut.end())
+ {
+ GuestDnDMsg *pMsg = *itMsg;
+ if (pMsg)
+ delete pMsg;
+
+ itMsg++;
+ }
+
+ m_DataBase.lstMsgOut.clear();
+}
+
+/**
+ * Sends a request to the guest side to cancel the current DnD operation.
+ *
+ * @returns VBox status code.
+ */
+int GuestDnDBase::sendCancel(void)
+{
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_CANCEL);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+
+ LogRel2(("DnD: Cancelling operation on guest ...\n"));
+
+ int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_FAILURE(rc))
+ LogRel(("DnD: Cancelling operation on guest failed with %Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * Helper function to update the progress based on given a GuestDnDData object.
+ *
+ * @returns VBox status code.
+ * @param pData GuestDnDData object to use for accounting.
+ * @param pState Guest state to update its progress object for.
+ * @param cbDataAdd By how much data (in bytes) to update the progress.
+ */
+int GuestDnDBase::updateProgress(GuestDnDData *pData, GuestDnDState *pState,
+ size_t cbDataAdd /* = 0 */)
+{
+ AssertPtrReturn(pData, VERR_INVALID_POINTER);
+ AssertPtrReturn(pState, VERR_INVALID_POINTER);
+ /* cbDataAdd is optional. */
+
+ LogFlowFunc(("cbExtra=%zu, cbProcessed=%zu, cbRemaining=%zu, cbDataAdd=%zu\n",
+ pData->cbExtra, pData->cbProcessed, pData->getRemaining(), cbDataAdd));
+
+ if ( !pState
+ || !cbDataAdd) /* Only update if something really changes. */
+ return VINF_SUCCESS;
+
+ if (cbDataAdd)
+ pData->addProcessed(cbDataAdd);
+
+ const uint8_t uPercent = pData->getPercentComplete();
+
+ LogRel2(("DnD: Transfer %RU8%% complete\n", uPercent));
+
+ int rc = pState->setProgress(uPercent,
+ pData->isComplete()
+ ? DND_PROGRESS_COMPLETE
+ : DND_PROGRESS_RUNNING);
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Waits for a specific guest callback event to get signalled.
+ *
+ * @returns VBox status code. Will return VERR_CANCELLED if the user has cancelled the progress object.
+ * @param pEvent Callback event to wait for.
+ * @param pState Guest state to update.
+ * @param msTimeout Timeout (in ms) to wait.
+ */
+int GuestDnDBase::waitForEvent(GuestDnDCallbackEvent *pEvent, GuestDnDState *pState, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+ AssertPtrReturn(pState, VERR_INVALID_POINTER);
+
+ int rc;
+
+ LogFlowFunc(("msTimeout=%RU32\n", msTimeout));
+
+ uint64_t tsStart = RTTimeMilliTS();
+ do
+ {
+ /*
+ * Wait until our desired callback triggered the
+ * wait event. As we don't want to block if the guest does not
+ * respond, do busy waiting here.
+ */
+ rc = pEvent->Wait(500 /* ms */);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pEvent->Result();
+ LogFlowFunc(("Callback done, result is %Rrc\n", rc));
+ break;
+ }
+ else if (rc == VERR_TIMEOUT) /* Continue waiting. */
+ rc = VINF_SUCCESS;
+
+ if ( msTimeout != RT_INDEFINITE_WAIT
+ && RTTimeMilliTS() - tsStart > msTimeout)
+ {
+ rc = VERR_TIMEOUT;
+ LogRel2(("DnD: Error: Guest did not respond within time\n"));
+ }
+ else if (pState->isProgressCanceled())
+ {
+ LogRel2(("DnD: Operation was canceled by user\n"));
+ rc = VERR_CANCELLED;
+ }
+
+ } while (RT_SUCCESS(rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+
diff --git a/src/VBox/Main/src-client/GuestDnDSourceImpl.cpp b/src/VBox/Main/src-client/GuestDnDSourceImpl.cpp
new file mode 100644
index 00000000..8a127aa5
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestDnDSourceImpl.cpp
@@ -0,0 +1,1733 @@
+/* $Id: GuestDnDSourceImpl.cpp $ */
+/** @file
+ * VBox Console COM Class implementation - Guest drag and drop source.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_GUEST_DND //LOG_GROUP_MAIN_GUESTDNDSOURCE
+#include "LoggingNew.h"
+
+#include "GuestImpl.h"
+#include "GuestDnDSourceImpl.h"
+#include "GuestDnDPrivate.h"
+#include "ConsoleImpl.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+#include "ThreadTask.h"
+
+#include <iprt/asm.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/uri.h>
+
+#include <iprt/cpp/utils.h> /* For unconst(). */
+
+#include <VBox/com/array.h>
+
+
+/**
+ * Base class for a source task.
+ */
+class GuestDnDSourceTask : public ThreadTask
+{
+public:
+
+ GuestDnDSourceTask(GuestDnDSource *pSource)
+ : ThreadTask("GenericGuestDnDSourceTask")
+ , mSource(pSource)
+ , mRC(VINF_SUCCESS) { }
+
+ virtual ~GuestDnDSourceTask(void) { }
+
+ /** Returns the overall result of the task. */
+ int getRC(void) const { return mRC; }
+ /** Returns if the overall result of the task is ok (succeeded) or not. */
+ bool isOk(void) const { return RT_SUCCESS(mRC); }
+
+protected:
+
+ /** COM object pointer to the parent (source). */
+ const ComObjPtr<GuestDnDSource> mSource;
+ /** Overall result of the task. */
+ int mRC;
+};
+
+/**
+ * Task structure for receiving data from a source using
+ * a worker thread.
+ */
+class GuestDnDRecvDataTask : public GuestDnDSourceTask
+{
+public:
+
+ GuestDnDRecvDataTask(GuestDnDSource *pSource, GuestDnDRecvCtx *pCtx)
+ : GuestDnDSourceTask(pSource)
+ , mpCtx(pCtx)
+ {
+ m_strTaskName = "dndSrcRcvData";
+ }
+
+ void handler()
+ {
+ LogFlowThisFunc(("\n"));
+
+ const ComObjPtr<GuestDnDSource> pThis(mSource);
+ Assert(!pThis.isNull());
+
+ AutoCaller autoCaller(pThis);
+ if (FAILED(autoCaller.rc()))
+ return;
+
+ int vrc = pThis->i_receiveData(mpCtx, RT_INDEFINITE_WAIT /* msTimeout */);
+ if (RT_FAILURE(vrc)) /* In case we missed some error handling within i_receiveData(). */
+ {
+ if (vrc != VERR_CANCELLED)
+ LogRel(("DnD: Receiving data from guest failed with %Rrc\n", vrc));
+
+ /* Make sure to fire a cancel request to the guest side in case something went wrong. */
+ pThis->sendCancel();
+ }
+ }
+
+ virtual ~GuestDnDRecvDataTask(void) { }
+
+protected:
+
+ /** Pointer to receive data context. */
+ GuestDnDRecvCtx *mpCtx;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+GuestDnDSource::GuestDnDSource(void)
+ : GuestDnDBase(this) { }
+
+GuestDnDSource::~GuestDnDSource(void) { }
+
+HRESULT GuestDnDSource::FinalConstruct(void)
+{
+ /*
+ * Set the maximum block size this source can handle to 64K. This always has
+ * been hardcoded until now.
+ *
+ * Note: Never ever rely on information from the guest; the host dictates what and
+ * how to do something, so try to negogiate a sensible value here later.
+ */
+ mData.mcbBlockSize = DND_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */
+
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void GuestDnDSource::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDnDSource::init(const ComObjPtr<Guest>& pGuest)
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(m_pGuest) = pGuest;
+
+ /* Set the response we're going to use for this object.
+ *
+ * At the moment we only have one response total, as we
+ * don't allow
+ * 1) parallel transfers (multiple G->H at the same time)
+ * nor 2) mixed transfers (G->H + H->G at the same time).
+ */
+ m_pState = GuestDnDInst()->getState();
+ AssertPtrReturn(m_pState, E_POINTER);
+
+ /* Confirm a successful initialization when it's the case. */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void GuestDnDSource::uninit(void)
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+}
+
+// implementation of wrapped IDnDBase methods.
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDnDSource::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aSupported = GuestDnDBase::i_isFormatSupported(aFormat) ? TRUE : FALSE;
+
+ return S_OK;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDSource::getFormats(GuestDnDMIMEList &aFormats)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aFormats = GuestDnDBase::i_getFormats();
+
+ return S_OK;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDSource::addFormats(const GuestDnDMIMEList &aFormats)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return GuestDnDBase::i_addFormats(aFormats);
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDSource::removeFormats(const GuestDnDMIMEList &aFormats)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return GuestDnDBase::i_removeFormats(aFormats);
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+// implementation of wrapped IDnDSource methods.
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDnDSource::dragIsPending(ULONG uScreenId, GuestDnDMIMEList &aFormats,
+ std::vector<DnDAction_T> &aAllowedActions, DnDAction_T *aDefaultAction)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ /* aDefaultAction is optional. */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ /* Default is ignoring the action. */
+ if (aDefaultAction)
+ *aDefaultAction = DnDAction_Ignore;
+
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtr(pState);
+
+ /* Check if any operation is active, and if so, bail out, returning an ignore action (see above). */
+ if (pState->get() != VBOXDNDSTATE_UNKNOWN)
+ return S_OK;
+
+ pState->set(VBOXDNDSTATE_QUERY_FORMATS);
+
+ HRESULT hrc = S_OK;
+
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_GH_REQ_PENDING);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendUInt32(uScreenId);
+
+ int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest;
+ vrc = pState->waitForGuestResponseEx(100 /* Timeout in ms */, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!isDnDIgnoreAction(pState->getActionDefault()))
+ {
+ /*
+ * In the GuestDnDSource case the source formats are from the guest,
+ * as GuestDnDSource acts as a target for the guest. The host always
+ * dictates what's supported and what's not, so filter out all formats
+ * which are not supported by the host.
+ */
+ GuestDnDMIMEList const &lstGuest = pState->formats();
+ GuestDnDMIMEList const lstFiltered = GuestDnD::toFilteredFormatList(m_lstFmtSupported, lstGuest);
+ if (lstFiltered.size())
+ {
+ LogRel2(("DnD: Host offered the following formats:\n"));
+ for (size_t i = 0; i < lstFiltered.size(); i++)
+ LogRel2(("DnD:\tFormat #%zu: %s\n", i, lstFiltered.at(i).c_str()));
+
+ aFormats = lstFiltered;
+ aAllowedActions = GuestDnD::toMainActions(pState->getActionsAllowed());
+ if (aDefaultAction)
+ *aDefaultAction = GuestDnD::toMainAction(pState->getActionDefault());
+
+ /* Apply the (filtered) formats list. */
+ m_lstFmtOffered = lstFiltered;
+ }
+ else
+ {
+ bool fSetError = true; /* Whether to set an error and reset or not. */
+
+ /*
+ * HACK ALERT: As we now expose an error (via i_setErrorAndReset(), see below) back to the API client, we
+ * have to add a kludge here. Older X11-based Guest Additions report "TARGETS, MULTIPLE" back
+ * to us, even if they don't offer any other *supported* formats of the host. This then in turn
+ * would lead to exposing an error, whereas we just should ignore those specific X11-based
+ * formats. For anything other we really want to be notified by setting an error though.
+ */
+ if ( lstGuest.size() == 2
+ && GuestDnD::isFormatInFormatList("TARGETS", lstGuest)
+ && GuestDnD::isFormatInFormatList("MULTIPLE", lstGuest))
+ {
+ fSetError = false;
+ }
+ /* HACK ALERT END */
+
+ if (fSetError)
+ hrc = i_setErrorAndReset(tr("Negotiation of formats between guest and host failed!\n\nHost offers: %s\n\nGuest offers: %s"),
+ GuestDnD::toFormatString(m_lstFmtSupported , ",").c_str(),
+ GuestDnD::toFormatString(pState->formats() , ",").c_str());
+ else /* Just silently reset. */
+ i_reset();
+ }
+ }
+ /* Note: Don't report an error here when the action is "ignore" -- that only means that the current window on the guest
+ simply doesn't support the format or drag and drop at all. */
+ }
+ else
+ hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Requesting pending data from guest failed"));
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_ACCESS_DENIED:
+ {
+ hrc = i_setErrorAndReset(tr("Dragging from guest to host not allowed -- make sure that the correct drag'n drop mode is set"));
+ break;
+ }
+
+ case VERR_NOT_SUPPORTED:
+ {
+ hrc = i_setErrorAndReset(tr("Dragging from guest to host not supported by guest -- make sure that the Guest Additions are properly installed and running"));
+ break;
+ }
+
+ default:
+ {
+ hrc = i_setErrorAndReset(vrc, tr("Sending drag pending event to guest failed"));
+ break;
+ }
+ }
+ }
+
+ pState->set(VBOXDNDSTATE_UNKNOWN);
+
+ LogFlowFunc(("hr=%Rhrc\n", hrc));
+ return hrc;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDSource::drop(const com::Utf8Str &aFormat, DnDAction_T aAction, ComPtr<IProgress> &aProgress)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFunc(("aFormat=%s, aAction=%RU32\n", aFormat.c_str(), aAction));
+
+ /* Input validation. */
+ if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No drop format specified"));
+
+ /* Is the specified format in our list of (left over) offered formats? */
+ if (!GuestDnD::isFormatInFormatList(aFormat, m_lstFmtOffered))
+ return setError(E_INVALIDARG, tr("Specified format '%s' is not supported"), aFormat.c_str());
+
+ /* Check that the given action is supported by us. */
+ VBOXDNDACTION dndAction = GuestDnD::toHGCMAction(aAction);
+ if (isDnDIgnoreAction(dndAction)) /* If there is no usable action, ignore this request. */
+ return S_OK;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Check if this object still is in a pending state and bail out if so. */
+ if (m_fIsPending)
+ return setError(E_FAIL, tr("Current drop operation to host still in progress"));
+
+ /* Reset our internal state. */
+ i_reset();
+
+ /* At the moment we only support one transfer at a time. */
+ if (GuestDnDInst()->getSourceCount())
+ return setError(E_INVALIDARG, tr("Another drag and drop operation to the host already is in progress"));
+
+ /* Reset progress object. */
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtr(pState);
+ HRESULT hr = pState->resetProgress(m_pGuest, tr("Dropping data to host"));
+ if (FAILED(hr))
+ return hr;
+
+ GuestDnDRecvDataTask *pTask = NULL;
+
+ try
+ {
+ mData.mRecvCtx.pSource = this;
+ mData.mRecvCtx.pState = pState;
+ mData.mRecvCtx.enmAction = dndAction;
+ mData.mRecvCtx.strFmtReq = aFormat;
+ mData.mRecvCtx.lstFmtOffered = m_lstFmtOffered;
+
+ LogRel2(("DnD: Requesting data from guest in format '%s'\n", aFormat.c_str()));
+
+ pTask = new GuestDnDRecvDataTask(this, &mData.mRecvCtx);
+ if (!pTask->isOk())
+ {
+ delete pTask;
+ LogRel2(("DnD: Receive data task failed to initialize\n"));
+ throw hr = E_FAIL;
+ }
+
+ /* Drop write lock before creating thread. */
+ alock.release();
+
+ /* This function delete pTask in case of exceptions,
+ * so there is no need in the call of delete operator. */
+ hr = pTask->createThreadWithType(RTTHREADTYPE_MAIN_WORKER);
+ pTask = NULL; /* Note: pTask is now owned by the worker thread. */
+ }
+ catch (std::bad_alloc &)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ catch (...)
+ {
+ LogRel2(("DnD: Could not create thread for data receiving task\n"));
+ hr = E_FAIL;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ /* Register ourselves at the DnD manager. */
+ GuestDnDInst()->registerSource(this);
+
+ hr = pState->queryProgressTo(aProgress.asOutParam());
+ ComAssertComRC(hr);
+
+ }
+ else
+ hr = i_setErrorAndReset(tr("Starting thread for GuestDnDSource failed (%Rhrc)"), hr);
+
+ LogFlowFunc(("Returning hr=%Rhrc\n", hr));
+ return hr;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDSource::receiveData(std::vector<BYTE> &aData)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Don't allow receiving the actual data until our current transfer is complete. */
+ if (m_fIsPending)
+ return setError(E_FAIL, tr("Current drop operation to host still in progress"));
+
+ HRESULT hr = S_OK;
+
+ try
+ {
+ GuestDnDRecvCtx *pCtx = &mData.mRecvCtx;
+ if (DnDMIMENeedsDropDir(pCtx->strFmtRecv.c_str(), pCtx->strFmtRecv.length()))
+ {
+ PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles;
+
+ const char *pcszDropDirAbs = DnDDroppedFilesGetDirAbs(pDF);
+ AssertPtr(pcszDropDirAbs);
+
+ LogRel2(("DnD: Using drop directory '%s', got %RU64 root entries\n",
+ pcszDropDirAbs, DnDTransferListGetRootCount(&pCtx->Transfer.List)));
+
+ /* We return the data as "text/uri-list" MIME data here. */
+ char *pszBuf = NULL;
+ size_t cbBuf = 0;
+ int rc = DnDTransferListGetRootsEx(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI,
+ pcszDropDirAbs, DND_PATH_SEPARATOR_STR, &pszBuf, &cbBuf);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbBuf);
+ AssertPtr(pszBuf);
+
+ aData.resize(cbBuf);
+ memcpy(&aData.front(), pszBuf, cbBuf);
+ RTStrFree(pszBuf);
+ }
+ else
+ LogRel(("DnD: Unable to build source root list, rc=%Rrc\n", rc));
+ }
+ else /* Raw data. */
+ {
+ if (pCtx->Meta.cbData)
+ {
+ /* Copy the data into a safe array of bytes. */
+ aData.resize(pCtx->Meta.cbData);
+ memcpy(&aData.front(), pCtx->Meta.pvData, pCtx->Meta.cbData);
+ }
+ else
+ aData.resize(0);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ LogFlowFunc(("Returning hr=%Rhrc\n", hr));
+ return hr;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+// implementation of internal methods.
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns an error string from a guest DnD error.
+ *
+ * @returns Error string.
+ * @param guestRc Guest error to return error string for.
+ */
+/* static */
+Utf8Str GuestDnDSource::i_guestErrorToString(int guestRc)
+{
+ Utf8Str strError;
+
+ switch (guestRc)
+ {
+ case VERR_ACCESS_DENIED:
+ strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest "
+ "user does not have the appropriate access rights for. Please make sure that all selected "
+ "elements can be accessed and that your guest user has the appropriate rights"));
+ break;
+
+ case VERR_NOT_FOUND:
+ /* Should not happen due to file locking on the guest, but anyway ... */
+ strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not"
+ "found on the guest anymore. This can be the case if the guest files were moved and/or"
+ "altered while the drag and drop operation was in progress"));
+ break;
+
+ case VERR_SHARING_VIOLATION:
+ strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. "
+ "Please make sure that all selected elements can be accessed and that your guest user has "
+ "the appropriate rights"));
+ break;
+
+ case VERR_TIMEOUT:
+ strError += Utf8StrFmt(tr("The guest was not able to retrieve the drag and drop data within time"));
+ break;
+
+ default:
+ strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc);
+ break;
+ }
+
+ return strError;
+}
+
+/**
+ * Returns an error string from a host DnD error.
+ *
+ * @returns Error string.
+ * @param hostRc Host error to return error string for.
+ */
+/* static */
+Utf8Str GuestDnDSource::i_hostErrorToString(int hostRc)
+{
+ Utf8Str strError;
+
+ switch (hostRc)
+ {
+ case VERR_ACCESS_DENIED:
+ strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host "
+ "user does not have the appropriate access rights for. Please make sure that all selected "
+ "elements can be accessed and that your host user has the appropriate rights."));
+ break;
+
+ case VERR_DISK_FULL:
+ strError += Utf8StrFmt(tr("Host disk ran out of space (disk is full)."));
+ break;
+
+ case VERR_NOT_FOUND:
+ /* Should not happen due to file locking on the host, but anyway ... */
+ strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not"
+ "found on the host anymore. This can be the case if the host files were moved and/or"
+ "altered while the drag and drop operation was in progress."));
+ break;
+
+ case VERR_SHARING_VIOLATION:
+ strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. "
+ "Please make sure that all selected elements can be accessed and that your host user has "
+ "the appropriate rights."));
+ break;
+
+ default:
+ strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc);
+ break;
+ }
+
+ return strError;
+}
+
+/**
+ * Resets all internal data and state.
+ */
+void GuestDnDSource::i_reset(void)
+{
+ LogRel2(("DnD: Source reset\n"));
+
+ mData.mRecvCtx.reset();
+
+ m_fIsPending = false;
+
+ /* Unregister ourselves from the DnD manager. */
+ GuestDnDInst()->unregisterSource(this);
+}
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+
+/**
+ * Handles receiving a send data header from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param pDataHdr Pointer to send data header from the guest.
+ */
+int GuestDnDSource::i_onReceiveDataHdr(GuestDnDRecvCtx *pCtx, PVBOXDNDSNDDATAHDR pDataHdr)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER);
+
+ LogRel2(("DnD: Receiving %RU64 bytes total data (%RU32 bytes meta data, %RU64 objects) from guest ...\n",
+ pDataHdr->cbTotal, pDataHdr->cbMeta, pDataHdr->cObjects));
+
+ AssertReturn(pDataHdr->cbTotal >= pDataHdr->cbMeta, VERR_INVALID_PARAMETER);
+
+ pCtx->Meta.cbAnnounced = pDataHdr->cbMeta;
+ pCtx->cbExtra = pDataHdr->cbTotal - pDataHdr->cbMeta;
+
+ Assert(pCtx->Transfer.cObjToProcess == 0); /* Sanity. */
+ Assert(pCtx->Transfer.cObjProcessed == 0);
+
+ pCtx->Transfer.reset();
+
+ pCtx->Transfer.cObjToProcess = pDataHdr->cObjects;
+
+ /** @todo Handle compression type. */
+ /** @todo Handle checksum type. */
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Main function for receiving data from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param pSndData Pointer to send data block from the guest.
+ */
+int GuestDnDSource::i_onReceiveData(GuestDnDRecvCtx *pCtx, PVBOXDNDSNDDATA pSndData)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSndData, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ try
+ {
+ GuestDnDTransferRecvData *pTransfer = &pCtx->Transfer;
+
+ size_t cbData;
+ void *pvData;
+ size_t cbTotalAnnounced;
+ size_t cbMetaAnnounced;
+
+ if (m_pState->m_uProtocolVersion < 3)
+ {
+ cbData = pSndData->u.v1.cbData;
+ pvData = pSndData->u.v1.pvData;
+
+ /* Sends the total data size to receive for every data chunk. */
+ cbTotalAnnounced = pSndData->u.v1.cbTotalSize;
+
+ /* Meta data size always is cbData, meaning there cannot be an
+ * extended data chunk transfer by sending further data. */
+ cbMetaAnnounced = cbData;
+ }
+ else
+ {
+ cbData = pSndData->u.v3.cbData;
+ pvData = pSndData->u.v3.pvData;
+
+ /* Note: Data sizes get initialized in i_onReceiveDataHdr().
+ * So just use the set values here. */
+ cbTotalAnnounced = pCtx->getTotalAnnounced();
+ cbMetaAnnounced = pCtx->Meta.cbAnnounced;
+ }
+
+ if (cbData > cbTotalAnnounced)
+ {
+ AssertMsgFailed(("Incoming data size invalid: cbData=%zu, cbTotal=%zu\n", cbData, cbTotalAnnounced));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else if ( cbTotalAnnounced == 0
+ || cbTotalAnnounced < cbMetaAnnounced)
+ {
+ AssertMsgFailed(("cbTotal (%zu) is smaller than cbMeta (%zu)\n", cbTotalAnnounced, cbMetaAnnounced));
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ AssertReturn(cbData <= mData.mcbBlockSize, VERR_BUFFER_OVERFLOW);
+
+ const size_t cbMetaRecv = pCtx->Meta.add(pvData, cbData);
+ AssertReturn(cbMetaRecv <= pCtx->Meta.cbData, VERR_BUFFER_OVERFLOW);
+
+ LogFlowThisFunc(("cbData=%zu, cbMetaRecv=%zu, cbMetaAnnounced=%zu, cbTotalAnnounced=%zu\n",
+ cbData, cbMetaRecv, cbMetaAnnounced, cbTotalAnnounced));
+
+ LogRel2(("DnD: %RU8%% of meta data complete (%zu/%zu bytes)\n",
+ (uint8_t)(cbMetaRecv * 100 / RT_MAX(cbMetaAnnounced, 1)), cbMetaRecv, cbMetaAnnounced));
+
+ /*
+ * (Meta) Data transfer complete?
+ */
+ if (cbMetaAnnounced == cbMetaRecv)
+ {
+ LogRel2(("DnD: Receiving meta data complete\n"));
+
+ if (DnDMIMENeedsDropDir(pCtx->strFmtRecv.c_str(), pCtx->strFmtRecv.length()))
+ {
+ rc = DnDTransferListInitEx(&pTransfer->List,
+ DnDDroppedFilesGetDirAbs(&pTransfer->DroppedFiles), DNDTRANSFERLISTFMT_NATIVE);
+ if (RT_SUCCESS(rc))
+ rc = DnDTransferListAppendRootsFromBuffer(&pTransfer->List, DNDTRANSFERLISTFMT_URI,
+ (const char *)pCtx->Meta.pvData, pCtx->Meta.cbData, DND_PATH_SEPARATOR_STR,
+ DNDTRANSFERLIST_FLAGS_NONE);
+ /* Validation. */
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cRoots = DnDTransferListGetRootCount(&pTransfer->List);
+
+ LogRel2(("DnD: Received %RU64 root entries from guest\n", cRoots));
+
+ if ( cRoots == 0
+ || cRoots > pTransfer->cObjToProcess)
+ {
+ LogRel(("DnD: Number of root entries invalid / mismatch: Got %RU64, expected %RU64\n",
+ cRoots, pTransfer->cObjToProcess));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Update our process with the data we already received. */
+ rc = updateProgress(pCtx, pCtx->pState, cbMetaAnnounced);
+ AssertRC(rc);
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("DnD: Error building root entry list, rc=%Rrc\n", rc));
+ }
+ else /* Raw data. */
+ {
+ rc = updateProgress(pCtx, pCtx->pState, cbData);
+ AssertRC(rc);
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("DnD: Error receiving meta data, rc=%Rrc\n", rc));
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = VERR_NO_MEMORY;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+int GuestDnDSource::i_onReceiveDir(GuestDnDRecvCtx *pCtx, const char *pszPath, uint32_t cbPath, uint32_t fMode)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(cbPath, VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("pszPath=%s, cbPath=%RU32, fMode=0x%x\n", pszPath, cbPath, fMode));
+
+ const PDNDTRANSFEROBJECT pObj = &pCtx->Transfer.ObjCur;
+ const PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles;
+
+ int rc = DnDTransferObjectInitEx(pObj, DNDTRANSFEROBJTYPE_DIRECTORY,
+ DnDDroppedFilesGetDirAbs(pDF), pszPath);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pcszPathAbs = DnDTransferObjectGetSourcePath(pObj);
+ AssertPtr(pcszPathAbs);
+
+ rc = RTDirCreateFullPath(pcszPathAbs, fMode);
+ if (RT_SUCCESS(rc))
+ {
+ pCtx->Transfer.cObjProcessed++;
+ if (pCtx->Transfer.cObjProcessed <= pCtx->Transfer.cObjToProcess)
+ {
+ rc = DnDDroppedFilesAddDir(pDF, pcszPathAbs);
+ }
+ else
+ rc = VERR_TOO_MUCH_DATA;
+
+ DnDTransferObjectDestroy(pObj);
+
+ if (RT_FAILURE(rc))
+ LogRel2(("DnD: Created guest directory '%s' on host\n", pcszPathAbs));
+ }
+ else
+ LogRel(("DnD: Error creating guest directory '%s' on host, rc=%Rrc\n", pcszPathAbs, rc));
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("DnD: Receiving guest directory '%s' failed with rc=%Rrc\n", pszPath, rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives a file header from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param pszPath File path of file to use.
+ * @param cbPath Size (in bytes, including terminator) of file path.
+ * @param cbSize File size (in bytes) to receive.
+ * @param fMode File mode to use.
+ * @param fFlags Additional receive flags; not used yet.
+ */
+int GuestDnDSource::i_onReceiveFileHdr(GuestDnDRecvCtx *pCtx, const char *pszPath, uint32_t cbPath,
+ uint64_t cbSize, uint32_t fMode, uint32_t fFlags)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(cbPath, VERR_INVALID_PARAMETER);
+ AssertReturn(fMode, VERR_INVALID_PARAMETER);
+ /* fFlags are optional. */
+
+ RT_NOREF(fFlags);
+
+ LogFlowFunc(("pszPath=%s, cbPath=%RU32, cbSize=%RU64, fMode=0x%x, fFlags=0x%x\n", pszPath, cbPath, cbSize, fMode, fFlags));
+
+ AssertMsgReturn(cbSize <= pCtx->cbExtra,
+ ("File size (%RU64) exceeds extra size to transfer (%RU64)\n", cbSize, pCtx->cbExtra), VERR_INVALID_PARAMETER);
+ AssertMsgReturn( pCtx->isComplete() == false
+ && pCtx->Transfer.cObjToProcess,
+ ("Data transfer already complete, bailing out\n"), VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+
+ do
+ {
+ const PDNDTRANSFEROBJECT pObj = &pCtx->Transfer.ObjCur;
+
+ if ( DnDTransferObjectIsOpen(pObj)
+ && !DnDTransferObjectIsComplete(pObj))
+ {
+ AssertMsgFailed(("Object '%s' not complete yet\n", DnDTransferObjectGetSourcePath(pObj)));
+ rc = VERR_WRONG_ORDER;
+ break;
+ }
+
+ const PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles;
+
+ rc = DnDTransferObjectInitEx(pObj, DNDTRANSFEROBJTYPE_FILE, DnDDroppedFilesGetDirAbs(pDF), pszPath);
+ AssertRCBreak(rc);
+
+ const char *pcszSource = DnDTransferObjectGetSourcePath(pObj);
+ AssertPtrBreakStmt(pcszSource, VERR_INVALID_POINTER);
+
+ /** @todo Add sparse file support based on fFlags? (Use Open(..., fFlags | SPARSE). */
+ rc = DnDTransferObjectOpen(pObj, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE,
+ (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR, DNDTRANSFEROBJECT_FLAGS_NONE);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DnD: Error opening/creating guest file '%s' on host, rc=%Rrc\n", pcszSource, rc));
+ break;
+ }
+
+ /* Note: Protocol v1 does not send any file sizes, so always 0. */
+ if (m_pState->m_uProtocolVersion >= 2)
+ rc = DnDTransferObjectSetSize(pObj, cbSize);
+
+ /** @todo Unescape path before printing. */
+ LogRel2(("DnD: Transferring guest file '%s' to host (%RU64 bytes, mode %#x)\n",
+ pcszSource, DnDTransferObjectGetSize(pObj), DnDTransferObjectGetMode(pObj)));
+
+ /** @todo Set progress object title to current file being transferred? */
+
+ if (DnDTransferObjectIsComplete(pObj)) /* 0-byte file? We're done already. */
+ {
+ LogRel2(("DnD: Transferring guest file '%s' (0 bytes) to host complete\n", pcszSource));
+
+ pCtx->Transfer.cObjProcessed++;
+ if (pCtx->Transfer.cObjProcessed <= pCtx->Transfer.cObjToProcess)
+ {
+ /* Add for having a proper rollback. */
+ rc = DnDDroppedFilesAddFile(pDF, pcszSource);
+ }
+ else
+ rc = VERR_TOO_MUCH_DATA;
+
+ DnDTransferObjectDestroy(pObj);
+ }
+
+ } while (0);
+
+ if (RT_FAILURE(rc))
+ LogRel(("DnD: Error receiving guest file header, rc=%Rrc\n", rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives file data from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param pvData Pointer to file data received from the guest.
+ * @param pCtx Size (in bytes) of file data received from the guest.
+ */
+int GuestDnDSource::i_onReceiveFileData(GuestDnDRecvCtx *pCtx, const void *pvData, uint32_t cbData)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pvData=%p, cbData=%RU32, cbBlockSize=%RU32\n", pvData, cbData, mData.mcbBlockSize));
+
+ /*
+ * Sanity checking.
+ */
+ if (cbData > mData.mcbBlockSize)
+ return VERR_INVALID_PARAMETER;
+
+ do
+ {
+ const PDNDTRANSFEROBJECT pObj = &pCtx->Transfer.ObjCur;
+
+ const char *pcszSource = DnDTransferObjectGetSourcePath(pObj);
+ AssertPtrBreakStmt(pcszSource, VERR_INVALID_POINTER);
+
+ AssertMsgReturn(DnDTransferObjectIsOpen(pObj),
+ ("Object '%s' not open (anymore)\n", pcszSource), VERR_WRONG_ORDER);
+ AssertMsgReturn(DnDTransferObjectIsComplete(pObj) == false,
+ ("Object '%s' already marked as complete\n", pcszSource), VERR_WRONG_ORDER);
+
+ uint32_t cbWritten;
+ rc = DnDTransferObjectWrite(pObj, pvData, cbData, &cbWritten);
+ if (RT_FAILURE(rc))
+ LogRel(("DnD: Error writing guest file data for '%s', rc=%Rrc\n", pcszSource, rc));
+
+ Assert(cbWritten <= cbData);
+ if (cbWritten < cbData)
+ {
+ LogRel(("DnD: Only written %RU32 of %RU32 bytes of guest file '%s' -- disk full?\n",
+ cbWritten, cbData, pcszSource));
+ rc = VERR_IO_GEN_FAILURE; /** @todo Find a better rc. */
+ break;
+ }
+
+ rc = updateProgress(pCtx, pCtx->pState, cbWritten);
+ AssertRCBreak(rc);
+
+ if (DnDTransferObjectIsComplete(pObj))
+ {
+ LogRel2(("DnD: Transferring guest file '%s' to host complete\n", pcszSource));
+
+ pCtx->Transfer.cObjProcessed++;
+ if (pCtx->Transfer.cObjProcessed > pCtx->Transfer.cObjToProcess)
+ rc = VERR_TOO_MUCH_DATA;
+
+ DnDTransferObjectDestroy(pObj);
+ }
+
+ } while (0);
+
+ if (RT_FAILURE(rc))
+ LogRel(("DnD: Error receiving guest file data, rc=%Rrc\n", rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
+
+/**
+ * Main function to receive DnD data from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param msTimeout Timeout (in ms) to wait for receiving data.
+ */
+int GuestDnDSource::i_receiveData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ /* Sanity. */
+ AssertMsgReturn(pCtx->enmAction,
+ ("Action to perform is none when it shouldn't\n"), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(pCtx->strFmtReq.isNotEmpty(),
+ ("Requested format from host is empty when it shouldn't\n"), VERR_INVALID_PARAMETER);
+
+ /*
+ * Do we need to receive a different format than initially requested?
+ *
+ * For example, receiving a file link as "text/plain" requires still to receive
+ * the file from the guest as "text/uri-list" first, then pointing to
+ * the file path on the host in the "text/plain" data returned.
+ */
+
+ bool fFoundFormat = true; /* Whether we've found a common format between host + guest. */
+
+ LogFlowFunc(("strFmtReq=%s, strFmtRecv=%s, enmAction=0x%x\n",
+ pCtx->strFmtReq.c_str(), pCtx->strFmtRecv.c_str(), pCtx->enmAction));
+
+ /* Plain text wanted? */
+ if ( pCtx->strFmtReq.equalsIgnoreCase("text/plain")
+ || pCtx->strFmtReq.equalsIgnoreCase("text/plain;charset=utf-8"))
+ {
+ /* Did the guest offer a file? Receive a file instead. */
+ if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->lstFmtOffered))
+ pCtx->strFmtRecv = "text/uri-list";
+ /* Guest only offers (plain) text. */
+ else
+ pCtx->strFmtRecv = "text/plain;charset=utf-8";
+
+ /** @todo Add more conversions here. */
+ }
+ /* File(s) wanted? */
+ else if (pCtx->strFmtReq.equalsIgnoreCase("text/uri-list"))
+ {
+ /* Does the guest support sending files? */
+ if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->lstFmtOffered))
+ pCtx->strFmtRecv = "text/uri-list";
+ else /* Bail out. */
+ fFoundFormat = false;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ if (fFoundFormat)
+ {
+ if (!pCtx->strFmtRecv.equals(pCtx->strFmtReq))
+ LogRel2(("DnD: Requested data in format '%s', receiving in intermediate format '%s' now\n",
+ pCtx->strFmtReq.c_str(), pCtx->strFmtRecv.c_str()));
+
+ /*
+ * Call the appropriate receive handler based on the data format to handle.
+ */
+ bool fURIData = DnDMIMENeedsDropDir(pCtx->strFmtRecv.c_str(), pCtx->strFmtRecv.length());
+ if (fURIData)
+ {
+ rc = i_receiveTransferData(pCtx, msTimeout);
+ }
+ else
+ {
+ rc = i_receiveRawData(pCtx, msTimeout);
+ }
+ }
+ else /* Just inform the user (if verbose release logging is enabled). */
+ {
+ LogRel(("DnD: The guest does not support format '%s':\n", pCtx->strFmtReq.c_str()));
+ LogRel(("DnD: Guest offered the following formats:\n"));
+ for (size_t i = 0; i < pCtx->lstFmtOffered.size(); i++)
+ LogRel(("DnD:\tFormat #%zu: %s\n", i, pCtx->lstFmtOffered.at(i).c_str()));
+
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DnD: Receiving data from guest failed with %Rrc\n", rc));
+
+ /* Let the guest side know first. */
+ sendCancel();
+
+ /* Reset state. */
+ i_reset();
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives raw (meta) data from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param msTimeout Timeout (in ms) to wait for receiving data.
+ */
+int GuestDnDSource::i_receiveRawData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ int rc;
+
+ LogFlowFuncEnter();
+
+ GuestDnDState *pState = pCtx->pState;
+ AssertPtr(pCtx->pState);
+
+ GuestDnD *pInst = GuestDnDInst();
+ if (!pInst)
+ return VERR_INVALID_POINTER;
+
+#define REGISTER_CALLBACK(x) \
+ do { \
+ rc = pState->setCallback(x, i_receiveRawDataCallback, pCtx); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ } while (0)
+
+#define UNREGISTER_CALLBACK(x) \
+ do { \
+ int rc2 = pState->setCallback(x, NULL); \
+ AssertRC(rc2); \
+ } while (0)
+
+ /*
+ * Register callbacks.
+ */
+ REGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
+ REGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
+ REGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
+ if (m_pState->m_uProtocolVersion >= 3)
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA_HDR);
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA);
+
+ do
+ {
+ /*
+ * Receive the raw data.
+ */
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_GH_EVT_DROPPED);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendPointer((void*)pCtx->strFmtRecv.c_str(), (uint32_t)pCtx->strFmtRecv.length() + 1);
+ Msg.appendUInt32((uint32_t)pCtx->strFmtRecv.length() + 1);
+ Msg.appendUInt32(pCtx->enmAction);
+
+ /* Make the initial call to the guest by telling that we initiated the "dropped" event on
+ * the host and therefore now waiting for the actual raw data. */
+ rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(rc))
+ {
+ rc = waitForEvent(&pCtx->EventCallback, pCtx->pState, msTimeout);
+ if (RT_SUCCESS(rc))
+ rc = pCtx->pState->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
+ }
+
+ } while (0);
+
+ /*
+ * Unregister callbacks.
+ */
+ UNREGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
+ if (m_pState->m_uProtocolVersion >= 3)
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA_HDR);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA);
+
+#undef REGISTER_CALLBACK
+#undef UNREGISTER_CALLBACK
+
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CANCELLED) /* Transfer was cancelled by the host. */
+ {
+ /*
+ * Now that we've cleaned up tell the guest side to cancel.
+ * This does not imply we're waiting for the guest to react, as the
+ * host side never must depend on anything from the guest.
+ */
+ int rc2 = sendCancel();
+ AssertRC(rc2);
+
+ rc2 = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED);
+ AssertRC(rc2);
+ }
+ else if (rc != VERR_DND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
+ {
+ int rc2 = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR,
+ rc, GuestDnDSource::i_hostErrorToString(rc));
+ AssertRC(rc2);
+ }
+
+ rc = VINF_SUCCESS; /* The error was handled by the setProgress() calls above. */
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives transfer data (files / directories / ...) from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param msTimeout Timeout (in ms) to wait for receiving data.
+ */
+int GuestDnDSource::i_receiveTransferData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ int rc;
+
+ LogFlowFuncEnter();
+
+ GuestDnDState *pState = pCtx->pState;
+ AssertPtr(pCtx->pState);
+
+ GuestDnD *pInst = GuestDnDInst();
+ if (!pInst)
+ return VERR_INVALID_POINTER;
+
+#define REGISTER_CALLBACK(x) \
+ do { \
+ rc = pState->setCallback(x, i_receiveTransferDataCallback, pCtx); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ } while (0)
+
+#define UNREGISTER_CALLBACK(x) \
+ do { \
+ int rc2 = pState->setCallback(x, NULL); \
+ AssertRC(rc2); \
+ } while (0)
+
+ /*
+ * Register callbacks.
+ */
+ /* Guest callbacks. */
+ REGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
+ REGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
+ REGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
+ if (m_pState->m_uProtocolVersion >= 3)
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA_HDR);
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA);
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DIR);
+ if (m_pState->m_uProtocolVersion >= 2)
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_FILE_HDR);
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_FILE_DATA);
+
+ const PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles;
+
+ do
+ {
+ rc = DnDDroppedFilesOpenTemp(pDF, 0 /* fFlags */);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DnD: Opening dropped files directory '%s' on the host failed with rc=%Rrc\n",
+ DnDDroppedFilesGetDirAbs(pDF), rc));
+ break;
+ }
+
+ /*
+ * Receive the transfer list.
+ */
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_GH_EVT_DROPPED);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendPointer((void*)pCtx->strFmtRecv.c_str(), (uint32_t)pCtx->strFmtRecv.length() + 1);
+ Msg.appendUInt32((uint32_t)pCtx->strFmtRecv.length() + 1);
+ Msg.appendUInt32(pCtx->enmAction);
+
+ /* Make the initial call to the guest by telling that we initiated the "dropped" event on
+ * the host and therefore now waiting for the actual URI data. */
+ rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("Waiting ...\n"));
+
+ rc = waitForEvent(&pCtx->EventCallback, pCtx->pState, msTimeout);
+ if (RT_SUCCESS(rc))
+ rc = pCtx->pState->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
+
+ LogFlowFunc(("Waiting ended with rc=%Rrc\n", rc));
+ }
+
+ } while (0);
+
+ /*
+ * Unregister callbacks.
+ */
+ UNREGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA_HDR);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DIR);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_FILE_HDR);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_FILE_DATA);
+
+#undef REGISTER_CALLBACK
+#undef UNREGISTER_CALLBACK
+
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = DnDDroppedFilesRollback(pDF);
+ if (RT_FAILURE(rc2))
+ LogRel(("DnD: Deleting left over temporary files failed (%Rrc), please remove directory '%s' manually\n",
+ rc2, DnDDroppedFilesGetDirAbs(pDF)));
+
+ if (rc == VERR_CANCELLED)
+ {
+ /*
+ * Now that we've cleaned up tell the guest side to cancel.
+ * This does not imply we're waiting for the guest to react, as the
+ * host side never must depend on anything from the guest.
+ */
+ rc2 = sendCancel();
+ AssertRC(rc2);
+
+ rc2 = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED);
+ AssertRC(rc2);
+
+ /* Cancelling is not an error, just set success here. */
+ rc = VINF_SUCCESS;
+ }
+ else if (rc != VERR_DND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
+ {
+ rc2 = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR,
+ rc, GuestDnDSource::i_hostErrorToString(rc));
+ AssertRC(rc2);
+ }
+ }
+
+ DnDDroppedFilesClose(pDF);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Static HGCM service callback which handles receiving raw data.
+ *
+ * @returns VBox status code. Will get sent back to the host service.
+ * @param uMsg HGCM message ID (function number).
+ * @param pvParms Pointer to additional message data. Optional and can be NULL.
+ * @param cbParms Size (in bytes) additional message data. Optional and can be 0.
+ * @param pvUser User-supplied pointer on callback registration.
+ */
+/* static */
+DECLCALLBACK(int) GuestDnDSource::i_receiveRawDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
+{
+ GuestDnDRecvCtx *pCtx = (GuestDnDRecvCtx *)pvUser;
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ GuestDnDSource *pThis = pCtx->pSource;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
+
+ int rc = VINF_SUCCESS;
+
+ int rcCallback = VINF_SUCCESS; /* rc for the callback. */
+ bool fNotify = false;
+
+ switch (uMsg)
+ {
+ case GUEST_DND_FN_CONNECT:
+ /* Nothing to do here (yet). */
+ break;
+
+ case GUEST_DND_FN_DISCONNECT:
+ rc = VERR_CANCELLED;
+ break;
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+ case GUEST_DND_FN_GH_SND_DATA_HDR:
+ {
+ PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
+ break;
+ }
+ case GUEST_DND_FN_GH_SND_DATA:
+ {
+ PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ rc = pThis->i_onReceiveData(pCtx, &pCBData->data);
+ break;
+ }
+ case GUEST_DND_FN_EVT_ERROR:
+ {
+ PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ pCtx->pState->reset();
+
+ if (RT_SUCCESS(pCBData->rc))
+ {
+ AssertMsgFailed(("Received guest error with no error code set\n"));
+ pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
+ }
+ else if (pCBData->rc == VERR_WRONG_ORDER)
+ {
+ rc = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED);
+ }
+ else
+ rc = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
+ GuestDnDSource::i_guestErrorToString(pCBData->rc));
+
+ LogRel3(("DnD: Guest reported data transfer error: %Rrc\n", pCBData->rc));
+
+ if (RT_SUCCESS(rc))
+ rcCallback = VERR_DND_GUEST_ERROR;
+ break;
+ }
+#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if ( RT_FAILURE(rc)
+ || RT_FAILURE(rcCallback))
+ {
+ fNotify = true;
+ if (RT_SUCCESS(rcCallback))
+ rcCallback = rc;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ switch (rc)
+ {
+ case VERR_NO_DATA:
+ LogRel2(("DnD: Data transfer to host complete\n"));
+ break;
+
+ case VERR_CANCELLED:
+ LogRel2(("DnD: Data transfer to host canceled\n"));
+ break;
+
+ default:
+ LogRel(("DnD: Error %Rrc occurred, aborting data transfer to host\n", rc));
+ break;
+ }
+
+ /* Unregister this callback. */
+ AssertPtr(pCtx->pState);
+ int rc2 = pCtx->pState->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
+ AssertRC(rc2);
+ }
+
+ /* All data processed? */
+ if (pCtx->isComplete())
+ fNotify = true;
+
+ LogFlowFunc(("cbProcessed=%RU64, cbExtra=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n",
+ pCtx->cbProcessed, pCtx->cbExtra, fNotify, rcCallback, rc));
+
+ if (fNotify)
+ {
+ int rc2 = pCtx->EventCallback.Notify(rcCallback);
+ AssertRC(rc2);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc; /* Tell the guest. */
+}
+
+/**
+ * Static HGCM service callback which handles receiving transfer data from the guest.
+ *
+ * @returns VBox status code. Will get sent back to the host service.
+ * @param uMsg HGCM message ID (function number).
+ * @param pvParms Pointer to additional message data. Optional and can be NULL.
+ * @param cbParms Size (in bytes) additional message data. Optional and can be 0.
+ * @param pvUser User-supplied pointer on callback registration.
+ */
+/* static */
+DECLCALLBACK(int) GuestDnDSource::i_receiveTransferDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
+{
+ GuestDnDRecvCtx *pCtx = (GuestDnDRecvCtx *)pvUser;
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ GuestDnDSource *pThis = pCtx->pSource;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
+
+ int rc = VINF_SUCCESS;
+
+ int rcCallback = VINF_SUCCESS; /* rc for the callback. */
+ bool fNotify = false;
+
+ switch (uMsg)
+ {
+ case GUEST_DND_FN_CONNECT:
+ /* Nothing to do here (yet). */
+ break;
+
+ case GUEST_DND_FN_DISCONNECT:
+ rc = VERR_CANCELLED;
+ break;
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+ case GUEST_DND_FN_GH_SND_DATA_HDR:
+ {
+ PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
+ break;
+ }
+ case GUEST_DND_FN_GH_SND_DATA:
+ {
+ PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ rc = pThis->i_onReceiveData(pCtx, &pCBData->data);
+ break;
+ }
+ case GUEST_DND_FN_GH_SND_DIR:
+ {
+ PVBOXDNDCBSNDDIRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDIRDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDDIRDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_DIR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ rc = pThis->i_onReceiveDir(pCtx, pCBData->pszPath, pCBData->cbPath, pCBData->fMode);
+ break;
+ }
+ case GUEST_DND_FN_GH_SND_FILE_HDR:
+ {
+ PVBOXDNDCBSNDFILEHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEHDRDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDFILEHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_FILE_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->pszFilePath, pCBData->cbFilePath,
+ pCBData->cbSize, pCBData->fMode, pCBData->fFlags);
+ break;
+ }
+ case GUEST_DND_FN_GH_SND_FILE_DATA:
+ {
+ PVBOXDNDCBSNDFILEDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEDATADATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDFILEDATADATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_FILE_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ if (pThis->m_pState->m_uProtocolVersion <= 1)
+ {
+ /**
+ * Notes for protocol v1 (< VBox 5.0):
+ * - Every time this command is being sent it includes the file header,
+ * so just process both calls here.
+ * - There was no information whatsoever about the total file size; the old code only
+ * appended data to the desired file. So just pass 0 as cbSize.
+ */
+ rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->u.v1.pszFilePath, pCBData->u.v1.cbFilePath,
+ 0 /* cbSize */, pCBData->u.v1.fMode, 0 /* fFlags */);
+ if (RT_SUCCESS(rc))
+ rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
+ }
+ else /* Protocol v2 and up. */
+ rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
+ break;
+ }
+ case GUEST_DND_FN_EVT_ERROR:
+ {
+ PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ pCtx->pState->reset();
+
+ if (RT_SUCCESS(pCBData->rc))
+ {
+ AssertMsgFailed(("Received guest error with no error code set\n"));
+ pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
+ }
+ else if (pCBData->rc == VERR_WRONG_ORDER)
+ {
+ rc = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED);
+ }
+ else
+ rc = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
+ GuestDnDSource::i_guestErrorToString(pCBData->rc));
+
+ LogRel3(("DnD: Guest reported file transfer error: %Rrc\n", pCBData->rc));
+
+ if (RT_SUCCESS(rc))
+ rcCallback = VERR_DND_GUEST_ERROR;
+ break;
+ }
+#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if ( RT_FAILURE(rc)
+ || RT_FAILURE(rcCallback))
+ {
+ fNotify = true;
+ if (RT_SUCCESS(rcCallback))
+ rcCallback = rc;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ switch (rc)
+ {
+ case VERR_NO_DATA:
+ LogRel2(("DnD: File transfer to host complete\n"));
+ break;
+
+ case VERR_CANCELLED:
+ LogRel2(("DnD: File transfer to host canceled\n"));
+ break;
+
+ default:
+ LogRel(("DnD: Error %Rrc occurred, aborting file transfer to host\n", rc));
+ break;
+ }
+
+ /* Unregister this callback. */
+ AssertPtr(pCtx->pState);
+ int rc2 = pCtx->pState->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
+ AssertRC(rc2);
+ }
+
+ /* All data processed? */
+ if ( pCtx->Transfer.isComplete()
+ && pCtx->isComplete())
+ {
+ fNotify = true;
+ }
+
+ LogFlowFunc(("cbProcessed=%RU64, cbExtra=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n",
+ pCtx->cbProcessed, pCtx->cbExtra, fNotify, rcCallback, rc));
+
+ if (fNotify)
+ {
+ int rc2 = pCtx->EventCallback.Notify(rcCallback);
+ AssertRC(rc2);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc; /* Tell the guest. */
+}
+
diff --git a/src/VBox/Main/src-client/GuestDnDTargetImpl.cpp b/src/VBox/Main/src-client/GuestDnDTargetImpl.cpp
new file mode 100644
index 00000000..50008d80
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestDnDTargetImpl.cpp
@@ -0,0 +1,1789 @@
+/* $Id: GuestDnDTargetImpl.cpp $ */
+/** @file
+ * VBox Console COM Class implementation - Guest drag'n drop target.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_GUEST_DND //LOG_GROUP_MAIN_GUESTDNDTARGET
+#include "LoggingNew.h"
+
+#include "GuestImpl.h"
+#include "GuestDnDTargetImpl.h"
+#include "ConsoleImpl.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+#include "ThreadTask.h"
+
+#include <algorithm> /* For std::find(). */
+
+#include <iprt/asm.h>
+#include <iprt/file.h>
+#include <iprt/dir.h>
+#include <iprt/path.h>
+#include <iprt/uri.h>
+#include <iprt/cpp/utils.h> /* For unconst(). */
+
+#include <VBox/com/array.h>
+
+#include <VBox/GuestHost/DragAndDrop.h>
+#include <VBox/HostServices/Service.h>
+
+
+/**
+ * Base class for a target task.
+ */
+class GuestDnDTargetTask : public ThreadTask
+{
+public:
+
+ GuestDnDTargetTask(GuestDnDTarget *pTarget)
+ : ThreadTask("GenericGuestDnDTargetTask")
+ , mTarget(pTarget)
+ , mRC(VINF_SUCCESS) { }
+
+ virtual ~GuestDnDTargetTask(void) { }
+
+ /** Returns the overall result of the task. */
+ int getRC(void) const { return mRC; }
+ /** Returns if the overall result of the task is ok (succeeded) or not. */
+ bool isOk(void) const { return RT_SUCCESS(mRC); }
+
+protected:
+
+ /** COM object pointer to the parent (source). */
+ const ComObjPtr<GuestDnDTarget> mTarget;
+ /** Overall result of the task. */
+ int mRC;
+};
+
+/**
+ * Task structure for sending data to a target using
+ * a worker thread.
+ */
+class GuestDnDSendDataTask : public GuestDnDTargetTask
+{
+public:
+
+ GuestDnDSendDataTask(GuestDnDTarget *pTarget, GuestDnDSendCtx *pCtx)
+ : GuestDnDTargetTask(pTarget),
+ mpCtx(pCtx)
+ {
+ m_strTaskName = "dndTgtSndData";
+ }
+
+ void handler()
+ {
+ const ComObjPtr<GuestDnDTarget> pThis(mTarget);
+ Assert(!pThis.isNull());
+
+ AutoCaller autoCaller(pThis);
+ if (autoCaller.isNotOk())
+ return;
+
+ /* ignore rc */ pThis->i_sendData(mpCtx, RT_INDEFINITE_WAIT /* msTimeout */);
+ }
+
+ virtual ~GuestDnDSendDataTask(void) { }
+
+protected:
+
+ /** Pointer to send data context. */
+ GuestDnDSendCtx *mpCtx;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+GuestDnDTarget::GuestDnDTarget(void)
+ : GuestDnDBase(this) { }
+
+GuestDnDTarget::~GuestDnDTarget(void) { }
+
+HRESULT GuestDnDTarget::FinalConstruct(void)
+{
+ /* Set the maximum block size our guests can handle to 64K. This always has
+ * been hardcoded until now. */
+ /* Note: Never ever rely on information from the guest; the host dictates what and
+ * how to do something, so try to negogiate a sensible value here later. */
+ mData.mcbBlockSize = DND_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */
+
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void GuestDnDTarget::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDnDTarget::init(const ComObjPtr<Guest>& pGuest)
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(m_pGuest) = pGuest;
+
+ /* Set the response we're going to use for this object.
+ *
+ * At the moment we only have one response total, as we
+ * don't allow
+ * 1) parallel transfers (multiple G->H at the same time)
+ * nor 2) mixed transfers (G->H + H->G at the same time).
+ */
+ m_pState = GuestDnDInst()->getState();
+ AssertPtrReturn(m_pState, E_POINTER);
+
+ /* Confirm a successful initialization when it's the case. */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void GuestDnDTarget::uninit(void)
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+}
+
+// implementation of wrapped IDnDBase methods.
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDnDTarget::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aSupported = GuestDnDBase::i_isFormatSupported(aFormat) ? TRUE : FALSE;
+
+ return S_OK;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDTarget::getFormats(GuestDnDMIMEList &aFormats)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aFormats = GuestDnDBase::i_getFormats();
+
+ return S_OK;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDTarget::addFormats(const GuestDnDMIMEList &aFormats)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return GuestDnDBase::i_addFormats(aFormats);
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDTarget::removeFormats(const GuestDnDMIMEList &aFormats)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return GuestDnDBase::i_removeFormats(aFormats);
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+// implementation of wrapped IDnDTarget methods.
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDnDTarget::enter(ULONG aScreenId, ULONG aX, ULONG aY,
+ DnDAction_T aDefaultAction,
+ const std::vector<DnDAction_T> &aAllowedActions,
+ const GuestDnDMIMEList &aFormats,
+ DnDAction_T *aResultAction)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ /* Input validation. */
+ if (aDefaultAction == DnDAction_Ignore)
+ return setError(E_INVALIDARG, tr("No default action specified"));
+ if (!aAllowedActions.size())
+ return setError(E_INVALIDARG, tr("Number of allowed actions is empty"));
+ if (!aFormats.size())
+ return setError(E_INVALIDARG, tr("Number of supported formats is empty"));
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.rc();
+
+ /* Default action is ignoring. */
+ DnDAction_T resAction = DnDAction_Ignore;
+
+ /* Check & convert the drag & drop actions. */
+ VBOXDNDACTION dndActionDefault = 0;
+ VBOXDNDACTIONLIST dndActionListAllowed = 0;
+ GuestDnD::toHGCMActions(aDefaultAction, &dndActionDefault,
+ aAllowedActions, &dndActionListAllowed);
+
+ /* If there is no usable action, ignore this request. */
+ if (isDnDIgnoreAction(dndActionDefault))
+ return S_OK;
+
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtrReturn(pState, E_POINTER);
+
+ /*
+ * Make a flat data string out of the supported format list.
+ * In the GuestDnDTarget case the source formats are from the host,
+ * as GuestDnDTarget acts as a source for the guest.
+ */
+ Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
+ if (strFormats.isEmpty())
+ return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
+ const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */
+
+ LogRel2(("DnD: Offered formats to guest:\n"));
+ RTCList<RTCString> lstFormats = strFormats.split(DND_PATH_SEPARATOR_STR);
+ for (size_t i = 0; i < lstFormats.size(); i++)
+ LogRel2(("DnD: \t%s\n", lstFormats[i].c_str()));
+
+ /* Save the formats offered to the guest. This is needed to later
+ * decide what to do with the data when sending stuff to the guest. */
+ m_lstFmtOffered = aFormats;
+ Assert(m_lstFmtOffered.size());
+
+ /* Adjust the coordinates in a multi-monitor setup. */
+ HRESULT hrc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
+ if (SUCCEEDED(hrc))
+ {
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_HG_EVT_ENTER);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendUInt32(aScreenId);
+ Msg.appendUInt32(aX);
+ Msg.appendUInt32(aY);
+ Msg.appendUInt32(dndActionDefault);
+ Msg.appendUInt32(dndActionListAllowed);
+ Msg.appendPointer((void *)strFormats.c_str(), cbFormats);
+ Msg.appendUInt32(cbFormats);
+
+ int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest;
+ if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest)))
+ {
+ resAction = GuestDnD::toMainAction(m_pState->getActionDefault());
+
+ LogRel2(("DnD: Host enters the VM window at %RU32,%RU32 (screen %u, default action is '%s') -> guest reported back action '%s'\n",
+ aX, aY, aScreenId, DnDActionToStr(dndActionDefault), DnDActionToStr(resAction)));
+
+ pState->set(VBOXDNDSTATE_ENTERED);
+ }
+ else
+ hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Entering VM window failed"));
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_ACCESS_DENIED:
+ {
+ hrc = i_setErrorAndReset(tr("Drag and drop to guest not allowed. Select the right mode first"));
+ break;
+ }
+
+ case VERR_NOT_SUPPORTED:
+ {
+ hrc = i_setErrorAndReset(tr("Drag and drop to guest not possible -- either the guest OS does not support this, "
+ "or the Guest Additions are not installed"));
+ break;
+ }
+
+ default:
+ hrc = i_setErrorAndReset(vrc, tr("Entering VM window failed"));
+ break;
+ }
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ if (aResultAction)
+ *aResultAction = resAction;
+ }
+
+ LogFlowFunc(("hrc=%Rhrc, resAction=%ld\n", hrc, resAction));
+ return hrc;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDTarget::move(ULONG aScreenId, ULONG aX, ULONG aY,
+ DnDAction_T aDefaultAction,
+ const std::vector<DnDAction_T> &aAllowedActions,
+ const GuestDnDMIMEList &aFormats,
+ DnDAction_T *aResultAction)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ /* Input validation. */
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.rc();
+
+ /* Default action is ignoring. */
+ DnDAction_T resAction = DnDAction_Ignore;
+
+ /* Check & convert the drag & drop actions. */
+ VBOXDNDACTION dndActionDefault = 0;
+ VBOXDNDACTIONLIST dndActionListAllowed = 0;
+ GuestDnD::toHGCMActions(aDefaultAction, &dndActionDefault,
+ aAllowedActions, &dndActionListAllowed);
+
+ /* If there is no usable action, ignore this request. */
+ if (isDnDIgnoreAction(dndActionDefault))
+ return S_OK;
+
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtrReturn(pState, E_POINTER);
+
+ /*
+ * Make a flat data string out of the supported format list.
+ * In the GuestDnDTarget case the source formats are from the host,
+ * as GuestDnDTarget acts as a source for the guest.
+ */
+ Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
+ if (strFormats.isEmpty())
+ return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
+ const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */
+
+ HRESULT hrc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
+ if (SUCCEEDED(hrc))
+ {
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_HG_EVT_MOVE);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendUInt32(aScreenId);
+ Msg.appendUInt32(aX);
+ Msg.appendUInt32(aY);
+ Msg.appendUInt32(dndActionDefault);
+ Msg.appendUInt32(dndActionListAllowed);
+ Msg.appendPointer((void *)strFormats.c_str(), cbFormats);
+ Msg.appendUInt32(cbFormats);
+
+ int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest;
+ if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest)))
+ {
+ resAction = GuestDnD::toMainAction(pState->getActionDefault());
+
+ LogRel2(("DnD: Host moved to %RU32,%RU32 in VM window (screen %u, default action is '%s') -> guest reported back action '%s'\n",
+ aX, aY, aScreenId, DnDActionToStr(dndActionDefault), DnDActionToStr(resAction)));
+
+ pState->set(VBOXDNDSTATE_DRAGGING);
+ }
+ else
+ hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc,
+ tr("Moving to %RU32,%RU32 (screen %u) failed"), aX, aY, aScreenId);
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_ACCESS_DENIED:
+ {
+ hrc = i_setErrorAndReset(tr("Moving in guest not allowed. Select the right mode first"));
+ break;
+ }
+
+ case VERR_NOT_SUPPORTED:
+ {
+ hrc = i_setErrorAndReset(tr("Moving in guest not possible -- either the guest OS does not support this, "
+ "or the Guest Additions are not installed"));
+ break;
+ }
+
+ default:
+ hrc = i_setErrorAndReset(vrc, tr("Moving in VM window failed"));
+ break;
+ }
+ }
+ }
+ else
+ hrc = i_setErrorAndReset(tr("Retrieving move coordinates failed"));
+
+ if (SUCCEEDED(hrc))
+ {
+ if (aResultAction)
+ *aResultAction = resAction;
+ }
+
+ LogFlowFunc(("hrc=%Rhrc, *pResultAction=%ld\n", hrc, resAction));
+ return hrc;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDTarget::leave(ULONG uScreenId)
+{
+ RT_NOREF(uScreenId);
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.rc();
+
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtrReturn(pState, E_POINTER);
+
+ if (pState->get() == VBOXDNDSTATE_DROP_STARTED)
+ return S_OK;
+
+ HRESULT hrc = S_OK;
+
+ LogRel2(("DnD: Host left the VM window (screen %u)\n", uScreenId));
+
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_HG_EVT_LEAVE);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+
+ int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest;
+ if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest)))
+ {
+ pState->set(VBOXDNDSTATE_LEFT);
+ }
+ else
+ hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Leaving VM window failed"));
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_ACCESS_DENIED:
+ {
+ hrc = i_setErrorAndReset(tr("Leaving guest not allowed. Select the right mode first"));
+ break;
+ }
+
+ case VERR_NOT_SUPPORTED:
+ {
+ hrc = i_setErrorAndReset(tr("Leaving guest not possible -- either the guest OS does not support this, "
+ "or the Guest Additions are not installed"));
+ break;
+ }
+
+ default:
+ hrc = i_setErrorAndReset(vrc, tr("Leaving VM window failed"));
+ break;
+ }
+ }
+
+ LogFlowFunc(("hrc=%Rhrc\n", hrc));
+ return hrc;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDTarget::drop(ULONG aScreenId, ULONG aX, ULONG aY,
+ DnDAction_T aDefaultAction,
+ const std::vector<DnDAction_T> &aAllowedActions,
+ const GuestDnDMIMEList &aFormats,
+ com::Utf8Str &aFormat,
+ DnDAction_T *aResultAction)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ if (aDefaultAction == DnDAction_Ignore)
+ return setError(E_INVALIDARG, tr("Invalid default action specified"));
+ if (!aAllowedActions.size())
+ return setError(E_INVALIDARG, tr("Invalid allowed actions specified"));
+ if (!aFormats.size())
+ return setError(E_INVALIDARG, tr("No drop format(s) specified"));
+ /* aResultAction is optional. */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ /* Default action is ignoring. */
+ DnDAction_T resAct = DnDAction_Ignore;
+ Utf8Str resFmt;
+
+ /* Check & convert the drag & drop actions to HGCM codes. */
+ VBOXDNDACTION dndActionDefault = VBOX_DND_ACTION_IGNORE;
+ VBOXDNDACTIONLIST dndActionListAllowed = 0;
+ GuestDnD::toHGCMActions(aDefaultAction, &dndActionDefault,
+ aAllowedActions, &dndActionListAllowed);
+
+ /* If there is no usable action, ignore this request. */
+ if (isDnDIgnoreAction(dndActionDefault))
+ {
+ aFormat = "";
+ if (aResultAction)
+ *aResultAction = DnDAction_Ignore;
+ return S_OK;
+ }
+
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtrReturn(pState, E_POINTER);
+
+ /*
+ * Make a flat data string out of the supported format list.
+ * In the GuestDnDTarget case the source formats are from the host,
+ * as GuestDnDTarget acts as a source for the guest.
+ */
+ Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
+ if (strFormats.isEmpty())
+ return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
+ const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */
+
+ /* Adjust the coordinates in a multi-monitor setup. */
+ HRESULT hrc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
+ if (SUCCEEDED(hrc))
+ {
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_HG_EVT_DROPPED);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendUInt32(aScreenId);
+ Msg.appendUInt32(aX);
+ Msg.appendUInt32(aY);
+ Msg.appendUInt32(dndActionDefault);
+ Msg.appendUInt32(dndActionListAllowed);
+ Msg.appendPointer((void*)strFormats.c_str(), cbFormats);
+ Msg.appendUInt32(cbFormats);
+
+ LogRel2(("DnD: Host drops at %RU32,%RU32 in VM window (screen %u, default action is '%s')\n",
+ aX, aY, aScreenId, DnDActionToStr(dndActionDefault)));
+
+ int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest;
+ if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest)))
+ {
+ resAct = GuestDnD::toMainAction(pState->getActionDefault());
+ if (resAct != DnDAction_Ignore) /* Does the guest accept a drop at the current position? */
+ {
+ GuestDnDMIMEList lstFormats = pState->formats();
+ if (lstFormats.size() == 1) /* Exactly one format to use specified? */
+ {
+ resFmt = lstFormats.at(0);
+
+ LogRel2(("DnD: Guest accepted drop in format '%s' (action %#x, %zu format(s))\n",
+ resFmt.c_str(), resAct, lstFormats.size()));
+
+ pState->set(VBOXDNDSTATE_DROP_STARTED);
+ }
+ else
+ {
+ if (lstFormats.size() == 0)
+ hrc = i_setErrorAndReset(VERR_DND_GUEST_ERROR, tr("Guest accepted drop, but did not specify the format"));
+ else
+ hrc = i_setErrorAndReset(VERR_DND_GUEST_ERROR, tr("Guest accepted drop, but returned more than one drop format (%zu formats)"),
+ lstFormats.size());
+ }
+ }
+ }
+ else
+ hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Dropping into VM failed"));
+ }
+ else
+ hrc = i_setErrorAndReset(vrc, tr("Sending dropped event to guest failed"));
+ }
+ else
+ hrc = i_setErrorAndReset(hrc, tr("Retrieving drop coordinates failed"));
+
+ if (SUCCEEDED(hrc))
+ {
+ aFormat = resFmt;
+ if (aResultAction)
+ *aResultAction = resAct;
+ }
+
+ return hrc;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+/**
+ * Initiates a data transfer from the host to the guest.
+ *
+ * The source is the host, whereas the target is the guest.
+ *
+ * @return HRESULT
+ * @param aScreenId Screen ID where this data transfer was initiated from.
+ * @param aFormat Format of data to send. MIME-style.
+ * @param aData Actual data to send.
+ * @param aProgress Where to return the progress object on success.
+ */
+HRESULT GuestDnDTarget::sendData(ULONG aScreenId, const com::Utf8Str &aFormat, const std::vector<BYTE> &aData,
+ ComPtr<IProgress> &aProgress)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ /* Input validation. */
+ if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No data format specified"));
+ if (RT_UNLIKELY(!aData.size()))
+ return setError(E_INVALIDARG, tr("No data to send specified"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Check if this object still is in a pending state and bail out if so. */
+ if (m_fIsPending)
+ return setError(E_FAIL, tr("Current drop operation to guest still in progress"));
+
+ /* At the moment we only support one transfer at a time. */
+ if (GuestDnDInst()->getTargetCount())
+ return setError(E_INVALIDARG, tr("Another drag and drop operation to the guest already is in progress"));
+
+ /* Reset progress object. */
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtr(pState);
+ HRESULT hr = pState->resetProgress(m_pGuest, tr("Dropping data to guest"));
+ if (FAILED(hr))
+ return hr;
+
+ GuestDnDSendDataTask *pTask = NULL;
+
+ try
+ {
+ mData.mSendCtx.reset();
+
+ mData.mSendCtx.pTarget = this;
+ mData.mSendCtx.pState = pState;
+ mData.mSendCtx.uScreenID = aScreenId;
+
+ mData.mSendCtx.Meta.strFmt = aFormat;
+ mData.mSendCtx.Meta.add(aData);
+
+ LogRel2(("DnD: Host sends data in format '%s'\n", aFormat.c_str()));
+
+ pTask = new GuestDnDSendDataTask(this, &mData.mSendCtx);
+ if (!pTask->isOk())
+ {
+ delete pTask;
+ LogRel(("DnD: Could not create SendDataTask object\n"));
+ throw hr = E_FAIL;
+ }
+
+ /* This function delete pTask in case of exceptions,
+ * so there is no need in the call of delete operator. */
+ hr = pTask->createThreadWithType(RTTHREADTYPE_MAIN_WORKER);
+ pTask = NULL; /* Note: pTask is now owned by the worker thread. */
+ }
+ catch (std::bad_alloc &)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ catch (...)
+ {
+ LogRel(("DnD: Could not create thread for data sending task\n"));
+ hr = E_FAIL;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ /* Register ourselves at the DnD manager. */
+ GuestDnDInst()->registerTarget(this);
+
+ /* Return progress to caller. */
+ hr = pState->queryProgressTo(aProgress.asOutParam());
+ ComAssertComRC(hr);
+ }
+ else
+ hr = i_setErrorAndReset(tr("Starting thread for GuestDnDTarget failed (%Rhrc)"), hr);
+
+ LogFlowFunc(("Returning hr=%Rhrc\n", hr));
+ return hr;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+/**
+ * Returns an error string from a guest DnD error.
+ *
+ * @returns Error string.
+ * @param guestRc Guest error to return error string for.
+ */
+/* static */
+Utf8Str GuestDnDTarget::i_guestErrorToString(int guestRc)
+{
+ Utf8Str strError;
+
+ switch (guestRc)
+ {
+ case VERR_ACCESS_DENIED:
+ strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest "
+ "user does not have the appropriate access rights for. Please make sure that all selected "
+ "elements can be accessed and that your guest user has the appropriate rights"));
+ break;
+
+ case VERR_NOT_FOUND:
+ /* Should not happen due to file locking on the guest, but anyway ... */
+ strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not"
+ "found on the guest anymore. This can be the case if the guest files were moved and/or"
+ "altered while the drag and drop operation was in progress"));
+ break;
+
+ case VERR_SHARING_VIOLATION:
+ strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. "
+ "Please make sure that all selected elements can be accessed and that your guest user has "
+ "the appropriate rights"));
+ break;
+
+ case VERR_TIMEOUT:
+ strError += Utf8StrFmt(tr("The guest was not able to process the drag and drop data within time"));
+ break;
+
+ default:
+ strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc);
+ break;
+ }
+
+ return strError;
+}
+
+/**
+ * Returns an error string from a host DnD error.
+ *
+ * @returns Error string.
+ * @param hostRc Host error to return error string for.
+ */
+/* static */
+Utf8Str GuestDnDTarget::i_hostErrorToString(int hostRc)
+{
+ Utf8Str strError;
+
+ switch (hostRc)
+ {
+ case VERR_ACCESS_DENIED:
+ strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host "
+ "user does not have the appropriate access rights for. Please make sure that all selected "
+ "elements can be accessed and that your host user has the appropriate rights."));
+ break;
+
+ case VERR_NOT_FOUND:
+ /* Should not happen due to file locking on the host, but anyway ... */
+ strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not"
+ "found on the host anymore. This can be the case if the host files were moved and/or"
+ "altered while the drag and drop operation was in progress."));
+ break;
+
+ case VERR_SHARING_VIOLATION:
+ strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. "
+ "Please make sure that all selected elements can be accessed and that your host user has "
+ "the appropriate rights."));
+ break;
+
+ default:
+ strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc);
+ break;
+ }
+
+ return strError;
+}
+
+/**
+ * Resets all internal data and state.
+ */
+void GuestDnDTarget::i_reset(void)
+{
+ LogRel2(("DnD: Target reset\n"));
+
+ mData.mSendCtx.reset();
+
+ m_fIsPending = false;
+
+ /* Unregister ourselves from the DnD manager. */
+ GuestDnDInst()->unregisterTarget(this);
+}
+
+/**
+ * Main function for sending DnD host data to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ * @param msTimeout Timeout (in ms) to wait for getting the data sent.
+ */
+int GuestDnDTarget::i_sendData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ /* Don't allow receiving the actual data until our current transfer is complete. */
+ if (m_fIsPending)
+ return setError(E_FAIL, tr("Current drop operation to guest still in progress"));
+
+ /* Clear all remaining outgoing messages. */
+ m_DataBase.lstMsgOut.clear();
+
+ /**
+ * Do we need to build up a file tree?
+ * Note: The decision whether we need to build up a file tree and sending
+ * actual file data only depends on the actual formats offered by this target.
+ * If the guest does not want a transfer list ("text/uri-list") but text ("TEXT" and
+ * friends) instead, still send the data over to the guest -- the file as such still
+ * is needed on the guest in this case, as the guest then just wants a simple path
+ * instead of a transfer list (pointing to a file on the guest itself).
+ *
+ ** @todo Support more than one format; add a format<->function handler concept. Later. */
+ int vrc;
+ const bool fHasURIList = std::find(m_lstFmtOffered.begin(),
+ m_lstFmtOffered.end(), "text/uri-list") != m_lstFmtOffered.end();
+ if (fHasURIList)
+ {
+ vrc = i_sendTransferData(pCtx, msTimeout);
+ }
+ else
+ {
+ vrc = i_sendRawData(pCtx, msTimeout);
+ }
+
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtrReturn(pState, E_POINTER);
+
+ if (RT_SUCCESS(vrc))
+ {
+ pState->set(VBOXDNDSTATE_DROP_ENDED);
+ }
+ else
+ {
+ if (vrc == VERR_CANCELLED)
+ {
+ LogRel(("DnD: Sending data to guest cancelled by the user\n"));
+ pState->set(VBOXDNDSTATE_CANCELLED);
+ }
+ else
+ {
+ LogRel(("DnD: Sending data to guest failed with %Rrc\n", vrc));
+ pState->set(VBOXDNDSTATE_ERROR);
+ }
+
+ /* Make sure to fire a cancel request to the guest side in any case to prevent any guest side hangs. */
+ sendCancel();
+ }
+
+ /* Reset state. */
+ i_reset();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sends the common meta data body to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ */
+int GuestDnDTarget::i_sendMetaDataBody(GuestDnDSendCtx *pCtx)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ uint8_t *pvData = (uint8_t *)pCtx->Meta.pvData;
+ size_t cbData = pCtx->Meta.cbData;
+
+ int vrc = VINF_SUCCESS;
+
+ const size_t cbFmt = pCtx->Meta.strFmt.length() + 1; /* Include terminator. */
+ const char *pcszFmt = pCtx->Meta.strFmt.c_str();
+
+ LogFlowFunc(("uProtoVer=%RU32, szFmt=%s, cbFmt=%RU32, cbData=%zu\n", m_pState->m_uProtocolVersion, pcszFmt, cbFmt, cbData));
+
+ LogRel2(("DnD: Sending meta data to guest as '%s' (%zu bytes)\n", pcszFmt, cbData));
+
+#ifdef DEBUG
+ RTCList<RTCString> lstFilesURI = RTCString((char *)pvData, cbData).split(DND_PATH_SEPARATOR_STR);
+ LogFlowFunc(("lstFilesURI=%zu\n", lstFilesURI.size()));
+ for (size_t i = 0; i < lstFilesURI.size(); i++)
+ LogFlowFunc(("\t%s\n", lstFilesURI.at(i).c_str()));
+#endif
+
+ uint8_t *pvChunk = pvData;
+ size_t cbChunk = RT_MIN(mData.mcbBlockSize, cbData);
+ while (cbData)
+ {
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_HG_SND_DATA);
+
+ if (m_pState->m_uProtocolVersion < 3)
+ {
+ Msg.appendUInt32(pCtx->uScreenID); /* uScreenId */
+ Msg.appendPointer(unconst(pcszFmt), (uint32_t)cbFmt); /* pvFormat */
+ Msg.appendUInt32((uint32_t)cbFmt); /* cbFormat */
+ Msg.appendPointer(pvChunk, (uint32_t)cbChunk); /* pvData */
+ /* Fill in the current data block size to send.
+ * Note: Only supports uint32_t. */
+ Msg.appendUInt32((uint32_t)cbChunk); /* cbData */
+ }
+ else
+ {
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendPointer(pvChunk, (uint32_t)cbChunk); /* pvData */
+ Msg.appendUInt32((uint32_t)cbChunk); /* cbData */
+ Msg.appendPointer(NULL, 0); /** @todo pvChecksum; not used yet. */
+ Msg.appendUInt32(0); /** @todo cbChecksum; not used yet. */
+ }
+
+ vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_FAILURE(vrc))
+ break;
+
+ pvChunk += cbChunk;
+ AssertBreakStmt(cbData >= cbChunk, VERR_BUFFER_UNDERFLOW);
+ cbData -= cbChunk;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = updateProgress(pCtx, pCtx->pState, (uint32_t)pCtx->Meta.cbData);
+ AssertRC(vrc);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sends the common meta data header to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ */
+int GuestDnDTarget::i_sendMetaDataHeader(GuestDnDSendCtx *pCtx)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ if (m_pState->m_uProtocolVersion < 3) /* Protocol < v3 did not support this, skip. */
+ return VINF_SUCCESS;
+
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_HG_SND_DATA_HDR);
+
+ LogRel2(("DnD: Sending meta data header to guest (%RU64 bytes total data, %RU32 bytes meta data, %RU64 objects)\n",
+ pCtx->getTotalAnnounced(), pCtx->Meta.cbData, pCtx->Transfer.cObjToProcess));
+
+ Msg.appendUInt32(0); /** @todo uContext; not used yet. */
+ Msg.appendUInt32(0); /** @todo uFlags; not used yet. */
+ Msg.appendUInt32(pCtx->uScreenID); /* uScreen */
+ Msg.appendUInt64(pCtx->getTotalAnnounced()); /* cbTotal */
+ Msg.appendUInt32((uint32_t)pCtx->Meta.cbData); /* cbMeta*/
+ Msg.appendPointer(unconst(pCtx->Meta.strFmt.c_str()), (uint32_t)pCtx->Meta.strFmt.length() + 1); /* pvMetaFmt */
+ Msg.appendUInt32((uint32_t)pCtx->Meta.strFmt.length() + 1); /* cbMetaFmt */
+ Msg.appendUInt64(pCtx->Transfer.cObjToProcess); /* cObjects */
+ Msg.appendUInt32(0); /** @todo enmCompression; not used yet. */
+ Msg.appendUInt32(0); /** @todo enmChecksumType; not used yet. */
+ Msg.appendPointer(NULL, 0); /** @todo pvChecksum; not used yet. */
+ Msg.appendUInt32(0); /** @todo cbChecksum; not used yet. */
+
+ int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sends a directory entry to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ * @param pObj Transfer object to send. Must be a directory.
+ * @param pMsg Where to store the message to send.
+ */
+int GuestDnDTarget::i_sendDirectory(GuestDnDSendCtx *pCtx, PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
+
+ const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj);
+ AssertPtrReturn(pcszDstPath, VERR_INVALID_POINTER);
+ const size_t cchPath = RTStrNLen(pcszDstPath, RTPATH_MAX); /* Note: Maximum is RTPATH_MAX on guest side. */
+ AssertReturn(cchPath, VERR_INVALID_PARAMETER);
+
+ LogRel2(("DnD: Transferring host directory '%s' to guest\n", DnDTransferObjectGetSourcePath(pObj)));
+
+ pMsg->setType(HOST_DND_FN_HG_SND_DIR);
+ if (m_pState->m_uProtocolVersion >= 3)
+ pMsg->appendUInt32(0); /** @todo ContextID not used yet. */
+ pMsg->appendString(pcszDstPath); /* path */
+ pMsg->appendUInt32((uint32_t)(cchPath + 1)); /* path length, including terminator. */
+ pMsg->appendUInt32(DnDTransferObjectGetMode(pObj)); /* mode */
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sends a file to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ * @param pObj Transfer object to send. Must be a file.
+ * @param pMsg Where to store the message to send.
+ */
+int GuestDnDTarget::i_sendFile(GuestDnDSendCtx *pCtx,
+ PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+ AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
+
+ const char *pcszSrcPath = DnDTransferObjectGetSourcePath(pObj);
+ AssertPtrReturn(pcszSrcPath, VERR_INVALID_POINTER);
+ const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj);
+ AssertPtrReturn(pcszDstPath, VERR_INVALID_POINTER);
+
+ int vrc = VINF_SUCCESS;
+
+ if (!DnDTransferObjectIsOpen(pObj))
+ {
+ LogRel2(("DnD: Opening host file '%s' for transferring to guest\n", pcszSrcPath));
+
+ vrc = DnDTransferObjectOpen(pObj, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, 0 /* fMode */,
+ DNDTRANSFEROBJECT_FLAGS_NONE);
+ if (RT_FAILURE(vrc))
+ LogRel(("DnD: Opening host file '%s' failed, rc=%Rrc\n", pcszSrcPath, vrc));
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ bool fSendData = false;
+ if (RT_SUCCESS(vrc)) /** @todo r=aeichner Could save an identation level here as there is a error check above already... */
+ {
+ if (m_pState->m_uProtocolVersion >= 2)
+ {
+ if (!(pCtx->Transfer.fObjState & DND_OBJ_STATE_HAS_HDR))
+ {
+ const size_t cchDstPath = RTStrNLen(pcszDstPath, RTPATH_MAX);
+ const size_t cbSize = DnDTransferObjectGetSize(pObj);
+ const RTFMODE fMode = DnDTransferObjectGetMode(pObj);
+
+ /*
+ * Since protocol v2 the file header and the actual file contents are
+ * separate messages, so send the file header first.
+ * The just registered callback will be called by the guest afterwards.
+ */
+ pMsg->setType(HOST_DND_FN_HG_SND_FILE_HDR);
+ pMsg->appendUInt32(0); /** @todo ContextID not used yet. */
+ pMsg->appendString(pcszDstPath); /* pvName */
+ pMsg->appendUInt32((uint32_t)(cchDstPath + 1)); /* cbName */
+ pMsg->appendUInt32(0); /* uFlags */
+ pMsg->appendUInt32(fMode); /* fMode */
+ pMsg->appendUInt64(cbSize); /* uSize */
+
+ LogRel2(("DnD: Transferring host file '%s' to guest (as '%s', %zu bytes, mode %#x)\n",
+ pcszSrcPath, pcszDstPath, cbSize, fMode));
+
+ /** @todo Set progress object title to current file being transferred? */
+
+ /* Update object state to reflect that we have sent the file header. */
+ pCtx->Transfer.fObjState |= DND_OBJ_STATE_HAS_HDR;
+ }
+ else
+ {
+ /* File header was sent, so only send the actual file data. */
+ fSendData = true;
+ }
+ }
+ else /* Protocol v1. */
+ {
+ /* Always send the file data, every time. */
+ fSendData = true;
+ }
+ }
+
+ if ( RT_SUCCESS(vrc)
+ && fSendData)
+ {
+ vrc = i_sendFileData(pCtx, pObj, pMsg);
+ }
+
+ if (RT_FAILURE(vrc))
+ LogRel(("DnD: Sending host file '%s' to guest failed, rc=%Rrc\n", pcszSrcPath, vrc));
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Helper function to send actual file data to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ * @param pObj Transfer object to send. Must be a file.
+ * @param pMsg Where to store the message to send.
+ */
+int GuestDnDTarget::i_sendFileData(GuestDnDSendCtx *pCtx,
+ PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+ AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pCtx->pState, VERR_WRONG_ORDER);
+
+ /** @todo Don't allow concurrent reads per context! */
+
+ /* Set the message type. */
+ pMsg->setType(HOST_DND_FN_HG_SND_FILE_DATA);
+
+ const char *pcszSrcPath = DnDTransferObjectGetSourcePath(pObj);
+ const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj);
+
+ /* Protocol version 1 sends the file path *every* time with a new file chunk.
+ * In protocol version 2 we only do this once with HOST_DND_FN_HG_SND_FILE_HDR. */
+ if (m_pState->m_uProtocolVersion <= 1)
+ {
+ const size_t cchDstPath = RTStrNLen(pcszDstPath, RTPATH_MAX);
+
+ pMsg->appendString(pcszDstPath); /* pvName */
+ pMsg->appendUInt32((uint32_t)cchDstPath + 1); /* cbName */
+ }
+ else if (m_pState->m_uProtocolVersion >= 2)
+ {
+ pMsg->appendUInt32(0); /** @todo ContextID not used yet. */
+ }
+
+ void *pvBuf = pCtx->Transfer.pvScratchBuf;
+ AssertPtr(pvBuf);
+ size_t cbBuf = pCtx->Transfer.cbScratchBuf;
+ Assert(cbBuf);
+
+ uint32_t cbRead;
+
+ int vrc = DnDTransferObjectRead(pObj, pvBuf, cbBuf, &cbRead);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowFunc(("cbBufe=%zu, cbRead=%RU32\n", cbBuf, cbRead));
+
+ if (m_pState->m_uProtocolVersion <= 1)
+ {
+ pMsg->appendPointer(pvBuf, cbRead); /* pvData */
+ pMsg->appendUInt32(cbRead); /* cbData */
+ pMsg->appendUInt32(DnDTransferObjectGetMode(pObj)); /* fMode */
+ }
+ else /* Protocol v2 and up. */
+ {
+ pMsg->appendPointer(pvBuf, cbRead); /* pvData */
+ pMsg->appendUInt32(cbRead); /* cbData */
+
+ if (m_pState->m_uProtocolVersion >= 3)
+ {
+ /** @todo Calculate checksum. */
+ pMsg->appendPointer(NULL, 0); /* pvChecksum */
+ pMsg->appendUInt32(0); /* cbChecksum */
+ }
+ }
+
+ int vrc2 = updateProgress(pCtx, pCtx->pState, (uint32_t)cbRead);
+ AssertRC(vrc2);
+
+ /* DnDTransferObjectRead() will return VINF_EOF if reading is complete. */
+ if (vrc == VINF_EOF)
+ vrc = VINF_SUCCESS;
+
+ if (DnDTransferObjectIsComplete(pObj)) /* Done reading? */
+ LogRel2(("DnD: Transferring host file '%s' to guest complete\n", pcszSrcPath));
+ }
+ else
+ LogRel(("DnD: Reading from host file '%s' failed, vrc=%Rrc\n", pcszSrcPath, vrc));
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Static HGCM service callback which handles sending transfer data to the guest.
+ *
+ * @returns VBox status code. Will get sent back to the host service.
+ * @param uMsg HGCM message ID (function number).
+ * @param pvParms Pointer to additional message data. Optional and can be NULL.
+ * @param cbParms Size (in bytes) additional message data. Optional and can be 0.
+ * @param pvUser User-supplied pointer on callback registration.
+ */
+/* static */
+DECLCALLBACK(int) GuestDnDTarget::i_sendTransferDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
+{
+ GuestDnDSendCtx *pCtx = (GuestDnDSendCtx *)pvUser;
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ GuestDnDTarget *pThis = pCtx->pTarget;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ /* At the moment we only have one transfer list per transfer. */
+ PDNDTRANSFERLIST pList = &pCtx->Transfer.List;
+
+ LogFlowFunc(("pThis=%p, pList=%p, uMsg=%RU32\n", pThis, pList, uMsg));
+
+ int vrc = VINF_SUCCESS;
+ int vrcGuest = VINF_SUCCESS; /* Contains error code from guest in case of VERR_DND_GUEST_ERROR. */
+ bool fNotify = false;
+
+ switch (uMsg)
+ {
+ case GUEST_DND_FN_CONNECT:
+ /* Nothing to do here (yet). */
+ break;
+
+ case GUEST_DND_FN_DISCONNECT:
+ vrc = VERR_CANCELLED;
+ break;
+
+ case GUEST_DND_FN_GET_NEXT_HOST_MSG:
+ {
+ PVBOXDNDCBHGGETNEXTHOSTMSG pCBData = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSG>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSG) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ try
+ {
+ GuestDnDMsg *pMsg = new GuestDnDMsg();
+
+ vrc = pThis->i_sendTransferListObject(pCtx, pList, pMsg);
+ if (vrc == VINF_EOF) /* Transfer complete? */
+ {
+ LogFlowFunc(("Last transfer item processed, bailing out\n"));
+ }
+ else if (RT_SUCCESS(vrc))
+ {
+ vrc = pThis->msgQueueAdd(pMsg);
+ if (RT_SUCCESS(vrc)) /* Return message type & required parameter count to the guest. */
+ {
+ LogFlowFunc(("GUEST_DND_FN_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount()));
+ pCBData->uMsg = pMsg->getType();
+ pCBData->cParms = pMsg->getCount();
+ }
+ }
+
+ if ( RT_FAILURE(vrc)
+ || vrc == VINF_EOF) /* Transfer complete? */
+ {
+ delete pMsg;
+ pMsg = NULL;
+ }
+ }
+ catch(std::bad_alloc & /*e*/)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ break;
+ }
+ case GUEST_DND_FN_EVT_ERROR:
+ {
+ PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ pCtx->pState->reset();
+
+ if (RT_SUCCESS(pCBData->rc))
+ {
+ AssertMsgFailed(("Guest has sent an error event but did not specify an actual error code\n"));
+ pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
+ }
+
+ vrc = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
+ GuestDnDTarget::i_guestErrorToString(pCBData->rc));
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = VERR_DND_GUEST_ERROR;
+ vrcGuest = pCBData->rc;
+ }
+ break;
+ }
+ case HOST_DND_FN_HG_SND_DIR:
+ case HOST_DND_FN_HG_SND_FILE_HDR:
+ case HOST_DND_FN_HG_SND_FILE_DATA:
+ {
+ PVBOXDNDCBHGGETNEXTHOSTMSGDATA pCBData
+ = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSGDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSGDATA) == cbParms, VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("pCBData->uMsg=%RU32, paParms=%p, cParms=%RU32\n", pCBData->uMsg, pCBData->paParms, pCBData->cParms));
+
+ GuestDnDMsg *pMsg = pThis->msgQueueGetNext();
+ if (pMsg)
+ {
+ /*
+ * Sanity checks.
+ */
+ if ( pCBData->uMsg != uMsg
+ || pCBData->paParms == NULL
+ || pCBData->cParms != pMsg->getCount())
+ {
+ LogFlowFunc(("Current message does not match:\n"));
+ LogFlowFunc(("\tCallback: uMsg=%RU32, cParms=%RU32, paParms=%p\n",
+ pCBData->uMsg, pCBData->cParms, pCBData->paParms));
+ LogFlowFunc(("\t Next: uMsg=%RU32, cParms=%RU32\n", pMsg->getType(), pMsg->getCount()));
+
+ /* Start over. */
+ pThis->msgQueueClear();
+
+ vrc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowFunc(("Returning uMsg=%RU32\n", uMsg));
+ vrc = HGCM::Message::CopyParms(pCBData->paParms, pCBData->cParms, pMsg->getParms(), pMsg->getCount(),
+ false /* fDeepCopy */);
+ if (RT_SUCCESS(vrc))
+ {
+ pCBData->cParms = pMsg->getCount();
+ pThis->msgQueueRemoveNext();
+ }
+ else
+ LogFlowFunc(("Copying parameters failed with vrc=%Rrc\n", vrc));
+ }
+ }
+ else
+ vrc = VERR_NO_DATA;
+
+ LogFlowFunc(("Processing next message ended with vrc=%Rrc\n", vrc));
+ break;
+ }
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ int vrcToGuest = VINF_SUCCESS; /* Status which will be sent back to the guest. */
+
+ /*
+ * Resolve errors.
+ */
+ switch (vrc)
+ {
+ case VINF_SUCCESS:
+ break;
+
+ case VINF_EOF:
+ {
+ LogRel2(("DnD: Transfer to guest complete\n"));
+
+ /* Complete operation on host side. */
+ fNotify = true;
+
+ /* The guest expects VERR_NO_DATA if the transfer is complete. */
+ vrcToGuest = VERR_NO_DATA;
+ break;
+ }
+
+ case VERR_DND_GUEST_ERROR:
+ {
+ LogRel(("DnD: Guest reported error %Rrc, aborting transfer to guest\n", vrcGuest));
+ break;
+ }
+
+ case VERR_CANCELLED:
+ {
+ LogRel2(("DnD: Transfer to guest canceled\n"));
+ vrcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
+ break;
+ }
+
+ default:
+ {
+ LogRel(("DnD: Host error %Rrc occurred, aborting transfer to guest\n", vrc));
+ vrcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
+ break;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ /* Unregister this callback. */
+ AssertPtr(pCtx->pState);
+ int vrc2 = pCtx->pState->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
+ AssertRC(vrc2);
+
+ /* Let the waiter(s) know. */
+ fNotify = true;
+ }
+
+ LogFlowFunc(("fNotify=%RTbool, vrc=%Rrc, vrcToGuest=%Rrc\n", fNotify, vrc, vrcToGuest));
+
+ if (fNotify)
+ {
+ int vrc2 = pCtx->EventCallback.Notify(vrc); /** @todo Also pass guest error back? */
+ AssertRC(vrc2);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrcToGuest; /* Tell the guest. */
+}
+
+/**
+ * Main function for sending the actual transfer data (i.e. files + directories) to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ * @param msTimeout Timeout (in ms) to use for getting the data sent.
+ */
+int GuestDnDTarget::i_sendTransferData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtr(pCtx->pState);
+
+#define REGISTER_CALLBACK(x) \
+ do { \
+ vrc = pCtx->pState->setCallback(x, i_sendTransferDataCallback, pCtx); \
+ if (RT_FAILURE(vrc)) \
+ return vrc; \
+ } while (0)
+
+#define UNREGISTER_CALLBACK(x) \
+ do { \
+ int vrc2 = pCtx->pState->setCallback(x, NULL); \
+ AssertRC(vrc2); \
+ } while (0)
+
+ int vrc = pCtx->Transfer.init(mData.mcbBlockSize);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ vrc = pCtx->EventCallback.Reset();
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /*
+ * Register callbacks.
+ */
+ /* Guest callbacks. */
+ REGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
+ REGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
+ REGISTER_CALLBACK(GUEST_DND_FN_GET_NEXT_HOST_MSG);
+ REGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
+ /* Host callbacks. */
+ REGISTER_CALLBACK(HOST_DND_FN_HG_SND_DIR);
+ if (m_pState->m_uProtocolVersion >= 2)
+ REGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_HDR);
+ REGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_DATA);
+
+ do
+ {
+ /*
+ * Extract transfer list from current meta data.
+ */
+ vrc = DnDTransferListAppendPathsFromBuffer(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI,
+ (const char *)pCtx->Meta.pvData, pCtx->Meta.cbData, DND_PATH_SEPARATOR_STR,
+ DNDTRANSFERLIST_FLAGS_RECURSIVE);
+ if (RT_FAILURE(vrc))
+ break;
+
+ /*
+ * Update internal state to reflect everything we need to work with it.
+ */
+ pCtx->cbExtra = DnDTransferListObjTotalBytes(&pCtx->Transfer.List);
+ /* cbExtra can be 0, if all files are of 0 bytes size. */
+ pCtx->Transfer.cObjToProcess = DnDTransferListObjCount(&pCtx->Transfer.List);
+ AssertBreakStmt(pCtx->Transfer.cObjToProcess, vrc = VERR_INVALID_PARAMETER);
+
+ /* Update the meta data to have the current root transfer entries in the right shape. */
+ if (DnDMIMEHasFileURLs(pCtx->Meta.strFmt.c_str(), RTSTR_MAX))
+ {
+ /* Save original format we're still going to use after updating the actual meta data. */
+ Utf8Str strFmt = pCtx->Meta.strFmt;
+
+ /* Reset stale data. */
+ pCtx->Meta.reset();
+
+ void *pvData;
+ size_t cbData;
+#ifdef DEBUG
+ vrc = DnDTransferListGetRootsEx(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI, "" /* pcszPathBase */,
+ "\n" /* pcszSeparator */, (char **)&pvData, &cbData);
+ AssertRCReturn(vrc, vrc);
+ LogFlowFunc(("URI data:\n%s", (char *)pvData));
+ RTMemFree(pvData);
+ cbData = 0;
+#endif
+ vrc = DnDTransferListGetRoots(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI,
+ (char **)&pvData, &cbData);
+ AssertRCReturn(vrc, vrc);
+
+ /* pCtx->Meta now owns the allocated data. */
+ pCtx->Meta.strFmt = strFmt;
+ pCtx->Meta.pvData = pvData;
+ pCtx->Meta.cbData = cbData;
+ pCtx->Meta.cbAllocated = cbData;
+ pCtx->Meta.cbAnnounced = cbData;
+ }
+
+ /*
+ * The first message always is the data header. The meta data itself then follows
+ * and *only* contains the root elements of a transfer list.
+ *
+ * After the meta data we generate the messages required to send the
+ * file/directory data itself.
+ *
+ * Note: Protocol < v3 use the first data message to tell what's being sent.
+ */
+
+ /*
+ * Send the data header first.
+ */
+ if (m_pState->m_uProtocolVersion >= 3)
+ vrc = i_sendMetaDataHeader(pCtx);
+
+ /*
+ * Send the (meta) data body.
+ */
+ if (RT_SUCCESS(vrc))
+ vrc = i_sendMetaDataBody(pCtx);
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = waitForEvent(&pCtx->EventCallback, pCtx->pState, msTimeout);
+ if (RT_SUCCESS(vrc))
+ pCtx->pState->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
+ }
+
+ } while (0);
+
+ /*
+ * Unregister callbacks.
+ */
+ /* Guest callbacks. */
+ UNREGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GET_NEXT_HOST_MSG);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
+ /* Host callbacks. */
+ UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_DIR);
+ if (m_pState->m_uProtocolVersion >= 2)
+ UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_HDR);
+ UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_DATA);
+
+#undef REGISTER_CALLBACK
+#undef UNREGISTER_CALLBACK
+
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_CANCELLED) /* Transfer was cancelled by the host. */
+ {
+ /*
+ * Now that we've cleaned up tell the guest side to cancel.
+ * This does not imply we're waiting for the guest to react, as the
+ * host side never must depend on anything from the guest.
+ */
+ int vrc2 = sendCancel();
+ AssertRC(vrc2);
+
+ LogRel2(("DnD: Sending transfer data to guest cancelled by user\n"));
+
+ vrc2 = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
+ AssertRC(vrc2);
+
+ /* Cancelling is not an error, just set success here. */
+ vrc = VINF_SUCCESS;
+ }
+ else if (vrc != VERR_DND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
+ {
+ LogRel(("DnD: Sending transfer data to guest failed with vrc=%Rrc\n", vrc));
+ int vrc2 = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, vrc,
+ GuestDnDTarget::i_hostErrorToString(vrc));
+ AssertRC(vrc2);
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sends the next object of a transfer list to the guest.
+ *
+ * @returns VBox status code. VINF_EOF if the transfer list is complete.
+ * @param pCtx Send context to use.
+ * @param pList Transfer list to use.
+ * @param pMsg Message to store send data into.
+ */
+int GuestDnDTarget::i_sendTransferListObject(GuestDnDSendCtx *pCtx, PDNDTRANSFERLIST pList, GuestDnDMsg *pMsg)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
+
+ int vrc = updateProgress(pCtx, pCtx->pState);
+ AssertRCReturn(vrc, vrc);
+
+ PDNDTRANSFEROBJECT pObj = DnDTransferListObjGetFirst(pList);
+ if (!pObj) /* Transfer complete? */
+ return VINF_EOF;
+
+ switch (DnDTransferObjectGetType(pObj))
+ {
+ case DNDTRANSFEROBJTYPE_DIRECTORY:
+ vrc = i_sendDirectory(pCtx, pObj, pMsg);
+ break;
+
+ case DNDTRANSFEROBJTYPE_FILE:
+ vrc = i_sendFile(pCtx, pObj, pMsg);
+ break;
+
+ default:
+ AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
+ break;
+ }
+
+ if ( RT_SUCCESS(vrc)
+ && DnDTransferObjectIsComplete(pObj))
+ {
+ DnDTransferListObjRemove(pList, pObj);
+ pObj = NULL;
+
+ AssertReturn(pCtx->Transfer.cObjProcessed + 1 <= pCtx->Transfer.cObjToProcess, VERR_WRONG_ORDER);
+ pCtx->Transfer.cObjProcessed++;
+
+ pCtx->Transfer.fObjState = DND_OBJ_STATE_NONE;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Main function for sending raw data (e.g. text, RTF, ...) to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ * @param msTimeout Timeout (in ms) to use for getting the data sent.
+ */
+int GuestDnDTarget::i_sendRawData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ NOREF(msTimeout);
+
+ /** @todo At the moment we only allow sending up to 64K raw data.
+ * For protocol v1+v2: Fix this by using HOST_DND_FN_HG_SND_MORE_DATA.
+ * For protocol v3 : Send another HOST_DND_FN_HG_SND_DATA message. */
+ if (!pCtx->Meta.cbData)
+ return VINF_SUCCESS;
+
+ int vrc = i_sendMetaDataHeader(pCtx);
+ if (RT_SUCCESS(vrc))
+ vrc = i_sendMetaDataBody(pCtx);
+
+ int vrc2;
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("DnD: Sending raw data to guest failed with vrc=%Rrc\n", vrc));
+ vrc2 = pCtx->pState->setProgress(100 /* Percent */, DND_PROGRESS_ERROR, vrc,
+ GuestDnDTarget::i_hostErrorToString(vrc));
+ }
+ else
+ vrc2 = pCtx->pState->setProgress(100 /* Percent */, DND_PROGRESS_COMPLETE, vrc);
+ AssertRC(vrc2);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Cancels sending DnD data.
+ *
+ * @returns VBox status code.
+ * @param aVeto Whether cancelling was vetoed or not.
+ * Not implemented yet.
+ */
+HRESULT GuestDnDTarget::cancel(BOOL *aVeto)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ LogRel2(("DnD: Sending cancelling request to the guest ...\n"));
+
+ int vrc = GuestDnDBase::sendCancel();
+
+ if (aVeto)
+ *aVeto = FALSE; /** @todo Implement vetoing. */
+
+ HRESULT hrc = RT_SUCCESS(vrc) ? S_OK : VBOX_E_DND_ERROR;
+
+ LogFlowFunc(("hrc=%Rhrc\n", hrc));
+ return hrc;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
diff --git a/src/VBox/Main/src-client/GuestFileImpl.cpp b/src/VBox/Main/src-client/GuestFileImpl.cpp
new file mode 100644
index 00000000..d2560a05
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestFileImpl.cpp
@@ -0,0 +1,1902 @@
+/* $Id: GuestFileImpl.cpp $ */
+/** @file
+ * VirtualBox Main - Guest file handling.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_GUESTFILE
+#include "LoggingNew.h"
+
+#ifndef VBOX_WITH_GUEST_CONTROL
+# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
+#endif
+#include "GuestFileImpl.h"
+#include "GuestSessionImpl.h"
+#include "GuestCtrlImplPrivate.h"
+#include "ConsoleImpl.h"
+#include "VirtualBoxErrorInfoImpl.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+#include "VBoxEvents.h"
+
+#include <iprt/cpp/utils.h> /* For unconst(). */
+#include <iprt/file.h>
+
+#include <VBox/com/array.h>
+#include <VBox/com/listeners.h>
+#include <VBox/AssertGuest.h>
+
+
+/**
+ * Internal listener class to serve events in an
+ * active manner, e.g. without polling delays.
+ */
+class GuestFileListener
+{
+public:
+
+ GuestFileListener(void)
+ {
+ }
+
+ virtual ~GuestFileListener()
+ {
+ }
+
+ HRESULT init(GuestFile *pFile)
+ {
+ AssertPtrReturn(pFile, E_POINTER);
+ mFile = pFile;
+ return S_OK;
+ }
+
+ void uninit(void)
+ {
+ mFile = NULL;
+ }
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
+ {
+ switch (aType)
+ {
+ case VBoxEventType_OnGuestFileStateChanged:
+ case VBoxEventType_OnGuestFileOffsetChanged:
+ case VBoxEventType_OnGuestFileRead:
+ case VBoxEventType_OnGuestFileWrite:
+ {
+ AssertPtrReturn(mFile, E_POINTER);
+ int vrc2 = mFile->signalWaitEvent(aType, aEvent);
+ RT_NOREF(vrc2);
+#ifdef DEBUG_andy
+ LogFlowFunc(("Signalling events of type=%RU32, file=%p resulted in vrc=%Rrc\n",
+ aType, mFile, vrc2));
+#endif
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Unhandled event %RU32\n", aType));
+ break;
+ }
+
+ return S_OK;
+ }
+
+private:
+
+ /** Weak pointer to the guest file object to listen for. */
+ GuestFile *mFile;
+};
+typedef ListenerImpl<GuestFileListener, GuestFile*> GuestFileListenerImpl;
+
+VBOX_LISTENER_DECLARE(GuestFileListenerImpl)
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(GuestFile)
+
+HRESULT GuestFile::FinalConstruct(void)
+{
+ LogFlowThisFuncEnter();
+ return BaseFinalConstruct();
+}
+
+void GuestFile::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes a file object but does *not* open the file on the guest
+ * yet. This is done in the dedidcated openFile call.
+ *
+ * @return IPRT status code.
+ * @param pConsole Pointer to console object.
+ * @param pSession Pointer to session object.
+ * @param aObjectID The object's ID.
+ * @param openInfo File opening information.
+ */
+int GuestFile::init(Console *pConsole, GuestSession *pSession,
+ ULONG aObjectID, const GuestFileOpenInfo &openInfo)
+{
+ LogFlowThisFunc(("pConsole=%p, pSession=%p, aObjectID=%RU32, strPath=%s\n",
+ pConsole, pSession, aObjectID, openInfo.mFilename.c_str()));
+
+ AssertPtrReturn(pConsole, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED);
+
+ int vrc = bindToSession(pConsole, pSession, aObjectID);
+ if (RT_SUCCESS(vrc))
+ {
+ mSession = pSession;
+
+ mData.mOpenInfo = openInfo;
+ mData.mInitialSize = 0;
+ mData.mStatus = FileStatus_Undefined;
+ mData.mLastError = VINF_SUCCESS;
+ mData.mOffCurrent = 0;
+
+ unconst(mEventSource).createObject();
+ HRESULT hr = mEventSource->init();
+ if (FAILED(hr))
+ vrc = VERR_COM_UNEXPECTED;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ try
+ {
+ GuestFileListener *pListener = new GuestFileListener();
+ ComObjPtr<GuestFileListenerImpl> thisListener;
+ HRESULT hr = thisListener.createObject();
+ if (SUCCEEDED(hr))
+ hr = thisListener->init(pListener, this);
+
+ if (SUCCEEDED(hr))
+ {
+ com::SafeArray <VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileOffsetChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileRead);
+ eventTypes.push_back(VBoxEventType_OnGuestFileWrite);
+ hr = mEventSource->RegisterListener(thisListener,
+ ComSafeArrayAsInParam(eventTypes),
+ TRUE /* Active listener */);
+ if (SUCCEEDED(hr))
+ {
+ vrc = baseInit();
+ if (RT_SUCCESS(vrc))
+ {
+ mLocalListener = thisListener;
+ }
+ }
+ else
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ else
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ catch(std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Confirm a successful initialization when it's the case. */
+ autoInitSpan.setSucceeded();
+ }
+ else
+ autoInitSpan.setFailed();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void GuestFile::uninit(void)
+{
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFuncEnter();
+
+ baseUninit();
+ LogFlowThisFuncLeave();
+}
+
+// implementation of public getters/setters for attributes
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestFile::getCreationMode(ULONG *aCreationMode)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCreationMode = mData.mOpenInfo.mCreationMode;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getOpenAction(FileOpenAction_T *aOpenAction)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aOpenAction = mData.mOpenInfo.mOpenAction;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ /* No need to lock - lifetime constant. */
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getFilename(com::Utf8Str &aFilename)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aFilename = mData.mOpenInfo.mFilename;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getId(ULONG *aId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aId = mObjectID;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getInitialSize(LONG64 *aInitialSize)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aInitialSize = mData.mInitialSize;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getOffset(LONG64 *aOffset)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * This is updated by GuestFile::i_onFileNotify() when read, write and seek
+ * confirmation messages are recevied.
+ *
+ * Note! This will not be accurate with older (< 5.2.32, 6.0.0 - 6.0.9)
+ * Guest Additions when using writeAt, readAt or writing to a file
+ * opened in append mode.
+ */
+ *aOffset = mData.mOffCurrent;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getAccessMode(FileAccessMode_T *aAccessMode)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAccessMode = mData.mOpenInfo.mAccessMode;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getStatus(FileStatus_T *aStatus)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aStatus = mData.mStatus;
+
+ return S_OK;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Entry point for guest side file callbacks.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCb Host callback data.
+ */
+int GuestFile::i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("strName=%s, uContextID=%RU32, uFunction=%RU32, pSvcCb=%p\n",
+ mData.mOpenInfo.mFilename.c_str(), pCbCtx->uContextID, pCbCtx->uMessage, pSvcCb));
+
+ int vrc;
+ switch (pCbCtx->uMessage)
+ {
+ case GUEST_MSG_DISCONNECTED:
+ vrc = i_onGuestDisconnected(pCbCtx, pSvcCb);
+ break;
+
+ case GUEST_MSG_FILE_NOTIFY:
+ vrc = i_onFileNotify(pCbCtx, pSvcCb);
+ break;
+
+ default:
+ /* Silently ignore not implemented functions. */
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+#ifdef DEBUG
+ LogFlowFuncLeaveRC(vrc);
+#endif
+ return vrc;
+}
+
+/**
+ * Closes the file on the guest side and unregisters it.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned.
+ */
+int GuestFile::i_closeFile(int *prcGuest)
+{
+ LogFlowThisFunc(("strFile=%s\n", mData.mOpenInfo.mFilename.c_str()));
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mObjectID /* Guest file ID */);
+
+ vrc = sendMessage(HOST_MSG_FILE_CLOSE, i, paParms);
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForStatusChange(pEvent, 30 * 1000 /* Timeout in ms */,
+ NULL /* FileStatus */, prcGuest);
+ unregisterWaitEvent(pEvent);
+
+ /* Unregister the file object from the guest session. */
+ AssertPtr(mSession);
+ int vrc2 = mSession->i_fileUnregister(this);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Converts a given guest file error to a string.
+ *
+ * @returns Error string.
+ * @param rcGuest Guest file error to return string for.
+ * @param pcszWhat Hint of what was involved when the error occurred.
+ */
+/* static */
+Utf8Str GuestFile::i_guestErrorToString(int rcGuest, const char *pcszWhat)
+{
+ AssertPtrReturn(pcszWhat, "");
+
+ Utf8Str strErr;
+ switch (rcGuest)
+ {
+#define CASE_MSG(a_iRc, ...) \
+ case a_iRc: strErr.printf(__VA_ARGS__); break;
+ CASE_MSG(VERR_ACCESS_DENIED , tr("Access to guest file \"%s\" denied"), pcszWhat);
+ CASE_MSG(VERR_ALREADY_EXISTS , tr("Guest file \"%s\" already exists"), pcszWhat);
+ CASE_MSG(VERR_FILE_NOT_FOUND , tr("Guest file \"%s\" not found"), pcszWhat);
+ CASE_MSG(VERR_NET_HOST_NOT_FOUND, tr("Host name \"%s\", not found"), pcszWhat);
+ CASE_MSG(VERR_SHARING_VIOLATION , tr("Sharing violation for guest file \"%s\""), pcszWhat);
+ default:
+ strErr.printf(tr("Error %Rrc for guest file \"%s\" occurred\n"), rcGuest, pcszWhat);
+ break;
+#undef CASE_MSG
+ }
+
+ return strErr;
+}
+
+/**
+ * Called when the guest side notifies the host of a file event.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ */
+int GuestFile::i_onFileNotify(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ LogFlowThisFuncEnter();
+
+ if (pSvcCbData->mParms < 3)
+ return VERR_INVALID_PARAMETER;
+
+ int idx = 1; /* Current parameter index. */
+ CALLBACKDATA_FILE_NOTIFY dataCb;
+ RT_ZERO(dataCb);
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ HGCMSvcGetU32(&pSvcCbData->mpaParms[idx++], &dataCb.uType);
+ HGCMSvcGetU32(&pSvcCbData->mpaParms[idx++], &dataCb.rc);
+
+ int vrcGuest = (int)dataCb.rc; /* uint32_t vs. int. */
+
+ LogFlowThisFunc(("uType=%RU32, vrcGuest=%Rrc\n", dataCb.uType, vrcGuest));
+
+ if (RT_FAILURE(vrcGuest))
+ {
+ int vrc2 = i_setFileStatus(FileStatus_Error, vrcGuest);
+ AssertRC(vrc2);
+
+ /* Ignore rc, as the event to signal might not be there (anymore). */
+ signalWaitEventInternal(pCbCtx, vrcGuest, NULL /* pPayload */);
+ return VINF_SUCCESS; /* Report to the guest. */
+ }
+
+ AssertMsg(mObjectID == VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pCbCtx->uContextID),
+ ("File ID %RU32 does not match object ID %RU32\n", mObjectID,
+ VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pCbCtx->uContextID)));
+
+ int vrc = VERR_NOT_SUPPORTED; /* Play safe by default. */
+
+ switch (dataCb.uType)
+ {
+ case GUEST_FILE_NOTIFYTYPE_ERROR:
+ {
+ vrc = i_setFileStatus(FileStatus_Error, vrcGuest);
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_OPEN:
+ {
+ if (pSvcCbData->mParms == 4)
+ {
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[idx++], &dataCb.u.open.uHandle);
+ if (RT_FAILURE(vrc))
+ break;
+
+ /* Set the process status. */
+ vrc = i_setFileStatus(FileStatus_Open, vrcGuest);
+ }
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_CLOSE:
+ {
+ vrc = i_setFileStatus(FileStatus_Closed, vrcGuest);
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_READ:
+ {
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 4, ("mParms=%u\n", pSvcCbData->mParms),
+ vrc = VERR_WRONG_PARAMETER_COUNT);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_PTR,
+ ("type=%u\n", pSvcCbData->mpaParms[idx].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+
+ vrc = HGCMSvcGetPv(&pSvcCbData->mpaParms[idx++], &dataCb.u.read.pvData, &dataCb.u.read.cbData);
+ if (RT_FAILURE(vrc))
+ break;
+
+ const uint32_t cbRead = dataCb.u.read.cbData;
+ Log3ThisFunc(("cbRead=%RU32\n", cbRead));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mOffCurrent += cbRead; /* Bogus for readAt, which is why we've got GUEST_FILE_NOTIFYTYPE_READ_OFFSET. */
+ alock.release();
+
+ try
+ {
+ com::SafeArray<BYTE> data((size_t)cbRead);
+ AssertBreakStmt(data.size() == cbRead, vrc = VERR_NO_MEMORY);
+ data.initFrom((BYTE *)dataCb.u.read.pvData, cbRead);
+ ::FireGuestFileReadEvent(mEventSource, mSession, this, mData.mOffCurrent, cbRead, ComSafeArrayAsInParam(data));
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_READ_OFFSET:
+ {
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 5, ("mParms=%u\n", pSvcCbData->mParms),
+ vrc = VERR_WRONG_PARAMETER_COUNT);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_PTR,
+ ("type=%u\n", pSvcCbData->mpaParms[idx].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx + 1].type == VBOX_HGCM_SVC_PARM_64BIT,
+ ("type=%u\n", pSvcCbData->mpaParms[idx + 1].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+ BYTE const * const pbData = (BYTE const *)pSvcCbData->mpaParms[idx].u.pointer.addr;
+ uint32_t const cbRead = pSvcCbData->mpaParms[idx].u.pointer.size;
+ int64_t offNew = (int64_t)pSvcCbData->mpaParms[idx + 1].u.uint64;
+ Log3ThisFunc(("cbRead=%RU32 offNew=%RI64 (%#RX64)\n", cbRead, offNew, offNew));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (offNew < 0) /* non-seekable */
+ offNew = mData.mOffCurrent + cbRead;
+ mData.mOffCurrent = offNew;
+ alock.release();
+
+ try
+ {
+ com::SafeArray<BYTE> data((size_t)cbRead);
+ AssertBreakStmt(data.size() == cbRead, vrc = VERR_NO_MEMORY);
+ data.initFrom(pbData, cbRead);
+ ::FireGuestFileReadEvent(mEventSource, mSession, this, offNew, cbRead, ComSafeArrayAsInParam(data));
+ vrc = VINF_SUCCESS;
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_WRITE:
+ {
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 4, ("mParms=%u\n", pSvcCbData->mParms),
+ vrc = VERR_WRONG_PARAMETER_COUNT);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_32BIT,
+ ("type=%u\n", pSvcCbData->mpaParms[idx].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+
+ uint32_t const cbWritten = pSvcCbData->mpaParms[idx].u.uint32;
+
+ Log3ThisFunc(("cbWritten=%RU32\n", cbWritten));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mOffCurrent += cbWritten; /* Bogus for writeAt and append mode, thus GUEST_FILE_NOTIFYTYPE_WRITE_OFFSET. */
+ alock.release();
+
+ ::FireGuestFileWriteEvent(mEventSource, mSession, this, mData.mOffCurrent, cbWritten);
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_WRITE_OFFSET:
+ {
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 5, ("mParms=%u\n", pSvcCbData->mParms),
+ vrc = VERR_WRONG_PARAMETER_COUNT);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_32BIT,
+ ("type=%u\n", pSvcCbData->mpaParms[idx].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx + 1].type == VBOX_HGCM_SVC_PARM_64BIT,
+ ("type=%u\n", pSvcCbData->mpaParms[idx].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+ uint32_t const cbWritten = pSvcCbData->mpaParms[idx].u.uint32;
+ int64_t offNew = (int64_t)pSvcCbData->mpaParms[idx + 1].u.uint64;
+ Log3ThisFunc(("cbWritten=%RU32 offNew=%RI64 (%#RX64)\n", cbWritten, offNew, offNew));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (offNew < 0) /* non-seekable */
+ offNew = mData.mOffCurrent + cbWritten;
+ mData.mOffCurrent = offNew;
+ alock.release();
+
+ HRESULT hrc2 = ::FireGuestFileWriteEvent(mEventSource, mSession, this, offNew, cbWritten);
+ vrc = SUCCEEDED(hrc2) ? VINF_SUCCESS : Global::vboxStatusCodeFromCOM(hrc2);
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_SEEK:
+ {
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 4, ("mParms=%u\n", pSvcCbData->mParms),
+ vrc = VERR_WRONG_PARAMETER_COUNT);
+
+ vrc = HGCMSvcGetU64(&pSvcCbData->mpaParms[idx++], &dataCb.u.seek.uOffActual);
+ if (RT_FAILURE(vrc))
+ break;
+
+ Log3ThisFunc(("uOffActual=%RU64\n", dataCb.u.seek.uOffActual));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mOffCurrent = dataCb.u.seek.uOffActual;
+ alock.release();
+
+ ::FireGuestFileOffsetChangedEvent(mEventSource, mSession, this, dataCb.u.seek.uOffActual, 0 /* Processed */);
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_TELL:
+ /* We don't issue any HOST_MSG_FILE_TELL, so we shouldn't get these notifications! */
+ AssertFailed();
+ break;
+
+ case GUEST_FILE_NOTIFYTYPE_SET_SIZE:
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 4, ("mParms=%u\n", pSvcCbData->mParms),
+ vrc = VERR_WRONG_PARAMETER_COUNT);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_64BIT,
+ ("type=%u\n", pSvcCbData->mpaParms[idx].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+ dataCb.u.SetSize.cbSize = pSvcCbData->mpaParms[idx].u.uint64;
+ Log3ThisFunc(("cbSize=%RU64\n", dataCb.u.SetSize.cbSize));
+
+ ::FireGuestFileSizeChangedEvent(mEventSource, mSession, this, dataCb.u.SetSize.cbSize);
+ vrc = VINF_SUCCESS;
+ break;
+
+ default:
+ break;
+ }
+
+ try
+ {
+ if (RT_SUCCESS(vrc))
+ {
+ GuestWaitEventPayload payload(dataCb.uType, &dataCb, sizeof(dataCb));
+
+ /* Ignore rc, as the event to signal might not be there (anymore). */
+ signalWaitEventInternal(pCbCtx, vrcGuest, &payload);
+ }
+ else /* OOM situation, wrong HGCM parameters or smth. not expected. */
+ {
+ /* Ignore rc, as the event to signal might not be there (anymore). */
+ signalWaitEventInternalEx(pCbCtx, vrc, 0 /* guestRc */, NULL /* pPayload */);
+ }
+ }
+ catch (int vrcEx) /* Thrown by GuestWaitEventPayload constructor. */
+ {
+ /* Also try to signal the waiter, to let it know of the OOM situation.
+ * Ignore rc, as the event to signal might not be there (anymore). */
+ signalWaitEventInternalEx(pCbCtx, vrcEx, 0 /* guestRc */, NULL /* pPayload */);
+ vrc = vrcEx;
+ }
+
+ LogFlowThisFunc(("uType=%RU32, rcGuest=%Rrc, rc=%Rrc\n", dataCb.uType, vrcGuest, vrc));
+ return vrc;
+}
+
+/**
+ * Called when the guest side of the file has been disconnected (closed, terminated, +++).
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ */
+int GuestFile::i_onGuestDisconnected(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ int vrc = i_setFileStatus(FileStatus_Down, VINF_SUCCESS);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * @copydoc GuestObject::i_onUnregister
+ */
+int GuestFile::i_onUnregister(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+
+ /*
+ * Note: The event source stuff holds references to this object,
+ * so make sure that this is cleaned up *before* calling uninit().
+ */
+ if (!mEventSource.isNull())
+ {
+ mEventSource->UnregisterListener(mLocalListener);
+
+ mLocalListener.setNull();
+ unconst(mEventSource).setNull();
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * @copydoc GuestObject::i_onSessionStatusChange
+ */
+int GuestFile::i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc = VINF_SUCCESS;
+
+ /* If the session now is in a terminated state, set the file status
+ * to "down", as there is not much else we can do now. */
+ if (GuestSession::i_isTerminated(enmSessionStatus))
+ vrc = i_setFileStatus(FileStatus_Down, 0 /* fileRc, ignored */);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Opens the file on the guest.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestFile::i_openFile(uint32_t uTimeoutMS, int *prcGuest)
+{
+ AssertReturn(mData.mOpenInfo.mFilename.isNotEmpty(), VERR_INVALID_PARAMETER);
+
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("strFile=%s, enmAccessMode=%d, enmOpenAction=%d, uCreationMode=%o, mfOpenEx=%#x\n",
+ mData.mOpenInfo.mFilename.c_str(), mData.mOpenInfo.mAccessMode, mData.mOpenInfo.mOpenAction,
+ mData.mOpenInfo.mCreationMode, mData.mOpenInfo.mfOpenEx));
+
+ /* Validate and translate open action. */
+ const char *pszOpenAction = NULL;
+ switch (mData.mOpenInfo.mOpenAction)
+ {
+ case FileOpenAction_OpenExisting: pszOpenAction = "oe"; break;
+ case FileOpenAction_OpenOrCreate: pszOpenAction = "oc"; break;
+ case FileOpenAction_CreateNew: pszOpenAction = "ce"; break;
+ case FileOpenAction_CreateOrReplace: pszOpenAction = "ca"; break;
+ case FileOpenAction_OpenExistingTruncated: pszOpenAction = "ot"; break;
+ case FileOpenAction_AppendOrCreate:
+ pszOpenAction = "oa"; /** @todo get rid of this one and implement AppendOnly/AppendRead. */
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Validate and translate access mode. */
+ const char *pszAccessMode = NULL;
+ switch (mData.mOpenInfo.mAccessMode)
+ {
+ case FileAccessMode_ReadOnly: pszAccessMode = "r"; break;
+ case FileAccessMode_WriteOnly: pszAccessMode = "w"; break;
+ case FileAccessMode_ReadWrite: pszAccessMode = "r+"; break;
+ case FileAccessMode_AppendOnly: pszAccessMode = "a"; break;
+ case FileAccessMode_AppendRead: pszAccessMode = "a+"; break;
+ default: return VERR_INVALID_PARAMETER;
+ }
+
+ /* Validate and translate sharing mode. */
+ const char *pszSharingMode = NULL;
+ switch (mData.mOpenInfo.mSharingMode)
+ {
+ case FileSharingMode_All: pszSharingMode = ""; break;
+ case FileSharingMode_Read: RT_FALL_THRU();
+ case FileSharingMode_Write: RT_FALL_THRU();
+ case FileSharingMode_ReadWrite: RT_FALL_THRU();
+ case FileSharingMode_Delete: RT_FALL_THRU();
+ case FileSharingMode_ReadDelete: RT_FALL_THRU();
+ case FileSharingMode_WriteDelete: return VERR_NOT_IMPLEMENTED;
+ default: return VERR_INVALID_PARAMETER;
+ }
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[8];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetPv(&paParms[i++], (void*)mData.mOpenInfo.mFilename.c_str(),
+ (ULONG)mData.mOpenInfo.mFilename.length() + 1);
+ HGCMSvcSetStr(&paParms[i++], pszAccessMode);
+ HGCMSvcSetStr(&paParms[i++], pszOpenAction);
+ HGCMSvcSetStr(&paParms[i++], pszSharingMode);
+ HGCMSvcSetU32(&paParms[i++], mData.mOpenInfo.mCreationMode);
+ HGCMSvcSetU64(&paParms[i++], 0 /*unused offset*/);
+ /** @todo Next protocol version: add flags, replace strings, remove initial offset. */
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_OPEN, i, paParms);
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForStatusChange(pEvent, uTimeoutMS, NULL /* FileStatus */, prcGuest);
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Queries file system information from a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param objData Where to store the file system object data on success.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestFile::i_queryInfo(GuestFsObjData &objData, int *prcGuest)
+{
+ AssertPtr(mSession);
+ return mSession->i_fsQueryInfo(mData.mOpenInfo.mFilename, FALSE /* fFollowSymlinks */, objData, prcGuest);
+}
+
+/**
+ * Reads data from a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uSize Size (in bytes) to read.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Where to store the read data on success.
+ * @param cbData Size (in bytes) of \a pvData on input.
+ * @param pcbRead Where to return to size (in bytes) read on success.
+ * Optional.
+ */
+int GuestFile::i_readData(uint32_t uSize, uint32_t uTimeoutMS,
+ void* pvData, uint32_t cbData, uint32_t* pcbRead)
+{
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("uSize=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%zu\n",
+ uSize, uTimeoutMS, pvData, cbData));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileRead);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */);
+ HGCMSvcSetU32(&paParms[i++], uSize /* Size (in bytes) to read */);
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_READ, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t cbRead = 0;
+ vrc = i_waitForRead(pEvent, uTimeoutMS, pvData, cbData, &cbRead);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("cbRead=%RU32\n", cbRead));
+ if (pcbRead)
+ *pcbRead = cbRead;
+ }
+ else if (pEvent->HasGuestError()) /* Return guest rc if available. */
+ {
+ vrc = pEvent->GetGuestError();
+ }
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Reads data from a specific position from a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uOffset Offset (in bytes) to start reading from.
+ * @param uSize Size (in bytes) to read.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Where to store the read data on success.
+ * @param cbData Size (in bytes) of \a pvData on input.
+ * @param pcbRead Where to return to size (in bytes) read on success.
+ * Optional.
+ */
+int GuestFile::i_readDataAt(uint64_t uOffset, uint32_t uSize, uint32_t uTimeoutMS,
+ void* pvData, size_t cbData, size_t* pcbRead)
+{
+ LogFlowThisFunc(("uOffset=%RU64, uSize=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%zu\n",
+ uOffset, uSize, uTimeoutMS, pvData, cbData));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileRead);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */);
+ HGCMSvcSetU64(&paParms[i++], uOffset /* Offset (in bytes) to start reading */);
+ HGCMSvcSetU32(&paParms[i++], uSize /* Size (in bytes) to read */);
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_READ_AT, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t cbRead = 0;
+ vrc = i_waitForRead(pEvent, uTimeoutMS, pvData, cbData, &cbRead);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("cbRead=%RU32\n", cbRead));
+
+ if (pcbRead)
+ *pcbRead = cbRead;
+ }
+ else if (pEvent->HasGuestError()) /* Return guest rc if available. */
+ {
+ vrc = pEvent->GetGuestError();
+ }
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Seeks a guest file to a specific position.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param iOffset Offset (in bytes) to seek.
+ * @param eSeekType Seek type to use.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param puOffset Where to return the new current file position (in bytes) on success.
+ */
+int GuestFile::i_seekAt(int64_t iOffset, GUEST_FILE_SEEKTYPE eSeekType,
+ uint32_t uTimeoutMS, uint64_t *puOffset)
+{
+ LogFlowThisFunc(("iOffset=%RI64, uTimeoutMS=%RU32\n",
+ iOffset, uTimeoutMS));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileOffsetChanged);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */);
+ HGCMSvcSetU32(&paParms[i++], eSeekType /* Seek method */);
+ /** @todo uint64_t vs. int64_t! */
+ HGCMSvcSetU64(&paParms[i++], (uint64_t)iOffset /* Offset (in bytes) to start reading */);
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_SEEK, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ uint64_t uOffset;
+ vrc = i_waitForOffsetChange(pEvent, uTimeoutMS, &uOffset);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("uOffset=%RU64\n", uOffset));
+
+ if (puOffset)
+ *puOffset = uOffset;
+ }
+ else if (pEvent->HasGuestError()) /* Return guest rc if available. */
+ {
+ vrc = pEvent->GetGuestError();
+ }
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sets the current internal file object status.
+ *
+ * @returns VBox status code.
+ * @param fileStatus New file status to set.
+ * @param fileRc New result code to set.
+ *
+ * @note Takes the write lock.
+ */
+int GuestFile::i_setFileStatus(FileStatus_T fileStatus, int fileRc)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, fileRc=%Rrc\n",
+ mData.mStatus, fileStatus, fileRc));
+
+#ifdef VBOX_STRICT
+ if (fileStatus == FileStatus_Error)
+ {
+ AssertMsg(RT_FAILURE(fileRc), ("Guest rc must be an error (%Rrc)\n", fileRc));
+ }
+ else
+ AssertMsg(RT_SUCCESS(fileRc), ("Guest rc must not be an error (%Rrc)\n", fileRc));
+#endif
+
+ if (mData.mStatus != fileStatus)
+ {
+ mData.mStatus = fileStatus;
+ mData.mLastError = fileRc;
+
+ ComObjPtr<VirtualBoxErrorInfo> errorInfo;
+ HRESULT hr = errorInfo.createObject();
+ ComAssertComRC(hr);
+ if (RT_FAILURE(fileRc))
+ {
+ hr = errorInfo->initEx(VBOX_E_IPRT_ERROR, fileRc,
+ COM_IIDOF(IGuestFile), getComponentName(),
+ i_guestErrorToString(fileRc, mData.mOpenInfo.mFilename.c_str()));
+ ComAssertComRC(hr);
+ }
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestFileStateChangedEvent(mEventSource, mSession, this, fileStatus, errorInfo);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Waits for a guest file offset change.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param pEvent Guest wait event to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param puOffset Where to return the new offset (in bytes) on success.
+ * Optional and can be NULL.
+ */
+int GuestFile::i_waitForOffsetChange(GuestWaitEvent *pEvent,
+ uint32_t uTimeoutMS, uint64_t *puOffset)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestFileOffsetChanged)
+ {
+ if (puOffset)
+ {
+ ComPtr<IGuestFileOffsetChangedEvent> pFileEvent = pIEvent;
+ Assert(!pFileEvent.isNull());
+
+ HRESULT hr = pFileEvent->COMGETTER(Offset)((LONG64*)puOffset);
+ ComAssertComRC(hr);
+ }
+ }
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
+ }
+
+ return vrc;
+}
+
+/**
+ * Waits for reading from a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param pEvent Guest wait event to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Where to store read file data on success.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param pcbRead Where to return the actual bytes read on success.
+ * Optional and can be NULL.
+ */
+int GuestFile::i_waitForRead(GuestWaitEvent *pEvent, uint32_t uTimeoutMS,
+ void *pvData, size_t cbData, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestFileRead)
+ {
+ vrc = VINF_SUCCESS;
+
+ ComPtr<IGuestFileReadEvent> pFileEvent = pIEvent;
+ Assert(!pFileEvent.isNull());
+
+ if (pvData)
+ {
+ com::SafeArray <BYTE> data;
+ HRESULT hrc1 = pFileEvent->COMGETTER(Data)(ComSafeArrayAsOutParam(data));
+ ComAssertComRC(hrc1);
+ const size_t cbRead = data.size();
+ if (cbRead)
+ {
+ if (cbRead <= cbData)
+ memcpy(pvData, data.raw(), cbRead);
+ else
+ vrc = VERR_BUFFER_OVERFLOW;
+ }
+ /* else: used to be VERR_NO_DATA, but that messes stuff up. */
+
+ if (pcbRead)
+ {
+ *pcbRead = (uint32_t)cbRead;
+ Assert(*pcbRead == cbRead);
+ }
+ }
+ else if (pcbRead)
+ {
+ *pcbRead = 0;
+ HRESULT hrc2 = pFileEvent->COMGETTER(Processed)((ULONG *)pcbRead);
+ ComAssertComRC(hrc2); NOREF(hrc2);
+ }
+ }
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
+ }
+
+ return vrc;
+}
+
+/**
+ * Waits for a guest file status change.
+ *
+ * @note Similar code in GuestProcess::i_waitForStatusChange() and
+ * GuestSession::i_waitForStatusChange().
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param pEvent Guest wait event to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pFileStatus Where to return the file status on success.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned.
+ */
+int GuestFile::i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t uTimeoutMS,
+ FileStatus_T *pFileStatus, int *prcGuest)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+ /* pFileStatus is optional. */
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(evtType == VBoxEventType_OnGuestFileStateChanged);
+ ComPtr<IGuestFileStateChangedEvent> pFileEvent = pIEvent;
+ Assert(!pFileEvent.isNull());
+
+ HRESULT hr;
+ if (pFileStatus)
+ {
+ hr = pFileEvent->COMGETTER(Status)(pFileStatus);
+ ComAssertComRC(hr);
+ }
+
+ ComPtr<IVirtualBoxErrorInfo> errorInfo;
+ hr = pFileEvent->COMGETTER(Error)(errorInfo.asOutParam());
+ ComAssertComRC(hr);
+
+ LONG lGuestRc;
+ hr = errorInfo->COMGETTER(ResultDetail)(&lGuestRc);
+ ComAssertComRC(hr);
+
+ LogFlowThisFunc(("resultDetail=%RI32 (%Rrc)\n",
+ lGuestRc, lGuestRc));
+
+ if (RT_FAILURE((int)lGuestRc))
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+
+ if (prcGuest)
+ *prcGuest = (int)lGuestRc;
+ }
+ /* waitForEvent may also return VERR_GSTCTL_GUEST_ERROR like we do above, so make prcGuest is set. */
+ /** @todo r=bird: Andy, you seem to have forgotten this scenario. Showed up occasionally when
+ * using the wrong password with a copyto command in a debug build on windows, error info
+ * contained "Unknown Status -858993460 (0xcccccccc)". As you know windows fills the stack frames
+ * with 0xcccccccc in debug builds to highlight use of uninitialized data, so that's what happened
+ * here. It's actually good you didn't initialize lGuest, as it would be heck to find otherwise.
+ *
+ * I'm still not very impressed with the error managment or the usuefullness of the documentation
+ * in this code, though the latter is getting better! */
+ else if (vrc == VERR_GSTCTL_GUEST_ERROR && prcGuest)
+ *prcGuest = pEvent->GuestResult();
+ Assert(vrc != VERR_GSTCTL_GUEST_ERROR || !prcGuest || *prcGuest != (int)0xcccccccc);
+
+ return vrc;
+}
+
+int GuestFile::i_waitForWrite(GuestWaitEvent *pEvent,
+ uint32_t uTimeoutMS, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestFileWrite)
+ {
+ if (pcbWritten)
+ {
+ ComPtr<IGuestFileWriteEvent> pFileEvent = pIEvent;
+ Assert(!pFileEvent.isNull());
+
+ HRESULT hr = pFileEvent->COMGETTER(Processed)((ULONG*)pcbWritten);
+ ComAssertComRC(hr);
+ }
+ }
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
+ }
+
+ return vrc;
+}
+
+/**
+ * Writes data to a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Data to write.
+ * @param cbData Size (in bytes) of \a pvData to write.
+ * @param pcbWritten Where to return to size (in bytes) written on success.
+ * Optional.
+ */
+int GuestFile::i_writeData(uint32_t uTimeoutMS, const void *pvData, uint32_t cbData,
+ uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("uTimeoutMS=%RU32, pvData=%p, cbData=%zu\n",
+ uTimeoutMS, pvData, cbData));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileWrite);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[8];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */);
+ HGCMSvcSetU32(&paParms[i++], cbData /* Size (in bytes) to write */);
+ HGCMSvcSetPv (&paParms[i++], unconst(pvData), cbData);
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_WRITE, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t cbWritten = 0;
+ vrc = i_waitForWrite(pEvent, uTimeoutMS, &cbWritten);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("cbWritten=%RU32\n", cbWritten));
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ }
+ else if (pEvent->HasGuestError()) /* Return guest rc if available. */
+ {
+ vrc = pEvent->GetGuestError();
+ }
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+
+/**
+ * Writes data to a specific position to a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uOffset Offset (in bytes) to start writing at.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Data to write.
+ * @param cbData Size (in bytes) of \a pvData to write.
+ * @param pcbWritten Where to return to size (in bytes) written on success.
+ * Optional.
+ */
+int GuestFile::i_writeDataAt(uint64_t uOffset, uint32_t uTimeoutMS,
+ const void *pvData, uint32_t cbData, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("uOffset=%RU64, uTimeoutMS=%RU32, pvData=%p, cbData=%zu\n",
+ uOffset, uTimeoutMS, pvData, cbData));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileWrite);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[8];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */);
+ HGCMSvcSetU64(&paParms[i++], uOffset /* Offset where to starting writing */);
+ HGCMSvcSetU32(&paParms[i++], cbData /* Size (in bytes) to write */);
+ HGCMSvcSetPv (&paParms[i++], unconst(pvData), cbData);
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_WRITE_AT, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t cbWritten = 0;
+ vrc = i_waitForWrite(pEvent, uTimeoutMS, &cbWritten);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("cbWritten=%RU32\n", cbWritten));
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ }
+ else if (pEvent->HasGuestError()) /* Return guest rc if available. */
+ {
+ vrc = pEvent->GetGuestError();
+ }
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+// Wrapped IGuestFile methods
+/////////////////////////////////////////////////////////////////////////////
+HRESULT GuestFile::close()
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFlowThisFuncEnter();
+
+ /* Close file on guest. */
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_closeFile(&vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_File, vrcGuest, mData.mOpenInfo.mFilename.c_str());
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Closing guest file failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Closing guest file \"%s\" failed with %Rrc\n"),
+ mData.mOpenInfo.mFilename.c_str(), vrc);
+ }
+
+ LogFlowThisFunc(("Returning S_OK / vrc=%Rrc\n", vrc));
+ return S_OK;
+}
+
+HRESULT GuestFile::queryInfo(ComPtr<IFsObjInfo> &aObjInfo)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ GuestFsObjData fsObjData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_queryInfo(fsObjData, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ ComObjPtr<GuestFsObjInfo> ptrFsObjInfo;
+ hrc = ptrFsObjInfo.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ vrc = ptrFsObjInfo->init(fsObjData);
+ if (RT_SUCCESS(vrc))
+ hrc = ptrFsObjInfo.queryInterfaceTo(aObjInfo.asOutParam());
+ else
+ hrc = setErrorVrc(vrc,
+ tr("Initialization of guest file object for \"%s\" failed: %Rrc"),
+ mData.mOpenInfo.mFilename.c_str(), vrc);
+ }
+ }
+ else
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, vrcGuest, mData.mOpenInfo.mFilename.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Querying guest file information failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ else
+ hrc = setErrorVrc(vrc,
+ tr("Querying guest file information for \"%s\" failed: %Rrc"), mData.mOpenInfo.mFilename.c_str(), vrc);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::querySize(LONG64 *aSize)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ GuestFsObjData fsObjData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_queryInfo(fsObjData, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ *aSize = fsObjData.mObjectSize;
+ }
+ else
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, vrcGuest, mData.mOpenInfo.mFilename.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Querying guest file size failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Querying guest file size for \"%s\" failed: %Rrc"), mData.mOpenInfo.mFilename.c_str(), vrc);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::read(ULONG aToRead, ULONG aTimeoutMS, std::vector<BYTE> &aData)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (aToRead == 0)
+ return setError(E_INVALIDARG, tr("The size to read is zero"));
+
+ LogFlowThisFuncEnter();
+
+ /* Cap the read at 1MiB because that's all the guest will return anyway. */
+ if (aToRead > _1M)
+ aToRead = _1M;
+
+ HRESULT hrc = S_OK;
+
+ int vrc;
+ try
+ {
+ aData.resize(aToRead);
+
+ uint32_t cbRead;
+ vrc = i_readData(aToRead, aTimeoutMS,
+ &aData.front(), aToRead, &cbRead);
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (aData.size() != cbRead)
+ aData.resize(cbRead);
+ }
+ else
+ {
+ aData.resize(0);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading from file \"%s\" failed: %Rrc"),
+ mData.mOpenInfo.mFilename.c_str(), vrc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::readAt(LONG64 aOffset, ULONG aToRead, ULONG aTimeoutMS, std::vector<BYTE> &aData)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (aToRead == 0)
+ return setError(E_INVALIDARG, tr("The size to read for guest file \"%s\" is zero"), mData.mOpenInfo.mFilename.c_str());
+
+ LogFlowThisFuncEnter();
+
+ /* Cap the read at 1MiB because that's all the guest will return anyway. */
+ if (aToRead > _1M)
+ aToRead = _1M;
+
+ HRESULT hrc = S_OK;
+
+ int vrc;
+ try
+ {
+ aData.resize(aToRead);
+
+ size_t cbRead;
+ vrc = i_readDataAt(aOffset, aToRead, aTimeoutMS,
+ &aData.front(), aToRead, &cbRead);
+ if (RT_SUCCESS(vrc))
+ {
+ if (aData.size() != cbRead)
+ aData.resize(cbRead);
+ }
+ else
+ {
+ aData.resize(0);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading from file \"%s\" (at offset %RU64) failed: %Rrc"),
+ mData.mOpenInfo.mFilename.c_str(), aOffset, vrc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::seek(LONG64 aOffset, FileSeekOrigin_T aWhence, LONG64 *aNewOffset)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ HRESULT hrc = S_OK;
+
+ GUEST_FILE_SEEKTYPE eSeekType;
+ switch (aWhence)
+ {
+ case FileSeekOrigin_Begin:
+ eSeekType = GUEST_FILE_SEEKTYPE_BEGIN;
+ break;
+
+ case FileSeekOrigin_Current:
+ eSeekType = GUEST_FILE_SEEKTYPE_CURRENT;
+ break;
+
+ case FileSeekOrigin_End:
+ eSeekType = GUEST_FILE_SEEKTYPE_END;
+ break;
+
+ default:
+ return setError(E_INVALIDARG, tr("Invalid seek type for guest file \"%s\" specified"),
+ mData.mOpenInfo.mFilename.c_str());
+ }
+
+ LogFlowThisFuncEnter();
+
+ uint64_t uNewOffset;
+ int vrc = i_seekAt(aOffset, eSeekType,
+ 30 * 1000 /* 30s timeout */, &uNewOffset);
+ if (RT_SUCCESS(vrc))
+ *aNewOffset = RT_MIN(uNewOffset, (uint64_t)INT64_MAX);
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Seeking file \"%s\" (to offset %RI64) failed: %Rrc"),
+ mData.mOpenInfo.mFilename.c_str(), aOffset, vrc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::setACL(const com::Utf8Str &aAcl, ULONG aMode)
+{
+ RT_NOREF(aAcl, aMode);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestFile::setSize(LONG64 aSize)
+{
+ LogFlowThisFuncEnter();
+
+ /*
+ * Validate.
+ */
+ if (aSize < 0)
+ return setError(E_INVALIDARG, tr("The size (%RI64) for guest file \"%s\" cannot be a negative value"),
+ aSize, mData.mOpenInfo.mFilename.c_str());
+
+ /*
+ * Register event callbacks.
+ */
+ int vrc;
+ GuestWaitEvent *pWaitEvent = NULL;
+ GuestEventTypes lstEventTypes;
+ try
+ {
+ lstEventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ lstEventTypes.push_back(VBoxEventType_OnGuestFileSizeChanged);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ vrc = registerWaitEvent(lstEventTypes, &pWaitEvent);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Send of the HGCM message.
+ */
+ VBOXHGCMSVCPARM aParms[3];
+ HGCMSvcSetU32(&aParms[0], pWaitEvent->ContextID());
+ HGCMSvcSetU32(&aParms[1], mObjectID /* File handle */);
+ HGCMSvcSetU64(&aParms[2], aSize);
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_SET_SIZE, RT_ELEMENTS(aParms), aParms);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Wait for the event.
+ */
+ VBoxEventType_T enmEvtType;
+ ComPtr<IEvent> pIEvent;
+ vrc = waitForEvent(pWaitEvent, RT_MS_1MIN / 2, &enmEvtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (enmEvtType == VBoxEventType_OnGuestFileSizeChanged)
+ vrc = VINF_SUCCESS;
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
+ }
+ if (RT_FAILURE(vrc) && pWaitEvent->HasGuestError()) /* Return guest rc if available. */
+ vrc = pWaitEvent->GetGuestError();
+ }
+
+ /*
+ * Unregister the wait event and deal with error reporting if needed.
+ */
+ unregisterWaitEvent(pWaitEvent);
+ }
+ HRESULT hrc;
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Setting the guest file size of \"%s\" to %RU64 (%#RX64) bytes failed: %Rrc", "", aSize),
+ mData.mOpenInfo.mFilename.c_str(), aSize, aSize, vrc);
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::write(const std::vector<BYTE> &aData, ULONG aTimeoutMS, ULONG *aWritten)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (aData.size() == 0)
+ return setError(E_INVALIDARG, tr("No data to write specified"), mData.mOpenInfo.mFilename.c_str());
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ const uint32_t cbData = (uint32_t)aData.size();
+ const void *pvData = (void *)&aData.front();
+ int vrc = i_writeData(aTimeoutMS, pvData, cbData, (uint32_t*)aWritten);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Writing %zu bytes to guest file \"%s\" failed: %Rrc", "", aData.size()),
+ aData.size(), mData.mOpenInfo.mFilename.c_str(), vrc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::writeAt(LONG64 aOffset, const std::vector<BYTE> &aData, ULONG aTimeoutMS, ULONG *aWritten)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (aData.size() == 0)
+ return setError(E_INVALIDARG, tr("No data to write at for guest file \"%s\" specified"), mData.mOpenInfo.mFilename.c_str());
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ const uint32_t cbData = (uint32_t)aData.size();
+ const void *pvData = (void *)&aData.front();
+ int vrc = i_writeDataAt(aOffset, aTimeoutMS, pvData, cbData, (uint32_t*)aWritten);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Writing %zu bytes to file \"%s\" (at offset %RU64) failed: %Rrc", "", aData.size()),
+ aData.size(), mData.mOpenInfo.mFilename.c_str(), aOffset, vrc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
diff --git a/src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp b/src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp
new file mode 100644
index 00000000..df4d1c59
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp
@@ -0,0 +1,237 @@
+/* $Id: GuestFsObjInfoImpl.cpp $ */
+/** @file
+ * VirtualBox Main - Guest file system object information handling.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_GUESTFSOBJINFO
+#include "LoggingNew.h"
+
+#ifndef VBOX_WITH_GUEST_CONTROL
+# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
+#endif
+#include "GuestFsObjInfoImpl.h"
+#include "GuestCtrlImplPrivate.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+
+#include <VBox/com/array.h>
+
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(GuestFsObjInfo)
+
+HRESULT GuestFsObjInfo::FinalConstruct(void)
+{
+ LogFlowThisFuncEnter();
+ return BaseFinalConstruct();
+}
+
+void GuestFsObjInfo::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+int GuestFsObjInfo::init(const GuestFsObjData &objData)
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED);
+
+ mData = objData;
+
+ /* Confirm a successful initialization when it's the case. */
+ autoInitSpan.setSucceeded();
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void GuestFsObjInfo::uninit(void)
+{
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFuncEnter();
+}
+
+// implementation of wrapped private getters/setters for attributes
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestFsObjInfo::getAccessTime(LONG64 *aAccessTime)
+{
+ *aAccessTime = mData.mAccessTime;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getAllocatedSize(LONG64 *aAllocatedSize)
+{
+ *aAllocatedSize = mData.mAllocatedSize;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getBirthTime(LONG64 *aBirthTime)
+{
+ *aBirthTime = mData.mBirthTime;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getChangeTime(LONG64 *aChangeTime)
+{
+ *aChangeTime = mData.mChangeTime;
+
+ return S_OK;
+}
+
+
+
+HRESULT GuestFsObjInfo::getDeviceNumber(ULONG *aDeviceNumber)
+{
+ *aDeviceNumber = mData.mDeviceNumber;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getFileAttributes(com::Utf8Str &aFileAttributes)
+{
+ aFileAttributes = mData.mFileAttrs;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getGenerationId(ULONG *aGenerationId)
+{
+ *aGenerationId = mData.mGenerationID;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getGID(LONG *aGID)
+{
+ *aGID = mData.mGID;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getGroupName(com::Utf8Str &aGroupName)
+{
+ aGroupName = mData.mGroupName;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getHardLinks(ULONG *aHardLinks)
+{
+ *aHardLinks = mData.mNumHardLinks;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getModificationTime(LONG64 *aModificationTime)
+{
+ *aModificationTime = mData.mModificationTime;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getName(com::Utf8Str &aName)
+{
+ aName = mData.mName;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getNodeId(LONG64 *aNodeId)
+{
+ *aNodeId = mData.mNodeID;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getNodeIdDevice(ULONG *aNodeIdDevice)
+{
+ *aNodeIdDevice = mData.mNodeIDDevice;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getObjectSize(LONG64 *aObjectSize)
+{
+ *aObjectSize = mData.mObjectSize;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getType(FsObjType_T *aType)
+{
+ *aType = mData.mType;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getUID(LONG *aUID)
+{
+ *aUID = mData.mUID;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getUserFlags(ULONG *aUserFlags)
+{
+ *aUserFlags = mData.mUserFlags;
+
+ return S_OK;
+}
+
+HRESULT GuestFsObjInfo::getUserName(com::Utf8Str &aUserName)
+{
+ aUserName = mData.mUserName;
+
+ return S_OK;
+}
+
diff --git a/src/VBox/Main/src-client/GuestImpl.cpp b/src/VBox/Main/src-client/GuestImpl.cpp
new file mode 100644
index 00000000..227da127
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestImpl.cpp
@@ -0,0 +1,1201 @@
+/* $Id: GuestImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation: Guest features.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_GUEST
+#include "LoggingNew.h"
+
+#include "GuestImpl.h"
+#ifdef VBOX_WITH_GUEST_CONTROL
+# include "GuestSessionImpl.h"
+#endif
+#include "Global.h"
+#include "ConsoleImpl.h"
+#include "ProgressImpl.h"
+#ifdef VBOX_WITH_DRAG_AND_DROP
+# include "GuestDnDPrivate.h"
+#endif
+#include "VMMDev.h"
+
+#include "AutoCaller.h"
+#include "Performance.h"
+#include "VBoxEvents.h"
+
+#include <VBox/VMMDev.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/ctype.h>
+#include <iprt/stream.h>
+#include <iprt/timer.h>
+#include <VBox/vmm/pgm.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/version.h>
+
+// defines
+/////////////////////////////////////////////////////////////////////////////
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(Guest)
+
+HRESULT Guest::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void Guest::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the guest object.
+ */
+HRESULT Guest::init(Console *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ ULONG aMemoryBalloonSize = 0;
+ HRESULT hr = mParent->i_machine()->COMGETTER(MemoryBalloonSize)(&aMemoryBalloonSize);
+ if (SUCCEEDED(hr))
+ mMemoryBalloonSize = aMemoryBalloonSize;
+ else
+ mMemoryBalloonSize = 0; /* Default is no ballooning */
+
+ BOOL fPageFusionEnabled = FALSE;
+ hr = mParent->i_machine()->COMGETTER(PageFusionEnabled)(&fPageFusionEnabled);
+ if (SUCCEEDED(hr))
+ mfPageFusionEnabled = fPageFusionEnabled;
+ else
+ mfPageFusionEnabled = false; /* Default is no page fusion*/
+
+ mStatUpdateInterval = 0; /* Default is not to report guest statistics at all */
+ mCollectVMMStats = false;
+
+ /* Clear statistics. */
+ mNetStatRx = mNetStatTx = 0;
+ mNetStatLastTs = RTTimeNanoTS();
+ for (unsigned i = 0 ; i < GUESTSTATTYPE_MAX; i++)
+ mCurrentGuestStat[i] = 0;
+ mVmValidStats = pm::VMSTATMASK_NONE;
+ RT_ZERO(mCurrentGuestCpuUserStat);
+ RT_ZERO(mCurrentGuestCpuKernelStat);
+ RT_ZERO(mCurrentGuestCpuIdleStat);
+
+ mMagic = GUEST_MAGIC;
+ mStatTimer = NIL_RTTIMERLR;
+
+ hr = unconst(mEventSource).createObject();
+ if (SUCCEEDED(hr))
+ hr = mEventSource->init();
+
+ mCpus = 1;
+
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ if (SUCCEEDED(hr))
+ {
+ try
+ {
+ GuestDnD::createInstance(this /* pGuest */);
+ hr = unconst(mDnDSource).createObject();
+ if (SUCCEEDED(hr))
+ hr = mDnDSource->init(this /* pGuest */);
+ if (SUCCEEDED(hr))
+ {
+ hr = unconst(mDnDTarget).createObject();
+ if (SUCCEEDED(hr))
+ hr = mDnDTarget->init(this /* pGuest */);
+ }
+
+ LogFlowFunc(("Drag and drop initializied with hr=%Rhrc\n", hr));
+ }
+ catch (std::bad_alloc &)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+#endif
+
+ /* Confirm a successful initialization when it's the case: */
+ if (SUCCEEDED(hr))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed();
+
+ LogFlowFunc(("hr=%Rhrc\n", hr));
+ return hr;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void Guest::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ /* Destroy stat update timer */
+ int vrc = RTTimerLRDestroy(mStatTimer);
+ AssertMsgRC(vrc, ("Failed to create guest statistics update timer(%Rra)\n", vrc));
+ mStatTimer = NIL_RTTIMERLR;
+ mMagic = 0;
+
+#ifdef VBOX_WITH_GUEST_CONTROL
+ LogFlowThisFunc(("Closing sessions (%RU64 total)\n",
+ mData.mGuestSessions.size()));
+ GuestSessions::iterator itSessions = mData.mGuestSessions.begin();
+ while (itSessions != mData.mGuestSessions.end())
+ {
+# ifdef DEBUG
+/** @todo r=bird: hit a use-after-free situation here while debugging the
+ * 0xcccccccc status code issue in copyto. My bet is that this happens
+ * because of an uninit race, where GuestSession::close(), or someone, does
+ * not ensure that the parent object (Guest) is okay to use (in the AutoCaller
+ * sense), only their own object. */
+ ULONG cRefs = itSessions->second->AddRef();
+ LogFlowThisFunc(("sessionID=%RU32, cRefs=%RU32\n", itSessions->first, cRefs > 1 ? cRefs - 1 : 0));
+ itSessions->second->Release();
+# endif
+ itSessions->second->uninit();
+ ++itSessions;
+ }
+ mData.mGuestSessions.clear();
+#endif
+
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ GuestDnD::destroyInstance();
+ unconst(mDnDSource).setNull();
+ unconst(mDnDTarget).setNull();
+#endif
+
+ unconst(mEventSource).setNull();
+ unconst(mParent) = NULL;
+
+ LogFlowFuncLeave();
+}
+
+/* static */
+DECLCALLBACK(void) Guest::i_staticUpdateStats(RTTIMERLR hTimerLR, void *pvUser, uint64_t iTick)
+{
+ AssertReturnVoid(pvUser != NULL);
+ Guest *guest = static_cast<Guest *>(pvUser);
+ Assert(guest->mMagic == GUEST_MAGIC);
+ if (guest->mMagic == GUEST_MAGIC)
+ guest->i_updateStats(iTick);
+
+ NOREF(hTimerLR);
+}
+
+/* static */
+DECLCALLBACK(int) Guest::i_staticEnumStatsCallback(const char *pszName, STAMTYPE enmType, void *pvSample,
+ STAMUNIT enmUnit, const char *pszUnit, STAMVISIBILITY enmVisiblity,
+ const char *pszDesc, void *pvUser)
+{
+ RT_NOREF(enmVisiblity, pszDesc, pszUnit);
+ AssertLogRelMsgReturn(enmType == STAMTYPE_COUNTER, ("Unexpected sample type %d ('%s')\n", enmType, pszName), VINF_SUCCESS);
+ AssertLogRelMsgReturn(enmUnit == STAMUNIT_BYTES, ("Unexpected sample unit %d ('%s')\n", enmUnit, pszName), VINF_SUCCESS);
+
+ /* Get the base name w/ slash. */
+ const char *pszLastSlash = strrchr(pszName, '/');
+ AssertLogRelMsgReturn(pszLastSlash, ("Unexpected sample '%s'\n", pszName), VINF_SUCCESS);
+
+ /* Receive or transmit? */
+ bool fRx;
+ if (!strcmp(pszLastSlash, "/BytesReceived"))
+ fRx = true;
+ else if (!strcmp(pszLastSlash, "/BytesTransmitted"))
+ fRx = false;
+ else
+ AssertLogRelMsgFailedReturn(("Unexpected sample '%s'\n", pszName), VINF_SUCCESS);
+
+#if 0 /* not used for anything, so don't bother parsing it. */
+ /* Find start of instance number. ASSUMES '/Public/Net/Name<Instance digits>/Bytes...' */
+ do
+ --pszLastSlash;
+ while (pszLastSlash > pszName && RT_C_IS_DIGIT(*pszLastSlash));
+ pszLastSlash++;
+
+ uint8_t uInstance;
+ int vrc = RTStrToUInt8Ex(pszLastSlash, NULL, 10, &uInstance);
+ AssertLogRelMsgReturn(RT_SUCCESS(vrc) && vrc != VWRN_NUMBER_TOO_BIG && vrc != VWRN_NEGATIVE_UNSIGNED,
+ ("%Rrc '%s'\n", vrc, pszName), VINF_SUCCESS)
+#endif
+
+ /* Add the bytes to our counters. */
+ PSTAMCOUNTER pCnt = (PSTAMCOUNTER)pvSample;
+ Guest *pGuest = (Guest *)pvUser;
+ uint64_t cb = pCnt->c;
+#if 0
+ LogFlowFunc(("%s i=%u d=%s %llu bytes\n", pszName, uInstance, fRx ? "RX" : "TX", cb));
+#else
+ LogFlowFunc(("%s d=%s %llu bytes\n", pszName, fRx ? "RX" : "TX", cb));
+#endif
+ if (fRx)
+ pGuest->mNetStatRx += cb;
+ else
+ pGuest->mNetStatTx += cb;
+
+ return VINF_SUCCESS;
+}
+
+void Guest::i_updateStats(uint64_t iTick)
+{
+ RT_NOREF(iTick);
+
+ uint64_t cbFreeTotal = 0;
+ uint64_t cbAllocTotal = 0;
+ uint64_t cbBalloonedTotal = 0;
+ uint64_t cbSharedTotal = 0;
+ uint64_t cbSharedMem = 0;
+ ULONG uNetStatRx = 0;
+ ULONG uNetStatTx = 0;
+ ULONG aGuestStats[GUESTSTATTYPE_MAX];
+ RT_ZERO(aGuestStats);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ULONG validStats = mVmValidStats;
+ /* Check if we have anything to report */
+ if (validStats)
+ {
+ mVmValidStats = pm::VMSTATMASK_NONE;
+ memcpy(aGuestStats, mCurrentGuestStat, sizeof(aGuestStats));
+ }
+ alock.release();
+
+ /*
+ * Calling SessionMachine may take time as the object resides in VBoxSVC
+ * process. This is why we took a snapshot of currently collected stats
+ * and released the lock.
+ */
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ {
+ int vrc;
+
+ /*
+ * There is no point in collecting VM shared memory if other memory
+ * statistics are not available yet. Or is there?
+ */
+ if (validStats)
+ {
+ /* Query the missing per-VM memory statistics. */
+ uint64_t cbTotalMemIgn, cbPrivateMemIgn, cbZeroMemIgn;
+ vrc = ptrVM.vtable()->pfnPGMR3QueryMemoryStats(ptrVM.rawUVM(), &cbTotalMemIgn, &cbPrivateMemIgn,
+ &cbSharedMem, &cbZeroMemIgn);
+ if (vrc == VINF_SUCCESS)
+ validStats |= pm::VMSTATMASK_GUEST_MEMSHARED;
+ }
+
+ if (mCollectVMMStats)
+ {
+ vrc = ptrVM.vtable()->pfnPGMR3QueryGlobalMemoryStats(ptrVM.rawUVM(), &cbAllocTotal, &cbFreeTotal,
+ &cbBalloonedTotal, &cbSharedTotal);
+ AssertRC(vrc);
+ if (vrc == VINF_SUCCESS)
+ validStats |= pm::VMSTATMASK_VMM_ALLOC | pm::VMSTATMASK_VMM_FREE
+ | pm::VMSTATMASK_VMM_BALOON | pm::VMSTATMASK_VMM_SHARED;
+ }
+
+ uint64_t uRxPrev = mNetStatRx;
+ uint64_t uTxPrev = mNetStatTx;
+ mNetStatRx = mNetStatTx = 0;
+ vrc = ptrVM.vtable()->pfnSTAMR3Enum(ptrVM.rawUVM(), "/Public/Net/*/Bytes*", i_staticEnumStatsCallback, this);
+ AssertRC(vrc);
+
+ uint64_t uTsNow = RTTimeNanoTS();
+ uint64_t cNsPassed = uTsNow - mNetStatLastTs;
+ if (cNsPassed >= 1000)
+ {
+ mNetStatLastTs = uTsNow;
+
+ uNetStatRx = (ULONG)((mNetStatRx - uRxPrev) * 1000000 / (cNsPassed / 1000)); /* in bytes per second */
+ uNetStatTx = (ULONG)((mNetStatTx - uTxPrev) * 1000000 / (cNsPassed / 1000)); /* in bytes per second */
+ validStats |= pm::VMSTATMASK_NET_RX | pm::VMSTATMASK_NET_TX;
+ LogFlowThisFunc(("Net Rx=%llu Tx=%llu Ts=%llu Delta=%llu\n", mNetStatRx, mNetStatTx, uTsNow, cNsPassed));
+ }
+ else
+ {
+ /* Can happen on resume or if we're using a non-monotonic clock
+ source for the timer and the time is adjusted. */
+ mNetStatRx = uRxPrev;
+ mNetStatTx = uTxPrev;
+ LogThisFunc(("Net Ts=%llu cNsPassed=%llu - too small interval\n", uTsNow, cNsPassed));
+ }
+ }
+
+ mParent->i_reportVmStatistics(validStats,
+ aGuestStats[GUESTSTATTYPE_CPUUSER],
+ aGuestStats[GUESTSTATTYPE_CPUKERNEL],
+ aGuestStats[GUESTSTATTYPE_CPUIDLE],
+ /* Convert the units for RAM usage stats: page (4K) -> 1KB units */
+ mCurrentGuestStat[GUESTSTATTYPE_MEMTOTAL] * (_4K/_1K),
+ mCurrentGuestStat[GUESTSTATTYPE_MEMFREE] * (_4K/_1K),
+ mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K),
+ (ULONG)(cbSharedMem / _1K), /* bytes -> KB */
+ mCurrentGuestStat[GUESTSTATTYPE_MEMCACHE] * (_4K/_1K),
+ mCurrentGuestStat[GUESTSTATTYPE_PAGETOTAL] * (_4K/_1K),
+ (ULONG)(cbAllocTotal / _1K), /* bytes -> KB */
+ (ULONG)(cbFreeTotal / _1K),
+ (ULONG)(cbBalloonedTotal / _1K),
+ (ULONG)(cbSharedTotal / _1K),
+ uNetStatRx,
+ uNetStatTx);
+}
+
+// IGuest properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT Guest::getOSTypeId(com::Utf8Str &aOSTypeId)
+{
+ HRESULT hrc = S_OK;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (!mData.mInterfaceVersion.isEmpty())
+ aOSTypeId = mData.mOSTypeId;
+ else
+ {
+ /* Redirect the call to IMachine if no additions are installed. */
+ ComPtr<IMachine> ptrMachine(mParent->i_machine());
+ alock.release();
+ Bstr bstr;
+ hrc = ptrMachine->COMGETTER(OSTypeId)(bstr.asOutParam());
+ aOSTypeId = bstr;
+ }
+ return hrc;
+}
+
+HRESULT Guest::getAdditionsRunLevel(AdditionsRunLevelType_T *aAdditionsRunLevel)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAdditionsRunLevel = mData.mAdditionsRunLevel;
+
+ return S_OK;
+}
+
+HRESULT Guest::getAdditionsVersion(com::Utf8Str &aAdditionsVersion)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = S_OK;
+
+ /*
+ * Return the ReportGuestInfo2 version info if available.
+ */
+ if ( !mData.mAdditionsVersionNew.isEmpty()
+ || mData.mAdditionsRunLevel <= AdditionsRunLevelType_None)
+ aAdditionsVersion = mData.mAdditionsVersionNew;
+ else
+ {
+ /*
+ * If we're running older Guest Additions (< 3.2.0) try get it from
+ * the guest properties. Detected switched around Version and
+ * Revision in early 3.1.x releases (see r57115).
+ */
+ ComPtr<IMachine> ptrMachine = mParent->i_machine();
+ alock.release(); /* No need to hold this during the IPC fun. */
+
+ Bstr bstr;
+ hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Version").raw(), bstr.asOutParam());
+ if ( SUCCEEDED(hrc)
+ && !bstr.isEmpty())
+ {
+ Utf8Str str(bstr);
+ if (str.count('.') == 0)
+ hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Revision").raw(), bstr.asOutParam());
+ str = bstr;
+ if (str.count('.') != 2)
+ hrc = E_FAIL;
+ }
+
+ if (SUCCEEDED(hrc))
+ aAdditionsVersion = bstr;
+ else
+ {
+ /* Returning 1.4 is better than nothing. */
+ alock.acquire();
+ aAdditionsVersion = mData.mInterfaceVersion;
+ hrc = S_OK;
+ }
+ }
+ return hrc;
+}
+
+HRESULT Guest::getAdditionsRevision(ULONG *aAdditionsRevision)
+{
+ HRESULT hrc = S_OK;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Return the ReportGuestInfo2 version info if available.
+ */
+ if ( !mData.mAdditionsVersionNew.isEmpty()
+ || mData.mAdditionsRunLevel <= AdditionsRunLevelType_None)
+ *aAdditionsRevision = mData.mAdditionsRevision;
+ else
+ {
+ /*
+ * If we're running older Guest Additions (< 3.2.0) try get it from
+ * the guest properties. Detected switched around Version and
+ * Revision in early 3.1.x releases (see r57115).
+ */
+ ComPtr<IMachine> ptrMachine = mParent->i_machine();
+ alock.release(); /* No need to hold this during the IPC fun. */
+
+ Bstr bstr;
+ hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Revision").raw(), bstr.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Utf8Str str(bstr);
+ uint32_t uRevision;
+ int vrc = RTStrToUInt32Full(str.c_str(), 0, &uRevision);
+ if (vrc != VINF_SUCCESS && str.count('.') == 2)
+ {
+ hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Version").raw(), bstr.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ str = bstr;
+ vrc = RTStrToUInt32Full(str.c_str(), 0, &uRevision);
+ }
+ }
+ if (vrc == VINF_SUCCESS)
+ *aAdditionsRevision = uRevision;
+ else
+ hrc = VBOX_E_IPRT_ERROR;
+ }
+ if (FAILED(hrc))
+ {
+ /* Return 0 if we don't know. */
+ *aAdditionsRevision = 0;
+ hrc = S_OK;
+ }
+ }
+ return hrc;
+}
+
+HRESULT Guest::getDnDSource(ComPtr<IGuestDnDSource> &aDnDSource)
+{
+#ifndef VBOX_WITH_DRAG_AND_DROP
+ RT_NOREF(aDnDSource);
+ ReturnComNotImplemented();
+#else
+ LogFlowThisFuncEnter();
+
+ /* No need to lock - lifetime constant. */
+ HRESULT hr = mDnDSource.queryInterfaceTo(aDnDSource.asOutParam());
+
+ LogFlowFuncLeaveRC(hr);
+ return hr;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT Guest::getDnDTarget(ComPtr<IGuestDnDTarget> &aDnDTarget)
+{
+#ifndef VBOX_WITH_DRAG_AND_DROP
+ RT_NOREF(aDnDTarget);
+ ReturnComNotImplemented();
+#else
+ LogFlowThisFuncEnter();
+
+ /* No need to lock - lifetime constant. */
+ HRESULT hr = mDnDTarget.queryInterfaceTo(aDnDTarget.asOutParam());
+
+ LogFlowFuncLeaveRC(hr);
+ return hr;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT Guest::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ LogFlowThisFuncEnter();
+
+ /* No need to lock - lifetime constant. */
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ LogFlowFuncLeaveRC(S_OK);
+ return S_OK;
+}
+
+HRESULT Guest::getFacilities(std::vector<ComPtr<IAdditionsFacility> > &aFacilities)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aFacilities.resize(mData.mFacilityMap.size());
+ size_t i = 0;
+ for (FacilityMapIter it = mData.mFacilityMap.begin(); it != mData.mFacilityMap.end(); ++it, ++i)
+ it->second.queryInterfaceTo(aFacilities[i].asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Guest::getSessions(std::vector<ComPtr<IGuestSession> > &aSessions)
+{
+#ifdef VBOX_WITH_GUEST_CONTROL
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aSessions.resize(mData.mGuestSessions.size());
+ size_t i = 0;
+ for (GuestSessions::iterator it = mData.mGuestSessions.begin(); it != mData.mGuestSessions.end(); ++it, ++i)
+ it->second.queryInterfaceTo(aSessions[i].asOutParam());
+
+ return S_OK;
+#else
+ ReturnComNotImplemented();
+#endif
+}
+
+BOOL Guest::i_isPageFusionEnabled()
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return mfPageFusionEnabled;
+}
+
+HRESULT Guest::getMemoryBalloonSize(ULONG *aMemoryBalloonSize)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aMemoryBalloonSize = mMemoryBalloonSize;
+
+ return S_OK;
+}
+
+HRESULT Guest::setMemoryBalloonSize(ULONG aMemoryBalloonSize)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* We must be 100% sure that IMachine::COMSETTER(MemoryBalloonSize)
+ * does not call us back in any way! */
+ HRESULT ret = mParent->i_machine()->COMSETTER(MemoryBalloonSize)(aMemoryBalloonSize);
+ if (ret == S_OK)
+ {
+ mMemoryBalloonSize = aMemoryBalloonSize;
+ /* forward the information to the VMM device */
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ /* MUST release all locks before calling VMM device as its critsect
+ * has higher lock order than anything in Main. */
+ alock.release();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ pVMMDevPort->pfnSetMemoryBalloon(pVMMDevPort, aMemoryBalloonSize);
+ }
+ }
+
+ return ret;
+}
+
+HRESULT Guest::getStatisticsUpdateInterval(ULONG *aStatisticsUpdateInterval)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aStatisticsUpdateInterval = mStatUpdateInterval;
+ return S_OK;
+}
+
+HRESULT Guest::setStatisticsUpdateInterval(ULONG aStatisticsUpdateInterval)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Update the timer, creating it the first time we're called with a non-zero value. */
+ int vrc;
+ HRESULT hrc = S_OK;
+ if (aStatisticsUpdateInterval > 0)
+ {
+ if (mStatTimer == NIL_RTTIMERLR)
+ {
+ vrc = RTTimerLRCreate(&mStatTimer, aStatisticsUpdateInterval * RT_MS_1SEC, &Guest::i_staticUpdateStats, this);
+ AssertRCStmt(vrc, hrc = setErrorVrc(vrc, tr("Failed to create guest statistics update timer (%Rrc)"), vrc));
+ }
+ else if (aStatisticsUpdateInterval != mStatUpdateInterval)
+ {
+ vrc = RTTimerLRChangeInterval(mStatTimer, aStatisticsUpdateInterval * RT_NS_1SEC_64);
+ AssertRCStmt(vrc, hrc = setErrorVrc(vrc, tr("Failed to change guest statistics update timer interval from %u to %u failed (%Rrc)"),
+ mStatUpdateInterval, aStatisticsUpdateInterval, vrc));
+ if (mStatUpdateInterval == 0)
+ {
+ vrc = RTTimerLRStart(mStatTimer, 0);
+ AssertRCStmt(vrc, hrc = setErrorVrc(vrc, tr("Failed to start the guest statistics update timer (%Rrc)"), vrc));
+ }
+ }
+ }
+ /* Setting interval to zero - stop the update timer if needed: */
+ else if (mStatUpdateInterval > 0 && mStatTimer != NIL_RTTIMERLR)
+ {
+ vrc = RTTimerLRStop(mStatTimer);
+ AssertRCStmt(vrc, hrc = setErrorVrc(vrc, tr("Failed to stop the guest statistics update timer (%Rrc)"), vrc));
+ }
+
+ /* Update the interval now that the timer is in sync. */
+ mStatUpdateInterval = aStatisticsUpdateInterval;
+
+ /* Forward the information to the VMM device.
+ MUST release all locks before calling VMM device as its critsect
+ has higher lock order than anything in Main. */
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ alock.release();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ pVMMDevPort->pfnSetStatisticsInterval(pVMMDevPort, aStatisticsUpdateInterval);
+ }
+
+ return hrc;
+}
+
+
+HRESULT Guest::internalGetStatistics(ULONG *aCpuUser, ULONG *aCpuKernel, ULONG *aCpuIdle,
+ ULONG *aMemTotal, ULONG *aMemFree, ULONG *aMemBalloon,
+ ULONG *aMemShared, ULONG *aMemCache, ULONG *aPageTotal,
+ ULONG *aMemAllocTotal, ULONG *aMemFreeTotal,
+ ULONG *aMemBalloonTotal, ULONG *aMemSharedTotal)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCpuUser = mCurrentGuestStat[GUESTSTATTYPE_CPUUSER];
+ *aCpuKernel = mCurrentGuestStat[GUESTSTATTYPE_CPUKERNEL];
+ *aCpuIdle = mCurrentGuestStat[GUESTSTATTYPE_CPUIDLE];
+ *aMemTotal = mCurrentGuestStat[GUESTSTATTYPE_MEMTOTAL] * (_4K/_1K); /* page (4K) -> 1KB units */
+ *aMemFree = mCurrentGuestStat[GUESTSTATTYPE_MEMFREE] * (_4K/_1K); /* page (4K) -> 1KB units */
+ *aMemBalloon = mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K); /* page (4K) -> 1KB units */
+ *aMemCache = mCurrentGuestStat[GUESTSTATTYPE_MEMCACHE] * (_4K/_1K); /* page (4K) -> 1KB units */
+ *aPageTotal = mCurrentGuestStat[GUESTSTATTYPE_PAGETOTAL] * (_4K/_1K); /* page (4K) -> 1KB units */
+
+ /* Play safe or smth? */
+ *aMemAllocTotal = 0;
+ *aMemFreeTotal = 0;
+ *aMemBalloonTotal = 0;
+ *aMemSharedTotal = 0;
+ *aMemShared = 0;
+
+ /* MUST release all locks before calling any PGM statistics queries,
+ * as they are executed by EMT and that might deadlock us by VMM device
+ * activity which waits for the Guest object lock. */
+ alock.release();
+ Console::SafeVMPtr ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return E_FAIL;
+
+ uint64_t cbFreeTotal, cbAllocTotal, cbBalloonedTotal, cbSharedTotal;
+ int vrc = ptrVM.vtable()->pfnPGMR3QueryGlobalMemoryStats(ptrVM.rawUVM(), &cbAllocTotal, &cbFreeTotal,
+ &cbBalloonedTotal, &cbSharedTotal);
+ AssertRCReturn(vrc, E_FAIL);
+
+ *aMemAllocTotal = (ULONG)(cbAllocTotal / _1K); /* bytes -> KB */
+ *aMemFreeTotal = (ULONG)(cbFreeTotal / _1K);
+ *aMemBalloonTotal = (ULONG)(cbBalloonedTotal / _1K);
+ *aMemSharedTotal = (ULONG)(cbSharedTotal / _1K);
+
+ /* Query the missing per-VM memory statistics. */
+ uint64_t cbTotalMemIgn, cbPrivateMemIgn, cbSharedMem, cbZeroMemIgn;
+ vrc = ptrVM.vtable()->pfnPGMR3QueryMemoryStats(ptrVM.rawUVM(), &cbTotalMemIgn, &cbPrivateMemIgn, &cbSharedMem, &cbZeroMemIgn);
+ AssertRCReturn(vrc, E_FAIL);
+ *aMemShared = (ULONG)(cbSharedMem / _1K);
+
+ return S_OK;
+}
+
+HRESULT Guest::i_setStatistic(ULONG aCpuId, GUESTSTATTYPE enmType, ULONG aVal)
+{
+ static ULONG indexToPerfMask[] =
+ {
+ pm::VMSTATMASK_GUEST_CPUUSER,
+ pm::VMSTATMASK_GUEST_CPUKERNEL,
+ pm::VMSTATMASK_GUEST_CPUIDLE,
+ pm::VMSTATMASK_GUEST_MEMTOTAL,
+ pm::VMSTATMASK_GUEST_MEMFREE,
+ pm::VMSTATMASK_GUEST_MEMBALLOON,
+ pm::VMSTATMASK_GUEST_MEMCACHE,
+ pm::VMSTATMASK_GUEST_PAGETOTAL,
+ pm::VMSTATMASK_NONE
+ };
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (enmType >= GUESTSTATTYPE_MAX)
+ return E_INVALIDARG;
+
+ if (aCpuId < VMM_MAX_CPU_COUNT)
+ {
+ ULONG *paCpuStats;
+ switch (enmType)
+ {
+ case GUESTSTATTYPE_CPUUSER: paCpuStats = mCurrentGuestCpuUserStat; break;
+ case GUESTSTATTYPE_CPUKERNEL: paCpuStats = mCurrentGuestCpuKernelStat; break;
+ case GUESTSTATTYPE_CPUIDLE: paCpuStats = mCurrentGuestCpuIdleStat; break;
+ default: paCpuStats = NULL; break;
+ }
+ if (paCpuStats)
+ {
+ paCpuStats[aCpuId] = aVal;
+ aVal = 0;
+ for (uint32_t i = 0; i < mCpus && i < VMM_MAX_CPU_COUNT; i++)
+ aVal += paCpuStats[i];
+ aVal /= mCpus;
+ }
+ }
+
+ mCurrentGuestStat[enmType] = aVal;
+ mVmValidStats |= indexToPerfMask[enmType];
+ return S_OK;
+}
+
+/**
+ * Returns the status of a specified Guest Additions facility.
+ *
+ * @return COM status code
+ * @param aFacility Facility to get the status from.
+ * @param aTimestamp Timestamp of last facility status update in ms (optional).
+ * @param aStatus Current status of the specified facility.
+ */
+HRESULT Guest::getFacilityStatus(AdditionsFacilityType_T aFacility, LONG64 *aTimestamp, AdditionsFacilityStatus_T *aStatus)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Not checking for aTimestamp is intentional; it's optional. */
+ FacilityMapIterConst it = mData.mFacilityMap.find(aFacility);
+ if (it != mData.mFacilityMap.end())
+ {
+ AdditionsFacility *pFacility = it->second;
+ ComAssert(pFacility);
+ *aStatus = pFacility->i_getStatus();
+ if (aTimestamp)
+ *aTimestamp = pFacility->i_getLastUpdated();
+ }
+ else
+ {
+ /*
+ * Do not fail here -- could be that the facility never has been brought up (yet) but
+ * the host wants to have its status anyway. So just tell we don't know at this point.
+ */
+ *aStatus = AdditionsFacilityStatus_Unknown;
+ if (aTimestamp)
+ *aTimestamp = RTTimeMilliTS();
+ }
+ return S_OK;
+}
+
+HRESULT Guest::getAdditionsStatus(AdditionsRunLevelType_T aLevel, BOOL *aActive)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+ switch (aLevel)
+ {
+ case AdditionsRunLevelType_System:
+ *aActive = (mData.mAdditionsRunLevel > AdditionsRunLevelType_None);
+ break;
+
+ case AdditionsRunLevelType_Userland:
+ *aActive = (mData.mAdditionsRunLevel >= AdditionsRunLevelType_Userland);
+ break;
+
+ case AdditionsRunLevelType_Desktop:
+ *aActive = (mData.mAdditionsRunLevel >= AdditionsRunLevelType_Desktop);
+ break;
+
+ default:
+ hrc = setError(VBOX_E_NOT_SUPPORTED,
+ tr("Invalid status level defined: %u"), aLevel);
+ break;
+ }
+
+ return hrc;
+}
+
+HRESULT Guest::setCredentials(const com::Utf8Str &aUserName, const com::Utf8Str &aPassword,
+ const com::Utf8Str &aDomain, BOOL aAllowInteractiveLogon)
+{
+ /* Check for magic domain names which are used to pass encryption keys to the disk. */
+ if (Utf8Str(aDomain) == "@@disk")
+ return mParent->i_setDiskEncryptionKeys(aPassword);
+ if (Utf8Str(aDomain) == "@@mem")
+ {
+ /** @todo */
+ return E_NOTIMPL;
+ }
+
+ /* forward the information to the VMM device */
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ {
+ uint32_t u32Flags = VMMDEV_SETCREDENTIALS_GUESTLOGON;
+ if (!aAllowInteractiveLogon)
+ u32Flags = VMMDEV_SETCREDENTIALS_NOLOCALLOGON;
+
+ pVMMDevPort->pfnSetCredentials(pVMMDevPort,
+ aUserName.c_str(),
+ aPassword.c_str(),
+ aDomain.c_str(),
+ u32Flags);
+ return S_OK;
+ }
+ }
+
+ return setError(VBOX_E_VM_ERROR, tr("VMM device is not available (is the VM running?)"));
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Sets the general Guest Additions information like
+ * API (interface) version and OS type. Gets called by
+ * vmmdevUpdateGuestInfo.
+ *
+ * @param aInterfaceVersion
+ * @param aOsType
+ */
+void Guest::i_setAdditionsInfo(const com::Utf8Str &aInterfaceVersion, VBOXOSTYPE aOsType)
+{
+ RTTIMESPEC TimeSpecTS;
+ RTTimeNow(&TimeSpecTS);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Note: The Guest Additions API (interface) version is deprecated
+ * and will not be used anymore! We might need it to at least report
+ * something as version number if *really* ancient Guest Additions are
+ * installed (without the guest version + revision properties having set).
+ */
+ mData.mInterfaceVersion = aInterfaceVersion;
+
+ /*
+ * Older Additions rely on the Additions API version whether they
+ * are assumed to be active or not. Since newer Additions do report
+ * the Additions version *before* calling this function (by calling
+ * VMMDevReportGuestInfo2, VMMDevReportGuestStatus, VMMDevReportGuestInfo,
+ * in that order) we can tell apart old and new Additions here. Old
+ * Additions never would set VMMDevReportGuestInfo2 (which set mData.mAdditionsVersion)
+ * so they just rely on the aInterfaceVersion string (which gets set by
+ * VMMDevReportGuestInfo).
+ *
+ * So only mark the Additions as being active (run level = system) when we
+ * don't have the Additions version set.
+ */
+ if (mData.mAdditionsVersionNew.isEmpty())
+ {
+ if (aInterfaceVersion.isEmpty())
+ mData.mAdditionsRunLevel = AdditionsRunLevelType_None;
+ else
+ {
+ mData.mAdditionsRunLevel = AdditionsRunLevelType_System;
+
+ /*
+ * To keep it compatible with the old Guest Additions behavior we need to set the
+ * "graphics" (feature) facility to active as soon as we got the Guest Additions
+ * interface version.
+ */
+ i_facilityUpdate(VBoxGuestFacilityType_Graphics, VBoxGuestFacilityStatus_Active, 0 /*fFlags*/, &TimeSpecTS);
+ }
+ }
+
+ /*
+ * Older Additions didn't have this finer grained capability bit,
+ * so enable it by default. Newer Additions will not enable this here
+ * and use the setSupportedFeatures function instead.
+ */
+ /** @todo r=bird: I don't get the above comment nor the code below...
+ * One talks about capability bits, the one always does something to a facility.
+ * Then there is the comment below it all, which is placed like it addresses the
+ * mOSTypeId, but talks about something which doesn't remotely like mOSTypeId...
+ *
+ * Andy, could you please try clarify and make the comments shorter and more
+ * coherent! Also, explain why this is important and what depends on it.
+ *
+ * PS. There is the VMMDEV_GUEST_SUPPORTS_GRAPHICS capability* report... It
+ * should come in pretty quickly after this update, normally.
+ */
+ i_facilityUpdate(VBoxGuestFacilityType_Graphics,
+ i_facilityIsActive(VBoxGuestFacilityType_VBoxGuestDriver)
+ ? VBoxGuestFacilityStatus_Active : VBoxGuestFacilityStatus_Inactive,
+ 0 /*fFlags*/, &TimeSpecTS); /** @todo the timestamp isn't gonna be right here on saved state restore. */
+
+ /*
+ * Note! There is a race going on between setting mAdditionsRunLevel and
+ * mSupportsGraphics here and disabling/enabling it later according to
+ * its real status when using new(er) Guest Additions.
+ */
+ mData.mOSType = aOsType;
+ mData.mOSTypeId = Global::OSTypeId(aOsType);
+
+ /*
+ * Always fire an event here.
+ */
+ AdditionsRunLevelType_T const enmRunLevel = mData.mAdditionsRunLevel;
+ alock.release();
+ ::FireGuestAdditionsStatusChangedEvent(mEventSource, AdditionsFacilityType_None, AdditionsFacilityStatus_Active,
+ enmRunLevel, RTTimeSpecGetMilli(&TimeSpecTS));
+}
+
+/**
+ * Sets the Guest Additions version information details.
+ *
+ * Gets called by vmmdevUpdateGuestInfo2 and vmmdevUpdateGuestInfo (to clear the
+ * state).
+ *
+ * @param a_uFullVersion VBoxGuestInfo2::additionsMajor,
+ * VBoxGuestInfo2::additionsMinor and
+ * VBoxGuestInfo2::additionsBuild combined into
+ * one value by VBOX_FULL_VERSION_MAKE.
+ *
+ * When this is 0, it's vmmdevUpdateGuestInfo
+ * calling to reset the state.
+ *
+ * @param a_pszName Build type tag and/or publisher tag, empty
+ * string if neiter of those are present.
+ * @param a_uRevision See VBoxGuestInfo2::additionsRevision.
+ * @param a_fFeatures See VBoxGuestInfo2::additionsFeatures.
+ */
+void Guest::i_setAdditionsInfo2(uint32_t a_uFullVersion, const char *a_pszName, uint32_t a_uRevision, uint32_t a_fFeatures)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (a_uFullVersion)
+ {
+ mData.mAdditionsVersionNew = Utf8StrFmt(*a_pszName ? "%u.%u.%u_%s" : "%u.%u.%u",
+ VBOX_FULL_VERSION_GET_MAJOR(a_uFullVersion),
+ VBOX_FULL_VERSION_GET_MINOR(a_uFullVersion),
+ VBOX_FULL_VERSION_GET_BUILD(a_uFullVersion),
+ a_pszName);
+ mData.mAdditionsVersionFull = a_uFullVersion;
+ mData.mAdditionsRevision = a_uRevision;
+ mData.mAdditionsFeatures = a_fFeatures;
+ }
+ else
+ {
+ Assert(!a_fFeatures && !a_uRevision && !*a_pszName);
+ mData.mAdditionsVersionNew.setNull();
+ mData.mAdditionsVersionFull = 0;
+ mData.mAdditionsRevision = 0;
+ mData.mAdditionsFeatures = 0;
+ }
+}
+
+bool Guest::i_facilityIsActive(VBoxGuestFacilityType enmFacility)
+{
+ Assert(enmFacility < INT32_MAX);
+ FacilityMapIterConst it = mData.mFacilityMap.find((AdditionsFacilityType_T)enmFacility);
+ if (it != mData.mFacilityMap.end())
+ {
+ AdditionsFacility *pFac = it->second;
+ return (pFac->i_getStatus() == AdditionsFacilityStatus_Active);
+ }
+ return false;
+}
+
+bool Guest::i_facilityUpdate(VBoxGuestFacilityType a_enmFacility, VBoxGuestFacilityStatus a_enmStatus,
+ uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS)
+{
+ AssertReturn( a_enmFacility < VBoxGuestFacilityType_All
+ && a_enmFacility > VBoxGuestFacilityType_Unknown, false);
+
+ bool fChanged;
+ FacilityMapIter it = mData.mFacilityMap.find((AdditionsFacilityType_T)a_enmFacility);
+ if (it != mData.mFacilityMap.end())
+ {
+ AdditionsFacility *pFac = it->second;
+ fChanged = pFac->i_update((AdditionsFacilityStatus_T)a_enmStatus, a_fFlags, a_pTimeSpecTS);
+ }
+ else
+ {
+ if (mData.mFacilityMap.size() > 64)
+ {
+ /* The easy way out for now. We could automatically destroy
+ inactive facilities like VMMDev does if we like... */
+ AssertFailedReturn(false);
+ }
+
+ ComObjPtr<AdditionsFacility> ptrFac;
+ HRESULT hrc = ptrFac.createObject();
+ AssertComRCReturn(hrc, false);
+ Assert(ptrFac);
+
+ hrc = ptrFac->init(this, (AdditionsFacilityType_T)a_enmFacility, (AdditionsFacilityStatus_T)a_enmStatus,
+ a_fFlags, a_pTimeSpecTS);
+ AssertComRCReturn(hrc, false);
+ try
+ {
+ mData.mFacilityMap.insert(std::make_pair((AdditionsFacilityType_T)a_enmFacility, ptrFac));
+ fChanged = true;
+ }
+ catch (std::bad_alloc &)
+ {
+ fChanged = false;
+ }
+ }
+ return fChanged;
+}
+
+/**
+ * Issued by the guest when a guest user changed its state.
+ *
+ * @return IPRT status code.
+ * @param aUser Guest user name.
+ * @param aDomain Domain of guest user account. Optional.
+ * @param enmState New state to indicate.
+ * @param pbDetails Pointer to state details. Optional.
+ * @param cbDetails Size (in bytes) of state details. Pass 0 if not used.
+ */
+void Guest::i_onUserStateChanged(const Utf8Str &aUser, const Utf8Str &aDomain, VBoxGuestUserState enmState,
+ const uint8_t *pbDetails, uint32_t cbDetails)
+{
+ RT_NOREF(pbDetails, cbDetails);
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ Utf8Str strDetails; /** @todo Implement state details here. */
+
+ ::FireGuestUserStateChangedEvent(mEventSource, aUser, aDomain, (GuestUserState_T)enmState, strDetails);
+ LogFlowFuncLeave();
+}
+
+/**
+ * Sets the status of a certain Guest Additions facility.
+ *
+ * Gets called by vmmdevUpdateGuestStatus, which just passes the report along.
+ *
+ * @param a_enmFacility The facility.
+ * @param a_enmStatus The status.
+ * @param a_fFlags Flags assoicated with the update. Currently
+ * reserved and should be ignored.
+ * @param a_pTimeSpecTS Pointer to the timestamp of this report.
+ * @sa PDMIVMMDEVCONNECTOR::pfnUpdateGuestStatus, vmmdevUpdateGuestStatus
+ * @thread The emulation thread.
+ */
+void Guest::i_setAdditionsStatus(VBoxGuestFacilityType a_enmFacility, VBoxGuestFacilityStatus a_enmStatus,
+ uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS)
+{
+ Assert( a_enmFacility > VBoxGuestFacilityType_Unknown
+ && a_enmFacility <= VBoxGuestFacilityType_All); /* Paranoia, VMMDev checks for this. */
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Set a specific facility status.
+ */
+ bool fFireEvent = false;
+ if (a_enmFacility == VBoxGuestFacilityType_All)
+ for (FacilityMapIter it = mData.mFacilityMap.begin(); it != mData.mFacilityMap.end(); ++it)
+ fFireEvent |= i_facilityUpdate((VBoxGuestFacilityType)it->first, a_enmStatus, a_fFlags, a_pTimeSpecTS);
+ else /* Update one facility only. */
+ fFireEvent = i_facilityUpdate(a_enmFacility, a_enmStatus, a_fFlags, a_pTimeSpecTS);
+
+ /*
+ * Recalc the runlevel.
+ */
+ AdditionsRunLevelType_T const enmOldRunLevel = mData.mAdditionsRunLevel;
+ if (i_facilityIsActive(VBoxGuestFacilityType_VBoxTrayClient))
+ mData.mAdditionsRunLevel = AdditionsRunLevelType_Desktop;
+ else if (i_facilityIsActive(VBoxGuestFacilityType_VBoxService))
+ mData.mAdditionsRunLevel = AdditionsRunLevelType_Userland;
+ else if (i_facilityIsActive(VBoxGuestFacilityType_VBoxGuestDriver))
+ mData.mAdditionsRunLevel = AdditionsRunLevelType_System;
+ else
+ mData.mAdditionsRunLevel = AdditionsRunLevelType_None;
+
+ /*
+ * Fire event if something actually changed.
+ */
+ AdditionsRunLevelType_T const enmNewRunLevel = mData.mAdditionsRunLevel;
+ if (fFireEvent || enmNewRunLevel != enmOldRunLevel)
+ {
+ alock.release();
+ ::FireGuestAdditionsStatusChangedEvent(mEventSource, (AdditionsFacilityType_T)a_enmFacility,
+ (AdditionsFacilityStatus_T)a_enmStatus, enmNewRunLevel,
+ RTTimeSpecGetMilli(a_pTimeSpecTS));
+ }
+}
+
+/**
+ * Sets the supported features (and whether they are active or not).
+ *
+ * @param aCaps Guest capability bit mask (VMMDEV_GUEST_SUPPORTS_XXX).
+ */
+void Guest::i_setSupportedFeatures(uint32_t aCaps)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo A nit: The timestamp is wrong on saved state restore. Would be better
+ * to move the graphics and seamless capability -> facility translation to
+ * VMMDev so this could be saved. */
+ RTTIMESPEC TimeSpecTS;
+ RTTimeNow(&TimeSpecTS);
+
+ bool fFireEvent = i_facilityUpdate(VBoxGuestFacilityType_Seamless,
+ aCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS
+ ? VBoxGuestFacilityStatus_Active : VBoxGuestFacilityStatus_Inactive,
+ 0 /*fFlags*/, &TimeSpecTS);
+ /** @todo Add VMMDEV_GUEST_SUPPORTS_GUEST_HOST_WINDOW_MAPPING */
+
+ /*
+ * Fire event if the state actually changed.
+ */
+ if (fFireEvent)
+ {
+ AdditionsRunLevelType_T const enmRunLevel = mData.mAdditionsRunLevel;
+ alock.release();
+ ::FireGuestAdditionsStatusChangedEvent(mEventSource, AdditionsFacilityType_Seamless,
+ aCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS
+ ? AdditionsFacilityStatus_Active : AdditionsFacilityStatus_Inactive,
+ enmRunLevel, RTTimeSpecGetMilli(&TimeSpecTS));
+ }
+}
diff --git a/src/VBox/Main/src-client/GuestProcessImpl.cpp b/src/VBox/Main/src-client/GuestProcessImpl.cpp
new file mode 100644
index 00000000..e2befb3f
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestProcessImpl.cpp
@@ -0,0 +1,3031 @@
+/* $Id: GuestProcessImpl.cpp $ */
+/** @file
+ * VirtualBox Main - Guest process handling.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**
+ * Locking rules:
+ * - When the main dispatcher (callbackDispatcher) is called it takes the
+ * WriteLock while dispatching to the various on* methods.
+ * - All other outer functions (accessible by Main) must not own a lock
+ * while waiting for a callback or for an event.
+ * - Only keep Read/WriteLocks as short as possible and only when necessary.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_GUESTPROCESS
+#include "LoggingNew.h"
+
+#ifndef VBOX_WITH_GUEST_CONTROL
+# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
+#endif
+#include "GuestImpl.h"
+#include "GuestProcessImpl.h"
+#include "GuestSessionImpl.h"
+#include "GuestCtrlImplPrivate.h"
+#include "ConsoleImpl.h"
+#include "VirtualBoxErrorInfoImpl.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+#include "VBoxEvents.h"
+#include "ThreadTask.h"
+
+#include <memory> /* For auto_ptr. */
+
+#include <iprt/asm.h>
+#include <iprt/cpp/utils.h> /* For unconst(). */
+#include <iprt/getopt.h>
+
+#include <VBox/com/listeners.h>
+
+#include <VBox/com/array.h>
+
+
+/**
+ * Base class for all guest process tasks.
+ */
+class GuestProcessTask : public ThreadTask
+{
+public:
+
+ GuestProcessTask(GuestProcess *pProcess)
+ : ThreadTask("GenericGuestProcessTask")
+ , mProcess(pProcess)
+ , mRC(VINF_SUCCESS) { }
+
+ virtual ~GuestProcessTask(void) { }
+
+ /** Returns the last set result code. */
+ int i_rc(void) const { return mRC; }
+ /** Returns whether the last set result is okay (successful) or not. */
+ bool i_isOk(void) const { return RT_SUCCESS(mRC); }
+ /** Returns the reference of the belonging progress object. */
+ const ComObjPtr<GuestProcess> &i_process(void) const { return mProcess; }
+
+protected:
+
+ /** Progress object this process belongs to. */
+ const ComObjPtr<GuestProcess> mProcess;
+ /** Last set result code. */
+ int mRC;
+};
+
+/**
+ * Task to start a process on the guest.
+ */
+class GuestProcessStartTask : public GuestProcessTask
+{
+public:
+
+ GuestProcessStartTask(GuestProcess *pProcess)
+ : GuestProcessTask(pProcess)
+ {
+ m_strTaskName = "gctlPrcStart";
+ }
+
+ void handler()
+ {
+ GuestProcess::i_startProcessThreadTask(this);
+ }
+};
+
+/**
+ * Internal listener class to serve events in an
+ * active manner, e.g. without polling delays.
+ */
+class GuestProcessListener
+{
+public:
+
+ GuestProcessListener(void)
+ {
+ }
+
+ virtual ~GuestProcessListener(void)
+ {
+ }
+
+ HRESULT init(GuestProcess *pProcess)
+ {
+ AssertPtrReturn(pProcess, E_POINTER);
+ mProcess = pProcess;
+ return S_OK;
+ }
+
+ void uninit(void)
+ {
+ mProcess = NULL;
+ }
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
+ {
+ switch (aType)
+ {
+ case VBoxEventType_OnGuestProcessStateChanged:
+ case VBoxEventType_OnGuestProcessInputNotify:
+ case VBoxEventType_OnGuestProcessOutput:
+ {
+ AssertPtrReturn(mProcess, E_POINTER);
+ int vrc2 = mProcess->signalWaitEvent(aType, aEvent);
+ RT_NOREF(vrc2);
+#ifdef LOG_ENABLED
+ LogFlowThisFunc(("Signalling events of type=%RU32, pProcess=%p resulted in vrc=%Rrc\n",
+ aType, &mProcess, vrc2));
+#endif
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Unhandled event %RU32\n", aType));
+ break;
+ }
+
+ return S_OK;
+ }
+
+private:
+
+ GuestProcess *mProcess;
+};
+typedef ListenerImpl<GuestProcessListener, GuestProcess*> GuestProcessListenerImpl;
+
+VBOX_LISTENER_DECLARE(GuestProcessListenerImpl)
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(GuestProcess)
+
+HRESULT GuestProcess::FinalConstruct(void)
+{
+ LogFlowThisFuncEnter();
+ return BaseFinalConstruct();
+}
+
+void GuestProcess::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initialies a guest process object.
+ *
+ * @returns VBox status code.
+ * @param aConsole Console this process is bound to.
+ * @param aSession Guest session this process is bound to.
+ * @param aObjectID Object ID to use for this process object.
+ * @param aProcInfo Process startup information to use.
+ * @param pBaseEnv Guest environment to apply when starting the process on the guest.
+ */
+int GuestProcess::init(Console *aConsole, GuestSession *aSession, ULONG aObjectID,
+ const GuestProcessStartupInfo &aProcInfo, const GuestEnvironment *pBaseEnv)
+{
+ LogFlowThisFunc(("aConsole=%p, aSession=%p, aObjectID=%RU32, pBaseEnv=%p\n",
+ aConsole, aSession, aObjectID, pBaseEnv));
+
+ AssertPtrReturn(aConsole, VERR_INVALID_POINTER);
+ AssertPtrReturn(aSession, VERR_INVALID_POINTER);
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED);
+
+ HRESULT hr;
+
+ int vrc = bindToSession(aConsole, aSession, aObjectID);
+ if (RT_SUCCESS(vrc))
+ {
+ hr = unconst(mEventSource).createObject();
+ if (FAILED(hr))
+ vrc = VERR_NO_MEMORY;
+ else
+ {
+ hr = mEventSource->init();
+ if (FAILED(hr))
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ try
+ {
+ GuestProcessListener *pListener = new GuestProcessListener();
+ ComObjPtr<GuestProcessListenerImpl> thisListener;
+ hr = thisListener.createObject();
+ if (SUCCEEDED(hr))
+ hr = thisListener->init(pListener, this);
+
+ if (SUCCEEDED(hr))
+ {
+ com::SafeArray <VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessInputNotify);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessOutput);
+ hr = mEventSource->RegisterListener(thisListener,
+ ComSafeArrayAsInParam(eventTypes),
+ TRUE /* Active listener */);
+ if (SUCCEEDED(hr))
+ {
+ vrc = baseInit();
+ if (RT_SUCCESS(vrc))
+ {
+ mLocalListener = thisListener;
+ }
+ }
+ else
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ else
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ catch(std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ mData.mProcess = aProcInfo;
+ mData.mpSessionBaseEnv = pBaseEnv;
+ if (pBaseEnv)
+ pBaseEnv->retainConst();
+ mData.mExitCode = 0;
+ mData.mPID = 0;
+ mData.mLastError = VINF_SUCCESS;
+ mData.mStatus = ProcessStatus_Undefined;
+ /* Everything else will be set by the actual starting routine. */
+
+ /* Confirm a successful initialization when it's the case. */
+ autoInitSpan.setSucceeded();
+
+ return vrc;
+ }
+
+ autoInitSpan.setFailed();
+ return vrc;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease() or IGuestSession::uninit().
+ */
+void GuestProcess::uninit(void)
+{
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFunc(("mExe=%s, PID=%RU32\n", mData.mProcess.mExecutable.c_str(), mData.mPID));
+
+ if (mData.mpSessionBaseEnv)
+ {
+ mData.mpSessionBaseEnv->releaseConst();
+ mData.mpSessionBaseEnv = NULL;
+ }
+
+ baseUninit();
+
+ LogFlowFuncLeave();
+}
+
+// implementation of public getters/setters for attributes
+/////////////////////////////////////////////////////////////////////////////
+HRESULT GuestProcess::getArguments(std::vector<com::Utf8Str> &aArguments)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aArguments = mData.mProcess.mArguments;
+ return S_OK;
+}
+
+HRESULT GuestProcess::getEnvironment(std::vector<com::Utf8Str> &aEnvironment)
+{
+#ifndef VBOX_WITH_GUEST_CONTROL
+ ReturnComNotImplemented();
+#else
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); /* (Paranoia since both environment objects are immutable.) */
+ HRESULT hrc;
+ if (mData.mpSessionBaseEnv)
+ {
+ int vrc;
+ if (mData.mProcess.mEnvironmentChanges.count() == 0)
+ vrc = mData.mpSessionBaseEnv->queryPutEnvArray(&aEnvironment);
+ else
+ {
+ GuestEnvironment TmpEnv;
+ vrc = TmpEnv.copy(*mData.mpSessionBaseEnv);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = TmpEnv.applyChanges(mData.mProcess.mEnvironmentChanges);
+ if (RT_SUCCESS(vrc))
+ vrc = TmpEnv.queryPutEnvArray(&aEnvironment);
+ }
+ }
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+ }
+ else
+ hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by installed Guest Additions"));
+ LogFlowThisFuncLeave();
+ return hrc;
+#endif
+}
+
+HRESULT GuestProcess::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ LogFlowThisFuncEnter();
+
+ // no need to lock - lifetime constant
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestProcess::getExecutablePath(com::Utf8Str &aExecutablePath)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aExecutablePath = mData.mProcess.mExecutable;
+
+ return S_OK;
+}
+
+HRESULT GuestProcess::getExitCode(LONG *aExitCode)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aExitCode = mData.mExitCode;
+
+ return S_OK;
+}
+
+HRESULT GuestProcess::getName(com::Utf8Str &aName)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aName = mData.mProcess.mName;
+
+ return S_OK;
+}
+
+HRESULT GuestProcess::getPID(ULONG *aPID)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aPID = mData.mPID;
+
+ return S_OK;
+}
+
+HRESULT GuestProcess::getStatus(ProcessStatus_T *aStatus)
+{
+ LogFlowThisFuncEnter();
+
+ *aStatus = i_getStatus();
+
+ return S_OK;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Entry point for guest side process callbacks.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCb Host callback data.
+ */
+int GuestProcess::i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+#ifdef DEBUG
+ LogFlowThisFunc(("uPID=%RU32, uContextID=%RU32, uMessage=%RU32, pSvcCb=%p\n",
+ mData.mPID, pCbCtx->uContextID, pCbCtx->uMessage, pSvcCb));
+#endif
+
+ int vrc;
+ switch (pCbCtx->uMessage)
+ {
+ case GUEST_MSG_DISCONNECTED:
+ {
+ vrc = i_onGuestDisconnected(pCbCtx, pSvcCb);
+ break;
+ }
+
+ case GUEST_MSG_EXEC_STATUS:
+ {
+ vrc = i_onProcessStatusChange(pCbCtx, pSvcCb);
+ break;
+ }
+
+ case GUEST_MSG_EXEC_OUTPUT:
+ {
+ vrc = i_onProcessOutput(pCbCtx, pSvcCb);
+ break;
+ }
+
+ case GUEST_MSG_EXEC_INPUT_STATUS:
+ {
+ vrc = i_onProcessInputStatus(pCbCtx, pSvcCb);
+ break;
+ }
+
+ default:
+ /* Silently ignore not implemented functions. */
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+#ifdef DEBUG
+ LogFlowFuncLeaveRC(vrc);
+#endif
+ return vrc;
+}
+
+/**
+ * Checks if the current assigned PID matches another PID (from a callback).
+ *
+ * In protocol v1 we don't have the possibility to terminate/kill
+ * processes so it can happen that a formerly started process A
+ * (which has the context ID 0 (session=0, process=0, count=0) will
+ * send a delayed message to the host if this process has already
+ * been discarded there and the same context ID was reused by
+ * a process B. Process B in turn then has a different guest PID.
+ *
+ * Note: This also can happen when restoring from a saved state which
+ * had a guest process running.
+ *
+ * @return IPRT status code.
+ * @param uPID PID to check.
+ */
+inline int GuestProcess::i_checkPID(uint32_t uPID)
+{
+ int vrc = VINF_SUCCESS;
+
+ /* Was there a PID assigned yet? */
+ if (mData.mPID)
+ {
+ if (RT_UNLIKELY(mData.mPID != uPID))
+ {
+ LogFlowFunc(("Stale guest process (PID=%RU32) sent data to a newly started process (pProcesS=%p, PID=%RU32, status=%RU32)\n",
+ uPID, this, mData.mPID, mData.mStatus));
+ vrc = VERR_NOT_FOUND;
+ }
+ }
+
+ return vrc;
+}
+
+/**
+ * Returns the current process status.
+ *
+ * @returns Current process status.
+ *
+ * @note Takes the read lock.
+ */
+ProcessStatus_T GuestProcess::i_getStatus(void)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return mData.mStatus;
+}
+
+/**
+ * Converts a given guest process error to a string.
+ *
+ * @returns Error as a string.
+ * @param rcGuest Guest process error to return string for.
+ * @param pcszWhat Hint of what was involved when the error occurred.
+ */
+/* static */
+Utf8Str GuestProcess::i_guestErrorToString(int rcGuest, const char *pcszWhat)
+{
+ AssertPtrReturn(pcszWhat, "");
+
+ Utf8Str strErr;
+ switch (rcGuest)
+ {
+#define CASE_MSG(a_iRc, ...) \
+ case a_iRc: strErr.printf(__VA_ARGS__); break;
+
+ CASE_MSG(VERR_FILE_NOT_FOUND, tr("No such file or directory \"%s\" on guest"), pcszWhat); /* This is the most likely error. */
+ CASE_MSG(VERR_PATH_NOT_FOUND, tr("No such file or directory \"%s\" on guest"), pcszWhat);
+ CASE_MSG(VERR_INVALID_VM_HANDLE, tr("VMM device is not available (is the VM running?)"));
+ CASE_MSG(VERR_HGCM_SERVICE_NOT_FOUND, tr("The guest execution service is not available"));
+ CASE_MSG(VERR_BAD_EXE_FORMAT, tr("The file \"%s\" is not an executable format on guest"), pcszWhat);
+ CASE_MSG(VERR_AUTHENTICATION_FAILURE, tr("The user \"%s\" was not able to logon on guest"), pcszWhat);
+ CASE_MSG(VERR_INVALID_NAME, tr("The file \"%s\" is an invalid name"), pcszWhat);
+ CASE_MSG(VERR_TIMEOUT, tr("The guest did not respond within time"));
+ CASE_MSG(VERR_CANCELLED, tr("The execution operation for \"%s\" was canceled"), pcszWhat);
+ CASE_MSG(VERR_GSTCTL_MAX_CID_OBJECTS_REACHED, tr("Maximum number of concurrent guest processes has been reached"));
+ CASE_MSG(VERR_NOT_FOUND, tr("The guest execution service is not ready (yet)"));
+ default:
+ strErr.printf(tr("Error %Rrc for guest process \"%s\" occurred\n"), rcGuest, pcszWhat);
+ break;
+#undef CASE_MSG
+ }
+
+ return strErr;
+}
+
+/**
+ * Translates a process status to a human readable string.
+ *
+ * @returns Process status as a string.
+ * @param enmStatus Guest process status to return string for.
+ */
+/* static */
+Utf8Str GuestProcess::i_statusToString(ProcessStatus_T enmStatus)
+{
+ switch (enmStatus)
+ {
+ case ProcessStatus_Starting:
+ return "starting";
+ case ProcessStatus_Started:
+ return "started";
+ case ProcessStatus_Paused:
+ return "paused";
+ case ProcessStatus_Terminating:
+ return "terminating";
+ case ProcessStatus_TerminatedNormally:
+ return "successfully terminated";
+ case ProcessStatus_TerminatedSignal:
+ return "terminated by signal";
+ case ProcessStatus_TerminatedAbnormally:
+ return "abnormally aborted";
+ case ProcessStatus_TimedOutKilled:
+ return "timed out";
+ case ProcessStatus_TimedOutAbnormally:
+ return "timed out, hanging";
+ case ProcessStatus_Down:
+ return "killed";
+ case ProcessStatus_Error:
+ return "error";
+ default:
+ break;
+ }
+
+ AssertFailed(); /* Should never happen! */
+ return "unknown";
+}
+
+/**
+ * Returns @c true if the passed in error code indicates an error which came
+ * from the guest side, or @c false if not.
+ *
+ * @return bool @c true if the passed in error code indicates an error which came
+ * from the guest side, or @c false if not.
+ * @param rc Error code to check.
+ */
+/* static */
+bool GuestProcess::i_isGuestError(int rc)
+{
+ return ( rc == VERR_GSTCTL_GUEST_ERROR
+ || rc == VERR_GSTCTL_PROCESS_EXIT_CODE);
+}
+
+/**
+ * Returns whether the guest process is alive (i.e. running) or not.
+ *
+ * @returns \c true if alive and running, or \c false if not.
+ */
+inline bool GuestProcess::i_isAlive(void)
+{
+ return ( mData.mStatus == ProcessStatus_Started
+ || mData.mStatus == ProcessStatus_Paused
+ || mData.mStatus == ProcessStatus_Terminating);
+}
+
+/**
+ * Returns whether the guest process has ended (i.e. terminated) or not.
+ *
+ * @returns \c true if ended, or \c false if not.
+ */
+inline bool GuestProcess::i_hasEnded(void)
+{
+ return ( mData.mStatus == ProcessStatus_TerminatedNormally
+ || mData.mStatus == ProcessStatus_TerminatedSignal
+ || mData.mStatus == ProcessStatus_TerminatedAbnormally
+ || mData.mStatus == ProcessStatus_TimedOutKilled
+ || mData.mStatus == ProcessStatus_TimedOutAbnormally
+ || mData.mStatus == ProcessStatus_Down
+ || mData.mStatus == ProcessStatus_Error);
+}
+
+/**
+ * Called when the guest side of the process has been disconnected (closed, terminated, +++).
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ */
+int GuestProcess::i_onGuestDisconnected(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ int vrc = i_setProcessStatus(ProcessStatus_Down, VINF_SUCCESS);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sets (reports) the current input status of the guest process.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_onProcessInputStatus(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+ /* pCallback is optional. */
+
+ if (pSvcCbData->mParms < 5)
+ return VERR_INVALID_PARAMETER;
+
+ CALLBACKDATA_PROC_INPUT dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uPID);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uStatus);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[3], &dataCb.uFlags);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[4], &dataCb.uProcessed);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RI32, cbProcessed=%RU32\n",
+ dataCb.uPID, dataCb.uStatus, dataCb.uFlags, dataCb.uProcessed));
+
+ vrc = i_checkPID(dataCb.uPID);
+ if (RT_SUCCESS(vrc))
+ {
+ ProcessInputStatus_T inputStatus = ProcessInputStatus_Undefined;
+ switch (dataCb.uStatus)
+ {
+ case INPUT_STS_WRITTEN:
+ inputStatus = ProcessInputStatus_Written;
+ break;
+ case INPUT_STS_ERROR:
+ inputStatus = ProcessInputStatus_Broken;
+ break;
+ case INPUT_STS_TERMINATED:
+ inputStatus = ProcessInputStatus_Broken;
+ break;
+ case INPUT_STS_OVERFLOW:
+ inputStatus = ProcessInputStatus_Overflow;
+ break;
+ case INPUT_STS_UNDEFINED:
+ /* Fall through is intentional. */
+ default:
+ AssertMsg(!dataCb.uProcessed, ("Processed data is not 0 in undefined input state\n"));
+ break;
+ }
+
+ if (inputStatus != ProcessInputStatus_Undefined)
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Copy over necessary data before releasing lock again. */
+ uint32_t uPID = mData.mPID;
+ /** @todo Also handle mSession? */
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestProcessInputNotifyEvent(mEventSource, mSession, this, uPID, 0 /* StdIn */, dataCb.uProcessed, inputStatus);
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Notifies of an I/O operation of the guest process.
+ *
+ * @returns VERR_NOT_IMPLEMENTED -- not implemented yet.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ */
+int GuestProcess::i_onProcessNotifyIO(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * Sets (reports) the current running status of the guest process.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_onProcessStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ if (pSvcCbData->mParms < 5)
+ return VERR_INVALID_PARAMETER;
+
+ CALLBACKDATA_PROC_STATUS dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uPID);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uStatus);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[3], &dataCb.uFlags);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetPv(&pSvcCbData->mpaParms[4], &dataCb.pvData, &dataCb.cbData);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RU32\n",
+ dataCb.uPID, dataCb.uStatus, dataCb.uFlags));
+
+ vrc = i_checkPID(dataCb.uPID);
+ if (RT_SUCCESS(vrc))
+ {
+ ProcessStatus_T procStatus = ProcessStatus_Undefined;
+ int procRc = VINF_SUCCESS;
+
+ switch (dataCb.uStatus)
+ {
+ case PROC_STS_STARTED:
+ {
+ procStatus = ProcessStatus_Started;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mPID = dataCb.uPID; /* Set the process PID. */
+ break;
+ }
+
+ case PROC_STS_TEN:
+ {
+ procStatus = ProcessStatus_TerminatedNormally;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mExitCode = dataCb.uFlags; /* Contains the exit code. */
+ break;
+ }
+
+ case PROC_STS_TES:
+ {
+ procStatus = ProcessStatus_TerminatedSignal;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mExitCode = dataCb.uFlags; /* Contains the signal. */
+ break;
+ }
+
+ case PROC_STS_TEA:
+ {
+ procStatus = ProcessStatus_TerminatedAbnormally;
+ break;
+ }
+
+ case PROC_STS_TOK:
+ {
+ procStatus = ProcessStatus_TimedOutKilled;
+ break;
+ }
+
+ case PROC_STS_TOA:
+ {
+ procStatus = ProcessStatus_TimedOutAbnormally;
+ break;
+ }
+
+ case PROC_STS_DWN:
+ {
+ procStatus = ProcessStatus_Down;
+ break;
+ }
+
+ case PROC_STS_ERROR:
+ {
+ procRc = dataCb.uFlags; /* mFlags contains the IPRT error sent from the guest. */
+ procStatus = ProcessStatus_Error;
+ break;
+ }
+
+ case PROC_STS_UNDEFINED:
+ default:
+ {
+ /* Silently skip this request. */
+ procStatus = ProcessStatus_Undefined;
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("Got rc=%Rrc, procSts=%RU32, procRc=%Rrc\n",
+ vrc, procStatus, procRc));
+
+ /* Set the process status. */
+ int vrc2 = i_setProcessStatus(procStatus, procRc);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sets (reports) the current output status of the guest process.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ */
+int GuestProcess::i_onProcessOutput(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ RT_NOREF(pCbCtx);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ if (pSvcCbData->mParms < 5)
+ return VERR_INVALID_PARAMETER;
+
+ CALLBACKDATA_PROC_OUTPUT dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uPID);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uHandle);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[3], &dataCb.uFlags);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetPv(&pSvcCbData->mpaParms[4], &dataCb.pvData, &dataCb.cbData);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RI32, pvData=%p, cbData=%RU32\n",
+ dataCb.uPID, dataCb.uHandle, dataCb.uFlags, dataCb.pvData, dataCb.cbData));
+
+ vrc = i_checkPID(dataCb.uPID);
+ if (RT_SUCCESS(vrc))
+ {
+ com::SafeArray<BYTE> data((size_t)dataCb.cbData);
+ if (dataCb.cbData)
+ data.initFrom((BYTE*)dataCb.pvData, dataCb.cbData);
+
+ ::FireGuestProcessOutputEvent(mEventSource, mSession, this,
+ mData.mPID, dataCb.uHandle, dataCb.cbData, ComSafeArrayAsInParam(data));
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * @copydoc GuestObject::i_onUnregister
+ */
+int GuestProcess::i_onUnregister(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+
+ /*
+ * Note: The event source stuff holds references to this object,
+ * so make sure that this is cleaned up *before* calling uninit().
+ */
+ if (!mEventSource.isNull())
+ {
+ mEventSource->UnregisterListener(mLocalListener);
+
+ mLocalListener.setNull();
+ unconst(mEventSource).setNull();
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * @copydoc GuestObject::i_onSessionStatusChange
+ */
+int GuestProcess::i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc = VINF_SUCCESS;
+
+ /* If the session now is in a terminated state, set the process status
+ * to "down", as there is not much else we can do now. */
+ if (GuestSession::i_isTerminated(enmSessionStatus))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ vrc = i_setProcessStatus(ProcessStatus_Down, 0 /* rc, ignored */);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Reads data from a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uHandle Internal file handle to use for reading.
+ * @param uSize Size (in bytes) to read.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Where to store the read data on success.
+ * @param cbData Size (in bytes) of \a pvData on input.
+ * @param pcbRead Where to return to size (in bytes) read on success.
+ * Optional.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_readData(uint32_t uHandle, uint32_t uSize, uint32_t uTimeoutMS,
+ void *pvData, size_t cbData, uint32_t *pcbRead, int *prcGuest)
+{
+ LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uSize=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%RU32, prcGuest=%p\n",
+ mData.mPID, uHandle, uSize, uTimeoutMS, pvData, cbData, prcGuest));
+ AssertReturn(uSize, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData >= uSize, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mData.mStatus != ProcessStatus_Started
+ /* Skip reading if the process wasn't started with the appropriate
+ * flags. */
+ || ( ( uHandle == GUEST_PROC_OUT_H_STDOUT
+ || uHandle == GUEST_PROC_OUT_H_STDOUT_DEPRECATED)
+ && !(mData.mProcess.mFlags & ProcessCreateFlag_WaitForStdOut))
+ || ( uHandle == GUEST_PROC_OUT_H_STDERR
+ && !(mData.mProcess.mFlags & ProcessCreateFlag_WaitForStdErr))
+ )
+ {
+ if (pcbRead)
+ *pcbRead = 0;
+ if (prcGuest)
+ *prcGuest = VINF_SUCCESS;
+ return VINF_SUCCESS; /* Nothing to read anymore. */
+ }
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ /*
+ * On Guest Additions < 4.3 there is no guarantee that the process status
+ * change arrives *after* the output event, e.g. if this was the last output
+ * block being read and the process will report status "terminate".
+ * So just skip checking for process status change and only wait for the
+ * output event.
+ */
+ if (mSession->i_getProtocolVersion() >= 2)
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessOutput);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ if (RT_SUCCESS(vrc))
+ {
+ VBOXHGCMSVCPARM paParms[8];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mData.mPID);
+ HGCMSvcSetU32(&paParms[i++], uHandle);
+ HGCMSvcSetU32(&paParms[i++], 0 /* Flags, none set yet. */);
+
+ alock.release(); /* Drop the write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_EXEC_GET_OUTPUT, i, paParms);
+ }
+
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForOutput(pEvent, uHandle, uTimeoutMS,
+ pvData, cbData, pcbRead);
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sets (reports) the current (overall) status of the guest process.
+ *
+ * @returns VBox status code.
+ * @param procStatus Guest process status to set.
+ * @param procRc Guest process result code to set.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_setProcessStatus(ProcessStatus_T procStatus, int procRc)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, procRc=%Rrc\n",
+ mData.mStatus, procStatus, procRc));
+
+ if (procStatus == ProcessStatus_Error)
+ {
+ AssertMsg(RT_FAILURE(procRc), ("Guest rc must be an error (%Rrc)\n", procRc));
+ /* Do not allow overwriting an already set error. If this happens
+ * this means we forgot some error checking/locking somewhere. */
+ AssertMsg(RT_SUCCESS(mData.mLastError), ("Guest rc already set (to %Rrc)\n", mData.mLastError));
+ }
+ else
+ AssertMsg(RT_SUCCESS(procRc), ("Guest rc must not be an error (%Rrc)\n", procRc));
+
+ int vrc = VINF_SUCCESS;
+
+ if (mData.mStatus != procStatus) /* Was there a process status change? */
+ {
+ mData.mStatus = procStatus;
+ mData.mLastError = procRc;
+
+ ComObjPtr<VirtualBoxErrorInfo> errorInfo;
+ HRESULT hrc = errorInfo.createObject();
+ ComAssertComRC(hrc);
+ if (RT_FAILURE(mData.mLastError))
+ {
+ hrc = errorInfo->initEx(VBOX_E_IPRT_ERROR, mData.mLastError,
+ COM_IIDOF(IGuestProcess), getComponentName(),
+ i_guestErrorToString(mData.mLastError, mData.mProcess.mExecutable.c_str()));
+ ComAssertComRC(hrc);
+ }
+
+ /* Copy over necessary data before releasing lock again. */
+ uint32_t uPID = mData.mPID;
+ /** @todo Also handle mSession? */
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestProcessStateChangedEvent(mEventSource, mSession, this, uPID, procStatus, errorInfo);
+#if 0
+ /*
+ * On Guest Additions < 4.3 there is no guarantee that outstanding
+ * requests will be delivered to the host after the process has ended,
+ * so just cancel all waiting events here to not let clients run
+ * into timeouts.
+ */
+ if ( mSession->getProtocolVersion() < 2
+ && hasEnded())
+ {
+ LogFlowThisFunc(("Process ended, canceling outstanding wait events ...\n"));
+ vrc = cancelWaitEvents();
+ }
+#endif
+ }
+
+ return vrc;
+}
+
+/**
+ * Starts the process on the guest.
+ *
+ * @returns VBox status code.
+ * @param cMsTimeout Timeout (in ms) to wait for starting the process.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_startProcess(uint32_t cMsTimeout, int *prcGuest)
+{
+ LogFlowThisFunc(("cMsTimeout=%RU32, procExe=%s, procTimeoutMS=%RU32, procFlags=%x, sessionID=%RU32\n",
+ cMsTimeout, mData.mProcess.mExecutable.c_str(), mData.mProcess.mTimeoutMS, mData.mProcess.mFlags,
+ mSession->i_getId()));
+
+ /* Wait until the caller function (if kicked off by a thread)
+ * has returned and continue operation. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mData.mStatus = ProcessStatus_Starting;
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ vrc = i_startProcessInner(cMsTimeout, alock, pEvent, prcGuest);
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Helper function to start a process on the guest. Do not call directly!
+ *
+ * @returns VBox status code.
+ * @param cMsTimeout Timeout (in ms) to wait for starting the process.
+ * @param rLock Write lock to use for serialization.
+ * @param pEvent Event to use for notifying waiters.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestProcess::i_startProcessInner(uint32_t cMsTimeout, AutoWriteLock &rLock, GuestWaitEvent *pEvent, int *prcGuest)
+{
+ GuestSession *pSession = mSession;
+ AssertPtr(pSession);
+ uint32_t const uProtocol = pSession->i_getProtocolVersion();
+
+ const GuestCredentials &sessionCreds = pSession->i_getCredentials();
+
+ /* Prepare arguments. */
+ size_t cArgs = mData.mProcess.mArguments.size();
+ if (cArgs >= 128*1024)
+ return VERR_BUFFER_OVERFLOW;
+
+ size_t cbArgs = 0;
+ char *pszArgs = NULL;
+ int vrc = VINF_SUCCESS;
+ if (cArgs)
+ {
+ char const **papszArgv = (char const **)RTMemAlloc((cArgs + 1) * sizeof(papszArgv[0]));
+ AssertReturn(papszArgv, VERR_NO_MEMORY);
+
+ for (size_t i = 0; i < cArgs; i++)
+ {
+ papszArgv[i] = mData.mProcess.mArguments[i].c_str();
+ AssertPtr(papszArgv[i]);
+ }
+ papszArgv[cArgs] = NULL;
+
+ Guest *pGuest = mSession->i_getParent();
+ AssertPtr(pGuest);
+
+ const uint64_t fGuestControlFeatures0 = pGuest->i_getGuestControlFeatures0();
+
+ /* If the Guest Additions don't support using argv[0] correctly (< 6.1.x), don't supply it. */
+ if (!(fGuestControlFeatures0 & VBOX_GUESTCTRL_GF_0_PROCESS_ARGV0))
+ vrc = RTGetOptArgvToString(&pszArgs, papszArgv + 1, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+ else /* ... else send the whole argv, including argv[0]. */
+ vrc = RTGetOptArgvToString(&pszArgs, papszArgv, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+
+ RTMemFree(papszArgv);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Note! No direct returns after this. */
+ }
+
+ /* Calculate arguments size (in bytes). */
+ AssertPtr(pszArgs);
+ cbArgs = strlen(pszArgs) + 1; /* Include terminating zero. */
+
+ /* Prepare environment. The guest service dislikes the empty string at the end, so drop it. */
+ size_t cbEnvBlock = 0; /* Shut up MSVC. */
+ char *pszzEnvBlock = NULL; /* Ditto. */
+ vrc = mData.mProcess.mEnvironmentChanges.queryUtf8Block(&pszzEnvBlock, &cbEnvBlock);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(cbEnvBlock > 0);
+ cbEnvBlock--;
+ AssertPtr(pszzEnvBlock);
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[16];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetRTCStr(&paParms[i++], mData.mProcess.mExecutable);
+ HGCMSvcSetU32(&paParms[i++], mData.mProcess.mFlags);
+ HGCMSvcSetU32(&paParms[i++], (uint32_t)mData.mProcess.mArguments.size());
+ HGCMSvcSetPv(&paParms[i++], pszArgs, (uint32_t)cbArgs);
+ HGCMSvcSetU32(&paParms[i++], mData.mProcess.mEnvironmentChanges.count());
+ HGCMSvcSetU32(&paParms[i++], (uint32_t)cbEnvBlock);
+ HGCMSvcSetPv(&paParms[i++], pszzEnvBlock, (uint32_t)cbEnvBlock);
+ if (uProtocol < 2)
+ {
+ /* In protocol v1 (VBox < 4.3) the credentials were part of the execution
+ * call. In newer protocols these credentials are part of the opened guest
+ * session, so not needed anymore here. */
+ HGCMSvcSetRTCStr(&paParms[i++], sessionCreds.mUser);
+ HGCMSvcSetRTCStr(&paParms[i++], sessionCreds.mPassword);
+ }
+ /*
+ * If the WaitForProcessStartOnly flag is set, we only want to define and wait for a timeout
+ * until the process was started - the process itself then gets an infinite timeout for execution.
+ * This is handy when we want to start a process inside a worker thread within a certain timeout
+ * but let the started process perform lengthly operations then.
+ */
+ if (mData.mProcess.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
+ HGCMSvcSetU32(&paParms[i++], UINT32_MAX /* Infinite timeout */);
+ else
+ HGCMSvcSetU32(&paParms[i++], mData.mProcess.mTimeoutMS);
+ if (uProtocol >= 2)
+ {
+ HGCMSvcSetU32(&paParms[i++], mData.mProcess.mPriority);
+ /* CPU affinity: We only support one CPU affinity block at the moment,
+ * so that makes up to 64 CPUs total. This can be more in the future. */
+ HGCMSvcSetU32(&paParms[i++], 1);
+ /* The actual CPU affinity blocks. */
+ HGCMSvcSetPv(&paParms[i++], (void *)&mData.mProcess.mAffinity, sizeof(mData.mProcess.mAffinity));
+ }
+
+ rLock.release(); /* Drop the write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_EXEC_CMD, i, paParms);
+ if (RT_FAILURE(vrc))
+ {
+ int vrc2 = i_setProcessStatus(ProcessStatus_Error, vrc);
+ AssertRC(vrc2);
+ }
+
+ mData.mProcess.mEnvironmentChanges.freeUtf8Block(pszzEnvBlock);
+ }
+
+ RTStrFree(pszArgs);
+
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForStatusChange(pEvent, cMsTimeout,
+ NULL /* Process status */, prcGuest);
+ return vrc;
+}
+
+/**
+ * Starts the process asynchronously (via worker thread) on the guest.
+ *
+ * @returns VBox status code.
+ */
+int GuestProcess::i_startProcessAsync(void)
+{
+ LogFlowThisFuncEnter();
+
+ /* Create the task: */
+ GuestProcessStartTask *pTask = NULL;
+ try
+ {
+ pTask = new GuestProcessStartTask(this);
+ }
+ catch (std::bad_alloc &)
+ {
+ LogFlowThisFunc(("out of memory\n"));
+ return VERR_NO_MEMORY;
+ }
+ AssertReturnStmt(pTask->i_isOk(), delete pTask, E_FAIL); /* cannot fail for GuestProcessStartTask. */
+ LogFlowThisFunc(("Successfully created GuestProcessStartTask object\n"));
+
+ /* Start the thread (always consumes the task): */
+ HRESULT hrc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(hrc))
+ return VINF_SUCCESS;
+ LogFlowThisFunc(("Failed to create thread for GuestProcessStartTask\n"));
+ return VERR_GENERAL_FAILURE;
+}
+
+/**
+ * Thread task which does the asynchronous starting of a guest process.
+ *
+ * @returns VBox status code.
+ * @param pTask Process start task (context) to process.
+ */
+/* static */
+int GuestProcess::i_startProcessThreadTask(GuestProcessStartTask *pTask)
+{
+ LogFlowFunc(("pTask=%p\n", pTask));
+
+ const ComObjPtr<GuestProcess> pProcess(pTask->i_process());
+ Assert(!pProcess.isNull());
+
+ AutoCaller autoCaller(pProcess);
+ if (FAILED(autoCaller.rc()))
+ return VERR_COM_UNEXPECTED;
+
+ int vrc = pProcess->i_startProcess(30 * 1000 /* 30s timeout */, NULL /* Guest rc, ignored */);
+ /* Nothing to do here anymore. */
+
+ LogFlowFunc(("pProcess=%p, vrc=%Rrc\n", (GuestProcess *)pProcess, vrc));
+ return vrc;
+}
+
+/**
+ * Terminates a guest process.
+ *
+ * @returns VBox status code.
+ * @retval VWRN_INVALID_STATE if process not in running state (anymore).
+ * @retval VERR_NOT_SUPPORTED if process termination is not supported on the guest.
+ * @param uTimeoutMS Timeout (in ms) to wait for process termination.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_terminateProcess(uint32_t uTimeoutMS, int *prcGuest)
+{
+ /* prcGuest is optional. */
+ LogFlowThisFunc(("uTimeoutMS=%RU32\n", uTimeoutMS));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+
+ if (mData.mStatus != ProcessStatus_Started)
+ {
+ LogFlowThisFunc(("Process not in started state (state is %RU32), skipping termination\n",
+ mData.mStatus));
+ vrc = VWRN_INVALID_STATE;
+ }
+ else
+ {
+ AssertPtr(mSession);
+ /* Note: VBox < 4.3 (aka protocol version 1) does not
+ * support this, so just skip. */
+ if (mSession->i_getProtocolVersion() < 2)
+ vrc = VERR_NOT_SUPPORTED;
+
+ if (RT_SUCCESS(vrc))
+ {
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mData.mPID);
+
+ alock.release(); /* Drop the write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_EXEC_TERMINATE, i, paParms);
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForStatusChange(pEvent, uTimeoutMS,
+ NULL /* ProcessStatus */, prcGuest);
+ unregisterWaitEvent(pEvent);
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Converts given process status / flags and wait flag combination
+ * to an overall process wait result.
+ *
+ * @returns Overall process wait result.
+ * @param fWaitFlags Process wait flags to use for conversion.
+ * @param oldStatus Old process status to use for conversion.
+ * @param newStatus New process status to use for conversion.
+ * @param uProcFlags Process flags to use for conversion.
+ * @param uProtocol Guest Control protocol version to use for conversion.
+ */
+/* static */
+ProcessWaitResult_T GuestProcess::i_waitFlagsToResultEx(uint32_t fWaitFlags,
+ ProcessStatus_T oldStatus, ProcessStatus_T newStatus,
+ uint32_t uProcFlags, uint32_t uProtocol)
+{
+ ProcessWaitResult_T waitResult = ProcessWaitResult_None;
+
+ switch (newStatus)
+ {
+ case ProcessStatus_TerminatedNormally:
+ case ProcessStatus_TerminatedSignal:
+ case ProcessStatus_TerminatedAbnormally:
+ case ProcessStatus_Down:
+ /* Nothing to wait for anymore. */
+ waitResult = ProcessWaitResult_Terminate;
+ break;
+
+ case ProcessStatus_TimedOutKilled:
+ case ProcessStatus_TimedOutAbnormally:
+ /* Dito. */
+ waitResult = ProcessWaitResult_Timeout;
+ break;
+
+ case ProcessStatus_Started:
+ switch (oldStatus)
+ {
+ case ProcessStatus_Undefined:
+ case ProcessStatus_Starting:
+ /* Also wait for process start. */
+ if (fWaitFlags & ProcessWaitForFlag_Start)
+ waitResult = ProcessWaitResult_Start;
+ else
+ {
+ /*
+ * If ProcessCreateFlag_WaitForProcessStartOnly was specified on process creation the
+ * caller is not interested in getting further process statuses -- so just don't notify
+ * anything here anymore and return.
+ */
+ if (uProcFlags & ProcessCreateFlag_WaitForProcessStartOnly)
+ waitResult = ProcessWaitResult_Start;
+ }
+ break;
+
+ case ProcessStatus_Started:
+ /* Only wait for process start. */
+ if (fWaitFlags & ProcessWaitForFlag_Start)
+ waitResult = ProcessWaitResult_Start;
+ break;
+
+ default:
+ AssertMsgFailed(("Unhandled old status %RU32 before new status 'started'\n",
+ oldStatus));
+ if (fWaitFlags & ProcessWaitForFlag_Start)
+ waitResult = ProcessWaitResult_Start;
+ break;
+ }
+ break;
+
+ case ProcessStatus_Error:
+ /* Nothing to wait for anymore. */
+ waitResult = ProcessWaitResult_Error;
+ break;
+
+ case ProcessStatus_Undefined:
+ case ProcessStatus_Starting:
+ case ProcessStatus_Terminating:
+ case ProcessStatus_Paused:
+ /* No result available yet, leave wait
+ * flags untouched. */
+ break;
+#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
+ case ProcessStatus_32BitHack: AssertFailedBreak(); /* (compiler warnings) */
+#endif
+ }
+
+ if (newStatus == ProcessStatus_Started)
+ {
+ /*
+ * Filter out waits which are *not* supported using
+ * older guest control Guest Additions.
+ *
+ */
+ /** @todo ProcessWaitForFlag_Std* flags are not implemented yet. */
+ if (uProtocol < 99) /* See @todo above. */
+ {
+ if ( waitResult == ProcessWaitResult_None
+ /* We don't support waiting for stdin, out + err,
+ * just skip waiting then. */
+ && ( (fWaitFlags & ProcessWaitForFlag_StdIn)
+ || (fWaitFlags & ProcessWaitForFlag_StdOut)
+ || (fWaitFlags & ProcessWaitForFlag_StdErr)
+ )
+ )
+ {
+ /* Use _WaitFlagNotSupported because we don't know what to tell the caller. */
+ waitResult = ProcessWaitResult_WaitFlagNotSupported;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ LogFlowFunc(("oldStatus=%RU32, newStatus=%RU32, fWaitFlags=0x%x, waitResult=%RU32\n",
+ oldStatus, newStatus, fWaitFlags, waitResult));
+#endif
+ return waitResult;
+}
+
+/**
+ * Converts given wait flags to an overall process wait result.
+ *
+ * @returns Overall process wait result.
+ * @param fWaitFlags Process wait flags to use for conversion.
+ */
+ProcessWaitResult_T GuestProcess::i_waitFlagsToResult(uint32_t fWaitFlags)
+{
+ AssertPtr(mSession);
+ return GuestProcess::i_waitFlagsToResultEx(fWaitFlags,
+ mData.mStatus /* oldStatus */, mData.mStatus /* newStatus */,
+ mData.mProcess.mFlags, mSession->i_getProtocolVersion());
+}
+
+/**
+ * Waits for certain events of the guest process.
+ *
+ * @returns VBox status code.
+ * @param fWaitFlags Process wait flags to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param waitResult Where to return the process wait result on success.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ * @note Takes the read lock.
+ */
+int GuestProcess::i_waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS,
+ ProcessWaitResult_T &waitResult, int *prcGuest)
+{
+ AssertReturn(fWaitFlags, VERR_INVALID_PARAMETER);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("fWaitFlags=0x%x, uTimeoutMS=%RU32, procStatus=%RU32, procRc=%Rrc, prcGuest=%p\n",
+ fWaitFlags, uTimeoutMS, mData.mStatus, mData.mLastError, prcGuest));
+
+ /* Did some error occur before? Then skip waiting and return. */
+ ProcessStatus_T curStatus = mData.mStatus;
+ if (curStatus == ProcessStatus_Error)
+ {
+ waitResult = ProcessWaitResult_Error;
+ AssertMsg(RT_FAILURE(mData.mLastError),
+ ("No error rc (%Rrc) set when guest process indicated an error\n", mData.mLastError));
+ if (prcGuest)
+ *prcGuest = mData.mLastError; /* Return last set error. */
+ LogFlowThisFunc(("Process is in error state (rcGuest=%Rrc)\n", mData.mLastError));
+ return VERR_GSTCTL_GUEST_ERROR;
+ }
+
+ waitResult = i_waitFlagsToResult(fWaitFlags);
+
+ /* No waiting needed? Return immediately using the last set error. */
+ if (waitResult != ProcessWaitResult_None)
+ {
+ if (prcGuest)
+ *prcGuest = mData.mLastError; /* Return last set error (if any). */
+ LogFlowThisFunc(("Nothing to wait for (rcGuest=%Rrc)\n", mData.mLastError));
+ return RT_SUCCESS(mData.mLastError) ? VINF_SUCCESS : VERR_GSTCTL_GUEST_ERROR;
+ }
+
+ /* Adjust timeout. Passing 0 means RT_INDEFINITE_WAIT. */
+ if (!uTimeoutMS)
+ uTimeoutMS = RT_INDEFINITE_WAIT;
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ alock.release(); /* Release lock before waiting. */
+
+ /*
+ * Do the actual waiting.
+ */
+ ProcessStatus_T newStatus = ProcessStatus_Undefined;
+ uint64_t u64StartMS = RTTimeMilliTS();
+ for (;;)
+ {
+ uint64_t u64ElapsedMS = RTTimeMilliTS() - u64StartMS;
+ if ( uTimeoutMS != RT_INDEFINITE_WAIT
+ && u64ElapsedMS >= uTimeoutMS)
+ {
+ vrc = VERR_TIMEOUT;
+ break;
+ }
+
+ vrc = i_waitForStatusChange(pEvent,
+ uTimeoutMS == RT_INDEFINITE_WAIT
+ ? RT_INDEFINITE_WAIT : uTimeoutMS - (uint32_t)u64ElapsedMS,
+ &newStatus, prcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ alock.acquire();
+
+ waitResult = i_waitFlagsToResultEx(fWaitFlags, curStatus, newStatus,
+ mData.mProcess.mFlags, mSession->i_getProtocolVersion());
+#ifdef DEBUG
+ LogFlowThisFunc(("Got new status change: fWaitFlags=0x%x, newStatus=%RU32, waitResult=%RU32\n",
+ fWaitFlags, newStatus, waitResult));
+#endif
+ if (ProcessWaitResult_None != waitResult) /* We got a waiting result. */
+ break;
+ }
+ else /* Waiting failed, bail out. */
+ break;
+
+ alock.release(); /* Don't hold lock in next waiting round. */
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowThisFunc(("Returned waitResult=%RU32, newStatus=%RU32, rc=%Rrc\n",
+ waitResult, newStatus, vrc));
+ return vrc;
+}
+
+/**
+ * Waits for a guest process input notification.
+ *
+ * @param pEvent Wait event to use for waiting.
+ * @param uHandle Guest process file handle to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pInputStatus Where to return the process input status on success.
+ * @param pcbProcessed Where to return the processed input (in bytes) on success.
+ */
+int GuestProcess::i_waitForInputNotify(GuestWaitEvent *pEvent, uint32_t uHandle, uint32_t uTimeoutMS,
+ ProcessInputStatus_T *pInputStatus, uint32_t *pcbProcessed)
+{
+ RT_NOREF(uHandle);
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestProcessInputNotify)
+ {
+ ComPtr<IGuestProcessInputNotifyEvent> pProcessEvent = pIEvent;
+ Assert(!pProcessEvent.isNull());
+
+ if (pInputStatus)
+ {
+ HRESULT hr2 = pProcessEvent->COMGETTER(Status)(pInputStatus);
+ ComAssertComRC(hr2);
+ }
+ if (pcbProcessed)
+ {
+ HRESULT hr2 = pProcessEvent->COMGETTER(Processed)((ULONG*)pcbProcessed);
+ ComAssertComRC(hr2);
+ }
+ }
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
+ }
+
+ LogFlowThisFunc(("Returning pEvent=%p, uHandle=%RU32, rc=%Rrc\n",
+ pEvent, uHandle, vrc));
+ return vrc;
+}
+
+/**
+ * Waits for a guest process input notification.
+ *
+ * @returns VBox status code.
+ * @param pEvent Wait event to use for waiting.
+ * @param uHandle Guest process file handle to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Where to store the guest process output on success.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param pcbRead Where to return the size (in bytes) read.
+ */
+int GuestProcess::i_waitForOutput(GuestWaitEvent *pEvent, uint32_t uHandle, uint32_t uTimeoutMS,
+ void *pvData, size_t cbData, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+ /* pvData is optional. */
+ /* cbData is optional. */
+ /* pcbRead is optional. */
+
+ LogFlowThisFunc(("cEventTypes=%zu, pEvent=%p, uHandle=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%zu, pcbRead=%p\n",
+ pEvent->TypeCount(), pEvent, uHandle, uTimeoutMS, pvData, cbData, pcbRead));
+
+ int vrc;
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ do
+ {
+ vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestProcessOutput)
+ {
+ ComPtr<IGuestProcessOutputEvent> pProcessEvent = pIEvent;
+ Assert(!pProcessEvent.isNull());
+
+ ULONG uHandleEvent;
+ HRESULT hr = pProcessEvent->COMGETTER(Handle)(&uHandleEvent);
+ if ( SUCCEEDED(hr)
+ && uHandleEvent == uHandle)
+ {
+ if (pvData)
+ {
+ com::SafeArray <BYTE> data;
+ hr = pProcessEvent->COMGETTER(Data)(ComSafeArrayAsOutParam(data));
+ ComAssertComRC(hr);
+ size_t cbRead = data.size();
+ if (cbRead)
+ {
+ if (cbRead <= cbData)
+ {
+ /* Copy data from event into our buffer. */
+ memcpy(pvData, data.raw(), data.size());
+ }
+ else
+ vrc = VERR_BUFFER_OVERFLOW;
+
+ LogFlowThisFunc(("Read %zu bytes (uHandle=%RU32), rc=%Rrc\n",
+ cbRead, uHandleEvent, vrc));
+ }
+ }
+
+ if ( RT_SUCCESS(vrc)
+ && pcbRead)
+ {
+ ULONG cbRead;
+ hr = pProcessEvent->COMGETTER(Processed)(&cbRead);
+ ComAssertComRC(hr);
+ *pcbRead = (uint32_t)cbRead;
+ }
+
+ break;
+ }
+ else if (FAILED(hr))
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
+ }
+
+ } while (vrc == VINF_SUCCESS);
+
+ if ( vrc != VINF_SUCCESS
+ && pcbRead)
+ {
+ *pcbRead = 0;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Waits for a guest process status change.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param pEvent Guest wait event to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pProcessStatus Where to return the process status on success.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned.
+ */
+int GuestProcess::i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t uTimeoutMS,
+ ProcessStatus_T *pProcessStatus, int *prcGuest)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+ /* pProcessStatus is optional. */
+ /* prcGuest is optional. */
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(evtType == VBoxEventType_OnGuestProcessStateChanged);
+ ComPtr<IGuestProcessStateChangedEvent> pProcessEvent = pIEvent;
+ Assert(!pProcessEvent.isNull());
+
+ ProcessStatus_T procStatus;
+ HRESULT hr = pProcessEvent->COMGETTER(Status)(&procStatus);
+ ComAssertComRC(hr);
+ if (pProcessStatus)
+ *pProcessStatus = procStatus;
+
+ ComPtr<IVirtualBoxErrorInfo> errorInfo;
+ hr = pProcessEvent->COMGETTER(Error)(errorInfo.asOutParam());
+ ComAssertComRC(hr);
+
+ LONG lGuestRc;
+ hr = errorInfo->COMGETTER(ResultDetail)(&lGuestRc);
+ ComAssertComRC(hr);
+
+ LogFlowThisFunc(("Got procStatus=%RU32, rcGuest=%RI32 (%Rrc)\n",
+ procStatus, lGuestRc, lGuestRc));
+
+ if (RT_FAILURE((int)lGuestRc))
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+
+ if (prcGuest)
+ *prcGuest = (int)lGuestRc;
+ }
+ /* waitForEvent may also return VERR_GSTCTL_GUEST_ERROR like we do above, so make prcGuest is set. */
+ else if (vrc == VERR_GSTCTL_GUEST_ERROR && prcGuest)
+ *prcGuest = pEvent->GuestResult();
+ Assert(vrc != VERR_GSTCTL_GUEST_ERROR || !prcGuest || *prcGuest != (int)0xcccccccc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+#if 0 /* Unused */
+/* static */
+bool GuestProcess::i_waitResultImpliesEx(ProcessWaitResult_T waitResult, ProcessStatus_T procStatus, uint32_t uProtocol)
+{
+ RT_NOREF(uProtocol);
+
+ bool fImplies;
+
+ switch (waitResult)
+ {
+ case ProcessWaitResult_Start:
+ fImplies = procStatus == ProcessStatus_Started;
+ break;
+
+ case ProcessWaitResult_Terminate:
+ fImplies = ( procStatus == ProcessStatus_TerminatedNormally
+ || procStatus == ProcessStatus_TerminatedSignal
+ || procStatus == ProcessStatus_TerminatedAbnormally
+ || procStatus == ProcessStatus_TimedOutKilled
+ || procStatus == ProcessStatus_TimedOutAbnormally
+ || procStatus == ProcessStatus_Down
+ || procStatus == ProcessStatus_Error);
+ break;
+
+ default:
+ fImplies = false;
+ break;
+ }
+
+ return fImplies;
+}
+#endif /* unused */
+
+/**
+ * Writes input data to a guest process.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uHandle Guest process file handle to write to.
+ * @param uFlags Input flags of type PRocessInputFlag_XXX.
+ * @param pvData Data to write to the guest process.
+ * @param cbData Size (in bytes) of \a pvData to write.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param puWritten Where to return the size (in bytes) written. Optional.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_writeData(uint32_t uHandle, uint32_t uFlags,
+ void *pvData, size_t cbData, uint32_t uTimeoutMS, uint32_t *puWritten, int *prcGuest)
+{
+ LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RU32, pvData=%p, cbData=%RU32, uTimeoutMS=%RU32, puWritten=%p, prcGuest=%p\n",
+ mData.mPID, uHandle, uFlags, pvData, cbData, uTimeoutMS, puWritten, prcGuest));
+ /* All is optional. There can be 0 byte writes. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData.mStatus != ProcessStatus_Started)
+ {
+ if (puWritten)
+ *puWritten = 0;
+ if (prcGuest)
+ *prcGuest = VINF_SUCCESS;
+ return VINF_SUCCESS; /* Not available for writing (anymore). */
+ }
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ /*
+ * On Guest Additions < 4.3 there is no guarantee that the process status
+ * change arrives *after* the input event, e.g. if this was the last input
+ * block being written and the process will report status "terminate".
+ * So just skip checking for process status change and only wait for the
+ * input event.
+ */
+ if (mSession->i_getProtocolVersion() >= 2)
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessInputNotify);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ VBOXHGCMSVCPARM paParms[5];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mData.mPID);
+ HGCMSvcSetU32(&paParms[i++], uFlags);
+ HGCMSvcSetPv(&paParms[i++], pvData, (uint32_t)cbData);
+ HGCMSvcSetU32(&paParms[i++], (uint32_t)cbData);
+
+ alock.release(); /* Drop the write lock before sending. */
+
+ uint32_t cbProcessed = 0;
+ vrc = sendMessage(HOST_MSG_EXEC_SET_INPUT, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ ProcessInputStatus_T inputStatus;
+ vrc = i_waitForInputNotify(pEvent, uHandle, uTimeoutMS,
+ &inputStatus, &cbProcessed);
+ if (RT_SUCCESS(vrc))
+ {
+ /** @todo Set rcGuest. */
+
+ if (puWritten)
+ *puWritten = cbProcessed;
+ }
+ /** @todo Error handling. */
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowThisFunc(("Returning cbProcessed=%RU32, rc=%Rrc\n",
+ cbProcessed, vrc));
+ return vrc;
+}
+
+// implementation of public methods
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestProcess::read(ULONG aHandle, ULONG aToRead, ULONG aTimeoutMS, std::vector<BYTE> &aData)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (aToRead == 0)
+ return setError(E_INVALIDARG, tr("The size to read is zero"));
+
+ LogFlowThisFuncEnter();
+
+ aData.resize(aToRead);
+
+ HRESULT hrc = S_OK;
+
+ uint32_t cbRead;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_readData(aHandle, aToRead, aTimeoutMS, &aData.front(), aToRead, &cbRead, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ if (aData.size() != cbRead)
+ aData.resize(cbRead);
+ }
+ else
+ {
+ aData.resize(0);
+
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest,
+ tr("Reading %RU32 bytes from guest process handle %RU32 failed: %s", "", aToRead),
+ aToRead, aHandle, GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading from guest process \"%s\" (PID %RU32) failed: %Rrc"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("rc=%Rrc, cbRead=%RU32\n", vrc, cbRead));
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestProcess::terminate()
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_terminateProcess(30 * 1000 /* Timeout in ms */, &vrcGuest);
+
+ switch (vrc)
+ {
+ case VINF_SUCCESS:
+ /* Nothing to do here, all good. */
+ break;
+
+ case VWRN_INVALID_STATE:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, VWRN_INVALID_STATE,
+ tr("Guest process is not in '%s' state anymore (current is in '%s')"),
+ GuestProcess::i_statusToString(ProcessStatus_Started).c_str(),
+ GuestProcess::i_statusToString(i_getStatus()).c_str());
+ break;
+ }
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Terminating guest process failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+
+ case VERR_NOT_SUPPORTED:
+ {
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Terminating guest process \"%s\" (PID %RU32) not supported by installed Guest Additions"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID);
+ break;
+ }
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Terminating guest process \"%s\" (PID %RU32) failed: %Rrc"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
+ break;
+ }
+
+ /* Note: Also could be VWRN_INVALID_STATE from i_terminateProcess().
+ * In such a case we have to keep the process in our list in order to fullfill any upcoming responses / requests. */
+ if (vrc == VINF_SUCCESS)
+ {
+ /* Remove process from guest session list. Now only API clients
+ * still can hold references to it. */
+ AssertPtr(mSession);
+ int vrc2 = mSession->i_processUnregister(this);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestProcess::waitFor(ULONG aWaitFor, ULONG aTimeoutMS, ProcessWaitResult_T *aReason)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFlowThisFuncEnter();
+
+ /* Validate flags: */
+ static ULONG const s_fValidFlags = ProcessWaitForFlag_None | ProcessWaitForFlag_Start | ProcessWaitForFlag_Terminate
+ | ProcessWaitForFlag_StdIn | ProcessWaitForFlag_StdOut | ProcessWaitForFlag_StdErr;
+ if (aWaitFor & ~s_fValidFlags)
+ return setErrorBoth(E_INVALIDARG, VERR_INVALID_FLAGS, tr("Flags value %#x, invalid: %#x"),
+ aWaitFor, aWaitFor & ~s_fValidFlags);
+
+ /*
+ * Note: Do not hold any locks here while waiting!
+ */
+ HRESULT hrc = S_OK;
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ ProcessWaitResult_T waitResult;
+ int vrc = i_waitFor(aWaitFor, aTimeoutMS, waitResult, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ *aReason = waitResult;
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Waiting for guest process (flags %#x) failed: %s"),
+ aWaitFor, GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ case VERR_TIMEOUT:
+ *aReason = ProcessWaitResult_Timeout;
+ break;
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Waiting for guest process \"%s\" (PID %RU32) failed: %Rrc"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
+ break;
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestProcess::waitForArray(const std::vector<ProcessWaitForFlag_T> &aWaitFor,
+ ULONG aTimeoutMS, ProcessWaitResult_T *aReason)
+{
+ uint32_t fWaitFor = ProcessWaitForFlag_None;
+ for (size_t i = 0; i < aWaitFor.size(); i++)
+ fWaitFor |= aWaitFor[i];
+
+ return WaitFor(fWaitFor, aTimeoutMS, aReason);
+}
+
+HRESULT GuestProcess::write(ULONG aHandle, ULONG aFlags, const std::vector<BYTE> &aData,
+ ULONG aTimeoutMS, ULONG *aWritten)
+{
+ static ULONG const s_fValidFlags = ProcessInputFlag_None | ProcessInputFlag_EndOfFile;
+ if (aFlags & ~s_fValidFlags)
+ return setErrorBoth(E_INVALIDARG, VERR_INVALID_FLAGS, tr("Flags value %#x, invalid: %#x"),
+ aFlags, aFlags & ~s_fValidFlags);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ uint32_t cbWritten;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ uint32_t cbData = (uint32_t)aData.size();
+ void *pvData = cbData > 0 ? (void *)&aData.front() : NULL;
+ int vrc = i_writeData(aHandle, aFlags, pvData, cbData, aTimeoutMS, &cbWritten, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest,
+ tr("Writing %RU32 bytes (flags %#x) to guest process failed: %s", "", cbData),
+ cbData, aFlags, GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Writing to guest process \"%s\" (PID %RU32) failed: %Rrc"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("rc=%Rrc, aWritten=%RU32\n", vrc, cbWritten));
+
+ *aWritten = (ULONG)cbWritten;
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestProcess::writeArray(ULONG aHandle, const std::vector<ProcessInputFlag_T> &aFlags,
+ const std::vector<BYTE> &aData, ULONG aTimeoutMS, ULONG *aWritten)
+{
+ LogFlowThisFuncEnter();
+
+ ULONG fWrite = ProcessInputFlag_None;
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fWrite |= aFlags[i];
+
+ return write(aHandle, fWrite, aData, aTimeoutMS, aWritten);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GuestProcessTool::GuestProcessTool(void)
+ : pSession(NULL),
+ pProcess(NULL)
+{
+}
+
+GuestProcessTool::~GuestProcessTool(void)
+{
+ uninit();
+}
+
+/**
+ * Initializes and starts a process tool on the guest.
+ *
+ * @returns VBox status code.
+ * @param pGuestSession Guest session the process tools should be started in.
+ * @param startupInfo Guest process startup info to use for starting.
+ * @param fAsync Whether to start asynchronously or not.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestProcessTool::init(GuestSession *pGuestSession, const GuestProcessStartupInfo &startupInfo,
+ bool fAsync, int *prcGuest)
+{
+ LogFlowThisFunc(("pGuestSession=%p, exe=%s, fAsync=%RTbool\n",
+ pGuestSession, startupInfo.mExecutable.c_str(), fAsync));
+
+ AssertPtrReturn(pGuestSession, VERR_INVALID_POINTER);
+ Assert(startupInfo.mArguments[0] == startupInfo.mExecutable);
+
+ pSession = pGuestSession;
+ mStartupInfo = startupInfo;
+
+ /* Make sure the process is hidden. */
+ mStartupInfo.mFlags |= ProcessCreateFlag_Hidden;
+
+ int vrc = pSession->i_processCreateEx(mStartupInfo, pProcess);
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest = VINF_SUCCESS;
+ vrc = fAsync
+ ? pProcess->i_startProcessAsync()
+ : pProcess->i_startProcess(30 * 1000 /* 30s timeout */, &vrcGuest);
+
+ if ( RT_SUCCESS(vrc)
+ && !fAsync
+ && RT_FAILURE(vrcGuest)
+ )
+ {
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+
+ if (prcGuest)
+ *prcGuest = vrcGuest;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Unitializes a guest process tool by terminating it on the guest.
+ */
+void GuestProcessTool::uninit(void)
+{
+ /* Make sure the process is terminated and unregistered from the guest session. */
+ int vrcGuestIgnored;
+ terminate(30 * 1000 /* 30s timeout */, &vrcGuestIgnored);
+
+ /* Unregister the process from the process (and the session's object) list. */
+ if ( pSession
+ && pProcess)
+ pSession->i_processUnregister(pProcess);
+
+ /* Release references. */
+ pProcess.setNull();
+ pSession.setNull();
+}
+
+/**
+ * Gets the current guest process stream block.
+ *
+ * @returns VBox status code.
+ * @param uHandle Guest process file handle to get current block for.
+ * @param strmBlock Where to return the stream block on success.
+ */
+int GuestProcessTool::getCurrentBlock(uint32_t uHandle, GuestProcessStreamBlock &strmBlock)
+{
+ const GuestProcessStream *pStream = NULL;
+ if (uHandle == GUEST_PROC_OUT_H_STDOUT)
+ pStream = &mStdOut;
+ else if (uHandle == GUEST_PROC_OUT_H_STDERR)
+ pStream = &mStdErr;
+
+ if (!pStream)
+ return VERR_INVALID_PARAMETER;
+
+ /** @todo Why not using pStream down below and hardcode to mStdOut? */
+
+ int vrc;
+ do
+ {
+ /* Try parsing the data to see if the current block is complete. */
+ vrc = mStdOut.ParseBlock(strmBlock);
+ if (strmBlock.GetCount())
+ break;
+ } while (RT_SUCCESS(vrc));
+
+ LogFlowThisFunc(("rc=%Rrc, %RU64 pairs\n",
+ vrc, strmBlock.GetCount()));
+ return vrc;
+}
+
+/**
+ * Returns the result code from an ended guest process tool.
+ *
+ * @returns Result code from guest process tool.
+ */
+int GuestProcessTool::getRc(void) const
+{
+ LONG exitCode = -1;
+ HRESULT hr = pProcess->COMGETTER(ExitCode(&exitCode));
+ AssertComRC(hr);
+
+ return GuestProcessTool::exitCodeToRc(mStartupInfo, exitCode);
+}
+
+/**
+ * Returns whether a guest process tool is still running or not.
+ *
+ * @returns \c true if running, or \c false if not.
+ */
+bool GuestProcessTool::isRunning(void)
+{
+ AssertReturn(!pProcess.isNull(), false);
+
+ ProcessStatus_T procStatus = ProcessStatus_Undefined;
+ HRESULT hr = pProcess->COMGETTER(Status(&procStatus));
+ AssertComRC(hr);
+
+ if ( procStatus == ProcessStatus_Started
+ || procStatus == ProcessStatus_Paused
+ || procStatus == ProcessStatus_Terminating)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Returns whether the tool has been run correctly or not, based on it's internal process
+ * status and reported exit status.
+ *
+ * @return @c true if the tool has been run correctly (exit status 0), or @c false if some error
+ * occurred (exit status <> 0 or wrong process state).
+ */
+bool GuestProcessTool::isTerminatedOk(void)
+{
+ return getTerminationStatus() == VINF_SUCCESS ? true : false;
+}
+
+/**
+ * Static helper function to start and wait for a certain toolbox tool.
+ *
+ * This function most likely is the one you want to use in the first place if you
+ * want to just use a toolbox tool and wait for its result. See runEx() if you also
+ * needs its output.
+ *
+ * @return VBox status code.
+ * @param pGuestSession Guest control session to use for starting the toolbox tool in.
+ * @param startupInfo Startup information about the toolbox tool.
+ * @param pvrcGuest Where to store the toolbox tool's specific error code in case
+ * VERR_GSTCTL_GUEST_ERROR is returned.
+ */
+/* static */
+int GuestProcessTool::run( GuestSession *pGuestSession,
+ const GuestProcessStartupInfo &startupInfo,
+ int *pvrcGuest /* = NULL */)
+{
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+
+ GuestProcessToolErrorInfo errorInfo = { VERR_IPE_UNINITIALIZED_STATUS, INT32_MAX };
+ int vrc = runErrorInfo(pGuestSession, startupInfo, errorInfo);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure to check the error information we got from the guest tool. */
+ if (GuestProcess::i_isGuestError(errorInfo.rcGuest))
+ {
+ if (errorInfo.rcGuest == VERR_GSTCTL_PROCESS_EXIT_CODE) /* Translate exit code to a meaningful error code. */
+ vrcGuest = GuestProcessTool::exitCodeToRc(startupInfo, errorInfo.iExitCode);
+ else /* At least return something. */
+ vrcGuest = errorInfo.rcGuest;
+
+ if (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+ }
+
+ LogFlowFunc(("Returned vrc=%Rrc, vrcGuest=%Rrc, iExitCode=%d\n", vrc, errorInfo.rcGuest, errorInfo.iExitCode));
+ return vrc;
+}
+
+/**
+ * Static helper function to start and wait for a certain toolbox tool, returning
+ * extended error information from the guest.
+ *
+ * @return VBox status code.
+ * @param pGuestSession Guest control session to use for starting the toolbox tool in.
+ * @param startupInfo Startup information about the toolbox tool.
+ * @param errorInfo Error information returned for error handling.
+ */
+/* static */
+int GuestProcessTool::runErrorInfo( GuestSession *pGuestSession,
+ const GuestProcessStartupInfo &startupInfo,
+ GuestProcessToolErrorInfo &errorInfo)
+{
+ return runExErrorInfo(pGuestSession, startupInfo,
+ NULL /* paStrmOutObjects */, 0 /* cStrmOutObjects */, errorInfo);
+}
+
+/**
+ * Static helper function to start and wait for output of a certain toolbox tool.
+ *
+ * @return IPRT status code.
+ * @param pGuestSession Guest control session to use for starting the toolbox tool in.
+ * @param startupInfo Startup information about the toolbox tool.
+ * @param paStrmOutObjects Pointer to stream objects array to use for retrieving the output of the toolbox tool.
+ * Optional.
+ * @param cStrmOutObjects Number of stream objects passed in. Optional.
+ * @param pvrcGuest Error code returned from the guest side if VERR_GSTCTL_GUEST_ERROR is returned. Optional.
+ */
+/* static */
+int GuestProcessTool::runEx( GuestSession *pGuestSession,
+ const GuestProcessStartupInfo &startupInfo,
+ GuestCtrlStreamObjects *paStrmOutObjects,
+ uint32_t cStrmOutObjects,
+ int *pvrcGuest /* = NULL */)
+{
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+
+ GuestProcessToolErrorInfo errorInfo = { VERR_IPE_UNINITIALIZED_STATUS, INT32_MAX };
+ int vrc = GuestProcessTool::runExErrorInfo(pGuestSession, startupInfo, paStrmOutObjects, cStrmOutObjects, errorInfo);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure to check the error information we got from the guest tool. */
+ if (GuestProcess::i_isGuestError(errorInfo.rcGuest))
+ {
+ if (errorInfo.rcGuest == VERR_GSTCTL_PROCESS_EXIT_CODE) /* Translate exit code to a meaningful error code. */
+ vrcGuest = GuestProcessTool::exitCodeToRc(startupInfo, errorInfo.iExitCode);
+ else /* At least return something. */
+ vrcGuest = errorInfo.rcGuest;
+
+ if (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+ }
+
+ LogFlowFunc(("Returned vrc=%Rrc, vrcGuest=%Rrc, iExitCode=%d\n", vrc, errorInfo.rcGuest, errorInfo.iExitCode));
+ return vrc;
+}
+
+/**
+ * Static helper function to start and wait for output of a certain toolbox tool.
+ *
+ * This is the extended version, which addds the possibility of retrieving parsable so-called guest stream
+ * objects. Those objects are issued on the guest side as part of VBoxService's toolbox tools (think of a BusyBox-like approach)
+ * on stdout and can be used on the host side to retrieve more information about the actual command issued on the guest side.
+ *
+ * @return VBox status code.
+ * @param pGuestSession Guest control session to use for starting the toolbox tool in.
+ * @param startupInfo Startup information about the toolbox tool.
+ * @param paStrmOutObjects Pointer to stream objects array to use for retrieving the output of the toolbox tool.
+ * Optional.
+ * @param cStrmOutObjects Number of stream objects passed in. Optional.
+ * @param errorInfo Error information returned for error handling.
+ */
+/* static */
+int GuestProcessTool::runExErrorInfo( GuestSession *pGuestSession,
+ const GuestProcessStartupInfo &startupInfo,
+ GuestCtrlStreamObjects *paStrmOutObjects,
+ uint32_t cStrmOutObjects,
+ GuestProcessToolErrorInfo &errorInfo)
+{
+ AssertPtrReturn(pGuestSession, VERR_INVALID_POINTER);
+ /* paStrmOutObjects is optional. */
+
+ /** @todo Check if this is a valid toolbox. */
+
+ GuestProcessTool procTool;
+ int vrc = procTool.init(pGuestSession, startupInfo, false /* Async */, &errorInfo.rcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ while (cStrmOutObjects--)
+ {
+ try
+ {
+ GuestProcessStreamBlock strmBlk;
+ vrc = procTool.waitEx( paStrmOutObjects
+ ? GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK
+ : GUESTPROCESSTOOL_WAIT_FLAG_NONE, &strmBlk, &errorInfo.rcGuest);
+ if (paStrmOutObjects)
+ paStrmOutObjects->push_back(strmBlk);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure the process runs until completion. */
+ vrc = procTool.wait(GUESTPROCESSTOOL_WAIT_FLAG_NONE, &errorInfo.rcGuest);
+ if (RT_SUCCESS(vrc))
+ errorInfo.rcGuest = procTool.getTerminationStatus(&errorInfo.iExitCode);
+ }
+
+ LogFlowFunc(("Returned rc=%Rrc, rcGuest=%Rrc, iExitCode=%d\n", vrc, errorInfo.rcGuest, errorInfo.iExitCode));
+ return vrc;
+}
+
+/**
+ * Reports if the tool has been run correctly.
+ *
+ * @return Will return VERR_GSTCTL_PROCESS_EXIT_CODE if the tool process returned an exit code <> 0,
+ * VERR_GSTCTL_PROCESS_WRONG_STATE if the tool process is in a wrong state (e.g. still running),
+ * or VINF_SUCCESS otherwise.
+ *
+ * @param piExitCode Exit code of the tool. Optional.
+ */
+int GuestProcessTool::getTerminationStatus(int32_t *piExitCode /* = NULL */)
+{
+ Assert(!pProcess.isNull());
+ /* pExitCode is optional. */
+
+ int vrc;
+ if (!isRunning())
+ {
+ LONG iExitCode = -1;
+ HRESULT hr = pProcess->COMGETTER(ExitCode(&iExitCode));
+ AssertComRC(hr);
+
+ if (piExitCode)
+ *piExitCode = iExitCode;
+
+ vrc = iExitCode != 0 ? VERR_GSTCTL_PROCESS_EXIT_CODE : VINF_SUCCESS;
+ }
+ else
+ vrc = VERR_GSTCTL_PROCESS_WRONG_STATE;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Waits for a guest process tool.
+ *
+ * @returns VBox status code.
+ * @param fToolWaitFlags Guest process tool wait flags to use for waiting.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestProcessTool::wait(uint32_t fToolWaitFlags, int *prcGuest)
+{
+ return waitEx(fToolWaitFlags, NULL /* pStrmBlkOut */, prcGuest);
+}
+
+/**
+ * Waits for a guest process tool, also returning process output.
+ *
+ * @returns VBox status code.
+ * @param fToolWaitFlags Guest process tool wait flags to use for waiting.
+ * @param pStrmBlkOut Where to store the guest process output.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestProcessTool::waitEx(uint32_t fToolWaitFlags, GuestProcessStreamBlock *pStrmBlkOut, int *prcGuest)
+{
+ LogFlowThisFunc(("fToolWaitFlags=0x%x, pStreamBlock=%p, prcGuest=%p\n", fToolWaitFlags, pStrmBlkOut, prcGuest));
+
+ /* Can we parse the next block without waiting? */
+ int vrc;
+ if (fToolWaitFlags & GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK)
+ {
+ AssertPtr(pStrmBlkOut);
+ vrc = getCurrentBlock(GUEST_PROC_OUT_H_STDOUT, *pStrmBlkOut);
+ if (RT_SUCCESS(vrc))
+ return vrc;
+ /* else do the waiting below. */
+ }
+
+ /* Do the waiting. */
+ uint32_t fProcWaitForFlags = ProcessWaitForFlag_Terminate;
+ if (mStartupInfo.mFlags & ProcessCreateFlag_WaitForStdOut)
+ fProcWaitForFlags |= ProcessWaitForFlag_StdOut;
+ if (mStartupInfo.mFlags & ProcessCreateFlag_WaitForStdErr)
+ fProcWaitForFlags |= ProcessWaitForFlag_StdErr;
+
+ /** @todo Decrease timeout while running. */
+ uint64_t u64StartMS = RTTimeMilliTS();
+ uint32_t uTimeoutMS = mStartupInfo.mTimeoutMS;
+
+ int vrcGuest = VINF_SUCCESS;
+ bool fDone = false;
+
+ BYTE byBuf[_64K];
+ uint32_t cbRead;
+
+ bool fHandleStdOut = false;
+ bool fHandleStdErr = false;
+
+ /**
+ * Updates the elapsed time and checks if a
+ * timeout happened, then breaking out of the loop.
+ */
+#define UPDATE_AND_CHECK_ELAPSED_TIME() \
+ u64ElapsedMS = RTTimeMilliTS() - u64StartMS; \
+ if ( uTimeoutMS != RT_INDEFINITE_WAIT \
+ && u64ElapsedMS >= uTimeoutMS) \
+ { \
+ vrc = VERR_TIMEOUT; \
+ break; \
+ }
+
+ /**
+ * Returns the remaining time (in ms).
+ */
+#define GET_REMAINING_TIME \
+ uTimeoutMS == RT_INDEFINITE_WAIT \
+ ? RT_INDEFINITE_WAIT : uTimeoutMS - (uint32_t)u64ElapsedMS \
+
+ ProcessWaitResult_T waitRes = ProcessWaitResult_None;
+ do
+ {
+ uint64_t u64ElapsedMS;
+ UPDATE_AND_CHECK_ELAPSED_TIME();
+
+ vrc = pProcess->i_waitFor(fProcWaitForFlags, GET_REMAINING_TIME, waitRes, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ break;
+
+ switch (waitRes)
+ {
+ case ProcessWaitResult_StdIn:
+ vrc = VERR_NOT_IMPLEMENTED;
+ break;
+
+ case ProcessWaitResult_StdOut:
+ fHandleStdOut = true;
+ break;
+
+ case ProcessWaitResult_StdErr:
+ fHandleStdErr = true;
+ break;
+
+ case ProcessWaitResult_WaitFlagNotSupported:
+ if (fProcWaitForFlags & ProcessWaitForFlag_StdOut)
+ fHandleStdOut = true;
+ if (fProcWaitForFlags & ProcessWaitForFlag_StdErr)
+ fHandleStdErr = true;
+ /* Since waiting for stdout / stderr is not supported by the guest,
+ * wait a bit to not hog the CPU too much when polling for data. */
+ RTThreadSleep(1); /* Optional, don't check rc. */
+ break;
+
+ case ProcessWaitResult_Error:
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ break;
+
+ case ProcessWaitResult_Terminate:
+ fDone = true;
+ break;
+
+ case ProcessWaitResult_Timeout:
+ vrc = VERR_TIMEOUT;
+ break;
+
+ case ProcessWaitResult_Start:
+ case ProcessWaitResult_Status:
+ /* Not used here, just skip. */
+ break;
+
+ default:
+ AssertMsgFailed(("Unhandled process wait result %RU32\n", waitRes));
+ break;
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ if (fHandleStdOut)
+ {
+ UPDATE_AND_CHECK_ELAPSED_TIME();
+
+ cbRead = 0;
+ vrc = pProcess->i_readData(GUEST_PROC_OUT_H_STDOUT, sizeof(byBuf),
+ GET_REMAINING_TIME,
+ byBuf, sizeof(byBuf),
+ &cbRead, &vrcGuest);
+ if ( RT_FAILURE(vrc)
+ || vrc == VWRN_GSTCTL_OBJECTSTATE_CHANGED)
+ break;
+
+ if (cbRead)
+ {
+ LogFlowThisFunc(("Received %RU32 bytes from stdout\n", cbRead));
+ vrc = mStdOut.AddData(byBuf, cbRead);
+
+ if ( RT_SUCCESS(vrc)
+ && (fToolWaitFlags & GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK))
+ {
+ AssertPtr(pStrmBlkOut);
+ vrc = getCurrentBlock(GUEST_PROC_OUT_H_STDOUT, *pStrmBlkOut);
+
+ /* When successful, break out of the loop because we're done
+ * with reading the first stream block. */
+ if (RT_SUCCESS(vrc))
+ fDone = true;
+ }
+ }
+
+ fHandleStdOut = false;
+ }
+
+ if (fHandleStdErr)
+ {
+ UPDATE_AND_CHECK_ELAPSED_TIME();
+
+ cbRead = 0;
+ vrc = pProcess->i_readData(GUEST_PROC_OUT_H_STDERR, sizeof(byBuf),
+ GET_REMAINING_TIME,
+ byBuf, sizeof(byBuf),
+ &cbRead, &vrcGuest);
+ if ( RT_FAILURE(vrc)
+ || vrc == VWRN_GSTCTL_OBJECTSTATE_CHANGED)
+ break;
+
+ if (cbRead)
+ {
+ LogFlowThisFunc(("Received %RU32 bytes from stderr\n", cbRead));
+ vrc = mStdErr.AddData(byBuf, cbRead);
+ }
+
+ fHandleStdErr = false;
+ }
+
+ } while (!fDone && RT_SUCCESS(vrc));
+
+#undef UPDATE_AND_CHECK_ELAPSED_TIME
+#undef GET_REMAINING_TIME
+
+ if (RT_FAILURE(vrcGuest))
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+
+ LogFlowThisFunc(("Loop ended with rc=%Rrc, vrcGuest=%Rrc, waitRes=%RU32\n",
+ vrc, vrcGuest, waitRes));
+ if (prcGuest)
+ *prcGuest = vrcGuest;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Terminates a guest process tool.
+ *
+ * @returns VBox status code.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestProcessTool::terminate(uint32_t uTimeoutMS, int *prcGuest)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc;
+ if (!pProcess.isNull())
+ vrc = pProcess->i_terminateProcess(uTimeoutMS, prcGuest);
+ else
+ vrc = VERR_NOT_FOUND;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Converts a toolbox tool's exit code to an IPRT error code.
+ *
+ * @returns VBox status code.
+ * @param startupInfo Startup info of the toolbox tool to lookup error code for.
+ * @param iExitCode The toolbox tool's exit code to lookup IPRT error for.
+ */
+/* static */
+int GuestProcessTool::exitCodeToRc(const GuestProcessStartupInfo &startupInfo, int32_t iExitCode)
+{
+ if (startupInfo.mArguments.size() == 0)
+ {
+ AssertFailed();
+ return VERR_GENERAL_FAILURE; /* Should not happen. */
+ }
+
+ return exitCodeToRc(startupInfo.mArguments[0].c_str(), iExitCode);
+}
+
+/**
+ * Converts a toolbox tool's exit code to an IPRT error code.
+ *
+ * @returns VBox status code.
+ * @param pszTool Name of toolbox tool to lookup error code for.
+ * @param iExitCode The toolbox tool's exit code to lookup IPRT error for.
+ */
+/* static */
+int GuestProcessTool::exitCodeToRc(const char *pszTool, int32_t iExitCode)
+{
+ AssertPtrReturn(pszTool, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("%s: %d\n", pszTool, iExitCode));
+
+ if (iExitCode == 0) /* No error? Bail out early. */
+ return VINF_SUCCESS;
+
+ if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_CAT))
+ {
+ switch (iExitCode)
+ {
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_ACCESS_DENIED: return VERR_ACCESS_DENIED;
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_FILE_NOT_FOUND: return VERR_FILE_NOT_FOUND;
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_PATH_NOT_FOUND: return VERR_PATH_NOT_FOUND;
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_SHARING_VIOLATION: return VERR_SHARING_VIOLATION;
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_IS_A_DIRECTORY: return VERR_IS_A_DIRECTORY;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_LS))
+ {
+ switch (iExitCode)
+ {
+ /** @todo Handle access denied? */
+ case RTEXITCODE_FAILURE: return VERR_PATH_NOT_FOUND;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_STAT))
+ {
+ switch (iExitCode)
+ {
+ case VBOXSERVICETOOLBOX_STAT_EXITCODE_ACCESS_DENIED: return VERR_ACCESS_DENIED;
+ case VBOXSERVICETOOLBOX_STAT_EXITCODE_FILE_NOT_FOUND: return VERR_FILE_NOT_FOUND;
+ case VBOXSERVICETOOLBOX_STAT_EXITCODE_PATH_NOT_FOUND: return VERR_PATH_NOT_FOUND;
+ case VBOXSERVICETOOLBOX_STAT_EXITCODE_NET_PATH_NOT_FOUND: return VERR_NET_PATH_NOT_FOUND;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_MKDIR))
+ {
+ switch (iExitCode)
+ {
+ case RTEXITCODE_FAILURE: return VERR_CANT_CREATE;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_MKTEMP))
+ {
+ switch (iExitCode)
+ {
+ case RTEXITCODE_FAILURE: return VERR_CANT_CREATE;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_RM))
+ {
+ switch (iExitCode)
+ {
+ case RTEXITCODE_FAILURE: return VERR_FILE_NOT_FOUND;
+ /** @todo RTPathRmCmd does not yet distinguish between not found and access denied yet. */
+ default: break;
+ }
+ }
+
+ LogFunc(("Warning: Exit code %d not handled for tool '%s', returning VERR_GENERAL_FAILURE\n", iExitCode, pszTool));
+
+ if (iExitCode == RTEXITCODE_SYNTAX)
+ return VERR_INTERNAL_ERROR_5;
+ return VERR_GENERAL_FAILURE;
+}
+
+/**
+ * Returns a stringyfied error of a guest process tool error.
+ *
+ * @returns Stringyfied error.
+ * @param pszTool Toolbox tool name to get stringyfied error for.
+ * @param guestErrorInfo Guest error info to get stringyfied error for.
+ */
+/* static */
+Utf8Str GuestProcessTool::guestErrorToString(const char *pszTool, const GuestErrorInfo &guestErrorInfo)
+{
+ Utf8Str strErr;
+
+ /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */
+ switch (guestErrorInfo.getRc())
+ {
+ case VERR_ACCESS_DENIED:
+ strErr.printf(tr("Access to \"%s\" denied"), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_FILE_NOT_FOUND: /* This is the most likely error. */
+ RT_FALL_THROUGH();
+ case VERR_PATH_NOT_FOUND:
+ strErr.printf(tr("No such file or directory \"%s\""), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_INVALID_VM_HANDLE:
+ strErr.printf(tr("VMM device is not available (is the VM running?)"));
+ break;
+
+ case VERR_HGCM_SERVICE_NOT_FOUND:
+ strErr.printf(tr("The guest execution service is not available"));
+ break;
+
+ case VERR_BAD_EXE_FORMAT:
+ strErr.printf(tr("The file \"%s\" is not an executable format"), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_AUTHENTICATION_FAILURE:
+ strErr.printf(tr("The user \"%s\" was not able to logon"), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_INVALID_NAME:
+ strErr.printf(tr("The file \"%s\" is an invalid name"), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_TIMEOUT:
+ strErr.printf(tr("The guest did not respond within time"));
+ break;
+
+ case VERR_CANCELLED:
+ strErr.printf(tr("The execution operation was canceled"));
+ break;
+
+ case VERR_GSTCTL_MAX_CID_OBJECTS_REACHED:
+ strErr.printf(tr("Maximum number of concurrent guest processes has been reached"));
+ break;
+
+ case VERR_NOT_FOUND:
+ strErr.printf(tr("The guest execution service is not ready (yet)"));
+ break;
+
+ default:
+ strErr.printf(tr("Unhandled error %Rrc for \"%s\" occurred for tool \"%s\" on guest -- please file a bug report"),
+ guestErrorInfo.getRc(), guestErrorInfo.getWhat().c_str(), pszTool);
+ break;
+ }
+
+ return strErr;
+}
+
diff --git a/src/VBox/Main/src-client/GuestSessionImpl.cpp b/src/VBox/Main/src-client/GuestSessionImpl.cpp
new file mode 100644
index 00000000..06d6c833
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestSessionImpl.cpp
@@ -0,0 +1,4821 @@
+/* $Id: GuestSessionImpl.cpp $ */
+/** @file
+ * VirtualBox Main - Guest session handling.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_GUESTSESSION
+#include "LoggingNew.h"
+
+#include "GuestImpl.h"
+#ifndef VBOX_WITH_GUEST_CONTROL
+# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
+#endif
+#include "GuestSessionImpl.h"
+#include "GuestSessionImplTasks.h"
+#include "GuestCtrlImplPrivate.h"
+#include "VirtualBoxErrorInfoImpl.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+#include "ProgressImpl.h"
+#include "VBoxEvents.h"
+#include "VMMDev.h"
+#include "ThreadTask.h"
+
+#include <memory> /* For auto_ptr. */
+
+#include <iprt/cpp/utils.h> /* For unconst(). */
+#include <iprt/ctype.h>
+#include <iprt/env.h>
+#include <iprt/file.h> /* For CopyTo/From. */
+#include <iprt/path.h>
+#include <iprt/rand.h>
+
+#include <VBox/com/array.h>
+#include <VBox/com/listeners.h>
+#include <VBox/version.h>
+
+
+/**
+ * Base class representing an internal
+ * asynchronous session task.
+ */
+class GuestSessionTaskInternal : public ThreadTask
+{
+public:
+
+ GuestSessionTaskInternal(GuestSession *pSession)
+ : ThreadTask("GenericGuestSessionTaskInternal")
+ , mSession(pSession)
+ , mRC(VINF_SUCCESS) { }
+
+ virtual ~GuestSessionTaskInternal(void) { }
+
+ /** Returns the last set result code. */
+ int rc(void) const { return mRC; }
+ /** Returns whether the last set result code indicates success or not. */
+ bool isOk(void) const { return RT_SUCCESS(mRC); }
+ /** Returns the task's guest session object. */
+ const ComObjPtr<GuestSession> &Session(void) const { return mSession; }
+
+protected:
+
+ /** Guest session the task belongs to. */
+ const ComObjPtr<GuestSession> mSession;
+ /** The last set result code. */
+ int mRC;
+};
+
+/**
+ * Class for asynchronously starting a guest session.
+ */
+class GuestSessionTaskInternalStart : public GuestSessionTaskInternal
+{
+public:
+
+ GuestSessionTaskInternalStart(GuestSession *pSession)
+ : GuestSessionTaskInternal(pSession)
+ {
+ m_strTaskName = "gctlSesStart";
+ }
+
+ void handler()
+ {
+ /* Ignore rc */ GuestSession::i_startSessionThreadTask(this);
+ }
+};
+
+/**
+ * Internal listener class to serve events in an
+ * active manner, e.g. without polling delays.
+ */
+class GuestSessionListener
+{
+public:
+
+ GuestSessionListener(void)
+ {
+ }
+
+ virtual ~GuestSessionListener(void)
+ {
+ }
+
+ HRESULT init(GuestSession *pSession)
+ {
+ AssertPtrReturn(pSession, E_POINTER);
+ mSession = pSession;
+ return S_OK;
+ }
+
+ void uninit(void)
+ {
+ mSession = NULL;
+ }
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
+ {
+ switch (aType)
+ {
+ case VBoxEventType_OnGuestSessionStateChanged:
+ {
+ AssertPtrReturn(mSession, E_POINTER);
+ int rc2 = mSession->signalWaitEvent(aType, aEvent);
+ RT_NOREF(rc2);
+#ifdef DEBUG_andy
+ LogFlowFunc(("Signalling events of type=%RU32, session=%p resulted in rc=%Rrc\n",
+ aType, mSession, rc2));
+#endif
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Unhandled event %RU32\n", aType));
+ break;
+ }
+
+ return S_OK;
+ }
+
+private:
+
+ GuestSession *mSession;
+};
+typedef ListenerImpl<GuestSessionListener, GuestSession*> GuestSessionListenerImpl;
+
+VBOX_LISTENER_DECLARE(GuestSessionListenerImpl)
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(GuestSession)
+
+HRESULT GuestSession::FinalConstruct(void)
+{
+ LogFlowThisFuncEnter();
+ return BaseFinalConstruct();
+}
+
+void GuestSession::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes a guest session but does *not* open in on the guest side
+ * yet. This needs to be done via the openSession() / openSessionAsync calls.
+ *
+ * @returns VBox status code.
+ * @param pGuest Guest object the guest session belongs to.
+ * @param ssInfo Guest session startup info to use.
+ * @param guestCreds Guest credentials to use for starting a guest session
+ * with a specific guest account.
+ */
+int GuestSession::init(Guest *pGuest, const GuestSessionStartupInfo &ssInfo,
+ const GuestCredentials &guestCreds)
+{
+ LogFlowThisFunc(("pGuest=%p, ssInfo=%p, guestCreds=%p\n",
+ pGuest, &ssInfo, &guestCreds));
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED);
+
+ AssertPtrReturn(pGuest, VERR_INVALID_POINTER);
+
+ /*
+ * Initialize our data members from the input.
+ */
+ mParent = pGuest;
+
+ /* Copy over startup info. */
+ /** @todo Use an overloaded copy operator. Later. */
+ mData.mSession.mID = ssInfo.mID;
+ mData.mSession.mIsInternal = ssInfo.mIsInternal;
+ mData.mSession.mName = ssInfo.mName;
+ mData.mSession.mOpenFlags = ssInfo.mOpenFlags;
+ mData.mSession.mOpenTimeoutMS = ssInfo.mOpenTimeoutMS;
+
+ /* Copy over session credentials. */
+ /** @todo Use an overloaded copy operator. Later. */
+ mData.mCredentials.mUser = guestCreds.mUser;
+ mData.mCredentials.mPassword = guestCreds.mPassword;
+ mData.mCredentials.mDomain = guestCreds.mDomain;
+
+ /* Initialize the remainder of the data. */
+ mData.mRC = VINF_SUCCESS;
+ mData.mStatus = GuestSessionStatus_Undefined;
+ mData.mpBaseEnvironment = NULL;
+
+ /*
+ * Register an object for the session itself to clearly
+ * distinguish callbacks which are for this session directly, or for
+ * objects (like files, directories, ...) which are bound to this session.
+ */
+ int rc = i_objectRegister(NULL /* pObject */, SESSIONOBJECTTYPE_SESSION, &mData.mObjectID);
+ if (RT_SUCCESS(rc))
+ {
+ rc = mData.mEnvironmentChanges.initChangeRecord(pGuest->i_isGuestInWindowsNtFamily()
+ ? RTENV_CREATE_F_ALLOW_EQUAL_FIRST_IN_VAR : 0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&mWaitEventCritSect);
+ AssertRC(rc);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = i_determineProtocolVersion();
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * <Replace this if you figure out what the code is doing.>
+ */
+ HRESULT hr = unconst(mEventSource).createObject();
+ if (SUCCEEDED(hr))
+ hr = mEventSource->init();
+ if (SUCCEEDED(hr))
+ {
+ try
+ {
+ GuestSessionListener *pListener = new GuestSessionListener();
+ ComObjPtr<GuestSessionListenerImpl> thisListener;
+ hr = thisListener.createObject();
+ if (SUCCEEDED(hr))
+ hr = thisListener->init(pListener, this); /* thisListener takes ownership of pListener. */
+ if (SUCCEEDED(hr))
+ {
+ com::SafeArray <VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged);
+ hr = mEventSource->RegisterListener(thisListener,
+ ComSafeArrayAsInParam(eventTypes),
+ TRUE /* Active listener */);
+ if (SUCCEEDED(hr))
+ {
+ mLocalListener = thisListener;
+
+ /*
+ * Mark this object as operational and return success.
+ */
+ autoInitSpan.setSucceeded();
+ LogFlowThisFunc(("mName=%s mID=%RU32 mIsInternal=%RTbool rc=VINF_SUCCESS\n",
+ mData.mSession.mName.c_str(), mData.mSession.mID, mData.mSession.mIsInternal));
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+ rc = Global::vboxStatusCodeFromCOM(hr);
+ }
+
+ autoInitSpan.setFailed();
+ LogThisFunc(("Failed! mName=%s mID=%RU32 mIsInternal=%RTbool => rc=%Rrc\n",
+ mData.mSession.mName.c_str(), mData.mSession.mID, mData.mSession.mIsInternal, rc));
+ return rc;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void GuestSession::uninit(void)
+{
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFuncEnter();
+
+ /* Call i_onRemove to take care of the object cleanups. */
+ i_onRemove();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Unregister the session's object ID. */
+ i_objectUnregister(mData.mObjectID);
+
+ Assert(mData.mObjects.size () == 0);
+ mData.mObjects.clear();
+
+ mData.mEnvironmentChanges.reset();
+
+ if (mData.mpBaseEnvironment)
+ {
+ mData.mpBaseEnvironment->releaseConst();
+ mData.mpBaseEnvironment = NULL;
+ }
+
+ /* Unitialize our local listener. */
+ mLocalListener.setNull();
+
+ baseUninit();
+
+ LogFlowFuncLeave();
+}
+
+// implementation of public getters/setters for attributes
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestSession::getUser(com::Utf8Str &aUser)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aUser = mData.mCredentials.mUser;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getDomain(com::Utf8Str &aDomain)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aDomain = mData.mCredentials.mDomain;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getName(com::Utf8Str &aName)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aName = mData.mSession.mName;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getId(ULONG *aId)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aId = mData.mSession.mID;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getStatus(GuestSessionStatus_T *aStatus)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aStatus = mData.mStatus;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getTimeout(ULONG *aTimeout)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aTimeout = mData.mTimeout;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::setTimeout(ULONG aTimeout)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mData.mTimeout = aTimeout;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getProtocolVersion(ULONG *aProtocolVersion)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aProtocolVersion = mData.mProtocolVersion;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getEnvironmentChanges(std::vector<com::Utf8Str> &aEnvironmentChanges)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ vrc = mData.mEnvironmentChanges.queryPutEnvArray(&aEnvironmentChanges);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return Global::vboxStatusCodeToCOM(vrc);
+}
+
+HRESULT GuestSession::setEnvironmentChanges(const std::vector<com::Utf8Str> &aEnvironmentChanges)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc;
+ size_t idxError = ~(size_t)0;
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mEnvironmentChanges.reset();
+ vrc = mData.mEnvironmentChanges.applyPutEnvArray(aEnvironmentChanges, &idxError);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+ if (vrc == VERR_ENV_INVALID_VAR_NAME)
+ return setError(E_INVALIDARG, tr("Invalid environment variable name '%s', index %zu"),
+ aEnvironmentChanges[idxError].c_str(), idxError);
+ return setErrorBoth(Global::vboxStatusCodeToCOM(vrc), vrc, tr("Failed to apply '%s', index %zu (%Rrc)"),
+ aEnvironmentChanges[idxError].c_str(), idxError, vrc);
+}
+
+HRESULT GuestSession::getEnvironmentBase(std::vector<com::Utf8Str> &aEnvironmentBase)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc;
+ if (mData.mpBaseEnvironment)
+ {
+ int vrc = mData.mpBaseEnvironment->queryPutEnvArray(&aEnvironmentBase);
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+ }
+ else if (mData.mProtocolVersion < 99999)
+ hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the Guest Additions"));
+ else
+ hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest"));
+
+ LogFlowFuncLeave();
+ return hrc;
+}
+
+HRESULT GuestSession::getProcesses(std::vector<ComPtr<IGuestProcess> > &aProcesses)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aProcesses.resize(mData.mProcesses.size());
+ size_t i = 0;
+ for (SessionProcesses::iterator it = mData.mProcesses.begin();
+ it != mData.mProcesses.end();
+ ++it, ++i)
+ {
+ it->second.queryInterfaceTo(aProcesses[i].asOutParam());
+ }
+
+ LogFlowFunc(("mProcesses=%zu\n", aProcesses.size()));
+ return S_OK;
+}
+
+HRESULT GuestSession::getPathStyle(PathStyle_T *aPathStyle)
+{
+ *aPathStyle = i_getGuestPathStyle();
+ return S_OK;
+}
+
+HRESULT GuestSession::getCurrentDirectory(com::Utf8Str &aCurrentDirectory)
+{
+ RT_NOREF(aCurrentDirectory);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::setCurrentDirectory(const com::Utf8Str &aCurrentDirectory)
+{
+ RT_NOREF(aCurrentDirectory);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::getUserHome(com::Utf8Str &aUserHome)
+{
+ HRESULT hr = i_isStartedExternal();
+ if (FAILED(hr))
+ return hr;
+
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_pathUserHome(aUserHome, &rcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ switch (rcGuest)
+ {
+ case VERR_NOT_SUPPORTED:
+ hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest,
+ tr("Getting the user's home path is not supported by installed Guest Additions"));
+ break;
+
+ default:
+ hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest,
+ tr("Getting the user's home path failed on the guest: %Rrc"), rcGuest);
+ break;
+ }
+ break;
+ }
+
+ default:
+ hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Getting the user's home path failed: %Rrc"), vrc);
+ break;
+ }
+ }
+
+ return hr;
+}
+
+HRESULT GuestSession::getUserDocuments(com::Utf8Str &aUserDocuments)
+{
+ HRESULT hr = i_isStartedExternal();
+ if (FAILED(hr))
+ return hr;
+
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_pathUserDocuments(aUserDocuments, &rcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ switch (rcGuest)
+ {
+ case VERR_NOT_SUPPORTED:
+ hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest,
+ tr("Getting the user's documents path is not supported by installed Guest Additions"));
+ break;
+
+ default:
+ hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest,
+ tr("Getting the user's documents path failed on the guest: %Rrc"), rcGuest);
+ break;
+ }
+ break;
+ }
+
+ default:
+ hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Getting the user's documents path failed: %Rrc"), vrc);
+ break;
+ }
+ }
+
+ return hr;
+}
+
+HRESULT GuestSession::getDirectories(std::vector<ComPtr<IGuestDirectory> > &aDirectories)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aDirectories.resize(mData.mDirectories.size());
+ size_t i = 0;
+ for (SessionDirectories::iterator it = mData.mDirectories.begin(); it != mData.mDirectories.end(); ++it, ++i)
+ {
+ it->second.queryInterfaceTo(aDirectories[i].asOutParam());
+ }
+
+ LogFlowFunc(("mDirectories=%zu\n", aDirectories.size()));
+ return S_OK;
+}
+
+HRESULT GuestSession::getFiles(std::vector<ComPtr<IGuestFile> > &aFiles)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aFiles.resize(mData.mFiles.size());
+ size_t i = 0;
+ for(SessionFiles::iterator it = mData.mFiles.begin(); it != mData.mFiles.end(); ++it, ++i)
+ it->second.queryInterfaceTo(aFiles[i].asOutParam());
+
+ LogFlowFunc(("mDirectories=%zu\n", aFiles.size()));
+
+ return S_OK;
+}
+
+HRESULT GuestSession::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ LogFlowThisFuncEnter();
+
+ // no need to lock - lifetime constant
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+// private methods
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Closes a guest session on the guest.
+ *
+ * @returns VBox status code.
+ * @param uFlags Guest session close flags.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_closeSession(uint32_t uFlags, uint32_t uTimeoutMS, int *prcGuest)
+{
+ AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("uFlags=%x, uTimeoutMS=%RU32\n", uFlags, uTimeoutMS));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Guest Additions < 4.3 don't support closing dedicated
+ guest sessions, skip. */
+ if (mData.mProtocolVersion < 2)
+ {
+ LogFlowThisFunc(("Installed Guest Additions don't support closing dedicated sessions, skipping\n"));
+ return VINF_SUCCESS;
+ }
+
+ /** @todo uFlags validation. */
+
+ if (mData.mStatus != GuestSessionStatus_Started)
+ {
+ LogFlowThisFunc(("Session ID=%RU32 not started (anymore), status now is: %RU32\n",
+ mData.mSession.mID, mData.mStatus));
+ return VINF_SUCCESS;
+ }
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged);
+
+ vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ LogFlowThisFunc(("Sending closing request to guest session ID=%RU32, uFlags=%x\n",
+ mData.mSession.mID, uFlags));
+
+ alock.release();
+
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], uFlags);
+
+ vrc = i_sendMessage(HOST_MSG_SESSION_CLOSE, i, paParms, VBOX_GUESTCTRL_DST_BOTH);
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForStatusChange(pEvent, GuestSessionWaitForFlag_Terminate, uTimeoutMS,
+ NULL /* Session status */, prcGuest);
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Internal worker function for public APIs that handle copying elements from
+ * guest to the host.
+ *
+ * @return HRESULT
+ * @param SourceSet Source set specifying what to copy.
+ * @param strDestination Destination path on the host. Host path style.
+ * @param pProgress Progress object returned to the caller.
+ */
+HRESULT GuestSession::i_copyFromGuest(const GuestSessionFsSourceSet &SourceSet,
+ const com::Utf8Str &strDestination, ComPtr<IProgress> &pProgress)
+{
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ /* Validate stuff. */
+ if (RT_UNLIKELY(SourceSet.size() == 0 || *(SourceSet[0].strSource.c_str()) == '\0')) /* At least one source must be present. */
+ return setError(E_INVALIDARG, tr("No source(s) specified"));
+ if (RT_UNLIKELY((strDestination.c_str()) == NULL || *(strDestination.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No destination specified"));
+
+ GuestSessionFsSourceSet::const_iterator itSrc = SourceSet.begin();
+ while (itSrc != SourceSet.end())
+ {
+ LogRel2(("Guest Control: Copying '%s' from guest to '%s' on the host (type: %s, filter: %s)\n",
+ itSrc->strSource.c_str(), strDestination.c_str(), GuestBase::fsObjTypeToStr(itSrc->enmType), itSrc->strFilter.c_str()));
+ ++itSrc;
+ }
+
+ /* Create a task and return the progress obejct for it. */
+ GuestSessionTaskCopyFrom *pTask = NULL;
+ try
+ {
+ pTask = new GuestSessionTaskCopyFrom(this /* GuestSession */, SourceSet, strDestination);
+ }
+ catch (std::bad_alloc &)
+ {
+ return setError(E_OUTOFMEMORY, tr("Failed to create GuestSessionTaskCopyFrom object"));
+ }
+
+ try
+ {
+ hrc = pTask->Init(Utf8StrFmt(tr("Copying to \"%s\" on the host"), strDestination.c_str()));
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ ComObjPtr<Progress> ptrProgressObj = pTask->GetProgressObject();
+
+ /* Kick off the worker thread. Note! Consumes pTask. */
+ hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER);
+ pTask = NULL;
+ if (SUCCEEDED(hrc))
+ hrc = ptrProgressObj.queryInterfaceTo(pProgress.asOutParam());
+ else
+ hrc = setError(hrc, tr("Starting thread for copying from guest to the host failed"));
+ }
+ else
+ {
+ hrc = setError(hrc, tr("Initializing GuestSessionTaskCopyFrom object failed"));
+ delete pTask;
+ }
+
+ LogFlowFunc(("Returning %Rhrc\n", hrc));
+ return hrc;
+}
+
+/**
+ * Internal worker function for public APIs that handle copying elements from
+ * host to the guest.
+ *
+ * @return HRESULT
+ * @param SourceSet Source set specifying what to copy.
+ * @param strDestination Destination path on the guest. Guest path style.
+ * @param pProgress Progress object returned to the caller.
+ */
+HRESULT GuestSession::i_copyToGuest(const GuestSessionFsSourceSet &SourceSet,
+ const com::Utf8Str &strDestination, ComPtr<IProgress> &pProgress)
+{
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ GuestSessionFsSourceSet::const_iterator itSrc = SourceSet.begin();
+ while (itSrc != SourceSet.end())
+ {
+ LogRel2(("Guest Control: Copying '%s' from host to '%s' on the guest (type: %s, filter: %s)\n",
+ itSrc->strSource.c_str(), strDestination.c_str(), GuestBase::fsObjTypeToStr(itSrc->enmType), itSrc->strFilter.c_str()));
+ ++itSrc;
+ }
+
+ /* Create a task and return the progress object for it. */
+ GuestSessionTaskCopyTo *pTask = NULL;
+ try
+ {
+ pTask = new GuestSessionTaskCopyTo(this /* GuestSession */, SourceSet, strDestination);
+ }
+ catch (std::bad_alloc &)
+ {
+ return setError(E_OUTOFMEMORY, tr("Failed to create GuestSessionTaskCopyTo object"));
+ }
+
+ try
+ {
+ hrc = pTask->Init(Utf8StrFmt(tr("Copying to \"%s\" on the guest"), strDestination.c_str()));
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ ComObjPtr<Progress> ptrProgressObj = pTask->GetProgressObject();
+
+ /* Kick off the worker thread. Note! Consumes pTask. */
+ hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER);
+ pTask = NULL;
+ if (SUCCEEDED(hrc))
+ hrc = ptrProgressObj.queryInterfaceTo(pProgress.asOutParam());
+ else
+ hrc = setError(hrc, tr("Starting thread for copying from host to the guest failed"));
+ }
+ else
+ {
+ hrc = setError(hrc, tr("Initializing GuestSessionTaskCopyTo object failed"));
+ delete pTask;
+ }
+
+ LogFlowFunc(("Returning %Rhrc\n", hrc));
+ return hrc;
+}
+
+/**
+ * Validates and extracts directory copy flags from a comma-separated string.
+ *
+ * @return COM status, error set on failure
+ * @param strFlags String to extract flags from.
+ * @param fStrict Whether to set an error when an unknown / invalid flag is detected.
+ * @param pfFlags Where to store the extracted (and validated) flags.
+ */
+HRESULT GuestSession::i_directoryCopyFlagFromStr(const com::Utf8Str &strFlags, bool fStrict, DirectoryCopyFlag_T *pfFlags)
+{
+ unsigned fFlags = DirectoryCopyFlag_None;
+
+ /* Validate and set flags. */
+ if (strFlags.isNotEmpty())
+ {
+ const char *pszNext = strFlags.c_str();
+ for (;;)
+ {
+ /* Find the next keyword, ignoring all whitespace. */
+ pszNext = RTStrStripL(pszNext);
+
+ const char * const pszComma = strchr(pszNext, ',');
+ size_t cchKeyword = pszComma ? pszComma - pszNext : strlen(pszNext);
+ while (cchKeyword > 0 && RT_C_IS_SPACE(pszNext[cchKeyword - 1]))
+ cchKeyword--;
+
+ if (cchKeyword > 0)
+ {
+ /* Convert keyword to flag. */
+#define MATCH_KEYWORD(a_szKeyword) ( cchKeyword == sizeof(a_szKeyword) - 1U \
+ && memcmp(pszNext, a_szKeyword, sizeof(a_szKeyword) - 1U) == 0)
+ if (MATCH_KEYWORD("CopyIntoExisting"))
+ fFlags |= (unsigned)DirectoryCopyFlag_CopyIntoExisting;
+ else if (MATCH_KEYWORD("Recursive"))
+ fFlags |= (unsigned)DirectoryCopyFlag_Recursive;
+ else if (MATCH_KEYWORD("FollowLinks"))
+ fFlags |= (unsigned)DirectoryCopyFlag_FollowLinks;
+ else if (fStrict)
+ return setError(E_INVALIDARG, tr("Invalid directory copy flag: %.*s"), (int)cchKeyword, pszNext);
+#undef MATCH_KEYWORD
+ }
+ if (!pszComma)
+ break;
+ pszNext = pszComma + 1;
+ }
+ }
+
+ if (pfFlags)
+ *pfFlags = (DirectoryCopyFlag_T)fFlags;
+ return S_OK;
+}
+
+/**
+ * Creates a directory on the guest.
+ *
+ * @returns VBox status code.
+ * @param strPath Path on guest to directory to create.
+ * @param uMode Creation mode to use (octal, 0777 max).
+ * @param uFlags Directory creation flags to use.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestSession::i_directoryCreate(const Utf8Str &strPath, uint32_t uMode,
+ uint32_t uFlags, int *prcGuest)
+{
+ AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("strPath=%s, uMode=%x, uFlags=%x\n", strPath.c_str(), uMode, uFlags));
+
+ int vrc = VINF_SUCCESS;
+
+ GuestProcessStartupInfo procInfo;
+ procInfo.mFlags = ProcessCreateFlag_Hidden;
+ procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_MKDIR);
+
+ try
+ {
+ procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */
+
+ /* Construct arguments. */
+ if (uFlags)
+ {
+ if (uFlags & DirectoryCreateFlag_Parents)
+ procInfo.mArguments.push_back(Utf8Str("--parents")); /* We also want to create the parent directories. */
+ else
+ vrc = VERR_INVALID_PARAMETER;
+ }
+
+ if ( RT_SUCCESS(vrc)
+ && uMode)
+ {
+ procInfo.mArguments.push_back(Utf8Str("--mode")); /* Set the creation mode. */
+
+ char szMode[16];
+ if (RTStrPrintf(szMode, sizeof(szMode), "%o", uMode))
+ {
+ procInfo.mArguments.push_back(Utf8Str(szMode));
+ }
+ else
+ vrc = VERR_BUFFER_OVERFLOW;
+ }
+
+ procInfo.mArguments.push_back("--"); /* '--version' is a valid directory name. */
+ procInfo.mArguments.push_back(strPath); /* The directory we want to create. */
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(vrc))
+ vrc = GuestProcessTool::run(this, procInfo, prcGuest);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Checks if a directory on the guest exists.
+ *
+ * @returns \c true if directory exists on the guest, \c false if not.
+ * @param strPath Path of directory to check.
+ */
+bool GuestSession::i_directoryExists(const Utf8Str &strPath)
+{
+ GuestFsObjData objDataIgnored;
+ int rcGuestIgnored;
+ int rc = i_directoryQueryInfo(strPath, true /* fFollowSymlinks */, objDataIgnored, &rcGuestIgnored);
+
+ return RT_SUCCESS(rc);
+}
+
+/**
+ * Checks if a directory object exists and optionally returns its object.
+ *
+ * @returns \c true if directory object exists, or \c false if not.
+ * @param uDirID ID of directory object to check.
+ * @param pDir Where to return the found directory object on success.
+ */
+inline bool GuestSession::i_directoryExists(uint32_t uDirID, ComObjPtr<GuestDirectory> *pDir)
+{
+ SessionDirectories::const_iterator it = mData.mDirectories.find(uDirID);
+ if (it != mData.mDirectories.end())
+ {
+ if (pDir)
+ *pDir = it->second;
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Queries information about a directory on the guest.
+ *
+ * @returns VBox status code, or VERR_NOT_A_DIRECTORY if the file system object exists but is not a directory.
+ * @param strPath Path to directory to query information for.
+ * @param fFollowSymlinks Whether to follow symlinks or not.
+ * @param objData Where to store the information returned on success.
+ * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR.
+ */
+int GuestSession::i_directoryQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks,
+ GuestFsObjData &objData, int *prcGuest)
+{
+ AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("strPath=%s, fFollowSymlinks=%RTbool\n", strPath.c_str(), fFollowSymlinks));
+
+ int vrc = i_fsQueryInfo(strPath, fFollowSymlinks, objData, prcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = objData.mType == FsObjType_Directory
+ ? VINF_SUCCESS : VERR_NOT_A_DIRECTORY;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Unregisters a directory object from a guest session.
+ *
+ * @returns VBox status code. VERR_NOT_FOUND if the directory is not registered (anymore).
+ * @param pDirectory Directory object to unregister from session.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_directoryUnregister(GuestDirectory *pDirectory)
+{
+ AssertPtrReturn(pDirectory, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("pDirectory=%p\n", pDirectory));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const uint32_t idObject = pDirectory->getObjectID();
+
+ LogFlowFunc(("Removing directory (objectID=%RU32) ...\n", idObject));
+
+ int rc = i_objectUnregister(idObject);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ SessionDirectories::iterator itDirs = mData.mDirectories.find(idObject);
+ AssertReturn(itDirs != mData.mDirectories.end(), VERR_NOT_FOUND);
+
+ /* Make sure to consume the pointer before the one of the iterator gets released. */
+ ComObjPtr<GuestDirectory> pDirConsumed = pDirectory;
+
+ LogFlowFunc(("Removing directory ID=%RU32 (session %RU32, now total %zu directories)\n",
+ idObject, mData.mSession.mID, mData.mDirectories.size()));
+
+ rc = pDirConsumed->i_onUnregister();
+ AssertRCReturn(rc, rc);
+
+ mData.mDirectories.erase(itDirs);
+
+ alock.release(); /* Release lock before firing off event. */
+
+// ::FireGuestDirectoryRegisteredEvent(mEventSource, this /* Session */, pDirConsumed, false /* Process unregistered */);
+
+ pDirConsumed.setNull();
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Removes a directory on the guest.
+ *
+ * @returns VBox status code.
+ * @param strPath Path of directory on guest to remove.
+ * @param fFlags Directory remove flags to use.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_directoryRemove(const Utf8Str &strPath, uint32_t fFlags, int *prcGuest)
+{
+ AssertReturn(!(fFlags & ~DIRREMOVEREC_FLAG_VALID_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("strPath=%s, uFlags=0x%x\n", strPath.c_str(), fFlags));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ GuestWaitEvent *pEvent = NULL;
+ int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[8];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetPv(&paParms[i++], (void*)strPath.c_str(),
+ (ULONG)strPath.length() + 1);
+ HGCMSvcSetU32(&paParms[i++], fFlags);
+
+ alock.release(); /* Drop lock before sending. */
+
+ vrc = i_sendMessage(HOST_MSG_DIR_REMOVE, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pEvent->Wait(30 * 1000);
+ if ( vrc == VERR_GSTCTL_GUEST_ERROR
+ && prcGuest)
+ *prcGuest = pEvent->GuestResult();
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Creates a temporary directory / file on the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @param strTemplate Name template to use.
+ * \sa RTDirCreateTemp / RTDirCreateTempSecure.
+ * @param strPath Path where to create the temporary directory / file.
+ * @param fDirectory Whether to create a temporary directory or file.
+ * @param strName Where to return the created temporary name on success.
+ * @param fMode File mode to use for creation (octal, umask-style).
+ * Ignored when \a fSecure is specified.
+ * @param fSecure Whether to perform a secure creation or not.
+ * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR.
+ */
+int GuestSession::i_fsCreateTemp(const Utf8Str &strTemplate, const Utf8Str &strPath, bool fDirectory, Utf8Str &strName,
+ uint32_t fMode, bool fSecure, int *prcGuest)
+{
+ AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
+ AssertReturn(fSecure || !(fMode & ~07777), VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("strTemplate=%s, strPath=%s, fDirectory=%RTbool, fMode=%o, fSecure=%RTbool\n",
+ strTemplate.c_str(), strPath.c_str(), fDirectory, fMode, fSecure));
+
+ GuestProcessStartupInfo procInfo;
+ procInfo.mFlags = ProcessCreateFlag_WaitForStdOut;
+ try
+ {
+ procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_MKTEMP);
+ procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */
+ procInfo.mArguments.push_back(Utf8Str("--machinereadable"));
+ if (fDirectory)
+ procInfo.mArguments.push_back(Utf8Str("-d"));
+ if (strPath.length()) /* Otherwise use /tmp or equivalent. */
+ {
+ procInfo.mArguments.push_back(Utf8Str("-t"));
+ procInfo.mArguments.push_back(strPath);
+ }
+ /* Note: Secure flag and mode cannot be specified at the same time. */
+ if (fSecure)
+ {
+ procInfo.mArguments.push_back(Utf8Str("--secure"));
+ }
+ else
+ {
+ procInfo.mArguments.push_back(Utf8Str("--mode"));
+
+ /* Note: Pass the mode unmodified down to the guest. See @ticketref{21394}. */
+ char szMode[16];
+ int vrc2 = RTStrPrintf2(szMode, sizeof(szMode), "%d", fMode);
+ AssertRCReturn(vrc2, vrc2);
+ procInfo.mArguments.push_back(szMode);
+ }
+ procInfo.mArguments.push_back("--"); /* strTemplate could be '--help'. */
+ procInfo.mArguments.push_back(strTemplate);
+ }
+ catch (std::bad_alloc &)
+ {
+ Log(("Out of memory!\n"));
+ return VERR_NO_MEMORY;
+ }
+
+ /** @todo Use an internal HGCM command for this operation, since
+ * we now can run in a user-dedicated session. */
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ GuestCtrlStreamObjects stdOut;
+ int vrc = GuestProcessTool::runEx(this, procInfo, &stdOut, 1 /* cStrmOutObjects */, &vrcGuest);
+ if (!GuestProcess::i_isGuestError(vrc))
+ {
+ GuestFsObjData objData;
+ if (!stdOut.empty())
+ {
+ vrc = objData.FromMkTemp(stdOut.at(0));
+ if (RT_FAILURE(vrc))
+ {
+ vrcGuest = vrc;
+ if (prcGuest)
+ *prcGuest = vrcGuest;
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+ }
+ else
+ vrc = VERR_BROKEN_PIPE;
+
+ if (RT_SUCCESS(vrc))
+ strName = objData.mName;
+ }
+ else if (prcGuest)
+ *prcGuest = vrcGuest;
+
+ LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest));
+ return vrc;
+}
+
+/**
+ * Open a directory on the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @param openInfo Open information to use.
+ * @param pDirectory Where to return the guest directory object on success.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_directoryOpen(const GuestDirectoryOpenInfo &openInfo,
+ ComObjPtr<GuestDirectory> &pDirectory, int *prcGuest)
+{
+ AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("strPath=%s, strPath=%s, uFlags=%x\n",
+ openInfo.mPath.c_str(), openInfo.mFilter.c_str(), openInfo.mFlags));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Create the directory object. */
+ HRESULT hr = pDirectory.createObject();
+ if (FAILED(hr))
+ return Global::vboxStatusCodeFromCOM(hr);
+
+ /* Register a new object ID. */
+ uint32_t idObject;
+ int vrc = i_objectRegister(pDirectory, SESSIONOBJECTTYPE_DIRECTORY, &idObject);
+ if (RT_FAILURE(vrc))
+ {
+ pDirectory.setNull();
+ return vrc;
+ }
+
+ /* We need to release the write lock first before initializing the directory object below,
+ * as we're starting a guest process as part of it. This in turn will try to acquire the session's
+ * write lock. */
+ alock.release();
+
+ Console *pConsole = mParent->i_getConsole();
+ AssertPtr(pConsole);
+
+ vrc = pDirectory->init(pConsole, this /* Parent */, idObject, openInfo);
+ if (RT_FAILURE(vrc))
+ {
+ /* Make sure to acquire the write lock again before unregistering the object. */
+ alock.acquire();
+
+ int vrc2 = i_objectUnregister(idObject);
+ AssertRC(vrc2);
+
+ pDirectory.setNull();
+ }
+ else
+ {
+ /* Make sure to acquire the write lock again before continuing. */
+ alock.acquire();
+
+ try
+ {
+ /* Add the created directory to our map. */
+ mData.mDirectories[idObject] = pDirectory;
+
+ LogFlowFunc(("Added new guest directory \"%s\" (Session: %RU32) (now total %zu directories)\n",
+ openInfo.mPath.c_str(), mData.mSession.mID, mData.mDirectories.size()));
+
+ alock.release(); /* Release lock before firing off event. */
+
+ /** @todo Fire off a VBoxEventType_OnGuestDirectoryRegistered event? */
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Nothing further to do here yet. */
+ if (prcGuest)
+ *prcGuest = VINF_SUCCESS;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Dispatches a host callback to its corresponding object.
+ *
+ * @return VBox status code. VERR_NOT_FOUND if no corresponding object was found.
+ * @param pCtxCb Host callback context.
+ * @param pSvcCb Service callback data.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_dispatchToObject(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
+{
+ LogFlowFunc(("pCtxCb=%p, pSvcCb=%p\n", pCtxCb, pSvcCb));
+
+ AssertPtrReturn(pCtxCb, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Find the object.
+ */
+ int rc = VERR_NOT_FOUND;
+ const uint32_t idObject = VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pCtxCb->uContextID);
+ SessionObjects::const_iterator itObj = mData.mObjects.find(idObject);
+ if (itObj != mData.mObjects.end())
+ {
+ /* Set protocol version so that pSvcCb can be interpreted right. */
+ pCtxCb->uProtocol = mData.mProtocolVersion;
+
+ switch (itObj->second.enmType)
+ {
+ /* Note: The session object is special, as it does not inherit from GuestObject we could call
+ * its dispatcher for -- so treat this separately and call it directly. */
+ case SESSIONOBJECTTYPE_SESSION:
+ {
+ alock.release();
+
+ rc = i_dispatchToThis(pCtxCb, pSvcCb);
+ break;
+ }
+ case SESSIONOBJECTTYPE_DIRECTORY:
+ {
+ ComObjPtr<GuestDirectory> pObj((GuestDirectory *)itObj->second.pObject);
+ AssertReturn(!pObj.isNull(), VERR_INVALID_POINTER);
+
+ alock.release();
+
+ rc = pObj->i_callbackDispatcher(pCtxCb, pSvcCb);
+ break;
+ }
+ case SESSIONOBJECTTYPE_FILE:
+ {
+ ComObjPtr<GuestFile> pObj((GuestFile *)itObj->second.pObject);
+ AssertReturn(!pObj.isNull(), VERR_INVALID_POINTER);
+
+ alock.release();
+
+ rc = pObj->i_callbackDispatcher(pCtxCb, pSvcCb);
+ break;
+ }
+ case SESSIONOBJECTTYPE_PROCESS:
+ {
+ ComObjPtr<GuestProcess> pObj((GuestProcess *)itObj->second.pObject);
+ AssertReturn(!pObj.isNull(), VERR_INVALID_POINTER);
+
+ alock.release();
+
+ rc = pObj->i_callbackDispatcher(pCtxCb, pSvcCb);
+ break;
+ }
+ default:
+ AssertMsgFailed(("%d\n", itObj->second.enmType));
+ rc = VERR_INTERNAL_ERROR_4;
+ break;
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Main handler for guest session messages from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context from HGCM service.
+ * @param pSvcCbData HGCM service callback data.
+ *
+ * @note No locking!
+ */
+int GuestSession::i_dispatchToThis(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("sessionID=%RU32, CID=%RU32, uMessage=%RU32, pSvcCb=%p\n",
+ mData.mSession.mID, pCbCtx->uContextID, pCbCtx->uMessage, pSvcCbData));
+ int rc;
+ switch (pCbCtx->uMessage)
+ {
+ case GUEST_MSG_DISCONNECTED:
+ /** @todo Handle closing all guest objects. */
+ rc = VERR_INTERNAL_ERROR;
+ break;
+
+ case GUEST_MSG_SESSION_NOTIFY: /* Guest Additions >= 4.3.0. */
+ {
+ rc = i_onSessionStatusChange(pCbCtx, pSvcCbData);
+ break;
+ }
+
+ default:
+ rc = dispatchGeneric(pCbCtx, pSvcCbData);
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Validates and extracts file copy flags from a comma-separated string.
+ *
+ * @return COM status, error set on failure
+ * @param strFlags String to extract flags from.
+ * @param fStrict Whether to set an error when an unknown / invalid flag is detected.
+ * @param pfFlags Where to store the extracted (and validated) flags.
+ */
+HRESULT GuestSession::i_fileCopyFlagFromStr(const com::Utf8Str &strFlags, bool fStrict, FileCopyFlag_T *pfFlags)
+{
+ unsigned fFlags = (unsigned)FileCopyFlag_None;
+
+ /* Validate and set flags. */
+ if (strFlags.isNotEmpty())
+ {
+ const char *pszNext = strFlags.c_str();
+ for (;;)
+ {
+ /* Find the next keyword, ignoring all whitespace. */
+ pszNext = RTStrStripL(pszNext);
+
+ const char * const pszComma = strchr(pszNext, ',');
+ size_t cchKeyword = pszComma ? pszComma - pszNext : strlen(pszNext);
+ while (cchKeyword > 0 && RT_C_IS_SPACE(pszNext[cchKeyword - 1]))
+ cchKeyword--;
+
+ if (cchKeyword > 0)
+ {
+ /* Convert keyword to flag. */
+#define MATCH_KEYWORD(a_szKeyword) ( cchKeyword == sizeof(a_szKeyword) - 1U \
+ && memcmp(pszNext, a_szKeyword, sizeof(a_szKeyword) - 1U) == 0)
+ if (MATCH_KEYWORD("NoReplace"))
+ fFlags |= (unsigned)FileCopyFlag_NoReplace;
+ else if (MATCH_KEYWORD("FollowLinks"))
+ fFlags |= (unsigned)FileCopyFlag_FollowLinks;
+ else if (MATCH_KEYWORD("Update"))
+ fFlags |= (unsigned)FileCopyFlag_Update;
+ else if (fStrict)
+ return setError(E_INVALIDARG, tr("Invalid file copy flag: %.*s"), (int)cchKeyword, pszNext);
+#undef MATCH_KEYWORD
+ }
+ if (!pszComma)
+ break;
+ pszNext = pszComma + 1;
+ }
+ }
+
+ if (pfFlags)
+ *pfFlags = (FileCopyFlag_T)fFlags;
+ return S_OK;
+}
+
+/**
+ * Checks if a file object exists and optionally returns its object.
+ *
+ * @returns \c true if file object exists, or \c false if not.
+ * @param uFileID ID of file object to check.
+ * @param pFile Where to return the found file object on success.
+ */
+inline bool GuestSession::i_fileExists(uint32_t uFileID, ComObjPtr<GuestFile> *pFile)
+{
+ SessionFiles::const_iterator it = mData.mFiles.find(uFileID);
+ if (it != mData.mFiles.end())
+ {
+ if (pFile)
+ *pFile = it->second;
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Unregisters a file object from a guest session.
+ *
+ * @returns VBox status code. VERR_NOT_FOUND if the file is not registered (anymore).
+ * @param pFile File object to unregister from session.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_fileUnregister(GuestFile *pFile)
+{
+ AssertPtrReturn(pFile, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("pFile=%p\n", pFile));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const uint32_t idObject = pFile->getObjectID();
+
+ LogFlowFunc(("Removing file (objectID=%RU32) ...\n", idObject));
+
+ int rc = i_objectUnregister(idObject);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ SessionFiles::iterator itFiles = mData.mFiles.find(idObject);
+ AssertReturn(itFiles != mData.mFiles.end(), VERR_NOT_FOUND);
+
+ /* Make sure to consume the pointer before the one of the iterator gets released. */
+ ComObjPtr<GuestFile> pFileConsumed = pFile;
+
+ LogFlowFunc(("Removing file ID=%RU32 (session %RU32, now total %zu files)\n",
+ pFileConsumed->getObjectID(), mData.mSession.mID, mData.mFiles.size()));
+
+ rc = pFileConsumed->i_onUnregister();
+ AssertRCReturn(rc, rc);
+
+ mData.mFiles.erase(itFiles);
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestFileRegisteredEvent(mEventSource, this, pFileConsumed, false /* Unregistered */);
+
+ pFileConsumed.setNull();
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Removes a file from the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @param strPath Path of file on guest to remove.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestSession::i_fileRemove(const Utf8Str &strPath, int *prcGuest)
+{
+ LogFlowThisFunc(("strPath=%s\n", strPath.c_str()));
+
+ GuestProcessStartupInfo procInfo;
+ GuestProcessStream streamOut;
+
+ procInfo.mFlags = ProcessCreateFlag_WaitForStdOut;
+ procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_RM);
+
+ try
+ {
+ procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */
+ procInfo.mArguments.push_back(Utf8Str("--machinereadable"));
+ procInfo.mArguments.push_back("--"); /* strPath could be '--help', which is a valid filename. */
+ procInfo.mArguments.push_back(strPath); /* The file we want to remove. */
+ }
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ GuestCtrlStreamObjects stdOut;
+ int vrc = GuestProcessTool::runEx(this, procInfo, &stdOut, 1 /* cStrmOutObjects */, &vrcGuest);
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ if (!stdOut.empty())
+ {
+ GuestFsObjData objData;
+ vrc = objData.FromRm(stdOut.at(0));
+ if (RT_FAILURE(vrc))
+ {
+ vrcGuest = vrc;
+ if (prcGuest)
+ *prcGuest = vrcGuest;
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+ }
+ else
+ vrc = VERR_BROKEN_PIPE;
+ }
+ else if (prcGuest)
+ *prcGuest = vrcGuest;
+
+ LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest));
+ return vrc;
+}
+
+/**
+ * Opens a file on the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @param aPath File path on guest to open.
+ * @param aAccessMode Access mode to use.
+ * @param aOpenAction Open action to use.
+ * @param aSharingMode Sharing mode to use.
+ * @param aCreationMode Creation mode to use.
+ * @param aFlags Open flags to use.
+ * @param pFile Where to return the file object on success.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_fileOpenEx(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction,
+ FileSharingMode_T aSharingMode, ULONG aCreationMode, const std::vector<FileOpenExFlag_T> &aFlags,
+ ComObjPtr<GuestFile> &pFile, int *prcGuest)
+{
+ GuestFileOpenInfo openInfo;
+ openInfo.mFilename = aPath;
+ openInfo.mCreationMode = aCreationMode;
+ openInfo.mAccessMode = aAccessMode;
+ openInfo.mOpenAction = aOpenAction;
+ openInfo.mSharingMode = aSharingMode;
+
+ /* Combine and validate flags. */
+ for (size_t i = 0; i < aFlags.size(); i++)
+ openInfo.mfOpenEx |= aFlags[i];
+ /* Validation is done in i_fileOpen(). */
+
+ return i_fileOpen(openInfo, pFile, prcGuest);
+}
+
+/**
+ * Opens a file on the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @param openInfo Open information to use for opening the file.
+ * @param pFile Where to return the file object on success.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_fileOpen(const GuestFileOpenInfo &openInfo, ComObjPtr<GuestFile> &pFile, int *prcGuest)
+{
+ LogFlowThisFunc(("strFile=%s, enmAccessMode=0x%x, enmOpenAction=0x%x, uCreationMode=%RU32, mfOpenEx=%RU32\n",
+ openInfo.mFilename.c_str(), openInfo.mAccessMode, openInfo.mOpenAction, openInfo.mCreationMode,
+ openInfo.mfOpenEx));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Guest Additions < 4.3 don't support handling guest files, skip. */
+ if (mData.mProtocolVersion < 2)
+ {
+ if (prcGuest)
+ *prcGuest = VERR_NOT_SUPPORTED;
+ return VERR_GSTCTL_GUEST_ERROR;
+ }
+
+ if (!openInfo.IsValid())
+ return VERR_INVALID_PARAMETER;
+
+ /* Create the directory object. */
+ HRESULT hr = pFile.createObject();
+ if (FAILED(hr))
+ return VERR_COM_UNEXPECTED;
+
+ /* Register a new object ID. */
+ uint32_t idObject;
+ int rc = i_objectRegister(pFile, SESSIONOBJECTTYPE_FILE, &idObject);
+ if (RT_FAILURE(rc))
+ {
+ pFile.setNull();
+ return rc;
+ }
+
+ Console *pConsole = mParent->i_getConsole();
+ AssertPtr(pConsole);
+
+ rc = pFile->init(pConsole, this /* GuestSession */, idObject, openInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Since this is a synchronous guest call we have to
+ * register the file object first, releasing the session's
+ * lock and then proceed with the actual opening command
+ * -- otherwise the file's opening callback would hang
+ * because the session's lock still is in place.
+ */
+ try
+ {
+ /* Add the created file to our vector. */
+ mData.mFiles[idObject] = pFile;
+
+ LogFlowFunc(("Added new guest file \"%s\" (Session: %RU32) (now total %zu files)\n",
+ openInfo.mFilename.c_str(), mData.mSession.mID, mData.mFiles.size()));
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestFileRegisteredEvent(mEventSource, this, pFile, true /* Registered */);
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ rc = pFile->i_openFile(30 * 1000 /* 30s timeout */, &rcGuest);
+ if ( rc == VERR_GSTCTL_GUEST_ERROR
+ && prcGuest)
+ {
+ *prcGuest = rcGuest;
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Queries information from a file on the guest.
+ *
+ * @returns IPRT status code. VERR_NOT_A_FILE if the queried file system object on the guest is not a file,
+ * or VERR_GSTCTL_GUEST_ERROR if prcGuest contains more error information from the guest.
+ * @param strPath Absolute path of file to query information for.
+ * @param fFollowSymlinks Whether or not to follow symbolic links on the guest.
+ * @param objData Where to store the acquired information.
+ * @param prcGuest Where to store the guest rc. Optional.
+ */
+int GuestSession::i_fileQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *prcGuest)
+{
+ LogFlowThisFunc(("strPath=%s fFollowSymlinks=%RTbool\n", strPath.c_str(), fFollowSymlinks));
+
+ int vrc = i_fsQueryInfo(strPath, fFollowSymlinks, objData, prcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = objData.mType == FsObjType_File
+ ? VINF_SUCCESS : VERR_NOT_A_FILE;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Queries the size of a file on the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @retval VERR_
+ * @param strPath Path of file on guest to query size for.
+ * @param fFollowSymlinks \c true when wanting to follow symbolic links, \c false if not.
+ * @param pllSize Where to return the queried file size on success.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestSession::i_fileQuerySize(const Utf8Str &strPath, bool fFollowSymlinks, int64_t *pllSize, int *prcGuest)
+{
+ AssertPtrReturn(pllSize, VERR_INVALID_POINTER);
+
+ GuestFsObjData objData;
+ int vrc = i_fileQueryInfo(strPath, fFollowSymlinks, objData, prcGuest);
+ if (RT_SUCCESS(vrc))
+ *pllSize = objData.mObjectSize;
+
+ return vrc;
+}
+
+/**
+ * Queries information of a file system object (file, directory, ...).
+ *
+ * @return IPRT status code.
+ * @param strPath Path to file system object to query information for.
+ * @param fFollowSymlinks Whether to follow symbolic links or not.
+ * @param objData Where to return the file system object data, if found.
+ * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR.
+ * Any other return code indicates some host side error.
+ */
+int GuestSession::i_fsQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *prcGuest)
+{
+ LogFlowThisFunc(("strPath=%s\n", strPath.c_str()));
+
+ /** @todo Merge this with IGuestFile::queryInfo(). */
+ GuestProcessStartupInfo procInfo;
+ procInfo.mFlags = ProcessCreateFlag_WaitForStdOut;
+ try
+ {
+ procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_STAT);
+ procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */
+ procInfo.mArguments.push_back(Utf8Str("--machinereadable"));
+ if (fFollowSymlinks)
+ procInfo.mArguments.push_back(Utf8Str("-L"));
+ procInfo.mArguments.push_back("--"); /* strPath could be '--help', which is a valid filename. */
+ procInfo.mArguments.push_back(strPath);
+ }
+ catch (std::bad_alloc &)
+ {
+ Log(("Out of memory!\n"));
+ return VERR_NO_MEMORY;
+ }
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ GuestCtrlStreamObjects stdOut;
+ int vrc = GuestProcessTool::runEx(this, procInfo,
+ &stdOut, 1 /* cStrmOutObjects */,
+ &vrcGuest);
+ if (!GuestProcess::i_isGuestError(vrc))
+ {
+ if (!stdOut.empty())
+ {
+ vrc = objData.FromStat(stdOut.at(0));
+ if (RT_FAILURE(vrc))
+ {
+ vrcGuest = vrc;
+ if (prcGuest)
+ *prcGuest = vrcGuest;
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+ }
+ else
+ vrc = VERR_BROKEN_PIPE;
+ }
+ else if (prcGuest)
+ *prcGuest = vrcGuest;
+
+ LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest));
+ return vrc;
+}
+
+/**
+ * Returns the guest credentials of a guest session.
+ *
+ * @returns Guest credentials.
+ */
+const GuestCredentials& GuestSession::i_getCredentials(void)
+{
+ return mData.mCredentials;
+}
+
+/**
+ * Returns the guest session (friendly) name.
+ *
+ * @returns Guest session name.
+ */
+Utf8Str GuestSession::i_getName(void)
+{
+ return mData.mSession.mName;
+}
+
+/**
+ * Returns a stringified error description for a given guest result code.
+ *
+ * @returns Stringified error description.
+ */
+/* static */
+Utf8Str GuestSession::i_guestErrorToString(int rcGuest)
+{
+ Utf8Str strError;
+
+ /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */
+ switch (rcGuest)
+ {
+ case VERR_INVALID_VM_HANDLE:
+ strError.printf(tr("VMM device is not available (is the VM running?)"));
+ break;
+
+ case VERR_HGCM_SERVICE_NOT_FOUND:
+ strError.printf(tr("The guest execution service is not available"));
+ break;
+
+ case VERR_ACCOUNT_RESTRICTED:
+ strError.printf(tr("The specified user account on the guest is restricted and can't be used to logon"));
+ break;
+
+ case VERR_AUTHENTICATION_FAILURE:
+ strError.printf(tr("The specified user was not able to logon on guest"));
+ break;
+
+ case VERR_TIMEOUT:
+ strError.printf(tr("The guest did not respond within time"));
+ break;
+
+ case VERR_CANCELLED:
+ strError.printf(tr("The session operation was canceled"));
+ break;
+
+ case VERR_GSTCTL_MAX_CID_OBJECTS_REACHED:
+ strError.printf(tr("Maximum number of concurrent guest processes has been reached"));
+ break;
+
+ case VERR_NOT_FOUND:
+ strError.printf(tr("The guest execution service is not ready (yet)"));
+ break;
+
+ default:
+ strError.printf("%Rrc", rcGuest);
+ break;
+ }
+
+ return strError;
+}
+
+/**
+ * Returns whether the session is in a started state or not.
+ *
+ * @returns \c true if in a started state, or \c false if not.
+ */
+bool GuestSession::i_isStarted(void) const
+{
+ return (mData.mStatus == GuestSessionStatus_Started);
+}
+
+/**
+ * Checks if this session is ready state where it can handle
+ * all session-bound actions (like guest processes, guest files).
+ * Only used by official API methods. Will set an external
+ * error when not ready.
+ */
+HRESULT GuestSession::i_isStartedExternal(void)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo Be a bit more informative. */
+ if (!i_isStarted())
+ return setError(E_UNEXPECTED, tr("Session is not in started state"));
+
+ return S_OK;
+}
+
+/**
+ * Returns whether a guest session status implies a terminated state or not.
+ *
+ * @returns \c true if it's a terminated state, or \c false if not.
+ */
+/* static */
+bool GuestSession::i_isTerminated(GuestSessionStatus_T enmStatus)
+{
+ switch (enmStatus)
+ {
+ case GuestSessionStatus_Terminated:
+ RT_FALL_THROUGH();
+ case GuestSessionStatus_TimedOutKilled:
+ RT_FALL_THROUGH();
+ case GuestSessionStatus_TimedOutAbnormally:
+ RT_FALL_THROUGH();
+ case GuestSessionStatus_Down:
+ RT_FALL_THROUGH();
+ case GuestSessionStatus_Error:
+ return true;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * Returns whether the session is in a terminated state or not.
+ *
+ * @returns \c true if in a terminated state, or \c false if not.
+ */
+bool GuestSession::i_isTerminated(void) const
+{
+ return GuestSession::i_isTerminated(mData.mStatus);
+}
+
+/**
+ * Called by IGuest right before this session gets removed from
+ * the public session list.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_onRemove(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = i_objectsUnregister();
+
+ /*
+ * Note: The event source stuff holds references to this object,
+ * so make sure that this is cleaned up *before* calling uninit.
+ */
+ if (!mEventSource.isNull())
+ {
+ mEventSource->UnregisterListener(mLocalListener);
+
+ mLocalListener.setNull();
+ unconst(mEventSource).setNull();
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Handles guest session status changes from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context from HGCM service.
+ * @param pSvcCbData HGCM service callback data.
+ *
+ * @note Takes the read lock (for session ID lookup).
+ */
+int GuestSession::i_onSessionStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ /* pCallback is optional. */
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ if (pSvcCbData->mParms < 3)
+ return VERR_INVALID_PARAMETER;
+
+ CALLBACKDATA_SESSION_NOTIFY dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uType);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uResult);
+ AssertRCReturn(vrc, vrc);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("ID=%RU32, uType=%RU32, rcGuest=%Rrc\n",
+ mData.mSession.mID, dataCb.uType, dataCb.uResult));
+
+ GuestSessionStatus_T sessionStatus = GuestSessionStatus_Undefined;
+
+ int rcGuest = dataCb.uResult; /** @todo uint32_t vs. int. */
+ switch (dataCb.uType)
+ {
+ case GUEST_SESSION_NOTIFYTYPE_ERROR:
+ sessionStatus = GuestSessionStatus_Error;
+ LogRel(("Guest Control: Error starting Session '%s' (%Rrc) \n", mData.mSession.mName.c_str(), rcGuest));
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_STARTED:
+ sessionStatus = GuestSessionStatus_Started;
+#if 0 /** @todo If we get some environment stuff along with this kind notification: */
+ const char *pszzEnvBlock = ...;
+ uint32_t cbEnvBlock = ...;
+ if (!mData.mpBaseEnvironment)
+ {
+ GuestEnvironment *pBaseEnv;
+ try { pBaseEnv = new GuestEnvironment(); } catch (std::bad_alloc &) { pBaseEnv = NULL; }
+ if (pBaseEnv)
+ {
+ int vrc = pBaseEnv->initNormal(Guest.i_isGuestInWindowsNtFamily() ? RTENV_CREATE_F_ALLOW_EQUAL_FIRST_IN_VAR : 0);
+ if (RT_SUCCESS(vrc))
+ vrc = pBaseEnv->copyUtf8Block(pszzEnvBlock, cbEnvBlock);
+ if (RT_SUCCESS(vrc))
+ mData.mpBaseEnvironment = pBaseEnv;
+ else
+ pBaseEnv->release();
+ }
+ }
+#endif
+ LogRel(("Guest Control: Session '%s' was successfully started\n", mData.mSession.mName.c_str()));
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_TEN:
+ LogRel(("Guest Control: Session '%s' was terminated normally with exit code %#x\n",
+ mData.mSession.mName.c_str(), dataCb.uResult));
+ sessionStatus = GuestSessionStatus_Terminated;
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_TEA:
+ LogRel(("Guest Control: Session '%s' was terminated abnormally\n", mData.mSession.mName.c_str()));
+ sessionStatus = GuestSessionStatus_Terminated;
+ /* dataCb.uResult is undefined. */
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_TES:
+ LogRel(("Guest Control: Session '%s' was terminated via signal %#x\n", mData.mSession.mName.c_str(), dataCb.uResult));
+ sessionStatus = GuestSessionStatus_Terminated;
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_TOK:
+ sessionStatus = GuestSessionStatus_TimedOutKilled;
+ LogRel(("Guest Control: Session '%s' timed out and was killed\n", mData.mSession.mName.c_str()));
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_TOA:
+ sessionStatus = GuestSessionStatus_TimedOutAbnormally;
+ LogRel(("Guest Control: Session '%s' timed out and was not killed successfully\n", mData.mSession.mName.c_str()));
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_DWN:
+ sessionStatus = GuestSessionStatus_Down;
+ LogRel(("Guest Control: Session '%s' got killed as guest service/OS is down\n", mData.mSession.mName.c_str()));
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_UNDEFINED:
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ /* Leave the lock, as i_setSessionStatus() below will require a write lock for actually
+ * committing the session state. */
+ alock.release();
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (RT_FAILURE(rcGuest))
+ sessionStatus = GuestSessionStatus_Error;
+ }
+
+ /* Set the session status. */
+ if (RT_SUCCESS(vrc))
+ vrc = i_setSessionStatus(sessionStatus, rcGuest);
+
+ LogFlowThisFunc(("ID=%RU32, rcGuest=%Rrc\n", mData.mSession.mID, rcGuest));
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Returns the path separation style used on the guest.
+ *
+ * @returns Separation style used on the guest.
+ */
+PathStyle_T GuestSession::i_getGuestPathStyle(void)
+{
+ PathStyle_T enmPathStyle;
+
+ VBOXOSTYPE enmOsType = mParent->i_getGuestOSType();
+ if (enmOsType < VBOXOSTYPE_DOS)
+ {
+ LogFlowFunc(("returns PathStyle_Unknown\n"));
+ enmPathStyle = PathStyle_Unknown;
+ }
+ else if (enmOsType < VBOXOSTYPE_Linux)
+ {
+ LogFlowFunc(("returns PathStyle_DOS\n"));
+ enmPathStyle = PathStyle_DOS;
+ }
+ else
+ {
+ LogFlowFunc(("returns PathStyle_UNIX\n"));
+ enmPathStyle = PathStyle_UNIX;
+ }
+
+ return enmPathStyle;
+}
+
+/**
+ * Returns the path separation style used on the host.
+ *
+ * @returns Separation style used on the host.
+ */
+/* static */
+PathStyle_T GuestSession::i_getHostPathStyle(void)
+{
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ return PathStyle_DOS;
+#else
+ return PathStyle_UNIX;
+#endif
+}
+
+/**
+ * Starts the guest session on the guest.
+ *
+ * @returns VBox status code.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the read and write locks.
+ */
+int GuestSession::i_startSession(int *prcGuest)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("mID=%RU32, mName=%s, uProtocolVersion=%RU32, openFlags=%x, openTimeoutMS=%RU32\n",
+ mData.mSession.mID, mData.mSession.mName.c_str(), mData.mProtocolVersion,
+ mData.mSession.mOpenFlags, mData.mSession.mOpenTimeoutMS));
+
+ /* Guest Additions < 4.3 don't support opening dedicated
+ guest sessions. Simply return success here. */
+ if (mData.mProtocolVersion < 2)
+ {
+ alock.release(); /* Release lock before changing status. */
+
+ /* ignore rc */ i_setSessionStatus(GuestSessionStatus_Started, VINF_SUCCESS);
+ LogFlowThisFunc(("Installed Guest Additions don't support opening dedicated sessions, skipping\n"));
+ return VINF_SUCCESS;
+ }
+
+ if (mData.mStatus != GuestSessionStatus_Undefined)
+ return VINF_SUCCESS;
+
+ /** @todo mData.mSession.uFlags validation. */
+
+ alock.release(); /* Release lock before changing status. */
+
+ /* Set current session status. */
+ int vrc = i_setSessionStatus(GuestSessionStatus_Starting, VINF_SUCCESS);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged);
+
+ vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ alock.acquire(); /* Re-acquire lock before accessing session attributes below. */
+
+ VBOXHGCMSVCPARM paParms[8];
+
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mData.mProtocolVersion);
+ HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mUser.c_str(),
+ (ULONG)mData.mCredentials.mUser.length() + 1);
+ HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mPassword.c_str(),
+ (ULONG)mData.mCredentials.mPassword.length() + 1);
+ HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mDomain.c_str(),
+ (ULONG)mData.mCredentials.mDomain.length() + 1);
+ HGCMSvcSetU32(&paParms[i++], mData.mSession.mOpenFlags);
+
+ alock.release(); /* Drop lock before sending. */
+
+ vrc = i_sendMessage(HOST_MSG_SESSION_CREATE, i, paParms, VBOX_GUESTCTRL_DST_ROOT_SVC);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = i_waitForStatusChange(pEvent, GuestSessionWaitForFlag_Start,
+ 30 * 1000 /* 30s timeout */,
+ NULL /* Session status */, prcGuest);
+ }
+ else
+ {
+ /*
+ * Unable to start guest session - update its current state.
+ * Since there is no (official API) way to recover a failed guest session
+ * this also marks the end state. Internally just calling this
+ * same function again will work though.
+ */
+ /* ignore rc */ i_setSessionStatus(GuestSessionStatus_Error, vrc);
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Starts the guest session asynchronously in a separate worker thread.
+ *
+ * @returns IPRT status code.
+ */
+int GuestSession::i_startSessionAsync(void)
+{
+ LogFlowThisFuncEnter();
+
+ /* Create task: */
+ GuestSessionTaskInternalStart *pTask = NULL;
+ try
+ {
+ pTask = new GuestSessionTaskInternalStart(this);
+ }
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+ if (pTask->isOk())
+ {
+ /* Kick off the thread: */
+ HRESULT hrc = pTask->createThread();
+ pTask = NULL; /* Not valid anymore, not even on failure! */
+ if (SUCCEEDED(hrc))
+ {
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+ }
+ LogFlow(("GuestSession: Failed to create thread for GuestSessionTaskInternalOpen task.\n"));
+ }
+ else
+ LogFlow(("GuestSession: GuestSessionTaskInternalStart creation failed: %Rhrc.\n", pTask->rc()));
+ LogFlowFuncLeaveRC(VERR_GENERAL_FAILURE);
+ return VERR_GENERAL_FAILURE;
+}
+
+/**
+ * Static function to start a guest session asynchronously.
+ *
+ * @returns IPRT status code.
+ * @param pTask Task object to use for starting the guest session.
+ */
+/* static */
+int GuestSession::i_startSessionThreadTask(GuestSessionTaskInternalStart *pTask)
+{
+ LogFlowFunc(("pTask=%p\n", pTask));
+ AssertPtr(pTask);
+
+ const ComObjPtr<GuestSession> pSession(pTask->Session());
+ Assert(!pSession.isNull());
+
+ AutoCaller autoCaller(pSession);
+ if (FAILED(autoCaller.rc()))
+ return VERR_COM_INVALID_OBJECT_STATE;
+
+ int vrc = pSession->i_startSession(NULL /* Guest rc, ignored */);
+ /* Nothing to do here anymore. */
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Registers an object with the session, i.e. allocates an object ID.
+ *
+ * @return VBox status code.
+ * @retval VERR_GSTCTL_MAX_OBJECTS_REACHED if the maximum of concurrent objects
+ * is reached.
+ * @param pObject Guest object to register (weak pointer). Optional.
+ * @param enmType Session object type to register.
+ * @param pidObject Where to return the object ID on success. Optional.
+ */
+int GuestSession::i_objectRegister(GuestObject *pObject, SESSIONOBJECTTYPE enmType, uint32_t *pidObject)
+{
+ /* pObject can be NULL. */
+ /* pidObject is optional. */
+
+ /*
+ * Pick a random bit as starting point. If it's in use, search forward
+ * for a free one, wrapping around. We've reserved both the zero'th and
+ * max-1 IDs (see Data constructor).
+ */
+ uint32_t idObject = RTRandU32Ex(1, VBOX_GUESTCTRL_MAX_OBJECTS - 2);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (!ASMBitTestAndSet(&mData.bmObjectIds[0], idObject))
+ { /* likely */ }
+ else if (mData.mObjects.size() < VBOX_GUESTCTRL_MAX_OBJECTS - 2 /* First and last are not used */)
+ {
+ /* Forward search. */
+ int iHit = ASMBitNextClear(&mData.bmObjectIds[0], VBOX_GUESTCTRL_MAX_OBJECTS, idObject);
+ if (iHit < 0)
+ iHit = ASMBitFirstClear(&mData.bmObjectIds[0], VBOX_GUESTCTRL_MAX_OBJECTS);
+ AssertLogRelMsgReturn(iHit >= 0, ("object count: %#zu\n", mData.mObjects.size()), VERR_GSTCTL_MAX_CID_OBJECTS_REACHED);
+ idObject = iHit;
+ AssertLogRelMsgReturn(!ASMBitTestAndSet(&mData.bmObjectIds[0], idObject), ("idObject=%#x\n", idObject), VERR_INTERNAL_ERROR_2);
+ }
+ else
+ {
+ LogFunc(("Maximum number of objects reached (enmType=%RU32, %zu objects)\n", enmType, mData.mObjects.size()));
+ return VERR_GSTCTL_MAX_CID_OBJECTS_REACHED;
+ }
+
+ Log2Func(("enmType=%RU32 -> idObject=%RU32 (%zu objects)\n", enmType, idObject, mData.mObjects.size()));
+
+ try
+ {
+ mData.mObjects[idObject].pObject = pObject; /* Can be NULL. */
+ mData.mObjects[idObject].enmType = enmType;
+ mData.mObjects[idObject].msBirth = RTTimeMilliTS();
+ }
+ catch (std::bad_alloc &)
+ {
+ ASMBitClear(&mData.bmObjectIds[0], idObject);
+ return VERR_NO_MEMORY;
+ }
+
+ if (pidObject)
+ *pidObject = idObject;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unregisters an object from the session objects list.
+ *
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_NOT_FOUND if the object ID was not found.
+ * @param idObject Object ID to unregister.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_objectUnregister(uint32_t idObject)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int rc = VINF_SUCCESS;
+ AssertMsgStmt(ASMBitTestAndClear(&mData.bmObjectIds, idObject), ("idObject=%#x\n", idObject), rc = VERR_NOT_FOUND);
+
+ SessionObjects::iterator ItObj = mData.mObjects.find(idObject);
+ AssertMsgReturn(ItObj != mData.mObjects.end(), ("idObject=%#x\n", idObject), VERR_NOT_FOUND);
+ mData.mObjects.erase(ItObj);
+
+ return rc;
+}
+
+/**
+ * Unregisters all objects from the session list.
+ *
+ * @returns VBox status code.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_objectsUnregister(void)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("Unregistering directories (%zu total)\n", mData.mDirectories.size()));
+
+ SessionDirectories::iterator itDirs;
+ while ((itDirs = mData.mDirectories.begin()) != mData.mDirectories.end())
+ {
+ alock.release();
+ i_directoryUnregister(itDirs->second);
+ alock.acquire();
+ }
+
+ Assert(mData.mDirectories.size() == 0);
+ mData.mDirectories.clear();
+
+ LogFlowThisFunc(("Unregistering files (%zu total)\n", mData.mFiles.size()));
+
+ SessionFiles::iterator itFiles;
+ while ((itFiles = mData.mFiles.begin()) != mData.mFiles.end())
+ {
+ alock.release();
+ i_fileUnregister(itFiles->second);
+ alock.acquire();
+ }
+
+ Assert(mData.mFiles.size() == 0);
+ mData.mFiles.clear();
+
+ LogFlowThisFunc(("Unregistering processes (%zu total)\n", mData.mProcesses.size()));
+
+ SessionProcesses::iterator itProcs;
+ while ((itProcs = mData.mProcesses.begin()) != mData.mProcesses.end())
+ {
+ alock.release();
+ i_processUnregister(itProcs->second);
+ alock.acquire();
+ }
+
+ Assert(mData.mProcesses.size() == 0);
+ mData.mProcesses.clear();
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Notifies all registered objects about a guest session status change.
+ *
+ * @returns VBox status code.
+ * @param enmSessionStatus Session status to notify objects about.
+ */
+int GuestSession::i_objectsNotifyAboutStatusChange(GuestSessionStatus_T enmSessionStatus)
+{
+ LogFlowThisFunc(("enmSessionStatus=%RU32\n", enmSessionStatus));
+
+ int vrc = VINF_SUCCESS;
+
+ SessionObjects::iterator itObjs = mData.mObjects.begin();
+ while (itObjs != mData.mObjects.end())
+ {
+ GuestObject *pObj = itObjs->second.pObject;
+ if (pObj) /* pObject can be NULL (weak pointer). */
+ {
+ int vrc2 = pObj->i_onSessionStatusChange(enmSessionStatus);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ /* If the session got terminated, make sure to cancel all wait events for
+ * the current object. */
+ if (i_isTerminated())
+ pObj->cancelWaitEvents();
+ }
+
+ ++itObjs;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Renames a path on the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @param strSource Source path on guest to rename.
+ * @param strDest Destination path on guest to rename \a strSource to.
+ * @param uFlags Renaming flags.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ * @note Takes the read lock.
+ */
+int GuestSession::i_pathRename(const Utf8Str &strSource, const Utf8Str &strDest, uint32_t uFlags, int *prcGuest)
+{
+ AssertReturn(!(uFlags & ~PATHRENAME_FLAG_VALID_MASK), VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("strSource=%s, strDest=%s, uFlags=0x%x\n",
+ strSource.c_str(), strDest.c_str(), uFlags));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ GuestWaitEvent *pEvent = NULL;
+ int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[8];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetPv(&paParms[i++], (void*)strSource.c_str(),
+ (ULONG)strSource.length() + 1);
+ HGCMSvcSetPv(&paParms[i++], (void*)strDest.c_str(),
+ (ULONG)strDest.length() + 1);
+ HGCMSvcSetU32(&paParms[i++], uFlags);
+
+ alock.release(); /* Drop lock before sending. */
+
+ vrc = i_sendMessage(HOST_MSG_PATH_RENAME, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pEvent->Wait(30 * 1000);
+ if ( vrc == VERR_GSTCTL_GUEST_ERROR
+ && prcGuest)
+ *prcGuest = pEvent->GuestResult();
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Returns the user's absolute documents path, if any.
+ *
+ * @returns VBox status code.
+ * @param strPath Where to store the user's document path.
+ * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR.
+ * Any other return code indicates some host side error.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_pathUserDocuments(Utf8Str &strPath, int *prcGuest)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo Cache the user's document path? */
+
+ GuestWaitEvent *pEvent = NULL;
+ int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[2];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+
+ alock.release(); /* Drop lock before sending. */
+
+ vrc = i_sendMessage(HOST_MSG_PATH_USER_DOCUMENTS, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pEvent->Wait(30 * 1000);
+ if (RT_SUCCESS(vrc))
+ {
+ strPath = pEvent->Payload().ToString();
+ }
+ else
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ {
+ if (prcGuest)
+ *prcGuest = pEvent->GuestResult();
+ }
+ }
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Returns the user's absolute home path, if any.
+ *
+ * @returns VBox status code.
+ * @param strPath Where to store the user's home path.
+ * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR.
+ * Any other return code indicates some host side error.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_pathUserHome(Utf8Str &strPath, int *prcGuest)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo Cache the user's home path? */
+
+ GuestWaitEvent *pEvent = NULL;
+ int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[2];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+
+ alock.release(); /* Drop lock before sending. */
+
+ vrc = i_sendMessage(HOST_MSG_PATH_USER_HOME, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pEvent->Wait(30 * 1000);
+ if (RT_SUCCESS(vrc))
+ {
+ strPath = pEvent->Payload().ToString();
+ }
+ else
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ {
+ if (prcGuest)
+ *prcGuest = pEvent->GuestResult();
+ }
+ }
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Unregisters a process object from a guest session.
+ *
+ * @returns VBox status code. VERR_NOT_FOUND if the process is not registered (anymore).
+ * @param pProcess Process object to unregister from session.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_processUnregister(GuestProcess *pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("pProcess=%p\n", pProcess));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const uint32_t idObject = pProcess->getObjectID();
+
+ LogFlowFunc(("Removing process (objectID=%RU32) ...\n", idObject));
+
+ int rc = i_objectUnregister(idObject);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ SessionProcesses::iterator itProcs = mData.mProcesses.find(idObject);
+ AssertReturn(itProcs != mData.mProcesses.end(), VERR_NOT_FOUND);
+
+ /* Make sure to consume the pointer before the one of the iterator gets released. */
+ ComObjPtr<GuestProcess> pProc = pProcess;
+
+ ULONG uPID;
+ HRESULT hr = pProc->COMGETTER(PID)(&uPID);
+ ComAssertComRC(hr);
+
+ LogFlowFunc(("Removing process ID=%RU32 (session %RU32, guest PID %RU32, now total %zu processes)\n",
+ idObject, mData.mSession.mID, uPID, mData.mProcesses.size()));
+
+ rc = pProcess->i_onUnregister();
+ AssertRCReturn(rc, rc);
+
+ mData.mProcesses.erase(itProcs);
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestProcessRegisteredEvent(mEventSource, this /* Session */, pProc, uPID, false /* Process unregistered */);
+
+ pProc.setNull();
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Creates but does *not* start the process yet.
+ *
+ * See GuestProcess::startProcess() or GuestProcess::startProcessAsync() for
+ * starting the process.
+ *
+ * @returns IPRT status code.
+ * @param procInfo Process startup info to use for starting the process.
+ * @param pProcess Where to return the created guest process object on success.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_processCreateEx(GuestProcessStartupInfo &procInfo, ComObjPtr<GuestProcess> &pProcess)
+{
+ LogFlowFunc(("mExe=%s, mFlags=%x, mTimeoutMS=%RU32\n",
+ procInfo.mExecutable.c_str(), procInfo.mFlags, procInfo.mTimeoutMS));
+#ifdef DEBUG
+ if (procInfo.mArguments.size())
+ {
+ LogFlowFunc(("Arguments:"));
+ ProcessArguments::const_iterator it = procInfo.mArguments.begin();
+ while (it != procInfo.mArguments.end())
+ {
+ LogFlow((" %s", (*it).c_str()));
+ ++it;
+ }
+ LogFlow(("\n"));
+ }
+#endif
+
+ /* Validate flags. */
+ if (procInfo.mFlags)
+ {
+ if ( !(procInfo.mFlags & ProcessCreateFlag_IgnoreOrphanedProcesses)
+ && !(procInfo.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
+ && !(procInfo.mFlags & ProcessCreateFlag_Hidden)
+ && !(procInfo.mFlags & ProcessCreateFlag_Profile)
+ && !(procInfo.mFlags & ProcessCreateFlag_WaitForStdOut)
+ && !(procInfo.mFlags & ProcessCreateFlag_WaitForStdErr))
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+ }
+
+ if ( (procInfo.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
+ && ( (procInfo.mFlags & ProcessCreateFlag_WaitForStdOut)
+ || (procInfo.mFlags & ProcessCreateFlag_WaitForStdErr)
+ )
+ )
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (procInfo.mPriority)
+ {
+ if (!(procInfo.mPriority & ProcessPriority_Default))
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Adjust timeout.
+ * If set to 0, we define an infinite timeout (unlimited process run time). */
+ if (procInfo.mTimeoutMS == 0)
+ procInfo.mTimeoutMS = UINT32_MAX;
+
+ /** @todo Implement process priority + affinity. */
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Create the process object. */
+ HRESULT hr = pProcess.createObject();
+ if (FAILED(hr))
+ return VERR_COM_UNEXPECTED;
+
+ /* Register a new object ID. */
+ uint32_t idObject;
+ int rc = i_objectRegister(pProcess, SESSIONOBJECTTYPE_PROCESS, &idObject);
+ if (RT_FAILURE(rc))
+ {
+ pProcess.setNull();
+ return rc;
+ }
+
+ rc = pProcess->init(mParent->i_getConsole() /* Console */, this /* Session */, idObject,
+ procInfo, mData.mpBaseEnvironment);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Add the created process to our map. */
+ try
+ {
+ mData.mProcesses[idObject] = pProcess;
+
+ LogFlowFunc(("Added new process (Session: %RU32) with process ID=%RU32 (now total %zu processes)\n",
+ mData.mSession.mID, idObject, mData.mProcesses.size()));
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestProcessRegisteredEvent(mEventSource, this /* Session */, pProcess, 0 /* PID */, true /* Process registered */);
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Checks if a process object exists and optionally returns its object.
+ *
+ * @returns \c true if process object exists, or \c false if not.
+ * @param uProcessID ID of process object to check.
+ * @param pProcess Where to return the found process object on success.
+ *
+ * @note No locking done!
+ */
+inline bool GuestSession::i_processExists(uint32_t uProcessID, ComObjPtr<GuestProcess> *pProcess)
+{
+ SessionProcesses::const_iterator it = mData.mProcesses.find(uProcessID);
+ if (it != mData.mProcesses.end())
+ {
+ if (pProcess)
+ *pProcess = it->second;
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Returns the process object from a guest PID.
+ *
+ * @returns VBox status code.
+ * @param uPID Guest PID to get process object for.
+ * @param pProcess Where to return the process object on success.
+ *
+ * @note No locking done!
+ */
+inline int GuestSession::i_processGetByPID(ULONG uPID, ComObjPtr<GuestProcess> *pProcess)
+{
+ AssertReturn(uPID, false);
+ /* pProcess is optional. */
+
+ SessionProcesses::iterator itProcs = mData.mProcesses.begin();
+ for (; itProcs != mData.mProcesses.end(); ++itProcs)
+ {
+ ComObjPtr<GuestProcess> pCurProc = itProcs->second;
+ AutoCaller procCaller(pCurProc);
+ if (!procCaller.isOk())
+ return VERR_COM_INVALID_OBJECT_STATE;
+
+ ULONG uCurPID;
+ HRESULT hr = pCurProc->COMGETTER(PID)(&uCurPID);
+ ComAssertComRC(hr);
+
+ if (uCurPID == uPID)
+ {
+ if (pProcess)
+ *pProcess = pCurProc;
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * Sends a message to the HGCM host service.
+ *
+ * @returns VBox status code.
+ * @param uMessage Message ID to send.
+ * @param uParms Number of parameters in \a paParms to send.
+ * @param paParms Array of HGCM parameters to send.
+ * @param fDst Host message destination flags of type VBOX_GUESTCTRL_DST_XXX.
+ */
+int GuestSession::i_sendMessage(uint32_t uMessage, uint32_t uParms, PVBOXHGCMSVCPARM paParms,
+ uint64_t fDst /*= VBOX_GUESTCTRL_DST_SESSION*/)
+{
+ LogFlowThisFuncEnter();
+
+#ifndef VBOX_GUESTCTRL_TEST_CASE
+ ComObjPtr<Console> pConsole = mParent->i_getConsole();
+ Assert(!pConsole.isNull());
+
+ /* Forward the information to the VMM device. */
+ VMMDev *pVMMDev = pConsole->i_getVMMDev();
+ AssertPtr(pVMMDev);
+
+ LogFlowThisFunc(("uMessage=%RU32 (%s), uParms=%RU32\n", uMessage, GstCtrlHostMsgtoStr((guestControl::eHostMsg)uMessage), uParms));
+
+ /* HACK ALERT! We extend the first parameter to 64-bit and use the
+ two topmost bits for call destination information. */
+ Assert(fDst == VBOX_GUESTCTRL_DST_SESSION || fDst == VBOX_GUESTCTRL_DST_ROOT_SVC || fDst == VBOX_GUESTCTRL_DST_BOTH);
+ Assert(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT);
+ paParms[0].type = VBOX_HGCM_SVC_PARM_64BIT;
+ paParms[0].u.uint64 = (uint64_t)paParms[0].u.uint32 | fDst;
+
+ /* Make the call. */
+ int vrc = pVMMDev->hgcmHostCall(HGCMSERVICE_NAME, uMessage, uParms, paParms);
+ if (RT_FAILURE(vrc))
+ {
+ /** @todo What to do here? */
+ }
+#else
+ /* Not needed within testcases. */
+ int vrc = VINF_SUCCESS;
+#endif
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sets the guest session's current status.
+ *
+ * @returns VBox status code.
+ * @param sessionStatus Session status to set.
+ * @param sessionRc Session result to set (for error handling).
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_setSessionStatus(GuestSessionStatus_T sessionStatus, int sessionRc)
+{
+ LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, sessionRc=%Rrc\n",
+ mData.mStatus, sessionStatus, sessionRc));
+
+ if (sessionStatus == GuestSessionStatus_Error)
+ {
+ AssertMsg(RT_FAILURE(sessionRc), ("Guest rc must be an error (%Rrc)\n", sessionRc));
+ /* Do not allow overwriting an already set error. If this happens
+ * this means we forgot some error checking/locking somewhere. */
+ AssertMsg(RT_SUCCESS(mData.mRC), ("Guest rc already set (to %Rrc)\n", mData.mRC));
+ }
+ else
+ AssertMsg(RT_SUCCESS(sessionRc), ("Guest rc must not be an error (%Rrc)\n", sessionRc));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+
+ if (mData.mStatus != sessionStatus)
+ {
+ mData.mStatus = sessionStatus;
+ mData.mRC = sessionRc;
+
+ /* Make sure to notify all underlying objects first. */
+ vrc = i_objectsNotifyAboutStatusChange(sessionStatus);
+
+ ComObjPtr<VirtualBoxErrorInfo> errorInfo;
+ HRESULT hr = errorInfo.createObject();
+ ComAssertComRC(hr);
+ int rc2 = errorInfo->initEx(VBOX_E_IPRT_ERROR, sessionRc,
+ COM_IIDOF(IGuestSession), getComponentName(),
+ i_guestErrorToString(sessionRc));
+ AssertRC(rc2);
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestSessionStateChangedEvent(mEventSource, this, mData.mSession.mID, sessionStatus, errorInfo);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/** @todo Unused --remove? */
+int GuestSession::i_signalWaiters(GuestSessionWaitResult_T enmWaitResult, int rc /*= VINF_SUCCESS */)
+{
+ RT_NOREF(enmWaitResult, rc);
+
+ /*LogFlowThisFunc(("enmWaitResult=%d, rc=%Rrc, mWaitCount=%RU32, mWaitEvent=%p\n",
+ enmWaitResult, rc, mData.mWaitCount, mData.mWaitEvent));*/
+
+ /* Note: No write locking here -- already done in the caller. */
+
+ int vrc = VINF_SUCCESS;
+ /*if (mData.mWaitEvent)
+ vrc = mData.mWaitEvent->Signal(enmWaitResult, rc);*/
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Shuts down (and optionally powers off / reboots) the guest.
+ * Needs supported Guest Additions installed.
+ *
+ * @returns VBox status code. VERR_NOT_SUPPORTED if not supported by Guest Additions.
+ * @param fFlags Guest shutdown flags.
+ * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR.
+ * Any other return code indicates some host side error.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_shutdown(uint32_t fFlags, int *prcGuest)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertPtrReturn(mParent, VERR_INVALID_POINTER);
+ if (!(mParent->i_getGuestControlFeatures0() & VBOX_GUESTCTRL_GF_0_SHUTDOWN))
+ return VERR_NOT_SUPPORTED;
+
+ LogRel(("Guest Control: Shutting down guest (flags = %#x) ...\n", fFlags));
+
+ GuestWaitEvent *pEvent = NULL;
+ int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[2];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], fFlags);
+
+ alock.release(); /* Drop lock before sending. */
+
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+
+ vrc = i_sendMessage(HOST_MSG_SHUTDOWN, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pEvent->Wait(30 * 1000);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ rcGuest = pEvent->GuestResult();
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Guest Control: Shutting down guest failed, rc=%Rrc\n",
+ vrc == VERR_GSTCTL_GUEST_ERROR ? rcGuest : vrc));
+
+ if ( vrc == VERR_GSTCTL_GUEST_ERROR
+ && prcGuest)
+ *prcGuest = rcGuest;
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Determines the protocol version (sets mData.mProtocolVersion).
+ *
+ * This is called from the init method prior to to establishing a guest
+ * session.
+ *
+ * @returns VBox status code.
+ */
+int GuestSession::i_determineProtocolVersion(void)
+{
+ /*
+ * We currently do this based on the reported Guest Additions version,
+ * ASSUMING that VBoxService and VBoxDrv are at the same version.
+ */
+ ComObjPtr<Guest> pGuest = mParent;
+ AssertReturn(!pGuest.isNull(), VERR_NOT_SUPPORTED);
+ uint32_t uGaVersion = pGuest->i_getAdditionsVersion();
+
+ /* Everyone supports version one, if they support anything at all. */
+ mData.mProtocolVersion = 1;
+
+ /* Guest control 2.0 was introduced with 4.3.0. */
+ if (uGaVersion >= VBOX_FULL_VERSION_MAKE(4,3,0))
+ mData.mProtocolVersion = 2; /* Guest control 2.0. */
+
+ LogFlowThisFunc(("uGaVersion=%u.%u.%u => mProtocolVersion=%u\n",
+ VBOX_FULL_VERSION_GET_MAJOR(uGaVersion), VBOX_FULL_VERSION_GET_MINOR(uGaVersion),
+ VBOX_FULL_VERSION_GET_BUILD(uGaVersion), mData.mProtocolVersion));
+
+ /*
+ * Inform the user about outdated Guest Additions (VM release log).
+ */
+ if (mData.mProtocolVersion < 2)
+ LogRelMax(3, ("Warning: Guest Additions v%u.%u.%u only supports the older guest control protocol version %u.\n"
+ " Please upgrade GAs to the current version to get full guest control capabilities.\n",
+ VBOX_FULL_VERSION_GET_MAJOR(uGaVersion), VBOX_FULL_VERSION_GET_MINOR(uGaVersion),
+ VBOX_FULL_VERSION_GET_BUILD(uGaVersion), mData.mProtocolVersion));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Waits for guest session events.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @retval VERR_TIMEOUT when a timeout has occurred.
+ * @param fWaitFlags Wait flags to use.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param waitResult Where to return the wait result on success.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS, GuestSessionWaitResult_T &waitResult, int *prcGuest)
+{
+ LogFlowThisFuncEnter();
+
+ AssertReturn(fWaitFlags, VERR_INVALID_PARAMETER);
+
+ /*LogFlowThisFunc(("fWaitFlags=0x%x, uTimeoutMS=%RU32, mStatus=%RU32, mWaitCount=%RU32, mWaitEvent=%p, prcGuest=%p\n",
+ fWaitFlags, uTimeoutMS, mData.mStatus, mData.mWaitCount, mData.mWaitEvent, prcGuest));*/
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Did some error occur before? Then skip waiting and return. */
+ if (mData.mStatus == GuestSessionStatus_Error)
+ {
+ waitResult = GuestSessionWaitResult_Error;
+ AssertMsg(RT_FAILURE(mData.mRC), ("No error rc (%Rrc) set when guest session indicated an error\n", mData.mRC));
+ if (prcGuest)
+ *prcGuest = mData.mRC; /* Return last set error. */
+ return VERR_GSTCTL_GUEST_ERROR;
+ }
+
+ /* Guest Additions < 4.3 don't support session handling, skip. */
+ if (mData.mProtocolVersion < 2)
+ {
+ waitResult = GuestSessionWaitResult_WaitFlagNotSupported;
+
+ LogFlowThisFunc(("Installed Guest Additions don't support waiting for dedicated sessions, skipping\n"));
+ return VINF_SUCCESS;
+ }
+
+ waitResult = GuestSessionWaitResult_None;
+ if (fWaitFlags & GuestSessionWaitForFlag_Terminate)
+ {
+ switch (mData.mStatus)
+ {
+ case GuestSessionStatus_Terminated:
+ case GuestSessionStatus_Down:
+ waitResult = GuestSessionWaitResult_Terminate;
+ break;
+
+ case GuestSessionStatus_TimedOutKilled:
+ case GuestSessionStatus_TimedOutAbnormally:
+ waitResult = GuestSessionWaitResult_Timeout;
+ break;
+
+ case GuestSessionStatus_Error:
+ /* Handled above. */
+ break;
+
+ case GuestSessionStatus_Started:
+ waitResult = GuestSessionWaitResult_Start;
+ break;
+
+ case GuestSessionStatus_Undefined:
+ case GuestSessionStatus_Starting:
+ /* Do the waiting below. */
+ break;
+
+ default:
+ AssertMsgFailed(("Unhandled session status %RU32\n", mData.mStatus));
+ return VERR_NOT_IMPLEMENTED;
+ }
+ }
+ else if (fWaitFlags & GuestSessionWaitForFlag_Start)
+ {
+ switch (mData.mStatus)
+ {
+ case GuestSessionStatus_Started:
+ case GuestSessionStatus_Terminating:
+ case GuestSessionStatus_Terminated:
+ case GuestSessionStatus_Down:
+ waitResult = GuestSessionWaitResult_Start;
+ break;
+
+ case GuestSessionStatus_Error:
+ waitResult = GuestSessionWaitResult_Error;
+ break;
+
+ case GuestSessionStatus_TimedOutKilled:
+ case GuestSessionStatus_TimedOutAbnormally:
+ waitResult = GuestSessionWaitResult_Timeout;
+ break;
+
+ case GuestSessionStatus_Undefined:
+ case GuestSessionStatus_Starting:
+ /* Do the waiting below. */
+ break;
+
+ default:
+ AssertMsgFailed(("Unhandled session status %RU32\n", mData.mStatus));
+ return VERR_NOT_IMPLEMENTED;
+ }
+ }
+
+ LogFlowThisFunc(("sessionStatus=%RU32, sessionRc=%Rrc, waitResult=%RU32\n",
+ mData.mStatus, mData.mRC, waitResult));
+
+ /* No waiting needed? Return immediately using the last set error. */
+ if (waitResult != GuestSessionWaitResult_None)
+ {
+ if (prcGuest)
+ *prcGuest = mData.mRC; /* Return last set error (if any). */
+ return RT_SUCCESS(mData.mRC) ? VINF_SUCCESS : VERR_GSTCTL_GUEST_ERROR;
+ }
+
+ int vrc = VINF_SUCCESS;
+
+ uint64_t const tsStart = RTTimeMilliTS();
+ uint64_t tsNow = tsStart;
+
+ while (tsNow - tsStart < uTimeoutMS)
+ {
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged);
+
+ vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ alock.release(); /* Release lock before waiting. */
+
+ GuestSessionStatus_T sessionStatus;
+ vrc = i_waitForStatusChange(pEvent, fWaitFlags,
+ uTimeoutMS - (tsNow - tsStart), &sessionStatus, prcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ switch (sessionStatus)
+ {
+ case GuestSessionStatus_Started:
+ waitResult = GuestSessionWaitResult_Start;
+ break;
+
+ case GuestSessionStatus_Starting:
+ RT_FALL_THROUGH();
+ case GuestSessionStatus_Terminating:
+ if (fWaitFlags & GuestSessionWaitForFlag_Status) /* Any status wanted? */
+ waitResult = GuestSessionWaitResult_Status;
+ /* else: Wait another round until we get the event(s) fWaitFlags defines. */
+ break;
+
+ case GuestSessionStatus_Terminated:
+ waitResult = GuestSessionWaitResult_Terminate;
+ break;
+
+ case GuestSessionStatus_TimedOutKilled:
+ RT_FALL_THROUGH();
+ case GuestSessionStatus_TimedOutAbnormally:
+ waitResult = GuestSessionWaitResult_Timeout;
+ break;
+
+ case GuestSessionStatus_Down:
+ waitResult = GuestSessionWaitResult_Terminate;
+ break;
+
+ case GuestSessionStatus_Error:
+ waitResult = GuestSessionWaitResult_Error;
+ break;
+
+ default:
+ waitResult = GuestSessionWaitResult_Status;
+ break;
+ }
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ /* Wait result not None, e.g. some result acquired or a wait error occurred? Bail out. */
+ if ( waitResult != GuestSessionWaitResult_None
+ || RT_FAILURE(vrc))
+ break;
+
+ tsNow = RTTimeMilliTS();
+
+ alock.acquire(); /* Re-acquire lock before waiting for the next event. */
+ }
+
+ if (tsNow - tsStart >= uTimeoutMS)
+ {
+ waitResult = GuestSessionWaitResult_None; /* Paranoia. */
+ vrc = VERR_TIMEOUT;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Waits for guest session status changes.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @retval VERR_WRONG_ORDER when an unexpected event type has been received.
+ * @param pEvent Wait event to use for waiting.
+ * @param fWaitFlags Wait flags to use.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pSessionStatus Where to return the guest session status.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestSession::i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t fWaitFlags, uint32_t uTimeoutMS,
+ GuestSessionStatus_T *pSessionStatus, int *prcGuest)
+{
+ RT_NOREF(fWaitFlags);
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS, &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestSessionStateChanged)
+ {
+ ComPtr<IGuestSessionStateChangedEvent> pChangedEvent = pIEvent;
+ Assert(!pChangedEvent.isNull());
+
+ GuestSessionStatus_T sessionStatus;
+ pChangedEvent->COMGETTER(Status)(&sessionStatus);
+ if (pSessionStatus)
+ *pSessionStatus = sessionStatus;
+
+ ComPtr<IVirtualBoxErrorInfo> errorInfo;
+ HRESULT hr = pChangedEvent->COMGETTER(Error)(errorInfo.asOutParam());
+ ComAssertComRC(hr);
+
+ LONG lGuestRc;
+ hr = errorInfo->COMGETTER(ResultDetail)(&lGuestRc);
+ ComAssertComRC(hr);
+ if (RT_FAILURE((int)lGuestRc))
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ if (prcGuest)
+ *prcGuest = (int)lGuestRc;
+
+ LogFlowThisFunc(("Status changed event for session ID=%RU32, new status is: %RU32 (%Rrc)\n",
+ mData.mSession.mID, sessionStatus,
+ RT_SUCCESS((int)lGuestRc) ? VINF_SUCCESS : (int)lGuestRc));
+ }
+ else /** @todo Re-visit this. Can this happen more frequently? */
+ AssertMsgFailedReturn(("Got unexpected event type %#x\n", evtType), VERR_WRONG_ORDER);
+ }
+ /* waitForEvent may also return VERR_GSTCTL_GUEST_ERROR like we do above, so make prcGuest is set. */
+ else if (vrc == VERR_GSTCTL_GUEST_ERROR && prcGuest)
+ *prcGuest = pEvent->GuestResult();
+ Assert(vrc != VERR_GSTCTL_GUEST_ERROR || !prcGuest || *prcGuest != (int)0xcccccccc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+// implementation of public methods
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestSession::close()
+{
+ LogFlowThisFuncEnter();
+
+ /* Note: Don't check if the session is ready via i_isStartedExternal() here;
+ * the session (already) could be in a stopped / aborted state. */
+
+ int vrc = VINF_SUCCESS; /* Shut up MSVC. */
+ int rcGuest = VINF_SUCCESS;
+
+ uint32_t msTimeout = RT_MS_10SEC; /* 10s timeout by default */
+ for (int i = 0; i < 3; i++)
+ {
+ if (i)
+ {
+ LogRel(("Guest Control: Closing session '%s' timed out (%RU32s timeout, attempt %d/10), retrying ...\n",
+ mData.mSession.mName.c_str(), msTimeout / RT_MS_1SEC, i + 1));
+ msTimeout += RT_MS_5SEC; /* Slightly increase the timeout. */
+ }
+
+ /* Close session on guest. */
+ vrc = i_closeSession(0 /* Flags */, msTimeout, &rcGuest);
+ if ( RT_SUCCESS(vrc)
+ || vrc != VERR_TIMEOUT) /* If something else happened there is no point in retrying further. */
+ break;
+ }
+
+ /* On failure don't return here, instead do all the cleanup
+ * work first and then return an error. */
+
+ /* Destroy session + remove ourselves from the session list. */
+ AssertPtr(mParent);
+ int vrc2 = mParent->i_sessionDestroy(mData.mSession.mID);
+ if (vrc2 == VERR_NOT_FOUND) /* Not finding the session anymore isn't critical. */
+ vrc2 = VINF_SUCCESS;
+
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ LogFlowThisFunc(("Returning rc=%Rrc, rcGuest=%Rrc\n", vrc, rcGuest));
+
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Session, rcGuest, mData.mSession.mName.c_str());
+ return setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Closing guest session failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ return setError(VBOX_E_IPRT_ERROR, tr("Closing guest session \"%s\" failed with %Rrc"),
+ mData.mSession.mName.c_str(), vrc);
+ }
+
+ return S_OK;
+}
+
+HRESULT GuestSession::fileCopy(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<FileCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aSource, aDestination, aFlags, aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::fileCopyFromGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<FileCopyFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress)
+{
+ uint32_t fFlags = FileCopyFlag_None;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fFlags |= aFlags[i];
+
+ const uint32_t fValidFlags = FileCopyFlag_None | FileCopyFlag_NoReplace | FileCopyFlag_FollowLinks | FileCopyFlag_Update;
+ if (fFlags & ~fValidFlags)
+ return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags);
+ }
+
+ GuestSessionFsSourceSet SourceSet;
+
+ GuestSessionFsSourceSpec source;
+ source.strSource = aSource;
+ source.enmType = FsObjType_File;
+ source.enmPathStyle = i_getGuestPathStyle();
+ source.fDryRun = false; /** @todo Implement support for a dry run. */
+ source.fDirCopyFlags = DirectoryCopyFlag_None;
+ source.fFileCopyFlags = (FileCopyFlag_T)fFlags;
+
+ SourceSet.push_back(source);
+
+ return i_copyFromGuest(SourceSet, aDestination, aProgress);
+}
+
+HRESULT GuestSession::fileCopyToGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<FileCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+ uint32_t fFlags = FileCopyFlag_None;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fFlags |= aFlags[i];
+
+ const uint32_t fValidFlags = FileCopyFlag_None | FileCopyFlag_NoReplace | FileCopyFlag_FollowLinks | FileCopyFlag_Update;
+ if (fFlags & ~fValidFlags)
+ return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags);
+ }
+
+ GuestSessionFsSourceSet SourceSet;
+
+ GuestSessionFsSourceSpec source;
+ source.strSource = aSource;
+ source.enmType = FsObjType_File;
+ source.enmPathStyle = GuestSession::i_getHostPathStyle();
+ source.fDryRun = false; /** @todo Implement support for a dry run. */
+ source.fDirCopyFlags = DirectoryCopyFlag_None;
+ source.fFileCopyFlags = (FileCopyFlag_T)fFlags;
+
+ SourceSet.push_back(source);
+
+ return i_copyToGuest(SourceSet, aDestination, aProgress);
+}
+
+HRESULT GuestSession::copyFromGuest(const std::vector<com::Utf8Str> &aSources, const std::vector<com::Utf8Str> &aFilters,
+ const std::vector<com::Utf8Str> &aFlags, const com::Utf8Str &aDestination,
+ ComPtr<IProgress> &aProgress)
+{
+ const size_t cSources = aSources.size();
+ if ( (aFilters.size() && aFilters.size() != cSources)
+ || (aFlags.size() && aFlags.size() != cSources))
+ {
+ return setError(E_INVALIDARG, tr("Parameter array sizes don't match to the number of sources specified"));
+ }
+
+ GuestSessionFsSourceSet SourceSet;
+
+ std::vector<com::Utf8Str>::const_iterator itSource = aSources.begin();
+ std::vector<com::Utf8Str>::const_iterator itFilter = aFilters.begin();
+ std::vector<com::Utf8Str>::const_iterator itFlags = aFlags.begin();
+
+ const bool fContinueOnErrors = false; /** @todo Do we want a flag for that? */
+ const bool fFollowSymlinks = true; /** @todo Ditto. */
+
+ while (itSource != aSources.end())
+ {
+ GuestFsObjData objData;
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fsQueryInfo(*(itSource), fFollowSymlinks, objData, &rcGuest);
+ if ( RT_FAILURE(vrc)
+ && !fContinueOnErrors)
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, rcGuest, (*itSource).c_str());
+ return setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying type for guest source failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ return setError(E_FAIL, tr("Querying type for guest source \"%s\" failed: %Rrc"), (*itSource).c_str(), vrc);
+ }
+
+ Utf8Str strFlags;
+ if (itFlags != aFlags.end())
+ {
+ strFlags = *itFlags;
+ ++itFlags;
+ }
+
+ Utf8Str strFilter;
+ if (itFilter != aFilters.end())
+ {
+ strFilter = *itFilter;
+ ++itFilter;
+ }
+
+ GuestSessionFsSourceSpec source;
+ source.strSource = *itSource;
+ source.strFilter = strFilter;
+ source.enmType = objData.mType;
+ source.enmPathStyle = i_getGuestPathStyle();
+ source.fDryRun = false; /** @todo Implement support for a dry run. */
+
+ /* Check both flag groups here, as copying a directory also could mean to explicitly
+ * *not* replacing any existing files (or just copy files which are newer, for instance). */
+ GuestSession::i_directoryCopyFlagFromStr(strFlags, false /* fStrict */, &source.fDirCopyFlags);
+ GuestSession::i_fileCopyFlagFromStr(strFlags, false /* fStrict */, &source.fFileCopyFlags);
+
+ SourceSet.push_back(source);
+
+ ++itSource;
+ }
+
+ return i_copyFromGuest(SourceSet, aDestination, aProgress);
+}
+
+HRESULT GuestSession::copyToGuest(const std::vector<com::Utf8Str> &aSources, const std::vector<com::Utf8Str> &aFilters,
+ const std::vector<com::Utf8Str> &aFlags, const com::Utf8Str &aDestination,
+ ComPtr<IProgress> &aProgress)
+{
+ const size_t cSources = aSources.size();
+ if ( (aFilters.size() && aFilters.size() != cSources)
+ || (aFlags.size() && aFlags.size() != cSources))
+ {
+ return setError(E_INVALIDARG, tr("Parameter array sizes don't match to the number of sources specified"));
+ }
+
+ GuestSessionFsSourceSet SourceSet;
+
+ std::vector<com::Utf8Str>::const_iterator itSource = aSources.begin();
+ std::vector<com::Utf8Str>::const_iterator itFilter = aFilters.begin();
+ std::vector<com::Utf8Str>::const_iterator itFlags = aFlags.begin();
+
+ const bool fContinueOnErrors = false; /** @todo Do we want a flag for that? */
+
+ while (itSource != aSources.end())
+ {
+ RTFSOBJINFO objInfo;
+ int vrc = RTPathQueryInfo((*itSource).c_str(), &objInfo, RTFSOBJATTRADD_NOTHING);
+ if ( RT_FAILURE(vrc)
+ && !fContinueOnErrors)
+ {
+ return setError(E_FAIL, tr("Unable to query type for source '%s' (%Rrc)"), (*itSource).c_str(), vrc);
+ }
+
+ Utf8Str strFlags;
+ if (itFlags != aFlags.end())
+ {
+ strFlags = *itFlags;
+ ++itFlags;
+ }
+
+ Utf8Str strFilter;
+ if (itFilter != aFilters.end())
+ {
+ strFilter = *itFilter;
+ ++itFilter;
+ }
+
+ GuestSessionFsSourceSpec source;
+ source.strSource = *itSource;
+ source.strFilter = strFilter;
+ source.enmType = GuestBase::fileModeToFsObjType(objInfo.Attr.fMode);
+ source.enmPathStyle = GuestSession::i_getHostPathStyle();
+ source.fDryRun = false; /** @todo Implement support for a dry run. */
+
+ GuestSession::i_directoryCopyFlagFromStr(strFlags, false /* fStrict */, &source.fDirCopyFlags);
+ GuestSession::i_fileCopyFlagFromStr(strFlags, false /* fStrict */, &source.fFileCopyFlags);
+
+ SourceSet.push_back(source);
+
+ ++itSource;
+ }
+
+ /* (Re-)Validate stuff. */
+ if (RT_UNLIKELY(SourceSet.size() == 0)) /* At least one source must be present. */
+ return setError(E_INVALIDARG, tr("No sources specified"));
+ if (RT_UNLIKELY(SourceSet[0].strSource.isEmpty()))
+ return setError(E_INVALIDARG, tr("First source entry is empty"));
+ if (RT_UNLIKELY(aDestination.isEmpty()))
+ return setError(E_INVALIDARG, tr("No destination specified"));
+
+ return i_copyToGuest(SourceSet, aDestination, aProgress);
+}
+
+HRESULT GuestSession::directoryCopy(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<DirectoryCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aSource, aDestination, aFlags, aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::directoryCopyFromGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<DirectoryCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+ uint32_t fFlags = DirectoryCopyFlag_None;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fFlags |= aFlags[i];
+
+ const uint32_t fValidFlags = DirectoryCopyFlag_None | DirectoryCopyFlag_CopyIntoExisting | DirectoryCopyFlag_Recursive
+ | DirectoryCopyFlag_FollowLinks;
+ if (fFlags & ~fValidFlags)
+ return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags);
+ }
+
+ GuestSessionFsSourceSet SourceSet;
+
+ GuestSessionFsSourceSpec source;
+ source.strSource = aSource;
+ source.enmType = FsObjType_Directory;
+ source.enmPathStyle = i_getGuestPathStyle();
+ source.fDryRun = false; /** @todo Implement support for a dry run. */
+ source.fDirCopyFlags = (DirectoryCopyFlag_T)fFlags;
+ source.fFileCopyFlags = FileCopyFlag_None; /* Overwrite existing files. */
+
+ SourceSet.push_back(source);
+
+ return i_copyFromGuest(SourceSet, aDestination, aProgress);
+}
+
+HRESULT GuestSession::directoryCopyToGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<DirectoryCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+ uint32_t fFlags = DirectoryCopyFlag_None;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fFlags |= aFlags[i];
+
+ const uint32_t fValidFlags = DirectoryCopyFlag_None | DirectoryCopyFlag_CopyIntoExisting | DirectoryCopyFlag_Recursive
+ | DirectoryCopyFlag_FollowLinks;
+ if (fFlags & ~fValidFlags)
+ return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags);
+ }
+
+ GuestSessionFsSourceSet SourceSet;
+
+ GuestSessionFsSourceSpec source;
+ source.strSource = aSource;
+ source.enmType = FsObjType_Directory;
+ source.enmPathStyle = GuestSession::i_getHostPathStyle();
+ source.fDryRun = false; /** @todo Implement support for a dry run. */
+ source.fDirCopyFlags = (DirectoryCopyFlag_T)fFlags;
+ source.fFileCopyFlags = FileCopyFlag_None; /* Overwrite existing files. */
+
+ SourceSet.push_back(source);
+
+ return i_copyToGuest(SourceSet, aDestination, aProgress);
+}
+
+HRESULT GuestSession::directoryCreate(const com::Utf8Str &aPath, ULONG aMode,
+ const std::vector<DirectoryCreateFlag_T> &aFlags)
+{
+ if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No directory to create specified"));
+
+ uint32_t fFlags = DirectoryCreateFlag_None;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fFlags |= aFlags[i];
+
+ if (fFlags & ~DirectoryCreateFlag_Parents)
+ return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), fFlags);
+ }
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ ComObjPtr <GuestDirectory> pDirectory; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_directoryCreate(aPath, (uint32_t)aMode, fFlags, &rcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Directory, rcGuest, aPath.c_str());
+ return setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Guest directory creation failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ switch (vrc)
+ {
+ case VERR_INVALID_PARAMETER:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Guest directory creation failed: Invalid parameters given"));
+ break;
+
+ case VERR_BROKEN_PIPE:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Guest directory creation failed: Unexpectedly aborted"));
+ break;
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Guest directory creation failed: %Rrc"), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::directoryCreateTemp(const com::Utf8Str &aTemplateName, ULONG aMode, const com::Utf8Str &aPath,
+ BOOL aSecure, com::Utf8Str &aDirectory)
+{
+ if (RT_UNLIKELY((aTemplateName.c_str()) == NULL || *(aTemplateName.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No template specified"));
+ if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No directory name specified"));
+ if (!aSecure) /* Ignore what mode is specified when a secure temp thing needs to be created. */
+ if (RT_UNLIKELY(aMode & ~07777))
+ return setError(E_INVALIDARG, tr("Mode invalid (must be specified in octal mode)"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fsCreateTemp(aTemplateName, aPath, true /* Directory */, aDirectory, aMode, RT_BOOL(aSecure), &rcGuest);
+ if (!RT_SUCCESS(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolMkTemp, rcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Temporary guest directory creation failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Temporary guest directory creation \"%s\" with template \"%s\" failed: %Rrc"),
+ aPath.c_str(), aTemplateName.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::directoryExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists)
+{
+ if (RT_UNLIKELY(aPath.isEmpty()))
+ return setError(E_INVALIDARG, tr("Empty path"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ GuestFsObjData objData;
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+
+ int vrc = i_directoryQueryInfo(aPath, aFollowSymlinks != FALSE, objData, &rcGuest);
+ if (RT_SUCCESS(vrc))
+ *aExists = TRUE;
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ switch (rcGuest)
+ {
+ case VERR_PATH_NOT_FOUND:
+ *aExists = FALSE;
+ break;
+ default:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying directory existence failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ }
+ break;
+ }
+
+ case VERR_NOT_A_DIRECTORY:
+ {
+ *aExists = FALSE;
+ break;
+ }
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying directory existence \"%s\" failed: %Rrc"),
+ aPath.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::directoryOpen(const com::Utf8Str &aPath, const com::Utf8Str &aFilter,
+ const std::vector<DirectoryOpenFlag_T> &aFlags, ComPtr<IGuestDirectory> &aDirectory)
+{
+ if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No directory to open specified"));
+ if (RT_UNLIKELY((aFilter.c_str()) != NULL && *(aFilter.c_str()) != '\0'))
+ return setError(E_INVALIDARG, tr("Directory filters are not implemented yet"));
+
+ uint32_t fFlags = DirectoryOpenFlag_None;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fFlags |= aFlags[i];
+
+ if (fFlags)
+ return setError(E_INVALIDARG, tr("Open flags (%#x) not implemented yet"), fFlags);
+ }
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ GuestDirectoryOpenInfo openInfo;
+ openInfo.mPath = aPath;
+ openInfo.mFilter = aFilter;
+ openInfo.mFlags = fFlags;
+
+ ComObjPtr<GuestDirectory> pDirectory; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_directoryOpen(openInfo, pDirectory, &rcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Return directory object to the caller. */
+ hrc = pDirectory.queryInterfaceTo(aDirectory.asOutParam());
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_INVALID_PARAMETER:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening guest directory \"%s\" failed; invalid parameters given"),
+ aPath.c_str());
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Directory, rcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Opening guest directory failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening guest directory \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::directoryRemove(const com::Utf8Str &aPath)
+{
+ if (RT_UNLIKELY(aPath.c_str() == NULL || *aPath.c_str() == '\0'))
+ return setError(E_INVALIDARG, tr("No directory to remove specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ /* No flags; only remove the directory when empty. */
+ uint32_t fFlags = DIRREMOVEREC_FLAG_NONE;
+
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_directoryRemove(aPath, fFlags, &rcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_NOT_SUPPORTED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Handling removing guest directories not supported by installed Guest Additions"));
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Directory, rcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Removing guest directory failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Removing guest directory \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::directoryRemoveRecursive(const com::Utf8Str &aPath, const std::vector<DirectoryRemoveRecFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress)
+{
+ if (RT_UNLIKELY(aPath.c_str() == NULL || *aPath.c_str() == '\0'))
+ return setError(E_INVALIDARG, tr("No directory to remove recursively specified"));
+
+ /* By defautl remove recursively as the function name implies. */
+ uint32_t fFlags = DIRREMOVEREC_FLAG_RECURSIVE;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ {
+ switch (aFlags[i])
+ {
+ case DirectoryRemoveRecFlag_None: /* Skip. */
+ continue;
+
+ case DirectoryRemoveRecFlag_ContentAndDir:
+ fFlags |= DIRREMOVEREC_FLAG_CONTENT_AND_DIR;
+ break;
+
+ case DirectoryRemoveRecFlag_ContentOnly:
+ fFlags |= DIRREMOVEREC_FLAG_CONTENT_ONLY;
+ break;
+
+ default:
+ return setError(E_INVALIDARG, tr("Invalid flags specified"));
+ }
+ }
+ }
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ ComObjPtr<Progress> pProgress;
+ hrc = pProgress.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = pProgress->init(static_cast<IGuestSession *>(this),
+ Bstr(tr("Removing guest directory")).raw(),
+ TRUE /*aCancelable*/);
+ if (FAILED(hrc))
+ return hrc;
+
+ /* Note: At the moment we don't supply progress information while
+ * deleting a guest directory recursively. So just complete
+ * the progress object right now. */
+ /** @todo Implement progress reporting on guest directory deletion! */
+ hrc = pProgress->i_notifyComplete(S_OK);
+ if (FAILED(hrc))
+ return hrc;
+
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_directoryRemove(aPath, fFlags, &rcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_NOT_SUPPORTED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Handling removing guest directories recursively not supported by installed Guest Additions"));
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Directory, rcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Recursively removing guest directory failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recursively removing guest directory \"%s\" failed: %Rrc"),
+ aPath.c_str(), vrc);
+ break;
+ }
+ }
+ else
+ {
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::environmentScheduleSet(const com::Utf8Str &aName, const com::Utf8Str &aValue)
+{
+ LogFlowThisFuncEnter();
+ int vrc;
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ vrc = mData.mEnvironmentChanges.setVariable(aName, aValue);
+ }
+ HRESULT hrc;
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else if (vrc == VERR_ENV_INVALID_VAR_NAME)
+ hrc = setError(E_INVALIDARG, tr("Invalid environment variable name '%s'"), aName.c_str());
+ else
+ hrc = setErrorVrc(vrc, tr("Failed to schedule setting environment variable '%s' to '%s'"), aName.c_str(), aValue.c_str());
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT GuestSession::environmentScheduleUnset(const com::Utf8Str &aName)
+{
+ LogFlowThisFuncEnter();
+ int vrc;
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ vrc = mData.mEnvironmentChanges.unsetVariable(aName);
+ }
+ HRESULT hrc;
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else if (vrc == VERR_ENV_INVALID_VAR_NAME)
+ hrc = setError(E_INVALIDARG, tr("Invalid environment variable name '%s'"), aName.c_str());
+ else
+ hrc = setErrorVrc(vrc, tr("Failed to schedule unsetting environment variable '%s'"), aName.c_str());
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT GuestSession::environmentGetBaseVariable(const com::Utf8Str &aName, com::Utf8Str &aValue)
+{
+ LogFlowThisFuncEnter();
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc;
+ if (mData.mpBaseEnvironment)
+ {
+ int vrc = mData.mpBaseEnvironment->getVariable(aName, &aValue);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else if (vrc == VERR_ENV_INVALID_VAR_NAME)
+ hrc = setError(E_INVALIDARG, tr("Invalid environment variable name '%s'"), aName.c_str());
+ else
+ hrc = setErrorVrc(vrc);
+ }
+ else if (mData.mProtocolVersion < 99999)
+ hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the Guest Additions"));
+ else
+ hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest"));
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT GuestSession::environmentDoesBaseVariableExist(const com::Utf8Str &aName, BOOL *aExists)
+{
+ LogFlowThisFuncEnter();
+ *aExists = FALSE;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc;
+ if (mData.mpBaseEnvironment)
+ {
+ hrc = S_OK;
+ *aExists = mData.mpBaseEnvironment->doesVariableExist(aName);
+ }
+ else if (mData.mProtocolVersion < 99999)
+ hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the Guest Additions"));
+ else
+ hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest"));
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT GuestSession::fileCreateTemp(const com::Utf8Str &aTemplateName, ULONG aMode, const com::Utf8Str &aPath, BOOL aSecure,
+ ComPtr<IGuestFile> &aFile)
+{
+ RT_NOREF(aTemplateName, aMode, aPath, aSecure, aFile);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::fileExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists)
+{
+ /* By default we return non-existent. */
+ *aExists = FALSE;
+
+ if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
+ return S_OK;
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ GuestFsObjData objData; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fileQueryInfo(aPath, RT_BOOL(aFollowSymlinks), objData, &rcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ *aExists = TRUE;
+ return S_OK;
+ }
+
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ switch (rcGuest)
+ {
+ case VERR_PATH_NOT_FOUND:
+ RT_FALL_THROUGH();
+ case VERR_FILE_NOT_FOUND:
+ break;
+
+ default:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying guest file existence failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ }
+
+ break;
+ }
+
+ case VERR_NOT_A_FILE:
+ break;
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying guest file information for \"%s\" failed: %Rrc"),
+ aPath.c_str(), vrc);
+ break;
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fileOpen(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction,
+ ULONG aCreationMode, ComPtr<IGuestFile> &aFile)
+{
+ LogFlowThisFuncEnter();
+
+ const std::vector<FileOpenExFlag_T> EmptyFlags;
+ return fileOpenEx(aPath, aAccessMode, aOpenAction, FileSharingMode_All, aCreationMode, EmptyFlags, aFile);
+}
+
+HRESULT GuestSession::fileOpenEx(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction,
+ FileSharingMode_T aSharingMode, ULONG aCreationMode,
+ const std::vector<FileOpenExFlag_T> &aFlags, ComPtr<IGuestFile> &aFile)
+{
+ if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No file to open specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ /* Validate aAccessMode. */
+ switch (aAccessMode)
+ {
+ case FileAccessMode_ReadOnly:
+ RT_FALL_THRU();
+ case FileAccessMode_WriteOnly:
+ RT_FALL_THRU();
+ case FileAccessMode_ReadWrite:
+ break;
+ case FileAccessMode_AppendOnly:
+ RT_FALL_THRU();
+ case FileAccessMode_AppendRead:
+ return setError(E_NOTIMPL, tr("Append access modes are not yet implemented"));
+ default:
+ return setError(E_INVALIDARG, tr("Unknown FileAccessMode value %u (%#x)"), aAccessMode, aAccessMode);
+ }
+
+ /* Validate aOpenAction to the old format. */
+ switch (aOpenAction)
+ {
+ case FileOpenAction_OpenExisting:
+ RT_FALL_THRU();
+ case FileOpenAction_OpenOrCreate:
+ RT_FALL_THRU();
+ case FileOpenAction_CreateNew:
+ RT_FALL_THRU();
+ case FileOpenAction_CreateOrReplace:
+ RT_FALL_THRU();
+ case FileOpenAction_OpenExistingTruncated:
+ RT_FALL_THRU();
+ case FileOpenAction_AppendOrCreate:
+ break;
+ default:
+ return setError(E_INVALIDARG, tr("Unknown FileOpenAction value %u (%#x)"), aAccessMode, aAccessMode);
+ }
+
+ /* Validate aSharingMode. */
+ switch (aSharingMode)
+ {
+ case FileSharingMode_All:
+ break;
+ case FileSharingMode_Read:
+ case FileSharingMode_Write:
+ case FileSharingMode_ReadWrite:
+ case FileSharingMode_Delete:
+ case FileSharingMode_ReadDelete:
+ case FileSharingMode_WriteDelete:
+ return setError(E_NOTIMPL, tr("Only FileSharingMode_All is currently implemented"));
+
+ default:
+ return setError(E_INVALIDARG, tr("Unknown FileOpenAction value %u (%#x)"), aAccessMode, aAccessMode);
+ }
+
+ /* Combine and validate flags. */
+ uint32_t fOpenEx = 0;
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fOpenEx |= aFlags[i];
+ if (fOpenEx)
+ return setError(E_INVALIDARG, tr("Unsupported FileOpenExFlag value(s) in aFlags (%#x)"), fOpenEx);
+
+ ComObjPtr <GuestFile> pFile;
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fileOpenEx(aPath, aAccessMode, aOpenAction, aSharingMode, aCreationMode, aFlags, pFile, &rcGuest);
+ if (RT_SUCCESS(vrc))
+ /* Return directory object to the caller. */
+ hrc = pFile.queryInterfaceTo(aFile.asOutParam());
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_NOT_SUPPORTED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Handling guest files not supported by installed Guest Additions"));
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_File, rcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Opening guest file failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening guest file \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fileQuerySize(const com::Utf8Str &aPath, BOOL aFollowSymlinks, LONG64 *aSize)
+{
+ if (aPath.isEmpty())
+ return setError(E_INVALIDARG, tr("No path specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ int64_t llSize; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fileQuerySize(aPath, aFollowSymlinks != FALSE, &llSize, &rcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ *aSize = llSize;
+ }
+ else
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying guest file size failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying guest file size of \"%s\" failed: %Rrc"),
+ vrc, aPath.c_str());
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fsQueryFreeSpace(const com::Utf8Str &aPath, LONG64 *aFreeSpace)
+{
+ RT_NOREF(aPath, aFreeSpace);
+
+ return E_NOTIMPL;
+}
+
+HRESULT GuestSession::fsQueryInfo(const com::Utf8Str &aPath, ComPtr<IGuestFsInfo> &aInfo)
+{
+ RT_NOREF(aPath, aInfo);
+
+ return E_NOTIMPL;
+}
+
+HRESULT GuestSession::fsObjExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists)
+{
+ if (aPath.isEmpty())
+ return setError(E_INVALIDARG, tr("No path specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFunc(("aPath=%s, aFollowSymlinks=%RTbool\n", aPath.c_str(), RT_BOOL(aFollowSymlinks)));
+
+ *aExists = false;
+
+ GuestFsObjData objData;
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fsQueryInfo(aPath, aFollowSymlinks != FALSE, objData, &rcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ *aExists = TRUE;
+ }
+ else
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ if ( rcGuest == VERR_NOT_A_FILE
+ || rcGuest == VERR_PATH_NOT_FOUND
+ || rcGuest == VERR_FILE_NOT_FOUND
+ || rcGuest == VERR_INVALID_NAME)
+ {
+ hrc = S_OK; /* Ignore these vrc values. */
+ }
+ else
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying guest file existence information failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Querying guest file existence information for \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fsObjQueryInfo(const com::Utf8Str &aPath, BOOL aFollowSymlinks, ComPtr<IGuestFsObjInfo> &aInfo)
+{
+ if (aPath.isEmpty())
+ return setError(E_INVALIDARG, tr("No path specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFunc(("aPath=%s, aFollowSymlinks=%RTbool\n", aPath.c_str(), RT_BOOL(aFollowSymlinks)));
+
+ GuestFsObjData Info; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fsQueryInfo(aPath, aFollowSymlinks != FALSE, Info, &rcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ ComObjPtr<GuestFsObjInfo> ptrFsObjInfo;
+ hrc = ptrFsObjInfo.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ vrc = ptrFsObjInfo->init(Info);
+ if (RT_SUCCESS(vrc))
+ hrc = ptrFsObjInfo.queryInterfaceTo(aInfo.asOutParam());
+ else
+ hrc = setErrorVrc(vrc);
+ }
+ }
+ else
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying guest file information failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Querying guest file information for \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fsObjRemove(const com::Utf8Str &aPath)
+{
+ if (RT_UNLIKELY(aPath.isEmpty()))
+ return setError(E_INVALIDARG, tr("No path specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFunc(("aPath=%s\n", aPath.c_str()));
+
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fileRemove(aPath, &rcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolRm, rcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Removing guest file failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Removing guest file \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fsObjRemoveArray(const std::vector<com::Utf8Str> &aPaths, ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aPaths, aProgress);
+ return E_NOTIMPL;
+}
+
+HRESULT GuestSession::fsObjRename(const com::Utf8Str &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<FsObjRenameFlag_T> &aFlags)
+{
+ if (RT_UNLIKELY(aSource.isEmpty()))
+ return setError(E_INVALIDARG, tr("No source path specified"));
+
+ if (RT_UNLIKELY(aDestination.isEmpty()))
+ return setError(E_INVALIDARG, tr("No destination path specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ /* Combine, validate and convert flags. */
+ uint32_t fApiFlags = 0;
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fApiFlags |= aFlags[i];
+ if (fApiFlags & ~((uint32_t)FsObjRenameFlag_NoReplace | (uint32_t)FsObjRenameFlag_Replace))
+ return setError(E_INVALIDARG, tr("Unknown rename flag: %#x"), fApiFlags);
+
+ LogFlowThisFunc(("aSource=%s, aDestination=%s\n", aSource.c_str(), aDestination.c_str()));
+
+ AssertCompile(FsObjRenameFlag_NoReplace == 0);
+ AssertCompile(FsObjRenameFlag_Replace != 0);
+ uint32_t fBackend;
+ if ((fApiFlags & ((uint32_t)FsObjRenameFlag_NoReplace | (uint32_t)FsObjRenameFlag_Replace)) == FsObjRenameFlag_Replace)
+ fBackend = PATHRENAME_FLAG_REPLACE;
+ else
+ fBackend = PATHRENAME_FLAG_NO_REPLACE;
+
+ /* Call worker to do the job. */
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_pathRename(aSource, aDestination, fBackend, &rcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_NOT_SUPPORTED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Handling renaming guest paths not supported by installed Guest Additions"));
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, rcGuest, aSource.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Renaming guest path failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Renaming guest path \"%s\" failed: %Rrc"),
+ aSource.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fsObjMove(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<FsObjMoveFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aSource, aDestination, aFlags, aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::fsObjMoveArray(const std::vector<com::Utf8Str> &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<FsObjMoveFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aSource, aDestination, aFlags, aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::fsObjCopyArray(const std::vector<com::Utf8Str> &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<FileCopyFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aSource, aDestination, aFlags, aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::fsObjSetACL(const com::Utf8Str &aPath, BOOL aFollowSymlinks, const com::Utf8Str &aAcl, ULONG aMode)
+{
+ RT_NOREF(aPath, aFollowSymlinks, aAcl, aMode);
+ ReturnComNotImplemented();
+}
+
+
+HRESULT GuestSession::processCreate(const com::Utf8Str &aExecutable, const std::vector<com::Utf8Str> &aArguments,
+ const std::vector<com::Utf8Str> &aEnvironment,
+ const std::vector<ProcessCreateFlag_T> &aFlags,
+ ULONG aTimeoutMS, ComPtr<IGuestProcess> &aGuestProcess)
+{
+ LogFlowThisFuncEnter();
+
+ std::vector<LONG> affinityIgnored;
+ return processCreateEx(aExecutable, aArguments, aEnvironment, aFlags, aTimeoutMS, ProcessPriority_Default,
+ affinityIgnored, aGuestProcess);
+}
+
+HRESULT GuestSession::processCreateEx(const com::Utf8Str &aExecutable, const std::vector<com::Utf8Str> &aArguments,
+ const std::vector<com::Utf8Str> &aEnvironment,
+ const std::vector<ProcessCreateFlag_T> &aFlags, ULONG aTimeoutMS,
+ ProcessPriority_T aPriority, const std::vector<LONG> &aAffinity,
+ ComPtr<IGuestProcess> &aGuestProcess)
+{
+ HRESULT hr = i_isStartedExternal();
+ if (FAILED(hr))
+ return hr;
+
+ /*
+ * Must have an executable to execute. If none is given, we try use the
+ * zero'th argument.
+ */
+ const char *pszExecutable = aExecutable.c_str();
+ if (RT_UNLIKELY(pszExecutable == NULL || *pszExecutable == '\0'))
+ {
+ if (aArguments.size() > 0)
+ pszExecutable = aArguments[0].c_str();
+ if (pszExecutable == NULL || *pszExecutable == '\0')
+ return setError(E_INVALIDARG, tr("No command to execute specified"));
+ }
+
+ /* The rest of the input is being validated in i_processCreateEx(). */
+
+ LogFlowThisFuncEnter();
+
+ /*
+ * Build the process startup info.
+ */
+ GuestProcessStartupInfo procInfo;
+
+ /* Executable and arguments. */
+ procInfo.mExecutable = pszExecutable;
+ if (aArguments.size())
+ {
+ for (size_t i = 0; i < aArguments.size(); i++)
+ procInfo.mArguments.push_back(aArguments[i]);
+ }
+ else /* If no arguments were given, add the executable as argv[0] by default. */
+ procInfo.mArguments.push_back(procInfo.mExecutable);
+
+ /* Combine the environment changes associated with the ones passed in by
+ the caller, giving priority to the latter. The changes are putenv style
+ and will be applied to the standard environment for the guest user. */
+ int vrc = procInfo.mEnvironmentChanges.copy(mData.mEnvironmentChanges);
+ if (RT_SUCCESS(vrc))
+ {
+ size_t idxError = ~(size_t)0;
+ vrc = procInfo.mEnvironmentChanges.applyPutEnvArray(aEnvironment, &idxError);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Convert the flag array into a mask. */
+ if (aFlags.size())
+ for (size_t i = 0; i < aFlags.size(); i++)
+ procInfo.mFlags |= aFlags[i];
+
+ procInfo.mTimeoutMS = aTimeoutMS;
+
+ /** @todo use RTCPUSET instead of archaic 64-bit variables! */
+ if (aAffinity.size())
+ for (size_t i = 0; i < aAffinity.size(); i++)
+ if (aAffinity[i])
+ procInfo.mAffinity |= (uint64_t)1 << i;
+
+ procInfo.mPriority = aPriority;
+
+ /*
+ * Create a guest process object.
+ */
+ ComObjPtr<GuestProcess> pProcess;
+ vrc = i_processCreateEx(procInfo, pProcess);
+ if (RT_SUCCESS(vrc))
+ {
+ ComPtr<IGuestProcess> pIProcess;
+ hr = pProcess.queryInterfaceTo(pIProcess.asOutParam());
+ if (SUCCEEDED(hr))
+ {
+ /*
+ * Start the process.
+ */
+ vrc = pProcess->i_startProcessAsync();
+ if (RT_SUCCESS(vrc))
+ {
+ aGuestProcess = pIProcess;
+
+ LogFlowFuncLeaveRC(vrc);
+ return S_OK;
+ }
+
+ hr = setErrorVrc(vrc, tr("Failed to start guest process: %Rrc"), vrc);
+ }
+ }
+ else if (vrc == VERR_GSTCTL_MAX_CID_OBJECTS_REACHED)
+ hr = setErrorVrc(vrc, tr("Maximum number of concurrent guest processes per session (%u) reached"),
+ VBOX_GUESTCTRL_MAX_OBJECTS);
+ else
+ hr = setErrorVrc(vrc, tr("Failed to create guest process object: %Rrc"), vrc);
+ }
+ else
+ hr = setErrorBoth(vrc == VERR_ENV_INVALID_VAR_NAME ? E_INVALIDARG : Global::vboxStatusCodeToCOM(vrc), vrc,
+ tr("Failed to apply environment variable '%s', index %u (%Rrc)'"),
+ aEnvironment[idxError].c_str(), idxError, vrc);
+ }
+ else
+ hr = setErrorVrc(vrc, tr("Failed to set up the environment: %Rrc"), vrc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return hr;
+}
+
+HRESULT GuestSession::processGet(ULONG aPid, ComPtr<IGuestProcess> &aGuestProcess)
+
+{
+ if (aPid == 0)
+ return setError(E_INVALIDARG, tr("No valid process ID (PID) specified"));
+
+ LogFlowThisFunc(("PID=%RU32\n", aPid));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hr = S_OK;
+
+ ComObjPtr<GuestProcess> pProcess;
+ int rc = i_processGetByPID(aPid, &pProcess);
+ if (RT_FAILURE(rc))
+ hr = setError(E_INVALIDARG, tr("No process with PID %RU32 found"), aPid);
+
+ /* This will set (*aProcess) to NULL if pProgress is NULL. */
+ HRESULT hr2 = pProcess.queryInterfaceTo(aGuestProcess.asOutParam());
+ if (SUCCEEDED(hr))
+ hr = hr2;
+
+ LogFlowThisFunc(("aProcess=%p, hr=%Rhrc\n", (IGuestProcess*)aGuestProcess, hr));
+ return hr;
+}
+
+HRESULT GuestSession::symlinkCreate(const com::Utf8Str &aSource, const com::Utf8Str &aTarget, SymlinkType_T aType)
+{
+ RT_NOREF(aSource, aTarget, aType);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::symlinkExists(const com::Utf8Str &aSymlink, BOOL *aExists)
+
+{
+ RT_NOREF(aSymlink, aExists);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::symlinkRead(const com::Utf8Str &aSymlink, const std::vector<SymlinkReadFlag_T> &aFlags,
+ com::Utf8Str &aTarget)
+{
+ RT_NOREF(aSymlink, aFlags, aTarget);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::waitFor(ULONG aWaitFor, ULONG aTimeoutMS, GuestSessionWaitResult_T *aReason)
+{
+ /* Note: No call to i_isStartedExternal() needed here, as the session might not has been started (yet). */
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ /*
+ * Note: Do not hold any locks here while waiting!
+ */
+ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; GuestSessionWaitResult_T waitResult;
+ int vrc = i_waitFor(aWaitFor, aTimeoutMS, waitResult, &rcGuest);
+ if (RT_SUCCESS(vrc))
+ *aReason = waitResult;
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Session, rcGuest, mData.mSession.mName.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Waiting for guest process failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ case VERR_TIMEOUT:
+ *aReason = GuestSessionWaitResult_Timeout;
+ break;
+
+ default:
+ {
+ const char *pszSessionName = mData.mSession.mName.c_str();
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Waiting for guest session \"%s\" failed: %Rrc"),
+ pszSessionName ? pszSessionName : tr("Unnamed"), vrc);
+ break;
+ }
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestSession::waitForArray(const std::vector<GuestSessionWaitForFlag_T> &aWaitFor, ULONG aTimeoutMS,
+ GuestSessionWaitResult_T *aReason)
+{
+ /* Note: No call to i_isStartedExternal() needed here, as the session might not has been started (yet). */
+
+ LogFlowThisFuncEnter();
+
+ /*
+ * Note: Do not hold any locks here while waiting!
+ */
+ uint32_t fWaitFor = GuestSessionWaitForFlag_None;
+ for (size_t i = 0; i < aWaitFor.size(); i++)
+ fWaitFor |= aWaitFor[i];
+
+ return WaitFor(fWaitFor, aTimeoutMS, aReason);
+}
diff --git a/src/VBox/Main/src-client/GuestSessionImplTasks.cpp b/src/VBox/Main/src-client/GuestSessionImplTasks.cpp
new file mode 100644
index 00000000..d3112ff0
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestSessionImplTasks.cpp
@@ -0,0 +1,3307 @@
+/* $Id: GuestSessionImplTasks.cpp $ */
+/** @file
+ * VirtualBox Main - Guest session tasks.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_GUESTSESSION
+#include "LoggingNew.h"
+
+#include "GuestImpl.h"
+#ifndef VBOX_WITH_GUEST_CONTROL
+# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
+#endif
+#include "GuestSessionImpl.h"
+#include "GuestSessionImplTasks.h"
+#include "GuestCtrlImplPrivate.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+#include "ConsoleImpl.h"
+#include "ProgressImpl.h"
+
+#include <memory> /* For auto_ptr. */
+
+#include <iprt/env.h>
+#include <iprt/file.h> /* For CopyTo/From. */
+#include <iprt/dir.h>
+#include <iprt/path.h>
+#include <iprt/fsvfs.h>
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+
+/**
+ * (Guest Additions) ISO file flags.
+ * Needed for handling Guest Additions updates.
+ */
+#define ISOFILE_FLAG_NONE 0
+/** Copy over the file from host to the
+ * guest. */
+#define ISOFILE_FLAG_COPY_FROM_ISO RT_BIT(0)
+/** Execute file on the guest after it has
+ * been successfully transferred. */
+#define ISOFILE_FLAG_EXECUTE RT_BIT(7)
+/** File is optional, does not have to be
+ * existent on the .ISO. */
+#define ISOFILE_FLAG_OPTIONAL RT_BIT(8)
+
+
+// session task classes
+/////////////////////////////////////////////////////////////////////////////
+
+GuestSessionTask::GuestSessionTask(GuestSession *pSession)
+ : ThreadTask("GenericGuestSessionTask")
+{
+ mSession = pSession;
+
+ switch (mSession->i_getGuestPathStyle())
+ {
+ case PathStyle_DOS:
+ mstrGuestPathStyle = "\\";
+ break;
+
+ default:
+ mstrGuestPathStyle = "/";
+ break;
+ }
+}
+
+GuestSessionTask::~GuestSessionTask(void)
+{
+}
+
+/**
+ * Creates (and initializes / sets) the progress objects of a guest session task.
+ *
+ * @returns VBox status code.
+ * @param cOperations Number of operation the task wants to perform.
+ */
+int GuestSessionTask::createAndSetProgressObject(ULONG cOperations /* = 1 */)
+{
+ LogFlowThisFunc(("cOperations=%ld\n", cOperations));
+
+ /* Create the progress object. */
+ ComObjPtr<Progress> pProgress;
+ HRESULT hr = pProgress.createObject();
+ if (FAILED(hr))
+ return VERR_COM_UNEXPECTED;
+
+ hr = pProgress->init(static_cast<IGuestSession*>(mSession),
+ Bstr(mDesc).raw(),
+ TRUE /* aCancelable */, cOperations, Bstr(mDesc).raw());
+ if (FAILED(hr))
+ return VERR_COM_UNEXPECTED;
+
+ mProgress = pProgress;
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+#if 0 /* unused */
+/** @note The task object is owned by the thread after this returns, regardless of the result. */
+int GuestSessionTask::RunAsync(const Utf8Str &strDesc, ComObjPtr<Progress> &pProgress)
+{
+ LogFlowThisFunc(("strDesc=%s\n", strDesc.c_str()));
+
+ mDesc = strDesc;
+ mProgress = pProgress;
+ HRESULT hrc = createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER);
+
+ LogFlowThisFunc(("Returning hrc=%Rhrc\n", hrc));
+ return Global::vboxStatusCodeToCOM(hrc);
+}
+#endif
+
+/**
+ * Gets a guest property from the VM.
+ *
+ * @returns VBox status code.
+ * @param pGuest Guest object of VM to get guest property from.
+ * @param strPath Guest property to path to get.
+ * @param strValue Where to store the guest property value on success.
+ */
+int GuestSessionTask::getGuestProperty(const ComObjPtr<Guest> &pGuest,
+ const Utf8Str &strPath, Utf8Str &strValue)
+{
+ ComObjPtr<Console> pConsole = pGuest->i_getConsole();
+ const ComPtr<IMachine> pMachine = pConsole->i_machine();
+
+ Assert(!pMachine.isNull());
+ Bstr strTemp, strFlags;
+ LONG64 i64Timestamp;
+ HRESULT hr = pMachine->GetGuestProperty(Bstr(strPath).raw(),
+ strTemp.asOutParam(),
+ &i64Timestamp, strFlags.asOutParam());
+ if (SUCCEEDED(hr))
+ {
+ strValue = strTemp;
+ return VINF_SUCCESS;
+ }
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * Sets the percentage of a guest session task progress.
+ *
+ * @returns VBox status code.
+ * @param uPercent Percentage (0-100) to set.
+ */
+int GuestSessionTask::setProgress(ULONG uPercent)
+{
+ if (mProgress.isNull()) /* Progress is optional. */
+ return VINF_SUCCESS;
+
+ BOOL fCanceled;
+ if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
+ && fCanceled)
+ return VERR_CANCELLED;
+ BOOL fCompleted;
+ if ( SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted)))
+ && fCompleted)
+ {
+ AssertMsgFailed(("Setting value of an already completed progress\n"));
+ return VINF_SUCCESS;
+ }
+ HRESULT hr = mProgress->SetCurrentOperationProgress(uPercent);
+ if (FAILED(hr))
+ return VERR_COM_UNEXPECTED;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sets the task's progress object to succeeded.
+ *
+ * @returns VBox status code.
+ */
+int GuestSessionTask::setProgressSuccess(void)
+{
+ if (mProgress.isNull()) /* Progress is optional. */
+ return VINF_SUCCESS;
+
+ BOOL fCompleted;
+ if ( SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted)))
+ && !fCompleted)
+ {
+#ifdef VBOX_STRICT
+ ULONG uCurOp; mProgress->COMGETTER(Operation(&uCurOp));
+ ULONG cOps; mProgress->COMGETTER(OperationCount(&cOps));
+ AssertMsg(uCurOp + 1 /* Zero-based */ == cOps, ("Not all operations done yet (%u/%u)\n", uCurOp + 1, cOps));
+#endif
+ HRESULT hr = mProgress->i_notifyComplete(S_OK);
+ if (FAILED(hr))
+ return VERR_COM_UNEXPECTED; /** @todo Find a better rc. */
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sets the task's progress object to an error using a string message.
+ *
+ * @returns Returns \a hr for convenience.
+ * @param hr Progress operation result to set.
+ * @param strMsg Message to set.
+ */
+HRESULT GuestSessionTask::setProgressErrorMsg(HRESULT hr, const Utf8Str &strMsg)
+{
+ LogFlowFunc(("hr=%Rhrc, strMsg=%s\n", hr, strMsg.c_str()));
+
+ if (mProgress.isNull()) /* Progress is optional. */
+ return hr; /* Return original rc. */
+
+ BOOL fCanceled;
+ BOOL fCompleted;
+ if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
+ && !fCanceled
+ && SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted)))
+ && !fCompleted)
+ {
+ HRESULT hr2 = mProgress->i_notifyComplete(hr,
+ COM_IIDOF(IGuestSession),
+ GuestSession::getStaticComponentName(),
+ /* Make sure to hand-in the message via format string to avoid problems
+ * with (file) paths which e.g. contain "%s" and friends. Can happen with
+ * randomly generated Validation Kit stuff. */
+ "%s", strMsg.c_str());
+ if (FAILED(hr2))
+ return hr2;
+ }
+ return hr; /* Return original rc. */
+}
+
+/**
+ * Sets the task's progress object to an error using a string message and a guest error info object.
+ *
+ * @returns Returns \a hr for convenience.
+ * @param hr Progress operation result to set.
+ * @param strMsg Message to set.
+ * @param guestErrorInfo Guest error info to use.
+ */
+HRESULT GuestSessionTask::setProgressErrorMsg(HRESULT hr, const Utf8Str &strMsg, const GuestErrorInfo &guestErrorInfo)
+{
+ return setProgressErrorMsg(hr, strMsg + Utf8Str(": ") + GuestBase::getErrorAsString(guestErrorInfo));
+}
+
+/**
+ * Creates a directory on the guest.
+ *
+ * @return VBox status code.
+ * VINF_ALREADY_EXISTS if directory on the guest already exists (\a fCanExist is \c true).
+ * VWRN_ALREADY_EXISTS if directory on the guest already exists but must not exist (\a fCanExist is \c false).
+ * @param strPath Absolute path to directory on the guest (guest style path) to create.
+ * @param fMode Directory mode to use for creation.
+ * @param enmDirectoryCreateFlags Directory creation flags.
+ * @param fFollowSymlinks Whether to follow symlinks on the guest or not.
+ * @param fCanExist Whether the directory to create is allowed to exist already.
+ */
+int GuestSessionTask::directoryCreateOnGuest(const com::Utf8Str &strPath,
+ uint32_t fMode, DirectoryCreateFlag_T enmDirectoryCreateFlags,
+ bool fFollowSymlinks, bool fCanExist)
+{
+ LogFlowFunc(("strPath=%s, enmDirectoryCreateFlags=0x%x, fMode=%RU32, fFollowSymlinks=%RTbool, fCanExist=%RTbool\n",
+ strPath.c_str(), enmDirectoryCreateFlags, fMode, fFollowSymlinks, fCanExist));
+
+ GuestFsObjData objData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = mSession->i_directoryQueryInfo(strPath, fFollowSymlinks, objData, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!fCanExist)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest directory \"%s\" already exists"), strPath.c_str()));
+ vrc = VERR_ALREADY_EXISTS;
+ }
+ else
+ vrc = VWRN_ALREADY_EXISTS;
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ switch (vrcGuest)
+ {
+ case VERR_FILE_NOT_FOUND:
+ RT_FALL_THROUGH();
+ case VERR_PATH_NOT_FOUND:
+ vrc = mSession->i_directoryCreate(strPath.c_str(), fMode, enmDirectoryCreateFlags, &vrcGuest);
+ break;
+ default:
+ break;
+ }
+
+ if (RT_FAILURE(vrc))
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest error creating directory \"%s\" on the guest: %Rrc"),
+ strPath.c_str(), vrcGuest));
+ break;
+ }
+
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host error creating directory \"%s\" on the guest: %Rrc"),
+ strPath.c_str(), vrc));
+ break;
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Creates a directory on the host.
+ *
+ * @return VBox status code. VERR_ALREADY_EXISTS if directory on the guest already exists.
+ * @param strPath Absolute path to directory on the host (host style path) to create.
+ * @param fMode Directory mode to use for creation.
+ * @param fCreate Directory creation flags.
+ * @param fCanExist Whether the directory to create is allowed to exist already.
+ */
+int GuestSessionTask::directoryCreateOnHost(const com::Utf8Str &strPath, uint32_t fMode, uint32_t fCreate, bool fCanExist)
+{
+ LogFlowFunc(("strPath=%s, fMode=%RU32, fCreate=0x%x, fCanExist=%RTbool\n", strPath.c_str(), fMode, fCreate, fCanExist));
+
+ LogRel2(("Guest Control: Creating host directory \"%s\" ...\n", strPath.c_str()));
+
+ int vrc = RTDirCreate(strPath.c_str(), fMode, fCreate);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_ALREADY_EXISTS)
+ {
+ if (!fCanExist)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host directory \"%s\" already exists"), strPath.c_str()));
+ }
+ else
+ vrc = VINF_SUCCESS;
+ }
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Could not create host directory \"%s\": %Rrc"),
+ strPath.c_str(), vrc));
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Main function for copying a file from guest to the host.
+ *
+ * @return VBox status code.
+ * @param strSrcFile Full path of source file on the host to copy.
+ * @param srcFile Guest file (source) to copy to the host. Must be in opened and ready state already.
+ * @param strDstFile Full destination path and file name (guest style) to copy file to.
+ * @param phDstFile Pointer to host file handle (destination) to copy to. Must be in opened and ready state already.
+ * @param fFileCopyFlags File copy flags.
+ * @param offCopy Offset (in bytes) where to start copying the source file.
+ * @param cbSize Size (in bytes) to copy from the source file.
+ */
+int GuestSessionTask::fileCopyFromGuestInner(const Utf8Str &strSrcFile, ComObjPtr<GuestFile> &srcFile,
+ const Utf8Str &strDstFile, PRTFILE phDstFile,
+ FileCopyFlag_T fFileCopyFlags, uint64_t offCopy, uint64_t cbSize)
+{
+ RT_NOREF(fFileCopyFlags);
+
+ if (!cbSize) /* Nothing to copy, i.e. empty file? Bail out. */
+ return VINF_SUCCESS;
+
+ BOOL fCanceled = FALSE;
+ uint64_t cbWrittenTotal = 0;
+ uint64_t cbToRead = cbSize;
+
+ uint32_t uTimeoutMs = 30 * 1000; /* 30s timeout. */
+
+ int vrc = VINF_SUCCESS;
+
+ if (offCopy)
+ {
+ uint64_t offActual;
+ vrc = srcFile->i_seekAt(offCopy, GUEST_FILE_SEEKTYPE_BEGIN, uTimeoutMs, &offActual);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Seeking to offset %RU64 of guest file \"%s\" failed: %Rrc"),
+ offCopy, strSrcFile.c_str(), vrc));
+ return vrc;
+ }
+ }
+
+ BYTE byBuf[_64K]; /** @todo Can we do better here? */
+ while (cbToRead)
+ {
+ uint32_t cbRead;
+ const uint32_t cbChunk = RT_MIN(cbToRead, sizeof(byBuf));
+ vrc = srcFile->i_readData(cbChunk, uTimeoutMs, byBuf, sizeof(byBuf), &cbRead);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Reading %RU32 bytes @ %RU64 from guest \"%s\" failed: %Rrc", "", cbChunk),
+ cbChunk, cbWrittenTotal, strSrcFile.c_str(), vrc));
+ break;
+ }
+
+ vrc = RTFileWrite(*phDstFile, byBuf, cbRead, NULL /* No partial writes */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Writing %RU32 bytes to host file \"%s\" failed: %Rrc", "", cbRead),
+ cbRead, strDstFile.c_str(), vrc));
+ break;
+ }
+
+ AssertBreak(cbToRead >= cbRead);
+ cbToRead -= cbRead;
+
+ /* Update total bytes written to the guest. */
+ cbWrittenTotal += cbRead;
+ AssertBreak(cbWrittenTotal <= cbSize);
+
+ /* Did the user cancel the operation above? */
+ if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
+ && fCanceled)
+ break;
+
+ AssertBreakStmt(cbSize, vrc = VERR_INTERNAL_ERROR);
+ vrc = setProgress(((double)cbWrittenTotal / (double)cbSize) * 100);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
+ && fCanceled)
+ return VINF_SUCCESS;
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /*
+ * Even if we succeeded until here make sure to check whether we really transferred
+ * everything.
+ */
+ if (cbWrittenTotal == 0)
+ {
+ /* If nothing was transferred but the file size was > 0 then "vbox_cat" wasn't able to write
+ * to the destination -> access denied. */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Writing guest file \"%s\" to host file \"%s\" failed: Access denied"),
+ strSrcFile.c_str(), strDstFile.c_str()));
+ vrc = VERR_ACCESS_DENIED;
+ }
+ else if (cbWrittenTotal < cbSize)
+ {
+ /* If we did not copy all let the user know. */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Copying guest file \"%s\" to host file \"%s\" failed (%RU64/%RU64 bytes transferred)"),
+ strSrcFile.c_str(), strDstFile.c_str(), cbWrittenTotal, cbSize));
+ vrc = VERR_INTERRUPTED;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Closes a formerly opened guest file.
+ *
+ * @returns VBox status code.
+ * @param file Guest file to close.
+ *
+ * @note Set a progress error message on error.
+ */
+int GuestSessionTask::fileClose(const ComObjPtr<GuestFile> &file)
+{
+ int vrcGuest;
+ int vrc = file->i_closeFile(&vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ Utf8Str strFilename;
+ HRESULT const hrc = file->getFilename(strFilename);
+ AssertComRCReturn(hrc, VERR_OBJECT_DESTROYED);
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Error closing guest file \"%s\": %Rrc"),
+ strFilename.c_str(), vrc == VERR_GSTCTL_GUEST_ERROR ? vrcGuest : vrc));
+ if (RT_SUCCESS(vrc))
+ vrc = vrc == VERR_GSTCTL_GUEST_ERROR ? vrcGuest : vrc;
+ }
+
+ return vrc;
+}
+
+/**
+ * Copies a file from the guest to the host.
+ *
+ * @return VBox status code.
+ * @retval VWRN_ALREADY_EXISTS if the file already exists and FileCopyFlag_NoReplace is specified,
+ * *or * the file at the destination has the same (or newer) modification time
+ * and FileCopyFlag_Update is specified.
+ * @param strSrc Full path of source file on the guest to copy.
+ * @param strDst Full destination path and file name (host style) to copy file to.
+ * @param fFileCopyFlags File copy flags.
+ */
+int GuestSessionTask::fileCopyFromGuest(const Utf8Str &strSrc, const Utf8Str &strDst, FileCopyFlag_T fFileCopyFlags)
+{
+ LogFlowThisFunc(("strSource=%s, strDest=%s, enmFileCopyFlags=%#x\n", strSrc.c_str(), strDst.c_str(), fFileCopyFlags));
+
+ GuestFileOpenInfo srcOpenInfo;
+ srcOpenInfo.mFilename = strSrc;
+ srcOpenInfo.mOpenAction = FileOpenAction_OpenExisting;
+ srcOpenInfo.mAccessMode = FileAccessMode_ReadOnly;
+ srcOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */
+
+ ComObjPtr<GuestFile> srcFile;
+
+ GuestFsObjData srcObjData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = mSession->i_fsQueryInfo(strSrc, TRUE /* fFollowSymlinks */, srcObjData, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Guest file lookup failed"),
+ GuestErrorInfo(GuestErrorInfo::Type_ToolStat, vrcGuest, strSrc.c_str()));
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file lookup for \"%s\" failed: %Rrc"), strSrc.c_str(), vrc));
+ }
+ else
+ {
+ switch (srcObjData.mType)
+ {
+ case FsObjType_File:
+ break;
+
+ case FsObjType_Symlink:
+ if (!(fFileCopyFlags & FileCopyFlag_FollowLinks))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file \"%s\" is a symbolic link"),
+ strSrc.c_str()));
+ vrc = VERR_IS_A_SYMLINK;
+ }
+ break;
+
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest object \"%s\" is not a file (is type %#x)"),
+ strSrc.c_str(), srcObjData.mType));
+ vrc = VERR_NOT_A_FILE;
+ break;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ vrc = mSession->i_fileOpen(srcOpenInfo, srcFile, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Guest file could not be opened"),
+ GuestErrorInfo(GuestErrorInfo::Type_File, vrcGuest, strSrc.c_str()));
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file \"%s\" could not be opened: %Rrc"), strSrc.c_str(), vrc));
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ RTFSOBJINFO dstObjInfo;
+ RT_ZERO(dstObjInfo);
+
+ bool fSkip = false; /* Whether to skip handling the file. */
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathQueryInfo(strDst.c_str(), &dstObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ if (fFileCopyFlags & FileCopyFlag_NoReplace)
+ {
+ LogRel2(("Guest Control: Host file \"%s\" already exists, skipping\n", strDst.c_str()));
+ vrc = VWRN_ALREADY_EXISTS;
+ fSkip = true;
+ }
+
+ if ( !fSkip
+ && fFileCopyFlags & FileCopyFlag_Update)
+ {
+ RTTIMESPEC srcModificationTimeTS;
+ RTTimeSpecSetSeconds(&srcModificationTimeTS, srcObjData.mModificationTime);
+ if (RTTimeSpecCompare(&srcModificationTimeTS, &dstObjInfo.ModificationTime) <= 0)
+ {
+ LogRel2(("Guest Control: Host file \"%s\" has same or newer modification date, skipping\n", strDst.c_str()));
+ vrc = VWRN_ALREADY_EXISTS;
+ fSkip = true;
+ }
+ }
+ }
+ else
+ {
+ if (vrc == VERR_PATH_NOT_FOUND) /* Destination file does not exist (yet)? */
+ vrc = VERR_FILE_NOT_FOUND; /* Needed in next block further down. */
+ else if (vrc != VERR_FILE_NOT_FOUND) /* Ditto. */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host file lookup for \"%s\" failed: %Rrc"), strDst.c_str(), vrc));
+ }
+ }
+
+ if (fSkip)
+ {
+ int vrc2 = fileClose(srcFile);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ return vrc;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (RTFS_IS_FILE(dstObjInfo.Attr.fMode))
+ {
+ if (fFileCopyFlags & FileCopyFlag_NoReplace)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Host file \"%s\" already exists"), strDst.c_str()));
+ vrc = VERR_ALREADY_EXISTS;
+ }
+ }
+ else if (RTFS_IS_DIRECTORY(dstObjInfo.Attr.fMode))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Host destination \"%s\" is a directory"), strDst.c_str()));
+ vrc = VERR_IS_A_DIRECTORY;
+ }
+ else if (RTFS_IS_SYMLINK(dstObjInfo.Attr.fMode))
+ {
+ if (!(fFileCopyFlags & FileCopyFlag_FollowLinks))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Host destination \"%s\" is a symbolic link"), strDst.c_str()));
+ vrc = VERR_IS_A_SYMLINK;
+ }
+ }
+ else
+ {
+ LogFlowThisFunc(("Host file system type %#x not supported\n", dstObjInfo.Attr.fMode & RTFS_TYPE_MASK));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ LogFlowFunc(("vrc=%Rrc, dstFsType=%#x, pszDstFile=%s\n", vrc, dstObjInfo.Attr.fMode & RTFS_TYPE_MASK, strDst.c_str()));
+
+ if ( RT_SUCCESS(vrc)
+ || vrc == VERR_FILE_NOT_FOUND)
+ {
+ LogRel2(("Guest Control: Copying file \"%s\" from guest to \"%s\" on host ...\n", strSrc.c_str(), strDst.c_str()));
+
+ RTFILE hDstFile;
+ vrc = RTFileOpen(&hDstFile, strDst.c_str(),
+ RTFILE_O_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_DENY_WRITE); /** @todo Use the correct open modes! */
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("Copying \"%s\" to \"%s\" (%RI64 bytes) ...\n",
+ strSrc.c_str(), strDst.c_str(), srcObjData.mObjectSize));
+
+ vrc = fileCopyFromGuestInner(strSrc, srcFile, strDst, &hDstFile, fFileCopyFlags,
+ 0 /* Offset, unused */, (uint64_t)srcObjData.mObjectSize);
+
+ int vrc2 = RTFileClose(hDstFile);
+ AssertRC(vrc2);
+ }
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Opening/creating host file \"%s\" failed: %Rrc"), strDst.c_str(), vrc));
+ }
+
+ int vrc2 = fileClose(srcFile);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Main function for copying a file from host to the guest.
+ *
+ * @return VBox status code.
+ * @param strSrcFile Full path of source file on the host to copy.
+ * @param hVfsFile The VFS file handle to read from.
+ * @param strDstFile Full destination path and file name (guest style) to copy file to.
+ * @param fileDst Guest file (destination) to copy to the guest. Must be in opened and ready state already.
+ * @param fFileCopyFlags File copy flags.
+ * @param offCopy Offset (in bytes) where to start copying the source file.
+ * @param cbSize Size (in bytes) to copy from the source file.
+ */
+int GuestSessionTask::fileCopyToGuestInner(const Utf8Str &strSrcFile, RTVFSFILE hVfsFile,
+ const Utf8Str &strDstFile, ComObjPtr<GuestFile> &fileDst,
+ FileCopyFlag_T fFileCopyFlags, uint64_t offCopy, uint64_t cbSize)
+{
+ RT_NOREF(fFileCopyFlags);
+
+ if (!cbSize) /* Nothing to copy, i.e. empty file? Bail out. */
+ return VINF_SUCCESS;
+
+ BOOL fCanceled = FALSE;
+ uint64_t cbWrittenTotal = 0;
+ uint64_t cbToRead = cbSize;
+
+ uint32_t uTimeoutMs = 30 * 1000; /* 30s timeout. */
+
+ int vrc = VINF_SUCCESS;
+
+ if (offCopy)
+ {
+ uint64_t offActual;
+ vrc = RTVfsFileSeek(hVfsFile, offCopy, RTFILE_SEEK_END, &offActual);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Seeking to offset %RU64 of host file \"%s\" failed: %Rrc"),
+ offCopy, strSrcFile.c_str(), vrc));
+ return vrc;
+ }
+ }
+
+ BYTE byBuf[_64K];
+ while (cbToRead)
+ {
+ size_t cbRead;
+ const uint32_t cbChunk = RT_MIN(cbToRead, sizeof(byBuf));
+ vrc = RTVfsFileRead(hVfsFile, byBuf, cbChunk, &cbRead);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Reading %RU32 bytes @ %RU64 from host file \"%s\" failed: %Rrc"),
+ cbChunk, cbWrittenTotal, strSrcFile.c_str(), vrc));
+ break;
+ }
+
+ vrc = fileDst->i_writeData(uTimeoutMs, byBuf, (uint32_t)cbRead, NULL /* No partial writes */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Writing %zu bytes to guest file \"%s\" failed: %Rrc"),
+ cbRead, strDstFile.c_str(), vrc));
+ break;
+ }
+
+ Assert(cbToRead >= cbRead);
+ cbToRead -= cbRead;
+
+ /* Update total bytes written to the guest. */
+ cbWrittenTotal += cbRead;
+ Assert(cbWrittenTotal <= cbSize);
+
+ /* Did the user cancel the operation above? */
+ if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
+ && fCanceled)
+ break;
+
+ AssertBreakStmt(cbSize, vrc = VERR_INTERNAL_ERROR);
+ vrc = setProgress(((double)cbWrittenTotal / (double)cbSize) * 100);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /*
+ * Even if we succeeded until here make sure to check whether we really transferred
+ * everything.
+ */
+ if (cbWrittenTotal == 0)
+ {
+ /* If nothing was transferred but the file size was > 0 then "vbox_cat" wasn't able to write
+ * to the destination -> access denied. */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Writing to guest file \"%s\" failed: Access denied"),
+ strDstFile.c_str()));
+ vrc = VERR_ACCESS_DENIED;
+ }
+ else if (cbWrittenTotal < cbSize)
+ {
+ /* If we did not copy all let the user know. */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Copying to guest file \"%s\" failed (%RU64/%RU64 bytes transferred)"),
+ strDstFile.c_str(), cbWrittenTotal, cbSize));
+ vrc = VERR_INTERRUPTED;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Copies a file from the host to the guest.
+ *
+ * @return VBox status code.
+ * @retval VWRN_ALREADY_EXISTS if the file already exists and FileCopyFlag_NoReplace is specified,
+ * *or * the file at the destination has the same (or newer) modification time
+ * and FileCopyFlag_Update is specified.
+ * @param strSrc Full path of source file on the host.
+ * @param strDst Full destination path and file name (guest style) to copy file to. Guest-path style.
+ * @param fFileCopyFlags File copy flags.
+ */
+int GuestSessionTask::fileCopyToGuest(const Utf8Str &strSrc, const Utf8Str &strDst, FileCopyFlag_T fFileCopyFlags)
+{
+ LogFlowThisFunc(("strSource=%s, strDst=%s, fFileCopyFlags=%#x\n", strSrc.c_str(), strDst.c_str(), fFileCopyFlags));
+
+ GuestFileOpenInfo dstOpenInfo;
+ dstOpenInfo.mFilename = strDst;
+ if (fFileCopyFlags & FileCopyFlag_NoReplace)
+ dstOpenInfo.mOpenAction = FileOpenAction_CreateNew;
+ else
+ dstOpenInfo.mOpenAction = FileOpenAction_CreateOrReplace;
+ dstOpenInfo.mAccessMode = FileAccessMode_WriteOnly;
+ dstOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */
+
+ ComObjPtr<GuestFile> dstFile;
+ int vrcGuest;
+ int vrc = mSession->i_fileOpen(dstOpenInfo, dstFile, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file \"%s\" could not be created or replaced"), strDst.c_str()),
+ GuestErrorInfo(GuestErrorInfo::Type_File, vrcGuest, strDst.c_str()));
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file \"%s\" could not be created or replaced: %Rrc"), strDst.c_str(), vrc));
+ return vrc;
+ }
+
+ char szSrcReal[RTPATH_MAX];
+
+ RTFSOBJINFO srcObjInfo;
+ RT_ZERO(srcObjInfo);
+
+ bool fSkip = false; /* Whether to skip handling the file. */
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathReal(strSrc.c_str(), szSrcReal, sizeof(szSrcReal));
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host path lookup for file \"%s\" failed: %Rrc"),
+ strSrc.c_str(), vrc));
+ }
+ else
+ {
+ vrc = RTPathQueryInfo(szSrcReal, &srcObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Only perform a remote file query when needed. */
+ if ( (fFileCopyFlags & FileCopyFlag_Update)
+ || (fFileCopyFlags & FileCopyFlag_NoReplace))
+ {
+ GuestFsObjData dstObjData;
+ vrc = mSession->i_fileQueryInfo(strDst, RT_BOOL(fFileCopyFlags & FileCopyFlag_FollowLinks), dstObjData,
+ &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ if (fFileCopyFlags & FileCopyFlag_NoReplace)
+ {
+ LogRel2(("Guest Control: Guest file \"%s\" already exists, skipping\n", strDst.c_str()));
+ vrc = VWRN_ALREADY_EXISTS;
+ fSkip = true;
+ }
+
+ if ( !fSkip
+ && fFileCopyFlags & FileCopyFlag_Update)
+ {
+ RTTIMESPEC dstModificationTimeTS;
+ RTTimeSpecSetSeconds(&dstModificationTimeTS, dstObjData.mModificationTime);
+ if (RTTimeSpecCompare(&dstModificationTimeTS, &srcObjInfo.ModificationTime) <= 0)
+ {
+ LogRel2(("Guest Control: Guest file \"%s\" has same or newer modification date, skipping\n",
+ strDst.c_str()));
+ vrc = VWRN_ALREADY_EXISTS;
+ fSkip = true;
+ }
+ }
+ }
+ else
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ {
+ switch (vrcGuest)
+ {
+ case VERR_FILE_NOT_FOUND:
+ vrc = VINF_SUCCESS;
+ break;
+
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest error while determining object data for guest file \"%s\": %Rrc"),
+ strDst.c_str(), vrcGuest));
+ break;
+ }
+ }
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host error while determining object data for guest file \"%s\": %Rrc"),
+ strDst.c_str(), vrc));
+ }
+ }
+ }
+ else
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host source file lookup for \"%s\" failed: %Rrc"),
+ szSrcReal, vrc));
+ }
+ }
+ }
+
+ if (fSkip)
+ {
+ int vrc2 = fileClose(dstFile);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ return vrc;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel2(("Guest Control: Copying file \"%s\" from host to \"%s\" on guest ...\n", strSrc.c_str(), strDst.c_str()));
+
+ RTVFSFILE hSrcFile;
+ vrc = RTVfsFileOpenNormal(szSrcReal, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hSrcFile);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("Copying \"%s\" to \"%s\" (%RI64 bytes) ...\n",
+ szSrcReal, strDst.c_str(), srcObjInfo.cbObject));
+
+ vrc = fileCopyToGuestInner(szSrcReal, hSrcFile, strDst, dstFile,
+ fFileCopyFlags, 0 /* Offset, unused */, srcObjInfo.cbObject);
+
+ int vrc2 = RTVfsFileRelease(hSrcFile);
+ AssertRC(vrc2);
+ }
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Opening host file \"%s\" failed: %Rrc"),
+ szSrcReal, vrc));
+ }
+
+ int vrc2 = fileClose(dstFile);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Adds a guest file system entry to a given list.
+ *
+ * @return VBox status code.
+ * @param strFile Path to file system entry to add.
+ * @param fsObjData Guest file system information of entry to add.
+ */
+int FsList::AddEntryFromGuest(const Utf8Str &strFile, const GuestFsObjData &fsObjData)
+{
+ LogFlowFunc(("Adding \"%s\"\n", strFile.c_str()));
+
+ FsEntry *pEntry = NULL;
+ try
+ {
+ pEntry = new FsEntry();
+ pEntry->fMode = fsObjData.GetFileMode();
+ pEntry->strPath = strFile;
+
+ mVecEntries.push_back(pEntry);
+ }
+ catch (std::bad_alloc &)
+ {
+ if (pEntry)
+ delete pEntry;
+ return VERR_NO_MEMORY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Adds a host file system entry to a given list.
+ *
+ * @return VBox status code.
+ * @param strFile Path to file system entry to add.
+ * @param pcObjInfo File system information of entry to add.
+ */
+int FsList::AddEntryFromHost(const Utf8Str &strFile, PCRTFSOBJINFO pcObjInfo)
+{
+ LogFlowFunc(("Adding \"%s\"\n", strFile.c_str()));
+
+ FsEntry *pEntry = NULL;
+ try
+ {
+ pEntry = new FsEntry();
+ pEntry->fMode = pcObjInfo->Attr.fMode;
+ pEntry->strPath = strFile;
+
+ mVecEntries.push_back(pEntry);
+ }
+ catch (std::bad_alloc &)
+ {
+ if (pEntry)
+ delete pEntry;
+ return VERR_NO_MEMORY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+FsList::FsList(const GuestSessionTask &Task)
+ : mTask(Task)
+{
+}
+
+FsList::~FsList()
+{
+ Destroy();
+}
+
+/**
+ * Initializes a file list.
+ *
+ * @return VBox status code.
+ * @param strSrcRootAbs Source root path (absolute) for this file list.
+ * @param strDstRootAbs Destination root path (absolute) for this file list.
+ * @param SourceSpec Source specification to use.
+ */
+int FsList::Init(const Utf8Str &strSrcRootAbs, const Utf8Str &strDstRootAbs,
+ const GuestSessionFsSourceSpec &SourceSpec)
+{
+ mSrcRootAbs = strSrcRootAbs;
+ mDstRootAbs = strDstRootAbs;
+ mSourceSpec = SourceSpec;
+
+ /* Note: Leave the source and dest roots unmodified -- how paths will be treated
+ * will be done directly when working on those. See @bugref{10139}. */
+
+ LogFlowFunc(("mSrcRootAbs=%s, mDstRootAbs=%s, fDirCopyFlags=%#x, fFileCopyFlags=%#x\n",
+ mSrcRootAbs.c_str(), mDstRootAbs.c_str(), mSourceSpec.fDirCopyFlags, mSourceSpec.fFileCopyFlags));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a file list.
+ */
+void FsList::Destroy(void)
+{
+ LogFlowFuncEnter();
+
+ FsEntries::iterator itEntry = mVecEntries.begin();
+ while (itEntry != mVecEntries.end())
+ {
+ FsEntry *pEntry = *itEntry;
+ delete pEntry;
+ mVecEntries.erase(itEntry);
+ itEntry = mVecEntries.begin();
+ }
+
+ Assert(mVecEntries.empty());
+
+ LogFlowFuncLeave();
+}
+
+#ifdef DEBUG
+/**
+ * Dumps a FsList to the debug log.
+ */
+void FsList::DumpToLog(void)
+{
+ LogFlowFunc(("strSrcRootAbs=%s, strDstRootAbs=%s\n", mSrcRootAbs.c_str(), mDstRootAbs.c_str()));
+
+ FsEntries::iterator itEntry = mVecEntries.begin();
+ while (itEntry != mVecEntries.end())
+ {
+ FsEntry *pEntry = *itEntry;
+ LogFlowFunc(("\tstrPath=%s (fMode %#x)\n", pEntry->strPath.c_str(), pEntry->fMode));
+ ++itEntry;
+ }
+
+ LogFlowFuncLeave();
+}
+#endif /* DEBUG */
+
+/**
+ * Builds a guest file list from a given path (and optional filter).
+ *
+ * @return VBox status code.
+ * @param strPath Directory on the guest to build list from.
+ * @param strSubDir Current sub directory path; needed for recursion.
+ * Set to an empty path.
+ */
+int FsList::AddDirFromGuest(const Utf8Str &strPath, const Utf8Str &strSubDir /* = "" */)
+{
+ Utf8Str strPathAbs = strPath;
+ if (!strPathAbs.endsWith(PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle)))
+ strPathAbs += PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle);
+
+ Utf8Str strPathSub = strSubDir;
+ if ( strPathSub.isNotEmpty()
+ && !strPathSub.endsWith(PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle)))
+ strPathSub += PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle);
+
+ strPathAbs += strPathSub;
+
+ LogFlowFunc(("Entering \"%s\" (sub \"%s\")\n", strPathAbs.c_str(), strPathSub.c_str()));
+
+ LogRel2(("Guest Control: Handling directory \"%s\" on guest ...\n", strPathAbs.c_str()));
+
+ GuestDirectoryOpenInfo dirOpenInfo;
+ dirOpenInfo.mFilter = "";
+ dirOpenInfo.mPath = strPathAbs;
+ dirOpenInfo.mFlags = 0; /** @todo Handle flags? */
+
+ const ComObjPtr<GuestSession> &pSession = mTask.GetSession();
+
+ ComObjPtr <GuestDirectory> pDir;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = pSession->i_directoryOpen(dirOpenInfo, pDir, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_INVALID_PARAMETER:
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ break;
+
+ default:
+ break;
+ }
+
+ return vrc;
+ }
+
+ if (strPathSub.isNotEmpty())
+ {
+ GuestFsObjData fsObjData;
+ fsObjData.mType = FsObjType_Directory;
+
+ vrc = AddEntryFromGuest(strPathSub, fsObjData);
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ ComObjPtr<GuestFsObjInfo> fsObjInfo;
+ while (RT_SUCCESS(vrc = pDir->i_read(fsObjInfo, &vrcGuest)))
+ {
+ FsObjType_T enmObjType = FsObjType_Unknown; /* Shut up MSC. */
+ HRESULT hrc2 = fsObjInfo->COMGETTER(Type)(&enmObjType);
+ AssertComRC(hrc2);
+
+ com::Bstr bstrName;
+ hrc2 = fsObjInfo->COMGETTER(Name)(bstrName.asOutParam());
+ AssertComRC(hrc2);
+
+ Utf8Str strEntry = strPathSub + Utf8Str(bstrName);
+
+ LogFlowFunc(("Entry \"%s\"\n", strEntry.c_str()));
+
+ switch (enmObjType)
+ {
+ case FsObjType_Directory:
+ {
+ if ( bstrName.equals(".")
+ || bstrName.equals(".."))
+ {
+ break;
+ }
+
+ LogRel2(("Guest Control: Directory \"%s\"\n", strEntry.c_str()));
+
+ if (!(mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_Recursive))
+ break;
+
+ vrc = AddDirFromGuest(strPath, strEntry);
+ break;
+ }
+
+ case FsObjType_Symlink:
+ {
+ if ( mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks
+ || mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks)
+ {
+ /** @todo Symlink handling from guest is not implemented yet.
+ * See IGuestSession::symlinkRead(). */
+ LogRel2(("Guest Control: Warning: Symlink support on guest side not available, skipping \"%s\"\n",
+ strEntry.c_str()));
+ }
+ break;
+ }
+
+ case FsObjType_File:
+ {
+ LogRel2(("Guest Control: File \"%s\"\n", strEntry.c_str()));
+
+ vrc = AddEntryFromGuest(strEntry, fsObjInfo->i_getData());
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ if (vrc == VERR_NO_MORE_FILES) /* End of listing reached? */
+ vrc = VINF_SUCCESS;
+ }
+
+ int vrc2 = pDir->i_closeInternal(&vrcGuest);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ return vrc;
+}
+
+/**
+ * Builds a host file list from a given path.
+ *
+ * @return VBox status code.
+ * @param strPath Directory on the host to build list from.
+ * @param strSubDir Current sub directory path; needed for recursion.
+ * Set to an empty path.
+ * @param pszPathReal Scratch buffer for holding the resolved real path.
+ * Needed for recursion.
+ * @param cbPathReal Size (in bytes) of \a pszPathReal.
+ * @param pDirEntry Where to store looked up directory information for handled paths.
+ * Needed for recursion.
+ */
+int FsList::AddDirFromHost(const Utf8Str &strPath, const Utf8Str &strSubDir,
+ char *pszPathReal, size_t cbPathReal, PRTDIRENTRYEX pDirEntry)
+{
+ Utf8Str strPathAbs = strPath;
+ if (!strPathAbs.endsWith(RTPATH_SLASH_STR))
+ strPathAbs += RTPATH_SLASH_STR;
+
+ Utf8Str strPathSub = strSubDir;
+ if ( strPathSub.isNotEmpty()
+ && !strPathSub.endsWith(RTPATH_SLASH_STR))
+ strPathSub += RTPATH_SLASH_STR;
+
+ strPathAbs += strPathSub;
+
+ LogFlowFunc(("Entering \"%s\" (sub \"%s\")\n", strPathAbs.c_str(), strPathSub.c_str()));
+
+ LogRel2(("Guest Control: Handling directory \"%s\" on host ...\n", strPathAbs.c_str()));
+
+ RTFSOBJINFO objInfo;
+ int vrc = RTPathQueryInfo(strPathAbs.c_str(), &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ if (strPathSub.isNotEmpty())
+ vrc = AddEntryFromHost(strPathSub, &objInfo);
+
+ if (RT_SUCCESS(vrc))
+ {
+ RTDIR hDir;
+ vrc = RTDirOpen(&hDir, strPathAbs.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ do
+ {
+ /* Retrieve the next directory entry. */
+ vrc = RTDirReadEx(hDir, pDirEntry, NULL, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_NO_MORE_FILES)
+ vrc = VINF_SUCCESS;
+ break;
+ }
+
+ Utf8Str strEntry = strPathSub + Utf8Str(pDirEntry->szName);
+
+ LogFlowFunc(("Entry \"%s\"\n", strEntry.c_str()));
+
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ /* Skip "." and ".." entries. */
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ break;
+
+ LogRel2(("Guest Control: Directory \"%s\"\n", strEntry.c_str()));
+
+ if (!(mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_Recursive))
+ break;
+
+ vrc = AddDirFromHost(strPath, strEntry, pszPathReal, cbPathReal, pDirEntry);
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ LogRel2(("Guest Control: File \"%s\"\n", strEntry.c_str()));
+
+ vrc = AddEntryFromHost(strEntry, &pDirEntry->Info);
+ break;
+ }
+
+ case RTFS_TYPE_SYMLINK:
+ {
+ Utf8Str strEntryAbs = strPathAbs + (const char *)pDirEntry->szName;
+
+ vrc = RTPathReal(strEntryAbs.c_str(), pszPathReal, cbPathReal);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathQueryInfo(pszPathReal, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ LogRel2(("Guest Control: Symbolic link \"%s\" -> \"%s\" (directory)\n",
+ strEntryAbs.c_str(), pszPathReal));
+ if (mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks)
+ vrc = AddDirFromHost(strPath, strEntry, pszPathReal, cbPathReal, pDirEntry);
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ LogRel2(("Guest Control: Symbolic link \"%s\" -> \"%s\" (file)\n",
+ strEntryAbs.c_str(), pszPathReal));
+ if (mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks)
+ vrc = AddEntryFromHost(strEntry, &objInfo);
+ }
+ else
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ if (RT_FAILURE(vrc))
+ LogRel2(("Guest Control: Unable to query symbolic link info for \"%s\", rc=%Rrc\n",
+ pszPathReal, vrc));
+ }
+ else
+ {
+ LogRel2(("Guest Control: Unable to resolve symlink for \"%s\", rc=%Rrc\n", strPathAbs.c_str(), vrc));
+ if (vrc == VERR_FILE_NOT_FOUND) /* Broken symlink, skip. */
+ vrc = VINF_SUCCESS;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ } while (RT_SUCCESS(vrc));
+
+ RTDirClose(hDir);
+ }
+ }
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ vrc = VERR_IS_A_FILE;
+ else if (RTFS_IS_SYMLINK(objInfo.Attr.fMode))
+ vrc = VERR_IS_A_SYMLINK;
+ else
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ else
+ LogFlowFunc(("Unable to query \"%s\", rc=%Rrc\n", strPathAbs.c_str(), vrc));
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+GuestSessionTaskOpen::GuestSessionTaskOpen(GuestSession *pSession, uint32_t uFlags, uint32_t uTimeoutMS)
+ : GuestSessionTask(pSession)
+ , mFlags(uFlags)
+ , mTimeoutMS(uTimeoutMS)
+{
+ m_strTaskName = "gctlSesOpen";
+}
+
+GuestSessionTaskOpen::~GuestSessionTaskOpen(void)
+{
+
+}
+
+/** @copydoc GuestSessionTask::Run */
+int GuestSessionTaskOpen::Run(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(mSession);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ int vrc = mSession->i_startSession(NULL /*pvrcGuest*/);
+ /* Nothing to do here anymore. */
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+GuestSessionCopyTask::GuestSessionCopyTask(GuestSession *pSession)
+ : GuestSessionTask(pSession)
+{
+}
+
+GuestSessionCopyTask::~GuestSessionCopyTask()
+{
+ FsLists::iterator itList = mVecLists.begin();
+ while (itList != mVecLists.end())
+ {
+ FsList *pFsList = (*itList);
+ pFsList->Destroy();
+ delete pFsList;
+ mVecLists.erase(itList);
+ itList = mVecLists.begin();
+ }
+
+ Assert(mVecLists.empty());
+}
+
+GuestSessionTaskCopyFrom::GuestSessionTaskCopyFrom(GuestSession *pSession, GuestSessionFsSourceSet const &vecSrc,
+ const Utf8Str &strDest)
+ : GuestSessionCopyTask(pSession)
+{
+ m_strTaskName = "gctlCpyFrm";
+
+ mSources = vecSrc;
+ mDest = strDest;
+}
+
+GuestSessionTaskCopyFrom::~GuestSessionTaskCopyFrom(void)
+{
+}
+
+/**
+ * Initializes a copy-from-guest task.
+ *
+ * @returns HRESULT
+ * @param strTaskDesc Friendly task description.
+ */
+HRESULT GuestSessionTaskCopyFrom::Init(const Utf8Str &strTaskDesc)
+{
+ setTaskDesc(strTaskDesc);
+
+ /* Create the progress object. */
+ ComObjPtr<Progress> pProgress;
+ HRESULT hrc = pProgress.createObject();
+ if (FAILED(hrc))
+ return hrc;
+
+ mProgress = pProgress;
+
+ int vrc = VINF_SUCCESS;
+
+ ULONG cOperations = 0;
+ Utf8Str strErrorInfo;
+
+ /**
+ * Note: We need to build up the file/directory here instead of GuestSessionTaskCopyFrom::Run
+ * because the caller expects a ready-for-operation progress object on return.
+ * The progress object will have a variable operation count, based on the elements to
+ * be processed.
+ */
+
+ if (mSources.empty())
+ {
+ strErrorInfo.printf(tr("No guest sources specified"));
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else if (mDest.isEmpty())
+ {
+ strErrorInfo.printf(tr("Host destination must not be empty"));
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ GuestSessionFsSourceSet::iterator itSrc = mSources.begin();
+ while (itSrc != mSources.end())
+ {
+ Utf8Str strSrc = itSrc->strSource;
+ Utf8Str strDst = mDest;
+
+ bool fFollowSymlinks;
+
+ if (strSrc.isEmpty())
+ {
+ strErrorInfo.printf(tr("Guest source entry must not be empty"));
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ if (itSrc->enmType == FsObjType_Directory)
+ {
+ fFollowSymlinks = itSrc->fDirCopyFlags & DirectoryCopyFlag_FollowLinks;
+ }
+ else
+ {
+ fFollowSymlinks = RT_BOOL(itSrc->fFileCopyFlags & FileCopyFlag_FollowLinks);
+ }
+
+ LogFlowFunc(("strSrc=%s (path style is %s), strDst=%s, fFollowSymlinks=%RTbool\n",
+ strSrc.c_str(), GuestBase::pathStyleToStr(itSrc->enmPathStyle), strDst.c_str(), fFollowSymlinks));
+
+ GuestFsObjData srcObjData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ vrc = mSession->i_fsQueryInfo(strSrc, fFollowSymlinks, srcObjData, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ strErrorInfo = GuestBase::getErrorAsString(tr("Guest source lookup failed"),
+ GuestErrorInfo(GuestErrorInfo::Type_ToolStat, vrcGuest, strSrc.c_str()));
+ else
+ strErrorInfo.printf(tr("Guest source lookup for \"%s\" failed: %Rrc"),
+ strSrc.c_str(), vrc);
+ break;
+ }
+
+ if (srcObjData.mType == FsObjType_Directory)
+ {
+ if (itSrc->enmType != FsObjType_Directory)
+ {
+ strErrorInfo.printf(tr("Guest source is not a file: %s"), strSrc.c_str());
+ vrc = VERR_NOT_A_FILE;
+ break;
+ }
+ }
+ else
+ {
+ if (itSrc->enmType != FsObjType_File)
+ {
+ strErrorInfo.printf(tr("Guest source is not a directory: %s"), strSrc.c_str());
+ vrc = VERR_NOT_A_DIRECTORY;
+ break;
+ }
+ }
+
+ FsList *pFsList = NULL;
+ try
+ {
+ pFsList = new FsList(*this);
+ vrc = pFsList->Init(strSrc, strDst, *itSrc);
+ if (RT_SUCCESS(vrc))
+ {
+ switch (itSrc->enmType)
+ {
+ case FsObjType_Directory:
+ {
+ vrc = pFsList->AddDirFromGuest(strSrc);
+ break;
+ }
+
+ case FsObjType_File:
+ /* The file name is already part of the actual list's source root (strSrc). */
+ break;
+
+ default:
+ LogRel2(("Guest Control: Warning: Unknown guest file system type %#x for source \"%s\", skipping\n",
+ itSrc->enmType, strSrc.c_str()));
+ break;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ delete pFsList;
+ strErrorInfo.printf(tr("Error adding guest source \"%s\" to list: %Rrc"),
+ strSrc.c_str(), vrc);
+ break;
+ }
+#ifdef DEBUG
+ pFsList->DumpToLog();
+#endif
+ mVecLists.push_back(pFsList);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+
+ AssertPtr(pFsList);
+ cOperations += (ULONG)pFsList->mVecEntries.size();
+
+ itSrc++;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* When there are no entries in the first source list, this means the source only contains a single file
+ * (see \a mSrcRootAbs of FsList). So use \a mSrcRootAbs directly. */
+ Utf8Str const &strFirstOp = mVecLists[0]->mVecEntries.size() > 0
+ ? mVecLists[0]->mVecEntries[0]->strPath : mVecLists[0]->mSrcRootAbs;
+
+ /* Now that we know how many objects we're handling, tweak the progress description so that it
+ * reflects more accurately what the progress is actually doing. */
+ if (cOperations > 1)
+ {
+ mDesc.printf(tr("Copying \"%s\" [and %zu %s] from guest to \"%s\" on the host ..."),
+ strFirstOp.c_str(), cOperations - 1, cOperations > 2 ? tr("others") : tr("other"), mDest.c_str());
+ }
+ else
+ mDesc.printf(tr("Copying \"%s\" from guest to \"%s\" on the host ..."), strFirstOp.c_str(), mDest.c_str());
+
+ hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(),
+ TRUE /* aCancelable */, cOperations + 1 /* Number of operations */, Bstr(strFirstOp).raw());
+ }
+ else /* On error we go with an "empty" progress object when will be used for error handling. */
+ hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(),
+ TRUE /* aCancelable */, 1 /* cOperations */, Bstr(mDesc).raw());
+
+ if (FAILED(hrc)) /* Progress object creation failed -- we're doomed. */
+ return hrc;
+
+ if (RT_FAILURE(vrc))
+ {
+ if (strErrorInfo.isEmpty())
+ strErrorInfo.printf(tr("Failed with %Rrc"), vrc);
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, strErrorInfo);
+ }
+
+ LogFlowFunc(("Returning %Rhrc (%Rrc)\n", hrc, vrc));
+ return hrc;
+}
+
+/** @copydoc GuestSessionTask::Run */
+int GuestSessionTaskCopyFrom::Run(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(mSession);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ int vrc = VINF_SUCCESS;
+
+ FsLists::const_iterator itList = mVecLists.begin();
+ while (itList != mVecLists.end())
+ {
+ FsList *pList = *itList;
+ AssertPtr(pList);
+
+ LogFlowFunc(("List: srcRootAbs=%s, dstRootAbs=%s\n", pList->mSrcRootAbs.c_str(), pList->mDstRootAbs.c_str()));
+
+ Utf8Str strSrcRootAbs = pList->mSrcRootAbs;
+ Utf8Str strDstRootAbs = pList->mDstRootAbs;
+
+ vrc = GuestPath::BuildDestinationPath(strSrcRootAbs, mSession->i_getGuestPathStyle() /* Source */,
+ strDstRootAbs, PATH_STYLE_NATIVE /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Building host destination root path \"%s\" failed: %Rrc"),
+ strDstRootAbs.c_str(), vrc));
+ break;
+ }
+
+ bool fCopyIntoExisting;
+ bool fFollowSymlinks;
+
+ if (pList->mSourceSpec.enmType == FsObjType_Directory)
+ {
+ fCopyIntoExisting = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_CopyIntoExisting);
+ fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks);
+ }
+ else if (pList->mSourceSpec.enmType == FsObjType_File)
+ {
+ fCopyIntoExisting = !RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_NoReplace);
+ fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks);
+ }
+ else
+ AssertFailedBreakStmt(vrc = VERR_NOT_IMPLEMENTED);
+
+ uint32_t const fDirMode = 0700; /** @todo Play safe by default; implement ACLs. */
+ uint32_t fDirCreate = 0;
+
+ bool fDstExists = true;
+
+ RTFSOBJINFO dstFsObjInfo;
+ RT_ZERO(dstFsObjInfo);
+ vrc = RTPathQueryInfoEx(strDstRootAbs.c_str(), &dstFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK /* fFlags */);
+ if (RT_SUCCESS(vrc))
+ {
+ char szPathReal[RTPATH_MAX];
+ vrc = RTPathReal(strDstRootAbs.c_str(), szPathReal, sizeof(szPathReal));
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathQueryInfoEx(szPathReal, &dstFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK /* fFlags */);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel2(("Guest Control: Host destination is a symbolic link \"%s\" -> \"%s\" (%s)\n",
+ strDstRootAbs.c_str(), szPathReal,
+ GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(dstFsObjInfo.Attr.fMode))));
+ }
+
+ strDstRootAbs = szPathReal;
+ }
+ }
+ else
+ {
+ if ( vrc == VERR_FILE_NOT_FOUND
+ || vrc == VERR_PATH_NOT_FOUND)
+ {
+ fDstExists = false;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host path lookup for \"%s\" failed: %Rrc"), strDstRootAbs.c_str(), vrc));
+ break;
+ }
+ }
+
+ /* Create the root directory. */
+ if (pList->mSourceSpec.enmType == FsObjType_Directory)
+ {
+ LogFlowFunc(("Directory: fDirCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n",
+ pList->mSourceSpec.fDirCopyFlags, fCopyIntoExisting, fFollowSymlinks,
+ fDstExists, GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(dstFsObjInfo.Attr.fMode))));
+
+ if (fDstExists)
+ {
+ switch (dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ if (!fCopyIntoExisting)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host root directory \"%s\" already exists"), strDstRootAbs.c_str()));
+ vrc = VERR_ALREADY_EXISTS;
+ break;
+ }
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Destination \"%s\" on the host already exists and is a file"), strDstRootAbs.c_str()));
+ vrc = VERR_IS_A_FILE;
+ break;
+ }
+
+ default:
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Unknown object type (%#x) on host for \"%s\""),
+ dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK, strDstRootAbs.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ /* Make sure the destination root directory exists. */
+ if (pList->mSourceSpec.fDryRun == false)
+ {
+ vrc = directoryCreateOnHost(strDstRootAbs, fDirMode, 0 /* fCreate */, true /* fCanExist */);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ AssertBreakStmt(pList->mSourceSpec.enmType == FsObjType_Directory, vrc = VERR_NOT_SUPPORTED);
+
+ /* Walk the entries. */
+ FsEntries::const_iterator itEntry = pList->mVecEntries.begin();
+ while (itEntry != pList->mVecEntries.end())
+ {
+ FsEntry *pEntry = *itEntry;
+ AssertPtr(pEntry);
+
+ Utf8Str strSrcAbs = strSrcRootAbs;
+ Utf8Str strDstAbs = strDstRootAbs;
+
+ strSrcAbs += PATH_STYLE_SEP_STR(pList->mSourceSpec.enmPathStyle);
+ strSrcAbs += pEntry->strPath;
+
+ strDstAbs += PATH_STYLE_SEP_STR(PATH_STYLE_NATIVE);
+ strDstAbs += pEntry->strPath;
+
+ /* Clean up the final guest source path. */
+ vrc = GuestPath::Translate(strSrcAbs, pList->mSourceSpec.enmPathStyle /* Source */,
+ pList->mSourceSpec.enmPathStyle /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Translating guest source path \"%s\" failed: %Rrc"),
+ strSrcAbs.c_str(), vrc));
+ break;
+ }
+
+ /* Translate the final host desitnation path. */
+ vrc = GuestPath::Translate(strDstAbs, mSession->i_getGuestPathStyle() /* Source */, PATH_STYLE_NATIVE /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Translating host destination path \"%s\" failed: %Rrc"),
+ strDstAbs.c_str(), vrc));
+ break;
+ }
+
+ mProgress->SetNextOperation(Bstr(strSrcAbs).raw(), 1);
+
+ switch (pEntry->fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ if (!pList->mSourceSpec.fDryRun)
+ vrc = directoryCreateOnHost(strDstAbs, fDirMode, fDirCreate, fCopyIntoExisting);
+ break;
+
+ case RTFS_TYPE_FILE:
+ RT_FALL_THROUGH();
+ case RTFS_TYPE_SYMLINK:
+ if (!pList->mSourceSpec.fDryRun)
+ vrc = fileCopyFromGuest(strSrcAbs, strDstAbs, pList->mSourceSpec.fFileCopyFlags);
+ break;
+
+ default:
+ AssertFailed(); /* Should never happen (we already have a filtered list). */
+ break;
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ ++itEntry;
+ }
+ }
+ else if (pList->mSourceSpec.enmType == FsObjType_File)
+ {
+ LogFlowFunc(("File: fFileCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n",
+ pList->mSourceSpec.fFileCopyFlags, fCopyIntoExisting, fFollowSymlinks,
+ fDstExists, GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(dstFsObjInfo.Attr.fMode))));
+
+ if (fDstExists)
+ {
+ switch (dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Destination \"%s\" on the host already exists and is a directory"),
+ strDstRootAbs.c_str()));
+ vrc = VERR_IS_A_DIRECTORY;
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ if (!fCopyIntoExisting)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host file \"%s\" already exists"), strDstRootAbs.c_str()));
+ vrc = VERR_ALREADY_EXISTS;
+ }
+ break;
+ }
+
+ default:
+ {
+ /** @todo Resolve symlinks? */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Unknown object type (%#x) on host for \"%s\""),
+ dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK, strDstRootAbs.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Translate the final host destination file path. */
+ vrc = GuestPath::Translate(strDstRootAbs,
+ mSession->i_getGuestPathStyle() /* Dest */, PATH_STYLE_NATIVE /* Source */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Translating host destination path \"%s\" failed: %Rrc"),
+ strDstRootAbs.c_str(), vrc));
+ break;
+ }
+
+ if (!pList->mSourceSpec.fDryRun)
+ vrc = fileCopyFromGuest(strSrcRootAbs, strDstRootAbs, pList->mSourceSpec.fFileCopyFlags);
+ }
+ }
+ else
+ AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ ++itList;
+ }
+
+ if (RT_SUCCESS(vrc))
+ vrc = setProgressSuccess();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+GuestSessionTaskCopyTo::GuestSessionTaskCopyTo(GuestSession *pSession, GuestSessionFsSourceSet const &vecSrc,
+ const Utf8Str &strDest)
+ : GuestSessionCopyTask(pSession)
+{
+ m_strTaskName = "gctlCpyTo";
+
+ mSources = vecSrc;
+ mDest = strDest;
+}
+
+GuestSessionTaskCopyTo::~GuestSessionTaskCopyTo(void)
+{
+}
+
+/**
+ * Initializes a copy-to-guest task.
+ *
+ * @returns HRESULT
+ * @param strTaskDesc Friendly task description.
+ */
+HRESULT GuestSessionTaskCopyTo::Init(const Utf8Str &strTaskDesc)
+{
+ LogFlowFuncEnter();
+
+ setTaskDesc(strTaskDesc);
+
+ /* Create the progress object. */
+ ComObjPtr<Progress> pProgress;
+ HRESULT hrc = pProgress.createObject();
+ if (FAILED(hrc))
+ return hrc;
+
+ mProgress = pProgress;
+
+ int vrc = VINF_SUCCESS;
+
+ ULONG cOperations = 0;
+ Utf8Str strErrorInfo;
+
+ /*
+ * Note: We need to build up the file/directory here instead of GuestSessionTaskCopyTo::Run
+ * because the caller expects a ready-for-operation progress object on return.
+ * The progress object will have a variable operation count, based on the elements to
+ * be processed.
+ */
+
+ if (mSources.empty())
+ {
+ strErrorInfo.printf(tr("No host sources specified"));
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else if (mDest.isEmpty())
+ {
+ strErrorInfo.printf(tr("Guest destination must not be empty"));
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ GuestSessionFsSourceSet::iterator itSrc = mSources.begin();
+ while (itSrc != mSources.end())
+ {
+ Utf8Str strSrc = itSrc->strSource;
+ Utf8Str strDst = mDest;
+
+ bool fFollowSymlinks;
+
+ if (strSrc.isEmpty())
+ {
+ strErrorInfo.printf(tr("Host source entry must not be empty"));
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ if (itSrc->enmType == FsObjType_Directory)
+ {
+ fFollowSymlinks = itSrc->fDirCopyFlags & DirectoryCopyFlag_FollowLinks;
+ }
+ else
+ {
+ fFollowSymlinks = RT_BOOL(itSrc->fFileCopyFlags & FileCopyFlag_FollowLinks);
+ }
+
+ LogFlowFunc(("strSrc=%s (path style is %s), strDst=%s\n",
+ strSrc.c_str(), GuestBase::pathStyleToStr(itSrc->enmPathStyle), strDst.c_str()));
+
+ RTFSOBJINFO srcFsObjInfo;
+ vrc = RTPathQueryInfoEx(strSrc.c_str(), &srcFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK /* fFlags */);
+ if (RT_FAILURE(vrc))
+ {
+ strErrorInfo.printf(tr("No such host file/directory: %s"), strSrc.c_str());
+ break;
+ }
+
+ switch (srcFsObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ if (itSrc->enmType != FsObjType_Directory)
+ {
+ strErrorInfo.printf(tr("Host source \"%s\" is not a file (is a directory)"), strSrc.c_str());
+ vrc = VERR_NOT_A_FILE;
+ }
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ if (itSrc->enmType == FsObjType_Directory)
+ {
+ strErrorInfo.printf(tr("Host source \"%s\" is not a directory (is a file)"), strSrc.c_str());
+ vrc = VERR_NOT_A_DIRECTORY;
+ }
+ break;
+ }
+
+ case RTFS_TYPE_SYMLINK:
+ {
+ if (!fFollowSymlinks)
+ {
+ strErrorInfo.printf(tr("Host source \"%s\" is a symbolic link"), strSrc.c_str());
+ vrc = VERR_IS_A_SYMLINK;
+ break;
+ }
+
+ char szPathReal[RTPATH_MAX];
+ vrc = RTPathReal(strSrc.c_str(), szPathReal, sizeof(szPathReal));
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathQueryInfoEx(szPathReal, &srcFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel2(("Guest Control: Host source is a symbolic link \"%s\" -> \"%s\" (%s)\n",
+ strSrc.c_str(), szPathReal,
+ GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(srcFsObjInfo.Attr.fMode))));
+
+ /* We want to keep the symbolic link name of the source instead of the target pointing to,
+ * so don't touch the source's name here. */
+ itSrc->enmType = GuestBase::fileModeToFsObjType(srcFsObjInfo.Attr.fMode);
+ }
+ else
+ {
+ strErrorInfo.printf(tr("Querying symbolic link info for host source \"%s\" failed"), strSrc.c_str());
+ break;
+ }
+ }
+ else
+ {
+ strErrorInfo.printf(tr("Resolving symbolic link for host source \"%s\" failed"), strSrc.c_str());
+ break;
+ }
+ break;
+ }
+
+ default:
+ LogRel2(("Guest Control: Warning: Unknown host file system type %#x for source \"%s\", skipping\n",
+ srcFsObjInfo.Attr.fMode & RTFS_TYPE_MASK, strSrc.c_str()));
+ break;
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ FsList *pFsList = NULL;
+ try
+ {
+ pFsList = new FsList(*this);
+ vrc = pFsList->Init(strSrc, strDst, *itSrc);
+ if (RT_SUCCESS(vrc))
+ {
+ switch (itSrc->enmType)
+ {
+ case FsObjType_Directory:
+ {
+ char szPathReal[RTPATH_MAX];
+ RTDIRENTRYEX DirEntry;
+ vrc = pFsList->AddDirFromHost(strSrc /* strPath */, "" /* strSubDir */,
+ szPathReal, sizeof(szPathReal), &DirEntry);
+ break;
+ }
+
+ case FsObjType_File:
+ /* The file name is already part of the actual list's source root (strSrc). */
+ break;
+
+ case FsObjType_Symlink:
+ AssertFailed(); /* Should never get here, as we do the resolving above. */
+ break;
+
+ default:
+ LogRel2(("Guest Control: Warning: Unknown source type %#x for host source \"%s\", skipping\n",
+ itSrc->enmType, strSrc.c_str()));
+ break;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ delete pFsList;
+ strErrorInfo.printf(tr("Error adding host source \"%s\" to list: %Rrc"),
+ strSrc.c_str(), vrc);
+ break;
+ }
+#ifdef DEBUG
+ pFsList->DumpToLog();
+#endif
+ mVecLists.push_back(pFsList);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+
+ AssertPtr(pFsList);
+ cOperations += (ULONG)pFsList->mVecEntries.size();
+
+ itSrc++;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* When there are no entries in the first source list, this means the source only contains a single file
+ * (see \a mSrcRootAbs of FsList). So use \a mSrcRootAbs directly. */
+ Utf8Str const &strFirstOp = mVecLists[0]->mVecEntries.size() > 0
+ ? mVecLists[0]->mVecEntries[0]->strPath : mVecLists[0]->mSrcRootAbs;
+
+ /* Now that we know how many objects we're handling, tweak the progress description so that it
+ * reflects more accurately what the progress is actually doing. */
+ if (cOperations > 1)
+ {
+ mDesc.printf(tr("Copying \"%s\" [and %zu %s] from host to \"%s\" on the guest ..."),
+ strFirstOp.c_str(), cOperations - 1, cOperations > 2 ? tr("others") : tr("other"), mDest.c_str());
+ }
+ else
+ mDesc.printf(tr("Copying \"%s\" from host to \"%s\" on the guest ..."), strFirstOp.c_str(), mDest.c_str());
+
+ hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(),
+ TRUE /* aCancelable */, cOperations + 1/* Number of operations */,
+ Bstr(strFirstOp).raw());
+ }
+ else /* On error we go with an "empty" progress object when will be used for error handling. */
+ hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(),
+ TRUE /* aCancelable */, 1 /* cOperations */, Bstr(mDesc).raw());
+
+ if (FAILED(hrc)) /* Progress object creation failed -- we're doomed. */
+ return hrc;
+
+ if (RT_FAILURE(vrc))
+ {
+ if (strErrorInfo.isEmpty())
+ strErrorInfo.printf(tr("Failed with %Rrc"), vrc);
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, strErrorInfo);
+ }
+
+ LogFlowFunc(("Returning %Rhrc (%Rrc)\n", hrc, vrc));
+ return hrc;
+}
+
+/** @copydoc GuestSessionTask::Run */
+int GuestSessionTaskCopyTo::Run(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(mSession);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ int vrc = VINF_SUCCESS;
+
+ FsLists::const_iterator itList = mVecLists.begin();
+ while (itList != mVecLists.end())
+ {
+ FsList *pList = *itList;
+ AssertPtr(pList);
+
+ LogFlowFunc(("List: srcRootAbs=%s, dstRootAbs=%s\n", pList->mSrcRootAbs.c_str(), pList->mDstRootAbs.c_str()));
+
+ Utf8Str strSrcRootAbs = pList->mSrcRootAbs;
+ Utf8Str strDstRootAbs = pList->mDstRootAbs;
+
+ vrc = GuestPath::BuildDestinationPath(strSrcRootAbs, PATH_STYLE_NATIVE /* Source */,
+ strDstRootAbs, mSession->i_getGuestPathStyle() /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Building guest destination root path \"%s\" failed: %Rrc"),
+ strDstRootAbs.c_str(), vrc));
+ break;
+ }
+
+ bool fCopyIntoExisting;
+ bool fFollowSymlinks;
+
+ if (pList->mSourceSpec.enmType == FsObjType_Directory)
+ {
+ fCopyIntoExisting = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_CopyIntoExisting);
+ fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks);
+ }
+ else if (pList->mSourceSpec.enmType == FsObjType_File)
+ {
+ fCopyIntoExisting = !RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_NoReplace);
+ fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks);
+ }
+ else
+ AssertFailedBreakStmt(vrc = VERR_NOT_IMPLEMENTED);
+
+ uint32_t const fDirMode = 0700; /** @todo Play safe by default; implement ACLs. */
+
+ bool fDstExists = true;
+
+ GuestFsObjData dstObjData;
+ int vrcGuest;
+ vrc = mSession->i_fsQueryInfo(strDstRootAbs, fFollowSymlinks, dstObjData, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ {
+ switch (vrcGuest)
+ {
+ case VERR_PATH_NOT_FOUND:
+ RT_FALL_THROUGH();
+ case VERR_FILE_NOT_FOUND:
+ {
+ fDstExists = false;
+ vrc = VINF_SUCCESS;
+ break;
+ }
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Querying information on guest for \"%s\" failed: %Rrc"),
+ strDstRootAbs.c_str(), vrcGuest));
+ break;
+ }
+ }
+ else
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Querying information on guest for \"%s\" failed: %Rrc"),
+ strDstRootAbs.c_str(), vrc));
+ break;
+ }
+ }
+
+ if (pList->mSourceSpec.enmType == FsObjType_Directory)
+ {
+ LogFlowFunc(("Directory: fDirCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n",
+ pList->mSourceSpec.fDirCopyFlags, fCopyIntoExisting, fFollowSymlinks,
+ fDstExists, GuestBase::fsObjTypeToStr(dstObjData.mType)));
+
+ if (fDstExists)
+ {
+ switch (dstObjData.mType)
+ {
+ case FsObjType_Directory:
+ {
+ if (!fCopyIntoExisting)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest root directory \"%s\" already exists"),
+ strDstRootAbs.c_str()));
+ vrc = VERR_ALREADY_EXISTS;
+ }
+ break;
+ }
+
+ case FsObjType_File:
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Destination \"%s\" on guest already exists and is a file"),
+ strDstRootAbs.c_str()));
+ vrc = VERR_IS_A_FILE;
+ }
+
+ case FsObjType_Symlink:
+ /** @todo Resolve symlinks? */
+ break;
+
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Unknown object type (%#x) on guest for \"%s\""),
+ dstObjData.mType, strDstRootAbs.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ /* Make sure the destination root directory exists. */
+ if (pList->mSourceSpec.fDryRun == false)
+ {
+ vrc = directoryCreateOnGuest(strDstRootAbs, fDirMode, DirectoryCreateFlag_None,
+ fFollowSymlinks, fCopyIntoExisting);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ /* Walk the entries. */
+ FsEntries::const_iterator itEntry = pList->mVecEntries.begin();
+ while ( RT_SUCCESS(vrc)
+ && itEntry != pList->mVecEntries.end())
+ {
+ FsEntry *pEntry = *itEntry;
+ AssertPtr(pEntry);
+
+ Utf8Str strSrcAbs = strSrcRootAbs;
+ Utf8Str strDstAbs = strDstRootAbs;
+
+ strSrcAbs += PATH_STYLE_SEP_STR(PATH_STYLE_NATIVE);
+ strSrcAbs += pEntry->strPath;
+
+ strDstAbs += PATH_STYLE_SEP_STR(mSession->i_getGuestPathStyle());
+ strDstAbs += pEntry->strPath;
+
+ /* Clean up the final host source path. */
+ vrc = GuestPath::Translate(strSrcAbs, pList->mSourceSpec.enmPathStyle /* Source */,
+ pList->mSourceSpec.enmPathStyle /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Translating host source path\"%s\" failed: %Rrc"),
+ strSrcAbs.c_str(), vrc));
+ break;
+ }
+
+ /* Translate final guest destination path. */
+ vrc = GuestPath::Translate(strDstAbs,
+ PATH_STYLE_NATIVE /* Source */, mSession->i_getGuestPathStyle() /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Translating guest destination path \"%s\" failed: %Rrc"),
+ strDstAbs.c_str(), vrc));
+ break;
+ }
+
+ mProgress->SetNextOperation(Bstr(strSrcAbs).raw(), 1);
+
+ switch (pEntry->fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ LogRel2(("Guest Control: Copying directory \"%s\" from host to \"%s\" on guest ...\n",
+ strSrcAbs.c_str(), strDstAbs.c_str()));
+ if (!pList->mSourceSpec.fDryRun)
+ vrc = directoryCreateOnGuest(strDstAbs, fDirMode, DirectoryCreateFlag_None,
+ fFollowSymlinks, fCopyIntoExisting);
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ if (!pList->mSourceSpec.fDryRun)
+ vrc = fileCopyToGuest(strSrcAbs, strDstAbs, pList->mSourceSpec.fFileCopyFlags);
+ break;
+ }
+
+ default:
+ LogRel2(("Guest Control: Warning: Host file system type 0x%x for \"%s\" is not supported, skipping\n",
+ pEntry->fMode & RTFS_TYPE_MASK, strSrcAbs.c_str()));
+ break;
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ ++itEntry;
+ }
+ }
+ else if (pList->mSourceSpec.enmType == FsObjType_File)
+ {
+ LogFlowFunc(("File: fFileCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n",
+ pList->mSourceSpec.fFileCopyFlags, fCopyIntoExisting, fFollowSymlinks,
+ fDstExists, GuestBase::fsObjTypeToStr(dstObjData.mType)));
+
+ if (fDstExists)
+ {
+ switch (dstObjData.mType)
+ {
+ case FsObjType_Directory:
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Destination \"%s\" on the guest already exists and is a directory"),
+ strDstRootAbs.c_str()));
+ vrc = VERR_IS_A_DIRECTORY;
+ break;
+ }
+
+ case FsObjType_File:
+ {
+ if (!fCopyIntoExisting)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file \"%s\" already exists"), strDstRootAbs.c_str()));
+ vrc = VERR_ALREADY_EXISTS;
+ }
+ break;
+ }
+
+ default:
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Unsupported guest file system type (%#x) for \"%s\""),
+ dstObjData.mType, strDstRootAbs.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Translate the final guest destination file path. */
+ vrc = GuestPath::Translate(strDstRootAbs,
+ PATH_STYLE_NATIVE /* Source */, mSession->i_getGuestPathStyle() /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Translating guest destination path \"%s\" failed: %Rrc"),
+ strDstRootAbs.c_str(), vrc));
+ break;
+ }
+
+ if (!pList->mSourceSpec.fDryRun)
+ vrc = fileCopyToGuest(strSrcRootAbs, strDstRootAbs, pList->mSourceSpec.fFileCopyFlags);
+ }
+ }
+ else
+ AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ ++itList;
+ }
+
+ if (RT_SUCCESS(vrc))
+ vrc = setProgressSuccess();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+GuestSessionTaskUpdateAdditions::GuestSessionTaskUpdateAdditions(GuestSession *pSession,
+ const Utf8Str &strSource,
+ const ProcessArguments &aArguments,
+ uint32_t fFlags)
+ : GuestSessionTask(pSession)
+{
+ m_strTaskName = "gctlUpGA";
+
+ mSource = strSource;
+ mArguments = aArguments;
+ mFlags = fFlags;
+}
+
+GuestSessionTaskUpdateAdditions::~GuestSessionTaskUpdateAdditions(void)
+{
+
+}
+
+/**
+ * Adds arguments to existing process arguments.
+ * Identical / already existing arguments will be filtered out.
+ *
+ * @returns VBox status code.
+ * @param aArgumentsDest Destination to add arguments to.
+ * @param aArgumentsSource Arguments to add.
+ */
+int GuestSessionTaskUpdateAdditions::addProcessArguments(ProcessArguments &aArgumentsDest, const ProcessArguments &aArgumentsSource)
+{
+ try
+ {
+ /* Filter out arguments which already are in the destination to
+ * not end up having them specified twice. Not the fastest method on the
+ * planet but does the job. */
+ ProcessArguments::const_iterator itSource = aArgumentsSource.begin();
+ while (itSource != aArgumentsSource.end())
+ {
+ bool fFound = false;
+ ProcessArguments::iterator itDest = aArgumentsDest.begin();
+ while (itDest != aArgumentsDest.end())
+ {
+ if ((*itDest).equalsIgnoreCase((*itSource)))
+ {
+ fFound = true;
+ break;
+ }
+ ++itDest;
+ }
+
+ if (!fFound)
+ aArgumentsDest.push_back((*itSource));
+
+ ++itSource;
+ }
+ }
+ catch(std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Helper function to copy a file from a VISO to the guest.
+ *
+ * @returns VBox status code.
+ * @param pSession Guest session to use.
+ * @param hVfsIso VISO handle to use.
+ * @param strFileSrc Source file path on VISO to copy.
+ * @param strFileDst Destination file path on guest.
+ * @param fOptional When set to \c true, the file is optional, i.e. can be skipped
+ * when not found, \c false if not.
+ */
+int GuestSessionTaskUpdateAdditions::copyFileToGuest(GuestSession *pSession, RTVFS hVfsIso,
+ Utf8Str const &strFileSrc, const Utf8Str &strFileDst, bool fOptional)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertReturn(hVfsIso != NIL_RTVFS, VERR_INVALID_POINTER);
+
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ int vrc = RTVfsFileOpen(hVfsIso, strFileSrc.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ uint64_t cbSrcSize = 0;
+ vrc = RTVfsFileQuerySize(hVfsFile, &cbSrcSize);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Copying Guest Additions installer file \"%s\" to \"%s\" on guest ...\n",
+ strFileSrc.c_str(), strFileDst.c_str()));
+
+ GuestFileOpenInfo dstOpenInfo;
+ dstOpenInfo.mFilename = strFileDst;
+ dstOpenInfo.mOpenAction = FileOpenAction_CreateOrReplace;
+ dstOpenInfo.mAccessMode = FileAccessMode_WriteOnly;
+ dstOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */
+
+ ComObjPtr<GuestFile> dstFile;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ vrc = mSession->i_fileOpen(dstOpenInfo, dstFile, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(vrcGuest, strFileDst.c_str()));
+ break;
+
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file \"%s\" could not be opened: %Rrc"),
+ strFileDst.c_str(), vrc));
+ break;
+ }
+ }
+ else
+ {
+ vrc = fileCopyToGuestInner(strFileSrc, hVfsFile, strFileDst, dstFile, FileCopyFlag_None, 0 /*offCopy*/, cbSrcSize);
+
+ int vrc2 = fileClose(dstFile);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ }
+ }
+
+ RTVfsFileRelease(hVfsFile);
+ }
+ else if (fOptional)
+ vrc = VINF_SUCCESS;
+
+ return vrc;
+}
+
+/**
+ * Helper function to run (start) a file on the guest.
+ *
+ * @returns VBox status code.
+ * @param pSession Guest session to use.
+ * @param procInfo Guest process startup info to use.
+ */
+int GuestSessionTaskUpdateAdditions::runFileOnGuest(GuestSession *pSession, GuestProcessStartupInfo &procInfo)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ LogRel(("Running %s ...\n", procInfo.mName.c_str()));
+
+ GuestProcessTool procTool;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = procTool.init(pSession, procInfo, false /* Async */, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ if (RT_SUCCESS(vrcGuest))
+ vrc = procTool.wait(GUESTPROCESSTOOL_WAIT_FLAG_NONE, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ vrc = procTool.getTerminationStatus();
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_PROCESS_EXIT_CODE:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Running update file \"%s\" on guest failed: %Rrc"),
+ procInfo.mExecutable.c_str(), procTool.getRc()));
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Running update file on guest failed"),
+ GuestErrorInfo(GuestErrorInfo::Type_Process, vrcGuest, procInfo.mExecutable.c_str()));
+ break;
+
+ case VERR_INVALID_STATE: /** @todo Special guest control rc needed! */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Update file \"%s\" reported invalid running state"),
+ procInfo.mExecutable.c_str()));
+ break;
+
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Error while running update file \"%s\" on guest: %Rrc"),
+ procInfo.mExecutable.c_str(), vrc));
+ break;
+ }
+ }
+
+ return vrc;
+}
+
+/**
+ * Helper function which waits until Guest Additions services started.
+ *
+ * @returns 0 on success or VERR_TIMEOUT if guest services were not
+ * started on time.
+ * @param pGuest Guest interface to use.
+ */
+int GuestSessionTaskUpdateAdditions::waitForGuestSession(ComObjPtr<Guest> pGuest)
+{
+ int vrc = VERR_GSTCTL_GUEST_ERROR;
+ int rc = VERR_TIMEOUT;
+
+ uint64_t tsStart = RTTimeSystemMilliTS();
+ const uint64_t timeoutMs = 600 * 1000;
+
+ AssertReturn(!pGuest.isNull(), VERR_TIMEOUT);
+
+ do
+ {
+ ComObjPtr<GuestSession> pSession;
+ GuestCredentials guestCreds;
+ GuestSessionStartupInfo startupInfo;
+
+ startupInfo.mName = "Guest Additions connection checker";
+ startupInfo.mOpenTimeoutMS = 100;
+
+ vrc = pGuest->i_sessionCreate(startupInfo, guestCreds, pSession);
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest = VERR_GSTCTL_GUEST_ERROR; /* unused. */
+
+ Assert(!pSession.isNull());
+
+ vrc = pSession->i_startSession(&vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ GuestSessionWaitResult_T enmWaitResult = GuestSessionWaitResult_None;
+ int rcGuest = 0; /* unused. */
+
+ /* Wait for VBoxService to start. */
+ vrc = pSession->i_waitFor(GuestSessionWaitForFlag_Start, 100 /* timeout, ms */, enmWaitResult, &rcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pSession->Close();
+ rc = 0;
+ break;
+ }
+ }
+
+ vrc = pSession->Close();
+ }
+
+ RTThreadSleep(100);
+
+ } while ((RTTimeSystemMilliTS() - tsStart) < timeoutMs);
+
+ return rc;
+}
+
+/** @copydoc GuestSessionTask::Run */
+int GuestSessionTaskUpdateAdditions::Run(void)
+{
+ LogFlowThisFuncEnter();
+
+ ComObjPtr<GuestSession> pSession = mSession;
+ Assert(!pSession.isNull());
+
+ AutoCaller autoCaller(pSession);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ int vrc = setProgress(10);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ HRESULT hrc = S_OK;
+
+ LogRel(("Automatic update of Guest Additions started, using \"%s\"\n", mSource.c_str()));
+
+ ComObjPtr<Guest> pGuest(mSession->i_getParent());
+#if 0
+ /*
+ * Wait for the guest being ready within 30 seconds.
+ */
+ AdditionsRunLevelType_T addsRunLevel;
+ uint64_t tsStart = RTTimeSystemMilliTS();
+ while ( SUCCEEDED(hrc = pGuest->COMGETTER(AdditionsRunLevel)(&addsRunLevel))
+ && ( addsRunLevel != AdditionsRunLevelType_Userland
+ && addsRunLevel != AdditionsRunLevelType_Desktop))
+ {
+ if ((RTTimeSystemMilliTS() - tsStart) > 30 * 1000)
+ {
+ vrc = VERR_TIMEOUT;
+ break;
+ }
+
+ RTThreadSleep(100); /* Wait a bit. */
+ }
+
+ if (FAILED(hrc)) vrc = VERR_TIMEOUT;
+ if (vrc == VERR_TIMEOUT)
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Guest Additions were not ready within time, giving up")));
+#else
+ /*
+ * For use with the GUI we don't want to wait, just return so that the manual .ISO mounting
+ * can continue.
+ */
+ AdditionsRunLevelType_T addsRunLevel;
+ if ( FAILED(hrc = pGuest->COMGETTER(AdditionsRunLevel)(&addsRunLevel))
+ || ( addsRunLevel != AdditionsRunLevelType_Userland
+ && addsRunLevel != AdditionsRunLevelType_Desktop))
+ {
+ if (addsRunLevel == AdditionsRunLevelType_System)
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Guest Additions are installed but not fully loaded yet, aborting automatic update")));
+ else
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Guest Additions not installed or ready, aborting automatic update")));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+#endif
+
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Determine if we are able to update automatically. This only works
+ * if there are recent Guest Additions installed already.
+ */
+ Utf8Str strAddsVer;
+ vrc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/Version", strAddsVer);
+ if ( RT_SUCCESS(vrc)
+ && RTStrVersionCompare(strAddsVer.c_str(), "4.1") < 0)
+ {
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Guest has too old Guest Additions (%s) installed for automatic updating, please update manually"),
+ strAddsVer.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ Utf8Str strOSVer;
+ eOSType osType = eOSType_Unknown;
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Determine guest OS type and the required installer image.
+ */
+ Utf8Str strOSType;
+ vrc = getGuestProperty(pGuest, "/VirtualBox/GuestInfo/OS/Product", strOSType);
+ if (RT_SUCCESS(vrc))
+ {
+ if ( strOSType.contains("Microsoft", Utf8Str::CaseInsensitive)
+ || strOSType.contains("Windows", Utf8Str::CaseInsensitive))
+ {
+ osType = eOSType_Windows;
+
+ /*
+ * Determine guest OS version.
+ */
+ vrc = getGuestProperty(pGuest, "/VirtualBox/GuestInfo/OS/Release", strOSVer);
+ if (RT_FAILURE(vrc))
+ {
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Unable to detected guest OS version, please update manually")));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ /* Because Windows 2000 + XP and is bitching with WHQL popups even if we have signed drivers we
+ * can't do automated updates here. */
+ /* Windows XP 64-bit (5.2) is a Windows 2003 Server actually, so skip this here. */
+ if ( RT_SUCCESS(vrc)
+ && RTStrVersionCompare(strOSVer.c_str(), "5.0") >= 0)
+ {
+ if ( strOSVer.startsWith("5.0") /* Exclude the build number. */
+ || strOSVer.startsWith("5.1") /* Exclude the build number. */)
+ {
+ /* If we don't have AdditionsUpdateFlag_WaitForUpdateStartOnly set we can't continue
+ * because the Windows Guest Additions installer will fail because of WHQL popups. If the
+ * flag is set this update routine ends successfully as soon as the installer was started
+ * (and the user has to deal with it in the guest). */
+ if (!(mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly))
+ {
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Windows 2000 and XP are not supported for automatic updating due to WHQL interaction, please update manually")));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ }
+ else
+ {
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("%s (%s) not supported for automatic updating, please update manually"),
+ strOSType.c_str(), strOSVer.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else if (strOSType.contains("Solaris", Utf8Str::CaseInsensitive))
+ {
+ osType = eOSType_Solaris;
+ }
+ else /* Everything else hopefully means Linux :-). */
+ osType = eOSType_Linux;
+
+ if ( RT_SUCCESS(vrc)
+ && ( osType != eOSType_Windows
+ && osType != eOSType_Linux))
+ /** @todo Support Solaris. */
+ {
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Detected guest OS (%s) does not support automatic Guest Additions updating, please update manually"),
+ strOSType.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Try to open the .ISO file to extract all needed files.
+ */
+ RTVFSFILE hVfsFileIso;
+ vrc = RTVfsFileOpenNormal(mSource.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFileIso);
+ if (RT_FAILURE(vrc))
+ {
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Unable to open Guest Additions .ISO file \"%s\": %Rrc"),
+ mSource.c_str(), vrc));
+ }
+ else
+ {
+ RTVFS hVfsIso;
+ vrc = RTFsIso9660VolOpen(hVfsFileIso, 0 /*fFlags*/, &hVfsIso, NULL);
+ if (RT_FAILURE(vrc))
+ {
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Unable to open file as ISO 9660 file system volume: %Rrc"), vrc));
+ }
+ else
+ {
+ Utf8Str strUpdateDir;
+
+ vrc = setProgress(5);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Try getting the installed Guest Additions version to know whether we
+ * can install our temporary Guest Addition data into the original installation
+ * directory.
+ *
+ * Because versions prior to 4.2 had bugs wrt spaces in paths we have to choose
+ * a different location then.
+ */
+ bool fUseInstallDir = false;
+
+ Utf8Str strAddsVer;
+ vrc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/Version", strAddsVer);
+ if ( RT_SUCCESS(vrc)
+ && RTStrVersionCompare(strAddsVer.c_str(), "4.2r80329") > 0)
+ {
+ fUseInstallDir = true;
+ }
+
+ if (fUseInstallDir)
+ {
+ vrc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/InstallDir", strUpdateDir);
+ if (RT_SUCCESS(vrc))
+ {
+ if (strUpdateDir.isNotEmpty())
+ {
+ if (osType == eOSType_Windows)
+ {
+ strUpdateDir.findReplace('/', '\\');
+ strUpdateDir.append("\\Update\\");
+ }
+ else
+ strUpdateDir.append("/update/");
+ }
+ /* else Older Guest Additions might not handle this property correctly. */
+ }
+ /* Ditto. */
+ }
+
+ /** @todo Set fallback installation directory. Make this a *lot* smarter. Later. */
+ if (strUpdateDir.isEmpty())
+ {
+ if (osType == eOSType_Windows)
+ strUpdateDir = "C:\\Temp\\";
+ else
+ strUpdateDir = "/tmp/";
+ }
+ }
+
+ /* Create the installation directory. */
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Guest Additions update directory is: %s\n", strUpdateDir.c_str()));
+
+ vrc = pSession->i_directoryCreate(strUpdateDir, 755 /* Mode */, DirectoryCreateFlag_Parents, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Creating installation directory on guest failed"),
+ GuestErrorInfo(GuestErrorInfo::Type_Directory, vrcGuest, strUpdateDir.c_str()));
+ break;
+
+ default:
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Creating installation directory \"%s\" on guest failed: %Rrc"),
+ strUpdateDir.c_str(), vrc));
+ break;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ vrc = setProgress(10);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Prepare the file(s) we want to copy over to the guest and
+ * (maybe) want to run. */
+ switch (osType)
+ {
+ case eOSType_Windows:
+ {
+ /* Do we need to install our certificates? We do this for W2K and up. */
+ bool fInstallCert = false;
+
+ /* Only Windows 2000 and up need certificates to be installed. */
+ if (RTStrVersionCompare(strOSVer.c_str(), "5.0") >= 0)
+ {
+ fInstallCert = true;
+ LogRel(("Certificates for auto updating WHQL drivers will be installed\n"));
+ }
+ else
+ LogRel(("Skipping installation of certificates for WHQL drivers\n"));
+
+ if (fInstallCert)
+ {
+ static struct { const char *pszDst, *pszIso; } const s_aCertFiles[] =
+ {
+ { "vbox.cer", "/CERT/VBOX.CER" },
+ { "vbox-sha1.cer", "/CERT/VBOX-SHA1.CER" },
+ { "vbox-sha256.cer", "/CERT/VBOX-SHA256.CER" },
+ { "vbox-sha256-r3.cer", "/CERT/VBOX-SHA256-R3.CER" },
+ { "oracle-vbox.cer", "/CERT/ORACLE-VBOX.CER" },
+ };
+ uint32_t fCopyCertUtil = ISOFILE_FLAG_COPY_FROM_ISO;
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_aCertFiles); i++)
+ {
+ /* Skip if not present on the ISO. */
+ RTFSOBJINFO ObjInfo;
+ vrc = RTVfsQueryPathInfo(hVfsIso, s_aCertFiles[i].pszIso, &ObjInfo, RTFSOBJATTRADD_NOTHING,
+ RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ continue;
+
+ /* Copy the certificate certificate. */
+ Utf8Str const strDstCert(strUpdateDir + s_aCertFiles[i].pszDst);
+ mFiles.push_back(ISOFile(s_aCertFiles[i].pszIso,
+ strDstCert,
+ ISOFILE_FLAG_COPY_FROM_ISO | ISOFILE_FLAG_OPTIONAL));
+
+ /* Out certificate installation utility. */
+ /* First pass: Copy over the file (first time only) + execute it to remove any
+ * existing VBox certificates. */
+ GuestProcessStartupInfo siCertUtilRem;
+ siCertUtilRem.mName = "VirtualBox Certificate Utility, removing old VirtualBox certificates";
+ /* The argv[0] should contain full path to the executable module */
+ siCertUtilRem.mArguments.push_back(strUpdateDir + "VBoxCertUtil.exe");
+ siCertUtilRem.mArguments.push_back(Utf8Str("remove-trusted-publisher"));
+ siCertUtilRem.mArguments.push_back(Utf8Str("--root")); /* Add root certificate as well. */
+ siCertUtilRem.mArguments.push_back(strDstCert);
+ siCertUtilRem.mArguments.push_back(strDstCert);
+ mFiles.push_back(ISOFile("CERT/VBOXCERTUTIL.EXE",
+ strUpdateDir + "VBoxCertUtil.exe",
+ fCopyCertUtil | ISOFILE_FLAG_EXECUTE | ISOFILE_FLAG_OPTIONAL,
+ siCertUtilRem));
+ fCopyCertUtil = 0;
+ /* Second pass: Only execute (but don't copy) again, this time installng the
+ * recent certificates just copied over. */
+ GuestProcessStartupInfo siCertUtilAdd;
+ siCertUtilAdd.mName = "VirtualBox Certificate Utility, installing VirtualBox certificates";
+ /* The argv[0] should contain full path to the executable module */
+ siCertUtilAdd.mArguments.push_back(strUpdateDir + "VBoxCertUtil.exe");
+ siCertUtilAdd.mArguments.push_back(Utf8Str("add-trusted-publisher"));
+ siCertUtilAdd.mArguments.push_back(Utf8Str("--root")); /* Add root certificate as well. */
+ siCertUtilAdd.mArguments.push_back(strDstCert);
+ siCertUtilAdd.mArguments.push_back(strDstCert);
+ mFiles.push_back(ISOFile("CERT/VBOXCERTUTIL.EXE",
+ strUpdateDir + "VBoxCertUtil.exe",
+ ISOFILE_FLAG_EXECUTE | ISOFILE_FLAG_OPTIONAL,
+ siCertUtilAdd));
+ }
+ }
+ /* The installers in different flavors, as we don't know (and can't assume)
+ * the guest's bitness. */
+ mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS-X86.EXE",
+ strUpdateDir + "VBoxWindowsAdditions-x86.exe",
+ ISOFILE_FLAG_COPY_FROM_ISO));
+ mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS-AMD64.EXE",
+ strUpdateDir + "VBoxWindowsAdditions-amd64.exe",
+ ISOFILE_FLAG_COPY_FROM_ISO));
+ /* The stub loader which decides which flavor to run. */
+ GuestProcessStartupInfo siInstaller;
+ siInstaller.mName = "VirtualBox Windows Guest Additions Installer";
+ /* Set a running timeout of 5 minutes -- the Windows Guest Additions
+ * setup can take quite a while, so be on the safe side. */
+ siInstaller.mTimeoutMS = 5 * 60 * 1000;
+
+ /* The argv[0] should contain full path to the executable module */
+ siInstaller.mArguments.push_back(strUpdateDir + "VBoxWindowsAdditions.exe");
+ siInstaller.mArguments.push_back(Utf8Str("/S")); /* We want to install in silent mode. */
+ siInstaller.mArguments.push_back(Utf8Str("/l")); /* ... and logging enabled. */
+ /* Don't quit VBoxService during upgrade because it still is used for this
+ * piece of code we're in right now (that is, here!) ... */
+ siInstaller.mArguments.push_back(Utf8Str("/no_vboxservice_exit"));
+ /* Tell the installer to report its current installation status
+ * using a running VBoxTray instance via balloon messages in the
+ * Windows taskbar. */
+ siInstaller.mArguments.push_back(Utf8Str("/post_installstatus"));
+ /* Add optional installer command line arguments from the API to the
+ * installer's startup info. */
+ vrc = addProcessArguments(siInstaller.mArguments, mArguments);
+ AssertRC(vrc);
+ /* If the caller does not want to wait for out guest update process to end,
+ * complete the progress object now so that the caller can do other work. */
+ if (mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly)
+ siInstaller.mFlags |= ProcessCreateFlag_WaitForProcessStartOnly;
+ mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS.EXE",
+ strUpdateDir + "VBoxWindowsAdditions.exe",
+ ISOFILE_FLAG_COPY_FROM_ISO | ISOFILE_FLAG_EXECUTE, siInstaller));
+ break;
+ }
+ case eOSType_Linux:
+ {
+ /* Copy over the installer to the guest but don't execute it.
+ * Execution will be done by the shell instead. */
+ mFiles.push_back(ISOFile("VBOXLINUXADDITIONS.RUN",
+ strUpdateDir + "VBoxLinuxAdditions.run", ISOFILE_FLAG_COPY_FROM_ISO));
+
+ GuestProcessStartupInfo siInstaller;
+ siInstaller.mName = "VirtualBox Linux Guest Additions Installer";
+ /* Set a running timeout of 5 minutes -- compiling modules and stuff for the Linux Guest Additions
+ * setup can take quite a while, so be on the safe side. */
+ siInstaller.mTimeoutMS = 5 * 60 * 1000;
+ /* The argv[0] should contain full path to the shell we're using to execute the installer. */
+ siInstaller.mArguments.push_back("/bin/sh");
+ /* Now add the stuff we need in order to execute the installer. */
+ siInstaller.mArguments.push_back(strUpdateDir + "VBoxLinuxAdditions.run");
+ /* Make sure to add "--nox11" to the makeself wrapper in order to not getting any blocking xterm
+ * window spawned when doing any unattended Linux GA installations. */
+ siInstaller.mArguments.push_back("--nox11");
+ siInstaller.mArguments.push_back("--");
+ /* Force the upgrade. Needed in order to skip the confirmation dialog about warning to upgrade. */
+ siInstaller.mArguments.push_back("--force"); /** @todo We might want a dedicated "--silent" switch here. */
+ /* If the caller does not want to wait for out guest update process to end,
+ * complete the progress object now so that the caller can do other work. */
+ if (mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly)
+ siInstaller.mFlags |= ProcessCreateFlag_WaitForProcessStartOnly;
+ mFiles.push_back(ISOFile("/bin/sh" /* Source */, "/bin/sh" /* Dest */,
+ ISOFILE_FLAG_EXECUTE, siInstaller));
+ break;
+ }
+ case eOSType_Solaris:
+ /** @todo Add Solaris support. */
+ break;
+ default:
+ AssertReleaseMsgFailed(("Unsupported guest type: %d\n", osType));
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* We want to spend 40% total for all copying operations. So roughly
+ * calculate the specific percentage step of each copied file. */
+ uint8_t uOffset = 20; /* Start at 20%. */
+ uint8_t uStep = 40 / (uint8_t)mFiles.size(); Assert(mFiles.size() <= 10);
+
+ LogRel(("Copying over Guest Additions update files to the guest ...\n"));
+
+ std::vector<ISOFile>::const_iterator itFiles = mFiles.begin();
+ while (itFiles != mFiles.end())
+ {
+ if (itFiles->fFlags & ISOFILE_FLAG_COPY_FROM_ISO)
+ {
+ bool fOptional = false;
+ if (itFiles->fFlags & ISOFILE_FLAG_OPTIONAL)
+ fOptional = true;
+ vrc = copyFileToGuest(pSession, hVfsIso, itFiles->strSource, itFiles->strDest, fOptional);
+ if (RT_FAILURE(vrc))
+ {
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Error while copying file \"%s\" to \"%s\" on the guest: %Rrc"),
+ itFiles->strSource.c_str(), itFiles->strDest.c_str(), vrc));
+ break;
+ }
+ }
+
+ vrc = setProgress(uOffset);
+ if (RT_FAILURE(vrc))
+ break;
+ uOffset += uStep;
+
+ ++itFiles;
+ }
+ }
+
+ /* Done copying, close .ISO file. */
+ RTVfsRelease(hVfsIso);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* We want to spend 35% total for all copying operations. So roughly
+ * calculate the specific percentage step of each copied file. */
+ uint8_t uOffset = 60; /* Start at 60%. */
+ uint8_t uStep = 35 / (uint8_t)mFiles.size(); Assert(mFiles.size() <= 10);
+
+ LogRel(("Executing Guest Additions update files ...\n"));
+
+ std::vector<ISOFile>::iterator itFiles = mFiles.begin();
+ while (itFiles != mFiles.end())
+ {
+ if (itFiles->fFlags & ISOFILE_FLAG_EXECUTE)
+ {
+ vrc = runFileOnGuest(pSession, itFiles->mProcInfo);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ vrc = setProgress(uOffset);
+ if (RT_FAILURE(vrc))
+ break;
+ uOffset += uStep;
+
+ ++itFiles;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Linux Guest Additions will restart VBoxService during installation process.
+ * In this case, connection to the guest will be temporary lost until new
+ * kernel modules will be rebuilt, loaded and new VBoxService restarted.
+ * Handle this case here: check if old connection was terminated and
+ * new one has started. */
+ if (osType == eOSType_Linux)
+ {
+ if (pSession->i_isTerminated())
+ {
+ LogRel(("Old guest session has terminated, waiting updated guest services to start\n"));
+
+ /* Wait for VBoxService to restart. */
+ vrc = waitForGuestSession(pSession->i_getParent());
+ if (RT_FAILURE(vrc))
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Automatic update of Guest Additions has failed: "
+ "guest services were not restarted, please reinstall Guest Additions")));
+ }
+ else
+ {
+ vrc = VERR_TRY_AGAIN;
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Old guest session is still active, guest services were not restarted "
+ "after installation, please reinstall Guest Additions")));
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Automatic update of Guest Additions succeeded\n"));
+ hrc = setProgressSuccess();
+ }
+ }
+ }
+
+ RTVfsFileRelease(hVfsFileIso);
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_CANCELLED)
+ {
+ LogRel(("Automatic update of Guest Additions was canceled\n"));
+
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Installation was canceled")));
+ }
+ else if (vrc == VERR_TIMEOUT)
+ {
+ LogRel(("Automatic update of Guest Additions has timed out\n"));
+
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Installation has timed out")));
+ }
+ else
+ {
+ Utf8Str strError = Utf8StrFmt("No further error information available (%Rrc)", vrc);
+ if (!mProgress.isNull()) /* Progress object is optional. */
+ {
+#ifdef VBOX_STRICT
+ /* If we forgot to set the progress object accordingly, let us know. */
+ LONG rcProgress;
+ AssertMsg( SUCCEEDED(mProgress->COMGETTER(ResultCode(&rcProgress)))
+ && FAILED(rcProgress), ("Task indicated an error (%Rrc), but progress did not indicate this (%Rhrc)\n",
+ vrc, rcProgress));
+#endif
+ com::ProgressErrorInfo errorInfo(mProgress);
+ if ( errorInfo.isFullAvailable()
+ || errorInfo.isBasicAvailable())
+ {
+ strError = errorInfo.getText();
+ }
+ }
+
+ LogRel(("Automatic update of Guest Additions failed: %s (%Rhrc)\n",
+ strError.c_str(), hrc));
+ }
+
+ LogRel(("Please install Guest Additions manually\n"));
+ }
+
+ /** @todo Clean up copied / left over installation files. */
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
diff --git a/src/VBox/Main/src-client/HGCM.cpp b/src/VBox/Main/src-client/HGCM.cpp
new file mode 100644
index 00000000..4595f9bd
--- /dev/null
+++ b/src/VBox/Main/src-client/HGCM.cpp
@@ -0,0 +1,3040 @@
+/* $Id: HGCM.cpp $ */
+/** @file
+ * HGCM (Host-Guest Communication Manager)
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_HGCM
+#include "LoggingNew.h"
+
+#include "HGCM.h"
+#include "HGCMThread.h"
+
+#include <VBox/err.h>
+#include <VBox/hgcmsvc.h>
+#include <VBox/vmm/ssm.h>
+#include <VBox/vmm/stam.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/sup.h>
+#include <VBox/AssertGuest.h>
+
+#include <iprt/alloc.h>
+#include <iprt/avl.h>
+#include <iprt/critsect.h>
+#include <iprt/asm.h>
+#include <iprt/ldr.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+
+#include <VBox/VMMDev.h>
+#include <new>
+
+/**
+ * A service gets one thread, which synchronously delivers messages to
+ * the service. This is good for serialization.
+ *
+ * Some services may want to process messages asynchronously, and will want
+ * a next message to be delivered, while a previous message is still being
+ * processed.
+ *
+ * The dedicated service thread delivers a next message when service
+ * returns after fetching a previous one. The service will call a message
+ * completion callback when message is actually processed. So returning
+ * from the service call means only that the service is processing message.
+ *
+ * 'Message processed' condition is indicated by service, which call the
+ * callback, even if the callback is called synchronously in the dedicated
+ * thread.
+ *
+ * This message completion callback is only valid for Call requests.
+ * Connect and Disconnect are processed synchronously by the service.
+ */
+
+
+/* The maximum allowed size of a service name in bytes. */
+#define VBOX_HGCM_SVC_NAME_MAX_BYTES 1024
+
+struct _HGCMSVCEXTHANDLEDATA
+{
+ char *pszServiceName;
+ /* The service name follows. */
+};
+
+class HGCMClient;
+
+/** Internal helper service object. HGCM code would use it to
+ * hold information about services and communicate with services.
+ * The HGCMService is an (in future) abstract class that implements
+ * common functionality. There will be derived classes for specific
+ * service types.
+ */
+
+class HGCMService
+{
+ private:
+ VBOXHGCMSVCHELPERS m_svcHelpers;
+
+ static HGCMService *sm_pSvcListHead;
+ static HGCMService *sm_pSvcListTail;
+
+ static int sm_cServices;
+
+ HGCMThread *m_pThread;
+ friend DECLCALLBACK(void) hgcmServiceThread(HGCMThread *pThread, void *pvUser);
+
+ uint32_t volatile m_u32RefCnt;
+
+ HGCMService *m_pSvcNext;
+ HGCMService *m_pSvcPrev;
+
+ char *m_pszSvcName;
+ char *m_pszSvcLibrary;
+
+ RTLDRMOD m_hLdrMod;
+ PFNVBOXHGCMSVCLOAD m_pfnLoad;
+
+ VBOXHGCMSVCFNTABLE m_fntable;
+
+ /** Set if servicing SVC_MSG_CONNECT or SVC_MSG_DISCONNECT.
+ * Used for context checking pfnDisconnectClient calls, as it can only
+ * safely be made when the main HGCM thread is waiting on the service to
+ * process those messages. */
+ bool m_fInConnectOrDisconnect;
+
+ uint32_t m_acClients[HGCM_CLIENT_CATEGORY_MAX]; /**< Clients per category. */
+ uint32_t m_cClients;
+ uint32_t m_cClientsAllocated;
+
+ uint32_t *m_paClientIds;
+
+ HGCMSVCEXTHANDLE m_hExtension;
+
+ PUVM m_pUVM;
+ PCVMMR3VTABLE m_pVMM;
+ PPDMIHGCMPORT m_pHgcmPort;
+
+ /** @name Statistics
+ * @{ */
+ STAMPROFILE m_StatHandleMsg;
+ STAMCOUNTER m_StatTooManyClients;
+ STAMCOUNTER m_StatTooManyCalls;
+ /** @} */
+
+ int loadServiceDLL(void);
+ void unloadServiceDLL(void);
+
+ /*
+ * Main HGCM thread methods.
+ */
+ int instanceCreate(const char *pszServiceLibrary, const char *pszServiceName,
+ PUVM pUVM, PCVMMR3VTABLE pVMM, PPDMIHGCMPORT pHgcmPort);
+ void registerStatistics(const char *pszServiceName, PUVM pUVM, PCVMMR3VTABLE pVMM);
+ void instanceDestroy(void);
+
+ int saveClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM);
+ int loadClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion);
+
+ HGCMService();
+ ~HGCMService() {};
+
+ static DECLCALLBACK(int) svcHlpCallComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc);
+ static DECLCALLBACK(int) svcHlpDisconnectClient(void *pvInstance, uint32_t idClient);
+ static DECLCALLBACK(bool) svcHlpIsCallRestored(VBOXHGCMCALLHANDLE callHandle);
+ static DECLCALLBACK(bool) svcHlpIsCallCancelled(VBOXHGCMCALLHANDLE callHandle);
+ static DECLCALLBACK(int) svcHlpStamRegisterV(void *pvInstance, void *pvSample, STAMTYPE enmType,
+ STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc,
+ const char *pszName, va_list va);
+ static DECLCALLBACK(int) svcHlpStamDeregisterV(void *pvInstance, const char *pszPatFmt, va_list va);
+ static DECLCALLBACK(int) svcHlpInfoRegister(void *pvInstance, const char *pszName, const char *pszDesc,
+ PFNDBGFHANDLEREXT pfnHandler, void *pvUser);
+ static DECLCALLBACK(int) svcHlpInfoDeregister(void *pvInstance, const char *pszName);
+ static DECLCALLBACK(uint32_t) svcHlpGetRequestor(VBOXHGCMCALLHANDLE hCall);
+ static DECLCALLBACK(uint64_t) svcHlpGetVMMDevSessionId(void *pvInstance);
+
+ public:
+
+ /*
+ * Main HGCM thread methods.
+ */
+ static int LoadService(const char *pszServiceLibrary, const char *pszServiceName,
+ PUVM pUVM, PCVMMR3VTABLE pVMM, PPDMIHGCMPORT pHgcmPort);
+ void UnloadService(bool fUvmIsInvalid);
+
+ static void UnloadAll(bool fUvmIsInvalid);
+
+ static int ResolveService(HGCMService **ppsvc, const char *pszServiceName);
+ void ReferenceService(void);
+ void ReleaseService(void);
+
+ static void Reset(void);
+
+ static int SaveState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM);
+ static int LoadState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion);
+
+ int CreateAndConnectClient(uint32_t *pu32ClientIdOut, uint32_t u32ClientIdIn, uint32_t fRequestor, bool fRestoring);
+ int DisconnectClient(uint32_t u32ClientId, bool fFromService, HGCMClient *pClient);
+
+ int HostCall(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM *paParms);
+ static void BroadcastNotify(HGCMNOTIFYEVENT enmEvent);
+ void Notify(HGCMNOTIFYEVENT enmEvent);
+
+ uint32_t SizeOfClient(void) { return m_fntable.cbClient; };
+
+ int RegisterExtension(HGCMSVCEXTHANDLE handle, PFNHGCMSVCEXT pfnExtension, void *pvExtension);
+ void UnregisterExtension(HGCMSVCEXTHANDLE handle);
+
+ /*
+ * The service thread methods.
+ */
+
+ int GuestCall(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientId, HGCMClient *pClient,
+ uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM aParms[], uint64_t tsArrival);
+ void GuestCancelled(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t idClient);
+};
+
+
+class HGCMClient: public HGCMObject
+{
+ public:
+ HGCMClient(uint32_t a_fRequestor, uint32_t a_idxCategory)
+ : HGCMObject(HGCMOBJ_CLIENT)
+ , pService(NULL)
+ , pvData(NULL)
+ , fRequestor(a_fRequestor)
+ , idxCategory(a_idxCategory)
+ , cPendingCalls(0)
+ , m_fGuestAccessible(false)
+ {
+ Assert(idxCategory < HGCM_CLIENT_CATEGORY_MAX);
+ }
+ ~HGCMClient();
+
+ int Init(HGCMService *pSvc);
+
+ /** Lookups a client object by its handle. */
+ static HGCMClient *ReferenceByHandle(uint32_t idClient)
+ {
+ return (HGCMClient *)hgcmObjReference(idClient, HGCMOBJ_CLIENT);
+ }
+
+ /** Lookups a client object by its handle and makes sure that it's accessible to the guest. */
+ static HGCMClient *ReferenceByHandleForGuest(uint32_t idClient)
+ {
+ HGCMClient *pClient = (HGCMClient *)hgcmObjReference(idClient, HGCMOBJ_CLIENT);
+ if (pClient)
+ {
+ if (RT_LIKELY(pClient->m_fGuestAccessible))
+ return pClient;
+ pClient->Dereference();
+ }
+ return NULL;
+ }
+
+ /** Make the client object accessible to the guest. */
+ void makeAccessibleToGuest()
+ {
+ ASMAtomicWriteBool(&m_fGuestAccessible, true);
+ }
+
+ /** Service that the client is connected to. */
+ HGCMService *pService;
+
+ /** Client specific data. */
+ void *pvData;
+
+ /** The requestor flags this client was created with.
+ * @sa VMMDevRequestHeader::fRequestor */
+ uint32_t fRequestor;
+
+ /** The client category (HGCM_CLIENT_CATEGORY_XXX). */
+ uint32_t idxCategory;
+
+ /** Number of pending calls. */
+ uint32_t volatile cPendingCalls;
+
+ protected:
+ /** Set if the client is accessible to the guest, clear if not. */
+ bool volatile m_fGuestAccessible;
+
+ private: /* none of this: */
+ HGCMClient();
+ HGCMClient(HGCMClient const &);
+ HGCMClient &operator=(HGCMClient const &);
+};
+
+HGCMClient::~HGCMClient()
+{
+ if (pService->SizeOfClient() > 0)
+ {
+ RTMemFree(pvData);
+ pvData = NULL;
+ }
+}
+
+
+int HGCMClient::Init(HGCMService *pSvc)
+{
+ pService = pSvc;
+
+ if (pService->SizeOfClient() > 0)
+ {
+ pvData = RTMemAllocZ(pService->SizeOfClient());
+
+ if (!pvData)
+ {
+ return VERR_NO_MEMORY;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+#define HGCM_CLIENT_DATA(pService, pClient)(pClient->pvData)
+
+
+
+HGCMService *HGCMService::sm_pSvcListHead = NULL;
+HGCMService *HGCMService::sm_pSvcListTail = NULL;
+int HGCMService::sm_cServices = 0;
+
+HGCMService::HGCMService()
+ :
+ m_pThread (NULL),
+ m_u32RefCnt (0),
+ m_pSvcNext (NULL),
+ m_pSvcPrev (NULL),
+ m_pszSvcName (NULL),
+ m_pszSvcLibrary (NULL),
+ m_hLdrMod (NIL_RTLDRMOD),
+ m_pfnLoad (NULL),
+ m_fInConnectOrDisconnect(false),
+ m_cClients (0),
+ m_cClientsAllocated (0),
+ m_paClientIds (NULL),
+ m_hExtension (NULL),
+ m_pUVM (NULL),
+ m_pVMM (NULL),
+ m_pHgcmPort (NULL)
+{
+ RT_ZERO(m_acClients);
+ RT_ZERO(m_fntable);
+}
+
+
+static bool g_fResetting = false;
+static bool g_fSaveState = false;
+
+
+/** Helper function to load a local service DLL.
+ *
+ * @return VBox code
+ */
+int HGCMService::loadServiceDLL(void)
+{
+ LogFlowFunc(("m_pszSvcLibrary = %s\n", m_pszSvcLibrary));
+
+ if (m_pszSvcLibrary == NULL)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ RTERRINFOSTATIC ErrInfo;
+ RTErrInfoInitStatic(&ErrInfo);
+
+ int vrc;
+
+ if (RTPathHasPath(m_pszSvcLibrary))
+ vrc = SUPR3HardenedLdrLoadPlugIn(m_pszSvcLibrary, &m_hLdrMod, &ErrInfo.Core);
+ else
+ vrc = SUPR3HardenedLdrLoadAppPriv(m_pszSvcLibrary, &m_hLdrMod, RTLDRLOAD_FLAGS_LOCAL, &ErrInfo.Core);
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowFunc(("successfully loaded the library.\n"));
+
+ m_pfnLoad = NULL;
+
+ vrc = RTLdrGetSymbol(m_hLdrMod, VBOX_HGCM_SVCLOAD_NAME, (void**)&m_pfnLoad);
+
+ if (RT_FAILURE(vrc) || !m_pfnLoad)
+ {
+ Log(("HGCMService::loadServiceDLL: Error resolving the service entry point %s, vrc = %Rrc, m_pfnLoad = %p\n",
+ VBOX_HGCM_SVCLOAD_NAME, vrc, m_pfnLoad));
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* m_pfnLoad was NULL */
+ vrc = VERR_SYMBOL_NOT_FOUND;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ RT_ZERO(m_fntable);
+
+ m_fntable.cbSize = sizeof(m_fntable);
+ m_fntable.u32Version = VBOX_HGCM_SVC_VERSION;
+ m_fntable.pHelpers = &m_svcHelpers;
+
+ /* Total max calls: (2048 + 1024 + 1024) * 8192 = 33 554 432 */
+ m_fntable.idxLegacyClientCategory = HGCM_CLIENT_CATEGORY_KERNEL;
+ m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL] = _2K;
+ m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_ROOT] = _1K;
+ m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_USER] = _1K;
+ m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL] = _8K;
+ m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT] = _4K;
+ m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER] = _2K;
+ /** @todo provide way to configure different values via extra data. */
+
+ vrc = m_pfnLoad(&m_fntable);
+
+ LogFlowFunc(("m_pfnLoad vrc = %Rrc\n", vrc));
+
+ if (RT_SUCCESS(vrc))
+ {
+ if ( m_fntable.pfnUnload != NULL
+ && m_fntable.pfnConnect != NULL
+ && m_fntable.pfnDisconnect != NULL
+ && m_fntable.pfnCall != NULL
+ )
+ {
+ Assert(m_fntable.idxLegacyClientCategory < RT_ELEMENTS(m_fntable.acMaxClients));
+ LogRel2(("HGCMService::loadServiceDLL: acMaxClients={%u,%u,%u} acMaxCallsPerClient={%u,%u,%u} => %RU64 calls; idxLegacyClientCategory=%d; %s\n",
+ m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL],
+ m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_ROOT],
+ m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_USER],
+ m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL],
+ m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT],
+ m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER],
+ (uint64_t)m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL]
+ * m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL]
+ + (uint64_t)m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_ROOT]
+ * m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT]
+ + (uint64_t)m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_USER]
+ * m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER],
+ m_fntable.idxLegacyClientCategory, m_pszSvcName));
+ }
+ else
+ {
+ Log(("HGCMService::loadServiceDLL: at least one of function pointers is NULL\n"));
+
+ vrc = VERR_INVALID_PARAMETER;
+
+ if (m_fntable.pfnUnload)
+ {
+ m_fntable.pfnUnload(m_fntable.pvService);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ LogRel(("HGCM: Failed to load the service library: [%s], vrc = %Rrc - %s. The service will be not available.\n",
+ m_pszSvcLibrary, vrc, ErrInfo.Core.pszMsg));
+ m_hLdrMod = NIL_RTLDRMOD;
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ unloadServiceDLL();
+ }
+
+ return vrc;
+}
+
+/** Helper function to free a local service DLL.
+ *
+ * @return VBox code
+ */
+void HGCMService::unloadServiceDLL(void)
+{
+ if (m_hLdrMod)
+ {
+ RTLdrClose(m_hLdrMod);
+ }
+
+ RT_ZERO(m_fntable);
+ m_pfnLoad = NULL;
+ m_hLdrMod = NIL_RTLDRMOD;
+}
+
+/*
+ * Messages processed by service threads. These threads only call the service entry points.
+ */
+
+#define SVC_MSG_LOAD (0) /**< Load the service library and call VBOX_HGCM_SVCLOAD_NAME entry point. */
+#define SVC_MSG_UNLOAD (1) /**< call pfnUnload and unload the service library. */
+#define SVC_MSG_CONNECT (2) /**< pfnConnect */
+#define SVC_MSG_DISCONNECT (3) /**< pfnDisconnect */
+#define SVC_MSG_GUESTCALL (4) /**< pfnGuestCall */
+#define SVC_MSG_HOSTCALL (5) /**< pfnHostCall */
+#define SVC_MSG_LOADSTATE (6) /**< pfnLoadState. */
+#define SVC_MSG_SAVESTATE (7) /**< pfnSaveState. */
+#define SVC_MSG_QUIT (8) /**< Terminate the thread. */
+#define SVC_MSG_REGEXT (9) /**< pfnRegisterExtension */
+#define SVC_MSG_UNREGEXT (10) /**< pfnRegisterExtension */
+#define SVC_MSG_NOTIFY (11) /**< pfnNotify */
+#define SVC_MSG_GUESTCANCELLED (12) /**< pfnCancelled */
+
+class HGCMMsgSvcLoad: public HGCMMsgCore
+{
+ public:
+ HGCMMsgSvcLoad() : HGCMMsgCore(), pUVM() {}
+
+ /** The user mode VM handle (for statistics and such). */
+ PUVM pUVM;
+};
+
+class HGCMMsgSvcUnload: public HGCMMsgCore
+{
+};
+
+class HGCMMsgSvcConnect: public HGCMMsgCore
+{
+ public:
+ /** client identifier */
+ uint32_t u32ClientId;
+ /** Requestor flags. */
+ uint32_t fRequestor;
+ /** Set if restoring. */
+ bool fRestoring;
+};
+
+class HGCMMsgSvcDisconnect: public HGCMMsgCore
+{
+ public:
+ /** client identifier */
+ uint32_t u32ClientId;
+ /** The client instance. */
+ HGCMClient *pClient;
+};
+
+class HGCMMsgHeader: public HGCMMsgCore
+{
+ public:
+ HGCMMsgHeader() : pCmd(NULL), pHGCMPort(NULL) {};
+
+ /* Command pointer/identifier. */
+ PVBOXHGCMCMD pCmd;
+
+ /* Port to be informed on message completion. */
+ PPDMIHGCMPORT pHGCMPort;
+};
+
+class HGCMMsgCall: public HGCMMsgHeader
+{
+ public:
+ HGCMMsgCall() : pcCounter(NULL)
+ { }
+
+ HGCMMsgCall(HGCMThread *pThread)
+ : pcCounter(NULL)
+ {
+ InitializeCore(SVC_MSG_GUESTCALL, pThread);
+ Initialize();
+ }
+ ~HGCMMsgCall()
+ {
+ Log(("~HGCMMsgCall %p\n", this));
+ Assert(!pcCounter);
+ }
+
+ /** Points to HGCMClient::cPendingCalls if it needs to be decremented. */
+ uint32_t volatile *pcCounter;
+
+ /* client identifier */
+ uint32_t u32ClientId;
+
+ /* function number */
+ uint32_t u32Function;
+
+ /* number of parameters */
+ uint32_t cParms;
+
+ VBOXHGCMSVCPARM *paParms;
+
+ /** The STAM_GET_TS() value when the request arrived. */
+ uint64_t tsArrival;
+};
+
+class HGCMMsgCancelled: public HGCMMsgHeader
+{
+ public:
+ HGCMMsgCancelled() {}
+
+ HGCMMsgCancelled(HGCMThread *pThread)
+ {
+ InitializeCore(SVC_MSG_GUESTCANCELLED, pThread);
+ Initialize();
+ }
+ ~HGCMMsgCancelled() { Log(("~HGCMMsgCancelled %p\n", this)); }
+
+ /** The client identifier. */
+ uint32_t idClient;
+};
+
+class HGCMMsgLoadSaveStateClient: public HGCMMsgCore
+{
+ public:
+ PSSMHANDLE pSSM;
+ PCVMMR3VTABLE pVMM;
+ uint32_t uVersion;
+ uint32_t u32ClientId;
+};
+
+class HGCMMsgHostCallSvc: public HGCMMsgCore
+{
+ public:
+ /* function number */
+ uint32_t u32Function;
+
+ /* number of parameters */
+ uint32_t cParms;
+
+ VBOXHGCMSVCPARM *paParms;
+};
+
+class HGCMMsgSvcRegisterExtension: public HGCMMsgCore
+{
+ public:
+ /* Handle of the extension to be registered. */
+ HGCMSVCEXTHANDLE handle;
+ /* The extension entry point. */
+ PFNHGCMSVCEXT pfnExtension;
+ /* The extension pointer. */
+ void *pvExtension;
+};
+
+class HGCMMsgSvcUnregisterExtension: public HGCMMsgCore
+{
+ public:
+ /* Handle of the registered extension. */
+ HGCMSVCEXTHANDLE handle;
+};
+
+class HGCMMsgNotify: public HGCMMsgCore
+{
+ public:
+ /** The event. */
+ HGCMNOTIFYEVENT enmEvent;
+};
+
+static HGCMMsgCore *hgcmMessageAllocSvc(uint32_t u32MsgId)
+{
+ switch (u32MsgId)
+ {
+ case SVC_MSG_LOAD: return new HGCMMsgSvcLoad();
+ case SVC_MSG_UNLOAD: return new HGCMMsgSvcUnload();
+ case SVC_MSG_CONNECT: return new HGCMMsgSvcConnect();
+ case SVC_MSG_DISCONNECT: return new HGCMMsgSvcDisconnect();
+ case SVC_MSG_HOSTCALL: return new HGCMMsgHostCallSvc();
+ case SVC_MSG_GUESTCALL: return new HGCMMsgCall();
+ case SVC_MSG_LOADSTATE:
+ case SVC_MSG_SAVESTATE: return new HGCMMsgLoadSaveStateClient();
+ case SVC_MSG_REGEXT: return new HGCMMsgSvcRegisterExtension();
+ case SVC_MSG_UNREGEXT: return new HGCMMsgSvcUnregisterExtension();
+ case SVC_MSG_NOTIFY: return new HGCMMsgNotify();
+ case SVC_MSG_GUESTCANCELLED: return new HGCMMsgCancelled();
+ default:
+ AssertReleaseMsgFailed(("Msg id = %08X\n", u32MsgId));
+ }
+
+ return NULL;
+}
+
+/*
+ * The service thread. Loads the service library and calls the service entry points.
+ */
+DECLCALLBACK(void) hgcmServiceThread(HGCMThread *pThread, void *pvUser)
+{
+ HGCMService *pSvc = (HGCMService *)pvUser;
+ AssertRelease(pSvc != NULL);
+
+ bool fQuit = false;
+
+ while (!fQuit)
+ {
+ HGCMMsgCore *pMsgCore;
+ int vrc = hgcmMsgGet(pThread, &pMsgCore);
+
+ if (RT_FAILURE(vrc))
+ {
+ /* The error means some serious unrecoverable problem in the hgcmMsg/hgcmThread layer. */
+ AssertMsgFailed(("%Rrc\n", vrc));
+ break;
+ }
+
+ STAM_REL_PROFILE_START(&pSvc->m_StatHandleMsg, a);
+
+ /* Cache required information to avoid unnecessary pMsgCore access. */
+ uint32_t u32MsgId = pMsgCore->MsgId();
+
+ switch (u32MsgId)
+ {
+ case SVC_MSG_LOAD:
+ {
+ LogFlowFunc(("SVC_MSG_LOAD\n"));
+ vrc = pSvc->loadServiceDLL();
+ } break;
+
+ case SVC_MSG_UNLOAD:
+ {
+ LogFlowFunc(("SVC_MSG_UNLOAD\n"));
+ if (pSvc->m_fntable.pfnUnload)
+ {
+ pSvc->m_fntable.pfnUnload(pSvc->m_fntable.pvService);
+ }
+
+ pSvc->unloadServiceDLL();
+ fQuit = true;
+ } break;
+
+ case SVC_MSG_CONNECT:
+ {
+ HGCMMsgSvcConnect *pMsg = (HGCMMsgSvcConnect *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_CONNECT u32ClientId = %d\n", pMsg->u32ClientId));
+
+ HGCMClient *pClient = HGCMClient::ReferenceByHandle(pMsg->u32ClientId);
+
+ if (pClient)
+ {
+ pSvc->m_fInConnectOrDisconnect = true;
+ vrc = pSvc->m_fntable.pfnConnect(pSvc->m_fntable.pvService, pMsg->u32ClientId,
+ HGCM_CLIENT_DATA(pSvc, pClient),
+ pMsg->fRequestor, pMsg->fRestoring);
+ pSvc->m_fInConnectOrDisconnect = false;
+
+ hgcmObjDereference(pClient);
+ }
+ else
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ }
+ } break;
+
+ case SVC_MSG_DISCONNECT:
+ {
+ HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_DISCONNECT u32ClientId = %d, pClient = %p\n", pMsg->u32ClientId, pMsg->pClient));
+
+ if (pMsg->pClient)
+ {
+ pSvc->m_fInConnectOrDisconnect = true;
+ vrc = pSvc->m_fntable.pfnDisconnect(pSvc->m_fntable.pvService, pMsg->u32ClientId,
+ HGCM_CLIENT_DATA(pSvc, pMsg->pClient));
+ pSvc->m_fInConnectOrDisconnect = false;
+ }
+ else
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ }
+ } break;
+
+ case SVC_MSG_GUESTCALL:
+ {
+ HGCMMsgCall *pMsg = (HGCMMsgCall *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_GUESTCALL u32ClientId = %d, u32Function = %d, cParms = %d, paParms = %p\n",
+ pMsg->u32ClientId, pMsg->u32Function, pMsg->cParms, pMsg->paParms));
+
+ HGCMClient *pClient = HGCMClient::ReferenceByHandleForGuest(pMsg->u32ClientId);
+
+ if (pClient)
+ {
+ pSvc->m_fntable.pfnCall(pSvc->m_fntable.pvService, (VBOXHGCMCALLHANDLE)pMsg, pMsg->u32ClientId,
+ HGCM_CLIENT_DATA(pSvc, pClient), pMsg->u32Function,
+ pMsg->cParms, pMsg->paParms, pMsg->tsArrival);
+
+ hgcmObjDereference(pClient);
+ }
+ else
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ }
+ } break;
+
+ case SVC_MSG_GUESTCANCELLED:
+ {
+ HGCMMsgCancelled *pMsg = (HGCMMsgCancelled *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_GUESTCANCELLED idClient = %d\n", pMsg->idClient));
+
+ HGCMClient *pClient = HGCMClient::ReferenceByHandleForGuest(pMsg->idClient);
+
+ if (pClient)
+ {
+ pSvc->m_fntable.pfnCancelled(pSvc->m_fntable.pvService, pMsg->idClient, HGCM_CLIENT_DATA(pSvc, pClient));
+
+ hgcmObjDereference(pClient);
+ }
+ else
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ }
+ } break;
+
+ case SVC_MSG_HOSTCALL:
+ {
+ HGCMMsgHostCallSvc *pMsg = (HGCMMsgHostCallSvc *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_HOSTCALL u32Function = %d, cParms = %d, paParms = %p\n",
+ pMsg->u32Function, pMsg->cParms, pMsg->paParms));
+
+ vrc = pSvc->m_fntable.pfnHostCall(pSvc->m_fntable.pvService, pMsg->u32Function, pMsg->cParms, pMsg->paParms);
+ } break;
+
+ case SVC_MSG_LOADSTATE:
+ {
+ HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_LOADSTATE\n"));
+
+ HGCMClient *pClient = HGCMClient::ReferenceByHandle(pMsg->u32ClientId);
+
+ if (pClient)
+ {
+ /* fRequestor: Restored by the message sender already. */
+ bool fHaveClientState = pSvc->m_fntable.pfnLoadState != NULL;
+ if (pMsg->uVersion > HGCM_SAVED_STATE_VERSION_V2)
+ vrc = pMsg->pVMM->pfnSSMR3GetBool(pMsg->pSSM, &fHaveClientState);
+ else
+ vrc = VINF_SUCCESS;
+ if (RT_SUCCESS(vrc) )
+ {
+ if (pSvc->m_fntable.pfnLoadState)
+ vrc = pSvc->m_fntable.pfnLoadState(pSvc->m_fntable.pvService, pMsg->u32ClientId,
+ HGCM_CLIENT_DATA(pSvc, pClient), pMsg->pSSM, pMsg->pVMM,
+ fHaveClientState ? pMsg->uVersion : 0);
+ else
+ AssertLogRelStmt(!fHaveClientState, vrc = VERR_INTERNAL_ERROR_5);
+ }
+ hgcmObjDereference(pClient);
+ }
+ else
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ }
+ } break;
+
+ case SVC_MSG_SAVESTATE:
+ {
+ HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_SAVESTATE\n"));
+
+ HGCMClient *pClient = HGCMClient::ReferenceByHandle(pMsg->u32ClientId);
+
+ vrc = VINF_SUCCESS;
+
+ if (pClient)
+ {
+ pMsg->pVMM->pfnSSMR3PutU32(pMsg->pSSM, pClient->fRequestor); /* Quicker to save this here than in the message sender. */
+ vrc = pMsg->pVMM->pfnSSMR3PutBool(pMsg->pSSM, pSvc->m_fntable.pfnSaveState != NULL);
+ if (RT_SUCCESS(vrc) && pSvc->m_fntable.pfnSaveState)
+ {
+ g_fSaveState = true;
+ vrc = pSvc->m_fntable.pfnSaveState(pSvc->m_fntable.pvService, pMsg->u32ClientId,
+ HGCM_CLIENT_DATA(pSvc, pClient), pMsg->pSSM, pMsg->pVMM);
+ g_fSaveState = false;
+ }
+
+ hgcmObjDereference(pClient);
+ }
+ else
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ }
+ } break;
+
+ case SVC_MSG_REGEXT:
+ {
+ HGCMMsgSvcRegisterExtension *pMsg = (HGCMMsgSvcRegisterExtension *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_REGEXT handle = %p, pfn = %p\n", pMsg->handle, pMsg->pfnExtension));
+
+ if (pSvc->m_hExtension)
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ else
+ {
+ if (pSvc->m_fntable.pfnRegisterExtension)
+ {
+ vrc = pSvc->m_fntable.pfnRegisterExtension(pSvc->m_fntable.pvService, pMsg->pfnExtension,
+ pMsg->pvExtension);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pSvc->m_hExtension = pMsg->handle;
+ }
+ }
+ } break;
+
+ case SVC_MSG_UNREGEXT:
+ {
+ HGCMMsgSvcUnregisterExtension *pMsg = (HGCMMsgSvcUnregisterExtension *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_UNREGEXT handle = %p\n", pMsg->handle));
+
+ if (pSvc->m_hExtension != pMsg->handle)
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ else
+ {
+ if (pSvc->m_fntable.pfnRegisterExtension)
+ {
+ vrc = pSvc->m_fntable.pfnRegisterExtension(pSvc->m_fntable.pvService, NULL, NULL);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ pSvc->m_hExtension = NULL;
+ }
+ } break;
+
+ case SVC_MSG_NOTIFY:
+ {
+ HGCMMsgNotify *pMsg = (HGCMMsgNotify *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_NOTIFY enmEvent = %d\n", pMsg->enmEvent));
+
+ pSvc->m_fntable.pfnNotify(pSvc->m_fntable.pvService, pMsg->enmEvent);
+ } break;
+
+ default:
+ {
+ AssertMsgFailed(("hgcmServiceThread::Unsupported message number %08X\n", u32MsgId));
+ vrc = VERR_NOT_SUPPORTED;
+ } break;
+ }
+
+ if (u32MsgId != SVC_MSG_GUESTCALL)
+ {
+ /* For SVC_MSG_GUESTCALL the service calls the completion helper.
+ * Other messages have to be completed here.
+ */
+ hgcmMsgComplete (pMsgCore, vrc);
+ }
+ STAM_REL_PROFILE_STOP(&pSvc->m_StatHandleMsg, a);
+ }
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnCallComplete}
+ */
+/* static */ DECLCALLBACK(int) HGCMService::svcHlpCallComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc)
+{
+ HGCMMsgCore *pMsgCore = (HGCMMsgCore *)callHandle;
+
+ /* Only call the completion for these messages. The helper
+ * is called by the service, and the service does not get
+ * any other messages.
+ */
+ AssertMsgReturn(pMsgCore->MsgId() == SVC_MSG_GUESTCALL, ("%d\n", pMsgCore->MsgId()), VERR_WRONG_TYPE);
+ return hgcmMsgComplete(pMsgCore, rc);
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnDisconnectClient}
+ */
+/* static */ DECLCALLBACK(int) HGCMService::svcHlpDisconnectClient(void *pvInstance, uint32_t idClient)
+{
+ HGCMService *pService = static_cast <HGCMService *> (pvInstance);
+ AssertReturn(pService, VERR_INVALID_HANDLE);
+
+ /* Only safe to call when the main HGCM thread is waiting on the service
+ to handle a SVC_MSG_CONNECT or SVC_MSG_DISCONNECT message. Otherwise
+ we'd risk racing it and corrupt data structures. */
+ AssertReturn(pService->m_fInConnectOrDisconnect, VERR_INVALID_CONTEXT);
+
+ /* Resolve the client ID and verify that it belongs to this service before
+ trying to disconnect it. */
+ int vrc = VERR_NOT_FOUND;
+ HGCMClient * const pClient = HGCMClient::ReferenceByHandle(idClient);
+ if (pClient)
+ {
+ if (pClient->pService == pService)
+ vrc = pService->DisconnectClient(idClient, true, pClient);
+ hgcmObjDereference(pClient);
+ }
+ return vrc;
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnIsCallRestored}
+ */
+/* static */ DECLCALLBACK(bool) HGCMService::svcHlpIsCallRestored(VBOXHGCMCALLHANDLE callHandle)
+{
+ HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)callHandle;
+ AssertPtrReturn(pMsgHdr, false);
+
+ PVBOXHGCMCMD pCmd = pMsgHdr->pCmd;
+ AssertPtrReturn(pCmd, false);
+
+ PPDMIHGCMPORT pHgcmPort = pMsgHdr->pHGCMPort;
+ AssertPtrReturn(pHgcmPort, false);
+
+ return pHgcmPort->pfnIsCmdRestored(pHgcmPort, pCmd);
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnIsCallCancelled}
+ */
+/* static */ DECLCALLBACK(bool) HGCMService::svcHlpIsCallCancelled(VBOXHGCMCALLHANDLE callHandle)
+{
+ HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)callHandle;
+ AssertPtrReturn(pMsgHdr, false);
+
+ PVBOXHGCMCMD pCmd = pMsgHdr->pCmd;
+ AssertPtrReturn(pCmd, false);
+
+ PPDMIHGCMPORT pHgcmPort = pMsgHdr->pHGCMPort;
+ AssertPtrReturn(pHgcmPort, false);
+
+ return pHgcmPort->pfnIsCmdCancelled(pHgcmPort, pCmd);
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnStamRegisterV}
+ */
+/* static */ DECLCALLBACK(int)
+HGCMService::svcHlpStamRegisterV(void *pvInstance, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility,
+ STAMUNIT enmUnit, const char *pszDesc, const char *pszName, va_list va)
+{
+ HGCMService *pService = static_cast <HGCMService *>(pvInstance);
+ AssertPtrReturn(pService, VERR_INVALID_PARAMETER);
+
+ if (pService->m_pUVM)
+ return pService->m_pVMM->pfnSTAMR3RegisterVU(pService->m_pUVM, pvSample, enmType, enmVisibility,
+ enmUnit, pszDesc, pszName, va);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnStamDeregisterV}
+ */
+/* static */ DECLCALLBACK(int) HGCMService::svcHlpStamDeregisterV(void *pvInstance, const char *pszPatFmt, va_list va)
+{
+ HGCMService *pService = static_cast <HGCMService *>(pvInstance);
+ AssertPtrReturn(pService, VERR_INVALID_PARAMETER);
+
+ if (pService->m_pUVM)
+ return pService->m_pVMM->pfnSTAMR3DeregisterV(pService->m_pUVM, pszPatFmt, va);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnInfoRegister}
+ */
+/* static */ DECLCALLBACK(int) HGCMService::svcHlpInfoRegister(void *pvInstance, const char *pszName, const char *pszDesc,
+ PFNDBGFHANDLEREXT pfnHandler, void *pvUser)
+{
+ HGCMService *pService = static_cast <HGCMService *>(pvInstance);
+ AssertPtrReturn(pService, VERR_INVALID_PARAMETER);
+
+ if (pService->m_pUVM)
+ return pService->m_pVMM->pfnDBGFR3InfoRegisterExternal(pService->m_pUVM, pszName, pszDesc, pfnHandler, pvUser);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnInfoDeregister}
+ */
+/* static */ DECLCALLBACK(int) HGCMService::svcHlpInfoDeregister(void *pvInstance, const char *pszName)
+{
+ HGCMService *pService = static_cast <HGCMService *>(pvInstance);
+ AssertPtrReturn(pService, VERR_INVALID_PARAMETER);
+ if (pService->m_pUVM)
+ return pService->m_pVMM->pfnDBGFR3InfoDeregisterExternal(pService->m_pUVM, pszName);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnGetRequestor}
+ */
+/* static */ DECLCALLBACK(uint32_t) HGCMService::svcHlpGetRequestor(VBOXHGCMCALLHANDLE hCall)
+{
+ HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)(hCall);
+ AssertPtrReturn(pMsgHdr, VMMDEV_REQUESTOR_LOWEST);
+
+ PVBOXHGCMCMD pCmd = pMsgHdr->pCmd;
+ AssertPtrReturn(pCmd, VMMDEV_REQUESTOR_LOWEST);
+
+ PPDMIHGCMPORT pHgcmPort = pMsgHdr->pHGCMPort;
+ AssertPtrReturn(pHgcmPort, VMMDEV_REQUESTOR_LOWEST);
+
+ return pHgcmPort->pfnGetRequestor(pHgcmPort, pCmd);
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnGetVMMDevSessionId}
+ */
+/* static */ DECLCALLBACK(uint64_t) HGCMService::svcHlpGetVMMDevSessionId(void *pvInstance)
+{
+ HGCMService *pService = static_cast <HGCMService *>(pvInstance);
+ AssertPtrReturn(pService, UINT64_MAX);
+
+ PPDMIHGCMPORT pHgcmPort = pService->m_pHgcmPort;
+ AssertPtrReturn(pHgcmPort, UINT64_MAX);
+
+ return pHgcmPort->pfnGetVMMDevSessionId(pHgcmPort);
+}
+
+
+static DECLCALLBACK(int) hgcmMsgCompletionCallback(int32_t result, HGCMMsgCore *pMsgCore)
+{
+ /* Call the VMMDev port interface to issue IRQ notification. */
+ HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)pMsgCore;
+
+ LogFlow(("MAIN::hgcmMsgCompletionCallback: message %p\n", pMsgCore));
+
+ if (pMsgHdr->pHGCMPort)
+ {
+ if (!g_fResetting)
+ return pMsgHdr->pHGCMPort->pfnCompleted(pMsgHdr->pHGCMPort,
+ g_fSaveState ? VINF_HGCM_SAVE_STATE : result, pMsgHdr->pCmd);
+ return VERR_ALREADY_RESET; /* best I could find. */
+ }
+ return VERR_NOT_AVAILABLE;
+}
+
+/*
+ * The main HGCM methods of the service.
+ */
+
+int HGCMService::instanceCreate(const char *pszServiceLibrary, const char *pszServiceName,
+ PUVM pUVM, PCVMMR3VTABLE pVMM, PPDMIHGCMPORT pHgcmPort)
+{
+ LogFlowFunc(("name %s, lib %s\n", pszServiceName, pszServiceLibrary));
+
+ /* The maximum length of the thread name, allowed by the RT is 15. */
+ char szThreadName[16];
+ if (!strncmp(pszServiceName, RT_STR_TUPLE("VBoxShared")))
+ RTStrPrintf(szThreadName, sizeof(szThreadName), "Sh%s", pszServiceName + 10);
+ else if (!strncmp(pszServiceName, RT_STR_TUPLE("VBox")))
+ RTStrCopy(szThreadName, sizeof(szThreadName), pszServiceName + 4);
+ else
+ RTStrCopy(szThreadName, sizeof(szThreadName), pszServiceName);
+
+ int vrc = hgcmThreadCreate(&m_pThread, szThreadName, hgcmServiceThread, this, pszServiceName, pUVM, pVMM);
+ if (RT_SUCCESS(vrc))
+ {
+ m_pszSvcName = RTStrDup(pszServiceName);
+ m_pszSvcLibrary = RTStrDup(pszServiceLibrary);
+
+ if (!m_pszSvcName || !m_pszSvcLibrary)
+ {
+ RTStrFree(m_pszSvcLibrary);
+ m_pszSvcLibrary = NULL;
+
+ RTStrFree(m_pszSvcName);
+ m_pszSvcName = NULL;
+
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ m_pUVM = pUVM;
+ m_pVMM = pVMM;
+ m_pHgcmPort = pHgcmPort;
+
+ registerStatistics(pszServiceName, pUVM, pVMM);
+
+ /* Initialize service helpers table. */
+ m_svcHelpers.pfnCallComplete = svcHlpCallComplete;
+ m_svcHelpers.pvInstance = this;
+ m_svcHelpers.pfnDisconnectClient = svcHlpDisconnectClient;
+ m_svcHelpers.pfnIsCallRestored = svcHlpIsCallRestored;
+ m_svcHelpers.pfnIsCallCancelled = svcHlpIsCallCancelled;
+ m_svcHelpers.pfnStamRegisterV = svcHlpStamRegisterV;
+ m_svcHelpers.pfnStamDeregisterV = svcHlpStamDeregisterV;
+ m_svcHelpers.pfnInfoRegister = svcHlpInfoRegister;
+ m_svcHelpers.pfnInfoDeregister = svcHlpInfoDeregister;
+ m_svcHelpers.pfnGetRequestor = svcHlpGetRequestor;
+ m_svcHelpers.pfnGetVMMDevSessionId = svcHlpGetVMMDevSessionId;
+
+ /* Execute the load request on the service thread. */
+ HGCMMsgCore *pCoreMsg;
+ vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_LOAD, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgSvcLoad *pMsg = (HGCMMsgSvcLoad *)pCoreMsg;
+
+ pMsg->pUVM = pUVM;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ instanceDestroy();
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** Called by HGCMService::instanceCreate to register statistics. */
+void HGCMService::registerStatistics(const char *pszServiceName, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatHandleMsg, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_OCCURENCE,
+ "Message handling", "/HGCM/%s/Msg", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatTooManyCalls, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Too many calls (per client)", "/HGCM/%s/TooManyCalls", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatTooManyClients, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Too many clients", "/HGCM/%s/TooManyClients", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_cClients, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Number of clients", "/HGCM/%s/Clients", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_acClients[HGCM_CLIENT_CATEGORY_KERNEL], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Number of kernel clients", "/HGCM/%s/Clients/Kernel", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_acClients[HGCM_CLIENT_CATEGORY_ROOT], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Number of root/admin clients", "/HGCM/%s/Clients/Root", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_acClients[HGCM_CLIENT_CATEGORY_USER], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Number of regular user clients", "/HGCM/%s/Clients/User", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Max number of kernel clients", "/HGCM/%s/Clients/KernelMax", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_ROOT], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Max number of root clients", "/HGCM/%s/Clients/RootMax", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_USER], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Max number of user clients", "/HGCM/%s/Clients/UserMax", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.idxLegacyClientCategory, STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Legacy client mapping", "/HGCM/%s/Clients/LegacyClientMapping", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Max number of call per kernel client", "/HGCM/%s/MaxCallsKernelClient", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Max number of call per root client", "/HGCM/%s/MaxCallsRootClient", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Max number of call per user client", "/HGCM/%s/MaxCallsUserClient", pszServiceName);
+}
+
+void HGCMService::instanceDestroy(void)
+{
+ LogFlowFunc(("%s\n", m_pszSvcName));
+
+ HGCMMsgCore *pMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pMsg, SVC_MSG_UNLOAD, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = hgcmMsgSend(pMsg);
+
+ if (RT_SUCCESS(vrc))
+ hgcmThreadWait(m_pThread);
+ }
+
+ if (m_pszSvcName && m_pUVM)
+ m_pVMM->pfnSTAMR3DeregisterF(m_pUVM, "/HGCM/%s/*", m_pszSvcName);
+ m_pUVM = NULL;
+ m_pHgcmPort = NULL;
+
+ RTStrFree(m_pszSvcLibrary);
+ m_pszSvcLibrary = NULL;
+
+ RTStrFree(m_pszSvcName);
+ m_pszSvcName = NULL;
+
+ if (m_paClientIds)
+ {
+ RTMemFree(m_paClientIds);
+ m_paClientIds = NULL;
+ }
+}
+
+int HGCMService::saveClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM)
+{
+ LogFlowFunc(("%s\n", m_pszSvcName));
+
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_SAVESTATE, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pCoreMsg;
+
+ pMsg->u32ClientId = u32ClientId;
+ pMsg->pSSM = pSSM;
+ pMsg->pVMM = pVMM;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int HGCMService::loadClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion)
+{
+ LogFlowFunc(("%s\n", m_pszSvcName));
+
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_LOADSTATE, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pCoreMsg;
+
+ pMsg->pSSM = pSSM;
+ pMsg->pVMM = pVMM;
+ pMsg->uVersion = uVersion;
+ pMsg->u32ClientId = u32ClientId;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+
+/** The method creates a service and references it.
+ *
+ * @param pszServiceLibrary The library to be loaded.
+ * @param pszServiceName The name of the service.
+ * @param pUVM The user mode VM handle (for statistics and such).
+ * @param pVMM The VMM vtable (for statistics and such).
+ * @param pHgcmPort The VMMDev HGCM port interface.
+ *
+ * @return VBox rc.
+ * @thread main HGCM
+ */
+/* static */ int HGCMService::LoadService(const char *pszServiceLibrary, const char *pszServiceName,
+ PUVM pUVM, PCVMMR3VTABLE pVMM, PPDMIHGCMPORT pHgcmPort)
+{
+ LogFlowFunc(("lib %s, name = %s, pUVM = %p\n", pszServiceLibrary, pszServiceName, pUVM));
+
+ /* Look at already loaded services to avoid double loading. */
+
+ HGCMService *pSvc;
+ int vrc = HGCMService::ResolveService(&pSvc, pszServiceName);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* The service is already loaded. */
+ pSvc->ReleaseService();
+ vrc = VERR_HGCM_SERVICE_EXISTS;
+ }
+ else
+ {
+ /* Create the new service. */
+ pSvc = new (std::nothrow) HGCMService();
+
+ if (!pSvc)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Load the library and call the initialization entry point. */
+ vrc = pSvc->instanceCreate(pszServiceLibrary, pszServiceName, pUVM, pVMM, pHgcmPort);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Insert the just created service to list for future references. */
+ pSvc->m_pSvcNext = sm_pSvcListHead;
+ pSvc->m_pSvcPrev = NULL;
+
+ if (sm_pSvcListHead)
+ sm_pSvcListHead->m_pSvcPrev = pSvc;
+ else
+ sm_pSvcListTail = pSvc;
+
+ sm_pSvcListHead = pSvc;
+
+ sm_cServices++;
+
+ /* Reference the service (for first time) until it is unloaded on HGCM termination. */
+ AssertRelease(pSvc->m_u32RefCnt == 0);
+ pSvc->ReferenceService();
+
+ LogFlowFunc(("service %p\n", pSvc));
+ }
+ }
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** The method unloads a service.
+ *
+ * @thread main HGCM
+ */
+void HGCMService::UnloadService(bool fUvmIsInvalid)
+{
+ LogFlowFunc(("name = %s\n", m_pszSvcName));
+
+ if (fUvmIsInvalid)
+ {
+ m_pUVM = NULL;
+ m_pHgcmPort = NULL;
+ }
+
+ /* Remove the service from the list. */
+ if (m_pSvcNext)
+ {
+ m_pSvcNext->m_pSvcPrev = m_pSvcPrev;
+ }
+ else
+ {
+ sm_pSvcListTail = m_pSvcPrev;
+ }
+
+ if (m_pSvcPrev)
+ {
+ m_pSvcPrev->m_pSvcNext = m_pSvcNext;
+ }
+ else
+ {
+ sm_pSvcListHead = m_pSvcNext;
+ }
+
+ sm_cServices--;
+
+ /* The service must be unloaded only if all clients were disconnected. */
+ LogFlowFunc(("m_u32RefCnt = %d\n", m_u32RefCnt));
+ AssertRelease(m_u32RefCnt == 1);
+
+ /* Now the service can be released. */
+ ReleaseService();
+}
+
+/** The method unloads all services.
+ *
+ * @thread main HGCM
+ */
+/* static */ void HGCMService::UnloadAll(bool fUvmIsInvalid)
+{
+ while (sm_pSvcListHead)
+ {
+ sm_pSvcListHead->UnloadService(fUvmIsInvalid);
+ }
+}
+
+/** The method obtains a referenced pointer to the service with
+ * specified name. The caller must call ReleaseService when
+ * the pointer is no longer needed.
+ *
+ * @param ppSvc Where to store the pointer to the service.
+ * @param pszServiceName The name of the service.
+ * @return VBox rc.
+ * @thread main HGCM
+ */
+/* static */ int HGCMService::ResolveService(HGCMService **ppSvc, const char *pszServiceName)
+{
+ LogFlowFunc(("ppSvc = %p name = %s\n",
+ ppSvc, pszServiceName));
+
+ if (!ppSvc || !pszServiceName)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ HGCMService *pSvc = sm_pSvcListHead;
+
+ while (pSvc)
+ {
+ if (strcmp(pSvc->m_pszSvcName, pszServiceName) == 0)
+ {
+ break;
+ }
+
+ pSvc = pSvc->m_pSvcNext;
+ }
+
+ LogFlowFunc(("lookup in the list is %p\n", pSvc));
+
+ if (pSvc == NULL)
+ {
+ *ppSvc = NULL;
+ return VERR_HGCM_SERVICE_NOT_FOUND;
+ }
+
+ pSvc->ReferenceService();
+
+ *ppSvc = pSvc;
+
+ return VINF_SUCCESS;
+}
+
+/** The method increases reference counter.
+ *
+ * @thread main HGCM
+ */
+void HGCMService::ReferenceService(void)
+{
+ ASMAtomicIncU32(&m_u32RefCnt);
+ LogFlowFunc(("[%s] m_u32RefCnt = %d\n", m_pszSvcName, m_u32RefCnt));
+}
+
+/** The method dereferences a service and deletes it when no more refs.
+ *
+ * @thread main HGCM
+ */
+void HGCMService::ReleaseService(void)
+{
+ LogFlowFunc(("m_u32RefCnt = %d\n", m_u32RefCnt));
+ uint32_t u32RefCnt = ASMAtomicDecU32(&m_u32RefCnt);
+ AssertRelease(u32RefCnt != ~0U);
+
+ LogFlowFunc(("u32RefCnt = %d, name %s\n", u32RefCnt, m_pszSvcName));
+
+ if (u32RefCnt == 0)
+ {
+ instanceDestroy();
+ delete this;
+ }
+}
+
+/** The method is called when the VM is being reset or terminated
+ * and disconnects all clients from all services.
+ *
+ * @thread main HGCM
+ */
+/* static */ void HGCMService::Reset(void)
+{
+ g_fResetting = true;
+
+ HGCMService *pSvc = sm_pSvcListHead;
+
+ while (pSvc)
+ {
+ while (pSvc->m_cClients && pSvc->m_paClientIds)
+ {
+ uint32_t const idClient = pSvc->m_paClientIds[0];
+ HGCMClient * const pClient = HGCMClient::ReferenceByHandle(idClient);
+ Assert(pClient);
+ LogFlowFunc(("handle %d/%p\n", pSvc->m_paClientIds[0], pClient));
+
+ pSvc->DisconnectClient(pSvc->m_paClientIds[0], false, pClient);
+
+ hgcmObjDereference(pClient);
+ }
+
+ pSvc = pSvc->m_pSvcNext;
+ }
+
+ g_fResetting = false;
+}
+
+/** The method saves the HGCM state.
+ *
+ * @param pSSM The saved state context.
+ * @param pVMM The VMM vtable.
+ * @return VBox status code.
+ * @thread main HGCM
+ */
+/* static */ int HGCMService::SaveState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM)
+{
+ /* Save the current handle count and restore afterwards to avoid client id conflicts. */
+ int vrc = pVMM->pfnSSMR3PutU32(pSSM, hgcmObjQueryHandleCount());
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowFunc(("%d services to be saved:\n", sm_cServices));
+
+ /* Save number of services. */
+ vrc = pVMM->pfnSSMR3PutU32(pSSM, sm_cServices);
+ AssertRCReturn(vrc, vrc);
+
+ /* Save every service. */
+ HGCMService *pSvc = sm_pSvcListHead;
+
+ while (pSvc)
+ {
+ LogFlowFunc(("Saving service [%s]\n", pSvc->m_pszSvcName));
+
+ /* Save the length of the service name. */
+ vrc = pVMM->pfnSSMR3PutU32(pSSM, (uint32_t) strlen(pSvc->m_pszSvcName) + 1);
+ AssertRCReturn(vrc, vrc);
+
+ /* Save the name of the service. */
+ vrc = pVMM->pfnSSMR3PutStrZ(pSSM, pSvc->m_pszSvcName);
+ AssertRCReturn(vrc, vrc);
+
+ /* Save the number of clients. */
+ vrc = pVMM->pfnSSMR3PutU32(pSSM, pSvc->m_cClients);
+ AssertRCReturn(vrc, vrc);
+
+ /* Call the service for every client. Normally a service must not have
+ * a global state to be saved: only per client info is relevant.
+ * The global state of a service is configured during VM startup.
+ */
+ uint32_t i;
+
+ for (i = 0; i < pSvc->m_cClients; i++)
+ {
+ uint32_t u32ClientId = pSvc->m_paClientIds[i];
+
+ Log(("client id 0x%08X\n", u32ClientId));
+
+ /* Save the client id. (fRequestor is saved via SVC_MSG_SAVESTATE for convenience.) */
+ vrc = pVMM->pfnSSMR3PutU32(pSSM, u32ClientId);
+ AssertRCReturn(vrc, vrc);
+
+ /* Call the service, so the operation is executed by the service thread. */
+ vrc = pSvc->saveClientState(u32ClientId, pSSM, pVMM);
+ AssertRCReturn(vrc, vrc);
+ }
+
+ pSvc = pSvc->m_pSvcNext;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/** The method loads saved HGCM state.
+ *
+ * @param pSSM The saved state handle.
+ * @param pVMM The VMM vtable.
+ * @param uVersion The state version being loaded.
+ * @return VBox status code.
+ * @thread main HGCM
+ */
+/* static */ int HGCMService::LoadState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion)
+{
+ /* Restore handle count to avoid client id conflicts. */
+ uint32_t u32;
+
+ int vrc = pVMM->pfnSSMR3GetU32(pSSM, &u32);
+ AssertRCReturn(vrc, vrc);
+
+ hgcmObjSetHandleCount(u32);
+
+ /* Get the number of services. */
+ uint32_t cServices;
+
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &cServices);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowFunc(("%d services to be restored:\n", cServices));
+
+ while (cServices--)
+ {
+ /* Get the length of the service name. */
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &u32);
+ AssertRCReturn(vrc, vrc);
+ AssertReturn(u32 <= VBOX_HGCM_SVC_NAME_MAX_BYTES, VERR_SSM_UNEXPECTED_DATA);
+
+ /* Get the service name. */
+ char szServiceName[VBOX_HGCM_SVC_NAME_MAX_BYTES];
+ vrc = pVMM->pfnSSMR3GetStrZ(pSSM, szServiceName, u32);
+ AssertRCReturn(vrc, vrc);
+
+ LogRel(("HGCM: Restoring [%s]\n", szServiceName));
+
+ /* Resolve the service instance. */
+ HGCMService *pSvc;
+ vrc = ResolveService(&pSvc, szServiceName);
+ AssertLogRelMsgReturn(pSvc, ("vrc=%Rrc, %s\n", vrc, szServiceName), VERR_SSM_UNEXPECTED_DATA);
+
+ /* Get the number of clients. */
+ uint32_t cClients;
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &cClients);
+ if (RT_FAILURE(vrc))
+ {
+ pSvc->ReleaseService();
+ AssertFailed();
+ return vrc;
+ }
+
+ while (cClients--)
+ {
+ /* Get the client ID and fRequest (convieniently save via SVC_MSG_SAVESTATE
+ but restored here in time for calling CreateAndConnectClient). */
+ uint32_t u32ClientId;
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &u32ClientId);
+ uint32_t fRequestor = VMMDEV_REQUESTOR_LEGACY;
+ if (RT_SUCCESS(vrc) && uVersion > HGCM_SAVED_STATE_VERSION_V2)
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &fRequestor);
+ AssertLogRelMsgRCReturnStmt(vrc, ("vrc=%Rrc, %s\n", vrc, szServiceName), pSvc->ReleaseService(), vrc);
+
+ /* Connect the client. */
+ vrc = pSvc->CreateAndConnectClient(NULL, u32ClientId, fRequestor, true /*fRestoring*/);
+ AssertLogRelMsgRCReturnStmt(vrc, ("vrc=%Rrc, %s\n", vrc, szServiceName), pSvc->ReleaseService(), vrc);
+
+ /* Call the service, so the operation is executed by the service thread. */
+ vrc = pSvc->loadClientState(u32ClientId, pSSM, pVMM, uVersion);
+ AssertLogRelMsgRCReturnStmt(vrc, ("vrc=%Rrc, %s\n", vrc, szServiceName), pSvc->ReleaseService(), vrc);
+ }
+
+ pSvc->ReleaseService();
+ }
+
+ return VINF_SUCCESS;
+}
+
+/* Create a new client instance and connect it to the service.
+ *
+ * @param pu32ClientIdOut If not NULL, then the method must generate a new handle for the client.
+ * If NULL, use the given 'u32ClientIdIn' handle.
+ * @param u32ClientIdIn The handle for the client, when 'pu32ClientIdOut' is NULL.
+ * @param fRequestor The requestor flags, VMMDEV_REQUESTOR_LEGACY if not available.
+ * @param fRestoring Set if we're restoring a saved state.
+ * @return VBox status code.
+ */
+int HGCMService::CreateAndConnectClient(uint32_t *pu32ClientIdOut, uint32_t u32ClientIdIn, uint32_t fRequestor, bool fRestoring)
+{
+ LogFlowFunc(("pu32ClientIdOut = %p, u32ClientIdIn = %d, fRequestor = %#x, fRestoring = %d\n",
+ pu32ClientIdOut, u32ClientIdIn, fRequestor, fRestoring));
+
+ /*
+ * Categorize the client (compress VMMDEV_REQUESTOR_USR_MASK)
+ * and check the respective client limit.
+ */
+ uint32_t idxClientCategory;
+ if (fRequestor == VMMDEV_REQUESTOR_LEGACY)
+ {
+ idxClientCategory = m_fntable.idxLegacyClientCategory;
+ AssertStmt(idxClientCategory < RT_ELEMENTS(m_acClients), idxClientCategory = HGCM_CLIENT_CATEGORY_KERNEL);
+ }
+ else
+ switch (fRequestor & VMMDEV_REQUESTOR_USR_MASK)
+ {
+ case VMMDEV_REQUESTOR_USR_DRV:
+ case VMMDEV_REQUESTOR_USR_DRV_OTHER:
+ idxClientCategory = HGCM_CLIENT_CATEGORY_KERNEL;
+ break;
+ case VMMDEV_REQUESTOR_USR_ROOT:
+ case VMMDEV_REQUESTOR_USR_SYSTEM:
+ idxClientCategory = HGCM_CLIENT_CATEGORY_ROOT;
+ break;
+ default:
+ idxClientCategory = HGCM_CLIENT_CATEGORY_USER;
+ break;
+ }
+
+ if ( m_acClients[idxClientCategory] < m_fntable.acMaxClients[idxClientCategory]
+ || fRestoring)
+ { }
+ else
+ {
+ LogRel2(("Too many concurrenct clients for HGCM service '%s': %u, max %u; category %u\n",
+ m_pszSvcName, m_cClients, m_fntable.acMaxClients[idxClientCategory], idxClientCategory));
+ STAM_REL_COUNTER_INC(&m_StatTooManyClients);
+ return VERR_HGCM_TOO_MANY_CLIENTS;
+ }
+
+ /* Allocate a client information structure. */
+ HGCMClient *pClient = new (std::nothrow) HGCMClient(fRequestor, idxClientCategory);
+
+ if (!pClient)
+ {
+ Log1WarningFunc(("Could not allocate HGCMClient!!!\n"));
+ return VERR_NO_MEMORY;
+ }
+
+ uint32_t handle;
+
+ if (pu32ClientIdOut != NULL)
+ {
+ handle = hgcmObjGenerateHandle(pClient);
+ }
+ else
+ {
+ handle = hgcmObjAssignHandle(pClient, u32ClientIdIn);
+ }
+
+ LogFlowFunc(("client id = %d\n", handle));
+
+ AssertRelease(handle);
+
+ /* Initialize the HGCM part of the client. */
+ int vrc = pClient->Init(this);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Call the service. */
+ HGCMMsgCore *pCoreMsg;
+
+ vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_CONNECT, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgSvcConnect *pMsg = (HGCMMsgSvcConnect *)pCoreMsg;
+
+ pMsg->u32ClientId = handle;
+ pMsg->fRequestor = fRequestor;
+ pMsg->fRestoring = fRestoring;
+
+ vrc = hgcmMsgSend(pMsg);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Add the client Id to the array. */
+ if (m_cClients == m_cClientsAllocated)
+ {
+ const uint32_t cDelta = 64;
+
+ /* Guards against integer overflow on 32bit arch and also limits size of m_paClientIds array to 4GB*/
+ if (m_cClientsAllocated < UINT32_MAX / sizeof(m_paClientIds[0]) - cDelta)
+ {
+ uint32_t *paClientIdsNew;
+
+ paClientIdsNew = (uint32_t *)RTMemRealloc(m_paClientIds,
+ (m_cClientsAllocated + cDelta) * sizeof(m_paClientIds[0]));
+ Assert(paClientIdsNew);
+
+ if (paClientIdsNew)
+ {
+ m_paClientIds = paClientIdsNew;
+ m_cClientsAllocated += cDelta;
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ m_paClientIds[m_cClients] = handle;
+ m_cClients++;
+ m_acClients[idxClientCategory]++;
+ LogFunc(("idClient=%u m_cClients=%u m_acClients[%u]=%u %s\n",
+ handle, m_cClients, idxClientCategory, m_acClients[idxClientCategory], m_pszSvcName));
+ }
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (pu32ClientIdOut != NULL)
+ {
+ *pu32ClientIdOut = handle;
+ }
+
+ ReferenceService();
+
+ /* The guest may now use this client object. */
+ pClient->makeAccessibleToGuest();
+ }
+ else
+ {
+ hgcmObjDeleteHandle(handle);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Disconnect the client from the service and delete the client handle.
+ *
+ * @param u32ClientId The handle of the client.
+ * @param fFromService Set if called by the service via
+ * svcHlpDisconnectClient().
+ * @param pClient The client disconnecting.
+ * @return VBox status code.
+ */
+int HGCMService::DisconnectClient(uint32_t u32ClientId, bool fFromService, HGCMClient *pClient)
+{
+ AssertPtr(pClient);
+ LogFlowFunc(("client id = %d, fFromService = %d, pClient = %p\n", u32ClientId, fFromService, pClient));
+
+ /*
+ * Destroy the client handle prior to the disconnecting to avoid creating
+ * a race with other messages from the same client. See @bugref{10038}
+ * for further details.
+ */
+ Assert(pClient->idxCategory < HGCM_CLIENT_CATEGORY_MAX);
+ Assert(m_acClients[pClient->idxCategory] > 0);
+
+ bool fReleaseService = false;
+ int vrc = VERR_NOT_FOUND;
+ for (uint32_t i = 0; i < m_cClients; i++)
+ {
+ if (m_paClientIds[i] == u32ClientId)
+ {
+ if (m_acClients[pClient->idxCategory] > 0)
+ m_acClients[pClient->idxCategory]--;
+
+ m_cClients--;
+
+ if (m_cClients > i)
+ memmove(&m_paClientIds[i], &m_paClientIds[i + 1], sizeof(m_paClientIds[0]) * (m_cClients - i));
+
+ /* Delete the client handle. */
+ hgcmObjDeleteHandle(u32ClientId);
+ fReleaseService = true;
+
+ vrc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ /* Some paranoia wrt to not trusting the client ID array. */
+ Assert(vrc == VINF_SUCCESS || fFromService);
+ if (vrc == VERR_NOT_FOUND && !fFromService)
+ {
+ if (m_acClients[pClient->idxCategory] > 0)
+ m_acClients[pClient->idxCategory]--;
+
+ hgcmObjDeleteHandle(u32ClientId);
+ fReleaseService = true;
+ }
+
+ LogFunc(("idClient=%u m_cClients=%u m_acClients[%u]=%u %s (cPendingCalls=%u) rc=%Rrc\n", u32ClientId, m_cClients,
+ pClient->idxCategory, m_acClients[pClient->idxCategory], m_pszSvcName, pClient->cPendingCalls, vrc));
+
+ /*
+ * Call the service.
+ */
+ if (!fFromService)
+ {
+ /* Call the service. */
+ HGCMMsgCore *pCoreMsg;
+
+ vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_DISCONNECT, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)pCoreMsg;
+
+ pMsg->u32ClientId = u32ClientId;
+ pMsg->pClient = pClient;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+ else
+ {
+ LogRel(("(%d, %d) [%s] hgcmMsgAlloc(%p, SVC_MSG_DISCONNECT) failed %Rrc\n",
+ u32ClientId, fFromService, RT_VALID_PTR(m_pszSvcName)? m_pszSvcName: "", m_pThread, vrc));
+ }
+ }
+
+
+ /*
+ * Release the pClient->pService reference.
+ */
+ if (fReleaseService)
+ ReleaseService();
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int HGCMService::RegisterExtension(HGCMSVCEXTHANDLE handle,
+ PFNHGCMSVCEXT pfnExtension,
+ void *pvExtension)
+{
+ LogFlowFunc(("%s\n", handle->pszServiceName));
+
+ /* Forward the message to the service thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_REGEXT, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgSvcRegisterExtension *pMsg = (HGCMMsgSvcRegisterExtension *)pCoreMsg;
+
+ pMsg->handle = handle;
+ pMsg->pfnExtension = pfnExtension;
+ pMsg->pvExtension = pvExtension;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+void HGCMService::UnregisterExtension(HGCMSVCEXTHANDLE handle)
+{
+ /* Forward the message to the service thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_UNREGEXT, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgSvcUnregisterExtension *pMsg = (HGCMMsgSvcUnregisterExtension *)pCoreMsg;
+
+ pMsg->handle = handle;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+}
+
+/** @callback_method_impl{FNHGCMMSGCALLBACK} */
+static DECLCALLBACK(int) hgcmMsgCallCompletionCallback(int32_t result, HGCMMsgCore *pMsgCore)
+{
+ /*
+ * Do common message completion then decrement the call counter
+ * for the client if necessary.
+ */
+ int vrc = hgcmMsgCompletionCallback(result, pMsgCore);
+
+ HGCMMsgCall *pMsg = (HGCMMsgCall *)pMsgCore;
+ if (pMsg->pcCounter)
+ {
+ uint32_t cCalls = ASMAtomicDecU32(pMsg->pcCounter);
+ AssertStmt(cCalls < UINT32_MAX / 2, ASMAtomicWriteU32(pMsg->pcCounter, 0));
+ pMsg->pcCounter = NULL;
+ Log3Func(("pMsg=%p cPendingCalls=%u / %u (fun %u, %u parms)\n",
+ pMsg, cCalls, pMsg->u32ClientId, pMsg->u32Function, pMsg->cParms));
+ }
+
+ return vrc;
+}
+
+/** Perform a guest call to the service.
+ *
+ * @param pHGCMPort The port to be used for completion confirmation.
+ * @param pCmd The VBox HGCM context.
+ * @param u32ClientId The client handle to be disconnected and deleted.
+ * @param pClient The client data.
+ * @param u32Function The function number.
+ * @param cParms Number of parameters.
+ * @param paParms Pointer to array of parameters.
+ * @param tsArrival The STAM_GET_TS() value when the request arrived.
+ * @return VBox rc.
+ * @retval VINF_HGCM_ASYNC_EXECUTE on success.
+ */
+int HGCMService::GuestCall(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientId, HGCMClient *pClient,
+ uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival)
+{
+ LogFlow(("MAIN::HGCMService::GuestCall\n"));
+
+ int vrc;
+ HGCMMsgCall *pMsg = new(std::nothrow) HGCMMsgCall(m_pThread);
+ if (pMsg)
+ {
+ pMsg->Reference(); /** @todo starts out with zero references. */
+
+ uint32_t cCalls = ASMAtomicIncU32(&pClient->cPendingCalls);
+ Assert(pClient->idxCategory < RT_ELEMENTS(m_fntable.acMaxCallsPerClient));
+ if (cCalls < m_fntable.acMaxCallsPerClient[pClient->idxCategory])
+ {
+ pMsg->pcCounter = &pClient->cPendingCalls;
+ Log3(("MAIN::HGCMService::GuestCall: pMsg=%p cPendingCalls=%u / %u / %s (fun %u, %u parms)\n",
+ pMsg, cCalls, u32ClientId, m_pszSvcName, u32Function, cParms));
+
+ pMsg->pCmd = pCmd;
+ pMsg->pHGCMPort = pHGCMPort;
+ pMsg->u32ClientId = u32ClientId;
+ pMsg->u32Function = u32Function;
+ pMsg->cParms = cParms;
+ pMsg->paParms = paParms;
+ pMsg->tsArrival = tsArrival;
+
+ vrc = hgcmMsgPost(pMsg, hgcmMsgCallCompletionCallback);
+
+ if (RT_SUCCESS(vrc))
+ { /* Reference donated on success. */ }
+ else
+ {
+ ASMAtomicDecU32(&pClient->cPendingCalls);
+ pMsg->pcCounter = NULL;
+ Log(("MAIN::HGCMService::GuestCall: hgcmMsgPost failed: %Rrc\n", vrc));
+ pMsg->Dereference();
+ }
+ }
+ else
+ {
+ ASMAtomicDecU32(&pClient->cPendingCalls);
+ LogRel2(("HGCM: Too many calls to '%s' from client %u: %u, max %u; category %u\n", m_pszSvcName, u32ClientId,
+ cCalls, m_fntable.acMaxCallsPerClient[pClient->idxCategory], pClient->idxCategory));
+ pMsg->Dereference();
+ STAM_REL_COUNTER_INC(&m_StatTooManyCalls);
+ vrc = VERR_HGCM_TOO_MANY_CLIENT_CALLS;
+ }
+ }
+ else
+ {
+ Log(("MAIN::HGCMService::GuestCall: Message allocation failed\n"));
+ vrc = VERR_NO_MEMORY;
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** Guest cancelled a request (call, connection attempt, disconnect attempt).
+ *
+ * @param pHGCMPort The port to be used for completion confirmation
+ * @param pCmd The VBox HGCM context.
+ * @param idClient The client handle to be disconnected and deleted.
+ * @return VBox rc.
+ */
+void HGCMService::GuestCancelled(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t idClient)
+{
+ LogFlow(("MAIN::HGCMService::GuestCancelled\n"));
+
+ if (m_fntable.pfnCancelled)
+ {
+ HGCMMsgCancelled *pMsg = new (std::nothrow) HGCMMsgCancelled(m_pThread);
+ if (pMsg)
+ {
+ pMsg->Reference(); /** @todo starts out with zero references. */
+
+ pMsg->pCmd = pCmd;
+ pMsg->pHGCMPort = pHGCMPort;
+ pMsg->idClient = idClient;
+
+ hgcmMsgPost(pMsg, NULL);
+ }
+ else
+ Log(("MAIN::HGCMService::GuestCancelled: Message allocation failed\n"));
+ }
+}
+
+/** Perform a host call the service.
+ *
+ * @param u32Function The function number.
+ * @param cParms Number of parameters.
+ * @param paParms Pointer to array of parameters.
+ * @return VBox rc.
+ */
+int HGCMService::HostCall(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM *paParms)
+{
+ LogFlowFunc(("%s u32Function = %d, cParms = %d, paParms = %p\n",
+ m_pszSvcName, u32Function, cParms, paParms));
+
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_HOSTCALL, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgHostCallSvc *pMsg = (HGCMMsgHostCallSvc *)pCoreMsg;
+
+ pMsg->u32Function = u32Function;
+ pMsg->cParms = cParms;
+ pMsg->paParms = paParms;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** Posts a broadcast notification event to all interested services.
+ *
+ * @param enmEvent The notification event.
+ */
+/*static*/ void HGCMService::BroadcastNotify(HGCMNOTIFYEVENT enmEvent)
+{
+ for (HGCMService *pService = sm_pSvcListHead; pService != NULL; pService = pService->m_pSvcNext)
+ {
+ pService->Notify(enmEvent);
+ }
+}
+
+/** Posts a broadcast notification event to the service.
+ *
+ * @param enmEvent The notification event.
+ */
+void HGCMService::Notify(HGCMNOTIFYEVENT enmEvent)
+{
+ LogFlowFunc(("%s enmEvent=%d pfnNotify=%p\n", m_pszSvcName, enmEvent, m_fntable.pfnNotify));
+ if (m_fntable.pfnNotify)
+ {
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_NOTIFY, hgcmMessageAllocSvc);
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgNotify *pMsg = (HGCMMsgNotify *)pCoreMsg;
+ pMsg->enmEvent = enmEvent;
+
+ vrc = hgcmMsgPost(pMsg, NULL);
+ AssertRC(vrc);
+ }
+ }
+}
+
+/*
+ * Main HGCM thread that manages services.
+ */
+
+/* Messages processed by the main HGCM thread. */
+#define HGCM_MSG_CONNECT (10) /**< Connect a client to a service. */
+#define HGCM_MSG_DISCONNECT (11) /**< Disconnect the specified client id. */
+#define HGCM_MSG_LOAD (12) /**< Load the service. */
+#define HGCM_MSG_HOSTCALL (13) /**< Call the service. */
+#define HGCM_MSG_LOADSTATE (14) /**< Load saved state for the specified service. */
+#define HGCM_MSG_SAVESTATE (15) /**< Save state for the specified service. */
+#define HGCM_MSG_RESET (16) /**< Disconnect all clients from the specified service. */
+#define HGCM_MSG_QUIT (17) /**< Unload all services and terminate the thread. */
+#define HGCM_MSG_REGEXT (18) /**< Register a service extension. */
+#define HGCM_MSG_UNREGEXT (19) /**< Unregister a service extension. */
+#define HGCM_MSG_BRD_NOTIFY (20) /**< Broadcast notification event (VM state change). */
+
+class HGCMMsgMainConnect: public HGCMMsgHeader
+{
+ public:
+ /* Service name. */
+ const char *pszServiceName;
+ /* Where to store the client handle. */
+ uint32_t *pu32ClientId;
+};
+
+class HGCMMsgMainDisconnect: public HGCMMsgHeader
+{
+ public:
+ /* Handle of the client to be disconnected. */
+ uint32_t u32ClientId;
+};
+
+class HGCMMsgMainLoad: public HGCMMsgCore
+{
+ public:
+ /* Name of the library to be loaded. */
+ const char *pszServiceLibrary;
+ /* Name to be assigned to the service. */
+ const char *pszServiceName;
+ /** The user mode VM handle (for statistics and such). */
+ PUVM pUVM;
+ /** The VMM vtable (for statistics and such). */
+ PCVMMR3VTABLE pVMM;
+ /** The HGCM port on the VMMDev device (for session ID and such). */
+ PPDMIHGCMPORT pHgcmPort;
+};
+
+class HGCMMsgMainHostCall: public HGCMMsgCore
+{
+ public:
+ /* Which service to call. */
+ const char *pszServiceName;
+ /* Function number. */
+ uint32_t u32Function;
+ /* Number of the function parameters. */
+ uint32_t cParms;
+ /* Pointer to array of the function parameters. */
+ VBOXHGCMSVCPARM *paParms;
+};
+
+class HGCMMsgMainLoadSaveState: public HGCMMsgCore
+{
+ public:
+ /** Saved state handle. */
+ PSSMHANDLE pSSM;
+ /** The VMM vtable. */
+ PCVMMR3VTABLE pVMM;
+ /** The HGCM saved state version being loaded (ignore for save). */
+ uint32_t uVersion;
+};
+
+class HGCMMsgMainReset: public HGCMMsgCore
+{
+ public:
+ /** Set if this is actually a shutdown and not a VM reset. */
+ bool fForShutdown;
+};
+
+class HGCMMsgMainQuit: public HGCMMsgCore
+{
+ public:
+ /** Whether UVM has gone invalid already or not. */
+ bool fUvmIsInvalid;
+};
+
+class HGCMMsgMainRegisterExtension: public HGCMMsgCore
+{
+ public:
+ /** Returned handle to be used in HGCMMsgMainUnregisterExtension. */
+ HGCMSVCEXTHANDLE *pHandle;
+ /** Name of the service. */
+ const char *pszServiceName;
+ /** The extension entry point. */
+ PFNHGCMSVCEXT pfnExtension;
+ /** The extension pointer. */
+ void *pvExtension;
+};
+
+class HGCMMsgMainUnregisterExtension: public HGCMMsgCore
+{
+ public:
+ /* Handle of the registered extension. */
+ HGCMSVCEXTHANDLE handle;
+};
+
+class HGCMMsgMainBroadcastNotify: public HGCMMsgCore
+{
+ public:
+ /** The notification event. */
+ HGCMNOTIFYEVENT enmEvent;
+};
+
+
+static HGCMMsgCore *hgcmMainMessageAlloc (uint32_t u32MsgId)
+{
+ switch (u32MsgId)
+ {
+ case HGCM_MSG_CONNECT: return new HGCMMsgMainConnect();
+ case HGCM_MSG_DISCONNECT: return new HGCMMsgMainDisconnect();
+ case HGCM_MSG_LOAD: return new HGCMMsgMainLoad();
+ case HGCM_MSG_HOSTCALL: return new HGCMMsgMainHostCall();
+ case HGCM_MSG_LOADSTATE:
+ case HGCM_MSG_SAVESTATE: return new HGCMMsgMainLoadSaveState();
+ case HGCM_MSG_RESET: return new HGCMMsgMainReset();
+ case HGCM_MSG_QUIT: return new HGCMMsgMainQuit();
+ case HGCM_MSG_REGEXT: return new HGCMMsgMainRegisterExtension();
+ case HGCM_MSG_UNREGEXT: return new HGCMMsgMainUnregisterExtension();
+ case HGCM_MSG_BRD_NOTIFY: return new HGCMMsgMainBroadcastNotify();
+
+ default:
+ AssertReleaseMsgFailed(("Msg id = %08X\n", u32MsgId));
+ }
+
+ return NULL;
+}
+
+
+/* The main HGCM thread handler. */
+static DECLCALLBACK(void) hgcmThread(HGCMThread *pThread, void *pvUser)
+{
+ LogFlowFunc(("pThread = %p, pvUser = %p\n", pThread, pvUser));
+
+ NOREF(pvUser);
+
+ bool fQuit = false;
+
+ while (!fQuit)
+ {
+ HGCMMsgCore *pMsgCore;
+ int vrc = hgcmMsgGet(pThread, &pMsgCore);
+
+ if (RT_FAILURE(vrc))
+ {
+ /* The error means some serious unrecoverable problem in the hgcmMsg/hgcmThread layer. */
+ AssertMsgFailed(("%Rrc\n", vrc));
+ break;
+ }
+
+ uint32_t u32MsgId = pMsgCore->MsgId();
+
+ switch (u32MsgId)
+ {
+ case HGCM_MSG_CONNECT:
+ {
+ HGCMMsgMainConnect *pMsg = (HGCMMsgMainConnect *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_CONNECT pszServiceName %s, pu32ClientId %p\n",
+ pMsg->pszServiceName, pMsg->pu32ClientId));
+
+ /* Resolve the service name to the pointer to service instance.
+ */
+ HGCMService *pService;
+ vrc = HGCMService::ResolveService(&pService, pMsg->pszServiceName);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Call the service instance method. */
+ vrc = pService->CreateAndConnectClient(pMsg->pu32ClientId,
+ 0,
+ pMsg->pHGCMPort->pfnGetRequestor(pMsg->pHGCMPort, pMsg->pCmd),
+ pMsg->pHGCMPort->pfnIsCmdRestored(pMsg->pHGCMPort, pMsg->pCmd));
+
+ /* Release the service after resolve. */
+ pService->ReleaseService();
+ }
+ } break;
+
+ case HGCM_MSG_DISCONNECT:
+ {
+ HGCMMsgMainDisconnect *pMsg = (HGCMMsgMainDisconnect *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_DISCONNECT u32ClientId = %d\n",
+ pMsg->u32ClientId));
+
+ HGCMClient *pClient = HGCMClient::ReferenceByHandle(pMsg->u32ClientId);
+
+ if (!pClient)
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ break;
+ }
+
+ /* The service the client belongs to. */
+ HGCMService *pService = pClient->pService;
+
+ /* Call the service instance to disconnect the client. */
+ vrc = pService->DisconnectClient(pMsg->u32ClientId, false, pClient);
+
+ hgcmObjDereference(pClient);
+ } break;
+
+ case HGCM_MSG_LOAD:
+ {
+ HGCMMsgMainLoad *pMsg = (HGCMMsgMainLoad *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_LOAD pszServiceName = %s, pMsg->pszServiceLibrary = %s, pMsg->pUVM = %p\n",
+ pMsg->pszServiceName, pMsg->pszServiceLibrary, pMsg->pUVM));
+
+ vrc = HGCMService::LoadService(pMsg->pszServiceLibrary, pMsg->pszServiceName,
+ pMsg->pUVM, pMsg->pVMM, pMsg->pHgcmPort);
+ } break;
+
+ case HGCM_MSG_HOSTCALL:
+ {
+ HGCMMsgMainHostCall *pMsg = (HGCMMsgMainHostCall *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_HOSTCALL pszServiceName %s, u32Function %d, cParms %d, paParms %p\n",
+ pMsg->pszServiceName, pMsg->u32Function, pMsg->cParms, pMsg->paParms));
+
+ /* Resolve the service name to the pointer to service instance. */
+ HGCMService *pService;
+ vrc = HGCMService::ResolveService(&pService, pMsg->pszServiceName);
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pService->HostCall(pMsg->u32Function, pMsg->cParms, pMsg->paParms);
+
+ pService->ReleaseService();
+ }
+ } break;
+
+ case HGCM_MSG_BRD_NOTIFY:
+ {
+ HGCMMsgMainBroadcastNotify *pMsg = (HGCMMsgMainBroadcastNotify *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_BRD_NOTIFY enmEvent=%d\n", pMsg->enmEvent));
+
+ HGCMService::BroadcastNotify(pMsg->enmEvent);
+ } break;
+
+ case HGCM_MSG_RESET:
+ {
+ LogFlowFunc(("HGCM_MSG_RESET\n"));
+
+ HGCMService::Reset();
+
+ HGCMMsgMainReset *pMsg = (HGCMMsgMainReset *)pMsgCore;
+ if (!pMsg->fForShutdown)
+ HGCMService::BroadcastNotify(HGCMNOTIFYEVENT_RESET);
+ } break;
+
+ case HGCM_MSG_LOADSTATE:
+ {
+ HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_LOADSTATE\n"));
+
+ vrc = HGCMService::LoadState(pMsg->pSSM, pMsg->pVMM, pMsg->uVersion);
+ } break;
+
+ case HGCM_MSG_SAVESTATE:
+ {
+ HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_SAVESTATE\n"));
+
+ vrc = HGCMService::SaveState(pMsg->pSSM, pMsg->pVMM);
+ } break;
+
+ case HGCM_MSG_QUIT:
+ {
+ HGCMMsgMainQuit *pMsg = (HGCMMsgMainQuit *)pMsgCore;
+ LogFlowFunc(("HGCM_MSG_QUIT\n"));
+
+ HGCMService::UnloadAll(pMsg->fUvmIsInvalid);
+
+ fQuit = true;
+ } break;
+
+ case HGCM_MSG_REGEXT:
+ {
+ HGCMMsgMainRegisterExtension *pMsg = (HGCMMsgMainRegisterExtension *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_REGEXT\n"));
+
+ /* Allocate the handle data. */
+ HGCMSVCEXTHANDLE handle = (HGCMSVCEXTHANDLE)RTMemAllocZ(sizeof(struct _HGCMSVCEXTHANDLEDATA)
+ + strlen(pMsg->pszServiceName)
+ + sizeof(char));
+
+ if (handle == NULL)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ handle->pszServiceName = (char *)((uint8_t *)handle + sizeof(struct _HGCMSVCEXTHANDLEDATA));
+ strcpy(handle->pszServiceName, pMsg->pszServiceName);
+
+ HGCMService *pService;
+ vrc = HGCMService::ResolveService(&pService, handle->pszServiceName);
+
+ if (RT_SUCCESS(vrc))
+ {
+ pService->RegisterExtension(handle, pMsg->pfnExtension, pMsg->pvExtension);
+
+ pService->ReleaseService();
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ RTMemFree(handle);
+ }
+ else
+ {
+ *pMsg->pHandle = handle;
+ }
+ }
+ } break;
+
+ case HGCM_MSG_UNREGEXT:
+ {
+ HGCMMsgMainUnregisterExtension *pMsg = (HGCMMsgMainUnregisterExtension *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_UNREGEXT\n"));
+
+ HGCMService *pService;
+ vrc = HGCMService::ResolveService(&pService, pMsg->handle->pszServiceName);
+
+ if (RT_SUCCESS(vrc))
+ {
+ pService->UnregisterExtension(pMsg->handle);
+
+ pService->ReleaseService();
+ }
+
+ RTMemFree(pMsg->handle);
+ } break;
+
+ default:
+ {
+ AssertMsgFailed(("hgcmThread: Unsupported message number %08X!!!\n", u32MsgId));
+ vrc = VERR_NOT_SUPPORTED;
+ } break;
+ }
+
+ /* Complete the message processing. */
+ hgcmMsgComplete(pMsgCore, vrc);
+
+ LogFlowFunc(("message processed %Rrc\n", vrc));
+ }
+}
+
+
+/*
+ * The HGCM API.
+ */
+
+/** The main hgcm thread. */
+static HGCMThread *g_pHgcmThread = 0;
+
+/*
+ * Public HGCM functions.
+ *
+ * hgcmGuest* - called as a result of the guest HGCM requests.
+ * hgcmHost* - called by the host.
+ */
+
+/* Load a HGCM service from the specified library.
+ * Assign the specified name to the service.
+ *
+ * @param pszServiceLibrary The library to be loaded.
+ * @param pszServiceName The name to be assigned to the service.
+ * @param pUVM The user mode VM handle (for statistics and such).
+ * @param pVMM The VMM vtable (for statistics and such).
+ * @param pHgcmPort The HGCM port on the VMMDev device (for session ID and such).
+ * @return VBox rc.
+ */
+int HGCMHostLoad(const char *pszServiceLibrary,
+ const char *pszServiceName,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ PPDMIHGCMPORT pHgcmPort)
+{
+ LogFlowFunc(("lib = %s, name = %s\n", pszServiceLibrary, pszServiceName));
+
+ if (!pszServiceLibrary || !pszServiceName)
+ return VERR_INVALID_PARAMETER;
+
+ /* Forward the request to the main hgcm thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_LOAD, hgcmMainMessageAlloc);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Initialize the message. Since the message is synchronous, use the supplied pointers. */
+ HGCMMsgMainLoad *pMsg = (HGCMMsgMainLoad *)pCoreMsg;
+
+ pMsg->pszServiceLibrary = pszServiceLibrary;
+ pMsg->pszServiceName = pszServiceName;
+ pMsg->pUVM = pUVM;
+ pMsg->pVMM = pVMM;
+ pMsg->pHgcmPort = pHgcmPort;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/* Register a HGCM service extension.
+ *
+ * @param pHandle Returned handle for the registered extension.
+ * @param pszServiceName The name of the service.
+ * @param pfnExtension The extension entry point (callback).
+ * @param pvExtension The extension pointer.
+ * @return VBox rc.
+ */
+int HGCMHostRegisterServiceExtension(HGCMSVCEXTHANDLE *pHandle,
+ const char *pszServiceName,
+ PFNHGCMSVCEXT pfnExtension,
+ void *pvExtension)
+{
+ LogFlowFunc(("pHandle = %p, name = %s, pfn = %p, rv = %p\n", pHandle, pszServiceName, pfnExtension, pvExtension));
+
+ if (!pHandle || !pszServiceName || !pfnExtension)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Forward the request to the main hgcm thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_REGEXT, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Initialize the message. Since the message is synchronous, use the supplied pointers. */
+ HGCMMsgMainRegisterExtension *pMsg = (HGCMMsgMainRegisterExtension *)pCoreMsg;
+
+ pMsg->pHandle = pHandle;
+ pMsg->pszServiceName = pszServiceName;
+ pMsg->pfnExtension = pfnExtension;
+ pMsg->pvExtension = pvExtension;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("*pHandle = %p, vrc = %Rrc\n", *pHandle, vrc));
+ return vrc;
+}
+
+void HGCMHostUnregisterServiceExtension(HGCMSVCEXTHANDLE handle)
+{
+ LogFlowFunc(("handle = %p\n", handle));
+
+ /* Forward the request to the main hgcm thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_UNREGEXT, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Initialize the message. */
+ HGCMMsgMainUnregisterExtension *pMsg = (HGCMMsgMainUnregisterExtension *)pCoreMsg;
+
+ pMsg->handle = handle;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return;
+}
+
+/* Find a service and inform it about a client connection, create a client handle.
+ *
+ * @param pHGCMPort The port to be used for completion confirmation.
+ * @param pCmd The VBox HGCM context.
+ * @param pszServiceName The name of the service to be connected to.
+ * @param pu32ClientId Where the store the created client handle.
+ * @return VBox rc.
+ */
+int HGCMGuestConnect(PPDMIHGCMPORT pHGCMPort,
+ PVBOXHGCMCMD pCmd,
+ const char *pszServiceName,
+ uint32_t *pu32ClientId)
+{
+ LogFlowFunc(("pHGCMPort = %p, pCmd = %p, name = %s, pu32ClientId = %p\n",
+ pHGCMPort, pCmd, pszServiceName, pu32ClientId));
+
+ if (pHGCMPort == NULL || pCmd == NULL || pszServiceName == NULL || pu32ClientId == NULL)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Forward the request to the main hgcm thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_CONNECT, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Initialize the message. Since 'pszServiceName' and 'pu32ClientId'
+ * will not be deallocated by the caller until the message is completed,
+ * use the supplied pointers.
+ */
+ HGCMMsgMainConnect *pMsg = (HGCMMsgMainConnect *)pCoreMsg;
+
+ pMsg->pHGCMPort = pHGCMPort;
+ pMsg->pCmd = pCmd;
+ pMsg->pszServiceName = pszServiceName;
+ pMsg->pu32ClientId = pu32ClientId;
+
+ vrc = hgcmMsgPost(pMsg, hgcmMsgCompletionCallback);
+ }
+
+ LogFlowFunc(("rc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/* Tell a service that the client is disconnecting, destroy the client handle.
+ *
+ * @param pHGCMPort The port to be used for completion confirmation.
+ * @param pCmd The VBox HGCM context.
+ * @param u32ClientId The client handle to be disconnected and deleted.
+ * @return VBox rc.
+ */
+int HGCMGuestDisconnect(PPDMIHGCMPORT pHGCMPort,
+ PVBOXHGCMCMD pCmd,
+ uint32_t u32ClientId)
+{
+ LogFlowFunc(("pHGCMPort = %p, pCmd = %p, u32ClientId = %d\n",
+ pHGCMPort, pCmd, u32ClientId));
+
+ if (!pHGCMPort || !pCmd || !u32ClientId)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Forward the request to the main hgcm thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_DISCONNECT, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Initialize the message. */
+ HGCMMsgMainDisconnect *pMsg = (HGCMMsgMainDisconnect *)pCoreMsg;
+
+ pMsg->pCmd = pCmd;
+ pMsg->pHGCMPort = pHGCMPort;
+ pMsg->u32ClientId = u32ClientId;
+
+ vrc = hgcmMsgPost(pMsg, hgcmMsgCompletionCallback);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** Helper to send either HGCM_MSG_SAVESTATE or HGCM_MSG_LOADSTATE messages to the main HGCM thread.
+ *
+ * @param pSSM The SSM handle.
+ * @param pVMM The VMM vtable.
+ * @param idMsg The message to be sent: HGCM_MSG_SAVESTATE or HGCM_MSG_LOADSTATE.
+ * @param uVersion The state version being loaded.
+ * @return VBox rc.
+ */
+static int hgcmHostLoadSaveState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t idMsg, uint32_t uVersion)
+{
+ LogFlowFunc(("pSSM = %p, pVMM = %p, idMsg = %d, uVersion = %#x\n", pSSM, pVMM, idMsg, uVersion));
+
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, idMsg, hgcmMainMessageAlloc);
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pCoreMsg;
+ AssertRelease(pMsg);
+
+ pMsg->pSSM = pSSM;
+ pMsg->pVMM = pVMM;
+ pMsg->uVersion = uVersion;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** Save the state of services.
+ *
+ * @param pSSM The SSM handle.
+ * @param pVMM The VMM vtable.
+ * @return VBox status code.
+ */
+int HGCMHostSaveState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM)
+{
+ return hgcmHostLoadSaveState(pSSM, pVMM, HGCM_MSG_SAVESTATE, HGCM_SAVED_STATE_VERSION);
+}
+
+/** Load the state of services.
+ *
+ * @param pSSM The SSM handle.
+ * @param pVMM The VMM vtable.
+ * @param uVersion The state version being loaded.
+ * @return VBox status code.
+ */
+int HGCMHostLoadState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion)
+{
+ return hgcmHostLoadSaveState(pSSM, pVMM, HGCM_MSG_LOADSTATE, uVersion);
+}
+
+/** The guest calls the service.
+ *
+ * @param pHGCMPort The port to be used for completion confirmation.
+ * @param pCmd The VBox HGCM context.
+ * @param u32ClientId The client handle.
+ * @param u32Function The function number.
+ * @param cParms Number of parameters.
+ * @param paParms Pointer to array of parameters.
+ * @param tsArrival The STAM_GET_TS() value when the request arrived.
+ * @return VBox rc.
+ */
+int HGCMGuestCall(PPDMIHGCMPORT pHGCMPort,
+ PVBOXHGCMCMD pCmd,
+ uint32_t u32ClientId,
+ uint32_t u32Function,
+ uint32_t cParms,
+ VBOXHGCMSVCPARM *paParms,
+ uint64_t tsArrival)
+{
+ LogFlowFunc(("pHGCMPort = %p, pCmd = %p, u32ClientId = %d, u32Function = %d, cParms = %d, paParms = %p\n",
+ pHGCMPort, pCmd, u32ClientId, u32Function, cParms, paParms));
+
+ if (!pHGCMPort || !pCmd || u32ClientId == 0)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ int vrc = VERR_HGCM_INVALID_CLIENT_ID;
+
+ /* Resolve the client handle to the client instance pointer. */
+ HGCMClient *pClient = HGCMClient::ReferenceByHandleForGuest(u32ClientId);
+
+ if (pClient)
+ {
+ AssertRelease(pClient->pService);
+
+ /* Forward the message to the service thread. */
+ vrc = pClient->pService->GuestCall(pHGCMPort, pCmd, u32ClientId, pClient, u32Function, cParms, paParms, tsArrival);
+
+ hgcmObjDereference(pClient);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** The guest cancelled a request (call, connect, disconnect)
+ *
+ * @param pHGCMPort The port to be used for completion confirmation.
+ * @param pCmd The VBox HGCM context.
+ * @param idClient The client handle.
+ */
+void HGCMGuestCancelled(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t idClient)
+{
+ LogFlowFunc(("pHGCMPort = %p, pCmd = %p, idClient = %d\n", pHGCMPort, pCmd, idClient));
+ AssertReturnVoid(pHGCMPort);
+ AssertReturnVoid(pCmd);
+ AssertReturnVoid(idClient != 0);
+
+ /* Resolve the client handle to the client instance pointer. */
+ HGCMClient *pClient = HGCMClient::ReferenceByHandleForGuest(idClient);
+
+ if (pClient)
+ {
+ AssertRelease(pClient->pService);
+
+ /* Forward the message to the service thread. */
+ pClient->pService->GuestCancelled(pHGCMPort, pCmd, idClient);
+
+ hgcmObjDereference(pClient);
+ }
+
+ LogFlowFunc(("returns\n"));
+}
+
+/** The host calls the service.
+ *
+ * @param pszServiceName The service name to be called.
+ * @param u32Function The function number.
+ * @param cParms Number of parameters.
+ * @param paParms Pointer to array of parameters.
+ * @return VBox rc.
+ */
+int HGCMHostCall(const char *pszServiceName,
+ uint32_t u32Function,
+ uint32_t cParms,
+ VBOXHGCMSVCPARM *paParms)
+{
+ LogFlowFunc(("name = %s, u32Function = %d, cParms = %d, paParms = %p\n",
+ pszServiceName, u32Function, cParms, paParms));
+
+ if (!pszServiceName)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Host calls go to main HGCM thread that resolves the service name to the
+ * service instance pointer and then, using the service pointer, forwards
+ * the message to the service thread.
+ * So it is slow but host calls are intended mostly for configuration and
+ * other non-time-critical functions.
+ */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_HOSTCALL, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgMainHostCall *pMsg = (HGCMMsgMainHostCall *)pCoreMsg;
+
+ pMsg->pszServiceName = (char *)pszServiceName;
+ pMsg->u32Function = u32Function;
+ pMsg->cParms = cParms;
+ pMsg->paParms = paParms;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** Posts a notification event to all services.
+ *
+ * @param enmEvent The notification event.
+ * @return VBox rc.
+ */
+int HGCMBroadcastEvent(HGCMNOTIFYEVENT enmEvent)
+{
+ LogFlowFunc(("enmEvent=%d\n", enmEvent));
+
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_BRD_NOTIFY, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgMainBroadcastNotify *pMsg = (HGCMMsgMainBroadcastNotify *)pCoreMsg;
+
+ pMsg->enmEvent = enmEvent;
+
+ vrc = hgcmMsgPost(pMsg, NULL);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+
+int HGCMHostReset(bool fForShutdown)
+{
+ LogFlowFunc(("\n"));
+
+ /* Disconnect all clients.
+ */
+
+ HGCMMsgCore *pMsgCore;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pMsgCore, HGCM_MSG_RESET, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgMainReset *pMsg = (HGCMMsgMainReset *)pMsgCore;
+
+ pMsg->fForShutdown = fForShutdown;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int HGCMHostInit(void)
+{
+ LogFlowFunc(("\n"));
+
+ int vrc = hgcmThreadInit();
+
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Start main HGCM thread.
+ */
+
+ vrc = hgcmThreadCreate(&g_pHgcmThread, "MainHGCMthread", hgcmThread, NULL /*pvUser*/,
+ NULL /*pszStatsSubDir*/, NULL /*pUVM*/, NULL /*pVMM*/);
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Failed to start HGCM thread. HGCM services will be unavailable!!! vrc = %Rrc\n", vrc));
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int HGCMHostShutdown(bool fUvmIsInvalid /*= false*/)
+{
+ LogFlowFunc(("\n"));
+
+ /*
+ * Do HGCMReset and then unload all services.
+ */
+
+ int vrc = HGCMHostReset(true /*fForShutdown*/);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Send the quit message to the main hgcmThread. */
+ HGCMMsgCore *pMsgCore;
+ vrc = hgcmMsgAlloc(g_pHgcmThread, &pMsgCore, HGCM_MSG_QUIT, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgMainQuit *pMsg = (HGCMMsgMainQuit *)pMsgCore;
+ pMsg->fUvmIsInvalid = fUvmIsInvalid;
+
+ vrc = hgcmMsgSend(pMsg);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Wait for the thread termination. */
+ hgcmThreadWait(g_pHgcmThread);
+ g_pHgcmThread = NULL;
+
+ hgcmThreadUninit();
+ }
+ }
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
diff --git a/src/VBox/Main/src-client/HGCMObjects.cpp b/src/VBox/Main/src-client/HGCMObjects.cpp
new file mode 100644
index 00000000..0b668618
--- /dev/null
+++ b/src/VBox/Main/src-client/HGCMObjects.cpp
@@ -0,0 +1,286 @@
+/* $Id: HGCMObjects.cpp $ */
+/** @file
+ * HGCMObjects - Host-Guest Communication Manager objects
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_HGCM
+#include "LoggingNew.h"
+
+#include "HGCMObjects.h"
+
+#include <iprt/string.h>
+#include <iprt/errcore.h>
+
+
+static RTCRITSECT g_critsect;
+
+/* There are internal handles, which are not saved,
+ * and client handles, which are saved.
+ * They use different range of values:
+ * 1..7FFFFFFF for clients,
+ * 0x80000001..0xFFFFFFFF for other handles.
+ */
+static uint32_t volatile g_u32InternalHandleCount;
+static uint32_t volatile g_u32ClientHandleCount;
+
+static PAVLU32NODECORE g_pTree;
+
+
+DECLINLINE(int) hgcmObjEnter(void)
+{
+ return RTCritSectEnter(&g_critsect);
+}
+
+DECLINLINE(void) hgcmObjLeave(void)
+{
+ RTCritSectLeave(&g_critsect);
+}
+
+int hgcmObjInit(void)
+{
+ LogFlow(("MAIN::hgcmObjInit\n"));
+
+ g_u32InternalHandleCount = 0x80000000;
+ g_u32ClientHandleCount = 0;
+ g_pTree = NULL;
+
+ int vrc = RTCritSectInit(&g_critsect);
+
+ LogFlow(("MAIN::hgcmObjInit: vrc = %Rrc\n", vrc));
+
+ return vrc;
+}
+
+void hgcmObjUninit(void)
+{
+ if (RTCritSectIsInitialized(&g_critsect))
+ RTCritSectDelete(&g_critsect);
+}
+
+uint32_t hgcmObjMake(HGCMObject *pObject, uint32_t u32HandleIn)
+{
+ int handle = 0;
+
+ LogFlow(("MAIN::hgcmObjGenerateHandle: pObject %p\n", pObject));
+
+ int vrc = hgcmObjEnter();
+
+ if (RT_SUCCESS(vrc))
+ {
+ ObjectAVLCore *pCore = &pObject->m_core;
+
+ /* Generate a new handle value. */
+
+ uint32_t volatile *pu32HandleCountSource = pObject->Type () == HGCMOBJ_CLIENT?
+ &g_u32ClientHandleCount:
+ &g_u32InternalHandleCount;
+
+ uint32_t u32Start = *pu32HandleCountSource;
+
+ for (;;)
+ {
+ uint32_t Key;
+
+ if (u32HandleIn == 0)
+ {
+ Key = ASMAtomicIncU32(pu32HandleCountSource);
+
+ if (Key == u32Start)
+ {
+ /* Rollover. Something is wrong. */
+ AssertReleaseFailed();
+ break;
+ }
+
+ /* 0 and 0x80000000 are not valid handles. */
+ if ((Key & 0x7FFFFFFF) == 0)
+ {
+ /* Over the invalid value, reinitialize the source. */
+ *pu32HandleCountSource = pObject->Type () == HGCMOBJ_CLIENT?
+ 0:
+ UINT32_C(0x80000000);
+ continue;
+ }
+ }
+ else
+ {
+ Key = u32HandleIn;
+ }
+
+ /* Insert object to AVL tree. */
+ pCore->AvlCore.Key = Key;
+
+ bool fRC = RTAvlU32Insert(&g_pTree, &pCore->AvlCore);
+
+ /* Could not insert a handle. */
+ if (!fRC)
+ {
+ if (u32HandleIn == 0)
+ {
+ /* Try another generated handle. */
+ continue;
+ }
+ /* Could not use the specified handle. */
+ break;
+ }
+
+ /* Initialize backlink. */
+ pCore->pSelf = pObject;
+
+ /* Reference the object for time while it resides in the tree. */
+ pObject->Reference();
+
+ /* Store returned handle. */
+ handle = Key;
+
+ Log(("Object key inserted 0x%08X\n", Key));
+
+ break;
+ }
+
+ hgcmObjLeave();
+ }
+ else
+ {
+ AssertReleaseMsgFailed(("MAIN::hgcmObjGenerateHandle: Failed to acquire object pool semaphore"));
+ }
+
+ LogFlow(("MAIN::hgcmObjGenerateHandle: handle = 0x%08X, vrc = %Rrc, return void\n", handle, vrc));
+
+ return handle;
+}
+
+uint32_t hgcmObjGenerateHandle(HGCMObject *pObject)
+{
+ return hgcmObjMake(pObject, 0);
+}
+
+uint32_t hgcmObjAssignHandle(HGCMObject *pObject, uint32_t u32Handle)
+{
+ return hgcmObjMake(pObject, u32Handle);
+}
+
+void hgcmObjDeleteHandle(uint32_t handle)
+{
+ int vrc = VINF_SUCCESS;
+
+ LogFlow(("MAIN::hgcmObjDeleteHandle: handle 0x%08X\n", handle));
+
+ if (handle)
+ {
+ vrc = hgcmObjEnter();
+
+ if (RT_SUCCESS(vrc))
+ {
+ ObjectAVLCore *pCore = (ObjectAVLCore *)RTAvlU32Remove(&g_pTree, handle);
+
+ if (pCore)
+ {
+ AssertRelease(pCore->pSelf);
+
+ pCore->pSelf->Dereference();
+ }
+
+ hgcmObjLeave();
+ }
+ else
+ {
+ AssertReleaseMsgFailed(("Failed to acquire object pool semaphore, vrc = %Rrc", vrc));
+ }
+ }
+
+ LogFlow(("MAIN::hgcmObjDeleteHandle: vrc = %Rrc, return void\n", vrc));
+}
+
+HGCMObject *hgcmObjReference (uint32_t handle, HGCMOBJ_TYPE enmObjType)
+{
+ LogFlow(("MAIN::hgcmObjReference: handle 0x%08X\n", handle));
+
+ HGCMObject *pObject = NULL;
+
+ if ((handle & UINT32_C(0x7FFFFFFF)) == 0)
+ {
+ return pObject;
+ }
+
+ int vrc = hgcmObjEnter();
+
+ if (RT_SUCCESS(vrc))
+ {
+ ObjectAVLCore *pCore = (ObjectAVLCore *)RTAvlU32Get(&g_pTree, handle);
+
+ Assert(!pCore || (pCore->pSelf && pCore->pSelf->Type() == enmObjType));
+ if ( pCore
+ && pCore->pSelf
+ && pCore->pSelf->Type() == enmObjType)
+ {
+ pObject = pCore->pSelf;
+
+ AssertRelease(pObject);
+
+ pObject->Reference();
+ }
+
+ hgcmObjLeave();
+ }
+ else
+ {
+ AssertReleaseMsgFailed(("Failed to acquire object pool semaphore, vrc = %Rrc", vrc));
+ }
+
+ LogFlow(("MAIN::hgcmObjReference: return pObject %p\n", pObject));
+
+ return pObject;
+}
+
+void hgcmObjDereference(HGCMObject *pObject)
+{
+ LogFlow(("MAIN::hgcmObjDereference: pObject %p\n", pObject));
+
+ AssertRelease(pObject);
+
+ pObject->Dereference();
+
+ LogFlow(("MAIN::hgcmObjDereference: return\n"));
+}
+
+uint32_t hgcmObjQueryHandleCount()
+{
+ return g_u32ClientHandleCount;
+}
+
+void hgcmObjSetHandleCount(uint32_t u32ClientHandleCount)
+{
+ Assert(g_u32ClientHandleCount <= u32ClientHandleCount);
+
+ int vrc = hgcmObjEnter();
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (g_u32ClientHandleCount <= u32ClientHandleCount)
+ g_u32ClientHandleCount = u32ClientHandleCount;
+ hgcmObjLeave();
+ }
+}
diff --git a/src/VBox/Main/src-client/HGCMThread.cpp b/src/VBox/Main/src-client/HGCMThread.cpp
new file mode 100644
index 00000000..8ea01bab
--- /dev/null
+++ b/src/VBox/Main/src-client/HGCMThread.cpp
@@ -0,0 +1,790 @@
+/* $Id: HGCMThread.cpp $ */
+/** @file
+ * HGCMThread - Host-Guest Communication Manager Threads
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_HGCM
+#include "LoggingNew.h"
+
+#include "HGCMThread.h"
+
+#include <VBox/err.h>
+#include <VBox/vmm/stam.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/string.h>
+
+#include <new> /* for std:nothrow */
+
+
+/* HGCM uses worker threads, which process messages from other threads.
+ * A message consists of the message header and message specific data.
+ * Message header is opaque for callers, but message data is defined
+ * and used by them.
+ *
+ * Messages are distinguished by message identifier and worker thread
+ * they are allocated for.
+ *
+ * Messages are allocated for a worker thread and belong to
+ * the thread. A worker thread holds the queue of messages.
+ *
+ * The calling thread creates a message, specifying which worker thread
+ * the message is created for, then, optionally, initializes message
+ * specific data and, also optionally, references the message.
+ *
+ * Message then is posted or sent to worker thread by inserting
+ * it to the worker thread message queue and referencing the message.
+ * Worker thread then again may fetch next message.
+ *
+ * Upon processing the message the worker thread dereferences it.
+ * Dereferencing also automatically deletes message from the thread
+ * queue and frees memory allocated for the message, if no more
+ * references left. If there are references, the message remains
+ * in the queue.
+ *
+ */
+
+/* Version of HGCM message header */
+#define HGCMMSG_VERSION (1)
+
+/* Thread is initializing. */
+#define HGCMMSG_TF_INITIALIZING (0x00000001)
+/* Thread must be terminated. */
+#define HGCMMSG_TF_TERMINATE (0x00000002)
+/* Thread has been terminated. */
+#define HGCMMSG_TF_TERMINATED (0x00000004)
+
+/** @todo consider use of RTReq */
+
+static DECLCALLBACK(int) hgcmWorkerThreadFunc(RTTHREAD ThreadSelf, void *pvUser);
+
+class HGCMThread : public HGCMReferencedObject
+{
+ private:
+ friend DECLCALLBACK(int) hgcmWorkerThreadFunc(RTTHREAD ThreadSelf, void *pvUser);
+
+ /* Worker thread function. */
+ PFNHGCMTHREAD m_pfnThread;
+
+ /* A user supplied thread parameter. */
+ void *m_pvUser;
+
+ /* The thread runtime handle. */
+ RTTHREAD m_hThread;
+
+ /** Event the thread waits for, signalled when a message to process is posted to
+ * the thread, automatically reset. */
+ RTSEMEVENT m_eventThread;
+
+ /* A caller thread waits for completion of a SENT message on this event. */
+ RTSEMEVENTMULTI m_eventSend;
+ int32_t volatile m_i32MessagesProcessed;
+
+ /* Critical section for accessing the thread data, mostly for message queues. */
+ RTCRITSECT m_critsect;
+
+ /* thread state/operation flags */
+ uint32_t m_fu32ThreadFlags;
+
+ /* Message queue variables. Messages are inserted at tail of message
+ * queue. They are consumed by worker thread sequentially. If a message was
+ * consumed, it is removed from message queue.
+ */
+
+ /* Head of message queue. */
+ HGCMMsgCore *m_pMsgInputQueueHead;
+ /* Message which another message will be inserted after. */
+ HGCMMsgCore *m_pMsgInputQueueTail;
+
+ /* Head of messages being processed queue. */
+ HGCMMsgCore *m_pMsgInProcessHead;
+ /* Message which another message will be inserted after. */
+ HGCMMsgCore *m_pMsgInProcessTail;
+
+ /* Head of free message structures list. */
+ HGCMMsgCore *m_pFreeHead;
+ /* Tail of free message structures list. */
+ HGCMMsgCore *m_pFreeTail;
+
+ /** @name Statistics
+ * @{ */
+ STAMCOUNTER m_StatPostMsgNoPending;
+ STAMCOUNTER m_StatPostMsgOnePending;
+ STAMCOUNTER m_StatPostMsgTwoPending;
+ STAMCOUNTER m_StatPostMsgThreePending;
+ STAMCOUNTER m_StatPostMsgManyPending;
+ /** @} */
+
+ inline int Enter(void);
+ inline void Leave(void);
+
+ HGCMMsgCore *FetchFreeListHead(void);
+
+ protected:
+ virtual ~HGCMThread(void);
+
+ public:
+
+ HGCMThread ();
+
+ int WaitForTermination (void);
+
+ int Initialize(const char *pszThreadName, PFNHGCMTHREAD pfnThread, void *pvUser,
+ const char *pszStatsSubDir, PUVM pUVM, PCVMMR3VTABLE pVMM);
+
+ int MsgAlloc(HGCMMsgCore **pMsg, uint32_t u32MsgId, PFNHGCMNEWMSGALLOC pfnNewMessage);
+ int MsgGet(HGCMMsgCore **ppMsg);
+ int MsgPost(HGCMMsgCore *pMsg, PFNHGCMMSGCALLBACK pfnCallback, bool bWait);
+ int MsgComplete(HGCMMsgCore *pMsg, int32_t result);
+};
+
+
+/*
+ * HGCMMsgCore implementation.
+ */
+
+#define HGCM_MSG_F_PROCESSED (0x00000001)
+#define HGCM_MSG_F_WAIT (0x00000002)
+#define HGCM_MSG_F_IN_PROCESS (0x00000004)
+
+void HGCMMsgCore::InitializeCore(uint32_t u32MsgId, HGCMThread *pThread)
+{
+ m_u32Version = HGCMMSG_VERSION;
+ m_u32Msg = u32MsgId;
+ m_pfnCallback = NULL;
+ m_pNext = NULL;
+ m_pPrev = NULL;
+ m_fu32Flags = 0;
+ m_rcSend = VINF_SUCCESS;
+ m_pThread = pThread;
+ pThread->Reference();
+}
+
+/* virtual */ HGCMMsgCore::~HGCMMsgCore()
+{
+ if (m_pThread)
+ {
+ m_pThread->Dereference();
+ m_pThread = NULL;
+ }
+}
+
+/*
+ * HGCMThread implementation.
+ */
+
+static DECLCALLBACK(int) hgcmWorkerThreadFunc(RTTHREAD hThreadSelf, void *pvUser)
+{
+ HGCMThread *pThread = (HGCMThread *)pvUser;
+
+ LogFlow(("MAIN::hgcmWorkerThreadFunc: starting HGCM thread %p\n", pThread));
+
+ AssertRelease(pThread);
+
+ pThread->m_hThread = hThreadSelf;
+ pThread->m_fu32ThreadFlags &= ~HGCMMSG_TF_INITIALIZING;
+ int vrc = RTThreadUserSignal(hThreadSelf);
+ AssertRC(vrc);
+
+ pThread->m_pfnThread(pThread, pThread->m_pvUser);
+
+ pThread->m_fu32ThreadFlags |= HGCMMSG_TF_TERMINATED;
+
+ LogFlow(("MAIN::hgcmWorkerThreadFunc: completed HGCM thread %p\n", pThread));
+
+ return vrc;
+}
+
+HGCMThread::HGCMThread()
+ :
+ HGCMReferencedObject(HGCMOBJ_THREAD),
+ m_pfnThread(NULL),
+ m_pvUser(NULL),
+ m_hThread(NIL_RTTHREAD),
+ m_eventThread(NIL_RTSEMEVENT),
+ m_eventSend(NIL_RTSEMEVENTMULTI),
+ m_i32MessagesProcessed(0),
+ m_fu32ThreadFlags(0),
+ m_pMsgInputQueueHead(NULL),
+ m_pMsgInputQueueTail(NULL),
+ m_pMsgInProcessHead(NULL),
+ m_pMsgInProcessTail(NULL),
+ m_pFreeHead(NULL),
+ m_pFreeTail(NULL)
+{
+ RT_ZERO(m_critsect);
+}
+
+HGCMThread::~HGCMThread()
+{
+ /*
+ * Free resources allocated for the thread.
+ */
+
+ Assert(m_fu32ThreadFlags & HGCMMSG_TF_TERMINATED);
+
+ if (RTCritSectIsInitialized(&m_critsect))
+ RTCritSectDelete(&m_critsect);
+
+ if (m_eventSend != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(m_eventSend);
+ m_eventSend = NIL_RTSEMEVENTMULTI;
+ }
+
+ if (m_eventThread != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(m_eventThread);
+ m_eventThread = NIL_RTSEMEVENT;
+ }
+}
+
+int HGCMThread::WaitForTermination(void)
+{
+ int vrc = VINF_SUCCESS;
+ LogFlowFunc(("\n"));
+
+ if (m_hThread != NIL_RTTHREAD)
+ {
+ vrc = RTThreadWait(m_hThread, 5000, NULL);
+ m_hThread = NIL_RTTHREAD;
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int HGCMThread::Initialize(const char *pszThreadName, PFNHGCMTHREAD pfnThread, void *pvUser,
+ const char *pszStatsSubDir, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ int vrc = RTSemEventCreate(&m_eventThread);
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTSemEventMultiCreate(&m_eventSend);
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTCritSectInit(&m_critsect);
+
+ if (RT_SUCCESS(vrc))
+ {
+ m_pfnThread = pfnThread;
+ m_pvUser = pvUser;
+
+ m_fu32ThreadFlags = HGCMMSG_TF_INITIALIZING;
+
+ RTTHREAD hThread;
+ vrc = RTThreadCreate(&hThread, hgcmWorkerThreadFunc, this, 0, /* default stack size; some services
+ may need quite a bit */
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE,
+ pszThreadName);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Register statistics while the thread starts. */
+ if (pUVM)
+ {
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgNoPending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_COUNT, "Times a message was appended to an empty input queue.",
+ "/HGCM/%s/PostMsg0Pending", pszStatsSubDir);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgOnePending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_COUNT,
+ "Times a message was appended to input queue with only one pending message.",
+ "/HGCM/%s/PostMsg1Pending", pszStatsSubDir);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgTwoPending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_COUNT,
+ "Times a message was appended to input queue with only one pending message.",
+ "/HGCM/%s/PostMsg2Pending", pszStatsSubDir);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgThreePending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_COUNT,
+ "Times a message was appended to input queue with only one pending message.",
+ "/HGCM/%s/PostMsg3Pending", pszStatsSubDir);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgManyPending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_COUNT,
+ "Times a message was appended to input queue with only one pending message.",
+ "/HGCM/%s/PostMsgManyPending", pszStatsSubDir);
+ }
+
+
+ /* Wait until the thread is ready. */
+ vrc = RTThreadUserWait(hThread, 30000);
+ AssertRC(vrc);
+ Assert(!(m_fu32ThreadFlags & HGCMMSG_TF_INITIALIZING) || RT_FAILURE(vrc));
+ }
+ else
+ {
+ m_hThread = NIL_RTTHREAD;
+ Log(("hgcmThreadCreate: FAILURE: Can't start worker thread.\n"));
+ }
+ }
+ else
+ {
+ Log(("hgcmThreadCreate: FAILURE: Can't init a critical section for a hgcm worker thread.\n"));
+ RT_ZERO(m_critsect);
+ }
+ }
+ else
+ {
+ Log(("hgcmThreadCreate: FAILURE: Can't create an event semaphore for a sent messages.\n"));
+ m_eventSend = NIL_RTSEMEVENTMULTI;
+ }
+ }
+ else
+ {
+ Log(("hgcmThreadCreate: FAILURE: Can't create an event semaphore for a hgcm worker thread.\n"));
+ m_eventThread = NIL_RTSEMEVENT;
+ }
+
+ return vrc;
+}
+
+inline int HGCMThread::Enter(void)
+{
+ int vrc = RTCritSectEnter(&m_critsect);
+
+#ifdef LOG_ENABLED
+ if (RT_FAILURE(vrc))
+ Log(("HGCMThread::MsgPost: FAILURE: could not obtain worker thread mutex, vrc = %Rrc!!!\n", vrc));
+#endif
+
+ return vrc;
+}
+
+inline void HGCMThread::Leave(void)
+{
+ RTCritSectLeave(&m_critsect);
+}
+
+
+int HGCMThread::MsgAlloc(HGCMMsgCore **ppMsg, uint32_t u32MsgId, PFNHGCMNEWMSGALLOC pfnNewMessage)
+{
+ /** @todo Implement this free list / cache thingy. */
+ HGCMMsgCore *pmsg = NULL;
+
+ bool fFromFreeList = false;
+
+ if (!pmsg)
+ {
+ /* We have to allocate a new memory block. */
+ pmsg = pfnNewMessage(u32MsgId);
+ if (pmsg != NULL)
+ pmsg->Reference(); /* (it's created with zero references) */
+ else
+ return VERR_NO_MEMORY;
+ }
+
+ /* Initialize just allocated message core */
+ pmsg->InitializeCore(u32MsgId, this);
+
+ /* and the message specific data. */
+ pmsg->Initialize();
+
+ LogFlow(("MAIN::hgcmMsgAlloc: allocated message %p\n", pmsg));
+
+ *ppMsg = pmsg;
+
+ if (fFromFreeList)
+ {
+ /* Message was referenced in the free list, now dereference it. */
+ pmsg->Dereference();
+ }
+
+ return VINF_SUCCESS;
+}
+
+int HGCMThread::MsgPost(HGCMMsgCore *pMsg, PFNHGCMMSGCALLBACK pfnCallback, bool fWait)
+{
+ LogFlow(("HGCMThread::MsgPost: thread = %p, pMsg = %p, pfnCallback = %p\n", this, pMsg, pfnCallback));
+
+ int vrc = Enter();
+
+ if (RT_SUCCESS(vrc))
+ {
+ pMsg->m_pfnCallback = pfnCallback;
+
+ if (fWait)
+ pMsg->m_fu32Flags |= HGCM_MSG_F_WAIT;
+
+ /* Insert the message to the queue tail. */
+ pMsg->m_pNext = NULL;
+ HGCMMsgCore * const pPrev = m_pMsgInputQueueTail;
+ pMsg->m_pPrev = pPrev;
+
+ if (pPrev)
+ {
+ pPrev->m_pNext = pMsg;
+ if (!pPrev->m_pPrev)
+ STAM_REL_COUNTER_INC(&m_StatPostMsgOnePending);
+ else if (!pPrev->m_pPrev)
+ STAM_REL_COUNTER_INC(&m_StatPostMsgTwoPending);
+ else if (!pPrev->m_pPrev->m_pPrev)
+ STAM_REL_COUNTER_INC(&m_StatPostMsgThreePending);
+ else
+ STAM_REL_COUNTER_INC(&m_StatPostMsgManyPending);
+ }
+ else
+ {
+ m_pMsgInputQueueHead = pMsg;
+ STAM_REL_COUNTER_INC(&m_StatPostMsgNoPending);
+ }
+
+ m_pMsgInputQueueTail = pMsg;
+
+ Leave();
+
+ LogFlow(("HGCMThread::MsgPost: going to inform the thread %p about message, fWait = %d\n", this, fWait));
+
+ /* Inform the worker thread that there is a message. */
+ RTSemEventSignal(m_eventThread);
+
+ LogFlow(("HGCMThread::MsgPost: event signalled\n"));
+
+ if (fWait)
+ {
+ /* Immediately check if the message has been processed. */
+ while ((pMsg->m_fu32Flags & HGCM_MSG_F_PROCESSED) == 0)
+ {
+ /* Poll infrequently to make sure no completed message has been missed. */
+ RTSemEventMultiWait(m_eventSend, 1000);
+
+ LogFlow(("HGCMThread::MsgPost: wait completed flags = %08X\n", pMsg->m_fu32Flags));
+
+ if ((pMsg->m_fu32Flags & HGCM_MSG_F_PROCESSED) == 0)
+ RTThreadYield();
+ }
+
+ /* 'Our' message has been processed, so should reset the semaphore.
+ * There is still possible that another message has been processed
+ * and the semaphore has been signalled again.
+ * Reset only if there are no other messages completed.
+ */
+ int32_t c = ASMAtomicDecS32(&m_i32MessagesProcessed);
+ Assert(c >= 0);
+ if (c == 0)
+ RTSemEventMultiReset(m_eventSend);
+
+ vrc = pMsg->m_rcSend;
+ }
+ }
+
+ LogFlow(("HGCMThread::MsgPost: vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+
+int HGCMThread::MsgGet(HGCMMsgCore **ppMsg)
+{
+ int vrc = VINF_SUCCESS;
+
+ LogFlow(("HGCMThread::MsgGet: thread = %p, ppMsg = %p\n", this, ppMsg));
+
+ for (;;)
+ {
+ if (m_fu32ThreadFlags & HGCMMSG_TF_TERMINATE)
+ {
+ vrc = VERR_INTERRUPTED;
+ break;
+ }
+
+ LogFlow(("MAIN::hgcmMsgGet: m_pMsgInputQueueHead = %p\n", m_pMsgInputQueueHead));
+
+ if (m_pMsgInputQueueHead)
+ {
+ /* Move the message to the m_pMsgInProcessHead list */
+ vrc = Enter();
+
+ if (RT_FAILURE(vrc))
+ {
+ break;
+ }
+
+ HGCMMsgCore *pMsg = m_pMsgInputQueueHead;
+
+ /* Remove the message from the head of Queue list. */
+ Assert(m_pMsgInputQueueHead->m_pPrev == NULL);
+
+ if (m_pMsgInputQueueHead->m_pNext)
+ {
+ m_pMsgInputQueueHead = m_pMsgInputQueueHead->m_pNext;
+ m_pMsgInputQueueHead->m_pPrev = NULL;
+ }
+ else
+ {
+ Assert(m_pMsgInputQueueHead == m_pMsgInputQueueTail);
+
+ m_pMsgInputQueueHead = NULL;
+ m_pMsgInputQueueTail = NULL;
+ }
+
+ /* Insert the message to the tail of the m_pMsgInProcessHead list. */
+ pMsg->m_pNext = NULL;
+ pMsg->m_pPrev = m_pMsgInProcessTail;
+
+ if (m_pMsgInProcessTail)
+ m_pMsgInProcessTail->m_pNext = pMsg;
+ else
+ m_pMsgInProcessHead = pMsg;
+
+ m_pMsgInProcessTail = pMsg;
+
+ pMsg->m_fu32Flags |= HGCM_MSG_F_IN_PROCESS;
+
+ Leave();
+
+ /* Return the message to the caller. */
+ *ppMsg = pMsg;
+
+ LogFlow(("MAIN::hgcmMsgGet: got message %p\n", *ppMsg));
+
+ break;
+ }
+
+ /* Wait for an event. */
+ RTSemEventWait(m_eventThread, RT_INDEFINITE_WAIT);
+ }
+
+ LogFlow(("HGCMThread::MsgGet: *ppMsg = %p, return vrc = %Rrc\n", *ppMsg, vrc));
+ return vrc;
+}
+
+int HGCMThread::MsgComplete(HGCMMsgCore *pMsg, int32_t result)
+{
+ LogFlow(("HGCMThread::MsgComplete: thread = %p, pMsg = %p, result = %Rrc (%d)\n", this, pMsg, result, result));
+
+ AssertRelease(pMsg->m_pThread == this);
+ AssertReleaseMsg((pMsg->m_fu32Flags & HGCM_MSG_F_IN_PROCESS) != 0, ("%p %x\n", pMsg, pMsg->m_fu32Flags));
+
+ int vrcRet = VINF_SUCCESS;
+ if (pMsg->m_pfnCallback)
+ {
+ /** @todo call callback with error code in MsgPost in case of errors */
+
+ vrcRet = pMsg->m_pfnCallback(result, pMsg);
+
+ LogFlow(("HGCMThread::MsgComplete: callback executed. pMsg = %p, thread = %p, rcRet = %Rrc\n", pMsg, this, vrcRet));
+ }
+
+ /* Message processing has been completed. */
+
+ int vrc = Enter();
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Remove the message from the InProcess queue. */
+
+ if (pMsg->m_pNext)
+ pMsg->m_pNext->m_pPrev = pMsg->m_pPrev;
+ else
+ m_pMsgInProcessTail = pMsg->m_pPrev;
+
+ if (pMsg->m_pPrev)
+ pMsg->m_pPrev->m_pNext = pMsg->m_pNext;
+ else
+ m_pMsgInProcessHead = pMsg->m_pNext;
+
+ pMsg->m_pNext = NULL;
+ pMsg->m_pPrev = NULL;
+
+ bool fWaited = ((pMsg->m_fu32Flags & HGCM_MSG_F_WAIT) != 0);
+
+ if (fWaited)
+ {
+ ASMAtomicIncS32(&m_i32MessagesProcessed);
+
+ /* This should be done before setting the HGCM_MSG_F_PROCESSED flag. */
+ pMsg->m_rcSend = result;
+ }
+
+ /* The message is now completed. */
+ pMsg->m_fu32Flags &= ~HGCM_MSG_F_IN_PROCESS;
+ pMsg->m_fu32Flags &= ~HGCM_MSG_F_WAIT;
+ pMsg->m_fu32Flags |= HGCM_MSG_F_PROCESSED;
+
+ pMsg->Dereference();
+
+ Leave();
+
+ if (fWaited)
+ {
+ /* Wake up all waiters. so they can decide if their message has been processed. */
+ RTSemEventMultiSignal(m_eventSend);
+ }
+ }
+
+ return vrcRet;
+}
+
+/*
+ * Thread API. Public interface.
+ */
+
+int hgcmThreadCreate(HGCMThread **ppThread, const char *pszThreadName, PFNHGCMTHREAD pfnThread, void *pvUser,
+ const char *pszStatsSubDir, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ LogFlow(("MAIN::hgcmThreadCreate\n"));
+ int vrc;
+
+ /* Allocate memory for a new thread object. */
+ HGCMThread *pThread = new (std::nothrow) HGCMThread();
+
+ if (pThread)
+ {
+ pThread->Reference(); /* (it's created with zero references) */
+
+ /* Initialize the object. */
+ vrc = pThread->Initialize(pszThreadName, pfnThread, pvUser, pszStatsSubDir, pUVM, pVMM);
+ if (RT_SUCCESS(vrc))
+ {
+ *ppThread = pThread;
+ LogFlow(("MAIN::hgcmThreadCreate: vrc = %Rrc\n", vrc));
+ return vrc;
+ }
+
+ Log(("hgcmThreadCreate: FAILURE: Initialize failed: vrc = %Rrc\n", vrc));
+
+ pThread->Dereference();
+ }
+ else
+ {
+ Log(("hgcmThreadCreate: FAILURE: Can't allocate memory for a hgcm worker thread.\n"));
+ vrc = VERR_NO_MEMORY;
+ }
+ *ppThread = NULL;
+
+ LogFlow(("MAIN::hgcmThreadCreate: vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int hgcmThreadWait(HGCMThread *pThread)
+{
+ LogFlowFunc(("%p\n", pThread));
+
+ int vrc;
+ if (pThread)
+ {
+ vrc = pThread->WaitForTermination();
+
+ pThread->Dereference();
+ }
+ else
+ vrc = VERR_INVALID_HANDLE;
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int hgcmMsgAlloc(HGCMThread *pThread, HGCMMsgCore **ppMsg, uint32_t u32MsgId, PFNHGCMNEWMSGALLOC pfnNewMessage)
+{
+ LogFlow(("hgcmMsgAlloc: pThread = %p, ppMsg = %p, sizeof (HGCMMsgCore) = %d\n", pThread, ppMsg, sizeof(HGCMMsgCore)));
+
+ AssertReturn(pThread, VERR_INVALID_HANDLE);
+ AssertReturn(ppMsg, VERR_INVALID_PARAMETER);
+
+ int vrc = pThread->MsgAlloc(ppMsg, u32MsgId, pfnNewMessage);
+
+ LogFlow(("MAIN::hgcmMsgAlloc: *ppMsg = %p, vrc = %Rrc\n", *ppMsg, vrc));
+ return vrc;
+}
+
+DECLINLINE(int) hgcmMsgPostInternal(HGCMMsgCore *pMsg, PFNHGCMMSGCALLBACK pfnCallback, bool fWait)
+{
+ LogFlow(("MAIN::hgcmMsgPostInternal: pMsg = %p, pfnCallback = %p, fWait = %d\n", pMsg, pfnCallback, fWait));
+ Assert(pMsg);
+
+ pMsg->Reference(); /* paranoia? */
+
+ int vrc = pMsg->Thread()->MsgPost(pMsg, pfnCallback, fWait);
+
+ pMsg->Dereference();
+
+ LogFlow(("MAIN::hgcmMsgPostInternal: pMsg = %p, vrc = %Rrc\n", pMsg, vrc));
+ return vrc;
+}
+
+int hgcmMsgPost(HGCMMsgCore *pMsg, PFNHGCMMSGCALLBACK pfnCallback)
+{
+ int vrc = hgcmMsgPostInternal(pMsg, pfnCallback, false);
+
+ if (RT_SUCCESS(vrc))
+ vrc = VINF_HGCM_ASYNC_EXECUTE;
+
+ return vrc;
+}
+
+int hgcmMsgSend(HGCMMsgCore *pMsg)
+{
+ return hgcmMsgPostInternal(pMsg, NULL, true);
+}
+
+int hgcmMsgGet(HGCMThread *pThread, HGCMMsgCore **ppMsg)
+{
+ LogFlow(("MAIN::hgcmMsgGet: pThread = %p, ppMsg = %p\n", pThread, ppMsg));
+
+ AssertReturn(pThread, VERR_INVALID_HANDLE);
+ AssertReturn(ppMsg, VERR_INVALID_PARAMETER);
+
+ pThread->Reference(); /* paranoia */
+
+ int vrc = pThread->MsgGet(ppMsg);
+
+ pThread->Dereference();
+
+ LogFlow(("MAIN::hgcmMsgGet: *ppMsg = %p, vrc = %Rrc\n", *ppMsg, vrc));
+ return vrc;
+}
+
+int hgcmMsgComplete(HGCMMsgCore *pMsg, int32_t rcMsg)
+{
+ LogFlow(("MAIN::hgcmMsgComplete: pMsg = %p, rcMsg = %Rrc (%d)\n", pMsg, rcMsg, rcMsg));
+
+ int vrc;
+ if (pMsg)
+ vrc = pMsg->Thread()->MsgComplete(pMsg, rcMsg);
+ else
+ vrc = VINF_SUCCESS;
+
+ LogFlow(("MAIN::hgcmMsgComplete: pMsg = %p, rcMsg =%Rrc (%d), returns vrc = %Rrc\n", pMsg, rcMsg, rcMsg, vrc));
+ return vrc;
+}
+
+int hgcmThreadInit(void)
+{
+ LogFlow(("MAIN::hgcmThreadInit\n"));
+
+ /** @todo error processing. */
+
+ int vrc = hgcmObjInit();
+
+ LogFlow(("MAIN::hgcmThreadInit: vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+void hgcmThreadUninit(void)
+{
+ hgcmObjUninit();
+}
+
diff --git a/src/VBox/Main/src-client/KeyboardImpl.cpp b/src/VBox/Main/src-client/KeyboardImpl.cpp
new file mode 100644
index 00000000..3682bc3f
--- /dev/null
+++ b/src/VBox/Main/src-client/KeyboardImpl.cpp
@@ -0,0 +1,548 @@
+/* $Id: KeyboardImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_KEYBOARD
+#include "LoggingNew.h"
+
+#include "KeyboardImpl.h"
+#include "ConsoleImpl.h"
+
+#include "AutoCaller.h"
+#include "VBoxEvents.h"
+
+#include <VBox/com/array.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/err.h>
+
+#include <iprt/cpp/utils.h>
+
+
+// defines
+////////////////////////////////////////////////////////////////////////////////
+
+// globals
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Keyboard device capabilities bitfield.
+ */
+enum
+{
+ /** The keyboard device does not wish to receive keystrokes. */
+ KEYBOARD_DEVCAP_DISABLED = 0,
+ /** The keyboard device does wishes to receive keystrokes. */
+ KEYBOARD_DEVCAP_ENABLED = 1
+};
+
+/**
+ * Keyboard driver instance data.
+ */
+typedef struct DRVMAINKEYBOARD
+{
+ /** Pointer to the keyboard object. */
+ Keyboard *pKeyboard;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the keyboard port interface of the driver/device above us. */
+ PPDMIKEYBOARDPORT pUpPort;
+ /** Our keyboard connector interface. */
+ PDMIKEYBOARDCONNECTOR IConnector;
+ /** The capabilities of this device. */
+ uint32_t u32DevCaps;
+} DRVMAINKEYBOARD, *PDRVMAINKEYBOARD;
+
+
+// constructor / destructor
+////////////////////////////////////////////////////////////////////////////////
+
+Keyboard::Keyboard()
+ : mParent(NULL)
+{
+}
+
+Keyboard::~Keyboard()
+{
+}
+
+HRESULT Keyboard::FinalConstruct()
+{
+ RT_ZERO(mpDrv);
+ menmLeds = PDMKEYBLEDS_NONE;
+ return BaseFinalConstruct();
+}
+
+void Keyboard::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public methods
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the keyboard object.
+ *
+ * @returns COM result indicator
+ * @param aParent handle of our parent object
+ */
+HRESULT Keyboard::init(Console *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ unconst(mEventSource).createObject();
+ HRESULT hrc = mEventSource->init();
+ AssertComRCReturnRC(hrc);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void Keyboard::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ for (unsigned i = 0; i < KEYBOARD_MAX_DEVICES; ++i)
+ {
+ if (mpDrv[i])
+ mpDrv[i]->pKeyboard = NULL;
+ mpDrv[i] = NULL;
+ }
+
+ menmLeds = PDMKEYBLEDS_NONE;
+
+ unconst(mParent) = NULL;
+ unconst(mEventSource).setNull();
+}
+
+/**
+ * Sends a scancode to the keyboard.
+ *
+ * @returns COM status code
+ * @param aScancode The scancode to send
+ */
+HRESULT Keyboard::putScancode(LONG aScancode)
+{
+ std::vector<LONG> scancodes;
+ scancodes.resize(1);
+ scancodes[0] = aScancode;
+ return putScancodes(scancodes, NULL);
+}
+
+/**
+ * Sends a list of scancodes to the keyboard.
+ *
+ * @returns COM status code
+ * @param aScancodes Pointer to the first scancode
+ * @param aCodesStored Address of variable to store the number
+ * of scancodes that were sent to the keyboard.
+ This value can be NULL.
+ */
+HRESULT Keyboard::putScancodes(const std::vector<LONG> &aScancodes,
+ ULONG *aCodesStored)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_CONSOLE_DRV(mpDrv[0]);
+
+ /* Send input to the last enabled device. Relies on the fact that
+ * the USB keyboard is always initialized after the PS/2 keyboard.
+ */
+ PPDMIKEYBOARDPORT pUpPort = NULL;
+ for (int i = KEYBOARD_MAX_DEVICES - 1; i >= 0 ; --i)
+ {
+ if (mpDrv[i] && (mpDrv[i]->u32DevCaps & KEYBOARD_DEVCAP_ENABLED))
+ {
+ pUpPort = mpDrv[i]->pUpPort;
+ break;
+ }
+ }
+
+ /* No enabled keyboard - throw the input away. */
+ if (!pUpPort)
+ {
+ if (aCodesStored)
+ *aCodesStored = (uint32_t)aScancodes.size();
+ return S_OK;
+ }
+
+ int vrc = VINF_SUCCESS;
+
+ uint32_t sent;
+ for (sent = 0; (sent < aScancodes.size()) && RT_SUCCESS(vrc); ++sent)
+ vrc = pUpPort->pfnPutEventScan(pUpPort, (uint8_t)aScancodes[sent]);
+
+ if (aCodesStored)
+ *aCodesStored = sent;
+
+ com::SafeArray<LONG> keys(aScancodes.size());
+ for (size_t i = 0; i < aScancodes.size(); ++i)
+ keys[i] = aScancodes[i];
+
+ ::FireGuestKeyboardEvent(mEventSource, ComSafeArrayAsInParam(keys));
+
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not send all scan codes to the virtual keyboard (%Rrc)"),
+ vrc);
+
+ return S_OK;
+}
+
+/**
+ * Sends a HID usage code and page to the keyboard.
+ *
+ * @returns COM status code
+ * @param aUsageCode The HID usage code to send
+ * @param aUsagePage The HID usage page corresponding to the code
+ * @param fKeyRelease The key release flag
+ */
+HRESULT Keyboard::putUsageCode(LONG aUsageCode, LONG aUsagePage, BOOL fKeyRelease)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_CONSOLE_DRV(mpDrv[0]);
+
+ /* Send input to the last enabled device. Relies on the fact that
+ * the USB keyboard is always initialized after the PS/2 keyboard.
+ */
+ PPDMIKEYBOARDPORT pUpPort = NULL;
+ for (int i = KEYBOARD_MAX_DEVICES - 1; i >= 0 ; --i)
+ {
+ if (mpDrv[i] && (mpDrv[i]->u32DevCaps & KEYBOARD_DEVCAP_ENABLED))
+ {
+ pUpPort = mpDrv[i]->pUpPort;
+ break;
+ }
+ }
+
+ /* No enabled keyboard - throw the input away. */
+ if (!pUpPort)
+ return S_OK;
+
+ uint32_t idUsage = (uint16_t)aUsageCode | ((uint32_t)(uint8_t)aUsagePage << 16) | (fKeyRelease ? UINT32_C(0x80000000) : 0);
+ int vrc = pUpPort->pfnPutEventHid(pUpPort, idUsage);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not send usage code to the virtual keyboard (%Rrc)"),
+ vrc);
+
+ return S_OK;
+}
+
+/**
+ * Sends Control-Alt-Delete to the keyboard. This could be done otherwise
+ * but it's so common that we'll be nice and supply a convenience API.
+ *
+ * @returns COM status code
+ *
+ */
+HRESULT Keyboard::putCAD()
+{
+ std::vector<LONG> cadSequence;
+ cadSequence.resize(8);
+
+ cadSequence[0] = 0x1d; // Ctrl down
+ cadSequence[1] = 0x38; // Alt down
+ cadSequence[2] = 0xe0; // Del down 1
+ cadSequence[3] = 0x53; // Del down 2
+ cadSequence[4] = 0xe0; // Del up 1
+ cadSequence[5] = 0xd3; // Del up 2
+ cadSequence[6] = 0xb8; // Alt up
+ cadSequence[7] = 0x9d; // Ctrl up
+
+ return putScancodes(cadSequence, NULL);
+}
+
+/**
+ * Releases all currently held keys in the virtual keyboard.
+ *
+ * @returns COM status code
+ *
+ */
+HRESULT Keyboard::releaseKeys()
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Release all keys on the active keyboard in order to start with a clean slate.
+ * Note that this should mirror the logic in Keyboard::putScancodes() when choosing
+ * which keyboard to send the release event to.
+ */
+ PPDMIKEYBOARDPORT pUpPort = NULL;
+ for (int i = KEYBOARD_MAX_DEVICES - 1; i >= 0 ; --i)
+ {
+ if (mpDrv[i] && (mpDrv[i]->u32DevCaps & KEYBOARD_DEVCAP_ENABLED))
+ {
+ pUpPort = mpDrv[i]->pUpPort;
+ break;
+ }
+ }
+
+ if (pUpPort)
+ {
+ int vrc = pUpPort->pfnReleaseKeys(pUpPort);
+ if (RT_FAILURE(vrc))
+ AssertMsgFailed(("Failed to release keys on all keyboards! vrc=%Rrc\n", vrc));
+ }
+
+ return S_OK;
+}
+
+HRESULT Keyboard::getKeyboardLEDs(std::vector<KeyboardLED_T> &aKeyboardLEDs)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aKeyboardLEDs.resize(0);
+
+ if (menmLeds & PDMKEYBLEDS_NUMLOCK) aKeyboardLEDs.push_back(KeyboardLED_NumLock);
+ if (menmLeds & PDMKEYBLEDS_CAPSLOCK) aKeyboardLEDs.push_back(KeyboardLED_CapsLock);
+ if (menmLeds & PDMKEYBLEDS_SCROLLLOCK) aKeyboardLEDs.push_back(KeyboardLED_ScrollLock);
+
+ return S_OK;
+}
+
+HRESULT Keyboard::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ // No need to lock - lifetime constant
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ return S_OK;
+}
+
+//
+// private methods
+//
+void Keyboard::onKeyboardLedsChange(PDMKEYBLEDS enmLeds)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Save the current status. */
+ menmLeds = enmLeds;
+
+ alock.release();
+
+ i_getParent()->i_onKeyboardLedsChange(RT_BOOL(enmLeds & PDMKEYBLEDS_NUMLOCK),
+ RT_BOOL(enmLeds & PDMKEYBLEDS_CAPSLOCK),
+ RT_BOOL(enmLeds & PDMKEYBLEDS_SCROLLLOCK));
+}
+
+DECLCALLBACK(void) Keyboard::i_keyboardLedStatusChange(PPDMIKEYBOARDCONNECTOR pInterface, PDMKEYBLEDS enmLeds)
+{
+ PDRVMAINKEYBOARD pDrv = RT_FROM_MEMBER(pInterface, DRVMAINKEYBOARD, IConnector);
+ pDrv->pKeyboard->onKeyboardLedsChange(enmLeds);
+}
+
+/**
+ * @interface_method_impl{PDMIKEYBOARDCONNECTOR,pfnSetActive}
+ */
+DECLCALLBACK(void) Keyboard::i_keyboardSetActive(PPDMIKEYBOARDCONNECTOR pInterface, bool fActive)
+{
+ PDRVMAINKEYBOARD pDrv = RT_FROM_MEMBER(pInterface, DRVMAINKEYBOARD, IConnector);
+
+ // Before activating a different keyboard, release all keys on the currently active one.
+ if (fActive)
+ pDrv->pKeyboard->releaseKeys();
+
+ if (fActive)
+ pDrv->u32DevCaps |= KEYBOARD_DEVCAP_ENABLED;
+ else
+ pDrv->u32DevCaps &= ~KEYBOARD_DEVCAP_ENABLED;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) Keyboard::i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINKEYBOARD pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINKEYBOARD);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIKEYBOARDCONNECTOR, &pDrv->IConnector);
+ return NULL;
+}
+
+
+/**
+ * Destruct a keyboard driver instance.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The driver instance data.
+ */
+DECLCALLBACK(void) Keyboard::i_drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVMAINKEYBOARD pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINKEYBOARD);
+ LogFlow(("Keyboard::drvDestruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ if (pThis->pKeyboard)
+ {
+ AutoWriteLock kbdLock(pThis->pKeyboard COMMA_LOCKVAL_SRC_POS);
+ for (unsigned cDev = 0; cDev < KEYBOARD_MAX_DEVICES; ++cDev)
+ if (pThis->pKeyboard->mpDrv[cDev] == pThis)
+ {
+ pThis->pKeyboard->mpDrv[cDev] = NULL;
+ break;
+ }
+ }
+}
+
+/**
+ * Construct a keyboard driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+DECLCALLBACK(int) Keyboard::i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ RT_NOREF(fFlags, pCfg);
+ PDRVMAINKEYBOARD pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINKEYBOARD);
+ LogFlow(("Keyboard::drvConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", "");
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * IBase.
+ */
+ pDrvIns->IBase.pfnQueryInterface = Keyboard::i_drvQueryInterface;
+
+ pThis->IConnector.pfnLedStatusChange = i_keyboardLedStatusChange;
+ pThis->IConnector.pfnSetActive = Keyboard::i_keyboardSetActive;
+
+ /*
+ * Get the IKeyboardPort interface of the above driver/device.
+ */
+ pThis->pUpPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIKEYBOARDPORT);
+ if (!pThis->pUpPort)
+ {
+ AssertMsgFailed(("Configuration error: No keyboard port interface above!\n"));
+ return VERR_PDM_MISSING_INTERFACE_ABOVE;
+ }
+
+ /*
+ * Get the Keyboard object pointer and update the mpDrv member.
+ */
+ com::Guid uuid(COM_IIDOF(IKeyboard));
+ IKeyboard *pIKeyboard = (IKeyboard *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw());
+ if (!pIKeyboard)
+ {
+ AssertMsgFailed(("Configuration error: No/bad Keyboard object!\n"));
+ return VERR_NOT_FOUND;
+ }
+ pThis->pKeyboard = static_cast<Keyboard *>(pIKeyboard);
+
+ unsigned cDev;
+ for (cDev = 0; cDev < KEYBOARD_MAX_DEVICES; ++cDev)
+ if (!pThis->pKeyboard->mpDrv[cDev])
+ {
+ pThis->pKeyboard->mpDrv[cDev] = pThis;
+ break;
+ }
+ if (cDev == KEYBOARD_MAX_DEVICES)
+ return VERR_NO_MORE_HANDLES;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Keyboard driver registration record.
+ */
+const PDMDRVREG Keyboard::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "MainKeyboard",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Main keyboard driver (Main as in the API).",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_KEYBOARD,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMAINKEYBOARD),
+ /* pfnConstruct */
+ Keyboard::i_drvConstruct,
+ /* pfnDestruct */
+ Keyboard::i_drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/MachineDebuggerImpl.cpp b/src/VBox/Main/src-client/MachineDebuggerImpl.cpp
new file mode 100644
index 00000000..c4646646
--- /dev/null
+++ b/src/VBox/Main/src-client/MachineDebuggerImpl.cpp
@@ -0,0 +1,1560 @@
+/* $Id: MachineDebuggerImpl.cpp $ */
+/** @file
+ * VBox IMachineDebugger COM class implementation (VBoxC).
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_MACHINEDEBUGGER
+#include "LoggingNew.h"
+
+#include "MachineDebuggerImpl.h"
+
+#include "Global.h"
+#include "ConsoleImpl.h"
+#include "ProgressImpl.h"
+
+#include "AutoCaller.h"
+
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/vmm/em.h>
+#include <VBox/vmm/uvm.h>
+#include <VBox/vmm/tm.h>
+#include <VBox/vmm/hm.h>
+#include <VBox/err.h>
+#include <iprt/cpp/utils.h>
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+MachineDebugger::MachineDebugger()
+ : mParent(NULL)
+{
+}
+
+MachineDebugger::~MachineDebugger()
+{
+}
+
+HRESULT MachineDebugger::FinalConstruct()
+{
+ unconst(mParent) = NULL;
+ return BaseFinalConstruct();
+}
+
+void MachineDebugger::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the machine debugger object.
+ *
+ * @returns COM result indicator
+ * @param aParent handle of our parent object
+ */
+HRESULT MachineDebugger::init(Console *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(maiQueuedEmExecPolicyParams); i++)
+ maiQueuedEmExecPolicyParams[i] = UINT8_MAX;
+ mSingleStepQueued = -1;
+ mLogEnabledQueued = -1;
+ mVirtualTimeRateQueued = UINT32_MAX;
+ mFlushMode = false;
+
+ m_hSampleReport = NULL;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void MachineDebugger::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(mParent) = NULL;
+ mFlushMode = false;
+}
+
+/**
+ * @callback_method_impl{FNDBGFPROGRESS}
+ */
+/*static*/ DECLCALLBACK(int) MachineDebugger::i_dbgfProgressCallback(void *pvUser, unsigned uPercentage)
+{
+ MachineDebugger *pThis = (MachineDebugger *)pvUser;
+
+ int vrc = pThis->m_Progress->i_iprtProgressCallback(uPercentage, static_cast<Progress *>(pThis->m_Progress));
+ if ( RT_SUCCESS(vrc)
+ && uPercentage == 100)
+ {
+ PCVMMR3VTABLE const pVMM = pThis->mParent->i_getVMMVTable();
+ AssertPtrReturn(pVMM, VERR_INTERNAL_ERROR_3);
+
+ vrc = pVMM->pfnDBGFR3SampleReportDumpToFile(pThis->m_hSampleReport, pThis->m_strFilename.c_str());
+ pVMM->pfnDBGFR3SampleReportRelease(pThis->m_hSampleReport);
+ pThis->m_hSampleReport = NULL;
+ if (RT_SUCCESS(vrc))
+ pThis->m_Progress->i_notifyComplete(S_OK);
+ else
+ {
+ HRESULT hrc = pThis->setError(VBOX_E_IPRT_ERROR,
+ tr("Writing the sample report to '%s' failed with %Rrc"),
+ pThis->m_strFilename.c_str(), vrc);
+ pThis->m_Progress->i_notifyComplete(hrc);
+ }
+ pThis->m_Progress.setNull();
+ }
+ else if (vrc == VERR_CANCELLED)
+ vrc = VERR_DBGF_CANCELLED;
+
+ return vrc;
+}
+
+// IMachineDebugger properties
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns the current singlestepping flag.
+ *
+ * @returns COM status code
+ * @param aSingleStep Where to store the result.
+ */
+HRESULT MachineDebugger::getSingleStep(BOOL *aSingleStep)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ RT_NOREF(aSingleStep); /** @todo */
+ ReturnComNotImplemented();
+ }
+ return hrc;
+}
+
+/**
+ * Sets the singlestepping flag.
+ *
+ * @returns COM status code
+ * @param aSingleStep The new state.
+ */
+HRESULT MachineDebugger::setSingleStep(BOOL aSingleStep)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ NOREF(aSingleStep); /** @todo */
+ ReturnComNotImplemented();
+ }
+ return hrc;
+}
+
+/**
+ * Internal worker for getting an EM executable policy setting.
+ *
+ * @returns COM status code.
+ * @param enmPolicy Which EM policy.
+ * @param pfEnforced Where to return the policy setting.
+ */
+HRESULT MachineDebugger::i_getEmExecPolicyProperty(EMEXECPOLICY enmPolicy, BOOL *pfEnforced)
+{
+ CheckComArgOutPointerValid(pfEnforced);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (i_queueSettings())
+ *pfEnforced = maiQueuedEmExecPolicyParams[enmPolicy] == 1;
+ else
+ {
+ bool fEnforced = false;
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ ptrVM.vtable()->pfnEMR3QueryExecutionPolicy(ptrVM.rawUVM(), enmPolicy, &fEnforced);
+ *pfEnforced = fEnforced;
+ }
+ }
+ return hrc;
+}
+
+/**
+ * Internal worker for setting an EM executable policy.
+ *
+ * @returns COM status code.
+ * @param enmPolicy Which policy to change.
+ * @param fEnforce Whether to enforce the policy or not.
+ */
+HRESULT MachineDebugger::i_setEmExecPolicyProperty(EMEXECPOLICY enmPolicy, BOOL fEnforce)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (i_queueSettings())
+ maiQueuedEmExecPolicyParams[enmPolicy] = fEnforce ? 1 : 0;
+ else
+ {
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnEMR3SetExecutionPolicy(ptrVM.rawUVM(), enmPolicy, fEnforce != FALSE);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("EMR3SetExecutionPolicy failed with %Rrc"), vrc);
+ }
+ }
+ }
+ return hrc;
+}
+
+/**
+ * Returns the current execute-all-in-IEM setting.
+ *
+ * @returns COM status code
+ * @param aExecuteAllInIEM Address of result variable.
+ */
+HRESULT MachineDebugger::getExecuteAllInIEM(BOOL *aExecuteAllInIEM)
+{
+ return i_getEmExecPolicyProperty(EMEXECPOLICY_IEM_ALL, aExecuteAllInIEM);
+}
+
+/**
+ * Changes the execute-all-in-IEM setting.
+ *
+ * @returns COM status code
+ * @param aExecuteAllInIEM New setting.
+ */
+HRESULT MachineDebugger::setExecuteAllInIEM(BOOL aExecuteAllInIEM)
+{
+ LogFlowThisFunc(("enable=%d\n", aExecuteAllInIEM));
+ return i_setEmExecPolicyProperty(EMEXECPOLICY_IEM_ALL, aExecuteAllInIEM);
+}
+
+/**
+ * Returns the log enabled / disabled status.
+ *
+ * @returns COM status code
+ * @param aLogEnabled address of result variable
+ */
+HRESULT MachineDebugger::getLogEnabled(BOOL *aLogEnabled)
+{
+#ifdef LOG_ENABLED
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const PRTLOGGER pLogInstance = RTLogDefaultInstance();
+ *aLogEnabled = pLogInstance && !(RTLogGetFlags(pLogInstance) & RTLOGFLAGS_DISABLED);
+#else
+ *aLogEnabled = false;
+#endif
+
+ return S_OK;
+}
+
+/**
+ * Enables or disables logging.
+ *
+ * @returns COM status code
+ * @param aLogEnabled The new code log state.
+ */
+HRESULT MachineDebugger::setLogEnabled(BOOL aLogEnabled)
+{
+ LogFlowThisFunc(("aLogEnabled=%d\n", aLogEnabled));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (i_queueSettings())
+ {
+ // queue the request
+ mLogEnabledQueued = aLogEnabled;
+ return S_OK;
+ }
+
+ Console::SafeVMPtr ptrVM(mParent);
+ if (FAILED(ptrVM.rc())) return ptrVM.rc();
+
+#ifdef LOG_ENABLED
+ int vrc = ptrVM.vtable()->pfnDBGFR3LogModifyFlags(ptrVM.rawUVM(), aLogEnabled ? "enabled" : "disabled");
+ if (RT_FAILURE(vrc))
+ {
+ /** @todo handle error code. */
+ }
+#endif
+
+ return S_OK;
+}
+
+HRESULT MachineDebugger::i_logStringProps(PRTLOGGER pLogger, PFNLOGGETSTR pfnLogGetStr,
+ const char *pszLogGetStr, Utf8Str *pstrSettings)
+{
+ /* Make sure the VM is powered up. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (FAILED(hrc))
+ return hrc;
+
+ /* Make sure we've got a logger. */
+ if (!pLogger)
+ {
+ *pstrSettings = "";
+ return S_OK;
+ }
+
+ /* Do the job. */
+ size_t cbBuf = _1K;
+ for (;;)
+ {
+ char *pszBuf = (char *)RTMemTmpAlloc(cbBuf);
+ AssertReturn(pszBuf, E_OUTOFMEMORY);
+ int vrc = pstrSettings->reserveNoThrow(cbBuf);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pfnLogGetStr(pLogger, pstrSettings->mutableRaw(), cbBuf);
+ if (RT_SUCCESS(vrc))
+ {
+ pstrSettings->jolt();
+ return S_OK;
+ }
+ *pstrSettings = "";
+ AssertReturn(vrc == VERR_BUFFER_OVERFLOW,
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("%s returned %Rrc"), pszLogGetStr, vrc));
+ }
+ else
+ return E_OUTOFMEMORY;
+
+ /* try again with a bigger buffer. */
+ cbBuf *= 2;
+ AssertReturn(cbBuf <= _256K, setError(E_FAIL, tr("%s returns too much data"), pszLogGetStr));
+ }
+}
+
+HRESULT MachineDebugger::getLogDbgFlags(com::Utf8Str &aLogDbgFlags)
+{
+ return i_logStringProps(RTLogGetDefaultInstance(), RTLogQueryFlags, "RTLogQueryFlags", &aLogDbgFlags);
+}
+
+HRESULT MachineDebugger::getLogDbgGroups(com::Utf8Str &aLogDbgGroups)
+{
+ return i_logStringProps(RTLogGetDefaultInstance(), RTLogQueryGroupSettings, "RTLogQueryGroupSettings", &aLogDbgGroups);
+}
+
+HRESULT MachineDebugger::getLogDbgDestinations(com::Utf8Str &aLogDbgDestinations)
+{
+ return i_logStringProps(RTLogGetDefaultInstance(), RTLogQueryDestinations, "RTLogQueryDestinations", &aLogDbgDestinations);
+}
+
+HRESULT MachineDebugger::getLogRelFlags(com::Utf8Str &aLogRelFlags)
+{
+ return i_logStringProps(RTLogRelGetDefaultInstance(), RTLogQueryFlags, "RTLogQueryFlags", &aLogRelFlags);
+}
+
+HRESULT MachineDebugger::getLogRelGroups(com::Utf8Str &aLogRelGroups)
+{
+ return i_logStringProps(RTLogRelGetDefaultInstance(), RTLogQueryGroupSettings, "RTLogQueryGroupSettings", &aLogRelGroups);
+}
+
+HRESULT MachineDebugger::getLogRelDestinations(com::Utf8Str &aLogRelDestinations)
+{
+ return i_logStringProps(RTLogRelGetDefaultInstance(), RTLogQueryDestinations, "RTLogQueryDestinations", &aLogRelDestinations);
+}
+
+/**
+ * Return the main execution engine of the VM.
+ *
+ * @returns COM status code
+ * @param apenmEngine Address of the result variable.
+ */
+HRESULT MachineDebugger::getExecutionEngine(VMExecutionEngine_T *apenmEngine)
+{
+ *apenmEngine = VMExecutionEngine_NotSet;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ {
+ uint8_t bEngine = UINT8_MAX;
+ int vrc = ptrVM.vtable()->pfnEMR3QueryMainExecutionEngine(ptrVM.rawUVM(), &bEngine);
+ if (RT_SUCCESS(vrc))
+ switch (bEngine)
+ {
+ case VM_EXEC_ENGINE_NOT_SET: *apenmEngine = VMExecutionEngine_NotSet; break;
+ case VM_EXEC_ENGINE_IEM: *apenmEngine = VMExecutionEngine_Emulated; break;
+ case VM_EXEC_ENGINE_HW_VIRT: *apenmEngine = VMExecutionEngine_HwVirt; break;
+ case VM_EXEC_ENGINE_NATIVE_API: *apenmEngine = VMExecutionEngine_NativeApi; break;
+ default: AssertMsgFailed(("bEngine=%d\n", bEngine));
+ }
+ }
+
+ return S_OK;
+}
+
+/**
+ * Returns the current nested paging flag.
+ *
+ * @returns COM status code
+ * @param aHWVirtExNestedPagingEnabled address of result variable
+ */
+HRESULT MachineDebugger::getHWVirtExNestedPagingEnabled(BOOL *aHWVirtExNestedPagingEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ *aHWVirtExNestedPagingEnabled = ptrVM.vtable()->pfnHMR3IsNestedPagingActive(ptrVM.rawUVM());
+ else
+ *aHWVirtExNestedPagingEnabled = false;
+
+ return S_OK;
+}
+
+/**
+ * Returns the current VPID flag.
+ *
+ * @returns COM status code
+ * @param aHWVirtExVPIDEnabled address of result variable
+ */
+HRESULT MachineDebugger::getHWVirtExVPIDEnabled(BOOL *aHWVirtExVPIDEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ *aHWVirtExVPIDEnabled = ptrVM.vtable()->pfnHMR3IsVpidActive(ptrVM.rawUVM());
+ else
+ *aHWVirtExVPIDEnabled = false;
+
+ return S_OK;
+}
+
+/**
+ * Returns the current unrestricted execution setting.
+ *
+ * @returns COM status code
+ * @param aHWVirtExUXEnabled address of result variable
+ */
+HRESULT MachineDebugger::getHWVirtExUXEnabled(BOOL *aHWVirtExUXEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ *aHWVirtExUXEnabled = ptrVM.vtable()->pfnHMR3IsUXActive(ptrVM.rawUVM());
+ else
+ *aHWVirtExUXEnabled = false;
+
+ return S_OK;
+}
+
+HRESULT MachineDebugger::getOSName(com::Utf8Str &aOSName)
+{
+ LogFlowThisFunc(("\n"));
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Do the job and try convert the name.
+ */
+ char szName[64];
+ int vrc = ptrVM.vtable()->pfnDBGFR3OSQueryNameAndVersion(ptrVM.rawUVM(), szName, sizeof(szName), NULL, 0);
+ if (RT_SUCCESS(vrc))
+ hrc = aOSName.assignEx(szName);
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("DBGFR3OSQueryNameAndVersion failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::getOSVersion(com::Utf8Str &aOSVersion)
+{
+ LogFlowThisFunc(("\n"));
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Do the job and try convert the name.
+ */
+ char szVersion[256];
+ int vrc = ptrVM.vtable()->pfnDBGFR3OSQueryNameAndVersion(ptrVM.rawUVM(), NULL, 0, szVersion, sizeof(szVersion));
+ if (RT_SUCCESS(vrc))
+ hrc = aOSVersion.assignEx(szVersion);
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("DBGFR3OSQueryNameAndVersion failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+/**
+ * Returns the current PAE flag.
+ *
+ * @returns COM status code
+ * @param aPAEEnabled address of result variable.
+ */
+HRESULT MachineDebugger::getPAEEnabled(BOOL *aPAEEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ {
+ uint32_t cr4;
+ int vrc = ptrVM.vtable()->pfnDBGFR3RegCpuQueryU32(ptrVM.rawUVM(), 0 /*idCpu*/, DBGFREG_CR4, &cr4); AssertRC(vrc);
+ *aPAEEnabled = RT_BOOL(cr4 & X86_CR4_PAE);
+ }
+ else
+ *aPAEEnabled = false;
+
+ return S_OK;
+}
+
+/**
+ * Returns the current virtual time rate.
+ *
+ * @returns COM status code.
+ * @param aVirtualTimeRate Where to store the rate.
+ */
+HRESULT MachineDebugger::getVirtualTimeRate(ULONG *aVirtualTimeRate)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ *aVirtualTimeRate = ptrVM.vtable()->pfnTMR3GetWarpDrive(ptrVM.rawUVM());
+
+ return hrc;
+}
+
+/**
+ * Set the virtual time rate.
+ *
+ * @returns COM status code.
+ * @param aVirtualTimeRate The new rate.
+ */
+HRESULT MachineDebugger::setVirtualTimeRate(ULONG aVirtualTimeRate)
+{
+ HRESULT hrc = S_OK;
+
+ if (aVirtualTimeRate < 2 || aVirtualTimeRate > 20000)
+ return setError(E_INVALIDARG, tr("%u is out of range [2..20000]"), aVirtualTimeRate);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (i_queueSettings())
+ mVirtualTimeRateQueued = aVirtualTimeRate;
+ else
+ {
+ Console::SafeVMPtr ptrVM(mParent);
+ hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnTMR3SetWarpDrive(ptrVM.rawUVM(), aVirtualTimeRate);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("TMR3SetWarpDrive(, %u) failed with rc=%Rrc"), aVirtualTimeRate, vrc);
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * Get the VM uptime in milliseconds.
+ *
+ * @returns COM status code
+ * @param aUptime Where to store the uptime.
+ */
+HRESULT MachineDebugger::getUptime(LONG64 *aUptime)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ *aUptime = (int64_t)ptrVM.vtable()->pfnTMR3TimeVirtGetMilli(ptrVM.rawUVM());
+
+ return hrc;
+}
+
+// IMachineDebugger methods
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT MachineDebugger::dumpGuestCore(const com::Utf8Str &aFilename, const com::Utf8Str &aCompression)
+{
+ if (aCompression.length())
+ return setError(E_INVALIDARG, tr("The compression parameter must be empty"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnDBGFR3CoreWrite(ptrVM.rawUVM(), aFilename.c_str(), false /*fReplaceFile*/);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3CoreWrite failed with %Rrc"), vrc);
+ }
+
+ return hrc;
+}
+
+HRESULT MachineDebugger::dumpHostProcessCore(const com::Utf8Str &aFilename, const com::Utf8Str &aCompression)
+{
+ RT_NOREF(aFilename, aCompression);
+ ReturnComNotImplemented();
+}
+
+/**
+ * Debug info string buffer formatter.
+ */
+typedef struct MACHINEDEBUGGERINOFHLP
+{
+ /** The core info helper structure. */
+ DBGFINFOHLP Core;
+ /** Pointer to the buffer. */
+ char *pszBuf;
+ /** The size of the buffer. */
+ size_t cbBuf;
+ /** The offset into the buffer */
+ size_t offBuf;
+ /** Indicates an out-of-memory condition. */
+ bool fOutOfMemory;
+} MACHINEDEBUGGERINOFHLP;
+/** Pointer to a Debug info string buffer formatter. */
+typedef MACHINEDEBUGGERINOFHLP *PMACHINEDEBUGGERINOFHLP;
+
+
+/**
+ * @callback_method_impl{FNRTSTROUTPUT}
+ */
+static DECLCALLBACK(size_t) MachineDebuggerInfoOutput(void *pvArg, const char *pachChars, size_t cbChars)
+{
+ PMACHINEDEBUGGERINOFHLP pHlp = (PMACHINEDEBUGGERINOFHLP)pvArg;
+
+ /*
+ * Grow the buffer if required.
+ */
+ size_t const cbRequired = cbChars + pHlp->offBuf + 1;
+ if (cbRequired > pHlp->cbBuf)
+ {
+ if (RT_UNLIKELY(pHlp->fOutOfMemory))
+ return 0;
+
+ size_t cbBufNew = pHlp->cbBuf * 2;
+ if (cbRequired > cbBufNew)
+ cbBufNew = RT_ALIGN_Z(cbRequired, 256);
+ void *pvBufNew = RTMemRealloc(pHlp->pszBuf, cbBufNew);
+ if (RT_UNLIKELY(!pvBufNew))
+ {
+ pHlp->fOutOfMemory = true;
+ RTMemFree(pHlp->pszBuf);
+ pHlp->pszBuf = NULL;
+ pHlp->cbBuf = 0;
+ pHlp->offBuf = 0;
+ return 0;
+ }
+
+ pHlp->pszBuf = (char *)pvBufNew;
+ pHlp->cbBuf = cbBufNew;
+ }
+
+ /*
+ * Copy the bytes into the buffer and terminate it.
+ */
+ if (cbChars)
+ {
+ memcpy(&pHlp->pszBuf[pHlp->offBuf], pachChars, cbChars);
+ pHlp->offBuf += cbChars;
+ }
+ pHlp->pszBuf[pHlp->offBuf] = '\0';
+ Assert(pHlp->offBuf < pHlp->cbBuf);
+ return cbChars;
+}
+
+/**
+ * @interface_method_impl{DBGFINFOHLP,pfnPrintfV}
+ */
+static DECLCALLBACK(void) MachineDebuggerInfoPrintfV(PCDBGFINFOHLP pHlp, const char *pszFormat, va_list args)
+{
+ RTStrFormatV(MachineDebuggerInfoOutput, (void *)pHlp, NULL, NULL, pszFormat, args);
+}
+
+/**
+ * @interface_method_impl{DBGFINFOHLP,pfnPrintf}
+ */
+static DECLCALLBACK(void) MachineDebuggerInfoPrintf(PCDBGFINFOHLP pHlp, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ MachineDebuggerInfoPrintfV(pHlp, pszFormat, va);
+ va_end(va);
+}
+
+/**
+ * Initializes the debug info string buffer formatter
+ *
+ * @param pHlp The help structure to init.
+ * @param pVMM The VMM vtable.
+ */
+static void MachineDebuggerInfoInit(PMACHINEDEBUGGERINOFHLP pHlp, PCVMMR3VTABLE pVMM)
+{
+ pHlp->Core.pfnPrintf = MachineDebuggerInfoPrintf;
+ pHlp->Core.pfnPrintfV = MachineDebuggerInfoPrintfV;
+ pHlp->Core.pfnGetOptError = pVMM->pfnDBGFR3InfoGenericGetOptError;
+ pHlp->pszBuf = NULL;
+ pHlp->cbBuf = 0;
+ pHlp->offBuf = 0;
+ pHlp->fOutOfMemory = false;
+}
+
+/**
+ * Deletes the debug info string buffer formatter.
+ * @param pHlp The helper structure to delete.
+ */
+static void MachineDebuggerInfoDelete(PMACHINEDEBUGGERINOFHLP pHlp)
+{
+ RTMemFree(pHlp->pszBuf);
+ pHlp->pszBuf = NULL;
+}
+
+HRESULT MachineDebugger::info(const com::Utf8Str &aName, const com::Utf8Str &aArgs, com::Utf8Str &aInfo)
+{
+ LogFlowThisFunc(("\n"));
+
+ /*
+ * Do the autocaller and lock bits.
+ */
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Create a helper and call DBGFR3Info.
+ */
+ MACHINEDEBUGGERINOFHLP Hlp;
+ MachineDebuggerInfoInit(&Hlp, ptrVM.vtable());
+ int vrc = ptrVM.vtable()->pfnDBGFR3Info(ptrVM.rawUVM(), aName.c_str(), aArgs.c_str(), &Hlp.Core);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!Hlp.fOutOfMemory)
+ hrc = aInfo.assignEx(Hlp.pszBuf);
+ else
+ hrc = E_OUTOFMEMORY;
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("DBGFR3Info failed with %Rrc"), vrc);
+ MachineDebuggerInfoDelete(&Hlp);
+ }
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::injectNMI()
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnDBGFR3InjectNMI(ptrVM.rawUVM(), 0);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3InjectNMI failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::modifyLogFlags(const com::Utf8Str &aSettings)
+{
+ LogFlowThisFunc(("aSettings=%s\n", aSettings.c_str()));
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnDBGFR3LogModifyFlags(ptrVM.rawUVM(), aSettings.c_str());
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3LogModifyFlags failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::modifyLogGroups(const com::Utf8Str &aSettings)
+{
+ LogFlowThisFunc(("aSettings=%s\n", aSettings.c_str()));
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnDBGFR3LogModifyGroups(ptrVM.rawUVM(), aSettings.c_str());
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3LogModifyGroups failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::modifyLogDestinations(const com::Utf8Str &aSettings)
+{
+ LogFlowThisFunc(("aSettings=%s\n", aSettings.c_str()));
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnDBGFR3LogModifyDestinations(ptrVM.rawUVM(), aSettings.c_str());
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3LogModifyDestinations failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::readPhysicalMemory(LONG64 aAddress, ULONG aSize, std::vector<BYTE> &aBytes)
+{
+ RT_NOREF(aAddress, aSize, aBytes);
+ ReturnComNotImplemented();
+}
+
+HRESULT MachineDebugger::writePhysicalMemory(LONG64 aAddress, ULONG aSize, const std::vector<BYTE> &aBytes)
+{
+ RT_NOREF(aAddress, aSize, aBytes);
+ ReturnComNotImplemented();
+}
+
+HRESULT MachineDebugger::readVirtualMemory(ULONG aCpuId, LONG64 aAddress, ULONG aSize, std::vector<BYTE> &aBytes)
+{
+ RT_NOREF(aCpuId, aAddress, aSize, aBytes);
+ ReturnComNotImplemented();
+}
+
+HRESULT MachineDebugger::writeVirtualMemory(ULONG aCpuId, LONG64 aAddress, ULONG aSize, const std::vector<BYTE> &aBytes)
+{
+ RT_NOREF(aCpuId, aAddress, aSize, aBytes);
+ ReturnComNotImplemented();
+}
+
+HRESULT MachineDebugger::loadPlugIn(const com::Utf8Str &aName, com::Utf8Str &aPlugInName)
+{
+ /*
+ * Lock the debugger and get the VM pointer
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Do the job and try convert the name.
+ */
+ if (aName.equals("all"))
+ {
+ ptrVM.vtable()->pfnDBGFR3PlugInLoadAll(ptrVM.rawUVM());
+ hrc = aPlugInName.assignEx("all");
+ }
+ else
+ {
+ RTERRINFOSTATIC ErrInfo;
+ char szName[80];
+ int vrc = ptrVM.vtable()->pfnDBGFR3PlugInLoad(ptrVM.rawUVM(), aName.c_str(), szName, sizeof(szName), RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(vrc))
+ hrc = aPlugInName.assignEx(szName);
+ else
+ hrc = setErrorVrc(vrc, "%s", ErrInfo.szMsg);
+ }
+ }
+ return hrc;
+
+}
+
+HRESULT MachineDebugger::unloadPlugIn(const com::Utf8Str &aName)
+{
+ /*
+ * Lock the debugger and get the VM pointer
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Do the job and try convert the name.
+ */
+ if (aName.equals("all"))
+ {
+ ptrVM.vtable()->pfnDBGFR3PlugInUnloadAll(ptrVM.rawUVM());
+ hrc = S_OK;
+ }
+ else
+ {
+ int vrc = ptrVM.vtable()->pfnDBGFR3PlugInUnload(ptrVM.rawUVM(), aName.c_str());
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else if (vrc == VERR_NOT_FOUND)
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Plug-in '%s' was not found"), aName.c_str());
+ else
+ hrc = setErrorVrc(vrc, tr("Error unloading '%s': %Rrc"), aName.c_str(), vrc);
+ }
+ }
+ return hrc;
+
+}
+
+HRESULT MachineDebugger::detectOS(com::Utf8Str &aOs)
+{
+ LogFlowThisFunc(("\n"));
+
+ /*
+ * Lock the debugger and get the VM pointer
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Do the job.
+ */
+ char szName[64];
+ int vrc = ptrVM.vtable()->pfnDBGFR3OSDetect(ptrVM.rawUVM(), szName, sizeof(szName));
+ if (RT_SUCCESS(vrc) && vrc != VINF_DBGF_OS_NOT_DETCTED)
+ hrc = aOs.assignEx(szName);
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("DBGFR3OSDetect failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::queryOSKernelLog(ULONG aMaxMessages, com::Utf8Str &aDmesg)
+{
+ /*
+ * Lock the debugger and get the VM pointer
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ PDBGFOSIDMESG pDmesg = (PDBGFOSIDMESG)ptrVM.vtable()->pfnDBGFR3OSQueryInterface(ptrVM.rawUVM(), DBGFOSINTERFACE_DMESG);
+ if (pDmesg)
+ {
+ size_t cbActual;
+ size_t cbBuf = _512K;
+ int vrc = aDmesg.reserveNoThrow(cbBuf);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t cMessages = aMaxMessages == 0 ? UINT32_MAX : aMaxMessages;
+ vrc = pDmesg->pfnQueryKernelLog(pDmesg, ptrVM.rawUVM(), ptrVM.vtable(), 0 /*fFlags*/, cMessages,
+ aDmesg.mutableRaw(), cbBuf, &cbActual);
+
+ uint32_t cTries = 10;
+ while (vrc == VERR_BUFFER_OVERFLOW && cbBuf < 16*_1M && cTries-- > 0)
+ {
+ cbBuf = RT_ALIGN_Z(cbActual + _4K, _4K);
+ vrc = aDmesg.reserveNoThrow(cbBuf);
+ if (RT_SUCCESS(vrc))
+ vrc = pDmesg->pfnQueryKernelLog(pDmesg, ptrVM.rawUVM(), ptrVM.vtable(), 0 /*fFlags*/, cMessages,
+ aDmesg.mutableRaw(), cbBuf, &cbActual);
+ }
+ if (RT_SUCCESS(vrc))
+ aDmesg.jolt();
+ else if (vrc == VERR_BUFFER_OVERFLOW)
+ hrc = setError(E_FAIL, tr("Too much log available, must use the maxMessages parameter to restrict."));
+ else
+ hrc = setErrorVrc(vrc);
+ }
+ else
+ hrc = setErrorBoth(E_OUTOFMEMORY, vrc);
+ }
+ else
+ hrc = setError(E_FAIL, tr("The dmesg interface isn't implemented by guest OS digger, or detectOS() has not been called."));
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::getRegister(ULONG aCpuId, const com::Utf8Str &aName, com::Utf8Str &aValue)
+{
+ /*
+ * The prologue.
+ */
+ LogFlowThisFunc(("\n"));
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Real work.
+ */
+ DBGFREGVAL Value;
+ DBGFREGVALTYPE enmType;
+ int vrc = ptrVM.vtable()->pfnDBGFR3RegNmQuery(ptrVM.rawUVM(), aCpuId, aName.c_str(), &Value, &enmType);
+ if (RT_SUCCESS(vrc))
+ {
+ char szHex[160];
+ ssize_t cch = ptrVM.vtable()->pfnDBGFR3RegFormatValue(szHex, sizeof(szHex), &Value, enmType, true /*fSpecial*/);
+ if (cch > 0)
+ hrc = aValue.assignEx(szHex);
+ else
+ hrc = E_UNEXPECTED;
+ }
+ else if (vrc == VERR_DBGF_REGISTER_NOT_FOUND)
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Register '%s' was not found"), aName.c_str());
+ else if (vrc == VERR_INVALID_CPU_ID)
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Invalid CPU ID: %u"), aCpuId);
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc,
+ tr("DBGFR3RegNmQuery failed with rc=%Rrc querying register '%s' with default cpu set to %u"),
+ vrc, aName.c_str(), aCpuId);
+ }
+
+ return hrc;
+}
+
+HRESULT MachineDebugger::getRegisters(ULONG aCpuId, std::vector<com::Utf8Str> &aNames, std::vector<com::Utf8Str> &aValues)
+{
+ RT_NOREF(aCpuId); /** @todo fix missing aCpuId usage! */
+
+ /*
+ * The prologue.
+ */
+ LogFlowThisFunc(("\n"));
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Real work.
+ */
+ size_t cRegs;
+ int vrc = ptrVM.vtable()->pfnDBGFR3RegNmQueryAllCount(ptrVM.rawUVM(), &cRegs);
+ if (RT_SUCCESS(vrc))
+ {
+ PDBGFREGENTRYNM paRegs = (PDBGFREGENTRYNM)RTMemAllocZ(sizeof(paRegs[0]) * cRegs);
+ if (paRegs)
+ {
+ vrc = ptrVM.vtable()->pfnDBGFR3RegNmQueryAll(ptrVM.rawUVM(), paRegs, cRegs);
+ if (RT_SUCCESS(vrc))
+ {
+ try
+ {
+ aValues.resize(cRegs);
+ aNames.resize(cRegs);
+ for (uint32_t iReg = 0; iReg < cRegs; iReg++)
+ {
+ char szHex[160];
+ szHex[159] = szHex[0] = '\0';
+ ssize_t cch = ptrVM.vtable()->pfnDBGFR3RegFormatValue(szHex, sizeof(szHex), &paRegs[iReg].Val,
+ paRegs[iReg].enmType, true /*fSpecial*/);
+ Assert(cch > 0); NOREF(cch);
+ aNames[iReg] = paRegs[iReg].pszName;
+ aValues[iReg] = szHex;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3RegNmQueryAll failed with %Rrc"), vrc);
+
+ RTMemFree(paRegs);
+ }
+ else
+ hrc = E_OUTOFMEMORY;
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3RegNmQueryAllCount failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::setRegister(ULONG aCpuId, const com::Utf8Str &aName, const com::Utf8Str &aValue)
+{
+ RT_NOREF(aCpuId, aName, aValue);
+ ReturnComNotImplemented();
+}
+
+HRESULT MachineDebugger::setRegisters(ULONG aCpuId, const std::vector<com::Utf8Str> &aNames,
+ const std::vector<com::Utf8Str> &aValues)
+{
+ RT_NOREF(aCpuId, aNames, aValues);
+ ReturnComNotImplemented();
+}
+
+HRESULT MachineDebugger::dumpGuestStack(ULONG aCpuId, com::Utf8Str &aStack)
+{
+ /*
+ * The prologue.
+ */
+ LogFlowThisFunc(("\n"));
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * There is currently a problem with the windows diggers and SMP, where
+ * guest driver memory is being read from CPU zero in order to ensure that
+ * we've got a consisten virtual memory view. If one of the other CPUs
+ * initiates a rendezvous while we're unwinding the stack and trying to
+ * read guest driver memory, we will deadlock.
+ *
+ * So, check the VM state and maybe suspend the VM before we continue.
+ */
+ int vrc = VINF_SUCCESS;
+ bool fPaused = false;
+ if (aCpuId != 0)
+ {
+ VMSTATE enmVmState = ptrVM.vtable()->pfnVMR3GetStateU(ptrVM.rawUVM());
+ if ( enmVmState == VMSTATE_RUNNING
+ || enmVmState == VMSTATE_RUNNING_LS)
+ {
+ alock.release();
+ vrc = ptrVM.vtable()->pfnVMR3Suspend(ptrVM.rawUVM(), VMSUSPENDREASON_USER);
+ alock.acquire();
+ fPaused = RT_SUCCESS(vrc);
+ }
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ PCDBGFSTACKFRAME pFirstFrame;
+ vrc = ptrVM.vtable()->pfnDBGFR3StackWalkBegin(ptrVM.rawUVM(), aCpuId, DBGFCODETYPE_GUEST, &pFirstFrame);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Print header.
+ */
+ try
+ {
+ uint32_t fBitFlags = 0;
+ for (PCDBGFSTACKFRAME pFrame = pFirstFrame;
+ pFrame;
+ pFrame = ptrVM.vtable()->pfnDBGFR3StackWalkNext(pFrame))
+ {
+ uint32_t const fCurBitFlags = pFrame->fFlags & (DBGFSTACKFRAME_FLAGS_16BIT | DBGFSTACKFRAME_FLAGS_32BIT | DBGFSTACKFRAME_FLAGS_64BIT);
+ if (fCurBitFlags & DBGFSTACKFRAME_FLAGS_16BIT)
+ {
+ if (fCurBitFlags != fBitFlags)
+ aStack.append("SS:BP Ret SS:BP Ret CS:EIP Arg0 Arg1 Arg2 Arg3 CS:EIP / Symbol [line]\n");
+ aStack.appendPrintf("%04RX16:%04RX16 %04RX16:%04RX16 %04RX32:%08RX32 %08RX32 %08RX32 %08RX32 %08RX32",
+ pFrame->AddrFrame.Sel,
+ (uint16_t)pFrame->AddrFrame.off,
+ pFrame->AddrReturnFrame.Sel,
+ (uint16_t)pFrame->AddrReturnFrame.off,
+ (uint32_t)pFrame->AddrReturnPC.Sel,
+ (uint32_t)pFrame->AddrReturnPC.off,
+ pFrame->Args.au32[0],
+ pFrame->Args.au32[1],
+ pFrame->Args.au32[2],
+ pFrame->Args.au32[3]);
+ }
+ else if (fCurBitFlags & DBGFSTACKFRAME_FLAGS_32BIT)
+ {
+ if (fCurBitFlags != fBitFlags)
+ aStack.append("EBP Ret EBP Ret CS:EIP Arg0 Arg1 Arg2 Arg3 CS:EIP / Symbol [line]\n");
+ aStack.appendPrintf("%08RX32 %08RX32 %04RX32:%08RX32 %08RX32 %08RX32 %08RX32 %08RX32",
+ (uint32_t)pFrame->AddrFrame.off,
+ (uint32_t)pFrame->AddrReturnFrame.off,
+ (uint32_t)pFrame->AddrReturnPC.Sel,
+ (uint32_t)pFrame->AddrReturnPC.off,
+ pFrame->Args.au32[0],
+ pFrame->Args.au32[1],
+ pFrame->Args.au32[2],
+ pFrame->Args.au32[3]);
+ }
+ else if (fCurBitFlags & DBGFSTACKFRAME_FLAGS_64BIT)
+ {
+ if (fCurBitFlags != fBitFlags)
+ aStack.append("RBP Ret SS:RBP Ret RIP CS:RIP / Symbol [line]\n");
+ aStack.appendPrintf("%016RX64 %04RX16:%016RX64 %016RX64",
+ (uint64_t)pFrame->AddrFrame.off,
+ pFrame->AddrReturnFrame.Sel,
+ (uint64_t)pFrame->AddrReturnFrame.off,
+ (uint64_t)pFrame->AddrReturnPC.off);
+ }
+
+ if (!pFrame->pSymPC)
+ aStack.appendPrintf(fCurBitFlags & DBGFSTACKFRAME_FLAGS_64BIT
+ ? " %RTsel:%016RGv"
+ : fCurBitFlags & DBGFSTACKFRAME_FLAGS_32BIT
+ ? " %RTsel:%08RGv"
+ : " %RTsel:%04RGv"
+ , pFrame->AddrPC.Sel, pFrame->AddrPC.off);
+ else
+ {
+ RTGCINTPTR offDisp = pFrame->AddrPC.FlatPtr - pFrame->pSymPC->Value; /** @todo this isn't 100% correct for segmented stuff. */
+ if (offDisp > 0)
+ aStack.appendPrintf(" %s+%llx", pFrame->pSymPC->szName, (int64_t)offDisp);
+ else if (offDisp < 0)
+ aStack.appendPrintf(" %s-%llx", pFrame->pSymPC->szName, -(int64_t)offDisp);
+ else
+ aStack.appendPrintf(" %s", pFrame->pSymPC->szName);
+ }
+ if (pFrame->pLinePC)
+ aStack.appendPrintf(" [%s @ 0i%d]", pFrame->pLinePC->szFilename, pFrame->pLinePC->uLineNo);
+ aStack.append("\n");
+
+ fBitFlags = fCurBitFlags;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+
+ ptrVM.vtable()->pfnDBGFR3StackWalkEnd(pFirstFrame);
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3StackWalkBegin failed with %Rrc"), vrc);
+
+ /*
+ * Resume the VM if we suspended it.
+ */
+ if (fPaused)
+ {
+ alock.release();
+ ptrVM.vtable()->pfnVMR3Resume(ptrVM.rawUVM(), VMRESUMEREASON_USER);
+ }
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Suspending the VM failed with %Rrc\n"), vrc);
+ }
+
+ return hrc;
+}
+
+/**
+ * Resets VM statistics.
+ *
+ * @returns COM status code.
+ * @param aPattern The selection pattern. A bit similar to filename globbing.
+ */
+HRESULT MachineDebugger::resetStats(const com::Utf8Str &aPattern)
+{
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Machine is not running"));
+
+ ptrVM.vtable()->pfnSTAMR3Reset(ptrVM.rawUVM(), aPattern.c_str());
+
+ return S_OK;
+}
+
+/**
+ * Dumps VM statistics to the log.
+ *
+ * @returns COM status code.
+ * @param aPattern The selection pattern. A bit similar to filename globbing.
+ */
+HRESULT MachineDebugger::dumpStats(const com::Utf8Str &aPattern)
+{
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Machine is not running"));
+
+ ptrVM.vtable()->pfnSTAMR3Dump(ptrVM.rawUVM(), aPattern.c_str());
+
+ return S_OK;
+}
+
+/**
+ * Get the VM statistics in an XML format.
+ *
+ * @returns COM status code.
+ * @param aPattern The selection pattern. A bit similar to filename globbing.
+ * @param aWithDescriptions Whether to include the descriptions.
+ * @param aStats The XML document containing the statistics.
+ */
+HRESULT MachineDebugger::getStats(const com::Utf8Str &aPattern, BOOL aWithDescriptions, com::Utf8Str &aStats)
+{
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Machine is not running"));
+
+ char *pszSnapshot;
+ int vrc = ptrVM.vtable()->pfnSTAMR3Snapshot(ptrVM.rawUVM(), aPattern.c_str(), &pszSnapshot, NULL, !!aWithDescriptions);
+ if (RT_FAILURE(vrc))
+ return vrc == VERR_NO_MEMORY ? E_OUTOFMEMORY : E_FAIL;
+
+ /** @todo this is horribly inefficient! And it's kinda difficult to tell whether it failed...
+ * Must use UTF-8 or ASCII here and completely avoid these two extra copy operations.
+ * Until that's done, this method is kind of useless for debugger statistics GUI because
+ * of the amount statistics in a debug build. */
+ HRESULT hrc = aStats.assignEx(pszSnapshot);
+ ptrVM.vtable()->pfnSTAMR3SnapshotFree(ptrVM.rawUVM(), pszSnapshot);
+
+ return hrc;
+}
+
+
+/** Wrapper around TMR3GetCpuLoadPercents. */
+HRESULT MachineDebugger::getCPULoad(ULONG aCpuId, ULONG *aPctExecuting, ULONG *aPctHalted, ULONG *aPctOther, LONG64 *aMsInterval)
+{
+ HRESULT hrc;
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ {
+ uint8_t uPctExecuting = 0;
+ uint8_t uPctHalted = 0;
+ uint8_t uPctOther = 0;
+ uint64_t msInterval = 0;
+ int vrc = ptrVM.vtable()->pfnTMR3GetCpuLoadPercents(ptrVM.rawUVM(), aCpuId >= UINT32_MAX / 2 ? VMCPUID_ALL : aCpuId,
+ &msInterval, &uPctExecuting, &uPctHalted, &uPctOther);
+ if (RT_SUCCESS(vrc))
+ {
+ *aPctExecuting = uPctExecuting;
+ *aPctHalted = uPctHalted;
+ *aPctOther = uPctOther;
+ *aMsInterval = msInterval;
+ hrc = S_OK;
+ }
+ else
+ hrc = setErrorVrc(vrc);
+ }
+ else
+ hrc = setError(VBOX_E_INVALID_VM_STATE, tr("Machine is not running"));
+ return hrc;
+}
+
+
+HRESULT MachineDebugger::takeGuestSample(const com::Utf8Str &aFilename, ULONG aUsInterval, LONG64 aUsSampleTime, ComPtr<IProgress> &pProgress)
+{
+ /*
+ * The prologue.
+ */
+ LogFlowThisFunc(("\n"));
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ if (!m_hSampleReport)
+ {
+ m_strFilename = aFilename;
+
+ int vrc = ptrVM.vtable()->pfnDBGFR3SampleReportCreate(ptrVM.rawUVM(), aUsInterval,
+ DBGF_SAMPLE_REPORT_F_STACK_REVERSE, &m_hSampleReport);
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = m_Progress.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = m_Progress->init(static_cast<IMachineDebugger*>(this),
+ tr("Creating guest sample report..."),
+ TRUE /* aCancelable */);
+ if (SUCCEEDED(hrc))
+ {
+ vrc = ptrVM.vtable()->pfnDBGFR3SampleReportStart(m_hSampleReport, aUsSampleTime, i_dbgfProgressCallback,
+ static_cast<MachineDebugger*>(this));
+ if (RT_SUCCESS(vrc))
+ hrc = m_Progress.queryInterfaceTo(pProgress.asOutParam());
+ else
+ hrc = setErrorVrc(vrc);
+ }
+ }
+
+ if (FAILED(hrc))
+ {
+ ptrVM.vtable()->pfnDBGFR3SampleReportRelease(m_hSampleReport);
+ m_hSampleReport = NULL;
+ }
+ }
+ else
+ hrc = setErrorVrc(vrc);
+ }
+ else
+ hrc = setError(VBOX_E_INVALID_VM_STATE, tr("A sample report is already in progress"));
+ }
+
+ return hrc;
+}
+
+/**
+ * Hack for getting the user mode VM handle (UVM) and VMM function table.
+ *
+ * @returns COM status code
+ * @param aMagicVersion The VMMR3VTABLE_MAGIC_VERSION value of the
+ * caller so we can check that the function table
+ * is compatible. (Otherwise, the caller can't
+ * safely release the UVM reference.)
+ * @param aUVM Where to store the vm handle. Since there is no
+ * uintptr_t in COM, we're using the max integer. (No,
+ * ULONG is not pointer sized!)
+ * @param aVMMFunctionTable Where to store the vm handle.
+ *
+ * @remarks The returned handle must be passed to VMR3ReleaseUVM()!
+ */
+HRESULT MachineDebugger::getUVMAndVMMFunctionTable(LONG64 aMagicVersion, LONG64 *aVMMFunctionTable, LONG64 *aUVM)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Make sure it is a local call.
+ */
+ RTTHREAD hThread = RTThreadSelf();
+ if (hThread != NIL_RTTHREAD)
+ {
+ const char *pszName = RTThreadGetName(hThread);
+ if ( !RTStrStartsWith(pszName, "ALIEN-") /* COM worker threads are aliens */
+ && !RTStrStartsWith(pszName, "nspr-") /* XPCOM worker threads are nspr-X */ )
+ {
+ /*
+ * Use safe VM pointer to get both the UVM and VMM function table.
+ */
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.rc();
+ if (SUCCEEDED(hrc))
+ {
+ if (VMMR3VTABLE_IS_COMPATIBLE_EX(ptrVM.vtable()->uMagicVersion, (uint64_t)aMagicVersion))
+ {
+ ptrVM.vtable()->pfnVMR3RetainUVM(ptrVM.rawUVM());
+ *aUVM = (intptr_t)ptrVM.rawUVM();
+ *aVMMFunctionTable = (intptr_t)ptrVM.vtable();
+ hrc = S_OK;
+ }
+ else
+ hrc = setError(E_FAIL, tr("Incompatible VMM function table: %RX64 vs %RX64 (caller)"),
+ ptrVM.vtable()->uMagicVersion, (uint64_t)aMagicVersion);
+ }
+ return hrc;
+ }
+ }
+
+ return setError(E_ACCESSDENIED, tr("The method getUVMAndVMMFunctionTable is only for local calls"));
+}
+
+
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+void MachineDebugger::i_flushQueuedSettings()
+{
+ mFlushMode = true;
+ if (mSingleStepQueued != -1)
+ {
+ COMSETTER(SingleStep)(mSingleStepQueued);
+ mSingleStepQueued = -1;
+ }
+ for (unsigned i = 0; i < EMEXECPOLICY_END; i++)
+ if (maiQueuedEmExecPolicyParams[i] != UINT8_MAX)
+ {
+ i_setEmExecPolicyProperty((EMEXECPOLICY)i, RT_BOOL(maiQueuedEmExecPolicyParams[i]));
+ maiQueuedEmExecPolicyParams[i] = UINT8_MAX;
+ }
+ if (mLogEnabledQueued != -1)
+ {
+ COMSETTER(LogEnabled)(mLogEnabledQueued);
+ mLogEnabledQueued = -1;
+ }
+ if (mVirtualTimeRateQueued != UINT32_MAX)
+ {
+ COMSETTER(VirtualTimeRate)(mVirtualTimeRateQueued);
+ mVirtualTimeRateQueued = UINT32_MAX;
+ }
+ mFlushMode = false;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+bool MachineDebugger::i_queueSettings() const
+{
+ if (!mFlushMode)
+ {
+ // check if the machine is running
+ MachineState_T machineState;
+ mParent->COMGETTER(State)(&machineState);
+ switch (machineState)
+ {
+ // queue the request
+ default:
+ return true;
+
+ case MachineState_Running:
+ case MachineState_Paused:
+ case MachineState_Stuck:
+ case MachineState_LiveSnapshotting:
+ case MachineState_Teleporting:
+ break;
+ }
+ }
+ return false;
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/Makefile.kup b/src/VBox/Main/src-client/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-client/Makefile.kup
diff --git a/src/VBox/Main/src-client/MouseImpl.cpp b/src/VBox/Main/src-client/MouseImpl.cpp
new file mode 100644
index 00000000..fb39e093
--- /dev/null
+++ b/src/VBox/Main/src-client/MouseImpl.cpp
@@ -0,0 +1,1404 @@
+/* $Id: MouseImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_MOUSE
+#include "LoggingNew.h"
+
+#include <iprt/cpp/utils.h>
+
+#include "MouseImpl.h"
+#include "DisplayImpl.h"
+#include "VMMDev.h"
+#include "MousePointerShapeWrap.h"
+#include "VBoxEvents.h"
+
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/VMMDev.h>
+#include <VBox/err.h>
+
+
+class ATL_NO_VTABLE MousePointerShape:
+ public MousePointerShapeWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(MousePointerShape)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ /* Public initializer/uninitializer for internal purposes only. */
+ HRESULT init(ComObjPtr<Mouse> pMouse,
+ bool fVisible, bool fAlpha,
+ uint32_t hotX, uint32_t hotY,
+ uint32_t width, uint32_t height,
+ const uint8_t *pu8Shape, uint32_t cbShape);
+ void uninit();
+
+private:
+ // wrapped IMousePointerShape properties
+ virtual HRESULT getVisible(BOOL *aVisible);
+ virtual HRESULT getAlpha(BOOL *aAlpha);
+ virtual HRESULT getHotX(ULONG *aHotX);
+ virtual HRESULT getHotY(ULONG *aHotY);
+ virtual HRESULT getWidth(ULONG *aWidth);
+ virtual HRESULT getHeight(ULONG *aHeight);
+ virtual HRESULT getShape(std::vector<BYTE> &aShape);
+
+ struct Data
+ {
+ ComObjPtr<Mouse> pMouse;
+ bool fVisible;
+ bool fAlpha;
+ uint32_t hotX;
+ uint32_t hotY;
+ uint32_t width;
+ uint32_t height;
+ std::vector<BYTE> shape;
+ };
+
+ Data m;
+};
+
+/*
+ * MousePointerShape implementation.
+ */
+DEFINE_EMPTY_CTOR_DTOR(MousePointerShape)
+
+HRESULT MousePointerShape::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void MousePointerShape::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+HRESULT MousePointerShape::init(ComObjPtr<Mouse> pMouse,
+ bool fVisible, bool fAlpha,
+ uint32_t hotX, uint32_t hotY,
+ uint32_t width, uint32_t height,
+ const uint8_t *pu8Shape, uint32_t cbShape)
+{
+ LogFlowThisFunc(("v %d, a %d, h %d,%d, %dx%d, cb %d\n",
+ fVisible, fAlpha, hotX, hotY, width, height, cbShape));
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m.pMouse = pMouse;
+ m.fVisible = fVisible;
+ m.fAlpha = fAlpha;
+ m.hotX = hotX;
+ m.hotY = hotY;
+ m.width = width;
+ m.height = height;
+ m.shape.resize(cbShape);
+ if (cbShape)
+ {
+ memcpy(&m.shape.front(), pu8Shape, cbShape);
+ }
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+void MousePointerShape::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m.pMouse.setNull();
+}
+
+HRESULT MousePointerShape::getVisible(BOOL *aVisible)
+{
+ *aVisible = m.fVisible;
+ return S_OK;
+}
+
+HRESULT MousePointerShape::getAlpha(BOOL *aAlpha)
+{
+ *aAlpha = m.fAlpha;
+ return S_OK;
+}
+
+HRESULT MousePointerShape::getHotX(ULONG *aHotX)
+{
+ *aHotX = m.hotX;
+ return S_OK;
+}
+
+HRESULT MousePointerShape::getHotY(ULONG *aHotY)
+{
+ *aHotY = m.hotY;
+ return S_OK;
+}
+
+HRESULT MousePointerShape::getWidth(ULONG *aWidth)
+{
+ *aWidth = m.width;
+ return S_OK;
+}
+
+HRESULT MousePointerShape::getHeight(ULONG *aHeight)
+{
+ *aHeight = m.height;
+ return S_OK;
+}
+
+HRESULT MousePointerShape::getShape(std::vector<BYTE> &aShape)
+{
+ aShape.resize(m.shape.size());
+ if (m.shape.size())
+ memcpy(&aShape.front(), &m.shape.front(), aShape.size());
+ return S_OK;
+}
+
+
+/** @name Mouse device capabilities bitfield
+ * @{ */
+enum
+{
+ /** The mouse device can do relative reporting */
+ MOUSE_DEVCAP_RELATIVE = 1,
+ /** The mouse device can do absolute reporting */
+ MOUSE_DEVCAP_ABSOLUTE = 2,
+ /** The mouse device can do absolute multi-touch reporting */
+ MOUSE_DEVCAP_MT_ABSOLUTE = 4,
+ /** The mouse device can do relative multi-touch reporting */
+ MOUSE_DEVCAP_MT_RELATIVE = 8,
+};
+/** @} */
+
+
+/**
+ * Mouse driver instance data.
+ */
+struct DRVMAINMOUSE
+{
+ /** Pointer to the mouse object. */
+ Mouse *pMouse;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the mouse port interface of the driver/device above us. */
+ PPDMIMOUSEPORT pUpPort;
+ /** Our mouse connector interface. */
+ PDMIMOUSECONNECTOR IConnector;
+ /** The capabilities of this device. */
+ uint32_t u32DevCaps;
+};
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+Mouse::Mouse()
+ : mParent(NULL)
+{
+}
+
+Mouse::~Mouse()
+{
+}
+
+
+HRESULT Mouse::FinalConstruct()
+{
+ RT_ZERO(mpDrv);
+ RT_ZERO(mPointerData);
+ mcLastX = 0x8000;
+ mcLastY = 0x8000;
+ mfLastButtons = 0;
+ mfVMMDevGuestCaps = 0;
+ return BaseFinalConstruct();
+}
+
+void Mouse::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the mouse object.
+ *
+ * @returns COM result indicator
+ * @param parent handle of our parent object
+ */
+HRESULT Mouse::init (ConsoleMouseInterface *parent)
+{
+ LogFlowThisFunc(("\n"));
+
+ ComAssertRet(parent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = parent;
+
+ unconst(mEventSource).createObject();
+ HRESULT hrc = mEventSource->init();
+ AssertComRCReturnRC(hrc);
+
+ ComPtr<IEvent> ptrEvent;
+ hrc = ::CreateGuestMouseEvent(ptrEvent.asOutParam(), mEventSource,
+ (GuestMouseEventMode_T)0, 0 /*x*/, 0 /*y*/, 0 /*z*/, 0 /*w*/, 0 /*buttons*/);
+ AssertComRCReturnRC(hrc);
+ mMouseEvent.init(ptrEvent, mEventSource);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void Mouse::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ for (unsigned i = 0; i < MOUSE_MAX_DEVICES; ++i)
+ {
+ if (mpDrv[i])
+ mpDrv[i]->pMouse = NULL;
+ mpDrv[i] = NULL;
+ }
+
+ mPointerShape.setNull();
+
+ RTMemFree(mPointerData.pu8Shape);
+ mPointerData.pu8Shape = NULL;
+ mPointerData.cbShape = 0;
+
+ mMouseEvent.uninit();
+ unconst(mEventSource).setNull();
+ unconst(mParent) = NULL;
+}
+
+void Mouse::updateMousePointerShape(bool fVisible, bool fAlpha,
+ uint32_t hotX, uint32_t hotY,
+ uint32_t width, uint32_t height,
+ const uint8_t *pu8Shape, uint32_t cbShape)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ RTMemFree(mPointerData.pu8Shape);
+ mPointerData.pu8Shape = NULL;
+ mPointerData.cbShape = 0;
+
+ mPointerData.fVisible = fVisible;
+ mPointerData.fAlpha = fAlpha;
+ mPointerData.hotX = hotX;
+ mPointerData.hotY = hotY;
+ mPointerData.width = width;
+ mPointerData.height = height;
+ if (cbShape)
+ {
+ mPointerData.pu8Shape = (uint8_t *)RTMemDup(pu8Shape, cbShape);
+ if (mPointerData.pu8Shape)
+ {
+ mPointerData.cbShape = cbShape;
+ }
+ }
+
+ mPointerShape.setNull();
+}
+
+// IMouse properties
+/////////////////////////////////////////////////////////////////////////////
+
+/** Report the front-end's mouse handling capabilities to the VMM device and
+ * thus to the guest.
+ * @note all calls out of this object are made with no locks held! */
+HRESULT Mouse::i_updateVMMDevMouseCaps(uint32_t fCapsAdded,
+ uint32_t fCapsRemoved)
+{
+ VMMDevMouseInterface *pVMMDev = mParent->i_getVMMDevMouseInterface();
+ if (!pVMMDev)
+ return E_FAIL; /* No assertion, as the front-ends can send events
+ * at all sorts of inconvenient times. */
+ DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface();
+ if (pDisplay == NULL)
+ return E_FAIL;
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (!pVMMDevPort)
+ return E_FAIL; /* same here */
+
+ int vrc = pVMMDevPort->pfnUpdateMouseCapabilities(pVMMDevPort, fCapsAdded,
+ fCapsRemoved);
+ if (RT_FAILURE(vrc))
+ return E_FAIL;
+ return pDisplay->i_reportHostCursorCapabilities(fCapsAdded, fCapsRemoved);
+}
+
+/**
+ * Returns whether the currently active device portfolio can accept absolute
+ * mouse events.
+ *
+ * @returns COM status code
+ * @param aAbsoluteSupported address of result variable
+ */
+HRESULT Mouse::getAbsoluteSupported(BOOL *aAbsoluteSupported)
+{
+ *aAbsoluteSupported = i_supportsAbs();
+ return S_OK;
+}
+
+/**
+ * Returns whether the currently active device portfolio can accept relative
+ * mouse events.
+ *
+ * @returns COM status code
+ * @param aRelativeSupported address of result variable
+ */
+HRESULT Mouse::getRelativeSupported(BOOL *aRelativeSupported)
+{
+ *aRelativeSupported = i_supportsRel();
+ return S_OK;
+}
+
+/**
+ * Returns whether the currently active device portfolio can accept multi-touch
+ * touchscreen events.
+ *
+ * @returns COM status code
+ * @param aTouchScreenSupported address of result variable
+ */
+HRESULT Mouse::getTouchScreenSupported(BOOL *aTouchScreenSupported)
+{
+ *aTouchScreenSupported = i_supportsTS();
+ return S_OK;
+}
+
+/**
+ * Returns whether the currently active device portfolio can accept multi-touch
+ * touchpad events.
+ *
+ * @returns COM status code
+ * @param aTouchPadSupported address of result variable
+ */
+HRESULT Mouse::getTouchPadSupported(BOOL *aTouchPadSupported)
+{
+ *aTouchPadSupported = i_supportsTP();
+ return S_OK;
+}
+
+/**
+ * Returns whether the guest can currently switch to drawing the mouse cursor
+ * itself if it is asked to by the front-end.
+ *
+ * @returns COM status code
+ * @param aNeedsHostCursor address of result variable
+ */
+HRESULT Mouse::getNeedsHostCursor(BOOL *aNeedsHostCursor)
+{
+ *aNeedsHostCursor = i_guestNeedsHostCursor();
+ return S_OK;
+}
+
+HRESULT Mouse::getPointerShape(ComPtr<IMousePointerShape> &aPointerShape)
+{
+ HRESULT hr = S_OK;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mPointerShape.isNull())
+ {
+ ComObjPtr<MousePointerShape> obj;
+ hr = obj.createObject();
+ if (SUCCEEDED(hr))
+ {
+ hr = obj->init(this, mPointerData.fVisible, mPointerData.fAlpha,
+ mPointerData.hotX, mPointerData.hotY,
+ mPointerData.width, mPointerData.height,
+ mPointerData.pu8Shape, mPointerData.cbShape);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ mPointerShape = obj;
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ aPointerShape = mPointerShape;
+ }
+
+ return hr;
+}
+
+// IMouse methods
+/////////////////////////////////////////////////////////////////////////////
+
+/** Converts a bitfield containing information about mouse buttons currently
+ * held down from the format used by the front-end to the format used by PDM
+ * and the emulated pointing devices. */
+static uint32_t i_mouseButtonsToPDM(LONG buttonState)
+{
+ uint32_t fButtons = 0;
+ if (buttonState & MouseButtonState_LeftButton)
+ fButtons |= PDMIMOUSEPORT_BUTTON_LEFT;
+ if (buttonState & MouseButtonState_RightButton)
+ fButtons |= PDMIMOUSEPORT_BUTTON_RIGHT;
+ if (buttonState & MouseButtonState_MiddleButton)
+ fButtons |= PDMIMOUSEPORT_BUTTON_MIDDLE;
+ if (buttonState & MouseButtonState_XButton1)
+ fButtons |= PDMIMOUSEPORT_BUTTON_X1;
+ if (buttonState & MouseButtonState_XButton2)
+ fButtons |= PDMIMOUSEPORT_BUTTON_X2;
+ return fButtons;
+}
+
+HRESULT Mouse::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ // no need to lock - lifetime constant
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+ return S_OK;
+}
+
+/**
+ * Send a relative pointer event to the relative device we deem most
+ * appropriate.
+ *
+ * @returns COM status code
+ */
+HRESULT Mouse::i_reportRelEventToMouseDev(int32_t dx, int32_t dy, int32_t dz,
+ int32_t dw, uint32_t fButtons)
+{
+ if (dx || dy || dz || dw || fButtons != mfLastButtons)
+ {
+ PPDMIMOUSEPORT pUpPort = NULL;
+ {
+ AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (unsigned i = 0; !pUpPort && i < MOUSE_MAX_DEVICES; ++i)
+ {
+ if (mpDrv[i] && (mpDrv[i]->u32DevCaps & MOUSE_DEVCAP_RELATIVE))
+ pUpPort = mpDrv[i]->pUpPort;
+ }
+ }
+ if (!pUpPort)
+ return S_OK;
+
+ int vrc = pUpPort->pfnPutEvent(pUpPort, dx, dy, dz, dw, fButtons);
+
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not send the mouse event to the virtual mouse (%Rrc)"),
+ vrc);
+ mfLastButtons = fButtons;
+ }
+ return S_OK;
+}
+
+
+/**
+ * Send an absolute pointer event to the emulated absolute device we deem most
+ * appropriate.
+ *
+ * @returns COM status code
+ */
+HRESULT Mouse::i_reportAbsEventToMouseDev(int32_t x, int32_t y,
+ int32_t dz, int32_t dw, uint32_t fButtons)
+{
+ if ( x < VMMDEV_MOUSE_RANGE_MIN
+ || x > VMMDEV_MOUSE_RANGE_MAX)
+ return S_OK;
+ if ( y < VMMDEV_MOUSE_RANGE_MIN
+ || y > VMMDEV_MOUSE_RANGE_MAX)
+ return S_OK;
+ if ( x != mcLastX || y != mcLastY
+ || dz || dw || fButtons != mfLastButtons)
+ {
+ PPDMIMOUSEPORT pUpPort = NULL;
+ {
+ AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (unsigned i = 0; !pUpPort && i < MOUSE_MAX_DEVICES; ++i)
+ {
+ if (mpDrv[i] && (mpDrv[i]->u32DevCaps & MOUSE_DEVCAP_ABSOLUTE))
+ pUpPort = mpDrv[i]->pUpPort;
+ }
+ }
+ if (!pUpPort)
+ return S_OK;
+
+ int vrc = pUpPort->pfnPutEventAbs(pUpPort, x, y, dz,
+ dw, fButtons);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not send the mouse event to the virtual mouse (%Rrc)"),
+ vrc);
+ mfLastButtons = fButtons;
+
+ }
+ return S_OK;
+}
+
+HRESULT Mouse::i_reportMultiTouchEventToDevice(uint8_t cContacts,
+ const uint64_t *pau64Contacts,
+ bool fTouchScreen,
+ uint32_t u32ScanTime)
+{
+ HRESULT hrc = S_OK;
+
+ int match = fTouchScreen ? MOUSE_DEVCAP_MT_ABSOLUTE : MOUSE_DEVCAP_MT_RELATIVE;
+ PPDMIMOUSEPORT pUpPort = NULL;
+ {
+ AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS);
+
+ unsigned i;
+ for (i = 0; i < MOUSE_MAX_DEVICES; ++i)
+ {
+ if ( mpDrv[i]
+ && (mpDrv[i]->u32DevCaps & match))
+ {
+ pUpPort = mpDrv[i]->pUpPort;
+ break;
+ }
+ }
+ }
+
+ if (pUpPort)
+ {
+ int vrc = pUpPort->pfnPutEventTouchScreen(pUpPort, cContacts, pau64Contacts, u32ScanTime);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not send the multi-touch event to the virtual device (%Rrc)"),
+ vrc);
+ }
+ else
+ {
+ hrc = E_UNEXPECTED;
+ }
+
+ return hrc;
+}
+
+
+/**
+ * Send an absolute position event to the VMM device.
+ * @note all calls out of this object are made with no locks held!
+ *
+ * @returns COM status code
+ */
+HRESULT Mouse::i_reportAbsEventToVMMDev(int32_t x, int32_t y, int32_t dz, int32_t dw, uint32_t fButtons)
+{
+ VMMDevMouseInterface *pVMMDev = mParent->i_getVMMDevMouseInterface();
+ ComAssertRet(pVMMDev, E_FAIL);
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ ComAssertRet(pVMMDevPort, E_FAIL);
+
+ if (x != mcLastX || y != mcLastY || dz || dw || fButtons != mfLastButtons)
+ {
+ int vrc = pVMMDevPort->pfnSetAbsoluteMouse(pVMMDevPort, x, y, dz, dw, fButtons);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not send the mouse event to the virtual mouse (%Rrc)"),
+ vrc);
+ }
+ return S_OK;
+}
+
+
+/**
+ * Send an absolute pointer event to a pointing device (the VMM device if
+ * possible or whatever emulated absolute device seems best to us if not).
+ *
+ * @returns COM status code
+ */
+HRESULT Mouse::i_reportAbsEventToInputDevices(int32_t x, int32_t y, int32_t dz, int32_t dw, uint32_t fButtons,
+ bool fUsesVMMDevEvent)
+{
+ HRESULT hrc = S_OK;
+ /** If we are using the VMMDev to report absolute position but without
+ * VMMDev IRQ support then we need to send a small "jiggle" to the emulated
+ * relative mouse device to alert the guest to changes. */
+ LONG cJiggle = 0;
+
+ if (i_vmmdevCanAbs())
+ {
+ /*
+ * Send the absolute mouse position to the VMM device.
+ */
+ if (x != mcLastX || y != mcLastY || dz || dw || fButtons != mfLastButtons)
+ {
+ hrc = i_reportAbsEventToVMMDev(x, y, dz, dw, fButtons);
+ cJiggle = !fUsesVMMDevEvent;
+ }
+
+ /* If guest cannot yet read full mouse state from DevVMM (i.e.,
+ * only 'x' and 'y' coordinates will be read) we need to pass buttons
+ * state as well as horizontal and vertical wheel movement over ever-present PS/2
+ * emulated mouse device. */
+ if (!(mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_USES_FULL_STATE_PROTOCOL))
+ hrc = i_reportRelEventToMouseDev(cJiggle, 0, dz, dw, fButtons);
+ }
+ else
+ hrc = i_reportAbsEventToMouseDev(x, y, dz, dw, fButtons);
+
+ mcLastX = x;
+ mcLastY = y;
+ mfLastButtons = fButtons;
+ return hrc;
+}
+
+
+/**
+ * Send an absolute position event to the display device.
+ * @note all calls out of this object are made with no locks held!
+ * @param x Cursor X position in pixels relative to the first screen, where
+ * (1, 1) is the upper left corner.
+ * @param y Cursor Y position in pixels relative to the first screen, where
+ * (1, 1) is the upper left corner.
+ */
+HRESULT Mouse::i_reportAbsEventToDisplayDevice(int32_t x, int32_t y)
+{
+ DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface();
+ ComAssertRet(pDisplay, E_FAIL);
+
+ if (x != mcLastX || y != mcLastY)
+ {
+ pDisplay->i_reportHostCursorPosition(x - 1, y - 1, false);
+ }
+ return S_OK;
+}
+
+
+void Mouse::i_fireMouseEvent(bool fAbsolute, LONG x, LONG y, LONG dz, LONG dw,
+ LONG fButtons)
+{
+ /* If mouse button is pressed, we generate new event, to avoid reusable events coalescing and thus
+ dropping key press events */
+ GuestMouseEventMode_T mode;
+ if (fAbsolute)
+ mode = GuestMouseEventMode_Absolute;
+ else
+ mode = GuestMouseEventMode_Relative;
+
+ if (fButtons != 0)
+ ::FireGuestMouseEvent(mEventSource, mode, x, y, dz, dw, fButtons);
+ else
+ {
+ ComPtr<IEvent> ptrEvent;
+ mMouseEvent.getEvent(ptrEvent.asOutParam());
+ ReinitGuestMouseEvent(ptrEvent, mode, x, y, dz, dw, fButtons);
+ mMouseEvent.fire(0);
+ }
+}
+
+void Mouse::i_fireMultiTouchEvent(uint8_t cContacts,
+ const LONG64 *paContacts,
+ bool fTouchScreen,
+ uint32_t u32ScanTime)
+{
+ com::SafeArray<SHORT> xPositions(cContacts);
+ com::SafeArray<SHORT> yPositions(cContacts);
+ com::SafeArray<USHORT> contactIds(cContacts);
+ com::SafeArray<USHORT> contactFlags(cContacts);
+
+ uint8_t i;
+ for (i = 0; i < cContacts; i++)
+ {
+ uint32_t u32Lo = RT_LO_U32(paContacts[i]);
+ uint32_t u32Hi = RT_HI_U32(paContacts[i]);
+ xPositions[i] = (int16_t)u32Lo;
+ yPositions[i] = (int16_t)(u32Lo >> 16);
+ contactIds[i] = RT_BYTE1(u32Hi);
+ contactFlags[i] = RT_BYTE2(u32Hi);
+ }
+
+ ::FireGuestMultiTouchEvent(mEventSource, cContacts, ComSafeArrayAsInParam(xPositions), ComSafeArrayAsInParam(yPositions),
+ ComSafeArrayAsInParam(contactIds), ComSafeArrayAsInParam(contactFlags), fTouchScreen, u32ScanTime);
+}
+
+/**
+ * Send a relative mouse event to the guest.
+ * @note the VMMDev capability change is so that the guest knows we are sending
+ * real events over the PS/2 device and not dummy events to signal the
+ * arrival of new absolute pointer data
+ *
+ * @returns COM status code
+ * @param dx X movement.
+ * @param dy Y movement.
+ * @param dz Z movement.
+ * @param dw Mouse wheel movement.
+ * @param aButtonState The mouse button state.
+ */
+HRESULT Mouse::putMouseEvent(LONG dx, LONG dy, LONG dz, LONG dw,
+ LONG aButtonState)
+{
+ LogRel3(("%s: dx=%d, dy=%d, dz=%d, dw=%d\n", __PRETTY_FUNCTION__,
+ dx, dy, dz, dw));
+
+ uint32_t fButtonsAdj = i_mouseButtonsToPDM(aButtonState);
+ /* Make sure that the guest knows that we are sending real movement
+ * events to the PS/2 device and not just dummy wake-up ones. */
+ i_updateVMMDevMouseCaps(0, VMMDEV_MOUSE_HOST_WANTS_ABSOLUTE);
+ HRESULT hrc = i_reportRelEventToMouseDev(dx, dy, dz, dw, fButtonsAdj);
+
+ i_fireMouseEvent(false, dx, dy, dz, dw, aButtonState);
+
+ return hrc;
+}
+
+/**
+ * Convert an (X, Y) value pair in screen co-ordinates (starting from 1) to a
+ * value from VMMDEV_MOUSE_RANGE_MIN to VMMDEV_MOUSE_RANGE_MAX. Sets the
+ * optional validity value to false if the pair is not on an active screen and
+ * to true otherwise.
+ * @note since guests with recent versions of X.Org use a different method
+ * to everyone else to map the valuator value to a screen pixel (they
+ * multiply by the screen dimension, do a floating point divide by
+ * the valuator maximum and round the result, while everyone else
+ * does truncating integer operations) we adjust the value we send
+ * so that it maps to the right pixel both when the result is rounded
+ * and when it is truncated.
+ *
+ * @returns COM status value
+ */
+HRESULT Mouse::i_convertDisplayRes(LONG x, LONG y, int32_t *pxAdj, int32_t *pyAdj,
+ bool *pfValid)
+{
+ AssertPtrReturn(pxAdj, E_POINTER);
+ AssertPtrReturn(pyAdj, E_POINTER);
+ AssertPtrNullReturn(pfValid, E_POINTER);
+ DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface();
+ ComAssertRet(pDisplay, E_FAIL);
+ /** The amount to add to the result (multiplied by the screen width/height)
+ * to compensate for differences in guest methods for mapping back to
+ * pixels */
+ enum { ADJUST_RANGE = - 3 * VMMDEV_MOUSE_RANGE / 4 };
+
+ if (pfValid)
+ *pfValid = true;
+ if (!(mfVMMDevGuestCaps & VMMDEV_MOUSE_NEW_PROTOCOL) && !pDisplay->i_isInputMappingSet())
+ {
+ ULONG displayWidth, displayHeight;
+ ULONG ulDummy;
+ LONG lDummy;
+ /* Takes the display lock */
+ HRESULT hrc = pDisplay->i_getScreenResolution(0, &displayWidth,
+ &displayHeight, &ulDummy, &lDummy, &lDummy);
+ if (FAILED(hrc))
+ return hrc;
+
+ *pxAdj = displayWidth ? (x * VMMDEV_MOUSE_RANGE + ADJUST_RANGE)
+ / (LONG) displayWidth: 0;
+ *pyAdj = displayHeight ? (y * VMMDEV_MOUSE_RANGE + ADJUST_RANGE)
+ / (LONG) displayHeight: 0;
+ }
+ else
+ {
+ int32_t x1, y1, x2, y2;
+ /* Takes the display lock */
+ pDisplay->i_getFramebufferDimensions(&x1, &y1, &x2, &y2);
+ *pxAdj = x1 < x2 ? ((x - x1) * VMMDEV_MOUSE_RANGE + ADJUST_RANGE)
+ / (x2 - x1) : 0;
+ *pyAdj = y1 < y2 ? ((y - y1) * VMMDEV_MOUSE_RANGE + ADJUST_RANGE)
+ / (y2 - y1) : 0;
+ if ( *pxAdj < VMMDEV_MOUSE_RANGE_MIN
+ || *pxAdj > VMMDEV_MOUSE_RANGE_MAX
+ || *pyAdj < VMMDEV_MOUSE_RANGE_MIN
+ || *pyAdj > VMMDEV_MOUSE_RANGE_MAX)
+ if (pfValid)
+ *pfValid = false;
+ }
+ return S_OK;
+}
+
+
+/**
+ * Send an absolute mouse event to the VM. This requires either VirtualBox-
+ * specific drivers installed in the guest or absolute pointing device
+ * emulation.
+ * @note the VMMDev capability change is so that the guest knows we are sending
+ * dummy events over the PS/2 device to signal the arrival of new
+ * absolute pointer data, and not pointer real movement data
+ * @note all calls out of this object are made with no locks held!
+ *
+ * @returns COM status code
+ * @param x X position (pixel), starting from 1
+ * @param y Y position (pixel), starting from 1
+ * @param dz Z movement
+ * @param dw mouse wheel movement
+ * @param aButtonState The mouse button state
+ */
+HRESULT Mouse::putMouseEventAbsolute(LONG x, LONG y, LONG dz, LONG dw,
+ LONG aButtonState)
+{
+ LogRel3(("%s: x=%d, y=%d, dz=%d, dw=%d, fButtons=0x%x\n",
+ __PRETTY_FUNCTION__, x, y, dz, dw, aButtonState));
+
+ DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface();
+ ComAssertRet(pDisplay, E_FAIL);
+ int32_t xAdj, yAdj;
+ uint32_t fButtonsAdj;
+ bool fValid;
+
+ /* If we are doing old-style (IRQ-less) absolute reporting to the VMM
+ * device then make sure the guest is aware of it, so that it knows to
+ * ignore relative movement on the PS/2 device. */
+ i_updateVMMDevMouseCaps(VMMDEV_MOUSE_HOST_WANTS_ABSOLUTE, 0);
+ /* Detect out-of-range. */
+ if (x == 0x7FFFFFFF && y == 0x7FFFFFFF)
+ {
+ pDisplay->i_reportHostCursorPosition(0, 0, true);
+ return S_OK;
+ }
+ /* Detect "report-only" (-1, -1). This is not ideal, as in theory the
+ * front-end could be sending negative values relative to the primary
+ * screen. */
+ if (x == -1 && y == -1)
+ return S_OK;
+ /** @todo the front end should do this conversion to avoid races */
+ /** @note Or maybe not... races are pretty inherent in everything done in
+ * this object and not really bad as far as I can see. */
+ HRESULT hrc = i_convertDisplayRes(x, y, &xAdj, &yAdj, &fValid);
+ if (FAILED(hrc)) return hrc;
+
+ fButtonsAdj = i_mouseButtonsToPDM(aButtonState);
+ if (fValid)
+ {
+ hrc = i_reportAbsEventToInputDevices(xAdj, yAdj, dz, dw, fButtonsAdj,
+ RT_BOOL(mfVMMDevGuestCaps & VMMDEV_MOUSE_NEW_PROTOCOL));
+ if (FAILED(hrc)) return hrc;
+
+ i_fireMouseEvent(true, x, y, dz, dw, aButtonState);
+ }
+ hrc = i_reportAbsEventToDisplayDevice(x, y);
+
+ return hrc;
+}
+
+/**
+ * Send a multi-touch event. This requires multi-touch pointing device emulation.
+ * @note all calls out of this object are made with no locks held!
+ *
+ * @returns COM status code.
+ * @param aCount Number of contacts.
+ * @param aContacts Information about each contact.
+ * @param aIsTouchscreen Distinguishes between touchscreen and touchpad events.
+ * @param aScanTime Timestamp.
+ */
+HRESULT Mouse::putEventMultiTouch(LONG aCount,
+ const std::vector<LONG64> &aContacts,
+ BOOL aIsTouchscreen,
+ ULONG aScanTime)
+{
+ LogRel3(("%s: aCount %d(actual %d), aScanTime %u\n",
+ __FUNCTION__, aCount, aContacts.size(), aScanTime));
+
+ HRESULT hrc = S_OK;
+
+ if ((LONG)aContacts.size() >= aCount)
+ {
+ const LONG64 *paContacts = aCount > 0? &aContacts.front(): NULL;
+
+ hrc = i_putEventMultiTouch(aCount, paContacts, aIsTouchscreen, aScanTime);
+ }
+ else
+ {
+ hrc = E_INVALIDARG;
+ }
+
+ return hrc;
+}
+
+/**
+ * Send a multi-touch event. Version for scripting languages.
+ *
+ * @returns COM status code.
+ * @param aCount Number of contacts.
+ * @param aContacts Information about each contact.
+ * @param aIsTouchscreen Distinguishes between touchscreen and touchpad events.
+ * @param aScanTime Timestamp.
+ */
+HRESULT Mouse::putEventMultiTouchString(LONG aCount,
+ const com::Utf8Str &aContacts,
+ BOOL aIsTouchscreen,
+ ULONG aScanTime)
+{
+ /** @todo implement: convert the string to LONG64 array and call putEventMultiTouch. */
+ NOREF(aCount);
+ NOREF(aContacts);
+ NOREF(aIsTouchscreen);
+ NOREF(aScanTime);
+ return E_NOTIMPL;
+}
+
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/* Used by PutEventMultiTouch and PutEventMultiTouchString. */
+HRESULT Mouse::i_putEventMultiTouch(LONG aCount,
+ const LONG64 *paContacts,
+ BOOL aIsTouchscreen,
+ ULONG aScanTime)
+{
+ if (aCount >= 256)
+ return E_INVALIDARG;
+
+ HRESULT hrc = S_OK;
+
+ /* Touch events in the touchscreen variant are currently mapped to the
+ * primary monitor, because the emulated USB touchscreen device is
+ * associated with one (normally the primary) screen in the guest.
+ * In the future this could/should be extended to multi-screen support. */
+ ULONG uScreenId = 0;
+
+ ULONG cWidth = 0;
+ ULONG cHeight = 0;
+ LONG xOrigin = 0;
+ LONG yOrigin = 0;
+
+ if (aIsTouchscreen)
+ {
+ DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface();
+ ComAssertRet(pDisplay, E_FAIL);
+ ULONG cBPP = 0;
+ hrc = pDisplay->i_getScreenResolution(uScreenId, &cWidth, &cHeight, &cBPP, &xOrigin, &yOrigin);
+ NOREF(cBPP);
+ ComAssertComRCRetRC(hrc);
+ }
+
+ uint64_t* pau64Contacts = NULL;
+ uint8_t cContacts = 0;
+
+ /* Deliver 0 contacts too, touch device may use this to reset the state. */
+ if (aCount > 0)
+ {
+ /* Create a copy with converted coords. */
+ pau64Contacts = (uint64_t *)RTMemTmpAlloc(aCount * sizeof(uint64_t));
+ if (pau64Contacts)
+ {
+ if (aIsTouchscreen)
+ {
+ int32_t x1 = xOrigin;
+ int32_t y1 = yOrigin;
+ int32_t x2 = x1 + cWidth;
+ int32_t y2 = y1 + cHeight;
+
+ LogRel3(("%s: screen [%d] %d,%d %d,%d\n",
+ __FUNCTION__, uScreenId, x1, y1, x2, y2));
+
+ LONG i;
+ for (i = 0; i < aCount; i++)
+ {
+ uint32_t u32Lo = RT_LO_U32(paContacts[i]);
+ uint32_t u32Hi = RT_HI_U32(paContacts[i]);
+ int32_t x = (int16_t)u32Lo;
+ int32_t y = (int16_t)(u32Lo >> 16);
+ uint8_t contactId = RT_BYTE1(u32Hi);
+ bool fInContact = (RT_BYTE2(u32Hi) & 0x1) != 0;
+ bool fInRange = (RT_BYTE2(u32Hi) & 0x2) != 0;
+
+ LogRel3(("%s: touchscreen [%d] %d,%d id %d, inContact %d, inRange %d\n",
+ __FUNCTION__, i, x, y, contactId, fInContact, fInRange));
+
+ /* x1,y1 are inclusive and x2,y2 are exclusive,
+ * while x,y start from 1 and are inclusive.
+ */
+ if (x <= x1 || x > x2 || y <= y1 || y > y2)
+ {
+ /* Out of range. Skip the contact. */
+ continue;
+ }
+
+ int32_t xAdj = x1 < x2? ((x - 1 - x1) * VMMDEV_MOUSE_RANGE) / (x2 - x1) : 0;
+ int32_t yAdj = y1 < y2? ((y - 1 - y1) * VMMDEV_MOUSE_RANGE) / (y2 - y1) : 0;
+
+ bool fValid = ( xAdj >= VMMDEV_MOUSE_RANGE_MIN
+ && xAdj <= VMMDEV_MOUSE_RANGE_MAX
+ && yAdj >= VMMDEV_MOUSE_RANGE_MIN
+ && yAdj <= VMMDEV_MOUSE_RANGE_MAX);
+
+ if (fValid)
+ {
+ uint8_t fu8 = (uint8_t)( (fInContact? 0x01: 0x00)
+ | (fInRange? 0x02: 0x00));
+ pau64Contacts[cContacts] = RT_MAKE_U64_FROM_U16((uint16_t)xAdj,
+ (uint16_t)yAdj,
+ RT_MAKE_U16(contactId, fu8),
+ 0);
+ cContacts++;
+ }
+ }
+ }
+ else
+ {
+ LONG i;
+ for (i = 0; i < aCount; i++)
+ {
+ uint32_t u32Lo = RT_LO_U32(paContacts[i]);
+ uint32_t u32Hi = RT_HI_U32(paContacts[i]);
+ uint16_t x = (uint16_t)u32Lo;
+ uint16_t y = (uint16_t)(u32Lo >> 16);
+ uint8_t contactId = RT_BYTE1(u32Hi);
+ bool fInContact = (RT_BYTE2(u32Hi) & 0x1) != 0;
+
+ LogRel3(("%s: touchpad [%d] %#04x,%#04x id %d, inContact %d\n",
+ __FUNCTION__, i, x, y, contactId, fInContact));
+
+ uint8_t fu8 = (uint8_t)(fInContact? 0x01: 0x00);
+
+ pau64Contacts[cContacts] = RT_MAKE_U64_FROM_U16(x, y,
+ RT_MAKE_U16(contactId, fu8),
+ 0);
+ cContacts++;
+ }
+ }
+ }
+ else
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ hrc = i_reportMultiTouchEventToDevice(cContacts, cContacts? pau64Contacts: NULL, !!aIsTouchscreen, (uint32_t)aScanTime);
+
+ /* Send the original contact information. */
+ i_fireMultiTouchEvent(cContacts, cContacts? paContacts: NULL, !!aIsTouchscreen, (uint32_t)aScanTime);
+ }
+
+ RTMemTmpFree(pau64Contacts);
+
+ return hrc;
+}
+
+
+/** Does the guest currently rely on the host to draw the mouse cursor or
+ * can it switch to doing it itself in software? */
+bool Mouse::i_guestNeedsHostCursor(void)
+{
+ return RT_BOOL(mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR);
+}
+
+
+/**
+ * Gets the combined capabilities of all currently enabled devices.
+ *
+ * @returns Combination of MOUSE_DEVCAP_XXX
+ */
+uint32_t Mouse::i_getDeviceCaps(void)
+{
+ uint32_t fCaps = 0;
+ AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS);
+ for (unsigned i = 0; i < MOUSE_MAX_DEVICES; ++i)
+ if (mpDrv[i])
+ fCaps |= mpDrv[i]->u32DevCaps;
+ return fCaps;
+}
+
+
+/** Does the VMM device currently support absolute reporting? */
+bool Mouse::i_vmmdevCanAbs(void)
+{
+ /* This requires the VMMDev cap and a relative device, which supposedly
+ consumes these. As seen in @bugref{10285} this isn't quite as clear cut. */
+ return (mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE)
+ && (i_getDeviceCaps() & MOUSE_DEVCAP_RELATIVE);
+}
+
+
+/** Does any device currently support absolute reporting w/o help from VMMDev? */
+bool Mouse::i_deviceCanAbs(void)
+{
+ return RT_BOOL(i_getDeviceCaps() & MOUSE_DEVCAP_ABSOLUTE);
+}
+
+
+/** Can we currently send relative events to the guest? */
+bool Mouse::i_supportsRel(void)
+{
+ return RT_BOOL(i_getDeviceCaps() & MOUSE_DEVCAP_RELATIVE);
+}
+
+
+/** Can we currently send absolute events to the guest (including via VMMDev)? */
+bool Mouse::i_supportsAbs(uint32_t fCaps) const
+{
+ return (fCaps & MOUSE_DEVCAP_ABSOLUTE)
+ || /* inlined i_vmmdevCanAbs() to avoid unnecessary i_getDeviceCaps call: */
+ ( (mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE)
+ && (fCaps & MOUSE_DEVCAP_RELATIVE));
+}
+
+
+/** Can we currently send absolute events to the guest? */
+bool Mouse::i_supportsAbs(void)
+{
+ return Mouse::i_supportsAbs(i_getDeviceCaps());
+}
+
+
+/** Can we currently send multi-touch events (touchscreen variant) to the guest? */
+bool Mouse::i_supportsTS(void)
+{
+ return RT_BOOL(i_getDeviceCaps() & MOUSE_DEVCAP_MT_ABSOLUTE);
+}
+
+
+/** Can we currently send multi-touch events (touchpad variant) to the guest? */
+bool Mouse::i_supportsTP(void)
+{
+ return RT_BOOL(i_getDeviceCaps() & MOUSE_DEVCAP_MT_RELATIVE);
+}
+
+
+/** Check what sort of reporting can be done using the devices currently
+ * enabled (including the VMM device) and notify the guest and the front-end.
+ */
+void Mouse::i_sendMouseCapsNotifications(void)
+{
+ bool fRelDev, fTSDev, fTPDev, fCanAbs, fNeedsHostCursor;
+ {
+ AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS);
+
+ uint32_t const fCaps = i_getDeviceCaps();
+ fRelDev = RT_BOOL(fCaps & MOUSE_DEVCAP_RELATIVE);
+ fTSDev = RT_BOOL(fCaps & MOUSE_DEVCAP_MT_ABSOLUTE);
+ fTPDev = RT_BOOL(fCaps & MOUSE_DEVCAP_MT_RELATIVE);
+ fCanAbs = i_supportsAbs(fCaps);
+ fNeedsHostCursor = i_guestNeedsHostCursor();
+ }
+ mParent->i_onMouseCapabilityChange(fCanAbs, fRelDev, fTSDev, fTPDev, fNeedsHostCursor);
+}
+
+
+/**
+ * @interface_method_impl{PDMIMOUSECONNECTOR,pfnReportModes}
+ */
+DECLCALLBACK(void) Mouse::i_mouseReportModes(PPDMIMOUSECONNECTOR pInterface, bool fRelative,
+ bool fAbsolute, bool fMTAbsolute, bool fMTRelative)
+{
+ PDRVMAINMOUSE pDrv = RT_FROM_MEMBER(pInterface, DRVMAINMOUSE, IConnector);
+ if (fRelative)
+ pDrv->u32DevCaps |= MOUSE_DEVCAP_RELATIVE;
+ else
+ pDrv->u32DevCaps &= ~MOUSE_DEVCAP_RELATIVE;
+ if (fAbsolute)
+ pDrv->u32DevCaps |= MOUSE_DEVCAP_ABSOLUTE;
+ else
+ pDrv->u32DevCaps &= ~MOUSE_DEVCAP_ABSOLUTE;
+ if (fMTAbsolute)
+ pDrv->u32DevCaps |= MOUSE_DEVCAP_MT_ABSOLUTE;
+ else
+ pDrv->u32DevCaps &= ~MOUSE_DEVCAP_MT_ABSOLUTE;
+ if (fMTRelative)
+ pDrv->u32DevCaps |= MOUSE_DEVCAP_MT_RELATIVE;
+ else
+ pDrv->u32DevCaps &= ~MOUSE_DEVCAP_MT_RELATIVE;
+
+ pDrv->pMouse->i_sendMouseCapsNotifications();
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) Mouse::i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINMOUSE pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINMOUSE);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUSECONNECTOR, &pDrv->IConnector);
+ return NULL;
+}
+
+
+/**
+ * Destruct a mouse driver instance.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The driver instance data.
+ */
+DECLCALLBACK(void) Mouse::i_drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVMAINMOUSE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINMOUSE);
+ LogFlow(("Mouse::drvDestruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ if (pThis->pMouse)
+ {
+ AutoWriteLock mouseLock(pThis->pMouse COMMA_LOCKVAL_SRC_POS);
+ for (unsigned cDev = 0; cDev < MOUSE_MAX_DEVICES; ++cDev)
+ if (pThis->pMouse->mpDrv[cDev] == pThis)
+ {
+ pThis->pMouse->mpDrv[cDev] = NULL;
+ break;
+ }
+ }
+}
+
+
+/**
+ * Construct a mouse driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+DECLCALLBACK(int) Mouse::i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ RT_NOREF(fFlags, pCfg);
+ PDRVMAINMOUSE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINMOUSE);
+ LogFlow(("drvMainMouse_Construct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", "");
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * IBase.
+ */
+ pDrvIns->IBase.pfnQueryInterface = Mouse::i_drvQueryInterface;
+
+ pThis->IConnector.pfnReportModes = Mouse::i_mouseReportModes;
+
+ /*
+ * Get the IMousePort interface of the above driver/device.
+ */
+ pThis->pUpPort = (PPDMIMOUSEPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMIMOUSEPORT_IID);
+ if (!pThis->pUpPort)
+ {
+ AssertMsgFailed(("Configuration error: No mouse port interface above!\n"));
+ return VERR_PDM_MISSING_INTERFACE_ABOVE;
+ }
+
+ /*
+ * Get the Mouse object pointer and update the mpDrv member.
+ */
+ com::Guid uuid(COM_IIDOF(IMouse));
+ IMouse *pIMouse = (IMouse *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw());
+ if (!pIMouse)
+ {
+ AssertMsgFailed(("Configuration error: No/bad Mouse object!\n"));
+ return VERR_NOT_FOUND;
+ }
+ pThis->pMouse = static_cast<Mouse *>(pIMouse);
+
+ unsigned cDev;
+ {
+ AutoWriteLock mouseLock(pThis->pMouse COMMA_LOCKVAL_SRC_POS);
+
+ for (cDev = 0; cDev < MOUSE_MAX_DEVICES; ++cDev)
+ if (!pThis->pMouse->mpDrv[cDev])
+ {
+ pThis->pMouse->mpDrv[cDev] = pThis;
+ break;
+ }
+ }
+ if (cDev == MOUSE_MAX_DEVICES)
+ return VERR_NO_MORE_HANDLES;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Main mouse driver registration record.
+ */
+const PDMDRVREG Mouse::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "MainMouse",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Main mouse driver (Main as in the API).",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_MOUSE,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMAINMOUSE),
+ /* pfnConstruct */
+ Mouse::i_drvConstruct,
+ /* pfnDestruct */
+ Mouse::i_drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/PCIRawDevImpl.cpp b/src/VBox/Main/src-client/PCIRawDevImpl.cpp
new file mode 100644
index 00000000..acf22bb0
--- /dev/null
+++ b/src/VBox/Main/src-client/PCIRawDevImpl.cpp
@@ -0,0 +1,230 @@
+/* $Id: PCIRawDevImpl.cpp $ */
+/** @file
+ * VirtualBox Driver Interface to raw PCI device.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_DEV_PCI_RAW
+#include "LoggingNew.h"
+
+#include "PCIRawDevImpl.h"
+#include "PCIDeviceAttachmentImpl.h"
+#include "ConsoleImpl.h"
+
+// generated header for events
+#include "VBoxEvents.h"
+
+#include <VBox/err.h>
+
+
+/**
+ * PCI raw driver instance data.
+ */
+typedef struct DRVMAINPCIRAWDEV
+{
+ /** Pointer to the real PCI raw object. */
+ PCIRawDev *pPCIRawDev;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Our PCI device connector interface. */
+ PDMIPCIRAWCONNECTOR IConnector;
+
+} DRVMAINPCIRAWDEV, *PDRVMAINPCIRAWDEV;
+
+//
+// constructor / destructor
+//
+PCIRawDev::PCIRawDev(Console *console)
+ : mParent(console),
+ mpDrv(NULL)
+{
+}
+
+PCIRawDev::~PCIRawDev()
+{
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) PCIRawDev::drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINPCIRAWDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINPCIRAWDEV);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIPCIRAWCONNECTOR, &pThis->IConnector);
+
+ return NULL;
+}
+
+
+/**
+ * @interface_method_impl{PDMIPCIRAWCONNECTOR,pfnDeviceConstructComplete}
+ */
+DECLCALLBACK(int) PCIRawDev::drvDeviceConstructComplete(PPDMIPCIRAWCONNECTOR pInterface, const char *pcszName,
+ uint32_t uHostPCIAddress, uint32_t uGuestPCIAddress,
+ int rc)
+{
+ PDRVMAINPCIRAWDEV pThis = RT_FROM_CPP_MEMBER(pInterface, DRVMAINPCIRAWDEV, IConnector);
+ Console *pConsole = pThis->pPCIRawDev->getParent();
+ const ComPtr<IMachine>& machine = pConsole->i_machine();
+ ComPtr<IVirtualBox> vbox;
+
+ HRESULT hrc = machine->COMGETTER(Parent)(vbox.asOutParam());
+ Assert(SUCCEEDED(hrc)); NOREF(hrc);
+
+ ComPtr<IEventSource> es;
+ hrc = vbox->COMGETTER(EventSource)(es.asOutParam());
+ Assert(SUCCEEDED(hrc));
+
+ Bstr bstrId;
+ hrc = machine->COMGETTER(Id)(bstrId.asOutParam());
+ Assert(SUCCEEDED(hrc));
+
+ ComObjPtr<PCIDeviceAttachment> pda;
+ BstrFmt bstrName(pcszName);
+ pda.createObject();
+ pda->init(machine, bstrName, uHostPCIAddress, uGuestPCIAddress, TRUE);
+
+ Bstr msg("");
+ if (RT_FAILURE(rc))
+ msg = BstrFmt("runtime error %Rrc", rc);
+
+ ::FireHostPCIDevicePlugEvent(es, bstrId.raw(), true /* plugged */, RT_SUCCESS_NP(rc) /* success */, pda, msg.raw());
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnReset}
+ */
+DECLCALLBACK(void) PCIRawDev::drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVMAINPCIRAWDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINPCIRAWDEV);
+
+ if (pThis->pPCIRawDev)
+ pThis->pPCIRawDev->mpDrv = NULL;
+}
+
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnConstruct}
+ */
+DECLCALLBACK(int) PCIRawDev::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVMAINPCIRAWDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINPCIRAWDEV);
+
+ /*
+ * Validate configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfgHandle, "Object\0"))
+ return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
+
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * IBase.
+ */
+ pDrvIns->IBase.pfnQueryInterface = PCIRawDev::drvQueryInterface;
+
+ /*
+ * IConnector.
+ */
+ pThis->IConnector.pfnDeviceConstructComplete = PCIRawDev::drvDeviceConstructComplete;
+
+ /*
+ * Get the object pointer and update the mpDrv member.
+ */
+ void *pv;
+ int rc = CFGMR3QueryPtr(pCfgHandle, "Object", &pv);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: No \"Object\" value! rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ pThis->pPCIRawDev = (PCIRawDev *)pv;
+ pThis->pPCIRawDev->mpDrv = pThis;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Main raw PCI driver registration record.
+ */
+const PDMDRVREG PCIRawDev::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "MainPciRaw",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Main PCI raw driver (Main as in the API).",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_PCIRAW,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMAINPCIRAWDEV),
+ /* pfnConstruct */
+ PCIRawDev::drvConstruct,
+ /* pfnDestruct */
+ PCIRawDev::drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Main/src-client/README.testing b/src/VBox/Main/src-client/README.testing
new file mode 100644
index 00000000..6edd1083
--- /dev/null
+++ b/src/VBox/Main/src-client/README.testing
@@ -0,0 +1,16 @@
+This file contains some notes about things to try out to give the client-side
+code in Main a reasonably thorough test. We will add cases of things which
+have been known to fail in the past to this file as we discover them.
+
+*Linux guests*: changes should be tested against the following guests, or
+equivalent vintages, preferably in 32-bit and 64-bit versions, with no
+Additions and with 4.2 and 4.3 Additions and with single and dual screens:
+CentOS 3, CentOS 5, CentOS 6, Current Ubuntu.
+
+Mouse interface changes:
+ * Test that mouse integration works.
+ * Test that disabling mouse integration on the fly works.
+
+Display interface changed:
+ * Test that dynamic resizing works.
+ * Test that switching to seamless mode and back works.
diff --git a/src/VBox/Main/src-client/Recording.cpp b/src/VBox/Main/src-client/Recording.cpp
new file mode 100644
index 00000000..907a5226
--- /dev/null
+++ b/src/VBox/Main/src-client/Recording.cpp
@@ -0,0 +1,945 @@
+/* $Id: Recording.cpp $ */
+/** @file
+ * Recording context code.
+ *
+ * This code employs a separate encoding thread per recording context
+ * to keep time spent in EMT as short as possible. Each configured VM display
+ * is represented by an own recording stream, which in turn has its own rendering
+ * queue. Common recording data across all recording streams is kept in a
+ * separate queue in the recording context to minimize data duplication and
+ * multiplexing overhead in EMT.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifdef LOG_GROUP
+# undef LOG_GROUP
+#endif
+#define LOG_GROUP LOG_GROUP_RECORDING
+#include "LoggingNew.h"
+
+#include <stdexcept>
+#include <vector>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#include <VBox/err.h>
+#include <VBox/com/VirtualBox.h>
+
+#include "ConsoleImpl.h"
+#include "Recording.h"
+#include "RecordingInternals.h"
+#include "RecordingStream.h"
+#include "RecordingUtils.h"
+#include "WebMWriter.h"
+
+using namespace com;
+
+#ifdef DEBUG_andy
+/** Enables dumping audio / video data for debugging reasons. */
+//# define VBOX_RECORDING_DUMP
+#endif
+
+
+/**
+ * Recording context constructor.
+ *
+ * @note Will throw rc when unable to create.
+ */
+RecordingContext::RecordingContext(void)
+ : m_pConsole(NULL)
+ , m_enmState(RECORDINGSTS_UNINITIALIZED)
+ , m_cStreamsEnabled(0)
+{
+ int vrc = RTCritSectInit(&m_CritSect);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+}
+
+/**
+ * Recording context constructor.
+ *
+ * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
+ * @param Settings Reference to recording settings to use for creation.
+ *
+ * @note Will throw rc when unable to create.
+ */
+RecordingContext::RecordingContext(Console *ptrConsole, const settings::RecordingSettings &Settings)
+ : m_pConsole(NULL)
+ , m_enmState(RECORDINGSTS_UNINITIALIZED)
+ , m_cStreamsEnabled(0)
+{
+ int vrc = RTCritSectInit(&m_CritSect);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+
+ vrc = RecordingContext::createInternal(ptrConsole, Settings);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+}
+
+RecordingContext::~RecordingContext(void)
+{
+ destroyInternal();
+
+ if (RTCritSectIsInitialized(&m_CritSect))
+ RTCritSectDelete(&m_CritSect);
+}
+
+/**
+ * Worker thread for all streams of a recording context.
+ *
+ * For video frames, this also does the RGB/YUV conversion and encoding.
+ */
+DECLCALLBACK(int) RecordingContext::threadMain(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RecordingContext *pThis = (RecordingContext *)pvUser;
+
+ /* Signal that we're up and rockin'. */
+ RTThreadUserSignal(hThreadSelf);
+
+ LogRel2(("Recording: Thread started\n"));
+
+ for (;;)
+ {
+ int vrc = RTSemEventWait(pThis->m_WaitEvent, RT_INDEFINITE_WAIT);
+ AssertRCBreak(vrc);
+
+ Log2Func(("Processing %zu streams\n", pThis->m_vecStreams.size()));
+
+ /* Process common raw blocks (data which not has been encoded yet). */
+ vrc = pThis->processCommonData(pThis->m_mapBlocksRaw, 100 /* ms timeout */);
+
+ /** @todo r=andy This is inefficient -- as we already wake up this thread
+ * for every screen from Main, we here go again (on every wake up) through
+ * all screens. */
+ RecordingStreams::iterator itStream = pThis->m_vecStreams.begin();
+ while (itStream != pThis->m_vecStreams.end())
+ {
+ RecordingStream *pStream = (*itStream);
+
+ /* Hand-in common encoded blocks. */
+ vrc = pStream->Process(pThis->m_mapBlocksEncoded);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Processing stream #%RU16 failed (%Rrc)\n", pStream->GetID(), vrc));
+ break;
+ }
+
+ ++itStream;
+ }
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Recording: Encoding thread failed (%Rrc)\n", vrc));
+
+ /* Keep going in case of errors. */
+
+ if (ASMAtomicReadBool(&pThis->m_fShutdown))
+ {
+ LogFunc(("Thread is shutting down ...\n"));
+ break;
+ }
+
+ } /* for */
+
+ LogRel2(("Recording: Thread ended\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Notifies a recording context's encoding thread.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::threadNotify(void)
+{
+ return RTSemEventSignal(m_WaitEvent);
+}
+
+/**
+ * Worker function for processing common block data.
+ *
+ * @returns VBox status code.
+ * @param mapCommon Common block map to handle.
+ * @param msTimeout Timeout to use for maximum time spending to process data.
+ * Use RT_INDEFINITE_WAIT for processing all data.
+ *
+ * @note Runs in recording thread.
+ */
+int RecordingContext::processCommonData(RecordingBlockMap &mapCommon, RTMSINTERVAL msTimeout)
+{
+ Log2Func(("Processing %zu common blocks (%RU32ms timeout)\n", mapCommon.size(), msTimeout));
+
+ int vrc = VINF_SUCCESS;
+
+ uint64_t const msStart = RTTimeMilliTS();
+ RecordingBlockMap::iterator itCommonBlocks = mapCommon.begin();
+ while (itCommonBlocks != mapCommon.end())
+ {
+ RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
+ while (itBlock != itCommonBlocks->second->List.end())
+ {
+ RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
+ switch (pBlockCommon->enmType)
+ {
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ case RECORDINGBLOCKTYPE_AUDIO:
+ {
+ PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData;
+
+ RECORDINGFRAME Frame;
+ Frame.msTimestamp = pBlockCommon->msTimestamp;
+ Frame.Audio.pvBuf = pAudioFrame->pvBuf;
+ Frame.Audio.cbBuf = pAudioFrame->cbBuf;
+
+ vrc = recordingCodecEncode(&m_CodecAudio, &Frame, NULL, NULL);
+ break;
+ }
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+ default:
+ /* Skip unknown stuff. */
+ break;
+ }
+
+ itCommonBlocks->second->List.erase(itBlock);
+ delete pBlockCommon;
+ itBlock = itCommonBlocks->second->List.begin();
+
+ if (RT_FAILURE(vrc) || RTTimeMilliTS() > msStart + msTimeout)
+ break;
+ }
+
+ /* If no entries are left over in the block map, remove it altogether. */
+ if (itCommonBlocks->second->List.empty())
+ {
+ delete itCommonBlocks->second;
+ mapCommon.erase(itCommonBlocks);
+ itCommonBlocks = mapCommon.begin();
+ }
+ else
+ ++itCommonBlocks;
+
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ return vrc;
+}
+
+/**
+ * Writes common block data (i.e. shared / the same) in all streams.
+ *
+ * The multiplexing is needed to supply all recorded (enabled) screens with the same
+ * data at the same given point in time.
+ *
+ * Currently this only is being used for audio data.
+ *
+ * @returns VBox status code.
+ * @param mapCommon Common block map to write data to.
+ * @param pCodec Pointer to codec instance which has written the data.
+ * @param pvData Pointer to written data (encoded).
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param msTimestamp Absolute PTS (in ms) of the written data.
+ * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
+ */
+int RecordingContext::writeCommonData(RecordingBlockMap &mapCommon, PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
+ uint64_t msTimestamp, uint32_t uFlags)
+{
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("pCodec=%p, cbData=%zu, msTimestamp=%zu, uFlags=%#x\n",
+ pCodec, cbData, msTimestamp, uFlags));
+
+ /** @todo Optimize this! Three allocations in here! */
+
+ RECORDINGBLOCKTYPE const enmType = pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
+ ? RECORDINGBLOCKTYPE_AUDIO : RECORDINGBLOCKTYPE_UNKNOWN;
+
+ AssertReturn(enmType != RECORDINGBLOCKTYPE_UNKNOWN, VERR_NOT_SUPPORTED);
+
+ RecordingBlock *pBlock = new RecordingBlock();
+
+ switch (enmType)
+ {
+ case RECORDINGBLOCKTYPE_AUDIO:
+ {
+ PRECORDINGAUDIOFRAME pFrame = (PRECORDINGAUDIOFRAME)RTMemAlloc(sizeof(RECORDINGAUDIOFRAME));
+ AssertPtrReturn(pFrame, VERR_NO_MEMORY);
+
+ pFrame->pvBuf = (uint8_t *)RTMemAlloc(cbData);
+ AssertPtrReturn(pFrame->pvBuf, VERR_NO_MEMORY);
+ pFrame->cbBuf = cbData;
+
+ memcpy(pFrame->pvBuf, pvData, cbData);
+
+ pBlock->enmType = enmType;
+ pBlock->pvData = pFrame;
+ pBlock->cbData = sizeof(RECORDINGAUDIOFRAME) + cbData;
+ pBlock->cRefs = m_cStreamsEnabled;
+ pBlock->msTimestamp = msTimestamp;
+ pBlock->uFlags = uFlags;
+
+ break;
+ }
+
+ default:
+ AssertFailed();
+ break;
+ }
+
+ lock();
+
+ int vrc;
+
+ try
+ {
+ RecordingBlockMap::iterator itBlocks = mapCommon.find(msTimestamp);
+ if (itBlocks == mapCommon.end())
+ {
+ RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
+ pRecordingBlocks->List.push_back(pBlock);
+
+ mapCommon.insert(std::make_pair(msTimestamp, pRecordingBlocks));
+ }
+ else
+ itBlocks->second->List.push_back(pBlock);
+
+ vrc = VINF_SUCCESS;
+ }
+ catch (const std::exception &ex)
+ {
+ RT_NOREF(ex);
+ vrc = VERR_NO_MEMORY;
+ }
+
+ unlock();
+
+ if (RT_SUCCESS(vrc))
+ vrc = threadNotify();
+
+ return vrc;
+}
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+/**
+ * Callback function for writing encoded audio data into the common encoded block map.
+ *
+ * This is called by the audio codec when finishing encoding audio data.
+ *
+ * @copydoc RECORDINGCODECCALLBACKS::pfnWriteData
+ */
+/* static */
+DECLCALLBACK(int) RecordingContext::audioCodecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
+ uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
+{
+ RecordingContext *pThis = (RecordingContext *)pvUser;
+ return pThis->writeCommonData(pThis->m_mapBlocksEncoded, pCodec, pvData, cbData, msAbsPTS, uFlags);
+}
+
+/**
+ * Initializes the audio codec for a (multiplexing) recording context.
+ *
+ * @returns VBox status code.
+ * @param screenSettings Reference to recording screen settings to use for initialization.
+ */
+int RecordingContext::audioInit(const settings::RecordingScreenSettings &screenSettings)
+{
+ RecordingAudioCodec_T const enmCodec = screenSettings.Audio.enmCodec;
+
+ if (enmCodec == RecordingAudioCodec_None)
+ {
+ LogRel2(("Recording: No audio codec configured, skipping audio init\n"));
+ return VINF_SUCCESS;
+ }
+
+ RECORDINGCODECCALLBACKS Callbacks;
+ Callbacks.pvUser = this;
+ Callbacks.pfnWriteData = RecordingContext::audioCodecWriteDataCallback;
+
+ int vrc = recordingCodecCreateAudio(&m_CodecAudio, enmCodec);
+ if (RT_SUCCESS(vrc))
+ vrc = recordingCodecInit(&m_CodecAudio, &Callbacks, screenSettings);
+
+ return vrc;
+}
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+
+/**
+ * Creates a recording context.
+ *
+ * @returns VBox status code.
+ * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
+ * @param Settings Reference to recording settings to use for creation.
+ */
+int RecordingContext::createInternal(Console *ptrConsole, const settings::RecordingSettings &Settings)
+{
+ int vrc = VINF_SUCCESS;
+
+ /* Copy the settings to our context. */
+ m_Settings = Settings;
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ settings::RecordingScreenSettingsMap::const_iterator itScreen0 = m_Settings.mapScreens.begin();
+ AssertReturn(itScreen0 != m_Settings.mapScreens.end(), VERR_WRONG_ORDER);
+
+ /* We always use the audio settings from screen 0, as we multiplex the audio data anyway. */
+ settings::RecordingScreenSettings const &screen0Settings = itScreen0->second;
+
+ vrc = this->audioInit(screen0Settings);
+ if (RT_FAILURE(vrc))
+ return vrc;
+#endif
+
+ m_pConsole = ptrConsole;
+
+ settings::RecordingScreenSettingsMap::const_iterator itScreen = m_Settings.mapScreens.begin();
+ while (itScreen != m_Settings.mapScreens.end())
+ {
+ RecordingStream *pStream = NULL;
+ try
+ {
+ pStream = new RecordingStream(this, itScreen->first /* Screen ID */, itScreen->second);
+ m_vecStreams.push_back(pStream);
+ if (itScreen->second.fEnabled)
+ m_cStreamsEnabled++;
+ LogFlowFunc(("pStream=%p\n", pStream));
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+ catch (int vrc_thrown) /* Catch rc thrown by constructor. */
+ {
+ vrc = vrc_thrown;
+ break;
+ }
+
+ ++itScreen;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ m_tsStartMs = RTTimeMilliTS();
+ m_enmState = RECORDINGSTS_CREATED;
+ m_fShutdown = false;
+
+ vrc = RTSemEventCreate(&m_WaitEvent);
+ AssertRCReturn(vrc, vrc);
+ }
+
+ if (RT_FAILURE(vrc))
+ destroyInternal();
+
+ return vrc;
+}
+
+/**
+ * Starts a recording context by creating its worker thread.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::startInternal(void)
+{
+ if (m_enmState == RECORDINGSTS_STARTED)
+ return VINF_SUCCESS;
+
+ Assert(m_enmState == RECORDINGSTS_CREATED);
+
+ int vrc = RTThreadCreate(&m_Thread, RecordingContext::threadMain, (void *)this, 0,
+ RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record");
+
+ if (RT_SUCCESS(vrc)) /* Wait for the thread to start. */
+ vrc = RTThreadUserWait(m_Thread, RT_MS_30SEC /* 30s timeout */);
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Recording: Started\n"));
+ m_enmState = RECORDINGSTS_STARTED;
+ }
+ else
+ Log(("Recording: Failed to start (%Rrc)\n", vrc));
+
+ return vrc;
+}
+
+/**
+ * Stops a recording context by telling the worker thread to stop and finalizing its operation.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::stopInternal(void)
+{
+ if (m_enmState != RECORDINGSTS_STARTED)
+ return VINF_SUCCESS;
+
+ LogThisFunc(("Shutting down thread ...\n"));
+
+ /* Set shutdown indicator. */
+ ASMAtomicWriteBool(&m_fShutdown, true);
+
+ /* Signal the thread and wait for it to shut down. */
+ int vrc = threadNotify();
+ if (RT_SUCCESS(vrc))
+ vrc = RTThreadWait(m_Thread, RT_MS_30SEC /* 30s timeout */, NULL);
+
+ lock();
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Recording: Stopped\n"));
+ m_enmState = RECORDINGSTS_CREATED;
+ }
+ else
+ Log(("Recording: Failed to stop (%Rrc)\n", vrc));
+
+ unlock();
+
+ LogFlowThisFunc(("%Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Destroys a recording context, internal version.
+ */
+void RecordingContext::destroyInternal(void)
+{
+ lock();
+
+ if (m_enmState == RECORDINGSTS_UNINITIALIZED)
+ {
+ unlock();
+ return;
+ }
+
+ int vrc = stopInternal();
+ AssertRCReturnVoid(vrc);
+
+ vrc = RTSemEventDestroy(m_WaitEvent);
+ AssertRCReturnVoid(vrc);
+
+ m_WaitEvent = NIL_RTSEMEVENT;
+
+ RecordingStreams::iterator it = m_vecStreams.begin();
+ while (it != m_vecStreams.end())
+ {
+ RecordingStream *pStream = (*it);
+
+ vrc = pStream->Uninit();
+ AssertRC(vrc);
+
+ delete pStream;
+ pStream = NULL;
+
+ m_vecStreams.erase(it);
+ it = m_vecStreams.begin();
+ }
+
+ /* Sanity. */
+ Assert(m_vecStreams.empty());
+ Assert(m_mapBlocksRaw.size() == 0);
+ Assert(m_mapBlocksEncoded.size() == 0);
+
+ m_enmState = RECORDINGSTS_UNINITIALIZED;
+
+ unlock();
+}
+
+/**
+ * Returns a recording context's current settings.
+ *
+ * @returns The recording context's current settings.
+ */
+const settings::RecordingSettings &RecordingContext::GetConfig(void) const
+{
+ return m_Settings;
+}
+
+/**
+ * Returns the recording stream for a specific screen.
+ *
+ * @returns Recording stream for a specific screen, or NULL if not found.
+ * @param uScreen Screen ID to retrieve recording stream for.
+ */
+RecordingStream *RecordingContext::getStreamInternal(unsigned uScreen) const
+{
+ RecordingStream *pStream;
+
+ try
+ {
+ pStream = m_vecStreams.at(uScreen);
+ }
+ catch (std::out_of_range &)
+ {
+ pStream = NULL;
+ }
+
+ return pStream;
+}
+
+/**
+ * Locks the recording context for serializing access.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::lock(void)
+{
+ int vrc = RTCritSectEnter(&m_CritSect);
+ AssertRC(vrc);
+ return vrc;
+}
+
+/**
+ * Unlocks the recording context for serializing access.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::unlock(void)
+{
+ int vrc = RTCritSectLeave(&m_CritSect);
+ AssertRC(vrc);
+ return vrc;
+}
+
+/**
+ * Retrieves a specific recording stream of a recording context.
+ *
+ * @returns Pointer to recording stream if found, or NULL if not found.
+ * @param uScreen Screen number of recording stream to look up.
+ */
+RecordingStream *RecordingContext::GetStream(unsigned uScreen) const
+{
+ return getStreamInternal(uScreen);
+}
+
+/**
+ * Returns the number of configured recording streams for a recording context.
+ *
+ * @returns Number of configured recording streams.
+ */
+size_t RecordingContext::GetStreamCount(void) const
+{
+ return m_vecStreams.size();
+}
+
+/**
+ * Creates a new recording context.
+ *
+ * @returns VBox status code.
+ * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
+ * @param Settings Reference to recording settings to use for creation.
+ */
+int RecordingContext::Create(Console *ptrConsole, const settings::RecordingSettings &Settings)
+{
+ return createInternal(ptrConsole, Settings);
+}
+
+/**
+ * Destroys a recording context.
+ */
+void RecordingContext::Destroy(void)
+{
+ destroyInternal();
+}
+
+/**
+ * Starts a recording context.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::Start(void)
+{
+ return startInternal();
+}
+
+/**
+ * Stops a recording context.
+ */
+int RecordingContext::Stop(void)
+{
+ return stopInternal();
+}
+
+/**
+ * Returns if a specific recoding feature is enabled for at least one of the attached
+ * recording streams or not.
+ *
+ * @returns @c true if at least one recording stream has this feature enabled, or @c false if
+ * no recording stream has this feature enabled.
+ * @param enmFeature Recording feature to check for.
+ */
+bool RecordingContext::IsFeatureEnabled(RecordingFeature_T enmFeature)
+{
+ lock();
+
+ RecordingStreams::const_iterator itStream = m_vecStreams.begin();
+ while (itStream != m_vecStreams.end())
+ {
+ if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature))
+ {
+ unlock();
+ return true;
+ }
+ ++itStream;
+ }
+
+ unlock();
+
+ return false;
+}
+
+/**
+ * Returns if this recording context is ready to start recording.
+ *
+ * @returns @c true if recording context is ready, @c false if not.
+ */
+bool RecordingContext::IsReady(void)
+{
+ lock();
+
+ const bool fIsReady = m_enmState >= RECORDINGSTS_CREATED;
+
+ unlock();
+
+ return fIsReady;
+}
+
+/**
+ * Returns if this recording context is ready to accept new recording data for a given screen.
+ *
+ * @returns @c true if the specified screen is ready, @c false if not.
+ * @param uScreen Screen ID.
+ * @param msTimestamp Timestamp (PTS, in ms). Currently not being used.
+ */
+bool RecordingContext::IsReady(uint32_t uScreen, uint64_t msTimestamp)
+{
+ RT_NOREF(msTimestamp);
+
+ lock();
+
+ bool fIsReady = false;
+
+ if (m_enmState != RECORDINGSTS_STARTED)
+ {
+ const RecordingStream *pStream = getStreamInternal(uScreen);
+ if (pStream)
+ fIsReady = pStream->IsReady();
+
+ /* Note: Do not check for other constraints like the video FPS rate here,
+ * as this check then also would affect other (non-FPS related) stuff
+ * like audio data. */
+ }
+
+ unlock();
+
+ return fIsReady;
+}
+
+/**
+ * Returns whether a given recording context has been started or not.
+ *
+ * @returns true if active, false if not.
+ */
+bool RecordingContext::IsStarted(void)
+{
+ lock();
+
+ const bool fIsStarted = m_enmState == RECORDINGSTS_STARTED;
+
+ unlock();
+
+ return fIsStarted;
+}
+
+/**
+ * Checks if a specified limit for recording has been reached.
+ *
+ * @returns true if any limit has been reached.
+ */
+bool RecordingContext::IsLimitReached(void)
+{
+ lock();
+
+ LogFlowThisFunc(("cStreamsEnabled=%RU16\n", m_cStreamsEnabled));
+
+ const bool fLimitReached = m_cStreamsEnabled == 0;
+
+ unlock();
+
+ return fLimitReached;
+}
+
+/**
+ * Checks if a specified limit for recording has been reached.
+ *
+ * @returns true if any limit has been reached.
+ * @param uScreen Screen ID.
+ * @param msTimestamp Timestamp (PTS, in ms) to check for.
+ */
+bool RecordingContext::IsLimitReached(uint32_t uScreen, uint64_t msTimestamp)
+{
+ lock();
+
+ bool fLimitReached = false;
+
+ const RecordingStream *pStream = getStreamInternal(uScreen);
+ if ( !pStream
+ || pStream->IsLimitReached(msTimestamp))
+ {
+ fLimitReached = true;
+ }
+
+ unlock();
+
+ return fLimitReached;
+}
+
+/**
+ * Returns if a specific screen needs to be fed with an update or not.
+ *
+ * @returns @c true if an update is needed, @c false if not.
+ * @param uScreen Screen ID to retrieve update stats for.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+bool RecordingContext::NeedsUpdate( uint32_t uScreen, uint64_t msTimestamp)
+{
+ lock();
+
+ bool fNeedsUpdate = false;
+
+ if (m_enmState == RECORDINGSTS_STARTED)
+ {
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ if ( recordingCodecIsInitialized(&m_CodecAudio)
+ && recordingCodecGetWritable(&m_CodecAudio, msTimestamp) > 0)
+ {
+ fNeedsUpdate = true;
+ }
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+
+ if (!fNeedsUpdate)
+ {
+ const RecordingStream *pStream = getStreamInternal(uScreen);
+ if (pStream)
+ fNeedsUpdate = pStream->NeedsUpdate(msTimestamp);
+ }
+ }
+
+ unlock();
+
+ return fNeedsUpdate;
+}
+
+DECLCALLBACK(int) RecordingContext::OnLimitReached(uint32_t uScreen, int rc)
+{
+ RT_NOREF(uScreen, rc);
+ LogFlowThisFunc(("Stream %RU32 has reached its limit (%Rrc)\n", uScreen, rc));
+
+ lock();
+
+ Assert(m_cStreamsEnabled);
+ m_cStreamsEnabled--;
+
+ LogFlowThisFunc(("cStreamsEnabled=%RU16\n", m_cStreamsEnabled));
+
+ unlock();
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sends an audio frame to the recording thread.
+ *
+ * @returns VBox status code.
+ * @param pvData Audio frame data to send.
+ * @param cbData Size (in bytes) of (encoded) audio frame data.
+ * @param msTimestamp Timestamp (PTS, in ms) of audio playback.
+ */
+int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
+{
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ return writeCommonData(m_mapBlocksRaw, &m_CodecAudio,
+ pvData, cbData, msTimestamp, RECORDINGCODEC_ENC_F_BLOCK_IS_KEY);
+#else
+ RT_NOREF(pvData, cbData, msTimestamp);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+/**
+ * Sends a video frame to the recording thread.
+ *
+ * @thread EMT
+ *
+ * @returns VBox status code.
+ * @param uScreen Screen number to send video frame to.
+ * @param x Starting x coordinate of the video frame.
+ * @param y Starting y coordinate of the video frame.
+ * @param uPixelFormat Pixel format.
+ * @param uBPP Bits Per Pixel (BPP).
+ * @param uBytesPerLine Bytes per scanline.
+ * @param uSrcWidth Width of the video frame.
+ * @param uSrcHeight Height of the video frame.
+ * @param puSrcData Pointer to video frame data.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+int RecordingContext::SendVideoFrame(uint32_t uScreen, uint32_t x, uint32_t y,
+ uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
+ uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData,
+ uint64_t msTimestamp)
+{
+ AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER);
+ AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
+ AssertReturn(puSrcData, VERR_INVALID_POINTER);
+
+ lock();
+
+ RecordingStream *pStream = getStreamInternal(uScreen);
+ if (!pStream)
+ {
+ unlock();
+
+ AssertFailed();
+ return VERR_NOT_FOUND;
+ }
+
+ int vrc = pStream->SendVideoFrame(x, y, uPixelFormat, uBPP, uBytesPerLine, uSrcWidth, uSrcHeight, puSrcData, msTimestamp);
+
+ unlock();
+
+ if ( RT_SUCCESS(vrc)
+ && vrc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */
+ {
+ threadNotify();
+ }
+
+ return vrc;
+}
+
diff --git a/src/VBox/Main/src-client/RecordingCodec.cpp b/src/VBox/Main/src-client/RecordingCodec.cpp
new file mode 100644
index 00000000..58da5d0d
--- /dev/null
+++ b/src/VBox/Main/src-client/RecordingCodec.cpp
@@ -0,0 +1,894 @@
+/* $Id: RecordingCodec.cpp $ */
+/** @file
+ * Recording codec wrapper.
+ */
+
+/*
+ * Copyright (C) 2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/* This code makes use of Vorbis (libvorbis):
+ *
+ * Copyright (c) 2002-2020 Xiph.org Foundation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the Xiph.org Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define LOG_GROUP LOG_GROUP_RECORDING
+#include "LoggingNew.h"
+
+#include <VBox/com/string.h>
+#include <VBox/err.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+
+#include "RecordingInternals.h"
+#include "RecordingUtils.h"
+#include "WebMWriter.h"
+
+#include <math.h>
+
+
+/*********************************************************************************************************************************
+* VPX (VP8 / VP9) codec *
+*********************************************************************************************************************************/
+
+#ifdef VBOX_WITH_LIBVPX
+/** @copydoc RECORDINGCODECOPS::pfnInit */
+static DECLCALLBACK(int) recordingCodecVPXInit(PRECORDINGCODEC pCodec)
+{
+ pCodec->cbScratch = _4K;
+ pCodec->pvScratch = RTMemAlloc(pCodec->cbScratch);
+ AssertPtrReturn(pCodec->pvScratch, VERR_NO_MEMORY);
+
+ pCodec->Parms.csFrame = 0;
+ pCodec->Parms.cbFrame = pCodec->Parms.Video.uWidth * pCodec->Parms.Video.uHeight * 4 /* 32-bit */;
+ pCodec->Parms.msFrame = 1; /* 1ms per frame. */
+
+# ifdef VBOX_WITH_LIBVPX_VP9
+ vpx_codec_iface_t *pCodecIface = vpx_codec_vp9_cx();
+# else /* Default is using VP8. */
+ vpx_codec_iface_t *pCodecIface = vpx_codec_vp8_cx();
+# endif
+ PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
+
+ vpx_codec_err_t rcv = vpx_codec_enc_config_default(pCodecIface, &pVPX->Cfg, 0 /* Reserved */);
+ if (rcv != VPX_CODEC_OK)
+ {
+ LogRel(("Recording: Failed to get default config for VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ /* Target bitrate in kilobits per second. */
+ pVPX->Cfg.rc_target_bitrate = pCodec->Parms.uBitrate;
+ /* Frame width. */
+ pVPX->Cfg.g_w = pCodec->Parms.Video.uWidth;
+ /* Frame height. */
+ pVPX->Cfg.g_h = pCodec->Parms.Video.uHeight;
+ /* ms per frame. */
+ pVPX->Cfg.g_timebase.num = pCodec->Parms.msFrame;
+ pVPX->Cfg.g_timebase.den = 1000;
+ /* Disable multithreading. */
+ pVPX->Cfg.g_threads = 0;
+
+ /* Initialize codec. */
+ rcv = vpx_codec_enc_init(&pVPX->Ctx, pCodecIface, &pVPX->Cfg, 0 /* Flags */);
+ if (rcv != VPX_CODEC_OK)
+ {
+ LogRel(("Recording: Failed to initialize VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ if (!vpx_img_alloc(&pVPX->RawImage, VPX_IMG_FMT_I420,
+ pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight, 1))
+ {
+ LogRel(("Recording: Failed to allocate image %RU32x%RU32\n", pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ /* Save a pointer to the first raw YUV plane. */
+ pVPX->pu8YuvBuf = pVPX->RawImage.planes[0];
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnDestroy */
+static DECLCALLBACK(int) recordingCodecVPXDestroy(PRECORDINGCODEC pCodec)
+{
+ PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
+
+ vpx_img_free(&pVPX->RawImage);
+ pVPX->pu8YuvBuf = NULL; /* Was pointing to VPX.RawImage. */
+
+ vpx_codec_err_t rcv = vpx_codec_destroy(&pVPX->Ctx);
+ Assert(rcv == VPX_CODEC_OK); RT_NOREF(rcv);
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnParseOptions */
+static DECLCALLBACK(int) recordingCodecVPXParseOptions(PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions)
+{
+ size_t pos = 0;
+ com::Utf8Str key, value;
+ while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos)
+ {
+ if (key.compare("vc_quality", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ const PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
+
+ if (value.compare("realtime", com::Utf8Str::CaseInsensitive) == 0)
+ pVPX->uEncoderDeadline = VPX_DL_REALTIME;
+ else if (value.compare("good", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ AssertStmt(pCodec->Parms.Video.uFPS, pCodec->Parms.Video.uFPS = 25);
+ pVPX->uEncoderDeadline = 1000000 / pCodec->Parms.Video.uFPS;
+ }
+ else if (value.compare("best", com::Utf8Str::CaseInsensitive) == 0)
+ pVPX->uEncoderDeadline = VPX_DL_BEST_QUALITY;
+ else
+ pVPX->uEncoderDeadline = value.toUInt32();
+ }
+ else
+ LogRel2(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str()));
+ } /* while */
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnEncode */
+static DECLCALLBACK(int) recordingCodecVPXEncode(PRECORDINGCODEC pCodec, PRECORDINGFRAME pFrame,
+ size_t *pcEncoded, size_t *pcbEncoded)
+{
+ RT_NOREF(pcEncoded, pcbEncoded);
+
+ AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
+
+ PRECORDINGVIDEOFRAME pVideoFrame = pFrame->VideoPtr;
+
+ int vrc = RecordingUtilsRGBToYUV(pVideoFrame->enmPixelFmt,
+ /* Destination */
+ pCodec->Video.VPX.pu8YuvBuf, pVideoFrame->uWidth, pVideoFrame->uHeight,
+ /* Source */
+ pVideoFrame->pu8RGBBuf, pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight);
+
+ PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
+
+ /* Presentation TimeStamp (PTS). */
+ vpx_codec_pts_t pts = pFrame->msTimestamp;
+ vpx_codec_err_t rcv = vpx_codec_encode(&pVPX->Ctx,
+ &pVPX->RawImage,
+ pts /* Timestamp */,
+ pCodec->Parms.Video.uDelayMs /* How long to show this frame */,
+ 0 /* Flags */,
+ pVPX->uEncoderDeadline /* Quality setting */);
+ if (rcv != VPX_CODEC_OK)
+ {
+ if (pCodec->State.cEncErrors++ < 64) /** @todo Make this configurable. */
+ LogRel(("Recording: Failed to encode video frame: %s\n", vpx_codec_err_to_string(rcv)));
+ return VERR_RECORDING_ENCODING_FAILED;
+ }
+
+ pCodec->State.cEncErrors = 0;
+
+ vpx_codec_iter_t iter = NULL;
+ vrc = VERR_NO_DATA;
+ for (;;)
+ {
+ const vpx_codec_cx_pkt_t *pPkt = vpx_codec_get_cx_data(&pVPX->Ctx, &iter);
+ if (!pPkt)
+ break;
+
+ switch (pPkt->kind)
+ {
+ case VPX_CODEC_CX_FRAME_PKT:
+ {
+ /* Calculate the absolute PTS of this frame (in ms). */
+ uint64_t tsAbsPTSMs = pPkt->data.frame.pts * 1000
+ * (uint64_t)pCodec->Video.VPX.Cfg.g_timebase.num / pCodec->Video.VPX.Cfg.g_timebase.den;
+
+ const bool fKeyframe = RT_BOOL(pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
+
+ uint32_t fFlags = RECORDINGCODEC_ENC_F_NONE;
+ if (fKeyframe)
+ fFlags |= RECORDINGCODEC_ENC_F_BLOCK_IS_KEY;
+ if (pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
+ fFlags |= RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE;
+
+ vrc = pCodec->Callbacks.pfnWriteData(pCodec, pPkt->data.frame.buf, pPkt->data.frame.sz,
+ tsAbsPTSMs, fFlags, pCodec->Callbacks.pvUser);
+ break;
+ }
+
+ default:
+ AssertFailed();
+ LogFunc(("Unexpected video packet type %ld\n", pPkt->kind));
+ break;
+ }
+ }
+
+ return vrc;
+}
+#endif /* VBOX_WITH_LIBVPX */
+
+
+/*********************************************************************************************************************************
+* Ogg Vorbis codec *
+*********************************************************************************************************************************/
+
+#ifdef VBOX_WITH_LIBVORBIS
+/** @copydoc RECORDINGCODECOPS::pfnInit */
+static DECLCALLBACK(int) recordingCodecVorbisInit(PRECORDINGCODEC pCodec)
+{
+ pCodec->cbScratch = _4K;
+ pCodec->pvScratch = RTMemAlloc(pCodec->cbScratch);
+ AssertPtrReturn(pCodec->pvScratch, VERR_NO_MEMORY);
+
+ const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+
+ /** @todo BUGBUG When left out this call, vorbis_block_init() does not find oggpack_writeinit and all goes belly up ... */
+ oggpack_buffer b;
+ oggpack_writeinit(&b);
+
+ vorbis_info_init(&pCodec->Audio.Vorbis.info);
+
+ int vorbis_rc;
+ if (pCodec->Parms.uBitrate == 0) /* No bitrate management? Then go for ABR (Average Bit Rate) only. */
+ vorbis_rc = vorbis_encode_init_vbr(&pCodec->Audio.Vorbis.info,
+ PDMAudioPropsChannels(pPCMProps), PDMAudioPropsHz(pPCMProps),
+ (float).4 /* Quality, from -.1 (lowest) to 1 (highest) */);
+ else
+ vorbis_rc = vorbis_encode_setup_managed(&pCodec->Audio.Vorbis.info, PDMAudioPropsChannels(pPCMProps), PDMAudioPropsHz(pPCMProps),
+ -1 /* max bitrate (unset) */, pCodec->Parms.uBitrate /* kbps, nominal */, -1 /* min bitrate (unset) */);
+ if (vorbis_rc)
+ {
+ LogRel(("Recording: Audio codec failed to setup %s mode (bitrate %RU32): %d\n",
+ pCodec->Parms.uBitrate == 0 ? "VBR" : "bitrate management", pCodec->Parms.uBitrate, vorbis_rc));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ vorbis_rc = vorbis_encode_setup_init(&pCodec->Audio.Vorbis.info);
+ if (vorbis_rc)
+ {
+ LogRel(("Recording: vorbis_encode_setup_init() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ /* Initialize the analysis state and encoding storage. */
+ vorbis_rc = vorbis_analysis_init(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.info);
+ if (vorbis_rc)
+ {
+ vorbis_info_clear(&pCodec->Audio.Vorbis.info);
+ LogRel(("Recording: vorbis_analysis_init() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ vorbis_rc = vorbis_block_init(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.block_cur);
+ if (vorbis_rc)
+ {
+ vorbis_info_clear(&pCodec->Audio.Vorbis.info);
+ LogRel(("Recording: vorbis_block_init() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ if (!pCodec->Parms.msFrame) /* No ms per frame defined? Use default. */
+ pCodec->Parms.msFrame = VBOX_RECORDING_VORBIS_FRAME_MS_DEFAULT;
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnDestroy */
+static DECLCALLBACK(int) recordingCodecVorbisDestroy(PRECORDINGCODEC pCodec)
+{
+ PRECORDINGCODECVORBIS pVorbis = &pCodec->Audio.Vorbis;
+
+ vorbis_block_clear(&pVorbis->block_cur);
+ vorbis_dsp_clear (&pVorbis->dsp_state);
+ vorbis_info_clear (&pVorbis->info);
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnEncode */
+static DECLCALLBACK(int) recordingCodecVorbisEncode(PRECORDINGCODEC pCodec,
+ const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded)
+{
+ const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+
+ Assert (pCodec->Parms.cbFrame);
+ AssertReturn(pFrame->Audio.cbBuf % pCodec->Parms.cbFrame == 0, VERR_INVALID_PARAMETER);
+ Assert (pFrame->Audio.cbBuf);
+ AssertReturn(pFrame->Audio.cbBuf % PDMAudioPropsFrameSize(pPCMProps) == 0, VERR_INVALID_PARAMETER);
+ AssertReturn(pCodec->cbScratch >= pFrame->Audio.cbBuf, VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+
+ int const cbFrame = PDMAudioPropsFrameSize(pPCMProps);
+ int const cFrames = (int)(pFrame->Audio.cbBuf / cbFrame);
+
+ /* Write non-interleaved frames. */
+ float **buffer = vorbis_analysis_buffer(&pCodec->Audio.Vorbis.dsp_state, cFrames);
+ int16_t *puSrc = (int16_t *)pFrame->Audio.pvBuf; RT_NOREF(puSrc);
+
+ /* Convert samples into floating point. */
+ /** @todo This is sloooooooooooow! Optimize this! */
+ uint8_t const cChannels = PDMAudioPropsChannels(pPCMProps);
+ AssertReturn(cChannels == 2, VERR_NOT_SUPPORTED);
+
+ float const div = 1.0f / 32768.0f;
+
+ for(int f = 0; f < cFrames; f++)
+ {
+ buffer[0][f] = (float)puSrc[0] * div;
+ buffer[1][f] = (float)puSrc[1] * div;
+ puSrc += cChannels;
+ }
+
+ int vorbis_rc = vorbis_analysis_wrote(&pCodec->Audio.Vorbis.dsp_state, cFrames);
+ if (vorbis_rc)
+ {
+ LogRel(("Recording: vorbis_analysis_wrote() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_ENCODING_FAILED;
+ }
+
+ if (pcEncoded)
+ *pcEncoded = 0;
+ if (pcbEncoded)
+ *pcbEncoded = 0;
+
+ size_t cBlocksEncoded = 0;
+ size_t cBytesEncoded = 0;
+
+ uint8_t *puDst = (uint8_t *)pCodec->pvScratch;
+
+ while (vorbis_analysis_blockout(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.block_cur) == 1 /* More available? */)
+ {
+ vorbis_rc = vorbis_analysis(&pCodec->Audio.Vorbis.block_cur, NULL);
+ if (vorbis_rc < 0)
+ {
+ LogRel(("Recording: vorbis_analysis() failed (%d)\n", vorbis_rc));
+ vorbis_rc = 0; /* Reset */
+ vrc = VERR_RECORDING_ENCODING_FAILED;
+ break;
+ }
+
+ vorbis_rc = vorbis_bitrate_addblock(&pCodec->Audio.Vorbis.block_cur);
+ if (vorbis_rc < 0)
+ {
+ LogRel(("Recording: vorbis_bitrate_addblock() failed (%d)\n", vorbis_rc));
+ vorbis_rc = 0; /* Reset */
+ vrc = VERR_RECORDING_ENCODING_FAILED;
+ break;
+ }
+
+ /* Vorbis expects us to flush packets one at a time directly to the container.
+ *
+ * If we flush more than one packet in a row, players can't decode this then. */
+ ogg_packet op;
+ while ((vorbis_rc = vorbis_bitrate_flushpacket(&pCodec->Audio.Vorbis.dsp_state, &op)) > 0)
+ {
+ cBytesEncoded += op.bytes;
+ AssertBreakStmt(cBytesEncoded <= pCodec->cbScratch, vrc = VERR_BUFFER_OVERFLOW);
+ cBlocksEncoded++;
+
+ vrc = pCodec->Callbacks.pfnWriteData(pCodec, op.packet, (size_t)op.bytes, pCodec->State.tsLastWrittenMs,
+ RECORDINGCODEC_ENC_F_BLOCK_IS_KEY /* Every Vorbis frame is a key frame */,
+ pCodec->Callbacks.pvUser);
+ }
+
+ RT_NOREF(puDst);
+
+ /* Note: When vorbis_rc is 0, this marks the last packet, a negative values means error. */
+ if (vorbis_rc < 0)
+ {
+ LogRel(("Recording: vorbis_bitrate_flushpacket() failed (%d)\n", vorbis_rc));
+ vorbis_rc = 0; /* Reset */
+ vrc = VERR_RECORDING_ENCODING_FAILED;
+ break;
+ }
+ }
+
+ if (vorbis_rc < 0)
+ {
+ LogRel(("Recording: vorbis_analysis_blockout() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_ENCODING_FAILED;
+ }
+
+ if (pcbEncoded)
+ *pcbEncoded = 0;
+ if (pcEncoded)
+ *pcEncoded = 0;
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Recording: Encoding Vorbis audio data failed, rc=%Rrc\n", vrc));
+
+ Log3Func(("cbSrc=%zu, cbDst=%zu, cEncoded=%zu, cbEncoded=%zu, vrc=%Rrc\n",
+ pFrame->Audio.cbBuf, pCodec->cbScratch, cBlocksEncoded, cBytesEncoded, vrc));
+
+ return vrc;
+}
+
+static DECLCALLBACK(int) recordingCodecVorbisFinalize(PRECORDINGCODEC pCodec)
+{
+ int vorbis_rc = vorbis_analysis_wrote(&pCodec->Audio.Vorbis.dsp_state, 0 /* Means finalize */);
+ if (vorbis_rc)
+ {
+ LogRel(("Recording: vorbis_analysis_wrote() failed for finalizing stream (%d)\n", vorbis_rc));
+ return VERR_RECORDING_ENCODING_FAILED;
+ }
+
+ return VINF_SUCCESS;
+}
+#endif /* VBOX_WITH_LIBVORBIS */
+
+
+/*********************************************************************************************************************************
+* Codec API *
+*********************************************************************************************************************************/
+
+/**
+ * Initializes an audio codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to initialize.
+ * @param pCallbacks Codec callback table to use for the codec.
+ * @param Settings Screen settings to use for initialization.
+ */
+static int recordingCodecInitAudio(const PRECORDINGCODEC pCodec,
+ const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
+{
+ AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
+
+ com::Utf8Str strCodec;
+ settings::RecordingScreenSettings::audioCodecToString(pCodec->Parms.enmAudioCodec, strCodec);
+ LogRel(("Recording: Initializing audio codec '%s'\n", strCodec.c_str()));
+
+ const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+
+ PDMAudioPropsInit(pPCMProps,
+ Settings.Audio.cBits / 8,
+ true /* fSigned */, Settings.Audio.cChannels, Settings.Audio.uHz);
+ pCodec->Parms.uBitrate = 0; /** @todo No bitrate management for audio yet. */
+
+ if (pCallbacks)
+ memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS));
+
+ int vrc = VINF_SUCCESS;
+
+ if (pCodec->Ops.pfnParseOptions)
+ vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions);
+
+ if (RT_SUCCESS(vrc))
+ vrc = pCodec->Ops.pfnInit(pCodec);
+
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(PDMAudioPropsAreValid(pPCMProps));
+
+ uint32_t uBitrate = pCodec->Parms.uBitrate; /* Bitrate management could have been changed by pfnInit(). */
+
+ LogRel2(("Recording: Audio codec is initialized with %RU32Hz, %RU8 channel(s), %RU8 bits per sample\n",
+ PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps), PDMAudioPropsSampleBits(pPCMProps)));
+ LogRel2(("Recording: Audio codec's bitrate management is %s (%RU32 kbps)\n", uBitrate ? "enabled" : "disabled", uBitrate));
+
+ if (!pCodec->Parms.msFrame || pCodec->Parms.msFrame >= RT_MS_1SEC) /* Not set yet by codec stuff above? */
+ pCodec->Parms.msFrame = 20; /* 20ms by default should be a sensible value; to prevent division by zero. */
+
+ pCodec->Parms.csFrame = PDMAudioPropsHz(pPCMProps) / (RT_MS_1SEC / pCodec->Parms.msFrame);
+ pCodec->Parms.cbFrame = PDMAudioPropsFramesToBytes(pPCMProps, pCodec->Parms.csFrame);
+
+ LogFlowFunc(("cbSample=%RU32, msFrame=%RU32 -> csFrame=%RU32, cbFrame=%RU32, uBitrate=%RU32\n",
+ PDMAudioPropsSampleSize(pPCMProps), pCodec->Parms.msFrame, pCodec->Parms.csFrame, pCodec->Parms.cbFrame, pCodec->Parms.uBitrate));
+ }
+ else
+ LogRel(("Recording: Error initializing audio codec (%Rrc)\n", vrc));
+
+ return vrc;
+}
+
+/**
+ * Initializes a video codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to initialize.
+ * @param pCallbacks Codec callback table to use for the codec.
+ * @param Settings Screen settings to use for initialization.
+ */
+static int recordingCodecInitVideo(const PRECORDINGCODEC pCodec,
+ const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
+{
+ com::Utf8Str strTemp;
+ settings::RecordingScreenSettings::videoCodecToString(pCodec->Parms.enmVideoCodec, strTemp);
+ LogRel(("Recording: Initializing video codec '%s'\n", strTemp.c_str()));
+
+ pCodec->Parms.uBitrate = Settings.Video.ulRate;
+ pCodec->Parms.Video.uFPS = Settings.Video.ulFPS;
+ pCodec->Parms.Video.uWidth = Settings.Video.ulWidth;
+ pCodec->Parms.Video.uHeight = Settings.Video.ulHeight;
+ pCodec->Parms.Video.uDelayMs = RT_MS_1SEC / pCodec->Parms.Video.uFPS;
+
+ if (pCallbacks)
+ memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS));
+
+ AssertReturn(pCodec->Parms.uBitrate, VERR_INVALID_PARAMETER); /* Bitrate must be set. */
+ AssertStmt(pCodec->Parms.Video.uFPS, pCodec->Parms.Video.uFPS = 25); /* Prevent division by zero. */
+
+ AssertReturn(pCodec->Parms.Video.uHeight, VERR_INVALID_PARAMETER);
+ AssertReturn(pCodec->Parms.Video.uWidth, VERR_INVALID_PARAMETER);
+ AssertReturn(pCodec->Parms.Video.uDelayMs, VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+
+ if (pCodec->Ops.pfnParseOptions)
+ vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions);
+
+ if ( RT_SUCCESS(vrc)
+ && pCodec->Ops.pfnInit)
+ vrc = pCodec->Ops.pfnInit(pCodec);
+
+ if (RT_SUCCESS(vrc))
+ {
+ pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO;
+ pCodec->Parms.enmVideoCodec = RecordingVideoCodec_VP8; /** @todo No VP9 yet. */
+ }
+ else
+ LogRel(("Recording: Error initializing video codec (%Rrc)\n", vrc));
+
+ return vrc;
+}
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+/**
+ * Lets an audio codec parse advanced options given from a string.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to parse options for.
+ * @param strOptions Options string to parse.
+ */
+static DECLCALLBACK(int) recordingCodecAudioParseOptions(PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions)
+{
+ AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
+
+ size_t pos = 0;
+ com::Utf8Str key, value;
+ while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos)
+ {
+ if (key.compare("ac_profile", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ if (value.compare("low", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ PDMAudioPropsInit(&pCodec->Parms.Audio.PCMProps, 16, true /* fSigned */, 1 /* Channels */, 8000 /* Hz */);
+ }
+ else if (value.startsWith("med" /* "med[ium]" */, com::Utf8Str::CaseInsensitive) == 0)
+ {
+ /* Stay with the defaults. */
+ }
+ else if (value.compare("high", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ PDMAudioPropsInit(&pCodec->Parms.Audio.PCMProps, 16, true /* fSigned */, 2 /* Channels */, 48000 /* Hz */);
+ }
+ }
+ else
+ LogRel(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str()));
+
+ } /* while */
+
+ return VINF_SUCCESS;
+}
+#endif
+
+static void recordingCodecReset(PRECORDINGCODEC pCodec)
+{
+ pCodec->State.tsLastWrittenMs = 0;
+
+ pCodec->State.cEncErrors = 0;
+#ifdef VBOX_WITH_STATISTICS
+ pCodec->STAM.cEncBlocks = 0;
+ pCodec->STAM.msEncTotal = 0;
+#endif
+}
+
+/**
+ * Common code for codec creation.
+ *
+ * @param pCodec Codec instance to create.
+ */
+static void recordingCodecCreateCommon(PRECORDINGCODEC pCodec)
+{
+ RT_ZERO(pCodec->Ops);
+ RT_ZERO(pCodec->Callbacks);
+}
+
+/**
+ * Creates an audio codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to create.
+ * @param enmAudioCodec Audio codec to create.
+ */
+int recordingCodecCreateAudio(PRECORDINGCODEC pCodec, RecordingAudioCodec_T enmAudioCodec)
+{
+ int vrc;
+
+ recordingCodecCreateCommon(pCodec);
+
+ switch (enmAudioCodec)
+ {
+# ifdef VBOX_WITH_LIBVORBIS
+ case RecordingAudioCodec_OggVorbis:
+ {
+ pCodec->Ops.pfnInit = recordingCodecVorbisInit;
+ pCodec->Ops.pfnDestroy = recordingCodecVorbisDestroy;
+ pCodec->Ops.pfnParseOptions = recordingCodecAudioParseOptions;
+ pCodec->Ops.pfnEncode = recordingCodecVorbisEncode;
+ pCodec->Ops.pfnFinalize = recordingCodecVorbisFinalize;
+
+ vrc = VINF_SUCCESS;
+ break;
+ }
+# endif /* VBOX_WITH_LIBVORBIS */
+
+ default:
+ LogRel(("Recording: Selected codec is not supported!\n"));
+ vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED;
+ break;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pCodec->Parms.enmType = RECORDINGCODECTYPE_AUDIO;
+ pCodec->Parms.enmAudioCodec = enmAudioCodec;
+ }
+
+ return vrc;
+}
+
+/**
+ * Creates a video codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to create.
+ * @param enmVideoCodec Video codec to create.
+ */
+int recordingCodecCreateVideo(PRECORDINGCODEC pCodec, RecordingVideoCodec_T enmVideoCodec)
+{
+ int vrc;
+
+ recordingCodecCreateCommon(pCodec);
+
+ switch (enmVideoCodec)
+ {
+# ifdef VBOX_WITH_LIBVPX
+ case RecordingVideoCodec_VP8:
+ {
+ pCodec->Ops.pfnInit = recordingCodecVPXInit;
+ pCodec->Ops.pfnDestroy = recordingCodecVPXDestroy;
+ pCodec->Ops.pfnParseOptions = recordingCodecVPXParseOptions;
+ pCodec->Ops.pfnEncode = recordingCodecVPXEncode;
+
+ vrc = VINF_SUCCESS;
+ break;
+ }
+# endif /* VBOX_WITH_LIBVPX */
+
+ default:
+ vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED;
+ break;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO;
+ pCodec->Parms.enmVideoCodec = enmVideoCodec;
+ }
+
+ return vrc;
+}
+
+/**
+ * Initializes a codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to initialize.
+ * @param pCallbacks Codec callback table to use. Optional and may be NULL.
+ * @param Settings Settings to use for initializing the codec.
+ */
+int recordingCodecInit(const PRECORDINGCODEC pCodec, const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
+{
+ recordingCodecReset(pCodec);
+
+ int vrc;
+ if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO)
+ vrc = recordingCodecInitAudio(pCodec, pCallbacks, Settings);
+ else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO)
+ vrc = recordingCodecInitVideo(pCodec, pCallbacks, Settings);
+ else
+ AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
+
+ return vrc;
+}
+
+/**
+ * Destroys an audio codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to destroy.
+ */
+static int recordingCodecDestroyAudio(PRECORDINGCODEC pCodec)
+{
+ AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
+
+ return pCodec->Ops.pfnDestroy(pCodec);
+}
+
+/**
+ * Destroys a video codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to destroy.
+ */
+static int recordingCodecDestroyVideo(PRECORDINGCODEC pCodec)
+{
+ AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO, VERR_INVALID_PARAMETER);
+
+ return pCodec->Ops.pfnDestroy(pCodec);
+}
+
+/**
+ * Destroys the codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to destroy.
+ */
+int recordingCodecDestroy(PRECORDINGCODEC pCodec)
+{
+ if (pCodec->Parms.enmType == RECORDINGCODECTYPE_INVALID)
+ return VINF_SUCCESS;
+
+ int vrc;
+
+ if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO)
+ vrc = recordingCodecDestroyAudio(pCodec);
+ else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO)
+ vrc =recordingCodecDestroyVideo(pCodec);
+ else
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (pCodec->pvScratch)
+ {
+ Assert(pCodec->cbScratch);
+ RTMemFree(pCodec->pvScratch);
+ pCodec->pvScratch = NULL;
+ pCodec->cbScratch = 0;
+ }
+
+ pCodec->Parms.enmType = RECORDINGCODECTYPE_INVALID;
+ pCodec->Parms.enmVideoCodec = RecordingVideoCodec_None;
+ }
+
+ return vrc;
+}
+
+/**
+ * Feeds the codec encoder with data to encode.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to use.
+ * @param pFrame Pointer to frame data to encode.
+ * @param pcEncoded Where to return the number of encoded blocks in \a pvDst on success. Optional.
+ * @param pcbEncoded Where to return the number of encoded bytes in \a pvDst on success. Optional.
+ */
+int recordingCodecEncode(PRECORDINGCODEC pCodec,
+ const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded)
+{
+ AssertPtrReturn(pCodec->Ops.pfnEncode, VERR_NOT_SUPPORTED);
+
+ size_t cEncoded, cbEncoded;
+ int vrc = pCodec->Ops.pfnEncode(pCodec, pFrame, &cEncoded, &cbEncoded);
+ if (RT_SUCCESS(vrc))
+ {
+ pCodec->State.tsLastWrittenMs = pFrame->msTimestamp;
+
+#ifdef VBOX_WITH_STATISTICS
+ pCodec->STAM.cEncBlocks += cEncoded;
+ pCodec->STAM.msEncTotal += pCodec->Parms.msFrame * cEncoded;
+#endif
+ if (pcEncoded)
+ *pcEncoded = cEncoded;
+ if (pcbEncoded)
+ *pcbEncoded = cbEncoded;
+ }
+
+ return vrc;
+}
+
+/**
+ * Tells the codec that has to finalize the stream.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to finalize stream for.
+ */
+int recordingCodecFinalize(PRECORDINGCODEC pCodec)
+{
+ if (pCodec->Ops.pfnFinalize)
+ return pCodec->Ops.pfnFinalize(pCodec);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Returns whether the codec has been initialized or not.
+ *
+ * @returns @c true if initialized, or @c false if not.
+ * @param pCodec Codec to return initialization status for.
+ */
+bool recordingCodecIsInitialized(const PRECORDINGCODEC pCodec)
+{
+ return pCodec->Ops.pfnInit != NULL; /* pfnInit acts as a beacon for initialization status. */
+}
+
+/**
+ * Returns the number of writable bytes for a given timestamp.
+ *
+ * This basically is a helper function to respect the set frames per second (FPS).
+ *
+ * @returns Number of writable bytes.
+ * @param pCodec Codec to return number of writable bytes for.
+ * @param msTimestamp Timestamp (PTS, in ms) return number of writable bytes for.
+ */
+uint32_t recordingCodecGetWritable(const PRECORDINGCODEC pCodec, uint64_t msTimestamp)
+{
+ Log3Func(("%RU64 -- tsLastWrittenMs=%RU64 + uDelayMs=%RU32\n",
+ msTimestamp, pCodec->State.tsLastWrittenMs,pCodec->Parms.Video.uDelayMs));
+
+ if (msTimestamp < pCodec->State.tsLastWrittenMs + pCodec->Parms.Video.uDelayMs)
+ return 0; /* Too early for writing (respect set FPS). */
+
+ /* For now we just return the complete frame space. */
+ AssertMsg(pCodec->Parms.cbFrame, ("Codec not initialized yet\n"));
+ return pCodec->Parms.cbFrame;
+}
diff --git a/src/VBox/Main/src-client/RecordingInternals.cpp b/src/VBox/Main/src-client/RecordingInternals.cpp
new file mode 100644
index 00000000..15f8168c
--- /dev/null
+++ b/src/VBox/Main/src-client/RecordingInternals.cpp
@@ -0,0 +1,158 @@
+/* $Id: RecordingInternals.cpp $ */
+/** @file
+ * Recording internals code.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "RecordingInternals.h"
+
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+/**
+ * Initializes a recording frame.
+ *
+ * @param pFrame Pointer to video frame to initialize.
+ * @param w Width (in pixel) of video frame.
+ * @param h Height (in pixel) of video frame.
+ * @param uBPP Bits per pixel (BPP).
+ * @param enmPixelFmt Pixel format to use.
+ */
+int RecordingVideoFrameInit(PRECORDINGVIDEOFRAME pFrame, int w, int h, uint8_t uBPP, RECORDINGPIXELFMT enmPixelFmt)
+{
+ /* Calculate bytes per pixel and set pixel format. */
+ const unsigned uBytesPerPixel = uBPP / 8;
+ const size_t cbRGBBuf = w * h * uBytesPerPixel;
+ AssertReturn(cbRGBBuf, VERR_INVALID_PARAMETER);
+
+ pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf);
+ AssertPtrReturn(pFrame->pu8RGBBuf, VERR_NO_MEMORY);
+ pFrame->cbRGBBuf = cbRGBBuf;
+
+ pFrame->uX = 0;
+ pFrame->uY = 0;
+ pFrame->uWidth = w;
+ pFrame->uHeight = h;
+ pFrame->enmPixelFmt = enmPixelFmt;
+ pFrame->uBPP = uBPP;
+ pFrame->uBytesPerLine = w * uBytesPerPixel;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a recording audio frame.
+ *
+ * @param pFrame Pointer to audio frame to destroy.
+ */
+static void recordingAudioFrameDestroy(PRECORDINGAUDIOFRAME pFrame)
+{
+ if (pFrame->pvBuf)
+ {
+ Assert(pFrame->cbBuf);
+ RTMemFree(pFrame->pvBuf);
+ pFrame->cbBuf = 0;
+ }
+}
+
+/**
+ * Frees a previously allocated recording audio frame.
+ *
+ * @param pFrame Audio frame to free. The pointer will be invalid after return.
+ */
+void RecordingAudioFrameFree(PRECORDINGAUDIOFRAME pFrame)
+{
+ if (!pFrame)
+ return;
+
+ recordingAudioFrameDestroy(pFrame);
+
+ RTMemFree(pFrame);
+ pFrame = NULL;
+}
+#endif
+
+/**
+ * Destroys a recording video frame.
+ *
+ * @param pFrame Pointer to video frame to destroy.
+ */
+void RecordingVideoFrameDestroy(PRECORDINGVIDEOFRAME pFrame)
+{
+ if (pFrame->pu8RGBBuf)
+ {
+ Assert(pFrame->cbRGBBuf);
+ RTMemFree(pFrame->pu8RGBBuf);
+ pFrame->cbRGBBuf = 0;
+ }
+}
+
+/**
+ * Frees a recording video frame.
+ *
+ * @returns VBox status code.
+ * @param pFrame Pointer to video frame to free. The pointer will be invalid after return.
+ */
+void RecordingVideoFrameFree(PRECORDINGVIDEOFRAME pFrame)
+{
+ if (!pFrame)
+ return;
+
+ RecordingVideoFrameDestroy(pFrame);
+
+ RTMemFree(pFrame);
+}
+
+/**
+ * Frees a recording frame.
+ *
+ * @returns VBox status code.
+ * @param pFrame Pointer to recording frame to free. The pointer will be invalid after return.
+ */
+void RecordingFrameFree(PRECORDINGFRAME pFrame)
+{
+ if (!pFrame)
+ return;
+
+ switch (pFrame->enmType)
+ {
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ case RECORDINGFRAME_TYPE_AUDIO:
+ recordingAudioFrameDestroy(&pFrame->Audio);
+ break;
+#endif
+ case RECORDINGFRAME_TYPE_VIDEO:
+ RecordingVideoFrameDestroy(&pFrame->Video);
+ break;
+
+ default:
+ AssertFailed();
+ break;
+ }
+
+ RTMemFree(pFrame);
+ pFrame = NULL;
+}
+
diff --git a/src/VBox/Main/src-client/RecordingStream.cpp b/src/VBox/Main/src-client/RecordingStream.cpp
new file mode 100644
index 00000000..d8d4b4f3
--- /dev/null
+++ b/src/VBox/Main/src-client/RecordingStream.cpp
@@ -0,0 +1,1039 @@
+/* $Id: RecordingStream.cpp $ */
+/** @file
+ * Recording stream code.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifdef LOG_GROUP
+# undef LOG_GROUP
+#endif
+#define LOG_GROUP LOG_GROUP_RECORDING
+#include "LoggingNew.h"
+
+#include <iprt/path.h>
+
+#ifdef VBOX_RECORDING_DUMP
+# include <iprt/formats/bmp.h>
+#endif
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+# include <VBox/vmm/pdmaudioinline.h>
+#endif
+
+#include "Recording.h"
+#include "RecordingUtils.h"
+#include "WebMWriter.h"
+
+
+RecordingStream::RecordingStream(RecordingContext *a_pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
+ : m_enmState(RECORDINGSTREAMSTATE_UNINITIALIZED)
+{
+ int vrc2 = initInternal(a_pCtx, uScreen, Settings);
+ if (RT_FAILURE(vrc2))
+ throw vrc2;
+}
+
+RecordingStream::~RecordingStream(void)
+{
+ int vrc2 = uninitInternal();
+ AssertRC(vrc2);
+}
+
+/**
+ * Opens a recording stream.
+ *
+ * @returns VBox status code.
+ * @param screenSettings Recording settings to use.
+ */
+int RecordingStream::open(const settings::RecordingScreenSettings &screenSettings)
+{
+ /* Sanity. */
+ Assert(screenSettings.enmDest != RecordingDestination_None);
+
+ int vrc;
+
+ switch (screenSettings.enmDest)
+ {
+ case RecordingDestination_File:
+ {
+ Assert(screenSettings.File.strName.isNotEmpty());
+
+ const char *pszFile = screenSettings.File.strName.c_str();
+
+ RTFILE hFile = NIL_RTFILE;
+ vrc = RTFileOpen(&hFile, pszFile, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel2(("Recording: Opened file '%s'\n", pszFile));
+
+ try
+ {
+ Assert(File.m_pWEBM == NULL);
+ File.m_pWEBM = new WebMWriter();
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ this->File.m_hFile = hFile;
+ m_ScreenSettings.File.strName = pszFile;
+ }
+ }
+ else
+ LogRel(("Recording: Failed to open file '%s' for screen %RU32, vrc=%Rrc\n",
+ pszFile ? pszFile : "<Unnamed>", m_uScreenID, vrc));
+
+ if (RT_FAILURE(vrc))
+ {
+ if (hFile != NIL_RTFILE)
+ RTFileClose(hFile);
+ }
+
+ break;
+ }
+
+ default:
+ vrc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Returns the recording stream's used configuration.
+ *
+ * @returns The recording stream's used configuration.
+ */
+const settings::RecordingScreenSettings &RecordingStream::GetConfig(void) const
+{
+ return m_ScreenSettings;
+}
+
+/**
+ * Checks if a specified limit for a recording stream has been reached, internal version.
+ *
+ * @returns @c true if any limit has been reached, @c false if not.
+ * @param msTimestamp Timestamp (PTS, in ms) to check for.
+ */
+bool RecordingStream::isLimitReachedInternal(uint64_t msTimestamp) const
+{
+ LogFlowThisFunc(("msTimestamp=%RU64, ulMaxTimeS=%RU32, tsStartMs=%RU64\n",
+ msTimestamp, m_ScreenSettings.ulMaxTimeS, m_tsStartMs));
+
+ if ( m_ScreenSettings.ulMaxTimeS
+ && msTimestamp >= m_tsStartMs + (m_ScreenSettings.ulMaxTimeS * RT_MS_1SEC))
+ {
+ LogRel(("Recording: Time limit for stream #%RU16 has been reached (%RU32s)\n",
+ m_uScreenID, m_ScreenSettings.ulMaxTimeS));
+ return true;
+ }
+
+ if (m_ScreenSettings.enmDest == RecordingDestination_File)
+ {
+ if (m_ScreenSettings.File.ulMaxSizeMB)
+ {
+ uint64_t sizeInMB = this->File.m_pWEBM->GetFileSize() / _1M;
+ if(sizeInMB >= m_ScreenSettings.File.ulMaxSizeMB)
+ {
+ LogRel(("Recording: File size limit for stream #%RU16 has been reached (%RU64MB)\n",
+ m_uScreenID, m_ScreenSettings.File.ulMaxSizeMB));
+ return true;
+ }
+ }
+
+ /* Check for available free disk space */
+ if ( this->File.m_pWEBM
+ && this->File.m_pWEBM->GetAvailableSpace() < 0x100000) /** @todo r=andy WTF? Fix this. */
+ {
+ LogRel(("Recording: Not enough free storage space available, stopping recording\n"));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Internal iteration main loop.
+ * Does housekeeping and recording context notification.
+ *
+ * @returns VBox status code.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+int RecordingStream::iterateInternal(uint64_t msTimestamp)
+{
+ if (!m_fEnabled)
+ return VINF_SUCCESS;
+
+ int vrc;
+
+ if (isLimitReachedInternal(msTimestamp))
+ {
+ vrc = VINF_RECORDING_LIMIT_REACHED;
+ }
+ else
+ vrc = VINF_SUCCESS;
+
+ AssertPtr(m_pCtx);
+
+ switch (vrc)
+ {
+ case VINF_RECORDING_LIMIT_REACHED:
+ {
+ m_fEnabled = false;
+
+ int vrc2 = m_pCtx->OnLimitReached(m_uScreenID, VINF_SUCCESS /* rc */);
+ AssertRC(vrc2);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Checks if a specified limit for a recording stream has been reached.
+ *
+ * @returns @c true if any limit has been reached, @c false if not.
+ * @param msTimestamp Timestamp (PTS, in ms) to check for.
+ */
+bool RecordingStream::IsLimitReached(uint64_t msTimestamp) const
+{
+ if (!IsReady())
+ return true;
+
+ return isLimitReachedInternal(msTimestamp);
+}
+
+/**
+ * Returns whether a recording stream is ready (e.g. enabled and active) or not.
+ *
+ * @returns @c true if ready, @c false if not.
+ */
+bool RecordingStream::IsReady(void) const
+{
+ return m_fEnabled;
+}
+
+/**
+ * Returns if a recording stream needs to be fed with an update or not.
+ *
+ * @returns @c true if an update is needed, @c false if not.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+bool RecordingStream::NeedsUpdate(uint64_t msTimestamp) const
+{
+ return recordingCodecGetWritable((const PRECORDINGCODEC)&m_CodecVideo, msTimestamp) > 0;
+}
+
+/**
+ * Processes a recording stream.
+ * This function takes care of the actual encoding and writing of a certain stream.
+ * As this can be very CPU intensive, this function usually is called from a separate thread.
+ *
+ * @returns VBox status code.
+ * @param mapBlocksCommon Map of common block to process for this stream.
+ *
+ * @note Runs in recording thread.
+ */
+int RecordingStream::Process(RecordingBlockMap &mapBlocksCommon)
+{
+ LogFlowFuncEnter();
+
+ lock();
+
+ if (!m_ScreenSettings.fEnabled)
+ {
+ unlock();
+ return VINF_SUCCESS;
+ }
+
+ int vrc = VINF_SUCCESS;
+
+ RecordingBlockMap::iterator itStreamBlocks = m_Blocks.Map.begin();
+ while (itStreamBlocks != m_Blocks.Map.end())
+ {
+ uint64_t const msTimestamp = itStreamBlocks->first;
+ RecordingBlocks *pBlocks = itStreamBlocks->second;
+
+ AssertPtr(pBlocks);
+
+ while (!pBlocks->List.empty())
+ {
+ RecordingBlock *pBlock = pBlocks->List.front();
+ AssertPtr(pBlock);
+
+ switch (pBlock->enmType)
+ {
+ case RECORDINGBLOCKTYPE_VIDEO:
+ {
+ RECORDINGFRAME Frame;
+ Frame.VideoPtr = (PRECORDINGVIDEOFRAME)pBlock->pvData;
+ Frame.msTimestamp = msTimestamp;
+
+ int vrc2 = recordingCodecEncode(&m_CodecVideo, &Frame, NULL, NULL);
+ AssertRC(vrc2);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ break;
+ }
+
+ default:
+ /* Note: Audio data already is encoded. */
+ break;
+ }
+
+ pBlocks->List.pop_front();
+ delete pBlock;
+ }
+
+ Assert(pBlocks->List.empty());
+ delete pBlocks;
+
+ m_Blocks.Map.erase(itStreamBlocks);
+ itStreamBlocks = m_Blocks.Map.begin();
+ }
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ /* Do we need to multiplex the common audio data to this stream? */
+ if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Audio))
+ {
+ /* As each (enabled) screen has to get the same audio data, look for common (audio) data which needs to be
+ * written to the screen's assigned recording stream. */
+ RecordingBlockMap::iterator itCommonBlocks = mapBlocksCommon.begin();
+ while (itCommonBlocks != mapBlocksCommon.end())
+ {
+ RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
+ while (itBlock != itCommonBlocks->second->List.end())
+ {
+ RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
+ switch (pBlockCommon->enmType)
+ {
+ case RECORDINGBLOCKTYPE_AUDIO:
+ {
+ PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData;
+ AssertPtr(pAudioFrame);
+ AssertPtr(pAudioFrame->pvBuf);
+ Assert(pAudioFrame->cbBuf);
+
+ AssertPtr(this->File.m_pWEBM);
+ int vrc2 = this->File.m_pWEBM->WriteBlock(m_uTrackAudio, pAudioFrame->pvBuf, pAudioFrame->cbBuf, pBlockCommon->msTimestamp, pBlockCommon->uFlags);
+ AssertRC(vrc2);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ break;
+ }
+
+ default:
+ AssertFailed();
+ break;
+ }
+
+ Assert(pBlockCommon->cRefs);
+ pBlockCommon->cRefs--;
+ if (pBlockCommon->cRefs == 0)
+ {
+ itCommonBlocks->second->List.erase(itBlock);
+ delete pBlockCommon;
+ itBlock = itCommonBlocks->second->List.begin();
+ }
+ else
+ ++itBlock;
+ }
+
+ /* If no entries are left over in the block map, remove it altogether. */
+ if (itCommonBlocks->second->List.empty())
+ {
+ delete itCommonBlocks->second;
+ mapBlocksCommon.erase(itCommonBlocks);
+ itCommonBlocks = mapBlocksCommon.begin();
+ }
+ else
+ ++itCommonBlocks;
+
+ LogFunc(("Common blocks: %zu\n", mapBlocksCommon.size()));
+ }
+ }
+#else
+ RT_NOREF(mapBlocksCommon);
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+
+ unlock();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sends a raw (e.g. not yet encoded) audio frame to the recording stream.
+ *
+ * @returns VBox status code.
+ * @param pvData Pointer to audio data.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+int RecordingStream::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
+{
+ AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
+ AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */
+
+ Log3Func(("cbData=%zu, msTimestamp=%RU64\n", cbData, msTimestamp));
+
+ /* As audio data is common across all streams, re-route this to the recording context, where
+ * the data is being encoded and stored in the common blocks queue. */
+ return m_pCtx->SendAudioFrame(pvData, cbData, msTimestamp);
+}
+
+/**
+ * Sends a raw (e.g. not yet encoded) video frame to the recording stream.
+ *
+ * @returns VBox status code. Will return VINF_RECORDING_LIMIT_REACHED if the stream's recording
+ * limit has been reached or VINF_RECORDING_THROTTLED if the frame is too early for the current
+ * FPS setting.
+ * @param x Upper left (X) coordinate where the video frame starts.
+ * @param y Upper left (Y) coordinate where the video frame starts.
+ * @param uPixelFormat Pixel format of the video frame.
+ * @param uBPP Bits per pixel (BPP) of the video frame.
+ * @param uBytesPerLine Bytes per line of the video frame.
+ * @param uSrcWidth Width (in pixels) of the video frame.
+ * @param uSrcHeight Height (in pixels) of the video frame.
+ * @param puSrcData Actual pixel data of the video frame.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+int RecordingStream::SendVideoFrame(uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
+ uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData, uint64_t msTimestamp)
+{
+ AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
+ AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */
+
+ lock();
+
+ Log3Func(("[%RU32 %RU32 %RU32 %RU32] msTimestamp=%RU64\n", x , y, uSrcWidth, uSrcHeight, msTimestamp));
+
+ PRECORDINGVIDEOFRAME pFrame = NULL;
+
+ int vrc = iterateInternal(msTimestamp);
+ if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
+ {
+ unlock();
+ return vrc;
+ }
+
+ do
+ {
+ int xDiff = ((int)m_ScreenSettings.Video.ulWidth - (int)uSrcWidth) / 2;
+ uint32_t w = uSrcWidth;
+ if ((int)w + xDiff + (int)x <= 0) /* Nothing visible. */
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ uint32_t destX;
+ if ((int)x < -xDiff)
+ {
+ w += xDiff + x;
+ x = -xDiff;
+ destX = 0;
+ }
+ else
+ destX = x + xDiff;
+
+ uint32_t h = uSrcHeight;
+ int yDiff = ((int)m_ScreenSettings.Video.ulHeight - (int)uSrcHeight) / 2;
+ if ((int)h + yDiff + (int)y <= 0) /* Nothing visible. */
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ uint32_t destY;
+ if ((int)y < -yDiff)
+ {
+ h += yDiff + (int)y;
+ y = -yDiff;
+ destY = 0;
+ }
+ else
+ destY = y + yDiff;
+
+ if ( destX > m_ScreenSettings.Video.ulWidth
+ || destY > m_ScreenSettings.Video.ulHeight)
+ {
+ vrc = VERR_INVALID_PARAMETER; /* Nothing visible. */
+ break;
+ }
+
+ if (destX + w > m_ScreenSettings.Video.ulWidth)
+ w = m_ScreenSettings.Video.ulWidth - destX;
+
+ if (destY + h > m_ScreenSettings.Video.ulHeight)
+ h = m_ScreenSettings.Video.ulHeight - destY;
+
+ pFrame = (PRECORDINGVIDEOFRAME)RTMemAllocZ(sizeof(RECORDINGVIDEOFRAME));
+ AssertBreakStmt(pFrame, vrc = VERR_NO_MEMORY);
+
+ /* Calculate bytes per pixel and set pixel format. */
+ const unsigned uBytesPerPixel = uBPP / 8;
+ if (uPixelFormat == BitmapFormat_BGR)
+ {
+ switch (uBPP)
+ {
+ case 32:
+ pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB32;
+ break;
+ case 24:
+ pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB24;
+ break;
+ case 16:
+ pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB565;
+ break;
+ default:
+ AssertMsgFailedBreakStmt(("Unknown color depth (%RU32)\n", uBPP), vrc = VERR_NOT_SUPPORTED);
+ break;
+ }
+ }
+ else
+ AssertMsgFailedBreakStmt(("Unknown pixel format (%RU32)\n", uPixelFormat), vrc = VERR_NOT_SUPPORTED);
+
+ const size_t cbRGBBuf = m_ScreenSettings.Video.ulWidth
+ * m_ScreenSettings.Video.ulHeight
+ * uBytesPerPixel;
+ AssertBreakStmt(cbRGBBuf, vrc = VERR_INVALID_PARAMETER);
+
+ pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf);
+ AssertBreakStmt(pFrame->pu8RGBBuf, vrc = VERR_NO_MEMORY);
+ pFrame->cbRGBBuf = cbRGBBuf;
+ pFrame->uWidth = uSrcWidth;
+ pFrame->uHeight = uSrcHeight;
+
+ /* If the current video frame is smaller than video resolution we're going to encode,
+ * clear the frame beforehand to prevent artifacts. */
+ if ( uSrcWidth < m_ScreenSettings.Video.ulWidth
+ || uSrcHeight < m_ScreenSettings.Video.ulHeight)
+ {
+ RT_BZERO(pFrame->pu8RGBBuf, pFrame->cbRGBBuf);
+ }
+
+ /* Calculate start offset in source and destination buffers. */
+ uint32_t offSrc = y * uBytesPerLine + x * uBytesPerPixel;
+ uint32_t offDst = (destY * m_ScreenSettings.Video.ulWidth + destX) * uBytesPerPixel;
+
+#ifdef VBOX_RECORDING_DUMP
+ BMPFILEHDR fileHdr;
+ RT_ZERO(fileHdr);
+
+ BMPWIN3XINFOHDR coreHdr;
+ RT_ZERO(coreHdr);
+
+ fileHdr.uType = BMP_HDR_MAGIC;
+ fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + (w * h * uBytesPerPixel));
+ fileHdr.offBits = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR));
+
+ coreHdr.cbSize = sizeof(BMPWIN3XINFOHDR);
+ coreHdr.uWidth = w;
+ coreHdr.uHeight = h;
+ coreHdr.cPlanes = 1;
+ coreHdr.cBits = uBPP;
+ coreHdr.uXPelsPerMeter = 5000;
+ coreHdr.uYPelsPerMeter = 5000;
+
+ char szFileName[RTPATH_MAX];
+ RTStrPrintf2(szFileName, sizeof(szFileName), "/tmp/VideoRecFrame-%RU32.bmp", m_uScreenID);
+
+ RTFILE fh;
+ int vrc2 = RTFileOpen(&fh, szFileName,
+ RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(vrc2))
+ {
+ RTFileWrite(fh, &fileHdr, sizeof(fileHdr), NULL);
+ RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL);
+ }
+#endif
+ Assert(pFrame->cbRGBBuf >= w * h * uBytesPerPixel);
+
+ /* Do the copy. */
+ for (unsigned int i = 0; i < h; i++)
+ {
+ /* Overflow check. */
+ Assert(offSrc + w * uBytesPerPixel <= uSrcHeight * uBytesPerLine);
+ Assert(offDst + w * uBytesPerPixel <= m_ScreenSettings.Video.ulHeight * m_ScreenSettings.Video.ulWidth * uBytesPerPixel);
+
+ memcpy(pFrame->pu8RGBBuf + offDst, puSrcData + offSrc, w * uBytesPerPixel);
+
+#ifdef VBOX_RECORDING_DUMP
+ if (RT_SUCCESS(rc2))
+ RTFileWrite(fh, pFrame->pu8RGBBuf + offDst, w * uBytesPerPixel, NULL);
+#endif
+ offSrc += uBytesPerLine;
+ offDst += m_ScreenSettings.Video.ulWidth * uBytesPerPixel;
+ }
+
+#ifdef VBOX_RECORDING_DUMP
+ if (RT_SUCCESS(vrc2))
+ RTFileClose(fh);
+#endif
+
+ } while (0);
+
+ if (vrc == VINF_SUCCESS) /* Note: Also could be VINF_TRY_AGAIN. */
+ {
+ RecordingBlock *pBlock = new RecordingBlock();
+ if (pBlock)
+ {
+ AssertPtr(pFrame);
+
+ pBlock->enmType = RECORDINGBLOCKTYPE_VIDEO;
+ pBlock->pvData = pFrame;
+ pBlock->cbData = sizeof(RECORDINGVIDEOFRAME) + pFrame->cbRGBBuf;
+
+ try
+ {
+ RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
+ pRecordingBlocks->List.push_back(pBlock);
+
+ Assert(m_Blocks.Map.find(msTimestamp) == m_Blocks.Map.end());
+ m_Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks));
+ }
+ catch (const std::exception &ex)
+ {
+ RT_NOREF(ex);
+
+ delete pBlock;
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ RecordingVideoFrameFree(pFrame);
+
+ unlock();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Initializes a recording stream.
+ *
+ * @returns VBox status code.
+ * @param pCtx Pointer to recording context.
+ * @param uScreen Screen number to use for this recording stream.
+ * @param Settings Recording screen configuration to use for initialization.
+ */
+int RecordingStream::Init(RecordingContext *pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
+{
+ return initInternal(pCtx, uScreen, Settings);
+}
+
+/**
+ * Initializes a recording stream, internal version.
+ *
+ * @returns VBox status code.
+ * @param pCtx Pointer to recording context.
+ * @param uScreen Screen number to use for this recording stream.
+ * @param screenSettings Recording screen configuration to use for initialization.
+ */
+int RecordingStream::initInternal(RecordingContext *pCtx, uint32_t uScreen,
+ const settings::RecordingScreenSettings &screenSettings)
+{
+ AssertReturn(m_enmState == RECORDINGSTREAMSTATE_UNINITIALIZED, VERR_WRONG_ORDER);
+
+ m_pCtx = pCtx;
+ m_uTrackAudio = UINT8_MAX;
+ m_uTrackVideo = UINT8_MAX;
+ m_tsStartMs = 0;
+ m_uScreenID = uScreen;
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ /* We use the codec from the recording context, as this stream only receives multiplexed data (same audio for all streams). */
+ m_pCodecAudio = m_pCtx->GetCodecAudio();
+#endif
+ m_ScreenSettings = screenSettings;
+
+ settings::RecordingScreenSettings *pSettings = &m_ScreenSettings;
+
+ int vrc = RTCritSectInit(&m_CritSect);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ this->File.m_pWEBM = NULL;
+ this->File.m_hFile = NIL_RTFILE;
+
+ vrc = open(*pSettings);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ const bool fVideoEnabled = pSettings->isFeatureEnabled(RecordingFeature_Video);
+ const bool fAudioEnabled = pSettings->isFeatureEnabled(RecordingFeature_Audio);
+
+ if (fVideoEnabled)
+ {
+ vrc = initVideo(*pSettings);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ switch (pSettings->enmDest)
+ {
+ case RecordingDestination_File:
+ {
+ Assert(pSettings->File.strName.isNotEmpty());
+ const char *pszFile = pSettings->File.strName.c_str();
+
+ AssertPtr(File.m_pWEBM);
+ vrc = File.m_pWEBM->OpenEx(pszFile, &this->File.m_hFile,
+ fAudioEnabled ? pSettings->Audio.enmCodec : RecordingAudioCodec_None,
+ fVideoEnabled ? pSettings->Video.enmCodec : RecordingVideoCodec_None);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Failed to create output file '%s' (%Rrc)\n", pszFile, vrc));
+ break;
+ }
+
+ if (fVideoEnabled)
+ {
+ vrc = this->File.m_pWEBM->AddVideoTrack(&m_CodecVideo,
+ pSettings->Video.ulWidth, pSettings->Video.ulHeight, pSettings->Video.ulFPS,
+ &m_uTrackVideo);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, vrc));
+ break;
+ }
+
+ LogRel(("Recording: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n",
+ m_uScreenID, pSettings->Video.ulWidth, pSettings->Video.ulHeight,
+ pSettings->Video.ulRate, pSettings->Video.ulFPS, m_uTrackVideo));
+ }
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ if (fAudioEnabled)
+ {
+ AssertPtr(m_pCodecAudio);
+ vrc = this->File.m_pWEBM->AddAudioTrack(m_pCodecAudio,
+ pSettings->Audio.uHz, pSettings->Audio.cChannels, pSettings->Audio.cBits,
+ &m_uTrackAudio);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, vrc));
+ break;
+ }
+
+ LogRel(("Recording: Recording audio of screen #%u in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n",
+ m_uScreenID, pSettings->Audio.uHz, pSettings->Audio.cBits, pSettings->Audio.cChannels,
+ pSettings->Audio.cChannels ? "channels" : "channel", m_uTrackAudio));
+ }
+#endif
+
+ if ( fVideoEnabled
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ || fAudioEnabled
+#endif
+ )
+ {
+ char szWhat[32] = { 0 };
+ if (fVideoEnabled)
+ RTStrCat(szWhat, sizeof(szWhat), "video");
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ if (fAudioEnabled)
+ {
+ if (fVideoEnabled)
+ RTStrCat(szWhat, sizeof(szWhat), " + ");
+ RTStrCat(szWhat, sizeof(szWhat), "audio");
+ }
+#endif
+ LogRel(("Recording: Recording %s of screen #%u to '%s'\n", szWhat, m_uScreenID, pszFile));
+ }
+
+ break;
+ }
+
+ default:
+ AssertFailed(); /* Should never happen. */
+ vrc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ m_enmState = RECORDINGSTREAMSTATE_INITIALIZED;
+ m_fEnabled = true;
+ m_tsStartMs = RTTimeProgramMilliTS();
+
+ return VINF_SUCCESS;
+ }
+
+ int vrc2 = uninitInternal();
+ AssertRC(vrc2);
+
+ LogRel(("Recording: Stream #%RU32 initialization failed with %Rrc\n", uScreen, vrc));
+ return vrc;
+}
+
+/**
+ * Closes a recording stream.
+ * Depending on the stream's recording destination, this function closes all associated handles
+ * and finalizes recording.
+ *
+ * @returns VBox status code.
+ */
+int RecordingStream::close(void)
+{
+ int vrc = VINF_SUCCESS;
+
+ switch (m_ScreenSettings.enmDest)
+ {
+ case RecordingDestination_File:
+ {
+ if (this->File.m_pWEBM)
+ vrc = this->File.m_pWEBM->Close();
+ break;
+ }
+
+ default:
+ AssertFailed(); /* Should never happen. */
+ break;
+ }
+
+ m_Blocks.Clear();
+
+ LogRel(("Recording: Recording screen #%u stopped\n", m_uScreenID));
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Error stopping recording screen #%u, vrc=%Rrc\n", m_uScreenID, vrc));
+ return vrc;
+ }
+
+ switch (m_ScreenSettings.enmDest)
+ {
+ case RecordingDestination_File:
+ {
+ if (RTFileIsValid(this->File.m_hFile))
+ {
+ vrc = RTFileClose(this->File.m_hFile);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Recording: Closed file '%s'\n", m_ScreenSettings.File.strName.c_str()));
+ }
+ else
+ {
+ LogRel(("Recording: Error closing file '%s', rc=%Rrc\n", m_ScreenSettings.File.strName.c_str(), vrc));
+ break;
+ }
+ }
+
+ WebMWriter *pWebMWriter = this->File.m_pWEBM;
+ AssertPtr(pWebMWriter);
+
+ if (pWebMWriter)
+ {
+ /* If no clusters (= data) was written, delete the file again. */
+ if (pWebMWriter->GetClusters() == 0)
+ {
+ int vrc2 = RTFileDelete(m_ScreenSettings.File.strName.c_str());
+ AssertRC(vrc2); /* Ignore rc on non-debug builds. */
+ }
+
+ delete pWebMWriter;
+ pWebMWriter = NULL;
+
+ this->File.m_pWEBM = NULL;
+ }
+ break;
+ }
+
+ default:
+ vrc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Uninitializes a recording stream.
+ *
+ * @returns VBox status code.
+ */
+int RecordingStream::Uninit(void)
+{
+ return uninitInternal();
+}
+
+/**
+ * Uninitializes a recording stream, internal version.
+ *
+ * @returns VBox status code.
+ */
+int RecordingStream::uninitInternal(void)
+{
+ if (m_enmState != RECORDINGSTREAMSTATE_INITIALIZED)
+ return VINF_SUCCESS;
+
+ int vrc = close();
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ m_pCodecAudio = NULL;
+#endif
+
+ if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
+ {
+ vrc = recordingCodecFinalize(&m_CodecVideo);
+ if (RT_SUCCESS(vrc))
+ vrc = recordingCodecDestroy(&m_CodecVideo);
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ RTCritSectDelete(&m_CritSect);
+
+ m_enmState = RECORDINGSTREAMSTATE_UNINITIALIZED;
+ m_fEnabled = false;
+ }
+
+ return vrc;
+}
+
+/**
+ * Writes encoded data to a WebM file instance.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec which has encoded the data.
+ * @param pvData Encoded data to write.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param msAbsPTS Absolute PTS (in ms) of written data.
+ * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
+ */
+int RecordingStream::codecWriteToWebM(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
+ uint64_t msAbsPTS, uint32_t uFlags)
+{
+ AssertPtr(this->File.m_pWEBM);
+ AssertPtr(pvData);
+ Assert (cbData);
+
+ WebMWriter::WebMBlockFlags blockFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
+ if (RT_LIKELY(uFlags != RECORDINGCODEC_ENC_F_NONE))
+ {
+ /* All set. */
+ }
+ else
+ {
+ if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_KEY)
+ blockFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
+ if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE)
+ blockFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
+ }
+
+ return this->File.m_pWEBM->WriteBlock( pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
+ ? m_uTrackAudio : m_uTrackVideo,
+ pvData, cbData, msAbsPTS, blockFlags);
+}
+
+/**
+ * Codec callback for writing encoded data to a recording stream.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec which has encoded the data.
+ * @param pvData Encoded data to write.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param msAbsPTS Absolute PTS (in ms) of written data.
+ * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
+ * @param pvUser User-supplied pointer.
+ */
+/* static */
+DECLCALLBACK(int) RecordingStream::codecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
+ uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
+{
+ RecordingStream *pThis = (RecordingStream *)pvUser;
+ AssertPtr(pThis);
+
+ /** @todo For now this is hardcoded to always write to a WebM file. Add other stuff later. */
+ return pThis->codecWriteToWebM(pCodec, pvData, cbData, msAbsPTS, uFlags);
+}
+
+/**
+ * Initializes the video recording for a recording stream.
+ *
+ * @returns VBox status code.
+ * @param screenSettings Screen settings to use.
+ */
+int RecordingStream::initVideo(const settings::RecordingScreenSettings &screenSettings)
+{
+ /* Sanity. */
+ AssertReturn(screenSettings.Video.ulRate, VERR_INVALID_PARAMETER);
+ AssertReturn(screenSettings.Video.ulWidth, VERR_INVALID_PARAMETER);
+ AssertReturn(screenSettings.Video.ulHeight, VERR_INVALID_PARAMETER);
+ AssertReturn(screenSettings.Video.ulFPS, VERR_INVALID_PARAMETER);
+
+ PRECORDINGCODEC pCodec = &m_CodecVideo;
+
+ RECORDINGCODECCALLBACKS Callbacks;
+ Callbacks.pvUser = this;
+ Callbacks.pfnWriteData = RecordingStream::codecWriteDataCallback;
+
+ int vrc = recordingCodecCreateVideo(pCodec, screenSettings.Video.enmCodec);
+ if (RT_SUCCESS(vrc))
+ vrc = recordingCodecInit(pCodec, &Callbacks, screenSettings);
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Recording: Initializing video codec failed with %Rrc\n", vrc));
+
+ return vrc;
+}
+
+/**
+ * Locks a recording stream.
+ */
+void RecordingStream::lock(void)
+{
+ int vrc = RTCritSectEnter(&m_CritSect);
+ AssertRC(vrc);
+}
+
+/**
+ * Unlocks a locked recording stream.
+ */
+void RecordingStream::unlock(void)
+{
+ int vrc = RTCritSectLeave(&m_CritSect);
+ AssertRC(vrc);
+}
+
diff --git a/src/VBox/Main/src-client/RecordingUtils.cpp b/src/VBox/Main/src-client/RecordingUtils.cpp
new file mode 100644
index 00000000..b23c19dd
--- /dev/null
+++ b/src/VBox/Main/src-client/RecordingUtils.cpp
@@ -0,0 +1,290 @@
+/* $Id: RecordingUtils.cpp $ */
+/** @file
+ * Recording utility code.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "Recording.h"
+#include "RecordingUtils.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#ifdef DEBUG
+#include <iprt/file.h>
+#include <iprt/formats/bmp.h>
+#endif
+
+
+/**
+ * Convert an image to YUV420p format.
+ *
+ * @return \c true on success, \c false on failure.
+ * @param aDstBuf The destination image buffer.
+ * @param aDstWidth Width (in pixel) of destination buffer.
+ * @param aDstHeight Height (in pixel) of destination buffer.
+ * @param aSrcBuf The source image buffer.
+ * @param aSrcWidth Width (in pixel) of source buffer.
+ * @param aSrcHeight Height (in pixel) of source buffer.
+ */
+template <class T>
+inline bool recordingUtilsColorConvWriteYUV420p(uint8_t *aDstBuf, unsigned aDstWidth, unsigned aDstHeight,
+ uint8_t *aSrcBuf, unsigned aSrcWidth, unsigned aSrcHeight)
+{
+ RT_NOREF(aDstWidth, aDstHeight);
+
+ AssertReturn(!(aSrcWidth & 1), false);
+ AssertReturn(!(aSrcHeight & 1), false);
+
+ bool fRc = true;
+ T iter1(aSrcWidth, aSrcHeight, aSrcBuf);
+ T iter2 = iter1;
+ iter2.skip(aSrcWidth);
+ unsigned cPixels = aSrcWidth * aSrcHeight;
+ unsigned offY = 0;
+ unsigned offU = cPixels;
+ unsigned offV = cPixels + cPixels / 4;
+ unsigned const cyHalf = aSrcHeight / 2;
+ unsigned const cxHalf = aSrcWidth / 2;
+ for (unsigned i = 0; i < cyHalf && fRc; ++i)
+ {
+ for (unsigned j = 0; j < cxHalf; ++j)
+ {
+ unsigned red, green, blue;
+ fRc = iter1.getRGB(&red, &green, &blue);
+ AssertReturn(fRc, false);
+ aDstBuf[offY] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
+ unsigned u = (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
+ unsigned v = (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
+
+ fRc = iter1.getRGB(&red, &green, &blue);
+ AssertReturn(fRc, false);
+ aDstBuf[offY + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
+ u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
+ v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
+
+ fRc = iter2.getRGB(&red, &green, &blue);
+ AssertReturn(fRc, false);
+ aDstBuf[offY + aSrcWidth] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
+ u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
+ v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
+
+ fRc = iter2.getRGB(&red, &green, &blue);
+ AssertReturn(fRc, false);
+ aDstBuf[offY + aSrcWidth + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
+ u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
+ v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
+
+ aDstBuf[offU] = u;
+ aDstBuf[offV] = v;
+ offY += 2;
+ ++offU;
+ ++offV;
+ }
+
+ iter1.skip(aSrcWidth);
+ iter2.skip(aSrcWidth);
+ offY += aSrcWidth;
+ }
+
+ return true;
+}
+
+/**
+ * Convert an image to RGB24 format.
+ *
+ * @returns true on success, false on failure.
+ * @param aWidth Width of image.
+ * @param aHeight Height of image.
+ * @param aDestBuf An allocated memory buffer large enough to hold the
+ * destination image (i.e. width * height * 12bits).
+ * @param aSrcBuf The source image as an array of bytes.
+ */
+template <class T>
+inline bool RecordingUtilsColorConvWriteRGB24(unsigned aWidth, unsigned aHeight,
+ uint8_t *aDestBuf, uint8_t *aSrcBuf)
+{
+ enum { PIX_SIZE = 3 };
+ bool fRc = true;
+ AssertReturn(0 == (aWidth & 1), false);
+ AssertReturn(0 == (aHeight & 1), false);
+ T iter(aWidth, aHeight, aSrcBuf);
+ unsigned cPixels = aWidth * aHeight;
+ for (unsigned i = 0; i < cPixels && fRc; ++i)
+ {
+ unsigned red, green, blue;
+ fRc = iter.getRGB(&red, &green, &blue);
+ if (fRc)
+ {
+ aDestBuf[i * PIX_SIZE ] = red;
+ aDestBuf[i * PIX_SIZE + 1] = green;
+ aDestBuf[i * PIX_SIZE + 2] = blue;
+ }
+ }
+ return fRc;
+}
+
+/**
+ * Converts a RGB to YUV buffer.
+ *
+ * @returns IPRT status code.
+ * @param enmPixelFormat Pixel format to use for conversion.
+ * @param paDst Pointer to destination buffer.
+ * @param uDstWidth Width (X, in pixels) of destination buffer.
+ * @param uDstHeight Height (Y, in pixels) of destination buffer.
+ * @param paSrc Pointer to source buffer.
+ * @param uSrcWidth Width (X, in pixels) of source buffer.
+ * @param uSrcHeight Height (Y, in pixels) of source buffer.
+ */
+int RecordingUtilsRGBToYUV(RECORDINGPIXELFMT enmPixelFormat,
+ uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight,
+ uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight)
+{
+ switch (enmPixelFormat)
+ {
+ case RECORDINGPIXELFMT_RGB32:
+ if (!recordingUtilsColorConvWriteYUV420p<ColorConvBGRA32Iter>(paDst, uDstWidth, uDstHeight,
+ paSrc, uSrcWidth, uSrcHeight))
+ return VERR_INVALID_PARAMETER;
+ break;
+ case RECORDINGPIXELFMT_RGB24:
+ if (!recordingUtilsColorConvWriteYUV420p<ColorConvBGR24Iter>(paDst, uDstWidth, uDstHeight,
+ paSrc, uSrcWidth, uSrcHeight))
+ return VERR_INVALID_PARAMETER;
+ break;
+ case RECORDINGPIXELFMT_RGB565:
+ if (!recordingUtilsColorConvWriteYUV420p<ColorConvBGR565Iter>(paDst, uDstWidth, uDstHeight,
+ paSrc, uSrcWidth, uSrcHeight))
+ return VERR_INVALID_PARAMETER;
+ break;
+ default:
+ AssertFailed();
+ return VERR_NOT_SUPPORTED;
+ }
+ return VINF_SUCCESS;
+}
+
+#ifdef DEBUG
+/**
+ * Dumps a video recording frame to a bitmap (BMP) file, extended version.
+ *
+ * @returns VBox status code.
+ * @param pu8RGBBuf Pointer to actual RGB frame data.
+ * @param cbRGBBuf Size (in bytes) of \a pu8RGBBuf.
+ * @param pszPath Absolute path to dump file to. Must exist.
+ * Specify NULL to use the system's temp directory.
+ * Existing frame files will be overwritten.
+ * @param pszPrefx Naming prefix to use. Optional and can be NULL.
+ * @param uWidth Width (in pixel) to write.
+ * @param uHeight Height (in pixel) to write.
+ * @param uBPP Bits in pixel.
+ */
+int RecordingUtilsDbgDumpFrameEx(const uint8_t *pu8RGBBuf, size_t cbRGBBuf, const char *pszPath, const char *pszPrefx,
+ uint16_t uWidth, uint32_t uHeight, uint8_t uBPP)
+{
+ RT_NOREF(cbRGBBuf);
+
+ const uint8_t uBytesPerPixel = uBPP / 8 /* Bits */;
+ const size_t cbData = uWidth * uHeight * uBytesPerPixel;
+
+ if (!cbData) /* No data to write? Bail out early. */
+ return VINF_SUCCESS;
+
+ BMPFILEHDR fileHdr;
+ RT_ZERO(fileHdr);
+
+ BMPWIN3XINFOHDR coreHdr;
+ RT_ZERO(coreHdr);
+
+ fileHdr.uType = BMP_HDR_MAGIC;
+ fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + cbData);
+ fileHdr.offBits = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR));
+
+ coreHdr.cbSize = sizeof(BMPWIN3XINFOHDR);
+ coreHdr.uWidth = uWidth ;
+ coreHdr.uHeight = uHeight;
+ coreHdr.cPlanes = 1;
+ coreHdr.cBits = uBPP;
+ coreHdr.uXPelsPerMeter = 5000;
+ coreHdr.uYPelsPerMeter = 5000;
+
+ static uint64_t s_iCount = 0;
+
+ char szPath[RTPATH_MAX];
+ if (!pszPath)
+ RTPathTemp(szPath, sizeof(szPath));
+
+ char szFileName[RTPATH_MAX];
+ if (RTStrPrintf2(szFileName, sizeof(szFileName), "%s/RecDump-%04RU64-%s-w%RU16h%RU16.bmp",
+ pszPath ? pszPath : szPath, s_iCount, pszPrefx ? pszPrefx : "Frame", uWidth, uHeight) <= 0)
+ {
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ s_iCount++;
+
+ RTFILE fh;
+ int vrc = RTFileOpen(&fh, szFileName,
+ RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(vrc))
+ {
+ RTFileWrite(fh, &fileHdr, sizeof(fileHdr), NULL);
+ RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL);
+
+ /* Bitmaps (DIBs) are stored upside-down (thanks, OS/2), so work from the bottom up. */
+ uint32_t offSrc = (uHeight * uWidth * uBytesPerPixel) - uWidth * uBytesPerPixel;
+
+ /* Do the copy. */
+ for (unsigned int i = 0; i < uHeight; i++)
+ {
+ RTFileWrite(fh, pu8RGBBuf + offSrc, uWidth * uBytesPerPixel, NULL);
+ offSrc -= uWidth * uBytesPerPixel;
+ }
+
+ RTFileClose(fh);
+ }
+
+ return vrc;
+}
+
+/**
+ * Dumps a video recording frame to a bitmap (BMP) file.
+ *
+ * @returns VBox status code.
+ * @param pFrame Video frame to dump.
+ */
+int RecordingUtilsDbgDumpFrame(const PRECORDINGFRAME pFrame)
+{
+ AssertReturn(pFrame->enmType == RECORDINGFRAME_TYPE_VIDEO, VERR_INVALID_PARAMETER);
+ return RecordingUtilsDbgDumpFrameEx(pFrame->Video.pu8RGBBuf, pFrame->Video.cbRGBBuf,
+ NULL /* Use temp directory */, NULL /* pszPrefix */,
+ pFrame->Video.uWidth, pFrame->Video.uHeight, pFrame->Video.uBPP);
+}
+#endif /* DEBUG */
+
diff --git a/src/VBox/Main/src-client/RemoteUSBBackend.cpp b/src/VBox/Main/src-client/RemoteUSBBackend.cpp
new file mode 100644
index 00000000..8a8077f8
--- /dev/null
+++ b/src/VBox/Main/src-client/RemoteUSBBackend.cpp
@@ -0,0 +1,1414 @@
+/* $Id: RemoteUSBBackend.cpp $ */
+/** @file
+ * VirtualBox Remote USB backend
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_USB_REMOTE
+#include "LoggingNew.h"
+
+#include "ConsoleImpl.h"
+#include "ConsoleVRDPServer.h"
+#include "RemoteUSBBackend.h"
+#include "RemoteUSBDeviceImpl.h"
+
+#include <VBox/RemoteDesktop/VRDE.h>
+#include <VBox/vrdpusb.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <VBox/vusb.h>
+
+#include <iprt/time.h>
+
+/** @page pg_vrdb_usb Async Remote USB
+ *
+ *
+ * USB backend functions are called in EMT so care must be taken to prevent
+ * delays in the functions execution.
+ *
+ * Among 11 backend functions 10 just return a success indicator.
+ *
+ * Such a function usually will check pending error code and if everything is ok,
+ * submit asynchronous RDP request and return success immediately.
+ *
+ * On actual completion of each request, the status will be saved as
+ * pending, so in case of an error all further functions will fail with
+ * device disconnected condition.
+ * @todo May be a device disconnect notification for console is required?
+ *
+ * The only remaining function that needs special processing is
+ * the reap_urb. It has a timeout parameter.
+ * Normally, the timeout is 0, as result of polling from VUSB frame timer.
+ * It is ok for async processing, the backend will periodically reap urbs from client.
+ * And already reaped URBs from client will be returned for the call.
+ * Exceptions:
+ * 1) during device initialization, when obtaining device descriptions
+ * the timeout is -1, and the request is expected to be processed synchronously.
+ * It looks like only 3 URBs with some information are retrieved that way.
+ * Probably, one can return this information in DEVICE_LIST together with the
+ * device description and when such request are submitted, just return
+ * the prefetched data.
+ * 2) during suspend timeout is non zero (10 or less milliseconds),
+ * and URB's are reaped for about 1 second. But here network delays
+ * will not affect the timeout, so it is ok.
+ *
+ *
+ * @section sub_vrdb_usb_dad Device attaching/detaching
+ *
+ * Devices are attached when client is connected or when a new device is connected to client.
+ * Devices are detached when client is disconnected (all devices) or a device is disconnected
+ * the client side.
+ *
+ * The backend polls the client for list of attached USB devices from RemoteUSBThread.
+ *
+ */
+
+/* Queued URB submitted to VRDP client. */
+typedef struct _REMOTEUSBQURB
+{
+ struct _REMOTEUSBQURB *next;
+ struct _REMOTEUSBQURB *prev;
+
+ PREMOTEUSBDEVICE pDevice; /* Device, the URB is queued for. */
+
+ uint32_t u32Handle; /* The handle of the URB. Generated by the Remote USB backend. */
+
+ void *pvData; /* Pointer to URB data allocated by VUSB. */
+ void *pvURB; /* Pointer to URB known to VUSB. */
+
+ uint32_t u32Len; /* Data length returned by the VRDP client. */
+ uint32_t u32Err; /* URB error code returned by the VRDP client. */
+
+ bool fCompleted; /* The URB has been returned back by VRDP client. */
+ bool fInput; /* This URB receives data from the client. */
+
+ uint32_t u32TransferredLen; /* For VRDE_USB_DIRECTION_OUT URBs = bytes written.
+ * For VRDE_USB_DIRECTION_IN URBs = bytes received.
+ */
+} REMOTEUSBQURB;
+
+/* Remote USB device instance data. */
+typedef struct _REMOTEUSBDEVICE
+{
+ struct _REMOTEUSBDEVICE *prev;
+ struct _REMOTEUSBDEVICE *next;
+
+ RemoteUSBBackend *pOwner;
+
+ VRDEUSBDEVID id; /* The remote identifier, assigned by client. */
+
+ uint32_t u32ClientId; /* The identifier of the remote client. */
+
+ REMOTEUSBQURB *pHeadQURBs; /* List of URBs queued for the device. */
+ REMOTEUSBQURB *pTailQURBs;
+
+ volatile uint32_t hURB; /* Source for URB's handles. */
+ bool fFailed; /* True if an operation has failed for the device. */
+ RTCRITSECT critsect; /* Protects the queued urb list. */
+ volatile bool fWokenUp; /* Flag whther the reaper was woken up. */
+} REMOTEUSBDEVICE;
+
+
+
+static void requestDevice(REMOTEUSBDEVICE *pDevice)
+{
+ int vrc = RTCritSectEnter(&pDevice->critsect);
+ AssertRC(vrc);
+}
+
+static void releaseDevice(REMOTEUSBDEVICE *pDevice)
+{
+ RTCritSectLeave(&pDevice->critsect);
+}
+
+static REMOTEUSBQURB *qurbAlloc(PREMOTEUSBDEVICE pDevice)
+{
+ /** @todo reuse URBs. */
+ REMOTEUSBQURB *pQURB = (REMOTEUSBQURB *)RTMemAllocZ (sizeof (REMOTEUSBQURB));
+
+ if (pQURB)
+ {
+ pQURB->pDevice = pDevice;
+ }
+
+ return pQURB;
+}
+
+static void qurbFree (REMOTEUSBQURB *pQURB)
+{
+ RTMemFree (pQURB);
+ return;
+}
+
+
+/* Called by VRDP server when the client responds to a request on USB channel. */
+DECLCALLBACK(int) USBClientResponseCallback(void *pv, uint32_t u32ClientId, uint8_t code, const void *pvRet, uint32_t cbRet)
+{
+ RT_NOREF(u32ClientId);
+ int vrc = VINF_SUCCESS;
+
+ LogFlow(("USBClientResponseCallback: id = %d, pv = %p, code = %d, pvRet = %p, cbRet = %d\n",
+ u32ClientId, pv, code, pvRet, cbRet));
+
+ RemoteUSBBackend *pThis = (RemoteUSBBackend *)pv;
+
+ switch (code)
+ {
+ case VRDE_USB_REQ_DEVICE_LIST:
+ {
+ vrc = pThis->saveDeviceList(pvRet, cbRet);
+ } break;
+
+ case VRDE_USB_REQ_NEGOTIATE:
+ {
+ if (pvRet && cbRet >= sizeof(VRDEUSBREQNEGOTIATERET))
+ {
+ VRDEUSBREQNEGOTIATERET *pret = (VRDEUSBREQNEGOTIATERET *)pvRet;
+
+ vrc = pThis->negotiateResponse(pret, cbRet);
+ }
+ else
+ {
+ Log(("USBClientResponseCallback: WARNING: not enough data in response: pv = %p, cb = %d, expected %d.\n",
+ pvRet, cbRet, sizeof(VRDEUSBREQNEGOTIATERET)));
+
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ } break;
+
+ case VRDE_USB_REQ_REAP_URB:
+ {
+ vrc = pThis->reapURB(pvRet, cbRet);
+
+ LogFlow(("USBClientResponseCallback: reap URB, rc = %Rrc.\n", vrc));
+ } break;
+
+ case VRDE_USB_REQ_QUEUE_URB:
+ case VRDE_USB_REQ_CLOSE:
+ case VRDE_USB_REQ_CANCEL_URB:
+ {
+ /* Do nothing, actually this should not happen. */
+ Log(("USBClientResponseCallback: WARNING: response to a request %d is not expected!!!\n", code));
+ } break;
+
+ case VRDE_USB_REQ_OPEN:
+ case VRDE_USB_REQ_RESET:
+ case VRDE_USB_REQ_SET_CONFIG:
+ case VRDE_USB_REQ_CLAIM_INTERFACE:
+ case VRDE_USB_REQ_RELEASE_INTERFACE:
+ case VRDE_USB_REQ_INTERFACE_SETTING:
+ case VRDE_USB_REQ_CLEAR_HALTED_EP:
+ {
+ /*
+ * Device specific responses with status codes.
+ */
+ if (pvRet && cbRet >= sizeof(VRDEUSBREQRETHDR))
+ {
+ VRDEUSBREQRETHDR *pret = (VRDEUSBREQRETHDR *)pvRet;
+
+ if (pret->status != VRDE_USB_STATUS_SUCCESS)
+ {
+ REMOTEUSBDEVICE *pDevice = pThis->deviceFromId(pret->id);
+
+ if (!pDevice)
+ {
+ Log(("USBClientResponseCallback: WARNING: invalid device id %08X.\n", pret->id));
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ Log(("USBClientResponseCallback: WARNING: the operation failed, status %d\n", pret->status));
+ pDevice->fFailed = true;
+ }
+ }
+ }
+ else
+ {
+ Log(("USBClientResponseCallback: WARNING: not enough data in response: pv = %p, cb = %d, expected %d.\n",
+ pvRet, cbRet, sizeof(VRDEUSBREQRETHDR)));
+ }
+ } break;
+
+ default:
+ {
+ Log(("USBClientResponseCallback: WARNING: invalid code %d\n", code));
+ } break;
+ }
+
+ return vrc;
+}
+
+/*
+ * Backend entry points.
+ */
+static DECLCALLBACK(int) iface_Open(PREMOTEUSBBACKEND pInstance, const char *pszAddress,
+ size_t cbAddress, PREMOTEUSBDEVICE *ppDevice)
+{
+ RT_NOREF(cbAddress);
+ int vrc = VINF_SUCCESS;
+
+ RemoteUSBBackend *pThis = (RemoteUSBBackend *)pInstance;
+
+ REMOTEUSBDEVICE *pDevice = (REMOTEUSBDEVICE *)RTMemAllocZ(sizeof(REMOTEUSBDEVICE));
+
+ if (!pDevice)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Parse given address string to find the device identifier.
+ * The format is "REMOTEUSB0xAAAABBBB&0xCCCCDDDD", where AAAABBBB is hex device identifier
+ * and CCCCDDDD is hex client id.
+ */
+ if (strncmp(pszAddress, REMOTE_USB_BACKEND_PREFIX_S, REMOTE_USB_BACKEND_PREFIX_LEN) != 0)
+ {
+ AssertFailed();
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ /* Initialize the device structure. */
+ pDevice->pOwner = pThis;
+ pDevice->fWokenUp = false;
+
+ vrc = RTCritSectInit(&pDevice->critsect);
+ AssertRC(vrc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ pDevice->id = RTStrToUInt32(&pszAddress[REMOTE_USB_BACKEND_PREFIX_LEN]);
+
+ size_t l = strlen(pszAddress);
+
+ if (l >= REMOTE_USB_BACKEND_PREFIX_LEN + strlen("0x12345678&0x87654321"))
+ {
+ const char *p = &pszAddress[REMOTE_USB_BACKEND_PREFIX_LEN + strlen("0x12345678")];
+ if (*p == '&')
+ {
+ pDevice->u32ClientId = RTStrToUInt32(p + 1);
+ }
+ else
+ {
+ AssertFailed();
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ AssertFailed();
+ vrc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ VRDE_USB_REQ_OPEN_PARM parm;
+
+ parm.code = VRDE_USB_REQ_OPEN;
+ parm.id = pDevice->id;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+ }
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ *ppDevice = pDevice;
+
+ pThis->addDevice(pDevice);
+ }
+ else
+ {
+ RTMemFree(pDevice);
+ }
+
+ return vrc;
+}
+
+static DECLCALLBACK(void) iface_Close(PREMOTEUSBDEVICE pDevice)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ VRDE_USB_REQ_CLOSE_PARM parm;
+
+ parm.code = VRDE_USB_REQ_CLOSE;
+ parm.id = pDevice->id;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ pThis->removeDevice(pDevice);
+
+ if (RTCritSectIsInitialized(&pDevice->critsect))
+ {
+ RTCritSectDelete(&pDevice->critsect);
+ }
+
+ RTMemFree(pDevice);
+
+ return;
+}
+
+static DECLCALLBACK(int) iface_Reset(PREMOTEUSBDEVICE pDevice)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ VRDE_USB_REQ_RESET_PARM parm;
+
+ parm.code = VRDE_USB_REQ_RESET;
+ parm.id = pDevice->id;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) iface_SetConfig(PREMOTEUSBDEVICE pDevice, uint8_t u8Cfg)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ VRDE_USB_REQ_SET_CONFIG_PARM parm;
+
+ parm.code = VRDE_USB_REQ_SET_CONFIG;
+ parm.id = pDevice->id;
+ parm.configuration = u8Cfg;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) iface_ClaimInterface(PREMOTEUSBDEVICE pDevice, uint8_t u8Ifnum)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ VRDE_USB_REQ_CLAIM_INTERFACE_PARM parm;
+
+ parm.code = VRDE_USB_REQ_CLAIM_INTERFACE;
+ parm.id = pDevice->id;
+ parm.iface = u8Ifnum;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) iface_ReleaseInterface(PREMOTEUSBDEVICE pDevice, uint8_t u8Ifnum)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ VRDE_USB_REQ_RELEASE_INTERFACE_PARM parm;
+
+ parm.code = VRDE_USB_REQ_RELEASE_INTERFACE;
+ parm.id = pDevice->id;
+ parm.iface = u8Ifnum;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) iface_InterfaceSetting(PREMOTEUSBDEVICE pDevice, uint8_t u8Ifnum, uint8_t u8Setting)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ VRDE_USB_REQ_INTERFACE_SETTING_PARM parm;
+
+ parm.code = VRDE_USB_REQ_INTERFACE_SETTING;
+ parm.id = pDevice->id;
+ parm.iface = u8Ifnum;
+ parm.setting = u8Setting;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) iface_ClearHaltedEP(PREMOTEUSBDEVICE pDevice, uint8_t u8Ep)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ VRDE_USB_REQ_CLEAR_HALTED_EP_PARM parm;
+
+ parm.code = VRDE_USB_REQ_CLEAR_HALTED_EP;
+ parm.id = pDevice->id;
+ parm.ep = u8Ep;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(void) iface_CancelURB(PREMOTEUSBDEVICE pDevice, PREMOTEUSBQURB pRemoteURB)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ VRDE_USB_REQ_CANCEL_URB_PARM parm;
+
+ parm.code = VRDE_USB_REQ_CANCEL_URB;
+ parm.id = pDevice->id;
+ parm.handle = pRemoteURB->u32Handle;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ requestDevice(pDevice);
+
+ /* Remove this urb from the queue. It is safe because if
+ * client will return the URB, it will be just ignored
+ * in reapURB.
+ */
+ if (pRemoteURB->prev)
+ {
+ pRemoteURB->prev->next = pRemoteURB->next;
+ }
+ else
+ {
+ pDevice->pHeadQURBs = pRemoteURB->next;
+ }
+
+ if (pRemoteURB->next)
+ {
+ pRemoteURB->next->prev = pRemoteURB->prev;
+ }
+ else
+ {
+ pDevice->pTailQURBs = pRemoteURB->prev;
+ }
+
+ qurbFree(pRemoteURB);
+
+ releaseDevice(pDevice);
+
+ return;
+}
+
+static DECLCALLBACK(int) iface_QueueURB(PREMOTEUSBDEVICE pDevice, uint8_t u8Type, uint8_t u8Ep, uint8_t u8Direction,
+ uint32_t u32Len, void *pvData, void *pvURB, PREMOTEUSBQURB *ppRemoteURB)
+{
+ int vrc = VINF_SUCCESS;
+
+#ifdef DEBUG_sunlover
+ LogFlow(("RemoteUSBBackend::iface_QueueURB: u8Type = %d, u8Ep = %d, u8Direction = %d, data\n%.*Rhxd\n",
+ u8Type, u8Ep, u8Direction, u32Len, pvData));
+#endif /* DEBUG_sunlover */
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ VRDE_USB_REQ_QUEUE_URB_PARM parm;
+ uint32_t u32Handle = 0;
+ uint32_t u32DataLen = 0;
+
+ REMOTEUSBQURB *qurb = qurbAlloc(pDevice);
+
+ if (qurb == NULL)
+ {
+ vrc = VERR_NO_MEMORY;
+ goto l_leave;
+ }
+
+ /*
+ * Compute length of data which need to be transferred to the client.
+ */
+ switch(u8Direction)
+ {
+ case VUSB_DIRECTION_IN:
+ {
+ if (u8Type == VUSBXFERTYPE_MSG)
+ {
+ u32DataLen = 8; /* 8 byte header. */
+ // u32DataLen = u32Len; /// @todo do messages need all information?
+ }
+ } break;
+
+ case VUSB_DIRECTION_OUT:
+ {
+ u32DataLen = u32Len;
+ } break;
+
+ default:
+ {
+ AssertFailed();
+ vrc = VERR_INVALID_PARAMETER;
+ goto l_leave;
+ }
+ }
+
+ parm.code = VRDE_USB_REQ_QUEUE_URB;
+ parm.id = pDevice->id;
+
+ u32Handle = pDevice->hURB++;
+ if (u32Handle == 0)
+ {
+ u32Handle = pDevice->hURB++;
+ }
+
+ LogFlow(("RemoteUSBBackend::iface_QueueURB: handle = %d\n", u32Handle));
+
+ parm.handle = u32Handle;
+
+ switch(u8Type)
+ {
+ case VUSBXFERTYPE_CTRL: parm.type = VRDE_USB_TRANSFER_TYPE_CTRL; break;
+ case VUSBXFERTYPE_ISOC: parm.type = VRDE_USB_TRANSFER_TYPE_ISOC; break;
+ case VUSBXFERTYPE_BULK: parm.type = VRDE_USB_TRANSFER_TYPE_BULK; break;
+ case VUSBXFERTYPE_INTR: parm.type = VRDE_USB_TRANSFER_TYPE_INTR; break;
+ case VUSBXFERTYPE_MSG: parm.type = VRDE_USB_TRANSFER_TYPE_MSG; break;
+ default: AssertFailed(); vrc = VERR_INVALID_PARAMETER; goto l_leave;
+ }
+
+ parm.ep = u8Ep;
+
+ switch(u8Direction)
+ {
+ case VUSB_DIRECTION_SETUP: AssertFailed(); parm.direction = VRDE_USB_DIRECTION_SETUP; break;
+ case VUSB_DIRECTION_IN: parm.direction = VRDE_USB_DIRECTION_IN; break;
+ case VUSB_DIRECTION_OUT: parm.direction = VRDE_USB_DIRECTION_OUT; break;
+ default: AssertFailed(); vrc = VERR_INVALID_PARAMETER; goto l_leave;
+ }
+
+ parm.urblen = u32Len;
+ parm.datalen = u32DataLen;
+
+ if (u32DataLen)
+ {
+ parm.data = pvData;
+ }
+
+ requestDevice (pDevice);
+
+ /* Add at tail of queued urb list. */
+ qurb->next = NULL;
+ qurb->prev = pDevice->pTailQURBs;
+ qurb->u32Err = VRDE_USB_XFER_OK;
+ qurb->u32Len = u32Len;
+ qurb->pvData = pvData;
+ qurb->pvURB = pvURB;
+ qurb->u32Handle = u32Handle;
+ qurb->fCompleted = false;
+ qurb->fInput = (u8Direction == VUSB_DIRECTION_IN);
+ qurb->u32TransferredLen = 0;
+
+ if (pDevice->pTailQURBs)
+ {
+ Assert(pDevice->pTailQURBs->next == NULL);
+ pDevice->pTailQURBs->next = qurb;
+ }
+ else
+ {
+ /* This is the first URB to be added. */
+ Assert(pDevice->pHeadQURBs == NULL);
+ pDevice->pHeadQURBs = qurb;
+ }
+
+ pDevice->pTailQURBs = qurb;
+
+ releaseDevice(pDevice);
+
+ *ppRemoteURB = qurb;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+l_leave:
+ if (RT_FAILURE(vrc))
+ {
+ qurbFree(qurb);
+ }
+
+ return vrc;
+}
+
+/* The function checks the URB queue for completed URBs. Also if the client
+ * has requested URB polling, the function will send URB poll requests.
+ */
+static DECLCALLBACK(int) iface_ReapURB(PREMOTEUSBDEVICE pDevice, uint32_t u32Millies, void **ppvURB,
+ uint32_t *pu32Len, uint32_t *pu32Err)
+{
+ int vrc = VINF_SUCCESS;
+
+ LogFlow(("RemoteUSBBackend::iface_ReapURB %d ms\n", u32Millies));
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ /* Wait for transaction completion. */
+ uint64_t u64StartTime = RTTimeMilliTS();
+
+ if (pThis->pollingEnabledURB())
+ {
+ VRDE_USB_REQ_REAP_URB_PARM parm;
+
+ parm.code = VRDE_USB_REQ_REAP_URB;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+ }
+
+ REMOTEUSBQURB *qurb = NULL;
+
+ for (;;)
+ {
+ uint32_t u32ClientId;
+
+ if (ASMAtomicXchgBool(&pDevice->fWokenUp, false))
+ break;
+
+ /* Scan queued URBs, look for completed. */
+ requestDevice(pDevice);
+
+ u32ClientId = pDevice->u32ClientId;
+
+ qurb = pDevice->pHeadQURBs;
+
+ while (qurb)
+ {
+ if (qurb->fCompleted)
+ {
+ /* Remove this completed urb from the queue. */
+ if (qurb->prev)
+ {
+ qurb->prev->next = qurb->next;
+ }
+ else
+ {
+ pDevice->pHeadQURBs = qurb->next;
+ }
+
+ if (qurb->next)
+ {
+ qurb->next->prev = qurb->prev;
+ }
+ else
+ {
+ pDevice->pTailQURBs = qurb->prev;
+ }
+
+ qurb->next = NULL;
+ qurb->prev = NULL;
+
+ break;
+ }
+
+ qurb = qurb->next;
+ }
+
+ releaseDevice(pDevice);
+
+ if ( qurb
+ || !pDevice->pHeadQURBs
+ || u32Millies == 0
+ || pDevice->fFailed
+ || (RTTimeMilliTS() - u64StartTime >= (uint64_t)u32Millies))
+ {
+ /* Got an URB or do not have to wait for an URB. */
+ break;
+ }
+
+ LogFlow(("RemoteUSBBackend::iface_ReapURB iteration.\n"));
+
+ RTThreadSleep(10);
+
+ if (pThis->pollingEnabledURB())
+ {
+ VRDE_USB_REQ_REAP_URB_PARM parm;
+
+ parm.code = VRDE_USB_REQ_REAP_URB;
+
+ pThis->VRDPServer()->SendUSBRequest(u32ClientId, &parm, sizeof(parm));
+ }
+ }
+
+ LogFlow(("RemoteUSBBackend::iface_ReapURB completed in %lld ms, qurb = %p\n", RTTimeMilliTS () - u64StartTime, qurb));
+
+ if (!qurb)
+ {
+ *ppvURB = NULL;
+ *pu32Len = 0;
+ *pu32Err = VUSBSTATUS_OK;
+ }
+ else
+ {
+ *ppvURB = qurb->pvURB;
+ *pu32Len = qurb->u32Len;
+ *pu32Err = qurb->u32Err;
+
+#ifdef LOG_ENABLED
+ Log(("URB len = %d, data = %p\n", qurb->u32Len, qurb->pvURB));
+ if (qurb->u32Len)
+ {
+ Log(("Received URB content:\n%.*Rhxd\n", qurb->u32Len, qurb->pvData));
+ }
+#endif
+
+ qurbFree(qurb);
+ }
+
+ return vrc;
+}
+
+static DECLCALLBACK(int) iface_Wakeup(PREMOTEUSBDEVICE pDevice)
+{
+ ASMAtomicXchgBool(&pDevice->fWokenUp, true);
+ return VINF_SUCCESS;
+}
+
+void RemoteUSBBackend::AddRef(void)
+{
+ cRefs++;
+}
+
+void RemoteUSBBackend::Release(void)
+{
+ cRefs--;
+
+ if (cRefs <= 0)
+ {
+ delete this;
+ }
+}
+
+void RemoteUSBBackend::PollRemoteDevices(void)
+{
+ if ( mfWillBeDeleted
+ && menmPollRemoteDevicesStatus != PollRemoteDevicesStatus_Dereferenced)
+ {
+ /* Unmount all remote USB devices. */
+ mConsole->i_processRemoteUSBDevices(mu32ClientId, NULL, 0, false);
+
+ menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_Dereferenced;
+
+ Release();
+
+ return;
+ }
+
+ switch(menmPollRemoteDevicesStatus)
+ {
+ case PollRemoteDevicesStatus_Negotiate:
+ {
+ VRDEUSBREQNEGOTIATEPARM parm;
+
+ parm.code = VRDE_USB_REQ_NEGOTIATE;
+ parm.version = VRDE_USB_VERSION;
+ /* VRDE_USB_VERSION_3: support VRDE_USB_REQ_DEVICE_LIST_EXT_RET. */
+ parm.flags = VRDE_USB_SERVER_CAPS_PORT_VERSION;
+
+ mServer->SendUSBRequest(mu32ClientId, &parm, sizeof(parm));
+
+ /* Reference the object. When the client disconnects and
+ * the backend is about to be deleted, the method must be called
+ * to disconnect the USB devices (as stated above).
+ */
+ AddRef();
+
+ /* Goto the disabled state. When a response will be received
+ * the state will be changed to the SendRequest.
+ */
+ menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_WaitNegotiateResponse;
+ } break;
+
+ case PollRemoteDevicesStatus_WaitNegotiateResponse:
+ {
+ LogFlow(("USB::PollRemoteDevices: WaitNegotiateResponse\n"));
+ /* Do nothing. */
+ } break;
+
+ case PollRemoteDevicesStatus_SendRequest:
+ {
+ LogFlow(("USB::PollRemoteDevices: SendRequest\n"));
+
+ /* Send a request for device list. */
+ VRDE_USB_REQ_DEVICE_LIST_PARM parm;
+
+ parm.code = VRDE_USB_REQ_DEVICE_LIST;
+
+ mServer->SendUSBRequest(mu32ClientId, &parm, sizeof(parm));
+
+ menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_WaitResponse;
+ } break;
+
+ case PollRemoteDevicesStatus_WaitResponse:
+ {
+ LogFlow(("USB::PollRemoteDevices: WaitResponse\n"));
+
+ if (mfHasDeviceList)
+ {
+ mConsole->i_processRemoteUSBDevices(mu32ClientId, (VRDEUSBDEVICEDESC *)mpvDeviceList, mcbDeviceList, mfDescExt);
+ LogFlow(("USB::PollRemoteDevices: WaitResponse after process\n"));
+
+ menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_SendRequest;
+
+ mfHasDeviceList = false;
+ }
+ } break;
+
+ case PollRemoteDevicesStatus_Dereferenced:
+ {
+ LogFlow(("USB::PollRemoteDevices: Dereferenced\n"));
+ /* Do nothing. */
+ } break;
+
+ default:
+ {
+ AssertFailed();
+ } break;
+ }
+}
+
+void RemoteUSBBackend::NotifyDelete(void)
+{
+ mfWillBeDeleted = true;
+}
+
+/*
+ * The backend maintains a list of UUIDs of devices
+ * which are managed by the backend.
+ */
+bool RemoteUSBBackend::addUUID(const Guid *pUuid)
+{
+ unsigned i;
+ for (i = 0; i < RT_ELEMENTS(aGuids); i++)
+ {
+ if (aGuids[i].isZero())
+ {
+ aGuids[i] = *pUuid;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool RemoteUSBBackend::findUUID(const Guid *pUuid)
+{
+ unsigned i;
+ for (i = 0; i < RT_ELEMENTS(aGuids); i++)
+ {
+ if (aGuids[i] == *pUuid)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void RemoteUSBBackend::removeUUID(const Guid *pUuid)
+{
+ unsigned i;
+ for (i = 0; i < RT_ELEMENTS(aGuids); i++)
+ {
+ if (aGuids[i] == *pUuid)
+ {
+ aGuids[i].clear();
+ break;
+ }
+ }
+}
+
+RemoteUSBBackend::RemoteUSBBackend(Console *console, ConsoleVRDPServer *server, uint32_t u32ClientId)
+ :
+ mConsole(console),
+ mServer(server),
+ cRefs(0),
+ mu32ClientId(u32ClientId),
+ mfHasDeviceList(false),
+ mpvDeviceList(NULL),
+ mcbDeviceList(0),
+ menmPollRemoteDevicesStatus(PollRemoteDevicesStatus_Negotiate),
+ mfPollURB(true),
+ mpDevices(NULL),
+ mfWillBeDeleted(false),
+ mClientVersion(0), /* VRDE_USB_VERSION_2: the client version. */
+ mfDescExt(false) /* VRDE_USB_VERSION_3: VRDE_USB_REQ_DEVICE_LIST_EXT_RET. */
+{
+ Assert(console);
+ Assert(server);
+
+ int vrc = RTCritSectInit(&mCritsect);
+
+ if (RT_FAILURE(vrc))
+ {
+ AssertFailed();
+ RT_ZERO(mCritsect);
+ }
+
+ mCallback.pInstance = (PREMOTEUSBBACKEND)this;
+ mCallback.pfnOpen = iface_Open;
+ mCallback.pfnClose = iface_Close;
+ mCallback.pfnReset = iface_Reset;
+ mCallback.pfnSetConfig = iface_SetConfig;
+ mCallback.pfnClaimInterface = iface_ClaimInterface;
+ mCallback.pfnReleaseInterface = iface_ReleaseInterface;
+ mCallback.pfnInterfaceSetting = iface_InterfaceSetting;
+ mCallback.pfnQueueURB = iface_QueueURB;
+ mCallback.pfnReapURB = iface_ReapURB;
+ mCallback.pfnClearHaltedEP = iface_ClearHaltedEP;
+ mCallback.pfnCancelURB = iface_CancelURB;
+ mCallback.pfnWakeup = iface_Wakeup;
+}
+
+RemoteUSBBackend::~RemoteUSBBackend()
+{
+ Assert(cRefs == 0);
+
+ if (RTCritSectIsInitialized(&mCritsect))
+ {
+ RTCritSectDelete(&mCritsect);
+ }
+
+ RTMemFree(mpvDeviceList);
+
+ mServer->usbBackendRemoveFromList(this);
+}
+
+int RemoteUSBBackend::negotiateResponse(const VRDEUSBREQNEGOTIATERET *pret, uint32_t cbRet)
+{
+ int vrc = VINF_SUCCESS;
+
+ Log(("RemoteUSBBackend::negotiateResponse: flags = %02X.\n", pret->flags));
+
+ LogRel(("Remote USB: Received negotiate response. Flags 0x%02X.\n",
+ pret->flags));
+
+ if (pret->flags & VRDE_USB_CAPS_FLAG_POLL)
+ {
+ Log(("RemoteUSBBackend::negotiateResponse: client requested URB polling.\n"));
+ mfPollURB = true;
+ }
+ else
+ {
+ mfPollURB = false;
+ }
+
+ /* VRDE_USB_VERSION_2: check the client version. */
+ if (pret->flags & VRDE_USB_CAPS2_FLAG_VERSION)
+ {
+ /* This could be a client version > 1. */
+ if (cbRet >= sizeof(VRDEUSBREQNEGOTIATERET_2))
+ {
+ VRDEUSBREQNEGOTIATERET_2 *pret2 = (VRDEUSBREQNEGOTIATERET_2 *)pret;
+
+ if (pret2->u32Version <= VRDE_USB_VERSION)
+ {
+ /* This is OK. The client wants a version supported by the server. */
+ mClientVersion = pret2->u32Version;
+ }
+ else
+ {
+ LogRel(("VRDP: ERROR: unsupported remote USB protocol client version %d.\n", pret2->u32Version));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ LogRel(("VRDP: ERROR: invalid remote USB negotiate request packet size %d.\n", cbRet));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ /* This is a client version 1. */
+ mClientVersion = VRDE_USB_VERSION_1;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDP: remote USB protocol version %d.\n", mClientVersion));
+
+ /* VRDE_USB_VERSION_3: check the client capabilities: VRDE_USB_CLIENT_CAPS_*. */
+ if (mClientVersion == VRDE_USB_VERSION_3)
+ {
+ if (cbRet >= sizeof(VRDEUSBREQNEGOTIATERET_3))
+ {
+ VRDEUSBREQNEGOTIATERET_3 *pret3 = (VRDEUSBREQNEGOTIATERET_3 *)pret;
+
+ mfDescExt = (pret3->u32Flags & VRDE_USB_CLIENT_CAPS_PORT_VERSION) != 0;
+ }
+ else
+ {
+ LogRel(("VRDP: ERROR: invalid remote USB negotiate request packet size %d.\n", cbRet));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_SendRequest;
+ }
+
+ return vrc;
+}
+
+int RemoteUSBBackend::saveDeviceList(const void *pvList, uint32_t cbList)
+{
+ Log(("RemoteUSBBackend::saveDeviceList: pvList = %p, cbList = %d\n", pvList, cbList));
+
+ if (!mfHasDeviceList)
+ {
+ RTMemFree(mpvDeviceList);
+ mpvDeviceList = NULL;
+
+ mcbDeviceList = cbList;
+
+ if (cbList > 0)
+ {
+ mpvDeviceList = RTMemAlloc(cbList);
+ memcpy(mpvDeviceList, pvList, cbList);
+ }
+
+ mfHasDeviceList = true;
+ }
+
+ return VINF_SUCCESS;
+}
+
+void RemoteUSBBackend::request(void)
+{
+ int vrc = RTCritSectEnter(&mCritsect);
+ AssertRC(vrc);
+}
+
+void RemoteUSBBackend::release(void)
+{
+ RTCritSectLeave(&mCritsect);
+}
+
+PREMOTEUSBDEVICE RemoteUSBBackend::deviceFromId(VRDEUSBDEVID id)
+{
+ request();
+
+ REMOTEUSBDEVICE *pDevice = mpDevices;
+
+ while (pDevice && pDevice->id != id)
+ {
+ pDevice = pDevice->next;
+ }
+
+ release();
+
+ return pDevice;
+}
+
+void RemoteUSBBackend::addDevice(PREMOTEUSBDEVICE pDevice)
+{
+ request();
+
+ pDevice->next = mpDevices;
+
+ if (mpDevices)
+ {
+ mpDevices->prev = pDevice;
+ }
+
+ mpDevices = pDevice;
+
+ release();
+}
+
+void RemoteUSBBackend::removeDevice(PREMOTEUSBDEVICE pDevice)
+{
+ request();
+
+ if (pDevice->prev)
+ {
+ pDevice->prev->next = pDevice->next;
+ }
+ else
+ {
+ mpDevices = pDevice->next;
+ }
+
+ if (pDevice->next)
+ {
+ pDevice->next->prev = pDevice->prev;
+ }
+
+ release();
+}
+
+int RemoteUSBBackend::reapURB(const void *pvBody, uint32_t cbBody)
+{
+ int vrc = VINF_SUCCESS;
+
+ LogFlow(("RemoteUSBBackend::reapURB: pvBody = %p, cbBody = %d\n", pvBody, cbBody));
+
+ VRDEUSBREQREAPURBBODY *pBody = (VRDEUSBREQREAPURBBODY *)pvBody;
+
+ /* 'pvBody' memory buffer can contain multiple URBs. */
+ while (cbBody >= sizeof(VRDEUSBREQREAPURBBODY))
+ {
+ Log(("RemoteUSBBackend::reapURB: id = %d, flags = %02X, error = %d, handle %d, len = %d.\n",
+ pBody->id, pBody->flags, pBody->error, pBody->handle, pBody->len));
+
+ uint8_t fu8ReapValidFlags;
+
+ if (mClientVersion == VRDE_USB_VERSION_1 || mClientVersion == VRDE_USB_VERSION_2)
+ {
+ fu8ReapValidFlags = VRDE_USB_REAP_VALID_FLAGS;
+ }
+ else
+ {
+ fu8ReapValidFlags = VRDE_USB_REAP_VALID_FLAGS_3;
+ }
+
+ /* Verify client's data. */
+ if ( (pBody->flags & ~fu8ReapValidFlags) != 0
+ || pBody->handle == 0)
+ {
+ LogFlow(("RemoteUSBBackend::reapURB: WARNING: invalid reply data. Skipping the reply.\n"));
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ PREMOTEUSBDEVICE pDevice = deviceFromId(pBody->id);
+
+ if (!pDevice)
+ {
+ LogFlow(("RemoteUSBBackend::reapURB: WARNING: invalid device id. Skipping the reply.\n"));
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ uint32_t cbBodyData = 0; /* Data contained in the URB body structure for input URBs. i.e. beyond VRDEUSBREQREAPURBBODY. */
+
+ requestDevice(pDevice);
+
+ /* Search the queued URB for given handle. */
+ REMOTEUSBQURB *qurb = pDevice->pHeadQURBs;
+
+ while (qurb && qurb->u32Handle != pBody->handle)
+ {
+ LogFlow(("RemoteUSBBackend::reapURB: searching: %p handle = %d.\n", qurb, qurb->u32Handle));
+ qurb = qurb->next;
+ }
+
+ if (!qurb)
+ {
+ LogFlow(("RemoteUSBBackend::reapURB: Queued URB not found, probably already canceled. Skipping the URB.\n"));
+ }
+ else
+ {
+ LogFlow(("RemoteUSBBackend::reapURB: qurb = %p, u32Err = %d\n", qurb, qurb->u32Err));
+
+ /* Update the URB error field, if it does not yet indicate an error. */
+ if (qurb->u32Err == VUSBSTATUS_OK)
+ {
+ if (mClientVersion == VRDE_USB_VERSION_1)
+ {
+ switch(pBody->error)
+ {
+ case VRDE_USB_XFER_OK: qurb->u32Err = VUSBSTATUS_OK; break;
+ case VRDE_USB_XFER_STALL: qurb->u32Err = VUSBSTATUS_STALL; break;
+ case VRDE_USB_XFER_DNR: qurb->u32Err = VUSBSTATUS_DNR; break;
+ case VRDE_USB_XFER_CRC: qurb->u32Err = VUSBSTATUS_CRC; break;
+ default: Log(("RemoteUSBBackend::reapURB: Invalid error %d\n", pBody->error));
+ qurb->u32Err = VUSBSTATUS_DNR; break;
+ }
+ }
+ else if ( mClientVersion == VRDE_USB_VERSION_2
+ || mClientVersion == VRDE_USB_VERSION_3)
+ {
+ switch(pBody->error)
+ {
+ case VRDE_USB_XFER_OK: qurb->u32Err = VUSBSTATUS_OK; break;
+ case VRDE_USB_XFER_STALL: qurb->u32Err = VUSBSTATUS_STALL; break;
+ case VRDE_USB_XFER_DNR: qurb->u32Err = VUSBSTATUS_DNR; break;
+ case VRDE_USB_XFER_CRC: qurb->u32Err = VUSBSTATUS_CRC; break;
+ case VRDE_USB_XFER_DO: qurb->u32Err = VUSBSTATUS_DATA_OVERRUN; break;
+ case VRDE_USB_XFER_DU: qurb->u32Err = VUSBSTATUS_DATA_UNDERRUN; break;
+
+ /* Unmapped errors. */
+ case VRDE_USB_XFER_BS:
+ case VRDE_USB_XFER_DTM:
+ case VRDE_USB_XFER_PCF:
+ case VRDE_USB_XFER_UPID:
+ case VRDE_USB_XFER_BO:
+ case VRDE_USB_XFER_BU:
+ case VRDE_USB_XFER_ERR:
+ default: Log(("RemoteUSBBackend::reapURB: Invalid error %d\n", pBody->error));
+ qurb->u32Err = VUSBSTATUS_DNR; break;
+ }
+ }
+ else
+ {
+ qurb->u32Err = VUSBSTATUS_DNR;
+ }
+ }
+
+ /* Get the URB data. The URB is completed unless the client tells that this is a fragment of an IN URB. */
+ bool fURBCompleted = true;
+
+ if (qurb->fInput)
+ {
+ if (pBody->len <= cbBody - sizeof(VRDEUSBREQREAPURBBODY))
+ cbBodyData = pBody->len; /* VRDE_USB_DIRECTION_IN URBs include some data. */
+ else
+ {
+ cbBodyData = cbBody - sizeof(VRDEUSBREQREAPURBBODY);
+ qurb->u32Err = VUSBSTATUS_DNR;
+ }
+
+ if (qurb->u32Err == VUSBSTATUS_OK)
+ {
+ LogFlow(("RemoteUSBBackend::reapURB: copying data %d bytes\n", pBody->len));
+ if (pBody->len > qurb->u32Len - qurb->u32TransferredLen)
+ {
+ /* Received more data than expected for this URB. If there more fragments follow,
+ * they will be discarded because the URB handle will not be valid anymore.
+ */
+ qurb->u32Err = VUSBSTATUS_DNR;
+ qurb->u32TransferredLen = qurb->u32Len;
+ }
+ else
+ {
+ memcpy ((uint8_t *)qurb->pvData + qurb->u32TransferredLen, &pBody[1], pBody->len);
+ qurb->u32TransferredLen += pBody->len;
+ }
+
+ if ( qurb->u32Err == VUSBSTATUS_OK
+ && (pBody->flags & VRDE_USB_REAP_FLAG_FRAGMENT) != 0)
+ {
+ /* If the client sends fragmented packets, accumulate the URB data. */
+ fURBCompleted = false;
+ }
+ }
+ }
+ else
+ qurb->u32TransferredLen += pBody->len; /* Update the value for OUT URBs. */
+
+ if (fURBCompleted)
+ {
+ /* Move the URB near the head of URB list, so that iface_ReapURB can
+ * find it faster. Note that the order of completion must be preserved!
+ */
+ if (qurb->prev)
+ {
+ /* The URB is not in the head. Unlink it from its current position. */
+ qurb->prev->next = qurb->next;
+
+ if (qurb->next)
+ {
+ qurb->next->prev = qurb->prev;
+ }
+ else
+ {
+ pDevice->pTailQURBs = qurb->prev;
+ }
+
+ /* And insert it to its new place. */
+ if (pDevice->pHeadQURBs->fCompleted)
+ {
+ /* At least one other completed URB; insert after the
+ * last completed URB.
+ */
+ REMOTEUSBQURB *prev_qurb = pDevice->pHeadQURBs;
+ while (prev_qurb->next && prev_qurb->next->fCompleted)
+ prev_qurb = prev_qurb->next;
+
+ qurb->next = prev_qurb->next;
+ qurb->prev = prev_qurb;
+
+ if (prev_qurb->next)
+ prev_qurb->next->prev = qurb;
+ else
+ pDevice->pTailQURBs = qurb;
+ prev_qurb->next = qurb;
+ }
+ else
+ {
+ /* No other completed URBs; insert at head. */
+ qurb->next = pDevice->pHeadQURBs;
+ qurb->prev = NULL;
+
+ pDevice->pHeadQURBs->prev = qurb;
+ pDevice->pHeadQURBs = qurb;
+ }
+ }
+
+ qurb->u32Len = qurb->u32TransferredLen; /* Update the final length. */
+ qurb->fCompleted = true;
+ }
+ }
+
+ releaseDevice (pDevice);
+
+ if (pBody->flags & VRDE_USB_REAP_FLAG_LAST)
+ {
+ break;
+ }
+
+ /* There is probably a further URB body. */
+ if (cbBodyData > cbBody - sizeof(VRDEUSBREQREAPURBBODY))
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ cbBody -= sizeof(VRDEUSBREQREAPURBBODY) + cbBodyData;
+ pBody = (VRDEUSBREQREAPURBBODY *)((uint8_t *)pBody + sizeof(VRDEUSBREQREAPURBBODY) + cbBodyData);
+ }
+
+ LogFlow(("RemoteUSBBackend::reapURB: returns %Rrc\n", vrc));
+
+ return vrc;
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/RemoteUSBDeviceImpl.cpp b/src/VBox/Main/src-client/RemoteUSBDeviceImpl.cpp
new file mode 100644
index 00000000..96f7aa9e
--- /dev/null
+++ b/src/VBox/Main/src-client/RemoteUSBDeviceImpl.cpp
@@ -0,0 +1,314 @@
+/* $Id: RemoteUSBDeviceImpl.cpp $ */
+/** @file
+ * VirtualBox IHostUSBDevice COM interface implementation for remote (VRDP) USB devices.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOSTUSBDEVICE
+#include "LoggingNew.h"
+
+#include "RemoteUSBDeviceImpl.h"
+
+#include "AutoCaller.h"
+
+#include <iprt/cpp/utils.h>
+
+#include <iprt/errcore.h>
+
+#include <VBox/RemoteDesktop/VRDE.h>
+#include <VBox/vrdpusb.h>
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(RemoteUSBDevice)
+
+HRESULT RemoteUSBDevice::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void RemoteUSBDevice::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/** @todo (sunlover) REMOTE_USB Device states. */
+
+/**
+ * Initializes the remote USB device object.
+ */
+HRESULT RemoteUSBDevice::init(uint32_t u32ClientId, VRDEUSBDEVICEDESC *pDevDesc, bool fDescExt)
+{
+ LogFlowThisFunc(("u32ClientId=%d,pDevDesc=%p\n", u32ClientId, pDevDesc));
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mData.id).create();
+
+ unconst(mData.vendorId) = pDevDesc->idVendor;
+ unconst(mData.productId) = pDevDesc->idProduct;
+ unconst(mData.revision) = pDevDesc->bcdRev;
+
+ unconst(mData.manufacturer) = pDevDesc->oManufacturer ? (char *)pDevDesc + pDevDesc->oManufacturer : "";
+ unconst(mData.product) = pDevDesc->oProduct ? (char *)pDevDesc + pDevDesc->oProduct : "";
+ unconst(mData.serialNumber) = pDevDesc->oSerialNumber ? (char *)pDevDesc + pDevDesc->oSerialNumber : "";
+
+ char id[64];
+ RTStrPrintf(id, sizeof(id), REMOTE_USB_BACKEND_PREFIX_S "0x%08X&0x%08X", pDevDesc->id, u32ClientId);
+ unconst(mData.address) = id;
+ unconst(mData.backend) = "vrdp";
+
+ char port[16];
+ RTStrPrintf(port, sizeof(port), "%u", pDevDesc->idPort);
+ unconst(mData.portPath) = port;
+
+ unconst(mData.port) = pDevDesc->idPort;
+ unconst(mData.version) = (uint16_t)(pDevDesc->bcdUSB >> 8);
+ if (fDescExt)
+ {
+ VRDEUSBDEVICEDESCEXT *pDevDescExt = (VRDEUSBDEVICEDESCEXT *)pDevDesc;
+ switch (pDevDescExt->u16DeviceSpeed)
+ {
+ default:
+ case VRDE_USBDEVICESPEED_UNKNOWN:
+ case VRDE_USBDEVICESPEED_LOW:
+ case VRDE_USBDEVICESPEED_FULL:
+ unconst(mData.speed) = USBConnectionSpeed_Full;
+ break;
+
+ case VRDE_USBDEVICESPEED_HIGH:
+ case VRDE_USBDEVICESPEED_VARIABLE:
+ unconst(mData.speed) = USBConnectionSpeed_High;
+ break;
+
+ case VRDE_USBDEVICESPEED_SUPERSPEED:
+ unconst(mData.speed) = USBConnectionSpeed_Super;
+ break;
+ }
+ }
+ else
+ {
+ unconst(mData.speed) = mData.version == 3 ? USBConnectionSpeed_Super
+ : mData.version == 2 ? USBConnectionSpeed_High
+ : USBConnectionSpeed_Full;
+ }
+
+ mData.state = USBDeviceState_Available;
+
+ mData.dirty = false;
+ unconst(mData.devId) = (uint16_t)pDevDesc->id;
+
+ unconst(mData.clientId) = u32ClientId;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void RemoteUSBDevice::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(mData.id).clear();
+
+ unconst(mData.vendorId) = 0;
+ unconst(mData.productId) = 0;
+ unconst(mData.revision) = 0;
+
+ unconst(mData.manufacturer).setNull();
+ unconst(mData.product).setNull();
+ unconst(mData.serialNumber).setNull();
+
+ unconst(mData.address).setNull();
+ unconst(mData.backend).setNull();
+
+ unconst(mData.port) = 0;
+ unconst(mData.portPath).setNull();
+ unconst(mData.version) = 1;
+
+ unconst(mData.dirty) = FALSE;
+
+ unconst(mData.devId) = 0;
+ unconst(mData.clientId) = 0;
+}
+
+// IUSBDevice properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT RemoteUSBDevice::getId(com::Guid &aId)
+{
+ aId = mData.id;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getVendorId(USHORT *aVendorId)
+{
+ /* this is const, no need to lock */
+ *aVendorId = mData.vendorId;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getProductId(USHORT *aProductId)
+{
+ /* this is const, no need to lock */
+ *aProductId = mData.productId;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getRevision(USHORT *aRevision)
+{
+ /* this is const, no need to lock */
+ *aRevision = mData.revision;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getManufacturer(com::Utf8Str &aManufacturer)
+{
+ /* this is const, no need to lock */
+ aManufacturer = mData.manufacturer;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getProduct(com::Utf8Str &aProduct)
+{
+ /* this is const, no need to lock */
+ aProduct = mData.product;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getSerialNumber(com::Utf8Str &aSerialNumber)
+{
+ /* this is const, no need to lock */
+ aSerialNumber = mData.serialNumber;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getAddress(com::Utf8Str &aAddress)
+{
+ /* this is const, no need to lock */
+ aAddress = mData.address;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getPort(USHORT *aPort)
+{
+ /* this is const, no need to lock */
+ *aPort = mData.port;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getPortPath(com::Utf8Str &aPortPath)
+{
+ /* this is const, no need to lock */
+ aPortPath = mData.portPath;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getVersion(USHORT *aVersion)
+{
+ /* this is const, no need to lock */
+ *aVersion = mData.version;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getSpeed(USBConnectionSpeed_T *aSpeed)
+{
+ /* this is const, no need to lock */
+ *aSpeed = mData.speed;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getRemote(BOOL *aRemote)
+{
+ /* RemoteUSBDevice is always remote. */
+ /* this is const, no need to lock */
+ *aRemote = TRUE;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getBackend(com::Utf8Str &aBackend)
+{
+ /* this is const, no need to lock */
+ aBackend = mData.backend;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getDeviceInfo(std::vector<com::Utf8Str> &aInfo)
+{
+ /* this is const, no need to lock */
+ aInfo.resize(2);
+ aInfo[0] = mData.manufacturer;
+ aInfo[1] = mData.product;
+
+ return S_OK;
+}
+
+// IHostUSBDevice properties
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT RemoteUSBDevice::getState(USBDeviceState_T *aState)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aState = mData.state;
+
+ return S_OK;
+}
+
+// public methods only for internal purposes
+////////////////////////////////////////////////////////////////////////////////
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/SessionImpl.cpp b/src/VBox/Main/src-client/SessionImpl.cpp
new file mode 100644
index 00000000..7fd7deaa
--- /dev/null
+++ b/src/VBox/Main/src-client/SessionImpl.cpp
@@ -0,0 +1,1339 @@
+/* $Id: SessionImpl.cpp $ */
+/** @file
+ * VBox Client Session COM Class implementation in VBoxC.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_SESSION
+#include "LoggingNew.h"
+
+#include "SessionImpl.h"
+#include "ConsoleImpl.h"
+#include "ClientTokenHolder.h"
+#include "Global.h"
+#include "StringifyEnums.h"
+
+#include "AutoCaller.h"
+
+#include <iprt/errcore.h>
+#include <iprt/process.h>
+
+
+/**
+ * Local macro to check whether the session is open and return an error if not.
+ * @note Don't forget to do |Auto[Reader]Lock alock (this);| before using this
+ * macro.
+ */
+#define CHECK_OPEN() \
+ do { \
+ if (mState != SessionState_Locked) \
+ return setError(E_UNEXPECTED, Session::tr("The session is not locked (session state: %s)"), \
+ Global::stringifySessionState(mState)); \
+ } while (0)
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+Session::Session()
+{
+}
+
+Session::~Session()
+{
+}
+
+HRESULT Session::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+
+ HRESULT hrc = init();
+
+ BaseFinalConstruct();
+
+ return hrc;
+}
+
+void Session::FinalRelease()
+{
+ LogFlowThisFunc(("\n"));
+
+ uninit();
+
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the Session object.
+ */
+HRESULT Session::init()
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ LogFlowThisFuncEnter();
+
+ mState = SessionState_Unlocked;
+ mType = SessionType_Null;
+
+ mClientTokenHolder = NULL;
+
+ /* Confirm a successful initialization when it's the case */
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the Session object.
+ *
+ * @note Locks this object for writing.
+ */
+void Session::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ {
+ LogFlowThisFunc(("Already uninitialized.\n"));
+ LogFlowThisFuncLeave();
+ return;
+ }
+
+ /* close() needs write lock */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mState != SessionState_Unlocked)
+ {
+ Assert(mState == SessionState_Locked ||
+ mState == SessionState_Spawning);
+
+ HRESULT hrc = i_unlockMachine(true /* aFinalRelease */, false /* aFromServer */, alock);
+ AssertComRC(hrc);
+ }
+
+ LogFlowThisFuncLeave();
+}
+
+// ISession properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT Session::getState(SessionState_T *aState)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aState = mState;
+
+ return S_OK;
+}
+
+HRESULT Session::getType(SessionType_T *aType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_OPEN();
+
+ *aType = mType;
+ return S_OK;
+}
+
+HRESULT Session::getName(com::Utf8Str &aName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aName = mName;
+ return S_OK;
+}
+
+HRESULT Session::setName(const com::Utf8Str &aName)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mState != SessionState_Unlocked)
+ return setError(VBOX_E_INVALID_OBJECT_STATE, tr("Trying to set name for a session which is not in state \"unlocked\""));
+
+ mName = aName;
+ return S_OK;
+}
+
+HRESULT Session::getMachine(ComPtr<IMachine> &aMachine)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_OPEN();
+
+ HRESULT hrc;
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (mConsole)
+ hrc = mConsole->i_machine().queryInterfaceTo(aMachine.asOutParam());
+ else
+#endif
+ hrc = mRemoteMachine.queryInterfaceTo(aMachine.asOutParam());
+ if (FAILED(hrc))
+ {
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (mConsole)
+ setError(hrc, tr("Failed to query the session machine"));
+ else
+#endif
+ if (FAILED_DEAD_INTERFACE(hrc))
+ setError(hrc, tr("Peer process crashed"));
+ else
+ setError(hrc, tr("Failed to query the remote session machine"));
+ }
+
+ return hrc;
+}
+
+HRESULT Session::getConsole(ComPtr<IConsole> &aConsole)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_OPEN();
+
+ HRESULT hrc = S_OK;
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (mConsole)
+ hrc = mConsole.queryInterfaceTo(aConsole.asOutParam());
+ else
+#endif
+ hrc = mRemoteConsole.queryInterfaceTo(aConsole.asOutParam());
+
+ if (FAILED(hrc))
+ {
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (mConsole)
+ setError(hrc, tr("Failed to query the console"));
+ else
+#endif
+ if (FAILED_DEAD_INTERFACE(hrc))
+ setError(hrc, tr("Peer process crashed"));
+ else
+ setError(hrc, tr("Failed to query the remote console"));
+ }
+
+ return hrc;
+}
+
+// ISession methods
+/////////////////////////////////////////////////////////////////////////////
+HRESULT Session::unlockMachine()
+{
+ LogFlowThisFunc(("mState=%d, mType=%d\n", mState, mType));
+
+ /* close() needs write lock */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_OPEN();
+ return i_unlockMachine(false /* aFinalRelease */, false /* aFromServer */, alock);
+}
+
+// IInternalSessionControl methods
+/////////////////////////////////////////////////////////////////////////////
+HRESULT Session::getPID(ULONG *aPid)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aPid = (ULONG)RTProcSelf();
+ AssertCompile(sizeof(*aPid) == sizeof(RTPROCESS));
+
+ return S_OK;
+}
+
+HRESULT Session::getRemoteConsole(ComPtr<IConsole> &aConsole)
+{
+ LogFlowThisFuncEnter();
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mType == SessionType_WriteLock && !!mConsole)
+ {
+ /* return a failure if the session already transitioned to Closing
+ * but the server hasn't processed Machine::OnSessionEnd() yet. */
+ if (mState == SessionState_Locked)
+ {
+ mConsole.queryInterfaceTo(aConsole.asOutParam());
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+ }
+ return VBOX_E_INVALID_VM_STATE;
+ }
+ return setError(VBOX_E_INVALID_OBJECT_STATE, "This is not a direct session");
+
+#else /* VBOX_COM_INPROC_API_CLIENT */
+ RT_NOREF(aConsole);
+ AssertFailed();
+ return VBOX_E_INVALID_OBJECT_STATE;
+#endif /* VBOX_COM_INPROC_API_CLIENT */
+}
+
+HRESULT Session::getNominalState(MachineState_T *aNominalState)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_getNominalState(*aNominalState);
+#else
+ RT_NOREF(aNominalState);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+HRESULT Session::assignMachine(const ComPtr<IMachine> &aMachine,
+ LockType_T aLockType,
+ const com::Utf8Str &aTokenId)
+#else
+HRESULT Session::assignMachine(const ComPtr<IMachine> &aMachine,
+ LockType_T aLockType,
+ const ComPtr<IToken> &aToken)
+#endif /* !VBOX_WITH_GENERIC_SESSION_WATCHER */
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(mState == SessionState_Unlocked, VBOX_E_INVALID_VM_STATE);
+
+ if (!aMachine)
+ {
+ /*
+ * A special case: the server informs us that this session has been
+ * passed to IMachine::launchVMProcess() so this session will become
+ * remote (but not existing) when AssignRemoteMachine() is called.
+ */
+
+ AssertReturn(mType == SessionType_Null, VBOX_E_INVALID_OBJECT_STATE);
+ mType = SessionType_Remote;
+ mState = SessionState_Spawning;
+
+ return S_OK;
+ }
+
+ /* query IInternalMachineControl interface */
+ mControl = aMachine;
+ AssertReturn(!!mControl, E_FAIL);
+
+ HRESULT hrc = S_OK;
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (aLockType == LockType_VM)
+ {
+ /* This is what is special about VM processes: they have a Console
+ * object which is the root of all VM related activity. */
+ hrc = mConsole.createObject();
+ AssertComRCReturn(hrc, hrc);
+
+ hrc = mConsole->initWithMachine(aMachine, mControl, aLockType);
+ AssertComRCReturn(hrc, hrc);
+ }
+ else
+ mRemoteMachine = aMachine;
+#else
+ RT_NOREF(aLockType);
+ mRemoteMachine = aMachine;
+#endif
+
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ Utf8Str strTokenId(aTokenId);
+ Assert(!strTokenId.isEmpty());
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ Assert(!aToken.isNull());
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ /* create the machine client token */
+ try
+ {
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ mClientTokenHolder = new ClientTokenHolder(strTokenId);
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ mClientTokenHolder = new ClientTokenHolder(aToken);
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ if (!mClientTokenHolder->isReady())
+ {
+ delete mClientTokenHolder;
+ mClientTokenHolder = NULL;
+ hrc = E_FAIL;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+
+ /*
+ * Reference the VirtualBox object to ensure the server is up
+ * until the session is closed
+ */
+ if (SUCCEEDED(hrc))
+ hrc = aMachine->COMGETTER(Parent)(mVirtualBox.asOutParam());
+
+ if (SUCCEEDED(hrc))
+ {
+ mType = SessionType_WriteLock;
+ mState = SessionState_Locked;
+ }
+ else
+ {
+ /* some cleanup */
+ mControl.setNull();
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (!mConsole.isNull())
+ {
+ mConsole->uninit();
+ mConsole.setNull();
+ }
+#endif
+ }
+
+ return hrc;
+}
+
+HRESULT Session::assignRemoteMachine(const ComPtr<IMachine> &aMachine,
+ const ComPtr<IConsole> &aConsole)
+
+{
+ AssertReturn(aMachine, E_INVALIDARG);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(mState == SessionState_Unlocked ||
+ mState == SessionState_Spawning, VBOX_E_INVALID_VM_STATE);
+
+ HRESULT hrc = E_FAIL;
+
+ /* query IInternalMachineControl interface */
+ mControl = aMachine;
+ AssertReturn(!!mControl, E_FAIL);
+
+ /// @todo (dmik)
+ // currently, the remote session returns the same machine and
+ // console objects as the direct session, thus giving the
+ // (remote) client full control over the direct session. For the
+ // console, it is the desired behavior (the ability to control
+ // VM execution is a must for the remote session). What about
+ // the machine object, we may want to prevent the remote client
+ // from modifying machine data. In this case, we must:
+ // 1) assign the Machine object (instead of the SessionMachine
+ // object that is passed to this method) to mRemoteMachine;
+ // 2) remove GetMachine() property from the IConsole interface
+ // because it always returns the SessionMachine object
+ // (alternatively, we can supply a separate IConsole
+ // implementation that will return the Machine object in
+ // response to GetMachine()).
+
+ mRemoteMachine = aMachine;
+ mRemoteConsole = aConsole;
+
+ /*
+ * Reference the VirtualBox object to ensure the server is up
+ * until the session is closed
+ */
+ hrc = aMachine->COMGETTER(Parent)(mVirtualBox.asOutParam());
+
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * RemoteSession type can be already set by AssignMachine() when its
+ * argument is NULL (a special case)
+ */
+ if (mType != SessionType_Remote)
+ mType = SessionType_Shared;
+ else
+ Assert(mState == SessionState_Spawning);
+
+ mState = SessionState_Locked;
+ }
+ else
+ {
+ /* some cleanup */
+ mControl.setNull();
+ mRemoteMachine.setNull();
+ mRemoteConsole.setNull();
+ }
+
+ LogFlowThisFunc(("hrc=%08X\n", hrc));
+ LogFlowThisFuncLeave();
+
+ return hrc;
+}
+
+HRESULT Session::updateMachineState(MachineState_T aMachineState)
+{
+
+ if (getObjectState().getState() != ObjectState::Ready)
+ {
+ /*
+ * We might have already entered Session::uninit() at this point, so
+ * return silently (not interested in the state change during uninit)
+ */
+ LogFlowThisFunc(("Already uninitialized.\n"));
+ return S_OK;
+ }
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mState == SessionState_Unlocking)
+ {
+ LogFlowThisFunc(("Already being unlocked.\n"));
+ return S_OK;
+ }
+
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+
+ AssertReturn(!mControl.isNull(), E_FAIL);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(!mConsole.isNull(), E_FAIL);
+
+ return mConsole->i_updateMachineState(aMachineState);
+#else
+ RT_NOREF(aMachineState);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::uninitialize()
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+
+ HRESULT hrc = S_OK;
+
+ if (getObjectState().getState() == ObjectState::Ready)
+ {
+ /* close() needs write lock */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("mState=%s, mType=%d\n", ::stringifySessionState(mState), mType));
+
+ if (mState == SessionState_Unlocking)
+ {
+ LogFlowThisFunc(("Already being unlocked.\n"));
+ return S_OK;
+ }
+
+ if ( mState == SessionState_Locked
+ || mState == SessionState_Spawning)
+ { /* likely */ }
+ else
+ {
+#ifndef DEBUG_bird /* bird: hitting this all the time running tdAddBaseic1.py. */
+ AssertMsgFailed(("Session is in wrong state (%d), expected locked (%d) or spawning (%d)\n",
+ mState, SessionState_Locked, SessionState_Spawning));
+#endif
+ return VBOX_E_INVALID_VM_STATE;
+ }
+
+ /* close ourselves */
+ hrc = i_unlockMachine(false /* aFinalRelease */, true /* aFromServer */, alock);
+ }
+ else if (getObjectState().getState() == ObjectState::InUninit)
+ {
+ /*
+ * We might have already entered Session::uninit() at this point,
+ * return silently
+ */
+ LogFlowThisFunc(("Already uninitialized.\n"));
+ }
+ else
+ {
+ Log1WarningThisFunc(("UNEXPECTED uninitialization!\n"));
+ hrc = autoCaller.rc();
+ }
+
+ LogFlowThisFunc(("hrc=%08X\n", hrc));
+ LogFlowThisFuncLeave();
+
+ return hrc;
+}
+
+HRESULT Session::onNetworkAdapterChange(const ComPtr<INetworkAdapter> &aNetworkAdapter,
+ BOOL aChangeAdapter)
+
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onNetworkAdapterChange(aNetworkAdapter, aChangeAdapter);
+#else
+ RT_NOREF(aNetworkAdapter, aChangeAdapter);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onAudioAdapterChange(const ComPtr<IAudioAdapter> &aAudioAdapter)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onAudioAdapterChange(aAudioAdapter);
+#else
+ RT_NOREF(aAudioAdapter);
+ return S_OK;
+#endif
+
+}
+
+HRESULT Session::onHostAudioDeviceChange(const ComPtr<IHostAudioDevice> &aDevice,
+ BOOL aNew, AudioDeviceState_T aState,
+ const ComPtr<IVirtualBoxErrorInfo> &aErrInfo)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onHostAudioDeviceChange(aDevice, aNew, aState, aErrInfo);
+#else
+ RT_NOREF(aDevice, aNew, aState, aErrInfo);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onSerialPortChange(const ComPtr<ISerialPort> &aSerialPort)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onSerialPortChange(aSerialPort);
+#else
+ RT_NOREF(aSerialPort);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onParallelPortChange(const ComPtr<IParallelPort> &aParallelPort)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onParallelPortChange(aParallelPort);
+#else
+ RT_NOREF(aParallelPort);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onStorageControllerChange(const Guid &aMachineId, const Utf8Str &aControllerName)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onStorageControllerChange(aMachineId, aControllerName);
+#else
+ NOREF(aMachineId);
+ NOREF(aControllerName);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onMediumChange(const ComPtr<IMediumAttachment> &aMediumAttachment,
+ BOOL aForce)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onMediumChange(aMediumAttachment, aForce);
+#else
+ RT_NOREF(aMediumAttachment, aForce);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onVMProcessPriorityChange(VMProcPriority_T priority)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onVMProcessPriorityChange(priority);
+#else
+ RT_NOREF(priority);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onCPUChange(ULONG aCpu, BOOL aAdd)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onCPUChange(aCpu, aAdd);
+#else
+ RT_NOREF(aCpu, aAdd);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onCPUExecutionCapChange(ULONG aExecutionCap)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onCPUExecutionCapChange(aExecutionCap);
+#else
+ RT_NOREF(aExecutionCap);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onVRDEServerChange(BOOL aRestart)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onVRDEServerChange(aRestart);
+#else
+ RT_NOREF(aRestart);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onRecordingChange(BOOL aEnable)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onRecordingChange(aEnable);
+#else
+ RT_NOREF(aEnable);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onUSBControllerChange()
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onUSBControllerChange();
+#else
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onSharedFolderChange(BOOL aGlobal)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onSharedFolderChange(aGlobal);
+#else
+ RT_NOREF(aGlobal);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onClipboardModeChange(ClipboardMode_T aClipboardMode)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onClipboardModeChange(aClipboardMode);
+#else
+ RT_NOREF(aClipboardMode);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onClipboardFileTransferModeChange(BOOL aEnabled)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onClipboardFileTransferModeChange(RT_BOOL(aEnabled));
+#else
+ RT_NOREF(aEnabled);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onDnDModeChange(DnDMode_T aDndMode)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onDnDModeChange(aDndMode);
+#else
+ RT_NOREF(aDndMode);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onGuestDebugControlChange(const ComPtr<IGuestDebugControl> &aGuestDebugControl)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onGuestDebugControlChange(aGuestDebugControl);
+#else
+ RT_NOREF(aGuestDebugControl);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onUSBDeviceAttach(const ComPtr<IUSBDevice> &aDevice,
+ const ComPtr<IVirtualBoxErrorInfo> &aError,
+ ULONG aMaskedInterfaces,
+ const com::Utf8Str &aCaptureFilename)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onUSBDeviceAttach(aDevice, aError, aMaskedInterfaces, aCaptureFilename);
+#else
+ RT_NOREF(aDevice, aError, aMaskedInterfaces, aCaptureFilename);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onUSBDeviceDetach(const com::Guid &aId,
+ const ComPtr<IVirtualBoxErrorInfo> &aError)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onUSBDeviceDetach(aId.toUtf16().raw(), aError);
+#else
+ RT_NOREF(aId, aError);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onShowWindow(BOOL aCheck, BOOL *aCanShow, LONG64 *aWinId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+#endif
+
+ if (mState != SessionState_Locked)
+ {
+ /* the call from Machine issued when the session is open can arrive
+ * after the session starts closing or gets closed. Note that when
+ * aCheck is false, we return E_FAIL to indicate that aWinId we return
+ * is not valid */
+ *aCanShow = FALSE;
+ *aWinId = 0;
+ return aCheck ? S_OK : E_FAIL;
+ }
+
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ return mConsole->i_onShowWindow(aCheck, aCanShow, aWinId);
+#else
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onBandwidthGroupChange(const ComPtr<IBandwidthGroup> &aBandwidthGroup)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onBandwidthGroupChange(aBandwidthGroup);
+#else
+ RT_NOREF(aBandwidthGroup);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onStorageDeviceChange(const ComPtr<IMediumAttachment> &aMediumAttachment, BOOL aRemove, BOOL aSilent)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onStorageDeviceChange(aMediumAttachment, aRemove, aSilent);
+#else
+ RT_NOREF(aMediumAttachment, aRemove, aSilent);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::accessGuestProperty(const com::Utf8Str &aName, const com::Utf8Str &aValue, const com::Utf8Str &aFlags,
+ ULONG aAccessMode, com::Utf8Str &aRetValue, LONG64 *aRetTimestamp, com::Utf8Str &aRetFlags)
+{
+#ifdef VBOX_WITH_GUEST_PROPS
+# ifndef VBOX_COM_INPROC_API_CLIENT
+ if (mState != SessionState_Locked)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Machine is not locked by session (session state: %s)."),
+ Global::stringifySessionState(mState));
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+ if (aName.isEmpty())
+ return E_INVALIDARG;
+ if (aAccessMode == 0 && !RT_VALID_PTR(aRetTimestamp))
+ return E_POINTER;
+
+ /* If this session is not in a VM process fend off the call. The caller
+ * handles this correctly, by doing the operation in VBoxSVC. */
+ if (!mConsole)
+ return E_ACCESSDENIED;
+
+ HRESULT hr;
+ if (aAccessMode == 2)
+ hr = mConsole->i_deleteGuestProperty(aName);
+ else if (aAccessMode == 1)
+ hr = mConsole->i_setGuestProperty(aName, aValue, aFlags);
+ else if (aAccessMode == 0)
+ hr = mConsole->i_getGuestProperty(aName, &aRetValue, aRetTimestamp, &aRetFlags);
+ else
+ hr = E_INVALIDARG;
+
+ return hr;
+# else /* VBOX_COM_INPROC_API_CLIENT */
+ /** @todo This is nonsense, non-VM API users shouldn't need to deal with this
+ * method call, VBoxSVC should be clever enough to see that the
+ * session doesn't have a console! */
+ RT_NOREF(aName, aValue, aFlags, aAccessMode, aRetValue, aRetTimestamp, aRetFlags);
+ return E_ACCESSDENIED;
+# endif /* VBOX_COM_INPROC_API_CLIENT */
+
+#else /* VBOX_WITH_GUEST_PROPS */
+ ReturnComNotImplemented();
+#endif /* VBOX_WITH_GUEST_PROPS */
+}
+
+HRESULT Session::enumerateGuestProperties(const com::Utf8Str &aPatterns,
+ std::vector<com::Utf8Str> &aKeys,
+ std::vector<com::Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<com::Utf8Str> &aFlags)
+{
+#if defined(VBOX_WITH_GUEST_PROPS) && !defined(VBOX_COM_INPROC_API_CLIENT)
+ if (mState != SessionState_Locked)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Machine is not locked by session (session state: %s)."),
+ Global::stringifySessionState(mState));
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+
+ /* If this session is not in a VM process fend off the call. The caller
+ * handles this correctly, by doing the operation in VBoxSVC. */
+ if (!mConsole)
+ return E_ACCESSDENIED;
+
+ return mConsole->i_enumerateGuestProperties(aPatterns, aKeys, aValues, aTimestamps, aFlags);
+
+#else /* VBOX_WITH_GUEST_PROPS not defined */
+ RT_NOREF(aPatterns, aKeys, aValues, aTimestamps, aFlags);
+ ReturnComNotImplemented();
+#endif /* VBOX_WITH_GUEST_PROPS not defined */
+}
+
+HRESULT Session::onlineMergeMedium(const ComPtr<IMediumAttachment> &aMediumAttachment, ULONG aSourceIdx,
+ ULONG aTargetIdx, const ComPtr<IProgress> &aProgress)
+{
+ if (mState != SessionState_Locked)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Machine is not locked by session (session state: %s)."),
+ Global::stringifySessionState(mState));
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onlineMergeMedium(aMediumAttachment,
+ aSourceIdx, aTargetIdx,
+ aProgress);
+#else
+ RT_NOREF(aMediumAttachment, aSourceIdx, aTargetIdx, aProgress);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Session::reconfigureMediumAttachments(const std::vector<ComPtr<IMediumAttachment> > &aAttachments)
+{
+ if (mState != SessionState_Locked)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Machine is not locked by session (session state: %s)."),
+ Global::stringifySessionState(mState));
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_reconfigureMediumAttachments(aAttachments);
+#else
+ RT_NOREF(aAttachments);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Session::enableVMMStatistics(BOOL aEnable)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ mConsole->i_enableVMMStatistics(aEnable);
+
+ return S_OK;
+#else
+ RT_NOREF(aEnable);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Session::pauseWithReason(Reason_T aReason)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_pause(aReason);
+#else
+ RT_NOREF(aReason);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Session::resumeWithReason(Reason_T aReason)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ AutoWriteLock dummyLock(mConsole COMMA_LOCKVAL_SRC_POS);
+ return mConsole->i_resume(aReason, dummyLock);
+#else
+ RT_NOREF(aReason);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Session::saveStateWithReason(Reason_T aReason,
+ const ComPtr<IProgress> &aProgress,
+ const ComPtr<ISnapshot> &aSnapshot,
+ const Utf8Str &aStateFilePath,
+ BOOL aPauseVM, BOOL *aLeftPaused)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ bool fLeftPaused = false;
+ HRESULT hrc = mConsole->i_saveState(aReason, aProgress, aSnapshot, aStateFilePath, !!aPauseVM, fLeftPaused);
+ if (aLeftPaused)
+ *aLeftPaused = fLeftPaused;
+ return hrc;
+#else
+ RT_NOREF(aReason, aProgress, aSnapshot, aStateFilePath, aPauseVM, aLeftPaused);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Session::cancelSaveStateWithReason()
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_cancelSaveState();
+#else
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+// private methods
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Unlocks a machine associated with the current session.
+ *
+ * @param aFinalRelease called as a result of FinalRelease()
+ * @param aFromServer called as a result of Uninitialize()
+ * @param aLockW The write lock this object is protected with.
+ * Must be acquired already and will be released
+ * and later reacquired during the unlocking.
+ *
+ * @note To be called only from #uninit(), ISession::UnlockMachine() or
+ * ISession::Uninitialize().
+ */
+HRESULT Session::i_unlockMachine(bool aFinalRelease, bool aFromServer, AutoWriteLock &aLockW)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aFinalRelease=%d, isFromServer=%d\n",
+ aFinalRelease, aFromServer));
+
+ LogFlowThisFunc(("mState=%s, mType=%d\n", ::stringifySessionState(mState), mType));
+
+ Assert(aLockW.isWriteLockOnCurrentThread());
+
+ if (mState != SessionState_Locked)
+ {
+ Assert(mState == SessionState_Spawning);
+
+ /* The session object is going to be uninitialized before it has been
+ * assigned a direct console of the machine the client requested to open
+ * a remote session to using IVirtualBox:: openRemoteSession(). It is OK
+ * only if this close request comes from the server (for example, it
+ * detected that the VM process it started terminated before opening a
+ * direct session). Otherwise, it means that the client is too fast and
+ * trying to close the session before waiting for the progress object it
+ * got from IVirtualBox:: openRemoteSession() to complete, so assert. */
+ Assert(aFromServer);
+
+ mState = SessionState_Unlocked;
+ mType = SessionType_Null;
+
+ Assert(!mClientTokenHolder);
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+ }
+
+ /* go to the closing state */
+ mState = SessionState_Unlocking;
+
+ if (mType == SessionType_WriteLock)
+ {
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (!mConsole.isNull())
+ {
+ mConsole->uninit();
+ mConsole.setNull();
+ }
+#else
+ mRemoteMachine.setNull();
+#endif
+ }
+ else
+ {
+ mRemoteMachine.setNull();
+ mRemoteConsole.setNull();
+ }
+
+ ComPtr<IProgress> progress;
+
+ if (!aFinalRelease && !aFromServer)
+ {
+ /*
+ * We trigger OnSessionEnd() only when the session closes itself using
+ * Close(). Note that if isFinalRelease = TRUE here, this means that
+ * the client process has already initialized the termination procedure
+ * without issuing Close() and the IPC channel is no more operational --
+ * so we cannot call the server's method (it will definitely fail). The
+ * server will instead simply detect the abnormal client death (since
+ * OnSessionEnd() is not called) and reset the machine state to Aborted.
+ */
+
+ /*
+ * while waiting for OnSessionEnd() to complete one of our methods
+ * can be called by the server (for example, Uninitialize(), if the
+ * direct session has initiated a closure just a bit before us) so
+ * we need to release the lock to avoid deadlocks. The state is already
+ * SessionState_Closing here, so it's safe.
+ */
+ aLockW.release();
+
+ Assert(!aLockW.isWriteLockOnCurrentThread());
+
+ LogFlowThisFunc(("Calling mControl->OnSessionEnd()...\n"));
+ HRESULT hrc = mControl->OnSessionEnd(this, progress.asOutParam());
+ LogFlowThisFunc(("mControl->OnSessionEnd()=%08X\n", hrc));
+
+ aLockW.acquire();
+
+ /*
+ * If we get E_UNEXPECTED this means that the direct session has already
+ * been closed, we're just too late with our notification and nothing more
+ *
+ * bird: Seems E_ACCESSDENIED is what gets returned these days; see
+ * ObjectState::addCaller.
+ */
+ if (mType != SessionType_WriteLock && (hrc == E_UNEXPECTED || hrc == E_ACCESSDENIED))
+ hrc = S_OK;
+
+#if !defined(DEBUG_bird) && !defined(DEBUG_andy) /* I don't want clients crashing on me just because VBoxSVC went belly up. */
+ AssertComRC(hrc);
+#endif
+ }
+
+ mControl.setNull();
+
+ if (mType == SessionType_WriteLock)
+ {
+ if (mClientTokenHolder)
+ {
+ delete mClientTokenHolder;
+ mClientTokenHolder = NULL;
+ }
+
+ if (!aFinalRelease && !aFromServer)
+ {
+ /*
+ * Wait for the server to grab the semaphore and destroy the session
+ * machine (allowing us to open a new session with the same machine
+ * once this method returns)
+ */
+ Assert(!!progress);
+ if (progress)
+ progress->WaitForCompletion(-1);
+ }
+ }
+
+ mState = SessionState_Unlocked;
+ mType = SessionType_Null;
+
+ /* release the VirtualBox instance as the very last step */
+ mVirtualBox.setNull();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/USBDeviceImpl.cpp b/src/VBox/Main/src-client/USBDeviceImpl.cpp
new file mode 100644
index 00000000..1e957514
--- /dev/null
+++ b/src/VBox/Main/src-client/USBDeviceImpl.cpp
@@ -0,0 +1,351 @@
+/* $Id: USBDeviceImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_USBDEVICE
+#include "LoggingNew.h"
+
+#include "USBDeviceImpl.h"
+
+#include "AutoCaller.h"
+
+#include <iprt/cpp/utils.h>
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR (OUSBDevice)
+
+HRESULT OUSBDevice::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void OUSBDevice::FinalRelease()
+{
+ uninit ();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the USB device object.
+ *
+ * @returns COM result indicator
+ * @param aUSBDevice The USB device (interface) to clone.
+ */
+HRESULT OUSBDevice::init(IUSBDevice *aUSBDevice)
+{
+ LogFlowThisFunc(("aUSBDevice=%p\n", aUSBDevice));
+
+ ComAssertRet(aUSBDevice, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT hrc = aUSBDevice->COMGETTER(VendorId)(&unconst(mData.vendorId));
+ ComAssertComRCRet(hrc, hrc);
+ ComAssertRet(mData.vendorId, E_INVALIDARG);
+
+ hrc = aUSBDevice->COMGETTER(ProductId)(&unconst(mData.productId));
+ ComAssertComRCRet(hrc, hrc);
+
+ hrc = aUSBDevice->COMGETTER(Revision)(&unconst(mData.revision));
+ ComAssertComRCRet(hrc, hrc);
+
+ Bstr bstr;
+
+ hrc = aUSBDevice->COMGETTER(Manufacturer)(bstr.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ unconst(mData.manufacturer) = bstr;
+
+ hrc = aUSBDevice->COMGETTER(Product)(bstr.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ unconst(mData.product) = bstr;
+
+ hrc = aUSBDevice->COMGETTER(SerialNumber)(bstr.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ unconst(mData.serialNumber) = bstr;
+
+ hrc = aUSBDevice->COMGETTER(Address)(bstr.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ unconst(mData.address) = bstr;
+
+ hrc = aUSBDevice->COMGETTER(Backend)(bstr.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ unconst(mData.backend) = bstr;
+
+ hrc = aUSBDevice->COMGETTER(Port)(&unconst(mData.port));
+ ComAssertComRCRet(hrc, hrc);
+
+ hrc = aUSBDevice->COMGETTER(PortPath)(bstr.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+
+ hrc = aUSBDevice->COMGETTER(Version)(&unconst(mData.version));
+ ComAssertComRCRet(hrc, hrc);
+
+ hrc = aUSBDevice->COMGETTER(Speed)(&unconst(mData.speed));
+ ComAssertComRCRet(hrc, hrc);
+
+ hrc = aUSBDevice->COMGETTER(Remote)(&unconst(mData.remote));
+ ComAssertComRCRet(hrc, hrc);
+
+ Bstr uuid;
+ hrc = aUSBDevice->COMGETTER(Id)(uuid.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ unconst(mData.id) = Guid(uuid);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void OUSBDevice::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(mData.id).clear();
+
+ unconst(mData.vendorId) = 0;
+ unconst(mData.productId) = 0;
+ unconst(mData.revision) = 0;
+
+ unconst(mData.manufacturer).setNull();
+ unconst(mData.product).setNull();
+ unconst(mData.serialNumber).setNull();
+
+ unconst(mData.address).setNull();
+ unconst(mData.backend).setNull();
+
+ unconst(mData.port) = 0;
+ unconst(mData.portPath).setNull();
+ unconst(mData.version) = 1;
+
+ unconst(mData.remote) = FALSE;
+}
+
+// IUSBDevice properties
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns the GUID.
+ *
+ * @returns COM status code
+ * @param aId Address of result variable.
+ */
+HRESULT OUSBDevice::getId(com::Guid &aId)
+{
+ /* this is const, no need to lock */
+ aId = mData.id;
+
+ return S_OK;
+}
+
+
+/**
+ * Returns the vendor Id.
+ *
+ * @returns COM status code
+ * @param aVendorId Where to store the vendor id.
+ */
+HRESULT OUSBDevice::getVendorId(USHORT *aVendorId)
+{
+ /* this is const, no need to lock */
+ *aVendorId = mData.vendorId;
+
+ return S_OK;
+}
+
+
+/**
+ * Returns the product Id.
+ *
+ * @returns COM status code
+ * @param aProductId Where to store the product id.
+ */
+HRESULT OUSBDevice::getProductId(USHORT *aProductId)
+{
+ /* this is const, no need to lock */
+ *aProductId = mData.productId;
+
+ return S_OK;
+}
+
+
+/**
+ * Returns the revision BCD.
+ *
+ * @returns COM status code
+ * @param aRevision Where to store the revision BCD.
+ */
+HRESULT OUSBDevice::getRevision(USHORT *aRevision)
+{
+ /* this is const, no need to lock */
+ *aRevision = mData.revision;
+
+ return S_OK;
+}
+
+/**
+ * Returns the manufacturer string.
+ *
+ * @returns COM status code
+ * @param aManufacturer Where to put the return string.
+ */
+HRESULT OUSBDevice::getManufacturer(com::Utf8Str &aManufacturer)
+{
+ /* this is const, no need to lock */
+ aManufacturer = mData.manufacturer;
+
+ return S_OK;
+}
+
+
+/**
+ * Returns the product string.
+ *
+ * @returns COM status code
+ * @param aProduct Where to put the return string.
+ */
+HRESULT OUSBDevice::getProduct(com::Utf8Str &aProduct)
+{
+ /* this is const, no need to lock */
+ aProduct = mData.product;
+
+ return S_OK;
+}
+
+
+/**
+ * Returns the serial number string.
+ *
+ * @returns COM status code
+ * @param aSerialNumber Where to put the return string.
+ */
+HRESULT OUSBDevice::getSerialNumber(com::Utf8Str &aSerialNumber)
+{
+ /* this is const, no need to lock */
+ aSerialNumber = mData.serialNumber;
+
+ return S_OK;
+}
+
+
+/**
+ * Returns the host specific device address.
+ *
+ * @returns COM status code
+ * @param aAddress Where to put the return string.
+ */
+HRESULT OUSBDevice::getAddress(com::Utf8Str &aAddress)
+{
+ /* this is const, no need to lock */
+ aAddress = mData.address;
+
+ return S_OK;
+}
+
+HRESULT OUSBDevice::getPort(USHORT *aPort)
+{
+ /* this is const, no need to lock */
+ *aPort = mData.port;
+
+ return S_OK;
+}
+
+HRESULT OUSBDevice::getPortPath(com::Utf8Str &aPortPath)
+{
+ /* this is const, no need to lock */
+ aPortPath = mData.portPath;
+
+ return S_OK;
+}
+
+HRESULT OUSBDevice::getVersion(USHORT *aVersion)
+{
+ /* this is const, no need to lock */
+ *aVersion = mData.version;
+
+ return S_OK;
+}
+
+HRESULT OUSBDevice::getSpeed(USBConnectionSpeed_T *aSpeed)
+{
+ /* this is const, no need to lock */
+ *aSpeed = mData.speed;
+
+ return S_OK;
+}
+
+HRESULT OUSBDevice::getRemote(BOOL *aRemote)
+{
+ /* this is const, no need to lock */
+ *aRemote = mData.remote;
+
+ return S_OK;
+}
+
+/**
+ * Returns the device specific backend.
+ *
+ * @returns COM status code
+ * @param aBackend Where to put the return string.
+ */
+HRESULT OUSBDevice::getBackend(com::Utf8Str &aBackend)
+{
+ /* this is const, no need to lock */
+ aBackend = mData.backend;
+
+ return S_OK;
+}
+
+HRESULT OUSBDevice::getDeviceInfo(std::vector<com::Utf8Str> &aInfo)
+{
+ /* this is const, no need to lock */
+ aInfo.resize(2);
+ aInfo[0] = mData.manufacturer;
+ aInfo[1] = mData.product;
+
+ return S_OK;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/UsbCardReader.cpp b/src/VBox/Main/src-client/UsbCardReader.cpp
new file mode 100644
index 00000000..9ffb9997
--- /dev/null
+++ b/src/VBox/Main/src-client/UsbCardReader.cpp
@@ -0,0 +1,1986 @@
+/* $Id: UsbCardReader.cpp $ */
+/** @file
+ * UsbCardReader - Driver Interface to USB Smart Card Reader emulation.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_USB_CARDREADER
+#include "LoggingNew.h"
+
+#include "UsbCardReader.h"
+#include "ConsoleImpl.h"
+#include "ConsoleVRDPServer.h"
+
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmcardreaderinfs.h>
+#include <VBox/err.h>
+
+#include <iprt/req.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct USBCARDREADER USBCARDREADER;
+typedef struct USBCARDREADER *PUSBCARDREADER;
+
+struct USBCARDREADER
+{
+ UsbCardReader *pUsbCardReader;
+
+ PPDMDRVINS pDrvIns;
+
+ PDMICARDREADERDOWN ICardReaderDown;
+ PPDMICARDREADERUP pICardReaderUp;
+
+ /* Thread handling Cmd to card reader */
+ PPDMTHREAD pThrCardReaderCmd;
+ /* Queue handling requests to cardreader */
+ RTREQQUEUE hReqQCardReaderCmd;
+};
+
+
+/*
+ * Command queue's callbacks.
+ */
+
+static DECLCALLBACK(void) drvCardReaderCmdStatusChange(PUSBCARDREADER pThis,
+ void *pvUser,
+ uint32_t u32Timeout,
+ PDMICARDREADER_READERSTATE *paReaderStats,
+ uint32_t cReaderStats)
+{
+ LogFlowFunc(("ENTER: pvUser:%p, u32Timeout:%d\n",
+ pvUser, u32Timeout));
+
+ UsbCardReader *pUsbCardReader = pThis->pUsbCardReader;
+ if (!pUsbCardReader)
+ {
+ pThis->pICardReaderUp->pfnSetStatusChange(pThis->pICardReaderUp,
+ pvUser, VRDE_SCARD_E_NO_SMARTCARD,
+ paReaderStats, cReaderStats);
+ }
+ else
+ {
+ pUsbCardReader->GetStatusChange(pThis, pvUser, u32Timeout,
+ paReaderStats, cReaderStats);
+ }
+
+ LogFlowFuncLeave();
+}
+
+
+static DECLCALLBACK(void) drvCardReaderCmdEstablishContext(PUSBCARDREADER pThis)
+{
+ LogFlowFunc(("\n"));
+
+ UsbCardReader *pUsbCardReader = pThis->pUsbCardReader;
+ if (!pUsbCardReader)
+ {
+ pThis->pICardReaderUp->pfnEstablishContext(pThis->pICardReaderUp,
+ VRDE_SCARD_E_NO_SMARTCARD);
+ }
+ else
+ {
+ pUsbCardReader->EstablishContext(pThis);
+ }
+
+ LogFlowFuncLeave();
+}
+
+static DECLCALLBACK(void) drvCardReaderCmdReleaseContext(PUSBCARDREADER pThis,
+ void *pvUser)
+{
+ LogFlowFunc(("ENTER: pvUser:%p\n",
+ pvUser));
+ NOREF(pvUser);
+
+ UsbCardReader *pUsbCardReader = pThis->pUsbCardReader;
+ if (!pUsbCardReader)
+ {
+ /* Do nothing. */
+ }
+ else
+ {
+ pUsbCardReader->ReleaseContext(pThis);
+ }
+
+ LogFlowFuncLeave();
+}
+
+static DECLCALLBACK(void) drvCardReaderCmdStatus(PUSBCARDREADER pThis,
+ void *pvUser)
+{
+ LogFlowFunc(("ENTER: pvUser:%p\n",
+ pvUser));
+
+ UsbCardReader *pUsbCardReader = pThis->pUsbCardReader;
+ if (!pUsbCardReader)
+ {
+ pThis->pICardReaderUp->pfnStatus(pThis->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD,
+ /* pszReaderName */ NULL,
+ /* cchReaderName */ 0,
+ /* u32CardState */ 0,
+ /* u32Protocol */ 0,
+ /* pu8Atr */ 0,
+ /* cbAtr */ 0);
+ }
+ else
+ {
+ pUsbCardReader->Status(pThis, pvUser);
+ }
+
+ LogFlowFuncLeave();
+}
+
+static DECLCALLBACK(void) drvCardReaderCmdConnect(PUSBCARDREADER pThis,
+ void *pvUser,
+ const char *pcszCardReaderName,
+ uint32_t u32ShareMode,
+ uint32_t u32PreferredProtocols)
+{
+ LogFlowFunc(("ENTER: pvUser:%p, pcszCardReaderName:%s, u32ShareMode:%RX32, u32PreferredProtocols:%RX32\n",
+ pvUser, pcszCardReaderName, u32ShareMode, u32PreferredProtocols));
+
+ UsbCardReader *pUsbCardReader = pThis->pUsbCardReader;
+ if (!pUsbCardReader)
+ {
+ pThis->pICardReaderUp->pfnConnect(pThis->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD,
+ 0);
+ }
+ else
+ {
+ pUsbCardReader->Connect(pThis, pvUser, pcszCardReaderName,
+ u32ShareMode, u32PreferredProtocols);
+ }
+
+ LogFlowFuncLeave();
+}
+
+static DECLCALLBACK(void) drvCardReaderCmdDisconnect(PUSBCARDREADER pThis,
+ void *pvUser,
+ uint32_t u32Disposition)
+{
+ LogFlowFunc(("ENTER: pvUser:%p, u32Disposition:%RX32\n",
+ pvUser, u32Disposition));
+
+ UsbCardReader *pUsbCardReader = pThis->pUsbCardReader;
+ if (!pUsbCardReader)
+ {
+ pThis->pICardReaderUp->pfnDisconnect(pThis->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD);
+ }
+ else
+ {
+ pUsbCardReader->Disconnect(pThis, pvUser, u32Disposition);
+ }
+
+ LogFlowFuncLeave();
+}
+
+static DECLCALLBACK(void) drvCardReaderCmdTransmit(PUSBCARDREADER pThis,
+ void *pvUser,
+ PDMICARDREADER_IO_REQUEST *pioSendRequest,
+ uint8_t *pu8SendBuffer,
+ uint32_t cbSendBuffer,
+ uint32_t cbRecvBuffer)
+{
+ LogFlowFunc(("ENTER: pvUser:%p, pioSendRequest:%p, pu8SendBuffer:%p, cbSendBuffer:%d, cbRecvBuffer:%d\n",
+ pvUser, pioSendRequest, pu8SendBuffer, cbSendBuffer, cbRecvBuffer));
+
+ UsbCardReader *pUsbCardReader = pThis->pUsbCardReader;
+ if (!pUsbCardReader)
+ {
+ pThis->pICardReaderUp->pfnTransmit(pThis->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD,
+ /* pioRecvPci */ NULL,
+ /* pu8RecvBuffer */ NULL,
+ /* cbRecvBuffer*/ 0);
+ }
+ else
+ {
+ pUsbCardReader->Transmit(pThis, pvUser, pioSendRequest,
+ pu8SendBuffer, cbSendBuffer, cbRecvBuffer);
+ }
+
+ /* Clean up buffers allocated by driver */
+ RTMemFree(pioSendRequest);
+ RTMemFree(pu8SendBuffer);
+
+ LogFlowFuncLeave();
+}
+
+static DECLCALLBACK(void) drvCardReaderCmdGetAttr(PUSBCARDREADER pThis,
+ void *pvUser,
+ uint32_t u32AttrId,
+ uint32_t cbAttrib)
+{
+ LogFlowFunc(("ENTER: pvUser:%p, u32AttrId:%RX32, cbAttrib:%d\n",
+ pvUser, u32AttrId, cbAttrib));
+
+ UsbCardReader *pUsbCardReader = pThis->pUsbCardReader;
+ if (!pUsbCardReader)
+ {
+ pThis->pICardReaderUp->pfnGetAttrib(pThis->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD,
+ u32AttrId,
+ /* pvAttrib */ NULL,
+ /* cbAttrib */ 0);
+ }
+ else
+ {
+ pUsbCardReader->GetAttrib(pThis, pvUser, u32AttrId, cbAttrib);
+ }
+
+ LogFlowFuncLeave();
+}
+
+static DECLCALLBACK(void) drvCardReaderCmdSetAttr(PUSBCARDREADER pThis,
+ void *pvUser,
+ uint32_t u32AttrId,
+ void *pvAttrib,
+ uint32_t cbAttrib)
+{
+ LogFlowFunc(("ENTER: pvUser:%p, u32AttrId:%RX32, pvAttrib:%p, cbAttrib:%d\n",
+ pvUser, u32AttrId, pvAttrib, cbAttrib));
+
+ UsbCardReader *pUsbCardReader = pThis->pUsbCardReader;
+ if (!pUsbCardReader)
+ {
+ pThis->pICardReaderUp->pfnSetAttrib(pThis->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD,
+ u32AttrId);
+ }
+ else
+ {
+ pUsbCardReader->SetAttrib(pThis, pvUser, u32AttrId, (uint8_t *)pvAttrib, cbAttrib);
+ }
+
+ /* Clean up buffers allocated by driver */
+ RTMemFree(pvAttrib);
+
+ LogFlowFuncLeave();
+}
+
+static DECLCALLBACK(void) drvCardReaderCmdControl(PUSBCARDREADER pThis,
+ void *pvUser,
+ uint32_t u32ControlCode,
+ void *pvInBuffer,
+ uint32_t cbInBuffer,
+ uint32_t cbOutBuffer)
+{
+ LogFlowFunc(("ENTER: pvUser:%p, u32ControlCode:%RX32, pvInBuffer:%p, cbInBuffer:%d, cbOutBuffer:%d\n",
+ pvUser, u32ControlCode, pvInBuffer, cbInBuffer, cbOutBuffer));
+
+ UsbCardReader *pUsbCardReader = pThis->pUsbCardReader;
+ if (!pUsbCardReader)
+ {
+ pThis->pICardReaderUp->pfnControl(pThis->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD,
+ u32ControlCode,
+ /* pvOutBuffer */ NULL,
+ /* cbOutBuffer */ 0);
+ }
+ else
+ {
+ pUsbCardReader->Control(pThis, pvUser, u32ControlCode,
+ (uint8_t *)pvInBuffer, cbInBuffer, cbOutBuffer);
+ }
+
+ /* Clean up buffers allocated by driver */
+ RTMemFree(pvInBuffer);
+
+ LogFlowFuncLeave();
+}
+
+
+/*
+ * PDMICARDREADERDOWN - interface
+ */
+
+static DECLCALLBACK(int) drvCardReaderDownConnect(PPDMICARDREADERDOWN pInterface,
+ void *pvUser,
+ const char *pcszCardReaderName,
+ uint32_t u32ShareMode,
+ uint32_t u32PreferredProtocols)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER: pcszCardReaderName:%s, pvUser:%p, u32ShareMode:%RX32, u32PreferredProtocols:%RX32\n",
+ pcszCardReaderName, pvUser, u32ShareMode, u32PreferredProtocols));
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown);
+ int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdConnect, 5,
+ pThis, pvUser, pcszCardReaderName, u32ShareMode, u32PreferredProtocols);
+ AssertRC(rc);
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) drvCardReaderDownDisconnect(PPDMICARDREADERDOWN pInterface,
+ void *pvUser,
+ uint32_t u32Disposition)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER: pvUser:%p, u32Disposition:%RX32\n",
+ pvUser, u32Disposition));
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown);
+ int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdDisconnect, 3,
+ pThis, pvUser, u32Disposition);
+ AssertRC(rc);
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) drvCardReaderDownEstablishContext(PPDMICARDREADERDOWN pInterface)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER:\n"));
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown);
+ int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdEstablishContext, 1,
+ pThis);
+ AssertRC(rc);
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) drvCardReaderDownReleaseContext(PPDMICARDREADERDOWN pInterface,
+ void *pvUser)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER: pvUser:%p\n",
+ pvUser));
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown);
+ /** @todo Device calls this when the driver already destroyed. */
+ if (pThis->hReqQCardReaderCmd == NIL_RTREQQUEUE)
+ {
+ LogFlowFunc(("LEAVE: device already deleted.\n"));
+ return VINF_SUCCESS;
+ }
+
+ int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdReleaseContext, 2,
+ pThis, pvUser);
+ AssertRC(rc);
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) drvCardReaderDownStatus(PPDMICARDREADERDOWN pInterface,
+ void *pvUser,
+ uint32_t cchReaderName,
+ uint32_t cbAtrLen)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER: pvUser:%p, cchReaderName:%d, cbAtrLen:%d\n",
+ pvUser, cchReaderName, cbAtrLen));
+ NOREF(cchReaderName);
+ NOREF(cbAtrLen);
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown);
+ int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdStatus, 2,
+ pThis, pvUser);
+ AssertRC(rc);
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) drvCardReaderDownGetStatusChange(PPDMICARDREADERDOWN pInterface,
+ void *pvUser,
+ uint32_t u32Timeout,
+ PDMICARDREADER_READERSTATE *paReaderStats,
+ uint32_t cReaderStats)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER: pvUser:%p, u32Timeout:%d, cReaderStats:%d\n",
+ pvUser, u32Timeout, cReaderStats));
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown);
+ int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdStatusChange, 5,
+ pThis, pvUser, u32Timeout, paReaderStats, cReaderStats);
+ AssertRC(rc);
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) drvCardReaderDownBeginTransaction(PPDMICARDREADERDOWN pInterface,
+ void *pvUser)
+{
+ RT_NOREF(pvUser);
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER: pvUser:%p\n",
+ pvUser));
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); NOREF(pThis);
+ int rc = VERR_NOT_SUPPORTED;
+ AssertRC(rc);
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) drvCardReaderDownEndTransaction(PPDMICARDREADERDOWN pInterface,
+ void *pvUser,
+ uint32_t u32Disposition)
+{
+ RT_NOREF(pvUser, u32Disposition);
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER: pvUser:%p, u32Disposition:%RX32\n",
+ pvUser, u32Disposition));
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); NOREF(pThis);
+ int rc = VERR_NOT_SUPPORTED;
+ AssertRC(rc);
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) drvCardReaderDownTransmit(PPDMICARDREADERDOWN pInterface,
+ void *pvUser,
+ const PDMICARDREADER_IO_REQUEST *pioSendRequest,
+ const uint8_t *pu8SendBuffer,
+ uint32_t cbSendBuffer,
+ uint32_t cbRecvBuffer)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER: pvUser:%p, pioSendRequest:%p, pu8SendBuffer:%p, cbSendBuffer:%d, cbRecvBuffer:%d\n",
+ pvUser, pioSendRequest, pu8SendBuffer, cbSendBuffer, cbRecvBuffer));
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown);
+ uint8_t *pu8SendBufferCopy = NULL;
+ if ( pu8SendBuffer
+ && cbSendBuffer)
+ {
+ pu8SendBufferCopy = (uint8_t *)RTMemDup(pu8SendBuffer, cbSendBuffer);
+ if (!pu8SendBufferCopy)
+ {
+ return VERR_NO_MEMORY;
+ }
+ }
+ PDMICARDREADER_IO_REQUEST *pioSendRequestCopy = NULL;
+ if (pioSendRequest)
+ {
+ pioSendRequestCopy = (PDMICARDREADER_IO_REQUEST *)RTMemDup(pioSendRequest, pioSendRequest->cbPciLength);
+ if (!pioSendRequestCopy)
+ {
+ RTMemFree(pu8SendBufferCopy);
+ return VERR_NO_MEMORY;
+ }
+ }
+ int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0,RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdTransmit, 6,
+ pThis, pvUser, pioSendRequestCopy, pu8SendBufferCopy, cbSendBuffer, cbRecvBuffer);
+ AssertRC(rc);
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) drvCardReaderDownGetAttr(PPDMICARDREADERDOWN pInterface,
+ void *pvUser,
+ uint32_t u32AttribId,
+ uint32_t cbAttrib)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER: pvUser:%p, u32AttribId:%RX32, cbAttrib:%d\n",
+ pvUser, u32AttribId, cbAttrib));
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown);
+ int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdGetAttr, 4,
+ pThis, pvUser, u32AttribId, cbAttrib);
+ AssertRC(rc);
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) drvCardReaderDownSetAttr(PPDMICARDREADERDOWN pInterface,
+ void *pvUser,
+ uint32_t u32AttribId,
+ const void *pvAttrib,
+ uint32_t cbAttrib)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER: pvUser:%p, u32AttribId:%RX32, pvAttrib:%p, cbAttrib:%d\n",
+ pvUser, u32AttribId, pvAttrib, cbAttrib));
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown);
+ void *pvAttribCopy = NULL;
+ if ( pvAttrib
+ && cbAttrib)
+ {
+ pvAttribCopy = RTMemDup(pvAttrib, cbAttrib);
+ AssertPtrReturn(pvAttribCopy, VERR_NO_MEMORY);
+ }
+ int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdSetAttr, 5,
+ pThis, pvUser, u32AttribId, pvAttribCopy, cbAttrib);
+ AssertRC(rc);
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) drvCardReaderDownControl(PPDMICARDREADERDOWN pInterface,
+ void *pvUser,
+ uint32_t u32ControlCode,
+ const void *pvInBuffer,
+ uint32_t cbInBuffer,
+ uint32_t cbOutBuffer)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER: pvUser:%p, u32ControlCode:%RX32 pvInBuffer:%p, cbInBuffer:%d, cbOutBuffer:%d\n",
+ pvUser, u32ControlCode, pvInBuffer, cbInBuffer, cbOutBuffer));
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown);
+ void *pvInBufferCopy = NULL;
+ if ( pvInBuffer
+ && cbInBuffer)
+ {
+ pvInBufferCopy = RTMemDup(pvInBuffer, cbInBuffer);
+ AssertReturn(pvInBufferCopy, VERR_NO_MEMORY);
+ }
+ int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdControl, 6,
+ pThis, pvUser, u32ControlCode, pvInBufferCopy, cbInBuffer, cbOutBuffer);
+ AssertRC(rc);
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+
+/*
+ * Cardreader driver thread routines
+ */
+static DECLCALLBACK(int) drvCardReaderThreadCmd(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ int rc = VINF_SUCCESS;
+ PUSBCARDREADER pThis = PDMINS_2_DATA(pDrvIns, PUSBCARDREADER);
+
+ LogFlowFunc(("ENTER: pDrvIns:%d, state %d\n", pDrvIns->iInstance, pThread->enmState));
+
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ {
+ LogFlowFunc(("LEAVE: INITIALIZING: VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+ }
+
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ rc = RTReqQueueProcess(pThis->hReqQCardReaderCmd, RT_INDEFINITE_WAIT);
+
+ AssertMsg(rc == VWRN_STATE_CHANGED,
+ ("Left RTReqProcess and error code is not VWRN_STATE_CHANGED rc=%Rrc\n",
+ rc));
+ }
+
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) drvCardReaderWakeupFunc(PUSBCARDREADER pThis)
+{
+ NOREF(pThis);
+ /* Returning a VINF_* will cause RTReqQueueProcess return. */
+ return VWRN_STATE_CHANGED;
+}
+
+static DECLCALLBACK(int) drvCardReaderThreadCmdWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pThread);
+ LogFlowFunc(("ENTER: pDrvIns:%i\n", pDrvIns->iInstance));
+ PUSBCARDREADER pThis = PDMINS_2_DATA(pDrvIns, PUSBCARDREADER);
+
+ AssertReturn(pThis->hReqQCardReaderCmd != NIL_RTREQQUEUE, VERR_INVALID_STATE);
+
+ PRTREQ pReq;
+ int rc = RTReqQueueCall(pThis->hReqQCardReaderCmd, &pReq, 10000, (PFNRT)drvCardReaderWakeupFunc, 1, pThis);
+ AssertMsgRC(rc, ("Inserting request into queue failed rc=%Rrc\n", rc));
+
+ if (RT_SUCCESS(rc))
+ RTReqRelease(pReq);
+ /** @todo handle VERR_TIMEOUT */
+
+ return rc;
+}
+
+
+/*
+ * USB Card reader driver implementation.
+ */
+
+UsbCardReader::UsbCardReader(Console *console)
+ :
+ mpDrv(NULL),
+ mParent(console),
+ m_pRemote(NULL)
+{
+ LogFlowFunc(("\n"));
+}
+
+UsbCardReader::~UsbCardReader()
+{
+ LogFlowFunc(("mpDrv %p\n", mpDrv));
+ if (mpDrv)
+ {
+ mpDrv->pUsbCardReader = NULL;
+ mpDrv = NULL;
+ }
+}
+
+typedef struct UCRREMOTEREADER
+{
+ bool fAvailable;
+ char szReaderName[1024];
+
+ bool fHandle;
+ VRDESCARDHANDLE hCard;
+} UCRREMOTEREADER;
+
+struct UCRREMOTE
+{
+ UsbCardReader *pUsbCardReader;
+
+ /* The remote identifiers. */
+ uint32_t u32ClientId;
+ uint32_t u32DeviceId;
+
+ bool fContext;
+ VRDESCARDCONTEXT context;
+
+ /* Possible a few readers. Currently only one. */
+ UCRREMOTEREADER reader;
+};
+
+typedef struct UCRREQCTX
+{
+ UCRREMOTE *pRemote;
+ uint32_t u32Function;
+ void *pvUser;
+ union
+ {
+ struct
+ {
+ PDMICARDREADER_READERSTATE *paReaderStats;
+ uint32_t cReaderStats;
+ } GetStatusChange;
+ struct
+ {
+ uint32_t u32AttrId;
+ } GetAttrib;
+ struct
+ {
+ uint32_t u32AttrId;
+ } SetAttrib;
+ struct
+ {
+ uint32_t u32ControlCode;
+ } Control;
+ } u;
+} UCRREQCTX;
+
+int UsbCardReader::vrdeSCardRequest(void *pvUser, uint32_t u32Function, const void *pvData, uint32_t cbData)
+{
+ int rc = mParent->i_consoleVRDPServer()->SCardRequest(pvUser, u32Function, pvData, cbData);
+ LogFlowFunc(("%d %Rrc\n", u32Function, rc));
+ return rc;
+}
+
+int UsbCardReader::VRDENotify(uint32_t u32Id, void *pvData, uint32_t cbData)
+{
+ RT_NOREF(cbData);
+ int rc = VINF_SUCCESS;
+
+ switch (u32Id)
+ {
+ case VRDE_SCARD_NOTIFY_ATTACH:
+ {
+ VRDESCARDNOTIFYATTACH *p = (VRDESCARDNOTIFYATTACH *)pvData;
+ Assert(cbData == sizeof(VRDESCARDNOTIFYATTACH));
+
+ LogFlowFunc(("[%d,%d]\n", p->u32ClientId, p->u32DeviceId));
+
+ /* Add this remote instance, which allow access to card readers attached to the client, to the list.
+ * @todo currently only one device is allowed.
+ */
+ if (m_pRemote)
+ {
+ AssertFailed();
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ UCRREMOTE *pRemote = (UCRREMOTE *)RTMemAllocZ(sizeof(UCRREMOTE));
+ if (pRemote == NULL)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pRemote->pUsbCardReader = this;
+ pRemote->u32ClientId = p->u32ClientId;
+ pRemote->u32DeviceId = p->u32DeviceId;
+
+ m_pRemote = pRemote;
+
+ /* Try to establish a context. */
+ VRDESCARDESTABLISHCONTEXTREQ req;
+ req.u32ClientId = m_pRemote->u32ClientId;
+ req.u32DeviceId = m_pRemote->u32DeviceId;
+
+ rc = vrdeSCardRequest(m_pRemote, VRDE_SCARD_FN_ESTABLISHCONTEXT, &req, sizeof(req));
+
+ LogFlowFunc(("sent ESTABLISHCONTEXT\n"));
+ } break;
+
+ case VRDE_SCARD_NOTIFY_DETACH:
+ {
+ VRDESCARDNOTIFYDETACH *p = (VRDESCARDNOTIFYDETACH *)pvData; NOREF(p);
+ Assert(cbData == sizeof(VRDESCARDNOTIFYDETACH));
+
+ /** @todo Just free. There should be no pending requests, because VRDP cancels them. */
+ RTMemFree(m_pRemote);
+ m_pRemote = NULL;
+ } break;
+
+ default:
+ rc = VERR_INVALID_PARAMETER;
+ AssertFailed();
+ break;
+ }
+
+ return rc;
+}
+
+int UsbCardReader::VRDEResponse(int rcRequest, void *pvUser, uint32_t u32Function, void *pvData, uint32_t cbData)
+{
+ RT_NOREF(cbData);
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("%Rrc %p %u %p %u\n",
+ rcRequest, pvUser, u32Function, pvData, cbData));
+
+ switch (u32Function)
+ {
+ case VRDE_SCARD_FN_ESTABLISHCONTEXT:
+ {
+ Assert(cbData == sizeof(VRDESCARDESTABLISHCONTEXTRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDESTABLISHCONTEXTRSP *pRsp = (VRDESCARDESTABLISHCONTEXTRSP *)pvData;
+ UCRREMOTE *pRemote = (UCRREMOTE *)pvUser;
+
+ /* Check if the context was created. */
+ Assert(!pRemote->fContext);
+ if ( RT_SUCCESS(rcRequest)
+ && pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS)
+ {
+ pRemote->fContext = true;
+ pRemote->context = pRsp->Context;
+
+ LogFlowFunc(("ESTABLISHCONTEXT success\n"));
+
+ /* Now list readers attached to the remote client. */
+ VRDESCARDLISTREADERSREQ req;
+ req.Context = pRemote->context;
+
+ rc = vrdeSCardRequest(pRemote, VRDE_SCARD_FN_LISTREADERS, &req, sizeof(req));
+ }
+ } break;
+
+ case VRDE_SCARD_FN_LISTREADERS:
+ {
+ Assert(cbData == sizeof(VRDESCARDLISTREADERSRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDLISTREADERSRSP *pRsp = (VRDESCARDLISTREADERSRSP *)pvData;
+ UCRREMOTE *pRemote = (UCRREMOTE *)pvUser;
+
+ Assert(pRemote->fContext);
+ if ( RT_SUCCESS(rcRequest)
+ && pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS
+ && pRemote->fContext)
+ {
+ LogFlowFunc(("LISTREADERS: cReaders %d\n",
+ pRsp->cReaders));
+
+ uint32_t i;
+ for (i = 0; i < pRsp->cReaders; i++)
+ {
+ LogFlowFunc(("LISTREADERS: [%d] [%s]\n",
+ i, pRsp->apszNames[i]));
+
+ /** @todo only the first reader is supported. */
+ if (i != 0)
+ {
+ continue;
+ }
+
+ RTStrCopy(pRemote->reader.szReaderName, sizeof(pRemote->reader.szReaderName), pRsp->apszNames[i]);
+ pRemote->reader.fHandle = false;
+ pRemote->reader.fAvailable = true;
+ }
+ }
+ } break;
+
+ case VRDE_SCARD_FN_RELEASECONTEXT:
+ {
+ Assert(cbData == sizeof(VRDESCARDRELEASECONTEXTRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDRELEASECONTEXTRSP *pRsp = (VRDESCARDRELEASECONTEXTRSP *)pvData; NOREF(pRsp);
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; NOREF(pCtx);
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("RELEASECONTEXT completed\n"));
+
+ /* No notification is expected here by the caller. */
+ Assert(!m_pRemote->fContext);
+ } break;
+
+ case VRDE_SCARD_FN_GETSTATUSCHANGE:
+ {
+ Assert(cbData == sizeof(VRDESCARDGETSTATUSCHANGERSP) || RT_FAILURE(rcRequest));
+ VRDESCARDGETSTATUSCHANGERSP *pRsp = (VRDESCARDGETSTATUSCHANGERSP *)pvData;
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser;
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("GETSTATUSCHANGE\n"));
+
+ uint32_t rcCard;
+ if (RT_FAILURE(rcRequest))
+ {
+ rcCard = VRDE_SCARD_E_NO_SMARTCARD;
+ }
+ else
+ {
+ rcCard = pRsp->u32ReturnCode;
+
+ if (pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS)
+ {
+ uint32_t i;
+ for (i = 0; i < pRsp->cReaders; i++)
+ {
+ LogFlowFunc(("GETSTATUSCHANGE: [%d] %RX32\n",
+ i, pRsp->aReaderStates[i].u32EventState));
+
+ /** @todo only the first reader is supported. */
+ if (i != 0)
+ {
+ continue;
+ }
+
+ if (i >= pCtx->u.GetStatusChange.cReaderStats)
+ {
+ continue;
+ }
+
+ pCtx->u.GetStatusChange.paReaderStats[i].u32EventState = pRsp->aReaderStates[i].u32EventState;
+ pCtx->u.GetStatusChange.paReaderStats[i].cbAtr = pRsp->aReaderStates[i].u32AtrLength > 36?
+ 36:
+ pRsp->aReaderStates[i].u32AtrLength;
+ memcpy(pCtx->u.GetStatusChange.paReaderStats[i].au8Atr,
+ pRsp->aReaderStates[i].au8Atr,
+ pCtx->u.GetStatusChange.paReaderStats[i].cbAtr);
+ }
+ }
+ }
+
+ mpDrv->pICardReaderUp->pfnSetStatusChange(mpDrv->pICardReaderUp,
+ pCtx->pvUser,
+ rcCard,
+ pCtx->u.GetStatusChange.paReaderStats,
+ pCtx->u.GetStatusChange.cReaderStats);
+
+ RTMemFree(pCtx);
+ } break;
+
+ case VRDE_SCARD_FN_CANCEL:
+ {
+ Assert(cbData == sizeof(VRDESCARDCANCELRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDCANCELRSP *pRsp = (VRDESCARDCANCELRSP *)pvData; NOREF(pRsp);
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; NOREF(pCtx);
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("CANCEL\n"));
+ } break;
+
+ case VRDE_SCARD_FN_CONNECT:
+ {
+ Assert(cbData == sizeof(VRDESCARDCONNECTRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDCONNECTRSP *pRsp = (VRDESCARDCONNECTRSP *)pvData;
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser;
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("CONNECT\n"));
+
+ uint32_t u32ActiveProtocol = 0;
+ uint32_t rcCard;
+
+ if (RT_FAILURE(rcRequest))
+ {
+ rcCard = VRDE_SCARD_E_NO_SMARTCARD;
+ }
+ else
+ {
+ rcCard = pRsp->u32ReturnCode;
+
+ if (pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS)
+ {
+ u32ActiveProtocol = pRsp->u32ActiveProtocol;
+
+ Assert(!m_pRemote->reader.fHandle);
+ m_pRemote->reader.hCard = pRsp->hCard;
+ m_pRemote->reader.fHandle = true;
+ }
+ }
+
+ mpDrv->pICardReaderUp->pfnConnect(mpDrv->pICardReaderUp,
+ pCtx->pvUser,
+ rcCard,
+ u32ActiveProtocol);
+
+ RTMemFree(pCtx);
+ } break;
+
+ case VRDE_SCARD_FN_RECONNECT:
+ {
+ Assert(cbData == sizeof(VRDESCARDRECONNECTRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDRECONNECTRSP *pRsp = (VRDESCARDRECONNECTRSP *)pvData; NOREF(pRsp);
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; NOREF(pCtx);
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("RECONNECT\n"));
+ } break;
+
+ case VRDE_SCARD_FN_DISCONNECT:
+ {
+ Assert(cbData == sizeof(VRDESCARDDISCONNECTRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDDISCONNECTRSP *pRsp = (VRDESCARDDISCONNECTRSP *)pvData;
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser;
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("DISCONNECT\n"));
+
+ Assert(!pCtx->pRemote->reader.fHandle);
+
+ uint32_t rcCard;
+
+ if (RT_FAILURE(rcRequest))
+ {
+ rcCard = VRDE_SCARD_E_NO_SMARTCARD;
+ }
+ else
+ {
+ rcCard = pRsp->u32ReturnCode;
+ }
+
+ mpDrv->pICardReaderUp->pfnDisconnect(mpDrv->pICardReaderUp,
+ pCtx->pvUser,
+ rcCard);
+
+ RTMemFree(pCtx);
+ } break;
+
+ case VRDE_SCARD_FN_BEGINTRANSACTION:
+ {
+ Assert(cbData == sizeof(VRDESCARDBEGINTRANSACTIONRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDBEGINTRANSACTIONRSP *pRsp = (VRDESCARDBEGINTRANSACTIONRSP *)pvData; NOREF(pRsp);
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; NOREF(pCtx);
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("BEGINTRANSACTION\n"));
+ } break;
+
+ case VRDE_SCARD_FN_ENDTRANSACTION:
+ {
+ Assert(cbData == sizeof(VRDESCARDENDTRANSACTIONRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDENDTRANSACTIONRSP *pRsp = (VRDESCARDENDTRANSACTIONRSP *)pvData; NOREF(pRsp);
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; NOREF(pCtx);
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("ENDTRANSACTION\n"));
+ } break;
+
+ case VRDE_SCARD_FN_STATE:
+ {
+ Assert(cbData == sizeof(VRDESCARDSTATERSP) || RT_FAILURE(rcRequest));
+ VRDESCARDSTATERSP *pRsp = (VRDESCARDSTATERSP *)pvData; NOREF(pRsp);
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; NOREF(pCtx);
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("STATE\n"));
+ } break;
+
+ case VRDE_SCARD_FN_STATUS:
+ {
+ Assert(cbData == sizeof(VRDESCARDSTATUSRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDSTATUSRSP *pRsp = (VRDESCARDSTATUSRSP *)pvData;
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser;
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("STATUS\n"));
+
+ char *pszReaderName = NULL;
+ uint32_t cchReaderName = 0;
+ uint32_t u32CardState = 0;
+ uint32_t u32Protocol = 0;
+ uint32_t u32AtrLength = 0;
+ uint8_t *pbAtr = NULL;
+
+ uint32_t rcCard;
+
+ if (RT_FAILURE(rcRequest))
+ {
+ rcCard = VRDE_SCARD_E_NO_SMARTCARD;
+ }
+ else
+ {
+ rcCard = pRsp->u32ReturnCode;
+
+ if (pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS)
+ {
+ pszReaderName = pRsp->szReader;
+ cchReaderName = (uint32_t)strlen(pRsp->szReader) + 1;
+ u32CardState = pRsp->u32State;
+ u32Protocol = pRsp->u32Protocol;
+ u32AtrLength = pRsp->u32AtrLength;
+ pbAtr = &pRsp->au8Atr[0];
+ }
+ }
+
+ mpDrv->pICardReaderUp->pfnStatus(mpDrv->pICardReaderUp,
+ pCtx->pvUser,
+ rcCard,
+ pszReaderName,
+ cchReaderName,
+ u32CardState,
+ u32Protocol,
+ pbAtr,
+ u32AtrLength);
+
+ RTMemFree(pCtx);
+ } break;
+
+ case VRDE_SCARD_FN_TRANSMIT:
+ {
+ Assert(cbData == sizeof(VRDESCARDTRANSMITRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDTRANSMITRSP *pRsp = (VRDESCARDTRANSMITRSP *)pvData;
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser;
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("TRANSMIT\n"));
+
+ PDMICARDREADER_IO_REQUEST *pioRecvPci = NULL;
+ uint8_t *pu8RecvBuffer = NULL;
+ uint32_t cbRecvBuffer = 0;
+
+ uint32_t rcCard;
+
+ if (RT_FAILURE(rcRequest))
+ {
+ rcCard = VRDE_SCARD_E_NO_SMARTCARD;
+ }
+ else
+ {
+ rcCard = pRsp->u32ReturnCode;
+
+ if (pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS)
+ {
+ pu8RecvBuffer = pRsp->pu8RecvBuffer;
+ cbRecvBuffer = pRsp->u32RecvLength;
+ /** @todo pioRecvPci */
+ }
+ }
+
+ mpDrv->pICardReaderUp->pfnTransmit(mpDrv->pICardReaderUp,
+ pCtx->pvUser,
+ rcCard,
+ pioRecvPci,
+ pu8RecvBuffer,
+ cbRecvBuffer);
+
+ RTMemFree(pioRecvPci);
+
+ RTMemFree(pCtx);
+ } break;
+
+ case VRDE_SCARD_FN_CONTROL:
+ {
+ Assert(cbData == sizeof(VRDESCARDCONTROLRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDCONTROLRSP *pRsp = (VRDESCARDCONTROLRSP *)pvData;
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser;
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("CONTROL\n"));
+
+ uint8_t *pu8OutBuffer = NULL;
+ uint32_t cbOutBuffer = 0;
+
+ uint32_t rcCard;
+
+ if (RT_FAILURE(rcRequest))
+ {
+ rcCard = VRDE_SCARD_E_NO_SMARTCARD;
+ }
+ else
+ {
+ rcCard = pRsp->u32ReturnCode;
+
+ if (pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS)
+ {
+ pu8OutBuffer = pRsp->pu8OutBuffer;
+ cbOutBuffer = pRsp->u32OutBufferSize;
+ }
+ }
+
+ mpDrv->pICardReaderUp->pfnControl(mpDrv->pICardReaderUp,
+ pCtx->pvUser,
+ rcCard,
+ pCtx->u.Control.u32ControlCode,
+ pu8OutBuffer,
+ cbOutBuffer);
+
+ RTMemFree(pCtx);
+ } break;
+
+ case VRDE_SCARD_FN_GETATTRIB:
+ {
+ Assert(cbData == sizeof(VRDESCARDGETATTRIBRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDGETATTRIBRSP *pRsp = (VRDESCARDGETATTRIBRSP *)pvData;
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser;
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("GETATTRIB\n"));
+
+ uint8_t *pu8Attrib = NULL;
+ uint32_t cbAttrib = 0;
+
+ uint32_t rcCard;
+
+ if (RT_FAILURE(rcRequest))
+ {
+ rcCard = VRDE_SCARD_E_NO_SMARTCARD;
+ }
+ else
+ {
+ rcCard = pRsp->u32ReturnCode;
+
+ if (pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS)
+ {
+ pu8Attrib = pRsp->pu8Attr;
+ cbAttrib = pRsp->u32AttrLength;
+ }
+ }
+
+ mpDrv->pICardReaderUp->pfnGetAttrib(mpDrv->pICardReaderUp,
+ pCtx->pvUser,
+ rcCard,
+ pCtx->u.GetAttrib.u32AttrId,
+ pu8Attrib,
+ cbAttrib);
+
+ RTMemFree(pCtx);
+ } break;
+
+ case VRDE_SCARD_FN_SETATTRIB:
+ {
+ Assert(cbData == sizeof(VRDESCARDSETATTRIBRSP) || RT_FAILURE(rcRequest));
+ VRDESCARDSETATTRIBRSP *pRsp = (VRDESCARDSETATTRIBRSP *)pvData;
+ UCRREQCTX *pCtx = (UCRREQCTX *)pvUser;
+
+ Assert(pCtx->u32Function == u32Function);
+
+ LogFlowFunc(("SETATTRIB\n"));
+
+ uint32_t rcCard;
+
+ if (RT_FAILURE(rcRequest))
+ {
+ rcCard = VRDE_SCARD_E_NO_SMARTCARD;
+ }
+ else
+ {
+ rcCard = pRsp->u32ReturnCode;
+ }
+
+ mpDrv->pICardReaderUp->pfnSetAttrib(mpDrv->pICardReaderUp,
+ pCtx->pvUser,
+ rcCard,
+ pCtx->u.SetAttrib.u32AttrId);
+
+ RTMemFree(pCtx);
+ } break;
+
+ default:
+ AssertFailed();
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ return rc;
+}
+
+int UsbCardReader::EstablishContext(struct USBCARDREADER *pDrv)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ /* The context here is a not a real device context.
+ * The device can be detached at the moment, for example the VRDP client did not connect yet.
+ */
+
+ return mpDrv->pICardReaderUp->pfnEstablishContext(mpDrv->pICardReaderUp,
+ VRDE_SCARD_S_SUCCESS);
+}
+
+int UsbCardReader::ReleaseContext(struct USBCARDREADER *pDrv)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int rc = VINF_SUCCESS;
+
+ if ( !m_pRemote
+ || !m_pRemote->fContext)
+ {
+ /* Do nothing. */
+ }
+ else
+ {
+ UCRREQCTX *pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX));
+ if (!pCtx)
+ {
+ /* Do nothing. */
+ }
+ else
+ {
+ pCtx->pRemote = m_pRemote;
+ pCtx->u32Function = VRDE_SCARD_FN_RELEASECONTEXT;
+ pCtx->pvUser = NULL;
+
+ VRDESCARDRELEASECONTEXTREQ req;
+ req.Context = m_pRemote->context;
+
+ rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_RELEASECONTEXT, &req, sizeof(req));
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pCtx);
+ }
+ else
+ {
+ m_pRemote->fContext = false;
+ }
+ }
+ }
+
+ return rc;
+}
+
+int UsbCardReader::GetStatusChange(struct USBCARDREADER *pDrv,
+ void *pvUser,
+ uint32_t u32Timeout,
+ PDMICARDREADER_READERSTATE *paReaderStats,
+ uint32_t cReaderStats)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int rc = VINF_SUCCESS;
+
+ if ( !m_pRemote
+ || !m_pRemote->fContext
+ || !m_pRemote->reader.fAvailable)
+ {
+ rc = mpDrv->pICardReaderUp->pfnSetStatusChange(mpDrv->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD,
+ paReaderStats,
+ cReaderStats);
+ }
+ else
+ {
+ UCRREQCTX *pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX));
+ if (!pCtx)
+ {
+ rc = mpDrv->pICardReaderUp->pfnSetStatusChange(mpDrv->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_MEMORY,
+ paReaderStats,
+ cReaderStats);
+ }
+ else
+ {
+ pCtx->pRemote = m_pRemote;
+ pCtx->u32Function = VRDE_SCARD_FN_GETSTATUSCHANGE;
+ pCtx->pvUser = pvUser;
+ pCtx->u.GetStatusChange.paReaderStats = paReaderStats;
+ pCtx->u.GetStatusChange.cReaderStats = cReaderStats;
+
+ VRDESCARDGETSTATUSCHANGEREQ req;
+ req.Context = m_pRemote->context;
+ req.u32Timeout = u32Timeout;
+ req.cReaders = 1;
+ req.aReaderStates[0].pszReader = &m_pRemote->reader.szReaderName[0];
+ req.aReaderStates[0].u32CurrentState = paReaderStats[0].u32CurrentState;
+
+ rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_GETSTATUSCHANGE, &req, sizeof(req));
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pCtx);
+ }
+ }
+ }
+
+ return rc;
+}
+
+int UsbCardReader::Connect(struct USBCARDREADER *pDrv,
+ void *pvUser,
+ const char *pszReaderName,
+ uint32_t u32ShareMode,
+ uint32_t u32PreferredProtocols)
+{
+ RT_NOREF(pszReaderName);
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int rc = VINF_SUCCESS;
+
+ if ( !m_pRemote
+ || !m_pRemote->fContext
+ || !m_pRemote->reader.fAvailable)
+ {
+ rc = mpDrv->pICardReaderUp->pfnConnect(mpDrv->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD,
+ VRDE_SCARD_PROTOCOL_T0);
+ }
+ else
+ {
+ UCRREQCTX *pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX));
+ if (!pCtx)
+ {
+ rc = mpDrv->pICardReaderUp->pfnConnect(mpDrv->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_MEMORY,
+ VRDE_SCARD_PROTOCOL_T0);
+ }
+ else
+ {
+ pCtx->pRemote = m_pRemote;
+ pCtx->u32Function = VRDE_SCARD_FN_CONNECT;
+ pCtx->pvUser = pvUser;
+
+ VRDESCARDCONNECTREQ req;
+ req.Context = m_pRemote->context;
+ req.pszReader = &m_pRemote->reader.szReaderName[0];
+ req.u32ShareMode = u32ShareMode;
+ req.u32PreferredProtocols = u32PreferredProtocols;
+
+ rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_CONNECT, &req, sizeof(req));
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pCtx);
+ }
+ }
+ }
+
+ return rc;
+}
+
+int UsbCardReader::Disconnect(struct USBCARDREADER *pDrv,
+ void *pvUser,
+ uint32_t u32Mode)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int rc = VINF_SUCCESS;
+
+ if ( !m_pRemote
+ || !m_pRemote->fContext
+ || !m_pRemote->reader.fAvailable
+ || !m_pRemote->reader.fHandle)
+ {
+ rc = mpDrv->pICardReaderUp->pfnDisconnect(mpDrv->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD);
+ }
+ else
+ {
+ UCRREQCTX *pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX));
+ if (!pCtx)
+ {
+ rc = mpDrv->pICardReaderUp->pfnDisconnect(mpDrv->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_MEMORY);
+ }
+ else
+ {
+ pCtx->pRemote = m_pRemote;
+ pCtx->u32Function = VRDE_SCARD_FN_DISCONNECT;
+ pCtx->pvUser = pvUser;
+
+ VRDESCARDDISCONNECTREQ req;
+ req.hCard = m_pRemote->reader.hCard;
+ req.u32Disposition = u32Mode;
+
+ rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_DISCONNECT, &req, sizeof(req));
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pCtx);
+ }
+ else
+ {
+ m_pRemote->reader.fHandle = false;
+ }
+ }
+ }
+
+ return rc;
+}
+
+int UsbCardReader::Status(struct USBCARDREADER *pDrv,
+ void *pvUser)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int rc = VINF_SUCCESS;
+
+ if ( !m_pRemote
+ || !m_pRemote->fContext
+ || !m_pRemote->reader.fAvailable
+ || !m_pRemote->reader.fHandle)
+ {
+ rc = mpDrv->pICardReaderUp->pfnStatus(mpDrv->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD,
+ /* pszReaderName */ NULL,
+ /* cchReaderName */ 0,
+ /* u32CardState */ 0,
+ /* u32Protocol */ 0,
+ /* pu8Atr */ 0,
+ /* cbAtr */ 0);
+ }
+ else
+ {
+ UCRREQCTX *pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX));
+ if (!pCtx)
+ {
+ rc = mpDrv->pICardReaderUp->pfnStatus(mpDrv->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_MEMORY,
+ /* pszReaderName */ NULL,
+ /* cchReaderName */ 0,
+ /* u32CardState */ 0,
+ /* u32Protocol */ 0,
+ /* pu8Atr */ 0,
+ /* cbAtr */ 0);
+ }
+ else
+ {
+ pCtx->pRemote = m_pRemote;
+ pCtx->u32Function = VRDE_SCARD_FN_STATUS;
+ pCtx->pvUser = pvUser;
+
+ VRDESCARDSTATUSREQ req;
+ req.hCard = m_pRemote->reader.hCard;
+
+ rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_STATUS, &req, sizeof(req));
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pCtx);
+ }
+ }
+ }
+
+ return rc;
+}
+
+int UsbCardReader::Transmit(struct USBCARDREADER *pDrv,
+ void *pvUser,
+ PDMICARDREADER_IO_REQUEST *pioSendRequest,
+ uint8_t *pu8SendBuffer,
+ uint32_t cbSendBuffer,
+ uint32_t cbRecvBuffer)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int rc = VINF_SUCCESS;
+
+ UCRREQCTX *pCtx = NULL;
+ uint32_t rcSCard = VRDE_SCARD_S_SUCCESS;
+
+ if ( !m_pRemote
+ || !m_pRemote->fContext
+ || !m_pRemote->reader.fAvailable
+ || !m_pRemote->reader.fHandle)
+ {
+ rcSCard = VRDE_SCARD_E_NO_SMARTCARD;
+ }
+
+ if (rcSCard == VRDE_SCARD_S_SUCCESS)
+ {
+ if ( !pioSendRequest
+ || ( pioSendRequest->cbPciLength < 2 * sizeof(uint32_t)
+ || pioSendRequest->cbPciLength > 2 * sizeof(uint32_t) + VRDE_SCARD_MAX_PCI_DATA)
+ )
+ {
+ AssertFailed();
+ rcSCard = VRDE_SCARD_E_INVALID_PARAMETER;
+ }
+ }
+
+ if (rcSCard == VRDE_SCARD_S_SUCCESS)
+ {
+ pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX));
+ if (!pCtx)
+ {
+ rcSCard = VRDE_SCARD_E_NO_MEMORY;
+ }
+ }
+
+ if (rcSCard != VRDE_SCARD_S_SUCCESS)
+ {
+ Assert(pCtx == NULL);
+
+ rc = pDrv->pICardReaderUp->pfnTransmit(pDrv->pICardReaderUp,
+ pvUser,
+ rcSCard,
+ /* pioRecvPci */ NULL,
+ /* pu8RecvBuffer */ NULL,
+ /* cbRecvBuffer*/ 0);
+ }
+ else
+ {
+ pCtx->pRemote = m_pRemote;
+ pCtx->u32Function = VRDE_SCARD_FN_TRANSMIT;
+ pCtx->pvUser = pvUser;
+
+ VRDESCARDTRANSMITREQ req;
+ req.hCard = m_pRemote->reader.hCard;
+
+ req.ioSendPci.u32Protocol = pioSendRequest->u32Protocol;
+ req.ioSendPci.u32PciLength = pioSendRequest->cbPciLength < 2 * sizeof(uint32_t)?
+ (uint32_t)(2 * sizeof(uint32_t)):
+ pioSendRequest->cbPciLength;
+ Assert(pioSendRequest->cbPciLength <= VRDE_SCARD_MAX_PCI_DATA + 2 * sizeof(uint32_t));
+ memcpy(req.ioSendPci.au8PciData,
+ (uint8_t *)pioSendRequest + 2 * sizeof(uint32_t),
+ req.ioSendPci.u32PciLength - 2 * sizeof(uint32_t));
+
+ req.u32SendLength = cbSendBuffer;
+ req.pu8SendBuffer = pu8SendBuffer;
+ req.u32RecvLength = cbRecvBuffer;
+
+ rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_TRANSMIT, &req, sizeof(req));
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pCtx);
+ }
+ }
+
+ return rc;
+}
+
+int UsbCardReader::Control(struct USBCARDREADER *pDrv,
+ void *pvUser,
+ uint32_t u32ControlCode,
+ uint8_t *pu8InBuffer,
+ uint32_t cbInBuffer,
+ uint32_t cbOutBuffer)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int rc = VINF_SUCCESS;
+
+ UCRREQCTX *pCtx = NULL;
+ uint32_t rcSCard = VRDE_SCARD_S_SUCCESS;
+
+ if ( !m_pRemote
+ || !m_pRemote->fContext
+ || !m_pRemote->reader.fAvailable
+ || !m_pRemote->reader.fHandle)
+ {
+ rcSCard = VRDE_SCARD_E_NO_SMARTCARD;
+ }
+
+ if (rcSCard == VRDE_SCARD_S_SUCCESS)
+ {
+ if ( cbInBuffer > _128K
+ || cbOutBuffer > _128K)
+ {
+ AssertFailed();
+ rcSCard = VRDE_SCARD_E_INVALID_PARAMETER;
+ }
+ }
+
+ if (rcSCard == VRDE_SCARD_S_SUCCESS)
+ {
+ pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX));
+ if (!pCtx)
+ {
+ rcSCard = VRDE_SCARD_E_NO_MEMORY;
+ }
+ }
+
+ if (rcSCard != VRDE_SCARD_S_SUCCESS)
+ {
+ Assert(pCtx == NULL);
+
+ rc = pDrv->pICardReaderUp->pfnControl(pDrv->pICardReaderUp,
+ pvUser,
+ rcSCard,
+ u32ControlCode,
+ /* pvOutBuffer */ NULL,
+ /* cbOutBuffer*/ 0);
+ }
+ else
+ {
+ pCtx->pRemote = m_pRemote;
+ pCtx->u32Function = VRDE_SCARD_FN_CONTROL;
+ pCtx->pvUser = pvUser;
+ pCtx->u.Control.u32ControlCode = u32ControlCode;
+
+ VRDESCARDCONTROLREQ req;
+ req.hCard = m_pRemote->reader.hCard;
+ req.u32ControlCode = u32ControlCode;
+ req.u32InBufferSize = cbInBuffer;
+ req.pu8InBuffer = pu8InBuffer;
+ req.u32OutBufferSize = cbOutBuffer;
+
+ rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_CONTROL, &req, sizeof(req));
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pCtx);
+ }
+ }
+
+ return rc;
+}
+
+int UsbCardReader::GetAttrib(struct USBCARDREADER *pDrv,
+ void *pvUser,
+ uint32_t u32AttrId,
+ uint32_t cbAttrib)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int rc = VINF_SUCCESS;
+
+ UCRREQCTX *pCtx = NULL;
+ uint32_t rcSCard = VRDE_SCARD_S_SUCCESS;
+
+ if ( !m_pRemote
+ || !m_pRemote->fContext
+ || !m_pRemote->reader.fAvailable
+ || !m_pRemote->reader.fHandle)
+ {
+ rcSCard = VRDE_SCARD_E_NO_SMARTCARD;
+ }
+
+ if (rcSCard == VRDE_SCARD_S_SUCCESS)
+ {
+ if (cbAttrib > _128K)
+ {
+ AssertFailed();
+ rcSCard = VRDE_SCARD_E_INVALID_PARAMETER;
+ }
+ }
+
+ if (rcSCard == VRDE_SCARD_S_SUCCESS)
+ {
+ pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX));
+ if (!pCtx)
+ {
+ rcSCard = VRDE_SCARD_E_NO_MEMORY;
+ }
+ }
+
+ if (rcSCard != VRDE_SCARD_S_SUCCESS)
+ {
+ Assert(pCtx == NULL);
+
+ pDrv->pICardReaderUp->pfnGetAttrib(pDrv->pICardReaderUp,
+ pvUser,
+ rcSCard,
+ u32AttrId,
+ /* pvAttrib */ NULL,
+ /* cbAttrib */ 0);
+ }
+ else
+ {
+ pCtx->pRemote = m_pRemote;
+ pCtx->u32Function = VRDE_SCARD_FN_GETATTRIB;
+ pCtx->pvUser = pvUser;
+ pCtx->u.GetAttrib.u32AttrId = u32AttrId;
+
+ VRDESCARDGETATTRIBREQ req;
+ req.hCard = m_pRemote->reader.hCard;
+ req.u32AttrId = u32AttrId;
+ req.u32AttrLen = cbAttrib;
+
+ rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_GETATTRIB, &req, sizeof(req));
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pCtx);
+ }
+ }
+
+ return rc;
+}
+
+int UsbCardReader::SetAttrib(struct USBCARDREADER *pDrv,
+ void *pvUser,
+ uint32_t u32AttrId,
+ uint8_t *pu8Attrib,
+ uint32_t cbAttrib)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int rc = VINF_SUCCESS;
+
+ UCRREQCTX *pCtx = NULL;
+ uint32_t rcSCard = VRDE_SCARD_S_SUCCESS;
+
+ if ( !m_pRemote
+ || !m_pRemote->fContext
+ || !m_pRemote->reader.fAvailable
+ || !m_pRemote->reader.fHandle)
+ {
+ rcSCard = VRDE_SCARD_E_NO_SMARTCARD;
+ }
+
+ if (rcSCard == VRDE_SCARD_S_SUCCESS)
+ {
+ if (cbAttrib > _128K)
+ {
+ AssertFailed();
+ rcSCard = VRDE_SCARD_E_INVALID_PARAMETER;
+ }
+ }
+
+ if (rcSCard == VRDE_SCARD_S_SUCCESS)
+ {
+ pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX));
+ if (!pCtx)
+ {
+ rcSCard = VRDE_SCARD_E_NO_MEMORY;
+ }
+ }
+
+ if (rcSCard != VRDE_SCARD_S_SUCCESS)
+ {
+ Assert(pCtx == NULL);
+
+ pDrv->pICardReaderUp->pfnSetAttrib(pDrv->pICardReaderUp,
+ pvUser,
+ rcSCard,
+ u32AttrId);
+ }
+ else
+ {
+ pCtx->pRemote = m_pRemote;
+ pCtx->u32Function = VRDE_SCARD_FN_SETATTRIB;
+ pCtx->pvUser = pvUser;
+ pCtx->u.SetAttrib.u32AttrId = u32AttrId;
+
+ VRDESCARDSETATTRIBREQ req;
+ req.hCard = m_pRemote->reader.hCard;
+ req.u32AttrId = u32AttrId;
+ req.u32AttrLen = cbAttrib;
+ req.pu8Attr = pu8Attrib;
+
+ rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_SETATTRIB, &req, sizeof(req));
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pCtx);
+ }
+ }
+
+ return rc;
+}
+
+
+/*
+ * PDMDRVINS
+ */
+
+/* static */ DECLCALLBACK(void *) UsbCardReader::drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ LogFlowFunc(("pInterface:%p, pszIID:%s\n", pInterface, pszIID));
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PUSBCARDREADER pThis = PDMINS_2_DATA(pDrvIns, PUSBCARDREADER);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMICARDREADERDOWN, &pThis->ICardReaderDown);
+ return NULL;
+}
+
+/* static */ DECLCALLBACK(void) UsbCardReader::drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ LogFlowFunc(("iInstance/%d\n",pDrvIns->iInstance));
+ PUSBCARDREADER pThis = PDMINS_2_DATA(pDrvIns, PUSBCARDREADER);
+
+ /** @todo The driver is destroyed before the device.
+ * So device calls ReleaseContext when there is no more driver.
+ * Notify the device here so it can do cleanup or
+ * do a cleanup now in the driver.
+ */
+ if (pThis->hReqQCardReaderCmd != NIL_RTREQQUEUE)
+ {
+ int rc = RTReqQueueDestroy(pThis->hReqQCardReaderCmd);
+ AssertRC(rc);
+ pThis->hReqQCardReaderCmd = NIL_RTREQQUEUE;
+ }
+
+ pThis->pUsbCardReader->mpDrv = NULL;
+ pThis->pUsbCardReader = NULL;
+ LogFlowFuncLeave();
+}
+
+/* static */ DECLCALLBACK(int) UsbCardReader::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags, pCfg);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ LogFlowFunc(("iInstance/%d, pCfg:%p, fFlags:%x\n", pDrvIns->iInstance, pCfg, fFlags));
+ PUSBCARDREADER pThis = PDMINS_2_DATA(pDrvIns, PUSBCARDREADER);
+
+ pThis->hReqQCardReaderCmd = NIL_RTREQQUEUE;
+
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", "");
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ com::Guid uuid(USBCARDREADER_OID);
+ pThis->pUsbCardReader = (UsbCardReader *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw());
+ AssertMsgReturn(RT_VALID_PTR(pThis->pUsbCardReader), ("Configuration error: No/bad USB card reader object value!\n"), VERR_NOT_FOUND);
+
+ pThis->pUsbCardReader->mpDrv = pThis;
+ pThis->pDrvIns = pDrvIns;
+
+ pDrvIns->IBase.pfnQueryInterface = UsbCardReader::drvQueryInterface;
+
+ pThis->ICardReaderDown.pfnEstablishContext = drvCardReaderDownEstablishContext;
+ pThis->ICardReaderDown.pfnReleaseContext = drvCardReaderDownReleaseContext;
+ pThis->ICardReaderDown.pfnConnect = drvCardReaderDownConnect;
+ pThis->ICardReaderDown.pfnDisconnect = drvCardReaderDownDisconnect;
+ pThis->ICardReaderDown.pfnStatus = drvCardReaderDownStatus;
+ pThis->ICardReaderDown.pfnGetStatusChange = drvCardReaderDownGetStatusChange;
+ pThis->ICardReaderDown.pfnBeginTransaction = drvCardReaderDownBeginTransaction;
+ pThis->ICardReaderDown.pfnEndTransaction = drvCardReaderDownEndTransaction;
+ pThis->ICardReaderDown.pfnTransmit = drvCardReaderDownTransmit;
+ pThis->ICardReaderDown.pfnGetAttr = drvCardReaderDownGetAttr;
+ pThis->ICardReaderDown.pfnSetAttr = drvCardReaderDownSetAttr;
+ pThis->ICardReaderDown.pfnControl = drvCardReaderDownControl;
+
+ pThis->pICardReaderUp = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMICARDREADERUP);
+ AssertReturn(pThis->pICardReaderUp, VERR_PDM_MISSING_INTERFACE);
+
+ /* Command Thread Synchronization primitives */
+ int rc = RTReqQueueCreate(&pThis->hReqQCardReaderCmd);
+ AssertLogRelRCReturn(rc, rc);
+
+ rc = PDMDrvHlpThreadCreate(pDrvIns,
+ &pThis->pThrCardReaderCmd,
+ pThis,
+ drvCardReaderThreadCmd /* worker routine */,
+ drvCardReaderThreadCmdWakeup /* wakeup routine */,
+ 128 * _1K, RTTHREADTYPE_IO, "UCRCMD");
+ if (RT_FAILURE(rc))
+ {
+ RTReqQueueDestroy(pThis->hReqQCardReaderCmd);
+ pThis->hReqQCardReaderCmd = NIL_RTREQQUEUE;
+ }
+
+ LogFlowFunc(("LEAVE: %Rrc\n", rc));
+ return rc;
+}
+
+/* static */ const PDMDRVREG UsbCardReader::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName[32] */
+ "UsbCardReader",
+ /* szRCMod[32] */
+ "",
+ /* szR0Mod[32] */
+ "",
+ /* pszDescription */
+ "Main Driver communicating with VRDE",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass */
+ PDM_DRVREG_CLASS_USB,
+ /* cMaxInstances */
+ 1,
+ /* cbInstance */
+ sizeof(USBCARDREADER),
+ /* pfnConstruct */
+ UsbCardReader::drvConstruct,
+ /* pfnDestruct */
+ UsbCardReader::drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DRVREG_VERSION
+};
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/UsbWebcamInterface.cpp b/src/VBox/Main/src-client/UsbWebcamInterface.cpp
new file mode 100644
index 00000000..8fe5427a
--- /dev/null
+++ b/src/VBox/Main/src-client/UsbWebcamInterface.cpp
@@ -0,0 +1,492 @@
+/* $Id: UsbWebcamInterface.cpp $ */
+/** @file
+ * UsbWebcamInterface - Driver Interface for USB Webcam emulation.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#define LOG_GROUP LOG_GROUP_USB_WEBCAM
+#include "LoggingNew.h"
+
+#include "UsbWebcamInterface.h"
+#include "ConsoleImpl.h"
+#include "ConsoleVRDPServer.h"
+#include "EmulatedUSBImpl.h"
+
+#include <VBox/vmm/pdmwebcaminfs.h>
+#include <VBox/err.h>
+
+
+typedef struct EMWEBCAMREMOTE
+{
+ EmWebcam *pEmWebcam;
+
+ VRDEVIDEOINDEVICEHANDLE deviceHandle; /* The remote identifier. */
+
+ /* Received from the remote client. */
+ uint32_t u32Version; /* VRDE_VIDEOIN_NEGOTIATE_VERSION */
+ uint32_t fu32Capabilities; /* VRDE_VIDEOIN_NEGOTIATE_CAP_* */
+ VRDEVIDEOINDEVICEDESC *pDeviceDesc;
+ uint32_t cbDeviceDesc;
+
+ /* The device identifier for the PDM device.*/
+ uint64_t u64DeviceId;
+} EMWEBCAMREMOTE;
+
+typedef struct EMWEBCAMDRV
+{
+ EMWEBCAMREMOTE *pRemote;
+ PPDMIWEBCAMDEV pIWebcamUp;
+ PDMIWEBCAMDRV IWebcamDrv;
+} EMWEBCAMDRV, *PEMWEBCAMDRV;
+
+typedef struct EMWEBCAMREQCTX
+{
+ EMWEBCAMREMOTE *pRemote;
+ void *pvUser;
+} EMWEBCAMREQCTX;
+
+
+static DECLCALLBACK(void) drvEmWebcamReady(PPDMIWEBCAMDRV pInterface,
+ bool fReady)
+{
+ NOREF(fReady);
+
+ PEMWEBCAMDRV pThis = RT_FROM_MEMBER(pInterface, EMWEBCAMDRV, IWebcamDrv);
+ EMWEBCAMREMOTE *pRemote = pThis->pRemote;
+
+ LogFlowFunc(("pRemote:%p\n", pThis->pRemote));
+
+ if (pThis->pIWebcamUp)
+ {
+ pThis->pIWebcamUp->pfnAttached(pThis->pIWebcamUp,
+ pRemote->u64DeviceId,
+ pRemote->pDeviceDesc,
+ pRemote->cbDeviceDesc,
+ pRemote->u32Version,
+ pRemote->fu32Capabilities);
+ }
+}
+
+static DECLCALLBACK(int) drvEmWebcamControl(PPDMIWEBCAMDRV pInterface,
+ void *pvUser,
+ uint64_t u64DeviceId,
+ const struct VRDEVIDEOINCTRLHDR *pCtrl,
+ uint32_t cbCtrl)
+{
+ PEMWEBCAMDRV pThis = RT_FROM_MEMBER(pInterface, EMWEBCAMDRV, IWebcamDrv);
+ EMWEBCAMREMOTE *pRemote = pThis->pRemote;
+
+ LogFlowFunc(("pRemote:%p, u64DeviceId %lld\n", pRemote, u64DeviceId));
+
+ return pRemote->pEmWebcam->SendControl(pThis, pvUser, u64DeviceId, pCtrl, cbCtrl);
+}
+
+
+EmWebcam::EmWebcam(ConsoleVRDPServer *pServer)
+ :
+ mParent(pServer),
+ mpDrv(NULL),
+ mpRemote(NULL),
+ mu64DeviceIdSrc(0)
+{
+}
+
+EmWebcam::~EmWebcam()
+{
+ if (mpDrv)
+ {
+ mpDrv->pRemote = NULL;
+ mpDrv = NULL;
+ }
+}
+
+void EmWebcam::EmWebcamConstruct(EMWEBCAMDRV *pDrv)
+{
+ AssertReturnVoid(mpDrv == NULL);
+
+ mpDrv = pDrv;
+}
+
+void EmWebcam::EmWebcamDestruct(EMWEBCAMDRV *pDrv)
+{
+ AssertReturnVoid(pDrv == mpDrv);
+
+ if (mpRemote)
+ {
+ mParent->VideoInDeviceDetach(&mpRemote->deviceHandle);
+
+ RTMemFree(mpRemote->pDeviceDesc);
+ mpRemote->pDeviceDesc = NULL;
+ mpRemote->cbDeviceDesc = 0;
+
+ RTMemFree(mpRemote);
+ mpRemote = NULL;
+ }
+
+ mpDrv->pRemote = NULL;
+ mpDrv = NULL;
+}
+
+void EmWebcam::EmWebcamCbNotify(uint32_t u32Id, const void *pvData, uint32_t cbData)
+{
+ int vrc = VINF_SUCCESS;
+
+ switch (u32Id)
+ {
+ case VRDE_VIDEOIN_NOTIFY_ID_ATTACH:
+ {
+ VRDEVIDEOINNOTIFYATTACH *p = (VRDEVIDEOINNOTIFYATTACH *)pvData;
+
+ /* Older versions did not report u32Version and fu32Capabilities. */
+ uint32_t u32Version = 1;
+ uint32_t fu32Capabilities = VRDE_VIDEOIN_NEGOTIATE_CAP_VOID;
+
+ if (cbData >= RT_UOFFSETOF(VRDEVIDEOINNOTIFYATTACH, u32Version) + sizeof(p->u32Version))
+ u32Version = p->u32Version;
+
+ if (cbData >= RT_UOFFSETOF(VRDEVIDEOINNOTIFYATTACH, fu32Capabilities) + sizeof(p->fu32Capabilities))
+ fu32Capabilities = p->fu32Capabilities;
+
+ LogFlowFunc(("ATTACH[%d,%d] version %d, caps 0x%08X\n",
+ p->deviceHandle.u32ClientId, p->deviceHandle.u32DeviceId,
+ u32Version, fu32Capabilities));
+
+ /* Currently only one device is allowed. */
+ if (mpRemote)
+ {
+ AssertFailed();
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ EMWEBCAMREMOTE *pRemote = (EMWEBCAMREMOTE *)RTMemAllocZ(sizeof(EMWEBCAMREMOTE));
+ if (pRemote == NULL)
+ {
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pRemote->pEmWebcam = this;
+ pRemote->deviceHandle = p->deviceHandle;
+ pRemote->u32Version = u32Version;
+ pRemote->fu32Capabilities = fu32Capabilities;
+ pRemote->pDeviceDesc = NULL;
+ pRemote->cbDeviceDesc = 0;
+ pRemote->u64DeviceId = ASMAtomicIncU64(&mu64DeviceIdSrc);
+
+ mpRemote = pRemote;
+
+ /* Tell the server that this webcam will be used. */
+ vrc = mParent->VideoInDeviceAttach(&mpRemote->deviceHandle, mpRemote);
+ if (RT_FAILURE(vrc))
+ {
+ RTMemFree(mpRemote);
+ mpRemote = NULL;
+ break;
+ }
+
+ /* Get the device description. */
+ vrc = mParent->VideoInGetDeviceDesc(NULL, &mpRemote->deviceHandle);
+
+ if (RT_FAILURE(vrc))
+ {
+ mParent->VideoInDeviceDetach(&mpRemote->deviceHandle);
+ RTMemFree(mpRemote);
+ mpRemote = NULL;
+ break;
+ }
+
+ LogFlowFunc(("sent DeviceDesc\n"));
+ } break;
+
+ case VRDE_VIDEOIN_NOTIFY_ID_DETACH:
+ {
+ VRDEVIDEOINNOTIFYDETACH *p = (VRDEVIDEOINNOTIFYDETACH *)pvData; NOREF(p);
+ Assert(cbData == sizeof(VRDEVIDEOINNOTIFYDETACH));
+
+ LogFlowFunc(("DETACH[%d,%d]\n", p->deviceHandle.u32ClientId, p->deviceHandle.u32DeviceId));
+
+ /** @todo */
+ if (mpRemote)
+ {
+ if (mpDrv && mpDrv->pIWebcamUp)
+ mpDrv->pIWebcamUp->pfnDetached(mpDrv->pIWebcamUp, mpRemote->u64DeviceId);
+ /* mpRemote is deallocated in EmWebcamDestruct */
+ }
+ } break;
+
+ default:
+ vrc = VERR_INVALID_PARAMETER;
+ AssertFailed();
+ break;
+ }
+
+ return;
+}
+
+void EmWebcam::EmWebcamCbDeviceDesc(int rcRequest, void *pDeviceCtx, void *pvUser,
+ const VRDEVIDEOINDEVICEDESC *pDeviceDesc, uint32_t cbDeviceDesc)
+{
+ RT_NOREF(pvUser);
+ EMWEBCAMREMOTE *pRemote = (EMWEBCAMREMOTE *)pDeviceCtx;
+ Assert(pRemote == mpRemote);
+
+ LogFlowFunc(("mpDrv %p, rcRequest %Rrc %p %p %p %d\n",
+ mpDrv, rcRequest, pDeviceCtx, pvUser, pDeviceDesc, cbDeviceDesc));
+
+ if (RT_SUCCESS(rcRequest))
+ {
+ /* Save device description. */
+ Assert(pRemote->pDeviceDesc == NULL);
+ pRemote->pDeviceDesc = (VRDEVIDEOINDEVICEDESC *)RTMemDup(pDeviceDesc, cbDeviceDesc);
+ pRemote->cbDeviceDesc = cbDeviceDesc;
+
+ /* Try to attach the device. */
+ EmulatedUSB *pEUSB = mParent->getConsole()->i_getEmulatedUSB();
+ pEUSB->i_webcamAttachInternal("", "", "EmWebcam", pRemote);
+ }
+ else
+ {
+ mParent->VideoInDeviceDetach(&mpRemote->deviceHandle);
+ RTMemFree(mpRemote);
+ mpRemote = NULL;
+ }
+}
+
+void EmWebcam::EmWebcamCbControl(int rcRequest, void *pDeviceCtx, void *pvUser,
+ const VRDEVIDEOINCTRLHDR *pControl, uint32_t cbControl)
+{
+ RT_NOREF(rcRequest);
+ EMWEBCAMREMOTE *pRemote = (EMWEBCAMREMOTE *)pDeviceCtx; NOREF(pRemote);
+ Assert(pRemote == mpRemote);
+
+ LogFlowFunc(("rcRequest %Rrc %p %p %p %d\n",
+ rcRequest, pDeviceCtx, pvUser, pControl, cbControl));
+
+ bool fResponse = (pvUser != NULL);
+
+ if (mpDrv && mpDrv->pIWebcamUp)
+ {
+ mpDrv->pIWebcamUp->pfnControl(mpDrv->pIWebcamUp,
+ fResponse,
+ pvUser,
+ mpRemote->u64DeviceId,
+ pControl,
+ cbControl);
+ }
+
+ RTMemFree(pvUser);
+}
+
+void EmWebcam::EmWebcamCbFrame(int rcRequest, void *pDeviceCtx,
+ const VRDEVIDEOINPAYLOADHDR *pFrame, uint32_t cbFrame)
+{
+ RT_NOREF(rcRequest, pDeviceCtx);
+ LogFlowFunc(("rcRequest %Rrc %p %p %d\n",
+ rcRequest, pDeviceCtx, pFrame, cbFrame));
+
+ if (mpDrv && mpDrv->pIWebcamUp)
+ {
+ if ( cbFrame >= sizeof(VRDEVIDEOINPAYLOADHDR)
+ && cbFrame >= pFrame->u8HeaderLength)
+ {
+ uint32_t cbImage = cbFrame - pFrame->u8HeaderLength;
+ const uint8_t *pu8Image = cbImage > 0? (const uint8_t *)pFrame + pFrame->u8HeaderLength: NULL;
+
+ mpDrv->pIWebcamUp->pfnFrame(mpDrv->pIWebcamUp,
+ mpRemote->u64DeviceId,
+ pFrame,
+ pFrame->u8HeaderLength,
+ pu8Image,
+ cbImage);
+ }
+ }
+}
+
+int EmWebcam::SendControl(EMWEBCAMDRV *pDrv, void *pvUser, uint64_t u64DeviceId,
+ const VRDEVIDEOINCTRLHDR *pControl, uint32_t cbControl)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int vrc = VINF_SUCCESS;
+
+ EMWEBCAMREQCTX *pCtx = NULL;
+
+ /* Verify that there is a remote device. */
+ if ( !mpRemote
+ || mpRemote->u64DeviceId != u64DeviceId)
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pCtx = (EMWEBCAMREQCTX *)RTMemAlloc(sizeof(EMWEBCAMREQCTX));
+ if (!pCtx)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pCtx->pRemote = mpRemote;
+ pCtx->pvUser = pvUser;
+
+ vrc = mParent->VideoInControl(pCtx, &mpRemote->deviceHandle, pControl, cbControl);
+
+ if (RT_FAILURE(vrc))
+ {
+ RTMemFree(pCtx);
+ }
+ }
+
+ return vrc;
+}
+
+/* static */ DECLCALLBACK(void *) EmWebcam::drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PEMWEBCAMDRV pThis = PDMINS_2_DATA(pDrvIns, PEMWEBCAMDRV);
+
+ LogFlowFunc(("pszIID:%s\n", pszIID));
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIWEBCAMDRV, &pThis->IWebcamDrv);
+ return NULL;
+}
+
+/* static */ DECLCALLBACK(void) EmWebcam::drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PEMWEBCAMDRV pThis = PDMINS_2_DATA(pDrvIns, PEMWEBCAMDRV);
+ EMWEBCAMREMOTE *pRemote = pThis->pRemote;
+
+ LogFlowFunc(("iInstance %d, pRemote %p, pIWebcamUp %p\n",
+ pDrvIns->iInstance, pRemote, pThis->pIWebcamUp));
+
+ if (pRemote && pRemote->pEmWebcam)
+ {
+ pRemote->pEmWebcam->EmWebcamDestruct(pThis);
+ }
+}
+
+/* static */ DECLCALLBACK(int) EmWebcam::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ LogFlowFunc(("iInstance:%d, pCfg:%p, fFlags:%x\n", pDrvIns->iInstance, pCfg, fFlags));
+
+ PEMWEBCAMDRV pThis = PDMINS_2_DATA(pDrvIns, PEMWEBCAMDRV);
+
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /* Check early that there is a device. No need to init anything if there is no device. */
+ pThis->pIWebcamUp = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIWEBCAMDEV);
+ if (pThis->pIWebcamUp == NULL)
+ {
+ LogRel(("USBWEBCAM: Emulated webcam device does not exist.\n"));
+ return VERR_PDM_MISSING_INTERFACE;
+ }
+
+ char *pszId = NULL;
+ int vrc = pDrvIns->pHlpR3->pfnCFGMQueryStringAlloc(pCfg, "Id", &pszId);
+ if (RT_SUCCESS(vrc))
+ {
+ RTUUID UuidEmulatedUsbIf;
+ vrc = RTUuidFromStr(&UuidEmulatedUsbIf, EMULATEDUSBIF_OID); AssertRC(vrc);
+
+ PEMULATEDUSBIF pEmulatedUsbIf = (PEMULATEDUSBIF)PDMDrvHlpQueryGenericUserObject(pDrvIns, &UuidEmulatedUsbIf);
+ AssertPtrReturn(pEmulatedUsbIf, VERR_INVALID_PARAMETER);
+
+ vrc = pEmulatedUsbIf->pfnQueryEmulatedUsbDataById(pEmulatedUsbIf->pvUser, pszId,
+ NULL /*ppvEmUsbCb*/, NULL /*ppvEmUsbCbData*/, (void **)&pThis->pRemote);
+ pDrvIns->pHlpR3->pfnMMHeapFree(pDrvIns, pszId);
+ AssertRCReturn(vrc, vrc);
+ }
+ else
+ return vrc;
+
+ /* Everything ok. Initialize. */
+ pThis->pRemote->pEmWebcam->EmWebcamConstruct(pThis);
+
+ pDrvIns->IBase.pfnQueryInterface = drvQueryInterface;
+
+ pThis->IWebcamDrv.pfnReady = drvEmWebcamReady;
+ pThis->IWebcamDrv.pfnControl = drvEmWebcamControl;
+
+ return VINF_SUCCESS;
+}
+
+/* static */ const PDMDRVREG EmWebcam::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName[32] */
+ "EmWebcam",
+ /* szRCMod[32] */
+ "",
+ /* szR0Mod[32] */
+ "",
+ /* pszDescription */
+ "Main Driver communicating with VRDE",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass */
+ PDM_DRVREG_CLASS_USB,
+ /* cMaxInstances */
+ 1,
+ /* cbInstance */
+ sizeof(EMWEBCAMDRV),
+ /* pfnConstruct */
+ EmWebcam::drvConstruct,
+ /* pfnDestruct */
+ EmWebcam::drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DRVREG_VERSION
+};
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/VBoxDriversRegister.cpp b/src/VBox/Main/src-client/VBoxDriversRegister.cpp
new file mode 100644
index 00000000..e9badd13
--- /dev/null
+++ b/src/VBox/Main/src-client/VBoxDriversRegister.cpp
@@ -0,0 +1,124 @@
+/* $Id: VBoxDriversRegister.cpp $ */
+/** @file
+ *
+ * Main driver registration.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN
+#include "LoggingNew.h"
+
+#include "MouseImpl.h"
+#include "KeyboardImpl.h"
+#include "DisplayImpl.h"
+#include "VMMDev.h"
+#include "NvramStoreImpl.h"
+#ifdef VBOX_WITH_AUDIO_VRDE
+# include "DrvAudioVRDE.h"
+#endif
+#ifdef VBOX_WITH_AUDIO_RECORDING
+# include "DrvAudioRec.h"
+#endif
+#include "UsbWebcamInterface.h"
+#ifdef VBOX_WITH_USB_CARDREADER
+# include "UsbCardReader.h"
+#endif
+#include "ConsoleImpl.h"
+#ifdef VBOX_WITH_PCI_PASSTHROUGH
+# include "PCIRawDevImpl.h"
+#endif
+
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/version.h>
+
+
+/**
+ * Register the main drivers.
+ *
+ * @returns VBox status code.
+ * @param pCallbacks Pointer to the callback table.
+ * @param u32Version VBox version number.
+ */
+extern "C" DECLEXPORT(int) VBoxDriversRegister(PCPDMDRVREGCB pCallbacks, uint32_t u32Version)
+{
+ LogFlow(("VBoxDriversRegister: u32Version=%#x\n", u32Version));
+ AssertReleaseMsg(u32Version == VBOX_VERSION, ("u32Version=%#x VBOX_VERSION=%#x\n", u32Version, VBOX_VERSION));
+
+ int rc = pCallbacks->pfnRegister(pCallbacks, &Mouse::DrvReg);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = pCallbacks->pfnRegister(pCallbacks, &Keyboard::DrvReg);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = pCallbacks->pfnRegister(pCallbacks, &Display::DrvReg);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = pCallbacks->pfnRegister(pCallbacks, &VMMDev::DrvReg);
+ if (RT_FAILURE(rc))
+ return rc;
+#ifdef VBOX_WITH_AUDIO_VRDE
+ rc = pCallbacks->pfnRegister(pCallbacks, &AudioVRDE::DrvReg);
+ if (RT_FAILURE(rc))
+ return rc;
+#endif
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ rc = pCallbacks->pfnRegister(pCallbacks, &AudioVideoRec::DrvReg);
+ if (RT_FAILURE(rc))
+ return rc;
+#endif
+
+ rc = pCallbacks->pfnRegister(pCallbacks, &EmWebcam::DrvReg);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#ifdef VBOX_WITH_USB_CARDREADER
+ rc = pCallbacks->pfnRegister(pCallbacks, &UsbCardReader::DrvReg);
+ if (RT_FAILURE(rc))
+ return rc;
+#endif
+
+ rc = pCallbacks->pfnRegister(pCallbacks, &Console::DrvStatusReg);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#ifdef VBOX_WITH_PCI_PASSTHROUGH
+ rc = pCallbacks->pfnRegister(pCallbacks, &PCIRawDev::DrvReg);
+ if (RT_FAILURE(rc))
+ return rc;
+#endif
+
+ rc = pCallbacks->pfnRegister(pCallbacks, &NvramStore::DrvReg);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ return VINF_SUCCESS;
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/VMMDevInterface.cpp b/src/VBox/Main/src-client/VMMDevInterface.cpp
new file mode 100644
index 00000000..e8c0011d
--- /dev/null
+++ b/src/VBox/Main/src-client/VMMDevInterface.cpp
@@ -0,0 +1,1265 @@
+/* $Id: VMMDevInterface.cpp $ */
+/** @file
+ * VirtualBox Driver Interface to VMM device.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_VMMDEVINTERFACES
+#include "LoggingNew.h"
+
+#include "VMMDev.h"
+#include "ConsoleImpl.h"
+#include "DisplayImpl.h"
+#include "GuestImpl.h"
+#include "MouseImpl.h"
+
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/VMMDev.h>
+#include <VBox/shflsvc.h>
+#include <iprt/asm.h>
+
+#ifdef VBOX_WITH_HGCM
+# include "HGCM.h"
+# include "HGCMObjects.h"
+#endif
+
+//
+// defines
+//
+
+#ifdef RT_OS_OS2
+# define VBOXSHAREDFOLDERS_DLL "VBoxSFld"
+#else
+# define VBOXSHAREDFOLDERS_DLL "VBoxSharedFolders"
+#endif
+
+//
+// globals
+//
+
+
+/**
+ * VMMDev driver instance data.
+ */
+typedef struct DRVMAINVMMDEV
+{
+ /** Pointer to the VMMDev object. */
+ VMMDev *pVMMDev;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the VMMDev port interface of the driver/device above us. */
+ PPDMIVMMDEVPORT pUpPort;
+ /** Our VMM device connector interface. */
+ PDMIVMMDEVCONNECTOR Connector;
+
+#ifdef VBOX_WITH_HGCM
+ /** Pointer to the HGCM port interface of the driver/device above us. */
+ PPDMIHGCMPORT pHGCMPort;
+ /** Our HGCM connector interface. */
+ PDMIHGCMCONNECTOR HGCMConnector;
+#endif
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ HGCMSVCEXTHANDLE hHgcmSvcExtGstProps;
+#endif
+#ifdef VBOX_WITH_GUEST_CONTROL
+ HGCMSVCEXTHANDLE hHgcmSvcExtGstCtrl;
+#endif
+} DRVMAINVMMDEV, *PDRVMAINVMMDEV;
+
+//
+// constructor / destructor
+//
+VMMDev::VMMDev(Console *console)
+ : mpDrv(NULL)
+ , mParent(console)
+{
+ int vrc = RTSemEventCreate(&mCredentialsEvent);
+ AssertRC(vrc);
+#ifdef VBOX_WITH_HGCM
+ vrc = HGCMHostInit();
+ AssertRC(vrc);
+ m_fHGCMActive = true;
+#endif /* VBOX_WITH_HGCM */
+ mu32CredentialsFlags = 0;
+}
+
+VMMDev::~VMMDev()
+{
+#ifdef VBOX_WITH_HGCM
+ if (ASMAtomicCmpXchgBool(&m_fHGCMActive, false, true))
+ HGCMHostShutdown(true /*fUvmIsInvalid*/);
+#endif
+ RTSemEventDestroy(mCredentialsEvent);
+ if (mpDrv)
+ mpDrv->pVMMDev = NULL;
+ mpDrv = NULL;
+}
+
+PPDMIVMMDEVPORT VMMDev::getVMMDevPort()
+{
+ if (!mpDrv)
+ return NULL;
+ return mpDrv->pUpPort;
+}
+
+
+
+//
+// public methods
+//
+
+/**
+ * Wait on event semaphore for guest credential judgement result.
+ */
+int VMMDev::WaitCredentialsJudgement(uint32_t u32Timeout, uint32_t *pu32CredentialsFlags)
+{
+ if (u32Timeout == 0)
+ {
+ u32Timeout = 5000;
+ }
+
+ int vrc = RTSemEventWait(mCredentialsEvent, u32Timeout);
+
+ if (RT_SUCCESS(vrc))
+ {
+ *pu32CredentialsFlags = mu32CredentialsFlags;
+ }
+
+ return vrc;
+}
+
+int VMMDev::SetCredentialsJudgementResult(uint32_t u32Flags)
+{
+ mu32CredentialsFlags = u32Flags;
+
+ int vrc = RTSemEventSignal(mCredentialsEvent);
+ AssertRC(vrc);
+
+ return vrc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIVMMDEVCONNECTOR,pfnUpdateGuestStatus}
+ */
+DECLCALLBACK(void) vmmdevUpdateGuestStatus(PPDMIVMMDEVCONNECTOR pInterface, uint32_t uFacility, uint16_t uStatus,
+ uint32_t fFlags, PCRTTIMESPEC pTimeSpecTS)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /* Store that information in IGuest */
+ Guest* guest = pConsole->i_getGuest();
+ AssertPtrReturnVoid(guest);
+
+ guest->i_setAdditionsStatus((VBoxGuestFacilityType)uFacility, (VBoxGuestFacilityStatus)uStatus, fFlags, pTimeSpecTS);
+ pConsole->i_onAdditionsStateChange();
+}
+
+
+/**
+ * @interface_method_impl{PDMIVMMDEVCONNECTOR,pfnUpdateGuestUserState}
+ */
+DECLCALLBACK(void) vmmdevUpdateGuestUserState(PPDMIVMMDEVCONNECTOR pInterface,
+ const char *pszUser, const char *pszDomain,
+ uint32_t uState,
+ const uint8_t *pabDetails, uint32_t cbDetails)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ AssertPtr(pDrv);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+ AssertPtr(pConsole);
+
+ /* Store that information in IGuest. */
+ Guest* pGuest = pConsole->i_getGuest();
+ AssertPtrReturnVoid(pGuest);
+
+ pGuest->i_onUserStateChanged(Utf8Str(pszUser), Utf8Str(pszDomain), (VBoxGuestUserState)uState, pabDetails, cbDetails);
+}
+
+
+/**
+ * Reports Guest Additions API and OS version.
+ *
+ * Called whenever the Additions issue a guest version report request or the VM
+ * is reset.
+ *
+ * @param pInterface Pointer to this interface.
+ * @param guestInfo Pointer to guest information structure.
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(void) vmmdevUpdateGuestInfo(PPDMIVMMDEVCONNECTOR pInterface, const VBoxGuestInfo *guestInfo)
+{
+ AssertPtrReturnVoid(guestInfo);
+
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /* Store that information in IGuest */
+ Guest* guest = pConsole->i_getGuest();
+ AssertPtrReturnVoid(guest);
+
+ if (guestInfo->interfaceVersion != 0)
+ {
+ char version[16];
+ RTStrPrintf(version, sizeof(version), "%d", guestInfo->interfaceVersion);
+ guest->i_setAdditionsInfo(Bstr(version), guestInfo->osType);
+
+ /*
+ * Tell the console interface about the event
+ * so that it can notify its consumers.
+ */
+ pConsole->i_onAdditionsStateChange();
+
+ if (guestInfo->interfaceVersion < VMMDEV_VERSION)
+ pConsole->i_onAdditionsOutdated();
+ }
+ else
+ {
+ /*
+ * The Guest Additions was disabled because of a reset
+ * or driver unload.
+ */
+ guest->i_setAdditionsInfo(Bstr(), guestInfo->osType); /* Clear interface version + OS type. */
+ /** @todo Would be better if GuestImpl.cpp did all this in the above method call
+ * while holding down the. */
+ guest->i_setAdditionsInfo2(0, "", 0, 0); /* Clear Guest Additions version. */
+ RTTIMESPEC TimeSpecTS;
+ RTTimeNow(&TimeSpecTS);
+ guest->i_setAdditionsStatus(VBoxGuestFacilityType_All, VBoxGuestFacilityStatus_Inactive, 0 /*fFlags*/, &TimeSpecTS);
+ pConsole->i_onAdditionsStateChange();
+ }
+}
+
+/**
+ * @interface_method_impl{PDMIVMMDEVCONNECTOR,pfnUpdateGuestInfo2}
+ */
+DECLCALLBACK(void) vmmdevUpdateGuestInfo2(PPDMIVMMDEVCONNECTOR pInterface, uint32_t uFullVersion,
+ const char *pszName, uint32_t uRevision, uint32_t fFeatures)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ AssertPtr(pszName);
+ Assert(uFullVersion);
+
+ /* Store that information in IGuest. */
+ Guest *pGuest = pDrv->pVMMDev->getParent()->i_getGuest();
+ AssertPtrReturnVoid(pGuest);
+
+ /* Just pass it on... */
+ pGuest->i_setAdditionsInfo2(uFullVersion, pszName, uRevision, fFeatures);
+
+ /*
+ * No need to tell the console interface about the update;
+ * vmmdevUpdateGuestInfo takes care of that when called as the
+ * last event in the chain.
+ */
+}
+
+/**
+ * Update the Guest Additions capabilities.
+ * This is called when the Guest Additions capabilities change. The new capabilities
+ * are given and the connector should update its internal state.
+ *
+ * @param pInterface Pointer to this interface.
+ * @param newCapabilities New capabilities.
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(void) vmmdevUpdateGuestCapabilities(PPDMIVMMDEVCONNECTOR pInterface, uint32_t newCapabilities)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ AssertPtr(pDrv);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /* store that information in IGuest */
+ Guest* pGuest = pConsole->i_getGuest();
+ AssertPtrReturnVoid(pGuest);
+
+ /*
+ * Report our current capabilities (and assume none is active yet).
+ */
+ pGuest->i_setSupportedFeatures(newCapabilities);
+
+ /*
+ * Tell the Display, so that it can update the "supports graphics"
+ * capability if the graphics card has not asserted it.
+ */
+ Display* pDisplay = pConsole->i_getDisplay();
+ AssertPtrReturnVoid(pDisplay);
+ pDisplay->i_handleUpdateVMMDevSupportsGraphics(RT_BOOL(newCapabilities & VMMDEV_GUEST_SUPPORTS_GRAPHICS));
+
+ /*
+ * Tell the console interface about the event
+ * so that it can notify its consumers.
+ */
+ pConsole->i_onAdditionsStateChange();
+}
+
+/**
+ * Update the mouse capabilities.
+ * This is called when the mouse capabilities change. The new capabilities
+ * are given and the connector should update its internal state.
+ *
+ * @param pInterface Pointer to this interface.
+ * @param fNewCaps New capabilities.
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(void) vmmdevUpdateMouseCapabilities(PPDMIVMMDEVCONNECTOR pInterface, uint32_t fNewCaps)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /*
+ * Tell the console interface about the event
+ * so that it can notify its consumers.
+ */
+ Mouse *pMouse = pConsole->i_getMouse();
+ if (pMouse) /** @todo and if not? Can that actually happen? */
+ pMouse->i_onVMMDevGuestCapsChange(fNewCaps & VMMDEV_MOUSE_GUEST_MASK);
+}
+
+/**
+ * Update the pointer shape or visibility.
+ *
+ * This is called when the mouse pointer shape changes or pointer is hidden/displaying.
+ * The new shape is passed as a caller allocated buffer that will be freed after returning.
+ *
+ * @param pInterface Pointer to this interface.
+ * @param fVisible Whether the pointer is visible or not.
+ * @param fAlpha Alpha channel information is present.
+ * @param xHot Horizontal coordinate of the pointer hot spot.
+ * @param yHot Vertical coordinate of the pointer hot spot.
+ * @param width Pointer width in pixels.
+ * @param height Pointer height in pixels.
+ * @param pShape The shape buffer. If NULL, then only pointer visibility is being changed.
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(void) vmmdevUpdatePointerShape(PPDMIVMMDEVCONNECTOR pInterface, bool fVisible, bool fAlpha,
+ uint32_t xHot, uint32_t yHot,
+ uint32_t width, uint32_t height,
+ void *pShape)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /* tell the console about it */
+ uint32_t cbShape = 0;
+ if (pShape)
+ {
+ cbShape = (width + 7) / 8 * height; /* size of the AND mask */
+ cbShape = ((cbShape + 3) & ~3) + width * 4 * height; /* + gap + size of the XOR mask */
+ }
+ pConsole->i_onMousePointerShapeChange(fVisible, fAlpha, xHot, yHot, width, height, (uint8_t *)pShape, cbShape);
+}
+
+DECLCALLBACK(int) iface_VideoAccelEnable(PPDMIVMMDEVCONNECTOR pInterface, bool fEnable, VBVAMEMORY *pVbvaMemory)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ Display *display = pConsole->i_getDisplay();
+
+ if (display)
+ {
+ Log9(("MAIN::VMMDevInterface::iface_VideoAccelEnable: %d, %p\n", fEnable, pVbvaMemory));
+ return display->VideoAccelEnableVMMDev(fEnable, pVbvaMemory);
+ }
+
+ return VERR_NOT_SUPPORTED;
+}
+DECLCALLBACK(void) iface_VideoAccelFlush(PPDMIVMMDEVCONNECTOR pInterface)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ Display *display = pConsole->i_getDisplay();
+
+ if (display)
+ {
+ Log9(("MAIN::VMMDevInterface::iface_VideoAccelFlush\n"));
+ display->VideoAccelFlushVMMDev();
+ }
+}
+
+DECLCALLBACK(int) vmmdevVideoModeSupported(PPDMIVMMDEVCONNECTOR pInterface, uint32_t display, uint32_t width, uint32_t height,
+ uint32_t bpp, bool *fSupported)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ if (!fSupported)
+ return VERR_INVALID_PARAMETER;
+#ifdef DEBUG_sunlover
+ Log(("vmmdevVideoModeSupported: [%d]: %dx%dx%d\n", display, width, height, bpp));
+#endif
+ IFramebuffer *framebuffer = NULL;
+ HRESULT hrc = pConsole->i_getDisplay()->QueryFramebuffer(display, &framebuffer);
+ if (SUCCEEDED(hrc) && framebuffer)
+ {
+ framebuffer->VideoModeSupported(width, height, bpp, (BOOL*)fSupported);
+ framebuffer->Release();
+ }
+ else
+ {
+#ifdef DEBUG_sunlover
+ Log(("vmmdevVideoModeSupported: hrc %x, framebuffer %p!!!\n", hrc, framebuffer));
+#endif
+ *fSupported = true;
+ }
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) vmmdevGetHeightReduction(PPDMIVMMDEVCONNECTOR pInterface, uint32_t *heightReduction)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ if (!heightReduction)
+ return VERR_INVALID_PARAMETER;
+ IFramebuffer *framebuffer = NULL;
+ HRESULT hrc = pConsole->i_getDisplay()->QueryFramebuffer(0, &framebuffer);
+ if (SUCCEEDED(hrc) && framebuffer)
+ {
+ framebuffer->COMGETTER(HeightReduction)((ULONG*)heightReduction);
+ framebuffer->Release();
+ }
+ else
+ *heightReduction = 0;
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) vmmdevSetCredentialsJudgementResult(PPDMIVMMDEVCONNECTOR pInterface, uint32_t u32Flags)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+
+ if (pDrv->pVMMDev)
+ return pDrv->pVMMDev->SetCredentialsJudgementResult(u32Flags);
+
+ return VERR_GENERAL_FAILURE;
+}
+
+DECLCALLBACK(int) vmmdevSetVisibleRegion(PPDMIVMMDEVCONNECTOR pInterface, uint32_t cRect, PRTRECT pRect)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /* Forward to Display, which calls corresponding framebuffers. */
+ pConsole->i_getDisplay()->i_handleSetVisibleRegion(cRect, pRect);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIVMMDEVCONNECTOR,pfnUpdateMonitorPositions}
+ */
+static DECLCALLBACK(int) vmmdevUpdateMonitorPositions(PPDMIVMMDEVCONNECTOR pInterface, uint32_t cPositions, PCRTPOINT paPositions)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ pConsole->i_getDisplay()->i_handleUpdateMonitorPositions(cPositions, paPositions);
+
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) vmmdevQueryVisibleRegion(PPDMIVMMDEVCONNECTOR pInterface, uint32_t *pcRects, PRTRECT paRects)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /* Forward to Display, which calls corresponding framebuffers. */
+ pConsole->i_getDisplay()->i_handleQueryVisibleRegion(pcRects, paRects);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Request the statistics interval
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to this interface.
+ * @param pulInterval Pointer to interval in seconds
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(int) vmmdevQueryStatisticsInterval(PPDMIVMMDEVCONNECTOR pInterface, uint32_t *pulInterval)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+ ULONG val = 0;
+
+ if (!pulInterval)
+ return VERR_INVALID_POINTER;
+
+ /* store that information in IGuest */
+ Guest* guest = pConsole->i_getGuest();
+ AssertPtrReturn(guest, VERR_GENERAL_FAILURE);
+
+ guest->COMGETTER(StatisticsUpdateInterval)(&val);
+ *pulInterval = val;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Query the current balloon size
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to this interface.
+ * @param pcbBalloon Balloon size
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(int) vmmdevQueryBalloonSize(PPDMIVMMDEVCONNECTOR pInterface, uint32_t *pcbBalloon)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+ ULONG val = 0;
+
+ if (!pcbBalloon)
+ return VERR_INVALID_POINTER;
+
+ /* store that information in IGuest */
+ Guest* guest = pConsole->i_getGuest();
+ AssertPtrReturn(guest, VERR_GENERAL_FAILURE);
+
+ guest->COMGETTER(MemoryBalloonSize)(&val);
+ *pcbBalloon = val;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Query the current page fusion setting
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to this interface.
+ * @param pfPageFusionEnabled Pointer to boolean
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(int) vmmdevIsPageFusionEnabled(PPDMIVMMDEVCONNECTOR pInterface, bool *pfPageFusionEnabled)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ if (!pfPageFusionEnabled)
+ return VERR_INVALID_POINTER;
+
+ /* store that information in IGuest */
+ Guest* guest = pConsole->i_getGuest();
+ AssertPtrReturn(guest, VERR_GENERAL_FAILURE);
+
+ *pfPageFusionEnabled = !!guest->i_isPageFusionEnabled();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Report new guest statistics
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to this interface.
+ * @param pGuestStats Guest statistics
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(int) vmmdevReportStatistics(PPDMIVMMDEVCONNECTOR pInterface, VBoxGuestStatistics *pGuestStats)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ AssertPtrReturn(pGuestStats, VERR_INVALID_POINTER);
+
+ /* store that information in IGuest */
+ Guest* guest = pConsole->i_getGuest();
+ AssertPtrReturn(guest, VERR_GENERAL_FAILURE);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_CPU_LOAD_IDLE)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_CPUIDLE, pGuestStats->u32CpuLoad_Idle);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_CPU_LOAD_KERNEL)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_CPUKERNEL, pGuestStats->u32CpuLoad_Kernel);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_CPU_LOAD_USER)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_CPUUSER, pGuestStats->u32CpuLoad_User);
+
+
+ /** @todo r=bird: Convert from 4KB to 1KB units?
+ * CollectorGuestHAL::i_getGuestMemLoad says it returns KB units to
+ * preCollect(). I might be wrong ofc, this is convoluted code... */
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_PHYS_MEM_TOTAL)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_MEMTOTAL, pGuestStats->u32PhysMemTotal);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_PHYS_MEM_AVAIL)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_MEMFREE, pGuestStats->u32PhysMemAvail);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_PHYS_MEM_BALLOON)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_MEMBALLOON, pGuestStats->u32PhysMemBalloon);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_MEM_SYSTEM_CACHE)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_MEMCACHE, pGuestStats->u32MemSystemCache);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_PAGE_FILE_SIZE)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_PAGETOTAL, pGuestStats->u32PageFileSize);
+
+ return VINF_SUCCESS;
+}
+
+#ifdef VBOX_WITH_HGCM
+
+/* HGCM connector interface */
+
+static DECLCALLBACK(int) iface_hgcmConnect(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd,
+ PHGCMSERVICELOCATION pServiceLocation,
+ uint32_t *pu32ClientID)
+{
+ Log9(("Enter\n"));
+
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector);
+
+ if ( !pServiceLocation
+ || ( pServiceLocation->type != VMMDevHGCMLoc_LocalHost
+ && pServiceLocation->type != VMMDevHGCMLoc_LocalHost_Existing))
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Check if service name is a string terminated by zero*/
+ size_t cchInfo = 0;
+ if (RTStrNLenEx(pServiceLocation->u.host.achName, sizeof(pServiceLocation->u.host.achName), &cchInfo) != VINF_SUCCESS)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (!pDrv->pVMMDev || !pDrv->pVMMDev->hgcmIsActive())
+ return VERR_INVALID_STATE;
+ return HGCMGuestConnect(pDrv->pHGCMPort, pCmd, pServiceLocation->u.host.achName, pu32ClientID);
+}
+
+static DECLCALLBACK(int) iface_hgcmDisconnect(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd, uint32_t u32ClientID)
+{
+ Log9(("Enter\n"));
+
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector);
+
+ if (!pDrv->pVMMDev || !pDrv->pVMMDev->hgcmIsActive())
+ return VERR_INVALID_STATE;
+
+ return HGCMGuestDisconnect(pDrv->pHGCMPort, pCmd, u32ClientID);
+}
+
+static DECLCALLBACK(int) iface_hgcmCall(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd, uint32_t u32ClientID,
+ uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms, uint64_t tsArrival)
+{
+ Log9(("Enter\n"));
+
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector);
+
+ if (!pDrv->pVMMDev || !pDrv->pVMMDev->hgcmIsActive())
+ return VERR_INVALID_STATE;
+
+ return HGCMGuestCall(pDrv->pHGCMPort, pCmd, u32ClientID, u32Function, cParms, paParms, tsArrival);
+}
+
+static DECLCALLBACK(void) iface_hgcmCancelled(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd, uint32_t idClient)
+{
+ Log9(("Enter\n"));
+
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector);
+ if ( pDrv->pVMMDev
+ && pDrv->pVMMDev->hgcmIsActive())
+ return HGCMGuestCancelled(pDrv->pHGCMPort, pCmd, idClient);
+}
+
+/**
+ * Execute state save operation.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns Driver instance of the driver which registered the data unit.
+ * @param pSSM SSM operation handle.
+ */
+/*static*/ DECLCALLBACK(int) VMMDev::hgcmSave(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM)
+{
+ PDRVMAINVMMDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV);
+ Log9(("Enter\n"));
+
+ AssertReturn(pThis->pVMMDev, VERR_INTERNAL_ERROR_2);
+ Console::SafeVMPtrQuiet ptrVM(pThis->pVMMDev->mParent);
+ AssertReturn(ptrVM.isOk(), VERR_INTERNAL_ERROR_3);
+ return HGCMHostSaveState(pSSM, ptrVM.vtable());
+}
+
+
+/**
+ * Execute state load operation.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns Driver instance of the driver which registered the data unit.
+ * @param pSSM SSM operation handle.
+ * @param uVersion Data layout version.
+ * @param uPass The data pass.
+ */
+/*static*/ DECLCALLBACK(int) VMMDev::hgcmLoad(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PDRVMAINVMMDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV);
+ LogFlowFunc(("Enter\n"));
+
+ if ( uVersion != HGCM_SAVED_STATE_VERSION
+ && uVersion != HGCM_SAVED_STATE_VERSION_V2)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ AssertReturn(pThis->pVMMDev, VERR_INTERNAL_ERROR_2);
+ Console::SafeVMPtrQuiet ptrVM(pThis->pVMMDev->mParent);
+ AssertReturn(ptrVM.isOk(), VERR_INTERNAL_ERROR_3);
+ return HGCMHostLoadState(pSSM, ptrVM.vtable(), uVersion);
+}
+
+int VMMDev::hgcmLoadService(const char *pszServiceLibrary, const char *pszServiceName)
+{
+ if (!hgcmIsActive())
+ return VERR_INVALID_STATE;
+
+ /** @todo Construct all the services in the VMMDev::drvConstruct()!! */
+ Assert( (mpDrv && mpDrv->pHGCMPort)
+ || !strcmp(pszServiceLibrary, "VBoxHostChannel")
+ || !strcmp(pszServiceLibrary, "VBoxSharedClipboard")
+ || !strcmp(pszServiceLibrary, "VBoxDragAndDropSvc")
+ || !strcmp(pszServiceLibrary, "VBoxGuestPropSvc")
+ || !strcmp(pszServiceLibrary, "VBoxSharedCrOpenGL")
+ );
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ return HGCMHostLoad(pszServiceLibrary, pszServiceName, ptrVM.rawUVM(), ptrVM.vtable(), mpDrv ? mpDrv->pHGCMPort : NULL);
+}
+
+int VMMDev::hgcmHostCall(const char *pszServiceName, uint32_t u32Function,
+ uint32_t cParms, PVBOXHGCMSVCPARM paParms)
+{
+ if (!hgcmIsActive())
+ return VERR_INVALID_STATE;
+ return HGCMHostCall(pszServiceName, u32Function, cParms, paParms);
+}
+
+/**
+ * Used by Console::i_powerDown to shut down the services before the VM is destroyed.
+ */
+void VMMDev::hgcmShutdown(bool fUvmIsInvalid /*= false*/)
+{
+#ifdef VBOX_WITH_GUEST_PROPS
+ if (mpDrv && mpDrv->hHgcmSvcExtGstProps)
+ {
+ HGCMHostUnregisterServiceExtension(mpDrv->hHgcmSvcExtGstProps);
+ mpDrv->hHgcmSvcExtGstProps = NULL;
+ }
+#endif
+
+#ifdef VBOX_WITH_GUEST_CONTROL
+ if (mpDrv && mpDrv->hHgcmSvcExtGstCtrl)
+ {
+ HGCMHostUnregisterServiceExtension(mpDrv->hHgcmSvcExtGstCtrl);
+ mpDrv->hHgcmSvcExtGstCtrl = NULL;
+ }
+#endif
+
+ if (ASMAtomicCmpXchgBool(&m_fHGCMActive, false, true))
+ HGCMHostShutdown(fUvmIsInvalid);
+}
+
+#endif /* HGCM */
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) VMMDev::drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINVMMDEV pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIVMMDEVCONNECTOR, &pDrv->Connector);
+#ifdef VBOX_WITH_HGCM
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHGCMCONNECTOR, &pDrv->HGCMConnector);
+#endif
+ return NULL;
+}
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnSuspend}
+ */
+/*static*/ DECLCALLBACK(void) VMMDev::drvSuspend(PPDMDRVINS pDrvIns)
+{
+ RT_NOREF(pDrvIns);
+#ifdef VBOX_WITH_HGCM
+ HGCMBroadcastEvent(HGCMNOTIFYEVENT_SUSPEND);
+#endif
+}
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnResume}
+ */
+/*static*/ DECLCALLBACK(void) VMMDev::drvResume(PPDMDRVINS pDrvIns)
+{
+ RT_NOREF(pDrvIns);
+#ifdef VBOX_WITH_HGCM
+ HGCMBroadcastEvent(HGCMNOTIFYEVENT_RESUME);
+#endif
+}
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnPowerOff}
+ */
+/*static*/ DECLCALLBACK(void) VMMDev::drvPowerOff(PPDMDRVINS pDrvIns)
+{
+ RT_NOREF(pDrvIns);
+#ifdef VBOX_WITH_HGCM
+ HGCMBroadcastEvent(HGCMNOTIFYEVENT_POWER_ON);
+#endif
+}
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnPowerOn}
+ */
+/*static*/ DECLCALLBACK(void) VMMDev::drvPowerOn(PPDMDRVINS pDrvIns)
+{
+ RT_NOREF(pDrvIns);
+#ifdef VBOX_WITH_HGCM
+ HGCMBroadcastEvent(HGCMNOTIFYEVENT_POWER_ON);
+#endif
+}
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnReset}
+ */
+DECLCALLBACK(void) VMMDev::drvReset(PPDMDRVINS pDrvIns)
+{
+ RT_NOREF(pDrvIns);
+ LogFlow(("VMMDev::drvReset: iInstance=%d\n", pDrvIns->iInstance));
+#ifdef VBOX_WITH_HGCM
+ HGCMHostReset(false /*fForShutdown*/);
+#endif
+}
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnDestruct}
+ */
+DECLCALLBACK(void) VMMDev::drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVMAINVMMDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV);
+ LogFlow(("VMMDev::drvDestruct: iInstance=%d\n", pDrvIns->iInstance));
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ if (pThis->hHgcmSvcExtGstProps)
+ {
+ HGCMHostUnregisterServiceExtension(pThis->hHgcmSvcExtGstProps);
+ pThis->hHgcmSvcExtGstProps = NULL;
+ }
+#endif
+
+#ifdef VBOX_WITH_GUEST_CONTROL
+ if (pThis->hHgcmSvcExtGstCtrl)
+ {
+ HGCMHostUnregisterServiceExtension(pThis->hHgcmSvcExtGstCtrl);
+ pThis->hHgcmSvcExtGstCtrl = NULL;
+ }
+#endif
+
+ if (pThis->pVMMDev)
+ {
+#ifdef VBOX_WITH_HGCM
+ /* When VM construction goes wrong, we prefer shutting down HGCM here
+ while pUVM is still valid, rather than in ~VMMDev. */
+ if (ASMAtomicCmpXchgBool(&pThis->pVMMDev->m_fHGCMActive, false, true))
+ HGCMHostShutdown();
+#endif
+ pThis->pVMMDev->mpDrv = NULL;
+ }
+}
+
+#ifdef VBOX_WITH_GUEST_PROPS
+
+/**
+ * Set an array of guest properties
+ */
+void VMMDev::i_guestPropSetMultiple(void *names, void *values, void *timestamps, void *flags)
+{
+ VBOXHGCMSVCPARM parms[4];
+
+ parms[0].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[0].u.pointer.addr = names;
+ parms[0].u.pointer.size = 0; /* We don't actually care. */
+ parms[1].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[1].u.pointer.addr = values;
+ parms[1].u.pointer.size = 0; /* We don't actually care. */
+ parms[2].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[2].u.pointer.addr = timestamps;
+ parms[2].u.pointer.size = 0; /* We don't actually care. */
+ parms[3].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[3].u.pointer.addr = flags;
+ parms[3].u.pointer.size = 0; /* We don't actually care. */
+
+ hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_PROPS, 4, &parms[0]);
+}
+
+/**
+ * Set a single guest property
+ */
+void VMMDev::i_guestPropSet(const char *pszName, const char *pszValue, const char *pszFlags)
+{
+ VBOXHGCMSVCPARM parms[4];
+
+ AssertPtrReturnVoid(pszName);
+ AssertPtrReturnVoid(pszValue);
+ AssertPtrReturnVoid(pszFlags);
+ parms[0].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[0].u.pointer.addr = (void *)pszName;
+ parms[0].u.pointer.size = (uint32_t)strlen(pszName) + 1;
+ parms[1].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[1].u.pointer.addr = (void *)pszValue;
+ parms[1].u.pointer.size = (uint32_t)strlen(pszValue) + 1;
+ parms[2].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[2].u.pointer.addr = (void *)pszFlags;
+ parms[2].u.pointer.size = (uint32_t)strlen(pszFlags) + 1;
+ hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_PROP, 3, &parms[0]);
+}
+
+/**
+ * Set the global flags value by calling the service
+ * @returns the status returned by the call to the service
+ *
+ * @param pTable the service instance handle
+ * @param eFlags the flags to set
+ */
+int VMMDev::i_guestPropSetGlobalPropertyFlags(uint32_t fFlags)
+{
+ VBOXHGCMSVCPARM parm;
+ HGCMSvcSetU32(&parm, fFlags);
+ int vrc = hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_GLOBAL_FLAGS, 1, &parm);
+ if (RT_FAILURE(vrc))
+ {
+ char szFlags[GUEST_PROP_MAX_FLAGS_LEN];
+ if (RT_FAILURE(GuestPropWriteFlags(fFlags, szFlags)))
+ Log(("Failed to set the global flags.\n"));
+ else
+ Log(("Failed to set the global flags \"%s\".\n", szFlags));
+ }
+ return vrc;
+}
+
+
+/**
+ * Set up the Guest Property service, populate it with properties read from
+ * the machine XML and set a couple of initial properties.
+ */
+int VMMDev::i_guestPropLoadAndConfigure()
+{
+ Assert(mpDrv);
+ ComObjPtr<Console> ptrConsole = this->mParent;
+ AssertReturn(ptrConsole.isNotNull(), VERR_INVALID_POINTER);
+
+ /*
+ * Load the service
+ */
+ int vrc = hgcmLoadService("VBoxGuestPropSvc", "VBoxGuestPropSvc");
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("VBoxGuestPropSvc is not available. vrc = %Rrc\n", vrc));
+ return VINF_SUCCESS; /* That is not a fatal failure. */
+ }
+
+ /*
+ * Pull over the properties from the server.
+ */
+ SafeArray<BSTR> namesOut;
+ SafeArray<BSTR> valuesOut;
+ SafeArray<LONG64> timestampsOut;
+ SafeArray<BSTR> flagsOut;
+ HRESULT hrc = ptrConsole->i_pullGuestProperties(ComSafeArrayAsOutParam(namesOut),
+ ComSafeArrayAsOutParam(valuesOut),
+ ComSafeArrayAsOutParam(timestampsOut),
+ ComSafeArrayAsOutParam(flagsOut));
+ AssertLogRelMsgReturn(SUCCEEDED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR);
+ size_t const cProps = namesOut.size();
+ size_t const cAlloc = cProps + 1;
+ AssertLogRelReturn(valuesOut.size() == cProps, VERR_INTERNAL_ERROR_2);
+ AssertLogRelReturn(timestampsOut.size() == cProps, VERR_INTERNAL_ERROR_3);
+ AssertLogRelReturn(flagsOut.size() == cProps, VERR_INTERNAL_ERROR_4);
+
+ char szEmpty[] = "";
+ char **papszNames = (char **)RTMemTmpAllocZ(sizeof(char *) * cAlloc);
+ char **papszValues = (char **)RTMemTmpAllocZ(sizeof(char *) * cAlloc);
+ LONG64 *pai64Timestamps = (LONG64 *)RTMemTmpAllocZ(sizeof(LONG64) * cAlloc);
+ char **papszFlags = (char **)RTMemTmpAllocZ(sizeof(char *) * cAlloc);
+ if (papszNames && papszValues && pai64Timestamps && papszFlags)
+ {
+ for (unsigned i = 0; RT_SUCCESS(vrc) && i < cProps; ++i)
+ {
+ AssertPtrBreakStmt(namesOut[i], vrc = VERR_INVALID_PARAMETER);
+ vrc = RTUtf16ToUtf8(namesOut[i], &papszNames[i]);
+ if (RT_FAILURE(vrc))
+ break;
+ if (valuesOut[i])
+ vrc = RTUtf16ToUtf8(valuesOut[i], &papszValues[i]);
+ else
+ papszValues[i] = szEmpty;
+ if (RT_FAILURE(vrc))
+ break;
+ pai64Timestamps[i] = timestampsOut[i];
+ if (flagsOut[i])
+ vrc = RTUtf16ToUtf8(flagsOut[i], &papszFlags[i]);
+ else
+ papszFlags[i] = szEmpty;
+ }
+ if (RT_SUCCESS(vrc))
+ i_guestPropSetMultiple((void *)papszNames, (void *)papszValues, (void *)pai64Timestamps, (void *)papszFlags);
+ for (unsigned i = 0; i < cProps; ++i)
+ {
+ RTStrFree(papszNames[i]);
+ if (valuesOut[i])
+ RTStrFree(papszValues[i]);
+ if (flagsOut[i])
+ RTStrFree(papszFlags[i]);
+ }
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ RTMemTmpFree(papszNames);
+ RTMemTmpFree(papszValues);
+ RTMemTmpFree(pai64Timestamps);
+ RTMemTmpFree(papszFlags);
+ AssertRCReturn(vrc, vrc);
+
+ /*
+ * Register the host notification callback
+ */
+ HGCMHostRegisterServiceExtension(&mpDrv->hHgcmSvcExtGstProps, "VBoxGuestPropSvc", Console::i_doGuestPropNotification, ptrConsole.m_p);
+
+# ifdef VBOX_WITH_GUEST_PROPS_RDONLY_GUEST
+ vrc = i_guestPropSetGlobalPropertyFlags(GUEST_PROP_F_RDONLYGUEST);
+ AssertRCReturn(vrc, vrc);
+# endif
+
+ Log(("Set VBoxGuestPropSvc property store\n"));
+ return VINF_SUCCESS;
+}
+
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnConstruct}
+ */
+DECLCALLBACK(int) VMMDev::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ RT_NOREF(fFlags, pCfg);
+ PDRVMAINVMMDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV);
+ LogFlow(("Keyboard::drvConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", "");
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * IBase.
+ */
+ pDrvIns->IBase.pfnQueryInterface = VMMDev::drvQueryInterface;
+
+ pThis->Connector.pfnUpdateGuestStatus = vmmdevUpdateGuestStatus;
+ pThis->Connector.pfnUpdateGuestUserState = vmmdevUpdateGuestUserState;
+ pThis->Connector.pfnUpdateGuestInfo = vmmdevUpdateGuestInfo;
+ pThis->Connector.pfnUpdateGuestInfo2 = vmmdevUpdateGuestInfo2;
+ pThis->Connector.pfnUpdateGuestCapabilities = vmmdevUpdateGuestCapabilities;
+ pThis->Connector.pfnUpdateMouseCapabilities = vmmdevUpdateMouseCapabilities;
+ pThis->Connector.pfnUpdatePointerShape = vmmdevUpdatePointerShape;
+ pThis->Connector.pfnVideoAccelEnable = iface_VideoAccelEnable;
+ pThis->Connector.pfnVideoAccelFlush = iface_VideoAccelFlush;
+ pThis->Connector.pfnVideoModeSupported = vmmdevVideoModeSupported;
+ pThis->Connector.pfnGetHeightReduction = vmmdevGetHeightReduction;
+ pThis->Connector.pfnSetCredentialsJudgementResult = vmmdevSetCredentialsJudgementResult;
+ pThis->Connector.pfnSetVisibleRegion = vmmdevSetVisibleRegion;
+ pThis->Connector.pfnUpdateMonitorPositions = vmmdevUpdateMonitorPositions;
+ pThis->Connector.pfnQueryVisibleRegion = vmmdevQueryVisibleRegion;
+ pThis->Connector.pfnReportStatistics = vmmdevReportStatistics;
+ pThis->Connector.pfnQueryStatisticsInterval = vmmdevQueryStatisticsInterval;
+ pThis->Connector.pfnQueryBalloonSize = vmmdevQueryBalloonSize;
+ pThis->Connector.pfnIsPageFusionEnabled = vmmdevIsPageFusionEnabled;
+
+#ifdef VBOX_WITH_HGCM
+ pThis->HGCMConnector.pfnConnect = iface_hgcmConnect;
+ pThis->HGCMConnector.pfnDisconnect = iface_hgcmDisconnect;
+ pThis->HGCMConnector.pfnCall = iface_hgcmCall;
+ pThis->HGCMConnector.pfnCancelled = iface_hgcmCancelled;
+#endif
+
+ /*
+ * Get the IVMMDevPort interface of the above driver/device.
+ */
+ pThis->pUpPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIVMMDEVPORT);
+ AssertMsgReturn(pThis->pUpPort, ("Configuration error: No VMMDev port interface above!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
+
+#ifdef VBOX_WITH_HGCM
+ pThis->pHGCMPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHGCMPORT);
+ AssertMsgReturn(pThis->pHGCMPort, ("Configuration error: No HGCM port interface above!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
+#endif
+
+ /*
+ * Get the Console object pointer and update the mpDrv member.
+ */
+ com::Guid uuid(VMMDEV_OID);
+ pThis->pVMMDev = (VMMDev *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw());
+ if (!pThis->pVMMDev)
+ {
+ AssertMsgFailed(("Configuration error: No/bad VMMDev object!\n"));
+ return VERR_NOT_FOUND;
+ }
+ pThis->pVMMDev->mpDrv = pThis;
+
+ int vrc = VINF_SUCCESS;
+#ifdef VBOX_WITH_HGCM
+ /*
+ * Load & configure the shared folders service.
+ */
+ vrc = pThis->pVMMDev->hgcmLoadService(VBOXSHAREDFOLDERS_DLL, "VBoxSharedFolders");
+ pThis->pVMMDev->fSharedFolderActive = RT_SUCCESS(vrc);
+ if (RT_SUCCESS(vrc))
+ {
+ PPDMLED pLed;
+ PPDMILEDPORTS pLedPort;
+
+ LogRel(("Shared Folders service loaded\n"));
+ pLedPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMILEDPORTS);
+ AssertMsgReturn(pLedPort, ("Configuration error: No LED port interface above!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
+ vrc = pLedPort->pfnQueryStatusLed(pLedPort, 0, &pLed);
+ if (RT_SUCCESS(vrc) && pLed)
+ {
+ VBOXHGCMSVCPARM parm;
+
+ parm.type = VBOX_HGCM_SVC_PARM_PTR;
+ parm.u.pointer.addr = pLed;
+ parm.u.pointer.size = sizeof(*pLed);
+
+ vrc = HGCMHostCall("VBoxSharedFolders", SHFL_FN_SET_STATUS_LED, 1, &parm);
+ }
+ else
+ AssertMsgFailed(("pfnQueryStatusLed failed with %Rrc (pLed=%x)\n", vrc, pLed));
+ }
+ else
+ LogRel(("Failed to load Shared Folders service %Rrc\n", vrc));
+
+
+ /*
+ * Load and configure the guest control service.
+ */
+# ifdef VBOX_WITH_GUEST_CONTROL
+ vrc = pThis->pVMMDev->hgcmLoadService("VBoxGuestControlSvc", "VBoxGuestControlSvc");
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = HGCMHostRegisterServiceExtension(&pThis->hHgcmSvcExtGstCtrl, "VBoxGuestControlSvc",
+ &Guest::i_notifyCtrlDispatcher,
+ pThis->pVMMDev->mParent->i_getGuest());
+ if (RT_SUCCESS(vrc))
+ LogRel(("Guest Control service loaded\n"));
+ else
+ LogRel(("Warning: Cannot register VBoxGuestControlSvc extension! vrc=%Rrc\n", vrc));
+ }
+ else
+ LogRel(("Warning!: Failed to load the Guest Control Service! %Rrc\n", vrc));
+# endif /* VBOX_WITH_GUEST_CONTROL */
+
+
+ /*
+ * Load and configure the guest properties service.
+ */
+# ifdef VBOX_WITH_GUEST_PROPS
+ vrc = pThis->pVMMDev->i_guestPropLoadAndConfigure();
+ AssertLogRelRCReturn(vrc, vrc);
+# endif
+
+
+ /*
+ * The HGCM saved state.
+ */
+ vrc = PDMDrvHlpSSMRegisterEx(pDrvIns, HGCM_SAVED_STATE_VERSION, 4096 /* bad guess */,
+ NULL, NULL, NULL,
+ NULL, VMMDev::hgcmSave, NULL,
+ NULL, VMMDev::hgcmLoad, NULL);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+#endif /* VBOX_WITH_HGCM */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * VMMDevice driver registration record.
+ */
+const PDMDRVREG VMMDev::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "HGCM",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Main VMMDev driver (Main as in the API).",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_VMMDEV,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMAINVMMDEV),
+ /* pfnConstruct */
+ VMMDev::drvConstruct,
+ /* pfnDestruct */
+ VMMDev::drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ VMMDev::drvPowerOn,
+ /* pfnReset */
+ VMMDev::drvReset,
+ /* pfnSuspend */
+ VMMDev::drvSuspend,
+ /* pfnResume */
+ VMMDev::drvResume,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ VMMDev::drvPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/VirtualBoxClientImpl.cpp b/src/VBox/Main/src-client/VirtualBoxClientImpl.cpp
new file mode 100644
index 00000000..bf72790f
--- /dev/null
+++ b/src/VBox/Main/src-client/VirtualBoxClientImpl.cpp
@@ -0,0 +1,833 @@
+/* $Id: VirtualBoxClientImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_VIRTUALBOXCLIENT
+#include "LoggingNew.h"
+
+#include "VirtualBoxClientImpl.h"
+
+#include "AutoCaller.h"
+#include "VBoxEvents.h"
+#include "VBox/com/ErrorInfo.h"
+#include "VBox/com/listeners.h"
+
+#include <iprt/asm.h>
+#include <iprt/thread.h>
+#include <iprt/critsect.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/utf16.h>
+#ifdef RT_OS_WINDOWS
+# include <iprt/err.h>
+# include <iprt/ldr.h>
+# include <msi.h>
+# include <WbemIdl.h>
+#endif
+
+#include <new>
+
+
+/** Waiting time between probing whether VBoxSVC is alive. */
+#define VBOXCLIENT_DEFAULT_INTERVAL 30000
+
+
+/** Initialize instance counter class variable */
+uint32_t VirtualBoxClient::g_cInstances = 0;
+
+LONG VirtualBoxClient::s_cUnnecessaryAtlModuleLocks = 0;
+
+#ifdef VBOX_WITH_MAIN_NLS
+
+/* listener class for language updates */
+class VBoxEventListener
+{
+public:
+ VBoxEventListener()
+ {}
+
+
+ HRESULT init(void *)
+ {
+ return S_OK;
+ }
+
+ HRESULT init()
+ {
+ return S_OK;
+ }
+
+ void uninit()
+ {
+ }
+
+ virtual ~VBoxEventListener()
+ {
+ }
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
+ {
+ switch(aType)
+ {
+ case VBoxEventType_OnLanguageChanged:
+ {
+ /*
+ * Proceed with uttmost care as we might be racing com::Shutdown()
+ * and have the ground open up beneath us.
+ */
+ LogFunc(("VBoxEventType_OnLanguageChanged\n"));
+ VirtualBoxTranslator *pTranslator = VirtualBoxTranslator::tryInstance();
+ if (pTranslator)
+ {
+ ComPtr<ILanguageChangedEvent> pEvent = aEvent;
+ Assert(pEvent);
+
+ /* This call may fail if we're racing COM shutdown. */
+ com::Bstr bstrLanguageId;
+ HRESULT hrc = pEvent->COMGETTER(LanguageId)(bstrLanguageId.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ com::Utf8Str strLanguageId(bstrLanguageId);
+ LogFunc(("New language ID: %s\n", strLanguageId.c_str()));
+ pTranslator->i_loadLanguage(strLanguageId.c_str());
+ }
+ catch (std::bad_alloc &)
+ {
+ LogFunc(("Caught bad_alloc"));
+ }
+ }
+ else
+ LogFunc(("Failed to get new language ID: %Rhrc\n", hrc));
+
+ pTranslator->release();
+ }
+ break;
+ }
+
+ default:
+ AssertFailed();
+ }
+
+ return S_OK;
+ }
+};
+
+typedef ListenerImpl<VBoxEventListener> VBoxEventListenerImpl;
+
+VBOX_LISTENER_DECLARE(VBoxEventListenerImpl)
+
+#endif /* VBOX_WITH_MAIN_NLS */
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+/** @relates VirtualBoxClient::FinalConstruct() */
+HRESULT VirtualBoxClient::FinalConstruct()
+{
+ HRESULT hrc = init();
+ BaseFinalConstruct();
+ return hrc;
+}
+
+void VirtualBoxClient::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the VirtualBoxClient object.
+ *
+ * @returns COM result indicator
+ */
+HRESULT VirtualBoxClient::init()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Important: DO NOT USE any kind of "early return" (except the single
+ * one above, checking the init span success) in this method. It is vital
+ * for correct error handling that it has only one point of return, which
+ * does all the magic on COM to signal object creation success and
+ * reporting the error later for every API method. COM translates any
+ * unsuccessful object creation to REGDB_E_CLASSNOTREG errors or similar
+ * unhelpful ones which cause us a lot of grief with troubleshooting. */
+
+ HRESULT hrc = S_OK;
+ try
+ {
+ if (ASMAtomicIncU32(&g_cInstances) != 1)
+ AssertFailedStmt(throw setError(E_FAIL, "Attempted to create more than one VirtualBoxClient instance"));
+
+ mData.m_ThreadWatcher = NIL_RTTHREAD;
+ mData.m_SemEvWatcher = NIL_RTSEMEVENT;
+
+ hrc = mData.m_pVirtualBox.createLocalObject(CLSID_VirtualBox);
+ if (FAILED(hrc))
+#ifdef RT_OS_WINDOWS
+ throw i_investigateVirtualBoxObjectCreationFailure(hrc);
+#else
+ throw hrc;
+#endif
+
+ /* VirtualBox error return is postponed to method calls, fetch it. */
+ ULONG rev;
+ hrc = mData.m_pVirtualBox->COMGETTER(Revision)(&rev);
+ if (FAILED(hrc))
+ throw hrc;
+
+ hrc = unconst(mData.m_pEventSource).createObject();
+ AssertComRCThrow(hrc, setError(hrc, "Could not create EventSource for VirtualBoxClient"));
+ hrc = mData.m_pEventSource->init();
+ AssertComRCThrow(hrc, setError(hrc, "Could not initialize EventSource for VirtualBoxClient"));
+
+ /* HACK ALERT! This is for DllCanUnloadNow(). */
+ s_cUnnecessaryAtlModuleLocks++;
+ AssertMsg(s_cUnnecessaryAtlModuleLocks == 1, ("%d\n", s_cUnnecessaryAtlModuleLocks));
+
+ int vrc;
+#ifdef VBOX_WITH_MAIN_NLS
+ /* Create the translator singelton (must work) and try load translations (non-fatal). */
+ mData.m_pVBoxTranslator = VirtualBoxTranslator::instance();
+ if (mData.m_pVBoxTranslator == NULL)
+ throw setError(VBOX_E_IPRT_ERROR, "Failed to create translator instance");
+
+ char szNlsPath[RTPATH_MAX];
+ vrc = RTPathAppPrivateNoArch(szNlsPath, sizeof(szNlsPath));
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppend(szNlsPath, sizeof(szNlsPath), "nls" RTPATH_SLASH_STR "VirtualBoxAPI");
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = mData.m_pVBoxTranslator->registerTranslation(szNlsPath, true, &mData.m_pTrComponent);
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = i_reloadApiLanguage();
+ if (SUCCEEDED(hrc))
+ i_registerEventListener(); /* for updates */
+ else
+ LogRelFunc(("i_reloadApiLanguage failed: %Rhrc\n", hrc));
+ }
+ else
+ LogRelFunc(("Register translation failed: %Rrc\n", vrc));
+ }
+ else
+ LogRelFunc(("Path constructing failed: %Rrc\n", vrc));
+#endif
+ /* Setting up the VBoxSVC watcher thread. If anything goes wrong here it
+ * is not considered important enough to cause any sort of visible
+ * failure. The monitoring will not be done, but that's all. */
+ vrc = RTSemEventCreate(&mData.m_SemEvWatcher);
+ if (RT_FAILURE(vrc))
+ {
+ mData.m_SemEvWatcher = NIL_RTSEMEVENT;
+ AssertRCStmt(vrc, throw setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to create semaphore (vrc=%Rrc)"), vrc));
+ }
+
+ vrc = RTThreadCreate(&mData.m_ThreadWatcher, SVCWatcherThread, this, 0,
+ RTTHREADTYPE_INFREQUENT_POLLER, RTTHREADFLAGS_WAITABLE, "VBoxSVCWatcher");
+ if (RT_FAILURE(vrc))
+ {
+ RTSemEventDestroy(mData.m_SemEvWatcher);
+ mData.m_SemEvWatcher = NIL_RTSEMEVENT;
+ AssertRCStmt(vrc, throw setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to create watcher thread (vrc=%Rrc)"), vrc));
+ }
+ }
+ catch (HRESULT err)
+ {
+ /* we assume that error info is set by the thrower */
+ hrc = err;
+ }
+ catch (...)
+ {
+ hrc = VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS);
+ }
+
+ /* Confirm a successful initialization when it's the case. Must be last,
+ * as on failure it will uninitialize the object. */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed(hrc);
+
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ /* Unconditionally return success, because the error return is delayed to
+ * the attribute/method calls through the InitFailed object state. */
+ return S_OK;
+}
+
+#ifdef RT_OS_WINDOWS
+
+/**
+ * Looks into why we failed to create the VirtualBox object.
+ *
+ * @returns hrcCaller thru setError.
+ * @param hrcCaller The failure status code.
+ */
+HRESULT VirtualBoxClient::i_investigateVirtualBoxObjectCreationFailure(HRESULT hrcCaller)
+{
+ HRESULT hrc;
+
+# ifdef VBOX_WITH_SDS
+ /*
+ * Check that the VBoxSDS service is configured to run as LocalSystem and is enabled.
+ */
+ WCHAR wszBuffer[256];
+ uint32_t uStartType;
+ int vrc = i_getServiceAccountAndStartType(L"VBoxSDS", wszBuffer, RT_ELEMENTS(wszBuffer), &uStartType);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRelFunc(("VBoxSDS service is running under the '%ls' account with start type %u.\n", wszBuffer, uStartType));
+ if (RTUtf16Cmp(wszBuffer, L"LocalSystem") != 0)
+ return setError(hrcCaller,
+ tr("VBoxSDS is misconfigured to run under the '%ls' account instead of the SYSTEM one.\n"
+ "Reinstall VirtualBox to fix it. Alternatively you can fix it using the Windows Service Control "
+ "Manager or by running 'sc config VBoxSDS obj=LocalSystem' on a command line."), wszBuffer);
+ if (uStartType == SERVICE_DISABLED)
+ return setError(hrcCaller,
+ tr("The VBoxSDS windows service is disabled.\n"
+ "Reinstall VirtualBox to fix it. Alternatively try reenable the service by setting it to "
+ " 'Manual' startup type in the Windows Service management console, or by runing "
+ "'sc config VBoxSDS start=demand' on the command line."));
+ }
+ else if (vrc == VERR_NOT_FOUND)
+ return setError(hrcCaller,
+ tr("The VBoxSDS windows service was not found.\n"
+ "Reinstall VirtualBox to fix it. Alternatively you can try start VirtualBox as Administrator, this "
+ "should automatically reinstall the service, or you can run "
+ "'VBoxSDS.exe --regservice' command from an elevated Administrator command line."));
+ else
+ LogRelFunc(("VirtualBoxClient::i_getServiceAccount failed: %Rrc\n", vrc));
+# endif
+
+ /*
+ * First step is to try get an IUnknown interface of the VirtualBox object.
+ *
+ * This will succeed even when oleaut32.msm (see @bugref{8016}, @ticketref{12087})
+ * is accidentally installed and messes up COM. It may also succeed when the COM
+ * registration is partially broken (though that's unlikely to happen these days).
+ */
+ IUnknown *pUnknown = NULL;
+ hrc = CoCreateInstance(CLSID_VirtualBox, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void **)&pUnknown);
+ if (FAILED(hrc))
+ {
+ if (hrc == hrcCaller)
+ return setError(hrcCaller, tr("Completely failed to instantiate CLSID_VirtualBox: %Rhrc"), hrcCaller);
+ return setError(hrcCaller, tr("Completely failed to instantiate CLSID_VirtualBox: %Rhrc & %Rhrc"), hrcCaller, hrc);
+ }
+
+ /*
+ * Try query the IVirtualBox interface (should fail), if it succeed we return
+ * straight away so we have more columns to spend on long messages below.
+ */
+ IVirtualBox *pVirtualBox;
+ hrc = pUnknown->QueryInterface(IID_IVirtualBox, (void **)&pVirtualBox);
+ if (SUCCEEDED(hrc))
+ {
+ pVirtualBox->Release();
+ pUnknown->Release();
+ return setError(hrcCaller,
+ tr("Failed to instantiate CLSID_VirtualBox the first time, but worked when checking out why ... weird"));
+ }
+
+ /*
+ * Check for oleaut32.msm traces in the registry.
+ */
+ HKEY hKey;
+ LSTATUS lrc = RegOpenKeyExW(HKEY_CLASSES_ROOT, L"CLSID\\{00020420-0000-0000-C000-000000000046}\\InprocServer32",
+ 0 /*fFlags*/, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | STANDARD_RIGHTS_READ, &hKey);
+ if (lrc == ERROR_SUCCESS)
+ {
+ wchar_t wszBuf[8192];
+ DWORD cbBuf = sizeof(wszBuf) - sizeof(wchar_t);
+ DWORD dwType = 0;
+ lrc = RegQueryValueExW(hKey, L"InprocServer32", NULL /*pvReserved*/, &dwType, (BYTE *)&wszBuf[0], &cbBuf);
+ if (lrc == ERROR_SUCCESS)
+ {
+ wszBuf[cbBuf / sizeof(wchar_t)] = '\0';
+ bool fSetError = false;
+
+ /*
+ * Try decode the string and improve the message.
+ */
+ typedef UINT (WINAPI *PFNMSIDECOMPOSEDESCRIPTORW)(PCWSTR pwszDescriptor,
+ LPWSTR pwszProductCode /*[40]*/,
+ LPWSTR pwszFeatureId /*[40]*/,
+ LPWSTR pwszComponentCode /*[40]*/,
+ DWORD *poffArguments);
+ PFNMSIDECOMPOSEDESCRIPTORW pfnMsiDecomposeDescriptorW;
+ pfnMsiDecomposeDescriptorW = (PFNMSIDECOMPOSEDESCRIPTORW)RTLdrGetSystemSymbol("msi.dll", "MsiDecomposeDescriptorW");
+ if ( pfnMsiDecomposeDescriptorW
+ && ( dwType == REG_SZ
+ || dwType == REG_MULTI_SZ))
+ {
+ wchar_t wszProductCode[RTUUID_STR_LENGTH + 2 + 16] = { 0 };
+ wchar_t wszFeatureId[RTUUID_STR_LENGTH + 2 + 16] = { 0 };
+ wchar_t wszComponentCode[RTUUID_STR_LENGTH + 2 + 16] = { 0 };
+ DWORD offArguments = ~(DWORD)0;
+ UINT uRc = pfnMsiDecomposeDescriptorW(wszBuf, wszProductCode, wszFeatureId, wszComponentCode, &offArguments);
+ if (uRc == 0)
+ {
+ /*
+ * Can we resolve the product code into a name?
+ */
+ typedef UINT (WINAPI *PFNMSIOPENPRODUCTW)(PCWSTR, MSIHANDLE *);
+ PFNMSIOPENPRODUCTW pfnMsiOpenProductW;
+ pfnMsiOpenProductW = (PFNMSIOPENPRODUCTW)RTLdrGetSystemSymbol("msi.dll", "MsiOpenProductW");
+
+ typedef UINT (WINAPI *PFNMSICLOSEHANDLE)(MSIHANDLE);
+ PFNMSICLOSEHANDLE pfnMsiCloseHandle;
+ pfnMsiCloseHandle = (PFNMSICLOSEHANDLE)RTLdrGetSystemSymbol("msi.dll", "MsiCloseHandle");
+
+ typedef UINT (WINAPI *PFNGETPRODUCTPROPERTYW)(MSIHANDLE, PCWSTR, PWSTR, PDWORD);
+ PFNGETPRODUCTPROPERTYW pfnMsiGetProductPropertyW;
+ pfnMsiGetProductPropertyW = (PFNGETPRODUCTPROPERTYW)RTLdrGetSystemSymbol("msi.dll", "MsiGetProductPropertyW");
+ if ( pfnMsiGetProductPropertyW
+ && pfnMsiCloseHandle
+ && pfnMsiOpenProductW)
+ {
+ MSIHANDLE hMsi = 0;
+ uRc = pfnMsiOpenProductW(wszProductCode, &hMsi);
+ if (uRc == 0)
+ {
+ static wchar_t const * const s_apwszProps[] =
+ {
+ INSTALLPROPERTY_INSTALLEDPRODUCTNAME,
+ INSTALLPROPERTY_PRODUCTNAME,
+ INSTALLPROPERTY_PACKAGENAME,
+ };
+
+ wchar_t wszProductName[1024];
+ DWORD cwcProductName;
+ unsigned i = 0;
+ do
+ {
+ cwcProductName = RT_ELEMENTS(wszProductName) - 1;
+ uRc = pfnMsiGetProductPropertyW(hMsi, s_apwszProps[i], wszProductName, &cwcProductName);
+ }
+ while ( ++i < RT_ELEMENTS(s_apwszProps)
+ && ( uRc != 0
+ || cwcProductName < 2
+ || cwcProductName >= RT_ELEMENTS(wszProductName)) );
+ uRc = pfnMsiCloseHandle(hMsi);
+ if (uRc == 0 && cwcProductName >= 2)
+ {
+ wszProductName[RT_MIN(cwcProductName, RT_ELEMENTS(wszProductName) - 1)] = '\0';
+ setError(hrcCaller,
+ tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.\n"
+ "PSDispatch looks broken by the '%ls' (%ls) program, suspecting that it features the broken oleaut32.msm module as component %ls.\n"
+ "\n"
+ "We suggest you try uninstall '%ls'.\n"
+ "\n"
+ "See also https://support.microsoft.com/en-us/kb/316911 "),
+ wszProductName, wszProductCode, wszComponentCode, wszProductName);
+ fSetError = true;
+ }
+ }
+ }
+
+ /* MSI uses COM and may mess up our stuff. So, we wait with the fallback till afterwards in this case. */
+ if (!fSetError)
+ {
+ setError(hrcCaller,
+ tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, CLSID_VirtualBox w/ IUnknown works.\n"
+ "PSDispatch looks broken by installer %ls featuring the broken oleaut32.msm module as component %ls.\n"
+ "\n"
+ "See also https://support.microsoft.com/en-us/kb/316911 "),
+ wszProductCode, wszComponentCode);
+ fSetError = true;
+ }
+ }
+ }
+ if (!fSetError)
+ setError(hrcCaller, tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, CLSID_VirtualBox w/ IUnknown works.\n"
+ "PSDispatch looks broken by some installer featuring the broken oleaut32.msm module as a component.\n"
+ "\n"
+ "See also https://support.microsoft.com/en-us/kb/316911 "));
+ }
+ else if (lrc == ERROR_FILE_NOT_FOUND)
+ setError(hrcCaller, tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.\n"
+ "PSDispatch looks fine. Weird"));
+ else
+ setError(hrcCaller, tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.\n"
+ "Checking out PSDispatch registration ended with error: %u (%#x)"), lrc, lrc);
+ RegCloseKey(hKey);
+ }
+
+ pUnknown->Release();
+ return hrcCaller;
+}
+
+# ifdef VBOX_WITH_SDS
+/**
+ * Gets the service account name and start type for the given service.
+ *
+ * @returns IPRT status code (for some reason).
+ * @param pwszServiceName The name of the service.
+ * @param pwszAccountName Where to return the account name.
+ * @param cwcAccountName The length of the account name buffer (in WCHARs).
+ * @param puStartType Where to return the start type.
+ */
+int VirtualBoxClient::i_getServiceAccountAndStartType(const wchar_t *pwszServiceName,
+ wchar_t *pwszAccountName, size_t cwcAccountName, uint32_t *puStartType)
+{
+ AssertPtr(pwszServiceName);
+ AssertPtr(pwszAccountName);
+ Assert(cwcAccountName);
+ *pwszAccountName = '\0';
+ *puStartType = SERVICE_DEMAND_START;
+
+ int vrc;
+
+ // Get a handle to the SCM database.
+ SC_HANDLE hSCManager = OpenSCManagerW(NULL /*pwszMachineName*/, NULL /*pwszDatabaseName*/, SC_MANAGER_CONNECT);
+ if (hSCManager != NULL)
+ {
+ SC_HANDLE hService = OpenServiceW(hSCManager, pwszServiceName, SERVICE_QUERY_CONFIG);
+ if (hService != NULL)
+ {
+ DWORD cbNeeded = sizeof(QUERY_SERVICE_CONFIGW) + _1K;
+ if (!QueryServiceConfigW(hService, NULL, 0, &cbNeeded))
+ {
+ Assert(GetLastError() == ERROR_INSUFFICIENT_BUFFER);
+ LPQUERY_SERVICE_CONFIGW pSc = (LPQUERY_SERVICE_CONFIGW)RTMemTmpAllocZ(cbNeeded + _1K);
+ if (pSc)
+ {
+ DWORD cbNeeded2 = 0;
+ if (QueryServiceConfigW(hService, pSc, cbNeeded + _1K, &cbNeeded2))
+ {
+ *puStartType = pSc->dwStartType;
+ vrc = RTUtf16Copy(pwszAccountName, cwcAccountName, pSc->lpServiceStartName);
+ if (RT_FAILURE(vrc))
+ LogRel(("Error: SDS service name is too long (%Rrc): %ls\n", vrc, pSc->lpServiceStartName));
+ }
+ else
+ {
+ int dwError = GetLastError();
+ vrc = RTErrConvertFromWin32(dwError);
+ LogRel(("Error: Failed querying '%ls' service config: %Rwc (%u) -> %Rrc; cbNeeded=%d cbNeeded2=%d\n",
+ pwszServiceName, dwError, dwError, vrc, cbNeeded, cbNeeded2));
+ }
+ RTMemTmpFree(pSc);
+ }
+ else
+ {
+ LogRel(("Error: Failed allocating %#x bytes of memory for service config!\n", cbNeeded + _1K));
+ vrc = VERR_NO_TMP_MEMORY;
+ }
+ }
+ else
+ {
+ AssertLogRelMsgFailed(("Error: QueryServiceConfigW returns success with zero buffer!\n"));
+ vrc = VERR_IPE_UNEXPECTED_STATUS;
+ }
+ CloseServiceHandle(hService);
+ }
+ else
+ {
+ int dwError = GetLastError();
+ vrc = RTErrConvertFromWin32(dwError);
+ LogRel(("Error: Could not open service '%ls': %Rwc (%u) -> %Rrc\n", pwszServiceName, dwError, dwError, vrc));
+ }
+ CloseServiceHandle(hSCManager);
+ }
+ else
+ {
+ int dwError = GetLastError();
+ vrc = RTErrConvertFromWin32(dwError);
+ LogRel(("Error: Could not open SCM: %Rwc (%u) -> %Rrc\n", dwError, dwError, vrc));
+ }
+ return vrc;
+}
+# endif /* VBOX_WITH_SDS */
+
+#endif /* RT_OS_WINDOWS */
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void VirtualBoxClient::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ {
+ LogFlowThisFunc(("already done\n"));
+ return;
+ }
+
+#ifdef VBOX_WITH_MAIN_NLS
+ i_unregisterEventListener();
+#endif
+
+ if (mData.m_ThreadWatcher != NIL_RTTHREAD)
+ {
+ /* Signal the event semaphore and wait for the thread to terminate.
+ * if it hangs for some reason exit anyway, this can cause a crash
+ * though as the object will no longer be available. */
+ RTSemEventSignal(mData.m_SemEvWatcher);
+ RTThreadWait(mData.m_ThreadWatcher, 30000, NULL);
+ mData.m_ThreadWatcher = NIL_RTTHREAD;
+ RTSemEventDestroy(mData.m_SemEvWatcher);
+ mData.m_SemEvWatcher = NIL_RTSEMEVENT;
+ }
+#ifdef VBOX_WITH_MAIN_NLS
+ if (mData.m_pVBoxTranslator != NULL)
+ {
+ mData.m_pVBoxTranslator->release();
+ mData.m_pVBoxTranslator = NULL;
+ mData.m_pTrComponent = NULL;
+ }
+#endif
+ mData.m_pToken.setNull();
+ mData.m_pVirtualBox.setNull();
+
+ ASMAtomicDecU32(&g_cInstances);
+
+ LogFlowThisFunc(("returns\n"));
+}
+
+// IVirtualBoxClient properties
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns a reference to the VirtualBox object.
+ *
+ * @returns COM status code
+ * @param aVirtualBox Address of result variable.
+ */
+HRESULT VirtualBoxClient::getVirtualBox(ComPtr<IVirtualBox> &aVirtualBox)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aVirtualBox = mData.m_pVirtualBox;
+ return S_OK;
+}
+
+/**
+ * Create a new Session object and return a reference to it.
+ *
+ * @returns COM status code
+ * @param aSession Address of result variable.
+ */
+HRESULT VirtualBoxClient::getSession(ComPtr<ISession> &aSession)
+{
+ /* this is not stored in this object, no need to lock */
+ ComPtr<ISession> pSession;
+ HRESULT hrc = pSession.createInprocObject(CLSID_Session);
+ if (SUCCEEDED(hrc))
+ aSession = pSession;
+ return hrc;
+}
+
+/**
+ * Return reference to the EventSource associated with this object.
+ *
+ * @returns COM status code
+ * @param aEventSource Address of result variable.
+ */
+HRESULT VirtualBoxClient::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ /* this is const, no need to lock */
+ aEventSource = mData.m_pEventSource;
+ return aEventSource.isNull() ? E_FAIL : S_OK;
+}
+
+// IVirtualBoxClient methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Checks a Machine object for any pending errors.
+ *
+ * @returns COM status code
+ * @param aMachine Machine object to check.
+ */
+HRESULT VirtualBoxClient::checkMachineError(const ComPtr<IMachine> &aMachine)
+{
+ BOOL fAccessible = FALSE;
+ HRESULT hrc = aMachine->COMGETTER(Accessible)(&fAccessible);
+ if (FAILED(hrc))
+ return setError(hrc, tr("Could not check the accessibility status of the VM"));
+ else if (!fAccessible)
+ {
+ ComPtr<IVirtualBoxErrorInfo> pAccessError;
+ hrc = aMachine->COMGETTER(AccessError)(pAccessError.asOutParam());
+ if (FAILED(hrc))
+ return setError(hrc, tr("Could not get the access error message of the VM"));
+ else
+ {
+ ErrorInfo info(pAccessError);
+ ErrorInfoKeeper eik(info);
+ return info.getResultCode();
+ }
+ }
+ return S_OK;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+
+/// @todo AM Add pinging of VBoxSDS
+/*static*/
+DECLCALLBACK(int) VirtualBoxClient::SVCWatcherThread(RTTHREAD ThreadSelf,
+ void *pvUser)
+{
+ NOREF(ThreadSelf);
+ Assert(pvUser);
+ VirtualBoxClient *pThis = (VirtualBoxClient *)pvUser;
+ RTSEMEVENT sem = pThis->mData.m_SemEvWatcher;
+ RTMSINTERVAL cMillies = VBOXCLIENT_DEFAULT_INTERVAL;
+
+ /* The likelihood of early crashes are high, so start with a short wait. */
+ int vrc = RTSemEventWait(sem, cMillies / 2);
+
+ /* As long as the waiting times out keep retrying the wait. */
+ while (RT_FAILURE(vrc))
+ {
+ {
+ HRESULT hrc = S_OK;
+ ComPtr<IVirtualBox> pV;
+ {
+ AutoReadLock alock(pThis COMMA_LOCKVAL_SRC_POS);
+ pV = pThis->mData.m_pVirtualBox;
+ }
+ if (!pV.isNull())
+ {
+ ULONG rev;
+ hrc = pV->COMGETTER(Revision)(&rev);
+ if (FAILED_DEAD_INTERFACE(hrc))
+ {
+ LogRel(("VirtualBoxClient: detected unresponsive VBoxSVC (hrc=%Rhrc)\n", hrc));
+ {
+ AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
+ /* Throw away the VirtualBox reference, it's no longer
+ * usable as VBoxSVC terminated in the mean time. */
+ pThis->mData.m_pVirtualBox.setNull();
+ }
+ ::FireVBoxSVCAvailabilityChangedEvent(pThis->mData.m_pEventSource, FALSE);
+ }
+ }
+ else
+ {
+ /* Try to get a new VirtualBox reference straight away, and if
+ * this fails use an increased waiting time as very frequent
+ * restart attempts in some wedged config can cause high CPU
+ * and disk load. */
+ ComPtr<IVirtualBox> pVirtualBox;
+ ComPtr<IToken> pToken;
+ hrc = pVirtualBox.createLocalObject(CLSID_VirtualBox);
+ if (FAILED(hrc))
+ cMillies = 3 * VBOXCLIENT_DEFAULT_INTERVAL;
+ else
+ {
+ LogRel(("VirtualBoxClient: detected working VBoxSVC (hrc=%Rhrc)\n", hrc));
+ {
+ AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
+ /* Update the VirtualBox reference, there's a working
+ * VBoxSVC again from now on. */
+ pThis->mData.m_pVirtualBox = pVirtualBox;
+ pThis->mData.m_pToken = pToken;
+#ifdef VBOX_WITH_MAIN_NLS
+ /* update language using new instance of IVirtualBox in case the language settings was changed */
+ pThis->i_reloadApiLanguage();
+ pThis->i_registerEventListener();
+#endif
+ }
+ ::FireVBoxSVCAvailabilityChangedEvent(pThis->mData.m_pEventSource, TRUE);
+ cMillies = VBOXCLIENT_DEFAULT_INTERVAL;
+ }
+ }
+ }
+ vrc = RTSemEventWait(sem, cMillies);
+ }
+ return 0;
+}
+
+#ifdef VBOX_WITH_MAIN_NLS
+
+HRESULT VirtualBoxClient::i_reloadApiLanguage()
+{
+ if (mData.m_pVBoxTranslator == NULL)
+ return S_OK;
+
+ HRESULT hrc = mData.m_pVBoxTranslator->loadLanguage(mData.m_pVirtualBox);
+ if (FAILED(hrc))
+ setError(hrc, tr("Failed to load user language instance"));
+ return hrc;
+}
+
+HRESULT VirtualBoxClient::i_registerEventListener()
+{
+ HRESULT hrc = mData.m_pVirtualBox->COMGETTER(EventSource)(mData.m_pVBoxEventSource.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ ComObjPtr<VBoxEventListenerImpl> pVBoxListener;
+ pVBoxListener.createObject();
+ pVBoxListener->init(new VBoxEventListener());
+ mData.m_pVBoxEventListener = pVBoxListener;
+ com::SafeArray<VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnLanguageChanged);
+ hrc = mData.m_pVBoxEventSource->RegisterListener(pVBoxListener, ComSafeArrayAsInParam(eventTypes), true);
+ if (FAILED(hrc))
+ {
+ hrc = setError(hrc, tr("Failed to register listener"));
+ mData.m_pVBoxEventListener.setNull();
+ mData.m_pVBoxEventSource.setNull();
+ }
+ }
+ else
+ hrc = setError(hrc, tr("Failed to get event source from VirtualBox"));
+ return hrc;
+}
+
+void VirtualBoxClient::i_unregisterEventListener()
+{
+ if (mData.m_pVBoxEventListener.isNotNull())
+ {
+ if (mData.m_pVBoxEventSource.isNotNull())
+ mData.m_pVBoxEventSource->UnregisterListener(mData.m_pVBoxEventListener);
+ mData.m_pVBoxEventListener.setNull();
+ }
+ mData.m_pVBoxEventSource.setNull();
+}
+
+#endif /* VBOX_WITH_MAIN_NLS */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/WebMWriter.cpp b/src/VBox/Main/src-client/WebMWriter.cpp
new file mode 100644
index 00000000..6361f13e
--- /dev/null
+++ b/src/VBox/Main/src-client/WebMWriter.cpp
@@ -0,0 +1,881 @@
+/* $Id: WebMWriter.cpp $ */
+/** @file
+ * WebMWriter.cpp - WebM container handling.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**
+ * For more information, see:
+ * - https://w3c.github.io/media-source/webm-byte-stream-format.html
+ * - https://www.webmproject.org/docs/container/#muxer-guidelines
+ */
+
+#define LOG_GROUP LOG_GROUP_RECORDING
+#include "LoggingNew.h"
+
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+
+#include <VBox/version.h>
+
+#include "RecordingInternals.h"
+#include "WebMWriter.h"
+
+
+WebMWriter::WebMWriter(void)
+{
+ /* Size (in bytes) of time code to write. We use 2 bytes (16 bit) by default. */
+ m_cbTimecode = 2;
+ m_uTimecodeMax = UINT16_MAX;
+
+ m_fInTracksSection = false;
+}
+
+WebMWriter::~WebMWriter(void)
+{
+ Close();
+}
+
+/**
+ * Opens (creates) an output file using an already open file handle.
+ *
+ * @returns VBox status code.
+ * @param a_pszFilename Name of the file the file handle points at.
+ * @param a_phFile Pointer to open file handle to use.
+ * @param a_enmAudioCodec Audio codec to use.
+ * @param a_enmVideoCodec Video codec to use.
+ */
+int WebMWriter::OpenEx(const char *a_pszFilename, PRTFILE a_phFile,
+ RecordingAudioCodec_T a_enmAudioCodec, RecordingVideoCodec_T a_enmVideoCodec)
+{
+ try
+ {
+ LogFunc(("Creating '%s'\n", a_pszFilename));
+
+ int vrc = createEx(a_pszFilename, a_phFile);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = init(a_enmAudioCodec, a_enmVideoCodec);
+ if (RT_SUCCESS(vrc))
+ vrc = writeHeader();
+ }
+ }
+ catch(int vrc)
+ {
+ return vrc;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Opens an output file.
+ *
+ * @returns VBox status code.
+ * @param a_pszFilename Name of the file to create.
+ * @param a_fOpen File open mode of type RTFILE_O_.
+ * @param a_enmAudioCodec Audio codec to use.
+ * @param a_enmVideoCodec Video codec to use.
+ */
+int WebMWriter::Open(const char *a_pszFilename, uint64_t a_fOpen,
+ RecordingAudioCodec_T a_enmAudioCodec, RecordingVideoCodec_T a_enmVideoCodec)
+{
+ try
+ {
+ LogFunc(("Creating '%s'\n", a_pszFilename));
+
+ int vrc = create(a_pszFilename, a_fOpen);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = init(a_enmAudioCodec, a_enmVideoCodec);
+ if (RT_SUCCESS(vrc))
+ vrc = writeHeader();
+ }
+ }
+ catch(int vrc)
+ {
+ return vrc;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Closes the WebM file and drains all queues.
+ *
+ * @returns VBox status code.
+ */
+int WebMWriter::Close(void)
+{
+ LogFlowFuncEnter();
+
+ if (!isOpen())
+ return VINF_SUCCESS;
+
+ /* Make sure to drain all queues. */
+ processQueue(&m_CurSeg.m_queueBlocks, true /* fForce */);
+
+ writeFooter();
+
+ WebMTracks::iterator itTrack = m_CurSeg.m_mapTracks.begin();
+ while (itTrack != m_CurSeg.m_mapTracks.end())
+ {
+ WebMTrack *pTrack = itTrack->second;
+ if (pTrack) /* Paranoia. */
+ delete pTrack;
+
+ m_CurSeg.m_mapTracks.erase(itTrack);
+
+ itTrack = m_CurSeg.m_mapTracks.begin();
+ }
+
+ Assert(m_CurSeg.m_queueBlocks.Map.size() == 0);
+ Assert(m_CurSeg.m_mapTracks.size() == 0);
+
+ com::Utf8Str strFileName = getFileName().c_str();
+
+ close();
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Adds an audio track.
+ *
+ * @returns VBox status code.
+ * @param pCodec Audio codec to use.
+ * @param uHz Input sampling rate.
+ * Must be supported by the selected audio codec.
+ * @param cChannels Number of input audio channels.
+ * @param cBits Number of input bits per channel.
+ * @param puTrack Track number on successful creation. Optional.
+ */
+int WebMWriter::AddAudioTrack(PRECORDINGCODEC pCodec, uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack)
+{
+ AssertReturn(uHz, VERR_INVALID_PARAMETER);
+ AssertReturn(cBits, VERR_INVALID_PARAMETER);
+ AssertReturn(cChannels, VERR_INVALID_PARAMETER);
+
+ /* Some players (e.g. Firefox with Nestegg) rely on track numbers starting at 1.
+ * Using a track number 0 will show those files as being corrupted. */
+ const uint8_t uTrack = (uint8_t)m_CurSeg.m_mapTracks.size() + 1;
+
+ subStart(MkvElem_TrackEntry);
+
+ serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
+ serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
+ serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
+
+ int vrc = VINF_SUCCESS;
+
+ WebMTrack *pTrack = NULL;
+ try
+ {
+ pTrack = new WebMTrack(WebMTrackType_Audio, pCodec, uTrack, RTFileTell(getFile()));
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4)
+ .serializeUnsignedInteger(MkvElem_TrackType, 2 /* Audio */);
+
+ switch (m_enmAudioCodec)
+ {
+# ifdef VBOX_WITH_LIBVORBIS
+ case RecordingAudioCodec_OggVorbis:
+ {
+ pTrack->Audio.msPerBlock = 0; /** @todo */
+ if (!pTrack->Audio.msPerBlock) /* No ms per frame defined? Use default. */
+ pTrack->Audio.msPerBlock = VBOX_RECORDING_VORBIS_FRAME_MS_DEFAULT;
+
+ vorbis_comment vc;
+ vorbis_comment_init(&vc);
+ vorbis_comment_add_tag(&vc,"ENCODER", vorbis_version_string());
+
+ ogg_packet pkt_ident;
+ ogg_packet pkt_comments;
+ ogg_packet pkt_setup;
+ vorbis_analysis_headerout(&pCodec->Audio.Vorbis.dsp_state, &vc, &pkt_ident, &pkt_comments, &pkt_setup);
+ AssertMsgBreakStmt(pkt_ident.bytes <= 255 && pkt_comments.bytes <= 255,
+ ("Too long header / comment packets\n"), vrc = VERR_INVALID_PARAMETER);
+
+ WEBMOGGVORBISPRIVDATA vorbisPrivData(pkt_ident.bytes, pkt_comments.bytes, pkt_setup.bytes);
+
+ uint8_t *pabHdr = &vorbisPrivData.abHdr[0];
+ memcpy(pabHdr, pkt_ident.packet, pkt_ident.bytes);
+ pabHdr += pkt_ident.bytes;
+ memcpy(pabHdr, pkt_comments.packet, pkt_comments.bytes);
+ pabHdr += pkt_comments.bytes;
+ memcpy(pabHdr, pkt_setup.packet, pkt_setup.bytes);
+ pabHdr += pkt_setup.bytes;
+
+ vorbis_comment_clear(&vc);
+
+ size_t const offHeaders = RT_OFFSETOF(WEBMOGGVORBISPRIVDATA, abHdr);
+
+ serializeString(MkvElem_CodecID, "A_VORBIS");
+ serializeData(MkvElem_CodecPrivate, &vorbisPrivData,
+ offHeaders + pkt_ident.bytes + pkt_comments.bytes + pkt_setup.bytes);
+ break;
+ }
+# endif /* VBOX_WITH_LIBVORBIS */
+ default:
+ AssertFailedStmt(vrc = VERR_NOT_SUPPORTED); /* Shouldn't ever happen (tm). */
+ break;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ serializeUnsignedInteger(MkvElem_CodecDelay, 0)
+ .serializeUnsignedInteger(MkvElem_SeekPreRoll, 80 * 1000000) /* 80ms in ns. */
+ .subStart(MkvElem_Audio)
+ .serializeFloat(MkvElem_SamplingFrequency, (float)uHz)
+ .serializeUnsignedInteger(MkvElem_Channels, cChannels)
+ .serializeUnsignedInteger(MkvElem_BitDepth, cBits)
+ .subEnd(MkvElem_Audio)
+ .subEnd(MkvElem_TrackEntry);
+
+ pTrack->Audio.uHz = uHz;
+ pTrack->Audio.framesPerBlock = uHz / (1000 /* s in ms */ / pTrack->Audio.msPerBlock);
+
+ LogRel2(("Recording: WebM track #%RU8: Audio codec @ %RU16Hz (%RU16ms, %RU16 frames per block)\n",
+ pTrack->uTrack, pTrack->Audio.uHz, pTrack->Audio.msPerBlock, pTrack->Audio.framesPerBlock));
+
+ m_CurSeg.m_mapTracks[uTrack] = pTrack;
+
+ if (puTrack)
+ *puTrack = uTrack;
+
+ return VINF_SUCCESS;
+ }
+ }
+
+ if (pTrack)
+ delete pTrack;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Adds a video track.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec data to use.
+ * @param uWidth Width (in pixels) of the video track.
+ * @param uHeight Height (in pixels) of the video track.
+ * @param uFPS FPS (Frames Per Second) of the video track.
+ * @param puTrack Track number of the added video track on success. Optional.
+ */
+int WebMWriter::AddVideoTrack(PRECORDINGCODEC pCodec, uint16_t uWidth, uint16_t uHeight, uint32_t uFPS, uint8_t *puTrack)
+{
+#ifdef VBOX_WITH_LIBVPX
+ /* Some players (e.g. Firefox with Nestegg) rely on track numbers starting at 1.
+ * Using a track number 0 will show those files as being corrupted. */
+ const uint8_t uTrack = (uint8_t)m_CurSeg.m_mapTracks.size() + 1;
+
+ subStart(MkvElem_TrackEntry);
+
+ serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
+ serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
+ serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
+
+ WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, pCodec, uTrack, RTFileTell(getFile()));
+
+ /** @todo Resolve codec type. */
+ serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID /* UID */, 4)
+ .serializeUnsignedInteger(MkvElem_TrackType, 1 /* Video */)
+ .serializeString(MkvElem_CodecID, "V_VP8")
+ .subStart(MkvElem_Video)
+ .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth)
+ .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight)
+ /* Some players rely on the FPS rate for timing calculations.
+ * So make sure to *always* include that. */
+ .serializeFloat (MkvElem_FrameRate, (float)uFPS)
+ .subEnd(MkvElem_Video);
+
+ subEnd(MkvElem_TrackEntry);
+
+ LogRel2(("Recording: WebM track #%RU8: Video\n", pTrack->uTrack));
+
+ m_CurSeg.m_mapTracks[uTrack] = pTrack;
+
+ if (puTrack)
+ *puTrack = uTrack;
+
+ return VINF_SUCCESS;
+#else
+ RT_NOREF(pCodec, uWidth, uHeight, uFPS, puTrack);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+/**
+ * Gets file name.
+ *
+ * @returns File name as UTF-8 string.
+ */
+const com::Utf8Str& WebMWriter::GetFileName(void)
+{
+ return getFileName();
+}
+
+/**
+ * Gets current output file size.
+ *
+ * @returns File size in bytes.
+ */
+uint64_t WebMWriter::GetFileSize(void)
+{
+ return getFileSize();
+}
+
+/**
+ * Gets current free storage space available for the file.
+ *
+ * @returns Available storage free space.
+ */
+uint64_t WebMWriter::GetAvailableSpace(void)
+{
+ return getAvailableSpace();
+}
+
+/**
+ * Takes care of the initialization of the instance.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NOT_SUPPORTED if a given codec is not supported.
+ * @param enmAudioCodec Audio codec to use.
+ * @param enmVideoCodec Video codec to use.
+ */
+int WebMWriter::init(RecordingAudioCodec_T enmAudioCodec, RecordingVideoCodec_T enmVideoCodec)
+{
+#ifndef VBOX_WITH_LIBVORBIS
+ AssertReturn(enmAudioCodec != RecordingAudioCodec_OggVorbis, VERR_NOT_SUPPORTED);
+#endif
+ AssertReturn( enmVideoCodec == RecordingVideoCodec_None
+ || enmVideoCodec == RecordingVideoCodec_VP8, VERR_NOT_SUPPORTED);
+
+ m_enmAudioCodec = enmAudioCodec;
+ m_enmVideoCodec = enmVideoCodec;
+
+ return m_CurSeg.init();
+}
+
+/**
+ * Takes care of the destruction of the instance.
+ */
+void WebMWriter::destroy(void)
+{
+ m_CurSeg.uninit();
+}
+
+/**
+ * Writes the WebM file header.
+ *
+ * @returns VBox status code.
+ */
+int WebMWriter::writeHeader(void)
+{
+ LogFunc(("Header @ %RU64\n", RTFileTell(getFile())));
+
+ subStart(MkvElem_EBML)
+ .serializeUnsignedInteger(MkvElem_EBMLVersion, 1)
+ .serializeUnsignedInteger(MkvElem_EBMLReadVersion, 1)
+ .serializeUnsignedInteger(MkvElem_EBMLMaxIDLength, 4)
+ .serializeUnsignedInteger(MkvElem_EBMLMaxSizeLength, 8)
+ .serializeString(MkvElem_DocType, "webm")
+ .serializeUnsignedInteger(MkvElem_DocTypeVersion, 2)
+ .serializeUnsignedInteger(MkvElem_DocTypeReadVersion, 2)
+ .subEnd(MkvElem_EBML);
+
+ subStart(MkvElem_Segment);
+
+ /* Save offset of current segment. */
+ m_CurSeg.m_offStart = RTFileTell(getFile());
+
+ writeSeekHeader();
+
+ /* Save offset of upcoming tracks segment. */
+ m_CurSeg.m_offTracks = RTFileTell(getFile());
+
+ /* The tracks segment starts right after this header. */
+ subStart(MkvElem_Tracks);
+ m_fInTracksSection = true;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes a simple block into the EBML structure.
+ *
+ * @returns VBox status code.
+ * @param a_pTrack Track the simple block is assigned to.
+ * @param a_pBlock Simple block to write.
+ */
+int WebMWriter::writeSimpleBlockEBML(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock)
+{
+#ifdef LOG_ENABLED
+ WebMCluster &Cluster = m_CurSeg.m_CurCluster;
+
+ Log3Func(("[T%RU8C%RU64] Off=%RU64, AbsPTSMs=%RU64, RelToClusterMs=%RU16, %zu bytes\n",
+ a_pTrack->uTrack, Cluster.uID, RTFileTell(getFile()),
+ a_pBlock->Data.tcAbsPTSMs, a_pBlock->Data.tcRelToClusterMs, a_pBlock->Data.cb));
+#endif
+ /*
+ * Write a "Simple Block".
+ */
+ writeClassId(MkvElem_SimpleBlock);
+ /* Block size. */
+ writeUnsignedInteger(0x10000000u | ( 1 /* Track number size. */
+ + m_cbTimecode /* Timecode size .*/
+ + 1 /* Flags size. */
+ + a_pBlock->Data.cb /* Actual frame data size. */), 4);
+ /* Track number. */
+ writeSize(a_pTrack->uTrack);
+ /* Timecode (relative to cluster opening timecode). */
+ writeUnsignedInteger(a_pBlock->Data.tcRelToClusterMs, m_cbTimecode);
+ /* Flags. */
+ writeUnsignedInteger(a_pBlock->Data.fFlags, 1);
+ /* Frame data. */
+ write(a_pBlock->Data.pv, a_pBlock->Data.cb);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes a simple block and enqueues it into the segment's render queue.
+ *
+ * @returns VBox status code.
+ * @param a_pTrack Track the simple block is assigned to.
+ * @param a_pBlock Simple block to write and enqueue.
+ */
+int WebMWriter::writeSimpleBlockQueued(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock)
+{
+ RT_NOREF(a_pTrack);
+
+ int vrc = VINF_SUCCESS;
+
+ try
+ {
+ const WebMTimecodeAbs tcAbsPTS = a_pBlock->Data.tcAbsPTSMs;
+
+ /* See if we already have an entry for the specified timecode in our queue. */
+ WebMBlockMap::iterator itQueue = m_CurSeg.m_queueBlocks.Map.find(tcAbsPTS);
+ if (itQueue != m_CurSeg.m_queueBlocks.Map.end()) /* Use existing queue. */
+ {
+ WebMTimecodeBlocks &Blocks = itQueue->second;
+ Blocks.Enqueue(a_pBlock);
+ }
+ else /* Create a new timecode entry. */
+ {
+ WebMTimecodeBlocks Blocks;
+ Blocks.Enqueue(a_pBlock);
+
+ m_CurSeg.m_queueBlocks.Map[tcAbsPTS] = Blocks;
+ }
+
+ vrc = processQueue(&m_CurSeg.m_queueBlocks, false /* fForce */);
+ }
+ catch(...)
+ {
+ delete a_pBlock;
+ a_pBlock = NULL;
+
+ vrc = VERR_NO_MEMORY;
+ }
+
+ return vrc;
+}
+
+/**
+ * Writes a data block to the specified track.
+ *
+ * @returns VBox status code.
+ * @param uTrack Track ID to write data to.
+ * @param pvData Pointer to data block to write.
+ * @param cbData Size (in bytes) of data block to write.
+ * @param tcAbsPTSMs Absolute PTS of simple data block.
+ * @param uFlags WebM block flags to use for this block.
+ */
+int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData, WebMTimecodeAbs tcAbsPTSMs, WebMBlockFlags uFlags)
+{
+ int vrc = RTCritSectEnter(&m_CurSeg.m_CritSect);
+ AssertRC(vrc);
+
+ WebMTracks::iterator itTrack = m_CurSeg.m_mapTracks.find(uTrack);
+ if (itTrack == m_CurSeg.m_mapTracks.end())
+ {
+ RTCritSectLeave(&m_CurSeg.m_CritSect);
+ return VERR_NOT_FOUND;
+ }
+
+ WebMTrack *pTrack = itTrack->second;
+ AssertPtr(pTrack);
+
+ if (m_fInTracksSection)
+ {
+ subEnd(MkvElem_Tracks);
+ m_fInTracksSection = false;
+ }
+
+ try
+ {
+ vrc = writeSimpleBlockQueued(pTrack,
+ new WebMSimpleBlock(pTrack,
+ tcAbsPTSMs, pvData, cbData, uFlags));
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ int vrc2 = RTCritSectLeave(&m_CurSeg.m_CritSect);
+ AssertRC(vrc2);
+
+ return vrc;
+}
+
+/**
+ * Processes a render queue.
+ *
+ * @returns VBox status code.
+ * @param pQueue Queue to process.
+ * @param fForce Whether forcing to process the render queue or not.
+ * Needed to drain the queues when terminating.
+ */
+int WebMWriter::processQueue(WebMQueue *pQueue, bool fForce)
+{
+ if (pQueue->tsLastProcessedMs == 0)
+ pQueue->tsLastProcessedMs = RTTimeMilliTS();
+
+ if (!fForce)
+ {
+ /* Only process when we reached a certain threshold. */
+ if (RTTimeMilliTS() - pQueue->tsLastProcessedMs < 5000 /* ms */ /** @todo Make this configurable */)
+ return VINF_SUCCESS;
+ }
+
+ WebMCluster &Cluster = m_CurSeg.m_CurCluster;
+
+ /* Iterate through the block map. */
+ WebMBlockMap::iterator it = pQueue->Map.begin();
+ while (it != m_CurSeg.m_queueBlocks.Map.end())
+ {
+ WebMTimecodeAbs mapAbsPTSMs = it->first;
+ WebMTimecodeBlocks mapBlocks = it->second;
+
+ /* Whether to start a new cluster or not. */
+ bool fClusterStart = false;
+
+ /* If the current segment does not have any clusters (yet),
+ * take the first absolute PTS as the starting point for that segment. */
+ if (m_CurSeg.m_cClusters == 0)
+ {
+ m_CurSeg.m_tcAbsStartMs = mapAbsPTSMs;
+ fClusterStart = true;
+ }
+
+ /* Determine if we need to start a new cluster. */
+ /* No blocks written yet? Start a new cluster. */
+ if ( Cluster.cBlocks == 0
+ /* Did we reach the maximum a cluster can hold? Use a new cluster then. */
+ || mapAbsPTSMs - Cluster.tcAbsStartMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS
+ /* If the block map indicates that a cluster is needed for this timecode, create one. */
+ || mapBlocks.fClusterNeeded)
+ {
+ fClusterStart = true;
+ }
+
+ if ( fClusterStart
+ && !mapBlocks.fClusterStarted)
+ {
+ /* Last written timecode of the current cluster. */
+ uint64_t tcAbsClusterLastWrittenMs;
+
+ if (Cluster.fOpen) /* Close current cluster first. */
+ {
+ Log2Func(("[C%RU64] End @ %RU64ms (duration = %RU64ms)\n",
+ Cluster.uID, Cluster.tcAbsLastWrittenMs, Cluster.tcAbsLastWrittenMs - Cluster.tcAbsStartMs));
+
+ /* Make sure that the current cluster contained some data. */
+ Assert(Cluster.offStart);
+ Assert(Cluster.cBlocks);
+
+ /* Save the last written timecode of the current cluster before closing it. */
+ tcAbsClusterLastWrittenMs = Cluster.tcAbsLastWrittenMs;
+
+ subEnd(MkvElem_Cluster);
+ Cluster.fOpen = false;
+ }
+ else /* First cluster ever? Use the segment's starting timecode. */
+ tcAbsClusterLastWrittenMs = m_CurSeg.m_tcAbsStartMs;
+
+ Cluster.fOpen = true;
+ Cluster.uID = m_CurSeg.m_cClusters;
+ /* Use the block map's currently processed TC as the cluster's starting TC. */
+ Cluster.tcAbsStartMs = mapAbsPTSMs;
+ Cluster.tcAbsLastWrittenMs = Cluster.tcAbsStartMs;
+ Cluster.offStart = RTFileTell(getFile());
+ Cluster.cBlocks = 0;
+
+ AssertMsg(Cluster.tcAbsStartMs <= mapAbsPTSMs,
+ ("Cluster #%RU64 start TC (%RU64) must not bigger than the block map's currently processed TC (%RU64)\n",
+ Cluster.uID, Cluster.tcAbsStartMs, mapAbsPTSMs));
+
+ Log2Func(("[C%RU64] Start @ %RU64ms (map TC is %RU64) / %RU64 bytes\n",
+ Cluster.uID, Cluster.tcAbsStartMs, mapAbsPTSMs, Cluster.offStart));
+
+ /* Insert cue points for all tracks if a new cluster has been started. */
+ WebMCuePoint *pCuePoint = new WebMCuePoint(Cluster.tcAbsStartMs);
+
+ WebMTracks::iterator itTrack = m_CurSeg.m_mapTracks.begin();
+ while (itTrack != m_CurSeg.m_mapTracks.end())
+ {
+ pCuePoint->Pos[itTrack->first] = new WebMCueTrackPosEntry(Cluster.offStart);
+ ++itTrack;
+ }
+
+ m_CurSeg.m_lstCuePoints.push_back(pCuePoint);
+
+ subStart(MkvElem_Cluster)
+ .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcAbsStartMs - m_CurSeg.m_tcAbsStartMs);
+
+ m_CurSeg.m_cClusters++;
+
+ mapBlocks.fClusterStarted = true;
+ }
+
+ Log2Func(("[C%RU64] SegTcAbsStartMs=%RU64, ClusterTcAbsStartMs=%RU64, ClusterTcAbsLastWrittenMs=%RU64, mapAbsPTSMs=%RU64\n",
+ Cluster.uID, m_CurSeg.m_tcAbsStartMs, Cluster.tcAbsStartMs, Cluster.tcAbsLastWrittenMs, mapAbsPTSMs));
+
+ /* Iterate through all blocks related to the current timecode. */
+ while (!mapBlocks.Queue.empty())
+ {
+ WebMSimpleBlock *pBlock = mapBlocks.Queue.front();
+ AssertPtr(pBlock);
+
+ WebMTrack *pTrack = pBlock->pTrack;
+ AssertPtr(pTrack);
+
+ /* Calculate the block's relative time code to the current cluster's starting time code. */
+ Assert(pBlock->Data.tcAbsPTSMs >= Cluster.tcAbsStartMs);
+ pBlock->Data.tcRelToClusterMs = pBlock->Data.tcAbsPTSMs - Cluster.tcAbsStartMs;
+
+ int vrc2 = writeSimpleBlockEBML(pTrack, pBlock);
+ AssertRC(vrc2);
+
+ Cluster.cBlocks++;
+ Cluster.tcAbsLastWrittenMs = pBlock->Data.tcAbsPTSMs;
+
+ pTrack->cTotalBlocks++;
+ pTrack->tcAbsLastWrittenMs = Cluster.tcAbsLastWrittenMs;
+
+ if (m_CurSeg.m_tcAbsLastWrittenMs < pTrack->tcAbsLastWrittenMs)
+ m_CurSeg.m_tcAbsLastWrittenMs = pTrack->tcAbsLastWrittenMs;
+
+ /* Save a cue point if this is a keyframe (if no new cluster has been started,
+ * as this implies that a cue point already is present. */
+ if ( !fClusterStart
+ && (pBlock->Data.fFlags & VBOX_WEBM_BLOCK_FLAG_KEY_FRAME))
+ {
+ /* Insert cue points for all tracks if a new cluster has been started. */
+ WebMCuePoint *pCuePoint = new WebMCuePoint(Cluster.tcAbsLastWrittenMs);
+
+ WebMTracks::iterator itTrack = m_CurSeg.m_mapTracks.begin();
+ while (itTrack != m_CurSeg.m_mapTracks.end())
+ {
+ pCuePoint->Pos[itTrack->first] = new WebMCueTrackPosEntry(Cluster.offStart);
+ ++itTrack;
+ }
+
+ m_CurSeg.m_lstCuePoints.push_back(pCuePoint);
+ }
+
+ delete pBlock;
+ pBlock = NULL;
+
+ mapBlocks.Queue.pop();
+ }
+
+ Assert(mapBlocks.Queue.empty());
+
+ m_CurSeg.m_queueBlocks.Map.erase(it);
+
+ it = m_CurSeg.m_queueBlocks.Map.begin();
+ }
+
+ Assert(m_CurSeg.m_queueBlocks.Map.empty());
+
+ pQueue->tsLastProcessedMs = RTTimeMilliTS();
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes the WebM footer.
+ *
+ * @returns VBox status code.
+ */
+int WebMWriter::writeFooter(void)
+{
+ AssertReturn(isOpen(), VERR_WRONG_ORDER);
+
+ if (m_fInTracksSection)
+ {
+ subEnd(MkvElem_Tracks);
+ m_fInTracksSection = false;
+ }
+
+ if (m_CurSeg.m_CurCluster.fOpen)
+ {
+ subEnd(MkvElem_Cluster);
+ m_CurSeg.m_CurCluster.fOpen = false;
+ }
+
+ /*
+ * Write Cues element.
+ */
+ m_CurSeg.m_offCues = RTFileTell(getFile());
+ LogFunc(("Cues @ %RU64\n", m_CurSeg.m_offCues));
+
+ subStart(MkvElem_Cues);
+
+ WebMCuePointList::iterator itCuePoint = m_CurSeg.m_lstCuePoints.begin();
+ while (itCuePoint != m_CurSeg.m_lstCuePoints.end())
+ {
+ WebMCuePoint *pCuePoint = (*itCuePoint);
+ AssertPtr(pCuePoint);
+
+ LogFunc(("CuePoint @ %RU64: %zu tracks, tcAbs=%RU64)\n",
+ RTFileTell(getFile()), pCuePoint->Pos.size(), pCuePoint->tcAbs));
+
+ subStart(MkvElem_CuePoint)
+ .serializeUnsignedInteger(MkvElem_CueTime, pCuePoint->tcAbs);
+
+ WebMCueTrackPosMap::iterator itTrackPos = pCuePoint->Pos.begin();
+ while (itTrackPos != pCuePoint->Pos.end())
+ {
+ WebMCueTrackPosEntry *pTrackPos = itTrackPos->second;
+ AssertPtr(pTrackPos);
+
+ LogFunc(("TrackPos (track #%RU32) @ %RU64, offCluster=%RU64)\n",
+ itTrackPos->first, RTFileTell(getFile()), pTrackPos->offCluster));
+
+ subStart(MkvElem_CueTrackPositions)
+ .serializeUnsignedInteger(MkvElem_CueTrack, itTrackPos->first)
+ .serializeUnsignedInteger(MkvElem_CueClusterPosition, pTrackPos->offCluster - m_CurSeg.m_offStart, 8)
+ .subEnd(MkvElem_CueTrackPositions);
+
+ ++itTrackPos;
+ }
+
+ subEnd(MkvElem_CuePoint);
+
+ ++itCuePoint;
+ }
+
+ subEnd(MkvElem_Cues);
+ subEnd(MkvElem_Segment);
+
+ /*
+ * Re-Update seek header with final information.
+ */
+
+ writeSeekHeader();
+
+ return RTFileSeek(getFile(), 0, RTFILE_SEEK_END, NULL);
+}
+
+/**
+ * Writes the segment's seek header.
+ */
+void WebMWriter::writeSeekHeader(void)
+{
+ if (m_CurSeg.m_offSeekInfo)
+ RTFileSeek(getFile(), m_CurSeg.m_offSeekInfo, RTFILE_SEEK_BEGIN, NULL);
+ else
+ m_CurSeg.m_offSeekInfo = RTFileTell(getFile());
+
+ LogFunc(("Seek Header @ %RU64\n", m_CurSeg.m_offSeekInfo));
+
+ subStart(MkvElem_SeekHead);
+
+ subStart(MkvElem_Seek)
+ .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks)
+ .serializeUnsignedInteger(MkvElem_SeekPosition, m_CurSeg.m_offTracks - m_CurSeg.m_offStart, 8)
+ .subEnd(MkvElem_Seek);
+
+ if (m_CurSeg.m_offCues)
+ LogFunc(("Updating Cues @ %RU64\n", m_CurSeg.m_offCues));
+
+ subStart(MkvElem_Seek)
+ .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues)
+ .serializeUnsignedInteger(MkvElem_SeekPosition, m_CurSeg.m_offCues - m_CurSeg.m_offStart, 8)
+ .subEnd(MkvElem_Seek);
+
+ subStart(MkvElem_Seek)
+ .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info)
+ .serializeUnsignedInteger(MkvElem_SeekPosition, m_CurSeg.m_offInfo - m_CurSeg.m_offStart, 8)
+ .subEnd(MkvElem_Seek);
+
+ subEnd(MkvElem_SeekHead);
+
+ /*
+ * Write the segment's info element.
+ */
+
+ /* Save offset of the segment's info element. */
+ m_CurSeg.m_offInfo = RTFileTell(getFile());
+
+ LogFunc(("Info @ %RU64\n", m_CurSeg.m_offInfo));
+
+ char szMux[64];
+ RTStrPrintf(szMux, sizeof(szMux),
+#ifdef VBOX_WITH_LIBVPX
+ "vpxenc%s", vpx_codec_version_str()
+#else
+ "unknown"
+#endif
+ );
+ char szApp[64];
+ RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision());
+
+ const WebMTimecodeAbs tcAbsDurationMs = m_CurSeg.m_tcAbsLastWrittenMs - m_CurSeg.m_tcAbsStartMs;
+
+ if (!m_CurSeg.m_lstCuePoints.empty())
+ {
+ LogFunc(("tcAbsDurationMs=%RU64\n", tcAbsDurationMs));
+ AssertMsg(tcAbsDurationMs, ("Segment seems to be empty (duration is 0)\n"));
+ }
+
+ subStart(MkvElem_Info)
+ .serializeUnsignedInteger(MkvElem_TimecodeScale, m_CurSeg.m_uTimecodeScaleFactor)
+ .serializeFloat(MkvElem_Segment_Duration, tcAbsDurationMs)
+ .serializeString(MkvElem_MuxingApp, szMux)
+ .serializeString(MkvElem_WritingApp, szApp)
+ .subEnd(MkvElem_Info);
+}
diff --git a/src/VBox/Main/src-client/win/Makefile.kup b/src/VBox/Main/src-client/win/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-client/win/Makefile.kup
diff --git a/src/VBox/Main/src-client/win/VBoxC.def b/src/VBox/Main/src-client/win/VBoxC.def
new file mode 100644
index 00000000..b19f0c32
--- /dev/null
+++ b/src/VBox/Main/src-client/win/VBoxC.def
@@ -0,0 +1,37 @@
+;; @file
+;
+; VBoxC DLL Definition File.
+;
+
+;
+; Copyright (C) 2006-2022 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; SPDX-License-Identifier: GPL-3.0-only
+;
+
+LIBRARY VBoxC.dll
+
+EXPORTS
+ ; COM entry points
+ DllGetClassObject PRIVATE
+ DllCanUnloadNow PRIVATE
+ DllRegisterServer PRIVATE
+ DllUnregisterServer PRIVATE
+ ; private entry points
+ VBoxDriversRegister PRIVATE
diff --git a/src/VBox/Main/src-client/win/VBoxC.rc b/src/VBox/Main/src-client/win/VBoxC.rc
new file mode 100644
index 00000000..2937636e
--- /dev/null
+++ b/src/VBox/Main/src-client/win/VBoxC.rc
@@ -0,0 +1,72 @@
+/* $Id: VBoxC.rc $ */
+/** @file
+ * VBoxC - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+#include "win/resource.h"
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4" // Lang=US English, CharSet=Windows Multilingual
+ BEGIN
+ VALUE "FileDescription", "VirtualBox Interface\0"
+ VALUE "InternalName", "VBoxC\0"
+ VALUE "OriginalFilename", "VBoxC.dll\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+
+ VALUE "OLESelfRegister", "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// REGISTRY
+//
+
+//IDR_VIRTUALBOX REGISTRY "VBoxC.rgs"
+
+1 TYPELIB "VirtualBox.tlb"
diff --git a/src/VBox/Main/src-client/win/VBoxClient-x86.def b/src/VBox/Main/src-client/win/VBoxClient-x86.def
new file mode 100644
index 00000000..602cd16a
--- /dev/null
+++ b/src/VBox/Main/src-client/win/VBoxClient-x86.def
@@ -0,0 +1,36 @@
+; $Id: VBoxClient-x86.def $
+;; @file
+; VBoxClient-x86 DLL Definition File.
+;
+
+;
+; Copyright (C) 2006-2022 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; SPDX-License-Identifier: GPL-3.0-only
+;
+
+LIBRARY VBoxClient-x86.dll
+
+EXPORTS
+ ; COM entry points
+ DllGetClassObject PRIVATE
+ DllCanUnloadNow PRIVATE
+ DllRegisterServer PRIVATE
+ DllUnregisterServer PRIVATE
+
diff --git a/src/VBox/Main/src-client/win/VBoxClient-x86.rc b/src/VBox/Main/src-client/win/VBoxClient-x86.rc
new file mode 100644
index 00000000..ede348eb
--- /dev/null
+++ b/src/VBox/Main/src-client/win/VBoxClient-x86.rc
@@ -0,0 +1,73 @@
+/* $Id: VBoxClient-x86.rc $ */
+/** @file
+ * VBoxC - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+#include "win/resource.h"
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4" // Lang=US English, CharSet=Windows Multilingual
+ BEGIN
+ VALUE "FileDescription", "VirtualBox Interface (32-bit)\0"
+ VALUE "InternalName", "VBoxClient-x86\0"
+ VALUE "OriginalFilename", "VBoxClient-x86.dll\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+
+ VALUE "OLESelfRegister", "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// REGISTRY
+//
+
+IDR_VIRTUALBOX REGISTRY "VBoxClient-x86.rgs"
+
+1 TYPELIB "VirtualBox-x86.tlb"
+
diff --git a/src/VBox/Main/src-client/win/dllmain.cpp b/src/VBox/Main/src-client/win/dllmain.cpp
new file mode 100644
index 00000000..3c681291
--- /dev/null
+++ b/src/VBox/Main/src-client/win/dllmain.cpp
@@ -0,0 +1,176 @@
+/* $Id: dllmain.cpp $ */
+/** @file
+ * VBoxC - COM DLL exports and DLL init/term.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBox/com/defs.h"
+
+#include <SessionImpl.h>
+#include <VirtualBoxClientImpl.h>
+
+#include <iprt/initterm.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static ATL::CComModule *g_pAtlComModule;
+
+BEGIN_OBJECT_MAP(ObjectMap)
+ OBJECT_ENTRY(CLSID_Session, Session)
+ OBJECT_ENTRY(CLSID_VirtualBoxClient, VirtualBoxClient)
+END_OBJECT_MAP()
+
+
+/////////////////////////////////////////////////////////////////////////////
+// DLL Entry Point
+
+extern "C"
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
+{
+ if (dwReason == DLL_PROCESS_ATTACH)
+ {
+ // idempotent, so doesn't harm, and needed for COM embedding scenario
+ RTR3InitDll(RTR3INIT_FLAGS_UNOBTRUSIVE);
+
+ g_pAtlComModule = new(ATL::CComModule);
+ if (!g_pAtlComModule)
+ return FALSE;
+
+ g_pAtlComModule->Init(ObjectMap, hInstance, &LIBID_VirtualBox);
+ DisableThreadLibraryCalls(hInstance);
+ }
+ else if (dwReason == DLL_PROCESS_DETACH)
+ {
+ if (g_pAtlComModule)
+ {
+ g_pAtlComModule->Term();
+ delete g_pAtlComModule;
+ g_pAtlComModule = NULL;
+ }
+ }
+ return TRUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Used to determine whether the DLL can be unloaded by OLE
+
+STDAPI DllCanUnloadNow(void)
+{
+ AssertReturn(g_pAtlComModule, S_OK);
+ LONG const cLocks = g_pAtlComModule->GetLockCount();
+ Assert(cLocks >= VirtualBoxClient::s_cUnnecessaryAtlModuleLocks);
+ return cLocks <= VirtualBoxClient::s_cUnnecessaryAtlModuleLocks ? S_OK : S_FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Returns a class factory to create an object of the requested type
+
+STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv)
+{
+ AssertReturn(g_pAtlComModule, E_UNEXPECTED);
+ return g_pAtlComModule->GetClassObject(rclsid, riid, ppv);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// DllRegisterServer - Adds entries to the system registry
+
+STDAPI DllRegisterServer(void)
+{
+#ifndef VBOX_WITH_MIDL_PROXY_STUB
+ // registers object, typelib and all interfaces in typelib
+ AssertReturn(g_pAtlComModule, E_UNEXPECTED);
+ return g_pAtlComModule->RegisterServer(TRUE);
+#else
+ return S_OK; /* VBoxProxyStub does all the work, no need to duplicate it here. */
+#endif
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// DllUnregisterServer - Removes entries from the system registry
+
+STDAPI DllUnregisterServer(void)
+{
+#ifndef VBOX_WITH_MIDL_PROXY_STUB
+ AssertReturn(g_pAtlComModule, E_UNEXPECTED);
+ HRESULT hrc = g_pAtlComModule->UnregisterServer(TRUE);
+ return hrc;
+#else
+ return S_OK; /* VBoxProxyStub does all the work, no need to duplicate it here. */
+#endif
+}
+
+
+#ifdef RT_OS_WINDOWS
+/*
+ * HACK ALERT! Really ugly trick to make the VirtualBoxClient object go away
+ * when nobody uses it anymore. This is to prevent its uninit()
+ * method from accessing IVirtualBox and similar proxy stubs after
+ * COM has been officially shut down.
+ *
+ * It is simply TOO LATE to destroy the client object from DllMain/detach!
+ *
+ * This hack ASSUMES ObjectMap order.
+ * This hack is subject to a re-instantiation race.
+ */
+ULONG VirtualBoxClient::InternalRelease()
+{
+ ULONG cRefs = VirtualBoxClientWrap::InternalRelease();
+# ifdef DEBUG_bird
+ char szMsg[64];
+ RTStrPrintf(szMsg, sizeof(szMsg), "VirtualBoxClient: cRefs=%d\n", cRefs);
+ OutputDebugStringA(szMsg);
+# endif
+# if 1 /* enable ugly hack */
+ if (cRefs == 1)
+ {
+ /* Make the factory to drop its reference. */
+ if (ObjectMap[1].pCF)
+ {
+ InternalAddRef();
+
+ CMyComClassFactorySingleton<VirtualBoxClient> *pFactory;
+ pFactory = dynamic_cast<CMyComClassFactorySingleton<VirtualBoxClient> *>(ObjectMap[1].pCF);
+ Assert(pFactory);
+ if (pFactory)
+ {
+ IUnknown *pUnknown = pFactory->m_spObj;
+ pFactory->m_spObj = NULL;
+ if (pUnknown)
+ pUnknown->Release();
+ }
+
+ cRefs = VirtualBoxClientWrap::InternalRelease();
+ }
+ }
+# endif
+ return cRefs;
+}
+#endif
+
diff --git a/src/VBox/Main/src-client/win/precomp_vcc.h b/src/VBox/Main/src-client/win/precomp_vcc.h
new file mode 100644
index 00000000..f77c87c2
--- /dev/null
+++ b/src/VBox/Main/src-client/win/precomp_vcc.h
@@ -0,0 +1,49 @@
+/* $Id: precomp_vcc.h $ */
+/** @file
+ * VirtualBox COM - Visual C++ precompiled header for VBoxC.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+
+#include <iprt/cdefs.h>
+#include <iprt/win/winsock2.h>
+#include <iprt/win/windows.h>
+#include <VBox/cdefs.h>
+#include <iprt/types.h>
+#include <iprt/cpp/list.h>
+#include <iprt/cpp/meta.h>
+#include <iprt/cpp/ministring.h>
+#include <VBox/com/microatl.h>
+#include <VBox/com/com.h>
+#include <VBox/com/array.h>
+#include <VBox/com/Guid.h>
+#include <VBox/com/string.h>
+
+#include "VBox/com/VirtualBox.h"
+
+#if defined(Log) || defined(LogIsEnabled)
+# error "Log() from iprt/log.h cannot be defined in the precompiled header!"
+#endif
+
diff --git a/src/VBox/Main/src-client/xpcom/Makefile.kup b/src/VBox/Main/src-client/xpcom/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-client/xpcom/Makefile.kup
diff --git a/src/VBox/Main/src-client/xpcom/module.cpp b/src/VBox/Main/src-client/xpcom/module.cpp
new file mode 100644
index 00000000..5e0748d2
--- /dev/null
+++ b/src/VBox/Main/src-client/xpcom/module.cpp
@@ -0,0 +1,159 @@
+/* $Id: module.cpp $ */
+/** @file
+ * XPCOM module implementation functions
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN
+
+/* Make sure all the stdint.h macros are included - must come first! */
+#ifndef __STDC_LIMIT_MACROS
+# define __STDC_LIMIT_MACROS
+#endif
+#ifndef __STDC_CONSTANT_MACROS
+# define __STDC_CONSTANT_MACROS
+#endif
+
+#include <nsIGenericFactory.h>
+
+// generated file
+#include <VBox/com/VirtualBox.h>
+
+#include "SessionImpl.h"
+#include "VirtualBoxClientImpl.h"
+#include "RemoteUSBDeviceImpl.h"
+#include "USBDeviceImpl.h"
+
+// XPCOM glue code unfolding
+
+/*
+ * Declare extern variables here to tell the compiler that
+ * NS_DECL_CLASSINFO(SessionWrap)
+ * already exists in the VBoxAPIWrap library.
+ */
+NS_DECL_CI_INTERFACE_GETTER(SessionWrap)
+extern nsIClassInfo *NS_CLASSINFO_NAME(SessionWrap);
+
+/*
+ * Declare extern variables here to tell the compiler that
+ * NS_DECL_CLASSINFO(VirtualBoxClientWrap)
+ * already exists in the VBoxAPIWrap library.
+ */
+NS_DECL_CI_INTERFACE_GETTER(VirtualBoxClientWrap)
+extern nsIClassInfo *NS_CLASSINFO_NAME(VirtualBoxClientWrap);
+
+/**
+ * Singleton class factory that holds a reference to the created instance
+ * (preventing it from being destroyed) until the module is explicitly
+ * unloaded by the XPCOM shutdown code.
+ *
+ * Suitable for IN-PROC components.
+ */
+class VirtualBoxClientClassFactory : public VirtualBoxClient
+{
+public:
+ virtual ~VirtualBoxClientClassFactory()
+ {
+ FinalRelease();
+ instance = 0;
+ }
+
+ static nsresult GetInstance(VirtualBoxClient **inst)
+ {
+ int rv = NS_OK;
+ if (instance == 0)
+ {
+ instance = new VirtualBoxClientClassFactory();
+ if (instance)
+ {
+ instance->AddRef(); // protect FinalConstruct()
+ rv = instance->FinalConstruct();
+ if (NS_FAILED(rv))
+ instance->Release();
+ else
+ instance->AddRef(); // self-reference
+ }
+ else
+ {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ else
+ {
+ instance->AddRef();
+ }
+ *inst = instance;
+ return rv;
+ }
+
+ static nsresult FactoryDestructor()
+ {
+ if (instance)
+ instance->Release();
+ return NS_OK;
+ }
+
+private:
+ static VirtualBoxClient *instance;
+};
+
+VirtualBoxClient *VirtualBoxClientClassFactory::instance = nsnull;
+
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_WITH_RC(Session)
+
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR_WITH_RC(VirtualBoxClient, VirtualBoxClientClassFactory::GetInstance)
+
+/**
+ * Component definition table.
+ * Lists all components defined in this module.
+ */
+static const nsModuleComponentInfo components[] =
+{
+ {
+ "Session component", // description
+ NS_SESSION_CID, NS_SESSION_CONTRACTID, // CID/ContractID
+ SessionConstructor, // constructor function
+ NULL, // registration function
+ NULL, // deregistration function
+ NULL, // destructor function
+ NS_CI_INTERFACE_GETTER_NAME(SessionWrap), // interfaces function
+ NULL, // language helper
+ &NS_CLASSINFO_NAME(SessionWrap) // global class info & flags
+ },
+ {
+ "VirtualBoxClient component", // description
+ NS_VIRTUALBOXCLIENT_CID, NS_VIRTUALBOXCLIENT_CONTRACTID, // CID/ContractID
+ VirtualBoxClientConstructor, // constructor function
+ NULL, // registration function
+ NULL, // deregistration function
+ VirtualBoxClientClassFactory::FactoryDestructor, // destructor function
+ NS_CI_INTERFACE_GETTER_NAME(VirtualBoxClientWrap), // interfaces function
+ NULL, // language helper
+ &NS_CLASSINFO_NAME(VirtualBoxClientWrap) // global class info & flags
+ },
+};
+
+NS_IMPL_NSGETMODULE (VirtualBox_Client_Module, components)
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/xpcom/precomp_gcc.h b/src/VBox/Main/src-client/xpcom/precomp_gcc.h
new file mode 100644
index 00000000..ce01deb8
--- /dev/null
+++ b/src/VBox/Main/src-client/xpcom/precomp_gcc.h
@@ -0,0 +1,53 @@
+/* $Id: precomp_gcc.h $ */
+/** @file
+ * VirtualBox COM - GNU C++ precompiled header for VBoxC.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#include <iprt/cdefs.h>
+#include <VBox/cdefs.h>
+#include <iprt/types.h>
+#include <iprt/cpp/list.h>
+#include <iprt/cpp/meta.h>
+#include <iprt/cpp/ministring.h>
+#include <VBox/com/com.h>
+#include <VBox/com/array.h>
+#include <VBox/com/Guid.h>
+#include <VBox/com/string.h>
+#include <VBox/com/VirtualBox.h>
+
+#if 1
+# include "VirtualBoxBase.h"
+# include <new>
+# include <list>
+# include <map>
+# include <array>
+# include <errno.h>
+#endif
+
+#if defined(Log) || defined(LogIsEnabled)
+# error "Log() from iprt/log.h cannot be defined in the precompiled header!"
+#endif
+
diff --git a/src/VBox/Main/src-global/Makefile.kup b/src/VBox/Main/src-global/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-global/Makefile.kup
diff --git a/src/VBox/Main/src-global/win/Makefile.kup b/src/VBox/Main/src-global/win/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-global/win/Makefile.kup
diff --git a/src/VBox/Main/src-global/win/VBoxSDS.cpp b/src/VBox/Main/src-global/win/VBoxSDS.cpp
new file mode 100644
index 00000000..14bfc7e0
--- /dev/null
+++ b/src/VBox/Main/src-global/win/VBoxSDS.cpp
@@ -0,0 +1,1047 @@
+/* $Id: VBoxSDS.cpp $ */
+/** @file
+ * VBoxSDS - COM global service main entry (System Directory Service)
+ */
+
+/*
+ * Copyright (C) 2017-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/** @page pg_VBoxSDS VBoxSDS - Per user CLSID_VirtualBox coordinater
+ *
+ * VBoxSDS is short for VirtualBox System Directory Service (SDS). Its purpose
+ * is to make sure there is only one CLSID_VirtualBox object running for each
+ * user using VirtualBox on a Windows host system.
+ *
+ *
+ * @section sec_vboxsds_backgroud Background
+ *
+ * COM is desktop oriented when it comes to activate-as-activator (AAA) COM
+ * servers. This means that if the users has two logins to the same box (e.g.
+ * physical console, RDP, SSHD) and tries to use an AAA COM server, a new server
+ * will be instantiated for each login. With the introduction of User Account
+ * Control (UAC) in Windows Vista, this was taken a step further and a user
+ * would talk different AAA COM server instances depending on the elevation
+ * level too.
+ *
+ * VBoxSVC is a service affected by this issue. Using VirtualBox across logins
+ * or between user elevation levels was impossible to do simultaneously. This
+ * was confusing and illogical to the user.
+ *
+ *
+ * @section sec_vboxsds_how How it works
+ *
+ * VBoxSDS assists in working around this problem by tracking which VBoxSVC
+ * server is currently providing CLSID_VirtualBox for a user. Each VBoxSVC
+ * instance will register itself with VBoxSDS when the CLSID_VirtualBox object
+ * is requested via their class factory. The first VBoxSVC registering for a
+ * given user will be allowed to instantate CLSID_VirtualBox. We will call this
+ * the chosen one. Subsequent VBoxSVC instance for the given user, regardless
+ * of elevation, session, windows station, or whatever else, will be told to use
+ * the instance from the first VBoxSVC.
+ *
+ * The registration call passes along an IVBoxSVCRegistration interface from
+ * VBoxSVC. VBoxSDS keeps this around for the chosen one only. When other
+ * VBoxSVC instances for the same user tries to register, VBoxSDS will ask the
+ * choosen one for its CLSID_VirtualBox object and return it to the new
+ * registrant.
+ *
+ * The chosen one will deregister with VBoxSDS before it terminates. Should it
+ * terminate abnormally, VBoxSDS will (probably) notice the next time it tries
+ * to request CLSID_VirtualBox from it and replace it as the chosen one with the
+ * new registrant.
+ *
+ *
+ * @section sec_vboxsds_locking Locking
+ *
+ * VBoxSDS stores data in a map indexed by the stringified secure identifier
+ * (SID) for each user. The map is protected by a shared critical section, so
+ * only inserting new users requires exclusive access.
+ *
+ * Each user data entry has it own lock (regular, not shared), so that it won't
+ * be necessary to hold down the map lock while accessing per user data. Thus
+ * preventing a user from blocking all others from using VirtualBox by
+ * suspending or debugging their chosen VBoxSVC process.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_VIRTUALBOXSDS
+#include <iprt/win/windows.h>
+#include <iprt/win/shlobj.h>
+
+#include "VBox/com/defs.h"
+#include "VBox/com/com.h"
+#include "VBox/com/VirtualBox.h"
+
+#include "VirtualBoxSDSImpl.h"
+#include "LoggingNew.h"
+
+#include <iprt/errcore.h>
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/path.h>
+#include <iprt/message.h>
+#include <iprt/string.h>
+
+#include <VBox/com/microatl.h>
+
+#define _ATL_FREE_THREADED /** @todo r=bird: WTF? */
+
+/**
+ * Implements Windows Service
+ */
+class ATL_NO_VTABLE CWindowsServiceModule
+{
+protected:
+ // data members
+ WCHAR m_wszServiceName[256];
+ WCHAR m_wszServiceDisplayName[256];
+ WCHAR m_wszServiceDescription[256];
+ SERVICE_STATUS_HANDLE m_hServiceStatus;
+ SERVICE_STATUS m_Status;
+ DWORD m_dwThreadID;
+
+ /** Pointer to the instance, for use by staticServiceMain and staticHandler. */
+ static CWindowsServiceModule *s_pInstance;
+
+public:
+ CWindowsServiceModule() throw()
+ {
+ // set up the initial service status
+ m_hServiceStatus = NULL;
+ m_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ m_Status.dwCurrentState = SERVICE_STOPPED;
+ m_Status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+ m_Status.dwWin32ExitCode = 0;
+ m_Status.dwServiceSpecificExitCode = 0;
+ m_Status.dwCheckPoint = 0;
+ m_Status.dwWaitHint = 3000;
+
+ s_pInstance = this;
+ }
+
+ virtual ~CWindowsServiceModule()
+ {
+ s_pInstance = NULL;
+ }
+
+ HRESULT startService(int /*nShowCmd*/) throw()
+ {
+ SERVICE_TABLE_ENTRY aServiceTable[] =
+ {
+ { m_wszServiceName, staticServiceMain },
+ { NULL, NULL }
+ };
+
+ if (::StartServiceCtrlDispatcher(aServiceTable) == 0)
+ {
+ m_Status.dwWin32ExitCode = ::GetLastError();
+ LogRelFunc(("Error: Cannot start service in console mode. Code: %u\n", m_Status.dwWin32ExitCode));
+ }
+
+ return m_Status.dwWin32ExitCode;
+ }
+
+ virtual HRESULT registerService() throw()
+ {
+ HRESULT hrc;
+ if (uninstallService())
+ {
+ hrc = onRegisterService();
+ if (SUCCEEDED(hrc))
+ {
+ if (installService())
+ hrc = S_OK;
+ else
+ hrc = E_FAIL;
+ }
+ }
+ else
+ hrc = E_FAIL;
+ return hrc;
+ }
+
+ virtual HRESULT unregisterService() throw()
+ {
+ HRESULT hrc = E_FAIL;
+ if (uninstallService())
+ hrc = onUnregisterService();
+ return hrc;
+ }
+
+private:
+ void serviceMain(DWORD, LPTSTR *) throw()
+ {
+ LogFunc(("Enter into serviceMain\n"));
+ // Register the control request handler
+ m_Status.dwCurrentState = SERVICE_START_PENDING;
+ m_dwThreadID = ::GetCurrentThreadId();
+ m_hServiceStatus = ::RegisterServiceCtrlHandler(m_wszServiceName, staticHandler);
+ if (m_hServiceStatus == NULL)
+ {
+ LogWarnFunc(("Handler not installed\n"));
+ return;
+ }
+ setServiceStatus(SERVICE_START_PENDING);
+
+ m_Status.dwWin32ExitCode = S_OK;
+ m_Status.dwCheckPoint = 0;
+ m_Status.dwWaitHint = 0;
+
+ // When the Run function returns, the service has stopped.
+ m_Status.dwWin32ExitCode = runService(SW_HIDE);
+
+ setServiceStatus(SERVICE_STOPPED);
+ LogFunc(("Windows Service stopped\n"));
+ }
+
+ /** Service table callback. */
+ static void WINAPI staticServiceMain(DWORD cArgs, LPTSTR *papwszArgs) throw()
+ {
+ AssertPtrReturnVoid(s_pInstance);
+ s_pInstance->serviceMain(cArgs, papwszArgs);
+ }
+
+ HRESULT runService(int nShowCmd = SW_HIDE) throw()
+ {
+ HRESULT hr = preMessageLoop(nShowCmd);
+
+ if (hr == S_OK)
+ runMessageLoop();
+
+ if (SUCCEEDED(hr))
+ hr = postMessageLoop();
+
+ return hr;
+ }
+
+protected:
+ /** Hook that's called before the message loop starts.
+ * Must return S_OK for it to start. */
+ virtual HRESULT preMessageLoop(int /*nShowCmd*/) throw()
+ {
+ LogFunc(("Enter\n"));
+ if (::InterlockedCompareExchange(&m_Status.dwCurrentState, SERVICE_RUNNING, SERVICE_START_PENDING) == SERVICE_START_PENDING)
+ {
+ LogFunc(("VBoxSDS Service started/resumed without delay\n"));
+ ::SetServiceStatus(m_hServiceStatus, &m_Status);
+ }
+ return S_OK;
+ }
+
+ /** Your typical windows message loop. */
+ virtual void runMessageLoop()
+ {
+ MSG msg;
+ while (::GetMessage(&msg, 0, 0, 0) > 0)
+ {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ }
+ }
+
+ /** Hook that's called after the message loop ends. */
+ virtual HRESULT postMessageLoop()
+ {
+ return S_OK;
+ }
+
+ /** @name Overridable status change handlers
+ * @{ */
+ virtual void onStop() throw()
+ {
+ setServiceStatus(SERVICE_STOP_PENDING);
+ ::PostThreadMessage(m_dwThreadID, WM_QUIT, 0, 0);
+ LogFunc(("Windows Service stopped\n"));
+ }
+
+ virtual void onPause() throw()
+ {
+ }
+
+ virtual void onContinue() throw()
+ {
+ }
+
+ virtual void onInterrogate() throw()
+ {
+ }
+
+ virtual void onShutdown() throw()
+ {
+ }
+
+ virtual void onUnknownRequest(DWORD dwOpcode) throw()
+ {
+ LogRelFunc(("Bad service request: %u (%#x)\n", dwOpcode, dwOpcode));
+ }
+
+ virtual HRESULT onRegisterService()
+ {
+ return S_OK;
+ }
+
+ virtual HRESULT onUnregisterService()
+ {
+ return S_OK;
+ }
+ /** @} */
+
+private:
+ void handler(DWORD dwOpcode) throw()
+ {
+
+ switch (dwOpcode)
+ {
+ case SERVICE_CONTROL_STOP:
+ onStop();
+ break;
+ case SERVICE_CONTROL_PAUSE:
+ onPause();
+ break;
+ case SERVICE_CONTROL_CONTINUE:
+ onContinue();
+ break;
+ case SERVICE_CONTROL_INTERROGATE:
+ onInterrogate();
+ break;
+ case SERVICE_CONTROL_SHUTDOWN:
+ onShutdown();
+ break;
+ default:
+ onUnknownRequest(dwOpcode);
+ }
+ }
+
+ static void WINAPI staticHandler(DWORD dwOpcode) throw()
+ {
+ AssertPtrReturnVoid(s_pInstance);
+ s_pInstance->handler(dwOpcode);
+ }
+
+protected:
+ void setServiceStatus(DWORD dwState) throw()
+ {
+ uint32_t const uPrevState = ASMAtomicXchgU32((uint32_t volatile *)&m_Status.dwCurrentState, dwState);
+ if (!::SetServiceStatus(m_hServiceStatus, &m_Status))
+ LogRel(("Error: SetServiceStatus(%u) failed: %u (uPrevState=%u)\n",
+ dwState, GetLastError(), uPrevState));
+ }
+
+
+public:
+ /** @note unused */
+ BOOL IsInstalled() throw()
+ {
+ BOOL fResult = FALSE;
+
+ SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+ if (hSCM != NULL)
+ {
+ SC_HANDLE hService = ::OpenService(hSCM, m_wszServiceName, SERVICE_QUERY_CONFIG);
+ if (hService != NULL)
+ {
+ fResult = TRUE;
+ ::CloseServiceHandle(hService);
+ }
+ ::CloseServiceHandle(hSCM);
+ }
+
+ return fResult;
+ }
+
+ BOOL installService() throw()
+ {
+ BOOL fResult = FALSE;
+ SC_HANDLE hSCM = ::OpenSCManagerW(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
+ if (hSCM != NULL)
+ {
+ SC_HANDLE hService = ::OpenService(hSCM, m_wszServiceName, SERVICE_QUERY_CONFIG);
+ if (hService != NULL)
+ {
+ fResult = TRUE; /* Already installed. */
+
+ ::CloseServiceHandle(hService);
+ }
+ else
+ {
+ // Get the executable file path and quote it.
+ const int QUOTES_SPACE = 2;
+ WCHAR wszFilePath[MAX_PATH + QUOTES_SPACE];
+ DWORD cwcFilePath = ::GetModuleFileNameW(NULL, wszFilePath + 1, MAX_PATH);
+ if (cwcFilePath != 0 && cwcFilePath < MAX_PATH)
+ {
+ wszFilePath[0] = L'\"';
+ wszFilePath[cwcFilePath + 1] = L'\"';
+ wszFilePath[cwcFilePath + 2] = L'\0';
+
+ hService = ::CreateServiceW(hSCM, m_wszServiceName, m_wszServiceDisplayName,
+ SERVICE_CHANGE_CONFIG,
+ SERVICE_WIN32_OWN_PROCESS,
+ SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
+ wszFilePath, NULL, NULL, L"RPCSS\0", NULL, NULL);
+ if (hService != NULL)
+ {
+ SERVICE_DESCRIPTIONW sd;
+ sd.lpDescription = m_wszServiceDescription;
+ if (!::ChangeServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, &sd))
+ AssertLogRelMsgFailed(("Error: could not set service description: %u\n", GetLastError()));
+
+ fResult = TRUE;
+
+ ::CloseServiceHandle(hService);
+ }
+ else
+ AssertLogRelMsgFailed(("Error: Could not create service '%ls': %u\n", m_wszServiceName, GetLastError()));
+ }
+ else
+ AssertLogRelMsgFailed(("Error: GetModuleFileNameW returned %u: %u\n", cwcFilePath, GetLastError()));
+ }
+ }
+ else
+ AssertLogRelMsgFailed(("Error: Could not open the service control manager: %u\n", GetLastError()));
+ return fResult;
+ }
+
+ BOOL uninstallService() throw()
+ {
+ BOOL fResult = FALSE;
+ SC_HANDLE hSCM = ::OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
+ if (hSCM != NULL)
+ {
+ SC_HANDLE hService = ::OpenService(hSCM, m_wszServiceName, SERVICE_STOP | DELETE);
+ if (hService == NULL)
+ {
+ DWORD dwErr = GetLastError();
+ hService = ::OpenService(hSCM, m_wszServiceName, SERVICE_QUERY_CONFIG);
+ if (hService == NULL)
+ fResult = TRUE; /* Probably not installed or some access problem. */
+ else
+ {
+ ::CloseServiceHandle(hService);
+ AssertLogRelMsgFailed(("Error: Failed to open '%ls' for stopping and deletion: %u\n", m_wszServiceName, dwErr));
+ }
+ }
+ else
+ {
+ /* Try stop it. */
+ SERVICE_STATUS status;
+ RT_ZERO(status);
+ if (!::ControlService(hService, SERVICE_CONTROL_STOP, &status))
+ {
+ DWORD dwErr = GetLastError();
+ AssertLogRelMsg( dwErr == ERROR_SERVICE_NOT_ACTIVE
+ || ( dwErr == ERROR_SERVICE_CANNOT_ACCEPT_CTRL
+ && status.dwCurrentState == SERVICE_STOP_PENDING)
+ , ("Error: Failed to stop serive '%ls': dwErr=%u dwCurrentState=%u\n",
+ m_wszServiceName, dwErr, status.dwCurrentState));
+ }
+
+ /* Try delete it. */
+ fResult = ::DeleteService(hService);
+ AssertLogRelMsg(fResult, ("Error: Failed to delete serivce '%ls': %u\n", m_wszServiceName, GetLastError()));
+
+ ::CloseServiceHandle(hService);
+ }
+ ::CloseServiceHandle(hSCM);
+ }
+ else
+ AssertLogRelMsgFailed(("Error: Could not open the service control manager: %u\n", GetLastError()));
+ return fResult;
+ }
+};
+
+/*static*/ CWindowsServiceModule *CWindowsServiceModule::s_pInstance = NULL;
+
+
+/**
+ * Implements COM Module that used within Windows Service.
+ *
+ * It is derived from ComModule to intercept Unlock() and derived from
+ * CWindowsServiceModule to implement Windows Service
+ */
+class CComServiceModule : public CWindowsServiceModule, public ATL::CComModule
+{
+private:
+ /** Tracks whether Init() has been called for debug purposes. */
+ bool m_fInitialized;
+ /** Tracks COM init status for no visible purpose other than debugging. */
+ bool m_fComInitialized;
+ /** Part of the shutdown monitoring logic. */
+ bool volatile m_fActivity;
+#ifdef WITH_WATCHER
+ /** Part of the shutdown monitoring logic. */
+ bool volatile m_fHasClients;
+#endif
+ /** Auto reset event for communicating with the shutdown thread.
+ * This is created by startMonitor(). */
+ HANDLE m_hEventShutdown;
+ /** The main thread ID.
+ * The monitorShutdown code needs this to post a WM_QUIT message. */
+ DWORD m_dwMainThreadID;
+
+public:
+ /** Time for EXE to be idle before shutting down.
+ * Can be decreased at system shutdown phase. */
+ volatile uint32_t m_cMsShutdownTimeOut;
+
+ /** The service module instance. */
+ static CComServiceModule * volatile s_pInstance;
+
+public:
+ /**
+ * Constructor.
+ *
+ * @param cMsShutdownTimeout Number of milliseconds to idle without clients
+ * before autoamtically shutting down the service.
+ *
+ * The default is 2 seconds, because VBoxSVC (our
+ * only client) already does 5 seconds making the
+ * effective idle time 7 seconds from clients like
+ * VBoxManage's point of view. We consider single
+ * user and development as the dominant usage
+ * patterns here, not configuration activity by
+ * multiple users via VBoxManage.
+ */
+ CComServiceModule(DWORD cMsShutdownTimeout = 2000)
+ : m_fInitialized(false)
+ , m_fComInitialized(false)
+ , m_fActivity(false)
+#ifdef WITH_WATCHER
+ , m_fHasClients(false)
+#endif
+ , m_hEventShutdown(INVALID_HANDLE_VALUE)
+ , m_dwMainThreadID(~(DWORD)42)
+ , m_cMsShutdownTimeOut(cMsShutdownTimeout)
+ {
+ }
+
+ /**
+ * Initialization function.
+ */
+ HRESULT init(ATL::_ATL_OBJMAP_ENTRY *p, HINSTANCE h, const GUID *pLibID,
+ wchar_t const *p_wszServiceName, wchar_t const *p_wszDisplayName, wchar_t const *p_wszDescription)
+ {
+ HRESULT hrc = ATL::CComModule::Init(p, h, pLibID);
+ if (SUCCEEDED(hrc))
+ {
+ // copy service name
+ int rc = ::RTUtf16Copy(m_wszServiceName, sizeof(m_wszServiceName), p_wszServiceName);
+ AssertRCReturn(rc, E_NOT_SUFFICIENT_BUFFER);
+ rc = ::RTUtf16Copy(m_wszServiceDisplayName, sizeof(m_wszServiceDisplayName), p_wszDisplayName);
+ AssertRCReturn(rc, E_NOT_SUFFICIENT_BUFFER);
+ rc = ::RTUtf16Copy(m_wszServiceDescription, sizeof(m_wszServiceDescription), p_wszDescription);
+ AssertRCReturn(rc, E_NOT_SUFFICIENT_BUFFER);
+
+ m_fInitialized = true;
+ }
+
+ return hrc;
+ }
+
+ /**
+ * Overload CAtlModule::Unlock to trigger delayed automatic shutdown action.
+ */
+ virtual LONG Unlock() throw()
+ {
+ LONG cLocks = ATL::CComModule::Unlock();
+ LogFunc(("Unlock() called. Ref=%d\n", cLocks));
+ if (cLocks == 0)
+ {
+ ::ASMAtomicWriteBool(&m_fActivity, true);
+ ::SetEvent(m_hEventShutdown); // tell monitor that we transitioned to zero
+ }
+ return cLocks;
+ }
+
+ /**
+ * Overload CAtlModule::Lock to untrigger automatic shutdown.
+ */
+ virtual LONG Lock() throw()
+ {
+ LONG cLocks = ATL::CComModule::Lock();
+ LogFunc(("Lock() called. Ref=%d\n", cLocks));
+#ifdef WITH_WATCHER
+ ::ASMAtomicWriteBool(&m_fActivity, true);
+ ::SetEvent(m_hEventShutdown); /* reset the timeout interval */
+#endif
+ return cLocks;
+ }
+
+#ifdef WITH_WATCHER
+
+ /** Called to start the automatic shutdown behaviour based on client count
+ * rather than lock count.. */
+ void notifyZeroClientConnections()
+ {
+ m_fHasClients = false;
+ ::ASMAtomicWriteBool(&m_fActivity, true);
+ ::SetEvent(m_hEventShutdown);
+ }
+
+ /** Called to make sure automatic shutdown is cancelled. */
+ void notifyHasClientConnections()
+ {
+ m_fHasClients = true;
+ ::ASMAtomicWriteBool(&m_fActivity, true);
+ }
+
+#endif /* WITH_WATCHER */
+
+protected:
+
+ bool hasActiveConnection()
+ {
+#ifdef WITH_WATCHER
+ return m_fActivity || (m_fHasClients && GetLockCount() > 0);
+#else
+ return m_fActivity || GetLockCount() > 0;
+#endif
+ }
+
+ void monitorShutdown() throw()
+ {
+ for (;;)
+ {
+ ::WaitForSingleObject(m_hEventShutdown, INFINITE);
+ DWORD dwWait;
+ do
+ {
+ m_fActivity = false;
+ dwWait = ::WaitForSingleObject(m_hEventShutdown, m_cMsShutdownTimeOut);
+ } while (dwWait == WAIT_OBJECT_0);
+
+ /* timed out */
+ if (!hasActiveConnection()) /* if no activity let's really bail */
+ {
+ ::CoSuspendClassObjects();
+
+ /* Disable log rotation at this point, worst case a log file becomes slightly
+ bigger than it should. Avoids quirks with log rotation: There might be
+ another API service process running at this point which would rotate the
+ logs concurrently, creating a mess. */
+ PRTLOGGER pReleaseLogger = ::RTLogRelGetDefaultInstance();
+ if (pReleaseLogger)
+ {
+ char szDest[1024];
+ int rc = ::RTLogQueryDestinations(pReleaseLogger, szDest, sizeof(szDest));
+ if (RT_SUCCESS(rc))
+ {
+ rc = ::RTStrCat(szDest, sizeof(szDest), " nohistory");
+ if (RT_SUCCESS(rc))
+ {
+ rc = ::RTLogDestinations(pReleaseLogger, szDest);
+ AssertRC(rc);
+ }
+ }
+ }
+
+ if (!hasActiveConnection())
+ break;
+ LogRel(("Still got active connection(s)...\n"));
+ }
+ }
+
+ LogRel(("Shutting down\n"));
+ if (m_hEventShutdown)
+ {
+ ::CloseHandle(m_hEventShutdown);
+ m_hEventShutdown = NULL;
+ }
+ ::PostThreadMessage(m_dwMainThreadID, WM_QUIT, 0, 0);
+ }
+
+ static DECLCALLBACK(int) monitorThreadProc(RTTHREAD hThreadSelf, void *pvUser) throw()
+ {
+ RT_NOREF(hThreadSelf);
+ CComServiceModule *p = static_cast<CComServiceModule *>(pvUser);
+ p->monitorShutdown();
+ return VINF_SUCCESS;
+ }
+
+ void startMonitor()
+ {
+ m_dwMainThreadID = ::GetCurrentThreadId();
+ m_hEventShutdown = ::CreateEvent(NULL, false, false, NULL);
+ AssertLogRelMsg(m_hEventShutdown != NULL, ("GetLastError => %u\n", GetLastError()));
+
+ int vrc = RTThreadCreate(NULL, monitorThreadProc, this, 0 /*cbStack*/, RTTHREADTYPE_DEFAULT, 0 /*fFlags*/, "MonShdwn");
+ if (RT_FAILURE(vrc))
+ {
+ ::CloseHandle(m_hEventShutdown);
+ m_hEventShutdown = NULL;
+ LogRel(("Error: RTThreadCreate failed to create shutdown monitor thread: %Rrc\n", vrc));
+ }
+ }
+
+ virtual HRESULT preMessageLoop(int nShowCmd) throw()
+ {
+ Assert(m_fInitialized);
+ LogFunc(("Enter\n"));
+
+ HRESULT hrc = com::Initialize();
+ if (SUCCEEDED(hrc))
+ {
+ m_fComInitialized = true;
+ hrc = ATL::CComModule::RegisterClassObjects(CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED);
+ if (SUCCEEDED(hrc))
+ {
+ // Start Shutdown monitor here
+ startMonitor();
+
+ hrc = CWindowsServiceModule::preMessageLoop(nShowCmd);
+ if (FAILED(hrc))
+ LogRelFunc(("Warning: preMessageLoop failed: %Rhrc\n", hrc));
+
+ hrc = CoResumeClassObjects();
+ if (FAILED(hrc))
+ {
+ ATL::CComModule::RevokeClassObjects();
+ LogRelFunc(("Error: CoResumeClassObjects failed: %Rhrc\n", hrc));
+ }
+ }
+ else
+ LogRel(("Error: ATL::CComModule::RegisterClassObjects: %Rhrc\n", hrc));
+ }
+ else
+ LogRel(("Error: com::Initialize failed\n", hrc));
+ return hrc;
+ }
+
+ virtual HRESULT postMessageLoop()
+ {
+ com::Shutdown();
+ m_fComInitialized = false;
+ return S_OK;
+ }
+};
+
+/*static*/ CComServiceModule * volatile CComServiceModule::s_pInstance = NULL;
+
+
+#ifdef WITH_WATCHER
+/**
+ * Go-between for CComServiceModule and VirtualBoxSDS.
+ */
+void VBoxSDSNotifyClientCount(uint32_t cClients)
+{
+ CComServiceModule *pInstance = CComServiceModule::s_pInstance;
+ if (pInstance)
+ {
+ if (cClients == 0)
+ pInstance->notifyZeroClientConnections();
+ else
+ pInstance->notifyHasClientConnections();
+ }
+}
+#endif
+
+
+/**
+ * Main function for the VBoxSDS process.
+ *
+ * @param hInstance The process instance.
+ * @param hPrevInstance Previous instance (not used here).
+ * @param nShowCmd The show flags.
+ * @param lpCmdLine The command line (not used here, we get it from the
+ * C runtime library).
+ *
+ * @return Exit code
+ */
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
+{
+ RT_NOREF(hPrevInstance, lpCmdLine);
+ int argc = __argc;
+ char **argv = __argv;
+
+ /*
+ * Initialize the VBox runtime without loading the support driver.
+ */
+ RTR3InitExe(argc, &argv, 0);
+
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--embedding", 'e', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "-embedding", 'e', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "/embedding", 'e', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "--unregservice", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "-unregservice", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "/unregservice", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "--regservice", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "-regservice", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "/regservice", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "--reregservice", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "-reregservice", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "/reregservice", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "--logfile", 'F', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
+ { "-logfile", 'F', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
+ { "/logfile", 'F', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
+ { "--logrotate", 'R', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "-logrotate", 'R', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "/logrotate", 'R', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "--logsize", 'S', RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_ICASE },
+ { "-logsize", 'S', RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_ICASE },
+ { "/logsize", 'S', RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_ICASE },
+ { "--loginterval", 'I', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "-loginterval", 'I', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "/loginterval", 'I', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ };
+
+ bool fRun = true;
+ bool fRegister = false;
+ bool fUnregister = false;
+ const char *pszLogFile = NULL;
+ uint32_t cHistory = 10; // enable log rotation, 10 files
+ uint32_t uHistoryFileTime = RT_SEC_1DAY; // max 1 day per file
+ uint64_t uHistoryFileSize = 100 * _1M; // max 100MB per file
+
+ RTGETOPTSTATE GetOptState;
+ int vrc = RTGetOptInit(&GetOptState, argc, argv, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
+ AssertRC(vrc);
+
+ RTGETOPTUNION ValueUnion;
+ while ((vrc = RTGetOpt(&GetOptState, &ValueUnion)))
+ {
+ switch (vrc)
+ {
+ case 'e':
+ break;
+
+ case 'u':
+ fUnregister = true;
+ fRun = false;
+ break;
+
+ case 'r':
+ fRegister = true;
+ fRun = false;
+ break;
+
+ case 'f':
+ fUnregister = true;
+ fRegister = true;
+ fRun = false;
+ break;
+
+ case 'F':
+ pszLogFile = ValueUnion.psz;
+ break;
+
+ case 'R':
+ cHistory = ValueUnion.u32;
+ break;
+
+ case 'S':
+ uHistoryFileSize = ValueUnion.u64;
+ break;
+
+ case 'I':
+ uHistoryFileTime = ValueUnion.u32;
+ break;
+
+ case 'h':
+ {
+ static WCHAR const s_wszHelpText[] =
+ L"Options:\n"
+ L"\n"
+ L"/RegService\t" L"register COM out-of-proc service\n"
+ L"/UnregService\t" L"unregister COM out-of-proc service\n"
+ L"/ReregService\t" L"unregister and register COM service\n"
+ L"no options\t" L"run the service";
+ MessageBoxW(NULL, s_wszHelpText, L"VBoxSDS - Usage", MB_OK);
+ return 0;
+ }
+
+ case 'V':
+ {
+ char *pszText = NULL;
+ RTStrAPrintf(&pszText, "%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+
+ PRTUTF16 pwszText = NULL;
+ RTStrToUtf16(pszText, &pwszText);
+
+ MessageBoxW(NULL, pwszText, L"VBoxSDS - Version", MB_OK);
+
+ RTStrFree(pszText);
+ RTUtf16Free(pwszText);
+ return 0;
+ }
+
+ default:
+ {
+ char szTmp[256];
+ RTGetOptFormatError(szTmp, sizeof(szTmp), vrc, &ValueUnion);
+
+ PRTUTF16 pwszText = NULL;
+ RTStrToUtf16(szTmp, &pwszText);
+
+ MessageBoxW(NULL, pwszText, L"VBoxSDS - Syntax error", MB_OK | MB_ICONERROR);
+
+ RTUtf16Free(pwszText);
+ return RTEXITCODE_SYNTAX;
+ }
+ }
+ }
+
+ /*
+ * Default log location is %ProgramData%\VirtualBox\VBoxSDS.log, falling back
+ * on %_CWD%\VBoxSDS.log (where _CWD typicaly is 'C:\Windows\System32').
+ *
+ * We change the current directory to %ProgramData%\VirtualBox\ if possible.
+ *
+ * We only create the log file when running VBoxSDS normally, but not
+ * when registering/unregistering, at least for now.
+ */
+ if (fRun)
+ {
+ char szLogFile[RTPATH_MAX];
+ if (!pszLogFile || !*pszLogFile)
+ {
+ WCHAR wszAppData[MAX_PATH + 16];
+ if (SHGetSpecialFolderPathW(NULL, wszAppData, CSIDL_COMMON_APPDATA, TRUE /*fCreate*/))
+ {
+ char *pszConv = szLogFile;
+ vrc = RTUtf16ToUtf8Ex(wszAppData, RTSTR_MAX, &pszConv, sizeof(szLogFile) - 12, NULL);
+ }
+ else
+ vrc = RTEnvGetUtf8("ProgramData", szLogFile, sizeof(szLogFile) - sizeof("VBoxSDS.log"), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathAppend(szLogFile, sizeof(szLogFile), "VirtualBox\\");
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure it exists. */
+ if (!RTDirExists(szLogFile))
+ vrc = RTDirCreate(szLogFile, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Change into it. */
+ RTPathSetCurrent(szLogFile);
+ }
+ }
+ }
+ if (RT_FAILURE(vrc)) /* ignore any failure above */
+ szLogFile[0] = '\0';
+ vrc = RTStrCat(szLogFile, sizeof(szLogFile), "VBoxSDS.log");
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct release log filename: %Rrc", vrc);
+ pszLogFile = szLogFile;
+ }
+
+ RTERRINFOSTATIC ErrInfo;
+ vrc = com::VBoxLogRelCreate("COM Service", pszLogFile,
+ RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
+ VBOXSDS_LOG_DEFAULT, "VBOXSDS_RELEASE_LOG",
+ RTLOGDEST_FILE | RTLOGDEST_FIXED_FILE | RTLOGDEST_FIXED_DIR,
+ UINT32_MAX /* cMaxEntriesPerGroup */,
+ cHistory, uHistoryFileTime, uHistoryFileSize,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, vrc);
+ }
+
+
+ /*
+ * Initialize COM.
+ */
+ HRESULT hrcExit = com::Initialize();
+ if (SUCCEEDED(hrcExit))
+ {
+ HRESULT hrcSec = CoInitializeSecurity(NULL,
+ -1,
+ NULL,
+ NULL,
+ RPC_C_AUTHN_LEVEL_DEFAULT,
+ RPC_C_IMP_LEVEL_IMPERSONATE,//RPC_C_IMP_LEVEL_IMPERSONATE, RPC_C_IMP_LEVEL_DELEGATE
+ NULL,
+ EOAC_NONE, //EOAC_DYNAMIC_CLOAKING,//EOAC_STATIC_CLOAKING, //EOAC_NONE,
+ NULL);
+ LogRelFunc(("VBoxSDS: InitializeSecurity: %x\n", hrcSec));
+
+ /*
+ * Instantiate our COM service class.
+ */
+ CComServiceModule *pServiceModule = new CComServiceModule();
+ if (pServiceModule)
+ {
+ BEGIN_OBJECT_MAP(s_aObjectMap)
+ OBJECT_ENTRY(CLSID_VirtualBoxSDS, VirtualBoxSDS)
+ END_OBJECT_MAP()
+ hrcExit = pServiceModule->init(s_aObjectMap, hInstance, &LIBID_VirtualBox,
+ L"VBoxSDS",
+ L"VirtualBox system service",
+ L"Used as a COM server for VirtualBox API.");
+
+ if (SUCCEEDED(hrcExit))
+ {
+ if (!fRun)
+ {
+ /*
+ * Do registration work and quit.
+ */
+ /// @todo The VBoxProxyStub should do all work for COM registration
+ if (fUnregister)
+ hrcExit = pServiceModule->unregisterService();
+ if (fRegister)
+ hrcExit = pServiceModule->registerService();
+ }
+ else
+ {
+ /*
+ * Run service.
+ */
+ CComServiceModule::s_pInstance = pServiceModule;
+ hrcExit = pServiceModule->startService(nShowCmd);
+ LogRelFunc(("VBoxSDS: Calling _ServiceModule.RevokeClassObjects()...\n"));
+ CComServiceModule::s_pInstance = NULL;
+ pServiceModule->RevokeClassObjects();
+ }
+
+ LogRelFunc(("VBoxSDS: Calling _ServiceModule.Term()...\n"));
+ pServiceModule->Term();
+ }
+ else
+ LogRelFunc(("VBoxSDS: new CComServiceModule::Init failed: %Rhrc\n", hrcExit));
+
+ delete pServiceModule;
+ pServiceModule = NULL;
+ }
+ else
+ LogRelFunc(("VBoxSDS: new CComServiceModule() failed\n"));
+
+ LogRelFunc(("VBoxSDS: Calling com::Shutdown\n"));
+ com::Shutdown();
+ }
+ else
+ LogRelFunc(("VBoxSDS: COM initialization failed: %Rrc\n", hrcExit));
+
+ LogRelFunc(("VBoxSDS: COM service process ends: hrcExit=%Rhrc (%#x)\n", hrcExit, hrcExit));
+ return (int)hrcExit;
+}
diff --git a/src/VBox/Main/src-global/win/VBoxSDS.rc b/src/VBox/Main/src-global/win/VBoxSDS.rc
new file mode 100644
index 00000000..7cc34092
--- /dev/null
+++ b/src/VBox/Main/src-global/win/VBoxSDS.rc
@@ -0,0 +1,88 @@
+/* $Id: VBoxSDS.rc $ */
+/** @file
+ * VBoxSDS - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+#include "win/resource.h"
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4" // Lang=US English, CharSet=Windows Multilingual
+ BEGIN
+ VALUE "FileDescription", "VirtualBox Global Interface\0"
+ VALUE "InternalName", "VBoxSDS\0"
+ VALUE "OriginalFilename", "VBoxSDS.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+
+ VALUE "OLESelfRegister", "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+/* Creates the application icon. */
+#include "VBoxSDS-icon.rc"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// REGISTRY
+//
+
+// IDR_VIRTUALBOX REGISTRY "VBoxSDS.rgs"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE DISCARDABLE
+BEGIN
+ IDS_SERVICENAME "VBoxSDS"
+ -100 "VirtualBox Global Service"
+ -101 "Workaround..."
+END
+
+1 TYPELIB "VirtualBox.tlb"
diff --git a/src/VBox/Main/src-global/win/VirtualBoxSDSImpl.cpp b/src/VBox/Main/src-global/win/VirtualBoxSDSImpl.cpp
new file mode 100644
index 00000000..b03bb5d6
--- /dev/null
+++ b/src/VBox/Main/src-global/win/VirtualBoxSDSImpl.cpp
@@ -0,0 +1,1371 @@
+/* $Id: VirtualBoxSDSImpl.cpp $ */
+/** @file
+ * VBox Global COM Class implementation.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_VIRTUALBOXSDS
+#include <VBox/com/VirtualBox.h>
+#include <VBox/com/utils.h>
+#include "VirtualBoxSDSImpl.h"
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "Wrapper.h" /* for ArrayBSTRInConverter */
+
+#include <iprt/errcore.h>
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/system.h>
+
+#include <rpcasync.h>
+#include <rpcdcep.h>
+#include <sddl.h>
+#include <lmcons.h> /* UNLEN */
+
+#include "MachineLaunchVMCommonWorker.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define INTERACTIVE_SID_FLAG 0x1
+#define LOCAL_SID_FLAG 0x2
+#define LOGON_SID_FLAG 0x4
+#define IS_INTERACTIVE (LOCAL_SID_FLAG|INTERACTIVE_SID_FLAG|LOGON_SID_FLAG)
+
+
+/**
+ * Per user data.
+ *
+ * @note We never delete instances of this class, except in case of an insertion
+ * race. This allows us to separate the map lock from the user data lock
+ * and avoid DoS issues.
+ */
+class VBoxSDSPerUserData
+{
+public:
+ /** The SID (secure identifier) for the user. This is the key. */
+ com::Utf8Str m_strUserSid;
+ /** The user name (if we could get it). */
+ com::Utf8Str m_strUsername;
+ /** The VBoxSVC chosen to instantiate CLSID_VirtualBox.
+ * This is NULL if not set. */
+ ComPtr<IVBoxSVCRegistration> m_ptrTheChosenOne;
+ /** The PID of the chosen one. */
+ RTPROCESS m_pidTheChosenOne;
+ /** The tick count when the process in Windows session 0 started */
+ uint32_t m_tickTheChosenOne;
+ /** The current watcher thread index, UINT32_MAX if not watched. */
+ uint32_t m_iWatcher;
+ /** The chosen one revision number.
+ * This is used to detect races while waiting for a full watcher queue. */
+ uint32_t volatile m_iTheChosenOneRevision;
+private:
+ /** Reference count to make destruction safe wrt hung callers.
+ * (References are retain while holding the map lock in some form, but
+ * released while holding no locks.) */
+ uint32_t volatile m_cRefs;
+ /** Critical section protecting everything here. */
+ RTCRITSECT m_Lock;
+
+public:
+ VBoxSDSPerUserData(com::Utf8Str const &a_rStrUserSid, com::Utf8Str const &a_rStrUsername)
+ : m_strUserSid(a_rStrUserSid)
+ , m_strUsername(a_rStrUsername)
+ , m_pidTheChosenOne(NIL_RTPROCESS)
+ , m_tickTheChosenOne(0)
+#ifdef WITH_WATCHER
+ , m_iWatcher(UINT32_MAX)
+ , m_iTheChosenOneRevision(0)
+#endif
+ , m_cRefs(1)
+ {
+ RTCritSectInit(&m_Lock);
+ }
+
+ ~VBoxSDSPerUserData()
+ {
+ RTCritSectDelete(&m_Lock);
+ i_unchooseTheOne(true /*fIrregular*/);
+ }
+
+ uint32_t i_retain()
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&m_cRefs);
+ Assert(cRefs > 1);
+ return cRefs;
+ }
+
+ uint32_t i_release()
+ {
+ uint32_t cRefs = ASMAtomicDecU32(&m_cRefs);
+ Assert(cRefs < _1K);
+ if (cRefs == 0)
+ delete this;
+ return cRefs;
+ }
+
+ void i_lock()
+ {
+ RTCritSectEnter(&m_Lock);
+ }
+
+ void i_unlock()
+ {
+ RTCritSectLeave(&m_Lock);
+ }
+
+ /** Reset the chosen one. */
+ void i_unchooseTheOne(bool fIrregular)
+ {
+ if (m_ptrTheChosenOne.isNotNull())
+ {
+ if (!fIrregular)
+ m_ptrTheChosenOne.setNull();
+ else
+ {
+ LogRel(("i_unchooseTheOne: Irregular release ... (pid=%d (%#x) user=%s sid=%s)\n",
+ m_pidTheChosenOne, m_pidTheChosenOne, m_strUsername.c_str(), m_strUserSid.c_str()));
+ m_ptrTheChosenOne.setNull();
+ LogRel(("i_unchooseTheOne: ... done.\n"));
+ }
+ }
+ m_pidTheChosenOne = NIL_RTPROCESS;
+ m_tickTheChosenOne = 0;
+ }
+
+};
+
+
+
+/*********************************************************************************************************************************
+* VirtualBoxSDS - constructor / destructor *
+*********************************************************************************************************************************/
+
+VirtualBoxSDS::VirtualBoxSDS()
+ : m_cVBoxSvcProcesses(0)
+#ifdef WITH_WATCHER
+ , m_cWatchers(0)
+ , m_papWatchers(NULL)
+#endif
+{
+}
+
+
+VirtualBoxSDS::~VirtualBoxSDS()
+{
+#ifdef WITH_WATCHER
+ i_shutdownAllWatchers();
+ RTMemFree(m_papWatchers);
+ m_papWatchers = NULL;
+ m_cWatchers = 0;
+#endif
+}
+
+
+HRESULT VirtualBoxSDS::FinalConstruct()
+{
+ LogRelFlowThisFuncEnter();
+
+ int vrc = RTCritSectRwInit(&m_MapCritSect);
+ AssertLogRelRCReturn(vrc, E_FAIL);
+
+#ifdef WITH_WATCHER
+ vrc = RTCritSectInit(&m_WatcherCritSect);
+ AssertLogRelRCReturn(vrc, E_FAIL);
+#endif
+
+ LogRelFlowThisFuncLeave();
+ return S_OK;
+}
+
+
+void VirtualBoxSDS::FinalRelease()
+{
+ LogRelFlowThisFuncEnter();
+
+#ifdef WITH_WATCHER
+ i_shutdownAllWatchers();
+ RTCritSectDelete(&m_WatcherCritSect);
+#endif
+
+ RTCritSectRwDelete(&m_MapCritSect);
+
+ for (UserDataMap_T::iterator it = m_UserDataMap.begin(); it != m_UserDataMap.end(); ++it)
+ {
+ VBoxSDSPerUserData *pUserData = it->second;
+ if (pUserData)
+ {
+ it->second = NULL;
+ pUserData->i_release();
+ }
+ }
+
+ LogRelFlowThisFuncLeave();
+}
+
+/* static */
+bool VirtualBoxSDS::i_isFeatureEnabled(wchar_t const *a_pwszFeature)
+{
+ HKEY hKey;
+ LSTATUS lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Oracle\\VirtualBox\\VBoxSDS", 0, KEY_READ, &hKey);
+ /* Treat any errors as the feature is off. Because the actual error value doesn't matter. */
+ if (lrc != ERROR_SUCCESS)
+ return false;
+
+ DWORD dwType = 0;
+ DWORD dwValue = 0;
+ DWORD cbValue = sizeof(DWORD);
+ lrc = RegQueryValueExW(hKey, a_pwszFeature, NULL, &dwType, (LPBYTE)&dwValue, &cbValue);
+
+ bool const fEnabled = lrc == ERROR_SUCCESS
+ && dwType == REG_DWORD
+ && dwValue != 0;
+
+ RegCloseKey(hKey);
+ return fEnabled;
+}
+
+
+/*********************************************************************************************************************************
+* VirtualBoxSDS - IVirtualBoxSDS methods *
+*********************************************************************************************************************************/
+
+/* SDS plan B interfaces: */
+STDMETHODIMP VirtualBoxSDS::RegisterVBoxSVC(IVBoxSVCRegistration *aVBoxSVC, LONG aPid, IUnknown **aExistingVirtualBox)
+{
+ LogRel(("registerVBoxSVC: aPid=%u (%#x)\n", aPid, aPid));
+
+ /*
+ * Get the caller PID so we can validate the aPid parameter with the other two.
+ * The V2 structure requires Vista or later, so fake it if older.
+ */
+ RPC_CALL_ATTRIBUTES_V2_W CallAttribs = { RPC_CALL_ATTRIBUTES_VERSION, RPC_QUERY_CLIENT_PID | RPC_QUERY_IS_CLIENT_LOCAL };
+ RPC_STATUS rcRpc;
+ if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0))
+ rcRpc = RpcServerInqCallAttributesW(NULL, &CallAttribs);
+ else
+ {
+ CallAttribs.ClientPID = (HANDLE)(intptr_t)aPid;
+ rcRpc = RPC_S_OK;
+ }
+
+ HRESULT hrc;
+ if ( RT_VALID_PTR(aVBoxSVC)
+ && RT_VALID_PTR(aExistingVirtualBox)
+ && rcRpc == RPC_S_OK
+ && (intptr_t)CallAttribs.ClientPID == aPid)
+ {
+ *aExistingVirtualBox = NULL;
+
+ /*
+ * Get the client user SID and name.
+ */
+ com::Utf8Str strSid;
+ com::Utf8Str strUsername;
+ if (i_getClientUserSid(&strSid, &strUsername))
+ {
+ VBoxSDSPerUserData *pUserData = i_lookupOrCreatePerUserData(strSid, strUsername); /* (returns holding the lock) */
+ if (pUserData)
+ {
+ /*
+ * If there already is a chosen one, ask it for a IVirtualBox instance
+ * to return to the caller. Should it be dead or unresponsive, the caller
+ * takes its place.
+ */
+ if (pUserData->m_ptrTheChosenOne.isNotNull())
+ {
+ try
+ {
+ hrc = pUserData->m_ptrTheChosenOne->GetVirtualBox(aExistingVirtualBox);
+ /* seems the VBoxSVC in windows session 0 is not yet finished object creation.
+ * Give it a time. */
+ if (FAILED(hrc) && GetTickCount() - pUserData->m_tickTheChosenOne < 60 * 1000)
+ hrc = E_PENDING;
+ }
+ catch (...)
+ {
+ LogRel(("registerVBoxSVC: Unexpected exception calling GetVirtualBox!!\n"));
+ hrc = E_FAIL;
+ }
+ if (FAILED_DEAD_INTERFACE(hrc))
+ {
+ LogRel(("registerVBoxSVC: Seems VBoxSVC instance died. Dropping it and letting caller take over. (hrc=%Rhrc)\n", hrc));
+#ifdef WITH_WATCHER
+ i_stopWatching(pUserData, pUserData->m_pidTheChosenOne);
+#endif
+ pUserData->i_unchooseTheOne(true /*fIrregular*/);
+ hrc = S_OK;
+ }
+ }
+ else
+ hrc = S_OK;
+
+ /* No chosen one? Make the caller the new chosen one! */
+ if (SUCCEEDED(hrc) && pUserData->m_ptrTheChosenOne.isNull())
+ {
+#ifdef VBOX_WITH_VBOXSVC_SESSION_0
+ DWORD dwSessionId = 0;
+ if (VirtualBoxSDS::i_isFeatureEnabled(L"ServerSession0"))
+ {
+ /* Get user token. */
+ HANDLE hThreadToken = NULL;
+ hrc = CoImpersonateClient();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = E_FAIL;
+ if (OpenThreadToken(GetCurrentThread(),
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE
+ | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE,
+ TRUE /* OpenAsSelf - for impersonation at SecurityIdentification level */,
+ &hThreadToken))
+ {
+ HANDLE hNewToken;
+ if (DuplicateTokenEx(hThreadToken, MAXIMUM_ALLOWED, NULL /*SecurityAttribs*/,
+ SecurityIdentification, TokenPrimary, &hNewToken))
+ {
+ CloseHandle(hThreadToken);
+ hThreadToken = hNewToken;
+ hrc = S_OK;
+ }
+ else
+ LogRel(("registerVBoxSVC: DuplicateTokenEx failed: %ld\n", GetLastError()));
+ }
+ else
+ LogRel(("registerVBoxSVC: OpenThreadToken failed: %ld\n", GetLastError()));
+
+ CoRevertToSelf();
+ }
+ else
+ LogRel(("registerVBoxSVC: CoImpersonateClient failed: %Rhrc\n", hrc));
+
+ /* check windows session */
+ if (SUCCEEDED(hrc) && hThreadToken != NULL)
+ {
+ hrc = E_FAIL;
+ DWORD cbSessionId = sizeof(DWORD);
+ if (GetTokenInformation(hThreadToken, TokenSessionId, (LPVOID)&dwSessionId, cbSessionId, &cbSessionId))
+ {
+ if (cbSessionId == sizeof(DWORD))
+ hrc = S_OK;
+ else
+ LogRel(("registerVBoxSVC: GetTokenInformation return value has invalid size\n"));
+ }
+ else
+ LogRel(("registerVBoxSVC: GetTokenInformation failed: %Rwc\n", GetLastError()));
+ }
+
+ /* Either the "VBoxSVC in windows session 0" feature is off or the request from VBoxSVC running
+ * in windows session 0. */
+ if (SUCCEEDED(hrc) && dwSessionId != 0)
+ {
+ /* if VBoxSVC in the Windows session 0 is not started or if it did not
+ * registered during a minute, start new one */
+ if ( pUserData->m_pidTheChosenOne == NIL_RTPROCESS
+ || GetTickCount() - pUserData->m_tickTheChosenOne > 60 * 1000)
+ {
+ uint32_t uSessionId = 0;
+ if (SetTokenInformation(hThreadToken, TokenSessionId, &uSessionId, sizeof(uint32_t)))
+ {
+ /*
+ * Start VBoxSVC process
+ */
+ char szPath[RTPATH_MAX];
+ int vrc = RTPathAppPrivateArch(szPath, sizeof(szPath));
+ AssertRCReturn(vrc, vrc);
+
+ size_t cbBufLeft = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath));
+ AssertReturn(cbBufLeft > 0, VERR_FILENAME_TOO_LONG);
+
+ char *pszNamePart = &szPath[cbBufLeft];
+ cbBufLeft = sizeof(szPath) - cbBufLeft;
+
+ static const char s_szVirtualBox_exe[] = "VBoxSVC.exe";
+ vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVirtualBox_exe);
+ AssertRCReturn(vrc, vrc);
+
+ const char *apszArgs[] =
+ {
+ szPath,
+ "--registervbox",
+ NULL
+ };
+
+ RTPROCESS pid;
+ vrc = RTProcCreateEx(szPath,
+ apszArgs,
+ RTENV_DEFAULT,
+ RTPROC_FLAGS_TOKEN_SUPPLIED,
+ NULL, NULL, NULL, NULL, NULL, &hThreadToken, &pid);
+
+ if (RT_SUCCESS(vrc))
+ {
+ pUserData->m_pidTheChosenOne = pid;
+ pUserData->m_tickTheChosenOne = GetTickCount();
+ hrc = E_PENDING;
+ }
+ else
+ LogRel(("registerVBoxSVC: Create VBoxSVC process failed: %Rrc\n", vrc));
+ }
+ else
+ {
+ hrc = E_FAIL;
+ LogRel(("registerVBoxSVC: SetTokenInformation failed: %ld\n", GetLastError()));
+ }
+ }
+ else /* the VBoxSVC in Windows session 0 already started */
+ hrc = E_PENDING;
+ }
+ CloseHandle(hThreadToken);
+ } /* Feature enabled */
+
+ if (SUCCEEDED(hrc) && dwSessionId == 0)
+ {
+#endif
+ LogRel(("registerVBoxSVC: Making aPid=%u (%#x) the chosen one for user %s (%s)!\n",
+ aPid, aPid, pUserData->m_strUserSid.c_str(), pUserData->m_strUsername.c_str()));
+#ifdef WITH_WATCHER
+ /* Open the process so we can watch it. */
+ HANDLE hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, FALSE /*fInherit*/, aPid);
+ if (hProcess == NULL)
+ hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE /*fInherit*/, aPid);
+ if (hProcess == NULL)
+ hProcess = OpenProcess(SYNCHRONIZE, FALSE /*fInherit*/, aPid);
+ if (hProcess != NULL)
+ {
+ if (i_watchIt(pUserData, hProcess, aPid))
+#endif
+ {
+ /* Make it official... */
+ pUserData->m_ptrTheChosenOne = aVBoxSVC;
+ pUserData->m_pidTheChosenOne = aPid;
+ hrc = S_OK;
+ }
+#ifdef WITH_WATCHER
+ else
+ {
+
+ LogRel(("registerVBoxSVC: i_watchIt failed!\n"));
+ hrc = RPC_E_OUT_OF_RESOURCES;
+ }
+ }
+ else
+ {
+ LogRel(("registerVBoxSVC: OpenProcess() failed: %Rwc\n", GetLastError()));
+ hrc = E_ACCESSDENIED;
+ }
+#endif
+#ifdef VBOX_WITH_VBOXSVC_SESSION_0
+ }
+#endif
+ }
+ pUserData->i_unlock();
+ pUserData->i_release();
+ }
+ else
+ hrc = E_OUTOFMEMORY;
+ }
+ else
+ hrc = E_FAIL;
+ }
+ else if ( !RT_VALID_PTR(aVBoxSVC)
+ || !RT_VALID_PTR(aExistingVirtualBox))
+ hrc = E_INVALIDARG;
+ else if (rcRpc != RPC_S_OK)
+ {
+ LogRel(("registerVBoxSVC: rcRpc=%d (%#x)!\n", rcRpc, rcRpc));
+ hrc = E_UNEXPECTED;
+ }
+ else
+ {
+ LogRel(("registerVBoxSVC: Client PID mismatch: aPid=%d (%#x), RPC ClientPID=%zd (%#zx)\n",
+ aPid, aPid, CallAttribs.ClientPID, CallAttribs.ClientPID));
+ hrc = E_INVALIDARG;
+ }
+ LogRel2(("VirtualBoxSDS::registerVBoxSVC: returns %Rhrc\n", hrc));
+ return hrc;
+}
+
+
+STDMETHODIMP VirtualBoxSDS::DeregisterVBoxSVC(IVBoxSVCRegistration *aVBoxSVC, LONG aPid)
+{
+ LogRel(("deregisterVBoxSVC: aPid=%u (%#x)\n", aPid, aPid));
+ HRESULT hrc;
+ if (RT_VALID_PTR(aVBoxSVC))
+ {
+ /* Get the client user SID and name. */
+ com::Utf8Str strSid;
+ com::Utf8Str strUsername;
+ if (i_getClientUserSid(&strSid, &strUsername))
+ {
+ VBoxSDSPerUserData *pUserData = i_lookupPerUserData(strSid);
+ if (pUserData)
+ {
+ if (aVBoxSVC == (IVBoxSVCRegistration *)pUserData->m_ptrTheChosenOne)
+ {
+ LogRel(("deregisterVBoxSVC: It's the chosen one for %s (%s)!\n",
+ pUserData->m_strUserSid.c_str(), pUserData->m_strUsername.c_str()));
+#ifdef WITH_WATCHER
+ i_stopWatching(pUserData, pUserData->m_pidTheChosenOne);
+#endif
+ pUserData->i_unchooseTheOne(false /*fIrregular*/);
+ }
+ else
+ LogRel(("deregisterVBoxSVC: not the chosen one\n"));
+ pUserData->i_unlock();
+ pUserData->i_release();
+
+ hrc = S_OK;
+ }
+ else
+ {
+ LogRel(("deregisterVBoxSVC: Found no user data for %s (%s) (pid %u)\n",
+ strSid.c_str(), strUsername.c_str(), aPid));
+ hrc = S_OK;
+ }
+ }
+ else
+ hrc = E_FAIL;
+ }
+ else
+ hrc = E_INVALIDARG;
+ LogRel2(("VirtualBoxSDS::deregisterVBoxSVC: returns %Rhrc\n", hrc));
+ return hrc;
+}
+
+
+STDMETHODIMP VirtualBoxSDS::LaunchVMProcess(IN_BSTR aMachine, IN_BSTR aComment, IN_BSTR aFrontend,
+ ComSafeArrayIn(IN_BSTR, aEnvironmentChanges),
+ IN_BSTR aCmdOptions, ULONG aSessionId, ULONG *aPid)
+{
+ /*
+ * Convert parameters to UTF-8.
+ */
+ Utf8Str strMachine(aMachine);
+ Utf8Str strComment(aComment);
+ Utf8Str strFrontend(aFrontend);
+ ArrayBSTRInConverter aStrEnvironmentChanges(ComSafeArrayInArg(aEnvironmentChanges));
+ Utf8Str strCmdOptions(aCmdOptions);
+
+ /*
+ * Impersonate the caller.
+ */
+ HRESULT hrc = CoImpersonateClient();
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ /*
+ * Try launch the VM process as the client.
+ */
+ RTPROCESS pid;
+ AssertCompile(sizeof(aSessionId) == sizeof(uint32_t));
+ int vrc = ::MachineLaunchVMCommonWorker(strMachine, strComment, strFrontend, aStrEnvironmentChanges.array(),
+ strCmdOptions, Utf8Str(),
+ RTPROC_FLAGS_AS_IMPERSONATED_TOKEN | RTPROC_FLAGS_SERVICE
+ | RTPROC_FLAGS_PROFILE | RTPROC_FLAGS_DESIRED_SESSION_ID,
+ &aSessionId, pid);
+ if (RT_SUCCESS(vrc))
+ {
+ *aPid = (ULONG)pid;
+ LogRel(("VirtualBoxSDS::LaunchVMProcess: launchVM succeeded\n"));
+ }
+ else if (vrc == VERR_INVALID_PARAMETER)
+ {
+ hrc = E_INVALIDARG;
+ LogRel(("VirtualBoxSDS::LaunchVMProcess: launchVM failed: %Rhrc\n", hrc));
+ }
+ else
+ {
+ hrc = VBOX_E_IPRT_ERROR;
+ LogRel(("VirtualBoxSDS::LaunchVMProcess: launchVM failed: %Rhrc (%Rrc)\n", hrc));
+ }
+ }
+ catch (...)
+ {
+ hrc = E_UNEXPECTED;
+ }
+ CoRevertToSelf();
+ }
+ else
+ LogRel(("VirtualBoxSDS::LaunchVMProcess: CoImpersonateClient failed: %Rhrc\n", hrc));
+ return hrc;
+}
+
+
+/*********************************************************************************************************************************
+* VirtualBoxSDS - Internal Methods *
+*********************************************************************************************************************************/
+
+/*static*/ bool VirtualBoxSDS::i_getClientUserSid(com::Utf8Str *a_pStrSid, com::Utf8Str *a_pStrUsername)
+{
+ bool fRet = false;
+ a_pStrSid->setNull();
+ a_pStrUsername->setNull();
+
+ HRESULT hrc = CoImpersonateClient();
+ if (SUCCEEDED(hrc))
+ {
+ HANDLE hToken = INVALID_HANDLE_VALUE;
+ if (::OpenThreadToken(GetCurrentThread(), TOKEN_READ, TRUE /*OpenAsSelf*/, &hToken))
+ {
+ CoRevertToSelf();
+
+ union
+ {
+ TOKEN_USER TokenUser;
+ uint8_t abPadding[SECURITY_MAX_SID_SIZE + 256];
+ WCHAR wszUsername[UNLEN + 1];
+ } uBuf;
+ RT_ZERO(uBuf);
+ DWORD cbActual = 0;
+ if (::GetTokenInformation(hToken, TokenUser, &uBuf, sizeof(uBuf), &cbActual))
+ {
+ WCHAR *pwszString;
+ if (ConvertSidToStringSidW(uBuf.TokenUser.User.Sid, &pwszString))
+ {
+ try
+ {
+ *a_pStrSid = pwszString;
+ a_pStrSid->toUpper(); /* (just to be on the safe side) */
+ fRet = true;
+ }
+ catch (std::bad_alloc &)
+ {
+ LogRel(("i_GetClientUserSID: std::bad_alloc setting rstrSid.\n"));
+ }
+ LocalFree((HLOCAL)pwszString);
+
+ /*
+ * Get the username too. We don't care if this step fails.
+ */
+ if (fRet)
+ {
+ WCHAR wszUsername[UNLEN * 2 + 1];
+ DWORD cwcUsername = RT_ELEMENTS(wszUsername);
+ WCHAR wszDomain[UNLEN * 2 + 1];
+ DWORD cwcDomain = RT_ELEMENTS(wszDomain);
+ SID_NAME_USE enmNameUse;
+ if (LookupAccountSidW(NULL, uBuf.TokenUser.User.Sid, wszUsername, &cwcUsername,
+ wszDomain, &cwcDomain, &enmNameUse))
+ {
+ wszUsername[RT_ELEMENTS(wszUsername) - 1] = '\0';
+ wszDomain[RT_ELEMENTS(wszDomain) - 1] = '\0';
+ try
+ {
+ *a_pStrUsername = wszDomain;
+ a_pStrUsername->append('/');
+ a_pStrUsername->append(Utf8Str(wszUsername));
+ }
+ catch (std::bad_alloc &)
+ {
+ LogRel(("i_GetClientUserSID: std::bad_alloc setting rStrUsername.\n"));
+ a_pStrUsername->setNull();
+ }
+ }
+ else
+ LogRel(("i_GetClientUserSID: LookupAccountSidW failed: %u/%x (cwcUsername=%u, cwcDomain=%u)\n",
+ GetLastError(), cwcUsername, cwcDomain));
+ }
+ }
+ else
+ LogRel(("i_GetClientUserSID: ConvertSidToStringSidW failed: %u\n", GetLastError()));
+ }
+ else
+ LogRel(("i_GetClientUserSID: GetTokenInformation/TokenUser failed: %u\n", GetLastError()));
+ CloseHandle(hToken);
+ }
+ else
+ {
+ CoRevertToSelf();
+ LogRel(("i_GetClientUserSID: OpenThreadToken failed: %u\n", GetLastError()));
+ }
+ }
+ else
+ LogRel(("i_GetClientUserSID: CoImpersonateClient failed: %Rhrc\n", hrc));
+ return fRet;
+}
+
+
+/**
+ * Looks up the given user.
+ *
+ * @returns Pointer to the LOCKED and RETAINED per user data.
+ * NULL if not found.
+ * @param a_rStrUserSid The user SID.
+ */
+VBoxSDSPerUserData *VirtualBoxSDS::i_lookupPerUserData(com::Utf8Str const &a_rStrUserSid)
+{
+ int vrc = RTCritSectRwEnterShared(&m_MapCritSect);
+ if (RT_SUCCESS(vrc))
+ {
+
+ UserDataMap_T::iterator it = m_UserDataMap.find(a_rStrUserSid);
+ if (it != m_UserDataMap.end())
+ {
+ VBoxSDSPerUserData *pUserData = it->second;
+ pUserData->i_retain();
+
+ RTCritSectRwLeaveShared(&m_MapCritSect);
+
+ pUserData->i_lock();
+ return pUserData;
+ }
+
+ RTCritSectRwLeaveShared(&m_MapCritSect);
+ }
+ return NULL;
+}
+
+
+/**
+ * Looks up the given user, creating it if not found
+ *
+ * @returns Pointer to the LOCKED and RETAINED per user data.
+ * NULL on allocation error.
+ * @param a_rStrUserSid The user SID.
+ * @param a_rStrUsername The user name if available.
+ */
+VBoxSDSPerUserData *VirtualBoxSDS::i_lookupOrCreatePerUserData(com::Utf8Str const &a_rStrUserSid,
+ com::Utf8Str const &a_rStrUsername)
+{
+ /*
+ * Try do a simple lookup first.
+ */
+ VBoxSDSPerUserData *pUserData = i_lookupPerUserData(a_rStrUserSid);
+ if (!pUserData)
+ {
+ /*
+ * SID is not in map, create a new one.
+ */
+ try
+ {
+ pUserData = new VBoxSDSPerUserData(a_rStrUserSid, a_rStrUsername);
+ }
+ catch (std::bad_alloc &)
+ {
+ pUserData = NULL;
+ }
+ if (pUserData)
+ {
+ /*
+ * Insert it. We must check if someone raced us here.
+ */
+ VBoxSDSPerUserData *pUserDataFree = pUserData;
+ pUserData->i_lock();
+
+ int vrc = RTCritSectRwEnterExcl(&m_MapCritSect);
+ if (RT_SUCCESS(vrc))
+ {
+
+ UserDataMap_T::iterator it = m_UserDataMap.find(a_rStrUserSid);
+ if (it == m_UserDataMap.end())
+ {
+ try
+ {
+ m_UserDataMap[a_rStrUserSid] = pUserData;
+ pUserData->i_retain();
+ }
+ catch (std::bad_alloc &)
+ {
+ pUserData = NULL;
+ }
+ }
+ else
+ pUserData = NULL;
+
+ RTCritSectRwLeaveExcl(&m_MapCritSect);
+
+ if (pUserData)
+ LogRel(("i_lookupOrCreatePerUserData: Created new entry for %s (%s)\n",
+ pUserData->m_strUserSid.c_str(), pUserData->m_strUsername.c_str() ));
+ else
+ {
+ pUserDataFree->i_unlock();
+ delete pUserDataFree;
+ }
+ }
+ }
+ }
+
+ return pUserData;
+}
+
+
+#ifdef WITH_WATCHER
+/**
+ * Data about what's being watched.
+ */
+typedef struct VBoxSDSWatcherData
+{
+ /** The per-user data (referenced). */
+ VBoxSDSPerUserData *pUserData;
+ /** The chosen one revision number (for handling an almost impossible race
+ * where a client terminates while making a deregistration call). */
+ uint32_t iRevision;
+ /** The PID we're watching. */
+ RTPROCESS pid;
+
+ /** Sets the members to NULL values. */
+ void setNull()
+ {
+ pUserData = NULL;
+ iRevision = UINT32_MAX;
+ pid = NIL_RTPROCESS;
+ }
+} VBoxSDSWatcherData;
+
+/**
+ * Per watcher data.
+ */
+typedef struct VBoxSDSWatcher
+{
+ /** Pointer to the VBoxSDS instance. */
+ VirtualBoxSDS *pVBoxSDS;
+ /** The thread handle. */
+ RTTHREAD hThread;
+ /** Number of references to this structure. */
+ uint32_t volatile cRefs;
+ /** Set if the thread should shut down. */
+ bool volatile fShutdown;
+ /** Number of pending items in the todo array. */
+ uint32_t cTodos;
+ /** The watcher number. */
+ uint32_t iWatcher;
+ /** The number of handles once TODOs have been taken into account. */
+ uint32_t cHandlesEffective;
+ /** Number of handles / user data items being monitored. */
+ uint32_t cHandles;
+ /** Array of handles.
+ * The zero'th entry is the event semaphore use to signal the thread. */
+ HANDLE aHandles[MAXIMUM_WAIT_OBJECTS];
+ /** Array the runs parallel to aHandles with the VBoxSVC data. */
+ VBoxSDSWatcherData aData[MAXIMUM_WAIT_OBJECTS];
+ /** Pending changes. */
+ struct
+ {
+ /** If NULL the data is being removed, otherwise it's being added and
+ * this is the process handle to watch for termination. */
+ HANDLE hProcess;
+ /** The data about what's being watched. */
+ VBoxSDSWatcherData Data;
+ } aTodos[MAXIMUM_WAIT_OBJECTS * 4];
+
+
+ /** Helper for removing a handle & data table entry. */
+ uint32_t removeHandle(uint32_t a_iEntry, uint32_t a_cHandles)
+ {
+ uint32_t cToShift = a_cHandles - a_iEntry - 1;
+ if (cToShift > 0)
+ {
+ memmove(&aData[a_iEntry], &aData[a_iEntry + 1], sizeof(aData[0]) * cToShift);
+ memmove(&aHandles[a_iEntry], &aHandles[a_iEntry + 1], sizeof(aHandles[0]) * cToShift);
+ }
+ a_cHandles--;
+ aHandles[a_cHandles] = NULL;
+ aData[a_cHandles].setNull();
+
+ return a_cHandles;
+ }
+} VBoxSDSWatcher;
+
+
+
+/**
+ * Watcher thread.
+ */
+/*static*/ DECLCALLBACK(int) VirtualBoxSDS::i_watcherThreadProc(RTTHREAD hSelf, void *pvUser)
+{
+ VBoxSDSWatcher *pThis = (VBoxSDSWatcher *)pvUser;
+ VirtualBoxSDS *pVBoxSDS = pThis->pVBoxSDS;
+ RT_NOREF(hSelf);
+
+ /*
+ * This thread may release references to IVBoxSVCRegistration objects.
+ */
+ CoInitializeEx(NULL, COINIT_MULTITHREADED);
+
+ /*
+ * The loop.
+ */
+ RTCritSectEnter(&pVBoxSDS->m_WatcherCritSect);
+ while (!pThis->fShutdown)
+ {
+ /*
+ * Deal with the todo list.
+ */
+ uint32_t cHandles = pThis->cHandles;
+ uint32_t cTodos = pThis->cTodos;
+
+ for (uint32_t i = 0; i < cTodos; i++)
+ {
+ VBoxSDSPerUserData *pUserData = pThis->aTodos[i].Data.pUserData;
+ AssertContinue(pUserData);
+ if (pThis->aTodos[i].hProcess != NULL)
+ {
+ /* Add: */
+ AssertLogRelMsgBreakStmt(cHandles < RT_ELEMENTS(pThis->aHandles),
+ ("cHandles=%u cTodos=%u i=%u iWatcher=%u\n", cHandles, cTodos, i, pThis->iWatcher),
+ pThis->fShutdown = true);
+ pThis->aHandles[cHandles] = pThis->aTodos[i].hProcess;
+ pThis->aData[cHandles] = pThis->aTodos[i].Data;
+ cHandles++;
+ }
+ else
+ {
+ /* Remove: */
+ uint32_t cRemoved = 0;
+ uint32_t j = cHandles;
+ while (j-- > 1)
+ if (pThis->aData[j].pUserData == pUserData)
+ {
+ cHandles = pThis->removeHandle(j, cHandles);
+ pUserData->i_release();
+ cRemoved++;
+ }
+ if (cRemoved != 1)
+ LogRel(("i_watcherThreadProc/#%u: Warning! cRemoved=%u\n", pThis->iWatcher, cRemoved));
+ }
+ /* Zap the entry in case we assert and leave further up. */
+ pThis->aTodos[i].Data.setNull();
+ pThis->aTodos[i].hProcess = NULL;
+ }
+
+ Assert(cHandles > 0 && cHandles <= RT_ELEMENTS(pThis->aHandles));
+ pThis->cHandles = cHandles;
+ pThis->cHandlesEffective = cHandles;
+ pThis->cTodos = 0;
+
+ if (pThis->fShutdown)
+ break;
+
+ /*
+ * Wait.
+ */
+ RTCritSectLeave(&pVBoxSDS->m_WatcherCritSect);
+
+ LogRel(("i_watcherThreadProc/#%u: Waiting on %u handles...\n", pThis->iWatcher, cHandles));
+ DWORD const dwWait = WaitForMultipleObjects(cHandles, pThis->aHandles, FALSE /*fWaitAll*/, INFINITE);
+ LogRel(("i_watcherThreadProc/#%u: ... wait returned: %#x (%d)\n", pThis->iWatcher, dwWait, dwWait));
+
+ uint32_t const iHandle = dwWait - WAIT_OBJECT_0;
+ if (iHandle < cHandles && iHandle > 0)
+ {
+ /*
+ * A VBoxSVC process has terminated.
+ *
+ * Note! We need to take the user data lock before the watcher one here.
+ */
+ VBoxSDSPerUserData * const pUserData = pThis->aData[iHandle].pUserData;
+ uint32_t const iRevision = pThis->aData[iHandle].iRevision;
+ RTPROCESS const pid = pThis->aData[iHandle].pid;
+
+ pUserData->i_lock();
+ RTCritSectEnter(&pVBoxSDS->m_WatcherCritSect);
+
+ DWORD dwExit = 0;
+ GetExitCodeProcess(pThis->aHandles[iHandle], &dwExit);
+ LogRel(("i_watcherThreadProc/#%u: %s: PID %u/%#x termination detected: %d (%#x) [iRev=%u, cur %u]\n",
+ pThis->iWatcher, pUserData->m_strUsername.c_str(), pid, pid, dwExit, dwExit,
+ iRevision, pUserData->m_iTheChosenOneRevision));
+
+ /* Remove it from the handle array. */
+ CloseHandle(pThis->aHandles[iHandle]);
+ pThis->cHandles = cHandles = pThis->removeHandle(iHandle, cHandles);
+ pThis->cHandlesEffective -= 1;
+
+ /* If the process we were watching is still the current chosen one,
+ unchoose it and decrement the client count. Otherwise we were subject
+ to a deregistration/termination race (unlikely). */
+ if (pUserData->m_iTheChosenOneRevision == iRevision)
+ {
+ pUserData->i_unchooseTheOne(true /*fIrregular*/);
+ pUserData->i_unlock();
+ pVBoxSDS->i_decrementClientCount();
+ }
+ else
+ pUserData->i_unlock();
+ pUserData->i_release();
+ }
+ else
+ {
+ RTCritSectEnter(&pThis->pVBoxSDS->m_WatcherCritSect);
+ AssertLogRelMsgBreak(iHandle == 0 || dwWait == WAIT_TIMEOUT,
+ ("dwWait=%u (%#x) cHandles=%u\n", dwWait, dwWait, cHandles));
+ }
+ }
+
+ RTCritSectLeave(&pThis->pVBoxSDS->m_WatcherCritSect);
+
+ /*
+ * In case we quit w/o being told, signal i_watchIt that we're out of action.
+ */
+ pThis->fShutdown = true;
+
+ /*
+ * Release all our data on the way out.
+ */
+ uint32_t i = pThis->cHandles;
+ while (i-- > 1)
+ {
+ if (pThis->aData[i].pUserData)
+ {
+ pThis->aData[i].pUserData->i_release();
+ pThis->aData[i].pUserData = NULL;
+ }
+ if (pThis->aHandles[i])
+ {
+ CloseHandle(pThis->aHandles[i]);
+ pThis->aHandles[i] = NULL;
+ }
+ }
+ if (pThis->aHandles[0])
+ {
+ CloseHandle(pThis->aHandles[0]);
+ pThis->aHandles[0] = NULL;
+ }
+
+ i = pThis->cTodos;
+ pThis->cTodos = 0;
+ while (i-- > 0)
+ {
+ if (pThis->aTodos[i].Data.pUserData)
+ {
+ pThis->aTodos[i].Data.pUserData->i_release();
+ pThis->aTodos[i].Data.pUserData = NULL;
+ }
+ if (pThis->aTodos[i].hProcess)
+ {
+ CloseHandle(pThis->aTodos[i].hProcess);
+ pThis->aTodos[i].hProcess = NULL;
+ }
+ }
+
+ if (ASMAtomicDecU32(&pThis->cRefs) == 0)
+ RTMemFree(pThis);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Starts monitoring a VBoxSVC process.
+ *
+ * @param pUserData The user which chosen VBoxSVC should be watched.
+ * @param hProcess Handle to the VBoxSVC process. Consumed.
+ * @param pid The VBoxSVC PID.
+ * @returns Success indicator.
+ */
+bool VirtualBoxSDS::i_watchIt(VBoxSDSPerUserData *pUserData, HANDLE hProcess, RTPROCESS pid)
+{
+ RTCritSectEnter(&m_WatcherCritSect);
+
+ /*
+ * Find a watcher with capacity left over (we save 8 entries for removals).
+ */
+ for (uint32_t i = 0; i < m_cWatchers; i++)
+ {
+ VBoxSDSWatcher *pWatcher = m_papWatchers[i];
+ if ( pWatcher->cHandlesEffective < RT_ELEMENTS(pWatcher->aHandles)
+ && !pWatcher->fShutdown)
+ {
+ uint32_t iTodo = pWatcher->cTodos;
+ if (iTodo + 8 < RT_ELEMENTS(pWatcher->aTodos))
+ {
+ pWatcher->aTodos[iTodo].hProcess = hProcess;
+ pWatcher->aTodos[iTodo].Data.pUserData = pUserData;
+ pWatcher->aTodos[iTodo].Data.iRevision = ++pUserData->m_iTheChosenOneRevision;
+ pWatcher->aTodos[iTodo].Data.pid = pid;
+ pWatcher->cTodos = iTodo + 1;
+
+ pUserData->m_iWatcher = pWatcher->iWatcher;
+ pUserData->i_retain();
+
+ BOOL fRc = SetEvent(pWatcher->aHandles[0]);
+ AssertLogRelMsg(fRc, ("SetEvent() failed: %u\n", GetLastError()));
+ LogRel(("i_watchIt: Added process to watcher #%u: %RTbool\n", pWatcher->iWatcher, fRc));
+
+ i_incrementClientCount();
+ RTCritSectLeave(&m_WatcherCritSect);
+ RTThreadYield();
+ return true;
+ }
+ }
+ }
+
+ /*
+ * No watcher with capacity was found, so create a new one with
+ * the user/handle prequeued.
+ */
+ void *pvNew = RTMemRealloc(m_papWatchers, sizeof(m_papWatchers[0]) * (m_cWatchers + 1));
+ if (pvNew)
+ {
+ m_papWatchers = (VBoxSDSWatcher **)pvNew;
+ VBoxSDSWatcher *pWatcher = (VBoxSDSWatcher *)RTMemAllocZ(sizeof(*pWatcher));
+ if (pWatcher)
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(pWatcher->aData); i++)
+ pWatcher->aData[i].setNull();
+ for (uint32_t i = 0; i < RT_ELEMENTS(pWatcher->aTodos); i++)
+ pWatcher->aTodos[i].Data.setNull();
+
+ pWatcher->pVBoxSDS = this;
+ pWatcher->iWatcher = m_cWatchers;
+ pWatcher->cRefs = 2;
+ pWatcher->cHandlesEffective = 2;
+ pWatcher->cHandles = 2;
+ pWatcher->aHandles[0] = CreateEventW(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL);
+ if (pWatcher->aHandles[0])
+ {
+ /* Add incoming VBoxSVC process in slot #1: */
+ pWatcher->aHandles[1] = hProcess;
+ pWatcher->aData[1].pid = pid;
+ pWatcher->aData[1].pUserData = pUserData;
+ pWatcher->aData[1].iRevision = ++pUserData->m_iTheChosenOneRevision;
+ pUserData->i_retain();
+ pUserData->m_iWatcher = pWatcher->iWatcher;
+
+ /* Start the thread and we're good. */
+ m_papWatchers[m_cWatchers++] = pWatcher;
+ int rc = RTThreadCreateF(&pWatcher->hThread, i_watcherThreadProc, pWatcher, 0, RTTHREADTYPE_MAIN_WORKER,
+ RTTHREADFLAGS_WAITABLE, "watcher%u", pWatcher->iWatcher);
+ if (RT_SUCCESS(rc))
+ {
+ LogRel(("i_watchIt: Created new watcher #%u\n", m_cWatchers));
+
+ i_incrementClientCount();
+ RTCritSectLeave(&m_WatcherCritSect);
+ return true;
+ }
+
+ LogRel(("i_watchIt: Error starting watcher thread: %Rrc\n", rc));
+ m_papWatchers[--m_cWatchers] = NULL;
+
+ pUserData->m_iWatcher = UINT32_MAX;
+ pUserData->i_release();
+ CloseHandle(pWatcher->aHandles[0]);
+ }
+ else
+ LogRel(("i_watchIt: CreateEventW failed: %u\n", GetLastError()));
+ RTMemFree(pWatcher);
+ }
+ else
+ LogRel(("i_watchIt: failed to allocate watcher structure!\n"));
+ }
+ else
+ LogRel(("i_watchIt: Failed to grow watcher array to %u entries!\n", m_cWatchers + 1));
+
+ RTCritSectLeave(&m_WatcherCritSect);
+ CloseHandle(hProcess);
+ return false;
+}
+
+
+/**
+ * Stops monitoring a VBoxSVC process.
+ *
+ * @param pUserData The user which chosen VBoxSVC should be watched.
+ * @param pid The VBoxSVC PID.
+ */
+void VirtualBoxSDS::i_stopWatching(VBoxSDSPerUserData *pUserData, RTPROCESS pid)
+{
+ /*
+ * Add a remove order in the watcher's todo queue.
+ */
+ RTCritSectEnter(&m_WatcherCritSect);
+ for (uint32_t iRound = 0; ; iRound++)
+ {
+ uint32_t const iWatcher = pUserData->m_iWatcher;
+ if (iWatcher < m_cWatchers)
+ {
+ VBoxSDSWatcher *pWatcher = m_papWatchers[pUserData->m_iWatcher];
+ if (!pWatcher->fShutdown)
+ {
+ /*
+ * Remove duplicate todo entries.
+ */
+ bool fAddIt = true;
+ uint32_t iTodo = pWatcher->cTodos;
+ while (iTodo-- > 0)
+ if (pWatcher->aTodos[iTodo].Data.pUserData == pUserData)
+ {
+ if (pWatcher->aTodos[iTodo].hProcess == NULL)
+ fAddIt = true;
+ else
+ {
+ fAddIt = false;
+ CloseHandle(pWatcher->aTodos[iTodo].hProcess);
+ }
+ uint32_t const cTodos = --pWatcher->cTodos;
+ uint32_t const cToShift = cTodos - iTodo;
+ if (cToShift > 0)
+ memmove(&pWatcher->aTodos[iTodo], &pWatcher->aTodos[iTodo + 1], sizeof(pWatcher->aTodos[0]) * cToShift);
+ pWatcher->aTodos[cTodos].hProcess = NULL;
+ pWatcher->aTodos[cTodos].Data.setNull();
+ }
+
+ /*
+ * Did we just eliminated the add and cancel out this operation?
+ */
+ if (!fAddIt)
+ {
+ pUserData->m_iWatcher = UINT32_MAX;
+ pUserData->m_iTheChosenOneRevision++;
+ i_decrementClientCount();
+
+ RTCritSectLeave(&m_WatcherCritSect);
+ RTThreadYield();
+ return;
+ }
+
+ /*
+ * No we didn't. So, try append a removal item.
+ */
+ iTodo = pWatcher->cTodos;
+ if (iTodo < RT_ELEMENTS(pWatcher->aTodos))
+ {
+ pWatcher->aTodos[iTodo].hProcess = NULL;
+ pWatcher->aTodos[iTodo].Data.pUserData = pUserData;
+ pWatcher->aTodos[iTodo].Data.pid = pid;
+ pWatcher->aTodos[iTodo].Data.iRevision = pUserData->m_iTheChosenOneRevision++;
+ pWatcher->cTodos = iTodo + 1;
+ SetEvent(pWatcher->aHandles[0]);
+
+ pUserData->m_iWatcher = UINT32_MAX;
+ i_decrementClientCount();
+
+ RTCritSectLeave(&m_WatcherCritSect);
+ RTThreadYield();
+ return;
+ }
+ }
+ else
+ {
+ LogRel(("i_stopWatching: Watcher #%u has shut down.\n", iWatcher));
+ break;
+ }
+
+ /*
+ * Todo queue is full. Sleep a little and let the watcher process it.
+ */
+ LogRel(("i_stopWatching: Watcher #%u todo queue is full! (round #%u)\n", iWatcher, iRound));
+
+ uint32_t const iTheChosenOneRevision = pUserData->m_iTheChosenOneRevision;
+ SetEvent(pWatcher->aHandles[0]);
+
+ RTCritSectLeave(&m_WatcherCritSect);
+ RTThreadSleep(1 + (iRound & 127));
+ RTCritSectEnter(&m_WatcherCritSect);
+
+ AssertLogRelMsgBreak(pUserData->m_iTheChosenOneRevision == iTheChosenOneRevision,
+ ("Impossible! m_iTheChosenOneRevision changed %#x -> %#x!\n",
+ iTheChosenOneRevision, pUserData->m_iTheChosenOneRevision));
+ }
+ else
+ {
+ AssertLogRelMsg(pUserData->m_iWatcher == UINT32_MAX,
+ ("Impossible! iWatcher=%d m_cWatcher=%u\n", iWatcher, m_cWatchers));
+ break;
+ }
+ }
+ RTCritSectLeave(&m_WatcherCritSect);
+}
+
+
+/**
+ * Shutdowns all the watchers.
+ */
+void VirtualBoxSDS::i_shutdownAllWatchers(void)
+{
+ LogRel(("i_shutdownAllWatchers: %u watchers\n", m_cWatchers));
+
+ /* Notify them all. */
+ uint32_t i = m_cWatchers;
+ while (i-- > 0)
+ {
+ ASMAtomicWriteBool(&m_papWatchers[i]->fShutdown, true);
+ SetEvent(m_papWatchers[i]->aHandles[0]);
+ }
+
+ /* Wait for them to complete and destroy their data. */
+ i = m_cWatchers;
+ m_cWatchers = 0;
+ while (i-- > 0)
+ {
+ VBoxSDSWatcher *pWatcher = m_papWatchers[i];
+ if (pWatcher)
+ {
+ m_papWatchers[i] = NULL;
+
+ int rc = RTThreadWait(pWatcher->hThread, RT_MS_1MIN / 2, NULL);
+ if (RT_SUCCESS(rc))
+ pWatcher->hThread = NIL_RTTHREAD;
+ else
+ LogRel(("i_shutdownAllWatchers: RTThreadWait failed on #%u: %Rrc\n", i, rc));
+
+ if (ASMAtomicDecU32(&pWatcher->cRefs) == 0)
+ RTMemFree(pWatcher);
+ }
+ }
+}
+
+
+/**
+ * Increments the VBoxSVC client count.
+ */
+void VirtualBoxSDS::i_incrementClientCount()
+{
+ Assert(RTCritSectIsOwner(&m_WatcherCritSect));
+ uint32_t cClients = ++m_cVBoxSvcProcesses;
+ Assert(cClients < 4096);
+ VBoxSDSNotifyClientCount(cClients);
+}
+
+
+/**
+ * Decrements the VBoxSVC client count.
+ */
+void VirtualBoxSDS::i_decrementClientCount()
+{
+ Assert(RTCritSectIsOwner(&m_WatcherCritSect));
+ uint32_t cClients = --m_cVBoxSvcProcesses;
+ Assert(cClients < 4096);
+ VBoxSDSNotifyClientCount(cClients);
+}
+
+
+#endif /* WITH_WATCHER */
+
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-helper-apps/Makefile.kup b/src/VBox/Main/src-helper-apps/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/Makefile.kup
diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk b/src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk
new file mode 100644
index 00000000..dc420fbe
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk
@@ -0,0 +1,107 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the OpenGLTest helper app.
+#
+
+#
+# Copyright (C) 2008-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+
+#
+# VBoxOGLTest - Library that VBoxSVC is linked with.
+#
+# On darwin this does the whole job, while on the other platforms it just
+# starts VBoxTestOGL.
+#
+LIBRARIES += VBoxOGLTest
+VBoxOGLTest_TEMPLATE := VBOXR3
+ifneq ($(KBUILD_TARGET),darwin)
+ VBoxOGLTest_SOURCES := OpenGLTest.cpp
+else
+ VBoxOGLTest_SOURCES.darwin := OpenGLTestDarwin.cpp
+ VBoxOGLTest_CXXFLAGS.darwin = $(VBOX_GCC_Wno-deprecated-declarations)
+endif
+
+
+#
+# VBoxTestOGL - OpenGL support test app.
+# Note! Doesn't link with VBOX_WITH_DEBUG_VCC_CRT defined because it uses Qt.
+#
+if defined(VBOX_WITH_QTGUI) \
+ && (defined(VBOX_WITH_VMSVGA3D) || defined(VBOX_WITH_VIDEOHWACCEL)) \
+ && !defined(VBOX_WITH_DEBUG_VCC_CRT) \
+ && "$(KBUILD_TARGET)" != "darwin"
+ ifdef VBOX_WITH_VIDEOHWACCEL
+ ifndef VBOX_WITH_QT6
+ USES += qt5
+ else
+ USES += qt6
+ endif
+ endif
+ PROGRAMS += VBoxTestOGL
+ VBoxTestOGL_TEMPLATE = $(if $(VBOX_WITH_VIDEOHWACCEL),$(if $(VBOX_WITH_HARDENING),VBOXQTGUI,VBOXQTGUIEXE),VBOXMAINEXE)
+ VBoxTestOGL_DEFS.win = _WIN32_WINNT=0x0500 WINDOWS=1
+ VBoxTestOGL_DEFS.linux = Linux=1 _GNU_SOURCE
+ VBoxTestOGL_DEFS.solaris = SunOS=1 _GNU_SOURCE #GLEXT_64_TYPES_DEFINED
+ VBoxTestOGL_DEFS.freebsd = FreeBSD=1 _GNU_SOURCE
+ ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ VBoxTestOGL_DEFS = VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)"
+ else
+ VBoxTestOGL_DEFS = VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
+ endif
+ VBoxTestOGL_SOURCES = OpenGLTestApp.cpp
+ VBoxTestOGL_SOURCES.win = VBoxTestOGL.rc
+ VBoxTestOGL_LIBS = \
+ $(LIB_RUNTIME)
+ if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris) # the X11 gang
+ VBoxTestOGL_LIBS += \
+ X11 \
+ Xext
+ VBoxTestOGL_LIBPATH = \
+ $(VBOX_LIBPATH_X11)
+ endif
+
+ ifdef VBOX_WITH_VIDEOHWACCEL
+ VBoxTestOGL_DEFS += VBOX_WITH_VIDEOHWACCEL
+ VBoxTestOGL_QT_MODULES = Core Gui OpenGL Widgets
+ ifdef VBOX_WITH_QT6
+ VBoxTestOGL_QT_MODULES += OpenGLWidgets
+ endif
+ if1of ($(KBUILD_TARGET), solaris linux freebsd)
+ VBoxTestOGL_LIBS += xcb GL pthread dl
+ endif
+ VBoxTestOGL_LIBS.win += $(PATH_SDK_$(VBOX_WINPSDK)_LIB)/Opengl32.lib
+ VBoxTestOGL_SOURCES += VBoxGLSupportInfo.cpp
+ endif
+
+ # Don't let ld strip out explicitly linked libraries even when they are not needed.
+ # This was causing some dynamic library loading problems in case of indirect dependencies
+ # in systems where RUNPATH instead of RPATH is utilized.
+ VBoxTestOGL_LDFLAGS.linux = -Wl,--no-as-needed
+ VBoxTestOGL_LDFLAGS.win = /SUBSYSTEM:windows
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTest.cpp b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTest.cpp
new file mode 100644
index 00000000..82eb6680
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTest.cpp
@@ -0,0 +1,113 @@
+/* $Id: OpenGLTest.cpp $ */
+/** @file
+ * VBox host opengl support test - generic implementation.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/err.h>
+#include <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/env.h>
+#include <iprt/log.h>
+
+#include <VBox/VBoxOGL.h>
+
+bool RTCALL VBoxOglIs3DAccelerationSupported(void)
+{
+ if (RTEnvExist("VBOX_3D_FORCE_SUPPORTED"))
+ {
+ LogRel(("VBOX_3D_FORCE_SUPPORTED is specified, skipping 3D test, and treating as supported\n"));
+ return true;
+ }
+
+ static char pszVBoxPath[RTPATH_MAX];
+ const char *papszArgs[4] = { NULL, "-test", "3D", NULL};
+ int rc;
+ RTPROCESS Process;
+ RTPROCSTATUS ProcStatus;
+ uint64_t StartTS;
+
+#ifdef __SANITIZE_ADDRESS__
+ /* The OpenGL test tool contains a number of memory leaks which cause it to
+ * return failure when run with ASAN unless we disable the leak detector. */
+ RTENV env;
+ if (RT_FAILURE(RTEnvClone(&env, RTENV_DEFAULT)))
+ return false;
+ RTEnvPutEx(env, "ASAN_OPTIONS=detect_leaks=0"); /* If this fails we will notice later */
+#endif
+ rc = RTPathExecDir(pszVBoxPath, RTPATH_MAX); AssertRCReturn(rc, false);
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ rc = RTPathAppend(pszVBoxPath, RTPATH_MAX, "VBoxTestOGL.exe");
+#else
+ rc = RTPathAppend(pszVBoxPath, RTPATH_MAX, "VBoxTestOGL");
+#endif
+ papszArgs[0] = pszVBoxPath; /* argv[0] */
+ AssertRCReturn(rc, false);
+
+#ifndef __SANITIZE_ADDRESS__
+ rc = RTProcCreate(pszVBoxPath, papszArgs, RTENV_DEFAULT, 0, &Process);
+#else
+ rc = RTProcCreate(pszVBoxPath, papszArgs, env, 0, &Process);
+ RTEnvDestroy(env);
+#endif
+ if (RT_FAILURE(rc))
+ return false;
+
+ StartTS = RTTimeMilliTS();
+
+ while (1)
+ {
+ rc = RTProcWait(Process, RTPROCWAIT_FLAGS_NOBLOCK, &ProcStatus);
+ if (rc != VERR_PROCESS_RUNNING)
+ break;
+
+#ifndef DEBUG_misha
+ if (RTTimeMilliTS() - StartTS > 30*1000 /* 30 sec */)
+ {
+ RTProcTerminate(Process);
+ RTThreadSleep(100);
+ RTProcWait(Process, RTPROCWAIT_FLAGS_NOBLOCK, &ProcStatus);
+ return false;
+ }
+#endif
+ RTThreadSleep(100);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if ((ProcStatus.enmReason==RTPROCEXITREASON_NORMAL) && (ProcStatus.iStatus==0))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestApp.cpp b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestApp.cpp
new file mode 100644
index 00000000..b73bee9a
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestApp.cpp
@@ -0,0 +1,526 @@
+/* $Id: OpenGLTestApp.cpp $ */
+/** @file
+ * VBox host opengl support test application.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/ldr.h>
+#include <iprt/message.h>
+#include <iprt/stream.h>
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#endif
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+# include <sys/resource.h>
+# include <fcntl.h>
+# include <unistd.h>
+#endif
+
+#include <string.h>
+
+#define VBOXGLTEST_WITH_LOGGING /** @todo r=andy Is this intentional? */
+
+#ifdef VBOXGLTEST_WITH_LOGGING
+# include "package-generated.h"
+
+# include <iprt/log.h>
+# include <iprt/param.h>
+# include <iprt/time.h>
+# include <iprt/system.h>
+# include <iprt/process.h>
+# include <iprt/env.h>
+
+# include <VBox/log.h>
+# include <VBox/version.h>
+#endif /* VBOXGLTEST_WITH_LOGGING */
+
+#ifndef RT_OS_WINDOWS
+# include <GL/gl.h> /* For GLubyte and friends. */
+#endif
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+# include <QApplication>
+# if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && defined(RT_OS_WINDOWS)
+# include <QGLWidget> /* for GL headers on windows */
+# endif
+# include <VBox/VBoxGL2D.h>
+#endif
+
+/**
+ * The OpenGL methods to look for when checking 3D presence.
+ */
+static const char * const g_apszOglMethods[] =
+{
+#ifdef RT_OS_WINDOWS
+ "wglCreateContext",
+ "wglDeleteContext",
+ "wglMakeCurrent",
+ "wglShareLists",
+#elif defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) || defined(RT_OS_SOLARIS)
+ "glXQueryVersion",
+ "glXChooseVisual",
+ "glXCreateContext",
+ "glXMakeCurrent",
+ "glXDestroyContext",
+#endif
+ "glAlphaFunc",
+ "glBindTexture",
+ "glBlendFunc",
+ "glClear",
+ "glClearColor",
+ "glClearDepth",
+ "glClearStencil",
+ "glClipPlane",
+ "glColorMask",
+ "glColorPointer",
+ "glCullFace",
+ "glDeleteTextures",
+ "glDepthFunc",
+ "glDepthMask",
+ "glDepthRange",
+ "glDisable",
+ "glDisableClientState",
+ "glDrawArrays",
+ "glDrawElements",
+ "glEnable",
+ "glEnableClientState",
+ "glFogf",
+ "glFogfv",
+ "glFogi",
+ "glFrontFace",
+ "glGenTextures",
+ "glGetBooleanv",
+ "glGetError",
+ "glGetFloatv",
+ "glGetIntegerv",
+ "glGetString",
+ "glGetTexImage",
+ "glLightModelfv",
+ "glLightf",
+ "glLightfv",
+ "glLineWidth",
+ "glLoadIdentity",
+ "glLoadMatrixf",
+ "glMaterialfv",
+ "glMatrixMode",
+ "glMultMatrixf",
+ "glNormalPointer",
+ "glPixelStorei",
+ "glPointSize",
+ "glPolygonMode",
+ "glPolygonOffset",
+ "glPopAttrib",
+ "glPopMatrix",
+ "glPushAttrib",
+ "glPushMatrix",
+ "glScissor",
+ "glShadeModel",
+ "glStencilFunc",
+ "glStencilMask",
+ "glStencilOp",
+ "glTexCoordPointer",
+ "glTexImage2D",
+ "glTexParameterf",
+ "glTexParameterfv",
+ "glTexParameteri",
+ "glTexSubImage2D",
+ "glVertexPointer",
+ "glViewport"
+};
+
+
+/**
+ * Tries to resolve the given OpenGL symbol.
+ *
+ * @returns Pointer to the symbol or nULL on error.
+ * @param pszSymbol The symbol to resolve.
+ */
+DECLINLINE(PFNRT) vboxTestOglGetProc(const char *pszSymbol)
+{
+ int rc;
+
+#ifdef RT_OS_WINDOWS
+ static RTLDRMOD s_hOpenGL32 = NULL;
+ if (s_hOpenGL32 == NULL)
+ {
+ rc = RTLdrLoadSystem("opengl32", /* fNoUnload = */ true, &s_hOpenGL32);
+ if (RT_FAILURE(rc))
+ s_hOpenGL32 = NULL;
+ }
+
+ typedef PROC (WINAPI *PFNWGLGETPROCADDRESS)(LPCSTR);
+ static PFNWGLGETPROCADDRESS s_wglGetProcAddress = NULL;
+ if (s_wglGetProcAddress == NULL)
+ {
+ if (s_hOpenGL32 != NULL)
+ {
+ rc = RTLdrGetSymbol(s_hOpenGL32, "wglGetProcAddress", (void **)&s_wglGetProcAddress);
+ if (RT_FAILURE(rc))
+ s_wglGetProcAddress = NULL;
+ }
+ }
+
+ if (s_wglGetProcAddress)
+ {
+ /* Khronos: [on failure] "some implementations will return other values. 1, 2, and 3 are used, as well as -1". */
+ PFNRT p = (PFNRT)s_wglGetProcAddress(pszSymbol);
+ if (RT_VALID_PTR(p))
+ return p;
+
+ /* Might be an exported symbol. */
+ rc = RTLdrGetSymbol(s_hOpenGL32, pszSymbol, (void **)&p);
+ if (RT_SUCCESS(rc))
+ return p;
+ }
+#else /* The X11 gang */
+ static RTLDRMOD s_hGL = NULL;
+ if (s_hGL == NULL)
+ {
+ static const char s_szLibGL[] = "libGL.so.1";
+ rc = RTLdrLoadEx(s_szLibGL, &s_hGL, RTLDRLOAD_FLAGS_GLOBAL | RTLDRLOAD_FLAGS_NO_UNLOAD, NULL);
+ if (RT_FAILURE(rc))
+ {
+ s_hGL = NULL;
+ return NULL;
+ }
+ }
+
+ typedef PFNRT (* PFNGLXGETPROCADDRESS)(const GLubyte * procName);
+ static PFNGLXGETPROCADDRESS s_glXGetProcAddress = NULL;
+ if (s_glXGetProcAddress == NULL)
+ {
+ rc = RTLdrGetSymbol(s_hGL, "glXGetProcAddress", (void **)&s_glXGetProcAddress);
+ if (RT_FAILURE(rc))
+ {
+ s_glXGetProcAddress = NULL;
+ return NULL;
+ }
+ }
+
+ PFNRT p = s_glXGetProcAddress((const GLubyte *)pszSymbol);
+ if (RT_VALID_PTR(p))
+ return p;
+
+ /* Might be an exported symbol. */
+ rc = RTLdrGetSymbol(s_hGL, pszSymbol, (void **)&p);
+ if (RT_SUCCESS(rc))
+ return p;
+#endif
+
+ return NULL;
+}
+
+static int vboxCheck3DAccelerationSupported()
+{
+ LogRel(("Testing 3D Support:\n"));
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_apszOglMethods); i++)
+ {
+ PFNRT pfn = vboxTestOglGetProc(g_apszOglMethods[i]);
+ if (!pfn)
+ {
+ LogRel(("Testing 3D Failed\n"));
+ return 1;
+ }
+ }
+
+ LogRel(("Testing 3D Succeeded!\n"));
+ return 0;
+}
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+static int vboxCheck2DVideoAccelerationSupported()
+{
+ LogRel(("Testing 2D Support:\n"));
+ char *apszDummyArgs[] = { (char *)"GLTest", NULL };
+ int cDummyArgs = RT_ELEMENTS(apszDummyArgs) - 1;
+ QApplication app(cDummyArgs, apszDummyArgs);
+
+ VBoxGLTmpContext ctx;
+ const MY_QOpenGLContext *pContext = ctx.makeCurrent();
+ if (pContext)
+ {
+ VBoxVHWAInfo supportInfo;
+ supportInfo.init(pContext);
+ if (supportInfo.isVHWASupported())
+ {
+ LogRel(("Testing 2D Succeeded!\n"));
+ return 0;
+ }
+ }
+ else
+ {
+ LogRel(("Failed to create gl context\n"));
+ }
+ LogRel(("Testing 2D Failed\n"));
+ return 1;
+}
+#endif
+
+#ifdef VBOXGLTEST_WITH_LOGGING
+static int vboxInitLogging(const char *pszFilename, bool bGenNameSuffix)
+{
+ PRTLOGGER loggerRelease;
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ RTUINT fFlags = RTLOGFLAGS_PREFIX_TIME_PROG;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ fFlags |= RTLOGFLAGS_USECRLF;
+#endif
+ const char * pszFilenameFmt;
+ RTLOGDEST enmLogDest;
+ if(pszFilename)
+ {
+ if(bGenNameSuffix)
+ pszFilenameFmt = "%s.%ld.log";
+ else
+ pszFilenameFmt = "%s";
+ enmLogDest = RTLOGDEST_FILE;
+ }
+ else
+ {
+ pszFilenameFmt = NULL;
+ enmLogDest = RTLOGDEST_STDOUT;
+ }
+
+
+ int vrc = RTLogCreateEx(&loggerRelease, "VBOX_RELEASE_LOG", fFlags, "all", RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX,
+ 0 /*cBufDescs*/, NULL /*paBufDescs*/, enmLogDest,
+ NULL /*pfnBeginEnd*/, 0 /*cHistory*/, 0 /*cbHistoryFileMax*/, 0 /*uHistoryTimeMax*/,
+ NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/,
+ NULL /*pErrInfo*/, pszFilenameFmt, pszFilename, RTTimeMilliTS());
+ if (RT_SUCCESS(vrc))
+ {
+ /* some introductory information */
+ RTTIMESPEC timeSpec;
+ char szTmp[256];
+ RTTimeSpecToString(RTTimeNow(&timeSpec), szTmp, sizeof(szTmp));
+ RTLogRelLogger(loggerRelease, 0, ~0U,
+ "VBoxTestGL %s r%u %s (%s %s) release log\n"
+#ifdef VBOX_BLEEDING_EDGE
+ "EXPERIMENTAL build " VBOX_BLEEDING_EDGE "\n"
+#endif
+ "Log opened %s\n",
+ VBOX_VERSION_STRING, RTBldCfgRevision(), VBOX_BUILD_TARGET,
+ __DATE__, __TIME__, szTmp);
+
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ RTLogRelLogger(loggerRelease, 0, ~0U, "OS Product: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ RTLogRelLogger(loggerRelease, 0, ~0U, "OS Release: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ RTLogRelLogger(loggerRelease, 0, ~0U, "OS Version: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ RTLogRelLogger(loggerRelease, 0, ~0U, "OS Service Pack: %s\n", szTmp);
+// RTLogRelLogger(loggerRelease, 0, ~0U, "Host RAM: %uMB RAM, available: %uMB\n",
+// uHostRamMb, uHostRamAvailMb);
+ /* the package type is interesting for Linux distributions */
+ char szExecName[RTPATH_MAX];
+ char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName));
+ RTLogRelLogger(loggerRelease, 0, ~0U,
+ "Executable: %s\n"
+ "Process ID: %u\n"
+ "Package type: %s"
+#ifdef VBOX_OSE
+ " (OSE)"
+#endif
+ "\n",
+ pszExecName ? pszExecName : "unknown",
+ RTProcSelf(),
+ VBOX_PACKAGE_STRING);
+
+ /* register this logger as the release logger */
+ RTLogRelSetDefaultInstance(loggerRelease);
+
+ return VINF_SUCCESS;
+ }
+
+ return vrc;
+}
+#endif
+
+static int vboxInitQuietMode()
+{
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+ /* This small test application might crash on some hosts. Do never
+ * generate a core dump as most likely some OpenGL library is
+ * responsible. */
+ struct rlimit lim = { 0, 0 };
+ setrlimit(RLIMIT_CORE, &lim);
+
+ /* Redirect stderr to /dev/null */
+ int fd = open("/dev/null", O_WRONLY);
+ if (fd != -1)
+ dup2(fd, STDERR_FILENO);
+#endif
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ RTR3InitExe(argc, &argv, 0);
+
+ int rc = 0;
+ if (argc < 2)
+ {
+ /* backwards compatibility: check 3D */
+ rc = vboxCheck3DAccelerationSupported();
+ }
+ else
+ {
+ static const RTGETOPTDEF s_aOptionDefs[] =
+ {
+ { "--test", 't', RTGETOPT_REQ_STRING },
+ { "-test", 't', RTGETOPT_REQ_STRING },
+#ifdef VBOXGLTEST_WITH_LOGGING
+ { "--log", 'l', RTGETOPT_REQ_STRING },
+ { "--log-to-stdout", 'L', RTGETOPT_REQ_NOTHING },
+#endif
+ };
+
+ RTGETOPTSTATE State;
+ rc = RTGetOptInit(&State, argc, argv, &s_aOptionDefs[0], RT_ELEMENTS(s_aOptionDefs), 1, 0);
+ AssertRCReturn(rc, 49);
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ bool bTest2D = false;
+#endif
+ bool bTest3D = false;
+#ifdef VBOXGLTEST_WITH_LOGGING
+ bool bLog = false;
+ bool bLogSuffix = false;
+ const char * pLog = NULL;
+#endif
+
+ for (;;)
+ {
+ RTGETOPTUNION Val;
+ rc = RTGetOpt(&State, &Val);
+ if (!rc)
+ break;
+ switch (rc)
+ {
+ case 't':
+ if (!strcmp(Val.psz, "3D") || !strcmp(Val.psz, "3d"))
+ bTest3D = true;
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ else if (!strcmp(Val.psz, "2D") || !strcmp(Val.psz, "2d"))
+ bTest2D = true;
+#endif
+ else
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unknown test: %s", Val.psz);
+ break;
+
+#ifdef VBOXGLTEST_WITH_LOGGING
+ case 'l':
+ bLog = true;
+ pLog = Val.psz;
+ break;
+ case 'L':
+ bLog = true;
+ pLog = NULL;
+ break;
+#endif
+ case 'h':
+ RTPrintf(VBOX_PRODUCT " Helper for testing 2D/3D OpenGL capabilities %u.%u.%u\n"
+ "Copyright (C) 2009-" VBOX_C_YEAR " " VBOX_VENDOR "\n"
+ "\n"
+ "Parameters:\n"
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ " --test 2D test for 2D (video) OpenGL capabilities\n"
+#endif
+ " --test 3D test for 3D OpenGL capabilities\n"
+#ifdef VBOXGLTEST_WITH_LOGGING
+ " --log <log_file_name> log the GL test result to the given file\n"
+ " --log-to-stdout log the GL test result to stdout\n"
+ "\n"
+ "Logging can alternatively be enabled by specifying the VBOXGLTEST_LOG=<log_file_name> env variable\n"
+
+#endif
+ "\n",
+ RTBldCfgVersionMajor(), RTBldCfgVersionMinor(), RTBldCfgVersionBuild());
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("$Revision: 153224 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ case VERR_GETOPT_UNKNOWN_OPTION:
+ case VINF_GETOPT_NOT_OPTION:
+ default:
+ return RTGetOptPrintError(rc, &Val);
+ }
+ }
+
+ /*
+ * Init logging and output.
+ */
+#ifdef VBOXGLTEST_WITH_LOGGING
+ if (!bLog)
+ {
+ /* check the VBOXGLTEST_LOG env var */
+ pLog = RTEnvGet("VBOXGLTEST_LOG");
+ if(pLog)
+ bLog = true;
+ bLogSuffix = true;
+ }
+ if (bLog)
+ rc = vboxInitLogging(pLog, bLogSuffix);
+ else
+#endif
+ rc = vboxInitQuietMode();
+
+ /*
+ * Do the job.
+ */
+ if (!rc && bTest3D)
+ rc = vboxCheck3DAccelerationSupported();
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ if (!rc && bTest2D)
+ rc = vboxCheck2DVideoAccelerationSupported();
+#endif
+ }
+
+ /*RTR3Term();*/
+ return rc;
+
+}
+
+#ifdef RT_OS_WINDOWS
+extern "C" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
+{
+ RT_NOREF(hInstance, hPrevInstance, lpCmdLine, nShowCmd);
+ return main(__argc, __argv);
+}
+#endif
+
diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestDarwin.cpp b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestDarwin.cpp
new file mode 100644
index 00000000..1a6f6c48
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestDarwin.cpp
@@ -0,0 +1,183 @@
+/* $Id: OpenGLTestDarwin.cpp $ */
+/** @file
+ * VBox host opengl support test
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/VBoxOGL.h>
+
+#include <IOKit/IOKitLib.h>
+#include <OpenGL/OpenGL.h>
+#include <ApplicationServices/ApplicationServices.h>
+#include <OpenGL/gl.h>
+#include <OpenGL/glu.h>
+
+#include <iprt/env.h>
+#include <iprt/log.h>
+#include <iprt/once.h>
+
+
+
+/**
+ * @callback_method_impl{FNRTONCE,
+ * For determining the cached VBoxOglIsOfflineRenderingAppropriate result.}
+ */
+static DECLCALLBACK(int32_t) vboxOglIsOfflineRenderingAppropriateOnce(void *pvUser)
+{
+ bool *pfAppropriate = (bool *)pvUser;
+
+ /* It is assumed that it is makes sense to enable offline rendering
+ only in case if host has more than one GPU installed. This routine
+ counts all the PCI devices in IORegistry which have IOName property
+ set to "display". If the number of such devices is greater than one,
+ it sets pfAppropriate to TRUE, otherwise to FALSE. */
+
+ CFStringRef apKeyStrings[] = { CFSTR(kIOProviderClassKey), CFSTR(kIONameMatchKey) };
+ CFStringRef apValueStrings[] = { CFSTR("IOPCIDevice"), CFSTR("display") };
+ Assert(RT_ELEMENTS(apKeyStrings) == RT_ELEMENTS(apValueStrings));
+
+ CFDictionaryRef pMatchingDictionary = CFDictionaryCreate(kCFAllocatorDefault,
+ (const void **)apKeyStrings,
+ (const void **)apValueStrings,
+ RT_ELEMENTS(apKeyStrings),
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ if (pMatchingDictionary)
+ {
+ /* The reference to pMatchingDictionary is consumed by the function below => no IORelease(pMatchingDictionary)! */
+ io_iterator_t matchingServices;
+ kern_return_t krc = IOServiceGetMatchingServices(kIOMasterPortDefault, pMatchingDictionary, &matchingServices);
+ if (krc == kIOReturnSuccess)
+ {
+ io_object_t matchingService;
+ int cMatchingServices = 0;
+
+ while ((matchingService = IOIteratorNext(matchingServices)) != 0)
+ {
+ cMatchingServices++;
+ IOObjectRelease(matchingService);
+ }
+
+ *pfAppropriate = cMatchingServices > 1;
+
+ IOObjectRelease(matchingServices);
+ }
+ }
+
+ LogRel(("OpenGL: Offline rendering support is %s (pid=%d)\n", *pfAppropriate ? "ON" : "OFF", (int)getpid()));
+ return VINF_SUCCESS;
+}
+
+
+bool RTCALL VBoxOglIsOfflineRenderingAppropriate(void)
+{
+ /* In order to do not slowdown 3D engine which can ask about offline rendering several times,
+ let's cache the result and assume that renderers amount value is constant. Use the IPRT
+ execute once construct to make sure there aren't any threading issues. */
+ static RTONCE s_Once = RTONCE_INITIALIZER;
+ static bool s_fCached = false;
+ int rc = RTOnce(&s_Once, vboxOglIsOfflineRenderingAppropriateOnce, &s_fCached);
+ AssertRC(rc);
+ return s_fCached;
+}
+
+
+bool RTCALL VBoxOglIs3DAccelerationSupported(void)
+{
+ if (RTEnvExist("VBOX_3D_FORCE_SUPPORTED"))
+ {
+ LogRel(("VBOX_3D_FORCE_SUPPORTED is specified, skipping 3D test, and treating as supported\n"));
+ return true;
+ }
+
+ CGOpenGLDisplayMask cglDisplayMask = CGDisplayIDToOpenGLDisplayMask(CGMainDisplayID());
+ CGLPixelFormatAttribute aAttribs[] =
+ {
+ kCGLPFADisplayMask,
+ (CGLPixelFormatAttribute)cglDisplayMask,
+ kCGLPFAAccelerated,
+ kCGLPFADoubleBuffer,
+ VBoxOglIsOfflineRenderingAppropriate() ? kCGLPFAAllowOfflineRenderers : (CGLPixelFormatAttribute)NULL,
+ (CGLPixelFormatAttribute)NULL
+ };
+ CGLPixelFormatObj pPixelFormat = NULL;
+ GLint cPixelFormatsIgnored = 0;
+ CGLError rcCgl = CGLChoosePixelFormat(aAttribs, &pPixelFormat, &cPixelFormatsIgnored);
+ if (rcCgl != kCGLNoError)
+ {
+ LogRel(("OpenGL Info: 3D test unable to choose pixel format (rcCgl=0x%X)\n", rcCgl));
+ return false;
+ }
+
+ if (pPixelFormat)
+ {
+ CGLContextObj pCglContext = 0;
+ rcCgl = CGLCreateContext(pPixelFormat, NULL, &pCglContext);
+ CGLDestroyPixelFormat(pPixelFormat);
+
+ if (rcCgl != kCGLNoError)
+ {
+ LogRel(("OpenGL Info: 3D test unable to create context (rcCgl=0x%X)\n", rcCgl));
+ return false;
+ }
+
+ if (pCglContext)
+ {
+ GLboolean isSupported = GL_TRUE;
+
+ /*
+ * In the Cocoa port we depend on the GL_EXT_framebuffer_object &
+ * the GL_EXT_texture_rectangle extension. If they are not
+ * available, disable 3D support.
+ */
+#pragma clang diagnostic ignored "-Wdeprecated-declarations" /* gluCheckExtension deprecated since 10.10 - use MetalKit. */
+ CGLSetCurrentContext(pCglContext);
+ const GLubyte *pszExts = glGetString(GL_EXTENSIONS);
+ isSupported = gluCheckExtension((const GLubyte *)"GL_EXT_framebuffer_object", pszExts);
+ if (isSupported)
+ {
+ isSupported = gluCheckExtension((const GLubyte *)"GL_EXT_texture_rectangle", pszExts);
+ if (!isSupported)
+ LogRel(("OpenGL Info: 3D test found that GL_EXT_texture_rectangle extension not supported.\n"));
+ }
+ else
+ LogRel(("OpenGL Info: 3D test found that GL_EXT_framebuffer_object extension not supported.\n"));
+
+ CGLDestroyContext(pCglContext);
+ LogRel(("OpenGL Info: 3D test %spassed\n", isSupported == GL_TRUE ? "" : "not "));
+ return isSupported == GL_TRUE;
+ }
+
+ LogRel(("OpenGL Info: 3D test unable to create context (internal error).\n"));
+ }
+ else
+ LogRel(("OpenGL Info: 3D test unable to choose pixel format (internal error).\n"));
+
+ return false;
+}
+
diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxFBOverlayCommon.h b/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxFBOverlayCommon.h
new file mode 100644
index 00000000..9fb85f91
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxFBOverlayCommon.h
@@ -0,0 +1,131 @@
+/* $Id: VBoxFBOverlayCommon.h $ */
+/** @file
+ * VBox Qt GUI - VBoxFrameBuffer Overlay classes declarations.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SRC_src_helper_apps_OpenGLTest_VBoxFBOverlayCommon_h
+#define MAIN_INCLUDED_SRC_src_helper_apps_OpenGLTest_VBoxFBOverlayCommon_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#if 0 //defined(DEBUG_misha)
+DECLINLINE(VOID) vboxDbgPrintF(LPCSTR szString, ...)
+{
+ char szBuffer[4096] = {0};
+ va_list pArgList;
+ va_start(pArgList, szString);
+ _vsnprintf(szBuffer, sizeof(szBuffer) / sizeof(szBuffer[0]), szString, pArgList);
+ va_end(pArgList);
+
+ OutputDebugStringA(szBuffer);
+}
+
+# include "iprt/stream.h"
+# define VBOXQGLLOG(_m) RTPrintf _m
+# define VBOXQGLLOGREL(_m) do { RTPrintf _m ; LogRel( _m ); } while(0)
+# define VBOXQGLDBGPRINT(_m) vboxDbgPrintF _m
+#else
+# define VBOXQGLLOG(_m) do {}while(0)
+# define VBOXQGLLOGREL(_m) LogRel( _m )
+# define VBOXQGLDBGPRINT(_m) do {}while(0)
+#endif
+#define VBOXQGLLOG_ENTER(_m) do {}while(0)
+//do{VBOXQGLLOG(("==>[%s]:", __FUNCTION__)); VBOXQGLLOG(_m);}while(0)
+#define VBOXQGLLOG_EXIT(_m) do {}while(0)
+//do{VBOXQGLLOG(("<==[%s]:", __FUNCTION__)); VBOXQGLLOG(_m);}while(0)
+#ifdef DEBUG
+ #define VBOXQGL_ASSERTNOERR() \
+ do { GLenum err = glGetError(); \
+ if(err != GL_NO_ERROR) VBOXQGLLOG(("gl error occurred (0x%x)\n", err)); \
+ Assert(err == GL_NO_ERROR); \
+ }while(0)
+
+ #define VBOXQGL_CHECKERR(_op) \
+ do { \
+ glGetError(); \
+ _op \
+ VBOXQGL_ASSERTNOERR(); \
+ }while(0)
+#else
+ #define VBOXQGL_ASSERTNOERR() \
+ do {}while(0)
+
+ #define VBOXQGL_CHECKERR(_op) \
+ do { \
+ _op \
+ }while(0)
+#endif
+
+#ifdef DEBUG
+#include <iprt/time.h>
+
+#define VBOXGETTIME() RTTimeNanoTS()
+
+#define VBOXPRINTDIF(_nano, _m) do{\
+ uint64_t cur = VBOXGETTIME(); NOREF(cur); \
+ VBOXQGLLOG(_m); \
+ VBOXQGLLOG(("(%Lu)\n", cur - (_nano))); \
+ }while(0)
+
+class VBoxVHWADbgTimeCounter
+{
+public:
+ VBoxVHWADbgTimeCounter(const char* msg) {mTime = VBOXGETTIME(); mMsg=msg;}
+ ~VBoxVHWADbgTimeCounter() {VBOXPRINTDIF(mTime, (mMsg));}
+private:
+ uint64_t mTime;
+ const char* mMsg;
+};
+
+#define VBOXQGLLOG_METHODTIME(_m) VBoxVHWADbgTimeCounter _dbgTimeCounter(_m)
+
+#define VBOXQG_CHECKCONTEXT() \
+ { \
+ const GLubyte * str; \
+ VBOXQGL_CHECKERR( \
+ str = glGetString(GL_VERSION); \
+ ); \
+ Assert(str); \
+ if(str) \
+ { \
+ Assert(str[0]); \
+ } \
+ }
+#else
+#define VBOXQGLLOG_METHODTIME(_m)
+#define VBOXQG_CHECKCONTEXT() do{}while(0)
+#endif
+
+#define VBOXQGLLOG_QRECT(_p, _pr, _s) do{\
+ VBOXQGLLOG((_p " x(%d), y(%d), w(%d), h(%d)" _s, (_pr)->x(), (_pr)->y(), (_pr)->width(), (_pr)->height()));\
+ }while(0)
+
+#define VBOXQGLLOG_CKEY(_p, _pck, _s) do{\
+ VBOXQGLLOG((_p " l(0x%x), u(0x%x)" _s, (_pck)->lower(), (_pck)->upper()));\
+ }while(0)
+
+#endif /* !MAIN_INCLUDED_SRC_src_helper_apps_OpenGLTest_VBoxFBOverlayCommon_h */
+
diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxGLSupportInfo.cpp b/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxGLSupportInfo.cpp
new file mode 100644
index 00000000..ab57641c
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxGLSupportInfo.cpp
@@ -0,0 +1,727 @@
+/* $Id: VBoxGLSupportInfo.cpp $ */
+/** @file
+ * VBox Qt GUI - OpenGL support info used for 2D support detection.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h> /* QGLWidget drags in Windows.h; -Wall forces us to use wrapper. */
+# include <iprt/stdint.h> /* QGLWidget drags in stdint.h; -Wall forces us to use wrapper. */
+#endif
+
+#include <QGuiApplication> /* For QT_VERSION */
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+# include <QMainWindow>
+# include <QOpenGLWidget>
+# include <QOpenGLContext>
+#else
+# include <QGLWidget>
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/log.h>
+#include <iprt/env.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+
+#include <VBox/VBoxGL2D.h>
+#include "VBoxFBOverlayCommon.h"
+#include <iprt/err.h>
+
+
+/*****************/
+
+/* functions */
+
+PFNVBOXVHWA_ACTIVE_TEXTURE vboxglActiveTexture = NULL;
+PFNVBOXVHWA_MULTI_TEX_COORD2I vboxglMultiTexCoord2i = NULL;
+PFNVBOXVHWA_MULTI_TEX_COORD2D vboxglMultiTexCoord2d = NULL;
+PFNVBOXVHWA_MULTI_TEX_COORD2F vboxglMultiTexCoord2f = NULL;
+
+
+PFNVBOXVHWA_CREATE_SHADER vboxglCreateShader = NULL;
+PFNVBOXVHWA_SHADER_SOURCE vboxglShaderSource = NULL;
+PFNVBOXVHWA_COMPILE_SHADER vboxglCompileShader = NULL;
+PFNVBOXVHWA_DELETE_SHADER vboxglDeleteShader = NULL;
+
+PFNVBOXVHWA_CREATE_PROGRAM vboxglCreateProgram = NULL;
+PFNVBOXVHWA_ATTACH_SHADER vboxglAttachShader = NULL;
+PFNVBOXVHWA_DETACH_SHADER vboxglDetachShader = NULL;
+PFNVBOXVHWA_LINK_PROGRAM vboxglLinkProgram = NULL;
+PFNVBOXVHWA_USE_PROGRAM vboxglUseProgram = NULL;
+PFNVBOXVHWA_DELETE_PROGRAM vboxglDeleteProgram = NULL;
+
+PFNVBOXVHWA_IS_SHADER vboxglIsShader = NULL;
+PFNVBOXVHWA_GET_SHADERIV vboxglGetShaderiv = NULL;
+PFNVBOXVHWA_IS_PROGRAM vboxglIsProgram = NULL;
+PFNVBOXVHWA_GET_PROGRAMIV vboxglGetProgramiv = NULL;
+PFNVBOXVHWA_GET_ATTACHED_SHADERS vboxglGetAttachedShaders = NULL;
+PFNVBOXVHWA_GET_SHADER_INFO_LOG vboxglGetShaderInfoLog = NULL;
+PFNVBOXVHWA_GET_PROGRAM_INFO_LOG vboxglGetProgramInfoLog = NULL;
+
+PFNVBOXVHWA_GET_UNIFORM_LOCATION vboxglGetUniformLocation = NULL;
+
+PFNVBOXVHWA_UNIFORM1F vboxglUniform1f = NULL;
+PFNVBOXVHWA_UNIFORM2F vboxglUniform2f = NULL;
+PFNVBOXVHWA_UNIFORM3F vboxglUniform3f = NULL;
+PFNVBOXVHWA_UNIFORM4F vboxglUniform4f = NULL;
+
+PFNVBOXVHWA_UNIFORM1I vboxglUniform1i = NULL;
+PFNVBOXVHWA_UNIFORM2I vboxglUniform2i = NULL;
+PFNVBOXVHWA_UNIFORM3I vboxglUniform3i = NULL;
+PFNVBOXVHWA_UNIFORM4I vboxglUniform4i = NULL;
+
+PFNVBOXVHWA_GEN_BUFFERS vboxglGenBuffers = NULL;
+PFNVBOXVHWA_DELETE_BUFFERS vboxglDeleteBuffers = NULL;
+PFNVBOXVHWA_BIND_BUFFER vboxglBindBuffer = NULL;
+PFNVBOXVHWA_BUFFER_DATA vboxglBufferData = NULL;
+PFNVBOXVHWA_MAP_BUFFER vboxglMapBuffer = NULL;
+PFNVBOXVHWA_UNMAP_BUFFER vboxglUnmapBuffer = NULL;
+
+PFNVBOXVHWA_IS_FRAMEBUFFER vboxglIsFramebuffer = NULL;
+PFNVBOXVHWA_BIND_FRAMEBUFFER vboxglBindFramebuffer = NULL;
+PFNVBOXVHWA_DELETE_FRAMEBUFFERS vboxglDeleteFramebuffers = NULL;
+PFNVBOXVHWA_GEN_FRAMEBUFFERS vboxglGenFramebuffers = NULL;
+PFNVBOXVHWA_CHECK_FRAMEBUFFER_STATUS vboxglCheckFramebufferStatus = NULL;
+PFNVBOXVHWA_FRAMEBUFFER_TEXTURE1D vboxglFramebufferTexture1D = NULL;
+PFNVBOXVHWA_FRAMEBUFFER_TEXTURE2D vboxglFramebufferTexture2D = NULL;
+PFNVBOXVHWA_FRAMEBUFFER_TEXTURE3D vboxglFramebufferTexture3D = NULL;
+PFNVBOXVHWA_GET_FRAMEBUFFER_ATTACHMENT_PARAMETRIV vboxglGetFramebufferAttachmentParameteriv = NULL;
+
+#define VBOXVHWA_GETPROCADDRESS(_c, _t, _n) ((_t)(uintptr_t)(_c).getProcAddress(_n))
+
+#define VBOXVHWA_PFNINIT_SAME(_c, _t, _v, _rc) \
+ do { \
+ if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_v)) == NULL) \
+ { \
+ VBOXQGLLOGREL(("ERROR: '%s' function not found\n", "gl"#_v));\
+ AssertBreakpoint(); \
+ if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_v"ARB")) == NULL) \
+ { \
+ VBOXQGLLOGREL(("ERROR: '%s' function not found\n", "gl"#_v"ARB"));\
+ AssertBreakpoint(); \
+ if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_v"EXT")) == NULL) \
+ { \
+ VBOXQGLLOGREL(("ERROR: '%s' function not found\n", "gl"#_v"EXT"));\
+ AssertBreakpoint(); \
+ (_rc)++; \
+ } \
+ } \
+ } \
+ }while(0)
+
+#define VBOXVHWA_PFNINIT(_c, _t, _v, _f,_rc) \
+ do { \
+ if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_f)) == NULL) \
+ { \
+ VBOXQGLLOGREL(("ERROR: '%s' function is not found\n", "gl"#_f));\
+ AssertBreakpoint(); \
+ (_rc)++; \
+ } \
+ }while(0)
+
+#define VBOXVHWA_PFNINIT_OBJECT_ARB(_c, _t, _v, _rc) \
+ do { \
+ if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_v"ObjectARB")) == NULL) \
+ { \
+ VBOXQGLLOGREL(("ERROR: '%s' function is not found\n", "gl"#_v"ObjectARB"));\
+ AssertBreakpoint(); \
+ (_rc)++; \
+ } \
+ }while(0)
+
+#define VBOXVHWA_PFNINIT_ARB(_c, _t, _v, _rc) \
+ do { \
+ if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_v"ARB")) == NULL) \
+ { \
+ VBOXQGLLOGREL(("ERROR: '%s' function is not found\n", "gl"#_v"ARB"));\
+ AssertBreakpoint(); \
+ (_rc)++; \
+ } \
+ }while(0)
+
+#define VBOXVHWA_PFNINIT_EXT(_c, _t, _v, _rc) \
+ do { \
+ if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_v"EXT")) == NULL) \
+ { \
+ VBOXQGLLOGREL(("ERROR: '%s' function is not found\n", "gl"#_v"EXT"));\
+ AssertBreakpoint(); \
+ (_rc)++; \
+ } \
+ }while(0)
+
+static int vboxVHWAGlParseSubver(const GLubyte * ver, const GLubyte ** pNext, bool bSpacePrefixAllowed)
+{
+ int val = 0;
+
+ for(;;++ver)
+ {
+ if(*ver >= '0' && *ver <= '9')
+ {
+ if(!val)
+ {
+ if(*ver == '0')
+ continue;
+ }
+ else
+ {
+ val *= 10;
+ }
+ val += *ver - '0';
+ }
+ else if(*ver == '.')
+ {
+ *pNext = ver+1;
+ break;
+ }
+ else if(*ver == '\0')
+ {
+ *pNext = NULL;
+ break;
+ }
+ else if(*ver == ' ' || *ver == '\t' || *ver == 0x0d || *ver == 0x0a)
+ {
+ if(bSpacePrefixAllowed)
+ {
+ if(!val)
+ {
+ continue;
+ }
+ }
+
+ /* treat this as the end ov version string */
+ *pNext = NULL;
+ break;
+ }
+ else
+ {
+ Assert(0);
+ val = -1;
+ break;
+ }
+ }
+
+ return val;
+}
+
+/* static */
+int VBoxGLInfo::parseVersion(const GLubyte * ver)
+{
+ int iVer = vboxVHWAGlParseSubver(ver, &ver, true);
+ if(iVer)
+ {
+ iVer <<= 16;
+ if(ver)
+ {
+ int tmp = vboxVHWAGlParseSubver(ver, &ver, false);
+ if(tmp >= 0)
+ {
+ iVer |= tmp << 8;
+ if(ver)
+ {
+ tmp = vboxVHWAGlParseSubver(ver, &ver, false);
+ if(tmp >= 0)
+ {
+ iVer |= tmp;
+ }
+ else
+ {
+ Assert(0);
+ iVer = -1;
+ }
+ }
+ }
+ else
+ {
+ Assert(0);
+ iVer = -1;
+ }
+ }
+ }
+ return iVer;
+}
+
+void VBoxGLInfo::init(const MY_QOpenGLContext *pContext)
+{
+ if (mInitialized)
+ return;
+
+ mInitialized = true;
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ if (!QGLFormat::hasOpenGL())
+ {
+ VBOXQGLLOGREL (("no gl support available\n"));
+ return;
+ }
+#endif
+
+// pContext->makeCurrent();
+
+ const GLubyte * str;
+ VBOXQGL_CHECKERR(
+ str = glGetString(GL_VERSION);
+ );
+
+ if (str)
+ {
+ VBOXQGLLOGREL (("gl version string: 0%s\n", str));
+
+ mGLVersion = parseVersion (str);
+ Assert(mGLVersion > 0);
+ if(mGLVersion < 0)
+ {
+ mGLVersion = 0;
+ }
+ else
+ {
+ VBOXQGLLOGREL (("gl version: 0x%x\n", mGLVersion));
+ VBOXQGL_CHECKERR(
+ str = glGetString (GL_EXTENSIONS);
+ );
+
+ VBOXQGLLOGREL (("gl extensions: %s\n", str));
+
+ const char * pos = strstr((const char *)str, "GL_ARB_multitexture");
+ m_GL_ARB_multitexture = pos != NULL;
+ VBOXQGLLOGREL (("GL_ARB_multitexture: %d\n", m_GL_ARB_multitexture));
+
+ pos = strstr((const char *)str, "GL_ARB_shader_objects");
+ m_GL_ARB_shader_objects = pos != NULL;
+ VBOXQGLLOGREL (("GL_ARB_shader_objects: %d\n", m_GL_ARB_shader_objects));
+
+ pos = strstr((const char *)str, "GL_ARB_fragment_shader");
+ m_GL_ARB_fragment_shader = pos != NULL;
+ VBOXQGLLOGREL (("GL_ARB_fragment_shader: %d\n", m_GL_ARB_fragment_shader));
+
+ pos = strstr((const char *)str, "GL_ARB_pixel_buffer_object");
+ m_GL_ARB_pixel_buffer_object = pos != NULL;
+ VBOXQGLLOGREL (("GL_ARB_pixel_buffer_object: %d\n", m_GL_ARB_pixel_buffer_object));
+
+ pos = strstr((const char *)str, "GL_ARB_texture_rectangle");
+ m_GL_ARB_texture_rectangle = pos != NULL;
+ VBOXQGLLOGREL (("GL_ARB_texture_rectangle: %d\n", m_GL_ARB_texture_rectangle));
+
+ pos = strstr((const char *)str, "GL_EXT_texture_rectangle");
+ m_GL_EXT_texture_rectangle = pos != NULL;
+ VBOXQGLLOGREL (("GL_EXT_texture_rectangle: %d\n", m_GL_EXT_texture_rectangle));
+
+ pos = strstr((const char *)str, "GL_NV_texture_rectangle");
+ m_GL_NV_texture_rectangle = pos != NULL;
+ VBOXQGLLOGREL (("GL_NV_texture_rectangle: %d\n", m_GL_NV_texture_rectangle));
+
+ pos = strstr((const char *)str, "GL_ARB_texture_non_power_of_two");
+ m_GL_ARB_texture_non_power_of_two = pos != NULL;
+ VBOXQGLLOGREL (("GL_ARB_texture_non_power_of_two: %d\n", m_GL_ARB_texture_non_power_of_two));
+
+ pos = strstr((const char *)str, "GL_EXT_framebuffer_object");
+ m_GL_EXT_framebuffer_object = pos != NULL;
+ VBOXQGLLOGREL (("GL_EXT_framebuffer_object: %d\n", m_GL_EXT_framebuffer_object));
+
+
+ initExtSupport(*pContext);
+ }
+ }
+ else
+ {
+ VBOXQGLLOGREL (("failed to make the context current, treating as unsupported\n"));
+ }
+}
+
+void VBoxGLInfo::initExtSupport(const MY_QOpenGLContext &context)
+{
+ int rc = 0;
+ do
+ {
+ rc = 0;
+ mMultiTexNumSupported = 1; /* default, 1 means not supported */
+ if(mGLVersion >= 0x010201) /* ogl >= 1.2.1 */
+ {
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_ACTIVE_TEXTURE, ActiveTexture, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_MULTI_TEX_COORD2I, MultiTexCoord2i, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_MULTI_TEX_COORD2D, MultiTexCoord2d, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_MULTI_TEX_COORD2F, MultiTexCoord2f, rc);
+ }
+ else if(m_GL_ARB_multitexture)
+ {
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_ACTIVE_TEXTURE, ActiveTexture, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_MULTI_TEX_COORD2I, MultiTexCoord2i, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_MULTI_TEX_COORD2D, MultiTexCoord2d, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_MULTI_TEX_COORD2F, MultiTexCoord2f, rc);
+ }
+ else
+ {
+ break;
+ }
+
+ if(RT_FAILURE(rc))
+ break;
+
+ GLint maxCoords, maxUnits;
+ glGetIntegerv(GL_MAX_TEXTURE_COORDS, &maxCoords);
+ glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxUnits);
+
+ VBOXQGLLOGREL(("Max Tex Coords (%d), Img Units (%d)\n", maxCoords, maxUnits));
+ /* take the minimum of those */
+ if(maxUnits < maxCoords)
+ maxCoords = maxUnits;
+ if(maxUnits < 2)
+ {
+ VBOXQGLLOGREL(("Max Tex Coord or Img Units < 2 disabling MultiTex support\n"));
+ break;
+ }
+
+ mMultiTexNumSupported = maxUnits;
+ }while(0);
+
+
+ do
+ {
+ rc = 0;
+ mPBOSupported = false;
+
+ if(m_GL_ARB_pixel_buffer_object)
+ {
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_GEN_BUFFERS, GenBuffers, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_DELETE_BUFFERS, DeleteBuffers, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_BIND_BUFFER, BindBuffer, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_BUFFER_DATA, BufferData, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_MAP_BUFFER, MapBuffer, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNMAP_BUFFER, UnmapBuffer, rc);
+ }
+ else
+ {
+ break;
+ }
+
+ if(RT_FAILURE(rc))
+ break;
+
+ mPBOSupported = true;
+ } while(0);
+
+ do
+ {
+ rc = 0;
+ mFragmentShaderSupported = false;
+
+ if(mGLVersion >= 0x020000) /* if ogl >= 2.0*/
+ {
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_CREATE_SHADER, CreateShader, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_SHADER_SOURCE, ShaderSource, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_COMPILE_SHADER, CompileShader, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_DELETE_SHADER, DeleteShader, rc);
+
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_CREATE_PROGRAM, CreateProgram, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_ATTACH_SHADER, AttachShader, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_DETACH_SHADER, DetachShader, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_LINK_PROGRAM, LinkProgram, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_USE_PROGRAM, UseProgram, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_DELETE_PROGRAM, DeleteProgram, rc);
+
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_IS_SHADER, IsShader, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_GET_SHADERIV, GetShaderiv, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_IS_PROGRAM, IsProgram, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_GET_PROGRAMIV, GetProgramiv, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_GET_ATTACHED_SHADERS, GetAttachedShaders, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_GET_SHADER_INFO_LOG, GetShaderInfoLog, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_GET_PROGRAM_INFO_LOG, GetProgramInfoLog, rc);
+
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_GET_UNIFORM_LOCATION, GetUniformLocation, rc);
+
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM1F, Uniform1f, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM2F, Uniform2f, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM3F, Uniform3f, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM4F, Uniform4f, rc);
+
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM1I, Uniform1i, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM2I, Uniform2i, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM3I, Uniform3i, rc);
+ VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM4I, Uniform4i, rc);
+ }
+ else if(m_GL_ARB_shader_objects && m_GL_ARB_fragment_shader)
+ {
+ VBOXVHWA_PFNINIT_OBJECT_ARB(context, PFNVBOXVHWA_CREATE_SHADER, CreateShader, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_SHADER_SOURCE, ShaderSource, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_COMPILE_SHADER, CompileShader, rc);
+ VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_DELETE_SHADER, DeleteShader, DeleteObjectARB, rc);
+
+ VBOXVHWA_PFNINIT_OBJECT_ARB(context, PFNVBOXVHWA_CREATE_PROGRAM, CreateProgram, rc);
+ VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_ATTACH_SHADER, AttachShader, AttachObjectARB, rc);
+ VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_DETACH_SHADER, DetachShader, DetachObjectARB, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_LINK_PROGRAM, LinkProgram, rc);
+ VBOXVHWA_PFNINIT_OBJECT_ARB(context, PFNVBOXVHWA_USE_PROGRAM, UseProgram, rc);
+ VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_DELETE_PROGRAM, DeleteProgram, DeleteObjectARB, rc);
+
+ /// @todo VBOXVHWA_PFNINIT(PFNVBOXVHWA_IS_SHADER, IsShader, rc);
+ VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_GET_SHADERIV, GetShaderiv, GetObjectParameterivARB, rc);
+ /// @todo VBOXVHWA_PFNINIT(PFNVBOXVHWA_IS_PROGRAM, IsProgram, rc);
+ VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_GET_PROGRAMIV, GetProgramiv, GetObjectParameterivARB, rc);
+ VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_GET_ATTACHED_SHADERS, GetAttachedShaders, GetAttachedObjectsARB, rc);
+ VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_GET_SHADER_INFO_LOG, GetShaderInfoLog, GetInfoLogARB, rc);
+ VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_GET_PROGRAM_INFO_LOG, GetProgramInfoLog, GetInfoLogARB, rc);
+
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_GET_UNIFORM_LOCATION, GetUniformLocation, rc);
+
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM1F, Uniform1f, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM2F, Uniform2f, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM3F, Uniform3f, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM4F, Uniform4f, rc);
+
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM1I, Uniform1i, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM2I, Uniform2i, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM3I, Uniform3i, rc);
+ VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM4I, Uniform4i, rc);
+ }
+ else
+ {
+ break;
+ }
+
+ if(RT_FAILURE(rc))
+ break;
+
+ mFragmentShaderSupported = true;
+ } while(0);
+
+ do
+ {
+ rc = 0;
+ mFBOSupported = false;
+
+ if(m_GL_EXT_framebuffer_object)
+ {
+ VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_IS_FRAMEBUFFER, IsFramebuffer, rc);
+ VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_BIND_FRAMEBUFFER, BindFramebuffer, rc);
+ VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_DELETE_FRAMEBUFFERS, DeleteFramebuffers, rc);
+ VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_GEN_FRAMEBUFFERS, GenFramebuffers, rc);
+ VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_CHECK_FRAMEBUFFER_STATUS, CheckFramebufferStatus, rc);
+ VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_FRAMEBUFFER_TEXTURE1D, FramebufferTexture1D, rc);
+ VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_FRAMEBUFFER_TEXTURE2D, FramebufferTexture2D, rc);
+ VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_FRAMEBUFFER_TEXTURE3D, FramebufferTexture3D, rc);
+ VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_GET_FRAMEBUFFER_ATTACHMENT_PARAMETRIV, GetFramebufferAttachmentParameteriv, rc);
+ }
+ else
+ {
+ break;
+ }
+
+ if(RT_FAILURE(rc))
+ break;
+
+ mFBOSupported = true;
+ } while(0);
+
+ if(m_GL_ARB_texture_rectangle || m_GL_EXT_texture_rectangle || m_GL_NV_texture_rectangle)
+ {
+ mTextureRectangleSupported = true;
+ }
+ else
+ {
+ mTextureRectangleSupported = false;
+ }
+
+ mTextureNP2Supported = m_GL_ARB_texture_non_power_of_two;
+}
+
+void VBoxVHWAInfo::init(const MY_QOpenGLContext *pContext)
+{
+ if(mInitialized)
+ return;
+
+ mInitialized = true;
+
+ mglInfo.init(pContext);
+
+ if(mglInfo.isFragmentShaderSupported() && mglInfo.isTextureRectangleSupported())
+ {
+ uint32_t num = 0;
+ mFourccSupportedList[num++] = FOURCC_AYUV;
+ mFourccSupportedList[num++] = FOURCC_UYVY;
+ mFourccSupportedList[num++] = FOURCC_YUY2;
+ if(mglInfo.getMultiTexNumSupported() >= 4)
+ {
+ /* YV12 currently requires 3 units (for each color component)
+ * + 1 unit for dst texture for color-keying + 3 units for each color component
+ * TODO: we could store YV12 data in one texture to eliminate this requirement*/
+ mFourccSupportedList[num++] = FOURCC_YV12;
+ }
+
+ Assert(num <= VBOXVHWA_NUMFOURCC);
+ mFourccSupportedCount = num;
+ }
+ else
+ {
+ mFourccSupportedCount = 0;
+ }
+}
+
+bool VBoxVHWAInfo::isVHWASupported() const
+{
+ if(mglInfo.getGLVersion() <= 0)
+ {
+ /* error occurred while gl info initialization */
+ VBOXQGLLOGREL(("2D not supported: gl version info not initialized properly\n"));
+ return false;
+ }
+
+#ifndef DEBUGVHWASTRICT
+ /* in case we do not support shaders & multitexturing we can not support dst colorkey,
+ * no sense to report Video Acceleration supported */
+ if(!mglInfo.isFragmentShaderSupported())
+ {
+ VBOXQGLLOGREL(("2D not supported: fragment shader unsupported\n"));
+ return false;
+ }
+#endif
+ if(mglInfo.getMultiTexNumSupported() < 2)
+ {
+ VBOXQGLLOGREL(("2D not supported: multitexture unsupported\n"));
+ return false;
+ }
+
+ /* color conversion now supported only GL_TEXTURE_RECTANGLE
+ * in this case only stretching is accelerated
+ * report as unsupported, TODO: probably should report as supported for stretch acceleration */
+ if(!mglInfo.isTextureRectangleSupported())
+ {
+ VBOXQGLLOGREL(("2D not supported: texture rectangle unsupported\n"));
+ return false;
+ }
+
+ VBOXQGLLOGREL(("2D is supported!\n"));
+ return true;
+}
+
+/* static */
+bool VBoxVHWAInfo::checkVHWASupport()
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)
+ static char pszVBoxPath[RTPATH_MAX];
+ const char *papszArgs[] = { NULL, "-test", "2D", NULL};
+ int rc;
+ RTPROCESS Process;
+ RTPROCSTATUS ProcStatus;
+ uint64_t StartTS;
+
+ rc = RTPathExecDir(pszVBoxPath, RTPATH_MAX); AssertRCReturn(rc, false);
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ rc = RTPathAppend(pszVBoxPath, RTPATH_MAX, "VBoxTestOGL.exe");
+#else
+ rc = RTPathAppend(pszVBoxPath, RTPATH_MAX, "VBoxTestOGL");
+#endif
+ papszArgs[0] = pszVBoxPath; /* argv[0] */
+ AssertRCReturn(rc, false);
+
+ rc = RTProcCreate(pszVBoxPath, papszArgs, RTENV_DEFAULT, 0, &Process);
+ if (RT_FAILURE(rc))
+ {
+ VBOXQGLLOGREL(("2D support test failed: failed to create a test process\n"));
+ return false;
+ }
+
+ StartTS = RTTimeMilliTS();
+
+ while (1)
+ {
+ rc = RTProcWait(Process, RTPROCWAIT_FLAGS_NOBLOCK, &ProcStatus);
+ if (rc != VERR_PROCESS_RUNNING)
+ break;
+
+ if (RTTimeMilliTS() - StartTS > 30*1000 /* 30 sec */)
+ {
+ RTProcTerminate(Process);
+ RTThreadSleep(100);
+ RTProcWait(Process, RTPROCWAIT_FLAGS_NOBLOCK, &ProcStatus);
+ VBOXQGLLOGREL(("2D support test failed: the test did not complete within 30 sec\n"));
+ return false;
+ }
+ RTThreadSleep(100);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if ((ProcStatus.enmReason==RTPROCEXITREASON_NORMAL) && (ProcStatus.iStatus==0))
+ {
+ VBOXQGLLOGREL(("2D support test succeeded\n"));
+ return true;
+ }
+ }
+
+ VBOXQGLLOGREL(("2D support test failed: err code (%Rra)\n", rc));
+
+ return false;
+#else
+ /** @todo test & enable external app approach*/
+ VBoxGLTmpContext ctx;
+ const MY_QOpenGLContext *pContext = ctx.makeCurrent();
+ Assert(pContext);
+ if (pContext)
+ {
+ VBoxVHWAInfo info;
+ info.init(pContext);
+ return info.isVHWASupported();
+ }
+ return false;
+#endif
+}
+
+VBoxGLTmpContext::VBoxGLTmpContext()
+{
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ mWidget = new MY_QOpenGLWidget(/*new QMainWindow()*/);
+#else
+ if (QGLFormat::hasOpenGL())
+ mWidget = new MY_QOpenGLWidget();
+ else
+ mWidget = NULL;
+#endif
+}
+
+VBoxGLTmpContext::~VBoxGLTmpContext()
+{
+ if (mWidget)
+ {
+ delete mWidget;
+ mWidget = NULL;
+ }
+}
+
+const MY_QOpenGLContext *VBoxGLTmpContext::makeCurrent()
+{
+ if (mWidget)
+ {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ mWidget->grabFramebuffer(); /* This is a hack to trigger GL initialization or context() will return NULL. */
+#endif
+ mWidget->makeCurrent();
+ return mWidget->context();
+ }
+ return NULL;
+}
+
diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxTestOGL.rc b/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxTestOGL.rc
new file mode 100644
index 00000000..b937940e
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxTestOGL.rc
@@ -0,0 +1,61 @@
+/* $Id: VBoxTestOGL.rc $ */
+/** @file
+ * VBoxTestOGL - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox OpenGL Test Tool\0"
+ VALUE "InternalName", "VBoxTestOGL\0"
+ VALUE "OriginalFilename", "VBoxTestOGL.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp b/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp
new file mode 100644
index 00000000..811116e1
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp
@@ -0,0 +1,2017 @@
+/* $Id: VBoxExtPackHelperApp.cpp $ */
+/** @file
+ * VirtualBox Main - Extension Pack Helper Application, usually set-uid-to-root.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "../include/ExtPackUtil.h"
+
+#include <iprt/buildconfig.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/fs.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/manifest.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/thread.h>
+#include <iprt/utf16.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+#include <iprt/cpp/ministring.h>
+
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <VBox/sup.h>
+#include <VBox/version.h>
+
+#ifdef RT_OS_WINDOWS
+# define _WIN32_WINNT 0x0501
+# include <iprt/win/windows.h> /* ShellExecuteEx, ++ */
+# include <iprt/win/objbase.h> /* CoInitializeEx */
+# ifdef DEBUG
+# include <Sddl.h>
+# endif
+#endif
+
+#ifdef RT_OS_DARWIN
+# include <Security/Authorization.h>
+# include <Security/AuthorizationTags.h>
+# include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#if !defined(RT_OS_OS2)
+# include <stdio.h>
+# include <errno.h>
+# if !defined(RT_OS_WINDOWS)
+# include <sys/types.h>
+# include <unistd.h> /* geteuid */
+# endif
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Enable elevation on Windows and Darwin. */
+#if !defined(RT_OS_OS2) || defined(DOXYGEN_RUNNING)
+# define WITH_ELEVATION
+#endif
+
+
+/** @name Command and option names
+ * @{ */
+#define CMD_INSTALL 1000
+#define CMD_UNINSTALL 1001
+#define CMD_CLEANUP 1002
+#ifdef WITH_ELEVATION
+# define OPT_ELEVATED 1090
+# define OPT_STDOUT 1091
+# define OPT_STDERR 1092
+#endif
+#define OPT_DISP_INFO_HACK 1093
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+static HINSTANCE g_hInstance;
+#endif
+
+#ifdef IN_RT_R3
+/* Override RTAssertShouldPanic to prevent gdb process creation. */
+RTDECL(bool) RTAssertShouldPanic(void)
+{
+ return true;
+}
+#endif
+
+
+
+/**
+ * Handle the special standard options when these are specified after the
+ * command.
+ *
+ * @param ch The option character.
+ */
+static RTEXITCODE DoStandardOption(int ch)
+{
+ switch (ch)
+ {
+ case 'h':
+ {
+ RTMsgInfo(VBOX_PRODUCT " Extension Pack Helper App\n"
+ "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
+ "\n"
+ "This NOT intended for general use, please use VBoxManage instead\n"
+ "or call the IExtPackManager API directly.\n"
+ "\n"
+ "Usage: %s <command> [options]\n"
+ "Commands:\n"
+ " install --base-dir <dir> --cert-dir <dir> --name <name> \\\n"
+ " --tarball <tarball> --tarball-fd <fd>\n"
+ " uninstall --base-dir <dir> --name <name>\n"
+ " cleanup --base-dir <dir>\n"
+ , RTProcShortName());
+ return RTEXITCODE_SUCCESS;
+ }
+
+ case 'V':
+ RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ AssertFailedReturn(RTEXITCODE_FAILURE);
+ }
+}
+
+
+/**
+ * Checks if the cerficiate directory is valid.
+ *
+ * @returns true if it is valid, false if it isn't.
+ * @param pszCertDir The certificate directory to validate.
+ */
+static bool IsValidCertificateDir(const char *pszCertDir)
+{
+ /*
+ * Just be darn strict for now.
+ */
+ char szCorrect[RTPATH_MAX];
+ int rc = RTPathAppPrivateNoArch(szCorrect, sizeof(szCorrect));
+ if (RT_FAILURE(rc))
+ return false;
+ rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_CERT_DIR);
+ if (RT_FAILURE(rc))
+ return false;
+
+ return RTPathCompare(szCorrect, pszCertDir) == 0;
+}
+
+
+/**
+ * Checks if the base directory is valid.
+ *
+ * @returns true if it is valid, false if it isn't.
+ * @param pszBaesDir The base directory to validate.
+ */
+static bool IsValidBaseDir(const char *pszBaseDir)
+{
+ /*
+ * Just be darn strict for now.
+ */
+ char szCorrect[RTPATH_MAX];
+ int rc = RTPathAppPrivateArchTop(szCorrect, sizeof(szCorrect));
+ if (RT_FAILURE(rc))
+ return false;
+ rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_INSTALL_DIR);
+ if (RT_FAILURE(rc))
+ return false;
+
+ return RTPathCompare(szCorrect, pszBaseDir) == 0;
+}
+
+
+/**
+ * Cleans up a temporary extension pack directory.
+ *
+ * This is used by 'uninstall', 'cleanup' and in the failure path of 'install'.
+ *
+ * @returns The program exit code.
+ * @param pszDir The directory to clean up. The caller is
+ * responsible for making sure this is valid.
+ * @param fTemporary Whether this is a temporary install directory or
+ * not.
+ */
+static RTEXITCODE RemoveExtPackDir(const char *pszDir, bool fTemporary)
+{
+ /** @todo May have to undo 555 modes here later. */
+ int rc = RTDirRemoveRecursive(pszDir, RTDIRRMREC_F_CONTENT_AND_DIR);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "Failed to delete the %sextension pack directory: %Rrc ('%s')",
+ fTemporary ? "temporary " : "", rc, pszDir);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Wrapper around RTDirRename that may retry the operation for up to 15 seconds
+ * on windows to deal with AV software.
+ */
+static int CommonDirRenameWrapper(const char *pszSrc, const char *pszDst, uint32_t fFlags)
+{
+#ifdef RT_OS_WINDOWS
+ uint64_t nsNow = RTTimeNanoTS();
+ for (;;)
+ {
+ int rc = RTDirRename(pszSrc, pszDst, fFlags);
+ if ( ( rc != VERR_ACCESS_DENIED
+ && rc != VERR_SHARING_VIOLATION)
+ || RTTimeNanoTS() - nsNow > RT_NS_15SEC)
+ return rc;
+ RTThreadSleep(128);
+ }
+#else
+ return RTDirRename(pszSrc, pszDst, fFlags);
+#endif
+}
+
+/**
+ * Common uninstall worker used by both uninstall and install --replace.
+ *
+ * @returns success or failure, message displayed on failure.
+ * @param pszExtPackDir The extension pack directory name.
+ */
+static RTEXITCODE CommonUninstallWorker(const char *pszExtPackDir)
+{
+ /* Rename the extension pack directory before deleting it to prevent new
+ VM processes from picking it up. */
+ char szExtPackUnInstDir[RTPATH_MAX];
+ int rc = RTStrCopy(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), pszExtPackDir);
+ if (RT_SUCCESS(rc))
+ rc = RTStrCat(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), "-_-uninst");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct temporary extension pack path: %Rrc", rc);
+
+ rc = CommonDirRenameWrapper(pszExtPackDir, szExtPackUnInstDir, RTPATHRENAME_FLAGS_NO_REPLACE);
+ if (rc == VERR_ALREADY_EXISTS)
+ {
+ /* Automatic cleanup and try again. It's in theory possible that we're
+ racing another cleanup operation here, so just ignore errors and try
+ again. (There is no installation race due to the exclusive temporary
+ installation directory.) */
+ RemoveExtPackDir(szExtPackUnInstDir, false /*fTemporary*/);
+ rc = CommonDirRenameWrapper(pszExtPackDir, szExtPackUnInstDir, RTPATHRENAME_FLAGS_NO_REPLACE);
+ }
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "Failed to rename the extension pack directory: %Rrc\n"
+ "If the problem persists, try running the command: VBoxManage extpack cleanup", rc);
+
+ /* Recursively delete the directory content. */
+ return RemoveExtPackDir(szExtPackUnInstDir, false /*fTemporary*/);
+}
+
+
+/**
+ * Wrapper around VBoxExtPackOpenTarFss.
+ *
+ * @returns success or failure, message displayed on failure.
+ * @param hTarballFile The handle to the tarball file.
+ * @param phTarFss Where to return the filesystem stream handle.
+ */
+static RTEXITCODE OpenTarFss(RTFILE hTarballFile, PRTVFSFSSTREAM phTarFss)
+{
+ char szError[8192];
+ int rc = VBoxExtPackOpenTarFss(hTarballFile, szError, sizeof(szError), phTarFss, NULL);
+ if (RT_FAILURE(rc))
+ {
+ Assert(szError[0]);
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError);
+ }
+ Assert(!szError[0]);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Sets the permissions of the temporary extension pack directory just before
+ * renaming it.
+ *
+ * By default the temporary directory is only accessible by root, this function
+ * will make it world readable and browseable.
+ *
+ * @returns The program exit code.
+ * @param pszDir The temporary extension pack directory.
+ */
+static RTEXITCODE SetExtPackPermissions(const char *pszDir)
+{
+ RTMsgInfo("Setting permissions...");
+#if !defined(RT_OS_WINDOWS)
+ int rc = RTPathSetMode(pszDir, 0755);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions: %Rrc ('%s')", rc, pszDir);
+#else
+ /** @todo TrustedInstaller? */
+ RT_NOREF1(pszDir);
+#endif
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Wrapper around VBoxExtPackValidateMember.
+ *
+ * @returns Program exit code, failure with message.
+ * @param pszName The name of the directory.
+ * @param enmType The object type.
+ * @param hVfsObj The VFS object.
+ */
+static RTEXITCODE ValidateMemberOfExtPack(const char *pszName, RTVFSOBJTYPE enmType, RTVFSOBJ hVfsObj)
+{
+ char szError[8192];
+ int rc = VBoxExtPackValidateMember(pszName, enmType, hVfsObj, szError, sizeof(szError));
+ if (RT_FAILURE(rc))
+ {
+ Assert(szError[0]);
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError);
+ }
+ Assert(!szError[0]);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Validates the extension pack tarball prior to unpacking.
+ *
+ * Operations performed:
+ * - Hardening checks.
+ *
+ * @returns The program exit code.
+ * @param pszDir The directory where the extension pack has been
+ * unpacked.
+ * @param pszExtPackName The expected extension pack name.
+ * @param pszTarball The name of the tarball in case we have to
+ * complain about something.
+ */
+static RTEXITCODE ValidateUnpackedExtPack(const char *pszDir, const char *pszTarball, const char *pszExtPackName)
+{
+ RT_NOREF2(pszTarball, pszExtPackName);
+ RTMsgInfo("Validating unpacked extension pack...");
+
+ RTERRINFOSTATIC ErrInfo;
+ RTErrInfoInitStatic(&ErrInfo);
+ int rc = SUPR3HardenedVerifyDir(pszDir, true /*fRecursive*/, true /*fCheckFiles*/, &ErrInfo.Core);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Hardening check failed with %Rrc: %s", rc, ErrInfo.Core.pszMsg);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Unpacks a directory from an extension pack tarball.
+ *
+ * @returns Program exit code, failure with message.
+ * @param pszDstDirName The name of the unpacked directory.
+ * @param hVfsObj The source object for the directory.
+ */
+static RTEXITCODE UnpackExtPackDir(const char *pszDstDirName, RTVFSOBJ hVfsObj)
+{
+ /*
+ * Get the mode mask before creating the directory.
+ */
+ RTFSOBJINFO ObjInfo;
+ int rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszDstDirName, rc);
+ ObjInfo.Attr.fMode &= ~(RTFS_UNIX_IWOTH | RTFS_UNIX_IWGRP);
+
+ rc = RTDirCreate(pszDstDirName, ObjInfo.Attr.fMode, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create directory '%s': %Rrc", pszDstDirName, rc);
+
+#ifndef RT_OS_WINDOWS
+ /*
+ * Because of umask, we have to apply the mode again.
+ */
+ rc = RTPathSetMode(pszDstDirName, ObjInfo.Attr.fMode);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions on '%s': %Rrc", pszDstDirName, rc);
+#else
+ /** @todo Ownership tricks on windows? */
+#endif
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Unpacks a file from an extension pack tarball.
+ *
+ * @returns Program exit code, failure with message.
+ * @param pszName The name in the tarball.
+ * @param pszDstFilename The name of the unpacked file.
+ * @param hVfsIosSrc The source stream for the file.
+ * @param hUnpackManifest The manifest to add the file digest to.
+ */
+static RTEXITCODE UnpackExtPackFile(const char *pszName, const char *pszDstFilename,
+ RTVFSIOSTREAM hVfsIosSrc, RTMANIFEST hUnpackManifest)
+{
+ /*
+ * Query the object info, we'll need it for buffer sizing as well as
+ * setting the file mode.
+ */
+ RTFSOBJINFO ObjInfo;
+ int rc = RTVfsIoStrmQueryInfo(hVfsIosSrc, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmQueryInfo failed with %Rrc on '%s'", rc, pszDstFilename);
+
+ /*
+ * Create the file.
+ */
+ uint32_t fFlags = RTFILE_O_WRITE | RTFILE_O_DENY_ALL | RTFILE_O_CREATE | (0600 << RTFILE_O_CREATE_MODE_SHIFT);
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pszDstFilename, fFlags);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create '%s': %Rrc", pszDstFilename, rc);
+
+ /*
+ * Create a I/O stream for the destination file, stack a manifest entry
+ * creator on top of it.
+ */
+ RTVFSIOSTREAM hVfsIosDst2;
+ rc = RTVfsIoStrmFromRTFile(hFile, fFlags, true /*fLeaveOpen*/, &hVfsIosDst2);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSIOSTREAM hVfsIosDst;
+ rc = RTManifestEntryAddPassthruIoStream(hUnpackManifest, hVfsIosDst2, pszName,
+ RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256,
+ false /*fReadOrWrite*/, &hVfsIosDst);
+ RTVfsIoStrmRelease(hVfsIosDst2);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Pump the data thru.
+ */
+ rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(ObjInfo.cbObject, _1G));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTManifestPtIosAddEntryNow(hVfsIosDst);
+ if (RT_SUCCESS(rc))
+ {
+ RTVfsIoStrmRelease(hVfsIosDst);
+ hVfsIosDst = NIL_RTVFSIOSTREAM;
+
+ /*
+ * Set the mode mask.
+ */
+ ObjInfo.Attr.fMode &= ~(RTFS_UNIX_IWOTH | RTFS_UNIX_IWGRP);
+ rc = RTFileSetMode(hFile, ObjInfo.Attr.fMode);
+ /** @todo Windows needs to do more here, I think. */
+ if (RT_SUCCESS(rc))
+ {
+ RTFileClose(hFile);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ RTMsgError("Failed to set the mode of '%s' to %RTfmode: %Rrc", pszDstFilename, ObjInfo.Attr.fMode, rc);
+ }
+ else
+ RTMsgError("RTManifestPtIosAddEntryNow failed for '%s': %Rrc", pszDstFilename, rc);
+ }
+ else
+ RTMsgError("RTVfsUtilPumpIoStreams failed for '%s': %Rrc", pszDstFilename, rc);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ }
+ else
+ RTMsgError("RTManifestEntryAddPassthruIoStream failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTVfsIoStrmFromRTFile failed: %Rrc", rc);
+ RTFileClose(hFile);
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Unpacks the extension pack into the specified directory.
+ *
+ * This will apply ownership and permission changes to all the content, the
+ * exception is @a pszDirDst which will be handled by SetExtPackPermissions.
+ *
+ * @returns The program exit code.
+ * @param hTarballFile The tarball to unpack.
+ * @param pszDirDst Where to unpack it.
+ * @param hValidManifest The manifest we've validated.
+ * @param pszTarball The name of the tarball in case we have to
+ * complain about something.
+ */
+static RTEXITCODE UnpackExtPack(RTFILE hTarballFile, const char *pszDirDst, RTMANIFEST hValidManifest,
+ const char *pszTarball)
+{
+ RT_NOREF1(pszTarball);
+ RTMsgInfo("Unpacking extension pack into '%s'...", pszDirDst);
+
+ /*
+ * Set up the destination path.
+ */
+ char szDstPath[RTPATH_MAX];
+ int rc = RTPathAbs(pszDirDst, szDstPath, sizeof(szDstPath) - VBOX_EXTPACK_MAX_MEMBER_NAME_LENGTH - 2);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs('%s',,) failed: %Rrc", pszDirDst, rc);
+ size_t offDstPath = RTPathStripTrailingSlash(szDstPath);
+ szDstPath[offDstPath++] = '/';
+ szDstPath[offDstPath] = '\0';
+
+ /*
+ * Open the tar.gz filesystem stream and set up an manifest in-memory file.
+ */
+ RTVFSFSSTREAM hTarFss;
+ RTEXITCODE rcExit = OpenTarFss(hTarballFile, &hTarFss);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ RTMANIFEST hUnpackManifest;
+ rc = RTManifestCreate(0 /*fFlags*/, &hUnpackManifest);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Process the tarball (would be nice to move this to a function).
+ */
+ for (;;)
+ {
+ /*
+ * Get the next stream object.
+ */
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ RTVFSOBJTYPE enmType;
+ rc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj);
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_EOF)
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFsStrmNext failed: %Rrc", rc);
+ break;
+ }
+ const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
+
+ /*
+ * Check the type & name validity then unpack it.
+ */
+ rcExit = ValidateMemberOfExtPack(pszName, enmType, hVfsObj);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ szDstPath[offDstPath] = '\0';
+ rc = RTStrCopy(&szDstPath[offDstPath], sizeof(szDstPath) - offDstPath, pszAdjName);
+ if (RT_SUCCESS(rc))
+ {
+ if ( enmType == RTVFSOBJTYPE_FILE
+ || enmType == RTVFSOBJTYPE_IO_STREAM)
+ {
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ rcExit = UnpackExtPackFile(pszAdjName, szDstPath, hVfsIos, hUnpackManifest);
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ else if (*pszAdjName && strcmp(pszAdjName, "."))
+ rcExit = UnpackExtPackDir(szDstPath, hVfsObj);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Name is too long: '%s' (%Rrc)", pszAdjName, rc);
+ }
+
+ /*
+ * Clean up and break out on failure.
+ */
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+ }
+
+ /*
+ * Check that what we just extracted matches the already verified
+ * manifest.
+ */
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ char szError[RTPATH_MAX];
+ rc = RTManifestEqualsEx(hUnpackManifest, hValidManifest, NULL /*papszIgnoreEntries*/, NULL /*papszIgnoreAttr*/,
+ 0 /*fFlags*/, szError, sizeof(szError));
+ if (RT_SUCCESS(rc))
+ rcExit = RTEXITCODE_SUCCESS;
+ else if (rc == VERR_NOT_EQUAL && szError[0])
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Manifest mismatch: %s", szError);
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTManifestEqualsEx failed: %Rrc", rc);
+ }
+#if 0
+ RTVFSIOSTREAM hVfsIosStdOut = NIL_RTVFSIOSTREAM;
+ RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, true, &hVfsIosStdOut);
+ RTVfsIoStrmWrite(hVfsIosStdOut, "Unpack:\n", sizeof("Unpack:\n") - 1, true, NULL);
+ RTManifestWriteStandard(hUnpackManifest, hVfsIosStdOut);
+ RTVfsIoStrmWrite(hVfsIosStdOut, "Valid:\n", sizeof("Valid:\n") - 1, true, NULL);
+ RTManifestWriteStandard(hValidManifest, hVfsIosStdOut);
+#endif
+ RTManifestRelease(hUnpackManifest);
+ }
+ RTVfsFsStrmRelease(hTarFss);
+
+ return rcExit;
+}
+
+
+
+/**
+ * Wrapper around VBoxExtPackValidateTarball.
+ *
+ * @returns The program exit code.
+ * @param hTarballFile The handle to open the @a pszTarball file.
+ * @param pszExtPackName The name of the extension pack name.
+ * @param pszTarball The name of the tarball in case we have to
+ * complain about something.
+ * @param pszTarballDigest The SHA-256 digest of the tarball.
+ * @param phValidManifest Where to return the handle to fully validated
+ * the manifest for the extension pack. This
+ * includes all files.
+ */
+static RTEXITCODE ValidateExtPackTarball(RTFILE hTarballFile, const char *pszExtPackName, const char *pszTarball,
+ const char *pszTarballDigest, PRTMANIFEST phValidManifest)
+{
+ *phValidManifest = NIL_RTMANIFEST;
+ RTMsgInfo("Validating extension pack '%s' ('%s')...", pszTarball, pszExtPackName);
+ Assert(pszTarballDigest && *pszTarballDigest);
+
+ char szError[8192];
+ int rc = VBoxExtPackValidateTarball(hTarballFile, pszExtPackName, pszTarball, pszTarballDigest,
+ szError, sizeof(szError), phValidManifest, NULL /*phXmlFile*/, NULL /*pStrDigest*/);
+ if (RT_FAILURE(rc))
+ {
+ Assert(szError[0]);
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError);
+ }
+ Assert(!szError[0]);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * The 2nd part of the installation process.
+ *
+ * @returns The program exit code.
+ * @param pszBaseDir The base directory.
+ * @param pszCertDir The certificat directory.
+ * @param pszTarball The tarball name.
+ * @param pszTarballDigest The SHA-256 digest of the tarball. Empty string
+ * if no digest available.
+ * @param hTarballFile The handle to open the @a pszTarball file.
+ * @param hTarballFileOpt The tarball file handle (optional).
+ * @param pszName The extension pack name.
+ * @param pszMangledName The mangled extension pack name.
+ * @param fReplace Whether to replace any existing ext pack.
+ */
+static RTEXITCODE DoInstall2(const char *pszBaseDir, const char *pszCertDir, const char *pszTarball,
+ const char *pszTarballDigest, RTFILE hTarballFile, RTFILE hTarballFileOpt,
+ const char *pszName, const char *pszMangledName, bool fReplace)
+{
+ RT_NOREF1(pszCertDir);
+
+ /*
+ * Do some basic validation of the tarball file.
+ */
+ RTFSOBJINFO ObjInfo;
+ int rc = RTFileQueryInfo(hTarballFile, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on '%s'", rc, pszTarball);
+ if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Not a regular file: %s", pszTarball);
+
+ if (hTarballFileOpt != NIL_RTFILE)
+ {
+ RTFSOBJINFO ObjInfo2;
+ rc = RTFileQueryInfo(hTarballFileOpt, &ObjInfo2, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on --tarball-fd", rc);
+ if ( ObjInfo.Attr.u.Unix.INodeIdDevice != ObjInfo2.Attr.u.Unix.INodeIdDevice
+ || ObjInfo.Attr.u.Unix.INodeId != ObjInfo2.Attr.u.Unix.INodeId)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "--tarball and --tarball-fd does not match");
+ }
+
+ /*
+ * Construct the paths to the two directories we'll be using.
+ */
+ char szFinalPath[RTPATH_MAX];
+ rc = RTPathJoin(szFinalPath, sizeof(szFinalPath), pszBaseDir, pszMangledName);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "Failed to construct the path to the final extension pack directory: %Rrc", rc);
+
+ char szTmpPath[RTPATH_MAX];
+ rc = RTPathJoin(szTmpPath, sizeof(szTmpPath) - 64, pszBaseDir, pszMangledName);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchTmpPath = strlen(szTmpPath);
+ RTStrPrintf(&szTmpPath[cchTmpPath], sizeof(szTmpPath) - cchTmpPath, "-_-inst-%u", (uint32_t)RTProcSelf());
+ }
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "Failed to construct the path to the temporary extension pack directory: %Rrc", rc);
+
+ /*
+ * Check that they don't exist at this point in time, unless fReplace=true.
+ */
+ rc = RTPathQueryInfoEx(szFinalPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc) && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ {
+ if (!fReplace)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "The extension pack is already installed. You must uninstall the old one first.");
+ }
+ else if (RT_SUCCESS(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "Found non-directory file system object where the extension pack would be installed ('%s')",
+ szFinalPath);
+ else if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
+
+ rc = RTPathQueryInfoEx(szTmpPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
+
+ /*
+ * Create the temporary directory and prepare the extension pack within it.
+ * If all checks out correctly, rename it to the final directory.
+ */
+ RTDirCreate(pszBaseDir, 0755, 0);
+#ifndef RT_OS_WINDOWS
+ /*
+ * Because of umask, we have to apply the mode again.
+ */
+ rc = RTPathSetMode(pszBaseDir, 0755);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions on '%s': %Rrc", pszBaseDir, rc);
+#else
+ /** @todo Ownership tricks on windows? */
+#endif
+ rc = RTDirCreate(szTmpPath, 0700, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create temporary directory: %Rrc ('%s')", rc, szTmpPath);
+
+ RTMANIFEST hValidManifest = NIL_RTMANIFEST;
+ RTEXITCODE rcExit = ValidateExtPackTarball(hTarballFile, pszName, pszTarball, pszTarballDigest, &hValidManifest);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = UnpackExtPack(hTarballFile, szTmpPath, hValidManifest, pszTarball);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = ValidateUnpackedExtPack(szTmpPath, pszTarball, pszName);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = SetExtPackPermissions(szTmpPath);
+ RTManifestRelease(hValidManifest);
+
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ rc = CommonDirRenameWrapper(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE);
+ if ( RT_FAILURE(rc)
+ && fReplace
+ && RTDirExists(szFinalPath))
+ {
+ /* Automatic uninstall if --replace was given. */
+ rcExit = CommonUninstallWorker(szFinalPath);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rc = CommonDirRenameWrapper(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE);
+ }
+ if (RT_SUCCESS(rc))
+ RTMsgInfo("Successfully installed '%s' (%s)", pszName, pszTarball);
+ else if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "Failed to rename the temporary directory to the final one: %Rrc ('%s' -> '%s')",
+ rc, szTmpPath, szFinalPath);
+ }
+
+ /*
+ * Clean up the temporary directory on failure.
+ */
+ if (rcExit != RTEXITCODE_SUCCESS)
+ RemoveExtPackDir(szTmpPath, true /*fTemporary*/);
+
+ return rcExit;
+}
+
+
+/**
+ * Implements the 'install' command.
+ *
+ * @returns The program exit code.
+ * @param argc The number of program arguments.
+ * @param argv The program arguments.
+ */
+static RTEXITCODE DoInstall(int argc, char **argv)
+{
+ /*
+ * Parse the parameters.
+ *
+ * Note! The --base-dir and --cert-dir are only for checking that the
+ * caller and this help applications have the same idea of where
+ * things are. Likewise, the --name is for verifying assumptions
+ * the caller made about the name. The optional --tarball-fd option
+ * is just for easing the paranoia on the user side.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--base-dir", 'b', RTGETOPT_REQ_STRING },
+ { "--cert-dir", 'c', RTGETOPT_REQ_STRING },
+ { "--name", 'n', RTGETOPT_REQ_STRING },
+ { "--tarball", 't', RTGETOPT_REQ_STRING },
+ { "--tarball-fd", 'd', RTGETOPT_REQ_UINT64 },
+ { "--replace", 'r', RTGETOPT_REQ_NOTHING },
+ { "--sha-256", 's', RTGETOPT_REQ_STRING }
+ };
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
+
+ const char *pszBaseDir = NULL;
+ const char *pszCertDir = NULL;
+ const char *pszName = NULL;
+ const char *pszTarball = NULL;
+ const char *pszTarballDigest = NULL;
+ RTFILE hTarballFileOpt = NIL_RTFILE;
+ bool fReplace = false;
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'b':
+ if (pszBaseDir)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
+ pszBaseDir = ValueUnion.psz;
+ if (!IsValidBaseDir(pszBaseDir))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
+ break;
+
+ case 'c':
+ if (pszCertDir)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --cert-dir options");
+ pszCertDir = ValueUnion.psz;
+ if (!IsValidCertificateDir(pszCertDir))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid certificate directory: '%s'", pszCertDir);
+ break;
+
+ case 'n':
+ if (pszName)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options");
+ pszName = ValueUnion.psz;
+ if (!VBoxExtPackIsValidName(pszName))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName);
+ break;
+
+ case 't':
+ if (pszTarball)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball options");
+ pszTarball = ValueUnion.psz;
+ break;
+
+ case 'd':
+ {
+ if (hTarballFileOpt != NIL_RTFILE)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball-fd options");
+ RTHCUINTPTR hNative = (RTHCUINTPTR)ValueUnion.u64;
+ if (hNative != ValueUnion.u64)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --tarball-fd value is out of range: %#RX64", ValueUnion.u64);
+ rc = RTFileFromNative(&hTarballFileOpt, hNative);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTFileFromNative failed on --target-fd value: %Rrc", rc);
+ break;
+ }
+
+ case 'r':
+ fReplace = true;
+ break;
+
+ case 's':
+ {
+ if (pszTarballDigest)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --sha-256 options");
+ pszTarballDigest = ValueUnion.psz;
+
+ uint8_t abDigest[RTSHA256_HASH_SIZE];
+ rc = RTSha256FromString(pszTarballDigest, abDigest);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Bad SHA-256 string: %Rrc", rc);
+ break;
+ }
+
+ case 'h':
+ case 'V':
+ return DoStandardOption(ch);
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (!pszName)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option");
+ if (!pszBaseDir)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
+ if (!pszCertDir)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --cert-dir option");
+ if (!pszTarball)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --tarball option");
+ if (!pszTarballDigest)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --sha-256 option");
+
+ /*
+ * Ok, down to business.
+ */
+ RTCString *pstrMangledName = VBoxExtPackMangleName(pszName);
+ if (!pstrMangledName)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to mangle name ('%s)", pszName);
+
+ RTEXITCODE rcExit;
+ RTFILE hTarballFile;
+ rc = RTFileOpen(&hTarballFile, pszTarball, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rcExit = DoInstall2(pszBaseDir, pszCertDir, pszTarball, pszTarballDigest, hTarballFile, hTarballFileOpt,
+ pszName, pstrMangledName->c_str(), fReplace);
+ RTFileClose(hTarballFile);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open the extension pack tarball: %Rrc ('%s')", rc, pszTarball);
+
+ delete pstrMangledName;
+ return rcExit;
+}
+
+
+/**
+ * Implements the 'uninstall' command.
+ *
+ * @returns The program exit code.
+ * @param argc The number of program arguments.
+ * @param argv The program arguments.
+ */
+static RTEXITCODE DoUninstall(int argc, char **argv)
+{
+ /*
+ * Parse the parameters.
+ *
+ * Note! The --base-dir is only for checking that the caller and this help
+ * applications have the same idea of where things are.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--base-dir", 'b', RTGETOPT_REQ_STRING },
+ { "--name", 'n', RTGETOPT_REQ_STRING },
+ { "--forced", 'f', RTGETOPT_REQ_NOTHING },
+ };
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
+
+ const char *pszBaseDir = NULL;
+ const char *pszName = NULL;
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'b':
+ if (pszBaseDir)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
+ pszBaseDir = ValueUnion.psz;
+ if (!IsValidBaseDir(pszBaseDir))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
+ break;
+
+ case 'n':
+ if (pszName)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options");
+ pszName = ValueUnion.psz;
+ if (!VBoxExtPackIsValidName(pszName))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName);
+ break;
+
+ case 'f':
+ /* ignored */
+ break;
+
+ case 'h':
+ case 'V':
+ return DoStandardOption(ch);
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (!pszName)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option");
+ if (!pszBaseDir)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
+
+ /*
+ * Mangle the name so we can construct the directory names.
+ */
+ RTCString *pstrMangledName = VBoxExtPackMangleName(pszName);
+ if (!pstrMangledName)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to mangle name ('%s)", pszName);
+ RTCString strMangledName(*pstrMangledName);
+ delete pstrMangledName;
+
+ /*
+ * Ok, down to business.
+ */
+ /* Check that it exists. */
+ char szExtPackDir[RTPATH_MAX];
+ rc = RTPathJoin(szExtPackDir, sizeof(szExtPackDir), pszBaseDir, strMangledName.c_str());
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct extension pack path: %Rrc", rc);
+
+ if (!RTDirExists(szExtPackDir))
+ {
+ RTMsgInfo("Extension pack not installed. Nothing to do.");
+ return RTEXITCODE_SUCCESS;
+ }
+
+ RTEXITCODE rcExit = CommonUninstallWorker(szExtPackDir);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ RTMsgInfo("Successfully removed extension pack '%s'\n", pszName);
+
+ return rcExit;
+}
+
+/**
+ * Implements the 'cleanup' command.
+ *
+ * @returns The program exit code.
+ * @param argc The number of program arguments.
+ * @param argv The program arguments.
+ */
+static RTEXITCODE DoCleanup(int argc, char **argv)
+{
+ /*
+ * Parse the parameters.
+ *
+ * Note! The --base-dir is only for checking that the caller and this help
+ * applications have the same idea of where things are.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--base-dir", 'b', RTGETOPT_REQ_STRING },
+ };
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
+
+ const char *pszBaseDir = NULL;
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'b':
+ if (pszBaseDir)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
+ pszBaseDir = ValueUnion.psz;
+ if (!IsValidBaseDir(pszBaseDir))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
+ break;
+
+ case 'h':
+ case 'V':
+ return DoStandardOption(ch);
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (!pszBaseDir)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
+
+ /*
+ * Ok, down to business.
+ */
+ RTDIR hDir;
+ rc = RTDirOpen(&hDir, pszBaseDir);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed open the base directory: %Rrc ('%s')", rc, pszBaseDir);
+
+ uint32_t cCleaned = 0;
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ for (;;)
+ {
+ RTDIRENTRYEX Entry;
+ rc = RTDirReadEx(hDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_NO_MORE_FILES)
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirReadEx returns %Rrc", rc);
+ break;
+ }
+
+ /*
+ * Only directories which conform with our temporary install/uninstall
+ * naming scheme are candidates for cleaning.
+ */
+ if ( RTFS_IS_DIRECTORY(Entry.Info.Attr.fMode)
+ && strcmp(Entry.szName, ".") != 0
+ && strcmp(Entry.szName, "..") != 0)
+ {
+ bool fCandidate = false;
+ char *pszMarker = strstr(Entry.szName, "-_-");
+ if ( pszMarker
+ && ( !strcmp(pszMarker, "-_-uninst")
+ || !strncmp(pszMarker, RT_STR_TUPLE("-_-inst"))))
+ fCandidate = VBoxExtPackIsValidMangledName(Entry.szName, pszMarker - &Entry.szName[0]);
+ if (fCandidate)
+ {
+ /*
+ * Recursive delete, safe.
+ */
+ char szPath[RTPATH_MAX];
+ rc = RTPathJoin(szPath, sizeof(szPath), pszBaseDir, Entry.szName);
+ if (RT_SUCCESS(rc))
+ {
+ RTEXITCODE rcExit2 = RemoveExtPackDir(szPath, true /*fTemporary*/);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ RTMsgInfo("Successfully removed '%s'.", Entry.szName);
+ else if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathJoin failed with %Rrc for '%s'", rc, Entry.szName);
+ cCleaned++;
+ }
+ }
+ }
+ RTDirClose(hDir);
+ if (!cCleaned)
+ RTMsgInfo("Nothing to clean.");
+ return rcExit;
+}
+
+#ifdef WITH_ELEVATION
+
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_DARWIN)
+/**
+ * Looks in standard locations for a suitable exec tool.
+ *
+ * @returns true if found, false if not.
+ * @param pszPath Where to store the path to the tool on
+ * successs.
+ * @param cbPath The size of the buffer @a pszPath points to.
+ * @param pszName The name of the tool we're looking for.
+ */
+static bool FindExecTool(char *pszPath, size_t cbPath, const char *pszName)
+{
+ static const char * const s_apszPaths[] =
+ {
+ "/bin",
+ "/usr/bin",
+ "/usr/local/bin",
+ "/sbin",
+ "/usr/sbin",
+ "/usr/local/sbin",
+#ifdef RT_OS_SOLARIS
+ "/usr/sfw/bin",
+ "/usr/gnu/bin",
+ "/usr/xpg4/bin",
+ "/usr/xpg6/bin",
+ "/usr/openwin/bin",
+ "/usr/ucb"
+#endif
+ };
+
+ for (unsigned i = 0; i < RT_ELEMENTS(s_apszPaths); i++)
+ {
+ int rc = RTPathJoin(pszPath, cbPath, s_apszPaths[i], pszName);
+ if (RT_SUCCESS(rc))
+ {
+ RTFSOBJINFO ObjInfo;
+ rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ if (!(ObjInfo.Attr.fMode & RTFS_UNIX_IWOTH))
+ return true;
+ }
+ }
+ }
+ return false;
+}
+#endif
+
+
+/**
+ * Copies the content of a file to a stream.
+ *
+ * @param hSrc The source file.
+ * @param pDst The destination stream.
+ * @param fComplain Whether to complain about errors (i.e. is this
+ * stderr, if not keep the trap shut because it
+ * may be missing when running under VBoxSVC.)
+ */
+static void CopyFileToStdXxx(RTFILE hSrc, PRTSTREAM pDst, bool fComplain)
+{
+ int rc;
+ for (;;)
+ {
+ char abBuf[0x1000];
+ size_t cbRead;
+ rc = RTFileRead(hSrc, abBuf, sizeof(abBuf), &cbRead);
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("RTFileRead failed: %Rrc", rc);
+ break;
+ }
+ if (!cbRead)
+ break;
+ rc = RTStrmWrite(pDst, abBuf, cbRead);
+ if (RT_FAILURE(rc))
+ {
+ if (fComplain)
+ RTMsgError("RTStrmWrite failed: %Rrc", rc);
+ break;
+ }
+ }
+ rc = RTStrmFlush(pDst);
+ if (RT_FAILURE(rc) && fComplain)
+ RTMsgError("RTStrmFlush failed: %Rrc", rc);
+}
+
+
+/**
+ * Relaunches ourselves as a elevated process using platform specific facilities.
+ *
+ * @returns Program exit code.
+ * @param pszExecPath The executable path.
+ * @param papszArgs The arguments.
+ * @param cSuArgs The number of argument entries reserved for the
+ * 'su' like programs at the start of papszArgs.
+ * @param cMyArgs The number of arguments following @a cSuArgs.
+ * @param iCmd The command that is being executed. (For
+ * selecting messages.)
+ * @param pszDisplayInfoHack Display information hack. Platform specific++.
+ */
+static RTEXITCODE RelaunchElevatedNative(const char *pszExecPath, const char **papszArgs, int cSuArgs, int cMyArgs,
+ int iCmd, const char *pszDisplayInfoHack)
+{
+ RT_NOREF1(cMyArgs);
+ RTEXITCODE rcExit = RTEXITCODE_FAILURE;
+#ifdef RT_OS_WINDOWS
+ NOREF(iCmd);
+
+ MSG Msg;
+ PeekMessage(&Msg, NULL, 0, 0, PM_NOREMOVE);
+ CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
+
+ SHELLEXECUTEINFOW Info;
+
+ Info.cbSize = sizeof(Info);
+ Info.fMask = SEE_MASK_NOCLOSEPROCESS;
+ Info.hwnd = NULL;
+ Info.lpVerb = L"runas";
+ int rc = RTStrToUtf16(pszExecPath, (PRTUTF16 *)&Info.lpFile);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszCmdLine;
+ rc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs + 1], RTGETOPTARGV_CNV_QUOTE_MS_CRT);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrToUtf16(pszCmdLine, (PRTUTF16 *)&Info.lpParameters);
+ if (RT_SUCCESS(rc))
+ {
+ Info.lpDirectory = NULL;
+ Info.nShow = SW_SHOWMAXIMIZED;
+ Info.hInstApp = NULL;
+ Info.lpIDList = NULL;
+ Info.lpClass = NULL;
+ Info.hkeyClass = NULL;
+ Info.dwHotKey = 0;
+ Info.hMonitor = NULL;
+ Info.hProcess = INVALID_HANDLE_VALUE;
+
+ /* Apply display hacks. */
+ if (pszDisplayInfoHack)
+ {
+ const char *pszArg = strstr(pszDisplayInfoHack, "hwnd=");
+ if (pszArg)
+ {
+ uint64_t u64Hwnd;
+ rc = RTStrToUInt64Ex(pszArg + sizeof("hwnd=") - 1, NULL, 0, &u64Hwnd);
+ if (RT_SUCCESS(rc))
+ {
+ HWND hwnd = (HWND)(uintptr_t)u64Hwnd;
+ Info.hwnd = hwnd;
+ Info.hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
+ }
+ }
+ }
+ if (Info.hMonitor == NULL)
+ {
+ POINT Pt = {0,0};
+ Info.hMonitor = MonitorFromPoint(Pt, MONITOR_DEFAULTTOPRIMARY);
+ }
+ if (Info.hMonitor != NULL)
+ Info.fMask |= SEE_MASK_HMONITOR;
+
+ if (ShellExecuteExW(&Info))
+ {
+ if (Info.hProcess != INVALID_HANDLE_VALUE)
+ {
+ /*
+ * Wait for the process, make sure the deal with messages.
+ */
+ for (;;)
+ {
+ DWORD dwRc = MsgWaitForMultipleObjects(1, &Info.hProcess, FALSE, 5000/*ms*/, QS_ALLEVENTS);
+ if (dwRc == WAIT_OBJECT_0)
+ break;
+ if ( dwRc != WAIT_TIMEOUT
+ && dwRc != WAIT_OBJECT_0 + 1)
+ {
+ RTMsgError("MsgWaitForMultipleObjects returned: %#x (%d), err=%u", dwRc, dwRc, GetLastError());
+ break;
+ }
+ while (PeekMessageW(&Msg, NULL, 0, 0, PM_REMOVE))
+ {
+ TranslateMessage(&Msg);
+ DispatchMessageW(&Msg);
+ }
+ }
+
+ DWORD dwExitCode;
+ if (GetExitCodeProcess(Info.hProcess, &dwExitCode))
+ {
+ if (dwExitCode < 128)
+ rcExit = (RTEXITCODE)dwExitCode;
+ else
+ rcExit = RTEXITCODE_FAILURE;
+ }
+ CloseHandle(Info.hProcess);
+ }
+ else
+ RTMsgError("ShellExecuteExW return INVALID_HANDLE_VALUE as Info.hProcess");
+ }
+ else
+ RTMsgError("ShellExecuteExW failed: %u (%#x)", GetLastError(), GetLastError());
+
+
+ RTUtf16Free((PRTUTF16)Info.lpParameters);
+ }
+ RTStrFree(pszCmdLine);
+ }
+
+ RTUtf16Free((PRTUTF16)Info.lpFile);
+ }
+ else
+ RTMsgError("RTStrToUtf16 failed: %Rc", rc);
+
+#elif defined(RT_OS_DARWIN)
+ RT_NOREF(pszDisplayInfoHack);
+ char szIconName[RTPATH_MAX];
+ int rc = RTPathAppPrivateArch(szIconName, sizeof(szIconName));
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppend(szIconName, sizeof(szIconName), "../Resources/virtualbox.png");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct icon path: %Rrc", rc);
+
+ AuthorizationRef AuthRef;
+ OSStatus orc = AuthorizationCreate(NULL, 0, kAuthorizationFlagDefaults, &AuthRef);
+ if (orc == errAuthorizationSuccess)
+ {
+ /*
+ * Preautorize the privileged execution of ourselves.
+ */
+ AuthorizationItem AuthItem = { kAuthorizationRightExecute, 0, NULL, 0 };
+ AuthorizationRights AuthRights = { 1, &AuthItem };
+
+ NOREF(iCmd);
+ static char s_szPrompt[] = "VirtualBox needs further rights to make changes to your installation.\n\n";
+ AuthorizationItem aAuthEnvItems[] =
+ {
+ { kAuthorizationEnvironmentPrompt, strlen(s_szPrompt), s_szPrompt, 0 },
+ { kAuthorizationEnvironmentIcon, strlen(szIconName), szIconName, 0 }
+ };
+ AuthorizationEnvironment AuthEnv = { RT_ELEMENTS(aAuthEnvItems), aAuthEnvItems };
+
+ orc = AuthorizationCopyRights(AuthRef, &AuthRights, &AuthEnv,
+ kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed
+ | kAuthorizationFlagExtendRights,
+ NULL);
+ if (orc == errAuthorizationSuccess)
+ {
+ /*
+ * Execute with extra permissions
+ */
+ FILE *pSocketStrm;
+#if defined(__clang__) || RT_GNUC_PREREQ(4, 4)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ orc = AuthorizationExecuteWithPrivileges(AuthRef, pszExecPath, kAuthorizationFlagDefaults,
+ (char * const *)&papszArgs[cSuArgs + 3],
+ &pSocketStrm);
+#if defined(__clang__) || RT_GNUC_PREREQ(4, 4)
+# pragma GCC diagnostic pop
+#endif
+ if (orc == errAuthorizationSuccess)
+ {
+ /*
+ * Read the output of the tool, the read will fail when it quits.
+ */
+ for (;;)
+ {
+ char achBuf[1024];
+ size_t cbRead = fread(achBuf, 1, sizeof(achBuf), pSocketStrm);
+ if (!cbRead)
+ break;
+ fwrite(achBuf, 1, cbRead, stdout);
+ }
+ rcExit = RTEXITCODE_SUCCESS;
+ fclose(pSocketStrm);
+ }
+ else
+ RTMsgError("AuthorizationExecuteWithPrivileges failed: %d", orc);
+ }
+ else if (orc == errAuthorizationCanceled)
+ RTMsgError("Authorization canceled by the user");
+ else
+ RTMsgError("AuthorizationCopyRights failed: %d", orc);
+ AuthorizationFree(AuthRef, kAuthorizationFlagDefaults);
+ }
+ else
+ RTMsgError("AuthorizationCreate failed: %d", orc);
+
+#else
+
+ RT_NOREF2(pszExecPath, pszDisplayInfoHack);
+
+ /*
+ * Several of the alternatives below will require a command line.
+ */
+ char *pszCmdLine;
+ int rc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs], RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvToString failed: %Rrc", rc);
+
+ /*
+ * Look for various standard stuff for executing a program as root.
+ *
+ * N.B. When adding new arguments, please make 100% sure RelaunchElevated
+ * allocates enough array entries.
+ *
+ * TODO: Feel free to contribute code for using PolicyKit directly.
+ */
+ bool fHaveDisplayVar = RTEnvExist("DISPLAY");
+ int iSuArg = cSuArgs;
+ char szExecTool[260];
+ char szXterm[260];
+
+ /*
+ * kdesudo is available on KDE3/KDE4
+ */
+ if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "kdesudo"))
+ {
+ iSuArg = cSuArgs - 4;
+ papszArgs[cSuArgs - 4] = szExecTool;
+ papszArgs[cSuArgs - 3] = "--comment";
+ papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL
+ ? "VirtualBox extension pack installer"
+ : iCmd == CMD_UNINSTALL
+ ? "VirtualBox extension pack uninstaller"
+ : "VirtualBox extension pack maintainer";
+ papszArgs[cSuArgs - 1] = "--";
+ }
+ /*
+ * gksu is our favorite as it is very well integrated.
+ */
+ else if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "gksu"))
+ {
+#if 0 /* older gksu does not grok --description nor '--' and multiple args. */
+ iSuArg = cSuArgs - 4;
+ papszArgs[cSuArgs - 4] = szExecTool;
+ papszArgs[cSuArgs - 3] = "--description";
+ papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL
+ ? "VirtualBox extension pack installer"
+ : iCmd == CMD_UNINSTALL
+ ? "VirtualBox extension pack uninstaller"
+ : "VirtualBox extension pack maintainer";
+ papszArgs[cSuArgs - 1] = "--";
+#elif defined(RT_OS_SOLARIS) /* Force it not to use pfexec as it won't wait then. */
+ iSuArg = cSuArgs - 4;
+ papszArgs[cSuArgs - 4] = szExecTool;
+ papszArgs[cSuArgs - 3] = "-au";
+ papszArgs[cSuArgs - 2] = "root";
+ papszArgs[cSuArgs - 1] = pszCmdLine;
+ papszArgs[cSuArgs] = NULL;
+#else
+ iSuArg = cSuArgs - 2;
+ papszArgs[cSuArgs - 2] = szExecTool;
+ papszArgs[cSuArgs - 1] = pszCmdLine;
+ papszArgs[cSuArgs] = NULL;
+#endif
+ }
+ /*
+ * pkexec may work for ssh console sessions as well if the right agents
+ * are installed. However it is very generic and does not allow for any
+ * custom messages. Thus it comes after gksu.
+ */
+ else if (FindExecTool(szExecTool, sizeof(szExecTool), "pkexec"))
+ {
+ iSuArg = cSuArgs - 1;
+ papszArgs[cSuArgs - 1] = szExecTool;
+ }
+ /*
+ * The ultimate fallback is running 'su -' within an xterm. We use the
+ * title of the xterm to tell what is going on.
+ */
+ else if ( fHaveDisplayVar
+ && FindExecTool(szExecTool, sizeof(szExecTool), "su")
+ && FindExecTool(szXterm, sizeof(szXterm), "xterm"))
+ {
+ iSuArg = cSuArgs - 9;
+ papszArgs[cSuArgs - 9] = szXterm;
+ papszArgs[cSuArgs - 8] = "-T";
+ papszArgs[cSuArgs - 7] = iCmd == CMD_INSTALL
+ ? "VirtualBox extension pack installer - su"
+ : iCmd == CMD_UNINSTALL
+ ? "VirtualBox extension pack uninstaller - su"
+ : "VirtualBox extension pack maintainer - su";
+ papszArgs[cSuArgs - 6] = "-e";
+ papszArgs[cSuArgs - 5] = szExecTool;
+ papszArgs[cSuArgs - 4] = "-";
+ papszArgs[cSuArgs - 3] = "root";
+ papszArgs[cSuArgs - 2] = "-c";
+ papszArgs[cSuArgs - 1] = pszCmdLine;
+ papszArgs[cSuArgs] = NULL;
+ }
+ else if (fHaveDisplayVar)
+ RTMsgError("Unable to locate 'pkexec', 'gksu' or 'su+xterm'. Try perform the operation using VBoxManage running as root");
+ else
+ RTMsgError("Unable to locate 'pkexec'. Try perform the operation using VBoxManage running as root");
+ if (iSuArg != cSuArgs)
+ {
+ AssertRelease(iSuArg >= 0);
+
+ /*
+ * Argument list constructed, execute it and wait for the exec
+ * program to complete.
+ */
+ RTPROCESS hProcess;
+ rc = RTProcCreateEx(papszArgs[iSuArg], &papszArgs[iSuArg], RTENV_DEFAULT, 0 /*fFlags*/, NULL /*phStdIn*/,
+ NULL /*phStdOut*/, NULL /*phStdErr*/, NULL /*pszAsUser*/, NULL /*pszPassword*/, NULL /* pvExtraData*/,
+ &hProcess);
+ if (RT_SUCCESS(rc))
+ {
+ RTPROCSTATUS Status;
+ rc = RTProcWait(hProcess, RTPROCWAIT_FLAGS_BLOCK, &Status);
+ if (RT_SUCCESS(rc))
+ {
+ if (Status.enmReason == RTPROCEXITREASON_NORMAL)
+ rcExit = (RTEXITCODE)Status.iStatus;
+ else
+ rcExit = RTEXITCODE_FAILURE;
+ }
+ else
+ RTMsgError("Error while waiting for '%s': %Rrc", papszArgs[iSuArg], rc);
+ }
+ else
+ RTMsgError("Failed to execute '%s': %Rrc", papszArgs[iSuArg], rc);
+ }
+ RTStrFree(pszCmdLine);
+
+#endif
+ return rcExit;
+}
+
+
+/**
+ * Relaunches ourselves as a elevated process using platform specific facilities.
+ *
+ * @returns Program exit code.
+ * @param argc The number of arguments.
+ * @param argv The arguments.
+ * @param iCmd The command that is being executed.
+ * @param pszDisplayInfoHack Display information hack. Platform specific++.
+ */
+static RTEXITCODE RelaunchElevated(int argc, char **argv, int iCmd, const char *pszDisplayInfoHack)
+{
+ /*
+ * We need the executable name later, so get it now when it's easy to quit.
+ */
+ char szExecPath[RTPATH_MAX];
+ if (!RTProcGetExecutablePath(szExecPath,sizeof(szExecPath)))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed");
+
+ /*
+ * Create a couple of temporary files for stderr and stdout.
+ */
+ char szTempDir[RTPATH_MAX - sizeof("/stderr")];
+ int rc = RTPathTemp(szTempDir, sizeof(szTempDir));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathTemp failed: %Rrc", rc);
+ rc = RTPathAppend(szTempDir, sizeof(szTempDir), "VBoxExtPackHelper-XXXXXX");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAppend failed: %Rrc", rc);
+ rc = RTDirCreateTemp(szTempDir, 0700);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirCreateTemp failed: %Rrc", rc);
+
+ RTEXITCODE rcExit = RTEXITCODE_FAILURE;
+ char szStdOut[RTPATH_MAX];
+ char szStdErr[RTPATH_MAX];
+ rc = RTPathJoin(szStdOut, sizeof(szStdOut), szTempDir, "stdout");
+ if (RT_SUCCESS(rc))
+ rc = RTPathJoin(szStdErr, sizeof(szStdErr), szTempDir, "stderr");
+ if (RT_SUCCESS(rc))
+ {
+ RTFILE hStdOut;
+ rc = RTFileOpen(&hStdOut, szStdOut, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE
+ | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
+ if (RT_SUCCESS(rc))
+ {
+ RTFILE hStdErr;
+ rc = RTFileOpen(&hStdErr, szStdErr, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE
+ | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Insert the --elevated and stdout/err names into the argument
+ * list. Note that darwin skips the --stdout bit, so don't
+ * change the order here.
+ */
+ int const cSuArgs = 12;
+ int cArgs = argc + 5 + 1;
+ char const **papszArgs = (char const **)RTMemTmpAllocZ((cSuArgs + cArgs + 1) * sizeof(const char *));
+ if (papszArgs)
+ {
+ int iDst = cSuArgs;
+ papszArgs[iDst++] = argv[0];
+ papszArgs[iDst++] = "--stdout";
+ papszArgs[iDst++] = szStdOut;
+ papszArgs[iDst++] = "--stderr";
+ papszArgs[iDst++] = szStdErr;
+ papszArgs[iDst++] = "--elevated";
+ for (int iSrc = 1; iSrc <= argc; iSrc++)
+ papszArgs[iDst++] = argv[iSrc];
+
+ /*
+ * Do the platform specific process execution (waiting included).
+ */
+ rcExit = RelaunchElevatedNative(szExecPath, papszArgs, cSuArgs, cArgs, iCmd, pszDisplayInfoHack);
+
+ /*
+ * Copy the standard files to our standard handles.
+ */
+ CopyFileToStdXxx(hStdErr, g_pStdErr, true /*fComplain*/);
+ CopyFileToStdXxx(hStdOut, g_pStdOut, false);
+
+ RTMemTmpFree(papszArgs);
+ }
+
+ RTFileClose(hStdErr);
+ RTFileDelete(szStdErr);
+ }
+ RTFileClose(hStdOut);
+ RTFileDelete(szStdOut);
+ }
+ }
+ RTDirRemove(szTempDir);
+
+ return rcExit;
+}
+
+
+/**
+ * Checks if the process is elevated or not.
+ *
+ * @returns RTEXITCODE_SUCCESS if preconditions are fine,
+ * otherwise error message + RTEXITCODE_FAILURE.
+ * @param pfElevated Where to store the elevation indicator.
+ */
+static RTEXITCODE ElevationCheck(bool *pfElevated)
+{
+ *pfElevated = false;
+
+# if defined(RT_OS_WINDOWS)
+ /** @todo This should probably check if UAC is diabled and if we are
+ * Administrator first. Also needs to check for Vista+ first, probably.
+ */
+ DWORD cb;
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ HANDLE hToken;
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "OpenProcessToken failed: %u (%#x)", GetLastError(), GetLastError());
+
+ /*
+ * Check if we're member of the Administrators group. If we aren't, there
+ * is no way to elevate ourselves to system admin.
+ * N.B. CheckTokenMembership does not do the job here (due to attributes?).
+ */
+ BOOL fIsAdmin = FALSE;
+ SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
+ PSID pAdminGrpSid;
+ if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminGrpSid))
+ {
+# ifdef DEBUG
+ char *pszAdminGrpSid = NULL;
+ ConvertSidToStringSid(pAdminGrpSid, &pszAdminGrpSid);
+# endif
+
+ if ( !GetTokenInformation(hToken, TokenGroups, NULL, 0, &cb)
+ && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ {
+ PTOKEN_GROUPS pTokenGroups = (PTOKEN_GROUPS)RTMemAllocZ(cb);
+ if (GetTokenInformation(hToken, TokenGroups, pTokenGroups, cb, &cb))
+ {
+ for (DWORD iGrp = 0; iGrp < pTokenGroups->GroupCount; iGrp++)
+ {
+# ifdef DEBUG
+ char *pszGrpSid = NULL;
+ ConvertSidToStringSid(pTokenGroups->Groups[iGrp].Sid, &pszGrpSid);
+# endif
+ if (EqualSid(pAdminGrpSid, pTokenGroups->Groups[iGrp].Sid))
+ {
+ /* That it's listed is enough I think, ignore attributes. */
+ fIsAdmin = TRUE;
+ break;
+ }
+ }
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,cb) failed: %u (%#x)", GetLastError(), GetLastError());
+ RTMemFree(pTokenGroups);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,0) failed: %u (%#x)", GetLastError(), GetLastError());
+
+ FreeSid(pAdminGrpSid);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "AllocateAndInitializeSid failed: %u (%#x)", GetLastError(), GetLastError());
+ if (fIsAdmin)
+ {
+ /*
+ * Check the integrity level (Vista / UAC).
+ */
+# define MY_SECURITY_MANDATORY_HIGH_RID 0x00003000L
+# define MY_TokenIntegrityLevel ((TOKEN_INFORMATION_CLASS)25)
+ if ( !GetTokenInformation(hToken, MY_TokenIntegrityLevel, NULL, 0, &cb)
+ && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ {
+ PSID_AND_ATTRIBUTES pSidAndAttr = (PSID_AND_ATTRIBUTES)RTMemAlloc(cb);
+ if (GetTokenInformation(hToken, MY_TokenIntegrityLevel, pSidAndAttr, cb, &cb))
+ {
+ DWORD dwIntegrityLevel = *GetSidSubAuthority(pSidAndAttr->Sid, *GetSidSubAuthorityCount(pSidAndAttr->Sid) - 1U);
+
+ if (dwIntegrityLevel >= MY_SECURITY_MANDATORY_HIGH_RID)
+ *pfElevated = true;
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError());
+ RTMemFree(pSidAndAttr);
+ }
+ else if ( GetLastError() == ERROR_INVALID_PARAMETER
+ || GetLastError() == ERROR_NOT_SUPPORTED)
+ *pfElevated = true; /* Older Windows version. */
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError());
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Membership in the Administrators group is required to perform this action");
+
+ CloseHandle(hToken);
+ return rcExit;
+
+# else
+ /*
+ * On Unixy systems, we check if the executable and the current user is
+ * the same. This heuristic works fine for both hardened and development
+ * builds.
+ */
+ char szExecPath[RTPATH_MAX];
+ if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath)) == NULL)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed");
+
+ RTFSOBJINFO ObjInfo;
+ int rc = RTPathQueryInfoEx(szExecPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathQueryInfoEx failed");
+
+ *pfElevated = ObjInfo.Attr.u.Unix.uid == geteuid()
+ || ObjInfo.Attr.u.Unix.uid == getuid();
+ return RTEXITCODE_SUCCESS;
+# endif
+}
+
+#endif /* WITH_ELEVATION */
+
+int main(int argc, char **argv)
+{
+ /*
+ * Initialize IPRT and check that we're correctly installed.
+ */
+#ifdef RT_OS_WINDOWS
+ int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_UTF8_ARGV); /* WinMain gives us UTF-8, see below. */
+#else
+ int rc = RTR3InitExe(argc, &argv, 0);
+#endif
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ SUPR3HardenedVerifyInit();
+ RTERRINFOSTATIC ErrInfo;
+ RTErrInfoInitStatic(&ErrInfo);
+ rc = SUPR3HardenedVerifySelf(argv[0], true /*fInternal*/, &ErrInfo.Core);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", ErrInfo.Core.pszMsg);
+
+ /*
+ * Elevation check.
+ */
+ const char *pszDisplayInfoHack = NULL;
+ RTEXITCODE rcExit;
+#ifdef WITH_ELEVATION
+ bool fElevated;
+ rcExit = ElevationCheck(&fElevated);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+#endif
+
+ /*
+ * Parse the top level arguments until we find a command.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "install", CMD_INSTALL, RTGETOPT_REQ_NOTHING },
+ { "uninstall", CMD_UNINSTALL, RTGETOPT_REQ_NOTHING },
+ { "cleanup", CMD_CLEANUP, RTGETOPT_REQ_NOTHING },
+#ifdef WITH_ELEVATION
+ { "--elevated", OPT_ELEVATED, RTGETOPT_REQ_NOTHING },
+ { "--stdout", OPT_STDOUT, RTGETOPT_REQ_STRING },
+ { "--stderr", OPT_STDERR, RTGETOPT_REQ_STRING },
+#endif
+ { "--display-info-hack", OPT_DISP_INFO_HACK, RTGETOPT_REQ_STRING },
+ };
+ RTGETOPTSTATE GetState;
+ rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
+ for (;;)
+ {
+ RTGETOPTUNION ValueUnion;
+ int ch = RTGetOpt(&GetState, &ValueUnion);
+ switch (ch)
+ {
+ case 0:
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No command specified");
+
+ case CMD_INSTALL:
+ case CMD_UNINSTALL:
+ case CMD_CLEANUP:
+ {
+#ifdef WITH_ELEVATION
+ if (!fElevated)
+ return RelaunchElevated(argc, argv, ch, pszDisplayInfoHack);
+#endif
+ int cCmdargs = argc - GetState.iNext;
+ char **papszCmdArgs = argv + GetState.iNext;
+ switch (ch)
+ {
+ case CMD_INSTALL:
+ rcExit = DoInstall( cCmdargs, papszCmdArgs);
+ break;
+ case CMD_UNINSTALL:
+ rcExit = DoUninstall(cCmdargs, papszCmdArgs);
+ break;
+ case CMD_CLEANUP:
+ rcExit = DoCleanup( cCmdargs, papszCmdArgs);
+ break;
+ default:
+ AssertReleaseFailedReturn(RTEXITCODE_FAILURE);
+ }
+
+ /*
+ * Standard error should end with rcExit=RTEXITCODE_SUCCESS on
+ * success since the exit code may otherwise get lost in the
+ * process elevation fun.
+ */
+ RTStrmFlush(g_pStdOut);
+ RTStrmFlush(g_pStdErr);
+ switch (rcExit)
+ {
+ case RTEXITCODE_SUCCESS:
+ RTStrmPrintf(g_pStdErr, "rcExit=RTEXITCODE_SUCCESS\n");
+ break;
+ default:
+ RTStrmPrintf(g_pStdErr, "rcExit=%d\n", rcExit);
+ break;
+ }
+ RTStrmFlush(g_pStdErr);
+ RTStrmFlush(g_pStdOut);
+ return rcExit;
+ }
+
+#ifdef WITH_ELEVATION
+ case OPT_ELEVATED:
+ fElevated = true;
+ break;
+
+ case OPT_STDERR:
+ case OPT_STDOUT:
+ {
+# ifdef RT_OS_WINDOWS
+ PRTUTF16 pwszName = NULL;
+ rc = RTStrToUtf16(ValueUnion.psz, &pwszName);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error converting '%s' to UTF-16: %Rrc\n", ValueUnion.psz, rc);
+ FILE *pFile = _wfreopen(pwszName, L"r+", ch == OPT_STDOUT ? stdout : stderr);
+ RTUtf16Free(pwszName);
+# else
+ FILE *pFile = freopen(ValueUnion.psz, "r+", ch == OPT_STDOUT ? stdout : stderr);
+# endif
+ if (!pFile)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "freopen on '%s': %Rrc", ValueUnion.psz, rc);
+ }
+ break;
+ }
+#endif
+
+ case OPT_DISP_INFO_HACK:
+ if (pszDisplayInfoHack)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "--display-info-hack shall only occur once");
+ pszDisplayInfoHack = ValueUnion.psz;
+ break;
+
+ case 'h':
+ case 'V':
+ return DoStandardOption(ch);
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ /* not currently reached */
+ }
+ /* not reached */
+}
+
+
+#ifdef RT_OS_WINDOWS
+extern "C" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
+{
+ g_hInstance = hInstance;
+ NOREF(hPrevInstance); NOREF(nShowCmd); NOREF(lpCmdLine);
+
+ int rc = RTR3InitExeNoArguments(0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ LPWSTR pwszCmdLine = GetCommandLineW();
+ if (!pwszCmdLine)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "GetCommandLineW failed");
+
+ char *pszCmdLine;
+ rc = RTUtf16ToUtf8(pwszCmdLine, &pszCmdLine); /* leaked */
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to convert the command line: %Rrc", rc);
+
+ int cArgs;
+ char **papszArgs;
+ rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszCmdLine, RTGETOPTARGV_CNV_QUOTE_MS_CRT, NULL);
+ if (RT_SUCCESS(rc))
+ {
+
+ rc = main(cArgs, papszArgs);
+
+ RTGetOptArgvFree(papszArgs);
+ }
+ else
+ rc = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvFromString failed: %Rrc", rc);
+ RTStrFree(pszCmdLine);
+
+ return rc;
+}
+#endif
+
diff --git a/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.rc b/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.rc
new file mode 100644
index 00000000..896bb4ae
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.rc
@@ -0,0 +1,61 @@
+/* $Id: VBoxExtPackHelperApp.rc $ */
+/** @file
+ * VBoxExtPackHelperApp - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox ExtPack Helper\0"
+ VALUE "InternalName", "VBoxExtPackHelperApp\0"
+ VALUE "OriginalFilename", "VBoxExtPackHelperApp.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp b/src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp
new file mode 100644
index 00000000..489ae72e
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp
@@ -0,0 +1,107 @@
+/* $Id: VBoxVolInfo.cpp $ */
+/** @file
+ * Apps - VBoxVolInfo, Volume information tool.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <dirent.h>
+extern "C"
+{
+#define private privatekw
+#include <libdevmapper.h>
+}
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+/*********************************************************************************************************************************
+* Function Prototypes *
+*********************************************************************************************************************************/
+void print_dev_name(dev_t devid);
+
+/*
+ * Extracts logical volume dependencies via devmapper API and print them out.
+ */
+int main(int argc, char **argv)
+{
+ struct dm_task *dmtask;
+ struct dm_info dminfo;
+
+ if (argc != 2)
+ {
+ fprintf(stderr, "USAGE: %s <volume_name>\n", argv[0]);
+ return 1;
+ }
+
+ dmtask = dm_task_create(DM_DEVICE_DEPS);
+ if (!dmtask)
+ return 2;
+
+ if (dm_task_set_name(dmtask, argv[1]))
+ if (dm_task_run(dmtask))
+ if (dm_task_get_info(dmtask, &dminfo))
+ {
+ struct dm_deps *dmdeps = dm_task_get_deps(dmtask);
+ if (dmdeps)
+ {
+ unsigned i;
+ for (i = 0; i < dmdeps->count; ++i)
+ print_dev_name(dmdeps->device[i]);
+ }
+ }
+
+ dm_task_destroy(dmtask);
+ return 0;
+}
+
+/*
+ * Looks up device name by id using /dev directory. Prints it to stdout.
+ */
+void print_dev_name(dev_t devid)
+{
+ char path[PATH_MAX];
+ struct dirent *de;
+ DIR *dir = opendir("/dev");
+
+ while ((de = readdir(dir)) != NULL)
+ {
+ struct stat st;
+ snprintf(path, sizeof(path), "/dev/%s", de->d_name);
+ if (!stat(path, &st))
+ if (S_ISBLK(st.st_mode))
+ if (devid == st.st_rdev)
+ {
+ puts(de->d_name);
+ break;
+ }
+ }
+ closedir(dir);
+}
diff --git a/src/VBox/Main/src-helper-apps/os2/Makefile.kmk b/src/VBox/Main/src-helper-apps/os2/Makefile.kmk
new file mode 100644
index 00000000..40464f48
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/os2/Makefile.kmk
@@ -0,0 +1,66 @@
+# $Id: Makefile.kmk $
+## @file
+# Top-level makefile for src/VBox/Main/src-helper-apps/os2.
+#
+
+#
+# Copyright (C) 2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# OS/2 Unattended installation helper utility.
+# This is checked in as a binary, this is just the makefile for re-builting it.
+#
+ifdef VBOX_WITH_OPEN_WATCOM
+ PROGRAMS += os2_util
+endif
+os2_util_TEMPLATE = DUMMY
+os2_util_TOOL = OPENWATCOM-16
+os2_util_ASTOOL = OPENWATCOM-16
+os2_util_LDTOOL = OPENWATCOM-WL
+os2_util_BLD_TRG = os2
+os2_util_BLD_TRG_ARCH = x86
+os2_util_EXESUFF = .exe
+os2_util_INST = $(INST_UNATTENDED_TEMPLATES)
+os2_util_MODE = a+r,u+w
+os2_util_DEFS = IN_RING3
+os2_util_CFLAGS = -zl -s -ml -os
+os2_util_LDFLAGS = \
+ SYSTEM os2 \
+ OPTION START=_Os2UtilMain \
+ OPTION STACK=8192 \
+ OPTION HEAPSize=4096 \
+ OPTION NEWFile \
+ OPTION PROTmode \
+ SEGMENT IOPL IOPL EXECUTEREAD
+if 0
+os2_util_LDFLAGS += Debug Watcom All
+os2_util_CFLAGS += -d2 -hw
+endif
+
+os2_util_INCS = $(PATH_TOOL_OPENWATCOM)/h/os21x
+os2_util_SOURCES = os2_util.c os2_utilA.asm
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Main/src-helper-apps/os2/os2_util.c b/src/VBox/Main/src-helper-apps/os2/os2_util.c
new file mode 100644
index 00000000..a008d8b2
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/os2/os2_util.c
@@ -0,0 +1,1031 @@
+/* $Id: os2_util.c $ */
+/** @file
+ * Os2Util - Unattended Installation Helper Utility for OS/2.
+ *
+ * Helps TEE'ing the installation script output to VBox.log and guest side log
+ * files. Also helps with displaying program exit codes, something CMD.exe can't.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define INCL_BASE
+#include <os2.h>
+#include <iprt/asm-amd64-x86.h>
+#include <VBox/log.h>
+
+#include "VBox/version.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define IS_BLANK(ch) ((ch) == ' ' || (ch) == '\t' || (ch) == '\r' || (ch) == '\n')
+
+/** NIL HQUEUE value. */
+#define NIL_HQUEUE (~(HQUEUE)0)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to buffered output. */
+typedef struct MYBUFFER __far *PMYBUFFER;
+
+/** Buffered output. */
+typedef struct MYBUFFER
+{
+ PMYBUFFER pNext;
+ USHORT cb;
+ USHORT off;
+ CHAR sz[65536 - sizeof(USHORT) * 2 - sizeof(PMYBUFFER) - 2];
+} MYBUFFER;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+void __far VBoxBackdoorPrint(PSZ psz, unsigned cch);
+static PSZ MyGetOptValue(PSZ psz, PSZ pszOption, PSZ *ppszValue);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static HFILE g_hStdOut = 1;
+static HFILE g_hStdErr = 2;
+static BOOL g_fOutputToBackdoor = FALSE;
+static USHORT g_cBuffers = 0;
+static PMYBUFFER g_pBufferHead = NULL;
+static PMYBUFFER g_pBufferTail = NULL;
+
+
+
+/** strlen-like function. */
+static unsigned MyStrLen(PSZ psz)
+{
+ unsigned cch = 0;
+ while (psz[cch] != '\0')
+ cch++;
+ return cch;
+}
+
+
+/** strchr-like function. */
+static char __far *MyStrChr(const char __far *psz, char chNeedle)
+{
+ char ch;
+ while ((ch = *psz) != '\0')
+ {
+ if (ch == chNeedle)
+ return (char __far *)psz;
+ psz++;
+ }
+ return NULL;
+}
+
+
+/** memcpy-like function. */
+static void *MyMemCopy(void __far *pvDst, void const __far *pvSrc, USHORT cb)
+{
+ BYTE __far *pbDst = (BYTE __far *)pvDst;
+ BYTE const __far *pbSrc = (BYTE const __far *)pvSrc;
+ while (cb-- > 0)
+ *pbDst++ = *pbSrc++;
+ return pvDst;
+}
+
+
+static void MyOutStr(PSZ psz)
+{
+ unsigned const cch = MyStrLen(psz);
+ USHORT usIgnored;
+ DosWrite(g_hStdErr, psz, cch, &usIgnored);
+ if (g_fOutputToBackdoor)
+ VBoxBackdoorPrint(psz, cch);
+}
+
+
+static PSZ MyNumToString(PSZ pszBuf, unsigned uNum)
+{
+ /* Convert to decimal and inverted digit order: */
+ char szTmp[32];
+ unsigned off = 0;
+ do
+ {
+ szTmp[off++] = uNum % 10 + '0';
+ uNum /= 10;
+ } while (uNum);
+
+ /* Copy it out to the destination buffer in the right order and add a terminator: */
+ while (off-- > 0)
+ *pszBuf++ = szTmp[off];
+ *pszBuf = '\0';
+ return pszBuf;
+}
+
+
+static void MyOutNum(unsigned uNum)
+{
+ char szTmp[32];
+ MyNumToString(szTmp, uNum);
+ MyOutStr(szTmp);
+}
+
+
+static DECL_NO_RETURN(void) MyApiErrorAndQuit(PSZ pszOperation, USHORT rc)
+{
+ MyOutStr("Os2Util: error: ");
+ MyOutStr(pszOperation);
+ MyOutStr(" failed: ");
+ MyOutNum(rc);
+ MyOutStr("\r\n");
+ DosExit(EXIT_PROCESS, 1);
+}
+
+
+static DECL_NO_RETURN(void) MyApiError3AndQuit(PSZ pszOperation, PSZ psz2, PSZ psz3, USHORT rc)
+{
+ MyOutStr("Os2Util: error: ");
+ MyOutStr(pszOperation);
+ MyOutStr(psz2);
+ MyOutStr(psz3);
+ MyOutStr(" failed: ");
+ MyOutNum(rc);
+ MyOutStr("\r\n");
+ DosExit(EXIT_PROCESS, 1);
+}
+
+
+static DECL_NO_RETURN(void) MySyntaxErrorAndQuit(PSZ pszMsg)
+{
+ MyOutStr("Os2Util: syntax error: ");
+ MyOutStr(pszMsg);
+ MyOutStr("\r\n");
+ DosExit(EXIT_PROCESS, 1);
+}
+
+
+static HFILE OpenTeeFile(PSZ pszTeeToFile, BOOL fAppend, PSZ pszToWrite, USHORT cchToWrite)
+{
+ PMYBUFFER pBuf, pNext;
+ USHORT usIgnored;
+ USHORT usAction = 0;
+ HFILE hFile = -1;
+ USHORT rc;
+ rc = DosOpen(pszTeeToFile, &hFile, &usAction, 0 /*cbInitial*/, 0 /*fFileAttribs*/,
+ OPEN_ACTION_CREATE_IF_NEW | OPEN_ACTION_OPEN_IF_EXISTS,
+ OPEN_ACCESS_WRITEONLY | OPEN_SHARE_DENYNONE | OPEN_FLAGS_NOINHERIT | OPEN_FLAGS_SEQUENTIAL, 0 /*Reserved*/);
+ if (rc == NO_ERROR)
+ {
+
+ if (fAppend)
+ {
+ ULONG offNew = 0;
+ DosChgFilePtr(hFile, 0, FILE_END, &offNew);
+ }
+
+ /*
+ * Write out buffered data
+ */
+ /** @todo this does not seem to work. */
+ pBuf = g_pBufferHead;
+ while (pBuf)
+ {
+ do
+ rc = DosWrite(hFile, pBuf->sz, pBuf->off, &usIgnored);
+ while (rc == ERROR_INTERRUPT);
+ pNext = pBuf->pNext;
+ DosFreeSeg((__segment)pBuf);
+ pBuf = pNext;
+ }
+ g_pBufferTail = g_pBufferHead = NULL;
+
+ /*
+ * Write the current output.
+ */
+ do
+ rc = DosWrite(hFile, pszToWrite, cchToWrite, &usIgnored);
+ while (rc == ERROR_INTERRUPT);
+ }
+ else
+ {
+ /*
+ * Failed to open the file. Buffer a bit in case the file can be
+ * opened later (like when we've formatted the disk).
+ */
+ pBuf = g_pBufferTail;
+ if (pBuf && pBuf->off < pBuf->cb)
+ {
+ USHORT cbToCopy = pBuf->cb - pBuf->off;
+ if (cbToCopy > cchToWrite)
+ cbToCopy = cchToWrite;
+ MyMemCopy(&pBuf->sz[pBuf->off], pszToWrite, cbToCopy);
+ pszToWrite += cbToCopy;
+ cchToWrite -= cbToCopy;
+ }
+ if (cchToWrite > 0)
+ {
+ USHORT uSel = 0xffff;
+ if ( g_cBuffers < 10
+ && (rc = DosAllocSeg(0 /*64KiB*/, &uSel, 0 /*fFlags*/)) == NO_ERROR)
+ {
+ pBuf = ((__segment)uSel) :> ((MYBUFFER __near *)0);
+ pBuf->pNext = NULL;
+ pBuf->cb = sizeof(pBuf->sz);
+ pBuf->off = cchToWrite;
+ MyMemCopy(&pBuf->sz[0], pszToWrite, cchToWrite);
+
+ if (g_pBufferTail)
+ g_pBufferTail->pNext = pBuf;
+ else
+ g_pBufferHead = pBuf;
+ g_pBufferTail = pBuf;
+ }
+ else if (g_cBuffers > 0)
+ {
+ pBuf = g_pBufferHead;
+ pBuf->off = cchToWrite;
+ MyMemCopy(&pBuf->sz[0], pszToWrite, cchToWrite);
+
+ if (g_pBufferTail != pBuf)
+ {
+ g_pBufferHead = pBuf->pNext;
+ pBuf->pNext = NULL;
+ g_pBufferTail->pNext = pBuf;
+ g_pBufferTail = pBuf;
+ }
+ }
+ }
+ hFile = -1;
+ }
+ return hFile;
+}
+
+
+/**
+ * Waits for the child progress to complete, returning it's status code.
+ */
+static void DoWait(PID pidChild, USHORT idSession, HQUEUE hQueue, PRESULTCODES pResultCodes)
+{
+ /*
+ * Can we use DosCwait?
+ */
+ if (hQueue == NIL_HQUEUE)
+ {
+ for (;;)
+ {
+ PID pidIgnored;
+ USHORT rc = DosCwait(DCWA_PROCESS, DCWW_WAIT, pResultCodes, &pidIgnored, pidChild);
+ if (rc == NO_ERROR)
+ break;
+ if (rc != ERROR_INTERRUPT)
+ {
+ MyOutStr("Os2Util: error: DosCwait(DCWA_PROCESS,DCWW_WAIT,,,");
+ MyOutNum(pidChild);
+ MyOutStr(") failed: ");
+ MyOutNum(rc);
+ MyOutStr("\r\n");
+ }
+ }
+ }
+ else
+ {
+ /*
+ * No we have to use the queue interface to the session manager.
+ */
+ for (;;)
+ {
+ ULONG ulAdderPidAndEvent = 0;
+ PUSHORT pausData = NULL;
+ USHORT cbData = 0;
+ BYTE bPriority = 0;
+ HSEM hSem = NULL;
+ USHORT rc = DosReadQueue(hQueue, &ulAdderPidAndEvent, &cbData, (PULONG)&pausData,
+ 0 /*uElementCode*/, 0 /* fNoWait */, &bPriority, &hSem);
+ if (rc == NO_ERROR)
+ {
+ if (cbData >= sizeof(USHORT) * 2)
+ {
+ USHORT idTermSession = pausData[0];
+ USHORT uExitCode = pausData[1];
+ if (idTermSession == idSession)
+ {
+ pResultCodes->codeTerminate = 0;
+ pResultCodes->codeResult = uExitCode;
+ break;
+ }
+ if (1)
+ {
+ MyOutStr("OutUtil: info: idTermSession=");
+ MyOutNum(idTermSession);
+ MyOutStr(" uExitCode=");
+ MyOutNum(uExitCode);
+ MyOutStr("\r\n");
+ }
+ }
+ else
+ {
+ MyOutStr("OutUtil: warning: bogus queue element size: cbData=");
+ MyOutNum(cbData);
+ MyOutStr("\r\n");
+ }
+ DosFreeSeg((__segment)pausData);
+ }
+ else if (rc != ERROR_INTERRUPT)
+ {
+ DosCloseQueue(hQueue);
+ MyApiErrorAndQuit("DosReadQueue", rc);
+ }
+ }
+ }
+}
+
+
+/**
+ * Handles --file-to-backdoor / -c.
+ */
+static void CopyFileToBackdoorAndQuit(PSZ psz, BOOL fLongOpt, PSZ pszBuf, USHORT cbBuf)
+{
+ HFILE hFile = 0;
+ USHORT usAction = 0;
+ USHORT rc;
+
+ /*
+ * Get the filename and check that it is the last thing on the commandline.
+ */
+ PSZ pszFilename = NULL;
+ CHAR ch;
+ psz = MyGetOptValue(psz, fLongOpt ? "--file-to-backdoor" : "-c", &pszFilename);
+ while ((ch = *psz) != '\0' && IS_BLANK(ch))
+ psz++;
+ if (ch != '\0')
+ MySyntaxErrorAndQuit("No options allowed after -c/--file-to-backdoor");
+
+ /*
+ * Open the file
+ */
+ rc = DosOpen(pszFilename, &hFile, &usAction, 0 /*cbInitial*/, 0 /*fFileAttribs*/,
+ OPEN_ACTION_FAIL_IF_NEW | OPEN_ACTION_OPEN_IF_EXISTS,
+ OPEN_ACCESS_READONLY | OPEN_SHARE_DENYNONE | OPEN_FLAGS_NOINHERIT | OPEN_FLAGS_SEQUENTIAL, 0 /*Reserved*/);
+ if (rc != NO_ERROR)
+ MyApiError3AndQuit("Failed to open \"", pszFilename, "\" for reading", rc);
+
+ VBoxBackdoorPrint(RT_STR_TUPLE("--- BEGIN OF \""));
+ VBoxBackdoorPrint(pszFilename, MyStrLen(pszFilename));
+ VBoxBackdoorPrint(RT_STR_TUPLE("\" ---\n"));
+
+ for (;;)
+ {
+ USHORT cbRead = 0;
+ rc = DosRead(hFile, pszBuf, cbBuf, &cbRead);
+ if (rc == NO_ERROR)
+ {
+ if (cbRead == 0)
+ break;
+ VBoxBackdoorPrint(pszBuf, cbRead);
+ }
+ else if (rc != ERROR_INTERRUPT)
+ MyApiError3AndQuit("Reading \"", pszFilename, "\"", rc);
+ }
+
+ VBoxBackdoorPrint(RT_STR_TUPLE("--- END OF \""));
+ VBoxBackdoorPrint(pszFilename, MyStrLen(pszFilename));
+ VBoxBackdoorPrint(RT_STR_TUPLE("\" ---\n"));
+
+ DosClose(hFile);
+ DosExit(EXIT_PROCESS, 1);
+}
+
+
+/** Displays version string and quits. */
+static DECL_NO_RETURN(void) ShowVersionAndQuit(void)
+{
+ CHAR szVer[] = "$Rev: 153224 $\r\n";
+ USHORT usIgnored;
+ DosWrite(g_hStdOut, szVer, sizeof(szVer) - 1, &usIgnored);
+ DosExit(EXIT_PROCESS, 0);
+}
+
+
+/** Displays usage info and quits. */
+static DECL_NO_RETURN(void) ShowUsageAndQuit(void)
+{
+ static char s_szHelp[] =
+ VBOX_PRODUCT " OS/2 Unattended Helper Version " VBOX_VERSION_STRING "\r\n"
+ "Copyright (C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\r\n"
+ "\r\n"
+ "Os2Util.exe is tiny helper utility that implements TEE'ing to the VBox release\r\n"
+ "log, files and shows the actual exit code of a program. Standard error and\r\n"
+ "output will be merged into one for simplicity reasons.\r\n"
+ "\r\n"
+ "Usage: Os2Util.exe [-a|--append] [-f<filename>|--tee-to-file <filename>] \\\r\n"
+ " [-b|--tee-to-backdoor] [-z<exit>|--as-zero <exit> [..]] \\\r\n"
+ " -- <prog> [args]\r\n"
+ " or Os2Util.exe <-w<msg>|--write-backdoor <msg>>\r\n"
+ " or Os2Util.exe <-c<file>|--file-to-backdoor <file>>\r\n"
+ "\r\n"
+ "Note! Does not supported any kind of quoting before the child arguments.\r\n"
+ ;
+ USHORT usIgnored;
+ DosWrite(g_hStdOut, s_szHelp, sizeof(s_szHelp) - 1, &usIgnored);
+ DosExit(EXIT_PROCESS, 0);
+}
+
+
+/**
+ * Gets the an option value.
+ *
+ * The option value string will be terminated.
+ */
+static PSZ MyGetOptValue(PSZ psz, PSZ pszOption, PSZ *ppszValue)
+{
+ CHAR ch;
+ while ((ch = *psz) != '\0' && IS_BLANK(ch))
+ psz++;
+ if (*psz == '\0')
+ {
+ MyOutStr("Os2Util: syntax error: Option '");
+ MyOutStr(pszOption);
+ MyOutStr("' takes a value\r\n");
+ DosExit(EXIT_PROCESS, 2);
+ }
+
+ *ppszValue = psz;
+
+ while ((ch = *psz) != '\0' && !IS_BLANK(ch))
+ psz++;
+ if (ch != '\0')
+ *psz++ = '\0';
+ return psz;
+}
+
+
+/**
+ * Gets the an numeric option value.
+ */
+static PSZ MyGetOptNum(PSZ psz, PSZ pszOption, PUSHORT puValue)
+{
+ PSZ pszError = NULL;
+ PSZ pszValue = NULL;
+ PSZ const pszRet = MyGetOptValue(psz, pszOption, &pszValue);
+ PSZ const pszValueStart = pszValue;
+ USHORT uValue = 0;
+ CHAR ch;
+ if (pszValue[0] == '0' && ((ch = pszValue[1]) == 'x' || ch == 'X'))
+ {
+ pszValue += 2;
+ while ((ch = *pszValue++) != '\0')
+ {
+ BYTE bDigit;
+ if (ch <= '9' && ch >= '0')
+ bDigit = ch - '0';
+ else if (ch <= 'f' && ch >= 'a')
+ bDigit = ch - 'a' + 10;
+ else if (ch <= 'F' && ch >= 'A')
+ bDigit = ch - 'A' + 10;
+ else
+ {
+ pszError = "': invalid hex value\r\n";
+ break;
+ }
+ if (uValue >> 12)
+ {
+ pszError = "': hex value out of range\r\n";
+ break;
+ }
+ uValue <<= 4;
+ uValue |= bDigit;
+ }
+ }
+ else
+ {
+ while ((ch = *pszValue++) != '\0')
+ {
+ BYTE bDigit;
+ if (ch <= '9' && ch >= '0')
+ bDigit = ch - '0';
+ else
+ {
+ pszError = "': invalid decimal value\r\n";
+ break;
+ }
+ if (uValue * 10 / 10 != uValue)
+ {
+ pszError = "': decimal value out of range\r\n";
+ break;
+ }
+ uValue *= 10;
+ uValue += bDigit;
+ }
+ }
+
+ if (pszError)
+ {
+ MyOutStr("Os2Util: syntax error: Option '");
+ MyOutStr(pszOption);
+ MyOutStr("' with value '");
+ MyOutStr(pszValueStart);
+ MyOutStr(pszError);
+ DosExit(EXIT_PROCESS, 2);
+ }
+
+ *puValue = uValue;
+ return pszRet;
+}
+
+
+/**
+ * Checks if @a pszOption matches @a *ppsz, advance *ppsz if TRUE.
+ */
+static BOOL MyMatchLongOption(PSZ _far *ppsz, PSZ pszOption, unsigned cchOption)
+{
+ /* Match option and command line strings: */
+ PSZ psz = *ppsz;
+ while (cchOption-- > 0)
+ {
+ if (*psz != *pszOption)
+ return FALSE;
+ psz++;
+ pszOption++;
+ }
+
+ /* Is this the end of a word on the command line? */
+ if (*psz == '\0')
+ *ppsz = psz;
+ else if (IS_BLANK(*psz))
+ *ppsz = psz + 1;
+ else
+ return FALSE;
+ return TRUE;
+}
+
+
+/**
+ * The entrypoint (no crt).
+ */
+#pragma aux Os2UtilMain "_*" parm caller [ ax ] [ bx ];
+void Os2UtilMain(USHORT uSelEnv, USHORT offCmdLine)
+{
+ PSZ pszzEnv = ((__segment)uSelEnv) :> ((char _near *)0);
+ PSZ pszzCmdLine = ((__segment)uSelEnv) :> ((char _near *)offCmdLine);
+ USHORT uExitCode = 1;
+ BOOL fTeeToBackdoor = FALSE;
+ BOOL fAppend = FALSE;
+ PSZ pszTeeToFile = NULL;
+ HFILE hTeeToFile = -1;
+ HFILE hPipeRead = -1;
+ PSZ pszzNewCmdLine;
+ PSZ psz;
+ CHAR ch;
+ USHORT usIgnored;
+ USHORT rc;
+ RESULTCODES ResultCodes = { 0xffff, 0xffff };
+ CHAR szBuf[512];
+ CHAR szExeFull[CCHMAXPATH];
+ PSZ pszExe;
+ USHORT uExeType;
+ USHORT idSession = 0;
+ PID pidChild = 0;
+ HQUEUE hQueue = NIL_HQUEUE;
+ CHAR szQueueName[64];
+ unsigned cAsZero = 0;
+ USHORT auAsZero[16];
+
+ /*
+ * Parse the command line.
+ * Note! We do not accept any kind of quoting.
+ */
+ /* Skip the executable filename: */
+ psz = pszzCmdLine;
+ while (*psz != '\0')
+ psz++;
+ psz++;
+
+ /* Now parse arguments. */
+ while ((ch = *psz) != '\0')
+ {
+ if (IS_BLANK(ch))
+ psz++;
+ else if (ch != '-')
+ break;
+ else
+ {
+ PSZ const pszOptStart = psz;
+ ch = *++psz;
+ if (ch == '-')
+ {
+ ch = *++psz;
+ if (IS_BLANK(ch))
+ {
+ /* Found end-of-arguments marker "--" */
+ psz++;
+ break;
+ }
+ if (ch == 'a' && MyMatchLongOption(&psz, RT_STR_TUPLE("append")))
+ fAppend = TRUE;
+ else if (ch == 'a' && MyMatchLongOption(&psz, RT_STR_TUPLE("as-zero")))
+ {
+ if (cAsZero > RT_ELEMENTS(auAsZero))
+ MySyntaxErrorAndQuit("Too many --as-zero/-z options");
+ psz = MyGetOptNum(psz, "--as-zero", &auAsZero[cAsZero]);
+ cAsZero++;
+ }
+ else if (ch == 'f' && MyMatchLongOption(&psz, RT_STR_TUPLE("file-to-backdoor")))
+ CopyFileToBackdoorAndQuit(psz, TRUE /*fLongOpt*/, szBuf, sizeof(szBuf));
+ else if (ch == 'h' && MyMatchLongOption(&psz, RT_STR_TUPLE("help")))
+ ShowUsageAndQuit();
+ else if (ch == 't' && MyMatchLongOption(&psz, RT_STR_TUPLE("tee-to-backdoor")))
+ g_fOutputToBackdoor = fTeeToBackdoor = TRUE;
+ else if (ch == 't' && MyMatchLongOption(&psz, RT_STR_TUPLE("tee-to-file")))
+ psz = MyGetOptValue(psz, "--tee-to-file", &pszTeeToFile);
+ else if (ch == 'v' && MyMatchLongOption(&psz, RT_STR_TUPLE("version")))
+ ShowVersionAndQuit();
+ else if (ch == 'w' && MyMatchLongOption(&psz, RT_STR_TUPLE("write-backdoor")))
+ {
+ VBoxBackdoorPrint(psz, MyStrLen(psz));
+ VBoxBackdoorPrint("\n", 1);
+ DosExit(EXIT_PROCESS, 0);
+ }
+ else
+ {
+ MyOutStr("Os2util: syntax error: ");
+ MyOutStr(pszOptStart);
+ MyOutStr("\r\n");
+ DosExit(EXIT_PROCESS, 2);
+ }
+ }
+ else
+ {
+ do
+ {
+ if (ch == 'a')
+ fAppend = TRUE;
+ else if (ch == 'b')
+ g_fOutputToBackdoor = fTeeToBackdoor = TRUE;
+ else if (ch == 'c')
+ CopyFileToBackdoorAndQuit(psz + 1, FALSE /*fLongOpt*/, szBuf, sizeof(szBuf));
+ else if (ch == 'f')
+ {
+ psz = MyGetOptValue(psz + 1, "-f", &pszTeeToFile);
+ break;
+ }
+ else if (ch == 'w')
+ {
+ psz++;
+ VBoxBackdoorPrint(psz, MyStrLen(psz));
+ VBoxBackdoorPrint("\n", 1);
+ DosExit(EXIT_PROCESS, 0);
+ }
+ else if (ch == 'z')
+ {
+ if (cAsZero > RT_ELEMENTS(auAsZero))
+ MySyntaxErrorAndQuit("Too many --as-zero/-z options");
+ psz = MyGetOptNum(psz + 1, "-z", &auAsZero[cAsZero]);
+ cAsZero++;
+ }
+ else if (ch == '?' || ch == 'h' || ch == 'H')
+ ShowUsageAndQuit();
+ else if (ch == 'V')
+ ShowVersionAndQuit();
+ else
+ {
+ MyOutStr("Os2util: syntax error: ");
+ if (ch)
+ DosWrite(g_hStdErr, &ch, 1, &usIgnored);
+ else
+ MyOutStr("lone dash");
+ MyOutStr(" (");
+ MyOutStr(pszOptStart);
+ MyOutStr(")\r\n");
+ DosExit(EXIT_PROCESS, 2);
+ }
+ ch = *++psz;
+ } while (!IS_BLANK(ch) && ch != '\0');
+ }
+ }
+ }
+
+ /*
+ * Zero terminate the executable name in the command line.
+ */
+ pszzNewCmdLine = psz;
+ if (ch == '\0')
+ {
+ MyOutStr("Os2Util: syntax error: No program specified\r\n");
+ DosExit(EXIT_PROCESS, 2);
+ }
+ psz++;
+ while ((ch = *psz) != '\0' && !IS_BLANK(ch))
+ psz++;
+ *psz++ = '\0';
+
+ /*
+ * Find the executable and check its type.
+ */
+ if ( pszzNewCmdLine[1] == ':'
+ || MyStrChr(pszzNewCmdLine, '\\')
+ || MyStrChr(pszzNewCmdLine, '/'))
+ pszExe = pszzNewCmdLine;
+ else
+ {
+ rc = DosSearchPath(SEARCH_CUR_DIRECTORY | SEARCH_ENVIRONMENT | SEARCH_IGNORENETERRS, "PATH",
+ pszzNewCmdLine, szExeFull, sizeof(szExeFull));
+ if (rc != NO_ERROR)
+ MyApiError3AndQuit("DosSearchPath(7, \"PATH\", \"", pszzNewCmdLine, "\",,)", rc);
+ pszExe = &szExeFull[0];
+ }
+
+ /* Perhapse we should use WinQueryProgramType here instead? */
+ rc = DosQAppType(pszExe, &uExeType);
+ if (rc != NO_ERROR)
+ MyApiErrorAndQuit("DosQAppType(pszExe, &uExeType)", rc);
+#ifdef DEBUG
+ MyOutStr("Os2Util: debug: uExeType="); MyOutNum(uExeType); MyOutStr("\r\n");
+#endif
+ /** @todo deal with launching winos2 programs too... */
+
+ /*
+ * Prepare redirection.
+ */
+ if (fTeeToBackdoor || pszTeeToFile != NULL)
+ {
+ HFILE hPipeWrite = -1;
+ HFILE hDup;
+
+ /* Make new copies of the standard handles. */
+ hDup = 0xffff;
+ rc = DosDupHandle(g_hStdErr, &hDup);
+ if (rc != NO_ERROR)
+ MyApiErrorAndQuit("DosDupHandle(g_hStdErr, &hDup)", rc);
+ g_hStdErr = hDup;
+ DosSetFHandState(hDup, OPEN_FLAGS_NOINHERIT); /* not strictly necessary, so ignore errors */
+
+ hDup = 0xffff;
+ rc = DosDupHandle(g_hStdOut, &hDup);
+ if (rc != NO_ERROR)
+ MyApiErrorAndQuit("DosDupHandle(g_hStdOut, &hDup)", rc);
+ g_hStdOut = hDup;
+ DosSetFHandState(hDup, OPEN_FLAGS_NOINHERIT); /* not strictly necessary, so ignore errors */
+
+ /* Create the pipe and make the read-end non-inheritable (we'll hang otherwise). */
+ rc = DosMakePipe(&hPipeRead, &hPipeWrite, 0 /*default size*/);
+ if (rc != NO_ERROR)
+ MyApiErrorAndQuit("DosMakePipe", rc);
+
+ rc = DosSetFHandState(hPipeRead, OPEN_FLAGS_NOINHERIT);
+ if (rc != NO_ERROR)
+ MyApiErrorAndQuit("DosSetFHandState(hPipeRead, OPEN_FLAGS_NOINHERIT)", rc);
+
+ /* Replace standard output and standard error with the write end of the pipe. */
+ hDup = 1;
+ rc = DosDupHandle(hPipeWrite, &hDup);
+ if (rc != NO_ERROR)
+ MyApiErrorAndQuit("DosDupHandle(hPipeWrite, &hDup[=1])", rc);
+
+ hDup = 2;
+ rc = DosDupHandle(hPipeWrite, &hDup);
+ if (rc != NO_ERROR)
+ MyApiErrorAndQuit("DosDupHandle(hPipeWrite, &hDup[=2])", rc);
+
+ /* We can close the write end of the pipe as we don't need the original handle any more. */
+ DosClose(hPipeWrite);
+ }
+
+ /*
+ * Execute the program.
+ */
+ szBuf[0] = '\0';
+#define FAPPTYP_TYPE_MASK 7
+ if ((uExeType & FAPPTYP_TYPE_MASK) == PT_WINDOWABLEVIO) /** @todo what if we're in fullscreen ourselves? */
+ {
+ /* For same type programs we can use DosExecPgm: */
+ rc = DosExecPgm(szBuf, sizeof(szBuf), hPipeRead == -1 ? EXEC_SYNC : EXEC_ASYNCRESULT,
+ pszzNewCmdLine, pszzEnv, &ResultCodes, pszExe);
+ if (rc != NO_ERROR)
+ {
+ MyOutStr("Os2Util: error: DosExecPgm failed for \"");
+ MyOutStr(pszzNewCmdLine);
+ MyOutStr("\": ");
+ MyOutNum(rc);
+ if (szBuf[0])
+ {
+ MyOutStr(" ErrObj=");
+ szBuf[sizeof(szBuf) - 1] = '\0';
+ MyOutStr(szBuf);
+ }
+ MyOutStr("\r\n");
+ DosExit(EXIT_PROCESS, 1);
+ }
+ if (hPipeRead != -1)
+ {
+ pidChild = ResultCodes.codeTerminate;
+ MyOutStr("info: started pid ");
+ MyOutNum(pidChild);
+ MyOutStr("\r\n");
+ }
+ }
+ else
+ {
+ /* For different typed programs we have to use DosStartSession, which
+ is a lot more tedious to use. */
+ static const char s_szQueueBase[] = "\\QUEUES\\OS2_UTIL-";
+ union
+ {
+ STARTDATA StartData;
+ BYTE abPadding[sizeof(STARTDATA) + 64];
+ struct
+ {
+ STARTDATA Core;
+ ULONG ulReserved;
+ PSZ pszBuf;
+ USHORT cbBuf;
+ } s;
+ } u;
+ PIDINFO PidInfo = {0, 0, 0};
+
+ /* Create the wait queue first. */
+ DosGetPID(&PidInfo);
+ MyMemCopy(szQueueName, s_szQueueBase, sizeof(s_szQueueBase));
+ MyNumToString(&szQueueName[sizeof(s_szQueueBase) - 1], PidInfo.pid);
+
+ rc = DosCreateQueue(&hQueue, 0 /*FIFO*/, szQueueName);
+ if (rc != NO_ERROR)
+ MyApiError3AndQuit("DosCreateQueue(&hQueue, 0, \"", szQueueName, "\")", rc);
+
+ u.StartData.Length = sizeof(u.StartData);
+ u.StartData.Related = 1 /* SSF_RELATED_CHILD */;
+ u.StartData.FgBg = (uExeType & FAPPTYP_TYPE_MASK) == PT_PM
+ ? 1 /* SSF_FGBG_BACK - try avoid ERROR_SMG_START_IN_BACKGROUND */
+ : 0 /* SSF_FGBG_FORE */;
+ u.StartData.TraceOpt = 0 /* SSF_TRACEOPT_NONE */;
+ u.StartData.PgmTitle = NULL;
+ u.StartData.PgmName = pszExe;
+ u.StartData.PgmInputs = psz; /* just arguments, not exec apparently.*/
+ u.StartData.TermQ = szQueueName;
+ u.StartData.Environment = NULL; /* Inherit our env. Note! Using pszzEnv causes it to be freed
+ and we'll crash reporting the error. */
+ u.StartData.InheritOpt = 1 /* SSF_INHERTOPT_PARENT */;
+ u.StartData.SessionType = uExeType & FAPPTYP_TYPE_MASK;
+ if (uExeType & 0x20 /*FAPPTYP_DOS*/)
+ u.StartData.SessionType = 4 /* SSF_TYPE_VDM */;
+ u.StartData.IconFile = NULL;
+ u.StartData.PgmHandle = 0;
+ u.StartData.PgmControl = 0 /* SSF_CONTROL_VISIBLE */;
+ u.StartData.InitXPos = 0;
+ u.StartData.InitYPos = 0;
+ u.StartData.InitXSize = 0;
+ u.StartData.InitYSize = 0;
+ u.s.ulReserved = 0;
+ u.s.pszBuf = NULL;
+ u.s.cbBuf = 0;
+
+ rc = DosStartSession(&u.StartData, &idSession, &pidChild);
+ if (rc != NO_ERROR && rc != ERROR_SMG_START_IN_BACKGROUND)
+ {
+ DosCloseQueue(hQueue);
+ MyApiError3AndQuit("DosStartSession for \"", pszExe, "\"", rc);
+ }
+
+ if (1)
+ {
+ MyOutStr("info: started session ");
+ MyOutNum(idSession);
+ MyOutStr(", pid ");
+ MyOutNum(pidChild);
+ MyOutStr("\r\n");
+ }
+ }
+
+ /*
+ * Wait for the child process to complete.
+ */
+ if (hPipeRead != -1)
+ {
+
+ /* Close the write handles or we'll hang in the read loop. */
+ DosClose(1);
+ DosClose(2);
+
+ /* Disable hard error popups (file output to unformatted disks). */
+ DosError(2 /* only exceptions */);
+
+ /*
+ * Read the pipe and tee it to the desired outputs
+ */
+ for (;;)
+ {
+ USHORT cbRead = 0;
+ rc = DosRead(hPipeRead, szBuf, sizeof(szBuf), &cbRead);
+ if (rc == NO_ERROR)
+ {
+ if (cbRead == 0)
+ break; /* No more writers. */
+
+ /* Standard output: */
+ do
+ rc = DosWrite(g_hStdOut, szBuf, cbRead, &usIgnored);
+ while (rc == ERROR_INTERRUPT);
+
+ /* Backdoor: */
+ if (fTeeToBackdoor)
+ VBoxBackdoorPrint(szBuf, cbRead);
+
+ /* File: */
+ if (hTeeToFile != -1)
+ do
+ rc = DosWrite(hTeeToFile, szBuf, cbRead, &usIgnored);
+ while (rc == ERROR_INTERRUPT);
+ else if (pszTeeToFile != NULL)
+ hTeeToFile = OpenTeeFile(pszTeeToFile, fAppend, szBuf, cbRead);
+ }
+ else if (rc == ERROR_BROKEN_PIPE)
+ break;
+ else
+ {
+ MyOutStr("Os2Util: error: Error reading pipe: ");
+ MyOutNum(rc);
+ MyOutStr("\r\n");
+ break;
+ }
+ }
+
+ DosClose(hPipeRead);
+
+ /*
+ * Wait for the process to complete.
+ */
+ DoWait(pidChild, idSession, hQueue, &ResultCodes);
+ }
+ /*
+ * Must wait for the session completion too.
+ */
+ else if (idSession != 0)
+ DoWait(pidChild, idSession, hQueue, &ResultCodes);
+
+ /*
+ * Report the status code and quit.
+ */
+ MyOutStr("Os2Util: Child: ");
+ MyOutStr(pszzNewCmdLine);
+ MyOutStr(" ");
+ MyOutStr(psz);
+ MyOutStr("\r\n"
+ "Os2Util: codeTerminate=");
+ MyOutNum(ResultCodes.codeTerminate);
+ MyOutStr(" codeResult=");
+ MyOutNum(ResultCodes.codeResult);
+ MyOutStr("\r\n");
+
+ /* Treat it as zero? */
+ if (ResultCodes.codeTerminate == 0)
+ {
+ unsigned i = cAsZero;
+ while (i-- > 0)
+ if (auAsZero[i] == ResultCodes.codeResult)
+ {
+ MyOutStr("Os2Util: info: treating status as zero\r\n");
+ ResultCodes.codeResult = 0;
+ break;
+ }
+ }
+
+ if (idSession != 0)
+ DosCloseQueue(hQueue);
+ for (;;)
+ DosExit(EXIT_PROCESS, ResultCodes.codeTerminate == 0 ? ResultCodes.codeResult : 127);
+}
+
+
+/**
+ * Backdoor print function living in an IOPL=2 segment.
+ */
+#pragma code_seg("IOPL", "CODE")
+void __far VBoxBackdoorPrint(PSZ psz, unsigned cch)
+{
+ ASMOutStrU8(RTLOG_DEBUG_PORT, psz, cch);
+}
+
diff --git a/src/VBox/Main/src-helper-apps/os2/os2_utilA.asm b/src/VBox/Main/src-helper-apps/os2/os2_utilA.asm
new file mode 100644
index 00000000..1d7d6d59
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/os2/os2_utilA.asm
@@ -0,0 +1,54 @@
+; $Id: os2_utilA.asm $
+;; @file
+; Os2UtilA - Watcom assembly file that defines the stack.
+;
+
+;
+; Copyright (C) 2022 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; SPDX-License-Identifier: GPL-3.0-only
+;
+
+DGROUP group _NULL,CONST,CONST2,STRINGS,_DATA,_BSS,STACK
+
+_NULL segment para public 'BEGDATA'
+ dw 10 dup(0)
+_NULL ends
+
+CONST segment word public 'DATA'
+CONST ends
+
+CONST2 segment word public 'DATA'
+CONST2 ends
+
+STRINGS segment word public 'DATA'
+STRINGS ends
+
+_DATA segment word public 'DATA'
+_DATA ends
+
+_BSS segment word public 'BSS'
+_BSS ends
+
+STACK segment para stack 'STACK'
+ db 1000h dup(?)
+STACK ends
+
+ end
+
diff --git a/src/VBox/Main/src-server/ApplianceImpl.cpp b/src/VBox/Main/src-server/ApplianceImpl.cpp
new file mode 100644
index 00000000..333ffc61
--- /dev/null
+++ b/src/VBox/Main/src-server/ApplianceImpl.cpp
@@ -0,0 +1,1943 @@
+/* $Id: ApplianceImpl.cpp $ */
+/** @file
+ * IAppliance and IVirtualSystem COM class implementations.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_APPLIANCE
+#include <iprt/path.h>
+#include <iprt/cpp/path.h>
+#include <iprt/cpp/utils.h>
+#include <VBox/com/array.h>
+#include <map>
+
+#include "ApplianceImpl.h"
+#include "VFSExplorerImpl.h"
+#include "VirtualBoxImpl.h"
+#include "GuestOSTypeImpl.h"
+#include "Global.h"
+#include "ProgressImpl.h"
+#include "MachineImpl.h"
+#include "SystemPropertiesImpl.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "CertificateImpl.h"
+
+#include "ApplianceImplPrivate.h"
+
+using namespace std;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const char * const g_pszISOURI = "http://www.ecma-international.org/publications/standards/Ecma-119.htm";
+static const char * const g_pszVMDKStreamURI = "http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized";
+static const char * const g_pszVMDKSparseURI = "http://www.vmware.com/specifications/vmdk.html#sparse";
+static const char * const g_pszVMDKCompressedURI = "http://www.vmware.com/specifications/vmdk.html#compressed";
+static const char * const g_pszVMDKCompressedURI2 = "http://www.vmware.com/interfaces/specifications/vmdk.html#compressed";
+static const char * const g_pszrVHDURI = "http://go.microsoft.com/fwlink/?LinkId=137171";
+static char g_szIsoBackend[128];
+static char g_szVmdkBackend[128];
+static char g_szVhdBackend[128];
+/** Set after the g_szXxxxBackend variables has been initialized. */
+static bool volatile g_fInitializedBackendNames = false;
+
+static struct
+{
+ const char *pszUri, *pszBackend;
+} const g_aUriToBackend[] =
+{
+ { g_pszISOURI, g_szIsoBackend },
+ { g_pszVMDKStreamURI, g_szVmdkBackend },
+ { g_pszVMDKSparseURI, g_szVmdkBackend },
+ { g_pszVMDKCompressedURI, g_szVmdkBackend },
+ { g_pszVMDKCompressedURI2, g_szVmdkBackend },
+ { g_pszrVHDURI, g_szVhdBackend },
+};
+
+static std::map<Utf8Str, Utf8Str> supportedStandardsURI;
+
+static struct
+{
+ ovf::CIMOSType_T cim;
+ VBOXOSTYPE osType;
+} const g_aOsTypes[] =
+{
+ { ovf::CIMOSType_CIMOS_Unknown, VBOXOSTYPE_Unknown },
+ { ovf::CIMOSType_CIMOS_OS2, VBOXOSTYPE_OS2 },
+ { ovf::CIMOSType_CIMOS_OS2, VBOXOSTYPE_OS2Warp3 },
+ { ovf::CIMOSType_CIMOS_OS2, VBOXOSTYPE_OS2Warp4 },
+ { ovf::CIMOSType_CIMOS_OS2, VBOXOSTYPE_OS2Warp45 },
+ { ovf::CIMOSType_CIMOS_OS2, VBOXOSTYPE_OS21x },
+ { ovf::CIMOSType_CIMOS_OS2, VBOXOSTYPE_ECS },
+ { ovf::CIMOSType_CIMOS_OS2, VBOXOSTYPE_ArcaOS },
+ { ovf::CIMOSType_CIMOS_MSDOS, VBOXOSTYPE_DOS },
+ { ovf::CIMOSType_CIMOS_WIN3x, VBOXOSTYPE_Win31 },
+ { ovf::CIMOSType_CIMOS_WIN95, VBOXOSTYPE_Win95 },
+ { ovf::CIMOSType_CIMOS_WIN98, VBOXOSTYPE_Win98 },
+ { ovf::CIMOSType_CIMOS_WINNT, VBOXOSTYPE_WinNT },
+ { ovf::CIMOSType_CIMOS_WINNT, VBOXOSTYPE_WinNT4 },
+ { ovf::CIMOSType_CIMOS_WINNT, VBOXOSTYPE_WinNT3x },
+ { ovf::CIMOSType_CIMOS_NetWare, VBOXOSTYPE_Netware },
+ { ovf::CIMOSType_CIMOS_NovellOES, VBOXOSTYPE_Netware },
+ { ovf::CIMOSType_CIMOS_Solaris, VBOXOSTYPE_Solaris },
+ { ovf::CIMOSType_CIMOS_Solaris_64, VBOXOSTYPE_Solaris_x64 },
+ { ovf::CIMOSType_CIMOS_Solaris, VBOXOSTYPE_Solaris10U8_or_later },
+ { ovf::CIMOSType_CIMOS_Solaris_64, VBOXOSTYPE_Solaris10U8_or_later_x64 },
+ { ovf::CIMOSType_CIMOS_SunOS, VBOXOSTYPE_Solaris },
+ { ovf::CIMOSType_CIMOS_FreeBSD, VBOXOSTYPE_FreeBSD },
+ { ovf::CIMOSType_CIMOS_NetBSD, VBOXOSTYPE_NetBSD },
+ { ovf::CIMOSType_CIMOS_QNX, VBOXOSTYPE_QNX },
+ { ovf::CIMOSType_CIMOS_Windows2000, VBOXOSTYPE_Win2k },
+ { ovf::CIMOSType_CIMOS_WindowsMe, VBOXOSTYPE_WinMe },
+ { ovf::CIMOSType_CIMOS_OpenBSD, VBOXOSTYPE_OpenBSD },
+ { ovf::CIMOSType_CIMOS_WindowsXP, VBOXOSTYPE_WinXP },
+ { ovf::CIMOSType_CIMOS_WindowsXPEmbedded, VBOXOSTYPE_WinXP },
+ { ovf::CIMOSType_CIMOS_WindowsEmbeddedforPointofService, VBOXOSTYPE_WinXP },
+ { ovf::CIMOSType_CIMOS_MicrosoftWindowsServer2003, VBOXOSTYPE_Win2k3 },
+ { ovf::CIMOSType_CIMOS_MicrosoftWindowsServer2003_64, VBOXOSTYPE_Win2k3_x64 },
+ { ovf::CIMOSType_CIMOS_WindowsXP_64, VBOXOSTYPE_WinXP_x64 },
+ { ovf::CIMOSType_CIMOS_WindowsVista, VBOXOSTYPE_WinVista },
+ { ovf::CIMOSType_CIMOS_WindowsVista_64, VBOXOSTYPE_WinVista_x64 },
+ { ovf::CIMOSType_CIMOS_MicrosoftWindowsServer2008, VBOXOSTYPE_Win2k8 },
+ { ovf::CIMOSType_CIMOS_MicrosoftWindowsServer2008_64, VBOXOSTYPE_Win2k8_x64 },
+ { ovf::CIMOSType_CIMOS_FreeBSD_64, VBOXOSTYPE_FreeBSD_x64 },
+ { ovf::CIMOSType_CIMOS_MACOS, VBOXOSTYPE_MacOS },
+ { ovf::CIMOSType_CIMOS_MACOS, VBOXOSTYPE_MacOS_x64 }, // there is no CIM 64-bit type for this
+ { ovf::CIMOSType_CIMOS_MACOS, VBOXOSTYPE_MacOS106 },
+ { ovf::CIMOSType_CIMOS_MACOS, VBOXOSTYPE_MacOS106_x64 },
+ { ovf::CIMOSType_CIMOS_MACOS, VBOXOSTYPE_MacOS107_x64 },
+ { ovf::CIMOSType_CIMOS_MACOS, VBOXOSTYPE_MacOS108_x64 },
+ { ovf::CIMOSType_CIMOS_MACOS, VBOXOSTYPE_MacOS109_x64 },
+ { ovf::CIMOSType_CIMOS_MACOS, VBOXOSTYPE_MacOS1010_x64 },
+ { ovf::CIMOSType_CIMOS_MACOS, VBOXOSTYPE_MacOS1011_x64 },
+ { ovf::CIMOSType_CIMOS_MACOS, VBOXOSTYPE_MacOS1012_x64 },
+ { ovf::CIMOSType_CIMOS_MACOS, VBOXOSTYPE_MacOS1013_x64 },
+
+ // Linuxes
+ { ovf::CIMOSType_CIMOS_RedHatEnterpriseLinux, VBOXOSTYPE_RedHat },
+ { ovf::CIMOSType_CIMOS_RedHatEnterpriseLinux_64, VBOXOSTYPE_RedHat_x64 },
+ { ovf::CIMOSType_CIMOS_RedHatEnterpriseLinux, VBOXOSTYPE_RedHat3 },
+ { ovf::CIMOSType_CIMOS_RedHatEnterpriseLinux_64, VBOXOSTYPE_RedHat3_x64 },
+ { ovf::CIMOSType_CIMOS_RedHatEnterpriseLinux, VBOXOSTYPE_RedHat4 },
+ { ovf::CIMOSType_CIMOS_RedHatEnterpriseLinux_64, VBOXOSTYPE_RedHat4_x64 },
+ { ovf::CIMOSType_CIMOS_RedHatEnterpriseLinux, VBOXOSTYPE_RedHat5 },
+ { ovf::CIMOSType_CIMOS_RedHatEnterpriseLinux_64, VBOXOSTYPE_RedHat5_x64 },
+ { ovf::CIMOSType_CIMOS_RedHatEnterpriseLinux, VBOXOSTYPE_RedHat6 },
+ { ovf::CIMOSType_CIMOS_RedHatEnterpriseLinux_64, VBOXOSTYPE_RedHat6_x64 },
+ { ovf::CIMOSType_CIMOS_RedHatEnterpriseLinux_64, VBOXOSTYPE_RedHat7_x64 }, // 64-bit only
+ { ovf::CIMOSType_CIMOS_RedHatEnterpriseLinux_64, VBOXOSTYPE_RedHat8_x64 }, // 64-bit only
+ { ovf::CIMOSType_CIMOS_SUSE, VBOXOSTYPE_OpenSUSE },
+ { ovf::CIMOSType_CIMOS_SLES, VBOXOSTYPE_SUSE_LE },
+ { ovf::CIMOSType_CIMOS_NovellLinuxDesktop, VBOXOSTYPE_OpenSUSE },
+ { ovf::CIMOSType_CIMOS_SUSE_64, VBOXOSTYPE_OpenSUSE_x64 },
+ { ovf::CIMOSType_CIMOS_SLES_64, VBOXOSTYPE_SUSE_LE_x64 },
+ { ovf::CIMOSType_CIMOS_SUSE_64, VBOXOSTYPE_OpenSUSE_Leap_x64 }, // 64-bit only
+ { ovf::CIMOSType_CIMOS_SUSE, VBOXOSTYPE_OpenSUSE_Tumbleweed },
+ { ovf::CIMOSType_CIMOS_SUSE_64, VBOXOSTYPE_OpenSUSE_Tumbleweed_x64 },
+ { ovf::CIMOSType_CIMOS_LINUX, VBOXOSTYPE_Linux },
+ { ovf::CIMOSType_CIMOS_LINUX, VBOXOSTYPE_Linux22 },
+ { ovf::CIMOSType_CIMOS_SunJavaDesktopSystem, VBOXOSTYPE_Linux },
+ { ovf::CIMOSType_CIMOS_TurboLinux, VBOXOSTYPE_Turbolinux },
+ { ovf::CIMOSType_CIMOS_TurboLinux_64, VBOXOSTYPE_Turbolinux_x64 },
+ { ovf::CIMOSType_CIMOS_Mandriva, VBOXOSTYPE_Mandriva },
+ { ovf::CIMOSType_CIMOS_Mandriva_64, VBOXOSTYPE_Mandriva_x64 },
+ { ovf::CIMOSType_CIMOS_Mandriva, VBOXOSTYPE_OpenMandriva_Lx },
+ { ovf::CIMOSType_CIMOS_Mandriva_64, VBOXOSTYPE_OpenMandriva_Lx_x64 },
+ { ovf::CIMOSType_CIMOS_Mandriva, VBOXOSTYPE_PCLinuxOS },
+ { ovf::CIMOSType_CIMOS_Mandriva_64, VBOXOSTYPE_PCLinuxOS_x64 },
+ { ovf::CIMOSType_CIMOS_Mandriva, VBOXOSTYPE_Mageia },
+ { ovf::CIMOSType_CIMOS_Mandriva_64, VBOXOSTYPE_Mageia_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu10_LTS },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu10_LTS_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu10 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu10_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu11 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu11_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu12_LTS },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu12_LTS_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu12 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu12_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu13 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu13_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu14_LTS },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu14_LTS_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu14 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu14_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu15 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu15_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu16_LTS },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu16_LTS_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu16 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu16_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu17 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu17_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu18_LTS },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu18_LTS_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu18 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu18_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Ubuntu19 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu19_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu20_LTS_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu20_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu21_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu22_LTS_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Ubuntu22_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Lubuntu },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Lubuntu_x64 },
+ { ovf::CIMOSType_CIMOS_Ubuntu, VBOXOSTYPE_Xubuntu },
+ { ovf::CIMOSType_CIMOS_Ubuntu_64, VBOXOSTYPE_Xubuntu_x64 },
+ { ovf::CIMOSType_CIMOS_Debian, VBOXOSTYPE_Debian },
+ { ovf::CIMOSType_CIMOS_Debian_64, VBOXOSTYPE_Debian_x64 },
+ { ovf::CIMOSType_CIMOS_Debian, VBOXOSTYPE_Debian31 },
+ { ovf::CIMOSType_CIMOS_Debian, VBOXOSTYPE_Debian4 },
+ { ovf::CIMOSType_CIMOS_Debian_64, VBOXOSTYPE_Debian4_x64 },
+ { ovf::CIMOSType_CIMOS_Debian, VBOXOSTYPE_Debian5 },
+ { ovf::CIMOSType_CIMOS_Debian_64, VBOXOSTYPE_Debian5_x64 },
+ { ovf::CIMOSType_CIMOS_Debian, VBOXOSTYPE_Debian6 },
+ { ovf::CIMOSType_CIMOS_Debian_64, VBOXOSTYPE_Debian6_x64 },
+ { ovf::CIMOSType_CIMOS_Debian, VBOXOSTYPE_Debian7 },
+ { ovf::CIMOSType_CIMOS_Debian_64, VBOXOSTYPE_Debian7_x64 },
+ { ovf::CIMOSType_CIMOS_Debian, VBOXOSTYPE_Debian8 },
+ { ovf::CIMOSType_CIMOS_Debian_64, VBOXOSTYPE_Debian8_x64 },
+ { ovf::CIMOSType_CIMOS_Debian, VBOXOSTYPE_Debian9 },
+ { ovf::CIMOSType_CIMOS_Debian_64, VBOXOSTYPE_Debian9_x64 },
+ { ovf::CIMOSType_CIMOS_Debian, VBOXOSTYPE_Debian10 },
+ { ovf::CIMOSType_CIMOS_Debian_64, VBOXOSTYPE_Debian10_x64 },
+ { ovf::CIMOSType_CIMOS_Debian, VBOXOSTYPE_Debian11 },
+ { ovf::CIMOSType_CIMOS_Debian_64, VBOXOSTYPE_Debian11_x64 },
+ { ovf::CIMOSType_CIMOS_Linux_2_4_x, VBOXOSTYPE_Linux24 },
+ { ovf::CIMOSType_CIMOS_Linux_2_4_x_64, VBOXOSTYPE_Linux24_x64 },
+ { ovf::CIMOSType_CIMOS_Linux_2_6_x, VBOXOSTYPE_Linux26 },
+ { ovf::CIMOSType_CIMOS_Linux_2_6_x_64, VBOXOSTYPE_Linux26_x64 },
+ { ovf::CIMOSType_CIMOS_Linux_64, VBOXOSTYPE_Linux26_x64 },
+
+ // types that we have support for but CIM doesn't
+ { ovf::CIMOSType_CIMOS_Linux_2_6_x, VBOXOSTYPE_ArchLinux },
+ { ovf::CIMOSType_CIMOS_Linux_2_6_x_64, VBOXOSTYPE_ArchLinux_x64 },
+ { ovf::CIMOSType_CIMOS_Linux_2_6_x, VBOXOSTYPE_FedoraCore },
+ { ovf::CIMOSType_CIMOS_Linux_2_6_x_64, VBOXOSTYPE_FedoraCore_x64 },
+ { ovf::CIMOSType_CIMOS_Linux_2_6_x, VBOXOSTYPE_Gentoo },
+ { ovf::CIMOSType_CIMOS_Linux_2_6_x_64, VBOXOSTYPE_Gentoo_x64 },
+ { ovf::CIMOSType_CIMOS_Linux_2_6_x, VBOXOSTYPE_Xandros },
+ { ovf::CIMOSType_CIMOS_Linux_2_6_x_64, VBOXOSTYPE_Xandros_x64 },
+ { ovf::CIMOSType_CIMOS_Solaris, VBOXOSTYPE_OpenSolaris },
+ { ovf::CIMOSType_CIMOS_Solaris_64, VBOXOSTYPE_OpenSolaris_x64 },
+
+ // types added with CIM 2.25.0 follow:
+ { ovf::CIMOSType_CIMOS_WindowsServer2008R2, VBOXOSTYPE_Win2k8 }, // duplicate, see above
+// { ovf::CIMOSType_CIMOS_VMwareESXi = 104, // we can't run ESX in a VM
+ { ovf::CIMOSType_CIMOS_Windows7, VBOXOSTYPE_Win7 },
+ { ovf::CIMOSType_CIMOS_Windows7, VBOXOSTYPE_Win7_x64 }, // there is no
+ // CIM 64-bit type for this
+ { ovf::CIMOSType_CIMOS_CentOS, VBOXOSTYPE_RedHat },
+ { ovf::CIMOSType_CIMOS_CentOS_64, VBOXOSTYPE_RedHat_x64 },
+ { ovf::CIMOSType_CIMOS_OracleLinux, VBOXOSTYPE_Oracle },
+ { ovf::CIMOSType_CIMOS_OracleLinux_64, VBOXOSTYPE_Oracle_x64 },
+ { ovf::CIMOSType_CIMOS_OracleLinux, VBOXOSTYPE_Oracle4 },
+ { ovf::CIMOSType_CIMOS_OracleLinux_64, VBOXOSTYPE_Oracle4_x64 },
+ { ovf::CIMOSType_CIMOS_OracleLinux, VBOXOSTYPE_Oracle5 },
+ { ovf::CIMOSType_CIMOS_OracleLinux_64, VBOXOSTYPE_Oracle5_x64 },
+ { ovf::CIMOSType_CIMOS_OracleLinux, VBOXOSTYPE_Oracle6 },
+ { ovf::CIMOSType_CIMOS_OracleLinux_64, VBOXOSTYPE_Oracle6_x64 },
+ { ovf::CIMOSType_CIMOS_OracleLinux_64, VBOXOSTYPE_Oracle7_x64 }, // 64-bit only
+ { ovf::CIMOSType_CIMOS_OracleLinux_64, VBOXOSTYPE_Oracle8_x64 }, // 64-bit only
+ { ovf::CIMOSType_CIMOS_eComStation, VBOXOSTYPE_ECS },
+
+ { ovf::CIMOSType_CIMOS_WindowsServer2011, VBOXOSTYPE_Win2k8_x64 }, // no 1:1 match on the VBox side
+ { ovf::CIMOSType_CIMOS_WindowsServer2012, VBOXOSTYPE_Win2k12_x64 },
+ { ovf::CIMOSType_CIMOS_Windows8, VBOXOSTYPE_Win8 },
+ { ovf::CIMOSType_CIMOS_Windows8_64, VBOXOSTYPE_Win8_x64 },
+ { ovf::CIMOSType_CIMOS_WindowsServer2012R2, VBOXOSTYPE_Win2k12_x64 },
+ { ovf::CIMOSType_CIMOS_Windows8_1, VBOXOSTYPE_Win81 },
+ { ovf::CIMOSType_CIMOS_Windows8_1_64, VBOXOSTYPE_Win81_x64 },
+ { ovf::CIMOSType_CIMOS_WindowsServer2016, VBOXOSTYPE_Win2k16_x64 },
+ { ovf::CIMOSType_CIMOS_Windows10, VBOXOSTYPE_Win10 },
+ { ovf::CIMOSType_CIMOS_Windows10_64, VBOXOSTYPE_Win10_x64 },
+ { ovf::CIMOSType_CIMOS_Windows10_64, VBOXOSTYPE_Win10_x64 },
+ { ovf::CIMOSType_CIMOS_WindowsServer2016, VBOXOSTYPE_Win2k19_x64 }, // no CIM type for this yet
+
+ // there are no CIM types for these, so these turn to "other" on export:
+ // VBOXOSTYPE_OpenBSD
+ // VBOXOSTYPE_OpenBSD_x64
+ // VBOXOSTYPE_NetBSD
+ // VBOXOSTYPE_NetBSD_x64
+
+};
+
+/* Pattern structure for matching the OS type description field */
+struct osTypePattern
+{
+ const char *pcszPattern;
+ VBOXOSTYPE osType;
+};
+
+/* These are the 32-Bit ones. They are sorted by priority. */
+static const osTypePattern g_aOsTypesPattern[] =
+{
+ {"Windows NT", VBOXOSTYPE_WinNT4},
+ {"Windows XP", VBOXOSTYPE_WinXP},
+ {"Windows 2000", VBOXOSTYPE_Win2k},
+ {"Windows 2003", VBOXOSTYPE_Win2k3},
+ {"Windows Vista", VBOXOSTYPE_WinVista},
+ {"Windows 2008", VBOXOSTYPE_Win2k8},
+ {"Windows 7", VBOXOSTYPE_Win7},
+ {"Windows 8.1", VBOXOSTYPE_Win81},
+ {"Windows 8", VBOXOSTYPE_Win8},
+ {"Windows 10", VBOXOSTYPE_Win10},
+ {"SUSE", VBOXOSTYPE_OpenSUSE},
+ {"Novell", VBOXOSTYPE_OpenSUSE},
+ {"Red Hat", VBOXOSTYPE_RedHat},
+ {"Mandriva", VBOXOSTYPE_Mandriva},
+ {"Ubuntu", VBOXOSTYPE_Ubuntu},
+ {"Debian", VBOXOSTYPE_Debian},
+ {"QNX", VBOXOSTYPE_QNX},
+ {"Linux 2.4", VBOXOSTYPE_Linux24},
+ {"Linux 2.6", VBOXOSTYPE_Linux26},
+ {"Linux", VBOXOSTYPE_Linux},
+ {"OpenSolaris", VBOXOSTYPE_OpenSolaris},
+ {"Solaris", VBOXOSTYPE_OpenSolaris},
+ {"FreeBSD", VBOXOSTYPE_FreeBSD},
+ {"NetBSD", VBOXOSTYPE_NetBSD},
+ {"Windows 95", VBOXOSTYPE_Win95},
+ {"Windows 98", VBOXOSTYPE_Win98},
+ {"Windows Me", VBOXOSTYPE_WinMe},
+ {"Windows 3.", VBOXOSTYPE_Win31},
+ {"DOS", VBOXOSTYPE_DOS},
+ {"OS2", VBOXOSTYPE_OS2}
+};
+
+/* These are the 64-Bit ones. They are sorted by priority. */
+static const osTypePattern g_aOsTypesPattern64[] =
+{
+ {"Windows XP", VBOXOSTYPE_WinXP_x64},
+ {"Windows 2003", VBOXOSTYPE_Win2k3_x64},
+ {"Windows Vista", VBOXOSTYPE_WinVista_x64},
+ {"Windows 2008", VBOXOSTYPE_Win2k8_x64},
+ {"Windows 7", VBOXOSTYPE_Win7_x64},
+ {"Windows 8.1", VBOXOSTYPE_Win81_x64},
+ {"Windows 8", VBOXOSTYPE_Win8_x64},
+ {"Windows 2012", VBOXOSTYPE_Win2k12_x64},
+ {"Windows 10", VBOXOSTYPE_Win10_x64},
+ {"Windows 2016", VBOXOSTYPE_Win2k16_x64},
+ {"Windows 2019", VBOXOSTYPE_Win2k19_x64},
+ {"SUSE", VBOXOSTYPE_OpenSUSE_x64},
+ {"Novell", VBOXOSTYPE_OpenSUSE_x64},
+ {"Red Hat", VBOXOSTYPE_RedHat_x64},
+ {"Mandriva", VBOXOSTYPE_Mandriva_x64},
+ {"Ubuntu", VBOXOSTYPE_Ubuntu_x64},
+ {"Debian", VBOXOSTYPE_Debian_x64},
+ {"Linux 2.4", VBOXOSTYPE_Linux24_x64},
+ {"Linux 2.6", VBOXOSTYPE_Linux26_x64},
+ {"Linux", VBOXOSTYPE_Linux26_x64},
+ {"OpenSolaris", VBOXOSTYPE_OpenSolaris_x64},
+ {"Solaris", VBOXOSTYPE_OpenSolaris_x64},
+ {"FreeBSD", VBOXOSTYPE_FreeBSD_x64},
+};
+
+/**
+ * Private helper func that suggests a VirtualBox guest OS type
+ * for the given OVF operating system type.
+ */
+void convertCIMOSType2VBoxOSType(Utf8Str &strType, ovf::CIMOSType_T c, const Utf8Str &cStr)
+{
+ /* First check if the type is other/other_64 */
+ if (c == ovf::CIMOSType_CIMOS_Other)
+ {
+ for (size_t i = 0; i < RT_ELEMENTS(g_aOsTypesPattern); ++i)
+ if (cStr.contains(g_aOsTypesPattern[i].pcszPattern, Utf8Str::CaseInsensitive))
+ {
+ strType = Global::OSTypeId(g_aOsTypesPattern[i].osType);
+ return;
+ }
+ }
+ else if (c == ovf::CIMOSType_CIMOS_Other_64)
+ {
+ for (size_t i = 0; i < RT_ELEMENTS(g_aOsTypesPattern64); ++i)
+ if (cStr.contains(g_aOsTypesPattern64[i].pcszPattern, Utf8Str::CaseInsensitive))
+ {
+ strType = Global::OSTypeId(g_aOsTypesPattern64[i].osType);
+ return;
+ }
+ }
+
+ for (size_t i = 0; i < RT_ELEMENTS(g_aOsTypes); ++i)
+ {
+ if (c == g_aOsTypes[i].cim)
+ {
+ strType = Global::OSTypeId(g_aOsTypes[i].osType);
+ return;
+ }
+ }
+
+ if (c == ovf::CIMOSType_CIMOS_Other_64)
+ strType = Global::OSTypeId(VBOXOSTYPE_Unknown_x64);
+ else
+ strType = Global::OSTypeId(VBOXOSTYPE_Unknown);
+}
+
+/**
+ * Private helper func that suggests a VirtualBox guest OS type
+ * for the given OVF operating system type.
+ * @returns CIM OS type.
+ * @param pcszVBox Our guest OS type identifier string.
+ * @param fLongMode Whether long mode is enabled and a 64-bit CIM type is
+ * preferred even if the VBox guest type isn't 64-bit.
+ */
+ovf::CIMOSType_T convertVBoxOSType2CIMOSType(const char *pcszVBox, BOOL fLongMode)
+{
+ for (size_t i = 0; i < RT_ELEMENTS(g_aOsTypes); ++i)
+ {
+ if (!RTStrICmp(pcszVBox, Global::OSTypeId(g_aOsTypes[i].osType)))
+ {
+ if (fLongMode && !(g_aOsTypes[i].osType & VBOXOSTYPE_x64))
+ {
+ VBOXOSTYPE enmDesiredOsType = (VBOXOSTYPE)((int)g_aOsTypes[i].osType | (int)VBOXOSTYPE_x64);
+ for (size_t j = i+1; j < RT_ELEMENTS(g_aOsTypes); j++)
+ if (g_aOsTypes[j].osType == enmDesiredOsType)
+ return g_aOsTypes[j].cim;
+ if (i > 0)
+ {
+ for (size_t j = i-1; j > 0; j++)
+ if (g_aOsTypes[j].osType == enmDesiredOsType)
+ return g_aOsTypes[j].cim;
+ }
+ /* Not all OSes have 64-bit versions, so just return the 32-bit variant. */
+ }
+ return g_aOsTypes[i].cim;
+ }
+ }
+
+ return fLongMode ? ovf::CIMOSType_CIMOS_Other_64 : ovf::CIMOSType_CIMOS_Other;
+}
+
+Utf8Str convertNetworkAttachmentTypeToString(NetworkAttachmentType_T type)
+{
+ Utf8Str strType;
+ switch (type)
+ {
+ case NetworkAttachmentType_NAT: strType = "NAT"; break;
+ case NetworkAttachmentType_Bridged: strType = "Bridged"; break;
+ case NetworkAttachmentType_Internal: strType = "Internal"; break;
+ case NetworkAttachmentType_HostOnly: strType = "HostOnly"; break;
+ case NetworkAttachmentType_HostOnlyNetwork: strType = "HostOnlyNetwork"; break;
+ case NetworkAttachmentType_Generic: strType = "Generic"; break;
+ case NetworkAttachmentType_NATNetwork: strType = "NATNetwork"; break;
+ case NetworkAttachmentType_Null: strType = "Null"; break;
+ case NetworkAttachmentType_Cloud: strType = "Cloud"; break;
+#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
+ case NetworkAttachmentType_32BitHack: AssertFailedBreak(); /* (compiler warnings) */
+#endif
+ }
+ return strType;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Appliance constructor / destructor
+//
+// ////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(VirtualSystemDescription)
+
+HRESULT VirtualSystemDescription::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void VirtualSystemDescription::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+Appliance::Appliance()
+ : mVirtualBox(NULL)
+{
+}
+
+Appliance::~Appliance()
+{
+}
+
+
+HRESULT Appliance::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void Appliance::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Internal helpers
+//
+////////////////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// IVirtualBox public methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// This code is here so we won't have to include the appliance headers in the
+// IVirtualBox implementation.
+
+/**
+ * Implementation for IVirtualBox::createAppliance.
+ *
+ * @param aAppliance IAppliance object created if S_OK is returned.
+ * @return S_OK or error.
+ */
+HRESULT VirtualBox::createAppliance(ComPtr<IAppliance> &aAppliance)
+{
+ ComObjPtr<Appliance> appliance;
+ HRESULT hrc = appliance.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = appliance->init(this);
+ if (SUCCEEDED(hrc))
+ hrc = appliance.queryInterfaceTo(aAppliance.asOutParam());
+ }
+ return hrc;
+}
+
+/**
+ * Appliance COM initializer.
+ * @param aVirtualBox The VirtualBox object.
+ */
+HRESULT Appliance::init(VirtualBox *aVirtualBox)
+{
+ HRESULT rc = S_OK;
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Weak reference to a VirtualBox object */
+ unconst(mVirtualBox) = aVirtualBox;
+
+ // initialize data
+ m = new Data;
+ m->m_pSecretKeyStore = new SecretKeyStore(false /* fRequireNonPageable*/);
+ AssertReturn(m->m_pSecretKeyStore, E_FAIL);
+
+ rc = i_initBackendNames();
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return rc;
+}
+
+/**
+ * Appliance COM uninitializer.
+ */
+void Appliance::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ if (m->m_pSecretKeyStore)
+ delete m->m_pSecretKeyStore;
+
+ delete m;
+ m = NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// IAppliance public methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Public method implementation.
+ */
+HRESULT Appliance::getPath(com::Utf8Str &aPath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aPath = m->locInfo.strPath;
+
+ return S_OK;
+}
+
+/**
+ * Public method implementation.
+ */
+HRESULT Appliance::getDisks(std::vector<com::Utf8Str> &aDisks)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aDisks.resize(0);
+
+ if (m->pReader) // OVFReader instantiated?
+ {
+ aDisks.resize(m->pReader->m_mapDisks.size());
+
+ ovf::DiskImagesMap::const_iterator it;
+ size_t i = 0;
+ for (it = m->pReader->m_mapDisks.begin();
+ it != m->pReader->m_mapDisks.end();
+ ++it, ++i)
+ {
+ // create a string representing this disk
+ const ovf::DiskImage &d = it->second;
+ char *psz = NULL;
+ RTStrAPrintf(&psz,
+ "%s\t"
+ "%RI64\t"
+ "%RI64\t"
+ "%s\t"
+ "%s\t"
+ "%RI64\t"
+ "%RI64\t"
+ "%s",
+ d.strDiskId.c_str(),
+ d.iCapacity,
+ d.iPopulatedSize,
+ d.strFormat.c_str(),
+ d.strHref.c_str(),
+ d.iSize,
+ d.iChunkSize,
+ d.strCompression.c_str());
+ Utf8Str utf(psz);
+ aDisks[i] = utf;
+ RTStrFree(psz);
+ }
+ }
+
+ return S_OK;
+}
+
+/**
+ * Public method implementation.
+ */
+HRESULT Appliance::getCertificate(ComPtr<ICertificate> &aCertificateInfo)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Can be NULL at this point, queryInterfaceto handles that. */
+ m->ptrCertificateInfo.queryInterfaceTo(aCertificateInfo.asOutParam());
+ return S_OK;
+}
+
+/**
+ * Public method implementation.
+ */
+HRESULT Appliance::getVirtualSystemDescriptions(std::vector<ComPtr<IVirtualSystemDescription> > &aVirtualSystemDescriptions)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aVirtualSystemDescriptions.resize(m->virtualSystemDescriptions.size());
+ std::list< ComObjPtr<VirtualSystemDescription> > vsds(m->virtualSystemDescriptions);
+ size_t i = 0;
+ for (std::list< ComObjPtr<VirtualSystemDescription> >::iterator it = vsds.begin(); it != vsds.end(); ++it, ++i)
+ {
+ (*it).queryInterfaceTo(aVirtualSystemDescriptions[i].asOutParam());
+ }
+ return S_OK;
+}
+
+/**
+ * Public method implementation.
+ */
+HRESULT Appliance::getMachines(std::vector<com::Utf8Str> &aMachines)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aMachines.resize(m->llGuidsMachinesCreated.size());
+ size_t i = 0;
+ for (std::list<Guid>::const_iterator it = m->llGuidsMachinesCreated.begin();
+ it != m->llGuidsMachinesCreated.end();
+ ++it, ++i)
+ {
+ const Guid &uuid = *it;
+ aMachines[i] = uuid.toUtf16();
+ }
+ return S_OK;
+}
+
+HRESULT Appliance::createVFSExplorer(const com::Utf8Str &aURI, ComPtr<IVFSExplorer> &aExplorer)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<VFSExplorer> explorer;
+ HRESULT rc = S_OK;
+ try
+ {
+ Utf8Str uri(aURI);
+ /* Check which kind of export the user has requested */
+ LocationInfo li;
+ i_parseURI(aURI, li);
+ /* Create the explorer object */
+ explorer.createObject();
+ rc = explorer->init(li.storageType, li.strPath, li.strHostname, li.strUsername, li.strPassword, mVirtualBox);
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ if (SUCCEEDED(rc))
+ /* Return explorer to the caller */
+ explorer.queryInterfaceTo(aExplorer.asOutParam());
+
+ return rc;
+}
+
+
+/**
+ * Public method implementation.
+ * Add the "aRequested" numbers of new empty objects of VSD into the list
+ * "virtualSystemDescriptions".
+ * The parameter "aCreated" keeps the actual number of the added objects.
+ * In case of exception all added objects are removed from the list.
+ */
+HRESULT Appliance::createVirtualSystemDescriptions(ULONG aRequested, ULONG *aCreated)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+ uint32_t lQuantity = aRequested;
+ uint32_t i=0;
+
+ if (lQuantity < 1)
+ return setError(E_FAIL, tr("The number of VirtualSystemDescription objects must be at least 1 or more."));
+ try
+ {
+ for (; i<lQuantity; ++i)
+ {
+ ComObjPtr<VirtualSystemDescription> opVSD;
+ rc = opVSD.createObject();
+ if (SUCCEEDED(rc))
+ {
+ rc = opVSD->init();
+ if (SUCCEEDED(rc))
+ m->virtualSystemDescriptions.push_back(opVSD);
+ else
+ break;
+ }
+ else
+ break;
+ }
+
+ if (i<lQuantity)
+ LogRel(("Number of created VirtualSystemDescription objects is less than requested"
+ "(Requested %d, Created %d)",lQuantity, i));
+
+ *aCreated = i;
+ }
+ catch (HRESULT aRC)
+ {
+ for (; i>0; --i)
+ {
+ if (!m->virtualSystemDescriptions.empty())
+ m->virtualSystemDescriptions.pop_back();
+ else
+ break;
+ }
+ rc = aRC;
+ }
+
+ return rc;
+}
+
+/**
+ * Public method implementation.
+ */
+HRESULT Appliance::getWarnings(std::vector<com::Utf8Str> &aWarnings)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aWarnings.resize(m->llWarnings.size());
+
+ list<Utf8Str>::const_iterator it;
+ size_t i = 0;
+ for (it = m->llWarnings.begin();
+ it != m->llWarnings.end();
+ ++it, ++i)
+ {
+ aWarnings[i] = *it;
+ }
+
+ return S_OK;
+}
+
+HRESULT Appliance::getPasswordIds(std::vector<com::Utf8Str> &aIdentifiers)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aIdentifiers = m->m_vecPasswordIdentifiers;
+ return S_OK;
+}
+
+HRESULT Appliance::getMediumIdsForPasswordId(const com::Utf8Str &aPasswordId, std::vector<com::Guid> &aIdentifiers)
+{
+ HRESULT hrc = S_OK;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ std::map<com::Utf8Str, GUIDVEC>::const_iterator it = m->m_mapPwIdToMediumIds.find(aPasswordId);
+ if (it != m->m_mapPwIdToMediumIds.end())
+ aIdentifiers = it->second;
+ else
+ hrc = setError(E_FAIL, tr("The given password identifier is not associated with any medium"));
+
+ return hrc;
+}
+
+HRESULT Appliance::addPasswords(const std::vector<com::Utf8Str> &aIdentifiers,
+ const std::vector<com::Utf8Str> &aPasswords)
+{
+ HRESULT hrc = S_OK;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Check that the IDs do not exist already before changing anything. */
+ for (unsigned i = 0; i < aIdentifiers.size(); i++)
+ {
+ SecretKey *pKey = NULL;
+ int rc = m->m_pSecretKeyStore->retainSecretKey(aIdentifiers[i], &pKey);
+ if (rc != VERR_NOT_FOUND)
+ {
+ AssertPtr(pKey);
+ if (pKey)
+ pKey->release();
+ return setError(VBOX_E_OBJECT_IN_USE, tr("A password with the given ID already exists"));
+ }
+ }
+
+ for (unsigned i = 0; i < aIdentifiers.size() && SUCCEEDED(hrc); i++)
+ {
+ size_t cbKey = aPasswords[i].length() + 1; /* Include terminator */
+ const uint8_t *pbKey = (const uint8_t *)aPasswords[i].c_str();
+
+ int rc = m->m_pSecretKeyStore->addSecretKey(aIdentifiers[i], pbKey, cbKey);
+ if (RT_SUCCESS(rc))
+ m->m_cPwProvided++;
+ else if (rc == VERR_NO_MEMORY)
+ hrc = setError(E_OUTOFMEMORY, tr("Failed to allocate enough secure memory for the key"));
+ else
+ hrc = setError(E_FAIL, tr("Unknown error happened while adding a password (%Rrc)"), rc);
+ }
+
+ return hrc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Appliance private methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT Appliance::i_initBackendNames()
+{
+ HRESULT hrc = S_OK;
+ if (!g_fInitializedBackendNames)
+ {
+ /*
+ * Use the system properties to translate file extensions into
+ * storage backend names.
+ */
+ static struct
+ {
+ const char *pszExt; /**< extension */
+ char *pszBackendName;
+ size_t cbBackendName;
+ } const s_aFormats[] =
+ {
+ { "iso", g_szIsoBackend, sizeof(g_szIsoBackend) },
+ { "vmdk", g_szVmdkBackend, sizeof(g_szVmdkBackend) },
+ { "vhd", g_szVhdBackend, sizeof(g_szVhdBackend) },
+ };
+ SystemProperties *pSysProps = mVirtualBox->i_getSystemProperties();
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aFormats); i++)
+ {
+ ComObjPtr<MediumFormat> trgFormat = pSysProps->i_mediumFormatFromExtension(s_aFormats[i].pszExt);
+ if (trgFormat.isNotNull())
+ {
+ const char *pszName = trgFormat->i_getName().c_str();
+ int vrc = RTStrCopy(s_aFormats[i].pszBackendName, s_aFormats[i].cbBackendName, pszName);
+ AssertRCStmt(vrc, hrc = setError(E_FAIL, "Unexpected storage backend name copy error %Rrc for %s.", vrc, pszName));
+ }
+ else
+ hrc = setError(E_FAIL, tr("Can't find appropriate medium format for ISO type of a virtual disk."));
+ }
+
+ if (SUCCEEDED(hrc))
+ g_fInitializedBackendNames = true;
+ }
+
+ return hrc;
+}
+
+Utf8Str Appliance::i_typeOfVirtualDiskFormatFromURI(Utf8Str uri) const
+{
+ Assert(g_fInitializedBackendNames);
+
+ unsigned i = RT_ELEMENTS(g_aUriToBackend);
+ while (i-- > 0)
+ if (RTStrICmp(g_aUriToBackend[i].pszUri, uri.c_str()) == 0)
+ return Utf8Str(g_aUriToBackend[i].pszBackend);
+ return Utf8Str();
+}
+
+#if 0 /* unused */
+std::set<Utf8Str> Appliance::i_URIFromTypeOfVirtualDiskFormat(Utf8Str type)
+{
+ Assert(g_fInitializedBackendNames);
+
+ std::set<Utf8Str> UriSet;
+ unsigned i = RT_ELEMENTS(g_aUriToBackend);
+ while (i-- > 0)
+ if (RTStrICmp(g_aUriToBackend[i].pszBackend, type.c_str()) == 0)
+ UriSet.insert(g_aUriToBackend[i].pszUri);
+ return UriSet;
+}
+#endif
+
+/**
+ * Returns a medium format object corresponding to the given
+ * disk image or null if no such format.
+ *
+ * @param di Disk Image
+ * @param mf Medium Format
+ *
+ * @return ComObjPtr<MediumFormat>
+ */
+HRESULT Appliance::i_findMediumFormatFromDiskImage(const ovf::DiskImage &di, ComObjPtr<MediumFormat>& mf)
+{
+ HRESULT rc = S_OK;
+
+ /* Get the system properties. */
+ SystemProperties *pSysProps = mVirtualBox->i_getSystemProperties();
+
+ /* We need a proper source format description */
+ /* Which format to use? */
+ Utf8Str strSrcFormat = i_typeOfVirtualDiskFormatFromURI(di.strFormat);
+
+ /*
+ * fallback, if we can't determine virtual disk format using URI from the attribute ovf:format
+ * in the corresponding section <Disk> in the OVF file.
+ */
+ if (strSrcFormat.isEmpty())
+ {
+ strSrcFormat = di.strHref;
+
+ /* check either file gzipped or not
+ * if "yes" then remove last extension,
+ * i.e. "image.vmdk.gz"->"image.vmdk"
+ */
+ if (di.strCompression == "gzip")
+ {
+ if (RTPathHasSuffix(strSrcFormat.c_str()))
+ {
+ strSrcFormat.stripSuffix();
+ }
+ else
+ {
+ mf.setNull();
+ rc = setError(E_FAIL,
+ tr("Internal inconsistency looking up medium format for the disk image '%s'"),
+ di.strHref.c_str());
+ return rc;
+ }
+ }
+ /* Figure out from extension which format the image of disk has. */
+ if (RTPathHasSuffix(strSrcFormat.c_str()))
+ {
+ const char *pszExt = RTPathSuffix(strSrcFormat.c_str());
+ if (pszExt)
+ pszExt++;
+ mf = pSysProps->i_mediumFormatFromExtension(pszExt);
+ }
+ else
+ mf.setNull();
+ }
+ else
+ mf = pSysProps->i_mediumFormat(strSrcFormat);
+
+ if (mf.isNull())
+ rc = setError(E_FAIL, tr("Internal inconsistency looking up medium format for the disk image '%s'"),
+ di.strHref.c_str());
+
+ return rc;
+}
+
+/**
+ * Setup automatic I/O stream digest calculation, adding it to hOurManifest.
+ *
+ * @returns Passthru I/O stream, of @a hVfsIos if no digest calc needed.
+ * @param hVfsIos The stream to wrap. Always consumed.
+ * @param pszManifestEntry The manifest entry.
+ * @param fRead Set if read stream, clear if write.
+ * @throws Nothing.
+ */
+RTVFSIOSTREAM Appliance::i_manifestSetupDigestCalculationForGivenIoStream(RTVFSIOSTREAM hVfsIos, const char *pszManifestEntry,
+ bool fRead /*= true */)
+{
+ int vrc;
+ Assert(!RTManifestPtIosIsInstanceOf(hVfsIos));
+
+ if (m->fDigestTypes == 0)
+ return hVfsIos;
+
+ /* Create the manifest if necessary. */
+ if (m->hOurManifest == NIL_RTMANIFEST)
+ {
+ vrc = RTManifestCreate(0 /*fFlags*/, &m->hOurManifest);
+ AssertRCReturnStmt(vrc, RTVfsIoStrmRelease(hVfsIos), NIL_RTVFSIOSTREAM);
+ }
+
+ /* Setup the stream. */
+ RTVFSIOSTREAM hVfsIosPt;
+ vrc = RTManifestEntryAddPassthruIoStream(m->hOurManifest, hVfsIos, pszManifestEntry, m->fDigestTypes, fRead, &hVfsIosPt);
+
+ RTVfsIoStrmRelease(hVfsIos); /* always consumed! */
+ if (RT_SUCCESS(vrc))
+ return hVfsIosPt;
+
+ setErrorVrc(vrc, tr("RTManifestEntryAddPassthruIoStream failed with rc=%Rrc"), vrc);
+ return NIL_RTVFSIOSTREAM;
+}
+
+/**
+ * Returns true if the appliance is in "idle" state. This should always be the
+ * case unless an import or export is currently in progress. Similar to machine
+ * states, this permits the Appliance implementation code to let go of the
+ * Appliance object lock while a time-consuming disk conversion is in progress
+ * without exposing the appliance to conflicting calls.
+ *
+ * This sets an error on "this" (the appliance) and returns false if the appliance
+ * is busy. The caller should then return E_ACCESSDENIED.
+ *
+ * Must be called from under the object lock!
+ */
+bool Appliance::i_isApplianceIdle()
+{
+ if (m->state == ApplianceImporting)
+ setError(VBOX_E_INVALID_OBJECT_STATE, tr("The appliance is busy importing files"));
+ else if (m->state == ApplianceExporting)
+ setError(VBOX_E_INVALID_OBJECT_STATE, tr("The appliance is busy exporting files"));
+ else
+ return true;
+
+ return false;
+}
+
+HRESULT Appliance::i_searchUniqueVMName(Utf8Str &aName) const
+{
+ ComPtr<IMachine> ptrMachine;
+ char *tmpName = RTStrDup(aName.c_str());
+ int i = 1;
+ while (mVirtualBox->FindMachine(Bstr(tmpName).raw(), ptrMachine.asOutParam()) != VBOX_E_OBJECT_NOT_FOUND)
+ {
+ RTStrFree(tmpName);
+ RTStrAPrintf(&tmpName, "%s %d", aName.c_str(), i);
+ ++i;
+ }
+ aName = tmpName;
+ RTStrFree(tmpName);
+
+ return S_OK;
+}
+
+HRESULT Appliance::i_ensureUniqueImageFilePath(const Utf8Str &aMachineFolder, DeviceType_T aDeviceType, Utf8Str &aName) const
+{
+ /*
+ * Check if the file exists or if a medium with this path is registered already
+ */
+ Utf8Str strAbsName;
+ size_t offDashNum = ~(size_t)0;
+ size_t cchDashNum = 0;
+ for (unsigned i = 1;; i++)
+ {
+ /* Complete the path (could be relative to machine folder). */
+ int rc = RTPathAbsExCxx(strAbsName, aMachineFolder, aName);
+ AssertRCReturn(rc, Global::vboxStatusCodeToCOM(rc)); /** @todo stupid caller ignores this */
+
+ /* Check that the file does not exist and that there is no media somehow matching the name. */
+ if (!RTPathExists(strAbsName.c_str()))
+ {
+ ComPtr<IMedium> ptrMedium;
+ HRESULT hrc = mVirtualBox->OpenMedium(Bstr(strAbsName).raw(), aDeviceType, AccessMode_ReadWrite,
+ FALSE /* fForceNewUuid */, ptrMedium.asOutParam());
+ if (hrc == VBOX_E_OBJECT_NOT_FOUND)
+ return S_OK;
+ }
+
+ /* Insert '_%i' before the suffix and try again. */
+ if (offDashNum == ~(size_t)0)
+ {
+ const char *pszSuffix = RTPathSuffix(aName.c_str());
+ offDashNum = pszSuffix ? (size_t)(pszSuffix - aName.c_str()) : aName.length();
+ }
+ char szTmp[32];
+ size_t cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "_%u", i);
+ aName.replace(offDashNum, cchDashNum, szTmp, cchTmp);
+ cchDashNum = cchTmp;
+ }
+}
+
+/**
+ * Called from Appliance::importImpl() and Appliance::writeImpl() to set up a
+ * progress object with the proper weights and maximum progress values.
+ */
+HRESULT Appliance::i_setUpProgress(ComObjPtr<Progress> &pProgress,
+ const Utf8Str &strDescription,
+ SetUpProgressMode mode)
+{
+ HRESULT rc;
+
+ /* Create the progress object */
+ try
+ {
+ rc = pProgress.createObject();
+ if (FAILED(rc))
+ return rc;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // compute the disks weight (this sets ulTotalDisksMB and cDisks in the instance data)
+ i_disksWeight();
+
+ m->ulWeightForManifestOperation = 0;
+
+ ULONG cOperations = 1 // one for XML setup
+ + m->cDisks; // plus one per disk
+ ULONG ulTotalOperationsWeight;
+ if (m->ulTotalDisksMB)
+ {
+ m->ulWeightForXmlOperation = (ULONG)((double)m->ulTotalDisksMB * 1 / 100); // use 1% of the progress for the XML
+ ulTotalOperationsWeight = m->ulTotalDisksMB + m->ulWeightForXmlOperation;
+ }
+ else
+ {
+ // no disks to export:
+ m->ulWeightForXmlOperation = 1;
+ ulTotalOperationsWeight = 1;
+ }
+
+ switch (mode)
+ {
+ case ImportFile:
+ {
+ break;
+ }
+ case WriteFile:
+ {
+ // assume that creating the manifest will take .1% of the time it takes to export the disks
+ if (m->fManifest)
+ {
+ ++cOperations; // another one for creating the manifest
+
+ m->ulWeightForManifestOperation = (ULONG)((double)m->ulTotalDisksMB * .1 / 100); // use .5% of the
+ // progress for the manifest
+ ulTotalOperationsWeight += m->ulWeightForManifestOperation;
+ }
+ break;
+ }
+ case ImportS3:
+ {
+ cOperations += 1 + 1; // another one for the manifest file & another one for the import
+ ulTotalOperationsWeight = m->ulTotalDisksMB;
+ if (!m->ulTotalDisksMB)
+ // no disks to export:
+ ulTotalOperationsWeight = 1;
+
+ ULONG ulImportWeight = (ULONG)((double)ulTotalOperationsWeight * 50 / 100); // use 50% for import
+ ulTotalOperationsWeight += ulImportWeight;
+
+ m->ulWeightForXmlOperation = ulImportWeight; /* save for using later */
+
+ ULONG ulInitWeight = (ULONG)((double)ulTotalOperationsWeight * 0.1 / 100); // use 0.1% for init
+ ulTotalOperationsWeight += ulInitWeight;
+ break;
+ }
+ case WriteS3:
+ {
+ cOperations += 1 + 1; // another one for the mf & another one for temporary creation
+
+ if (m->ulTotalDisksMB)
+ {
+ m->ulWeightForXmlOperation = (ULONG)((double)m->ulTotalDisksMB * 1 / 100); // use 1% of the progress
+ // for OVF file upload
+ // (we didn't know the
+ // size at this point)
+ ulTotalOperationsWeight = m->ulTotalDisksMB + m->ulWeightForXmlOperation;
+ }
+ else
+ {
+ // no disks to export:
+ ulTotalOperationsWeight = 1;
+ m->ulWeightForXmlOperation = 1;
+ }
+ ULONG ulOVFCreationWeight = (ULONG)((double)ulTotalOperationsWeight * 50.0 / 100.0); /* Use 50% for the
+ creation of the OVF
+ & the disks */
+ ulTotalOperationsWeight += ulOVFCreationWeight;
+ break;
+ }
+ case ExportCloud:
+ case ImportCloud:
+ break;
+ }
+ Log(("Setting up progress object: ulTotalMB = %d, cDisks = %d, => cOperations = %d, ulTotalOperationsWeight = %d, m->ulWeightForXmlOperation = %d\n",
+ m->ulTotalDisksMB, m->cDisks, cOperations, ulTotalOperationsWeight, m->ulWeightForXmlOperation));
+
+ return pProgress->init(mVirtualBox, static_cast<IAppliance*>(this),
+ strDescription,
+ TRUE /* aCancelable */,
+ cOperations, // ULONG cOperations,
+ ulTotalOperationsWeight, // ULONG ulTotalOperationsWeight,
+ strDescription, // CBSTR bstrFirstOperationDescription,
+ m->ulWeightForXmlOperation); // ULONG ulFirstOperationWeight,
+}
+
+void Appliance::i_addWarning(const char* aWarning, ...)
+{
+ try
+ {
+ va_list args;
+ va_start(args, aWarning);
+ Utf8Str str(aWarning, args);
+ va_end(args);
+ m->llWarnings.push_back(str);
+ }
+ catch (...)
+ {
+ AssertFailed();
+ }
+}
+
+/**
+ * Refreshes the cDisks and ulTotalDisksMB members in the instance data.
+ * Requires that virtual system descriptions are present.
+ */
+void Appliance::i_disksWeight()
+{
+ m->ulTotalDisksMB = 0;
+ m->cDisks = 0;
+ // weigh the disk images according to their sizes
+ list< ComObjPtr<VirtualSystemDescription> >::const_iterator it;
+ for (it = m->virtualSystemDescriptions.begin();
+ it != m->virtualSystemDescriptions.end();
+ ++it)
+ {
+ ComObjPtr<VirtualSystemDescription> vsdescThis = (*it);
+ /* One for every medium of the Virtual System */
+ std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
+ std::list<VirtualSystemDescriptionEntry*>::const_iterator itH;
+ for (itH = avsdeHDs.begin();
+ itH != avsdeHDs.end();
+ ++itH)
+ {
+ const VirtualSystemDescriptionEntry *pHD = *itH;
+ m->ulTotalDisksMB += pHD->ulSizeMB;
+ ++m->cDisks;
+ }
+
+ avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM);
+ for (itH = avsdeHDs.begin();
+ itH != avsdeHDs.end();
+ ++itH)
+ {
+ const VirtualSystemDescriptionEntry *pHD = *itH;
+ m->ulTotalDisksMB += pHD->ulSizeMB;
+ ++m->cDisks;
+ }
+ }
+
+}
+
+void Appliance::i_parseBucket(Utf8Str &aPath, Utf8Str &aBucket)
+{
+ /* Buckets are S3 specific. So parse the bucket out of the file path */
+ if (!aPath.startsWith("/"))
+ throw setError(E_INVALIDARG,
+ tr("The path '%s' must start with /"), aPath.c_str());
+ size_t bpos = aPath.find("/", 1);
+ if (bpos != Utf8Str::npos)
+ {
+ aBucket = aPath.substr(1, bpos - 1); /* The bucket without any slashes */
+ aPath = aPath.substr(bpos); /* The rest of the file path */
+ }
+ /* If there is no bucket name provided reject it */
+ if (aBucket.isEmpty())
+ throw setError(E_INVALIDARG,
+ tr("You doesn't provide a bucket name in the URI '%s'"), aPath.c_str());
+}
+
+/**
+ * Worker for TaskOVF::handler.
+ *
+ * The TaskOVF is started in Appliance::readImpl() and Appliance::importImpl()
+ * and Appliance::writeImpl().
+ *
+ * This will in turn call Appliance::readFS() or Appliance::importFS() or
+ * Appliance::writeFS().
+ *
+ * @thread pTask The task.
+ */
+/* static */ void Appliance::i_importOrExportThreadTask(TaskOVF *pTask)
+{
+ LogFlowFuncEnter();
+ AssertReturnVoid(pTask);
+
+ Appliance *pAppliance = pTask->pAppliance;
+ LogFlowFunc(("Appliance %p taskType=%d\n", pAppliance, pTask->taskType));
+
+ switch (pTask->taskType)
+ {
+ case TaskOVF::Read:
+ pAppliance->m->resetReadData();
+ if (pTask->locInfo.storageType == VFSType_File)
+ pTask->rc = pAppliance->i_readFS(pTask);
+ else
+ pTask->rc = E_NOTIMPL;
+ break;
+
+ case TaskOVF::Import:
+ /** @todo allow overriding these? */
+ if (!pAppliance->m->fSignatureValid && pAppliance->m->pbSignedDigest)
+ pTask->rc = pAppliance->setError(E_FAIL, tr("The manifest signature for '%s' is not valid"),
+ pTask->locInfo.strPath.c_str());
+ else if (!pAppliance->m->fCertificateValid && pAppliance->m->pbSignedDigest)
+ {
+ if (pAppliance->m->strCertError.isNotEmpty())
+ pTask->rc = pAppliance->setError(E_FAIL, tr("The certificate used to signed '%s' is not valid: %s"),
+ pTask->locInfo.strPath.c_str(), pAppliance->m->strCertError.c_str());
+ else
+ pTask->rc = pAppliance->setError(E_FAIL, tr("The certificate used to signed '%s' is not valid"),
+ pTask->locInfo.strPath.c_str());
+ }
+ // fusion does not consider this a show stopper (we've filed a warning during read).
+ //else if (pAppliance->m->fCertificateMissingPath && pAppliance->m->pbSignedDigest)
+ // pTask->rc = pAppliance->setError(E_FAIL, tr("The certificate used to signed '%s' is does not have a valid CA path"),
+ // pTask->locInfo.strPath.c_str());
+ else
+ {
+ if (pTask->locInfo.storageType == VFSType_File)
+ pTask->rc = pAppliance->i_importFS(pTask);
+ else
+ pTask->rc = E_NOTIMPL;
+ }
+ break;
+
+ case TaskOVF::Write:
+ if (pTask->locInfo.storageType == VFSType_File)
+ pTask->rc = pAppliance->i_writeFS(pTask);
+ else
+ pTask->rc = E_NOTIMPL;
+ break;
+
+ default:
+ AssertFailed();
+ pTask->rc = E_FAIL;
+ break;
+ }
+
+ if (!pTask->pProgress.isNull())
+ pTask->pProgress->i_notifyComplete(pTask->rc);
+
+ LogFlowFuncLeave();
+}
+
+/* static */ DECLCALLBACK(int) Appliance::TaskOVF::updateProgress(unsigned uPercent, void *pvUser)
+{
+ Appliance::TaskOVF* pTask = *(Appliance::TaskOVF**)pvUser;
+
+ if ( pTask
+ && !pTask->pProgress.isNull())
+ {
+ BOOL fCanceled;
+ pTask->pProgress->COMGETTER(Canceled)(&fCanceled);
+ if (fCanceled)
+ return -1;
+ pTask->pProgress->SetCurrentOperationProgress(uPercent);
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Worker for TaskOPC::handler.
+ * @thread pTask The task.
+ */
+/* static */
+void Appliance::i_exportOPCThreadTask(TaskOPC *pTask)
+{
+ LogFlowFuncEnter();
+ AssertReturnVoid(pTask);
+
+ Appliance *pAppliance = pTask->pAppliance;
+ LogFlowFunc(("Appliance %p taskType=%d\n", pAppliance, pTask->taskType));
+
+ switch (pTask->taskType)
+ {
+ case TaskOPC::Export:
+ pTask->rc = pAppliance->i_writeFSOPC(pTask);
+ break;
+
+ default:
+ AssertFailed();
+ pTask->rc = E_FAIL;
+ break;
+ }
+
+ if (!pTask->pProgress.isNull())
+ pTask->pProgress->i_notifyComplete(pTask->rc);
+
+ LogFlowFuncLeave();
+}
+
+/* static */
+DECLCALLBACK(int) Appliance::TaskOPC::updateProgress(unsigned uPercent, void *pvUser)
+{
+ Appliance::TaskOPC* pTask = *(Appliance::TaskOPC**)pvUser;
+
+ if ( pTask
+ && !pTask->pProgress.isNull())
+ {
+ BOOL fCanceled;
+ pTask->pProgress->COMGETTER(Canceled)(&fCanceled);
+ if (fCanceled)
+ return -1;
+ pTask->pProgress->SetCurrentOperationProgress(uPercent);
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Worker for TaskCloud::handler.
+ * @thread pTask The task.
+ */
+/* static */
+void Appliance::i_importOrExportCloudThreadTask(TaskCloud *pTask)
+{
+ LogFlowFuncEnter();
+ AssertReturnVoid(pTask);
+
+ Appliance *pAppliance = pTask->pAppliance;
+ LogFlowFunc(("Appliance %p taskType=%d\n", pAppliance, pTask->taskType));
+
+ switch (pTask->taskType)
+ {
+ case TaskCloud::Export:
+ pAppliance->i_setApplianceState(ApplianceExporting);
+ pTask->rc = pAppliance->i_exportCloudImpl(pTask);
+ break;
+ case TaskCloud::Import:
+ pAppliance->i_setApplianceState(ApplianceImporting);
+ pTask->rc = pAppliance->i_importCloudImpl(pTask);
+ break;
+ case TaskCloud::ReadData:
+ pAppliance->i_setApplianceState(ApplianceImporting);
+ pTask->rc = pAppliance->i_gettingCloudData(pTask);
+ break;
+ default:
+ AssertFailed();
+ pTask->rc = E_FAIL;
+ break;
+ }
+
+ pAppliance->i_setApplianceState(ApplianceIdle);
+
+ if (!pTask->pProgress.isNull())
+ pTask->pProgress->i_notifyComplete(pTask->rc);
+
+ LogFlowFuncLeave();
+}
+
+/* static */
+DECLCALLBACK(int) Appliance::TaskCloud::updateProgress(unsigned uPercent, void *pvUser)
+{
+ Appliance::TaskCloud* pTask = *(Appliance::TaskCloud**)pvUser;
+
+ if ( pTask
+ && !pTask->pProgress.isNull())
+ {
+ BOOL fCanceled;
+ pTask->pProgress->COMGETTER(Canceled)(&fCanceled);
+ if (fCanceled)
+ return -1;
+ pTask->pProgress->SetCurrentOperationProgress(uPercent);
+ }
+ return VINF_SUCCESS;
+}
+
+void i_parseURI(Utf8Str strUri, LocationInfo &locInfo)
+{
+ /* Check the URI for the protocol */
+ if (strUri.startsWith("file://", Utf8Str::CaseInsensitive)) /* File based */
+ {
+ locInfo.storageType = VFSType_File;
+ strUri = strUri.substr(sizeof("file://") - 1);
+ }
+ else if (strUri.startsWith("SunCloud://", Utf8Str::CaseInsensitive)) /* Sun Cloud service */
+ {
+ locInfo.storageType = VFSType_S3;
+ strUri = strUri.substr(sizeof("SunCloud://") - 1);
+ }
+ else if (strUri.startsWith("S3://", Utf8Str::CaseInsensitive)) /* S3 service */
+ {
+ locInfo.storageType = VFSType_S3;
+ strUri = strUri.substr(sizeof("S3://") - 1);
+ }
+ else if (strUri.startsWith("OCI://", Utf8Str::CaseInsensitive)) /* OCI service (storage or compute) */
+ {
+ locInfo.storageType = VFSType_Cloud;
+ locInfo.strProvider = "OCI";
+ strUri = strUri.substr(sizeof("OCI://") - 1);
+ }
+ else if (strUri.startsWith("webdav://", Utf8Str::CaseInsensitive)) /* webdav service */
+ throw E_NOTIMPL;
+
+ /* Not necessary on a file based URI */
+// if (locInfo.storageType != VFSType_File)
+// {
+// size_t uppos = strUri.find("@"); /* username:password combo */
+// if (uppos != Utf8Str::npos)
+// {
+// locInfo.strUsername = strUri.substr(0, uppos);
+// strUri = strUri.substr(uppos + 1);
+// size_t upos = locInfo.strUsername.find(":");
+// if (upos != Utf8Str::npos)
+// {
+// locInfo.strPassword = locInfo.strUsername.substr(upos + 1);
+// locInfo.strUsername = locInfo.strUsername.substr(0, upos);
+// }
+// }
+// size_t hpos = strUri.find("/"); /* hostname part */
+// if (hpos != Utf8Str::npos)
+// {
+// locInfo.strHostname = strUri.substr(0, hpos);
+// strUri = strUri.substr(hpos);
+// }
+// }
+
+ locInfo.strPath = strUri;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// IVirtualSystemDescription constructor / destructor
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * COM initializer.
+ * @return
+ */
+HRESULT VirtualSystemDescription::init()
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Initialize data */
+ m = new Data();
+ m->pConfig = NULL;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+ return S_OK;
+}
+
+/**
+* COM uninitializer.
+*/
+
+void VirtualSystemDescription::uninit()
+{
+ if (m->pConfig)
+ delete m->pConfig;
+ delete m;
+ m = NULL;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// IVirtualSystemDescription public methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Public method implementation.
+ */
+HRESULT VirtualSystemDescription::getCount(ULONG *aCount)
+{
+ if (!aCount)
+ return E_POINTER;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCount = (ULONG)m->maDescriptions.size();
+ return S_OK;
+}
+
+/**
+ * Public method implementation.
+ */
+HRESULT VirtualSystemDescription::getDescription(std::vector<VirtualSystemDescriptionType_T> &aTypes,
+ std::vector<com::Utf8Str> &aRefs,
+ std::vector<com::Utf8Str> &aOVFValues,
+ std::vector<com::Utf8Str> &aVBoxValues,
+ std::vector<com::Utf8Str> &aExtraConfigValues)
+
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ size_t c = m->maDescriptions.size();
+ aTypes.resize(c);
+ aRefs.resize(c);
+ aOVFValues.resize(c);
+ aVBoxValues.resize(c);
+ aExtraConfigValues.resize(c);
+
+ for (size_t i = 0; i < c; i++)
+ {
+ const VirtualSystemDescriptionEntry &vsde = m->maDescriptions[i];
+ aTypes[i] = vsde.type;
+ aRefs[i] = vsde.strRef;
+ aOVFValues[i] = vsde.strOvf;
+ aVBoxValues[i] = vsde.strVBoxCurrent;
+ aExtraConfigValues[i] = vsde.strExtraConfigCurrent;
+ }
+ return S_OK;
+}
+
+/**
+ * Public method implementation.
+ */
+HRESULT VirtualSystemDescription::getDescriptionByType(VirtualSystemDescriptionType_T aType,
+ std::vector<VirtualSystemDescriptionType_T> &aTypes,
+ std::vector<com::Utf8Str> &aRefs,
+ std::vector<com::Utf8Str> &aOVFValues,
+ std::vector<com::Utf8Str> &aVBoxValues,
+ std::vector<com::Utf8Str> &aExtraConfigValues)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ std::list<VirtualSystemDescriptionEntry*> vsd = i_findByType(aType);
+
+ size_t c = vsd.size();
+ aTypes.resize(c);
+ aRefs.resize(c);
+ aOVFValues.resize(c);
+ aVBoxValues.resize(c);
+ aExtraConfigValues.resize(c);
+
+ size_t i = 0;
+ for (list<VirtualSystemDescriptionEntry*>::const_iterator it = vsd.begin(); it != vsd.end(); ++it, ++i)
+ {
+ const VirtualSystemDescriptionEntry *vsde = (*it);
+ aTypes[i] = vsde->type;
+ aRefs[i] = vsde->strRef;
+ aOVFValues[i] = vsde->strOvf;
+ aVBoxValues[i] = vsde->strVBoxCurrent;
+ aExtraConfigValues[i] = vsde->strExtraConfigCurrent;
+ }
+
+ return S_OK;
+}
+
+/**
+ * Public method implementation.
+ */
+HRESULT VirtualSystemDescription::getValuesByType(VirtualSystemDescriptionType_T aType,
+ VirtualSystemDescriptionValueType_T aWhich,
+ std::vector<com::Utf8Str> &aValues)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ std::list<VirtualSystemDescriptionEntry*> vsd = i_findByType (aType);
+ aValues.resize((ULONG)vsd.size());
+
+ list<VirtualSystemDescriptionEntry*>::const_iterator it;
+ size_t i = 0;
+ for (it = vsd.begin();
+ it != vsd.end();
+ ++it, ++i)
+ {
+ const VirtualSystemDescriptionEntry *vsde = (*it);
+
+ Bstr bstr;
+ switch (aWhich)
+ {
+ case VirtualSystemDescriptionValueType_Reference: aValues[i] = vsde->strRef; break;
+ case VirtualSystemDescriptionValueType_Original: aValues[i] = vsde->strOvf; break;
+ case VirtualSystemDescriptionValueType_Auto: aValues[i] = vsde->strVBoxCurrent; break;
+ case VirtualSystemDescriptionValueType_ExtraConfig: aValues[i] = vsde->strExtraConfigCurrent; break;
+#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
+ case VirtualSystemDescriptionValueType_32BitHack: AssertFailedBreak(); /* (compiler warnings) */
+#endif
+ }
+ }
+
+ return S_OK;
+}
+
+/**
+ * Public method implementation.
+ */
+HRESULT VirtualSystemDescription::setFinalValues(const std::vector<BOOL> &aEnabled,
+ const std::vector<com::Utf8Str> &aVBoxValues,
+ const std::vector<com::Utf8Str> &aExtraConfigValues)
+{
+#ifndef RT_OS_WINDOWS
+ // NOREF(aEnabledSize);
+#endif /* RT_OS_WINDOWS */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( (aEnabled.size() != m->maDescriptions.size())
+ || (aVBoxValues.size() != m->maDescriptions.size())
+ || (aExtraConfigValues.size() != m->maDescriptions.size())
+ )
+ return E_INVALIDARG;
+
+ size_t i = 0;
+ for (vector<VirtualSystemDescriptionEntry>::iterator it = m->maDescriptions.begin();
+ it != m->maDescriptions.end();
+ ++it, ++i)
+ {
+ VirtualSystemDescriptionEntry& vsde = *it;
+
+ if (aEnabled[i])
+ {
+ vsde.strVBoxCurrent = aVBoxValues[i];
+ vsde.strExtraConfigCurrent = aExtraConfigValues[i];
+ }
+ else
+ vsde.type = VirtualSystemDescriptionType_Ignore;
+ }
+
+ return S_OK;
+}
+
+/**
+ * Public method implementation.
+ */
+HRESULT VirtualSystemDescription::addDescription(VirtualSystemDescriptionType_T aType,
+ const com::Utf8Str &aVBoxValue,
+ const com::Utf8Str &aExtraConfigValue)
+
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ i_addEntry(aType, "", aVBoxValue, aVBoxValue, 0, aExtraConfigValue);
+ return S_OK;
+}
+
+/**
+ * Internal method; adds a new description item to the member list.
+ * @param aType Type of description for the new item.
+ * @param strRef Reference item; only used with storage controllers.
+ * @param aOvfValue Corresponding original value from OVF.
+ * @param aVBoxValue Initial configuration value (can be overridden by caller with setFinalValues).
+ * @param ulSizeMB Weight for IProgress
+ * @param strExtraConfig Extra configuration; meaning dependent on type.
+ */
+void VirtualSystemDescription::i_addEntry(VirtualSystemDescriptionType_T aType,
+ const Utf8Str &strRef,
+ const Utf8Str &aOvfValue,
+ const Utf8Str &aVBoxValue,
+ uint32_t ulSizeMB,
+ const Utf8Str &strExtraConfig /*= ""*/)
+{
+ VirtualSystemDescriptionEntry vsde;
+ vsde.ulIndex = (uint32_t)m->maDescriptions.size(); // each entry gets an index so the client side can reference them
+ vsde.type = aType;
+ vsde.strRef = strRef;
+ vsde.strOvf = aOvfValue;
+ vsde.strVBoxSuggested /* remember original value */
+ = vsde.strVBoxCurrent /* and set current value which can be overridden by setFinalValues() */
+ = aVBoxValue;
+ vsde.strExtraConfigSuggested
+ = vsde.strExtraConfigCurrent
+ = strExtraConfig;
+ vsde.ulSizeMB = ulSizeMB;
+
+ vsde.skipIt = false;
+
+ m->maDescriptions.push_back(vsde);
+}
+
+/**
+ * Private method; returns a list of description items containing all the items from the member
+ * description items of this virtual system that match the given type.
+ */
+std::list<VirtualSystemDescriptionEntry*> VirtualSystemDescription::i_findByType(VirtualSystemDescriptionType_T aType)
+{
+ std::list<VirtualSystemDescriptionEntry*> vsd;
+ for (vector<VirtualSystemDescriptionEntry>::iterator it = m->maDescriptions.begin();
+ it != m->maDescriptions.end();
+ ++it)
+ {
+ if (it->type == aType)
+ vsd.push_back(&(*it));
+ }
+
+ return vsd;
+}
+
+HRESULT VirtualSystemDescription::removeDescriptionByType(VirtualSystemDescriptionType_T aType)
+{
+ std::vector<VirtualSystemDescriptionEntry>::iterator it = m->maDescriptions.begin();
+ while (it != m->maDescriptions.end())
+ {
+ if (it->type == aType)
+ it = m->maDescriptions.erase(it);
+ else
+ ++it;
+ }
+
+ return S_OK;
+}
+
+/* Private method; delete all records from the list
+ * m->llDescriptions that match the given type.
+ */
+void VirtualSystemDescription::i_removeByType(VirtualSystemDescriptionType_T aType)
+{
+ std::vector<VirtualSystemDescriptionEntry>::iterator it = m->maDescriptions.begin();
+ while (it != m->maDescriptions.end())
+ {
+ if (it->type == aType)
+ it = m->maDescriptions.erase(it);
+ else
+ ++it;
+ }
+}
+
+/**
+ * Private method; looks thru the member hardware items for the IDE, SATA, or SCSI controller with
+ * the given reference ID. Useful when needing the controller for a particular
+ * virtual disk.
+ */
+const VirtualSystemDescriptionEntry* VirtualSystemDescription::i_findControllerFromID(const Utf8Str &id)
+{
+ vector<VirtualSystemDescriptionEntry>::const_iterator it;
+ for (it = m->maDescriptions.begin();
+ it != m->maDescriptions.end();
+ ++it)
+ {
+ const VirtualSystemDescriptionEntry &d = *it;
+ switch (d.type)
+ {
+ case VirtualSystemDescriptionType_HardDiskControllerIDE:
+ case VirtualSystemDescriptionType_HardDiskControllerSATA:
+ case VirtualSystemDescriptionType_HardDiskControllerSCSI:
+ case VirtualSystemDescriptionType_HardDiskControllerVirtioSCSI:
+ case VirtualSystemDescriptionType_HardDiskControllerSAS:
+ if (d.strRef == id)
+ return &d;
+ break;
+ default: break; /* Shut up MSC. */
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * Method called from Appliance::Interpret() if the source OVF for a virtual system
+ * contains a <vbox:Machine> element. This method then attempts to parse that and
+ * create a MachineConfigFile instance from it which is stored in this instance data
+ * and can then be used to create a machine.
+ *
+ * This must only be called once per instance.
+ *
+ * This rethrows all XML and logic errors from MachineConfigFile.
+ *
+ * @param elmMachine <vbox:Machine> element with attributes and subelements from some
+ * DOM tree.
+ */
+void VirtualSystemDescription::i_importVBoxMachineXML(const xml::ElementNode &elmMachine)
+{
+ settings::MachineConfigFile *pConfig = NULL;
+
+ Assert(m->pConfig == NULL);
+
+ try
+ {
+ pConfig = new settings::MachineConfigFile(NULL);
+ pConfig->importMachineXML(elmMachine);
+
+ m->pConfig = pConfig;
+ }
+ catch (...)
+ {
+ if (pConfig)
+ delete pConfig;
+ throw;
+ }
+}
+
+/**
+ * Returns the machine config created by importVBoxMachineXML() or NULL if there's none.
+ */
+const settings::MachineConfigFile* VirtualSystemDescription::i_getMachineConfig() const
+{
+ return m->pConfig;
+}
+
+/**
+ * Private method; walks through the array of VirtualSystemDescriptionEntry entries
+ * and returns the one matching the given index.
+ */
+const VirtualSystemDescriptionEntry* VirtualSystemDescription::i_findByIndex(const uint32_t aIndex)
+{
+ vector<VirtualSystemDescriptionEntry>::const_iterator it;
+ for (it = m->maDescriptions.begin();
+ it != m->maDescriptions.end();
+ ++it)
+ {
+ const VirtualSystemDescriptionEntry &d = *it;
+ if (d.ulIndex == aIndex)
+ return &d;
+ }
+
+ return NULL;
+}
+
diff --git a/src/VBox/Main/src-server/ApplianceImplExport.cpp b/src/VBox/Main/src-server/ApplianceImplExport.cpp
new file mode 100644
index 00000000..ef04f0ef
--- /dev/null
+++ b/src/VBox/Main/src-server/ApplianceImplExport.cpp
@@ -0,0 +1,2872 @@
+/* $Id: ApplianceImplExport.cpp $ */
+/** @file
+ * IAppliance and IVirtualSystem COM class implementations.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_APPLIANCE
+#include <iprt/buildconfig.h>
+#include <iprt/path.h>
+#include <iprt/dir.h>
+#include <iprt/param.h>
+#include <iprt/s3.h>
+#include <iprt/manifest.h>
+#include <iprt/stream.h>
+#include <iprt/zip.h>
+
+#include <VBox/version.h>
+
+#include "ApplianceImpl.h"
+#include "VirtualBoxImpl.h"
+#include "ProgressImpl.h"
+#include "MachineImpl.h"
+#include "MediumImpl.h"
+#include "LoggingNew.h"
+#include "Global.h"
+#include "MediumFormatImpl.h"
+#include "SystemPropertiesImpl.h"
+
+#include "AutoCaller.h"
+
+#include "ApplianceImplPrivate.h"
+
+using namespace std;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// IMachine public methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// This code is here so we won't have to include the appliance headers in the
+// IMachine implementation, and we also need to access private appliance data.
+
+/**
+* Public method implementation.
+* @param aAppliance Appliance object.
+* @param aLocation Where to store the appliance.
+* @param aDescription Appliance description.
+* @return
+*/
+HRESULT Machine::exportTo(const ComPtr<IAppliance> &aAppliance, const com::Utf8Str &aLocation,
+ ComPtr<IVirtualSystemDescription> &aDescription)
+{
+ HRESULT rc = S_OK;
+
+ if (!aAppliance)
+ return E_POINTER;
+
+ ComObjPtr<VirtualSystemDescription> pNewDesc;
+
+ try
+ {
+ IAppliance *iAppliance = aAppliance;
+ Appliance *pAppliance = static_cast<Appliance*>(iAppliance);
+
+ LocationInfo locInfo;
+ i_parseURI(aLocation, locInfo);
+
+ Utf8Str strBasename(locInfo.strPath);
+ strBasename.stripPath().stripSuffix();
+ if (locInfo.strPath.endsWith(".tar.gz", Utf8Str::CaseSensitive))
+ strBasename.stripSuffix();
+
+ // create a new virtual system to store in the appliance
+ rc = pNewDesc.createObject();
+ if (FAILED(rc)) throw rc;
+ rc = pNewDesc->init();
+ if (FAILED(rc)) throw rc;
+
+ // store the machine object so we can dump the XML in Appliance::Write()
+ pNewDesc->m->pMachine = this;
+
+#ifdef VBOX_WITH_USB
+ // first, call the COM methods, as they request locks
+ BOOL fUSBEnabled = FALSE;
+ com::SafeIfaceArray<IUSBController> usbControllers;
+ rc = COMGETTER(USBControllers)(ComSafeArrayAsOutParam(usbControllers));
+ if (SUCCEEDED(rc))
+ {
+ for (unsigned i = 0; i < usbControllers.size(); ++i)
+ {
+ USBControllerType_T enmType;
+
+ rc = usbControllers[i]->COMGETTER(Type)(&enmType);
+ if (FAILED(rc)) throw rc;
+
+ if (enmType == USBControllerType_OHCI)
+ fUSBEnabled = TRUE;
+ }
+ }
+#endif /* VBOX_WITH_USB */
+
+ // request the machine lock while accessing internal members
+ AutoReadLock alock1(this COMMA_LOCKVAL_SRC_POS);
+
+ ComPtr<IAudioAdapter> pAudioAdapter;
+ rc = mAudioSettings->COMGETTER(Adapter)(pAudioAdapter.asOutParam());
+ if (FAILED(rc)) throw rc;
+ BOOL fAudioEnabled;
+ rc = pAudioAdapter->COMGETTER(Enabled)(&fAudioEnabled);
+ if (FAILED(rc)) throw rc;
+ AudioControllerType_T audioController;
+ rc = pAudioAdapter->COMGETTER(AudioController)(&audioController);
+ if (FAILED(rc)) throw rc;
+
+ // get name
+ Utf8Str strVMName = mUserData->s.strName;
+ // get description
+ Utf8Str strDescription = mUserData->s.strDescription;
+ // get guest OS
+ Utf8Str strOsTypeVBox = mUserData->s.strOsType;
+ // CPU count
+ uint32_t cCPUs = mHWData->mCPUCount;
+ // memory size in MB
+ uint32_t ulMemSizeMB = mHWData->mMemorySize;
+ // VRAM size?
+ // BIOS settings?
+ // 3D acceleration enabled?
+ // hardware virtualization enabled?
+ // nested paging enabled?
+ // HWVirtExVPIDEnabled?
+ // PAEEnabled?
+ // Long mode enabled?
+ BOOL fLongMode;
+ rc = GetCPUProperty(CPUPropertyType_LongMode, &fLongMode);
+ if (FAILED(rc)) throw rc;
+
+ // snapshotFolder?
+ // VRDPServer?
+
+ /* Guest OS type */
+ ovf::CIMOSType_T cim = convertVBoxOSType2CIMOSType(strOsTypeVBox.c_str(), fLongMode);
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_OS,
+ "",
+ Utf8StrFmt("%RI32", cim),
+ strOsTypeVBox);
+
+ /* VM name */
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_Name,
+ "",
+ strVMName,
+ strVMName);
+
+ // description
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_Description,
+ "",
+ strDescription,
+ strDescription);
+
+ /* CPU count*/
+ Utf8Str strCpuCount = Utf8StrFmt("%RI32", cCPUs);
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_CPU,
+ "",
+ strCpuCount,
+ strCpuCount);
+
+ /* Memory */
+ Utf8Str strMemory = Utf8StrFmt("%RI64", (uint64_t)ulMemSizeMB * _1M);
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_Memory,
+ "",
+ strMemory,
+ strMemory);
+
+ // the one VirtualBox IDE controller has two channels with two ports each, which is
+ // considered two IDE controllers with two ports each by OVF, so export it as two
+ int32_t lIDEControllerPrimaryIndex = 0;
+ int32_t lIDEControllerSecondaryIndex = 0;
+ int32_t lSATAControllerIndex = 0;
+ int32_t lSCSIControllerIndex = 0;
+ int32_t lVirtioSCSIControllerIndex = 0;
+
+ /* Fetch all available storage controllers */
+ com::SafeIfaceArray<IStorageController> nwControllers;
+ rc = COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(nwControllers));
+ if (FAILED(rc)) throw rc;
+
+ ComPtr<IStorageController> pIDEController;
+ ComPtr<IStorageController> pSATAController;
+ ComPtr<IStorageController> pSCSIController;
+ ComPtr<IStorageController> pVirtioSCSIController;
+ ComPtr<IStorageController> pSASController;
+ for (size_t j = 0; j < nwControllers.size(); ++j)
+ {
+ StorageBus_T eType;
+ rc = nwControllers[j]->COMGETTER(Bus)(&eType);
+ if (FAILED(rc)) throw rc;
+ if ( eType == StorageBus_IDE
+ && pIDEController.isNull())
+ pIDEController = nwControllers[j];
+ else if ( eType == StorageBus_SATA
+ && pSATAController.isNull())
+ pSATAController = nwControllers[j];
+ else if ( eType == StorageBus_SCSI
+ && pSCSIController.isNull())
+ pSCSIController = nwControllers[j];
+ else if ( eType == StorageBus_SAS
+ && pSASController.isNull())
+ pSASController = nwControllers[j];
+ else if ( eType == StorageBus_VirtioSCSI
+ && pVirtioSCSIController.isNull())
+ pVirtioSCSIController = nwControllers[j];
+ }
+
+// <const name="HardDiskControllerIDE" value="6" />
+ if (!pIDEController.isNull())
+ {
+ StorageControllerType_T ctlr;
+ rc = pIDEController->COMGETTER(ControllerType)(&ctlr);
+ if (FAILED(rc)) throw rc;
+
+ Utf8Str strVBox;
+ switch (ctlr)
+ {
+ case StorageControllerType_PIIX3: strVBox = "PIIX3"; break;
+ case StorageControllerType_PIIX4: strVBox = "PIIX4"; break;
+ case StorageControllerType_ICH6: strVBox = "ICH6"; break;
+ default: break; /* Shut up MSC. */
+ }
+
+ if (strVBox.length())
+ {
+ lIDEControllerPrimaryIndex = (int32_t)pNewDesc->m->maDescriptions.size();
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE,
+ Utf8StrFmt("%d", lIDEControllerPrimaryIndex), // strRef
+ strVBox, // aOvfValue
+ strVBox); // aVBoxValue
+ lIDEControllerSecondaryIndex = lIDEControllerPrimaryIndex + 1;
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE,
+ Utf8StrFmt("%d", lIDEControllerSecondaryIndex),
+ strVBox,
+ strVBox);
+ }
+ }
+
+// <const name="HardDiskControllerSATA" value="7" />
+ if (!pSATAController.isNull())
+ {
+ Utf8Str strVBox = "AHCI";
+ lSATAControllerIndex = (int32_t)pNewDesc->m->maDescriptions.size();
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerSATA,
+ Utf8StrFmt("%d", lSATAControllerIndex),
+ strVBox,
+ strVBox);
+ }
+
+// <const name="HardDiskControllerSCSI" value="8" />
+ if (!pSCSIController.isNull())
+ {
+ StorageControllerType_T ctlr;
+ rc = pSCSIController->COMGETTER(ControllerType)(&ctlr);
+ if (SUCCEEDED(rc))
+ {
+ Utf8Str strVBox = "LsiLogic"; // the default in VBox
+ switch (ctlr)
+ {
+ case StorageControllerType_LsiLogic: strVBox = "LsiLogic"; break;
+ case StorageControllerType_BusLogic: strVBox = "BusLogic"; break;
+ default: break; /* Shut up MSC. */
+ }
+ lSCSIControllerIndex = (int32_t)pNewDesc->m->maDescriptions.size();
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerSCSI,
+ Utf8StrFmt("%d", lSCSIControllerIndex),
+ strVBox,
+ strVBox);
+ }
+ else
+ throw rc;
+ }
+
+ if (!pSASController.isNull())
+ {
+ // VirtualBox considers the SAS controller a class of its own but in OVF
+ // it should be a SCSI controller
+ Utf8Str strVBox = "LsiLogicSas";
+ lSCSIControllerIndex = (int32_t)pNewDesc->m->maDescriptions.size();
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerSAS,
+ Utf8StrFmt("%d", lSCSIControllerIndex),
+ strVBox,
+ strVBox);
+ }
+
+ if (!pVirtioSCSIController.isNull())
+ {
+ StorageControllerType_T ctlr;
+ rc = pVirtioSCSIController->COMGETTER(ControllerType)(&ctlr);
+ if (SUCCEEDED(rc))
+ {
+ Utf8Str strVBox = "VirtioSCSI"; // the default in VBox
+ switch (ctlr)
+ {
+ case StorageControllerType_VirtioSCSI: strVBox = "VirtioSCSI"; break;
+ default: break; /* Shut up MSC. */
+ }
+ lVirtioSCSIControllerIndex = (int32_t)pNewDesc->m->maDescriptions.size();
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerVirtioSCSI,
+ Utf8StrFmt("%d", lVirtioSCSIControllerIndex),
+ strVBox,
+ strVBox);
+ }
+ else
+ throw rc;
+ }
+
+// <const name="HardDiskImage" value="9" />
+// <const name="Floppy" value="18" />
+// <const name="CDROM" value="19" />
+
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ ComObjPtr<MediumAttachment> pHDA = *it;
+
+ // the attachment's data
+ ComPtr<IMedium> pMedium;
+ ComPtr<IStorageController> ctl;
+ Bstr controllerName;
+
+ rc = pHDA->COMGETTER(Controller)(controllerName.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ rc = GetStorageControllerByName(controllerName.raw(), ctl.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ StorageBus_T storageBus;
+ DeviceType_T deviceType;
+ LONG lChannel;
+ LONG lDevice;
+
+ rc = ctl->COMGETTER(Bus)(&storageBus);
+ if (FAILED(rc)) throw rc;
+
+ rc = pHDA->COMGETTER(Type)(&deviceType);
+ if (FAILED(rc)) throw rc;
+
+ rc = pHDA->COMGETTER(Port)(&lChannel);
+ if (FAILED(rc)) throw rc;
+
+ rc = pHDA->COMGETTER(Device)(&lDevice);
+ if (FAILED(rc)) throw rc;
+
+ rc = pHDA->COMGETTER(Medium)(pMedium.asOutParam());
+ if (FAILED(rc)) throw rc;
+ if (pMedium.isNull())
+ {
+ Utf8Str strStBus;
+ if ( storageBus == StorageBus_IDE)
+ strStBus = "IDE";
+ else if ( storageBus == StorageBus_SATA)
+ strStBus = "SATA";
+ else if ( storageBus == StorageBus_SCSI)
+ strStBus = "SCSI";
+ else if ( storageBus == StorageBus_SAS)
+ strStBus = "SAS";
+ else if ( storageBus == StorageBus_VirtioSCSI)
+ strStBus = "VirtioSCSI";
+
+ LogRel(("Warning: skip the medium (bus: %s, slot: %d, port: %d). No storage device attached.\n",
+ strStBus.c_str(), lDevice, lChannel));
+ continue;
+ }
+
+ Utf8Str strTargetImageName;
+ Utf8Str strLocation;
+ LONG64 llSize = 0;
+
+ if ( deviceType == DeviceType_HardDisk
+ && pMedium)
+ {
+ Bstr bstrLocation;
+
+ rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(rc)) throw rc;
+ strLocation = bstrLocation;
+
+ // find the source's base medium for two things:
+ // 1) we'll use its name to determine the name of the target disk, which is readable,
+ // as opposed to the UUID filename of a differencing image, if pMedium is one
+ // 2) we need the size of the base image so we can give it to addEntry(), and later
+ // on export, the progress will be based on that (and not the diff image)
+ ComPtr<IMedium> pBaseMedium;
+ rc = pMedium->COMGETTER(Base)(pBaseMedium.asOutParam());
+ // returns pMedium if there are no diff images
+ if (FAILED(rc)) throw rc;
+
+ strTargetImageName = Utf8StrFmt("%s-disk%.3d.vmdk", strBasename.c_str(), ++pAppliance->m->cDisks);
+ if (strTargetImageName.length() > RTTAR_NAME_MAX)
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Cannot attach disk '%s' -- file name too long"), strTargetImageName.c_str());
+
+ // force reading state, or else size will be returned as 0
+ MediumState_T ms;
+ rc = pBaseMedium->RefreshState(&ms);
+ if (FAILED(rc)) throw rc;
+
+ rc = pBaseMedium->COMGETTER(Size)(&llSize);
+ if (FAILED(rc)) throw rc;
+
+ /* If the medium is encrypted add the key identifier to the list. */
+ IMedium *iBaseMedium = pBaseMedium;
+ Medium *pBase = static_cast<Medium*>(iBaseMedium);
+ const com::Utf8Str strKeyId = pBase->i_getKeyId();
+ if (!strKeyId.isEmpty())
+ {
+ IMedium *iMedium = pMedium;
+ Medium *pMed = static_cast<Medium*>(iMedium);
+ com::Guid mediumUuid = pMed->i_getId();
+ bool fKnown = false;
+
+ /* Check whether the ID is already in our sequence, add it otherwise. */
+ for (unsigned i = 0; i < pAppliance->m->m_vecPasswordIdentifiers.size(); i++)
+ {
+ if (strKeyId.equals(pAppliance->m->m_vecPasswordIdentifiers[i]))
+ {
+ fKnown = true;
+ break;
+ }
+ }
+
+ if (!fKnown)
+ {
+ GUIDVEC vecMediumIds;
+
+ vecMediumIds.push_back(mediumUuid);
+ pAppliance->m->m_vecPasswordIdentifiers.push_back(strKeyId);
+ pAppliance->m->m_mapPwIdToMediumIds.insert(std::pair<com::Utf8Str, GUIDVEC>(strKeyId, vecMediumIds));
+ }
+ else
+ {
+ std::map<com::Utf8Str, GUIDVEC>::iterator itMap = pAppliance->m->m_mapPwIdToMediumIds.find(strKeyId);
+ if (itMap == pAppliance->m->m_mapPwIdToMediumIds.end())
+ throw setError(E_FAIL, tr("Internal error adding a medium UUID to the map"));
+ itMap->second.push_back(mediumUuid);
+ }
+ }
+ }
+ else if ( deviceType == DeviceType_DVD
+ && pMedium)
+ {
+ /*
+ * check the minimal rules to grant access to export an image
+ * 1. no host drive CD/DVD image
+ * 2. the image must be accessible and readable
+ * 3. only ISO image is exported
+ */
+
+ //1. no host drive CD/DVD image
+ BOOL fHostDrive = false;
+ rc = pMedium->COMGETTER(HostDrive)(&fHostDrive);
+ if (FAILED(rc)) throw rc;
+
+ if(fHostDrive)
+ continue;
+
+ //2. the image must be accessible and readable
+ MediumState_T ms;
+ rc = pMedium->RefreshState(&ms);
+ if (FAILED(rc)) throw rc;
+
+ if (ms != MediumState_Created)
+ continue;
+
+ //3. only ISO image is exported
+ Bstr bstrLocation;
+ rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ strLocation = bstrLocation;
+
+ Utf8Str ext = strLocation;
+ ext.assignEx(RTPathSuffix(strLocation.c_str()));//returns extension with dot (".iso")
+
+ int eq = ext.compare(".iso", Utf8Str::CaseInsensitive);
+ if (eq != 0)
+ continue;
+
+ strTargetImageName = Utf8StrFmt("%s-disk%.3d.iso", strBasename.c_str(), ++pAppliance->m->cDisks);
+ if (strTargetImageName.length() > RTTAR_NAME_MAX)
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Cannot attach image '%s' -- file name too long"), strTargetImageName.c_str());
+
+ rc = pMedium->COMGETTER(Size)(&llSize);
+ if (FAILED(rc)) throw rc;
+ }
+ // and how this translates to the virtual system
+ int32_t lControllerVsys = 0;
+ LONG lChannelVsys;
+
+ switch (storageBus)
+ {
+ case StorageBus_IDE:
+ // this is the exact reverse to what we're doing in Appliance::taskThreadImportMachines,
+ // and it must be updated when that is changed!
+ // Before 3.2 we exported one IDE controller with channel 0-3, but we now maintain
+ // compatibility with what VMware does and export two IDE controllers with two channels each
+
+ if (lChannel == 0 && lDevice == 0) // primary master
+ {
+ lControllerVsys = lIDEControllerPrimaryIndex;
+ lChannelVsys = 0;
+ }
+ else if (lChannel == 0 && lDevice == 1) // primary slave
+ {
+ lControllerVsys = lIDEControllerPrimaryIndex;
+ lChannelVsys = 1;
+ }
+ else if (lChannel == 1 && lDevice == 0) // secondary master; by default this is the CD-ROM but
+ // as of VirtualBox 3.1 that can change
+ {
+ lControllerVsys = lIDEControllerSecondaryIndex;
+ lChannelVsys = 0;
+ }
+ else if (lChannel == 1 && lDevice == 1) // secondary slave
+ {
+ lControllerVsys = lIDEControllerSecondaryIndex;
+ lChannelVsys = 1;
+ }
+ else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Cannot handle medium attachment: channel is %d, device is %d"), lChannel, lDevice);
+ break;
+
+ case StorageBus_SATA:
+ lChannelVsys = lChannel; // should be between 0 and 29
+ lControllerVsys = lSATAControllerIndex;
+ break;
+
+ case StorageBus_VirtioSCSI:
+ lChannelVsys = lChannel; // should be between 0 and 255
+ lControllerVsys = lVirtioSCSIControllerIndex;
+ break;
+
+ case StorageBus_SCSI:
+ case StorageBus_SAS:
+ lChannelVsys = lChannel; // should be between 0 and 15
+ lControllerVsys = lSCSIControllerIndex;
+ break;
+
+ case StorageBus_Floppy:
+ lChannelVsys = 0;
+ lControllerVsys = 0;
+ break;
+
+ default:
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Cannot handle medium attachment: storageBus is %d, channel is %d, device is %d"),
+ storageBus, lChannel, lDevice);
+ }
+
+ Utf8StrFmt strExtra("controller=%RI32;channel=%RI32", lControllerVsys, lChannelVsys);
+ Utf8Str strEmpty;
+
+ switch (deviceType)
+ {
+ case DeviceType_HardDisk:
+ Log(("Adding VirtualSystemDescriptionType_HardDiskImage, disk size: %RI64\n", llSize));
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskImage,
+ strTargetImageName, // disk ID: let's use the name
+ strTargetImageName, // OVF value:
+ strLocation, // vbox value: media path
+ (uint32_t)(llSize / _1M),
+ strExtra);
+ break;
+
+ case DeviceType_DVD:
+ Log(("Adding VirtualSystemDescriptionType_CDROM, disk size: %RI64\n", llSize));
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_CDROM,
+ strTargetImageName, // disk ID
+ strTargetImageName, // OVF value
+ strLocation, // vbox value
+ (uint32_t)(llSize / _1M),// ulSize
+ strExtra);
+ break;
+
+ case DeviceType_Floppy:
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_Floppy,
+ strEmpty, // disk ID
+ strEmpty, // OVF value
+ strEmpty, // vbox value
+ 1, // ulSize
+ strExtra);
+ break;
+
+ default: break; /* Shut up MSC. */
+ }
+ }
+
+// <const name="NetworkAdapter" />
+ uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(i_getChipsetType());
+ size_t a;
+ for (a = 0; a < maxNetworkAdapters; ++a)
+ {
+ ComPtr<INetworkAdapter> pNetworkAdapter;
+ BOOL fEnabled;
+ NetworkAdapterType_T adapterType;
+ NetworkAttachmentType_T attachmentType;
+
+ rc = GetNetworkAdapter((ULONG)a, pNetworkAdapter.asOutParam());
+ if (FAILED(rc)) throw rc;
+ /* Enable the network card & set the adapter type */
+ rc = pNetworkAdapter->COMGETTER(Enabled)(&fEnabled);
+ if (FAILED(rc)) throw rc;
+
+ if (fEnabled)
+ {
+ rc = pNetworkAdapter->COMGETTER(AdapterType)(&adapterType);
+ if (FAILED(rc)) throw rc;
+
+ rc = pNetworkAdapter->COMGETTER(AttachmentType)(&attachmentType);
+ if (FAILED(rc)) throw rc;
+
+ Utf8Str strAttachmentType = convertNetworkAttachmentTypeToString(attachmentType);
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_NetworkAdapter,
+ "", // ref
+ strAttachmentType, // orig
+ Utf8StrFmt("%RI32", (uint32_t)adapterType), // conf
+ 0,
+ Utf8StrFmt("type=%s", strAttachmentType.c_str())); // extra conf
+ }
+ }
+
+// <const name="USBController" />
+#ifdef VBOX_WITH_USB
+ if (fUSBEnabled)
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_USBController, "", "", "");
+#endif /* VBOX_WITH_USB */
+
+// <const name="SoundCard" />
+ if (fAudioEnabled)
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_SoundCard,
+ "",
+ "ensoniq1371", // this is what OVFTool writes and VMware supports
+ Utf8StrFmt("%RI32", audioController));
+
+ /* We return the new description to the caller */
+ ComPtr<IVirtualSystemDescription> copy(pNewDesc);
+ copy.queryInterfaceTo(aDescription.asOutParam());
+
+ AutoWriteLock alock(pAppliance COMMA_LOCKVAL_SRC_POS);
+ // finally, add the virtual system to the appliance
+ pAppliance->m->virtualSystemDescriptions.push_back(pNewDesc);
+ }
+ catch(HRESULT arc)
+ {
+ rc = arc;
+ }
+
+ return rc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// IAppliance public methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Public method implementation.
+ * @param aFormat Appliance format.
+ * @param aOptions Export options.
+ * @param aPath Path to write the appliance to.
+ * @param aProgress Progress object.
+ * @return
+ */
+HRESULT Appliance::write(const com::Utf8Str &aFormat,
+ const std::vector<ExportOptions_T> &aOptions,
+ const com::Utf8Str &aPath,
+ ComPtr<IProgress> &aProgress)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->optListExport.clear();
+ if (aOptions.size())
+ {
+ for (size_t i = 0; i < aOptions.size(); ++i)
+ {
+ m->optListExport.insert(i, aOptions[i]);
+ }
+ }
+
+ HRESULT rc = S_OK;
+// AssertReturn(!(m->optListExport.contains(ExportOptions_CreateManifest)
+// && m->optListExport.contains(ExportOptions_ExportDVDImages)), E_INVALIDARG);
+
+ /* Parse all necessary info out of the URI */
+ i_parseURI(aPath, m->locInfo);
+
+ if (m->locInfo.storageType == VFSType_Cloud)
+ {
+ rc = S_OK;
+ ComObjPtr<Progress> progress;
+ try
+ {
+ rc = i_writeCloudImpl(m->locInfo, progress);
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ if (SUCCEEDED(rc))
+ /* Return progress to the caller */
+ progress.queryInterfaceTo(aProgress.asOutParam());
+ }
+ else
+ {
+ m->fExportISOImages = m->optListExport.contains(ExportOptions_ExportDVDImages);
+
+ if (!m->fExportISOImages)/* remove all ISO images from VirtualSystemDescription */
+ {
+ for (list<ComObjPtr<VirtualSystemDescription> >::const_iterator
+ it = m->virtualSystemDescriptions.begin();
+ it != m->virtualSystemDescriptions.end();
+ ++it)
+ {
+ ComObjPtr<VirtualSystemDescription> vsdescThis = *it;
+ std::list<VirtualSystemDescriptionEntry*> skipped = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM);
+ std::list<VirtualSystemDescriptionEntry*>::const_iterator itSkipped = skipped.begin();
+ while (itSkipped != skipped.end())
+ {
+ (*itSkipped)->skipIt = true;
+ ++itSkipped;
+ }
+ }
+ }
+
+ // do not allow entering this method if the appliance is busy reading or writing
+ if (!i_isApplianceIdle())
+ return E_ACCESSDENIED;
+
+ // figure the export format. We exploit the unknown version value for oracle public cloud.
+ ovf::OVFVersion_T ovfF;
+ if (aFormat == "ovf-0.9")
+ ovfF = ovf::OVFVersion_0_9;
+ else if (aFormat == "ovf-1.0")
+ ovfF = ovf::OVFVersion_1_0;
+ else if (aFormat == "ovf-2.0")
+ ovfF = ovf::OVFVersion_2_0;
+ else if (aFormat == "opc-1.0")
+ ovfF = ovf::OVFVersion_unknown;
+ else
+ return setError(VBOX_E_FILE_ERROR,
+ tr("Invalid format \"%s\" specified"), aFormat.c_str());
+
+ // Check the extension.
+ if (ovfF == ovf::OVFVersion_unknown)
+ {
+ if (!aPath.endsWith(".tar.gz", Utf8Str::CaseInsensitive))
+ return setError(VBOX_E_FILE_ERROR,
+ tr("OPC appliance file must have .tar.gz extension"));
+ }
+ else if ( !aPath.endsWith(".ovf", Utf8Str::CaseInsensitive)
+ && !aPath.endsWith(".ova", Utf8Str::CaseInsensitive))
+ return setError(VBOX_E_FILE_ERROR, tr("Appliance file must have .ovf or .ova extension"));
+
+
+ /* As of OVF 2.0 we have to use SHA-256 in the manifest. */
+ m->fManifest = m->optListExport.contains(ExportOptions_CreateManifest);
+ if (m->fManifest)
+ m->fDigestTypes = ovfF >= ovf::OVFVersion_2_0 ? RTMANIFEST_ATTR_SHA256 : RTMANIFEST_ATTR_SHA1;
+ Assert(m->hOurManifest == NIL_RTMANIFEST);
+
+ /* Check whether all passwords are supplied or error out. */
+ if (m->m_cPwProvided < m->m_vecPasswordIdentifiers.size())
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Appliance export failed because not all passwords were provided for all encrypted media"));
+
+ ComObjPtr<Progress> progress;
+ rc = S_OK;
+ try
+ {
+ /* Parse all necessary info out of the URI */
+ i_parseURI(aPath, m->locInfo);
+
+ switch (ovfF)
+ {
+ case ovf::OVFVersion_unknown:
+ rc = i_writeOPCImpl(ovfF, m->locInfo, progress);
+ break;
+ default:
+ rc = i_writeImpl(ovfF, m->locInfo, progress);
+ break;
+ }
+
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ if (SUCCEEDED(rc))
+ /* Return progress to the caller */
+ progress.queryInterfaceTo(aProgress.asOutParam());
+ }
+
+ return rc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Appliance private methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/*******************************************************************************
+ * Export stuff
+ ******************************************************************************/
+
+/**
+ * Implementation for writing out the OVF to disk. This starts a new thread which will call
+ * Appliance::taskThreadWriteOVF().
+ *
+ * This is in a separate private method because it is used from two locations:
+ *
+ * 1) from the public Appliance::Write().
+ *
+ * 2) in a second worker thread; in that case, Appliance::Write() called Appliance::i_writeImpl(), which
+ * called Appliance::i_writeFSOVA(), which called Appliance::i_writeImpl(), which then called this again.
+ *
+ * @param aFormat
+ * @param aLocInfo
+ * @param aProgress
+ * @return
+ */
+HRESULT Appliance::i_writeImpl(ovf::OVFVersion_T aFormat, const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress)
+{
+ /* Prepare progress object: */
+ HRESULT hrc;
+ try
+ {
+ hrc = i_setUpProgress(aProgress,
+ Utf8StrFmt(tr("Export appliance '%s'"), aLocInfo.strPath.c_str()),
+ aLocInfo.storageType == VFSType_File ? WriteFile : WriteS3);
+ }
+ catch (std::bad_alloc &) /* only Utf8StrFmt */
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ /* Create our worker task: */
+ TaskOVF *pTask = NULL;
+ try
+ {
+ pTask = new TaskOVF(this, TaskOVF::Write, aLocInfo, aProgress);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /* The OVF version to produce: */
+ pTask->enFormat = aFormat;
+
+ /* Start the thread: */
+ hrc = pTask->createThread();
+ pTask = NULL;
+ }
+ return hrc;
+}
+
+
+HRESULT Appliance::i_writeCloudImpl(const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress)
+{
+ for (list<ComObjPtr<VirtualSystemDescription> >::const_iterator
+ it = m->virtualSystemDescriptions.begin();
+ it != m->virtualSystemDescriptions.end();
+ ++it)
+ {
+ ComObjPtr<VirtualSystemDescription> vsdescThis = *it;
+ std::list<VirtualSystemDescriptionEntry*> skipped = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM);
+ std::list<VirtualSystemDescriptionEntry*>::const_iterator itSkipped = skipped.begin();
+ while (itSkipped != skipped.end())
+ {
+ (*itSkipped)->skipIt = true;
+ ++itSkipped;
+ }
+
+ //remove all disks from the VirtualSystemDescription exept one
+ skipped = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
+ itSkipped = skipped.begin();
+
+ Utf8Str strBootLocation;
+ while (itSkipped != skipped.end())
+ {
+ if (strBootLocation.isEmpty())
+ strBootLocation = (*itSkipped)->strVBoxCurrent;
+ else
+ (*itSkipped)->skipIt = true;
+ ++itSkipped;
+ }
+
+ //just in case
+ if (vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage).empty())
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("There are no images to export to Cloud after preparation steps"));
+
+ /*
+ * Fills out the OCI settings
+ */
+ std::list<VirtualSystemDescriptionEntry*> profileName
+ = vsdescThis->i_findByType(VirtualSystemDescriptionType_CloudProfileName);
+ if (profileName.size() > 1)
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Cloud: More than one profile name was found."));
+ if (profileName.empty())
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Cloud: Profile name wasn't specified."));
+
+ if (profileName.front()->strVBoxCurrent.isEmpty())
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Cloud: Cloud user profile name is empty"));
+
+ LogRel(("profile name: %s\n", profileName.front()->strVBoxCurrent.c_str()));
+ }
+
+ // Create a progress object here otherwise Task won't be created successfully
+ HRESULT hrc = aProgress.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ if (aLocInfo.strProvider.equals("OCI"))
+ hrc = aProgress->init(mVirtualBox, static_cast<IAppliance *>(this),
+ Utf8Str(tr("Exporting VM to Cloud...")),
+ TRUE /* aCancelable */,
+ 5, // ULONG cOperations,
+ 1000, // ULONG ulTotalOperationsWeight,
+ Utf8Str(tr("Exporting VM to Cloud...")), // aFirstOperationDescription
+ 10); // ULONG ulFirstOperationWeight
+ else
+ hrc = setError(VBOX_E_NOT_SUPPORTED,
+ tr("Only \"OCI\" cloud provider is supported for now. \"%s\" isn't supported."),
+ aLocInfo.strProvider.c_str());
+ if (SUCCEEDED(hrc))
+ {
+ /* Initialize the worker task: */
+ TaskCloud *pTask = NULL;
+ try
+ {
+ pTask = new Appliance::TaskCloud(this, TaskCloud::Export, aLocInfo, aProgress);
+ }
+ catch (std::bad_alloc &)
+ {
+ pTask = NULL;
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ /* Kick off the worker task: */
+ hrc = pTask->createThread();
+ pTask = NULL;
+ }
+ }
+ }
+ return hrc;
+}
+
+HRESULT Appliance::i_writeOPCImpl(ovf::OVFVersion_T aFormat, const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress)
+{
+ RT_NOREF(aFormat);
+
+ /* Prepare progress object: */
+ HRESULT hrc;
+ try
+ {
+ hrc = i_setUpProgress(aProgress,
+ Utf8StrFmt(tr("Export appliance '%s'"), aLocInfo.strPath.c_str()),
+ aLocInfo.storageType == VFSType_File ? WriteFile : WriteS3);
+ }
+ catch (std::bad_alloc &) /* only Utf8StrFmt */
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ /* Create our worker task: */
+ TaskOPC *pTask = NULL;
+ try
+ {
+ pTask = new Appliance::TaskOPC(this, TaskOPC::Export, aLocInfo, aProgress);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /* Kick it off: */
+ hrc = pTask->createThread();
+ pTask = NULL;
+ }
+ return hrc;
+}
+
+
+/**
+ * Called from Appliance::i_writeFS() for creating a XML document for this
+ * Appliance.
+ *
+ * @param writeLock The current write lock.
+ * @param doc The xml document to fill.
+ * @param stack Structure for temporary private
+ * data shared with caller.
+ * @param strPath Path to the target OVF.
+ * instance for which to write XML.
+ * @param enFormat OVF format (0.9 or 1.0).
+ */
+void Appliance::i_buildXML(AutoWriteLockBase& writeLock,
+ xml::Document &doc,
+ XMLStack &stack,
+ const Utf8Str &strPath,
+ ovf::OVFVersion_T enFormat)
+{
+ xml::ElementNode *pelmRoot = doc.createRootElement("Envelope");
+
+ pelmRoot->setAttribute("ovf:version", enFormat == ovf::OVFVersion_2_0 ? "2.0"
+ : enFormat == ovf::OVFVersion_1_0 ? "1.0"
+ : "0.9");
+ pelmRoot->setAttribute("xml:lang", "en-US");
+
+ Utf8Str strNamespace;
+
+ if (enFormat == ovf::OVFVersion_0_9)
+ {
+ strNamespace = ovf::OVF09_URI_string;
+ }
+ else if (enFormat == ovf::OVFVersion_1_0)
+ {
+ strNamespace = ovf::OVF10_URI_string;
+ }
+ else
+ {
+ strNamespace = ovf::OVF20_URI_string;
+ }
+
+ pelmRoot->setAttribute("xmlns", strNamespace);
+ pelmRoot->setAttribute("xmlns:ovf", strNamespace);
+
+ // pelmRoot->setAttribute("xmlns:ovfstr", "http://schema.dmtf.org/ovf/strings/1");
+ pelmRoot->setAttribute("xmlns:rasd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData");
+ pelmRoot->setAttribute("xmlns:vssd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData");
+ pelmRoot->setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+ pelmRoot->setAttribute("xmlns:vbox", "http://www.virtualbox.org/ovf/machine");
+ // pelmRoot->setAttribute("xsi:schemaLocation", "http://schemas.dmtf.org/ovf/envelope/1 ../ovf-envelope.xsd");
+
+ if (enFormat == ovf::OVFVersion_2_0)
+ {
+ pelmRoot->setAttribute("xmlns:epasd",
+ "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_EthernetPortAllocationSettingData.xsd");
+ pelmRoot->setAttribute("xmlns:sasd",
+ "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_StorageAllocationSettingData.xsd");
+ }
+
+ // <Envelope>/<References>
+ xml::ElementNode *pelmReferences = pelmRoot->createChild("References"); // 0.9 and 1.0
+
+ /* <Envelope>/<DiskSection>:
+ <DiskSection>
+ <Info>List of the virtual disks used in the package</Info>
+ <Disk ovf:capacity="4294967296" ovf:diskId="lamp" ovf:format="..." ovf:populatedSize="1924967692"/>
+ </DiskSection> */
+ xml::ElementNode *pelmDiskSection;
+ if (enFormat == ovf::OVFVersion_0_9)
+ {
+ // <Section xsi:type="ovf:DiskSection_Type">
+ pelmDiskSection = pelmRoot->createChild("Section");
+ pelmDiskSection->setAttribute("xsi:type", "ovf:DiskSection_Type");
+ }
+ else
+ pelmDiskSection = pelmRoot->createChild("DiskSection");
+
+ xml::ElementNode *pelmDiskSectionInfo = pelmDiskSection->createChild("Info");
+ pelmDiskSectionInfo->addContent("List of the virtual disks used in the package");
+
+ /* <Envelope>/<NetworkSection>:
+ <NetworkSection>
+ <Info>Logical networks used in the package</Info>
+ <Network ovf:name="VM Network">
+ <Description>The network that the LAMP Service will be available on</Description>
+ </Network>
+ </NetworkSection> */
+ xml::ElementNode *pelmNetworkSection;
+ if (enFormat == ovf::OVFVersion_0_9)
+ {
+ // <Section xsi:type="ovf:NetworkSection_Type">
+ pelmNetworkSection = pelmRoot->createChild("Section");
+ pelmNetworkSection->setAttribute("xsi:type", "ovf:NetworkSection_Type");
+ }
+ else
+ pelmNetworkSection = pelmRoot->createChild("NetworkSection");
+
+ xml::ElementNode *pelmNetworkSectionInfo = pelmNetworkSection->createChild("Info");
+ pelmNetworkSectionInfo->addContent("Logical networks used in the package");
+
+ // and here come the virtual systems:
+
+ // write a collection if we have more than one virtual system _and_ we're
+ // writing OVF 1.0; otherwise fail since ovftool can't import more than
+ // one machine, it seems
+ xml::ElementNode *pelmToAddVirtualSystemsTo;
+ if (m->virtualSystemDescriptions.size() > 1)
+ {
+ if (enFormat == ovf::OVFVersion_0_9)
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Cannot export more than one virtual system with OVF 0.9, use OVF 1.0"));
+
+ pelmToAddVirtualSystemsTo = pelmRoot->createChild("VirtualSystemCollection");
+ pelmToAddVirtualSystemsTo->setAttribute("ovf:name", "ExportedVirtualBoxMachines"); // whatever
+ }
+ else
+ pelmToAddVirtualSystemsTo = pelmRoot; // add virtual system directly under root element
+
+ // this list receives pointers to the XML elements in the machine XML which
+ // might have UUIDs that need fixing after we know the UUIDs of the exported images
+ std::list<xml::ElementNode*> llElementsWithUuidAttributes;
+ uint32_t ulFile = 1;
+ /* Iterate through all virtual systems of that appliance */
+ for (list<ComObjPtr<VirtualSystemDescription> >::const_iterator
+ itV = m->virtualSystemDescriptions.begin();
+ itV != m->virtualSystemDescriptions.end();
+ ++itV)
+ {
+ ComObjPtr<VirtualSystemDescription> vsdescThis = *itV;
+ i_buildXMLForOneVirtualSystem(writeLock,
+ *pelmToAddVirtualSystemsTo,
+ &llElementsWithUuidAttributes,
+ vsdescThis,
+ enFormat,
+ stack); // disks and networks stack
+
+ list<Utf8Str> diskList;
+
+ for (list<Utf8Str>::const_iterator
+ itDisk = stack.mapDiskSequenceForOneVM.begin();
+ itDisk != stack.mapDiskSequenceForOneVM.end();
+ ++itDisk)
+ {
+ const Utf8Str &strDiskID = *itDisk;
+ const VirtualSystemDescriptionEntry *pDiskEntry = stack.mapDisks[strDiskID];
+
+ // source path: where the VBox image is
+ const Utf8Str &strSrcFilePath = pDiskEntry->strVBoxCurrent;
+ Bstr bstrSrcFilePath(strSrcFilePath);
+
+ //skip empty Medium. There are no information to add into section <References> or <DiskSection>
+ if (strSrcFilePath.isEmpty() ||
+ pDiskEntry->skipIt == true)
+ continue;
+
+ // Do NOT check here whether the file exists. FindMedium will figure
+ // that out, and filesystem-based tests are simply wrong in the
+ // general case (think of iSCSI).
+
+ // We need some info from the source disks
+ ComPtr<IMedium> pSourceDisk;
+ //DeviceType_T deviceType = DeviceType_HardDisk;// by default
+
+ Log(("Finding source disk \"%ls\"\n", bstrSrcFilePath.raw()));
+
+ HRESULT rc;
+
+ if (pDiskEntry->type == VirtualSystemDescriptionType_HardDiskImage)
+ {
+ rc = mVirtualBox->OpenMedium(bstrSrcFilePath.raw(),
+ DeviceType_HardDisk,
+ AccessMode_ReadWrite,
+ FALSE /* fForceNewUuid */,
+ pSourceDisk.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+ }
+ else if (pDiskEntry->type == VirtualSystemDescriptionType_CDROM)//may be, this is CD/DVD
+ {
+ rc = mVirtualBox->OpenMedium(bstrSrcFilePath.raw(),
+ DeviceType_DVD,
+ AccessMode_ReadOnly,
+ FALSE,
+ pSourceDisk.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ Bstr uuidSource;
+ rc = pSourceDisk->COMGETTER(Id)(uuidSource.asOutParam());
+ if (FAILED(rc)) throw rc;
+ Guid guidSource(uuidSource);
+
+ // output filename
+ const Utf8Str &strTargetFileNameOnly = pDiskEntry->strOvf;
+
+ // target path needs to be composed from where the output OVF is
+ Utf8Str strTargetFilePath(strPath);
+ strTargetFilePath.stripFilename();
+ strTargetFilePath.append("/");
+ strTargetFilePath.append(strTargetFileNameOnly);
+
+ // We are always exporting to VMDK stream optimized for now
+ //Bstr bstrSrcFormat = L"VMDK";//not used
+
+ diskList.push_back(strTargetFilePath);
+
+ LONG64 cbCapacity = 0; // size reported to guest
+ rc = pSourceDisk->COMGETTER(LogicalSize)(&cbCapacity);
+ if (FAILED(rc)) throw rc;
+ /// @todo r=poetzsch: wrong it is reported in bytes ...
+ // capacity is reported in megabytes, so...
+ //cbCapacity *= _1M;
+
+ Guid guidTarget; /* Creates a new uniq number for the target disk. */
+ guidTarget.create();
+
+ // now handle the XML for the disk:
+ Utf8StrFmt strFileRef("file%RI32", ulFile++);
+ // <File ovf:href="WindowsXpProfessional-disk1.vmdk" ovf:id="file1" ovf:size="1710381056"/>
+ xml::ElementNode *pelmFile = pelmReferences->createChild("File");
+ pelmFile->setAttribute("ovf:id", strFileRef);
+ pelmFile->setAttribute("ovf:href", strTargetFileNameOnly);
+ /// @todo the actual size is not available at this point of time,
+ // cause the disk will be compressed. The 1.0 standard says this is
+ // optional! 1.1 isn't fully clear if the "gzip" format is used.
+ // Need to be checked. */
+ // pelmFile->setAttribute("ovf:size", Utf8StrFmt("%RI64", cbFile).c_str());
+
+ // add disk to XML Disks section
+ // <Disk ovf:capacity="8589934592" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="..."/>
+ xml::ElementNode *pelmDisk = pelmDiskSection->createChild("Disk");
+ pelmDisk->setAttribute("ovf:capacity", Utf8StrFmt("%RI64", cbCapacity).c_str());
+ pelmDisk->setAttribute("ovf:diskId", strDiskID);
+ pelmDisk->setAttribute("ovf:fileRef", strFileRef);
+
+ if (pDiskEntry->type == VirtualSystemDescriptionType_HardDiskImage)//deviceType == DeviceType_HardDisk
+ {
+ pelmDisk->setAttribute("ovf:format",
+ (enFormat == ovf::OVFVersion_0_9)
+ ? "http://www.vmware.com/specifications/vmdk.html#sparse" // must be sparse or ovftoo
+ : "http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized"
+ // correct string as communicated to us by VMware (public bug #6612)
+ );
+ }
+ else //pDiskEntry->type == VirtualSystemDescriptionType_CDROM, deviceType == DeviceType_DVD
+ {
+ pelmDisk->setAttribute("ovf:format",
+ "http://www.ecma-international.org/publications/standards/Ecma-119.htm"
+ );
+ }
+
+ // add the UUID of the newly target image to the OVF disk element, but in the
+ // vbox: namespace since it's not part of the standard
+ pelmDisk->setAttribute("vbox:uuid", Utf8StrFmt("%RTuuid", guidTarget.raw()).c_str());
+
+ // now, we might have other XML elements from vbox:Machine pointing to this image,
+ // but those would refer to the UUID of the _source_ image (which we created the
+ // export image from); those UUIDs need to be fixed to the export image
+ Utf8Str strGuidSourceCurly = guidSource.toStringCurly();
+ for (std::list<xml::ElementNode*>::const_iterator
+ it = llElementsWithUuidAttributes.begin();
+ it != llElementsWithUuidAttributes.end();
+ ++it)
+ {
+ xml::ElementNode *pelmImage = *it;
+ Utf8Str strUUID;
+ pelmImage->getAttributeValue("uuid", strUUID);
+ if (strUUID == strGuidSourceCurly)
+ // overwrite existing uuid attribute
+ pelmImage->setAttribute("uuid", guidTarget.toStringCurly());
+ }
+ }
+ llElementsWithUuidAttributes.clear();
+ stack.mapDiskSequenceForOneVM.clear();
+ }
+
+ // now, fill in the network section we set up empty above according
+ // to the networks we found with the hardware items
+ for (map<Utf8Str, bool>::const_iterator
+ it = stack.mapNetworks.begin();
+ it != stack.mapNetworks.end();
+ ++it)
+ {
+ const Utf8Str &strNetwork = it->first;
+ xml::ElementNode *pelmNetwork = pelmNetworkSection->createChild("Network");
+ pelmNetwork->setAttribute("ovf:name", strNetwork.c_str());
+ pelmNetwork->createChild("Description")->addContent("Logical network used by this appliance.");
+ }
+
+}
+
+/**
+ * Called from Appliance::i_buildXML() for each virtual system (machine) that
+ * needs XML written out.
+ *
+ * @param writeLock The current write lock.
+ * @param elmToAddVirtualSystemsTo XML element to append elements to.
+ * @param pllElementsWithUuidAttributes out: list of XML elements produced here
+ * with UUID attributes for quick
+ * fixing by caller later
+ * @param vsdescThis The IVirtualSystemDescription
+ * instance for which to write XML.
+ * @param enFormat OVF format (0.9 or 1.0).
+ * @param stack Structure for temporary private
+ * data shared with caller.
+ */
+void Appliance::i_buildXMLForOneVirtualSystem(AutoWriteLockBase& writeLock,
+ xml::ElementNode &elmToAddVirtualSystemsTo,
+ std::list<xml::ElementNode*> *pllElementsWithUuidAttributes,
+ ComObjPtr<VirtualSystemDescription> &vsdescThis,
+ ovf::OVFVersion_T enFormat,
+ XMLStack &stack)
+{
+ LogFlowFunc(("ENTER appliance %p\n", this));
+
+ xml::ElementNode *pelmVirtualSystem;
+ if (enFormat == ovf::OVFVersion_0_9)
+ {
+ // <Section xsi:type="ovf:NetworkSection_Type">
+ pelmVirtualSystem = elmToAddVirtualSystemsTo.createChild("Content");
+ pelmVirtualSystem->setAttribute("xsi:type", "ovf:VirtualSystem_Type");
+ }
+ else
+ pelmVirtualSystem = elmToAddVirtualSystemsTo.createChild("VirtualSystem");
+
+ /*xml::ElementNode *pelmVirtualSystemInfo =*/ pelmVirtualSystem->createChild("Info")->addContent("A virtual machine");
+
+ std::list<VirtualSystemDescriptionEntry*> llName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
+ if (llName.empty())
+ throw setError(VBOX_E_NOT_SUPPORTED, tr("Missing VM name"));
+ Utf8Str &strVMName = llName.back()->strVBoxCurrent;
+ pelmVirtualSystem->setAttribute("ovf:id", strVMName);
+
+ // product info
+ std::list<VirtualSystemDescriptionEntry*> llProduct = vsdescThis->i_findByType(VirtualSystemDescriptionType_Product);
+ std::list<VirtualSystemDescriptionEntry*> llProductUrl = vsdescThis->i_findByType(VirtualSystemDescriptionType_ProductUrl);
+ std::list<VirtualSystemDescriptionEntry*> llVendor = vsdescThis->i_findByType(VirtualSystemDescriptionType_Vendor);
+ std::list<VirtualSystemDescriptionEntry*> llVendorUrl = vsdescThis->i_findByType(VirtualSystemDescriptionType_VendorUrl);
+ std::list<VirtualSystemDescriptionEntry*> llVersion = vsdescThis->i_findByType(VirtualSystemDescriptionType_Version);
+ bool fProduct = llProduct.size() && !llProduct.back()->strVBoxCurrent.isEmpty();
+ bool fProductUrl = llProductUrl.size() && !llProductUrl.back()->strVBoxCurrent.isEmpty();
+ bool fVendor = llVendor.size() && !llVendor.back()->strVBoxCurrent.isEmpty();
+ bool fVendorUrl = llVendorUrl.size() && !llVendorUrl.back()->strVBoxCurrent.isEmpty();
+ bool fVersion = llVersion.size() && !llVersion.back()->strVBoxCurrent.isEmpty();
+ if (fProduct || fProductUrl || fVendor || fVendorUrl || fVersion)
+ {
+ /* <Section ovf:required="false" xsi:type="ovf:ProductSection_Type">
+ <Info>Meta-information about the installed software</Info>
+ <Product>VAtest</Product>
+ <Vendor>SUN Microsystems</Vendor>
+ <Version>10.0</Version>
+ <ProductUrl>http://blogs.sun.com/VirtualGuru</ProductUrl>
+ <VendorUrl>http://www.sun.com</VendorUrl>
+ </Section> */
+ xml::ElementNode *pelmAnnotationSection;
+ if (enFormat == ovf::OVFVersion_0_9)
+ {
+ // <Section ovf:required="false" xsi:type="ovf:ProductSection_Type">
+ pelmAnnotationSection = pelmVirtualSystem->createChild("Section");
+ pelmAnnotationSection->setAttribute("xsi:type", "ovf:ProductSection_Type");
+ }
+ else
+ pelmAnnotationSection = pelmVirtualSystem->createChild("ProductSection");
+
+ pelmAnnotationSection->createChild("Info")->addContent("Meta-information about the installed software");
+ if (fProduct)
+ pelmAnnotationSection->createChild("Product")->addContent(llProduct.back()->strVBoxCurrent);
+ if (fVendor)
+ pelmAnnotationSection->createChild("Vendor")->addContent(llVendor.back()->strVBoxCurrent);
+ if (fVersion)
+ pelmAnnotationSection->createChild("Version")->addContent(llVersion.back()->strVBoxCurrent);
+ if (fProductUrl)
+ pelmAnnotationSection->createChild("ProductUrl")->addContent(llProductUrl.back()->strVBoxCurrent);
+ if (fVendorUrl)
+ pelmAnnotationSection->createChild("VendorUrl")->addContent(llVendorUrl.back()->strVBoxCurrent);
+ }
+
+ // description
+ std::list<VirtualSystemDescriptionEntry*> llDescription = vsdescThis->i_findByType(VirtualSystemDescriptionType_Description);
+ if (llDescription.size() &&
+ !llDescription.back()->strVBoxCurrent.isEmpty())
+ {
+ /* <Section ovf:required="false" xsi:type="ovf:AnnotationSection_Type">
+ <Info>A human-readable annotation</Info>
+ <Annotation>Plan 9</Annotation>
+ </Section> */
+ xml::ElementNode *pelmAnnotationSection;
+ if (enFormat == ovf::OVFVersion_0_9)
+ {
+ // <Section ovf:required="false" xsi:type="ovf:AnnotationSection_Type">
+ pelmAnnotationSection = pelmVirtualSystem->createChild("Section");
+ pelmAnnotationSection->setAttribute("xsi:type", "ovf:AnnotationSection_Type");
+ }
+ else
+ pelmAnnotationSection = pelmVirtualSystem->createChild("AnnotationSection");
+
+ pelmAnnotationSection->createChild("Info")->addContent("A human-readable annotation");
+ pelmAnnotationSection->createChild("Annotation")->addContent(llDescription.back()->strVBoxCurrent);
+ }
+
+ // license
+ std::list<VirtualSystemDescriptionEntry*> llLicense = vsdescThis->i_findByType(VirtualSystemDescriptionType_License);
+ if (llLicense.size() &&
+ !llLicense.back()->strVBoxCurrent.isEmpty())
+ {
+ /* <EulaSection>
+ <Info ovf:msgid="6">License agreement for the Virtual System.</Info>
+ <License ovf:msgid="1">License terms can go in here.</License>
+ </EulaSection> */
+ xml::ElementNode *pelmEulaSection;
+ if (enFormat == ovf::OVFVersion_0_9)
+ {
+ pelmEulaSection = pelmVirtualSystem->createChild("Section");
+ pelmEulaSection->setAttribute("xsi:type", "ovf:EulaSection_Type");
+ }
+ else
+ pelmEulaSection = pelmVirtualSystem->createChild("EulaSection");
+
+ pelmEulaSection->createChild("Info")->addContent("License agreement for the virtual system");
+ pelmEulaSection->createChild("License")->addContent(llLicense.back()->strVBoxCurrent);
+ }
+
+ // operating system
+ std::list<VirtualSystemDescriptionEntry*> llOS = vsdescThis->i_findByType(VirtualSystemDescriptionType_OS);
+ if (llOS.empty())
+ throw setError(VBOX_E_NOT_SUPPORTED, tr("Missing OS type"));
+ /* <OperatingSystemSection ovf:id="82">
+ <Info>Guest Operating System</Info>
+ <Description>Linux 2.6.x</Description>
+ </OperatingSystemSection> */
+ VirtualSystemDescriptionEntry *pvsdeOS = llOS.back();
+ xml::ElementNode *pelmOperatingSystemSection;
+ if (enFormat == ovf::OVFVersion_0_9)
+ {
+ pelmOperatingSystemSection = pelmVirtualSystem->createChild("Section");
+ pelmOperatingSystemSection->setAttribute("xsi:type", "ovf:OperatingSystemSection_Type");
+ }
+ else
+ pelmOperatingSystemSection = pelmVirtualSystem->createChild("OperatingSystemSection");
+
+ pelmOperatingSystemSection->setAttribute("ovf:id", pvsdeOS->strOvf);
+ pelmOperatingSystemSection->createChild("Info")->addContent("The kind of installed guest operating system");
+ Utf8Str strOSDesc;
+ convertCIMOSType2VBoxOSType(strOSDesc, (ovf::CIMOSType_T)pvsdeOS->strOvf.toInt32(), "");
+ pelmOperatingSystemSection->createChild("Description")->addContent(strOSDesc);
+ // add the VirtualBox ostype in a custom tag in a different namespace
+ xml::ElementNode *pelmVBoxOSType = pelmOperatingSystemSection->createChild("vbox:OSType");
+ pelmVBoxOSType->setAttribute("ovf:required", "false");
+ pelmVBoxOSType->addContent(pvsdeOS->strVBoxCurrent);
+
+ // <VirtualHardwareSection ovf:id="hw1" ovf:transport="iso">
+ xml::ElementNode *pelmVirtualHardwareSection;
+ if (enFormat == ovf::OVFVersion_0_9)
+ {
+ // <Section xsi:type="ovf:VirtualHardwareSection_Type">
+ pelmVirtualHardwareSection = pelmVirtualSystem->createChild("Section");
+ pelmVirtualHardwareSection->setAttribute("xsi:type", "ovf:VirtualHardwareSection_Type");
+ }
+ else
+ pelmVirtualHardwareSection = pelmVirtualSystem->createChild("VirtualHardwareSection");
+
+ pelmVirtualHardwareSection->createChild("Info")->addContent("Virtual hardware requirements for a virtual machine");
+
+ /* <System>
+ <vssd:Description>Description of the virtual hardware section.</vssd:Description>
+ <vssd:ElementName>vmware</vssd:ElementName>
+ <vssd:InstanceID>1</vssd:InstanceID>
+ <vssd:VirtualSystemIdentifier>MyLampService</vssd:VirtualSystemIdentifier>
+ <vssd:VirtualSystemType>vmx-4</vssd:VirtualSystemType>
+ </System> */
+ xml::ElementNode *pelmSystem = pelmVirtualHardwareSection->createChild("System");
+
+ pelmSystem->createChild("vssd:ElementName")->addContent("Virtual Hardware Family"); // required OVF 1.0
+
+ // <vssd:InstanceId>0</vssd:InstanceId>
+ if (enFormat == ovf::OVFVersion_0_9)
+ pelmSystem->createChild("vssd:InstanceId")->addContent("0");
+ else // capitalization changed...
+ pelmSystem->createChild("vssd:InstanceID")->addContent("0");
+
+ // <vssd:VirtualSystemIdentifier>VAtest</vssd:VirtualSystemIdentifier>
+ pelmSystem->createChild("vssd:VirtualSystemIdentifier")->addContent(strVMName);
+ // <vssd:VirtualSystemType>vmx-4</vssd:VirtualSystemType>
+ const char *pcszHardware = "virtualbox-2.2";
+ if (enFormat == ovf::OVFVersion_0_9)
+ // pretend to be vmware compatible then
+ pcszHardware = "vmx-6";
+ pelmSystem->createChild("vssd:VirtualSystemType")->addContent(pcszHardware);
+
+ // loop thru all description entries twice; once to write out all
+ // devices _except_ disk images, and a second time to assign the
+ // disk images; this is because disk images need to reference
+ // IDE controllers, and we can't know their instance IDs without
+ // assigning them first
+
+ uint32_t idIDEPrimaryController = 0;
+ int32_t lIDEPrimaryControllerIndex = 0;
+ uint32_t idIDESecondaryController = 0;
+ int32_t lIDESecondaryControllerIndex = 0;
+ uint32_t idSATAController = 0;
+ int32_t lSATAControllerIndex = 0;
+ uint32_t idSCSIController = 0;
+ int32_t lSCSIControllerIndex = 0;
+ uint32_t idVirtioSCSIController = 0;
+ int32_t lVirtioSCSIControllerIndex = 0;
+
+ uint32_t ulInstanceID = 1;
+
+ uint32_t cDVDs = 0;
+
+ for (size_t uLoop = 1; uLoop <= 2; ++uLoop)
+ {
+ int32_t lIndexThis = 0;
+ for (vector<VirtualSystemDescriptionEntry>::const_iterator
+ it = vsdescThis->m->maDescriptions.begin();
+ it != vsdescThis->m->maDescriptions.end();
+ ++it, ++lIndexThis)
+ {
+ const VirtualSystemDescriptionEntry &desc = *it;
+
+ LogFlowFunc(("Loop %u: handling description entry ulIndex=%u, type=%s, strRef=%s, strOvf=%s, strVBox=%s, strExtraConfig=%s\n",
+ uLoop,
+ desc.ulIndex,
+ ( desc.type == VirtualSystemDescriptionType_HardDiskControllerIDE ? "HardDiskControllerIDE"
+ : desc.type == VirtualSystemDescriptionType_HardDiskControllerSATA ? "HardDiskControllerSATA"
+ : desc.type == VirtualSystemDescriptionType_HardDiskControllerSCSI ? "HardDiskControllerSCSI"
+ : desc.type == VirtualSystemDescriptionType_HardDiskControllerSAS ? "HardDiskControllerSAS"
+ : desc.type == VirtualSystemDescriptionType_HardDiskImage ? "HardDiskImage"
+ : Utf8StrFmt("%d", desc.type).c_str()),
+ desc.strRef.c_str(),
+ desc.strOvf.c_str(),
+ desc.strVBoxCurrent.c_str(),
+ desc.strExtraConfigCurrent.c_str()));
+
+ ovf::ResourceType_T type = (ovf::ResourceType_T)0; // if this becomes != 0 then we do stuff
+ Utf8Str strResourceSubType;
+
+ Utf8Str strDescription; // results in <rasd:Description>...</rasd:Description> block
+ Utf8Str strCaption; // results in <rasd:Caption>...</rasd:Caption> block
+
+ uint32_t ulParent = 0;
+
+ int32_t lVirtualQuantity = -1;
+ Utf8Str strAllocationUnits;
+
+ int32_t lAddress = -1;
+ int32_t lBusNumber = -1;
+ int32_t lAddressOnParent = -1;
+
+ int32_t lAutomaticAllocation = -1; // 0 means "false", 1 means "true"
+ Utf8Str strConnection; // results in <rasd:Connection>...</rasd:Connection> block
+ Utf8Str strHostResource;
+
+ uint64_t uTemp;
+
+ ovf::VirtualHardwareItem vhi;
+ ovf::StorageItem si;
+ ovf::EthernetPortItem epi;
+
+ switch (desc.type)
+ {
+ case VirtualSystemDescriptionType_CPU:
+ /* <Item>
+ <rasd:Caption>1 virtual CPU</rasd:Caption>
+ <rasd:Description>Number of virtual CPUs</rasd:Description>
+ <rasd:ElementName>virtual CPU</rasd:ElementName>
+ <rasd:InstanceID>1</rasd:InstanceID>
+ <rasd:ResourceType>3</rasd:ResourceType>
+ <rasd:VirtualQuantity>1</rasd:VirtualQuantity>
+ </Item> */
+ if (uLoop == 1)
+ {
+ strDescription = "Number of virtual CPUs";
+ type = ovf::ResourceType_Processor; // 3
+ desc.strVBoxCurrent.toInt(uTemp);
+ lVirtualQuantity = (int32_t)uTemp;
+ strCaption = Utf8StrFmt("%d virtual CPU", lVirtualQuantity); // without this ovftool
+ // won't eat the item
+ }
+ break;
+
+ case VirtualSystemDescriptionType_Memory:
+ /* <Item>
+ <rasd:AllocationUnits>MegaBytes</rasd:AllocationUnits>
+ <rasd:Caption>256 MB of memory</rasd:Caption>
+ <rasd:Description>Memory Size</rasd:Description>
+ <rasd:ElementName>Memory</rasd:ElementName>
+ <rasd:InstanceID>2</rasd:InstanceID>
+ <rasd:ResourceType>4</rasd:ResourceType>
+ <rasd:VirtualQuantity>256</rasd:VirtualQuantity>
+ </Item> */
+ if (uLoop == 1)
+ {
+ strDescription = "Memory Size";
+ type = ovf::ResourceType_Memory; // 4
+ desc.strVBoxCurrent.toInt(uTemp);
+ lVirtualQuantity = (int32_t)(uTemp / _1M);
+ strAllocationUnits = "MegaBytes";
+ strCaption = Utf8StrFmt("%d MB of memory", lVirtualQuantity); // without this ovftool
+ // won't eat the item
+ }
+ break;
+
+ case VirtualSystemDescriptionType_HardDiskControllerIDE:
+ /* <Item>
+ <rasd:Caption>ideController1</rasd:Caption>
+ <rasd:Description>IDE Controller</rasd:Description>
+ <rasd:InstanceId>5</rasd:InstanceId>
+ <rasd:ResourceType>5</rasd:ResourceType>
+ <rasd:Address>1</rasd:Address>
+ <rasd:BusNumber>1</rasd:BusNumber>
+ </Item> */
+ if (uLoop == 1)
+ {
+ strDescription = "IDE Controller";
+ type = ovf::ResourceType_IDEController; // 5
+ strResourceSubType = desc.strVBoxCurrent;
+
+ if (!lIDEPrimaryControllerIndex)
+ {
+ // first IDE controller:
+ strCaption = "ideController0";
+ lAddress = 0;
+ lBusNumber = 0;
+ // remember this ID
+ idIDEPrimaryController = ulInstanceID;
+ lIDEPrimaryControllerIndex = lIndexThis;
+ }
+ else
+ {
+ // second IDE controller:
+ strCaption = "ideController1";
+ lAddress = 1;
+ lBusNumber = 1;
+ // remember this ID
+ idIDESecondaryController = ulInstanceID;
+ lIDESecondaryControllerIndex = lIndexThis;
+ }
+ }
+ break;
+
+ case VirtualSystemDescriptionType_HardDiskControllerSATA:
+ /* <Item>
+ <rasd:Caption>sataController0</rasd:Caption>
+ <rasd:Description>SATA Controller</rasd:Description>
+ <rasd:InstanceId>4</rasd:InstanceId>
+ <rasd:ResourceType>20</rasd:ResourceType>
+ <rasd:ResourceSubType>ahci</rasd:ResourceSubType>
+ <rasd:Address>0</rasd:Address>
+ <rasd:BusNumber>0</rasd:BusNumber>
+ </Item>
+ */
+ if (uLoop == 1)
+ {
+ strDescription = "SATA Controller";
+ strCaption = "sataController0";
+ type = ovf::ResourceType_OtherStorageDevice; // 20
+ // it seems that OVFTool always writes these two, and since we can only
+ // have one SATA controller, we'll use this as well
+ lAddress = 0;
+ lBusNumber = 0;
+
+ if ( desc.strVBoxCurrent.isEmpty() // AHCI is the default in VirtualBox
+ || (!desc.strVBoxCurrent.compare("ahci", Utf8Str::CaseInsensitive))
+ )
+ strResourceSubType = "AHCI";
+ else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Invalid config string \"%s\" in SATA controller"), desc.strVBoxCurrent.c_str());
+
+ // remember this ID
+ idSATAController = ulInstanceID;
+ lSATAControllerIndex = lIndexThis;
+ }
+ break;
+
+ case VirtualSystemDescriptionType_HardDiskControllerSCSI:
+ case VirtualSystemDescriptionType_HardDiskControllerSAS:
+ /* <Item>
+ <rasd:Caption>scsiController0</rasd:Caption>
+ <rasd:Description>SCSI Controller</rasd:Description>
+ <rasd:InstanceId>4</rasd:InstanceId>
+ <rasd:ResourceType>6</rasd:ResourceType>
+ <rasd:ResourceSubType>buslogic</rasd:ResourceSubType>
+ <rasd:Address>0</rasd:Address>
+ <rasd:BusNumber>0</rasd:BusNumber>
+ </Item>
+ */
+ if (uLoop == 1)
+ {
+ strDescription = "SCSI Controller";
+ strCaption = "scsiController0";
+ type = ovf::ResourceType_ParallelSCSIHBA; // 6
+ // it seems that OVFTool always writes these two, and since we can only
+ // have one SATA controller, we'll use this as well
+ lAddress = 0;
+ lBusNumber = 0;
+
+ if ( desc.strVBoxCurrent.isEmpty() // LsiLogic is the default in VirtualBox
+ || (!desc.strVBoxCurrent.compare("lsilogic", Utf8Str::CaseInsensitive))
+ )
+ strResourceSubType = "lsilogic";
+ else if (!desc.strVBoxCurrent.compare("buslogic", Utf8Str::CaseInsensitive))
+ strResourceSubType = "buslogic";
+ else if (!desc.strVBoxCurrent.compare("lsilogicsas", Utf8Str::CaseInsensitive))
+ strResourceSubType = "lsilogicsas";
+ else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Invalid config string \"%s\" in SCSI/SAS controller"),
+ desc.strVBoxCurrent.c_str());
+
+ // remember this ID
+ idSCSIController = ulInstanceID;
+ lSCSIControllerIndex = lIndexThis;
+ }
+ break;
+
+
+ case VirtualSystemDescriptionType_HardDiskControllerVirtioSCSI:
+ /* <Item>
+ <rasd:Caption>VirtioSCSIController0</rasd:Caption>
+ <rasd:Description>VirtioSCSI Controller</rasd:Description>
+ <rasd:InstanceId>4</rasd:InstanceId>
+ <rasd:ResourceType>20</rasd:ResourceType>
+ <rasd:Address>0</rasd:Address>
+ <rasd:BusNumber>0</rasd:BusNumber>
+ </Item>
+ */
+ if (uLoop == 1)
+ {
+ strDescription = "VirtioSCSI Controller";
+ strCaption = "virtioSCSIController0";
+ type = ovf::ResourceType_OtherStorageDevice; // 20
+ lAddress = 0;
+ lBusNumber = 0;
+ strResourceSubType = "VirtioSCSI";
+ // remember this ID
+ idVirtioSCSIController = ulInstanceID;
+ lVirtioSCSIControllerIndex = lIndexThis;
+ }
+ break;
+
+ case VirtualSystemDescriptionType_HardDiskImage:
+ /* <Item>
+ <rasd:Caption>disk1</rasd:Caption>
+ <rasd:InstanceId>8</rasd:InstanceId>
+ <rasd:ResourceType>17</rasd:ResourceType>
+ <rasd:HostResource>/disk/vmdisk1</rasd:HostResource>
+ <rasd:Parent>4</rasd:Parent>
+ <rasd:AddressOnParent>0</rasd:AddressOnParent>
+ </Item> */
+ if (uLoop == 2)
+ {
+ uint32_t cDisks = (uint32_t)stack.mapDisks.size();
+ Utf8Str strDiskID = Utf8StrFmt("vmdisk%RI32", ++cDisks);
+
+ strDescription = "Disk Image";
+ strCaption = Utf8StrFmt("disk%RI32", cDisks); // this is not used for anything else
+ type = ovf::ResourceType_HardDisk; // 17
+
+ // the following references the "<Disks>" XML block
+ strHostResource = Utf8StrFmt("/disk/%s", strDiskID.c_str());
+
+ // controller=<index>;channel=<c>
+ size_t pos1 = desc.strExtraConfigCurrent.find("controller=");
+ size_t pos2 = desc.strExtraConfigCurrent.find("channel=");
+ int32_t lControllerIndex = -1;
+ if (pos1 != Utf8Str::npos)
+ {
+ RTStrToInt32Ex(desc.strExtraConfigCurrent.c_str() + pos1 + 11, NULL, 0, &lControllerIndex);
+ if (lControllerIndex == lIDEPrimaryControllerIndex)
+ ulParent = idIDEPrimaryController;
+ else if (lControllerIndex == lIDESecondaryControllerIndex)
+ ulParent = idIDESecondaryController;
+ else if (lControllerIndex == lSCSIControllerIndex)
+ ulParent = idSCSIController;
+ else if (lControllerIndex == lSATAControllerIndex)
+ ulParent = idSATAController;
+ else if (lControllerIndex == lVirtioSCSIControllerIndex)
+ ulParent = idVirtioSCSIController;
+ }
+ if (pos2 != Utf8Str::npos)
+ RTStrToInt32Ex(desc.strExtraConfigCurrent.c_str() + pos2 + 8, NULL, 0, &lAddressOnParent);
+
+ LogFlowFunc(("HardDiskImage details: pos1=%d, pos2=%d, lControllerIndex=%d, lIDEPrimaryControllerIndex=%d, lIDESecondaryControllerIndex=%d, ulParent=%d, lAddressOnParent=%d\n",
+ pos1, pos2, lControllerIndex, lIDEPrimaryControllerIndex, lIDESecondaryControllerIndex,
+ ulParent, lAddressOnParent));
+
+ if ( !ulParent
+ || lAddressOnParent == -1
+ )
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Missing or bad extra config string in hard disk image: \"%s\""),
+ desc.strExtraConfigCurrent.c_str());
+
+ stack.mapDisks[strDiskID] = &desc;
+
+ //use the list stack.mapDiskSequence where the disks go as the "VirtualSystem" should be placed
+ //in the OVF description file.
+ stack.mapDiskSequence.push_back(strDiskID);
+ stack.mapDiskSequenceForOneVM.push_back(strDiskID);
+ }
+ break;
+
+ case VirtualSystemDescriptionType_Floppy:
+ if (uLoop == 1)
+ {
+ strDescription = "Floppy Drive";
+ strCaption = "floppy0"; // this is what OVFTool writes
+ type = ovf::ResourceType_FloppyDrive; // 14
+ lAutomaticAllocation = 0;
+ lAddressOnParent = 0; // this is what OVFTool writes
+ }
+ break;
+
+ case VirtualSystemDescriptionType_CDROM:
+ /* <Item>
+ <rasd:Caption>cdrom1</rasd:Caption>
+ <rasd:InstanceId>8</rasd:InstanceId>
+ <rasd:ResourceType>15</rasd:ResourceType>
+ <rasd:HostResource>/disk/cdrom1</rasd:HostResource>
+ <rasd:Parent>4</rasd:Parent>
+ <rasd:AddressOnParent>0</rasd:AddressOnParent>
+ </Item> */
+ if (uLoop == 2)
+ {
+ uint32_t cDisks = (uint32_t)stack.mapDisks.size();
+ Utf8Str strDiskID = Utf8StrFmt("iso%RI32", ++cDisks);
+ ++cDVDs;
+ strDescription = "CD-ROM Drive";
+ strCaption = Utf8StrFmt("cdrom%RI32", cDVDs); // OVFTool starts with 1
+ type = ovf::ResourceType_CDDrive; // 15
+ lAutomaticAllocation = 1;
+
+ //skip empty Medium. There are no information to add into section <References> or <DiskSection>
+ if (desc.strVBoxCurrent.isNotEmpty() &&
+ desc.skipIt == false)
+ {
+ // the following references the "<Disks>" XML block
+ strHostResource = Utf8StrFmt("/disk/%s", strDiskID.c_str());
+ }
+
+ // controller=<index>;channel=<c>
+ size_t pos1 = desc.strExtraConfigCurrent.find("controller=");
+ size_t pos2 = desc.strExtraConfigCurrent.find("channel=");
+ int32_t lControllerIndex = -1;
+ if (pos1 != Utf8Str::npos)
+ {
+ RTStrToInt32Ex(desc.strExtraConfigCurrent.c_str() + pos1 + 11, NULL, 0, &lControllerIndex);
+ if (lControllerIndex == lIDEPrimaryControllerIndex)
+ ulParent = idIDEPrimaryController;
+ else if (lControllerIndex == lIDESecondaryControllerIndex)
+ ulParent = idIDESecondaryController;
+ else if (lControllerIndex == lSCSIControllerIndex)
+ ulParent = idSCSIController;
+ else if (lControllerIndex == lSATAControllerIndex)
+ ulParent = idSATAController;
+ }
+ if (pos2 != Utf8Str::npos)
+ RTStrToInt32Ex(desc.strExtraConfigCurrent.c_str() + pos2 + 8, NULL, 0, &lAddressOnParent);
+
+ LogFlowFunc(("DVD drive details: pos1=%d, pos2=%d, lControllerIndex=%d, lIDEPrimaryControllerIndex=%d, lIDESecondaryControllerIndex=%d, ulParent=%d, lAddressOnParent=%d\n",
+ pos1, pos2, lControllerIndex, lIDEPrimaryControllerIndex,
+ lIDESecondaryControllerIndex, ulParent, lAddressOnParent));
+
+ if ( !ulParent
+ || lAddressOnParent == -1
+ )
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Missing or bad extra config string in DVD drive medium: \"%s\""),
+ desc.strExtraConfigCurrent.c_str());
+
+ stack.mapDisks[strDiskID] = &desc;
+
+ //use the list stack.mapDiskSequence where the disks go as the "VirtualSystem" should be placed
+ //in the OVF description file.
+ stack.mapDiskSequence.push_back(strDiskID);
+ stack.mapDiskSequenceForOneVM.push_back(strDiskID);
+ // there is no DVD drive map to update because it is
+ // handled completely with this entry.
+ }
+ break;
+
+ case VirtualSystemDescriptionType_NetworkAdapter:
+ /* <Item>
+ <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+ <rasd:Caption>Ethernet adapter on 'VM Network'</rasd:Caption>
+ <rasd:Connection>VM Network</rasd:Connection>
+ <rasd:ElementName>VM network</rasd:ElementName>
+ <rasd:InstanceID>3</rasd:InstanceID>
+ <rasd:ResourceType>10</rasd:ResourceType>
+ </Item> */
+ if (uLoop == 2)
+ {
+ lAutomaticAllocation = 1;
+ strCaption = Utf8StrFmt("Ethernet adapter on '%s'", desc.strOvf.c_str());
+ type = ovf::ResourceType_EthernetAdapter; // 10
+ /* Set the hardware type to something useful.
+ * To be compatible with vmware & others we set
+ * PCNet32 for our PCNet types & E1000 for the
+ * E1000 cards. */
+ switch (desc.strVBoxCurrent.toInt32())
+ {
+ case NetworkAdapterType_Am79C970A:
+ case NetworkAdapterType_Am79C973: strResourceSubType = "PCNet32"; break;
+#ifdef VBOX_WITH_E1000
+ case NetworkAdapterType_I82540EM:
+ case NetworkAdapterType_I82545EM:
+ case NetworkAdapterType_I82543GC: strResourceSubType = "E1000"; break;
+#endif /* VBOX_WITH_E1000 */
+ }
+ strConnection = desc.strOvf;
+
+ stack.mapNetworks[desc.strOvf] = true;
+ }
+ break;
+
+ case VirtualSystemDescriptionType_USBController:
+ /* <Item ovf:required="false">
+ <rasd:Caption>usb</rasd:Caption>
+ <rasd:Description>USB Controller</rasd:Description>
+ <rasd:InstanceId>3</rasd:InstanceId>
+ <rasd:ResourceType>23</rasd:ResourceType>
+ <rasd:Address>0</rasd:Address>
+ <rasd:BusNumber>0</rasd:BusNumber>
+ </Item> */
+ if (uLoop == 1)
+ {
+ strDescription = "USB Controller";
+ strCaption = "usb";
+ type = ovf::ResourceType_USBController; // 23
+ lAddress = 0; // this is what OVFTool writes
+ lBusNumber = 0; // this is what OVFTool writes
+ }
+ break;
+
+ case VirtualSystemDescriptionType_SoundCard:
+ /* <Item ovf:required="false">
+ <rasd:Caption>sound</rasd:Caption>
+ <rasd:Description>Sound Card</rasd:Description>
+ <rasd:InstanceId>10</rasd:InstanceId>
+ <rasd:ResourceType>35</rasd:ResourceType>
+ <rasd:ResourceSubType>ensoniq1371</rasd:ResourceSubType>
+ <rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
+ <rasd:AddressOnParent>3</rasd:AddressOnParent>
+ </Item> */
+ if (uLoop == 1)
+ {
+ strDescription = "Sound Card";
+ strCaption = "sound";
+ type = ovf::ResourceType_SoundCard; // 35
+ strResourceSubType = desc.strOvf; // e.g. ensoniq1371
+ lAutomaticAllocation = 0;
+ lAddressOnParent = 3; // what gives? this is what OVFTool writes
+ }
+ break;
+
+ default: break; /* Shut up MSC. */
+ }
+
+ if (type)
+ {
+ xml::ElementNode *pItem;
+ xml::ElementNode *pItemHelper;
+ RTCString itemElement;
+ RTCString itemElementHelper;
+
+ if (enFormat == ovf::OVFVersion_2_0)
+ {
+ if(uLoop == 2)
+ {
+ if (desc.type == VirtualSystemDescriptionType_NetworkAdapter)
+ {
+ itemElement = "epasd:";
+ pItem = pelmVirtualHardwareSection->createChild("EthernetPortItem");
+ }
+ else if (desc.type == VirtualSystemDescriptionType_CDROM ||
+ desc.type == VirtualSystemDescriptionType_HardDiskImage)
+ {
+ itemElement = "sasd:";
+ pItem = pelmVirtualHardwareSection->createChild("StorageItem");
+ }
+ else
+ pItem = NULL;
+ }
+ else
+ {
+ itemElement = "rasd:";
+ pItem = pelmVirtualHardwareSection->createChild("Item");
+ }
+ }
+ else
+ {
+ itemElement = "rasd:";
+ pItem = pelmVirtualHardwareSection->createChild("Item");
+ }
+
+ // NOTE: DO NOT CHANGE THE ORDER of these items! The OVF standards prescribes that
+ // the elements from the rasd: namespace must be sorted by letter, and VMware
+ // actually requires this as well (see public bug #6612)
+
+ if (lAddress != -1)
+ {
+ //pItem->createChild("rasd:Address")->addContent(Utf8StrFmt("%d", lAddress));
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("Address").c_str());
+ pItemHelper->addContent(Utf8StrFmt("%d", lAddress));
+ }
+
+ if (lAddressOnParent != -1)
+ {
+ //pItem->createChild("rasd:AddressOnParent")->addContent(Utf8StrFmt("%d", lAddressOnParent));
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("AddressOnParent").c_str());
+ pItemHelper->addContent(Utf8StrFmt("%d", lAddressOnParent));
+ }
+
+ if (!strAllocationUnits.isEmpty())
+ {
+ //pItem->createChild("rasd:AllocationUnits")->addContent(strAllocationUnits);
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("AllocationUnits").c_str());
+ pItemHelper->addContent(strAllocationUnits);
+ }
+
+ if (lAutomaticAllocation != -1)
+ {
+ //pItem->createChild("rasd:AutomaticAllocation")->addContent( (lAutomaticAllocation) ? "true" : "false" );
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("AutomaticAllocation").c_str());
+ pItemHelper->addContent((lAutomaticAllocation) ? "true" : "false" );
+ }
+
+ if (lBusNumber != -1)
+ {
+ if (enFormat == ovf::OVFVersion_0_9)
+ {
+ // BusNumber is invalid OVF 1.0 so only write it in 0.9 mode for OVFTool
+ //pItem->createChild("rasd:BusNumber")->addContent(Utf8StrFmt("%d", lBusNumber));
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("BusNumber").c_str());
+ pItemHelper->addContent(Utf8StrFmt("%d", lBusNumber));
+ }
+ }
+
+ if (!strCaption.isEmpty())
+ {
+ //pItem->createChild("rasd:Caption")->addContent(strCaption);
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("Caption").c_str());
+ pItemHelper->addContent(strCaption);
+ }
+
+ if (!strConnection.isEmpty())
+ {
+ //pItem->createChild("rasd:Connection")->addContent(strConnection);
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("Connection").c_str());
+ pItemHelper->addContent(strConnection);
+ }
+
+ if (!strDescription.isEmpty())
+ {
+ //pItem->createChild("rasd:Description")->addContent(strDescription);
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("Description").c_str());
+ pItemHelper->addContent(strDescription);
+ }
+
+ if (!strCaption.isEmpty())
+ {
+ if (enFormat == ovf::OVFVersion_1_0)
+ {
+ //pItem->createChild("rasd:ElementName")->addContent(strCaption);
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("ElementName").c_str());
+ pItemHelper->addContent(strCaption);
+ }
+ }
+
+ if (!strHostResource.isEmpty())
+ {
+ //pItem->createChild("rasd:HostResource")->addContent(strHostResource);
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("HostResource").c_str());
+ pItemHelper->addContent(strHostResource);
+ }
+
+ {
+ // <rasd:InstanceID>1</rasd:InstanceID>
+ itemElementHelper = itemElement;
+ if (enFormat == ovf::OVFVersion_0_9)
+ //pelmInstanceID = pItem->createChild("rasd:InstanceId");
+ pItemHelper = pItem->createChild(itemElementHelper.append("InstanceId").c_str());
+ else
+ //pelmInstanceID = pItem->createChild("rasd:InstanceID"); // capitalization changed...
+ pItemHelper = pItem->createChild(itemElementHelper.append("InstanceID").c_str());
+
+ pItemHelper->addContent(Utf8StrFmt("%d", ulInstanceID++));
+ }
+
+ if (ulParent)
+ {
+ //pItem->createChild("rasd:Parent")->addContent(Utf8StrFmt("%d", ulParent));
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("Parent").c_str());
+ pItemHelper->addContent(Utf8StrFmt("%d", ulParent));
+ }
+
+ if (!strResourceSubType.isEmpty())
+ {
+ //pItem->createChild("rasd:ResourceSubType")->addContent(strResourceSubType);
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("ResourceSubType").c_str());
+ pItemHelper->addContent(strResourceSubType);
+ }
+
+ {
+ // <rasd:ResourceType>3</rasd:ResourceType>
+ //pItem->createChild("rasd:ResourceType")->addContent(Utf8StrFmt("%d", type));
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("ResourceType").c_str());
+ pItemHelper->addContent(Utf8StrFmt("%d", type));
+ }
+
+ // <rasd:VirtualQuantity>1</rasd:VirtualQuantity>
+ if (lVirtualQuantity != -1)
+ {
+ //pItem->createChild("rasd:VirtualQuantity")->addContent(Utf8StrFmt("%d", lVirtualQuantity));
+ itemElementHelper = itemElement;
+ pItemHelper = pItem->createChild(itemElementHelper.append("VirtualQuantity").c_str());
+ pItemHelper->addContent(Utf8StrFmt("%d", lVirtualQuantity));
+ }
+ }
+ }
+ } // for (size_t uLoop = 1; uLoop <= 2; ++uLoop)
+
+ // now that we're done with the official OVF <Item> tags under <VirtualSystem>, write out VirtualBox XML
+ // under the vbox: namespace
+ xml::ElementNode *pelmVBoxMachine = pelmVirtualSystem->createChild("vbox:Machine");
+ // ovf:required="false" tells other OVF parsers that they can ignore this thing
+ pelmVBoxMachine->setAttribute("ovf:required", "false");
+ // ovf:Info element is required or VMware will bail out on the vbox:Machine element
+ pelmVBoxMachine->createChild("ovf:Info")->addContent("Complete VirtualBox machine configuration in VirtualBox format");
+
+ // create an empty machine config
+ // use the same settings version as the current VM settings file
+ settings::MachineConfigFile *pConfig = new settings::MachineConfigFile(&vsdescThis->m->pMachine->i_getSettingsFileFull());
+
+ writeLock.release();
+ try
+ {
+ AutoWriteLock machineLock(vsdescThis->m->pMachine COMMA_LOCKVAL_SRC_POS);
+ // fill the machine config
+ vsdescThis->m->pMachine->i_copyMachineDataToSettings(*pConfig);
+ pConfig->machineUserData.strName = strVMName;
+
+ // Apply export tweaks to machine settings
+ bool fStripAllMACs = m->optListExport.contains(ExportOptions_StripAllMACs);
+ bool fStripAllNonNATMACs = m->optListExport.contains(ExportOptions_StripAllNonNATMACs);
+ if (fStripAllMACs || fStripAllNonNATMACs)
+ {
+ for (settings::NetworkAdaptersList::iterator
+ it = pConfig->hardwareMachine.llNetworkAdapters.begin();
+ it != pConfig->hardwareMachine.llNetworkAdapters.end();
+ ++it)
+ {
+ settings::NetworkAdapter &nic = *it;
+ if (fStripAllMACs || (fStripAllNonNATMACs && nic.mode != NetworkAttachmentType_NAT))
+ nic.strMACAddress.setNull();
+ }
+ }
+
+ // write the machine config to the vbox:Machine element
+ pConfig->buildMachineXML(*pelmVBoxMachine,
+ settings::MachineConfigFile::BuildMachineXML_WriteVBoxVersionAttribute
+ /*| settings::MachineConfigFile::BuildMachineXML_SkipRemovableMedia*/
+ | settings::MachineConfigFile::BuildMachineXML_SuppressSavedState,
+ // but not BuildMachineXML_IncludeSnapshots nor BuildMachineXML_MediaRegistry
+ pllElementsWithUuidAttributes);
+ delete pConfig;
+ }
+ catch (...)
+ {
+ writeLock.acquire();
+ delete pConfig;
+ throw;
+ }
+ writeLock.acquire();
+}
+
+/**
+ * Actual worker code for writing out OVF/OVA to disk. This is called from Appliance::taskThreadWriteOVF()
+ * and therefore runs on the OVF/OVA write worker thread.
+ *
+ * This runs in one context:
+ *
+ * 1) in a first worker thread; in that case, Appliance::Write() called Appliance::i_writeImpl();
+ *
+ * @param pTask
+ * @return
+ */
+HRESULT Appliance::i_writeFS(TaskOVF *pTask)
+{
+ LogFlowFuncEnter();
+ LogFlowFunc(("ENTER appliance %p\n", this));
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ HRESULT rc = S_OK;
+
+ // Lock the media tree early to make sure nobody else tries to make changes
+ // to the tree. Also lock the IAppliance object for writing.
+ AutoMultiWriteLock2 multiLock(&mVirtualBox->i_getMediaTreeLockHandle(), this->lockHandle() COMMA_LOCKVAL_SRC_POS);
+ // Additional protect the IAppliance object, cause we leave the lock
+ // when starting the disk export and we don't won't block other
+ // callers on this lengthy operations.
+ m->state = ApplianceExporting;
+
+ if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
+ rc = i_writeFSOVF(pTask, multiLock);
+ else
+ rc = i_writeFSOVA(pTask, multiLock);
+
+ // reset the state so others can call methods again
+ m->state = ApplianceIdle;
+
+ LogFlowFunc(("rc=%Rhrc\n", rc));
+ LogFlowFuncLeave();
+ return rc;
+}
+
+HRESULT Appliance::i_writeFSOVF(TaskOVF *pTask, AutoWriteLockBase& writeLock)
+{
+ LogFlowFuncEnter();
+
+ /*
+ * Create write-to-dir file system stream for the target directory.
+ * This unifies the disk access with the TAR based OVA variant.
+ */
+ HRESULT hrc;
+ int vrc;
+ RTVFSFSSTREAM hVfsFss2Dir = NIL_RTVFSFSSTREAM;
+ try
+ {
+ Utf8Str strTargetDir(pTask->locInfo.strPath);
+ strTargetDir.stripFilename();
+ vrc = RTVfsFsStrmToNormalDir(strTargetDir.c_str(), 0 /*fFlags*/, &hVfsFss2Dir);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorVrc(vrc, tr("Failed to open directory '%s' (%Rrc)"), strTargetDir.c_str(), vrc);
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Join i_writeFSOVA. On failure, delete (undo) anything we might
+ * have written to the disk before failing.
+ */
+ hrc = i_writeFSImpl(pTask, writeLock, hVfsFss2Dir);
+ if (FAILED(hrc))
+ RTVfsFsStrmToDirUndo(hVfsFss2Dir);
+ RTVfsFsStrmRelease(hVfsFss2Dir);
+ }
+
+ LogFlowFuncLeave();
+ return hrc;
+}
+
+HRESULT Appliance::i_writeFSOVA(TaskOVF *pTask, AutoWriteLockBase &writeLock)
+{
+ LogFlowFuncEnter();
+
+ /*
+ * Open the output file and attach a TAR creator to it.
+ * The OVF 1.1.0 spec specifies the TAR format to be compatible with USTAR
+ * according to POSIX 1003.1-2008. We use the 1988 spec here as it's the
+ * only variant we currently implement.
+ */
+ HRESULT hrc;
+ RTVFSIOSTREAM hVfsIosTar;
+ int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
+ RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE,
+ &hVfsIosTar);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSFSSTREAM hVfsFssTar;
+ vrc = RTZipTarFsStreamToIoStream(hVfsIosTar, RTZIPTARFORMAT_USTAR, 0 /*fFlags*/, &hVfsFssTar);
+ RTVfsIoStrmRelease(hVfsIosTar);
+ if (RT_SUCCESS(vrc))
+ {
+ RTZipTarFsStreamSetFileMode(hVfsFssTar, 0660, 0440);
+ RTZipTarFsStreamSetOwner(hVfsFssTar, VBOX_VERSION_MAJOR,
+ pTask->enFormat == ovf::OVFVersion_0_9 ? "vboxovf09"
+ : pTask->enFormat == ovf::OVFVersion_1_0 ? "vboxovf10"
+ : pTask->enFormat == ovf::OVFVersion_2_0 ? "vboxovf20"
+ : "vboxovf");
+ RTZipTarFsStreamSetGroup(hVfsFssTar, VBOX_VERSION_MINOR,
+ Utf8StrFmt("vbox_v" RT_XSTR(VBOX_VERSION_MAJOR) "." RT_XSTR(VBOX_VERSION_MINOR) "."
+ RT_XSTR(VBOX_VERSION_BUILD) "r%RU32", RTBldCfgRevision()).c_str());
+
+ hrc = i_writeFSImpl(pTask, writeLock, hVfsFssTar);
+ RTVfsFsStrmRelease(hVfsFssTar);
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Failed create TAR creator for '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
+
+ /* Delete the OVA on failure. */
+ if (FAILED(hrc))
+ RTFileDelete(pTask->locInfo.strPath.c_str());
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Failed to open '%s' for writing (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
+
+ LogFlowFuncLeave();
+ return hrc;
+}
+
+/**
+ * Upload the image to the OCI Storage service, next import the
+ * uploaded image into internal OCI image format and launch an
+ * instance with this image in the OCI Compute service.
+ */
+HRESULT Appliance::i_exportCloudImpl(TaskCloud *pTask)
+{
+ LogFlowFuncEnter();
+
+ HRESULT hrc = S_OK;
+ ComPtr<ICloudProviderManager> cpm;
+ hrc = mVirtualBox->COMGETTER(CloudProviderManager)(cpm.asOutParam());
+ if (FAILED(hrc))
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("%s: Cloud provider manager object wasn't found"), __FUNCTION__);
+
+ Utf8Str strProviderName = pTask->locInfo.strProvider;
+ ComPtr<ICloudProvider> cloudProvider;
+ ComPtr<ICloudProfile> cloudProfile;
+ hrc = cpm->GetProviderByShortName(Bstr(strProviderName.c_str()).raw(), cloudProvider.asOutParam());
+
+ if (FAILED(hrc))
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("%s: Cloud provider object wasn't found"), __FUNCTION__);
+
+ ComPtr<IVirtualSystemDescription> vsd = m->virtualSystemDescriptions.front();
+
+ com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
+ com::SafeArray<BSTR> aRefs;
+ com::SafeArray<BSTR> aOvfValues;
+ com::SafeArray<BSTR> aVBoxValues;
+ com::SafeArray<BSTR> aExtraConfigValues;
+
+ hrc = vsd->GetDescriptionByType(VirtualSystemDescriptionType_CloudProfileName,
+ ComSafeArrayAsOutParam(retTypes),
+ ComSafeArrayAsOutParam(aRefs),
+ ComSafeArrayAsOutParam(aOvfValues),
+ ComSafeArrayAsOutParam(aVBoxValues),
+ ComSafeArrayAsOutParam(aExtraConfigValues));
+ if (FAILED(hrc))
+ return hrc;
+
+ Utf8Str profileName(aVBoxValues[0]);
+ if (profileName.isEmpty())
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("%s: Cloud user profile name wasn't found"), __FUNCTION__);
+
+ hrc = cloudProvider->GetProfileByName(aVBoxValues[0], cloudProfile.asOutParam());
+ if (FAILED(hrc))
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("%s: Cloud profile object wasn't found"), __FUNCTION__);
+
+ ComObjPtr<ICloudClient> cloudClient;
+ hrc = cloudProfile->CreateCloudClient(cloudClient.asOutParam());
+ if (FAILED(hrc))
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("%s: Cloud client object wasn't found"), __FUNCTION__);
+
+ if (m->virtualSystemDescriptions.size() == 1)
+ {
+ ComPtr<IVirtualBox> VBox(mVirtualBox);
+ hrc = cloudClient->ExportVM(m->virtualSystemDescriptions.front(), pTask->pProgress);
+ }
+ else
+ hrc = setErrorVrc(VERR_MISMATCH, tr("Export to Cloud isn't supported for more than one VM instance."));
+
+ LogFlowFuncLeave();
+ return hrc;
+}
+
+
+/**
+ * Writes the Oracle Public Cloud appliance.
+ *
+ * It expect raw disk images inside a gzipped tarball. We enable sparse files
+ * to save diskspace on the target host system.
+ */
+HRESULT Appliance::i_writeFSOPC(TaskOPC *pTask)
+{
+ LogFlowFuncEnter();
+ HRESULT hrc = S_OK;
+
+ // Lock the media tree early to make sure nobody else tries to make changes
+ // to the tree. Also lock the IAppliance object for writing.
+ AutoMultiWriteLock2 multiLock(&mVirtualBox->i_getMediaTreeLockHandle(), this->lockHandle() COMMA_LOCKVAL_SRC_POS);
+ // Additional protect the IAppliance object, cause we leave the lock
+ // when starting the disk export and we don't won't block other
+ // callers on this lengthy operations.
+ m->state = ApplianceExporting;
+
+ /*
+ * We're duplicating parts of i_writeFSImpl here because that's simpler
+ * and creates less spaghetti code.
+ */
+ std::list<Utf8Str> lstTarballs;
+
+ /*
+ * Use i_buildXML to build a stack of disk images. We don't care about the XML doc here.
+ */
+ XMLStack stack;
+ {
+ xml::Document doc;
+ i_buildXML(multiLock, doc, stack, pTask->locInfo.strPath, ovf::OVFVersion_2_0);
+ }
+
+ /*
+ * Process the disk images.
+ */
+ unsigned cTarballs = 0;
+ for (list<Utf8Str>::const_iterator it = stack.mapDiskSequence.begin();
+ it != stack.mapDiskSequence.end();
+ ++it)
+ {
+ const Utf8Str &strDiskID = *it;
+ const VirtualSystemDescriptionEntry *pDiskEntry = stack.mapDisks[strDiskID];
+ const Utf8Str &strSrcFilePath = pDiskEntry->strVBoxCurrent; // where the VBox image is
+
+ /*
+ * Some skipping.
+ */
+ if (pDiskEntry->skipIt)
+ continue;
+
+ /* Skip empty media (DVD-ROM, floppy). */
+ if (strSrcFilePath.isEmpty())
+ continue;
+
+ /* Only deal with harddisk and DVD-ROMs, skip any floppies for now. */
+ if ( pDiskEntry->type != VirtualSystemDescriptionType_HardDiskImage
+ && pDiskEntry->type != VirtualSystemDescriptionType_CDROM)
+ continue;
+
+ /*
+ * Locate the Medium object for this entry (by location/path).
+ */
+ Log(("Finding source disk \"%s\"\n", strSrcFilePath.c_str()));
+ ComObjPtr<Medium> ptrSourceDisk;
+ if (pDiskEntry->type == VirtualSystemDescriptionType_HardDiskImage)
+ hrc = mVirtualBox->i_findHardDiskByLocation(strSrcFilePath, true /*aSetError*/, &ptrSourceDisk);
+ else
+ hrc = mVirtualBox->i_findDVDOrFloppyImage(DeviceType_DVD, NULL /*aId*/, strSrcFilePath,
+ true /*aSetError*/, &ptrSourceDisk);
+ if (FAILED(hrc))
+ break;
+ if (strSrcFilePath.isEmpty())
+ continue;
+
+ /*
+ * Figure out the names.
+ */
+
+ /* The name inside the tarball. Replace the suffix of harddisk images with ".img". */
+ Utf8Str strInsideName = pDiskEntry->strOvf;
+ if (pDiskEntry->type == VirtualSystemDescriptionType_HardDiskImage)
+ strInsideName.stripSuffix().append(".img");
+
+ /* The first tarball we create uses the specified name. Subsequent
+ takes the name from the disk entry or something. */
+ Utf8Str strTarballPath = pTask->locInfo.strPath;
+ if (cTarballs > 0)
+ {
+ strTarballPath.stripFilename().append(RTPATH_SLASH_STR).append(pDiskEntry->strOvf);
+ const char *pszExt = RTPathSuffix(pDiskEntry->strOvf.c_str());
+ if (pszExt && pszExt[0] == '.' && pszExt[1] != '\0')
+ {
+ strTarballPath.stripSuffix();
+ if (pDiskEntry->type != VirtualSystemDescriptionType_HardDiskImage)
+ strTarballPath.append("_").append(&pszExt[1]);
+ }
+ strTarballPath.append(".tar.gz");
+ }
+ cTarballs++;
+
+ /*
+ * Create the tar output stream.
+ */
+ RTVFSIOSTREAM hVfsIosFile;
+ int vrc = RTVfsIoStrmOpenNormal(strTarballPath.c_str(),
+ RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE,
+ &hVfsIosFile);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hVfsIosGzip = NIL_RTVFSIOSTREAM;
+ vrc = RTZipGzipCompressIoStream(hVfsIosFile, 0 /*fFlags*/, 6 /*uLevel*/, &hVfsIosGzip);
+ RTVfsIoStrmRelease(hVfsIosFile);
+
+ /** @todo insert I/O thread here between gzip and the tar creator. Needs
+ * implementing. */
+
+ RTVFSFSSTREAM hVfsFssTar = NIL_RTVFSFSSTREAM;
+ if (RT_SUCCESS(vrc))
+ vrc = RTZipTarFsStreamToIoStream(hVfsIosGzip, RTZIPTARFORMAT_GNU, RTZIPTAR_C_SPARSE, &hVfsFssTar);
+ RTVfsIoStrmRelease(hVfsIosGzip);
+ if (RT_SUCCESS(vrc))
+ {
+ RTZipTarFsStreamSetFileMode(hVfsFssTar, 0660, 0440);
+ RTZipTarFsStreamSetOwner(hVfsFssTar, VBOX_VERSION_MAJOR, "vboxopc10");
+ RTZipTarFsStreamSetGroup(hVfsFssTar, VBOX_VERSION_MINOR,
+ Utf8StrFmt("vbox_v" RT_XSTR(VBOX_VERSION_MAJOR) "." RT_XSTR(VBOX_VERSION_MINOR) "."
+ RT_XSTR(VBOX_VERSION_BUILD) "r%RU32", RTBldCfgRevision()).c_str());
+
+ /*
+ * Let the Medium code do the heavy work.
+ *
+ * The exporting requests a lock on the media tree. So temporarily
+ * leave the appliance lock.
+ */
+ multiLock.release();
+
+ pTask->pProgress->SetNextOperation(BstrFmt(tr("Exporting to disk image '%Rbn'"), strTarballPath.c_str()).raw(),
+ pDiskEntry->ulSizeMB); // operation's weight, as set up
+ // with the IProgress originally
+ hrc = ptrSourceDisk->i_addRawToFss(strInsideName.c_str(), m->m_pSecretKeyStore, hVfsFssTar,
+ pTask->pProgress, true /*fSparse*/);
+
+ multiLock.acquire();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Complete and close the tarball.
+ */
+ vrc = RTVfsFsStrmEnd(hVfsFssTar);
+ RTVfsFsStrmRelease(hVfsFssTar);
+ hVfsFssTar = NIL_RTVFSFSSTREAM;
+ if (RT_SUCCESS(vrc))
+ {
+ /* Remember the tarball name for cleanup. */
+ try
+ {
+ lstTarballs.push_back(strTarballPath.c_str());
+ strTarballPath.setNull();
+ }
+ catch (std::bad_alloc &)
+ { hrc = E_OUTOFMEMORY; }
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Error completing TAR file '%s' (%Rrc)"), strTarballPath.c_str(), vrc);
+ }
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Failed to TAR creator instance for '%s' (%Rrc)"), strTarballPath.c_str(), vrc);
+
+ if (FAILED(hrc) && strTarballPath.isNotEmpty())
+ RTFileDelete(strTarballPath.c_str());
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Failed to create '%s' (%Rrc)"), strTarballPath.c_str(), vrc);
+ if (FAILED(hrc))
+ break;
+ }
+
+ /*
+ * Delete output files on failure.
+ */
+ if (FAILED(hrc))
+ for (list<Utf8Str>::const_iterator it = lstTarballs.begin(); it != lstTarballs.end(); ++it)
+ RTFileDelete(it->c_str());
+
+ // reset the state so others can call methods again
+ m->state = ApplianceIdle;
+
+ LogFlowFuncLeave();
+ return hrc;
+
+}
+
+HRESULT Appliance::i_writeFSImpl(TaskOVF *pTask, AutoWriteLockBase &writeLock, RTVFSFSSTREAM hVfsFssDst)
+{
+ LogFlowFuncEnter();
+
+ HRESULT rc = S_OK;
+ int vrc;
+ try
+ {
+ // the XML stack contains two maps for disks and networks, which allows us to
+ // a) have a list of unique disk names (to make sure the same disk name is only added once)
+ // and b) keep a list of all networks
+ XMLStack stack;
+ // Scope this to free the memory as soon as this is finished
+ {
+ /* Construct the OVF name. */
+ Utf8Str strOvfFile(pTask->locInfo.strPath);
+ strOvfFile.stripPath().stripSuffix().append(".ovf");
+
+ /* Render a valid ovf document into a memory buffer. The unknown
+ version upgrade relates to the OPC hack up in Appliance::write(). */
+ xml::Document doc;
+ i_buildXML(writeLock, doc, stack, pTask->locInfo.strPath,
+ pTask->enFormat != ovf::OVFVersion_unknown ? pTask->enFormat : ovf::OVFVersion_2_0);
+
+ void *pvBuf = NULL;
+ size_t cbSize = 0;
+ xml::XmlMemWriter writer;
+ writer.write(doc, &pvBuf, &cbSize);
+ if (RT_UNLIKELY(!pvBuf))
+ throw setError(VBOX_E_FILE_ERROR, tr("Could not create OVF file '%s'"), strOvfFile.c_str());
+
+ /* Write the ovf file to "disk". */
+ rc = i_writeBufferToFile(hVfsFssDst, strOvfFile.c_str(), pvBuf, cbSize);
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ // We need a proper format description
+ ComObjPtr<MediumFormat> formatTemp;
+
+ ComObjPtr<MediumFormat> format;
+ // Scope for the AutoReadLock
+ {
+ SystemProperties *pSysProps = mVirtualBox->i_getSystemProperties();
+ AutoReadLock propsLock(pSysProps COMMA_LOCKVAL_SRC_POS);
+ // We are always exporting to VMDK stream optimized for now
+ formatTemp = pSysProps->i_mediumFormatFromExtension("iso");
+
+ format = pSysProps->i_mediumFormat("VMDK");
+ if (format.isNull())
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Invalid medium storage format"));
+ }
+
+ // Finally, write out the disks!
+ //use the list stack.mapDiskSequence where the disks were put as the "VirtualSystem"s had been placed
+ //in the OVF description file. I.e. we have one "VirtualSystem" in the OVF file, we extract all disks
+ //attached to it. And these disks are stored in the stack.mapDiskSequence. Next we shift to the next
+ //"VirtualSystem" and repeat the operation.
+ //And here we go through the list and extract all disks in the same sequence
+ for (list<Utf8Str>::const_iterator
+ it = stack.mapDiskSequence.begin();
+ it != stack.mapDiskSequence.end();
+ ++it)
+ {
+ const Utf8Str &strDiskID = *it;
+ const VirtualSystemDescriptionEntry *pDiskEntry = stack.mapDisks[strDiskID];
+
+ // source path: where the VBox image is
+ const Utf8Str &strSrcFilePath = pDiskEntry->strVBoxCurrent;
+
+ //skip empty Medium. In common, It's may be empty CD/DVD
+ if (strSrcFilePath.isEmpty() ||
+ pDiskEntry->skipIt == true)
+ continue;
+
+ // Do NOT check here whether the file exists. findHardDisk will
+ // figure that out, and filesystem-based tests are simply wrong
+ // in the general case (think of iSCSI).
+
+ // clone the disk:
+ ComObjPtr<Medium> pSourceDisk;
+
+ Log(("Finding source disk \"%s\"\n", strSrcFilePath.c_str()));
+
+ if (pDiskEntry->type == VirtualSystemDescriptionType_HardDiskImage)
+ {
+ rc = mVirtualBox->i_findHardDiskByLocation(strSrcFilePath, true, &pSourceDisk);
+ if (FAILED(rc)) throw rc;
+ }
+ else//may be CD or DVD
+ {
+ rc = mVirtualBox->i_findDVDOrFloppyImage(DeviceType_DVD,
+ NULL,
+ strSrcFilePath,
+ true,
+ &pSourceDisk);
+ if (FAILED(rc)) throw rc;
+ }
+
+ Bstr uuidSource;
+ rc = pSourceDisk->COMGETTER(Id)(uuidSource.asOutParam());
+ if (FAILED(rc)) throw rc;
+ Guid guidSource(uuidSource);
+
+ // output filename
+ const Utf8Str &strTargetFileNameOnly = pDiskEntry->strOvf;
+
+ // target path needs to be composed from where the output OVF is
+ const Utf8Str &strTargetFilePath = strTargetFileNameOnly;
+
+ // The exporting requests a lock on the media tree. So leave our lock temporary.
+ writeLock.release();
+ try
+ {
+ // advance to the next operation
+ pTask->pProgress->SetNextOperation(BstrFmt(tr("Exporting to disk image '%s'"),
+ RTPathFilename(strTargetFilePath.c_str())).raw(),
+ pDiskEntry->ulSizeMB); // operation's weight, as set up
+ // with the IProgress originally
+
+ // create a flat copy of the source disk image
+ if (pDiskEntry->type == VirtualSystemDescriptionType_HardDiskImage)
+ {
+ /*
+ * Export a disk image.
+ */
+ /* For compressed VMDK fun, we let i_exportFile produce the image bytes. */
+ RTVFSIOSTREAM hVfsIosDst;
+ vrc = RTVfsFsStrmPushFile(hVfsFssDst, strTargetFilePath.c_str(), UINT64_MAX,
+ NULL /*paObjInfo*/, 0 /*cObjInfo*/, RTVFSFSSTRM_PUSH_F_STREAM, &hVfsIosDst);
+ if (RT_FAILURE(vrc))
+ throw setErrorVrc(vrc, tr("RTVfsFsStrmPushFile failed for '%s' (%Rrc)"), strTargetFilePath.c_str(), vrc);
+ hVfsIosDst = i_manifestSetupDigestCalculationForGivenIoStream(hVfsIosDst, strTargetFilePath.c_str(),
+ false /*fRead*/);
+ if (hVfsIosDst == NIL_RTVFSIOSTREAM)
+ throw setError(E_FAIL, "i_manifestSetupDigestCalculationForGivenIoStream(%s)", strTargetFilePath.c_str());
+
+ rc = pSourceDisk->i_exportFile(strTargetFilePath.c_str(),
+ format,
+ MediumVariant_VmdkStreamOptimized,
+ m->m_pSecretKeyStore,
+ hVfsIosDst,
+ pTask->pProgress);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ }
+ else
+ {
+ /*
+ * Copy CD/DVD/floppy image.
+ */
+ Assert(pDiskEntry->type == VirtualSystemDescriptionType_CDROM);
+ rc = pSourceDisk->i_addRawToFss(strTargetFilePath.c_str(), m->m_pSecretKeyStore, hVfsFssDst,
+ pTask->pProgress, false /*fSparse*/);
+ }
+ if (FAILED(rc)) throw rc;
+ }
+ catch (HRESULT rc3)
+ {
+ writeLock.acquire();
+ /// @todo file deletion on error? If not, we can remove that whole try/catch block.
+ throw rc3;
+ }
+ // Finished, lock again (so nobody mess around with the medium tree
+ // in the meantime)
+ writeLock.acquire();
+ }
+
+ if (m->fManifest)
+ {
+ // Create & write the manifest file
+ Utf8Str strMfFilePath = Utf8Str(pTask->locInfo.strPath).stripSuffix().append(".mf");
+ Utf8Str strMfFileName = Utf8Str(strMfFilePath).stripPath();
+ pTask->pProgress->SetNextOperation(BstrFmt(tr("Creating manifest file '%s'"), strMfFileName.c_str()).raw(),
+ m->ulWeightForManifestOperation); // operation's weight, as set up
+ // with the IProgress originally);
+ /* Create a memory I/O stream and write the manifest to it. */
+ RTVFSIOSTREAM hVfsIosManifest;
+ vrc = RTVfsMemIoStrmCreate(NIL_RTVFSIOSTREAM, _1K, &hVfsIosManifest);
+ if (RT_FAILURE(vrc))
+ throw setErrorVrc(vrc, tr("RTVfsMemIoStrmCreate failed (%Rrc)"), vrc);
+ if (m->hOurManifest != NIL_RTMANIFEST) /* In case it's empty. */
+ vrc = RTManifestWriteStandard(m->hOurManifest, hVfsIosManifest);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Rewind the stream and add it to the output. */
+ size_t cbIgnored;
+ vrc = RTVfsIoStrmReadAt(hVfsIosManifest, 0 /*offset*/, &cbIgnored, 0, true /*fBlocking*/, &cbIgnored);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSOBJ hVfsObjManifest = RTVfsObjFromIoStream(hVfsIosManifest);
+ vrc = RTVfsFsStrmAdd(hVfsFssDst, strMfFileName.c_str(), hVfsObjManifest, 0 /*fFlags*/);
+ if (RT_SUCCESS(vrc))
+ rc = S_OK;
+ else
+ rc = setErrorVrc(vrc, tr("RTVfsFsStrmAdd failed for the manifest (%Rrc)"), vrc);
+ }
+ else
+ rc = setErrorVrc(vrc, tr("RTManifestWriteStandard failed (%Rrc)"), vrc);
+ }
+ else
+ rc = setErrorVrc(vrc, tr("RTManifestWriteStandard failed (%Rrc)"), vrc);
+ RTVfsIoStrmRelease(hVfsIosManifest);
+ if (FAILED(rc))
+ throw rc;
+ }
+ }
+ catch (RTCError &x) // includes all XML exceptions
+ {
+ rc = setError(VBOX_E_FILE_ERROR,
+ x.what());
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ LogFlowFunc(("rc=%Rhrc\n", rc));
+ LogFlowFuncLeave();
+
+ return rc;
+}
+
+
+/**
+ * Writes a memory buffer to a file in the output file system stream.
+ *
+ * @returns COM status code.
+ * @param hVfsFssDst The file system stream to add the file to.
+ * @param pszFilename The file name (w/ path if desired).
+ * @param pvContent Pointer to buffer containing the file content.
+ * @param cbContent Size of the content.
+ */
+HRESULT Appliance::i_writeBufferToFile(RTVFSFSSTREAM hVfsFssDst, const char *pszFilename, const void *pvContent, size_t cbContent)
+{
+ /*
+ * Create a VFS file around the memory, converting it to a base VFS object handle.
+ */
+ HRESULT hrc;
+ RTVFSIOSTREAM hVfsIosSrc;
+ int vrc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pvContent, cbContent, &hVfsIosSrc);
+ if (RT_SUCCESS(vrc))
+ {
+ hVfsIosSrc = i_manifestSetupDigestCalculationForGivenIoStream(hVfsIosSrc, pszFilename);
+ AssertReturn(hVfsIosSrc != NIL_RTVFSIOSTREAM,
+ setErrorVrc(vrc, "i_manifestSetupDigestCalculationForGivenIoStream"));
+
+ RTVFSOBJ hVfsObj = RTVfsObjFromIoStream(hVfsIosSrc);
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ AssertReturn(hVfsObj != NIL_RTVFSOBJ, E_FAIL);
+
+ /*
+ * Add it to the stream.
+ */
+ vrc = RTVfsFsStrmAdd(hVfsFssDst, pszFilename, hVfsObj, 0);
+ RTVfsObjRelease(hVfsObj);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorVrc(vrc, tr("RTVfsFsStrmAdd failed for '%s' (%Rrc)"), pszFilename, vrc);
+ }
+ else
+ hrc = setErrorVrc(vrc, "RTVfsIoStrmFromBuffer");
+ return hrc;
+}
+
diff --git a/src/VBox/Main/src-server/ApplianceImplImport.cpp b/src/VBox/Main/src-server/ApplianceImplImport.cpp
new file mode 100644
index 00000000..9a914c70
--- /dev/null
+++ b/src/VBox/Main/src-server/ApplianceImplImport.cpp
@@ -0,0 +1,6170 @@
+/* $Id: ApplianceImplImport.cpp $ */
+/** @file
+ * IAppliance and IVirtualSystem COM class implementations.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_APPLIANCE
+#include <iprt/alloca.h>
+#include <iprt/path.h>
+#include <iprt/cpp/path.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/s3.h>
+#include <iprt/sha.h>
+#include <iprt/manifest.h>
+#include <iprt/tar.h>
+#include <iprt/zip.h>
+#include <iprt/stream.h>
+#include <iprt/crypto/digest.h>
+#include <iprt/crypto/pkix.h>
+#include <iprt/crypto/store.h>
+#include <iprt/crypto/x509.h>
+#include <iprt/rand.h>
+
+#include <VBox/vd.h>
+#include <VBox/com/array.h>
+
+#include "ApplianceImpl.h"
+#include "VirtualBoxImpl.h"
+#include "GuestOSTypeImpl.h"
+#include "ProgressImpl.h"
+#include "MachineImpl.h"
+#include "MediumImpl.h"
+#include "MediumFormatImpl.h"
+#include "SystemPropertiesImpl.h"
+#include "HostImpl.h"
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+#include "ApplianceImplPrivate.h"
+#include "CertificateImpl.h"
+#include "ovfreader.h"
+
+#include <VBox/param.h>
+#include <VBox/version.h>
+#include <VBox/settings.h>
+
+#include <set>
+
+using namespace std;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// IAppliance public methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Public method implementation. This opens the OVF with ovfreader.cpp.
+ * Thread implementation is in Appliance::readImpl().
+ *
+ * @param aFile File to read the appliance from.
+ * @param aProgress Progress object.
+ * @return
+ */
+HRESULT Appliance::read(const com::Utf8Str &aFile,
+ ComPtr<IProgress> &aProgress)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!i_isApplianceIdle())
+ return E_ACCESSDENIED;
+
+ if (m->pReader)
+ {
+ delete m->pReader;
+ m->pReader = NULL;
+ }
+
+ /* Parse all necessary info out of the URI (please not how stupid utterly wasteful
+ this status & allocation error throwing is): */
+ try
+ {
+ i_parseURI(aFile, m->locInfo); /* may trhow rc. */
+ }
+ catch (HRESULT aRC)
+ {
+ return aRC;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // see if we can handle this file; for now we insist it has an ovf/ova extension
+ if ( m->locInfo.storageType == VFSType_File
+ && !aFile.endsWith(".ovf", Utf8Str::CaseInsensitive)
+ && !aFile.endsWith(".ova", Utf8Str::CaseInsensitive))
+ return setError(VBOX_E_FILE_ERROR, tr("Appliance file must have .ovf or .ova extension"));
+
+ ComObjPtr<Progress> progress;
+ HRESULT hrc = i_readImpl(m->locInfo, progress);
+ if (SUCCEEDED(hrc))
+ progress.queryInterfaceTo(aProgress.asOutParam());
+ return hrc;
+}
+
+/**
+ * Public method implementation. This looks at the output of ovfreader.cpp and creates
+ * VirtualSystemDescription instances.
+ * @return
+ */
+HRESULT Appliance::interpret()
+{
+ /// @todo
+ // - don't use COM methods but the methods directly (faster, but needs appropriate
+ // locking of that objects itself (s. HardDisk))
+ // - Appropriate handle errors like not supported file formats
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!i_isApplianceIdle())
+ return E_ACCESSDENIED;
+
+ HRESULT rc = S_OK;
+
+ /* Clear any previous virtual system descriptions */
+ m->virtualSystemDescriptions.clear();
+
+ if (m->locInfo.storageType == VFSType_File && !m->pReader)
+ return setError(E_FAIL,
+ tr("Cannot interpret appliance without reading it first (call read() before interpret())"));
+
+ // Change the appliance state so we can safely leave the lock while doing time-consuming
+ // medium imports; also the below method calls do all kinds of locking which conflicts with
+ // the appliance object lock
+ m->state = ApplianceImporting;
+ alock.release();
+
+ /* Try/catch so we can clean up on error */
+ try
+ {
+ list<ovf::VirtualSystem>::const_iterator it;
+ /* Iterate through all virtual systems */
+ for (it = m->pReader->m_llVirtualSystems.begin();
+ it != m->pReader->m_llVirtualSystems.end();
+ ++it)
+ {
+ const ovf::VirtualSystem &vsysThis = *it;
+
+ ComObjPtr<VirtualSystemDescription> pNewDesc;
+ rc = pNewDesc.createObject();
+ if (FAILED(rc)) throw rc;
+ rc = pNewDesc->init();
+ if (FAILED(rc)) throw rc;
+
+ // if the virtual system in OVF had a <vbox:Machine> element, have the
+ // VirtualBox settings code parse that XML now
+ if (vsysThis.pelmVBoxMachine)
+ pNewDesc->i_importVBoxMachineXML(*vsysThis.pelmVBoxMachine);
+
+ // Guest OS type
+ // This is taken from one of three places, in this order:
+ Utf8Str strOsTypeVBox;
+ Utf8StrFmt strCIMOSType("%RU32", (uint32_t)vsysThis.cimos);
+ // 1) If there is a <vbox:Machine>, then use the type from there.
+ if ( vsysThis.pelmVBoxMachine
+ && pNewDesc->m->pConfig->machineUserData.strOsType.isNotEmpty()
+ )
+ strOsTypeVBox = pNewDesc->m->pConfig->machineUserData.strOsType;
+ // 2) Otherwise, if there is OperatingSystemSection/vbox:OSType, use that one.
+ else if (vsysThis.strTypeVBox.isNotEmpty()) // OVFReader has found vbox:OSType
+ strOsTypeVBox = vsysThis.strTypeVBox;
+ // 3) Otherwise, make a best guess what the vbox type is from the OVF (CIM) OS type.
+ else
+ convertCIMOSType2VBoxOSType(strOsTypeVBox, vsysThis.cimos, vsysThis.strCimosDesc);
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_OS,
+ "",
+ strCIMOSType,
+ strOsTypeVBox);
+
+ /* VM name */
+ Utf8Str nameVBox;
+ /* If there is a <vbox:Machine>, we always prefer the setting from there. */
+ if ( vsysThis.pelmVBoxMachine
+ && pNewDesc->m->pConfig->machineUserData.strName.isNotEmpty())
+ nameVBox = pNewDesc->m->pConfig->machineUserData.strName;
+ else
+ nameVBox = vsysThis.strName;
+ /* If there isn't any name specified create a default one out
+ * of the OS type */
+ if (nameVBox.isEmpty())
+ nameVBox = strOsTypeVBox;
+ i_searchUniqueVMName(nameVBox);
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_Name,
+ "",
+ vsysThis.strName,
+ nameVBox);
+
+ /* VM Primary Group */
+ Utf8Str strPrimaryGroup;
+ if ( vsysThis.pelmVBoxMachine
+ && pNewDesc->m->pConfig->machineUserData.llGroups.size())
+ strPrimaryGroup = pNewDesc->m->pConfig->machineUserData.llGroups.front();
+ if (strPrimaryGroup.isEmpty())
+ strPrimaryGroup = "/";
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_PrimaryGroup,
+ "",
+ "" /* no direct OVF correspondence */,
+ strPrimaryGroup);
+
+ /* Based on the VM name, create a target machine path. */
+ Bstr bstrSettingsFilename;
+ rc = mVirtualBox->ComposeMachineFilename(Bstr(nameVBox).raw(),
+ Bstr(strPrimaryGroup).raw(),
+ NULL /* aCreateFlags */,
+ NULL /* aBaseFolder */,
+ bstrSettingsFilename.asOutParam());
+ if (FAILED(rc)) throw rc;
+ Utf8Str strMachineFolder(bstrSettingsFilename);
+ strMachineFolder.stripFilename();
+
+#if 1
+ /* The import logic should work exactly the same whether the
+ * following 2 items are present or not, but of course it may have
+ * an influence on the exact presentation of the import settings
+ * of an API client. */
+ Utf8Str strSettingsFilename(bstrSettingsFilename);
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_SettingsFile,
+ "",
+ "" /* no direct OVF correspondence */,
+ strSettingsFilename);
+ Utf8Str strBaseFolder;
+ mVirtualBox->i_getDefaultMachineFolder(strBaseFolder);
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_BaseFolder,
+ "",
+ "" /* no direct OVF correspondence */,
+ strBaseFolder);
+#endif
+
+ /* VM Product */
+ if (!vsysThis.strProduct.isEmpty())
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_Product,
+ "",
+ vsysThis.strProduct,
+ vsysThis.strProduct);
+
+ /* VM Vendor */
+ if (!vsysThis.strVendor.isEmpty())
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_Vendor,
+ "",
+ vsysThis.strVendor,
+ vsysThis.strVendor);
+
+ /* VM Version */
+ if (!vsysThis.strVersion.isEmpty())
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_Version,
+ "",
+ vsysThis.strVersion,
+ vsysThis.strVersion);
+
+ /* VM ProductUrl */
+ if (!vsysThis.strProductUrl.isEmpty())
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_ProductUrl,
+ "",
+ vsysThis.strProductUrl,
+ vsysThis.strProductUrl);
+
+ /* VM VendorUrl */
+ if (!vsysThis.strVendorUrl.isEmpty())
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_VendorUrl,
+ "",
+ vsysThis.strVendorUrl,
+ vsysThis.strVendorUrl);
+
+ /* VM description */
+ if (!vsysThis.strDescription.isEmpty())
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_Description,
+ "",
+ vsysThis.strDescription,
+ vsysThis.strDescription);
+
+ /* VM license */
+ if (!vsysThis.strLicenseText.isEmpty())
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_License,
+ "",
+ vsysThis.strLicenseText,
+ vsysThis.strLicenseText);
+
+ /* Now that we know the OS type, get our internal defaults based on
+ * that, if it is known (otherwise pGuestOSType will be NULL). */
+ ComPtr<IGuestOSType> pGuestOSType;
+ mVirtualBox->GetGuestOSType(Bstr(strOsTypeVBox).raw(), pGuestOSType.asOutParam());
+
+ /* CPU count */
+ ULONG cpuCountVBox;
+ /* If there is a <vbox:Machine>, we always prefer the setting from there. */
+ if ( vsysThis.pelmVBoxMachine
+ && pNewDesc->m->pConfig->hardwareMachine.cCPUs)
+ cpuCountVBox = pNewDesc->m->pConfig->hardwareMachine.cCPUs;
+ else
+ cpuCountVBox = vsysThis.cCPUs;
+ /* Check for the constraints */
+ if (cpuCountVBox > SchemaDefs::MaxCPUCount)
+ {
+ i_addWarning(tr("Virtual appliance \"%s\" was configured with %u CPUs however VirtualBox "
+ "supports a maximum of %u CPUs. Setting the CPU count to %u."),
+ vsysThis.strName.c_str(), cpuCountVBox, SchemaDefs::MaxCPUCount, SchemaDefs::MaxCPUCount);
+ cpuCountVBox = SchemaDefs::MaxCPUCount;
+ }
+ if (vsysThis.cCPUs == 0)
+ cpuCountVBox = 1;
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_CPU,
+ "",
+ Utf8StrFmt("%RU32", (uint32_t)vsysThis.cCPUs),
+ Utf8StrFmt("%RU32", (uint32_t)cpuCountVBox));
+
+ /* RAM (in bytes) */
+ uint64_t ullMemSizeVBox;
+ /* If there is a <vbox:Machine>, we always prefer the setting from there. */
+ if ( vsysThis.pelmVBoxMachine
+ && pNewDesc->m->pConfig->hardwareMachine.ulMemorySizeMB)
+ ullMemSizeVBox = (uint64_t)pNewDesc->m->pConfig->hardwareMachine.ulMemorySizeMB * _1M;
+ else
+ ullMemSizeVBox = vsysThis.ullMemorySize; /* already in bytes via OVFReader::HandleVirtualSystemContent() */
+ /* Check for the constraints */
+ if ( ullMemSizeVBox != 0
+ && ( ullMemSizeVBox < MM_RAM_MIN
+ || ullMemSizeVBox > MM_RAM_MAX
+ )
+ )
+ {
+ i_addWarning(tr("Virtual appliance \"%s\" was configured with %RU64 MB of memory (RAM) "
+ "however VirtualBox supports a minimum of %u MB and a maximum of %u MB "
+ "of memory."),
+ vsysThis.strName.c_str(), ullMemSizeVBox / _1M, MM_RAM_MIN_IN_MB, MM_RAM_MAX_IN_MB);
+ ullMemSizeVBox = RT_MIN(RT_MAX(ullMemSizeVBox, MM_RAM_MIN), MM_RAM_MAX);
+ }
+ if (vsysThis.ullMemorySize == 0)
+ {
+ /* If the RAM of the OVF is zero, use our predefined values */
+ ULONG memSizeVBox2;
+ if (!pGuestOSType.isNull())
+ {
+ rc = pGuestOSType->COMGETTER(RecommendedRAM)(&memSizeVBox2);
+ if (FAILED(rc)) throw rc;
+ }
+ else
+ memSizeVBox2 = 1024;
+ /* IGuestOSType::recommendedRAM() returns the size in MB so convert to bytes */
+ ullMemSizeVBox = (uint64_t)memSizeVBox2 * _1M;
+ }
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_Memory,
+ "",
+ Utf8StrFmt("%RU64", vsysThis.ullMemorySize),
+ Utf8StrFmt("%RU64", ullMemSizeVBox));
+
+ /* Audio */
+ Utf8Str strSoundCard;
+ Utf8Str strSoundCardOrig;
+ /* If there is a <vbox:Machine>, we always prefer the setting from there. */
+ if ( vsysThis.pelmVBoxMachine
+ && pNewDesc->m->pConfig->hardwareMachine.audioAdapter.fEnabled)
+ {
+ strSoundCard = Utf8StrFmt("%RU32",
+ (uint32_t)pNewDesc->m->pConfig->hardwareMachine.audioAdapter.controllerType);
+ }
+ else if (vsysThis.strSoundCardType.isNotEmpty())
+ {
+ /* Set the AC97 always for the simple OVF case.
+ * @todo: figure out the hardware which could be possible */
+ strSoundCard = Utf8StrFmt("%RU32", (uint32_t)AudioControllerType_AC97);
+ strSoundCardOrig = vsysThis.strSoundCardType;
+ }
+ if (strSoundCard.isNotEmpty())
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_SoundCard,
+ "",
+ strSoundCardOrig,
+ strSoundCard);
+
+#ifdef VBOX_WITH_USB
+ /* USB Controller */
+ /* If there is a <vbox:Machine>, we always prefer the setting from there. */
+ if ( ( vsysThis.pelmVBoxMachine
+ && pNewDesc->m->pConfig->hardwareMachine.usbSettings.llUSBControllers.size() > 0)
+ || vsysThis.fHasUsbController)
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_USBController, "", "", "");
+#endif /* VBOX_WITH_USB */
+
+ /* Network Controller */
+ /* If there is a <vbox:Machine>, we always prefer the setting from there. */
+ if (vsysThis.pelmVBoxMachine)
+ {
+ uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(pNewDesc->m->pConfig->hardwareMachine.chipsetType);
+
+ const settings::NetworkAdaptersList &llNetworkAdapters = pNewDesc->m->pConfig->hardwareMachine.llNetworkAdapters;
+ /* Check for the constrains */
+ if (llNetworkAdapters.size() > maxNetworkAdapters)
+ i_addWarning(tr("Virtual appliance \"%s\" was configured with %zu network adapters however "
+ "VirtualBox supports a maximum of %u network adapters.", "", llNetworkAdapters.size()),
+ vsysThis.strName.c_str(), llNetworkAdapters.size(), maxNetworkAdapters);
+ /* Iterate through all network adapters. */
+ settings::NetworkAdaptersList::const_iterator it1;
+ size_t a = 0;
+ for (it1 = llNetworkAdapters.begin();
+ it1 != llNetworkAdapters.end() && a < maxNetworkAdapters;
+ ++it1, ++a)
+ {
+ if (it1->fEnabled)
+ {
+ Utf8Str strMode = convertNetworkAttachmentTypeToString(it1->mode);
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_NetworkAdapter,
+ "", // ref
+ strMode, // orig
+ Utf8StrFmt("%RU32", (uint32_t)it1->type), // conf
+ 0,
+ Utf8StrFmt("slot=%RU32;type=%s", it1->ulSlot, strMode.c_str())); // extra conf
+ }
+ }
+ }
+ /* else we use the ovf configuration. */
+ else if (vsysThis.llEthernetAdapters.size() > 0)
+ {
+ size_t cEthernetAdapters = vsysThis.llEthernetAdapters.size();
+ uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(ChipsetType_PIIX3);
+
+ /* Check for the constrains */
+ if (cEthernetAdapters > maxNetworkAdapters)
+ i_addWarning(tr("Virtual appliance \"%s\" was configured with %zu network adapters however "
+ "VirtualBox supports a maximum of %u network adapters.", "", cEthernetAdapters),
+ vsysThis.strName.c_str(), cEthernetAdapters, maxNetworkAdapters);
+
+ /* Get the default network adapter type for the selected guest OS */
+ NetworkAdapterType_T defaultAdapterVBox = NetworkAdapterType_Am79C970A;
+ if (!pGuestOSType.isNull())
+ {
+ rc = pGuestOSType->COMGETTER(AdapterType)(&defaultAdapterVBox);
+ if (FAILED(rc)) throw rc;
+ }
+ else
+ {
+#ifdef VBOX_WITH_E1000
+ defaultAdapterVBox = NetworkAdapterType_I82540EM;
+#else
+ defaultAdapterVBox = NetworkAdapterType_Am79C973A;
+#endif
+ }
+
+ ovf::EthernetAdaptersList::const_iterator itEA;
+ /* Iterate through all abstract networks. Ignore network cards
+ * which exceed the limit of VirtualBox. */
+ size_t a = 0;
+ for (itEA = vsysThis.llEthernetAdapters.begin();
+ itEA != vsysThis.llEthernetAdapters.end() && a < maxNetworkAdapters;
+ ++itEA, ++a)
+ {
+ const ovf::EthernetAdapter &ea = *itEA; // logical network to connect to
+ Utf8Str strNetwork = ea.strNetworkName;
+ // make sure it's one of these two
+ if ( (strNetwork.compare("Null", Utf8Str::CaseInsensitive))
+ && (strNetwork.compare("NAT", Utf8Str::CaseInsensitive))
+ && (strNetwork.compare("Bridged", Utf8Str::CaseInsensitive))
+ && (strNetwork.compare("Internal", Utf8Str::CaseInsensitive))
+ && (strNetwork.compare("HostOnly", Utf8Str::CaseInsensitive))
+ && (strNetwork.compare("Generic", Utf8Str::CaseInsensitive))
+ )
+ strNetwork = "Bridged"; // VMware assumes this is the default apparently
+
+ /* Figure out the hardware type */
+ NetworkAdapterType_T nwAdapterVBox = defaultAdapterVBox;
+ if (!ea.strAdapterType.compare("PCNet32", Utf8Str::CaseInsensitive))
+ {
+ /* If the default adapter is already one of the two
+ * PCNet adapters use the default one. If not use the
+ * Am79C970A as fallback. */
+ if (!(defaultAdapterVBox == NetworkAdapterType_Am79C970A ||
+ defaultAdapterVBox == NetworkAdapterType_Am79C973))
+ nwAdapterVBox = NetworkAdapterType_Am79C970A;
+ }
+#ifdef VBOX_WITH_E1000
+ /* VMWare accidentally write this with VirtualCenter 3.5,
+ so make sure in this case always to use the VMWare one */
+ else if (!ea.strAdapterType.compare("E10000", Utf8Str::CaseInsensitive))
+ nwAdapterVBox = NetworkAdapterType_I82545EM;
+ else if (!ea.strAdapterType.compare("E1000", Utf8Str::CaseInsensitive))
+ {
+ /* Check if this OVF was written by VirtualBox */
+ if (Utf8Str(vsysThis.strVirtualSystemType).contains("virtualbox", Utf8Str::CaseInsensitive))
+ {
+ /* If the default adapter is already one of the three
+ * E1000 adapters use the default one. If not use the
+ * I82545EM as fallback. */
+ if (!(defaultAdapterVBox == NetworkAdapterType_I82540EM ||
+ defaultAdapterVBox == NetworkAdapterType_I82543GC ||
+ defaultAdapterVBox == NetworkAdapterType_I82545EM))
+ nwAdapterVBox = NetworkAdapterType_I82540EM;
+ }
+ else
+ /* Always use this one since it's what VMware uses */
+ nwAdapterVBox = NetworkAdapterType_I82545EM;
+ }
+#endif /* VBOX_WITH_E1000 */
+ else if ( !ea.strAdapterType.compare("VirtioNet", Utf8Str::CaseInsensitive)
+ || !ea.strAdapterType.compare("virtio-net", Utf8Str::CaseInsensitive)
+ || !ea.strAdapterType.compare("3", Utf8Str::CaseInsensitive))
+ nwAdapterVBox = NetworkAdapterType_Virtio;
+
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_NetworkAdapter,
+ "", // ref
+ ea.strNetworkName, // orig
+ Utf8StrFmt("%RU32", (uint32_t)nwAdapterVBox), // conf
+ 0,
+ Utf8StrFmt("type=%s", strNetwork.c_str())); // extra conf
+ }
+ }
+
+ /* If there is a <vbox:Machine>, we always prefer the setting from there. */
+ bool fFloppy = false;
+ bool fDVD = false;
+ if (vsysThis.pelmVBoxMachine)
+ {
+ settings::StorageControllersList &llControllers = pNewDesc->m->pConfig->hardwareMachine.storage.llStorageControllers;
+ settings::StorageControllersList::iterator it3;
+ for (it3 = llControllers.begin();
+ it3 != llControllers.end();
+ ++it3)
+ {
+ settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
+ settings::AttachedDevicesList::iterator it4;
+ for (it4 = llAttachments.begin();
+ it4 != llAttachments.end();
+ ++it4)
+ {
+ fDVD |= it4->deviceType == DeviceType_DVD;
+ fFloppy |= it4->deviceType == DeviceType_Floppy;
+ if (fFloppy && fDVD)
+ break;
+ }
+ if (fFloppy && fDVD)
+ break;
+ }
+ }
+ else
+ {
+ fFloppy = vsysThis.fHasFloppyDrive;
+ fDVD = vsysThis.fHasCdromDrive;
+ }
+ /* Floppy Drive */
+ if (fFloppy)
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_Floppy, "", "", "");
+ /* CD Drive */
+ if (fDVD)
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_CDROM, "", "", "");
+
+ /* Storage Controller */
+ uint16_t cIDEused = 0;
+ uint16_t cSATAused = 0; NOREF(cSATAused);
+ uint16_t cSCSIused = 0; NOREF(cSCSIused);
+ uint16_t cVIRTIOSCSIused = 0; NOREF(cVIRTIOSCSIused);
+
+ ovf::ControllersMap::const_iterator hdcIt;
+ /* Iterate through all storage controllers */
+ for (hdcIt = vsysThis.mapControllers.begin();
+ hdcIt != vsysThis.mapControllers.end();
+ ++hdcIt)
+ {
+ const ovf::HardDiskController &hdc = hdcIt->second;
+
+ switch (hdc.system)
+ {
+ case ovf::HardDiskController::IDE:
+ /* Check for the constrains */
+ if (cIDEused < 4)
+ {
+ /// @todo figure out the IDE types
+ /* Use PIIX4 as default */
+ Utf8Str strType = "PIIX4";
+ if (!hdc.strControllerType.compare("PIIX3", Utf8Str::CaseInsensitive))
+ strType = "PIIX3";
+ else if (!hdc.strControllerType.compare("ICH6", Utf8Str::CaseInsensitive))
+ strType = "ICH6";
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerIDE,
+ hdc.strIdController, // strRef
+ hdc.strControllerType, // aOvfValue
+ strType); // aVBoxValue
+ }
+ else
+ /* Warn only once */
+ if (cIDEused == 2)
+ i_addWarning(tr("Virtual appliance \"%s\" was configured with more than two "
+ "IDE controllers however VirtualBox supports a maximum of two "
+ "IDE controllers."),
+ vsysThis.strName.c_str());
+
+ ++cIDEused;
+ break;
+
+ case ovf::HardDiskController::SATA:
+ /* Check for the constrains */
+ if (cSATAused < 1)
+ {
+ /// @todo figure out the SATA types
+ /* We only support a plain AHCI controller, so use them always */
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerSATA,
+ hdc.strIdController,
+ hdc.strControllerType,
+ "AHCI");
+ }
+ else
+ {
+ /* Warn only once */
+ if (cSATAused == 1)
+ i_addWarning(tr("Virtual appliance \"%s\" was configured with more than one "
+ "SATA controller however VirtualBox supports a maximum of one "
+ "SATA controller."),
+ vsysThis.strName.c_str());
+
+ }
+ ++cSATAused;
+ break;
+
+ case ovf::HardDiskController::SCSI:
+ /* Check for the constrains */
+ if (cSCSIused < 1)
+ {
+ VirtualSystemDescriptionType_T vsdet = VirtualSystemDescriptionType_HardDiskControllerSCSI;
+ Utf8Str hdcController = "LsiLogic";
+ if (!hdc.strControllerType.compare("lsilogicsas", Utf8Str::CaseInsensitive))
+ {
+ // OVF considers SAS a variant of SCSI but VirtualBox considers it a class of its own
+ vsdet = VirtualSystemDescriptionType_HardDiskControllerSAS;
+ hdcController = "LsiLogicSas";
+ }
+ else if (!hdc.strControllerType.compare("BusLogic", Utf8Str::CaseInsensitive))
+ hdcController = "BusLogic";
+ pNewDesc->i_addEntry(vsdet,
+ hdc.strIdController,
+ hdc.strControllerType,
+ hdcController);
+ }
+ else
+ i_addWarning(tr("Virtual appliance \"%s\" was configured with more than one SCSI "
+ "controller of type \"%s\" with ID %s however VirtualBox supports "
+ "a maximum of one SCSI controller for each type."),
+ vsysThis.strName.c_str(),
+ hdc.strControllerType.c_str(),
+ hdc.strIdController.c_str());
+ ++cSCSIused;
+ break;
+
+ case ovf::HardDiskController::VIRTIOSCSI:
+ /* Check for the constrains */
+ if (cVIRTIOSCSIused < 1)
+ {
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskControllerVirtioSCSI,
+ hdc.strIdController,
+ hdc.strControllerType,
+ "VirtioSCSI");
+ }
+ else
+ {
+ /* Warn only once */
+ if (cVIRTIOSCSIused == 1)
+ i_addWarning(tr("Virtual appliance \"%s\" was configured with more than one "
+ "VirtioSCSI controller however VirtualBox supports a maximum "
+ "of one VirtioSCSI controller."),
+ vsysThis.strName.c_str());
+
+ }
+ ++cVIRTIOSCSIused;
+ break;
+
+ }
+ }
+
+ /* Storage devices (hard disks/DVDs/...) */
+ if (vsysThis.mapVirtualDisks.size() > 0)
+ {
+ ovf::VirtualDisksMap::const_iterator itVD;
+ /* Iterate through all storage devices */
+ for (itVD = vsysThis.mapVirtualDisks.begin();
+ itVD != vsysThis.mapVirtualDisks.end();
+ ++itVD)
+ {
+ const ovf::VirtualDisk &hd = itVD->second;
+ /* Get the associated image */
+ ovf::DiskImage di;
+ std::map<RTCString, ovf::DiskImage>::iterator foundDisk;
+
+ foundDisk = m->pReader->m_mapDisks.find(hd.strDiskId);
+ if (foundDisk == m->pReader->m_mapDisks.end())
+ continue;
+ else
+ {
+ di = foundDisk->second;
+ }
+
+ /*
+ * Figure out from URI which format the image has.
+ * There is no strict mapping of image URI to image format.
+ * It's possible we aren't able to recognize some URIs.
+ */
+
+ ComObjPtr<MediumFormat> mediumFormat;
+ rc = i_findMediumFormatFromDiskImage(di, mediumFormat);
+ if (FAILED(rc))
+ throw rc;
+
+ Bstr bstrFormatName;
+ rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+ Utf8Str vdf = Utf8Str(bstrFormatName);
+
+ /// @todo
+ // - figure out all possible vmdk formats we also support
+ // - figure out if there is a url specifier for vhd already
+ // - we need a url specifier for the vdi format
+
+ Utf8Str strFilename = di.strHref;
+ DeviceType_T devType = DeviceType_Null;
+ if (vdf.compare("VMDK", Utf8Str::CaseInsensitive) == 0)
+ {
+ /* If the href is empty use the VM name as filename */
+ if (!strFilename.length())
+ strFilename = Utf8StrFmt("%s.vmdk", hd.strDiskId.c_str());
+ devType = DeviceType_HardDisk;
+ }
+ else if (vdf.compare("RAW", Utf8Str::CaseInsensitive) == 0)
+ {
+ /* If the href is empty use the VM name as filename */
+ if (!strFilename.length())
+ strFilename = Utf8StrFmt("%s.iso", hd.strDiskId.c_str());
+ devType = DeviceType_DVD;
+ }
+ else
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Unsupported format for virtual disk image %s in OVF: \"%s\""),
+ di.strHref.c_str(),
+ di.strFormat.c_str());
+
+ /*
+ * Remove last extension from the file name if the file is compressed
+ */
+ if (di.strCompression.compare("gzip", Utf8Str::CaseInsensitive)==0)
+ strFilename.stripSuffix();
+
+ i_ensureUniqueImageFilePath(strMachineFolder, devType, strFilename); /** @todo check the return code! */
+
+ /* find the description for the storage controller
+ * that has the same ID as hd.strIdController */
+ const VirtualSystemDescriptionEntry *pController;
+ if (!(pController = pNewDesc->i_findControllerFromID(hd.strIdController)))
+ throw setError(E_FAIL,
+ tr("Cannot find storage controller with OVF instance ID \"%s\" "
+ "to which medium \"%s\" should be attached"),
+ hd.strIdController.c_str(),
+ di.strHref.c_str());
+
+ /* controller to attach to, and the bus within that controller */
+ Utf8StrFmt strExtraConfig("controller=%RI16;channel=%RI16",
+ pController->ulIndex,
+ hd.ulAddressOnParent);
+ pNewDesc->i_addEntry(VirtualSystemDescriptionType_HardDiskImage,
+ hd.strDiskId,
+ di.strHref,
+ strFilename,
+ di.ulSuggestedSizeMB,
+ strExtraConfig);
+ }
+ }
+
+ m->virtualSystemDescriptions.push_back(pNewDesc);
+ }
+ }
+ catch (HRESULT aRC)
+ {
+ /* On error we clear the list & return */
+ m->virtualSystemDescriptions.clear();
+ rc = aRC;
+ }
+
+ // reset the appliance state
+ alock.acquire();
+ m->state = ApplianceIdle;
+
+ return rc;
+}
+
+/**
+ * Public method implementation. This creates one or more new machines according to the
+ * VirtualSystemScription instances created by Appliance::Interpret().
+ * Thread implementation is in Appliance::i_importImpl().
+ * @param aOptions Import options.
+ * @param aProgress Progress object.
+ * @return
+ */
+HRESULT Appliance::importMachines(const std::vector<ImportOptions_T> &aOptions,
+ ComPtr<IProgress> &aProgress)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aOptions.size())
+ {
+ try
+ {
+ m->optListImport.setCapacity(aOptions.size());
+ for (size_t i = 0; i < aOptions.size(); ++i)
+ m->optListImport.insert(i, aOptions[i]);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ AssertReturn(!( m->optListImport.contains(ImportOptions_KeepAllMACs)
+ && m->optListImport.contains(ImportOptions_KeepNATMACs) )
+ , E_INVALIDARG);
+
+ // do not allow entering this method if the appliance is busy reading or writing
+ if (!i_isApplianceIdle())
+ return E_ACCESSDENIED;
+
+ //check for the local import only. For import from the Cloud m->pReader is always NULL.
+ if (m->locInfo.storageType == VFSType_File && !m->pReader)
+ return setError(E_FAIL,
+ tr("Cannot import machines without reading it first (call read() before i_importMachines())"));
+
+ ComObjPtr<Progress> progress;
+ HRESULT hrc = i_importImpl(m->locInfo, progress);
+ if (SUCCEEDED(hrc))
+ progress.queryInterfaceTo(aProgress.asOutParam());
+
+ return hrc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Appliance private methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Ensures that there is a look-ahead object ready.
+ *
+ * @returns true if there's an object handy, false if end-of-stream.
+ * @throws HRESULT if the next object isn't a regular file. Sets error info
+ * (which is why it's a method on Appliance and not the
+ * ImportStack).
+ */
+bool Appliance::i_importEnsureOvaLookAhead(ImportStack &stack)
+{
+ Assert(stack.hVfsFssOva != NULL);
+ if (stack.hVfsIosOvaLookAhead == NIL_RTVFSIOSTREAM)
+ {
+ RTStrFree(stack.pszOvaLookAheadName);
+ stack.pszOvaLookAheadName = NULL;
+
+ RTVFSOBJTYPE enmType;
+ RTVFSOBJ hVfsObj;
+ int vrc = RTVfsFsStrmNext(stack.hVfsFssOva, &stack.pszOvaLookAheadName, &enmType, &hVfsObj);
+ if (RT_SUCCESS(vrc))
+ {
+ stack.hVfsIosOvaLookAhead = RTVfsObjToIoStream(hVfsObj);
+ RTVfsObjRelease(hVfsObj);
+ if ( ( enmType != RTVFSOBJTYPE_FILE
+ && enmType != RTVFSOBJTYPE_IO_STREAM)
+ || stack.hVfsIosOvaLookAhead == NIL_RTVFSIOSTREAM)
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Malformed OVA. '%s' is not a regular file (%d)."), stack.pszOvaLookAheadName, enmType);
+ }
+ else if (vrc == VERR_EOF)
+ return false;
+ else
+ throw setErrorVrc(vrc, tr("RTVfsFsStrmNext failed (%Rrc)"), vrc);
+ }
+ return true;
+}
+
+HRESULT Appliance::i_preCheckImageAvailability(ImportStack &stack)
+{
+ if (i_importEnsureOvaLookAhead(stack))
+ return S_OK;
+ throw setError(VBOX_E_FILE_ERROR, tr("Unexpected end of OVA package"));
+ /** @todo r=bird: dunno why this bother returning a value and the caller
+ * having a special 'continue' case for it. It always threw all non-OK
+ * status codes. It's possibly to handle out of order stuff, so that
+ * needs adding to the testcase! */
+}
+
+/**
+ * Opens a source file (for reading obviously).
+ *
+ * @param stack
+ * @param rstrSrcPath The source file to open.
+ * @param pszManifestEntry The manifest entry of the source file. This is
+ * used when constructing our manifest using a pass
+ * thru.
+ * @returns I/O stream handle to the source file.
+ * @throws HRESULT error status, error info set.
+ */
+RTVFSIOSTREAM Appliance::i_importOpenSourceFile(ImportStack &stack, Utf8Str const &rstrSrcPath, const char *pszManifestEntry)
+{
+ /*
+ * Open the source file. Special considerations for OVAs.
+ */
+ RTVFSIOSTREAM hVfsIosSrc;
+ if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
+ {
+ for (uint32_t i = 0;; i++)
+ {
+ if (!i_importEnsureOvaLookAhead(stack))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, VERR_EOF,
+ tr("Unexpected end of OVA / internal error - missing '%s' (skipped %u)"),
+ rstrSrcPath.c_str(), i);
+ if (RTStrICmp(stack.pszOvaLookAheadName, rstrSrcPath.c_str()) == 0)
+ break;
+
+ /* release the current object, loop to get the next. */
+ RTVfsIoStrmRelease(stack.claimOvaLookAHead());
+ }
+ hVfsIosSrc = stack.claimOvaLookAHead();
+ }
+ else
+ {
+ int vrc = RTVfsIoStrmOpenNormal(rstrSrcPath.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hVfsIosSrc);
+ if (RT_FAILURE(vrc))
+ throw setErrorVrc(vrc, tr("Error opening '%s' for reading (%Rrc)"), rstrSrcPath.c_str(), vrc);
+ }
+
+ /*
+ * Digest calculation filtering.
+ */
+ hVfsIosSrc = i_manifestSetupDigestCalculationForGivenIoStream(hVfsIosSrc, pszManifestEntry);
+ if (hVfsIosSrc == NIL_RTVFSIOSTREAM)
+ throw E_FAIL;
+
+ return hVfsIosSrc;
+}
+
+/**
+ * Creates the destination file and fills it with bytes from the source stream.
+ *
+ * This assumes that we digest the source when fDigestTypes is non-zero, and
+ * thus calls RTManifestPtIosAddEntryNow when done.
+ *
+ * @param rstrDstPath The path to the destination file. Missing path
+ * components will be created.
+ * @param hVfsIosSrc The source I/O stream.
+ * @param rstrSrcLogNm The name of the source for logging and error
+ * messages.
+ * @returns COM status code.
+ * @throws Nothing (as the caller has VFS handles to release).
+ */
+HRESULT Appliance::i_importCreateAndWriteDestinationFile(Utf8Str const &rstrDstPath, RTVFSIOSTREAM hVfsIosSrc,
+ Utf8Str const &rstrSrcLogNm)
+{
+ int vrc;
+
+ /*
+ * Create the output file, including necessary paths.
+ * Any existing file will be overwritten.
+ */
+ HRESULT hrc = VirtualBox::i_ensureFilePathExists(rstrDstPath, true /*fCreate*/);
+ if (SUCCEEDED(hrc))
+ {
+ RTVFSIOSTREAM hVfsIosDst;
+ vrc = RTVfsIoStrmOpenNormal(rstrDstPath.c_str(),
+ RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_ALL,
+ &hVfsIosDst);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Pump the bytes thru. If we fail, delete the output file.
+ */
+ vrc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, 0);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorVrc(vrc, tr("Error occured decompressing '%s' to '%s' (%Rrc)"),
+ rstrSrcLogNm.c_str(), rstrDstPath.c_str(), vrc);
+ uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosDst);
+ AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
+ if (RT_FAILURE(vrc))
+ RTFileDelete(rstrDstPath.c_str());
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Error opening destionation image '%s' for writing (%Rrc)"), rstrDstPath.c_str(), vrc);
+ }
+ return hrc;
+}
+
+
+/**
+ *
+ * @param stack Import stack.
+ * @param rstrSrcPath Source path.
+ * @param rstrDstPath Destination path.
+ * @param pszManifestEntry The manifest entry of the source file. This is
+ * used when constructing our manifest using a pass
+ * thru.
+ * @throws HRESULT error status, error info set.
+ */
+void Appliance::i_importCopyFile(ImportStack &stack, Utf8Str const &rstrSrcPath, Utf8Str const &rstrDstPath,
+ const char *pszManifestEntry)
+{
+ /*
+ * Open the file (throws error) and add a read ahead thread so we can do
+ * concurrent reads (+digest) and writes.
+ */
+ RTVFSIOSTREAM hVfsIosSrc = i_importOpenSourceFile(stack, rstrSrcPath, pszManifestEntry);
+ RTVFSIOSTREAM hVfsIosReadAhead;
+ int vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlags*/, 0 /*cBuffers=default*/, 0 /*cbBuffers=default*/,
+ &hVfsIosReadAhead);
+ if (RT_FAILURE(vrc))
+ {
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"), rstrSrcPath.c_str(), vrc);
+ }
+
+ /*
+ * Write the destination file (nothrow).
+ */
+ HRESULT hrc = i_importCreateAndWriteDestinationFile(rstrDstPath, hVfsIosReadAhead, rstrSrcPath);
+ RTVfsIoStrmRelease(hVfsIosReadAhead);
+
+ /*
+ * Before releasing the source stream, make sure we've successfully added
+ * the digest to our manifest.
+ */
+ if (SUCCEEDED(hrc) && m->fDigestTypes)
+ {
+ vrc = RTManifestPtIosAddEntryNow(hVfsIosSrc);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorVrc(vrc, tr("RTManifestPtIosAddEntryNow failed with %Rrc"), vrc);
+ }
+
+ uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosSrc);
+ AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
+ if (SUCCEEDED(hrc))
+ return;
+ throw hrc;
+}
+
+/**
+ *
+ * @param stack
+ * @param rstrSrcPath
+ * @param rstrDstPath
+ * @param pszManifestEntry The manifest entry of the source file. This is
+ * used when constructing our manifest using a pass
+ * thru.
+ * @throws HRESULT error status, error info set.
+ */
+void Appliance::i_importDecompressFile(ImportStack &stack, Utf8Str const &rstrSrcPath, Utf8Str const &rstrDstPath,
+ const char *pszManifestEntry)
+{
+ RTVFSIOSTREAM hVfsIosSrcCompressed = i_importOpenSourceFile(stack, rstrSrcPath, pszManifestEntry);
+
+ /*
+ * Add a read ahead thread here. This means reading and digest calculation
+ * is done on one thread, while unpacking and writing is one on this thread.
+ */
+ RTVFSIOSTREAM hVfsIosReadAhead;
+ int vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrcCompressed, 0 /*fFlags*/, 0 /*cBuffers=default*/,
+ 0 /*cbBuffers=default*/, &hVfsIosReadAhead);
+ if (RT_FAILURE(vrc))
+ {
+ RTVfsIoStrmRelease(hVfsIosSrcCompressed);
+ throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"), rstrSrcPath.c_str(), vrc);
+ }
+
+ /*
+ * Add decompression step.
+ */
+ RTVFSIOSTREAM hVfsIosSrc;
+ vrc = RTZipGzipDecompressIoStream(hVfsIosReadAhead, 0, &hVfsIosSrc);
+ RTVfsIoStrmRelease(hVfsIosReadAhead);
+ if (RT_FAILURE(vrc))
+ {
+ RTVfsIoStrmRelease(hVfsIosSrcCompressed);
+ throw setErrorVrc(vrc, tr("Error initializing gzip decompression for '%s' (%Rrc)"), rstrSrcPath.c_str(), vrc);
+ }
+
+ /*
+ * Write the stream to the destination file (nothrow).
+ */
+ HRESULT hrc = i_importCreateAndWriteDestinationFile(rstrDstPath, hVfsIosSrc, rstrSrcPath);
+
+ /*
+ * Before releasing the source stream, make sure we've successfully added
+ * the digest to our manifest.
+ */
+ if (SUCCEEDED(hrc) && m->fDigestTypes)
+ {
+ vrc = RTManifestPtIosAddEntryNow(hVfsIosSrcCompressed);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorVrc(vrc, tr("RTManifestPtIosAddEntryNow failed with %Rrc"), vrc);
+ }
+
+ uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosSrc);
+ AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
+
+ cRefs = RTVfsIoStrmRelease(hVfsIosSrcCompressed);
+ AssertMsg(cRefs == 0, ("cRefs=%u\n", cRefs)); NOREF(cRefs);
+
+ if (SUCCEEDED(hrc))
+ return;
+ throw hrc;
+}
+
+/*******************************************************************************
+ * Read stuff
+ ******************************************************************************/
+
+/**
+ * Implementation for reading an OVF (via task).
+ *
+ * This starts a new thread which will call
+ * Appliance::taskThreadImportOrExport() which will then call readFS(). This
+ * will then open the OVF with ovfreader.cpp.
+ *
+ * This is in a separate private method because it is used from two locations:
+ *
+ * 1) from the public Appliance::Read().
+ *
+ * 2) in a second worker thread; in that case, Appliance::ImportMachines() called Appliance::i_importImpl(), which
+ * called Appliance::readFSOVA(), which called Appliance::i_importImpl(), which then called this again.
+ *
+ * @returns COM status with error info set.
+ * @param aLocInfo The OVF location.
+ * @param aProgress Where to return the progress object.
+ */
+HRESULT Appliance::i_readImpl(const LocationInfo &aLocInfo, ComObjPtr<Progress> &aProgress)
+{
+ /*
+ * Create the progress object.
+ */
+ HRESULT hrc;
+ aProgress.createObject();
+ try
+ {
+ if (aLocInfo.storageType == VFSType_Cloud)
+ {
+ /* 1 operation only */
+ hrc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this),
+ Utf8Str(tr("Getting cloud instance information")), TRUE /* aCancelable */);
+
+ /* Create an empty ovf::OVFReader for manual filling it.
+ * It's not a normal usage case, but we try to re-use some OVF stuff to friend
+ * the cloud import with OVF import.
+ * In the standard case the ovf::OVFReader is created in the Appliance::i_readOVFFile().
+ * We need the existing m->pReader for Appliance::i_importCloudImpl() where we re-use OVF logic. */
+ m->pReader = new ovf::OVFReader();
+ }
+ else
+ {
+ Utf8StrFmt strDesc(tr("Reading appliance '%s'"), aLocInfo.strPath.c_str());
+ if (aLocInfo.storageType == VFSType_File)
+ /* 1 operation only */
+ hrc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this), strDesc, TRUE /* aCancelable */);
+ else
+ /* 4/5 is downloading, 1/5 is reading */
+ hrc = aProgress->init(mVirtualBox, static_cast<IAppliance*>(this), strDesc, TRUE /* aCancelable */,
+ 2, // ULONG cOperations,
+ 5, // ULONG ulTotalOperationsWeight,
+ Utf8StrFmt(tr("Download appliance '%s'"),
+ aLocInfo.strPath.c_str()), // CBSTR bstrFirstOperationDescription,
+ 4); // ULONG ulFirstOperationWeight,
+ }
+ }
+ catch (std::bad_alloc &) /* Utf8Str/Utf8StrFmt */
+ {
+ return E_OUTOFMEMORY;
+ }
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Initialize the worker task.
+ */
+ ThreadTask *pTask;
+ try
+ {
+ if (aLocInfo.storageType == VFSType_Cloud)
+ pTask = new TaskCloud(this, TaskCloud::ReadData, aLocInfo, aProgress);
+ else
+ pTask = new TaskOVF(this, TaskOVF::Read, aLocInfo, aProgress);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Kick off the worker thread.
+ */
+ hrc = pTask->createThread();
+ pTask = NULL; /* Note! createThread has consumed the task.*/
+ if (SUCCEEDED(hrc))
+ return hrc;
+ return setError(hrc, tr("Failed to create thread for reading appliance data"));
+}
+
+HRESULT Appliance::i_gettingCloudData(TaskCloud *pTask)
+{
+ LogFlowFuncEnter();
+ LogFlowFunc(("Appliance %p\n", this));
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ try
+ {
+ Utf8Str strBasename(pTask->locInfo.strPath);
+ RTCList<RTCString, RTCString *> parts = strBasename.split("/");
+ if (parts.size() != 2)//profile + instance id
+ return setErrorVrc(VERR_MISMATCH,
+ tr("%s: The profile name or instance id are absent or contain unsupported characters: %s"),
+ __FUNCTION__, strBasename.c_str());
+
+ //Get information about the passed cloud instance
+ ComPtr<ICloudProviderManager> cpm;
+ hrc = mVirtualBox->COMGETTER(CloudProviderManager)(cpm.asOutParam());
+ if (FAILED(hrc))
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("%s: Cloud provider manager object wasn't found (%Rhrc)"), __FUNCTION__, hrc);
+
+ Utf8Str strProviderName = pTask->locInfo.strProvider;
+ ComPtr<ICloudProvider> cloudProvider;
+ ComPtr<ICloudProfile> cloudProfile;
+ hrc = cpm->GetProviderByShortName(Bstr(strProviderName.c_str()).raw(), cloudProvider.asOutParam());
+
+ if (FAILED(hrc))
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("%s: Cloud provider object wasn't found (%Rhrc)"), __FUNCTION__, hrc);
+
+ Utf8Str profileName(parts.at(0));//profile
+ if (profileName.isEmpty())
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("%s: Cloud user profile name wasn't found (%Rhrc)"), __FUNCTION__, hrc);
+
+ hrc = cloudProvider->GetProfileByName(Bstr(parts.at(0)).raw(), cloudProfile.asOutParam());
+ if (FAILED(hrc))
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("%s: Cloud profile object wasn't found (%Rhrc)"), __FUNCTION__, hrc);
+
+ ComObjPtr<ICloudClient> cloudClient;
+ hrc = cloudProfile->CreateCloudClient(cloudClient.asOutParam());
+ if (FAILED(hrc))
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("%s: Cloud client object wasn't found (%Rhrc)"), __FUNCTION__, hrc);
+
+ m->virtualSystemDescriptions.clear();//clear all for assurance before creating new
+ std::vector<ComPtr<IVirtualSystemDescription> > vsdArray;
+ ULONG requestedVSDnums = 1;
+ ULONG newVSDnums = 0;
+ hrc = createVirtualSystemDescriptions(requestedVSDnums, &newVSDnums);
+ if (FAILED(hrc)) throw hrc;
+ if (requestedVSDnums != newVSDnums)
+ throw setErrorVrc(VERR_MISMATCH, tr("%s: Requested (%d) and created (%d) numbers of VSD are differ ."),
+ __FUNCTION__, requestedVSDnums, newVSDnums);
+
+ hrc = getVirtualSystemDescriptions(vsdArray);
+ if (FAILED(hrc)) throw hrc;
+ ComPtr<IVirtualSystemDescription> instanceDescription = vsdArray[0];
+
+ LogRel(("%s: calling CloudClient::GetInstanceInfo()\n", __FUNCTION__));
+
+ ComPtr<IProgress> pProgress;
+ hrc = cloudClient->GetInstanceInfo(Bstr(parts.at(1)).raw(), instanceDescription, pProgress.asOutParam());
+ if (FAILED(hrc)) throw hrc;
+ hrc = pTask->pProgress->WaitForOtherProgressCompletion(pProgress, 60000);//timeout 1 min = 60000 millisec
+ if (FAILED(hrc)) throw hrc;
+
+ // set cloud profile
+ instanceDescription->AddDescription(VirtualSystemDescriptionType_CloudProfileName, Bstr(profileName).raw(), NULL);
+
+ Utf8StrFmt strSetting("VM with id %s imported from the cloud provider %s",
+ parts.at(1).c_str(), strProviderName.c_str());
+ // set description
+ instanceDescription->AddDescription(VirtualSystemDescriptionType_Description, Bstr(strSetting).raw(), NULL);
+ }
+ catch (HRESULT arc)
+ {
+ LogFlowFunc(("arc=%Rhrc\n", arc));
+ hrc = arc;
+ }
+
+ LogFlowFunc(("rc=%Rhrc\n", hrc));
+ LogFlowFuncLeave();
+
+ return hrc;
+}
+
+void Appliance::i_setApplianceState(const ApplianceState &state)
+{
+ AutoWriteLock writeLock(this COMMA_LOCKVAL_SRC_POS);
+ m->state = state;
+ writeLock.release();
+}
+
+/**
+ * Actual worker code for import from the Cloud
+ *
+ * @param pTask
+ * @return
+ */
+HRESULT Appliance::i_importCloudImpl(TaskCloud *pTask)
+{
+ LogFlowFuncEnter();
+ LogFlowFunc(("Appliance %p\n", this));
+
+ int vrc = VINF_SUCCESS;
+ /** @todo r=klaus This should be a MultiResult, because this can cause
+ * multiple errors and warnings which should be relevant for the caller.
+ * Needs some work, because there might be errors which need to be
+ * excluded if they happen in error recovery code paths. */
+ HRESULT hrc = S_OK;
+ bool fKeepDownloadedObject = false;//in the future should be passed from the caller
+
+ /* Clear the list of imported machines, if any */
+ m->llGuidsMachinesCreated.clear();
+
+ ComPtr<ICloudProviderManager> cpm;
+ hrc = mVirtualBox->COMGETTER(CloudProviderManager)(cpm.asOutParam());
+ if (FAILED(hrc))
+ return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud provider manager object wasn't found"), __FUNCTION__);
+
+ Utf8Str strProviderName = pTask->locInfo.strProvider;
+ ComPtr<ICloudProvider> cloudProvider;
+ ComPtr<ICloudProfile> cloudProfile;
+ hrc = cpm->GetProviderByShortName(Bstr(strProviderName.c_str()).raw(), cloudProvider.asOutParam());
+
+ if (FAILED(hrc))
+ return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud provider object wasn't found"), __FUNCTION__);
+
+ /* Get the actual VSD, only one VSD object can be there for now so just call the function front() */
+ ComPtr<IVirtualSystemDescription> vsd = m->virtualSystemDescriptions.front();
+
+ Utf8Str vsdData;
+ com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
+ com::SafeArray<BSTR> aRefs;
+ com::SafeArray<BSTR> aOvfValues;
+ com::SafeArray<BSTR> aVBoxValues;
+ com::SafeArray<BSTR> aExtraConfigValues;
+
+/*
+ * local #define for better reading the code
+ * uses only the previously locally declared variable names
+ * set hrc as the result of operation
+ *
+ * What the above description fail to say is that this returns:
+ * - retTypes
+ * - aRefs
+ * - aOvfValues
+ * - aVBoxValues
+ * - aExtraConfigValues
+ */
+/** @todo r=bird: The setNull calls here are implicit in ComSafeArraySasOutParam,
+ * so we're doing twice here for no good reason! Btw. very untidy to not wrap
+ * this in do { } while (0) and require ';' when used. */
+#define GET_VSD_DESCRIPTION_BY_TYPE(aParamType) do { \
+ retTypes.setNull(); \
+ aRefs.setNull(); \
+ aOvfValues.setNull(); \
+ aVBoxValues.setNull(); \
+ aExtraConfigValues.setNull(); \
+ vsd->GetDescriptionByType(aParamType, \
+ ComSafeArrayAsOutParam(retTypes), \
+ ComSafeArrayAsOutParam(aRefs), \
+ ComSafeArrayAsOutParam(aOvfValues), \
+ ComSafeArrayAsOutParam(aVBoxValues), \
+ ComSafeArrayAsOutParam(aExtraConfigValues)); \
+ } while (0)
+
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudProfileName);
+ if (aVBoxValues.size() == 0)
+ return setErrorVrc(VERR_NOT_FOUND, tr("%s: Cloud user profile name wasn't found"), __FUNCTION__);
+
+ Utf8Str profileName(aVBoxValues[0]);
+ if (profileName.isEmpty())
+ return setErrorVrc(VERR_INVALID_STATE, tr("%s: Cloud user profile name is empty"), __FUNCTION__);
+
+ hrc = cloudProvider->GetProfileByName(aVBoxValues[0], cloudProfile.asOutParam());
+ if (FAILED(hrc))
+ return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud profile object wasn't found"), __FUNCTION__);
+
+ ComObjPtr<ICloudClient> cloudClient;
+ hrc = cloudProfile->CreateCloudClient(cloudClient.asOutParam());
+ if (FAILED(hrc))
+ return setErrorVrc(VERR_COM_OBJECT_NOT_FOUND, tr("%s: Cloud client object wasn't found"), __FUNCTION__);
+
+ ComPtr<IProgress> pProgress;
+ hrc = pTask->pProgress.queryInterfaceTo(pProgress.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ Utf8Str strOsType;
+ ComPtr<IGuestOSType> pGuestOSType;
+ {
+ VBOXOSTYPE guestOsType = VBOXOSTYPE_Unknown;
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_OS); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() != 0)
+ {
+ strOsType = aVBoxValues[0];
+ /* Check the OS type */
+ uint32_t const idxOSType = Global::getOSTypeIndexFromId(strOsType.c_str());
+ guestOsType = idxOSType < Global::cOSTypes ? Global::sOSTypes[idxOSType].osType : VBOXOSTYPE_Unknown;
+
+ /* Case when some invalid OS type or garbage was passed. Set to VBOXOSTYPE_Unknown. */
+ if (idxOSType > Global::cOSTypes)
+ {
+ strOsType = Global::OSTypeId(guestOsType);
+ vsd->RemoveDescriptionByType(VirtualSystemDescriptionType_OS);
+ vsd->AddDescription(VirtualSystemDescriptionType_OS,
+ Bstr(strOsType).raw(),
+ NULL);
+ }
+ }
+ /* Case when no OS type was passed. Set to VBOXOSTYPE_Unknown. */
+ else
+ {
+ strOsType = Global::OSTypeId(guestOsType);
+ vsd->AddDescription(VirtualSystemDescriptionType_OS,
+ Bstr(strOsType).raw(),
+ NULL);
+ }
+
+ LogRel(("%s: OS type is %s\n", __FUNCTION__, strOsType.c_str()));
+
+ /* We can get some default settings from GuestOSType when it's needed */
+ hrc = mVirtualBox->GetGuestOSType(Bstr(strOsType).raw(), pGuestOSType.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /* Should be defined here because it's used later, at least when ComposeMachineFilename() is called */
+ Utf8Str strVMName("VM_exported_from_cloud");
+
+ if (m->virtualSystemDescriptions.size() == 1)
+ {
+ do
+ {
+ ComPtr<IVirtualBox> VBox(mVirtualBox);
+
+ {
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_Name); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() != 0)//paranoia but anyway...
+ strVMName = aVBoxValues[0];
+ LogRel(("%s: VM name is %s\n", __FUNCTION__, strVMName.c_str()));
+ }
+
+// i_searchUniqueVMName(strVMName);//internally calls setError() in the case of absent the registered VM with such name
+
+ ComPtr<IMachine> machine;
+ hrc = mVirtualBox->FindMachine(Bstr(strVMName.c_str()).raw(), machine.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ /* what to do? create a new name from the old one with some suffix? */
+ uint64_t uRndSuff = RTRandU64();
+ vrc = strVMName.appendPrintfNoThrow("__%RU64", uRndSuff);
+ AssertRCBreakStmt(vrc, hrc = E_OUTOFMEMORY);
+
+ vsd->RemoveDescriptionByType(VirtualSystemDescriptionType_Name);
+ vsd->AddDescription(VirtualSystemDescriptionType_Name,
+ Bstr(strVMName).raw(),
+ NULL);
+ /* No check again because it would be weird if a VM with such unique name exists */
+ }
+
+ /* Check the target path. If the path exists and folder isn't empty return an error */
+ {
+ Bstr bstrSettingsFilename;
+ /* Based on the VM name, create a target machine path. */
+ hrc = mVirtualBox->ComposeMachineFilename(Bstr(strVMName).raw(),
+ Bstr("/").raw(),
+ NULL /* aCreateFlags */,
+ NULL /* aBaseFolder */,
+ bstrSettingsFilename.asOutParam());
+ if (FAILED(hrc))
+ break;
+
+ Utf8Str strMachineFolder(bstrSettingsFilename);
+ strMachineFolder.stripFilename();
+
+ RTFSOBJINFO dirInfo;
+ vrc = RTPathQueryInfo(strMachineFolder.c_str(), &dirInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ size_t counter = 0;
+ RTDIR hDir;
+ vrc = RTDirOpen(&hDir, strMachineFolder.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ RTDIRENTRY DirEntry;
+ while (RT_SUCCESS(RTDirRead(hDir, &DirEntry, NULL)))
+ {
+ if (RTDirEntryIsStdDotLink(&DirEntry))
+ continue;
+ ++counter;
+ }
+
+ if ( hDir != NULL)
+ vrc = RTDirClose(hDir);
+ }
+ else
+ return setErrorVrc(vrc, tr("Can't open folder %s"), strMachineFolder.c_str());
+
+ if (counter > 0)
+ return setErrorVrc(VERR_ALREADY_EXISTS,
+ tr("The target folder %s has already contained some files (%d items). Clear the folder from the files or choose another folder"),
+ strMachineFolder.c_str(), counter);
+ }
+ }
+
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudInstanceId); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() == 0)
+ return setErrorVrc(VERR_NOT_FOUND, "%s: Cloud Instance Id wasn't found", __FUNCTION__);
+
+ Utf8Str strInsId = aVBoxValues[0];
+
+ LogRelFunc(("calling CloudClient::ImportInstance\n"));
+
+ /* Here it's strongly supposed that cloud import produces ONE object on the disk.
+ * Because it much easier to manage one object in any case.
+ * In the case when cloud import creates several object on the disk all of them
+ * must be combined together into one object by cloud client.
+ * The most simple way is to create a TAR archive. */
+ hrc = cloudClient->ImportInstance(m->virtualSystemDescriptions.front(), pProgress);
+ if (FAILED(hrc))
+ {
+ LogRelFunc(("Cloud import (cloud phase) failed. Used cloud instance is \'%s\'\n", strInsId.c_str()));
+ hrc = setError(hrc, tr("%s: Cloud import (cloud phase) failed. Used cloud instance is \'%s\'\n"),
+ __FUNCTION__, strInsId.c_str());
+ break;
+ }
+
+ } while (0);
+ }
+ else
+ {
+ hrc = setErrorVrc(VERR_NOT_SUPPORTED, tr("Import from Cloud isn't supported for more than one VM instance."));
+ return hrc;
+ }
+
+
+ /* In any case we delete the cloud leavings which may exist after the first phase (cloud phase).
+ * Should they be deleted in the OCICloudClient::importInstance()?
+ * Because deleting them here is not easy as it in the importInstance(). */
+ {
+ ErrorInfoKeeper eik; /* save the error info */
+ HRESULT const hrcSaved = hrc;
+
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudInstanceId); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() == 0)
+ hrc = setErrorVrc(VERR_NOT_FOUND, tr("%s: Cloud cleanup action - the instance wasn't found"), __FUNCTION__);
+ else
+ {
+ vsdData = aVBoxValues[0];
+
+ /** @todo
+ * future function which will eliminate the temporary objects created during the first phase.
+ * hrc = cloud.EliminateImportLeavings(aVBoxValues[0], pProgress); */
+/*
+ if (FAILED(hrc))
+ {
+ hrc = setError(hrc, tr("Some leavings may exist in the Cloud."));
+ LogRel(("%s: Cleanup action - the leavings in the %s after import the "
+ "instance %s may not have been deleted\n",
+ __FUNCTION__, strProviderName.c_str(), vsdData.c_str()));
+ }
+ else
+ LogRel(("%s: Cleanup action - the leavings in the %s after import the "
+ "instance %s have been deleted\n",
+ __FUNCTION__, strProviderName.c_str(), vsdData.c_str()));
+*/
+ }
+
+ /* Because during the cleanup phase the hrc may have the good result
+ * Thus we restore the original error in the case when the cleanup phase was successful
+ * Otherwise we return not the original error but the last error in the cleanup phase */
+ /** @todo r=bird: do this conditionally perhaps?
+ * if (FAILED(hrcSaved))
+ * hrc = hrcSaved;
+ * else
+ * eik.forget();
+ */
+ hrc = hrcSaved;
+ }
+
+ if (FAILED(hrc))
+ {
+ const char *pszGeneralRollBackErrorMessage = tr("Rollback action for Import Cloud operation failed. "
+ "Some leavings may exist on the local disk or in the Cloud.");
+ /*
+ * Roll-back actions.
+ * we finish here if:
+ * 1. Getting the object from the Cloud has been failed.
+ * 2. Something is wrong with getting data from ComPtr<IVirtualSystemDescription> vsd.
+ * 3. More than 1 VirtualSystemDescription is presented in the list m->virtualSystemDescriptions.
+ * Maximum what we have there are:
+ * 1. The downloaded object, so just check the presence and delete it if one exists
+ */
+
+ { /** @todo r=bird: Pointless {}. */
+ if (!fKeepDownloadedObject)
+ {
+ ErrorInfoKeeper eik; /* save the error info */
+ HRESULT const hrcSaved = hrc;
+
+ /* small explanation here, the image here points out to the whole downloaded object (not to the image only)
+ * filled during the first cloud import stage (in the ICloudClient::importInstance()) */
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskImage); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() == 0)
+ hrc = setErrorVrc(VERR_NOT_FOUND, pszGeneralRollBackErrorMessage);
+ else
+ {
+ vsdData = aVBoxValues[0];
+ //try to delete the downloaded object
+ bool fExist = RTPathExists(vsdData.c_str());
+ if (fExist)
+ {
+ vrc = RTFileDelete(vsdData.c_str());
+ if (RT_FAILURE(vrc))
+ {
+ hrc = setErrorVrc(vrc, pszGeneralRollBackErrorMessage);
+ LogRel(("%s: Rollback action - the object %s hasn't been deleted\n", __FUNCTION__, vsdData.c_str()));
+ }
+ else
+ LogRel(("%s: Rollback action - the object %s has been deleted\n", __FUNCTION__, vsdData.c_str()));
+ }
+ }
+
+ /* Because during the rollback phase the hrc may have the good result
+ * Thus we restore the original error in the case when the rollback phase was successful
+ * Otherwise we return not the original error but the last error in the rollback phase */
+ hrc = hrcSaved;
+ }
+ }
+ }
+ else
+ {
+ Utf8Str strMachineFolder;
+ Utf8Str strAbsSrcPath;
+ Utf8Str strGroup("/");//default VM group
+ Utf8Str strTargetFormat("VMDK");//default image format
+ Bstr bstrSettingsFilename;
+ SystemProperties *pSysProps = NULL;
+ RTCList<Utf8Str> extraCreatedFiles;/* All extra created files, it's used during cleanup */
+
+ /* Put all VFS* declaration here because they are needed to be release by the corresponding
+ RTVfs***Release functions in the case of exception */
+ RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
+ RTVFSFSSTREAM hVfsFssObject = NIL_RTVFSFSSTREAM;
+ RTVFSIOSTREAM hVfsIosCurr = NIL_RTVFSIOSTREAM;
+
+ try
+ {
+ /* Small explanation here, the image here points out to the whole downloaded object (not to the image only)
+ * filled during the first cloud import stage (in the ICloudClient::importInstance()) */
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskImage); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() == 0)
+ throw setErrorVrc(VERR_NOT_FOUND, "%s: The description of the downloaded object wasn't found", __FUNCTION__);
+
+ strAbsSrcPath = aVBoxValues[0];
+
+ /* Based on the VM name, create a target machine path. */
+ hrc = mVirtualBox->ComposeMachineFilename(Bstr(strVMName).raw(),
+ Bstr(strGroup).raw(),
+ NULL /* aCreateFlags */,
+ NULL /* aBaseFolder */,
+ bstrSettingsFilename.asOutParam());
+ if (FAILED(hrc)) throw hrc;
+
+ strMachineFolder = bstrSettingsFilename;
+ strMachineFolder.stripFilename();
+
+ /* Get the system properties. */
+ pSysProps = mVirtualBox->i_getSystemProperties();
+ if (pSysProps == NULL)
+ throw VBOX_E_OBJECT_NOT_FOUND;
+
+ ComObjPtr<MediumFormat> trgFormat;
+ trgFormat = pSysProps->i_mediumFormatFromExtension(strTargetFormat);
+ if (trgFormat.isNull())
+ throw VBOX_E_OBJECT_NOT_FOUND;
+
+ /* Continue and create new VM using data from VSD and downloaded object.
+ * The downloaded images should be converted to VDI/VMDK if they have another format */
+ Utf8Str strInstId("default cloud instance id");
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CloudInstanceId); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() != 0)//paranoia but anyway...
+ strInstId = aVBoxValues[0];
+ LogRel(("%s: Importing cloud instance %s\n", __FUNCTION__, strInstId.c_str()));
+
+ /* Processing the downloaded object (prepare for the local import) */
+ RTVFSIOSTREAM hVfsIosSrc;
+ vrc = RTVfsIoStrmOpenNormal(strAbsSrcPath.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hVfsIosSrc);
+ if (RT_FAILURE(vrc))
+ throw setErrorVrc(vrc, tr("Error opening '%s' for reading (%Rrc)\n"), strAbsSrcPath.c_str(), vrc);
+
+ vrc = RTZipTarFsStreamFromIoStream(hVfsIosSrc, 0 /*fFlags*/, &hVfsFssObject);
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ if (RT_FAILURE(vrc))
+ throw setErrorVrc(vrc, tr("Error reading the downloaded file '%s' (%Rrc)"), strAbsSrcPath.c_str(), vrc);
+
+ /* Create a new virtual system and work directly on the list copy. */
+ m->pReader->m_llVirtualSystems.push_back(ovf::VirtualSystem());
+ ovf::VirtualSystem &vsys = m->pReader->m_llVirtualSystems.back();
+
+ /* Try to re-use some OVF stuff here */
+ {
+ vsys.strName = strVMName;
+ uint32_t cpus = 1;
+ {
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_CPU); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() != 0)
+ {
+ vsdData = aVBoxValues[0];
+ cpus = vsdData.toUInt32();
+ }
+ vsys.cCPUs = (uint16_t)cpus;
+ LogRel(("%s: Number of CPUs is %s\n", __FUNCTION__, vsdData.c_str()));
+ }
+
+ ULONG memory;//Mb
+ pGuestOSType->COMGETTER(RecommendedRAM)(&memory);
+ {
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_Memory); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() != 0)
+ {
+ vsdData = aVBoxValues[0];
+ if (memory > vsdData.toUInt32())
+ memory = vsdData.toUInt32();
+ }
+ vsys.ullMemorySize = memory;
+ LogRel(("%s: Size of RAM is %d MB\n", __FUNCTION__, vsys.ullMemorySize));
+ }
+
+ {
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_Description); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() != 0)
+ {
+ vsdData = aVBoxValues[0];
+ vsys.strDescription = vsdData;
+ }
+ LogRel(("%s: VM description \'%s\'\n", __FUNCTION__, vsdData.c_str()));
+ }
+
+ {
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_OS); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() != 0)
+ strOsType = aVBoxValues[0];
+ vsys.strTypeVBox = strOsType;
+ LogRel(("%s: OS type is %s\n", __FUNCTION__, strOsType.c_str()));
+ }
+
+ ovf::EthernetAdapter ea;
+ {
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_NetworkAdapter); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() != 0)
+ {
+ ea.strAdapterType = (Utf8Str)(aVBoxValues[0]);
+ ea.strNetworkName = "NAT";//default
+ vsys.llEthernetAdapters.push_back(ea);
+ LogRel(("%s: Network adapter type is %s\n", __FUNCTION__, ea.strAdapterType.c_str()));
+ }
+ else
+ {
+ NetworkAdapterType_T defaultAdapterType = NetworkAdapterType_Am79C970A;
+ pGuestOSType->COMGETTER(AdapterType)(&defaultAdapterType);
+ Utf8StrFmt dat("%RU32", (uint32_t)defaultAdapterType);
+ vsd->AddDescription(VirtualSystemDescriptionType_NetworkAdapter,
+ Bstr(dat).raw(),
+ Bstr(Utf8Str("NAT")).raw());
+ }
+ }
+
+ ovf::HardDiskController hdc;
+ {
+ //It's thought that SATA is supported by any OS types
+ hdc.system = ovf::HardDiskController::SATA;
+ hdc.strIdController = "0";
+
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskControllerSATA); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() != 0)
+ hdc.strControllerType = (Utf8Str)(aVBoxValues[0]);
+ else
+ hdc.strControllerType = "AHCI";
+
+ LogRel(("%s: Hard disk controller type is %s\n", __FUNCTION__, hdc.strControllerType.c_str()));
+ vsys.mapControllers[hdc.strIdController] = hdc;
+
+ if (aVBoxValues.size() == 0)
+ {
+ /* we should do it here because it'll be used later in the OVF logic (inside i_importMachines()) */
+ vsd->AddDescription(VirtualSystemDescriptionType_HardDiskControllerSATA,
+ Bstr(hdc.strControllerType).raw(),
+ NULL);
+ }
+ }
+
+ {
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_SoundCard); //aVBoxValues is set in this #define
+ if (aVBoxValues.size() != 0)
+ vsys.strSoundCardType = (Utf8Str)(aVBoxValues[0]);
+ else
+ {
+ AudioControllerType_T defaultAudioController;
+ pGuestOSType->COMGETTER(RecommendedAudioController)(&defaultAudioController);
+ vsys.strSoundCardType = Utf8StrFmt("%RU32", (uint32_t)defaultAudioController);//"ensoniq1371";//"AC97";
+ vsd->AddDescription(VirtualSystemDescriptionType_SoundCard,
+ Bstr(vsys.strSoundCardType).raw(),
+ NULL);
+ }
+
+ LogRel(("%s: Sound card is %s\n", __FUNCTION__, vsys.strSoundCardType.c_str()));
+ }
+
+ vsys.fHasFloppyDrive = false;
+ vsys.fHasCdromDrive = false;
+ vsys.fHasUsbController = true;
+ }
+
+ unsigned currImageObjectNum = 0;
+ hrc = S_OK;
+ do
+ {
+ char *pszName = NULL;
+ RTVFSOBJTYPE enmType;
+ vrc = RTVfsFsStrmNext(hVfsFssObject, &pszName, &enmType, &hVfsObj);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc != VERR_EOF)
+ {
+ hrc = setErrorVrc(vrc, tr("%s: Error reading '%s' (%Rrc)"), __FUNCTION__, strAbsSrcPath.c_str(), vrc);
+ throw hrc;
+ }
+ break;
+ }
+
+ /* We only care about entries that are files. Get the I/O stream handle for them. */
+ if ( enmType == RTVFSOBJTYPE_IO_STREAM
+ || enmType == RTVFSOBJTYPE_FILE)
+ {
+ /* Find the suffix and check if this is a possibly interesting file. */
+ char *pszSuffix = RTStrToLower(strrchr(pszName, '.'));
+
+ /* Get the I/O stream. */
+ hVfsIosCurr = RTVfsObjToIoStream(hVfsObj);
+ Assert(hVfsIosCurr != NIL_RTVFSIOSTREAM);
+
+ /* Get the source medium format */
+ ComObjPtr<MediumFormat> srcFormat;
+ srcFormat = pSysProps->i_mediumFormatFromExtension(pszSuffix + 1);
+
+ /* unknown image format so just extract a file without any processing */
+ if (srcFormat == NULL)
+ {
+ /* Read the file into a memory buffer */
+ void *pvBuffered;
+ size_t cbBuffered;
+ RTVFSFILE hVfsDstFile = NIL_RTVFSFILE;
+ try
+ {
+ vrc = RTVfsIoStrmReadAll(hVfsIosCurr, &pvBuffered, &cbBuffered);
+ RTVfsIoStrmRelease(hVfsIosCurr);
+ hVfsIosCurr = NIL_RTVFSIOSTREAM;
+ if (RT_FAILURE(vrc))
+ throw setErrorVrc(vrc, tr("Could not read the file '%s' (%Rrc)"), strAbsSrcPath.c_str(), vrc);
+
+ Utf8StrFmt strAbsDstPath("%s%s%s", strMachineFolder.c_str(), RTPATH_SLASH_STR, pszName);
+
+ /* Simple logic - just try to get dir info, in case of absent try to create one.
+ No deep errors analysis */
+ RTFSOBJINFO dirInfo;
+ vrc = RTPathQueryInfo(strMachineFolder.c_str(), &dirInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
+ {
+ vrc = RTDirCreateFullPath(strMachineFolder.c_str(), 0755);
+ if (RT_FAILURE(vrc))
+ throw setErrorVrc(vrc, tr("Could not create the directory '%s' (%Rrc)"),
+ strMachineFolder.c_str(), vrc);
+ }
+ else
+ throw setErrorVrc(vrc, tr("Error during getting info about the directory '%s' (%Rrc)"),
+ strMachineFolder.c_str(), vrc);
+ }
+
+ /* Write the file on the disk */
+ vrc = RTVfsFileOpenNormal(strAbsDstPath.c_str(),
+ RTFILE_O_WRITE | RTFILE_O_DENY_ALL | RTFILE_O_CREATE,
+ &hVfsDstFile);
+ if (RT_FAILURE(vrc))
+ throw setErrorVrc(vrc, tr("Could not create the file '%s' (%Rrc)"), strAbsDstPath.c_str(), vrc);
+
+ size_t cbWritten;
+ vrc = RTVfsFileWrite(hVfsDstFile, pvBuffered, cbBuffered, &cbWritten);
+ if (RT_FAILURE(vrc))
+ throw setErrorVrc(vrc, tr("Could not write into the file '%s' (%Rrc)"), strAbsDstPath.c_str(), vrc);
+
+ /* Remember this file */
+ extraCreatedFiles.append(strAbsDstPath);
+ }
+ catch (HRESULT aRc)
+ {
+ hrc = aRc;
+ LogRel(("%s: Processing the downloaded object was failed. The exception (%Rhrc)\n",
+ __FUNCTION__, hrc));
+ }
+ catch (int aRc)
+ {
+ hrc = setErrorVrc(aRc);
+ LogRel(("%s: Processing the downloaded object was failed. The exception (%Rrc/%Rhrc)\n",
+ __FUNCTION__, aRc, hrc));
+ }
+ catch (...)
+ {
+ hrc = setErrorVrc(VERR_UNEXPECTED_EXCEPTION);
+ LogRel(("%s: Processing the downloaded object was failed. The exception (VERR_UNEXPECTED_EXCEPTION/%Rhrc)\n",
+ __FUNCTION__, hrc));
+ }
+ }
+ else
+ {
+ /* Just skip the rest images if they exist. Only the first image is used as the base image. */
+ if (currImageObjectNum >= 1)
+ continue;
+
+ /* Image format is supported by VBox so extract the file and try to convert
+ * one to the default format (which is VMDK for now) */
+ Utf8Str z(bstrSettingsFilename);
+ Utf8StrFmt strAbsDstPath("%s_%d.%s",
+ z.stripSuffix().c_str(),
+ currImageObjectNum,
+ strTargetFormat.c_str());
+
+ hrc = mVirtualBox->i_findHardDiskByLocation(strAbsDstPath, false, NULL);
+ if (SUCCEEDED(hrc))
+ throw setErrorVrc(VERR_ALREADY_EXISTS, tr("The hard disk '%s' already exists."), strAbsDstPath.c_str());
+
+ /* Create an IMedium object. */
+ ComObjPtr<Medium> pTargetMedium;
+ pTargetMedium.createObject();
+ hrc = pTargetMedium->init(mVirtualBox,
+ strTargetFormat,
+ strAbsDstPath,
+ Guid::Empty /* media registry: none yet */,
+ DeviceType_HardDisk);
+ if (FAILED(hrc))
+ throw hrc;
+
+ pTask->pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"), pszName).raw(),
+ 200);
+ ComObjPtr<Medium> nullParent;
+ ComPtr<IProgress> pProgressImport;
+ ComObjPtr<Progress> pProgressImportTmp;
+ hrc = pProgressImportTmp.createObject();
+ if (FAILED(hrc))
+ throw hrc;
+
+ hrc = pProgressImportTmp->init(mVirtualBox,
+ static_cast<IAppliance*>(this),
+ Utf8StrFmt(tr("Importing medium '%s'"), pszName),
+ TRUE);
+ if (FAILED(hrc))
+ throw hrc;
+
+ pProgressImportTmp.queryInterfaceTo(pProgressImport.asOutParam());
+
+ hrc = pTargetMedium->i_importFile(pszName,
+ srcFormat,
+ MediumVariant_Standard,
+ hVfsIosCurr,
+ nullParent,
+ pProgressImportTmp,
+ true /* aNotify */);
+ RTVfsIoStrmRelease(hVfsIosCurr);
+ hVfsIosCurr = NIL_RTVFSIOSTREAM;
+ /* Now wait for the background import operation to complete;
+ * this throws HRESULTs on error. */
+ hrc = pTask->pProgress->WaitForOtherProgressCompletion(pProgressImport, 0 /* indefinite wait */);
+
+ /* Try to re-use some OVF stuff here */
+ if (SUCCEEDED(hrc))
+ {
+ /* Small trick here.
+ * We add new item into the actual VSD after successful conversion.
+ * There is no need to delete any previous records describing the images in the VSD
+ * because later in the code the search of the images in the VSD will use such records
+ * with the actual image id (d.strDiskId = pTargetMedium->i_getId().toString()) which is
+ * used as a key for m->pReader->m_mapDisks, vsys.mapVirtualDisks.
+ * So all 3 objects are tied via the image id.
+ * In the OVF case we already have all such records in the VSD after reading OVF
+ * description file (read() and interpret() functions).*/
+ ovf::DiskImage d;
+ d.strDiskId = pTargetMedium->i_getId().toString();
+ d.strHref = pTargetMedium->i_getLocationFull();
+ d.strFormat = pTargetMedium->i_getFormat();
+ d.iSize = (int64_t)pTargetMedium->i_getSize();
+ d.ulSuggestedSizeMB = (uint32_t)(d.iSize/_1M);
+
+ m->pReader->m_mapDisks[d.strDiskId] = d;
+
+ ComObjPtr<VirtualSystemDescription> vsdescThis = m->virtualSystemDescriptions.front();
+
+ /* It's needed here to use the internal function i_addEntry() instead of the API function
+ * addDescription() because we should pass the d.strDiskId for the proper handling this
+ * disk later in the i_importMachineGeneric():
+ * - find the line like this "if (vsdeHD->strRef == diCurrent.strDiskId)".
+ * if those code can be eliminated then addDescription() will be used. */
+ vsdescThis->i_addEntry(VirtualSystemDescriptionType_HardDiskImage,
+ d.strDiskId,
+ d.strHref,
+ d.strHref,
+ d.ulSuggestedSizeMB);
+
+ ovf::VirtualDisk vd;
+ //next line may generates std::out_of_range exception in case of failure
+ vd.strIdController = vsys.mapControllers.at("0").strIdController;
+ vd.ulAddressOnParent = 0;
+ vd.strDiskId = d.strDiskId;
+ vsys.mapVirtualDisks[vd.strDiskId] = vd;
+
+ ++currImageObjectNum;
+ }
+ }
+
+ RTVfsIoStrmRelease(hVfsIosCurr);
+ hVfsIosCurr = NIL_RTVFSIOSTREAM;
+ }
+
+ RTVfsObjRelease(hVfsObj);
+ hVfsObj = NIL_RTVFSOBJ;
+
+ RTStrFree(pszName);
+
+ } while (SUCCEEDED(hrc));
+
+ RTVfsFsStrmRelease(hVfsFssObject);
+ hVfsFssObject = NIL_RTVFSFSSTREAM;
+
+ if (SUCCEEDED(hrc))
+ {
+ pTask->pProgress->SetNextOperation(BstrFmt(tr("Creating new VM '%s'"), strVMName.c_str()).raw(), 50);
+ /* Create the import stack to comply OVF logic.
+ * Before we filled some other data structures which are needed by OVF logic too.*/
+ ImportStack stack(pTask->locInfo, m->pReader->m_mapDisks, pTask->pProgress, NIL_RTVFSFSSTREAM);
+ i_importMachines(stack);
+ }
+
+ }
+ catch (HRESULT aRc)
+ {
+ hrc = aRc;
+ LogRel(("%s: Cloud import (local phase) failed. The exception (%Rhrc)\n",
+ __FUNCTION__, hrc));
+ }
+ catch (int aRc)
+ {
+ hrc = setErrorVrc(aRc);
+ LogRel(("%s: Cloud import (local phase) failed. The exception (%Rrc/%Rhrc)\n",
+ __FUNCTION__, aRc, hrc));
+ }
+ catch (...)
+ {
+ hrc = setErrorVrc(VERR_UNRESOLVED_ERROR);
+ LogRel(("%s: Cloud import (local phase) failed. The exception (VERR_UNRESOLVED_ERROR/%Rhrc)\n",
+ __FUNCTION__, hrc));
+ }
+
+ LogRel(("%s: Cloud import (local phase) final result (%Rrc).\n", __FUNCTION__, hrc));
+
+ /* Try to free VFS stuff because some of them might not be released due to the exception */
+ if (hVfsIosCurr != NIL_RTVFSIOSTREAM)
+ RTVfsIoStrmRelease(hVfsIosCurr);
+ if (hVfsObj != NIL_RTVFSOBJ)
+ RTVfsObjRelease(hVfsObj);
+ if (hVfsFssObject != NIL_RTVFSFSSTREAM)
+ RTVfsFsStrmRelease(hVfsFssObject);
+
+ /* Small explanation here.
+ * After adding extracted files into the actual VSD the returned list will contain not only the
+ * record about the downloaded object but also the records about the extracted files from this object.
+ * It's needed to go through this list to find the record about the downloaded object.
+ * But it was the first record added into the list, so aVBoxValues[0] should be correct here.
+ */
+ GET_VSD_DESCRIPTION_BY_TYPE(VirtualSystemDescriptionType_HardDiskImage); //aVBoxValues is set in this #define
+ if (!fKeepDownloadedObject)
+ {
+ if (aVBoxValues.size() != 0)
+ {
+ vsdData = aVBoxValues[0];
+ //try to delete the downloaded object
+ bool fExist = RTPathExists(vsdData.c_str());
+ if (fExist)
+ {
+ vrc = RTFileDelete(vsdData.c_str());
+ if (RT_FAILURE(vrc))
+ LogRel(("%s: Cleanup action - the downloaded object %s hasn't been deleted\n", __FUNCTION__, vsdData.c_str()));
+ else
+ LogRel(("%s: Cleanup action - the downloaded object %s has been deleted\n", __FUNCTION__, vsdData.c_str()));
+ }
+ }
+ }
+
+ if (FAILED(hrc))
+ {
+ /* What to do here?
+ * For now:
+ * - check the registration of created VM and delete one.
+ * - check the list of imported images, detach them and next delete if they have still registered in the VBox.
+ * - check some other leavings and delete them if they exist.
+ */
+
+ /* It's not needed to call "pTask->pProgress->SetNextOperation(BstrFmt("The cleanup phase").raw(), 50)" here
+ * because, WaitForOtherProgressCompletion() calls the SetNextOperation() iternally.
+ * At least, it's strange that the operation description is set to the previous value. */
+
+ ComPtr<IMachine> pMachine;
+ Utf8Str machineNameOrId = strVMName;
+
+ /* m->llGuidsMachinesCreated is filled in the i_importMachineGeneric()/i_importVBoxMachine()
+ * after successful registration of new VM */
+ if (!m->llGuidsMachinesCreated.empty())
+ machineNameOrId = m->llGuidsMachinesCreated.front().toString();
+
+ hrc = mVirtualBox->FindMachine(Bstr(machineNameOrId).raw(), pMachine.asOutParam());
+
+ if (SUCCEEDED(hrc))
+ {
+ LogRel(("%s: Cleanup action - the VM with the name(or id) %s was found\n", __FUNCTION__, machineNameOrId.c_str()));
+ SafeIfaceArray<IMedium> aMedia;
+ hrc = pMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(aMedia));
+ if (SUCCEEDED(hrc))
+ {
+ LogRel(("%s: Cleanup action - the VM %s has been unregistered\n", __FUNCTION__, machineNameOrId.c_str()));
+ ComPtr<IProgress> pProgress1;
+ hrc = pMachine->DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress1.asOutParam());
+ pTask->pProgress->WaitForOtherProgressCompletion(pProgress1, 0 /* indefinite wait */);
+
+ LogRel(("%s: Cleanup action - the VM config file and the attached images have been deleted\n",
+ __FUNCTION__));
+ }
+ }
+ else
+ {
+ /* Re-check the items in the array with the images names (paths).
+ * if the import fails before creation VM, then VM won't be found
+ * -> VM can't be unregistered and the images can't be deleted.
+ * The rest items in the array aVBoxValues are the images which might
+ * have still been registered in the VBox.
+ * So go through the array and detach-unregister-delete those images */
+
+ /* have to get write lock as the whole find/update sequence must be done
+ * in one critical section, otherwise there are races which can lead to
+ * multiple Medium objects with the same content */
+
+ AutoWriteLock treeLock(mVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ for (size_t i = 1; i < aVBoxValues.size(); ++i)
+ {
+ vsdData = aVBoxValues[i];
+ ComObjPtr<Medium> poHardDisk;
+ hrc = mVirtualBox->i_findHardDiskByLocation(vsdData, false, &poHardDisk);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mVirtualBox->i_unregisterMedium((Medium*)(poHardDisk));
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<IProgress> pProgress1;
+ hrc = poHardDisk->DeleteStorage(pProgress1.asOutParam());
+ pTask->pProgress->WaitForOtherProgressCompletion(pProgress1, 0 /* indefinite wait */);
+ }
+ if (SUCCEEDED(hrc))
+ LogRel(("%s: Cleanup action - the image %s has been deleted\n", __FUNCTION__, vsdData.c_str()));
+ }
+ else if (hrc == VBOX_E_OBJECT_NOT_FOUND)
+ {
+ LogRel(("%s: Cleanup action - the image %s wasn't found. Nothing to delete.\n", __FUNCTION__, vsdData.c_str()));
+ hrc = S_OK;
+ }
+
+ }
+ }
+
+ /* Deletion of all additional files which were created during unpacking the downloaded object */
+ for (size_t i = 0; i < extraCreatedFiles.size(); ++i)
+ {
+ vrc = RTFileDelete(extraCreatedFiles.at(i).c_str());
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc);
+ else
+ LogRel(("%s: Cleanup action - file %s has been deleted\n", __FUNCTION__, extraCreatedFiles.at(i).c_str()));
+ }
+
+ /* Deletion of the other files in the VM folder and the folder itself */
+ {
+ RTDIR hDir;
+ vrc = RTDirOpen(&hDir, strMachineFolder.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ for (;;)
+ {
+ RTDIRENTRYEX Entry;
+ vrc = RTDirReadEx(hDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ {
+ AssertLogRelMsg(vrc == VERR_NO_MORE_FILES, ("%Rrc\n", vrc));
+ break;
+ }
+ if (RTFS_IS_FILE(Entry.Info.Attr.fMode))
+ {
+ vrc = RTFileDelete(Entry.szName);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc);
+ else
+ LogRel(("%s: Cleanup action - file %s has been deleted\n", __FUNCTION__, Entry.szName));
+ }
+ }
+ RTDirClose(hDir);
+ }
+
+ vrc = RTDirRemove(strMachineFolder.c_str());
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc);
+ }
+
+ if (FAILED(hrc))
+ LogRel(("%s: Cleanup action - some leavings still may exist in the folder %s\n",
+ __FUNCTION__, strMachineFolder.c_str()));
+ }
+ else
+ {
+ /* See explanation in the Appliance::i_importImpl() where Progress was setup */
+ ULONG operationCount;
+ ULONG currOperation;
+ pTask->pProgress->COMGETTER(OperationCount)(&operationCount);
+ pTask->pProgress->COMGETTER(Operation)(&currOperation);
+ while (++currOperation < operationCount)
+ {
+ pTask->pProgress->SetNextOperation(BstrFmt("Skipping the cleanup phase. All right.").raw(), 1);
+ LogRel(("%s: Skipping the cleanup step %d\n", __FUNCTION__, currOperation));
+ }
+ }
+ }
+
+ LogFlowFunc(("rc=%Rhrc\n", hrc));
+ LogFlowFuncLeave();
+ return hrc;
+}
+
+/**
+ * Actual worker code for reading an OVF from disk. This is called from Appliance::taskThreadImportOrExport()
+ * and therefore runs on the OVF read worker thread. This opens the OVF with ovfreader.cpp.
+ *
+ * This runs in one context:
+ *
+ * 1) in a first worker thread; in that case, Appliance::Read() called Appliance::readImpl();
+ *
+ * @param pTask
+ * @return
+ */
+HRESULT Appliance::i_readFS(TaskOVF *pTask)
+{
+ LogFlowFuncEnter();
+ LogFlowFunc(("Appliance %p\n", this));
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc;
+ if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
+ rc = i_readFSOVF(pTask);
+ else
+ rc = i_readFSOVA(pTask);
+
+ LogFlowFunc(("rc=%Rhrc\n", rc));
+ LogFlowFuncLeave();
+
+ return rc;
+}
+
+HRESULT Appliance::i_readFSOVF(TaskOVF *pTask)
+{
+ LogFlowFunc(("'%s'\n", pTask->locInfo.strPath.c_str()));
+
+ /*
+ * Allocate a buffer for filenames and prep it for suffix appending.
+ */
+ char *pszNameBuf = (char *)alloca(pTask->locInfo.strPath.length() + 16);
+ AssertReturn(pszNameBuf, E_OUTOFMEMORY);
+ memcpy(pszNameBuf, pTask->locInfo.strPath.c_str(), pTask->locInfo.strPath.length() + 1);
+ RTPathStripSuffix(pszNameBuf);
+ size_t const cchBaseName = strlen(pszNameBuf);
+
+ /*
+ * Open the OVF file first since that is what this is all about.
+ */
+ RTVFSIOSTREAM hIosOvf;
+ int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
+ RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosOvf);
+ if (RT_FAILURE(vrc))
+ return setErrorVrc(vrc, tr("Failed to open OVF file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
+
+ HRESULT hrc = i_readOVFFile(pTask, hIosOvf, RTPathFilename(pTask->locInfo.strPath.c_str())); /* consumes hIosOvf */
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Try open the manifest file (for signature purposes and to determine digest type(s)).
+ */
+ RTVFSIOSTREAM hIosMf;
+ strcpy(&pszNameBuf[cchBaseName], ".mf");
+ vrc = RTVfsIoStrmOpenNormal(pszNameBuf, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosMf);
+ if (RT_SUCCESS(vrc))
+ {
+ const char * const pszFilenamePart = RTPathFilename(pszNameBuf);
+ hrc = i_readManifestFile(pTask, hIosMf /*consumed*/, pszFilenamePart);
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Check for the signature file.
+ */
+ RTVFSIOSTREAM hIosCert;
+ strcpy(&pszNameBuf[cchBaseName], ".cert");
+ vrc = RTVfsIoStrmOpenNormal(pszNameBuf, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hIosCert);
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = i_readSignatureFile(pTask, hIosCert /*consumed*/, pszFilenamePart);
+ if (FAILED(hrc))
+ return hrc;
+ }
+ else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
+ return setErrorVrc(vrc, tr("Failed to open the signature file '%s' (%Rrc)"), pszNameBuf, vrc);
+
+ }
+ else if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
+ {
+ m->fDeterminedDigestTypes = true;
+ m->fDigestTypes = 0;
+ }
+ else
+ return setErrorVrc(vrc, tr("Failed to open the manifest file '%s' (%Rrc)"), pszNameBuf, vrc);
+
+ /*
+ * Do tail processing (check the signature).
+ */
+ hrc = i_readTailProcessing(pTask);
+
+ LogFlowFunc(("returns %Rhrc\n", hrc));
+ return hrc;
+}
+
+HRESULT Appliance::i_readFSOVA(TaskOVF *pTask)
+{
+ LogFlowFunc(("'%s'\n", pTask->locInfo.strPath.c_str()));
+
+ /*
+ * Open the tar file as file stream.
+ */
+ RTVFSIOSTREAM hVfsIosOva;
+ int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsIosOva);
+ if (RT_FAILURE(vrc))
+ return setErrorVrc(vrc, tr("Error opening the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
+
+ RTVFSFSSTREAM hVfsFssOva;
+ vrc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssOva);
+ RTVfsIoStrmRelease(hVfsIosOva);
+ if (RT_FAILURE(vrc))
+ return setErrorVrc(vrc, tr("Error reading the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
+
+ /*
+ * Since jumping thru an OVA file with seekable disk backing is rather
+ * efficient, we can process .ovf, .mf and .cert files here without any
+ * strict ordering restrictions.
+ *
+ * (Technically, the .ovf-file comes first, while the manifest and its
+ * optional signature file either follows immediately or at the very end of
+ * the OVA. The manifest is optional.)
+ */
+ char *pszOvfNameBase = NULL;
+ size_t cchOvfNameBase = 0; NOREF(cchOvfNameBase);
+ unsigned cLeftToFind = 3;
+ HRESULT hrc = S_OK;
+ do
+ {
+ char *pszName = NULL;
+ RTVFSOBJTYPE enmType;
+ RTVFSOBJ hVfsObj;
+ vrc = RTVfsFsStrmNext(hVfsFssOva, &pszName, &enmType, &hVfsObj);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc != VERR_EOF)
+ hrc = setErrorVrc(vrc, tr("Error reading OVA '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
+ break;
+ }
+
+ /* We only care about entries that are files. Get the I/O stream handle for them. */
+ if ( enmType == RTVFSOBJTYPE_IO_STREAM
+ || enmType == RTVFSOBJTYPE_FILE)
+ {
+ /* Find the suffix and check if this is a possibly interesting file. */
+ char *pszSuffix = strrchr(pszName, '.');
+ if ( pszSuffix
+ && ( RTStrICmp(pszSuffix + 1, "ovf") == 0
+ || RTStrICmp(pszSuffix + 1, "mf") == 0
+ || RTStrICmp(pszSuffix + 1, "cert") == 0) )
+ {
+ /* Match the OVF base name. */
+ *pszSuffix = '\0';
+ if ( pszOvfNameBase == NULL
+ || RTStrICmp(pszName, pszOvfNameBase) == 0)
+ {
+ *pszSuffix = '.';
+
+ /* Since we're pretty sure we'll be processing this file, get the I/O stream. */
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ Assert(hVfsIos != NIL_RTVFSIOSTREAM);
+
+ /* Check for the OVF (should come first). */
+ if (RTStrICmp(pszSuffix + 1, "ovf") == 0)
+ {
+ if (pszOvfNameBase == NULL)
+ {
+ hrc = i_readOVFFile(pTask, hVfsIos, pszName);
+ hVfsIos = NIL_RTVFSIOSTREAM;
+
+ /* Set the base name. */
+ *pszSuffix = '\0';
+ pszOvfNameBase = pszName;
+ cchOvfNameBase = strlen(pszName);
+ pszName = NULL;
+ cLeftToFind--;
+ }
+ else
+ LogRel(("i_readFSOVA: '%s' contains more than one OVF file ('%s'), picking the first one\n",
+ pTask->locInfo.strPath.c_str(), pszName));
+ }
+ /* Check for manifest. */
+ else if (RTStrICmp(pszSuffix + 1, "mf") == 0)
+ {
+ if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
+ {
+ hrc = i_readManifestFile(pTask, hVfsIos, pszName);
+ hVfsIos = NIL_RTVFSIOSTREAM; /*consumed*/
+ cLeftToFind--;
+ }
+ else
+ LogRel(("i_readFSOVA: '%s' contains more than one manifest file ('%s'), picking the first one\n",
+ pTask->locInfo.strPath.c_str(), pszName));
+ }
+ /* Check for signature. */
+ else if (RTStrICmp(pszSuffix + 1, "cert") == 0)
+ {
+ if (!m->fSignerCertLoaded)
+ {
+ hrc = i_readSignatureFile(pTask, hVfsIos, pszName);
+ hVfsIos = NIL_RTVFSIOSTREAM; /*consumed*/
+ cLeftToFind--;
+ }
+ else
+ LogRel(("i_readFSOVA: '%s' contains more than one signature file ('%s'), picking the first one\n",
+ pTask->locInfo.strPath.c_str(), pszName));
+ }
+ else
+ AssertFailed();
+ if (hVfsIos != NIL_RTVFSIOSTREAM)
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ }
+ }
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ } while (cLeftToFind > 0 && SUCCEEDED(hrc));
+
+ RTVfsFsStrmRelease(hVfsFssOva);
+ RTStrFree(pszOvfNameBase);
+
+ /*
+ * Check that we found and OVF file.
+ */
+ if (SUCCEEDED(hrc) && !pszOvfNameBase)
+ hrc = setError(VBOX_E_FILE_ERROR, tr("OVA '%s' does not contain an .ovf-file"), pTask->locInfo.strPath.c_str());
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Do tail processing (check the signature).
+ */
+ hrc = i_readTailProcessing(pTask);
+ }
+ LogFlowFunc(("returns %Rhrc\n", hrc));
+ return hrc;
+}
+
+/**
+ * Reads & parses the OVF file.
+ *
+ * @param pTask The read task.
+ * @param hVfsIosOvf The I/O stream for the OVF. The reference is
+ * always consumed.
+ * @param pszManifestEntry The manifest entry name.
+ * @returns COM status code, error info set.
+ * @throws Nothing
+ */
+HRESULT Appliance::i_readOVFFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosOvf, const char *pszManifestEntry)
+{
+ LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszManifestEntry));
+
+ /*
+ * Set the OVF manifest entry name (needed for tweaking the manifest
+ * validation during import).
+ */
+ try { m->strOvfManifestEntry = pszManifestEntry; }
+ catch (...) { return E_OUTOFMEMORY; }
+
+ /*
+ * Set up digest calculation.
+ */
+ hVfsIosOvf = i_manifestSetupDigestCalculationForGivenIoStream(hVfsIosOvf, pszManifestEntry);
+ if (hVfsIosOvf == NIL_RTVFSIOSTREAM)
+ return VBOX_E_FILE_ERROR;
+
+ /*
+ * Read the OVF into a memory buffer and parse it.
+ */
+ void *pvBufferedOvf;
+ size_t cbBufferedOvf;
+ int vrc = RTVfsIoStrmReadAll(hVfsIosOvf, &pvBufferedOvf, &cbBufferedOvf);
+ uint32_t cRefs = RTVfsIoStrmRelease(hVfsIosOvf); /* consumes stream handle. */
+ NOREF(cRefs);
+ Assert(cRefs == 0);
+ if (RT_FAILURE(vrc))
+ return setErrorVrc(vrc, tr("Could not read the OVF file for '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
+
+ HRESULT hrc;
+ try
+ {
+ m->pReader = new ovf::OVFReader(pvBufferedOvf, cbBufferedOvf, pTask->locInfo.strPath);
+ hrc = S_OK;
+ }
+ catch (RTCError &rXcpt) // includes all XML exceptions
+ {
+ hrc = setError(VBOX_E_FILE_ERROR, rXcpt.what());
+ }
+ catch (HRESULT aRC)
+ {
+ hrc = aRC;
+ }
+ catch (...)
+ {
+ hrc = E_FAIL;
+ }
+ LogFlowFunc(("OVFReader(%s) -> rc=%Rhrc\n", pTask->locInfo.strPath.c_str(), hrc));
+
+ RTVfsIoStrmReadAllFree(pvBufferedOvf, cbBufferedOvf);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * If we see an OVF v2.0 envelope, select only the SHA-256 digest.
+ */
+ if ( !m->fDeterminedDigestTypes
+ && m->pReader->m_envelopeData.getOVFVersion() == ovf::OVFVersion_2_0)
+ m->fDigestTypes &= ~RTMANIFEST_ATTR_SHA256;
+ }
+
+ return hrc;
+}
+
+/**
+ * Reads & parses the manifest file.
+ *
+ * @param pTask The read task.
+ * @param hVfsIosMf The I/O stream for the manifest file. The
+ * reference is always consumed.
+ * @param pszSubFileNm The manifest filename (no path) for error
+ * messages and logging.
+ * @returns COM status code, error info set.
+ * @throws Nothing
+ */
+HRESULT Appliance::i_readManifestFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosMf, const char *pszSubFileNm)
+{
+ LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
+
+ /*
+ * Copy the manifest into a memory backed file so we can later do signature
+ * validation independent of the algorithms used by the signature.
+ */
+ int vrc = RTVfsMemorizeIoStreamAsFile(hVfsIosMf, RTFILE_O_READ, &m->hMemFileTheirManifest);
+ RTVfsIoStrmRelease(hVfsIosMf); /* consumes stream handle. */
+ if (RT_FAILURE(vrc))
+ return setErrorVrc(vrc, tr("Error reading the manifest file '%s' for '%s' (%Rrc)"),
+ pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
+
+ /*
+ * Parse the manifest.
+ */
+ Assert(m->hTheirManifest == NIL_RTMANIFEST);
+ vrc = RTManifestCreate(0 /*fFlags*/, &m->hTheirManifest);
+ AssertStmt(RT_SUCCESS(vrc), Global::vboxStatusCodeToCOM(vrc));
+
+ char szErr[256];
+ RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(m->hMemFileTheirManifest);
+ vrc = RTManifestReadStandardEx(m->hTheirManifest, hVfsIos, szErr, sizeof(szErr));
+ RTVfsIoStrmRelease(hVfsIos);
+ if (RT_FAILURE(vrc))
+ return setErrorVrc(vrc, tr("Failed to parse manifest file '%s' for '%s' (%Rrc): %s"),
+ pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, szErr);
+
+ /*
+ * Check which digest files are used.
+ * Note! the file could be empty, in which case fDigestTypes is set to 0.
+ */
+ vrc = RTManifestQueryAllAttrTypes(m->hTheirManifest, true /*fEntriesOnly*/, &m->fDigestTypes);
+ AssertRCReturn(vrc, Global::vboxStatusCodeToCOM(vrc));
+ m->fDeterminedDigestTypes = true;
+
+ return S_OK;
+}
+
+/**
+ * Reads the signature & certificate file.
+ *
+ * @param pTask The read task.
+ * @param hVfsIosCert The I/O stream for the signature file. The
+ * reference is always consumed.
+ * @param pszSubFileNm The signature filename (no path) for error
+ * messages and logging. Used to construct
+ * .mf-file name.
+ * @returns COM status code, error info set.
+ * @throws Nothing
+ */
+HRESULT Appliance::i_readSignatureFile(TaskOVF *pTask, RTVFSIOSTREAM hVfsIosCert, const char *pszSubFileNm)
+{
+ LogFlowFunc(("%s[%s]\n", pTask->locInfo.strPath.c_str(), pszSubFileNm));
+
+ /*
+ * Construct the manifest filename from pszSubFileNm.
+ */
+ Utf8Str strManifestName;
+ try
+ {
+ const char *pszSuffix = strrchr(pszSubFileNm, '.');
+ AssertReturn(pszSuffix, E_FAIL);
+ strManifestName = Utf8Str(pszSubFileNm, (size_t)(pszSuffix - pszSubFileNm));
+ strManifestName.append(".mf");
+ }
+ catch (...)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Copy the manifest into a memory buffer. We'll do the signature processing
+ * later to not force any specific order in the OVAs or any other archive we
+ * may be accessing later.
+ */
+ void *pvSignature;
+ size_t cbSignature;
+ int vrc = RTVfsIoStrmReadAll(hVfsIosCert, &pvSignature, &cbSignature);
+ RTVfsIoStrmRelease(hVfsIosCert); /* consumes stream handle. */
+ if (RT_FAILURE(vrc))
+ return setErrorVrc(vrc, tr("Error reading the signature file '%s' for '%s' (%Rrc)"),
+ pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc);
+
+ /*
+ * Parse the signing certificate. Unlike the manifest parser we use below,
+ * this API ignores parts of the file that aren't relevant.
+ */
+ RTERRINFOSTATIC StaticErrInfo;
+ vrc = RTCrX509Certificate_ReadFromBuffer(&m->SignerCert, pvSignature, cbSignature,
+ RTCRX509CERT_READ_F_PEM_ONLY,
+ &g_RTAsn1DefaultAllocator, RTErrInfoInitStatic(&StaticErrInfo), pszSubFileNm);
+ HRESULT hrc;
+ if (RT_SUCCESS(vrc))
+ {
+ m->fSignerCertLoaded = true;
+ m->fCertificateIsSelfSigned = RTCrX509Certificate_IsSelfSigned(&m->SignerCert);
+
+ /*
+ * Find the start of the certificate part of the file, so we can avoid
+ * upsetting the manifest parser with it.
+ */
+ char *pszSplit = (char *)RTCrPemFindFirstSectionInContent(pvSignature, cbSignature,
+ g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers);
+ if (pszSplit)
+ while ( pszSplit != (char *)pvSignature
+ && pszSplit[-1] != '\n'
+ && pszSplit[-1] != '\r')
+ pszSplit--;
+ else
+ {
+ AssertLogRelMsgFailed(("Failed to find BEGIN CERTIFICATE markers in '%s'::'%s' - impossible unless it's a DER encoded certificate!",
+ pTask->locInfo.strPath.c_str(), pszSubFileNm));
+ pszSplit = (char *)pvSignature + cbSignature;
+ }
+ char const chSaved = *pszSplit;
+ *pszSplit = '\0';
+
+ /*
+ * Now, read the manifest part. We use the IPRT manifest reader here
+ * to avoid duplicating code and be somewhat flexible wrt the digest
+ * type choosen by the signer.
+ */
+ RTMANIFEST hSignedDigestManifest;
+ vrc = RTManifestCreate(0 /*fFlags*/, &hSignedDigestManifest);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hVfsIosTmp;
+ vrc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pvSignature, (size_t)(pszSplit - (char *)pvSignature), &hVfsIosTmp);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTManifestReadStandardEx(hSignedDigestManifest, hVfsIosTmp, StaticErrInfo.szMsg, sizeof(StaticErrInfo.szMsg));
+ RTVfsIoStrmRelease(hVfsIosTmp);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Get signed digest, we prefer SHA-2, so explicitly query those first.
+ */
+ uint32_t fDigestType;
+ char szSignedDigest[_8K + 1];
+ vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
+ RTMANIFEST_ATTR_SHA512 | RTMANIFEST_ATTR_SHA256,
+ szSignedDigest, sizeof(szSignedDigest), &fDigestType);
+ if (vrc == VERR_MANIFEST_ATTR_TYPE_NOT_FOUND)
+ vrc = RTManifestEntryQueryAttr(hSignedDigestManifest, strManifestName.c_str(), NULL,
+ RTMANIFEST_ATTR_ANY, szSignedDigest, sizeof(szSignedDigest), &fDigestType);
+ if (RT_SUCCESS(vrc))
+ {
+ const char *pszSignedDigest = RTStrStrip(szSignedDigest);
+ size_t cbSignedDigest = strlen(pszSignedDigest) / 2;
+ uint8_t abSignedDigest[sizeof(szSignedDigest) / 2];
+ vrc = RTStrConvertHexBytes(szSignedDigest, abSignedDigest, cbSignedDigest, 0 /*fFlags*/);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Convert it to RTDIGESTTYPE_XXX and save the binary value for later use.
+ */
+ switch (fDigestType)
+ {
+ case RTMANIFEST_ATTR_SHA1: m->enmSignedDigestType = RTDIGESTTYPE_SHA1; break;
+ case RTMANIFEST_ATTR_SHA256: m->enmSignedDigestType = RTDIGESTTYPE_SHA256; break;
+ case RTMANIFEST_ATTR_SHA512: m->enmSignedDigestType = RTDIGESTTYPE_SHA512; break;
+ case RTMANIFEST_ATTR_MD5: m->enmSignedDigestType = RTDIGESTTYPE_MD5; break;
+ default: AssertFailed(); m->enmSignedDigestType = RTDIGESTTYPE_INVALID; break;
+ }
+ if (m->enmSignedDigestType != RTDIGESTTYPE_INVALID)
+ {
+ m->pbSignedDigest = (uint8_t *)RTMemDup(abSignedDigest, cbSignedDigest);
+ m->cbSignedDigest = cbSignedDigest;
+ hrc = S_OK;
+ }
+ else
+ hrc = setError(E_FAIL, tr("Unsupported signed digest type (%#x)"), fDigestType);
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Error reading signed manifest digest: %Rrc"), vrc);
+ }
+ else if (vrc == VERR_NOT_FOUND)
+ hrc = setErrorVrc(vrc, tr("Could not locate signed digest for '%s' in the cert-file for '%s'"),
+ strManifestName.c_str(), pTask->locInfo.strPath.c_str());
+ else
+ hrc = setErrorVrc(vrc, tr("RTManifestEntryQueryAttr failed unexpectedly: %Rrc"), vrc);
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Error parsing the .cert-file for '%s': %s"),
+ pTask->locInfo.strPath.c_str(), StaticErrInfo.szMsg);
+ }
+ else
+ hrc = E_OUTOFMEMORY;
+ RTManifestRelease(hSignedDigestManifest);
+ }
+ else
+ hrc = E_OUTOFMEMORY;
+
+ /*
+ * Look for the additional for PKCS#7/CMS signature we produce when we sign stuff.
+ */
+ if (SUCCEEDED(hrc))
+ {
+ *pszSplit = chSaved;
+ vrc = RTCrPkcs7_ReadFromBuffer(&m->ContentInfo, pvSignature, cbSignature, RTCRPKCS7_READ_F_PEM_ONLY,
+ &g_RTAsn1DefaultAllocator, NULL /*pfCmsLabeled*/,
+ RTErrInfoInitStatic(&StaticErrInfo), pszSubFileNm);
+ if (RT_SUCCESS(vrc))
+ m->fContentInfoLoaded = true;
+ else if (vrc != VERR_NOT_FOUND)
+ hrc = setErrorVrc(vrc, tr("Error reading the PKCS#7/CMS signature from '%s' for '%s' (%Rrc): %s"),
+ pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
+ }
+ }
+ else if (vrc == VERR_NOT_FOUND || vrc == VERR_EOF)
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Malformed .cert-file for '%s': Signer's certificate not found (%Rrc)"),
+ pTask->locInfo.strPath.c_str(), vrc);
+ else
+ hrc = setErrorVrc(vrc, tr("Error reading the signer's certificate from '%s' for '%s' (%Rrc): %s"),
+ pszSubFileNm, pTask->locInfo.strPath.c_str(), vrc, StaticErrInfo.Core.pszMsg);
+
+ RTVfsIoStrmReadAllFree(pvSignature, cbSignature);
+ LogFlowFunc(("returns %Rhrc (%Rrc)\n", hrc, vrc));
+ return hrc;
+}
+
+
+/**
+ * Does tail processing after the files have been read in.
+ *
+ * @param pTask The read task.
+ * @returns COM status.
+ * @throws Nothing!
+ */
+HRESULT Appliance::i_readTailProcessing(TaskOVF *pTask)
+{
+ /*
+ * Parse and validate the signature file.
+ *
+ * The signature file nominally has two parts, manifest part and a PEM
+ * encoded certificate. The former contains an entry for the manifest file
+ * with a digest that is encrypted with the certificate in the latter part.
+ *
+ * When an appliance is signed by VirtualBox, a PKCS#7/CMS signedData part
+ * is added by default, supplying more info than the bits mandated by the
+ * OVF specs. We will validate both the signedData and the standard OVF
+ * signature. Another requirement is that the first signedData signer
+ * uses the same certificate as the regular OVF signature, allowing us to
+ * only do path building for the signedData with the additional info it
+ * ships with.
+ */
+ if (m->pbSignedDigest)
+ {
+ /* Since we're validating the digest of the manifest, there have to be
+ a manifest. We cannot allow a the manifest to be missing. */
+ if (m->hMemFileTheirManifest == NIL_RTVFSFILE)
+ return setError(VBOX_E_FILE_ERROR, tr("Found .cert-file but no .mf-file for '%s'"), pTask->locInfo.strPath.c_str());
+
+ /*
+ * Validate the signed digest.
+ *
+ * It's possible we should allow the user to ignore signature
+ * mismatches, but for now it is a solid show stopper.
+ */
+ HRESULT hrc;
+ RTERRINFOSTATIC StaticErrInfo;
+
+ /* Calc the digest of the manifest using the algorithm found above. */
+ RTCRDIGEST hDigest;
+ int vrc = RTCrDigestCreateByType(&hDigest, m->enmSignedDigestType);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTCrDigestUpdateFromVfsFile(hDigest, m->hMemFileTheirManifest, true /*fRewindFile*/);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Compare the signed digest with the one we just calculated. (This
+ API will do the verification twice, once using IPRT's own crypto
+ and once using OpenSSL. Both must OK it for success.) */
+ vrc = RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo(&m->SignerCert.TbsCertificate.SubjectPublicKeyInfo,
+ m->pbSignedDigest, m->cbSignedDigest, hDigest,
+ RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ m->fSignatureValid = true;
+ hrc = S_OK;
+ }
+ else if (vrc == VERR_CR_PKIX_SIGNATURE_MISMATCH)
+ hrc = setErrorVrc(vrc, tr("The manifest signature does not match"));
+ else
+ hrc = setErrorVrc(vrc,
+ tr("Error validating the manifest signature (%Rrc, %s)"), vrc, StaticErrInfo.Core.pszMsg);
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("RTCrDigestUpdateFromVfsFile failed: %Rrc"), vrc);
+ RTCrDigestRelease(hDigest);
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("RTCrDigestCreateByType failed: %Rrc"), vrc);
+
+ /*
+ * If we have a PKCS#7/CMS signature, validate it and check that the
+ * certificate matches the first signerInfo entry.
+ */
+ HRESULT hrc2 = i_readTailProcessingSignedData(&StaticErrInfo);
+ if (FAILED(hrc2) && SUCCEEDED(hrc))
+ hrc = hrc2;
+
+ /*
+ * Validate the certificate.
+ *
+ * We don't fail here if we cannot validate the certificate, we postpone
+ * that till the import stage, so that we can allow the user to ignore it.
+ *
+ * The certificate validity time is deliberately left as warnings as the
+ * OVF specification does not provision for any timestamping of the
+ * signature. This is course a security concern, but the whole signing
+ * of OVFs is currently weirdly trusting (self signed * certs), so this
+ * is the least of our current problems.
+ *
+ * While we try build and verify certificate paths properly, the
+ * "neighbours" quietly ignores this and seems only to check the signature
+ * and not whether the certificate is trusted. Also, we don't currently
+ * complain about self-signed certificates either (ditto "neighbours").
+ * The OVF creator is also a bit restricted wrt to helping us build the
+ * path as he cannot supply intermediate certificates. Anyway, we issue
+ * warnings (goes to /dev/null, am I right?) for self-signed certificates
+ * and certificates we cannot build and verify a root path for.
+ *
+ * (The OVF sillibuggers should've used PKCS#7, CMS or something else
+ * that's already been standardized instead of combining manifests with
+ * certificate PEM files in some very restrictive manner! I wonder if
+ * we could add a PKCS#7 section to the .cert file in addition to the CERT
+ * and manifest stuff dictated by the standard. Would depend on how others
+ * deal with it.)
+ */
+ Assert(!m->fCertificateValid);
+ Assert(m->fCertificateMissingPath);
+ Assert(!m->fCertificateValidTime);
+ Assert(m->strCertError.isEmpty());
+ Assert(m->fCertificateIsSelfSigned == RTCrX509Certificate_IsSelfSigned(&m->SignerCert));
+
+ /* We'll always needs the trusted cert store. */
+ hrc2 = S_OK;
+ RTCRSTORE hTrustedCerts;
+ vrc = RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts(&hTrustedCerts, RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ /* If we don't have a PKCS7/CMS signature or if it uses a different
+ certificate, we try our best to validate the OVF certificate. */
+ if (!m->fContentInfoOkay || !m->fContentInfoSameCert)
+ {
+ if (m->fCertificateIsSelfSigned)
+ hrc2 = i_readTailProcessingVerifySelfSignedOvfCert(pTask, hTrustedCerts, &StaticErrInfo);
+ else
+ hrc2 = i_readTailProcessingVerifyIssuedOvfCert(pTask, hTrustedCerts, &StaticErrInfo);
+ }
+
+ /* If there is a PKCS7/CMS signature, we always verify its certificates. */
+ if (m->fContentInfoOkay)
+ {
+ void *pvData = NULL;
+ size_t cbData = 0;
+ HRESULT hrc3 = i_readTailProcessingGetManifestData(&pvData, &cbData);
+ if (SUCCEEDED(hrc3))
+ {
+ hrc3 = i_readTailProcessingVerifyContentInfoCerts(pvData, cbData, hTrustedCerts, &StaticErrInfo);
+ RTMemTmpFree(pvData);
+ }
+ if (FAILED(hrc3) && SUCCEEDED(hrc2))
+ hrc2 = hrc3;
+ }
+ RTCrStoreRelease(hTrustedCerts);
+ }
+ else
+ hrc2 = setErrorBoth(E_FAIL, vrc,
+ tr("Failed to query trusted CAs and Certificates from the system and for the current user (%Rrc%RTeim)"),
+ vrc, &StaticErrInfo.Core);
+
+ /* Merge statuses from signature and certificate validation, prefering the signature one. */
+ if (SUCCEEDED(hrc) && FAILED(hrc2))
+ hrc = hrc2;
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /** @todo provide details about the signatory, signature, etc. */
+ if (m->fSignerCertLoaded)
+ {
+ /** @todo PKCS7/CMS certs too */
+ m->ptrCertificateInfo.createObject();
+ m->ptrCertificateInfo->initCertificate(&m->SignerCert,
+ m->fCertificateValid && !m->fCertificateMissingPath,
+ !m->fCertificateValidTime);
+ }
+
+ /*
+ * If there is a manifest, check that the OVF digest matches up (if present).
+ */
+
+ NOREF(pTask);
+ return S_OK;
+}
+
+/**
+ * Reads hMemFileTheirManifest into a memory buffer so it can be passed to
+ * RTCrPkcs7VerifySignedDataWithExternalData.
+ *
+ * Use RTMemTmpFree to free the memory.
+ */
+HRESULT Appliance::i_readTailProcessingGetManifestData(void **ppvData, size_t *pcbData)
+{
+ uint64_t cbData;
+ int vrc = RTVfsFileQuerySize(m->hMemFileTheirManifest, &cbData);
+ AssertRCReturn(vrc, setErrorVrc(vrc, "RTVfsFileQuerySize"));
+
+ void *pvData = RTMemTmpAllocZ((size_t)cbData);
+ AssertPtrReturn(pvData, E_OUTOFMEMORY);
+
+ vrc = RTVfsFileReadAt(m->hMemFileTheirManifest, 0, pvData, (size_t)cbData, NULL);
+ AssertRCReturnStmt(vrc, RTMemTmpFree(pvData), setErrorVrc(vrc, "RTVfsFileReadAt"));
+
+ *pcbData = (size_t)cbData;
+ *ppvData = pvData;
+ return S_OK;
+}
+
+/**
+ * Worker for i_readTailProcessing that validates the signedData.
+ *
+ * If we have a PKCS#7/CMS signature:
+ * - validate it
+ * - check that the OVF certificate matches the first signerInfo entry
+ * - verify the signature, but leave the certificate path validation for
+ * later.
+ *
+ * @param pErrInfo Static error info buffer (not for returning, just for
+ * avoiding wasting stack).
+ * @returns COM status.
+ * @throws Nothing!
+ */
+HRESULT Appliance::i_readTailProcessingSignedData(PRTERRINFOSTATIC pErrInfo)
+{
+ m->fContentInfoOkay = false;
+ m->fContentInfoSameCert = false;
+ m->fContentInfoValidSignature = false;
+
+ if (!m->fContentInfoLoaded)
+ return S_OK;
+
+ /*
+ * Validate it.
+ */
+ HRESULT hrc = S_OK;
+ PCRTCRPKCS7SIGNEDDATA pSignedData = m->ContentInfo.u.pSignedData;
+ if (!RTCrPkcs7ContentInfo_IsSignedData(&m->ContentInfo))
+ i_addWarning(tr("Invalid PKCS#7/CMS type: %s, expected %s (signedData)"),
+ m->ContentInfo.ContentType.szObjId, RTCRPKCS7SIGNEDDATA_OID);
+ else if (RTAsn1ObjId_CompareWithString(&pSignedData->ContentInfo.ContentType, RTCR_PKCS7_DATA_OID) != 0)
+ i_addWarning(tr("Invalid PKCS#7/CMS inner type: %s, expected %s (data)"),
+ pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID);
+ else if (RTAsn1OctetString_IsPresent(&pSignedData->ContentInfo.Content))
+ i_addWarning(tr("Invalid PKCS#7/CMS data: embedded (%u bytes), expected external","",
+ pSignedData->ContentInfo.Content.Asn1Core.cb),
+ pSignedData->ContentInfo.Content.Asn1Core.cb);
+ else if (pSignedData->SignerInfos.cItems == 0)
+ i_addWarning(tr("Invalid PKCS#7/CMS: No signers"));
+ else
+ {
+ m->fContentInfoOkay = true;
+
+ /*
+ * Same certificate as the OVF signature?
+ */
+ PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[0];
+ if ( RTCrX509Name_Compare(&pSignerInfo->IssuerAndSerialNumber.Name, &m->SignerCert.TbsCertificate.Issuer) == 0
+ && RTAsn1Integer_Compare(&pSignerInfo->IssuerAndSerialNumber.SerialNumber,
+ &m->SignerCert.TbsCertificate.SerialNumber) == 0)
+ m->fContentInfoSameCert = true;
+ else
+ i_addWarning(tr("Invalid PKCS#7/CMS: Using a different certificate"));
+
+ /*
+ * Then perform a validation of the signatures, but first without
+ * validating the certificate trust paths yet.
+ */
+ RTCRSTORE hTrustedCerts = NIL_RTCRSTORE;
+ int vrc = RTCrStoreCreateInMem(&hTrustedCerts, 1);
+ AssertRCReturn(vrc, setErrorVrc(vrc, tr("RTCrStoreCreateInMem failed: %Rrc"), vrc));
+
+ vrc = RTCrStoreCertAddX509(hTrustedCerts, 0, &m->SignerCert, RTErrInfoInitStatic(pErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ void *pvData = NULL;
+ size_t cbData = 0;
+ hrc = i_readTailProcessingGetManifestData(&pvData, &cbData);
+ if (SUCCEEDED(hrc))
+ {
+ RTTIMESPEC Now;
+ vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_TRUST_ALL_CERTS,
+ NIL_RTCRSTORE /*hAdditionalCerts*/, hTrustedCerts,
+ RTTimeNow(&Now), NULL /*pfnVerifyCert*/, NULL /*pvUser*/,
+ pvData, cbData, RTErrInfoInitStatic(pErrInfo));
+ if (RT_SUCCESS(vrc))
+ m->fContentInfoValidSignature = true;
+ else
+ i_addWarning(tr("Failed to validate PKCS#7/CMS signature: %Rrc%RTeim"), vrc, &pErrInfo->Core);
+ RTMemTmpFree(pvData);
+ }
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("RTCrStoreCertAddX509 failed: %Rrc%RTeim"), vrc, &pErrInfo->Core);
+ RTCrStoreRelease(hTrustedCerts);
+ }
+
+ return hrc;
+}
+
+
+/**
+ * Worker for i_readTailProcessing that verifies a self signed certificate when
+ * no PKCS\#7/CMS signature using the same certificate is present.
+ */
+HRESULT Appliance::i_readTailProcessingVerifySelfSignedOvfCert(TaskOVF *pTask, RTCRSTORE hTrustedStore, PRTERRINFOSTATIC pErrInfo)
+{
+ /*
+ * It's a self signed certificate. We assume the frontend will
+ * present this fact to the user and give a choice whether this
+ * is acceptable. But, first make sure it makes internal sense.
+ */
+ m->fCertificateMissingPath = true;
+ PCRTCRCERTCTX pCertCtx = RTCrStoreCertByIssuerAndSerialNo(hTrustedStore, &m->SignerCert.TbsCertificate.Issuer,
+ &m->SignerCert.TbsCertificate.SerialNumber);
+ if (pCertCtx)
+ {
+ if (pCertCtx->pCert && RTCrX509Certificate_Compare(pCertCtx->pCert, &m->SignerCert) == 0)
+ m->fCertificateMissingPath = true;
+ RTCrCertCtxRelease(pCertCtx);
+ }
+
+ int vrc = RTCrX509Certificate_VerifySignatureSelfSigned(&m->SignerCert, RTErrInfoInitStatic(pErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ m->fCertificateValid = true;
+
+ /* Check whether the certificate is currently valid, just warn if not. */
+ RTTIMESPEC Now;
+ m->fCertificateValidTime = RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now));
+ if (m->fCertificateValidTime)
+ {
+ m->fCertificateValidTime = true;
+ i_addWarning(tr("A self signed certificate was used to sign '%s'"), pTask->locInfo.strPath.c_str());
+ }
+ else
+ i_addWarning(tr("Self signed certificate used to sign '%s' is not currently valid"),
+ pTask->locInfo.strPath.c_str());
+ }
+ else
+ {
+ m->strCertError.printfNoThrow(tr("Verification of the self signed certificate failed (%Rrc%#RTeim)"),
+ vrc, &pErrInfo->Core);
+ i_addWarning(tr("Verification of the self signed certificate used to sign '%s' failed (%Rrc)%RTeim"),
+ pTask->locInfo.strPath.c_str(), vrc, &pErrInfo->Core);
+ }
+
+ /* Just warn if it's not a CA. Self-signed certificates are
+ hardly trustworthy to start with without the user's consent. */
+ if ( !m->SignerCert.TbsCertificate.T3.pBasicConstraints
+ || !m->SignerCert.TbsCertificate.T3.pBasicConstraints->CA.fValue)
+ i_addWarning(tr("Self signed certificate used to sign '%s' is not marked as certificate authority (CA)"),
+ pTask->locInfo.strPath.c_str());
+
+ return S_OK;
+}
+
+/**
+ * Worker for i_readTailProcessing that verfies a non-self-issued OVF
+ * certificate when no PKCS\#7/CMS signature using the same certificate is
+ * present.
+ */
+HRESULT Appliance::i_readTailProcessingVerifyIssuedOvfCert(TaskOVF *pTask, RTCRSTORE hTrustedStore, PRTERRINFOSTATIC pErrInfo)
+{
+ /*
+ * The certificate is not self-signed. Use the system certificate
+ * stores to try build a path that validates successfully.
+ */
+ HRESULT hrc = S_OK;
+ RTCRX509CERTPATHS hCertPaths;
+ int vrc = RTCrX509CertPathsCreate(&hCertPaths, &m->SignerCert);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Get trusted certificates from the system and add them to the path finding mission. */
+ vrc = RTCrX509CertPathsSetTrustedStore(hCertPaths, hTrustedStore);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(E_FAIL, vrc, tr("RTCrX509CertPathsSetTrustedStore failed (%Rrc)"), vrc);
+
+ /* Add untrusted intermediate certificates. */
+ if (RT_SUCCESS(vrc))
+ {
+ /// @todo RTCrX509CertPathsSetUntrustedStore(hCertPaths, hAdditionalCerts);
+ /// We should look for intermediate certificates on the system, at least.
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Do the building and verification of certificate paths.
+ */
+ vrc = RTCrX509CertPathsBuild(hCertPaths, RTErrInfoInitStatic(pErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(pErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Mark the certificate as good.
+ */
+ /** @todo check the certificate purpose? If so, share with self-signed. */
+ m->fCertificateValid = true;
+ m->fCertificateMissingPath = false;
+
+ /*
+ * We add a warning if the certificate path isn't valid at the current
+ * time. Since the time is only considered during path validation and we
+ * can repeat the validation process (but not building), it's easy to check.
+ */
+ RTTIMESPEC Now;
+ vrc = RTCrX509CertPathsSetValidTimeSpec(hCertPaths, RTTimeNow(&Now));
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, RTErrInfoInitStatic(pErrInfo));
+ if (RT_SUCCESS(vrc))
+ m->fCertificateValidTime = true;
+ else
+ i_addWarning(tr("The certificate used to sign '%s' (or a certificate in the path) is not currently valid (%Rrc)"),
+ pTask->locInfo.strPath.c_str(), vrc);
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("RTCrX509CertPathsSetValidTimeSpec failed: %Rrc"), vrc);
+ }
+ else if (vrc == VERR_CR_X509_CPV_NO_TRUSTED_PATHS)
+ {
+ m->fCertificateValid = true;
+ i_addWarning(tr("No trusted certificate paths"));
+
+ /* Add another warning if the pathless certificate is not valid at present. */
+ RTTIMESPEC Now;
+ if (RTCrX509Validity_IsValidAtTimeSpec(&m->SignerCert.TbsCertificate.Validity, RTTimeNow(&Now)))
+ m->fCertificateValidTime = true;
+ else
+ i_addWarning(tr("The certificate used to sign '%s' is not currently valid"),
+ pTask->locInfo.strPath.c_str());
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Certificate path validation failed (%Rrc%RTeim)"), vrc, &pErrInfo->Core);
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Certificate path building failed (%Rrc%RTeim)"), vrc, &pErrInfo->Core);
+ }
+ RTCrX509CertPathsRelease(hCertPaths);
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("RTCrX509CertPathsCreate failed: %Rrc"), vrc);
+ return hrc;
+}
+
+/**
+ * Helper for i_readTailProcessingVerifySignerInfo that reports a verfication
+ * failure.
+ *
+ * @returns S_OK
+ */
+HRESULT Appliance::i_readTailProcessingVerifyContentInfoFailOne(const char *pszSignature, int vrc, PRTERRINFOSTATIC pErrInfo)
+{
+ i_addWarning(tr("%s verification failed: %Rrc%RTeim"), pszSignature, vrc, &pErrInfo->Core);
+ if (m->strCertError.isEmpty())
+ m->strCertError.printfNoThrow(tr("%s verification failed: %Rrc%RTeim"), pszSignature, vrc, &pErrInfo->Core);
+ return S_OK;
+}
+
+/**
+ * Worker for i_readTailProcessingVerifyContentInfoCerts that analyzes why the
+ * standard verification of a signer info entry failed (@a vrc & @a pErrInfo).
+ *
+ * There are a couple of things we might want try to investigate deeper here:
+ * 1. Untrusted signing certificate, often self-signed.
+ * 2. Untrusted timstamp signing certificate.
+ * 3. Certificate not valid at the current time and there isn't a
+ * timestamp counter signature.
+ *
+ * That said, it is difficult to get an accurate fix and report on the
+ * issues here since there are a number of error sources, so just try identify
+ * the more typical cases.
+ *
+ * @note Caller cleans up *phTrustedStore2 if not NIL.
+ */
+HRESULT Appliance::i_readTailProcessingVerifyAnalyzeSignerInfo(void const *pvData, size_t cbData, RTCRSTORE hTrustedStore,
+ uint32_t iSigner, PRTTIMESPEC pNow, int vrc,
+ PRTERRINFOSTATIC pErrInfo, PRTCRSTORE phTrustedStore2)
+{
+ PRTCRPKCS7SIGNEDDATA const pSignedData = m->ContentInfo.u.pSignedData;
+ PRTCRPKCS7SIGNERINFO const pSigner = pSignedData->SignerInfos.papItems[iSigner];
+
+ /*
+ * Error/warning message prefix:
+ */
+ const char *pszSignature;
+ if (iSigner == 0 && m->fContentInfoSameCert)
+ pszSignature = tr("OVF & PKCS#7/CMS signature");
+ else
+ pszSignature = tr("PKCS#7/CMS signature");
+ char szSignatureBuf[64];
+ if (pSignedData->SignerInfos.cItems > 1)
+ {
+ RTStrPrintf(szSignatureBuf, sizeof(szSignatureBuf), "%s #%u", pszSignature, iSigner + 1);
+ pszSignature = szSignatureBuf;
+ }
+
+ /*
+ * Don't try handle weird stuff:
+ */
+ /** @todo Are there more statuses we can deal with here? */
+ if ( vrc != VERR_CR_X509_CPV_NOT_VALID_AT_TIME
+ && vrc != VERR_CR_X509_NO_TRUST_ANCHOR)
+ return i_readTailProcessingVerifyContentInfoFailOne(pszSignature, vrc, pErrInfo);
+
+ /*
+ * Find the signing certificate.
+ * We require the certificate to be included in the signed data here.
+ */
+ PCRTCRX509CERTIFICATE pSigningCert;
+ pSigningCert = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(&pSignedData->Certificates,
+ &pSigner->IssuerAndSerialNumber.Name,
+ &pSigner->IssuerAndSerialNumber.SerialNumber);
+ if (!pSigningCert)
+ {
+ i_addWarning(tr("PKCS#7/CMS signature #%u does not include the signing certificate"), iSigner + 1);
+ if (m->strCertError.isEmpty())
+ m->strCertError.printfNoThrow(tr("PKCS#7/CMS signature #%u does not include the signing certificate"), iSigner + 1);
+ return S_OK;
+ }
+
+ PCRTCRCERTCTX const pCertCtxTrusted = RTCrStoreCertByIssuerAndSerialNo(hTrustedStore, &pSigner->IssuerAndSerialNumber.Name,
+ &pSigner->IssuerAndSerialNumber.SerialNumber);
+ bool const fSelfSigned = RTCrX509Certificate_IsSelfSigned(pSigningCert);
+
+ /*
+ * Add warning about untrusted self-signed certificate:
+ */
+ if (fSelfSigned && !pCertCtxTrusted)
+ i_addWarning(tr("%s: Untrusted self-signed certificate"), pszSignature);
+
+ /*
+ * Start by eliminating signing time issues (2 + 3) first as primary problem.
+ * Keep the error info and status for later failures.
+ */
+ char szTime[RTTIME_STR_LEN];
+ RTTIMESPEC Now2 = *pNow;
+ vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED
+ | RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME
+ | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner)
+ | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS, NIL_RTCRSTORE,
+ hTrustedStore, &Now2, NULL, NULL,
+ pvData, cbData, RTErrInfoInitStatic(pErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ /* Okay, is it an untrusted time signing certificate or just signing time in general? */
+ RTTIMESPEC Now3 = *pNow;
+ vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED
+ | RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
+ | RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME
+ | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner)
+ | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS, NIL_RTCRSTORE,
+ hTrustedStore, &Now3, NULL, NULL, pvData, cbData, NULL);
+ if (RT_SUCCESS(vrc))
+ i_addWarning(tr("%s: Untrusted timestamp (%s)"), pszSignature, RTTimeSpecToString(&Now3, szTime, sizeof(szTime)));
+ else
+ i_addWarning(tr("%s: Not valid at current time, but validates fine for untrusted signing time (%s)"),
+ pszSignature, RTTimeSpecToString(&Now2, szTime, sizeof(szTime)));
+ return S_OK;
+ }
+
+ /* If we've got a trusted signing certificate (unlikely, but whatever), we can stop already.
+ If we haven't got a self-signed certificate, stop too as messaging becomes complicated otherwise. */
+ if (pCertCtxTrusted || !fSelfSigned)
+ return i_readTailProcessingVerifyContentInfoFailOne(pszSignature, vrc, pErrInfo);
+
+ int const vrcErrInfo = vrc;
+
+ /*
+ * Create a new trust store that includes the signing certificate
+ * to see what that changes.
+ */
+ vrc = RTCrStoreCreateInMemEx(phTrustedStore2, 1, hTrustedStore);
+ AssertRCReturn(vrc, setErrorVrc(vrc, "RTCrStoreCreateInMemEx"));
+ vrc = RTCrStoreCertAddX509(*phTrustedStore2, 0, (PRTCRX509CERTIFICATE)pSigningCert, NULL);
+ AssertRCReturn(vrc, setErrorVrc(vrc, "RTCrStoreCertAddX509/%u", iSigner));
+
+ vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo,
+ RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
+ | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner)
+ | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS, NIL_RTCRSTORE,
+ *phTrustedStore2, pNow, NULL, NULL, pvData, cbData, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!fSelfSigned)
+ i_readTailProcessingVerifyContentInfoFailOne(pszSignature, vrcErrInfo, pErrInfo);
+ return S_OK;
+ }
+
+ /*
+ * Time problems too? Repeat what we did above, but with the modified trust store.
+ */
+ Now2 = *pNow;
+ vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED
+ | RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME
+ | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner)
+ | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS, NIL_RTCRSTORE,
+ *phTrustedStore2, pNow, NULL, NULL, pvData, cbData, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Okay, is it an untrusted time signing certificate or just signing time in general? */
+ RTTIMESPEC Now3 = *pNow;
+ vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo, RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED
+ | RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
+ | RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME
+ | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner)
+ | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS, NIL_RTCRSTORE,
+ *phTrustedStore2, &Now3, NULL, NULL, pvData, cbData, NULL);
+ if (RT_SUCCESS(vrc))
+ i_addWarning(tr("%s: Untrusted timestamp (%s)"), pszSignature, RTTimeSpecToString(&Now3, szTime, sizeof(szTime)));
+ else
+ i_addWarning(tr("%s: Not valid at current time, but validates fine for untrusted signing time (%s)"),
+ pszSignature, RTTimeSpecToString(&Now2, szTime, sizeof(szTime)));
+ }
+ else
+ i_readTailProcessingVerifyContentInfoFailOne(pszSignature, vrcErrInfo, pErrInfo);
+
+ return S_OK;
+}
+
+/**
+ * Verify the signing certificates used to sign the PKCS\#7/CMS signature.
+ *
+ * ASSUMES that we've previously verified the PKCS\#7/CMS stuff in
+ * trust-all-certs-without-question mode and it's just the certificate
+ * validation that can fail now.
+ */
+HRESULT Appliance::i_readTailProcessingVerifyContentInfoCerts(void const *pvData, size_t cbData,
+ RTCRSTORE hTrustedStore, PRTERRINFOSTATIC pErrInfo)
+{
+ /*
+ * Just do a run and see what happens (note we've already verified
+ * the data signatures, which just leaves certificates and paths).
+ */
+ RTTIMESPEC Now;
+ int vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo,
+ RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
+ | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS,
+ NIL_RTCRSTORE /*hAdditionalCerts*/, hTrustedStore,
+ RTTimeNow(&Now), NULL /*pfnVerifyCert*/, NULL /*pvUser*/,
+ pvData, cbData, RTErrInfoInitStatic(pErrInfo));
+ if (RT_SUCCESS(vrc))
+ m->fContentInfoVerifiedOkay = true;
+ else
+ {
+ /*
+ * Deal with each of the signatures separately to try figure out
+ * more exactly what's going wrong.
+ */
+ uint32_t cVerifiedOkay = 0;
+ PRTCRPKCS7SIGNEDDATA pSignedData = m->ContentInfo.u.pSignedData;
+ for (uint32_t iSigner = 0; iSigner < pSignedData->SignerInfos.cItems; iSigner++)
+ {
+ vrc = RTCrPkcs7VerifySignedDataWithExternalData(&m->ContentInfo,
+ RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
+ | RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX(iSigner)
+ | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS,
+ NIL_RTCRSTORE /*hAdditionalCerts*/, hTrustedStore,
+ &Now, NULL /*pfnVerifyCert*/, NULL /*pvUser*/,
+ pvData, cbData, RTErrInfoInitStatic(pErrInfo));
+ if (RT_SUCCESS(vrc))
+ cVerifiedOkay++;
+ else
+ {
+ RTCRSTORE hTrustedStore2 = NIL_RTCRSTORE;
+ HRESULT hrc = i_readTailProcessingVerifyAnalyzeSignerInfo(pvData, cbData, hTrustedStore, iSigner, &Now,
+ vrc, pErrInfo, &hTrustedStore2);
+ RTCrStoreRelease(hTrustedStore2);
+ if (FAILED(hrc))
+ return hrc;
+ }
+ }
+
+ if ( pSignedData->SignerInfos.cItems > 1
+ && pSignedData->SignerInfos.cItems != cVerifiedOkay)
+ i_addWarning(tr("%u out of %u PKCS#7/CMS signatures verfified okay", "",
+ pSignedData->SignerInfos.cItems),
+ cVerifiedOkay, pSignedData->SignerInfos.cItems);
+ }
+
+ return S_OK;
+}
+
+
+
+/*******************************************************************************
+ * Import stuff
+ ******************************************************************************/
+
+/**
+ * Implementation for importing OVF data into VirtualBox. This starts a new thread which will call
+ * Appliance::taskThreadImportOrExport().
+ *
+ * This creates one or more new machines according to the VirtualSystemScription instances created by
+ * Appliance::Interpret().
+ *
+ * This is in a separate private method because it is used from one location:
+ *
+ * 1) from the public Appliance::ImportMachines().
+ *
+ * @param locInfo
+ * @param progress
+ * @return
+ */
+HRESULT Appliance::i_importImpl(const LocationInfo &locInfo,
+ ComObjPtr<Progress> &progress)
+{
+ HRESULT rc;
+
+ /* Initialize our worker task */
+ ThreadTask *pTask;
+ if (locInfo.storageType != VFSType_Cloud)
+ {
+ rc = i_setUpProgress(progress, Utf8StrFmt(tr("Importing appliance '%s'"), locInfo.strPath.c_str()),
+ locInfo.storageType == VFSType_File ? ImportFile : ImportS3);
+ if (FAILED(rc))
+ return setError(rc, tr("Failed to create task for importing appliance into VirtualBox"));
+ try
+ {
+ pTask = new TaskOVF(this, TaskOVF::Import, locInfo, progress);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+ else
+ {
+ if (locInfo.strProvider.equals("OCI"))
+ {
+ /*
+ * 1. Create a custom image from the instance:
+ * - 2 operations (starting and waiting)
+ * 2. Import the custom image into the Object Storage (OCI format - TAR file with QCOW2 image and JSON file):
+ * - 2 operations (starting and waiting)
+ * 3. Download the object from the Object Storage:
+ * - 1 operation (starting and downloadind is one operation)
+ * 4. Open the object, extract an image and convert one to VDI:
+ * - 1 operation (extracting and conversion are piped) because only 1 base bootable image is imported for now
+ * 5. Create VM with user settings and attach the converted image to VM:
+ * - 1 operation.
+ * 6. Cleanup phase.
+ * - 1 to N operations.
+ * The number of the correct Progress operations are much tricky here.
+ * Whether Machine::deleteConfig() is called or Medium::deleteStorage() is called in the loop.
+ * Both require a new Progress object. To work with these functions the original Progress object uses
+ * the function Progress::waitForOtherProgressCompletion().
+ *
+ * Some speculation here...
+ * Total: 2+2+1(cloud) + 1+1(local) + 1+1+1(cleanup) = 10 operations
+ * or
+ * Total: 2+2+1(cloud) + 1+1(local) + 1(cleanup) = 8 operations
+ * if VM wasn't created we would have only 1 registered image for cleanup.
+ *
+ * Weight "#define"s for the Cloud operations are located in the file OCICloudClient.h.
+ * Weight of cloud import operations (1-3 items from above):
+ * Total = 750 = 25+75(start and wait)+25+375(start and wait)+250(download)
+ *
+ * Weight of local import operations (4-5 items from above):
+ * Total = 150 = 100 (extract and convert) + 50 (create VM, attach disks)
+ *
+ * Weight of local cleanup operations (6 item from above):
+ * Some speculation here...
+ * Total = 3 = 1 (1 image) + 1 (1 setting file)+ 1 (1 prev setting file) - quick operations
+ * or
+ * Total = 1 (1 image) if VM wasn't created we would have only 1 registered image for now.
+ */
+ try
+ {
+ rc = progress.createObject();
+ if (SUCCEEDED(rc))
+ rc = progress->init(mVirtualBox, static_cast<IAppliance *>(this),
+ Utf8Str(tr("Importing VM from Cloud...")),
+ TRUE /* aCancelable */,
+ 10, // ULONG cOperations,
+ 1000, // ULONG ulTotalOperationsWeight,
+ Utf8Str(tr("Start import VM from the Cloud...")), // aFirstOperationDescription
+ 25); // ULONG ulFirstOperationWeight
+ if (SUCCEEDED(rc))
+ pTask = new TaskCloud(this, TaskCloud::Import, locInfo, progress);
+ else
+ pTask = NULL; /* shut up vcc */
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ if (FAILED(rc))
+ return setError(rc, tr("Failed to create task for importing appliance into VirtualBox"));
+ }
+ else
+ return setError(E_NOTIMPL, tr("Only \"OCI\" cloud provider is supported for now. \"%s\" isn't supported."),
+ locInfo.strProvider.c_str());
+ }
+
+ /*
+ * Start the task thread.
+ */
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(rc))
+ return rc;
+ return setError(rc, tr("Failed to start thread for importing appliance into VirtualBox"));
+}
+
+/**
+ * Actual worker code for importing OVF data into VirtualBox.
+ *
+ * This is called from Appliance::taskThreadImportOrExport() and therefore runs
+ * on the OVF import worker thread. This creates one or more new machines
+ * according to the VirtualSystemScription instances created by
+ * Appliance::Interpret().
+ *
+ * This runs in two contexts:
+ *
+ * 1) in a first worker thread; in that case, Appliance::ImportMachines() called
+ * Appliance::i_importImpl();
+ *
+ * 2) in a second worker thread; in that case, Appliance::ImportMachines()
+ * called Appliance::i_importImpl(), which called Appliance::i_importFSOVA(),
+ * which called Appliance::i_importImpl(), which then called this again.
+ *
+ * @param pTask The OVF task data.
+ * @return COM status code.
+ */
+HRESULT Appliance::i_importFS(TaskOVF *pTask)
+{
+ LogFlowFuncEnter();
+ LogFlowFunc(("Appliance %p\n", this));
+
+ /* Change the appliance state so we can safely leave the lock while doing
+ * time-consuming image imports; also the below method calls do all kinds of
+ * locking which conflicts with the appliance object lock. */
+ AutoWriteLock writeLock(this COMMA_LOCKVAL_SRC_POS);
+ /* Check if the appliance is currently busy. */
+ if (!i_isApplianceIdle())
+ return E_ACCESSDENIED;
+ /* Set the internal state to importing. */
+ m->state = ApplianceImporting;
+
+ HRESULT rc = S_OK;
+
+ /* Clear the list of imported machines, if any */
+ m->llGuidsMachinesCreated.clear();
+
+ if (pTask->locInfo.strPath.endsWith(".ovf", Utf8Str::CaseInsensitive))
+ rc = i_importFSOVF(pTask, writeLock);
+ else
+ rc = i_importFSOVA(pTask, writeLock);
+ if (FAILED(rc))
+ {
+ /* With _whatever_ error we've had, do a complete roll-back of
+ * machines and images we've created */
+ writeLock.release();
+ ErrorInfoKeeper eik;
+ for (list<Guid>::iterator itID = m->llGuidsMachinesCreated.begin();
+ itID != m->llGuidsMachinesCreated.end();
+ ++itID)
+ {
+ Guid guid = *itID;
+ Bstr bstrGuid = guid.toUtf16();
+ ComPtr<IMachine> failedMachine;
+ HRESULT rc2 = mVirtualBox->FindMachine(bstrGuid.raw(), failedMachine.asOutParam());
+ if (SUCCEEDED(rc2))
+ {
+ SafeIfaceArray<IMedium> aMedia;
+ rc2 = failedMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(aMedia));
+ ComPtr<IProgress> pProgress2;
+ rc2 = failedMachine->DeleteConfig(ComSafeArrayAsInParam(aMedia), pProgress2.asOutParam());
+ pProgress2->WaitForCompletion(-1);
+ }
+ }
+ writeLock.acquire();
+ }
+
+ /* Reset the state so others can call methods again */
+ m->state = ApplianceIdle;
+
+ LogFlowFunc(("rc=%Rhrc\n", rc));
+ LogFlowFuncLeave();
+ return rc;
+}
+
+HRESULT Appliance::i_importFSOVF(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
+{
+ return i_importDoIt(pTask, rWriteLock);
+}
+
+HRESULT Appliance::i_importFSOVA(TaskOVF *pTask, AutoWriteLockBase &rWriteLock)
+{
+ LogFlowFuncEnter();
+
+ /*
+ * Open the tar file as file stream.
+ */
+ RTVFSIOSTREAM hVfsIosOva;
+ int vrc = RTVfsIoStrmOpenNormal(pTask->locInfo.strPath.c_str(),
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsIosOva);
+ if (RT_FAILURE(vrc))
+ return setErrorVrc(vrc, tr("Error opening the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
+
+ RTVFSFSSTREAM hVfsFssOva;
+ vrc = RTZipTarFsStreamFromIoStream(hVfsIosOva, 0 /*fFlags*/, &hVfsFssOva);
+ RTVfsIoStrmRelease(hVfsIosOva);
+ if (RT_FAILURE(vrc))
+ return setErrorVrc(vrc, tr("Error reading the OVA file '%s' (%Rrc)"), pTask->locInfo.strPath.c_str(), vrc);
+
+ /*
+ * Join paths with the i_importFSOVF code.
+ *
+ * Note! We don't need to skip the OVF, manifest or signature files, as the
+ * i_importMachineGeneric, i_importVBoxMachine and i_importOpenSourceFile
+ * code will deal with this (as there could be other files in the OVA
+ * that we don't process, like 'de-DE-resources.xml' in EXAMPLE 1,
+ * Appendix D.1, OVF v2.1.0).
+ */
+ HRESULT hrc = i_importDoIt(pTask, rWriteLock, hVfsFssOva);
+
+ RTVfsFsStrmRelease(hVfsFssOva);
+
+ LogFlowFunc(("returns %Rhrc\n", hrc));
+ return hrc;
+}
+
+/**
+ * Does the actual importing after the caller has made the source accessible.
+ *
+ * @param pTask The import task.
+ * @param rWriteLock The write lock the caller's caller is holding,
+ * will be released for some reason.
+ * @param hVfsFssOva The file system stream if OVA, NIL if not.
+ * @returns COM status code.
+ * @throws Nothing.
+ */
+HRESULT Appliance::i_importDoIt(TaskOVF *pTask, AutoWriteLockBase &rWriteLock, RTVFSFSSTREAM hVfsFssOva /*= NIL_RTVFSFSSTREAM*/)
+{
+ rWriteLock.release();
+
+ HRESULT hrc = E_FAIL;
+ try
+ {
+ /*
+ * Create the import stack for the rollback on errors.
+ */
+ ImportStack stack(pTask->locInfo, m->pReader->m_mapDisks, pTask->pProgress, hVfsFssOva);
+
+ try
+ {
+ /* Do the importing. */
+ i_importMachines(stack);
+
+ /* We should've processed all the files now, so compare. */
+ hrc = i_verifyManifestFile(stack);
+
+ /* If everything was successful so far check if some extension
+ * pack wants to do file sanity checking. */
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo */;
+ }
+ }
+ catch (HRESULT hrcXcpt)
+ {
+ hrc = hrcXcpt;
+ }
+ catch (...)
+ {
+ AssertFailed();
+ hrc = E_FAIL;
+ }
+ if (FAILED(hrc))
+ {
+ /*
+ * Restoring original UUID from OVF description file.
+ * During import VBox creates new UUIDs for imported images and
+ * assigns them to the images. In case of failure we have to restore
+ * the original UUIDs because those new UUIDs are obsolete now and
+ * won't be used anymore.
+ */
+ ErrorInfoKeeper eik; /* paranoia */
+ list< ComObjPtr<VirtualSystemDescription> >::const_iterator itvsd;
+ /* Iterate through all virtual systems of that appliance */
+ for (itvsd = m->virtualSystemDescriptions.begin();
+ itvsd != m->virtualSystemDescriptions.end();
+ ++itvsd)
+ {
+ ComObjPtr<VirtualSystemDescription> vsdescThis = (*itvsd);
+ settings::MachineConfigFile *pConfig = vsdescThis->m->pConfig;
+ if(vsdescThis->m->pConfig!=NULL)
+ stack.restoreOriginalUUIDOfAttachedDevice(pConfig);
+ }
+ }
+ }
+ catch (...)
+ {
+ hrc = E_FAIL;
+ AssertFailed();
+ }
+
+ rWriteLock.acquire();
+ return hrc;
+}
+
+/**
+ * Undocumented, you figure it from the name.
+ *
+ * @returns Undocumented
+ * @param stack Undocumented.
+ */
+HRESULT Appliance::i_verifyManifestFile(ImportStack &stack)
+{
+ LogFlowThisFuncEnter();
+ HRESULT hrc;
+ int vrc;
+
+ /*
+ * No manifest is fine, it always matches.
+ */
+ if (m->hTheirManifest == NIL_RTMANIFEST)
+ hrc = S_OK;
+ else
+ {
+ /*
+ * Hack: If the manifest we just read doesn't have a digest for the OVF, copy
+ * it from the manifest we got from the caller.
+ * @bugref{6022#c119}
+ */
+ if ( !RTManifestEntryExists(m->hTheirManifest, m->strOvfManifestEntry.c_str())
+ && RTManifestEntryExists(m->hOurManifest, m->strOvfManifestEntry.c_str()) )
+ {
+ uint32_t fType = 0;
+ char szDigest[512 + 1];
+ vrc = RTManifestEntryQueryAttr(m->hOurManifest, m->strOvfManifestEntry.c_str(), NULL, RTMANIFEST_ATTR_ANY,
+ szDigest, sizeof(szDigest), &fType);
+ if (RT_SUCCESS(vrc))
+ vrc = RTManifestEntrySetAttr(m->hTheirManifest, m->strOvfManifestEntry.c_str(),
+ NULL /*pszAttr*/, szDigest, fType);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Error fudging missing OVF digest in manifest: %Rrc"), vrc);
+ }
+
+ /*
+ * Compare with the digests we've created while read/processing the import.
+ *
+ * We specify the RTMANIFEST_EQUALS_IGN_MISSING_ATTRS to ignore attributes
+ * (SHA1, SHA256, etc) that are only present in one of the manifests, as long
+ * as each entry has at least one common attribute that we can check. This
+ * is important for the OVF in OVAs, for which we generates several digests
+ * since we don't know which are actually used in the manifest (OVF comes
+ * first in an OVA, then manifest).
+ */
+ char szErr[256];
+ vrc = RTManifestEqualsEx(m->hTheirManifest, m->hOurManifest, NULL /*papszIgnoreEntries*/,
+ NULL /*papszIgnoreAttrs*/,
+ RTMANIFEST_EQUALS_IGN_MISSING_ATTRS | RTMANIFEST_EQUALS_IGN_MISSING_ENTRIES_2ND,
+ szErr, sizeof(szErr));
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorVrc(vrc, tr("Digest mismatch (%Rrc): %s"), vrc, szErr);
+ }
+
+ NOREF(stack);
+ LogFlowThisFunc(("returns %Rhrc\n", hrc));
+ return hrc;
+}
+
+/**
+ * Helper that converts VirtualSystem attachment values into VirtualBox attachment values.
+ * Throws HRESULT values on errors!
+ *
+ * @param hdc in: the HardDiskController structure to attach to.
+ * @param ulAddressOnParent in: the AddressOnParent parameter from OVF.
+ * @param controllerName out: the name of the storage controller to attach to (e.g. "IDE").
+ * @param lControllerPort out: the channel (controller port) of the controller to attach to.
+ * @param lDevice out: the device number to attach to.
+ */
+void Appliance::i_convertDiskAttachmentValues(const ovf::HardDiskController &hdc,
+ uint32_t ulAddressOnParent,
+ Utf8Str &controllerName,
+ int32_t &lControllerPort,
+ int32_t &lDevice)
+{
+ Log(("Appliance::i_convertDiskAttachmentValues: hdc.system=%d, hdc.fPrimary=%d, ulAddressOnParent=%d\n",
+ hdc.system,
+ hdc.fPrimary,
+ ulAddressOnParent));
+
+ switch (hdc.system)
+ {
+ case ovf::HardDiskController::IDE:
+ // For the IDE bus, the port parameter can be either 0 or 1, to specify the primary
+ // or secondary IDE controller, respectively. For the primary controller of the IDE bus,
+ // the device number can be either 0 or 1, to specify the master or the slave device,
+ // respectively. For the secondary IDE controller, the device number is always 1 because
+ // the master device is reserved for the CD-ROM drive.
+ controllerName = "IDE";
+ switch (ulAddressOnParent)
+ {
+ case 0: // master
+ if (!hdc.fPrimary)
+ {
+ // secondary master
+ lControllerPort = 1;
+ lDevice = 0;
+ }
+ else // primary master
+ {
+ lControllerPort = 0;
+ lDevice = 0;
+ }
+ break;
+
+ case 1: // slave
+ if (!hdc.fPrimary)
+ {
+ // secondary slave
+ lControllerPort = 1;
+ lDevice = 1;
+ }
+ else // primary slave
+ {
+ lControllerPort = 0;
+ lDevice = 1;
+ }
+ break;
+
+ // used by older VBox exports
+ case 2: // interpret this as secondary master
+ lControllerPort = 1;
+ lDevice = 0;
+ break;
+
+ // used by older VBox exports
+ case 3: // interpret this as secondary slave
+ lControllerPort = 1;
+ lDevice = 1;
+ break;
+
+ default:
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Invalid channel %RU32 specified; IDE controllers support only 0, 1 or 2"),
+ ulAddressOnParent);
+ break;
+ }
+ break;
+
+ case ovf::HardDiskController::SATA:
+ controllerName = "SATA";
+ lControllerPort = (int32_t)ulAddressOnParent;
+ lDevice = 0;
+ break;
+
+ case ovf::HardDiskController::SCSI:
+ {
+ if (hdc.strControllerType.compare("lsilogicsas")==0)
+ controllerName = "SAS";
+ else
+ controllerName = "SCSI";
+ lControllerPort = (int32_t)ulAddressOnParent;
+ lDevice = 0;
+ break;
+ }
+
+ case ovf::HardDiskController::VIRTIOSCSI:
+ controllerName = "VirtioSCSI";
+ lControllerPort = (int32_t)ulAddressOnParent;
+ lDevice = 0;
+ break;
+
+ default: break;
+ }
+
+ Log(("=> lControllerPort=%d, lDevice=%d\n", lControllerPort, lDevice));
+}
+
+/**
+ * Imports one image.
+ *
+ * This is common code shared between
+ * -- i_importMachineGeneric() for the OVF case; in that case the information comes from
+ * the OVF virtual systems;
+ * -- i_importVBoxMachine(); in that case, the information comes from the <vbox:Machine>
+ * tag.
+ *
+ * Both ways of describing machines use the OVF disk references section, so in both cases
+ * the caller needs to pass in the ovf::DiskImage structure from ovfreader.cpp.
+ *
+ * As a result, in both cases, if di.strHref is empty, we create a new image as per the OVF
+ * spec, even though this cannot really happen in the vbox:Machine case since such data
+ * would never have been exported.
+ *
+ * This advances stack.pProgress by one operation with the image's weight.
+ *
+ * @param di ovfreader.cpp structure describing the image from the OVF that is to be imported
+ * @param strDstPath Where to create the target image.
+ * @param pTargetMedium out: The newly created target medium. This also gets pushed on stack.llHardDisksCreated for cleanup.
+ * @param stack
+ *
+ * @throws HRESULT
+ */
+void Appliance::i_importOneDiskImage(const ovf::DiskImage &di,
+ const Utf8Str &strDstPath,
+ ComObjPtr<Medium> &pTargetMedium,
+ ImportStack &stack)
+{
+ HRESULT rc;
+
+ Utf8Str strAbsDstPath;
+ int vrc = RTPathAbsExCxx(strAbsDstPath, stack.strMachineFolder, strDstPath);
+ AssertRCStmt(vrc, throw Global::vboxStatusCodeToCOM(vrc));
+
+ /* Get the system properties. */
+ SystemProperties *pSysProps = mVirtualBox->i_getSystemProperties();
+
+ /* Keep the source file ref handy for later. */
+ const Utf8Str &strSourceOVF = di.strHref;
+
+ /* Construct source file path */
+ Utf8Str strSrcFilePath;
+ if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
+ strSrcFilePath = strSourceOVF;
+ else
+ {
+ strSrcFilePath = stack.strSourceDir;
+ strSrcFilePath.append(RTPATH_SLASH_STR);
+ strSrcFilePath.append(strSourceOVF);
+ }
+
+ /* First of all check if the original (non-absolute) destination path is
+ * a valid medium UUID. If so, the user wants to import the image into
+ * an existing path. This is useful for iSCSI for example. */
+ /** @todo r=klaus the code structure after this point is totally wrong,
+ * full of unnecessary code duplication and other issues. 4.2 still had
+ * the right structure for importing into existing medium objects, which
+ * the current code can't possibly handle. */
+ RTUUID uuid;
+ vrc = RTUuidFromStr(&uuid, strDstPath.c_str());
+ if (vrc == VINF_SUCCESS)
+ {
+ rc = mVirtualBox->i_findHardDiskById(Guid(uuid), true, &pTargetMedium);
+ if (FAILED(rc)) throw rc;
+ }
+ else
+ {
+ RTVFSIOSTREAM hVfsIosSrc = NIL_RTVFSIOSTREAM;
+
+ /* check read file to GZIP compression */
+ bool const fGzipped = di.strCompression.compare("gzip", Utf8Str::CaseInsensitive) == 0;
+ Utf8Str strDeleteTemp;
+ try
+ {
+ Utf8Str strTrgFormat = "VMDK";
+ ComObjPtr<MediumFormat> trgFormat;
+ Bstr bstrFormatName;
+ ULONG lCabs = 0;
+
+ char *pszSuff = RTPathSuffix(strAbsDstPath.c_str());
+ if (pszSuff != NULL)
+ {
+ /*
+ * Figure out which format the user like to have. Default is VMDK
+ * or it can be VDI if according command-line option is set
+ */
+
+ /*
+ * We need a proper target format
+ * if target format has been changed by user via GUI import wizard
+ * or via VBoxManage import command (option --importtovdi)
+ * then we need properly process such format like ISO
+ * Because there is no conversion ISO to VDI
+ */
+ trgFormat = pSysProps->i_mediumFormatFromExtension(++pszSuff);
+ if (trgFormat.isNull())
+ throw setError(E_FAIL, tr("Unsupported medium format for disk image '%s'"), di.strHref.c_str());
+
+ rc = trgFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ strTrgFormat = Utf8Str(bstrFormatName);
+
+ if ( m->optListImport.contains(ImportOptions_ImportToVDI)
+ && strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) != 0)
+ {
+ /* change the target extension */
+ strTrgFormat = "vdi";
+ trgFormat = pSysProps->i_mediumFormatFromExtension(strTrgFormat);
+ strAbsDstPath.stripSuffix();
+ strAbsDstPath.append(".");
+ strAbsDstPath.append(strTrgFormat.c_str());
+ }
+
+ /* Check the capabilities. We need create capabilities. */
+ lCabs = 0;
+ com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap;
+ rc = trgFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap));
+
+ if (FAILED(rc))
+ throw rc;
+
+ for (ULONG j = 0; j < mediumFormatCap.size(); j++)
+ lCabs |= mediumFormatCap[j];
+
+ if ( !(lCabs & MediumFormatCapabilities_CreateFixed)
+ && !(lCabs & MediumFormatCapabilities_CreateDynamic) )
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Could not find a valid medium format for the target disk '%s'"),
+ strAbsDstPath.c_str());
+ }
+ else
+ {
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("The target disk '%s' has no extension "),
+ strAbsDstPath.c_str(), VERR_INVALID_NAME);
+ }
+
+ /*CD/DVD case*/
+ if (strTrgFormat.compare("RAW", Utf8Str::CaseInsensitive) == 0)
+ {
+ try
+ {
+ if (fGzipped)
+ i_importDecompressFile(stack, strSrcFilePath, strAbsDstPath, strSourceOVF.c_str());
+ else
+ i_importCopyFile(stack, strSrcFilePath, strAbsDstPath, strSourceOVF.c_str());
+
+ ComPtr<IMedium> pTmp;
+ rc = mVirtualBox->OpenMedium(Bstr(strAbsDstPath).raw(),
+ DeviceType_DVD,
+ AccessMode_ReadWrite,
+ false,
+ pTmp.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+
+ IMedium *iM = pTmp;
+ pTargetMedium = static_cast<Medium*>(iM);
+ }
+ catch (HRESULT /*arc*/)
+ {
+ throw;
+ }
+
+ /* Advance to the next operation. */
+ /* operation's weight, as set up with the IProgress originally */
+ stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
+ RTPathFilename(strSourceOVF.c_str())).raw(),
+ di.ulSuggestedSizeMB);
+ }
+ else/* HDD case*/
+ {
+ /* Create an IMedium object. */
+ pTargetMedium.createObject();
+
+ rc = pTargetMedium->init(mVirtualBox,
+ strTrgFormat,
+ strAbsDstPath,
+ Guid::Empty /* media registry: none yet */,
+ DeviceType_HardDisk);
+ if (FAILED(rc)) throw rc;
+
+ ComPtr<IProgress> pProgressImport;
+ /* If strHref is empty we have to create a new file. */
+ if (strSourceOVF.isEmpty())
+ {
+ com::SafeArray<MediumVariant_T> mediumVariant;
+ mediumVariant.push_back(MediumVariant_Standard);
+
+ /* Kick off the creation of a dynamic growing disk image with the given capacity. */
+ rc = pTargetMedium->CreateBaseStorage(di.iCapacity / _1M,
+ ComSafeArrayAsInParam(mediumVariant),
+ pProgressImport.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ /* Advance to the next operation. */
+ /* operation's weight, as set up with the IProgress originally */
+ stack.pProgress->SetNextOperation(BstrFmt(tr("Creating disk image '%s'"),
+ strAbsDstPath.c_str()).raw(),
+ di.ulSuggestedSizeMB);
+ }
+ else
+ {
+ /* We need a proper source format description */
+ /* Which format to use? */
+ ComObjPtr<MediumFormat> srcFormat;
+ rc = i_findMediumFormatFromDiskImage(di, srcFormat);
+ if (FAILED(rc))
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Could not find a valid medium format for the source disk '%s' "
+ "Check correctness of the image format URL in the OVF description file "
+ "or extension of the image"),
+ RTPathFilename(strSourceOVF.c_str()));
+
+ /* If gzipped, decompress the GZIP file and save a new file in the target path */
+ if (fGzipped)
+ {
+ Utf8Str strTargetFilePath(strAbsDstPath);
+ strTargetFilePath.stripFilename();
+ strTargetFilePath.append(RTPATH_SLASH_STR);
+ strTargetFilePath.append("temp_");
+ strTargetFilePath.append(RTPathFilename(strSrcFilePath.c_str()));
+ strDeleteTemp = strTargetFilePath;
+
+ i_importDecompressFile(stack, strSrcFilePath, strTargetFilePath, strSourceOVF.c_str());
+
+ /* Correct the source and the target with the actual values */
+ strSrcFilePath = strTargetFilePath;
+
+ /* Open the new source file. */
+ vrc = RTVfsIoStrmOpenNormal(strSrcFilePath.c_str(), RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ &hVfsIosSrc);
+ if (RT_FAILURE(vrc))
+ throw setErrorVrc(vrc, tr("Error opening decompressed image file '%s' (%Rrc)"),
+ strSrcFilePath.c_str(), vrc);
+ }
+ else
+ hVfsIosSrc = i_importOpenSourceFile(stack, strSrcFilePath, strSourceOVF.c_str());
+
+ /* Add a read ahead thread to try speed things up with concurrent reads and
+ writes going on in different threads. */
+ RTVFSIOSTREAM hVfsIosReadAhead;
+ vrc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlags*/, 0 /*cBuffers=default*/,
+ 0 /*cbBuffers=default*/, &hVfsIosReadAhead);
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ if (RT_FAILURE(vrc))
+ throw setErrorVrc(vrc, tr("Error initializing read ahead thread for '%s' (%Rrc)"),
+ strSrcFilePath.c_str(), vrc);
+
+ /* Start the source image cloning operation. */
+ ComObjPtr<Medium> nullParent;
+ ComObjPtr<Progress> pProgressImportTmp;
+ rc = pProgressImportTmp.createObject();
+ if (FAILED(rc)) throw rc;
+ rc = pProgressImportTmp->init(mVirtualBox,
+ static_cast<IAppliance*>(this),
+ Utf8StrFmt(tr("Importing medium '%s'"),
+ strAbsDstPath.c_str()),
+ TRUE);
+ if (FAILED(rc)) throw rc;
+ pProgressImportTmp.queryInterfaceTo(pProgressImport.asOutParam());
+ /* pProgressImportTmp is in parameter for Medium::i_importFile,
+ * which is somewhat unusual and might be changed later. */
+ rc = pTargetMedium->i_importFile(strSrcFilePath.c_str(),
+ srcFormat,
+ MediumVariant_Standard,
+ hVfsIosReadAhead,
+ nullParent,
+ pProgressImportTmp,
+ true /* aNotify */);
+ RTVfsIoStrmRelease(hVfsIosReadAhead);
+ hVfsIosSrc = NIL_RTVFSIOSTREAM;
+ if (FAILED(rc))
+ throw rc;
+
+ /* Advance to the next operation. */
+ /* operation's weight, as set up with the IProgress originally */
+ stack.pProgress->SetNextOperation(BstrFmt(tr("Importing virtual disk image '%s'"),
+ RTPathFilename(strSourceOVF.c_str())).raw(),
+ di.ulSuggestedSizeMB);
+ }
+
+ /* Now wait for the background import operation to complete; this throws
+ * HRESULTs on error. */
+ stack.pProgress->WaitForOtherProgressCompletion(pProgressImport, 0 /* indefinite wait */);
+
+ /* The creating/importing has placed the medium in the global
+ * media registry since the VM isn't created yet. Remove it
+ * again to let it added to the right registry when the VM
+ * has been created below. */
+ pTargetMedium->i_removeRegistry(mVirtualBox->i_getGlobalRegistryId());
+ }
+ }
+ catch (...)
+ {
+ if (strDeleteTemp.isNotEmpty())
+ RTFileDelete(strDeleteTemp.c_str());
+ throw;
+ }
+
+ /* Make sure the source file is closed. */
+ if (hVfsIosSrc != NIL_RTVFSIOSTREAM)
+ RTVfsIoStrmRelease(hVfsIosSrc);
+
+ /*
+ * Delete the temp gunzip result, if any.
+ */
+ if (strDeleteTemp.isNotEmpty())
+ {
+ vrc = RTFileDelete(strSrcFilePath.c_str());
+ if (RT_FAILURE(vrc))
+ setWarning(VBOX_E_FILE_ERROR,
+ tr("Failed to delete the temporary file '%s' (%Rrc)"), strSrcFilePath.c_str(), vrc);
+ }
+ }
+}
+
+/**
+ * Helper routine to parse the ExtraData Utf8Str for a storage controller's
+ * value or channel value.
+ *
+ * @param aExtraData The ExtraData string with a format of
+ * 'controller=13;channel=3'.
+ * @param pszKey The string being looked up, either 'controller' or
+ * 'channel'.
+ * @param puVal The integer value of the 'controller=' or 'channel='
+ * key in the ExtraData string.
+ * @returns COM status code.
+ * @throws Nothing.
+ */
+static int getStorageControllerDetailsFromStr(const com::Utf8Str &aExtraData, const char *pszKey, uint32_t *puVal)
+{
+ size_t posKey = aExtraData.find(pszKey);
+ if (posKey == Utf8Str::npos)
+ return VERR_INVALID_PARAMETER;
+
+ int vrc = RTStrToUInt32Ex(aExtraData.c_str() + posKey + strlen(pszKey), NULL, 0, puVal);
+ if (vrc == VWRN_NUMBER_TOO_BIG || vrc == VWRN_NEGATIVE_UNSIGNED)
+ return VERR_INVALID_PARAMETER;
+
+ return vrc;
+}
+
+/**
+ * Verifies the validity of a storage controller's channel (aka controller port).
+ *
+ * @param aStorageControllerType The type of storage controller as idenfitied
+ * by the enum of type StorageControllerType_T.
+ * @param uControllerPort The controller port value.
+ * @param aMaxPortCount The maximum number of ports allowed for this
+ * storage controller type.
+ * @returns COM status code.
+ * @throws Nothing.
+ */
+HRESULT Appliance::i_verifyStorageControllerPortValid(const StorageControllerType_T aStorageControllerType,
+ const uint32_t uControllerPort,
+ ULONG *aMaxPortCount)
+{
+ SystemProperties *pSysProps;
+ pSysProps = mVirtualBox->i_getSystemProperties();
+ if (pSysProps == NULL)
+ return VBOX_E_OBJECT_NOT_FOUND;
+
+ StorageBus_T enmStorageBus = StorageBus_Null;
+ HRESULT vrc = pSysProps->GetStorageBusForStorageControllerType(aStorageControllerType, &enmStorageBus);
+ if (FAILED(vrc))
+ return vrc;
+
+ vrc = pSysProps->GetMaxPortCountForStorageBus(enmStorageBus, aMaxPortCount);
+ if (FAILED(vrc))
+ return vrc;
+
+ if (uControllerPort >= *aMaxPortCount)
+ return E_INVALIDARG;
+
+ return S_OK;
+}
+
+/**
+ * Imports one OVF virtual system (described by the given ovf::VirtualSystem and VirtualSystemDescription)
+ * into VirtualBox by creating an IMachine instance, which is returned.
+ *
+ * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
+ * up any leftovers from this function. For this, the given ImportStack instance has received information
+ * about what needs cleaning up (to support rollback).
+ *
+ * @param vsysThis OVF virtual system (machine) to import.
+ * @param vsdescThis Matching virtual system description (machine) to import.
+ * @param[out] pNewMachineRet Newly created machine.
+ * @param stack Cleanup stack for when this throws.
+ *
+ * @throws HRESULT
+ */
+void Appliance::i_importMachineGeneric(const ovf::VirtualSystem &vsysThis,
+ ComObjPtr<VirtualSystemDescription> &vsdescThis,
+ ComPtr<IMachine> &pNewMachineRet,
+ ImportStack &stack)
+{
+ LogFlowFuncEnter();
+ HRESULT rc;
+
+ // Get the instance of IGuestOSType which matches our string guest OS type so we
+ // can use recommended defaults for the new machine where OVF doesn't provide any
+ ComPtr<IGuestOSType> osType;
+ rc = mVirtualBox->GetGuestOSType(Bstr(stack.strOsTypeVBox).raw(), osType.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ /* Create the machine */
+ SafeArray<BSTR> groups; /* no groups, or maybe one group... */
+ if (!stack.strPrimaryGroup.isEmpty() && stack.strPrimaryGroup != "/")
+ Bstr(stack.strPrimaryGroup).detachTo(groups.appendedRaw());
+ ComPtr<IMachine> pNewMachine;
+ rc = mVirtualBox->CreateMachine(Bstr(stack.strSettingsFilename).raw(),
+ Bstr(stack.strNameVBox).raw(),
+ ComSafeArrayAsInParam(groups),
+ Bstr(stack.strOsTypeVBox).raw(),
+ NULL, /* aCreateFlags */
+ NULL, /* aCipher */
+ NULL, /* aPasswordId */
+ NULL, /* aPassword */
+ pNewMachine.asOutParam());
+ if (FAILED(rc)) throw rc;
+ pNewMachineRet = pNewMachine;
+
+ // set the description
+ if (!stack.strDescription.isEmpty())
+ {
+ rc = pNewMachine->COMSETTER(Description)(Bstr(stack.strDescription).raw());
+ if (FAILED(rc)) throw rc;
+ }
+
+ // CPU count
+ rc = pNewMachine->COMSETTER(CPUCount)(stack.cCPUs);
+ if (FAILED(rc)) throw rc;
+
+ if (stack.fForceHWVirt)
+ {
+ rc = pNewMachine->SetHWVirtExProperty(HWVirtExPropertyType_Enabled, TRUE);
+ if (FAILED(rc)) throw rc;
+ }
+
+ // RAM
+ rc = pNewMachine->COMSETTER(MemorySize)(stack.ulMemorySizeMB);
+ if (FAILED(rc)) throw rc;
+
+ /* VRAM */
+ /* Get the recommended VRAM for this guest OS type */
+ ULONG vramVBox;
+ rc = osType->COMGETTER(RecommendedVRAM)(&vramVBox);
+ if (FAILED(rc)) throw rc;
+
+ /* Set the VRAM */
+ ComPtr<IGraphicsAdapter> pGraphicsAdapter;
+ rc = pNewMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
+ if (FAILED(rc)) throw rc;
+ rc = pGraphicsAdapter->COMSETTER(VRAMSize)(vramVBox);
+ if (FAILED(rc)) throw rc;
+
+ // I/O APIC: Generic OVF has no setting for this. Enable it if we
+ // import a Windows VM because if if Windows was installed without IOAPIC,
+ // it will not mind finding an one later on, but if Windows was installed
+ // _with_ an IOAPIC, it will bluescreen if it's not found
+ if (!stack.fForceIOAPIC)
+ {
+ Bstr bstrFamilyId;
+ rc = osType->COMGETTER(FamilyId)(bstrFamilyId.asOutParam());
+ if (FAILED(rc)) throw rc;
+ if (bstrFamilyId == "Windows")
+ stack.fForceIOAPIC = true;
+ }
+
+ if (stack.fForceIOAPIC)
+ {
+ ComPtr<IBIOSSettings> pBIOSSettings;
+ rc = pNewMachine->COMGETTER(BIOSSettings)(pBIOSSettings.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ rc = pBIOSSettings->COMSETTER(IOAPICEnabled)(TRUE);
+ if (FAILED(rc)) throw rc;
+ }
+
+ if (stack.strFirmwareType.isNotEmpty())
+ {
+ FirmwareType_T firmwareType = FirmwareType_BIOS;
+ if (stack.strFirmwareType.contains("EFI"))
+ {
+ if (stack.strFirmwareType.contains("32"))
+ firmwareType = FirmwareType_EFI32;
+ if (stack.strFirmwareType.contains("64"))
+ firmwareType = FirmwareType_EFI64;
+ else
+ firmwareType = FirmwareType_EFI;
+ }
+ rc = pNewMachine->COMSETTER(FirmwareType)(firmwareType);
+ if (FAILED(rc)) throw rc;
+ }
+
+ if (!stack.strAudioAdapter.isEmpty())
+ if (stack.strAudioAdapter.compare("null", Utf8Str::CaseInsensitive) != 0)
+ {
+ ComPtr<IAudioSettings> audioSettings;
+ rc = pNewMachine->COMGETTER(AudioSettings)(audioSettings.asOutParam());
+ if (FAILED(rc)) throw rc;
+ uint32_t audio = RTStrToUInt32(stack.strAudioAdapter.c_str()); // should be 0 for AC97
+ ComPtr<IAudioAdapter> audioAdapter;
+ rc = audioSettings->COMGETTER(Adapter)(audioAdapter.asOutParam());
+ if (FAILED(rc)) throw rc;
+ rc = audioAdapter->COMSETTER(Enabled)(true);
+ if (FAILED(rc)) throw rc;
+ rc = audioAdapter->COMSETTER(AudioController)(static_cast<AudioControllerType_T>(audio));
+ if (FAILED(rc)) throw rc;
+ }
+
+#ifdef VBOX_WITH_USB
+ /* USB Controller */
+ if (stack.fUSBEnabled)
+ {
+ ComPtr<IUSBController> usbController;
+ rc = pNewMachine->AddUSBController(Bstr("OHCI").raw(), USBControllerType_OHCI, usbController.asOutParam());
+ if (FAILED(rc)) throw rc;
+ }
+#endif /* VBOX_WITH_USB */
+
+ /* Change the network adapters */
+ uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(ChipsetType_PIIX3);
+
+ std::list<VirtualSystemDescriptionEntry*> vsdeNW = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
+ if (vsdeNW.empty())
+ {
+ /* No network adapters, so we have to disable our default one */
+ ComPtr<INetworkAdapter> nwVBox;
+ rc = pNewMachine->GetNetworkAdapter(0, nwVBox.asOutParam());
+ if (FAILED(rc)) throw rc;
+ rc = nwVBox->COMSETTER(Enabled)(false);
+ if (FAILED(rc)) throw rc;
+ }
+ else if (vsdeNW.size() > maxNetworkAdapters)
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Too many network adapters: OVF requests %d network adapters, "
+ "but VirtualBox only supports %d", "", vsdeNW.size()),
+ vsdeNW.size(), maxNetworkAdapters);
+ else
+ {
+ list<VirtualSystemDescriptionEntry*>::const_iterator nwIt;
+ size_t a = 0;
+ for (nwIt = vsdeNW.begin();
+ nwIt != vsdeNW.end();
+ ++nwIt, ++a)
+ {
+ const VirtualSystemDescriptionEntry* pvsys = *nwIt;
+
+ const Utf8Str &nwTypeVBox = pvsys->strVBoxCurrent;
+ uint32_t tt1 = RTStrToUInt32(nwTypeVBox.c_str());
+ ComPtr<INetworkAdapter> pNetworkAdapter;
+ rc = pNewMachine->GetNetworkAdapter((ULONG)a, pNetworkAdapter.asOutParam());
+ if (FAILED(rc)) throw rc;
+ /* Enable the network card & set the adapter type */
+ rc = pNetworkAdapter->COMSETTER(Enabled)(true);
+ if (FAILED(rc)) throw rc;
+ rc = pNetworkAdapter->COMSETTER(AdapterType)(static_cast<NetworkAdapterType_T>(tt1));
+ if (FAILED(rc)) throw rc;
+
+ // default is NAT; change to "bridged" if extra conf says so
+ if (pvsys->strExtraConfigCurrent.endsWith("type=Bridged", Utf8Str::CaseInsensitive))
+ {
+ /* Attach to the right interface */
+ rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Bridged);
+ if (FAILED(rc)) throw rc;
+ ComPtr<IHost> host;
+ rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
+ if (FAILED(rc)) throw rc;
+ com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
+ rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
+ if (FAILED(rc)) throw rc;
+ // We search for the first host network interface which
+ // is usable for bridged networking
+ for (size_t j = 0;
+ j < nwInterfaces.size();
+ ++j)
+ {
+ HostNetworkInterfaceType_T itype;
+ rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
+ if (FAILED(rc)) throw rc;
+ if (itype == HostNetworkInterfaceType_Bridged)
+ {
+ Bstr name;
+ rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
+ if (FAILED(rc)) throw rc;
+ /* Set the interface name to attach to */
+ rc = pNetworkAdapter->COMSETTER(BridgedInterface)(name.raw());
+ if (FAILED(rc)) throw rc;
+ break;
+ }
+ }
+ }
+ /* Next test for host only interfaces */
+ else if (pvsys->strExtraConfigCurrent.endsWith("type=HostOnly", Utf8Str::CaseInsensitive))
+ {
+ /* Attach to the right interface */
+ rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_HostOnly);
+ if (FAILED(rc)) throw rc;
+ ComPtr<IHost> host;
+ rc = mVirtualBox->COMGETTER(Host)(host.asOutParam());
+ if (FAILED(rc)) throw rc;
+ com::SafeIfaceArray<IHostNetworkInterface> nwInterfaces;
+ rc = host->COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(nwInterfaces));
+ if (FAILED(rc)) throw rc;
+ // We search for the first host network interface which
+ // is usable for host only networking
+ for (size_t j = 0;
+ j < nwInterfaces.size();
+ ++j)
+ {
+ HostNetworkInterfaceType_T itype;
+ rc = nwInterfaces[j]->COMGETTER(InterfaceType)(&itype);
+ if (FAILED(rc)) throw rc;
+ if (itype == HostNetworkInterfaceType_HostOnly)
+ {
+ Bstr name;
+ rc = nwInterfaces[j]->COMGETTER(Name)(name.asOutParam());
+ if (FAILED(rc)) throw rc;
+ /* Set the interface name to attach to */
+ rc = pNetworkAdapter->COMSETTER(HostOnlyInterface)(name.raw());
+ if (FAILED(rc)) throw rc;
+ break;
+ }
+ }
+ }
+ /* Next test for internal interfaces */
+ else if (pvsys->strExtraConfigCurrent.endsWith("type=Internal", Utf8Str::CaseInsensitive))
+ {
+ /* Attach to the right interface */
+ rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Internal);
+ if (FAILED(rc)) throw rc;
+ }
+ /* Next test for Generic interfaces */
+ else if (pvsys->strExtraConfigCurrent.endsWith("type=Generic", Utf8Str::CaseInsensitive))
+ {
+ /* Attach to the right interface */
+ rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Generic);
+ if (FAILED(rc)) throw rc;
+ }
+
+ /* Next test for NAT network interfaces */
+ else if (pvsys->strExtraConfigCurrent.endsWith("type=NATNetwork", Utf8Str::CaseInsensitive))
+ {
+ /* Attach to the right interface */
+ rc = pNetworkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_NATNetwork);
+ if (FAILED(rc)) throw rc;
+ com::SafeIfaceArray<INATNetwork> nwNATNetworks;
+ rc = mVirtualBox->COMGETTER(NATNetworks)(ComSafeArrayAsOutParam(nwNATNetworks));
+ if (FAILED(rc)) throw rc;
+ // Pick the first NAT network (if there is any)
+ if (nwNATNetworks.size())
+ {
+ Bstr name;
+ rc = nwNATNetworks[0]->COMGETTER(NetworkName)(name.asOutParam());
+ if (FAILED(rc)) throw rc;
+ /* Set the NAT network name to attach to */
+ rc = pNetworkAdapter->COMSETTER(NATNetwork)(name.raw());
+ if (FAILED(rc)) throw rc;
+ break;
+ }
+ }
+ }
+ }
+
+ // Storage controller IDE
+ std::list<VirtualSystemDescriptionEntry*> vsdeHDCIDE =
+ vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerIDE);
+ /*
+ * In OVF (at least VMware's version of it), an IDE controller has two ports,
+ * so VirtualBox's single IDE controller with two channels and two ports each counts as
+ * two OVF IDE controllers -- so we accept one or two such IDE controllers
+ */
+ size_t cIDEControllers = vsdeHDCIDE.size();
+ if (cIDEControllers > 2)
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Too many IDE controllers in OVF; import facility only supports two"));
+ if (!vsdeHDCIDE.empty())
+ {
+ // one or two IDE controllers present in OVF: add one VirtualBox controller
+ ComPtr<IStorageController> pController;
+ rc = pNewMachine->AddStorageController(Bstr("IDE").raw(), StorageBus_IDE, pController.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ const char *pcszIDEType = vsdeHDCIDE.front()->strVBoxCurrent.c_str();
+ if (!strcmp(pcszIDEType, "PIIX3"))
+ rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX3);
+ else if (!strcmp(pcszIDEType, "PIIX4"))
+ rc = pController->COMSETTER(ControllerType)(StorageControllerType_PIIX4);
+ else if (!strcmp(pcszIDEType, "ICH6"))
+ rc = pController->COMSETTER(ControllerType)(StorageControllerType_ICH6);
+ else
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Invalid IDE controller type \"%s\""),
+ pcszIDEType);
+ if (FAILED(rc)) throw rc;
+ }
+
+ /* Storage controller SATA */
+ std::list<VirtualSystemDescriptionEntry*> vsdeHDCSATA =
+ vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSATA);
+ if (vsdeHDCSATA.size() > 1)
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Too many SATA controllers in OVF; import facility only supports one"));
+ if (!vsdeHDCSATA.empty())
+ {
+ ComPtr<IStorageController> pController;
+ const Utf8Str &hdcVBox = vsdeHDCSATA.front()->strVBoxCurrent;
+ if (hdcVBox == "AHCI")
+ {
+ rc = pNewMachine->AddStorageController(Bstr("SATA").raw(),
+ StorageBus_SATA,
+ pController.asOutParam());
+ if (FAILED(rc)) throw rc;
+ }
+ else
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Invalid SATA controller type \"%s\""),
+ hdcVBox.c_str());
+ }
+
+ /* Storage controller SCSI */
+ std::list<VirtualSystemDescriptionEntry*> vsdeHDCSCSI =
+ vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSCSI);
+ if (vsdeHDCSCSI.size() > 1)
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Too many SCSI controllers in OVF; import facility only supports one"));
+ if (!vsdeHDCSCSI.empty())
+ {
+ ComPtr<IStorageController> pController;
+ Utf8Str strName("SCSI");
+ StorageBus_T busType = StorageBus_SCSI;
+ StorageControllerType_T controllerType;
+ const Utf8Str &hdcVBox = vsdeHDCSCSI.front()->strVBoxCurrent;
+ if (hdcVBox == "LsiLogic")
+ controllerType = StorageControllerType_LsiLogic;
+ else if (hdcVBox == "LsiLogicSas")
+ {
+ // OVF treats LsiLogicSas as a SCSI controller but VBox considers it a class of its own
+ strName = "SAS";
+ busType = StorageBus_SAS;
+ controllerType = StorageControllerType_LsiLogicSas;
+ }
+ else if (hdcVBox == "BusLogic")
+ controllerType = StorageControllerType_BusLogic;
+ else
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Invalid SCSI controller type \"%s\""),
+ hdcVBox.c_str());
+
+ rc = pNewMachine->AddStorageController(Bstr(strName).raw(), busType, pController.asOutParam());
+ if (FAILED(rc)) throw rc;
+ rc = pController->COMSETTER(ControllerType)(controllerType);
+ if (FAILED(rc)) throw rc;
+ }
+
+ /* Storage controller SAS */
+ std::list<VirtualSystemDescriptionEntry*> vsdeHDCSAS =
+ vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerSAS);
+ if (vsdeHDCSAS.size() > 1)
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Too many SAS controllers in OVF; import facility only supports one"));
+ if (!vsdeHDCSAS.empty())
+ {
+ ComPtr<IStorageController> pController;
+ rc = pNewMachine->AddStorageController(Bstr(L"SAS").raw(),
+ StorageBus_SAS,
+ pController.asOutParam());
+ if (FAILED(rc)) throw rc;
+ rc = pController->COMSETTER(ControllerType)(StorageControllerType_LsiLogicSas);
+ if (FAILED(rc)) throw rc;
+ }
+
+
+ /* Storage controller VirtioSCSI */
+ std::list<VirtualSystemDescriptionEntry*> vsdeHDCVirtioSCSI =
+ vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskControllerVirtioSCSI);
+ if (vsdeHDCVirtioSCSI.size() > 1)
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Too many VirtioSCSI controllers in OVF; import facility only supports one"));
+ if (!vsdeHDCVirtioSCSI.empty())
+ {
+ ComPtr<IStorageController> pController;
+ Utf8Str strName("VirtioSCSI");
+ const Utf8Str &hdcVBox = vsdeHDCVirtioSCSI.front()->strVBoxCurrent;
+ if (hdcVBox == "VirtioSCSI")
+ {
+ rc = pNewMachine->AddStorageController(Bstr(strName).raw(),
+ StorageBus_VirtioSCSI,
+ pController.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ rc = pController->COMSETTER(ControllerType)(StorageControllerType_VirtioSCSI);
+ if (FAILED(rc)) throw rc;
+ }
+ else
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Invalid VirtioSCSI controller type \"%s\""),
+ hdcVBox.c_str());
+ }
+
+ /* Now its time to register the machine before we add any storage devices */
+ rc = mVirtualBox->RegisterMachine(pNewMachine);
+ if (FAILED(rc)) throw rc;
+
+ // store new machine for roll-back in case of errors
+ Bstr bstrNewMachineId;
+ rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
+ if (FAILED(rc)) throw rc;
+ Guid uuidNewMachine(bstrNewMachineId);
+ m->llGuidsMachinesCreated.push_back(uuidNewMachine);
+
+ // Add floppies and CD-ROMs to the appropriate controllers.
+ std::list<VirtualSystemDescriptionEntry*> vsdeFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy);
+ if (vsdeFloppy.size() > 1)
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Too many floppy controllers in OVF; import facility only supports one"));
+ std::list<VirtualSystemDescriptionEntry*> vsdeCDROM = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM);
+ if ( !vsdeFloppy.empty()
+ || !vsdeCDROM.empty()
+ )
+ {
+ // If there's an error here we need to close the session, so
+ // we need another try/catch block.
+
+ try
+ {
+ // to attach things we need to open a session for the new machine
+ rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
+ if (FAILED(rc)) throw rc;
+ stack.fSessionOpen = true;
+
+ ComPtr<IMachine> sMachine;
+ rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ // floppy first
+ if (vsdeFloppy.size() == 1)
+ {
+ ComPtr<IStorageController> pController;
+ rc = sMachine->AddStorageController(Bstr("Floppy").raw(),
+ StorageBus_Floppy,
+ pController.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ Bstr bstrName;
+ rc = pController->COMGETTER(Name)(bstrName.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ // this is for rollback later
+ MyHardDiskAttachment mhda;
+ mhda.pMachine = pNewMachine;
+ mhda.controllerName = bstrName;
+ mhda.lControllerPort = 0;
+ mhda.lDevice = 0;
+
+ Log(("Attaching floppy\n"));
+
+ rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),
+ mhda.lControllerPort,
+ mhda.lDevice,
+ DeviceType_Floppy,
+ NULL);
+ if (FAILED(rc)) throw rc;
+
+ stack.llHardDiskAttachments.push_back(mhda);
+ }
+
+ rc = sMachine->SaveSettings();
+ if (FAILED(rc)) throw rc;
+
+ // only now that we're done with all storage devices, close the session
+ rc = stack.pSession->UnlockMachine();
+ if (FAILED(rc)) throw rc;
+ stack.fSessionOpen = false;
+ }
+ catch(HRESULT aRC)
+ {
+ com::ErrorInfo info;
+
+ if (stack.fSessionOpen)
+ stack.pSession->UnlockMachine();
+
+ if (info.isFullAvailable())
+ throw setError(aRC, Utf8Str(info.getText()).c_str());
+ else
+ throw setError(aRC, tr("Unknown error during OVF import"));
+ }
+ }
+
+ // create the storage devices & connect them to the appropriate controllers
+ std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
+ if (!avsdeHDs.empty())
+ {
+ // If there's an error here we need to close the session, so
+ // we need another try/catch block.
+ try
+ {
+#ifdef LOG_ENABLED
+ if (LogIsEnabled())
+ {
+ size_t i = 0;
+ for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
+ itHD != avsdeHDs.end(); ++itHD, i++)
+ Log(("avsdeHDs[%zu]: strRef=%s strOvf=%s\n", i, (*itHD)->strRef.c_str(), (*itHD)->strOvf.c_str()));
+ i = 0;
+ for (ovf::DiskImagesMap::const_iterator itDisk = stack.mapDisks.begin(); itDisk != stack.mapDisks.end(); ++itDisk)
+ Log(("mapDisks[%zu]: strDiskId=%s strHref=%s\n",
+ i, itDisk->second.strDiskId.c_str(), itDisk->second.strHref.c_str()));
+
+ }
+#endif
+
+ // to attach things we need to open a session for the new machine
+ rc = pNewMachine->LockMachine(stack.pSession, LockType_Write);
+ if (FAILED(rc)) throw rc;
+ stack.fSessionOpen = true;
+
+ /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
+ std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
+ std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
+ VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
+
+
+ ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
+ std::set<RTCString> disksResolvedNames;
+
+ uint32_t cImportedDisks = 0;
+
+ while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
+ {
+/** @todo r=bird: Most of the code here is duplicated in the other machine
+ * import method, factor out. */
+ ovf::DiskImage diCurrent = oit->second;
+
+ Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
+ /* Iterate over all given images of the virtual system
+ * description. We need to find the target image path,
+ * which could be changed by the user. */
+ VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
+ for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
+ itHD != avsdeHDs.end();
+ ++itHD)
+ {
+ VirtualSystemDescriptionEntry *vsdeHD = *itHD;
+ if (vsdeHD->strRef == diCurrent.strDiskId)
+ {
+ vsdeTargetHD = vsdeHD;
+ break;
+ }
+ }
+ if (!vsdeTargetHD)
+ {
+ /* possible case if an image belongs to other virtual system (OVF package with multiple VMs inside) */
+ Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
+ oit->first.c_str(), vmNameEntry->strOvf.c_str()));
+ NOREF(vmNameEntry);
+ ++oit;
+ continue;
+ }
+
+ //diCurrent.strDiskId contains the image identifier (e.g. "vmdisk1"), which should exist
+ //in the virtual system's images map under that ID and also in the global images map
+ ovf::VirtualDisksMap::const_iterator itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
+ if (itVDisk == vsysThis.mapVirtualDisks.end())
+ throw setError(E_FAIL,
+ tr("Internal inconsistency looking up disk image '%s'"),
+ diCurrent.strHref.c_str());
+
+ /*
+ * preliminary check availability of the image
+ * This step is useful if image is placed in the OVA (TAR) package
+ */
+ if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
+ {
+ /* It means that we possibly have imported the storage earlier on the previous loop steps*/
+ std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
+ if (h != disksResolvedNames.end())
+ {
+ /* Yes, image name was found, we can skip it*/
+ ++oit;
+ continue;
+ }
+l_skipped:
+ rc = i_preCheckImageAvailability(stack);
+ if (SUCCEEDED(rc))
+ {
+ /* current opened file isn't the same as passed one */
+ if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
+ {
+ /* availableImage contains the image file reference (e.g. "disk1.vmdk"), which should
+ * exist in the global images map.
+ * And find the image from the OVF's disk list */
+ ovf::DiskImagesMap::const_iterator itDiskImage;
+ for (itDiskImage = stack.mapDisks.begin();
+ itDiskImage != stack.mapDisks.end();
+ itDiskImage++)
+ if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
+ Utf8Str::CaseInsensitive) == 0)
+ break;
+ if (itDiskImage == stack.mapDisks.end())
+ {
+ LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
+ RTVfsIoStrmRelease(stack.claimOvaLookAHead());
+ goto l_skipped;
+ }
+
+ /* replace with a new found image */
+ diCurrent = *(&itDiskImage->second);
+
+ /*
+ * Again iterate over all given images of the virtual system
+ * description using the found image
+ */
+ for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
+ itHD != avsdeHDs.end();
+ ++itHD)
+ {
+ VirtualSystemDescriptionEntry *vsdeHD = *itHD;
+ if (vsdeHD->strRef == diCurrent.strDiskId)
+ {
+ vsdeTargetHD = vsdeHD;
+ break;
+ }
+ }
+
+ /*
+ * in this case it's an error because something is wrong with the OVF description file.
+ * May be VBox imports OVA package with wrong file sequence inside the archive.
+ */
+ if (!vsdeTargetHD)
+ throw setError(E_FAIL,
+ tr("Internal inconsistency looking up disk image '%s'"),
+ diCurrent.strHref.c_str());
+
+ itVDisk = vsysThis.mapVirtualDisks.find(diCurrent.strDiskId);
+ if (itVDisk == vsysThis.mapVirtualDisks.end())
+ throw setError(E_FAIL,
+ tr("Internal inconsistency looking up disk image '%s'"),
+ diCurrent.strHref.c_str());
+ }
+ else
+ {
+ ++oit;
+ }
+ }
+ else
+ {
+ ++oit;
+ continue;
+ }
+ }
+ else
+ {
+ /* just continue with normal files */
+ ++oit;
+ }
+
+ /* very important to store image name for the next checks */
+ disksResolvedNames.insert(diCurrent.strHref);
+////// end of duplicated code.
+ const ovf::VirtualDisk &ovfVdisk = itVDisk->second;
+
+ ComObjPtr<Medium> pTargetMedium;
+ if (stack.locInfo.storageType == VFSType_Cloud)
+ {
+ /* We have already all disks prepared (converted and registered in the VBox)
+ * and in the correct place (VM machine folder).
+ * so what is needed is to get the disk uuid from VirtualDisk::strDiskId
+ * and find the Medium object with this uuid.
+ * next just attach the Medium object to new VM.
+ * VirtualDisk::strDiskId is filled in the */
+
+ Guid id(ovfVdisk.strDiskId);
+ rc = mVirtualBox->i_findHardDiskById(id, false, &pTargetMedium);
+ if (FAILED(rc))
+ throw rc;
+ }
+ else
+ {
+ i_importOneDiskImage(diCurrent,
+ vsdeTargetHD->strVBoxCurrent,
+ pTargetMedium,
+ stack);
+ }
+
+ // now use the new uuid to attach the medium to our new machine
+ ComPtr<IMachine> sMachine;
+ rc = stack.pSession->COMGETTER(Machine)(sMachine.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+
+ // this is for rollback later
+ MyHardDiskAttachment mhda;
+ mhda.pMachine = pNewMachine;
+
+ // find the hard disk controller to which we should attach
+ ovf::HardDiskController hdc;
+
+ /*
+ * Before importing the virtual hard disk found above (diCurrent/vsdeTargetHD) first
+ * check if the user requested to change either the controller it is to be attached
+ * to and/or the controller port (aka 'channel') on the controller.
+ */
+ if ( !vsdeTargetHD->strExtraConfigCurrent.isEmpty()
+ && vsdeTargetHD->strExtraConfigSuggested != vsdeTargetHD->strExtraConfigCurrent)
+ {
+ int vrc;
+ uint32_t uTargetControllerIndex;
+ vrc = getStorageControllerDetailsFromStr(vsdeTargetHD->strExtraConfigCurrent, "controller=",
+ &uTargetControllerIndex);
+ if (RT_FAILURE(vrc))
+ throw setError(E_FAIL,
+ tr("Target controller value invalid or missing: '%s'"),
+ vsdeTargetHD->strExtraConfigCurrent.c_str());
+
+ uint32_t uNewControllerPortValue;
+ vrc = getStorageControllerDetailsFromStr(vsdeTargetHD->strExtraConfigCurrent, "channel=",
+ &uNewControllerPortValue);
+ if (RT_FAILURE(vrc))
+ throw setError(E_FAIL,
+ tr("Target controller port ('channel=') invalid or missing: '%s'"),
+ vsdeTargetHD->strExtraConfigCurrent.c_str());
+
+ const VirtualSystemDescriptionEntry *vsdeTargetController;
+ vsdeTargetController = vsdescThis->i_findByIndex(uTargetControllerIndex);
+ if (!vsdeTargetController)
+ throw setError(E_FAIL,
+ tr("Failed to find storage controller '%u' in the System Description list"),
+ uTargetControllerIndex);
+
+ hdc = (*vsysThis.mapControllers.find(vsdeTargetController->strRef.c_str())).second;
+
+ StorageControllerType_T hdStorageControllerType = StorageControllerType_Null;
+ switch (hdc.system)
+ {
+ case ovf::HardDiskController::IDE:
+ hdStorageControllerType = StorageControllerType_PIIX3;
+ break;
+ case ovf::HardDiskController::SATA:
+ hdStorageControllerType = StorageControllerType_IntelAhci;
+ break;
+ case ovf::HardDiskController::SCSI:
+ {
+ if (hdc.strControllerType.compare("lsilogicsas")==0)
+ hdStorageControllerType = StorageControllerType_LsiLogicSas;
+ else
+ hdStorageControllerType = StorageControllerType_LsiLogic;
+ break;
+ }
+ case ovf::HardDiskController::VIRTIOSCSI:
+ hdStorageControllerType = StorageControllerType_VirtioSCSI;
+ break;
+ default:
+ throw setError(E_FAIL,
+ tr("Invalid hard disk contoller type: '%d'"),
+ hdc.system);
+ break;
+ }
+
+ ULONG ulMaxPorts;
+ rc = i_verifyStorageControllerPortValid(hdStorageControllerType,
+ uNewControllerPortValue,
+ &ulMaxPorts);
+ if (FAILED(rc))
+ {
+ if (rc == E_INVALIDARG)
+ {
+ const char *pcszSCType = Global::stringifyStorageControllerType(hdStorageControllerType);
+ throw setError(E_INVALIDARG,
+ tr("Illegal channel: '%u'. For %s controllers the valid values are "
+ "0 to %lu (inclusive).\n"), uNewControllerPortValue, pcszSCType, ulMaxPorts-1);
+ }
+ else
+ throw rc;
+ }
+
+ unconst(ovfVdisk.ulAddressOnParent) = uNewControllerPortValue;
+ }
+ else
+ hdc = (*vsysThis.mapControllers.find(ovfVdisk.strIdController)).second;
+
+
+ i_convertDiskAttachmentValues(hdc,
+ ovfVdisk.ulAddressOnParent,
+ mhda.controllerName,
+ mhda.lControllerPort,
+ mhda.lDevice);
+
+ Log(("Attaching disk %s to port %d on device %d\n",
+ vsdeTargetHD->strVBoxCurrent.c_str(), mhda.lControllerPort, mhda.lDevice));
+
+ DeviceType_T devType = DeviceType_Null;
+ rc = pTargetMedium->COMGETTER(DeviceType)(&devType);
+ if (FAILED(rc))
+ throw rc;
+
+ rc = sMachine->AttachDevice(Bstr(mhda.controllerName).raw(),// name
+ mhda.lControllerPort, // long controllerPort
+ mhda.lDevice, // long device
+ devType, // DeviceType_T type
+ pTargetMedium);
+ if (FAILED(rc))
+ throw rc;
+
+ stack.llHardDiskAttachments.push_back(mhda);
+
+ rc = sMachine->SaveSettings();
+ if (FAILED(rc))
+ throw rc;
+
+ ++cImportedDisks;
+
+ } // end while(oit != stack.mapDisks.end())
+
+ /*
+ * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
+ */
+ if(cImportedDisks < avsdeHDs.size())
+ {
+ Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
+ vmNameEntry->strOvf.c_str()));
+ }
+
+ // only now that we're done with all disks, close the session
+ rc = stack.pSession->UnlockMachine();
+ if (FAILED(rc))
+ throw rc;
+ stack.fSessionOpen = false;
+ }
+ catch(HRESULT aRC)
+ {
+ com::ErrorInfo info;
+ if (stack.fSessionOpen)
+ stack.pSession->UnlockMachine();
+
+ if (info.isFullAvailable())
+ throw setError(aRC, Utf8Str(info.getText()).c_str());
+ else
+ throw setError(aRC, tr("Unknown error during OVF import"));
+ }
+ }
+ LogFlowFuncLeave();
+}
+
+/**
+ * Imports one OVF virtual system (described by a vbox:Machine tag represented by the given config
+ * structure) into VirtualBox by creating an IMachine instance, which is returned.
+ *
+ * This throws HRESULT error codes for anything that goes wrong, in which case the caller must clean
+ * up any leftovers from this function. For this, the given ImportStack instance has received information
+ * about what needs cleaning up (to support rollback).
+ *
+ * The machine config stored in the settings::MachineConfigFile structure contains the UUIDs of
+ * the disk attachments used by the machine when it was exported. We also add vbox:uuid attributes
+ * to the OVF disks sections so we can look them up. While importing these UUIDs into a second host
+ * will most probably work, reimporting them into the same host will cause conflicts, so we always
+ * generate new ones on import. This involves the following:
+ *
+ * 1) Scan the machine config for disk attachments.
+ *
+ * 2) For each disk attachment found, look up the OVF disk image from the disk references section
+ * and import the disk into VirtualBox, which creates a new UUID for it. In the machine config,
+ * replace the old UUID with the new one.
+ *
+ * 3) Change the machine config according to the OVF virtual system descriptions, in case the
+ * caller has modified them using setFinalValues().
+ *
+ * 4) Create the VirtualBox machine with the modfified machine config.
+ *
+ * @param vsdescThis
+ * @param pReturnNewMachine
+ * @param stack
+ */
+void Appliance::i_importVBoxMachine(ComObjPtr<VirtualSystemDescription> &vsdescThis,
+ ComPtr<IMachine> &pReturnNewMachine,
+ ImportStack &stack)
+{
+ LogFlowFuncEnter();
+ Assert(vsdescThis->m->pConfig);
+
+ HRESULT rc = S_OK;
+
+ settings::MachineConfigFile &config = *vsdescThis->m->pConfig;
+
+ /*
+ * step 1): modify machine config according to OVF config, in case the user
+ * has modified them using setFinalValues()
+ */
+
+ /* OS Type */
+ config.machineUserData.strOsType = stack.strOsTypeVBox;
+ /* Groups */
+ if (stack.strPrimaryGroup.isEmpty() || stack.strPrimaryGroup == "/")
+ {
+ config.machineUserData.llGroups.clear();
+ config.machineUserData.llGroups.push_back("/");
+ }
+ else
+ {
+ /* Replace the primary group if there is one, otherwise add it. */
+ if (config.machineUserData.llGroups.size())
+ config.machineUserData.llGroups.pop_front();
+ config.machineUserData.llGroups.push_front(stack.strPrimaryGroup);
+ }
+ /* Description */
+ config.machineUserData.strDescription = stack.strDescription;
+ /* CPU count & extented attributes */
+ config.hardwareMachine.cCPUs = stack.cCPUs;
+ if (stack.fForceIOAPIC)
+ config.hardwareMachine.fHardwareVirt = true;
+ if (stack.fForceIOAPIC)
+ config.hardwareMachine.biosSettings.fIOAPICEnabled = true;
+ /* RAM size */
+ config.hardwareMachine.ulMemorySizeMB = stack.ulMemorySizeMB;
+
+/*
+ <const name="HardDiskControllerIDE" value="14" />
+ <const name="HardDiskControllerSATA" value="15" />
+ <const name="HardDiskControllerSCSI" value="16" />
+ <const name="HardDiskControllerSAS" value="17" />
+ <const name="HardDiskControllerVirtioSCSI" value="60" />
+*/
+
+#ifdef VBOX_WITH_USB
+ /* USB controller */
+ if (stack.fUSBEnabled)
+ {
+ /** @todo r=klaus add support for arbitrary USB controller types, this can't handle
+ * multiple controllers due to its design anyway */
+ /* Usually the OHCI controller is enabled already, need to check. But
+ * do this only if there is no xHCI controller. */
+ bool fOHCIEnabled = false;
+ bool fXHCIEnabled = false;
+ settings::USBControllerList &llUSBControllers = config.hardwareMachine.usbSettings.llUSBControllers;
+ settings::USBControllerList::iterator it;
+ for (it = llUSBControllers.begin(); it != llUSBControllers.end(); ++it)
+ {
+ if (it->enmType == USBControllerType_OHCI)
+ fOHCIEnabled = true;
+ if (it->enmType == USBControllerType_XHCI)
+ fXHCIEnabled = true;
+ }
+
+ if (!fXHCIEnabled && !fOHCIEnabled)
+ {
+ settings::USBController ctrl;
+ ctrl.strName = "OHCI";
+ ctrl.enmType = USBControllerType_OHCI;
+
+ llUSBControllers.push_back(ctrl);
+ }
+ }
+ else
+ config.hardwareMachine.usbSettings.llUSBControllers.clear();
+#endif
+ /* Audio adapter */
+ if (stack.strAudioAdapter.isNotEmpty())
+ {
+ config.hardwareMachine.audioAdapter.fEnabled = true;
+ config.hardwareMachine.audioAdapter.controllerType = (AudioControllerType_T)stack.strAudioAdapter.toUInt32();
+ }
+ else
+ config.hardwareMachine.audioAdapter.fEnabled = false;
+ /* Network adapter */
+ settings::NetworkAdaptersList &llNetworkAdapters = config.hardwareMachine.llNetworkAdapters;
+ /* First disable all network cards, they will be enabled below again. */
+ settings::NetworkAdaptersList::iterator it1;
+ bool fKeepAllMACs = m->optListImport.contains(ImportOptions_KeepAllMACs);
+ bool fKeepNATMACs = m->optListImport.contains(ImportOptions_KeepNATMACs);
+ for (it1 = llNetworkAdapters.begin(); it1 != llNetworkAdapters.end(); ++it1)
+ {
+ it1->fEnabled = false;
+ if (!( fKeepAllMACs
+ || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NAT)
+ || (fKeepNATMACs && it1->mode == NetworkAttachmentType_NATNetwork)))
+ /* Force generation of new MAC address below. */
+ it1->strMACAddress.setNull();
+ }
+ /* Now iterate over all network entries. */
+ std::list<VirtualSystemDescriptionEntry*> avsdeNWs = vsdescThis->i_findByType(VirtualSystemDescriptionType_NetworkAdapter);
+ if (!avsdeNWs.empty())
+ {
+ /* Iterate through all network adapter entries and search for the
+ * corresponding one in the machine config. If one is found, configure
+ * it based on the user settings. */
+ list<VirtualSystemDescriptionEntry*>::const_iterator itNW;
+ for (itNW = avsdeNWs.begin();
+ itNW != avsdeNWs.end();
+ ++itNW)
+ {
+ VirtualSystemDescriptionEntry *vsdeNW = *itNW;
+ if ( vsdeNW->strExtraConfigCurrent.startsWith("slot=", Utf8Str::CaseInsensitive)
+ && vsdeNW->strExtraConfigCurrent.length() > 6)
+ {
+ uint32_t iSlot = vsdeNW->strExtraConfigCurrent.substr(5).toUInt32();
+ /* Iterate through all network adapters in the machine config. */
+ for (it1 = llNetworkAdapters.begin();
+ it1 != llNetworkAdapters.end();
+ ++it1)
+ {
+ /* Compare the slots. */
+ if (it1->ulSlot == iSlot)
+ {
+ it1->fEnabled = true;
+ if (it1->strMACAddress.isEmpty())
+ Host::i_generateMACAddress(it1->strMACAddress);
+ it1->type = (NetworkAdapterType_T)vsdeNW->strVBoxCurrent.toUInt32();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /* Floppy controller */
+ bool fFloppy = vsdescThis->i_findByType(VirtualSystemDescriptionType_Floppy).size() > 0;
+ /* DVD controller */
+ bool fDVD = vsdescThis->i_findByType(VirtualSystemDescriptionType_CDROM).size() > 0;
+ /* Iterate over all storage controller check the attachments and remove
+ * them when necessary. Also detect broken configs with more than one
+ * attachment. Old VirtualBox versions (prior to 3.2.10) had all disk
+ * attachments pointing to the last hard disk image, which causes import
+ * failures. A long fixed bug, however the OVF files are long lived. */
+ settings::StorageControllersList &llControllers = config.hardwareMachine.storage.llStorageControllers;
+ uint32_t cDisks = 0;
+ bool fInconsistent = false;
+ bool fRepairDuplicate = false;
+ settings::StorageControllersList::iterator it3;
+ for (it3 = llControllers.begin();
+ it3 != llControllers.end();
+ ++it3)
+ {
+ Guid hdUuid;
+ settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
+ settings::AttachedDevicesList::iterator it4 = llAttachments.begin();
+ while (it4 != llAttachments.end())
+ {
+ if ( ( !fDVD
+ && it4->deviceType == DeviceType_DVD)
+ ||
+ ( !fFloppy
+ && it4->deviceType == DeviceType_Floppy))
+ {
+ it4 = llAttachments.erase(it4);
+ continue;
+ }
+ else if (it4->deviceType == DeviceType_HardDisk)
+ {
+ const Guid &thisUuid = it4->uuid;
+ cDisks++;
+ if (cDisks == 1)
+ {
+ if (hdUuid.isZero())
+ hdUuid = thisUuid;
+ else
+ fInconsistent = true;
+ }
+ else
+ {
+ if (thisUuid.isZero())
+ fInconsistent = true;
+ else if (thisUuid == hdUuid)
+ fRepairDuplicate = true;
+ }
+ }
+ ++it4;
+ }
+ }
+ /* paranoia... */
+ if (fInconsistent || cDisks == 1)
+ fRepairDuplicate = false;
+
+ /*
+ * step 2: scan the machine config for media attachments
+ */
+ /* get VM name from virtual system description. Only one record is possible (size of list is equal 1). */
+ std::list<VirtualSystemDescriptionEntry*> vmName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
+ std::list<VirtualSystemDescriptionEntry*>::iterator vmNameIt = vmName.begin();
+ VirtualSystemDescriptionEntry* vmNameEntry = *vmNameIt;
+
+ /* Get all hard disk descriptions. */
+ std::list<VirtualSystemDescriptionEntry*> avsdeHDs = vsdescThis->i_findByType(VirtualSystemDescriptionType_HardDiskImage);
+ std::list<VirtualSystemDescriptionEntry*>::iterator avsdeHDsIt = avsdeHDs.begin();
+ /* paranoia - if there is no 1:1 match do not try to repair. */
+ if (cDisks != avsdeHDs.size())
+ fRepairDuplicate = false;
+
+ // there must be an image in the OVF disk structs with the same UUID
+
+ ovf::DiskImagesMap::const_iterator oit = stack.mapDisks.begin();
+ std::set<RTCString> disksResolvedNames;
+
+ uint32_t cImportedDisks = 0;
+
+ while (oit != stack.mapDisks.end() && cImportedDisks != avsdeHDs.size())
+ {
+/** @todo r=bird: Most of the code here is duplicated in the other machine
+ * import method, factor out. */
+ ovf::DiskImage diCurrent = oit->second;
+
+ Log(("diCurrent.strDiskId=%s diCurrent.strHref=%s\n", diCurrent.strDiskId.c_str(), diCurrent.strHref.c_str()));
+
+ /* Iterate over all given disk images of the virtual system
+ * disks description. We need to find the target disk path,
+ * which could be changed by the user. */
+ VirtualSystemDescriptionEntry *vsdeTargetHD = NULL;
+ for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
+ itHD != avsdeHDs.end();
+ ++itHD)
+ {
+ VirtualSystemDescriptionEntry *vsdeHD = *itHD;
+ if (vsdeHD->strRef == oit->first)
+ {
+ vsdeTargetHD = vsdeHD;
+ break;
+ }
+ }
+ if (!vsdeTargetHD)
+ {
+ /* possible case if a disk image belongs to other virtual system (OVF package with multiple VMs inside) */
+ Log1Warning(("OVA/OVF import: Disk image %s was missed during import of VM %s\n",
+ oit->first.c_str(), vmNameEntry->strOvf.c_str()));
+ NOREF(vmNameEntry);
+ ++oit;
+ continue;
+ }
+
+ /*
+ * preliminary check availability of the image
+ * This step is useful if image is placed in the OVA (TAR) package
+ */
+ if (stack.hVfsFssOva != NIL_RTVFSFSSTREAM)
+ {
+ /* It means that we possibly have imported the storage earlier on a previous loop step. */
+ std::set<RTCString>::const_iterator h = disksResolvedNames.find(diCurrent.strHref);
+ if (h != disksResolvedNames.end())
+ {
+ /* Yes, disk name was found, we can skip it*/
+ ++oit;
+ continue;
+ }
+l_skipped:
+ rc = i_preCheckImageAvailability(stack);
+ if (SUCCEEDED(rc))
+ {
+ /* current opened file isn't the same as passed one */
+ if (RTStrICmp(diCurrent.strHref.c_str(), stack.pszOvaLookAheadName) != 0)
+ {
+ // availableImage contains the disk identifier (e.g. "vmdisk1"), which should exist
+ // in the virtual system's disks map under that ID and also in the global images map
+ // and find the disk from the OVF's disk list
+ ovf::DiskImagesMap::const_iterator itDiskImage;
+ for (itDiskImage = stack.mapDisks.begin();
+ itDiskImage != stack.mapDisks.end();
+ itDiskImage++)
+ if (itDiskImage->second.strHref.compare(stack.pszOvaLookAheadName,
+ Utf8Str::CaseInsensitive) == 0)
+ break;
+ if (itDiskImage == stack.mapDisks.end())
+ {
+ LogFunc(("Skipping '%s'\n", stack.pszOvaLookAheadName));
+ RTVfsIoStrmRelease(stack.claimOvaLookAHead());
+ goto l_skipped;
+ }
+ //throw setError(E_FAIL,
+ // tr("Internal inconsistency looking up disk image '%s'. "
+ // "Check compliance OVA package structure and file names "
+ // "references in the section <References> in the OVF file."),
+ // stack.pszOvaLookAheadName);
+
+ /* replace with a new found disk image */
+ diCurrent = *(&itDiskImage->second);
+
+ /*
+ * Again iterate over all given disk images of the virtual system
+ * disks description using the found disk image
+ */
+ vsdeTargetHD = NULL;
+ for (list<VirtualSystemDescriptionEntry*>::const_iterator itHD = avsdeHDs.begin();
+ itHD != avsdeHDs.end();
+ ++itHD)
+ {
+ VirtualSystemDescriptionEntry *vsdeHD = *itHD;
+ if (vsdeHD->strRef == diCurrent.strDiskId)
+ {
+ vsdeTargetHD = vsdeHD;
+ break;
+ }
+ }
+
+ /*
+ * in this case it's an error because something is wrong with the OVF description file.
+ * May be VBox imports OVA package with wrong file sequence inside the archive.
+ */
+ if (!vsdeTargetHD)
+ throw setError(E_FAIL,
+ tr("Internal inconsistency looking up disk image '%s'"),
+ diCurrent.strHref.c_str());
+ }
+ else
+ {
+ ++oit;
+ }
+ }
+ else
+ {
+ ++oit;
+ continue;
+ }
+ }
+ else
+ {
+ /* just continue with normal files*/
+ ++oit;
+ }
+
+ /* Important! to store disk name for the next checks */
+ disksResolvedNames.insert(diCurrent.strHref);
+////// end of duplicated code.
+ // there must be an image in the OVF disk structs with the same UUID
+ bool fFound = false;
+ Utf8Str strUuid;
+
+ /*
+ * Before importing the virtual hard disk found above (diCurrent/vsdeTargetHD) first
+ * check if the user requested to change either the controller it is to be attached
+ * to and/or the controller port (aka 'channel') on the controller.
+ */
+ if ( !vsdeTargetHD->strExtraConfigCurrent.isEmpty()
+ && vsdeTargetHD->strExtraConfigSuggested != vsdeTargetHD->strExtraConfigCurrent)
+ {
+ /*
+ * First, we examine the extra configuration values for this vdisk:
+ * vsdeTargetHD->strExtraConfigSuggested
+ * vsdeTargetHD->strExtraConfigCurrent
+ * in order to extract both the "before" and "after" storage controller and port
+ * details. The strExtraConfigSuggested string contains the current controller
+ * and port the vdisk is attached to and is populated by Appliance::interpret()
+ * when processing the OVF data; it is in the following format:
+ * 'controller=12;channel=0' (the 'channel=' label for the controller port is
+ * historical and is documented as such in the SDK so can't be changed). The
+ * strExtraConfigSuggested string contains the target controller and port specified
+ * by the user and it has the same format. The 'controller=' value is not a
+ * controller-ID but rather it is the index for the corresponding storage controller
+ * in the array of VirtualSystemDescriptionEntry entries.
+ */
+ int vrc;
+ uint32_t uOrigControllerIndex;
+ vrc = getStorageControllerDetailsFromStr(vsdeTargetHD->strExtraConfigSuggested, "controller=", &uOrigControllerIndex);
+ if (RT_FAILURE(vrc))
+ throw setError(E_FAIL,
+ tr("Original controller value invalid or missing: '%s'"),
+ vsdeTargetHD->strExtraConfigSuggested.c_str());
+
+ uint32_t uTargetControllerIndex;
+ vrc = getStorageControllerDetailsFromStr(vsdeTargetHD->strExtraConfigCurrent, "controller=", &uTargetControllerIndex);
+ if (RT_FAILURE(vrc))
+ throw setError(E_FAIL,
+ tr("Target controller value invalid or missing: '%s'"),
+ vsdeTargetHD->strExtraConfigCurrent.c_str());
+
+ uint32_t uOrigControllerPortValue;
+ vrc = getStorageControllerDetailsFromStr(vsdeTargetHD->strExtraConfigSuggested, "channel=",
+ &uOrigControllerPortValue);
+ if (RT_FAILURE(vrc))
+ throw setError(E_FAIL,
+ tr("Original controller port ('channel=') invalid or missing: '%s'"),
+ vsdeTargetHD->strExtraConfigSuggested.c_str());
+
+ uint32_t uNewControllerPortValue;
+ vrc = getStorageControllerDetailsFromStr(vsdeTargetHD->strExtraConfigCurrent, "channel=", &uNewControllerPortValue);
+ if (RT_FAILURE(vrc))
+ throw setError(E_FAIL,
+ tr("Target controller port ('channel=') invalid or missing: '%s'"),
+ vsdeTargetHD->strExtraConfigCurrent.c_str());
+
+ /*
+ * Second, now that we have the storage controller indexes we locate the corresponding
+ * VirtualSystemDescriptionEntry (VSDE) for both storage controllers which contain
+ * identifying details which will be needed later when walking the list of storage
+ * controllers.
+ */
+ const VirtualSystemDescriptionEntry *vsdeOrigController;
+ vsdeOrigController = vsdescThis->i_findByIndex(uOrigControllerIndex);
+ if (!vsdeOrigController)
+ throw setError(E_FAIL,
+ tr("Failed to find storage controller '%u' in the System Description list"),
+ uOrigControllerIndex);
+
+ const VirtualSystemDescriptionEntry *vsdeTargetController;
+ vsdeTargetController = vsdescThis->i_findByIndex(uTargetControllerIndex);
+ if (!vsdeTargetController)
+ throw setError(E_FAIL,
+ tr("Failed to find storage controller '%u' in the System Description list"),
+ uTargetControllerIndex);
+
+ /*
+ * Third, grab the UUID of the current vdisk so we can identify which device
+ * attached to the original storage controller needs to be updated (channel) and/or
+ * removed.
+ */
+ ovf::DiskImagesMap::const_iterator itDiskImageMap = stack.mapDisks.find(vsdeTargetHD->strRef);
+ if (itDiskImageMap == stack.mapDisks.end())
+ throw setError(E_FAIL,
+ tr("Failed to find virtual disk '%s' in DiskImagesMap"),
+ vsdeTargetHD->strVBoxCurrent.c_str());
+ const ovf::DiskImage &targetDiskImage = itDiskImageMap->second;
+ Utf8Str strTargetDiskUuid = targetDiskImage.uuidVBox;;
+
+ /*
+ * Fourth, walk the attached devices of the original storage controller to find the
+ * current vdisk and update the controller port (aka channel) value if necessary and
+ * also remove the vdisk from this controller if needed.
+ *
+ * A short note on the choice of which items to compare when determining the type of
+ * storage controller here and below in the vdisk addition scenario:
+ * + The VirtualSystemDescriptionEntry 'strOvf' field is populated from the OVF
+ * data which can contain a value like 'vmware.sata.ahci' if created by VMWare so
+ * it isn't a reliable choice.
+ * + The settings::StorageController 'strName' field can have varying content based
+ * on the version of the settings file, e.g. 'IDE Controller' vs. 'IDE' so it
+ * isn't a reliable choice. Further, this field can contain 'SATA' whereas
+ * 'AHCI' is used in 'strOvf' and 'strVBoxSuggested'.
+ * + The VirtualSystemDescriptionEntry 'strVBoxSuggested' field is populated by
+ * Appliance::interpret()->VirtualSystemDescription::i_addEntry() and is thus
+ * under VBox's control and has a fixed format and predictable content.
+ */
+ bool fDiskRemoved = false;
+ settings::AttachedDevice originalAttachedDevice;
+ settings::StorageControllersList::iterator itSCL;
+ for (itSCL = config.hardwareMachine.storage.llStorageControllers.begin();
+ itSCL != config.hardwareMachine.storage.llStorageControllers.end();
+ ++itSCL)
+ {
+ settings::StorageController &SC = *itSCL;
+ const char *pcszSCType = Global::stringifyStorageControllerType(SC.controllerType);
+
+ /* There can only be one storage controller of each type in the OVF data. */
+ if (!vsdeOrigController->strVBoxSuggested.compare(pcszSCType, Utf8Str::CaseInsensitive))
+ {
+ settings::AttachedDevicesList::iterator itAD;
+ for (itAD = SC.llAttachedDevices.begin();
+ itAD != SC.llAttachedDevices.end();
+ ++itAD)
+ {
+ settings::AttachedDevice &AD = *itAD;
+
+ if (AD.uuid.toString() == strTargetDiskUuid)
+ {
+ ULONG ulMaxPorts;
+ rc = i_verifyStorageControllerPortValid(SC.controllerType,
+ uNewControllerPortValue,
+ &ulMaxPorts);
+ if (FAILED(rc))
+ {
+ if (rc == E_INVALIDARG)
+ throw setError(E_INVALIDARG,
+ tr("Illegal channel: '%u'. For %s controllers the valid values are "
+ "0 to %lu (inclusive).\n"), uNewControllerPortValue, pcszSCType, ulMaxPorts-1);
+ else
+ throw rc;
+ }
+
+ if (uOrigControllerPortValue != uNewControllerPortValue)
+ {
+ AD.lPort = (int32_t)uNewControllerPortValue;
+ }
+ if (uOrigControllerIndex != uTargetControllerIndex)
+ {
+ LogFunc(("Removing vdisk '%s' (uuid = %RTuuid) from the %s storage controller.\n",
+ vsdeTargetHD->strVBoxCurrent.c_str(),
+ itAD->uuid.raw(),
+ SC.strName.c_str()));
+ originalAttachedDevice = AD;
+ SC.llAttachedDevices.erase(itAD);
+ fDiskRemoved = true;
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Fifth, if we are moving the vdisk to a different controller and not just changing
+ * the channel then we walk the attached devices of the target controller and check
+ * for conflicts before adding the vdisk detached/removed above.
+ */
+ bool fDiskAdded = false;
+ if (fDiskRemoved)
+ {
+ for (itSCL = config.hardwareMachine.storage.llStorageControllers.begin();
+ itSCL != config.hardwareMachine.storage.llStorageControllers.end();
+ ++itSCL)
+ {
+ settings::StorageController &SC = *itSCL;
+ const char *pcszSCType = Global::stringifyStorageControllerType(SC.controllerType);
+
+ /* There can only be one storage controller of each type in the OVF data. */
+ if (!vsdeTargetController->strVBoxSuggested.compare(pcszSCType, Utf8Str::CaseInsensitive))
+ {
+ settings::AttachedDevicesList::iterator itAD;
+ for (itAD = SC.llAttachedDevices.begin();
+ itAD != SC.llAttachedDevices.end();
+ ++itAD)
+ {
+ settings::AttachedDevice &AD = *itAD;
+ if ( AD.lDevice == originalAttachedDevice.lDevice
+ && AD.lPort == originalAttachedDevice.lPort)
+ throw setError(E_FAIL,
+ tr("Device of type '%s' already attached to the %s controller at this "
+ "port/channel (%d)."),
+ Global::stringifyDeviceType(AD.deviceType), pcszSCType, AD.lPort);
+ }
+
+ LogFunc(("Adding vdisk '%s' (uuid = %RTuuid) to the %s storage controller\n",
+ vsdeTargetHD->strVBoxCurrent.c_str(),
+ originalAttachedDevice.uuid.raw(),
+ SC.strName.c_str()));
+ SC.llAttachedDevices.push_back(originalAttachedDevice);
+ fDiskAdded = true;
+ }
+ }
+
+ if (!fDiskAdded)
+ throw setError(E_FAIL,
+ tr("Failed to add disk '%s' (uuid=%RTuuid) to the %s storage controller."),
+ vsdeTargetHD->strVBoxCurrent.c_str(),
+ originalAttachedDevice.uuid.raw(),
+ vsdeTargetController->strVBoxSuggested.c_str());
+ }
+
+ /*
+ * Sixth, update the machine settings since we've changed the storage controller
+ * and/or controller port for this vdisk.
+ */
+ AutoWriteLock vboxLock(mVirtualBox COMMA_LOCKVAL_SRC_POS);
+ mVirtualBox->i_saveSettings();
+ vboxLock.release();
+ }
+
+ // for each storage controller...
+ for (settings::StorageControllersList::iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
+ sit != config.hardwareMachine.storage.llStorageControllers.end();
+ ++sit)
+ {
+ settings::StorageController &sc = *sit;
+
+ // for each medium attachment to this controller...
+ for (settings::AttachedDevicesList::iterator dit = sc.llAttachedDevices.begin();
+ dit != sc.llAttachedDevices.end();
+ ++dit)
+ {
+ settings::AttachedDevice &d = *dit;
+
+ if (d.uuid.isZero())
+ // empty DVD and floppy media
+ continue;
+
+ // When repairing a broken VirtualBox xml config section (written
+ // by VirtualBox versions earlier than 3.2.10) assume the disks
+ // show up in the same order as in the OVF description.
+ if (fRepairDuplicate)
+ {
+ VirtualSystemDescriptionEntry *vsdeHD = *avsdeHDsIt;
+ ovf::DiskImagesMap::const_iterator itDiskImage = stack.mapDisks.find(vsdeHD->strRef);
+ if (itDiskImage != stack.mapDisks.end())
+ {
+ const ovf::DiskImage &di = itDiskImage->second;
+ d.uuid = Guid(di.uuidVBox);
+ }
+ ++avsdeHDsIt;
+ }
+
+ // convert the Guid to string
+ strUuid = d.uuid.toString();
+
+ if (diCurrent.uuidVBox != strUuid)
+ {
+ continue;
+ }
+
+ /*
+ * step 3: import disk
+ */
+ ComObjPtr<Medium> pTargetMedium;
+ i_importOneDiskImage(diCurrent,
+ vsdeTargetHD->strVBoxCurrent,
+ pTargetMedium,
+ stack);
+
+ // ... and replace the old UUID in the machine config with the one of
+ // the imported disk that was just created
+ Bstr hdId;
+ rc = pTargetMedium->COMGETTER(Id)(hdId.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ /*
+ * 1. saving original UUID for restoring in case of failure.
+ * 2. replacement of original UUID by new UUID in the current VM config (settings::MachineConfigFile).
+ */
+ {
+ rc = stack.saveOriginalUUIDOfAttachedDevice(d, Utf8Str(hdId));
+ d.uuid = hdId;
+ }
+
+ fFound = true;
+ break;
+ } // for (settings::AttachedDevicesList::const_iterator dit = sc.llAttachedDevices.begin();
+ } // for (settings::StorageControllersList::const_iterator sit = config.hardwareMachine.storage.llStorageControllers.begin();
+
+ // no disk with such a UUID found:
+ if (!fFound)
+ throw setError(E_FAIL,
+ tr("<vbox:Machine> element in OVF contains a medium attachment for the disk image %s "
+ "but the OVF describes no such image"),
+ strUuid.c_str());
+
+ ++cImportedDisks;
+
+ }// while(oit != stack.mapDisks.end())
+
+
+ /*
+ * quantity of the imported disks isn't equal to the size of the avsdeHDs list.
+ */
+ if(cImportedDisks < avsdeHDs.size())
+ {
+ Log1Warning(("Not all disk images were imported for VM %s. Check OVF description file.",
+ vmNameEntry->strOvf.c_str()));
+ }
+
+ /*
+ * step 4): create the machine and have it import the config
+ */
+
+ ComObjPtr<Machine> pNewMachine;
+ rc = pNewMachine.createObject();
+ if (FAILED(rc)) throw rc;
+
+ // this magic constructor fills the new machine object with the MachineConfig
+ // instance that we created from the vbox:Machine
+ rc = pNewMachine->init(mVirtualBox,
+ stack.strNameVBox,// name from OVF preparations; can be suffixed to avoid duplicates
+ stack.strSettingsFilename,
+ config); // the whole machine config
+ if (FAILED(rc)) throw rc;
+
+ pReturnNewMachine = ComPtr<IMachine>(pNewMachine);
+
+ // and register it
+ rc = mVirtualBox->RegisterMachine(pNewMachine);
+ if (FAILED(rc)) throw rc;
+
+ // store new machine for roll-back in case of errors
+ Bstr bstrNewMachineId;
+ rc = pNewMachine->COMGETTER(Id)(bstrNewMachineId.asOutParam());
+ if (FAILED(rc)) throw rc;
+ m->llGuidsMachinesCreated.push_back(Guid(bstrNewMachineId));
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * @throws HRESULT errors.
+ */
+void Appliance::i_importMachines(ImportStack &stack)
+{
+ // this is safe to access because this thread only gets started
+ const ovf::OVFReader &reader = *m->pReader;
+
+ // create a session for the machine + disks we manipulate below
+ HRESULT rc = stack.pSession.createInprocObject(CLSID_Session);
+ ComAssertComRCThrowRC(rc);
+
+ list<ovf::VirtualSystem>::const_iterator it;
+ list< ComObjPtr<VirtualSystemDescription> >::const_iterator it1;
+ /* Iterate through all virtual systems of that appliance */
+ size_t i = 0;
+ for (it = reader.m_llVirtualSystems.begin(), it1 = m->virtualSystemDescriptions.begin();
+ it != reader.m_llVirtualSystems.end() && it1 != m->virtualSystemDescriptions.end();
+ ++it, ++it1, ++i)
+ {
+ const ovf::VirtualSystem &vsysThis = *it;
+ ComObjPtr<VirtualSystemDescription> vsdescThis = (*it1);
+
+ // there are two ways in which we can create a vbox machine from OVF:
+ // -- either this OVF was written by vbox 3.2 or later, in which case there is a <vbox:Machine> element
+ // in the <VirtualSystem>; then the VirtualSystemDescription::Data has a settings::MachineConfigFile
+ // with all the machine config pretty-parsed;
+ // -- or this is an OVF from an older vbox or an external source, and then we need to translate the
+ // VirtualSystemDescriptionEntry and do import work
+
+ // Even for the vbox:Machine case, there are a number of configuration items that will be taken from
+ // the OVF because otherwise the "override import parameters" mechanism in the GUI won't work.
+
+ // VM name
+ std::list<VirtualSystemDescriptionEntry*> vsdeName = vsdescThis->i_findByType(VirtualSystemDescriptionType_Name);
+ if (vsdeName.size() < 1)
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Missing VM name"));
+ stack.strNameVBox = vsdeName.front()->strVBoxCurrent;
+
+ // Primary group, which is entirely optional.
+ stack.strPrimaryGroup.setNull();
+ std::list<VirtualSystemDescriptionEntry*> vsdePrimaryGroup = vsdescThis->i_findByType(VirtualSystemDescriptionType_PrimaryGroup);
+ if (vsdePrimaryGroup.size() >= 1)
+ {
+ stack.strPrimaryGroup = vsdePrimaryGroup.front()->strVBoxCurrent;
+ if (stack.strPrimaryGroup.isEmpty())
+ stack.strPrimaryGroup = "/";
+ }
+
+ // Draw the right conclusions from the (possibly modified) VM settings
+ // file name and base folder. If the VM settings file name is modified,
+ // it takes precedence, otherwise it is recreated from the base folder
+ // and the primary group.
+ stack.strSettingsFilename.setNull();
+ std::list<VirtualSystemDescriptionEntry*> vsdeSettingsFile = vsdescThis->i_findByType(VirtualSystemDescriptionType_SettingsFile);
+ if (vsdeSettingsFile.size() >= 1)
+ {
+ VirtualSystemDescriptionEntry *vsdeSF1 = vsdeSettingsFile.front();
+ if (vsdeSF1->strVBoxCurrent != vsdeSF1->strVBoxSuggested)
+ stack.strSettingsFilename = vsdeSF1->strVBoxCurrent;
+ }
+ if (stack.strSettingsFilename.isEmpty())
+ {
+ Utf8Str strBaseFolder;
+ std::list<VirtualSystemDescriptionEntry*> vsdeBaseFolder = vsdescThis->i_findByType(VirtualSystemDescriptionType_BaseFolder);
+ if (vsdeBaseFolder.size() >= 1)
+ strBaseFolder = vsdeBaseFolder.front()->strVBoxCurrent;
+ Bstr bstrSettingsFilename;
+ rc = mVirtualBox->ComposeMachineFilename(Bstr(stack.strNameVBox).raw(),
+ Bstr(stack.strPrimaryGroup).raw(),
+ NULL /* aCreateFlags */,
+ Bstr(strBaseFolder).raw(),
+ bstrSettingsFilename.asOutParam());
+ if (FAILED(rc)) throw rc;
+ stack.strSettingsFilename = bstrSettingsFilename;
+ }
+
+ // Determine the machine folder from the settings file.
+ LogFunc(("i=%zu strName=%s strSettingsFilename=%s\n", i, stack.strNameVBox.c_str(), stack.strSettingsFilename.c_str()));
+ stack.strMachineFolder = stack.strSettingsFilename;
+ stack.strMachineFolder.stripFilename();
+
+ // guest OS type
+ std::list<VirtualSystemDescriptionEntry*> vsdeOS;
+ vsdeOS = vsdescThis->i_findByType(VirtualSystemDescriptionType_OS);
+ if (vsdeOS.size() < 1)
+ throw setError(VBOX_E_FILE_ERROR,
+ tr("Missing guest OS type"));
+ stack.strOsTypeVBox = vsdeOS.front()->strVBoxCurrent;
+
+ // Firmware
+ std::list<VirtualSystemDescriptionEntry*> firmware = vsdescThis->i_findByType(VirtualSystemDescriptionType_BootingFirmware);
+ if (firmware.size() != 1)
+ stack.strFirmwareType = "BIOS";//try default BIOS type
+ else
+ stack.strFirmwareType = firmware.front()->strVBoxCurrent;
+
+ // CPU count
+ std::list<VirtualSystemDescriptionEntry*> vsdeCPU = vsdescThis->i_findByType(VirtualSystemDescriptionType_CPU);
+ if (vsdeCPU.size() != 1)
+ throw setError(VBOX_E_FILE_ERROR, tr("CPU count missing"));
+
+ stack.cCPUs = vsdeCPU.front()->strVBoxCurrent.toUInt32();
+ // We need HWVirt & IO-APIC if more than one CPU is requested
+ if (stack.cCPUs > 1)
+ {
+ stack.fForceHWVirt = true;
+ stack.fForceIOAPIC = true;
+ }
+
+ // RAM
+ std::list<VirtualSystemDescriptionEntry*> vsdeRAM = vsdescThis->i_findByType(VirtualSystemDescriptionType_Memory);
+ if (vsdeRAM.size() != 1)
+ throw setError(VBOX_E_FILE_ERROR, tr("RAM size missing"));
+ uint64_t ullMemorySizeMB = vsdeRAM.front()->strVBoxCurrent.toUInt64() / _1M;
+ stack.ulMemorySizeMB = (uint32_t)ullMemorySizeMB;
+
+#ifdef VBOX_WITH_USB
+ // USB controller
+ std::list<VirtualSystemDescriptionEntry*> vsdeUSBController =
+ vsdescThis->i_findByType(VirtualSystemDescriptionType_USBController);
+ // USB support is enabled if there's at least one such entry; to disable USB support,
+ // the type of the USB item would have been changed to "ignore"
+ stack.fUSBEnabled = !vsdeUSBController.empty();
+#endif
+ // audio adapter
+ std::list<VirtualSystemDescriptionEntry*> vsdeAudioAdapter =
+ vsdescThis->i_findByType(VirtualSystemDescriptionType_SoundCard);
+ /** @todo we support one audio adapter only */
+ if (!vsdeAudioAdapter.empty())
+ stack.strAudioAdapter = vsdeAudioAdapter.front()->strVBoxCurrent;
+
+ // for the description of the new machine, always use the OVF entry, the user may have changed it in the import config
+ std::list<VirtualSystemDescriptionEntry*> vsdeDescription =
+ vsdescThis->i_findByType(VirtualSystemDescriptionType_Description);
+ if (!vsdeDescription.empty())
+ stack.strDescription = vsdeDescription.front()->strVBoxCurrent;
+
+ // import vbox:machine or OVF now
+ ComPtr<IMachine> pNewMachine; /** @todo pointless */
+ if (vsdescThis->m->pConfig)
+ // vbox:Machine config
+ i_importVBoxMachine(vsdescThis, pNewMachine, stack);
+ else
+ // generic OVF config
+ i_importMachineGeneric(vsysThis, vsdescThis, pNewMachine, stack);
+
+ } // for (it = pAppliance->m->llVirtualSystems.begin() ...
+}
+
+HRESULT Appliance::ImportStack::saveOriginalUUIDOfAttachedDevice(settings::AttachedDevice &device,
+ const Utf8Str &newlyUuid)
+{
+ HRESULT rc = S_OK;
+
+ /* save for restoring */
+ mapNewUUIDsToOriginalUUIDs.insert(std::make_pair(newlyUuid, device.uuid.toString()));
+
+ return rc;
+}
+
+HRESULT Appliance::ImportStack::restoreOriginalUUIDOfAttachedDevice(settings::MachineConfigFile *config)
+{
+ HRESULT rc = S_OK;
+
+ settings::StorageControllersList &llControllers = config->hardwareMachine.storage.llStorageControllers;
+ settings::StorageControllersList::iterator itscl;
+ for (itscl = llControllers.begin();
+ itscl != llControllers.end();
+ ++itscl)
+ {
+ settings::AttachedDevicesList &llAttachments = itscl->llAttachedDevices;
+ settings::AttachedDevicesList::iterator itadl = llAttachments.begin();
+ while (itadl != llAttachments.end())
+ {
+ std::map<Utf8Str , Utf8Str>::iterator it =
+ mapNewUUIDsToOriginalUUIDs.find(itadl->uuid.toString());
+ if(it!=mapNewUUIDsToOriginalUUIDs.end())
+ {
+ Utf8Str uuidOriginal = it->second;
+ itadl->uuid = Guid(uuidOriginal);
+ mapNewUUIDsToOriginalUUIDs.erase(it->first);
+ }
+ ++itadl;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @throws Nothing
+ */
+RTVFSIOSTREAM Appliance::ImportStack::claimOvaLookAHead(void)
+{
+ RTVFSIOSTREAM hVfsIos = this->hVfsIosOvaLookAhead;
+ this->hVfsIosOvaLookAhead = NIL_RTVFSIOSTREAM;
+ /* We don't free the name since it may be referenced in error messages and such. */
+ return hVfsIos;
+}
+
diff --git a/src/VBox/Main/src-server/AudioAdapterImpl.cpp b/src/VBox/Main/src-server/AudioAdapterImpl.cpp
new file mode 100644
index 00000000..c6a861f6
--- /dev/null
+++ b/src/VBox/Main/src-server/AudioAdapterImpl.cpp
@@ -0,0 +1,690 @@
+/* $Id: AudioAdapterImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_AUDIOADAPTER
+#include "AudioAdapterImpl.h"
+#include "MachineImpl.h"
+#include "SystemPropertiesImpl.h"
+#include "VirtualBoxImpl.h"
+
+#include <iprt/cpp/utils.h>
+
+#include <VBox/settings.h>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AudioAdapter private data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct AudioAdapter::Data
+{
+ Data()
+ : pParent(NULL)
+ { }
+
+ AudioSettings * const pParent;
+ const ComObjPtr<AudioAdapter> pPeer;
+
+ // use the XML settings structure in the members for simplicity
+ Backupable<settings::AudioAdapter> bd;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+AudioAdapter::AudioAdapter()
+{
+}
+
+AudioAdapter::~AudioAdapter()
+{
+}
+
+HRESULT AudioAdapter::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void AudioAdapter::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the audio adapter object.
+ *
+ * @returns HRESULT
+ * @param aParent Pointer of the parent object.
+ */
+HRESULT AudioAdapter::init(AudioSettings *aParent)
+{
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pParent) = aParent;
+ /* mPeer is left null */
+
+ /* We now always default to the "Default" audio driver, to make it easier
+ * to move VMs around different host OSes.
+ *
+ * This can be changed by the user explicitly, if needed / wanted. */
+ m->bd.allocate();
+ m->bd->driverType = AudioDriverType_Default;
+ m->bd->fEnabledIn = false;
+ m->bd->fEnabledOut = false;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the audio adapter object given another audio adapter object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for reading.
+ *
+ * @returns HRESULT
+ * @param aParent Pointer of the parent object.
+ * @param aThat Pointer to audio adapter to use settings from.
+ */
+HRESULT AudioAdapter::init(AudioSettings *aParent, AudioAdapter *aThat)
+{
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pParent) = aParent;
+ unconst(m->pPeer) = aThat;
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.share(aThat->m->bd);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the audio adapter object given another audio adapter object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ *
+ * @note Locks @a aThat object for reading.
+ *
+ * @returns HRESULT
+ * @param aParent Pointer of the parent object.
+ * @param aThat Pointer to audio adapter to use settings from.
+ */
+HRESULT AudioAdapter::initCopy(AudioSettings *aParent, AudioAdapter *aThat)
+{
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pParent) = aParent;
+ /* mPeer is left null */
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.attachCopy(aThat->m->bd);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void AudioAdapter::uninit(void)
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pParent) = NULL;
+
+ delete m;
+ m = NULL;
+}
+
+// IAudioAdapter properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT AudioAdapter::getEnabled(BOOL *aEnabled)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aEnabled = m->bd->fEnabled;
+
+ return S_OK;
+}
+
+HRESULT AudioAdapter::setEnabled(BOOL aEnabled)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->fEnabled != RT_BOOL(aEnabled))
+ {
+ m->bd.backup();
+ m->bd->fEnabled = RT_BOOL(aEnabled);
+ alock.release();
+
+ m->pParent->i_onSettingsChanged(); // mParent is const, needs no locking
+ m->pParent->i_onAdapterChanged(this);
+ }
+
+ return S_OK;
+}
+
+HRESULT AudioAdapter::getEnabledIn(BOOL *aEnabled)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aEnabled = RT_BOOL(m->bd->fEnabledIn);
+
+ return S_OK;
+}
+
+HRESULT AudioAdapter::setEnabledIn(BOOL aEnabled)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (RT_BOOL(aEnabled) != m->bd->fEnabledIn)
+ {
+ m->bd.backup();
+ m->bd->fEnabledIn = RT_BOOL(aEnabled);
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged(); // mParent is const, needs no locking
+ m->pParent->i_onAdapterChanged(this);
+ }
+
+ return S_OK;
+}
+
+HRESULT AudioAdapter::getEnabledOut(BOOL *aEnabled)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aEnabled = RT_BOOL(m->bd->fEnabledOut);
+
+ return S_OK;
+}
+
+HRESULT AudioAdapter::setEnabledOut(BOOL aEnabled)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (RT_BOOL(aEnabled) != m->bd->fEnabledOut)
+ {
+ m->bd.backup();
+ m->bd->fEnabledOut = RT_BOOL(aEnabled);
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged(); // mParent is const, needs no locking
+ m->pParent->i_onAdapterChanged(this);
+ }
+
+ return S_OK;
+}
+
+HRESULT AudioAdapter::getAudioDriver(AudioDriverType_T *aAudioDriver)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAudioDriver = m->bd->driverType;
+
+ return S_OK;
+}
+
+HRESULT AudioAdapter::setAudioDriver(AudioDriverType_T aAudioDriver)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ if (m->bd->driverType != aAudioDriver)
+ {
+ if (settings::MachineConfigFile::isAudioDriverAllowedOnThisHost(aAudioDriver))
+ {
+ m->bd.backup();
+ m->bd->driverType = aAudioDriver;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged(); // mParent is const, needs no locking
+ }
+ else
+ {
+ AssertMsgFailed(("Wrong audio driver type %d\n", aAudioDriver));
+ rc = E_FAIL;
+ }
+ }
+
+ return rc;
+}
+
+HRESULT AudioAdapter::getAudioController(AudioControllerType_T *aAudioController)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAudioController = m->bd->controllerType;
+
+ return S_OK;
+}
+
+HRESULT AudioAdapter::setAudioController(AudioControllerType_T aAudioController)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ if (m->bd->controllerType != aAudioController)
+ {
+ AudioCodecType_T defaultCodec;
+
+ /*
+ * which audio hardware type are we supposed to use?
+ */
+ switch (aAudioController)
+ {
+ /* codec type needs to match the controller. */
+ case AudioControllerType_AC97:
+ defaultCodec = AudioCodecType_STAC9700;
+ break;
+ case AudioControllerType_SB16:
+ defaultCodec = AudioCodecType_SB16;
+ break;
+ case AudioControllerType_HDA:
+ defaultCodec = AudioCodecType_STAC9221;
+ break;
+
+ default:
+ AssertMsgFailed(("Wrong audio controller type %d\n", aAudioController));
+ defaultCodec = AudioCodecType_Null; /* Shut up MSC */
+ hrc = E_FAIL;
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ m->bd.backup();
+ m->bd->controllerType = aAudioController;
+ m->bd->codecType = defaultCodec;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged(); // mParent is const, needs no locking
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT AudioAdapter::getAudioCodec(AudioCodecType_T *aAudioCodec)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAudioCodec = m->bd->codecType;
+
+ return S_OK;
+}
+
+HRESULT AudioAdapter::setAudioCodec(AudioCodecType_T aAudioCodec)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ /*
+ * ensure that the codec type matches the audio controller
+ */
+ switch (m->bd->controllerType)
+ {
+ case AudioControllerType_AC97:
+ {
+ if ( (aAudioCodec != AudioCodecType_STAC9700)
+ && (aAudioCodec != AudioCodecType_AD1980))
+ hrc = E_INVALIDARG;
+ break;
+ }
+
+ case AudioControllerType_SB16:
+ {
+ if (aAudioCodec != AudioCodecType_SB16)
+ hrc = E_INVALIDARG;
+ break;
+ }
+
+ case AudioControllerType_HDA:
+ {
+ if (aAudioCodec != AudioCodecType_STAC9221)
+ hrc = E_INVALIDARG;
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Wrong audio controller type %d\n",
+ m->bd->controllerType));
+ hrc = E_FAIL;
+ }
+
+ if (!SUCCEEDED(hrc))
+ return setError(hrc,
+ tr("Invalid audio codec type %d"),
+ aAudioCodec);
+
+ if (m->bd->codecType != aAudioCodec)
+ {
+ m->bd.backup();
+ m->bd->codecType = aAudioCodec;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged(); // mParent is const, needs no locking
+ }
+
+ return hrc;
+}
+
+HRESULT AudioAdapter::getPropertiesList(std::vector<com::Utf8Str>& aProperties)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ using namespace settings;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aProperties.resize(0);
+ StringsMap::const_iterator cit = m->bd->properties.begin();
+ while(cit != m->bd->properties.end())
+ {
+ Utf8Str key = cit->first;
+ aProperties.push_back(cit->first);
+ ++cit;
+ }
+
+ return S_OK;
+}
+
+HRESULT AudioAdapter::getProperty(const com::Utf8Str &aKey, com::Utf8Str &aValue)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ settings::StringsMap::const_iterator cit = m->bd->properties.find(aKey);
+ if (cit != m->bd->properties.end())
+ aValue = cit->second;
+
+ return S_OK;
+}
+
+HRESULT AudioAdapter::setProperty(const com::Utf8Str &aKey, const com::Utf8Str &aValue)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Generic properties processing.
+ * Look up the old value first; if nothing's changed then do nothing.
+ */
+ Utf8Str strOldValue;
+
+ settings::StringsMap::const_iterator cit = m->bd->properties.find(aKey);
+ if (cit != m->bd->properties.end())
+ strOldValue = cit->second;
+
+ if (strOldValue != aValue)
+ {
+ if (aValue.isEmpty())
+ m->bd->properties.erase(aKey);
+ else
+ m->bd->properties[aKey] = aValue;
+ }
+
+ alock.release();
+
+ return S_OK;
+}
+
+// IAudioAdapter methods
+/////////////////////////////////////////////////////////////////////////////
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Loads settings from the given machine node.
+ * May be called once right after this object creation.
+ *
+ * @returns HRESULT
+ * @param data Audio adapter configuration settings to load from.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT AudioAdapter::i_loadSettings(const settings::AudioAdapter &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Note: we assume that the default values for attributes of optional
+ * nodes are assigned in the Data::Data() constructor and don't do it
+ * here. It implies that this method may only be called after constructing
+ * a new AudioAdapter object while all its data fields are in the default
+ * values. Exceptions are fields whose creation time defaults don't match
+ * values that should be applied when these fields are not explicitly set
+ * in the settings file (for backwards compatibility reasons). This takes
+ * place when a setting of a newly created object must default to A while
+ * the same setting of an object loaded from the old settings file must
+ * default to B. */
+ m->bd.assignCopy(&data);
+
+ return S_OK;
+}
+
+/**
+ * Saves settings to the given machine node.
+ *
+ * @returns HRESULT
+ * @param data Audio adapter configuration settings to save to.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT AudioAdapter::i_saveSettings(settings::AudioAdapter &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ data = *m->bd.data();
+
+ return S_OK;
+}
+
+/**
+ * Rolls back the current configuration to a former state.
+ *
+ * @note Locks this object for writing.
+ */
+void AudioAdapter::i_rollback()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.rollback();
+}
+
+/**
+ * Commits the current settings and propagates those to a peer (if assigned).
+ *
+ * @note Locks this object for writing, together with the peer object (also
+ * for writing) if there is one.
+ */
+void AudioAdapter::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ {
+ m->bd.commit();
+ if (m->pPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ m->pPeer->m->bd.attach(m->bd);
+ }
+ }
+}
+
+/**
+ * Copies settings from a given audio adapter object.
+ *
+ * This object makes a private copy of data of the original object passed as
+ * an argument.
+ *
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ *
+ * @param aThat Audio adapter to load settings from.
+ */
+void AudioAdapter::i_copyFrom(AudioAdapter *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ m->bd.assignCopy(aThat->m->bd);
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/AudioSettingsImpl.cpp b/src/VBox/Main/src-server/AudioSettingsImpl.cpp
new file mode 100644
index 00000000..8d6786d6
--- /dev/null
+++ b/src/VBox/Main/src-server/AudioSettingsImpl.cpp
@@ -0,0 +1,434 @@
+/* $Id: AudioSettingsImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation - Audio settings for a VM.
+ */
+
+/*
+ * Copyright (C) 2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_AUDIOSETTINGS
+#include "AudioSettingsImpl.h"
+#include "MachineImpl.h"
+
+#include <iprt/cpp/utils.h>
+
+#include <VBox/settings.h>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AudioSettings private data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct AudioSettings::Data
+{
+ Data()
+ : pMachine(NULL)
+ { }
+
+ Machine * const pMachine;
+ const ComObjPtr<AudioAdapter> pAdapter;
+ const ComObjPtr<AudioSettings> pPeer;
+};
+
+DEFINE_EMPTY_CTOR_DTOR(AudioSettings)
+
+HRESULT AudioSettings::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void AudioSettings::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+
+// public initializer/uninitializer for internal purposes only
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the audio settings object.
+ *
+ * @returns HRESULT
+ * @param aParent Pointer of the parent object.
+ */
+HRESULT AudioSettings::init(Machine *aParent)
+{
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ /* share the parent weakly */
+ unconst(m->pMachine) = aParent;
+
+ /* create the audio adapter object (always present, default is disabled) */
+ unconst(m->pAdapter).createObject();
+ m->pAdapter->init(this);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the audio settings object given another audio settings object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for reading.
+ *
+ * @returns HRESULT
+ * @param aParent Pointer of the parent object.
+ * @param aThat Pointer to audio adapter to use settings from.
+ */
+HRESULT AudioSettings::init(Machine *aParent, AudioSettings *aThat)
+{
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ unconst(m->pPeer) = aThat;
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = unconst(m->pAdapter).createObject();
+ ComAssertComRCRet(hrc, hrc);
+ hrc = m->pAdapter->init(this, aThat->m->pAdapter);
+ ComAssertComRCRet(hrc, hrc);
+
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the guest object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ *
+ * @note Locks @a aThat object for reading.
+ *
+ * @returns HRESULT
+ * @param aParent Pointer of the parent object.
+ * @param aThat Pointer to audio adapter to use settings from.
+ */
+HRESULT AudioSettings::initCopy(Machine *aParent, AudioSettings *aThat)
+{
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ // pPeer is left null
+
+ AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = unconst(m->pAdapter).createObject();
+ ComAssertComRCRet(hrc, hrc);
+ hrc = m->pAdapter->init(this);
+ ComAssertComRCRet(hrc, hrc);
+ m->pAdapter->i_copyFrom(aThat->m->pAdapter);
+
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void AudioSettings::uninit(void)
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pMachine) = NULL;
+
+ delete m;
+ m = NULL;
+}
+
+
+// IAudioSettings properties
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT AudioSettings::getAdapter(ComPtr<IAudioAdapter> &aAdapter)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aAdapter = m->pAdapter;
+
+ return S_OK;
+}
+
+
+// IAudioSettings methods
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT AudioSettings::getHostAudioDevice(AudioDirection_T aUsage, ComPtr<IHostAudioDevice> &aDevice)
+{
+ RT_NOREF(aUsage, aDevice);
+ ReturnComNotImplemented();
+}
+
+HRESULT AudioSettings::setHostAudioDevice(const ComPtr<IHostAudioDevice> &aDevice, AudioDirection_T aUsage)
+{
+ RT_NOREF(aDevice, aUsage);
+ ReturnComNotImplemented();
+}
+
+
+// public methods only for internal purposes
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Determines whether the audio settings currently can be changed or not.
+ *
+ * @returns \c true if the settings can be changed, \c false if not.
+ */
+bool AudioSettings::i_canChangeSettings(void)
+{
+ AutoAnyStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc()))
+ return false;
+
+ /** @todo Do some more checks here? */
+ return true;
+}
+
+/**
+ * Gets called when the machine object needs to know that audio adapter settings
+ * have been changed.
+ *
+ * @param pAdapter Pointer to audio adapter which has changed.
+ */
+void AudioSettings::i_onAdapterChanged(IAudioAdapter *pAdapter)
+{
+ AssertPtrReturnVoid(pAdapter);
+ m->pMachine->i_onAudioAdapterChange(pAdapter); // mParent is const, needs no locking
+}
+
+/**
+ * Gets called when the machine object needs to know that a host audio device
+ * has been changed.
+ *
+ * @param pDevice Host audio device which has changed.
+ * @param fIsNew Set to \c true if this is a new device (i.e. has not been present before), \c false if not.
+ * @param enmState The current state of the device.
+ * @param pErrInfo Additional error information in case of error(s).
+ */
+void AudioSettings::i_onHostDeviceChanged(IHostAudioDevice *pDevice,
+ bool fIsNew, AudioDeviceState_T enmState, IVirtualBoxErrorInfo *pErrInfo)
+{
+ AssertPtrReturnVoid(pDevice);
+ m->pMachine->i_onHostAudioDeviceChange(pDevice, fIsNew, enmState, pErrInfo); // mParent is const, needs no locking
+}
+
+/**
+ * Gets called when the machine object needs to know that the audio settings
+ * have been changed.
+ */
+void AudioSettings::i_onSettingsChanged(void)
+{
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_AudioSettings);
+ mlock.release();
+}
+
+/**
+ * Loads settings from the given machine node.
+ * May be called once right after this object creation.
+ *
+ * @returns HRESULT
+ * @param data Audio adapter configuration settings to load from.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT AudioSettings::i_loadSettings(const settings::AudioAdapter &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->pAdapter->i_loadSettings(data);
+
+ /* Note: The host audio device selection is run-time only, e.g. won't be serialized in the settings! */
+ return S_OK;
+}
+
+/**
+ * Saves audio settings to the given machine node.
+ *
+ * @returns HRESULT
+ * @param data Audio configuration settings to save to.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT AudioSettings::i_saveSettings(settings::AudioAdapter &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->pAdapter->i_saveSettings(data);
+
+ /* Note: The host audio device selection is run-time only, e.g. won't be serialized in the settings! */
+ return S_OK;
+}
+
+/**
+ * Copies settings from a given audio settings object.
+ *
+ * This object makes a private copy of data of the original object passed as
+ * an argument.
+ *
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ *
+ * @param aThat Audio settings to load from.
+ */
+void AudioSettings::i_copyFrom(AudioSettings *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ m->pAdapter->i_copyFrom(aThat->m->pAdapter);
+}
+
+/**
+ * Applies default audio settings, based on the given guest OS type.
+ *
+ * @returns HRESULT
+ * @param aGuestOsType Guest OS type to use for basing the default settings on.
+ */
+HRESULT AudioSettings::i_applyDefaults(ComObjPtr<GuestOSType> &aGuestOsType)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AudioControllerType_T audioController;
+ HRESULT rc = aGuestOsType->COMGETTER(RecommendedAudioController)(&audioController);
+ if (FAILED(rc)) return rc;
+
+ rc = m->pAdapter->COMSETTER(AudioController)(audioController);
+ if (FAILED(rc)) return rc;
+
+ AudioCodecType_T audioCodec;
+ rc = aGuestOsType->COMGETTER(RecommendedAudioCodec)(&audioCodec);
+ if (FAILED(rc)) return rc;
+
+ rc = m->pAdapter->COMSETTER(AudioCodec)(audioCodec);
+ if (FAILED(rc)) return rc;
+
+ rc = m->pAdapter->COMSETTER(Enabled)(true);
+ if (FAILED(rc)) return rc;
+
+ rc = m->pAdapter->COMSETTER(EnabledOut)(true);
+ if (FAILED(rc)) return rc;
+
+ /* Note: We do NOT enable audio input by default due to security reasons!
+ * This always has to be done by the user manually. */
+
+ /* Note: Does not touch the host audio device selection, as this is a run-time only setting. */
+ return S_OK;
+}
+
+/**
+ * Rolls back the current configuration to a former state.
+ *
+ * @note Locks this object for writing.
+ */
+void AudioSettings::i_rollback(void)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->pAdapter->i_rollback();
+
+ /* Note: Does not touch the host audio device selection, as this is a run-time only setting. */
+}
+
+/**
+ * Commits the current settings and propagates those to a peer (if assigned).
+ *
+ * @note Locks this object for writing, together with the peer object (also
+ * for writing) if there is one.
+ */
+void AudioSettings::i_commit(void)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ m->pAdapter->i_commit();
+
+ /* Note: Does not touch the host audio device selection, as this is a run-time only setting. */
+}
+
diff --git a/src/VBox/Main/src-server/BIOSSettingsImpl.cpp b/src/VBox/Main/src-server/BIOSSettingsImpl.cpp
new file mode 100644
index 00000000..1e08feab
--- /dev/null
+++ b/src/VBox/Main/src-server/BIOSSettingsImpl.cpp
@@ -0,0 +1,621 @@
+/* $Id: BIOSSettingsImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation - Machine BIOS settings.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_BIOSSETTINGS
+#include "BIOSSettingsImpl.h"
+#include "MachineImpl.h"
+#include "GuestOSTypeImpl.h"
+
+#include <iprt/cpp/utils.h>
+#include <VBox/settings.h>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// BIOSSettings private data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct BIOSSettings::Data
+{
+ Data()
+ : pMachine(NULL)
+ { }
+
+ Machine * const pMachine;
+ ComObjPtr<BIOSSettings> pPeer;
+
+ // use the XML settings structure in the members for simplicity
+ Backupable<settings::BIOSSettings> bd;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(BIOSSettings)
+
+HRESULT BIOSSettings::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void BIOSSettings::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the BIOS settings object.
+ *
+ * @returns COM result indicator
+ */
+HRESULT BIOSSettings::init(Machine *aParent)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ /* share the parent weakly */
+ unconst(m->pMachine) = aParent;
+
+ m->bd.allocate();
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Initializes the BIOS settings object given another BIOS settings object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ */
+HRESULT BIOSSettings::init(Machine *aParent, BIOSSettings *that)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p, that: %p\n", aParent, that));
+
+ ComAssertRet(aParent && that, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ m->pPeer = that;
+
+ AutoWriteLock thatlock(that COMMA_LOCKVAL_SRC_POS);
+ m->bd.share(that->m->bd);
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Initializes the guest object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ */
+HRESULT BIOSSettings::initCopy(Machine *aParent, BIOSSettings *that)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p, that: %p\n", aParent, that));
+
+ ComAssertRet(aParent && that, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ // mPeer is left null
+
+ AutoWriteLock thatlock(that COMMA_LOCKVAL_SRC_POS); /** @todo r=andy Shouldn't a read lock be sufficient here? */
+ m->bd.attachCopy(that->m->bd);
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void BIOSSettings::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m->bd.free();
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pMachine) = NULL;
+
+ delete m;
+ m = NULL;
+
+ LogFlowThisFuncLeave();
+}
+
+// IBIOSSettings properties
+/////////////////////////////////////////////////////////////////////////////
+
+
+HRESULT BIOSSettings::getLogoFadeIn(BOOL *enabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *enabled = m->bd->fLogoFadeIn;
+
+ return S_OK;
+}
+
+HRESULT BIOSSettings::setLogoFadeIn(BOOL enable)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->fLogoFadeIn = RT_BOOL(enable);
+
+ alock.release();
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_BIOS);
+
+ return S_OK;
+}
+
+
+HRESULT BIOSSettings::getLogoFadeOut(BOOL *enabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *enabled = m->bd->fLogoFadeOut;
+
+ return S_OK;
+}
+
+HRESULT BIOSSettings::setLogoFadeOut(BOOL enable)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->fLogoFadeOut = RT_BOOL(enable);
+
+ alock.release();
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_BIOS);
+
+ return S_OK;
+}
+
+
+HRESULT BIOSSettings::getLogoDisplayTime(ULONG *displayTime)
+{
+ if (!displayTime)
+ return E_POINTER;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *displayTime = m->bd->ulLogoDisplayTime;
+
+ return S_OK;
+}
+
+HRESULT BIOSSettings::setLogoDisplayTime(ULONG displayTime)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->ulLogoDisplayTime = displayTime;
+
+ alock.release();
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_BIOS);
+
+ return S_OK;
+}
+
+
+HRESULT BIOSSettings::getLogoImagePath(com::Utf8Str &imagePath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ imagePath = m->bd->strLogoImagePath;
+ return S_OK;
+}
+
+HRESULT BIOSSettings::setLogoImagePath(const com::Utf8Str &imagePath)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->strLogoImagePath = imagePath;
+
+ alock.release();
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_BIOS);
+
+ return S_OK;
+}
+
+HRESULT BIOSSettings::getBootMenuMode(BIOSBootMenuMode_T *bootMenuMode)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *bootMenuMode = m->bd->biosBootMenuMode;
+ return S_OK;
+}
+
+HRESULT BIOSSettings::setBootMenuMode(BIOSBootMenuMode_T bootMenuMode)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->biosBootMenuMode = bootMenuMode;
+
+ alock.release();
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_BIOS);
+
+ return S_OK;
+}
+
+
+HRESULT BIOSSettings::getACPIEnabled(BOOL *enabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *enabled = m->bd->fACPIEnabled;
+
+ return S_OK;
+}
+
+HRESULT BIOSSettings::setACPIEnabled(BOOL enable)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->fACPIEnabled = RT_BOOL(enable);
+
+ alock.release();
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_BIOS);
+
+ return S_OK;
+}
+
+
+HRESULT BIOSSettings::getIOAPICEnabled(BOOL *aIOAPICEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aIOAPICEnabled = m->bd->fIOAPICEnabled;
+
+ return S_OK;
+}
+
+HRESULT BIOSSettings::setIOAPICEnabled(BOOL aIOAPICEnabled)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->fIOAPICEnabled = RT_BOOL(aIOAPICEnabled);
+
+ alock.release();
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_BIOS);
+
+ return S_OK;
+}
+
+
+HRESULT BIOSSettings::getAPICMode(APICMode_T *aAPICMode)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAPICMode = m->bd->apicMode;
+
+ return S_OK;
+}
+
+HRESULT BIOSSettings::setAPICMode(APICMode_T aAPICMode)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->apicMode = aAPICMode;
+
+ alock.release();
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_BIOS);
+
+ return S_OK;
+}
+
+
+HRESULT BIOSSettings::getPXEDebugEnabled(BOOL *enabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *enabled = m->bd->fPXEDebugEnabled;
+
+ return S_OK;
+}
+
+HRESULT BIOSSettings::setPXEDebugEnabled(BOOL enable)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->fPXEDebugEnabled = RT_BOOL(enable);
+
+ alock.release();
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_BIOS);
+
+ return S_OK;
+}
+
+
+HRESULT BIOSSettings::getTimeOffset(LONG64 *offset)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *offset = m->bd->llTimeOffset;
+
+ return S_OK;
+}
+
+HRESULT BIOSSettings::setTimeOffset(LONG64 offset)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->llTimeOffset = offset;
+
+ alock.release();
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_BIOS);
+
+ return S_OK;
+}
+
+
+HRESULT BIOSSettings::getSMBIOSUuidLittleEndian(BOOL *enabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *enabled = m->bd->fSmbiosUuidLittleEndian;
+
+ return S_OK;
+}
+
+HRESULT BIOSSettings::setSMBIOSUuidLittleEndian(BOOL enable)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->fSmbiosUuidLittleEndian = RT_BOOL(enable);
+
+ alock.release();
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_BIOS);
+
+ return S_OK;
+}
+
+
+// IBIOSSettings methods
+/////////////////////////////////////////////////////////////////////////////
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Loads settings from the given machine node.
+ * May be called once right after this object creation.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT BIOSSettings::i_loadSettings(const settings::BIOSSettings &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // simply copy
+ m->bd.assignCopy(&data);
+ return S_OK;
+}
+
+/**
+ * Saves settings to the given machine node.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT BIOSSettings::i_saveSettings(settings::BIOSSettings &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ data = *m->bd.data();
+
+ return S_OK;
+}
+
+void BIOSSettings::i_rollback()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->bd.rollback();
+}
+
+void BIOSSettings::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ {
+ m->bd.commit();
+ if (m->pPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ AutoWriteLock peerlock(m->pPeer COMMA_LOCKVAL_SRC_POS);
+ m->pPeer->m->bd.attach(m->bd);
+ }
+ }
+}
+
+void BIOSSettings::i_copyFrom(BIOSSettings *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ m->bd.assignCopy(aThat->m->bd);
+}
+
+void BIOSSettings::i_applyDefaults(GuestOSType *aOsType)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Initialize default BIOS settings here */
+ if (aOsType)
+ m->bd->fIOAPICEnabled = aOsType->i_recommendedIOAPIC();
+ else
+ m->bd->fIOAPICEnabled = true;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/BandwidthControlImpl.cpp b/src/VBox/Main/src-server/BandwidthControlImpl.cpp
new file mode 100644
index 00000000..ee976bd3
--- /dev/null
+++ b/src/VBox/Main/src-server/BandwidthControlImpl.cpp
@@ -0,0 +1,596 @@
+/* $Id: BandwidthControlImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_BANDWIDTHCONTROL
+#include "BandwidthControlImpl.h"
+#include "BandwidthGroupImpl.h"
+#include "MachineImpl.h"
+#include "Global.h"
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+#include <iprt/cpp/utils.h>
+#include <VBox/com/array.h>
+#include <VBox/param.h>
+#include <algorithm>
+
+// defines
+/////////////////////////////////////////////////////////////////////////////
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+DEFINE_EMPTY_CTOR_DTOR(BandwidthControl)
+
+
+HRESULT BandwidthControl::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void BandwidthControl::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the bandwidth group object.
+ *
+ * @returns COM result indicator.
+ * @param aParent Pointer to our parent object.
+ */
+HRESULT BandwidthControl::init(Machine *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+
+ /* m->pPeer is left null */
+
+ m->llBandwidthGroups.allocate();
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the object given another object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for writing if @a aReshare is @c true, or for
+ * reading if @a aReshare is false.
+ */
+HRESULT BandwidthControl::init(Machine *aParent,
+ BandwidthControl *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+
+ /* sanity */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ unconst(m->pPeer) = aThat;
+ AutoWriteLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+
+ /* create copies of all groups */
+ m->llBandwidthGroups.allocate();
+ BandwidthGroupList::const_iterator it;
+ for (it = aThat->m->llBandwidthGroups->begin();
+ it != aThat->m->llBandwidthGroups->end();
+ ++it)
+ {
+ ComObjPtr<BandwidthGroup> group;
+ group.createObject();
+ group->init(this, *it);
+ m->llBandwidthGroups->push_back(group);
+ }
+
+ /* Confirm successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the bandwidth control object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ */
+HRESULT BandwidthControl::initCopy(Machine *aParent, BandwidthControl *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+ /* m->pPeer is left null */
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS);
+
+ /* create copies of all groups */
+ m->llBandwidthGroups.allocate();
+ BandwidthGroupList::const_iterator it;
+ for (it = aThat->m->llBandwidthGroups->begin();
+ it != aThat->m->llBandwidthGroups->end();
+ ++it)
+ {
+ ComObjPtr<BandwidthGroup> group;
+ group.createObject();
+ group->initCopy(this, *it);
+ m->llBandwidthGroups->push_back(group);
+ }
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+
+/**
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ */
+void BandwidthControl::i_copyFrom(BandwidthControl *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* even more sanity */
+ AutoAnyStateDependency adep(m->pParent);
+ AssertComRCReturnVoid(adep.rc());
+ /* Machine::copyFrom() may not be called when the VM is running */
+ AssertReturnVoid(!Global::IsOnline(adep.machineState()));
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* create private copies of all bandwidth groups */
+ m->llBandwidthGroups.backup();
+ m->llBandwidthGroups->clear();
+ BandwidthGroupList::const_iterator it;
+ for (it = aThat->m->llBandwidthGroups->begin();
+ it != aThat->m->llBandwidthGroups->end();
+ ++it)
+ {
+ ComObjPtr<BandwidthGroup> group;
+ group.createObject();
+ group->initCopy(this, *it);
+ m->llBandwidthGroups->push_back(group);
+ }
+}
+
+/** @note Locks objects for writing! */
+void BandwidthControl::i_rollback()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* we need the machine state */
+ AutoAnyStateDependency adep(m->pParent);
+ AssertComRCReturnVoid(adep.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ BandwidthGroupList::const_iterator it;
+
+ if (!m->llBandwidthGroups.isNull())
+ {
+ if (m->llBandwidthGroups.isBackedUp())
+ {
+ /* unitialize all new groups (absent in the backed up list). */
+ BandwidthGroupList *backedList = m->llBandwidthGroups.backedUpData();
+ for (it = m->llBandwidthGroups->begin();
+ it != m->llBandwidthGroups->end();
+ ++it)
+ {
+ if ( std::find(backedList->begin(), backedList->end(), *it)
+ == backedList->end())
+ (*it)->uninit();
+ }
+
+ /* restore the list */
+ m->llBandwidthGroups.rollback();
+ }
+
+ /* rollback any changes to groups after restoring the list */
+ for (it = m->llBandwidthGroups->begin();
+ it != m->llBandwidthGroups->end();
+ ++it)
+ (*it)->i_rollback();
+ }
+}
+
+void BandwidthControl::i_commit()
+{
+ bool commitBandwidthGroups = false;
+ BandwidthGroupList::const_iterator it;
+
+ if (m->llBandwidthGroups.isBackedUp())
+ {
+ m->llBandwidthGroups.commit();
+
+ if (m->pPeer)
+ {
+ AutoWriteLock peerlock(m->pPeer COMMA_LOCKVAL_SRC_POS);
+
+ /* Commit all changes to new groups (this will reshare data with
+ * peers for those who have peers) */
+ BandwidthGroupList *newList = new BandwidthGroupList();
+ for (it = m->llBandwidthGroups->begin();
+ it != m->llBandwidthGroups->end();
+ ++it)
+ {
+ (*it)->i_commit();
+
+ /* look if this group has a peer group */
+ ComObjPtr<BandwidthGroup> peer = (*it)->i_getPeer();
+ if (!peer)
+ {
+ /* no peer means the device is a newly created one;
+ * create a peer owning data this device share it with */
+ peer.createObject();
+ peer->init(m->pPeer, *it, true /* aReshare */);
+ }
+ else
+ {
+ /* remove peer from the old list */
+ m->pPeer->m->llBandwidthGroups->remove(peer);
+ }
+ /* and add it to the new list */
+ newList->push_back(peer);
+ }
+
+ /* uninit old peer's groups that are left */
+ for (it = m->pPeer->m->llBandwidthGroups->begin();
+ it != m->pPeer->m->llBandwidthGroups->end();
+ ++it)
+ (*it)->uninit();
+
+ /* attach new list of groups to our peer */
+ m->pPeer->m->llBandwidthGroups.attach(newList);
+ }
+ else
+ {
+ /* we have no peer (our parent is the newly created machine);
+ * just commit changes to devices */
+ commitBandwidthGroups = true;
+ }
+ }
+ else
+ {
+ /* the list of groups itself is not changed,
+ * just commit changes to groups themselves */
+ commitBandwidthGroups = true;
+ }
+
+ if (commitBandwidthGroups)
+ {
+ for (it = m->llBandwidthGroups->begin();
+ it != m->llBandwidthGroups->end();
+ ++it)
+ (*it)->i_commit();
+ }
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void BandwidthControl::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ // uninit all groups on the list (it's a standard std::list not an ObjectsList
+ // so we must uninit() manually)
+ BandwidthGroupList::iterator it;
+ for (it = m->llBandwidthGroups->begin();
+ it != m->llBandwidthGroups->end();
+ ++it)
+ (*it)->uninit();
+
+ m->llBandwidthGroups.free();
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pParent) = NULL;
+
+ delete m;
+ m = NULL;
+}
+
+/**
+ * Returns a bandwidth group object with the given name.
+ *
+ * @param aName bandwidth group name to find
+ * @param aBandwidthGroup where to return the found bandwidth group
+ * @param aSetError true to set extended error info on failure
+ */
+HRESULT BandwidthControl::i_getBandwidthGroupByName(const com::Utf8Str &aName,
+ ComObjPtr<BandwidthGroup> &aBandwidthGroup,
+ bool aSetError /* = false */)
+{
+ AssertReturn(!aName.isEmpty(), E_INVALIDARG);
+
+ for (BandwidthGroupList::const_iterator it = m->llBandwidthGroups->begin();
+ it != m->llBandwidthGroups->end();
+ ++it)
+ {
+ if ((*it)->i_getName() == aName)
+ {
+ aBandwidthGroup = (*it);
+ return S_OK;
+ }
+ }
+
+ if (aSetError)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Could not find a bandwidth group named '%s'"),
+ aName.c_str());
+ return VBOX_E_OBJECT_NOT_FOUND;
+}
+// To do
+HRESULT BandwidthControl::createBandwidthGroup(const com::Utf8Str &aName,
+ BandwidthGroupType_T aType,
+ LONG64 aMaxBytesPerSec)
+{
+ /*
+ * Validate input.
+ */
+ if (aMaxBytesPerSec < 0)
+ return setError(E_INVALIDARG, tr("Bandwidth group limit cannot be negative"));
+ switch (aType)
+ {
+ case BandwidthGroupType_Null: /*??*/
+ case BandwidthGroupType_Disk:
+ break;
+ case BandwidthGroupType_Network:
+ if (aName.length() > PDM_NET_SHAPER_MAX_NAME_LEN)
+ return setError(E_INVALIDARG, tr("Bandwidth name is too long: %zu, max %u"),
+ aName.length(), PDM_NET_SHAPER_MAX_NAME_LEN);
+ break;
+ default:
+ AssertFailedReturn(setError(E_INVALIDARG, tr("Invalid group type: %d"), aType));
+ }
+ if (aName.isEmpty())
+ return setError(E_INVALIDARG, tr("Bandwidth group name must not be empty")); /* ConsoleImpl2.cpp fails then */
+
+ /*
+ * The machine needs to be mutable:
+ */
+ AutoMutableOrSavedStateDependency adep(m->pParent);
+ HRESULT hrc = adep.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Check that the group doesn't already exist:
+ */
+ ComObjPtr<BandwidthGroup> group;
+ hrc = i_getBandwidthGroupByName(aName, group, false /* aSetError */);
+ if (FAILED(hrc))
+ {
+ /*
+ * There is an upper limit of the number of network groups imposed by PDM.
+ */
+ size_t cNetworkGroups = 0;
+ if (aType == BandwidthGroupType_Network)
+ for (BandwidthGroupList::const_iterator it = m->llBandwidthGroups->begin();
+ it != m->llBandwidthGroups->end();
+ ++it)
+ if ((*it)->i_getType() == BandwidthGroupType_Network)
+ cNetworkGroups++;
+ if (cNetworkGroups < PDM_NET_SHAPER_MAX_GROUPS)
+ {
+ /*
+ * Create the new group.
+ */
+ hrc = group.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = group->init(this, aName, aType, aMaxBytesPerSec);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Add it to the settings.
+ */
+ m->pParent->i_setModified(Machine::IsModified_BandwidthControl);
+ m->llBandwidthGroups.backup();
+ m->llBandwidthGroups->push_back(group);
+ hrc = S_OK;
+ }
+ }
+ }
+ else
+ hrc = setError(E_FAIL, tr("Too many network bandwidth groups (max %u)"), PDM_NET_SHAPER_MAX_GROUPS);
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_IN_USE, tr("Bandwidth group named '%s' already exists"), aName.c_str());
+ }
+ return hrc;
+}
+
+HRESULT BandwidthControl::deleteBandwidthGroup(const com::Utf8Str &aName)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<BandwidthGroup> group;
+ HRESULT rc = i_getBandwidthGroupByName(aName, group, true /* aSetError */);
+ if (FAILED(rc)) return rc;
+
+ if (group->i_getReferences() != 0)
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("The bandwidth group '%s' is still in use"), aName.c_str());
+
+ /* We can remove it now. */
+ m->pParent->i_setModified(Machine::IsModified_BandwidthControl);
+ m->llBandwidthGroups.backup();
+
+ group->i_unshare();
+
+ m->llBandwidthGroups->remove(group);
+
+ /* inform the direct session if any */
+ alock.release();
+ //onStorageControllerChange(); @todo
+
+ return S_OK;
+}
+
+HRESULT BandwidthControl::getNumGroups(ULONG *aNumGroups)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aNumGroups = (ULONG)m->llBandwidthGroups->size();
+
+ return S_OK;
+}
+
+HRESULT BandwidthControl::getBandwidthGroup(const com::Utf8Str &aName, ComPtr<IBandwidthGroup> &aBandwidthGroup)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<BandwidthGroup> group;
+ HRESULT rc = i_getBandwidthGroupByName(aName, group, true /* aSetError */);
+
+ if (SUCCEEDED(rc))
+ group.queryInterfaceTo(aBandwidthGroup.asOutParam());
+
+ return rc;
+}
+
+HRESULT BandwidthControl::getAllBandwidthGroups(std::vector<ComPtr<IBandwidthGroup> > &aBandwidthGroups)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aBandwidthGroups.resize(0);
+ BandwidthGroupList::const_iterator it;
+ for (it = m->llBandwidthGroups->begin();
+ it != m->llBandwidthGroups->end();
+ ++it)
+ aBandwidthGroups.push_back(*it);
+
+ return S_OK;
+}
+
+HRESULT BandwidthControl::i_loadSettings(const settings::IOSettings &data)
+{
+ HRESULT rc = S_OK;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+ settings::BandwidthGroupList::const_iterator it;
+ for (it = data.llBandwidthGroups.begin();
+ it != data.llBandwidthGroups.end();
+ ++it)
+ {
+ const settings::BandwidthGroup &gr = *it;
+ rc = createBandwidthGroup(gr.strName, gr.enmType, (LONG64)gr.cMaxBytesPerSec);
+ if (FAILED(rc)) break;
+ }
+
+ return rc;
+}
+
+HRESULT BandwidthControl::i_saveSettings(settings::IOSettings &data)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ data.llBandwidthGroups.clear();
+ BandwidthGroupList::const_iterator it;
+ for (it = m->llBandwidthGroups->begin();
+ it != m->llBandwidthGroups->end();
+ ++it)
+ {
+ AutoWriteLock groupLock(*it COMMA_LOCKVAL_SRC_POS);
+ settings::BandwidthGroup group;
+
+ group.strName = (*it)->i_getName();
+ group.enmType = (*it)->i_getType();
+ group.cMaxBytesPerSec = (uint64_t)(*it)->i_getMaxBytesPerSec();
+
+ data.llBandwidthGroups.push_back(group);
+ }
+
+ return S_OK;
+}
+
+Machine * BandwidthControl::i_getMachine() const
+{
+ return m->pParent;
+}
+
diff --git a/src/VBox/Main/src-server/BandwidthGroupImpl.cpp b/src/VBox/Main/src-server/BandwidthGroupImpl.cpp
new file mode 100644
index 00000000..07d7d858
--- /dev/null
+++ b/src/VBox/Main/src-server/BandwidthGroupImpl.cpp
@@ -0,0 +1,355 @@
+/* $Id: BandwidthGroupImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_BANDWIDTHGROUP
+#include "BandwidthGroupImpl.h"
+#include "MachineImpl.h"
+#include "Global.h"
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+#include <iprt/cpp/utils.h>
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+//
+DEFINE_EMPTY_CTOR_DTOR(BandwidthGroup)
+
+HRESULT BandwidthGroup::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void BandwidthGroup::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the bandwidth group object.
+ *
+ * @returns COM result indicator.
+ * @param aParent Pointer to our parent object.
+ * @param aName Name of the bandwidth group.
+ * @param aType Type of the bandwidth group (net, disk).
+ * @param aMaxBytesPerSec Maximum bandwidth for the bandwidth group.
+ */
+HRESULT BandwidthGroup::init(BandwidthControl *aParent,
+ const Utf8Str &aName,
+ BandwidthGroupType_T aType,
+ LONG64 aMaxBytesPerSec)
+{
+ LogFlowThisFunc(("aParent=%p aName=\"%s\"\n",
+ aParent, aName.c_str()));
+
+ ComAssertRet(aParent && !aName.isEmpty(), E_INVALIDARG);
+ if ( (aType <= BandwidthGroupType_Null)
+ || (aType > BandwidthGroupType_Network))
+ return setError(E_INVALIDARG,
+ tr("Invalid bandwidth group type"));
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+
+ /* m->pPeer is left null */
+
+ m->bd.allocate();
+
+ m->bd->mData.strName = aName;
+ m->bd->mData.enmType = aType;
+ m->bd->cReferences = 0;
+ m->bd->mData.cMaxBytesPerSec = (uint64_t)aMaxBytesPerSec;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the object given another object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @param aParent Pointer to our parent object.
+ * @param aThat
+ * @param aReshare
+ * When false, the original object will remain a data owner.
+ * Otherwise, data ownership will be transferred from the original
+ * object to this one.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for writing if @a aReshare is @c true, or for
+ * reading if @a aReshare is false.
+ */
+HRESULT BandwidthGroup::init(BandwidthControl *aParent,
+ BandwidthGroup *aThat,
+ bool aReshare /* = false */)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p, aReshare=%RTbool\n",
+ aParent, aThat, aReshare));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+
+ /* sanity */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ if (aReshare)
+ {
+ AutoWriteLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+
+ unconst(aThat->m->pPeer) = this;
+ m->bd.attach(aThat->m->bd);
+ }
+ else
+ {
+ unconst(m->pPeer) = aThat;
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.share(aThat->m->bd);
+ }
+
+ /* Confirm successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the bandwidth group object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ */
+HRESULT BandwidthGroup::initCopy(BandwidthControl *aParent, BandwidthGroup *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+ /* m->pPeer is left null */
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.attachCopy(aThat->m->bd);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void BandwidthGroup::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m->bd.free();
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pParent) = NULL;
+
+ delete m;
+ m = NULL;
+}
+
+HRESULT BandwidthGroup::getName(com::Utf8Str &aName)
+{
+ /* mName is constant during life time, no need to lock */
+ aName = m->bd.data()->mData.strName;
+
+ return S_OK;
+}
+
+HRESULT BandwidthGroup::getType(BandwidthGroupType_T *aType)
+{
+ /* type is constant during life time, no need to lock */
+ *aType = m->bd->mData.enmType;
+
+ return S_OK;
+}
+
+HRESULT BandwidthGroup::getReference(ULONG *aReferences)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aReferences = m->bd->cReferences;
+
+ return S_OK;
+}
+
+HRESULT BandwidthGroup::getMaxBytesPerSec(LONG64 *aMaxBytesPerSec)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aMaxBytesPerSec = (LONG64)m->bd->mData.cMaxBytesPerSec;
+
+ return S_OK;
+}
+
+HRESULT BandwidthGroup::setMaxBytesPerSec(LONG64 aMaxBytesPerSec)
+{
+ if (aMaxBytesPerSec < 0)
+ return setError(E_INVALIDARG,
+ tr("Bandwidth group limit cannot be negative"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->mData.cMaxBytesPerSec = (uint64_t)aMaxBytesPerSec;
+
+ /* inform direct session if any. */
+ ComObjPtr<Machine> pMachine = m->pParent->i_getMachine();
+ alock.release();
+ pMachine->i_onBandwidthGroupChange(this);
+
+ return S_OK;
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/** @note Locks objects for writing! */
+void BandwidthGroup::i_rollback()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.rollback();
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object (also
+ * for writing) if there is one.
+ */
+void BandwidthGroup::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (m->pPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ {
+ m->bd.commit();
+ if (m->pPeer)
+ {
+ // attach new data to the peer and reshare it
+ m->pPeer->m->bd.attach(m->bd);
+ }
+ }
+}
+
+
+/**
+ * Cancels sharing (if any) by making an independent copy of data.
+ * This operation also resets this object's peer to NULL.
+ *
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ */
+void BandwidthGroup::i_unshare()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* peer is not modified, lock it for reading (m->pPeer is "master" so locked
+ * first) */
+ AutoReadLock rl(m->pPeer COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isShared())
+ {
+ if (!m->bd.isBackedUp())
+ m->bd.backup();
+
+ m->bd.commit();
+ }
+
+ unconst(m->pPeer) = NULL;
+}
+
+void BandwidthGroup::i_reference()
+{
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+ m->bd.backup();
+ m->bd->cReferences++;
+}
+
+void BandwidthGroup::i_release()
+{
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+ m->bd.backup();
+ m->bd->cReferences--;
+}
+
diff --git a/src/VBox/Main/src-server/CPUProfileImpl.cpp b/src/VBox/Main/src-server/CPUProfileImpl.cpp
new file mode 100644
index 00000000..e74f730e
--- /dev/null
+++ b/src/VBox/Main/src-server/CPUProfileImpl.cpp
@@ -0,0 +1,146 @@
+/* $Id: CPUProfileImpl.cpp $ */
+/** @file
+ * VirtualBox Main - interface for CPU profiles, VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2020-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "CPUProfileImpl.h"
+
+#include <VBox/vmm/cpum.h>
+#include <iprt/x86.h>
+#include "AutoCaller.h"
+
+
+DEFINE_EMPTY_CTOR_DTOR(CPUProfile)
+
+/**
+ * Called by ComObjPtr::createObject when creating the object.
+ *
+ * Just initialize the basic object state, do the rest in initFromDbEntry().
+ *
+ * @returns S_OK.
+ */
+HRESULT CPUProfile::FinalConstruct()
+{
+ m_enmArchitecture = CPUArchitecture_Any;
+ return BaseFinalConstruct();
+}
+
+/**
+ * Initializes the CPU profile from the given CPUM CPU database entry.
+ *
+ * @returns COM status code.
+ * @param a_pDbEntry The CPU database entry.
+ */
+HRESULT CPUProfile::initFromDbEntry(PCCPUMDBENTRY a_pDbEntry) RT_NOEXCEPT
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /*
+ * Initialize our private data.
+ */
+
+ /* Determin x86 or AMD64 by scanning the CPUID leaves for the long mode feature bit: */
+ m_enmArchitecture = CPUArchitecture_x86;
+ uint32_t iLeaf = a_pDbEntry->cCpuIdLeaves;
+ while (iLeaf-- > 0)
+ if (a_pDbEntry->paCpuIdLeaves[iLeaf].uLeaf <= UINT32_C(0x80000001))
+ {
+ if ( a_pDbEntry->paCpuIdLeaves[iLeaf].uLeaf == UINT32_C(0x80000001)
+ && (a_pDbEntry->paCpuIdLeaves[iLeaf].uEdx & X86_CPUID_EXT_FEATURE_EDX_LONG_MODE))
+ m_enmArchitecture = CPUArchitecture_AMD64;
+ break;
+ }
+
+ HRESULT hrc = m_strName.assignEx(a_pDbEntry->pszName);
+ if (SUCCEEDED(hrc))
+ hrc = m_strFullName.assignEx(a_pDbEntry->pszFullName);
+
+ /*
+ * Update the object state.
+ */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed(hrc);
+ return hrc;
+}
+
+/**
+ * COM cruft.
+ */
+void CPUProfile::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+/**
+ * Do the actual cleanup.
+ */
+void CPUProfile::uninit()
+{
+ AutoUninitSpan autoUninitSpan(this);
+}
+
+/**
+ * Used by SystemProperties::getCPUProfiles to do matching.
+ */
+bool CPUProfile::i_match(CPUArchitecture_T a_enmArchitecture, CPUArchitecture_T a_enmSecondaryArch,
+ const com::Utf8Str &a_strNamePattern) const RT_NOEXCEPT
+{
+ if ( m_enmArchitecture == a_enmArchitecture
+ || m_enmArchitecture == a_enmSecondaryArch)
+ {
+ if (a_strNamePattern.isEmpty())
+ return true;
+ return RTStrSimplePatternMatch(a_strNamePattern.c_str(), m_strName.c_str());
+ }
+ return false;
+}
+
+
+HRESULT CPUProfile::getName(com::Utf8Str &aName)
+{
+ return aName.assignEx(m_strName);
+}
+
+HRESULT CPUProfile::getFullName(com::Utf8Str &aFullName)
+{
+ return aFullName.assignEx(m_strFullName);
+}
+
+HRESULT CPUProfile::getArchitecture(CPUArchitecture_T *aArchitecture)
+{
+ *aArchitecture = m_enmArchitecture;
+ return S_OK;
+}
+
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/CertificateImpl.cpp b/src/VBox/Main/src-server/CertificateImpl.cpp
new file mode 100644
index 00000000..e346a337
--- /dev/null
+++ b/src/VBox/Main/src-server/CertificateImpl.cpp
@@ -0,0 +1,590 @@
+/* $Id: CertificateImpl.cpp $ */
+/** @file
+ * ICertificate COM class implementations.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_CERTIFICATE
+#include <iprt/err.h>
+#include <iprt/path.h>
+#include <iprt/cpp/utils.h>
+#include <VBox/com/array.h>
+#include <iprt/crypto/x509.h>
+
+#include "ProgressImpl.h"
+#include "CertificateImpl.h"
+#include "AutoCaller.h"
+#include "Global.h"
+#include "LoggingNew.h"
+
+using namespace std;
+
+
+/**
+ * Private instance data for the #Certificate class.
+ * @see Certificate::m
+ */
+struct Certificate::Data
+{
+ Data()
+ : fTrusted(false)
+ , fExpired(false)
+ , fValidX509(false)
+ {
+ RT_ZERO(X509);
+ }
+
+ ~Data()
+ {
+ if (fValidX509)
+ {
+ RTCrX509Certificate_Delete(&X509);
+ RT_ZERO(X509);
+ fValidX509 = false;
+ }
+ }
+
+ /** Whether the certificate is trusted. */
+ bool fTrusted;
+ /** Whether the certificate is trusted. */
+ bool fExpired;
+ /** Valid data in mX509. */
+ bool fValidX509;
+ /** Clone of the X.509 certificate. */
+ RTCRX509CERTIFICATE X509;
+
+private:
+ Data(const Certificate::Data &rTodo) { AssertFailed(); NOREF(rTodo); }
+ Data &operator=(const Certificate::Data &rTodo) { AssertFailed(); NOREF(rTodo); return *this; }
+};
+
+
+///////////////////////////////////////////////////////////////////////////////////
+//
+// Certificate constructor / destructor
+//
+// ////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(Certificate)
+
+HRESULT Certificate::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void Certificate::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+/**
+ * Initializes a certificate instance.
+ *
+ * @returns COM status code.
+ * @param a_pCert The certificate.
+ * @param a_fTrusted Whether the caller trusts the certificate or not.
+ * @param a_fExpired Whether the caller consideres the certificate to be
+ * expired.
+ */
+HRESULT Certificate::initCertificate(PCRTCRX509CERTIFICATE a_pCert, bool a_fTrusted, bool a_fExpired)
+{
+ HRESULT rc = S_OK;
+ LogFlowThisFuncEnter();
+
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ int vrc = RTCrX509Certificate_Clone(&m->X509, a_pCert, &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(vrc))
+ {
+ m->fValidX509 = true;
+ m->fTrusted = a_fTrusted;
+ m->fExpired = a_fExpired;
+ autoInitSpan.setSucceeded();
+ }
+ else
+ rc = Global::vboxStatusCodeToCOM(vrc);
+
+ LogFlowThisFunc(("returns rc=%Rhrc\n", rc));
+ return rc;
+}
+
+void Certificate::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ delete m;
+ m = NULL;
+}
+
+
+/** @name Wrapped ICertificate properties
+ * @{
+ */
+
+HRESULT Certificate::getVersionNumber(CertificateVersion_T *aVersionNumber)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->fValidX509);
+ switch (m->X509.TbsCertificate.T0.Version.uValue.u)
+ {
+ case RTCRX509TBSCERTIFICATE_V1: *aVersionNumber = CertificateVersion_V1; break;
+ case RTCRX509TBSCERTIFICATE_V2: *aVersionNumber = CertificateVersion_V2; break;
+ case RTCRX509TBSCERTIFICATE_V3: *aVersionNumber = CertificateVersion_V3; break;
+ default: AssertFailed(); *aVersionNumber = CertificateVersion_Unknown; break;
+ }
+ return S_OK;
+}
+
+HRESULT Certificate::getSerialNumber(com::Utf8Str &aSerialNumber)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->fValidX509);
+
+ char szTmp[_2K];
+ int vrc = RTAsn1Integer_ToString(&m->X509.TbsCertificate.SerialNumber, szTmp, sizeof(szTmp), 0, NULL);
+ if (RT_SUCCESS(vrc))
+ aSerialNumber = szTmp;
+ else
+ return Global::vboxStatusCodeToCOM(vrc);
+
+ return S_OK;
+}
+
+HRESULT Certificate::getSignatureAlgorithmOID(com::Utf8Str &aSignatureAlgorithmOID)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->fValidX509);
+ aSignatureAlgorithmOID = m->X509.TbsCertificate.Signature.Algorithm.szObjId;
+
+ return S_OK;
+}
+
+HRESULT Certificate::getSignatureAlgorithmName(com::Utf8Str &aSignatureAlgorithmName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->fValidX509);
+ return i_getAlgorithmName(&m->X509.TbsCertificate.Signature, aSignatureAlgorithmName);
+}
+
+HRESULT Certificate::getIssuerName(std::vector<com::Utf8Str> &aIssuerName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->fValidX509);
+ return i_getX509Name(&m->X509.TbsCertificate.Issuer, aIssuerName);
+}
+
+HRESULT Certificate::getSubjectName(std::vector<com::Utf8Str> &aSubjectName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->fValidX509);
+ return i_getX509Name(&m->X509.TbsCertificate.Subject, aSubjectName);
+}
+
+HRESULT Certificate::getFriendlyName(com::Utf8Str &aFriendlyName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->fValidX509);
+
+ PCRTCRX509NAME pName = &m->X509.TbsCertificate.Subject;
+
+ /*
+ * Enumerate the subject name and pick interesting attributes we can use to
+ * form a name more friendly than the RTCrX509Name_FormatAsString output.
+ */
+ const char *pszOrg = NULL;
+ const char *pszOrgUnit = NULL;
+ const char *pszGivenName = NULL;
+ const char *pszSurname = NULL;
+ const char *pszEmail = NULL;
+ for (uint32_t i = 0; i < pName->cItems; i++)
+ {
+ PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = pName->papItems[i];
+ for (uint32_t j = 0; j < pRdn->cItems; j++)
+ {
+ PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = pRdn->papItems[j];
+ AssertContinue(pComponent->Value.enmType == RTASN1TYPE_STRING);
+
+ /* Select interesting components based on the short RDN prefix
+ string (easier to read and write than OIDs, for now). */
+ const char *pszPrefix = RTCrX509Name_GetShortRdn(&pComponent->Type);
+ if (pszPrefix)
+ {
+ const char *pszUtf8;
+ int vrc = RTAsn1String_QueryUtf8(&pComponent->Value.u.String, &pszUtf8, NULL);
+ if (RT_SUCCESS(vrc) && *pszUtf8)
+ {
+ if (!strcmp(pszPrefix, "Email"))
+ pszEmail = pszUtf8;
+ else if (!strcmp(pszPrefix, "O"))
+ pszOrg = pszUtf8;
+ else if (!strcmp(pszPrefix, "OU"))
+ pszOrgUnit = pszUtf8;
+ else if (!strcmp(pszPrefix, "S"))
+ pszSurname = pszUtf8;
+ else if (!strcmp(pszPrefix, "G"))
+ pszGivenName = pszUtf8;
+ }
+ }
+ }
+ }
+
+ if (pszGivenName && pszSurname)
+ {
+ if (pszEmail)
+ aFriendlyName = Utf8StrFmt("%s, %s <%s>", pszSurname, pszGivenName, pszEmail);
+ else if (pszOrg)
+ aFriendlyName = Utf8StrFmt("%s, %s (%s)", pszSurname, pszGivenName, pszOrg);
+ else if (pszOrgUnit)
+ aFriendlyName = Utf8StrFmt("%s, %s (%s)", pszSurname, pszGivenName, pszOrgUnit);
+ else
+ aFriendlyName = Utf8StrFmt("%s, %s", pszSurname, pszGivenName);
+ }
+ else if (pszOrg && pszOrgUnit)
+ aFriendlyName = Utf8StrFmt("%s, %s", pszOrg, pszOrgUnit);
+ else if (pszOrg)
+ aFriendlyName = Utf8StrFmt("%s", pszOrg);
+ else if (pszOrgUnit)
+ aFriendlyName = Utf8StrFmt("%s", pszOrgUnit);
+ else
+ {
+ /*
+ * Fall back on unfriendly but accurate.
+ */
+ char szTmp[_8K];
+ RT_ZERO(szTmp);
+ RTCrX509Name_FormatAsString(pName, szTmp, sizeof(szTmp) - 1, NULL);
+ aFriendlyName = szTmp;
+ }
+
+ return S_OK;
+}
+
+HRESULT Certificate::getValidityPeriodNotBefore(com::Utf8Str &aValidityPeriodNotBefore)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->fValidX509);
+ return i_getTime(&m->X509.TbsCertificate.Validity.NotBefore, aValidityPeriodNotBefore);
+}
+
+HRESULT Certificate::getValidityPeriodNotAfter(com::Utf8Str &aValidityPeriodNotAfter)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->fValidX509);
+ return i_getTime(&m->X509.TbsCertificate.Validity.NotAfter, aValidityPeriodNotAfter);
+}
+
+HRESULT Certificate::getPublicKeyAlgorithmOID(com::Utf8Str &aPublicKeyAlgorithmOID)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->fValidX509);
+ aPublicKeyAlgorithmOID = m->X509.TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm.szObjId;
+ return S_OK;
+}
+
+HRESULT Certificate::getPublicKeyAlgorithm(com::Utf8Str &aPublicKeyAlgorithm)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->fValidX509);
+ return i_getAlgorithmName(&m->X509.TbsCertificate.SubjectPublicKeyInfo.Algorithm, aPublicKeyAlgorithm);
+}
+
+HRESULT Certificate::getSubjectPublicKey(std::vector<BYTE> &aSubjectPublicKey)
+{
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Getting encoded ASN.1 bytes may make changes to X509. */
+ return i_getEncodedBytes(&m->X509.TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey.Asn1Core, aSubjectPublicKey);
+}
+
+HRESULT Certificate::getIssuerUniqueIdentifier(com::Utf8Str &aIssuerUniqueIdentifier)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return i_getUniqueIdentifier(&m->X509.TbsCertificate.T1.IssuerUniqueId, aIssuerUniqueIdentifier);
+}
+
+HRESULT Certificate::getSubjectUniqueIdentifier(com::Utf8Str &aSubjectUniqueIdentifier)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return i_getUniqueIdentifier(&m->X509.TbsCertificate.T2.SubjectUniqueId, aSubjectUniqueIdentifier);
+}
+
+HRESULT Certificate::getCertificateAuthority(BOOL *aCertificateAuthority)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCertificateAuthority = m->X509.TbsCertificate.T3.pBasicConstraints
+ && m->X509.TbsCertificate.T3.pBasicConstraints->CA.fValue;
+
+ return S_OK;
+}
+
+HRESULT Certificate::getKeyUsage(ULONG *aKeyUsage)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aKeyUsage = m->X509.TbsCertificate.T3.fKeyUsage;
+ return S_OK;
+}
+
+HRESULT Certificate::getExtendedKeyUsage(std::vector<com::Utf8Str> &aExtendedKeyUsage)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ NOREF(aExtendedKeyUsage);
+ return E_NOTIMPL;
+}
+
+HRESULT Certificate::getRawCertData(std::vector<BYTE> &aRawCertData)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Getting encoded ASN.1 bytes may make changes to X509. */
+ return i_getEncodedBytes(&m->X509.SeqCore.Asn1Core, aRawCertData);
+}
+
+HRESULT Certificate::getSelfSigned(BOOL *aSelfSigned)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->fValidX509);
+ *aSelfSigned = RTCrX509Certificate_IsSelfSigned(&m->X509);
+
+ return S_OK;
+}
+
+HRESULT Certificate::getTrusted(BOOL *aTrusted)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->fValidX509);
+ *aTrusted = m->fTrusted;
+
+ return S_OK;
+}
+
+HRESULT Certificate::getExpired(BOOL *aExpired)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Assert(m->fValidX509);
+ *aExpired = m->fExpired;
+ return S_OK;
+}
+
+/** @} */
+
+/** @name Wrapped ICertificate methods
+ * @{
+ */
+
+HRESULT Certificate::isCurrentlyExpired(BOOL *aResult)
+{
+ AssertReturnStmt(m->fValidX509, *aResult = TRUE, E_UNEXPECTED);
+ RTTIMESPEC Now;
+ *aResult = RTCrX509Validity_IsValidAtTimeSpec(&m->X509.TbsCertificate.Validity, RTTimeNow(&Now)) ? FALSE : TRUE;
+ return S_OK;
+}
+
+HRESULT Certificate::queryInfo(LONG aWhat, com::Utf8Str &aResult)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ /* Insurance. */
+ NOREF(aResult);
+ return setError(E_FAIL, tr("Unknown item %u"), aWhat);
+}
+
+/** @} */
+
+
+/** @name Methods extracting COM data from the certificate object
+ * @{
+ */
+
+/**
+ * Translates an algorithm OID into a human readable string, if possible.
+ *
+ * @returns S_OK.
+ * @param a_pAlgId The algorithm.
+ * @param a_rReturn The return string value.
+ * @throws std::bad_alloc
+ */
+HRESULT Certificate::i_getAlgorithmName(PCRTCRX509ALGORITHMIDENTIFIER a_pAlgId, com::Utf8Str &a_rReturn)
+{
+ const char *pszOid = a_pAlgId->Algorithm.szObjId;
+ const char *pszName;
+ if (!pszOid) pszName = "";
+ else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_RSA)) pszName = "rsaEncryption";
+ else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD2_WITH_RSA)) pszName = "md2WithRSAEncryption";
+ else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD4_WITH_RSA)) pszName = "md4WithRSAEncryption";
+ else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD5_WITH_RSA)) pszName = "md5WithRSAEncryption";
+ else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA1_WITH_RSA)) pszName = "sha1WithRSAEncryption";
+ else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA224_WITH_RSA)) pszName = "sha224WithRSAEncryption";
+ else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA256_WITH_RSA)) pszName = "sha256WithRSAEncryption";
+ else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA384_WITH_RSA)) pszName = "sha384WithRSAEncryption";
+ else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512_WITH_RSA)) pszName = "sha512WithRSAEncryption";
+ else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224_WITH_RSA)) pszName = "sha512-224WithRSAEncryption";
+ else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256_WITH_RSA)) pszName = "sha512-256WithRSAEncryption";
+ else
+ pszName = pszOid;
+ a_rReturn = pszName;
+ return S_OK;
+}
+
+/**
+ * Formats a X.509 name into a string array.
+ *
+ * The name is prefix with a short hand of the relative distinguished name
+ * type followed by an equal sign.
+ *
+ * @returns S_OK.
+ * @param a_pName The X.509 name.
+ * @param a_rReturn The return string array.
+ * @throws std::bad_alloc
+ */
+HRESULT Certificate::i_getX509Name(PCRTCRX509NAME a_pName, std::vector<com::Utf8Str> &a_rReturn)
+{
+ if (RTCrX509Name_IsPresent(a_pName))
+ {
+ for (uint32_t i = 0; i < a_pName->cItems; i++)
+ {
+ PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = a_pName->papItems[i];
+ for (uint32_t j = 0; j < pRdn->cItems; j++)
+ {
+ PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = pRdn->papItems[j];
+
+ AssertReturn(pComponent->Value.enmType == RTASN1TYPE_STRING,
+ setErrorVrc(VERR_CR_X509_NAME_NOT_STRING, "VERR_CR_X509_NAME_NOT_STRING"));
+
+ /* Get the prefix for this name component. */
+ const char *pszPrefix = RTCrX509Name_GetShortRdn(&pComponent->Type);
+ AssertStmt(pszPrefix, pszPrefix = pComponent->Type.szObjId);
+
+ /* Get the string. */
+ const char *pszUtf8;
+ int vrc = RTAsn1String_QueryUtf8(&pComponent->Value.u.String, &pszUtf8, NULL /*pcch*/);
+ AssertRCReturn(vrc, setErrorVrc(vrc, "RTAsn1String_QueryUtf8(%u/%u,,) -> %Rrc", i, j, vrc));
+
+ a_rReturn.push_back(Utf8StrFmt("%s=%s", pszPrefix, pszUtf8));
+ }
+ }
+ }
+ return S_OK;
+}
+
+/**
+ * Translates an ASN.1 timestamp into an ISO timestamp string.
+ *
+ * @returns S_OK.
+ * @param a_pTime The timestamp
+ * @param a_rReturn The return string value.
+ * @throws std::bad_alloc
+ */
+HRESULT Certificate::i_getTime(PCRTASN1TIME a_pTime, com::Utf8Str &a_rReturn)
+{
+ char szTmp[128];
+ if (RTTimeToString(&a_pTime->Time, szTmp, sizeof(szTmp)))
+ {
+ a_rReturn = szTmp;
+ return S_OK;
+ }
+ AssertFailed();
+ return E_FAIL;
+}
+
+/**
+ * Translates a X.509 unique identifier to a string.
+ *
+ * @returns S_OK.
+ * @param a_pUniqueId The unique identifier.
+ * @param a_rReturn The return string value.
+ * @throws std::bad_alloc
+ */
+HRESULT Certificate::i_getUniqueIdentifier(PCRTCRX509UNIQUEIDENTIFIER a_pUniqueId, com::Utf8Str &a_rReturn)
+{
+ /* The a_pUniqueId may not be present! */
+ if (RTCrX509UniqueIdentifier_IsPresent(a_pUniqueId))
+ {
+ void const *pvData = RTASN1BITSTRING_GET_BIT0_PTR(a_pUniqueId);
+ size_t const cbData = RTASN1BITSTRING_GET_BYTE_SIZE(a_pUniqueId);
+ size_t const cbFormatted = cbData * 3 - 1 + 1;
+ a_rReturn.reserve(cbFormatted); /* throws */
+ int vrc = RTStrPrintHexBytes(a_rReturn.mutableRaw(), cbFormatted, pvData, cbData, RTSTRPRINTHEXBYTES_F_SEP_COLON);
+ a_rReturn.jolt();
+ AssertRCReturn(vrc, Global::vboxStatusCodeToCOM(vrc));
+ }
+ else
+ Assert(a_rReturn.isEmpty());
+ return S_OK;
+}
+
+/**
+ * Translates any ASN.1 object into a (DER encoded) byte array.
+ *
+ * @returns S_OK.
+ * @param a_pAsn1Obj The ASN.1 object to get the DER encoded bytes for.
+ * @param a_rReturn The return byte vector.
+ * @throws std::bad_alloc
+ */
+HRESULT Certificate::i_getEncodedBytes(PRTASN1CORE a_pAsn1Obj, std::vector<BYTE> &a_rReturn)
+{
+ HRESULT hrc = S_OK;
+ Assert(a_rReturn.size() == 0);
+ if (RTAsn1Core_IsPresent(a_pAsn1Obj))
+ {
+ uint32_t cbEncoded;
+ int vrc = RTAsn1EncodePrepare(a_pAsn1Obj, 0, &cbEncoded, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ a_rReturn.resize(cbEncoded);
+ Assert(a_rReturn.size() == cbEncoded);
+ if (cbEncoded)
+ {
+ vrc = RTAsn1EncodeToBuffer(a_pAsn1Obj, 0, &a_rReturn.front(), a_rReturn.size(), NULL);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorVrc(vrc, tr("RTAsn1EncodeToBuffer failed with %Rrc"), vrc);
+ }
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("RTAsn1EncodePrepare failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+/** @} */
+
diff --git a/src/VBox/Main/src-server/ClientToken.cpp b/src/VBox/Main/src-server/ClientToken.cpp
new file mode 100644
index 00000000..e900d462
--- /dev/null
+++ b/src/VBox/Main/src-server/ClientToken.cpp
@@ -0,0 +1,350 @@
+/* $Id: ClientToken.cpp $ */
+/** @file
+ *
+ * VirtualBox API client session crash token handling
+ */
+
+/*
+ * Copyright (C) 2004-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <VBox/log.h>
+#include <iprt/semaphore.h>
+#include <iprt/process.h>
+
+#ifdef VBOX_WITH_SYS_V_IPC_SESSION_WATCHER
+# include <errno.h>
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <sys/ipc.h>
+# include <sys/sem.h>
+#endif
+
+#include <VBox/com/defs.h>
+
+#include <vector>
+
+#include "VirtualBoxBase.h"
+#include "AutoCaller.h"
+#include "ClientToken.h"
+#include "MachineImpl.h"
+
+#ifdef RT_OS_WINDOWS
+# include <sddl.h>
+#endif
+
+Machine::ClientToken::ClientToken()
+{
+ AssertReleaseFailed();
+}
+
+Machine::ClientToken::~ClientToken()
+{
+#if defined(RT_OS_WINDOWS)
+ if (mClientToken)
+ {
+ LogFlowFunc(("Closing mClientToken=%p\n", mClientToken));
+ ::CloseHandle(mClientToken);
+ }
+#elif defined(RT_OS_OS2)
+ if (mClientToken != NULLHANDLE)
+ ::DosCloseMutexSem(mClientToken);
+#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
+ if (mClientToken >= 0)
+ ::semctl(mClientToken, 0, IPC_RMID);
+# ifdef VBOX_WITH_NEW_SYS_V_KEYGEN
+ mClientTokenId = "0";
+# endif /* VBOX_WITH_NEW_SYS_V_KEYGEN */
+#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+ /* release the token, uses reference counting */
+ if (mClientToken)
+ {
+ if (!mClientTokenPassed)
+ mClientToken->Release();
+ mClientToken = NULL;
+ }
+#else
+# error "Port me!"
+#endif
+ mClientToken = CTTOKENARG;
+}
+
+Machine::ClientToken::ClientToken(const ComObjPtr<Machine> &pMachine,
+ SessionMachine *pSessionMachine) :
+ mMachine(pMachine)
+{
+#if defined(RT_OS_WINDOWS)
+ NOREF(pSessionMachine);
+
+ /* Get user's SID to use it as part of the mutex name to distinguish shared machine instances
+ * between users
+ */
+ Utf8Str strUserSid;
+ HANDLE hProcessToken = INVALID_HANDLE_VALUE;
+ if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &hProcessToken))
+ {
+ DWORD dwSize = 0;
+ BOOL fRc = ::GetTokenInformation(hProcessToken, TokenUser, NULL, 0, &dwSize);
+ DWORD dwErr = ::GetLastError();
+ if (!fRc && dwErr == ERROR_INSUFFICIENT_BUFFER && dwSize > 0)
+ {
+ PTOKEN_USER pTokenUser = (PTOKEN_USER)RTMemTmpAllocZ(dwSize);
+ if (pTokenUser)
+ {
+ if (::GetTokenInformation(hProcessToken, TokenUser, pTokenUser, dwSize, &dwSize))
+ {
+ PRTUTF16 wstrSid = NULL;
+ if (::ConvertSidToStringSid(pTokenUser->User.Sid, &wstrSid))
+ {
+ strUserSid = wstrSid;
+ ::LocalFree(wstrSid);
+ }
+ else
+ AssertMsgFailed(("Cannot convert SID to string, err=%u", ::GetLastError()));
+ }
+ else
+ AssertMsgFailed(("Cannot get thread access token information, err=%u", ::GetLastError()));
+ RTMemFree(pTokenUser);
+ }
+ else
+ AssertMsgFailed(("No memory"));
+ }
+ else
+ AssertMsgFailed(("Cannot get thread access token information, err=%u", ::GetLastError()));
+ CloseHandle(hProcessToken);
+ }
+ else
+ AssertMsgFailed(("Cannot get thread access token, err=%u", ::GetLastError()));
+
+ BstrFmt tokenId("Global\\VBoxSession-%s-VM-%RTuuid", strUserSid.c_str(), pMachine->mData->mUuid.raw());
+
+ /* create security descriptor to allow SYNCHRONIZE access from any windows sessions and users.
+ * otherwise VM can't open the mutex if VBoxSVC and VM are in different session (e.g. some VM
+ * started by autostart service)
+ *
+ * SDDL string contains following ACEs:
+ * CreateOwner : MUTEX_ALL_ACCESS
+ * System : MUTEX_ALL_ACCESS
+ * BuiltInAdministrators : MUTEX_ALL_ACCESS
+ * Everyone : SYNCHRONIZE|MUTEX_MODIFY_STATE
+ */
+
+ //static const RTUTF16 s_wszSecDesc[] = L"D:(A;;0x1F0001;;;CO)(A;;0x1F0001;;;SY)(A;;0x1F0001;;;BA)(A;;0x100001;;;WD)";
+ com::BstrFmt bstrSecDesc("D:(A;;0x1F0001;;;CO)"
+ "(A;;0x1F0001;;;SY)"
+ "(A;;0x1F0001;;;BA)"
+ "(A;;0x1F0001;;;BA)"
+ "(A;;0x1F0001;;;%s)"
+ , strUserSid.c_str());
+ PSECURITY_DESCRIPTOR pSecDesc = NULL;
+ //AssertMsgStmt(::ConvertStringSecurityDescriptorToSecurityDescriptor(s_wszSecDesc, SDDL_REVISION_1, &pSecDesc, NULL),
+ AssertMsgStmt(::ConvertStringSecurityDescriptorToSecurityDescriptor(bstrSecDesc.raw(), SDDL_REVISION_1, &pSecDesc, NULL),
+ ("Cannot create security descriptor for token '%ls', err=%u", tokenId.raw(), GetLastError()),
+ pSecDesc = NULL);
+
+ SECURITY_ATTRIBUTES SecAttr;
+ SecAttr.lpSecurityDescriptor = pSecDesc;
+ SecAttr.nLength = sizeof(SecAttr);
+ SecAttr.bInheritHandle = FALSE;
+ mClientToken = ::CreateMutex(&SecAttr, FALSE, tokenId.raw());
+ mClientTokenId = tokenId;
+ AssertMsg(mClientToken, ("Cannot create token '%s', err=%d", mClientTokenId.c_str(), ::GetLastError()));
+
+ if (pSecDesc)
+ ::LocalFree(pSecDesc);
+
+#elif defined(RT_OS_OS2)
+ NOREF(pSessionMachine);
+ Utf8Str ipcSem = Utf8StrFmt("\\SEM32\\VBOX\\VM\\{%RTuuid}",
+ pMachine->mData->mUuid.raw());
+ mClientTokenId = ipcSem;
+ APIRET arc = ::DosCreateMutexSem((PSZ)ipcSem.c_str(), &mClientToken, 0, FALSE);
+ AssertMsg(arc == NO_ERROR,
+ ("Cannot create token '%s', arc=%ld",
+ ipcSem.c_str(), arc));
+#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
+ NOREF(pSessionMachine);
+# ifdef VBOX_WITH_NEW_SYS_V_KEYGEN
+# if defined(RT_OS_FREEBSD) && (HC_ARCH_BITS == 64)
+ /** @todo Check that this still works correctly. */
+ AssertCompileSize(key_t, 8);
+# else
+ AssertCompileSize(key_t, 4);
+# endif
+ key_t key;
+ mClientToken = -1;
+ mClientTokenId = "0";
+ for (uint32_t i = 0; i < 1 << 24; i++)
+ {
+ key = ((uint32_t)'V' << 24) | i;
+ int sem = ::semget(key, 1, S_IRUSR | S_IWUSR | IPC_CREAT | IPC_EXCL);
+ if (sem >= 0 || (errno != EEXIST && errno != EACCES))
+ {
+ mClientToken = sem;
+ if (sem >= 0)
+ mClientTokenId = BstrFmt("%u", key);
+ break;
+ }
+ }
+# else /* !VBOX_WITH_NEW_SYS_V_KEYGEN */
+ Utf8Str semName = pMachine->mData->m_strConfigFileFull;
+ char *pszSemName = NULL;
+ RTStrUtf8ToCurrentCP(&pszSemName, semName);
+ key_t key = ::ftok(pszSemName, 'V');
+ RTStrFree(pszSemName);
+
+ mClientToken = ::semget(key, 1, S_IRWXU | S_IRWXG | S_IRWXO | IPC_CREAT);
+# endif /* !VBOX_WITH_NEW_SYS_V_KEYGEN */
+
+ int errnoSave = errno;
+ if (mClientToken < 0 && errnoSave == ENOSYS)
+ {
+ mMachine->setError(E_FAIL,
+ tr("Cannot create IPC semaphore. Most likely your host kernel lacks "
+ "support for SysV IPC. Check the host kernel configuration for "
+ "CONFIG_SYSVIPC=y"));
+ mClientToken = CTTOKENARG;
+ return;
+ }
+ /* ENOSPC can also be the result of VBoxSVC crashes without properly freeing
+ * the token */
+ if (mClientToken < 0 && errnoSave == ENOSPC)
+ {
+#ifdef RT_OS_LINUX
+ mMachine->setError(E_FAIL,
+ tr("Cannot create IPC semaphore because the system limit for the "
+ "maximum number of semaphore sets (SEMMNI), or the system wide "
+ "maximum number of semaphores (SEMMNS) would be exceeded. The "
+ "current set of SysV IPC semaphores can be determined from "
+ "the file /proc/sysvipc/sem"));
+#else
+ mMachine->setError(E_FAIL,
+ tr("Cannot create IPC semaphore because the system-imposed limit "
+ "on the maximum number of allowed semaphores or semaphore "
+ "identifiers system-wide would be exceeded"));
+#endif
+ mClientToken = CTTOKENARG;
+ return;
+ }
+ AssertMsgReturnVoid(mClientToken >= 0, ("Cannot create token, errno=%d", errnoSave));
+ /* set the initial value to 1 */
+ int rv = ::semctl(mClientToken, 0, SETVAL, 1);
+ errnoSave = errno;
+ if (rv != 0)
+ {
+ ::semctl(mClientToken, 0, IPC_RMID);
+ mClientToken = CTTOKENARG;
+ AssertMsgFailedReturnVoid(("Cannot init token, errno=%d", errnoSave));
+ }
+#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+ ComObjPtr<MachineToken> pToken;
+ HRESULT rc = pToken.createObject();
+ if (SUCCEEDED(rc))
+ {
+ rc = pToken->init(pSessionMachine);
+ if (SUCCEEDED(rc))
+ {
+ mClientToken = pToken;
+ if (mClientToken)
+ {
+ rc = mClientToken->AddRef();
+ if (FAILED(rc))
+ mClientToken = NULL;
+ }
+ }
+ }
+ pToken.setNull();
+ mClientTokenPassed = false;
+ /* mClientTokenId isn't really used */
+ mClientTokenId = pMachine->mData->m_strConfigFileFull;
+ AssertMsg(mClientToken,
+ ("Cannot create token '%s', rc=%Rhrc",
+ mClientTokenId.c_str(), rc));
+#else
+# error "Port me!"
+#endif
+}
+
+bool Machine::ClientToken::isReady()
+{
+ return mClientToken != CTTOKENARG;
+}
+
+void Machine::ClientToken::getId(Utf8Str &strId)
+{
+ strId = mClientTokenId;
+}
+
+CTTOKENTYPE Machine::ClientToken::getToken()
+{
+#ifdef VBOX_WITH_GENERIC_SESSION_WATCHER
+ mClientTokenPassed = true;
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ return mClientToken;
+}
+
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+bool Machine::ClientToken::release()
+{
+ bool terminated = false;
+
+#if defined(RT_OS_WINDOWS)
+ AssertMsg(mClientToken, ("semaphore must be created"));
+
+ /* release the token */
+ ::ReleaseMutex(mClientToken);
+ terminated = true;
+#elif defined(RT_OS_OS2)
+ AssertMsg(mClientToken, ("semaphore must be created"));
+
+ /* release the token */
+ ::DosReleaseMutexSem(mClientToken);
+ terminated = true;
+#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
+ AssertMsg(mClientToken >= 0, ("semaphore must be created"));
+ int val = ::semctl(mClientToken, 0, GETVAL);
+ if (val > 0)
+ {
+ /* the semaphore is signaled, meaning the session is terminated */
+ terminated = true;
+ }
+#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+ /** @todo r=klaus never tested, this code is not reached */
+ AssertMsg(mClientToken, ("token must be created"));
+ /* release the token, uses reference counting */
+ if (mClientToken)
+ {
+ if (!mClientTokenPassed)
+ mClientToken->Release();
+ mClientToken = NULL;
+ }
+ terminated = true;
+#else
+# error "Port me!"
+#endif
+ return terminated;
+}
+#endif /* !VBOX_WITH_GENERIC_SESSION_WATCHER */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/ClientWatcher.cpp b/src/VBox/Main/src-server/ClientWatcher.cpp
new file mode 100644
index 00000000..95042bc4
--- /dev/null
+++ b/src/VBox/Main/src-server/ClientWatcher.cpp
@@ -0,0 +1,1055 @@
+/* $Id: ClientWatcher.cpp $ */
+/** @file
+ * VirtualBox API client session crash watcher
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/semaphore.h>
+#include <iprt/process.h>
+
+#include <VBox/log.h>
+#include <VBox/com/defs.h>
+
+#include <vector>
+
+#include "VirtualBoxBase.h"
+#include "AutoCaller.h"
+#include "ClientWatcher.h"
+#include "ClientToken.h"
+#include "VirtualBoxImpl.h"
+#include "MachineImpl.h"
+
+#if defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) || defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+/** Table for adaptive timeouts. After an update the counter starts at the
+ * maximum value and decreases to 0, i.e. first the short timeouts are used
+ * and then the longer ones. This minimizes the detection latency in the
+ * cases where a change is expected, for crashes. */
+static const RTMSINTERVAL s_aUpdateTimeoutSteps[] = { 500, 200, 100, 50, 20, 10, 5 };
+#endif
+
+
+
+VirtualBox::ClientWatcher::ClientWatcher() :
+ mLock(LOCKCLASS_OBJECTSTATE)
+{
+ AssertReleaseFailed();
+}
+
+VirtualBox::ClientWatcher::~ClientWatcher()
+{
+ if (mThread != NIL_RTTHREAD)
+ {
+ /* signal the client watcher thread, should be exiting now */
+ update();
+ /* wait for termination */
+ RTThreadWait(mThread, RT_INDEFINITE_WAIT, NULL);
+ mThread = NIL_RTTHREAD;
+ }
+ mProcesses.clear();
+#if defined(RT_OS_WINDOWS)
+ if (mUpdateReq != NULL)
+ {
+ ::CloseHandle(mUpdateReq);
+ mUpdateReq = NULL;
+ }
+#elif defined(RT_OS_OS2) || defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) || defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+ if (mUpdateReq != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(mUpdateReq);
+ mUpdateReq = NIL_RTSEMEVENT;
+ }
+#else
+# error "Port me!"
+#endif
+}
+
+VirtualBox::ClientWatcher::ClientWatcher(const ComObjPtr<VirtualBox> &pVirtualBox) :
+ mVirtualBox(pVirtualBox),
+ mThread(NIL_RTTHREAD),
+ mUpdateReq(CWUPDATEREQARG),
+ mLock(LOCKCLASS_OBJECTSTATE)
+{
+#if defined(RT_OS_WINDOWS)
+ /* Misc state. */
+ mfTerminate = false;
+ mcMsWait = INFINITE;
+ mcActiveSubworkers = 0;
+
+ /* Update request. The UpdateReq event is also used to wake up subthreads. */
+ mfUpdateReq = false;
+ mUpdateReq = ::CreateEvent(NULL /*pSecAttr*/, TRUE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pszName*/);
+ AssertRelease(mUpdateReq != NULL);
+
+ /* Initialize the handle array. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(mahWaitHandles); i++)
+ mahWaitHandles[i] = NULL;
+ for (uint32_t i = 0; i < RT_ELEMENTS(mahWaitHandles); i += CW_MAX_HANDLES_PER_THREAD)
+ mahWaitHandles[i] = mUpdateReq;
+ mcWaitHandles = 1;
+
+#elif defined(RT_OS_OS2)
+ RTSemEventCreate(&mUpdateReq);
+#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) || defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+ RTSemEventCreate(&mUpdateReq);
+ /* start with high timeouts, nothing to do */
+ ASMAtomicUoWriteU8(&mUpdateAdaptCtr, 0);
+#else
+# error "Port me!"
+#endif
+
+ int vrc = RTThreadCreate(&mThread,
+ worker,
+ (void *)this,
+ 0,
+ RTTHREADTYPE_MAIN_WORKER,
+ RTTHREADFLAGS_WAITABLE,
+ "Watcher");
+ AssertRC(vrc);
+}
+
+bool VirtualBox::ClientWatcher::isReady()
+{
+ return mThread != NIL_RTTHREAD;
+}
+
+/**
+ * Sends a signal to the thread to rescan the clients/VMs having open sessions.
+ */
+void VirtualBox::ClientWatcher::update()
+{
+ AssertReturnVoid(mThread != NIL_RTTHREAD);
+ LogFlowFunc(("ping!\n"));
+
+ /* sent an update request */
+#if defined(RT_OS_WINDOWS)
+ ASMAtomicWriteBool(&mfUpdateReq, true);
+ ::SetEvent(mUpdateReq);
+
+#elif defined(RT_OS_OS2)
+ RTSemEventSignal(mUpdateReq);
+
+#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
+ /* use short timeouts, as we expect changes */
+ ASMAtomicUoWriteU8(&mUpdateAdaptCtr, RT_ELEMENTS(s_aUpdateTimeoutSteps) - 1);
+ RTSemEventSignal(mUpdateReq);
+
+#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+ RTSemEventSignal(mUpdateReq);
+
+#else
+# error "Port me!"
+#endif
+}
+
+/**
+ * Adds a process to the list of processes to be reaped. This call should be
+ * followed by a call to update() to cause the necessary actions immediately,
+ * in case the process crashes straight away.
+ */
+void VirtualBox::ClientWatcher::addProcess(RTPROCESS pid)
+{
+ AssertReturnVoid(mThread != NIL_RTTHREAD);
+ AutoWriteLock alock(mLock COMMA_LOCKVAL_SRC_POS);
+ mProcesses.push_back(pid);
+}
+
+/**
+ * Reaps dead processes in the mProcesses list.
+ *
+ * @returns Number of reaped processes.
+ */
+uint32_t VirtualBox::ClientWatcher::reapProcesses(void)
+{
+ uint32_t cReaped = 0;
+
+ AutoWriteLock alock(mLock COMMA_LOCKVAL_SRC_POS);
+ if (mProcesses.size())
+ {
+ LogFlowFunc(("UPDATE: child process count = %zu\n", mProcesses.size()));
+ VirtualBox::ClientWatcher::ProcessList::iterator it = mProcesses.begin();
+ while (it != mProcesses.end())
+ {
+ RTPROCESS pid = *it;
+ RTPROCSTATUS Status;
+ int vrc = ::RTProcWait(pid, RTPROCWAIT_FLAGS_NOBLOCK, &Status);
+ if (vrc == VINF_SUCCESS)
+ {
+ if ( Status.enmReason != RTPROCEXITREASON_NORMAL
+ || Status.iStatus != RTEXITCODE_SUCCESS)
+ {
+ switch (Status.enmReason)
+ {
+ default:
+ case RTPROCEXITREASON_NORMAL:
+ LogRel(("Reaper: Pid %d (%#x) exited normally: %d (%#x)\n",
+ pid, pid, Status.iStatus, Status.iStatus));
+ break;
+ case RTPROCEXITREASON_ABEND:
+ LogRel(("Reaper: Pid %d (%#x) abended: %d (%#x)\n",
+ pid, pid, Status.iStatus, Status.iStatus));
+ break;
+ case RTPROCEXITREASON_SIGNAL:
+ LogRel(("Reaper: Pid %d (%#x) was signalled: %s (%d / %#x)\n",
+ pid, pid, RTProcSignalName(Status.iStatus), Status.iStatus, Status.iStatus));
+ break;
+ }
+ }
+ else
+ LogFlowFunc(("pid %d (%x) was reaped, status=%d, reason=%d\n", pid, pid, Status.iStatus, Status.enmReason));
+ it = mProcesses.erase(it);
+ cReaped++;
+ }
+ else
+ {
+ LogFlowFunc(("pid %d (%x) was NOT reaped, vrc=%Rrc\n", pid, pid, vrc));
+ if (vrc != VERR_PROCESS_RUNNING)
+ {
+ /* remove the process if it is not already running */
+ it = mProcesses.erase(it);
+ cReaped++;
+ }
+ else
+ ++it;
+ }
+ }
+ }
+
+ return cReaped;
+}
+
+#ifdef RT_OS_WINDOWS
+
+/**
+ * Closes all the client process handles in mahWaitHandles.
+ *
+ * The array is divided into two ranges, first range are mutext handles of
+ * established sessions, the second range is zero or more process handles of
+ * spawning sessions. It's the latter that we close here, the former will just
+ * be NULLed out.
+ *
+ * @param cProcHandles The number of process handles.
+ */
+void VirtualBox::ClientWatcher::winResetHandleArray(uint32_t cProcHandles)
+{
+ uint32_t idxHandle = mcWaitHandles;
+ Assert(cProcHandles < idxHandle);
+ Assert(idxHandle > 0);
+
+ /* Spawning process handles. */
+ while (cProcHandles-- > 0 && idxHandle > 0)
+ {
+ idxHandle--;
+ if (idxHandle % CW_MAX_HANDLES_PER_THREAD)
+ {
+ Assert(mahWaitHandles[idxHandle] != mUpdateReq);
+ LogFlow(("UPDATE: closing %p\n", mahWaitHandles[idxHandle]));
+ CloseHandle(mahWaitHandles[idxHandle]);
+ mahWaitHandles[idxHandle] = NULL;
+ }
+ else
+ Assert(mahWaitHandles[idxHandle] == mUpdateReq);
+ }
+
+ /* Mutex handles (not to be closed). */
+ while (idxHandle-- > 0)
+ if (idxHandle % CW_MAX_HANDLES_PER_THREAD)
+ {
+ Assert(mahWaitHandles[idxHandle] != mUpdateReq);
+ mahWaitHandles[idxHandle] = NULL;
+ }
+ else
+ Assert(mahWaitHandles[idxHandle] == mUpdateReq);
+
+ /* Reset the handle count. */
+ mcWaitHandles = 1;
+}
+
+/**
+ * Does the waiting on a section of the handle array.
+ *
+ * @param pSubworker Pointer to the calling thread's data.
+ * @param cMsWait Number of milliseconds to wait.
+ */
+void VirtualBox::ClientWatcher::subworkerWait(VirtualBox::ClientWatcher::PerSubworker *pSubworker, uint32_t cMsWait)
+{
+ /*
+ * Figure out what section to wait on and do the waiting.
+ */
+ uint32_t idxHandle = pSubworker->iSubworker * CW_MAX_HANDLES_PER_THREAD;
+ uint32_t cHandles = CW_MAX_HANDLES_PER_THREAD;
+ if (idxHandle + cHandles > mcWaitHandles)
+ {
+ cHandles = mcWaitHandles - idxHandle;
+ AssertStmt(idxHandle < mcWaitHandles, cHandles = 1);
+ }
+ Assert(mahWaitHandles[idxHandle] == mUpdateReq);
+
+ DWORD dwWait = ::WaitForMultipleObjects(cHandles,
+ &mahWaitHandles[idxHandle],
+ FALSE /*fWaitAll*/,
+ cMsWait);
+ pSubworker->dwWait = dwWait;
+
+ /*
+ * If we didn't wake up because of the UpdateReq handle, signal it to make
+ * sure everyone else wakes up too.
+ */
+ if (dwWait != WAIT_OBJECT_0)
+ {
+ BOOL fRc = SetEvent(mUpdateReq);
+ Assert(fRc); NOREF(fRc);
+ }
+
+ /*
+ * Last one signals the main thread.
+ */
+ if (ASMAtomicDecU32(&mcActiveSubworkers) == 0)
+ {
+ int vrc = RTThreadUserSignal(maSubworkers[0].hThread);
+ AssertLogRelMsg(RT_SUCCESS(vrc), ("RTThreadUserSignal -> %Rrc\n", vrc));
+ }
+
+}
+
+/**
+ * Thread worker function that watches the termination of all client processes
+ * that have open sessions using IMachine::LockMachine()
+ */
+/*static*/
+DECLCALLBACK(int) VirtualBox::ClientWatcher::subworkerThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ VirtualBox::ClientWatcher::PerSubworker *pSubworker = (VirtualBox::ClientWatcher::PerSubworker *)pvUser;
+ VirtualBox::ClientWatcher *pThis = pSubworker->pSelf;
+ int vrc;
+ while (!pThis->mfTerminate)
+ {
+ /* Before we start waiting, reset the event semaphore. */
+ vrc = RTThreadUserReset(pSubworker->hThread);
+ AssertLogRelMsg(RT_SUCCESS(vrc), ("RTThreadUserReset [iSubworker=%#u] -> %Rrc", pSubworker->iSubworker, vrc));
+
+ /* Do the job. */
+ pThis->subworkerWait(pSubworker, pThis->mcMsWait);
+
+ /* Wait for the next job. */
+ do
+ {
+ vrc = RTThreadUserWaitNoResume(hThreadSelf, RT_INDEFINITE_WAIT);
+ Assert(vrc == VINF_SUCCESS || vrc == VERR_INTERRUPTED);
+ }
+ while ( vrc != VINF_SUCCESS
+ && !pThis->mfTerminate);
+ }
+ return VINF_SUCCESS;
+}
+
+
+#endif /* RT_OS_WINDOWS */
+
+/**
+ * Thread worker function that watches the termination of all client processes
+ * that have open sessions using IMachine::LockMachine()
+ */
+/*static*/
+DECLCALLBACK(int) VirtualBox::ClientWatcher::worker(RTTHREAD hThreadSelf, void *pvUser)
+{
+ LogFlowFuncEnter();
+ NOREF(hThreadSelf);
+
+ VirtualBox::ClientWatcher *that = (VirtualBox::ClientWatcher *)pvUser;
+ Assert(that);
+
+ typedef std::vector<ComObjPtr<Machine> > MachineVector;
+ typedef std::vector<ComObjPtr<SessionMachine> > SessionMachineVector;
+
+ SessionMachineVector machines;
+ MachineVector spawnedMachines;
+
+ size_t cnt = 0;
+ size_t cntSpawned = 0;
+
+ VirtualBoxBase::initializeComForThread();
+
+#if defined(RT_OS_WINDOWS)
+
+ int vrc;
+
+ /* Initialize all the subworker data. */
+ that->maSubworkers[0].hThread = hThreadSelf;
+ for (uint32_t iSubworker = 1; iSubworker < RT_ELEMENTS(that->maSubworkers); iSubworker++)
+ that->maSubworkers[iSubworker].hThread = NIL_RTTHREAD;
+ for (uint32_t iSubworker = 0; iSubworker < RT_ELEMENTS(that->maSubworkers); iSubworker++)
+ {
+ that->maSubworkers[iSubworker].pSelf = that;
+ that->maSubworkers[iSubworker].iSubworker = iSubworker;
+ }
+
+ do
+ {
+ /* VirtualBox has been early uninitialized, terminate. */
+ AutoCaller autoCaller(that->mVirtualBox);
+ if (!autoCaller.isOk())
+ break;
+
+ bool fPidRace = false; /* We poll if the PID of a spawning session hasn't been established yet. */
+ bool fRecentDeath = false; /* We slowly poll if a session has recently been closed to do reaping. */
+ for (;;)
+ {
+ /* release the caller to let uninit() ever proceed */
+ autoCaller.release();
+
+ /* Kick of the waiting. */
+ uint32_t const cSubworkers = (that->mcWaitHandles + CW_MAX_HANDLES_PER_THREAD - 1) / CW_MAX_HANDLES_PER_THREAD;
+ uint32_t const cMsWait = fPidRace ? 500 : fRecentDeath ? 5000 : INFINITE;
+ LogFlowFunc(("UPDATE: Waiting. %u handles, %u subworkers, %u ms wait\n", that->mcWaitHandles, cSubworkers, cMsWait));
+
+ that->mcMsWait = cMsWait;
+ ASMAtomicWriteU32(&that->mcActiveSubworkers, cSubworkers);
+ RTThreadUserReset(hThreadSelf);
+
+ for (uint32_t iSubworker = 1; iSubworker < cSubworkers; iSubworker++)
+ {
+ if (that->maSubworkers[iSubworker].hThread != NIL_RTTHREAD)
+ {
+ vrc = RTThreadUserSignal(that->maSubworkers[iSubworker].hThread);
+ AssertLogRelMsg(RT_SUCCESS(vrc), ("RTThreadUserSignal -> %Rrc\n", vrc));
+ }
+ else
+ {
+ vrc = RTThreadCreateF(&that->maSubworkers[iSubworker].hThread,
+ VirtualBox::ClientWatcher::subworkerThread, &that->maSubworkers[iSubworker],
+ _128K, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "Watcher%u", iSubworker);
+ AssertLogRelMsgStmt(RT_SUCCESS(vrc), ("%Rrc iSubworker=%u\n", vrc, iSubworker),
+ that->maSubworkers[iSubworker].hThread = NIL_RTTHREAD);
+ }
+ if (RT_FAILURE(vrc))
+ that->subworkerWait(&that->maSubworkers[iSubworker], 1);
+ }
+
+ /* Wait ourselves. */
+ that->subworkerWait(&that->maSubworkers[0], cMsWait);
+
+ /* Make sure all waiters are done waiting. */
+ BOOL fRc = SetEvent(that->mUpdateReq);
+ Assert(fRc); NOREF(fRc);
+
+ vrc = RTThreadUserWait(hThreadSelf, RT_INDEFINITE_WAIT);
+ AssertLogRelMsg(RT_SUCCESS(vrc), ("RTThreadUserWait -> %Rrc\n", vrc));
+ Assert(that->mcActiveSubworkers == 0);
+
+ /* Consume pending update request before proceeding with processing the wait results. */
+ fRc = ResetEvent(that->mUpdateReq);
+ Assert(fRc);
+
+ bool update = ASMAtomicXchgBool(&that->mfUpdateReq, false);
+ if (update)
+ LogFlowFunc(("UPDATE: Update request pending\n"));
+ update |= fPidRace;
+
+ /* Process the wait results. */
+ autoCaller.add();
+ if (!autoCaller.isOk())
+ break;
+ fRecentDeath = false;
+ for (uint32_t iSubworker = 0; iSubworker < cSubworkers; iSubworker++)
+ {
+ DWORD dwWait = that->maSubworkers[iSubworker].dwWait;
+ LogFlowFunc(("UPDATE: subworker #%u: dwWait=%#x\n", iSubworker, dwWait));
+ if ( (dwWait > WAIT_OBJECT_0 && dwWait < WAIT_OBJECT_0 + CW_MAX_HANDLES_PER_THREAD)
+ || (dwWait > WAIT_ABANDONED_0 && dwWait < WAIT_ABANDONED_0 + CW_MAX_HANDLES_PER_THREAD) )
+ {
+ uint32_t idxHandle = iSubworker * CW_MAX_HANDLES_PER_THREAD;
+ if (dwWait > WAIT_OBJECT_0 && dwWait < WAIT_OBJECT_0 + CW_MAX_HANDLES_PER_THREAD)
+ idxHandle += dwWait - WAIT_OBJECT_0;
+ else
+ idxHandle += dwWait - WAIT_ABANDONED_0;
+
+ uint32_t const idxMachine = idxHandle - (iSubworker + 1);
+ if (idxMachine < cnt)
+ {
+ /* Machine mutex is released or abandond due to client process termination. */
+ LogFlowFunc(("UPDATE: Calling i_checkForDeath on idxMachine=%u (idxHandle=%u) dwWait=%#x\n",
+ idxMachine, idxHandle, dwWait));
+ fRecentDeath |= (machines[idxMachine])->i_checkForDeath();
+ }
+ else if (idxMachine < cnt + cntSpawned)
+ {
+ /* Spawned VM process has terminated normally. */
+ Assert(dwWait < WAIT_ABANDONED_0);
+ LogFlowFunc(("UPDATE: Calling i_checkForSpawnFailure on idxMachine=%u/%u idxHandle=%u dwWait=%#x\n",
+ idxMachine, idxMachine - cnt, idxHandle, dwWait));
+ fRecentDeath |= (spawnedMachines[idxMachine - cnt])->i_checkForSpawnFailure();
+ }
+ else
+ AssertFailed();
+ update = true;
+ }
+ else
+ Assert(dwWait == WAIT_OBJECT_0 || dwWait == WAIT_TIMEOUT);
+ }
+
+ if (update)
+ {
+ LogFlowFunc(("UPDATE: Update pending (cnt=%u cntSpawned=%u)...\n", cnt, cntSpawned));
+
+ /* close old process handles */
+ that->winResetHandleArray((uint32_t)cntSpawned);
+
+ // get reference to the machines list in VirtualBox
+ VirtualBox::MachinesOList &allMachines = that->mVirtualBox->i_getMachinesList();
+
+ // lock the machines list for reading
+ AutoReadLock thatLock(allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ /* obtain a new set of opened machines */
+ cnt = 0;
+ machines.clear();
+ uint32_t idxHandle = 0;
+
+ for (MachinesOList::iterator it = allMachines.begin();
+ it != allMachines.end();
+ ++it)
+ {
+ AssertMsgBreak(idxHandle < CW_MAX_CLIENTS, ("CW_MAX_CLIENTS reached"));
+
+ ComObjPtr<SessionMachine> sm;
+ if ((*it)->i_isSessionOpenOrClosing(sm))
+ {
+ AutoCaller smCaller(sm);
+ if (smCaller.isOk())
+ {
+ AutoReadLock smLock(sm COMMA_LOCKVAL_SRC_POS);
+ Machine::ClientToken *ct = sm->i_getClientToken();
+ if (ct)
+ {
+ HANDLE ipcSem = ct->getToken();
+ machines.push_back(sm);
+ if (!(idxHandle % CW_MAX_HANDLES_PER_THREAD))
+ idxHandle++;
+ that->mahWaitHandles[idxHandle++] = ipcSem;
+ ++cnt;
+ }
+ }
+ }
+ }
+
+ LogFlowFunc(("UPDATE: direct session count = %d\n", cnt));
+
+ /* obtain a new set of spawned machines */
+ fPidRace = false;
+ cntSpawned = 0;
+ spawnedMachines.clear();
+
+ for (MachinesOList::iterator it = allMachines.begin();
+ it != allMachines.end();
+ ++it)
+ {
+ AssertMsgBreak(idxHandle < CW_MAX_CLIENTS, ("CW_MAX_CLIENTS reached"));
+
+ if ((*it)->i_isSessionSpawning())
+ {
+ ULONG pid;
+ HRESULT hrc = (*it)->COMGETTER(SessionPID)(&pid);
+ if (SUCCEEDED(hrc))
+ {
+ if (pid != NIL_RTPROCESS)
+ {
+ HANDLE hProc = OpenProcess(SYNCHRONIZE, FALSE, pid);
+ AssertMsg(hProc != NULL, ("OpenProcess (pid=%d) failed with %d\n", pid, GetLastError()));
+ if (hProc != NULL)
+ {
+ spawnedMachines.push_back(*it);
+ if (!(idxHandle % CW_MAX_HANDLES_PER_THREAD))
+ idxHandle++;
+ that->mahWaitHandles[idxHandle++] = hProc;
+ ++cntSpawned;
+ }
+ }
+ else
+ fPidRace = true;
+ }
+ }
+ }
+
+ LogFlowFunc(("UPDATE: spawned session count = %d\n", cntSpawned));
+
+ /* Update mcWaitHandles and make sure there is at least one handle to wait on. */
+ that->mcWaitHandles = RT_MAX(idxHandle, 1);
+
+ // machines lock unwinds here
+ }
+ else
+ LogFlowFunc(("UPDATE: No update pending.\n"));
+
+ /* reap child processes */
+ that->reapProcesses();
+
+ } /* for ever (well, till autoCaller fails). */
+
+ } while (0);
+
+ /* Terminate subworker threads. */
+ ASMAtomicWriteBool(&that->mfTerminate, true);
+ for (uint32_t iSubworker = 1; iSubworker < RT_ELEMENTS(that->maSubworkers); iSubworker++)
+ if (that->maSubworkers[iSubworker].hThread != NIL_RTTHREAD)
+ RTThreadUserSignal(that->maSubworkers[iSubworker].hThread);
+ for (uint32_t iSubworker = 1; iSubworker < RT_ELEMENTS(that->maSubworkers); iSubworker++)
+ if (that->maSubworkers[iSubworker].hThread != NIL_RTTHREAD)
+ {
+ vrc = RTThreadWait(that->maSubworkers[iSubworker].hThread, RT_MS_1MIN, NULL /*prc*/);
+ if (RT_SUCCESS(vrc))
+ that->maSubworkers[iSubworker].hThread = NIL_RTTHREAD;
+ else
+ AssertLogRelMsgFailed(("RTThreadWait -> %Rrc\n", vrc));
+ }
+
+ /* close old process handles */
+ that->winResetHandleArray((uint32_t)cntSpawned);
+
+ /* release sets of machines if any */
+ machines.clear();
+ spawnedMachines.clear();
+
+ ::CoUninitialize();
+
+#elif defined(RT_OS_OS2)
+
+ /* according to PMREF, 64 is the maximum for the muxwait list */
+ SEMRECORD handles[64];
+
+ HMUX muxSem = NULLHANDLE;
+
+ do
+ {
+ AutoCaller autoCaller(that->mVirtualBox);
+ /* VirtualBox has been early uninitialized, terminate */
+ if (!autoCaller.isOk())
+ break;
+
+ for (;;)
+ {
+ /* release the caller to let uninit() ever proceed */
+ autoCaller.release();
+
+ int vrc = RTSemEventWait(that->mUpdateReq, 500);
+
+ /* Restore the caller before using VirtualBox. If it fails, this
+ * means VirtualBox is being uninitialized and we must terminate. */
+ autoCaller.add();
+ if (!autoCaller.isOk())
+ break;
+
+ bool update = false;
+ bool updateSpawned = false;
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* update event is signaled */
+ update = true;
+ updateSpawned = true;
+ }
+ else
+ {
+ AssertMsg(vrc == VERR_TIMEOUT || vrc == VERR_INTERRUPTED,
+ ("RTSemEventWait returned %Rrc\n", vrc));
+
+ /* are there any mutexes? */
+ if (cnt > 0)
+ {
+ /* figure out what's going on with machines */
+
+ unsigned long semId = 0;
+ APIRET arc = ::DosWaitMuxWaitSem(muxSem,
+ SEM_IMMEDIATE_RETURN, &semId);
+
+ if (arc == NO_ERROR)
+ {
+ /* machine mutex is normally released */
+ Assert(semId >= 0 && semId < cnt);
+ if (semId >= 0 && semId < cnt)
+ {
+#if 0//def DEBUG
+ {
+ AutoReadLock machineLock(machines[semId] COMMA_LOCKVAL_SRC_POS);
+ LogFlowFunc(("released mutex: machine='%ls'\n",
+ machines[semId]->name().raw()));
+ }
+#endif
+ machines[semId]->i_checkForDeath();
+ }
+ update = true;
+ }
+ else if (arc == ERROR_SEM_OWNER_DIED)
+ {
+ /* machine mutex is abandoned due to client process
+ * termination; find which mutex is in the Owner Died
+ * state */
+ for (size_t i = 0; i < cnt; ++i)
+ {
+ PID pid; TID tid;
+ unsigned long reqCnt;
+ arc = DosQueryMutexSem((HMTX)handles[i].hsemCur, &pid, &tid, &reqCnt);
+ if (arc == ERROR_SEM_OWNER_DIED)
+ {
+ /* close the dead mutex as asked by PMREF */
+ ::DosCloseMutexSem((HMTX)handles[i].hsemCur);
+
+ Assert(i >= 0 && i < cnt);
+ if (i >= 0 && i < cnt)
+ {
+#if 0//def DEBUG
+ {
+ AutoReadLock machineLock(machines[semId] COMMA_LOCKVAL_SRC_POS);
+ LogFlowFunc(("mutex owner dead: machine='%ls'\n",
+ machines[i]->name().raw()));
+ }
+#endif
+ machines[i]->i_checkForDeath();
+ }
+ }
+ }
+ update = true;
+ }
+ else
+ AssertMsg(arc == ERROR_INTERRUPT || arc == ERROR_TIMEOUT,
+ ("DosWaitMuxWaitSem returned %d\n", arc));
+ }
+
+ /* are there any spawning sessions? */
+ if (cntSpawned > 0)
+ {
+ for (size_t i = 0; i < cntSpawned; ++i)
+ updateSpawned |= (spawnedMachines[i])->
+ i_checkForSpawnFailure();
+ }
+ }
+
+ if (update || updateSpawned)
+ {
+ // get reference to the machines list in VirtualBox
+ VirtualBox::MachinesOList &allMachines = that->mVirtualBox->i_getMachinesList();
+
+ // lock the machines list for reading
+ AutoReadLock thatLock(allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ if (update)
+ {
+ /* close the old muxsem */
+ if (muxSem != NULLHANDLE)
+ ::DosCloseMuxWaitSem(muxSem);
+
+ /* obtain a new set of opened machines */
+ cnt = 0;
+ machines.clear();
+
+ for (MachinesOList::iterator it = allMachines.begin();
+ it != allMachines.end(); ++it)
+ {
+ /// @todo handle situations with more than 64 objects
+ AssertMsg(cnt <= 64 /* according to PMREF */,
+ ("maximum of 64 mutex semaphores reached (%d)",
+ cnt));
+
+ ComObjPtr<SessionMachine> sm;
+ if ((*it)->i_isSessionOpenOrClosing(sm))
+ {
+ AutoCaller smCaller(sm);
+ if (smCaller.isOk())
+ {
+ AutoReadLock smLock(sm COMMA_LOCKVAL_SRC_POS);
+ ClientToken *ct = sm->i_getClientToken();
+ if (ct)
+ {
+ HMTX ipcSem = ct->getToken();
+ machines.push_back(sm);
+ handles[cnt].hsemCur = (HSEM)ipcSem;
+ handles[cnt].ulUser = cnt;
+ ++cnt;
+ }
+ }
+ }
+ }
+
+ LogFlowFunc(("UPDATE: direct session count = %d\n", cnt));
+
+ if (cnt > 0)
+ {
+ /* create a new muxsem */
+ APIRET arc = ::DosCreateMuxWaitSem(NULL, &muxSem, cnt,
+ handles,
+ DCMW_WAIT_ANY);
+ AssertMsg(arc == NO_ERROR,
+ ("DosCreateMuxWaitSem returned %d\n", arc));
+ NOREF(arc);
+ }
+ }
+
+ if (updateSpawned)
+ {
+ /* obtain a new set of spawned machines */
+ spawnedMachines.clear();
+
+ for (MachinesOList::iterator it = allMachines.begin();
+ it != allMachines.end(); ++it)
+ {
+ if ((*it)->i_isSessionSpawning())
+ spawnedMachines.push_back(*it);
+ }
+
+ cntSpawned = spawnedMachines.size();
+ LogFlowFunc(("UPDATE: spawned session count = %d\n", cntSpawned));
+ }
+ }
+
+ /* reap child processes */
+ that->reapProcesses();
+
+ } /* for ever (well, till autoCaller fails). */
+
+ } while (0);
+
+ /* close the muxsem */
+ if (muxSem != NULLHANDLE)
+ ::DosCloseMuxWaitSem(muxSem);
+
+ /* release sets of machines if any */
+ machines.clear();
+ spawnedMachines.clear();
+
+#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
+
+ bool update = false;
+ bool updateSpawned = false;
+
+ do
+ {
+ AutoCaller autoCaller(that->mVirtualBox);
+ if (!autoCaller.isOk())
+ break;
+
+ do
+ {
+ /* release the caller to let uninit() ever proceed */
+ autoCaller.release();
+
+ /* determine wait timeout adaptively: after updating information
+ * relevant to the client watcher, check a few times more
+ * frequently. This ensures good reaction time when the signalling
+ * has to be done a bit before the actual change for technical
+ * reasons, and saves CPU cycles when no activities are expected. */
+ RTMSINTERVAL cMillies;
+ {
+ uint8_t uOld, uNew;
+ do
+ {
+ uOld = ASMAtomicUoReadU8(&that->mUpdateAdaptCtr);
+ uNew = uOld ? uOld - 1 : uOld;
+ } while (!ASMAtomicCmpXchgU8(&that->mUpdateAdaptCtr, uNew, uOld));
+ Assert(uOld <= RT_ELEMENTS(s_aUpdateTimeoutSteps) - 1);
+ cMillies = s_aUpdateTimeoutSteps[uOld];
+ }
+
+ int rc = RTSemEventWait(that->mUpdateReq, cMillies);
+
+ /*
+ * Restore the caller before using VirtualBox. If it fails, this
+ * means VirtualBox is being uninitialized and we must terminate.
+ */
+ autoCaller.add();
+ if (!autoCaller.isOk())
+ break;
+
+ if (RT_SUCCESS(rc) || update || updateSpawned)
+ {
+ /* RT_SUCCESS(rc) means an update event is signaled */
+
+ // get reference to the machines list in VirtualBox
+ VirtualBox::MachinesOList &allMachines = that->mVirtualBox->i_getMachinesList();
+
+ // lock the machines list for reading
+ AutoReadLock thatLock(allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ if (RT_SUCCESS(rc) || update)
+ {
+ /* obtain a new set of opened machines */
+ machines.clear();
+
+ for (MachinesOList::iterator it = allMachines.begin();
+ it != allMachines.end();
+ ++it)
+ {
+ ComObjPtr<SessionMachine> sm;
+ if ((*it)->i_isSessionOpenOrClosing(sm))
+ machines.push_back(sm);
+ }
+
+ cnt = machines.size();
+ LogFlowFunc(("UPDATE: direct session count = %d\n", cnt));
+ }
+
+ if (RT_SUCCESS(rc) || updateSpawned)
+ {
+ /* obtain a new set of spawned machines */
+ spawnedMachines.clear();
+
+ for (MachinesOList::iterator it = allMachines.begin();
+ it != allMachines.end();
+ ++it)
+ {
+ if ((*it)->i_isSessionSpawning())
+ spawnedMachines.push_back(*it);
+ }
+
+ cntSpawned = spawnedMachines.size();
+ LogFlowFunc(("UPDATE: spawned session count = %d\n", cntSpawned));
+ }
+
+ // machines lock unwinds here
+ }
+
+ update = false;
+ for (size_t i = 0; i < cnt; ++i)
+ update |= (machines[i])->i_checkForDeath();
+
+ updateSpawned = false;
+ for (size_t i = 0; i < cntSpawned; ++i)
+ updateSpawned |= (spawnedMachines[i])->i_checkForSpawnFailure();
+
+ /* reap child processes */
+ that->reapProcesses();
+ }
+ while (true);
+ }
+ while (0);
+
+ /* release sets of machines if any */
+ machines.clear();
+ spawnedMachines.clear();
+
+#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+
+ bool updateSpawned = false;
+
+ do
+ {
+ AutoCaller autoCaller(that->mVirtualBox);
+ if (!autoCaller.isOk())
+ break;
+
+ do
+ {
+ /* release the caller to let uninit() ever proceed */
+ autoCaller.release();
+
+ /* determine wait timeout adaptively: after updating information
+ * relevant to the client watcher, check a few times more
+ * frequently. This ensures good reaction time when the signalling
+ * has to be done a bit before the actual change for technical
+ * reasons, and saves CPU cycles when no activities are expected. */
+ RTMSINTERVAL cMillies;
+ {
+ uint8_t uOld, uNew;
+ do
+ {
+ uOld = ASMAtomicUoReadU8(&that->mUpdateAdaptCtr);
+ uNew = uOld ? (uint8_t)(uOld - 1) : uOld;
+ } while (!ASMAtomicCmpXchgU8(&that->mUpdateAdaptCtr, uNew, uOld));
+ Assert(uOld <= RT_ELEMENTS(s_aUpdateTimeoutSteps) - 1);
+ cMillies = s_aUpdateTimeoutSteps[uOld];
+ }
+
+ int rc = RTSemEventWait(that->mUpdateReq, cMillies);
+
+ /*
+ * Restore the caller before using VirtualBox. If it fails, this
+ * means VirtualBox is being uninitialized and we must terminate.
+ */
+ autoCaller.add();
+ if (!autoCaller.isOk())
+ break;
+
+ /** @todo this quite big effort for catching machines in spawning
+ * state which can't be caught by the token mechanism (as the token
+ * can't be in the other process yet) could be eliminated if the
+ * reaping is made smarter, having cross-reference information
+ * from the pid to the corresponding machine object. Both cases do
+ * more or less the same thing anyway. */
+ if (RT_SUCCESS(rc) || updateSpawned)
+ {
+ /* RT_SUCCESS(rc) means an update event is signaled */
+
+ // get reference to the machines list in VirtualBox
+ VirtualBox::MachinesOList &allMachines = that->mVirtualBox->i_getMachinesList();
+
+ // lock the machines list for reading
+ AutoReadLock thatLock(allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ if (RT_SUCCESS(rc) || updateSpawned)
+ {
+ /* obtain a new set of spawned machines */
+ spawnedMachines.clear();
+
+ for (MachinesOList::iterator it = allMachines.begin();
+ it != allMachines.end();
+ ++it)
+ {
+ if ((*it)->i_isSessionSpawning())
+ spawnedMachines.push_back(*it);
+ }
+
+ cntSpawned = spawnedMachines.size();
+ LogFlowFunc(("UPDATE: spawned session count = %d\n", cntSpawned));
+ }
+
+ NOREF(cnt);
+ // machines lock unwinds here
+ }
+
+ updateSpawned = false;
+ for (size_t i = 0; i < cntSpawned; ++i)
+ updateSpawned |= (spawnedMachines[i])->i_checkForSpawnFailure();
+
+ /* reap child processes */
+ that->reapProcesses();
+ }
+ while (true);
+ }
+ while (0);
+
+ /* release sets of machines if any */
+ machines.clear();
+ spawnedMachines.clear();
+
+#else
+# error "Port me!"
+#endif
+
+ VirtualBoxBase::uninitializeComForThread();
+
+ LogFlowFuncLeave();
+ return 0;
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/CloudNetworkImpl.cpp b/src/VBox/Main/src-server/CloudNetworkImpl.cpp
new file mode 100644
index 00000000..41a5c989
--- /dev/null
+++ b/src/VBox/Main/src-server/CloudNetworkImpl.cpp
@@ -0,0 +1,262 @@
+/* $Id: CloudNetworkImpl.cpp $ */
+/** @file
+ * ICloudNetwork COM class implementations.
+ */
+
+/*
+ * Copyright (C) 2019-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#define LOG_GROUP LOG_GROUP_MAIN_CLOUDNETWORK
+#include <VBox/settings.h>
+#include <iprt/cpp/utils.h>
+
+#include "VirtualBoxImpl.h"
+#include "CloudNetworkImpl.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+
+struct CloudNetwork::Data
+{
+ Data() : pVirtualBox(NULL) {}
+ virtual ~Data() {}
+
+ /** weak VirtualBox parent */
+ VirtualBox * const pVirtualBox;
+
+ /** CloudNetwork settings */
+ settings::CloudNetwork s;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// CloudNetwork constructor / destructor
+//
+// ////////////////////////////////////////////////////////////////////////////////
+CloudNetwork::CloudNetwork() : m(NULL)
+{
+}
+
+CloudNetwork::~CloudNetwork()
+{
+}
+
+
+HRESULT CloudNetwork::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void CloudNetwork::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+HRESULT CloudNetwork::init(VirtualBox *aVirtualBox, Utf8Str aName)
+{
+ // Enclose the state transition NotReady->InInit->Ready.
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+ /* share VirtualBox weakly */
+ unconst(m->pVirtualBox) = aVirtualBox;
+
+ m->s.strNetworkName = aName;
+ m->s.fEnabled = true;
+ m->s.strProviderShortName = "OCI";
+ m->s.strProfileName = "Default";
+
+ autoInitSpan.setSucceeded();
+ return S_OK;
+}
+
+void CloudNetwork::uninit()
+{
+ // Enclose the state transition Ready->InUninit->NotReady.
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+}
+
+HRESULT CloudNetwork::i_loadSettings(const settings::CloudNetwork &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->s = data;
+
+ return S_OK;
+}
+
+HRESULT CloudNetwork::i_saveSettings(settings::CloudNetwork &data)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(!m->s.strNetworkName.isEmpty(), E_FAIL);
+ data = m->s;
+
+ return S_OK;
+}
+
+Utf8Str CloudNetwork::i_getProvider()
+{
+ return m->s.strProviderShortName;
+}
+
+Utf8Str CloudNetwork::i_getProfile()
+{
+ return m->s.strProfileName;
+}
+
+Utf8Str CloudNetwork::i_getNetworkId()
+{
+ return m->s.strNetworkId;
+}
+
+Utf8Str CloudNetwork::i_getNetworkName()
+{
+ return m->s.strNetworkName;
+}
+
+
+HRESULT CloudNetwork::getNetworkName(com::Utf8Str &aNetworkName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(!m->s.strNetworkName.isEmpty(), E_FAIL);
+ aNetworkName = m->s.strNetworkName;
+ return S_OK;
+}
+
+HRESULT CloudNetwork::setNetworkName(const com::Utf8Str &aNetworkName)
+{
+ if (aNetworkName.isEmpty())
+ return setError(E_INVALIDARG,
+ tr("Network name cannot be empty"));
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aNetworkName == m->s.strNetworkName)
+ return S_OK;
+
+ m->s.strNetworkName = aNetworkName;
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ return S_OK;
+}
+
+HRESULT CloudNetwork::getEnabled(BOOL *aEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aEnabled = m->s.fEnabled;
+ return S_OK;
+}
+
+HRESULT CloudNetwork::setEnabled(BOOL aEnabled)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (RT_BOOL(aEnabled) == m->s.fEnabled)
+ return S_OK;
+ m->s.fEnabled = RT_BOOL(aEnabled);
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ return S_OK;
+}
+
+HRESULT CloudNetwork::getProvider(com::Utf8Str &aProvider)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aProvider = m->s.strProviderShortName;
+ return S_OK;
+}
+
+HRESULT CloudNetwork::setProvider(const com::Utf8Str &aProvider)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aProvider == m->s.strProviderShortName)
+ return S_OK;
+ m->s.strProviderShortName = aProvider;
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ return S_OK;
+}
+
+HRESULT CloudNetwork::getProfile(com::Utf8Str &aProfile)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aProfile = m->s.strProfileName;
+ return S_OK;
+}
+
+HRESULT CloudNetwork::setProfile(const com::Utf8Str &aProfile)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aProfile == m->s.strProfileName)
+ return S_OK;
+ m->s.strProfileName = aProfile;
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ return S_OK;
+}
+
+HRESULT CloudNetwork::getNetworkId(com::Utf8Str &aNetworkId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aNetworkId = m->s.strNetworkId;
+ return S_OK;
+}
+
+HRESULT CloudNetwork::setNetworkId(const com::Utf8Str &aNetworkId)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aNetworkId == m->s.strNetworkId)
+ return S_OK;
+ m->s.strNetworkId = aNetworkId;
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ return S_OK;
+}
+
diff --git a/src/VBox/Main/src-server/CloudProviderManagerImpl.cpp b/src/VBox/Main/src-server/CloudProviderManagerImpl.cpp
new file mode 100644
index 00000000..48ebe3e9
--- /dev/null
+++ b/src/VBox/Main/src-server/CloudProviderManagerImpl.cpp
@@ -0,0 +1,321 @@
+/* $Id: CloudProviderManagerImpl.cpp $ */
+/** @file
+ * ICloudProviderManager COM class implementations.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#define LOG_GROUP LOG_GROUP_MAIN_CLOUDPROVIDERMANAGER
+#include <iprt/cpp/utils.h>
+#include <VBox/com/array.h>
+
+#include "VirtualBoxImpl.h"
+#include "CloudProviderManagerImpl.h"
+#include "ExtPackManagerImpl.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// CloudProviderManager constructor / destructor
+//
+// ////////////////////////////////////////////////////////////////////////////////
+CloudProviderManager::CloudProviderManager()
+ : m_pVirtualBox(NULL)
+{
+}
+
+CloudProviderManager::~CloudProviderManager()
+{
+}
+
+
+HRESULT CloudProviderManager::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void CloudProviderManager::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+HRESULT CloudProviderManager::init(VirtualBox *aVirtualBox)
+{
+ // Enclose the state transition NotReady->InInit->Ready.
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m_apCloudProviders.clear();
+ unconst(m_pVirtualBox) = aVirtualBox;
+
+ autoInitSpan.setSucceeded();
+ return S_OK;
+}
+
+void CloudProviderManager::uninit()
+{
+ // Enclose the state transition Ready->InUninit->NotReady.
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+#ifdef VBOX_WITH_EXTPACK
+ m_mapCloudProviderManagers.clear();
+#endif
+ m_apCloudProviders.clear();
+
+ unconst(m_pVirtualBox) = NULL; // not a ComPtr, but be pedantic
+}
+
+
+#ifdef VBOX_WITH_EXTPACK
+
+bool CloudProviderManager::i_canRemoveExtPack(IExtPack *aExtPack)
+{
+ AssertReturn(aExtPack, false);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // If any cloud provider in this extension pack fails to prepare the
+ // uninstall it and the cloud provider will be kept, so that the user
+ // can retry safely later. All other cloud providers in this extpack
+ // will be done as usual. No attempt is made to bring back the other
+ // cloud providers into working shape.
+
+ bool fRes = true;
+
+ Bstr bstrExtPackName;
+ aExtPack->COMGETTER(Name)(bstrExtPackName.asOutParam());
+ Utf8Str strExtPackName(bstrExtPackName);
+
+ /* is there a cloud provider in this extpack? */
+ ExtPackNameCloudProviderManagerMap::iterator it
+ = m_mapCloudProviderManagers.find(strExtPackName);
+ if (it != m_mapCloudProviderManagers.end())
+ {
+ // const ComPtr<ICloudProviderManager> pManager(it->second); /* unused */
+
+ /* loop over all providers checking for those from the aExtPack */
+ Assert(m_astrExtPackNames.size() == m_apCloudProviders.size());
+ for (size_t i = 0; i < m_astrExtPackNames.size(); )
+ {
+ /* the horse it rode in on? */
+ if (m_astrExtPackNames[i] != strExtPackName)
+ {
+ i++;
+ continue; /* not the extpack we are looking for */
+ }
+
+ /* note the id of this provider to send an event later */
+ Bstr bstrProviderId;
+
+ /*
+ * pTmpProvider will point to an object with refcount > 0
+ * until the ComPtr is removed from m_apCloudProviders.
+ * PrepareUninstall checks that that is the only reference
+ */
+ HRESULT hrc = S_OK;
+ ULONG uRefCnt = 1;
+ ICloudProvider *pTmpProvider(m_apCloudProviders[i]);
+ if (pTmpProvider)
+ {
+ /* do this before the provider goes over the rainbow bridge */
+ hrc = pTmpProvider->COMGETTER(Id)(bstrProviderId.asOutParam());
+
+ /*
+ * We send this event @em before we try to uninstall
+ * the provider. The GUI can get the event and get
+ * rid of any references to the objects related to
+ * this provider that it still has.
+ */
+ if (bstrProviderId.isNotEmpty())
+ m_pVirtualBox->i_onCloudProviderUninstall(bstrProviderId);
+
+ hrc = pTmpProvider->PrepareUninstall();
+ pTmpProvider->AddRef();
+ uRefCnt = pTmpProvider->Release();
+ }
+
+ /* has PrepareUninstall uninited the provider? */
+ if (SUCCEEDED(hrc) && uRefCnt == 1)
+ {
+ m_astrExtPackNames.erase(m_astrExtPackNames.begin() + (ssize_t)i);
+ m_apCloudProviders.erase(m_apCloudProviders.begin() + (ssize_t)i);
+
+ if (bstrProviderId.isNotEmpty())
+ m_pVirtualBox->i_onCloudProviderRegistered(bstrProviderId, FALSE);
+
+ /* NB: not advancing loop index */
+ }
+ else
+ {
+ LogRel(("CloudProviderManager: provider '%s' blocks extpack uninstall, result=%Rhrc, refcount=%u\n",
+ strExtPackName.c_str(), hrc, uRefCnt));
+ fRes = false;
+ i++;
+ }
+ }
+
+ if (fRes)
+ m_mapCloudProviderManagers.erase(it);
+
+ /**
+ * Tell listeners we are done and they can re-read the new
+ * list of providers.
+ */
+ m_pVirtualBox->i_onCloudProviderListChanged(FALSE);
+ }
+
+ return fRes;
+}
+
+void CloudProviderManager::i_addExtPack(IExtPack *aExtPack)
+{
+ HRESULT hrc;
+
+ AssertReturnVoid(aExtPack);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Bstr bstrExtPackName;
+ aExtPack->COMGETTER(Name)(bstrExtPackName.asOutParam());
+ Utf8Str strExtPackName(bstrExtPackName);
+
+ /* get the extpack's cloud provider manager object if present */
+ ComPtr<IUnknown> pObj;
+ com::Guid idObj(COM_IIDOF(ICloudProviderManager));
+ hrc = aExtPack->QueryObject(Bstr(idObj.toString()).raw(), pObj.asOutParam());
+ if (FAILED(hrc))
+ return;
+ const ComPtr<ICloudProviderManager> pManager(pObj);
+ if (pManager.isNull())
+ return;
+
+ /* get the list of cloud providers */
+ SafeIfaceArray<ICloudProvider> apProvidersFromCurrExtPack;
+ hrc = pManager->COMGETTER(Providers)(ComSafeArrayAsOutParam(apProvidersFromCurrExtPack));
+ if (FAILED(hrc))
+ return;
+ if (apProvidersFromCurrExtPack.size() == 0)
+ return;
+
+ m_mapCloudProviderManagers[strExtPackName] = pManager;
+
+ for (unsigned i = 0; i < apProvidersFromCurrExtPack.size(); i++)
+ {
+ const ComPtr<ICloudProvider> pProvider(apProvidersFromCurrExtPack[i]);
+ if (!pProvider.isNull())
+ {
+ // Sanity check each cloud provider by forcing a QueryInterface call,
+ // making sure that it implements the right interface.
+ ComPtr<ICloudProvider> pProviderCheck;
+ pProvider.queryInterfaceTo(pProviderCheck.asOutParam());
+ if (!pProviderCheck.isNull()) /* ok, seems legit */
+ {
+ /* save the provider and the name of the extpack it came from */
+ Assert(m_astrExtPackNames.size() == m_apCloudProviders.size());
+ m_astrExtPackNames.push_back(strExtPackName);
+ m_apCloudProviders.push_back(pProvider);
+
+ Bstr bstrProviderId;
+ pProvider->COMGETTER(Id)(bstrProviderId.asOutParam());
+ if (bstrProviderId.isNotEmpty())
+ m_pVirtualBox->i_onCloudProviderRegistered(bstrProviderId, TRUE);
+ }
+ }
+ }
+
+ /**
+ * Tell listeners we are done and they can re-read the new list of
+ * providers.
+ */
+ m_pVirtualBox->i_onCloudProviderListChanged(TRUE);
+}
+
+#endif /* VBOX_WITH_EXTPACK */
+
+HRESULT CloudProviderManager::getProviders(std::vector<ComPtr<ICloudProvider> > &aProviders)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aProviders = m_apCloudProviders;
+ return S_OK;
+}
+
+HRESULT CloudProviderManager::getProviderById(const com::Guid &aProviderId,
+ ComPtr<ICloudProvider> &aProvider)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ for (size_t i = 0; i < m_apCloudProviders.size(); i++)
+ {
+ Bstr bstrId;
+ HRESULT hrc = m_apCloudProviders[i]->COMGETTER(Id)(bstrId.asOutParam());
+ if (SUCCEEDED(hrc) && aProviderId == bstrId)
+ {
+ aProvider = m_apCloudProviders[i];
+ return S_OK;
+ }
+ }
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Could not find a cloud provider with UUID {%RTuuid}"),
+ aProviderId.raw());
+}
+
+HRESULT CloudProviderManager::getProviderByShortName(const com::Utf8Str &aProviderName,
+ ComPtr<ICloudProvider> &aProvider)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ for (size_t i = 0; i < m_apCloudProviders.size(); i++)
+ {
+ Bstr bstrName;
+ HRESULT hrc = m_apCloudProviders[i]->COMGETTER(ShortName)(bstrName.asOutParam());
+ if (SUCCEEDED(hrc) && bstrName.equals(aProviderName))
+ {
+ aProvider = m_apCloudProviders[i];
+ return S_OK;
+ }
+ }
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Could not find a cloud provider with short name '%s'"),
+ aProviderName.c_str());
+}
+
+HRESULT CloudProviderManager::getProviderByName(const com::Utf8Str &aProviderName,
+ ComPtr<ICloudProvider> &aProvider)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ for (size_t i = 0; i < m_apCloudProviders.size(); i++)
+ {
+ Bstr bstrName;
+ HRESULT hrc = m_apCloudProviders[i]->COMGETTER(Name)(bstrName.asOutParam());
+ if (SUCCEEDED(hrc) && bstrName.equals(aProviderName))
+ {
+ aProvider = m_apCloudProviders[i];
+ return S_OK;
+ }
+ }
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Could not find a cloud provider with name '%s'"),
+ aProviderName.c_str());
+}
+
diff --git a/src/VBox/Main/src-server/DHCPConfigImpl.cpp b/src/VBox/Main/src-server/DHCPConfigImpl.cpp
new file mode 100644
index 00000000..8e091612
--- /dev/null
+++ b/src/VBox/Main/src-server/DHCPConfigImpl.cpp
@@ -0,0 +1,1509 @@
+/* $Id: DHCPConfigImpl.cpp $ */
+/** @file
+ * VirtualBox Main - IDHCPConfig, IDHCPConfigGlobal, IDHCPConfigGroup, IDHCPConfigIndividual implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_DHCPCONFIG
+#include "DHCPConfigImpl.h"
+#include "LoggingNew.h"
+
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/net.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/cpp/xml.h>
+
+#include <VBox/com/array.h>
+#include <VBox/settings.h>
+
+#include "AutoCaller.h"
+#include "DHCPServerImpl.h"
+#include "MachineImpl.h"
+#include "VirtualBoxImpl.h"
+
+#include "../../NetworkServices/Dhcpd/DhcpOptions.h"
+
+
+
+/*********************************************************************************************************************************
+* DHCPConfig Implementation *
+*********************************************************************************************************************************/
+
+HRESULT DHCPConfig::i_initWithDefaults(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent)
+{
+ unconst(m_pVirtualBox) = a_pVirtualBox;
+ unconst(m_pParent) = a_pParent;
+ return S_OK;
+}
+
+
+HRESULT DHCPConfig::i_initWithSettings(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent, const settings::DHCPConfig &rConfig)
+{
+ unconst(m_pVirtualBox) = a_pVirtualBox;
+ unconst(m_pParent) = a_pParent;
+
+ m_secMinLeaseTime = rConfig.secMinLeaseTime;
+ m_secDefaultLeaseTime = rConfig.secDefaultLeaseTime;
+ m_secMaxLeaseTime = rConfig.secMaxLeaseTime;
+
+ /*
+ * The two option list:
+ */
+ struct
+ {
+ const char *psz;
+ std::vector<DHCPOption_T> *pDst;
+ } aStr2Vec[] =
+ {
+ { rConfig.strForcedOptions.c_str(), &m_vecForcedOptions },
+ { rConfig.strSuppressedOptions.c_str(), &m_vecSuppressedOptions },
+ };
+ for (size_t i = 0; i < RT_ELEMENTS(aStr2Vec); i++)
+ {
+ Assert(aStr2Vec[i].pDst->size() == 0);
+ const char *psz = RTStrStripL(aStr2Vec[i].psz);
+ while (*psz != '\0')
+ {
+ uint8_t bOpt;
+ char *pszNext;
+ int vrc = RTStrToUInt8Ex(psz, &pszNext, 10, &bOpt);
+ if ( vrc == VINF_SUCCESS
+ || vrc == VWRN_TRAILING_SPACES
+ || vrc == VWRN_TRAILING_CHARS)
+ {
+ try
+ {
+ aStr2Vec[i].pDst->push_back((DHCPOption_T)bOpt);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+ else
+ {
+ LogRelFunc(("Trouble at offset %#zu converting '%s' to a DHCPOption_T vector (vrc=%Rrc)! Ignornig the remainder.\n",
+ psz - aStr2Vec[i].psz, aStr2Vec[i].psz, vrc));
+ break;
+ }
+ psz = RTStrStripL(pszNext);
+ }
+ }
+
+ /*
+ * The option map:
+ */
+ for (settings::DhcpOptionMap::const_iterator it = rConfig.mapOptions.begin(); it != rConfig.mapOptions.end(); ++it)
+ {
+ try
+ {
+ m_OptionMap[it->first] = settings::DhcpOptValue(it->second.strValue, it->second.enmEncoding);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ return S_OK;
+}
+
+
+HRESULT DHCPConfig::i_saveSettings(settings::DHCPConfig &a_rDst)
+{
+ /* lease times */
+ a_rDst.secMinLeaseTime = m_secMinLeaseTime;
+ a_rDst.secDefaultLeaseTime = m_secDefaultLeaseTime;
+ a_rDst.secMaxLeaseTime = m_secMaxLeaseTime;
+
+ /* Forced and suppressed vectors: */
+ try
+ {
+ a_rDst.strForcedOptions.setNull();
+ for (size_t i = 0; i < m_vecForcedOptions.size(); i++)
+ a_rDst.strForcedOptions.appendPrintf(i ? " %d" : "%d", m_vecForcedOptions[i]);
+
+ a_rDst.strSuppressedOptions.setNull();
+ for (size_t i = 0; i < m_vecSuppressedOptions.size(); i++)
+ a_rDst.strSuppressedOptions.appendPrintf(i ? " %d" : "%d", m_vecSuppressedOptions[i]);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+
+ /* Options: */
+ try
+ {
+ a_rDst.mapOptions = m_OptionMap;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+
+HRESULT DHCPConfig::i_getScope(DHCPConfigScope_T *aScope)
+{
+ /* No locking needed. */
+ *aScope = m_enmScope;
+ return S_OK;
+}
+
+
+HRESULT DHCPConfig::i_getMinLeaseTime(ULONG *aMinLeaseTime)
+{
+ AutoReadLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+ *aMinLeaseTime = m_secMinLeaseTime;
+ return S_OK;
+}
+
+
+HRESULT DHCPConfig::i_setMinLeaseTime(ULONG aMinLeaseTime)
+{
+ {
+ AutoWriteLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+ m_secMinLeaseTime = aMinLeaseTime;
+ }
+ return i_doWriteConfig();
+}
+
+
+HRESULT DHCPConfig::i_getDefaultLeaseTime(ULONG *aDefaultLeaseTime)
+{
+ AutoReadLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+ *aDefaultLeaseTime = m_secDefaultLeaseTime;
+ return S_OK;
+}
+
+
+HRESULT DHCPConfig::i_setDefaultLeaseTime(ULONG aDefaultLeaseTime)
+{
+ {
+ AutoWriteLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+ m_secDefaultLeaseTime = aDefaultLeaseTime;
+ }
+ return i_doWriteConfig();
+}
+
+
+HRESULT DHCPConfig::i_getMaxLeaseTime(ULONG *aMaxLeaseTime)
+{
+ AutoReadLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+ *aMaxLeaseTime = m_secMaxLeaseTime;
+ return S_OK;
+}
+
+
+HRESULT DHCPConfig::i_setMaxLeaseTime(ULONG aMaxLeaseTime)
+{
+ {
+ AutoWriteLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+ m_secMaxLeaseTime = aMaxLeaseTime;
+ }
+ return i_doWriteConfig();
+}
+
+
+HRESULT DHCPConfig::i_getForcedOptions(std::vector<DHCPOption_T> &aOptions)
+{
+ AutoReadLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+ try
+ {
+ aOptions = m_vecForcedOptions;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+
+HRESULT DHCPConfig::i_setForcedOptions(const std::vector<DHCPOption_T> &aOptions)
+{
+ /*
+ * Validate the options.
+ */
+ try
+ {
+ std::map<DHCPOption_T, bool> mapDuplicates;
+ for (size_t i = 0; i < aOptions.size(); i++)
+ {
+ DHCPOption_T enmOpt = aOptions[i];
+ if ((int)enmOpt > 0 && (int)enmOpt < 255)
+ {
+ if (mapDuplicates.find(enmOpt) == mapDuplicates.end())
+ mapDuplicates[enmOpt] = true;
+ else
+ return m_pHack->setError(E_INVALIDARG, tr("Duplicate option value: %d"), (int)enmOpt);
+ }
+ else
+ return m_pHack->setError(E_INVALIDARG, tr("Invalid option value: %d"), (int)enmOpt);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Do the updating.
+ */
+ {
+ AutoWriteLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+
+ /* Actually changed? */
+ if (m_vecForcedOptions.size() == aOptions.size())
+ {
+ ssize_t i = (ssize_t)m_vecForcedOptions.size();
+ while (i-- > 0)
+ if (m_vecForcedOptions[(size_t)i] != aOptions[(size_t)i])
+ break;
+ if (i < 0)
+ return S_OK;
+ }
+
+ /* Copy over the changes: */
+ try
+ {
+ m_vecForcedOptions = aOptions;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ return i_doWriteConfig();
+}
+
+
+HRESULT DHCPConfig::i_getSuppressedOptions(std::vector<DHCPOption_T> &aOptions)
+{
+ AutoReadLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+ try
+ {
+ aOptions = m_vecSuppressedOptions;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+
+HRESULT DHCPConfig::i_setSuppressedOptions(const std::vector<DHCPOption_T> &aOptions)
+{
+ /*
+ * Validate and normalize it.
+ */
+ std::map<DHCPOption_T, bool> mapNormalized;
+ try
+ {
+ for (size_t i = 0; i < aOptions.size(); i++)
+ {
+ DHCPOption_T enmOpt = aOptions[i];
+ if ((int)enmOpt > 0 && (int)enmOpt < 255)
+ mapNormalized[enmOpt] = true;
+ else
+ return m_pHack->setError(E_INVALIDARG, tr("Invalid option value: %d"), (int)enmOpt);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Do the updating.
+ */
+ {
+ AutoWriteLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+
+ /* Actually changed? */
+ if (m_vecSuppressedOptions.size() == mapNormalized.size())
+ {
+ size_t i = 0;
+ for (std::map<DHCPOption_T, bool>::const_iterator itMap = mapNormalized.begin();; ++itMap, i++)
+ {
+ if (itMap == mapNormalized.end())
+ return S_OK; /* no change */
+ if (itMap->first != m_vecSuppressedOptions[i])
+ break;
+ }
+ }
+
+ /* Copy over the changes: */
+ try
+ {
+ m_vecSuppressedOptions.resize(mapNormalized.size());
+ size_t i = 0;
+ for (std::map<DHCPOption_T, bool>::const_iterator itMap = mapNormalized.begin();
+ itMap != mapNormalized.end(); ++itMap, i++)
+ m_vecSuppressedOptions[i] = itMap->first;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ return i_doWriteConfig();
+}
+
+
+HRESULT DHCPConfig::i_setOption(DHCPOption_T aOption, DHCPOptionEncoding_T aEncoding, const com::Utf8Str &aValue)
+{
+ /*
+ * Validate the option as there is no point in allowing the user to set
+ * something that the DHCP server does not grok. It will only lead to
+ * startup failures an no DHCP. We share this code with the server.
+ */
+ DhcpOption *pParsed = NULL;
+ int rc = VINF_SUCCESS;
+ try
+ {
+ pParsed = DhcpOption::parse((uint8_t)aOption, aEncoding, aValue.c_str(), &rc);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ if (pParsed)
+ {
+ delete pParsed;
+
+ /*
+ * Add/change it.
+ */
+ {
+ AutoWriteLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+ try
+ {
+ m_OptionMap[aOption] = settings::DhcpOptValue(aValue, aEncoding);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+ i_doWriteConfig();
+ return S_OK;
+ }
+
+ if (rc == VERR_WRONG_TYPE)
+ return m_pHack->setError(E_INVALIDARG, tr("Unsupported encoding %d (option %d, value %s)"),
+ (int)aEncoding, (int)aOption, aValue.c_str());
+ if (rc == VERR_NOT_SUPPORTED)
+ return m_pHack->setError(E_INVALIDARG, tr("Unsupported option %d (encoding %d, value %s)"),
+ (int)aOption, (int)aEncoding, aValue.c_str());
+ return m_pHack->setError(E_INVALIDARG, tr("Malformed option %d value '%s' (encoding %d, rc=%Rrc)"),
+ (int)aOption, aValue.c_str(), (int)aEncoding, rc);
+}
+
+
+HRESULT DHCPConfig::i_removeOption(DHCPOption_T aOption)
+{
+ {
+ AutoWriteLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+ settings::DhcpOptionMap::iterator it = m_OptionMap.find(aOption);
+ if (it != m_OptionMap.end())
+ m_OptionMap.erase(it);
+ else
+ return m_pHack->setError(VBOX_E_OBJECT_NOT_FOUND, tr("DHCP option %u was not found"), aOption);
+ }
+ return i_doWriteConfig();
+}
+
+
+HRESULT DHCPConfig::i_removeAllOptions()
+{
+ {
+ AutoWriteLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+ m_OptionMap.erase(m_OptionMap.begin(), m_OptionMap.end());
+ }
+ return i_doWriteConfig();
+}
+
+
+HRESULT DHCPConfig::i_getOption(DHCPOption_T aOption, DHCPOptionEncoding_T *aEncoding, com::Utf8Str &aValue)
+{
+ AutoReadLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+ settings::DhcpOptionMap::const_iterator it = m_OptionMap.find(aOption);
+ if (it != m_OptionMap.end())
+ {
+ *aEncoding = it->second.enmEncoding;
+ return aValue.assignEx(it->second.strValue);
+ }
+ return m_pHack->setError(VBOX_E_OBJECT_NOT_FOUND, tr("DHCP option %u was not found"), aOption);
+}
+
+
+HRESULT DHCPConfig::i_getAllOptions(std::vector<DHCPOption_T> &aOptions, std::vector<DHCPOptionEncoding_T> &aEncodings,
+ std::vector<com::Utf8Str> &aValues)
+{
+ AutoReadLock alock(m_pHack COMMA_LOCKVAL_SRC_POS);
+ try
+ {
+ aOptions.resize(m_OptionMap.size());
+ aEncodings.resize(m_OptionMap.size());
+ aValues.resize(m_OptionMap.size());
+ size_t i = 0;
+ for (settings::DhcpOptionMap::iterator it = m_OptionMap.begin(); it != m_OptionMap.end(); ++it, i++)
+ {
+ aOptions[i] = it->first;
+ aEncodings[i] = it->second.enmEncoding;
+ aValues[i] = it->second.strValue;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+
+HRESULT DHCPConfig::i_remove()
+{
+ return m_pParent->i_removeConfig(this, m_enmScope);
+}
+
+
+
+/**
+ * Causes the global VirtualBox configuration file to be written
+ *
+ * @returns COM status code.
+ *
+ * @note Must hold no locks when this is called!
+ * @note Public because DHCPGroupCondition needs to call it too.
+ */
+HRESULT DHCPConfig::i_doWriteConfig()
+{
+ AssertPtrReturn(m_pVirtualBox, E_FAIL);
+
+ AutoWriteLock alock(m_pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ return m_pVirtualBox->i_saveSettings();
+}
+
+
+/**
+ * Produces the Dhcpd configuration.
+ *
+ * The base class only saves DHCP options.
+ *
+ * @param pElmConfig The element where to put the configuration.
+ * @throws std::bad_alloc
+ */
+void DHCPConfig::i_writeDhcpdConfig(xml::ElementNode *pElmConfig)
+{
+ if (m_secMinLeaseTime > 0 )
+ pElmConfig->setAttribute("secMinLeaseTime", (uint32_t)m_secMinLeaseTime);
+ if (m_secDefaultLeaseTime > 0 )
+ pElmConfig->setAttribute("secDefaultLeaseTime", (uint32_t)m_secDefaultLeaseTime);
+ if (m_secMaxLeaseTime > 0 )
+ pElmConfig->setAttribute("secMaxLeaseTime", (uint32_t)m_secMaxLeaseTime);
+
+ struct
+ {
+ const char *pszElement;
+ std::vector<DHCPOption_T> *pVec;
+ } aVec2Elm[] = { { "ForcedOption", &m_vecForcedOptions }, { "SuppressedOption", &m_vecSuppressedOptions }, };
+ for (size_t i = 0; i < RT_ELEMENTS(aVec2Elm); i++)
+ for (std::vector<DHCPOption_T>::const_iterator it = aVec2Elm[i].pVec->begin(); it != aVec2Elm[i].pVec->end(); ++it)
+ {
+ xml::ElementNode *pElmChild = pElmConfig->createChild(aVec2Elm[i].pszElement);
+ pElmChild->setAttribute("name", (int)*it);
+ }
+
+ for (settings::DhcpOptionMap::const_iterator it = m_OptionMap.begin(); it != m_OptionMap.end(); ++it)
+ {
+ xml::ElementNode *pElmOption = pElmConfig->createChild("Option");
+ pElmOption->setAttribute("name", (int)it->first);
+ pElmOption->setAttribute("encoding", it->second.enmEncoding);
+ pElmOption->setAttribute("value", it->second.strValue);
+ }
+}
+
+
+
+/*********************************************************************************************************************************
+* DHCPGlobalConfig Implementation *
+*********************************************************************************************************************************/
+#undef LOG_GROUP
+#define LOG_GROUP LOG_GROUP_MAIN_DHCPGLOBALCONFIG
+
+HRESULT DHCPGlobalConfig::initWithDefaults(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT hrc = DHCPConfig::i_initWithDefaults(a_pVirtualBox, a_pParent);
+ if (SUCCEEDED(hrc))
+ hrc = i_setOption(DHCPOption_SubnetMask, DHCPOptionEncoding_Normal, "0.0.0.0");
+
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ return hrc;
+}
+
+
+HRESULT DHCPGlobalConfig::initWithSettings(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent, const settings::DHCPConfig &rConfig)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT hrc = DHCPConfig::i_initWithSettings(a_pVirtualBox, a_pParent, rConfig);
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed(hrc);
+ return hrc;
+}
+
+
+void DHCPGlobalConfig::uninit()
+{
+ AutoUninitSpan autoUninitSpan(this);
+ if (!autoUninitSpan.uninitDone())
+ autoUninitSpan.setSucceeded();
+}
+
+
+HRESULT DHCPGlobalConfig::i_saveSettings(settings::DHCPConfig &a_rDst)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return DHCPConfig::i_saveSettings(a_rDst);
+}
+
+
+/**
+ * For getting the network mask option value (IDHCPServer::netmask attrib).
+ *
+ * @returns COM status code.
+ * @param a_rDst Where to return it.
+ * @throws nothing
+ */
+HRESULT DHCPGlobalConfig::i_getNetworkMask(com::Utf8Str &a_rDst)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ settings::DhcpOptionMap::const_iterator it = m_OptionMap.find(DHCPOption_SubnetMask);
+ if (it != m_OptionMap.end())
+ {
+ if (it->second.enmEncoding == DHCPOptionEncoding_Normal)
+ return a_rDst.assignEx(it->second.strValue);
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("DHCP option DHCPOption_SubnetMask is not in a legacy encoding"));
+ }
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("DHCP option DHCPOption_SubnetMask was not found"));
+}
+
+
+/**
+ * For setting the network mask option value (IDHCPServer::netmask attrib).
+ *
+ * @returns COM status code.
+ * @param a_rSrc The new value.
+ * @throws nothing
+ */
+HRESULT DHCPGlobalConfig::i_setNetworkMask(const com::Utf8Str &a_rSrc)
+{
+ /* Validate it before setting it: */
+ RTNETADDRIPV4 AddrIgnored;
+ int vrc = RTNetStrToIPv4Addr(a_rSrc.c_str(), &AddrIgnored);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid IPv4 netmask '%s': %Rrc"), a_rSrc.c_str(), vrc);
+
+ return i_setOption(DHCPOption_SubnetMask, DHCPOptionEncoding_Normal, a_rSrc);
+}
+
+
+/**
+ * Overriden to ensure the sanity of the DHCPOption_SubnetMask option.
+ */
+HRESULT DHCPGlobalConfig::i_setOption(DHCPOption_T aOption, DHCPOptionEncoding_T aEncoding, const com::Utf8Str &aValue)
+{
+ if (aOption != DHCPOption_SubnetMask || aEncoding == DHCPOptionEncoding_Normal)
+ return DHCPConfig::i_setOption(aOption, aEncoding, aValue);
+ return setError(E_FAIL, tr("DHCPOption_SubnetMask must use DHCPOptionEncoding_Normal as it is reflected by IDHCPServer::networkMask"));
+}
+
+
+/**
+ * Overriden to ensure the sanity of the DHCPOption_SubnetMask option.
+ */
+HRESULT DHCPGlobalConfig::i_removeOption(DHCPOption_T aOption)
+{
+ if (aOption != DHCPOption_SubnetMask)
+ return DHCPConfig::i_removeOption(aOption);
+ return setError(E_FAIL, tr("DHCPOption_SubnetMask cannot be removed as it reflects IDHCPServer::networkMask"));
+}
+
+
+/**
+ * Overriden to preserve the DHCPOption_SubnetMask option.
+ */
+HRESULT DHCPGlobalConfig::i_removeAllOptions()
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ settings::DhcpOptionMap::iterator it = m_OptionMap.find(DHCPOption_SubnetMask);
+ m_OptionMap.erase(m_OptionMap.begin(), it);
+ if (it != m_OptionMap.end())
+ {
+ ++it;
+ if (it != m_OptionMap.end())
+ m_OptionMap.erase(it, m_OptionMap.end());
+ }
+ }
+
+ return i_doWriteConfig();
+}
+
+
+/**
+ * Overriden to prevent removal.
+ */
+HRESULT DHCPGlobalConfig::i_remove()
+{
+ return setError(E_ACCESSDENIED, tr("Cannot delete the global config"));
+}
+
+
+
+/*********************************************************************************************************************************
+* DHCPGroupCondition Implementation *
+*********************************************************************************************************************************/
+#undef LOG_GROUP
+#define LOG_GROUP LOG_GROUP_MAIN_DHCPGROUPCONDITION
+
+HRESULT DHCPGroupCondition::initWithDefaults(DHCPGroupConfig *a_pParent, bool a_fInclusive, DHCPGroupConditionType_T a_enmType,
+ const com::Utf8Str a_strValue)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m_pParent = a_pParent;
+ m_fInclusive = a_fInclusive;
+ m_enmType = a_enmType;
+ HRESULT hrc = m_strValue.assignEx(a_strValue);
+
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed(hrc);
+ return hrc;
+}
+
+
+HRESULT DHCPGroupCondition::initWithSettings(DHCPGroupConfig *a_pParent, const settings::DHCPGroupCondition &a_rSrc)
+{
+ return initWithDefaults(a_pParent, a_rSrc.fInclusive, a_rSrc.enmType, a_rSrc.strValue);
+}
+
+
+void DHCPGroupCondition::uninit()
+{
+ AutoUninitSpan autoUninitSpan(this);
+ if (!autoUninitSpan.uninitDone())
+ autoUninitSpan.setSucceeded();
+}
+
+
+HRESULT DHCPGroupCondition::i_saveSettings(settings::DHCPGroupCondition &a_rDst)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ a_rDst.fInclusive = m_fInclusive;
+ a_rDst.enmType = m_enmType;
+ return a_rDst.strValue.assignEx(m_strValue);
+}
+
+
+/**
+ * Worker for validating the condition value according to the given type.
+ *
+ * @returns COM status code.
+ * @param enmType The condition type.
+ * @param strValue The condition value.
+ * @param pErrorDst The object to use for reporting errors.
+ */
+/*static*/ HRESULT DHCPGroupCondition::i_validateTypeAndValue(DHCPGroupConditionType_T enmType, com::Utf8Str const &strValue,
+ VirtualBoxBase *pErrorDst)
+{
+ switch (enmType)
+ {
+ case DHCPGroupConditionType_MAC:
+ {
+ RTMAC MACAddress;
+ int vrc = RTNetStrToMacAddr(strValue.c_str(), &MACAddress);
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+ return pErrorDst->setError(E_INVALIDARG, tr("Not a valid MAC address: %s"), strValue.c_str());
+ }
+
+ case DHCPGroupConditionType_MACWildcard:
+ {
+ /* This must be colon separated double xdigit bytes. Single bytes
+ shorthand or raw hexstrings won't match anything. For reasons of
+ simplicity, '?' can only be used to match xdigits, '*' must match 1+
+ chars. */
+ /** @todo test this properly... */
+ const char *psz = strValue.c_str();
+ size_t off = 0;
+ unsigned cPairsLeft = 6;
+ bool fSeenAsterisk = false;
+ for (;;)
+ {
+ char ch = psz[off++];
+ if (RT_C_IS_XDIGIT(ch) || ch == '?')
+ {
+ ch = psz[off++];
+ if (RT_C_IS_XDIGIT(ch) || ch == '?')
+ {
+ ch = psz[off++];
+ cPairsLeft -= 1;
+ if (cPairsLeft == 0)
+ {
+ if (!ch)
+ return S_OK;
+ return pErrorDst->setError(E_INVALIDARG,
+ tr("Trailing chars in MAC wildcard address: %s (offset %zu)"),
+ psz, off - 1);
+ }
+ if (ch == ':' || ch == '*')
+ continue;
+ if (ch == '\0' && fSeenAsterisk)
+ return S_OK;
+ return pErrorDst->setError(E_INVALIDARG,
+ tr("Malformed MAC wildcard address: %s (offset %zu)"),
+ psz, off - 1);
+ }
+
+ if (ch == '*')
+ {
+ fSeenAsterisk = true;
+ do
+ ch = psz[off++];
+ while (ch == '*');
+ if (ch == '\0')
+ return S_OK;
+ cPairsLeft -= 1;
+ if (cPairsLeft == 0)
+ return pErrorDst->setError(E_INVALIDARG,
+ tr("Trailing chars in MAC wildcard address: %s (offset %zu)"),
+ psz, off - 1);
+ if (ch == ':')
+ continue;
+ }
+ else
+ return pErrorDst->setError(E_INVALIDARG, tr("Malformed MAC wildcard address: %s (offset %zu)"),
+ psz, off - 1);
+ }
+ else if (ch == '*')
+ {
+ fSeenAsterisk = true;
+ do
+ ch = psz[off++];
+ while (ch == '*');
+ if (ch == '\0')
+ return S_OK;
+ if (ch == ':')
+ {
+ cPairsLeft -= 1;
+ if (cPairsLeft == 0)
+ return pErrorDst->setError(E_INVALIDARG,
+ tr("Trailing chars in MAC wildcard address: %s (offset %zu)"),
+ psz, off - 1);
+ continue;
+ }
+
+ }
+ else
+ return pErrorDst->setError(E_INVALIDARG, tr("Malformed MAC wildcard address: %s (offset %zu)"),
+ psz, off - 1);
+
+ /* Pick up after '*' in the two cases above: ch is not ':' or '\0'. */
+ Assert(ch != ':' && ch != '\0');
+ if (RT_C_IS_XDIGIT(ch) || ch == '?')
+ {
+ ch = psz[off++];
+ if (RT_C_IS_XDIGIT(ch) || ch == '?' || ch == '*')
+ {
+ off -= 2;
+ continue;
+ }
+ if (ch == ':')
+ {
+ ch = psz[off++];
+ if (ch == '\0')
+ return S_OK;
+ cPairsLeft -= 1;
+ if (cPairsLeft == 0)
+ return pErrorDst->setError(E_INVALIDARG,
+ tr("Trailing chars in MAC wildcard address: %s (offset %zu)"),
+ psz, off - 1);
+ continue;
+ }
+ if (ch == '\0')
+ return S_OK;
+ return pErrorDst->setError(E_INVALIDARG,
+ tr("Trailing chars in MAC wildcard address: %s (offset %zu)"),
+ psz, off - 1);
+ }
+ return pErrorDst->setError(E_INVALIDARG,
+ tr("Malformed MAC wildcard address: %s (offset %zu)"),
+ psz, off - 1);
+ }
+ break;
+ }
+
+ case DHCPGroupConditionType_vendorClassID:
+ case DHCPGroupConditionType_vendorClassIDWildcard:
+ case DHCPGroupConditionType_userClassID:
+ case DHCPGroupConditionType_userClassIDWildcard:
+ if (strValue.length() == 0)
+ return pErrorDst->setError(E_INVALIDARG, tr("Value cannot be empty"));
+ if (strValue.length() < 255)
+ return pErrorDst->setError(E_INVALIDARG, tr("Value is too long: %zu bytes", "", strValue.length()),
+ strValue.length());
+ break;
+
+ default:
+ return pErrorDst->setError(E_INVALIDARG, tr("Invalid condition type: %d"), enmType);
+ }
+
+ return S_OK;
+}
+
+
+HRESULT DHCPGroupCondition::getInclusive(BOOL *aInclusive)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aInclusive = m_fInclusive;
+ return S_OK;
+}
+
+
+HRESULT DHCPGroupCondition::setInclusive(BOOL aInclusive)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if ((aInclusive != FALSE) == m_fInclusive)
+ return S_OK;
+ m_fInclusive = aInclusive != FALSE;
+ }
+ return m_pParent->i_doWriteConfig();
+}
+
+
+HRESULT DHCPGroupCondition::getType(DHCPGroupConditionType_T *aType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aType = m_enmType;
+ return S_OK;
+}
+
+
+HRESULT DHCPGroupCondition::setType(DHCPGroupConditionType_T aType)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aType == m_enmType)
+ return S_OK;
+ HRESULT hrc = i_validateTypeAndValue(aType, m_strValue, this);
+ if (FAILED(hrc))
+ return hrc;
+ m_enmType = aType;
+ }
+ return m_pParent->i_doWriteConfig();
+}
+
+
+HRESULT DHCPGroupCondition::getValue(com::Utf8Str &aValue)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return aValue.assignEx(m_strValue);
+}
+
+
+HRESULT DHCPGroupCondition::setValue(const com::Utf8Str &aValue)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aValue == m_strValue)
+ return S_OK;
+ HRESULT hrc = i_validateTypeAndValue(m_enmType, aValue, this);
+ if (FAILED(hrc))
+ return hrc;
+ hrc = m_strValue.assignEx(aValue);
+ if (FAILED(hrc))
+ return hrc;
+ }
+ return m_pParent->i_doWriteConfig();
+}
+
+
+HRESULT DHCPGroupCondition::remove()
+{
+ return m_pParent->i_removeCondition(this);
+}
+
+
+
+/*********************************************************************************************************************************
+* DHCPGroupConfig Implementation *
+*********************************************************************************************************************************/
+#undef LOG_GROUP
+#define LOG_GROUP LOG_GROUP_MAIN_DHCPGROUPCONFIG
+
+
+HRESULT DHCPGroupConfig::initWithDefaults(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent, const com::Utf8Str &a_rName)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ Assert(m_Conditions.size() == 0);
+ HRESULT hrc = DHCPConfig::i_initWithDefaults(a_pVirtualBox, a_pParent);
+ if (SUCCEEDED(hrc))
+ hrc = m_strName.assignEx(a_rName);
+
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed(hrc);
+ return hrc;
+}
+
+
+HRESULT DHCPGroupConfig::initWithSettings(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent, const settings::DHCPGroupConfig &a_rSrc)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ Assert(m_Conditions.size() == 0);
+ HRESULT hrc = DHCPConfig::i_initWithSettings(a_pVirtualBox, a_pParent, a_rSrc);
+ if (SUCCEEDED(hrc))
+ hrc = m_strName.assignEx(a_rSrc.strName);
+
+ for (settings::DHCPGroupConditionVec::const_iterator it = a_rSrc.vecConditions.begin();
+ it != a_rSrc.vecConditions.end() && SUCCEEDED(hrc); ++it)
+ {
+ ComObjPtr<DHCPGroupCondition> ptrCondition;
+ hrc = ptrCondition.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = ptrCondition->initWithSettings(this, *it);
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ m_Conditions.push_back(ptrCondition);
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed(hrc);
+ return hrc;
+}
+
+
+void DHCPGroupConfig::uninit()
+{
+ AutoUninitSpan autoUninitSpan(this);
+ if (!autoUninitSpan.uninitDone())
+ autoUninitSpan.setSucceeded();
+}
+
+
+HRESULT DHCPGroupConfig::i_saveSettings(settings::DHCPGroupConfig &a_rDst)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = DHCPConfig::i_saveSettings(a_rDst);
+ if (SUCCEEDED(hrc))
+ hrc = a_rDst.strName.assignEx(m_strName);
+ if (SUCCEEDED(hrc))
+ {
+ size_t const cConditions = m_Conditions.size();
+ try
+ {
+ a_rDst.vecConditions.resize(cConditions);
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+
+ for (size_t i = 0; i < cConditions && SUCCEEDED(hrc); i++)
+ hrc = m_Conditions[i]->i_saveSettings(a_rDst.vecConditions[i]);
+ }
+ return hrc;
+}
+
+
+HRESULT DHCPGroupConfig::i_removeCondition(DHCPGroupCondition *a_pCondition)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (ConditionsIterator it = m_Conditions.begin(); it != m_Conditions.end();)
+ {
+ DHCPGroupCondition *pCurCondition = *it;
+ if (pCurCondition == a_pCondition)
+ it = m_Conditions.erase(it);
+ else
+ ++it;
+ }
+
+ /* Never mind if already delete, right? */
+ return S_OK;
+}
+
+
+/**
+ * Overridden to add a 'name' attribute and emit condition child elements.
+ */
+void DHCPGroupConfig::i_writeDhcpdConfig(xml::ElementNode *a_pElmGroup)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* The name attribute: */
+ a_pElmGroup->setAttribute("name", m_strName);
+
+ /*
+ * Conditions:
+ */
+ for (ConditionsIterator it = m_Conditions.begin(); it != m_Conditions.end(); ++it)
+ {
+ xml::ElementNode *pElmCondition;
+ switch ((*it)->i_getType())
+ {
+ case DHCPGroupConditionType_MAC:
+ pElmCondition = a_pElmGroup->createChild("ConditionMAC");
+ break;
+ case DHCPGroupConditionType_MACWildcard:
+ pElmCondition = a_pElmGroup->createChild("ConditionMACWildcard");
+ break;
+ case DHCPGroupConditionType_vendorClassID:
+ pElmCondition = a_pElmGroup->createChild("ConditionVendorClassID");
+ break;
+ case DHCPGroupConditionType_vendorClassIDWildcard:
+ pElmCondition = a_pElmGroup->createChild("ConditionVendorClassIDWildcard");
+ break;
+ case DHCPGroupConditionType_userClassID:
+ pElmCondition = a_pElmGroup->createChild("ConditionUserClassID");
+ break;
+ case DHCPGroupConditionType_userClassIDWildcard:
+ pElmCondition = a_pElmGroup->createChild("ConditionUserClassIDWildcard");
+ break;
+ default:
+ AssertLogRelMsgFailed(("m_enmType=%d\n", (*it)->i_getType()));
+ continue;
+ }
+ pElmCondition->setAttribute("inclusive", (*it)->i_getInclusive());
+ pElmCondition->setAttribute("value", (*it)->i_getValue());
+ }
+
+ DHCPConfig::i_writeDhcpdConfig(a_pElmGroup);
+}
+
+
+HRESULT DHCPGroupConfig::getName(com::Utf8Str &aName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return aName.assignEx(m_strName);
+}
+
+
+HRESULT DHCPGroupConfig::setName(const com::Utf8Str &aName)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aName == m_strName)
+ return S_OK;
+ HRESULT hrc = m_strName.assignEx(aName);
+ if (FAILED(hrc))
+ return hrc;
+ }
+ return i_doWriteConfig();
+}
+
+
+HRESULT DHCPGroupConfig::getConditions(std::vector<ComPtr<IDHCPGroupCondition> > &aConditions)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ size_t const cConditions = m_Conditions.size();
+ try
+ {
+ aConditions.resize(cConditions);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ HRESULT hrc = S_OK;
+ for (size_t i = 0; i < cConditions && SUCCEEDED(hrc); i++)
+ hrc = m_Conditions[i].queryInterfaceTo(aConditions[i].asOutParam());
+ return hrc;
+}
+
+
+HRESULT DHCPGroupConfig::addCondition(BOOL aInclusive, DHCPGroupConditionType_T aType, const com::Utf8Str &aValue,
+ ComPtr<IDHCPGroupCondition> &aCondition)
+{
+ /*
+ * Valdiate it.
+ */
+ HRESULT hrc = DHCPGroupCondition::i_validateTypeAndValue(aType, aValue, this);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Add it.
+ */
+ ComObjPtr<DHCPGroupCondition> ptrCondition;
+ hrc = ptrCondition.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = ptrCondition->initWithDefaults(this, aInclusive != FALSE, aType, aValue);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = ptrCondition.queryInterfaceTo(aCondition.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ try
+ {
+ m_Conditions.push_back(ptrCondition);
+ }
+ catch (std::bad_alloc &)
+ {
+ aCondition.setNull();
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ hrc = i_doWriteConfig();
+ if (FAILED(hrc))
+ aCondition.setNull();
+ }
+ }
+ }
+
+ return hrc;
+}
+
+
+HRESULT DHCPGroupConfig::removeAllConditions()
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (m_Conditions.size() == 0)
+ return S_OK;
+
+ /** @todo sever the weak parent link for each entry... */
+ m_Conditions.erase(m_Conditions.begin(), m_Conditions.end());
+ }
+
+ return i_doWriteConfig();
+}
+
+
+
+/*********************************************************************************************************************************
+* DHCPIndividualConfig Implementation *
+*********************************************************************************************************************************/
+#undef LOG_GROUP
+#define LOG_GROUP LOG_GROUP_MAIN_DHCPINDIVIDUALCONFIG
+
+HRESULT DHCPIndividualConfig::initWithMachineIdAndSlot(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent,
+ com::Guid const &a_idMachine, ULONG a_uSlot, uint32_t a_uMACAddressVersion)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT hrc = DHCPConfig::i_initWithDefaults(a_pVirtualBox, a_pParent);
+ if (SUCCEEDED(hrc))
+ {
+ unconst(m_enmScope) = DHCPConfigScope_MachineNIC;
+ unconst(m_idMachine) = a_idMachine;
+ unconst(m_uSlot) = a_uSlot;
+ m_uMACAddressResolvedVersion = a_uMACAddressVersion;
+
+ autoInitSpan.setSucceeded();
+ }
+ return hrc;
+}
+
+
+HRESULT DHCPIndividualConfig::initWithMACAddress(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent, PCRTMAC a_pMACAddress)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT hrc = DHCPConfig::i_initWithDefaults(a_pVirtualBox, a_pParent);
+ if (SUCCEEDED(hrc))
+ {
+ unconst(m_enmScope) = DHCPConfigScope_MAC;
+ unconst(m_MACAddress) = *a_pMACAddress;
+
+ autoInitSpan.setSucceeded();
+ }
+ return hrc;
+}
+
+
+HRESULT DHCPIndividualConfig::initWithSettingsAndMachineIdAndSlot(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent,
+ settings::DHCPIndividualConfig const &rConfig,
+ com::Guid const &a_idMachine, ULONG a_uSlot,
+ uint32_t a_uMACAddressVersion)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT hrc = DHCPConfig::i_initWithSettings(a_pVirtualBox, a_pParent, rConfig);
+ if (SUCCEEDED(hrc))
+ {
+ unconst(m_enmScope) = DHCPConfigScope_MachineNIC;
+ unconst(m_idMachine) = a_idMachine;
+ unconst(m_uSlot) = a_uSlot;
+ m_uMACAddressResolvedVersion = a_uMACAddressVersion;
+ m_strFixedAddress = rConfig.strFixedAddress;
+
+ autoInitSpan.setSucceeded();
+ }
+ return hrc;
+}
+
+
+HRESULT DHCPIndividualConfig::initWithSettingsAndMACAddress(VirtualBox *a_pVirtualBox, DHCPServer *a_pParent,
+ settings::DHCPIndividualConfig const &rConfig, PCRTMAC a_pMACAddress)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT hrc = DHCPConfig::i_initWithSettings(a_pVirtualBox, a_pParent, rConfig);
+ if (SUCCEEDED(hrc))
+ {
+ unconst(m_enmScope) = DHCPConfigScope_MAC;
+ unconst(m_MACAddress) = *a_pMACAddress;
+ m_strFixedAddress = rConfig.strFixedAddress;
+
+ autoInitSpan.setSucceeded();
+ }
+ return hrc;
+}
+
+
+void DHCPIndividualConfig::uninit()
+{
+ AutoUninitSpan autoUninitSpan(this);
+ if (!autoUninitSpan.uninitDone())
+ autoUninitSpan.setSucceeded();
+}
+
+
+HRESULT DHCPIndividualConfig::i_saveSettings(settings::DHCPIndividualConfig &a_rDst)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ a_rDst.uSlot = m_uSlot;
+ int vrc = a_rDst.strMACAddress.printfNoThrow("%RTmac", &m_MACAddress);
+ if (m_idMachine.isValid() && !m_idMachine.isZero() && RT_SUCCESS(vrc))
+ vrc = a_rDst.strVMName.printfNoThrow("%RTuuid", m_idMachine.raw());
+ if (RT_SUCCESS(vrc))
+ vrc = a_rDst.strFixedAddress.assignNoThrow(m_strFixedAddress);
+ if (RT_SUCCESS(vrc))
+ return DHCPConfig::i_saveSettings(a_rDst);
+ return E_OUTOFMEMORY;;
+}
+
+
+HRESULT DHCPIndividualConfig::getMACAddress(com::Utf8Str &aMACAddress)
+{
+ /* No locking needed here (the MAC address, machine UUID and NIC slot number cannot change). */
+ RTMAC MACAddress;
+ if (m_enmScope == DHCPConfigScope_MAC)
+ MACAddress = m_MACAddress;
+ else
+ {
+ HRESULT hrc = i_getMachineMAC(&MACAddress);
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /* Format the return string: */
+ int vrc = aMACAddress.printfNoThrow("%RTmac", &MACAddress);
+ return RT_SUCCESS(vrc) ? S_OK : E_OUTOFMEMORY;
+}
+
+
+HRESULT DHCPIndividualConfig::getMachineId(com::Guid &aId)
+{
+ AutoReadLock(this COMMA_LOCKVAL_SRC_POS);
+ aId = m_idMachine;
+ return S_OK;
+}
+
+
+HRESULT DHCPIndividualConfig::getSlot(ULONG *aSlot)
+{
+ AutoReadLock(this COMMA_LOCKVAL_SRC_POS);
+ *aSlot = m_uSlot;
+ return S_OK;
+}
+
+HRESULT DHCPIndividualConfig::getFixedAddress(com::Utf8Str &aFixedAddress)
+{
+ AutoReadLock(this COMMA_LOCKVAL_SRC_POS);
+ return aFixedAddress.assignEx(m_strFixedAddress);
+}
+
+
+HRESULT DHCPIndividualConfig::setFixedAddress(const com::Utf8Str &aFixedAddress)
+{
+ if (aFixedAddress.isNotEmpty())
+ {
+ RTNETADDRIPV4 AddrIgnored;
+ int vrc = RTNetStrToIPv4Addr(aFixedAddress.c_str(), &AddrIgnored);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid IPv4 address '%s': %Rrc"), aFixedAddress.c_str(), vrc);
+ }
+
+ {
+ AutoWriteLock(this COMMA_LOCKVAL_SRC_POS);
+ m_strFixedAddress = aFixedAddress;
+ }
+ return i_doWriteConfig();
+}
+
+
+/**
+ * Gets the MAC address of m_idMachine + m_uSlot.
+ *
+ * @returns COM status code w/ setError.
+ * @param pMACAddress Where to return the address.
+ *
+ * @note Must be called without holding any DHCP related locks as that would
+ * be lock order violation. The m_idMachine and m_uSlot values are
+ * practically const, so we don't need any locks here anyway.
+ */
+HRESULT DHCPIndividualConfig::i_getMachineMAC(PRTMAC pMACAddress)
+{
+ ComObjPtr<Machine> ptrMachine;
+ HRESULT hrc = m_pVirtualBox->i_findMachine(m_idMachine, false /*fPermitInaccessible*/, true /*aSetError*/, &ptrMachine);
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<INetworkAdapter> ptrNetworkAdapter;
+ hrc = ptrMachine->GetNetworkAdapter(m_uSlot, ptrNetworkAdapter.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ com::Bstr bstrMACAddress;
+ hrc = ptrNetworkAdapter->COMGETTER(MACAddress)(bstrMACAddress.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Utf8Str strMACAddress;
+ try
+ {
+ strMACAddress = bstrMACAddress;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ int vrc = RTNetStrToMacAddr(strMACAddress.c_str(), pMACAddress);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("INetworkAdapter returned bogus MAC address '%ls': %Rrc"),
+ bstrMACAddress.raw(), vrc);
+ }
+ }
+ }
+ return hrc;
+}
+
+
+HRESULT DHCPIndividualConfig::i_resolveMACAddress(uint32_t uVersion)
+{
+ HRESULT hrc;
+ if (m_enmScope == DHCPConfigScope_MachineNIC)
+ {
+ RTMAC MACAddress;
+ hrc = i_getMachineMAC(&MACAddress);
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if ((int32_t)(uVersion - m_uMACAddressResolvedVersion) >= 0)
+ {
+ m_uMACAddressResolvedVersion = uVersion;
+ m_MACAddress = MACAddress;
+ }
+ }
+ }
+ else
+ hrc = S_OK;
+ return hrc;
+}
+
+
+/**
+ * Overridden to write out additional config.
+ */
+void DHCPIndividualConfig::i_writeDhcpdConfig(xml::ElementNode *pElmConfig)
+{
+ char szTmp[RTUUID_STR_LENGTH + 32];
+ RTStrPrintf(szTmp, sizeof(szTmp), "%RTmac", &m_MACAddress);
+ pElmConfig->setAttribute("MACAddress", szTmp);
+
+ if (m_enmScope == DHCPConfigScope_MachineNIC)
+ {
+ RTStrPrintf(szTmp, sizeof(szTmp), "%RTuuid/%u", m_idMachine.raw(), m_uSlot);
+ pElmConfig->setAttribute("name", szTmp);
+ }
+
+ pElmConfig->setAttribute("fixedAddress", m_strFixedAddress);
+
+ DHCPConfig::i_writeDhcpdConfig(pElmConfig);
+}
+
diff --git a/src/VBox/Main/src-server/DHCPServerImpl.cpp b/src/VBox/Main/src-server/DHCPServerImpl.cpp
new file mode 100644
index 00000000..84ccc8a1
--- /dev/null
+++ b/src/VBox/Main/src-server/DHCPServerImpl.cpp
@@ -0,0 +1,1276 @@
+/* $Id: DHCPServerImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_DHCPSERVER
+#include "DHCPServerImpl.h"
+#include "LoggingNew.h"
+
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/net.h>
+#include <iprt/path.h>
+#include <iprt/cpp/path.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/cpp/xml.h>
+
+#include <VBox/com/array.h>
+#include <VBox/settings.h>
+
+#include "AutoCaller.h"
+#include "DHCPConfigImpl.h"
+#include "MachineImpl.h"
+#include "NetworkServiceRunner.h"
+#include "VirtualBoxImpl.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+# define DHCP_EXECUTABLE_NAME "VBoxNetDHCP.exe"
+#else
+# define DHCP_EXECUTABLE_NAME "VBoxNetDHCP"
+#endif
+
+
+/**
+ * DHCP server specialization of NetworkServiceRunner.
+ *
+ * Just defines the executable name and adds option constants.
+ */
+class DHCPServerRunner : public NetworkServiceRunner
+{
+public:
+ DHCPServerRunner() : NetworkServiceRunner(DHCP_EXECUTABLE_NAME)
+ {}
+ virtual ~DHCPServerRunner()
+ {}
+};
+
+
+/**
+ * Hidden private data of the DHCPServer class.
+ */
+struct DHCPServer::Data
+{
+ Data()
+ : pVirtualBox(NULL)
+ , strName()
+ , enabled(FALSE)
+ , uIndividualMACAddressVersion(1)
+ {
+ }
+
+ /** weak VirtualBox parent */
+ VirtualBox * const pVirtualBox;
+ /** The DHCP server name (network). */
+ Utf8Str const strName;
+
+ Utf8Str IPAddress;
+ Utf8Str lowerIP;
+ Utf8Str upperIP;
+
+ BOOL enabled;
+ DHCPServerRunner dhcp;
+
+ com::Utf8Str strLeasesFilename;
+ com::Utf8Str strConfigFilename;
+ com::Utf8Str strLogFilename;
+
+ com::Utf8Str trunkName;
+ com::Utf8Str trunkType;
+
+ /** Global configuration. */
+ ComObjPtr<DHCPGlobalConfig> globalConfig;
+
+ /** Group configuration indexed by name. */
+ std::map<com::Utf8Str, ComObjPtr<DHCPGroupConfig> > groupConfigs;
+ /** Iterator for groupConfigs. */
+ typedef std::map<com::Utf8Str, ComObjPtr<DHCPGroupConfig> >::iterator GroupConfigIterator;
+
+ /** Individual (host) configuration indexed by MAC address or VM UUID. */
+ std::map<com::Utf8Str, ComObjPtr<DHCPIndividualConfig> > individualConfigs;
+ /** Iterator for individualConfigs. */
+ typedef std::map<com::Utf8Str, ComObjPtr<DHCPIndividualConfig> >::iterator IndividualConfigIterator;
+
+ /** Part of a lock-avoidance hack to resolve the VM ID + slot into MAC
+ * addresses before writing out the Dhcpd configuration file. */
+ uint32_t uIndividualMACAddressVersion;
+};
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+
+DHCPServer::DHCPServer()
+ : m(NULL)
+{
+ m = new DHCPServer::Data();
+}
+
+
+DHCPServer::~DHCPServer()
+{
+ if (m)
+ {
+ delete m;
+ m = NULL;
+ }
+}
+
+
+HRESULT DHCPServer::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+
+void DHCPServer::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+
+void DHCPServer::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ if (m->dhcp.isRunning())
+ stop();
+
+ unconst(m->pVirtualBox) = NULL;
+}
+
+
+HRESULT DHCPServer::init(VirtualBox *aVirtualBox, const Utf8Str &aName)
+{
+ AssertReturn(!aName.isEmpty(), E_INVALIDARG);
+
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* share VirtualBox weakly (parent remains NULL so far) */
+ unconst(m->pVirtualBox) = aVirtualBox;
+
+ unconst(m->strName) = aName;
+ m->IPAddress = "0.0.0.0";
+ m->lowerIP = "0.0.0.0";
+ m->upperIP = "0.0.0.0";
+ m->enabled = FALSE;
+
+ /* Global configuration: */
+ HRESULT hrc = m->globalConfig.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = m->globalConfig->initWithDefaults(aVirtualBox, this);
+
+ Assert(m->groupConfigs.size() == 0);
+ Assert(m->individualConfigs.size() == 0);
+
+ /* Confirm a successful initialization or not: */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed(hrc);
+ return hrc;
+}
+
+
+HRESULT DHCPServer::init(VirtualBox *aVirtualBox, const settings::DHCPServer &rData)
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* share VirtualBox weakly (parent remains NULL so far) */
+ unconst(m->pVirtualBox) = aVirtualBox;
+
+ unconst(m->strName) = rData.strNetworkName;
+ m->IPAddress = rData.strIPAddress;
+ m->enabled = rData.fEnabled;
+ m->lowerIP = rData.strIPLower;
+ m->upperIP = rData.strIPUpper;
+
+ /*
+ * Global configuration:
+ */
+ HRESULT hrc = m->globalConfig.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = m->globalConfig->initWithSettings(aVirtualBox, this, rData.globalConfig);
+
+ /*
+ * Group configurations:
+ */
+ Assert(m->groupConfigs.size() == 0);
+ for (settings::DHCPGroupConfigVec::const_iterator it = rData.vecGroupConfigs.begin();
+ it != rData.vecGroupConfigs.end() && SUCCEEDED(hrc); ++it)
+ {
+ ComObjPtr<DHCPGroupConfig> ptrGroupConfig;
+ hrc = ptrGroupConfig.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = ptrGroupConfig->initWithSettings(aVirtualBox, this, *it);
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ m->groupConfigs[it->strName] = ptrGroupConfig;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+ }
+
+ /*
+ * Individual configuration:
+ */
+ Assert(m->individualConfigs.size() == 0);
+ for (settings::DHCPIndividualConfigMap::const_iterator it = rData.mapIndividualConfigs.begin();
+ it != rData.mapIndividualConfigs.end() && SUCCEEDED(hrc); ++it)
+ {
+ ComObjPtr<DHCPIndividualConfig> ptrIndiCfg;
+ com::Utf8Str strKey;
+ if (it->second.strVMName.isEmpty())
+ {
+ RTMAC MACAddress;
+ int vrc = RTNetStrToMacAddr(it->second.strMACAddress.c_str(), &MACAddress);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Ignoring invalid MAC address for individual DHCP config: '%s' - %Rrc\n", it->second.strMACAddress.c_str(), vrc));
+ continue;
+ }
+
+ vrc = strKey.printfNoThrow("%RTmac", &MACAddress);
+ AssertRCReturn(vrc, E_OUTOFMEMORY);
+
+ hrc = ptrIndiCfg.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = ptrIndiCfg->initWithSettingsAndMACAddress(aVirtualBox, this, it->second, &MACAddress);
+ }
+ else
+ {
+ /* This ASSUMES that we're being called after the machines have been
+ loaded so we can resolve VM names into UUID for old settings. */
+ com::Guid idMachine;
+ hrc = i_vmNameToIdAndValidateSlot(it->second.strVMName, it->second.uSlot, idMachine);
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = strKey.printfNoThrow("%RTuuid/%u", idMachine.raw(), it->second.uSlot);
+ AssertRCReturn(vrc, E_OUTOFMEMORY);
+
+ hrc = ptrIndiCfg.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = ptrIndiCfg->initWithSettingsAndMachineIdAndSlot(aVirtualBox, this, it->second,
+ idMachine, it->second.uSlot,
+ m->uIndividualMACAddressVersion - UINT32_MAX / 4);
+ }
+ }
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ m->individualConfigs[strKey] = ptrIndiCfg;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+ }
+
+ /* Confirm a successful initialization or not: */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed(hrc);
+ return hrc;
+}
+
+
+/**
+ * Called by VirtualBox to save our settings.
+ */
+HRESULT DHCPServer::i_saveSettings(settings::DHCPServer &rData)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ rData.strNetworkName = m->strName;
+ rData.strIPAddress = m->IPAddress;
+ rData.fEnabled = m->enabled != FALSE;
+ rData.strIPLower = m->lowerIP;
+ rData.strIPUpper = m->upperIP;
+
+ /* Global configuration: */
+ HRESULT hrc = m->globalConfig->i_saveSettings(rData.globalConfig);
+
+ /* Group configuration: */
+ size_t const cGroupConfigs = m->groupConfigs.size();
+ try
+ {
+ rData.vecGroupConfigs.resize(cGroupConfigs);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ size_t i = 0;
+ for (Data::GroupConfigIterator it = m->groupConfigs.begin(); it != m->groupConfigs.end() && SUCCEEDED(hrc); ++it, i++)
+ {
+ try
+ {
+ rData.vecGroupConfigs[i] = settings::DHCPGroupConfig();
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ hrc = it->second->i_saveSettings(rData.vecGroupConfigs[i]);
+ }
+
+ /* Individual configuration: */
+ for (Data::IndividualConfigIterator it = m->individualConfigs.begin();
+ it != m->individualConfigs.end() && SUCCEEDED(hrc); ++it)
+ {
+ try
+ {
+ rData.mapIndividualConfigs[it->first] = settings::DHCPIndividualConfig();
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ hrc = it->second->i_saveSettings(rData.mapIndividualConfigs[it->first]);
+ }
+
+ return hrc;
+}
+
+
+HRESULT DHCPServer::i_removeConfig(DHCPConfig *pConfig, DHCPConfigScope_T enmScope)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ bool fFound = false;
+ switch (enmScope)
+ {
+ case DHCPConfigScope_Group:
+ {
+ for (Data::GroupConfigIterator it = m->groupConfigs.begin(); it != m->groupConfigs.end();)
+ {
+ DHCPConfig *pCurConfig = it->second;
+ if (pCurConfig == pConfig)
+ {
+ m->groupConfigs.erase(it++); /* Post increment returns copy of original that is then erased. */
+ fFound = true;
+ }
+ else
+ ++it;
+ }
+ break;
+ }
+
+ case DHCPConfigScope_MAC:
+ case DHCPConfigScope_MachineNIC:
+ {
+ for (Data::IndividualConfigIterator it = m->individualConfigs.begin(); it != m->individualConfigs.end();)
+ {
+ DHCPConfig *pCurConfig = it->second;
+ if (pCurConfig == pConfig)
+ {
+ m->individualConfigs.erase(it++); /* Post increment returns copy of original that is then erased. */
+ fFound = true;
+ }
+ else
+ ++it;
+ }
+ break;
+ }
+
+ default:
+ AssertFailedReturn(E_FAIL);
+ }
+
+ /* Don't complain if already removed, right? */
+ if (!fFound)
+ return S_OK;
+ }
+
+ return i_doSaveSettings();
+}
+
+
+/**
+ * Internal worker that saves the settings after a modification was made.
+ *
+ * @returns COM status code.
+ *
+ * @note Caller must not hold any locks!
+ */
+HRESULT DHCPServer::i_doSaveSettings()
+{
+ // save the global settings; for that we should hold only the VirtualBox lock
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ return m->pVirtualBox->i_saveSettings();
+}
+
+
+HRESULT DHCPServer::getNetworkName(com::Utf8Str &aName)
+{
+ /* The name is const, so no need to for locking. */
+ return aName.assignEx(m->strName);
+}
+
+
+HRESULT DHCPServer::getEnabled(BOOL *aEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aEnabled = m->enabled;
+ return S_OK;
+}
+
+
+HRESULT DHCPServer::setEnabled(BOOL aEnabled)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->enabled = aEnabled;
+ }
+ return i_doSaveSettings();
+}
+
+
+HRESULT DHCPServer::getIPAddress(com::Utf8Str &aIPAddress)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return aIPAddress.assignEx(m->IPAddress);
+}
+
+
+HRESULT DHCPServer::getNetworkMask(com::Utf8Str &aNetworkMask)
+{
+ return m->globalConfig->i_getNetworkMask(aNetworkMask);
+}
+
+
+HRESULT DHCPServer::getLowerIP(com::Utf8Str &aIPAddress)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return aIPAddress.assignEx(m->lowerIP);
+}
+
+
+HRESULT DHCPServer::getUpperIP(com::Utf8Str &aIPAddress)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return aIPAddress.assignEx(m->upperIP);
+}
+
+
+HRESULT DHCPServer::setConfiguration(const com::Utf8Str &aIPAddress,
+ const com::Utf8Str &aNetworkMask,
+ const com::Utf8Str &aLowerIP,
+ const com::Utf8Str &aUpperIP)
+{
+ RTNETADDRIPV4 IPAddress, NetworkMask, LowerIP, UpperIP;
+
+ int vrc = RTNetStrToIPv4Addr(aIPAddress.c_str(), &IPAddress);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid server address: %s"), aIPAddress.c_str());
+
+ vrc = RTNetStrToIPv4Addr(aNetworkMask.c_str(), &NetworkMask);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid netmask: %s"), aNetworkMask.c_str());
+
+ vrc = RTNetStrToIPv4Addr(aLowerIP.c_str(), &LowerIP);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid range lower address: %s"), aLowerIP.c_str());
+
+ vrc = RTNetStrToIPv4Addr(aUpperIP.c_str(), &UpperIP);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid range upper address: %s"), aUpperIP.c_str());
+
+ /*
+ * Insist on continuous mask. May be also accept prefix length
+ * here or address/prefix for aIPAddress?
+ */
+ vrc = RTNetMaskToPrefixIPv4(&NetworkMask, NULL);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid netmask: %s"), aNetworkMask.c_str());
+
+ /* It's more convenient to convert to host order once: */
+ IPAddress.u = RT_N2H_U32(IPAddress.u);
+ NetworkMask.u = RT_N2H_U32(NetworkMask.u);
+ LowerIP.u = RT_N2H_U32(LowerIP.u);
+ UpperIP.u = RT_N2H_U32(UpperIP.u);
+
+ /*
+ * Addresses must be unicast and from the same network
+ */
+ if ( (IPAddress.u & UINT32_C(0xe0000000)) == UINT32_C(0xe0000000)
+ || (IPAddress.u & ~NetworkMask.u) == 0
+ || ((IPAddress.u & ~NetworkMask.u) | NetworkMask.u) == UINT32_C(0xffffffff))
+ return setError(E_INVALIDARG, tr("Invalid server address: %s (mask %s)"), aIPAddress.c_str(), aNetworkMask.c_str());
+
+ if ( (LowerIP.u & UINT32_C(0xe0000000)) == UINT32_C(0xe0000000)
+ || (LowerIP.u & NetworkMask.u) != (IPAddress.u &NetworkMask.u)
+ || (LowerIP.u & ~NetworkMask.u) == 0
+ || ((LowerIP.u & ~NetworkMask.u) | NetworkMask.u) == UINT32_C(0xffffffff))
+ return setError(E_INVALIDARG, tr("Invalid range lower address: %s (mask %s)"), aLowerIP.c_str(), aNetworkMask.c_str());
+
+ if ( (UpperIP.u & UINT32_C(0xe0000000)) == UINT32_C(0xe0000000)
+ || (UpperIP.u & NetworkMask.u) != (IPAddress.u &NetworkMask.u)
+ || (UpperIP.u & ~NetworkMask.u) == 0
+ || ((UpperIP.u & ~NetworkMask.u) | NetworkMask.u) == UINT32_C(0xffffffff))
+ return setError(E_INVALIDARG, tr("Invalid range upper address"), aUpperIP.c_str(), aNetworkMask.c_str());
+
+ /* The range should be valid. (It's okay to overlap the server IP.) */
+ if (LowerIP.u > UpperIP.u)
+ return setError(E_INVALIDARG, tr("Lower bound must be less or eqaul than the upper: %s vs %s"),
+ aLowerIP.c_str(), aUpperIP.c_str());
+
+ /*
+ * Input is valid, effect the changes.
+ */
+ HRESULT hrc;
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->IPAddress = aIPAddress;
+ m->lowerIP = aLowerIP;
+ m->upperIP = aUpperIP;
+ hrc = m->globalConfig->i_setNetworkMask(aNetworkMask);
+ }
+ if (SUCCEEDED(hrc))
+ hrc = i_doSaveSettings();
+ return hrc;
+}
+
+
+/**
+ * Validates the VM name and slot, returning the machine ID.
+ *
+ * If a machine ID is given instead of a name, we won't check whether it
+ * actually exists...
+ *
+ * @returns COM status code.
+ * @param aVmName The VM name or UUID.
+ * @param a_uSlot The slot.
+ * @param idMachine Where to return the VM UUID.
+ */
+HRESULT DHCPServer::i_vmNameToIdAndValidateSlot(const com::Utf8Str &aVmName, ULONG a_uSlot, com::Guid &idMachine)
+{
+ if (a_uSlot <= 32)
+ {
+ /* Is it a UUID? */
+ idMachine = aVmName;
+ if (idMachine.isValid() && !idMachine.isZero())
+ return S_OK;
+
+ /* No, find the VM and get it's UUID. */
+ ComObjPtr<Machine> ptrMachine;
+ HRESULT hrc = m->pVirtualBox->i_findMachineByName(aVmName, true /*aSetError*/, &ptrMachine);
+ if (SUCCEEDED(hrc))
+ idMachine = ptrMachine->i_getId();
+ return hrc;
+ }
+ return setError(E_INVALIDARG, tr("NIC slot number (%d) is out of range (0..32)"), a_uSlot);
+}
+
+
+/**
+ * Translates a VM name/id and slot to an individual configuration object.
+ *
+ * @returns COM status code.
+ * @param a_strVmName The VM name or ID.
+ * @param a_uSlot The NIC slot.
+ * @param a_fCreateIfNeeded Whether to create a new entry if not found.
+ * @param a_rPtrConfig Where to return the config object. It's
+ * implicitly referenced, so we don't be returning
+ * with any locks held.
+ *
+ * @note Caller must not be holding any locks!
+ */
+HRESULT DHCPServer::i_vmNameAndSlotToConfig(const com::Utf8Str &a_strVmName, ULONG a_uSlot, bool a_fCreateIfNeeded,
+ ComObjPtr<DHCPIndividualConfig> &a_rPtrConfig)
+{
+ /*
+ * Validate the slot and normalize the name into a UUID.
+ */
+ com::Guid idMachine;
+ HRESULT hrc = i_vmNameToIdAndValidateSlot(a_strVmName, a_uSlot, idMachine);
+ if (SUCCEEDED(hrc))
+ {
+ Utf8Str strKey;
+ int vrc = strKey.printfNoThrow("%RTuuid/%u", idMachine.raw(), a_uSlot);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Look it up.
+ */
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Data::IndividualConfigIterator it = m->individualConfigs.find(strKey);
+ if (it != m->individualConfigs.end())
+ {
+ a_rPtrConfig = it->second;
+ return S_OK;
+ }
+ }
+ if (a_fCreateIfNeeded)
+ {
+ /*
+ * Create a new slot.
+ */
+ /* Instantiate the object: */
+ hrc = a_rPtrConfig.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = a_rPtrConfig->initWithMachineIdAndSlot(m->pVirtualBox, this, idMachine, a_uSlot,
+ m->uIndividualMACAddressVersion - UINT32_MAX / 4);
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Check for creation race: */
+ Data::IndividualConfigIterator it = m->individualConfigs.find(strKey);
+ if (it != m->individualConfigs.end())
+ {
+ a_rPtrConfig.setNull();
+ a_rPtrConfig = it->second;
+ return S_OK;
+ }
+
+ /* Add it. */
+ try
+ {
+ m->individualConfigs[strKey] = a_rPtrConfig;
+
+ /* Save settings. */
+ alock.release();
+ return i_doSaveSettings();
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ a_rPtrConfig.setNull();
+ }
+ }
+ else
+ hrc = VBOX_E_OBJECT_NOT_FOUND;
+ }
+ else
+ hrc = E_OUTOFMEMORY;
+ }
+ return hrc;
+}
+
+
+HRESULT DHCPServer::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ NOREF(aEventSource);
+ ReturnComNotImplemented();
+}
+
+
+HRESULT DHCPServer::getGlobalConfig(ComPtr<IDHCPGlobalConfig> &aGlobalConfig)
+{
+ /* The global configuration is immutable, so no need to lock anything here. */
+ return m->globalConfig.queryInterfaceTo(aGlobalConfig.asOutParam());
+}
+
+
+HRESULT DHCPServer::getGroupConfigs(std::vector<ComPtr<IDHCPGroupConfig> > &aGroupConfigs)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ size_t const cGroupConfigs = m->groupConfigs.size();
+ try
+ {
+ aGroupConfigs.resize(cGroupConfigs);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ size_t i = 0;
+ for (Data::GroupConfigIterator it = m->groupConfigs.begin(); it != m->groupConfigs.end(); ++it, i++)
+ {
+ Assert(i < cGroupConfigs);
+ HRESULT hrc = it->second.queryInterfaceTo(aGroupConfigs[i].asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ return S_OK;
+}
+
+
+HRESULT DHCPServer::getIndividualConfigs(std::vector<ComPtr<IDHCPIndividualConfig> > &aIndividualConfigs)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ size_t const cIndividualConfigs = m->individualConfigs.size();
+ try
+ {
+ aIndividualConfigs.resize(cIndividualConfigs);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ size_t i = 0;
+ for (Data::IndividualConfigIterator it = m->individualConfigs.begin(); it != m->individualConfigs.end(); ++it, i++)
+ {
+ Assert(i < cIndividualConfigs);
+ HRESULT hrc = it->second.queryInterfaceTo(aIndividualConfigs[i].asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ return S_OK;
+}
+
+
+HRESULT DHCPServer::restart()
+{
+ if (!m->dhcp.isRunning())
+ return setErrorBoth(E_FAIL, VERR_PROCESS_NOT_FOUND, tr("not running"));
+
+ /*
+ * Disabled servers will be brought down, but won't be restarted.
+ * (see DHCPServer::start)
+ */
+ HRESULT hrc = stop();
+ if (SUCCEEDED(hrc))
+ hrc = start(m->trunkName, m->trunkType);
+ return hrc;
+}
+
+
+/**
+ * @throws std::bad_alloc
+ */
+HRESULT DHCPServer::i_writeDhcpdConfig(const char *pszFilename, uint32_t uMACAddressVersion) RT_NOEXCEPT
+{
+ /*
+ * Produce the DHCP server configuration.
+ */
+ xml::Document doc;
+ try
+ {
+ xml::ElementNode *pElmRoot = doc.createRootElement("DHCPServer");
+ pElmRoot->setAttribute("networkName", m->strName);
+ if (m->trunkName.isNotEmpty())
+ pElmRoot->setAttribute("trunkName", m->trunkName);
+ pElmRoot->setAttribute("trunkType", m->trunkType);
+ pElmRoot->setAttribute("IPAddress", m->IPAddress);
+ pElmRoot->setAttribute("lowerIP", m->lowerIP);
+ pElmRoot->setAttribute("upperIP", m->upperIP);
+ pElmRoot->setAttribute("leasesFilename", m->strLeasesFilename);
+ Utf8Str strNetworkMask;
+ HRESULT hrc = m->globalConfig->i_getNetworkMask(strNetworkMask);
+ if (FAILED(hrc))
+ return hrc;
+ pElmRoot->setAttribute("networkMask", strNetworkMask);
+
+ /*
+ * Process global options
+ */
+ m->globalConfig->i_writeDhcpdConfig(pElmRoot->createChild("Options"));
+
+ /*
+ * Groups.
+ */
+ for (Data::GroupConfigIterator it = m->groupConfigs.begin(); it != m->groupConfigs.end(); ++it)
+ it->second->i_writeDhcpdConfig(pElmRoot->createChild("Group"));
+
+ /*
+ * Individual NIC configurations.
+ */
+ for (Data::IndividualConfigIterator it = m->individualConfigs.begin(); it != m->individualConfigs.end(); ++it)
+ if (it->second->i_isMACAddressResolved(uMACAddressVersion))
+ it->second->i_writeDhcpdConfig(pElmRoot->createChild("Config"));
+ else
+ LogRelFunc(("Skipping %RTuuid/%u, no MAC address.\n", it->second->i_getMachineId().raw(), it->second->i_getSlot()));
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Write out the document.
+ */
+ try
+ {
+ xml::XmlFileWriter writer(doc);
+ writer.write(pszFilename, false);
+ }
+ catch (...)
+ {
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+
+HRESULT DHCPServer::start(const com::Utf8Str &aTrunkName, const com::Utf8Str &aTrunkType)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Silently ignore attempts to run disabled servers. */
+ if (!m->enabled)
+ return S_OK;
+
+ /*
+ * Resolve the MAC addresses. This requires us to leave the lock.
+ */
+ uint32_t uMACAddressVersion = m->uIndividualMACAddressVersion;
+ if (m->individualConfigs.size() > 0)
+ {
+ m->uIndividualMACAddressVersion = uMACAddressVersion + 1;
+
+ /* Retain pointers to all the individual configuration objects so we
+ can safely access these after releaseing the lock: */
+ std::vector< ComObjPtr<DHCPIndividualConfig> > vecIndividualConfigs;
+ try
+ {
+ vecIndividualConfigs.resize(m->individualConfigs.size());
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ size_t i = 0;
+ for (Data::IndividualConfigIterator it = m->individualConfigs.begin(); it != m->individualConfigs.end(); ++it, i++)
+ vecIndividualConfigs[i] = it->second;
+
+ /* Drop the lock and resolve the MAC addresses: */
+ alock.release();
+
+ i = vecIndividualConfigs.size();
+ while (i-- > 0)
+ vecIndividualConfigs[i]->i_resolveMACAddress(uMACAddressVersion);
+
+ /* Reacquire the lock */
+ alock.acquire();
+ if (!m->enabled)
+ return S_OK;
+ }
+
+ /*
+ * Refuse to start a 2nd DHCP server instance for the same network.
+ */
+ if (m->dhcp.isRunning())
+ return setErrorBoth(VBOX_E_OBJECT_IN_USE, VERR_PROCESS_RUNNING,
+ tr("Cannot start DHCP server because it is already running (pid %RTproc)"), m->dhcp.getPid());
+
+ /*
+ * Copy the startup parameters.
+ */
+ m->trunkName = aTrunkName;
+ m->trunkType = aTrunkType;
+ HRESULT hrc = i_calcLeasesConfigAndLogFilenames(m->strName);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Create configuration file path and write out the configuration.
+ */
+ hrc = i_writeDhcpdConfig(m->strConfigFilename.c_str(), uMACAddressVersion);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Setup the arguments and start the DHCP server.
+ */
+ m->dhcp.resetArguments();
+ int vrc = m->dhcp.addArgPair("--comment", m->strName.c_str());
+ if (RT_SUCCESS(vrc))
+ vrc = m->dhcp.addArgPair("--config", m->strConfigFilename.c_str());
+ if (RT_SUCCESS(vrc))
+ vrc = m->dhcp.addArgPair("--log", m->strLogFilename.c_str());
+ /** @todo Add --log-flags, --log-group-settings, and --log-destinations with
+ * associated IDHCPServer attributes. (Not doing it now because that'll
+ * exhaust all reserved attribute slot in 6.0.) */
+ if (RT_SUCCESS(vrc))
+ {
+ /* Start it: */
+ vrc = m->dhcp.start(true /*aKillProcessOnStop*/);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorVrc(vrc, tr("Failed to start DHCP server for '%s': %Rrc"), m->strName.c_str(), vrc);
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Failed to assemble the command line for DHCP server '%s': %Rrc"),
+ m->strName.c_str(), vrc);
+ }
+ }
+ return hrc;
+}
+
+
+HRESULT DHCPServer::stop(void)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = m->dhcp.stop();
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+ return setErrorVrc(vrc);
+}
+
+
+HRESULT DHCPServer::findLeaseByMAC(const com::Utf8Str &aMac, LONG aType,
+ com::Utf8Str &aAddress, com::Utf8Str &aState, LONG64 *aIssued, LONG64 *aExpire)
+{
+ /* Reset output before we start */
+ *aIssued = 0;
+ *aExpire = 0;
+ aAddress.setNull();
+ aState.setNull();
+
+ /*
+ * Convert and check input.
+ */
+ RTMAC MacAddress;
+ int vrc = RTStrConvertHexBytes(aMac.c_str(), &MacAddress, sizeof(MacAddress), RTSTRCONVERTHEXBYTES_F_SEP_COLON);
+ if (vrc != VINF_SUCCESS)
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid MAC address '%s': %Rrc"), aMac.c_str(), vrc);
+ if (aType != 0)
+ return setError(E_INVALIDARG, tr("flags must be zero (not %#x)"), aType);
+
+ /*
+ * Make sure we've got a lease filename to work with.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (m->strLeasesFilename.isEmpty())
+ {
+ HRESULT hrc = i_calcLeasesConfigAndLogFilenames(m->strName);
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /*
+ * Try at least twice to read the lease database, more if busy.
+ */
+ uint64_t const nsStart = RTTimeNanoTS();
+ for (uint32_t uReadAttempt = 0; ; uReadAttempt++)
+ {
+ /*
+ * Try read the file.
+ */
+ xml::Document doc;
+ try
+ {
+ xml::XmlFileParser parser;
+ parser.read(m->strLeasesFilename.c_str(), doc);
+ }
+ catch (const xml::EIPRTFailure &e)
+ {
+ vrc = e.rc();
+ LogThisFunc(("caught xml::EIPRTFailure: rc=%Rrc (attempt %u, msg=%s)\n", vrc, uReadAttempt, e.what()));
+ if ( ( vrc == VERR_FILE_NOT_FOUND
+ || vrc == VERR_OPEN_FAILED
+ || vrc == VERR_ACCESS_DENIED
+ || vrc == VERR_SHARING_VIOLATION
+ || vrc == VERR_READ_ERROR /*?*/)
+ && ( uReadAttempt == 0
+ || ( uReadAttempt < 64
+ && RTTimeNanoTS() - nsStart < RT_NS_1SEC / 4)) )
+ {
+ alock.release();
+
+ if (uReadAttempt > 0)
+ RTThreadYield();
+ RTThreadSleep(8/*ms*/);
+
+ alock.acquire();
+ LogThisFunc(("Retrying...\n"));
+ continue;
+ }
+ return setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Reading '%s' failed: %Rrc - %s"),
+ m->strLeasesFilename.c_str(), vrc, e.what());
+ }
+ catch (const RTCError &e)
+ {
+ if (e.what())
+ return setError(VBOX_E_FILE_ERROR, tr("Reading '%s' failed: %s"), m->strLeasesFilename.c_str(), e.what());
+ return setError(VBOX_E_FILE_ERROR, tr("Reading '%s' failed: RTCError"), m->strLeasesFilename.c_str());
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ catch (...)
+ {
+ AssertFailed();
+ return setError(VBOX_E_FILE_ERROR, tr("Reading '%s' failed: Unexpected exception"), m->strLeasesFilename.c_str());
+ }
+
+ /*
+ * Look for that mac address.
+ */
+ xml::ElementNode *pElmRoot = doc.getRootElement();
+ if (pElmRoot && pElmRoot->nameEquals("Leases"))
+ {
+ xml::NodesLoop it(*pElmRoot);
+ const xml::ElementNode *pElmLease;
+ while ((pElmLease = it.forAllNodes()) != NULL)
+ if (pElmLease->nameEquals("Lease"))
+ {
+ const char *pszCurMacAddress = pElmLease->findAttributeValue("mac");
+ RTMAC CurMacAddress;
+ if ( pszCurMacAddress
+ && RT_SUCCESS(RTNetStrToMacAddr(pszCurMacAddress, &CurMacAddress))
+ && memcmp(&CurMacAddress, &MacAddress, sizeof(MacAddress)) == 0)
+ {
+ /*
+ * Found it!
+ */
+ xml::ElementNode const *pElmTime = pElmLease->findChildElement("Time");
+ int64_t secIssued = 0;
+ uint32_t cSecsToLive = 0;
+ if (pElmTime)
+ {
+ pElmTime->getAttributeValue("issued", &secIssued);
+ pElmTime->getAttributeValue("expiration", &cSecsToLive);
+ *aIssued = secIssued;
+ *aExpire = secIssued + cSecsToLive;
+ }
+ try
+ {
+ aAddress = pElmLease->findChildElementAttributeValue("Address", "value");
+ aState = pElmLease->findAttributeValue("state");
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /* Check if the lease has expired in the mean time. */
+ HRESULT hrc = S_OK;
+ RTTIMESPEC Now;
+ if ( (aState.equals("acked") || aState.equals("offered") || aState.isEmpty())
+ && secIssued + cSecsToLive < RTTimeSpecGetSeconds(RTTimeNow(&Now)))
+ hrc = RT_SUCCESS(aState.assignNoThrow("expired")) ? S_OK : E_OUTOFMEMORY;
+ return hrc;
+ }
+ }
+ }
+ break;
+ }
+
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Could not find a lease for %RTmac"), &MacAddress);
+}
+
+
+HRESULT DHCPServer::getConfig(DHCPConfigScope_T aScope, const com::Utf8Str &aName, ULONG aSlot, BOOL aMayAdd,
+ ComPtr<IDHCPConfig> &aConfig)
+{
+ if (aSlot != 0 && aScope != DHCPConfigScope_MachineNIC)
+ return setError(E_INVALIDARG, tr("The 'slot' argument must be zero for all but the MachineNIC scope!"));
+
+ switch (aScope)
+ {
+ case DHCPConfigScope_Global:
+ if (aName.isNotEmpty())
+ return setError(E_INVALIDARG, tr("The name must be empty or NULL for the Global scope!"));
+
+ /* No locking required here. */
+ return m->globalConfig.queryInterfaceTo(aConfig.asOutParam());
+
+ case DHCPConfigScope_Group:
+ {
+ if (aName.isEmpty())
+ return setError(E_INVALIDARG, tr("A group must have a name!"));
+ if (aName.length() > _1K)
+ return setError(E_INVALIDARG, tr("Name too long! %zu bytes", "", aName.length()), aName.length());
+
+ /* Look up the group: */
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Data::GroupConfigIterator it = m->groupConfigs.find(aName);
+ if (it != m->groupConfigs.end())
+ return it->second.queryInterfaceTo(aConfig.asOutParam());
+ }
+ /* Create a new group if we can. */
+ if (!aMayAdd)
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Found no configuration for group %s"), aName.c_str());
+ ComObjPtr<DHCPGroupConfig> ptrGroupConfig;
+ HRESULT hrc = ptrGroupConfig.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = ptrGroupConfig->initWithDefaults(m->pVirtualBox, this, aName);
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Check for insertion race: */
+ Data::GroupConfigIterator it = m->groupConfigs.find(aName);
+ if (it != m->groupConfigs.end())
+ return it->second.queryInterfaceTo(aConfig.asOutParam()); /* creation race*/
+
+ /* Try insert it: */
+ try
+ {
+ m->groupConfigs[aName] = ptrGroupConfig;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return ptrGroupConfig.queryInterfaceTo(aConfig.asOutParam());
+ }
+ return hrc;
+ }
+
+ case DHCPConfigScope_MachineNIC:
+ {
+ ComObjPtr<DHCPIndividualConfig> ptrIndividualConfig;
+ HRESULT hrc = i_vmNameAndSlotToConfig(aName, aSlot, aMayAdd != FALSE, ptrIndividualConfig);
+ if (SUCCEEDED(hrc))
+ hrc = ptrIndividualConfig.queryInterfaceTo(aConfig.asOutParam());
+ return hrc;
+ }
+
+ case DHCPConfigScope_MAC:
+ {
+ /* Check and Normalize the MAC address into a key: */
+ RTMAC MACAddress;
+ int vrc = RTNetStrToMacAddr(aName.c_str(), &MACAddress);
+ if (RT_SUCCESS(vrc))
+ {
+ Utf8Str strKey;
+ vrc = strKey.printfNoThrow("%RTmac", &MACAddress);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Look up the MAC address: */
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Data::IndividualConfigIterator it = m->individualConfigs.find(strKey);
+ if (it != m->individualConfigs.end())
+ return it->second.queryInterfaceTo(aConfig.asOutParam());
+ }
+ if (aMayAdd)
+ {
+ /* Create a new individiual configuration: */
+ ComObjPtr<DHCPIndividualConfig> ptrIndividualConfig;
+ HRESULT hrc = ptrIndividualConfig.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = ptrIndividualConfig->initWithMACAddress(m->pVirtualBox, this, &MACAddress);
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Check for insertion race: */
+ Data::IndividualConfigIterator it = m->individualConfigs.find(strKey);
+ if (it != m->individualConfigs.end())
+ return it->second.queryInterfaceTo(aConfig.asOutParam()); /* creation race*/
+
+ /* Try insert it: */
+ try
+ {
+ m->individualConfigs[strKey] = ptrIndividualConfig;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return ptrIndividualConfig.queryInterfaceTo(aConfig.asOutParam());
+ }
+ }
+ else
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Found no configuration for MAC address %s"), strKey.c_str());
+ }
+ return E_OUTOFMEMORY;
+ }
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid MAC address: %s"), aName.c_str());
+ }
+
+ default:
+ return E_FAIL;
+ }
+}
+
+
+/**
+ * Calculates and updates the value of strLeasesFilename given @a aNetwork.
+ */
+HRESULT DHCPServer::i_calcLeasesConfigAndLogFilenames(const com::Utf8Str &aNetwork) RT_NOEXCEPT
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* The lease file must be the same as we used the last time, so careful when changing this code. */
+ int vrc = m->strLeasesFilename.assignNoThrow(m->pVirtualBox->i_homeDir());
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppendCxx(m->strLeasesFilename, aNetwork);
+ if (RT_SUCCESS(vrc))
+ {
+ RTPathPurgeFilename(RTPathFilename(m->strLeasesFilename.mutableRaw()), RTPATH_STR_F_STYLE_HOST);
+
+ /* The configuration file: */
+ vrc = m->strConfigFilename.assignNoThrow(m->strLeasesFilename);
+ if (RT_SUCCESS(vrc))
+ vrc = m->strConfigFilename.appendNoThrow("-Dhcpd.config");
+
+
+ /* The log file: */
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = m->strLogFilename.assignNoThrow(m->strLeasesFilename);
+ if (RT_SUCCESS(vrc))
+ vrc = m->strLogFilename.appendNoThrow("-Dhcpd.log");
+
+ /* Finally, complete the leases file: */
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = m->strLeasesFilename.appendNoThrow("-Dhcpd.leases");
+ if (RT_SUCCESS(vrc))
+ {
+ RTPathPurgeFilename(RTPathFilename(m->strLeasesFilename.mutableRaw()), RTPATH_STR_F_STYLE_HOST);
+ m->strLeasesFilename.jolt();
+ return S_OK;
+ }
+ }
+ }
+ }
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to construct leases, config and log filenames: %Rrc"), vrc);
+}
+
diff --git a/src/VBox/Main/src-server/DataStreamImpl.cpp b/src/VBox/Main/src-server/DataStreamImpl.cpp
new file mode 100644
index 00000000..1fdb9bea
--- /dev/null
+++ b/src/VBox/Main/src-server/DataStreamImpl.cpp
@@ -0,0 +1,297 @@
+/* $Id: DataStreamImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation - DataStream
+ */
+
+/*
+ * Copyright (C) 2018-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_DATASTREAM
+#include "DataStreamImpl.h"
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include <iprt/errcore.h>
+
+
+/*********************************************************************************************************************************
+* Boilerplate constructor & destructor *
+*********************************************************************************************************************************/
+
+DEFINE_EMPTY_CTOR_DTOR(DataStream)
+
+HRESULT DataStream::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void DataStream::FinalRelease()
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+
+/*********************************************************************************************************************************
+* Initializer & uninitializer *
+*********************************************************************************************************************************/
+
+/**
+ * Initializes the DataStream object.
+ *
+ * @param aBufferSize Size of the intermediate buffer.
+ *
+ */
+HRESULT DataStream::init(unsigned long aBufferSize)
+{
+ LogFlowThisFunc(("cbBuffer=%zu\n", aBufferSize));
+
+ /*
+ * Enclose the state transition NotReady->InInit->Ready
+ */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /*
+ * Allocate data instance.
+ */
+ HRESULT hrc = S_OK;
+
+ m_hSemEvtDataAvail = NIL_RTSEMEVENT;
+ m_hSemEvtBufSpcAvail = NIL_RTSEMEVENT;
+ m_pBuffer = NULL;
+ m_fEos = false;
+ int vrc = RTSemEventCreate(&m_hSemEvtDataAvail);
+ if (RT_SUCCESS(vrc))
+ vrc = RTSemEventCreate(&m_hSemEvtBufSpcAvail);
+ if (RT_SUCCESS(vrc))
+ vrc = RTCircBufCreate(&m_pBuffer, aBufferSize);
+
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to initialize data stream object (%Rrc)"), vrc);
+
+ /*
+ * Done. Just update object readiness state.
+ */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed(hrc);
+
+ LogFlowThisFunc(("returns %Rhrc\n", hrc));
+ return hrc;
+}
+
+/**
+ * Uninitializes the instance (called from FinalRelease()).
+ */
+void DataStream::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (!autoUninitSpan.uninitDone())
+ {
+ if (m_hSemEvtDataAvail != NIL_RTSEMEVENT)
+ RTSemEventDestroy(m_hSemEvtDataAvail);
+ if (m_hSemEvtBufSpcAvail != NIL_RTSEMEVENT)
+ RTSemEventDestroy(m_hSemEvtBufSpcAvail);
+ if (m_pBuffer != NULL)
+ RTCircBufDestroy(m_pBuffer);
+ m_hSemEvtDataAvail = NIL_RTSEMEVENT;
+ m_hSemEvtBufSpcAvail = NIL_RTSEMEVENT;
+ }
+
+ LogFlowThisFuncLeave();
+}
+
+
+/*********************************************************************************************************************************
+* IDataStream attributes *
+*********************************************************************************************************************************/
+
+HRESULT DataStream::getReadSize(ULONG *aReadSize)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aReadSize = (ULONG)RTCircBufUsed(m_pBuffer);
+ return S_OK;
+}
+
+
+/*********************************************************************************************************************************
+* IDataStream methods *
+*********************************************************************************************************************************/
+
+HRESULT DataStream::read(ULONG aSize, ULONG aTimeoutMS, std::vector<BYTE> &aData)
+{
+ /*
+ * Allocate return buffer.
+ */
+ try
+ {
+ aData.resize(aSize);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Do the reading. To play safe we exclusivly lock the object while doing this.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+ while ( !RTCircBufUsed(m_pBuffer)
+ && !m_fEos
+ && RT_SUCCESS(vrc))
+ {
+ /* Wait for something to become available. */
+ alock.release();
+ vrc = RTSemEventWait(m_hSemEvtDataAvail, aTimeoutMS == 0 ? RT_INDEFINITE_WAIT : aTimeoutMS);
+ alock.acquire();
+ }
+
+ /*
+ * Manage the result.
+ */
+ HRESULT hrc = S_OK;
+ if ( RT_SUCCESS(vrc)
+ && RTCircBufUsed(m_pBuffer))
+ {
+
+ size_t off = 0;
+ size_t cbCopy = RT_MIN(aSize, RTCircBufUsed(m_pBuffer));
+ if (cbCopy != aSize)
+ {
+ Assert(cbCopy < aSize);
+ aData.resize(cbCopy);
+ }
+
+ while (cbCopy)
+ {
+ void *pvSrc = NULL;
+ size_t cbThisCopy = 0;
+
+ RTCircBufAcquireReadBlock(m_pBuffer, cbCopy, &pvSrc, &cbThisCopy);
+ memcpy(&aData.front() + off, pvSrc, cbThisCopy);
+ RTCircBufReleaseReadBlock(m_pBuffer, cbThisCopy);
+
+ cbCopy -= cbThisCopy;
+ off += cbThisCopy;
+ }
+ vrc = RTSemEventSignal(m_hSemEvtBufSpcAvail);
+ AssertRC(vrc);
+ }
+ else
+ {
+ Assert( RT_FAILURE(vrc)
+ || ( m_fEos
+ && !RTCircBufUsed(m_pBuffer)));
+
+ aData.resize(0);
+ if (vrc == VERR_TIMEOUT)
+ hrc = VBOX_E_TIMEOUT;
+ else if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Error reading %u bytes: %Rrc", "", aSize), aSize, vrc);
+ }
+
+ return hrc;
+}
+
+
+/*********************************************************************************************************************************
+* DataStream internal methods *
+*********************************************************************************************************************************/
+
+/**
+ * Writes the given data into the temporary buffer blocking if it is full.
+ *
+ * @returns IPRT status code.
+ * @param pvBuf The data to write.
+ * @param cbWrite How much to write.
+ * @param pcbWritten Where to store the amount of data written.
+ */
+int DataStream::i_write(const void *pvBuf, size_t cbWrite, size_t *pcbWritten)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(!m_fEos, VERR_INVALID_STATE);
+
+ *pcbWritten = 0;
+
+ int vrc = VINF_SUCCESS;
+ while ( !RTCircBufFree(m_pBuffer)
+ && RT_SUCCESS(vrc))
+ {
+ /* Wait for space to become available. */
+ alock.release();
+ vrc = RTSemEventWait(m_hSemEvtBufSpcAvail, RT_INDEFINITE_WAIT);
+ alock.acquire();
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ const uint8_t *pbBuf = (const uint8_t *)pvBuf;
+ size_t cbCopy = RT_MIN(cbWrite, RTCircBufFree(m_pBuffer));
+
+ *pcbWritten = cbCopy;
+
+ while (cbCopy)
+ {
+ void *pvDst = NULL;
+ size_t cbThisCopy = 0;
+
+ RTCircBufAcquireWriteBlock(m_pBuffer, cbCopy, &pvDst, &cbThisCopy);
+ memcpy(pvDst, pbBuf, cbThisCopy);
+ RTCircBufReleaseWriteBlock(m_pBuffer, cbThisCopy);
+
+ cbCopy -= cbThisCopy;
+ pbBuf += cbThisCopy;
+ }
+
+ RTSemEventSignal(m_hSemEvtDataAvail);
+ }
+
+ return vrc;
+}
+
+/**
+ * Marks the end of the stream.
+ *
+ * @returns IPRT status code.
+ */
+int DataStream::i_close()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m_fEos = true;
+ RTSemEventSignal(m_hSemEvtDataAvail);
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Main/src-server/GraphicsAdapterImpl.cpp b/src/VBox/Main/src-server/GraphicsAdapterImpl.cpp
new file mode 100644
index 00000000..7ebe01cc
--- /dev/null
+++ b/src/VBox/Main/src-server/GraphicsAdapterImpl.cpp
@@ -0,0 +1,445 @@
+/* $Id: GraphicsAdapterImpl.cpp $ */
+/** @file
+ * Implementation of IGraphicsAdapter in VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2004-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_GRAPHICSADAPTER
+
+#include "LoggingNew.h"
+
+#include "GraphicsAdapterImpl.h"
+#include "MachineImpl.h"
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+
+#include <iprt/cpp/utils.h>
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+GraphicsAdapter::GraphicsAdapter() :
+ mParent(NULL)
+{}
+
+GraphicsAdapter::~GraphicsAdapter()
+{}
+
+HRESULT GraphicsAdapter::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void GraphicsAdapter::FinalRelease()
+{
+ LogFlowThisFunc(("\n"));
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the graphics adapter object.
+ *
+ * @param aParent Handle of the parent object.
+ */
+HRESULT GraphicsAdapter::init(Machine *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+ /* mPeer is left null */
+
+ mData.allocate();
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the graphics adapter object given another graphics adapter
+ * object (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT GraphicsAdapter::init(Machine *aParent, GraphicsAdapter *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+ unconst(mPeer) = aThat;
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ mData.share(aThat->mData);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the graphics adapter object given another graphics adapter
+ * object (a kind of copy constructor). This object makes a private copy
+ * of data of the original object passed as an argument.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT GraphicsAdapter::initCopy(Machine *aParent, GraphicsAdapter *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+ /* mPeer is left null */
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ mData.attachCopy(aThat->mData);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void GraphicsAdapter::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ mData.free();
+
+ unconst(mPeer) = NULL;
+ unconst(mParent) = NULL;
+}
+
+// Wrapped IGraphicsAdapter properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GraphicsAdapter::getGraphicsControllerType(GraphicsControllerType_T *aGraphicsControllerType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aGraphicsControllerType = mData->graphicsControllerType;
+
+ return S_OK;
+}
+
+HRESULT GraphicsAdapter::setGraphicsControllerType(GraphicsControllerType_T aGraphicsControllerType)
+{
+ switch (aGraphicsControllerType)
+ {
+ case GraphicsControllerType_Null:
+ case GraphicsControllerType_VBoxVGA:
+#ifdef VBOX_WITH_VMSVGA
+ case GraphicsControllerType_VMSVGA:
+ case GraphicsControllerType_VBoxSVGA:
+#endif
+ break;
+ default:
+ return setError(E_INVALIDARG, tr("The graphics controller type (%d) is invalid"), aGraphicsControllerType);
+ }
+
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mParent->i_setModified(Machine::IsModified_GraphicsAdapter);
+ mData.backup();
+ mData->graphicsControllerType = aGraphicsControllerType;
+
+ return S_OK;
+}
+
+HRESULT GraphicsAdapter::getVRAMSize(ULONG *aVRAMSize)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aVRAMSize = mData->ulVRAMSizeMB;
+
+ return S_OK;
+}
+
+HRESULT GraphicsAdapter::setVRAMSize(ULONG aVRAMSize)
+{
+ /* check VRAM limits */
+ if (aVRAMSize > SchemaDefs::MaxGuestVRAM)
+ return setError(E_INVALIDARG,
+ tr("Invalid VRAM size: %lu MB (must be in range [%lu, %lu] MB)"),
+ aVRAMSize, SchemaDefs::MinGuestVRAM, SchemaDefs::MaxGuestVRAM);
+
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mParent->i_setModified(Machine::IsModified_GraphicsAdapter);
+ mData.backup();
+ mData->ulVRAMSizeMB = aVRAMSize;
+
+ return S_OK;
+}
+
+HRESULT GraphicsAdapter::getAccelerate3DEnabled(BOOL *aAccelerate3DEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAccelerate3DEnabled = mData->fAccelerate3D;
+
+ return S_OK;
+}
+
+HRESULT GraphicsAdapter::setAccelerate3DEnabled(BOOL aAccelerate3DEnabled)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo check validity! */
+
+ mParent->i_setModified(Machine::IsModified_GraphicsAdapter);
+ mData.backup();
+ mData->fAccelerate3D = !!aAccelerate3DEnabled;
+
+ return S_OK;
+}
+
+
+HRESULT GraphicsAdapter::getAccelerate2DVideoEnabled(BOOL *aAccelerate2DVideoEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* bugref:9691 The legacy VHWA acceleration has been disabled completely. */
+ *aAccelerate2DVideoEnabled = FALSE;
+
+ return S_OK;
+}
+
+HRESULT GraphicsAdapter::setAccelerate2DVideoEnabled(BOOL aAccelerate2DVideoEnabled)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo check validity! */
+
+ mParent->i_setModified(Machine::IsModified_GraphicsAdapter);
+ mData.backup();
+ mData->fAccelerate2DVideo = !!aAccelerate2DVideoEnabled;
+
+ return S_OK;
+}
+
+HRESULT GraphicsAdapter::getMonitorCount(ULONG *aMonitorCount)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aMonitorCount = mData->cMonitors;
+
+ return S_OK;
+}
+
+HRESULT GraphicsAdapter::setMonitorCount(ULONG aMonitorCount)
+{
+ /* make sure monitor count is a sensible number */
+ if (aMonitorCount < 1 || aMonitorCount > SchemaDefs::MaxGuestMonitors)
+ return setError(E_INVALIDARG,
+ tr("Invalid monitor count: %lu (must be in range [%lu, %lu])"),
+ aMonitorCount, 1, SchemaDefs::MaxGuestMonitors);
+
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mParent->i_setModified(Machine::IsModified_GraphicsAdapter);
+ mData.backup();
+ mData->cMonitors = aMonitorCount;
+
+ return S_OK;
+}
+
+// Wrapped IGraphicsAdapter methods
+/////////////////////////////////////////////////////////////////////////////
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Loads settings from the given machine node.
+ * May be called once right after this object creation.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT GraphicsAdapter::i_loadSettings(const settings::GraphicsAdapter &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mData.assignCopy(&data);
+
+ return S_OK;
+}
+
+/**
+ * Saves settings to the given machine node.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT GraphicsAdapter::i_saveSettings(settings::GraphicsAdapter &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ data = *mData.data();
+
+ return S_OK;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+void GraphicsAdapter::i_rollback()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mData.rollback();
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object (also
+ * for writing) if there is one.
+ */
+void GraphicsAdapter::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(mPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(mPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData.isBackedUp())
+ {
+ mData.commit();
+ if (mPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ mPeer->mData.attach(mData);
+ }
+ }
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ */
+void GraphicsAdapter::i_copyFrom(GraphicsAdapter *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ mData.assignCopy(aThat->mData);
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/GuestDebugControlImpl.cpp b/src/VBox/Main/src-server/GuestDebugControlImpl.cpp
new file mode 100644
index 00000000..a57ff273
--- /dev/null
+++ b/src/VBox/Main/src-server/GuestDebugControlImpl.cpp
@@ -0,0 +1,457 @@
+/* $Id: GuestDebugControlImpl.cpp $ */
+/** @file
+ * VirtualBox/GuestDebugControl COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_GUESTDEBUGCONTROL
+#include "GuestDebugControlImpl.h"
+#include "MachineImpl.h"
+#include "VirtualBoxImpl.h"
+#include "GuestOSTypeImpl.h"
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/cpp/utils.h>
+
+#include <VBox/settings.h>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+//////////////////////////////////////////////////////////////////////////////////
+//
+// GuestDebugControl private data definition
+//
+//////////////////////////////////////////////////////////////////////////////////
+
+struct GuestDebugControl::Data
+{
+ Data()
+ : pMachine(NULL)
+ { }
+
+ Machine * const pMachine;
+ const ComObjPtr<GuestDebugControl> pPeer;
+ Backupable<settings::Debugging> bd;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(GuestDebugControl)
+
+HRESULT GuestDebugControl::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void GuestDebugControl::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the Guest Debug Control object.
+ *
+ * @param aParent Handle of the parent object.
+ */
+HRESULT GuestDebugControl::init(Machine *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ /* m->pPeer is left null */
+
+ m->bd.allocate();
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the Guest Debug Control object given another Guest Debug Control object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT GuestDebugControl::init(Machine *aParent, GuestDebugControl *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ unconst(m->pPeer) = aThat;
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.share(aThat->m->bd);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the guest object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT GuestDebugControl::initCopy(Machine *aParent, GuestDebugControl *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ /* pPeer is left null */
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.attachCopy(aThat->m->bd);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void GuestDebugControl::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m->bd.free();
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pMachine) = NULL;
+
+ delete m;
+ m = NULL;
+}
+
+// IGuestDebugControl properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDebugControl::getDebugProvider(GuestDebugProvider_T *aDebugProvider)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aDebugProvider = m->bd->enmDbgProvider;
+ return S_OK;
+}
+
+
+HRESULT GuestDebugControl::setDebugProvider(GuestDebugProvider_T aDebugProvider)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->enmDbgProvider != aDebugProvider)
+ {
+ m->bd.backup();
+ m->bd->enmDbgProvider = aDebugProvider;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_GuestDebugControl);
+ mlock.release();
+
+ m->pMachine->i_onGuestDebugControlChange(this);
+ }
+
+ return S_OK;
+}
+
+
+HRESULT GuestDebugControl::getDebugIoProvider(GuestDebugIoProvider_T *aDebugIoProvider)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aDebugIoProvider = m->bd->enmIoProvider;
+ return S_OK;
+}
+
+HRESULT GuestDebugControl::setDebugIoProvider(GuestDebugIoProvider_T aDebugIoProvider)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->enmIoProvider != aDebugIoProvider)
+ {
+ m->bd.backup();
+ m->bd->enmIoProvider = aDebugIoProvider;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_GuestDebugControl);
+ mlock.release();
+
+ m->pMachine->i_onGuestDebugControlChange(this);
+ }
+
+ return S_OK;
+}
+
+HRESULT GuestDebugControl::getDebugAddress(com::Utf8Str &aAddress)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aAddress = m->bd->strAddress;
+
+ return S_OK;
+}
+
+
+HRESULT GuestDebugControl::setDebugAddress(const com::Utf8Str &aAddress)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aAddress != m->bd->strAddress)
+ {
+ m->bd.backup();
+ m->bd->strAddress = aAddress;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_GuestDebugControl);
+ mlock.release();
+
+ m->pMachine->i_onGuestDebugControlChange(this);
+ }
+
+ return S_OK;
+}
+
+HRESULT GuestDebugControl::getDebugPort(ULONG *aPort)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aPort = m->bd->ulPort;
+ return S_OK;
+}
+
+HRESULT GuestDebugControl::setDebugPort(ULONG aPort)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->ulPort != aPort)
+ {
+ m->bd.backup();
+ m->bd->ulPort = aPort;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_GuestDebugControl);
+ mlock.release();
+
+ m->pMachine->i_onGuestDebugControlChange(this);
+ }
+
+ return S_OK;
+}
+
+// public methods only for internal purposes
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Loads debug settings from the given settings.
+ * May be called once right after this object creation.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT GuestDebugControl::i_loadSettings(const settings::Debugging &data)
+{
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // simply copy
+ *m->bd.data() = data;
+
+ return S_OK;
+}
+
+/**
+ * Saves the debug settings to the given settings.
+ *
+ * Note that the given Port node is completely empty on input.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT GuestDebugControl::i_saveSettings(settings::Debugging &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // simply copy
+ data = *m->bd.data();
+
+ return S_OK;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+void GuestDebugControl::i_rollback()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.rollback();
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object (also
+ * for writing) if there is one.
+ */
+void GuestDebugControl::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (pPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ {
+ m->bd.commit();
+ if (m->pPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ m->pPeer->m->bd.attach(m->bd);
+ }
+ }
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ */
+void GuestDebugControl::i_copyFrom(GuestDebugControl *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ m->bd.assignCopy(aThat->m->bd);
+}
diff --git a/src/VBox/Main/src-server/GuestOSTypeImpl.cpp b/src/VBox/Main/src-server/GuestOSTypeImpl.cpp
new file mode 100644
index 00000000..727cee66
--- /dev/null
+++ b/src/VBox/Main/src-server/GuestOSTypeImpl.cpp
@@ -0,0 +1,470 @@
+/* $Id: GuestOSTypeImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_GUESTOSTYPE
+#include "GuestOSTypeImpl.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include <iprt/cpp/utils.h>
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+GuestOSType::GuestOSType()
+ : mOSType(VBOXOSTYPE_Unknown)
+ , mOSHint(VBOXOSHINT_NONE)
+ , mRAMSize(0)
+ , mCPUCount(1)
+ , mGraphicsControllerType(GraphicsControllerType_Null)
+ , mVRAMSize(0)
+ , mHDDSize(0)
+ , mNetworkAdapterType(NetworkAdapterType_Am79C973)
+ , mNumSerialEnabled(0)
+ , mDVDStorageControllerType(StorageControllerType_PIIX3)
+ , mDVDStorageBusType(StorageBus_IDE)
+ , mHDStorageControllerType(StorageControllerType_PIIX3)
+ , mHDStorageBusType(StorageBus_IDE)
+ , mChipsetType(ChipsetType_PIIX3)
+ , mIommuType(IommuType_None)
+ , mAudioControllerType(AudioControllerType_AC97)
+ , mAudioCodecType(AudioCodecType_STAC9700)
+{
+}
+
+GuestOSType::~GuestOSType()
+{
+}
+
+HRESULT GuestOSType::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void GuestOSType::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the guest OS type object.
+ *
+ * @returns COM result indicator
+ * @param ostype containing the following parts:
+ * @a aFamilyId os family short name string
+ * @a aFamilyDescription os family name string
+ * @a aId os short name string
+ * @a aDescription os name string
+ * @a aOSType global OS type ID
+ * @a aOSHint os configuration hint
+ * @a aRAMSize recommended RAM size in megabytes
+ * @a aVRAMSize recommended video memory size in megabytes
+ * @a aHDDSize recommended HDD size in bytes
+ */
+HRESULT GuestOSType::init(const Global::OSType &ostype)
+{
+ ComAssertRet(ostype.familyId && ostype.familyDescription && ostype.id && ostype.description, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mFamilyID) = ostype.familyId;
+ unconst(mFamilyDescription) = ostype.familyDescription;
+ unconst(mID) = ostype.id;
+ unconst(mDescription) = ostype.description;
+ unconst(mOSType) = ostype.osType;
+ unconst(mOSHint) = ostype.osHint;
+ unconst(mRAMSize) = ostype.recommendedRAM;
+ unconst(mCPUCount) = ostype.recommendedCPUCount;
+ unconst(mGraphicsControllerType) = ostype.graphicsControllerType;
+ unconst(mVRAMSize) = ostype.recommendedVRAM;
+ unconst(mHDDSize) = ostype.recommendedHDD;
+ unconst(mNetworkAdapterType) = ostype.networkAdapterType;
+ unconst(mNumSerialEnabled) = ostype.numSerialEnabled;
+ unconst(mDVDStorageControllerType) = ostype.dvdStorageControllerType;
+ unconst(mDVDStorageBusType) = ostype.dvdStorageBusType;
+ unconst(mHDStorageControllerType) = ostype.hdStorageControllerType;
+ unconst(mHDStorageBusType) = ostype.hdStorageBusType;
+ unconst(mChipsetType) = ostype.chipsetType;
+ unconst(mIommuType) = ostype.iommuType;
+ unconst(mAudioControllerType) = ostype.audioControllerType;
+ unconst(mAudioCodecType) = ostype.audioCodecType;
+
+ /* Confirm a successful initialization when it's the case */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void GuestOSType::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+}
+
+// IGuestOSType properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestOSType::getFamilyId(com::Utf8Str &aFamilyId)
+{
+ /* mFamilyID is constant during life time, no need to lock */
+ aFamilyId = mFamilyID;
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getFamilyDescription(com::Utf8Str &aFamilyDescription)
+{
+ /* mFamilyDescription is constant during life time, no need to lock */
+ aFamilyDescription = mFamilyDescription;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getId(com::Utf8Str &aId)
+{
+ /* mID is constant during life time, no need to lock */
+ aId = mID;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getDescription(com::Utf8Str &aDescription)
+{
+ /* mDescription is constant during life time, no need to lock */
+ aDescription = mDescription;
+
+ return S_OK;
+}
+
+HRESULT GuestOSType::getIs64Bit(BOOL *aIs64Bit)
+{
+ /* mIs64Bit is constant during life time, no need to lock */
+ *aIs64Bit = !!(mOSHint & VBOXOSHINT_64BIT);
+
+ return S_OK;
+}
+
+HRESULT GuestOSType::getRecommendedIOAPIC(BOOL *aRecommendedIOAPIC)
+{
+ /* mRecommendedIOAPIC is constant during life time, no need to lock */
+ *aRecommendedIOAPIC = !!(mOSHint & VBOXOSHINT_IOAPIC);
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedVirtEx(BOOL *aRecommendedVirtEx)
+{
+ /* mRecommendedVirtEx is constant during life time, no need to lock */
+ *aRecommendedVirtEx = !!(mOSHint & VBOXOSHINT_HWVIRTEX);
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedRAM(ULONG *aRAMSize)
+{
+ /* mRAMSize is constant during life time, no need to lock */
+ *aRAMSize = mRAMSize;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedGraphicsController(GraphicsControllerType_T *aRecommendedGraphicsController)
+{
+ /* mGraphicsController is constant during life time, no need to lock */
+ *aRecommendedGraphicsController = mGraphicsControllerType;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedVRAM(ULONG *aVRAMSize)
+{
+ /* mVRAMSize is constant during life time, no need to lock */
+ *aVRAMSize = mVRAMSize;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommended2DVideoAcceleration(BOOL *aRecommended2DVideoAcceleration)
+{
+ /* Constant during life time, no need to lock */
+ *aRecommended2DVideoAcceleration = !!(mOSHint & VBOXOSHINT_ACCEL2D);
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommended3DAcceleration(BOOL *aRecommended3DAcceleration)
+{
+ /* Constant during life time, no need to lock */
+ *aRecommended3DAcceleration = !!(mOSHint & VBOXOSHINT_ACCEL3D);
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedHDD(LONG64 *aHDDSize)
+{
+ /* mHDDSize is constant during life time, no need to lock */
+ *aHDDSize = (LONG64)mHDDSize;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getAdapterType(NetworkAdapterType_T *aNetworkAdapterType)
+{
+ /* mNetworkAdapterType is constant during life time, no need to lock */
+ *aNetworkAdapterType = mNetworkAdapterType;
+
+ return S_OK;
+}
+
+HRESULT GuestOSType::getRecommendedPAE(BOOL *aRecommendedPAE)
+{
+ /* recommended PAE is constant during life time, no need to lock */
+ *aRecommendedPAE = !!(mOSHint & VBOXOSHINT_PAE);
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedFirmware(FirmwareType_T *aFirmwareType)
+{
+ /* firmware type is constant during life time, no need to lock */
+ if (mOSHint & VBOXOSHINT_EFI)
+ *aFirmwareType = FirmwareType_EFI;
+ else
+ *aFirmwareType = FirmwareType_BIOS;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedDVDStorageController(StorageControllerType_T *aStorageControllerType)
+{
+ /* storage controller type is constant during life time, no need to lock */
+ *aStorageControllerType = mDVDStorageControllerType;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedDVDStorageBus(StorageBus_T *aStorageBusType)
+{
+ /* storage controller type is constant during life time, no need to lock */
+ *aStorageBusType = mDVDStorageBusType;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedHDStorageController(StorageControllerType_T *aStorageControllerType)
+{
+ /* storage controller type is constant during life time, no need to lock */
+ *aStorageControllerType = mHDStorageControllerType;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedHDStorageBus(StorageBus_T *aStorageBusType)
+{
+ /* storage controller type is constant during life time, no need to lock */
+ *aStorageBusType = mHDStorageBusType;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedUSBHID(BOOL *aRecommendedUSBHID)
+{
+ /* HID type is constant during life time, no need to lock */
+ *aRecommendedUSBHID = !!(mOSHint & VBOXOSHINT_USBHID);
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedHPET(BOOL *aRecommendedHPET)
+{
+ /* HPET recommendation is constant during life time, no need to lock */
+ *aRecommendedHPET = !!(mOSHint & VBOXOSHINT_HPET);
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedUSBTablet(BOOL *aRecommendedUSBTablet)
+{
+ /* HID type is constant during life time, no need to lock */
+ *aRecommendedUSBTablet = !!(mOSHint & VBOXOSHINT_USBTABLET);
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedRTCUseUTC(BOOL *aRecommendedRTCUseUTC)
+{
+ /* Value is constant during life time, no need to lock */
+ *aRecommendedRTCUseUTC = !!(mOSHint & VBOXOSHINT_RTCUTC);
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedChipset(ChipsetType_T *aChipsetType)
+{
+ /* chipset type is constant during life time, no need to lock */
+ *aChipsetType = mChipsetType;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedIommuType(IommuType_T *aIommuType)
+{
+ /* IOMMU type is constant during life time, no need to lock */
+ *aIommuType = mIommuType;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedAudioController(AudioControllerType_T *aAudioController)
+{
+ *aAudioController = mAudioControllerType;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedAudioCodec(AudioCodecType_T *aAudioCodec)
+{
+ *aAudioCodec = mAudioCodecType;
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedFloppy(BOOL *aRecommendedFloppy)
+{
+ /* Value is constant during life time, no need to lock */
+ *aRecommendedFloppy = !!(mOSHint & VBOXOSHINT_FLOPPY);
+
+ return S_OK;
+}
+
+
+HRESULT GuestOSType::getRecommendedUSB(BOOL *aRecommendedUSB)
+{
+ /* Value is constant during life time, no need to lock */
+ *aRecommendedUSB = !(mOSHint & VBOXOSHINT_NOUSB);
+
+ return S_OK;
+}
+
+HRESULT GuestOSType::getRecommendedUSB3(BOOL *aRecommendedUSB3)
+{
+ /* Value is constant during life time, no need to lock */
+ *aRecommendedUSB3 = !!(mOSHint & VBOXOSHINT_USB3);
+
+ return S_OK;
+}
+
+HRESULT GuestOSType::getRecommendedTFReset(BOOL *aRecommendedTFReset)
+{
+ /* recommended triple fault behavior is constant during life time, no need to lock */
+ *aRecommendedTFReset = !!(mOSHint & VBOXOSHINT_TFRESET);
+
+ return S_OK;
+}
+
+HRESULT GuestOSType::getRecommendedX2APIC(BOOL *aRecommendedX2APIC)
+{
+ /* mRecommendedX2APIC is constant during life time, no need to lock */
+ *aRecommendedX2APIC = !!(mOSHint & VBOXOSHINT_X2APIC);
+
+ return S_OK;
+}
+
+HRESULT GuestOSType::getRecommendedCPUCount(ULONG *aRecommendedCPUCount)
+{
+ /* mCPUCount is constant during life time, no need to lock */
+ *aRecommendedCPUCount = mCPUCount;
+
+ return S_OK;
+}
+
+HRESULT GuestOSType::getRecommendedTpmType(TpmType_T *aRecommendedTpmType)
+{
+ /* Value is constant during life time, no need to lock */
+ if (mOSHint & VBOXOSHINT_TPM2)
+ *aRecommendedTpmType = TpmType_v2_0;
+ else if (mOSHint & VBOXOSHINT_TPM)
+ *aRecommendedTpmType = TpmType_v1_2;
+ else
+ *aRecommendedTpmType = TpmType_None;
+
+ return S_OK;
+}
+
+HRESULT GuestOSType::getRecommendedSecureBoot(BOOL *aRecommendedSecureBoot)
+{
+ /* Value is constant during life time, no need to lock */
+ *aRecommendedSecureBoot = !!(mOSHint & VBOXOSHINT_EFI_SECUREBOOT);
+
+ return S_OK;
+}
+
+HRESULT GuestOSType::getRecommendedWDDMGraphics(BOOL *aRecommendedWDDMGraphics)
+{
+ /* Value is constant during life time, no need to lock */
+ *aRecommendedWDDMGraphics = !!(mOSHint & VBOXOSHINT_WDDM_GRAPHICS);
+
+ return S_OK;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/HostAudioDeviceImpl.cpp b/src/VBox/Main/src-server/HostAudioDeviceImpl.cpp
new file mode 100644
index 00000000..b820b410
--- /dev/null
+++ b/src/VBox/Main/src-server/HostAudioDeviceImpl.cpp
@@ -0,0 +1,99 @@
+/* $Id: HostAudioDeviceImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation - Host audio device implementation.
+ */
+
+/*
+ * Copyright (C) 2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOSTAUDIODEVICE
+#include "HostAudioDeviceImpl.h"
+#include "VirtualBoxImpl.h"
+
+#include <iprt/cpp/utils.h>
+
+#include <VBox/settings.h>
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+
+// constructor / destructor
+////////////////////////////////////////////////////////////////////////////////
+
+HostAudioDevice::HostAudioDevice()
+{
+}
+
+HostAudioDevice::~HostAudioDevice()
+{
+}
+
+HRESULT HostAudioDevice::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void HostAudioDevice::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+
+// public initializer/uninitializer for internal purposes only
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the audio device object.
+ *
+ * @returns HRESULT
+ */
+HRESULT HostAudioDevice::init(void)
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void HostAudioDevice::uninit(void)
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+}
+
+
+// IHostAudioDevice properties
+////////////////////////////////////////////////////////////////////////////////
diff --git a/src/VBox/Main/src-server/HostDnsService.cpp b/src/VBox/Main/src-server/HostDnsService.cpp
new file mode 100644
index 00000000..bd91ca41
--- /dev/null
+++ b/src/VBox/Main/src-server/HostDnsService.cpp
@@ -0,0 +1,440 @@
+/* $Id: HostDnsService.cpp $ */
+/** @file
+ * Base class for Host DNS & Co services.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOST
+#include <VBox/com/array.h>
+#include <VBox/com/ptr.h>
+#include <VBox/com/string.h>
+
+#include <iprt/cpp/utils.h>
+
+#include "LoggingNew.h"
+#include "VirtualBoxImpl.h"
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+#include <iprt/critsect.h>
+
+#include <algorithm>
+#include <set>
+#include <iprt/sanitized/string>
+#include "HostDnsService.h"
+
+
+
+static void dumpHostDnsStrVector(const std::string &prefix, const std::vector<std::string> &v)
+{
+ int i = 1;
+ for (std::vector<std::string>::const_iterator it = v.begin();
+ it != v.end();
+ ++it, ++i)
+ LogRel((" %s %d: %s\n", prefix.c_str(), i, it->c_str()));
+ if (v.empty())
+ LogRel((" no %s entries\n", prefix.c_str()));
+}
+
+static void dumpHostDnsInformation(const HostDnsInformation &info)
+{
+ dumpHostDnsStrVector("server", info.servers);
+
+ if (!info.domain.empty())
+ LogRel((" domain: %s\n", info.domain.c_str()));
+ else
+ LogRel((" no domain set\n"));
+
+ dumpHostDnsStrVector("search string", info.searchList);
+}
+
+bool HostDnsInformation::equals(const HostDnsInformation &info, uint32_t fLaxComparison) const
+{
+ bool fSameServers;
+ if ((fLaxComparison & IGNORE_SERVER_ORDER) == 0)
+ fSameServers = (servers == info.servers);
+ else
+ {
+ std::set<std::string> l(servers.begin(), servers.end());
+ std::set<std::string> r(info.servers.begin(), info.servers.end());
+
+ fSameServers = (l == r);
+ }
+
+ bool fSameDomain, fSameSearchList;
+ if ((fLaxComparison & IGNORE_SUFFIXES) == 0)
+ {
+ fSameDomain = (domain == info.domain);
+ fSameSearchList = (searchList == info.searchList);
+ }
+ else
+ fSameDomain = fSameSearchList = true;
+
+ return fSameServers && fSameDomain && fSameSearchList;
+}
+
+DECLINLINE(void) detachVectorOfString(const std::vector<std::string>& v, std::vector<com::Utf8Str> &aArray)
+{
+ aArray.resize(v.size());
+ size_t i = 0;
+ for (std::vector<std::string>::const_iterator it = v.begin(); it != v.end(); ++it, ++i)
+ aArray[i] = Utf8Str(it->c_str()); /** @todo r=bird: *it isn't necessarily UTF-8 clean!!
+ * On darwin we do silly shit like using CFStringGetSystemEncoding()
+ * that may be UTF-8 but doesn't need to be.
+ *
+ * Why on earth are we using std::string here anyway?
+ */
+}
+
+struct HostDnsServiceBase::Data
+{
+ Data(bool aThreaded)
+ : pProxy(NULL)
+ , fThreaded(aThreaded)
+ , hMonitorThreadEvent(NIL_RTSEMEVENT)
+ , hMonitorThread(NIL_RTTHREAD)
+ {}
+
+ /** Weak pointer to parent proxy object. */
+ HostDnsMonitorProxy *pProxy;
+ /** Whether the DNS monitor implementation has a dedicated monitoring thread. Optional. */
+ const bool fThreaded;
+ /** Event for the monitor thread, if any. */
+ RTSEMEVENT hMonitorThreadEvent;
+ /** Handle of the monitor thread, if any. */
+ RTTHREAD hMonitorThread;
+ /** Generic host DNS information. */
+ HostDnsInformation info;
+};
+
+struct HostDnsMonitorProxy::Data
+{
+ Data(HostDnsServiceBase *aMonitor, VirtualBox *aParent)
+ : pVirtualBox(aParent)
+ , pMonitorImpl(aMonitor)
+ , uLastExtraDataPoll(0)
+ , fLaxComparison(0)
+ , info()
+ {}
+
+ VirtualBox *pVirtualBox;
+ HostDnsServiceBase *pMonitorImpl;
+
+ uint64_t uLastExtraDataPoll;
+ uint32_t fLaxComparison;
+ HostDnsInformation info;
+};
+
+
+HostDnsServiceBase::HostDnsServiceBase(bool fThreaded)
+ : m(NULL)
+{
+ m = new HostDnsServiceBase::Data(fThreaded);
+}
+
+HostDnsServiceBase::~HostDnsServiceBase()
+{
+ if (m)
+ {
+ delete m;
+ m = NULL;
+ }
+}
+
+/* static */
+HostDnsServiceBase *HostDnsServiceBase::createHostDnsMonitor(void)
+{
+ HostDnsServiceBase *pMonitor = NULL;
+
+#if defined (RT_OS_DARWIN)
+ pMonitor = new HostDnsServiceDarwin();
+#elif defined(RT_OS_WINDOWS)
+ pMonitor = new HostDnsServiceWin();
+#elif defined(RT_OS_LINUX)
+ pMonitor = new HostDnsServiceLinux();
+#elif defined(RT_OS_SOLARIS)
+ pMonitor = new HostDnsServiceSolaris();
+#elif defined(RT_OS_FREEBSD)
+ pMonitor = new HostDnsServiceFreebsd();
+#elif defined(RT_OS_OS2)
+ pMonitor = new HostDnsServiceOs2();
+#else
+ pMonitor = new HostDnsServiceBase();
+#endif
+
+ return pMonitor;
+}
+
+HRESULT HostDnsServiceBase::init(HostDnsMonitorProxy *pProxy)
+{
+ LogRel(("HostDnsMonitor: initializing\n"));
+
+ AssertPtrReturn(pProxy, E_POINTER);
+ m->pProxy = pProxy;
+
+ if (m->fThreaded)
+ {
+ LogRel2(("HostDnsMonitor: starting thread ...\n"));
+
+ int rc = RTSemEventCreate(&m->hMonitorThreadEvent);
+ AssertRCReturn(rc, E_FAIL);
+
+ rc = RTThreadCreate(&m->hMonitorThread,
+ HostDnsServiceBase::threadMonitorProc,
+ this, 128 * _1K, RTTHREADTYPE_IO,
+ RTTHREADFLAGS_WAITABLE, "dns-monitor");
+ AssertRCReturn(rc, E_FAIL);
+
+ RTSemEventWait(m->hMonitorThreadEvent, RT_INDEFINITE_WAIT);
+
+ LogRel2(("HostDnsMonitor: thread started\n"));
+ }
+
+ return S_OK;
+}
+
+void HostDnsServiceBase::uninit(void)
+{
+ LogRel(("HostDnsMonitor: shutting down ...\n"));
+
+ if (m->fThreaded)
+ {
+ LogRel2(("HostDnsMonitor: waiting for thread ...\n"));
+
+ const RTMSINTERVAL uTimeoutMs = 30 * 1000; /* 30s */
+
+ monitorThreadShutdown(uTimeoutMs);
+
+ int rc = RTThreadWait(m->hMonitorThread, uTimeoutMs, NULL);
+ if (RT_FAILURE(rc))
+ LogRel(("HostDnsMonitor: waiting for thread failed with rc=%Rrc\n", rc));
+
+ if (m->hMonitorThreadEvent != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(m->hMonitorThreadEvent);
+ m->hMonitorThreadEvent = NIL_RTSEMEVENT;
+ }
+ }
+
+ LogRel(("HostDnsMonitor: shut down\n"));
+}
+
+void HostDnsServiceBase::setInfo(const HostDnsInformation &info)
+{
+ if (m->pProxy != NULL)
+ m->pProxy->notify(info);
+}
+
+void HostDnsMonitorProxy::pollGlobalExtraData(void)
+{
+ VirtualBox *pVirtualBox = m->pVirtualBox;
+ if (RT_UNLIKELY(pVirtualBox == NULL))
+ return;
+
+ uint64_t uNow = RTTimeNanoTS();
+ if (uNow - m->uLastExtraDataPoll >= RT_NS_30SEC || m->uLastExtraDataPoll == 0)
+ {
+ m->uLastExtraDataPoll = uNow;
+
+ /*
+ * Should we ignore the order of DNS servers?
+ */
+ const com::Bstr bstrHostDNSOrderIgnoreKey("VBoxInternal2/HostDNSOrderIgnore");
+ com::Bstr bstrHostDNSOrderIgnore;
+ pVirtualBox->GetExtraData(bstrHostDNSOrderIgnoreKey.raw(),
+ bstrHostDNSOrderIgnore.asOutParam());
+ uint32_t fDNSOrderIgnore = 0;
+ if (bstrHostDNSOrderIgnore.isNotEmpty())
+ {
+ if (bstrHostDNSOrderIgnore != "0")
+ fDNSOrderIgnore = HostDnsInformation::IGNORE_SERVER_ORDER;
+ }
+
+ if (fDNSOrderIgnore != (m->fLaxComparison & HostDnsInformation::IGNORE_SERVER_ORDER))
+ {
+
+ m->fLaxComparison ^= HostDnsInformation::IGNORE_SERVER_ORDER;
+ LogRel(("HostDnsMonitor: %ls=%ls\n",
+ bstrHostDNSOrderIgnoreKey.raw(),
+ bstrHostDNSOrderIgnore.raw()));
+ }
+
+ /*
+ * Should we ignore changes to the domain name or the search list?
+ */
+ const com::Bstr bstrHostDNSSuffixesIgnoreKey("VBoxInternal2/HostDNSSuffixesIgnore");
+ com::Bstr bstrHostDNSSuffixesIgnore;
+ pVirtualBox->GetExtraData(bstrHostDNSSuffixesIgnoreKey.raw(),
+ bstrHostDNSSuffixesIgnore.asOutParam());
+ uint32_t fDNSSuffixesIgnore = 0;
+ if (bstrHostDNSSuffixesIgnore.isNotEmpty())
+ {
+ if (bstrHostDNSSuffixesIgnore != "0")
+ fDNSSuffixesIgnore = HostDnsInformation::IGNORE_SUFFIXES;
+ }
+
+ if (fDNSSuffixesIgnore != (m->fLaxComparison & HostDnsInformation::IGNORE_SUFFIXES))
+ {
+
+ m->fLaxComparison ^= HostDnsInformation::IGNORE_SUFFIXES;
+ LogRel(("HostDnsMonitor: %ls=%ls\n",
+ bstrHostDNSSuffixesIgnoreKey.raw(),
+ bstrHostDNSSuffixesIgnore.raw()));
+ }
+ }
+}
+
+void HostDnsServiceBase::onMonitorThreadInitDone(void)
+{
+ if (!m->fThreaded) /* If non-threaded, bail out, nothing to do here. */
+ return;
+
+ RTSemEventSignal(m->hMonitorThreadEvent);
+}
+
+DECLCALLBACK(int) HostDnsServiceBase::threadMonitorProc(RTTHREAD, void *pvUser)
+{
+ HostDnsServiceBase *pThis = static_cast<HostDnsServiceBase *>(pvUser);
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ return pThis->monitorThreadProc();
+}
+
+/* HostDnsMonitorProxy */
+HostDnsMonitorProxy::HostDnsMonitorProxy()
+ : m(NULL)
+{
+}
+
+HostDnsMonitorProxy::~HostDnsMonitorProxy()
+{
+ uninit();
+}
+
+HRESULT HostDnsMonitorProxy::init(VirtualBox* aParent)
+{
+ AssertMsgReturn(m == NULL, ("DNS monitor proxy already initialized\n"), E_FAIL);
+
+ HostDnsServiceBase *pMonitorImpl = HostDnsServiceBase::createHostDnsMonitor();
+ AssertPtrReturn(pMonitorImpl, E_OUTOFMEMORY);
+
+ Assert(m == NULL); /* Paranoia. */
+ m = new HostDnsMonitorProxy::Data(pMonitorImpl, aParent);
+ AssertPtrReturn(m, E_OUTOFMEMORY);
+
+ return m->pMonitorImpl->init(this);
+}
+
+void HostDnsMonitorProxy::uninit(void)
+{
+ if (m)
+ {
+ if (m->pMonitorImpl)
+ {
+ m->pMonitorImpl->uninit();
+
+ delete m->pMonitorImpl;
+ m->pMonitorImpl = NULL;
+ }
+
+ delete m;
+ m = NULL;
+ }
+}
+
+void HostDnsMonitorProxy::notify(const HostDnsInformation &info)
+{
+ const bool fNotify = updateInfo(info);
+ if (fNotify)
+ m->pVirtualBox->i_onHostNameResolutionConfigurationChange();
+}
+
+HRESULT HostDnsMonitorProxy::GetNameServers(std::vector<com::Utf8Str> &aNameServers)
+{
+ AssertReturn(m != NULL, E_FAIL);
+ RTCLock grab(m_LockMtx);
+
+ LogRel(("HostDnsMonitorProxy::GetNameServers:\n"));
+ dumpHostDnsStrVector("name server", m->info.servers);
+
+ detachVectorOfString(m->info.servers, aNameServers);
+
+ return S_OK;
+}
+
+HRESULT HostDnsMonitorProxy::GetDomainName(com::Utf8Str *pDomainName)
+{
+ AssertReturn(m != NULL, E_FAIL);
+ RTCLock grab(m_LockMtx);
+
+ LogRel(("HostDnsMonitorProxy::GetDomainName: %s\n",
+ m->info.domain.empty() ? "no domain set" : m->info.domain.c_str()));
+
+ *pDomainName = m->info.domain.c_str();
+
+ return S_OK;
+}
+
+HRESULT HostDnsMonitorProxy::GetSearchStrings(std::vector<com::Utf8Str> &aSearchStrings)
+{
+ AssertReturn(m != NULL, E_FAIL);
+ RTCLock grab(m_LockMtx);
+
+ LogRel(("HostDnsMonitorProxy::GetSearchStrings:\n"));
+ dumpHostDnsStrVector("search string", m->info.searchList);
+
+ detachVectorOfString(m->info.searchList, aSearchStrings);
+
+ return S_OK;
+}
+
+bool HostDnsMonitorProxy::updateInfo(const HostDnsInformation &info)
+{
+ LogRel(("HostDnsMonitor: updating information\n"));
+ RTCLock grab(m_LockMtx);
+
+ if (info.equals(m->info))
+ {
+ LogRel(("HostDnsMonitor: unchanged\n"));
+ return false;
+ }
+
+ pollGlobalExtraData();
+
+ LogRel(("HostDnsMonitor: old information\n"));
+ dumpHostDnsInformation(m->info);
+ LogRel(("HostDnsMonitor: new information\n"));
+ dumpHostDnsInformation(info);
+
+ bool fIgnore = m->fLaxComparison != 0 && info.equals(m->info, m->fLaxComparison);
+ m->info = info;
+
+ if (fIgnore)
+ {
+ LogRel(("HostDnsMonitor: lax comparison %#x, not notifying\n", m->fLaxComparison));
+ return false;
+ }
+
+ return true;
+}
+
diff --git a/src/VBox/Main/src-server/HostDnsService.h b/src/VBox/Main/src-server/HostDnsService.h
new file mode 100644
index 00000000..42bed701
--- /dev/null
+++ b/src/VBox/Main/src-server/HostDnsService.h
@@ -0,0 +1,302 @@
+/* $Id: HostDnsService.h $ */
+/** @file
+ * Host DNS listener.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SRC_src_server_HostDnsService_h
+#define MAIN_INCLUDED_SRC_src_server_HostDnsService_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+#include "VirtualBoxBase.h"
+
+#include <iprt/err.h> /* VERR_IGNORED */
+#include <iprt/cpp/lock.h>
+
+#include <list>
+#include <iprt/sanitized/string>
+#include <vector>
+
+typedef std::list<com::Utf8Str> Utf8StrList;
+typedef Utf8StrList::iterator Utf8StrListIterator;
+
+class HostDnsMonitorProxy;
+typedef const HostDnsMonitorProxy *PCHostDnsMonitorProxy;
+
+class HostDnsInformation
+{
+public:
+ static const uint32_t IGNORE_SERVER_ORDER = RT_BIT_32(0);
+ static const uint32_t IGNORE_SUFFIXES = RT_BIT_32(1);
+
+public:
+ /** @todo r=bird: Why on earth are we using std::string and not Utf8Str? */
+ std::vector<std::string> servers;
+ std::string domain;
+ std::vector<std::string> searchList;
+ bool equals(const HostDnsInformation &, uint32_t fLaxComparison = 0) const;
+};
+
+/**
+ * Base class for host DNS service implementations.
+ * This class supposed to be a real DNS monitor object as a singleton,
+ * so it lifecycle starts and ends together with VBoxSVC.
+ */
+class HostDnsServiceBase
+{
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(HostDnsServiceBase);
+
+public:
+
+ static HostDnsServiceBase *createHostDnsMonitor(void);
+
+public:
+
+ /* @note: method will wait till client call
+ HostDnsService::monitorThreadInitializationDone() */
+ virtual HRESULT init(HostDnsMonitorProxy *pProxy);
+ virtual void uninit(void);
+
+ virtual ~HostDnsServiceBase();
+
+protected:
+
+ explicit HostDnsServiceBase(bool fThreaded = false);
+
+ void setInfo(const HostDnsInformation &);
+
+ /* this function used only if HostDnsMonitor::HostDnsMonitor(true) */
+ void onMonitorThreadInitDone();
+
+public:
+
+ virtual int monitorThreadShutdown(RTMSINTERVAL uTimeoutMs)
+ {
+ RT_NOREF(uTimeoutMs); AssertFailed(); return VERR_NOT_IMPLEMENTED;
+ }
+
+ virtual int monitorThreadProc(void) { AssertFailed(); return VERR_NOT_IMPLEMENTED; }
+
+private:
+
+ static DECLCALLBACK(int) threadMonitorProc(RTTHREAD, void *);
+
+protected:
+
+ mutable RTCLockMtx m_LockMtx;
+
+public: /** @todo r=andy Why is this public? */
+
+ struct Data;
+ Data *m;
+};
+
+/**
+ * This class supposed to be a proxy for events on changing Host Name Resolving configurations.
+ */
+class HostDnsMonitorProxy
+{
+public:
+
+ HostDnsMonitorProxy();
+ virtual ~HostDnsMonitorProxy();
+
+public:
+
+ HRESULT init(VirtualBox *virtualbox);
+ void uninit(void);
+ void notify(const HostDnsInformation &info);
+
+ HRESULT GetNameServers(std::vector<com::Utf8Str> &aNameServers);
+ HRESULT GetDomainName(com::Utf8Str *pDomainName);
+ HRESULT GetSearchStrings(std::vector<com::Utf8Str> &aSearchStrings);
+
+private:
+
+ void pollGlobalExtraData(void);
+ bool updateInfo(const HostDnsInformation &info);
+
+private:
+
+ mutable RTCLockMtx m_LockMtx;
+
+ struct Data;
+ Data *m;
+};
+
+# if defined(RT_OS_DARWIN) || defined(DOXYGEN_RUNNING)
+class HostDnsServiceDarwin : public HostDnsServiceBase
+{
+public:
+
+ HostDnsServiceDarwin();
+ virtual ~HostDnsServiceDarwin();
+
+public:
+
+ HRESULT init(HostDnsMonitorProxy *pProxy);
+ void uninit(void);
+
+protected:
+
+ int monitorThreadShutdown(RTMSINTERVAL uTimeoutMs);
+ int monitorThreadProc(void);
+
+private:
+
+ int updateInfo(void);
+ static void hostDnsServiceStoreCallback(void *store, void *arrayRef, void *info);
+ struct Data;
+ Data *m;
+};
+# endif
+# if defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING)
+class HostDnsServiceWin : public HostDnsServiceBase
+{
+public:
+ HostDnsServiceWin();
+ virtual ~HostDnsServiceWin();
+
+public:
+
+ HRESULT init(HostDnsMonitorProxy *pProxy);
+ void uninit(void);
+
+protected:
+
+ int monitorThreadShutdown(RTMSINTERVAL uTimeoutMs);
+ int monitorThreadProc(void);
+
+private:
+
+ HRESULT updateInfo(void);
+
+private:
+
+ struct Data;
+ Data *m;
+};
+# endif
+# if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) || defined(RT_OS_OS2) || defined(RT_OS_FREEBSD) \
+ || defined(DOXYGEN_RUNNING)
+class HostDnsServiceResolvConf: public HostDnsServiceBase
+{
+public:
+
+ explicit HostDnsServiceResolvConf(bool fThreaded = false) : HostDnsServiceBase(fThreaded), m(NULL) {}
+ virtual ~HostDnsServiceResolvConf();
+
+public:
+
+ HRESULT init(HostDnsMonitorProxy *pProxy, const char *aResolvConfFileName);
+ void uninit(void);
+
+ const std::string& getResolvConf(void) const;
+
+protected:
+
+ HRESULT readResolvConf(void);
+
+protected:
+
+ struct Data;
+ Data *m;
+};
+# if defined(RT_OS_SOLARIS) || defined(DOXYGEN_RUNNING)
+/**
+ * XXX: https://blogs.oracle.com/praks/entry/file_events_notification
+ */
+class HostDnsServiceSolaris : public HostDnsServiceResolvConf
+{
+public:
+
+ HostDnsServiceSolaris() {}
+ virtual ~HostDnsServiceSolaris() {}
+
+public:
+
+ virtual HRESULT init(HostDnsMonitorProxy *pProxy) {
+ return HostDnsServiceResolvConf::init(pProxy, "/etc/resolv.conf");
+ }
+};
+
+# endif
+# if defined(RT_OS_LINUX) || defined(DOXYGEN_RUNNING)
+class HostDnsServiceLinux : public HostDnsServiceResolvConf
+{
+public:
+
+ HostDnsServiceLinux() : HostDnsServiceResolvConf(true) {}
+ virtual ~HostDnsServiceLinux();
+
+public:
+
+ HRESULT init(HostDnsMonitorProxy *pProxy);
+
+protected:
+
+ int monitorThreadShutdown(RTMSINTERVAL uTimeoutMs);
+ int monitorThreadProc(void);
+};
+
+# endif
+# if defined(RT_OS_FREEBSD) || defined(DOXYGEN_RUNNING)
+class HostDnsServiceFreebsd: public HostDnsServiceResolvConf
+{
+public:
+
+ HostDnsServiceFreebsd(){}
+ virtual ~HostDnsServiceFreebsd() {}
+
+public:
+
+ virtual HRESULT init(HostDnsMonitorProxy *pProxy)
+ {
+ return HostDnsServiceResolvConf::init(pProxy, "/etc/resolv.conf");
+ }
+};
+
+# endif
+# if defined(RT_OS_OS2) || defined(DOXYGEN_RUNNING)
+class HostDnsServiceOs2 : public HostDnsServiceResolvConf
+{
+public:
+
+ HostDnsServiceOs2() {}
+ virtual ~HostDnsServiceOs2() {}
+
+public:
+
+ /* XXX: \\MPTN\\ETC should be taken from environment variable ETC */
+ virtual HRESULT init(HostDnsMonitorProxy *pProxy)
+ {
+ return HostDnsServiceResolvConf::init(pProxy, "\\MPTN\\ETC\\RESOLV2");
+ }
+};
+
+# endif
+# endif
+
+#endif /* !MAIN_INCLUDED_SRC_src_server_HostDnsService_h */
diff --git a/src/VBox/Main/src-server/HostDnsServiceResolvConf.cpp b/src/VBox/Main/src-server/HostDnsServiceResolvConf.cpp
new file mode 100644
index 00000000..7d2f2b59
--- /dev/null
+++ b/src/VBox/Main/src-server/HostDnsServiceResolvConf.cpp
@@ -0,0 +1,131 @@
+/* $Id: HostDnsServiceResolvConf.cpp $ */
+/** @file
+ * Base class for Host DNS & Co services.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/* -*- indent-tabs-mode: nil; -*- */
+#include <VBox/com/string.h>
+#include <VBox/com/ptr.h>
+
+
+#ifdef RT_OS_OS2
+# include <sys/socket.h>
+typedef int socklen_t;
+#endif
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/critsect.h>
+
+#include <VBox/log.h>
+
+#include <iprt/sanitized/string>
+
+#include "HostDnsService.h"
+#include "../../Devices/Network/slirp/resolv_conf_parser.h"
+
+
+struct HostDnsServiceResolvConf::Data
+{
+ Data(const char *fileName)
+ : resolvConfFilename(fileName)
+ {
+ };
+
+ std::string resolvConfFilename;
+};
+
+HostDnsServiceResolvConf::~HostDnsServiceResolvConf()
+{
+ if (m)
+ {
+ delete m;
+ m = NULL;
+ }
+}
+
+HRESULT HostDnsServiceResolvConf::init(HostDnsMonitorProxy *pProxy, const char *aResolvConfFileName)
+{
+ HRESULT hr = HostDnsServiceBase::init(pProxy);
+ AssertComRCReturn(hr, hr);
+
+ m = new Data(aResolvConfFileName);
+ AssertPtrReturn(m, E_OUTOFMEMORY);
+
+ return readResolvConf();
+}
+
+void HostDnsServiceResolvConf::uninit(void)
+{
+ if (m)
+ {
+ delete m;
+ m = NULL;
+ }
+
+ HostDnsServiceBase::uninit();
+}
+
+const std::string& HostDnsServiceResolvConf::getResolvConf(void) const
+{
+ return m->resolvConfFilename;
+}
+
+HRESULT HostDnsServiceResolvConf::readResolvConf(void)
+{
+ struct rcp_state st;
+
+ st.rcps_flags = RCPSF_NO_STR2IPCONV;
+ int rc = rcp_parse(&st, m->resolvConfFilename.c_str());
+ if (rc == -1)
+ return S_OK;
+
+ HostDnsInformation info;
+ for (unsigned i = 0; i != st.rcps_num_nameserver; ++i)
+ {
+ AssertBreak(st.rcps_str_nameserver[i]);
+ info.servers.push_back(st.rcps_str_nameserver[i]);
+ }
+
+ if (st.rcps_domain)
+ info.domain = st.rcps_domain;
+
+ for (unsigned i = 0; i != st.rcps_num_searchlist; ++i)
+ {
+ AssertBreak(st.rcps_searchlist[i]);
+ info.searchList.push_back(st.rcps_searchlist[i]);
+ }
+ setInfo(info);
+
+ return S_OK;
+}
+
diff --git a/src/VBox/Main/src-server/HostDriveImpl.cpp b/src/VBox/Main/src-server/HostDriveImpl.cpp
new file mode 100644
index 00000000..d6a3c993
--- /dev/null
+++ b/src/VBox/Main/src-server/HostDriveImpl.cpp
@@ -0,0 +1,273 @@
+/* $Id: HostDriveImpl.cpp $ */
+/** @file
+ * VirtualBox Main - IHostDrive implementation, VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOSTDRIVE
+#include "Global.h"
+#include "HostDriveImpl.h"
+#include "HostDrivePartitionImpl.h"
+#include "LoggingNew.h"
+#include "VirtualBoxImpl.h"
+
+#include <iprt/dvm.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/vfs.h>
+#include <VBox/com/Guid.h>
+
+
+/*
+ * HostDrive implementation.
+ */
+DEFINE_EMPTY_CTOR_DTOR(HostDrive)
+
+HRESULT HostDrive::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void HostDrive::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+/**
+ * Initializes the instance.
+ */
+HRESULT HostDrive::initFromPathAndModel(const com::Utf8Str &drivePath, const com::Utf8Str &driveModel)
+{
+ LogFlowThisFunc(("\n"));
+
+ AssertReturn(!drivePath.isEmpty(), E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m.partitioningType = PartitioningType_MBR;
+ m.drivePath = drivePath;
+ m.model = driveModel;
+ m.partitions.clear();
+
+ /*
+ * Try open the drive so we can extract futher details,
+ * like the size, sector size and partitions.
+ */
+ HRESULT hrc = E_FAIL;
+ RTFILE hRawFile = NIL_RTFILE;
+ int vrc = RTFileOpen(&hRawFile, drivePath.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTFileQuerySize(hRawFile, &m.cbDisk);
+ int vrc2 = RTFileQuerySectorSize(hRawFile, &m.cbSector);
+ if (RT_FAILURE(vrc2))
+ vrc = vrc2;
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Hand it to DVM.
+ */
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ vrc = RTVfsFileFromRTFile(hRawFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE /*fOpen*/,
+ true /*fLeaveOpen*/, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ RTDVM hVolMgr = NIL_RTDVM;
+ vrc = RTDvmCreate(&hVolMgr, hVfsFile, m.cbSector, 0 /*fFlags*/);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTDvmMapOpen(hVolMgr);
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = S_OK;
+
+ /*
+ * Get details.
+ */
+ switch (RTDvmMapGetFormatType(hVolMgr))
+ {
+ case RTDVMFORMATTYPE_GPT:
+ m.partitioningType = PartitioningType_GPT;
+ break;
+ case RTDVMFORMATTYPE_MBR:
+ m.partitioningType = PartitioningType_MBR;
+ break;
+ case RTDVMFORMATTYPE_BSD_LABEL:
+ AssertMsgFailed(("TODO\n"));
+ break;
+ default:
+ AssertFailed();
+ }
+
+ RTUUID Uuid = RTUUID_INITIALIZE_NULL;
+ if (RT_SUCCESS(RTDvmMapQueryDiskUuid(hVolMgr, &Uuid)))
+ m.uuid = Uuid;
+
+ /*
+ * Enumerate volumes and tuck them into the partitions list..
+ */
+ uint32_t const cVolumes = RTDvmMapGetValidVolumes(hVolMgr);
+ RTDVMVOLUME hVol = NIL_RTDVMVOLUME;
+ for (uint32_t i = 0; i < cVolumes; i++)
+ {
+ /* Enumeration cruft: */
+ RTDVMVOLUME hVolNext = NIL_RTDVMVOLUME;
+ if (i == 0)
+ vrc = RTDvmMapQueryFirstVolume(hVolMgr, &hVolNext);
+ else
+ vrc = RTDvmMapQueryNextVolume(hVolMgr, hVol, &hVolNext);
+ AssertRCBreakStmt(vrc, hrc = Global::vboxStatusCodeToCOM(vrc));
+
+ uint32_t cRefs = RTDvmVolumeRelease(hVol);
+ Assert(cRefs != UINT32_MAX); RT_NOREF(cRefs);
+ hVol = hVolNext;
+
+ /* Instantiate a new partition object and add it to the list: */
+ ComObjPtr<HostDrivePartition> ptrHostPartition;
+ hrc = ptrHostPartition.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = ptrHostPartition->initFromDvmVol(hVol);
+ if (SUCCEEDED(hrc))
+ try
+ {
+ m.partitions.push_back(ptrHostPartition);
+ }
+ catch (std::bad_alloc &)
+ {
+ AssertFailedBreakStmt(hrc = E_OUTOFMEMORY);
+ }
+ }
+ RTDvmVolumeRelease(hVol);
+ }
+ else
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+ RTDvmRelease(hVolMgr);
+ }
+ else
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+ }
+ else /* VERR_IO_NOT_READ / STATUS_NO_MEDIA_IN_DEVICE is likely for card readers on windows. */
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+ RTFileClose(hRawFile);
+ }
+ else
+ {
+ /*
+ * We don't use the Global::vboxStatusCodeToCOM(vrc) here
+ * because RTFileOpen can return some error which causes
+ * the assertion and breaks original idea of returning
+ * the object in the limited state.
+ */
+ if ( vrc == VERR_RESOURCE_BUSY
+ || vrc == VERR_ACCESS_DENIED)
+ hrc = E_ACCESSDENIED;
+ else
+ hrc = VBOX_E_IPRT_ERROR;
+ }
+
+ /* Confirm a successful initialization */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setLimited(hrc);
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void HostDrive::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m.drivePath.setNull();
+ m.partitions.clear();
+}
+
+
+/*********************************************************************************************************************************
+* IHostDrive properties *
+*********************************************************************************************************************************/
+
+HRESULT HostDrive::getPartitioningType(PartitioningType_T *aPartitioningType)
+{
+ *aPartitioningType = m.partitioningType;
+ return S_OK;
+}
+
+HRESULT HostDrive::getDrivePath(com::Utf8Str &aDrivePath)
+{
+ aDrivePath = m.drivePath;
+ return S_OK;
+}
+
+HRESULT HostDrive::getUuid(com::Guid &aUuid)
+{
+ aUuid = m.uuid;
+ return S_OK;
+}
+
+HRESULT HostDrive::getSectorSize(ULONG *aSectorSize)
+{
+ *aSectorSize = m.cbSector;
+ return S_OK;
+}
+
+HRESULT HostDrive::getSize(LONG64 *aSize)
+{
+ *aSize = (LONG64)m.cbDisk;
+ if (*aSize < 0)
+ *aSize = INT64_MAX;
+ return S_OK;
+}
+
+HRESULT HostDrive::getModel(com::Utf8Str &aModel)
+{
+ return aModel.assignEx(m.model);
+}
+
+HRESULT HostDrive::getPartitions(std::vector<ComPtr<IHostDrivePartition> > &aPartitions)
+{
+ aPartitions = m.partitions;
+ return S_OK;
+}
+
+
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/HostDrivePartitionImpl.cpp b/src/VBox/Main/src-server/HostDrivePartitionImpl.cpp
new file mode 100644
index 00000000..a5a40333
--- /dev/null
+++ b/src/VBox/Main/src-server/HostDrivePartitionImpl.cpp
@@ -0,0 +1,381 @@
+/* $Id: HostDrivePartitionImpl.cpp $ */
+/** @file
+ * VirtualBox Main - IHostDrivePartition implementation, VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOSTDRIVEPARTITION
+#include "HostDrivePartitionImpl.h"
+#include "LoggingNew.h"
+
+#include <iprt/errcore.h>
+
+/*
+ * HostDrivePartition implementation.
+ */
+DEFINE_EMPTY_CTOR_DTOR(HostDrivePartition)
+
+HRESULT HostDrivePartition::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void HostDrivePartition::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+/*
+ * Initializes the instance.
+ */
+HRESULT HostDrivePartition::initFromDvmVol(RTDVMVOLUME hVol)
+{
+ LogFlowThisFunc(("\n"));
+
+ AssertReturn(hVol != NIL_RTDVMVOLUME, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Common attributes: */
+ m.number = RTDvmVolumeGetIndex(hVol, RTDVMVOLIDX_HOST);
+ m.cbVol = (LONG64)RTDvmVolumeGetSize(hVol);
+ if (m.cbVol < 0)
+ m.cbVol = INT64_MAX;
+
+ uint64_t offStart = 0;
+ uint64_t offLastIgnored = 0;
+ int vrc = RTDvmVolumeQueryRange(hVol, &offStart, &offLastIgnored);
+ AssertRC(vrc);
+ m.offStart = RT_SUCCESS(vrc) ? (LONG64)offStart : 0;
+ Assert((uint64_t)m.cbVol == offLastIgnored - offStart + 1 || RT_FAILURE(vrc));
+ if (m.offStart < 0)
+ m.offStart = INT64_MAX;
+
+ uint64_t fFlags = RTDvmVolumeGetFlags(hVol);
+ m.active = (fFlags & (DVMVOLUME_FLAGS_BOOTABLE | DVMVOLUME_FLAGS_ACTIVE)) != 0;
+
+ /* MBR: */
+ m.firstCylinder = (uint16_t)RTDvmVolumeGetPropU64(hVol, RTDVMVOLPROP_MBR_FIRST_CYLINDER, 0);
+ m.firstHead = (uint8_t )RTDvmVolumeGetPropU64(hVol, RTDVMVOLPROP_MBR_FIRST_HEAD, 0);
+ m.firstSector = (uint8_t )RTDvmVolumeGetPropU64(hVol, RTDVMVOLPROP_MBR_FIRST_SECTOR, 0);
+ m.lastCylinder = (uint16_t)RTDvmVolumeGetPropU64(hVol, RTDVMVOLPROP_MBR_LAST_CYLINDER, 0);
+ m.lastHead = (uint8_t )RTDvmVolumeGetPropU64(hVol, RTDVMVOLPROP_MBR_LAST_HEAD, 0);
+ m.lastSector = (uint8_t )RTDvmVolumeGetPropU64(hVol, RTDVMVOLPROP_MBR_LAST_SECTOR, 0);
+ m.bMBRType = (uint8_t )RTDvmVolumeGetPropU64(hVol, RTDVMVOLPROP_MBR_TYPE, 0);
+
+ /* GPT: */
+ RTUUID Uuid;
+ vrc = RTDvmVolumeQueryProp(hVol, RTDVMVOLPROP_GPT_TYPE, &Uuid, sizeof(Uuid), NULL);
+ if (RT_SUCCESS(vrc))
+ m.typeUuid = Uuid;
+ vrc = RTDvmVolumeQueryProp(hVol, RTDVMVOLPROP_GPT_UUID, &Uuid, sizeof(Uuid), NULL);
+ if (RT_SUCCESS(vrc))
+ m.uuid = Uuid;
+
+ char *pszName = NULL;
+ vrc = RTDvmVolumeQueryName(hVol, &pszName);
+ if (RT_SUCCESS(vrc))
+ {
+ HRESULT hrc = m.name.assignEx(pszName);
+ RTStrFree(pszName);
+ AssertComRCReturn(hrc, hrc);
+ }
+
+ /*
+ * Do the type translation to the best of our ability.
+ */
+ m.enmType = PartitionType_Unknown;
+ if (m.typeUuid.isZero())
+ switch ((PartitionType_T)m.bMBRType)
+ {
+ case PartitionType_FAT12:
+ case PartitionType_FAT16:
+ case PartitionType_FAT:
+ case PartitionType_IFS:
+ case PartitionType_FAT32CHS:
+ case PartitionType_FAT32LBA:
+ case PartitionType_FAT16B:
+ case PartitionType_Extended:
+ case PartitionType_WindowsRE:
+ case PartitionType_LinuxSwapOld:
+ case PartitionType_LinuxOld:
+ case PartitionType_DragonFlyBSDSlice:
+ case PartitionType_LinuxSwap:
+ case PartitionType_Linux:
+ case PartitionType_LinuxExtended:
+ case PartitionType_LinuxLVM:
+ case PartitionType_BSDSlice:
+ case PartitionType_AppleUFS:
+ case PartitionType_AppleHFS:
+ case PartitionType_Solaris:
+ case PartitionType_GPT:
+ case PartitionType_EFI:
+ m.enmType = (PartitionType_T)m.bMBRType;
+ break;
+
+ case PartitionType_Empty:
+ default:
+ break;
+ }
+ else
+ {
+ static struct { const char *pszUuid; PartitionType_T enmType; } const s_aUuidToType[] =
+ {
+ { "024dee41-33e7-11d3-9d69-0008c781f39f", PartitionType_MBR },
+ { "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", PartitionType_EFI },
+ { "d3bfe2de-3daf-11df-ba40-e3a556d89593", PartitionType_iFFS },
+ { "f4019732-066e-4e12-8273-346c5641494f", PartitionType_SonyBoot },
+ { "bfbfafe7-a34f-448a-9a5b-6213eb736c22", PartitionType_LenovoBoot },
+ /* Win: */
+ { "e3c9e316-0b5c-4db8-817d-f92df00215ae", PartitionType_WindowsMSR },
+ { "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7", PartitionType_WindowsBasicData },
+ { "5808c8aa-7e8f-42e0-85d2-e1e90434cfb3", PartitionType_WindowsLDMMeta },
+ { "af9b60a0-1431-4f62-bc68-3311714a69ad", PartitionType_WindowsLDMData },
+ { "de94bba4-06d1-4d40-a16a-bfd50179d6ac", PartitionType_WindowsRecovery },
+ { "e75caf8f-f680-4cee-afa3-b001e56efc2d", PartitionType_WindowsStorageSpaces },
+ { "558d43c5-a1ac-43c0-aac8-d1472b2923d1", PartitionType_WindowsStorageReplica },
+ { "37affc90-ef7d-4e96-91c3-2d7ae055b174", PartitionType_IBMGPFS },
+ /* Linux: */
+ { "0fc63daf-8483-4772-8e79-3d69d8477de4", PartitionType_LinuxData },
+ { "a19d880f-05fc-4d3b-a006-743f0f84911e", PartitionType_LinuxRAID },
+ { "44479540-f297-41b2-9af7-d131d5f0458a", PartitionType_LinuxRootX86 },
+ { "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", PartitionType_LinuxRootAMD64 },
+ { "69dad710-2ce4-4e3c-b16c-21a1d49abed3", PartitionType_LinuxRootARM32 },
+ { "b921b045-1df0-41c3-af44-4c6f280d3fae", PartitionType_LinuxRootARM64 },
+ { "933ac7e1-2eb4-4f13-b844-0e14e2aef915", PartitionType_LinuxHome },
+ { "3b8f8425-20e0-4f3b-907f-1a25a76f98e8", PartitionType_LinuxSrv },
+ { "0657fd6d-a4ab-43c4-84e5-0933c84b4f4f", PartitionType_LinuxSwap },
+ { "e6d6d379-f507-44c2-a23c-238f2a3df928", PartitionType_LinuxLVM },
+ { "7ffec5c9-2d00-49b7-8941-3ea10a5586b7", PartitionType_LinuxPlainDmCrypt },
+ { "ca7d7ccb-63ed-4c53-861c-1742536059cc", PartitionType_LinuxLUKS },
+ { "8da63339-0007-60c0-c436-083ac8230908", PartitionType_LinuxReserved },
+ /* FreeBSD: */
+ { "83bd6b9d-7f41-11dc-be0b-001560b84f0f", PartitionType_FreeBSDBoot },
+ { "516e7cb4-6ecf-11d6-8ff8-00022d09712b", PartitionType_FreeBSDData },
+ { "516e7cb5-6ecf-11d6-8ff8-00022d09712b", PartitionType_FreeBSDSwap },
+ { "516e7cb6-6ecf-11d6-8ff8-00022d09712b", PartitionType_FreeBSDUFS },
+ { "516e7cb8-6ecf-11d6-8ff8-00022d09712b", PartitionType_FreeBSDVinum },
+ { "516e7cba-6ecf-11d6-8ff8-00022d09712b", PartitionType_FreeBSDZFS },
+ /* Apple/macOS: */
+ { "48465300-0000-11aa-aa11-00306543ecac", PartitionType_AppleHFSPlus },
+ { "7c3457ef-0000-11aa-aa11-00306543ecac", PartitionType_AppleAPFS },
+ { "55465300-0000-11aa-aa11-00306543ecac", PartitionType_AppleUFS },
+ { "52414944-0000-11aa-aa11-00306543ecac", PartitionType_AppleRAID },
+ { "52414944-5f4f-11aa-aa11-00306543ecac", PartitionType_AppleRAIDOffline },
+ { "426f6f74-0000-11aa-aa11-00306543ecac", PartitionType_AppleBoot },
+ { "4c616265-6c00-11aa-aa11-00306543ecac", PartitionType_AppleLabel },
+ { "5265636f-7665-11aa-aa11-00306543ecac", PartitionType_AppleTvRecovery },
+ { "53746f72-6167-11aa-aa11-00306543ecac", PartitionType_AppleCoreStorage },
+ { "b6fa30da-92d2-4a9a-96f1-871ec6486200", PartitionType_SoftRAIDStatus },
+ { "2e313465-19b9-463f-8126-8a7993773801", PartitionType_SoftRAIDScratch },
+ { "fa709c7e-65b1-4593-bfd5-e71d61de9b02", PartitionType_SoftRAIDVolume },
+ { "bbba6df5-f46f-4a89-8f59-8765b2727503", PartitionType_SoftRAIDCache },
+ /* Solaris */
+ { "6a82cb45-1dd2-11b2-99a6-080020736631", PartitionType_SolarisBoot },
+ { "6a85cf4d-1dd2-11b2-99a6-080020736631", PartitionType_SolarisRoot },
+ { "6a87c46f-1dd2-11b2-99a6-080020736631", PartitionType_SolarisSwap },
+ { "6a8b642b-1dd2-11b2-99a6-080020736631", PartitionType_SolarisBackup },
+ { "6a898cc3-1dd2-11b2-99a6-080020736631", PartitionType_SolarisUsr },
+ { "6a8ef2e9-1dd2-11b2-99a6-080020736631", PartitionType_SolarisVar },
+ { "6a90ba39-1dd2-11b2-99a6-080020736631", PartitionType_SolarisHome },
+ { "6a9283a5-1dd2-11b2-99a6-080020736631", PartitionType_SolarisAltSector },
+ { "6a945a3b-1dd2-11b2-99a6-080020736631", PartitionType_SolarisReserved },
+ { "6a9630d1-1dd2-11b2-99a6-080020736631", PartitionType_SolarisReserved },
+ { "6a980767-1dd2-11b2-99a6-080020736631", PartitionType_SolarisReserved },
+ { "6a96237f-1dd2-11b2-99a6-080020736631", PartitionType_SolarisReserved },
+ { "6a8d2ac7-1dd2-11b2-99a6-080020736631", PartitionType_SolarisReserved },
+ /* NetBSD: */
+ { "49f48d32-b10e-11dc-b99b-0019d1879648", PartitionType_NetBSDSwap },
+ { "49f48d5a-b10e-11dc-b99b-0019d1879648", PartitionType_NetBSDFFS },
+ { "49f48d82-b10e-11dc-b99b-0019d1879648", PartitionType_NetBSDLFS },
+ { "49f48daa-b10e-11dc-b99b-0019d1879648", PartitionType_NetBSDRAID },
+ { "2db519c4-b10f-11dc-b99b-0019d1879648", PartitionType_NetBSDConcatenated },
+ { "2db519ec-b10f-11dc-b99b-0019d1879648", PartitionType_NetBSDEncrypted },
+ /* Chrome OS: */
+ { "fe3a2a5d-4f32-41a7-b725-accc3285a309", PartitionType_ChromeOSKernel },
+ { "3cb8e202-3b7e-47dd-8a3c-7ff2a13cfcec", PartitionType_ChromeOSRootFS },
+ { "2e0a753d-9e48-43b0-8337-b15192cb1b5e", PartitionType_ChromeOSFuture },
+ /* Container Linux: */
+ { "5dfbf5f4-2848-4bac-aa5e-0d9a20b745a6", PartitionType_ContLnxUsr },
+ { "3884dd41-8582-4404-b9a8-e9b84f2df50e", PartitionType_ContLnxRoot },
+ { "c95dc21a-df0e-4340-8d7b-26cbfa9a03e0", PartitionType_ContLnxReserved },
+ { "be9067b9-ea49-4f15-b4f6-f36f8c9e1818", PartitionType_ContLnxRootRAID },
+ /* Haiku: */
+ { "42465331-3ba3-10f1-802a-4861696b7521", PartitionType_HaikuBFS },
+ /* MidnightBSD */
+ { "85d5e45e-237c-11e1-b4b3-e89a8f7fc3a7", PartitionType_MidntBSDBoot },
+ { "85d5e45a-237c-11e1-b4b3-e89a8f7fc3a7", PartitionType_MidntBSDData },
+ { "85d5e45b-237c-11e1-b4b3-e89a8f7fc3a7", PartitionType_MidntBSDSwap },
+ { "0394ef8b-237e-11e1-b4b3-e89a8f7fc3a7", PartitionType_MidntBSDUFS },
+ { "85d5e45c-237c-11e1-b4b3-e89a8f7fc3a7", PartitionType_MidntBSDVium },
+ { "85d5e45d-237c-11e1-b4b3-e89a8f7fc3a7", PartitionType_MidntBSDZFS },
+ /* OpenBSD: */
+ { "824cc7a0-36a8-11e3-890a-952519ad3f61", PartitionType_OpenBSDData },
+ /* QNX: */
+ { "cef5a9ad-73bc-4601-89f3-cdeeeee321a1", PartitionType_QNXPowerSafeFS },
+ /* Plan 9: */
+ { "c91818f9-8025-47af-89d2-f030d7000c2c", PartitionType_Plan9 },
+ /* VMWare ESX: */
+ { "9d275380-40ad-11db-bf97-000c2911d1b8", PartitionType_VMWareVMKCore },
+ { "aa31e02a-400f-11db-9590-000c2911d1b8", PartitionType_VMWareVMFS },
+ { "9198effc-31c0-11db-8f78-000c2911d1b8", PartitionType_VMWareReserved },
+ /* Android-x86: */
+ { "2568845d-2332-4675-bc39-8fa5a4748d15", PartitionType_AndroidX86Bootloader },
+ { "114eaffe-1552-4022-b26e-9b053604cf84", PartitionType_AndroidX86Bootloader2 },
+ { "49a4d17f-93a3-45c1-a0de-f50b2ebe2599", PartitionType_AndroidX86Boot },
+ { "4177c722-9e92-4aab-8644-43502bfd5506", PartitionType_AndroidX86Recovery },
+ { "ef32a33b-a409-486c-9141-9ffb711f6266", PartitionType_AndroidX86Misc },
+ { "20ac26be-20b7-11e3-84c5-6cfdb94711e9", PartitionType_AndroidX86Metadata },
+ { "38f428e6-d326-425d-9140-6e0ea133647c", PartitionType_AndroidX86System },
+ { "a893ef21-e428-470a-9e55-0668fd91a2d9", PartitionType_AndroidX86Cache },
+ { "dc76dda9-5ac1-491c-af42-a82591580c0d", PartitionType_AndroidX86Data },
+ { "ebc597d0-2053-4b15-8b64-e0aac75f4db1", PartitionType_AndroidX86Persistent },
+ { "c5a0aeec-13ea-11e5-a1b1-001e67ca0c3c", PartitionType_AndroidX86Vendor },
+ { "bd59408b-4514-490d-bf12-9878d963f378", PartitionType_AndroidX86Config },
+ { "8f68cc74-c5e5-48da-be91-a0c8c15e9c80", PartitionType_AndroidX86Factory },
+ { "9fdaa6ef-4b3f-40d2-ba8d-bff16bfb887b", PartitionType_AndroidX86FactoryAlt },
+ { "767941d0-2085-11e3-ad3b-6cfdb94711e9", PartitionType_AndroidX86Fastboot },
+ { "ac6d7924-eb71-4df8-b48d-e267b27148ff", PartitionType_AndroidX86OEM },
+ /* Android ARM: */
+ { "19a710a2-b3ca-11e4-b026-10604b889dcf", PartitionType_AndroidARMMeta },
+ { "193d1ea4-b3ca-11e4-b075-10604b889dcf", PartitionType_AndroidARMExt },
+ /* Open Network Install Environment: */
+ { "7412f7d5-a156-4b13-81dc-867174929325", PartitionType_ONIEBoot },
+ { "d4e6e2cd-4469-46f3-b5cb-1bff57afc149", PartitionType_ONIEConfig },
+ /* PowerPC: */
+ { "9e1a2d38-c612-4316-aa26-8b49521e5a8b", PartitionType_PowerPCPrep },
+ /* freedesktop.org: */
+ { "bc13c2ff-59e6-4262-a352-b275fd6f7172", PartitionType_XDGShrBootConfig },
+ /* Ceph: */
+ { "cafecafe-9b03-4f30-b4c6-b4b80ceff106", PartitionType_CephBlock },
+ { "30cd0809-c2b2-499c-8879-2d6b78529876", PartitionType_CephBlockDB },
+ { "93b0052d-02d9-4d8a-a43b-33a3ee4dfbc3", PartitionType_CephBlockDBDmc },
+ { "166418da-c469-4022-adf4-b30afd37f176", PartitionType_CephBlockDBDmcLUKS },
+ { "cafecafe-9b03-4f30-b4c6-5ec00ceff106", PartitionType_CephBlockDmc },
+ { "cafecafe-9b03-4f30-b4c6-35865ceff106", PartitionType_CephBlockDmcLUKS },
+ { "5ce17fce-4087-4169-b7ff-056cc58473f9", PartitionType_CephBlockWALog },
+ { "306e8683-4fe2-4330-b7c0-00a917c16966", PartitionType_CephBlockWALogDmc },
+ { "86a32090-3647-40b9-bbbd-38d8c573aa86", PartitionType_CephBlockWALogDmcLUKS },
+ { "89c57f98-2fe5-4dc0-89c1-f3ad0ceff2be", PartitionType_CephDisk },
+ { "89c57f98-2fe5-4dc0-89c1-5ec00ceff2be", PartitionType_CephDiskDmc },
+ { "45b0969e-9b03-4f30-b4c6-b4b80ceff106", PartitionType_CephJournal },
+ { "45b0969e-9b03-4f30-b4c6-5ec00ceff106", PartitionType_CephJournalDmc },
+ { "45b0969e-9b03-4f30-b4c6-35865ceff106", PartitionType_CephJournalDmcLUKS },
+ { "fb3aabf9-d25f-47cc-bf5e-721d1816496b", PartitionType_CephLockbox },
+ { "cafecafe-8ae0-4982-bf9d-5a8d867af560", PartitionType_CephMultipathBlock1 },
+ { "7f4a666a-16f3-47a2-8445-152ef4d03f6c", PartitionType_CephMultipathBlock2 },
+ { "ec6d6385-e346-45dc-be91-da2a7c8b3261", PartitionType_CephMultipathBlockDB },
+ { "01b41e1b-002a-453c-9f17-88793989ff8f", PartitionType_CephMultipathBLockWALog },
+ { "45b0969e-8ae0-4982-bf9d-5a8d867af560", PartitionType_CephMultipathJournal },
+ { "4fbd7e29-8ae0-4982-bf9d-5a8d867af560", PartitionType_CephMultipathOSD },
+ { "4fbd7e29-9d25-41b8-afd0-062c0ceff05d", PartitionType_CephOSD },
+ { "4fbd7e29-9d25-41b8-afd0-5ec00ceff05d", PartitionType_CephOSDDmc },
+ { "4fbd7e29-9d25-41b8-afd0-35865ceff05d", PartitionType_CephOSDDmcLUKS },
+ };
+ for (size_t i = 0; i < RT_ELEMENTS(s_aUuidToType); i++)
+ if (m.typeUuid.equalsString(s_aUuidToType[i].pszUuid))
+ {
+ m.enmType = s_aUuidToType[i].enmType;
+ break;
+ }
+
+ /* Some OSes are using non-random UUIDs and we can at least identify the
+ OS if not the exact type. */
+ if (m.enmType == PartitionType_Unknown)
+ {
+ char szType[RTUUID_STR_LENGTH];
+ m.typeUuid.toString(szType, sizeof(szType));
+ RTStrToLower(szType);
+ if (RTStrSimplePatternMatch(szType, "516e7c??-6ecf-11d6-8ff8-00022d09712b"))
+ m.enmType = PartitionType_FreeBSDUnknown;
+ else if (RTStrSimplePatternMatch(szType, "????????-????-11aa-aa11-00306543ecac"))
+ m.enmType = PartitionType_AppleUnknown;
+ else if (RTStrSimplePatternMatch(szType, "????????-1dd2-11b2-99a6-080020736631"))
+ m.enmType = PartitionType_SolarisUnknown;
+ else if (RTStrSimplePatternMatch(szType, "????????-b1??-11dc-b99b-0019d1879648"))
+ m.enmType = PartitionType_NetBSDUnknown;
+ else if (RTStrSimplePatternMatch(szType, "????????-23??-11e1-b4b3-e89a8f7fc3a7"))
+ m.enmType = PartitionType_MidntBSDUnknown;
+ else if (RTStrSimplePatternMatch(szType, "????????-????-11db-????-000c2911d1b8"))
+ m.enmType = PartitionType_VMWareUnknown;
+ }
+
+#ifdef VBOX_STRICT
+ /* Make sure we've done have any duplicates in the translation table: */
+ static bool s_fCheckedForDuplicates = false;
+ if (!s_fCheckedForDuplicates)
+ {
+ for (size_t i = 0; i < RT_ELEMENTS(s_aUuidToType); i++)
+ for (size_t j = i + 1; j < RT_ELEMENTS(s_aUuidToType); j++)
+ AssertMsg(RTUuidCompare2Strs(s_aUuidToType[i].pszUuid, s_aUuidToType[j].pszUuid) != 0,
+ ("%d & %d: %s\n", i, j, s_aUuidToType[i].pszUuid));
+ s_fCheckedForDuplicates = true;
+ }
+#endif
+
+ }
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/*
+ * Uninitializes the instance.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void HostDrivePartition::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m.number = 0;
+ m.cbVol = 0;
+ m.offStart = 0;
+ m.enmType = PartitionType_Empty;
+ m.active = 0;
+
+ m.bMBRType = 0;
+ m.firstCylinder = 0;
+ m.firstHead = 0;
+ m.firstSector = 0;
+ m.lastCylinder = 0;
+ m.lastHead = 0;
+ m.lastSector = 0;
+
+ m.typeUuid.clear();
+ m.uuid.clear();
+ m.name.setNull();
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/HostImpl.cpp b/src/VBox/Main/src-server/HostImpl.cpp
new file mode 100644
index 00000000..2df0412d
--- /dev/null
+++ b/src/VBox/Main/src-server/HostImpl.cpp
@@ -0,0 +1,4162 @@
+/* $Id: HostImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation: Host
+ */
+
+/*
+ * Copyright (C) 2004-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOST
+
+#define __STDC_LIMIT_MACROS
+#define __STDC_CONSTANT_MACROS
+
+// VBoxNetCfg-win.h needs winsock2.h and thus MUST be included before any other
+// header file includes Windows.h.
+#if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_NETFLT)
+# include <VBox/VBoxNetCfg-win.h>
+#endif
+
+// for some reason Windows burns in sdk\...\winsock.h if this isn't included first
+#include "VBox/com/ptr.h"
+
+#include "HostImpl.h"
+
+#ifdef VBOX_WITH_USB
+# include "HostUSBDeviceImpl.h"
+# include "USBDeviceFilterImpl.h"
+# include "USBProxyService.h"
+#else
+# include "VirtualBoxImpl.h"
+#endif // VBOX_WITH_USB
+
+#include "HostNetworkInterfaceImpl.h"
+#include "HostVideoInputDeviceImpl.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "Performance.h"
+#ifdef VBOX_WITH_UPDATE_AGENT
+# include "UpdateAgentImpl.h"
+#endif
+
+#include "MediumImpl.h"
+#include "HostPower.h"
+
+#if defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)
+# include <HostHardwareLinux.h>
+#endif
+
+#include <set>
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+# include "PerformanceImpl.h"
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+
+#if defined(RT_OS_DARWIN) && ARCH_BITS == 32
+# include <sys/types.h>
+# include <sys/sysctl.h>
+#endif
+
+#ifdef RT_OS_LINUX
+# include <sys/ioctl.h>
+# include <errno.h>
+# include <net/if.h>
+# include <net/if_arp.h>
+#endif /* RT_OS_LINUX */
+
+#ifdef RT_OS_SOLARIS
+# include <fcntl.h>
+# include <unistd.h>
+# include <stropts.h>
+# include <errno.h>
+# include <limits.h>
+# include <stdio.h>
+# include <libdevinfo.h>
+# include <sys/mkdev.h>
+# include <sys/scsi/generic/inquiry.h>
+# include <net/if.h>
+# include <sys/socket.h>
+# include <sys/sockio.h>
+# include <net/if_arp.h>
+# include <net/if.h>
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <sys/cdio.h>
+# include <sys/dkio.h>
+# include <sys/mnttab.h>
+# include <sys/mntent.h>
+/* Dynamic loading of libhal on Solaris hosts */
+# ifdef VBOX_USE_LIBHAL
+# include "vbox-libhal.h"
+extern "C" char *getfullrawname(char *);
+# endif
+# include "solaris/DynLoadLibSolaris.h"
+
+/**
+ * Solaris DVD drive list as returned by getDVDInfoFromDevTree().
+ */
+typedef struct SOLARISDVD
+{
+ struct SOLARISDVD *pNext;
+ char szDescription[512];
+ char szRawDiskPath[PATH_MAX];
+} SOLARISDVD;
+/** Pointer to a Solaris DVD descriptor. */
+typedef SOLARISDVD *PSOLARISDVD;
+
+/** Solaris fixed drive (SSD, HDD, ++) descriptor list entry as returned by the
+ * solarisWalkDeviceNodeForFixedDrive callback. */
+typedef SOLARISDVD SOLARISFIXEDDISK;
+/** Pointer to a Solaris fixed drive (SSD, HDD, ++) descriptor. */
+typedef SOLARISFIXEDDISK *PSOLARISFIXEDDISK;
+
+
+#endif /* RT_OS_SOLARIS */
+
+#ifdef RT_OS_WINDOWS
+# define _WIN32_DCOM
+# include <iprt/win/windows.h>
+# include <shellapi.h>
+# define INITGUID
+# include <guiddef.h>
+# include <devguid.h>
+# include <iprt/win/objbase.h>
+# include <iprt/win/shlobj.h>
+# include <cfgmgr32.h>
+# include <tchar.h>
+#endif /* RT_OS_WINDOWS */
+
+#ifdef RT_OS_DARWIN
+# include "darwin/iokit.h"
+#endif
+
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+# include <iprt/asm-amd64-x86.h>
+#endif
+#ifdef RT_OS_SOLARIS
+# include <iprt/ctype.h>
+#endif
+#if defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS)
+# include <iprt/file.h>
+#endif
+#include <iprt/mp.h>
+#include <iprt/env.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#ifndef RT_OS_WINDOWS
+# include <iprt/path.h>
+#endif
+#include <iprt/time.h>
+#ifdef RT_OS_WINDOWS
+# include <iprt/dir.h>
+# include <iprt/vfs.h>
+#endif
+
+#ifdef VBOX_WITH_HOSTNETIF_API
+# include "netif.h"
+#endif
+
+#include <VBox/usb.h>
+#include <VBox/err.h>
+#include <VBox/settings.h>
+#include <VBox/sup.h>
+#ifdef VBOX_WITH_3D_ACCELERATION
+# include <VBox/VBoxOGL.h>
+#endif
+#include <iprt/x86.h>
+
+#include "VBox/com/MultiResult.h"
+#include "VBox/com/array.h"
+
+#include <stdio.h>
+
+#include <algorithm>
+#include <iprt/sanitized/string>
+#include <vector>
+
+#include "HostDnsService.h"
+#include "HostDriveImpl.h"
+#include "HostDrivePartitionImpl.h"
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Host private data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct Host::Data
+{
+ Data()
+ :
+ fDVDDrivesListBuilt(false),
+ fFloppyDrivesListBuilt(false),
+ fPersistentConfigUpToDate(false)
+ {};
+
+ VirtualBox *pParent;
+
+ HostNetworkInterfaceList llNetIfs; // list of network interfaces
+
+#ifdef VBOX_WITH_USB
+ USBDeviceFilterList llChildren; // all USB device filters
+ USBDeviceFilterList llUSBDeviceFilters; // USB device filters in use by the USB proxy service
+
+ /** Pointer to the USBProxyService object. */
+ USBProxyService *pUSBProxyService;
+#endif /* VBOX_WITH_USB */
+
+ // list of host drives; lazily created by getDVDDrives() and getFloppyDrives(),
+ // and protected by the medium tree lock handle (including the bools).
+ MediaList llDVDDrives,
+ llFloppyDrives;
+ bool fDVDDrivesListBuilt,
+ fFloppyDrivesListBuilt;
+
+#if defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)
+ /** Object with information about host drives */
+ VBoxMainDriveInfo hostDrives;
+#endif
+
+ /** @name Features that can be queried with GetProcessorFeature.
+ * @{ */
+ bool fVTSupported,
+ fLongModeSupported,
+ fPAESupported,
+ fNestedPagingSupported,
+ fUnrestrictedGuestSupported,
+ fNestedHWVirtSupported,
+ fVirtVmsaveVmload,
+ fRecheckVTSupported;
+
+ /** @} */
+
+ /** 3D hardware acceleration supported? Tristate, -1 meaning not probed. */
+ int f3DAccelerationSupported;
+
+ HostPowerService *pHostPowerService;
+ /** Host's DNS informaton fetching */
+ HostDnsMonitorProxy hostDnsMonitorProxy;
+
+ /** Startup syncing of persistent config in extra data */
+ bool fPersistentConfigUpToDate;
+
+#ifdef VBOX_WITH_UPDATE_AGENT
+ /** Reference to the host update agent. */
+ const ComObjPtr<HostUpdateAgent> pUpdateHost;
+#endif
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Constructor / destructor
+//
+////////////////////////////////////////////////////////////////////////////////
+DEFINE_EMPTY_CTOR_DTOR(Host)
+
+HRESULT Host::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void Host::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+/**
+ * Initializes the host object.
+ *
+ * @param aParent VirtualBox parent object.
+ */
+HRESULT Host::init(VirtualBox *aParent)
+{
+ HRESULT hrc;
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ m->pParent = aParent;
+
+#ifdef VBOX_WITH_USB
+ /*
+ * Create and initialize the USB Proxy Service.
+ */
+ m->pUSBProxyService = new USBProxyService(this);
+ hrc = m->pUSBProxyService->init();
+ AssertComRCReturn(hrc, hrc);
+#endif /* VBOX_WITH_USB */
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ i_registerMetrics(aParent->i_performanceCollector());
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+ /* Create the list of network interfaces so their metrics get registered. */
+ i_updateNetIfList();
+
+ m->hostDnsMonitorProxy.init(m->pParent);
+
+#ifdef VBOX_WITH_UPDATE_AGENT
+ hrc = unconst(m->pUpdateHost).createObject();
+ if (SUCCEEDED(hrc))
+ hrc = m->pUpdateHost->init(m->pParent);
+ AssertComRCReturn(hrc, hrc);
+#endif
+
+#if defined(RT_OS_WINDOWS)
+ m->pHostPowerService = new HostPowerServiceWin(m->pParent);
+#elif defined(RT_OS_LINUX) && defined(VBOX_WITH_DBUS)
+ m->pHostPowerService = new HostPowerServiceLinux(m->pParent);
+#elif defined(RT_OS_DARWIN)
+ m->pHostPowerService = new HostPowerServiceDarwin(m->pParent);
+#else
+ m->pHostPowerService = new HostPowerService(m->pParent);
+#endif
+
+ /* Cache the features reported by GetProcessorFeature. */
+ m->fVTSupported = false;
+ m->fLongModeSupported = false;
+ m->fPAESupported = false;
+ m->fNestedPagingSupported = false;
+ m->fUnrestrictedGuestSupported = false;
+ m->fNestedHWVirtSupported = false;
+ m->fVirtVmsaveVmload = false;
+ m->fRecheckVTSupported = false;
+
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+ if (ASMHasCpuId())
+ {
+ /* Note! This code is duplicated in SUPDrv.c and other places! */
+ uint32_t uMaxId, uVendorEBX, uVendorECX, uVendorEDX;
+ ASMCpuId(0, &uMaxId, &uVendorEBX, &uVendorECX, &uVendorEDX);
+ if (RTX86IsValidStdRange(uMaxId))
+ {
+ /* PAE? */
+ uint32_t uDummy, fFeaturesEcx, fFeaturesEdx;
+ ASMCpuId(1, &uDummy, &uDummy, &fFeaturesEcx, &fFeaturesEdx);
+ m->fPAESupported = RT_BOOL(fFeaturesEdx & X86_CPUID_FEATURE_EDX_PAE);
+
+ /* Long Mode? */
+ uint32_t uExtMaxId, fExtFeaturesEcx, fExtFeaturesEdx;
+ ASMCpuId(0x80000000, &uExtMaxId, &uDummy, &uDummy, &uDummy);
+ ASMCpuId(0x80000001, &uDummy, &uDummy, &fExtFeaturesEcx, &fExtFeaturesEdx);
+ m->fLongModeSupported = RTX86IsValidExtRange(uExtMaxId)
+ && (fExtFeaturesEdx & X86_CPUID_EXT_FEATURE_EDX_LONG_MODE);
+
+# if defined(RT_OS_DARWIN) && ARCH_BITS == 32 /* darwin.x86 has some optimizations of 64-bit on 32-bit. */
+ int f64bitCapable = 0;
+ size_t cbParameter = sizeof(f64bitCapable);
+ if (sysctlbyname("hw.cpu64bit_capable", &f64bitCapable, &cbParameter, NULL, NULL) != -1)
+ m->fLongModeSupported = f64bitCapable != 0;
+# endif
+
+ /* VT-x? */
+ if ( RTX86IsIntelCpu(uVendorEBX, uVendorECX, uVendorEDX)
+ || RTX86IsViaCentaurCpu(uVendorEBX, uVendorECX, uVendorEDX)
+ || RTX86IsShanghaiCpu(uVendorEBX, uVendorECX, uVendorEDX))
+ {
+ if ( (fFeaturesEcx & X86_CPUID_FEATURE_ECX_VMX)
+ && (fFeaturesEdx & X86_CPUID_FEATURE_EDX_MSR)
+ && (fFeaturesEdx & X86_CPUID_FEATURE_EDX_FXSR)
+ )
+ {
+ const char *pszIgn;
+ int rc = SUPR3QueryVTxSupported(&pszIgn);
+ if (RT_SUCCESS(rc))
+ m->fVTSupported = true;
+ }
+ }
+ /* AMD-V */
+ else if ( RTX86IsAmdCpu(uVendorEBX, uVendorECX, uVendorEDX)
+ || RTX86IsHygonCpu(uVendorEBX, uVendorECX, uVendorEDX))
+ {
+ if ( (fExtFeaturesEcx & X86_CPUID_AMD_FEATURE_ECX_SVM)
+ && (fFeaturesEdx & X86_CPUID_FEATURE_EDX_MSR)
+ && (fFeaturesEdx & X86_CPUID_FEATURE_EDX_FXSR)
+ && RTX86IsValidExtRange(uExtMaxId)
+ )
+ {
+ m->fVTSupported = true;
+ m->fUnrestrictedGuestSupported = true;
+
+ /* Query AMD features. */
+ if (uExtMaxId >= 0x8000000a)
+ {
+ uint32_t fSVMFeaturesEdx;
+ ASMCpuId(0x8000000a, &uDummy, &uDummy, &uDummy, &fSVMFeaturesEdx);
+ if (fSVMFeaturesEdx & X86_CPUID_SVM_FEATURE_EDX_NESTED_PAGING)
+ m->fNestedPagingSupported = true;
+ if (fSVMFeaturesEdx & X86_CPUID_SVM_FEATURE_EDX_VIRT_VMSAVE_VMLOAD)
+ m->fVirtVmsaveVmload = true;
+ }
+ }
+ }
+ }
+ }
+#endif /* defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) */
+
+
+ /* Check with SUPDrv if VT-x and AMD-V are really supported (may fail). */
+ if (m->fVTSupported)
+ {
+ m->fRecheckVTSupported = true; /* Try again later when the driver is loaded; cleared by i_updateProcessorFeatures on success. */
+ i_updateProcessorFeatures();
+ }
+
+ /* Check for NEM in root paritition (hyper-V / windows). */
+ if (!m->fVTSupported && SUPR3IsNemSupportedWhenNoVtxOrAmdV())
+ {
+ m->fVTSupported = m->fNestedPagingSupported = true;
+ m->fRecheckVTSupported = false;
+ }
+
+#ifdef VBOX_WITH_3D_ACCELERATION
+ /* Test for 3D hardware acceleration support later when (if ever) need. */
+ m->f3DAccelerationSupported = -1;
+#else
+ m->f3DAccelerationSupported = false;
+#endif
+
+#if defined(VBOX_WITH_HOSTNETIF_API) && (defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD))
+ /* Extract the list of configured host-only interfaces */
+ std::set<Utf8Str> aConfiguredNames;
+ SafeArray<BSTR> aGlobalExtraDataKeys;
+ hrc = aParent->GetExtraDataKeys(ComSafeArrayAsOutParam(aGlobalExtraDataKeys));
+ AssertMsg(SUCCEEDED(hrc), ("VirtualBox::GetExtraDataKeys failed with %Rhrc\n", hrc));
+ for (size_t i = 0; i < aGlobalExtraDataKeys.size(); ++i)
+ {
+ Utf8Str strKey = aGlobalExtraDataKeys[i];
+
+ if (!strKey.startsWith("HostOnly/vboxnet"))
+ continue;
+
+ size_t pos = strKey.find("/", sizeof("HostOnly/vboxnet"));
+ if (pos != Utf8Str::npos)
+ aConfiguredNames.insert(strKey.substr(sizeof("HostOnly"),
+ pos - sizeof("HostOnly")));
+ }
+
+ for (std::set<Utf8Str>::const_iterator it = aConfiguredNames.begin();
+ it != aConfiguredNames.end();
+ ++it)
+ {
+ ComPtr<IHostNetworkInterface> hif;
+ ComPtr<IProgress> progress;
+
+ int vrc = NetIfCreateHostOnlyNetworkInterface(m->pParent,
+ hif.asOutParam(),
+ progress.asOutParam(),
+ it->c_str());
+ if (RT_FAILURE(vrc))
+ LogRel(("failed to create %s, error (%Rrc)\n", it->c_str(), vrc));
+ }
+
+#endif /* defined(VBOX_WITH_HOSTNETIF_API) && (defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD)) */
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the host object and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void Host::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ PerformanceCollector *aCollector = m->pParent->i_performanceCollector();
+ i_unregisterMetrics(aCollector);
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+ /*
+ * Note that unregisterMetrics() has unregistered all metrics associated
+ * with Host including network interface ones. We can destroy network
+ * interface objects now. Don't forget the uninit call, otherwise this
+ * causes a race with crashing API clients getting their stale references
+ * cleaned up and VirtualBox shutting down.
+ */
+ while (!m->llNetIfs.empty())
+ {
+ ComObjPtr<HostNetworkInterface> &pNet = m->llNetIfs.front();
+ pNet->uninit();
+ m->llNetIfs.pop_front();
+ }
+
+ m->hostDnsMonitorProxy.uninit();
+
+#ifdef VBOX_WITH_UPDATE_AGENT
+ if (m->pUpdateHost)
+ {
+ m->pUpdateHost->uninit();
+ unconst(m->pUpdateHost).setNull();
+ }
+#endif
+
+#ifdef VBOX_WITH_USB
+ /* wait for USB proxy service to terminate before we uninit all USB
+ * devices */
+ LogFlowThisFunc(("Stopping USB proxy service...\n"));
+ delete m->pUSBProxyService;
+ m->pUSBProxyService = NULL;
+ LogFlowThisFunc(("Done stopping USB proxy service.\n"));
+#endif
+
+ delete m->pHostPowerService;
+
+#ifdef VBOX_WITH_USB
+ /* uninit all USB device filters still referenced by clients
+ * Note! HostUSBDeviceFilter::uninit() will modify llChildren.
+ * This list should be already empty, but better be safe than sorry. */
+ while (!m->llChildren.empty())
+ {
+ ComObjPtr<HostUSBDeviceFilter> &pChild = m->llChildren.front();
+ pChild->uninit();
+ m->llChildren.pop_front();
+ }
+
+ /* No need to uninit these, as either Machine::uninit() or the above loop
+ * already covered them all. Subset of llChildren. */
+ m->llUSBDeviceFilters.clear();
+#endif
+
+ /* uninit all host DVD medium objects */
+ while (!m->llDVDDrives.empty())
+ {
+ ComObjPtr<Medium> &pMedium = m->llDVDDrives.front();
+ pMedium->uninit();
+ m->llDVDDrives.pop_front();
+ }
+ /* uninit all host floppy medium objects */
+ while (!m->llFloppyDrives.empty())
+ {
+ ComObjPtr<Medium> &pMedium = m->llFloppyDrives.front();
+ pMedium->uninit();
+ m->llFloppyDrives.pop_front();
+ }
+
+ delete m;
+ m = NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// IHost public methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns a list of host DVD drives.
+ *
+ * @returns COM status code
+ * @param aDVDDrives address of result pointer
+ */
+
+HRESULT Host::getDVDDrives(std::vector<ComPtr<IMedium> > &aDVDDrives)
+{
+ AutoWriteLock treeLock(m->pParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ MediaList *pList;
+ HRESULT rc = i_getDrives(DeviceType_DVD, true /* fRefresh */, pList, treeLock);
+ if (FAILED(rc))
+ return rc;
+
+ aDVDDrives.resize(pList->size());
+ size_t i = 0;
+ for (MediaList::const_iterator it = pList->begin(); it != pList->end(); ++it, ++i)
+ (*it).queryInterfaceTo(aDVDDrives[i].asOutParam());
+
+ return S_OK;
+}
+
+/**
+ * Returns a list of host floppy drives.
+ *
+ * @returns COM status code
+ * @param aFloppyDrives address of result pointer
+ */
+HRESULT Host::getFloppyDrives(std::vector<ComPtr<IMedium> > &aFloppyDrives)
+{
+ AutoWriteLock treeLock(m->pParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ MediaList *pList;
+ HRESULT rc = i_getDrives(DeviceType_Floppy, true /* fRefresh */, pList, treeLock);
+ if (FAILED(rc))
+ return rc;
+
+ aFloppyDrives.resize(pList->size());
+ size_t i = 0;
+ for (MediaList::const_iterator it = pList->begin(); it != pList->end(); ++it, ++i)
+ (*it).queryInterfaceTo(aFloppyDrives[i].asOutParam());
+
+ return S_OK;
+}
+
+
+#if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_NETFLT)
+# define VBOX_APP_NAME L"VirtualBox"
+
+static int vboxNetWinAddComponent(std::list< ComObjPtr<HostNetworkInterface> > *pPist,
+ INetCfgComponent *pncc)
+{
+ LPWSTR lpszName;
+ GUID IfGuid;
+ HRESULT hr;
+ int rc = VERR_GENERAL_FAILURE;
+
+ hr = pncc->GetDisplayName(&lpszName);
+ Assert(hr == S_OK);
+ if (hr == S_OK)
+ {
+ Bstr name((CBSTR)lpszName);
+
+ hr = pncc->GetInstanceGuid(&IfGuid);
+ Assert(hr == S_OK);
+ if (hr == S_OK)
+ {
+ /* create a new object and add it to the list */
+ ComObjPtr<HostNetworkInterface> iface;
+ iface.createObject();
+ /* remove the curly bracket at the end */
+ if (SUCCEEDED(iface->init(name, name, Guid(IfGuid), HostNetworkInterfaceType_Bridged)))
+ {
+// iface->setVirtualBox(m->pParent);
+ pPist->push_back(iface);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ Assert(0);
+ }
+ }
+ CoTaskMemFree(lpszName);
+ }
+
+ return rc;
+}
+#endif /* defined(RT_OS_WINDOWS) && defined(VBOX_WITH_NETFLT) */
+
+#if defined(RT_OS_WINDOWS)
+struct HostOnlyInfo
+{
+ HostOnlyInfo() : fDhcpEnabled(false), uIPv6PrefixLength(0) {};
+
+ Bstr bstrName;
+ bool fDhcpEnabled;
+ Bstr strIPv4Address;
+ Bstr strIPv4NetMask;
+ Bstr strIPv6Address;
+ ULONG uIPv6PrefixLength;
+};
+
+typedef std::map<Utf8Str, HostOnlyInfo*> GUID_TO_HOST_ONLY_INFO;
+
+HRESULT Host::i_updatePersistentConfigForHostOnlyAdapters(void)
+{
+ /* No need to do the sync twice */
+ if (m->fPersistentConfigUpToDate)
+ return S_OK;
+ m->fPersistentConfigUpToDate = true;
+ bool fChangesMade = false;
+
+ /* Extract the list of configured host-only interfaces */
+ GUID_TO_HOST_ONLY_INFO aSavedAdapters;
+ SafeArray<BSTR> aGlobalExtraDataKeys;
+ HRESULT hrc = m->pParent->GetExtraDataKeys(ComSafeArrayAsOutParam(aGlobalExtraDataKeys));
+ AssertMsg(SUCCEEDED(hrc), ("VirtualBox::GetExtraDataKeys failed with %Rhrc\n", hrc));
+ for (size_t i = 0; i < aGlobalExtraDataKeys.size(); ++i)
+ {
+ Utf8Str strKey = aGlobalExtraDataKeys[i];
+
+ if (strKey.startsWith("HostOnly/{"))
+ {
+ Bstr bstrValue;
+ hrc = m->pParent->GetExtraData(aGlobalExtraDataKeys[i], bstrValue.asOutParam());
+ if (hrc != S_OK)
+ continue;
+
+ Utf8Str strGuid = strKey.substr(10, 36); /* Skip "HostOnly/{" */
+ if (aSavedAdapters.find(strGuid) == aSavedAdapters.end())
+ aSavedAdapters[strGuid] = new HostOnlyInfo();
+
+ if (strKey.endsWith("}/Name"))
+ aSavedAdapters[strGuid]->bstrName = bstrValue;
+ else if (strKey.endsWith("}/IPAddress"))
+ {
+ if (bstrValue == "DHCP")
+ aSavedAdapters[strGuid]->fDhcpEnabled = true;
+ else
+ aSavedAdapters[strGuid]->strIPv4Address = bstrValue;
+ }
+ else if (strKey.endsWith("}/IPNetMask"))
+ aSavedAdapters[strGuid]->strIPv4NetMask = bstrValue;
+ else if (strKey.endsWith("}/IPV6Address"))
+ aSavedAdapters[strGuid]->strIPv6Address = bstrValue;
+ else if (strKey.endsWith("}/IPV6PrefixLen"))
+ aSavedAdapters[strGuid]->uIPv6PrefixLength = Utf8Str(bstrValue).toUInt32();
+ }
+ }
+
+ /* Go over the list of existing adapters and update configs saved in extra data */
+ std::set<Bstr> aKnownNames;
+ for (HostNetworkInterfaceList::iterator it = m->llNetIfs.begin(); it != m->llNetIfs.end(); ++it)
+ {
+ /* Get type */
+ HostNetworkInterfaceType_T t;
+ hrc = (*it)->COMGETTER(InterfaceType)(&t);
+ if (FAILED(hrc) || t != HostNetworkInterfaceType_HostOnly)
+ continue;
+ /* Get id */
+ Bstr bstrGuid;
+ hrc = (*it)->COMGETTER(Id)(bstrGuid.asOutParam());
+ if (FAILED(hrc))
+ continue;
+ /* Get name */
+ Bstr bstrName;
+ hrc = (*it)->COMGETTER(Name)(bstrName.asOutParam());
+ if (FAILED(hrc))
+ continue;
+
+ /* Remove adapter from map as it does not need any further processing */
+ aSavedAdapters.erase(Utf8Str(bstrGuid));
+ /* Add adapter name to the list of known names, so we won't attempt to create adapters with the same name */
+ aKnownNames.insert(bstrName);
+ /* Make sure our extra data contains the latest config */
+ hrc = (*it)->i_updatePersistentConfig();
+ if (hrc != S_OK)
+ break;
+ }
+
+ /* The following loop not only creates missing adapters, it destroys HostOnlyInfo objects contained in the map as well */
+ for (GUID_TO_HOST_ONLY_INFO::iterator it = aSavedAdapters.begin(); it != aSavedAdapters.end(); ++it)
+ {
+ Utf8Str strGuid = (*it).first;
+ HostOnlyInfo *pInfo = (*it).second;
+ /* We create adapters only if we haven't seen one with the same name */
+ if (aKnownNames.find(pInfo->bstrName) == aKnownNames.end())
+ {
+ /* There is no adapter with such name yet, create it */
+ ComPtr<IHostNetworkInterface> hif;
+ ComPtr<IProgress> progress;
+
+ int vrc = NetIfCreateHostOnlyNetworkInterface(m->pParent, hif.asOutParam(), progress.asOutParam(),
+ pInfo->bstrName.raw());
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Failed to create host-only adapter (%Rrc)\n", vrc));
+ hrc = E_UNEXPECTED;
+ break;
+ }
+
+ /* Wait for the adapter to get configured completely, before we modify IP addresses. */
+ progress->WaitForCompletion(-1);
+ fChangesMade = true;
+ if (pInfo->fDhcpEnabled)
+ {
+ hrc = hif->EnableDynamicIPConfig();
+ if (FAILED(hrc))
+ LogRel(("EnableDynamicIPConfig failed with 0x%x\n", hrc));
+ }
+ else
+ {
+ hrc = hif->EnableStaticIPConfig(pInfo->strIPv4Address.raw(), pInfo->strIPv4NetMask.raw());
+ if (FAILED(hrc))
+ LogRel(("EnableStaticIpConfig failed with 0x%x\n", hrc));
+ }
+# if 0
+ /* Somehow HostNetworkInterface::EnableStaticIPConfigV6 is not implemented yet. */
+ if (SUCCEEDED(hrc))
+ {
+ hrc = hif->EnableStaticIPConfigV6(pInfo->strIPv6Address.raw(), pInfo->uIPv6PrefixLength);
+ if (FAILED(hrc))
+ LogRel(("EnableStaticIPConfigV6 failed with 0x%x\n", hrc));
+ }
+# endif
+ /* Now we have seen this name */
+ aKnownNames.insert(pInfo->bstrName);
+ /* Drop the old config as the newly created adapter has different GUID */
+ i_removePersistentConfig(strGuid);
+ }
+ delete pInfo;
+ }
+ /* Update the list again if we have created some adapters */
+ if (SUCCEEDED(hrc) && fChangesMade)
+ hrc = i_updateNetIfList();
+
+ return hrc;
+}
+#endif /* defined(RT_OS_WINDOWS) */
+
+/**
+ * Returns a list of host network interfaces.
+ *
+ * @returns COM status code
+ * @param aNetworkInterfaces address of result pointer
+ */
+HRESULT Host::getNetworkInterfaces(std::vector<ComPtr<IHostNetworkInterface> > &aNetworkInterfaces)
+{
+#if defined(RT_OS_WINDOWS) || defined(VBOX_WITH_NETFLT) /*|| defined(RT_OS_OS2)*/
+# ifdef VBOX_WITH_HOSTNETIF_API
+ HRESULT rc = i_updateNetIfList();
+ if (FAILED(rc))
+ {
+ Log(("Failed to update host network interface list with rc=%Rhrc\n", rc));
+ return rc;
+ }
+#if defined(RT_OS_WINDOWS)
+ rc = i_updatePersistentConfigForHostOnlyAdapters();
+ if (FAILED(rc))
+ {
+ LogRel(("Failed to update persistent config for host-only adapters with rc=%Rhrc\n", rc));
+ return rc;
+ }
+#endif /* defined(RT_OS_WINDOWS) */
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aNetworkInterfaces.resize(m->llNetIfs.size());
+ size_t i = 0;
+ for (HostNetworkInterfaceList::iterator it = m->llNetIfs.begin(); it != m->llNetIfs.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aNetworkInterfaces[i].asOutParam());
+
+ return S_OK;
+# else
+ std::list<ComObjPtr<HostNetworkInterface> > list;
+
+# if defined(RT_OS_DARWIN)
+ PDARWINETHERNIC pEtherNICs = DarwinGetEthernetControllers();
+ while (pEtherNICs)
+ {
+ ComObjPtr<HostNetworkInterface> IfObj;
+ IfObj.createObject();
+ if (SUCCEEDED(IfObj->init(Bstr(pEtherNICs->szName), Guid(pEtherNICs->Uuid), HostNetworkInterfaceType_Bridged)))
+ list.push_back(IfObj);
+
+ /* next, free current */
+ void *pvFree = pEtherNICs;
+ pEtherNICs = pEtherNICs->pNext;
+ RTMemFree(pvFree);
+ }
+
+# elif defined RT_OS_WINDOWS
+# ifndef VBOX_WITH_NETFLT
+ hr = E_NOTIMPL;
+# else /* # if defined VBOX_WITH_NETFLT */
+ INetCfg *pNc;
+ INetCfgComponent *pMpNcc;
+ INetCfgComponent *pTcpIpNcc;
+ LPWSTR lpszApp;
+ HRESULT hr;
+ IEnumNetCfgBindingPath *pEnumBp;
+ INetCfgBindingPath *pBp;
+ IEnumNetCfgBindingInterface *pEnumBi;
+ INetCfgBindingInterface *pBi;
+
+ /* we are using the INetCfg API for getting the list of miniports */
+ hr = VBoxNetCfgWinQueryINetCfg(FALSE,
+ VBOX_APP_NAME,
+ &pNc,
+ &lpszApp);
+ Assert(hr == S_OK);
+ if (hr == S_OK)
+ {
+# ifdef VBOX_NETFLT_ONDEMAND_BIND
+ /* for the protocol-based approach for now we just get all miniports the MS_TCPIP protocol binds to */
+ hr = pNc->FindComponent(L"MS_TCPIP", &pTcpIpNcc);
+# else
+ /* for the filter-based approach we get all miniports our filter (oracle_VBoxNetLwf)is bound to */
+ hr = pNc->FindComponent(L"oracle_VBoxNetLwf", &pTcpIpNcc);
+ if (hr != S_OK)
+ {
+ /* fall back to NDIS5 miniport lookup (sun_VBoxNetFlt) */
+ hr = pNc->FindComponent(L"sun_VBoxNetFlt", &pTcpIpNcc);
+ }
+# ifndef VBOX_WITH_HARDENING
+ if (hr != S_OK)
+ {
+ /** @todo try to install the netflt from here */
+ }
+# endif
+
+# endif
+
+ if (hr == S_OK)
+ {
+ hr = VBoxNetCfgWinGetBindingPathEnum(pTcpIpNcc, EBP_BELOW, &pEnumBp);
+ Assert(hr == S_OK);
+ if (hr == S_OK)
+ {
+ hr = VBoxNetCfgWinGetFirstBindingPath(pEnumBp, &pBp);
+ Assert(hr == S_OK || hr == S_FALSE);
+ while (hr == S_OK)
+ {
+ /* S_OK == enabled, S_FALSE == disabled */
+ if (pBp->IsEnabled() == S_OK)
+ {
+ hr = VBoxNetCfgWinGetBindingInterfaceEnum(pBp, &pEnumBi);
+ Assert(hr == S_OK);
+ if (hr == S_OK)
+ {
+ hr = VBoxNetCfgWinGetFirstBindingInterface(pEnumBi, &pBi);
+ Assert(hr == S_OK);
+ while (hr == S_OK)
+ {
+ hr = pBi->GetLowerComponent(&pMpNcc);
+ Assert(hr == S_OK);
+ if (hr == S_OK)
+ {
+ ULONG uComponentStatus;
+ hr = pMpNcc->GetDeviceStatus(&uComponentStatus);
+ Assert(hr == S_OK);
+ if (hr == S_OK)
+ {
+ if (uComponentStatus == 0)
+ {
+ vboxNetWinAddComponent(&list, pMpNcc);
+ }
+ }
+ VBoxNetCfgWinReleaseRef(pMpNcc);
+ }
+ VBoxNetCfgWinReleaseRef(pBi);
+
+ hr = VBoxNetCfgWinGetNextBindingInterface(pEnumBi, &pBi);
+ }
+ VBoxNetCfgWinReleaseRef(pEnumBi);
+ }
+ }
+ VBoxNetCfgWinReleaseRef(pBp);
+
+ hr = VBoxNetCfgWinGetNextBindingPath(pEnumBp, &pBp);
+ }
+ VBoxNetCfgWinReleaseRef(pEnumBp);
+ }
+ VBoxNetCfgWinReleaseRef(pTcpIpNcc);
+ }
+ else
+ {
+ LogRel(("failed to get the oracle_VBoxNetLwf(sun_VBoxNetFlt) component, error (0x%x)\n", hr));
+ }
+
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE);
+ }
+# endif /* # if defined VBOX_WITH_NETFLT */
+
+
+# elif defined RT_OS_LINUX
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock >= 0)
+ {
+ char pBuffer[2048];
+ struct ifconf ifConf;
+ ifConf.ifc_len = sizeof(pBuffer);
+ ifConf.ifc_buf = pBuffer;
+ if (ioctl(sock, SIOCGIFCONF, &ifConf) >= 0)
+ {
+ for (struct ifreq *pReq = ifConf.ifc_req; (char*)pReq < pBuffer + ifConf.ifc_len; pReq++)
+ {
+ if (ioctl(sock, SIOCGIFHWADDR, pReq) >= 0)
+ {
+ if (pReq->ifr_hwaddr.sa_family == ARPHRD_ETHER)
+ {
+ RTUUID uuid;
+ Assert(sizeof(uuid) <= sizeof(*pReq));
+ memcpy(&uuid, pReq, sizeof(uuid));
+
+ ComObjPtr<HostNetworkInterface> IfObj;
+ IfObj.createObject();
+ if (SUCCEEDED(IfObj->init(Bstr(pReq->ifr_name), Guid(uuid), HostNetworkInterfaceType_Bridged)))
+ list.push_back(IfObj);
+ }
+ }
+ }
+ }
+ close(sock);
+ }
+# endif /* RT_OS_LINUX */
+
+ aNetworkInterfaces.resize(list.size());
+ size_t i = 0;
+ for (std::list<ComObjPtr<HostNetworkInterface> >::const_iterator it = list.begin(); it != list.end(); ++it, ++i)
+ aNetworkInterfaces[i] = *it;
+
+ return S_OK;
+# endif
+#else
+ /* Not implemented / supported on this platform. */
+ RT_NOREF(aNetworkInterfaces);
+ ReturnComNotImplemented();
+#endif
+}
+
+HRESULT Host::getAudioDevices(std::vector<ComPtr<IHostAudioDevice> > &aAudioDevices)
+{
+ RT_NOREF(aAudioDevices);
+ ReturnComNotImplemented();
+}
+
+HRESULT Host::getUSBDevices(std::vector<ComPtr<IHostUSBDevice> > &aUSBDevices)
+{
+#ifdef VBOX_WITH_USB
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ MultiResult rc = i_checkUSBProxyService();
+ if (FAILED(rc) || SUCCEEDED_WARNING(rc))
+ return rc;
+
+ return m->pUSBProxyService->getDeviceCollection(aUSBDevices);
+#else
+ /* Note: The GUI depends on this method returning E_NOTIMPL with no
+ * extended error info to indicate that USB is simply not available
+ * (w/o treating it as a failure), for example, as in OSE. */
+ RT_NOREF(aUSBDevices);
+ ReturnComNotImplemented();
+#endif
+}
+
+/**
+ * This method return the list of registered name servers
+ */
+HRESULT Host::getNameServers(std::vector<com::Utf8Str> &aNameServers)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return m->hostDnsMonitorProxy.GetNameServers(aNameServers);
+}
+
+
+/**
+ * This method returns the domain name of the host
+ */
+HRESULT Host::getDomainName(com::Utf8Str &aDomainName)
+{
+ /* XXX: note here should be synchronization with thread polling state
+ * changes in name resoving system on host */
+ return m->hostDnsMonitorProxy.GetDomainName(&aDomainName);
+}
+
+
+/**
+ * This method returns the search string.
+ */
+HRESULT Host::getSearchStrings(std::vector<com::Utf8Str> &aSearchStrings)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return m->hostDnsMonitorProxy.GetSearchStrings(aSearchStrings);
+}
+
+HRESULT Host::getUSBDeviceFilters(std::vector<ComPtr<IHostUSBDeviceFilter> > &aUSBDeviceFilters)
+{
+#ifdef VBOX_WITH_USB
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ MultiResult rc = i_checkUSBProxyService();
+ if (FAILED(rc))
+ return rc;
+
+ aUSBDeviceFilters.resize(m->llUSBDeviceFilters.size());
+ size_t i = 0;
+ for (USBDeviceFilterList::iterator it = m->llUSBDeviceFilters.begin(); it != m->llUSBDeviceFilters.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aUSBDeviceFilters[i].asOutParam());
+
+ return rc;
+#else
+ /* Note: The GUI depends on this method returning E_NOTIMPL with no
+ * extended error info to indicate that USB is simply not available
+ * (w/o treating it as a failure), for example, as in OSE. */
+ RT_NOREF(aUSBDeviceFilters);
+ ReturnComNotImplemented();
+#endif
+}
+
+/**
+ * Returns the number of installed logical processors
+ *
+ * @returns COM status code
+ * @param aCount address of result variable
+ */
+
+HRESULT Host::getProcessorCount(ULONG *aCount)
+{
+ // no locking required
+
+ *aCount = RTMpGetPresentCount();
+ return S_OK;
+}
+
+/**
+ * Returns the number of online logical processors
+ *
+ * @returns COM status code
+ * @param aCount address of result variable
+ */
+HRESULT Host::getProcessorOnlineCount(ULONG *aCount)
+{
+ // no locking required
+
+ *aCount = RTMpGetOnlineCount();
+ return S_OK;
+}
+
+/**
+ * Returns the number of installed physical processor cores.
+ *
+ * @returns COM status code
+ * @param aCount address of result variable
+ */
+HRESULT Host::getProcessorCoreCount(ULONG *aCount)
+{
+ // no locking required
+
+ *aCount = RTMpGetPresentCoreCount();
+ return S_OK;
+}
+
+/**
+ * Returns the number of installed physical processor cores.
+ *
+ * @returns COM status code
+ * @param aCount address of result variable
+ */
+HRESULT Host::getProcessorOnlineCoreCount(ULONG *aCount)
+{
+ // no locking required
+
+ *aCount = RTMpGetOnlineCoreCount();
+ return S_OK;
+}
+
+/**
+ * Returns the (approximate) maximum speed of the given host CPU in MHz
+ *
+ * @returns COM status code
+ * @param aCpuId id to get info for.
+ * @param aSpeed address of result variable, speed is 0 if unknown or aCpuId
+ * is invalid.
+ */
+HRESULT Host::getProcessorSpeed(ULONG aCpuId,
+ ULONG *aSpeed)
+{
+ // no locking required
+
+ *aSpeed = RTMpGetMaxFrequency(aCpuId);
+ return S_OK;
+}
+
+/**
+ * Returns a description string for the host CPU
+ *
+ * @returns COM status code
+ * @param aCpuId id to get info for.
+ * @param aDescription address of result variable, empty string if not known
+ * or aCpuId is invalid.
+ */
+HRESULT Host::getProcessorDescription(ULONG aCpuId, com::Utf8Str &aDescription)
+{
+ // no locking required
+
+ int vrc = aDescription.reserveNoThrow(80);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTMpGetDescription(aCpuId, aDescription.mutableRaw(), aDescription.capacity());
+ if (RT_SUCCESS(vrc))
+ {
+ aDescription.jolt();
+ return S_OK;
+ }
+ }
+ return setErrorVrc(vrc);
+}
+
+/**
+ * Updates fVTSupported, fNestedPagingSupported, fUnrestrictedGuestSupported,
+ * fVirtVmsaveVmload and fNestedHWVirtSupported with info from SUPR3QueryVTCaps().
+ *
+ * This is repeated till we successfully open the support driver, in case it
+ * is loaded after VBoxSVC starts.
+ */
+void Host::i_updateProcessorFeatures()
+{
+ /* Perhaps the driver is available now... */
+ int rc = SUPR3InitEx(SUPR3INIT_F_LIMITED, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fVTCaps;
+ rc = SUPR3QueryVTCaps(&fVTCaps);
+ AssertMsg(RT_SUCCESS(rc) || rc == VERR_SUP_DRIVERLESS, ("SUPR3QueryVTCaps failed rc=%Rrc\n", rc));
+
+ SUPR3Term(false);
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+ if (RT_FAILURE(rc))
+ {
+ fVTCaps = 0;
+ if (rc != VERR_SUP_DRIVERLESS)
+ LogRel(("SUPR0QueryVTCaps -> %Rrc\n", rc));
+# if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) /* Preserve detected VT-x/AMD-V support for show. */
+ else
+ fVTCaps = m->fVTSupported ? SUPVTCAPS_AMD_V | SUPVTCAPS_VT_X : 0;
+# endif
+ }
+ m->fVTSupported = (fVTCaps & (SUPVTCAPS_AMD_V | SUPVTCAPS_VT_X)) != 0;
+ m->fNestedPagingSupported = (fVTCaps & SUPVTCAPS_NESTED_PAGING) != 0;
+ m->fUnrestrictedGuestSupported = (fVTCaps & (SUPVTCAPS_AMD_V | SUPVTCAPS_VTX_UNRESTRICTED_GUEST)) != 0;
+ m->fNestedHWVirtSupported = (fVTCaps & (SUPVTCAPS_AMD_V | SUPVTCAPS_NESTED_PAGING))
+ == (SUPVTCAPS_AMD_V | SUPVTCAPS_NESTED_PAGING)
+ || (fVTCaps & ( SUPVTCAPS_VT_X | SUPVTCAPS_NESTED_PAGING
+ | SUPVTCAPS_VTX_UNRESTRICTED_GUEST | SUPVTCAPS_VTX_VMCS_SHADOWING))
+ == ( SUPVTCAPS_VT_X | SUPVTCAPS_NESTED_PAGING
+ | SUPVTCAPS_VTX_UNRESTRICTED_GUEST | SUPVTCAPS_VTX_VMCS_SHADOWING);
+ m->fVirtVmsaveVmload = (fVTCaps & SUPVTCAPS_AMDV_VIRT_VMSAVE_VMLOAD) != 0;
+ m->fRecheckVTSupported = false; /* No need to try again, we cached everything. */
+ }
+}
+
+/**
+ * Returns whether a host processor feature is supported or not
+ *
+ * @returns COM status code
+ * @param aFeature to query.
+ * @param aSupported supported bool result variable
+ */
+HRESULT Host::getProcessorFeature(ProcessorFeature_T aFeature, BOOL *aSupported)
+{
+ /* Validate input. */
+ switch (aFeature)
+ {
+ case ProcessorFeature_HWVirtEx:
+ case ProcessorFeature_PAE:
+ case ProcessorFeature_LongMode:
+ case ProcessorFeature_NestedPaging:
+ case ProcessorFeature_UnrestrictedGuest:
+ case ProcessorFeature_NestedHWVirt:
+ case ProcessorFeature_VirtVmsaveVmload:
+ break;
+ default:
+ return setError(E_INVALIDARG, tr("The aFeature value %d (%#x) is out of range."), (int)aFeature, (int)aFeature);
+ }
+
+ /* Do the job. */
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( m->fRecheckVTSupported
+ && ( aFeature == ProcessorFeature_HWVirtEx
+ || aFeature == ProcessorFeature_NestedPaging
+ || aFeature == ProcessorFeature_UnrestrictedGuest
+ || aFeature == ProcessorFeature_NestedHWVirt
+ || aFeature == ProcessorFeature_VirtVmsaveVmload)
+ )
+ {
+ alock.release();
+ i_updateProcessorFeatures();
+ alock.acquire();
+ }
+
+ switch (aFeature)
+ {
+ case ProcessorFeature_HWVirtEx:
+ *aSupported = m->fVTSupported;
+ break;
+
+ case ProcessorFeature_PAE:
+ *aSupported = m->fPAESupported;
+ break;
+
+ case ProcessorFeature_LongMode:
+ *aSupported = m->fLongModeSupported;
+ break;
+
+ case ProcessorFeature_NestedPaging:
+ *aSupported = m->fNestedPagingSupported;
+ break;
+
+ case ProcessorFeature_UnrestrictedGuest:
+ *aSupported = m->fUnrestrictedGuestSupported;
+ break;
+
+ case ProcessorFeature_NestedHWVirt:
+ *aSupported = m->fNestedHWVirtSupported;
+ break;
+
+ case ProcessorFeature_VirtVmsaveVmload:
+ *aSupported = m->fVirtVmsaveVmload;
+ break;
+
+ default:
+ AssertFailed();
+ }
+ }
+ return hrc;
+}
+
+/**
+ * Returns the specific CPUID leaf.
+ *
+ * @returns COM status code
+ * @param aCpuId The CPU number. Mostly ignored.
+ * @param aLeaf The leaf number.
+ * @param aSubLeaf The sub-leaf number.
+ * @param aValEAX Where to return EAX.
+ * @param aValEBX Where to return EBX.
+ * @param aValECX Where to return ECX.
+ * @param aValEDX Where to return EDX.
+ */
+HRESULT Host::getProcessorCPUIDLeaf(ULONG aCpuId, ULONG aLeaf, ULONG aSubLeaf,
+ ULONG *aValEAX, ULONG *aValEBX, ULONG *aValECX, ULONG *aValEDX)
+{
+ // no locking required
+
+ /* Check that the CPU is online. */
+ /** @todo later use RTMpOnSpecific. */
+ if (!RTMpIsCpuOnline(aCpuId))
+ return RTMpIsCpuPresent(aCpuId)
+ ? setError(E_FAIL, tr("CPU no.%u is not present"), aCpuId)
+ : setError(E_FAIL, tr("CPU no.%u is not online"), aCpuId);
+
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+ uint32_t uEAX, uEBX, uECX, uEDX;
+ ASMCpuId_Idx_ECX(aLeaf, aSubLeaf, &uEAX, &uEBX, &uECX, &uEDX);
+ *aValEAX = uEAX;
+ *aValEBX = uEBX;
+ *aValECX = uECX;
+ *aValEDX = uEDX;
+#else
+ *aValEAX = 0;
+ *aValEBX = 0;
+ *aValECX = 0;
+ *aValEDX = 0;
+#endif
+
+ return S_OK;
+}
+
+/**
+ * Returns the amount of installed system memory in megabytes
+ *
+ * @returns COM status code
+ * @param aSize address of result variable
+ */
+HRESULT Host::getMemorySize(ULONG *aSize)
+{
+ // no locking required
+
+ uint64_t cb;
+ int rc = RTSystemQueryTotalRam(&cb);
+ if (RT_FAILURE(rc))
+ return E_FAIL;
+ *aSize = (ULONG)(cb / _1M);
+ return S_OK;
+}
+
+/**
+ * Returns the current system memory free space in megabytes
+ *
+ * @returns COM status code
+ * @param aAvailable address of result variable
+ */
+HRESULT Host::getMemoryAvailable(ULONG *aAvailable)
+{
+ // no locking required
+
+ uint64_t cb;
+ int rc = RTSystemQueryAvailableRam(&cb);
+ if (RT_FAILURE(rc))
+ return E_FAIL;
+ *aAvailable = (ULONG)(cb / _1M);
+ return S_OK;
+}
+
+/**
+ * Returns the name string of the host operating system
+ *
+ * @returns COM status code
+ * @param aOperatingSystem result variable
+ */
+HRESULT Host::getOperatingSystem(com::Utf8Str &aOperatingSystem)
+{
+ // no locking required
+
+ char szOSName[80];
+ int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szOSName, sizeof(szOSName));
+ if (RT_FAILURE(vrc))
+ return E_FAIL; /** @todo error reporting? */
+ aOperatingSystem = Utf8Str(szOSName);
+ return S_OK;
+}
+
+/**
+ * Returns the version string of the host operating system
+ *
+ * @returns COM status code
+ * @param aVersion address of result variable
+ */
+HRESULT Host::getOSVersion(com::Utf8Str &aVersion)
+{
+ // no locking required
+
+ /* Get the OS release. Reserve some buffer space for the service pack. */
+ char szOSRelease[128];
+ int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOSRelease, sizeof(szOSRelease) - 32);
+ if (RT_FAILURE(vrc))
+ return E_FAIL; /** @todo error reporting? */
+
+ /* Append the service pack if present. */
+ char szOSServicePack[80];
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szOSServicePack, sizeof(szOSServicePack));
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc != VERR_NOT_SUPPORTED)
+ return E_FAIL; /** @todo error reporting? */
+ szOSServicePack[0] = '\0';
+ }
+ if (szOSServicePack[0] != '\0')
+ {
+ char *psz = strchr(szOSRelease, '\0');
+ RTStrPrintf(psz, (size_t)(&szOSRelease[sizeof(szOSRelease)] - psz), "sp%s", szOSServicePack);
+ }
+
+ aVersion = szOSRelease;
+ return S_OK;
+}
+
+/**
+ * Returns the current host time in milliseconds since 1970-01-01 UTC.
+ *
+ * @returns COM status code
+ * @param aUTCTime address of result variable
+ */
+HRESULT Host::getUTCTime(LONG64 *aUTCTime)
+{
+ // no locking required
+
+ RTTIMESPEC now;
+ *aUTCTime = RTTimeSpecGetMilli(RTTimeNow(&now));
+
+ return S_OK;
+}
+
+
+HRESULT Host::getAcceleration3DAvailable(BOOL *aSupported)
+{
+ HRESULT hrc = S_OK;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (m->f3DAccelerationSupported != -1)
+ *aSupported = m->f3DAccelerationSupported;
+ else
+ {
+ alock.release();
+
+#ifdef VBOX_WITH_3D_ACCELERATION
+ bool fSupported = VBoxOglIs3DAccelerationSupported();
+#else
+ bool fSupported = false; /* shouldn't get here, but just in case. */
+#endif
+ AutoWriteLock alock2(this COMMA_LOCKVAL_SRC_POS);
+
+ m->f3DAccelerationSupported = fSupported;
+ alock2.release();
+ *aSupported = fSupported;
+ }
+
+#ifdef DEBUG_misha
+ AssertMsgFailed(("should not be here any more!\n"));
+#endif
+
+ return hrc;
+}
+
+HRESULT Host::createHostOnlyNetworkInterface(ComPtr<IHostNetworkInterface> &aHostInterface,
+ ComPtr<IProgress> &aProgress)
+
+{
+#ifdef VBOX_WITH_HOSTNETIF_API
+ /* No need to lock anything. If there ever will - watch out, the function
+ * called below grabs the VirtualBox lock. */
+
+ int vrc = NetIfCreateHostOnlyNetworkInterface(m->pParent, aHostInterface.asOutParam(), aProgress.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (aHostInterface.isNull())
+ return setError(E_FAIL,
+ tr("Unable to create a host network interface"));
+
+# if !defined(RT_OS_WINDOWS)
+ Bstr tmpAddr, tmpMask, tmpName;
+ HRESULT hrc;
+ hrc = aHostInterface->COMGETTER(Name)(tmpName.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ hrc = aHostInterface->COMGETTER(IPAddress)(tmpAddr.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ hrc = aHostInterface->COMGETTER(NetworkMask)(tmpMask.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+
+ /*
+ * We need to write the default IP address and mask to extra data now,
+ * so the interface gets re-created after vboxnetadp.ko reload.
+ * Note that we avoid calling EnableStaticIpConfig since it would
+ * change the address on host's interface as well and we want to
+ * postpone the change until VM actually starts.
+ */
+ hrc = m->pParent->SetExtraData(BstrFmt("HostOnly/%ls/IPAddress", tmpName.raw()).raw(),
+ tmpAddr.raw());
+ ComAssertComRCRet(hrc, hrc);
+
+ hrc = m->pParent->SetExtraData(BstrFmt("HostOnly/%ls/IPNetMask", tmpName.raw()).raw(),
+ tmpMask.raw());
+ ComAssertComRCRet(hrc, hrc);
+# endif /* !defined(RT_OS_WINDOWS) */
+ }
+
+ return S_OK;
+#else
+ RT_NOREF(aHostInterface, aProgress);
+ return E_NOTIMPL;
+#endif
+}
+
+
+#ifdef RT_OS_WINDOWS
+HRESULT Host::i_removePersistentConfig(const Bstr &bstrGuid)
+{
+ HRESULT hrc = m->pParent->SetExtraData(BstrFmt("HostOnly/{%ls}/Name", bstrGuid.raw()).raw(), NULL);
+ if (SUCCEEDED(hrc)) hrc = m->pParent->SetExtraData(BstrFmt("HostOnly/{%ls}/IPAddress", bstrGuid.raw()).raw(), NULL);
+ if (SUCCEEDED(hrc)) hrc = m->pParent->SetExtraData(BstrFmt("HostOnly/{%ls}/IPNetMask", bstrGuid.raw()).raw(), NULL);
+ if (SUCCEEDED(hrc)) hrc = m->pParent->SetExtraData(BstrFmt("HostOnly/{%ls}/IPV6Address", bstrGuid.raw()).raw(), NULL);
+ if (SUCCEEDED(hrc)) hrc = m->pParent->SetExtraData(BstrFmt("HostOnly/{%ls}/IPV6PrefixLen", bstrGuid.raw()).raw(), NULL);
+ return hrc;
+}
+#endif /* RT_OS_WINDOWS */
+
+HRESULT Host::removeHostOnlyNetworkInterface(const com::Guid &aId,
+ ComPtr<IProgress> &aProgress)
+
+{
+#ifdef VBOX_WITH_HOSTNETIF_API
+ /* No need to lock anything, the code below does not touch the state
+ * of the host object. If that ever changes then check for lock order
+ * violations with the called functions. */
+
+ Bstr name;
+ HRESULT rc;
+
+ /* first check whether an interface with the given name already exists */
+ {
+ ComPtr<IHostNetworkInterface> iface;
+ rc = findHostNetworkInterfaceById(aId, iface);
+ if (FAILED(rc))
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Host network interface with UUID {%RTuuid} does not exist"),
+ Guid(aId).raw());
+ rc = iface->COMGETTER(Name)(name.asOutParam());
+ ComAssertComRCRet(rc, rc);
+ }
+
+ int r = NetIfRemoveHostOnlyNetworkInterface(m->pParent, aId, aProgress.asOutParam());
+ if (RT_SUCCESS(r))
+ {
+ /* Drop configuration parameters for removed interface */
+#ifdef RT_OS_WINDOWS
+ rc = i_removePersistentConfig(Utf8StrFmt("%RTuuid", &aId));
+ if (FAILED(rc))
+ LogRel(("i_removePersistentConfig(%RTuuid) failed with 0x%x\n", &aId, rc));
+#else /* !RT_OS_WINDOWS */
+ rc = m->pParent->SetExtraData(BstrFmt("HostOnly/%ls/IPAddress", name.raw()).raw(), NULL);
+ rc = m->pParent->SetExtraData(BstrFmt("HostOnly/%ls/IPNetMask", name.raw()).raw(), NULL);
+ rc = m->pParent->SetExtraData(BstrFmt("HostOnly/%ls/IPV6Address", name.raw()).raw(), NULL);
+ rc = m->pParent->SetExtraData(BstrFmt("HostOnly/%ls/IPV6NetMask", name.raw()).raw(), NULL);
+#endif /* !RT_OS_WINDOWS */
+
+ return S_OK;
+ }
+
+ return r == VERR_NOT_IMPLEMENTED ? E_NOTIMPL : E_FAIL;
+#else
+ RT_NOREF(aId, aProgress);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Host::createUSBDeviceFilter(const com::Utf8Str &aName,
+ ComPtr<IHostUSBDeviceFilter> &aFilter)
+{
+#ifdef VBOX_WITH_USB
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<HostUSBDeviceFilter> filter;
+ filter.createObject();
+ HRESULT rc = filter->init(this, Bstr(aName).raw());
+ ComAssertComRCRet(rc, rc);
+ rc = filter.queryInterfaceTo(aFilter.asOutParam());
+ AssertComRCReturn(rc, rc);
+ return S_OK;
+#else
+ /* Note: The GUI depends on this method returning E_NOTIMPL with no
+ * extended error info to indicate that USB is simply not available
+ * (w/o treating it as a failure), for example, as in OSE. */
+ RT_NOREF(aName, aFilter);
+ ReturnComNotImplemented();
+#endif
+}
+
+HRESULT Host::insertUSBDeviceFilter(ULONG aPosition,
+ const ComPtr<IHostUSBDeviceFilter> &aFilter)
+{
+#ifdef VBOX_WITH_USB
+ /* Note: HostUSBDeviceFilter and USBProxyService also uses this lock. */
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ MultiResult rc = i_checkUSBProxyService();
+ if (FAILED(rc))
+ return rc;
+
+ ComObjPtr<HostUSBDeviceFilter> pFilter;
+ for (USBDeviceFilterList::iterator it = m->llChildren.begin();
+ it != m->llChildren.end();
+ ++it)
+ {
+ if (*it == aFilter)
+ {
+ pFilter = *it;
+ break;
+ }
+ }
+ if (pFilter.isNull())
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The given USB device filter is not created within this VirtualBox instance"));
+
+ if (pFilter->mInList)
+ return setError(E_INVALIDARG,
+ tr("The given USB device filter is already in the list"));
+
+ /* iterate to the position... */
+ USBDeviceFilterList::iterator itPos = m->llUSBDeviceFilters.begin();
+ std::advance(itPos, aPosition);
+ /* ...and insert */
+ m->llUSBDeviceFilters.insert(itPos, pFilter);
+ pFilter->mInList = true;
+
+ /* notify the proxy (only when the filter is active) */
+ if ( m->pUSBProxyService->isActive()
+ && pFilter->i_getData().mData.fActive)
+ {
+ ComAssertRet(pFilter->i_getId() == NULL, E_FAIL);
+ pFilter->i_getId() = m->pUSBProxyService->insertFilter(&pFilter->i_getData().mUSBFilter);
+ }
+
+ // save the global settings; for that we should hold only the VirtualBox lock
+ alock.release();
+ AutoWriteLock vboxLock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ return rc = m->pParent->i_saveSettings();
+#else
+
+ /* Note: The GUI depends on this method returning E_NOTIMPL with no
+ * extended error info to indicate that USB is simply not available
+ * (w/o treating it as a failure), for example, as in OSE. */
+ RT_NOREF(aPosition, aFilter);
+ ReturnComNotImplemented();
+#endif
+}
+
+HRESULT Host::removeUSBDeviceFilter(ULONG aPosition)
+{
+#ifdef VBOX_WITH_USB
+
+ /* Note: HostUSBDeviceFilter and USBProxyService also uses this lock. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ MultiResult rc = i_checkUSBProxyService();
+ if (FAILED(rc))
+ return rc;
+
+ if (!m->llUSBDeviceFilters.size())
+ return setError(E_INVALIDARG,
+ tr("The USB device filter list is empty"));
+
+ if (aPosition >= m->llUSBDeviceFilters.size())
+ return setError(E_INVALIDARG,
+ tr("Invalid position: %lu (must be in range [0, %lu])"),
+ aPosition, m->llUSBDeviceFilters.size() - 1);
+
+ ComObjPtr<HostUSBDeviceFilter> filter;
+ {
+ /* iterate to the position... */
+ USBDeviceFilterList::iterator it = m->llUSBDeviceFilters.begin();
+ std::advance(it, aPosition);
+ /* ...get an element from there... */
+ filter = *it;
+ /* ...and remove */
+ filter->mInList = false;
+ m->llUSBDeviceFilters.erase(it);
+ }
+
+ /* notify the proxy (only when the filter is active) */
+ if (m->pUSBProxyService->isActive() && filter->i_getData().mData.fActive)
+ {
+ ComAssertRet(filter->i_getId() != NULL, E_FAIL);
+ m->pUSBProxyService->removeFilter(filter->i_getId());
+ filter->i_getId() = NULL;
+ }
+
+ // save the global settings; for that we should hold only the VirtualBox lock
+ alock.release();
+ AutoWriteLock vboxLock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ return rc = m->pParent->i_saveSettings();
+#else
+ /* Note: The GUI depends on this method returning E_NOTIMPL with no
+ * extended error info to indicate that USB is simply not available
+ * (w/o treating it as a failure), for example, as in OSE. */
+ RT_NOREF(aPosition);
+ ReturnComNotImplemented();
+#endif
+}
+
+HRESULT Host::findHostDVDDrive(const com::Utf8Str &aName,
+ ComPtr<IMedium> &aDrive)
+{
+ ComObjPtr<Medium> medium;
+ HRESULT rc = i_findHostDriveByNameOrId(DeviceType_DVD, aName, medium);
+ if (SUCCEEDED(rc))
+ rc = medium.queryInterfaceTo(aDrive.asOutParam());
+ else
+ rc = setError(rc, tr("The host DVD drive named '%s' could not be found"), aName.c_str());
+ return rc;
+}
+
+HRESULT Host::findHostFloppyDrive(const com::Utf8Str &aName, ComPtr<IMedium> &aDrive)
+{
+ aDrive = NULL;
+
+ ComObjPtr<Medium>medium;
+
+ HRESULT rc = i_findHostDriveByNameOrId(DeviceType_Floppy, aName, medium);
+ if (SUCCEEDED(rc))
+ return medium.queryInterfaceTo(aDrive.asOutParam());
+ else
+ return setError(rc, tr("The host floppy drive named '%s' could not be found"), aName.c_str());
+}
+
+HRESULT Host::findHostNetworkInterfaceByName(const com::Utf8Str &aName,
+ ComPtr<IHostNetworkInterface> &aNetworkInterface)
+{
+#ifndef VBOX_WITH_HOSTNETIF_API
+ RT_NOREF(aName, aNetworkInterface);
+ return E_NOTIMPL;
+#else
+ if (!aName.length())
+ return E_INVALIDARG;
+
+ HRESULT rc = i_updateNetIfList();
+ if (FAILED(rc))
+ {
+ Log(("Failed to update host network interface list with rc=%Rhrc\n", rc));
+ return rc;
+ }
+#if defined(RT_OS_WINDOWS)
+ rc = i_updatePersistentConfigForHostOnlyAdapters();
+ if (FAILED(rc))
+ {
+ LogRel(("Failed to update persistent config for host-only adapters with rc=%Rhrc\n", rc));
+ return rc;
+ }
+#endif /* defined(RT_OS_WINDOWS) */
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<HostNetworkInterface> found;
+ for (HostNetworkInterfaceList::iterator it = m->llNetIfs.begin(); it != m->llNetIfs.end(); ++it)
+ {
+ Bstr n;
+ (*it)->COMGETTER(Name)(n.asOutParam());
+ if (n == aName)
+ found = *it;
+ }
+
+ if (!found)
+ return setError(E_INVALIDARG,
+ tr("The host network interface named '%s' could not be found"), aName.c_str());
+
+ return found.queryInterfaceTo(aNetworkInterface.asOutParam());
+#endif
+}
+
+HRESULT Host::findHostNetworkInterfaceById(const com::Guid &aId,
+ ComPtr<IHostNetworkInterface> &aNetworkInterface)
+{
+#ifndef VBOX_WITH_HOSTNETIF_API
+ RT_NOREF(aId, aNetworkInterface);
+ return E_NOTIMPL;
+#else
+ if (!aId.isValid())
+ return E_INVALIDARG;
+
+ HRESULT rc = i_updateNetIfList();
+ if (FAILED(rc))
+ {
+ Log(("Failed to update host network interface list with rc=%Rhrc\n", rc));
+ return rc;
+ }
+#if defined(RT_OS_WINDOWS)
+ rc = i_updatePersistentConfigForHostOnlyAdapters();
+ if (FAILED(rc))
+ {
+ LogRel(("Failed to update persistent config for host-only adapters with rc=%Rhrc\n", rc));
+ return rc;
+ }
+#endif /* defined(RT_OS_WINDOWS) */
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<HostNetworkInterface> found;
+ for (HostNetworkInterfaceList::iterator it = m->llNetIfs.begin(); it != m->llNetIfs.end(); ++it)
+ {
+ Bstr g;
+ (*it)->COMGETTER(Id)(g.asOutParam());
+ if (Guid(g) == aId)
+ found = *it;
+ }
+
+ if (!found)
+ return setError(E_INVALIDARG,
+ tr("The host network interface with the given GUID could not be found"));
+ return found.queryInterfaceTo(aNetworkInterface.asOutParam());
+
+#endif
+}
+
+HRESULT Host::findHostNetworkInterfacesOfType(HostNetworkInterfaceType_T aType,
+ std::vector<ComPtr<IHostNetworkInterface> > &aNetworkInterfaces)
+{
+#ifdef VBOX_WITH_HOSTNETIF_API
+ HRESULT rc = i_updateNetIfList();
+ if (FAILED(rc))
+ {
+ Log(("Failed to update host network interface list with rc=%Rhrc\n", rc));
+ return rc;
+ }
+#if defined(RT_OS_WINDOWS)
+ rc = i_updatePersistentConfigForHostOnlyAdapters();
+ if (FAILED(rc))
+ {
+ LogRel(("Failed to update persistent config for host-only adapters with rc=%Rhrc\n", rc));
+ return rc;
+ }
+#endif /* defined(RT_OS_WINDOWS) */
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HostNetworkInterfaceList resultList;
+ for (HostNetworkInterfaceList::iterator it = m->llNetIfs.begin(); it != m->llNetIfs.end(); ++it)
+ {
+ HostNetworkInterfaceType_T t;
+ HRESULT hr = (*it)->COMGETTER(InterfaceType)(&t);
+ if (FAILED(hr))
+ return hr;
+
+ if (t == aType)
+ resultList.push_back(*it);
+ }
+ aNetworkInterfaces.resize(resultList.size());
+ size_t i = 0;
+ for (HostNetworkInterfaceList::iterator it = resultList.begin(); it != resultList.end(); ++it, ++i)
+ {
+ (*it).queryInterfaceTo(aNetworkInterfaces[i].asOutParam());
+ }
+
+ return S_OK;
+#else
+ RT_NOREF(aType, aNetworkInterfaces);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Host::findUSBDeviceByAddress(const com::Utf8Str &aName,
+ ComPtr<IHostUSBDevice> &aDevice)
+{
+#ifdef VBOX_WITH_USB
+
+ aDevice = NULL;
+ SafeIfaceArray<IHostUSBDevice> devsvec;
+ HRESULT rc = COMGETTER(USBDevices)(ComSafeArrayAsOutParam(devsvec));
+ if (FAILED(rc))
+ return rc;
+
+ for (size_t i = 0; i < devsvec.size(); ++i)
+ {
+ Bstr address;
+ rc = devsvec[i]->COMGETTER(Address)(address.asOutParam());
+ if (FAILED(rc))
+ return rc;
+ if (address == aName)
+ {
+ return (ComPtr<IHostUSBDevice>(devsvec[i]).queryInterfaceTo(aDevice.asOutParam()));
+ }
+ }
+
+ return setErrorNoLog(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Could not find a USB device with address '%s'"),
+ aName.c_str());
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aName, aDevice);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_USB */
+}
+HRESULT Host::findUSBDeviceById(const com::Guid &aId,
+ ComPtr<IHostUSBDevice> &aDevice)
+{
+#ifdef VBOX_WITH_USB
+ if (!aId.isValid())
+ return E_INVALIDARG;
+
+ aDevice = NULL;
+
+ SafeIfaceArray<IHostUSBDevice> devsvec;
+ HRESULT rc = COMGETTER(USBDevices)(ComSafeArrayAsOutParam(devsvec));
+ if (FAILED(rc))
+ return rc;
+
+ for (size_t i = 0; i < devsvec.size(); ++i)
+ {
+ Bstr id;
+ rc = devsvec[i]->COMGETTER(Id)(id.asOutParam());
+ if (FAILED(rc))
+ return rc;
+ if (Guid(id) == aId)
+ {
+ return (ComPtr<IHostUSBDevice>(devsvec[i]).queryInterfaceTo(aDevice.asOutParam()));
+ }
+ }
+ return setErrorNoLog(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Could not find a USB device with uuid {%RTuuid}"),
+ aId.raw());
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aId, aDevice);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_USB */
+}
+
+HRESULT Host::generateMACAddress(com::Utf8Str &aAddress)
+{
+ // no locking required
+ i_generateMACAddress(aAddress);
+ return S_OK;
+}
+
+/**
+ * Returns a list of host video capture devices (webcams, etc).
+ *
+ * @returns COM status code
+ * @param aVideoInputDevices Array of interface pointers to be filled.
+ */
+HRESULT Host::getVideoInputDevices(std::vector<ComPtr<IHostVideoInputDevice> > &aVideoInputDevices)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HostVideoInputDeviceList list;
+
+ HRESULT rc = HostVideoInputDevice::queryHostDevices(m->pParent, &list);
+ if (FAILED(rc))
+ return rc;
+
+ aVideoInputDevices.resize(list.size());
+ size_t i = 0;
+ for (HostVideoInputDeviceList::const_iterator it = list.begin(); it != list.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aVideoInputDevices[i].asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Host::addUSBDeviceSource(const com::Utf8Str &aBackend, const com::Utf8Str &aId, const com::Utf8Str &aAddress,
+ const std::vector<com::Utf8Str> &aPropertyNames, const std::vector<com::Utf8Str> &aPropertyValues)
+{
+#ifdef VBOX_WITH_USB
+ /* The USB proxy service will do the locking. */
+ return m->pUSBProxyService->addUSBDeviceSource(aBackend, aId, aAddress, aPropertyNames, aPropertyValues);
+#else
+ RT_NOREF(aBackend, aId, aAddress, aPropertyNames, aPropertyValues);
+ ReturnComNotImplemented();
+#endif
+}
+
+HRESULT Host::removeUSBDeviceSource(const com::Utf8Str &aId)
+{
+#ifdef VBOX_WITH_USB
+ /* The USB proxy service will do the locking. */
+ return m->pUSBProxyService->removeUSBDeviceSource(aId);
+#else
+ RT_NOREF(aId);
+ ReturnComNotImplemented();
+#endif
+}
+
+HRESULT Host::getUpdateHost(ComPtr<IUpdateAgent> &aUpdate)
+{
+#ifdef VBOX_WITH_UPDATE_AGENT
+ HRESULT hrc = m->pUpdateHost.queryInterfaceTo(aUpdate.asOutParam());
+ return hrc;
+#else
+ RT_NOREF(aUpdate);
+ ReturnComNotImplemented();
+#endif
+}
+
+HRESULT Host::getUpdateExtPack(ComPtr<IUpdateAgent> &aUpdate)
+{
+ RT_NOREF(aUpdate);
+ ReturnComNotImplemented();
+}
+
+HRESULT Host::getUpdateGuestAdditions(ComPtr<IUpdateAgent> &aUpdate)
+{
+ RT_NOREF(aUpdate);
+ ReturnComNotImplemented();
+}
+
+HRESULT Host::getHostDrives(std::vector<ComPtr<IHostDrive> > &aHostDrives)
+{
+ std::list<std::pair<com::Utf8Str, com::Utf8Str> > llDrivesPathsList;
+ HRESULT hrc = i_getDrivesPathsList(llDrivesPathsList);
+ if (SUCCEEDED(hrc))
+ {
+ for (std::list<std::pair<com::Utf8Str, com::Utf8Str> >::const_iterator it = llDrivesPathsList.begin();
+ it != llDrivesPathsList.end();
+ ++it)
+ {
+ ComObjPtr<HostDrive> pHostDrive;
+ hrc = pHostDrive.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = pHostDrive->initFromPathAndModel(it->first, it->second);
+ if (FAILED(hrc))
+ break;
+ aHostDrives.push_back(pHostDrive);
+ }
+ }
+ return hrc;
+}
+
+
+// public methods only for internal purposes
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT Host::i_loadSettings(const settings::Host &data)
+{
+ HRESULT rc = S_OK;
+#ifdef VBOX_WITH_USB
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (settings::USBDeviceFiltersList::const_iterator it = data.llUSBDeviceFilters.begin();
+ it != data.llUSBDeviceFilters.end();
+ ++it)
+ {
+ const settings::USBDeviceFilter &f = *it;
+ ComObjPtr<HostUSBDeviceFilter> pFilter;
+ pFilter.createObject();
+ rc = pFilter->init(this, f);
+ if (FAILED(rc))
+ break;
+
+ m->llUSBDeviceFilters.push_back(pFilter);
+ pFilter->mInList = true;
+
+ /* notify the proxy (only when the filter is active) */
+ if (pFilter->i_getData().mData.fActive)
+ {
+ HostUSBDeviceFilter *flt = pFilter; /* resolve ambiguity */
+ flt->i_getId() = m->pUSBProxyService->insertFilter(&pFilter->i_getData().mUSBFilter);
+ }
+ }
+
+ rc = m->pUSBProxyService->i_loadSettings(data.llUSBDeviceSources);
+#else
+ RT_NOREF(data);
+#endif /* VBOX_WITH_USB */
+
+#ifdef VBOX_WITH_UPDATE_AGENT
+ rc = m->pUpdateHost->i_loadSettings(data.updateHost);
+ ComAssertComRCRet(rc, rc);
+ /** @todo Add handling for ExtPack and Guest Additions updates here later. See @bugref{7983}. */
+#endif
+
+ return rc;
+}
+
+HRESULT Host::i_saveSettings(settings::Host &data)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc;
+
+#ifdef VBOX_WITH_USB
+ data.llUSBDeviceFilters.clear();
+ data.llUSBDeviceSources.clear();
+
+ for (USBDeviceFilterList::const_iterator it = m->llUSBDeviceFilters.begin();
+ it != m->llUSBDeviceFilters.end();
+ ++it)
+ {
+ ComObjPtr<HostUSBDeviceFilter> pFilter = *it;
+ settings::USBDeviceFilter f;
+ pFilter->i_saveSettings(f);
+ data.llUSBDeviceFilters.push_back(f);
+ }
+
+ rc = m->pUSBProxyService->i_saveSettings(data.llUSBDeviceSources);
+ ComAssertComRCRet(rc, rc);
+#else
+ RT_NOREF(data);
+#endif /* VBOX_WITH_USB */
+
+#ifdef VBOX_WITH_UPDATE_AGENT
+ rc = m->pUpdateHost->i_saveSettings(data.updateHost);
+ ComAssertComRCRet(rc, rc);
+ /** @todo Add handling for ExtPack and Guest Additions updates here later. See @bugref{7983}. */
+#endif
+
+ return S_OK;
+}
+
+/**
+ * Sets the given pointer to point to the static list of DVD or floppy
+ * drives in the Host instance data, depending on the @a mediumType
+ * parameter.
+ *
+ * This builds the list on the first call; it adds or removes host drives
+ * that may have changed if fRefresh == true.
+ *
+ * The caller must hold the medium tree write lock before calling this.
+ * To protect the list to which the caller's pointer points, the caller
+ * must also hold that lock.
+ *
+ * @param mediumType Must be DeviceType_Floppy or DeviceType_DVD.
+ * @param fRefresh Whether to refresh the host drives list even if this is not the first call.
+ * @param pll Caller's pointer which gets set to the static list of host drives.
+ * @param treeLock Reference to media tree lock, need to drop it temporarily.
+ * @returns COM status code
+ */
+HRESULT Host::i_getDrives(DeviceType_T mediumType,
+ bool fRefresh,
+ MediaList *&pll,
+ AutoWriteLock &treeLock)
+{
+ HRESULT rc = S_OK;
+ Assert(m->pParent->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+
+ MediaList llNew;
+ MediaList *pllCached;
+ bool *pfListBuilt = NULL;
+
+ switch (mediumType)
+ {
+ case DeviceType_DVD:
+ if (!m->fDVDDrivesListBuilt || fRefresh)
+ {
+ rc = i_buildDVDDrivesList(llNew);
+ if (FAILED(rc))
+ return rc;
+ pfListBuilt = &m->fDVDDrivesListBuilt;
+ }
+ pllCached = &m->llDVDDrives;
+ break;
+
+ case DeviceType_Floppy:
+ if (!m->fFloppyDrivesListBuilt || fRefresh)
+ {
+ rc = i_buildFloppyDrivesList(llNew);
+ if (FAILED(rc))
+ return rc;
+ pfListBuilt = &m->fFloppyDrivesListBuilt;
+ }
+ pllCached = &m->llFloppyDrives;
+ break;
+
+ default:
+ return E_INVALIDARG;
+ }
+
+ if (pfListBuilt)
+ {
+ // a list was built in llNew above:
+ if (!*pfListBuilt)
+ {
+ // this was the first call (instance bool is still false): then just copy the whole list and return
+ *pllCached = llNew;
+ // and mark the instance data as "built"
+ *pfListBuilt = true;
+ }
+ else
+ {
+ // list was built, and this was a subsequent call: then compare the old and the new lists
+
+ // remove drives from the cached list which are no longer present
+ for (MediaList::iterator itCached = pllCached->begin();
+ itCached != pllCached->end();
+ /*nothing */)
+ {
+ Medium *pCached = *itCached;
+ const Utf8Str strLocationCached = pCached->i_getLocationFull();
+ bool fFound = false;
+ for (MediaList::iterator itNew = llNew.begin();
+ itNew != llNew.end();
+ ++itNew)
+ {
+ Medium *pNew = *itNew;
+ const Utf8Str strLocationNew = pNew->i_getLocationFull();
+ if (strLocationNew == strLocationCached)
+ {
+ fFound = true;
+ break;
+ }
+ }
+ if (!fFound)
+ {
+ pCached->uninit();
+ itCached = pllCached->erase(itCached);
+ }
+ else
+ ++itCached;
+ }
+
+ // add drives to the cached list that are not on there yet
+ for (MediaList::iterator itNew = llNew.begin();
+ itNew != llNew.end();
+ ++itNew)
+ {
+ Medium *pNew = *itNew;
+ const Utf8Str strLocationNew = pNew->i_getLocationFull();
+ bool fFound = false;
+ for (MediaList::iterator itCached = pllCached->begin();
+ itCached != pllCached->end();
+ ++itCached)
+ {
+ Medium *pCached = *itCached;
+ const Utf8Str strLocationCached = pCached->i_getLocationFull();
+ if (strLocationNew == strLocationCached)
+ {
+ fFound = true;
+ break;
+ }
+ }
+
+ if (!fFound)
+ pllCached->push_back(pNew);
+ }
+ }
+ }
+
+ // return cached list to caller
+ pll = pllCached;
+
+ // Make sure the media tree lock is released before llNew is cleared,
+ // as this usually triggers calls to uninit().
+ treeLock.release();
+
+ llNew.clear();
+
+ treeLock.acquire();
+
+ return rc;
+}
+
+/**
+ * Goes through the list of host drives that would be returned by getDrives()
+ * and looks for a host drive with the given UUID. If found, it sets pMedium
+ * to that drive; otherwise returns VBOX_E_OBJECT_NOT_FOUND.
+ *
+ * @param mediumType Must be DeviceType_DVD or DeviceType_Floppy.
+ * @param uuid Medium UUID of host drive to look for.
+ * @param fRefresh Whether to refresh the host drives list (see getDrives())
+ * @param pMedium Medium object, if found...
+ * @return VBOX_E_OBJECT_NOT_FOUND if not found, or S_OK if found, or errors from getDrives().
+ */
+HRESULT Host::i_findHostDriveById(DeviceType_T mediumType,
+ const Guid &uuid,
+ bool fRefresh,
+ ComObjPtr<Medium> &pMedium)
+{
+ MediaList *pllMedia;
+
+ AutoWriteLock treeLock(m->pParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = i_getDrives(mediumType, fRefresh, pllMedia, treeLock);
+ if (SUCCEEDED(rc))
+ {
+ for (MediaList::iterator it = pllMedia->begin();
+ it != pllMedia->end();
+ ++it)
+ {
+ Medium *pThis = *it;
+ AutoCaller mediumCaller(pThis);
+ AutoReadLock mediumLock(pThis COMMA_LOCKVAL_SRC_POS);
+ if (pThis->i_getId() == uuid)
+ {
+ pMedium = pThis;
+ return S_OK;
+ }
+ }
+ }
+
+ return VBOX_E_OBJECT_NOT_FOUND;
+}
+
+/**
+ * Goes through the list of host drives that would be returned by getDrives()
+ * and looks for a host drive with the given name. If found, it sets pMedium
+ * to that drive; otherwise returns VBOX_E_OBJECT_NOT_FOUND.
+ *
+ * @param mediumType Must be DeviceType_DVD or DeviceType_Floppy.
+ * @param strLocationFull Name (path) of host drive to look for.
+ * @param fRefresh Whether to refresh the host drives list (see getDrives())
+ * @param pMedium Medium object, if found
+ * @return VBOX_E_OBJECT_NOT_FOUND if not found, or S_OK if found, or errors from getDrives().
+ */
+HRESULT Host::i_findHostDriveByName(DeviceType_T mediumType,
+ const Utf8Str &strLocationFull,
+ bool fRefresh,
+ ComObjPtr<Medium> &pMedium)
+{
+ MediaList *pllMedia;
+
+ AutoWriteLock treeLock(m->pParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = i_getDrives(mediumType, fRefresh, pllMedia, treeLock);
+ if (SUCCEEDED(rc))
+ {
+ for (MediaList::iterator it = pllMedia->begin();
+ it != pllMedia->end();
+ ++it)
+ {
+ Medium *pThis = *it;
+ AutoCaller mediumCaller(pThis);
+ AutoReadLock mediumLock(pThis COMMA_LOCKVAL_SRC_POS);
+ if (pThis->i_getLocationFull() == strLocationFull)
+ {
+ pMedium = pThis;
+ return S_OK;
+ }
+ }
+ }
+
+ return VBOX_E_OBJECT_NOT_FOUND;
+}
+
+/**
+ * Goes through the list of host drives that would be returned by getDrives()
+ * and looks for a host drive with the given name, location or ID. If found,
+ * it sets pMedium to that drive; otherwise returns VBOX_E_OBJECT_NOT_FOUND.
+ *
+ * @param mediumType Must be DeviceType_DVD or DeviceType_Floppy.
+ * @param strNameOrId Name or full location or UUID of host drive to look for.
+ * @param pMedium Medium object, if found...
+ * @return VBOX_E_OBJECT_NOT_FOUND if not found, or S_OK if found, or errors from getDrives().
+ */
+HRESULT Host::i_findHostDriveByNameOrId(DeviceType_T mediumType,
+ const Utf8Str &strNameOrId,
+ ComObjPtr<Medium> &pMedium)
+{
+ AutoWriteLock wlock(m->pParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ Guid uuid(strNameOrId);
+ if (uuid.isValid() && !uuid.isZero())
+ return i_findHostDriveById(mediumType, uuid, true /* fRefresh */, pMedium);
+
+ // string is not a syntactically valid UUID: try a name then
+ return i_findHostDriveByName(mediumType, strNameOrId, true /* fRefresh */, pMedium);
+}
+
+/**
+ * Called from getDrives() to build the DVD drives list.
+ * @param list Media list
+ * @return
+ */
+HRESULT Host::i_buildDVDDrivesList(MediaList &list)
+{
+ HRESULT rc = S_OK;
+
+ Assert(m->pParent->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+
+ try
+ {
+#if defined(RT_OS_WINDOWS)
+ int sz = GetLogicalDriveStrings(0, NULL);
+ TCHAR *hostDrives = new TCHAR[sz+1];
+ GetLogicalDriveStrings(sz, hostDrives);
+ wchar_t driveName[3] = { '?', ':', '\0' };
+ TCHAR *p = hostDrives;
+ do
+ {
+ if (GetDriveType(p) == DRIVE_CDROM)
+ {
+ driveName[0] = *p;
+ ComObjPtr<Medium> hostDVDDriveObj;
+ hostDVDDriveObj.createObject();
+ hostDVDDriveObj->init(m->pParent, DeviceType_DVD, Bstr(driveName));
+ list.push_back(hostDVDDriveObj);
+ }
+ p += _tcslen(p) + 1;
+ }
+ while (*p);
+ delete[] hostDrives;
+
+#elif defined(RT_OS_SOLARIS)
+# ifdef VBOX_USE_LIBHAL
+ if (!i_getDVDInfoFromHal(list))
+# endif
+ {
+ i_getDVDInfoFromDevTree(list);
+ }
+
+#elif defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)
+ if (RT_SUCCESS(m->hostDrives.updateDVDs()))
+ for (DriveInfoList::const_iterator it = m->hostDrives.DVDBegin();
+ SUCCEEDED(rc) && it != m->hostDrives.DVDEnd(); ++it)
+ {
+ ComObjPtr<Medium> hostDVDDriveObj;
+ Utf8Str location(it->mDevice);
+ Utf8Str description(it->mDescription);
+ if (SUCCEEDED(rc))
+ rc = hostDVDDriveObj.createObject();
+ if (SUCCEEDED(rc))
+ rc = hostDVDDriveObj->init(m->pParent, DeviceType_DVD, location, description);
+ if (SUCCEEDED(rc))
+ list.push_back(hostDVDDriveObj);
+ }
+#elif defined(RT_OS_DARWIN)
+ PDARWINDVD cur = DarwinGetDVDDrives();
+ while (cur)
+ {
+ ComObjPtr<Medium> hostDVDDriveObj;
+ hostDVDDriveObj.createObject();
+ hostDVDDriveObj->init(m->pParent, DeviceType_DVD, Bstr(cur->szName));
+ list.push_back(hostDVDDriveObj);
+
+ /* next */
+ void *freeMe = cur;
+ cur = cur->pNext;
+ RTMemFree(freeMe);
+ }
+#else
+ /* PORTME */
+#endif
+ }
+ catch(std::bad_alloc &)
+ {
+ rc = E_OUTOFMEMORY;
+ }
+ return rc;
+}
+
+/**
+ * Called from getDrives() to build the floppy drives list.
+ * @param list
+ * @return
+ */
+HRESULT Host::i_buildFloppyDrivesList(MediaList &list)
+{
+ HRESULT rc = S_OK;
+
+ Assert(m->pParent->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+
+ try
+ {
+#ifdef RT_OS_WINDOWS
+ int sz = GetLogicalDriveStrings(0, NULL);
+ TCHAR *hostDrives = new TCHAR[sz+1];
+ GetLogicalDriveStrings(sz, hostDrives);
+ wchar_t driveName[3] = { '?', ':', '\0' };
+ TCHAR *p = hostDrives;
+ do
+ {
+ if (GetDriveType(p) == DRIVE_REMOVABLE)
+ {
+ driveName[0] = *p;
+ ComObjPtr<Medium> hostFloppyDriveObj;
+ hostFloppyDriveObj.createObject();
+ hostFloppyDriveObj->init(m->pParent, DeviceType_Floppy, Bstr(driveName));
+ list.push_back(hostFloppyDriveObj);
+ }
+ p += _tcslen(p) + 1;
+ }
+ while (*p);
+ delete[] hostDrives;
+#elif defined(RT_OS_LINUX)
+ if (RT_SUCCESS(m->hostDrives.updateFloppies()))
+ for (DriveInfoList::const_iterator it = m->hostDrives.FloppyBegin();
+ SUCCEEDED(rc) && it != m->hostDrives.FloppyEnd(); ++it)
+ {
+ ComObjPtr<Medium> hostFloppyDriveObj;
+ Utf8Str location(it->mDevice);
+ Utf8Str description(it->mDescription);
+ if (SUCCEEDED(rc))
+ rc = hostFloppyDriveObj.createObject();
+ if (SUCCEEDED(rc))
+ rc = hostFloppyDriveObj->init(m->pParent, DeviceType_Floppy, location, description);
+ if (SUCCEEDED(rc))
+ list.push_back(hostFloppyDriveObj);
+ }
+#else
+ RT_NOREF(list);
+ /* PORTME */
+#endif
+ }
+ catch(std::bad_alloc &)
+ {
+ rc = E_OUTOFMEMORY;
+ }
+
+ return rc;
+}
+
+#ifdef VBOX_WITH_USB
+USBProxyService* Host::i_usbProxyService()
+{
+ return m->pUSBProxyService;
+}
+
+HRESULT Host::i_addChild(HostUSBDeviceFilter *pChild)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->llChildren.push_back(pChild);
+
+ return S_OK;
+}
+
+HRESULT Host::i_removeChild(HostUSBDeviceFilter *pChild)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (USBDeviceFilterList::iterator it = m->llChildren.begin();
+ it != m->llChildren.end();
+ ++it)
+ {
+ if (*it == pChild)
+ {
+ m->llChildren.erase(it);
+ break;
+ }
+ }
+
+ return S_OK;
+}
+
+VirtualBox* Host::i_parent()
+{
+ return m->pParent;
+}
+
+/**
+ * Called by setter methods of all USB device filters.
+ */
+HRESULT Host::i_onUSBDeviceFilterChange(HostUSBDeviceFilter *aFilter,
+ BOOL aActiveChanged /* = FALSE */)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aFilter->mInList)
+ {
+ if (aActiveChanged)
+ {
+ // insert/remove the filter from the proxy
+ if (aFilter->i_getData().mData.fActive)
+ {
+ ComAssertRet(aFilter->i_getId() == NULL, E_FAIL);
+ aFilter->i_getId() = m->pUSBProxyService->insertFilter(&aFilter->i_getData().mUSBFilter);
+ }
+ else
+ {
+ ComAssertRet(aFilter->i_getId() != NULL, E_FAIL);
+ m->pUSBProxyService->removeFilter(aFilter->i_getId());
+ aFilter->i_getId() = NULL;
+ }
+ }
+ else
+ {
+ if (aFilter->i_getData().mData.fActive)
+ {
+ // update the filter in the proxy
+ ComAssertRet(aFilter->i_getId() != NULL, E_FAIL);
+ m->pUSBProxyService->removeFilter(aFilter->i_getId());
+ aFilter->i_getId() = m->pUSBProxyService->insertFilter(&aFilter->i_getData().mUSBFilter);
+ }
+ }
+
+ // save the global settings... yeah, on every single filter property change
+ // for that we should hold only the VirtualBox lock
+ alock.release();
+ AutoWriteLock vboxLock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ return m->pParent->i_saveSettings();
+ }
+
+ return S_OK;
+}
+
+
+/**
+ * Interface for obtaining a copy of the USBDeviceFilterList,
+ * used by the USBProxyService.
+ *
+ * @param aGlobalFilters Where to put the global filter list copy.
+ * @param aMachines Where to put the machine vector.
+ */
+void Host::i_getUSBFilters(Host::USBDeviceFilterList *aGlobalFilters)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aGlobalFilters = m->llUSBDeviceFilters;
+}
+
+#endif /* VBOX_WITH_USB */
+
+// private methods
+////////////////////////////////////////////////////////////////////////////////
+
+#if defined(RT_OS_SOLARIS) && defined(VBOX_USE_LIBHAL)
+
+/**
+ * Helper function to get the slice number from a device path
+ *
+ * @param pszDevLinkPath Pointer to a device path (/dev/(r)dsk/c7d1t0d0s3 etc.)
+ * @returns Pointer to the slice portion of the given path.
+ */
+static char *solarisGetSliceFromPath(const char *pszDevLinkPath)
+{
+ char *pszSlice = (char *)strrchr(pszDevLinkPath, 's');
+ char *pszDisk = (char *)strrchr(pszDevLinkPath, 'd');
+ char *pszFound;
+ if (pszSlice && (uintptr_t)pszSlice > (uintptr_t)pszDisk)
+ pszFound = pszSlice;
+ else
+ pszFound = pszDisk;
+
+ if (pszFound && RT_C_IS_DIGIT(pszFound[1]))
+ return pszFound;
+
+ return NULL;
+}
+
+/**
+ * Walk device links and returns an allocated path for the first one in the snapshot.
+ *
+ * @param DevLink Handle to the device link being walked.
+ * @param pvArg Opaque pointer that we use to point to the return
+ * variable (char *). Caller must call RTStrFree on it.
+ * @returns DI_WALK_TERMINATE to stop the walk.
+ */
+static int solarisWalkDevLink(di_devlink_t DevLink, void *pvArg)
+{
+ char **ppszPath = (char **)pvArg;
+ *ppszPath = RTStrDup(di_devlink_path(DevLink));
+ return DI_WALK_TERMINATE;
+}
+
+/**
+ * Walk all devices in the system and enumerate CD/DVD drives.
+ * @param Node Handle to the current node.
+ * @param pvArg Opaque data (holds list pointer).
+ * @returns Solaris specific code whether to continue walking or not.
+ */
+static int solarisWalkDeviceNodeForDVD(di_node_t Node, void *pvArg)
+{
+ PSOLARISDVD *ppDrives = (PSOLARISDVD *)pvArg;
+
+ /*
+ * Check for "removable-media" or "hotpluggable" instead of "SCSI" so that we also include USB CD-ROMs.
+ * As unfortunately the Solaris drivers only export these common properties.
+ */
+ int *pInt = NULL;
+ if ( di_prop_lookup_ints(DDI_DEV_T_ANY, Node, "removable-media", &pInt) >= 0
+ || di_prop_lookup_ints(DDI_DEV_T_ANY, Node, "hotpluggable", &pInt) >= 0)
+ {
+ if (di_prop_lookup_ints(DDI_DEV_T_ANY, Node, "inquiry-device-type", &pInt) > 0
+ && ( *pInt == DTYPE_RODIRECT /* CDROM */
+ || *pInt == DTYPE_OPTICAL)) /* Optical Drive */
+ {
+ char *pszProduct = NULL;
+ if (di_prop_lookup_strings(DDI_DEV_T_ANY, Node, "inquiry-product-id", &pszProduct) > 0)
+ {
+ char *pszVendor = NULL;
+ if (di_prop_lookup_strings(DDI_DEV_T_ANY, Node, "inquiry-vendor-id", &pszVendor) > 0)
+ {
+ /*
+ * Found a DVD drive, we need to scan the minor nodes to find the correct
+ * slice that represents the whole drive. "s2" is always the whole drive for CD/DVDs.
+ */
+ int Major = di_driver_major(Node);
+ di_minor_t Minor = DI_MINOR_NIL;
+ di_devlink_handle_t DevLink = di_devlink_init(NULL /* name */, 0 /* flags */);
+ if (DevLink)
+ {
+ while ((Minor = di_minor_next(Node, Minor)) != DI_MINOR_NIL)
+ {
+ dev_t Dev = di_minor_devt(Minor);
+ if ( Major != (int)major(Dev)
+ || di_minor_spectype(Minor) == S_IFBLK
+ || di_minor_type(Minor) != DDM_MINOR)
+ {
+ continue;
+ }
+
+ char *pszMinorPath = di_devfs_minor_path(Minor);
+ if (!pszMinorPath)
+ continue;
+
+ char *pszDevLinkPath = NULL;
+ di_devlink_walk(DevLink, NULL, pszMinorPath, DI_PRIMARY_LINK, &pszDevLinkPath, solarisWalkDevLink);
+ di_devfs_path_free(pszMinorPath);
+
+ if (pszDevLinkPath)
+ {
+ char *pszSlice = solarisGetSliceFromPath(pszDevLinkPath);
+ if ( pszSlice && !strcmp(pszSlice, "s2")
+ && !strncmp(pszDevLinkPath, RT_STR_TUPLE("/dev/rdsk"))) /* We want only raw disks */
+ {
+ /*
+ * We've got a fully qualified DVD drive. Add it to the list.
+ */
+ PSOLARISDVD pDrive = (PSOLARISDVD)RTMemAllocZ(sizeof(SOLARISDVD));
+ if (RT_LIKELY(pDrive))
+ {
+ RTStrPrintf(pDrive->szDescription, sizeof(pDrive->szDescription),
+ "%s %s", pszVendor, pszProduct);
+ RTStrPurgeEncoding(pDrive->szDescription);
+ RTStrCopy(pDrive->szRawDiskPath, sizeof(pDrive->szRawDiskPath), pszDevLinkPath);
+ if (*ppDrives)
+ pDrive->pNext = *ppDrives;
+ *ppDrives = pDrive;
+
+ /* We're not interested in any of the other slices, stop minor nodes traversal. */
+ RTStrFree(pszDevLinkPath);
+ break;
+ }
+ }
+ RTStrFree(pszDevLinkPath);
+ }
+ }
+ di_devlink_fini(&DevLink);
+ }
+ }
+ }
+ }
+ }
+ return DI_WALK_CONTINUE;
+}
+
+/**
+ * Solaris specific function to enumerate CD/DVD drives via the device tree.
+ * Works on Solaris 10 as well as OpenSolaris without depending on libhal.
+ */
+void Host::i_getDVDInfoFromDevTree(std::list<ComObjPtr<Medium> > &list)
+{
+ PSOLARISDVD pDrives = NULL;
+ di_node_t RootNode = di_init("/", DINFOCPYALL);
+ if (RootNode != DI_NODE_NIL)
+ di_walk_node(RootNode, DI_WALK_CLDFIRST, &pDrives, solarisWalkDeviceNodeForDVD);
+
+ di_fini(RootNode);
+
+ while (pDrives)
+ {
+ ComObjPtr<Medium> hostDVDDriveObj;
+ hostDVDDriveObj.createObject();
+ hostDVDDriveObj->init(m->pParent, DeviceType_DVD, Bstr(pDrives->szRawDiskPath), Bstr(pDrives->szDescription));
+ list.push_back(hostDVDDriveObj);
+
+ void *pvDrive = pDrives;
+ pDrives = pDrives->pNext;
+ RTMemFree(pvDrive);
+ }
+}
+
+
+/**
+ * Walk all devices in the system and enumerate fixed drives.
+ * @param Node Handle to the current node.
+ * @param pvArg Opaque data (holds list pointer).
+ * @returns Solaris specific code whether to continue walking or not.
+ */
+static int solarisWalkDeviceNodeForFixedDrive(di_node_t Node, void *pvArg) RT_NOEXCEPT
+{
+ PSOLARISFIXEDDISK *ppDrives = (PSOLARISFIXEDDISK *)pvArg;
+
+ int *pInt = NULL;
+ if ( di_prop_lookup_ints(DDI_DEV_T_ANY, Node, "inquiry-device-type", &pInt) > 0
+ && *pInt == DTYPE_DIRECT) /* Fixed drive */
+ {
+ char *pszProduct = NULL;
+ if (di_prop_lookup_strings(DDI_DEV_T_ANY, Node, "inquiry-product-id", &pszProduct) > 0)
+ {
+ char *pszVendor = NULL;
+ if (di_prop_lookup_strings(DDI_DEV_T_ANY, Node, "inquiry-vendor-id", &pszVendor) > 0)
+ {
+ /*
+ * Found a fixed drive, we need to scan the minor nodes to find the correct
+ * slice that represents the whole drive.
+ */
+ int Major = di_driver_major(Node);
+ di_minor_t Minor = DI_MINOR_NIL;
+ di_devlink_handle_t DevLink = di_devlink_init(NULL /* name */, 0 /* flags */);
+ if (DevLink)
+ {
+ /*
+ * The device name we have to select depends on drive type. For fixed drives, the
+ * name without slice or partition should be selected, for USB flash drive the
+ * partition 0 should be selected and slice 0 for other cases.
+ */
+ char *pszDisk = NULL;
+ char *pszPartition0 = NULL;
+ char *pszSlice0 = NULL;
+ while ((Minor = di_minor_next(Node, Minor)) != DI_MINOR_NIL)
+ {
+ dev_t Dev = di_minor_devt(Minor);
+ if ( Major != (int)major(Dev)
+ || di_minor_spectype(Minor) == S_IFBLK
+ || di_minor_type(Minor) != DDM_MINOR)
+ continue;
+
+ char *pszMinorPath = di_devfs_minor_path(Minor);
+ if (!pszMinorPath)
+ continue;
+
+ char *pszDevLinkPath = NULL;
+ di_devlink_walk(DevLink, NULL, pszMinorPath, DI_PRIMARY_LINK, &pszDevLinkPath, solarisWalkDevLink);
+ di_devfs_path_free(pszMinorPath);
+
+ if (pszDevLinkPath)
+ {
+ char const *pszCurSlice = strrchr(pszDevLinkPath, 's');
+ char const *pszCurDisk = strrchr(pszDevLinkPath, 'd');
+ char const *pszCurPart = strrchr(pszDevLinkPath, 'p');
+ char **ppszDst = NULL;
+ if (pszCurSlice && (uintptr_t)pszCurSlice > (uintptr_t)pszCurDisk && !strcmp(pszCurSlice, "s0"))
+ ppszDst = &pszSlice0;
+ else if (pszCurPart && (uintptr_t)pszCurPart > (uintptr_t)pszCurDisk && !strcmp(pszCurPart, "p0"))
+ ppszDst = &pszPartition0;
+ else if ( (!pszCurSlice || (uintptr_t)pszCurSlice < (uintptr_t)pszCurDisk)
+ && (!pszCurPart || (uintptr_t)pszCurPart < (uintptr_t)pszCurDisk)
+ && *pszDevLinkPath != '\0')
+ ppszDst = &pszDisk;
+ else
+ RTStrFree(pszDevLinkPath);
+ if (ppszDst)
+ {
+ if (*ppszDst != NULL)
+ RTStrFree(*ppszDst);
+ *ppszDst = pszDevLinkPath;
+ }
+ }
+ }
+ di_devlink_fini(&DevLink);
+ if (pszDisk || pszPartition0 || pszSlice0)
+ {
+ PSOLARISFIXEDDISK pDrive = (PSOLARISFIXEDDISK)RTMemAllocZ(sizeof(*pDrive));
+ if (RT_LIKELY(pDrive))
+ {
+ RTStrPrintf(pDrive->szDescription, sizeof(pDrive->szDescription), "%s %s", pszVendor, pszProduct);
+ RTStrPurgeEncoding(pDrive->szDescription);
+
+ const char *pszDevPath = pszDisk ? pszDisk : pszPartition0 ? pszPartition0 : pszSlice0;
+ int rc = RTStrCopy(pDrive->szRawDiskPath, sizeof(pDrive->szRawDiskPath), pszDevPath);
+ AssertRC(rc);
+
+ if (*ppDrives)
+ pDrive->pNext = *ppDrives;
+ *ppDrives = pDrive;
+ }
+ RTStrFree(pszDisk);
+ RTStrFree(pszPartition0);
+ RTStrFree(pszSlice0);
+ }
+ }
+ }
+ }
+ }
+ return DI_WALK_CONTINUE;
+}
+
+
+/**
+ * Solaris specific function to enumerate fixed drives via the device tree.
+ * Works on Solaris 10 as well as OpenSolaris without depending on libhal.
+ *
+ * @returns COM status, either S_OK or E_OUTOFMEMORY.
+ * @param list Reference to list where the the path/model pairs are to
+ * be returned.
+ */
+HRESULT Host::i_getFixedDrivesFromDevTree(std::list<std::pair<com::Utf8Str, com::Utf8Str> > &list) RT_NOEXCEPT
+{
+ PSOLARISFIXEDDISK pDrives = NULL;
+ di_node_t RootNode = di_init("/", DINFOCPYALL);
+ if (RootNode != DI_NODE_NIL)
+ di_walk_node(RootNode, DI_WALK_CLDFIRST, &pDrives, solarisWalkDeviceNodeForFixedDrive);
+ di_fini(RootNode);
+
+ HRESULT hrc = S_OK;
+ try
+ {
+ for (PSOLARISFIXEDDISK pCurDrv = pDrives; pCurDrv; pCurDrv = pCurDrv->pNext)
+ list.push_back(std::pair<com::Utf8Str, com::Utf8Str>(pCurDrv->szRawDiskPath, pCurDrv->szDescription));
+ }
+ catch (std::bad_alloc &)
+ {
+ LogRelFunc(("Out of memory!\n"));
+ list.clear();
+ hrc = E_OUTOFMEMORY;
+ }
+
+ while (pDrives)
+ {
+ PSOLARISFIXEDDISK pFreeMe = pDrives;
+ pDrives = pDrives->pNext;
+ ASMCompilerBarrier();
+ RTMemFree(pFreeMe);
+ }
+
+ return hrc;
+}
+
+
+/* Solaris hosts, loading libhal at runtime */
+
+/**
+ * Helper function to query the hal subsystem for information about DVD drives attached to the
+ * system.
+ *
+ * @returns true if information was successfully obtained, false otherwise
+ * @param list Reference to list where the DVDs drives are to be returned.
+ */
+bool Host::i_getDVDInfoFromHal(std::list<ComObjPtr<Medium> > &list)
+{
+ bool halSuccess = false;
+ DBusError dbusError;
+ if (!gLibHalCheckPresence())
+ return false;
+ gDBusErrorInit(&dbusError);
+ DBusConnection *dbusConnection = gDBusBusGet(DBUS_BUS_SYSTEM, &dbusError);
+ if (dbusConnection != 0)
+ {
+ LibHalContext *halContext = gLibHalCtxNew();
+ if (halContext != 0)
+ {
+ if (gLibHalCtxSetDBusConnection(halContext, dbusConnection))
+ {
+ if (gLibHalCtxInit(halContext, &dbusError))
+ {
+ int numDevices;
+ char **halDevices = gLibHalFindDeviceStringMatch(halContext,
+ "storage.drive_type", "cdrom",
+ &numDevices, &dbusError);
+ if (halDevices != 0)
+ {
+ /* Hal is installed and working, so if no devices are reported, assume
+ that there are none. */
+ halSuccess = true;
+ for (int i = 0; i < numDevices; i++)
+ {
+ char *devNode = gLibHalDeviceGetPropertyString(halContext,
+ halDevices[i], "block.device", &dbusError);
+#ifdef RT_OS_SOLARIS
+ /* The CD/DVD ioctls work only for raw device nodes. */
+ char *tmp = getfullrawname(devNode);
+ gLibHalFreeString(devNode);
+ devNode = tmp;
+#endif
+
+ if (devNode != 0)
+ {
+// if (validateDevice(devNode, true))
+// {
+ Utf8Str description;
+ char *vendor, *product;
+ /* We do not check the error here, as this field may
+ not even exist. */
+ vendor = gLibHalDeviceGetPropertyString(halContext,
+ halDevices[i], "info.vendor", 0);
+ product = gLibHalDeviceGetPropertyString(halContext,
+ halDevices[i], "info.product", &dbusError);
+ if ((product != 0 && product[0] != 0))
+ {
+ if ((vendor != 0) && (vendor[0] != 0))
+ {
+ description = Utf8StrFmt("%s %s",
+ vendor, product);
+ }
+ else
+ {
+ description = product;
+ }
+ ComObjPtr<Medium> hostDVDDriveObj;
+ hostDVDDriveObj.createObject();
+ hostDVDDriveObj->init(m->pParent, DeviceType_DVD,
+ Bstr(devNode), Bstr(description));
+ list.push_back(hostDVDDriveObj);
+ }
+ else
+ {
+ if (product == 0)
+ {
+ LogRel(("Host::COMGETTER(DVDDrives): failed to get property \"info.product\" for device %s. dbus error: %s (%s)\n",
+ halDevices[i], dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ ComObjPtr<Medium> hostDVDDriveObj;
+ hostDVDDriveObj.createObject();
+ hostDVDDriveObj->init(m->pParent, DeviceType_DVD,
+ Bstr(devNode));
+ list.push_back(hostDVDDriveObj);
+ }
+ if (vendor != 0)
+ {
+ gLibHalFreeString(vendor);
+ }
+ if (product != 0)
+ {
+ gLibHalFreeString(product);
+ }
+// }
+// else
+// {
+// LogRel(("Host::COMGETTER(DVDDrives): failed to validate the block device %s as a DVD drive\n"));
+// }
+#ifndef RT_OS_SOLARIS
+ gLibHalFreeString(devNode);
+#else
+ free(devNode);
+#endif
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(DVDDrives): failed to get property \"block.device\" for device %s. dbus error: %s (%s)\n",
+ halDevices[i], dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ }
+ gLibHalFreeStringArray(halDevices);
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(DVDDrives): failed to get devices with capability \"storage.cdrom\". dbus error: %s (%s)\n", dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ if (!gLibHalCtxShutdown(halContext, &dbusError)) /* what now? */
+ {
+ LogRel(("Host::COMGETTER(DVDDrives): failed to shutdown the libhal context. dbus error: %s (%s)\n",
+ dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(DVDDrives): failed to initialise libhal context. dbus error: %s (%s)\n",
+ dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ gLibHalCtxFree(halContext);
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(DVDDrives): failed to set libhal connection to dbus.\n"));
+ }
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(DVDDrives): failed to get a libhal context - out of memory?\n"));
+ }
+ gDBusConnectionUnref(dbusConnection);
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(DVDDrives): failed to connect to dbus. dbus error: %s (%s)\n",
+ dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ return halSuccess;
+}
+
+
+/**
+ * Helper function to query the hal subsystem for information about floppy drives attached to the
+ * system.
+ *
+ * @returns true if information was successfully obtained, false otherwise
+ * @retval list drives found will be attached to this list
+ */
+bool Host::i_getFloppyInfoFromHal(std::list< ComObjPtr<Medium> > &list)
+{
+ bool halSuccess = false;
+ DBusError dbusError;
+ if (!gLibHalCheckPresence())
+ return false;
+ gDBusErrorInit(&dbusError);
+ DBusConnection *dbusConnection = gDBusBusGet(DBUS_BUS_SYSTEM, &dbusError);
+ if (dbusConnection != 0)
+ {
+ LibHalContext *halContext = gLibHalCtxNew();
+ if (halContext != 0)
+ {
+ if (gLibHalCtxSetDBusConnection(halContext, dbusConnection))
+ {
+ if (gLibHalCtxInit(halContext, &dbusError))
+ {
+ int numDevices;
+ char **halDevices = gLibHalFindDeviceStringMatch(halContext,
+ "storage.drive_type", "floppy",
+ &numDevices, &dbusError);
+ if (halDevices != 0)
+ {
+ /* Hal is installed and working, so if no devices are reported, assume
+ that there are none. */
+ halSuccess = true;
+ for (int i = 0; i < numDevices; i++)
+ {
+ char *driveType = gLibHalDeviceGetPropertyString(halContext,
+ halDevices[i], "storage.drive_type", 0);
+ if (driveType != 0)
+ {
+ if (strcmp(driveType, "floppy") != 0)
+ {
+ gLibHalFreeString(driveType);
+ continue;
+ }
+ gLibHalFreeString(driveType);
+ }
+ else
+ {
+ /* An error occurred. The attribute "storage.drive_type"
+ probably didn't exist. */
+ continue;
+ }
+ char *devNode = gLibHalDeviceGetPropertyString(halContext,
+ halDevices[i], "block.device", &dbusError);
+ if (devNode != 0)
+ {
+// if (validateDevice(devNode, false))
+// {
+ Utf8Str description;
+ char *vendor, *product;
+ /* We do not check the error here, as this field may
+ not even exist. */
+ vendor = gLibHalDeviceGetPropertyString(halContext,
+ halDevices[i], "info.vendor", 0);
+ product = gLibHalDeviceGetPropertyString(halContext,
+ halDevices[i], "info.product", &dbusError);
+ if ((product != 0) && (product[0] != 0))
+ {
+ if ((vendor != 0) && (vendor[0] != 0))
+ {
+ description = Utf8StrFmt("%s %s",
+ vendor, product);
+ }
+ else
+ {
+ description = product;
+ }
+ ComObjPtr<Medium> hostFloppyDrive;
+ hostFloppyDrive.createObject();
+ hostFloppyDrive->init(m->pParent, DeviceType_DVD,
+ Bstr(devNode), Bstr(description));
+ list.push_back(hostFloppyDrive);
+ }
+ else
+ {
+ if (product == 0)
+ {
+ LogRel(("Host::COMGETTER(FloppyDrives): failed to get property \"info.product\" for device %s. dbus error: %s (%s)\n",
+ halDevices[i], dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ ComObjPtr<Medium> hostFloppyDrive;
+ hostFloppyDrive.createObject();
+ hostFloppyDrive->init(m->pParent, DeviceType_DVD,
+ Bstr(devNode));
+ list.push_back(hostFloppyDrive);
+ }
+ if (vendor != 0)
+ {
+ gLibHalFreeString(vendor);
+ }
+ if (product != 0)
+ {
+ gLibHalFreeString(product);
+ }
+// }
+// else
+// {
+// LogRel(("Host::COMGETTER(FloppyDrives): failed to validate the block device %s as a floppy drive\n"));
+// }
+ gLibHalFreeString(devNode);
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(FloppyDrives): failed to get property \"block.device\" for device %s. dbus error: %s (%s)\n",
+ halDevices[i], dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ }
+ gLibHalFreeStringArray(halDevices);
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(FloppyDrives): failed to get devices with capability \"storage.cdrom\". dbus error: %s (%s)\n", dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ if (!gLibHalCtxShutdown(halContext, &dbusError)) /* what now? */
+ {
+ LogRel(("Host::COMGETTER(FloppyDrives): failed to shutdown the libhal context. dbus error: %s (%s)\n",
+ dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(FloppyDrives): failed to initialise libhal context. dbus error: %s (%s)\n",
+ dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ gLibHalCtxFree(halContext);
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(FloppyDrives): failed to set libhal connection to dbus.\n"));
+ }
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(FloppyDrives): failed to get a libhal context - out of memory?\n"));
+ }
+ gDBusConnectionUnref(dbusConnection);
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(FloppyDrives): failed to connect to dbus. dbus error: %s (%s)\n",
+ dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ return halSuccess;
+}
+
+
+/**
+ * Helper function to query the hal subsystem for information about fixed drives attached to the
+ * system.
+ *
+ * @returns COM status code. (setError is not called on failure as we only fail
+ * with E_OUTOFMEMORY.)
+ * @retval S_OK on success.
+ * @retval S_FALSE if HAL cannot be used.
+ * @param list Reference to list to return the path/model string pairs.
+ */
+HRESULT Host::i_getFixedDrivesFromHal(std::list<std::pair<com::Utf8Str, com::Utf8Str> > &list) RT_NOEXCEPT
+{
+ HRESULT hrc = S_FALSE;
+ if (!gLibHalCheckPresence())
+ return hrc;
+
+ DBusError dbusError;
+ gDBusErrorInit(&dbusError);
+ DBusConnection *dbusConnection = gDBusBusGet(DBUS_BUS_SYSTEM, &dbusError);
+ if (dbusConnection != 0)
+ {
+ LibHalContext *halContext = gLibHalCtxNew();
+ if (halContext != 0)
+ {
+ if (gLibHalCtxSetDBusConnection(halContext, dbusConnection))
+ {
+ if (gLibHalCtxInit(halContext, &dbusError))
+ {
+ int cDevices;
+ char **halDevices = gLibHalFindDeviceStringMatch(halContext, "storage.drive_type", "disk",
+ &cDevices, &dbusError);
+ if (halDevices != 0)
+ {
+ /* Hal is installed and working, so if no devices are reported, assume
+ that there are none. */
+ hrc = S_OK;
+ for (int i = 0; i < cDevices && hrc == S_OK; i++)
+ {
+ char *pszDevNode = gLibHalDeviceGetPropertyString(halContext, halDevices[i], "block.device",
+ &dbusError);
+ /* The fixed drive ioctls work only for raw device nodes. */
+ char *pszTmp = getfullrawname(pszDevNode);
+ gLibHalFreeString(pszDevNode);
+ pszDevNode = pszTmp;
+ if (pszDevNode != 0)
+ {
+ /* We do not check the error here, as this field may
+ not even exist. */
+ char *pszVendor = gLibHalDeviceGetPropertyString(halContext, halDevices[i], "info.vendor", 0);
+ char *pszProduct = gLibHalDeviceGetPropertyString(halContext, halDevices[i], "info.product",
+ &dbusError);
+ Utf8Str strDescription;
+ if (pszProduct != NULL && pszProduct[0] != '\0')
+ {
+ int vrc;
+ if (pszVendor != NULL && pszVendor[0] != '\0')
+ vrc = strDescription.printfNoThrow("%s %s", pszVendor, pszProduct);
+ else
+ vrc = strDescription.assignNoThrow(pszProduct);
+ AssertRCStmt(vrc, hrc = E_OUTOFMEMORY);
+ }
+ if (pszVendor != NULL)
+ gLibHalFreeString(pszVendor);
+ if (pszProduct != NULL)
+ gLibHalFreeString(pszProduct);
+
+ /* Correct device/partition/slice already choosen. Just add it to the return list */
+ if (hrc == S_OK)
+ try
+ {
+ list.push_back(std::pair<com::Utf8Str, com::Utf8Str>(pszDevNode, strDescription));
+ }
+ catch (std::bad_alloc &)
+ {
+ AssertFailedStmt(hrc = E_OUTOFMEMORY);
+ }
+ gLibHalFreeString(pszDevNode);
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(HostDrives): failed to get property \"block.device\" for device %s. dbus error: %s (%s)\n",
+ halDevices[i], dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ }
+ gLibHalFreeStringArray(halDevices);
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(HostDrives): failed to get devices with capability \"storage.disk\". dbus error: %s (%s)\n", dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ if (!gLibHalCtxShutdown(halContext, &dbusError)) /* what now? */
+ {
+ LogRel(("Host::COMGETTER(HostDrives): failed to shutdown the libhal context. dbus error: %s (%s)\n",
+ dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(HostDrives): failed to initialise libhal context. dbus error: %s (%s)\n",
+ dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ gLibHalCtxFree(halContext);
+ }
+ else
+ LogRel(("Host::COMGETTER(HostDrives): failed to set libhal connection to dbus.\n"));
+ }
+ else
+ LogRel(("Host::COMGETTER(HostDrives): failed to get a libhal context - out of memory?\n"));
+ gDBusConnectionUnref(dbusConnection);
+ }
+ else
+ {
+ LogRel(("Host::COMGETTER(HostDrives): failed to connect to dbus. dbus error: %s (%s)\n",
+ dbusError.name, dbusError.message));
+ gDBusErrorFree(&dbusError);
+ }
+ return hrc;
+}
+
+#endif /* RT_OS_SOLARIS and VBOX_USE_HAL */
+
+/** @todo get rid of dead code below - RT_OS_SOLARIS and RT_OS_LINUX are never both set */
+#if defined(RT_OS_SOLARIS)
+
+/**
+ * Helper function to parse the given mount file and add found entries
+ */
+void Host::i_parseMountTable(char *mountTable, std::list< ComObjPtr<Medium> > &list)
+{
+#ifdef RT_OS_LINUX
+ FILE *mtab = setmntent(mountTable, "r");
+ if (mtab)
+ {
+ struct mntent *mntent;
+ char *mnt_type;
+ char *mnt_dev;
+ char *tmp;
+ while ((mntent = getmntent(mtab)))
+ {
+ mnt_type = (char*)malloc(strlen(mntent->mnt_type) + 1);
+ mnt_dev = (char*)malloc(strlen(mntent->mnt_fsname) + 1);
+ strcpy(mnt_type, mntent->mnt_type);
+ strcpy(mnt_dev, mntent->mnt_fsname);
+ // supermount fs case
+ if (strcmp(mnt_type, "supermount") == 0)
+ {
+ tmp = strstr(mntent->mnt_opts, "fs=");
+ if (tmp)
+ {
+ free(mnt_type);
+ mnt_type = strdup(tmp + strlen("fs="));
+ if (mnt_type)
+ {
+ tmp = strchr(mnt_type, ',');
+ if (tmp)
+ *tmp = '\0';
+ }
+ }
+ tmp = strstr(mntent->mnt_opts, "dev=");
+ if (tmp)
+ {
+ free(mnt_dev);
+ mnt_dev = strdup(tmp + strlen("dev="));
+ if (mnt_dev)
+ {
+ tmp = strchr(mnt_dev, ',');
+ if (tmp)
+ *tmp = '\0';
+ }
+ }
+ }
+ // use strstr here to cover things fs types like "udf,iso9660"
+ if (strstr(mnt_type, "iso9660") == 0)
+ {
+ /** @todo check whether we've already got the drive in our list! */
+ if (i_validateDevice(mnt_dev, true))
+ {
+ ComObjPtr<Medium> hostDVDDriveObj;
+ hostDVDDriveObj.createObject();
+ hostDVDDriveObj->init(m->pParent, DeviceType_DVD, Bstr(mnt_dev));
+ list.push_back (hostDVDDriveObj);
+ }
+ }
+ free(mnt_dev);
+ free(mnt_type);
+ }
+ endmntent(mtab);
+ }
+#else // RT_OS_SOLARIS
+ FILE *mntFile = fopen(mountTable, "r");
+ if (mntFile)
+ {
+ struct mnttab mntTab;
+ while (getmntent(mntFile, &mntTab) == 0)
+ {
+ const char *mountName = mntTab.mnt_special;
+ const char *mountPoint = mntTab.mnt_mountp;
+ const char *mountFSType = mntTab.mnt_fstype;
+ if (mountName && mountPoint && mountFSType)
+ {
+ // skip devices we are not interested in
+ if ((*mountName && mountName[0] == '/') && // skip 'fake' devices (like -hosts,
+ // proc, fd, swap)
+ (*mountFSType && (strncmp(mountFSType, RT_STR_TUPLE("devfs")) != 0 && // skip devfs
+ // (i.e. /devices)
+ strncmp(mountFSType, RT_STR_TUPLE("dev")) != 0 && // skip dev (i.e. /dev)
+ strncmp(mountFSType, RT_STR_TUPLE("lofs")) != 0))) // skip loop-back file-system (lofs)
+ {
+ char *rawDevName = getfullrawname((char *)mountName);
+ if (i_validateDevice(rawDevName, true))
+ {
+ ComObjPtr<Medium> hostDVDDriveObj;
+ hostDVDDriveObj.createObject();
+ hostDVDDriveObj->init(m->pParent, DeviceType_DVD, Bstr(rawDevName));
+ list.push_back(hostDVDDriveObj);
+ }
+ free(rawDevName);
+ }
+ }
+ }
+
+ fclose(mntFile);
+ }
+#endif
+}
+
+/**
+ * Helper function to check whether the given device node is a valid drive
+ */
+bool Host::i_validateDevice(const char *deviceNode, bool isCDROM)
+{
+ struct stat statInfo;
+ bool retValue = false;
+
+ // sanity check
+ if (!deviceNode)
+ {
+ return false;
+ }
+
+ // first a simple stat() call
+ if (stat(deviceNode, &statInfo) < 0)
+ {
+ return false;
+ }
+ else
+ {
+ if (isCDROM)
+ {
+ if (S_ISCHR(statInfo.st_mode) || S_ISBLK(statInfo.st_mode))
+ {
+ int fileHandle;
+ // now try to open the device
+ fileHandle = open(deviceNode, O_RDONLY | O_NONBLOCK, 0);
+ if (fileHandle >= 0)
+ {
+ cdrom_subchnl cdChannelInfo;
+ cdChannelInfo.cdsc_format = CDROM_MSF;
+ // this call will finally reveal the whole truth
+#ifdef RT_OS_LINUX
+ if ((ioctl(fileHandle, CDROMSUBCHNL, &cdChannelInfo) == 0) ||
+ (errno == EIO) || (errno == ENOENT) ||
+ (errno == EINVAL) || (errno == ENOMEDIUM))
+#else
+ if ((ioctl(fileHandle, CDROMSUBCHNL, &cdChannelInfo) == 0) ||
+ (errno == EIO) || (errno == ENOENT) ||
+ (errno == EINVAL))
+#endif
+ {
+ retValue = true;
+ }
+ close(fileHandle);
+ }
+ }
+ } else
+ {
+ // floppy case
+ if (S_ISCHR(statInfo.st_mode) || S_ISBLK(statInfo.st_mode))
+ {
+ /// @todo do some more testing, maybe a nice IOCTL!
+ retValue = true;
+ }
+ }
+ }
+ return retValue;
+}
+#endif // RT_OS_SOLARIS
+
+#ifdef VBOX_WITH_USB
+/**
+ * Checks for the presence and status of the USB Proxy Service.
+ * Returns S_OK when the Proxy is present and OK, VBOX_E_HOST_ERROR (as a
+ * warning) if the proxy service is not available due to the way the host is
+ * configured (at present, that means that usbfs and hal/DBus are not
+ * available on a Linux host) or E_FAIL and a corresponding error message
+ * otherwise. Intended to be used by methods that rely on the Proxy Service
+ * availability.
+ *
+ * @note This method may return a warning result code. It is recommended to use
+ * MultiError to store the return value.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT Host::i_checkUSBProxyService()
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(m->pUSBProxyService, E_FAIL);
+ if (!m->pUSBProxyService->isActive())
+ {
+ /* disable the USB controller completely to avoid assertions if the
+ * USB proxy service could not start. */
+
+ switch (m->pUSBProxyService->getLastError())
+ {
+ case VERR_FILE_NOT_FOUND: /** @todo what does this mean? */
+ return setWarning(E_FAIL,
+ tr("Could not load the Host USB Proxy Service (VERR_FILE_NOT_FOUND). The service might not be installed on the host computer"));
+ case VERR_VUSB_USB_DEVICE_PERMISSION:
+ return setWarning(E_FAIL,
+ tr("VirtualBox is not currently allowed to access USB devices. You can change this by adding your user to the 'vboxusers' group. Please see the user manual for a more detailed explanation"));
+ case VERR_VUSB_USBFS_PERMISSION:
+ return setWarning(E_FAIL,
+ tr("VirtualBox is not currently allowed to access USB devices. You can change this by allowing your user to access the 'usbfs' folder and files. Please see the user manual for a more detailed explanation"));
+ case VINF_SUCCESS:
+ return setWarning(E_FAIL,
+ tr("The USB Proxy Service has not yet been ported to this host"));
+ default:
+ return setWarning(E_FAIL, "%s: %Rrc",
+ tr("Could not load the Host USB Proxy service"),
+ m->pUSBProxyService->getLastError());
+ }
+ }
+
+ return S_OK;
+}
+#endif /* VBOX_WITH_USB */
+
+HRESULT Host::i_updateNetIfList()
+{
+#ifdef VBOX_WITH_HOSTNETIF_API
+ AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL);
+
+ /** @todo r=klaus it would save lots of clock cycles if for concurrent
+ * threads executing this code we'd only do one interface enumeration
+ * and update, and let the other threads use the result as is. However
+ * if there's a constant hammering of this method, we don't want this
+ * to cause update starvation. */
+ HostNetworkInterfaceList list;
+ int rc = NetIfList(list);
+ if (rc)
+ {
+ Log(("Failed to get host network interface list with rc=%Rrc\n", rc));
+ return E_FAIL;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(m->pParent, E_FAIL);
+ /* Make a copy as the original may be partially destroyed later. */
+ HostNetworkInterfaceList listCopy(list);
+ HostNetworkInterfaceList::iterator itOld, itNew;
+# ifdef VBOX_WITH_RESOURCE_USAGE_API
+ PerformanceCollector *aCollector = m->pParent->i_performanceCollector();
+# endif
+ for (itOld = m->llNetIfs.begin(); itOld != m->llNetIfs.end(); ++itOld)
+ {
+ bool fGone = true;
+ Bstr nameOld;
+ (*itOld)->COMGETTER(Name)(nameOld.asOutParam());
+ for (itNew = listCopy.begin(); itNew != listCopy.end(); ++itNew)
+ {
+ Bstr nameNew;
+ (*itNew)->COMGETTER(Name)(nameNew.asOutParam());
+ if (nameNew == nameOld)
+ {
+ fGone = false;
+ (*itNew)->uninit();
+ listCopy.erase(itNew);
+ break;
+ }
+ }
+ if (fGone)
+ {
+# ifdef VBOX_WITH_RESOURCE_USAGE_API
+ (*itOld)->i_unregisterMetrics(aCollector, this);
+ (*itOld)->uninit();
+# endif
+ }
+ }
+ /*
+ * Need to set the references to VirtualBox object in all interface objects
+ * (see @bugref{6439}).
+ */
+ for (itNew = list.begin(); itNew != list.end(); ++itNew)
+ (*itNew)->i_setVirtualBox(m->pParent);
+ /* At this point listCopy will contain newly discovered interfaces only. */
+ for (itNew = listCopy.begin(); itNew != listCopy.end(); ++itNew)
+ {
+ HostNetworkInterfaceType_T t;
+ HRESULT hrc = (*itNew)->COMGETTER(InterfaceType)(&t);
+ if (FAILED(hrc))
+ {
+ Bstr n;
+ (*itNew)->COMGETTER(Name)(n.asOutParam());
+ LogRel(("Host::updateNetIfList: failed to get interface type for %ls\n", n.raw()));
+ }
+ else if (t == HostNetworkInterfaceType_Bridged)
+ {
+# ifdef VBOX_WITH_RESOURCE_USAGE_API
+ (*itNew)->i_registerMetrics(aCollector, this);
+# endif
+ }
+ }
+ m->llNetIfs = list;
+ return S_OK;
+#else
+ return E_NOTIMPL;
+#endif
+}
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+
+void Host::i_registerDiskMetrics(PerformanceCollector *aCollector)
+{
+ pm::CollectorHAL *hal = aCollector->getHAL();
+ /* Create sub metrics */
+ Utf8StrFmt fsNameBase("FS/{%s}/Usage", "/");
+ //Utf8StrFmt fsNameBase("Filesystem/[root]/Usage");
+ pm::SubMetric *fsRootUsageTotal = new pm::SubMetric(fsNameBase + "/Total",
+ "Root file system size.");
+ pm::SubMetric *fsRootUsageUsed = new pm::SubMetric(fsNameBase + "/Used",
+ "Root file system space currently occupied.");
+ pm::SubMetric *fsRootUsageFree = new pm::SubMetric(fsNameBase + "/Free",
+ "Root file system space currently empty.");
+
+ pm::BaseMetric *fsRootUsage = new pm::HostFilesystemUsage(hal, this,
+ fsNameBase, "/",
+ fsRootUsageTotal,
+ fsRootUsageUsed,
+ fsRootUsageFree);
+ aCollector->registerBaseMetric(fsRootUsage);
+
+ aCollector->registerMetric(new pm::Metric(fsRootUsage, fsRootUsageTotal, 0));
+ aCollector->registerMetric(new pm::Metric(fsRootUsage, fsRootUsageTotal,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(fsRootUsage, fsRootUsageTotal,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(fsRootUsage, fsRootUsageTotal,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(fsRootUsage, fsRootUsageUsed, 0));
+ aCollector->registerMetric(new pm::Metric(fsRootUsage, fsRootUsageUsed,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(fsRootUsage, fsRootUsageUsed,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(fsRootUsage, fsRootUsageUsed,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(fsRootUsage, fsRootUsageFree, 0));
+ aCollector->registerMetric(new pm::Metric(fsRootUsage, fsRootUsageFree,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(fsRootUsage, fsRootUsageFree,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(fsRootUsage, fsRootUsageFree,
+ new pm::AggregateMax()));
+
+ /* For now we are concerned with the root file system only. */
+ pm::DiskList disksUsage, disksLoad;
+ int rc = hal->getDiskListByFs("/", disksUsage, disksLoad);
+ if (RT_FAILURE(rc))
+ return;
+ pm::DiskList::iterator it;
+ for (it = disksLoad.begin(); it != disksLoad.end(); ++it)
+ {
+ Utf8StrFmt strName("Disk/%s", it->c_str());
+ pm::SubMetric *fsLoadUtil = new pm::SubMetric(strName + "/Load/Util",
+ "Percentage of time disk was busy serving I/O requests.");
+ pm::BaseMetric *fsLoad = new pm::HostDiskLoadRaw(hal, this, strName + "/Load",
+ *it, fsLoadUtil);
+ aCollector->registerBaseMetric(fsLoad);
+
+ aCollector->registerMetric(new pm::Metric(fsLoad, fsLoadUtil, 0));
+ aCollector->registerMetric(new pm::Metric(fsLoad, fsLoadUtil,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(fsLoad, fsLoadUtil,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(fsLoad, fsLoadUtil,
+ new pm::AggregateMax()));
+ }
+ for (it = disksUsage.begin(); it != disksUsage.end(); ++it)
+ {
+ Utf8StrFmt strName("Disk/%s", it->c_str());
+ pm::SubMetric *fsUsageTotal = new pm::SubMetric(strName + "/Usage/Total",
+ "Disk size.");
+ pm::BaseMetric *fsUsage = new pm::HostDiskUsage(hal, this, strName + "/Usage",
+ *it, fsUsageTotal);
+ aCollector->registerBaseMetric(fsUsage);
+
+ aCollector->registerMetric(new pm::Metric(fsUsage, fsUsageTotal, 0));
+ aCollector->registerMetric(new pm::Metric(fsUsage, fsUsageTotal,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(fsUsage, fsUsageTotal,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(fsUsage, fsUsageTotal,
+ new pm::AggregateMax()));
+ }
+}
+
+void Host::i_registerMetrics(PerformanceCollector *aCollector)
+{
+ pm::CollectorHAL *hal = aCollector->getHAL();
+ /* Create sub metrics */
+ pm::SubMetric *cpuLoadUser = new pm::SubMetric("CPU/Load/User",
+ "Percentage of processor time spent in user mode.");
+ pm::SubMetric *cpuLoadKernel = new pm::SubMetric("CPU/Load/Kernel",
+ "Percentage of processor time spent in kernel mode.");
+ pm::SubMetric *cpuLoadIdle = new pm::SubMetric("CPU/Load/Idle",
+ "Percentage of processor time spent idling.");
+ pm::SubMetric *cpuMhzSM = new pm::SubMetric("CPU/MHz",
+ "Average of current frequency of all processors.");
+ pm::SubMetric *ramUsageTotal = new pm::SubMetric("RAM/Usage/Total",
+ "Total physical memory installed.");
+ pm::SubMetric *ramUsageUsed = new pm::SubMetric("RAM/Usage/Used",
+ "Physical memory currently occupied.");
+ pm::SubMetric *ramUsageFree = new pm::SubMetric("RAM/Usage/Free",
+ "Physical memory currently available to applications.");
+ pm::SubMetric *ramVMMUsed = new pm::SubMetric("RAM/VMM/Used",
+ "Total physical memory used by the hypervisor.");
+ pm::SubMetric *ramVMMFree = new pm::SubMetric("RAM/VMM/Free",
+ "Total physical memory free inside the hypervisor.");
+ pm::SubMetric *ramVMMBallooned = new pm::SubMetric("RAM/VMM/Ballooned",
+ "Total physical memory ballooned by the hypervisor.");
+ pm::SubMetric *ramVMMShared = new pm::SubMetric("RAM/VMM/Shared",
+ "Total physical memory shared between VMs.");
+
+
+ /* Create and register base metrics */
+ pm::BaseMetric *cpuLoad = new pm::HostCpuLoadRaw(hal, this, cpuLoadUser, cpuLoadKernel,
+ cpuLoadIdle);
+ aCollector->registerBaseMetric(cpuLoad);
+ pm::BaseMetric *cpuMhz = new pm::HostCpuMhz(hal, this, cpuMhzSM);
+ aCollector->registerBaseMetric(cpuMhz);
+ pm::BaseMetric *ramUsage = new pm::HostRamUsage(hal, this,
+ ramUsageTotal,
+ ramUsageUsed,
+ ramUsageFree);
+ aCollector->registerBaseMetric(ramUsage);
+ pm::BaseMetric *ramVmm = new pm::HostRamVmm(aCollector->getGuestManager(), this,
+ ramVMMUsed,
+ ramVMMFree,
+ ramVMMBallooned,
+ ramVMMShared);
+ aCollector->registerBaseMetric(ramVmm);
+
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadUser, 0));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadUser,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadUser,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadUser,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadKernel, 0));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadKernel,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadKernel,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadKernel,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadIdle, 0));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadIdle,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadIdle,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadIdle,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(cpuMhz, cpuMhzSM, 0));
+ aCollector->registerMetric(new pm::Metric(cpuMhz, cpuMhzSM,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(cpuMhz, cpuMhzSM,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(cpuMhz, cpuMhzSM,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageTotal, 0));
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageTotal,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageTotal,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageTotal,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageUsed, 0));
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageUsed,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageUsed,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageUsed,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageFree, 0));
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageFree,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageFree,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageFree,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMUsed, 0));
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMUsed,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMUsed,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMUsed,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMFree, 0));
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMFree,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMFree,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMFree,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMBallooned, 0));
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMBallooned,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMBallooned,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMBallooned,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMShared, 0));
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMShared,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMShared,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(ramVmm, ramVMMShared,
+ new pm::AggregateMax()));
+ i_registerDiskMetrics(aCollector);
+}
+
+void Host::i_unregisterMetrics(PerformanceCollector *aCollector)
+{
+ aCollector->unregisterMetricsFor(this);
+ aCollector->unregisterBaseMetricsFor(this);
+}
+
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+
+
+/* static */
+void Host::i_generateMACAddress(Utf8Str &mac)
+{
+ /*
+ * Our strategy is as follows: the first three bytes are our fixed
+ * vendor ID (080027). The remaining 3 bytes will be taken from the
+ * start of a GUID. This is a fairly safe algorithm.
+ */
+ Guid guid;
+ guid.create();
+ mac = Utf8StrFmt("080027%02X%02X%02X",
+ guid.raw()->au8[0], guid.raw()->au8[1], guid.raw()->au8[2]);
+}
+
+#ifdef RT_OS_WINDOWS
+HRESULT Host::i_getFixedDrivesFromGlobalNamespace(std::list<std::pair<com::Utf8Str, com::Utf8Str> > &aDriveList) RT_NOEXCEPT
+{
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ RTVFSDIR hVfsDir;
+ int rc = RTVfsChainOpenDir("\\\\:iprtnt:\\GLOBAL??", 0 /*fFlags*/, &hVfsDir, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return setError(E_FAIL, tr("Failed to open NT\\GLOBAL?? (error %Rrc)"), rc);
+
+ /*
+ * Scan whole directory and find any 'PhysicalDiskX' entries. Next, combine with '\\.\'
+ * to obtain the harddisk dev path.
+ */
+ size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
+ PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (!pDirEntry)
+ {
+ RTVfsDirRelease(hVfsDir);
+ return setError(E_OUTOFMEMORY, tr("Out of memory! (direntry buffer)"));
+ }
+
+ HRESULT hrc = S_OK;
+ for (;;)
+ {
+ size_t cbDirEntry = cbDirEntryAlloced;
+ rc = RTVfsDirReadEx(hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_NOTHING);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ RTMemTmpFree(pDirEntry);
+ cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
+ pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (pDirEntry)
+ continue;
+ hrc = setError(E_OUTOFMEMORY, tr("Out of memory! (direntry buffer)"));
+ }
+ else if (rc != VERR_NO_MORE_FILES)
+ hrc = setError(VBOX_E_IPRT_ERROR, tr("RTVfsDirReadEx failed: %Rrc"), rc);
+ break;
+ }
+ if (RTStrStartsWith(pDirEntry->szName, "PhysicalDrive"))
+ {
+ char szPhysicalDrive[64];
+ RTStrPrintf(szPhysicalDrive, sizeof(szPhysicalDrive), "\\\\.\\%s", pDirEntry->szName);
+
+ RTFILE hRawFile = NIL_RTFILE;
+ int vrc = RTFileOpen(&hRawFile, szPhysicalDrive, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_FAILURE(vrc))
+ {
+ try
+ {
+ aDriveList.push_back(std::pair<com::Utf8Str, com::Utf8Str>(szPhysicalDrive, tr("Unknown (Access denied)")));
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = setError(E_OUTOFMEMORY, tr("Out of memory"));
+ break;
+ }
+ continue;
+ }
+
+ DWORD cbBytesReturned = 0;
+ uint8_t abBuffer[1024];
+ RT_ZERO(abBuffer);
+
+ STORAGE_PROPERTY_QUERY query;
+ RT_ZERO(query);
+ query.PropertyId = StorageDeviceProperty;
+ query.QueryType = PropertyStandardQuery;
+
+ BOOL fRc = DeviceIoControl((HANDLE)RTFileToNative(hRawFile),
+ IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query),
+ abBuffer, sizeof(abBuffer), &cbBytesReturned, NULL);
+ RTFileClose(hRawFile);
+ char szModel[1024];
+ if (fRc)
+ {
+ PSTORAGE_DEVICE_DESCRIPTOR pDevDescriptor = (PSTORAGE_DEVICE_DESCRIPTOR)abBuffer;
+ char *pszProduct = pDevDescriptor->ProductIdOffset ? (char *)&abBuffer[pDevDescriptor->ProductIdOffset] : NULL;
+ if (pszProduct)
+ {
+ RTStrPurgeEncoding(pszProduct);
+ if (*pszProduct != '\0')
+ {
+ char *pszVendor = pDevDescriptor->VendorIdOffset ? (char *)&abBuffer[pDevDescriptor->VendorIdOffset] : NULL;
+ if (pszVendor)
+ RTStrPurgeEncoding(pszVendor);
+ if (pszVendor && *pszVendor)
+ RTStrPrintf(szModel, sizeof(szModel), "%s %s", pszVendor, pszProduct);
+ else
+ RTStrCopy(szModel, sizeof(szModel), pszProduct);
+ }
+ }
+ }
+ try
+ {
+ aDriveList.push_back(std::pair<com::Utf8Str, com::Utf8Str>(szPhysicalDrive, szModel));
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = setError(E_OUTOFMEMORY, tr("Out of memory"));
+ break;
+ }
+ }
+ }
+ if (FAILED(hrc))
+ aDriveList.clear();
+ RTMemTmpFree(pDirEntry);
+ RTVfsDirRelease(hVfsDir);
+ return hrc;
+}
+#endif
+
+/**
+ * @throws nothing
+ */
+HRESULT Host::i_getDrivesPathsList(std::list<std::pair<com::Utf8Str, com::Utf8Str> > &aDriveList) RT_NOEXCEPT
+{
+#ifdef RT_OS_WINDOWS
+ return i_getFixedDrivesFromGlobalNamespace(aDriveList);
+
+#elif defined(RT_OS_DARWIN)
+ /*
+ * Get the list of fixed drives from iokit.cpp and transfer it to aDriveList.
+ */
+ PDARWINFIXEDDRIVE pDrives = DarwinGetFixedDrives();
+ HRESULT hrc;
+ try
+ {
+ for (PDARWINFIXEDDRIVE pCurDrv = pDrives; pCurDrv; pCurDrv = pCurDrv->pNext)
+ aDriveList.push_back(std::pair<com::Utf8Str, com::Utf8Str>(pCurDrv->szName, pCurDrv->pszModel));
+ hrc = S_OK;
+ }
+ catch (std::bad_alloc &)
+ {
+ aDriveList.clear();
+ hrc = E_OUTOFMEMORY;
+ }
+
+ while (pDrives)
+ {
+ PDARWINFIXEDDRIVE pFreeMe = pDrives;
+ pDrives = pDrives->pNext;
+ ASMCompilerBarrier();
+ RTMemFree(pFreeMe);
+ }
+ return hrc;
+
+#elif defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)
+ /*
+ * The list of fixed drives is kept in the VBoxMainDriveInfo instance, so
+ * update it and tranfer the info to aDriveList.
+ *
+ * This obviously requires us to write lock the object!
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ int vrc = m->hostDrives.updateFixedDrives(); /* nothrow */
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to update fixed drive list (%Rrc)"), vrc);
+
+ try
+ {
+ for (DriveInfoList::const_iterator it = m->hostDrives.FixedDriveBegin(); it != m->hostDrives.FixedDriveEnd(); ++it)
+ aDriveList.push_back(std::pair<com::Utf8Str, com::Utf8Str>(it->mDevice, it->mDescription));
+ }
+ catch (std::bad_alloc &)
+ {
+ aDriveList.clear();
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+
+#elif defined(RT_OS_SOLARIS)
+ /*
+ * We can get the info from HAL, if not present/working we'll get by
+ * walking the device tree.
+ */
+# ifdef VBOX_USE_LIBHAL
+ HRESULT hrc = i_getFixedDrivesFromHal(aDriveList);
+ if (hrc != S_FALSE)
+ return hrc;
+ aDriveList.clear(); /* just in case */
+# endif
+ return i_getFixedDrivesFromDevTree(aDriveList);
+
+#else
+ /* PORTME */
+ RT_NOREF(aDriveList);
+ return E_NOTIMPL;
+#endif
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/HostNetworkInterfaceImpl.cpp b/src/VBox/Main/src-server/HostNetworkInterfaceImpl.cpp
new file mode 100644
index 00000000..a0f2e4b0
--- /dev/null
+++ b/src/VBox/Main/src-server/HostNetworkInterfaceImpl.cpp
@@ -0,0 +1,812 @@
+/* $Id: HostNetworkInterfaceImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOSTNETWORKINTERFACE
+#include "HostNetworkInterfaceImpl.h"
+#include "AutoCaller.h"
+#include "netif.h"
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+# include "Performance.h"
+# include "PerformanceImpl.h"
+#endif
+#include "LoggingNew.h"
+
+#include <iprt/cpp/utils.h>
+
+#ifdef RT_OS_FREEBSD
+# include <netinet/in.h> /* INADDR_NONE */
+#endif /* RT_OS_FREEBSD */
+
+#include "VirtualBoxImpl.h"
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+HostNetworkInterface::HostNetworkInterface()
+ : mVirtualBox(NULL)
+{
+}
+
+HostNetworkInterface::~HostNetworkInterface()
+{
+}
+
+HRESULT HostNetworkInterface::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void HostNetworkInterface::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the host object.
+ *
+ * @returns COM result indicator
+ * @param aInterfaceName name of the network interface
+ * @param aShortName short name of the network interface
+ * @param aGuid GUID of the host network interface
+ * @param ifType interface type
+ */
+HRESULT HostNetworkInterface::init(Utf8Str aInterfaceName, Utf8Str aShortName, Guid aGuid, HostNetworkInterfaceType_T ifType)
+{
+ LogFlowThisFunc(("aInterfaceName={%s}, aGuid={%s}\n",
+ aInterfaceName.c_str(), aGuid.toString().c_str()));
+
+ ComAssertRet(!aInterfaceName.isEmpty(), E_INVALIDARG);
+ ComAssertRet(aGuid.isValid(), E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mInterfaceName) = aInterfaceName;
+#ifdef VBOX_WITH_HOSTNETIF_API
+ unconst(mNetworkName) = i_composeNetworkName(aShortName);
+#endif
+ unconst(mShortName) = aShortName;
+ unconst(mGuid) = aGuid;
+ mIfType = ifType;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+
+void HostNetworkInterface::i_registerMetrics(PerformanceCollector *aCollector, ComPtr<IUnknown> objptr)
+{
+ LogFlowThisFunc(("mShortName={%s}, mInterfaceName={%s}, mGuid={%s}, mSpeedMbits=%u\n",
+ mShortName.c_str(), mInterfaceName.c_str(), mGuid.toString().c_str(), m.speedMbits));
+ pm::CollectorHAL *hal = aCollector->getHAL();
+ /* Create sub metrics */
+ Utf8StrFmt strName("Net/%s", mShortName.c_str());
+ pm::SubMetric *networkLoadRx = new pm::SubMetric(strName + "/Load/Rx",
+ "Percentage of network interface receive bandwidth used.");
+ pm::SubMetric *networkLoadTx = new pm::SubMetric(strName + "/Load/Tx",
+ "Percentage of network interface transmit bandwidth used.");
+ pm::SubMetric *networkLinkSpeed = new pm::SubMetric(strName + "/LinkSpeed",
+ "Physical link speed.");
+
+ /* Create and register base metrics */
+ pm::BaseMetric *networkSpeed = new pm::HostNetworkSpeed(hal, objptr, strName + "/LinkSpeed",
+ Utf8Str(mShortName), Utf8Str(mInterfaceName),
+ m.speedMbits, networkLinkSpeed);
+ aCollector->registerBaseMetric(networkSpeed);
+ pm::BaseMetric *networkLoad = new pm::HostNetworkLoadRaw(hal, objptr, strName + "/Load",
+ Utf8Str(mShortName), Utf8Str(mInterfaceName),
+ m.speedMbits, networkLoadRx, networkLoadTx);
+ aCollector->registerBaseMetric(networkLoad);
+
+ aCollector->registerMetric(new pm::Metric(networkSpeed, networkLinkSpeed, 0));
+ aCollector->registerMetric(new pm::Metric(networkSpeed, networkLinkSpeed,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(networkSpeed, networkLinkSpeed,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(networkSpeed, networkLinkSpeed,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(networkLoad, networkLoadRx, 0));
+ aCollector->registerMetric(new pm::Metric(networkLoad, networkLoadRx,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(networkLoad, networkLoadRx,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(networkLoad, networkLoadRx,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(networkLoad, networkLoadTx, 0));
+ aCollector->registerMetric(new pm::Metric(networkLoad, networkLoadTx,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(networkLoad, networkLoadTx,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(networkLoad, networkLoadTx,
+ new pm::AggregateMax()));
+}
+
+void HostNetworkInterface::i_unregisterMetrics(PerformanceCollector *aCollector, ComPtr<IUnknown> objptr)
+{
+ LogFlowThisFunc(("mShortName={%s}, mInterfaceName={%s}, mGuid={%s}\n",
+ mShortName.c_str(), mInterfaceName.c_str(), mGuid.toString().c_str()));
+ Utf8StrFmt name("Net/%s", mShortName.c_str());
+ aCollector->unregisterMetricsFor(objptr, name + "/*");
+ aCollector->unregisterBaseMetricsFor(objptr, name);
+}
+
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+
+#ifdef VBOX_WITH_HOSTNETIF_API
+#if defined(RT_OS_WINDOWS)
+
+HRESULT HostNetworkInterface::saveAdapterConfigParameter(const char *szParamName, const Utf8Str &strValue)
+{
+ AssertReturn(mVirtualBox != NULL, E_POINTER);
+ return mVirtualBox->SetExtraData(BstrFmt("HostOnly/{%RTuuid}/%s", mGuid.raw(), szParamName).raw(), Bstr(strValue).raw());
+}
+
+HRESULT HostNetworkInterface::eraseAdapterConfigParameter(const char *szParamName)
+{
+ AssertReturn(mVirtualBox != NULL, E_POINTER);
+ return mVirtualBox->SetExtraData(BstrFmt("HostOnly/{%RTuuid}/%s", mGuid.raw(), szParamName).raw(), NULL);
+}
+
+HRESULT HostNetworkInterface::saveAdapterConfigIPv4Dhcp()
+{
+ HRESULT hrc = saveAdapterConfigParameter("IPAddress", "DHCP");
+ if (hrc == S_OK)
+ hrc = eraseAdapterConfigParameter("IPNetMask");
+ return hrc;
+}
+
+HRESULT HostNetworkInterface::saveAdapterConfigIPv4(ULONG addr, ULONG mask)
+{
+ HRESULT hrc = saveAdapterConfigParameter("IPAddress", Utf8StrFmt("%RTnaipv4", addr));
+ if (hrc == S_OK)
+ hrc = saveAdapterConfigParameter("IPNetMask", Utf8StrFmt("%RTnaipv4", mask));
+ return hrc;
+}
+
+HRESULT HostNetworkInterface::saveAdapterConfigIPv6(const Utf8Str& addr, ULONG prefix)
+{
+ HRESULT hrc = saveAdapterConfigParameter("IPV6Address", addr);
+ if (hrc == S_OK)
+ hrc = saveAdapterConfigParameter("IPV6PrefixLen", Utf8StrFmt("%u", prefix));
+ return hrc;
+}
+
+bool HostNetworkInterface::isInConfigFile(void)
+{
+ /* We care about host-only adapters only */
+ if (mIfType != HostNetworkInterfaceType_HostOnly)
+ return true;
+
+ Assert(mVirtualBox != NULL);
+ if (mVirtualBox == NULL)
+ return false; /* Trigger config update, which will fail with proper return code */
+ Bstr tmpName;
+ mVirtualBox->GetExtraData(BstrFmt("HostOnly/{%RTuuid}/Name", mGuid.raw()).raw(), tmpName.asOutParam());
+ return (tmpName.isNotEmpty() && tmpName == mInterfaceName);
+
+}
+
+HRESULT HostNetworkInterface::saveAdapterConfig(void)
+{
+ /* We care about host-only adapters only */
+ if (mIfType != HostNetworkInterfaceType_HostOnly)
+ return true;
+
+ HRESULT hrc = saveAdapterConfigParameter("Name", mInterfaceName.c_str());
+ if (FAILED(hrc))
+ return hrc;
+ if (m.dhcpEnabled)
+ hrc = saveAdapterConfigIPv4Dhcp();
+ else
+ hrc = saveAdapterConfigIPv4(m.IPAddress, m.networkMask);
+ if (SUCCEEDED(hrc))
+ hrc = saveAdapterConfigIPv6(m.IPV6Address.c_str(), m.IPV6NetworkMaskPrefixLength);
+ return hrc;
+}
+
+HRESULT HostNetworkInterface::i_updatePersistentConfig(void)
+{
+ if (mVirtualBox == NULL)
+ return E_POINTER;
+
+ HRESULT hrc = S_OK;
+ if (!isInConfigFile())
+ {
+ hrc = saveAdapterConfig();
+ }
+ return hrc;
+}
+
+#endif /* defined(RT_OS_WINDOWS) */
+
+HRESULT HostNetworkInterface::updateConfig()
+{
+ NETIFINFO info;
+ int rc = NetIfGetConfig(this, &info);
+ if (RT_SUCCESS(rc))
+ {
+ int iPrefixIPv6;
+
+ m.realIPAddress = m.IPAddress = info.IPAddress.u;
+ m.realNetworkMask = m.networkMask = info.IPNetMask.u;
+ m.dhcpEnabled = info.fDhcpEnabled;
+ if (info.IPv6Address.s.Lo || info.IPv6Address.s.Hi)
+ m.realIPV6Address = m.IPV6Address = Utf8StrFmt("%RTnaipv6", &info.IPv6Address);
+ else
+ m.realIPV6Address = m.IPV6Address = Utf8Str::Empty;
+ RTNetMaskToPrefixIPv6(&info.IPv6NetMask, &iPrefixIPv6);
+ m.realIPV6PrefixLength = m.IPV6NetworkMaskPrefixLength = (ULONG)iPrefixIPv6;
+ m.hardwareAddress = Utf8StrFmt("%RTmac", &info.MACAddress);
+ AssertCompile((unsigned)NETIF_T_UNKNOWN == (unsigned)HostNetworkInterfaceMediumType_Unknown);
+ m.mediumType = (HostNetworkInterfaceMediumType_T)info.enmMediumType;
+ AssertCompile((unsigned)NETIF_S_UNKNOWN == (unsigned)HostNetworkInterfaceStatus_Unknown);
+ m.status = (HostNetworkInterfaceStatus_T)info.enmStatus;
+ m.speedMbits = info.uSpeedMbits;
+ m.wireless = info.fWireless;
+ return S_OK;
+ }
+ return rc == VERR_NOT_IMPLEMENTED ? E_NOTIMPL : E_FAIL;
+}
+
+Utf8Str HostNetworkInterface::i_composeNetworkName(const Utf8Str aShortName)
+{
+ return Utf8Str("HostInterfaceNetworking-").append(aShortName);
+}
+/**
+ * Initializes the host object.
+ *
+ * @returns COM result indicator
+ * @param aInterfaceName name of the network interface
+ * @param aGuid GUID of the host network interface
+ */
+HRESULT HostNetworkInterface::init(Utf8Str aInterfaceName, HostNetworkInterfaceType_T ifType, PNETIFINFO pIf)
+{
+// LogFlowThisFunc(("aInterfaceName={%s}, aGuid={%s}\n",
+// aInterfaceName.c_str(), aGuid.toString().c_str()));
+
+// ComAssertRet(aInterfaceName, E_INVALIDARG);
+// ComAssertRet(aGuid.isValid(), E_INVALIDARG);
+ ComAssertRet(pIf, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mInterfaceName) = aInterfaceName;
+ unconst(mGuid) = pIf->Uuid;
+ if (pIf->szShortName[0])
+ {
+ unconst(mNetworkName) = i_composeNetworkName(pIf->szShortName);
+ unconst(mShortName) = pIf->szShortName;
+ }
+ else
+ {
+ unconst(mNetworkName) = i_composeNetworkName(aInterfaceName);
+ unconst(mShortName) = aInterfaceName;
+ }
+ mIfType = ifType;
+
+ int iPrefixIPv6;
+
+ m.realIPAddress = m.IPAddress = pIf->IPAddress.u;
+ m.realNetworkMask = m.networkMask = pIf->IPNetMask.u;
+ if (pIf->IPv6Address.s.Lo || pIf->IPv6Address.s.Hi)
+ m.realIPV6Address = m.IPV6Address = Utf8StrFmt("%RTnaipv6", &pIf->IPv6Address);
+ else
+ m.realIPV6Address = m.IPV6Address = Utf8Str::Empty;
+ RTNetMaskToPrefixIPv6(&pIf->IPv6NetMask, &iPrefixIPv6);
+ m.realIPV6PrefixLength = m.IPV6NetworkMaskPrefixLength = (ULONG)iPrefixIPv6;
+ m.dhcpEnabled = pIf->fDhcpEnabled;
+ m.hardwareAddress = Utf8StrFmt("%RTmac", &pIf->MACAddress);
+ AssertCompile((unsigned)NETIF_T_UNKNOWN == (unsigned)HostNetworkInterfaceMediumType_Unknown);
+ m.mediumType = (HostNetworkInterfaceMediumType_T)pIf->enmMediumType;
+ AssertCompile((unsigned)NETIF_S_UNKNOWN == (unsigned)HostNetworkInterfaceStatus_Unknown);
+ m.status = (HostNetworkInterfaceStatus_T)pIf->enmStatus;
+ m.speedMbits = pIf->uSpeedMbits;
+ m.wireless = pIf->fWireless;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+#endif /* VBOX_WITH_HOSTNETIF_API */
+
+// wrapped IHostNetworkInterface properties
+/////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the name of the host network interface.
+ *
+ * @returns COM status code
+ * @param aInterfaceName - Interface Name
+ */
+
+HRESULT HostNetworkInterface::getName(com::Utf8Str &aInterfaceName)
+{
+ aInterfaceName = mInterfaceName;
+ return S_OK;
+}
+
+/**
+ * Returns the short name of the host network interface.
+ *
+ * @returns COM status code
+ * @param aShortName Short Name
+ */
+
+HRESULT HostNetworkInterface::getShortName(com::Utf8Str &aShortName)
+{
+ aShortName = mShortName;
+
+ return S_OK;
+}
+
+/**
+ * Returns the GUID of the host network interface.
+ *
+ * @returns COM status code
+ * @param aGuid GUID
+ */
+HRESULT HostNetworkInterface::getId(com::Guid &aGuid)
+{
+ aGuid = mGuid;
+
+ return S_OK;
+}
+
+HRESULT HostNetworkInterface::getDHCPEnabled(BOOL *aDHCPEnabled)
+{
+ *aDHCPEnabled = m.dhcpEnabled;
+
+ return S_OK;
+}
+
+
+/**
+ * Returns the IP address of the host network interface.
+ *
+ * @returns COM status code
+ * @param aIPAddress Address name
+ */
+HRESULT HostNetworkInterface::getIPAddress(com::Utf8Str &aIPAddress)
+{
+ in_addr tmp;
+#if defined(RT_OS_WINDOWS)
+ tmp.S_un.S_addr = m.IPAddress;
+#else
+ tmp.s_addr = m.IPAddress;
+#endif
+ char *addr = inet_ntoa(tmp);
+ if (addr)
+ {
+ aIPAddress = addr;
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+/**
+ * Returns the netwok mask of the host network interface.
+ *
+ * @returns COM status code
+ * @param aNetworkMask name.
+ */
+HRESULT HostNetworkInterface::getNetworkMask(com::Utf8Str &aNetworkMask)
+{
+
+ in_addr tmp;
+#if defined(RT_OS_WINDOWS)
+ tmp.S_un.S_addr = m.networkMask;
+#else
+ tmp.s_addr = m.networkMask;
+#endif
+ char *addr = inet_ntoa(tmp);
+ if (addr)
+ {
+ aNetworkMask = Utf8Str(addr);
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+HRESULT HostNetworkInterface::getIPV6Supported(BOOL *aIPV6Supported)
+{
+#if defined(RT_OS_WINDOWS)
+ *aIPV6Supported = FALSE;
+#else
+ *aIPV6Supported = TRUE;
+#endif
+
+ return S_OK;
+}
+
+/**
+ * Returns the IP V6 address of the host network interface.
+ *
+ * @returns COM status code
+ * @param aIPV6Address
+ */
+HRESULT HostNetworkInterface::getIPV6Address(com::Utf8Str &aIPV6Address)
+{
+ aIPV6Address = m.IPV6Address;
+ return S_OK;
+}
+
+/**
+ * Returns the IP V6 network mask prefix length of the host network interface.
+ *
+ * @returns COM status code
+ * @param aIPV6NetworkMaskPrefixLength address of result pointer
+ */
+HRESULT HostNetworkInterface::getIPV6NetworkMaskPrefixLength(ULONG *aIPV6NetworkMaskPrefixLength)
+{
+ *aIPV6NetworkMaskPrefixLength = m.IPV6NetworkMaskPrefixLength;
+
+ return S_OK;
+}
+
+/**
+ * Returns the hardware address of the host network interface.
+ *
+ * @returns COM status code
+ * @param aHardwareAddress hardware address
+ */
+HRESULT HostNetworkInterface::getHardwareAddress(com::Utf8Str &aHardwareAddress)
+{
+ aHardwareAddress = m.hardwareAddress;
+ return S_OK;
+}
+
+/**
+ * Returns the encapsulation protocol type of the host network interface.
+ *
+ * @returns COM status code
+ * @param aType address of result pointer
+ */
+HRESULT HostNetworkInterface::getMediumType(HostNetworkInterfaceMediumType_T *aType)
+{
+ *aType = m.mediumType;
+
+ return S_OK;
+}
+
+/**
+ * Returns the current state of the host network interface.
+ *
+ * @returns COM status code
+ * @param aStatus address of result pointer
+ */
+HRESULT HostNetworkInterface::getStatus(HostNetworkInterfaceStatus_T *aStatus)
+{
+ *aStatus = m.status;
+
+ return S_OK;
+}
+
+/**
+ * Returns network interface type
+ *
+ * @returns COM status code
+ * @param aType address of result pointer
+ */
+HRESULT HostNetworkInterface::getInterfaceType(HostNetworkInterfaceType_T *aType)
+{
+ *aType = mIfType;
+
+ return S_OK;
+
+}
+
+HRESULT HostNetworkInterface::getNetworkName(com::Utf8Str &aNetworkName)
+{
+ aNetworkName = mNetworkName;
+
+ return S_OK;
+}
+
+HRESULT HostNetworkInterface::getWireless(BOOL *aWireless)
+{
+ *aWireless = m.wireless;
+
+ return S_OK;
+}
+
+HRESULT HostNetworkInterface::enableStaticIPConfig(const com::Utf8Str &aIPAddress,
+ const com::Utf8Str &aNetworkMask)
+{
+#ifndef VBOX_WITH_HOSTNETIF_API
+ RT_NOREF(aIPAddress, aNetworkMask);
+ return E_NOTIMPL;
+#else
+ HRESULT hrc;
+
+ if (aIPAddress.isEmpty())
+ {
+ if (m.IPAddress)
+ {
+ int rc = NetIfEnableStaticIpConfig(mVirtualBox, this, m.IPAddress, 0, 0);
+ if (RT_SUCCESS(rc))
+ {
+ m.realIPAddress = 0;
+#if defined(RT_OS_WINDOWS)
+ eraseAdapterConfigParameter("IPAddress");
+ eraseAdapterConfigParameter("IPNetMask");
+#else /* !defined(RT_OS_WINDOWS) */
+ if (FAILED(mVirtualBox->SetExtraData(BstrFmt("HostOnly/%s/IPAddress",
+ mInterfaceName.c_str()).raw(), NULL)))
+ return E_FAIL;
+ if (FAILED(mVirtualBox->SetExtraData(BstrFmt("HostOnly/%s/IPNetMask",
+ mInterfaceName.c_str()).raw(), NULL)))
+ return E_FAIL;
+#endif /* !defined(RT_OS_WINDOWS) */
+ return S_OK;
+ }
+ }
+ else
+ return S_OK;
+ }
+
+ ULONG ip, mask;
+ ip = inet_addr(aIPAddress.c_str());
+ if (ip != INADDR_NONE)
+ {
+ if (aNetworkMask.isEmpty())
+ mask = 0xFFFFFF;
+ else
+ mask = inet_addr(aNetworkMask.c_str());
+ if (mask != INADDR_NONE)
+ {
+ if (m.realIPAddress == ip && m.realNetworkMask == mask)
+ return S_OK;
+ int rc = NetIfEnableStaticIpConfig(mVirtualBox, this, m.IPAddress, ip, mask);
+ if (RT_SUCCESS(rc))
+ {
+ m.realIPAddress = ip;
+ m.realNetworkMask = mask;
+#if defined(RT_OS_WINDOWS)
+ saveAdapterConfigIPv4(ip, mask);
+#else /* !defined(RT_OS_WINDOWS) */
+ if (FAILED(mVirtualBox->SetExtraData(BstrFmt("HostOnly/%s/IPAddress",
+ mInterfaceName.c_str()).raw(),
+ Bstr(aIPAddress).raw())))
+ return E_FAIL;
+ if (FAILED(mVirtualBox->SetExtraData(BstrFmt("HostOnly/%s/IPNetMask",
+ mInterfaceName.c_str()).raw(),
+ Bstr(aNetworkMask).raw())))
+ return E_FAIL;
+#endif /* !defined(RT_OS_WINDOWS) */
+ return S_OK;
+ }
+ else
+ {
+ LogRel(("Failed to EnableStaticIpConfig with rc=%Rrc\n", rc));
+ /* Global::vboxStatusCodeToCOM assert things we can guarantee */
+ switch (rc)
+ {
+ case VERR_NOT_IMPLEMENTED:
+ hrc = E_NOTIMPL;
+ break;
+ case VERR_ACCESS_DENIED:
+ hrc = E_ACCESSDENIED;
+ break;
+ default:
+ hrc = E_FAIL;
+ break;
+ }
+ return hrc;
+ }
+
+ }
+ }
+ return E_FAIL;
+#endif
+}
+
+HRESULT HostNetworkInterface::enableStaticIPConfigV6(const com::Utf8Str &aIPV6Address,
+ ULONG aIPV6NetworkMaskPrefixLength)
+{
+#ifndef VBOX_WITH_HOSTNETIF_API
+ RT_NOREF(aIPV6Address, aIPV6NetworkMaskPrefixLength);
+ return E_NOTIMPL;
+#else
+ if (aIPV6NetworkMaskPrefixLength > 128)
+ return mVirtualBox->setErrorBoth(E_INVALIDARG, VERR_INVALID_PARAMETER,
+ tr("Invalid IPv6 prefix length"));
+
+ HRESULT hrc;
+ int rc;
+
+ RTNETADDRIPV6 AddrOld, AddrNew;
+ char *pszZoneIgnored;
+ bool fAddrChanged;
+
+ rc = RTNetStrToIPv6Addr(aIPV6Address.c_str(), &AddrNew, &pszZoneIgnored);
+ if (RT_FAILURE(rc))
+ {
+ return mVirtualBox->setErrorBoth(E_INVALIDARG, rc, tr("Invalid IPv6 address"));
+ }
+
+ rc = RTNetStrToIPv6Addr(com::Utf8Str(m.realIPV6Address).c_str(), &AddrOld, &pszZoneIgnored);
+ if (RT_SUCCESS(rc))
+ {
+ fAddrChanged = (AddrNew.s.Lo != AddrOld.s.Lo || AddrNew.s.Hi != AddrOld.s.Hi);
+ }
+ else
+ {
+ fAddrChanged = true;
+ }
+
+ if ( fAddrChanged
+ || m.realIPV6PrefixLength != aIPV6NetworkMaskPrefixLength)
+ {
+ if (aIPV6NetworkMaskPrefixLength == 0)
+ aIPV6NetworkMaskPrefixLength = 64;
+ rc = NetIfEnableStaticIpConfigV6(mVirtualBox, this, m.IPV6Address.c_str(),
+ aIPV6Address.c_str(),
+ aIPV6NetworkMaskPrefixLength);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Failed to EnableStaticIpConfigV6 with rc=%Rrc\n", rc));
+ /* Global::vboxStatusCodeToCOM assert things we can guarantee */
+ switch (rc)
+ {
+ case VERR_NOT_IMPLEMENTED:
+ hrc = E_NOTIMPL;
+ break;
+ case VERR_ACCESS_DENIED:
+ hrc = E_ACCESSDENIED;
+ break;
+ default:
+ hrc = E_FAIL;
+ break;
+ }
+ return hrc;
+ }
+ else
+ {
+ m.realIPV6Address = aIPV6Address;
+ m.realIPV6PrefixLength = aIPV6NetworkMaskPrefixLength;
+#if defined(RT_OS_WINDOWS)
+ saveAdapterConfigIPv6(Bstr(aIPV6Address).raw(), aIPV6NetworkMaskPrefixLength);
+#else /* !defined(RT_OS_WINDOWS) */
+ if (FAILED(mVirtualBox->SetExtraData(BstrFmt("HostOnly/%s/IPV6Address",
+ mInterfaceName.c_str()).raw(),
+ Bstr(aIPV6Address).raw())))
+ return E_FAIL;
+ if (FAILED(mVirtualBox->SetExtraData(BstrFmt("HostOnly/%s/IPV6NetMask",
+ mInterfaceName.c_str()).raw(),
+ BstrFmt("%u", aIPV6NetworkMaskPrefixLength).raw())))
+#endif /* !defined(RT_OS_WINDOWS) */
+ return E_FAIL;
+ }
+
+ }
+ return S_OK;
+#endif
+}
+
+HRESULT HostNetworkInterface::enableDynamicIPConfig()
+{
+#ifndef VBOX_WITH_HOSTNETIF_API
+ return E_NOTIMPL;
+#else
+ int rc = NetIfEnableDynamicIpConfig(mVirtualBox, this);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Failed to EnableDynamicIpConfig with rc=%Rrc\n", rc));
+ return rc == VERR_NOT_IMPLEMENTED ? E_NOTIMPL : E_FAIL;
+ }
+ return S_OK;
+#endif
+}
+
+HRESULT HostNetworkInterface::dHCPRediscover()
+{
+#ifndef VBOX_WITH_HOSTNETIF_API
+ return E_NOTIMPL;
+#else
+ int rc = NetIfDhcpRediscover(mVirtualBox, this);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Failed to DhcpRediscover with rc=%Rrc\n", rc));
+ return rc == VERR_NOT_IMPLEMENTED ? E_NOTIMPL : E_FAIL;
+ }
+ return S_OK;
+#endif
+}
+
+HRESULT HostNetworkInterface::i_setVirtualBox(VirtualBox *pVirtualBox)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AssertReturn(mVirtualBox != pVirtualBox, S_OK);
+
+ unconst(mVirtualBox) = pVirtualBox;
+
+#if !defined(RT_OS_WINDOWS)
+ /* If IPv4 address hasn't been initialized */
+ if (m.IPAddress == 0 && mIfType == HostNetworkInterfaceType_HostOnly)
+ {
+ Bstr tmpAddr, tmpMask;
+ HRESULT hrc = mVirtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPAddress",
+ mInterfaceName.c_str()).raw(),
+ tmpAddr.asOutParam());
+ if (FAILED(hrc) || tmpAddr.isEmpty())
+ tmpAddr = getDefaultIPv4Address(mInterfaceName);
+
+ hrc = mVirtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPNetMask",
+ mInterfaceName.c_str()).raw(),
+ tmpMask.asOutParam());
+ if (FAILED(hrc) || tmpMask.isEmpty())
+ tmpMask = Bstr(VBOXNET_IPV4MASK_DEFAULT);
+
+ m.IPAddress = inet_addr(Utf8Str(tmpAddr).c_str());
+ m.networkMask = inet_addr(Utf8Str(tmpMask).c_str());
+ }
+
+ if (m.IPV6Address.isEmpty())
+ {
+ Bstr bstrIPV4Addr;
+ Bstr tmpPrefixLen;
+ HRESULT hrc = mVirtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPV6Address",
+ mInterfaceName.c_str()).raw(),
+ bstrIPV4Addr.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ m.IPV6Address = bstrIPV4Addr;
+ if (!m.IPV6Address.isEmpty())
+ {
+ hrc = mVirtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPV6PrefixLen",
+ mInterfaceName.c_str()).raw(),
+ tmpPrefixLen.asOutParam());
+ if (SUCCEEDED(hrc) && !tmpPrefixLen.isEmpty())
+ m.IPV6NetworkMaskPrefixLength = Utf8Str(tmpPrefixLen).toUInt32();
+ else
+ m.IPV6NetworkMaskPrefixLength = 64;
+ }
+ }
+ }
+#endif
+
+ return S_OK;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/HostOnlyNetworkImpl.cpp b/src/VBox/Main/src-server/HostOnlyNetworkImpl.cpp
new file mode 100644
index 00000000..c38f8e41
--- /dev/null
+++ b/src/VBox/Main/src-server/HostOnlyNetworkImpl.cpp
@@ -0,0 +1,287 @@
+/* $Id: HostOnlyNetworkImpl.cpp $ */
+/** @file
+ * IHostOnlyNetwork COM class implementations.
+ */
+
+/*
+ * Copyright (C) 2021-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOSTONLYNETWORK
+#include <VBox/settings.h>
+#include <iprt/cpp/utils.h>
+
+#include "VirtualBoxImpl.h"
+#include "HostOnlyNetworkImpl.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+
+struct HostOnlyNetwork::Data
+{
+ Data() : pVirtualBox(NULL) {}
+ virtual ~Data() {}
+
+ /** weak VirtualBox parent */
+ VirtualBox * const pVirtualBox;
+
+ /** HostOnlyNetwork settings */
+ settings::HostOnlyNetwork s;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// HostOnlyNetwork constructor / destructor
+//
+// ////////////////////////////////////////////////////////////////////////////////
+HostOnlyNetwork::HostOnlyNetwork() : m(NULL)
+{
+}
+
+HostOnlyNetwork::~HostOnlyNetwork()
+{
+}
+
+
+HRESULT HostOnlyNetwork::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void HostOnlyNetwork::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+HRESULT HostOnlyNetwork::init(VirtualBox *aVirtualBox, Utf8Str aName)
+{
+ // Enclose the state transition NotReady->InInit->Ready.
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+ /* share VirtualBox weakly */
+ unconst(m->pVirtualBox) = aVirtualBox;
+
+ m->s.strNetworkName = aName;
+ m->s.fEnabled = true;
+ m->s.uuid.create();
+
+ autoInitSpan.setSucceeded();
+ return S_OK;
+}
+
+void HostOnlyNetwork::uninit()
+{
+ // Enclose the state transition Ready->InUninit->NotReady.
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+}
+
+HRESULT HostOnlyNetwork::i_loadSettings(const settings::HostOnlyNetwork &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->s = data;
+
+ return S_OK;
+}
+
+HRESULT HostOnlyNetwork::i_saveSettings(settings::HostOnlyNetwork &data)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(!m->s.strNetworkName.isEmpty(), E_FAIL);
+ data = m->s;
+
+ return S_OK;
+}
+
+#if 0
+Utf8Str HostOnlyNetwork::i_getNetworkId()
+{
+ return m->s.strNetworkId;
+}
+
+Utf8Str HostOnlyNetwork::i_getNetworkName()
+{
+ return m->s.strNetworkName;
+}
+#endif
+
+
+HRESULT HostOnlyNetwork::getNetworkName(com::Utf8Str &aNetworkName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(!m->s.strNetworkName.isEmpty(), E_FAIL);
+ aNetworkName = m->s.strNetworkName;
+ return S_OK;
+}
+
+HRESULT HostOnlyNetwork::setNetworkName(const com::Utf8Str &aNetworkName)
+{
+ if (aNetworkName.isEmpty())
+ return setError(E_INVALIDARG,
+ tr("Network name cannot be empty"));
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aNetworkName == m->s.strNetworkName)
+ return S_OK;
+
+ m->s.strNetworkName = aNetworkName;
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ return S_OK;
+}
+
+HRESULT HostOnlyNetwork::getNetworkMask(com::Utf8Str &aNetworkMask)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(!m->s.strNetworkMask.isEmpty(), E_FAIL);
+ aNetworkMask = m->s.strNetworkMask;
+ return S_OK;
+}
+
+HRESULT HostOnlyNetwork::setNetworkMask(const com::Utf8Str &aNetworkMask)
+{
+ if (aNetworkMask.isEmpty())
+ return setError(E_INVALIDARG,
+ tr("Network mask cannot be empty"));
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aNetworkMask == m->s.strNetworkMask)
+ return S_OK;
+
+ m->s.strNetworkMask = aNetworkMask;
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ return S_OK;
+}
+
+HRESULT HostOnlyNetwork::getEnabled(BOOL *aEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aEnabled = m->s.fEnabled;
+ return S_OK;
+}
+
+HRESULT HostOnlyNetwork::setEnabled(BOOL aEnabled)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (RT_BOOL(aEnabled) == m->s.fEnabled)
+ return S_OK;
+ m->s.fEnabled = RT_BOOL(aEnabled);
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ return S_OK;
+}
+
+HRESULT HostOnlyNetwork::getHostIP(com::Utf8Str &aHostIP)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aHostIP = m->s.strIPLower;
+ return S_OK;
+}
+
+HRESULT HostOnlyNetwork::getLowerIP(com::Utf8Str &aLowerIP)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aLowerIP = m->s.strIPLower;
+ return S_OK;
+}
+
+HRESULT HostOnlyNetwork::setLowerIP(const com::Utf8Str &aLowerIP)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aLowerIP == m->s.strIPLower)
+ return S_OK;
+ m->s.strIPLower = aLowerIP;
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ return S_OK;
+}
+
+HRESULT HostOnlyNetwork::getUpperIP(com::Utf8Str &aUpperIP)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aUpperIP = m->s.strIPUpper;
+ return S_OK;
+}
+
+HRESULT HostOnlyNetwork::setUpperIP(const com::Utf8Str &aUpperIP)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aUpperIP == m->s.strIPUpper)
+ return S_OK;
+ m->s.strIPUpper = aUpperIP;
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ return S_OK;
+}
+
+HRESULT HostOnlyNetwork::getId(com::Guid &aId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aId = m->s.uuid;
+ return S_OK;
+}
+
+HRESULT HostOnlyNetwork::setId(const com::Guid &aId)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aId == m->s.uuid)
+ return S_OK;
+ m->s.uuid = aId;
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ return S_OK;
+}
+
diff --git a/src/VBox/Main/src-server/HostPower.cpp b/src/VBox/Main/src-server/HostPower.cpp
new file mode 100644
index 00000000..55b18dd3
--- /dev/null
+++ b/src/VBox/Main/src-server/HostPower.cpp
@@ -0,0 +1,208 @@
+/* $Id: HostPower.cpp $ */
+/** @file
+ * VirtualBox interface to host's power notification service
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_HOST
+#include "HostPower.h"
+#include "LoggingNew.h"
+
+#include <VBox/com/ptr.h>
+
+#include "VirtualBoxImpl.h"
+#include "MachineImpl.h"
+
+#include <iprt/mem.h>
+#include <iprt/cpp/utils.h>
+
+HostPowerService::HostPowerService(VirtualBox *aVirtualBox)
+{
+ AssertPtr(aVirtualBox);
+ mVirtualBox = aVirtualBox;
+}
+
+HostPowerService::~HostPowerService()
+{
+}
+
+void HostPowerService::notify(Reason_T aReason)
+{
+ SessionMachinesList machines;
+ VirtualBox::InternalControlList controls;
+
+ HRESULT rc = S_OK;
+
+ switch (aReason)
+ {
+ case Reason_HostSuspend:
+ {
+ LogFunc(("HOST SUSPEND\n"));
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ /* Suspend performance sampling to avoid unnecessary callbacks due to jumps in time. */
+ PerformanceCollector *perfcollector = mVirtualBox->i_performanceCollector();
+
+ if (perfcollector)
+ perfcollector->suspendSampling();
+#endif
+ mVirtualBox->i_getOpenedMachines(machines, &controls);
+
+ /* pause running VMs */
+ for (VirtualBox::InternalControlList::const_iterator it = controls.begin();
+ it != controls.end();
+ ++it)
+ {
+ ComPtr<IInternalSessionControl> pControl = *it;
+
+ /* PauseWithReason() will simply return a failure if
+ * the VM is in an inappropriate state */
+ rc = pControl->PauseWithReason(Reason_HostSuspend);
+ if (FAILED(rc))
+ continue;
+
+ /* save the control to un-pause the VM later */
+ mSessionControls.push_back(pControl);
+ }
+
+ LogRel(("Host suspending: Paused %d VMs\n", mSessionControls.size()));
+ break;
+ }
+
+ case Reason_HostResume:
+ {
+ LogFunc(("HOST RESUME\n"));
+
+ size_t resumed = 0;
+
+ /* go through VMs we paused on Suspend */
+ for (size_t i = 0; i < mSessionControls.size(); ++i)
+ {
+ /* note that Resume() will simply return a failure if the VM is
+ * in an inappropriate state (it will also fail if the VM has
+ * been somehow closed by this time already so that the
+ * console reference we have is dead) */
+ rc = mSessionControls[i]->ResumeWithReason(Reason_HostResume);
+ if (FAILED(rc))
+ continue;
+
+ ++resumed;
+ }
+
+ LogRel(("Host resumed: Resumed %d VMs\n", resumed));
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ /* Resume the performance sampling. */
+ PerformanceCollector *perfcollector = mVirtualBox->i_performanceCollector();
+
+ if (perfcollector)
+ perfcollector->resumeSampling();
+#endif
+
+ mSessionControls.clear();
+ break;
+ }
+
+ case Reason_HostBatteryLow:
+ {
+ LogFunc(("BATTERY LOW\n"));
+
+ Bstr value;
+ rc = mVirtualBox->GetExtraData(Bstr("VBoxInternal2/SavestateOnBatteryLow").raw(),
+ value.asOutParam());
+ int fGlobal = 0;
+ if (SUCCEEDED(rc) && !value.isEmpty())
+ {
+ if (value != "0")
+ fGlobal = 1;
+ else if (value == "0")
+ fGlobal = -1;
+ }
+
+ mVirtualBox->i_getOpenedMachines(machines, &controls);
+ size_t saved = 0;
+
+ /* save running VMs */
+ for (SessionMachinesList::const_iterator it = machines.begin();
+ it != machines.end();
+ ++it)
+ {
+ ComPtr<SessionMachine> pMachine = *it;
+ rc = pMachine->GetExtraData(Bstr("VBoxInternal2/SavestateOnBatteryLow").raw(),
+ value.asOutParam());
+ int fPerVM = 0;
+ if (SUCCEEDED(rc) && !value.isEmpty())
+ {
+ /* per-VM overrides global */
+ if (value != "0")
+ fPerVM = 2;
+ else if (value == "0")
+ fPerVM = -2;
+ }
+
+ /* default is true */
+ if (fGlobal + fPerVM >= 0)
+ {
+ ComPtr<IProgress> progress;
+
+ /* SessionMachine::i_saveStateWithReason() will return
+ * a failure if the VM is in an inappropriate state */
+ rc = pMachine->i_saveStateWithReason(Reason_HostBatteryLow, progress);
+ if (FAILED(rc))
+ {
+ LogRel(("SaveState '%s' failed with %Rhrc\n", pMachine->i_getName().c_str(), rc));
+ continue;
+ }
+
+ /* Wait until the operation has been completed. */
+ rc = progress->WaitForCompletion(-1);
+ if (SUCCEEDED(rc))
+ {
+ LONG iRc;
+ progress->COMGETTER(ResultCode)(&iRc);
+ rc = (HRESULT)iRc;
+ }
+
+ AssertMsg(SUCCEEDED(rc), ("SaveState WaitForCompletion failed with %Rhrc (%#08X)\n", rc, rc));
+
+ if (SUCCEEDED(rc))
+ {
+ LogRel(("SaveState '%s' succeeded\n", pMachine->i_getName().c_str()));
+ ++saved;
+ }
+ }
+ }
+ LogRel(("Battery Low: saved %d VMs\n", saved));
+ break;
+ }
+
+ default:
+ /* nothing */;
+ }
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/HostUSBDeviceImpl.cpp b/src/VBox/Main/src-server/HostUSBDeviceImpl.cpp
new file mode 100644
index 00000000..addc6715
--- /dev/null
+++ b/src/VBox/Main/src-server/HostUSBDeviceImpl.cpp
@@ -0,0 +1,2596 @@
+/* $Id: HostUSBDeviceImpl.cpp $ */
+/** @file
+ * VirtualBox IHostUSBDevice COM interface implementation.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOSTUSBDEVICE
+#include <iprt/types.h> /* for UINT64_C */
+
+#include "HostUSBDeviceImpl.h"
+#include "MachineImpl.h"
+#include "HostImpl.h"
+#include "VirtualBoxErrorInfoImpl.h"
+#include "USBProxyBackend.h"
+#include "USBIdDatabase.h"
+#include "LoggingNew.h"
+
+#include "AutoCaller.h"
+
+#include <VBox/err.h>
+#include <iprt/cpp/utils.h>
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(HostUSBDevice)
+
+HRESULT HostUSBDevice::FinalConstruct()
+{
+ mUSBProxyBackend = NULL;
+ mUsb = NULL;
+
+ return BaseFinalConstruct();
+}
+
+void HostUSBDevice::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the USB device object.
+ *
+ * @returns COM result indicator
+ * @param aUsb Pointer to the usb device structure for which the object is to be a wrapper.
+ * This structure is now fully owned by the HostUSBDevice object and will be
+ * freed when it is destructed.
+ * @param aUSBProxyBackend Pointer to the USB Proxy Backend object owning the device.
+ */
+HRESULT HostUSBDevice::init(PUSBDEVICE aUsb, USBProxyBackend *aUSBProxyBackend)
+{
+ ComAssertRet(aUsb, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /*
+ * We need a unique ID for this VBoxSVC session.
+ * The UUID isn't stored anywhere.
+ */
+ unconst(mId).create();
+
+ /*
+ * Set the initial device state.
+ */
+ AssertMsgReturn( aUsb->enmState >= USBDEVICESTATE_UNSUPPORTED
+ && aUsb->enmState < USBDEVICESTATE_USED_BY_GUEST, /* used-by-guest is not a legal initial state. */
+ ("%d\n", aUsb->enmState), E_FAIL);
+ mUniState = (HostUSBDeviceState)aUsb->enmState;
+ mUniSubState = kHostUSBDeviceSubState_Default;
+ mPendingUniState = kHostUSBDeviceState_Invalid;
+ mPrevUniState = mUniState;
+ mIsPhysicallyDetached = false;
+
+ /* Other data members */
+ mUSBProxyBackend = aUSBProxyBackend;
+ mUsb = aUsb;
+
+ /* Set the name. */
+ mNameObj = i_getName();
+ mName = mNameObj.c_str();
+
+ /* Confirm the successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void HostUSBDevice::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ if (mUsb != NULL)
+ {
+ USBProxyBackend::freeDevice(mUsb);
+ mUsb = NULL;
+ }
+
+ mUSBProxyBackend = NULL;
+ mUniState = kHostUSBDeviceState_Invalid;
+}
+
+// Wrapped IUSBDevice properties
+/////////////////////////////////////////////////////////////////////////////
+HRESULT HostUSBDevice::getId(com::Guid &aId)
+{
+ /* mId is constant during life time, no need to lock */
+ aId = mId;
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDevice::getVendorId(USHORT *aVendorId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aVendorId = mUsb->idVendor;
+
+ return S_OK;
+}
+
+HRESULT HostUSBDevice::getProductId(USHORT *aProductId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aProductId = mUsb->idProduct;
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDevice::getRevision(USHORT *aRevision)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aRevision = mUsb->bcdDevice;
+
+ return S_OK;
+}
+
+HRESULT HostUSBDevice::getManufacturer(com::Utf8Str &aManufacturer)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aManufacturer = mUsb->pszManufacturer;
+ return S_OK;
+}
+
+
+HRESULT HostUSBDevice::getProduct(com::Utf8Str &aProduct)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aProduct = mUsb->pszProduct;
+ return S_OK;
+}
+
+
+HRESULT HostUSBDevice::getSerialNumber(com::Utf8Str &aSerialNumber)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aSerialNumber = mUsb->pszSerialNumber;
+
+ return S_OK;
+}
+
+HRESULT HostUSBDevice::getAddress(com::Utf8Str &aAddress)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aAddress = mUsb->pszAddress;
+ return S_OK;
+}
+
+
+HRESULT HostUSBDevice::getPort(USHORT *aPort)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aPort = mUsb->bPort;
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDevice::getPortPath(com::Utf8Str &aPortPath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aPortPath = mUsb->pszPortPath;
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDevice::getVersion(USHORT *aVersion)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aVersion = (USHORT)(mUsb->bcdUSB >> 8);
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDevice::getSpeed(USBConnectionSpeed_T *aSpeed)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* If the speed is unknown (which it shouldn't be), make a guess
+ * which will be correct for USB 1 and 3 devices, but may be wrong
+ * for USB 2.0 devices
+ */
+ switch (mUsb->enmSpeed)
+ {
+ case USBDEVICESPEED_LOW: *aSpeed = USBConnectionSpeed_Low; break;
+ case USBDEVICESPEED_FULL: *aSpeed = USBConnectionSpeed_Full; break;
+ case USBDEVICESPEED_HIGH: *aSpeed = USBConnectionSpeed_High; break;
+ case USBDEVICESPEED_SUPER: *aSpeed = USBConnectionSpeed_Super; break;
+// case USBDEVICESPEED_SUPERPLUS: *aSpeed = USBConnectionSpeed_SuperPlus; break;
+ default:
+ switch (mUsb->bcdUSB >> 8)
+ {
+ case 3: *aSpeed = USBConnectionSpeed_Super; break;
+ case 2: *aSpeed = USBConnectionSpeed_High; break;
+ default: *aSpeed = USBConnectionSpeed_Full;
+ }
+ }
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDevice::getPortVersion(USHORT *aPortVersion)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ /* Port version is 2 (EHCI) if and only if the device runs at high speed;
+ * if speed is unknown, fall back to the old and inaccurate method.
+ */
+ if (mUsb->enmSpeed == USBDEVICESPEED_UNKNOWN)
+ *aPortVersion = (USHORT)(mUsb->bcdUSB >> 8);
+ else
+ {
+ switch (mUsb->enmSpeed)
+ {
+ case USBDEVICESPEED_SUPER:
+ *aPortVersion = 3;
+ break;
+ case USBDEVICESPEED_HIGH:
+ *aPortVersion = 2;
+ break;
+ case USBDEVICESPEED_FULL:
+ case USBDEVICESPEED_LOW:
+ case USBDEVICESPEED_VARIABLE:
+ *aPortVersion = 1;
+ break;
+ default:
+ AssertMsgFailed(("Invalid USB speed: %d\n", mUsb->enmSpeed));
+ *aPortVersion = 1;
+ }
+ }
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDevice::getRemote(BOOL *aRemote)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aRemote = FALSE;
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDevice::getState(USBDeviceState_T *aState)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aState = i_canonicalState();
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDevice::getBackend(com::Utf8Str &aBackend)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aBackend = mUsb->pszBackend;
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDevice::getDeviceInfo(std::vector<com::Utf8Str> &aInfo)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ com::Utf8Str strManufacturer;
+ com::Utf8Str strProduct;
+
+ if (mUsb->pszManufacturer && *mUsb->pszManufacturer)
+ strManufacturer = mUsb->pszManufacturer;
+ else
+ strManufacturer = USBIdDatabase::findVendor(mUsb->idVendor);
+
+ if (mUsb->pszProduct && *mUsb->pszProduct)
+ strProduct = mUsb->pszProduct;
+ else
+ strProduct = USBIdDatabase::findProduct(mUsb->idVendor, mUsb->idProduct);
+
+ aInfo.resize(2);
+ aInfo[0] = strManufacturer;
+ aInfo[1] = strProduct;
+
+ return S_OK;
+}
+
+// public methods only for internal purposes
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * @note Locks this object for reading.
+ */
+com::Utf8Str HostUSBDevice::i_getName()
+{
+ Utf8Str name;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), name);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ bool haveManufacturer = mUsb->pszManufacturer && *mUsb->pszManufacturer;
+ bool haveProduct = mUsb->pszProduct && *mUsb->pszProduct;
+ if (haveManufacturer && haveProduct)
+ name = Utf8StrFmt("%s %s", mUsb->pszManufacturer, mUsb->pszProduct);
+ else
+ {
+ Utf8Str strProduct;
+ Utf8Str strVendor = USBIdDatabase::findVendorAndProduct(mUsb->idVendor, mUsb->idProduct, &strProduct);
+ if ( (strVendor.isNotEmpty() || haveManufacturer)
+ && (strProduct.isNotEmpty() || haveProduct))
+ name = Utf8StrFmt("%s %s", haveManufacturer ? mUsb->pszManufacturer
+ : strVendor.c_str(),
+ haveProduct ? mUsb->pszProduct
+ : strProduct.c_str());
+ else
+ {
+ LogRel(("USB: Unknown USB device detected (idVendor: 0x%04x, idProduct: 0x%04x)\n",
+ mUsb->idVendor, mUsb->idProduct));
+ if (strVendor.isNotEmpty())
+ name = strVendor;
+ else
+ {
+ Assert(strProduct.isEmpty());
+ name = "<unknown>";
+ }
+ }
+ }
+
+ return name;
+}
+
+/**
+ * Requests the USB proxy service capture the device (from the host)
+ * and attach it to a VM.
+ *
+ * As a convenience, this method will operate like attachToVM() if the device
+ * is already held by the proxy. Note that it will then perform IPC to the VM
+ * process, which means it will temporarily release all locks. (Is this a good idea?)
+ *
+ * @param aMachine Machine this device should be attach to.
+ * @param aSetError Whether to set full error message or not to bother.
+ * @param aCaptureFilename The filename to capture the USB traffic to.
+ * @param aMaskedIfs The interfaces to hide from the guest.
+ *
+ * @returns Status indicating whether it was successfully captured and/or attached.
+ * @retval S_OK on success.
+ * @retval E_UNEXPECTED if the device state doesn't permit for any attaching.
+ * @retval E_* as appropriate.
+ */
+HRESULT HostUSBDevice::i_requestCaptureForVM(SessionMachine *aMachine, bool aSetError,
+ const com::Utf8Str &aCaptureFilename, ULONG aMaskedIfs /* = 0*/)
+{
+ /*
+ * Validate preconditions and input.
+ */
+ AssertReturn(aMachine, E_INVALIDARG);
+ AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL);
+ AssertReturn(!aMachine->isWriteLockOnCurrentThread(), E_FAIL);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("{%s} aMachine=%p aMaskedIfs=%#x\n", mName, aMachine, aMaskedIfs));
+
+ if (aSetError)
+ {
+ if (mUniState == kHostUSBDeviceState_Unsupported)
+ return setError(E_INVALIDARG,
+ tr("USB device '%s' with UUID {%RTuuid} cannot be accessed by guest computers"),
+ mName, mId.raw());
+ if (mUniState == kHostUSBDeviceState_UsedByHost)
+ return setError(E_INVALIDARG,
+ tr("USB device '%s' with UUID {%RTuuid} is being exclusively used by the host computer"),
+ mName, mId.raw());
+ if (mUniState == kHostUSBDeviceState_UsedByVM)
+ {
+ /* Machine::name() requires a read lock */
+ alock.release();
+ AutoReadLock machLock(mMachine COMMA_LOCKVAL_SRC_POS);
+ return setError(E_INVALIDARG,
+ tr("USB device '%s' with UUID {%RTuuid} is already captured by the virtual machine '%s'"),
+ mName, mId.raw(), mMachine->i_getName().c_str());
+ }
+ if (mUniState >= kHostUSBDeviceState_FirstTransitional)
+ return setError(E_INVALIDARG,
+ tr("USB device '%s' with UUID {%RTuuid} is busy with a previous request. Please try again later"),
+ mName, mId.raw());
+ if ( mUniState != kHostUSBDeviceState_Unused
+ && mUniState != kHostUSBDeviceState_HeldByProxy
+ && mUniState != kHostUSBDeviceState_Capturable)
+ return setError(E_INVALIDARG,
+ tr("USB device '%s' with UUID {%RTuuid} is not in the right state for capturing (%s)"),
+ mName, mId.raw(), i_getStateName());
+ }
+
+ AssertReturn( mUniState == kHostUSBDeviceState_HeldByProxy
+ || mUniState == kHostUSBDeviceState_Unused
+ || mUniState == kHostUSBDeviceState_Capturable,
+ E_UNEXPECTED);
+ Assert(mMachine.isNull());
+
+ /*
+ * If it's already held by the proxy, we'll simply call
+ * attachToVM synchronously.
+ */
+ if (mUniState == kHostUSBDeviceState_HeldByProxy)
+ {
+ alock.release();
+ HRESULT hrc = i_attachToVM(aMachine, aCaptureFilename, aMaskedIfs);
+ return hrc;
+ }
+
+ /*
+ * Need to capture the device before it can be used.
+ *
+ * The device will be attached to the VM by the USB proxy service thread
+ * when the request succeeds (i.e. asynchronously).
+ */
+ LogFlowThisFunc(("{%s} capturing the device.\n", mName));
+ if (mUSBProxyBackend->i_isDevReEnumerationRequired())
+ i_setState(kHostUSBDeviceState_Capturing, kHostUSBDeviceState_UsedByVM, kHostUSBDeviceSubState_AwaitingDetach);
+ else
+ i_setState(kHostUSBDeviceState_Capturing, kHostUSBDeviceState_UsedByVM);
+
+ mMachine = aMachine;
+ mMaskedIfs = aMaskedIfs;
+ mCaptureFilename = aCaptureFilename;
+ alock.release();
+ int vrc = mUSBProxyBackend->captureDevice(this);
+ if (RT_FAILURE(vrc))
+ {
+ alock.acquire();
+ i_failTransition(kHostUSBDeviceState_Invalid);
+ mMachine.setNull();
+ if (vrc == VERR_SHARING_VIOLATION)
+ return setErrorBoth(E_FAIL, vrc,
+ tr("USB device '%s' with UUID {%RTuuid} is in use by someone else"),
+ mName, mId.raw());
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+/**
+ * Attempts to attach the USB device to a VM.
+ *
+ * The device must be in the HeldByProxy state or about to exit the
+ * Capturing state.
+ *
+ * This method will make an IPC to the VM process and do the actual
+ * attaching. While in the IPC locks will be abandond.
+ *
+ * @returns Status indicating whether it was successfully attached or not.
+ * @retval S_OK on success.
+ * @retval E_UNEXPECTED if the device state doesn't permit for any attaching.
+ * @retval E_* as appropriate.
+ *
+ * @param aMachine Machine this device should be attach to.
+ * @param aCaptureFilename Filename to capture the USB traffic to.
+ * @param aMaskedIfs The interfaces to hide from the guest.
+ */
+HRESULT HostUSBDevice::i_attachToVM(SessionMachine *aMachine, const com::Utf8Str &aCaptureFilename,
+ ULONG aMaskedIfs /* = 0*/)
+{
+ AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ /*
+ * Validate and update the state.
+ */
+ AssertReturn( mUniState == kHostUSBDeviceState_Capturing
+ || mUniState == kHostUSBDeviceState_HeldByProxy
+ || mUniState == kHostUSBDeviceState_AttachingToVM,
+ E_UNEXPECTED);
+ i_setState(kHostUSBDeviceState_AttachingToVM, kHostUSBDeviceState_UsedByVM);
+
+ /*
+ * The VM process will query the object, so grab a reference to ourselves and release the locks.
+ */
+ ComPtr<IUSBDevice> d = this;
+
+ /*
+ * Call the VM process (IPC) and request it to attach the device.
+ *
+ * There are many reasons for this to fail, so, as a consequence we don't
+ * assert the return code as it will crash the daemon and annoy the heck
+ * out of people.
+ */
+ LogFlowThisFunc(("{%s} Calling machine->onUSBDeviceAttach()...\n", mName));
+ alock.release();
+ HRESULT hrc = aMachine->i_onUSBDeviceAttach(d, NULL, aMaskedIfs, aCaptureFilename);
+ LogFlowThisFunc(("{%s} Done machine->onUSBDeviceAttach()=%08X\n", mName, hrc));
+
+ /*
+ * As we re-acquire the lock, we'll have to check if the device was
+ * physically detached while we were busy.
+ */
+ alock.acquire();
+
+ if (SUCCEEDED(hrc))
+ {
+ mMachine = aMachine;
+ if (!mIsPhysicallyDetached)
+ i_setState(kHostUSBDeviceState_UsedByVM);
+ else
+ {
+ alock.release();
+ i_detachFromVM(kHostUSBDeviceState_PhysDetached);
+ hrc = E_UNEXPECTED;
+ }
+ }
+ else
+ {
+ mMachine.setNull();
+ if (!mIsPhysicallyDetached)
+ {
+ i_setState(kHostUSBDeviceState_HeldByProxy);
+ if (hrc == E_UNEXPECTED)
+ hrc = E_FAIL; /* No confusion. */
+ }
+ else
+ {
+ alock.release();
+ i_onPhysicalDetachedInternal();
+ hrc = E_UNEXPECTED;
+ }
+ }
+ return hrc;
+}
+
+
+/**
+ * Detaches the device from the VM.
+ *
+ * This is used for a special scenario in attachToVM() and from
+ * onPhysicalDetachedInternal().
+ *
+ * @param aFinalState The final state (PhysDetached).
+ */
+void HostUSBDevice::i_detachFromVM(HostUSBDeviceState aFinalState)
+{
+ NOREF(aFinalState);
+
+ /*
+ * Assert preconditions.
+ */
+ Assert(aFinalState == kHostUSBDeviceState_PhysDetached);
+ AssertReturnVoid(!isWriteLockOnCurrentThread());
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Assert( mUniState == kHostUSBDeviceState_AttachingToVM
+ || mUniState == kHostUSBDeviceState_UsedByVM);
+ Assert(!mMachine.isNull());
+
+ /*
+ * Change the state and abandon the locks. The VM may query
+ * data and we don't want to deadlock - the state protects us,
+ * so, it's not a bit issue here.
+ */
+ i_setState(kHostUSBDeviceState_PhysDetachingFromVM, kHostUSBDeviceState_PhysDetached);
+
+ /*
+ * Call the VM process (IPC) and request it to detach the device.
+ *
+ * There are many reasons for this to fail, so, as a consequence we don't
+ * assert the return code as it will crash the daemon and annoy the heck
+ * out of people.
+ */
+ alock.release();
+ LogFlowThisFunc(("{%s} Calling machine->onUSBDeviceDetach()...\n", mName));
+ HRESULT hrc = mMachine->i_onUSBDeviceDetach(mId.toUtf16().raw(), NULL);
+ LogFlowThisFunc(("{%s} Done machine->onUSBDeviceDetach()=%Rhrc\n", mName, hrc));
+ NOREF(hrc);
+
+ /*
+ * Re-acquire the locks and complete the transition.
+ */
+ alock.acquire();
+ i_advanceTransition();
+}
+
+/**
+ * Called when the VM process to inform us about the device being
+ * detached from it.
+ *
+ * This is NOT called when we detach the device via onUSBDeviceDetach.
+ *
+ *
+ * @param[in] aMachine The machine making the request.
+ * This must be the machine this device is currently attached to.
+ * @param[in] aDone When set to false, the VM just informs us that it's about
+ * to detach this device but hasn't done it just yet.
+ * When set to true, the VM informs us that it has completed
+ * the detaching of this device.
+ * @param[out] aRunFilters Whether to run filters.
+ * @param[in] aAbnormal Set if we're cleaning up after a crashed VM.
+ *
+ * @returns S_OK on success, and E_UNEXPECTED if the device isn't in the right state.
+ *
+ * @note Must be called from under the object write lock.
+ */
+HRESULT HostUSBDevice::i_onDetachFromVM(SessionMachine *aMachine, bool aDone, bool *aRunFilters, bool aAbnormal /*= true*/)
+{
+ LogFlowThisFunc(("{%s} state=%s aDone=%RTbool aAbnormal=%RTbool\n", mName, i_getStateName(), aDone, aAbnormal));
+
+ /*
+ * Validate preconditions.
+ */
+ AssertPtrReturn(aRunFilters, E_INVALIDARG);
+ AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
+ if (!aDone)
+ {
+ if (mUniState != kHostUSBDeviceState_UsedByVM)
+ return setError(E_INVALIDARG,
+ tr("USB device '%s' with UUID {%RTuuid} is busy (state '%s'). Please try again later"),
+ mName, mId.raw(), i_getStateName());
+ }
+ else
+ AssertMsgReturn( mUniState == kHostUSBDeviceState_DetachingFromVM /** @todo capturing for VM
+ ends up here on termination. */
+ || (mUniState == kHostUSBDeviceState_UsedByVM && aAbnormal),
+ ("{%s} %s\n", mName, i_getStateName()), E_UNEXPECTED);
+ AssertMsgReturn((mMachine == aMachine), ("%p != %p\n", (void *)mMachine, aMachine), E_FAIL);
+
+ /*
+ * Change the state.
+ */
+ if (!aDone)
+ {
+ *aRunFilters = i_startTransition(kHostUSBDeviceState_DetachingFromVM, kHostUSBDeviceState_HeldByProxy);
+ /* PORTME: This might require host specific changes if you re-enumerate the device. */
+ }
+ else if (aAbnormal && mUniState == kHostUSBDeviceState_UsedByVM)
+ {
+ /* Fast forward thru the DetachingFromVM state and on to HeldByProxy. */
+ /** @todo need to update the state machine to handle crashed VMs. */
+ i_startTransition(kHostUSBDeviceState_DetachingFromVM, kHostUSBDeviceState_HeldByProxy);
+ *aRunFilters = i_advanceTransition();
+ mMachine.setNull();
+ /* PORTME: ditto / trouble if you depend on the VM process to do anything. */
+ }
+ else
+ {
+ /* normal completion. */
+ Assert(mUniSubState == kHostUSBDeviceSubState_Default); /* PORTME: ditto */
+ *aRunFilters = i_advanceTransition();
+ mMachine.setNull();
+ }
+
+ return S_OK;
+}
+
+/**
+ * Requests the USB proxy service to release the device back to the host.
+ *
+ * This method will ignore (not assert) calls for devices that already
+ * belong to the host because it simplifies the usage a bit.
+ *
+ * @returns COM status code.
+ * @retval S_OK on success.
+ * @retval E_UNEXPECTED on bad state.
+ * @retval E_* as appropriate.
+ *
+ * @note Must be called without holding the object lock.
+ */
+HRESULT HostUSBDevice::i_requestReleaseToHost()
+{
+ /*
+ * Validate preconditions.
+ */
+ AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL);
+ Assert(mMachine.isNull());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("{%s}\n", mName));
+ if ( mUniState == kHostUSBDeviceState_Unused
+ || mUniState == kHostUSBDeviceState_Capturable)
+ return S_OK;
+ AssertMsgReturn(mUniState == kHostUSBDeviceState_HeldByProxy, ("{%s} %s\n", mName, i_getStateName()), E_UNEXPECTED);
+
+ /*
+ * Try release it.
+ */
+ if (mUSBProxyBackend->i_isDevReEnumerationRequired())
+ i_startTransition(kHostUSBDeviceState_ReleasingToHost, kHostUSBDeviceState_Unused, kHostUSBDeviceSubState_AwaitingDetach);
+ else
+ i_startTransition(kHostUSBDeviceState_ReleasingToHost, kHostUSBDeviceState_Unused);
+
+ alock.release();
+ int rc = mUSBProxyBackend->releaseDevice(this);
+ if (RT_FAILURE(rc))
+ {
+ alock.acquire();
+ i_failTransition(kHostUSBDeviceState_Invalid);
+ return E_FAIL;
+ }
+ return S_OK;
+}
+
+/**
+ * Requests the USB proxy service to capture and hold the device.
+ *
+ * The device must be owned by the host at the time of the call. But for
+ * the callers convenience, calling this method on a device that is already
+ * being held will success without any assertions.
+ *
+ * @returns COM status code.
+ * @retval S_OK on success.
+ * @retval E_UNEXPECTED on bad state.
+ * @retval E_* as appropriate.
+ *
+ * @note Must be called without holding the object lock.
+ */
+HRESULT HostUSBDevice::i_requestHold()
+{
+ /*
+ * Validate preconditions.
+ */
+ AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("{%s}\n", mName));
+ AssertMsgReturn( mUniState == kHostUSBDeviceState_Unused
+ || mUniState == kHostUSBDeviceState_Capturable
+ || mUniState == kHostUSBDeviceState_HeldByProxy,
+ ("{%s} %s\n", mName, i_getStateName()),
+ E_UNEXPECTED);
+
+ Assert(mMachine.isNull());
+ mMachine.setNull();
+
+ if (mUniState == kHostUSBDeviceState_HeldByProxy)
+ return S_OK;
+
+ /*
+ * Do the job.
+ */
+ if (mUSBProxyBackend->i_isDevReEnumerationRequired())
+ i_startTransition(kHostUSBDeviceState_Capturing, kHostUSBDeviceState_HeldByProxy, kHostUSBDeviceSubState_AwaitingDetach);
+ else
+ i_startTransition(kHostUSBDeviceState_Capturing, kHostUSBDeviceState_HeldByProxy);
+
+ alock.release();
+ int rc = mUSBProxyBackend->captureDevice(this);
+ if (RT_FAILURE(rc))
+ {
+ alock.acquire();
+ i_failTransition(kHostUSBDeviceState_Invalid);
+ return E_FAIL;
+ }
+ return S_OK;
+}
+
+
+/**
+ * Check a detach detected by the USB Proxy Service to see if
+ * it's a real one or just a logical following a re-enumeration.
+ *
+ * This will work the internal sub state of the device and do time
+ * outs, so it does more than just querying data!
+ *
+ * @returns true if it was actually detached, false if it's just a re-enumeration.
+ */
+bool HostUSBDevice::i_wasActuallyDetached()
+{
+ /*
+ * This only applies to the detach and re-attach states.
+ */
+ switch (mUniState)
+ {
+ case kHostUSBDeviceState_Capturing:
+ case kHostUSBDeviceState_ReleasingToHost:
+ case kHostUSBDeviceState_AttachingToVM:
+ case kHostUSBDeviceState_DetachingFromVM:
+ switch (mUniSubState)
+ {
+ /*
+ * If we're awaiting a detach, the this has now occurred
+ * and the state should be advanced.
+ */
+ case kHostUSBDeviceSubState_AwaitingDetach:
+ i_advanceTransition();
+ return false; /* not physically detached. */
+
+ /*
+ * Check for timeouts.
+ */
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ {
+#ifndef RT_OS_WINDOWS /* check the implementation details here. */
+ uint64_t elapsedNanoseconds = RTTimeNanoTS() - mLastStateChangeTS;
+ if (elapsedNanoseconds > UINT64_C(60000000000)) /* 60 seconds */
+ {
+ LogRel(("USB: Async operation timed out for device %s (state: %s)\n", mName, i_getStateName()));
+ i_failTransition(kHostUSBDeviceState_PhysDetached);
+ }
+#endif
+ return false; /* not physically detached. */
+ }
+
+ /* not applicable.*/
+ case kHostUSBDeviceSubState_Default:
+ break;
+ }
+ break;
+
+ /* not applicable. */
+ case kHostUSBDeviceState_Unsupported:
+ case kHostUSBDeviceState_UsedByHost:
+ case kHostUSBDeviceState_Capturable:
+ case kHostUSBDeviceState_Unused:
+ case kHostUSBDeviceState_HeldByProxy:
+ case kHostUSBDeviceState_UsedByVM:
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ case kHostUSBDeviceState_PhysDetached:
+ break;
+
+ default:
+ AssertLogRelMsgFailed(("this=%p %s\n", this, i_getStateName()));
+ break;
+ }
+
+ /* It was detached. */
+ return true;
+}
+
+/**
+ * Notification from the USB Proxy that the device was physically detached.
+ *
+ * If a transition is pending, mIsPhysicallyDetached will be set and
+ * handled when the transition advances forward.
+ *
+ * Otherwise the device will be detached from any VM currently using it - this
+ * involves IPC and will temporarily abandon locks - and all the device data
+ * reset.
+ */
+void HostUSBDevice::i_onPhysicalDetached()
+{
+ AssertReturnVoid(!isWriteLockOnCurrentThread());
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("{%s}\n", mName));
+
+ mIsPhysicallyDetached = true;
+ if (mUniState < kHostUSBDeviceState_FirstTransitional)
+ {
+ alock.release();
+ i_onPhysicalDetachedInternal();
+ }
+}
+
+
+/**
+ * Do the physical detach work for a device in a stable state or
+ * at a transition state change.
+ *
+ * See onPhysicalDetach() for details.
+ */
+void HostUSBDevice::i_onPhysicalDetachedInternal()
+{
+ AssertReturnVoid(!isWriteLockOnCurrentThread());
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("{%s}\n", mName));
+ Assert(mIsPhysicallyDetached);
+
+ /*
+ * Do we need to detach it from the VM first?
+ */
+ if ( !mMachine.isNull()
+ && ( mUniState == kHostUSBDeviceState_UsedByVM
+ || mUniState == kHostUSBDeviceState_AttachingToVM))
+ {
+ alock.release();
+ i_detachFromVM(kHostUSBDeviceState_PhysDetached);
+ alock.acquire();
+ }
+ else
+ AssertMsg(mMachine.isNull(), ("%s\n", i_getStateName()));
+
+ /*
+ * Reset the data and enter the final state.
+ */
+ mMachine.setNull();
+ i_setState(kHostUSBDeviceState_PhysDetached);
+}
+
+
+/**
+ * Returns true if this device matches the given filter data.
+ *
+ * @note It is assumed, that the filter data owner is appropriately
+ * locked before calling this method.
+ *
+ * @note
+ * This method MUST correlate with
+ * USBController::hasMatchingFilter (IUSBDevice *)
+ * in the sense of the device matching logic.
+ *
+ * @note Locks this object for reading.
+ */
+bool HostUSBDevice::i_isMatch(const USBDeviceFilter::BackupableUSBDeviceFilterData &aData)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), false);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!aData.mData.fActive)
+ return false;
+
+ if (!aData.mRemote.isMatch(FALSE))
+ return false;
+
+ if (!USBFilterMatchDevice(&aData.mUSBFilter, mUsb))
+ return false;
+
+ /* Don't match busy devices with a 100% wildcard filter - this will
+ later become a filter prop (ring-3 only). */
+ if ( mUsb->enmState == USBDEVICESTATE_USED_BY_HOST_CAPTURABLE
+ && !USBFilterHasAnySubstatialCriteria(&aData.mUSBFilter))
+ return false;
+
+ LogFlowThisFunc(("returns true\n"));
+ return true;
+}
+
+/**
+ * Compares this device with a USBDEVICE and decides if the match or which comes first.
+ *
+ * This will take into account device re-attaching and omit the bits
+ * that may change during a device re-enumeration.
+ *
+ * @param aDev2 Device 2.
+ *
+ * @returns < 0 if this should come before aDev2.
+ * @returns 0 if this and aDev2 are equal.
+ * @returns > 0 if this should come after aDev2.
+ *
+ * @note Must be called from under the object write lock.
+ */
+int HostUSBDevice::i_compare(PCUSBDEVICE aDev2)
+{
+ AssertReturn(isWriteLockOnCurrentThread(), -1);
+ //Log3(("%Rfn: %p {%s}\n", __PRETTY_FUNCTION__, this, mName));
+ return i_compare(mUsb, aDev2,
+ mUniSubState == kHostUSBDeviceSubState_AwaitingDetach /* (In case we don't get the detach notice.) */
+ || mUniSubState == kHostUSBDeviceSubState_AwaitingReAttach);
+}
+
+/**
+ * Compares two USBDEVICE structures and decides if the match or which comes first.
+ *
+ * @param aDev1 Device 1.
+ * @param aDev2 Device 2.
+ * @param aIsAwaitingReAttach Whether to omit bits that will change in a device
+ * re-enumeration (true) or not (false).
+ *
+ * @returns < 0 if aDev1 should come before aDev2.
+ * @returns 0 if aDev1 and aDev2 are equal.
+ * @returns > 0 if aDev1 should come after aDev2.
+ */
+/*static*/
+int HostUSBDevice::i_compare(PCUSBDEVICE aDev1, PCUSBDEVICE aDev2, bool aIsAwaitingReAttach /*= false */)
+{
+ /* Comparing devices from different backends doesn't make any sense and should not happen. */
+ AssertReturn(!strcmp(aDev1->pszBackend, aDev2->pszBackend), -1);
+
+ /*
+ * Things that stays the same everywhere.
+ *
+ * The more uniquely these properties identifies a device the less the chance
+ * that we mix similar devices during re-enumeration. Bus+port would help
+ * provide ~99.8% accuracy if the host can provide those attributes.
+ */
+ int iDiff = aDev1->idVendor - aDev2->idVendor;
+ if (iDiff)
+ return iDiff;
+
+ iDiff = aDev1->idProduct - aDev2->idProduct;
+ if (iDiff)
+ return iDiff;
+
+ iDiff = aDev1->bcdDevice - aDev2->bcdDevice;
+ if (iDiff)
+ {
+ //Log3(("compare: bcdDevice: %#x != %#x\n", aDev1->bcdDevice, aDev2->bcdDevice));
+ return iDiff;
+ }
+
+#ifdef RT_OS_WINDOWS /* the string query may fail on windows during replugging, ignore serial mismatch if this is the case. */
+ if ( aDev1->u64SerialHash != aDev2->u64SerialHash
+ && ( !aIsAwaitingReAttach
+ || (aDev2->pszSerialNumber && *aDev2->pszSerialNumber)
+ || (aDev2->pszManufacturer && *aDev2->pszManufacturer)
+ || (aDev2->pszProduct && *aDev2->pszProduct))
+ )
+#else
+ if (aDev1->u64SerialHash != aDev2->u64SerialHash)
+#endif
+ {
+ //Log3(("compare: u64SerialHash: %#llx != %#llx\n", aDev1->u64SerialHash, aDev2->u64SerialHash));
+ return aDev1->u64SerialHash < aDev2->u64SerialHash ? -1 : 1;
+ }
+
+ /* The hub/bus + port should help a lot in a re-attach situation. */
+#ifdef RT_OS_WINDOWS
+ /* The hub name makes only sense for the host backend. */
+ if ( !strcmp(aDev1->pszBackend, "host")
+ && aDev1->pszHubName
+ && aDev2->pszHubName)
+ {
+ iDiff = strcmp(aDev1->pszHubName, aDev2->pszHubName);
+ if (iDiff)
+ {
+ //Log3(("compare: HubName: %s != %s\n", aDev1->pszHubName, aDev2->pszHubName));
+ return iDiff;
+ }
+ }
+#else
+ iDiff = aDev1->bBus - aDev2->bBus;
+ if (iDiff)
+ {
+ //Log3(("compare: bBus: %#x != %#x\n", aDev1->bBus, aDev2->bBus));
+ return iDiff;
+ }
+#endif
+
+ iDiff = aDev1->bPort - aDev2->bPort; /* shouldn't change anywhere and help pinpoint it very accurately. */
+ if (iDiff)
+ {
+ //Log3(("compare: bPort: %#x != %#x\n", aDev1->bPort, aDev2->bPort));
+ return iDiff;
+ }
+
+ /*
+ * Things that usually doesn't stay the same when re-enumerating
+ * a device. The fewer things in the category the better chance
+ * that we avoid messing up when more than one device of the same
+ * kind is attached.
+ */
+ if (aIsAwaitingReAttach)
+ {
+ //Log3(("aDev1=%p == aDev2=%p\n", aDev1, aDev2));
+ return 0;
+ }
+ /* device number always changes. */
+ return strcmp(aDev1->pszAddress, aDev2->pszAddress);
+}
+
+/**
+ * Updates the state of the device.
+ *
+ * If this method returns @c true, Host::onUSBDeviceStateChanged() will be
+ * called to process the state change (complete the state change request,
+ * inform the VM process etc.).
+ *
+ * If this method returns @c false, it is assumed that the given state change
+ * is "minor": it doesn't require any further action other than update the
+ * mState field with the actual state value.
+ *
+ * Regardless of the return value, this method always takes ownership of the
+ * new USBDEVICE structure passed in and updates the pNext and pPrev fiends in
+ * it using the values of the old structure.
+ *
+ * @param[in] aDev The current device state as seen by the proxy backend.
+ * @param[out] aRunFilters Whether the state change should be accompanied by
+ * running filters on the device.
+ * @param[out] aIgnoreMachine Machine to ignore when running filters.
+ *
+ * @returns Whether the Host object should be bothered with this state change.
+ *
+ * @todo Just do everything here, that is, call filter runners and everything that
+ * works by state change. Using 3 return codes/parameters is just plain ugly.
+ */
+bool HostUSBDevice::i_updateState(PCUSBDEVICE aDev, bool *aRunFilters, SessionMachine **aIgnoreMachine)
+{
+ *aRunFilters = false;
+ *aIgnoreMachine = NULL;
+
+ /*
+ * Locking.
+ */
+ AssertReturn(!isWriteLockOnCurrentThread(), false);
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), false);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Replace the existing structure by the new one.
+ */
+ const USBDEVICESTATE enmOldState = mUsb->enmState; NOREF(enmOldState);
+ if (mUsb != aDev)
+ {
+#if defined(RT_OS_WINDOWS)
+ /* we used this logic of string comparison in HostUSBDevice::compare
+ * now we need to preserve strings from the old device if the new device has zero strings
+ * this ensures the device is correctly matched later on
+ * otherwise we may end up with a phantom misconfigured device instance */
+ if ((mUniSubState == kHostUSBDeviceSubState_AwaitingDetach /* (In case we don't get the detach notice.) */
+ || mUniSubState == kHostUSBDeviceSubState_AwaitingReAttach)
+ && (!aDev->pszSerialNumber || !*aDev->pszSerialNumber)
+ && (!aDev->pszManufacturer || !*aDev->pszManufacturer)
+ && (!aDev->pszProduct || !*aDev->pszProduct))
+ {
+ aDev->u64SerialHash = mUsb->u64SerialHash;
+
+ if (mUsb->pszSerialNumber && *mUsb->pszSerialNumber)
+ {
+ if (aDev->pszSerialNumber)
+ RTStrFree((char *)aDev->pszSerialNumber);
+
+ /* since we're going to free old device later on,
+ * we can just assign the string from it to the new device
+ * and zero up the string filed for the old device */
+ aDev->pszSerialNumber = mUsb->pszSerialNumber;
+ mUsb->pszSerialNumber = NULL;
+ }
+
+ if (mUsb->pszManufacturer && *mUsb->pszManufacturer)
+ {
+ if (aDev->pszManufacturer)
+ RTStrFree((char *)aDev->pszManufacturer);
+
+ /* since we're going to free old device later on,
+ * we can just assign the string from it to the new device
+ * and zero up the string filed for the old device */
+ aDev->pszManufacturer = mUsb->pszManufacturer;
+ mUsb->pszManufacturer = NULL;
+ }
+
+ if (mUsb->pszProduct && *mUsb->pszProduct)
+ {
+ if (aDev->pszProduct)
+ RTStrFree((char *)aDev->pszProduct);
+
+ /* since we're going to free old device later on,
+ * we can just assign the string from it to the new device
+ * and zero up the string filed for the old device */
+ aDev->pszProduct = mUsb->pszProduct;
+ mUsb->pszProduct = NULL;
+ }
+ }
+#endif
+ aDev->pNext = mUsb->pNext;
+ aDev->pPrev = mUsb->pPrev;
+ USBProxyBackend::freeDevice(mUsb);
+ mUsb = aDev;
+ }
+
+/*
+ * Defined on hosts where we have a driver that keeps proper device states.
+ */
+# if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN)
+# define HOSTUSBDEVICE_FUZZY_STATE 1
+# else
+# undef HOSTUSBDEVICE_FUZZY_STATE
+# endif
+ /*
+ * For some hosts we'll have to be pretty careful here because
+ * they don't always have a clue what is going on. This is
+ * particularly true on linux and solaris, while windows and
+ * darwin generally knows a bit more.
+ */
+ bool fIsImportant = false;
+ if (enmOldState != mUsb->enmState)
+ {
+ LogFlowThisFunc(("%p {%s} %s\n", this, mName, i_getStateName()));
+ switch (mUsb->enmState)
+ {
+ /*
+ * Little fuzziness here, except where we fake capture.
+ */
+ case USBDEVICESTATE_USED_BY_HOST:
+ switch (mUniState)
+ {
+ /* Host drivers installed, that's fine. */
+ case kHostUSBDeviceState_Capturable:
+ case kHostUSBDeviceState_Unused:
+ LogThisFunc(("{%s} %s -> %s\n", mName, i_getStateName(), i_stateName(kHostUSBDeviceState_UsedByHost)));
+ *aRunFilters = i_setState(kHostUSBDeviceState_UsedByHost);
+ break;
+ case kHostUSBDeviceState_UsedByHost:
+ break;
+
+ /* Can only mean that we've failed capturing it. */
+ case kHostUSBDeviceState_Capturing:
+ LogThisFunc(("{%s} capture failed! (#1)\n", mName));
+ mUSBProxyBackend->captureDeviceCompleted(this, false /* aSuccess */);
+ *aRunFilters = i_failTransition(kHostUSBDeviceState_UsedByHost);
+ mMachine.setNull();
+ break;
+
+ /* Guess we've successfully released it. */
+ case kHostUSBDeviceState_ReleasingToHost:
+ LogThisFunc(("{%s} %s -> %s\n", mName, i_getStateName(), i_stateName(kHostUSBDeviceState_UsedByHost)));
+ mUSBProxyBackend->releaseDeviceCompleted(this, true /* aSuccess */);
+ *aRunFilters = i_setState(kHostUSBDeviceState_UsedByHost);
+ break;
+
+ /* These are IPC states and should be left alone. */
+ case kHostUSBDeviceState_AttachingToVM:
+ case kHostUSBDeviceState_DetachingFromVM:
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ LogThisFunc(("{%s} %s - changed to USED_BY_HOST...\n", mName, i_getStateName()));
+ break;
+
+#ifdef HOSTUSBDEVICE_FUZZY_STATE
+ /* Fake: We can't prevent anyone from grabbing it. */
+ case kHostUSBDeviceState_HeldByProxy:
+ LogThisFunc(("{%s} %s -> %s!\n", mName, i_getStateName(), i_stateName(kHostUSBDeviceState_UsedByHost)));
+ *aRunFilters = i_setState(kHostUSBDeviceState_UsedByHost);
+ break;
+ //case kHostUSBDeviceState_UsedByVM:
+ // /** @todo needs to be detached from the VM. */
+ // break;
+#endif
+ /* Not supposed to happen... */
+#ifndef HOSTUSBDEVICE_FUZZY_STATE
+ case kHostUSBDeviceState_HeldByProxy:
+#endif
+ case kHostUSBDeviceState_UsedByVM:
+ case kHostUSBDeviceState_PhysDetached:
+ case kHostUSBDeviceState_Unsupported:
+ default:
+ AssertMsgFailed(("{%s} %s\n", mName, i_getStateName()));
+ break;
+ }
+ break;
+
+ /*
+ * It changed to capturable. Fuzzy hosts might easily
+ * confuse UsedByVM with this one.
+ */
+ case USBDEVICESTATE_USED_BY_HOST_CAPTURABLE:
+ switch (mUniState)
+ {
+ /* No change. */
+#ifdef HOSTUSBDEVICE_FUZZY_STATE
+ case kHostUSBDeviceState_HeldByProxy:
+ case kHostUSBDeviceState_UsedByVM:
+#endif
+ case kHostUSBDeviceState_Capturable:
+ break;
+
+ /* Changed! */
+ case kHostUSBDeviceState_UsedByHost:
+ fIsImportant = true;
+ RT_FALL_THRU();
+ case kHostUSBDeviceState_Unused:
+ LogThisFunc(("{%s} %s -> %s\n", mName, i_getStateName(), i_stateName(kHostUSBDeviceState_Capturable)));
+ *aRunFilters = i_setState(kHostUSBDeviceState_Capturable);
+ break;
+
+ /* Can only mean that we've failed capturing it. */
+ case kHostUSBDeviceState_Capturing:
+ LogThisFunc(("{%s} capture failed! (#2)\n", mName));
+ mUSBProxyBackend->captureDeviceCompleted(this, false /* aSuccess */);
+ *aRunFilters = i_failTransition(kHostUSBDeviceState_Capturable);
+ mMachine.setNull();
+ break;
+
+ /* Guess we've successfully released it. */
+ case kHostUSBDeviceState_ReleasingToHost:
+ LogThisFunc(("{%s} %s -> %s\n", mName, i_getStateName(), i_stateName(kHostUSBDeviceState_Capturable)));
+ mUSBProxyBackend->releaseDeviceCompleted(this, true /* aSuccess */);
+ *aRunFilters = i_setState(kHostUSBDeviceState_Capturable);
+ break;
+
+ /* These are IPC states and should be left alone. */
+ case kHostUSBDeviceState_AttachingToVM:
+ case kHostUSBDeviceState_DetachingFromVM:
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ LogThisFunc(("{%s} %s - changed to USED_BY_HOST_CAPTURABLE...\n", mName, i_getStateName()));
+ break;
+
+ /* Not supposed to happen*/
+#ifndef HOSTUSBDEVICE_FUZZY_STATE
+ case kHostUSBDeviceState_HeldByProxy:
+ case kHostUSBDeviceState_UsedByVM:
+#endif
+ case kHostUSBDeviceState_Unsupported:
+ case kHostUSBDeviceState_PhysDetached:
+ default:
+ AssertMsgFailed(("{%s} %s\n", mName, i_getStateName()));
+ break;
+ }
+ break;
+
+
+ /*
+ * It changed to capturable. Fuzzy hosts might easily
+ * confuse UsedByVM and HeldByProxy with this one.
+ */
+ case USBDEVICESTATE_UNUSED:
+ switch (mUniState)
+ {
+ /* No change. */
+#ifdef HOSTUSBDEVICE_FUZZY_STATE
+ case kHostUSBDeviceState_HeldByProxy:
+ case kHostUSBDeviceState_UsedByVM:
+#endif
+ case kHostUSBDeviceState_Unused:
+ break;
+
+ /* Changed! */
+ case kHostUSBDeviceState_UsedByHost:
+ case kHostUSBDeviceState_Capturable:
+ fIsImportant = true;
+ LogThisFunc(("{%s} %s -> %s\n", mName, i_getStateName(), i_stateName(kHostUSBDeviceState_Unused)));
+ *aRunFilters = i_setState(kHostUSBDeviceState_Unused);
+ break;
+
+ /* Can mean that we've failed capturing it, but on windows it is the detach signal. */
+ case kHostUSBDeviceState_Capturing:
+#if defined(RT_OS_WINDOWS)
+ if (mUniSubState == kHostUSBDeviceSubState_AwaitingDetach)
+ {
+ LogThisFunc(("{%s} capture advancing thru UNUSED...\n", mName));
+ *aRunFilters = i_advanceTransition();
+ }
+ else
+#endif
+ {
+ LogThisFunc(("{%s} capture failed! (#3)\n", mName));
+ mUSBProxyBackend->captureDeviceCompleted(this, false /* aSuccess */);
+ *aRunFilters = i_failTransition(kHostUSBDeviceState_Unused);
+ mMachine.setNull();
+ }
+ break;
+
+ /* Guess we've successfully released it. */
+ case kHostUSBDeviceState_ReleasingToHost:
+ LogThisFunc(("{%s} %s -> %s\n", mName, i_getStateName(), i_stateName(kHostUSBDeviceState_Unused)));
+ mUSBProxyBackend->releaseDeviceCompleted(this, true /* aSuccess */);
+ *aRunFilters = i_setState(kHostUSBDeviceState_Unused);
+ break;
+
+ /* These are IPC states and should be left alone. */
+ case kHostUSBDeviceState_AttachingToVM:
+ case kHostUSBDeviceState_DetachingFromVM:
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ LogThisFunc(("{%s} %s - changed to UNUSED...\n", mName, i_getStateName()));
+ break;
+
+ /* Not supposed to happen*/
+#ifndef HOSTUSBDEVICE_FUZZY_STATE
+ case kHostUSBDeviceState_HeldByProxy:
+ case kHostUSBDeviceState_UsedByVM:
+#endif
+ case kHostUSBDeviceState_Unsupported:
+ case kHostUSBDeviceState_PhysDetached:
+ default:
+ AssertMsgFailed(("{%s} %s\n", mName, i_getStateName()));
+ break;
+ }
+ break;
+
+ /*
+ * This is pretty straight forward, except that everyone
+ * might sometimes confuse this and the UsedByVM state.
+ */
+ case USBDEVICESTATE_HELD_BY_PROXY:
+ switch (mUniState)
+ {
+ /* No change. */
+ case kHostUSBDeviceState_HeldByProxy:
+ break;
+ case kHostUSBDeviceState_UsedByVM:
+ LogThisFunc(("{%s} %s - changed to HELD_BY_PROXY...\n", mName, i_getStateName()));
+ break;
+
+ /* Guess we've successfully captured it. */
+ case kHostUSBDeviceState_Capturing:
+ LogThisFunc(("{%s} capture succeeded!\n", mName));
+ mUSBProxyBackend->captureDeviceCompleted(this, true /* aSuccess */);
+ *aRunFilters = i_advanceTransition(true /* fast forward thru re-attach */);
+
+ /* Take action if we're supposed to attach it to a VM. */
+ if (mUniState == kHostUSBDeviceState_AttachingToVM)
+ {
+ alock.release();
+ i_attachToVM(mMachine, mCaptureFilename, mMaskedIfs);
+ alock.acquire();
+ }
+ break;
+
+ /* Can only mean that we've failed capturing it. */
+ case kHostUSBDeviceState_ReleasingToHost:
+ LogThisFunc(("{%s} %s failed!\n", mName, i_getStateName()));
+ mUSBProxyBackend->releaseDeviceCompleted(this, false /* aSuccess */);
+ *aRunFilters = i_setState(kHostUSBDeviceState_HeldByProxy);
+ break;
+
+ /* These are IPC states and should be left alone. */
+ case kHostUSBDeviceState_AttachingToVM:
+ case kHostUSBDeviceState_DetachingFromVM:
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ LogThisFunc(("{%s} %s - changed to HELD_BY_PROXY...\n", mName, i_getStateName()));
+ break;
+
+ /* Not supposed to happen. */
+ case kHostUSBDeviceState_Unsupported:
+ case kHostUSBDeviceState_UsedByHost:
+ case kHostUSBDeviceState_Capturable:
+ case kHostUSBDeviceState_Unused:
+ case kHostUSBDeviceState_PhysDetached:
+ default:
+ AssertMsgFailed(("{%s} %s\n", mName, i_getStateName()));
+ break;
+ }
+ break;
+
+ /*
+ * This is very straight forward and only Darwin implements it.
+ */
+ case USBDEVICESTATE_USED_BY_GUEST:
+ switch (mUniState)
+ {
+ /* No change. */
+ case kHostUSBDeviceState_HeldByProxy:
+ LogThisFunc(("{%s} %s - changed to USED_BY_GUEST...\n", mName, i_getStateName()));
+ break;
+ case kHostUSBDeviceState_UsedByVM:
+ break;
+
+ /* These are IPC states and should be left alone. */
+ case kHostUSBDeviceState_AttachingToVM:
+ case kHostUSBDeviceState_DetachingFromVM:
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ LogThisFunc(("{%s} %s - changed to USED_BY_GUEST...\n", mName, i_getStateName()));
+ break;
+
+ /* Not supposed to happen. */
+ case kHostUSBDeviceState_Unsupported:
+ case kHostUSBDeviceState_Capturable:
+ case kHostUSBDeviceState_Unused:
+ case kHostUSBDeviceState_UsedByHost:
+ case kHostUSBDeviceState_PhysDetached:
+ case kHostUSBDeviceState_ReleasingToHost:
+ case kHostUSBDeviceState_Capturing:
+ default:
+ AssertMsgFailed(("{%s} %s\n", mName, i_getStateName()));
+ break;
+ }
+ break;
+
+ /*
+ * This is not supposed to happen and indicates a bug in the backend!
+ */
+ case USBDEVICESTATE_UNSUPPORTED:
+ AssertMsgFailed(("enmOldState=%d {%s} %s\n", enmOldState, mName, i_getStateName()));
+ break;
+ default:
+ AssertMsgFailed(("enmState=%d {%s} %s\n", mUsb->enmState, mName, i_getStateName()));
+ break;
+ }
+ }
+ else if ( mUniSubState == kHostUSBDeviceSubState_AwaitingDetach
+ && i_hasAsyncOperationTimedOut())
+ {
+ LogRel(("USB: timeout in %s for {%RTuuid} / {%s}\n", i_getStateName(), mId.raw(), mName));
+ *aRunFilters = i_failTransition(kHostUSBDeviceState_Invalid);
+ fIsImportant = true;
+ }
+ else
+ {
+ LogFlowThisFunc(("%p {%s} %s - no change %d\n", this, mName, i_getStateName(), enmOldState));
+ /** @todo might have to handle some stuff here too if we cannot make the release/capture
+ * handling deal with that above ... */
+ }
+
+ return fIsImportant;
+}
+
+
+/**
+ * Updates the state of the device, checking for cases which we fake.
+ *
+ * See HostUSBDevice::updateState() for details.
+ *
+ * @param[in] aDev See HostUSBDevice::updateState().
+ * @param[out] aRunFilters See HostUSBDevice::updateState()
+ * @param[out] aIgnoreMachine See HostUSBDevice::updateState()
+ *
+ * @returns See HostUSBDevice::updateState()
+ */
+bool HostUSBDevice::i_updateStateFake(PCUSBDEVICE aDev, bool *aRunFilters, SessionMachine **aIgnoreMachine)
+{
+ Assert(!isWriteLockOnCurrentThread());
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ const HostUSBDeviceState enmState = mUniState;
+ switch (enmState)
+ {
+ case kHostUSBDeviceState_Capturing:
+ case kHostUSBDeviceState_ReleasingToHost:
+ {
+ *aIgnoreMachine = mUniState == kHostUSBDeviceState_ReleasingToHost ? mMachine : NULL;
+ *aRunFilters = i_advanceTransition();
+ LogThisFunc(("{%s} %s\n", mName, i_getStateName()));
+
+ if (mUsb != aDev)
+ {
+ aDev->pNext = mUsb->pNext;
+ aDev->pPrev = mUsb->pPrev;
+ USBProxyBackend::freeDevice(mUsb);
+ mUsb = aDev;
+ }
+
+ /* call the completion method */
+ if (enmState == kHostUSBDeviceState_Capturing)
+ mUSBProxyBackend->captureDeviceCompleted(this, true /* aSuccess */);
+ else
+ mUSBProxyBackend->releaseDeviceCompleted(this, true /* aSuccess */);
+
+ /* Take action if we're supposed to attach it to a VM. */
+ if (mUniState == kHostUSBDeviceState_AttachingToVM)
+ {
+ alock.release();
+ i_attachToVM(mMachine, mCaptureFilename, mMaskedIfs);
+ }
+ return true;
+ }
+
+ default:
+ alock.release();
+ return i_updateState(aDev, aRunFilters, aIgnoreMachine);
+ }
+}
+
+
+/**
+ * Checks if there is a pending asynchronous operation and whether
+ * it has timed out or not.
+ *
+ * @returns true on timeout, false if not.
+ *
+ * @note Caller must have read or write locked the object before calling.
+ */
+bool HostUSBDevice::i_hasAsyncOperationTimedOut() const
+{
+ switch (mUniSubState)
+ {
+#ifndef RT_OS_WINDOWS /* no timeouts on windows yet since I don't have all the details here... */
+ case kHostUSBDeviceSubState_AwaitingDetach:
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ {
+ uint64_t elapsedNanoseconds = RTTimeNanoTS() - mLastStateChangeTS;
+ return elapsedNanoseconds > UINT64_C(60000000000); /* 60 seconds */ /* PORTME */
+ }
+#endif
+ default:
+ return false;
+ }
+}
+
+
+/**
+ * Translate the state into
+ *
+ * @returns
+ * @param aState
+ * @param aSubState
+ * @param aPendingState
+ */
+/*static*/ const char *HostUSBDevice::i_stateName(HostUSBDeviceState aState,
+ HostUSBDeviceState aPendingState /*= kHostUSBDeviceState_Invalid*/,
+ HostUSBDeviceSubState aSubState /*= kHostUSBDeviceSubState_Default*/)
+{
+ switch (aState)
+ {
+ case kHostUSBDeviceState_Unsupported:
+ AssertReturn(aPendingState == kHostUSBDeviceState_Invalid, "Unsupported{bad}");
+ AssertReturn(aSubState == kHostUSBDeviceSubState_Default, "Unsupported[bad]");
+ return "Unsupported";
+
+ case kHostUSBDeviceState_UsedByHost:
+ AssertReturn(aPendingState == kHostUSBDeviceState_Invalid, "UsedByHost{bad}");
+ AssertReturn(aSubState == kHostUSBDeviceSubState_Default, "UsedByHost[bad]");
+ return "UsedByHost";
+
+ case kHostUSBDeviceState_Capturable:
+ AssertReturn(aPendingState == kHostUSBDeviceState_Invalid, "Capturable{bad}");
+ AssertReturn(aSubState == kHostUSBDeviceSubState_Default, "Capturable[bad]");
+ return "Capturable";
+
+ case kHostUSBDeviceState_Unused:
+ AssertReturn(aPendingState == kHostUSBDeviceState_Invalid, "Unused{bad}");
+ AssertReturn(aSubState == kHostUSBDeviceSubState_Default, "Unused[bad]");
+ return "Unused";
+
+ case kHostUSBDeviceState_HeldByProxy:
+ AssertReturn(aPendingState == kHostUSBDeviceState_Invalid, "HeldByProxy{bad}");
+ AssertReturn(aSubState == kHostUSBDeviceSubState_Default, "HeldByProxy[bad]");
+ return "HeldByProxy";
+
+ case kHostUSBDeviceState_UsedByVM:
+ AssertReturn(aPendingState == kHostUSBDeviceState_Invalid, "UsedByVM{bad}");
+ AssertReturn(aSubState == kHostUSBDeviceSubState_Default, "UsedByVM[bad]");
+ return "UsedByVM";
+
+ case kHostUSBDeviceState_PhysDetached:
+ AssertReturn(aPendingState == kHostUSBDeviceState_Invalid, "PhysDetached{bad}");
+ AssertReturn(aSubState == kHostUSBDeviceSubState_Default, "PhysDetached[bad]");
+ return "PhysDetached";
+
+ case kHostUSBDeviceState_Capturing:
+ switch (aPendingState)
+ {
+ case kHostUSBDeviceState_UsedByVM:
+ switch (aSubState)
+ {
+ case kHostUSBDeviceSubState_Default:
+ return "CapturingForVM";
+ case kHostUSBDeviceSubState_AwaitingDetach:
+ return "CapturingForVM[Detach]";
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ return "CapturingForVM[Attach]";
+ default:
+ AssertFailedReturn("CapturingForVM[bad]");
+ }
+ break;
+
+ case kHostUSBDeviceState_HeldByProxy:
+ switch (aSubState)
+ {
+ case kHostUSBDeviceSubState_Default:
+ return "CapturingForProxy";
+ case kHostUSBDeviceSubState_AwaitingDetach:
+ return "CapturingForProxy[Detach]";
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ return "CapturingForProxy[Attach]";
+ default:
+ AssertFailedReturn("CapturingForProxy[bad]");
+ }
+ break;
+
+ default:
+ AssertFailedReturn("Capturing{bad}");
+ }
+ break;
+
+ case kHostUSBDeviceState_ReleasingToHost:
+ switch (aPendingState)
+ {
+ case kHostUSBDeviceState_Unused:
+ switch (aSubState)
+ {
+ case kHostUSBDeviceSubState_Default:
+ return "ReleasingToHost";
+ case kHostUSBDeviceSubState_AwaitingDetach:
+ return "ReleasingToHost[Detach]";
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ return "ReleasingToHost[Attach]";
+ default:
+ AssertFailedReturn("ReleasingToHost[bad]");
+ }
+ break;
+ default:
+ AssertFailedReturn("ReleasingToHost{bad}");
+ }
+ break;
+
+ case kHostUSBDeviceState_DetachingFromVM:
+ switch (aPendingState)
+ {
+ case kHostUSBDeviceState_HeldByProxy:
+ switch (aSubState)
+ {
+ case kHostUSBDeviceSubState_Default:
+ return "DetatchingFromVM>Proxy";
+ case kHostUSBDeviceSubState_AwaitingDetach:
+ return "DetatchingFromVM>Proxy[Detach]";
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ return "DetatchingFromVM>Proxy[Attach]";
+ default:
+ AssertFailedReturn("DetatchingFromVM>Proxy[bad]");
+ }
+ break;
+
+ case kHostUSBDeviceState_Unused:
+ switch (aSubState)
+ {
+ case kHostUSBDeviceSubState_Default:
+ return "DetachingFromVM>Host";
+ case kHostUSBDeviceSubState_AwaitingDetach:
+ return "DetachingFromVM>Host[Detach]";
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ return "DetachingFromVM>Host[Attach]";
+ default:
+ AssertFailedReturn("DetachingFromVM>Host[bad]");
+ }
+ break;
+
+ default:
+ AssertFailedReturn("DetachingFromVM{bad}");
+ }
+ break;
+
+ case kHostUSBDeviceState_AttachingToVM:
+ switch (aPendingState)
+ {
+ case kHostUSBDeviceState_UsedByVM:
+ switch (aSubState)
+ {
+ case kHostUSBDeviceSubState_Default:
+ return "AttachingToVM";
+ case kHostUSBDeviceSubState_AwaitingDetach:
+ return "AttachingToVM[Detach]";
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ return "AttachingToVM[Attach]";
+ default:
+ AssertFailedReturn("AttachingToVM[bad]");
+ }
+ break;
+
+ default:
+ AssertFailedReturn("AttachingToVM{bad}");
+ }
+ break;
+
+
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ switch (aPendingState)
+ {
+ case kHostUSBDeviceState_PhysDetached:
+ switch (aSubState)
+ {
+ case kHostUSBDeviceSubState_Default:
+ return "PhysDetachingFromVM";
+ default:
+ AssertFailedReturn("AttachingToVM[bad]");
+ }
+ break;
+
+ default:
+ AssertFailedReturn("AttachingToVM{bad}");
+ }
+ break;
+
+ default:
+ AssertFailedReturn("BadState");
+
+ }
+ /* not reached */
+}
+
+/**
+ * Set the device state.
+ *
+ * This method will verify that the state transition is a legal one
+ * according to the statemachine. It will also take care of the
+ * associated house keeping and determine if filters needs to be applied.
+ *
+ * @param aNewState The new state.
+ * @param aNewPendingState The final state of a transition when applicable.
+ * @param aNewSubState The new sub-state when applicable.
+ *
+ * @returns true if filters should be applied to the device, false if not.
+ *
+ * @note The caller must own the write lock for this object.
+ */
+bool HostUSBDevice::i_setState(HostUSBDeviceState aNewState,
+ HostUSBDeviceState aNewPendingState /*= kHostUSBDeviceState_Invalid*/,
+ HostUSBDeviceSubState aNewSubState /*= kHostUSBDeviceSubState_Default*/)
+{
+ Assert(isWriteLockOnCurrentThread());
+ Assert( aNewSubState == kHostUSBDeviceSubState_Default
+ || aNewSubState == kHostUSBDeviceSubState_AwaitingDetach
+ || aNewSubState == kHostUSBDeviceSubState_AwaitingReAttach);
+
+ /*
+ * If the state is unchanged, then don't bother going
+ * thru the validation and setting. This saves a bit of code.
+ */
+ if ( aNewState == mUniState
+ && aNewPendingState == mPendingUniState
+ && aNewSubState == mUniSubState)
+ return false;
+
+ /*
+ * Welcome to the switch orgies!
+ * You're welcome to check out the ones in startTransition(),
+ * advanceTransition(), failTransition() and i_getStateName() too. Enjoy!
+ */
+
+ bool fFilters = false;
+ HostUSBDeviceState NewPrevState = mUniState;
+ switch (mUniState)
+ {
+ /*
+ * Not much can be done with a device in this state.
+ */
+ case kHostUSBDeviceState_Unsupported:
+ switch (aNewState)
+ {
+ case kHostUSBDeviceState_PhysDetached:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ break;
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+
+ /*
+ * Only the host OS (or the user) can make changes
+ * that'll make a device get out of this state.
+ */
+ case kHostUSBDeviceState_UsedByHost:
+ switch (aNewState)
+ {
+ case kHostUSBDeviceState_Capturable:
+ case kHostUSBDeviceState_Unused:
+ fFilters = true;
+ RT_FALL_THRU();
+ case kHostUSBDeviceState_PhysDetached:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ break;
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+
+ /*
+ * Now it gets interesting.
+ */
+ case kHostUSBDeviceState_Capturable:
+ switch (aNewState)
+ {
+ /* Host changes. */
+ case kHostUSBDeviceState_Unused:
+ fFilters = true; /* Wildcard only... */
+ RT_FALL_THRU();
+ case kHostUSBDeviceState_UsedByHost:
+ case kHostUSBDeviceState_PhysDetached:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ break;
+
+ /* VBox actions */
+ case kHostUSBDeviceState_Capturing:
+ switch (aNewPendingState)
+ {
+ case kHostUSBDeviceState_HeldByProxy:
+ case kHostUSBDeviceState_UsedByVM:
+ break;
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+
+ case kHostUSBDeviceState_Unused:
+ switch (aNewState)
+ {
+ /* Host changes. */
+ case kHostUSBDeviceState_PhysDetached:
+ case kHostUSBDeviceState_UsedByHost:
+ case kHostUSBDeviceState_Capturable:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ break;
+
+ /* VBox actions */
+ case kHostUSBDeviceState_Capturing:
+ switch (aNewPendingState)
+ {
+ case kHostUSBDeviceState_HeldByProxy:
+ case kHostUSBDeviceState_UsedByVM:
+ break;
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+
+ /*
+ * VBox owns this device now, what's next...
+ */
+ case kHostUSBDeviceState_HeldByProxy:
+ switch (aNewState)
+ {
+ /* Host changes. */
+ case kHostUSBDeviceState_PhysDetached:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ break;
+
+ /* VBox actions */
+ case kHostUSBDeviceState_AttachingToVM:
+ switch (aNewPendingState)
+ {
+ case kHostUSBDeviceState_UsedByVM:
+ break;
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+ case kHostUSBDeviceState_ReleasingToHost:
+ switch (aNewPendingState)
+ {
+ case kHostUSBDeviceState_Unused: /* Only this! */
+ break;
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+
+
+ case kHostUSBDeviceState_UsedByVM:
+ switch (aNewState)
+ {
+ /* Host changes. */
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ Assert(aNewPendingState == kHostUSBDeviceState_PhysDetached);
+ break;
+
+ /* VBox actions */
+ case kHostUSBDeviceState_DetachingFromVM:
+ switch (aNewPendingState)
+ {
+ case kHostUSBDeviceState_HeldByProxy:
+ case kHostUSBDeviceState_Unused: /* Only this! */
+ break;
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+
+ /*
+ * The final state.
+ */
+ case kHostUSBDeviceState_PhysDetached:
+ switch (mUniState)
+ {
+ case kHostUSBDeviceState_Unsupported:
+ case kHostUSBDeviceState_UsedByHost:
+ case kHostUSBDeviceState_Capturable:
+ case kHostUSBDeviceState_Unused:
+ case kHostUSBDeviceState_HeldByProxy:
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ case kHostUSBDeviceState_DetachingFromVM: // ??
+ case kHostUSBDeviceState_Capturing:
+ case kHostUSBDeviceState_ReleasingToHost:
+ break;
+
+ case kHostUSBDeviceState_AttachingToVM: // ??
+ case kHostUSBDeviceState_UsedByVM:
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+
+
+ /*
+ * The transitional states.
+ */
+ case kHostUSBDeviceState_Capturing:
+ NewPrevState = mPrevUniState;
+ switch (aNewState)
+ {
+ /* Sub state advance. */
+ case kHostUSBDeviceState_Capturing:
+ switch (aNewSubState)
+ {
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ Assert(mUniSubState == kHostUSBDeviceSubState_AwaitingDetach);
+ Assert(aNewPendingState == mPendingUniState);
+ break;
+ default:
+ AssertReleaseMsgFailedReturn(("this=%p mUniState=%d\n", this, mUniState), false);
+ }
+ break;
+
+ /* Host/User/Failure. */
+ case kHostUSBDeviceState_PhysDetached:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ break;
+ case kHostUSBDeviceState_UsedByHost:
+ case kHostUSBDeviceState_Capturable:
+ case kHostUSBDeviceState_Unused:
+ Assert(aNewState == mPrevUniState); /** @todo This is kind of wrong, see i_failTransition. */
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ break;
+
+ /* VBox */
+ case kHostUSBDeviceState_HeldByProxy:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ Assert( mPendingUniState == kHostUSBDeviceState_HeldByProxy
+ || mPendingUniState == kHostUSBDeviceState_UsedByVM /* <- failure */ );
+ break;
+ case kHostUSBDeviceState_AttachingToVM:
+ Assert(aNewPendingState == kHostUSBDeviceState_UsedByVM);
+ NewPrevState = kHostUSBDeviceState_HeldByProxy;
+ break;
+
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+
+ case kHostUSBDeviceState_ReleasingToHost:
+ Assert(mPrevUniState == kHostUSBDeviceState_HeldByProxy);
+ NewPrevState = mPrevUniState;
+ switch (aNewState)
+ {
+ /* Sub state advance. */
+ case kHostUSBDeviceState_ReleasingToHost:
+ switch (aNewSubState)
+ {
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ Assert(mUniSubState == kHostUSBDeviceSubState_AwaitingDetach);
+ Assert(aNewPendingState == mPendingUniState);
+ break;
+ default:
+ AssertReleaseMsgFailedReturn(("this=%p mUniState=%d\n", this, mUniState), false);
+ }
+ break;
+
+ /* Host/Failure. */
+ case kHostUSBDeviceState_PhysDetached:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ break;
+ case kHostUSBDeviceState_HeldByProxy:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ Assert(mPendingUniState == kHostUSBDeviceState_Unused);
+ break;
+
+ /* Success */
+ case kHostUSBDeviceState_UsedByHost:
+ case kHostUSBDeviceState_Capturable:
+ case kHostUSBDeviceState_Unused:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ Assert(mPendingUniState == kHostUSBDeviceState_Unused);
+ break;
+
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+
+ case kHostUSBDeviceState_AttachingToVM:
+ Assert(mPrevUniState == kHostUSBDeviceState_HeldByProxy);
+ NewPrevState = mPrevUniState;
+ switch (aNewState)
+ {
+ /* Host/Failure. */
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ Assert(aNewPendingState == kHostUSBDeviceState_PhysDetached);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ break;
+ case kHostUSBDeviceState_HeldByProxy:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ Assert(mPendingUniState == kHostUSBDeviceState_UsedByVM);
+ break;
+
+ /* Success */
+ case kHostUSBDeviceState_UsedByVM:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ Assert(mPendingUniState == kHostUSBDeviceState_UsedByVM);
+ break;
+
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+
+ case kHostUSBDeviceState_DetachingFromVM:
+ Assert(mPrevUniState == kHostUSBDeviceState_UsedByVM);
+ NewPrevState = mPrevUniState;
+ switch (aNewState)
+ {
+ /* Host/Failure. */
+ case kHostUSBDeviceState_PhysDetached: //??
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ break;
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ Assert(aNewPendingState == kHostUSBDeviceState_PhysDetached);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ break;
+
+ /* Success */
+ case kHostUSBDeviceState_HeldByProxy:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ Assert(mPendingUniState == kHostUSBDeviceState_HeldByProxy);
+ fFilters = true;
+ break;
+
+ case kHostUSBDeviceState_ReleasingToHost:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ Assert(mPendingUniState == kHostUSBDeviceState_Unused);
+ NewPrevState = kHostUSBDeviceState_HeldByProxy;
+ break;
+
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ Assert( mPrevUniState == kHostUSBDeviceState_DetachingFromVM
+ || mPrevUniState == kHostUSBDeviceState_AttachingToVM
+ || mPrevUniState == kHostUSBDeviceState_UsedByVM);
+ NewPrevState = mPrevUniState; /* preserving it is more useful. */
+ switch (aNewState)
+ {
+ case kHostUSBDeviceState_PhysDetached:
+ Assert(aNewPendingState == kHostUSBDeviceState_Invalid);
+ Assert(aNewSubState == kHostUSBDeviceSubState_Default);
+ break;
+ default:
+ AssertLogRelMsgFailedReturn(("this=%p %s -X-> %s\n", this, i_getStateName(),
+ i_stateName(aNewState, aNewPendingState, aNewSubState)), false);
+ }
+ break;
+
+ default:
+ AssertReleaseMsgFailedReturn(("this=%p mUniState=%d\n", this, mUniState), false);
+ }
+
+ /*
+ * Make the state change.
+ */
+ if (NewPrevState != mPrevUniState)
+ LogFlowThisFunc(("%s -> %s (prev: %s -> %s) [%s]\n",
+ i_getStateName(), i_stateName(aNewState, aNewPendingState, aNewSubState),
+ i_stateName(mPrevUniState), i_stateName(NewPrevState), mName));
+ else
+ LogFlowThisFunc(("%s -> %s (prev: %s) [%s]\n",
+ i_getStateName(), i_stateName(aNewState, aNewPendingState, aNewSubState),
+ i_stateName(NewPrevState), mName));
+ mPrevUniState = NewPrevState;
+ mUniState = aNewState;
+ mUniSubState = aNewSubState;
+ mPendingUniState = aNewPendingState;
+ mLastStateChangeTS = RTTimeNanoTS();
+
+ return fFilters;
+}
+
+
+/**
+ * A convenience for entering a transitional state.
+
+ * @param aNewState The new state (transitional).
+ * @param aFinalState The final state of the transition (non-transitional).
+ * @param aNewSubState The new sub-state when applicable.
+ *
+ * @returns Always false because filters are never applied for the start of a transition.
+ *
+ * @note The caller must own the write lock for this object.
+ */
+bool HostUSBDevice::i_startTransition(HostUSBDeviceState aNewState, HostUSBDeviceState aFinalState,
+ HostUSBDeviceSubState aNewSubState /*= kHostUSBDeviceSubState_Default*/)
+{
+ AssertReturn(isWriteLockOnCurrentThread(), false);
+ /*
+ * A quick prevalidation thing. Not really necessary since setState
+ * verifies this too, but it's very easy here.
+ */
+ switch (mUniState)
+ {
+ case kHostUSBDeviceState_Unsupported:
+ case kHostUSBDeviceState_UsedByHost:
+ case kHostUSBDeviceState_Capturable:
+ case kHostUSBDeviceState_Unused:
+ case kHostUSBDeviceState_HeldByProxy:
+ case kHostUSBDeviceState_UsedByVM:
+ break;
+
+ case kHostUSBDeviceState_DetachingFromVM:
+ case kHostUSBDeviceState_Capturing:
+ case kHostUSBDeviceState_ReleasingToHost:
+ case kHostUSBDeviceState_AttachingToVM:
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ AssertMsgFailedReturn(("this=%p %s is a transitional state.\n", this, i_getStateName()), false);
+
+ case kHostUSBDeviceState_PhysDetached:
+ default:
+ AssertReleaseMsgFailedReturn(("this=%p mUniState=%d\n", this, mUniState), false);
+ }
+
+ return i_setState(aNewState, aFinalState, aNewSubState);
+}
+
+
+/**
+ * A convenience for advancing a transitional state forward.
+ *
+ * @param aSkipReAttach Fast forwards thru the re-attach substate if
+ * applicable.
+ *
+ * @returns true if filters should be applied to the device, false if not.
+ *
+ * @note The caller must own the write lock for this object.
+ */
+bool HostUSBDevice::i_advanceTransition(bool aSkipReAttach /* = false */)
+{
+ AssertReturn(isWriteLockOnCurrentThread(), false);
+ HostUSBDeviceState enmPending = mPendingUniState;
+ HostUSBDeviceSubState enmSub = mUniSubState;
+ HostUSBDeviceState enmState = mUniState;
+ switch (enmState)
+ {
+ case kHostUSBDeviceState_Capturing:
+ switch (enmSub)
+ {
+ case kHostUSBDeviceSubState_AwaitingDetach:
+ enmSub = kHostUSBDeviceSubState_AwaitingReAttach;
+ break;
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ enmSub = kHostUSBDeviceSubState_Default;
+ RT_FALL_THRU();
+ case kHostUSBDeviceSubState_Default:
+ switch (enmPending)
+ {
+ case kHostUSBDeviceState_UsedByVM:
+ enmState = kHostUSBDeviceState_AttachingToVM;
+ break;
+ case kHostUSBDeviceState_HeldByProxy:
+ enmState = enmPending;
+ enmPending = kHostUSBDeviceState_Invalid;
+ break;
+ default:
+ AssertMsgFailedReturn(("this=%p invalid pending state %d: %s\n",
+ this, enmPending, i_getStateName()), false);
+ }
+ break;
+ default:
+ AssertReleaseMsgFailedReturn(("this=%p mUniState=%d\n", this, mUniState), false);
+ }
+ break;
+
+ case kHostUSBDeviceState_ReleasingToHost:
+ switch (enmSub)
+ {
+ case kHostUSBDeviceSubState_AwaitingDetach:
+ enmSub = kHostUSBDeviceSubState_AwaitingReAttach;
+ break;
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ enmSub = kHostUSBDeviceSubState_Default;
+ RT_FALL_THRU();
+ case kHostUSBDeviceSubState_Default:
+ switch (enmPending)
+ {
+ /* Use Unused here since it implies that filters has been applied
+ and will make sure they aren't applied if the final state really
+ is Capturable. */
+ case kHostUSBDeviceState_Unused:
+ enmState = enmPending;
+ enmPending = kHostUSBDeviceState_Invalid;
+ break;
+ default:
+ AssertMsgFailedReturn(("this=%p invalid pending state %d: %s\n",
+ this, enmPending, i_getStateName()), false);
+ }
+ break;
+ default:
+ AssertReleaseMsgFailedReturn(("this=%p mUniState=%d\n", this, mUniState), false);
+ }
+ break;
+
+ case kHostUSBDeviceState_AttachingToVM:
+ switch (enmSub)
+ {
+ case kHostUSBDeviceSubState_AwaitingDetach:
+ enmSub = kHostUSBDeviceSubState_AwaitingReAttach;
+ break;
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ enmSub = kHostUSBDeviceSubState_Default;
+ RT_FALL_THRU();
+ case kHostUSBDeviceSubState_Default:
+ switch (enmPending)
+ {
+ case kHostUSBDeviceState_UsedByVM:
+ enmState = enmPending;
+ enmPending = kHostUSBDeviceState_Invalid;
+ break;
+ default:
+ AssertMsgFailedReturn(("this=%p invalid pending state %d: %s\n",
+ this, enmPending, i_getStateName()), false);
+ }
+ break;
+ default:
+ AssertReleaseMsgFailedReturn(("this=%p mUniState=%d\n", this, mUniState), false);
+ }
+ break;
+
+ case kHostUSBDeviceState_DetachingFromVM:
+ switch (enmSub)
+ {
+ case kHostUSBDeviceSubState_AwaitingDetach:
+ enmSub = kHostUSBDeviceSubState_AwaitingReAttach;
+ break;
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ enmSub = kHostUSBDeviceSubState_Default;
+ RT_FALL_THRU();
+ case kHostUSBDeviceSubState_Default:
+ switch (enmPending)
+ {
+ case kHostUSBDeviceState_HeldByProxy:
+ enmState = enmPending;
+ enmPending = kHostUSBDeviceState_Invalid;
+ break;
+ case kHostUSBDeviceState_Unused:
+ enmState = kHostUSBDeviceState_ReleasingToHost;
+ break;
+ default:
+ AssertMsgFailedReturn(("this=%p invalid pending state %d: %s\n",
+ this, enmPending, i_getStateName()), false);
+ }
+ break;
+ default:
+ AssertReleaseMsgFailedReturn(("this=%p mUniState=%d\n", this, mUniState), false);
+ }
+ break;
+
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ switch (enmSub)
+ {
+ case kHostUSBDeviceSubState_Default:
+ switch (enmPending)
+ {
+ case kHostUSBDeviceState_PhysDetached:
+ enmState = enmPending;
+ enmPending = kHostUSBDeviceState_Invalid;
+ break;
+ default:
+ AssertMsgFailedReturn(("this=%p invalid pending state %d: %s\n",
+ this, enmPending, i_getStateName()), false);
+ }
+ break;
+ default:
+ AssertReleaseMsgFailedReturn(("this=%p mUniState=%d\n", this, mUniState), false);
+ }
+ break;
+
+ case kHostUSBDeviceState_Unsupported:
+ case kHostUSBDeviceState_UsedByHost:
+ case kHostUSBDeviceState_Capturable:
+ case kHostUSBDeviceState_Unused:
+ case kHostUSBDeviceState_HeldByProxy:
+ case kHostUSBDeviceState_UsedByVM:
+ AssertMsgFailedReturn(("this=%p %s is not transitional\n", this, i_getStateName()), false);
+ case kHostUSBDeviceState_PhysDetached:
+ default:
+ AssertReleaseMsgFailedReturn(("this=%p mUniState=%d\n", this, enmState), false);
+
+ }
+
+ bool fRc = i_setState(enmState, enmPending, enmSub);
+ if (aSkipReAttach && mUniSubState == kHostUSBDeviceSubState_AwaitingReAttach)
+ fRc |= i_advanceTransition(false /* don't fast forward re-attach */);
+ return fRc;
+}
+
+/**
+ * A convenience for failing a transitional state.
+ *
+ * @return true if filters should be applied to the device, false if not.
+ * @param a_enmStateHint USB device state hint. kHostUSBDeviceState_Invalid
+ * if the caller doesn't have a clue to give.
+ *
+ * @note The caller must own the write lock for this object.
+ */
+bool HostUSBDevice::i_failTransition(HostUSBDeviceState a_enmStateHint)
+{
+ AssertReturn(isWriteLockOnCurrentThread(), false);
+ HostUSBDeviceSubState enmSub = mUniSubState;
+ HostUSBDeviceState enmState = mUniState;
+ switch (enmState)
+ {
+ /*
+ * There are just two cases, either we got back to the
+ * previous state (assumes Capture+Attach-To-VM updates it)
+ * or we assume the device has been unplugged (physically).
+ */
+ case kHostUSBDeviceState_DetachingFromVM:
+ case kHostUSBDeviceState_Capturing:
+ case kHostUSBDeviceState_ReleasingToHost:
+ case kHostUSBDeviceState_AttachingToVM:
+ switch (enmSub)
+ {
+ case kHostUSBDeviceSubState_AwaitingDetach:
+ enmSub = kHostUSBDeviceSubState_Default;
+ RT_FALL_THRU();
+ case kHostUSBDeviceSubState_Default:
+ enmState = mPrevUniState;
+ break;
+ case kHostUSBDeviceSubState_AwaitingReAttach:
+ enmSub = kHostUSBDeviceSubState_Default;
+ if (a_enmStateHint != kHostUSBDeviceState_Invalid)
+ enmState = mPrevUniState; /** @todo enmState = a_enmStateHint is more correct, but i_setState doesn't like it. It will usually correct itself shortly. */
+ else
+ enmState = kHostUSBDeviceState_PhysDetached;
+ break;
+ default:
+ AssertReleaseMsgFailedReturn(("this=%p mUniState=%d\n", this, mUniState), false);
+ }
+ break;
+
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ AssertMsgFailedReturn(("this=%p %s shall not fail\n", this, i_getStateName()), false);
+
+ case kHostUSBDeviceState_Unsupported:
+ case kHostUSBDeviceState_UsedByHost:
+ case kHostUSBDeviceState_Capturable:
+ case kHostUSBDeviceState_Unused:
+ case kHostUSBDeviceState_HeldByProxy:
+ case kHostUSBDeviceState_UsedByVM:
+ AssertMsgFailedReturn(("this=%p %s is not transitional\n", this, i_getStateName()), false);
+ case kHostUSBDeviceState_PhysDetached:
+ default:
+ AssertReleaseMsgFailedReturn(("this=%p mUniState=%d\n", this, mUniState), false);
+
+ }
+
+ return i_setState(enmState, kHostUSBDeviceState_Invalid, enmSub);
+}
+
+
+/**
+ * Determines the canonical state of the device.
+ *
+ * @returns canonical state.
+ *
+ * @note The caller must own the read (or write) lock for this object.
+ */
+USBDeviceState_T HostUSBDevice::i_canonicalState() const
+{
+ switch (mUniState)
+ {
+ /*
+ * Straight forward.
+ */
+ case kHostUSBDeviceState_Unsupported:
+ return USBDeviceState_NotSupported;
+
+ case kHostUSBDeviceState_UsedByHost:
+ return USBDeviceState_Unavailable;
+
+ case kHostUSBDeviceState_Capturable:
+ return USBDeviceState_Busy;
+
+ case kHostUSBDeviceState_Unused:
+ return USBDeviceState_Available;
+
+ case kHostUSBDeviceState_HeldByProxy:
+ return USBDeviceState_Held;
+
+ case kHostUSBDeviceState_UsedByVM:
+ return USBDeviceState_Captured;
+
+ /*
+ * Pretend we've reached the final state.
+ */
+ case kHostUSBDeviceState_Capturing:
+ Assert( mPendingUniState == kHostUSBDeviceState_UsedByVM
+ || mPendingUniState == kHostUSBDeviceState_HeldByProxy);
+ return mPendingUniState == kHostUSBDeviceState_UsedByVM ? USBDeviceState_Captured : USBDeviceState_Held;
+ /* The cast ^^^^ is because xidl is using different enums for
+ each of the values. *Very* nice idea... :-) */
+
+ case kHostUSBDeviceState_AttachingToVM:
+ return USBDeviceState_Captured;
+
+ /*
+ * Return the previous state.
+ */
+ case kHostUSBDeviceState_ReleasingToHost:
+ Assert( mPrevUniState == kHostUSBDeviceState_UsedByVM
+ || mPrevUniState == kHostUSBDeviceState_HeldByProxy);
+ return mPrevUniState == kHostUSBDeviceState_UsedByVM ? USBDeviceState_Captured : USBDeviceState_Held;
+ /* The cast ^^^^ is because xidl is using different enums for
+ each of the values. *Very* nice idea... :-) */
+
+ case kHostUSBDeviceState_DetachingFromVM:
+ return USBDeviceState_Captured;
+ case kHostUSBDeviceState_PhysDetachingFromVM:
+ return USBDeviceState_Captured;
+
+ case kHostUSBDeviceState_PhysDetached:
+ default:
+ AssertReleaseMsgFailedReturn(("this=%p mUniState=%d\n", this, mUniState), USBDeviceState_NotSupported);
+ }
+ /* won't ever get here. */
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/HostVideoInputDeviceImpl.cpp b/src/VBox/Main/src-server/HostVideoInputDeviceImpl.cpp
new file mode 100644
index 00000000..8c2eae53
--- /dev/null
+++ b/src/VBox/Main/src-server/HostVideoInputDeviceImpl.cpp
@@ -0,0 +1,256 @@
+/* $Id: HostVideoInputDeviceImpl.cpp $ */
+/** @file
+ * Host video capture device implementation.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOSTVIDEOINPUTDEVICE
+#include "HostVideoInputDeviceImpl.h"
+#include "LoggingNew.h"
+#include "VirtualBoxImpl.h"
+#ifdef VBOX_WITH_EXTPACK
+# include "ExtPackManagerImpl.h"
+#endif
+
+#include <iprt/err.h>
+#include <iprt/ldr.h>
+#include <iprt/path.h>
+
+#include <VBox/sup.h>
+
+/*
+ * HostVideoInputDevice implementation.
+ */
+DEFINE_EMPTY_CTOR_DTOR(HostVideoInputDevice)
+
+HRESULT HostVideoInputDevice::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void HostVideoInputDevice::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+/*
+ * Initializes the instance.
+ */
+HRESULT HostVideoInputDevice::init(const com::Utf8Str &name, const com::Utf8Str &path, const com::Utf8Str &alias)
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m.name = name;
+ m.path = path;
+ m.alias = alias;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/*
+ * Uninitializes the instance.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void HostVideoInputDevice::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m.name.setNull();
+ m.path.setNull();
+ m.alias.setNull();
+}
+
+static HRESULT hostVideoInputDeviceAdd(HostVideoInputDeviceList *pList,
+ const com::Utf8Str &name,
+ const com::Utf8Str &path,
+ const com::Utf8Str &alias)
+{
+ ComObjPtr<HostVideoInputDevice> obj;
+ HRESULT hr = obj.createObject();
+ if (SUCCEEDED(hr))
+ {
+ hr = obj->init(name, path, alias);
+ if (SUCCEEDED(hr))
+ pList->push_back(obj);
+ }
+ return hr;
+}
+
+static DECLCALLBACK(int) hostWebcamAdd(void *pvUser,
+ const char *pszName,
+ const char *pszPath,
+ const char *pszAlias,
+ uint64_t *pu64Result)
+{
+ HostVideoInputDeviceList *pList = (HostVideoInputDeviceList *)pvUser;
+ HRESULT hr = hostVideoInputDeviceAdd(pList, pszName, pszPath, pszAlias);
+ if (FAILED(hr))
+ {
+ *pu64Result = (uint64_t)hr;
+ return VERR_NOT_SUPPORTED;
+ }
+ return VINF_SUCCESS;
+}
+
+/** @todo These typedefs must be in a header. */
+typedef DECLCALLBACKTYPE(int, FNVBOXHOSTWEBCAMADD,(void *pvUser,
+ const char *pszName,
+ const char *pszPath,
+ const char *pszAlias,
+ uint64_t *pu64Result));
+typedef FNVBOXHOSTWEBCAMADD *PFNVBOXHOSTWEBCAMADD;
+
+typedef DECLCALLBACKTYPE(int, FNVBOXHOSTWEBCAMLIST,(PFNVBOXHOSTWEBCAMADD pfnWebcamAdd,
+ void *pvUser,
+ uint64_t *pu64WebcamAddResult));
+typedef FNVBOXHOSTWEBCAMLIST *PFNVBOXHOSTWEBCAMLIST;
+
+
+/*
+ * Work around clang being unhappy about PFNVBOXHOSTWEBCAMLIST
+ * ("exception specifications are not allowed beyond a single level of
+ * indirection"). The original comment for 13.0 check said: "assuming
+ * this issue will be fixed eventually". Well, 13.0 is now out, and
+ * it was not.
+ */
+#define CLANG_EXCEPTION_SPEC_HACK (RT_CLANG_PREREQ(11, 0) /* && !RT_CLANG_PREREQ(13, 0) */)
+
+#if CLANG_EXCEPTION_SPEC_HACK
+static int loadHostWebcamLibrary(const char *pszPath, RTLDRMOD *phmod, void **ppfn)
+#else
+static int loadHostWebcamLibrary(const char *pszPath, RTLDRMOD *phmod, PFNVBOXHOSTWEBCAMLIST *ppfn)
+#endif
+{
+ int rc;
+ if (RTPathHavePath(pszPath))
+ {
+ RTLDRMOD hmod = NIL_RTLDRMOD;
+ RTERRINFOSTATIC ErrInfo;
+ rc = SUPR3HardenedLdrLoadPlugIn(pszPath, &hmod, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ static const char s_szSymbol[] = "VBoxHostWebcamList";
+ rc = RTLdrGetSymbol(hmod, s_szSymbol, (void **)ppfn);
+ if (RT_SUCCESS(rc))
+ *phmod = hmod;
+ else
+ {
+ if (rc != VERR_SYMBOL_NOT_FOUND)
+ LogRel(("Resolving symbol '%s': %Rrc\n", s_szSymbol, rc));
+ RTLdrClose(hmod);
+ hmod = NIL_RTLDRMOD;
+ }
+ }
+ else
+ {
+ LogRel(("Loading the library '%s': %Rrc\n", pszPath, rc));
+ if (RTErrInfoIsSet(&ErrInfo.Core))
+ LogRel((" %s\n", ErrInfo.Core.pszMsg));
+ }
+ }
+ else
+ {
+ LogRel(("Loading the library '%s': No path! Refusing to try loading it!\n", pszPath));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ return rc;
+}
+
+
+static HRESULT fillDeviceList(VirtualBox *pVirtualBox, HostVideoInputDeviceList *pList)
+{
+ HRESULT hr;
+ Utf8Str strLibrary;
+
+#ifdef VBOX_WITH_EXTPACK
+ ExtPackManager *pExtPackMgr = pVirtualBox->i_getExtPackManager();
+ hr = pExtPackMgr->i_getLibraryPathForExtPack("VBoxHostWebcam", ORACLE_PUEL_EXTPACK_NAME, &strLibrary);
+#else
+ hr = E_NOTIMPL;
+#endif
+
+ if (SUCCEEDED(hr))
+ {
+ PFNVBOXHOSTWEBCAMLIST pfn = NULL;
+ RTLDRMOD hmod = NIL_RTLDRMOD;
+#if CLANG_EXCEPTION_SPEC_HACK
+ int vrc = loadHostWebcamLibrary(strLibrary.c_str(), &hmod, (void **)&pfn);
+#else
+ int vrc = loadHostWebcamLibrary(strLibrary.c_str(), &hmod, &pfn);
+#endif
+
+ LogRel(("Load [%s] vrc=%Rrc\n", strLibrary.c_str(), vrc));
+
+ if (RT_SUCCESS(vrc))
+ {
+ uint64_t u64Result = S_OK;
+ vrc = pfn(hostWebcamAdd, pList, &u64Result);
+ Log(("VBoxHostWebcamList vrc %Rrc, result 0x%08RX64\n", vrc, u64Result));
+ if (RT_FAILURE(vrc))
+ {
+ hr = (HRESULT)u64Result;
+ }
+
+ RTLdrClose(hmod);
+ hmod = NIL_RTLDRMOD;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ if (RT_FAILURE(vrc))
+ hr = pVirtualBox->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ HostVideoInputDevice::tr("Failed to get webcam list: %Rrc"), vrc);
+ }
+ }
+
+ return hr;
+}
+
+/* static */ HRESULT HostVideoInputDevice::queryHostDevices(VirtualBox *pVirtualBox, HostVideoInputDeviceList *pList)
+{
+ HRESULT hr = fillDeviceList(pVirtualBox, pList);
+
+ if (FAILED(hr))
+ {
+ pList->clear();
+ }
+
+ return hr;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/MachineImpl.cpp b/src/VBox/Main/src-server/MachineImpl.cpp
new file mode 100644
index 00000000..6e6ad495
--- /dev/null
+++ b/src/VBox/Main/src-server/MachineImpl.cpp
@@ -0,0 +1,17130 @@
+/* $Id: MachineImpl.cpp $ */
+/** @file
+ * Implementation of IMachine in VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2004-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_MACHINE
+
+/* Make sure all the stdint.h macros are included - must come first! */
+#ifndef __STDC_LIMIT_MACROS
+# define __STDC_LIMIT_MACROS
+#endif
+#ifndef __STDC_CONSTANT_MACROS
+# define __STDC_CONSTANT_MACROS
+#endif
+
+#include "LoggingNew.h"
+#include "VirtualBoxImpl.h"
+#include "MachineImpl.h"
+#include "SnapshotImpl.h"
+#include "ClientToken.h"
+#include "ProgressImpl.h"
+#include "ProgressProxyImpl.h"
+#include "MediumAttachmentImpl.h"
+#include "MediumImpl.h"
+#include "MediumLock.h"
+#include "USBControllerImpl.h"
+#include "USBDeviceFiltersImpl.h"
+#include "HostImpl.h"
+#include "SharedFolderImpl.h"
+#include "GuestOSTypeImpl.h"
+#include "VirtualBoxErrorInfoImpl.h"
+#include "StorageControllerImpl.h"
+#include "DisplayImpl.h"
+#include "DisplayUtils.h"
+#include "MachineImplCloneVM.h"
+#include "AutostartDb.h"
+#include "SystemPropertiesImpl.h"
+#include "MachineImplMoveVM.h"
+#include "ExtPackManagerImpl.h"
+#include "MachineLaunchVMCommonWorker.h"
+#include "CryptoUtils.h"
+
+// generated header
+#include "VBoxEvents.h"
+
+#ifdef VBOX_WITH_USB
+# include "USBProxyService.h"
+#endif
+
+#include "AutoCaller.h"
+#include "HashedPw.h"
+#include "Performance.h"
+#include "StringifyEnums.h"
+
+#include <iprt/asm.h>
+#include <iprt/path.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/lockvalidator.h>
+#include <iprt/memsafer.h>
+#include <iprt/process.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/cpp/xml.h> /* xml::XmlFileWriter::s_psz*Suff. */
+#include <iprt/sha.h>
+#include <iprt/string.h>
+#include <iprt/ctype.h>
+
+#include <VBox/com/array.h>
+#include <VBox/com/list.h>
+#include <VBox/VBoxCryptoIf.h>
+
+#include <VBox/err.h>
+#include <VBox/param.h>
+#include <VBox/settings.h>
+#include <VBox/VMMDev.h>
+#include <VBox/vmm/ssm.h>
+
+#ifdef VBOX_WITH_GUEST_PROPS
+# include <VBox/HostServices/GuestPropertySvc.h>
+# include <VBox/com/array.h>
+#endif
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+# include <VBox/HostServices/VBoxClipboardSvc.h>
+#endif
+
+#include "VBox/com/MultiResult.h"
+
+#include <algorithm>
+
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+# include "dtrace/VBoxAPI.h"
+#endif
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+# define HOSTSUFF_EXE ".exe"
+#else /* !RT_OS_WINDOWS */
+# define HOSTSUFF_EXE ""
+#endif /* !RT_OS_WINDOWS */
+
+// defines / prototypes
+/////////////////////////////////////////////////////////////////////////////
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+# define BUF_DATA_SIZE _64K
+
+enum CipherMode
+{
+ CipherModeGcm = 0,
+ CipherModeCtr,
+ CipherModeXts,
+ CipherModeMax
+};
+
+enum AesSize
+{
+ Aes128 = 0,
+ Aes256,
+ AesMax
+};
+
+const char *g_apszCipher[AesMax][CipherModeMax] =
+{
+ {"AES-GCM128", "AES-CTR128", "AES-XTS128-PLAIN64"},
+ {"AES-GCM256", "AES-CTR256", "AES-XTS256-PLAIN64"}
+};
+const char *g_apszCipherAlgo[AesMax] = {"AES-128", "AES-256"};
+
+static const char *getCipherString(const char *pszAlgo, const int iMode)
+{
+ if (iMode >= CipherModeMax)
+ return pszAlgo;
+
+ for (int i = 0; i < AesMax; i++)
+ {
+ if (strcmp(pszAlgo, g_apszCipherAlgo[i]) == 0)
+ return g_apszCipher[i][iMode];
+ }
+ return pszAlgo;
+}
+
+static const char *getCipherStringWithoutMode(const char *pszAlgo)
+{
+ for (int i = 0; i < AesMax; i++)
+ {
+ for (int j = 0; j < CipherModeMax; j++)
+ {
+ if (strcmp(pszAlgo, g_apszCipher[i][j]) == 0)
+ return g_apszCipherAlgo[i];
+ }
+ }
+ return pszAlgo;
+}
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+// Machine::Data structure
+/////////////////////////////////////////////////////////////////////////////
+
+Machine::Data::Data()
+{
+ mRegistered = FALSE;
+ pMachineConfigFile = NULL;
+ /* Contains hints on what has changed when the user is using the VM (config
+ * changes, running the VM, ...). This is used to decide if a config needs
+ * to be written to disk. */
+ flModifications = 0;
+ /* VM modification usually also trigger setting the current state to
+ * "Modified". Although this is not always the case. An e.g. is the VM
+ * initialization phase or when snapshot related data is changed. The
+ * actually behavior is controlled by the following flag. */
+ m_fAllowStateModification = false;
+ mAccessible = FALSE;
+ /* mUuid is initialized in Machine::init() */
+
+ mMachineState = MachineState_PoweredOff;
+ RTTimeNow(&mLastStateChange);
+
+ mMachineStateDeps = 0;
+ mMachineStateDepsSem = NIL_RTSEMEVENTMULTI;
+ mMachineStateChangePending = 0;
+
+ mCurrentStateModified = TRUE;
+ mGuestPropertiesModified = FALSE;
+
+ mSession.mPID = NIL_RTPROCESS;
+ mSession.mLockType = LockType_Null;
+ mSession.mState = SessionState_Unlocked;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ mpKeyStore = NULL;
+#endif
+}
+
+Machine::Data::~Data()
+{
+ if (mMachineStateDepsSem != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(mMachineStateDepsSem);
+ mMachineStateDepsSem = NIL_RTSEMEVENTMULTI;
+ }
+ if (pMachineConfigFile)
+ {
+ delete pMachineConfigFile;
+ pMachineConfigFile = NULL;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Machine::HWData structure
+/////////////////////////////////////////////////////////////////////////////
+
+Machine::HWData::HWData()
+{
+ /* default values for a newly created machine */
+ mHWVersion.printf("%d", SchemaDefs::DefaultHardwareVersion);
+ mMemorySize = 128;
+ mCPUCount = 1;
+ mCPUHotPlugEnabled = false;
+ mMemoryBalloonSize = 0;
+ mPageFusionEnabled = false;
+ mHWVirtExEnabled = true;
+ mHWVirtExNestedPagingEnabled = true;
+ mHWVirtExLargePagesEnabled = HC_ARCH_BITS == 64; /* Not supported on 32 bits hosts. */
+ mHWVirtExVPIDEnabled = true;
+ mHWVirtExUXEnabled = true;
+ mHWVirtExForceEnabled = false;
+ mHWVirtExUseNativeApi = false;
+ mHWVirtExVirtVmsaveVmload = true;
+#if HC_ARCH_BITS == 64 || defined(RT_OS_WINDOWS) || defined(RT_OS_DARWIN)
+ mPAEEnabled = true;
+#else
+ mPAEEnabled = false;
+#endif
+ mLongMode = HC_ARCH_BITS == 64 ? settings::Hardware::LongMode_Enabled : settings::Hardware::LongMode_Disabled;
+ mTripleFaultReset = false;
+ mAPIC = true;
+ mX2APIC = false;
+ mIBPBOnVMExit = false;
+ mIBPBOnVMEntry = false;
+ mSpecCtrl = false;
+ mSpecCtrlByHost = false;
+ mL1DFlushOnSched = true;
+ mL1DFlushOnVMEntry = false;
+ mMDSClearOnSched = true;
+ mMDSClearOnVMEntry = false;
+ mNestedHWVirt = false;
+ mHPETEnabled = false;
+ mCpuExecutionCap = 100; /* Maximum CPU execution cap by default. */
+ mCpuIdPortabilityLevel = 0;
+ mCpuProfile = "host";
+
+ /* default boot order: floppy - DVD - HDD */
+ mBootOrder[0] = DeviceType_Floppy;
+ mBootOrder[1] = DeviceType_DVD;
+ mBootOrder[2] = DeviceType_HardDisk;
+ for (size_t i = 3; i < RT_ELEMENTS(mBootOrder); ++i)
+ mBootOrder[i] = DeviceType_Null;
+
+ mClipboardMode = ClipboardMode_Disabled;
+ mClipboardFileTransfersEnabled = FALSE;
+
+ mDnDMode = DnDMode_Disabled;
+
+ mFirmwareType = FirmwareType_BIOS;
+ mKeyboardHIDType = KeyboardHIDType_PS2Keyboard;
+ mPointingHIDType = PointingHIDType_PS2Mouse;
+ mChipsetType = ChipsetType_PIIX3;
+ mIommuType = IommuType_None;
+ mParavirtProvider = ParavirtProvider_Default;
+ mEmulatedUSBCardReaderEnabled = FALSE;
+
+ for (size_t i = 0; i < RT_ELEMENTS(mCPUAttached); ++i)
+ mCPUAttached[i] = false;
+
+ mIOCacheEnabled = true;
+ mIOCacheSize = 5; /* 5MB */
+}
+
+Machine::HWData::~HWData()
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Machine class
+/////////////////////////////////////////////////////////////////////////////
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+Machine::Machine() :
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ mCollectorGuest(NULL),
+#endif
+ mPeer(NULL),
+ mParent(NULL),
+ mSerialPorts(),
+ mParallelPorts(),
+ uRegistryNeedsSaving(0)
+{}
+
+Machine::~Machine()
+{}
+
+HRESULT Machine::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void Machine::FinalRelease()
+{
+ LogFlowThisFunc(("\n"));
+ uninit();
+ BaseFinalRelease();
+}
+
+/**
+ * Initializes a new machine instance; this init() variant creates a new, empty machine.
+ * This gets called from VirtualBox::CreateMachine().
+ *
+ * @param aParent Associated parent object
+ * @param strConfigFile Local file system path to the VM settings file (can
+ * be relative to the VirtualBox config directory).
+ * @param strName name for the machine
+ * @param llGroups list of groups for the machine
+ * @param strOsType OS Type string (stored as is if aOsType is NULL).
+ * @param aOsType OS Type of this machine or NULL.
+ * @param aId UUID for the new machine.
+ * @param fForceOverwrite Whether to overwrite an existing machine settings file.
+ * @param fDirectoryIncludesUUID Whether the use a special VM directory naming
+ * scheme (includes the UUID).
+ * @param aCipher The cipher to encrypt the VM with.
+ * @param aPasswordId The password ID, empty if the VM should not be encrypted.
+ * @param aPassword The password to encrypt the VM with.
+ *
+ * @return Success indicator. if not S_OK, the machine object is invalid
+ */
+HRESULT Machine::init(VirtualBox *aParent,
+ const Utf8Str &strConfigFile,
+ const Utf8Str &strName,
+ const StringsList &llGroups,
+ const Utf8Str &strOsType,
+ GuestOSType *aOsType,
+ const Guid &aId,
+ bool fForceOverwrite,
+ bool fDirectoryIncludesUUID,
+ const com::Utf8Str &aCipher,
+ const com::Utf8Str &aPasswordId,
+ const com::Utf8Str &aPassword)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("(Init_New) aConfigFile='%s'\n", strConfigFile.c_str()));
+
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ RT_NOREF(aCipher);
+ if (aPassword.isNotEmpty() || aPasswordId.isNotEmpty())
+ return setError(VBOX_E_NOT_SUPPORTED, tr("Full VM encryption is not available with this build"));
+#endif
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT rc = initImpl(aParent, strConfigFile);
+ if (FAILED(rc)) return rc;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ com::Utf8Str strSsmKeyId;
+ com::Utf8Str strSsmKeyStore;
+ com::Utf8Str strNVRAMKeyId;
+ com::Utf8Str strNVRAMKeyStore;
+
+ if (aPassword.isNotEmpty() && aPasswordId.isNotEmpty())
+ {
+ /* Resolve the cryptographic interface. */
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ HRESULT hrc = aParent->i_retainCryptoIf(&pCryptoIf);
+ if (SUCCEEDED(hrc))
+ {
+ CipherMode aenmMode[] = {CipherModeGcm, CipherModeGcm, CipherModeGcm, CipherModeCtr};
+ com::Utf8Str *astrKeyId[] = {&mData->mstrKeyId, &strSsmKeyId, &strNVRAMKeyId, &mData->mstrLogKeyId};
+ com::Utf8Str *astrKeyStore[] = {&mData->mstrKeyStore, &strSsmKeyStore, &strNVRAMKeyStore, &mData->mstrLogKeyStore};
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(astrKeyId); i++)
+ {
+ const char *pszCipher = getCipherString(aCipher.c_str(), aenmMode[i]);
+ if (!pszCipher)
+ {
+ hrc = setError(VBOX_E_NOT_SUPPORTED,
+ tr("The cipher '%s' is not supported"), aCipher.c_str());
+ break;
+ }
+
+ VBOXCRYPTOCTX hCryptoCtx;
+ int vrc = pCryptoIf->pfnCryptoCtxCreate(pszCipher, aPassword.c_str(), &hCryptoCtx);
+ if (RT_FAILURE(vrc))
+ {
+ hrc = setErrorBoth(E_FAIL, vrc, tr("New key store creation failed, (%Rrc)"), vrc);
+ break;
+ }
+
+ char *pszKeyStore;
+ vrc = pCryptoIf->pfnCryptoCtxSave(hCryptoCtx, &pszKeyStore);
+ int vrc2 = pCryptoIf->pfnCryptoCtxDestroy(hCryptoCtx);
+ AssertRC(vrc2);
+
+ if (RT_FAILURE(vrc))
+ {
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Saving the key store failed, (%Rrc)"), vrc);
+ break;
+ }
+
+ *(astrKeyStore[i]) = pszKeyStore;
+ RTMemFree(pszKeyStore);
+ *(astrKeyId[i]) = aPasswordId;
+ }
+
+ HRESULT hrc2 = aParent->i_releaseCryptoIf(pCryptoIf);
+ Assert(hrc2 == S_OK); RT_NOREF(hrc2);
+
+ if (FAILED(hrc))
+ return hrc; /* Error is set. */
+ }
+ else
+ return hrc; /* Error is set. */
+ }
+#endif
+
+ rc = i_tryCreateMachineConfigFile(fForceOverwrite);
+ if (FAILED(rc)) return rc;
+
+ if (SUCCEEDED(rc))
+ {
+ // create an empty machine config
+ mData->pMachineConfigFile = new settings::MachineConfigFile(NULL);
+
+ rc = initDataAndChildObjects();
+ }
+
+ if (SUCCEEDED(rc))
+ {
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ mSSData->strStateKeyId = strSsmKeyId;
+ mSSData->strStateKeyStore = strSsmKeyStore;
+#endif
+
+ // set to true now to cause uninit() to call uninitDataAndChildObjects() on failure
+ mData->mAccessible = TRUE;
+
+ unconst(mData->mUuid) = aId;
+
+ mUserData->s.strName = strName;
+
+ if (llGroups.size())
+ mUserData->s.llGroups = llGroups;
+
+ mUserData->s.fDirectoryIncludesUUID = fDirectoryIncludesUUID;
+ // the "name sync" flag determines whether the machine directory gets renamed along
+ // with the machine file; say so if the settings file name is the same as the
+ // settings file parent directory (machine directory)
+ mUserData->s.fNameSync = i_isInOwnDir();
+
+ // initialize the default snapshots folder
+ rc = COMSETTER(SnapshotFolder)(NULL);
+ AssertComRC(rc);
+
+ if (aOsType)
+ {
+ /* Store OS type */
+ mUserData->s.strOsType = aOsType->i_id();
+
+ /* Let the OS type select 64-bit ness. */
+ mHWData->mLongMode = aOsType->i_is64Bit()
+ ? settings::Hardware::LongMode_Enabled : settings::Hardware::LongMode_Disabled;
+
+ /* Let the OS type enable the X2APIC */
+ mHWData->mX2APIC = aOsType->i_recommendedX2APIC();
+
+ rc = aOsType->COMGETTER(RecommendedFirmware)(&mHWData->mFirmwareType);
+ AssertComRC(rc);
+ }
+ else if (!strOsType.isEmpty())
+ {
+ /* Store OS type */
+ mUserData->s.strOsType = strOsType;
+
+ /* No guest OS type object. Pick some plausible defaults which the
+ * host can handle. There's no way to know or validate anything. */
+ mHWData->mLongMode = HC_ARCH_BITS == 64 ? settings::Hardware::LongMode_Enabled : settings::Hardware::LongMode_Disabled;
+ mHWData->mX2APIC = false;
+ }
+
+ /* Apply BIOS defaults. */
+ mBIOSSettings->i_applyDefaults(aOsType);
+
+ /* Apply TPM defaults. */
+ mTrustedPlatformModule->i_applyDefaults(aOsType);
+
+ /* Apply recording defaults. */
+ mRecordingSettings->i_applyDefaults();
+
+ /* Apply network adapters defaults */
+ for (ULONG slot = 0; slot < mNetworkAdapters.size(); ++slot)
+ mNetworkAdapters[slot]->i_applyDefaults(aOsType);
+
+ /* Apply serial port defaults */
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); ++slot)
+ mSerialPorts[slot]->i_applyDefaults(aOsType);
+
+ /* Apply parallel port defaults */
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); ++slot)
+ mParallelPorts[slot]->i_applyDefaults();
+
+ /* Enable the VMMDev testing feature for bootsector VMs: */
+ if (aOsType && aOsType->i_id() == "VBoxBS_64")
+ mData->pMachineConfigFile->mapExtraDataItems["VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled"] = "1";
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ rc = mNvramStore->i_updateEncryptionSettings(strNVRAMKeyId, strNVRAMKeyStore);
+#endif
+ if (SUCCEEDED(rc))
+ {
+ /* At this point the changing of the current state modification
+ * flag is allowed. */
+ i_allowStateModification();
+
+ /* commit all changes made during the initialization */
+ i_commit();
+ }
+ }
+
+ /* Confirm a successful initialization when it's the case */
+ if (SUCCEEDED(rc))
+ {
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (aPassword.isNotEmpty() && aPasswordId.isNotEmpty())
+ {
+ size_t cbPassword = aPassword.length() + 1;
+ uint8_t *pbPassword = (uint8_t *)aPassword.c_str();
+ mData->mpKeyStore->addSecretKey(aPasswordId, pbPassword, cbPassword);
+ }
+#endif
+
+ if (mData->mAccessible)
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setLimited();
+ }
+
+ LogFlowThisFunc(("mName='%s', mRegistered=%RTbool, mAccessible=%RTbool, rc=%08X\n",
+ !!mUserData ? mUserData->s.strName.c_str() : "NULL",
+ mData->mRegistered,
+ mData->mAccessible,
+ rc));
+
+ LogFlowThisFuncLeave();
+
+ return rc;
+}
+
+/**
+ * Initializes a new instance with data from machine XML (formerly Init_Registered).
+ * Gets called in two modes:
+ *
+ * -- from VirtualBox::initMachines() during VirtualBox startup; in that case, the
+ * UUID is specified and we mark the machine as "registered";
+ *
+ * -- from the public VirtualBox::OpenMachine() API, in which case the UUID is NULL
+ * and the machine remains unregistered until RegisterMachine() is called.
+ *
+ * @param aParent Associated parent object
+ * @param strConfigFile Local file system path to the VM settings file (can
+ * be relative to the VirtualBox config directory).
+ * @param aId UUID of the machine or NULL (see above).
+ * @param strPassword Password for decrypting the config
+ *
+ * @return Success indicator. if not S_OK, the machine object is invalid
+ */
+HRESULT Machine::initFromSettings(VirtualBox *aParent,
+ const Utf8Str &strConfigFile,
+ const Guid *aId,
+ const com::Utf8Str &strPassword)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("(Init_Registered) aConfigFile='%s\n", strConfigFile.c_str()));
+
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (strPassword.isNotEmpty())
+ return setError(VBOX_E_NOT_SUPPORTED, tr("Full VM encryption is not available with this build"));
+#else
+ if (strPassword.isNotEmpty())
+ {
+ /* Get at the crpytographic interface. */
+ HRESULT hrc = aParent->i_retainCryptoIf(&pCryptoIf);
+ if (FAILED(hrc))
+ return hrc; /* Error is set. */
+ }
+#endif
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT rc = initImpl(aParent, strConfigFile);
+ if (FAILED(rc)) return rc;
+
+ if (aId)
+ {
+ // loading a registered VM:
+ unconst(mData->mUuid) = *aId;
+ mData->mRegistered = TRUE;
+ // now load the settings from XML:
+ rc = i_registeredInit();
+ // this calls initDataAndChildObjects() and loadSettings()
+ }
+ else
+ {
+ // opening an unregistered VM (VirtualBox::OpenMachine()):
+ rc = initDataAndChildObjects();
+
+ if (SUCCEEDED(rc))
+ {
+ // set to true now to cause uninit() to call uninitDataAndChildObjects() on failure
+ mData->mAccessible = TRUE;
+
+ try
+ {
+ // load and parse machine XML; this will throw on XML or logic errors
+ mData->pMachineConfigFile = new settings::MachineConfigFile(&mData->m_strConfigFileFull,
+ pCryptoIf,
+ strPassword.c_str());
+
+ // reject VM UUID duplicates, they can happen if someone
+ // tries to register an already known VM config again
+ if (aParent->i_findMachine(mData->pMachineConfigFile->uuid,
+ true /* fPermitInaccessible */,
+ false /* aDoSetError */,
+ NULL) != VBOX_E_OBJECT_NOT_FOUND)
+ {
+ throw setError(E_FAIL,
+ tr("Trying to open a VM config '%s' which has the same UUID as an existing virtual machine"),
+ mData->m_strConfigFile.c_str());
+ }
+
+ // use UUID from machine config
+ unconst(mData->mUuid) = mData->pMachineConfigFile->uuid;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ // No exception is thrown if config is encrypted, allowing us to get the uuid and the encryption fields.
+ // We fill in the encryptions fields, and the rest will be filled in if all data parsed.
+ mData->mstrKeyId = mData->pMachineConfigFile->strKeyId;
+ mData->mstrKeyStore = mData->pMachineConfigFile->strKeyStore;
+#endif
+
+ if (mData->pMachineConfigFile->enmParseState == settings::MachineConfigFile::ParseState_PasswordError)
+ {
+ // We just set the inaccessible state and fill the error info allowing the caller
+ // to register the machine with encrypted config even if the password is incorrect
+ mData->mAccessible = FALSE;
+
+ /* fetch the current error info */
+ mData->mAccessError = com::ErrorInfo();
+
+ setError(VBOX_E_PASSWORD_INCORRECT,
+ tr("Decryption of the machine {%RTuuid} failed. Incorrect or unknown password"),
+ mData->pMachineConfigFile->uuid.raw());
+ }
+ else
+ {
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (strPassword.isNotEmpty())
+ {
+ size_t cbKey = strPassword.length() + 1; /* Include terminator */
+ const uint8_t *pbKey = (const uint8_t *)strPassword.c_str();
+ mData->mpKeyStore->addSecretKey(mData->mstrKeyId, pbKey, cbKey);
+ }
+#endif
+
+ rc = i_loadMachineDataFromSettings(*mData->pMachineConfigFile,
+ NULL /* puuidRegistry */);
+ if (FAILED(rc)) throw rc;
+
+ /* At this point the changing of the current state modification
+ * flag is allowed. */
+ i_allowStateModification();
+
+ i_commit();
+ }
+ }
+ catch (HRESULT err)
+ {
+ /* we assume that error info is set by the thrower */
+ rc = err;
+ }
+ catch (...)
+ {
+ rc = VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS);
+ }
+ }
+ }
+
+ /* Confirm a successful initialization when it's the case */
+ if (SUCCEEDED(rc))
+ {
+ if (mData->mAccessible)
+ autoInitSpan.setSucceeded();
+ else
+ {
+ autoInitSpan.setLimited();
+
+ // uninit media from this machine's media registry, or else
+ // reloading the settings will fail
+ mParent->i_unregisterMachineMedia(i_getId());
+ }
+ }
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (pCryptoIf)
+ {
+ HRESULT hrc2 = aParent->i_releaseCryptoIf(pCryptoIf);
+ Assert(hrc2 == S_OK); RT_NOREF(hrc2);
+ }
+#endif
+
+ LogFlowThisFunc(("mName='%s', mRegistered=%RTbool, mAccessible=%RTbool "
+ "rc=%08X\n",
+ !!mUserData ? mUserData->s.strName.c_str() : "NULL",
+ mData->mRegistered, mData->mAccessible, rc));
+
+ LogFlowThisFuncLeave();
+
+ return rc;
+}
+
+/**
+ * Initializes a new instance from a machine config that is already in memory
+ * (import OVF case). Since we are importing, the UUID in the machine
+ * config is ignored and we always generate a fresh one.
+ *
+ * @param aParent Associated parent object.
+ * @param strName Name for the new machine; this overrides what is specified in config.
+ * @param strSettingsFilename File name of .vbox file.
+ * @param config Machine configuration loaded and parsed from XML.
+ *
+ * @return Success indicator. if not S_OK, the machine object is invalid
+ */
+HRESULT Machine::init(VirtualBox *aParent,
+ const Utf8Str &strName,
+ const Utf8Str &strSettingsFilename,
+ const settings::MachineConfigFile &config)
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT rc = initImpl(aParent, strSettingsFilename);
+ if (FAILED(rc)) return rc;
+
+ rc = i_tryCreateMachineConfigFile(false /* fForceOverwrite */);
+ if (FAILED(rc)) return rc;
+
+ rc = initDataAndChildObjects();
+
+ if (SUCCEEDED(rc))
+ {
+ // set to true now to cause uninit() to call uninitDataAndChildObjects() on failure
+ mData->mAccessible = TRUE;
+
+ // create empty machine config for instance data
+ mData->pMachineConfigFile = new settings::MachineConfigFile(NULL);
+
+ // generate fresh UUID, ignore machine config
+ unconst(mData->mUuid).create();
+
+ rc = i_loadMachineDataFromSettings(config,
+ &mData->mUuid); // puuidRegistry: initialize media with this registry ID
+
+ // override VM name as well, it may be different
+ mUserData->s.strName = strName;
+
+ if (SUCCEEDED(rc))
+ {
+ /* At this point the changing of the current state modification
+ * flag is allowed. */
+ i_allowStateModification();
+
+ /* commit all changes made during the initialization */
+ i_commit();
+ }
+ }
+
+ /* Confirm a successful initialization when it's the case */
+ if (SUCCEEDED(rc))
+ {
+ if (mData->mAccessible)
+ autoInitSpan.setSucceeded();
+ else
+ {
+ /* Ignore all errors from unregistering, they would destroy
+- * the more interesting error information we already have,
+- * pinpointing the issue with the VM config. */
+ ErrorInfoKeeper eik;
+
+ autoInitSpan.setLimited();
+
+ // uninit media from this machine's media registry, or else
+ // reloading the settings will fail
+ mParent->i_unregisterMachineMedia(i_getId());
+ }
+ }
+
+ LogFlowThisFunc(("mName='%s', mRegistered=%RTbool, mAccessible=%RTbool "
+ "rc=%08X\n",
+ !!mUserData ? mUserData->s.strName.c_str() : "NULL",
+ mData->mRegistered, mData->mAccessible, rc));
+
+ LogFlowThisFuncLeave();
+
+ return rc;
+}
+
+/**
+ * Shared code between the various init() implementations.
+ * @param aParent The VirtualBox object.
+ * @param strConfigFile Settings file.
+ * @return
+ */
+HRESULT Machine::initImpl(VirtualBox *aParent,
+ const Utf8Str &strConfigFile)
+{
+ LogFlowThisFuncEnter();
+
+ AssertReturn(aParent, E_INVALIDARG);
+ AssertReturn(!strConfigFile.isEmpty(), E_INVALIDARG);
+
+ HRESULT rc = S_OK;
+
+ /* share the parent weakly */
+ unconst(mParent) = aParent;
+
+ /* allocate the essential machine data structure (the rest will be
+ * allocated later by initDataAndChildObjects() */
+ mData.allocate();
+
+ /* memorize the config file name (as provided) */
+ mData->m_strConfigFile = strConfigFile;
+
+ /* get the full file name */
+ int vrc1 = mParent->i_calculateFullPath(strConfigFile, mData->m_strConfigFileFull);
+ if (RT_FAILURE(vrc1))
+ return setErrorBoth(VBOX_E_FILE_ERROR, vrc1,
+ tr("Invalid machine settings file name '%s' (%Rrc)"),
+ strConfigFile.c_str(),
+ vrc1);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ /** @todo Only create when the machine is going to be encrypted. */
+ /* Non-pageable memory is not accessible for non-VM process */
+ mData->mpKeyStore = new SecretKeyStore(false /* fKeyBufNonPageable */);
+ AssertReturn(mData->mpKeyStore, E_OUTOFMEMORY);
+#endif
+
+ LogFlowThisFuncLeave();
+
+ return rc;
+}
+
+/**
+ * Tries to create a machine settings file in the path stored in the machine
+ * instance data. Used when a new machine is created to fail gracefully if
+ * the settings file could not be written (e.g. because machine dir is read-only).
+ * @return
+ */
+HRESULT Machine::i_tryCreateMachineConfigFile(bool fForceOverwrite)
+{
+ HRESULT rc = S_OK;
+
+ // when we create a new machine, we must be able to create the settings file
+ RTFILE f = NIL_RTFILE;
+ int vrc = RTFileOpen(&f, mData->m_strConfigFileFull.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if ( RT_SUCCESS(vrc)
+ || vrc == VERR_SHARING_VIOLATION
+ )
+ {
+ if (RT_SUCCESS(vrc))
+ RTFileClose(f);
+ if (!fForceOverwrite)
+ rc = setError(VBOX_E_FILE_ERROR,
+ tr("Machine settings file '%s' already exists"),
+ mData->m_strConfigFileFull.c_str());
+ else
+ {
+ /* try to delete the config file, as otherwise the creation
+ * of a new settings file will fail. */
+ i_deleteFile(mData->m_strConfigFileFull.c_str(), false /* fIgnoreFailures */, tr("existing settings file"));
+ }
+ }
+ else if ( vrc != VERR_FILE_NOT_FOUND
+ && vrc != VERR_PATH_NOT_FOUND
+ )
+ rc = setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Invalid machine settings file name '%s' (%Rrc)"),
+ mData->m_strConfigFileFull.c_str(),
+ vrc);
+ return rc;
+}
+
+/**
+ * Initializes the registered machine by loading the settings file.
+ * This method is separated from #init() in order to make it possible to
+ * retry the operation after VirtualBox startup instead of refusing to
+ * startup the whole VirtualBox server in case if the settings file of some
+ * registered VM is invalid or inaccessible.
+ *
+ * @note Must be always called from this object's write lock
+ * (unless called from #init() that doesn't need any locking).
+ * @note Locks the mUSBController method for writing.
+ * @note Subclasses must not call this method.
+ */
+HRESULT Machine::i_registeredInit()
+{
+ AssertReturn(!i_isSessionMachine(), E_FAIL);
+ AssertReturn(!i_isSnapshotMachine(), E_FAIL);
+ AssertReturn(mData->mUuid.isValid(), E_FAIL);
+ AssertReturn(!mData->mAccessible, E_FAIL);
+
+ HRESULT rc = initDataAndChildObjects();
+
+ if (SUCCEEDED(rc))
+ {
+ /* Temporarily reset the registered flag in order to let setters
+ * potentially called from loadSettings() succeed (isMutable() used in
+ * all setters will return FALSE for a Machine instance if mRegistered
+ * is TRUE). */
+ mData->mRegistered = FALSE;
+
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ SecretKey *pKey = NULL;
+ const char *pszPassword = NULL;
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ /* Resolve password and cryptographic support interface if machine is encrypted. */
+ if (mData->mstrKeyId.isNotEmpty())
+ {
+ /* Get at the crpytographic interface. */
+ rc = mParent->i_retainCryptoIf(&pCryptoIf);
+ if (SUCCEEDED(rc))
+ {
+ int vrc = mData->mpKeyStore->retainSecretKey(mData->mstrKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ pszPassword = (const char *)pKey->getKeyBuffer();
+ else
+ rc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to retain key for key ID '%s' with %Rrc"),
+ mData->mstrKeyId.c_str(), vrc);
+ }
+ }
+#else
+ RT_NOREF(pKey);
+#endif
+
+ if (SUCCEEDED(rc))
+ {
+ try
+ {
+ // load and parse machine XML; this will throw on XML or logic errors
+ mData->pMachineConfigFile = new settings::MachineConfigFile(&mData->m_strConfigFileFull,
+ pCryptoIf, pszPassword);
+
+ if (mData->mUuid != mData->pMachineConfigFile->uuid)
+ throw setError(E_FAIL,
+ tr("Machine UUID {%RTuuid} in '%s' doesn't match its UUID {%s} in the registry file '%s'"),
+ mData->pMachineConfigFile->uuid.raw(),
+ mData->m_strConfigFileFull.c_str(),
+ mData->mUuid.toString().c_str(),
+ mParent->i_settingsFilePath().c_str());
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ // If config is encrypted, no exception is thrown allowing us to get the uuid and the encryption fields.
+ // We fill in the encryptions fields, and the rest will be filled in if all data parsed
+ mData->mstrKeyId = mData->pMachineConfigFile->strKeyId;
+ mData->mstrKeyStore = mData->pMachineConfigFile->strKeyStore;
+
+ if (mData->pMachineConfigFile->enmParseState == settings::MachineConfigFile::ParseState_PasswordError)
+ rc = setError(VBOX_E_PASSWORD_INCORRECT,
+ tr("Config decryption of the machine {%RTuuid} failed. Incorrect or unknown password"),
+ mData->pMachineConfigFile->uuid.raw());
+ else
+#endif
+ rc = i_loadMachineDataFromSettings(*mData->pMachineConfigFile,
+ NULL /* const Guid *puuidRegistry */);
+ if (FAILED(rc)) throw rc;
+ }
+ catch (HRESULT err)
+ {
+ /* we assume that error info is set by the thrower */
+ rc = err;
+ }
+ catch (...)
+ {
+ rc = VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS);
+ }
+
+ /* Restore the registered flag (even on failure) */
+ mData->mRegistered = TRUE;
+ }
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (pCryptoIf)
+ mParent->i_releaseCryptoIf(pCryptoIf);
+ if (pKey)
+ mData->mpKeyStore->releaseSecretKey(mData->mstrKeyId);
+#endif
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ /* Set mAccessible to TRUE only if we successfully locked and loaded
+ * the settings file */
+ mData->mAccessible = TRUE;
+
+ /* commit all changes made during loading the settings file */
+ i_commit(); /// @todo r=dj why do we need a commit during init?!? this is very expensive
+ /// @todo r=klaus for some reason the settings loading logic backs up
+ // the settings, and therefore a commit is needed. Should probably be changed.
+ }
+ else
+ {
+ /* If the machine is registered, then, instead of returning a
+ * failure, we mark it as inaccessible and set the result to
+ * success to give it a try later */
+
+ /* fetch the current error info */
+ mData->mAccessError = com::ErrorInfo();
+ Log1Warning(("Machine {%RTuuid} is inaccessible! [%ls]\n", mData->mUuid.raw(), mData->mAccessError.getText().raw()));
+
+ /* rollback all changes */
+ i_rollback(false /* aNotify */);
+
+ // uninit media from this machine's media registry, or else
+ // reloading the settings will fail
+ mParent->i_unregisterMachineMedia(i_getId());
+
+ /* uninitialize the common part to make sure all data is reset to
+ * default (null) values */
+ uninitDataAndChildObjects();
+
+ rc = S_OK;
+ }
+
+ return rc;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ *
+ * @note The caller of this method must make sure that this object
+ * a) doesn't have active callers on the current thread and b) is not locked
+ * by the current thread; otherwise uninit() will hang either a) due to
+ * AutoUninitSpan waiting for a number of calls to drop to zero or b) due to
+ * a dead-lock caused by this thread waiting for all callers on the other
+ * threads are done but preventing them from doing so by holding a lock.
+ */
+void Machine::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ Assert(!isWriteLockOnCurrentThread());
+
+ Assert(!uRegistryNeedsSaving);
+ if (uRegistryNeedsSaving)
+ {
+ AutoCaller autoCaller(this);
+ if (SUCCEEDED(autoCaller.rc()))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ i_saveSettings(NULL, alock, Machine::SaveS_Force);
+ }
+ }
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ Assert(!i_isSnapshotMachine());
+ Assert(!i_isSessionMachine());
+ Assert(!!mData);
+
+ LogFlowThisFunc(("initFailed()=%d\n", autoUninitSpan.initFailed()));
+ LogFlowThisFunc(("mRegistered=%d\n", mData->mRegistered));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mData->mSession.mMachine.isNull())
+ {
+ /* Theoretically, this can only happen if the VirtualBox server has been
+ * terminated while there were clients running that owned open direct
+ * sessions. Since in this case we are definitely called by
+ * VirtualBox::uninit(), we may be sure that SessionMachine::uninit()
+ * won't happen on the client watcher thread (because it has a
+ * VirtualBox caller for the duration of the
+ * SessionMachine::i_checkForDeath() call, so that VirtualBox::uninit()
+ * cannot happen until the VirtualBox caller is released). This is
+ * important, because SessionMachine::uninit() cannot correctly operate
+ * after we return from this method (it expects the Machine instance is
+ * still valid). We'll call it ourselves below.
+ */
+ Log1WarningThisFunc(("Session machine is not NULL (%p), the direct session is still open!\n",
+ (SessionMachine*)mData->mSession.mMachine));
+
+ if (Global::IsOnlineOrTransient(mData->mMachineState))
+ {
+ Log1WarningThisFunc(("Setting state to Aborted!\n"));
+ /* set machine state using SessionMachine reimplementation */
+ static_cast<Machine*>(mData->mSession.mMachine)->i_setMachineState(MachineState_Aborted);
+ }
+
+ /*
+ * Uninitialize SessionMachine using public uninit() to indicate
+ * an unexpected uninitialization.
+ */
+ mData->mSession.mMachine->uninit();
+ /* SessionMachine::uninit() must set mSession.mMachine to null */
+ Assert(mData->mSession.mMachine.isNull());
+ }
+
+ // uninit media from this machine's media registry, if they're still there
+ Guid uuidMachine(i_getId());
+
+ /* the lock is no more necessary (SessionMachine is uninitialized) */
+ alock.release();
+
+ /* XXX This will fail with
+ * "cannot be closed because it is still attached to 1 virtual machines"
+ * because at this point we did not call uninitDataAndChildObjects() yet
+ * and therefore also removeBackReference() for all these mediums was not called! */
+
+ if (uuidMachine.isValid() && !uuidMachine.isZero()) // can be empty if we're called from a failure of Machine::init
+ mParent->i_unregisterMachineMedia(uuidMachine);
+
+ // has machine been modified?
+ if (mData->flModifications)
+ {
+ Log1WarningThisFunc(("Discarding unsaved settings changes!\n"));
+ i_rollback(false /* aNotify */);
+ }
+
+ if (mData->mAccessible)
+ uninitDataAndChildObjects();
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (mData->mpKeyStore != NULL)
+ delete mData->mpKeyStore;
+#endif
+
+ /* free the essential data structure last */
+ mData.free();
+
+ LogFlowThisFuncLeave();
+}
+
+// Wrapped IMachine properties
+/////////////////////////////////////////////////////////////////////////////
+HRESULT Machine::getParent(ComPtr<IVirtualBox> &aParent)
+{
+ /* mParent is constant during life time, no need to lock */
+ ComObjPtr<VirtualBox> pVirtualBox(mParent);
+ aParent = pVirtualBox;
+
+ return S_OK;
+}
+
+
+HRESULT Machine::getAccessible(BOOL *aAccessible)
+{
+ /* In some cases (medium registry related), it is necessary to be able to
+ * go through the list of all machines. Happens when an inaccessible VM
+ * has a sensible medium registry. */
+ AutoReadLock mllock(mParent->i_getMachinesListLockHandle() COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ if (!mData->mAccessible)
+ {
+ /* try to initialize the VM once more if not accessible */
+
+ AutoReinitSpan autoReinitSpan(this);
+ AssertReturn(autoReinitSpan.isOk(), E_FAIL);
+
+#ifdef DEBUG
+ LogFlowThisFunc(("Dumping media backreferences\n"));
+ mParent->i_dumpAllBackRefs();
+#endif
+
+ if (mData->pMachineConfigFile)
+ {
+ // reset the XML file to force loadSettings() (called from i_registeredInit())
+ // to parse it again; the file might have changed
+ delete mData->pMachineConfigFile;
+ mData->pMachineConfigFile = NULL;
+ }
+
+ rc = i_registeredInit();
+
+ if (SUCCEEDED(rc) && mData->mAccessible)
+ {
+ autoReinitSpan.setSucceeded();
+
+ /* make sure interesting parties will notice the accessibility
+ * state change */
+ mParent->i_onMachineStateChanged(mData->mUuid, mData->mMachineState);
+ mParent->i_onMachineDataChanged(mData->mUuid);
+ }
+ }
+
+ if (SUCCEEDED(rc))
+ *aAccessible = mData->mAccessible;
+
+ LogFlowThisFuncLeave();
+
+ return rc;
+}
+
+HRESULT Machine::getAccessError(ComPtr<IVirtualBoxErrorInfo> &aAccessError)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->mAccessible || !mData->mAccessError.isBasicAvailable())
+ {
+ /* return shortly */
+ aAccessError = NULL;
+ return S_OK;
+ }
+
+ HRESULT rc = S_OK;
+
+ ComObjPtr<VirtualBoxErrorInfo> errorInfo;
+ rc = errorInfo.createObject();
+ if (SUCCEEDED(rc))
+ {
+ errorInfo->init(mData->mAccessError.getResultCode(),
+ mData->mAccessError.getInterfaceID().ref(),
+ Utf8Str(mData->mAccessError.getComponent()).c_str(),
+ Utf8Str(mData->mAccessError.getText()));
+ aAccessError = errorInfo;
+ }
+
+ return rc;
+}
+
+HRESULT Machine::getName(com::Utf8Str &aName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aName = mUserData->s.strName;
+
+ return S_OK;
+}
+
+HRESULT Machine::setName(const com::Utf8Str &aName)
+{
+ // prohibit setting a UUID only as the machine name, or else it can
+ // never be found by findMachine()
+ Guid test(aName);
+
+ if (test.isValid())
+ return setError(E_INVALIDARG, tr("A machine cannot have a UUID as its name"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrSavedStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mUserData.backup();
+ mUserData->s.strName = aName;
+
+ return S_OK;
+}
+
+HRESULT Machine::getDescription(com::Utf8Str &aDescription)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aDescription = mUserData->s.strDescription;
+
+ return S_OK;
+}
+
+HRESULT Machine::setDescription(const com::Utf8Str &aDescription)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // this can be done in principle in any state as it doesn't affect the VM
+ // significantly, but play safe by not messing around while complex
+ // activities are going on
+ HRESULT rc = i_checkStateDependency(MutableOrSavedOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mUserData.backup();
+ mUserData->s.strDescription = aDescription;
+
+ return S_OK;
+}
+
+HRESULT Machine::getId(com::Guid &aId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aId = mData->mUuid;
+
+ return S_OK;
+}
+
+HRESULT Machine::getGroups(std::vector<com::Utf8Str> &aGroups)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aGroups.resize(mUserData->s.llGroups.size());
+ size_t i = 0;
+ for (StringsList::const_iterator
+ it = mUserData->s.llGroups.begin();
+ it != mUserData->s.llGroups.end();
+ ++it, ++i)
+ aGroups[i] = (*it);
+
+ return S_OK;
+}
+
+HRESULT Machine::setGroups(const std::vector<com::Utf8Str> &aGroups)
+{
+ StringsList llGroups;
+ HRESULT rc = mParent->i_convertMachineGroups(aGroups, &llGroups);
+ if (FAILED(rc))
+ return rc;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ rc = i_checkStateDependency(MutableOrSavedStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mUserData.backup();
+ mUserData->s.llGroups = llGroups;
+
+ mParent->i_onMachineGroupsChanged(mData->mUuid);
+ return S_OK;
+}
+
+HRESULT Machine::getOSTypeId(com::Utf8Str &aOSTypeId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aOSTypeId = mUserData->s.strOsType;
+
+ return S_OK;
+}
+
+HRESULT Machine::setOSTypeId(const com::Utf8Str &aOSTypeId)
+{
+ /* look up the object by Id to check it is valid */
+ ComObjPtr<GuestOSType> pGuestOSType;
+ mParent->i_findGuestOSType(aOSTypeId, pGuestOSType);
+
+ /* when setting, always use the "etalon" value for consistency -- lookup
+ * by ID is case-insensitive and the input value may have different case */
+ Utf8Str osTypeId = !pGuestOSType.isNull() ? pGuestOSType->i_id() : aOSTypeId;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mUserData.backup();
+ mUserData->s.strOsType = osTypeId;
+
+ return S_OK;
+}
+
+HRESULT Machine::getFirmwareType(FirmwareType_T *aFirmwareType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aFirmwareType = mHWData->mFirmwareType;
+
+ return S_OK;
+}
+
+HRESULT Machine::setFirmwareType(FirmwareType_T aFirmwareType)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mFirmwareType = aFirmwareType;
+ Utf8Str strNVRAM = i_getDefaultNVRAMFilename();
+ alock.release();
+
+ mNvramStore->i_updateNonVolatileStorageFile(strNVRAM);
+
+ return S_OK;
+}
+
+HRESULT Machine::getKeyboardHIDType(KeyboardHIDType_T *aKeyboardHIDType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aKeyboardHIDType = mHWData->mKeyboardHIDType;
+
+ return S_OK;
+}
+
+HRESULT Machine::setKeyboardHIDType(KeyboardHIDType_T aKeyboardHIDType)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mKeyboardHIDType = aKeyboardHIDType;
+
+ return S_OK;
+}
+
+HRESULT Machine::getPointingHIDType(PointingHIDType_T *aPointingHIDType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aPointingHIDType = mHWData->mPointingHIDType;
+
+ return S_OK;
+}
+
+HRESULT Machine::setPointingHIDType(PointingHIDType_T aPointingHIDType)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mPointingHIDType = aPointingHIDType;
+
+ return S_OK;
+}
+
+HRESULT Machine::getChipsetType(ChipsetType_T *aChipsetType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aChipsetType = mHWData->mChipsetType;
+
+ return S_OK;
+}
+
+HRESULT Machine::setChipsetType(ChipsetType_T aChipsetType)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ if (aChipsetType != mHWData->mChipsetType)
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mChipsetType = aChipsetType;
+
+ // Resize network adapter array, to be finalized on commit/rollback.
+ // We must not throw away entries yet, otherwise settings are lost
+ // without a way to roll back.
+ size_t newCount = Global::getMaxNetworkAdapters(aChipsetType);
+ size_t oldCount = mNetworkAdapters.size();
+ if (newCount > oldCount)
+ {
+ mNetworkAdapters.resize(newCount);
+ for (size_t slot = oldCount; slot < mNetworkAdapters.size(); slot++)
+ {
+ unconst(mNetworkAdapters[slot]).createObject();
+ mNetworkAdapters[slot]->init(this, (ULONG)slot);
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT Machine::getIommuType(IommuType_T *aIommuType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aIommuType = mHWData->mIommuType;
+
+ return S_OK;
+}
+
+HRESULT Machine::setIommuType(IommuType_T aIommuType)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ if (aIommuType != mHWData->mIommuType)
+ {
+ if (aIommuType == IommuType_Intel)
+ {
+#ifndef VBOX_WITH_IOMMU_INTEL
+ LogRelFunc(("Setting Intel IOMMU when Intel IOMMU support not available!\n"));
+ return E_UNEXPECTED;
+#endif
+ }
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mIommuType = aIommuType;
+ }
+
+ return S_OK;
+}
+
+HRESULT Machine::getParavirtDebug(com::Utf8Str &aParavirtDebug)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aParavirtDebug = mHWData->mParavirtDebug;
+ return S_OK;
+}
+
+HRESULT Machine::setParavirtDebug(const com::Utf8Str &aParavirtDebug)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ /** @todo Parse/validate options? */
+ if (aParavirtDebug != mHWData->mParavirtDebug)
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mParavirtDebug = aParavirtDebug;
+ }
+
+ return S_OK;
+}
+
+HRESULT Machine::getParavirtProvider(ParavirtProvider_T *aParavirtProvider)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aParavirtProvider = mHWData->mParavirtProvider;
+
+ return S_OK;
+}
+
+HRESULT Machine::setParavirtProvider(ParavirtProvider_T aParavirtProvider)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ if (aParavirtProvider != mHWData->mParavirtProvider)
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mParavirtProvider = aParavirtProvider;
+ }
+
+ return S_OK;
+}
+
+HRESULT Machine::getEffectiveParavirtProvider(ParavirtProvider_T *aParavirtProvider)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aParavirtProvider = mHWData->mParavirtProvider;
+ switch (mHWData->mParavirtProvider)
+ {
+ case ParavirtProvider_None:
+ case ParavirtProvider_HyperV:
+ case ParavirtProvider_KVM:
+ case ParavirtProvider_Minimal:
+ break;
+
+ /* Resolve dynamic provider types to the effective types. */
+ default:
+ {
+ ComObjPtr<GuestOSType> pGuestOSType;
+ HRESULT hrc2 = mParent->i_findGuestOSType(mUserData->s.strOsType,
+ pGuestOSType);
+ if (FAILED(hrc2) || pGuestOSType.isNull())
+ {
+ *aParavirtProvider = ParavirtProvider_None;
+ break;
+ }
+
+ Utf8Str guestTypeFamilyId = pGuestOSType->i_familyId();
+ bool fOsXGuest = guestTypeFamilyId == "MacOS";
+
+ switch (mHWData->mParavirtProvider)
+ {
+ case ParavirtProvider_Legacy:
+ {
+ if (fOsXGuest)
+ *aParavirtProvider = ParavirtProvider_Minimal;
+ else
+ *aParavirtProvider = ParavirtProvider_None;
+ break;
+ }
+
+ case ParavirtProvider_Default:
+ {
+ if (fOsXGuest)
+ *aParavirtProvider = ParavirtProvider_Minimal;
+ else if ( mUserData->s.strOsType == "Windows11_64"
+ || mUserData->s.strOsType == "Windows10"
+ || mUserData->s.strOsType == "Windows10_64"
+ || mUserData->s.strOsType == "Windows81"
+ || mUserData->s.strOsType == "Windows81_64"
+ || mUserData->s.strOsType == "Windows8"
+ || mUserData->s.strOsType == "Windows8_64"
+ || mUserData->s.strOsType == "Windows7"
+ || mUserData->s.strOsType == "Windows7_64"
+ || mUserData->s.strOsType == "WindowsVista"
+ || mUserData->s.strOsType == "WindowsVista_64"
+ || ( ( mUserData->s.strOsType.startsWith("Windows202")
+ || mUserData->s.strOsType.startsWith("Windows201"))
+ && mUserData->s.strOsType.endsWith("_64"))
+ || mUserData->s.strOsType == "Windows2012"
+ || mUserData->s.strOsType == "Windows2012_64"
+ || mUserData->s.strOsType == "Windows2008"
+ || mUserData->s.strOsType == "Windows2008_64")
+ {
+ *aParavirtProvider = ParavirtProvider_HyperV;
+ }
+ else if (guestTypeFamilyId == "Linux" &&
+ mUserData->s.strOsType != "Linux22" && // Linux22 and Linux24{_64} excluded as they're too old
+ mUserData->s.strOsType != "Linux24" && // to have any KVM paravirtualization support.
+ mUserData->s.strOsType != "Linux24_64")
+ {
+ *aParavirtProvider = ParavirtProvider_KVM;
+ }
+ else
+ *aParavirtProvider = ParavirtProvider_None;
+ break;
+ }
+
+ default: AssertFailedBreak(); /* Shut up MSC. */
+ }
+ break;
+ }
+ }
+
+ Assert( *aParavirtProvider == ParavirtProvider_None
+ || *aParavirtProvider == ParavirtProvider_Minimal
+ || *aParavirtProvider == ParavirtProvider_HyperV
+ || *aParavirtProvider == ParavirtProvider_KVM);
+ return S_OK;
+}
+
+HRESULT Machine::getHardwareVersion(com::Utf8Str &aHardwareVersion)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aHardwareVersion = mHWData->mHWVersion;
+
+ return S_OK;
+}
+
+HRESULT Machine::setHardwareVersion(const com::Utf8Str &aHardwareVersion)
+{
+ /* check known version */
+ Utf8Str hwVersion = aHardwareVersion;
+ if ( hwVersion.compare("1") != 0
+ && hwVersion.compare("2") != 0) // VBox 2.1.x and later (VMMDev heap)
+ return setError(E_INVALIDARG,
+ tr("Invalid hardware version: %s\n"), aHardwareVersion.c_str());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mHWVersion = aHardwareVersion;
+
+ return S_OK;
+}
+
+HRESULT Machine::getHardwareUUID(com::Guid &aHardwareUUID)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mHWData->mHardwareUUID.isZero())
+ aHardwareUUID = mHWData->mHardwareUUID;
+ else
+ aHardwareUUID = mData->mUuid;
+
+ return S_OK;
+}
+
+HRESULT Machine::setHardwareUUID(const com::Guid &aHardwareUUID)
+{
+ if (!aHardwareUUID.isValid())
+ return E_INVALIDARG;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ if (aHardwareUUID == mData->mUuid)
+ mHWData->mHardwareUUID.clear();
+ else
+ mHWData->mHardwareUUID = aHardwareUUID;
+
+ return S_OK;
+}
+
+HRESULT Machine::getMemorySize(ULONG *aMemorySize)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aMemorySize = mHWData->mMemorySize;
+
+ return S_OK;
+}
+
+HRESULT Machine::setMemorySize(ULONG aMemorySize)
+{
+ /* check RAM limits */
+ if ( aMemorySize < MM_RAM_MIN_IN_MB
+ || aMemorySize > MM_RAM_MAX_IN_MB
+ )
+ return setError(E_INVALIDARG,
+ tr("Invalid RAM size: %lu MB (must be in range [%lu, %lu] MB)"),
+ aMemorySize, MM_RAM_MIN_IN_MB, MM_RAM_MAX_IN_MB);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mMemorySize = aMemorySize;
+
+ return S_OK;
+}
+
+HRESULT Machine::getCPUCount(ULONG *aCPUCount)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCPUCount = mHWData->mCPUCount;
+
+ return S_OK;
+}
+
+HRESULT Machine::setCPUCount(ULONG aCPUCount)
+{
+ /* check CPU limits */
+ if ( aCPUCount < SchemaDefs::MinCPUCount
+ || aCPUCount > SchemaDefs::MaxCPUCount
+ )
+ return setError(E_INVALIDARG,
+ tr("Invalid virtual CPU count: %lu (must be in range [%lu, %lu])"),
+ aCPUCount, SchemaDefs::MinCPUCount, SchemaDefs::MaxCPUCount);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* We cant go below the current number of CPUs attached if hotplug is enabled*/
+ if (mHWData->mCPUHotPlugEnabled)
+ {
+ for (unsigned idx = aCPUCount; idx < SchemaDefs::MaxCPUCount; idx++)
+ {
+ if (mHWData->mCPUAttached[idx])
+ return setError(E_INVALIDARG,
+ tr("There is still a CPU attached to socket %lu."
+ "Detach the CPU before removing the socket"),
+ aCPUCount, idx+1);
+ }
+ }
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mCPUCount = aCPUCount;
+
+ return S_OK;
+}
+
+HRESULT Machine::getCPUExecutionCap(ULONG *aCPUExecutionCap)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCPUExecutionCap = mHWData->mCpuExecutionCap;
+
+ return S_OK;
+}
+
+HRESULT Machine::setCPUExecutionCap(ULONG aCPUExecutionCap)
+{
+ HRESULT rc = S_OK;
+
+ /* check throttle limits */
+ if ( aCPUExecutionCap < 1
+ || aCPUExecutionCap > 100
+ )
+ return setError(E_INVALIDARG,
+ tr("Invalid CPU execution cap value: %lu (must be in range [%lu, %lu])"),
+ aCPUExecutionCap, 1, 100);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ rc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ alock.release();
+ rc = i_onCPUExecutionCapChange(aCPUExecutionCap);
+ alock.acquire();
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mCpuExecutionCap = aCPUExecutionCap;
+
+ /** Save settings if online - @todo why is this required? -- @bugref{6818} */
+ if (Global::IsOnline(mData->mMachineState))
+ i_saveSettings(NULL, alock);
+
+ return S_OK;
+}
+
+HRESULT Machine::getCPUHotPlugEnabled(BOOL *aCPUHotPlugEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCPUHotPlugEnabled = mHWData->mCPUHotPlugEnabled;
+
+ return S_OK;
+}
+
+HRESULT Machine::setCPUHotPlugEnabled(BOOL aCPUHotPlugEnabled)
+{
+ HRESULT rc = S_OK;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ if (mHWData->mCPUHotPlugEnabled != aCPUHotPlugEnabled)
+ {
+ if (aCPUHotPlugEnabled)
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+
+ /* Add the amount of CPUs currently attached */
+ for (unsigned i = 0; i < mHWData->mCPUCount; ++i)
+ mHWData->mCPUAttached[i] = true;
+ }
+ else
+ {
+ /*
+ * We can disable hotplug only if the amount of maximum CPUs is equal
+ * to the amount of attached CPUs
+ */
+ unsigned cCpusAttached = 0;
+ unsigned iHighestId = 0;
+
+ for (unsigned i = 0; i < SchemaDefs::MaxCPUCount; ++i)
+ {
+ if (mHWData->mCPUAttached[i])
+ {
+ cCpusAttached++;
+ iHighestId = i;
+ }
+ }
+
+ if ( (cCpusAttached != mHWData->mCPUCount)
+ || (iHighestId >= mHWData->mCPUCount))
+ return setError(E_INVALIDARG,
+ tr("CPU hotplugging can't be disabled because the maximum number of CPUs is not equal to the amount of CPUs attached"));
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ }
+ }
+
+ mHWData->mCPUHotPlugEnabled = aCPUHotPlugEnabled;
+
+ return rc;
+}
+
+HRESULT Machine::getCPUIDPortabilityLevel(ULONG *aCPUIDPortabilityLevel)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCPUIDPortabilityLevel = mHWData->mCpuIdPortabilityLevel;
+
+ return S_OK;
+}
+
+HRESULT Machine::setCPUIDPortabilityLevel(ULONG aCPUIDPortabilityLevel)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = i_checkStateDependency(MutableStateDep);
+ if (SUCCEEDED(hrc))
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mCpuIdPortabilityLevel = aCPUIDPortabilityLevel;
+ }
+ return hrc;
+}
+
+HRESULT Machine::getCPUProfile(com::Utf8Str &aCPUProfile)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aCPUProfile = mHWData->mCpuProfile;
+ return S_OK;
+}
+
+HRESULT Machine::setCPUProfile(const com::Utf8Str &aCPUProfile)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = i_checkStateDependency(MutableStateDep);
+ if (SUCCEEDED(hrc))
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ /* Empty equals 'host'. */
+ if (aCPUProfile.isNotEmpty())
+ mHWData->mCpuProfile = aCPUProfile;
+ else
+ mHWData->mCpuProfile = "host";
+ }
+ return hrc;
+}
+
+HRESULT Machine::getEmulatedUSBCardReaderEnabled(BOOL *aEmulatedUSBCardReaderEnabled)
+{
+#ifdef VBOX_WITH_USB_CARDREADER
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aEmulatedUSBCardReaderEnabled = mHWData->mEmulatedUSBCardReaderEnabled;
+
+ return S_OK;
+#else
+ NOREF(aEmulatedUSBCardReaderEnabled);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Machine::setEmulatedUSBCardReaderEnabled(BOOL aEmulatedUSBCardReaderEnabled)
+{
+#ifdef VBOX_WITH_USB_CARDREADER
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrSavedStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mEmulatedUSBCardReaderEnabled = aEmulatedUSBCardReaderEnabled;
+
+ return S_OK;
+#else
+ NOREF(aEmulatedUSBCardReaderEnabled);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Machine::getHPETEnabled(BOOL *aHPETEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aHPETEnabled = mHWData->mHPETEnabled;
+
+ return S_OK;
+}
+
+HRESULT Machine::setHPETEnabled(BOOL aHPETEnabled)
+{
+ HRESULT rc = S_OK;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+
+ mHWData->mHPETEnabled = aHPETEnabled;
+
+ return rc;
+}
+
+/** @todo this method should not be public */
+HRESULT Machine::getMemoryBalloonSize(ULONG *aMemoryBalloonSize)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aMemoryBalloonSize = mHWData->mMemoryBalloonSize;
+
+ return S_OK;
+}
+
+/**
+ * Set the memory balloon size.
+ *
+ * This method is also called from IGuest::COMSETTER(MemoryBalloonSize) so
+ * we have to make sure that we never call IGuest from here.
+ */
+HRESULT Machine::setMemoryBalloonSize(ULONG aMemoryBalloonSize)
+{
+ /* This must match GMMR0Init; currently we only support memory ballooning on all 64-bit hosts except Mac OS X */
+#if HC_ARCH_BITS == 64 && (defined(RT_OS_WINDOWS) || defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD))
+ /* check limits */
+ if (aMemoryBalloonSize >= VMMDEV_MAX_MEMORY_BALLOON(mHWData->mMemorySize))
+ return setError(E_INVALIDARG,
+ tr("Invalid memory balloon size: %lu MB (must be in range [%lu, %lu] MB)"),
+ aMemoryBalloonSize, 0, VMMDEV_MAX_MEMORY_BALLOON(mHWData->mMemorySize));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mMemoryBalloonSize = aMemoryBalloonSize;
+
+ return S_OK;
+#else
+ NOREF(aMemoryBalloonSize);
+ return setError(E_NOTIMPL, tr("Memory ballooning is only supported on 64-bit hosts"));
+#endif
+}
+
+HRESULT Machine::getPageFusionEnabled(BOOL *aPageFusionEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aPageFusionEnabled = mHWData->mPageFusionEnabled;
+ return S_OK;
+}
+
+HRESULT Machine::setPageFusionEnabled(BOOL aPageFusionEnabled)
+{
+#ifdef VBOX_WITH_PAGE_SHARING
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ /** @todo must support changes for running vms and keep this in sync with IGuest. */
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mPageFusionEnabled = aPageFusionEnabled;
+ return S_OK;
+#else
+ NOREF(aPageFusionEnabled);
+ return setError(E_NOTIMPL, tr("Page fusion is only supported on 64-bit hosts"));
+#endif
+}
+
+HRESULT Machine::getBIOSSettings(ComPtr<IBIOSSettings> &aBIOSSettings)
+{
+ /* mBIOSSettings is constant during life time, no need to lock */
+ aBIOSSettings = mBIOSSettings;
+
+ return S_OK;
+}
+
+HRESULT Machine::getTrustedPlatformModule(ComPtr<ITrustedPlatformModule> &aTrustedPlatformModule)
+{
+ /* mTrustedPlatformModule is constant during life time, no need to lock */
+ aTrustedPlatformModule = mTrustedPlatformModule;
+
+ return S_OK;
+}
+
+HRESULT Machine::getNonVolatileStore(ComPtr<INvramStore> &aNvramStore)
+{
+ /* mNvramStore is constant during life time, no need to lock */
+ aNvramStore = mNvramStore;
+
+ return S_OK;
+}
+
+HRESULT Machine::getRecordingSettings(ComPtr<IRecordingSettings> &aRecordingSettings)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aRecordingSettings = mRecordingSettings;
+
+ return S_OK;
+}
+
+HRESULT Machine::getGraphicsAdapter(ComPtr<IGraphicsAdapter> &aGraphicsAdapter)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aGraphicsAdapter = mGraphicsAdapter;
+
+ return S_OK;
+}
+
+HRESULT Machine::getCPUProperty(CPUPropertyType_T aProperty, BOOL *aValue)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ switch (aProperty)
+ {
+ case CPUPropertyType_PAE:
+ *aValue = mHWData->mPAEEnabled;
+ break;
+
+ case CPUPropertyType_LongMode:
+ if (mHWData->mLongMode == settings::Hardware::LongMode_Enabled)
+ *aValue = TRUE;
+ else if (mHWData->mLongMode == settings::Hardware::LongMode_Disabled)
+ *aValue = FALSE;
+#if HC_ARCH_BITS == 64
+ else
+ *aValue = TRUE;
+#else
+ else
+ {
+ *aValue = FALSE;
+
+ ComObjPtr<GuestOSType> pGuestOSType;
+ HRESULT hrc2 = mParent->i_findGuestOSType(mUserData->s.strOsType,
+ pGuestOSType);
+ if (SUCCEEDED(hrc2) && !pGuestOSType.isNull())
+ {
+ if (pGuestOSType->i_is64Bit())
+ {
+ ComObjPtr<Host> pHost = mParent->i_host();
+ alock.release();
+
+ hrc2 = pHost->GetProcessorFeature(ProcessorFeature_LongMode, aValue); AssertComRC(hrc2);
+ if (FAILED(hrc2))
+ *aValue = FALSE;
+ }
+ }
+ }
+#endif
+ break;
+
+ case CPUPropertyType_TripleFaultReset:
+ *aValue = mHWData->mTripleFaultReset;
+ break;
+
+ case CPUPropertyType_APIC:
+ *aValue = mHWData->mAPIC;
+ break;
+
+ case CPUPropertyType_X2APIC:
+ *aValue = mHWData->mX2APIC;
+ break;
+
+ case CPUPropertyType_IBPBOnVMExit:
+ *aValue = mHWData->mIBPBOnVMExit;
+ break;
+
+ case CPUPropertyType_IBPBOnVMEntry:
+ *aValue = mHWData->mIBPBOnVMEntry;
+ break;
+
+ case CPUPropertyType_SpecCtrl:
+ *aValue = mHWData->mSpecCtrl;
+ break;
+
+ case CPUPropertyType_SpecCtrlByHost:
+ *aValue = mHWData->mSpecCtrlByHost;
+ break;
+
+ case CPUPropertyType_HWVirt:
+ *aValue = mHWData->mNestedHWVirt;
+ break;
+
+ case CPUPropertyType_L1DFlushOnEMTScheduling:
+ *aValue = mHWData->mL1DFlushOnSched;
+ break;
+
+ case CPUPropertyType_L1DFlushOnVMEntry:
+ *aValue = mHWData->mL1DFlushOnVMEntry;
+ break;
+
+ case CPUPropertyType_MDSClearOnEMTScheduling:
+ *aValue = mHWData->mMDSClearOnSched;
+ break;
+
+ case CPUPropertyType_MDSClearOnVMEntry:
+ *aValue = mHWData->mMDSClearOnVMEntry;
+ break;
+
+ default:
+ return E_INVALIDARG;
+ }
+ return S_OK;
+}
+
+HRESULT Machine::setCPUProperty(CPUPropertyType_T aProperty, BOOL aValue)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ switch (aProperty)
+ {
+ case CPUPropertyType_PAE:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mPAEEnabled = !!aValue;
+ break;
+
+ case CPUPropertyType_LongMode:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mLongMode = !aValue ? settings::Hardware::LongMode_Disabled : settings::Hardware::LongMode_Enabled;
+ break;
+
+ case CPUPropertyType_TripleFaultReset:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mTripleFaultReset = !!aValue;
+ break;
+
+ case CPUPropertyType_APIC:
+ if (mHWData->mX2APIC)
+ aValue = TRUE;
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mAPIC = !!aValue;
+ break;
+
+ case CPUPropertyType_X2APIC:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mX2APIC = !!aValue;
+ if (aValue)
+ mHWData->mAPIC = !!aValue;
+ break;
+
+ case CPUPropertyType_IBPBOnVMExit:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mIBPBOnVMExit = !!aValue;
+ break;
+
+ case CPUPropertyType_IBPBOnVMEntry:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mIBPBOnVMEntry = !!aValue;
+ break;
+
+ case CPUPropertyType_SpecCtrl:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mSpecCtrl = !!aValue;
+ break;
+
+ case CPUPropertyType_SpecCtrlByHost:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mSpecCtrlByHost = !!aValue;
+ break;
+
+ case CPUPropertyType_HWVirt:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mNestedHWVirt = !!aValue;
+ break;
+
+ case CPUPropertyType_L1DFlushOnEMTScheduling:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mL1DFlushOnSched = !!aValue;
+ break;
+
+ case CPUPropertyType_L1DFlushOnVMEntry:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mL1DFlushOnVMEntry = !!aValue;
+ break;
+
+ case CPUPropertyType_MDSClearOnEMTScheduling:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mMDSClearOnSched = !!aValue;
+ break;
+
+ case CPUPropertyType_MDSClearOnVMEntry:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mMDSClearOnVMEntry = !!aValue;
+ break;
+
+ default:
+ return E_INVALIDARG;
+ }
+ return S_OK;
+}
+
+HRESULT Machine::getCPUIDLeafByOrdinal(ULONG aOrdinal, ULONG *aIdx, ULONG *aSubIdx, ULONG *aValEax, ULONG *aValEbx,
+ ULONG *aValEcx, ULONG *aValEdx)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aOrdinal < mHWData->mCpuIdLeafList.size())
+ {
+ for (settings::CpuIdLeafsList::const_iterator it = mHWData->mCpuIdLeafList.begin();
+ it != mHWData->mCpuIdLeafList.end();
+ ++it)
+ {
+ if (aOrdinal == 0)
+ {
+ const settings::CpuIdLeaf &rLeaf= *it;
+ *aIdx = rLeaf.idx;
+ *aSubIdx = rLeaf.idxSub;
+ *aValEax = rLeaf.uEax;
+ *aValEbx = rLeaf.uEbx;
+ *aValEcx = rLeaf.uEcx;
+ *aValEdx = rLeaf.uEdx;
+ return S_OK;
+ }
+ aOrdinal--;
+ }
+ }
+ return E_INVALIDARG;
+}
+
+HRESULT Machine::getCPUIDLeaf(ULONG aIdx, ULONG aSubIdx, ULONG *aValEax, ULONG *aValEbx, ULONG *aValEcx, ULONG *aValEdx)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Search the list.
+ */
+ for (settings::CpuIdLeafsList::const_iterator it = mHWData->mCpuIdLeafList.begin(); it != mHWData->mCpuIdLeafList.end(); ++it)
+ {
+ const settings::CpuIdLeaf &rLeaf= *it;
+ if ( rLeaf.idx == aIdx
+ && ( aSubIdx == UINT32_MAX
+ || rLeaf.idxSub == aSubIdx) )
+ {
+ *aValEax = rLeaf.uEax;
+ *aValEbx = rLeaf.uEbx;
+ *aValEcx = rLeaf.uEcx;
+ *aValEdx = rLeaf.uEdx;
+ return S_OK;
+ }
+ }
+
+ return E_INVALIDARG;
+}
+
+
+HRESULT Machine::setCPUIDLeaf(ULONG aIdx, ULONG aSubIdx, ULONG aValEax, ULONG aValEbx, ULONG aValEcx, ULONG aValEdx)
+{
+ /*
+ * Validate input before taking locks and checking state.
+ */
+ if (aSubIdx != 0 && aSubIdx != UINT32_MAX)
+ return setError(E_INVALIDARG, tr("Currently only aSubIdx values 0 and 0xffffffff are supported: %#x"), aSubIdx);
+ if ( aIdx >= UINT32_C(0x20)
+ && aIdx - UINT32_C(0x80000000) >= UINT32_C(0x20)
+ && aIdx - UINT32_C(0xc0000000) >= UINT32_C(0x10) )
+ return setError(E_INVALIDARG, tr("CpuId override leaf %#x is out of range"), aIdx);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ /*
+ * Impose a maximum number of leaves.
+ */
+ if (mHWData->mCpuIdLeafList.size() > 256)
+ return setError(E_FAIL, tr("Max of 256 CPUID override leaves reached"));
+
+ /*
+ * Updating the list is a bit more complicated. So, let's do a remove first followed by an insert.
+ */
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+
+ for (settings::CpuIdLeafsList::iterator it = mHWData->mCpuIdLeafList.begin(); it != mHWData->mCpuIdLeafList.end(); )
+ {
+ settings::CpuIdLeaf &rLeaf= *it;
+ if ( rLeaf.idx == aIdx
+ && ( aSubIdx == UINT32_MAX
+ || rLeaf.idxSub == aSubIdx) )
+ it = mHWData->mCpuIdLeafList.erase(it);
+ else
+ ++it;
+ }
+
+ settings::CpuIdLeaf NewLeaf;
+ NewLeaf.idx = aIdx;
+ NewLeaf.idxSub = aSubIdx == UINT32_MAX ? 0 : aSubIdx;
+ NewLeaf.uEax = aValEax;
+ NewLeaf.uEbx = aValEbx;
+ NewLeaf.uEcx = aValEcx;
+ NewLeaf.uEdx = aValEdx;
+ mHWData->mCpuIdLeafList.push_back(NewLeaf);
+ return S_OK;
+}
+
+HRESULT Machine::removeCPUIDLeaf(ULONG aIdx, ULONG aSubIdx)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ /*
+ * Do the removal.
+ */
+ bool fModified = mHWData.isBackedUp();
+ for (settings::CpuIdLeafsList::iterator it = mHWData->mCpuIdLeafList.begin(); it != mHWData->mCpuIdLeafList.end(); )
+ {
+ settings::CpuIdLeaf &rLeaf= *it;
+ if ( rLeaf.idx == aIdx
+ && ( aSubIdx == UINT32_MAX
+ || rLeaf.idxSub == aSubIdx) )
+ {
+ if (!fModified)
+ {
+ fModified = true;
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ // Start from the beginning, since mHWData.backup() creates
+ // a new list, causing iterator mixup. This makes sure that
+ // the settings are not unnecessarily marked as modified,
+ // at the price of extra list walking.
+ it = mHWData->mCpuIdLeafList.begin();
+ }
+ else
+ it = mHWData->mCpuIdLeafList.erase(it);
+ }
+ else
+ ++it;
+ }
+
+ return S_OK;
+}
+
+HRESULT Machine::removeAllCPUIDLeaves()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ if (mHWData->mCpuIdLeafList.size() > 0)
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+
+ mHWData->mCpuIdLeafList.clear();
+ }
+
+ return S_OK;
+}
+HRESULT Machine::getHWVirtExProperty(HWVirtExPropertyType_T aProperty, BOOL *aValue)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ switch(aProperty)
+ {
+ case HWVirtExPropertyType_Enabled:
+ *aValue = mHWData->mHWVirtExEnabled;
+ break;
+
+ case HWVirtExPropertyType_VPID:
+ *aValue = mHWData->mHWVirtExVPIDEnabled;
+ break;
+
+ case HWVirtExPropertyType_NestedPaging:
+ *aValue = mHWData->mHWVirtExNestedPagingEnabled;
+ break;
+
+ case HWVirtExPropertyType_UnrestrictedExecution:
+ *aValue = mHWData->mHWVirtExUXEnabled;
+ break;
+
+ case HWVirtExPropertyType_LargePages:
+ *aValue = mHWData->mHWVirtExLargePagesEnabled;
+ break;
+
+ case HWVirtExPropertyType_Force:
+ *aValue = mHWData->mHWVirtExForceEnabled;
+ break;
+
+ case HWVirtExPropertyType_UseNativeApi:
+ *aValue = mHWData->mHWVirtExUseNativeApi;
+ break;
+
+ case HWVirtExPropertyType_VirtVmsaveVmload:
+ *aValue = mHWData->mHWVirtExVirtVmsaveVmload;
+ break;
+
+ default:
+ return E_INVALIDARG;
+ }
+ return S_OK;
+}
+
+HRESULT Machine::setHWVirtExProperty(HWVirtExPropertyType_T aProperty, BOOL aValue)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ switch (aProperty)
+ {
+ case HWVirtExPropertyType_Enabled:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mHWVirtExEnabled = !!aValue;
+ break;
+
+ case HWVirtExPropertyType_VPID:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mHWVirtExVPIDEnabled = !!aValue;
+ break;
+
+ case HWVirtExPropertyType_NestedPaging:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mHWVirtExNestedPagingEnabled = !!aValue;
+ break;
+
+ case HWVirtExPropertyType_UnrestrictedExecution:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mHWVirtExUXEnabled = !!aValue;
+ break;
+
+ case HWVirtExPropertyType_LargePages:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mHWVirtExLargePagesEnabled = !!aValue;
+ break;
+
+ case HWVirtExPropertyType_Force:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mHWVirtExForceEnabled = !!aValue;
+ break;
+
+ case HWVirtExPropertyType_UseNativeApi:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mHWVirtExUseNativeApi = !!aValue;
+ break;
+
+ case HWVirtExPropertyType_VirtVmsaveVmload:
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mHWVirtExVirtVmsaveVmload = !!aValue;
+ break;
+
+ default:
+ return E_INVALIDARG;
+ }
+
+ return S_OK;
+}
+
+HRESULT Machine::getSnapshotFolder(com::Utf8Str &aSnapshotFolder)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ i_calculateFullPath(mUserData->s.strSnapshotFolder, aSnapshotFolder);
+
+ return S_OK;
+}
+
+HRESULT Machine::setSnapshotFolder(const com::Utf8Str &aSnapshotFolder)
+{
+ /** @todo (r=dmik):
+ * 1. Allow to change the name of the snapshot folder containing snapshots
+ * 2. Rename the folder on disk instead of just changing the property
+ * value (to be smart and not to leave garbage). Note that it cannot be
+ * done here because the change may be rolled back. Thus, the right
+ * place is #saveSettings().
+ */
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ if (!mData->mCurrentSnapshot.isNull())
+ return setError(E_FAIL,
+ tr("The snapshot folder of a machine with snapshots cannot be changed (please delete all snapshots first)"));
+
+ Utf8Str strSnapshotFolder(aSnapshotFolder); // keep original
+
+ if (strSnapshotFolder.isEmpty())
+ strSnapshotFolder = "Snapshots";
+ int vrc = i_calculateFullPath(strSnapshotFolder, strSnapshotFolder);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc,
+ tr("Invalid snapshot folder '%s' (%Rrc)"),
+ strSnapshotFolder.c_str(), vrc);
+
+ i_setModified(IsModified_MachineData);
+ mUserData.backup();
+
+ i_copyPathRelativeToMachine(strSnapshotFolder, mUserData->s.strSnapshotFolder);
+
+ return S_OK;
+}
+
+HRESULT Machine::getMediumAttachments(std::vector<ComPtr<IMediumAttachment> > &aMediumAttachments)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aMediumAttachments.resize(mMediumAttachments->size());
+ size_t i = 0;
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it, ++i)
+ aMediumAttachments[i] = *it;
+
+ return S_OK;
+}
+
+HRESULT Machine::getVRDEServer(ComPtr<IVRDEServer> &aVRDEServer)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Assert(!!mVRDEServer);
+
+ aVRDEServer = mVRDEServer;
+
+ return S_OK;
+}
+
+HRESULT Machine::getAudioSettings(ComPtr<IAudioSettings> &aAudioSettings)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aAudioSettings = mAudioSettings;
+
+ return S_OK;
+}
+
+HRESULT Machine::getUSBControllers(std::vector<ComPtr<IUSBController> > &aUSBControllers)
+{
+#ifdef VBOX_WITH_VUSB
+ clearError();
+ MultiResult rc(S_OK);
+
+# ifdef VBOX_WITH_USB
+ rc = mParent->i_host()->i_checkUSBProxyService();
+ if (FAILED(rc)) return rc;
+# endif
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aUSBControllers.resize(mUSBControllers->size());
+ size_t i = 0;
+ for (USBControllerList::const_iterator
+ it = mUSBControllers->begin();
+ it != mUSBControllers->end();
+ ++it, ++i)
+ aUSBControllers[i] = *it;
+
+ return S_OK;
+#else
+ /* Note: The GUI depends on this method returning E_NOTIMPL with no
+ * extended error info to indicate that USB is simply not available
+ * (w/o treating it as a failure), for example, as in OSE */
+ NOREF(aUSBControllers);
+ ReturnComNotImplemented();
+#endif /* VBOX_WITH_VUSB */
+}
+
+HRESULT Machine::getUSBDeviceFilters(ComPtr<IUSBDeviceFilters> &aUSBDeviceFilters)
+{
+#ifdef VBOX_WITH_VUSB
+ clearError();
+ MultiResult rc(S_OK);
+
+# ifdef VBOX_WITH_USB
+ rc = mParent->i_host()->i_checkUSBProxyService();
+ if (FAILED(rc)) return rc;
+# endif
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aUSBDeviceFilters = mUSBDeviceFilters;
+ return rc;
+#else
+ /* Note: The GUI depends on this method returning E_NOTIMPL with no
+ * extended error info to indicate that USB is simply not available
+ * (w/o treating it as a failure), for example, as in OSE */
+ NOREF(aUSBDeviceFilters);
+ ReturnComNotImplemented();
+#endif /* VBOX_WITH_VUSB */
+}
+
+HRESULT Machine::getSettingsFilePath(com::Utf8Str &aSettingsFilePath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aSettingsFilePath = mData->m_strConfigFileFull;
+
+ return S_OK;
+}
+
+HRESULT Machine::getSettingsAuxFilePath(com::Utf8Str &aSettingsFilePath)
+{
+ RT_NOREF(aSettingsFilePath);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::getSettingsModified(BOOL *aSettingsModified)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrSavedOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ if (!mData->pMachineConfigFile->fileExists())
+ // this is a new machine, and no config file exists yet:
+ *aSettingsModified = TRUE;
+ else
+ *aSettingsModified = (mData->flModifications != 0);
+
+ return S_OK;
+}
+
+HRESULT Machine::getSessionState(SessionState_T *aSessionState)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aSessionState = mData->mSession.mState;
+
+ return S_OK;
+}
+
+HRESULT Machine::getSessionName(com::Utf8Str &aSessionName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aSessionName = mData->mSession.mName;
+
+ return S_OK;
+}
+
+HRESULT Machine::getSessionPID(ULONG *aSessionPID)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aSessionPID = mData->mSession.mPID;
+
+ return S_OK;
+}
+
+HRESULT Machine::getState(MachineState_T *aState)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aState = mData->mMachineState;
+ Assert(mData->mMachineState != MachineState_Null);
+
+ return S_OK;
+}
+
+HRESULT Machine::getLastStateChange(LONG64 *aLastStateChange)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aLastStateChange = RTTimeSpecGetMilli(&mData->mLastStateChange);
+
+ return S_OK;
+}
+
+HRESULT Machine::getStateFilePath(com::Utf8Str &aStateFilePath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aStateFilePath = mSSData->strStateFilePath;
+
+ return S_OK;
+}
+
+HRESULT Machine::getLogFolder(com::Utf8Str &aLogFolder)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ i_getLogFolder(aLogFolder);
+
+ return S_OK;
+}
+
+HRESULT Machine::getCurrentSnapshot(ComPtr<ISnapshot> &aCurrentSnapshot)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aCurrentSnapshot = mData->mCurrentSnapshot;
+
+ return S_OK;
+}
+
+HRESULT Machine::getSnapshotCount(ULONG *aSnapshotCount)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aSnapshotCount = mData->mFirstSnapshot.isNull()
+ ? 0
+ : mData->mFirstSnapshot->i_getAllChildrenCount() + 1;
+
+ return S_OK;
+}
+
+HRESULT Machine::getCurrentStateModified(BOOL *aCurrentStateModified)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Note: for machines with no snapshots, we always return FALSE
+ * (mData->mCurrentStateModified will be TRUE in this case, for historical
+ * reasons :) */
+
+ *aCurrentStateModified = mData->mFirstSnapshot.isNull()
+ ? FALSE
+ : mData->mCurrentStateModified;
+
+ return S_OK;
+}
+
+HRESULT Machine::getSharedFolders(std::vector<ComPtr<ISharedFolder> > &aSharedFolders)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aSharedFolders.resize(mHWData->mSharedFolders.size());
+ size_t i = 0;
+ for (std::list<ComObjPtr<SharedFolder> >::const_iterator
+ it = mHWData->mSharedFolders.begin();
+ it != mHWData->mSharedFolders.end();
+ ++it, ++i)
+ aSharedFolders[i] = *it;
+
+ return S_OK;
+}
+
+HRESULT Machine::getClipboardMode(ClipboardMode_T *aClipboardMode)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aClipboardMode = mHWData->mClipboardMode;
+
+ return S_OK;
+}
+
+HRESULT Machine::setClipboardMode(ClipboardMode_T aClipboardMode)
+{
+ HRESULT rc = S_OK;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ rc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ alock.release();
+ rc = i_onClipboardModeChange(aClipboardMode);
+ alock.acquire();
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mClipboardMode = aClipboardMode;
+
+ /** Save settings if online - @todo why is this required? -- @bugref{6818} */
+ if (Global::IsOnline(mData->mMachineState))
+ i_saveSettings(NULL, alock);
+
+ return S_OK;
+}
+
+HRESULT Machine::getClipboardFileTransfersEnabled(BOOL *aEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aEnabled = mHWData->mClipboardFileTransfersEnabled;
+
+ return S_OK;
+}
+
+HRESULT Machine::setClipboardFileTransfersEnabled(BOOL aEnabled)
+{
+ HRESULT rc = S_OK;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ rc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ alock.release();
+ rc = i_onClipboardFileTransferModeChange(aEnabled);
+ alock.acquire();
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mClipboardFileTransfersEnabled = aEnabled;
+
+ /** Save settings if online - @todo why is this required? -- @bugref{6818} */
+ if (Global::IsOnline(mData->mMachineState))
+ i_saveSettings(NULL, alock);
+
+ return S_OK;
+}
+
+HRESULT Machine::getDnDMode(DnDMode_T *aDnDMode)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aDnDMode = mHWData->mDnDMode;
+
+ return S_OK;
+}
+
+HRESULT Machine::setDnDMode(DnDMode_T aDnDMode)
+{
+ HRESULT rc = S_OK;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ rc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ alock.release();
+ rc = i_onDnDModeChange(aDnDMode);
+
+ alock.acquire();
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mDnDMode = aDnDMode;
+
+ /** Save settings if online - @todo why is this required? -- @bugref{6818} */
+ if (Global::IsOnline(mData->mMachineState))
+ i_saveSettings(NULL, alock);
+
+ return S_OK;
+}
+
+HRESULT Machine::getStorageControllers(std::vector<ComPtr<IStorageController> > &aStorageControllers)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aStorageControllers.resize(mStorageControllers->size());
+ size_t i = 0;
+ for (StorageControllerList::const_iterator
+ it = mStorageControllers->begin();
+ it != mStorageControllers->end();
+ ++it, ++i)
+ aStorageControllers[i] = *it;
+
+ return S_OK;
+}
+
+HRESULT Machine::getTeleporterEnabled(BOOL *aEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aEnabled = mUserData->s.fTeleporterEnabled;
+
+ return S_OK;
+}
+
+HRESULT Machine::setTeleporterEnabled(BOOL aTeleporterEnabled)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Only allow it to be set to true when PoweredOff or Aborted.
+ (Clearing it is always permitted.) */
+ if ( aTeleporterEnabled
+ && mData->mRegistered
+ && ( !i_isSessionMachine()
+ || ( mData->mMachineState != MachineState_PoweredOff
+ && mData->mMachineState != MachineState_Teleported
+ && mData->mMachineState != MachineState_Aborted
+ )
+ )
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("The machine is not powered off (state is %s)"),
+ Global::stringifyMachineState(mData->mMachineState));
+
+ i_setModified(IsModified_MachineData);
+ mUserData.backup();
+ mUserData->s.fTeleporterEnabled = !! aTeleporterEnabled;
+
+ return S_OK;
+}
+
+HRESULT Machine::getTeleporterPort(ULONG *aTeleporterPort)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aTeleporterPort = (ULONG)mUserData->s.uTeleporterPort;
+
+ return S_OK;
+}
+
+HRESULT Machine::setTeleporterPort(ULONG aTeleporterPort)
+{
+ if (aTeleporterPort >= _64K)
+ return setError(E_INVALIDARG, tr("Invalid port number %d"), aTeleporterPort);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrSavedStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mUserData.backup();
+ mUserData->s.uTeleporterPort = (uint32_t)aTeleporterPort;
+
+ return S_OK;
+}
+
+HRESULT Machine::getTeleporterAddress(com::Utf8Str &aTeleporterAddress)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aTeleporterAddress = mUserData->s.strTeleporterAddress;
+
+ return S_OK;
+}
+
+HRESULT Machine::setTeleporterAddress(const com::Utf8Str &aTeleporterAddress)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrSavedStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mUserData.backup();
+ mUserData->s.strTeleporterAddress = aTeleporterAddress;
+
+ return S_OK;
+}
+
+HRESULT Machine::getTeleporterPassword(com::Utf8Str &aTeleporterPassword)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aTeleporterPassword = mUserData->s.strTeleporterPassword;
+
+ return S_OK;
+}
+
+HRESULT Machine::setTeleporterPassword(const com::Utf8Str &aTeleporterPassword)
+{
+ /*
+ * Hash the password first.
+ */
+ com::Utf8Str aT = aTeleporterPassword;
+
+ if (!aT.isEmpty())
+ {
+ if (VBoxIsPasswordHashed(&aT))
+ return setError(E_INVALIDARG, tr("Cannot set an already hashed password, only plain text password please"));
+ VBoxHashPassword(&aT);
+ }
+
+ /*
+ * Do the update.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = i_checkStateDependency(MutableOrSavedStateDep);
+ if (SUCCEEDED(hrc))
+ {
+ i_setModified(IsModified_MachineData);
+ mUserData.backup();
+ mUserData->s.strTeleporterPassword = aT;
+ }
+
+ return hrc;
+}
+
+HRESULT Machine::getRTCUseUTC(BOOL *aRTCUseUTC)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aRTCUseUTC = mUserData->s.fRTCUseUTC;
+
+ return S_OK;
+}
+
+HRESULT Machine::setRTCUseUTC(BOOL aRTCUseUTC)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Only allow it to be set to true when PoweredOff or Aborted.
+ (Clearing it is always permitted.) */
+ if ( aRTCUseUTC
+ && mData->mRegistered
+ && ( !i_isSessionMachine()
+ || ( mData->mMachineState != MachineState_PoweredOff
+ && mData->mMachineState != MachineState_Teleported
+ && mData->mMachineState != MachineState_Aborted
+ )
+ )
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("The machine is not powered off (state is %s)"),
+ Global::stringifyMachineState(mData->mMachineState));
+
+ i_setModified(IsModified_MachineData);
+ mUserData.backup();
+ mUserData->s.fRTCUseUTC = !!aRTCUseUTC;
+
+ return S_OK;
+}
+
+HRESULT Machine::getIOCacheEnabled(BOOL *aIOCacheEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aIOCacheEnabled = mHWData->mIOCacheEnabled;
+
+ return S_OK;
+}
+
+HRESULT Machine::setIOCacheEnabled(BOOL aIOCacheEnabled)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mIOCacheEnabled = aIOCacheEnabled;
+
+ return S_OK;
+}
+
+HRESULT Machine::getIOCacheSize(ULONG *aIOCacheSize)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aIOCacheSize = mHWData->mIOCacheSize;
+
+ return S_OK;
+}
+
+HRESULT Machine::setIOCacheSize(ULONG aIOCacheSize)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mIOCacheSize = aIOCacheSize;
+
+ return S_OK;
+}
+
+HRESULT Machine::getStateKeyId(com::Utf8Str &aKeyId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ aKeyId = mSSData->strStateKeyId;
+#else
+ aKeyId = com::Utf8Str::Empty;
+#endif
+
+ return S_OK;
+}
+
+HRESULT Machine::getStateKeyStore(com::Utf8Str &aKeyStore)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ aKeyStore = mSSData->strStateKeyStore;
+#else
+ aKeyStore = com::Utf8Str::Empty;
+#endif
+
+ return S_OK;
+}
+
+HRESULT Machine::getLogKeyId(com::Utf8Str &aKeyId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ aKeyId = mData->mstrLogKeyId;
+#else
+ aKeyId = com::Utf8Str::Empty;
+#endif
+
+ return S_OK;
+}
+
+HRESULT Machine::getLogKeyStore(com::Utf8Str &aKeyStore)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ aKeyStore = mData->mstrLogKeyStore;
+#else
+ aKeyStore = com::Utf8Str::Empty;
+#endif
+
+ return S_OK;
+}
+
+HRESULT Machine::getGuestDebugControl(ComPtr<IGuestDebugControl> &aGuestDebugControl)
+{
+ mGuestDebugControl.queryInterfaceTo(aGuestDebugControl.asOutParam());
+
+ return S_OK;
+}
+
+
+/**
+ * @note Locks objects!
+ */
+HRESULT Machine::lockMachine(const ComPtr<ISession> &aSession,
+ LockType_T aLockType)
+{
+ /* check the session state */
+ SessionState_T state;
+ HRESULT rc = aSession->COMGETTER(State)(&state);
+ if (FAILED(rc)) return rc;
+
+ if (state != SessionState_Unlocked)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The given session is busy"));
+
+ // get the client's IInternalSessionControl interface
+ ComPtr<IInternalSessionControl> pSessionControl = aSession;
+ ComAssertMsgRet(!!pSessionControl, (tr("No IInternalSessionControl interface")),
+ E_INVALIDARG);
+
+ // session name (only used in some code paths)
+ Utf8Str strSessionName;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mData->mRegistered)
+ return setError(E_UNEXPECTED,
+ tr("The machine '%s' is not registered"),
+ mUserData->s.strName.c_str());
+
+ LogFlowThisFunc(("mSession.mState=%s\n", ::stringifySessionState(mData->mSession.mState)));
+
+ SessionState_T oldState = mData->mSession.mState;
+ /* Hack: in case the session is closing and there is a progress object
+ * which allows waiting for the session to be closed, take the opportunity
+ * and do a limited wait (max. 1 second). This helps a lot when the system
+ * is busy and thus session closing can take a little while. */
+ if ( mData->mSession.mState == SessionState_Unlocking
+ && mData->mSession.mProgress)
+ {
+ alock.release();
+ mData->mSession.mProgress->WaitForCompletion(1000);
+ alock.acquire();
+ LogFlowThisFunc(("after waiting: mSession.mState=%s\n", ::stringifySessionState(mData->mSession.mState)));
+ }
+
+ // try again now
+ if ( (mData->mSession.mState == SessionState_Locked) // machine is write-locked already
+ // (i.e. session machine exists)
+ && (aLockType == LockType_Shared) // caller wants a shared link to the
+ // existing session that holds the write lock:
+ )
+ {
+ // OK, share the session... we are now dealing with three processes:
+ // 1) VBoxSVC (where this code runs);
+ // 2) process C: the caller's client process (who wants a shared session);
+ // 3) process W: the process which already holds the write lock on the machine (write-locking session)
+
+ // copy pointers to W (the write-locking session) before leaving lock (these must not be NULL)
+ ComPtr<IInternalSessionControl> pSessionW = mData->mSession.mDirectControl;
+ ComAssertRet(!pSessionW.isNull(), E_FAIL);
+ ComObjPtr<SessionMachine> pSessionMachine = mData->mSession.mMachine;
+ AssertReturn(!pSessionMachine.isNull(), E_FAIL);
+
+ /*
+ * Release the lock before calling the client process. It's safe here
+ * since the only thing to do after we get the lock again is to add
+ * the remote control to the list (which doesn't directly influence
+ * anything).
+ */
+ alock.release();
+
+ // get the console of the session holding the write lock (this is a remote call)
+ ComPtr<IConsole> pConsoleW;
+ if (mData->mSession.mLockType == LockType_VM)
+ {
+ LogFlowThisFunc(("Calling GetRemoteConsole()...\n"));
+ rc = pSessionW->COMGETTER(RemoteConsole)(pConsoleW.asOutParam());
+ LogFlowThisFunc(("GetRemoteConsole() returned %08X\n", rc));
+ if (FAILED(rc))
+ // the failure may occur w/o any error info (from RPC), so provide one
+ return setError(VBOX_E_VM_ERROR,
+ tr("Failed to get a console object from the direct session (%Rhrc)"), rc);
+ ComAssertRet(!pConsoleW.isNull(), E_FAIL);
+ }
+
+ // share the session machine and W's console with the caller's session
+ LogFlowThisFunc(("Calling AssignRemoteMachine()...\n"));
+ rc = pSessionControl->AssignRemoteMachine(pSessionMachine, pConsoleW);
+ LogFlowThisFunc(("AssignRemoteMachine() returned %08X\n", rc));
+
+ if (FAILED(rc))
+ // the failure may occur w/o any error info (from RPC), so provide one
+ return setError(VBOX_E_VM_ERROR,
+ tr("Failed to assign the machine to the session (%Rhrc)"), rc);
+ alock.acquire();
+
+ // need to revalidate the state after acquiring the lock again
+ if (mData->mSession.mState != SessionState_Locked)
+ {
+ pSessionControl->Uninitialize();
+ return setError(VBOX_E_INVALID_SESSION_STATE,
+ tr("The machine '%s' was unlocked unexpectedly while attempting to share its session"),
+ mUserData->s.strName.c_str());
+ }
+
+ // add the caller's session to the list
+ mData->mSession.mRemoteControls.push_back(pSessionControl);
+ }
+ else if ( mData->mSession.mState == SessionState_Locked
+ || mData->mSession.mState == SessionState_Unlocking
+ )
+ {
+ // sharing not permitted, or machine still unlocking:
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The machine '%s' is already locked for a session (or being unlocked)"),
+ mUserData->s.strName.c_str());
+ }
+ else
+ {
+ // machine is not locked: then write-lock the machine (create the session machine)
+
+ // must not be busy
+ AssertReturn(!Global::IsOnlineOrTransient(mData->mMachineState), E_FAIL);
+
+ // get the caller's session PID
+ RTPROCESS pid = NIL_RTPROCESS;
+ AssertCompile(sizeof(ULONG) == sizeof(RTPROCESS));
+ pSessionControl->COMGETTER(PID)((ULONG*)&pid);
+ Assert(pid != NIL_RTPROCESS);
+
+ bool fLaunchingVMProcess = (mData->mSession.mState == SessionState_Spawning);
+
+ if (fLaunchingVMProcess)
+ {
+ if (mData->mSession.mPID == NIL_RTPROCESS)
+ {
+ // two or more clients racing for a lock, the one which set the
+ // session state to Spawning will win, the others will get an
+ // error as we can't decide here if waiting a little would help
+ // (only for shared locks this would avoid an error)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The machine '%s' already has a lock request pending"),
+ mUserData->s.strName.c_str());
+ }
+
+ // this machine is awaiting for a spawning session to be opened:
+ // then the calling process must be the one that got started by
+ // LaunchVMProcess()
+
+ LogFlowThisFunc(("mSession.mPID=%d(0x%x)\n", mData->mSession.mPID, mData->mSession.mPID));
+ LogFlowThisFunc(("session.pid=%d(0x%x)\n", pid, pid));
+
+#if defined(VBOX_WITH_HARDENING) && defined(RT_OS_WINDOWS)
+ /* Hardened windows builds spawns three processes when a VM is
+ launched, the 3rd one is the one that will end up here. */
+ RTPROCESS pidParent;
+ int vrc = RTProcQueryParent(pid, &pidParent);
+ if (RT_SUCCESS(vrc))
+ vrc = RTProcQueryParent(pidParent, &pidParent);
+ if ( (RT_SUCCESS(vrc) && mData->mSession.mPID == pidParent)
+ || vrc == VERR_ACCESS_DENIED)
+ {
+ LogFlowThisFunc(("mSession.mPID => %d(%#x) - windows hardening stub\n", mData->mSession.mPID, pid));
+ mData->mSession.mPID = pid;
+ }
+#endif
+
+ if (mData->mSession.mPID != pid)
+ return setError(E_ACCESSDENIED,
+ tr("An unexpected process (PID=0x%08X) has tried to lock the "
+ "machine '%s', while only the process started by LaunchVMProcess (PID=0x%08X) is allowed"),
+ pid, mUserData->s.strName.c_str(), mData->mSession.mPID);
+ }
+
+ // create the mutable SessionMachine from the current machine
+ ComObjPtr<SessionMachine> sessionMachine;
+ sessionMachine.createObject();
+ rc = sessionMachine->init(this);
+ AssertComRC(rc);
+
+ /* NOTE: doing return from this function after this point but
+ * before the end is forbidden since it may call SessionMachine::uninit()
+ * (through the ComObjPtr's destructor) which requests the VirtualBox write
+ * lock while still holding the Machine lock in alock so that a deadlock
+ * is possible due to the wrong lock order. */
+
+ if (SUCCEEDED(rc))
+ {
+ /*
+ * Set the session state to Spawning to protect against subsequent
+ * attempts to open a session and to unregister the machine after
+ * we release the lock.
+ */
+ SessionState_T origState = mData->mSession.mState;
+ mData->mSession.mState = SessionState_Spawning;
+
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ /* Get the client token ID to be passed to the client process */
+ Utf8Str strTokenId;
+ sessionMachine->i_getTokenId(strTokenId);
+ Assert(!strTokenId.isEmpty());
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ /* Get the client token to be passed to the client process */
+ ComPtr<IToken> pToken(sessionMachine->i_getToken());
+ /* The token is now "owned" by pToken, fix refcount */
+ if (!pToken.isNull())
+ pToken->Release();
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+
+ /*
+ * Release the lock before calling the client process -- it will call
+ * Machine/SessionMachine methods. Releasing the lock here is quite safe
+ * because the state is Spawning, so that LaunchVMProcess() and
+ * LockMachine() calls will fail. This method, called before we
+ * acquire the lock again, will fail because of the wrong PID.
+ *
+ * Note that mData->mSession.mRemoteControls accessed outside
+ * the lock may not be modified when state is Spawning, so it's safe.
+ */
+ alock.release();
+
+ LogFlowThisFunc(("Calling AssignMachine()...\n"));
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ rc = pSessionControl->AssignMachine(sessionMachine, aLockType, Bstr(strTokenId).raw());
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ rc = pSessionControl->AssignMachine(sessionMachine, aLockType, pToken);
+ /* Now the token is owned by the client process. */
+ pToken.setNull();
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ LogFlowThisFunc(("AssignMachine() returned %08X\n", rc));
+
+ /* The failure may occur w/o any error info (from RPC), so provide one */
+ if (FAILED(rc))
+ setError(VBOX_E_VM_ERROR,
+ tr("Failed to assign the machine to the session (%Rhrc)"), rc);
+
+ // get session name, either to remember or to compare against
+ // the already known session name.
+ {
+ Bstr bstrSessionName;
+ HRESULT rc2 = aSession->COMGETTER(Name)(bstrSessionName.asOutParam());
+ if (SUCCEEDED(rc2))
+ strSessionName = bstrSessionName;
+ }
+
+ if ( SUCCEEDED(rc)
+ && fLaunchingVMProcess
+ )
+ {
+ /* complete the remote session initialization */
+
+ /* get the console from the direct session */
+ ComPtr<IConsole> console;
+ rc = pSessionControl->COMGETTER(RemoteConsole)(console.asOutParam());
+ ComAssertComRC(rc);
+
+ if (SUCCEEDED(rc) && !console)
+ {
+ ComAssert(!!console);
+ rc = E_FAIL;
+ }
+
+ /* assign machine & console to the remote session */
+ if (SUCCEEDED(rc))
+ {
+ /*
+ * after LaunchVMProcess(), the first and the only
+ * entry in remoteControls is that remote session
+ */
+ LogFlowThisFunc(("Calling AssignRemoteMachine()...\n"));
+ rc = mData->mSession.mRemoteControls.front()->AssignRemoteMachine(sessionMachine, console);
+ LogFlowThisFunc(("AssignRemoteMachine() returned %08X\n", rc));
+
+ /* The failure may occur w/o any error info (from RPC), so provide one */
+ if (FAILED(rc))
+ setError(VBOX_E_VM_ERROR,
+ tr("Failed to assign the machine to the remote session (%Rhrc)"), rc);
+ }
+
+ if (FAILED(rc))
+ pSessionControl->Uninitialize();
+ }
+
+ /* acquire the lock again */
+ alock.acquire();
+
+ /* Restore the session state */
+ mData->mSession.mState = origState;
+ }
+
+ // finalize spawning anyway (this is why we don't return on errors above)
+ if (fLaunchingVMProcess)
+ {
+ Assert(mData->mSession.mName == strSessionName || FAILED(rc));
+ /* Note that the progress object is finalized later */
+ /** @todo Consider checking mData->mSession.mProgress for cancellation
+ * around here. */
+
+ /* We don't reset mSession.mPID here because it is necessary for
+ * SessionMachine::uninit() to reap the child process later. */
+
+ if (FAILED(rc))
+ {
+ /* Close the remote session, remove the remote control from the list
+ * and reset session state to Closed (@note keep the code in sync
+ * with the relevant part in checkForSpawnFailure()). */
+
+ Assert(mData->mSession.mRemoteControls.size() == 1);
+ if (mData->mSession.mRemoteControls.size() == 1)
+ {
+ ErrorInfoKeeper eik;
+ mData->mSession.mRemoteControls.front()->Uninitialize();
+ }
+
+ mData->mSession.mRemoteControls.clear();
+ mData->mSession.mState = SessionState_Unlocked;
+ }
+ }
+ else
+ {
+ /* memorize PID of the directly opened session */
+ if (SUCCEEDED(rc))
+ mData->mSession.mPID = pid;
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ mData->mSession.mLockType = aLockType;
+ /* memorize the direct session control and cache IUnknown for it */
+ mData->mSession.mDirectControl = pSessionControl;
+ mData->mSession.mState = SessionState_Locked;
+ if (!fLaunchingVMProcess)
+ mData->mSession.mName = strSessionName;
+ /* associate the SessionMachine with this Machine */
+ mData->mSession.mMachine = sessionMachine;
+
+ /* request an IUnknown pointer early from the remote party for later
+ * identity checks (it will be internally cached within mDirectControl
+ * at least on XPCOM) */
+ ComPtr<IUnknown> unk = mData->mSession.mDirectControl;
+ NOREF(unk);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (aLockType == LockType_VM)
+ {
+ /* get the console from the direct session */
+ ComPtr<IConsole> console;
+ rc = pSessionControl->COMGETTER(RemoteConsole)(console.asOutParam());
+ ComAssertComRC(rc);
+ /* send passswords to console */
+ for (SecretKeyStore::SecretKeyMap::iterator it = mData->mpKeyStore->begin();
+ it != mData->mpKeyStore->end();
+ ++it)
+ {
+ SecretKey *pKey = it->second;
+ pKey->retain();
+ console->AddEncryptionPassword(Bstr(it->first).raw(),
+ Bstr((const char*)pKey->getKeyBuffer()).raw(),
+ TRUE);
+ pKey->release();
+ }
+
+ }
+#endif
+ }
+
+ /* Release the lock since SessionMachine::uninit() locks VirtualBox which
+ * would break the lock order */
+ alock.release();
+
+ /* uninitialize the created session machine on failure */
+ if (FAILED(rc))
+ sessionMachine->uninit();
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ /*
+ * tell the client watcher thread to update the set of
+ * machines that have open sessions
+ */
+ mParent->i_updateClientWatcher();
+
+ if (oldState != SessionState_Locked)
+ /* fire an event */
+ mParent->i_onSessionStateChanged(i_getId(), SessionState_Locked);
+ }
+
+ return rc;
+}
+
+/**
+ * @note Locks objects!
+ */
+HRESULT Machine::launchVMProcess(const ComPtr<ISession> &aSession,
+ const com::Utf8Str &aName,
+ const std::vector<com::Utf8Str> &aEnvironmentChanges,
+ ComPtr<IProgress> &aProgress)
+{
+ Utf8Str strFrontend(aName);
+ /* "emergencystop" doesn't need the session, so skip the checks/interface
+ * retrieval. This code doesn't quite fit in here, but introducing a
+ * special API method would be even more effort, and would require explicit
+ * support by every API client. It's better to hide the feature a bit. */
+ if (strFrontend != "emergencystop")
+ CheckComArgNotNull(aSession);
+
+ HRESULT rc = S_OK;
+ if (strFrontend.isEmpty())
+ {
+ Bstr bstrFrontend;
+ rc = COMGETTER(DefaultFrontend)(bstrFrontend.asOutParam());
+ if (FAILED(rc))
+ return rc;
+ strFrontend = bstrFrontend;
+ if (strFrontend.isEmpty())
+ {
+ ComPtr<ISystemProperties> systemProperties;
+ rc = mParent->COMGETTER(SystemProperties)(systemProperties.asOutParam());
+ if (FAILED(rc))
+ return rc;
+ rc = systemProperties->COMGETTER(DefaultFrontend)(bstrFrontend.asOutParam());
+ if (FAILED(rc))
+ return rc;
+ strFrontend = bstrFrontend;
+ }
+ /* paranoia - emergencystop is not a valid default */
+ if (strFrontend == "emergencystop")
+ strFrontend = Utf8Str::Empty;
+ }
+ /* default frontend: Qt GUI */
+ if (strFrontend.isEmpty())
+ strFrontend = "GUI/Qt";
+
+ if (strFrontend != "emergencystop")
+ {
+ /* check the session state */
+ SessionState_T state;
+ rc = aSession->COMGETTER(State)(&state);
+ if (FAILED(rc))
+ return rc;
+
+ if (state != SessionState_Unlocked)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The given session is busy"));
+
+ /* get the IInternalSessionControl interface */
+ ComPtr<IInternalSessionControl> control(aSession);
+ ComAssertMsgRet(!control.isNull(),
+ ("No IInternalSessionControl interface"),
+ E_INVALIDARG);
+
+ /* get the teleporter enable state for the progress object init. */
+ BOOL fTeleporterEnabled;
+ rc = COMGETTER(TeleporterEnabled)(&fTeleporterEnabled);
+ if (FAILED(rc))
+ return rc;
+
+ /* create a progress object */
+ ComObjPtr<ProgressProxy> progress;
+ progress.createObject();
+ rc = progress->init(mParent,
+ static_cast<IMachine*>(this),
+ Bstr(tr("Starting VM")).raw(),
+ TRUE /* aCancelable */,
+ fTeleporterEnabled ? 20 : 10 /* uTotalOperationsWeight */,
+ BstrFmt(tr("Creating process for virtual machine \"%s\" (%s)"),
+ mUserData->s.strName.c_str(), strFrontend.c_str()).raw(),
+ 2 /* uFirstOperationWeight */,
+ fTeleporterEnabled ? 3 : 1 /* cOtherProgressObjectOperations */);
+
+ if (SUCCEEDED(rc))
+ {
+ rc = i_launchVMProcess(control, strFrontend, aEnvironmentChanges, progress);
+ if (SUCCEEDED(rc))
+ {
+ aProgress = progress;
+
+ /* signal the client watcher thread */
+ mParent->i_updateClientWatcher();
+
+ /* fire an event */
+ mParent->i_onSessionStateChanged(i_getId(), SessionState_Spawning);
+ }
+ }
+ }
+ else
+ {
+ /* no progress object - either instant success or failure */
+ aProgress = NULL;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->mSession.mState != SessionState_Locked)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The machine '%s' is not locked by a session"),
+ mUserData->s.strName.c_str());
+
+ /* must have a VM process associated - do not kill normal API clients
+ * with an open session */
+ if (!Global::IsOnline(mData->mMachineState))
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The machine '%s' does not have a VM process"),
+ mUserData->s.strName.c_str());
+
+ /* forcibly terminate the VM process */
+ if (mData->mSession.mPID != NIL_RTPROCESS)
+ RTProcTerminate(mData->mSession.mPID);
+
+ /* signal the client watcher thread, as most likely the client has
+ * been terminated */
+ mParent->i_updateClientWatcher();
+ }
+
+ return rc;
+}
+
+HRESULT Machine::setBootOrder(ULONG aPosition, DeviceType_T aDevice)
+{
+ if (aPosition < 1 || aPosition > SchemaDefs::MaxBootPosition)
+ return setError(E_INVALIDARG,
+ tr("Invalid boot position: %lu (must be in range [1, %lu])"),
+ aPosition, SchemaDefs::MaxBootPosition);
+
+ if (aDevice == DeviceType_USB)
+ return setError(E_NOTIMPL,
+ tr("Booting from USB device is currently not supported"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mBootOrder[aPosition - 1] = aDevice;
+
+ return S_OK;
+}
+
+HRESULT Machine::getBootOrder(ULONG aPosition, DeviceType_T *aDevice)
+{
+ if (aPosition < 1 || aPosition > SchemaDefs::MaxBootPosition)
+ return setError(E_INVALIDARG,
+ tr("Invalid boot position: %lu (must be in range [1, %lu])"),
+ aPosition, SchemaDefs::MaxBootPosition);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aDevice = mHWData->mBootOrder[aPosition - 1];
+
+ return S_OK;
+}
+
+HRESULT Machine::attachDevice(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ DeviceType_T aType,
+ const ComPtr<IMedium> &aMedium)
+{
+ IMedium *aM = aMedium;
+ LogFlowThisFunc(("aControllerName=\"%s\" aControllerPort=%d aDevice=%d aType=%d aMedium=%p\n",
+ aName.c_str(), aControllerPort, aDevice, aType, aM));
+
+ // request the host lock first, since might be calling Host methods for getting host drives;
+ // next, protect the media tree all the while we're in here, as well as our member variables
+ AutoMultiWriteLock2 alock(mParent->i_host(), this COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock treeLock(&mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ /// @todo NEWMEDIA implicit machine registration
+ if (!mData->mRegistered)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot attach storage devices to an unregistered machine"));
+
+ AssertReturn(mData->mMachineState != MachineState_Saved, E_FAIL);
+
+ /* Check for an existing controller. */
+ ComObjPtr<StorageController> ctl;
+ rc = i_getStorageControllerByName(aName, ctl, true /* aSetError */);
+ if (FAILED(rc)) return rc;
+
+ StorageControllerType_T ctrlType;
+ rc = ctl->COMGETTER(ControllerType)(&ctrlType);
+ if (FAILED(rc))
+ return setError(E_FAIL,
+ tr("Could not get type of controller '%s'"),
+ aName.c_str());
+
+ bool fSilent = false;
+ Utf8Str strReconfig;
+
+ /* Check whether the flag to allow silent storage attachment reconfiguration is set. */
+ strReconfig = i_getExtraData(Utf8Str("VBoxInternal2/SilentReconfigureWhilePaused"));
+ if ( mData->mMachineState == MachineState_Paused
+ && strReconfig == "1")
+ fSilent = true;
+
+ /* Check that the controller can do hot-plugging if we attach the device while the VM is running. */
+ bool fHotplug = false;
+ if (!fSilent && Global::IsOnlineOrTransient(mData->mMachineState))
+ fHotplug = true;
+
+ if (fHotplug && !i_isControllerHotplugCapable(ctrlType))
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Controller '%s' does not support hot-plugging"),
+ aName.c_str());
+
+ /* Attaching a USB device when a VM is powered off should default to being marked as hot-pluggable */
+ if (!fHotplug && !Global::IsOnlineOrTransient(mData->mMachineState) && ctrlType == StorageControllerType_USB)
+ fHotplug = true;
+
+ // check that the port and device are not out of range
+ rc = ctl->i_checkPortAndDeviceValid(aControllerPort, aDevice);
+ if (FAILED(rc)) return rc;
+
+ /* check if the device slot is already busy */
+ MediumAttachment *pAttachTemp;
+ if ((pAttachTemp = i_findAttachment(*mMediumAttachments.data(),
+ aName,
+ aControllerPort,
+ aDevice)))
+ {
+ Medium *pMedium = pAttachTemp->i_getMedium();
+ if (pMedium)
+ {
+ AutoReadLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Medium '%s' is already attached to port %d, device %d of controller '%s' of this virtual machine"),
+ pMedium->i_getLocationFull().c_str(),
+ aControllerPort,
+ aDevice,
+ aName.c_str());
+ }
+ else
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Device is already attached to port %d, device %d of controller '%s' of this virtual machine"),
+ aControllerPort, aDevice, aName.c_str());
+ }
+
+ ComObjPtr<Medium> medium = static_cast<Medium*>(aM);
+ if (aMedium && medium.isNull())
+ return setError(E_INVALIDARG, tr("The given medium pointer is invalid"));
+
+ AutoCaller mediumCaller(medium);
+ if (FAILED(mediumCaller.rc())) return mediumCaller.rc();
+
+ AutoWriteLock mediumLock(medium COMMA_LOCKVAL_SRC_POS);
+
+ if ( (pAttachTemp = i_findAttachment(*mMediumAttachments.data(), medium))
+ && !medium.isNull()
+ && ( medium->i_getType() != MediumType_Readonly
+ || medium->i_getDeviceType() != DeviceType_DVD)
+ )
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Medium '%s' is already attached to this virtual machine"),
+ medium->i_getLocationFull().c_str());
+
+ if (!medium.isNull())
+ {
+ MediumType_T mtype = medium->i_getType();
+ // MediumType_Readonly is also new, but only applies to DVDs and floppies.
+ // For DVDs it's not written to the config file, so needs no global config
+ // version bump. For floppies it's a new attribute "type", which is ignored
+ // by older VirtualBox version, so needs no global config version bump either.
+ // For hard disks this type is not accepted.
+ if (mtype == MediumType_MultiAttach)
+ {
+ // This type is new with VirtualBox 4.0 and therefore requires settings
+ // version 1.11 in the settings backend. Unfortunately it is not enough to do
+ // the usual routine in MachineConfigFile::bumpSettingsVersionIfNeeded() for
+ // two reasons: The medium type is a property of the media registry tree, which
+ // can reside in the global config file (for pre-4.0 media); we would therefore
+ // possibly need to bump the global config version. We don't want to do that though
+ // because that might make downgrading to pre-4.0 impossible.
+ // As a result, we can only use these two new types if the medium is NOT in the
+ // global registry:
+ const Guid &uuidGlobalRegistry = mParent->i_getGlobalRegistryId();
+ if ( medium->i_isInRegistry(uuidGlobalRegistry)
+ || !mData->pMachineConfigFile->canHaveOwnMediaRegistry()
+ )
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot attach medium '%s': the media type 'MultiAttach' can only be attached "
+ "to machines that were created with VirtualBox 4.0 or later"),
+ medium->i_getLocationFull().c_str());
+ }
+ }
+
+ bool fIndirect = false;
+ if (!medium.isNull())
+ fIndirect = medium->i_isReadOnly();
+ bool associate = true;
+
+ do
+ {
+ if ( aType == DeviceType_HardDisk
+ && mMediumAttachments.isBackedUp())
+ {
+ const MediumAttachmentList &oldAtts = *mMediumAttachments.backedUpData();
+
+ /* check if the medium was attached to the VM before we started
+ * changing attachments in which case the attachment just needs to
+ * be restored */
+ if ((pAttachTemp = i_findAttachment(oldAtts, medium)))
+ {
+ AssertReturn(!fIndirect, E_FAIL);
+
+ /* see if it's the same bus/channel/device */
+ if (pAttachTemp->i_matches(aName, aControllerPort, aDevice))
+ {
+ /* the simplest case: restore the whole attachment
+ * and return, nothing else to do */
+ mMediumAttachments->push_back(pAttachTemp);
+
+ /* Reattach the medium to the VM. */
+ if (fHotplug || fSilent)
+ {
+ mediumLock.release();
+ treeLock.release();
+ alock.release();
+
+ MediumLockList *pMediumLockList(new MediumLockList());
+
+ rc = medium->i_createMediumLockList(true /* fFailIfInaccessible */,
+ medium /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pMediumLockList);
+ alock.acquire();
+ if (FAILED(rc))
+ delete pMediumLockList;
+ else
+ {
+ mData->mSession.mLockedMedia.Unlock();
+ alock.release();
+ rc = mData->mSession.mLockedMedia.Insert(pAttachTemp, pMediumLockList);
+ mData->mSession.mLockedMedia.Lock();
+ alock.acquire();
+ }
+ alock.release();
+
+ if (SUCCEEDED(rc))
+ {
+ rc = i_onStorageDeviceChange(pAttachTemp, FALSE /* aRemove */, fSilent);
+ /* Remove lock list in case of error. */
+ if (FAILED(rc))
+ {
+ mData->mSession.mLockedMedia.Unlock();
+ mData->mSession.mLockedMedia.Remove(pAttachTemp);
+ mData->mSession.mLockedMedia.Lock();
+ }
+ }
+ }
+
+ return S_OK;
+ }
+
+ /* bus/channel/device differ; we need a new attachment object,
+ * but don't try to associate it again */
+ associate = false;
+ break;
+ }
+ }
+
+ /* go further only if the attachment is to be indirect */
+ if (!fIndirect)
+ break;
+
+ /* perform the so called smart attachment logic for indirect
+ * attachments. Note that smart attachment is only applicable to base
+ * hard disks. */
+
+ if (medium->i_getParent().isNull())
+ {
+ /* first, investigate the backup copy of the current hard disk
+ * attachments to make it possible to re-attach existing diffs to
+ * another device slot w/o losing their contents */
+ if (mMediumAttachments.isBackedUp())
+ {
+ const MediumAttachmentList &oldAtts = *mMediumAttachments.backedUpData();
+
+ MediumAttachmentList::const_iterator foundIt = oldAtts.end();
+ uint32_t foundLevel = 0;
+
+ for (MediumAttachmentList::const_iterator
+ it = oldAtts.begin();
+ it != oldAtts.end();
+ ++it)
+ {
+ uint32_t level = 0;
+ MediumAttachment *pAttach = *it;
+ ComObjPtr<Medium> pMedium = pAttach->i_getMedium();
+ Assert(!pMedium.isNull() || pAttach->i_getType() != DeviceType_HardDisk);
+ if (pMedium.isNull())
+ continue;
+
+ if (pMedium->i_getBase(&level) == medium)
+ {
+ /* skip the hard disk if its currently attached (we
+ * cannot attach the same hard disk twice) */
+ if (i_findAttachment(*mMediumAttachments.data(),
+ pMedium))
+ continue;
+
+ /* matched device, channel and bus (i.e. attached to the
+ * same place) will win and immediately stop the search;
+ * otherwise the attachment that has the youngest
+ * descendant of medium will be used
+ */
+ if (pAttach->i_matches(aName, aControllerPort, aDevice))
+ {
+ /* the simplest case: restore the whole attachment
+ * and return, nothing else to do */
+ mMediumAttachments->push_back(*it);
+
+ /* Reattach the medium to the VM. */
+ if (fHotplug || fSilent)
+ {
+ mediumLock.release();
+ treeLock.release();
+ alock.release();
+
+ MediumLockList *pMediumLockList(new MediumLockList());
+
+ rc = medium->i_createMediumLockList(true /* fFailIfInaccessible */,
+ medium /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pMediumLockList);
+ alock.acquire();
+ if (FAILED(rc))
+ delete pMediumLockList;
+ else
+ {
+ mData->mSession.mLockedMedia.Unlock();
+ alock.release();
+ rc = mData->mSession.mLockedMedia.Insert(pAttachTemp, pMediumLockList);
+ mData->mSession.mLockedMedia.Lock();
+ alock.acquire();
+ }
+ alock.release();
+
+ if (SUCCEEDED(rc))
+ {
+ rc = i_onStorageDeviceChange(pAttachTemp, FALSE /* aRemove */, fSilent);
+ /* Remove lock list in case of error. */
+ if (FAILED(rc))
+ {
+ mData->mSession.mLockedMedia.Unlock();
+ mData->mSession.mLockedMedia.Remove(pAttachTemp);
+ mData->mSession.mLockedMedia.Lock();
+ }
+ }
+ }
+
+ return S_OK;
+ }
+ else if ( foundIt == oldAtts.end()
+ || level > foundLevel /* prefer younger */
+ )
+ {
+ foundIt = it;
+ foundLevel = level;
+ }
+ }
+ }
+
+ if (foundIt != oldAtts.end())
+ {
+ /* use the previously attached hard disk */
+ medium = (*foundIt)->i_getMedium();
+ mediumCaller.attach(medium);
+ if (FAILED(mediumCaller.rc())) return mediumCaller.rc();
+ mediumLock.attach(medium);
+ /* not implicit, doesn't require association with this VM */
+ fIndirect = false;
+ associate = false;
+ /* go right to the MediumAttachment creation */
+ break;
+ }
+ }
+
+ /* must give up the medium lock and medium tree lock as below we
+ * go over snapshots, which needs a lock with higher lock order. */
+ mediumLock.release();
+ treeLock.release();
+
+ /* then, search through snapshots for the best diff in the given
+ * hard disk's chain to base the new diff on */
+
+ ComObjPtr<Medium> base;
+ ComObjPtr<Snapshot> snap = mData->mCurrentSnapshot;
+ while (snap)
+ {
+ AutoReadLock snapLock(snap COMMA_LOCKVAL_SRC_POS);
+
+ const MediumAttachmentList &snapAtts = *snap->i_getSnapshotMachine()->mMediumAttachments.data();
+
+ MediumAttachment *pAttachFound = NULL;
+ uint32_t foundLevel = 0;
+
+ for (MediumAttachmentList::const_iterator
+ it = snapAtts.begin();
+ it != snapAtts.end();
+ ++it)
+ {
+ MediumAttachment *pAttach = *it;
+ ComObjPtr<Medium> pMedium = pAttach->i_getMedium();
+ Assert(!pMedium.isNull() || pAttach->i_getType() != DeviceType_HardDisk);
+ if (pMedium.isNull())
+ continue;
+
+ uint32_t level = 0;
+ if (pMedium->i_getBase(&level) == medium)
+ {
+ /* matched device, channel and bus (i.e. attached to the
+ * same place) will win and immediately stop the search;
+ * otherwise the attachment that has the youngest
+ * descendant of medium will be used
+ */
+ if ( pAttach->i_getDevice() == aDevice
+ && pAttach->i_getPort() == aControllerPort
+ && pAttach->i_getControllerName() == aName
+ )
+ {
+ pAttachFound = pAttach;
+ break;
+ }
+ else if ( !pAttachFound
+ || level > foundLevel /* prefer younger */
+ )
+ {
+ pAttachFound = pAttach;
+ foundLevel = level;
+ }
+ }
+ }
+
+ if (pAttachFound)
+ {
+ base = pAttachFound->i_getMedium();
+ break;
+ }
+
+ snap = snap->i_getParent();
+ }
+
+ /* re-lock medium tree and the medium, as we need it below */
+ treeLock.acquire();
+ mediumLock.acquire();
+
+ /* found a suitable diff, use it as a base */
+ if (!base.isNull())
+ {
+ medium = base;
+ mediumCaller.attach(medium);
+ if (FAILED(mediumCaller.rc())) return mediumCaller.rc();
+ mediumLock.attach(medium);
+ }
+ }
+
+ Utf8Str strFullSnapshotFolder;
+ i_calculateFullPath(mUserData->s.strSnapshotFolder, strFullSnapshotFolder);
+
+ ComObjPtr<Medium> diff;
+ diff.createObject();
+ // store this diff in the same registry as the parent
+ Guid uuidRegistryParent;
+ if (!medium->i_getFirstRegistryMachineId(uuidRegistryParent))
+ {
+ // parent image has no registry: this can happen if we're attaching a new immutable
+ // image that has not yet been attached (medium then points to the base and we're
+ // creating the diff image for the immutable, and the parent is not yet registered);
+ // put the parent in the machine registry then
+ mediumLock.release();
+ treeLock.release();
+ alock.release();
+ i_addMediumToRegistry(medium);
+ alock.acquire();
+ treeLock.acquire();
+ mediumLock.acquire();
+ medium->i_getFirstRegistryMachineId(uuidRegistryParent);
+ }
+ rc = diff->init(mParent,
+ medium->i_getPreferredDiffFormat(),
+ strFullSnapshotFolder.append(RTPATH_SLASH_STR),
+ uuidRegistryParent,
+ DeviceType_HardDisk);
+ if (FAILED(rc)) return rc;
+
+ /* Apply the normal locking logic to the entire chain. */
+ MediumLockList *pMediumLockList(new MediumLockList());
+ mediumLock.release();
+ treeLock.release();
+ rc = diff->i_createMediumLockList(true /* fFailIfInaccessible */,
+ diff /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ medium,
+ *pMediumLockList);
+ treeLock.acquire();
+ mediumLock.acquire();
+ if (SUCCEEDED(rc))
+ {
+ mediumLock.release();
+ treeLock.release();
+ rc = pMediumLockList->Lock();
+ treeLock.acquire();
+ mediumLock.acquire();
+ if (FAILED(rc))
+ setError(rc,
+ tr("Could not lock medium when creating diff '%s'"),
+ diff->i_getLocationFull().c_str());
+ else
+ {
+ /* will release the lock before the potentially lengthy
+ * operation, so protect with the special state */
+ MachineState_T oldState = mData->mMachineState;
+ i_setMachineState(MachineState_SettingUp);
+
+ mediumLock.release();
+ treeLock.release();
+ alock.release();
+
+ rc = medium->i_createDiffStorage(diff,
+ medium->i_getPreferredDiffVariant(),
+ pMediumLockList,
+ NULL /* aProgress */,
+ true /* aWait */,
+ false /* aNotify */);
+
+ alock.acquire();
+ treeLock.acquire();
+ mediumLock.acquire();
+
+ i_setMachineState(oldState);
+ }
+ }
+
+ /* Unlock the media and free the associated memory. */
+ delete pMediumLockList;
+
+ if (FAILED(rc)) return rc;
+
+ /* use the created diff for the actual attachment */
+ medium = diff;
+ mediumCaller.attach(medium);
+ if (FAILED(mediumCaller.rc())) return mediumCaller.rc();
+ mediumLock.attach(medium);
+ }
+ while (0);
+
+ ComObjPtr<MediumAttachment> attachment;
+ attachment.createObject();
+ rc = attachment->init(this,
+ medium,
+ aName,
+ aControllerPort,
+ aDevice,
+ aType,
+ fIndirect,
+ false /* fPassthrough */,
+ false /* fTempEject */,
+ false /* fNonRotational */,
+ false /* fDiscard */,
+ fHotplug /* fHotPluggable */,
+ Utf8Str::Empty);
+ if (FAILED(rc)) return rc;
+
+ if (associate && !medium.isNull())
+ {
+ // as the last step, associate the medium to the VM
+ rc = medium->i_addBackReference(mData->mUuid);
+ // here we can fail because of Deleting, or being in process of creating a Diff
+ if (FAILED(rc)) return rc;
+
+ mediumLock.release();
+ treeLock.release();
+ alock.release();
+ i_addMediumToRegistry(medium);
+ alock.acquire();
+ treeLock.acquire();
+ mediumLock.acquire();
+ }
+
+ /* success: finally remember the attachment */
+ i_setModified(IsModified_Storage);
+ mMediumAttachments.backup();
+ mMediumAttachments->push_back(attachment);
+
+ mediumLock.release();
+ treeLock.release();
+ alock.release();
+
+ if (fHotplug || fSilent)
+ {
+ if (!medium.isNull())
+ {
+ MediumLockList *pMediumLockList(new MediumLockList());
+
+ rc = medium->i_createMediumLockList(true /* fFailIfInaccessible */,
+ medium /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pMediumLockList);
+ alock.acquire();
+ if (FAILED(rc))
+ delete pMediumLockList;
+ else
+ {
+ mData->mSession.mLockedMedia.Unlock();
+ alock.release();
+ rc = mData->mSession.mLockedMedia.Insert(attachment, pMediumLockList);
+ mData->mSession.mLockedMedia.Lock();
+ alock.acquire();
+ }
+ alock.release();
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ rc = i_onStorageDeviceChange(attachment, FALSE /* aRemove */, fSilent);
+ /* Remove lock list in case of error. */
+ if (FAILED(rc))
+ {
+ mData->mSession.mLockedMedia.Unlock();
+ mData->mSession.mLockedMedia.Remove(attachment);
+ mData->mSession.mLockedMedia.Lock();
+ }
+ }
+ }
+
+ /* Save modified registries, but skip this machine as it's the caller's
+ * job to save its settings like all other settings changes. */
+ mParent->i_unmarkRegistryModified(i_getId());
+ mParent->i_saveModifiedRegistries();
+
+ if (SUCCEEDED(rc))
+ {
+ if (fIndirect && medium != aM)
+ mParent->i_onMediumConfigChanged(medium);
+ mParent->i_onStorageDeviceChanged(attachment, FALSE, fSilent);
+ }
+
+ return rc;
+}
+
+HRESULT Machine::detachDevice(const com::Utf8Str &aName, LONG aControllerPort,
+ LONG aDevice)
+{
+ LogFlowThisFunc(("aControllerName=\"%s\" aControllerPort=%d aDevice=%d\n",
+ aName.c_str(), aControllerPort, aDevice));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ AssertReturn(mData->mMachineState != MachineState_Saved, E_FAIL);
+
+ /* Check for an existing controller. */
+ ComObjPtr<StorageController> ctl;
+ rc = i_getStorageControllerByName(aName, ctl, true /* aSetError */);
+ if (FAILED(rc)) return rc;
+
+ StorageControllerType_T ctrlType;
+ rc = ctl->COMGETTER(ControllerType)(&ctrlType);
+ if (FAILED(rc))
+ return setError(E_FAIL,
+ tr("Could not get type of controller '%s'"),
+ aName.c_str());
+
+ bool fSilent = false;
+ Utf8Str strReconfig;
+
+ /* Check whether the flag to allow silent storage attachment reconfiguration is set. */
+ strReconfig = i_getExtraData(Utf8Str("VBoxInternal2/SilentReconfigureWhilePaused"));
+ if ( mData->mMachineState == MachineState_Paused
+ && strReconfig == "1")
+ fSilent = true;
+
+ /* Check that the controller can do hot-plugging if we detach the device while the VM is running. */
+ bool fHotplug = false;
+ if (!fSilent && Global::IsOnlineOrTransient(mData->mMachineState))
+ fHotplug = true;
+
+ if (fHotplug && !i_isControllerHotplugCapable(ctrlType))
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Controller '%s' does not support hot-plugging"),
+ aName.c_str());
+
+ MediumAttachment *pAttach = i_findAttachment(*mMediumAttachments.data(),
+ aName,
+ aControllerPort,
+ aDevice);
+ if (!pAttach)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("No storage device attached to device slot %d on port %d of controller '%s'"),
+ aDevice, aControllerPort, aName.c_str());
+
+ if (fHotplug && !pAttach->i_getHotPluggable())
+ return setError(VBOX_E_NOT_SUPPORTED,
+ tr("The device slot %d on port %d of controller '%s' does not support hot-plugging"),
+ aDevice, aControllerPort, aName.c_str());
+
+ /*
+ * The VM has to detach the device before we delete any implicit diffs.
+ * If this fails we can roll back without loosing data.
+ */
+ if (fHotplug || fSilent)
+ {
+ alock.release();
+ rc = i_onStorageDeviceChange(pAttach, TRUE /* aRemove */, fSilent);
+ alock.acquire();
+ }
+ if (FAILED(rc)) return rc;
+
+ /* If we are here everything went well and we can delete the implicit now. */
+ rc = i_detachDevice(pAttach, alock, NULL /* pSnapshot */);
+
+ alock.release();
+
+ /* Save modified registries, but skip this machine as it's the caller's
+ * job to save its settings like all other settings changes. */
+ mParent->i_unmarkRegistryModified(i_getId());
+ mParent->i_saveModifiedRegistries();
+
+ if (SUCCEEDED(rc))
+ mParent->i_onStorageDeviceChanged(pAttach, TRUE, fSilent);
+
+ return rc;
+}
+
+HRESULT Machine::passthroughDevice(const com::Utf8Str &aName, LONG aControllerPort,
+ LONG aDevice, BOOL aPassthrough)
+{
+ LogFlowThisFunc(("aName=\"%s\" aControllerPort=%d aDevice=%d aPassthrough=%d\n",
+ aName.c_str(), aControllerPort, aDevice, aPassthrough));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ AssertReturn(mData->mMachineState != MachineState_Saved, E_FAIL);
+
+ /* Check for an existing controller. */
+ ComObjPtr<StorageController> ctl;
+ rc = i_getStorageControllerByName(aName, ctl, true /* aSetError */);
+ if (FAILED(rc)) return rc;
+
+ StorageControllerType_T ctrlType;
+ rc = ctl->COMGETTER(ControllerType)(&ctrlType);
+ if (FAILED(rc))
+ return setError(E_FAIL,
+ tr("Could not get type of controller '%s'"),
+ aName.c_str());
+
+ bool fSilent = false;
+ Utf8Str strReconfig;
+
+ /* Check whether the flag to allow silent storage attachment reconfiguration is set. */
+ strReconfig = i_getExtraData(Utf8Str("VBoxInternal2/SilentReconfigureWhilePaused"));
+ if ( mData->mMachineState == MachineState_Paused
+ && strReconfig == "1")
+ fSilent = true;
+
+ /* Check that the controller can do hot-plugging if we detach the device while the VM is running. */
+ bool fHotplug = false;
+ if (!fSilent && Global::IsOnlineOrTransient(mData->mMachineState))
+ fHotplug = true;
+
+ if (fHotplug && !i_isControllerHotplugCapable(ctrlType))
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Controller '%s' does not support hot-plugging which is required to change the passthrough setting while the VM is running"),
+ aName.c_str());
+
+ MediumAttachment *pAttach = i_findAttachment(*mMediumAttachments.data(),
+ aName,
+ aControllerPort,
+ aDevice);
+ if (!pAttach)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("No storage device attached to device slot %d on port %d of controller '%s'"),
+ aDevice, aControllerPort, aName.c_str());
+
+
+ i_setModified(IsModified_Storage);
+ mMediumAttachments.backup();
+
+ AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS);
+
+ if (pAttach->i_getType() != DeviceType_DVD)
+ return setError(E_INVALIDARG,
+ tr("Setting passthrough rejected as the device attached to device slot %d on port %d of controller '%s' is not a DVD"),
+ aDevice, aControllerPort, aName.c_str());
+
+ bool fValueChanged = pAttach->i_getPassthrough() != (aPassthrough != 0);
+
+ pAttach->i_updatePassthrough(!!aPassthrough);
+
+ attLock.release();
+ alock.release();
+ rc = i_onStorageDeviceChange(pAttach, FALSE /* aRemove */, FALSE /* aSilent */);
+ if (SUCCEEDED(rc) && fValueChanged)
+ mParent->i_onStorageDeviceChanged(pAttach, FALSE, FALSE);
+
+ return rc;
+}
+
+HRESULT Machine::temporaryEjectDevice(const com::Utf8Str &aName, LONG aControllerPort,
+ LONG aDevice, BOOL aTemporaryEject)
+{
+
+ LogFlowThisFunc(("aName=\"%s\" aControllerPort=%d aDevice=%d aTemporaryEject=%d\n",
+ aName.c_str(), aControllerPort, aDevice, aTemporaryEject));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrSavedOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ MediumAttachment *pAttach = i_findAttachment(*mMediumAttachments.data(),
+ aName,
+ aControllerPort,
+ aDevice);
+ if (!pAttach)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("No storage device attached to device slot %d on port %d of controller '%s'"),
+ aDevice, aControllerPort, aName.c_str());
+
+
+ i_setModified(IsModified_Storage);
+ mMediumAttachments.backup();
+
+ AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS);
+
+ if (pAttach->i_getType() != DeviceType_DVD)
+ return setError(E_INVALIDARG,
+ tr("Setting temporary eject flag rejected as the device attached to device slot %d on port %d of controller '%s' is not a DVD"),
+ aDevice, aControllerPort, aName.c_str());
+ pAttach->i_updateTempEject(!!aTemporaryEject);
+
+ return S_OK;
+}
+
+HRESULT Machine::nonRotationalDevice(const com::Utf8Str &aName, LONG aControllerPort,
+ LONG aDevice, BOOL aNonRotational)
+{
+
+ LogFlowThisFunc(("aName=\"%s\" aControllerPort=%d aDevice=%d aNonRotational=%d\n",
+ aName.c_str(), aControllerPort, aDevice, aNonRotational));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ AssertReturn(mData->mMachineState != MachineState_Saved, E_FAIL);
+
+ if (Global::IsOnlineOrTransient(mData->mMachineState))
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Invalid machine state: %s"),
+ Global::stringifyMachineState(mData->mMachineState));
+
+ MediumAttachment *pAttach = i_findAttachment(*mMediumAttachments.data(),
+ aName,
+ aControllerPort,
+ aDevice);
+ if (!pAttach)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("No storage device attached to device slot %d on port %d of controller '%s'"),
+ aDevice, aControllerPort, aName.c_str());
+
+
+ i_setModified(IsModified_Storage);
+ mMediumAttachments.backup();
+
+ AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS);
+
+ if (pAttach->i_getType() != DeviceType_HardDisk)
+ return setError(E_INVALIDARG,
+ tr("Setting the non-rotational medium flag rejected as the device attached to device slot %d on port %d of controller '%s' is not a hard disk"),
+ aDevice, aControllerPort, aName.c_str());
+ pAttach->i_updateNonRotational(!!aNonRotational);
+
+ return S_OK;
+}
+
+HRESULT Machine::setAutoDiscardForDevice(const com::Utf8Str &aName, LONG aControllerPort,
+ LONG aDevice, BOOL aDiscard)
+{
+
+ LogFlowThisFunc(("aName=\"%s\" aControllerPort=%d aDevice=%d aDiscard=%d\n",
+ aName.c_str(), aControllerPort, aDevice, aDiscard));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ AssertReturn(mData->mMachineState != MachineState_Saved, E_FAIL);
+
+ if (Global::IsOnlineOrTransient(mData->mMachineState))
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Invalid machine state: %s"),
+ Global::stringifyMachineState(mData->mMachineState));
+
+ MediumAttachment *pAttach = i_findAttachment(*mMediumAttachments.data(),
+ aName,
+ aControllerPort,
+ aDevice);
+ if (!pAttach)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("No storage device attached to device slot %d on port %d of controller '%s'"),
+ aDevice, aControllerPort, aName.c_str());
+
+
+ i_setModified(IsModified_Storage);
+ mMediumAttachments.backup();
+
+ AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS);
+
+ if (pAttach->i_getType() != DeviceType_HardDisk)
+ return setError(E_INVALIDARG,
+ tr("Setting the discard medium flag rejected as the device attached to device slot %d on port %d of controller '%s' is not a hard disk"),
+ aDevice, aControllerPort, aName.c_str());
+ pAttach->i_updateDiscard(!!aDiscard);
+
+ return S_OK;
+}
+
+HRESULT Machine::setHotPluggableForDevice(const com::Utf8Str &aName, LONG aControllerPort,
+ LONG aDevice, BOOL aHotPluggable)
+{
+ LogFlowThisFunc(("aName=\"%s\" aControllerPort=%d aDevice=%d aHotPluggable=%d\n",
+ aName.c_str(), aControllerPort, aDevice, aHotPluggable));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ AssertReturn(mData->mMachineState != MachineState_Saved, E_FAIL);
+
+ if (Global::IsOnlineOrTransient(mData->mMachineState))
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Invalid machine state: %s"),
+ Global::stringifyMachineState(mData->mMachineState));
+
+ MediumAttachment *pAttach = i_findAttachment(*mMediumAttachments.data(),
+ aName,
+ aControllerPort,
+ aDevice);
+ if (!pAttach)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("No storage device attached to device slot %d on port %d of controller '%s'"),
+ aDevice, aControllerPort, aName.c_str());
+
+ /* Check for an existing controller. */
+ ComObjPtr<StorageController> ctl;
+ rc = i_getStorageControllerByName(aName, ctl, true /* aSetError */);
+ if (FAILED(rc)) return rc;
+
+ StorageControllerType_T ctrlType;
+ rc = ctl->COMGETTER(ControllerType)(&ctrlType);
+ if (FAILED(rc))
+ return setError(E_FAIL,
+ tr("Could not get type of controller '%s'"),
+ aName.c_str());
+
+ if (!i_isControllerHotplugCapable(ctrlType))
+ return setError(VBOX_E_NOT_SUPPORTED,
+ tr("Controller '%s' does not support changing the hot-pluggable device flag"),
+ aName.c_str());
+
+ /* silently ignore attempts to modify the hot-plug status of USB devices */
+ if (ctrlType == StorageControllerType_USB)
+ return S_OK;
+
+ i_setModified(IsModified_Storage);
+ mMediumAttachments.backup();
+
+ AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS);
+
+ if (pAttach->i_getType() == DeviceType_Floppy)
+ return setError(E_INVALIDARG,
+ tr("Setting the hot-pluggable device flag rejected as the device attached to device slot %d on port %d of controller '%s' is a floppy drive"),
+ aDevice, aControllerPort, aName.c_str());
+ pAttach->i_updateHotPluggable(!!aHotPluggable);
+
+ return S_OK;
+}
+
+HRESULT Machine::setNoBandwidthGroupForDevice(const com::Utf8Str &aName, LONG aControllerPort,
+ LONG aDevice)
+{
+ LogFlowThisFunc(("aName=\"%s\" aControllerPort=%d aDevice=%d\n",
+ aName.c_str(), aControllerPort, aDevice));
+
+ return setBandwidthGroupForDevice(aName, aControllerPort, aDevice, NULL);
+}
+
+HRESULT Machine::setBandwidthGroupForDevice(const com::Utf8Str &aName, LONG aControllerPort,
+ LONG aDevice, const ComPtr<IBandwidthGroup> &aBandwidthGroup)
+{
+ LogFlowThisFunc(("aName=\"%s\" aControllerPort=%d aDevice=%d\n",
+ aName.c_str(), aControllerPort, aDevice));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrSavedStateDep);
+ if (FAILED(rc)) return rc;
+
+ if (Global::IsOnlineOrTransient(mData->mMachineState))
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Invalid machine state: %s"),
+ Global::stringifyMachineState(mData->mMachineState));
+
+ MediumAttachment *pAttach = i_findAttachment(*mMediumAttachments.data(),
+ aName,
+ aControllerPort,
+ aDevice);
+ if (!pAttach)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("No storage device attached to device slot %d on port %d of controller '%s'"),
+ aDevice, aControllerPort, aName.c_str());
+
+
+ i_setModified(IsModified_Storage);
+ mMediumAttachments.backup();
+
+ IBandwidthGroup *iB = aBandwidthGroup;
+ ComObjPtr<BandwidthGroup> group = static_cast<BandwidthGroup*>(iB);
+ if (aBandwidthGroup && group.isNull())
+ return setError(E_INVALIDARG, tr("The given bandwidth group pointer is invalid"));
+
+ AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS);
+
+ const Utf8Str strBandwidthGroupOld = pAttach->i_getBandwidthGroup();
+ if (strBandwidthGroupOld.isNotEmpty())
+ {
+ /* Get the bandwidth group object and release it - this must not fail. */
+ ComObjPtr<BandwidthGroup> pBandwidthGroupOld;
+ rc = i_getBandwidthGroup(strBandwidthGroupOld, pBandwidthGroupOld, false);
+ Assert(SUCCEEDED(rc));
+
+ pBandwidthGroupOld->i_release();
+ pAttach->i_updateBandwidthGroup(Utf8Str::Empty);
+ }
+
+ if (!group.isNull())
+ {
+ group->i_reference();
+ pAttach->i_updateBandwidthGroup(group->i_getName());
+ }
+
+ return S_OK;
+}
+
+HRESULT Machine::attachDeviceWithoutMedium(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ DeviceType_T aType)
+{
+ LogFlowThisFunc(("aName=\"%s\" aControllerPort=%d aDevice=%d aType=%d\n",
+ aName.c_str(), aControllerPort, aDevice, aType));
+
+ return attachDevice(aName, aControllerPort, aDevice, aType, NULL);
+}
+
+
+HRESULT Machine::unmountMedium(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ BOOL aForce)
+{
+ LogFlowThisFunc(("aName=\"%s\" aControllerPort=%d aDevice=%d",
+ aName.c_str(), aControllerPort, aForce));
+
+ return mountMedium(aName, aControllerPort, aDevice, NULL, aForce);
+}
+
+HRESULT Machine::mountMedium(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ const ComPtr<IMedium> &aMedium,
+ BOOL aForce)
+{
+ LogFlowThisFunc(("aName=\"%s\" aControllerPort=%d aDevice=%d aForce=%d\n",
+ aName.c_str(), aControllerPort, aDevice, aForce));
+
+ // request the host lock first, since might be calling Host methods for getting host drives;
+ // next, protect the media tree all the while we're in here, as well as our member variables
+ AutoMultiWriteLock3 multiLock(mParent->i_host()->lockHandle(),
+ this->lockHandle(),
+ &mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(hrc)) return hrc;
+
+ ComObjPtr<MediumAttachment> pAttach = i_findAttachment(*mMediumAttachments.data(),
+ aName,
+ aControllerPort,
+ aDevice);
+ if (pAttach.isNull())
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("No drive attached to device slot %d on port %d of controller '%s'"),
+ aDevice, aControllerPort, aName.c_str());
+
+ /* Remember previously mounted medium. The medium before taking the
+ * backup is not necessarily the same thing. */
+ ComObjPtr<Medium> oldmedium;
+ oldmedium = pAttach->i_getMedium();
+
+ IMedium *iM = aMedium;
+ ComObjPtr<Medium> pMedium = static_cast<Medium*>(iM);
+ if (aMedium && pMedium.isNull())
+ return setError(E_INVALIDARG, tr("The given medium pointer is invalid"));
+
+ /* Check if potential medium is already mounted */
+ if (pMedium == oldmedium)
+ return S_OK;
+
+ AutoCaller mediumCaller(pMedium);
+ if (FAILED(mediumCaller.rc())) return mediumCaller.rc();
+
+ AutoWriteLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
+ if (pMedium)
+ {
+ DeviceType_T mediumType = pAttach->i_getType();
+ switch (mediumType)
+ {
+ case DeviceType_DVD:
+ case DeviceType_Floppy:
+ break;
+
+ default:
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The device at port %d, device %d of controller '%s' of this virtual machine is not removeable"),
+ aControllerPort,
+ aDevice,
+ aName.c_str());
+ }
+ }
+
+ i_setModified(IsModified_Storage);
+ mMediumAttachments.backup();
+
+ {
+ // The backup operation makes the pAttach reference point to the
+ // old settings. Re-get the correct reference.
+ pAttach = i_findAttachment(*mMediumAttachments.data(),
+ aName,
+ aControllerPort,
+ aDevice);
+ if (!oldmedium.isNull())
+ oldmedium->i_removeBackReference(mData->mUuid);
+ if (!pMedium.isNull())
+ {
+ pMedium->i_addBackReference(mData->mUuid);
+
+ mediumLock.release();
+ multiLock.release();
+ i_addMediumToRegistry(pMedium);
+ multiLock.acquire();
+ mediumLock.acquire();
+ }
+
+ AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS);
+ pAttach->i_updateMedium(pMedium);
+ }
+
+ i_setModified(IsModified_Storage);
+
+ mediumLock.release();
+ multiLock.release();
+ HRESULT rc = i_onMediumChange(pAttach, aForce);
+ multiLock.acquire();
+ mediumLock.acquire();
+
+ /* On error roll back this change only. */
+ if (FAILED(rc))
+ {
+ if (!pMedium.isNull())
+ pMedium->i_removeBackReference(mData->mUuid);
+ pAttach = i_findAttachment(*mMediumAttachments.data(),
+ aName,
+ aControllerPort,
+ aDevice);
+ /* If the attachment is gone in the meantime, bail out. */
+ if (pAttach.isNull())
+ return rc;
+ AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS);
+ if (!oldmedium.isNull())
+ oldmedium->i_addBackReference(mData->mUuid);
+ pAttach->i_updateMedium(oldmedium);
+ }
+
+ mediumLock.release();
+ multiLock.release();
+
+ /* Save modified registries, but skip this machine as it's the caller's
+ * job to save its settings like all other settings changes. */
+ mParent->i_unmarkRegistryModified(i_getId());
+ mParent->i_saveModifiedRegistries();
+
+ return rc;
+}
+HRESULT Machine::getMedium(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ ComPtr<IMedium> &aMedium)
+{
+ LogFlowThisFunc(("aName=\"%s\" aControllerPort=%d aDevice=%d\n",
+ aName.c_str(), aControllerPort, aDevice));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aMedium = NULL;
+
+ ComObjPtr<MediumAttachment> pAttach = i_findAttachment(*mMediumAttachments.data(),
+ aName,
+ aControllerPort,
+ aDevice);
+ if (pAttach.isNull())
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("No storage device attached to device slot %d on port %d of controller '%s'"),
+ aDevice, aControllerPort, aName.c_str());
+
+ aMedium = pAttach->i_getMedium();
+
+ return S_OK;
+}
+
+HRESULT Machine::getSerialPort(ULONG aSlot, ComPtr<ISerialPort> &aPort)
+{
+ if (aSlot < RT_ELEMENTS(mSerialPorts))
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mSerialPorts[aSlot].queryInterfaceTo(aPort.asOutParam());
+ return S_OK;
+ }
+ return setError(E_INVALIDARG, tr("Serial port slot %RU32 is out of bounds (max %zu)"), aSlot, RT_ELEMENTS(mSerialPorts));
+}
+
+HRESULT Machine::getParallelPort(ULONG aSlot, ComPtr<IParallelPort> &aPort)
+{
+ if (aSlot < RT_ELEMENTS(mParallelPorts))
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mParallelPorts[aSlot].queryInterfaceTo(aPort.asOutParam());
+ return S_OK;
+ }
+ return setError(E_INVALIDARG, tr("Parallel port slot %RU32 is out of bounds (max %zu)"), aSlot, RT_ELEMENTS(mParallelPorts));
+}
+
+
+HRESULT Machine::getNetworkAdapter(ULONG aSlot, ComPtr<INetworkAdapter> &aAdapter)
+{
+ /* Do not assert if slot is out of range, just return the advertised
+ status. testdriver/vbox.py triggers this in logVmInfo. */
+ if (aSlot >= mNetworkAdapters.size())
+ return setError(E_INVALIDARG,
+ tr("No network adapter in slot %RU32 (total %RU32 adapters)", "", mNetworkAdapters.size()),
+ aSlot, mNetworkAdapters.size());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mNetworkAdapters[aSlot].queryInterfaceTo(aAdapter.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Machine::getExtraDataKeys(std::vector<com::Utf8Str> &aKeys)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aKeys.resize(mData->pMachineConfigFile->mapExtraDataItems.size());
+ size_t i = 0;
+ for (settings::StringsMap::const_iterator
+ it = mData->pMachineConfigFile->mapExtraDataItems.begin();
+ it != mData->pMachineConfigFile->mapExtraDataItems.end();
+ ++it, ++i)
+ aKeys[i] = it->first;
+
+ return S_OK;
+}
+
+ /**
+ * @note Locks this object for reading.
+ */
+HRESULT Machine::getExtraData(const com::Utf8Str &aKey,
+ com::Utf8Str &aValue)
+{
+ /* start with nothing found */
+ aValue = "";
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ settings::StringsMap::const_iterator it = mData->pMachineConfigFile->mapExtraDataItems.find(aKey);
+ if (it != mData->pMachineConfigFile->mapExtraDataItems.end())
+ // found:
+ aValue = it->second; // source is a Utf8Str
+
+ /* return the result to caller (may be empty) */
+ return S_OK;
+}
+
+ /**
+ * @note Locks mParent for writing + this object for writing.
+ */
+HRESULT Machine::setExtraData(const com::Utf8Str &aKey, const com::Utf8Str &aValue)
+{
+ /* Because control characters in aKey have caused problems in the settings
+ * they are rejected unless the key should be deleted. */
+ if (!aValue.isEmpty())
+ {
+ for (size_t i = 0; i < aKey.length(); ++i)
+ {
+ char ch = aKey[i];
+ if (RTLocCIsCntrl(ch))
+ return E_INVALIDARG;
+ }
+ }
+
+ Utf8Str strOldValue; // empty
+
+ // locking note: we only hold the read lock briefly to look up the old value,
+ // then release it and call the onExtraCanChange callbacks. There is a small
+ // chance of a race insofar as the callback might be called twice if two callers
+ // change the same key at the same time, but that's a much better solution
+ // than the deadlock we had here before. The actual changing of the extradata
+ // is then performed under the write lock and race-free.
+
+ // look up the old value first; if nothing has changed then we need not do anything
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); // hold read lock only while looking up
+
+ // For snapshots don't even think about allowing changes, extradata
+ // is global for a machine, so there is nothing snapshot specific.
+ if (i_isSnapshotMachine())
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot set extradata for a snapshot"));
+
+ // check if the right IMachine instance is used
+ if (mData->mRegistered && !i_isSessionMachine())
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot set extradata for an immutable machine"));
+
+ settings::StringsMap::const_iterator it = mData->pMachineConfigFile->mapExtraDataItems.find(aKey);
+ if (it != mData->pMachineConfigFile->mapExtraDataItems.end())
+ strOldValue = it->second;
+ }
+
+ bool fChanged;
+ if ((fChanged = (strOldValue != aValue)))
+ {
+ // ask for permission from all listeners outside the locks;
+ // i_onExtraDataCanChange() only briefly requests the VirtualBox
+ // lock to copy the list of callbacks to invoke
+ Bstr bstrError;
+ if (!mParent->i_onExtraDataCanChange(mData->mUuid, aKey, aValue, bstrError))
+ {
+ const char *sep = bstrError.isEmpty() ? "" : ": ";
+ Log1WarningFunc(("Someone vetoed! Change refused%s%ls\n", sep, bstrError.raw()));
+ return setError(E_ACCESSDENIED,
+ tr("Could not set extra data because someone refused the requested change of '%s' to '%s'%s%ls"),
+ aKey.c_str(),
+ aValue.c_str(),
+ sep,
+ bstrError.raw());
+ }
+
+ // data is changing and change not vetoed: then write it out under the lock
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aValue.isEmpty())
+ mData->pMachineConfigFile->mapExtraDataItems.erase(aKey);
+ else
+ mData->pMachineConfigFile->mapExtraDataItems[aKey] = aValue;
+ // creates a new key if needed
+
+ bool fNeedsGlobalSaveSettings = false;
+ // This saving of settings is tricky: there is no "old state" for the
+ // extradata items at all (unlike all other settings), so the old/new
+ // settings comparison would give a wrong result!
+ i_saveSettings(&fNeedsGlobalSaveSettings, alock, SaveS_Force);
+
+ if (fNeedsGlobalSaveSettings)
+ {
+ // save the global settings; for that we should hold only the VirtualBox lock
+ alock.release();
+ AutoWriteLock vboxlock(mParent COMMA_LOCKVAL_SRC_POS);
+ mParent->i_saveSettings();
+ }
+ }
+
+ // fire notification outside the lock
+ if (fChanged)
+ mParent->i_onExtraDataChanged(mData->mUuid, aKey, aValue);
+
+ return S_OK;
+}
+
+HRESULT Machine::setSettingsFilePath(const com::Utf8Str &aSettingsFilePath, ComPtr<IProgress> &aProgress)
+{
+ aProgress = NULL;
+ NOREF(aSettingsFilePath);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::saveSettings()
+{
+ AutoWriteLock mlock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrSavedOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ /* the settings file path may never be null */
+ ComAssertRet(!mData->m_strConfigFileFull.isEmpty(), E_FAIL);
+
+ /* save all VM data excluding snapshots */
+ bool fNeedsGlobalSaveSettings = false;
+ rc = i_saveSettings(&fNeedsGlobalSaveSettings, mlock);
+ mlock.release();
+
+ if (SUCCEEDED(rc) && fNeedsGlobalSaveSettings)
+ {
+ // save the global settings; for that we should hold only the VirtualBox lock
+ AutoWriteLock vlock(mParent COMMA_LOCKVAL_SRC_POS);
+ rc = mParent->i_saveSettings();
+ }
+
+ return rc;
+}
+
+
+HRESULT Machine::discardSettings()
+{
+ /*
+ * We need to take the machine list lock here as well as the machine one
+ * or we'll get into trouble should any media stuff require rolling back.
+ *
+ * Details:
+ *
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 Wrong locking order! [uId=00007ff6853f6c34 thrd=ALIEN-1]
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 Lock: s 000000000259ef40 RTCritSectRw-3 srec=000000000259f150 cls=4-LISTOFMACHINES/any [s]
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 Other lock: 00000000025ec710 RTCritSectRw-158 own=ALIEN-1 r=1 cls=5-MACHINEOBJECT/any pos={MachineImpl.cpp(5085) Machine::discardSettings 00007ff6853f6ce4} [x]
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 My class: class=0000000000d5eb10 4-LISTOFMACHINES created={AutoLock.cpp(98) util::InitAutoLockSystem 00007ff6853f571f} sub-class=any
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 My class: Prior: #00: 2-VIRTUALBOXOBJECT, manually , 4 lookups
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 My class: #01: 3-HOSTOBJECT, manually , 0 lookups
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 My class: Hash Stats: 3 hits, 1 misses
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 Other class: class=0000000000d5ecd0 5-MACHINEOBJECT created={AutoLock.cpp(98) util::InitAutoLockSystem 00007ff6853f571f} sub-class=any
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 Other class: Prior: #00: 2-VIRTUALBOXOBJECT, manually , 2 lookups
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 Other class: #01: 3-HOSTOBJECT, manually , 6 lookups
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 Other class: #02: 4-LISTOFMACHINES, manually , 5 lookups
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 Other class: Hash Stats: 10 hits, 3 misses
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 ---- start of lock stack for 000000000259d2d0 ALIEN-1 - 2 entries ----
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 #00: 00000000025ec710 RTCritSectRw-158 own=ALIEN-1 r=2 cls=5-MACHINEOBJECT/any pos={MachineImpl.cpp(11705) Machine::i_rollback 00007ff6853f6ce4} [x/r]
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 #01: 00000000025ec710 RTCritSectRw-158 own=ALIEN-1 r=1 cls=5-MACHINEOBJECT/any pos={MachineImpl.cpp(5085) Machine::discardSettings 00007ff6853f6ce4} [x] (*)
+ * 11:06:01.934284 00:00:05.805182 ALIEN-1 ---- end of lock stack ----
+ * 0:005> k
+ * # Child-SP RetAddr Call Site
+ * 00 00000000`0287bc90 00007ffc`8c0bc8dc VBoxRT!rtLockValComplainPanic+0x23 [e:\vbox\svn\trunk\src\vbox\runtime\common\misc\lockvalidator.cpp @ 807]
+ * 01 00000000`0287bcc0 00007ffc`8c0bc083 VBoxRT!rtLockValidatorStackWrongOrder+0xac [e:\vbox\svn\trunk\src\vbox\runtime\common\misc\lockvalidator.cpp @ 2149]
+ * 02 00000000`0287bd10 00007ffc`8c0bbfc3 VBoxRT!rtLockValidatorStackCheckLockingOrder2+0x93 [e:\vbox\svn\trunk\src\vbox\runtime\common\misc\lockvalidator.cpp @ 2227]
+ * 03 00000000`0287bdd0 00007ffc`8c0bf3c0 VBoxRT!rtLockValidatorStackCheckLockingOrder+0x523 [e:\vbox\svn\trunk\src\vbox\runtime\common\misc\lockvalidator.cpp @ 2406]
+ * 04 00000000`0287be40 00007ffc`8c180de4 VBoxRT!RTLockValidatorRecSharedCheckOrder+0x210 [e:\vbox\svn\trunk\src\vbox\runtime\common\misc\lockvalidator.cpp @ 3607]
+ * 05 00000000`0287be90 00007ffc`8c1819b8 VBoxRT!rtCritSectRwEnterShared+0x1a4 [e:\vbox\svn\trunk\src\vbox\runtime\generic\critsectrw-generic.cpp @ 222]
+ * 06 00000000`0287bf60 00007ff6`853f5e78 VBoxRT!RTCritSectRwEnterSharedDebug+0x58 [e:\vbox\svn\trunk\src\vbox\runtime\generic\critsectrw-generic.cpp @ 428]
+ * 07 00000000`0287bfb0 00007ff6`853f6c34 VBoxSVC!util::RWLockHandle::lockRead+0x58 [e:\vbox\svn\trunk\src\vbox\main\glue\autolock.cpp @ 245]
+ * 08 00000000`0287c000 00007ff6`853f68a1 VBoxSVC!util::AutoReadLock::callLockImpl+0x64 [e:\vbox\svn\trunk\src\vbox\main\glue\autolock.cpp @ 552]
+ * 09 00000000`0287c040 00007ff6`853f6a59 VBoxSVC!util::AutoLockBase::callLockOnAllHandles+0xa1 [e:\vbox\svn\trunk\src\vbox\main\glue\autolock.cpp @ 455]
+ * 0a 00000000`0287c0a0 00007ff6`85038fdb VBoxSVC!util::AutoLockBase::acquire+0x89 [e:\vbox\svn\trunk\src\vbox\main\glue\autolock.cpp @ 500]
+ * 0b 00000000`0287c0d0 00007ff6`85216dcf VBoxSVC!util::AutoReadLock::AutoReadLock+0x7b [e:\vbox\svn\trunk\include\vbox\com\autolock.h @ 370]
+ * 0c 00000000`0287c120 00007ff6`8521cf08 VBoxSVC!VirtualBox::i_findMachine+0x14f [e:\vbox\svn\trunk\src\vbox\main\src-server\virtualboximpl.cpp @ 3216]
+ * 0d 00000000`0287c260 00007ff6`8517a4b0 VBoxSVC!VirtualBox::i_markRegistryModified+0xa8 [e:\vbox\svn\trunk\src\vbox\main\src-server\virtualboximpl.cpp @ 4697]
+ * 0e 00000000`0287c2f0 00007ff6`8517fac0 VBoxSVC!Medium::i_markRegistriesModified+0x170 [e:\vbox\svn\trunk\src\vbox\main\src-server\mediumimpl.cpp @ 4056]
+ * 0f 00000000`0287c500 00007ff6`8511ca9d VBoxSVC!Medium::i_deleteStorage+0xb90 [e:\vbox\svn\trunk\src\vbox\main\src-server\mediumimpl.cpp @ 5114]
+ * 10 00000000`0287cad0 00007ff6`8511ef0e VBoxSVC!Machine::i_deleteImplicitDiffs+0x11ed [e:\vbox\svn\trunk\src\vbox\main\src-server\machineimpl.cpp @ 11117]
+ * 11 00000000`0287d2e0 00007ff6`8511f896 VBoxSVC!Machine::i_rollbackMedia+0x42e [e:\vbox\svn\trunk\src\vbox\main\src-server\machineimpl.cpp @ 11657]
+ * 12 00000000`0287d3c0 00007ff6`850fd17a VBoxSVC!Machine::i_rollback+0x6a6 [e:\vbox\svn\trunk\src\vbox\main\src-server\machineimpl.cpp @ 11786]
+ * 13 00000000`0287d710 00007ff6`85342dbe VBoxSVC!Machine::discardSettings+0x9a [e:\vbox\svn\trunk\src\vbox\main\src-server\machineimpl.cpp @ 5096]
+ * 14 00000000`0287d790 00007ffc`c06813ff VBoxSVC!MachineWrap::DiscardSettings+0x16e [e:\vbox\svn\trunk\out\win.amd64\debug\obj\vboxapiwrap\machinewrap.cpp @ 9171]
+ *
+ */
+ AutoReadLock alockMachines(mParent->i_getMachinesListLockHandle() COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrSavedOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ /*
+ * during this rollback, the session will be notified if data has
+ * been actually changed
+ */
+ i_rollback(true /* aNotify */);
+
+ return S_OK;
+}
+
+/** @note Locks objects! */
+HRESULT Machine::unregister(AutoCaller &autoCaller,
+ CleanupMode_T aCleanupMode,
+ std::vector<ComPtr<IMedium> > &aMedia)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Guid id(i_getId());
+
+ if (mData->mSession.mState != SessionState_Unlocked)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot unregister the machine '%s' while it is locked"),
+ mUserData->s.strName.c_str());
+
+ // wait for state dependents to drop to zero
+ i_ensureNoStateDependencies(alock);
+
+ if (!mData->mAccessible)
+ {
+ // inaccessible machines can only be unregistered; uninitialize ourselves
+ // here because currently there may be no unregistered that are inaccessible
+ // (this state combination is not supported). Note releasing the caller and
+ // leaving the lock before calling uninit()
+ alock.release();
+ autoCaller.release();
+
+ uninit();
+
+ mParent->i_unregisterMachine(this, CleanupMode_UnregisterOnly, id);
+ // calls VirtualBox::i_saveSettings()
+
+ return S_OK;
+ }
+
+ HRESULT rc = S_OK;
+ mData->llFilesToDelete.clear();
+
+ if (!mSSData->strStateFilePath.isEmpty())
+ mData->llFilesToDelete.push_back(mSSData->strStateFilePath);
+
+ Utf8Str strNVRAMFile = mNvramStore->i_getNonVolatileStorageFile();
+ if (!strNVRAMFile.isEmpty() && RTFileExists(strNVRAMFile.c_str()))
+ mData->llFilesToDelete.push_back(strNVRAMFile);
+
+ // This list collects the medium objects from all medium attachments
+ // which we will detach from the machine and its snapshots, in a specific
+ // order which allows for closing all media without getting "media in use"
+ // errors, simply by going through the list from the front to the back:
+ // 1) first media from machine attachments (these have the "leaf" attachments with snapshots
+ // and must be closed before the parent media from the snapshots, or closing the parents
+ // will fail because they still have children);
+ // 2) media from the youngest snapshots followed by those from the parent snapshots until
+ // the root ("first") snapshot of the machine.
+ MediaList llMedia;
+
+ if ( !mMediumAttachments.isNull() // can be NULL if machine is inaccessible
+ && mMediumAttachments->size()
+ )
+ {
+ // we have media attachments: detach them all and add the Medium objects to our list
+ i_detachAllMedia(alock, NULL /* pSnapshot */, aCleanupMode, llMedia);
+ }
+
+ if (mData->mFirstSnapshot)
+ {
+ // add the media from the medium attachments of the snapshots to
+ // llMedia as well, after the "main" machine media;
+ // Snapshot::uninitAll() calls Machine::detachAllMedia() for each
+ // snapshot machine, depth first.
+
+ // Snapshot::beginDeletingSnapshot() asserts if the machine state is not this
+ MachineState_T oldState = mData->mMachineState;
+ mData->mMachineState = MachineState_DeletingSnapshot;
+
+ // make a copy of the first snapshot reference so the refcount does not
+ // drop to 0 in beginDeletingSnapshot, which sets pFirstSnapshot to 0
+ // (would hang due to the AutoCaller voodoo)
+ ComObjPtr<Snapshot> pFirstSnapshot = mData->mFirstSnapshot;
+
+ // GO!
+ pFirstSnapshot->i_uninitAll(alock, aCleanupMode, llMedia, mData->llFilesToDelete);
+
+ mData->mMachineState = oldState;
+ }
+
+ if (FAILED(rc))
+ {
+ i_rollbackMedia();
+ return rc;
+ }
+
+ // commit all the media changes made above
+ i_commitMedia();
+
+ mData->mRegistered = false;
+
+ // machine lock no longer needed
+ alock.release();
+
+ /* Make sure that the settings of the current VM are not saved, because
+ * they are rather crippled at this point to meet the cleanup expectations
+ * and there's no point destroying the VM config on disk just because. */
+ mParent->i_unmarkRegistryModified(id);
+
+ // return media to caller
+ aMedia.resize(llMedia.size());
+ size_t i = 0;
+ for (MediaList::const_iterator
+ it = llMedia.begin();
+ it != llMedia.end();
+ ++it, ++i)
+ (*it).queryInterfaceTo(aMedia[i].asOutParam());
+
+ mParent->i_unregisterMachine(this, aCleanupMode, id);
+ // calls VirtualBox::i_saveSettings() and VirtualBox::saveModifiedRegistries()
+
+ return S_OK;
+}
+
+/**
+ * Task record for deleting a machine config.
+ */
+class Machine::DeleteConfigTask
+ : public Machine::Task
+{
+public:
+ DeleteConfigTask(Machine *m,
+ Progress *p,
+ const Utf8Str &t,
+ const RTCList<ComPtr<IMedium> > &llMediums,
+ const StringsList &llFilesToDelete)
+ : Task(m, p, t),
+ m_llMediums(llMediums),
+ m_llFilesToDelete(llFilesToDelete)
+ {}
+
+private:
+ void handler()
+ {
+ try
+ {
+ m_pMachine->i_deleteConfigHandler(*this);
+ }
+ catch (...)
+ {
+ LogRel(("Some exception in the function Machine::i_deleteConfigHandler()\n"));
+ }
+ }
+
+ RTCList<ComPtr<IMedium> > m_llMediums;
+ StringsList m_llFilesToDelete;
+
+ friend void Machine::i_deleteConfigHandler(DeleteConfigTask &task);
+};
+
+/**
+ * Task thread implementation for SessionMachine::DeleteConfig(), called from
+ * SessionMachine::taskHandler().
+ *
+ * @note Locks this object for writing.
+ *
+ * @param task
+ * @return
+ */
+void Machine::i_deleteConfigHandler(DeleteConfigTask &task)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ LogFlowThisFunc(("state=%d\n", getObjectState().getState()));
+ if (FAILED(autoCaller.rc()))
+ {
+ /* we might have been uninitialized because the session was accidentally
+ * closed by the client, so don't assert */
+ HRESULT rc = setError(E_FAIL,
+ tr("The session has been accidentally closed"));
+ task.m_pProgress->i_notifyComplete(rc);
+ LogFlowThisFuncLeave();
+ return;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ try
+ {
+ ULONG uLogHistoryCount = 3;
+ ComPtr<ISystemProperties> systemProperties;
+ rc = mParent->COMGETTER(SystemProperties)(systemProperties.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ if (!systemProperties.isNull())
+ {
+ rc = systemProperties->COMGETTER(LogHistoryCount)(&uLogHistoryCount);
+ if (FAILED(rc)) throw rc;
+ }
+
+ MachineState_T oldState = mData->mMachineState;
+ i_setMachineState(MachineState_SettingUp);
+ alock.release();
+ for (size_t i = 0; i < task.m_llMediums.size(); ++i)
+ {
+ ComObjPtr<Medium> pMedium = (Medium*)(IMedium*)(task.m_llMediums.at(i));
+ {
+ AutoCaller mac(pMedium);
+ if (FAILED(mac.rc())) throw mac.rc();
+ Utf8Str strLocation = pMedium->i_getLocationFull();
+ LogFunc(("Deleting file %s\n", strLocation.c_str()));
+ rc = task.m_pProgress->SetNextOperation(BstrFmt(tr("Deleting '%s'"), strLocation.c_str()).raw(), 1);
+ if (FAILED(rc)) throw rc;
+ }
+ if (pMedium->i_isMediumFormatFile())
+ {
+ ComPtr<IProgress> pProgress2;
+ rc = pMedium->DeleteStorage(pProgress2.asOutParam());
+ if (FAILED(rc)) throw rc;
+ rc = task.m_pProgress->WaitForOtherProgressCompletion(pProgress2, 0 /* indefinite wait */);
+ if (FAILED(rc)) throw rc;
+ }
+
+ /* Close the medium, deliberately without checking the return
+ * code, and without leaving any trace in the error info, as
+ * a failure here is a very minor issue, which shouldn't happen
+ * as above we even managed to delete the medium. */
+ {
+ ErrorInfoKeeper eik;
+ pMedium->Close();
+ }
+ }
+ i_setMachineState(oldState);
+ alock.acquire();
+
+ // delete the files pushed on the task list by Machine::Delete()
+ // (this includes saved states of the machine and snapshots and
+ // medium storage files from the IMedium list passed in, and the
+ // machine XML file)
+ for (StringsList::const_iterator
+ it = task.m_llFilesToDelete.begin();
+ it != task.m_llFilesToDelete.end();
+ ++it)
+ {
+ const Utf8Str &strFile = *it;
+ LogFunc(("Deleting file %s\n", strFile.c_str()));
+ rc = task.m_pProgress->SetNextOperation(BstrFmt(tr("Deleting '%s'"), it->c_str()).raw(), 1);
+ if (FAILED(rc)) throw rc;
+ i_deleteFile(strFile);
+ }
+
+ rc = task.m_pProgress->SetNextOperation(Bstr(tr("Cleaning up machine directory")).raw(), 1);
+ if (FAILED(rc)) throw rc;
+
+ /* delete the settings only when the file actually exists */
+ if (mData->pMachineConfigFile->fileExists())
+ {
+ /* Delete any backup or uncommitted XML files. Ignore failures.
+ See the fSafe parameter of xml::XmlFileWriter::write for details. */
+ /** @todo Find a way to avoid referring directly to iprt/xml.h here. */
+ Utf8StrFmt otherXml("%s%s", mData->m_strConfigFileFull.c_str(), xml::XmlFileWriter::s_pszTmpSuff);
+ i_deleteFile(otherXml, true /* fIgnoreFailures */);
+ otherXml.printf("%s%s", mData->m_strConfigFileFull.c_str(), xml::XmlFileWriter::s_pszPrevSuff);
+ i_deleteFile(otherXml, true /* fIgnoreFailures */);
+
+ /* delete the Logs folder, nothing important should be left
+ * there (we don't check for errors because the user might have
+ * some private files there that we don't want to delete) */
+ Utf8Str logFolder;
+ getLogFolder(logFolder);
+ Assert(logFolder.length());
+ if (RTDirExists(logFolder.c_str()))
+ {
+ /* Delete all VBox.log[.N] files from the Logs folder
+ * (this must be in sync with the rotation logic in
+ * Console::powerUpThread()). Also, delete the VBox.png[.N]
+ * files that may have been created by the GUI. */
+ Utf8StrFmt log("%s%cVBox.log", logFolder.c_str(), RTPATH_DELIMITER);
+ i_deleteFile(log, true /* fIgnoreFailures */);
+ log.printf("%s%cVBox.png", logFolder.c_str(), RTPATH_DELIMITER);
+ i_deleteFile(log, true /* fIgnoreFailures */);
+ for (ULONG i = uLogHistoryCount; i > 0; i--)
+ {
+ log.printf("%s%cVBox.log.%u", logFolder.c_str(), RTPATH_DELIMITER, i);
+ i_deleteFile(log, true /* fIgnoreFailures */);
+ log.printf("%s%cVBox.png.%u", logFolder.c_str(), RTPATH_DELIMITER, i);
+ i_deleteFile(log, true /* fIgnoreFailures */);
+ }
+ log.printf("%s%cVBoxUI.log", logFolder.c_str(), RTPATH_DELIMITER);
+ i_deleteFile(log, true /* fIgnoreFailures */);
+#if defined(RT_OS_WINDOWS)
+ log.printf("%s%cVBoxStartup.log", logFolder.c_str(), RTPATH_DELIMITER);
+ i_deleteFile(log, true /* fIgnoreFailures */);
+ log.printf("%s%cVBoxHardening.log", logFolder.c_str(), RTPATH_DELIMITER);
+ i_deleteFile(log, true /* fIgnoreFailures */);
+#endif
+
+ RTDirRemove(logFolder.c_str());
+ }
+
+ /* delete the Snapshots folder, nothing important should be left
+ * there (we don't check for errors because the user might have
+ * some private files there that we don't want to delete) */
+ Utf8Str strFullSnapshotFolder;
+ i_calculateFullPath(mUserData->s.strSnapshotFolder, strFullSnapshotFolder);
+ Assert(!strFullSnapshotFolder.isEmpty());
+ if (RTDirExists(strFullSnapshotFolder.c_str()))
+ RTDirRemove(strFullSnapshotFolder.c_str());
+
+ // delete the directory that contains the settings file, but only
+ // if it matches the VM name
+ Utf8Str settingsDir;
+ if (i_isInOwnDir(&settingsDir))
+ RTDirRemove(settingsDir.c_str());
+ }
+
+ alock.release();
+
+ mParent->i_saveModifiedRegistries();
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ task.m_pProgress->i_notifyComplete(rc);
+
+ LogFlowThisFuncLeave();
+}
+
+HRESULT Machine::deleteConfig(const std::vector<ComPtr<IMedium> > &aMedia, ComPtr<IProgress> &aProgress)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ if (mData->mRegistered)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot delete settings of a registered machine"));
+
+ // collect files to delete
+ StringsList llFilesToDelete(mData->llFilesToDelete); // saved states and NVRAM files pushed here by Unregister()
+ // machine config file
+ if (mData->pMachineConfigFile->fileExists())
+ llFilesToDelete.push_back(mData->m_strConfigFileFull);
+ // backup of machine config file
+ Utf8Str strTmp(mData->m_strConfigFileFull);
+ strTmp.append("-prev");
+ if (RTFileExists(strTmp.c_str()))
+ llFilesToDelete.push_back(strTmp);
+
+ RTCList<ComPtr<IMedium> > llMediums;
+ for (size_t i = 0; i < aMedia.size(); ++i)
+ {
+ IMedium *pIMedium(aMedia[i]);
+ ComObjPtr<Medium> pMedium = static_cast<Medium*>(pIMedium);
+ if (pMedium.isNull())
+ return setError(E_INVALIDARG, tr("The given medium pointer with index %d is invalid"), i);
+ SafeArray<BSTR> ids;
+ rc = pMedium->COMGETTER(MachineIds)(ComSafeArrayAsOutParam(ids));
+ if (FAILED(rc)) return rc;
+ /* At this point the medium should not have any back references
+ * anymore. If it has it is attached to another VM and *must* not
+ * deleted. */
+ if (ids.size() < 1)
+ llMediums.append(pMedium);
+ }
+
+ ComObjPtr<Progress> pProgress;
+ pProgress.createObject();
+ rc = pProgress->init(i_getVirtualBox(),
+ static_cast<IMachine*>(this) /* aInitiator */,
+ tr("Deleting files"),
+ true /* fCancellable */,
+ (ULONG)(1 + llMediums.size() + llFilesToDelete.size() + 1), // cOperations
+ tr("Collecting file inventory"));
+ if (FAILED(rc))
+ return rc;
+
+ /* create and start the task on a separate thread (note that it will not
+ * start working until we release alock) */
+ DeleteConfigTask *pTask = new DeleteConfigTask(this, pProgress, "DeleteVM", llMediums, llFilesToDelete);
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (FAILED(rc))
+ return rc;
+
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+
+ LogFlowFuncLeave();
+
+ return S_OK;
+}
+
+HRESULT Machine::findSnapshot(const com::Utf8Str &aNameOrId, ComPtr<ISnapshot> &aSnapshot)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<Snapshot> pSnapshot;
+ HRESULT rc;
+
+ if (aNameOrId.isEmpty())
+ // null case (caller wants root snapshot): i_findSnapshotById() handles this
+ rc = i_findSnapshotById(Guid(), pSnapshot, true /* aSetError */);
+ else
+ {
+ Guid uuid(aNameOrId);
+ if (uuid.isValid())
+ rc = i_findSnapshotById(uuid, pSnapshot, true /* aSetError */);
+ else
+ rc = i_findSnapshotByName(aNameOrId, pSnapshot, true /* aSetError */);
+ }
+ pSnapshot.queryInterfaceTo(aSnapshot.asOutParam());
+
+ return rc;
+}
+
+HRESULT Machine::createSharedFolder(const com::Utf8Str &aName, const com::Utf8Str &aHostPath, BOOL aWritable,
+ BOOL aAutomount, const com::Utf8Str &aAutoMountPoint)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ ComObjPtr<SharedFolder> sharedFolder;
+ rc = i_findSharedFolder(aName, sharedFolder, false /* aSetError */);
+ if (SUCCEEDED(rc))
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Shared folder named '%s' already exists"),
+ aName.c_str());
+
+ sharedFolder.createObject();
+ rc = sharedFolder->init(i_getMachine(),
+ aName,
+ aHostPath,
+ !!aWritable,
+ !!aAutomount,
+ aAutoMountPoint,
+ true /* fFailOnError */);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_SharedFolders);
+ mHWData.backup();
+ mHWData->mSharedFolders.push_back(sharedFolder);
+
+ /* inform the direct session if any */
+ alock.release();
+ i_onSharedFolderChange();
+
+ return S_OK;
+}
+
+HRESULT Machine::removeSharedFolder(const com::Utf8Str &aName)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ ComObjPtr<SharedFolder> sharedFolder;
+ rc = i_findSharedFolder(aName, sharedFolder, true /* aSetError */);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_SharedFolders);
+ mHWData.backup();
+ mHWData->mSharedFolders.remove(sharedFolder);
+
+ /* inform the direct session if any */
+ alock.release();
+ i_onSharedFolderChange();
+
+ return S_OK;
+}
+
+HRESULT Machine::canShowConsoleWindow(BOOL *aCanShow)
+{
+ /* start with No */
+ *aCanShow = FALSE;
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->mSession.mState != SessionState_Locked)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Machine is not locked for session (session state: %s)"),
+ Global::stringifySessionState(mData->mSession.mState));
+
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore calls made after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ LONG64 dummy;
+ return directControl->OnShowWindow(TRUE /* aCheck */, aCanShow, &dummy);
+}
+
+HRESULT Machine::showConsoleWindow(LONG64 *aWinId)
+{
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->mSession.mState != SessionState_Locked)
+ return setError(E_FAIL,
+ tr("Machine is not locked for session (session state: %s)"),
+ Global::stringifySessionState(mData->mSession.mState));
+
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore calls made after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ BOOL dummy;
+ return directControl->OnShowWindow(FALSE /* aCheck */, &dummy, aWinId);
+}
+
+#ifdef VBOX_WITH_GUEST_PROPS
+/**
+ * Look up a guest property in VBoxSVC's internal structures.
+ */
+HRESULT Machine::i_getGuestPropertyFromService(const com::Utf8Str &aName,
+ com::Utf8Str &aValue,
+ LONG64 *aTimestamp,
+ com::Utf8Str &aFlags) const
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HWData::GuestPropertyMap::const_iterator it = mHWData->mGuestProperties.find(aName);
+ if (it != mHWData->mGuestProperties.end())
+ {
+ char szFlags[GUEST_PROP_MAX_FLAGS_LEN + 1];
+ aValue = it->second.strValue;
+ *aTimestamp = it->second.mTimestamp;
+ GuestPropWriteFlags(it->second.mFlags, szFlags);
+ aFlags = Utf8Str(szFlags);
+ }
+
+ return S_OK;
+}
+
+/**
+ * Query the VM that a guest property belongs to for the property.
+ * @returns E_ACCESSDENIED if the VM process is not available or not
+ * currently handling queries and the lookup should then be done in
+ * VBoxSVC.
+ */
+HRESULT Machine::i_getGuestPropertyFromVM(const com::Utf8Str &aName,
+ com::Utf8Str &aValue,
+ LONG64 *aTimestamp,
+ com::Utf8Str &aFlags) const
+{
+ HRESULT rc = S_OK;
+ Bstr bstrValue;
+ Bstr bstrFlags;
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore calls made after #OnSessionEnd() is called */
+ if (!directControl)
+ rc = E_ACCESSDENIED;
+ else
+ rc = directControl->AccessGuestProperty(Bstr(aName).raw(), Bstr::Empty.raw(), Bstr::Empty.raw(),
+ 0 /* accessMode */,
+ bstrValue.asOutParam(), aTimestamp, bstrFlags.asOutParam());
+
+ aValue = bstrValue;
+ aFlags = bstrFlags;
+
+ return rc;
+}
+#endif // VBOX_WITH_GUEST_PROPS
+
+HRESULT Machine::getGuestProperty(const com::Utf8Str &aName,
+ com::Utf8Str &aValue,
+ LONG64 *aTimestamp,
+ com::Utf8Str &aFlags)
+{
+#ifndef VBOX_WITH_GUEST_PROPS
+ ReturnComNotImplemented();
+#else // VBOX_WITH_GUEST_PROPS
+
+ HRESULT rc = i_getGuestPropertyFromVM(aName, aValue, aTimestamp, aFlags);
+
+ if (rc == E_ACCESSDENIED)
+ /* The VM is not running or the service is not (yet) accessible */
+ rc = i_getGuestPropertyFromService(aName, aValue, aTimestamp, aFlags);
+ return rc;
+#endif // VBOX_WITH_GUEST_PROPS
+}
+
+HRESULT Machine::getGuestPropertyValue(const com::Utf8Str &aProperty, com::Utf8Str &aValue)
+{
+ LONG64 dummyTimestamp;
+ com::Utf8Str dummyFlags;
+ HRESULT rc = getGuestProperty(aProperty, aValue, &dummyTimestamp, dummyFlags);
+ return rc;
+
+}
+HRESULT Machine::getGuestPropertyTimestamp(const com::Utf8Str &aProperty, LONG64 *aValue)
+{
+ com::Utf8Str dummyFlags;
+ com::Utf8Str dummyValue;
+ HRESULT rc = getGuestProperty(aProperty, dummyValue, aValue, dummyFlags);
+ return rc;
+}
+
+#ifdef VBOX_WITH_GUEST_PROPS
+/**
+ * Set a guest property in VBoxSVC's internal structures.
+ */
+HRESULT Machine::i_setGuestPropertyToService(const com::Utf8Str &aName, const com::Utf8Str &aValue,
+ const com::Utf8Str &aFlags, bool fDelete)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = i_checkStateDependency(MutableOrSavedStateDep);
+ if (FAILED(rc)) return rc;
+
+ try
+ {
+ uint32_t fFlags = GUEST_PROP_F_NILFLAG;
+ if (aFlags.length() && RT_FAILURE(GuestPropValidateFlags(aFlags.c_str(), &fFlags)))
+ return setError(E_INVALIDARG, tr("Invalid guest property flag values: '%s'"), aFlags.c_str());
+
+ if (fFlags & (GUEST_PROP_F_TRANSIENT | GUEST_PROP_F_TRANSRESET))
+ return setError(E_INVALIDARG, tr("Properties with TRANSIENT or TRANSRESET flag cannot be set or modified if VM is not running"));
+
+ HWData::GuestPropertyMap::iterator it = mHWData->mGuestProperties.find(aName);
+ if (it == mHWData->mGuestProperties.end())
+ {
+ if (!fDelete)
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData.backupEx();
+
+ RTTIMESPEC time;
+ HWData::GuestProperty prop;
+ prop.strValue = aValue;
+ prop.mTimestamp = RTTimeSpecGetNano(RTTimeNow(&time));
+ prop.mFlags = fFlags;
+ mHWData->mGuestProperties[aName] = prop;
+ }
+ }
+ else
+ {
+ if (it->second.mFlags & (GUEST_PROP_F_RDONLYHOST))
+ {
+ rc = setError(E_ACCESSDENIED, tr("The property '%s' cannot be changed by the host"), aName.c_str());
+ }
+ else
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData.backupEx();
+
+ /* The backupEx() operation invalidates our iterator,
+ * so get a new one. */
+ it = mHWData->mGuestProperties.find(aName);
+ Assert(it != mHWData->mGuestProperties.end());
+
+ if (!fDelete)
+ {
+ RTTIMESPEC time;
+ it->second.strValue = aValue;
+ it->second.mTimestamp = RTTimeSpecGetNano(RTTimeNow(&time));
+ it->second.mFlags = fFlags;
+ }
+ else
+ mHWData->mGuestProperties.erase(it);
+ }
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ alock.release();
+
+ mParent->i_onGuestPropertyChanged(mData->mUuid, aName, aValue, aFlags, fDelete);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = E_OUTOFMEMORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Set a property on the VM that that property belongs to.
+ * @returns E_ACCESSDENIED if the VM process is not available or not
+ * currently handling queries and the setting should then be done in
+ * VBoxSVC.
+ */
+HRESULT Machine::i_setGuestPropertyToVM(const com::Utf8Str &aName, const com::Utf8Str &aValue,
+ const com::Utf8Str &aFlags, bool fDelete)
+{
+ HRESULT rc;
+
+ try
+ {
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ Bstr dummy1; /* will not be changed (setter) */
+ Bstr dummy2; /* will not be changed (setter) */
+ LONG64 dummy64;
+ if (!directControl)
+ rc = E_ACCESSDENIED;
+ else
+ /** @todo Fix when adding DeleteGuestProperty(), see defect. */
+ rc = directControl->AccessGuestProperty(Bstr(aName).raw(), Bstr(aValue).raw(), Bstr(aFlags).raw(),
+ fDelete ? 2 : 1 /* accessMode */,
+ dummy1.asOutParam(), &dummy64, dummy2.asOutParam());
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = E_OUTOFMEMORY;
+ }
+
+ return rc;
+}
+#endif // VBOX_WITH_GUEST_PROPS
+
+HRESULT Machine::setGuestProperty(const com::Utf8Str &aProperty, const com::Utf8Str &aValue,
+ const com::Utf8Str &aFlags)
+{
+#ifndef VBOX_WITH_GUEST_PROPS
+ ReturnComNotImplemented();
+#else // VBOX_WITH_GUEST_PROPS
+
+ int vrc = GuestPropValidateName(aProperty.c_str(), aProperty.length() + 1 /* '\0' */);
+ AssertRCReturn(vrc, setErrorBoth(E_INVALIDARG, vrc));
+
+ vrc = GuestPropValidateValue(aValue.c_str(), aValue.length() + 1 /* '\0' */);
+ AssertRCReturn(vrc, setErrorBoth(E_INVALIDARG, vrc));
+
+ HRESULT rc = i_setGuestPropertyToVM(aProperty, aValue, aFlags, /* fDelete = */ false);
+ if (rc == E_ACCESSDENIED)
+ /* The VM is not running or the service is not (yet) accessible */
+ rc = i_setGuestPropertyToService(aProperty, aValue, aFlags, /* fDelete = */ false);
+ return rc;
+#endif // VBOX_WITH_GUEST_PROPS
+}
+
+HRESULT Machine::setGuestPropertyValue(const com::Utf8Str &aProperty, const com::Utf8Str &aValue)
+{
+ return setGuestProperty(aProperty, aValue, "");
+}
+
+HRESULT Machine::deleteGuestProperty(const com::Utf8Str &aName)
+{
+#ifndef VBOX_WITH_GUEST_PROPS
+ ReturnComNotImplemented();
+#else // VBOX_WITH_GUEST_PROPS
+ HRESULT rc = i_setGuestPropertyToVM(aName, "", "", /* fDelete = */ true);
+ if (rc == E_ACCESSDENIED)
+ /* The VM is not running or the service is not (yet) accessible */
+ rc = i_setGuestPropertyToService(aName, "", "", /* fDelete = */ true);
+ return rc;
+#endif // VBOX_WITH_GUEST_PROPS
+}
+
+#ifdef VBOX_WITH_GUEST_PROPS
+/**
+ * Enumerate the guest properties in VBoxSVC's internal structures.
+ */
+HRESULT Machine::i_enumerateGuestPropertiesInService(const com::Utf8Str &aPatterns,
+ std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<com::Utf8Str> &aFlags)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Utf8Str strPatterns(aPatterns);
+
+ /*
+ * Look for matching patterns and build up a list.
+ */
+ HWData::GuestPropertyMap propMap;
+ for (HWData::GuestPropertyMap::const_iterator
+ it = mHWData->mGuestProperties.begin();
+ it != mHWData->mGuestProperties.end();
+ ++it)
+ {
+ if ( strPatterns.isEmpty()
+ || RTStrSimplePatternMultiMatch(strPatterns.c_str(),
+ RTSTR_MAX,
+ it->first.c_str(),
+ RTSTR_MAX,
+ NULL)
+ )
+ propMap.insert(*it);
+ }
+
+ alock.release();
+
+ /*
+ * And build up the arrays for returning the property information.
+ */
+ size_t cEntries = propMap.size();
+
+ aNames.resize(cEntries);
+ aValues.resize(cEntries);
+ aTimestamps.resize(cEntries);
+ aFlags.resize(cEntries);
+
+ size_t i = 0;
+ for (HWData::GuestPropertyMap::const_iterator
+ it = propMap.begin();
+ it != propMap.end();
+ ++it, ++i)
+ {
+ aNames[i] = it->first;
+ int vrc = GuestPropValidateName(aNames[i].c_str(), aNames[i].length() + 1 /* '\0' */);
+ AssertRCReturn(vrc, setErrorBoth(E_INVALIDARG /*bad choice for internal error*/, vrc));
+
+ aValues[i] = it->second.strValue;
+ vrc = GuestPropValidateValue(aValues[i].c_str(), aValues[i].length() + 1 /* '\0' */);
+ AssertRCReturn(vrc, setErrorBoth(E_INVALIDARG /*bad choice for internal error*/, vrc));
+
+ aTimestamps[i] = it->second.mTimestamp;
+
+ char szFlags[GUEST_PROP_MAX_FLAGS_LEN + 1];
+ GuestPropWriteFlags(it->second.mFlags, szFlags);
+ aFlags[i] = szFlags;
+ }
+
+ return S_OK;
+}
+
+/**
+ * Enumerate the properties managed by a VM.
+ * @returns E_ACCESSDENIED if the VM process is not available or not
+ * currently handling queries and the setting should then be done in
+ * VBoxSVC.
+ */
+HRESULT Machine::i_enumerateGuestPropertiesOnVM(const com::Utf8Str &aPatterns,
+ std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<com::Utf8Str> &aFlags)
+{
+ HRESULT rc;
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ com::SafeArray<BSTR> bNames;
+ com::SafeArray<BSTR> bValues;
+ com::SafeArray<LONG64> bTimestamps;
+ com::SafeArray<BSTR> bFlags;
+
+ if (!directControl)
+ rc = E_ACCESSDENIED;
+ else
+ rc = directControl->EnumerateGuestProperties(Bstr(aPatterns).raw(),
+ ComSafeArrayAsOutParam(bNames),
+ ComSafeArrayAsOutParam(bValues),
+ ComSafeArrayAsOutParam(bTimestamps),
+ ComSafeArrayAsOutParam(bFlags));
+ size_t i;
+ aNames.resize(bNames.size());
+ for (i = 0; i < bNames.size(); ++i)
+ aNames[i] = Utf8Str(bNames[i]);
+ aValues.resize(bValues.size());
+ for (i = 0; i < bValues.size(); ++i)
+ aValues[i] = Utf8Str(bValues[i]);
+ aTimestamps.resize(bTimestamps.size());
+ for (i = 0; i < bTimestamps.size(); ++i)
+ aTimestamps[i] = bTimestamps[i];
+ aFlags.resize(bFlags.size());
+ for (i = 0; i < bFlags.size(); ++i)
+ aFlags[i] = Utf8Str(bFlags[i]);
+
+ return rc;
+}
+#endif // VBOX_WITH_GUEST_PROPS
+HRESULT Machine::enumerateGuestProperties(const com::Utf8Str &aPatterns,
+ std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<com::Utf8Str> &aFlags)
+{
+#ifndef VBOX_WITH_GUEST_PROPS
+ ReturnComNotImplemented();
+#else // VBOX_WITH_GUEST_PROPS
+
+ HRESULT rc = i_enumerateGuestPropertiesOnVM(aPatterns, aNames, aValues, aTimestamps, aFlags);
+
+ if (rc == E_ACCESSDENIED)
+ /* The VM is not running or the service is not (yet) accessible */
+ rc = i_enumerateGuestPropertiesInService(aPatterns, aNames, aValues, aTimestamps, aFlags);
+ return rc;
+#endif // VBOX_WITH_GUEST_PROPS
+}
+
+HRESULT Machine::getMediumAttachmentsOfController(const com::Utf8Str &aName,
+ std::vector<ComPtr<IMediumAttachment> > &aMediumAttachments)
+{
+ MediumAttachmentList atts;
+
+ HRESULT rc = i_getMediumAttachmentsOfController(aName, atts);
+ if (FAILED(rc)) return rc;
+
+ aMediumAttachments.resize(atts.size());
+ size_t i = 0;
+ for (MediumAttachmentList::const_iterator
+ it = atts.begin();
+ it != atts.end();
+ ++it, ++i)
+ (*it).queryInterfaceTo(aMediumAttachments[i].asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Machine::getMediumAttachment(const com::Utf8Str &aName,
+ LONG aControllerPort,
+ LONG aDevice,
+ ComPtr<IMediumAttachment> &aAttachment)
+{
+ LogFlowThisFunc(("aControllerName=\"%s\" aControllerPort=%d aDevice=%d\n",
+ aName.c_str(), aControllerPort, aDevice));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aAttachment = NULL;
+
+ ComObjPtr<MediumAttachment> pAttach = i_findAttachment(*mMediumAttachments.data(),
+ aName,
+ aControllerPort,
+ aDevice);
+ if (pAttach.isNull())
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("No storage device attached to device slot %d on port %d of controller '%s'"),
+ aDevice, aControllerPort, aName.c_str());
+
+ pAttach.queryInterfaceTo(aAttachment.asOutParam());
+
+ return S_OK;
+}
+
+
+HRESULT Machine::addStorageController(const com::Utf8Str &aName,
+ StorageBus_T aConnectionType,
+ ComPtr<IStorageController> &aController)
+{
+ if ( (aConnectionType <= StorageBus_Null)
+ || (aConnectionType > StorageBus_VirtioSCSI))
+ return setError(E_INVALIDARG,
+ tr("Invalid connection type: %d"),
+ aConnectionType);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ /* try to find one with the name first. */
+ ComObjPtr<StorageController> ctrl;
+
+ rc = i_getStorageControllerByName(aName, ctrl, false /* aSetError */);
+ if (SUCCEEDED(rc))
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Storage controller named '%s' already exists"),
+ aName.c_str());
+
+ ctrl.createObject();
+
+ /* get a new instance number for the storage controller */
+ ULONG ulInstance = 0;
+ bool fBootable = true;
+ for (StorageControllerList::const_iterator
+ it = mStorageControllers->begin();
+ it != mStorageControllers->end();
+ ++it)
+ {
+ if ((*it)->i_getStorageBus() == aConnectionType)
+ {
+ ULONG ulCurInst = (*it)->i_getInstance();
+
+ if (ulCurInst >= ulInstance)
+ ulInstance = ulCurInst + 1;
+
+ /* Only one controller of each type can be marked as bootable. */
+ if ((*it)->i_getBootable())
+ fBootable = false;
+ }
+ }
+
+ rc = ctrl->init(this, aName, aConnectionType, ulInstance, fBootable);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_Storage);
+ mStorageControllers.backup();
+ mStorageControllers->push_back(ctrl);
+
+ ctrl.queryInterfaceTo(aController.asOutParam());
+
+ /* inform the direct session if any */
+ alock.release();
+ i_onStorageControllerChange(i_getId(), aName);
+
+ return S_OK;
+}
+
+HRESULT Machine::getStorageControllerByName(const com::Utf8Str &aName,
+ ComPtr<IStorageController> &aStorageController)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<StorageController> ctrl;
+
+ HRESULT rc = i_getStorageControllerByName(aName, ctrl, true /* aSetError */);
+ if (SUCCEEDED(rc))
+ ctrl.queryInterfaceTo(aStorageController.asOutParam());
+
+ return rc;
+}
+
+HRESULT Machine::getStorageControllerByInstance(StorageBus_T aConnectionType,
+ ULONG aInstance,
+ ComPtr<IStorageController> &aStorageController)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (StorageControllerList::const_iterator
+ it = mStorageControllers->begin();
+ it != mStorageControllers->end();
+ ++it)
+ {
+ if ( (*it)->i_getStorageBus() == aConnectionType
+ && (*it)->i_getInstance() == aInstance)
+ {
+ (*it).queryInterfaceTo(aStorageController.asOutParam());
+ return S_OK;
+ }
+ }
+
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Could not find a storage controller with instance number '%lu'"),
+ aInstance);
+}
+
+HRESULT Machine::setStorageControllerBootable(const com::Utf8Str &aName, BOOL aBootable)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ ComObjPtr<StorageController> ctrl;
+
+ rc = i_getStorageControllerByName(aName, ctrl, true /* aSetError */);
+ if (SUCCEEDED(rc))
+ {
+ /* Ensure that only one controller of each type is marked as bootable. */
+ if (aBootable == TRUE)
+ {
+ for (StorageControllerList::const_iterator
+ it = mStorageControllers->begin();
+ it != mStorageControllers->end();
+ ++it)
+ {
+ ComObjPtr<StorageController> aCtrl = (*it);
+
+ if ( (aCtrl->i_getName() != aName)
+ && aCtrl->i_getBootable() == TRUE
+ && aCtrl->i_getStorageBus() == ctrl->i_getStorageBus()
+ && aCtrl->i_getControllerType() == ctrl->i_getControllerType())
+ {
+ aCtrl->i_setBootable(FALSE);
+ break;
+ }
+ }
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ ctrl->i_setBootable(aBootable);
+ i_setModified(IsModified_Storage);
+ }
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ /* inform the direct session if any */
+ alock.release();
+ i_onStorageControllerChange(i_getId(), aName);
+ }
+
+ return rc;
+}
+
+HRESULT Machine::removeStorageController(const com::Utf8Str &aName)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ ComObjPtr<StorageController> ctrl;
+ rc = i_getStorageControllerByName(aName, ctrl, true /* aSetError */);
+ if (FAILED(rc)) return rc;
+
+ MediumAttachmentList llDetachedAttachments;
+ {
+ /* find all attached devices to the appropriate storage controller and detach them all */
+ // make a temporary list because detachDevice invalidates iterators into
+ // mMediumAttachments
+ MediumAttachmentList llAttachments2 = *mMediumAttachments.data();
+
+ for (MediumAttachmentList::const_iterator
+ it = llAttachments2.begin();
+ it != llAttachments2.end();
+ ++it)
+ {
+ MediumAttachment *pAttachTemp = *it;
+
+ AutoCaller localAutoCaller(pAttachTemp);
+ if (FAILED(localAutoCaller.rc())) return localAutoCaller.rc();
+
+ AutoReadLock local_alock(pAttachTemp COMMA_LOCKVAL_SRC_POS);
+
+ if (pAttachTemp->i_getControllerName() == aName)
+ {
+ llDetachedAttachments.push_back(pAttachTemp);
+ rc = i_detachDevice(pAttachTemp, alock, NULL);
+ if (FAILED(rc)) return rc;
+ }
+ }
+ }
+
+ /* send event about detached devices before removing parent controller */
+ for (MediumAttachmentList::const_iterator
+ it = llDetachedAttachments.begin();
+ it != llDetachedAttachments.end();
+ ++it)
+ {
+ mParent->i_onStorageDeviceChanged(*it, TRUE, FALSE);
+ }
+
+ /* We can remove it now. */
+ i_setModified(IsModified_Storage);
+ mStorageControllers.backup();
+
+ ctrl->i_unshare();
+
+ mStorageControllers->remove(ctrl);
+
+ /* inform the direct session if any */
+ alock.release();
+ i_onStorageControllerChange(i_getId(), aName);
+
+ return S_OK;
+}
+
+HRESULT Machine::addUSBController(const com::Utf8Str &aName, USBControllerType_T aType,
+ ComPtr<IUSBController> &aController)
+{
+ if ( (aType <= USBControllerType_Null)
+ || (aType >= USBControllerType_Last))
+ return setError(E_INVALIDARG,
+ tr("Invalid USB controller type: %d"),
+ aType);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ /* try to find one with the same type first. */
+ ComObjPtr<USBController> ctrl;
+
+ rc = i_getUSBControllerByName(aName, ctrl, false /* aSetError */);
+ if (SUCCEEDED(rc))
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("USB controller named '%s' already exists"),
+ aName.c_str());
+
+ /* Check that we don't exceed the maximum number of USB controllers for the given type. */
+ ULONG maxInstances;
+ rc = mParent->i_getSystemProperties()->GetMaxInstancesOfUSBControllerType(mHWData->mChipsetType, aType, &maxInstances);
+ if (FAILED(rc))
+ return rc;
+
+ ULONG cInstances = i_getUSBControllerCountByType(aType);
+ if (cInstances >= maxInstances)
+ return setError(E_INVALIDARG,
+ tr("Too many USB controllers of this type"));
+
+ ctrl.createObject();
+
+ rc = ctrl->init(this, aName, aType);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_USB);
+ mUSBControllers.backup();
+ mUSBControllers->push_back(ctrl);
+
+ ctrl.queryInterfaceTo(aController.asOutParam());
+
+ /* inform the direct session if any */
+ alock.release();
+ i_onUSBControllerChange();
+
+ return S_OK;
+}
+
+HRESULT Machine::getUSBControllerByName(const com::Utf8Str &aName, ComPtr<IUSBController> &aController)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<USBController> ctrl;
+
+ HRESULT rc = i_getUSBControllerByName(aName, ctrl, true /* aSetError */);
+ if (SUCCEEDED(rc))
+ ctrl.queryInterfaceTo(aController.asOutParam());
+
+ return rc;
+}
+
+HRESULT Machine::getUSBControllerCountByType(USBControllerType_T aType,
+ ULONG *aControllers)
+{
+ if ( (aType <= USBControllerType_Null)
+ || (aType >= USBControllerType_Last))
+ return setError(E_INVALIDARG,
+ tr("Invalid USB controller type: %d"),
+ aType);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<USBController> ctrl;
+
+ *aControllers = i_getUSBControllerCountByType(aType);
+
+ return S_OK;
+}
+
+HRESULT Machine::removeUSBController(const com::Utf8Str &aName)
+{
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ ComObjPtr<USBController> ctrl;
+ rc = i_getUSBControllerByName(aName, ctrl, true /* aSetError */);
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_USB);
+ mUSBControllers.backup();
+
+ ctrl->i_unshare();
+
+ mUSBControllers->remove(ctrl);
+
+ /* inform the direct session if any */
+ alock.release();
+ i_onUSBControllerChange();
+
+ return S_OK;
+}
+
+HRESULT Machine::querySavedGuestScreenInfo(ULONG aScreenId,
+ ULONG *aOriginX,
+ ULONG *aOriginY,
+ ULONG *aWidth,
+ ULONG *aHeight,
+ BOOL *aEnabled)
+{
+ uint32_t u32OriginX= 0;
+ uint32_t u32OriginY= 0;
+ uint32_t u32Width = 0;
+ uint32_t u32Height = 0;
+ uint16_t u16Flags = 0;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ SsmStream SavedStateStream(mParent, mData->mpKeyStore, mSSData->strStateKeyId, mSSData->strStateKeyStore);
+#else
+ SsmStream SavedStateStream(mParent, NULL /*pKeyStore*/, Utf8Str::Empty, Utf8Str::Empty);
+#endif
+ int vrc = readSavedGuestScreenInfo(SavedStateStream, mSSData->strStateFilePath, aScreenId,
+ &u32OriginX, &u32OriginY, &u32Width, &u32Height, &u16Flags);
+ if (RT_FAILURE(vrc))
+ {
+#ifdef RT_OS_WINDOWS
+ /* HACK: GUI sets *pfEnabled to 'true' and expects it to stay so if the API fails.
+ * This works with XPCOM. But Windows COM sets all output parameters to zero.
+ * So just assign fEnable to TRUE again.
+ * The right fix would be to change GUI API wrappers to make sure that parameters
+ * are changed only if API succeeds.
+ */
+ *aEnabled = TRUE;
+#endif
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Saved guest size is not available (%Rrc)"),
+ vrc);
+ }
+
+ *aOriginX = u32OriginX;
+ *aOriginY = u32OriginY;
+ *aWidth = u32Width;
+ *aHeight = u32Height;
+ *aEnabled = (u16Flags & VBVA_SCREEN_F_DISABLED) == 0;
+
+ return S_OK;
+}
+
+HRESULT Machine::readSavedThumbnailToArray(ULONG aScreenId, BitmapFormat_T aBitmapFormat,
+ ULONG *aWidth, ULONG *aHeight, std::vector<BYTE> &aData)
+{
+ if (aScreenId != 0)
+ return E_NOTIMPL;
+
+ if ( aBitmapFormat != BitmapFormat_BGR0
+ && aBitmapFormat != BitmapFormat_BGRA
+ && aBitmapFormat != BitmapFormat_RGBA
+ && aBitmapFormat != BitmapFormat_PNG)
+ return setError(E_NOTIMPL,
+ tr("Unsupported saved thumbnail format 0x%08X"), aBitmapFormat);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ uint8_t *pu8Data = NULL;
+ uint32_t cbData = 0;
+ uint32_t u32Width = 0;
+ uint32_t u32Height = 0;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ SsmStream SavedStateStream(mParent, mData->mpKeyStore, mSSData->strStateKeyId, mSSData->strStateKeyStore);
+#else
+ SsmStream SavedStateStream(mParent, NULL /*pKeyStore*/, Utf8Str::Empty, Utf8Str::Empty);
+#endif
+ int vrc = readSavedDisplayScreenshot(SavedStateStream, mSSData->strStateFilePath, 0 /* u32Type */,
+ &pu8Data, &cbData, &u32Width, &u32Height);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Saved thumbnail data is not available (%Rrc)"),
+ vrc);
+
+ HRESULT hr = S_OK;
+
+ *aWidth = u32Width;
+ *aHeight = u32Height;
+
+ if (cbData > 0)
+ {
+ /* Convert pixels to the format expected by the API caller. */
+ if (aBitmapFormat == BitmapFormat_BGR0)
+ {
+ /* [0] B, [1] G, [2] R, [3] 0. */
+ aData.resize(cbData);
+ memcpy(&aData.front(), pu8Data, cbData);
+ }
+ else if (aBitmapFormat == BitmapFormat_BGRA)
+ {
+ /* [0] B, [1] G, [2] R, [3] A. */
+ aData.resize(cbData);
+ for (uint32_t i = 0; i < cbData; i += 4)
+ {
+ aData[i] = pu8Data[i];
+ aData[i + 1] = pu8Data[i + 1];
+ aData[i + 2] = pu8Data[i + 2];
+ aData[i + 3] = 0xff;
+ }
+ }
+ else if (aBitmapFormat == BitmapFormat_RGBA)
+ {
+ /* [0] R, [1] G, [2] B, [3] A. */
+ aData.resize(cbData);
+ for (uint32_t i = 0; i < cbData; i += 4)
+ {
+ aData[i] = pu8Data[i + 2];
+ aData[i + 1] = pu8Data[i + 1];
+ aData[i + 2] = pu8Data[i];
+ aData[i + 3] = 0xff;
+ }
+ }
+ else if (aBitmapFormat == BitmapFormat_PNG)
+ {
+ uint8_t *pu8PNG = NULL;
+ uint32_t cbPNG = 0;
+ uint32_t cxPNG = 0;
+ uint32_t cyPNG = 0;
+
+ vrc = DisplayMakePNG(pu8Data, u32Width, u32Height, &pu8PNG, &cbPNG, &cxPNG, &cyPNG, 0);
+
+ if (RT_SUCCESS(vrc))
+ {
+ aData.resize(cbPNG);
+ if (cbPNG)
+ memcpy(&aData.front(), pu8PNG, cbPNG);
+ }
+ else
+ hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not convert saved thumbnail to PNG (%Rrc)"),
+ vrc);
+
+ RTMemFree(pu8PNG);
+ }
+ }
+
+ freeSavedDisplayScreenshot(pu8Data);
+
+ return hr;
+}
+
+HRESULT Machine::querySavedScreenshotInfo(ULONG aScreenId,
+ ULONG *aWidth,
+ ULONG *aHeight,
+ std::vector<BitmapFormat_T> &aBitmapFormats)
+{
+ if (aScreenId != 0)
+ return E_NOTIMPL;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ uint8_t *pu8Data = NULL;
+ uint32_t cbData = 0;
+ uint32_t u32Width = 0;
+ uint32_t u32Height = 0;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ SsmStream SavedStateStream(mParent, mData->mpKeyStore, mSSData->strStateKeyId, mSSData->strStateKeyStore);
+#else
+ SsmStream SavedStateStream(mParent, NULL /*pKeyStore*/, Utf8Str::Empty, Utf8Str::Empty);
+#endif
+ int vrc = readSavedDisplayScreenshot(SavedStateStream, mSSData->strStateFilePath, 1 /* u32Type */,
+ &pu8Data, &cbData, &u32Width, &u32Height);
+
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Saved screenshot data is not available (%Rrc)"),
+ vrc);
+
+ *aWidth = u32Width;
+ *aHeight = u32Height;
+ aBitmapFormats.resize(1);
+ aBitmapFormats[0] = BitmapFormat_PNG;
+
+ freeSavedDisplayScreenshot(pu8Data);
+
+ return S_OK;
+}
+
+HRESULT Machine::readSavedScreenshotToArray(ULONG aScreenId,
+ BitmapFormat_T aBitmapFormat,
+ ULONG *aWidth,
+ ULONG *aHeight,
+ std::vector<BYTE> &aData)
+{
+ if (aScreenId != 0)
+ return E_NOTIMPL;
+
+ if (aBitmapFormat != BitmapFormat_PNG)
+ return E_NOTIMPL;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ uint8_t *pu8Data = NULL;
+ uint32_t cbData = 0;
+ uint32_t u32Width = 0;
+ uint32_t u32Height = 0;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ SsmStream SavedStateStream(mParent, mData->mpKeyStore, mSSData->strStateKeyId, mSSData->strStateKeyStore);
+#else
+ SsmStream SavedStateStream(mParent, NULL /*pKeyStore*/, Utf8Str::Empty, Utf8Str::Empty);
+#endif
+ int vrc = readSavedDisplayScreenshot(SavedStateStream, mSSData->strStateFilePath, 1 /* u32Type */,
+ &pu8Data, &cbData, &u32Width, &u32Height);
+
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Saved screenshot thumbnail data is not available (%Rrc)"),
+ vrc);
+
+ *aWidth = u32Width;
+ *aHeight = u32Height;
+
+ aData.resize(cbData);
+ if (cbData)
+ memcpy(&aData.front(), pu8Data, cbData);
+
+ freeSavedDisplayScreenshot(pu8Data);
+
+ return S_OK;
+}
+
+HRESULT Machine::hotPlugCPU(ULONG aCpu)
+{
+ HRESULT rc = S_OK;
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mHWData->mCPUHotPlugEnabled)
+ return setError(E_INVALIDARG, tr("CPU hotplug is not enabled"));
+
+ if (aCpu >= mHWData->mCPUCount)
+ return setError(E_INVALIDARG, tr("CPU id exceeds number of possible CPUs [0:%lu]"), mHWData->mCPUCount-1);
+
+ if (mHWData->mCPUAttached[aCpu])
+ return setError(VBOX_E_OBJECT_IN_USE, tr("CPU %lu is already attached"), aCpu);
+
+ rc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ alock.release();
+ rc = i_onCPUChange(aCpu, false);
+ alock.acquire();
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mCPUAttached[aCpu] = true;
+
+ /** Save settings if online - @todo why is this required? -- @bugref{6818} */
+ if (Global::IsOnline(mData->mMachineState))
+ i_saveSettings(NULL, alock);
+
+ return S_OK;
+}
+
+HRESULT Machine::hotUnplugCPU(ULONG aCpu)
+{
+ HRESULT rc = S_OK;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mHWData->mCPUHotPlugEnabled)
+ return setError(E_INVALIDARG, tr("CPU hotplug is not enabled"));
+
+ if (aCpu >= SchemaDefs::MaxCPUCount)
+ return setError(E_INVALIDARG,
+ tr("CPU index exceeds maximum CPU count (must be in range [0:%lu])"),
+ SchemaDefs::MaxCPUCount);
+
+ if (!mHWData->mCPUAttached[aCpu])
+ return setError(VBOX_E_OBJECT_NOT_FOUND, tr("CPU %lu is not attached"), aCpu);
+
+ /* CPU 0 can't be detached */
+ if (aCpu == 0)
+ return setError(E_INVALIDARG, tr("It is not possible to detach CPU 0"));
+
+ rc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ alock.release();
+ rc = i_onCPUChange(aCpu, true);
+ alock.acquire();
+ if (FAILED(rc)) return rc;
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mCPUAttached[aCpu] = false;
+
+ /** Save settings if online - @todo why is this required? -- @bugref{6818} */
+ if (Global::IsOnline(mData->mMachineState))
+ i_saveSettings(NULL, alock);
+
+ return S_OK;
+}
+
+HRESULT Machine::getCPUStatus(ULONG aCpu, BOOL *aAttached)
+{
+ *aAttached = false;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* If hotplug is enabled the CPU is always enabled. */
+ if (!mHWData->mCPUHotPlugEnabled)
+ {
+ if (aCpu < mHWData->mCPUCount)
+ *aAttached = true;
+ }
+ else
+ {
+ if (aCpu < SchemaDefs::MaxCPUCount)
+ *aAttached = mHWData->mCPUAttached[aCpu];
+ }
+
+ return S_OK;
+}
+
+HRESULT Machine::queryLogFilename(ULONG aIdx, com::Utf8Str &aFilename)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Utf8Str log = i_getLogFilename(aIdx);
+ if (!RTFileExists(log.c_str()))
+ log.setNull();
+ aFilename = log;
+
+ return S_OK;
+}
+
+HRESULT Machine::readLog(ULONG aIdx, LONG64 aOffset, LONG64 aSize, std::vector<BYTE> &aData)
+{
+ if (aSize < 0)
+ return setError(E_INVALIDARG, tr("The size argument (%lld) is negative"), aSize);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+ Utf8Str log = i_getLogFilename(aIdx);
+
+ /* do not unnecessarily hold the lock while doing something which does
+ * not need the lock and potentially takes a long time. */
+ alock.release();
+
+ /* Limit the chunk size to 512K. Gives good performance over (XP)COM, and
+ * keeps the SOAP reply size under 1M for the webservice (we're using
+ * base64 encoded strings for binary data for years now, avoiding the
+ * expansion of each byte array element to approx. 25 bytes of XML. */
+ size_t cbData = (size_t)RT_MIN(aSize, _512K);
+ aData.resize(cbData);
+
+ int vrc = VINF_SUCCESS;
+ RTVFSIOSTREAM hVfsIosLog = NIL_RTVFSIOSTREAM;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (mData->mstrLogKeyId.isNotEmpty() && mData->mstrLogKeyStore.isNotEmpty())
+ {
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ rc = i_getVirtualBox()->i_retainCryptoIf(&pCryptoIf);
+ if (SUCCEEDED(rc))
+ {
+ alock.acquire();
+
+ SecretKey *pKey = NULL;
+ vrc = mData->mpKeyStore->retainSecretKey(mData->mstrLogKeyId, &pKey);
+ alock.release();
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsIoStrmOpenNormal(log.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hVfsIosLog);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hVfsIosLogDec = NIL_RTVFSIOSTREAM;
+ vrc = pCryptoIf->pfnCryptoIoStrmFromVfsIoStrmDecrypt(hVfsIosLog, mData->mstrLogKeyStore.c_str(),
+ (const char *)pKey->getKeyBuffer(), &hVfsIosLogDec);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVfsIoStrmRelease(hVfsIosLog);
+ hVfsIosLog = hVfsIosLogDec;
+ }
+ }
+
+ pKey->release();
+ }
+
+ i_getVirtualBox()->i_releaseCryptoIf(pCryptoIf);
+ }
+ }
+ else
+ vrc = RTVfsIoStrmOpenNormal(log.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hVfsIosLog);
+#else
+ vrc = RTVfsIoStrmOpenNormal(log.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hVfsIosLog);
+#endif
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsIoStrmReadAt(hVfsIosLog, aOffset,
+ cbData ? &aData.front() : NULL, cbData,
+ true /*fBlocking*/, &cbData);
+ if (RT_SUCCESS(vrc))
+ aData.resize(cbData);
+ else
+ rc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not read log file '%s' (%Rrc)"),
+ log.c_str(), vrc);
+
+ RTVfsIoStrmRelease(hVfsIosLog);
+ }
+ else
+ rc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not open log file '%s' (%Rrc)"),
+ log.c_str(), vrc);
+
+ if (FAILED(rc))
+ aData.resize(0);
+
+ return rc;
+}
+
+
+/**
+ * Currently this method doesn't attach device to the running VM,
+ * just makes sure it's plugged on next VM start.
+ */
+HRESULT Machine::attachHostPCIDevice(LONG aHostAddress, LONG aDesiredGuestAddress, BOOL /* aTryToUnbind */)
+{
+ // lock scope
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ ChipsetType_T aChipset = ChipsetType_PIIX3;
+ COMGETTER(ChipsetType)(&aChipset);
+
+ if (aChipset != ChipsetType_ICH9)
+ {
+ return setError(E_INVALIDARG,
+ tr("Host PCI attachment only supported with ICH9 chipset"));
+ }
+
+ // check if device with this host PCI address already attached
+ for (HWData::PCIDeviceAssignmentList::const_iterator
+ it = mHWData->mPCIDeviceAssignments.begin();
+ it != mHWData->mPCIDeviceAssignments.end();
+ ++it)
+ {
+ LONG iHostAddress = -1;
+ ComPtr<PCIDeviceAttachment> pAttach;
+ pAttach = *it;
+ pAttach->COMGETTER(HostAddress)(&iHostAddress);
+ if (iHostAddress == aHostAddress)
+ return setError(E_INVALIDARG,
+ tr("Device with host PCI address already attached to this VM"));
+ }
+
+ ComObjPtr<PCIDeviceAttachment> pda;
+ char name[32];
+
+ RTStrPrintf(name, sizeof(name), "host%02x:%02x.%x", (aHostAddress>>8) & 0xff,
+ (aHostAddress & 0xf8) >> 3, aHostAddress & 7);
+ pda.createObject();
+ pda->init(this, name, aHostAddress, aDesiredGuestAddress, TRUE);
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mPCIDeviceAssignments.push_back(pda);
+ }
+
+ return S_OK;
+}
+
+/**
+ * Currently this method doesn't detach device from the running VM,
+ * just makes sure it's not plugged on next VM start.
+ */
+HRESULT Machine::detachHostPCIDevice(LONG aHostAddress)
+{
+ ComObjPtr<PCIDeviceAttachment> pAttach;
+ bool fRemoved = false;
+ HRESULT rc;
+
+ // lock scope
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ for (HWData::PCIDeviceAssignmentList::const_iterator
+ it = mHWData->mPCIDeviceAssignments.begin();
+ it != mHWData->mPCIDeviceAssignments.end();
+ ++it)
+ {
+ LONG iHostAddress = -1;
+ pAttach = *it;
+ pAttach->COMGETTER(HostAddress)(&iHostAddress);
+ if (iHostAddress != -1 && iHostAddress == aHostAddress)
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+ mHWData->mPCIDeviceAssignments.remove(pAttach);
+ fRemoved = true;
+ break;
+ }
+ }
+ }
+
+
+ /* Fire event outside of the lock */
+ if (fRemoved)
+ {
+ Assert(!pAttach.isNull());
+ ComPtr<IEventSource> es;
+ rc = mParent->COMGETTER(EventSource)(es.asOutParam());
+ Assert(SUCCEEDED(rc));
+ Bstr mid;
+ rc = this->COMGETTER(Id)(mid.asOutParam());
+ Assert(SUCCEEDED(rc));
+ ::FireHostPCIDevicePlugEvent(es, mid.raw(), false /* unplugged */, true /* success */, pAttach, NULL);
+ }
+
+ return fRemoved ? S_OK : setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("No host PCI device %08x attached"),
+ aHostAddress
+ );
+}
+
+HRESULT Machine::getPCIDeviceAssignments(std::vector<ComPtr<IPCIDeviceAttachment> > &aPCIDeviceAssignments)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aPCIDeviceAssignments.resize(mHWData->mPCIDeviceAssignments.size());
+ size_t i = 0;
+ for (std::list<ComObjPtr<PCIDeviceAttachment> >::const_iterator
+ it = mHWData->mPCIDeviceAssignments.begin();
+ it != mHWData->mPCIDeviceAssignments.end();
+ ++it, ++i)
+ (*it).queryInterfaceTo(aPCIDeviceAssignments[i].asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Machine::getBandwidthControl(ComPtr<IBandwidthControl> &aBandwidthControl)
+{
+ mBandwidthControl.queryInterfaceTo(aBandwidthControl.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Machine::getTracingEnabled(BOOL *aTracingEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aTracingEnabled = mHWData->mDebugging.fTracingEnabled;
+
+ return S_OK;
+}
+
+HRESULT Machine::setTracingEnabled(BOOL aTracingEnabled)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = i_checkStateDependency(MutableStateDep);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mHWData.backupEx();
+ if (SUCCEEDED(hrc))
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData->mDebugging.fTracingEnabled = aTracingEnabled != FALSE;
+ }
+ }
+ return hrc;
+}
+
+HRESULT Machine::getTracingConfig(com::Utf8Str &aTracingConfig)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aTracingConfig = mHWData->mDebugging.strTracingConfig;
+ return S_OK;
+}
+
+HRESULT Machine::setTracingConfig(const com::Utf8Str &aTracingConfig)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = i_checkStateDependency(MutableStateDep);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mHWData.backupEx();
+ if (SUCCEEDED(hrc))
+ {
+ mHWData->mDebugging.strTracingConfig = aTracingConfig;
+ if (SUCCEEDED(hrc))
+ i_setModified(IsModified_MachineData);
+ }
+ }
+ return hrc;
+}
+
+HRESULT Machine::getAllowTracingToAccessVM(BOOL *aAllowTracingToAccessVM)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAllowTracingToAccessVM = mHWData->mDebugging.fAllowTracingToAccessVM;
+
+ return S_OK;
+}
+
+HRESULT Machine::setAllowTracingToAccessVM(BOOL aAllowTracingToAccessVM)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = i_checkStateDependency(MutableStateDep);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mHWData.backupEx();
+ if (SUCCEEDED(hrc))
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData->mDebugging.fAllowTracingToAccessVM = aAllowTracingToAccessVM != FALSE;
+ }
+ }
+ return hrc;
+}
+
+HRESULT Machine::getAutostartEnabled(BOOL *aAutostartEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAutostartEnabled = mHWData->mAutostart.fAutostartEnabled;
+
+ return S_OK;
+}
+
+HRESULT Machine::setAutostartEnabled(BOOL aAutostartEnabled)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = i_checkStateDependency(MutableOrSavedOrRunningStateDep);
+ if ( SUCCEEDED(hrc)
+ && mHWData->mAutostart.fAutostartEnabled != !!aAutostartEnabled)
+ {
+ AutostartDb *autostartDb = mParent->i_getAutostartDb();
+ int vrc;
+
+ if (aAutostartEnabled)
+ vrc = autostartDb->addAutostartVM(mUserData->s.strName.c_str());
+ else
+ vrc = autostartDb->removeAutostartVM(mUserData->s.strName.c_str());
+
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = mHWData.backupEx();
+ if (SUCCEEDED(hrc))
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData->mAutostart.fAutostartEnabled = aAutostartEnabled != FALSE;
+ }
+ }
+ else if (vrc == VERR_NOT_SUPPORTED)
+ hrc = setError(VBOX_E_NOT_SUPPORTED,
+ tr("The VM autostart feature is not supported on this platform"));
+ else if (vrc == VERR_PATH_NOT_FOUND)
+ hrc = setError(E_FAIL,
+ tr("The path to the autostart database is not set"));
+ else
+ hrc = setError(E_UNEXPECTED,
+ aAutostartEnabled ?
+ tr("Adding machine '%s' to the autostart database failed with %Rrc") :
+ tr("Removing machine '%s' from the autostart database failed with %Rrc"),
+ mUserData->s.strName.c_str(), vrc);
+ }
+ return hrc;
+}
+
+HRESULT Machine::getAutostartDelay(ULONG *aAutostartDelay)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAutostartDelay = mHWData->mAutostart.uAutostartDelay;
+
+ return S_OK;
+}
+
+HRESULT Machine::setAutostartDelay(ULONG aAutostartDelay)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = i_checkStateDependency(MutableOrSavedOrRunningStateDep);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mHWData.backupEx();
+ if (SUCCEEDED(hrc))
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData->mAutostart.uAutostartDelay = aAutostartDelay;
+ }
+ }
+ return hrc;
+}
+
+HRESULT Machine::getAutostopType(AutostopType_T *aAutostopType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAutostopType = mHWData->mAutostart.enmAutostopType;
+
+ return S_OK;
+}
+
+HRESULT Machine::setAutostopType(AutostopType_T aAutostopType)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = i_checkStateDependency(MutableOrSavedOrRunningStateDep);
+ if ( SUCCEEDED(hrc)
+ && mHWData->mAutostart.enmAutostopType != aAutostopType)
+ {
+ AutostartDb *autostartDb = mParent->i_getAutostartDb();
+ int vrc;
+
+ if (aAutostopType != AutostopType_Disabled)
+ vrc = autostartDb->addAutostopVM(mUserData->s.strName.c_str());
+ else
+ vrc = autostartDb->removeAutostopVM(mUserData->s.strName.c_str());
+
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = mHWData.backupEx();
+ if (SUCCEEDED(hrc))
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData->mAutostart.enmAutostopType = aAutostopType;
+ }
+ }
+ else if (vrc == VERR_NOT_SUPPORTED)
+ hrc = setError(VBOX_E_NOT_SUPPORTED,
+ tr("The VM autostop feature is not supported on this platform"));
+ else if (vrc == VERR_PATH_NOT_FOUND)
+ hrc = setError(E_FAIL,
+ tr("The path to the autostart database is not set"));
+ else
+ hrc = setError(E_UNEXPECTED,
+ aAutostopType != AutostopType_Disabled ?
+ tr("Adding machine '%s' to the autostop database failed with %Rrc") :
+ tr("Removing machine '%s' from the autostop database failed with %Rrc"),
+ mUserData->s.strName.c_str(), vrc);
+ }
+ return hrc;
+}
+
+HRESULT Machine::getDefaultFrontend(com::Utf8Str &aDefaultFrontend)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aDefaultFrontend = mHWData->mDefaultFrontend;
+
+ return S_OK;
+}
+
+HRESULT Machine::setDefaultFrontend(const com::Utf8Str &aDefaultFrontend)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = i_checkStateDependency(MutableOrSavedStateDep);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mHWData.backupEx();
+ if (SUCCEEDED(hrc))
+ {
+ i_setModified(IsModified_MachineData);
+ mHWData->mDefaultFrontend = aDefaultFrontend;
+ }
+ }
+ return hrc;
+}
+
+HRESULT Machine::getIcon(std::vector<BYTE> &aIcon)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ size_t cbIcon = mUserData->s.ovIcon.size();
+ aIcon.resize(cbIcon);
+ if (cbIcon)
+ memcpy(&aIcon.front(), &mUserData->s.ovIcon[0], cbIcon);
+ return S_OK;
+}
+
+HRESULT Machine::setIcon(const std::vector<BYTE> &aIcon)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = i_checkStateDependency(MutableOrSavedStateDep);
+ if (SUCCEEDED(hrc))
+ {
+ i_setModified(IsModified_MachineData);
+ mUserData.backup();
+ size_t cbIcon = aIcon.size();
+ mUserData->s.ovIcon.resize(cbIcon);
+ if (cbIcon)
+ memcpy(&mUserData->s.ovIcon[0], &aIcon.front(), cbIcon);
+ }
+ return hrc;
+}
+
+HRESULT Machine::getUSBProxyAvailable(BOOL *aUSBProxyAvailable)
+{
+#ifdef VBOX_WITH_USB
+ *aUSBProxyAvailable = true;
+#else
+ *aUSBProxyAvailable = false;
+#endif
+ return S_OK;
+}
+
+HRESULT Machine::getVMProcessPriority(VMProcPriority_T *aVMProcessPriority)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aVMProcessPriority = mUserData->s.enmVMPriority;
+
+ return S_OK;
+}
+
+HRESULT Machine::setVMProcessPriority(VMProcPriority_T aVMProcessPriority)
+{
+ RT_NOREF(aVMProcessPriority);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = i_checkStateDependency(MutableOrSavedOrRunningStateDep);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mUserData.backupEx();
+ if (SUCCEEDED(hrc))
+ {
+ i_setModified(IsModified_MachineData);
+ mUserData->s.enmVMPriority = aVMProcessPriority;
+ }
+ }
+ alock.release();
+ if (SUCCEEDED(hrc))
+ hrc = i_onVMProcessPriorityChange(aVMProcessPriority);
+ return hrc;
+}
+
+HRESULT Machine::cloneTo(const ComPtr<IMachine> &aTarget, CloneMode_T aMode, const std::vector<CloneOptions_T> &aOptions,
+ ComPtr<IProgress> &aProgress)
+{
+ ComObjPtr<Progress> pP;
+ Progress *ppP = pP;
+ IProgress *iP = static_cast<IProgress *>(ppP);
+ IProgress **pProgress = &iP;
+
+ IMachine *pTarget = aTarget;
+
+ /* Convert the options. */
+ RTCList<CloneOptions_T> optList;
+ if (aOptions.size())
+ for (size_t i = 0; i < aOptions.size(); ++i)
+ optList.append(aOptions[i]);
+
+ if (optList.contains(CloneOptions_Link))
+ {
+ if (!i_isSnapshotMachine())
+ return setError(E_INVALIDARG,
+ tr("Linked clone can only be created from a snapshot"));
+ if (aMode != CloneMode_MachineState)
+ return setError(E_INVALIDARG,
+ tr("Linked clone can only be created for a single machine state"));
+ }
+ AssertReturn(!(optList.contains(CloneOptions_KeepAllMACs) && optList.contains(CloneOptions_KeepNATMACs)), E_INVALIDARG);
+
+ MachineCloneVM *pWorker = new MachineCloneVM(this, static_cast<Machine*>(pTarget), aMode, optList);
+
+ HRESULT rc = pWorker->start(pProgress);
+
+ pP = static_cast<Progress *>(*pProgress);
+ pP.queryInterfaceTo(aProgress.asOutParam());
+
+ return rc;
+
+}
+
+HRESULT Machine::moveTo(const com::Utf8Str &aTargetPath,
+ const com::Utf8Str &aType,
+ ComPtr<IProgress> &aProgress)
+{
+ LogFlowThisFuncEnter();
+
+ ComObjPtr<Progress> ptrProgress;
+ HRESULT hrc = ptrProgress.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ com::Utf8Str strDefaultPath;
+ if (aTargetPath.isEmpty())
+ i_calculateFullPath(".", strDefaultPath);
+
+ /* Initialize our worker task */
+ MachineMoveVM *pTask = NULL;
+ try
+ {
+ pTask = new MachineMoveVM(this, aTargetPath.isEmpty() ? strDefaultPath : aTargetPath, aType, ptrProgress);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ hrc = pTask->init();//no exceptions are thrown
+
+ if (SUCCEEDED(hrc))
+ {
+ hrc = pTask->createThread();
+ pTask = NULL; /* Consumed by createThread(). */
+ if (SUCCEEDED(hrc))
+ ptrProgress.queryInterfaceTo(aProgress.asOutParam());
+ else
+ setError(hrc, tr("Failed to create a worker thread for the MachineMoveVM task"));
+ }
+ else
+ delete pTask;
+ }
+
+ LogFlowThisFuncLeave();
+ return hrc;
+
+}
+
+HRESULT Machine::saveState(ComPtr<IProgress> &aProgress)
+{
+ NOREF(aProgress);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // This check should always fail.
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ AssertFailedReturn(E_NOTIMPL);
+}
+
+HRESULT Machine::adoptSavedState(const com::Utf8Str &aSavedStateFile)
+{
+ NOREF(aSavedStateFile);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // This check should always fail.
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ AssertFailedReturn(E_NOTIMPL);
+}
+
+HRESULT Machine::discardSavedState(BOOL aFRemoveFile)
+{
+ NOREF(aFRemoveFile);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // This check should always fail.
+ HRESULT rc = i_checkStateDependency(MutableOrSavedStateDep);
+ if (FAILED(rc)) return rc;
+
+ AssertFailedReturn(E_NOTIMPL);
+}
+
+// public methods for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Adds the given IsModified_* flag to the dirty flags of the machine.
+ * This must be called either during i_loadSettings or under the machine write lock.
+ * @param fl Flag
+ * @param fAllowStateModification If state modifications are allowed.
+ */
+void Machine::i_setModified(uint32_t fl, bool fAllowStateModification /* = true */)
+{
+ mData->flModifications |= fl;
+ if (fAllowStateModification && i_isStateModificationAllowed())
+ mData->mCurrentStateModified = true;
+}
+
+/**
+ * Adds the given IsModified_* flag to the dirty flags of the machine, taking
+ * care of the write locking.
+ *
+ * @param fModification The flag to add.
+ * @param fAllowStateModification If state modifications are allowed.
+ */
+void Machine::i_setModifiedLock(uint32_t fModification, bool fAllowStateModification /* = true */)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ i_setModified(fModification, fAllowStateModification);
+}
+
+/**
+ * Saves the registry entry of this machine to the given configuration node.
+ *
+ * @param data Machine registry data.
+ *
+ * @note locks this object for reading.
+ */
+HRESULT Machine::i_saveRegistryEntry(settings::MachineRegistryEntry &data)
+{
+ AutoLimitedCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ data.uuid = mData->mUuid;
+ data.strSettingsFile = mData->m_strConfigFile;
+
+ return S_OK;
+}
+
+/**
+ * Calculates the absolute path of the given path taking the directory of the
+ * machine settings file as the current directory.
+ *
+ * @param strPath Path to calculate the absolute path for.
+ * @param aResult Where to put the result (used only on success, can be the
+ * same Utf8Str instance as passed in @a aPath).
+ * @return IPRT result.
+ *
+ * @note Locks this object for reading.
+ */
+int Machine::i_calculateFullPath(const Utf8Str &strPath, Utf8Str &aResult)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), Global::vboxStatusCodeFromCOM(autoCaller.rc()));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(!mData->m_strConfigFileFull.isEmpty(), VERR_GENERAL_FAILURE);
+
+ Utf8Str strSettingsDir = mData->m_strConfigFileFull;
+
+ strSettingsDir.stripFilename();
+ char szFolder[RTPATH_MAX];
+ size_t cbFolder = sizeof(szFolder);
+ int vrc = RTPathAbsEx(strSettingsDir.c_str(), strPath.c_str(), RTPATH_STR_F_STYLE_HOST, szFolder, &cbFolder);
+ if (RT_SUCCESS(vrc))
+ aResult = szFolder;
+
+ return vrc;
+}
+
+/**
+ * Copies strSource to strTarget, making it relative to the machine folder
+ * if it is a subdirectory thereof, or simply copying it otherwise.
+ *
+ * @param strSource Path to evaluate and copy.
+ * @param strTarget Buffer to receive target path.
+ *
+ * @note Locks this object for reading.
+ */
+void Machine::i_copyPathRelativeToMachine(const Utf8Str &strSource,
+ Utf8Str &strTarget)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), (void)0);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturnVoid(!mData->m_strConfigFileFull.isEmpty());
+ // use strTarget as a temporary buffer to hold the machine settings dir
+ strTarget = mData->m_strConfigFileFull;
+ strTarget.stripFilename();
+ if (RTPathStartsWith(strSource.c_str(), strTarget.c_str()))
+ {
+ // is relative: then append what's left
+ strTarget = strSource.substr(strTarget.length() + 1); // skip '/'
+ // for empty paths (only possible for subdirs) use "." to avoid
+ // triggering default settings for not present config attributes.
+ if (strTarget.isEmpty())
+ strTarget = ".";
+ }
+ else
+ // is not relative: then overwrite
+ strTarget = strSource;
+}
+
+/**
+ * Returns the full path to the machine's log folder in the
+ * \a aLogFolder argument.
+ */
+void Machine::i_getLogFolder(Utf8Str &aLogFolder)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ char szTmp[RTPATH_MAX];
+ int vrc = RTEnvGetEx(RTENV_DEFAULT, "VBOX_USER_VMLOGDIR", szTmp, sizeof(szTmp), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ if (szTmp[0] && !mUserData.isNull())
+ {
+ char szTmp2[RTPATH_MAX];
+ vrc = RTPathAbs(szTmp, szTmp2, sizeof(szTmp2));
+ if (RT_SUCCESS(vrc))
+ aLogFolder.printf("%s%c%s",
+ szTmp2,
+ RTPATH_DELIMITER,
+ mUserData->s.strName.c_str()); // path/to/logfolder/vmname
+ }
+ else
+ vrc = VERR_PATH_IS_RELATIVE;
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ // fallback if VBOX_USER_LOGHOME is not set or invalid
+ aLogFolder = mData->m_strConfigFileFull; // path/to/machinesfolder/vmname/vmname.vbox
+ aLogFolder.stripFilename(); // path/to/machinesfolder/vmname
+ aLogFolder.append(RTPATH_DELIMITER);
+ aLogFolder.append("Logs"); // path/to/machinesfolder/vmname/Logs
+ }
+}
+
+/**
+ * Returns the full path to the machine's log file for an given index.
+ */
+Utf8Str Machine::i_getLogFilename(ULONG idx)
+{
+ Utf8Str logFolder;
+ getLogFolder(logFolder);
+ Assert(logFolder.length());
+
+ Utf8Str log;
+ if (idx == 0)
+ log.printf("%s%cVBox.log", logFolder.c_str(), RTPATH_DELIMITER);
+#if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_HARDENING)
+ else if (idx == 1)
+ log.printf("%s%cVBoxHardening.log", logFolder.c_str(), RTPATH_DELIMITER);
+ else
+ log.printf("%s%cVBox.log.%u", logFolder.c_str(), RTPATH_DELIMITER, idx - 1);
+#else
+ else
+ log.printf("%s%cVBox.log.%u", logFolder.c_str(), RTPATH_DELIMITER, idx);
+#endif
+ return log;
+}
+
+/**
+ * Returns the full path to the machine's hardened log file.
+ */
+Utf8Str Machine::i_getHardeningLogFilename(void)
+{
+ Utf8Str strFilename;
+ getLogFolder(strFilename);
+ Assert(strFilename.length());
+ strFilename.append(RTPATH_SLASH_STR "VBoxHardening.log");
+ return strFilename;
+}
+
+/**
+ * Returns the default NVRAM filename based on the location of the VM config.
+ * Note that this is a relative path.
+ */
+Utf8Str Machine::i_getDefaultNVRAMFilename()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), Utf8Str::Empty);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (i_isSnapshotMachine())
+ return Utf8Str::Empty;
+
+ Utf8Str strNVRAMFilePath = mData->m_strConfigFileFull;
+ strNVRAMFilePath.stripPath();
+ strNVRAMFilePath.stripSuffix();
+ strNVRAMFilePath += ".nvram";
+
+ return strNVRAMFilePath;
+}
+
+/**
+ * Returns the NVRAM filename for a new snapshot. This intentionally works
+ * similarly to the saved state file naming. Note that this is usually
+ * a relative path, unless the snapshot folder is absolute.
+ */
+Utf8Str Machine::i_getSnapshotNVRAMFilename()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), Utf8Str::Empty);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ RTTIMESPEC ts;
+ RTTimeNow(&ts);
+ RTTIME time;
+ RTTimeExplode(&time, &ts);
+
+ Utf8Str strNVRAMFilePath = mUserData->s.strSnapshotFolder;
+ strNVRAMFilePath += RTPATH_DELIMITER;
+ strNVRAMFilePath.appendPrintf("%04d-%02u-%02uT%02u-%02u-%02u-%09uZ.nvram",
+ time.i32Year, time.u8Month, time.u8MonthDay,
+ time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond);
+
+ return strNVRAMFilePath;
+}
+
+/**
+ * Returns the version of the settings file.
+ */
+SettingsVersion_T Machine::i_getSettingsVersion(void)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), SettingsVersion_Null);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return mData->pMachineConfigFile->getSettingsVersion();
+}
+
+/**
+ * Composes a unique saved state filename based on the current system time. The filename is
+ * granular to the second so this will work so long as no more than one snapshot is taken on
+ * a machine per second.
+ *
+ * Before version 4.1, we used this formula for saved state files:
+ * Utf8StrFmt("%s%c{%RTuuid}.sav", strFullSnapshotFolder.c_str(), RTPATH_DELIMITER, mData->mUuid.raw())
+ * which no longer works because saved state files can now be shared between the saved state of the
+ * "saved" machine and an online snapshot, and the following would cause problems:
+ * 1) save machine
+ * 2) create online snapshot from that machine state --> reusing saved state file
+ * 3) save machine again --> filename would be reused, breaking the online snapshot
+ *
+ * So instead we now use a timestamp.
+ *
+ * @param strStateFilePath
+ */
+
+void Machine::i_composeSavedStateFilename(Utf8Str &strStateFilePath)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ i_calculateFullPath(mUserData->s.strSnapshotFolder, strStateFilePath);
+ }
+
+ RTTIMESPEC ts;
+ RTTimeNow(&ts);
+ RTTIME time;
+ RTTimeExplode(&time, &ts);
+
+ strStateFilePath += RTPATH_DELIMITER;
+ strStateFilePath.appendPrintf("%04d-%02u-%02uT%02u-%02u-%02u-%09uZ.sav",
+ time.i32Year, time.u8Month, time.u8MonthDay,
+ time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond);
+}
+
+/**
+ * Returns whether at least one USB controller is present for the VM.
+ */
+bool Machine::i_isUSBControllerPresent()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), false);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return (mUSBControllers->size() > 0);
+}
+
+
+/**
+ * @note Locks this object for writing, calls the client process
+ * (inside the lock).
+ */
+HRESULT Machine::i_launchVMProcess(IInternalSessionControl *aControl,
+ const Utf8Str &strFrontend,
+ const std::vector<com::Utf8Str> &aEnvironmentChanges,
+ ProgressProxy *aProgress)
+{
+ LogFlowThisFuncEnter();
+
+ AssertReturn(aControl, E_FAIL);
+ AssertReturn(aProgress, E_FAIL);
+ AssertReturn(!strFrontend.isEmpty(), E_FAIL);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mData->mRegistered)
+ return setError(E_UNEXPECTED,
+ tr("The machine '%s' is not registered"),
+ mUserData->s.strName.c_str());
+
+ LogFlowThisFunc(("mSession.mState=%s\n", ::stringifySessionState(mData->mSession.mState)));
+
+ /* The process started when launching a VM with separate UI/VM processes is always
+ * the UI process, i.e. needs special handling as it won't claim the session. */
+ bool fSeparate = strFrontend.endsWith("separate", Utf8Str::CaseInsensitive);
+
+ if (fSeparate)
+ {
+ if (mData->mSession.mState != SessionState_Unlocked && mData->mSession.mName != "headless")
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The machine '%s' is in a state which is incompatible with launching a separate UI process"),
+ mUserData->s.strName.c_str());
+ }
+ else
+ {
+ if ( mData->mSession.mState == SessionState_Locked
+ || mData->mSession.mState == SessionState_Spawning
+ || mData->mSession.mState == SessionState_Unlocking)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The machine '%s' is already locked by a session (or being locked or unlocked)"),
+ mUserData->s.strName.c_str());
+
+ /* may not be busy */
+ AssertReturn(!Global::IsOnlineOrTransient(mData->mMachineState), E_FAIL);
+ }
+
+ /* Hardening logging */
+#if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_HARDENING)
+ Utf8Str strSupHardeningLogArg("--sup-hardening-log=");
+ {
+ Utf8Str strHardeningLogFile = i_getHardeningLogFilename();
+ int vrc2;
+ /* ignore rc */ i_deleteFile(strHardeningLogFile, false /* fIgnoreFailures */, tr("hardening log file"), &vrc2);
+ if (vrc2 == VERR_PATH_NOT_FOUND || vrc2 == VERR_FILE_NOT_FOUND)
+ {
+ Utf8Str strStartupLogDir = strHardeningLogFile;
+ strStartupLogDir.stripFilename();
+ RTDirCreateFullPath(strStartupLogDir.c_str(), 0755); /** @todo add a variant for creating the path to a
+ file without stripping the file. */
+ }
+ strSupHardeningLogArg.append(strHardeningLogFile);
+
+ /* Remove legacy log filename to avoid confusion. */
+ Utf8Str strOldStartupLogFile;
+ getLogFolder(strOldStartupLogFile);
+ strOldStartupLogFile.append(RTPATH_SLASH_STR "VBoxStartup.log");
+ i_deleteFile(strOldStartupLogFile, true /* fIgnoreFailures */);
+ }
+#else
+ Utf8Str strSupHardeningLogArg;
+#endif
+
+ Utf8Str strAppOverride;
+#ifdef RT_OS_DARWIN /* Avoid Launch Services confusing this with the selector by using a helper app. */
+ strAppOverride = i_getExtraData(Utf8Str("VBoxInternal2/VirtualBoxVMAppOverride"));
+#endif
+
+ bool fUseVBoxSDS = false;
+ Utf8Str strCanonicalName;
+ if (false)
+ { }
+#ifdef VBOX_WITH_QTGUI
+ else if ( !strFrontend.compare("gui", Utf8Str::CaseInsensitive)
+ || !strFrontend.compare("GUI/Qt", Utf8Str::CaseInsensitive)
+ || !strFrontend.compare("separate", Utf8Str::CaseInsensitive)
+ || !strFrontend.compare("gui/separate", Utf8Str::CaseInsensitive)
+ || !strFrontend.compare("GUI/Qt/separate", Utf8Str::CaseInsensitive))
+ {
+ strCanonicalName = "GUI/Qt";
+ fUseVBoxSDS = true;
+ }
+#endif
+#ifdef VBOX_WITH_VBOXSDL
+ else if ( !strFrontend.compare("sdl", Utf8Str::CaseInsensitive)
+ || !strFrontend.compare("GUI/SDL", Utf8Str::CaseInsensitive)
+ || !strFrontend.compare("sdl/separate", Utf8Str::CaseInsensitive)
+ || !strFrontend.compare("GUI/SDL/separate", Utf8Str::CaseInsensitive))
+ {
+ strCanonicalName = "GUI/SDL";
+ fUseVBoxSDS = true;
+ }
+#endif
+#ifdef VBOX_WITH_HEADLESS
+ else if ( !strFrontend.compare("headless", Utf8Str::CaseInsensitive)
+ || !strFrontend.compare("capture", Utf8Str::CaseInsensitive)
+ || !strFrontend.compare("vrdp", Utf8Str::CaseInsensitive) /* Deprecated. Same as headless. */)
+ {
+ strCanonicalName = "headless";
+ }
+#endif
+ else
+ return setError(E_INVALIDARG, tr("Invalid frontend name: '%s'"), strFrontend.c_str());
+
+ Utf8Str idStr = mData->mUuid.toString();
+ Utf8Str const &strMachineName = mUserData->s.strName;
+ RTPROCESS pid = NIL_RTPROCESS;
+
+#if !defined(VBOX_WITH_SDS) || !defined(RT_OS_WINDOWS)
+ RT_NOREF(fUseVBoxSDS);
+#else
+ DWORD idCallerSession = ~(DWORD)0;
+ if (fUseVBoxSDS)
+ {
+ /*
+ * The VBoxSDS should be used for process launching the VM with
+ * GUI only if the caller and the VBoxSDS are in different Windows
+ * sessions and the caller in the interactive one.
+ */
+ fUseVBoxSDS = false;
+
+ /* Get windows session of the current process. The process token used
+ due to several reasons:
+ 1. The token is absent for the current thread except someone set it
+ for us.
+ 2. Needs to get the id of the session where the process is started.
+ We only need to do this once, though. */
+ static DWORD s_idCurrentSession = ~(DWORD)0;
+ DWORD idCurrentSession = s_idCurrentSession;
+ if (idCurrentSession == ~(DWORD)0)
+ {
+ HANDLE hCurrentProcessToken = NULL;
+ if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_READ, &hCurrentProcessToken))
+ {
+ DWORD cbIgn = 0;
+ if (GetTokenInformation(hCurrentProcessToken, TokenSessionId, &idCurrentSession, sizeof(idCurrentSession), &cbIgn))
+ s_idCurrentSession = idCurrentSession;
+ else
+ {
+ idCurrentSession = ~(DWORD)0;
+ LogRelFunc(("GetTokenInformation/TokenSessionId on self failed: %u\n", GetLastError()));
+ }
+ CloseHandle(hCurrentProcessToken);
+ }
+ else
+ LogRelFunc(("OpenProcessToken/self failed: %u\n", GetLastError()));
+ }
+
+ /* get the caller's session */
+ HRESULT hrc = CoImpersonateClient();
+ if (SUCCEEDED(hrc))
+ {
+ HANDLE hCallerThreadToken;
+ if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_READ,
+ FALSE /* OpenAsSelf - for impersonation at SecurityIdentification level */,
+ &hCallerThreadToken))
+ {
+ SetLastError(NO_ERROR);
+ DWORD cbIgn = 0;
+ if (GetTokenInformation(hCallerThreadToken, TokenSessionId, &idCallerSession, sizeof(DWORD), &cbIgn))
+ {
+ /* Only need to use SDS if the session ID differs: */
+ if (idCurrentSession != idCallerSession)
+ {
+ fUseVBoxSDS = false;
+
+ /* Obtain the groups the access token belongs to so we can see if the session is interactive: */
+ DWORD cbTokenGroups = 0;
+ PTOKEN_GROUPS pTokenGroups = NULL;
+ if ( !GetTokenInformation(hCallerThreadToken, TokenGroups, pTokenGroups, 0, &cbTokenGroups)
+ && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ pTokenGroups = (PTOKEN_GROUPS)RTMemTmpAllocZ(cbTokenGroups);
+ if (GetTokenInformation(hCallerThreadToken, TokenGroups, pTokenGroups, cbTokenGroups, &cbTokenGroups))
+ {
+ /* Well-known interactive SID: SECURITY_INTERACTIVE_RID, S-1-5-4: */
+ SID_IDENTIFIER_AUTHORITY sidIdNTAuthority = SECURITY_NT_AUTHORITY;
+ PSID pInteractiveSid = NULL;
+ if (AllocateAndInitializeSid(&sidIdNTAuthority, 1, 4, 0, 0, 0, 0, 0, 0, 0, &pInteractiveSid))
+ {
+ /* Iterate over the groups looking for the interactive SID: */
+ fUseVBoxSDS = false;
+ for (DWORD dwIndex = 0; dwIndex < pTokenGroups->GroupCount; dwIndex++)
+ if (EqualSid(pTokenGroups->Groups[dwIndex].Sid, pInteractiveSid))
+ {
+ fUseVBoxSDS = true;
+ break;
+ }
+ FreeSid(pInteractiveSid);
+ }
+ }
+ else
+ LogRelFunc(("GetTokenInformation/TokenGroups failed: %u\n", GetLastError()));
+ RTMemTmpFree(pTokenGroups);
+ }
+ }
+ else
+ LogRelFunc(("GetTokenInformation/TokenSessionId failed: %u\n", GetLastError()));
+ CloseHandle(hCallerThreadToken);
+ }
+ else
+ LogRelFunc(("OpenThreadToken/client failed: %u\n", GetLastError()));
+ CoRevertToSelf();
+ }
+ else
+ LogRelFunc(("CoImpersonateClient failed: %Rhrc\n", hrc));
+ }
+ if (fUseVBoxSDS)
+ {
+ /* connect to VBoxSDS */
+ ComPtr<IVirtualBoxSDS> pVBoxSDS;
+ HRESULT rc = pVBoxSDS.createLocalObject(CLSID_VirtualBoxSDS);
+ if (FAILED(rc))
+ return setError(rc, tr("Failed to start the machine '%s'. A connection to VBoxSDS cannot be established"),
+ strMachineName.c_str());
+
+ /* By default the RPC_C_IMP_LEVEL_IDENTIFY is used for impersonation the client. It allows
+ ACL checking but restricts an access to system objects e.g. files. Call to CoSetProxyBlanket
+ elevates the impersonation level up to RPC_C_IMP_LEVEL_IMPERSONATE allowing the VBoxSDS
+ service to access the files. */
+ rc = CoSetProxyBlanket(pVBoxSDS,
+ RPC_C_AUTHN_DEFAULT,
+ RPC_C_AUTHZ_DEFAULT,
+ COLE_DEFAULT_PRINCIPAL,
+ RPC_C_AUTHN_LEVEL_DEFAULT,
+ RPC_C_IMP_LEVEL_IMPERSONATE,
+ NULL,
+ EOAC_DEFAULT);
+ if (FAILED(rc))
+ return setError(rc, tr("Failed to start the machine '%s'. CoSetProxyBlanket failed"), strMachineName.c_str());
+
+ size_t const cEnvVars = aEnvironmentChanges.size();
+ com::SafeArray<IN_BSTR> aBstrEnvironmentChanges(cEnvVars);
+ for (size_t i = 0; i < cEnvVars; i++)
+ aBstrEnvironmentChanges[i] = Bstr(aEnvironmentChanges[i]).raw();
+
+ ULONG uPid = 0;
+ rc = pVBoxSDS->LaunchVMProcess(Bstr(idStr).raw(), Bstr(strMachineName).raw(), Bstr(strFrontend).raw(),
+ ComSafeArrayAsInParam(aBstrEnvironmentChanges), Bstr(strSupHardeningLogArg).raw(),
+ idCallerSession, &uPid);
+ if (FAILED(rc))
+ return setError(rc, tr("Failed to start the machine '%s'. Process creation failed"), strMachineName.c_str());
+ pid = (RTPROCESS)uPid;
+ }
+ else
+#endif /* VBOX_WITH_SDS && RT_OS_WINDOWS */
+ {
+ int vrc = MachineLaunchVMCommonWorker(idStr, strMachineName, strFrontend, aEnvironmentChanges, strSupHardeningLogArg,
+ strAppOverride, 0 /*fFlags*/, NULL /*pvExtraData*/, pid);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not launch the VM process for the machine '%s' (%Rrc)"), strMachineName.c_str(), vrc);
+ }
+
+ LogRel(("Launched VM: %u pid: %u (%#x) frontend: %s name: %s\n",
+ idStr.c_str(), pid, pid, strFrontend.c_str(), strMachineName.c_str()));
+
+ if (!fSeparate)
+ {
+ /*
+ * Note that we don't release the lock here before calling the client,
+ * because it doesn't need to call us back if called with a NULL argument.
+ * Releasing the lock here is dangerous because we didn't prepare the
+ * launch data yet, but the client we've just started may happen to be
+ * too fast and call LockMachine() that will fail (because of PID, etc.),
+ * so that the Machine will never get out of the Spawning session state.
+ */
+
+ /* inform the session that it will be a remote one */
+ LogFlowThisFunc(("Calling AssignMachine (NULL)...\n"));
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ HRESULT rc = aControl->AssignMachine(NULL, LockType_Write, Bstr::Empty.raw());
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ HRESULT rc = aControl->AssignMachine(NULL, LockType_Write, NULL);
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ LogFlowThisFunc(("AssignMachine (NULL) returned %08X\n", rc));
+
+ if (FAILED(rc))
+ {
+ /* restore the session state */
+ mData->mSession.mState = SessionState_Unlocked;
+ alock.release();
+ mParent->i_addProcessToReap(pid);
+ /* The failure may occur w/o any error info (from RPC), so provide one */
+ return setError(VBOX_E_VM_ERROR,
+ tr("Failed to assign the machine to the session (%Rhrc)"), rc);
+ }
+
+ /* attach launch data to the machine */
+ Assert(mData->mSession.mPID == NIL_RTPROCESS);
+ mData->mSession.mRemoteControls.push_back(aControl);
+ mData->mSession.mProgress = aProgress;
+ mData->mSession.mPID = pid;
+ mData->mSession.mState = SessionState_Spawning;
+ Assert(strCanonicalName.isNotEmpty());
+ mData->mSession.mName = strCanonicalName;
+ }
+ else
+ {
+ /* For separate UI process we declare the launch as completed instantly, as the
+ * actual headless VM start may or may not come. No point in remembering anything
+ * yet, as what matters for us is when the headless VM gets started. */
+ aProgress->i_notifyComplete(S_OK);
+ }
+
+ alock.release();
+ mParent->i_addProcessToReap(pid);
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Returns @c true if the given session machine instance has an open direct
+ * session (and optionally also for direct sessions which are closing) and
+ * returns the session control machine instance if so.
+ *
+ * Note that when the method returns @c false, the arguments remain unchanged.
+ *
+ * @param aMachine Session machine object.
+ * @param aControl Direct session control object (optional).
+ * @param aRequireVM If true then only allow VM sessions.
+ * @param aAllowClosing If true then additionally a session which is currently
+ * being closed will also be allowed.
+ *
+ * @note locks this object for reading.
+ */
+bool Machine::i_isSessionOpen(ComObjPtr<SessionMachine> &aMachine,
+ ComPtr<IInternalSessionControl> *aControl /*= NULL*/,
+ bool aRequireVM /*= false*/,
+ bool aAllowClosing /*= false*/)
+{
+ AutoLimitedCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), false);
+
+ /* just return false for inaccessible machines */
+ if (getObjectState().getState() != ObjectState::Ready)
+ return false;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( ( mData->mSession.mState == SessionState_Locked
+ && (!aRequireVM || mData->mSession.mLockType == LockType_VM))
+ || (aAllowClosing && mData->mSession.mState == SessionState_Unlocking)
+ )
+ {
+ AssertReturn(!mData->mSession.mMachine.isNull(), false);
+
+ aMachine = mData->mSession.mMachine;
+
+ if (aControl != NULL)
+ *aControl = mData->mSession.mDirectControl;
+
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Returns @c true if the given machine has an spawning direct session.
+ *
+ * @note locks this object for reading.
+ */
+bool Machine::i_isSessionSpawning()
+{
+ AutoLimitedCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), false);
+
+ /* just return false for inaccessible machines */
+ if (getObjectState().getState() != ObjectState::Ready)
+ return false;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->mSession.mState == SessionState_Spawning)
+ return true;
+
+ return false;
+}
+
+/**
+ * Called from the client watcher thread to check for unexpected client process
+ * death during Session_Spawning state (e.g. before it successfully opened a
+ * direct session).
+ *
+ * On Win32 and on OS/2, this method is called only when we've got the
+ * direct client's process termination notification, so it always returns @c
+ * true.
+ *
+ * On other platforms, this method returns @c true if the client process is
+ * terminated and @c false if it's still alive.
+ *
+ * @note Locks this object for writing.
+ */
+bool Machine::i_checkForSpawnFailure()
+{
+ AutoCaller autoCaller(this);
+ if (!autoCaller.isOk())
+ {
+ /* nothing to do */
+ LogFlowThisFunc(("Already uninitialized!\n"));
+ return true;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->mSession.mState != SessionState_Spawning)
+ {
+ /* nothing to do */
+ LogFlowThisFunc(("Not spawning any more!\n"));
+ return true;
+ }
+
+ HRESULT rc = S_OK;
+
+ /* PID not yet initialized, skip check. */
+ if (mData->mSession.mPID == NIL_RTPROCESS)
+ return false;
+
+ RTPROCSTATUS status;
+ int vrc = RTProcWait(mData->mSession.mPID, RTPROCWAIT_FLAGS_NOBLOCK, &status);
+
+ if (vrc != VERR_PROCESS_RUNNING)
+ {
+ Utf8Str strExtraInfo;
+
+#if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_HARDENING)
+ /* If the startup logfile exists and is of non-zero length, tell the
+ user to look there for more details to encourage them to attach it
+ when reporting startup issues. */
+ Utf8Str strHardeningLogFile = i_getHardeningLogFilename();
+ uint64_t cbStartupLogFile = 0;
+ int vrc2 = RTFileQuerySizeByPath(strHardeningLogFile.c_str(), &cbStartupLogFile);
+ if (RT_SUCCESS(vrc2) && cbStartupLogFile > 0)
+ strExtraInfo.appendPrintf(tr(". More details may be available in '%s'"), strHardeningLogFile.c_str());
+#endif
+
+ if (RT_SUCCESS(vrc) && status.enmReason == RTPROCEXITREASON_NORMAL)
+ rc = setError(E_FAIL,
+ tr("The virtual machine '%s' has terminated unexpectedly during startup with exit code %d (%#x)%s"),
+ i_getName().c_str(), status.iStatus, status.iStatus, strExtraInfo.c_str());
+ else if (RT_SUCCESS(vrc) && status.enmReason == RTPROCEXITREASON_SIGNAL)
+ rc = setError(E_FAIL,
+ tr("The virtual machine '%s' has terminated unexpectedly during startup because of signal %d%s"),
+ i_getName().c_str(), status.iStatus, strExtraInfo.c_str());
+ else if (RT_SUCCESS(vrc) && status.enmReason == RTPROCEXITREASON_ABEND)
+ rc = setError(E_FAIL,
+ tr("The virtual machine '%s' has terminated abnormally (iStatus=%#x)%s"),
+ i_getName().c_str(), status.iStatus, strExtraInfo.c_str());
+ else
+ rc = setErrorBoth(E_FAIL, vrc,
+ tr("The virtual machine '%s' has terminated unexpectedly during startup (%Rrc)%s"),
+ i_getName().c_str(), vrc, strExtraInfo.c_str());
+ }
+
+ if (FAILED(rc))
+ {
+ /* Close the remote session, remove the remote control from the list
+ * and reset session state to Closed (@note keep the code in sync with
+ * the relevant part in LockMachine()). */
+
+ Assert(mData->mSession.mRemoteControls.size() == 1);
+ if (mData->mSession.mRemoteControls.size() == 1)
+ {
+ ErrorInfoKeeper eik;
+ mData->mSession.mRemoteControls.front()->Uninitialize();
+ }
+
+ mData->mSession.mRemoteControls.clear();
+ mData->mSession.mState = SessionState_Unlocked;
+
+ /* finalize the progress after setting the state */
+ if (!mData->mSession.mProgress.isNull())
+ {
+ mData->mSession.mProgress->notifyComplete(rc);
+ mData->mSession.mProgress.setNull();
+ }
+
+ mData->mSession.mPID = NIL_RTPROCESS;
+
+ mParent->i_onSessionStateChanged(mData->mUuid, SessionState_Unlocked);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Checks whether the machine can be registered. If so, commits and saves
+ * all settings.
+ *
+ * @note Must be called from mParent's write lock. Locks this object and
+ * children for writing.
+ */
+HRESULT Machine::i_prepareRegister()
+{
+ AssertReturn(mParent->isWriteLockOnCurrentThread(), E_FAIL);
+
+ AutoLimitedCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* wait for state dependents to drop to zero */
+ i_ensureNoStateDependencies(alock);
+
+ if (!mData->mAccessible)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The machine '%s' with UUID {%s} is inaccessible and cannot be registered"),
+ mUserData->s.strName.c_str(),
+ mData->mUuid.toString().c_str());
+
+ AssertReturn(getObjectState().getState() == ObjectState::Ready, E_FAIL);
+
+ if (mData->mRegistered)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The machine '%s' with UUID {%s} is already registered"),
+ mUserData->s.strName.c_str(),
+ mData->mUuid.toString().c_str());
+
+ HRESULT rc = S_OK;
+
+ // Ensure the settings are saved. If we are going to be registered and
+ // no config file exists yet, create it by calling i_saveSettings() too.
+ if ( (mData->flModifications)
+ || (!mData->pMachineConfigFile->fileExists())
+ )
+ {
+ rc = i_saveSettings(NULL, alock);
+ // no need to check whether VirtualBox.xml needs saving too since
+ // we can't have a machine XML file rename pending
+ if (FAILED(rc)) return rc;
+ }
+
+ /* more config checking goes here */
+
+ if (SUCCEEDED(rc))
+ {
+ /* we may have had implicit modifications we want to fix on success */
+ i_commit();
+
+ mData->mRegistered = true;
+ }
+ else
+ {
+ /* we may have had implicit modifications we want to cancel on failure*/
+ i_rollback(false /* aNotify */);
+ }
+
+ return rc;
+}
+
+/**
+ * Increases the number of objects dependent on the machine state or on the
+ * registered state. Guarantees that these two states will not change at least
+ * until #i_releaseStateDependency() is called.
+ *
+ * Depending on the @a aDepType value, additional state checks may be made.
+ * These checks will set extended error info on failure. See
+ * #i_checkStateDependency() for more info.
+ *
+ * If this method returns a failure, the dependency is not added and the caller
+ * is not allowed to rely on any particular machine state or registration state
+ * value and may return the failed result code to the upper level.
+ *
+ * @param aDepType Dependency type to add.
+ * @param aState Current machine state (NULL if not interested).
+ * @param aRegistered Current registered state (NULL if not interested).
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Machine::i_addStateDependency(StateDependency aDepType /* = AnyStateDep */,
+ MachineState_T *aState /* = NULL */,
+ BOOL *aRegistered /* = NULL */)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(aDepType);
+ if (FAILED(rc)) return rc;
+
+ {
+ if (mData->mMachineStateChangePending != 0)
+ {
+ /* i_ensureNoStateDependencies() is waiting for state dependencies to
+ * drop to zero so don't add more. It may make sense to wait a bit
+ * and retry before reporting an error (since the pending state
+ * transition should be really quick) but let's just assert for
+ * now to see if it ever happens on practice. */
+
+ AssertFailed();
+
+ return setError(E_ACCESSDENIED,
+ tr("Machine state change is in progress. Please retry the operation later."));
+ }
+
+ ++mData->mMachineStateDeps;
+ Assert(mData->mMachineStateDeps != 0 /* overflow */);
+ }
+
+ if (aState)
+ *aState = mData->mMachineState;
+ if (aRegistered)
+ *aRegistered = mData->mRegistered;
+
+ return S_OK;
+}
+
+/**
+ * Decreases the number of objects dependent on the machine state.
+ * Must always complete the #i_addStateDependency() call after the state
+ * dependency is no more necessary.
+ */
+void Machine::i_releaseStateDependency()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* releaseStateDependency() w/o addStateDependency()? */
+ AssertReturnVoid(mData->mMachineStateDeps != 0);
+ -- mData->mMachineStateDeps;
+
+ if (mData->mMachineStateDeps == 0)
+ {
+ /* inform i_ensureNoStateDependencies() that there are no more deps */
+ if (mData->mMachineStateChangePending != 0)
+ {
+ Assert(mData->mMachineStateDepsSem != NIL_RTSEMEVENTMULTI);
+ RTSemEventMultiSignal (mData->mMachineStateDepsSem);
+ }
+ }
+}
+
+Utf8Str Machine::i_getExtraData(const Utf8Str &strKey)
+{
+ /* start with nothing found */
+ Utf8Str strResult("");
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ settings::StringsMap::const_iterator it = mData->pMachineConfigFile->mapExtraDataItems.find(strKey);
+ if (it != mData->pMachineConfigFile->mapExtraDataItems.end())
+ // found:
+ strResult = it->second; // source is a Utf8Str
+
+ return strResult;
+}
+
+// protected methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Performs machine state checks based on the @a aDepType value. If a check
+ * fails, this method will set extended error info, otherwise it will return
+ * S_OK. It is supposed, that on failure, the caller will immediately return
+ * the return value of this method to the upper level.
+ *
+ * When @a aDepType is AnyStateDep, this method always returns S_OK.
+ *
+ * When @a aDepType is MutableStateDep, this method returns S_OK only if the
+ * current state of this machine object allows to change settings of the
+ * machine (i.e. the machine is not registered, or registered but not running
+ * and not saved). It is useful to call this method from Machine setters
+ * before performing any change.
+ *
+ * When @a aDepType is MutableOrSavedStateDep, this method behaves the same
+ * as for MutableStateDep except that if the machine is saved, S_OK is also
+ * returned. This is useful in setters which allow changing machine
+ * properties when it is in the saved state.
+ *
+ * When @a aDepType is MutableOrRunningStateDep, this method returns S_OK only
+ * if the current state of this machine object allows to change runtime
+ * changeable settings of the machine (i.e. the machine is not registered, or
+ * registered but either running or not running and not saved). It is useful
+ * to call this method from Machine setters before performing any changes to
+ * runtime changeable settings.
+ *
+ * When @a aDepType is MutableOrSavedOrRunningStateDep, this method behaves
+ * the same as for MutableOrRunningStateDep except that if the machine is
+ * saved, S_OK is also returned. This is useful in setters which allow
+ * changing runtime and saved state changeable machine properties.
+ *
+ * @param aDepType Dependency type to check.
+ *
+ * @note Non Machine based classes should use #i_addStateDependency() and
+ * #i_releaseStateDependency() methods or the smart AutoStateDependency
+ * template.
+ *
+ * @note This method must be called from under this object's read or write
+ * lock.
+ */
+HRESULT Machine::i_checkStateDependency(StateDependency aDepType)
+{
+ switch (aDepType)
+ {
+ case AnyStateDep:
+ {
+ break;
+ }
+ case MutableStateDep:
+ {
+ if ( mData->mRegistered
+ && ( !i_isSessionMachine()
+ || ( mData->mMachineState != MachineState_Aborted
+ && mData->mMachineState != MachineState_Teleported
+ && mData->mMachineState != MachineState_PoweredOff
+ )
+ )
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("The machine is not mutable (state is %s)"),
+ Global::stringifyMachineState(mData->mMachineState));
+ break;
+ }
+ case MutableOrSavedStateDep:
+ {
+ if ( mData->mRegistered
+ && ( !i_isSessionMachine()
+ || ( mData->mMachineState != MachineState_Aborted
+ && mData->mMachineState != MachineState_Teleported
+ && mData->mMachineState != MachineState_Saved
+ && mData->mMachineState != MachineState_AbortedSaved
+ && mData->mMachineState != MachineState_PoweredOff
+ )
+ )
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("The machine is not mutable or saved (state is %s)"),
+ Global::stringifyMachineState(mData->mMachineState));
+ break;
+ }
+ case MutableOrRunningStateDep:
+ {
+ if ( mData->mRegistered
+ && ( !i_isSessionMachine()
+ || ( mData->mMachineState != MachineState_Aborted
+ && mData->mMachineState != MachineState_Teleported
+ && mData->mMachineState != MachineState_PoweredOff
+ && !Global::IsOnline(mData->mMachineState)
+ )
+ )
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("The machine is not mutable or running (state is %s)"),
+ Global::stringifyMachineState(mData->mMachineState));
+ break;
+ }
+ case MutableOrSavedOrRunningStateDep:
+ {
+ if ( mData->mRegistered
+ && ( !i_isSessionMachine()
+ || ( mData->mMachineState != MachineState_Aborted
+ && mData->mMachineState != MachineState_Teleported
+ && mData->mMachineState != MachineState_Saved
+ && mData->mMachineState != MachineState_AbortedSaved
+ && mData->mMachineState != MachineState_PoweredOff
+ && !Global::IsOnline(mData->mMachineState)
+ )
+ )
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("The machine is not mutable, saved or running (state is %s)"),
+ Global::stringifyMachineState(mData->mMachineState));
+ break;
+ }
+ }
+
+ return S_OK;
+}
+
+/**
+ * Helper to initialize all associated child objects and allocate data
+ * structures.
+ *
+ * This method must be called as a part of the object's initialization procedure
+ * (usually done in the #init() method).
+ *
+ * @note Must be called only from #init() or from #i_registeredInit().
+ */
+HRESULT Machine::initDataAndChildObjects()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+ AssertReturn( getObjectState().getState() == ObjectState::InInit
+ || getObjectState().getState() == ObjectState::Limited, E_FAIL);
+
+ AssertReturn(!mData->mAccessible, E_FAIL);
+
+ /* allocate data structures */
+ mSSData.allocate();
+ mUserData.allocate();
+ mHWData.allocate();
+ mMediumAttachments.allocate();
+ mStorageControllers.allocate();
+ mUSBControllers.allocate();
+
+ /* initialize mOSTypeId */
+ mUserData->s.strOsType = mParent->i_getUnknownOSType()->i_id();
+
+/** @todo r=bird: init() methods never fails, right? Why don't we make them
+ * return void then! */
+
+ /* create associated BIOS settings object */
+ unconst(mBIOSSettings).createObject();
+ mBIOSSettings->init(this);
+
+ /* create associated recording settings object */
+ unconst(mRecordingSettings).createObject();
+ mRecordingSettings->init(this);
+
+ /* create associated trusted platform module object */
+ unconst(mTrustedPlatformModule).createObject();
+ mTrustedPlatformModule->init(this);
+
+ /* create associated NVRAM store object */
+ unconst(mNvramStore).createObject();
+ mNvramStore->init(this);
+
+ /* create the graphics adapter object (always present) */
+ unconst(mGraphicsAdapter).createObject();
+ mGraphicsAdapter->init(this);
+
+ /* create an associated VRDE object (default is disabled) */
+ unconst(mVRDEServer).createObject();
+ mVRDEServer->init(this);
+
+ /* create associated serial port objects */
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); ++slot)
+ {
+ unconst(mSerialPorts[slot]).createObject();
+ mSerialPorts[slot]->init(this, slot);
+ }
+
+ /* create associated parallel port objects */
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); ++slot)
+ {
+ unconst(mParallelPorts[slot]).createObject();
+ mParallelPorts[slot]->init(this, slot);
+ }
+
+ /* create the audio settings object */
+ unconst(mAudioSettings).createObject();
+ mAudioSettings->init(this);
+
+ /* create the USB device filters object (always present) */
+ unconst(mUSBDeviceFilters).createObject();
+ mUSBDeviceFilters->init(this);
+
+ /* create associated network adapter objects */
+ mNetworkAdapters.resize(Global::getMaxNetworkAdapters(mHWData->mChipsetType));
+ for (ULONG slot = 0; slot < mNetworkAdapters.size(); ++slot)
+ {
+ unconst(mNetworkAdapters[slot]).createObject();
+ mNetworkAdapters[slot]->init(this, slot);
+ }
+
+ /* create the bandwidth control */
+ unconst(mBandwidthControl).createObject();
+ mBandwidthControl->init(this);
+
+ /* create the guest debug control object */
+ unconst(mGuestDebugControl).createObject();
+ mGuestDebugControl->init(this);
+
+ return S_OK;
+}
+
+/**
+ * Helper to uninitialize all associated child objects and to free all data
+ * structures.
+ *
+ * This method must be called as a part of the object's uninitialization
+ * procedure (usually done in the #uninit() method).
+ *
+ * @note Must be called only from #uninit() or from #i_registeredInit().
+ */
+void Machine::uninitDataAndChildObjects()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+ /* Machine object has state = ObjectState::InInit during registeredInit, even if it fails to get settings */
+ AssertReturnVoid( getObjectState().getState() == ObjectState::InInit
+ || getObjectState().getState() == ObjectState::InUninit
+ || getObjectState().getState() == ObjectState::Limited);
+
+ /* tell all our other child objects we've been uninitialized */
+ if (mGuestDebugControl)
+ {
+ mGuestDebugControl->uninit();
+ unconst(mGuestDebugControl).setNull();
+ }
+
+ if (mBandwidthControl)
+ {
+ mBandwidthControl->uninit();
+ unconst(mBandwidthControl).setNull();
+ }
+
+ for (ULONG slot = 0; slot < mNetworkAdapters.size(); ++slot)
+ {
+ if (mNetworkAdapters[slot])
+ {
+ mNetworkAdapters[slot]->uninit();
+ unconst(mNetworkAdapters[slot]).setNull();
+ }
+ }
+
+ if (mUSBDeviceFilters)
+ {
+ mUSBDeviceFilters->uninit();
+ unconst(mUSBDeviceFilters).setNull();
+ }
+
+ if (mAudioSettings)
+ {
+ mAudioSettings->uninit();
+ unconst(mAudioSettings).setNull();
+ }
+
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); ++slot)
+ {
+ if (mParallelPorts[slot])
+ {
+ mParallelPorts[slot]->uninit();
+ unconst(mParallelPorts[slot]).setNull();
+ }
+ }
+
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); ++slot)
+ {
+ if (mSerialPorts[slot])
+ {
+ mSerialPorts[slot]->uninit();
+ unconst(mSerialPorts[slot]).setNull();
+ }
+ }
+
+ if (mVRDEServer)
+ {
+ mVRDEServer->uninit();
+ unconst(mVRDEServer).setNull();
+ }
+
+ if (mGraphicsAdapter)
+ {
+ mGraphicsAdapter->uninit();
+ unconst(mGraphicsAdapter).setNull();
+ }
+
+ if (mBIOSSettings)
+ {
+ mBIOSSettings->uninit();
+ unconst(mBIOSSettings).setNull();
+ }
+
+ if (mRecordingSettings)
+ {
+ mRecordingSettings->uninit();
+ unconst(mRecordingSettings).setNull();
+ }
+
+ if (mTrustedPlatformModule)
+ {
+ mTrustedPlatformModule->uninit();
+ unconst(mTrustedPlatformModule).setNull();
+ }
+
+ if (mNvramStore)
+ {
+ mNvramStore->uninit();
+ unconst(mNvramStore).setNull();
+ }
+
+ /* Deassociate media (only when a real Machine or a SnapshotMachine
+ * instance is uninitialized; SessionMachine instances refer to real
+ * Machine media). This is necessary for a clean re-initialization of
+ * the VM after successfully re-checking the accessibility state. Note
+ * that in case of normal Machine or SnapshotMachine uninitialization (as
+ * a result of unregistering or deleting the snapshot), outdated media
+ * attachments will already be uninitialized and deleted, so this
+ * code will not affect them. */
+ if ( !mMediumAttachments.isNull()
+ && !i_isSessionMachine()
+ )
+ {
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ ComObjPtr<Medium> pMedium = (*it)->i_getMedium();
+ if (pMedium.isNull())
+ continue;
+ HRESULT rc = pMedium->i_removeBackReference(mData->mUuid, i_getSnapshotId());
+ AssertComRC(rc);
+ }
+ }
+
+ if (!i_isSessionMachine() && !i_isSnapshotMachine())
+ {
+ // clean up the snapshots list (Snapshot::uninit() will handle the snapshot's children)
+ if (mData->mFirstSnapshot)
+ {
+ // Snapshots tree is protected by machine write lock.
+ // Otherwise we assert in Snapshot::uninit()
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData->mFirstSnapshot->uninit();
+ mData->mFirstSnapshot.setNull();
+ }
+
+ mData->mCurrentSnapshot.setNull();
+ }
+
+ /* free data structures (the essential mData structure is not freed here
+ * since it may be still in use) */
+ mMediumAttachments.free();
+ mStorageControllers.free();
+ mUSBControllers.free();
+ mHWData.free();
+ mUserData.free();
+ mSSData.free();
+}
+
+/**
+ * Returns a pointer to the Machine object for this machine that acts like a
+ * parent for complex machine data objects such as shared folders, etc.
+ *
+ * For primary Machine objects and for SnapshotMachine objects, returns this
+ * object's pointer itself. For SessionMachine objects, returns the peer
+ * (primary) machine pointer.
+ */
+Machine *Machine::i_getMachine()
+{
+ if (i_isSessionMachine())
+ return (Machine*)mPeer;
+ return this;
+}
+
+/**
+ * Makes sure that there are no machine state dependents. If necessary, waits
+ * for the number of dependents to drop to zero.
+ *
+ * Make sure this method is called from under this object's write lock to
+ * guarantee that no new dependents may be added when this method returns
+ * control to the caller.
+ *
+ * @note Receives a lock to this object for writing. The lock will be released
+ * while waiting (if necessary).
+ *
+ * @warning To be used only in methods that change the machine state!
+ */
+void Machine::i_ensureNoStateDependencies(AutoWriteLock &alock)
+{
+ AssertReturnVoid(isWriteLockOnCurrentThread());
+
+ /* Wait for all state dependents if necessary */
+ if (mData->mMachineStateDeps != 0)
+ {
+ /* lazy semaphore creation */
+ if (mData->mMachineStateDepsSem == NIL_RTSEMEVENTMULTI)
+ RTSemEventMultiCreate(&mData->mMachineStateDepsSem);
+
+ LogFlowThisFunc(("Waiting for state deps (%d) to drop to zero...\n",
+ mData->mMachineStateDeps));
+
+ ++mData->mMachineStateChangePending;
+
+ /* reset the semaphore before waiting, the last dependent will signal
+ * it */
+ RTSemEventMultiReset(mData->mMachineStateDepsSem);
+
+ alock.release();
+
+ RTSemEventMultiWait(mData->mMachineStateDepsSem, RT_INDEFINITE_WAIT);
+
+ alock.acquire();
+
+ -- mData->mMachineStateChangePending;
+ }
+}
+
+/**
+ * Changes the machine state and informs callbacks.
+ *
+ * This method is not intended to fail so it either returns S_OK or asserts (and
+ * returns a failure).
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Machine::i_setMachineState(MachineState_T aMachineState)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aMachineState=%s\n", ::stringifyMachineState(aMachineState) ));
+ Assert(aMachineState != MachineState_Null);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* wait for state dependents to drop to zero */
+ i_ensureNoStateDependencies(alock);
+
+ MachineState_T const enmOldState = mData->mMachineState;
+ if (enmOldState != aMachineState)
+ {
+ mData->mMachineState = aMachineState;
+ RTTimeNow(&mData->mLastStateChange);
+
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+ VBOXAPI_MACHINE_STATE_CHANGED(this, aMachineState, enmOldState, mData->mUuid.toStringCurly().c_str());
+#endif
+ mParent->i_onMachineStateChanged(mData->mUuid, aMachineState);
+ }
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Searches for a shared folder with the given logical name
+ * in the collection of shared folders.
+ *
+ * @param aName logical name of the shared folder
+ * @param aSharedFolder where to return the found object
+ * @param aSetError whether to set the error info if the folder is
+ * not found
+ * @return
+ * S_OK when found or VBOX_E_OBJECT_NOT_FOUND when not found
+ *
+ * @note
+ * must be called from under the object's lock!
+ */
+HRESULT Machine::i_findSharedFolder(const Utf8Str &aName,
+ ComObjPtr<SharedFolder> &aSharedFolder,
+ bool aSetError /* = false */)
+{
+ HRESULT rc = VBOX_E_OBJECT_NOT_FOUND;
+ for (HWData::SharedFolderList::const_iterator
+ it = mHWData->mSharedFolders.begin();
+ it != mHWData->mSharedFolders.end();
+ ++it)
+ {
+ SharedFolder *pSF = *it;
+ AutoCaller autoCaller(pSF);
+ if (pSF->i_getName() == aName)
+ {
+ aSharedFolder = pSF;
+ rc = S_OK;
+ break;
+ }
+ }
+
+ if (aSetError && FAILED(rc))
+ setError(rc, tr("Could not find a shared folder named '%s'"), aName.c_str());
+
+ return rc;
+}
+
+/**
+ * Initializes all machine instance data from the given settings structures
+ * from XML. The exception is the machine UUID which needs special handling
+ * depending on the caller's use case, so the caller needs to set that herself.
+ *
+ * This gets called in several contexts during machine initialization:
+ *
+ * -- When machine XML exists on disk already and needs to be loaded into memory,
+ * for example, from #i_registeredInit() to load all registered machines on
+ * VirtualBox startup. In this case, puuidRegistry is NULL because the media
+ * attached to the machine should be part of some media registry already.
+ *
+ * -- During OVF import, when a machine config has been constructed from an
+ * OVF file. In this case, puuidRegistry is set to the machine UUID to
+ * ensure that the media listed as attachments in the config (which have
+ * been imported from the OVF) receive the correct registry ID.
+ *
+ * -- During VM cloning.
+ *
+ * @param config Machine settings from XML.
+ * @param puuidRegistry If != NULL, Medium::setRegistryIdIfFirst() gets called with this registry ID
+ * for each attached medium in the config.
+ * @return
+ */
+HRESULT Machine::i_loadMachineDataFromSettings(const settings::MachineConfigFile &config,
+ const Guid *puuidRegistry)
+{
+ // copy name, description, OS type, teleporter, UTC etc.
+ mUserData->s = config.machineUserData;
+
+ // look up the object by Id to check it is valid
+ ComObjPtr<GuestOSType> pGuestOSType;
+ mParent->i_findGuestOSType(mUserData->s.strOsType, pGuestOSType);
+ if (!pGuestOSType.isNull())
+ mUserData->s.strOsType = pGuestOSType->i_id();
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ // stateFile encryption (optional)
+ mSSData->strStateKeyId = config.strStateKeyId;
+ mSSData->strStateKeyStore = config.strStateKeyStore;
+ mData->mstrLogKeyId = config.strLogKeyId;
+ mData->mstrLogKeyStore = config.strLogKeyStore;
+#endif
+
+ // stateFile (optional)
+ if (config.strStateFile.isEmpty())
+ mSSData->strStateFilePath.setNull();
+ else
+ {
+ Utf8Str stateFilePathFull(config.strStateFile);
+ int vrc = i_calculateFullPath(stateFilePathFull, stateFilePathFull);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc,
+ tr("Invalid saved state file path '%s' (%Rrc)"),
+ config.strStateFile.c_str(),
+ vrc);
+ mSSData->strStateFilePath = stateFilePathFull;
+ }
+
+ // snapshot folder needs special processing so set it again
+ HRESULT rc = COMSETTER(SnapshotFolder)(Bstr(config.machineUserData.strSnapshotFolder).raw());
+ if (FAILED(rc)) return rc;
+
+ /* Copy the extra data items (config may or may not be the same as
+ * mData->pMachineConfigFile) if necessary. When loading the XML files
+ * from disk they are the same, but not for OVF import. */
+ if (mData->pMachineConfigFile != &config)
+ mData->pMachineConfigFile->mapExtraDataItems = config.mapExtraDataItems;
+
+ /* currentStateModified (optional, default is true) */
+ mData->mCurrentStateModified = config.fCurrentStateModified;
+
+ mData->mLastStateChange = config.timeLastStateChange;
+
+ /*
+ * note: all mUserData members must be assigned prior this point because
+ * we need to commit changes in order to let mUserData be shared by all
+ * snapshot machine instances.
+ */
+ mUserData.commitCopy();
+
+ // machine registry, if present (must be loaded before snapshots)
+ if (config.canHaveOwnMediaRegistry())
+ {
+ // determine machine folder
+ Utf8Str strMachineFolder = i_getSettingsFileFull();
+ strMachineFolder.stripFilename();
+ rc = mParent->initMedia(i_getId(), // media registry ID == machine UUID
+ config.mediaRegistry,
+ strMachineFolder);
+ if (FAILED(rc)) return rc;
+ }
+
+ /* Snapshot node (optional) */
+ size_t cRootSnapshots;
+ if ((cRootSnapshots = config.llFirstSnapshot.size()))
+ {
+ // there must be only one root snapshot
+ Assert(cRootSnapshots == 1);
+ const settings::Snapshot &snap = config.llFirstSnapshot.front();
+
+ rc = i_loadSnapshot(snap,
+ config.uuidCurrentSnapshot);
+ if (FAILED(rc)) return rc;
+ }
+
+ // hardware data
+ rc = i_loadHardware(puuidRegistry, NULL, config.hardwareMachine, &config.debugging, &config.autostart,
+ config.recordingSettings);
+ if (FAILED(rc)) return rc;
+
+ /*
+ * NOTE: the assignment below must be the last thing to do,
+ * otherwise it will be not possible to change the settings
+ * somewhere in the code above because all setters will be
+ * blocked by i_checkStateDependency(MutableStateDep).
+ */
+
+ /* set the machine state to either Aborted-Saved, Aborted, or Saved if appropriate */
+ if (config.fAborted && !mSSData->strStateFilePath.isEmpty())
+ {
+ /* no need to use i_setMachineState() during init() */
+ mData->mMachineState = MachineState_AbortedSaved;
+ }
+ else if (config.fAborted)
+ {
+ mSSData->strStateFilePath.setNull();
+
+ /* no need to use i_setMachineState() during init() */
+ mData->mMachineState = MachineState_Aborted;
+ }
+ else if (!mSSData->strStateFilePath.isEmpty())
+ {
+ /* no need to use i_setMachineState() during init() */
+ mData->mMachineState = MachineState_Saved;
+ }
+
+ // after loading settings, we are no longer different from the XML on disk
+ mData->flModifications = 0;
+
+ return S_OK;
+}
+
+/**
+ * Loads all snapshots starting from the given settings.
+ *
+ * @param data snapshot settings.
+ * @param aCurSnapshotId Current snapshot ID from the settings file.
+ */
+HRESULT Machine::i_loadSnapshot(const settings::Snapshot &data,
+ const Guid &aCurSnapshotId)
+{
+ AssertReturn(!i_isSnapshotMachine(), E_FAIL);
+ AssertReturn(!i_isSessionMachine(), E_FAIL);
+
+ HRESULT rc = S_OK;
+
+ std::list<const settings::Snapshot *> llSettingsTodo;
+ llSettingsTodo.push_back(&data);
+ std::list<Snapshot *> llParentsTodo;
+ llParentsTodo.push_back(NULL);
+
+ while (llSettingsTodo.size() > 0)
+ {
+ const settings::Snapshot *current = llSettingsTodo.front();
+ llSettingsTodo.pop_front();
+ Snapshot *pParent = llParentsTodo.front();
+ llParentsTodo.pop_front();
+
+ Utf8Str strStateFile;
+ if (!current->strStateFile.isEmpty())
+ {
+ /* optional */
+ strStateFile = current->strStateFile;
+ int vrc = i_calculateFullPath(strStateFile, strStateFile);
+ if (RT_FAILURE(vrc))
+ {
+ setErrorBoth(E_FAIL, vrc,
+ tr("Invalid saved state file path '%s' (%Rrc)"),
+ strStateFile.c_str(), vrc);
+ }
+ }
+
+ /* create a snapshot machine object */
+ ComObjPtr<SnapshotMachine> pSnapshotMachine;
+ pSnapshotMachine.createObject();
+ rc = pSnapshotMachine->initFromSettings(this,
+ current->hardware,
+ &current->debugging,
+ &current->autostart,
+ current->recordingSettings,
+ current->uuid.ref(),
+ strStateFile);
+ if (FAILED(rc)) break;
+
+ /* create a snapshot object */
+ ComObjPtr<Snapshot> pSnapshot;
+ pSnapshot.createObject();
+ /* initialize the snapshot */
+ rc = pSnapshot->init(mParent, // VirtualBox object
+ current->uuid,
+ current->strName,
+ current->strDescription,
+ current->timestamp,
+ pSnapshotMachine,
+ pParent);
+ if (FAILED(rc)) break;
+
+ /* memorize the first snapshot if necessary */
+ if (!mData->mFirstSnapshot)
+ {
+ Assert(pParent == NULL);
+ mData->mFirstSnapshot = pSnapshot;
+ }
+
+ /* memorize the current snapshot when appropriate */
+ if ( !mData->mCurrentSnapshot
+ && pSnapshot->i_getId() == aCurSnapshotId
+ )
+ mData->mCurrentSnapshot = pSnapshot;
+
+ /* create all children */
+ std::list<settings::Snapshot>::const_iterator itBegin = current->llChildSnapshots.begin();
+ std::list<settings::Snapshot>::const_iterator itEnd = current->llChildSnapshots.end();
+ for (std::list<settings::Snapshot>::const_iterator it = itBegin; it != itEnd; ++it)
+ {
+ llSettingsTodo.push_back(&*it);
+ llParentsTodo.push_back(pSnapshot);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Loads settings into mHWData.
+ *
+ * @param puuidRegistry Registry ID.
+ * @param puuidSnapshot Snapshot ID
+ * @param data Reference to the hardware settings.
+ * @param pDbg Pointer to the debugging settings.
+ * @param pAutostart Pointer to the autostart settings
+ * @param recording Reference to recording settings.
+ */
+HRESULT Machine::i_loadHardware(const Guid *puuidRegistry,
+ const Guid *puuidSnapshot,
+ const settings::Hardware &data,
+ const settings::Debugging *pDbg,
+ const settings::Autostart *pAutostart,
+ const settings::RecordingSettings &recording)
+{
+ AssertReturn(!i_isSessionMachine(), E_FAIL);
+
+ HRESULT rc = S_OK;
+
+ try
+ {
+ ComObjPtr<GuestOSType> pGuestOSType;
+ mParent->i_findGuestOSType(mUserData->s.strOsType, pGuestOSType);
+
+ /* The hardware version attribute (optional). */
+ mHWData->mHWVersion = data.strVersion;
+ mHWData->mHardwareUUID = data.uuid;
+
+ mHWData->mHWVirtExEnabled = data.fHardwareVirt;
+ mHWData->mHWVirtExNestedPagingEnabled = data.fNestedPaging;
+ mHWData->mHWVirtExLargePagesEnabled = data.fLargePages;
+ mHWData->mHWVirtExVPIDEnabled = data.fVPID;
+ mHWData->mHWVirtExUXEnabled = data.fUnrestrictedExecution;
+ mHWData->mHWVirtExForceEnabled = data.fHardwareVirtForce;
+ mHWData->mHWVirtExUseNativeApi = data.fUseNativeApi;
+ mHWData->mHWVirtExVirtVmsaveVmload = data.fVirtVmsaveVmload;
+ mHWData->mPAEEnabled = data.fPAE;
+ mHWData->mLongMode = data.enmLongMode;
+ mHWData->mTripleFaultReset = data.fTripleFaultReset;
+ mHWData->mAPIC = data.fAPIC;
+ mHWData->mX2APIC = data.fX2APIC;
+ mHWData->mIBPBOnVMExit = data.fIBPBOnVMExit;
+ mHWData->mIBPBOnVMEntry = data.fIBPBOnVMEntry;
+ mHWData->mSpecCtrl = data.fSpecCtrl;
+ mHWData->mSpecCtrlByHost = data.fSpecCtrlByHost;
+ mHWData->mL1DFlushOnSched = data.fL1DFlushOnSched;
+ mHWData->mL1DFlushOnVMEntry = data.fL1DFlushOnVMEntry;
+ mHWData->mMDSClearOnSched = data.fMDSClearOnSched;
+ mHWData->mMDSClearOnVMEntry = data.fMDSClearOnVMEntry;
+ mHWData->mNestedHWVirt = data.fNestedHWVirt;
+ mHWData->mCPUCount = data.cCPUs;
+ mHWData->mCPUHotPlugEnabled = data.fCpuHotPlug;
+ mHWData->mCpuExecutionCap = data.ulCpuExecutionCap;
+ mHWData->mCpuIdPortabilityLevel = data.uCpuIdPortabilityLevel;
+ mHWData->mCpuProfile = data.strCpuProfile;
+
+ // cpu
+ if (mHWData->mCPUHotPlugEnabled)
+ {
+ for (settings::CpuList::const_iterator
+ it = data.llCpus.begin();
+ it != data.llCpus.end();
+ ++it)
+ {
+ const settings::Cpu &cpu = *it;
+
+ mHWData->mCPUAttached[cpu.ulId] = true;
+ }
+ }
+
+ // cpuid leafs
+ for (settings::CpuIdLeafsList::const_iterator
+ it = data.llCpuIdLeafs.begin();
+ it != data.llCpuIdLeafs.end();
+ ++it)
+ {
+ const settings::CpuIdLeaf &rLeaf= *it;
+ if ( rLeaf.idx < UINT32_C(0x20)
+ || rLeaf.idx - UINT32_C(0x80000000) < UINT32_C(0x20)
+ || rLeaf.idx - UINT32_C(0xc0000000) < UINT32_C(0x10) )
+ mHWData->mCpuIdLeafList.push_back(rLeaf);
+ /* else: just ignore */
+ }
+
+ mHWData->mMemorySize = data.ulMemorySizeMB;
+ mHWData->mPageFusionEnabled = data.fPageFusionEnabled;
+
+ // boot order
+ for (unsigned i = 0; i < RT_ELEMENTS(mHWData->mBootOrder); ++i)
+ {
+ settings::BootOrderMap::const_iterator it = data.mapBootOrder.find(i);
+ if (it == data.mapBootOrder.end())
+ mHWData->mBootOrder[i] = DeviceType_Null;
+ else
+ mHWData->mBootOrder[i] = it->second;
+ }
+
+ mHWData->mFirmwareType = data.firmwareType;
+ mHWData->mPointingHIDType = data.pointingHIDType;
+ mHWData->mKeyboardHIDType = data.keyboardHIDType;
+ mHWData->mChipsetType = data.chipsetType;
+ mHWData->mIommuType = data.iommuType;
+ mHWData->mParavirtProvider = data.paravirtProvider;
+ mHWData->mParavirtDebug = data.strParavirtDebug;
+ mHWData->mEmulatedUSBCardReaderEnabled = data.fEmulatedUSBCardReader;
+ mHWData->mHPETEnabled = data.fHPETEnabled;
+
+ /* GraphicsAdapter */
+ rc = mGraphicsAdapter->i_loadSettings(data.graphicsAdapter);
+ if (FAILED(rc)) return rc;
+
+ /* VRDEServer */
+ rc = mVRDEServer->i_loadSettings(data.vrdeSettings);
+ if (FAILED(rc)) return rc;
+
+ /* BIOS */
+ rc = mBIOSSettings->i_loadSettings(data.biosSettings);
+ if (FAILED(rc)) return rc;
+
+ /* Recording */
+ rc = mRecordingSettings->i_loadSettings(recording);
+ if (FAILED(rc)) return rc;
+
+ /* Trusted Platform Module */
+ rc = mTrustedPlatformModule->i_loadSettings(data.tpmSettings);
+ if (FAILED(rc)) return rc;
+
+ rc = mNvramStore->i_loadSettings(data.nvramSettings);
+ if (FAILED(rc)) return rc;
+
+ // Bandwidth control (must come before network adapters)
+ rc = mBandwidthControl->i_loadSettings(data.ioSettings);
+ if (FAILED(rc)) return rc;
+
+ /* USB controllers */
+ for (settings::USBControllerList::const_iterator
+ it = data.usbSettings.llUSBControllers.begin();
+ it != data.usbSettings.llUSBControllers.end();
+ ++it)
+ {
+ const settings::USBController &settingsCtrl = *it;
+ ComObjPtr<USBController> newCtrl;
+
+ newCtrl.createObject();
+ newCtrl->init(this, settingsCtrl.strName, settingsCtrl.enmType);
+ mUSBControllers->push_back(newCtrl);
+ }
+
+ /* USB device filters */
+ rc = mUSBDeviceFilters->i_loadSettings(data.usbSettings);
+ if (FAILED(rc)) return rc;
+
+ // network adapters (establish array size first and apply defaults, to
+ // ensure reading the same settings as we saved, since the list skips
+ // adapters having defaults)
+ size_t newCount = Global::getMaxNetworkAdapters(mHWData->mChipsetType);
+ size_t oldCount = mNetworkAdapters.size();
+ if (newCount > oldCount)
+ {
+ mNetworkAdapters.resize(newCount);
+ for (size_t slot = oldCount; slot < mNetworkAdapters.size(); ++slot)
+ {
+ unconst(mNetworkAdapters[slot]).createObject();
+ mNetworkAdapters[slot]->init(this, (ULONG)slot);
+ }
+ }
+ else if (newCount < oldCount)
+ mNetworkAdapters.resize(newCount);
+ for (unsigned i = 0; i < mNetworkAdapters.size(); i++)
+ mNetworkAdapters[i]->i_applyDefaults(pGuestOSType);
+ for (settings::NetworkAdaptersList::const_iterator
+ it = data.llNetworkAdapters.begin();
+ it != data.llNetworkAdapters.end();
+ ++it)
+ {
+ const settings::NetworkAdapter &nic = *it;
+
+ /* slot uniqueness is guaranteed by XML Schema */
+ AssertBreak(nic.ulSlot < mNetworkAdapters.size());
+ rc = mNetworkAdapters[nic.ulSlot]->i_loadSettings(mBandwidthControl, nic);
+ if (FAILED(rc)) return rc;
+ }
+
+ // serial ports (establish defaults first, to ensure reading the same
+ // settings as we saved, since the list skips ports having defaults)
+ for (unsigned i = 0; i < RT_ELEMENTS(mSerialPorts); i++)
+ mSerialPorts[i]->i_applyDefaults(pGuestOSType);
+ for (settings::SerialPortsList::const_iterator
+ it = data.llSerialPorts.begin();
+ it != data.llSerialPorts.end();
+ ++it)
+ {
+ const settings::SerialPort &s = *it;
+
+ AssertBreak(s.ulSlot < RT_ELEMENTS(mSerialPorts));
+ rc = mSerialPorts[s.ulSlot]->i_loadSettings(s);
+ if (FAILED(rc)) return rc;
+ }
+
+ // parallel ports (establish defaults first, to ensure reading the same
+ // settings as we saved, since the list skips ports having defaults)
+ for (unsigned i = 0; i < RT_ELEMENTS(mParallelPorts); i++)
+ mParallelPorts[i]->i_applyDefaults();
+ for (settings::ParallelPortsList::const_iterator
+ it = data.llParallelPorts.begin();
+ it != data.llParallelPorts.end();
+ ++it)
+ {
+ const settings::ParallelPort &p = *it;
+
+ AssertBreak(p.ulSlot < RT_ELEMENTS(mParallelPorts));
+ rc = mParallelPorts[p.ulSlot]->i_loadSettings(p);
+ if (FAILED(rc)) return rc;
+ }
+
+ /* Audio settings */
+ rc = mAudioSettings->i_loadSettings(data.audioAdapter);
+ if (FAILED(rc)) return rc;
+
+ /* storage controllers */
+ rc = i_loadStorageControllers(data.storage,
+ puuidRegistry,
+ puuidSnapshot);
+ if (FAILED(rc)) return rc;
+
+ /* Shared folders */
+ for (settings::SharedFoldersList::const_iterator
+ it = data.llSharedFolders.begin();
+ it != data.llSharedFolders.end();
+ ++it)
+ {
+ const settings::SharedFolder &sf = *it;
+
+ ComObjPtr<SharedFolder> sharedFolder;
+ /* Check for double entries. Not allowed! */
+ rc = i_findSharedFolder(sf.strName, sharedFolder, false /* aSetError */);
+ if (SUCCEEDED(rc))
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Shared folder named '%s' already exists"),
+ sf.strName.c_str());
+
+ /* Create the new shared folder. Don't break on error. This will be
+ * reported when the machine starts. */
+ sharedFolder.createObject();
+ rc = sharedFolder->init(i_getMachine(),
+ sf.strName,
+ sf.strHostPath,
+ RT_BOOL(sf.fWritable),
+ RT_BOOL(sf.fAutoMount),
+ sf.strAutoMountPoint,
+ false /* fFailOnError */);
+ if (FAILED(rc)) return rc;
+ mHWData->mSharedFolders.push_back(sharedFolder);
+ }
+
+ // Clipboard
+ mHWData->mClipboardMode = data.clipboardMode;
+ mHWData->mClipboardFileTransfersEnabled = data.fClipboardFileTransfersEnabled ? TRUE : FALSE;
+
+ // drag'n'drop
+ mHWData->mDnDMode = data.dndMode;
+
+ // guest settings
+ mHWData->mMemoryBalloonSize = data.ulMemoryBalloonSize;
+
+ // IO settings
+ mHWData->mIOCacheEnabled = data.ioSettings.fIOCacheEnabled;
+ mHWData->mIOCacheSize = data.ioSettings.ulIOCacheSize;
+
+ // Host PCI devices
+ for (settings::HostPCIDeviceAttachmentList::const_iterator
+ it = data.pciAttachments.begin();
+ it != data.pciAttachments.end();
+ ++it)
+ {
+ const settings::HostPCIDeviceAttachment &hpda = *it;
+ ComObjPtr<PCIDeviceAttachment> pda;
+
+ pda.createObject();
+ pda->i_loadSettings(this, hpda);
+ mHWData->mPCIDeviceAssignments.push_back(pda);
+ }
+
+ /*
+ * (The following isn't really real hardware, but it lives in HWData
+ * for reasons of convenience.)
+ */
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ /* Guest properties (optional) */
+
+ /* Only load transient guest properties for configs which have saved
+ * state, because there shouldn't be any for powered off VMs. The same
+ * logic applies for snapshots, as offline snapshots shouldn't have
+ * any such properties. They confuse the code in various places.
+ * Note: can't rely on the machine state, as it isn't set yet. */
+ bool fSkipTransientGuestProperties = mSSData->strStateFilePath.isEmpty();
+ /* apologies for the hacky unconst() usage, but this needs hacking
+ * actually inconsistent settings into consistency, otherwise there
+ * will be some corner cases where the inconsistency survives
+ * surprisingly long without getting fixed, especially for snapshots
+ * as there are no config changes. */
+ settings::GuestPropertiesList &llGuestProperties = unconst(data.llGuestProperties);
+ for (settings::GuestPropertiesList::iterator
+ it = llGuestProperties.begin();
+ it != llGuestProperties.end();
+ /*nothing*/)
+ {
+ const settings::GuestProperty &prop = *it;
+ uint32_t fFlags = GUEST_PROP_F_NILFLAG;
+ GuestPropValidateFlags(prop.strFlags.c_str(), &fFlags);
+ if ( fSkipTransientGuestProperties
+ && ( fFlags & GUEST_PROP_F_TRANSIENT
+ || fFlags & GUEST_PROP_F_TRANSRESET))
+ {
+ it = llGuestProperties.erase(it);
+ continue;
+ }
+ HWData::GuestProperty property = { prop.strValue, (LONG64) prop.timestamp, fFlags };
+ mHWData->mGuestProperties[prop.strName] = property;
+ ++it;
+ }
+#endif /* VBOX_WITH_GUEST_PROPS defined */
+
+ rc = i_loadDebugging(pDbg);
+ if (FAILED(rc))
+ return rc;
+
+ mHWData->mAutostart = *pAutostart;
+
+ /* default frontend */
+ mHWData->mDefaultFrontend = data.strDefaultFrontend;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ AssertComRC(rc);
+ return rc;
+}
+
+/**
+ * Called from i_loadHardware() to load the debugging settings of the
+ * machine.
+ *
+ * @param pDbg Pointer to the settings.
+ */
+HRESULT Machine::i_loadDebugging(const settings::Debugging *pDbg)
+{
+ mHWData->mDebugging = *pDbg;
+ /* no more processing currently required, this will probably change. */
+
+ HRESULT rc = mGuestDebugControl->i_loadSettings(*pDbg);
+ if (FAILED(rc)) return rc;
+
+ return S_OK;
+}
+
+/**
+ * Called from i_loadMachineDataFromSettings() for the storage controller data, including media.
+ *
+ * @param data storage settings.
+ * @param puuidRegistry media registry ID to set media to or NULL;
+ * see Machine::i_loadMachineDataFromSettings()
+ * @param puuidSnapshot snapshot ID
+ * @return
+ */
+HRESULT Machine::i_loadStorageControllers(const settings::Storage &data,
+ const Guid *puuidRegistry,
+ const Guid *puuidSnapshot)
+{
+ AssertReturn(!i_isSessionMachine(), E_FAIL);
+
+ HRESULT rc = S_OK;
+
+ for (settings::StorageControllersList::const_iterator
+ it = data.llStorageControllers.begin();
+ it != data.llStorageControllers.end();
+ ++it)
+ {
+ const settings::StorageController &ctlData = *it;
+
+ ComObjPtr<StorageController> pCtl;
+ /* Try to find one with the name first. */
+ rc = i_getStorageControllerByName(ctlData.strName, pCtl, false /* aSetError */);
+ if (SUCCEEDED(rc))
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Storage controller named '%s' already exists"),
+ ctlData.strName.c_str());
+
+ pCtl.createObject();
+ rc = pCtl->init(this,
+ ctlData.strName,
+ ctlData.storageBus,
+ ctlData.ulInstance,
+ ctlData.fBootable);
+ if (FAILED(rc)) return rc;
+
+ mStorageControllers->push_back(pCtl);
+
+ rc = pCtl->COMSETTER(ControllerType)(ctlData.controllerType);
+ if (FAILED(rc)) return rc;
+
+ rc = pCtl->COMSETTER(PortCount)(ctlData.ulPortCount);
+ if (FAILED(rc)) return rc;
+
+ rc = pCtl->COMSETTER(UseHostIOCache)(ctlData.fUseHostIOCache);
+ if (FAILED(rc)) return rc;
+
+ /* Load the attached devices now. */
+ rc = i_loadStorageDevices(pCtl,
+ ctlData,
+ puuidRegistry,
+ puuidSnapshot);
+ if (FAILED(rc)) return rc;
+ }
+
+ return S_OK;
+}
+
+/**
+ * Called from i_loadStorageControllers for a controller's devices.
+ *
+ * @param aStorageController
+ * @param data
+ * @param puuidRegistry media registry ID to set media to or NULL; see
+ * Machine::i_loadMachineDataFromSettings()
+ * @param puuidSnapshot pointer to the snapshot ID if this is a snapshot machine
+ * @return
+ */
+HRESULT Machine::i_loadStorageDevices(StorageController *aStorageController,
+ const settings::StorageController &data,
+ const Guid *puuidRegistry,
+ const Guid *puuidSnapshot)
+{
+ HRESULT rc = S_OK;
+
+ /* paranoia: detect duplicate attachments */
+ for (settings::AttachedDevicesList::const_iterator
+ it = data.llAttachedDevices.begin();
+ it != data.llAttachedDevices.end();
+ ++it)
+ {
+ const settings::AttachedDevice &ad = *it;
+
+ for (settings::AttachedDevicesList::const_iterator it2 = it;
+ it2 != data.llAttachedDevices.end();
+ ++it2)
+ {
+ if (it == it2)
+ continue;
+
+ const settings::AttachedDevice &ad2 = *it2;
+
+ if ( ad.lPort == ad2.lPort
+ && ad.lDevice == ad2.lDevice)
+ {
+ return setError(E_FAIL,
+ tr("Duplicate attachments for storage controller '%s', port %d, device %d of the virtual machine '%s'"),
+ aStorageController->i_getName().c_str(),
+ ad.lPort,
+ ad.lDevice,
+ mUserData->s.strName.c_str());
+ }
+ }
+ }
+
+ for (settings::AttachedDevicesList::const_iterator
+ it = data.llAttachedDevices.begin();
+ it != data.llAttachedDevices.end();
+ ++it)
+ {
+ const settings::AttachedDevice &dev = *it;
+ ComObjPtr<Medium> medium;
+
+ switch (dev.deviceType)
+ {
+ case DeviceType_Floppy:
+ case DeviceType_DVD:
+ if (dev.strHostDriveSrc.isNotEmpty())
+ rc = mParent->i_host()->i_findHostDriveByName(dev.deviceType, dev.strHostDriveSrc,
+ false /* fRefresh */, medium);
+ else
+ rc = mParent->i_findRemoveableMedium(dev.deviceType,
+ dev.uuid,
+ false /* fRefresh */,
+ false /* aSetError */,
+ medium);
+ if (rc == VBOX_E_OBJECT_NOT_FOUND)
+ // This is not an error. The host drive or UUID might have vanished, so just go
+ // ahead without this removeable medium attachment
+ rc = S_OK;
+ break;
+
+ case DeviceType_HardDisk:
+ {
+ /* find a hard disk by UUID */
+ rc = mParent->i_findHardDiskById(dev.uuid, true /* aDoSetError */, &medium);
+ if (FAILED(rc))
+ {
+ if (i_isSnapshotMachine())
+ {
+ // wrap another error message around the "cannot find hard disk" set by findHardDisk
+ // so the user knows that the bad disk is in a snapshot somewhere
+ com::ErrorInfo info;
+ return setError(E_FAIL,
+ tr("A differencing image of snapshot {%RTuuid} could not be found. %ls"),
+ puuidSnapshot->raw(),
+ info.getText().raw());
+ }
+ else
+ return rc;
+ }
+
+ AutoWriteLock hdLock(medium COMMA_LOCKVAL_SRC_POS);
+
+ if (medium->i_getType() == MediumType_Immutable)
+ {
+ if (i_isSnapshotMachine())
+ return setError(E_FAIL,
+ tr("Immutable hard disk '%s' with UUID {%RTuuid} cannot be directly attached to snapshot with UUID {%RTuuid} "
+ "of the virtual machine '%s' ('%s')"),
+ medium->i_getLocationFull().c_str(),
+ dev.uuid.raw(),
+ puuidSnapshot->raw(),
+ mUserData->s.strName.c_str(),
+ mData->m_strConfigFileFull.c_str());
+
+ return setError(E_FAIL,
+ tr("Immutable hard disk '%s' with UUID {%RTuuid} cannot be directly attached to the virtual machine '%s' ('%s')"),
+ medium->i_getLocationFull().c_str(),
+ dev.uuid.raw(),
+ mUserData->s.strName.c_str(),
+ mData->m_strConfigFileFull.c_str());
+ }
+
+ if (medium->i_getType() == MediumType_MultiAttach)
+ {
+ if (i_isSnapshotMachine())
+ return setError(E_FAIL,
+ tr("Multi-attach hard disk '%s' with UUID {%RTuuid} cannot be directly attached to snapshot with UUID {%RTuuid} "
+ "of the virtual machine '%s' ('%s')"),
+ medium->i_getLocationFull().c_str(),
+ dev.uuid.raw(),
+ puuidSnapshot->raw(),
+ mUserData->s.strName.c_str(),
+ mData->m_strConfigFileFull.c_str());
+
+ return setError(E_FAIL,
+ tr("Multi-attach hard disk '%s' with UUID {%RTuuid} cannot be directly attached to the virtual machine '%s' ('%s')"),
+ medium->i_getLocationFull().c_str(),
+ dev.uuid.raw(),
+ mUserData->s.strName.c_str(),
+ mData->m_strConfigFileFull.c_str());
+ }
+
+ if ( !i_isSnapshotMachine()
+ && medium->i_getChildren().size() != 0
+ )
+ return setError(E_FAIL,
+ tr("Hard disk '%s' with UUID {%RTuuid} cannot be directly attached to the virtual machine '%s' ('%s') "
+ "because it has %d differencing child hard disks"),
+ medium->i_getLocationFull().c_str(),
+ dev.uuid.raw(),
+ mUserData->s.strName.c_str(),
+ mData->m_strConfigFileFull.c_str(),
+ medium->i_getChildren().size());
+
+ if (i_findAttachment(*mMediumAttachments.data(),
+ medium))
+ return setError(E_FAIL,
+ tr("Hard disk '%s' with UUID {%RTuuid} is already attached to the virtual machine '%s' ('%s')"),
+ medium->i_getLocationFull().c_str(),
+ dev.uuid.raw(),
+ mUserData->s.strName.c_str(),
+ mData->m_strConfigFileFull.c_str());
+
+ break;
+ }
+
+ default:
+ return setError(E_FAIL,
+ tr("Controller '%s' port %u unit %u has device with unknown type (%d) - virtual machine '%s' ('%s')"),
+ data.strName.c_str(), dev.lPort, dev.lDevice, dev.deviceType,
+ mUserData->s.strName.c_str(), mData->m_strConfigFileFull.c_str());
+ }
+
+ if (FAILED(rc))
+ break;
+
+ /* Bandwidth groups are loaded at this point. */
+ ComObjPtr<BandwidthGroup> pBwGroup;
+
+ if (!dev.strBwGroup.isEmpty())
+ {
+ rc = mBandwidthControl->i_getBandwidthGroupByName(dev.strBwGroup, pBwGroup, false /* aSetError */);
+ if (FAILED(rc))
+ return setError(E_FAIL,
+ tr("Device '%s' with unknown bandwidth group '%s' is attached to the virtual machine '%s' ('%s')"),
+ medium->i_getLocationFull().c_str(),
+ dev.strBwGroup.c_str(),
+ mUserData->s.strName.c_str(),
+ mData->m_strConfigFileFull.c_str());
+ pBwGroup->i_reference();
+ }
+
+ const Utf8Str controllerName = aStorageController->i_getName();
+ ComObjPtr<MediumAttachment> pAttachment;
+ pAttachment.createObject();
+ rc = pAttachment->init(this,
+ medium,
+ controllerName,
+ dev.lPort,
+ dev.lDevice,
+ dev.deviceType,
+ false,
+ dev.fPassThrough,
+ dev.fTempEject,
+ dev.fNonRotational,
+ dev.fDiscard,
+ dev.fHotPluggable,
+ pBwGroup.isNull() ? Utf8Str::Empty : pBwGroup->i_getName());
+ if (FAILED(rc)) break;
+
+ /* associate the medium with this machine and snapshot */
+ if (!medium.isNull())
+ {
+ AutoCaller medCaller(medium);
+ if (FAILED(medCaller.rc())) return medCaller.rc();
+ AutoWriteLock mlock(medium COMMA_LOCKVAL_SRC_POS);
+
+ if (i_isSnapshotMachine())
+ rc = medium->i_addBackReference(mData->mUuid, *puuidSnapshot);
+ else
+ rc = medium->i_addBackReference(mData->mUuid);
+ /* If the medium->addBackReference fails it sets an appropriate
+ * error message, so no need to do any guesswork here. */
+
+ if (puuidRegistry)
+ // caller wants registry ID to be set on all attached media (OVF import case)
+ medium->i_addRegistry(*puuidRegistry);
+ }
+
+ if (FAILED(rc))
+ break;
+
+ /* back up mMediumAttachments to let registeredInit() properly rollback
+ * on failure (= limited accessibility) */
+ i_setModified(IsModified_Storage);
+ mMediumAttachments.backup();
+ mMediumAttachments->push_back(pAttachment);
+ }
+
+ return rc;
+}
+
+/**
+ * Returns the snapshot with the given UUID or fails of no such snapshot exists.
+ *
+ * @param aId snapshot UUID to find (empty UUID refers the first snapshot)
+ * @param aSnapshot where to return the found snapshot
+ * @param aSetError true to set extended error info on failure
+ */
+HRESULT Machine::i_findSnapshotById(const Guid &aId,
+ ComObjPtr<Snapshot> &aSnapshot,
+ bool aSetError /* = false */)
+{
+ AutoReadLock chlock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mData->mFirstSnapshot)
+ {
+ if (aSetError)
+ return setError(E_FAIL, tr("This machine does not have any snapshots"));
+ return E_FAIL;
+ }
+
+ if (aId.isZero())
+ aSnapshot = mData->mFirstSnapshot;
+ else
+ aSnapshot = mData->mFirstSnapshot->i_findChildOrSelf(aId.ref());
+
+ if (!aSnapshot)
+ {
+ if (aSetError)
+ return setError(E_FAIL,
+ tr("Could not find a snapshot with UUID {%s}"),
+ aId.toString().c_str());
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+/**
+ * Returns the snapshot with the given name or fails of no such snapshot.
+ *
+ * @param strName snapshot name to find
+ * @param aSnapshot where to return the found snapshot
+ * @param aSetError true to set extended error info on failure
+ */
+HRESULT Machine::i_findSnapshotByName(const Utf8Str &strName,
+ ComObjPtr<Snapshot> &aSnapshot,
+ bool aSetError /* = false */)
+{
+ AssertReturn(!strName.isEmpty(), E_INVALIDARG);
+
+ AutoReadLock chlock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mData->mFirstSnapshot)
+ {
+ if (aSetError)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("This machine does not have any snapshots"));
+ return VBOX_E_OBJECT_NOT_FOUND;
+ }
+
+ aSnapshot = mData->mFirstSnapshot->i_findChildOrSelf(strName);
+
+ if (!aSnapshot)
+ {
+ if (aSetError)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Could not find a snapshot named '%s'"), strName.c_str());
+ return VBOX_E_OBJECT_NOT_FOUND;
+ }
+
+ return S_OK;
+}
+
+/**
+ * Returns a storage controller object with the given name.
+ *
+ * @param aName storage controller name to find
+ * @param aStorageController where to return the found storage controller
+ * @param aSetError true to set extended error info on failure
+ */
+HRESULT Machine::i_getStorageControllerByName(const Utf8Str &aName,
+ ComObjPtr<StorageController> &aStorageController,
+ bool aSetError /* = false */)
+{
+ AssertReturn(!aName.isEmpty(), E_INVALIDARG);
+
+ for (StorageControllerList::const_iterator
+ it = mStorageControllers->begin();
+ it != mStorageControllers->end();
+ ++it)
+ {
+ if ((*it)->i_getName() == aName)
+ {
+ aStorageController = (*it);
+ return S_OK;
+ }
+ }
+
+ if (aSetError)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Could not find a storage controller named '%s'"),
+ aName.c_str());
+ return VBOX_E_OBJECT_NOT_FOUND;
+}
+
+/**
+ * Returns a USB controller object with the given name.
+ *
+ * @param aName USB controller name to find
+ * @param aUSBController where to return the found USB controller
+ * @param aSetError true to set extended error info on failure
+ */
+HRESULT Machine::i_getUSBControllerByName(const Utf8Str &aName,
+ ComObjPtr<USBController> &aUSBController,
+ bool aSetError /* = false */)
+{
+ AssertReturn(!aName.isEmpty(), E_INVALIDARG);
+
+ for (USBControllerList::const_iterator
+ it = mUSBControllers->begin();
+ it != mUSBControllers->end();
+ ++it)
+ {
+ if ((*it)->i_getName() == aName)
+ {
+ aUSBController = (*it);
+ return S_OK;
+ }
+ }
+
+ if (aSetError)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Could not find a storage controller named '%s'"),
+ aName.c_str());
+ return VBOX_E_OBJECT_NOT_FOUND;
+}
+
+/**
+ * Returns the number of USB controller instance of the given type.
+ *
+ * @param enmType USB controller type.
+ */
+ULONG Machine::i_getUSBControllerCountByType(USBControllerType_T enmType)
+{
+ ULONG cCtrls = 0;
+
+ for (USBControllerList::const_iterator
+ it = mUSBControllers->begin();
+ it != mUSBControllers->end();
+ ++it)
+ {
+ if ((*it)->i_getControllerType() == enmType)
+ cCtrls++;
+ }
+
+ return cCtrls;
+}
+
+HRESULT Machine::i_getMediumAttachmentsOfController(const Utf8Str &aName,
+ MediumAttachmentList &atts)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ const ComObjPtr<MediumAttachment> &pAtt = *it;
+ // should never happen, but deal with NULL pointers in the list.
+ AssertContinue(!pAtt.isNull());
+
+ // getControllerName() needs caller+read lock
+ AutoCaller autoAttCaller(pAtt);
+ if (FAILED(autoAttCaller.rc()))
+ {
+ atts.clear();
+ return autoAttCaller.rc();
+ }
+ AutoReadLock attLock(pAtt COMMA_LOCKVAL_SRC_POS);
+
+ if (pAtt->i_getControllerName() == aName)
+ atts.push_back(pAtt);
+ }
+
+ return S_OK;
+}
+
+
+/**
+ * Helper for #i_saveSettings. Cares about renaming the settings directory and
+ * file if the machine name was changed and about creating a new settings file
+ * if this is a new machine.
+ *
+ * @note Must be never called directly but only from #saveSettings().
+ */
+HRESULT Machine::i_prepareSaveSettings(bool *pfNeedsGlobalSaveSettings,
+ bool *pfSettingsFileIsNew)
+{
+ AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
+
+ HRESULT rc = S_OK;
+
+ bool fSettingsFileIsNew = !mData->pMachineConfigFile->fileExists();
+ /// @todo need to handle primary group change, too
+
+ /* attempt to rename the settings file if machine name is changed */
+ if ( mUserData->s.fNameSync
+ && mUserData.isBackedUp()
+ && ( mUserData.backedUpData()->s.strName != mUserData->s.strName
+ || mUserData.backedUpData()->s.llGroups.front() != mUserData->s.llGroups.front())
+ )
+ {
+ bool dirRenamed = false;
+ bool fileRenamed = false;
+
+ Utf8Str configFile, newConfigFile;
+ Utf8Str configFilePrev, newConfigFilePrev;
+ Utf8Str NVRAMFile, newNVRAMFile;
+ Utf8Str configDir, newConfigDir;
+
+ do
+ {
+ int vrc = VINF_SUCCESS;
+
+ Utf8Str name = mUserData.backedUpData()->s.strName;
+ Utf8Str newName = mUserData->s.strName;
+ Utf8Str group = mUserData.backedUpData()->s.llGroups.front();
+ if (group == "/")
+ group.setNull();
+ Utf8Str newGroup = mUserData->s.llGroups.front();
+ if (newGroup == "/")
+ newGroup.setNull();
+
+ configFile = mData->m_strConfigFileFull;
+
+ /* first, rename the directory if it matches the group and machine name */
+ Utf8StrFmt groupPlusName("%s%c%s", group.c_str(), RTPATH_DELIMITER, name.c_str());
+ /** @todo hack, make somehow use of ComposeMachineFilename */
+ if (mUserData->s.fDirectoryIncludesUUID)
+ groupPlusName.appendPrintf(" (%RTuuid)", mData->mUuid.raw());
+ Utf8StrFmt newGroupPlusName("%s%c%s", newGroup.c_str(), RTPATH_DELIMITER, newName.c_str());
+ /** @todo hack, make somehow use of ComposeMachineFilename */
+ if (mUserData->s.fDirectoryIncludesUUID)
+ newGroupPlusName.appendPrintf(" (%RTuuid)", mData->mUuid.raw());
+ configDir = configFile;
+ configDir.stripFilename();
+ newConfigDir = configDir;
+ if ( configDir.length() >= groupPlusName.length()
+ && !RTPathCompare(configDir.substr(configDir.length() - groupPlusName.length(), groupPlusName.length()).c_str(),
+ groupPlusName.c_str()))
+ {
+ newConfigDir = newConfigDir.substr(0, configDir.length() - groupPlusName.length());
+ Utf8Str newConfigBaseDir(newConfigDir);
+ newConfigDir.append(newGroupPlusName);
+ /* consistency: use \ if appropriate on the platform */
+ RTPathChangeToDosSlashes(newConfigDir.mutableRaw(), false);
+ /* new dir and old dir cannot be equal here because of 'if'
+ * above and because name != newName */
+ Assert(configDir != newConfigDir);
+ if (!fSettingsFileIsNew)
+ {
+ /* perform real rename only if the machine is not new */
+ vrc = RTPathRename(configDir.c_str(), newConfigDir.c_str(), 0);
+ if ( vrc == VERR_FILE_NOT_FOUND
+ || vrc == VERR_PATH_NOT_FOUND)
+ {
+ /* create the parent directory, then retry renaming */
+ Utf8Str parent(newConfigDir);
+ parent.stripFilename();
+ (void)RTDirCreateFullPath(parent.c_str(), 0700);
+ vrc = RTPathRename(configDir.c_str(), newConfigDir.c_str(), 0);
+ }
+ if (RT_FAILURE(vrc))
+ {
+ rc = setErrorBoth(E_FAIL, vrc,
+ tr("Could not rename the directory '%s' to '%s' to save the settings file (%Rrc)"),
+ configDir.c_str(),
+ newConfigDir.c_str(),
+ vrc);
+ break;
+ }
+ /* delete subdirectories which are no longer needed */
+ Utf8Str dir(configDir);
+ dir.stripFilename();
+ while (dir != newConfigBaseDir && dir != ".")
+ {
+ vrc = RTDirRemove(dir.c_str());
+ if (RT_FAILURE(vrc))
+ break;
+ dir.stripFilename();
+ }
+ dirRenamed = true;
+ }
+ }
+
+ newConfigFile.printf("%s%c%s.vbox", newConfigDir.c_str(), RTPATH_DELIMITER, newName.c_str());
+
+ /* then try to rename the settings file itself */
+ if (newConfigFile != configFile)
+ {
+ /* get the path to old settings file in renamed directory */
+ Assert(mData->m_strConfigFileFull == configFile);
+ configFile.printf("%s%c%s",
+ newConfigDir.c_str(),
+ RTPATH_DELIMITER,
+ RTPathFilename(mData->m_strConfigFileFull.c_str()));
+ if (!fSettingsFileIsNew)
+ {
+ /* perform real rename only if the machine is not new */
+ vrc = RTFileRename(configFile.c_str(), newConfigFile.c_str(), 0);
+ if (RT_FAILURE(vrc))
+ {
+ rc = setErrorBoth(E_FAIL, vrc,
+ tr("Could not rename the settings file '%s' to '%s' (%Rrc)"),
+ configFile.c_str(),
+ newConfigFile.c_str(),
+ vrc);
+ break;
+ }
+ fileRenamed = true;
+ configFilePrev = configFile;
+ configFilePrev += "-prev";
+ newConfigFilePrev = newConfigFile;
+ newConfigFilePrev += "-prev";
+ RTFileRename(configFilePrev.c_str(), newConfigFilePrev.c_str(), 0);
+ NVRAMFile = mNvramStore->i_getNonVolatileStorageFile();
+ if (NVRAMFile.isNotEmpty())
+ {
+ // in the NVRAM file path, replace the old directory with the new directory
+ if (RTPathStartsWith(NVRAMFile.c_str(), configDir.c_str()))
+ {
+ Utf8Str strNVRAMFile = NVRAMFile.c_str() + configDir.length();
+ NVRAMFile = newConfigDir + strNVRAMFile;
+ }
+ newNVRAMFile = newConfigFile;
+ newNVRAMFile.stripSuffix();
+ newNVRAMFile += ".nvram";
+ RTFileRename(NVRAMFile.c_str(), newNVRAMFile.c_str(), 0);
+ }
+ }
+ }
+
+ // update m_strConfigFileFull amd mConfigFile
+ mData->m_strConfigFileFull = newConfigFile;
+ // compute the relative path too
+ mParent->i_copyPathRelativeToConfig(newConfigFile, mData->m_strConfigFile);
+
+ // store the old and new so that VirtualBox::i_saveSettings() can update
+ // the media registry
+ if ( mData->mRegistered
+ && (configDir != newConfigDir || configFile != newConfigFile))
+ {
+ mParent->i_rememberMachineNameChangeForMedia(configDir, newConfigDir);
+
+ if (pfNeedsGlobalSaveSettings)
+ *pfNeedsGlobalSaveSettings = true;
+ }
+
+ // in the saved state file path, replace the old directory with the new directory
+ if (RTPathStartsWith(mSSData->strStateFilePath.c_str(), configDir.c_str()))
+ {
+ Utf8Str strStateFileName = mSSData->strStateFilePath.c_str() + configDir.length();
+ mSSData->strStateFilePath = newConfigDir + strStateFileName;
+ }
+ if (newNVRAMFile.isNotEmpty())
+ mNvramStore->i_updateNonVolatileStorageFile(newNVRAMFile);
+
+ // and do the same thing for the saved state file paths of all the online snapshots and NVRAM files of all snapshots
+ if (mData->mFirstSnapshot)
+ {
+ mData->mFirstSnapshot->i_updateSavedStatePaths(configDir.c_str(),
+ newConfigDir.c_str());
+ mData->mFirstSnapshot->i_updateNVRAMPaths(configDir.c_str(),
+ newConfigDir.c_str());
+ }
+ }
+ while (0);
+
+ if (FAILED(rc))
+ {
+ /* silently try to rename everything back */
+ if (fileRenamed)
+ {
+ RTFileRename(newConfigFilePrev.c_str(), configFilePrev.c_str(), 0);
+ RTFileRename(newConfigFile.c_str(), configFile.c_str(), 0);
+ if (NVRAMFile.isNotEmpty() && newNVRAMFile.isNotEmpty())
+ RTFileRename(newNVRAMFile.c_str(), NVRAMFile.c_str(), 0);
+ }
+ if (dirRenamed)
+ RTPathRename(newConfigDir.c_str(), configDir.c_str(), 0);
+ }
+
+ if (FAILED(rc)) return rc;
+ }
+
+ if (fSettingsFileIsNew)
+ {
+ /* create a virgin config file */
+ int vrc = VINF_SUCCESS;
+
+ /* ensure the settings directory exists */
+ Utf8Str path(mData->m_strConfigFileFull);
+ path.stripFilename();
+ if (!RTDirExists(path.c_str()))
+ {
+ vrc = RTDirCreateFullPath(path.c_str(), 0700);
+ if (RT_FAILURE(vrc))
+ {
+ return setErrorBoth(E_FAIL, vrc,
+ tr("Could not create a directory '%s' to save the settings file (%Rrc)"),
+ path.c_str(),
+ vrc);
+ }
+ }
+
+ /* Note: open flags must correlate with RTFileOpen() in lockConfig() */
+ path = mData->m_strConfigFileFull;
+ RTFILE f = NIL_RTFILE;
+ vrc = RTFileOpen(&f, path.c_str(),
+ RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc,
+ tr("Could not create the settings file '%s' (%Rrc)"),
+ path.c_str(),
+ vrc);
+ RTFileClose(f);
+ }
+ if (pfSettingsFileIsNew)
+ *pfSettingsFileIsNew = fSettingsFileIsNew;
+
+ return rc;
+}
+
+/**
+ * Saves and commits machine data, user data and hardware data.
+ *
+ * Note that on failure, the data remains uncommitted.
+ *
+ * @a aFlags may combine the following flags:
+ *
+ * - SaveS_ResetCurStateModified: Resets mData->mCurrentStateModified to FALSE.
+ * Used when saving settings after an operation that makes them 100%
+ * correspond to the settings from the current snapshot.
+ * - SaveS_Force: settings will be saved without doing a deep compare of the
+ * settings structures. This is used when this is called because snapshots
+ * have changed to avoid the overhead of the deep compare.
+ *
+ * @note Must be called from under this object's write lock. Locks children for
+ * writing.
+ *
+ * @param pfNeedsGlobalSaveSettings Optional pointer to a bool that must have been
+ * initialized to false and that will be set to true by this function if
+ * the caller must invoke VirtualBox::i_saveSettings() because the global
+ * settings have changed. This will happen if a machine rename has been
+ * saved and the global machine and media registries will therefore need
+ * updating.
+ * @param alock Reference to the lock for this machine object.
+ * @param aFlags Flags.
+ */
+HRESULT Machine::i_saveSettings(bool *pfNeedsGlobalSaveSettings,
+ AutoWriteLock &alock,
+ int aFlags /*= 0*/)
+{
+ LogFlowThisFuncEnter();
+
+ AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
+
+ /* make sure child objects are unable to modify the settings while we are
+ * saving them */
+ i_ensureNoStateDependencies(alock);
+
+ AssertReturn(!i_isSnapshotMachine(),
+ E_FAIL);
+
+ if (!mData->mAccessible)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("The machine is not accessible, so cannot save settings"));
+
+ HRESULT rc = S_OK;
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ const char *pszPassword = NULL;
+ SecretKey *pKey = NULL;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (mData->mstrKeyId.isNotEmpty())
+ {
+ /* VM is going to be encrypted. */
+ alock.release(); /** @todo Revise the locking. */
+ rc = mParent->i_retainCryptoIf(&pCryptoIf);
+ alock.acquire();
+ if (FAILED(rc)) return rc; /* Error is set. */
+
+ int vrc = mData->mpKeyStore->retainSecretKey(mData->mstrKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ pszPassword = (const char *)pKey->getKeyBuffer();
+ else
+ {
+ mParent->i_releaseCryptoIf(pCryptoIf);
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Failed to retain VM encryption password using ID '%s' with %Rrc"),
+ mData->mstrKeyId.c_str(), vrc);
+ }
+ }
+#else
+ RT_NOREF(pKey);
+#endif
+
+ bool fNeedsWrite = false;
+ bool fSettingsFileIsNew = false;
+
+ /* First, prepare to save settings. It will care about renaming the
+ * settings directory and file if the machine name was changed and about
+ * creating a new settings file if this is a new machine. */
+ rc = i_prepareSaveSettings(pfNeedsGlobalSaveSettings,
+ &fSettingsFileIsNew);
+ if (FAILED(rc))
+ {
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (pCryptoIf)
+ {
+ alock.release(); /** @todo Revise the locking. */
+ mParent->i_releaseCryptoIf(pCryptoIf);
+ alock.acquire();
+ }
+ if (pKey)
+ mData->mpKeyStore->releaseSecretKey(mData->mstrKeyId);
+#endif
+ return rc;
+ }
+
+ // keep a pointer to the current settings structures
+ settings::MachineConfigFile *pOldConfig = mData->pMachineConfigFile;
+ settings::MachineConfigFile *pNewConfig = NULL;
+
+ try
+ {
+ // make a fresh one to have everyone write stuff into
+ pNewConfig = new settings::MachineConfigFile(NULL);
+ pNewConfig->copyBaseFrom(*mData->pMachineConfigFile);
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ pNewConfig->strKeyId = mData->mstrKeyId;
+ pNewConfig->strKeyStore = mData->mstrKeyStore;
+#endif
+
+ // now go and copy all the settings data from COM to the settings structures
+ // (this calls i_saveSettings() on all the COM objects in the machine)
+ i_copyMachineDataToSettings(*pNewConfig);
+
+ if (aFlags & SaveS_ResetCurStateModified)
+ {
+ // this gets set by takeSnapshot() (if offline snapshot) and restoreSnapshot()
+ mData->mCurrentStateModified = FALSE;
+ fNeedsWrite = true; // always, no need to compare
+ }
+ else if (aFlags & SaveS_Force)
+ {
+ fNeedsWrite = true; // always, no need to compare
+ }
+ else
+ {
+ if (!mData->mCurrentStateModified)
+ {
+ // do a deep compare of the settings that we just saved with the settings
+ // previously stored in the config file; this invokes MachineConfigFile::operator==
+ // which does a deep compare of all the settings, which is expensive but less expensive
+ // than writing out XML in vain
+ bool fAnySettingsChanged = !(*pNewConfig == *pOldConfig);
+
+ // could still be modified if any settings changed
+ mData->mCurrentStateModified = fAnySettingsChanged;
+
+ fNeedsWrite = fAnySettingsChanged;
+ }
+ else
+ fNeedsWrite = true;
+ }
+
+ pNewConfig->fCurrentStateModified = !!mData->mCurrentStateModified;
+
+ if (fNeedsWrite)
+ {
+ // now spit it all out!
+ pNewConfig->write(mData->m_strConfigFileFull, pCryptoIf, pszPassword);
+ if (aFlags & SaveS_RemoveBackup)
+ i_deleteFile(mData->m_strConfigFileFull + "-prev", true /* fIgnoreFailures */);
+ }
+
+ mData->pMachineConfigFile = pNewConfig;
+ delete pOldConfig;
+ i_commit();
+
+ // after saving settings, we are no longer different from the XML on disk
+ mData->flModifications = 0;
+ }
+ catch (HRESULT err)
+ {
+ // we assume that error info is set by the thrower
+ rc = err;
+
+ // delete any newly created settings file
+ if (fSettingsFileIsNew)
+ i_deleteFile(mData->m_strConfigFileFull, true /* fIgnoreFailures */);
+
+ // restore old config
+ delete pNewConfig;
+ mData->pMachineConfigFile = pOldConfig;
+ }
+ catch (...)
+ {
+ rc = VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS);
+ }
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (pCryptoIf)
+ {
+ alock.release(); /** @todo Revise the locking. */
+ mParent->i_releaseCryptoIf(pCryptoIf);
+ alock.acquire();
+ }
+ if (pKey)
+ mData->mpKeyStore->releaseSecretKey(mData->mstrKeyId);
+#endif
+
+ if (fNeedsWrite)
+ {
+ /* Fire the data change event, even on failure (since we've already
+ * committed all data). This is done only for SessionMachines because
+ * mutable Machine instances are always not registered (i.e. private
+ * to the client process that creates them) and thus don't need to
+ * inform callbacks. */
+ if (i_isSessionMachine())
+ mParent->i_onMachineDataChanged(mData->mUuid);
+ }
+
+ LogFlowThisFunc(("rc=%08X\n", rc));
+ LogFlowThisFuncLeave();
+ return rc;
+}
+
+/**
+ * Implementation for saving the machine settings into the given
+ * settings::MachineConfigFile instance. This copies machine extradata
+ * from the previous machine config file in the instance data, if any.
+ *
+ * This gets called from two locations:
+ *
+ * -- Machine::i_saveSettings(), during the regular XML writing;
+ *
+ * -- Appliance::buildXMLForOneVirtualSystem(), when a machine gets
+ * exported to OVF and we write the VirtualBox proprietary XML
+ * into a <vbox:Machine> tag.
+ *
+ * This routine fills all the fields in there, including snapshots, *except*
+ * for the following:
+ *
+ * -- fCurrentStateModified. There is some special logic associated with that.
+ *
+ * The caller can then call MachineConfigFile::write() or do something else
+ * with it.
+ *
+ * Caller must hold the machine lock!
+ *
+ * This throws XML errors and HRESULT, so the caller must have a catch block!
+ */
+void Machine::i_copyMachineDataToSettings(settings::MachineConfigFile &config)
+{
+ // deep copy extradata, being extra careful with self assignment (the STL
+ // map assignment on Mac OS X clang based Xcode isn't checking)
+ if (&config != mData->pMachineConfigFile)
+ config.mapExtraDataItems = mData->pMachineConfigFile->mapExtraDataItems;
+
+ config.uuid = mData->mUuid;
+
+ // copy name, description, OS type, teleport, UTC etc.
+ config.machineUserData = mUserData->s;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ config.strStateKeyId = mSSData->strStateKeyId;
+ config.strStateKeyStore = mSSData->strStateKeyStore;
+ config.strLogKeyId = mData->mstrLogKeyId;
+ config.strLogKeyStore = mData->mstrLogKeyStore;
+#endif
+
+ if ( mData->mMachineState == MachineState_Saved
+ || mData->mMachineState == MachineState_AbortedSaved
+ || mData->mMachineState == MachineState_Restoring
+ // when doing certain snapshot operations we may or may not have
+ // a saved state in the current state, so keep everything as is
+ || ( ( mData->mMachineState == MachineState_Snapshotting
+ || mData->mMachineState == MachineState_DeletingSnapshot
+ || mData->mMachineState == MachineState_RestoringSnapshot)
+ && (!mSSData->strStateFilePath.isEmpty())
+ )
+ )
+ {
+ Assert(!mSSData->strStateFilePath.isEmpty());
+ /* try to make the file name relative to the settings file dir */
+ i_copyPathRelativeToMachine(mSSData->strStateFilePath, config.strStateFile);
+ }
+ else
+ {
+ Assert(mSSData->strStateFilePath.isEmpty() || mData->mMachineState == MachineState_Saving);
+ config.strStateFile.setNull();
+ }
+
+ if (mData->mCurrentSnapshot)
+ config.uuidCurrentSnapshot = mData->mCurrentSnapshot->i_getId();
+ else
+ config.uuidCurrentSnapshot.clear();
+
+ config.timeLastStateChange = mData->mLastStateChange;
+ config.fAborted = (mData->mMachineState == MachineState_Aborted || mData->mMachineState == MachineState_AbortedSaved);
+ /// @todo Live Migration: config.fTeleported = (mData->mMachineState == MachineState_Teleported);
+
+ HRESULT hrc = i_saveHardware(config.hardwareMachine, &config.debugging, &config.autostart, config.recordingSettings);
+ if (FAILED(hrc)) throw hrc;
+
+ // save machine's media registry if this is VirtualBox 4.0 or later
+ if (config.canHaveOwnMediaRegistry())
+ {
+ // determine machine folder
+ Utf8Str strMachineFolder = i_getSettingsFileFull();
+ strMachineFolder.stripFilename();
+ mParent->i_saveMediaRegistry(config.mediaRegistry,
+ i_getId(), // only media with registry ID == machine UUID
+ strMachineFolder);
+ // this throws HRESULT
+ }
+
+ // save snapshots
+ hrc = i_saveAllSnapshots(config);
+ if (FAILED(hrc)) throw hrc;
+}
+
+/**
+ * Saves all snapshots of the machine into the given machine config file. Called
+ * from Machine::buildMachineXML() and SessionMachine::deleteSnapshotHandler().
+ * @param config
+ * @return
+ */
+HRESULT Machine::i_saveAllSnapshots(settings::MachineConfigFile &config)
+{
+ AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
+
+ HRESULT rc = S_OK;
+
+ try
+ {
+ config.llFirstSnapshot.clear();
+
+ if (mData->mFirstSnapshot)
+ {
+ // the settings use a list for "the first snapshot"
+ config.llFirstSnapshot.push_back(settings::Snapshot::Empty);
+
+ // get reference to the snapshot on the list and work on that
+ // element straight in the list to avoid excessive copying later
+ rc = mData->mFirstSnapshot->i_saveSnapshot(config.llFirstSnapshot.back());
+ if (FAILED(rc)) throw rc;
+ }
+
+// if (mType == IsSessionMachine)
+// mParent->onMachineDataChange(mData->mUuid); @todo is this necessary?
+
+ }
+ catch (HRESULT err)
+ {
+ /* we assume that error info is set by the thrower */
+ rc = err;
+ }
+ catch (...)
+ {
+ rc = VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS);
+ }
+
+ return rc;
+}
+
+/**
+ * Saves the VM hardware configuration. It is assumed that the
+ * given node is empty.
+ *
+ * @param data Reference to the settings object for the hardware config.
+ * @param pDbg Pointer to the settings object for the debugging config
+ * which happens to live in mHWData.
+ * @param pAutostart Pointer to the settings object for the autostart config
+ * which happens to live in mHWData.
+ * @param recording Reference to reecording settings.
+ */
+HRESULT Machine::i_saveHardware(settings::Hardware &data, settings::Debugging *pDbg,
+ settings::Autostart *pAutostart, settings::RecordingSettings &recording)
+{
+ HRESULT rc = S_OK;
+
+ try
+ {
+ /* The hardware version attribute (optional).
+ Automatically upgrade from 1 to current default hardware version
+ when there is no saved state. (ugly!) */
+ if ( mHWData->mHWVersion == "1"
+ && mSSData->strStateFilePath.isEmpty()
+ )
+ mHWData->mHWVersion.printf("%d", SchemaDefs::DefaultHardwareVersion);
+
+ data.strVersion = mHWData->mHWVersion;
+ data.uuid = mHWData->mHardwareUUID;
+
+ // CPU
+ data.fHardwareVirt = !!mHWData->mHWVirtExEnabled;
+ data.fNestedPaging = !!mHWData->mHWVirtExNestedPagingEnabled;
+ data.fLargePages = !!mHWData->mHWVirtExLargePagesEnabled;
+ data.fVPID = !!mHWData->mHWVirtExVPIDEnabled;
+ data.fUnrestrictedExecution = !!mHWData->mHWVirtExUXEnabled;
+ data.fHardwareVirtForce = !!mHWData->mHWVirtExForceEnabled;
+ data.fUseNativeApi = !!mHWData->mHWVirtExUseNativeApi;
+ data.fVirtVmsaveVmload = !!mHWData->mHWVirtExVirtVmsaveVmload;
+ data.fPAE = !!mHWData->mPAEEnabled;
+ data.enmLongMode = mHWData->mLongMode;
+ data.fTripleFaultReset = !!mHWData->mTripleFaultReset;
+ data.fAPIC = !!mHWData->mAPIC;
+ data.fX2APIC = !!mHWData->mX2APIC;
+ data.fIBPBOnVMExit = !!mHWData->mIBPBOnVMExit;
+ data.fIBPBOnVMEntry = !!mHWData->mIBPBOnVMEntry;
+ data.fSpecCtrl = !!mHWData->mSpecCtrl;
+ data.fSpecCtrlByHost = !!mHWData->mSpecCtrlByHost;
+ data.fL1DFlushOnSched = !!mHWData->mL1DFlushOnSched;
+ data.fL1DFlushOnVMEntry = !!mHWData->mL1DFlushOnVMEntry;
+ data.fMDSClearOnSched = !!mHWData->mMDSClearOnSched;
+ data.fMDSClearOnVMEntry = !!mHWData->mMDSClearOnVMEntry;
+ data.fNestedHWVirt = !!mHWData->mNestedHWVirt;
+ data.cCPUs = mHWData->mCPUCount;
+ data.fCpuHotPlug = !!mHWData->mCPUHotPlugEnabled;
+ data.ulCpuExecutionCap = mHWData->mCpuExecutionCap;
+ data.uCpuIdPortabilityLevel = mHWData->mCpuIdPortabilityLevel;
+ data.strCpuProfile = mHWData->mCpuProfile;
+
+ data.llCpus.clear();
+ if (data.fCpuHotPlug)
+ {
+ for (unsigned idx = 0; idx < data.cCPUs; ++idx)
+ {
+ if (mHWData->mCPUAttached[idx])
+ {
+ settings::Cpu cpu;
+ cpu.ulId = idx;
+ data.llCpus.push_back(cpu);
+ }
+ }
+ }
+
+ /* Standard and Extended CPUID leafs. */
+ data.llCpuIdLeafs.clear();
+ data.llCpuIdLeafs = mHWData->mCpuIdLeafList;
+
+ // memory
+ data.ulMemorySizeMB = mHWData->mMemorySize;
+ data.fPageFusionEnabled = !!mHWData->mPageFusionEnabled;
+
+ // firmware
+ data.firmwareType = mHWData->mFirmwareType;
+
+ // HID
+ data.pointingHIDType = mHWData->mPointingHIDType;
+ data.keyboardHIDType = mHWData->mKeyboardHIDType;
+
+ // chipset
+ data.chipsetType = mHWData->mChipsetType;
+
+ // iommu
+ data.iommuType = mHWData->mIommuType;
+
+ // paravirt
+ data.paravirtProvider = mHWData->mParavirtProvider;
+ data.strParavirtDebug = mHWData->mParavirtDebug;
+
+ // emulated USB card reader
+ data.fEmulatedUSBCardReader = !!mHWData->mEmulatedUSBCardReaderEnabled;
+
+ // HPET
+ data.fHPETEnabled = !!mHWData->mHPETEnabled;
+
+ // boot order
+ data.mapBootOrder.clear();
+ for (unsigned i = 0; i < RT_ELEMENTS(mHWData->mBootOrder); ++i)
+ data.mapBootOrder[i] = mHWData->mBootOrder[i];
+
+ /* VRDEServer settings (optional) */
+ rc = mVRDEServer->i_saveSettings(data.vrdeSettings);
+ if (FAILED(rc)) throw rc;
+
+ /* BIOS settings (required) */
+ rc = mBIOSSettings->i_saveSettings(data.biosSettings);
+ if (FAILED(rc)) throw rc;
+
+ /* Recording settings. */
+ rc = mRecordingSettings->i_saveSettings(recording);
+ if (FAILED(rc)) throw rc;
+
+ /* Trusted Platform Module settings (required) */
+ rc = mTrustedPlatformModule->i_saveSettings(data.tpmSettings);
+ if (FAILED(rc)) throw rc;
+
+ /* NVRAM settings (required) */
+ rc = mNvramStore->i_saveSettings(data.nvramSettings);
+ if (FAILED(rc)) throw rc;
+
+ /* GraphicsAdapter settings (required) */
+ rc = mGraphicsAdapter->i_saveSettings(data.graphicsAdapter);
+ if (FAILED(rc)) throw rc;
+
+ /* USB Controller (required) */
+ data.usbSettings.llUSBControllers.clear();
+ for (USBControllerList::const_iterator
+ it = mUSBControllers->begin();
+ it != mUSBControllers->end();
+ ++it)
+ {
+ ComObjPtr<USBController> ctrl = *it;
+ settings::USBController settingsCtrl;
+
+ settingsCtrl.strName = ctrl->i_getName();
+ settingsCtrl.enmType = ctrl->i_getControllerType();
+
+ data.usbSettings.llUSBControllers.push_back(settingsCtrl);
+ }
+
+ /* USB device filters (required) */
+ rc = mUSBDeviceFilters->i_saveSettings(data.usbSettings);
+ if (FAILED(rc)) throw rc;
+
+ /* Network adapters (required) */
+ size_t uMaxNICs = RT_MIN(Global::getMaxNetworkAdapters(mHWData->mChipsetType), mNetworkAdapters.size());
+ data.llNetworkAdapters.clear();
+ /* Write out only the nominal number of network adapters for this
+ * chipset type. Since Machine::commit() hasn't been called there
+ * may be extra NIC settings in the vector. */
+ for (size_t slot = 0; slot < uMaxNICs; ++slot)
+ {
+ settings::NetworkAdapter nic;
+ nic.ulSlot = (uint32_t)slot;
+ /* paranoia check... must not be NULL, but must not crash either. */
+ if (mNetworkAdapters[slot])
+ {
+ if (mNetworkAdapters[slot]->i_hasDefaults())
+ continue;
+
+ rc = mNetworkAdapters[slot]->i_saveSettings(nic);
+ if (FAILED(rc)) throw rc;
+
+ data.llNetworkAdapters.push_back(nic);
+ }
+ }
+
+ /* Serial ports */
+ data.llSerialPorts.clear();
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); ++slot)
+ {
+ if (mSerialPorts[slot]->i_hasDefaults())
+ continue;
+
+ settings::SerialPort s;
+ s.ulSlot = slot;
+ rc = mSerialPorts[slot]->i_saveSettings(s);
+ if (FAILED(rc)) return rc;
+
+ data.llSerialPorts.push_back(s);
+ }
+
+ /* Parallel ports */
+ data.llParallelPorts.clear();
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); ++slot)
+ {
+ if (mParallelPorts[slot]->i_hasDefaults())
+ continue;
+
+ settings::ParallelPort p;
+ p.ulSlot = slot;
+ rc = mParallelPorts[slot]->i_saveSettings(p);
+ if (FAILED(rc)) return rc;
+
+ data.llParallelPorts.push_back(p);
+ }
+
+ /* Audio settings */
+ rc = mAudioSettings->i_saveSettings(data.audioAdapter);
+ if (FAILED(rc)) return rc;
+
+ rc = i_saveStorageControllers(data.storage);
+ if (FAILED(rc)) return rc;
+
+ /* Shared folders */
+ data.llSharedFolders.clear();
+ for (HWData::SharedFolderList::const_iterator
+ it = mHWData->mSharedFolders.begin();
+ it != mHWData->mSharedFolders.end();
+ ++it)
+ {
+ SharedFolder *pSF = *it;
+ AutoCaller sfCaller(pSF);
+ AutoReadLock sfLock(pSF COMMA_LOCKVAL_SRC_POS);
+ settings::SharedFolder sf;
+ sf.strName = pSF->i_getName();
+ sf.strHostPath = pSF->i_getHostPath();
+ sf.fWritable = !!pSF->i_isWritable();
+ sf.fAutoMount = !!pSF->i_isAutoMounted();
+ sf.strAutoMountPoint = pSF->i_getAutoMountPoint();
+
+ data.llSharedFolders.push_back(sf);
+ }
+
+ // clipboard
+ data.clipboardMode = mHWData->mClipboardMode;
+ data.fClipboardFileTransfersEnabled = RT_BOOL(mHWData->mClipboardFileTransfersEnabled);
+
+ // drag'n'drop
+ data.dndMode = mHWData->mDnDMode;
+
+ /* Guest */
+ data.ulMemoryBalloonSize = mHWData->mMemoryBalloonSize;
+
+ // IO settings
+ data.ioSettings.fIOCacheEnabled = !!mHWData->mIOCacheEnabled;
+ data.ioSettings.ulIOCacheSize = mHWData->mIOCacheSize;
+
+ /* BandwidthControl (required) */
+ rc = mBandwidthControl->i_saveSettings(data.ioSettings);
+ if (FAILED(rc)) throw rc;
+
+ /* Host PCI devices */
+ data.pciAttachments.clear();
+ for (HWData::PCIDeviceAssignmentList::const_iterator
+ it = mHWData->mPCIDeviceAssignments.begin();
+ it != mHWData->mPCIDeviceAssignments.end();
+ ++it)
+ {
+ ComObjPtr<PCIDeviceAttachment> pda = *it;
+ settings::HostPCIDeviceAttachment hpda;
+
+ rc = pda->i_saveSettings(hpda);
+ if (FAILED(rc)) throw rc;
+
+ data.pciAttachments.push_back(hpda);
+ }
+
+ // guest properties
+ data.llGuestProperties.clear();
+#ifdef VBOX_WITH_GUEST_PROPS
+ for (HWData::GuestPropertyMap::const_iterator
+ it = mHWData->mGuestProperties.begin();
+ it != mHWData->mGuestProperties.end();
+ ++it)
+ {
+ HWData::GuestProperty property = it->second;
+
+ /* Remove transient guest properties at shutdown unless we
+ * are saving state. Note that restoring snapshot intentionally
+ * keeps them, they will be removed if appropriate once the final
+ * machine state is set (as crashes etc. need to work). */
+ if ( ( mData->mMachineState == MachineState_PoweredOff
+ || mData->mMachineState == MachineState_Aborted
+ || mData->mMachineState == MachineState_Teleported)
+ && (property.mFlags & (GUEST_PROP_F_TRANSIENT | GUEST_PROP_F_TRANSRESET)))
+ continue;
+ settings::GuestProperty prop; /// @todo r=bird: some excellent variable name choices here: 'prop' and 'property'; No 'const' clue either.
+ prop.strName = it->first;
+ prop.strValue = property.strValue;
+ prop.timestamp = (uint64_t)property.mTimestamp;
+ char szFlags[GUEST_PROP_MAX_FLAGS_LEN + 1];
+ GuestPropWriteFlags(property.mFlags, szFlags);
+ prop.strFlags = szFlags;
+
+ data.llGuestProperties.push_back(prop);
+ }
+
+ /* I presume this doesn't require a backup(). */
+ mData->mGuestPropertiesModified = FALSE;
+#endif /* VBOX_WITH_GUEST_PROPS defined */
+
+ rc = mGuestDebugControl->i_saveSettings(mHWData->mDebugging);
+ if (FAILED(rc)) throw rc;
+
+ *pDbg = mHWData->mDebugging; /// @todo r=aeichner: Move this to guest debug control. */
+ *pAutostart = mHWData->mAutostart;
+
+ data.strDefaultFrontend = mHWData->mDefaultFrontend;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ AssertComRC(rc);
+ return rc;
+}
+
+/**
+ * Saves the storage controller configuration.
+ *
+ * @param data storage settings.
+ */
+HRESULT Machine::i_saveStorageControllers(settings::Storage &data)
+{
+ data.llStorageControllers.clear();
+
+ for (StorageControllerList::const_iterator
+ it = mStorageControllers->begin();
+ it != mStorageControllers->end();
+ ++it)
+ {
+ HRESULT rc;
+ ComObjPtr<StorageController> pCtl = *it;
+
+ settings::StorageController ctl;
+ ctl.strName = pCtl->i_getName();
+ ctl.controllerType = pCtl->i_getControllerType();
+ ctl.storageBus = pCtl->i_getStorageBus();
+ ctl.ulInstance = pCtl->i_getInstance();
+ ctl.fBootable = pCtl->i_getBootable();
+
+ /* Save the port count. */
+ ULONG portCount;
+ rc = pCtl->COMGETTER(PortCount)(&portCount);
+ ComAssertComRCRet(rc, rc);
+ ctl.ulPortCount = portCount;
+
+ /* Save fUseHostIOCache */
+ BOOL fUseHostIOCache;
+ rc = pCtl->COMGETTER(UseHostIOCache)(&fUseHostIOCache);
+ ComAssertComRCRet(rc, rc);
+ ctl.fUseHostIOCache = !!fUseHostIOCache;
+
+ /* save the devices now. */
+ rc = i_saveStorageDevices(pCtl, ctl);
+ ComAssertComRCRet(rc, rc);
+
+ data.llStorageControllers.push_back(ctl);
+ }
+
+ return S_OK;
+}
+
+/**
+ * Saves the hard disk configuration.
+ */
+HRESULT Machine::i_saveStorageDevices(ComObjPtr<StorageController> aStorageController,
+ settings::StorageController &data)
+{
+ MediumAttachmentList atts;
+
+ HRESULT rc = i_getMediumAttachmentsOfController(aStorageController->i_getName(), atts);
+ if (FAILED(rc)) return rc;
+
+ data.llAttachedDevices.clear();
+ for (MediumAttachmentList::const_iterator
+ it = atts.begin();
+ it != atts.end();
+ ++it)
+ {
+ settings::AttachedDevice dev;
+ IMediumAttachment *iA = *it;
+ MediumAttachment *pAttach = static_cast<MediumAttachment *>(iA);
+ Medium *pMedium = pAttach->i_getMedium();
+
+ dev.deviceType = pAttach->i_getType();
+ dev.lPort = pAttach->i_getPort();
+ dev.lDevice = pAttach->i_getDevice();
+ dev.fPassThrough = pAttach->i_getPassthrough();
+ dev.fHotPluggable = pAttach->i_getHotPluggable();
+ if (pMedium)
+ {
+ if (pMedium->i_isHostDrive())
+ dev.strHostDriveSrc = pMedium->i_getLocationFull();
+ else
+ dev.uuid = pMedium->i_getId();
+ dev.fTempEject = pAttach->i_getTempEject();
+ dev.fNonRotational = pAttach->i_getNonRotational();
+ dev.fDiscard = pAttach->i_getDiscard();
+ }
+
+ dev.strBwGroup = pAttach->i_getBandwidthGroup();
+
+ data.llAttachedDevices.push_back(dev);
+ }
+
+ return S_OK;
+}
+
+/**
+ * Saves machine state settings as defined by aFlags
+ * (SaveSTS_* values).
+ *
+ * @param aFlags Combination of SaveSTS_* flags.
+ *
+ * @note Locks objects for writing.
+ */
+HRESULT Machine::i_saveStateSettings(int aFlags)
+{
+ if (aFlags == 0)
+ return S_OK;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ /* This object's write lock is also necessary to serialize file access
+ * (prevent concurrent reads and writes) */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ Assert(mData->pMachineConfigFile);
+
+ try
+ {
+ if (aFlags & SaveSTS_CurStateModified)
+ mData->pMachineConfigFile->fCurrentStateModified = true;
+
+ if (aFlags & SaveSTS_StateFilePath)
+ {
+ if (!mSSData->strStateFilePath.isEmpty())
+ /* try to make the file name relative to the settings file dir */
+ i_copyPathRelativeToMachine(mSSData->strStateFilePath, mData->pMachineConfigFile->strStateFile);
+ else
+ mData->pMachineConfigFile->strStateFile.setNull();
+ }
+
+ if (aFlags & SaveSTS_StateTimeStamp)
+ {
+ Assert( mData->mMachineState != MachineState_Aborted
+ || mSSData->strStateFilePath.isEmpty());
+
+ mData->pMachineConfigFile->timeLastStateChange = mData->mLastStateChange;
+
+ mData->pMachineConfigFile->fAborted = (mData->mMachineState == MachineState_Aborted
+ || mData->mMachineState == MachineState_AbortedSaved);
+/// @todo live migration mData->pMachineConfigFile->fTeleported = (mData->mMachineState == MachineState_Teleported);
+ }
+
+ mData->pMachineConfigFile->write(mData->m_strConfigFileFull);
+ }
+ catch (...)
+ {
+ rc = VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS);
+ }
+
+ return rc;
+}
+
+/**
+ * Ensures that the given medium is added to a media registry. If this machine
+ * was created with 4.0 or later, then the machine registry is used. Otherwise
+ * the global VirtualBox media registry is used.
+ *
+ * Caller must NOT hold machine lock, media tree or any medium locks!
+ *
+ * @param pMedium
+ */
+void Machine::i_addMediumToRegistry(ComObjPtr<Medium> &pMedium)
+{
+ /* Paranoia checks: do not hold machine or media tree locks. */
+ AssertReturnVoid(!isWriteLockOnCurrentThread());
+ AssertReturnVoid(!mParent->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+
+ ComObjPtr<Medium> pBase;
+ {
+ AutoReadLock treeLock(&mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+ pBase = pMedium->i_getBase();
+ }
+
+ /* Paranoia checks: do not hold medium locks. */
+ AssertReturnVoid(!pMedium->isWriteLockOnCurrentThread());
+ AssertReturnVoid(!pBase->isWriteLockOnCurrentThread());
+
+ // decide which medium registry to use now that the medium is attached:
+ Guid uuid;
+ bool fCanHaveOwnMediaRegistry = mData->pMachineConfigFile->canHaveOwnMediaRegistry();
+ if (fCanHaveOwnMediaRegistry)
+ // machine XML is VirtualBox 4.0 or higher:
+ uuid = i_getId(); // machine UUID
+ else
+ uuid = mParent->i_getGlobalRegistryId(); // VirtualBox global registry UUID
+
+ if (fCanHaveOwnMediaRegistry && pMedium->i_removeRegistry(mParent->i_getGlobalRegistryId()))
+ mParent->i_markRegistryModified(mParent->i_getGlobalRegistryId());
+ if (pMedium->i_addRegistry(uuid))
+ mParent->i_markRegistryModified(uuid);
+
+ /* For more complex hard disk structures it can happen that the base
+ * medium isn't yet associated with any medium registry. Do that now. */
+ if (pMedium != pBase)
+ {
+ /* Tree lock needed by Medium::addRegistryAll. */
+ AutoReadLock treeLock(&mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+ if (fCanHaveOwnMediaRegistry && pBase->i_removeRegistryAll(mParent->i_getGlobalRegistryId()))
+ {
+ treeLock.release();
+ mParent->i_markRegistryModified(mParent->i_getGlobalRegistryId());
+ treeLock.acquire();
+ }
+ if (pBase->i_addRegistryAll(uuid))
+ {
+ treeLock.release();
+ mParent->i_markRegistryModified(uuid);
+ }
+ }
+}
+
+/**
+ * Physically deletes a file belonging to a machine.
+ *
+ * @returns HRESULT
+ * @retval VBOX_E_FILE_ERROR on failure.
+ * @param strFile File to delete.
+ * @param fIgnoreFailures Whether to ignore deletion failures. Defaults to \c false.
+ * VERR_FILE_NOT_FOUND and VERR_PATH_NOT_FOUND always will be ignored.
+ * @param strWhat File hint which will be used when setting an error. Optional.
+ * @param prc Where to return IPRT's error code on failure. Optional and can be NULL.
+ */
+HRESULT Machine::i_deleteFile(const Utf8Str &strFile, bool fIgnoreFailures /* = false */,
+ const Utf8Str &strWhat /* = "" */, int *prc /* = NULL */)
+{
+ AssertReturn(strFile.isNotEmpty(), E_INVALIDARG);
+
+ HRESULT hrc = S_OK;
+
+ LogFunc(("Deleting file '%s'\n", strFile.c_str()));
+
+ int vrc = RTFileDelete(strFile.c_str());
+ if (RT_FAILURE(vrc))
+ {
+ if ( !fIgnoreFailures
+ /* Don't (externally) bitch about stuff which doesn't exist. */
+ && ( vrc != VERR_FILE_NOT_FOUND
+ && vrc != VERR_PATH_NOT_FOUND
+ )
+ )
+ {
+ LogRel(("Deleting file '%s' failed: %Rrc\n", strFile.c_str(), vrc));
+
+ Utf8StrFmt strError("Error deleting %s '%s' (%Rrc)",
+ strWhat.isEmpty() ? tr("file") : strWhat.c_str(), strFile.c_str(), vrc);
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, strError.c_str(),
+ strFile.c_str(), vrc);
+ }
+
+ if (prc)
+ *prc = vrc;
+ }
+
+ return hrc;
+}
+
+/**
+ * Creates differencing hard disks for all normal hard disks attached to this
+ * machine and a new set of attachments to refer to created disks.
+ *
+ * Used when taking a snapshot or when deleting the current state. Gets called
+ * from SessionMachine::BeginTakingSnapshot() and SessionMachine::restoreSnapshotHandler().
+ *
+ * This method assumes that mMediumAttachments contains the original hard disk
+ * attachments it needs to create diffs for. On success, these attachments will
+ * be replaced with the created diffs.
+ *
+ * Attachments with non-normal hard disks are left as is.
+ *
+ * If @a aOnline is @c false then the original hard disks that require implicit
+ * diffs will be locked for reading. Otherwise it is assumed that they are
+ * already locked for writing (when the VM was started). Note that in the latter
+ * case it is responsibility of the caller to lock the newly created diffs for
+ * writing if this method succeeds.
+ *
+ * @param aProgress Progress object to run (must contain at least as
+ * many operations left as the number of hard disks
+ * attached).
+ * @param aWeight Weight of this operation.
+ * @param aOnline Whether the VM was online prior to this operation.
+ *
+ * @note The progress object is not marked as completed, neither on success nor
+ * on failure. This is a responsibility of the caller.
+ *
+ * @note Locks this object and the media tree for writing.
+ */
+HRESULT Machine::i_createImplicitDiffs(IProgress *aProgress,
+ ULONG aWeight,
+ bool aOnline)
+{
+ LogFlowThisFunc(("aOnline=%d\n", aOnline));
+
+ ComPtr<IInternalProgressControl> pProgressControl(aProgress);
+ AssertReturn(!!pProgressControl, E_INVALIDARG);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ AutoMultiWriteLock2 alock(this->lockHandle(),
+ &mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ /* must be in a protective state because we release the lock below */
+ AssertReturn( mData->mMachineState == MachineState_Snapshotting
+ || mData->mMachineState == MachineState_OnlineSnapshotting
+ || mData->mMachineState == MachineState_LiveSnapshotting
+ || mData->mMachineState == MachineState_RestoringSnapshot
+ || mData->mMachineState == MachineState_DeletingSnapshot
+ , E_FAIL);
+
+ HRESULT rc = S_OK;
+
+ // use appropriate locked media map (online or offline)
+ MediumLockListMap lockedMediaOffline;
+ MediumLockListMap *lockedMediaMap;
+ if (aOnline)
+ lockedMediaMap = &mData->mSession.mLockedMedia;
+ else
+ lockedMediaMap = &lockedMediaOffline;
+
+ try
+ {
+ if (!aOnline)
+ {
+ /* lock all attached hard disks early to detect "in use"
+ * situations before creating actual diffs */
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ MediumAttachment *pAtt = *it;
+ if (pAtt->i_getType() == DeviceType_HardDisk)
+ {
+ Medium *pMedium = pAtt->i_getMedium();
+ Assert(pMedium);
+
+ MediumLockList *pMediumLockList(new MediumLockList());
+ alock.release();
+ rc = pMedium->i_createMediumLockList(true /* fFailIfInaccessible */,
+ NULL /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pMediumLockList);
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw rc;
+ }
+ rc = lockedMediaMap->Insert(pAtt, pMediumLockList);
+ if (FAILED(rc))
+ {
+ throw setError(rc,
+ tr("Collecting locking information for all attached media failed"));
+ }
+ }
+ }
+
+ /* Now lock all media. If this fails, nothing is locked. */
+ alock.release();
+ rc = lockedMediaMap->Lock();
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ throw setError(rc,
+ tr("Locking of attached media failed"));
+ }
+ }
+
+ /* remember the current list (note that we don't use backup() since
+ * mMediumAttachments may be already backed up) */
+ MediumAttachmentList atts = *mMediumAttachments.data();
+
+ /* start from scratch */
+ mMediumAttachments->clear();
+
+ /* go through remembered attachments and create diffs for normal hard
+ * disks and attach them */
+ for (MediumAttachmentList::const_iterator
+ it = atts.begin();
+ it != atts.end();
+ ++it)
+ {
+ MediumAttachment *pAtt = *it;
+
+ DeviceType_T devType = pAtt->i_getType();
+ Medium *pMedium = pAtt->i_getMedium();
+
+ if ( devType != DeviceType_HardDisk
+ || pMedium == NULL
+ || pMedium->i_getType() != MediumType_Normal)
+ {
+ /* copy the attachment as is */
+
+ /** @todo the progress object created in SessionMachine::TakeSnaphot
+ * only expects operations for hard disks. Later other
+ * device types need to show up in the progress as well. */
+ if (devType == DeviceType_HardDisk)
+ {
+ if (pMedium == NULL)
+ pProgressControl->SetNextOperation(Bstr(tr("Skipping attachment without medium")).raw(),
+ aWeight); // weight
+ else
+ pProgressControl->SetNextOperation(BstrFmt(tr("Skipping medium '%s'"),
+ pMedium->i_getBase()->i_getName().c_str()).raw(),
+ aWeight); // weight
+ }
+
+ mMediumAttachments->push_back(pAtt);
+ continue;
+ }
+
+ /* need a diff */
+ pProgressControl->SetNextOperation(BstrFmt(tr("Creating differencing hard disk for '%s'"),
+ pMedium->i_getBase()->i_getName().c_str()).raw(),
+ aWeight); // weight
+
+ Utf8Str strFullSnapshotFolder;
+ i_calculateFullPath(mUserData->s.strSnapshotFolder, strFullSnapshotFolder);
+
+ ComObjPtr<Medium> diff;
+ diff.createObject();
+ // store the diff in the same registry as the parent
+ // (this cannot fail here because we can't create implicit diffs for
+ // unregistered images)
+ Guid uuidRegistryParent;
+ bool fInRegistry = pMedium->i_getFirstRegistryMachineId(uuidRegistryParent);
+ Assert(fInRegistry); NOREF(fInRegistry);
+ rc = diff->init(mParent,
+ pMedium->i_getPreferredDiffFormat(),
+ strFullSnapshotFolder.append(RTPATH_SLASH_STR),
+ uuidRegistryParent,
+ DeviceType_HardDisk);
+ if (FAILED(rc)) throw rc;
+
+ /** @todo r=bird: How is the locking and diff image cleaned up if we fail before
+ * the push_back? Looks like we're going to release medium with the
+ * wrong kind of lock (general issue with if we fail anywhere at all)
+ * and an orphaned VDI in the snapshots folder. */
+
+ /* update the appropriate lock list */
+ MediumLockList *pMediumLockList;
+ rc = lockedMediaMap->Get(pAtt, pMediumLockList);
+ AssertComRCThrowRC(rc);
+ if (aOnline)
+ {
+ alock.release();
+ /* The currently attached medium will be read-only, change
+ * the lock type to read. */
+ rc = pMediumLockList->Update(pMedium, false);
+ alock.acquire();
+ AssertComRCThrowRC(rc);
+ }
+
+ /* release the locks before the potentially lengthy operation */
+ alock.release();
+ rc = pMedium->i_createDiffStorage(diff,
+ pMedium->i_getPreferredDiffVariant(),
+ pMediumLockList,
+ NULL /* aProgress */,
+ true /* aWait */,
+ false /* aNotify */);
+ alock.acquire();
+ if (FAILED(rc)) throw rc;
+
+ /* actual lock list update is done in Machine::i_commitMedia */
+
+ rc = diff->i_addBackReference(mData->mUuid);
+ AssertComRCThrowRC(rc);
+
+ /* add a new attachment */
+ ComObjPtr<MediumAttachment> attachment;
+ attachment.createObject();
+ rc = attachment->init(this,
+ diff,
+ pAtt->i_getControllerName(),
+ pAtt->i_getPort(),
+ pAtt->i_getDevice(),
+ DeviceType_HardDisk,
+ true /* aImplicit */,
+ false /* aPassthrough */,
+ false /* aTempEject */,
+ pAtt->i_getNonRotational(),
+ pAtt->i_getDiscard(),
+ pAtt->i_getHotPluggable(),
+ pAtt->i_getBandwidthGroup());
+ if (FAILED(rc)) throw rc;
+
+ rc = lockedMediaMap->ReplaceKey(pAtt, attachment);
+ AssertComRCThrowRC(rc);
+ mMediumAttachments->push_back(attachment);
+ }
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ /* unlock all hard disks we locked when there is no VM */
+ if (!aOnline)
+ {
+ ErrorInfoKeeper eik;
+
+ HRESULT rc1 = lockedMediaMap->Clear();
+ AssertComRC(rc1);
+ }
+
+ return rc;
+}
+
+/**
+ * Deletes implicit differencing hard disks created either by
+ * #i_createImplicitDiffs() or by #attachDevice() and rolls back
+ * mMediumAttachments.
+ *
+ * Note that to delete hard disks created by #attachDevice() this method is
+ * called from #i_rollbackMedia() when the changes are rolled back.
+ *
+ * @note Locks this object and the media tree for writing.
+ */
+HRESULT Machine::i_deleteImplicitDiffs(bool aOnline)
+{
+ LogFlowThisFunc(("aOnline=%d\n", aOnline));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ AutoMultiWriteLock2 alock(this->lockHandle(),
+ &mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ /* We absolutely must have backed up state. */
+ AssertReturn(mMediumAttachments.isBackedUp(), E_FAIL);
+
+ /* Check if there are any implicitly created diff images. */
+ bool fImplicitDiffs = false;
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ const ComObjPtr<MediumAttachment> &pAtt = *it;
+ if (pAtt->i_isImplicit())
+ {
+ fImplicitDiffs = true;
+ break;
+ }
+ }
+ /* If there is nothing to do, leave early. This saves lots of image locking
+ * effort. It also avoids a MachineStateChanged event without real reason.
+ * This is important e.g. when loading a VM config, because there should be
+ * no events. Otherwise API clients can become thoroughly confused for
+ * inaccessible VMs (the code for loading VM configs uses this method for
+ * cleanup if the config makes no sense), as they take such events as an
+ * indication that the VM is alive, and they would force the VM config to
+ * be reread, leading to an endless loop. */
+ if (!fImplicitDiffs)
+ return S_OK;
+
+ HRESULT rc = S_OK;
+ MachineState_T oldState = mData->mMachineState;
+
+ /* will release the lock before the potentially lengthy operation,
+ * so protect with the special state (unless already protected) */
+ if ( oldState != MachineState_Snapshotting
+ && oldState != MachineState_OnlineSnapshotting
+ && oldState != MachineState_LiveSnapshotting
+ && oldState != MachineState_RestoringSnapshot
+ && oldState != MachineState_DeletingSnapshot
+ && oldState != MachineState_DeletingSnapshotOnline
+ && oldState != MachineState_DeletingSnapshotPaused
+ )
+ i_setMachineState(MachineState_SettingUp);
+
+ // use appropriate locked media map (online or offline)
+ MediumLockListMap lockedMediaOffline;
+ MediumLockListMap *lockedMediaMap;
+ if (aOnline)
+ lockedMediaMap = &mData->mSession.mLockedMedia;
+ else
+ lockedMediaMap = &lockedMediaOffline;
+
+ try
+ {
+ if (!aOnline)
+ {
+ /* lock all attached hard disks early to detect "in use"
+ * situations before deleting actual diffs */
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ MediumAttachment *pAtt = *it;
+ if (pAtt->i_getType() == DeviceType_HardDisk)
+ {
+ Medium *pMedium = pAtt->i_getMedium();
+ Assert(pMedium);
+
+ MediumLockList *pMediumLockList(new MediumLockList());
+ alock.release();
+ rc = pMedium->i_createMediumLockList(true /* fFailIfInaccessible */,
+ NULL /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pMediumLockList);
+ alock.acquire();
+
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw rc;
+ }
+
+ rc = lockedMediaMap->Insert(pAtt, pMediumLockList);
+ if (FAILED(rc))
+ throw rc;
+ }
+ }
+
+ if (FAILED(rc))
+ throw rc;
+ } // end of offline
+
+ /* Lock lists are now up to date and include implicitly created media */
+
+ /* Go through remembered attachments and delete all implicitly created
+ * diffs and fix up the attachment information */
+ const MediumAttachmentList &oldAtts = *mMediumAttachments.backedUpData();
+ MediumAttachmentList implicitAtts;
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ ComObjPtr<MediumAttachment> pAtt = *it;
+ ComObjPtr<Medium> pMedium = pAtt->i_getMedium();
+ if (pMedium.isNull())
+ continue;
+
+ // Implicit attachments go on the list for deletion and back references are removed.
+ if (pAtt->i_isImplicit())
+ {
+ /* Deassociate and mark for deletion */
+ LogFlowThisFunc(("Detaching '%s', pending deletion\n", pAtt->i_getLogName()));
+ rc = pMedium->i_removeBackReference(mData->mUuid);
+ if (FAILED(rc))
+ throw rc;
+ implicitAtts.push_back(pAtt);
+ continue;
+ }
+
+ /* Was this medium attached before? */
+ if (!i_findAttachment(oldAtts, pMedium))
+ {
+ /* no: de-associate */
+ LogFlowThisFunc(("Detaching '%s', no deletion\n", pAtt->i_getLogName()));
+ rc = pMedium->i_removeBackReference(mData->mUuid);
+ if (FAILED(rc))
+ throw rc;
+ continue;
+ }
+ LogFlowThisFunc(("Not detaching '%s'\n", pAtt->i_getLogName()));
+ }
+
+ /* If there are implicit attachments to delete, throw away the lock
+ * map contents (which will unlock all media) since the medium
+ * attachments will be rolled back. Below we need to completely
+ * recreate the lock map anyway since it is infinitely complex to
+ * do this incrementally (would need reconstructing each attachment
+ * change, which would be extremely hairy). */
+ if (implicitAtts.size() != 0)
+ {
+ ErrorInfoKeeper eik;
+
+ HRESULT rc1 = lockedMediaMap->Clear();
+ AssertComRC(rc1);
+ }
+
+ /* rollback hard disk changes */
+ mMediumAttachments.rollback();
+
+ MultiResult mrc(S_OK);
+
+ // Delete unused implicit diffs.
+ if (implicitAtts.size() != 0)
+ {
+ alock.release();
+
+ for (MediumAttachmentList::const_iterator
+ it = implicitAtts.begin();
+ it != implicitAtts.end();
+ ++it)
+ {
+ // Remove medium associated with this attachment.
+ ComObjPtr<MediumAttachment> pAtt = *it;
+ Assert(pAtt);
+ LogFlowThisFunc(("Deleting '%s'\n", pAtt->i_getLogName()));
+ ComObjPtr<Medium> pMedium = pAtt->i_getMedium();
+ Assert(pMedium);
+
+ rc = pMedium->i_deleteStorage(NULL /*aProgress*/, true /*aWait*/, false /*aNotify*/);
+ // continue on delete failure, just collect error messages
+ AssertMsg(SUCCEEDED(rc), ("rc=%Rhrc it=%s hd=%s\n", rc, pAtt->i_getLogName(),
+ pMedium->i_getLocationFull().c_str() ));
+ mrc = rc;
+ }
+ // Clear the list of deleted implicit attachments now, while not
+ // holding the lock, as it will ultimately trigger Medium::uninit()
+ // calls which assume that the media tree lock isn't held.
+ implicitAtts.clear();
+
+ alock.acquire();
+
+ /* if there is a VM recreate media lock map as mentioned above,
+ * otherwise it is a waste of time and we leave things unlocked */
+ if (aOnline)
+ {
+ const ComObjPtr<SessionMachine> pMachine = mData->mSession.mMachine;
+ /* must never be NULL, but better safe than sorry */
+ if (!pMachine.isNull())
+ {
+ alock.release();
+ rc = mData->mSession.mMachine->i_lockMedia();
+ alock.acquire();
+ if (FAILED(rc))
+ throw rc;
+ }
+ }
+ }
+ }
+ catch (HRESULT aRC) {rc = aRC;}
+
+ if (mData->mMachineState == MachineState_SettingUp)
+ i_setMachineState(oldState);
+
+ /* unlock all hard disks we locked when there is no VM */
+ if (!aOnline)
+ {
+ ErrorInfoKeeper eik;
+
+ HRESULT rc1 = lockedMediaMap->Clear();
+ AssertComRC(rc1);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Looks through the given list of media attachments for one with the given parameters
+ * and returns it, or NULL if not found. The list is a parameter so that backup lists
+ * can be searched as well if needed.
+ *
+ * @param ll
+ * @param aControllerName
+ * @param aControllerPort
+ * @param aDevice
+ * @return
+ */
+MediumAttachment *Machine::i_findAttachment(const MediumAttachmentList &ll,
+ const Utf8Str &aControllerName,
+ LONG aControllerPort,
+ LONG aDevice)
+{
+ for (MediumAttachmentList::const_iterator
+ it = ll.begin();
+ it != ll.end();
+ ++it)
+ {
+ MediumAttachment *pAttach = *it;
+ if (pAttach->i_matches(aControllerName, aControllerPort, aDevice))
+ return pAttach;
+ }
+
+ return NULL;
+}
+
+/**
+ * Looks through the given list of media attachments for one with the given parameters
+ * and returns it, or NULL if not found. The list is a parameter so that backup lists
+ * can be searched as well if needed.
+ *
+ * @param ll
+ * @param pMedium
+ * @return
+ */
+MediumAttachment *Machine::i_findAttachment(const MediumAttachmentList &ll,
+ ComObjPtr<Medium> pMedium)
+{
+ for (MediumAttachmentList::const_iterator
+ it = ll.begin();
+ it != ll.end();
+ ++it)
+ {
+ MediumAttachment *pAttach = *it;
+ ComObjPtr<Medium> pMediumThis = pAttach->i_getMedium();
+ if (pMediumThis == pMedium)
+ return pAttach;
+ }
+
+ return NULL;
+}
+
+/**
+ * Looks through the given list of media attachments for one with the given parameters
+ * and returns it, or NULL if not found. The list is a parameter so that backup lists
+ * can be searched as well if needed.
+ *
+ * @param ll
+ * @param id
+ * @return
+ */
+MediumAttachment *Machine::i_findAttachment(const MediumAttachmentList &ll,
+ Guid &id)
+{
+ for (MediumAttachmentList::const_iterator
+ it = ll.begin();
+ it != ll.end();
+ ++it)
+ {
+ MediumAttachment *pAttach = *it;
+ ComObjPtr<Medium> pMediumThis = pAttach->i_getMedium();
+ if (pMediumThis->i_getId() == id)
+ return pAttach;
+ }
+
+ return NULL;
+}
+
+/**
+ * Main implementation for Machine::DetachDevice. This also gets called
+ * from Machine::prepareUnregister() so it has been taken out for simplicity.
+ *
+ * @param pAttach Medium attachment to detach.
+ * @param writeLock Machine write lock which the caller must have locked once.
+ * This may be released temporarily in here.
+ * @param pSnapshot If NULL, then the detachment is for the current machine.
+ * Otherwise this is for a SnapshotMachine, and this must be
+ * its snapshot.
+ * @return
+ */
+HRESULT Machine::i_detachDevice(MediumAttachment *pAttach,
+ AutoWriteLock &writeLock,
+ Snapshot *pSnapshot)
+{
+ ComObjPtr<Medium> oldmedium = pAttach->i_getMedium();
+ DeviceType_T mediumType = pAttach->i_getType();
+
+ LogFlowThisFunc(("Entering, medium of attachment is %s\n", oldmedium ? oldmedium->i_getLocationFull().c_str() : "NULL"));
+
+ if (pAttach->i_isImplicit())
+ {
+ /* attempt to implicitly delete the implicitly created diff */
+
+ /// @todo move the implicit flag from MediumAttachment to Medium
+ /// and forbid any hard disk operation when it is implicit. Or maybe
+ /// a special media state for it to make it even more simple.
+
+ Assert(mMediumAttachments.isBackedUp());
+
+ /* will release the lock before the potentially lengthy operation, so
+ * protect with the special state */
+ MachineState_T oldState = mData->mMachineState;
+ i_setMachineState(MachineState_SettingUp);
+
+ writeLock.release();
+
+ HRESULT rc = oldmedium->i_deleteStorage(NULL /*aProgress*/,
+ true /*aWait*/,
+ false /*aNotify*/);
+
+ writeLock.acquire();
+
+ i_setMachineState(oldState);
+
+ if (FAILED(rc)) return rc;
+ }
+
+ i_setModified(IsModified_Storage);
+ mMediumAttachments.backup();
+ mMediumAttachments->remove(pAttach);
+
+ if (!oldmedium.isNull())
+ {
+ // if this is from a snapshot, do not defer detachment to i_commitMedia()
+ if (pSnapshot)
+ oldmedium->i_removeBackReference(mData->mUuid, pSnapshot->i_getId());
+ // else if non-hard disk media, do not defer detachment to i_commitMedia() either
+ else if (mediumType != DeviceType_HardDisk)
+ oldmedium->i_removeBackReference(mData->mUuid);
+ }
+
+ return S_OK;
+}
+
+/**
+ * Goes thru all media of the given list and
+ *
+ * 1) calls i_detachDevice() on each of them for this machine and
+ * 2) adds all Medium objects found in the process to the given list,
+ * depending on cleanupMode.
+ *
+ * If cleanupMode is CleanupMode_DetachAllReturnHardDisksOnly, this only
+ * adds hard disks to the list. If it is CleanupMode_Full, this adds all
+ * media to the list.
+ * CleanupMode_DetachAllReturnHardDisksAndVMRemovable adds hard disk and
+ * also removable medias if they are located in the VM folder and referenced
+ * only by this VM (media prepared by unattended installer).
+ *
+ * This gets called from Machine::Unregister, both for the actual Machine and
+ * the SnapshotMachine objects that might be found in the snapshots.
+ *
+ * Requires caller and locking. The machine lock must be passed in because it
+ * will be passed on to i_detachDevice which needs it for temporary unlocking.
+ *
+ * @param writeLock Machine lock from top-level caller; this gets passed to
+ * i_detachDevice.
+ * @param pSnapshot Must be NULL when called for a "real" Machine or a snapshot
+ * object if called for a SnapshotMachine.
+ * @param cleanupMode If DetachAllReturnHardDisksOnly, only hard disk media get
+ * added to llMedia; if Full, then all media get added;
+ * otherwise no media get added.
+ * @param llMedia Caller's list to receive Medium objects which got detached so
+ * caller can close() them, depending on cleanupMode.
+ * @return
+ */
+HRESULT Machine::i_detachAllMedia(AutoWriteLock &writeLock,
+ Snapshot *pSnapshot,
+ CleanupMode_T cleanupMode,
+ MediaList &llMedia)
+{
+ Assert(isWriteLockOnCurrentThread());
+
+ HRESULT rc;
+
+ // make a temporary list because i_detachDevice invalidates iterators into
+ // mMediumAttachments
+ MediumAttachmentList llAttachments2 = *mMediumAttachments.data();
+
+ for (MediumAttachmentList::iterator
+ it = llAttachments2.begin();
+ it != llAttachments2.end();
+ ++it)
+ {
+ ComObjPtr<MediumAttachment> &pAttach = *it;
+ ComObjPtr<Medium> pMedium = pAttach->i_getMedium();
+
+ if (!pMedium.isNull())
+ {
+ AutoCaller mac(pMedium);
+ if (FAILED(mac.rc())) return mac.rc();
+ AutoReadLock lock(pMedium COMMA_LOCKVAL_SRC_POS);
+ DeviceType_T devType = pMedium->i_getDeviceType();
+ size_t cBackRefs = pMedium->i_getMachineBackRefCount();
+ Utf8Str strMediumLocation = pMedium->i_getLocationFull();
+ strMediumLocation.stripFilename();
+ Utf8Str strMachineFolder = i_getSettingsFileFull();
+ strMachineFolder.stripFilename();
+ if ( ( cleanupMode == CleanupMode_DetachAllReturnHardDisksOnly
+ && devType == DeviceType_HardDisk)
+ || ( cleanupMode == CleanupMode_DetachAllReturnHardDisksAndVMRemovable
+ && ( devType == DeviceType_HardDisk
+ || ( cBackRefs <= 1
+ && strMediumLocation == strMachineFolder
+ && *pMedium->i_getFirstMachineBackrefId() == i_getId())))
+ || (cleanupMode == CleanupMode_Full)
+ )
+ {
+ llMedia.push_back(pMedium);
+ ComObjPtr<Medium> pParent = pMedium->i_getParent();
+ /* Not allowed to keep this lock as below we need the parent
+ * medium lock, and the lock order is parent to child. */
+ lock.release();
+ /*
+ * Search for medias which are not attached to any machine, but
+ * in the chain to an attached disk. Mediums are only consided
+ * if they are:
+ * - have only one child
+ * - no references to any machines
+ * - are of normal medium type
+ */
+ while (!pParent.isNull())
+ {
+ AutoCaller mac1(pParent);
+ if (FAILED(mac1.rc())) return mac1.rc();
+ AutoReadLock lock1(pParent COMMA_LOCKVAL_SRC_POS);
+ if (pParent->i_getChildren().size() == 1)
+ {
+ if ( pParent->i_getMachineBackRefCount() == 0
+ && pParent->i_getType() == MediumType_Normal
+ && find(llMedia.begin(), llMedia.end(), pParent) == llMedia.end())
+ llMedia.push_back(pParent);
+ }
+ else
+ break;
+ pParent = pParent->i_getParent();
+ }
+ }
+ }
+
+ // real machine: then we need to use the proper method
+ rc = i_detachDevice(pAttach, writeLock, pSnapshot);
+
+ if (FAILED(rc))
+ return rc;
+ }
+
+ return S_OK;
+}
+
+/**
+ * Perform deferred hard disk detachments.
+ *
+ * Does nothing if the hard disk attachment data (mMediumAttachments) is not
+ * changed (not backed up).
+ *
+ * If @a aOnline is @c true then this method will also unlock the old hard
+ * disks for which the new implicit diffs were created and will lock these new
+ * diffs for writing.
+ *
+ * @param aOnline Whether the VM was online prior to this operation.
+ *
+ * @note Locks this object for writing!
+ */
+void Machine::i_commitMedia(bool aOnline /*= false*/)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("Entering, aOnline=%d\n", aOnline));
+
+ HRESULT rc = S_OK;
+
+ /* no attach/detach operations -- nothing to do */
+ if (!mMediumAttachments.isBackedUp())
+ return;
+
+ MediumAttachmentList &oldAtts = *mMediumAttachments.backedUpData();
+ bool fMediaNeedsLocking = false;
+
+ /* enumerate new attachments */
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ MediumAttachment *pAttach = *it;
+
+ pAttach->i_commit();
+
+ Medium *pMedium = pAttach->i_getMedium();
+ bool fImplicit = pAttach->i_isImplicit();
+
+ LogFlowThisFunc(("Examining current medium '%s' (implicit: %d)\n",
+ (pMedium) ? pMedium->i_getName().c_str() : "NULL",
+ fImplicit));
+
+ /** @todo convert all this Machine-based voodoo to MediumAttachment
+ * based commit logic. */
+ if (fImplicit)
+ {
+ /* convert implicit attachment to normal */
+ pAttach->i_setImplicit(false);
+
+ if ( aOnline
+ && pMedium
+ && pAttach->i_getType() == DeviceType_HardDisk
+ )
+ {
+ /* update the appropriate lock list */
+ MediumLockList *pMediumLockList;
+ rc = mData->mSession.mLockedMedia.Get(pAttach, pMediumLockList);
+ AssertComRC(rc);
+ if (pMediumLockList)
+ {
+ /* unlock if there's a need to change the locking */
+ if (!fMediaNeedsLocking)
+ {
+ rc = mData->mSession.mLockedMedia.Unlock();
+ AssertComRC(rc);
+ fMediaNeedsLocking = true;
+ }
+ rc = pMediumLockList->Update(pMedium->i_getParent(), false);
+ AssertComRC(rc);
+ rc = pMediumLockList->Append(pMedium, true);
+ AssertComRC(rc);
+ }
+ }
+
+ continue;
+ }
+
+ if (pMedium)
+ {
+ /* was this medium attached before? */
+ for (MediumAttachmentList::iterator
+ oldIt = oldAtts.begin();
+ oldIt != oldAtts.end();
+ ++oldIt)
+ {
+ MediumAttachment *pOldAttach = *oldIt;
+ if (pOldAttach->i_getMedium() == pMedium)
+ {
+ LogFlowThisFunc(("--> medium '%s' was attached before, will not remove\n", pMedium->i_getName().c_str()));
+
+ /* yes: remove from old to avoid de-association */
+ oldAtts.erase(oldIt);
+ break;
+ }
+ }
+ }
+ }
+
+ /* enumerate remaining old attachments and de-associate from the
+ * current machine state */
+ for (MediumAttachmentList::const_iterator
+ it = oldAtts.begin();
+ it != oldAtts.end();
+ ++it)
+ {
+ MediumAttachment *pAttach = *it;
+ Medium *pMedium = pAttach->i_getMedium();
+
+ /* Detach only hard disks, since DVD/floppy media is detached
+ * instantly in MountMedium. */
+ if (pAttach->i_getType() == DeviceType_HardDisk && pMedium)
+ {
+ LogFlowThisFunc(("detaching medium '%s' from machine\n", pMedium->i_getName().c_str()));
+
+ /* now de-associate from the current machine state */
+ rc = pMedium->i_removeBackReference(mData->mUuid);
+ AssertComRC(rc);
+
+ if (aOnline)
+ {
+ /* unlock since medium is not used anymore */
+ MediumLockList *pMediumLockList;
+ rc = mData->mSession.mLockedMedia.Get(pAttach, pMediumLockList);
+ if (RT_UNLIKELY(rc == VBOX_E_INVALID_OBJECT_STATE))
+ {
+ /* this happens for online snapshots, there the attachment
+ * is changing, but only to a diff image created under
+ * the old one, so there is no separate lock list */
+ Assert(!pMediumLockList);
+ }
+ else
+ {
+ AssertComRC(rc);
+ if (pMediumLockList)
+ {
+ rc = mData->mSession.mLockedMedia.Remove(pAttach);
+ AssertComRC(rc);
+ }
+ }
+ }
+ }
+ }
+
+ /* take media locks again so that the locking state is consistent */
+ if (fMediaNeedsLocking)
+ {
+ Assert(aOnline);
+ rc = mData->mSession.mLockedMedia.Lock();
+ AssertComRC(rc);
+ }
+
+ /* commit the hard disk changes */
+ mMediumAttachments.commit();
+
+ if (i_isSessionMachine())
+ {
+ /*
+ * Update the parent machine to point to the new owner.
+ * This is necessary because the stored parent will point to the
+ * session machine otherwise and cause crashes or errors later
+ * when the session machine gets invalid.
+ */
+ /** @todo Change the MediumAttachment class to behave like any other
+ * class in this regard by creating peer MediumAttachment
+ * objects for session machines and share the data with the peer
+ * machine.
+ */
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ (*it)->i_updateParentMachine(mPeer);
+
+ /* attach new data to the primary machine and reshare it */
+ mPeer->mMediumAttachments.attach(mMediumAttachments);
+ }
+
+ return;
+}
+
+/**
+ * Perform deferred deletion of implicitly created diffs.
+ *
+ * Does nothing if the hard disk attachment data (mMediumAttachments) is not
+ * changed (not backed up).
+ *
+ * @note Locks this object for writing!
+ */
+void Machine::i_rollbackMedia()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ // AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("Entering rollbackMedia\n"));
+
+ HRESULT rc = S_OK;
+
+ /* no attach/detach operations -- nothing to do */
+ if (!mMediumAttachments.isBackedUp())
+ return;
+
+ /* enumerate new attachments */
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ MediumAttachment *pAttach = *it;
+ /* Fix up the backrefs for DVD/floppy media. */
+ if (pAttach->i_getType() != DeviceType_HardDisk)
+ {
+ Medium *pMedium = pAttach->i_getMedium();
+ if (pMedium)
+ {
+ rc = pMedium->i_removeBackReference(mData->mUuid);
+ AssertComRC(rc);
+ }
+ }
+
+ (*it)->i_rollback();
+
+ pAttach = *it;
+ /* Fix up the backrefs for DVD/floppy media. */
+ if (pAttach->i_getType() != DeviceType_HardDisk)
+ {
+ Medium *pMedium = pAttach->i_getMedium();
+ if (pMedium)
+ {
+ rc = pMedium->i_addBackReference(mData->mUuid);
+ AssertComRC(rc);
+ }
+ }
+ }
+
+ /** @todo convert all this Machine-based voodoo to MediumAttachment
+ * based rollback logic. */
+ i_deleteImplicitDiffs(Global::IsOnline(mData->mMachineState));
+
+ return;
+}
+
+/**
+ * Returns true if the settings file is located in the directory named exactly
+ * as the machine; this means, among other things, that the machine directory
+ * should be auto-renamed.
+ *
+ * @param aSettingsDir if not NULL, the full machine settings file directory
+ * name will be assigned there.
+ *
+ * @note Doesn't lock anything.
+ * @note Not thread safe (must be called from this object's lock).
+ */
+bool Machine::i_isInOwnDir(Utf8Str *aSettingsDir /* = NULL */) const
+{
+ Utf8Str strMachineDirName(mData->m_strConfigFileFull); // path/to/machinesfolder/vmname/vmname.vbox
+ strMachineDirName.stripFilename(); // path/to/machinesfolder/vmname
+ if (aSettingsDir)
+ *aSettingsDir = strMachineDirName;
+ strMachineDirName.stripPath(); // vmname
+ Utf8Str strConfigFileOnly(mData->m_strConfigFileFull); // path/to/machinesfolder/vmname/vmname.vbox
+ strConfigFileOnly.stripPath() // vmname.vbox
+ .stripSuffix(); // vmname
+ /** @todo hack, make somehow use of ComposeMachineFilename */
+ if (mUserData->s.fDirectoryIncludesUUID)
+ strConfigFileOnly.appendPrintf(" (%RTuuid)", mData->mUuid.raw());
+
+ AssertReturn(!strMachineDirName.isEmpty(), false);
+ AssertReturn(!strConfigFileOnly.isEmpty(), false);
+
+ return strMachineDirName == strConfigFileOnly;
+}
+
+/**
+ * Discards all changes to machine settings.
+ *
+ * @param aNotify Whether to notify the direct session about changes or not.
+ *
+ * @note Locks objects for writing!
+ */
+void Machine::i_rollback(bool aNotify)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), (void)0);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mStorageControllers.isNull())
+ {
+ if (mStorageControllers.isBackedUp())
+ {
+ /* unitialize all new devices (absent in the backed up list). */
+ StorageControllerList *backedList = mStorageControllers.backedUpData();
+ for (StorageControllerList::const_iterator
+ it = mStorageControllers->begin();
+ it != mStorageControllers->end();
+ ++it)
+ {
+ if ( std::find(backedList->begin(), backedList->end(), *it)
+ == backedList->end()
+ )
+ {
+ (*it)->uninit();
+ }
+ }
+
+ /* restore the list */
+ mStorageControllers.rollback();
+ }
+
+ /* rollback any changes to devices after restoring the list */
+ if (mData->flModifications & IsModified_Storage)
+ {
+ for (StorageControllerList::const_iterator
+ it = mStorageControllers->begin();
+ it != mStorageControllers->end();
+ ++it)
+ {
+ (*it)->i_rollback();
+ }
+ }
+ }
+
+ if (!mUSBControllers.isNull())
+ {
+ if (mUSBControllers.isBackedUp())
+ {
+ /* unitialize all new devices (absent in the backed up list). */
+ USBControllerList *backedList = mUSBControllers.backedUpData();
+ for (USBControllerList::const_iterator
+ it = mUSBControllers->begin();
+ it != mUSBControllers->end();
+ ++it)
+ {
+ if ( std::find(backedList->begin(), backedList->end(), *it)
+ == backedList->end()
+ )
+ {
+ (*it)->uninit();
+ }
+ }
+
+ /* restore the list */
+ mUSBControllers.rollback();
+ }
+
+ /* rollback any changes to devices after restoring the list */
+ if (mData->flModifications & IsModified_USB)
+ {
+ for (USBControllerList::const_iterator
+ it = mUSBControllers->begin();
+ it != mUSBControllers->end();
+ ++it)
+ {
+ (*it)->i_rollback();
+ }
+ }
+ }
+
+ mUserData.rollback();
+
+ mHWData.rollback();
+
+ if (mData->flModifications & IsModified_Storage)
+ i_rollbackMedia();
+
+ if (mBIOSSettings)
+ mBIOSSettings->i_rollback();
+
+ if (mRecordingSettings && (mData->flModifications & IsModified_Recording))
+ mRecordingSettings->i_rollback();
+
+ if (mTrustedPlatformModule)
+ mTrustedPlatformModule->i_rollback();
+
+ if (mNvramStore)
+ mNvramStore->i_rollback();
+
+ if (mGraphicsAdapter && (mData->flModifications & IsModified_GraphicsAdapter))
+ mGraphicsAdapter->i_rollback();
+
+ if (mVRDEServer && (mData->flModifications & IsModified_VRDEServer))
+ mVRDEServer->i_rollback();
+
+ if (mAudioSettings && (mData->flModifications & IsModified_AudioSettings))
+ mAudioSettings->i_rollback();
+
+ if (mUSBDeviceFilters && (mData->flModifications & IsModified_USB))
+ mUSBDeviceFilters->i_rollback();
+
+ if (mBandwidthControl && (mData->flModifications & IsModified_BandwidthControl))
+ mBandwidthControl->i_rollback();
+
+ if (mGuestDebugControl && (mData->flModifications & IsModified_GuestDebugControl))
+ mGuestDebugControl->i_rollback();
+
+ if (!mHWData.isNull())
+ mNetworkAdapters.resize(Global::getMaxNetworkAdapters(mHWData->mChipsetType));
+ NetworkAdapterVector networkAdapters(mNetworkAdapters.size());
+ ComPtr<ISerialPort> serialPorts[RT_ELEMENTS(mSerialPorts)];
+ ComPtr<IParallelPort> parallelPorts[RT_ELEMENTS(mParallelPorts)];
+
+ if (mData->flModifications & IsModified_NetworkAdapters)
+ for (ULONG slot = 0; slot < mNetworkAdapters.size(); ++slot)
+ if ( mNetworkAdapters[slot]
+ && mNetworkAdapters[slot]->i_isModified())
+ {
+ mNetworkAdapters[slot]->i_rollback();
+ networkAdapters[slot] = mNetworkAdapters[slot];
+ }
+
+ if (mData->flModifications & IsModified_SerialPorts)
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); ++slot)
+ if ( mSerialPorts[slot]
+ && mSerialPorts[slot]->i_isModified())
+ {
+ mSerialPorts[slot]->i_rollback();
+ serialPorts[slot] = mSerialPorts[slot];
+ }
+
+ if (mData->flModifications & IsModified_ParallelPorts)
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); ++slot)
+ if ( mParallelPorts[slot]
+ && mParallelPorts[slot]->i_isModified())
+ {
+ mParallelPorts[slot]->i_rollback();
+ parallelPorts[slot] = mParallelPorts[slot];
+ }
+
+ if (aNotify)
+ {
+ /* inform the direct session about changes */
+
+ ComObjPtr<Machine> that = this;
+ uint32_t flModifications = mData->flModifications;
+ alock.release();
+
+ if (flModifications & IsModified_SharedFolders)
+ that->i_onSharedFolderChange();
+
+ if (flModifications & IsModified_VRDEServer)
+ that->i_onVRDEServerChange(/* aRestart */ TRUE);
+ if (flModifications & IsModified_USB)
+ that->i_onUSBControllerChange();
+
+ for (ULONG slot = 0; slot < networkAdapters.size(); ++slot)
+ if (networkAdapters[slot])
+ that->i_onNetworkAdapterChange(networkAdapters[slot], FALSE);
+ for (ULONG slot = 0; slot < RT_ELEMENTS(serialPorts); ++slot)
+ if (serialPorts[slot])
+ that->i_onSerialPortChange(serialPorts[slot]);
+ for (ULONG slot = 0; slot < RT_ELEMENTS(parallelPorts); ++slot)
+ if (parallelPorts[slot])
+ that->i_onParallelPortChange(parallelPorts[slot]);
+
+ if (flModifications & IsModified_Storage)
+ {
+ for (StorageControllerList::const_iterator
+ it = mStorageControllers->begin();
+ it != mStorageControllers->end();
+ ++it)
+ {
+ that->i_onStorageControllerChange(that->i_getId(), (*it)->i_getName());
+ }
+ }
+
+ if (flModifications & IsModified_GuestDebugControl)
+ that->i_onGuestDebugControlChange(mGuestDebugControl);
+
+#if 0
+ if (flModifications & IsModified_BandwidthControl)
+ that->onBandwidthControlChange();
+#endif
+ }
+}
+
+/**
+ * Commits all the changes to machine settings.
+ *
+ * Note that this operation is supposed to never fail.
+ *
+ * @note Locks this object and children for writing.
+ */
+void Machine::i_commit()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoCaller peerCaller(mPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ AutoMultiWriteLock2 alock(mPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * use safe commit to ensure Snapshot machines (that share mUserData)
+ * will still refer to a valid memory location
+ */
+ mUserData.commitCopy();
+
+ mHWData.commit();
+
+ if (mMediumAttachments.isBackedUp())
+ i_commitMedia(Global::IsOnline(mData->mMachineState));
+
+ mBIOSSettings->i_commit();
+ mRecordingSettings->i_commit();
+ mTrustedPlatformModule->i_commit();
+ mNvramStore->i_commit();
+ mGraphicsAdapter->i_commit();
+ mVRDEServer->i_commit();
+ mAudioSettings->i_commit();
+ mUSBDeviceFilters->i_commit();
+ mBandwidthControl->i_commit();
+ mGuestDebugControl->i_commit();
+
+ /* Since mNetworkAdapters is a list which might have been changed (resized)
+ * without using the Backupable<> template we need to handle the copying
+ * of the list entries manually, including the creation of peers for the
+ * new objects. */
+ bool commitNetworkAdapters = false;
+ size_t newSize = Global::getMaxNetworkAdapters(mHWData->mChipsetType);
+ if (mPeer)
+ {
+ /* commit everything, even the ones which will go away */
+ for (size_t slot = 0; slot < mNetworkAdapters.size(); slot++)
+ mNetworkAdapters[slot]->i_commit();
+ /* copy over the new entries, creating a peer and uninit the original */
+ mPeer->mNetworkAdapters.resize(RT_MAX(newSize, mPeer->mNetworkAdapters.size()));
+ for (size_t slot = 0; slot < newSize; slot++)
+ {
+ /* look if this adapter has a peer device */
+ ComObjPtr<NetworkAdapter> peer = mNetworkAdapters[slot]->i_getPeer();
+ if (!peer)
+ {
+ /* no peer means the adapter is a newly created one;
+ * create a peer owning data this data share it with */
+ peer.createObject();
+ peer->init(mPeer, mNetworkAdapters[slot], true /* aReshare */);
+ }
+ mPeer->mNetworkAdapters[slot] = peer;
+ }
+ /* uninit any no longer needed network adapters */
+ for (size_t slot = newSize; slot < mNetworkAdapters.size(); ++slot)
+ mNetworkAdapters[slot]->uninit();
+ for (size_t slot = newSize; slot < mPeer->mNetworkAdapters.size(); ++slot)
+ {
+ if (mPeer->mNetworkAdapters[slot])
+ mPeer->mNetworkAdapters[slot]->uninit();
+ }
+ /* Keep the original network adapter count until this point, so that
+ * discarding a chipset type change will not lose settings. */
+ mNetworkAdapters.resize(newSize);
+ mPeer->mNetworkAdapters.resize(newSize);
+ }
+ else
+ {
+ /* we have no peer (our parent is the newly created machine);
+ * just commit changes to the network adapters */
+ commitNetworkAdapters = true;
+ }
+ if (commitNetworkAdapters)
+ for (size_t slot = 0; slot < mNetworkAdapters.size(); ++slot)
+ mNetworkAdapters[slot]->i_commit();
+
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); ++slot)
+ mSerialPorts[slot]->i_commit();
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); ++slot)
+ mParallelPorts[slot]->i_commit();
+
+ bool commitStorageControllers = false;
+
+ if (mStorageControllers.isBackedUp())
+ {
+ mStorageControllers.commit();
+
+ if (mPeer)
+ {
+ /* Commit all changes to new controllers (this will reshare data with
+ * peers for those who have peers) */
+ StorageControllerList *newList = new StorageControllerList();
+ for (StorageControllerList::const_iterator
+ it = mStorageControllers->begin();
+ it != mStorageControllers->end();
+ ++it)
+ {
+ (*it)->i_commit();
+
+ /* look if this controller has a peer device */
+ ComObjPtr<StorageController> peer = (*it)->i_getPeer();
+ if (!peer)
+ {
+ /* no peer means the device is a newly created one;
+ * create a peer owning data this device share it with */
+ peer.createObject();
+ peer->init(mPeer, *it, true /* aReshare */);
+ }
+ else
+ {
+ /* remove peer from the old list */
+ mPeer->mStorageControllers->remove(peer);
+ }
+ /* and add it to the new list */
+ newList->push_back(peer);
+ }
+
+ /* uninit old peer's controllers that are left */
+ for (StorageControllerList::const_iterator
+ it = mPeer->mStorageControllers->begin();
+ it != mPeer->mStorageControllers->end();
+ ++it)
+ {
+ (*it)->uninit();
+ }
+
+ /* attach new list of controllers to our peer */
+ mPeer->mStorageControllers.attach(newList);
+ }
+ else
+ {
+ /* we have no peer (our parent is the newly created machine);
+ * just commit changes to devices */
+ commitStorageControllers = true;
+ }
+ }
+ else
+ {
+ /* the list of controllers itself is not changed,
+ * just commit changes to controllers themselves */
+ commitStorageControllers = true;
+ }
+
+ if (commitStorageControllers)
+ {
+ for (StorageControllerList::const_iterator
+ it = mStorageControllers->begin();
+ it != mStorageControllers->end();
+ ++it)
+ {
+ (*it)->i_commit();
+ }
+ }
+
+ bool commitUSBControllers = false;
+
+ if (mUSBControllers.isBackedUp())
+ {
+ mUSBControllers.commit();
+
+ if (mPeer)
+ {
+ /* Commit all changes to new controllers (this will reshare data with
+ * peers for those who have peers) */
+ USBControllerList *newList = new USBControllerList();
+ for (USBControllerList::const_iterator
+ it = mUSBControllers->begin();
+ it != mUSBControllers->end();
+ ++it)
+ {
+ (*it)->i_commit();
+
+ /* look if this controller has a peer device */
+ ComObjPtr<USBController> peer = (*it)->i_getPeer();
+ if (!peer)
+ {
+ /* no peer means the device is a newly created one;
+ * create a peer owning data this device share it with */
+ peer.createObject();
+ peer->init(mPeer, *it, true /* aReshare */);
+ }
+ else
+ {
+ /* remove peer from the old list */
+ mPeer->mUSBControllers->remove(peer);
+ }
+ /* and add it to the new list */
+ newList->push_back(peer);
+ }
+
+ /* uninit old peer's controllers that are left */
+ for (USBControllerList::const_iterator
+ it = mPeer->mUSBControllers->begin();
+ it != mPeer->mUSBControllers->end();
+ ++it)
+ {
+ (*it)->uninit();
+ }
+
+ /* attach new list of controllers to our peer */
+ mPeer->mUSBControllers.attach(newList);
+ }
+ else
+ {
+ /* we have no peer (our parent is the newly created machine);
+ * just commit changes to devices */
+ commitUSBControllers = true;
+ }
+ }
+ else
+ {
+ /* the list of controllers itself is not changed,
+ * just commit changes to controllers themselves */
+ commitUSBControllers = true;
+ }
+
+ if (commitUSBControllers)
+ {
+ for (USBControllerList::const_iterator
+ it = mUSBControllers->begin();
+ it != mUSBControllers->end();
+ ++it)
+ {
+ (*it)->i_commit();
+ }
+ }
+
+ if (i_isSessionMachine())
+ {
+ /* attach new data to the primary machine and reshare it */
+ mPeer->mUserData.attach(mUserData);
+ mPeer->mHWData.attach(mHWData);
+ /* mmMediumAttachments is reshared by fixupMedia */
+ // mPeer->mMediumAttachments.attach(mMediumAttachments);
+ Assert(mPeer->mMediumAttachments.data() == mMediumAttachments.data());
+ }
+}
+
+/**
+ * Copies all the hardware data from the given machine.
+ *
+ * Currently, only called when the VM is being restored from a snapshot. In
+ * particular, this implies that the VM is not running during this method's
+ * call.
+ *
+ * @note This method must be called from under this object's lock.
+ *
+ * @note This method doesn't call #i_commit(), so all data remains backed up and
+ * unsaved.
+ */
+void Machine::i_copyFrom(Machine *aThat)
+{
+ AssertReturnVoid(!i_isSnapshotMachine());
+ AssertReturnVoid(aThat->i_isSnapshotMachine());
+
+ AssertReturnVoid(!Global::IsOnline(mData->mMachineState));
+
+ mHWData.assignCopy(aThat->mHWData);
+
+ // create copies of all shared folders (mHWData after attaching a copy
+ // contains just references to original objects)
+ for (HWData::SharedFolderList::iterator
+ it = mHWData->mSharedFolders.begin();
+ it != mHWData->mSharedFolders.end();
+ ++it)
+ {
+ ComObjPtr<SharedFolder> folder;
+ folder.createObject();
+ HRESULT rc = folder->initCopy(i_getMachine(), *it);
+ AssertComRC(rc);
+ *it = folder;
+ }
+
+ mBIOSSettings->i_copyFrom(aThat->mBIOSSettings);
+ mRecordingSettings->i_copyFrom(aThat->mRecordingSettings);
+ mTrustedPlatformModule->i_copyFrom(aThat->mTrustedPlatformModule);
+ mNvramStore->i_copyFrom(aThat->mNvramStore);
+ mGraphicsAdapter->i_copyFrom(aThat->mGraphicsAdapter);
+ mVRDEServer->i_copyFrom(aThat->mVRDEServer);
+ mAudioSettings->i_copyFrom(aThat->mAudioSettings);
+ mUSBDeviceFilters->i_copyFrom(aThat->mUSBDeviceFilters);
+ mBandwidthControl->i_copyFrom(aThat->mBandwidthControl);
+ mGuestDebugControl->i_copyFrom(aThat->mGuestDebugControl);
+
+ /* create private copies of all controllers */
+ mStorageControllers.backup();
+ mStorageControllers->clear();
+ for (StorageControllerList::const_iterator
+ it = aThat->mStorageControllers->begin();
+ it != aThat->mStorageControllers->end();
+ ++it)
+ {
+ ComObjPtr<StorageController> ctrl;
+ ctrl.createObject();
+ ctrl->initCopy(this, *it);
+ mStorageControllers->push_back(ctrl);
+ }
+
+ /* create private copies of all USB controllers */
+ mUSBControllers.backup();
+ mUSBControllers->clear();
+ for (USBControllerList::const_iterator
+ it = aThat->mUSBControllers->begin();
+ it != aThat->mUSBControllers->end();
+ ++it)
+ {
+ ComObjPtr<USBController> ctrl;
+ ctrl.createObject();
+ ctrl->initCopy(this, *it);
+ mUSBControllers->push_back(ctrl);
+ }
+
+ mNetworkAdapters.resize(aThat->mNetworkAdapters.size());
+ for (ULONG slot = 0; slot < mNetworkAdapters.size(); ++slot)
+ {
+ if (mNetworkAdapters[slot].isNotNull())
+ mNetworkAdapters[slot]->i_copyFrom(aThat->mNetworkAdapters[slot]);
+ else
+ {
+ unconst(mNetworkAdapters[slot]).createObject();
+ mNetworkAdapters[slot]->initCopy(this, aThat->mNetworkAdapters[slot]);
+ }
+ }
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); ++slot)
+ mSerialPorts[slot]->i_copyFrom(aThat->mSerialPorts[slot]);
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); ++slot)
+ mParallelPorts[slot]->i_copyFrom(aThat->mParallelPorts[slot]);
+}
+
+/**
+ * Returns whether the given storage controller is hotplug capable.
+ *
+ * @returns true if the controller supports hotplugging
+ * false otherwise.
+ * @param enmCtrlType The controller type to check for.
+ */
+bool Machine::i_isControllerHotplugCapable(StorageControllerType_T enmCtrlType)
+{
+ ComPtr<ISystemProperties> systemProperties;
+ HRESULT rc = mParent->COMGETTER(SystemProperties)(systemProperties.asOutParam());
+ if (FAILED(rc))
+ return false;
+
+ BOOL aHotplugCapable = FALSE;
+ systemProperties->GetStorageControllerHotplugCapable(enmCtrlType, &aHotplugCapable);
+
+ return RT_BOOL(aHotplugCapable);
+}
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+
+void Machine::i_getDiskList(MediaList &list)
+{
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ MediumAttachment *pAttach = *it;
+ /* just in case */
+ AssertContinue(pAttach);
+
+ AutoCaller localAutoCallerA(pAttach);
+ if (FAILED(localAutoCallerA.rc())) continue;
+
+ AutoReadLock local_alockA(pAttach COMMA_LOCKVAL_SRC_POS);
+
+ if (pAttach->i_getType() == DeviceType_HardDisk)
+ list.push_back(pAttach->i_getMedium());
+ }
+}
+
+void Machine::i_registerMetrics(PerformanceCollector *aCollector, Machine *aMachine, RTPROCESS pid)
+{
+ AssertReturnVoid(isWriteLockOnCurrentThread());
+ AssertPtrReturnVoid(aCollector);
+
+ pm::CollectorHAL *hal = aCollector->getHAL();
+ /* Create sub metrics */
+ pm::SubMetric *cpuLoadUser = new pm::SubMetric("CPU/Load/User",
+ "Percentage of processor time spent in user mode by the VM process.");
+ pm::SubMetric *cpuLoadKernel = new pm::SubMetric("CPU/Load/Kernel",
+ "Percentage of processor time spent in kernel mode by the VM process.");
+ pm::SubMetric *ramUsageUsed = new pm::SubMetric("RAM/Usage/Used",
+ "Size of resident portion of VM process in memory.");
+ pm::SubMetric *diskUsageUsed = new pm::SubMetric("Disk/Usage/Used",
+ "Actual size of all VM disks combined.");
+ pm::SubMetric *machineNetRx = new pm::SubMetric("Net/Rate/Rx",
+ "Network receive rate.");
+ pm::SubMetric *machineNetTx = new pm::SubMetric("Net/Rate/Tx",
+ "Network transmit rate.");
+ /* Create and register base metrics */
+ pm::BaseMetric *cpuLoad = new pm::MachineCpuLoadRaw(hal, aMachine, pid,
+ cpuLoadUser, cpuLoadKernel);
+ aCollector->registerBaseMetric(cpuLoad);
+ pm::BaseMetric *ramUsage = new pm::MachineRamUsage(hal, aMachine, pid,
+ ramUsageUsed);
+ aCollector->registerBaseMetric(ramUsage);
+ MediaList disks;
+ i_getDiskList(disks);
+ pm::BaseMetric *diskUsage = new pm::MachineDiskUsage(hal, aMachine, disks,
+ diskUsageUsed);
+ aCollector->registerBaseMetric(diskUsage);
+
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadUser, 0));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadUser,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadUser,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadUser,
+ new pm::AggregateMax()));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadKernel, 0));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadKernel,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadKernel,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadKernel,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageUsed, 0));
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageUsed,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageUsed,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageUsed,
+ new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(diskUsage, diskUsageUsed, 0));
+ aCollector->registerMetric(new pm::Metric(diskUsage, diskUsageUsed,
+ new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(diskUsage, diskUsageUsed,
+ new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(diskUsage, diskUsageUsed,
+ new pm::AggregateMax()));
+
+
+ /* Guest metrics collector */
+ mCollectorGuest = new pm::CollectorGuest(aMachine, pid);
+ aCollector->registerGuest(mCollectorGuest);
+ Log7Func(("{%p}: mCollectorGuest=%p\n", this, mCollectorGuest));
+
+ /* Create sub metrics */
+ pm::SubMetric *guestLoadUser = new pm::SubMetric("Guest/CPU/Load/User",
+ "Percentage of processor time spent in user mode as seen by the guest.");
+ pm::SubMetric *guestLoadKernel = new pm::SubMetric("Guest/CPU/Load/Kernel",
+ "Percentage of processor time spent in kernel mode as seen by the guest.");
+ pm::SubMetric *guestLoadIdle = new pm::SubMetric("Guest/CPU/Load/Idle",
+ "Percentage of processor time spent idling as seen by the guest.");
+
+ /* The total amount of physical ram is fixed now, but we'll support dynamic guest ram configurations in the future. */
+ pm::SubMetric *guestMemTotal = new pm::SubMetric("Guest/RAM/Usage/Total", "Total amount of physical guest RAM.");
+ pm::SubMetric *guestMemFree = new pm::SubMetric("Guest/RAM/Usage/Free", "Free amount of physical guest RAM.");
+ pm::SubMetric *guestMemBalloon = new pm::SubMetric("Guest/RAM/Usage/Balloon", "Amount of ballooned physical guest RAM.");
+ pm::SubMetric *guestMemShared = new pm::SubMetric("Guest/RAM/Usage/Shared", "Amount of shared physical guest RAM.");
+ pm::SubMetric *guestMemCache = new pm::SubMetric(
+ "Guest/RAM/Usage/Cache", "Total amount of guest (disk) cache memory.");
+
+ pm::SubMetric *guestPagedTotal = new pm::SubMetric(
+ "Guest/Pagefile/Usage/Total", "Total amount of space in the page file.");
+
+ /* Create and register base metrics */
+ pm::BaseMetric *machineNetRate = new pm::MachineNetRate(mCollectorGuest, aMachine,
+ machineNetRx, machineNetTx);
+ aCollector->registerBaseMetric(machineNetRate);
+
+ pm::BaseMetric *guestCpuLoad = new pm::GuestCpuLoad(mCollectorGuest, aMachine,
+ guestLoadUser, guestLoadKernel, guestLoadIdle);
+ aCollector->registerBaseMetric(guestCpuLoad);
+
+ pm::BaseMetric *guestCpuMem = new pm::GuestRamUsage(mCollectorGuest, aMachine,
+ guestMemTotal, guestMemFree,
+ guestMemBalloon, guestMemShared,
+ guestMemCache, guestPagedTotal);
+ aCollector->registerBaseMetric(guestCpuMem);
+
+ aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetRx, 0));
+ aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetRx, new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetRx, new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetRx, new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetTx, 0));
+ aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetTx, new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetTx, new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetTx, new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadUser, 0));
+ aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadUser, new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadUser, new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadUser, new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadKernel, 0));
+ aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadKernel, new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadKernel, new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadKernel, new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadIdle, 0));
+ aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadIdle, new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadIdle, new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadIdle, new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemTotal, 0));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemTotal, new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemTotal, new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemTotal, new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemFree, 0));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemFree, new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemFree, new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemFree, new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemBalloon, 0));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemBalloon, new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemBalloon, new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemBalloon, new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemShared, 0));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemShared, new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemShared, new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemShared, new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemCache, 0));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemCache, new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemCache, new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestMemCache, new pm::AggregateMax()));
+
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestPagedTotal, 0));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestPagedTotal, new pm::AggregateAvg()));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestPagedTotal, new pm::AggregateMin()));
+ aCollector->registerMetric(new pm::Metric(guestCpuMem, guestPagedTotal, new pm::AggregateMax()));
+}
+
+void Machine::i_unregisterMetrics(PerformanceCollector *aCollector, Machine *aMachine)
+{
+ AssertReturnVoid(isWriteLockOnCurrentThread());
+
+ if (aCollector)
+ {
+ aCollector->unregisterMetricsFor(aMachine);
+ aCollector->unregisterBaseMetricsFor(aMachine);
+ }
+}
+
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(SessionMachine)
+
+HRESULT SessionMachine::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+
+ mClientToken = NULL;
+
+ return BaseFinalConstruct();
+}
+
+void SessionMachine::FinalRelease()
+{
+ LogFlowThisFunc(("\n"));
+
+ Assert(!mClientToken);
+ /* paranoia, should not hang around any more */
+ if (mClientToken)
+ {
+ delete mClientToken;
+ mClientToken = NULL;
+ }
+
+ uninit(Uninit::Unexpected);
+
+ BaseFinalRelease();
+}
+
+/**
+ * @note Must be called only by Machine::LockMachine() from its own write lock.
+ */
+HRESULT SessionMachine::init(Machine *aMachine)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("mName={%s}\n", aMachine->mUserData->s.strName.c_str()));
+
+ AssertReturn(aMachine, E_INVALIDARG);
+
+ AssertReturn(aMachine->lockHandle()->isWriteLockOnCurrentThread(), E_FAIL);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT rc = S_OK;
+
+ RT_ZERO(mAuthLibCtx);
+
+ /* create the machine client token */
+ try
+ {
+ mClientToken = new ClientToken(aMachine, this);
+ if (!mClientToken->isReady())
+ {
+ delete mClientToken;
+ mClientToken = NULL;
+ rc = E_FAIL;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = E_OUTOFMEMORY;
+ }
+ if (FAILED(rc))
+ return rc;
+
+ /* memorize the peer Machine */
+ unconst(mPeer) = aMachine;
+ /* share the parent pointer */
+ unconst(mParent) = aMachine->mParent;
+
+ /* take the pointers to data to share */
+ mData.share(aMachine->mData);
+ mSSData.share(aMachine->mSSData);
+
+ mUserData.share(aMachine->mUserData);
+ mHWData.share(aMachine->mHWData);
+ mMediumAttachments.share(aMachine->mMediumAttachments);
+
+ mStorageControllers.allocate();
+ for (StorageControllerList::const_iterator
+ it = aMachine->mStorageControllers->begin();
+ it != aMachine->mStorageControllers->end();
+ ++it)
+ {
+ ComObjPtr<StorageController> ctl;
+ ctl.createObject();
+ ctl->init(this, *it);
+ mStorageControllers->push_back(ctl);
+ }
+
+ mUSBControllers.allocate();
+ for (USBControllerList::const_iterator
+ it = aMachine->mUSBControllers->begin();
+ it != aMachine->mUSBControllers->end();
+ ++it)
+ {
+ ComObjPtr<USBController> ctl;
+ ctl.createObject();
+ ctl->init(this, *it);
+ mUSBControllers->push_back(ctl);
+ }
+
+ unconst(mBIOSSettings).createObject();
+ mBIOSSettings->init(this, aMachine->mBIOSSettings);
+
+ unconst(mRecordingSettings).createObject();
+ mRecordingSettings->init(this, aMachine->mRecordingSettings);
+
+ unconst(mTrustedPlatformModule).createObject();
+ mTrustedPlatformModule->init(this, aMachine->mTrustedPlatformModule);
+
+ unconst(mNvramStore).createObject();
+ mNvramStore->init(this, aMachine->mNvramStore);
+
+ /* create another GraphicsAdapter object that will be mutable */
+ unconst(mGraphicsAdapter).createObject();
+ mGraphicsAdapter->init(this, aMachine->mGraphicsAdapter);
+ /* create another VRDEServer object that will be mutable */
+ unconst(mVRDEServer).createObject();
+ mVRDEServer->init(this, aMachine->mVRDEServer);
+ /* create another audio settings object that will be mutable */
+ unconst(mAudioSettings).createObject();
+ mAudioSettings->init(this, aMachine->mAudioSettings);
+ /* create a list of serial ports that will be mutable */
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); ++slot)
+ {
+ unconst(mSerialPorts[slot]).createObject();
+ mSerialPorts[slot]->init(this, aMachine->mSerialPorts[slot]);
+ }
+ /* create a list of parallel ports that will be mutable */
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); ++slot)
+ {
+ unconst(mParallelPorts[slot]).createObject();
+ mParallelPorts[slot]->init(this, aMachine->mParallelPorts[slot]);
+ }
+
+ /* create another USB device filters object that will be mutable */
+ unconst(mUSBDeviceFilters).createObject();
+ mUSBDeviceFilters->init(this, aMachine->mUSBDeviceFilters);
+
+ /* create a list of network adapters that will be mutable */
+ mNetworkAdapters.resize(aMachine->mNetworkAdapters.size());
+ for (ULONG slot = 0; slot < mNetworkAdapters.size(); ++slot)
+ {
+ unconst(mNetworkAdapters[slot]).createObject();
+ mNetworkAdapters[slot]->init(this, aMachine->mNetworkAdapters[slot]);
+ }
+
+ /* create another bandwidth control object that will be mutable */
+ unconst(mBandwidthControl).createObject();
+ mBandwidthControl->init(this, aMachine->mBandwidthControl);
+
+ unconst(mGuestDebugControl).createObject();
+ mGuestDebugControl->init(this, aMachine->mGuestDebugControl);
+
+ /* default is to delete saved state on Saved -> PoweredOff transition */
+ mRemoveSavedState = true;
+
+ /* Confirm a successful initialization when it's the case */
+ autoInitSpan.setSucceeded();
+
+ miNATNetworksStarted = 0;
+
+ LogFlowThisFuncLeave();
+ return rc;
+}
+
+/**
+ * Uninitializes this session object. If the reason is other than
+ * Uninit::Unexpected, then this method MUST be called from #i_checkForDeath()
+ * or the client watcher code.
+ *
+ * @param aReason uninitialization reason
+ *
+ * @note Locks mParent + this object for writing.
+ */
+void SessionMachine::uninit(Uninit::Reason aReason)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("reason=%d\n", aReason));
+
+ /*
+ * Strongly reference ourselves to prevent this object deletion after
+ * mData->mSession.mMachine.setNull() below (which can release the last
+ * reference and call the destructor). Important: this must be done before
+ * accessing any members (and before AutoUninitSpan that does it as well).
+ * This self reference will be released as the very last step on return.
+ */
+ ComObjPtr<SessionMachine> selfRef;
+ if (aReason != Uninit::Unexpected)
+ selfRef = this;
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ {
+ LogFlowThisFunc(("Already uninitialized\n"));
+ LogFlowThisFuncLeave();
+ return;
+ }
+
+ if (autoUninitSpan.initFailed())
+ {
+ /* We've been called by init() because it's failed. It's not really
+ * necessary (nor it's safe) to perform the regular uninit sequence
+ * below, the following is enough.
+ */
+ LogFlowThisFunc(("Initialization failed.\n"));
+ /* destroy the machine client token */
+ if (mClientToken)
+ {
+ delete mClientToken;
+ mClientToken = NULL;
+ }
+ uninitDataAndChildObjects();
+ mData.free();
+ unconst(mParent) = NULL;
+ unconst(mPeer) = NULL;
+ LogFlowThisFuncLeave();
+ return;
+ }
+
+ MachineState_T lastState;
+ {
+ AutoReadLock tempLock(this COMMA_LOCKVAL_SRC_POS);
+ lastState = mData->mMachineState;
+ }
+ NOREF(lastState);
+
+#ifdef VBOX_WITH_USB
+ // release all captured USB devices, but do this before requesting the locks below
+ if (aReason == Uninit::Abnormal && Global::IsOnline(lastState))
+ {
+ /* Console::captureUSBDevices() is called in the VM process only after
+ * setting the machine state to Starting or Restoring.
+ * Console::detachAllUSBDevices() will be called upon successful
+ * termination. So, we need to release USB devices only if there was
+ * an abnormal termination of a running VM.
+ *
+ * This is identical to SessionMachine::DetachAllUSBDevices except
+ * for the aAbnormal argument. */
+ HRESULT rc = mUSBDeviceFilters->i_notifyProxy(false /* aInsertFilters */);
+ AssertComRC(rc);
+ NOREF(rc);
+
+ USBProxyService *service = mParent->i_host()->i_usbProxyService();
+ if (service)
+ service->detachAllDevicesFromVM(this, true /* aDone */, true /* aAbnormal */);
+ }
+#endif /* VBOX_WITH_USB */
+
+ // we need to lock this object in uninit() because the lock is shared
+ // with mPeer (as well as data we modify below). mParent lock is needed
+ // by several calls to it.
+ AutoMultiWriteLock2 multilock(mParent, this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ /*
+ * It is safe to call Machine::i_unregisterMetrics() here because
+ * PerformanceCollector::samplerCallback no longer accesses guest methods
+ * holding the lock.
+ */
+ i_unregisterMetrics(mParent->i_performanceCollector(), mPeer);
+ /* The guest must be unregistered after its metrics (@bugref{5949}). */
+ Log7Func(("{%p}: mCollectorGuest=%p\n", this, mCollectorGuest));
+ if (mCollectorGuest)
+ {
+ mParent->i_performanceCollector()->unregisterGuest(mCollectorGuest);
+ // delete mCollectorGuest; => CollectorGuestManager::destroyUnregistered()
+ mCollectorGuest = NULL;
+ }
+#endif
+
+ if (aReason == Uninit::Abnormal)
+ {
+ Log1WarningThisFunc(("ABNORMAL client termination! (wasBusy=%d)\n", Global::IsOnlineOrTransient(lastState)));
+
+ /*
+ * Move the VM to the 'Aborted' machine state unless we are restoring a
+ * VM that was in the 'Saved' machine state. In that case, if the VM
+ * fails before reaching either the 'Restoring' machine state or the
+ * 'Running' machine state then we set the machine state to
+ * 'AbortedSaved' in order to preserve the saved state file so that the
+ * VM can be restored in the future.
+ */
+ if (mData->mMachineState == MachineState_Saved || mData->mMachineState == MachineState_Restoring)
+ i_setMachineState(MachineState_AbortedSaved);
+ else if (mData->mMachineState != MachineState_Aborted && mData->mMachineState != MachineState_AbortedSaved)
+ i_setMachineState(MachineState_Aborted);
+ }
+
+ // any machine settings modified?
+ if (mData->flModifications)
+ {
+ Log1WarningThisFunc(("Discarding unsaved settings changes!\n"));
+ i_rollback(false /* aNotify */);
+ }
+
+ mData->mSession.mPID = NIL_RTPROCESS;
+
+ if (aReason == Uninit::Unexpected)
+ {
+ /* Uninitialization didn't come from #i_checkForDeath(), so tell the
+ * client watcher thread to update the set of machines that have open
+ * sessions. */
+ mParent->i_updateClientWatcher();
+ }
+
+ /* uninitialize all remote controls */
+ if (mData->mSession.mRemoteControls.size())
+ {
+ LogFlowThisFunc(("Closing remote sessions (%d):\n",
+ mData->mSession.mRemoteControls.size()));
+
+ /* Always restart a the beginning, since the iterator is invalidated
+ * by using erase(). */
+ for (Data::Session::RemoteControlList::iterator
+ it = mData->mSession.mRemoteControls.begin();
+ it != mData->mSession.mRemoteControls.end();
+ it = mData->mSession.mRemoteControls.begin())
+ {
+ ComPtr<IInternalSessionControl> pControl = *it;
+ mData->mSession.mRemoteControls.erase(it);
+ multilock.release();
+ LogFlowThisFunc((" Calling remoteControl->Uninitialize()...\n"));
+ HRESULT rc = pControl->Uninitialize();
+ LogFlowThisFunc((" remoteControl->Uninitialize() returned %08X\n", rc));
+ if (FAILED(rc))
+ Log1WarningThisFunc(("Forgot to close the remote session?\n"));
+ multilock.acquire();
+ }
+ mData->mSession.mRemoteControls.clear();
+ }
+
+ /* Remove all references to the NAT network service. The service will stop
+ * if all references (also from other VMs) are removed. */
+ for (; miNATNetworksStarted > 0; miNATNetworksStarted--)
+ {
+ for (ULONG slot = 0; slot < mNetworkAdapters.size(); ++slot)
+ {
+ BOOL enabled;
+ HRESULT hrc = mNetworkAdapters[slot]->COMGETTER(Enabled)(&enabled);
+ if ( FAILED(hrc)
+ || !enabled)
+ continue;
+
+ NetworkAttachmentType_T type;
+ hrc = mNetworkAdapters[slot]->COMGETTER(AttachmentType)(&type);
+ if ( SUCCEEDED(hrc)
+ && type == NetworkAttachmentType_NATNetwork)
+ {
+ Bstr name;
+ hrc = mNetworkAdapters[slot]->COMGETTER(NATNetwork)(name.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ multilock.release();
+ Utf8Str strName(name);
+ LogRel(("VM '%s' stops using NAT network '%s'\n",
+ mUserData->s.strName.c_str(), strName.c_str()));
+ mParent->i_natNetworkRefDec(strName);
+ multilock.acquire();
+ }
+ }
+ }
+ }
+
+ /*
+ * An expected uninitialization can come only from #i_checkForDeath().
+ * Otherwise it means that something's gone really wrong (for example,
+ * the Session implementation has released the VirtualBox reference
+ * before it triggered #OnSessionEnd(), or before releasing IPC semaphore,
+ * etc). However, it's also possible, that the client releases the IPC
+ * semaphore correctly (i.e. before it releases the VirtualBox reference),
+ * but the VirtualBox release event comes first to the server process.
+ * This case is practically possible, so we should not assert on an
+ * unexpected uninit, just log a warning.
+ */
+
+ if (aReason == Uninit::Unexpected)
+ Log1WarningThisFunc(("Unexpected SessionMachine uninitialization!\n"));
+
+ if (aReason != Uninit::Normal)
+ {
+ mData->mSession.mDirectControl.setNull();
+ }
+ else
+ {
+ /* this must be null here (see #OnSessionEnd()) */
+ Assert(mData->mSession.mDirectControl.isNull());
+ Assert(mData->mSession.mState == SessionState_Unlocking);
+ Assert(!mData->mSession.mProgress.isNull());
+ }
+ if (mData->mSession.mProgress)
+ {
+ if (aReason == Uninit::Normal)
+ mData->mSession.mProgress->i_notifyComplete(S_OK);
+ else
+ mData->mSession.mProgress->i_notifyComplete(E_FAIL,
+ COM_IIDOF(ISession),
+ getComponentName(),
+ tr("The VM session was aborted"));
+ mData->mSession.mProgress.setNull();
+ }
+
+ if (mConsoleTaskData.mProgress)
+ {
+ Assert(aReason == Uninit::Abnormal);
+ mConsoleTaskData.mProgress->i_notifyComplete(E_FAIL,
+ COM_IIDOF(ISession),
+ getComponentName(),
+ tr("The VM session was aborted"));
+ mConsoleTaskData.mProgress.setNull();
+ }
+
+ /* remove the association between the peer machine and this session machine */
+ Assert( (SessionMachine*)mData->mSession.mMachine == this
+ || aReason == Uninit::Unexpected);
+
+ /* reset the rest of session data */
+ mData->mSession.mLockType = LockType_Null;
+ mData->mSession.mMachine.setNull();
+ mData->mSession.mState = SessionState_Unlocked;
+ mData->mSession.mName.setNull();
+
+ /* destroy the machine client token before leaving the exclusive lock */
+ if (mClientToken)
+ {
+ delete mClientToken;
+ mClientToken = NULL;
+ }
+
+ /* fire an event */
+ mParent->i_onSessionStateChanged(mData->mUuid, SessionState_Unlocked);
+
+ uninitDataAndChildObjects();
+
+ /* free the essential data structure last */
+ mData.free();
+
+ /* release the exclusive lock before setting the below two to NULL */
+ multilock.release();
+
+ unconst(mParent) = NULL;
+ unconst(mPeer) = NULL;
+
+ AuthLibUnload(&mAuthLibCtx);
+
+ LogFlowThisFuncLeave();
+}
+
+// util::Lockable interface
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
+ * with the primary Machine instance (mPeer).
+ */
+RWLockHandle *SessionMachine::lockHandle() const
+{
+ AssertReturn(mPeer != NULL, NULL);
+ return mPeer->lockHandle();
+}
+
+// IInternalMachineControl methods
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Passes collected guest statistics to performance collector object
+ */
+HRESULT SessionMachine::reportVmStatistics(ULONG aValidStats, ULONG aCpuUser,
+ ULONG aCpuKernel, ULONG aCpuIdle,
+ ULONG aMemTotal, ULONG aMemFree,
+ ULONG aMemBalloon, ULONG aMemShared,
+ ULONG aMemCache, ULONG aPageTotal,
+ ULONG aAllocVMM, ULONG aFreeVMM,
+ ULONG aBalloonedVMM, ULONG aSharedVMM,
+ ULONG aVmNetRx, ULONG aVmNetTx)
+{
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ if (mCollectorGuest)
+ mCollectorGuest->updateStats(aValidStats, aCpuUser, aCpuKernel, aCpuIdle,
+ aMemTotal, aMemFree, aMemBalloon, aMemShared,
+ aMemCache, aPageTotal, aAllocVMM, aFreeVMM,
+ aBalloonedVMM, aSharedVMM, aVmNetRx, aVmNetTx);
+
+ return S_OK;
+#else
+ NOREF(aValidStats);
+ NOREF(aCpuUser);
+ NOREF(aCpuKernel);
+ NOREF(aCpuIdle);
+ NOREF(aMemTotal);
+ NOREF(aMemFree);
+ NOREF(aMemBalloon);
+ NOREF(aMemShared);
+ NOREF(aMemCache);
+ NOREF(aPageTotal);
+ NOREF(aAllocVMM);
+ NOREF(aFreeVMM);
+ NOREF(aBalloonedVMM);
+ NOREF(aSharedVMM);
+ NOREF(aVmNetRx);
+ NOREF(aVmNetTx);
+ return E_NOTIMPL;
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// SessionMachine task records
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Task record for saving the machine state.
+ */
+class SessionMachine::SaveStateTask
+ : public Machine::Task
+{
+public:
+ SaveStateTask(SessionMachine *m,
+ Progress *p,
+ const Utf8Str &t,
+ Reason_T enmReason,
+ const Utf8Str &strStateFilePath)
+ : Task(m, p, t),
+ m_enmReason(enmReason),
+ m_strStateFilePath(strStateFilePath)
+ {}
+
+private:
+ void handler()
+ {
+ ((SessionMachine *)(Machine *)m_pMachine)->i_saveStateHandler(*this);
+ }
+
+ Reason_T m_enmReason;
+ Utf8Str m_strStateFilePath;
+
+ friend class SessionMachine;
+};
+
+/**
+ * Task thread implementation for SessionMachine::SaveState(), called from
+ * SessionMachine::taskHandler().
+ *
+ * @note Locks this object for writing.
+ *
+ * @param task
+ * @return
+ */
+void SessionMachine::i_saveStateHandler(SaveStateTask &task)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ LogFlowThisFunc(("state=%d\n", getObjectState().getState()));
+ if (FAILED(autoCaller.rc()))
+ {
+ /* we might have been uninitialized because the session was accidentally
+ * closed by the client, so don't assert */
+ HRESULT rc = setError(E_FAIL,
+ tr("The session has been accidentally closed"));
+ task.m_pProgress->i_notifyComplete(rc);
+ LogFlowThisFuncLeave();
+ return;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ try
+ {
+ ComPtr<IInternalSessionControl> directControl;
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ if (directControl.isNull())
+ throw setError(VBOX_E_INVALID_VM_STATE,
+ tr("Trying to save state without a running VM"));
+ alock.release();
+ BOOL fSuspendedBySave;
+ rc = directControl->SaveStateWithReason(task.m_enmReason, task.m_pProgress, NULL, Bstr(task.m_strStateFilePath).raw(), task.m_machineStateBackup != MachineState_Paused, &fSuspendedBySave);
+ Assert(!fSuspendedBySave);
+ alock.acquire();
+
+ AssertStmt( (SUCCEEDED(rc) && mData->mMachineState == MachineState_Saved)
+ || (FAILED(rc) && mData->mMachineState == MachineState_Saving),
+ throw E_FAIL);
+
+ if (SUCCEEDED(rc))
+ {
+ mSSData->strStateFilePath = task.m_strStateFilePath;
+
+ /* save all VM settings */
+ rc = i_saveSettings(NULL, alock);
+ // no need to check whether VirtualBox.xml needs saving also since
+ // we can't have a name change pending at this point
+ }
+ else
+ {
+ // On failure, set the state to the state we had at the beginning.
+ i_setMachineState(task.m_machineStateBackup);
+ i_updateMachineStateOnClient();
+
+ // Delete the saved state file (might have been already created).
+ // No need to check whether this is shared with a snapshot here
+ // because we certainly created a fresh saved state file here.
+ i_deleteFile(task.m_strStateFilePath, true /* fIgnoreFailures */);
+ }
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ task.m_pProgress->i_notifyComplete(rc);
+
+ LogFlowThisFuncLeave();
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+HRESULT SessionMachine::saveState(ComPtr<IProgress> &aProgress)
+{
+ return i_saveStateWithReason(Reason_Unspecified, aProgress);
+}
+
+HRESULT SessionMachine::i_saveStateWithReason(Reason_T aReason, ComPtr<IProgress> &aProgress)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrRunningStateDep);
+ if (FAILED(rc)) return rc;
+
+ if ( mData->mMachineState != MachineState_Running
+ && mData->mMachineState != MachineState_Paused
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot save the execution state as the machine is not running or paused (machine state: %s)"),
+ Global::stringifyMachineState(mData->mMachineState));
+
+ ComObjPtr<Progress> pProgress;
+ pProgress.createObject();
+ rc = pProgress->init(i_getVirtualBox(),
+ static_cast<IMachine *>(this) /* aInitiator */,
+ tr("Saving the execution state of the virtual machine"),
+ FALSE /* aCancelable */);
+ if (FAILED(rc))
+ return rc;
+
+ Utf8Str strStateFilePath;
+ i_composeSavedStateFilename(strStateFilePath);
+
+ /* create and start the task on a separate thread (note that it will not
+ * start working until we release alock) */
+ SaveStateTask *pTask = new SaveStateTask(this, pProgress, "SaveState", aReason, strStateFilePath);
+ rc = pTask->createThread();
+ if (FAILED(rc))
+ return rc;
+
+ /* set the state to Saving (expected by Session::SaveStateWithReason()) */
+ i_setMachineState(MachineState_Saving);
+ i_updateMachineStateOnClient();
+
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+
+ return S_OK;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+HRESULT SessionMachine::adoptSavedState(const com::Utf8Str &aSavedStateFile)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableStateDep);
+ if (FAILED(rc)) return rc;
+
+ if ( mData->mMachineState != MachineState_PoweredOff
+ && mData->mMachineState != MachineState_Teleported
+ && mData->mMachineState != MachineState_Aborted
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot adopt the saved machine state as the machine is not in Powered Off, Teleported or Aborted state (machine state: %s)"),
+ Global::stringifyMachineState(mData->mMachineState));
+
+ com::Utf8Str stateFilePathFull;
+ int vrc = i_calculateFullPath(aSavedStateFile, stateFilePathFull);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Invalid saved state file path '%s' (%Rrc)"),
+ aSavedStateFile.c_str(),
+ vrc);
+
+ mSSData->strStateFilePath = stateFilePathFull;
+
+ /* The below i_setMachineState() will detect the state transition and will
+ * update the settings file */
+
+ return i_setMachineState(MachineState_Saved);
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+HRESULT SessionMachine::discardSavedState(BOOL aFRemoveFile)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = i_checkStateDependency(MutableOrSavedStateDep);
+ if (FAILED(rc)) return rc;
+
+ if ( mData->mMachineState != MachineState_Saved
+ && mData->mMachineState != MachineState_AbortedSaved)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot discard the saved state as the machine is not in the Saved or Aborted-Saved state (machine state: %s)"),
+ Global::stringifyMachineState(mData->mMachineState));
+
+ mRemoveSavedState = RT_BOOL(aFRemoveFile);
+
+ /*
+ * Saved -> PoweredOff transition will be detected in the SessionMachine
+ * and properly handled.
+ */
+ rc = i_setMachineState(MachineState_PoweredOff);
+ return rc;
+}
+
+
+/**
+ * @note Locks the same as #i_setMachineState() does.
+ */
+HRESULT SessionMachine::updateState(MachineState_T aState)
+{
+ return i_setMachineState(aState);
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+HRESULT SessionMachine::beginPowerUp(const ComPtr<IProgress> &aProgress)
+{
+ IProgress *pProgress(aProgress);
+
+ LogFlowThisFunc(("aProgress=%p\n", pProgress));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->mSession.mState != SessionState_Locked)
+ return VBOX_E_INVALID_OBJECT_STATE;
+
+ if (!mData->mSession.mProgress.isNull())
+ mData->mSession.mProgress->setOtherProgressObject(pProgress);
+
+ /* If we didn't reference the NAT network service yet, add a reference to
+ * force a start */
+ if (miNATNetworksStarted < 1)
+ {
+ for (ULONG slot = 0; slot < mNetworkAdapters.size(); ++slot)
+ {
+ BOOL enabled;
+ HRESULT hrc = mNetworkAdapters[slot]->COMGETTER(Enabled)(&enabled);
+ if ( FAILED(hrc)
+ || !enabled)
+ continue;
+
+ NetworkAttachmentType_T type;
+ hrc = mNetworkAdapters[slot]->COMGETTER(AttachmentType)(&type);
+ if ( SUCCEEDED(hrc)
+ && type == NetworkAttachmentType_NATNetwork)
+ {
+ Bstr name;
+ hrc = mNetworkAdapters[slot]->COMGETTER(NATNetwork)(name.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Utf8Str strName(name);
+ LogRel(("VM '%s' starts using NAT network '%s'\n",
+ mUserData->s.strName.c_str(), strName.c_str()));
+ mPeer->lockHandle()->unlockWrite();
+ mParent->i_natNetworkRefInc(strName);
+#ifdef RT_LOCK_STRICT
+ mPeer->lockHandle()->lockWrite(RT_SRC_POS);
+#else
+ mPeer->lockHandle()->lockWrite();
+#endif
+ }
+ }
+ }
+ miNATNetworksStarted++;
+ }
+
+ LogFlowThisFunc(("returns S_OK.\n"));
+ return S_OK;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+HRESULT SessionMachine::endPowerUp(LONG aResult)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->mSession.mState != SessionState_Locked)
+ return VBOX_E_INVALID_OBJECT_STATE;
+
+ /* Finalize the LaunchVMProcess progress object. */
+ if (mData->mSession.mProgress)
+ {
+ mData->mSession.mProgress->notifyComplete((HRESULT)aResult);
+ mData->mSession.mProgress.setNull();
+ }
+
+ if (SUCCEEDED((HRESULT)aResult))
+ {
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ /* The VM has been powered up successfully, so it makes sense
+ * now to offer the performance metrics for a running machine
+ * object. Doing it earlier wouldn't be safe. */
+ i_registerMetrics(mParent->i_performanceCollector(), mPeer,
+ mData->mSession.mPID);
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+ }
+
+ return S_OK;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+HRESULT SessionMachine::beginPoweringDown(ComPtr<IProgress> &aProgress)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(mConsoleTaskData.mLastState == MachineState_Null,
+ E_FAIL);
+
+ /* create a progress object to track operation completion */
+ ComObjPtr<Progress> pProgress;
+ pProgress.createObject();
+ pProgress->init(i_getVirtualBox(),
+ static_cast<IMachine *>(this) /* aInitiator */,
+ tr("Stopping the virtual machine"),
+ FALSE /* aCancelable */);
+
+ /* fill in the console task data */
+ mConsoleTaskData.mLastState = mData->mMachineState;
+ mConsoleTaskData.mProgress = pProgress;
+
+ /* set the state to Stopping (this is expected by Console::PowerDown()) */
+ i_setMachineState(MachineState_Stopping);
+
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+
+ return S_OK;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+HRESULT SessionMachine::endPoweringDown(LONG aResult,
+ const com::Utf8Str &aErrMsg)
+{
+ HRESULT const hrcResult = (HRESULT)aResult;
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn( ( (SUCCEEDED(hrcResult) && mData->mMachineState == MachineState_PoweredOff)
+ || (FAILED(hrcResult) && mData->mMachineState == MachineState_Stopping))
+ && mConsoleTaskData.mLastState != MachineState_Null,
+ E_FAIL);
+
+ /*
+ * On failure, set the state to the state we had when BeginPoweringDown()
+ * was called (this is expected by Console::PowerDown() and the associated
+ * task). On success the VM process already changed the state to
+ * MachineState_PoweredOff, so no need to do anything.
+ */
+ if (FAILED(hrcResult))
+ i_setMachineState(mConsoleTaskData.mLastState);
+
+ /* notify the progress object about operation completion */
+ Assert(mConsoleTaskData.mProgress);
+ if (SUCCEEDED(hrcResult))
+ mConsoleTaskData.mProgress->i_notifyComplete(S_OK);
+ else
+ {
+ if (aErrMsg.length())
+ mConsoleTaskData.mProgress->i_notifyComplete(hrcResult,
+ COM_IIDOF(ISession),
+ getComponentName(),
+ aErrMsg.c_str());
+ else
+ mConsoleTaskData.mProgress->i_notifyComplete(hrcResult);
+ }
+
+ /* clear out the temporary saved state data */
+ mConsoleTaskData.mLastState = MachineState_Null;
+ mConsoleTaskData.mProgress.setNull();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+
+/**
+ * Goes through the USB filters of the given machine to see if the given
+ * device matches any filter or not.
+ *
+ * @note Locks the same as USBController::hasMatchingFilter() does.
+ */
+HRESULT SessionMachine::runUSBDeviceFilters(const ComPtr<IUSBDevice> &aDevice,
+ BOOL *aMatched,
+ ULONG *aMaskedInterfaces)
+{
+ LogFlowThisFunc(("\n"));
+
+#ifdef VBOX_WITH_USB
+ *aMatched = mUSBDeviceFilters->i_hasMatchingFilter(aDevice, aMaskedInterfaces);
+#else
+ NOREF(aDevice);
+ NOREF(aMaskedInterfaces);
+ *aMatched = FALSE;
+#endif
+
+ return S_OK;
+}
+
+/**
+ * @note Locks the same as Host::captureUSBDevice() does.
+ */
+HRESULT SessionMachine::captureUSBDevice(const com::Guid &aId, const com::Utf8Str &aCaptureFilename)
+{
+ LogFlowThisFunc(("\n"));
+
+#ifdef VBOX_WITH_USB
+ /* if captureDeviceForVM() fails, it must have set extended error info */
+ clearError();
+ MultiResult rc = mParent->i_host()->i_checkUSBProxyService();
+ if (FAILED(rc) || SUCCEEDED_WARNING(rc))
+ return rc;
+
+ USBProxyService *service = mParent->i_host()->i_usbProxyService();
+ AssertReturn(service, E_FAIL);
+ return service->captureDeviceForVM(this, aId.ref(), aCaptureFilename);
+#else
+ RT_NOREF(aId, aCaptureFilename);
+ return E_NOTIMPL;
+#endif
+}
+
+/**
+ * @note Locks the same as Host::detachUSBDevice() does.
+ */
+HRESULT SessionMachine::detachUSBDevice(const com::Guid &aId,
+ BOOL aDone)
+{
+ LogFlowThisFunc(("\n"));
+
+#ifdef VBOX_WITH_USB
+ USBProxyService *service = mParent->i_host()->i_usbProxyService();
+ AssertReturn(service, E_FAIL);
+ return service->detachDeviceFromVM(this, aId.ref(), !!aDone);
+#else
+ NOREF(aId);
+ NOREF(aDone);
+ return E_NOTIMPL;
+#endif
+}
+
+/**
+ * Inserts all machine filters to the USB proxy service and then calls
+ * Host::autoCaptureUSBDevices().
+ *
+ * Called by Console from the VM process upon VM startup.
+ *
+ * @note Locks what called methods lock.
+ */
+HRESULT SessionMachine::autoCaptureUSBDevices()
+{
+ LogFlowThisFunc(("\n"));
+
+#ifdef VBOX_WITH_USB
+ HRESULT rc = mUSBDeviceFilters->i_notifyProxy(true /* aInsertFilters */);
+ AssertComRC(rc);
+ NOREF(rc);
+
+ USBProxyService *service = mParent->i_host()->i_usbProxyService();
+ AssertReturn(service, E_FAIL);
+ return service->autoCaptureDevicesForVM(this);
+#else
+ return S_OK;
+#endif
+}
+
+/**
+ * Removes all machine filters from the USB proxy service and then calls
+ * Host::detachAllUSBDevices().
+ *
+ * Called by Console from the VM process upon normal VM termination or by
+ * SessionMachine::uninit() upon abnormal VM termination (from under the
+ * Machine/SessionMachine lock).
+ *
+ * @note Locks what called methods lock.
+ */
+HRESULT SessionMachine::detachAllUSBDevices(BOOL aDone)
+{
+ LogFlowThisFunc(("\n"));
+
+#ifdef VBOX_WITH_USB
+ HRESULT rc = mUSBDeviceFilters->i_notifyProxy(false /* aInsertFilters */);
+ AssertComRC(rc);
+ NOREF(rc);
+
+ USBProxyService *service = mParent->i_host()->i_usbProxyService();
+ AssertReturn(service, E_FAIL);
+ return service->detachAllDevicesFromVM(this, !!aDone, false /* aAbnormal */);
+#else
+ NOREF(aDone);
+ return S_OK;
+#endif
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+HRESULT SessionMachine::onSessionEnd(const ComPtr<ISession> &aSession,
+ ComPtr<IProgress> &aProgress)
+{
+ LogFlowThisFuncEnter();
+
+ LogFlowThisFunc(("callerstate=%d\n", getObjectState().getState()));
+ /*
+ * We don't assert below because it might happen that a non-direct session
+ * informs us it is closed right after we've been uninitialized -- it's ok.
+ */
+
+ /* get IInternalSessionControl interface */
+ ComPtr<IInternalSessionControl> control(aSession);
+
+ ComAssertRet(!control.isNull(), E_INVALIDARG);
+
+ /* Creating a Progress object requires the VirtualBox lock, and
+ * thus locking it here is required by the lock order rules. */
+ AutoMultiWriteLock2 alock(mParent, this COMMA_LOCKVAL_SRC_POS);
+
+ if (control == mData->mSession.mDirectControl)
+ {
+ /* The direct session is being normally closed by the client process
+ * ----------------------------------------------------------------- */
+
+ /* go to the closing state (essential for all open*Session() calls and
+ * for #i_checkForDeath()) */
+ Assert(mData->mSession.mState == SessionState_Locked);
+ mData->mSession.mState = SessionState_Unlocking;
+
+ /* set direct control to NULL to release the remote instance */
+ mData->mSession.mDirectControl.setNull();
+ LogFlowThisFunc(("Direct control is set to NULL\n"));
+
+ if (mData->mSession.mProgress)
+ {
+ /* finalize the progress, someone might wait if a frontend
+ * closes the session before powering on the VM. */
+ mData->mSession.mProgress->notifyComplete(E_FAIL,
+ COM_IIDOF(ISession),
+ getComponentName(),
+ tr("The VM session was closed before any attempt to power it on"));
+ mData->mSession.mProgress.setNull();
+ }
+
+ /* Create the progress object the client will use to wait until
+ * #i_checkForDeath() is called to uninitialize this session object after
+ * it releases the IPC semaphore.
+ * Note! Because we're "reusing" mProgress here, this must be a proxy
+ * object just like for LaunchVMProcess. */
+ Assert(mData->mSession.mProgress.isNull());
+ ComObjPtr<ProgressProxy> progress;
+ progress.createObject();
+ ComPtr<IUnknown> pPeer(mPeer);
+ progress->init(mParent, pPeer,
+ Bstr(tr("Closing session")).raw(),
+ FALSE /* aCancelable */);
+ progress.queryInterfaceTo(aProgress.asOutParam());
+ mData->mSession.mProgress = progress;
+ }
+ else
+ {
+ /* the remote session is being normally closed */
+ bool found = false;
+ for (Data::Session::RemoteControlList::iterator
+ it = mData->mSession.mRemoteControls.begin();
+ it != mData->mSession.mRemoteControls.end();
+ ++it)
+ {
+ if (control == *it)
+ {
+ found = true;
+ // This MUST be erase(it), not remove(*it) as the latter
+ // triggers a very nasty use after free due to the place where
+ // the value "lives".
+ mData->mSession.mRemoteControls.erase(it);
+ break;
+ }
+ }
+ ComAssertMsgRet(found, (tr("The session is not found in the session list!")),
+ E_INVALIDARG);
+ }
+
+ /* signal the client watcher thread, because the client is going away */
+ mParent->i_updateClientWatcher();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT SessionMachine::pullGuestProperties(std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<com::Utf8Str> &aFlags)
+{
+ LogFlowThisFunc(("\n"));
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ size_t cEntries = mHWData->mGuestProperties.size();
+ aNames.resize(cEntries);
+ aValues.resize(cEntries);
+ aTimestamps.resize(cEntries);
+ aFlags.resize(cEntries);
+
+ size_t i = 0;
+ for (HWData::GuestPropertyMap::const_iterator
+ it = mHWData->mGuestProperties.begin();
+ it != mHWData->mGuestProperties.end();
+ ++it, ++i)
+ {
+ aNames[i] = it->first;
+ int vrc = GuestPropValidateName(aNames[i].c_str(), aNames[i].length() + 1 /* '\0' */);
+ AssertRCReturn(vrc, setErrorBoth(E_INVALIDARG /* bad choice */, vrc));
+
+ aValues[i] = it->second.strValue;
+ vrc = GuestPropValidateValue(aValues[i].c_str(), aValues[i].length() + 1 /* '\0' */);
+ AssertRCReturn(vrc, setErrorBoth(E_INVALIDARG /* bad choice */, vrc));
+
+ aTimestamps[i] = it->second.mTimestamp;
+
+ /* If it is NULL, keep it NULL. */
+ if (it->second.mFlags)
+ {
+ char szFlags[GUEST_PROP_MAX_FLAGS_LEN + 1];
+ GuestPropWriteFlags(it->second.mFlags, szFlags);
+ aFlags[i] = szFlags;
+ }
+ else
+ aFlags[i] = "";
+ }
+ return S_OK;
+#else
+ ReturnComNotImplemented();
+#endif
+}
+
+HRESULT SessionMachine::pushGuestProperty(const com::Utf8Str &aName,
+ const com::Utf8Str &aValue,
+ LONG64 aTimestamp,
+ const com::Utf8Str &aFlags,
+ BOOL fWasDeleted)
+{
+ LogFlowThisFunc(("\n"));
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ try
+ {
+ /*
+ * Convert input up front.
+ */
+ uint32_t fFlags = GUEST_PROP_F_NILFLAG;
+ if (aFlags.length())
+ {
+ int vrc = GuestPropValidateFlags(aFlags.c_str(), &fFlags);
+ AssertRCReturn(vrc, E_INVALIDARG);
+ }
+
+ /*
+ * Now grab the object lock, validate the state and do the update.
+ */
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!Global::IsOnline(mData->mMachineState))
+ AssertMsgFailedReturn(("%s\n", ::stringifyMachineState(mData->mMachineState)), VBOX_E_INVALID_VM_STATE);
+
+ i_setModified(IsModified_MachineData);
+ mHWData.backup();
+
+ HWData::GuestPropertyMap::iterator it = mHWData->mGuestProperties.find(aName);
+ if (it != mHWData->mGuestProperties.end())
+ {
+ if (!fWasDeleted)
+ {
+ it->second.strValue = aValue;
+ it->second.mTimestamp = aTimestamp;
+ it->second.mFlags = fFlags;
+ }
+ else
+ mHWData->mGuestProperties.erase(it);
+
+ mData->mGuestPropertiesModified = TRUE;
+ }
+ else if (!fWasDeleted)
+ {
+ HWData::GuestProperty prop;
+ prop.strValue = aValue;
+ prop.mTimestamp = aTimestamp;
+ prop.mFlags = fFlags;
+
+ mHWData->mGuestProperties[aName] = prop;
+ mData->mGuestPropertiesModified = TRUE;
+ }
+
+ alock.release();
+
+ mParent->i_onGuestPropertyChanged(mData->mUuid, aName, aValue, aFlags, fWasDeleted);
+ }
+ catch (...)
+ {
+ return VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS);
+ }
+ return S_OK;
+#else
+ ReturnComNotImplemented();
+#endif
+}
+
+
+HRESULT SessionMachine::lockMedia()
+{
+ AutoMultiWriteLock2 alock(this->lockHandle(),
+ &mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn( mData->mMachineState == MachineState_Starting
+ || mData->mMachineState == MachineState_Restoring
+ || mData->mMachineState == MachineState_TeleportingIn, E_FAIL);
+
+ clearError();
+ alock.release();
+ return i_lockMedia();
+}
+
+HRESULT SessionMachine::unlockMedia()
+{
+ HRESULT hrc = i_unlockMedia();
+ return hrc;
+}
+
+HRESULT SessionMachine::ejectMedium(const ComPtr<IMediumAttachment> &aAttachment,
+ ComPtr<IMediumAttachment> &aNewAttachment)
+{
+ // request the host lock first, since might be calling Host methods for getting host drives;
+ // next, protect the media tree all the while we're in here, as well as our member variables
+ AutoMultiWriteLock3 multiLock(mParent->i_host()->lockHandle(),
+ this->lockHandle(),
+ &mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ IMediumAttachment *iAttach = aAttachment;
+ ComObjPtr<MediumAttachment> pAttach = static_cast<MediumAttachment *>(iAttach);
+
+ Utf8Str ctrlName;
+ LONG lPort;
+ LONG lDevice;
+ bool fTempEject;
+ {
+ AutoReadLock attLock(pAttach COMMA_LOCKVAL_SRC_POS);
+
+ /* Need to query the details first, as the IMediumAttachment reference
+ * might be to the original settings, which we are going to change. */
+ ctrlName = pAttach->i_getControllerName();
+ lPort = pAttach->i_getPort();
+ lDevice = pAttach->i_getDevice();
+ fTempEject = pAttach->i_getTempEject();
+ }
+
+ if (!fTempEject)
+ {
+ /* Remember previously mounted medium. The medium before taking the
+ * backup is not necessarily the same thing. */
+ ComObjPtr<Medium> oldmedium;
+ oldmedium = pAttach->i_getMedium();
+
+ i_setModified(IsModified_Storage);
+ mMediumAttachments.backup();
+
+ // The backup operation makes the pAttach reference point to the
+ // old settings. Re-get the correct reference.
+ pAttach = i_findAttachment(*mMediumAttachments.data(),
+ ctrlName,
+ lPort,
+ lDevice);
+
+ {
+ AutoCaller autoAttachCaller(this);
+ if (FAILED(autoAttachCaller.rc())) return autoAttachCaller.rc();
+
+ AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS);
+ if (!oldmedium.isNull())
+ oldmedium->i_removeBackReference(mData->mUuid);
+
+ pAttach->i_updateMedium(NULL);
+ pAttach->i_updateEjected();
+ }
+
+ i_setModified(IsModified_Storage);
+ }
+ else
+ {
+ {
+ AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS);
+ pAttach->i_updateEjected();
+ }
+ }
+
+ pAttach.queryInterfaceTo(aNewAttachment.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT SessionMachine::authenticateExternal(const std::vector<com::Utf8Str> &aAuthParams,
+ com::Utf8Str &aResult)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hr = S_OK;
+
+ if (!mAuthLibCtx.hAuthLibrary)
+ {
+ /* Load the external authentication library. */
+ Bstr authLibrary;
+ mVRDEServer->COMGETTER(AuthLibrary)(authLibrary.asOutParam());
+
+ Utf8Str filename = authLibrary;
+
+ int vrc = AuthLibLoad(&mAuthLibCtx, filename.c_str());
+ if (RT_FAILURE(vrc))
+ hr = setErrorBoth(E_FAIL, vrc,
+ tr("Could not load the external authentication library '%s' (%Rrc)"),
+ filename.c_str(), vrc);
+ }
+
+ /* The auth library might need the machine lock. */
+ alock.release();
+
+ if (FAILED(hr))
+ return hr;
+
+ if (aAuthParams[0] == "VRDEAUTH" && aAuthParams.size() == 7)
+ {
+ enum VRDEAuthParams
+ {
+ parmUuid = 1,
+ parmGuestJudgement,
+ parmUser,
+ parmPassword,
+ parmDomain,
+ parmClientId
+ };
+
+ AuthResult result = AuthResultAccessDenied;
+
+ Guid uuid(aAuthParams[parmUuid]);
+ AuthGuestJudgement guestJudgement = (AuthGuestJudgement)aAuthParams[parmGuestJudgement].toUInt32();
+ uint32_t u32ClientId = aAuthParams[parmClientId].toUInt32();
+
+ result = AuthLibAuthenticate(&mAuthLibCtx,
+ uuid.raw(), guestJudgement,
+ aAuthParams[parmUser].c_str(),
+ aAuthParams[parmPassword].c_str(),
+ aAuthParams[parmDomain].c_str(),
+ u32ClientId);
+
+ /* Hack: aAuthParams[parmPassword] is const but the code believes in writable memory. */
+ size_t cbPassword = aAuthParams[parmPassword].length();
+ if (cbPassword)
+ {
+ RTMemWipeThoroughly((void *)aAuthParams[parmPassword].c_str(), cbPassword, 10 /* cPasses */);
+ memset((void *)aAuthParams[parmPassword].c_str(), 'x', cbPassword);
+ }
+
+ if (result == AuthResultAccessGranted)
+ aResult = "granted";
+ else
+ aResult = "denied";
+
+ LogRel(("AUTH: VRDE authentification for user '%s' result '%s'\n",
+ aAuthParams[parmUser].c_str(), aResult.c_str()));
+ }
+ else if (aAuthParams[0] == "VRDEAUTHDISCONNECT" && aAuthParams.size() == 3)
+ {
+ enum VRDEAuthDisconnectParams
+ {
+ parmUuid = 1,
+ parmClientId
+ };
+
+ Guid uuid(aAuthParams[parmUuid]);
+ uint32_t u32ClientId = 0;
+ AuthLibDisconnect(&mAuthLibCtx, uuid.raw(), u32ClientId);
+ }
+ else
+ {
+ hr = E_INVALIDARG;
+ }
+
+ return hr;
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+/**
+ * Called from the client watcher thread to check for expected or unexpected
+ * death of the client process that has a direct session to this machine.
+ *
+ * On Win32 and on OS/2, this method is called only when we've got the
+ * mutex (i.e. the client has either died or terminated normally) so it always
+ * returns @c true (the client is terminated, the session machine is
+ * uninitialized).
+ *
+ * On other platforms, the method returns @c true if the client process has
+ * terminated normally or abnormally and the session machine was uninitialized,
+ * and @c false if the client process is still alive.
+ *
+ * @note Locks this object for writing.
+ */
+bool SessionMachine::i_checkForDeath()
+{
+ Uninit::Reason reason;
+ bool terminated = false;
+
+ /* Enclose autoCaller with a block because calling uninit() from under it
+ * will deadlock. */
+ {
+ AutoCaller autoCaller(this);
+ if (!autoCaller.isOk())
+ {
+ /* return true if not ready, to cause the client watcher to exclude
+ * the corresponding session from watching */
+ LogFlowThisFunc(("Already uninitialized!\n"));
+ return true;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Determine the reason of death: if the session state is Closing here,
+ * everything is fine. Otherwise it means that the client did not call
+ * OnSessionEnd() before it released the IPC semaphore. This may happen
+ * either because the client process has abnormally terminated, or
+ * because it simply forgot to call ISession::Close() before exiting. We
+ * threat the latter also as an abnormal termination (see
+ * Session::uninit() for details). */
+ reason = mData->mSession.mState == SessionState_Unlocking ?
+ Uninit::Normal :
+ Uninit::Abnormal;
+
+ if (mClientToken)
+ terminated = mClientToken->release();
+ } /* AutoCaller block */
+
+ if (terminated)
+ uninit(reason);
+
+ return terminated;
+}
+
+void SessionMachine::i_getTokenId(Utf8Str &strTokenId)
+{
+ LogFlowThisFunc(("\n"));
+
+ strTokenId.setNull();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ Assert(mClientToken);
+ if (mClientToken)
+ mClientToken->getId(strTokenId);
+}
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+IToken *SessionMachine::i_getToken()
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), NULL);
+
+ Assert(mClientToken);
+ if (mClientToken)
+ return mClientToken->getToken();
+ else
+ return NULL;
+}
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+
+Machine::ClientToken *SessionMachine::i_getClientToken()
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), NULL);
+
+ return mClientToken;
+}
+
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onNetworkAdapterChange(INetworkAdapter *networkAdapter, BOOL changeAdapter)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnNetworkAdapterChange(networkAdapter, changeAdapter);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onNATRedirectRuleChanged(ULONG ulSlot, BOOL aNatRuleRemove, const Utf8Str &aRuleName,
+ NATProtocol_T aProto, const Utf8Str &aHostIp, LONG aHostPort,
+ const Utf8Str &aGuestIp, LONG aGuestPort)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+ /*
+ * instead acting like callback we ask IVirtualBox deliver corresponding event
+ */
+
+ mParent->i_onNatRedirectChanged(i_getId(), ulSlot, RT_BOOL(aNatRuleRemove), aRuleName, aProto, aHostIp,
+ (uint16_t)aHostPort, aGuestIp, (uint16_t)aGuestPort);
+ return S_OK;
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onAudioAdapterChange(IAudioAdapter *audioAdapter)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnAudioAdapterChange(audioAdapter);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onHostAudioDeviceChange(IHostAudioDevice *aDevice, BOOL aNew, AudioDeviceState_T aState, IVirtualBoxErrorInfo *aErrInfo)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnHostAudioDeviceChange(aDevice, aNew, aState, aErrInfo);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onSerialPortChange(ISerialPort *serialPort)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnSerialPortChange(serialPort);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onParallelPortChange(IParallelPort *parallelPort)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnParallelPortChange(parallelPort);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onStorageControllerChange(const Guid &aMachineId, const Utf8Str &aControllerName)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ mParent->i_onStorageControllerChanged(aMachineId, aControllerName);
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnStorageControllerChange(Bstr(aMachineId.toString()).raw(), Bstr(aControllerName).raw());
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onMediumChange(IMediumAttachment *aAttachment, BOOL aForce)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ mParent->i_onMediumChanged(aAttachment);
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnMediumChange(aAttachment, aForce);
+}
+
+HRESULT SessionMachine::i_onVMProcessPriorityChange(VMProcPriority_T aPriority)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnVMProcessPriorityChange(aPriority);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onCPUChange(ULONG aCPU, BOOL aRemove)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnCPUChange(aCPU, aRemove);
+}
+
+HRESULT SessionMachine::i_onCPUExecutionCapChange(ULONG aExecutionCap)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnCPUExecutionCapChange(aExecutionCap);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onVRDEServerChange(BOOL aRestart)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnVRDEServerChange(aRestart);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onRecordingChange(BOOL aEnable)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnRecordingChange(aEnable);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onUSBControllerChange()
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnUSBControllerChange();
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onSharedFolderChange()
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnSharedFolderChange(FALSE /* aGlobal */);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onClipboardModeChange(ClipboardMode_T aClipboardMode)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnClipboardModeChange(aClipboardMode);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onClipboardFileTransferModeChange(BOOL aEnable)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnClipboardFileTransferModeChange(aEnable);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onDnDModeChange(DnDMode_T aDnDMode)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnDnDModeChange(aDnDMode);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onBandwidthGroupChange(IBandwidthGroup *aBandwidthGroup)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnBandwidthGroupChange(aBandwidthGroup);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onStorageDeviceChange(IMediumAttachment *aAttachment, BOOL aRemove, BOOL aSilent)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnStorageDeviceChange(aAttachment, aRemove, aSilent);
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT SessionMachine::i_onGuestDebugControlChange(IGuestDebugControl *guestDebugControl)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->OnGuestDebugControlChange(guestDebugControl);
+}
+
+/**
+ * Returns @c true if this machine's USB controller reports it has a matching
+ * filter for the given USB device and @c false otherwise.
+ *
+ * @note locks this object for reading.
+ */
+bool SessionMachine::i_hasMatchingUSBFilter(const ComObjPtr<HostUSBDevice> &aDevice, ULONG *aMaskedIfs)
+{
+ AutoCaller autoCaller(this);
+ /* silently return if not ready -- this method may be called after the
+ * direct machine session has been called */
+ if (!autoCaller.isOk())
+ return false;
+
+#ifdef VBOX_WITH_USB
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ switch (mData->mMachineState)
+ {
+ case MachineState_Starting:
+ case MachineState_Restoring:
+ case MachineState_TeleportingIn:
+ case MachineState_Paused:
+ case MachineState_Running:
+ /** @todo Live Migration: snapshoting & teleporting. Need to fend things of
+ * elsewhere... */
+ alock.release();
+ return mUSBDeviceFilters->i_hasMatchingFilter(aDevice, aMaskedIfs);
+ default: break;
+ }
+#else
+ NOREF(aDevice);
+ NOREF(aMaskedIfs);
+#endif
+ return false;
+}
+
+/**
+ * @note The calls shall hold no locks. Will temporarily lock this object for reading.
+ */
+HRESULT SessionMachine::i_onUSBDeviceAttach(IUSBDevice *aDevice,
+ IVirtualBoxErrorInfo *aError,
+ ULONG aMaskedIfs,
+ const com::Utf8Str &aCaptureFilename)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+
+ /* This notification may happen after the machine object has been
+ * uninitialized (the session was closed), so don't assert. */
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* fail on notifications sent after #OnSessionEnd() is called, it is
+ * expected by the caller */
+ if (!directControl)
+ return E_FAIL;
+
+ /* No locks should be held at this point. */
+ AssertMsg(RTLockValidatorWriteLockGetCount(RTThreadSelf()) == 0, ("%d\n", RTLockValidatorWriteLockGetCount(RTThreadSelf())));
+ AssertMsg(RTLockValidatorReadLockGetCount(RTThreadSelf()) == 0, ("%d\n", RTLockValidatorReadLockGetCount(RTThreadSelf())));
+
+ return directControl->OnUSBDeviceAttach(aDevice, aError, aMaskedIfs, Bstr(aCaptureFilename).raw());
+}
+
+/**
+ * @note The calls shall hold no locks. Will temporarily lock this object for reading.
+ */
+HRESULT SessionMachine::i_onUSBDeviceDetach(IN_BSTR aId,
+ IVirtualBoxErrorInfo *aError)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+
+ /* This notification may happen after the machine object has been
+ * uninitialized (the session was closed), so don't assert. */
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ /* fail on notifications sent after #OnSessionEnd() is called, it is
+ * expected by the caller */
+ if (!directControl)
+ return E_FAIL;
+
+ /* No locks should be held at this point. */
+ AssertMsg(RTLockValidatorWriteLockGetCount(RTThreadSelf()) == 0, ("%d\n", RTLockValidatorWriteLockGetCount(RTThreadSelf())));
+ AssertMsg(RTLockValidatorReadLockGetCount(RTThreadSelf()) == 0, ("%d\n", RTLockValidatorReadLockGetCount(RTThreadSelf())));
+
+ return directControl->OnUSBDeviceDetach(aId, aError);
+}
+
+// protected methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Deletes the given file if it is no longer in use by either the current machine state
+ * (if the machine is "saved") or any of the machine's snapshots.
+ *
+ * Note: This checks mSSData->strStateFilePath, which is shared by the Machine and SessionMachine
+ * but is different for each SnapshotMachine. When calling this, the order of calling this
+ * function on the one hand and changing that variable OR the snapshots tree on the other hand
+ * is therefore critical. I know, it's all rather messy.
+ *
+ * @param strStateFile
+ * @param pSnapshotToIgnore Passed to Snapshot::sharesSavedStateFile(); this snapshot is ignored in
+ * the test for whether the saved state file is in use.
+ */
+void SessionMachine::i_releaseSavedStateFile(const Utf8Str &strStateFile,
+ Snapshot *pSnapshotToIgnore)
+{
+ // it is safe to delete this saved state file if it is not currently in use by the machine ...
+ if ( (strStateFile.isNotEmpty())
+ && (strStateFile != mSSData->strStateFilePath) // session machine's saved state
+ )
+ // ... and it must also not be shared with other snapshots
+ if ( !mData->mFirstSnapshot
+ || !mData->mFirstSnapshot->i_sharesSavedStateFile(strStateFile, pSnapshotToIgnore)
+ // this checks the SnapshotMachine's state file paths
+ )
+ i_deleteFile(strStateFile, true /* fIgnoreFailures */);
+}
+
+/**
+ * Locks the attached media.
+ *
+ * All attached hard disks are locked for writing and DVD/floppy are locked for
+ * reading. Parents of attached hard disks (if any) are locked for reading.
+ *
+ * This method also performs accessibility check of all media it locks: if some
+ * media is inaccessible, the method will return a failure and a bunch of
+ * extended error info objects per each inaccessible medium.
+ *
+ * Note that this method is atomic: if it returns a success, all media are
+ * locked as described above; on failure no media is locked at all (all
+ * succeeded individual locks will be undone).
+ *
+ * The caller is responsible for doing the necessary state sanity checks.
+ *
+ * The locks made by this method must be undone by calling #unlockMedia() when
+ * no more needed.
+ */
+HRESULT SessionMachine::i_lockMedia()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ AutoMultiWriteLock2 alock(this->lockHandle(),
+ &mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ /* bail out if trying to lock things with already set up locking */
+ AssertReturn(mData->mSession.mLockedMedia.IsEmpty(), E_FAIL);
+
+ MultiResult mrc(S_OK);
+
+ /* Collect locking information for all medium objects attached to the VM. */
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ MediumAttachment *pAtt = *it;
+ DeviceType_T devType = pAtt->i_getType();
+ Medium *pMedium = pAtt->i_getMedium();
+
+ MediumLockList *pMediumLockList(new MediumLockList());
+ // There can be attachments without a medium (floppy/dvd), and thus
+ // it's impossible to create a medium lock list. It still makes sense
+ // to have the empty medium lock list in the map in case a medium is
+ // attached later.
+ if (pMedium != NULL)
+ {
+ MediumType_T mediumType = pMedium->i_getType();
+ bool fIsReadOnlyLock = mediumType == MediumType_Readonly
+ || mediumType == MediumType_Shareable;
+ bool fIsVitalImage = (devType == DeviceType_HardDisk);
+
+ alock.release();
+ mrc = pMedium->i_createMediumLockList(fIsVitalImage /* fFailIfInaccessible */,
+ !fIsReadOnlyLock ? pMedium : NULL /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pMediumLockList);
+ alock.acquire();
+ if (FAILED(mrc))
+ {
+ delete pMediumLockList;
+ mData->mSession.mLockedMedia.Clear();
+ break;
+ }
+ }
+
+ HRESULT rc = mData->mSession.mLockedMedia.Insert(pAtt, pMediumLockList);
+ if (FAILED(rc))
+ {
+ mData->mSession.mLockedMedia.Clear();
+ mrc = setError(rc,
+ tr("Collecting locking information for all attached media failed"));
+ break;
+ }
+ }
+
+ if (SUCCEEDED(mrc))
+ {
+ /* Now lock all media. If this fails, nothing is locked. */
+ alock.release();
+ HRESULT rc = mData->mSession.mLockedMedia.Lock();
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ mrc = setError(rc,
+ tr("Locking of attached media failed. A possible reason is that one of the media is attached to a running VM"));
+ }
+ }
+
+ return mrc;
+}
+
+/**
+ * Undoes the locks made by by #lockMedia().
+ */
+HRESULT SessionMachine::i_unlockMedia()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(),autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* we may be holding important error info on the current thread;
+ * preserve it */
+ ErrorInfoKeeper eik;
+
+ HRESULT rc = mData->mSession.mLockedMedia.Clear();
+ AssertComRC(rc);
+ return rc;
+}
+
+/**
+ * Helper to change the machine state (reimplementation).
+ *
+ * @note Locks this object for writing.
+ * @note This method must not call i_saveSettings or SaveSettings, otherwise
+ * it can cause crashes in random places due to unexpectedly committing
+ * the current settings. The caller is responsible for that. The call
+ * to saveStateSettings is fine, because this method does not commit.
+ */
+HRESULT SessionMachine::i_setMachineState(MachineState_T aMachineState)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ MachineState_T oldMachineState = mData->mMachineState;
+
+ AssertMsgReturn(oldMachineState != aMachineState,
+ ("oldMachineState=%s, aMachineState=%s\n",
+ ::stringifyMachineState(oldMachineState), ::stringifyMachineState(aMachineState)),
+ E_FAIL);
+
+ HRESULT rc = S_OK;
+
+ int stsFlags = 0;
+ bool deleteSavedState = false;
+
+ /* detect some state transitions */
+
+ if ( ( ( oldMachineState == MachineState_Saved
+ || oldMachineState == MachineState_AbortedSaved
+ )
+ && aMachineState == MachineState_Restoring
+ )
+ || ( ( oldMachineState == MachineState_PoweredOff
+ || oldMachineState == MachineState_Teleported
+ || oldMachineState == MachineState_Aborted
+ )
+ && ( aMachineState == MachineState_TeleportingIn
+ || aMachineState == MachineState_Starting
+ )
+ )
+ )
+ {
+ /* The EMT thread is about to start */
+
+ /* Nothing to do here for now... */
+
+ /// @todo NEWMEDIA don't let mDVDDrive and other children
+ /// change anything when in the Starting/Restoring state
+ }
+ else if ( ( oldMachineState == MachineState_Running
+ || oldMachineState == MachineState_Paused
+ || oldMachineState == MachineState_Teleporting
+ || oldMachineState == MachineState_OnlineSnapshotting
+ || oldMachineState == MachineState_LiveSnapshotting
+ || oldMachineState == MachineState_Stuck
+ || oldMachineState == MachineState_Starting
+ || oldMachineState == MachineState_Stopping
+ || oldMachineState == MachineState_Saving
+ || oldMachineState == MachineState_Restoring
+ || oldMachineState == MachineState_TeleportingPausedVM
+ || oldMachineState == MachineState_TeleportingIn
+ )
+ && ( aMachineState == MachineState_PoweredOff
+ || aMachineState == MachineState_Saved
+ || aMachineState == MachineState_Teleported
+ || aMachineState == MachineState_Aborted
+ || aMachineState == MachineState_AbortedSaved
+ )
+ )
+ {
+ /* The EMT thread has just stopped, unlock attached media. Note that as
+ * opposed to locking that is done from Console, we do unlocking here
+ * because the VM process may have aborted before having a chance to
+ * properly unlock all media it locked. */
+
+ unlockMedia();
+ }
+
+ if (oldMachineState == MachineState_Restoring)
+ {
+ if (aMachineState != MachineState_Saved && aMachineState != MachineState_AbortedSaved)
+ {
+ /*
+ * delete the saved state file once the machine has finished
+ * restoring from it (note that Console sets the state from
+ * Restoring to AbortedSaved if the VM couldn't restore successfully,
+ * to give the user an ability to fix an error and retry --
+ * we keep the saved state file in this case)
+ */
+ deleteSavedState = true;
+ }
+ }
+ else if ( oldMachineState == MachineState_Saved
+ && ( aMachineState == MachineState_PoweredOff
+ || aMachineState == MachineState_Teleported
+ )
+ )
+ {
+ /* delete the saved state after SessionMachine::ForgetSavedState() is called */
+ deleteSavedState = true;
+ mData->mCurrentStateModified = TRUE;
+ stsFlags |= SaveSTS_CurStateModified;
+ }
+ /* failure to reach the restoring state should always go to MachineState_AbortedSaved */
+ Assert(!(oldMachineState == MachineState_Saved && aMachineState == MachineState_Aborted));
+
+ if ( aMachineState == MachineState_Starting
+ || aMachineState == MachineState_Restoring
+ || aMachineState == MachineState_TeleportingIn
+ )
+ {
+ /* set the current state modified flag to indicate that the current
+ * state is no more identical to the state in the
+ * current snapshot */
+ if (!mData->mCurrentSnapshot.isNull())
+ {
+ mData->mCurrentStateModified = TRUE;
+ stsFlags |= SaveSTS_CurStateModified;
+ }
+ }
+
+ if (deleteSavedState)
+ {
+ if (mRemoveSavedState)
+ {
+ Assert(!mSSData->strStateFilePath.isEmpty());
+
+ // it is safe to delete the saved state file if ...
+ if ( !mData->mFirstSnapshot // ... we have no snapshots or
+ || !mData->mFirstSnapshot->i_sharesSavedStateFile(mSSData->strStateFilePath, NULL /* pSnapshotToIgnore */)
+ // ... none of the snapshots share the saved state file
+ )
+ i_deleteFile(mSSData->strStateFilePath, true /* fIgnoreFailures */);
+ }
+
+ mSSData->strStateFilePath.setNull();
+ stsFlags |= SaveSTS_StateFilePath;
+ }
+
+ /* redirect to the underlying peer machine */
+ mPeer->i_setMachineState(aMachineState);
+
+ if ( oldMachineState != MachineState_RestoringSnapshot
+ && ( aMachineState == MachineState_PoweredOff
+ || aMachineState == MachineState_Teleported
+ || aMachineState == MachineState_Aborted
+ || aMachineState == MachineState_AbortedSaved
+ || aMachineState == MachineState_Saved))
+ {
+ /* the machine has stopped execution
+ * (or the saved state file was adopted) */
+ stsFlags |= SaveSTS_StateTimeStamp;
+ }
+
+ if ( ( oldMachineState == MachineState_PoweredOff
+ || oldMachineState == MachineState_Aborted
+ || oldMachineState == MachineState_Teleported
+ )
+ && aMachineState == MachineState_Saved)
+ {
+ /* the saved state file was adopted */
+ Assert(!mSSData->strStateFilePath.isEmpty());
+ stsFlags |= SaveSTS_StateFilePath;
+ }
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ if ( aMachineState == MachineState_PoweredOff
+ || aMachineState == MachineState_Aborted
+ || aMachineState == MachineState_Teleported)
+ {
+ /* Make sure any transient guest properties get removed from the
+ * property store on shutdown. */
+ BOOL fNeedsSaving = mData->mGuestPropertiesModified;
+
+ /* remove it from the settings representation */
+ settings::GuestPropertiesList &llGuestProperties = mData->pMachineConfigFile->hardwareMachine.llGuestProperties;
+ for (settings::GuestPropertiesList::iterator
+ it = llGuestProperties.begin();
+ it != llGuestProperties.end();
+ /*nothing*/)
+ {
+ const settings::GuestProperty &prop = *it;
+ if ( prop.strFlags.contains("TRANSRESET", Utf8Str::CaseInsensitive)
+ || prop.strFlags.contains("TRANSIENT", Utf8Str::CaseInsensitive))
+ {
+ it = llGuestProperties.erase(it);
+ fNeedsSaving = true;
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ /* Additionally remove it from the HWData representation. Required to
+ * keep everything in sync, as this is what the API keeps using. */
+ HWData::GuestPropertyMap &llHWGuestProperties = mHWData->mGuestProperties;
+ for (HWData::GuestPropertyMap::iterator
+ it = llHWGuestProperties.begin();
+ it != llHWGuestProperties.end();
+ /*nothing*/)
+ {
+ uint32_t fFlags = it->second.mFlags;
+ if (fFlags & (GUEST_PROP_F_TRANSIENT | GUEST_PROP_F_TRANSRESET))
+ {
+ /* iterator where we need to continue after the erase call
+ * (C++03 is a fact still, and it doesn't return the iterator
+ * which would allow continuing) */
+ HWData::GuestPropertyMap::iterator it2 = it;
+ ++it2;
+ llHWGuestProperties.erase(it);
+ it = it2;
+ fNeedsSaving = true;
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ if (fNeedsSaving)
+ {
+ mData->mCurrentStateModified = TRUE;
+ stsFlags |= SaveSTS_CurStateModified;
+ }
+ }
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+ rc = i_saveStateSettings(stsFlags);
+
+ if ( ( oldMachineState != MachineState_PoweredOff
+ && oldMachineState != MachineState_Aborted
+ && oldMachineState != MachineState_Teleported
+ )
+ && ( aMachineState == MachineState_PoweredOff
+ || aMachineState == MachineState_Aborted
+ || aMachineState == MachineState_Teleported
+ )
+ )
+ {
+ /* we've been shut down for any reason */
+ /* no special action so far */
+ }
+
+ LogFlowThisFunc(("rc=%Rhrc [%s]\n", rc, ::stringifyMachineState(mData->mMachineState) ));
+ LogFlowThisFuncLeave();
+ return rc;
+}
+
+/**
+ * Sends the current machine state value to the VM process.
+ *
+ * @note Locks this object for reading, then calls a client process.
+ */
+HRESULT SessionMachine::i_updateMachineStateOnClient()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(!!mData, E_FAIL);
+ if (mData->mSession.mLockType == LockType_VM)
+ directControl = mData->mSession.mDirectControl;
+
+ /* directControl may be already set to NULL here in #OnSessionEnd()
+ * called too early by the direct session process while there is still
+ * some operation (like deleting the snapshot) in progress. The client
+ * process in this case is waiting inside Session::close() for the
+ * "end session" process object to complete, while #uninit() called by
+ * #i_checkForDeath() on the Watcher thread is waiting for the pending
+ * operation to complete. For now, we accept this inconsistent behavior
+ * and simply do nothing here. */
+
+ if (mData->mSession.mState == SessionState_Unlocking)
+ return S_OK;
+ }
+
+ /* ignore notifications sent after #OnSessionEnd() is called */
+ if (!directControl)
+ return S_OK;
+
+ return directControl->UpdateMachineState(mData->mMachineState);
+}
+
+
+/*static*/
+HRESULT Machine::i_setErrorStatic(HRESULT aResultCode, const char *pcszMsg, ...)
+{
+ va_list args;
+ va_start(args, pcszMsg);
+ HRESULT rc = setErrorInternalV(aResultCode,
+ getStaticClassIID(),
+ getStaticComponentName(),
+ pcszMsg, args,
+ false /* aWarning */,
+ true /* aLogIt */);
+ va_end(args);
+ return rc;
+}
+
+
+HRESULT Machine::updateState(MachineState_T aState)
+{
+ NOREF(aState);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::beginPowerUp(const ComPtr<IProgress> &aProgress)
+{
+ NOREF(aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::endPowerUp(LONG aResult)
+{
+ NOREF(aResult);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::beginPoweringDown(ComPtr<IProgress> &aProgress)
+{
+ NOREF(aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::endPoweringDown(LONG aResult,
+ const com::Utf8Str &aErrMsg)
+{
+ NOREF(aResult);
+ NOREF(aErrMsg);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::runUSBDeviceFilters(const ComPtr<IUSBDevice> &aDevice,
+ BOOL *aMatched,
+ ULONG *aMaskedInterfaces)
+{
+ NOREF(aDevice);
+ NOREF(aMatched);
+ NOREF(aMaskedInterfaces);
+ ReturnComNotImplemented();
+
+}
+
+HRESULT Machine::captureUSBDevice(const com::Guid &aId, const com::Utf8Str &aCaptureFilename)
+{
+ NOREF(aId); NOREF(aCaptureFilename);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::detachUSBDevice(const com::Guid &aId,
+ BOOL aDone)
+{
+ NOREF(aId);
+ NOREF(aDone);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::autoCaptureUSBDevices()
+{
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::detachAllUSBDevices(BOOL aDone)
+{
+ NOREF(aDone);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::onSessionEnd(const ComPtr<ISession> &aSession,
+ ComPtr<IProgress> &aProgress)
+{
+ NOREF(aSession);
+ NOREF(aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::finishOnlineMergeMedium()
+{
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::pullGuestProperties(std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<com::Utf8Str> &aFlags)
+{
+ NOREF(aNames);
+ NOREF(aValues);
+ NOREF(aTimestamps);
+ NOREF(aFlags);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::pushGuestProperty(const com::Utf8Str &aName,
+ const com::Utf8Str &aValue,
+ LONG64 aTimestamp,
+ const com::Utf8Str &aFlags,
+ BOOL fWasDeleted)
+{
+ NOREF(aName);
+ NOREF(aValue);
+ NOREF(aTimestamp);
+ NOREF(aFlags);
+ NOREF(fWasDeleted);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::lockMedia()
+{
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::unlockMedia()
+{
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::ejectMedium(const ComPtr<IMediumAttachment> &aAttachment,
+ ComPtr<IMediumAttachment> &aNewAttachment)
+{
+ NOREF(aAttachment);
+ NOREF(aNewAttachment);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::reportVmStatistics(ULONG aValidStats,
+ ULONG aCpuUser,
+ ULONG aCpuKernel,
+ ULONG aCpuIdle,
+ ULONG aMemTotal,
+ ULONG aMemFree,
+ ULONG aMemBalloon,
+ ULONG aMemShared,
+ ULONG aMemCache,
+ ULONG aPagedTotal,
+ ULONG aMemAllocTotal,
+ ULONG aMemFreeTotal,
+ ULONG aMemBalloonTotal,
+ ULONG aMemSharedTotal,
+ ULONG aVmNetRx,
+ ULONG aVmNetTx)
+{
+ NOREF(aValidStats);
+ NOREF(aCpuUser);
+ NOREF(aCpuKernel);
+ NOREF(aCpuIdle);
+ NOREF(aMemTotal);
+ NOREF(aMemFree);
+ NOREF(aMemBalloon);
+ NOREF(aMemShared);
+ NOREF(aMemCache);
+ NOREF(aPagedTotal);
+ NOREF(aMemAllocTotal);
+ NOREF(aMemFreeTotal);
+ NOREF(aMemBalloonTotal);
+ NOREF(aMemSharedTotal);
+ NOREF(aVmNetRx);
+ NOREF(aVmNetTx);
+ ReturnComNotImplemented();
+}
+
+HRESULT Machine::authenticateExternal(const std::vector<com::Utf8Str> &aAuthParams,
+ com::Utf8Str &aResult)
+{
+ NOREF(aAuthParams);
+ NOREF(aResult);
+ ReturnComNotImplemented();
+}
+
+com::Utf8Str Machine::i_controllerNameFromBusType(StorageBus_T aBusType)
+{
+ com::Utf8Str strControllerName = "Unknown";
+ switch (aBusType)
+ {
+ case StorageBus_IDE:
+ {
+ strControllerName = "IDE";
+ break;
+ }
+ case StorageBus_SATA:
+ {
+ strControllerName = "SATA";
+ break;
+ }
+ case StorageBus_SCSI:
+ {
+ strControllerName = "SCSI";
+ break;
+ }
+ case StorageBus_Floppy:
+ {
+ strControllerName = "Floppy";
+ break;
+ }
+ case StorageBus_SAS:
+ {
+ strControllerName = "SAS";
+ break;
+ }
+ case StorageBus_USB:
+ {
+ strControllerName = "USB";
+ break;
+ }
+ default:
+ break;
+ }
+ return strControllerName;
+}
+
+HRESULT Machine::applyDefaults(const com::Utf8Str &aFlags)
+{
+ /* it's assumed the machine already registered. If not, it's a problem of the caller */
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(),autoCaller.rc());
+
+ HRESULT rc = S_OK;
+
+ /* get usb device filters from host, before any writes occurred to avoid deadlock */
+ ComPtr<IUSBDeviceFilters> usbDeviceFilters;
+ rc = getUSBDeviceFilters(usbDeviceFilters);
+ if (FAILED(rc)) return rc;
+
+ NOREF(aFlags);
+ com::Utf8Str osTypeId;
+ ComObjPtr<GuestOSType> osType = NULL;
+
+ /* Get the guest os type as a string from the VB. */
+ rc = getOSTypeId(osTypeId);
+ if (FAILED(rc)) return rc;
+
+ /* Get the os type obj that coresponds, can be used to get
+ * the defaults for this guest OS. */
+ rc = mParent->i_findGuestOSType(Bstr(osTypeId), osType);
+ if (FAILED(rc)) return rc;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Let the OS type select 64-bit ness. */
+ mHWData->mLongMode = osType->i_is64Bit()
+ ? settings::Hardware::LongMode_Enabled : settings::Hardware::LongMode_Disabled;
+
+ /* Let the OS type enable the X2APIC */
+ mHWData->mX2APIC = osType->i_recommendedX2APIC();
+
+ /* This one covers IOAPICEnabled. */
+ mBIOSSettings->i_applyDefaults(osType);
+
+ /* Initialize default record settings. */
+ mRecordingSettings->i_applyDefaults();
+
+ /* Initialize default BIOS settings here */
+ /* Hardware virtualization must be ON by default */
+ mHWData->mAPIC = true;
+ mHWData->mHWVirtExEnabled = true;
+
+ rc = osType->COMGETTER(RecommendedRAM)(&mHWData->mMemorySize);
+ if (FAILED(rc)) return rc;
+
+ rc = osType->COMGETTER(RecommendedCPUCount)(&mHWData->mCPUCount);
+ if (FAILED(rc)) return rc;
+
+ /* Graphics stuff. */
+ GraphicsControllerType_T graphicsController;
+ rc = osType->COMGETTER(RecommendedGraphicsController)(&graphicsController);
+ if (FAILED(rc)) return rc;
+
+ rc = mGraphicsAdapter->COMSETTER(GraphicsControllerType)(graphicsController);
+ if (FAILED(rc)) return rc;
+
+ ULONG vramSize;
+ rc = osType->COMGETTER(RecommendedVRAM)(&vramSize);
+ if (FAILED(rc)) return rc;
+
+ rc = mGraphicsAdapter->COMSETTER(VRAMSize)(vramSize);
+ if (FAILED(rc)) return rc;
+
+ BOOL fAccelerate2DVideoEnabled;
+ rc = osType->COMGETTER(Recommended2DVideoAcceleration)(&fAccelerate2DVideoEnabled);
+ if (FAILED(rc)) return rc;
+
+ rc = mGraphicsAdapter->COMSETTER(Accelerate2DVideoEnabled)(fAccelerate2DVideoEnabled);
+ if (FAILED(rc)) return rc;
+
+ BOOL fAccelerate3DEnabled;
+ rc = osType->COMGETTER(Recommended3DAcceleration)(&fAccelerate3DEnabled);
+ if (FAILED(rc)) return rc;
+
+ rc = mGraphicsAdapter->COMSETTER(Accelerate3DEnabled)(fAccelerate3DEnabled);
+ if (FAILED(rc)) return rc;
+
+ rc = osType->COMGETTER(RecommendedFirmware)(&mHWData->mFirmwareType);
+ if (FAILED(rc)) return rc;
+
+ rc = osType->COMGETTER(RecommendedPAE)(&mHWData->mPAEEnabled);
+ if (FAILED(rc)) return rc;
+
+ rc = osType->COMGETTER(RecommendedHPET)(&mHWData->mHPETEnabled);
+ if (FAILED(rc)) return rc;
+
+ BOOL mRTCUseUTC;
+ rc = osType->COMGETTER(RecommendedRTCUseUTC)(&mRTCUseUTC);
+ if (FAILED(rc)) return rc;
+
+ setRTCUseUTC(mRTCUseUTC);
+ if (FAILED(rc)) return rc;
+
+ /* the setter does more than just the assignment, so use it */
+ ChipsetType_T enmChipsetType;
+ rc = osType->COMGETTER(RecommendedChipset)(&enmChipsetType);
+ if (FAILED(rc)) return rc;
+
+ rc = COMSETTER(ChipsetType)(enmChipsetType);
+ if (FAILED(rc)) return rc;
+
+ rc = osType->COMGETTER(RecommendedTFReset)(&mHWData->mTripleFaultReset);
+ if (FAILED(rc)) return rc;
+
+ /* Apply IOMMU defaults. */
+ IommuType_T enmIommuType;
+ rc = osType->COMGETTER(RecommendedIommuType)(&enmIommuType);
+ if (FAILED(rc)) return rc;
+
+ rc = COMSETTER(IommuType)(enmIommuType);
+ if (FAILED(rc)) return rc;
+
+ /* Apply network adapters defaults */
+ for (ULONG slot = 0; slot < mNetworkAdapters.size(); ++slot)
+ mNetworkAdapters[slot]->i_applyDefaults(osType);
+
+ /* Apply serial port defaults */
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); ++slot)
+ mSerialPorts[slot]->i_applyDefaults(osType);
+
+ /* Apply parallel port defaults - not OS dependent*/
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); ++slot)
+ mParallelPorts[slot]->i_applyDefaults();
+
+ /* This one covers the TPM type. */
+ mTrustedPlatformModule->i_applyDefaults(osType);
+
+ /* This one covers secure boot. */
+ rc = mNvramStore->i_applyDefaults(osType);
+ if (FAILED(rc)) return rc;
+
+ /* Audio stuff. */
+ rc = mAudioSettings->i_applyDefaults(osType);
+ if (FAILED(rc)) return rc;
+
+ /* Storage Controllers */
+ StorageControllerType_T hdStorageControllerType;
+ StorageBus_T hdStorageBusType;
+ StorageControllerType_T dvdStorageControllerType;
+ StorageBus_T dvdStorageBusType;
+ BOOL recommendedFloppy;
+ ComPtr<IStorageController> floppyController;
+ ComPtr<IStorageController> hdController;
+ ComPtr<IStorageController> dvdController;
+ Utf8Str strFloppyName, strDVDName, strHDName;
+
+ /* GUI auto generates controller names using bus type. Do the same*/
+ strFloppyName = i_controllerNameFromBusType(StorageBus_Floppy);
+
+ /* Floppy recommended? add one. */
+ rc = osType->COMGETTER(RecommendedFloppy(&recommendedFloppy));
+ if (FAILED(rc)) return rc;
+ if (recommendedFloppy)
+ {
+ rc = addStorageController(strFloppyName,
+ StorageBus_Floppy,
+ floppyController);
+ if (FAILED(rc)) return rc;
+ }
+
+ /* Setup one DVD storage controller. */
+ rc = osType->COMGETTER(RecommendedDVDStorageController)(&dvdStorageControllerType);
+ if (FAILED(rc)) return rc;
+
+ rc = osType->COMGETTER(RecommendedDVDStorageBus)(&dvdStorageBusType);
+ if (FAILED(rc)) return rc;
+
+ strDVDName = i_controllerNameFromBusType(dvdStorageBusType);
+
+ rc = addStorageController(strDVDName,
+ dvdStorageBusType,
+ dvdController);
+ if (FAILED(rc)) return rc;
+
+ rc = dvdController->COMSETTER(ControllerType)(dvdStorageControllerType);
+ if (FAILED(rc)) return rc;
+
+ /* Setup one HDD storage controller. */
+ rc = osType->COMGETTER(RecommendedHDStorageController)(&hdStorageControllerType);
+ if (FAILED(rc)) return rc;
+
+ rc = osType->COMGETTER(RecommendedHDStorageBus)(&hdStorageBusType);
+ if (FAILED(rc)) return rc;
+
+ strHDName = i_controllerNameFromBusType(hdStorageBusType);
+
+ if (hdStorageBusType != dvdStorageBusType && hdStorageControllerType != dvdStorageControllerType)
+ {
+ rc = addStorageController(strHDName,
+ hdStorageBusType,
+ hdController);
+ if (FAILED(rc)) return rc;
+
+ rc = hdController->COMSETTER(ControllerType)(hdStorageControllerType);
+ if (FAILED(rc)) return rc;
+ }
+ else
+ {
+ /* The HD controller is the same as DVD: */
+ hdController = dvdController;
+ }
+
+ /* Limit the AHCI port count if it's used because windows has trouble with
+ * too many ports and other guest (OS X in particular) may take extra long
+ * boot: */
+
+ // pParent = static_cast<Medium*>(aP)
+ IStorageController *temp = hdController;
+ ComObjPtr<StorageController> storageController;
+ storageController = static_cast<StorageController *>(temp);
+
+ // tempHDController = aHDController;
+ if (hdStorageControllerType == StorageControllerType_IntelAhci)
+ storageController->COMSETTER(PortCount)(1 + (dvdStorageControllerType == StorageControllerType_IntelAhci));
+ else if (dvdStorageControllerType == StorageControllerType_IntelAhci)
+ storageController->COMSETTER(PortCount)(1);
+
+ /* USB stuff */
+
+ bool ohciEnabled = false;
+
+ ComPtr<IUSBController> usbController;
+ BOOL recommendedUSB3;
+ BOOL recommendedUSB;
+ BOOL usbProxyAvailable;
+
+ getUSBProxyAvailable(&usbProxyAvailable);
+ if (FAILED(rc)) return rc;
+
+ rc = osType->COMGETTER(RecommendedUSB3)(&recommendedUSB3);
+ if (FAILED(rc)) return rc;
+ rc = osType->COMGETTER(RecommendedUSB)(&recommendedUSB);
+ if (FAILED(rc)) return rc;
+
+ if (!usbDeviceFilters.isNull() && recommendedUSB3 && usbProxyAvailable)
+ {
+ rc = addUSBController("XHCI", USBControllerType_XHCI, usbController);
+ if (FAILED(rc)) return rc;
+
+ /* xHci includes OHCI */
+ ohciEnabled = true;
+ }
+ if ( !ohciEnabled
+ && !usbDeviceFilters.isNull() && recommendedUSB && usbProxyAvailable)
+ {
+ rc = addUSBController("OHCI", USBControllerType_OHCI, usbController);
+ if (FAILED(rc)) return rc;
+ ohciEnabled = true;
+
+ rc = addUSBController("EHCI", USBControllerType_EHCI, usbController);
+ if (FAILED(rc)) return rc;
+ }
+
+ /* Set recommended human interface device types: */
+ BOOL recommendedUSBHID;
+ rc = osType->COMGETTER(RecommendedUSBHID)(&recommendedUSBHID);
+ if (FAILED(rc)) return rc;
+
+ if (recommendedUSBHID)
+ {
+ mHWData->mKeyboardHIDType = KeyboardHIDType_USBKeyboard;
+ mHWData->mPointingHIDType = PointingHIDType_USBMouse;
+ if (!ohciEnabled && !usbDeviceFilters.isNull())
+ {
+ rc = addUSBController("OHCI", USBControllerType_OHCI, usbController);
+ if (FAILED(rc)) return rc;
+ }
+ }
+
+ BOOL recommendedUSBTablet;
+ rc = osType->COMGETTER(RecommendedUSBTablet)(&recommendedUSBTablet);
+ if (FAILED(rc)) return rc;
+
+ if (recommendedUSBTablet)
+ {
+ mHWData->mPointingHIDType = PointingHIDType_USBTablet;
+ if (!ohciEnabled && !usbDeviceFilters.isNull())
+ {
+ rc = addUSBController("OHCI", USBControllerType_OHCI, usbController);
+ if (FAILED(rc)) return rc;
+ }
+ }
+
+ /* Enable the VMMDev testing feature for bootsector VMs: */
+ if (osTypeId == "VBoxBS_64")
+ {
+ rc = setExtraData("VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled", "1");
+ if (FAILED(rc))
+ return rc;
+ }
+
+ return S_OK;
+}
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+/**
+ * Task record for change encryption settins.
+ */
+class Machine::ChangeEncryptionTask
+ : public Machine::Task
+{
+public:
+ ChangeEncryptionTask(Machine *m,
+ Progress *p,
+ const Utf8Str &t,
+ const com::Utf8Str &aCurrentPassword,
+ const com::Utf8Str &aCipher,
+ const com::Utf8Str &aNewPassword,
+ const com::Utf8Str &aNewPasswordId,
+ const BOOL aForce,
+ const MediaList &llMedia)
+ : Task(m, p, t),
+ mstrNewPassword(aNewPassword),
+ mstrCurrentPassword(aCurrentPassword),
+ mstrCipher(aCipher),
+ mstrNewPasswordId(aNewPasswordId),
+ mForce(aForce),
+ mllMedia(llMedia)
+ {}
+
+ ~ChangeEncryptionTask()
+ {
+ if (mstrNewPassword.length())
+ RTMemWipeThoroughly(mstrNewPassword.mutableRaw(), mstrNewPassword.length(), 10 /* cPasses */);
+ if (mstrCurrentPassword.length())
+ RTMemWipeThoroughly(mstrCurrentPassword.mutableRaw(), mstrCurrentPassword.length(), 10 /* cPasses */);
+ if (m_pCryptoIf)
+ {
+ m_pMachine->i_getVirtualBox()->i_releaseCryptoIf(m_pCryptoIf);
+ m_pCryptoIf = NULL;
+ }
+ }
+
+ Utf8Str mstrNewPassword;
+ Utf8Str mstrCurrentPassword;
+ Utf8Str mstrCipher;
+ Utf8Str mstrNewPasswordId;
+ BOOL mForce;
+ MediaList mllMedia;
+ PCVBOXCRYPTOIF m_pCryptoIf;
+private:
+ void handler()
+ {
+ try
+ {
+ m_pMachine->i_changeEncryptionHandler(*this);
+ }
+ catch (...)
+ {
+ LogRel(("Some exception in the function Machine::i_changeEncryptionHandler()\n"));
+ }
+ }
+
+ friend void Machine::i_changeEncryptionHandler(ChangeEncryptionTask &task);
+};
+
+/**
+ * Scans specified directory and fills list by files found
+ *
+ * @returns VBox status code.
+ * @param lstFiles
+ * @param strDir
+ * @param filePattern
+ */
+int Machine::i_findFiles(std::list<com::Utf8Str> &lstFiles, const com::Utf8Str &strDir,
+ const com::Utf8Str &strPattern)
+{
+ /* To get all entries including subdirectories. */
+ char *pszFilePattern = RTPathJoinA(strDir.c_str(), "*");
+ if (!pszFilePattern)
+ return VERR_NO_STR_MEMORY;
+
+ PRTDIRENTRYEX pDirEntry = NULL;
+ RTDIR hDir;
+ size_t cbDirEntry = sizeof(RTDIRENTRYEX);
+ int rc = RTDirOpenFiltered(&hDir, pszFilePattern, RTDIRFILTER_WINNT, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ pDirEntry = (PRTDIRENTRYEX)RTMemAllocZ(sizeof(RTDIRENTRYEX));
+ if (pDirEntry)
+ {
+ while ( (rc = RTDirReadEx(hDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK))
+ != VERR_NO_MORE_FILES)
+ {
+ char *pszFilePath = NULL;
+
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ /* allocate new buffer. */
+ RTMemFree(pDirEntry);
+ pDirEntry = (PRTDIRENTRYEX)RTMemAllocZ(cbDirEntry);
+ if (!pDirEntry)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ /* Retry. */
+ rc = RTDirReadEx(hDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (RT_FAILURE(rc))
+ break;
+
+ /* Exclude . and .. */
+ if ( (pDirEntry->szName[0] == '.' && pDirEntry->szName[1] == '\0')
+ || (pDirEntry->szName[0] == '.' && pDirEntry->szName[1] == '.' && pDirEntry->szName[2] == '\0'))
+ continue;
+ if (RTFS_IS_DIRECTORY(pDirEntry->Info.Attr.fMode))
+ {
+ char *pszSubDirPath = RTPathJoinA(strDir.c_str(), pDirEntry->szName);
+ if (!pszSubDirPath)
+ {
+ rc = VERR_NO_STR_MEMORY;
+ break;
+ }
+ rc = i_findFiles(lstFiles, pszSubDirPath, strPattern);
+ RTMemFree(pszSubDirPath);
+ if (RT_FAILURE(rc))
+ break;
+ continue;
+ }
+
+ /* We got the new entry. */
+ if (!RTFS_IS_FILE(pDirEntry->Info.Attr.fMode))
+ continue;
+
+ if (!RTStrSimplePatternMatch(strPattern.c_str(), pDirEntry->szName))
+ continue;
+
+ /* Prepend the path to the libraries. */
+ pszFilePath = RTPathJoinA(strDir.c_str(), pDirEntry->szName);
+ if (!pszFilePath)
+ {
+ rc = VERR_NO_STR_MEMORY;
+ break;
+ }
+
+ lstFiles.push_back(pszFilePath);
+ RTStrFree(pszFilePath);
+ }
+
+ RTMemFree(pDirEntry);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTDirClose(hDir);
+ }
+ else
+ {
+ /* On Windows the above immediately signals that there are no
+ * files matching, while on other platforms enumerating the
+ * files below fails. Either way: stop searching. */
+ }
+
+ if ( rc == VERR_NO_MORE_FILES
+ || rc == VERR_FILE_NOT_FOUND
+ || rc == VERR_PATH_NOT_FOUND)
+ rc = VINF_SUCCESS;
+ RTStrFree(pszFilePattern);
+ return rc;
+}
+
+/**
+ * Helper to set up an I/O stream to read or write a possibly encrypted file.
+ *
+ * @returns VBox status code.
+ * @param pszFilename The file to open.
+ * @param pCryptoIf Pointer to the cryptographic interface if the file should be encrypted or contains encrypted data.
+ * @param pszKeyStore The keystore if the file should be encrypted or contains encrypted data.
+ * @param pszPassword The password if the file should be encrypted or contains encrypted data.
+ * @param fOpen The open flags for the file.
+ * @param phVfsIos Where to store the handle to the I/O stream on success.
+ */
+int Machine::i_createIoStreamForFile(const char *pszFilename, PCVBOXCRYPTOIF pCryptoIf,
+ const char *pszKeyStore, const char *pszPassword,
+ uint64_t fOpen, PRTVFSIOSTREAM phVfsIos)
+{
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ int vrc = RTVfsFileOpenNormal(pszFilename, fOpen, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ if (pCryptoIf)
+ {
+ RTVFSFILE hVfsFileCrypto = NIL_RTVFSFILE;
+ vrc = pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFile, pszKeyStore, pszPassword, &hVfsFileCrypto);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVfsFileRelease(hVfsFile);
+ hVfsFile = hVfsFileCrypto;
+ }
+ }
+
+ *phVfsIos = RTVfsFileToIoStream(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ }
+
+ return vrc;
+}
+
+/**
+ * Helper function processing all actions for one component (saved state files,
+ * NVRAM files, etc). Used by Machine::i_changeEncryptionHandler only.
+ *
+ * @param task
+ * @param strDirectory
+ * @param strFilePattern
+ * @param strMagic
+ * @param strKeyStore
+ * @param strKeyId
+ * @return
+ */
+HRESULT Machine::i_changeEncryptionForComponent(ChangeEncryptionTask &task, const com::Utf8Str strDirectory,
+ const com::Utf8Str strFilePattern, com::Utf8Str &strKeyStore,
+ com::Utf8Str &strKeyId, int iCipherMode)
+{
+ bool fDecrypt = task.mstrCurrentPassword.isNotEmpty()
+ && task.mstrCipher.isEmpty()
+ && task.mstrNewPassword.isEmpty()
+ && task.mstrNewPasswordId.isEmpty();
+ bool fEncrypt = task.mstrCurrentPassword.isEmpty()
+ && task.mstrCipher.isNotEmpty()
+ && task.mstrNewPassword.isNotEmpty()
+ && task.mstrNewPasswordId.isNotEmpty();
+
+ /* check if the cipher is changed which causes the reencryption*/
+
+ const char *pszTaskCipher = NULL;
+ if (task.mstrCipher.isNotEmpty())
+ pszTaskCipher = getCipherString(task.mstrCipher.c_str(), iCipherMode);
+
+ if (!task.mForce && !fDecrypt && !fEncrypt)
+ {
+ char *pszCipher = NULL;
+ int vrc = task.m_pCryptoIf->pfnCryptoKeyStoreGetDekFromEncoded(strKeyStore.c_str(),
+ NULL /*pszPassword*/,
+ NULL /*ppbKey*/,
+ NULL /*pcbKey*/,
+ &pszCipher);
+ if (RT_SUCCESS(vrc))
+ {
+ task.mForce = strcmp(pszTaskCipher, pszCipher) != 0;
+ RTMemFree(pszCipher);
+ }
+ else
+ return setErrorBoth(E_FAIL, vrc, tr("Obtain cipher for '%s' files failed (%Rrc)"),
+ strFilePattern.c_str(), vrc);
+ }
+
+ /* Only the password needs to be changed */
+ if (!task.mForce && !fDecrypt && !fEncrypt)
+ {
+ Assert(task.m_pCryptoIf);
+
+ VBOXCRYPTOCTX hCryptoCtx;
+ int vrc = task.m_pCryptoIf->pfnCryptoCtxLoad(strKeyStore.c_str(), task.mstrCurrentPassword.c_str(), &hCryptoCtx);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Loading old key store for '%s' files failed, (%Rrc)"),
+ strFilePattern.c_str(), vrc);
+ vrc = task.m_pCryptoIf->pfnCryptoCtxPasswordChange(hCryptoCtx, task.mstrNewPassword.c_str());
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Changing the password for '%s' files failed, (%Rrc)"),
+ strFilePattern.c_str(), vrc);
+
+ char *pszKeyStore = NULL;
+ vrc = task.m_pCryptoIf->pfnCryptoCtxSave(hCryptoCtx, &pszKeyStore);
+ task.m_pCryptoIf->pfnCryptoCtxDestroy(hCryptoCtx);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Saving the key store for '%s' files failed, (%Rrc)"),
+ strFilePattern.c_str(), vrc);
+ strKeyStore = pszKeyStore;
+ RTMemFree(pszKeyStore);
+ strKeyId = task.mstrNewPasswordId;
+ return S_OK;
+ }
+
+ /* Reencryption required */
+ HRESULT rc = S_OK;
+ int vrc = VINF_SUCCESS;
+
+ std::list<com::Utf8Str> lstFiles;
+ if (SUCCEEDED(rc))
+ {
+ vrc = i_findFiles(lstFiles, strDirectory, strFilePattern);
+ if (RT_FAILURE(vrc))
+ rc = setErrorBoth(E_FAIL, vrc, tr("Getting file list for '%s' files failed, (%Rrc)"),
+ strFilePattern.c_str(), vrc);
+ }
+ com::Utf8Str strNewKeyStore;
+ if (SUCCEEDED(rc))
+ {
+ if (!fDecrypt)
+ {
+ VBOXCRYPTOCTX hCryptoCtx;
+ vrc = task.m_pCryptoIf->pfnCryptoCtxCreate(pszTaskCipher, task.mstrNewPassword.c_str(), &hCryptoCtx);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Create new key store for '%s' files failed, (%Rrc)"),
+ strFilePattern.c_str(), vrc);
+
+ char *pszKeyStore = NULL;
+ vrc = task.m_pCryptoIf->pfnCryptoCtxSave(hCryptoCtx, &pszKeyStore);
+ task.m_pCryptoIf->pfnCryptoCtxDestroy(hCryptoCtx);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Saving the new key store for '%s' files failed, (%Rrc)"),
+ strFilePattern.c_str(), vrc);
+ strNewKeyStore = pszKeyStore;
+ RTMemFree(pszKeyStore);
+ }
+
+ for (std::list<com::Utf8Str>::iterator it = lstFiles.begin();
+ it != lstFiles.end();
+ ++it)
+ {
+ RTVFSIOSTREAM hVfsIosOld = NIL_RTVFSIOSTREAM;
+ RTVFSIOSTREAM hVfsIosNew = NIL_RTVFSIOSTREAM;
+
+ uint64_t fOpenForRead = RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE;
+ uint64_t fOpenForWrite = RTFILE_O_READWRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_DENY_WRITE;
+
+ vrc = i_createIoStreamForFile((*it).c_str(),
+ fEncrypt ? NULL : task.m_pCryptoIf,
+ fEncrypt ? NULL : strKeyStore.c_str(),
+ fEncrypt ? NULL : task.mstrCurrentPassword.c_str(),
+ fOpenForRead, &hVfsIosOld);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = i_createIoStreamForFile((*it + ".tmp").c_str(),
+ fDecrypt ? NULL : task.m_pCryptoIf,
+ fDecrypt ? NULL : strNewKeyStore.c_str(),
+ fDecrypt ? NULL : task.mstrNewPassword.c_str(),
+ fOpenForWrite, &hVfsIosNew);
+ if (RT_FAILURE(vrc))
+ rc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening file '%s' failed, (%Rrc)"),
+ (*it + ".tmp").c_str(), vrc);
+ }
+ else
+ rc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening file '%s' failed, (%Rrc)"),
+ (*it).c_str(), vrc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsUtilPumpIoStreams(hVfsIosOld, hVfsIosNew, BUF_DATA_SIZE);
+ if (RT_FAILURE(vrc))
+ rc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Changing encryption of the file '%s' failed with %Rrc"),
+ (*it).c_str(), vrc);
+ }
+
+ if (hVfsIosOld != NIL_RTVFSIOSTREAM)
+ RTVfsIoStrmRelease(hVfsIosOld);
+ if (hVfsIosNew != NIL_RTVFSIOSTREAM)
+ RTVfsIoStrmRelease(hVfsIosNew);
+ }
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ for (std::list<com::Utf8Str>::iterator it = lstFiles.begin();
+ it != lstFiles.end();
+ ++it)
+ {
+ vrc = RTFileRename((*it + ".tmp").c_str(), (*it).c_str(), RTPATHRENAME_FLAGS_REPLACE);
+ if (RT_FAILURE(vrc))
+ {
+ rc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Renaming the file '%s' failed, (%Rrc)"),
+ (*it + ".tmp").c_str(), vrc);
+ break;
+ }
+ }
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ strKeyStore = strNewKeyStore;
+ strKeyId = task.mstrNewPasswordId;
+ }
+
+ return rc;
+}
+
+/**
+ * Task thread implementation for Machine::changeEncryption(), called from
+ * Machine::taskHandler().
+ *
+ * @note Locks this object for writing.
+ *
+ * @param task
+ * @return
+ */
+void Machine::i_changeEncryptionHandler(ChangeEncryptionTask &task)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ LogFlowThisFunc(("state=%d\n", getObjectState().getState()));
+ if (FAILED(autoCaller.rc()))
+ {
+ /* we might have been uninitialized because the session was accidentally
+ * closed by the client, so don't assert */
+ HRESULT rc = setError(E_FAIL,
+ tr("The session has been accidentally closed"));
+ task.m_pProgress->i_notifyComplete(rc);
+ LogFlowThisFuncLeave();
+ return;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+ com::Utf8Str strOldKeyId = mData->mstrKeyId;
+ com::Utf8Str strOldKeyStore = mData->mstrKeyStore;
+ try
+ {
+ rc = this->i_getVirtualBox()->i_retainCryptoIf(&task.m_pCryptoIf);
+ if (FAILED(rc))
+ throw rc;
+
+ if (task.mstrCurrentPassword.isEmpty())
+ {
+ if (mData->mstrKeyStore.isNotEmpty())
+ throw setError(VBOX_E_PASSWORD_INCORRECT,
+ tr("The password given for the encrypted VM is incorrect"));
+ }
+ else
+ {
+ if (mData->mstrKeyStore.isEmpty())
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The VM is not configured for encryption"));
+ rc = checkEncryptionPassword(task.mstrCurrentPassword);
+ if (rc == VBOX_E_PASSWORD_INCORRECT)
+ throw setError(VBOX_E_PASSWORD_INCORRECT,
+ tr("The password to decrypt the VM is incorrect"));
+ }
+
+ if (task.mstrCipher.isNotEmpty())
+ {
+ if ( task.mstrNewPassword.isEmpty()
+ && task.mstrNewPasswordId.isEmpty()
+ && task.mstrCurrentPassword.isNotEmpty())
+ {
+ /* An empty password and password ID will default to the current password. */
+ task.mstrNewPassword = task.mstrCurrentPassword;
+ }
+ else if (task.mstrNewPassword.isEmpty())
+ throw setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("A password must be given for the VM encryption"));
+ else if (task.mstrNewPasswordId.isEmpty())
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("A valid identifier for the password must be given"));
+ }
+ else if (task.mstrNewPasswordId.isNotEmpty() || task.mstrNewPassword.isNotEmpty())
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The password and password identifier must be empty if the output should be unencrypted"));
+
+ /*
+ * Save config.
+ * Must be first operation to prevent making encrypted copies
+ * for old version of the config file.
+ */
+ int fSave = Machine::SaveS_Force;
+ if (task.mstrNewPassword.isNotEmpty())
+ {
+ VBOXCRYPTOCTX hCryptoCtx;
+
+ int vrc = VINF_SUCCESS;
+ if (task.mForce || task.mstrCurrentPassword.isEmpty() || task.mstrCipher.isNotEmpty())
+ {
+ vrc = task.m_pCryptoIf->pfnCryptoCtxCreate(getCipherString(task.mstrCipher.c_str(), CipherModeGcm),
+ task.mstrNewPassword.c_str(), &hCryptoCtx);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(E_FAIL, vrc, tr("New key store creation failed, (%Rrc)"), vrc);
+ }
+ else
+ {
+ vrc = task.m_pCryptoIf->pfnCryptoCtxLoad(mData->mstrKeyStore.c_str(),
+ task.mstrCurrentPassword.c_str(),
+ &hCryptoCtx);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(E_FAIL, vrc, tr("Loading old key store failed, (%Rrc)"), vrc);
+ vrc = task.m_pCryptoIf->pfnCryptoCtxPasswordChange(hCryptoCtx, task.mstrNewPassword.c_str());
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(E_FAIL, vrc, tr("Changing the password failed, (%Rrc)"), vrc);
+ }
+
+ char *pszKeyStore;
+ vrc = task.m_pCryptoIf->pfnCryptoCtxSave(hCryptoCtx, &pszKeyStore);
+ task.m_pCryptoIf->pfnCryptoCtxDestroy(hCryptoCtx);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(E_FAIL, vrc, tr("Saving the key store failed, (%Rrc)"), vrc);
+ mData->mstrKeyStore = pszKeyStore;
+ RTStrFree(pszKeyStore);
+ mData->mstrKeyId = task.mstrNewPasswordId;
+ size_t cbPassword = task.mstrNewPassword.length() + 1;
+ uint8_t *pbPassword = (uint8_t *)task.mstrNewPassword.c_str();
+ mData->mpKeyStore->deleteSecretKey(task.mstrNewPasswordId);
+ mData->mpKeyStore->addSecretKey(task.mstrNewPasswordId, pbPassword, cbPassword);
+ mNvramStore->i_addPassword(task.mstrNewPasswordId, task.mstrNewPassword);
+
+ /*
+ * Remove backuped config after saving because it can contain
+ * unencrypted version of the config
+ */
+ fSave |= Machine::SaveS_RemoveBackup;
+ }
+ else
+ {
+ mData->mstrKeyId.setNull();
+ mData->mstrKeyStore.setNull();
+ }
+
+ Bstr bstrCurrentPassword(task.mstrCurrentPassword);
+ Bstr bstrCipher(getCipherString(task.mstrCipher.c_str(), CipherModeXts));
+ Bstr bstrNewPassword(task.mstrNewPassword);
+ Bstr bstrNewPasswordId(task.mstrNewPasswordId);
+ /* encrypt mediums */
+ alock.release();
+ for (MediaList::iterator it = task.mllMedia.begin();
+ it != task.mllMedia.end();
+ ++it)
+ {
+ ComPtr<IProgress> pProgress1;
+ HRESULT hrc = (*it)->ChangeEncryption(bstrCurrentPassword.raw(), bstrCipher.raw(),
+ bstrNewPassword.raw(), bstrNewPasswordId.raw(),
+ pProgress1.asOutParam());
+ if (FAILED(hrc)) throw hrc;
+ hrc = task.m_pProgress->WaitForOtherProgressCompletion(pProgress1, 0 /* indefinite wait */);
+ if (FAILED(hrc)) throw hrc;
+ }
+ alock.acquire();
+
+ task.m_pProgress->SetNextOperation(Bstr(tr("Change encryption of the SAV files")).raw(), 1);
+
+ Utf8Str strFullSnapshotFolder;
+ i_calculateFullPath(mUserData->s.strSnapshotFolder, strFullSnapshotFolder);
+
+ /* .sav files (main and snapshots) */
+ rc = i_changeEncryptionForComponent(task, strFullSnapshotFolder, "*.sav",
+ mSSData->strStateKeyStore, mSSData->strStateKeyId, CipherModeGcm);
+ if (FAILED(rc))
+ /* the helper function already sets error object */
+ throw rc;
+
+ task.m_pProgress->SetNextOperation(Bstr(tr("Change encryption of the NVRAM files")).raw(), 1);
+
+ /* .nvram files */
+ com::Utf8Str strNVRAMKeyId;
+ com::Utf8Str strNVRAMKeyStore;
+ rc = mNvramStore->i_getEncryptionSettings(strNVRAMKeyId, strNVRAMKeyStore);
+ if (FAILED(rc))
+ throw setError(rc, tr("Getting NVRAM encryption settings failed (%Rhrc)"), rc);
+
+ Utf8Str strMachineFolder;
+ i_calculateFullPath(".", strMachineFolder);
+
+ rc = i_changeEncryptionForComponent(task, strMachineFolder, "*.nvram",
+ strNVRAMKeyStore, strNVRAMKeyId, CipherModeGcm);
+ if (FAILED(rc))
+ /* the helper function already sets error object */
+ throw rc;
+
+ rc = mNvramStore->i_updateEncryptionSettings(strNVRAMKeyId, strNVRAMKeyStore);
+ if (FAILED(rc))
+ throw setError(rc, tr("Setting NVRAM encryption settings failed (%Rhrc)"), rc);
+
+ task.m_pProgress->SetNextOperation(Bstr(tr("Change encryption of log files")).raw(), 1);
+
+ /* .log files */
+ com::Utf8Str strLogFolder;
+ i_getLogFolder(strLogFolder);
+ rc = i_changeEncryptionForComponent(task, strLogFolder, "VBox.log*",
+ mData->mstrLogKeyStore, mData->mstrLogKeyId, CipherModeCtr);
+ if (FAILED(rc))
+ /* the helper function already sets error object */
+ throw rc;
+
+ task.m_pProgress->SetNextOperation(Bstr(tr("Change encryption of the config file")).raw(), 1);
+
+ i_saveSettings(NULL, alock, fSave);
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ mData->mstrKeyId = strOldKeyId;
+ mData->mstrKeyStore = strOldKeyStore;
+ }
+
+ task.m_pProgress->i_notifyComplete(rc);
+
+ LogFlowThisFuncLeave();
+}
+#endif /*!VBOX_WITH_FULL_VM_ENCRYPTION*/
+
+HRESULT Machine::changeEncryption(const com::Utf8Str &aCurrentPassword,
+ const com::Utf8Str &aCipher,
+ const com::Utf8Str &aNewPassword,
+ const com::Utf8Str &aNewPasswordId,
+ BOOL aForce,
+ ComPtr<IProgress> &aProgress)
+{
+ LogFlowFuncEnter();
+
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ RT_NOREF(aCurrentPassword, aCipher, aNewPassword, aNewPasswordId, aForce, aProgress);
+ return setError(VBOX_E_NOT_SUPPORTED, tr("Full VM encryption is not available with this build"));
+#else
+ /* make the VM accessible */
+ if (!mData->mAccessible)
+ {
+ if ( aCurrentPassword.isEmpty()
+ || mData->mstrKeyId.isEmpty())
+ return setError(E_ACCESSDENIED, tr("Machine is inaccessible"));
+
+ HRESULT rc = addEncryptionPassword(mData->mstrKeyId, aCurrentPassword);
+ if (FAILED(rc))
+ return rc;
+ }
+
+ AutoLimitedCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* define mediums to be change encryption */
+
+ MediaList llMedia;
+ for (MediumAttachmentList::iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ ComObjPtr<MediumAttachment> &pAttach = *it;
+ ComObjPtr<Medium> pMedium = pAttach->i_getMedium();
+
+ if (!pMedium.isNull())
+ {
+ AutoCaller mac(pMedium);
+ if (FAILED(mac.rc())) return mac.rc();
+ AutoReadLock lock(pMedium COMMA_LOCKVAL_SRC_POS);
+ DeviceType_T devType = pMedium->i_getDeviceType();
+ if (devType == DeviceType_HardDisk)
+ {
+ /*
+ * We need to move to last child because the Medium::changeEncryption
+ * encrypts all chain of specified medium with its parents.
+ * Also we perform cheking of back reference and children for
+ * all media in the chain to raise error before we start any action.
+ * So, we first move into root parent and then we will move to last child
+ * keeping latter in the list for encryption.
+ */
+
+ /* move to root parent */
+ ComObjPtr<Medium> pTmpMedium = pMedium;
+ while (pTmpMedium.isNotNull())
+ {
+ AutoCaller mediumAC(pTmpMedium);
+ if (FAILED(mediumAC.rc())) return mac.rc();
+ AutoReadLock mlock(pTmpMedium COMMA_LOCKVAL_SRC_POS);
+
+ /* Cannot encrypt media which are attached to more than one virtual machine. */
+ size_t cBackRefs = pTmpMedium->i_getMachineBackRefCount();
+ if (cBackRefs > 1)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot encrypt medium '%s' because it is attached to %d virtual machines", "", cBackRefs),
+ pTmpMedium->i_getName().c_str(), cBackRefs);
+
+ size_t cChildren = pTmpMedium->i_getChildren().size();
+ if (cChildren > 1)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot encrypt medium '%s' because it has %d children", "", cChildren),
+ pTmpMedium->i_getName().c_str(), cChildren);
+
+ pTmpMedium = pTmpMedium->i_getParent();
+ }
+ /* move to last child */
+ pTmpMedium = pMedium;
+ while (pTmpMedium.isNotNull() && pTmpMedium->i_getChildren().size() != 0)
+ {
+ AutoCaller mediumAC(pTmpMedium);
+ if (FAILED(mediumAC.rc())) return mac.rc();
+ AutoReadLock mlock(pTmpMedium COMMA_LOCKVAL_SRC_POS);
+
+ /* Cannot encrypt media which are attached to more than one virtual machine. */
+ size_t cBackRefs = pTmpMedium->i_getMachineBackRefCount();
+ if (cBackRefs > 1)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot encrypt medium '%s' because it is attached to %d virtual machines", "", cBackRefs),
+ pTmpMedium->i_getName().c_str(), cBackRefs);
+
+ size_t cChildren = pTmpMedium->i_getChildren().size();
+ if (cChildren > 1)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot encrypt medium '%s' because it has %d children", "", cChildren),
+ pTmpMedium->i_getName().c_str(), cChildren);
+
+ pTmpMedium = pTmpMedium->i_getChildren().front();
+ }
+ llMedia.push_back(pTmpMedium);
+ }
+ }
+ }
+
+ ComObjPtr<Progress> pProgress;
+ pProgress.createObject();
+ HRESULT rc = pProgress->init(i_getVirtualBox(),
+ static_cast<IMachine*>(this) /* aInitiator */,
+ tr("Change encryption"),
+ TRUE /* fCancellable */,
+ (ULONG)(4 + + llMedia.size()), // cOperations
+ tr("Change encryption of the mediuma"));
+ if (FAILED(rc))
+ return rc;
+
+ /* create and start the task on a separate thread (note that it will not
+ * start working until we release alock) */
+ ChangeEncryptionTask *pTask = new ChangeEncryptionTask(this, pProgress, "VM encryption",
+ aCurrentPassword, aCipher, aNewPassword,
+ aNewPasswordId, aForce, llMedia);
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (FAILED(rc))
+ return rc;
+
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+
+ LogFlowFuncLeave();
+
+ return S_OK;
+#endif
+}
+
+HRESULT Machine::getEncryptionSettings(com::Utf8Str &aCipher,
+ com::Utf8Str &aPasswordId)
+{
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ RT_NOREF(aCipher, aPasswordId);
+ return setError(VBOX_E_NOT_SUPPORTED, tr("Full VM encryption is not available with this build"));
+#else
+ AutoLimitedCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ HRESULT hrc = mParent->i_retainCryptoIf(&pCryptoIf);
+ if (FAILED(hrc)) return hrc; /* Error is set */
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->mstrKeyStore.isNotEmpty())
+ {
+ char *pszCipher = NULL;
+ int vrc = pCryptoIf->pfnCryptoKeyStoreGetDekFromEncoded(mData->mstrKeyStore.c_str(), NULL /*pszPassword*/,
+ NULL /*ppbKey*/, NULL /*pcbKey*/, &pszCipher);
+ if (RT_SUCCESS(vrc))
+ {
+ aCipher = getCipherStringWithoutMode(pszCipher);
+ RTStrFree(pszCipher);
+ aPasswordId = mData->mstrKeyId;
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Failed to query the encryption settings with %Rrc"),
+ vrc);
+ }
+ else
+ hrc = setError(VBOX_E_NOT_SUPPORTED, tr("This VM is not encrypted"));
+
+ mParent->i_releaseCryptoIf(pCryptoIf);
+
+ return hrc;
+#endif
+}
+
+HRESULT Machine::checkEncryptionPassword(const com::Utf8Str &aPassword)
+{
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ RT_NOREF(aPassword);
+ return setError(VBOX_E_NOT_SUPPORTED, tr("Full VM encryption is not available with this build"));
+#else
+ AutoLimitedCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ HRESULT hrc = mParent->i_retainCryptoIf(&pCryptoIf);
+ if (FAILED(hrc)) return hrc; /* Error is set */
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->mstrKeyStore.isNotEmpty())
+ {
+ char *pszCipher = NULL;
+ uint8_t *pbDek = NULL;
+ size_t cbDek = 0;
+ int vrc = pCryptoIf->pfnCryptoKeyStoreGetDekFromEncoded(mData->mstrKeyStore.c_str(), aPassword.c_str(),
+ &pbDek, &cbDek, &pszCipher);
+ if (RT_SUCCESS(vrc))
+ {
+ RTStrFree(pszCipher);
+ RTMemSaferFree(pbDek, cbDek);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_PASSWORD_INCORRECT, vrc,
+ tr("The password supplied for the encrypted machine is incorrect"));
+ }
+ else
+ hrc = setError(VBOX_E_NOT_SUPPORTED, tr("This VM is not encrypted"));
+
+ mParent->i_releaseCryptoIf(pCryptoIf);
+
+ return hrc;
+#endif
+}
+
+HRESULT Machine::addEncryptionPassword(const com::Utf8Str &aId,
+ const com::Utf8Str &aPassword)
+{
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ RT_NOREF(aId, aPassword);
+ return setError(VBOX_E_NOT_SUPPORTED, tr("Full VM encryption is not available with this build"));
+#else
+ AutoLimitedCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ size_t cbPassword = aPassword.length() + 1;
+ uint8_t *pbPassword = (uint8_t *)aPassword.c_str();
+
+ mData->mpKeyStore->addSecretKey(aId, pbPassword, cbPassword);
+
+ if ( mData->mAccessible
+ && mData->mSession.mState == SessionState_Locked
+ && mData->mSession.mLockType == LockType_VM
+ && mData->mSession.mDirectControl != NULL)
+ {
+ /* get the console from the direct session */
+ ComPtr<IConsole> console;
+ HRESULT rc = mData->mSession.mDirectControl->COMGETTER(RemoteConsole)(console.asOutParam());
+ ComAssertComRC(rc);
+ /* send passsword to console */
+ console->AddEncryptionPassword(Bstr(aId).raw(),
+ Bstr(aPassword).raw(),
+ TRUE);
+ }
+
+ if (mData->mstrKeyId == aId)
+ {
+ HRESULT hrc = checkEncryptionPassword(aPassword);
+ if (FAILED(hrc))
+ return hrc;
+
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Encryption is used and password is correct,
+ * Reinit the machine if required.
+ */
+ BOOL fAccessible;
+ alock.release();
+ getAccessible(&fAccessible);
+ alock.acquire();
+ }
+ }
+
+ /*
+ * Add the password into the NvramStore only after
+ * the machine becomes accessible and the NvramStore
+ * contains key id and key store.
+ */
+ if (mNvramStore.isNotNull())
+ mNvramStore->i_addPassword(aId, aPassword);
+
+ return S_OK;
+#endif
+}
+
+HRESULT Machine::addEncryptionPasswords(const std::vector<com::Utf8Str> &aIds,
+ const std::vector<com::Utf8Str> &aPasswords)
+{
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ RT_NOREF(aIds, aPasswords);
+ return setError(VBOX_E_NOT_SUPPORTED, tr("Full VM encryption is not available with this build"));
+#else
+ if (aIds.size() != aPasswords.size())
+ return setError(E_INVALIDARG, tr("Id and passwords arrays must have the same size"));
+
+ HRESULT hrc = S_OK;
+ for (size_t i = 0; i < aIds.size() && SUCCEEDED(hrc); ++i)
+ hrc = addEncryptionPassword(aIds[i], aPasswords[i]);
+
+ return hrc;
+#endif
+}
+
+HRESULT Machine::removeEncryptionPassword(AutoCaller &autoCaller, const com::Utf8Str &aId)
+{
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ RT_NOREF(autoCaller, aId);
+ return setError(VBOX_E_NOT_SUPPORTED, tr("Full VM encryption is not available with this build"));
+#else
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mData->mAccessible
+ && mData->mSession.mState == SessionState_Locked
+ && mData->mSession.mLockType == LockType_VM
+ && mData->mSession.mDirectControl != NULL)
+ {
+ /* get the console from the direct session */
+ ComPtr<IConsole> console;
+ HRESULT rc = mData->mSession.mDirectControl->COMGETTER(RemoteConsole)(console.asOutParam());
+ ComAssertComRC(rc);
+ /* send passsword to console */
+ console->RemoveEncryptionPassword(Bstr(aId).raw());
+ }
+
+ if (mData->mAccessible && mData->mstrKeyStore.isNotEmpty() && mData->mstrKeyId == aId)
+ {
+ if (Global::IsOnlineOrTransient(mData->mMachineState))
+ return setError(VBOX_E_INVALID_VM_STATE, tr("The machine is in online or transient state"));
+ alock.release();
+ autoCaller.release();
+ /* return because all passwords are purged when machine becomes inaccessible; */
+ return i_setInaccessible();
+ }
+
+ if (mNvramStore.isNotNull())
+ mNvramStore->i_removePassword(aId);
+ mData->mpKeyStore->deleteSecretKey(aId);
+ return S_OK;
+#endif
+}
+
+HRESULT Machine::clearAllEncryptionPasswords(AutoCaller &autoCaller)
+{
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ RT_NOREF(autoCaller);
+ return setError(VBOX_E_NOT_SUPPORTED, tr("Full VM encryption is not available with this build"));
+#else
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->mAccessible && mData->mstrKeyStore.isNotEmpty())
+ {
+ if (Global::IsOnlineOrTransient(mData->mMachineState))
+ return setError(VBOX_E_INVALID_VM_STATE, tr("The machine is in online or transient state"));
+ alock.release();
+ autoCaller.release();
+ /* return because all passwords are purged when machine becomes inaccessible; */
+ return i_setInaccessible();
+ }
+
+ mNvramStore->i_removeAllPasswords();
+ mData->mpKeyStore->deleteAllSecretKeys(false, true);
+ return S_OK;
+#endif
+}
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+HRESULT Machine::i_setInaccessible()
+{
+ if (!mData->mAccessible)
+ return S_OK;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ VirtualBox *pParent = mParent;
+ com::Utf8Str strConfigFile = mData->m_strConfigFile;
+ Guid id(i_getId());
+
+ alock.release();
+
+ uninit();
+ HRESULT rc = initFromSettings(pParent, strConfigFile, &id, com::Utf8Str());
+
+ alock.acquire();
+ mParent->i_onMachineStateChanged(mData->mUuid, mData->mMachineState);
+ return rc;
+}
+#endif
+
+/* This isn't handled entirely by the wrapper generator yet. */
+#ifdef VBOX_WITH_XPCOM
+NS_DECL_CLASSINFO(SessionMachine)
+NS_IMPL_THREADSAFE_ISUPPORTS2_CI(SessionMachine, IMachine, IInternalMachineControl)
+
+NS_DECL_CLASSINFO(SnapshotMachine)
+NS_IMPL_THREADSAFE_ISUPPORTS1_CI(SnapshotMachine, IMachine)
+#endif
diff --git a/src/VBox/Main/src-server/MachineImplCloneVM.cpp b/src/VBox/Main/src-server/MachineImplCloneVM.cpp
new file mode 100644
index 00000000..be4b3832
--- /dev/null
+++ b/src/VBox/Main/src-server/MachineImplCloneVM.cpp
@@ -0,0 +1,1698 @@
+/* $Id: MachineImplCloneVM.cpp $ */
+/** @file
+ * Implementation of MachineCloneVM
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <set>
+#include <map>
+#include "MachineImplCloneVM.h"
+
+#include "VirtualBoxImpl.h"
+#include "MediumImpl.h"
+#include "HostImpl.h"
+
+#include <iprt/path.h>
+#include <iprt/dir.h>
+#include <iprt/cpp/utils.h>
+#ifdef DEBUG_poetzsch
+# include <iprt/stream.h>
+#endif
+
+#include <VBox/com/list.h>
+#include <VBox/com/MultiResult.h>
+
+// typedefs
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct
+{
+ Utf8Str strBaseName;
+ ComPtr<IMedium> pMedium;
+ uint32_t uIdx;
+ ULONG uWeight;
+} MEDIUMTASK;
+
+typedef struct
+{
+ RTCList<MEDIUMTASK> chain;
+ DeviceType_T devType;
+ bool fCreateDiffs;
+ bool fAttachLinked;
+} MEDIUMTASKCHAIN;
+
+typedef struct
+{
+ Guid snapshotUuid;
+ Utf8Str strFile;
+ ULONG uWeight;
+} FILECOPYTASK;
+
+// The private class
+/////////////////////////////////////////////////////////////////////////////
+
+struct MachineCloneVMPrivate
+{
+ MachineCloneVMPrivate(MachineCloneVM *a_q, ComObjPtr<Machine> &a_pSrcMachine, ComObjPtr<Machine> &a_pTrgMachine,
+ CloneMode_T a_mode, const RTCList<CloneOptions_T> &opts)
+ : q_ptr(a_q)
+ , p(a_pSrcMachine)
+ , pSrcMachine(a_pSrcMachine)
+ , pTrgMachine(a_pTrgMachine)
+ , mode(a_mode)
+ , options(opts)
+ {}
+
+ DECLARE_TRANSLATE_METHODS(MachineCloneVMPrivate)
+
+ /* Thread management */
+ int startWorker()
+ {
+ return RTThreadCreate(NULL,
+ MachineCloneVMPrivate::workerThread,
+ static_cast<void*>(this),
+ 0,
+ RTTHREADTYPE_MAIN_WORKER,
+ 0,
+ "MachineClone");
+ }
+
+ static DECLCALLBACK(int) workerThread(RTTHREAD /* Thread */, void *pvUser)
+ {
+ MachineCloneVMPrivate *pTask = static_cast<MachineCloneVMPrivate*>(pvUser);
+ AssertReturn(pTask, VERR_INVALID_POINTER);
+
+ HRESULT rc = pTask->q_ptr->run();
+
+ pTask->pProgress->i_notifyComplete(rc);
+
+ pTask->q_ptr->destroy();
+
+ return VINF_SUCCESS;
+ }
+
+ /* Private helper methods */
+
+ /* MachineCloneVM::start helper: */
+ HRESULT createMachineList(const ComPtr<ISnapshot> &pSnapshot, RTCList< ComObjPtr<Machine> > &machineList) const;
+ inline void updateProgressStats(MEDIUMTASKCHAIN &mtc, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight) const;
+ inline HRESULT addSaveState(const ComObjPtr<Machine> &machine, bool fAttachCurrent, ULONG &uCount, ULONG &uTotalWeight);
+ inline HRESULT addNVRAM(const ComObjPtr<Machine> &machine, bool fAttachCurrent, ULONG &uCount, ULONG &uTotalWeight);
+ inline HRESULT queryBaseName(const ComPtr<IMedium> &pMedium, Utf8Str &strBaseName) const;
+ HRESULT queryMediasForMachineState(const RTCList<ComObjPtr<Machine> > &machineList,
+ bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight);
+ HRESULT queryMediasForMachineAndChildStates(const RTCList<ComObjPtr<Machine> > &machineList,
+ bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight);
+ HRESULT queryMediasForAllStates(const RTCList<ComObjPtr<Machine> > &machineList, bool fAttachLinked, ULONG &uCount,
+ ULONG &uTotalWeight);
+
+ /* MachineCloneVM::run helper: */
+ bool findSnapshot(const settings::SnapshotsList &snl, const Guid &id, settings::Snapshot &sn) const;
+ void updateMACAddresses(settings::NetworkAdaptersList &nwl) const;
+ void updateMACAddresses(settings::SnapshotsList &sl) const;
+ void updateStorageLists(settings::StorageControllersList &sc, const Bstr &bstrOldId, const Bstr &bstrNewId) const;
+ void updateSnapshotStorageLists(settings::SnapshotsList &sl, const Bstr &bstrOldId, const Bstr &bstrNewId) const;
+ void updateSaveStateFile(settings::SnapshotsList &snl, const Guid &id, const Utf8Str &strFile) const;
+ void updateNVRAMFile(settings::SnapshotsList &snl, const Guid &id, const Utf8Str &strFile) const;
+ HRESULT createDifferencingMedium(const ComObjPtr<Machine> &pMachine, const ComObjPtr<Medium> &pParent,
+ const Utf8Str &strSnapshotFolder, RTCList<ComObjPtr<Medium> > &newMedia,
+ ComObjPtr<Medium> *ppDiff) const;
+ static DECLCALLBACK(int) copyFileProgress(unsigned uPercentage, void *pvUser);
+ static void updateSnapshotHardwareUUIDs(settings::SnapshotsList &snapshot_list, const Guid &id);
+
+ /* Private q and parent pointer */
+ MachineCloneVM *q_ptr;
+ ComObjPtr<Machine> p;
+
+ /* Private helper members */
+ ComObjPtr<Machine> pSrcMachine;
+ ComObjPtr<Machine> pTrgMachine;
+ ComPtr<IMachine> pOldMachineState;
+ ComObjPtr<Progress> pProgress;
+ Guid snapshotId;
+ CloneMode_T mode;
+ RTCList<CloneOptions_T> options;
+ RTCList<MEDIUMTASKCHAIN> llMedias;
+ RTCList<FILECOPYTASK> llSaveStateFiles; /* Snapshot UUID -> File path */
+ RTCList<FILECOPYTASK> llNVRAMFiles; /* Snapshot UUID -> File path */
+};
+
+HRESULT MachineCloneVMPrivate::createMachineList(const ComPtr<ISnapshot> &pSnapshot,
+ RTCList< ComObjPtr<Machine> > &machineList) const
+{
+ HRESULT rc = S_OK;
+ Bstr name;
+ rc = pSnapshot->COMGETTER(Name)(name.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ ComPtr<IMachine> pMachine;
+ rc = pSnapshot->COMGETTER(Machine)(pMachine.asOutParam());
+ if (FAILED(rc)) return rc;
+ machineList.append((Machine*)(IMachine*)pMachine);
+
+ SafeIfaceArray<ISnapshot> sfaChilds;
+ rc = pSnapshot->COMGETTER(Children)(ComSafeArrayAsOutParam(sfaChilds));
+ if (FAILED(rc)) return rc;
+ for (size_t i = 0; i < sfaChilds.size(); ++i)
+ {
+ rc = createMachineList(sfaChilds[i], machineList);
+ if (FAILED(rc)) return rc;
+ }
+
+ return rc;
+}
+
+void MachineCloneVMPrivate::updateProgressStats(MEDIUMTASKCHAIN &mtc, bool fAttachLinked,
+ ULONG &uCount, ULONG &uTotalWeight) const
+{
+ if (fAttachLinked)
+ {
+ /* Implicit diff creation as part of attach is a pretty cheap
+ * operation, and does only need one operation per attachment. */
+ ++uCount;
+ uTotalWeight += 1; /* 1MB per attachment */
+ }
+ else
+ {
+ /* Currently the copying of diff images involves reading at least
+ * the biggest parent in the previous chain. So even if the new
+ * diff image is small in size, it could need some time to create
+ * it. Adding the biggest size in the chain should balance this a
+ * little bit more, i.e. the weight is the sum of the data which
+ * needs to be read and written. */
+ ULONG uMaxWeight = 0;
+ for (size_t e = mtc.chain.size(); e > 0; --e)
+ {
+ MEDIUMTASK &mt = mtc.chain.at(e - 1);
+ mt.uWeight += uMaxWeight;
+
+ /* Calculate progress data */
+ ++uCount;
+ uTotalWeight += mt.uWeight;
+
+ /* Save the max size for better weighting of diff image
+ * creation. */
+ uMaxWeight = RT_MAX(uMaxWeight, mt.uWeight);
+ }
+ }
+}
+
+HRESULT MachineCloneVMPrivate::addSaveState(const ComObjPtr<Machine> &machine, bool fAttachCurrent, ULONG &uCount, ULONG &uTotalWeight)
+{
+ Bstr bstrSrcSaveStatePath;
+ HRESULT rc = machine->COMGETTER(StateFilePath)(bstrSrcSaveStatePath.asOutParam());
+ if (FAILED(rc)) return rc;
+ if (!bstrSrcSaveStatePath.isEmpty())
+ {
+ FILECOPYTASK fct;
+ if (fAttachCurrent)
+ {
+ /* Make this saved state part of "current state" of the target
+ * machine, whether it is part of a snapshot or not. */
+ fct.snapshotUuid.clear();
+ }
+ else
+ fct.snapshotUuid = machine->i_getSnapshotId();
+ fct.strFile = bstrSrcSaveStatePath;
+ uint64_t cbSize;
+ int vrc = RTFileQuerySizeByPath(fct.strFile.c_str(), &cbSize);
+ if (RT_FAILURE(vrc))
+ return p->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not query file size of '%s' (%Rrc)"),
+ fct.strFile.c_str(), vrc);
+ /* same rule as above: count both the data which needs to
+ * be read and written */
+ fct.uWeight = (ULONG)(2 * (cbSize + _1M - 1) / _1M);
+ llSaveStateFiles.append(fct);
+ ++uCount;
+ uTotalWeight += fct.uWeight;
+ }
+ return S_OK;
+}
+
+HRESULT MachineCloneVMPrivate::addNVRAM(const ComObjPtr<Machine> &machine, bool fAttachCurrent, ULONG &uCount, ULONG &uTotalWeight)
+{
+ Bstr bstrSrcNVRAMPath;
+ ComPtr<INvramStore> pNvramStore;
+ HRESULT rc = machine->COMGETTER(NonVolatileStore)(pNvramStore.asOutParam());
+ if (FAILED(rc)) return rc;
+ rc = pNvramStore->COMGETTER(NonVolatileStorageFile)(bstrSrcNVRAMPath.asOutParam());
+ if (FAILED(rc)) return rc;
+ if (!bstrSrcNVRAMPath.isEmpty())
+ {
+ FILECOPYTASK fct;
+ if (fAttachCurrent)
+ {
+ /* Make this saved state part of "current state" of the target
+ * machine, whether it is part of a snapshot or not. */
+ fct.snapshotUuid.clear();
+ }
+ else
+ fct.snapshotUuid = machine->i_getSnapshotId();
+ fct.strFile = bstrSrcNVRAMPath;
+ if (!RTFileExists(fct.strFile.c_str()))
+ return S_OK;
+ uint64_t cbSize;
+ int vrc = RTFileQuerySizeByPath(fct.strFile.c_str(), &cbSize);
+ if (RT_FAILURE(vrc))
+ return p->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not query file size of '%s' (%Rrc)"),
+ fct.strFile.c_str(), vrc);
+ /* same rule as above: count both the data which needs to
+ * be read and written */
+ fct.uWeight = (ULONG)(2 * (cbSize + _1M - 1) / _1M);
+ llNVRAMFiles.append(fct);
+ ++uCount;
+ uTotalWeight += fct.uWeight;
+ }
+ return S_OK;
+}
+
+HRESULT MachineCloneVMPrivate::queryBaseName(const ComPtr<IMedium> &pMedium, Utf8Str &strBaseName) const
+{
+ ComPtr<IMedium> pBaseMedium;
+ HRESULT rc = pMedium->COMGETTER(Base)(pBaseMedium.asOutParam());
+ if (FAILED(rc)) return rc;
+ Bstr bstrBaseName;
+ rc = pBaseMedium->COMGETTER(Name)(bstrBaseName.asOutParam());
+ if (FAILED(rc)) return rc;
+ strBaseName = bstrBaseName;
+ return rc;
+}
+
+HRESULT MachineCloneVMPrivate::queryMediasForMachineState(const RTCList<ComObjPtr<Machine> > &machineList,
+ bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight)
+{
+ /* This mode is pretty straightforward. We didn't need to know about any
+ * parent/children relationship and therefore simply adding all directly
+ * attached images of the source VM as cloning targets. The IMedium code
+ * take than care to merge any (possibly) existing parents into the new
+ * image. */
+ HRESULT rc = S_OK;
+ for (size_t i = 0; i < machineList.size(); ++i)
+ {
+ const ComObjPtr<Machine> &machine = machineList.at(i);
+ /* If this is the Snapshot Machine we want to clone, we need to
+ * create a new diff file for the new "current state". */
+ const bool fCreateDiffs = (machine == pOldMachineState);
+ /* Add all attachments of the different machines to a worker list. */
+ SafeIfaceArray<IMediumAttachment> sfaAttachments;
+ rc = machine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
+ if (FAILED(rc)) return rc;
+ for (size_t a = 0; a < sfaAttachments.size(); ++a)
+ {
+ const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
+ DeviceType_T type;
+ rc = pAtt->COMGETTER(Type)(&type);
+ if (FAILED(rc)) return rc;
+
+ /* Only harddisks and floppies are of interest. */
+ if ( type != DeviceType_HardDisk
+ && type != DeviceType_Floppy)
+ continue;
+
+ /* Valid medium attached? */
+ ComPtr<IMedium> pSrcMedium;
+ rc = pAtt->COMGETTER(Medium)(pSrcMedium.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ if (pSrcMedium.isNull())
+ continue;
+
+ /* Create the medium task chain. In this case it will always
+ * contain one image only. */
+ MEDIUMTASKCHAIN mtc;
+ mtc.devType = type;
+ mtc.fCreateDiffs = fCreateDiffs;
+ mtc.fAttachLinked = fAttachLinked;
+
+ /* Refresh the state so that the file size get read. */
+ MediumState_T e;
+ rc = pSrcMedium->RefreshState(&e);
+ if (FAILED(rc)) return rc;
+ LONG64 lSize;
+ rc = pSrcMedium->COMGETTER(Size)(&lSize);
+ if (FAILED(rc)) return rc;
+
+ MEDIUMTASK mt;
+ mt.uIdx = UINT32_MAX; /* No read/write optimization possible. */
+
+ /* Save the base name. */
+ rc = queryBaseName(pSrcMedium, mt.strBaseName);
+ if (FAILED(rc)) return rc;
+
+ /* Save the current medium, for later cloning. */
+ mt.pMedium = pSrcMedium;
+ if (fAttachLinked)
+ mt.uWeight = 0; /* dummy */
+ else
+ mt.uWeight = (ULONG)((lSize + _1M - 1) / _1M);
+ mtc.chain.append(mt);
+
+ /* Update the progress info. */
+ updateProgressStats(mtc, fAttachLinked, uCount, uTotalWeight);
+ /* Append the list of images which have to be cloned. */
+ llMedias.append(mtc);
+ }
+ /* Add the save state file of this machine if there is one. */
+ rc = addSaveState(machine, true /*fAttachCurrent*/, uCount, uTotalWeight);
+ if (FAILED(rc)) return rc;
+ /* Add the NVRAM file of this machine if there is one. */
+ rc = addNVRAM(machine, true /*fAttachCurrent*/, uCount, uTotalWeight);
+ if (FAILED(rc)) return rc;
+ }
+
+ return rc;
+}
+
+HRESULT MachineCloneVMPrivate::queryMediasForMachineAndChildStates(const RTCList<ComObjPtr<Machine> > &machineList,
+ bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight)
+{
+ /* This is basically a three step approach. First select all medias
+ * directly or indirectly involved in the clone. Second create a histogram
+ * of the usage of all that medias. Third select the medias which are
+ * directly attached or have more than one directly/indirectly used child
+ * in the new clone. Step one and two are done in the first loop.
+ *
+ * Example of the histogram counts after going through 3 attachments from
+ * bottom to top:
+ *
+ * 3
+ * |
+ * -> 3
+ * / \
+ * 2 1 <-
+ * /
+ * -> 2
+ * / \
+ * -> 1 1
+ * \
+ * 1 <-
+ *
+ * Whenever the histogram count is changing compared to the previous one we
+ * need to include that image in the cloning step (Marked with <-). If we
+ * start at zero even the directly attached images are automatically
+ * included.
+ *
+ * Note: This still leads to media chains which can have the same medium
+ * included. This case is handled in "run" and therefore not critical, but
+ * it leads to wrong progress infos which isn't nice. */
+
+ Assert(!fAttachLinked);
+ HRESULT rc = S_OK;
+ std::map<ComPtr<IMedium>, uint32_t> mediaHist; /* Our usage histogram for the medias */
+ for (size_t i = 0; i < machineList.size(); ++i)
+ {
+ const ComObjPtr<Machine> &machine = machineList.at(i);
+ /* If this is the Snapshot Machine we want to clone, we need to
+ * create a new diff file for the new "current state". */
+ const bool fCreateDiffs = (machine == pOldMachineState);
+ /* Add all attachments (and their parents) of the different
+ * machines to a worker list. */
+ SafeIfaceArray<IMediumAttachment> sfaAttachments;
+ rc = machine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
+ if (FAILED(rc)) return rc;
+ for (size_t a = 0; a < sfaAttachments.size(); ++a)
+ {
+ const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
+ DeviceType_T type;
+ rc = pAtt->COMGETTER(Type)(&type);
+ if (FAILED(rc)) return rc;
+
+ /* Only harddisks and floppies are of interest. */
+ if ( type != DeviceType_HardDisk
+ && type != DeviceType_Floppy)
+ continue;
+
+ /* Valid medium attached? */
+ ComPtr<IMedium> pSrcMedium;
+ rc = pAtt->COMGETTER(Medium)(pSrcMedium.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ if (pSrcMedium.isNull())
+ continue;
+
+ MEDIUMTASKCHAIN mtc;
+ mtc.devType = type;
+ mtc.fCreateDiffs = fCreateDiffs;
+ mtc.fAttachLinked = fAttachLinked;
+
+ while (!pSrcMedium.isNull())
+ {
+ /* Build a histogram of used medias and the parent chain. */
+ ++mediaHist[pSrcMedium];
+
+ /* Refresh the state so that the file size get read. */
+ MediumState_T e;
+ rc = pSrcMedium->RefreshState(&e);
+ if (FAILED(rc)) return rc;
+ LONG64 lSize;
+ rc = pSrcMedium->COMGETTER(Size)(&lSize);
+ if (FAILED(rc)) return rc;
+
+ MEDIUMTASK mt;
+ mt.uIdx = UINT32_MAX;
+ mt.pMedium = pSrcMedium;
+ mt.uWeight = (ULONG)((lSize + _1M - 1) / _1M);
+ mtc.chain.append(mt);
+
+ /* Query next parent. */
+ rc = pSrcMedium->COMGETTER(Parent)(pSrcMedium.asOutParam());
+ if (FAILED(rc)) return rc;
+ }
+
+ llMedias.append(mtc);
+ }
+ /* Add the save state file of this machine if there is one. */
+ rc = addSaveState(machine, false /*fAttachCurrent*/, uCount, uTotalWeight);
+ if (FAILED(rc)) return rc;
+ /* Add the NVRAM file of this machine if there is one. */
+ rc = addNVRAM(machine, false /*fAttachCurrent*/, uCount, uTotalWeight);
+ if (FAILED(rc)) return rc;
+ /* If this is the newly created current state, make sure that the
+ * saved state and NVRAM is also attached to it. */
+ if (fCreateDiffs)
+ {
+ rc = addSaveState(machine, true /*fAttachCurrent*/, uCount, uTotalWeight);
+ if (FAILED(rc)) return rc;
+ rc = addNVRAM(machine, true /*fAttachCurrent*/, uCount, uTotalWeight);
+ if (FAILED(rc)) return rc;
+ }
+ }
+ /* Build up the index list of the image chain. Unfortunately we can't do
+ * that in the previous loop, cause there we go from child -> parent and
+ * didn't know how many are between. */
+ for (size_t i = 0; i < llMedias.size(); ++i)
+ {
+ uint32_t uIdx = 0;
+ MEDIUMTASKCHAIN &mtc = llMedias.at(i);
+ for (size_t a = mtc.chain.size(); a > 0; --a)
+ mtc.chain[a - 1].uIdx = uIdx++;
+ }
+#ifdef DEBUG_poetzsch
+ /* Print the histogram */
+ std::map<ComPtr<IMedium>, uint32_t>::iterator it;
+ for (it = mediaHist.begin(); it != mediaHist.end(); ++it)
+ {
+ Bstr bstrSrcName;
+ rc = (*it).first->COMGETTER(Name)(bstrSrcName.asOutParam());
+ if (FAILED(rc)) return rc;
+ RTPrintf("%ls: %d\n", bstrSrcName.raw(), (*it).second);
+ }
+#endif
+ /* Go over every medium in the list and check if it either a directly
+ * attached disk or has more than one children. If so it needs to be
+ * replicated. Also we have to make sure that any direct or indirect
+ * children knows of the new parent (which doesn't necessarily mean it
+ * is a direct children in the source chain). */
+ for (size_t i = 0; i < llMedias.size(); ++i)
+ {
+ MEDIUMTASKCHAIN &mtc = llMedias.at(i);
+ RTCList<MEDIUMTASK> newChain;
+ uint32_t used = 0;
+ for (size_t a = 0; a < mtc.chain.size(); ++a)
+ {
+ const MEDIUMTASK &mt = mtc.chain.at(a);
+ uint32_t hist = mediaHist[mt.pMedium];
+#ifdef DEBUG_poetzsch
+ Bstr bstrSrcName;
+ rc = mt.pMedium->COMGETTER(Name)(bstrSrcName.asOutParam());
+ if (FAILED(rc)) return rc;
+ RTPrintf("%ls: %d (%d)\n", bstrSrcName.raw(), hist, used);
+#endif
+ /* Check if there is a "step" in the histogram when going the chain
+ * upwards. If so, we need this image, cause there is another branch
+ * from here in the cloned VM. */
+ if (hist > used)
+ {
+ newChain.append(mt);
+ used = hist;
+ }
+ }
+ /* Make sure we always using the old base name as new base name, even
+ * if the base is a differencing image in the source VM (with the UUID
+ * as name). */
+ rc = queryBaseName(newChain.last().pMedium, newChain.last().strBaseName);
+ if (FAILED(rc)) return rc;
+ /* Update the old medium chain with the updated one. */
+ mtc.chain = newChain;
+ /* Update the progress info. */
+ updateProgressStats(mtc, fAttachLinked, uCount, uTotalWeight);
+ }
+
+ return rc;
+}
+
+HRESULT MachineCloneVMPrivate::queryMediasForAllStates(const RTCList<ComObjPtr<Machine> > &machineList,
+ bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight)
+{
+ /* In this case we create a exact copy of the original VM. This means just
+ * adding all directly and indirectly attached disk images to the worker
+ * list. */
+ Assert(!fAttachLinked);
+ HRESULT rc = S_OK;
+ for (size_t i = 0; i < machineList.size(); ++i)
+ {
+ const ComObjPtr<Machine> &machine = machineList.at(i);
+ /* If this is the Snapshot Machine we want to clone, we need to
+ * create a new diff file for the new "current state". */
+ const bool fCreateDiffs = (machine == pOldMachineState);
+ /* Add all attachments (and their parents) of the different
+ * machines to a worker list. */
+ SafeIfaceArray<IMediumAttachment> sfaAttachments;
+ rc = machine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
+ if (FAILED(rc)) return rc;
+ for (size_t a = 0; a < sfaAttachments.size(); ++a)
+ {
+ const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
+ DeviceType_T type;
+ rc = pAtt->COMGETTER(Type)(&type);
+ if (FAILED(rc)) return rc;
+
+ /* Only harddisks and floppies are of interest. */
+ if ( type != DeviceType_HardDisk
+ && type != DeviceType_Floppy)
+ continue;
+
+ /* Valid medium attached? */
+ ComPtr<IMedium> pSrcMedium;
+ rc = pAtt->COMGETTER(Medium)(pSrcMedium.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ if (pSrcMedium.isNull())
+ continue;
+
+ /* Build up a child->parent list of this attachment. (Note: we are
+ * not interested of any child's not attached to this VM. So this
+ * will not create a full copy of the base/child relationship.) */
+ MEDIUMTASKCHAIN mtc;
+ mtc.devType = type;
+ mtc.fCreateDiffs = fCreateDiffs;
+ mtc.fAttachLinked = fAttachLinked;
+
+ while (!pSrcMedium.isNull())
+ {
+ /* Refresh the state so that the file size get read. */
+ MediumState_T e;
+ rc = pSrcMedium->RefreshState(&e);
+ if (FAILED(rc)) return rc;
+ LONG64 lSize;
+ rc = pSrcMedium->COMGETTER(Size)(&lSize);
+ if (FAILED(rc)) return rc;
+
+ /* Save the current medium, for later cloning. */
+ MEDIUMTASK mt;
+ mt.uIdx = UINT32_MAX;
+ mt.pMedium = pSrcMedium;
+ mt.uWeight = (ULONG)((lSize + _1M - 1) / _1M);
+ mtc.chain.append(mt);
+
+ /* Query next parent. */
+ rc = pSrcMedium->COMGETTER(Parent)(pSrcMedium.asOutParam());
+ if (FAILED(rc)) return rc;
+ }
+ /* Update the progress info. */
+ updateProgressStats(mtc, fAttachLinked, uCount, uTotalWeight);
+ /* Append the list of images which have to be cloned. */
+ llMedias.append(mtc);
+ }
+ /* Add the save state file of this machine if there is one. */
+ rc = addSaveState(machine, false /*fAttachCurrent*/, uCount, uTotalWeight);
+ if (FAILED(rc)) return rc;
+ /* Add the NVRAM file of this machine if there is one. */
+ rc = addNVRAM(machine, false /*fAttachCurrent*/, uCount, uTotalWeight);
+ if (FAILED(rc)) return rc;
+ /* If this is the newly created current state, make sure that the
+ * saved state is also attached to it. */
+ if (fCreateDiffs)
+ {
+ rc = addSaveState(machine, true /*fAttachCurrent*/, uCount, uTotalWeight);
+ if (FAILED(rc)) return rc;
+ rc = addNVRAM(machine, true /*fAttachCurrent*/, uCount, uTotalWeight);
+ if (FAILED(rc)) return rc;
+ }
+ }
+ /* Build up the index list of the image chain. Unfortunately we can't do
+ * that in the previous loop, cause there we go from child -> parent and
+ * didn't know how many are between. */
+ for (size_t i = 0; i < llMedias.size(); ++i)
+ {
+ uint32_t uIdx = 0;
+ MEDIUMTASKCHAIN &mtc = llMedias.at(i);
+ for (size_t a = mtc.chain.size(); a > 0; --a)
+ mtc.chain[a - 1].uIdx = uIdx++;
+ }
+
+ return rc;
+}
+
+bool MachineCloneVMPrivate::findSnapshot(const settings::SnapshotsList &snl, const Guid &id, settings::Snapshot &sn) const
+{
+ settings::SnapshotsList::const_iterator it;
+ for (it = snl.begin(); it != snl.end(); ++it)
+ {
+ if (it->uuid == id)
+ {
+ sn = (*it);
+ return true;
+ }
+ else if (!it->llChildSnapshots.empty())
+ {
+ if (findSnapshot(it->llChildSnapshots, id, sn))
+ return true;
+ }
+ }
+ return false;
+}
+
+void MachineCloneVMPrivate::updateMACAddresses(settings::NetworkAdaptersList &nwl) const
+{
+ const bool fNotNAT = options.contains(CloneOptions_KeepNATMACs);
+ settings::NetworkAdaptersList::iterator it;
+ for (it = nwl.begin(); it != nwl.end(); ++it)
+ {
+ if ( fNotNAT
+ && it->mode == NetworkAttachmentType_NAT)
+ continue;
+ Host::i_generateMACAddress(it->strMACAddress);
+ }
+}
+
+void MachineCloneVMPrivate::updateMACAddresses(settings::SnapshotsList &sl) const
+{
+ settings::SnapshotsList::iterator it;
+ for (it = sl.begin(); it != sl.end(); ++it)
+ {
+ updateMACAddresses(it->hardware.llNetworkAdapters);
+ if (!it->llChildSnapshots.empty())
+ updateMACAddresses(it->llChildSnapshots);
+ }
+}
+
+void MachineCloneVMPrivate::updateStorageLists(settings::StorageControllersList &sc,
+ const Bstr &bstrOldId, const Bstr &bstrNewId) const
+{
+ settings::StorageControllersList::iterator it3;
+ for (it3 = sc.begin();
+ it3 != sc.end();
+ ++it3)
+ {
+ settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
+ settings::AttachedDevicesList::iterator it4;
+ for (it4 = llAttachments.begin();
+ it4 != llAttachments.end();
+ ++it4)
+ {
+ if ( ( it4->deviceType == DeviceType_HardDisk
+ || it4->deviceType == DeviceType_Floppy)
+ && it4->uuid == bstrOldId)
+ {
+ it4->uuid = bstrNewId;
+ }
+ }
+ }
+}
+
+void MachineCloneVMPrivate::updateSnapshotStorageLists(settings::SnapshotsList &sl, const Bstr &bstrOldId,
+ const Bstr &bstrNewId) const
+{
+ settings::SnapshotsList::iterator it;
+ for ( it = sl.begin();
+ it != sl.end();
+ ++it)
+ {
+ updateStorageLists(it->hardware.storage.llStorageControllers, bstrOldId, bstrNewId);
+ if (!it->llChildSnapshots.empty())
+ updateSnapshotStorageLists(it->llChildSnapshots, bstrOldId, bstrNewId);
+ }
+}
+
+void MachineCloneVMPrivate::updateSaveStateFile(settings::SnapshotsList &snl, const Guid &id, const Utf8Str &strFile) const
+{
+ settings::SnapshotsList::iterator it;
+ for (it = snl.begin(); it != snl.end(); ++it)
+ {
+ if (it->uuid == id)
+ it->strStateFile = strFile;
+ else if (!it->llChildSnapshots.empty())
+ updateSaveStateFile(it->llChildSnapshots, id, strFile);
+ }
+}
+
+void MachineCloneVMPrivate::updateNVRAMFile(settings::SnapshotsList &snl, const Guid &id, const Utf8Str &strFile) const
+{
+ settings::SnapshotsList::iterator it;
+ for (it = snl.begin(); it != snl.end(); ++it)
+ {
+ if (it->uuid == id)
+ it->hardware.nvramSettings.strNvramPath = strFile;
+ else if (!it->llChildSnapshots.empty())
+ updateNVRAMFile(it->llChildSnapshots, id, strFile);
+ }
+}
+
+HRESULT MachineCloneVMPrivate::createDifferencingMedium(const ComObjPtr<Machine> &pMachine, const ComObjPtr<Medium> &pParent,
+ const Utf8Str &strSnapshotFolder, RTCList<ComObjPtr<Medium> > &newMedia,
+ ComObjPtr<Medium> *ppDiff) const
+{
+ HRESULT rc = S_OK;
+ try
+ {
+ // check validity of parent object
+ {
+ AutoReadLock alock(pParent COMMA_LOCKVAL_SRC_POS);
+ Bstr bstrSrcId;
+ rc = pParent->COMGETTER(Id)(bstrSrcId.asOutParam());
+ if (FAILED(rc)) throw rc;
+ }
+ ComObjPtr<Medium> diff;
+ diff.createObject();
+ rc = diff->init(p->i_getVirtualBox(),
+ pParent->i_getPreferredDiffFormat(),
+ Utf8StrFmt("%s%c", strSnapshotFolder.c_str(), RTPATH_DELIMITER),
+ Guid::Empty /* empty media registry */,
+ DeviceType_HardDisk);
+ if (FAILED(rc)) throw rc;
+
+ MediumLockList *pMediumLockList(new MediumLockList());
+ rc = diff->i_createMediumLockList(true /* fFailIfInaccessible */,
+ diff /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ pParent,
+ *pMediumLockList);
+ if (FAILED(rc)) throw rc;
+ rc = pMediumLockList->Lock();
+ if (FAILED(rc)) throw rc;
+
+ /* this already registers the new diff image */
+ rc = pParent->i_createDiffStorage(diff,
+ pParent->i_getPreferredDiffVariant(),
+ pMediumLockList,
+ NULL /* aProgress */,
+ true /* aWait */,
+ false /* aNotify */);
+ delete pMediumLockList;
+ if (FAILED(rc)) throw rc;
+ /* Remember created medium. */
+ newMedia.append(diff);
+ *ppDiff = diff;
+ }
+ catch (HRESULT rc2)
+ {
+ rc = rc2;
+ }
+ catch (...)
+ {
+ rc = VirtualBoxBase::handleUnexpectedExceptions(pMachine, RT_SRC_POS);
+ }
+
+ return rc;
+}
+
+/* static */
+DECLCALLBACK(int) MachineCloneVMPrivate::copyFileProgress(unsigned uPercentage, void *pvUser)
+{
+ ComObjPtr<Progress> pProgress = *static_cast< ComObjPtr<Progress>* >(pvUser);
+
+ BOOL fCanceled = false;
+ HRESULT rc = pProgress->COMGETTER(Canceled)(&fCanceled);
+ if (FAILED(rc)) return VERR_GENERAL_FAILURE;
+ /* If canceled by the user tell it to the copy operation. */
+ if (fCanceled) return VERR_CANCELLED;
+ /* Set the new process. */
+ rc = pProgress->SetCurrentOperationProgress(uPercentage);
+ if (FAILED(rc)) return VERR_GENERAL_FAILURE;
+
+ return VINF_SUCCESS;
+}
+
+void MachineCloneVMPrivate::updateSnapshotHardwareUUIDs(settings::SnapshotsList &snapshot_list, const Guid &id)
+{
+ for (settings::SnapshotsList::iterator snapshot_it = snapshot_list.begin();
+ snapshot_it != snapshot_list.end();
+ ++snapshot_it)
+ {
+ if (!snapshot_it->hardware.uuid.isValid() || snapshot_it->hardware.uuid.isZero())
+ snapshot_it->hardware.uuid = id;
+ updateSnapshotHardwareUUIDs(snapshot_it->llChildSnapshots, id);
+ }
+}
+
+// The public class
+/////////////////////////////////////////////////////////////////////////////
+
+MachineCloneVM::MachineCloneVM(ComObjPtr<Machine> pSrcMachine, ComObjPtr<Machine> pTrgMachine, CloneMode_T mode,
+ const RTCList<CloneOptions_T> &opts) :
+ d_ptr(new MachineCloneVMPrivate(this, pSrcMachine, pTrgMachine, mode, opts))
+{
+}
+
+MachineCloneVM::~MachineCloneVM()
+{
+ delete d_ptr;
+}
+
+HRESULT MachineCloneVM::start(IProgress **pProgress)
+{
+ DPTR(MachineCloneVM);
+ ComObjPtr<Machine> &p = d->p;
+
+ HRESULT rc;
+ try
+ {
+ /** @todo r=klaus this code cannot deal with someone crazy specifying
+ * IMachine corresponding to a mutable machine as d->pSrcMachine */
+ if (d->pSrcMachine->i_isSessionMachine())
+ throw p->setError(E_INVALIDARG, tr("The source machine is mutable"));
+
+ /* Handle the special case that someone is requesting a _full_ clone
+ * with all snapshots (and the current state), but uses a snapshot
+ * machine (and not the current one) as source machine. In this case we
+ * just replace the source (snapshot) machine with the current machine. */
+ if ( d->mode == CloneMode_AllStates
+ && d->pSrcMachine->i_isSnapshotMachine())
+ {
+ Bstr bstrSrcMachineId;
+ rc = d->pSrcMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
+ if (FAILED(rc)) throw rc;
+ ComPtr<IMachine> newSrcMachine;
+ rc = d->pSrcMachine->i_getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), newSrcMachine.asOutParam());
+ if (FAILED(rc)) throw rc;
+ d->pSrcMachine = (Machine*)(IMachine*)newSrcMachine;
+ }
+ bool fSubtreeIncludesCurrent = false;
+ ComObjPtr<Machine> pCurrState;
+ if (d->mode == CloneMode_MachineAndChildStates)
+ {
+ if (d->pSrcMachine->i_isSnapshotMachine())
+ {
+ /* find machine object for current snapshot of current state */
+ Bstr bstrSrcMachineId;
+ rc = d->pSrcMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
+ if (FAILED(rc)) throw rc;
+ ComPtr<IMachine> pCurr;
+ rc = d->pSrcMachine->i_getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), pCurr.asOutParam());
+ if (FAILED(rc)) throw rc;
+ if (pCurr.isNull())
+ throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
+ pCurrState = (Machine *)(IMachine *)pCurr;
+ ComPtr<ISnapshot> pSnapshot;
+ rc = pCurrState->COMGETTER(CurrentSnapshot)(pSnapshot.asOutParam());
+ if (FAILED(rc)) throw rc;
+ if (pSnapshot.isNull())
+ throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
+ ComPtr<IMachine> pCurrSnapMachine;
+ rc = pSnapshot->COMGETTER(Machine)(pCurrSnapMachine.asOutParam());
+ if (FAILED(rc)) throw rc;
+ if (pCurrSnapMachine.isNull())
+ throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
+
+ /* now check if there is a parent chain which leads to the
+ * snapshot machine defining the subtree. */
+ while (!pSnapshot.isNull())
+ {
+ ComPtr<IMachine> pSnapMachine;
+ rc = pSnapshot->COMGETTER(Machine)(pSnapMachine.asOutParam());
+ if (FAILED(rc)) throw rc;
+ if (pSnapMachine.isNull())
+ throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
+ if (pSnapMachine == d->pSrcMachine)
+ {
+ fSubtreeIncludesCurrent = true;
+ break;
+ }
+ rc = pSnapshot->COMGETTER(Parent)(pSnapshot.asOutParam());
+ if (FAILED(rc)) throw rc;
+ }
+ }
+ else
+ {
+ /* If the subtree is only the Current State simply use the
+ * 'machine' case for cloning. It is easier to understand. */
+ d->mode = CloneMode_MachineState;
+ }
+ }
+
+ /* Lock the target machine early (so nobody mess around with it in the meantime). */
+ AutoWriteLock trgLock(d->pTrgMachine COMMA_LOCKVAL_SRC_POS);
+
+ if (d->pSrcMachine->i_isSnapshotMachine())
+ d->snapshotId = d->pSrcMachine->i_getSnapshotId();
+
+ /* Add the current machine and all snapshot machines below this machine
+ * in a list for further processing. */
+ RTCList< ComObjPtr<Machine> > machineList;
+
+ /* Include current state? */
+ if ( d->mode == CloneMode_MachineState
+ || d->mode == CloneMode_AllStates)
+ machineList.append(d->pSrcMachine);
+ /* Should be done a depth copy with all child snapshots? */
+ if ( d->mode == CloneMode_MachineAndChildStates
+ || d->mode == CloneMode_AllStates)
+ {
+ ULONG cSnapshots = 0;
+ rc = d->pSrcMachine->COMGETTER(SnapshotCount)(&cSnapshots);
+ if (FAILED(rc)) throw rc;
+ if (cSnapshots > 0)
+ {
+ Utf8Str id;
+ if (d->mode == CloneMode_MachineAndChildStates)
+ id = d->snapshotId.toString();
+ ComPtr<ISnapshot> pSnapshot;
+ rc = d->pSrcMachine->FindSnapshot(Bstr(id).raw(), pSnapshot.asOutParam());
+ if (FAILED(rc)) throw rc;
+ rc = d->createMachineList(pSnapshot, machineList);
+ if (FAILED(rc)) throw rc;
+ if (d->mode == CloneMode_MachineAndChildStates)
+ {
+ if (fSubtreeIncludesCurrent)
+ {
+ if (pCurrState.isNull())
+ throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
+ machineList.append(pCurrState);
+ }
+ else
+ {
+ rc = pSnapshot->COMGETTER(Machine)(d->pOldMachineState.asOutParam());
+ if (FAILED(rc)) throw rc;
+ }
+ }
+ }
+ }
+
+ /* We have different approaches for getting the medias which needs to
+ * be replicated based on the clone mode the user requested (this is
+ * mostly about the full clone mode).
+ * MachineState:
+ * - Only the images which are directly attached to an source VM will
+ * be cloned. Any parent disks in the original chain will be merged
+ * into the final cloned disk.
+ * MachineAndChildStates:
+ * - In this case we search for images which have more than one
+ * children in the cloned VM or are directly attached to the new VM.
+ * All others will be merged into the remaining images which are
+ * cloned.
+ * This case is the most complicated one and needs several iterations
+ * to make sure we are only cloning images which are really
+ * necessary.
+ * AllStates:
+ * - All disks which are directly or indirectly attached to the
+ * original VM are cloned.
+ *
+ * Note: If you change something generic in one of the methods its
+ * likely that it need to be changed in the others as well! */
+ ULONG uCount = 2; /* One init task and the machine creation. */
+ ULONG uTotalWeight = 2; /* The init task and the machine creation is worth one. */
+ bool fAttachLinked = d->options.contains(CloneOptions_Link); /* Linked clones requested? */
+ switch (d->mode)
+ {
+ case CloneMode_MachineState:
+ d->queryMediasForMachineState(machineList, fAttachLinked, uCount, uTotalWeight);
+ break;
+ case CloneMode_MachineAndChildStates:
+ d->queryMediasForMachineAndChildStates(machineList, fAttachLinked, uCount, uTotalWeight);
+ break;
+ case CloneMode_AllStates:
+ d->queryMediasForAllStates(machineList, fAttachLinked, uCount, uTotalWeight);
+ break;
+#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
+ case CloneMode_32BitHack: /* (compiler warnings) */
+ AssertFailedBreak();
+#endif
+ }
+
+ /* Now create the progress project, so the user knows whats going on. */
+ rc = d->pProgress.createObject();
+ if (FAILED(rc)) throw rc;
+ rc = d->pProgress->init(p->i_getVirtualBox(),
+ static_cast<IMachine*>(d->pSrcMachine) /* aInitiator */,
+ Bstr(tr("Cloning Machine")).raw(),
+ true /* fCancellable */,
+ uCount,
+ uTotalWeight,
+ Bstr(tr("Initialize Cloning")).raw(),
+ 1);
+ if (FAILED(rc)) throw rc;
+
+ int vrc = d->startWorker();
+
+ if (RT_FAILURE(vrc))
+ p->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not create machine clone thread (%Rrc)"), vrc);
+ }
+ catch (HRESULT rc2)
+ {
+ rc = rc2;
+ }
+
+ if (SUCCEEDED(rc))
+ d->pProgress.queryInterfaceTo(pProgress);
+
+ return rc;
+}
+
+HRESULT MachineCloneVM::run()
+{
+ DPTR(MachineCloneVM);
+ ComObjPtr<Machine> &p = d->p;
+
+ AutoCaller autoCaller(p);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock srcLock(p COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock trgLock(d->pTrgMachine COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ /*
+ * Todo:
+ * - What about log files?
+ */
+
+ /* Where should all the media go? */
+ Utf8Str strTrgSnapshotFolder;
+ Utf8Str strTrgMachineFolder = d->pTrgMachine->i_getSettingsFileFull();
+ strTrgMachineFolder.stripFilename();
+
+ RTCList<ComObjPtr<Medium> > newMedia; /* All created images */
+ RTCList<Utf8Str> newFiles; /* All extra created files (save states, ...) */
+ std::set<ComObjPtr<Medium> > pMediumsForNotify;
+ std::map<Guid, DeviceType_T> uIdsForNotify;
+ try
+ {
+ /* Copy all the configuration from this machine to an empty
+ * configuration dataset. */
+ settings::MachineConfigFile trgMCF = *d->pSrcMachine->mData->pMachineConfigFile;
+
+ /* keep source machine hardware UUID if enabled*/
+ if (d->options.contains(CloneOptions_KeepHwUUIDs))
+ {
+ /* because HW UUIDs must be preserved including snapshots by the option,
+ * just fill zero UUIDs with corresponding machine UUID before any snapshot
+ * processing will take place, while all uuids are from source machine */
+ if (!trgMCF.hardwareMachine.uuid.isValid() || trgMCF.hardwareMachine.uuid.isZero())
+ trgMCF.hardwareMachine.uuid = trgMCF.uuid;
+
+ MachineCloneVMPrivate::updateSnapshotHardwareUUIDs(trgMCF.llFirstSnapshot, trgMCF.uuid);
+ }
+
+
+ /* Reset media registry. */
+ trgMCF.mediaRegistry.llHardDisks.clear();
+ trgMCF.mediaRegistry.llDvdImages.clear();
+ trgMCF.mediaRegistry.llFloppyImages.clear();
+ /* If we got a valid snapshot id, replace the hardware/storage section
+ * with the stuff from the snapshot. */
+ settings::Snapshot sn;
+
+ if (d->snapshotId.isValid() && !d->snapshotId.isZero())
+ if (!d->findSnapshot(trgMCF.llFirstSnapshot, d->snapshotId, sn))
+ throw p->setError(E_FAIL,
+ tr("Could not find data to snapshots '%s'"), d->snapshotId.toString().c_str());
+
+ if (d->mode == CloneMode_MachineState)
+ {
+ if (sn.uuid.isValid() && !sn.uuid.isZero())
+ trgMCF.hardwareMachine = sn.hardware;
+
+ /* Remove any hint on snapshots. */
+ trgMCF.llFirstSnapshot.clear();
+ trgMCF.uuidCurrentSnapshot.clear();
+ }
+ else if ( d->mode == CloneMode_MachineAndChildStates
+ && sn.uuid.isValid()
+ && !sn.uuid.isZero())
+ {
+ if (!d->pOldMachineState.isNull())
+ {
+ /* Copy the snapshot data to the current machine. */
+ trgMCF.hardwareMachine = sn.hardware;
+
+ /* Current state is under root snapshot. */
+ trgMCF.uuidCurrentSnapshot = sn.uuid;
+ }
+ /* The snapshot will be the root one. */
+ trgMCF.llFirstSnapshot.clear();
+ trgMCF.llFirstSnapshot.push_back(sn);
+ }
+
+ /* Generate new MAC addresses for all machines when not forbidden. */
+ if (!d->options.contains(CloneOptions_KeepAllMACs))
+ {
+ d->updateMACAddresses(trgMCF.hardwareMachine.llNetworkAdapters);
+ d->updateMACAddresses(trgMCF.llFirstSnapshot);
+ }
+
+ /* When the current snapshot folder is absolute we reset it to the
+ * default relative folder. */
+ if (RTPathStartsWithRoot(trgMCF.machineUserData.strSnapshotFolder.c_str()))
+ trgMCF.machineUserData.strSnapshotFolder = "Snapshots";
+ trgMCF.strStateFile = "";
+ /* Set the new name. */
+ const Utf8Str strOldVMName = trgMCF.machineUserData.strName;
+ trgMCF.machineUserData.strName = d->pTrgMachine->mUserData->s.strName;
+ trgMCF.uuid = d->pTrgMachine->mData->mUuid;
+
+ Bstr bstrSrcSnapshotFolder;
+ rc = d->pSrcMachine->COMGETTER(SnapshotFolder)(bstrSrcSnapshotFolder.asOutParam());
+ if (FAILED(rc)) throw rc;
+ /* The absolute name of the snapshot folder. */
+ strTrgSnapshotFolder = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER,
+ trgMCF.machineUserData.strSnapshotFolder.c_str());
+
+ /* Should we rename the disk names. */
+ bool fKeepDiskNames = d->options.contains(CloneOptions_KeepDiskNames);
+
+ /* We need to create a map with the already created medias. This is
+ * necessary, cause different snapshots could have the same
+ * parents/parent chain. If a medium is in this map already, it isn't
+ * cloned a second time, but simply used. */
+ typedef std::map<Utf8Str, ComObjPtr<Medium> > TStrMediumMap;
+ typedef std::pair<Utf8Str, ComObjPtr<Medium> > TStrMediumPair;
+ TStrMediumMap map;
+ size_t cDisks = 0;
+ for (size_t i = 0; i < d->llMedias.size(); ++i)
+ {
+ const MEDIUMTASKCHAIN &mtc = d->llMedias.at(i);
+ ComObjPtr<Medium> pNewParent;
+ uint32_t uSrcParentIdx = UINT32_MAX;
+ uint32_t uTrgParentIdx = UINT32_MAX;
+ for (size_t a = mtc.chain.size(); a > 0; --a)
+ {
+ const MEDIUMTASK &mt = mtc.chain.at(a - 1);
+ ComPtr<IMedium> pMedium = mt.pMedium;
+
+ Bstr bstrSrcName;
+ rc = pMedium->COMGETTER(Name)(bstrSrcName.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ rc = d->pProgress->SetNextOperation(BstrFmt(tr("Cloning Disk '%ls' ..."), bstrSrcName.raw()).raw(),
+ mt.uWeight);
+ if (FAILED(rc)) throw rc;
+
+ Bstr bstrSrcId;
+ rc = pMedium->COMGETTER(Id)(bstrSrcId.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ if (mtc.fAttachLinked)
+ {
+ IMedium *pTmp = pMedium;
+ ComObjPtr<Medium> pLMedium = static_cast<Medium*>(pTmp);
+ if (pLMedium.isNull())
+ throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
+ ComObjPtr<Medium> pBase = pLMedium->i_getBase();
+ if (pBase->i_isReadOnly())
+ {
+ ComObjPtr<Medium> pDiff;
+ /* create the diff under the snapshot medium */
+ trgLock.release();
+ srcLock.release();
+ rc = d->createDifferencingMedium(p, pLMedium, strTrgSnapshotFolder,
+ newMedia, &pDiff);
+ srcLock.acquire();
+ trgLock.acquire();
+ if (FAILED(rc)) throw rc;
+ map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pDiff));
+ /* diff image has to be used... */
+ pNewParent = pDiff;
+ pMediumsForNotify.insert(pDiff->i_getParent());
+ uIdsForNotify[pDiff->i_getId()] = pDiff->i_getDeviceType();
+ }
+ else
+ {
+ /* Attach the medium directly, as its type is not
+ * subject to diff creation. */
+ newMedia.append(pLMedium);
+ map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pLMedium));
+ pNewParent = pLMedium;
+ }
+ }
+ else
+ {
+ /* Is a clone already there? */
+ TStrMediumMap::iterator it = map.find(Utf8Str(bstrSrcId));
+ if (it != map.end())
+ pNewParent = it->second;
+ else
+ {
+ ComPtr<IMediumFormat> pSrcFormat;
+ rc = pMedium->COMGETTER(MediumFormat)(pSrcFormat.asOutParam());
+ ULONG uSrcCaps = 0;
+ com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap;
+ rc = pSrcFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap));
+
+ if (FAILED(rc)) throw rc;
+ else
+ {
+ for (ULONG j = 0; j < mediumFormatCap.size(); j++)
+ uSrcCaps |= mediumFormatCap[j];
+ }
+
+ /* Default format? */
+ Utf8Str strDefaultFormat;
+ if (mtc.devType == DeviceType_HardDisk)
+ p->mParent->i_getDefaultHardDiskFormat(strDefaultFormat);
+ else
+ strDefaultFormat = "RAW";
+
+ Bstr bstrSrcFormat(strDefaultFormat);
+
+ ULONG srcVar = MediumVariant_Standard;
+ com::SafeArray <MediumVariant_T> mediumVariant;
+
+ /* Is the source file based? */
+ if ((uSrcCaps & MediumFormatCapabilities_File) == MediumFormatCapabilities_File)
+ {
+ /* Yes, just use the source format. Otherwise the defaults
+ * will be used. */
+ rc = pMedium->COMGETTER(Format)(bstrSrcFormat.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ rc = pMedium->COMGETTER(Variant)(ComSafeArrayAsOutParam(mediumVariant));
+ if (FAILED(rc)) throw rc;
+ else
+ {
+ for (size_t j = 0; j < mediumVariant.size(); j++)
+ srcVar |= mediumVariant[j];
+ }
+ }
+
+ Guid newId;
+ newId.create();
+ Utf8Str strNewName(bstrSrcName);
+ if (!fKeepDiskNames)
+ {
+ Utf8Str strSrcTest = bstrSrcName;
+ /* Check if we have to use another name. */
+ if (!mt.strBaseName.isEmpty())
+ strSrcTest = mt.strBaseName;
+ strSrcTest.stripSuffix();
+ /* If the old disk name was in {uuid} format we also
+ * want the new name in this format, but with the
+ * updated id of course. If the old disk was called
+ * like the VM name, we change it to the new VM name.
+ * For all other disks we rename them with this
+ * template: "new name-disk1.vdi". */
+ if (strSrcTest == strOldVMName)
+ strNewName = Utf8StrFmt("%s%s", trgMCF.machineUserData.strName.c_str(),
+ RTPathSuffix(Utf8Str(bstrSrcName).c_str()));
+ else if ( strSrcTest.startsWith("{")
+ && strSrcTest.endsWith("}"))
+ {
+ strSrcTest = strSrcTest.substr(1, strSrcTest.length() - 2);
+
+ Guid temp_guid(strSrcTest);
+ if (temp_guid.isValid() && !temp_guid.isZero())
+ strNewName = Utf8StrFmt("%s%s", newId.toStringCurly().c_str(),
+ RTPathSuffix(strNewName.c_str()));
+ }
+ else
+ strNewName = Utf8StrFmt("%s-disk%d%s", trgMCF.machineUserData.strName.c_str(), ++cDisks,
+ RTPathSuffix(Utf8Str(bstrSrcName).c_str()));
+ }
+
+ /* Check if this medium comes from the snapshot folder, if
+ * so, put it there in the cloned machine as well.
+ * Otherwise it goes to the machine folder. */
+ Bstr bstrSrcPath;
+ Utf8Str strFile = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, strNewName.c_str());
+ rc = pMedium->COMGETTER(Location)(bstrSrcPath.asOutParam());
+ if (FAILED(rc)) throw rc;
+ if ( !bstrSrcPath.isEmpty()
+ && RTPathStartsWith(Utf8Str(bstrSrcPath).c_str(), Utf8Str(bstrSrcSnapshotFolder).c_str())
+ && (fKeepDiskNames || mt.strBaseName.isEmpty()))
+ strFile = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER, strNewName.c_str());
+
+ /* Start creating the clone. */
+ ComObjPtr<Medium> pTarget;
+ rc = pTarget.createObject();
+ if (FAILED(rc)) throw rc;
+
+ rc = pTarget->init(p->mParent,
+ Utf8Str(bstrSrcFormat),
+ strFile,
+ Guid::Empty /* empty media registry */,
+ mtc.devType);
+ if (FAILED(rc)) throw rc;
+
+ /* Update the new uuid. */
+ pTarget->i_updateId(newId);
+
+ /* Do the disk cloning. */
+ ComPtr<IProgress> progress2;
+
+ ComObjPtr<Medium> pLMedium = static_cast<Medium*>((IMedium*)pMedium);
+ srcLock.release();
+ rc = pLMedium->i_cloneToEx(pTarget,
+ (MediumVariant_T)srcVar,
+ pNewParent,
+ progress2.asOutParam(),
+ uSrcParentIdx,
+ uTrgParentIdx,
+ false /* aNotify */);
+ srcLock.acquire();
+ if (FAILED(rc)) throw rc;
+
+ /* Wait until the async process has finished. */
+ srcLock.release();
+ rc = d->pProgress->WaitForOtherProgressCompletion(progress2, 0 /* indefinite wait */);
+ srcLock.acquire();
+ if (FAILED(rc)) throw rc;
+
+ /* Remember created medium. */
+ newMedia.append(pTarget);
+ /* Get the medium type from the source and set it to the
+ * new medium. */
+ MediumType_T type;
+ rc = pMedium->COMGETTER(Type)(&type);
+ if (FAILED(rc)) throw rc;
+ trgLock.release();
+ srcLock.release();
+ rc = pTarget->COMSETTER(Type)(type);
+ srcLock.acquire();
+ trgLock.acquire();
+ if (FAILED(rc)) throw rc;
+ map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pTarget));
+ /* register the new medium */
+ {
+ AutoWriteLock tlock(p->mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+ rc = p->mParent->i_registerMedium(pTarget, &pTarget,
+ tlock);
+ if (FAILED(rc)) throw rc;
+ }
+ /* This medium becomes the parent of the next medium in the
+ * chain. */
+ pNewParent = pTarget;
+ uIdsForNotify[pTarget->i_getId()] = pTarget->i_getDeviceType();
+ }
+ }
+ /* Save the current source medium index as the new parent
+ * medium index. */
+ uSrcParentIdx = mt.uIdx;
+ /* Simply increase the target index. */
+ ++uTrgParentIdx;
+ }
+
+ Bstr bstrSrcId;
+ rc = mtc.chain.first().pMedium->COMGETTER(Id)(bstrSrcId.asOutParam());
+ if (FAILED(rc)) throw rc;
+ Bstr bstrTrgId;
+ rc = pNewParent->COMGETTER(Id)(bstrTrgId.asOutParam());
+ if (FAILED(rc)) throw rc;
+ /* update snapshot configuration */
+ d->updateSnapshotStorageLists(trgMCF.llFirstSnapshot, bstrSrcId, bstrTrgId);
+
+ /* create new 'Current State' diff for caller defined place */
+ if (mtc.fCreateDiffs)
+ {
+ const MEDIUMTASK &mt = mtc.chain.first();
+ ComObjPtr<Medium> pLMedium = static_cast<Medium*>((IMedium*)mt.pMedium);
+ if (pLMedium.isNull())
+ throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
+ ComObjPtr<Medium> pBase = pLMedium->i_getBase();
+ if (pBase->i_isReadOnly())
+ {
+ ComObjPtr<Medium> pDiff;
+ trgLock.release();
+ srcLock.release();
+ rc = d->createDifferencingMedium(p, pNewParent, strTrgSnapshotFolder,
+ newMedia, &pDiff);
+ srcLock.acquire();
+ trgLock.acquire();
+ if (FAILED(rc)) throw rc;
+ /* diff image has to be used... */
+ pNewParent = pDiff;
+ pMediumsForNotify.insert(pDiff->i_getParent());
+ uIdsForNotify[pDiff->i_getId()] = pDiff->i_getDeviceType();
+ }
+ else
+ {
+ /* Attach the medium directly, as its type is not
+ * subject to diff creation. */
+ newMedia.append(pNewParent);
+ }
+
+ rc = pNewParent->COMGETTER(Id)(bstrTrgId.asOutParam());
+ if (FAILED(rc)) throw rc;
+ }
+ /* update 'Current State' configuration */
+ d->updateStorageLists(trgMCF.hardwareMachine.storage.llStorageControllers, bstrSrcId, bstrTrgId);
+ }
+ /* Make sure all disks know of the new machine uuid. We do this last to
+ * be able to change the medium type above. */
+ for (size_t i = newMedia.size(); i > 0; --i)
+ {
+ const ComObjPtr<Medium> &pMedium = newMedia.at(i - 1);
+ AutoCaller mac(pMedium);
+ if (FAILED(mac.rc())) throw mac.rc();
+ AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
+ Guid uuid = d->pTrgMachine->mData->mUuid;
+ if (d->options.contains(CloneOptions_Link))
+ {
+ ComObjPtr<Medium> pParent = pMedium->i_getParent();
+ mlock.release();
+ if (!pParent.isNull())
+ {
+ AutoCaller mac2(pParent);
+ if (FAILED(mac2.rc())) throw mac2.rc();
+ AutoReadLock mlock2(pParent COMMA_LOCKVAL_SRC_POS);
+ if (pParent->i_getFirstRegistryMachineId(uuid))
+ {
+ mlock2.release();
+ trgLock.release();
+ srcLock.release();
+ p->mParent->i_markRegistryModified(uuid);
+ srcLock.acquire();
+ trgLock.acquire();
+ mlock2.acquire();
+ }
+ }
+ mlock.acquire();
+ }
+ pMedium->i_removeRegistry(p->i_getVirtualBox()->i_getGlobalRegistryId());
+ pMedium->i_addRegistry(uuid);
+ }
+ /* Check if a snapshot folder is necessary and if so doesn't already
+ * exists. */
+ if ( !d->llSaveStateFiles.isEmpty()
+ && !RTDirExists(strTrgSnapshotFolder.c_str()))
+ {
+ int vrc = RTDirCreateFullPath(strTrgSnapshotFolder.c_str(), 0700);
+ if (RT_FAILURE(vrc))
+ throw p->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not create snapshots folder '%s' (%Rrc)"),
+ strTrgSnapshotFolder.c_str(), vrc);
+ }
+ /* Clone all save state files. */
+ for (size_t i = 0; i < d->llSaveStateFiles.size(); ++i)
+ {
+ FILECOPYTASK fct = d->llSaveStateFiles.at(i);
+ const Utf8Str &strTrgSaveState = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER,
+ RTPathFilename(fct.strFile.c_str()));
+
+ /* Move to next sub-operation. */
+ rc = d->pProgress->SetNextOperation(BstrFmt(tr("Copy save state file '%s' ..."),
+ RTPathFilename(fct.strFile.c_str())).raw(), fct.uWeight);
+ if (FAILED(rc)) throw rc;
+ /* Copy the file only if it was not copied already. */
+ if (!newFiles.contains(strTrgSaveState.c_str()))
+ {
+ int vrc = RTFileCopyEx(fct.strFile.c_str(), strTrgSaveState.c_str(), 0,
+ MachineCloneVMPrivate::copyFileProgress, &d->pProgress);
+ if (RT_FAILURE(vrc))
+ throw p->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not copy state file '%s' to '%s' (%Rrc)"),
+ fct.strFile.c_str(), strTrgSaveState.c_str(), vrc);
+ newFiles.append(strTrgSaveState);
+ }
+ /* Update the path in the configuration either for the current
+ * machine state or the snapshots. */
+ if (!fct.snapshotUuid.isValid() || fct.snapshotUuid.isZero())
+ trgMCF.strStateFile = strTrgSaveState;
+ else
+ d->updateSaveStateFile(trgMCF.llFirstSnapshot, fct.snapshotUuid, strTrgSaveState);
+ }
+
+ /* Clone all NVRAM files. */
+ for (size_t i = 0; i < d->llNVRAMFiles.size(); ++i)
+ {
+ FILECOPYTASK fct = d->llNVRAMFiles.at(i);
+ Utf8Str strTrgNVRAM;
+ if (!fct.snapshotUuid.isValid() || fct.snapshotUuid.isZero())
+ strTrgNVRAM = Utf8StrFmt("%s%c%s.nvram", strTrgMachineFolder.c_str(), RTPATH_DELIMITER,
+ trgMCF.machineUserData.strName.c_str());
+ else
+ strTrgNVRAM = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER,
+ RTPathFilename(fct.strFile.c_str()));
+
+ /* Move to next sub-operation. */
+ rc = d->pProgress->SetNextOperation(BstrFmt(tr("Copy NVRAM file '%s' ..."),
+ RTPathFilename(fct.strFile.c_str())).raw(), fct.uWeight);
+ if (FAILED(rc)) throw rc;
+ /* Copy the file only if it was not copied already. */
+ if (!newFiles.contains(strTrgNVRAM.c_str()))
+ {
+ rc = p->i_getVirtualBox()->i_ensureFilePathExists(strTrgNVRAM.c_str(), true);
+ if (FAILED(rc)) throw rc;
+ int vrc = RTFileCopyEx(fct.strFile.c_str(), strTrgNVRAM.c_str(), 0,
+ MachineCloneVMPrivate::copyFileProgress, &d->pProgress);
+ if (RT_FAILURE(vrc))
+ throw p->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not copy NVRAM file '%s' to '%s' (%Rrc)"),
+ fct.strFile.c_str(), strTrgNVRAM.c_str(), vrc);
+ newFiles.append(strTrgNVRAM);
+ }
+ /* Update the path in the configuration either for the current
+ * machine state or the snapshots. */
+ if (!fct.snapshotUuid.isValid() || fct.snapshotUuid.isZero())
+ trgMCF.hardwareMachine.nvramSettings.strNvramPath = strTrgNVRAM;
+ else
+ d->updateNVRAMFile(trgMCF.llFirstSnapshot, fct.snapshotUuid, strTrgNVRAM);
+ }
+
+ {
+ rc = d->pProgress->SetNextOperation(BstrFmt(tr("Create Machine Clone '%s' ..."),
+ trgMCF.machineUserData.strName.c_str()).raw(), 1);
+ if (FAILED(rc)) throw rc;
+ /* After modifying the new machine config, we can copy the stuff
+ * over to the new machine. The machine have to be mutable for
+ * this. */
+ rc = d->pTrgMachine->i_checkStateDependency(p->MutableStateDep);
+ if (FAILED(rc)) throw rc;
+ rc = d->pTrgMachine->i_loadMachineDataFromSettings(trgMCF, &d->pTrgMachine->mData->mUuid);
+ if (FAILED(rc)) throw rc;
+
+ /* Fix up the "current state modified" flag to what it should be,
+ * as the value guessed in i_loadMachineDataFromSettings can be
+ * quite far off the logical value for the cloned VM. */
+ if (d->mode == CloneMode_MachineState)
+ d->pTrgMachine->mData->mCurrentStateModified = FALSE;
+ else if ( d->mode == CloneMode_MachineAndChildStates
+ && sn.uuid.isValid()
+ && !sn.uuid.isZero())
+ {
+ if (!d->pOldMachineState.isNull())
+ {
+ /* There will be created a new differencing image based on
+ * this snapshot. So reset the modified state. */
+ d->pTrgMachine->mData->mCurrentStateModified = FALSE;
+ }
+ else
+ d->pTrgMachine->mData->mCurrentStateModified = p->mData->mCurrentStateModified;
+ }
+ else if (d->mode == CloneMode_AllStates)
+ d->pTrgMachine->mData->mCurrentStateModified = p->mData->mCurrentStateModified;
+
+ /* If the target machine has saved state we MUST adjust the machine
+ * state, otherwise saving settings will drop the information. */
+ if (trgMCF.strStateFile.isNotEmpty())
+ d->pTrgMachine->i_setMachineState(MachineState_Saved);
+
+ /* save all VM data */
+ bool fNeedsGlobalSaveSettings = false;
+ rc = d->pTrgMachine->i_saveSettings(&fNeedsGlobalSaveSettings, trgLock, Machine::SaveS_Force);
+ if (FAILED(rc)) throw rc;
+ /* Release all locks */
+ trgLock.release();
+ srcLock.release();
+ if (fNeedsGlobalSaveSettings)
+ {
+ /* save the global settings; for that we should hold only the
+ * VirtualBox lock */
+ AutoWriteLock vlock(p->mParent COMMA_LOCKVAL_SRC_POS);
+ rc = p->mParent->i_saveSettings();
+ if (FAILED(rc)) throw rc;
+ }
+ }
+
+ /* Any additional machines need saving? */
+ p->mParent->i_saveModifiedRegistries();
+ }
+ catch (HRESULT rc2)
+ {
+ /* Error handling code only works correctly without locks held. */
+ trgLock.release();
+ srcLock.release();
+ rc = rc2;
+ }
+ catch (...)
+ {
+ rc = VirtualBoxBase::handleUnexpectedExceptions(p, RT_SRC_POS);
+ }
+
+ MultiResult mrc(rc);
+ /* Cleanup on failure (CANCEL also) */
+ if (FAILED(rc))
+ {
+ int vrc = VINF_SUCCESS;
+ /* Delete all created files. */
+ for (size_t i = 0; i < newFiles.size(); ++i)
+ {
+ vrc = RTFileDelete(newFiles.at(i).c_str());
+ if (RT_FAILURE(vrc))
+ mrc = p->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not delete file '%s' (%Rrc)"), newFiles.at(i).c_str(), vrc);
+ }
+ /* Delete all already created medias. (Reverse, cause there could be
+ * parent->child relations.) */
+ for (size_t i = newMedia.size(); i > 0; --i)
+ {
+ const ComObjPtr<Medium> &pMedium = newMedia.at(i - 1);
+ mrc = pMedium->i_deleteStorage(NULL /* aProgress */,
+ true /* aWait */,
+ false /* aNotify */);
+ pMedium->Close();
+ }
+ /* Delete the snapshot folder when not empty. */
+ if (!strTrgSnapshotFolder.isEmpty())
+ RTDirRemove(strTrgSnapshotFolder.c_str());
+ /* Delete the machine folder when not empty. */
+ RTDirRemove(strTrgMachineFolder.c_str());
+
+ /* Must save the modified registries */
+ p->mParent->i_saveModifiedRegistries();
+ }
+ else
+ {
+ for (std::map<Guid, DeviceType_T>::const_iterator it = uIdsForNotify.begin();
+ it != uIdsForNotify.end();
+ ++it)
+ {
+ p->mParent->i_onMediumRegistered(it->first, it->second, TRUE);
+ }
+ for (std::set<ComObjPtr<Medium> >::const_iterator it = pMediumsForNotify.begin();
+ it != pMediumsForNotify.end();
+ ++it)
+ {
+ if (it->isNotNull())
+ p->mParent->i_onMediumConfigChanged(*it);
+ }
+ }
+
+ return mrc;
+}
+
+void MachineCloneVM::destroy()
+{
+ delete this;
+}
+
diff --git a/src/VBox/Main/src-server/MachineImplMoveVM.cpp b/src/VBox/Main/src-server/MachineImplMoveVM.cpp
new file mode 100644
index 00000000..20c83825
--- /dev/null
+++ b/src/VBox/Main/src-server/MachineImplMoveVM.cpp
@@ -0,0 +1,1702 @@
+/* $Id: MachineImplMoveVM.cpp $ */
+/** @file
+ * Implementation of MachineMoveVM
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_MACHINE
+#include <iprt/fs.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/stream.h>
+#include <VBox/com/ErrorInfo.h>
+
+#include "MachineImplMoveVM.h"
+#include "SnapshotImpl.h"
+#include "MediumFormatImpl.h"
+#include "VirtualBoxImpl.h"
+#include "LoggingNew.h"
+
+typedef std::multimap<Utf8Str, Utf8Str> list_t;
+typedef std::multimap<Utf8Str, Utf8Str>::const_iterator cit_t;
+typedef std::multimap<Utf8Str, Utf8Str>::iterator it_t;
+typedef std::pair <std::multimap<Utf8Str, Utf8Str>::iterator, std::multimap<Utf8Str, Utf8Str>::iterator> rangeRes_t;
+
+struct fileList_t
+{
+ HRESULT add(const Utf8Str &folder, const Utf8Str &file)
+ {
+ HRESULT rc = S_OK;
+ m_list.insert(std::make_pair(folder, file));
+ return rc;
+ }
+
+ HRESULT add(const Utf8Str &fullPath)
+ {
+ HRESULT rc = S_OK;
+ Utf8Str folder = fullPath;
+ folder.stripFilename();
+ Utf8Str filename = fullPath;
+ filename.stripPath();
+ m_list.insert(std::make_pair(folder, filename));
+ return rc;
+ }
+
+ HRESULT removeFileFromList(const Utf8Str &fullPath)
+ {
+ HRESULT rc = S_OK;
+ Utf8Str folder = fullPath;
+ folder.stripFilename();
+ Utf8Str filename = fullPath;
+ filename.stripPath();
+ rangeRes_t res = m_list.equal_range(folder);
+ for (it_t it=res.first; it!=res.second;)
+ {
+ if (it->second.equals(filename))
+ {
+ it_t it2 = it;
+ ++it;
+ m_list.erase(it2);
+ }
+ else
+ ++it;
+ }
+
+ return rc;
+ }
+
+ HRESULT removeFileFromList(const Utf8Str &path, const Utf8Str &fileName)
+ {
+ HRESULT rc = S_OK;
+ rangeRes_t res = m_list.equal_range(path);
+ for (it_t it=res.first; it!=res.second;)
+ {
+ if (it->second.equals(fileName))
+ {
+ it_t it2 = it;
+ ++it;
+ m_list.erase(it2);
+ }
+ else
+ ++it;
+ }
+ return rc;
+ }
+
+ HRESULT removeFolderFromList(const Utf8Str &path)
+ {
+ HRESULT rc = S_OK;
+ m_list.erase(path);
+ return rc;
+ }
+
+ rangeRes_t getFilesInRange(const Utf8Str &path)
+ {
+ rangeRes_t res;
+ res = m_list.equal_range(path);
+ return res;
+ }
+
+ std::list<Utf8Str> getFilesInList(const Utf8Str &path)
+ {
+ std::list<Utf8Str> list_;
+ rangeRes_t res = m_list.equal_range(path);
+ for (it_t it=res.first; it!=res.second; ++it)
+ list_.push_back(it->second);
+ return list_;
+ }
+
+
+ list_t m_list;
+
+};
+
+
+HRESULT MachineMoveVM::init()
+{
+ HRESULT hrc = S_OK;
+
+ Utf8Str strTargetFolder;
+ /* adding a trailing slash if it's needed */
+ {
+ size_t len = m_targetPath.length() + 2;
+ if (len >= RTPATH_MAX)
+ return m_pMachine->setError(VBOX_E_IPRT_ERROR, tr("The destination path exceeds the maximum value."));
+
+ /** @todo r=bird: I need to add a Utf8Str method or iprt/cxx/path.h thingy
+ * for doing this. We need this often and code like this doesn't
+ * need to be repeated and re-optimized in each instance... */
+ char *path = new char [len];
+ RTStrCopy(path, len, m_targetPath.c_str());
+ RTPathEnsureTrailingSeparator(path, len);
+ strTargetFolder = m_targetPath = path;
+ delete[] path;
+ }
+
+ /*
+ * We have a mode which user is able to request
+ * basic mode:
+ * - The images which are solely attached to the VM
+ * and located in the original VM folder will be moved.
+ *
+ * Comment: in the future some other modes can be added.
+ */
+
+ RTFOFF cbTotal = 0;
+ RTFOFF cbFree = 0;
+ uint32_t cbBlock = 0;
+ uint32_t cbSector = 0;
+
+
+ int vrc = RTFsQuerySizes(strTargetFolder.c_str(), &cbTotal, &cbFree, &cbBlock, &cbSector);
+ if (RT_FAILURE(vrc))
+ return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Unable to determine free space at move destination ('%s'): %Rrc"),
+ strTargetFolder.c_str(), vrc);
+
+ RTDIR hDir;
+ vrc = RTDirOpen(&hDir, strTargetFolder.c_str());
+ if (RT_FAILURE(vrc))
+ return m_pMachine->setErrorVrc(vrc);
+
+ Utf8Str strTempFile = strTargetFolder + "test.txt";
+ RTFILE hFile;
+ vrc = RTFileOpen(&hFile, strTempFile.c_str(), RTFILE_O_OPEN_CREATE | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE);
+ if (RT_FAILURE(vrc))
+ {
+ RTDirClose(hDir);
+ return m_pMachine->setErrorVrc(vrc,
+ tr("Can't create a test file test.txt in the %s. Check the access rights of the destination folder."),
+ strTargetFolder.c_str());
+ }
+
+ /** @todo r=vvp: Do we need to check each return result here? Looks excessively.
+ * And it's not so important for the test file.
+ * bird: I'd just do AssertRC on the same line, though the deletion
+ * of the test is a little important. */
+ vrc = RTFileClose(hFile); AssertRC(vrc);
+ RTFileDelete(strTempFile.c_str());
+ vrc = RTDirClose(hDir); AssertRC(vrc);
+
+ Log2(("blocks: total %RTfoff, free %RTfoff\n", cbTotal, cbFree));
+ Log2(("total space (Kb) %RTfoff (Mb) %RTfoff (Gb) %RTfoff\n", cbTotal/_1K, cbTotal/_1M, cbTotal/_1G));
+ Log2(("total free space (Kb) %RTfoff (Mb) %RTfoff (Gb) %RTfoff\n", cbFree/_1K, cbFree/_1M, cbFree/_1G));
+
+ RTFSPROPERTIES properties;
+ vrc = RTFsQueryProperties(strTargetFolder.c_str(), &properties);
+ if (RT_FAILURE(vrc))
+ return m_pMachine->setErrorVrc(vrc, "RTFsQueryProperties(%s): %Rrc", strTargetFolder.c_str(), vrc);
+
+ Log2(("disk properties: remote=%RTbool read only=%RTbool compressed=%RTbool\n",
+ properties.fRemote, properties.fReadOnly, properties.fCompressed));
+
+ /* Get the original VM path */
+ Utf8Str strSettingsFilePath;
+ Bstr bstr_settingsFilePath;
+ hrc = m_pMachine->COMGETTER(SettingsFilePath)(bstr_settingsFilePath.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ strSettingsFilePath = bstr_settingsFilePath;
+ strSettingsFilePath.stripFilename();
+
+ m_vmFolders.insert(std::make_pair(VBox_SettingFolder, strSettingsFilePath));
+
+ /* Collect all files from the VM's folder */
+ fileList_t fullFileList;
+ hrc = getFilesList(strSettingsFilePath, fullFileList);
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Collect all known folders used by the VM:
+ * - log folder;
+ * - state folder;
+ * - snapshot folder.
+ */
+ Utf8Str strLogFolder;
+ Bstr bstr_logFolder;
+ hrc = m_pMachine->COMGETTER(LogFolder)(bstr_logFolder.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ strLogFolder = bstr_logFolder;
+ if ( m_type.equals("basic")
+ && RTPathStartsWith(strLogFolder.c_str(), strSettingsFilePath.c_str()))
+ m_vmFolders.insert(std::make_pair(VBox_LogFolder, strLogFolder));
+
+ Utf8Str strStateFilePath;
+ Bstr bstr_stateFilePath;
+ MachineState_T machineState;
+ hrc = m_pMachine->COMGETTER(State)(&machineState);
+ if (FAILED(hrc))
+ return hrc;
+
+ if (machineState == MachineState_Saved || machineState == MachineState_AbortedSaved)
+ {
+ m_pMachine->COMGETTER(StateFilePath)(bstr_stateFilePath.asOutParam());
+ strStateFilePath = bstr_stateFilePath;
+ strStateFilePath.stripFilename();
+ if ( m_type.equals("basic")
+ && RTPathStartsWith(strStateFilePath.c_str(), strSettingsFilePath.c_str()))
+ m_vmFolders.insert(std::make_pair(VBox_StateFolder, strStateFilePath));
+ }
+
+ Utf8Str strSnapshotFolder;
+ Bstr bstr_snapshotFolder;
+ hrc = m_pMachine->COMGETTER(SnapshotFolder)(bstr_snapshotFolder.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ strSnapshotFolder = bstr_snapshotFolder;
+ if ( m_type.equals("basic")
+ && RTPathStartsWith(strSnapshotFolder.c_str(), strSettingsFilePath.c_str()))
+ m_vmFolders.insert(std::make_pair(VBox_SnapshotFolder, strSnapshotFolder));
+
+ if (m_pMachine->i_isSnapshotMachine())
+ {
+ Bstr bstrSrcMachineId;
+ hrc = m_pMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ ComPtr<IMachine> newSrcMachine;
+ hrc = m_pMachine->i_getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), newSrcMachine.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /* Add the current machine and all snapshot machines below this machine
+ * in a list for further processing.
+ */
+
+ int64_t neededFreeSpace = 0;
+
+ /* Actual file list */
+ fileList_t actualFileList;
+ Utf8Str strTargetImageName;
+
+ machineList.push_back(m_pMachine);
+
+ {
+ ULONG cSnapshots = 0;
+ hrc = m_pMachine->COMGETTER(SnapshotCount)(&cSnapshots);
+ if (FAILED(hrc))
+ return hrc;
+
+ if (cSnapshots > 0)
+ {
+ Utf8Str id;
+ if (m_pMachine->i_isSnapshotMachine())
+ id = m_pMachine->i_getSnapshotId().toString();
+ ComPtr<ISnapshot> pSnapshot;
+ hrc = m_pMachine->FindSnapshot(Bstr(id).raw(), pSnapshot.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ hrc = createMachineList(pSnapshot);
+ if (FAILED(hrc))
+ return hrc;
+ }
+ }
+
+ ULONG uCount = 1;//looks like it should be initialized by 1. See assertion in the Progress::setNextOperation()
+ ULONG uTotalWeight = 1;
+
+ /* The lists m_llMedias, m_llSaveStateFiles and m_llNVRAMFiles are filled in the queryMediasForAllStates() */
+ hrc = queryMediasForAllStates();
+ if (FAILED(hrc))
+ return hrc;
+
+ /* Calculate the total size of images. Fill m_finalMediumsMap */
+ { /** The scope here for better reading, apart from that the variables have limited scope too */
+ uint64_t totalMediumsSize = 0;
+
+ for (size_t i = 0; i < m_llMedias.size(); ++i)
+ {
+ MEDIUMTASKCHAINMOVE &mtc = m_llMedias.at(i);
+ for (size_t a = mtc.chain.size(); a > 0; --a)
+ {
+ Bstr bstrLocation;
+ Utf8Str name = mtc.chain[a - 1].strBaseName;
+ ComPtr<IMedium> plMedium = mtc.chain[a - 1].pMedium;
+ hrc = plMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ Utf8Str strLocation = bstrLocation;
+
+ /* if an image is located in the actual VM folder it will be added to the actual list */
+ if (strLocation.startsWith(strSettingsFilePath))
+ {
+ LONG64 cbSize = 0;
+ hrc = plMedium->COMGETTER(Size)(&cbSize);
+ if (FAILED(hrc))
+ return hrc;
+
+ std::pair<std::map<Utf8Str, MEDIUMTASKMOVE>::iterator,bool> ret;
+ ret = m_finalMediumsMap.insert(std::make_pair(name, mtc.chain[a - 1]));
+ if (ret.second == true)
+ {
+ /* Calculate progress data */
+ ++uCount;
+ uTotalWeight += mtc.chain[a - 1].uWeight;
+ totalMediumsSize += (uint64_t)cbSize;
+ Log2(("Image %s was added into the moved list\n", name.c_str()));
+ }
+ }
+ }
+ }
+
+ Log2(("Total Size of images is %lld bytes\n", totalMediumsSize));
+ neededFreeSpace += totalMediumsSize;
+ }
+
+ /* Prepare data for moving ".sav" files */
+ {
+ uint64_t totalStateSize = 0;
+
+ for (size_t i = 0; i < m_llSaveStateFiles.size(); ++i)
+ {
+ uint64_t cbFile = 0;
+ SNAPFILETASKMOVE &sft = m_llSaveStateFiles.at(i);
+
+ Utf8Str name = sft.strFile;
+ /* if a state file is located in the actual VM folder it will be added to the actual list */
+ if (RTPathStartsWith(name.c_str(), strSettingsFilePath.c_str()))
+ {
+ vrc = RTFileQuerySizeByPath(name.c_str(), &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ std::pair<std::map<Utf8Str, SNAPFILETASKMOVE>::iterator,bool> ret;
+ ret = m_finalSaveStateFilesMap.insert(std::make_pair(name, sft));
+ if (ret.second == true)
+ {
+ totalStateSize += cbFile;
+ ++uCount;
+ uTotalWeight += sft.uWeight;
+ Log2(("The state file %s was added into the moved list\n", name.c_str()));
+ }
+ }
+ else
+ {
+ Log2(("The state file %s wasn't added into the moved list. Couldn't get the file size.\n",
+ name.c_str()));
+ return m_pMachine->setErrorVrc(vrc,
+ tr("Failed to get file size for '%s': %Rrc"),
+ name.c_str(), vrc);
+ }
+ }
+ }
+
+ neededFreeSpace += totalStateSize;
+ }
+
+ /* Prepare data for moving ".nvram" files */
+ {
+ uint64_t totalNVRAMSize = 0;
+
+ for (size_t i = 0; i < m_llNVRAMFiles.size(); ++i)
+ {
+ uint64_t cbFile = 0;
+ SNAPFILETASKMOVE &sft = m_llNVRAMFiles.at(i);
+
+ Utf8Str name = sft.strFile;
+ /* if a NVRAM file is located in the actual VM folder it will be added to the actual list */
+ if (RTPathStartsWith(name.c_str(), strSettingsFilePath.c_str()))
+ {
+ vrc = RTFileQuerySizeByPath(name.c_str(), &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ std::pair<std::map<Utf8Str, SNAPFILETASKMOVE>::iterator,bool> ret;
+ ret = m_finalNVRAMFilesMap.insert(std::make_pair(name, sft));
+ if (ret.second == true)
+ {
+ totalNVRAMSize += cbFile;
+ ++uCount;
+ uTotalWeight += sft.uWeight;
+ Log2(("The NVRAM file %s was added into the moved list\n", name.c_str()));
+ }
+ }
+ else
+ {
+ Log2(("The NVRAM file %s wasn't added into the moved list. Couldn't get the file size.\n",
+ name.c_str()));
+ return m_pMachine->setErrorVrc(vrc,
+ tr("Failed to get file size for '%s': %Rrc"),
+ name.c_str(), vrc);
+ }
+ }
+ }
+
+ neededFreeSpace += totalNVRAMSize;
+ }
+
+ /* Prepare data for moving the log files */
+ {
+ Utf8Str strFolder = m_vmFolders[VBox_LogFolder];
+
+ if (RTPathExists(strFolder.c_str()))
+ {
+ uint64_t totalLogSize = 0;
+ hrc = getFolderSize(strFolder, totalLogSize);
+ if (SUCCEEDED(hrc))
+ {
+ neededFreeSpace += totalLogSize;
+ if (cbFree - neededFreeSpace <= _1M)
+ return m_pMachine->setError(E_FAIL,
+ tr("Insufficient disk space available (%RTfoff needed, %RTfoff free)"),
+ neededFreeSpace, cbFree);
+
+ fileList_t filesList;
+ hrc = getFilesList(strFolder, filesList);
+ if (FAILED(hrc))
+ return hrc;
+
+ cit_t it = filesList.m_list.begin();
+ while (it != filesList.m_list.end())
+ {
+ Utf8Str strFile = it->first.c_str();
+ strFile.append(RTPATH_DELIMITER).append(it->second.c_str());
+
+ uint64_t cbFile = 0;
+ vrc = RTFileQuerySizeByPath(strFile.c_str(), &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ uCount += 1;
+ uTotalWeight += (ULONG)((cbFile + _1M - 1) / _1M);
+ actualFileList.add(strFile);
+ Log2(("The log file %s added into the moved list\n", strFile.c_str()));
+ }
+ else
+ Log2(("The log file %s wasn't added into the moved list. Couldn't get the file size.\n", strFile.c_str()));
+ ++it;
+ }
+ }
+ else
+ return hrc;
+ }
+ else
+ {
+ Log2(("Information: The original log folder %s doesn't exist\n", strFolder.c_str()));
+ hrc = S_OK;//it's not error in this case if there isn't an original log folder
+ }
+ }
+
+ LogRel(("Total space needed is %lld bytes\n", neededFreeSpace));
+ /* Check a target location on enough room */
+ if (cbFree - neededFreeSpace <= _1M)
+ {
+ LogRel(("but free space on destination is %RTfoff\n", cbFree));
+ return m_pMachine->setError(VBOX_E_IPRT_ERROR,
+ tr("Insufficient disk space available (%RTfoff needed, %RTfoff free)"),
+ neededFreeSpace, cbFree);
+ }
+
+ /* Add step for .vbox machine setting file */
+ ++uCount;
+ uTotalWeight += 1;
+
+ /* Reserve additional steps in case of failure and rollback all changes */
+ uTotalWeight += uCount;//just add 1 for each possible rollback operation
+ uCount += uCount;//and increase the steps twice
+
+ /* Init Progress instance */
+ {
+ hrc = m_pProgress->init(m_pMachine->i_getVirtualBox(),
+ static_cast<IMachine *>(m_pMachine) /* aInitiator */,
+ Utf8Str(tr("Moving Machine")),
+ true /* fCancellable */,
+ uCount,
+ uTotalWeight,
+ Utf8Str(tr("Initialize Moving")),
+ 1);
+ if (FAILED(hrc))
+ return m_pMachine->setError(hrc,
+ tr("Couldn't correctly setup the progress object for moving VM operation"));
+ }
+
+ /* save all VM data */
+ m_pMachine->i_setModified(Machine::IsModified_MachineData);
+ hrc = m_pMachine->SaveSettings();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowFuncLeave();
+
+ return hrc;
+}
+
+void MachineMoveVM::printStateFile(settings::SnapshotsList &snl)
+{
+ settings::SnapshotsList::iterator it;
+ for (it = snl.begin(); it != snl.end(); ++it)
+ {
+ if (!it->strStateFile.isEmpty())
+ {
+ settings::Snapshot snap = (settings::Snapshot)(*it);
+ Log2(("snap.uuid = %s\n", snap.uuid.toStringCurly().c_str()));
+ Log2(("snap.strStateFile = %s\n", snap.strStateFile.c_str()));
+ }
+
+ if (!it->llChildSnapshots.empty())
+ printStateFile(it->llChildSnapshots);
+ }
+}
+
+/* static */
+DECLCALLBACK(int) MachineMoveVM::updateProgress(unsigned uPercent, void *pvUser)
+{
+ MachineMoveVM *pTask = *(MachineMoveVM **)pvUser;
+
+ if ( pTask
+ && !pTask->m_pProgress.isNull())
+ {
+ BOOL fCanceled;
+ pTask->m_pProgress->COMGETTER(Canceled)(&fCanceled);
+ if (fCanceled)
+ return -1;
+ pTask->m_pProgress->SetCurrentOperationProgress(uPercent);
+ }
+ return VINF_SUCCESS;
+}
+
+/* static */
+DECLCALLBACK(int) MachineMoveVM::copyFileProgress(unsigned uPercentage, void *pvUser)
+{
+ ComObjPtr<Progress> pProgress = *static_cast<ComObjPtr<Progress> *>(pvUser);
+
+ BOOL fCanceled = false;
+ HRESULT rc = pProgress->COMGETTER(Canceled)(&fCanceled);
+ if (FAILED(rc)) return VERR_GENERAL_FAILURE;
+ /* If canceled by the user tell it to the copy operation. */
+ if (fCanceled) return VERR_CANCELLED;
+ /* Set the new process. */
+ rc = pProgress->SetCurrentOperationProgress(uPercentage);
+ if (FAILED(rc)) return VERR_GENERAL_FAILURE;
+
+ return VINF_SUCCESS;
+}
+
+/* static */
+void MachineMoveVM::i_MoveVMThreadTask(MachineMoveVM *task)
+{
+ LogFlowFuncEnter();
+ HRESULT hrc = S_OK;
+
+ MachineMoveVM *taskMoveVM = task;
+ ComObjPtr<Machine> &machine = taskMoveVM->m_pMachine;
+
+ AutoCaller autoCaller(machine);
+// if (FAILED(autoCaller.rc())) return;//Should we return something here?
+
+ Utf8Str strTargetFolder = taskMoveVM->m_targetPath;
+ {
+ Bstr bstrMachineName;
+ hrc = machine->COMGETTER(Name)(bstrMachineName.asOutParam());
+ if (FAILED(hrc))
+ {
+ taskMoveVM->m_result = hrc;
+ if (!taskMoveVM->m_pProgress.isNull())
+ taskMoveVM->m_pProgress->i_notifyComplete(taskMoveVM->m_result);
+ return;
+ }
+ strTargetFolder.append(Utf8Str(bstrMachineName));
+ }
+
+ RTCList<Utf8Str> newFiles; /* All extra created files (save states, ...) */
+ RTCList<Utf8Str> originalFiles; /* All original files except images */
+ typedef std::map<Utf8Str, ComObjPtr<Medium> > MediumMap;
+ MediumMap mapOriginalMedium;
+
+ /*
+ * We have the couple modes which user is able to request
+ * basic mode:
+ * - The images which are solely attached to the VM
+ * and located in the original VM folder will be moved.
+ * All subfolders related to the original VM are also moved from the original location
+ * (Standard - snapshots and logs folders).
+ *
+ * canonical mode:
+ * - All disks tied with the VM will be moved into a new location if it's possible.
+ * All folders related to the original VM are also moved.
+ * This mode is intended to collect all files/images/snapshots related to the VM in the one place.
+ *
+ */
+
+ /*
+ * A way to handle shareable disk:
+ * Collect the shareable disks attched to the VM.
+ * Get the machines whom the shareable disks attach to.
+ * Return an error if the state of any VM doesn't allow to move a shareable disk and
+ * this disk is located in the VM's folder (it means the disk is intended for "moving").
+ */
+
+
+ /*
+ * Check new destination whether enough room for the VM or not. if "not" return an error.
+ * Make a copy of VM settings and a list with all files which are moved. Save the list on the disk.
+ * Start "move" operation.
+ * Check the result of operation.
+ * if the operation was successful:
+ * - delete all files in the original VM folder;
+ * - update VM disks info with new location;
+ * - update all other VM if it's needed;
+ * - update global settings
+ */
+
+ try
+ {
+ /* Move all disks */
+ hrc = taskMoveVM->moveAllDisks(taskMoveVM->m_finalMediumsMap, strTargetFolder);
+ if (FAILED(hrc))
+ throw hrc;
+
+ /* Get Machine::Data here because moveAllDisks() change it */
+ Machine::Data *machineData = machine->mData.data();
+ settings::MachineConfigFile *machineConfFile = machineData->pMachineConfigFile;
+
+ /* Copy all save state files. */
+ Utf8Str strTrgSnapshotFolder;
+ {
+ /* When the current snapshot folder is absolute we reset it to the
+ * default relative folder. */
+ if (RTPathStartsWithRoot(machineConfFile->machineUserData.strSnapshotFolder.c_str()))
+ machineConfFile->machineUserData.strSnapshotFolder = "Snapshots";
+ machineConfFile->strStateFile = "";
+
+ /* The absolute name of the snapshot folder. */
+ strTrgSnapshotFolder = Utf8StrFmt("%s%c%s", strTargetFolder.c_str(), RTPATH_DELIMITER,
+ machineConfFile->machineUserData.strSnapshotFolder.c_str());
+
+ /* Check if a snapshot folder is necessary and if so doesn't already
+ * exists. */
+ if ( ( taskMoveVM->m_finalSaveStateFilesMap.size() > 0
+ || taskMoveVM->m_finalNVRAMFilesMap.size() > 1)
+ && !RTDirExists(strTrgSnapshotFolder.c_str()))
+ {
+ int vrc = RTDirCreateFullPath(strTrgSnapshotFolder.c_str(), 0700);
+ if (RT_FAILURE(vrc))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not create snapshots folder '%s' (%Rrc)"),
+ strTrgSnapshotFolder.c_str(), vrc);
+ }
+
+ std::map<Utf8Str, SNAPFILETASKMOVE>::iterator itState = taskMoveVM->m_finalSaveStateFilesMap.begin();
+ while (itState != taskMoveVM->m_finalSaveStateFilesMap.end())
+ {
+ const SNAPFILETASKMOVE &sft = itState->second;
+ const Utf8Str &strTrgSaveState = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER,
+ RTPathFilename(sft.strFile.c_str()));
+
+ /* Move to next sub-operation. */
+ hrc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(tr("Copy the save state file '%s' ..."),
+ RTPathFilename(sft.strFile.c_str())).raw(),
+ sft.uWeight);
+ if (FAILED(hrc))
+ throw hrc;
+
+ int vrc = RTFileCopyEx(sft.strFile.c_str(), strTrgSaveState.c_str(), 0,
+ MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
+ if (RT_FAILURE(vrc))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not copy state file '%s' to '%s' (%Rrc)"),
+ sft.strFile.c_str(),
+ strTrgSaveState.c_str(),
+ vrc);
+
+ /* save new file in case of restoring */
+ newFiles.append(strTrgSaveState);
+ /* save original file for deletion in the end */
+ originalFiles.append(sft.strFile);
+ ++itState;
+ }
+
+ std::map<Utf8Str, SNAPFILETASKMOVE>::iterator itNVRAM = taskMoveVM->m_finalNVRAMFilesMap.begin();
+ while (itNVRAM != taskMoveVM->m_finalNVRAMFilesMap.end())
+ {
+ const SNAPFILETASKMOVE &sft = itNVRAM->second;
+ const Utf8Str &strTrgNVRAM = Utf8StrFmt("%s%c%s", sft.snapshotUuid.isZero() ? strTargetFolder.c_str() : strTrgSnapshotFolder.c_str(),
+ RTPATH_DELIMITER, RTPathFilename(sft.strFile.c_str()));
+
+ /* Move to next sub-operation. */
+ hrc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(tr("Copy the NVRAM file '%s' ..."),
+ RTPathFilename(sft.strFile.c_str())).raw(),
+ sft.uWeight);
+ if (FAILED(hrc))
+ throw hrc;
+
+ int vrc = RTFileCopyEx(sft.strFile.c_str(), strTrgNVRAM.c_str(), 0,
+ MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
+ if (RT_FAILURE(vrc))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not copy NVRAM file '%s' to '%s' (%Rrc)"),
+ sft.strFile.c_str(),
+ strTrgNVRAM.c_str(),
+ vrc);
+
+ /* save new file in case of restoring */
+ newFiles.append(strTrgNVRAM);
+ /* save original file for deletion in the end */
+ originalFiles.append(sft.strFile);
+ ++itNVRAM;
+ }
+ }
+
+ /*
+ * Update state file path
+ * very important step!
+ */
+ Log2(("Update state file path\n"));
+ /** @todo r=klaus: this update is not necessarily matching what the
+ * above code has set as the new folders, so it needs reimplementing */
+ taskMoveVM->updatePathsToStateFiles(taskMoveVM->m_vmFolders[VBox_SettingFolder],
+ strTargetFolder);
+
+ /*
+ * Update NVRAM file paths
+ * very important step!
+ */
+ Log2(("Update NVRAM paths\n"));
+ /** @todo r=klaus: this update is not necessarily matching what the
+ * above code has set as the new folders, so it needs reimplementing.
+ * What's good about this implementation: it does not look at the
+ * list of NVRAM files, because that only lists the existing ones,
+ * but all paths need fixing. */
+ taskMoveVM->updatePathsToNVRAMFiles(taskMoveVM->m_vmFolders[VBox_SettingFolder],
+ strTargetFolder);
+
+ /*
+ * Moving Machine settings file
+ * The settings file are moved after all disks and snapshots because this file should be updated
+ * with actual information and only then should be moved.
+ */
+ {
+ Log2(("Copy Machine settings file\n"));
+
+ hrc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(tr("Copy Machine settings file '%s' ..."),
+ machineConfFile->machineUserData.strName.c_str()).raw(),
+ 1);
+ if (FAILED(hrc))
+ throw hrc;
+
+ Utf8Str strTargetSettingsFilePath = strTargetFolder;
+
+ /* Check a folder existing and create one if it's not */
+ if (!RTDirExists(strTargetSettingsFilePath.c_str()))
+ {
+ int vrc = RTDirCreateFullPath(strTargetSettingsFilePath.c_str(), 0700);
+ if (RT_FAILURE(vrc))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not create a home machine folder '%s' (%Rrc)"),
+ strTargetSettingsFilePath.c_str(), vrc);
+
+ Log2(("Created a home machine folder %s\n", strTargetSettingsFilePath.c_str()));
+ }
+
+ /* Create a full path */
+ Bstr bstrMachineName;
+ machine->COMGETTER(Name)(bstrMachineName.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ strTargetSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName));
+ strTargetSettingsFilePath.append(".vbox");
+
+ Utf8Str strSettingsFilePath;
+ Bstr bstr_settingsFilePath;
+ machine->COMGETTER(SettingsFilePath)(bstr_settingsFilePath.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ strSettingsFilePath = bstr_settingsFilePath;
+
+ int vrc = RTFileCopyEx(strSettingsFilePath.c_str(), strTargetSettingsFilePath.c_str(), 0,
+ MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
+ if (RT_FAILURE(vrc))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not copy the setting file '%s' to '%s' (%Rrc)"),
+ strSettingsFilePath.c_str(),
+ strTargetSettingsFilePath.stripFilename().c_str(),
+ vrc);
+
+ Log2(("The setting file %s has been copied into the folder %s\n",
+ strSettingsFilePath.c_str(), strTargetSettingsFilePath.stripFilename().c_str()));
+
+ /* save new file in case of restoring */
+ newFiles.append(strTargetSettingsFilePath);
+ /* save original file for deletion in the end */
+ originalFiles.append(strSettingsFilePath);
+
+ Utf8Str strPrevSettingsFilePath = strSettingsFilePath;
+ strPrevSettingsFilePath.append("-prev");
+ if (RTFileExists(strPrevSettingsFilePath.c_str()))
+ originalFiles.append(strPrevSettingsFilePath);
+ }
+
+ /* Moving Machine log files */
+ {
+ Log2(("Copy machine log files\n"));
+
+ if (taskMoveVM->m_vmFolders[VBox_LogFolder].isNotEmpty())
+ {
+ /* Check an original log folder existence */
+ if (RTDirExists(taskMoveVM->m_vmFolders[VBox_LogFolder].c_str()))
+ {
+ Utf8Str strTargetLogFolderPath = strTargetFolder;
+ strTargetLogFolderPath.append(RTPATH_DELIMITER).append("Logs");
+
+ /* Check a destination log folder existence and create one if it's not */
+ if (!RTDirExists(strTargetLogFolderPath.c_str()))
+ {
+ int vrc = RTDirCreateFullPath(strTargetLogFolderPath.c_str(), 0700);
+ if (RT_FAILURE(vrc))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not create log folder '%s' (%Rrc)"),
+ strTargetLogFolderPath.c_str(), vrc);
+
+ Log2(("Created a log machine folder %s\n", strTargetLogFolderPath.c_str()));
+ }
+
+ fileList_t filesList;
+ taskMoveVM->getFilesList(taskMoveVM->m_vmFolders[VBox_LogFolder], filesList);
+ cit_t it = filesList.m_list.begin();
+ while (it != filesList.m_list.end())
+ {
+ Utf8Str strFullSourceFilePath = it->first.c_str();
+ strFullSourceFilePath.append(RTPATH_DELIMITER).append(it->second.c_str());
+
+ Utf8Str strFullTargetFilePath = strTargetLogFolderPath;
+ strFullTargetFilePath.append(RTPATH_DELIMITER).append(it->second.c_str());
+
+ /* Move to next sub-operation. */
+ hrc = taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(tr("Copying the log file '%s' ..."),
+ RTPathFilename(strFullSourceFilePath.c_str())).raw(),
+ 1);
+ if (FAILED(hrc))
+ throw hrc;
+
+ int vrc = RTFileCopyEx(strFullSourceFilePath.c_str(), strFullTargetFilePath.c_str(), 0,
+ MachineMoveVM::copyFileProgress, &taskMoveVM->m_pProgress);
+ if (RT_FAILURE(vrc))
+ throw machine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not copy the log file '%s' to '%s' (%Rrc)"),
+ strFullSourceFilePath.c_str(),
+ strFullTargetFilePath.stripFilename().c_str(),
+ vrc);
+
+ Log2(("The log file %s has been copied into the folder %s\n", strFullSourceFilePath.c_str(),
+ strFullTargetFilePath.stripFilename().c_str()));
+
+ /* save new file in case of restoring */
+ newFiles.append(strFullTargetFilePath);
+ /* save original file for deletion in the end */
+ originalFiles.append(strFullSourceFilePath);
+
+ ++it;
+ }
+ }
+ }
+ }
+
+ /* save all VM data */
+ hrc = machine->SaveSettings();
+ if (FAILED(hrc))
+ throw hrc;
+
+ Log2(("Update path to XML setting file\n"));
+ Utf8Str strTargetSettingsFilePath = strTargetFolder;
+ Bstr bstrMachineName;
+ hrc = machine->COMGETTER(Name)(bstrMachineName.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ strTargetSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName)).append(".vbox");
+ machineData->m_strConfigFileFull = strTargetSettingsFilePath;
+ machine->mParent->i_copyPathRelativeToConfig(strTargetSettingsFilePath, machineData->m_strConfigFile);
+
+ /* Marks the global registry for uuid as modified */
+ Guid uuid = machine->mData->mUuid;
+ machine->mParent->i_markRegistryModified(uuid);
+
+ /* for saving the global settings we should hold only the VirtualBox lock */
+ AutoWriteLock vboxLock(machine->mParent COMMA_LOCKVAL_SRC_POS);
+
+ /* Save global settings in the VirtualBox.xml */
+ hrc = machine->mParent->i_saveSettings();
+ if (FAILED(hrc))
+ throw hrc;
+ }
+ catch(HRESULT aRc)
+ {
+ hrc = aRc;
+ taskMoveVM->m_result = hrc;
+ }
+ catch (...)
+ {
+ Log2(("Moving machine to a new destination was failed. Check original and destination places.\n"));
+ hrc = VirtualBoxBase::handleUnexpectedExceptions(machine, RT_SRC_POS);
+ taskMoveVM->m_result = hrc;
+ }
+
+ /* Cleanup on failure */
+ if (FAILED(hrc))
+ {
+ Machine::Data *machineData = machine->mData.data();
+
+ /* Restoring the original mediums */
+ try
+ {
+ /*
+ * Fix the progress counter
+ * In instance, the whole "move vm" operation is failed on 5th step. But total count is 20.
+ * Where 20 = 2 * 10 operations, where 10 is the real number of operations. And this value was doubled
+ * earlier in the init() exactly for one reason - rollback operation. Because in this case we must do
+ * the same operations but in backward direction.
+ * Thus now we want to correct the progress counter from 5 to 15. Why?
+ * Because we should have evaluated the counter as "20/2 + (20/2 - 5)" = 15 or just "20 - 5" = 15
+ * And because the 5th step failed it shouldn't be counted.
+ * As result, we need to rollback 4 operations.
+ * Thus we start from "operation + 1" and finish when "i < operationCount - operation".
+ */
+
+ /** @todo r=vvp: Do we need to check each return result here? Looks excessively
+ * and what to do with any failure here? We are already in the rollback action.
+ * Throw only the important errors?
+ * We MUST finish this action anyway to avoid garbage and get the original VM state. */
+ /* ! Apparently we should update the Progress object !*/
+ ULONG operationCount = 0;
+ hrc = taskMoveVM->m_pProgress->COMGETTER(OperationCount)(&operationCount);
+ if (FAILED(hrc))
+ throw hrc;
+ ULONG operation = 0;
+ hrc = taskMoveVM->m_pProgress->COMGETTER(Operation)(&operation);
+ if (FAILED(hrc))
+ throw hrc;
+ Bstr bstrOperationDescription;
+ hrc = taskMoveVM->m_pProgress->COMGETTER(OperationDescription)(bstrOperationDescription.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ Utf8Str strOperationDescription = bstrOperationDescription;
+ ULONG operationPercent = 0;
+ hrc = taskMoveVM->m_pProgress->COMGETTER(OperationPercent)(&operationPercent);
+ if (FAILED(hrc))
+ throw hrc;
+ Bstr bstrMachineName;
+ hrc = machine->COMGETTER(Name)(bstrMachineName.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+
+ Log2(("Moving machine %s was failed on operation %s\n",
+ Utf8Str(bstrMachineName.raw()).c_str(), Utf8Str(bstrOperationDescription.raw()).c_str()));
+
+ for (ULONG i = operation + 1; i < operationCount - operation; ++i)
+ taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(tr("Skip the empty operation %d..."), i + 1).raw(), 1);
+
+ hrc = taskMoveVM->moveAllDisks(taskMoveVM->m_finalMediumsMap);
+ if (FAILED(hrc))
+ throw hrc;
+
+ /* Revert original paths to the state files */
+ taskMoveVM->updatePathsToStateFiles(strTargetFolder,
+ taskMoveVM->m_vmFolders[VBox_SettingFolder]);
+
+ /* Revert original paths to the NVRAM files */
+ taskMoveVM->updatePathsToNVRAMFiles(strTargetFolder,
+ taskMoveVM->m_vmFolders[VBox_SettingFolder]);
+
+ /* Delete all created files. Here we update progress object */
+ hrc = taskMoveVM->deleteFiles(newFiles);
+ if (FAILED(hrc))
+ {
+ Log2(("Rollback scenario: can't delete new created files. Check the destination folder.\n"));
+ throw hrc;
+ }
+
+ /* Delete destination folder */
+ int vrc = RTDirRemove(strTargetFolder.c_str());
+ if (RT_FAILURE(vrc))
+ {
+ Log2(("Rollback scenario: can't delete new destination folder.\n"));
+ throw machine->setErrorVrc(vrc, tr("Rollback scenario: can't delete new destination folder."));
+ }
+
+ /* save all VM data */
+ {
+ AutoWriteLock srcLock(machine COMMA_LOCKVAL_SRC_POS);
+ srcLock.release();
+ hrc = machine->SaveSettings();
+ if (FAILED(hrc))
+ {
+ Log2(("Rollback scenario: can't save machine settings.\n"));
+ throw hrc;
+ }
+ srcLock.acquire();
+ }
+
+ /* Restore an original path to XML setting file */
+ {
+ Log2(("Rollback scenario: restoration of the original path to XML setting file\n"));
+ Utf8Str strOriginalSettingsFilePath = taskMoveVM->m_vmFolders[VBox_SettingFolder];
+ strOriginalSettingsFilePath.append(RTPATH_DELIMITER).append(Utf8Str(bstrMachineName)).append(".vbox");
+ machineData->m_strConfigFileFull = strOriginalSettingsFilePath;
+ machine->mParent->i_copyPathRelativeToConfig(strOriginalSettingsFilePath, machineData->m_strConfigFile);
+ }
+
+ /* Marks the global registry for uuid as modified */
+ {
+ AutoWriteLock srcLock(machine COMMA_LOCKVAL_SRC_POS);
+ srcLock.release();
+ Guid uuid = machine->mData->mUuid;
+ machine->mParent->i_markRegistryModified(uuid);
+ srcLock.acquire();
+ }
+
+ /* save the global settings; for that we should hold only the VirtualBox lock */
+ {
+ AutoWriteLock vboxLock(machine->mParent COMMA_LOCKVAL_SRC_POS);
+ hrc = machine->mParent->i_saveSettings();
+ if (FAILED(hrc))
+ {
+ Log2(("Rollback scenario: can't save global settings.\n"));
+ throw hrc;
+ }
+ }
+ }
+ catch(HRESULT aRc)
+ {
+ hrc = aRc;
+ Log2(("Rollback scenario: restoration the original mediums were failed. Machine can be corrupted.\n"));
+ }
+ catch (...)
+ {
+ Log2(("Rollback scenario: restoration the original mediums were failed. Machine can be corrupted.\n"));
+ hrc = VirtualBoxBase::handleUnexpectedExceptions(machine, RT_SRC_POS);
+ }
+ /* In case of failure the progress object on the other side (user side) get notification about operation
+ completion but the operation percentage may not be set to 100% */
+ }
+ else /*Operation was successful and now we can delete the original files like the state files, XML setting, log files */
+ {
+ /*
+ * In case of success it's not urgent to update the progress object because we call i_notifyComplete() with
+ * the success result. As result, the last number of progress operation can be not equal the number of operations
+ * because we doubled the number of operations for rollback case.
+ * But if we want to update the progress object corectly it's needed to add all medium moved by standard
+ * "move medium" logic (for us it's taskMoveVM->m_finalMediumsMap) to the current number of operation.
+ */
+
+ ULONG operationCount = 0;
+ hrc = taskMoveVM->m_pProgress->COMGETTER(OperationCount)(&operationCount);
+ ULONG operation = 0;
+ hrc = taskMoveVM->m_pProgress->COMGETTER(Operation)(&operation);
+
+ for (ULONG i = operation; i < operation + taskMoveVM->m_finalMediumsMap.size() - 1; ++i)
+ taskMoveVM->m_pProgress->SetNextOperation(BstrFmt(tr("Skip the empty operation %d..."), i).raw(), 1);
+
+ hrc = taskMoveVM->deleteFiles(originalFiles);
+ if (FAILED(hrc))
+ Log2(("Forward scenario: can't delete all original files.\n"));
+
+ /* delete no longer needed source directories */
+ if ( taskMoveVM->m_vmFolders[VBox_SnapshotFolder].isNotEmpty()
+ && RTDirExists(taskMoveVM->m_vmFolders[VBox_SnapshotFolder].c_str()))
+ RTDirRemove(taskMoveVM->m_vmFolders[VBox_SnapshotFolder].c_str());
+
+ if ( taskMoveVM->m_vmFolders[VBox_LogFolder].isNotEmpty()
+ && RTDirExists(taskMoveVM->m_vmFolders[VBox_LogFolder].c_str()))
+ RTDirRemove(taskMoveVM->m_vmFolders[VBox_LogFolder].c_str());
+
+ if ( taskMoveVM->m_vmFolders[VBox_SettingFolder].isNotEmpty()
+ && RTDirExists(taskMoveVM->m_vmFolders[VBox_SettingFolder].c_str()))
+ RTDirRemove(taskMoveVM->m_vmFolders[VBox_SettingFolder].c_str());
+ }
+
+ if (!taskMoveVM->m_pProgress.isNull())
+ taskMoveVM->m_pProgress->i_notifyComplete(taskMoveVM->m_result);
+
+ LogFlowFuncLeave();
+}
+
+HRESULT MachineMoveVM::moveAllDisks(const std::map<Utf8Str, MEDIUMTASKMOVE> &listOfDisks,
+ const Utf8Str &strTargetFolder)
+{
+ HRESULT rc = S_OK;
+ ComObjPtr<Machine> &machine = m_pMachine;
+ Utf8Str strLocation;
+
+ AutoWriteLock machineLock(machine COMMA_LOCKVAL_SRC_POS);
+
+ try
+ {
+ std::map<Utf8Str, MEDIUMTASKMOVE>::const_iterator itMedium = listOfDisks.begin();
+ while (itMedium != listOfDisks.end())
+ {
+ const MEDIUMTASKMOVE &mt = itMedium->second;
+ ComPtr<IMedium> pMedium = mt.pMedium;
+ Utf8Str strTargetImageName;
+ Bstr bstrLocation;
+ Bstr bstrSrcName;
+
+ rc = pMedium->COMGETTER(Name)(bstrSrcName.asOutParam());
+ if (FAILED(rc)) throw rc;
+
+ if (strTargetFolder.isNotEmpty())
+ {
+ strTargetImageName = strTargetFolder;
+ rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(rc)) throw rc;
+ strLocation = bstrLocation;
+
+ if (mt.fSnapshot == true)
+ strLocation.stripFilename().stripPath().append(RTPATH_DELIMITER).append(Utf8Str(bstrSrcName));
+ else
+ strLocation.stripPath();
+
+ strTargetImageName.append(RTPATH_DELIMITER).append(strLocation);
+ rc = m_pProgress->SetNextOperation(BstrFmt(tr("Moving medium '%ls' ..."),
+ bstrSrcName.raw()).raw(),
+ mt.uWeight);
+ if (FAILED(rc)) throw rc;
+ }
+ else
+ {
+ strTargetImageName = mt.strBaseName;//Should contain full path to the image
+ rc = m_pProgress->SetNextOperation(BstrFmt(tr("Moving medium '%ls' back..."),
+ bstrSrcName.raw()).raw(),
+ mt.uWeight);
+ if (FAILED(rc)) throw rc;
+ }
+
+
+
+ /* consistency: use \ if appropriate on the platform */
+ RTPathChangeToDosSlashes(strTargetImageName.mutableRaw(), false);
+
+ bstrLocation = strTargetImageName.c_str();
+
+ MediumType_T mediumType;//immutable, shared, passthrough
+ rc = pMedium->COMGETTER(Type)(&mediumType);
+ if (FAILED(rc)) throw rc;
+
+ DeviceType_T deviceType;//floppy, hard, DVD
+ rc = pMedium->COMGETTER(DeviceType)(&deviceType);
+ if (FAILED(rc)) throw rc;
+
+ /* Drop lock early because IMedium::MoveTo needs to get the VirtualBox one. */
+ machineLock.release();
+
+ ComPtr<IProgress> moveDiskProgress;
+ rc = pMedium->MoveTo(bstrLocation.raw(), moveDiskProgress.asOutParam());
+ if (SUCCEEDED(rc))
+ {
+ /* In case of failure moveDiskProgress would be in the invalid state or not initialized at all
+ * Call i_waitForOtherProgressCompletion only in success
+ */
+ /* Wait until the other process has finished. */
+ rc = m_pProgress->WaitForOtherProgressCompletion(moveDiskProgress, 0 /* indefinite wait */);
+ }
+
+ /*acquire the lock back*/
+ machineLock.acquire();
+
+ if (FAILED(rc)) throw rc;
+
+ Log2(("Moving %s has been finished\n", strTargetImageName.c_str()));
+
+ ++itMedium;
+ }
+
+ machineLock.release();
+ }
+ catch(HRESULT hrc)
+ {
+ Log2(("Exception during moving the disk %s\n", strLocation.c_str()));
+ rc = hrc;
+ machineLock.release();
+ }
+ catch (...)
+ {
+ Log2(("Exception during moving the disk %s\n", strLocation.c_str()));
+ rc = VirtualBoxBase::handleUnexpectedExceptions(m_pMachine, RT_SRC_POS);
+ machineLock.release();
+ }
+
+ return rc;
+}
+
+void MachineMoveVM::updatePathsToStateFiles(const Utf8Str &sourcePath, const Utf8Str &targetPath)
+{
+ ComObjPtr<Snapshot> pSnapshot;
+ HRESULT rc = m_pMachine->i_findSnapshotById(Guid() /* zero */, pSnapshot, true);
+ if (SUCCEEDED(rc) && !pSnapshot.isNull())
+ pSnapshot->i_updateSavedStatePaths(sourcePath.c_str(),
+ targetPath.c_str());
+ if (m_pMachine->mSSData->strStateFilePath.isNotEmpty())
+ {
+ if (RTPathStartsWith(m_pMachine->mSSData->strStateFilePath.c_str(), sourcePath.c_str()))
+ m_pMachine->mSSData->strStateFilePath = Utf8StrFmt("%s%s",
+ targetPath.c_str(),
+ m_pMachine->mSSData->strStateFilePath.c_str() + sourcePath.length());
+ else
+ m_pMachine->mSSData->strStateFilePath = Utf8StrFmt("%s%c%s",
+ targetPath.c_str(),
+ RTPATH_DELIMITER,
+ RTPathFilename(m_pMachine->mSSData->strStateFilePath.c_str()));
+ }
+}
+
+void MachineMoveVM::updatePathsToNVRAMFiles(const Utf8Str &sourcePath, const Utf8Str &targetPath)
+{
+ ComObjPtr<Snapshot> pSnapshot;
+ HRESULT rc = m_pMachine->i_findSnapshotById(Guid() /* zero */, pSnapshot, true);
+ if (SUCCEEDED(rc) && !pSnapshot.isNull())
+ pSnapshot->i_updateNVRAMPaths(sourcePath.c_str(),
+ targetPath.c_str());
+ ComObjPtr<NvramStore> pNvramStore(m_pMachine->mNvramStore);
+ const Utf8Str NVRAMFile(pNvramStore->i_getNonVolatileStorageFile());
+ if (NVRAMFile.isNotEmpty())
+ {
+ Utf8Str newNVRAMFile;
+ if (RTPathStartsWith(NVRAMFile.c_str(), sourcePath.c_str()))
+ newNVRAMFile = Utf8StrFmt("%s%s", targetPath.c_str(), NVRAMFile.c_str() + sourcePath.length());
+ else
+ newNVRAMFile = Utf8StrFmt("%s%c%s", targetPath.c_str(), RTPATH_DELIMITER, RTPathFilename(newNVRAMFile.c_str()));
+ pNvramStore->i_updateNonVolatileStorageFile(newNVRAMFile);
+ }
+}
+
+HRESULT MachineMoveVM::getFilesList(const Utf8Str &strRootFolder, fileList_t &filesList)
+{
+ RTDIR hDir;
+ HRESULT hrc = S_OK;
+ int vrc = RTDirOpen(&hDir, strRootFolder.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ /** @todo r=bird: RTDIRENTRY is big and this function is doing
+ * unrestrained recursion of arbritrary depth. Four things:
+ * - Add a depth counter parameter and refuse to go deeper than
+ * a certain reasonable limit.
+ * - Split this method into a main and a worker, placing
+ * RTDIRENTRY on the stack in the main and passing it onto to
+ * worker as a parameter.
+ * - RTDirRead may fail for reasons other than
+ * VERR_NO_MORE_FILES. For instance someone could create an
+ * entry with a name longer than RTDIRENTRY have space to
+ * store (windows host with UTF-16 encoding shorter than 255
+ * chars, but UTF-8 encoding longer than 260).
+ * - enmType can be RTDIRENTRYTYPE_UNKNOWN if the file system or
+ * the host doesn't return the information. See
+ * RTDIRENTRY::enmType. Use RTDirQueryUnknownType() to get the
+ * actual type. */
+ RTDIRENTRY DirEntry;
+ while (RT_SUCCESS(RTDirRead(hDir, &DirEntry, NULL)))
+ {
+ if (RTDirEntryIsStdDotLink(&DirEntry))
+ continue;
+
+ if (DirEntry.enmType == RTDIRENTRYTYPE_FILE)
+ {
+ Utf8Str fullPath(strRootFolder);
+ filesList.add(strRootFolder, DirEntry.szName);
+ }
+ else if (DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY)
+ {
+ Utf8Str strNextFolder(strRootFolder);
+ strNextFolder.append(RTPATH_DELIMITER).append(DirEntry.szName);
+ hrc = getFilesList(strNextFolder, filesList);
+ if (FAILED(hrc))
+ break;
+ }
+ }
+
+ vrc = RTDirClose(hDir);
+ AssertRC(vrc);
+ }
+ else if (vrc == VERR_FILE_NOT_FOUND)
+ hrc = m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Folder '%s' doesn't exist (%Rrc)"),
+ strRootFolder.c_str(), vrc);
+ else
+ hrc = m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not open folder '%s' (%Rrc)"),
+ strRootFolder.c_str(), vrc);
+
+ return hrc;
+}
+
+HRESULT MachineMoveVM::deleteFiles(const RTCList<Utf8Str> &listOfFiles)
+{
+ HRESULT hrc = S_OK;
+ /* Delete all created files. */
+ for (size_t i = 0; i < listOfFiles.size(); ++i)
+ {
+ Log2(("Deleting file %s ...\n", listOfFiles.at(i).c_str()));
+ hrc = m_pProgress->SetNextOperation(BstrFmt(tr("Deleting file %s..."), listOfFiles.at(i).c_str()).raw(), 1);
+ if (FAILED(hrc)) return hrc;
+
+ int vrc = RTFileDelete(listOfFiles.at(i).c_str());
+ if (RT_FAILURE(vrc))
+ return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not delete file '%s' (%Rrc)"),
+ listOfFiles.at(i).c_str(), vrc);
+
+ else
+ Log2(("File %s has been deleted\n", listOfFiles.at(i).c_str()));
+ }
+
+ return hrc;
+}
+
+HRESULT MachineMoveVM::getFolderSize(const Utf8Str &strRootFolder, uint64_t &size)
+{
+ HRESULT hrc = S_OK;
+ int vrc = 0;
+ uint64_t totalFolderSize = 0;
+ fileList_t filesList;
+
+ bool ex = RTPathExists(strRootFolder.c_str());
+ if (ex == true)
+ {
+ hrc = getFilesList(strRootFolder, filesList);
+ if (SUCCEEDED(hrc))
+ {
+ cit_t it = filesList.m_list.begin();
+ while (it != filesList.m_list.end())
+ {
+ uint64_t cbFile = 0;
+ Utf8Str fullPath = it->first;
+ fullPath.append(RTPATH_DELIMITER).append(it->second);
+ vrc = RTFileQuerySizeByPath(fullPath.c_str(), &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ totalFolderSize += cbFile;
+ }
+ else
+ return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not get the size of file '%s': %Rrc"),
+ fullPath.c_str(),
+ vrc);
+
+ ++it;
+ }
+
+ size = totalFolderSize;
+ }
+ }
+ else
+ size = 0;
+
+ return hrc;
+}
+
+HRESULT MachineMoveVM::queryBaseName(const ComPtr<IMedium> &pMedium, Utf8Str &strBaseName) const
+{
+ ComPtr<IMedium> pBaseMedium;
+ HRESULT rc = pMedium->COMGETTER(Base)(pBaseMedium.asOutParam());
+ if (FAILED(rc)) return rc;
+ Bstr bstrBaseName;
+ rc = pBaseMedium->COMGETTER(Name)(bstrBaseName.asOutParam());
+ if (FAILED(rc)) return rc;
+ strBaseName = bstrBaseName;
+ return rc;
+}
+
+HRESULT MachineMoveVM::createMachineList(const ComPtr<ISnapshot> &pSnapshot)
+{
+ Bstr name;
+ HRESULT rc = pSnapshot->COMGETTER(Name)(name.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ ComPtr<IMachine> l_pMachine;
+ rc = pSnapshot->COMGETTER(Machine)(l_pMachine.asOutParam());
+ if (FAILED(rc)) return rc;
+ machineList.push_back((Machine*)(IMachine*)l_pMachine);
+
+ SafeIfaceArray<ISnapshot> sfaChilds;
+ rc = pSnapshot->COMGETTER(Children)(ComSafeArrayAsOutParam(sfaChilds));
+ if (FAILED(rc)) return rc;
+ for (size_t i = 0; i < sfaChilds.size(); ++i)
+ {
+ rc = createMachineList(sfaChilds[i]);
+ if (FAILED(rc)) return rc;
+ }
+
+ return rc;
+}
+
+HRESULT MachineMoveVM::queryMediasForAllStates()
+{
+ /* In this case we create a exact copy of the original VM. This means just
+ * adding all directly and indirectly attached disk images to the worker
+ * list. */
+ HRESULT rc = S_OK;
+ for (size_t i = 0; i < machineList.size(); ++i)
+ {
+ const ComObjPtr<Machine> &machine = machineList.at(i);
+
+ /* Add all attachments (and their parents) of the different
+ * machines to a worker list. */
+ SafeIfaceArray<IMediumAttachment> sfaAttachments;
+ rc = machine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
+ if (FAILED(rc)) return rc;
+ for (size_t a = 0; a < sfaAttachments.size(); ++a)
+ {
+ const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
+ DeviceType_T deviceType;//floppy, hard, DVD
+ rc = pAtt->COMGETTER(Type)(&deviceType);
+ if (FAILED(rc)) return rc;
+
+ /* Valid medium attached? */
+ ComPtr<IMedium> pMedium;
+ rc = pAtt->COMGETTER(Medium)(pMedium.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ if (pMedium.isNull())
+ continue;
+
+ Bstr bstrLocation;
+ rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ /* Cast to ComObjPtr<Medium> */
+ ComObjPtr<Medium> pObjMedium = (Medium *)(IMedium *)pMedium;
+
+ /* Check for "read-only" medium in terms that VBox can't create this one */
+ rc = isMediumTypeSupportedForMoving(pMedium);
+ if (FAILED(rc))
+ {
+ if (rc == S_FALSE)
+ {
+ Log2(("Skipping file %ls because of this medium type hasn't been supported for moving.\n",
+ bstrLocation.raw()));
+ continue;
+ }
+ else
+ return rc;
+ }
+
+ MEDIUMTASKCHAINMOVE mtc;
+ mtc.devType = deviceType;
+ mtc.fAttachLinked = false;
+ mtc.fCreateDiffs = false;
+
+ while (!pMedium.isNull())
+ {
+ /* Refresh the state so that the file size get read. */
+ MediumState_T e;
+ rc = pMedium->RefreshState(&e);
+ if (FAILED(rc)) return rc;
+
+ LONG64 lSize;
+ rc = pMedium->COMGETTER(Size)(&lSize);
+ if (FAILED(rc)) return rc;
+
+ MediumType_T mediumType;//immutable, shared, passthrough
+ rc = pMedium->COMGETTER(Type)(&mediumType);
+ if (FAILED(rc)) return rc;
+
+ rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ MEDIUMTASKMOVE mt;// = {false, "basename", NULL, 0, 0};
+ mt.strBaseName = bstrLocation;
+ Utf8Str const &strFolder = m_vmFolders[VBox_SnapshotFolder];
+
+ if (strFolder.isNotEmpty() && RTPathStartsWith(mt.strBaseName.c_str(), strFolder.c_str()))
+ mt.fSnapshot = true;
+ else
+ mt.fSnapshot = false;
+
+ mt.uIdx = UINT32_MAX;
+ mt.pMedium = pMedium;
+ mt.uWeight = (ULONG)((lSize + _1M - 1) / _1M);
+ mtc.chain.append(mt);
+
+ /* Query next parent. */
+ rc = pMedium->COMGETTER(Parent)(pMedium.asOutParam());
+ if (FAILED(rc)) return rc;
+ }
+
+ m_llMedias.append(mtc);
+ }
+
+ /* Add the save state files of this machine if there is one. */
+ rc = addSaveState(machine);
+ if (FAILED(rc)) return rc;
+
+ /* Add the NVRAM files of this machine if there is one. */
+ rc = addNVRAM(machine);
+ if (FAILED(rc)) return rc;
+ }
+
+ /* Build up the index list of the image chain. Unfortunately we can't do
+ * that in the previous loop, cause there we go from child -> parent and
+ * didn't know how many are between. */
+ for (size_t i = 0; i < m_llMedias.size(); ++i)
+ {
+ uint32_t uIdx = 0;
+ MEDIUMTASKCHAINMOVE &mtc = m_llMedias.at(i);
+ for (size_t a = mtc.chain.size(); a > 0; --a)
+ mtc.chain[a - 1].uIdx = uIdx++;
+ }
+
+ return rc;
+}
+
+HRESULT MachineMoveVM::addSaveState(const ComObjPtr<Machine> &machine)
+{
+ Bstr bstrSrcSaveStatePath;
+ HRESULT rc = machine->COMGETTER(StateFilePath)(bstrSrcSaveStatePath.asOutParam());
+ if (FAILED(rc)) return rc;
+ if (!bstrSrcSaveStatePath.isEmpty())
+ {
+ SNAPFILETASKMOVE sft;
+
+ sft.snapshotUuid = machine->i_getSnapshotId();
+ sft.strFile = bstrSrcSaveStatePath;
+ uint64_t cbSize;
+
+ int vrc = RTFileQuerySizeByPath(sft.strFile.c_str(), &cbSize);
+ if (RT_FAILURE(vrc))
+ return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not get file size of '%s': %Rrc"),
+ sft.strFile.c_str(),
+ vrc);
+
+ /* same rule as above: count both the data which needs to
+ * be read and written */
+ sft.uWeight = (ULONG)(2 * (cbSize + _1M - 1) / _1M);
+ m_llSaveStateFiles.append(sft);
+ }
+ return S_OK;
+}
+
+HRESULT MachineMoveVM::addNVRAM(const ComObjPtr<Machine> &machine)
+{
+ ComPtr<INvramStore> pNvramStore;
+ HRESULT rc = machine->COMGETTER(NonVolatileStore)(pNvramStore.asOutParam());
+ if (FAILED(rc)) return rc;
+ Bstr bstrSrcNVRAMPath;
+ rc = pNvramStore->COMGETTER(NonVolatileStorageFile)(bstrSrcNVRAMPath.asOutParam());
+ if (FAILED(rc)) return rc;
+ Utf8Str strSrcNVRAMPath(bstrSrcNVRAMPath);
+ if (!strSrcNVRAMPath.isEmpty() && RTFileExists(strSrcNVRAMPath.c_str()))
+ {
+ SNAPFILETASKMOVE sft;
+
+ sft.snapshotUuid = machine->i_getSnapshotId();
+ sft.strFile = strSrcNVRAMPath;
+ uint64_t cbSize;
+
+ int vrc = RTFileQuerySizeByPath(sft.strFile.c_str(), &cbSize);
+ if (RT_FAILURE(vrc))
+ return m_pMachine->setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not get file size of '%s': %Rrc"),
+ sft.strFile.c_str(),
+ vrc);
+
+ /* same rule as above: count both the data which needs to
+ * be read and written */
+ sft.uWeight = (ULONG)(2 * (cbSize + _1M - 1) / _1M);
+ m_llNVRAMFiles.append(sft);
+ }
+ return S_OK;
+}
+
+void MachineMoveVM::updateProgressStats(MEDIUMTASKCHAINMOVE &mtc, ULONG &uCount, ULONG &uTotalWeight) const
+{
+
+ /* Currently the copying of diff images involves reading at least
+ * the biggest parent in the previous chain. So even if the new
+ * diff image is small in size, it could need some time to create
+ * it. Adding the biggest size in the chain should balance this a
+ * little bit more, i.e. the weight is the sum of the data which
+ * needs to be read and written. */
+ ULONG uMaxWeight = 0;
+ for (size_t e = mtc.chain.size(); e > 0; --e)
+ {
+ MEDIUMTASKMOVE &mt = mtc.chain.at(e - 1);
+ mt.uWeight += uMaxWeight;
+
+ /* Calculate progress data */
+ ++uCount;
+ uTotalWeight += mt.uWeight;
+
+ /* Save the max size for better weighting of diff image
+ * creation. */
+ uMaxWeight = RT_MAX(uMaxWeight, mt.uWeight);
+ }
+}
+
+HRESULT MachineMoveVM::isMediumTypeSupportedForMoving(const ComPtr<IMedium> &pMedium)
+{
+ Bstr bstrLocation;
+ HRESULT rc = pMedium->COMGETTER(Location)(bstrLocation.asOutParam());
+ if (FAILED(rc))
+ return rc;
+
+ DeviceType_T deviceType;
+ rc = pMedium->COMGETTER(DeviceType)(&deviceType);
+ if (FAILED(rc))
+ return rc;
+
+ ComPtr<IMediumFormat> mediumFormat;
+ rc = pMedium->COMGETTER(MediumFormat)(mediumFormat.asOutParam());
+ if (FAILED(rc))
+ return rc;
+
+ /* Check whether VBox is able to create this medium format or not, i.e. medium can be "read-only" */
+ Bstr bstrFormatName;
+ rc = mediumFormat->COMGETTER(Name)(bstrFormatName.asOutParam());
+ if (FAILED(rc))
+ return rc;
+
+ Utf8Str formatName = Utf8Str(bstrFormatName);
+ if (formatName.compare("VHDX", Utf8Str::CaseInsensitive) == 0)
+ {
+ Log2(("Skipping medium %ls. VHDX format is supported in \"read-only\" mode only.\n", bstrLocation.raw()));
+ return S_FALSE;
+ }
+
+ /* Check whether medium is represented by file on the disk or not */
+ ComObjPtr<Medium> pObjMedium = (Medium *)(IMedium *)pMedium;
+ if (!pObjMedium->i_isMediumFormatFile())
+ {
+ Log2(("Skipping medium %ls because it's not a real file on the disk.\n", bstrLocation.raw()));
+ return S_FALSE;
+ }
+
+ /* some special checks for DVD */
+ if (deviceType == DeviceType_DVD)
+ {
+ Utf8Str ext = bstrLocation;
+ /* only ISO image is moved */
+ if (!ext.endsWith(".iso", Utf8Str::CaseInsensitive))
+ {
+ Log2(("Skipping file %ls. Only ISO images are supported for now.\n", bstrLocation.raw()));
+ return S_FALSE;
+ }
+ }
+
+ return S_OK;
+}
diff --git a/src/VBox/Main/src-server/Makefile.kup b/src/VBox/Main/src-server/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-server/Makefile.kup
diff --git a/src/VBox/Main/src-server/Matching.cpp b/src/VBox/Main/src-server/Matching.cpp
new file mode 100644
index 00000000..d87a2fc8
--- /dev/null
+++ b/src/VBox/Main/src-server/Matching.cpp
@@ -0,0 +1,212 @@
+/* $Id: Matching.cpp $ */
+/** @file
+ * @todo r=bird: brief description, please.
+ *
+ * Definition of template classes that provide simple API to
+ * do matching between values and value filters constructed from strings.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN
+#include "Matching.h"
+
+#include "LoggingNew.h"
+
+#include <stdlib.h>
+
+#include <iprt/errcore.h>
+
+namespace matching
+{
+
+// static
+void ParsedIntervalFilter_base::parse (const char *aFilter,
+ ParsedIntervalFilter_base *that)
+{
+ // initially null and valid
+ that->mNull = true;
+ that->mValid = true;
+ that->mErrorPosition = 0;
+
+ if (!aFilter || strncmp(aFilter, RT_STR_TUPLE("int:")) != 0)
+ return;
+
+ that->mNull = false;
+
+ size_t len = strlen (aFilter);
+
+ Mode mode = Single; // what's expected next
+ size_t start = 4, end = 4;
+ size_t err = 0; // less than 4 indicates success
+
+ do
+ {
+ end = strcspn(aFilter + start, ",-");
+ end += start;
+
+ char delim = aFilter[end];
+
+ if (delim == '-')
+ {
+ if (mode == End)
+ {
+ err = end;
+ break;
+ }
+ else
+ mode = Start;
+ }
+
+ // skip spaces around numbers
+ size_t s = start;
+ while (s < end && aFilter[s] == ' ') ++s;
+ size_t e = end - 1;
+ while (e > s && aFilter[e] == ' ') --e;
+ ++e;
+
+ that->parseValue(aFilter, s, e, mode);
+ if (!that->mValid)
+ return;
+
+ if (mode == Start)
+ mode = End;
+ else if (mode == End)
+ mode = Single;
+
+ start = end + 1;
+ }
+ while (start <= len);
+
+ if (err >= 4)
+ {
+ that->mValid = false;
+ that->mErrorPosition = err;
+ }
+}
+
+// static
+size_t ParsedIntervalFilter_base::parseValue (
+ const char *aFilter, size_t aStart, size_t aEnd,
+ bool aIsSigned, const Limits &aLimits,
+ Widest &val)
+{
+ char *endptr = NULL;
+
+ int vrc = 0;
+ if (aIsSigned)
+ vrc = RTStrToInt64Ex(aFilter + aStart, &endptr, 0, &val.ll);
+ else
+ vrc = RTStrToUInt64Ex(aFilter + aStart, &endptr, 0, &val.ull);
+
+ AssertReturn(endptr, 0);
+
+ size_t parsed = (size_t)(endptr - aFilter);
+
+ // return parsed if not able to parse to the end
+ if (parsed != aEnd)
+ return parsed;
+
+ // return aStart if out if range
+ if (vrc == VWRN_NUMBER_TOO_BIG ||
+ (aIsSigned &&
+ (val.ll < aLimits.min.ll ||
+ val.ll > aLimits.max.ll)) ||
+ (!aIsSigned &&
+ (val.ull < aLimits.min.ull ||
+ val.ull > aLimits.max.ull)))
+ return aStart;
+
+ return parsed;
+}
+
+void ParsedBoolFilter::parse (const Bstr &aFilter)
+{
+ mNull = false;
+ mValid = true;
+ mErrorPosition = 0;
+
+ if (aFilter.isEmpty())
+ {
+ mValueAny = true;
+ mValue = false;
+ }
+ else
+ {
+ mValueAny = false;
+ if (aFilter == L"true" || aFilter == L"yes" || aFilter == L"1")
+ mValue = true;
+ else
+ if (aFilter == L"false" || aFilter == L"no" || aFilter == L"0")
+ mValue = false;
+ else
+ mValid = false;
+ }
+}
+
+void ParsedRegexpFilter_base::parse (const Bstr &aFilter)
+{
+ /// @todo (dmik) parse "rx:<regexp>" string
+ // note, that min/max checks must not be done, when the string
+ // begins with "rx:". These limits are for exact matching only!
+
+ // empty or null string means any match (see #isMatch() below),
+ // so we don't apply Min/Max restrictions in this case
+
+ if (!aFilter.isEmpty())
+ {
+ size_t len = aFilter.length();
+
+ if (mMinLen > 0 && len < mMinLen)
+ {
+ mNull = mValid = false;
+ mErrorPosition = len;
+ return;
+ }
+
+ if (mMaxLen > 0 && len > mMaxLen)
+ {
+ mNull = mValid = false;
+ mErrorPosition = mMaxLen;
+ return;
+ }
+ }
+
+ mSimple = aFilter;
+ mNull = false;
+ mValid = true;
+ mErrorPosition = 0;
+}
+
+bool ParsedRegexpFilter_base::isMatch (const Bstr &aValue) const
+{
+ /// @todo (dmik) do regexp matching
+
+ // empty or null mSimple matches any match
+ return mSimple.isEmpty()
+ || (mIgnoreCase && mSimple.compare(aValue, Bstr::CaseInsensitive) == 0)
+ || (!mIgnoreCase && mSimple.compare(aValue) == 0);
+}
+
+} /* namespace matching */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/MediumAttachmentImpl.cpp b/src/VBox/Main/src-server/MediumAttachmentImpl.cpp
new file mode 100644
index 00000000..c29bc621
--- /dev/null
+++ b/src/VBox/Main/src-server/MediumAttachmentImpl.cpp
@@ -0,0 +1,644 @@
+/* $Id: MediumAttachmentImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_MEDIUMATTACHMENT
+#include "MediumAttachmentImpl.h"
+#include "MachineImpl.h"
+#include "MediumImpl.h"
+#include "Global.h"
+#include "StringifyEnums.h"
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+#include <iprt/cpp/utils.h>
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// private member data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct BackupableMediumAttachmentData
+{
+ BackupableMediumAttachmentData()
+ : fImplicit(false)
+ { }
+
+ ComObjPtr<Medium> pMedium;
+ /* Since MediumAttachment is not a first class citizen when it
+ * comes to managing settings, having a reference to the storage
+ * controller will not work - when settings are changed it will point
+ * to the old, uninitialized instance. Changing this requires
+ * substantial changes to MediumImpl.cpp. */
+ /* Same counts for the assigned bandwidth group */
+ bool fImplicit;
+ const Utf8Str strControllerName;
+ settings::AttachedDevice mData;
+};
+
+struct MediumAttachment::Data
+{
+ Data(Machine * const aMachine = NULL)
+ : pMachine(aMachine),
+ fIsEjected(false)
+ { }
+
+ /** Reference to Machine object, for checking mutable state. */
+ Machine * const pMachine;
+ /* later: const ComObjPtr<MediumAttachment> mPeer; */
+ bool fIsEjected;
+ Backupable<BackupableMediumAttachmentData> bd;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(MediumAttachment)
+
+HRESULT MediumAttachment::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void MediumAttachment::FinalRelease()
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the medium attachment object.
+ *
+ * @param aParent Machine object.
+ * @param aMedium Medium object.
+ * @param aControllerName Controller the hard disk is attached to.
+ * @param aPort Port number.
+ * @param aDevice Device number on the port.
+ * @param aType Device type.
+ * @param aImplicit
+ * @param aPassthrough Whether accesses are directly passed to the host drive.
+ * @param aTempEject Whether guest-triggered eject results in unmounting the medium.
+ * @param aNonRotational Whether this medium is non-rotational (aka SSD).
+ * @param aDiscard Whether this medium supports discarding unused blocks.
+ * @param aHotPluggable Whether this medium is hot-pluggable.
+ * @param strBandwidthGroup Bandwidth group.
+ */
+HRESULT MediumAttachment::init(Machine *aParent,
+ Medium *aMedium,
+ const Utf8Str &aControllerName,
+ LONG aPort,
+ LONG aDevice,
+ DeviceType_T aType,
+ bool aImplicit,
+ bool aPassthrough,
+ bool aTempEject,
+ bool aNonRotational,
+ bool aDiscard,
+ bool aHotPluggable,
+ const Utf8Str &strBandwidthGroup)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent=%p aMedium=%p aControllerName=%s aPort=%d aDevice=%d aType=%d aImplicit=%d aPassthrough=%d aTempEject=%d aNonRotational=%d aDiscard=%d aHotPluggable=%d strBandwithGroup=%s\n", aParent, aMedium, aControllerName.c_str(), aPort, aDevice, aType, aImplicit, aPassthrough, aTempEject, aNonRotational, aDiscard, aHotPluggable, strBandwidthGroup.c_str()));
+
+ if (aType == DeviceType_HardDisk)
+ AssertReturn(aMedium, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+
+ m->bd.allocate();
+ m->bd->pMedium = aMedium;
+ m->bd->mData.strBwGroup = strBandwidthGroup;
+ unconst(m->bd->strControllerName) = aControllerName;
+ m->bd->mData.lPort = aPort;
+ m->bd->mData.lDevice = aDevice;
+ m->bd->mData.deviceType = aType;
+
+ m->bd->mData.fPassThrough = aPassthrough;
+ m->bd->mData.fTempEject = aTempEject;
+ m->bd->mData.fNonRotational = aNonRotational;
+ m->bd->mData.fDiscard = aDiscard;
+ m->bd->fImplicit = aImplicit;
+ m->bd->mData.fHotPluggable = aHotPluggable;
+
+ /* Confirm a successful initialization when it's the case */
+ autoInitSpan.setSucceeded();
+
+ /* Construct a short log name for this attachment. */
+ i_updateLogName();
+
+ LogFlowThisFunc(("LEAVE - %s\n", i_getLogName()));
+ return S_OK;
+}
+
+/**
+ * Initializes the medium attachment object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ */
+HRESULT MediumAttachment::initCopy(Machine *aParent, MediumAttachment *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+ /* m->pPeer is left null */
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.attachCopy(aThat->m->bd);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ /* Construct a short log name for this attachment. */
+ i_updateLogName();
+
+ LogFlowThisFunc(("LEAVE - %s\n", i_getLogName()));
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void MediumAttachment::uninit()
+{
+ LogFlowThisFunc(("ENTER - %s\n", i_getLogName()));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m->bd.free();
+
+ unconst(m->pMachine) = NULL;
+
+ delete m;
+ m = NULL;
+
+ LogFlowThisFuncLeave();
+}
+
+// IHardDiskAttachment properties
+/////////////////////////////////////////////////////////////////////////////
+
+
+HRESULT MediumAttachment::getMachine(ComPtr<IMachine> &aMachine)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<Machine> pMachine(m->pMachine);
+ pMachine.queryInterfaceTo(aMachine.asOutParam());
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+
+HRESULT MediumAttachment::getMedium(ComPtr<IMedium> &aHardDisk)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aHardDisk = m->bd->pMedium;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+
+HRESULT MediumAttachment::getController(com::Utf8Str &aController)
+{
+ LogFlowThisFuncEnter();
+
+ /* m->controller is constant during life time, no need to lock */
+ aController = Utf8Str(m->bd->strControllerName);
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+
+HRESULT MediumAttachment::getPort(LONG *aPort)
+{
+ LogFlowThisFuncEnter();
+
+ /* m->bd->port is constant during life time, no need to lock */
+ *aPort = m->bd->mData.lPort;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT MediumAttachment::getDevice(LONG *aDevice)
+{
+ LogFlowThisFuncEnter();
+
+ /* m->bd->device is constant during life time, no need to lock */
+ *aDevice = m->bd->mData.lDevice;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT MediumAttachment::getType(DeviceType_T *aType)
+{
+ LogFlowThisFuncEnter();
+
+ /* m->bd->type is constant during life time, no need to lock */
+ *aType = m->bd->mData.deviceType;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+
+HRESULT MediumAttachment::getPassthrough(BOOL *aPassthrough)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock lock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aPassthrough = m->bd->mData.fPassThrough;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+
+HRESULT MediumAttachment::getTemporaryEject(BOOL *aTemporaryEject)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock lock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aTemporaryEject = m->bd->mData.fTempEject;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+
+HRESULT MediumAttachment::getIsEjected(BOOL *aEjected)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock lock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aEjected = m->fIsEjected;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+
+HRESULT MediumAttachment::getNonRotational(BOOL *aNonRotational)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock lock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aNonRotational = m->bd->mData.fNonRotational;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT MediumAttachment::getDiscard(BOOL *aDiscard)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock lock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aDiscard = m->bd->mData.fDiscard;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+
+HRESULT MediumAttachment::getBandwidthGroup(ComPtr<IBandwidthGroup> &aBandwidthGroup)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+ if (m->bd->mData.strBwGroup.isNotEmpty())
+ {
+ ComObjPtr<BandwidthGroup> pBwGroup;
+ hrc = m->pMachine->i_getBandwidthGroup(m->bd->mData.strBwGroup, pBwGroup, true /* fSetError */);
+
+ Assert(SUCCEEDED(hrc)); /* This is not allowed to fail because the existence of the
+ group was checked when it was attached. */
+
+ if (SUCCEEDED(hrc))
+ pBwGroup.queryInterfaceTo(aBandwidthGroup.asOutParam());
+ }
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT MediumAttachment::getHotPluggable(BOOL *aHotPluggable)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock lock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aHotPluggable = m->bd->mData.fHotPluggable;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+void MediumAttachment::i_rollback()
+{
+ LogFlowThisFunc(("ENTER - %s\n", i_getLogName()));
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.rollback();
+
+ LogFlowThisFunc(("LEAVE - %s\n", i_getLogName()));
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+void MediumAttachment::i_commit()
+{
+ LogFlowThisFunc(("ENTER - %s\n", i_getLogName()));
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ m->bd.commit();
+
+ LogFlowThisFunc(("LEAVE - %s\n", i_getLogName()));
+}
+
+bool MediumAttachment::i_isImplicit() const
+{
+ return m->bd->fImplicit;
+}
+
+void MediumAttachment::i_setImplicit(bool aImplicit)
+{
+ Assert(!m->pMachine->i_isSnapshotMachine());
+ m->bd->fImplicit = aImplicit;
+
+ /* Construct a short log name for this attachment. */
+ i_updateLogName();
+}
+
+const ComObjPtr<Medium>& MediumAttachment::i_getMedium() const
+{
+ return m->bd->pMedium;
+}
+
+const Utf8Str &MediumAttachment::i_getControllerName() const
+{
+ return m->bd->strControllerName;
+}
+
+LONG MediumAttachment::i_getPort() const
+{
+ return m->bd->mData.lPort;
+}
+
+LONG MediumAttachment::i_getDevice() const
+{
+ return m->bd->mData.lDevice;
+}
+
+DeviceType_T MediumAttachment::i_getType() const
+{
+ return m->bd->mData.deviceType;
+}
+
+bool MediumAttachment::i_getPassthrough() const
+{
+ AutoReadLock lock(this COMMA_LOCKVAL_SRC_POS);
+ return m->bd->mData.fPassThrough;
+}
+
+bool MediumAttachment::i_getTempEject() const
+{
+ AutoReadLock lock(this COMMA_LOCKVAL_SRC_POS);
+ return m->bd->mData.fTempEject;
+}
+
+bool MediumAttachment::i_getNonRotational() const
+{
+ AutoReadLock lock(this COMMA_LOCKVAL_SRC_POS);
+ return m->bd->mData.fNonRotational;
+}
+
+bool MediumAttachment::i_getDiscard() const
+{
+ AutoReadLock lock(this COMMA_LOCKVAL_SRC_POS);
+ return m->bd->mData.fDiscard;
+}
+
+bool MediumAttachment::i_getHotPluggable() const
+{
+ AutoReadLock lock(this COMMA_LOCKVAL_SRC_POS);
+ return m->bd->mData.fHotPluggable;
+}
+
+Utf8Str& MediumAttachment::i_getBandwidthGroup() const
+{
+ return m->bd->mData.strBwGroup;
+}
+
+bool MediumAttachment::i_matches(const Utf8Str &aControllerName, LONG aPort, LONG aDevice)
+{
+ return ( aControllerName == m->bd->strControllerName
+ && aPort == m->bd->mData.lPort
+ && aDevice == m->bd->mData.lDevice);
+}
+
+/** Must be called from under this object's write lock. */
+void MediumAttachment::i_updateName(const Utf8Str &aName)
+{
+ Assert(isWriteLockOnCurrentThread());
+ Assert(!m->pMachine->i_isSnapshotMachine());
+
+ m->bd.backup();
+ unconst(m->bd->strControllerName) = aName;
+
+ /* Construct a short log name for this attachment. */
+ i_updateLogName();
+}
+
+/**
+ * Sets the medium of this attachment and unsets the "implicit" flag.
+ * @param aMedium
+ */
+void MediumAttachment::i_updateMedium(const ComObjPtr<Medium> &aMedium)
+{
+ Assert(isWriteLockOnCurrentThread());
+ /* No assertion for a snapshot. Method used in deleting snapshot. */
+
+ m->bd.backup();
+ m->bd->pMedium = aMedium;
+ m->bd->fImplicit = false;
+ m->fIsEjected = false;
+}
+
+/** Must be called from under this object's write lock. */
+void MediumAttachment::i_updatePassthrough(bool aPassthrough)
+{
+ Assert(isWriteLockOnCurrentThread());
+ Assert(!m->pMachine->i_isSnapshotMachine());
+
+ m->bd.backup();
+ m->bd->mData.fPassThrough = aPassthrough;
+}
+
+/** Must be called from under this object's write lock. */
+void MediumAttachment::i_updateTempEject(bool aTempEject)
+{
+ Assert(isWriteLockOnCurrentThread());
+ Assert(!m->pMachine->i_isSnapshotMachine());
+
+ m->bd.backup();
+ m->bd->mData.fTempEject = aTempEject;
+}
+
+/** Must be called from under this object's write lock. */
+void MediumAttachment::i_updateEjected()
+{
+ Assert(isWriteLockOnCurrentThread());
+ Assert(!m->pMachine->i_isSnapshotMachine());
+
+ m->fIsEjected = true;
+}
+
+/** Must be called from under this object's write lock. */
+void MediumAttachment::i_updateNonRotational(bool aNonRotational)
+{
+ Assert(isWriteLockOnCurrentThread());
+ Assert(!m->pMachine->i_isSnapshotMachine());
+
+ m->bd.backup();
+ m->bd->mData.fNonRotational = aNonRotational;
+}
+
+/** Must be called from under this object's write lock. */
+void MediumAttachment::i_updateDiscard(bool aDiscard)
+{
+ Assert(isWriteLockOnCurrentThread());
+ Assert(!m->pMachine->i_isSnapshotMachine());
+
+ m->bd.backup();
+ m->bd->mData.fDiscard = aDiscard;
+}
+
+/** Must be called from under this object's write lock. */
+void MediumAttachment::i_updateHotPluggable(bool aHotPluggable)
+{
+ Assert(isWriteLockOnCurrentThread());
+ Assert(!m->pMachine->i_isSnapshotMachine());
+
+ m->bd.backup();
+ m->bd->mData.fHotPluggable = aHotPluggable;
+}
+
+void MediumAttachment::i_updateBandwidthGroup(const Utf8Str &aBandwidthGroup)
+{
+ LogFlowThisFuncEnter();
+ Assert(isWriteLockOnCurrentThread());
+ Assert(!m->pMachine->i_isSnapshotMachine());
+
+ m->bd.backup();
+ m->bd->mData.strBwGroup = aBandwidthGroup;
+
+ LogFlowThisFuncLeave();
+}
+
+void MediumAttachment::i_updateParentMachine(Machine * const pMachine)
+{
+ LogFlowThisFunc(("ENTER - %s\n", i_getLogName()));
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+ Assert(!m->pMachine->i_isSnapshotMachine());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ unconst(m->pMachine) = pMachine;
+
+ LogFlowThisFunc(("LEAVE - %s\n", i_getLogName()));
+}
+
+void MediumAttachment::i_updateLogName()
+{
+ const char *pszName = m->bd->strControllerName.c_str();
+ const char *pszEndNick = strpbrk(pszName, " \t:-");
+ mLogName = Utf8StrFmt("MA%p[%.*s:%u:%u:%s%s]",
+ this,
+ pszEndNick ? pszEndNick - pszName : 4, pszName,
+ m->bd->mData.lPort, m->bd->mData.lDevice, ::stringifyDeviceType(m->bd->mData.deviceType),
+ m->bd->fImplicit ? ":I" : "");
+}
diff --git a/src/VBox/Main/src-server/MediumFormatImpl.cpp b/src/VBox/Main/src-server/MediumFormatImpl.cpp
new file mode 100644
index 00000000..10993b12
--- /dev/null
+++ b/src/VBox/Main/src-server/MediumFormatImpl.cpp
@@ -0,0 +1,279 @@
+/* $Id: MediumFormatImpl.cpp $ */
+/** @file
+ * MediumFormat COM class implementation
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_MEDIUMFORMAT
+#include "MediumFormatImpl.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+#include <VBox/vd.h>
+
+#include <iprt/cpp/utils.h>
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(MediumFormat)
+
+HRESULT MediumFormat::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void MediumFormat::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the hard disk format object.
+ *
+ * @param aVDInfo Pointer to a backend info object.
+ */
+HRESULT MediumFormat::init(const VDBACKENDINFO *aVDInfo)
+{
+ LogFlowThisFunc(("aVDInfo=%p\n", aVDInfo));
+
+ ComAssertRet(aVDInfo, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* The ID of the backend */
+ unconst(m.strId) = aVDInfo->pszBackend;
+ /* The Name of the backend */
+ /* Use id for now as long as VDBACKENDINFO hasn't any extra
+ * name/description field. */
+ unconst(m.strName) = aVDInfo->pszBackend;
+ /* The capabilities of the backend. Assumes 1:1 mapping! */
+ unconst(m.capabilities) = (MediumFormatCapabilities_T)aVDInfo->uBackendCaps;
+ /* Save the supported file extensions in a list */
+ if (aVDInfo->paFileExtensions)
+ {
+ PCVDFILEEXTENSION papExtension = aVDInfo->paFileExtensions;
+ while (papExtension->pszExtension != NULL)
+ {
+ DeviceType_T devType;
+
+ unconst(m.maFileExtensions).push_back(papExtension->pszExtension);
+
+ switch(papExtension->enmType)
+ {
+ case VDTYPE_HDD:
+ devType = DeviceType_HardDisk;
+ break;
+ case VDTYPE_OPTICAL_DISC:
+ devType = DeviceType_DVD;
+ break;
+ case VDTYPE_FLOPPY:
+ devType = DeviceType_Floppy;
+ break;
+ default:
+ AssertMsgFailed(("Invalid enm type %d!\n", papExtension->enmType));
+ return E_INVALIDARG;
+ }
+
+ unconst(m.maDeviceTypes).push_back(devType);
+ ++papExtension;
+ }
+ }
+ /* Save a list of configure properties */
+ if (aVDInfo->paConfigInfo)
+ {
+ PCVDCONFIGINFO pa = aVDInfo->paConfigInfo;
+ /* Walk through all available keys */
+ while (pa->pszKey != NULL)
+ {
+ Utf8Str defaultValue("");
+ DataType_T dt;
+ ULONG flags = static_cast<ULONG>(pa->uKeyFlags);
+ /* Check for the configure data type */
+ switch (pa->enmValueType)
+ {
+ case VDCFGVALUETYPE_INTEGER:
+ {
+ dt = DataType_Int32;
+ /* If there is a default value get them in the right format */
+ if (pa->pszDefaultValue)
+ defaultValue = pa->pszDefaultValue;
+ break;
+ }
+ case VDCFGVALUETYPE_BYTES:
+ {
+ dt = DataType_Int8;
+ /* If there is a default value get them in the right format */
+ if (pa->pszDefaultValue)
+ {
+ /* Copy the bytes over - treated simply as a string */
+ defaultValue = pa->pszDefaultValue;
+ flags |= DataFlags_Array;
+ }
+ break;
+ }
+ case VDCFGVALUETYPE_STRING:
+ {
+ dt = DataType_String;
+ /* If there is a default value get them in the right format */
+ if (pa->pszDefaultValue)
+ defaultValue = pa->pszDefaultValue;
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Invalid enm type %d!\n", pa->enmValueType));
+ return E_INVALIDARG;
+ }
+
+ /// @todo add extendedFlags to Property when we reach the 32 bit
+ /// limit (or make the argument ULONG64 after checking that COM is
+ /// capable of defining enums (used to represent bit flags) that
+ /// contain 64-bit values)
+ ComAssertRet(pa->uKeyFlags == ((ULONG)pa->uKeyFlags), E_FAIL);
+
+ /* Create one property structure */
+ const Property prop = { Utf8Str(pa->pszKey),
+ Utf8Str(""),
+ dt,
+ flags,
+ defaultValue };
+ unconst(m.maProperties).push_back(prop);
+ ++pa;
+ }
+ }
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void MediumFormat::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(m.maProperties).clear();
+ unconst(m.maFileExtensions).clear();
+ unconst(m.maDeviceTypes).clear();
+ unconst(m.capabilities) = (MediumFormatCapabilities_T)0;
+ unconst(m.strName).setNull();
+ unconst(m.strId).setNull();
+}
+
+// IMediumFormat properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT MediumFormat::getId(com::Utf8Str &aId)
+{
+ /* this is const, no need to lock */
+ aId = m.strId;
+
+ return S_OK;
+}
+
+HRESULT MediumFormat::getName(com::Utf8Str &aName)
+{
+ /* this is const, no need to lock */
+ aName = m.strName;
+
+ return S_OK;
+}
+
+HRESULT MediumFormat::getCapabilities(std::vector<MediumFormatCapabilities_T> &aCapabilities)
+{
+ /* m.capabilities is const, no need to lock */
+
+ aCapabilities.resize(sizeof(MediumFormatCapabilities_T) * 8);
+ size_t cCapabilities = 0;
+ for (size_t i = 0; i < aCapabilities.size(); i++)
+ {
+ uint64_t tmp = m.capabilities;
+ tmp &= 1ULL << i;
+ if (tmp)
+ aCapabilities[cCapabilities++] = (MediumFormatCapabilities_T)tmp;
+ }
+ aCapabilities.resize(RT_MAX(cCapabilities, 1));
+
+ return S_OK;
+}
+
+// IMediumFormat methods
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT MediumFormat::describeFileExtensions(std::vector<com::Utf8Str> &aExtensions,
+ std::vector<DeviceType_T> &aTypes)
+{
+ /* this is const, no need to lock */
+ aExtensions = m.maFileExtensions;
+ aTypes = m.maDeviceTypes;
+
+ return S_OK;
+}
+
+HRESULT MediumFormat::describeProperties(std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aDescriptions,
+ std::vector<DataType_T> &aTypes,
+ std::vector<ULONG> &aFlags,
+ std::vector<com::Utf8Str> &aDefaults)
+{
+ /* this is const, no need to lock */
+ size_t c = m.maProperties.size();
+ aNames.resize(c);
+ aDescriptions.resize(c);
+ aTypes.resize(c);
+ aFlags.resize(c);
+ aDefaults.resize(c);
+ for (size_t i = 0; i < c; i++)
+ {
+ const Property &prop = m.maProperties[i];
+ aNames[i] = prop.strName;
+ aDescriptions[i] = prop.strDescription;
+ aTypes[i] = prop.type;
+ aFlags[i] = prop.flags;
+ aDefaults[i] = prop.strDefaultValue;
+ }
+
+ return S_OK;
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/MediumIOImpl.cpp b/src/VBox/Main/src-server/MediumIOImpl.cpp
new file mode 100644
index 00000000..57382b2e
--- /dev/null
+++ b/src/VBox/Main/src-server/MediumIOImpl.cpp
@@ -0,0 +1,905 @@
+/* $Id: MediumIOImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation: MediumIO
+ */
+
+/*
+ * Copyright (C) 2018-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_MEDIUMIO
+#include "MediumIOImpl.h"
+#include "MediumImpl.h"
+#include "MediumLock.h"
+#include "DataStreamImpl.h"
+#include "Global.h"
+#include "ProgressImpl.h"
+#include "VirtualBoxImpl.h"
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "ThreadTask.h"
+
+#include <iprt/fsvfs.h>
+#include <iprt/dvm.h>
+#include <iprt/zero.h>
+#include <iprt/cpp/utils.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Private member data.
+ */
+struct MediumIO::Data
+{
+ Data(Medium * const a_pMedium, VirtualBox * const a_pVirtualBox, bool a_fWritable, uint32_t a_cbSector = 512)
+ : ptrMedium(a_pMedium)
+ , ptrVirtualBox(a_pVirtualBox)
+ , fWritable(a_fWritable)
+ , cbSector(a_cbSector)
+ , PasswordStore(false /*fKeyBufNonPageable*/)
+ , pHdd(NULL)
+ , hVfsFile(NIL_RTVFSFILE)
+ {
+ }
+
+ /** Reference to the medium we're accessing. */
+ ComPtr<Medium> ptrMedium;
+ /** Reference to the VirtualBox object the medium is part of. */
+ ComPtr<VirtualBox> ptrVirtualBox;
+ /** Set if writable, clear if readonly. */
+ bool fWritable;
+ /** The sector size. */
+ uint32_t cbSector;
+ /** Secret key store used to hold the passwords for encrypted medium. */
+ SecretKeyStore PasswordStore;
+ /** Crypto filter settings. */
+ MediumCryptoFilterSettings CryptoSettings;
+ /** Medium lock list. */
+ MediumLockList LockList;
+ /** The HDD instance. */
+ PVDISK pHdd;
+ /** VFS file for the HDD instance. */
+ RTVFSFILE hVfsFile;
+
+private:
+ Data() : PasswordStore(false) { }
+};
+
+
+/**
+ * MediumIO::StreamTask class for asynchronous convert to stream operation.
+ *
+ * @note Instances of this class must be created using new() because the
+ * task thread function will delete them when the task is complete.
+ *
+ * @note The constructor of this class adds a caller on the managed Medium
+ * object which is automatically released upon destruction.
+ */
+class MediumIO::StreamTask : public ThreadTask
+{
+public:
+ StreamTask(MediumIO *pMediumIO, DataStream *pDataStream, Progress *pProgress, const char *pszFormat,
+ MediumVariant_T fMediumVariant)
+ : ThreadTask("StreamTask"),
+ mMediumIO(pMediumIO),
+ mMediumCaller(pMediumIO->m->ptrMedium),
+ m_pDataStream(pDataStream),
+ m_fMediumVariant(fMediumVariant),
+ m_strFormat(pszFormat),
+ mProgress(pProgress),
+ mVirtualBoxCaller(NULL)
+ {
+ AssertReturnVoidStmt(pMediumIO, mRC = E_FAIL);
+ AssertReturnVoidStmt(pDataStream, mRC = E_FAIL);
+ mRC = mMediumCaller.rc();
+ if (FAILED(mRC))
+ return;
+
+ /* Get strong VirtualBox reference, see below. */
+ VirtualBox *pVirtualBox = pMediumIO->m->ptrVirtualBox;
+ mVirtualBox = pVirtualBox;
+ mVirtualBoxCaller.attach(pVirtualBox);
+ mRC = mVirtualBoxCaller.rc();
+ if (FAILED(mRC))
+ return;
+ }
+
+ // Make all destructors virtual. Just in case.
+ virtual ~StreamTask()
+ {
+ /* send the notification of completion.*/
+ if ( isAsync()
+ && !mProgress.isNull())
+ mProgress->i_notifyComplete(mRC);
+ }
+
+ HRESULT rc() const { return mRC; }
+ bool isOk() const { return SUCCEEDED(rc()); }
+
+ const ComPtr<Progress>& GetProgressObject() const {return mProgress;}
+
+ /**
+ * Implementation code for the "create base" task.
+ * Used as function for execution from a standalone thread.
+ */
+ void handler()
+ {
+ LogFlowFuncEnter();
+ try
+ {
+ mRC = executeTask(); /* (destructor picks up mRC, see above) */
+ LogFlowFunc(("rc=%Rhrc\n", mRC));
+ }
+ catch (...)
+ {
+ LogRel(("Some exception in the function MediumIO::StreamTask:handler()\n"));
+ }
+
+ LogFlowFuncLeave();
+ }
+
+ const ComObjPtr<MediumIO> mMediumIO;
+ AutoCaller mMediumCaller;
+
+protected:
+ HRESULT mRC;
+
+ ComObjPtr<DataStream> m_pDataStream;
+ MediumVariant_T m_fMediumVariant;
+ Utf8Str m_strFormat;
+
+private:
+ HRESULT executeTask();
+
+ const ComObjPtr<Progress> mProgress;
+
+ /* Must have a strong VirtualBox reference during a task otherwise the
+ * reference count might drop to 0 while a task is still running. This
+ * would result in weird behavior, including deadlocks due to uninit and
+ * locking order issues. The deadlock often is not detectable because the
+ * uninit uses event semaphores which sabotages deadlock detection. */
+ ComObjPtr<VirtualBox> mVirtualBox;
+ AutoCaller mVirtualBoxCaller;
+
+ static DECLCALLBACK(int) i_vdStreamOpen(void *pvUser, const char *pszLocation, uint32_t fOpen,
+ PFNVDCOMPLETED pfnCompleted, void **ppStorage);
+ static DECLCALLBACK(int) i_vdStreamClose(void *pvUser, void *pStorage);
+ static DECLCALLBACK(int) i_vdStreamDelete(void *pvUser, const char *pcszFilename);
+ static DECLCALLBACK(int) i_vdStreamMove(void *pvUser, const char *pcszSrc, const char *pcszDst, unsigned fMove);
+ static DECLCALLBACK(int) i_vdStreamGetFreeSpace(void *pvUser, const char *pcszFilename, int64_t *pcbFreeSpace);
+ static DECLCALLBACK(int) i_vdStreamGetModificationTime(void *pvUser, const char *pcszFilename, PRTTIMESPEC pModificationTime);
+ static DECLCALLBACK(int) i_vdStreamGetSize(void *pvUser, void *pStorage, uint64_t *pcbSize);
+ static DECLCALLBACK(int) i_vdStreamSetSize(void *pvUser, void *pStorage, uint64_t cbSize);
+ static DECLCALLBACK(int) i_vdStreamRead(void *pvUser, void *pStorage, uint64_t uOffset, void *pvBuffer, size_t cbBuffer,
+ size_t *pcbRead);
+ static DECLCALLBACK(int) i_vdStreamWrite(void *pvUser, void *pStorage, uint64_t uOffset,
+ const void *pvBuffer, size_t cbBuffer, size_t *pcbWritten);
+ static DECLCALLBACK(int) i_vdStreamFlush(void *pvUser, void *pStorage);
+};
+
+
+/**
+ * State of a streamed file.
+ */
+typedef struct STREAMFILE
+{
+ /** The data stream for this file state. */
+ DataStream *pDataStream;
+ /** The last seen offset used to stream zeroes for non consecutive writes. */
+ uint64_t uOffsetLast;
+ /** Set file size. */
+ uint64_t cbFile;
+} STREAMFILE;
+/** Pointer to the stream file state. */
+typedef STREAMFILE *PSTREAMFILE;
+
+
+
+DECLCALLBACK(int) MediumIO::StreamTask::i_vdStreamOpen(void *pvUser, const char *pszLocation, uint32_t fOpen, PFNVDCOMPLETED pfnCompleted,
+ void **ppStorage)
+{
+ RT_NOREF2(pvUser, pszLocation);
+
+ /* Validate input. */
+ AssertPtrReturn(ppStorage, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnCompleted, VERR_INVALID_PARAMETER);
+ AssertReturn((fOpen & RTFILE_O_ACCESS_MASK) == RTFILE_O_WRITE, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ PSTREAMFILE pStreamFile = (PSTREAMFILE)RTMemAllocZ(sizeof(*pStreamFile));
+ if (RT_LIKELY(pStreamFile))
+ {
+ pStreamFile->pDataStream = (DataStream *)pvUser;
+ pStreamFile->uOffsetLast = 0;
+ pStreamFile->cbFile = 0;
+ *ppStorage = pStreamFile;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+DECLCALLBACK(int) MediumIO::StreamTask::i_vdStreamClose(void *pvUser, void *pStorage)
+{
+ RT_NOREF(pvUser);
+ PSTREAMFILE pStreamFile = (PSTREAMFILE)pStorage;
+ int rc = VINF_SUCCESS;
+
+ /* Fill up to the configured file size. */
+ if (pStreamFile->uOffsetLast < pStreamFile->cbFile)
+ {
+ do
+ {
+ size_t cbThisWrite = sizeof(g_abRTZero64K);
+ size_t cbWritten = 0;
+
+ if (pStreamFile->cbFile - pStreamFile->uOffsetLast < sizeof(g_abRTZero64K))
+ cbThisWrite = (size_t)(pStreamFile->cbFile - pStreamFile->uOffsetLast);
+
+ rc = pStreamFile->pDataStream->i_write(&g_abRTZero64K[0], cbThisWrite, &cbWritten);
+ if (RT_SUCCESS(rc))
+ pStreamFile->uOffsetLast += cbWritten;
+
+ } while ( RT_SUCCESS(rc)
+ && pStreamFile->uOffsetLast < pStreamFile->cbFile);
+ }
+
+ int rc2 = pStreamFile->pDataStream->i_close();
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ RTMemFree(pStreamFile);
+ return rc;
+}
+
+DECLCALLBACK(int) MediumIO::StreamTask::i_vdStreamDelete(void *pvUser, const char *pcszFilename)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+DECLCALLBACK(int) MediumIO::StreamTask::i_vdStreamMove(void *pvUser, const char *pcszSrc, const char *pcszDst, unsigned fMove)
+{
+ NOREF(pvUser);
+ NOREF(pcszSrc);
+ NOREF(pcszDst);
+ NOREF(fMove);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+DECLCALLBACK(int) MediumIO::StreamTask::i_vdStreamGetFreeSpace(void *pvUser, const char *pcszFilename, int64_t *pcbFreeSpace)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pcbFreeSpace, VERR_INVALID_POINTER);
+ *pcbFreeSpace = INT64_MAX;
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) MediumIO::StreamTask::i_vdStreamGetModificationTime(void *pvUser, const char *pcszFilename, PRTTIMESPEC pModificationTime)
+{
+ NOREF(pvUser);
+ NOREF(pcszFilename);
+ AssertPtrReturn(pModificationTime, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+DECLCALLBACK(int) MediumIO::StreamTask::i_vdStreamGetSize(void *pvUser, void *pStorage, uint64_t *pcbSize)
+{
+ NOREF(pvUser);
+ PSTREAMFILE pStreamFile = (PSTREAMFILE)pStorage;
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+
+ *pcbSize = pStreamFile->cbFile;
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) MediumIO::StreamTask::i_vdStreamSetSize(void *pvUser, void *pStorage, uint64_t cbSize)
+{
+ RT_NOREF(pvUser);
+ PSTREAMFILE pStreamFile = (PSTREAMFILE)pStorage;
+
+ /* Reducing the size is not supported. */
+ int rc = VINF_SUCCESS;
+ if (pStreamFile->cbFile < cbSize)
+ pStreamFile->cbFile = cbSize;
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ return rc;
+}
+
+DECLCALLBACK(int) MediumIO::StreamTask::i_vdStreamRead(void *pvUser, void *pStorage, uint64_t uOffset, void *pvBuffer, size_t cbBuffer,
+ size_t *pcbRead)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ NOREF(uOffset);
+ NOREF(cbBuffer);
+ NOREF(pcbRead);
+ AssertPtrReturn(pvBuffer, VERR_INVALID_POINTER);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+DECLCALLBACK(int) MediumIO::StreamTask::i_vdStreamWrite(void *pvUser, void *pStorage, uint64_t uOffset, const void *pvBuffer, size_t cbBuffer,
+ size_t *pcbWritten)
+{
+ RT_NOREF(pvUser);
+ PSTREAMFILE pStreamFile = (PSTREAMFILE)pStorage;
+ int rc = VINF_SUCCESS;
+
+ /* Fill up to the new offset if there is non consecutive access. */
+ if (pStreamFile->uOffsetLast < uOffset)
+ {
+ do
+ {
+ size_t cbThisWrite = sizeof(g_abRTZero64K);
+ size_t cbWritten = 0;
+
+ if (uOffset - pStreamFile->uOffsetLast < sizeof(g_abRTZero64K))
+ cbThisWrite = (size_t)(uOffset - pStreamFile->uOffsetLast);
+
+ rc = pStreamFile->pDataStream->i_write(&g_abRTZero64K[0], cbThisWrite, &cbWritten);
+ if (RT_SUCCESS(rc))
+ pStreamFile->uOffsetLast += cbWritten;
+
+ } while ( RT_SUCCESS(rc)
+ && pStreamFile->uOffsetLast < uOffset);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcbWritten)
+ rc = pStreamFile->pDataStream->i_write(pvBuffer, cbBuffer, pcbWritten);
+ else
+ {
+ const uint8_t *pbBuf = (const uint8_t *)pvBuffer;
+ size_t cbLeft = cbBuffer;
+ size_t cbWritten = 0;
+ while ( cbLeft > 0
+ && RT_SUCCESS(rc))
+ {
+ rc = pStreamFile->pDataStream->i_write(pbBuf, cbLeft, &cbWritten);
+ if (RT_SUCCESS(rc))
+ {
+ pbBuf += cbWritten;
+ cbLeft -= cbWritten;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbWritten = pcbWritten ? *pcbWritten : cbBuffer;
+
+ /* Adjust file size. */
+ if (uOffset + cbWritten > pStreamFile->cbFile)
+ pStreamFile->cbFile = uOffset + cbWritten;
+
+ pStreamFile->uOffsetLast = uOffset + cbWritten;
+ }
+ }
+
+ return rc;
+}
+
+DECLCALLBACK(int) MediumIO::StreamTask::i_vdStreamFlush(void *pvUser, void *pStorage)
+{
+ NOREF(pvUser);
+ NOREF(pStorage);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Implementation code for the "stream" task.
+ */
+HRESULT MediumIO::StreamTask::executeTask()
+{
+ HRESULT hrc = S_OK;
+ VDINTERFACEIO IfsOutputIO;
+ VDINTERFACEPROGRESS IfsProgress;
+ PVDINTERFACE pIfsOp = NULL;
+ PVDINTERFACE pIfsImg = NULL;
+ PVDISK pDstDisk;
+
+ if (mProgress)
+ {
+ IfsProgress.pfnProgress = mProgress->i_vdProgressCallback;
+ VDInterfaceAdd(&IfsProgress.Core,
+ "Medium::StreamTask::vdInterfaceProgress",
+ VDINTERFACETYPE_PROGRESS,
+ mProgress,
+ sizeof(IfsProgress),
+ &pIfsOp);
+ }
+
+ IfsOutputIO.pfnOpen = i_vdStreamOpen;
+ IfsOutputIO.pfnClose = i_vdStreamClose;
+ IfsOutputIO.pfnDelete = i_vdStreamDelete;
+ IfsOutputIO.pfnMove = i_vdStreamMove;
+ IfsOutputIO.pfnGetFreeSpace = i_vdStreamGetFreeSpace;
+ IfsOutputIO.pfnGetModificationTime = i_vdStreamGetModificationTime;
+ IfsOutputIO.pfnGetSize = i_vdStreamGetSize;
+ IfsOutputIO.pfnSetSize = i_vdStreamSetSize;
+ IfsOutputIO.pfnReadSync = i_vdStreamRead;
+ IfsOutputIO.pfnWriteSync = i_vdStreamWrite;
+ IfsOutputIO.pfnFlushSync = i_vdStreamFlush;
+ VDInterfaceAdd(&IfsOutputIO.Core, "stream", VDINTERFACETYPE_IO,
+ m_pDataStream, sizeof(VDINTERFACEIO), &pIfsImg);
+
+ int vrc = VDCreate(NULL, VDTYPE_HDD, &pDstDisk);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Create the output image */
+ vrc = VDCopy(mMediumIO->m->pHdd, VD_LAST_IMAGE, pDstDisk, m_strFormat.c_str(),
+ "stream", false, 0, m_fMediumVariant, NULL,
+ VD_OPEN_FLAGS_NORMAL | VD_OPEN_FLAGS_SEQUENTIAL, pIfsOp,
+ pIfsImg, NULL);
+ if (RT_FAILURE(vrc))
+ hrc = mMediumIO->setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Failed to convert and stream disk image"));
+
+ VDDestroy(pDstDisk);
+ }
+ else
+ hrc = mMediumIO->setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Failed to create destination disk container"));
+
+ return hrc;
+}
+
+
+/*********************************************************************************************************************************
+* Boilerplate constructor & destructor *
+*********************************************************************************************************************************/
+
+DEFINE_EMPTY_CTOR_DTOR(MediumIO)
+
+HRESULT MediumIO::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void MediumIO::FinalRelease()
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+
+/*********************************************************************************************************************************
+* Initializer & uninitializer *
+*********************************************************************************************************************************/
+
+/**
+ * Initializes the medium I/O object.
+ *
+ * @param pMedium Pointer to the medium to access.
+ * @param pVirtualBox Pointer to the VirtualBox object the medium is part of.
+ * @param fWritable Read-write (true) or readonly (false) access.
+ * @param rStrKeyId The key ID for an encrypted medium. Empty if not
+ * encrypted.
+ * @param rStrPassword The password for an encrypted medium. Empty if not
+ * encrypted.
+ *
+ */
+HRESULT MediumIO::initForMedium(Medium *pMedium, VirtualBox *pVirtualBox, bool fWritable,
+ com::Utf8Str const &rStrKeyId, com::Utf8Str const &rStrPassword)
+{
+ LogFlowThisFunc(("pMedium=%p pVirtualBox=%p fWritable=%RTbool\n", pMedium, pVirtualBox, fWritable));
+ CheckComArgExpr(rStrPassword, rStrPassword.isEmpty() == rStrKeyId.isEmpty()); /* checked by caller */
+
+ /*
+ * Enclose the state transition NotReady->InInit->Ready
+ */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /*
+ * Allocate data instance.
+ */
+ HRESULT hrc = S_OK;
+ m = new(std::nothrow) Data(pMedium, pVirtualBox, fWritable);
+ if (m)
+ {
+ /*
+ * Add the password to the keystore if specified.
+ */
+ if (rStrKeyId.isNotEmpty())
+ {
+ int vrc = m->PasswordStore.addSecretKey(rStrKeyId, (const uint8_t *)rStrPassword.c_str(),
+ rStrPassword.length() + 1 /*including the Schwarzenegger character*/);
+ if (vrc == VERR_NO_MEMORY)
+ hrc = setError(E_OUTOFMEMORY, tr("Failed to allocate enough secure memory for the key/password"));
+ else if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Unknown error happened while adding a password (%Rrc)"), vrc);
+ }
+
+ /*
+ * Try open the medium and then get a VFS file handle for it.
+ */
+ if (SUCCEEDED(hrc))
+ {
+ hrc = pMedium->i_openForIO(fWritable, &m->PasswordStore, &m->pHdd, &m->LockList, &m->CryptoSettings);
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = VDCreateVfsFileFromDisk(m->pHdd, 0 /*fFlags*/, &m->hVfsFile);
+ if (RT_FAILURE(vrc))
+ {
+ hrc = setErrorBoth(E_FAIL, vrc, tr("VDCreateVfsFileFromDisk failed: %Rrc"), vrc);
+ m->hVfsFile = NIL_RTVFSFILE;
+ }
+ }
+ }
+ }
+ else
+ hrc = E_OUTOFMEMORY;
+
+ /*
+ * Done. Just update object readiness state.
+ */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ else
+ {
+ if (m)
+ i_close(); /* Free password and whatever i_openHddForIO() may accidentally leave around on failure. */
+ autoInitSpan.setFailed(hrc);
+ }
+
+ LogFlowThisFunc(("returns %Rhrc\n", hrc));
+ return hrc;
+}
+
+/**
+ * Uninitializes the instance (called from FinalRelease()).
+ */
+void MediumIO::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (!autoUninitSpan.uninitDone())
+ {
+ if (m)
+ {
+ i_close();
+
+ delete m;
+ m = NULL;
+ }
+ }
+
+ LogFlowThisFuncLeave();
+}
+
+
+/*********************************************************************************************************************************
+* IMediumIO attributes *
+*********************************************************************************************************************************/
+
+HRESULT MediumIO::getMedium(ComPtr<IMedium> &a_rPtrMedium)
+{
+ a_rPtrMedium = m->ptrMedium;
+ return S_OK;
+}
+
+HRESULT MediumIO::getWritable(BOOL *a_fWritable)
+{
+ *a_fWritable = m->fWritable;
+ return S_OK;
+}
+
+HRESULT MediumIO::getExplorer(ComPtr<IVFSExplorer> &a_rPtrExplorer)
+{
+ RT_NOREF_PV(a_rPtrExplorer);
+ return E_NOTIMPL;
+}
+
+
+/*********************************************************************************************************************************
+* IMediumIO methods *
+*********************************************************************************************************************************/
+
+HRESULT MediumIO::read(LONG64 a_off, ULONG a_cbRead, std::vector<BYTE> &a_rData)
+{
+ /*
+ * Validate input.
+ */
+ if (a_cbRead > _256K)
+ return setError(E_INVALIDARG, tr("Max read size is 256KB, given: %u"), a_cbRead);
+ if (a_cbRead == 0)
+ return setError(E_INVALIDARG, tr("Zero byte read is not supported."));
+
+ /*
+ * Allocate return buffer.
+ */
+ try
+ {
+ a_rData.resize(a_cbRead);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Do the reading. To play safe we exclusivly lock the object while doing this.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ size_t cbActual = 0;
+ int vrc = RTVfsFileReadAt(m->hVfsFile, a_off, &a_rData.front(), a_cbRead, &cbActual);
+ alock.release();
+
+ /*
+ * Manage the result.
+ */
+ HRESULT hrc;
+ if (RT_SUCCESS(vrc))
+ {
+ if (cbActual != a_cbRead)
+ {
+ Assert(cbActual < a_cbRead);
+ a_rData.resize(cbActual);
+ }
+ hrc = S_OK;
+ }
+ else
+ {
+ a_rData.resize(0);
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error reading %u bytes at %RU64: %Rrc", "", a_cbRead),
+ a_cbRead, a_off, vrc);
+ }
+
+ return hrc;
+}
+
+HRESULT MediumIO::write(LONG64 a_off, const std::vector<BYTE> &a_rData, ULONG *a_pcbWritten)
+{
+ /*
+ * Validate input.
+ */
+ size_t cbToWrite = a_rData.size();
+ if (cbToWrite == 0)
+ return setError(E_INVALIDARG, tr("Zero byte write is not supported."));
+ if (!m->fWritable)
+ return setError(E_ACCESSDENIED, tr("Medium not opened for writing."));
+ CheckComArgPointerValid(a_pcbWritten);
+ *a_pcbWritten = 0;
+
+ /*
+ * Do the writing. To play safe we exclusivly lock the object while doing this.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ size_t cbActual = 0;
+ int vrc = RTVfsFileWriteAt(m->hVfsFile, a_off, &a_rData.front(), cbToWrite, &cbActual);
+ alock.release();
+
+ /*
+ * Manage the result.
+ */
+ HRESULT hrc;
+ if (RT_SUCCESS(vrc))
+ {
+ *a_pcbWritten = (ULONG)cbActual;
+ hrc = S_OK;
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error writing %zu bytes at %RU64: %Rrc", "", cbToWrite),
+ cbToWrite, a_off, vrc);
+
+ return hrc;
+}
+
+HRESULT MediumIO::formatFAT(BOOL a_fQuick)
+{
+ /*
+ * Validate input.
+ */
+ if (!m->fWritable)
+ return setError(E_ACCESSDENIED, tr("Medium not opened for writing."));
+
+ /*
+ * Format the medium as FAT and let the format API figure the parameters.
+ * We exclusivly lock the object while doing this as concurrent medium access makes no sense.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ RTERRINFOSTATIC ErrInfo;
+ int vrc = RTFsFatVolFormat(m->hVfsFile, 0, 0, a_fQuick ? RTFSFATVOL_FMT_F_QUICK : RTFSFATVOL_FMT_F_FULL,
+ (uint16_t)m->cbSector, 0, RTFSFATTYPE_INVALID, 0, 0, 0, 0, 0, RTErrInfoInitStatic(&ErrInfo));
+ alock.release();
+
+ /*
+ * Manage the result.
+ */
+ HRESULT hrc;
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error formatting (%Rrc): %s"), vrc, ErrInfo.Core.pszMsg);
+ else
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error formatting: %Rrc"), vrc);
+
+ return hrc;
+}
+
+HRESULT MediumIO::initializePartitionTable(PartitionTableType_T a_enmFormat, BOOL a_fWholeDiskInOneEntry)
+{
+ /*
+ * Validate input.
+ */
+ const char *pszFormat;
+ if (a_enmFormat == PartitionTableType_MBR)
+ pszFormat = "MBR"; /* RTDVMFORMATTYPE_MBR */
+ else if (a_enmFormat == PartitionTableType_GPT)
+ pszFormat = "GPT"; /* RTDVMFORMATTYPE_GPT */
+ else
+ return setError(E_INVALIDARG, tr("Invalid partition format type: %d"), a_enmFormat);
+ if (!m->fWritable)
+ return setError(E_ACCESSDENIED, tr("Medium not opened for writing."));
+ if (a_fWholeDiskInOneEntry)
+ return setError(E_NOTIMPL, tr("whole-disk-in-one-entry is not implemented yet, sorry."));
+
+ /*
+ * Do the partitioning.
+ * We exclusivly lock the object while doing this as concurrent medium access makes little sense.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ RTDVM hVolMgr;
+ int vrc = RTDvmCreate(&hVolMgr, m->hVfsFile, m->cbSector, 0 /*fFlags*/);
+ HRESULT hrc;
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTDvmMapInitialize(hVolMgr, pszFormat); /** @todo Why doesn't RTDvmMapInitialize take RTDVMFORMATTYPE? */
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Create a partition for the whole disk?
+ */
+ hrc = S_OK; /** @todo a_fWholeDiskInOneEntry requies RTDvm to get a function for creating partitions. */
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("RTDvmMapInitialize failed: %Rrc"), vrc);
+ RTDvmRelease(hVolMgr);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("RTDvmCreate failed: %Rrc"), vrc);
+
+ return hrc;
+}
+
+HRESULT MediumIO::convertToStream(const com::Utf8Str &aFormat,
+ const std::vector<MediumVariant_T> &aVariant,
+ ULONG aBufferSize,
+ ComPtr<IDataStream> &aStream,
+ ComPtr<IProgress> &aProgress)
+{
+ HRESULT rc = S_OK;
+ ComObjPtr<Progress> pProgress;
+ ComObjPtr<DataStream> pDataStream;
+ MediumIO::StreamTask *pTask = NULL;
+
+ try
+ {
+ pDataStream.createObject();
+ rc = pDataStream->init(aBufferSize);
+ if (FAILED(rc))
+ throw rc;
+
+ pProgress.createObject();
+ rc = pProgress->init(m->ptrVirtualBox,
+ static_cast<IMediumIO*>(this),
+ BstrFmt(tr("Converting medium '%s' to data stream"), m->ptrMedium->i_getLocationFull().c_str()),
+ TRUE /* aCancelable */);
+ if (FAILED(rc))
+ throw rc;
+
+ ULONG mediumVariantFlags = 0;
+
+ if (aVariant.size())
+ {
+ for (size_t i = 0; i < aVariant.size(); i++)
+ mediumVariantFlags |= (ULONG)aVariant[i];
+ }
+
+ /* setup task object to carry out the operation asynchronously */
+ pTask = new MediumIO::StreamTask(this, pDataStream, pProgress,
+ aFormat.c_str(), (MediumVariant_T)mediumVariantFlags);
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(rc))
+ {
+ pDataStream.queryInterfaceTo(aStream.asOutParam());
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+ }
+ }
+ else if (pTask != NULL)
+ delete pTask;
+
+ return rc;
+}
+
+HRESULT MediumIO::close()
+{
+ /*
+ * We need a write lock here to exclude all other access.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ i_close();
+ return S_OK;
+}
+
+
+
+/*********************************************************************************************************************************
+* IMediumIO internal methods *
+*********************************************************************************************************************************/
+
+/**
+ * This is used by both uninit and close().
+ *
+ * Expects exclusive access (write lock or autouninit) to the object.
+ */
+void MediumIO::i_close()
+{
+ if (m->hVfsFile != NIL_RTVFSFILE)
+ {
+ uint32_t cRefs = RTVfsFileRelease(m->hVfsFile);
+ Assert(cRefs == 0);
+ NOREF(cRefs);
+
+ m->hVfsFile = NIL_RTVFSFILE;
+ }
+
+ if (m->pHdd)
+ {
+ VDDestroy(m->pHdd);
+ m->pHdd = NULL;
+ }
+
+ m->LockList.Clear();
+ m->ptrMedium.setNull();
+ m->PasswordStore.deleteAllSecretKeys(false /* fSuspend */, true /* fForce */);
+}
+
diff --git a/src/VBox/Main/src-server/MediumImpl.cpp b/src/VBox/Main/src-server/MediumImpl.cpp
new file mode 100644
index 00000000..d9d02971
--- /dev/null
+++ b/src/VBox/Main/src-server/MediumImpl.cpp
@@ -0,0 +1,11233 @@
+/* $Id: MediumImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_MEDIUM
+#include "MediumImpl.h"
+#include "MediumIOImpl.h"
+#include "TokenImpl.h"
+#include "ProgressImpl.h"
+#include "SystemPropertiesImpl.h"
+#include "VirtualBoxImpl.h"
+#include "ExtPackManagerImpl.h"
+
+#include "AutoCaller.h"
+#include "Global.h"
+#include "LoggingNew.h"
+#include "ThreadTask.h"
+#include "VBox/com/MultiResult.h"
+#include "VBox/com/ErrorInfo.h"
+
+#include <VBox/err.h>
+#include <VBox/settings.h>
+
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/file.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/memsafer.h>
+#include <iprt/base64.h>
+#include <iprt/vfs.h>
+#include <iprt/fsvfs.h>
+
+#include <VBox/vd.h>
+
+#include <algorithm>
+#include <list>
+#include <set>
+#include <map>
+
+
+typedef std::list<Guid> GuidList;
+
+
+#ifdef VBOX_WITH_EXTPACK
+static const char g_szVDPlugin[] = "VDPluginCrypt";
+#endif
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Medium data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+#if __cplusplus < 201700 && RT_GNUC_PREREQ(11,0) /* gcc/libstdc++ 12.1.1 errors out here because unary_function is deprecated */
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+struct SnapshotRef
+{
+ /** Equality predicate for stdc++. */
+ struct EqualsTo
+#if __cplusplus < 201700 /* deprecated in C++11, removed in C++17. */
+ : public std::unary_function <SnapshotRef, bool>
+#endif
+ {
+ explicit EqualsTo(const Guid &aSnapshotId) : snapshotId(aSnapshotId) {}
+
+#if __cplusplus < 201700 /* deprecated in C++11, removed in C++17. */
+ bool operator()(const argument_type &aThat) const
+#else
+ bool operator()(const SnapshotRef &aThat) const
+#endif
+ {
+ return aThat.snapshotId == snapshotId;
+ }
+
+ const Guid snapshotId;
+ };
+
+ SnapshotRef(const Guid &aSnapshotId,
+ const int &aRefCnt = 1)
+ : snapshotId(aSnapshotId),
+ iRefCnt(aRefCnt) {}
+
+ Guid snapshotId;
+ /*
+ * The number of attachments of the medium in the same snapshot.
+ * Used for MediumType_Readonly. It is always equal to 1 for other types.
+ * Usual int is used because any changes in the BackRef are guarded by
+ * AutoWriteLock.
+ */
+ int iRefCnt;
+};
+
+/** Describes how a machine refers to this medium. */
+struct BackRef
+{
+ /** Equality predicate for stdc++. */
+ struct EqualsTo
+#if __cplusplus < 201700 /* deprecated in C++11, removed in C++17. */
+ : public std::unary_function <BackRef, bool>
+#endif
+ {
+ explicit EqualsTo(const Guid &aMachineId) : machineId(aMachineId) {}
+
+#if __cplusplus < 201700 /* deprecated in C++11, removed in C++17. */
+ bool operator()(const argument_type &aThat) const
+#else
+ bool operator()(const BackRef &aThat) const
+#endif
+ {
+ return aThat.machineId == machineId;
+ }
+
+ const Guid machineId;
+ };
+
+ BackRef(const Guid &aMachineId,
+ const Guid &aSnapshotId = Guid::Empty)
+ : machineId(aMachineId),
+ iRefCnt(1),
+ fInCurState(aSnapshotId.isZero())
+ {
+ if (aSnapshotId.isValid() && !aSnapshotId.isZero())
+ llSnapshotIds.push_back(SnapshotRef(aSnapshotId));
+ }
+
+ Guid machineId;
+ /*
+ * The number of attachments of the medium in the same machine.
+ * Used for MediumType_Readonly. It is always equal to 1 for other types.
+ * Usual int is used because any changes in the BackRef are guarded by
+ * AutoWriteLock.
+ */
+ int iRefCnt;
+ bool fInCurState : 1;
+ std::list<SnapshotRef> llSnapshotIds;
+};
+
+typedef std::list<BackRef> BackRefList;
+
+#if __cplusplus < 201700 && RT_GNUC_PREREQ(11,0)
+# pragma GCC diagnostic pop
+#endif
+
+
+struct Medium::Data
+{
+ Data()
+ : pVirtualBox(NULL),
+ state(MediumState_NotCreated),
+ variant(MediumVariant_Standard),
+ size(0),
+ readers(0),
+ preLockState(MediumState_NotCreated),
+ queryInfoSem(LOCKCLASS_MEDIUMQUERY),
+ queryInfoRunning(false),
+ type(MediumType_Normal),
+ devType(DeviceType_HardDisk),
+ logicalSize(0),
+ hddOpenMode(OpenReadWrite),
+ autoReset(false),
+ hostDrive(false),
+ implicit(false),
+ fClosing(false),
+ uOpenFlagsDef(VD_OPEN_FLAGS_IGNORE_FLUSH),
+ numCreateDiffTasks(0),
+ vdDiskIfaces(NULL),
+ vdImageIfaces(NULL),
+ fMoveThisMedium(false)
+ { }
+
+ /** weak VirtualBox parent */
+ VirtualBox * const pVirtualBox;
+
+ // pParent and llChildren are protected by VirtualBox::i_getMediaTreeLockHandle()
+ ComObjPtr<Medium> pParent;
+ MediaList llChildren; // to add a child, just call push_back; to remove
+ // a child, call child->deparent() which does a lookup
+
+ GuidList llRegistryIDs; // media registries in which this medium is listed
+
+ const Guid id;
+ Utf8Str strDescription;
+ MediumState_T state;
+ MediumVariant_T variant;
+ Utf8Str strLocationFull;
+ uint64_t size;
+ Utf8Str strLastAccessError;
+
+ BackRefList backRefs;
+
+ size_t readers;
+ MediumState_T preLockState;
+
+ /** Special synchronization for operations which must wait for
+ * Medium::i_queryInfo in another thread to complete. Using a SemRW is
+ * not quite ideal, but at least it is subject to the lock validator,
+ * unlike the SemEventMulti which we had here for many years. Catching
+ * possible deadlocks is more important than a tiny bit of efficiency. */
+ RWLockHandle queryInfoSem;
+ bool queryInfoRunning : 1;
+
+ const Utf8Str strFormat;
+ ComObjPtr<MediumFormat> formatObj;
+
+ MediumType_T type;
+ DeviceType_T devType;
+ uint64_t logicalSize;
+
+ HDDOpenMode hddOpenMode;
+
+ bool autoReset : 1;
+
+ /** New UUID to be set on the next Medium::i_queryInfo call. */
+ const Guid uuidImage;
+ /** New parent UUID to be set on the next Medium::i_queryInfo call. */
+ const Guid uuidParentImage;
+
+/** @todo r=bird: the boolean bitfields are pointless if they're not grouped! */
+ bool hostDrive : 1;
+
+ settings::StringsMap mapProperties;
+
+ bool implicit : 1;
+ /** Flag whether the medium is in the process of being closed. */
+ bool fClosing: 1;
+
+ /** Default flags passed to VDOpen(). */
+ unsigned uOpenFlagsDef;
+
+ uint32_t numCreateDiffTasks;
+
+ Utf8Str vdError; /*< Error remembered by the VD error callback. */
+
+ VDINTERFACEERROR vdIfError;
+
+ VDINTERFACECONFIG vdIfConfig;
+
+ /** The handle to the default VD TCP/IP interface. */
+ VDIFINST hTcpNetInst;
+
+ PVDINTERFACE vdDiskIfaces;
+ PVDINTERFACE vdImageIfaces;
+
+ /** Flag if the medium is going to move to a new
+ * location. */
+ bool fMoveThisMedium;
+ /** new location path */
+ Utf8Str strNewLocationFull;
+};
+
+typedef struct VDSOCKETINT
+{
+ /** Socket handle. */
+ RTSOCKET hSocket;
+} VDSOCKETINT, *PVDSOCKETINT;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Globals
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Medium::Task class for asynchronous operations.
+ *
+ * @note Instances of this class must be created using new() because the
+ * task thread function will delete them when the task is complete.
+ *
+ * @note The constructor of this class adds a caller on the managed Medium
+ * object which is automatically released upon destruction.
+ */
+class Medium::Task : public ThreadTask
+{
+public:
+ Task(Medium *aMedium, Progress *aProgress, bool fNotifyAboutChanges = true)
+ : ThreadTask("Medium::Task"),
+ mVDOperationIfaces(NULL),
+ mMedium(aMedium),
+ mMediumCaller(aMedium),
+ mProgress(aProgress),
+ mVirtualBoxCaller(NULL),
+ mNotifyAboutChanges(fNotifyAboutChanges)
+ {
+ AssertReturnVoidStmt(aMedium, mRC = E_FAIL);
+ mRC = mMediumCaller.rc();
+ if (FAILED(mRC))
+ return;
+
+ /* Get strong VirtualBox reference, see below. */
+ VirtualBox *pVirtualBox = aMedium->m->pVirtualBox;
+ mVirtualBox = pVirtualBox;
+ mVirtualBoxCaller.attach(pVirtualBox);
+ mRC = mVirtualBoxCaller.rc();
+ if (FAILED(mRC))
+ return;
+
+ /* Set up a per-operation progress interface, can be used freely (for
+ * binary operations you can use it either on the source or target). */
+ if (mProgress)
+ {
+ mVDIfProgress.pfnProgress = aProgress->i_vdProgressCallback;
+ int vrc = VDInterfaceAdd(&mVDIfProgress.Core,
+ "Medium::Task::vdInterfaceProgress",
+ VDINTERFACETYPE_PROGRESS,
+ mProgress,
+ sizeof(mVDIfProgress),
+ &mVDOperationIfaces);
+ AssertRC(vrc);
+ if (RT_FAILURE(vrc))
+ mRC = E_FAIL;
+ }
+ }
+
+ // Make all destructors virtual. Just in case.
+ virtual ~Task()
+ {
+ /* send the notification of completion.*/
+ if ( isAsync()
+ && !mProgress.isNull())
+ mProgress->i_notifyComplete(mRC);
+ }
+
+ HRESULT rc() const { return mRC; }
+ bool isOk() const { return SUCCEEDED(rc()); }
+ bool NotifyAboutChanges() const { return mNotifyAboutChanges; }
+
+ const ComPtr<Progress>& GetProgressObject() const {return mProgress;}
+
+ /**
+ * Runs Medium::Task::executeTask() on the current thread
+ * instead of creating a new one.
+ */
+ HRESULT runNow()
+ {
+ LogFlowFuncEnter();
+
+ mRC = executeTask();
+
+ LogFlowFunc(("rc=%Rhrc\n", mRC));
+ LogFlowFuncLeave();
+ return mRC;
+ }
+
+ /**
+ * Implementation code for the "create base" task.
+ * Used as function for execution from a standalone thread.
+ */
+ void handler()
+ {
+ LogFlowFuncEnter();
+ try
+ {
+ mRC = executeTask(); /* (destructor picks up mRC, see above) */
+ LogFlowFunc(("rc=%Rhrc\n", mRC));
+ }
+ catch (...)
+ {
+ LogRel(("Some exception in the function Medium::Task:handler()\n"));
+ }
+
+ LogFlowFuncLeave();
+ }
+
+ PVDINTERFACE mVDOperationIfaces;
+
+ const ComObjPtr<Medium> mMedium;
+ AutoCaller mMediumCaller;
+
+protected:
+ HRESULT mRC;
+
+private:
+ virtual HRESULT executeTask() = 0;
+
+ const ComObjPtr<Progress> mProgress;
+
+ VDINTERFACEPROGRESS mVDIfProgress;
+
+ /* Must have a strong VirtualBox reference during a task otherwise the
+ * reference count might drop to 0 while a task is still running. This
+ * would result in weird behavior, including deadlocks due to uninit and
+ * locking order issues. The deadlock often is not detectable because the
+ * uninit uses event semaphores which sabotages deadlock detection. */
+ ComObjPtr<VirtualBox> mVirtualBox;
+ AutoCaller mVirtualBoxCaller;
+ bool mNotifyAboutChanges;
+};
+
+class Medium::CreateBaseTask : public Medium::Task
+{
+public:
+ CreateBaseTask(Medium *aMedium,
+ Progress *aProgress,
+ uint64_t aSize,
+ MediumVariant_T aVariant,
+ bool fNotifyAboutChanges = true)
+ : Medium::Task(aMedium, aProgress, fNotifyAboutChanges),
+ mSize(aSize),
+ mVariant(aVariant)
+ {
+ m_strTaskName = "createBase";
+ }
+
+ uint64_t mSize;
+ MediumVariant_T mVariant;
+
+private:
+ HRESULT executeTask()
+ {
+ return mMedium->i_taskCreateBaseHandler(*this);
+ }
+};
+
+class Medium::CreateDiffTask : public Medium::Task
+{
+public:
+ CreateDiffTask(Medium *aMedium,
+ Progress *aProgress,
+ Medium *aTarget,
+ MediumVariant_T aVariant,
+ MediumLockList *aMediumLockList,
+ bool fKeepMediumLockList = false,
+ bool fNotifyAboutChanges = true)
+ : Medium::Task(aMedium, aProgress, fNotifyAboutChanges),
+ mpMediumLockList(aMediumLockList),
+ mTarget(aTarget),
+ mVariant(aVariant),
+ mTargetCaller(aTarget),
+ mfKeepMediumLockList(fKeepMediumLockList)
+ {
+ AssertReturnVoidStmt(aTarget != NULL, mRC = E_FAIL);
+ mRC = mTargetCaller.rc();
+ if (FAILED(mRC))
+ return;
+ m_strTaskName = "createDiff";
+ }
+
+ ~CreateDiffTask()
+ {
+ if (!mfKeepMediumLockList && mpMediumLockList)
+ delete mpMediumLockList;
+ }
+
+ MediumLockList *mpMediumLockList;
+
+ const ComObjPtr<Medium> mTarget;
+ MediumVariant_T mVariant;
+
+private:
+ HRESULT executeTask()
+ {
+ return mMedium->i_taskCreateDiffHandler(*this);
+ }
+
+ AutoCaller mTargetCaller;
+ bool mfKeepMediumLockList;
+};
+
+class Medium::CloneTask : public Medium::Task
+{
+public:
+ CloneTask(Medium *aMedium,
+ Progress *aProgress,
+ Medium *aTarget,
+ MediumVariant_T aVariant,
+ Medium *aParent,
+ uint32_t idxSrcImageSame,
+ uint32_t idxDstImageSame,
+ MediumLockList *aSourceMediumLockList,
+ MediumLockList *aTargetMediumLockList,
+ bool fKeepSourceMediumLockList = false,
+ bool fKeepTargetMediumLockList = false,
+ bool fNotifyAboutChanges = true,
+ uint64_t aTargetLogicalSize = 0)
+ : Medium::Task(aMedium, aProgress, fNotifyAboutChanges),
+ mTarget(aTarget),
+ mParent(aParent),
+ mTargetLogicalSize(aTargetLogicalSize),
+ mpSourceMediumLockList(aSourceMediumLockList),
+ mpTargetMediumLockList(aTargetMediumLockList),
+ mVariant(aVariant),
+ midxSrcImageSame(idxSrcImageSame),
+ midxDstImageSame(idxDstImageSame),
+ mTargetCaller(aTarget),
+ mParentCaller(aParent),
+ mfKeepSourceMediumLockList(fKeepSourceMediumLockList),
+ mfKeepTargetMediumLockList(fKeepTargetMediumLockList)
+ {
+ AssertReturnVoidStmt(aTarget != NULL, mRC = E_FAIL);
+ mRC = mTargetCaller.rc();
+ if (FAILED(mRC))
+ return;
+ /* aParent may be NULL */
+ mRC = mParentCaller.rc();
+ if (FAILED(mRC))
+ return;
+ AssertReturnVoidStmt(aSourceMediumLockList != NULL, mRC = E_FAIL);
+ AssertReturnVoidStmt(aTargetMediumLockList != NULL, mRC = E_FAIL);
+ m_strTaskName = "createClone";
+ }
+
+ ~CloneTask()
+ {
+ if (!mfKeepSourceMediumLockList && mpSourceMediumLockList)
+ delete mpSourceMediumLockList;
+ if (!mfKeepTargetMediumLockList && mpTargetMediumLockList)
+ delete mpTargetMediumLockList;
+ }
+
+ const ComObjPtr<Medium> mTarget;
+ const ComObjPtr<Medium> mParent;
+ uint64_t mTargetLogicalSize;
+ MediumLockList *mpSourceMediumLockList;
+ MediumLockList *mpTargetMediumLockList;
+ MediumVariant_T mVariant;
+ uint32_t midxSrcImageSame;
+ uint32_t midxDstImageSame;
+
+private:
+ HRESULT executeTask()
+ {
+ return mMedium->i_taskCloneHandler(*this);
+ }
+
+ AutoCaller mTargetCaller;
+ AutoCaller mParentCaller;
+ bool mfKeepSourceMediumLockList;
+ bool mfKeepTargetMediumLockList;
+};
+
+class Medium::MoveTask : public Medium::Task
+{
+public:
+ MoveTask(Medium *aMedium,
+ Progress *aProgress,
+ MediumVariant_T aVariant,
+ MediumLockList *aMediumLockList,
+ bool fKeepMediumLockList = false,
+ bool fNotifyAboutChanges = true)
+ : Medium::Task(aMedium, aProgress, fNotifyAboutChanges),
+ mpMediumLockList(aMediumLockList),
+ mVariant(aVariant),
+ mfKeepMediumLockList(fKeepMediumLockList)
+ {
+ AssertReturnVoidStmt(aMediumLockList != NULL, mRC = E_FAIL);
+ m_strTaskName = "createMove";
+ }
+
+ ~MoveTask()
+ {
+ if (!mfKeepMediumLockList && mpMediumLockList)
+ delete mpMediumLockList;
+ }
+
+ MediumLockList *mpMediumLockList;
+ MediumVariant_T mVariant;
+
+private:
+ HRESULT executeTask()
+ {
+ return mMedium->i_taskMoveHandler(*this);
+ }
+
+ bool mfKeepMediumLockList;
+};
+
+class Medium::CompactTask : public Medium::Task
+{
+public:
+ CompactTask(Medium *aMedium,
+ Progress *aProgress,
+ MediumLockList *aMediumLockList,
+ bool fKeepMediumLockList = false,
+ bool fNotifyAboutChanges = true)
+ : Medium::Task(aMedium, aProgress, fNotifyAboutChanges),
+ mpMediumLockList(aMediumLockList),
+ mfKeepMediumLockList(fKeepMediumLockList)
+ {
+ AssertReturnVoidStmt(aMediumLockList != NULL, mRC = E_FAIL);
+ m_strTaskName = "createCompact";
+ }
+
+ ~CompactTask()
+ {
+ if (!mfKeepMediumLockList && mpMediumLockList)
+ delete mpMediumLockList;
+ }
+
+ MediumLockList *mpMediumLockList;
+
+private:
+ HRESULT executeTask()
+ {
+ return mMedium->i_taskCompactHandler(*this);
+ }
+
+ bool mfKeepMediumLockList;
+};
+
+class Medium::ResizeTask : public Medium::Task
+{
+public:
+ ResizeTask(Medium *aMedium,
+ uint64_t aSize,
+ Progress *aProgress,
+ MediumLockList *aMediumLockList,
+ bool fKeepMediumLockList = false,
+ bool fNotifyAboutChanges = true)
+ : Medium::Task(aMedium, aProgress, fNotifyAboutChanges),
+ mSize(aSize),
+ mpMediumLockList(aMediumLockList),
+ mfKeepMediumLockList(fKeepMediumLockList)
+ {
+ AssertReturnVoidStmt(aMediumLockList != NULL, mRC = E_FAIL);
+ m_strTaskName = "createResize";
+ }
+
+ ~ResizeTask()
+ {
+ if (!mfKeepMediumLockList && mpMediumLockList)
+ delete mpMediumLockList;
+ }
+
+ uint64_t mSize;
+ MediumLockList *mpMediumLockList;
+
+private:
+ HRESULT executeTask()
+ {
+ return mMedium->i_taskResizeHandler(*this);
+ }
+
+ bool mfKeepMediumLockList;
+};
+
+class Medium::ResetTask : public Medium::Task
+{
+public:
+ ResetTask(Medium *aMedium,
+ Progress *aProgress,
+ MediumLockList *aMediumLockList,
+ bool fKeepMediumLockList = false,
+ bool fNotifyAboutChanges = true)
+ : Medium::Task(aMedium, aProgress, fNotifyAboutChanges),
+ mpMediumLockList(aMediumLockList),
+ mfKeepMediumLockList(fKeepMediumLockList)
+ {
+ m_strTaskName = "createReset";
+ }
+
+ ~ResetTask()
+ {
+ if (!mfKeepMediumLockList && mpMediumLockList)
+ delete mpMediumLockList;
+ }
+
+ MediumLockList *mpMediumLockList;
+
+private:
+ HRESULT executeTask()
+ {
+ return mMedium->i_taskResetHandler(*this);
+ }
+
+ bool mfKeepMediumLockList;
+};
+
+class Medium::DeleteTask : public Medium::Task
+{
+public:
+ DeleteTask(Medium *aMedium,
+ Progress *aProgress,
+ MediumLockList *aMediumLockList,
+ bool fKeepMediumLockList = false,
+ bool fNotifyAboutChanges = true)
+ : Medium::Task(aMedium, aProgress, fNotifyAboutChanges),
+ mpMediumLockList(aMediumLockList),
+ mfKeepMediumLockList(fKeepMediumLockList)
+ {
+ m_strTaskName = "createDelete";
+ }
+
+ ~DeleteTask()
+ {
+ if (!mfKeepMediumLockList && mpMediumLockList)
+ delete mpMediumLockList;
+ }
+
+ MediumLockList *mpMediumLockList;
+
+private:
+ HRESULT executeTask()
+ {
+ return mMedium->i_taskDeleteHandler(*this);
+ }
+
+ bool mfKeepMediumLockList;
+};
+
+class Medium::MergeTask : public Medium::Task
+{
+public:
+ MergeTask(Medium *aMedium,
+ Medium *aTarget,
+ bool fMergeForward,
+ Medium *aParentForTarget,
+ MediumLockList *aChildrenToReparent,
+ Progress *aProgress,
+ MediumLockList *aMediumLockList,
+ bool fKeepMediumLockList = false,
+ bool fNotifyAboutChanges = true)
+ : Medium::Task(aMedium, aProgress, fNotifyAboutChanges),
+ mTarget(aTarget),
+ mfMergeForward(fMergeForward),
+ mParentForTarget(aParentForTarget),
+ mpChildrenToReparent(aChildrenToReparent),
+ mpMediumLockList(aMediumLockList),
+ mTargetCaller(aTarget),
+ mParentForTargetCaller(aParentForTarget),
+ mfKeepMediumLockList(fKeepMediumLockList)
+ {
+ AssertReturnVoidStmt(aMediumLockList != NULL, mRC = E_FAIL);
+ m_strTaskName = "createMerge";
+ }
+
+ ~MergeTask()
+ {
+ if (!mfKeepMediumLockList && mpMediumLockList)
+ delete mpMediumLockList;
+ if (mpChildrenToReparent)
+ delete mpChildrenToReparent;
+ }
+
+ const ComObjPtr<Medium> mTarget;
+ bool mfMergeForward;
+ /* When mpChildrenToReparent is null then mParentForTarget is non-null and
+ * vice versa. In other words: they are used in different cases. */
+ const ComObjPtr<Medium> mParentForTarget;
+ MediumLockList *mpChildrenToReparent;
+ MediumLockList *mpMediumLockList;
+
+private:
+ HRESULT executeTask()
+ {
+ return mMedium->i_taskMergeHandler(*this);
+ }
+
+ AutoCaller mTargetCaller;
+ AutoCaller mParentForTargetCaller;
+ bool mfKeepMediumLockList;
+};
+
+class Medium::ImportTask : public Medium::Task
+{
+public:
+ ImportTask(Medium *aMedium,
+ Progress *aProgress,
+ const char *aFilename,
+ MediumFormat *aFormat,
+ MediumVariant_T aVariant,
+ RTVFSIOSTREAM aVfsIosSrc,
+ Medium *aParent,
+ MediumLockList *aTargetMediumLockList,
+ bool fKeepTargetMediumLockList = false,
+ bool fNotifyAboutChanges = true)
+ : Medium::Task(aMedium, aProgress, fNotifyAboutChanges),
+ mFilename(aFilename),
+ mFormat(aFormat),
+ mVariant(aVariant),
+ mParent(aParent),
+ mpTargetMediumLockList(aTargetMediumLockList),
+ mpVfsIoIf(NULL),
+ mParentCaller(aParent),
+ mfKeepTargetMediumLockList(fKeepTargetMediumLockList)
+ {
+ AssertReturnVoidStmt(aTargetMediumLockList != NULL, mRC = E_FAIL);
+ /* aParent may be NULL */
+ mRC = mParentCaller.rc();
+ if (FAILED(mRC))
+ return;
+
+ mVDImageIfaces = aMedium->m->vdImageIfaces;
+
+ int vrc = VDIfCreateFromVfsStream(aVfsIosSrc, RTFILE_O_READ, &mpVfsIoIf);
+ AssertRCReturnVoidStmt(vrc, mRC = E_FAIL);
+
+ vrc = VDInterfaceAdd(&mpVfsIoIf->Core, "Medium::ImportTaskVfsIos",
+ VDINTERFACETYPE_IO, mpVfsIoIf,
+ sizeof(VDINTERFACEIO), &mVDImageIfaces);
+ AssertRCReturnVoidStmt(vrc, mRC = E_FAIL);
+ m_strTaskName = "createImport";
+ }
+
+ ~ImportTask()
+ {
+ if (!mfKeepTargetMediumLockList && mpTargetMediumLockList)
+ delete mpTargetMediumLockList;
+ if (mpVfsIoIf)
+ {
+ VDIfDestroyFromVfsStream(mpVfsIoIf);
+ mpVfsIoIf = NULL;
+ }
+ }
+
+ Utf8Str mFilename;
+ ComObjPtr<MediumFormat> mFormat;
+ MediumVariant_T mVariant;
+ const ComObjPtr<Medium> mParent;
+ MediumLockList *mpTargetMediumLockList;
+ PVDINTERFACE mVDImageIfaces;
+ PVDINTERFACEIO mpVfsIoIf; /**< Pointer to the VFS I/O stream to VD I/O interface wrapper. */
+
+private:
+ HRESULT executeTask()
+ {
+ return mMedium->i_taskImportHandler(*this);
+ }
+
+ AutoCaller mParentCaller;
+ bool mfKeepTargetMediumLockList;
+};
+
+class Medium::EncryptTask : public Medium::Task
+{
+public:
+ EncryptTask(Medium *aMedium,
+ const com::Utf8Str &strNewPassword,
+ const com::Utf8Str &strCurrentPassword,
+ const com::Utf8Str &strCipher,
+ const com::Utf8Str &strNewPasswordId,
+ Progress *aProgress,
+ MediumLockList *aMediumLockList)
+ : Medium::Task(aMedium, aProgress, false),
+ mstrNewPassword(strNewPassword),
+ mstrCurrentPassword(strCurrentPassword),
+ mstrCipher(strCipher),
+ mstrNewPasswordId(strNewPasswordId),
+ mpMediumLockList(aMediumLockList)
+ {
+ AssertReturnVoidStmt(aMediumLockList != NULL, mRC = E_FAIL);
+ /* aParent may be NULL */
+ mRC = mParentCaller.rc();
+ if (FAILED(mRC))
+ return;
+
+ mVDImageIfaces = aMedium->m->vdImageIfaces;
+ m_strTaskName = "createEncrypt";
+ }
+
+ ~EncryptTask()
+ {
+ if (mstrNewPassword.length())
+ RTMemWipeThoroughly(mstrNewPassword.mutableRaw(), mstrNewPassword.length(), 10 /* cPasses */);
+ if (mstrCurrentPassword.length())
+ RTMemWipeThoroughly(mstrCurrentPassword.mutableRaw(), mstrCurrentPassword.length(), 10 /* cPasses */);
+
+ /* Keep any errors which might be set when deleting the lock list. */
+ ErrorInfoKeeper eik;
+ delete mpMediumLockList;
+ }
+
+ Utf8Str mstrNewPassword;
+ Utf8Str mstrCurrentPassword;
+ Utf8Str mstrCipher;
+ Utf8Str mstrNewPasswordId;
+ MediumLockList *mpMediumLockList;
+ PVDINTERFACE mVDImageIfaces;
+
+private:
+ HRESULT executeTask()
+ {
+ return mMedium->i_taskEncryptHandler(*this);
+ }
+
+ AutoCaller mParentCaller;
+};
+
+
+
+/**
+ * Converts the Medium device type to the VD type.
+ */
+static const char *getVDTypeName(VDTYPE enmType)
+{
+ switch (enmType)
+ {
+ case VDTYPE_HDD: return "HDD";
+ case VDTYPE_OPTICAL_DISC: return "DVD";
+ case VDTYPE_FLOPPY: return "floppy";
+ case VDTYPE_INVALID: return "invalid";
+ default:
+ AssertFailedReturn("unknown");
+ }
+}
+
+/**
+ * Converts the Medium device type to the VD type.
+ */
+static const char *getDeviceTypeName(DeviceType_T enmType)
+{
+ switch (enmType)
+ {
+ case DeviceType_HardDisk: return "HDD";
+ case DeviceType_DVD: return "DVD";
+ case DeviceType_Floppy: return "floppy";
+ case DeviceType_Null: return "null";
+ case DeviceType_Network: return "network";
+ case DeviceType_USB: return "USB";
+ case DeviceType_SharedFolder: return "shared folder";
+ case DeviceType_Graphics3D: return "graphics 3d";
+ default:
+ AssertFailedReturn("unknown");
+ }
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Medium constructor / destructor
+//
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(Medium)
+
+HRESULT Medium::FinalConstruct()
+{
+ m = new Data;
+
+ /* Initialize the callbacks of the VD error interface */
+ m->vdIfError.pfnError = i_vdErrorCall;
+ m->vdIfError.pfnMessage = NULL;
+
+ /* Initialize the callbacks of the VD config interface */
+ m->vdIfConfig.pfnAreKeysValid = i_vdConfigAreKeysValid;
+ m->vdIfConfig.pfnQuerySize = i_vdConfigQuerySize;
+ m->vdIfConfig.pfnQuery = i_vdConfigQuery;
+ m->vdIfConfig.pfnUpdate = i_vdConfigUpdate;
+ m->vdIfConfig.pfnQueryBytes = NULL;
+
+ /* Initialize the per-disk interface chain (could be done more globally,
+ * but it's not wasting much time or space so it's not worth it). */
+ int vrc;
+ vrc = VDInterfaceAdd(&m->vdIfError.Core,
+ "Medium::vdInterfaceError",
+ VDINTERFACETYPE_ERROR, this,
+ sizeof(VDINTERFACEERROR), &m->vdDiskIfaces);
+ AssertRCReturn(vrc, E_FAIL);
+
+ /* Initialize the per-image interface chain */
+ vrc = VDInterfaceAdd(&m->vdIfConfig.Core,
+ "Medium::vdInterfaceConfig",
+ VDINTERFACETYPE_CONFIG, this,
+ sizeof(VDINTERFACECONFIG), &m->vdImageIfaces);
+ AssertRCReturn(vrc, E_FAIL);
+
+ /* Initialize the callbacks of the VD TCP interface (we always use the host
+ * IP stack for now) */
+ vrc = VDIfTcpNetInstDefaultCreate(&m->hTcpNetInst, &m->vdImageIfaces);
+ AssertRCReturn(vrc, E_FAIL);
+
+ return BaseFinalConstruct();
+}
+
+void Medium::FinalRelease()
+{
+ uninit();
+
+ VDIfTcpNetInstDefaultDestroy(m->hTcpNetInst);
+ delete m;
+
+ BaseFinalRelease();
+}
+
+/**
+ * Initializes an empty hard disk object without creating or opening an associated
+ * storage unit.
+ *
+ * This gets called by VirtualBox::CreateMedium() in which case uuidMachineRegistry
+ * is empty since starting with VirtualBox 4.0, we no longer add opened media to a
+ * registry automatically (this is deferred until the medium is attached to a machine).
+ *
+ * This also gets called when VirtualBox creates diff images; in this case uuidMachineRegistry
+ * is set to the registry of the parent image to make sure they all end up in the same
+ * file.
+ *
+ * For hard disks that don't have the MediumFormatCapabilities_CreateFixed or
+ * MediumFormatCapabilities_CreateDynamic capability (and therefore cannot be created or deleted
+ * with the means of VirtualBox) the associated storage unit is assumed to be
+ * ready for use so the state of the hard disk object will be set to Created.
+ *
+ * @param aVirtualBox VirtualBox object.
+ * @param aFormat
+ * @param aLocation Storage unit location.
+ * @param uuidMachineRegistry The registry to which this medium should be added
+ * (global registry UUID or machine UUID or empty if none).
+ * @param aDeviceType Device Type.
+ */
+HRESULT Medium::init(VirtualBox *aVirtualBox,
+ const Utf8Str &aFormat,
+ const Utf8Str &aLocation,
+ const Guid &uuidMachineRegistry,
+ const DeviceType_T aDeviceType)
+{
+ AssertReturn(aVirtualBox != NULL, E_FAIL);
+ AssertReturn(!aFormat.isEmpty(), E_FAIL);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT rc = S_OK;
+
+ unconst(m->pVirtualBox) = aVirtualBox;
+
+ if (uuidMachineRegistry.isValid() && !uuidMachineRegistry.isZero())
+ m->llRegistryIDs.push_back(uuidMachineRegistry);
+
+ /* no storage yet */
+ m->state = MediumState_NotCreated;
+
+ /* cannot be a host drive */
+ m->hostDrive = false;
+
+ m->devType = aDeviceType;
+
+ /* No storage unit is created yet, no need to call Medium::i_queryInfo */
+
+ rc = i_setFormat(aFormat);
+ if (FAILED(rc)) return rc;
+
+ rc = i_setLocation(aLocation);
+ if (FAILED(rc)) return rc;
+
+ if (!(m->formatObj->i_getCapabilities() & ( MediumFormatCapabilities_CreateFixed
+ | MediumFormatCapabilities_CreateDynamic
+ | MediumFormatCapabilities_File))
+ )
+ {
+ /* Storage for mediums of this format can neither be explicitly
+ * created by VirtualBox nor deleted, so we place the medium to
+ * Inaccessible state here and also add it to the registry. The
+ * state means that one has to use RefreshState() to update the
+ * medium format specific fields. */
+ m->state = MediumState_Inaccessible;
+ // create new UUID
+ unconst(m->id).create();
+
+ AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+ ComObjPtr<Medium> pMedium;
+
+ /*
+ * Check whether the UUID is taken already and create a new one
+ * if required.
+ * Try this only a limited amount of times in case the PRNG is broken
+ * in some way to prevent an endless loop.
+ */
+ for (unsigned i = 0; i < 5; i++)
+ {
+ bool fInUse;
+
+ fInUse = m->pVirtualBox->i_isMediaUuidInUse(m->id, aDeviceType);
+ if (fInUse)
+ {
+ // create new UUID
+ unconst(m->id).create();
+ }
+ else
+ break;
+ }
+
+ rc = m->pVirtualBox->i_registerMedium(this, &pMedium, treeLock);
+ Assert(this == pMedium || FAILED(rc));
+ }
+
+ /* Confirm a successful initialization when it's the case */
+ if (SUCCEEDED(rc))
+ autoInitSpan.setSucceeded();
+
+ return rc;
+}
+
+/**
+ * Initializes the medium object by opening the storage unit at the specified
+ * location. The enOpenMode parameter defines whether the medium will be opened
+ * read/write or read-only.
+ *
+ * This gets called by VirtualBox::OpenMedium() and also by
+ * Machine::AttachDevice() and createImplicitDiffs() when new diff
+ * images are created.
+ *
+ * There is no registry for this case since starting with VirtualBox 4.0, we
+ * no longer add opened media to a registry automatically (this is deferred
+ * until the medium is attached to a machine).
+ *
+ * For hard disks, the UUID, format and the parent of this medium will be
+ * determined when reading the medium storage unit. For DVD and floppy images,
+ * which have no UUIDs in their storage units, new UUIDs are created.
+ * If the detected or set parent is not known to VirtualBox, then this method
+ * will fail.
+ *
+ * @param aVirtualBox VirtualBox object.
+ * @param aLocation Storage unit location.
+ * @param enOpenMode Whether to open the medium read/write or read-only.
+ * @param fForceNewUuid Whether a new UUID should be set to avoid duplicates.
+ * @param aDeviceType Device type of medium.
+ */
+HRESULT Medium::init(VirtualBox *aVirtualBox,
+ const Utf8Str &aLocation,
+ HDDOpenMode enOpenMode,
+ bool fForceNewUuid,
+ DeviceType_T aDeviceType)
+{
+ AssertReturn(aVirtualBox, E_INVALIDARG);
+ AssertReturn(!aLocation.isEmpty(), E_INVALIDARG);
+
+ HRESULT rc = S_OK;
+
+ {
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(m->pVirtualBox) = aVirtualBox;
+
+ /* there must be a storage unit */
+ m->state = MediumState_Created;
+
+ /* remember device type for correct unregistering later */
+ m->devType = aDeviceType;
+
+ /* cannot be a host drive */
+ m->hostDrive = false;
+
+ /* remember the open mode (defaults to ReadWrite) */
+ m->hddOpenMode = enOpenMode;
+
+ if (aDeviceType == DeviceType_DVD)
+ m->type = MediumType_Readonly;
+ else if (aDeviceType == DeviceType_Floppy)
+ m->type = MediumType_Writethrough;
+
+ rc = i_setLocation(aLocation);
+ if (FAILED(rc)) return rc;
+
+ /* get all the information about the medium from the storage unit */
+ if (fForceNewUuid)
+ unconst(m->uuidImage).create();
+
+ m->state = MediumState_Inaccessible;
+ m->strLastAccessError = tr("Accessibility check was not yet performed");
+
+ /* Confirm a successful initialization before the call to i_queryInfo.
+ * Otherwise we can end up with a AutoCaller deadlock because the
+ * medium becomes visible but is not marked as initialized. Causes
+ * locking trouble (e.g. trying to save media registries) which is
+ * hard to solve. */
+ autoInitSpan.setSucceeded();
+ }
+
+ /* we're normal code from now on, no longer init */
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return autoCaller.rc();
+
+ /* need to call i_queryInfo immediately to correctly place the medium in
+ * the respective media tree and update other information such as uuid */
+ rc = i_queryInfo(fForceNewUuid /* fSetImageId */, false /* fSetParentId */,
+ autoCaller);
+ if (SUCCEEDED(rc))
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* if the storage unit is not accessible, it's not acceptable for the
+ * newly opened media so convert this into an error */
+ if (m->state == MediumState_Inaccessible)
+ {
+ Assert(!m->strLastAccessError.isEmpty());
+ rc = setError(E_FAIL, "%s", m->strLastAccessError.c_str());
+ alock.release();
+ autoCaller.release();
+ uninit();
+ }
+ else
+ {
+ AssertStmt(!m->id.isZero(),
+ alock.release(); autoCaller.release(); uninit(); return E_FAIL);
+
+ /* storage format must be detected by Medium::i_queryInfo if the
+ * medium is accessible */
+ AssertStmt(!m->strFormat.isEmpty(),
+ alock.release(); autoCaller.release(); uninit(); return E_FAIL);
+ }
+ }
+ else
+ {
+ /* opening this image failed, mark the object as dead */
+ autoCaller.release();
+ uninit();
+ }
+
+ return rc;
+}
+
+/**
+ * Initializes the medium object by loading its data from the given settings
+ * node. The medium will always be opened read/write.
+ *
+ * In this case, since we're loading from a registry, uuidMachineRegistry is
+ * always set: it's either the global registry UUID or a machine UUID when
+ * loading from a per-machine registry.
+ *
+ * @param aParent Parent medium disk or NULL for a root (base) medium.
+ * @param aDeviceType Device type of the medium.
+ * @param uuidMachineRegistry The registry to which this medium should be
+ * added (global registry UUID or machine UUID).
+ * @param data Configuration settings.
+ * @param strMachineFolder The machine folder with which to resolve relative paths;
+ * if empty, then we use the VirtualBox home directory
+ *
+ * @note Locks the medium tree for writing.
+ */
+HRESULT Medium::initOne(Medium *aParent,
+ DeviceType_T aDeviceType,
+ const Guid &uuidMachineRegistry,
+ const Utf8Str &strMachineFolder,
+ const settings::Medium &data)
+{
+ HRESULT rc;
+
+ if (uuidMachineRegistry.isValid() && !uuidMachineRegistry.isZero())
+ m->llRegistryIDs.push_back(uuidMachineRegistry);
+
+ /* register with VirtualBox/parent early, since uninit() will
+ * unconditionally unregister on failure */
+ if (aParent)
+ {
+ // differencing medium: add to parent
+ AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+ // no need to check maximum depth as settings reading did it
+ i_setParent(aParent);
+ }
+
+ /* see below why we don't call Medium::i_queryInfo (and therefore treat
+ * the medium as inaccessible for now */
+ m->state = MediumState_Inaccessible;
+ m->strLastAccessError = tr("Accessibility check was not yet performed");
+
+ /* required */
+ unconst(m->id) = data.uuid;
+
+ /* assume not a host drive */
+ m->hostDrive = false;
+
+ /* optional */
+ m->strDescription = data.strDescription;
+
+ /* required */
+ if (aDeviceType == DeviceType_HardDisk)
+ {
+ AssertReturn(!data.strFormat.isEmpty(), E_FAIL);
+ rc = i_setFormat(data.strFormat);
+ if (FAILED(rc)) return rc;
+ }
+ else
+ {
+ /// @todo handle host drive settings here as well?
+ if (!data.strFormat.isEmpty())
+ rc = i_setFormat(data.strFormat);
+ else
+ rc = i_setFormat("RAW");
+ if (FAILED(rc)) return rc;
+ }
+
+ /* optional, only for diffs, default is false; we can only auto-reset
+ * diff media so they must have a parent */
+ if (aParent != NULL)
+ m->autoReset = data.fAutoReset;
+ else
+ m->autoReset = false;
+
+ /* properties (after setting the format as it populates the map). Note that
+ * if some properties are not supported but present in the settings file,
+ * they will still be read and accessible (for possible backward
+ * compatibility; we can also clean them up from the XML upon next
+ * XML format version change if we wish) */
+ for (settings::StringsMap::const_iterator it = data.properties.begin();
+ it != data.properties.end();
+ ++it)
+ {
+ const Utf8Str &name = it->first;
+ const Utf8Str &value = it->second;
+ m->mapProperties[name] = value;
+ }
+
+ /* try to decrypt an optional iSCSI initiator secret */
+ settings::StringsMap::const_iterator itCph = data.properties.find("InitiatorSecretEncrypted");
+ if ( itCph != data.properties.end()
+ && !itCph->second.isEmpty())
+ {
+ Utf8Str strPlaintext;
+ int vrc = m->pVirtualBox->i_decryptSetting(&strPlaintext, itCph->second);
+ if (RT_SUCCESS(vrc))
+ m->mapProperties["InitiatorSecret"] = strPlaintext;
+ }
+
+ Utf8Str strFull;
+ if (m->formatObj->i_getCapabilities() & MediumFormatCapabilities_File)
+ {
+ // compose full path of the medium, if it's not fully qualified...
+ // slightly convoluted logic here. If the caller has given us a
+ // machine folder, then a relative path will be relative to that:
+ if ( !strMachineFolder.isEmpty()
+ && !RTPathStartsWithRoot(data.strLocation.c_str())
+ )
+ {
+ strFull = strMachineFolder;
+ strFull += RTPATH_SLASH;
+ strFull += data.strLocation;
+ }
+ else
+ {
+ // Otherwise use the old VirtualBox "make absolute path" logic:
+ int vrc = m->pVirtualBox->i_calculateFullPath(data.strLocation, strFull);
+ if (RT_FAILURE(vrc))
+ return Global::vboxStatusCodeToCOM(vrc);
+ }
+ }
+ else
+ strFull = data.strLocation;
+
+ rc = i_setLocation(strFull);
+ if (FAILED(rc)) return rc;
+
+ if (aDeviceType == DeviceType_HardDisk)
+ {
+ /* type is only for base hard disks */
+ if (m->pParent.isNull())
+ m->type = data.hdType;
+ }
+ else if (aDeviceType == DeviceType_DVD)
+ m->type = MediumType_Readonly;
+ else
+ m->type = MediumType_Writethrough;
+
+ /* remember device type for correct unregistering later */
+ m->devType = aDeviceType;
+
+ LogFlowThisFunc(("m->strLocationFull='%s', m->strFormat=%s, m->id={%RTuuid}\n",
+ m->strLocationFull.c_str(), m->strFormat.c_str(), m->id.raw()));
+
+ return S_OK;
+}
+
+/**
+ * Initializes and registers the medium object and its children by loading its
+ * data from the given settings node. The medium will always be opened
+ * read/write.
+ *
+ * @todo r=bird: What's that stuff about 'always be opened read/write'?
+ *
+ * In this case, since we're loading from a registry, uuidMachineRegistry is
+ * always set: it's either the global registry UUID or a machine UUID when
+ * loading from a per-machine registry.
+ *
+ * The only caller is currently VirtualBox::initMedia().
+ *
+ * @param aVirtualBox VirtualBox object.
+ * @param aDeviceType Device type of the medium.
+ * @param uuidMachineRegistry The registry to which this medium should be added
+ * (global registry UUID or machine UUID).
+ * @param strMachineFolder The machine folder with which to resolve relative
+ * paths; if empty, then we use the VirtualBox home directory
+ * @param data Configuration settings.
+ * @param mediaTreeLock Autolock.
+ * @param uIdsForNotify List to be updated with newly registered media.
+ *
+ * @note Assumes that the medium tree lock is held for writing. May release
+ * and lock it again. At the end it is always held.
+ */
+/* static */
+HRESULT Medium::initFromSettings(VirtualBox *aVirtualBox,
+ DeviceType_T aDeviceType,
+ const Guid &uuidMachineRegistry,
+ const Utf8Str &strMachineFolder,
+ const settings::Medium &data,
+ AutoWriteLock &mediaTreeLock,
+ std::list<std::pair<Guid, DeviceType_T> > &uIdsForNotify)
+{
+ Assert(aVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+ AssertReturn(aVirtualBox, E_INVALIDARG);
+
+ HRESULT rc = S_OK;
+
+ MediaList llMediaTocleanup;
+
+ std::list<const settings::Medium *> llSettingsTodo;
+ llSettingsTodo.push_back(&data);
+ MediaList llParentsTodo;
+ llParentsTodo.push_back(NULL);
+
+ while (!llSettingsTodo.empty())
+ {
+ const settings::Medium *current = llSettingsTodo.front();
+ llSettingsTodo.pop_front();
+ ComObjPtr<Medium> pParent = llParentsTodo.front();
+ llParentsTodo.pop_front();
+
+ bool fReleasedMediaTreeLock = false;
+ ComObjPtr<Medium> pMedium;
+ rc = pMedium.createObject();
+ if (FAILED(rc))
+ break;
+ ComObjPtr<Medium> pActualMedium(pMedium);
+
+ {
+ AutoInitSpan autoInitSpan(pMedium);
+ AssertBreakStmt(autoInitSpan.isOk(), rc = E_FAIL);
+
+ unconst(pMedium->m->pVirtualBox) = aVirtualBox;
+ rc = pMedium->initOne(pParent, aDeviceType, uuidMachineRegistry, strMachineFolder, *current);
+ if (FAILED(rc))
+ break;
+ rc = aVirtualBox->i_registerMedium(pActualMedium, &pActualMedium, mediaTreeLock, true /*fCalledFromMediumInit*/);
+ if (FAILED(rc))
+ break;
+
+ if (pActualMedium == pMedium)
+ {
+ /* It is a truly new medium, remember details for cleanup. */
+ autoInitSpan.setSucceeded();
+ llMediaTocleanup.push_front(pMedium);
+ }
+ else
+ {
+ /* Since the newly created medium was replaced by an already
+ * known one when merging medium trees, we can immediately mark
+ * it as failed. */
+ autoInitSpan.setFailed();
+ mediaTreeLock.release();
+ fReleasedMediaTreeLock = true;
+ }
+ }
+ if (fReleasedMediaTreeLock)
+ {
+ /* With the InitSpan out of the way it's safe to let the refcount
+ * drop to 0 without causing uninit trouble. */
+ pMedium.setNull();
+ mediaTreeLock.acquire();
+ }
+
+ /* create all children */
+ std::list<settings::Medium>::const_iterator itBegin = current->llChildren.begin();
+ std::list<settings::Medium>::const_iterator itEnd = current->llChildren.end();
+ for (std::list<settings::Medium>::const_iterator it = itBegin; it != itEnd; ++it)
+ {
+ llSettingsTodo.push_back(&*it);
+ llParentsTodo.push_back(pActualMedium);
+ }
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ /* Check for consistency. */
+ Assert(llSettingsTodo.size() == 0);
+ Assert(llParentsTodo.size() == 0);
+ /* Create the list of notifications, parent first. */
+ MediaList::const_reverse_iterator itBegin = llMediaTocleanup.rbegin();
+ MediaList::const_reverse_iterator itEnd = llMediaTocleanup.rend();
+ for (MediaList::const_reverse_iterator it = itBegin; it != itEnd; --it)
+ {
+ ComObjPtr<Medium> pMedium = *it;
+ AutoCaller mediumCaller(pMedium);
+ if (FAILED(mediumCaller.rc())) continue;
+ const Guid &id = pMedium->i_getId();
+ uIdsForNotify.push_back(std::pair<Guid, DeviceType_T>(id, aDeviceType));
+ }
+ }
+ else
+ {
+ /* Forget state of the settings processing. */
+ llSettingsTodo.clear();
+ llParentsTodo.clear();
+ /* Unregister all accumulated medium objects in the right order (last
+ * created to first created, avoiding config leftovers). */
+ MediaList::const_iterator itBegin = llMediaTocleanup.begin();
+ MediaList::const_iterator itEnd = llMediaTocleanup.end();
+ for (MediaList::const_iterator it = itBegin; it != itEnd; ++it)
+ {
+ ComObjPtr<Medium> pMedium = *it;
+ pMedium->i_unregisterWithVirtualBox();
+ }
+ /* Forget the only references to all newly created medium objects,
+ * triggering freeing (uninit happened in unregistering above). */
+ mediaTreeLock.release();
+ llMediaTocleanup.clear();
+ mediaTreeLock.acquire();
+ }
+
+ return rc;
+}
+
+/**
+ * Initializes the medium object by providing the host drive information.
+ * Not used for anything but the host floppy/host DVD case.
+ *
+ * There is no registry for this case.
+ *
+ * @param aVirtualBox VirtualBox object.
+ * @param aDeviceType Device type of the medium.
+ * @param aLocation Location of the host drive.
+ * @param aDescription Comment for this host drive.
+ *
+ * @note Locks VirtualBox lock for writing.
+ */
+HRESULT Medium::init(VirtualBox *aVirtualBox,
+ DeviceType_T aDeviceType,
+ const Utf8Str &aLocation,
+ const Utf8Str &aDescription /* = Utf8Str::Empty */)
+{
+ ComAssertRet(aDeviceType == DeviceType_DVD || aDeviceType == DeviceType_Floppy, E_INVALIDARG);
+ ComAssertRet(!aLocation.isEmpty(), E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(m->pVirtualBox) = aVirtualBox;
+
+ // We do not store host drives in VirtualBox.xml or anywhere else, so if we want
+ // host drives to be identifiable by UUID and not give the drive a different UUID
+ // every time VirtualBox starts, we need to fake a reproducible UUID here:
+ RTUUID uuid;
+ RTUuidClear(&uuid);
+ if (aDeviceType == DeviceType_DVD)
+ memcpy(&uuid.au8[0], "DVD", 3);
+ else
+ memcpy(&uuid.au8[0], "FD", 2);
+ /* use device name, adjusted to the end of uuid, shortened if necessary */
+ size_t lenLocation = aLocation.length();
+ if (lenLocation > 12)
+ memcpy(&uuid.au8[4], aLocation.c_str() + (lenLocation - 12), 12);
+ else
+ memcpy(&uuid.au8[4 + 12 - lenLocation], aLocation.c_str(), lenLocation);
+ unconst(m->id) = uuid;
+
+ if (aDeviceType == DeviceType_DVD)
+ m->type = MediumType_Readonly;
+ else
+ m->type = MediumType_Writethrough;
+ m->devType = aDeviceType;
+ m->state = MediumState_Created;
+ m->hostDrive = true;
+ HRESULT rc = i_setFormat("RAW");
+ if (FAILED(rc)) return rc;
+ rc = i_setLocation(aLocation);
+ if (FAILED(rc)) return rc;
+ m->strDescription = aDescription;
+
+ autoInitSpan.setSucceeded();
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance.
+ *
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ *
+ * @note All children of this medium get uninitialized, too, in a stack
+ * friendly manner.
+ */
+void Medium::uninit()
+{
+ /* It is possible that some previous/concurrent uninit has already cleared
+ * the pVirtualBox reference, and in this case we don't need to continue.
+ * Normally this would be handled through the AutoUninitSpan magic, however
+ * this cannot be done at this point as the media tree must be locked
+ * before reaching the AutoUninitSpan, otherwise deadlocks can happen.
+ *
+ * NOTE: The tree lock is higher priority than the medium caller and medium
+ * object locks, i.e. the medium caller may have to be released and be
+ * re-acquired in the right place later. See Medium::getParent() for sample
+ * code how to do this safely. */
+ VirtualBox *pVirtualBox = m->pVirtualBox;
+ if (!pVirtualBox)
+ return;
+
+ /* Caller must not hold the object (checked below) or media tree lock. */
+ Assert(!pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+
+ AutoWriteLock treeLock(pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ /* Must use a list without refcounting help since "this" might already have
+ * reached 0, and then the refcount must not be increased again since it
+ * would otherwise trigger a double free. For all other list entries this
+ * needs manual refcount updating, to make sure the refcount for children
+ * does not drop to 0 too early. */
+ std::list<Medium *> llMediaTodo;
+ llMediaTodo.push_back(this);
+
+ while (!llMediaTodo.empty())
+ {
+ Medium *pMedium = llMediaTodo.front();
+ llMediaTodo.pop_front();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(pMedium);
+ if (autoUninitSpan.uninitDone())
+ {
+ if (pMedium != this)
+ pMedium->Release();
+ continue;
+ }
+
+ Assert(!pMedium->isWriteLockOnCurrentThread());
+#ifdef DEBUG
+ if (!pMedium->m->backRefs.empty())
+ pMedium->i_dumpBackRefs();
+#endif
+ Assert(pMedium->m->backRefs.empty());
+
+ pMedium->m->formatObj.setNull();
+
+ if (pMedium->m->state == MediumState_Deleting)
+ {
+ /* This medium has been already deleted (directly or as part of a
+ * merge). Reparenting has already been done. */
+ Assert(pMedium->m->pParent.isNull());
+ Assert(pMedium->m->llChildren.empty());
+ if (pMedium != this)
+ pMedium->Release();
+ continue;
+ }
+
+ //Assert(!pMedium->m->pParent);
+ /** @todo r=klaus Should not be necessary, since the caller should be
+ * doing the deparenting. No time right now to test everything. */
+ if (pMedium == this && pMedium->m->pParent)
+ pMedium->i_deparent();
+
+ /* Process all children */
+ MediaList::const_iterator itBegin = pMedium->m->llChildren.begin();
+ MediaList::const_iterator itEnd = pMedium->m->llChildren.end();
+ for (MediaList::const_iterator it = itBegin; it != itEnd; ++it)
+ {
+ Medium *pChild = *it;
+ pChild->m->pParent.setNull();
+ pChild->AddRef();
+ llMediaTodo.push_back(pChild);
+ }
+
+ /* Children information obsolete, will be processed anyway. */
+ pMedium->m->llChildren.clear();
+
+ unconst(pMedium->m->pVirtualBox) = NULL;
+
+ if (pMedium != this)
+ pMedium->Release();
+
+ autoUninitSpan.setSucceeded();
+ }
+}
+
+/**
+ * Internal helper that removes "this" from the list of children of its
+ * parent. Used in uninit() and other places when reparenting is necessary.
+ *
+ * The caller must hold the medium tree lock!
+ */
+void Medium::i_deparent()
+{
+ MediaList &llParent = m->pParent->m->llChildren;
+ for (MediaList::iterator it = llParent.begin();
+ it != llParent.end();
+ ++it)
+ {
+ Medium *pParentsChild = *it;
+ if (this == pParentsChild)
+ {
+ llParent.erase(it);
+ break;
+ }
+ }
+ m->pParent.setNull();
+}
+
+/**
+ * Internal helper that removes "this" from the list of children of its
+ * parent. Used in uninit() and other places when reparenting is necessary.
+ *
+ * The caller must hold the medium tree lock!
+ */
+void Medium::i_setParent(const ComObjPtr<Medium> &pParent)
+{
+ m->pParent = pParent;
+ if (pParent)
+ pParent->m->llChildren.push_back(this);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// IMedium public methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT Medium::getId(com::Guid &aId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aId = m->id;
+
+ return S_OK;
+}
+
+HRESULT Medium::getDescription(AutoCaller &autoCaller, com::Utf8Str &aDescription)
+{
+ NOREF(autoCaller);
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aDescription = m->strDescription;
+
+ return S_OK;
+}
+
+HRESULT Medium::setDescription(AutoCaller &autoCaller, const com::Utf8Str &aDescription)
+{
+ /// @todo update m->strDescription and save the global registry (and local
+ /// registries of portable VMs referring to this medium), this will also
+ /// require to add the mRegistered flag to data
+
+ HRESULT rc = S_OK;
+
+ MediumLockList *pMediumLockList(new MediumLockList());
+
+ try
+ {
+ autoCaller.release();
+
+ // to avoid redundant locking, which just takes a time, just call required functions.
+ // the error will be just stored and will be reported after locks will be acquired again
+
+ const char *pszError = NULL;
+
+
+ /* Build the lock list. */
+ rc = i_createMediumLockList(true /* fFailIfInaccessible */,
+ this /* pToLockWrite */,
+ true /* fMediumLockWriteAll */,
+ NULL,
+ *pMediumLockList);
+ if (FAILED(rc))
+ {
+ pszError = tr("Failed to create medium lock list for '%s'");
+ }
+ else
+ {
+ rc = pMediumLockList->Lock();
+ if (FAILED(rc))
+ pszError = tr("Failed to lock media '%s'");
+ }
+
+ // locking: we need the tree lock first because we access parent pointers
+ // and we need to write-lock the media involved
+ AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (FAILED(rc))
+ throw setError(rc, pszError, i_getLocationFull().c_str());
+
+ /* Set a new description */
+ m->strDescription = aDescription;
+
+ // save the settings
+ alock.release();
+ autoCaller.release();
+ treeLock.release();
+ i_markRegistriesModified();
+ m->pVirtualBox->i_saveModifiedRegistries();
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ delete pMediumLockList;
+
+ return rc;
+}
+
+HRESULT Medium::getState(MediumState_T *aState)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aState = m->state;
+
+ return S_OK;
+}
+
+HRESULT Medium::getVariant(std::vector<MediumVariant_T> &aVariant)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const size_t cBits = sizeof(MediumVariant_T) * 8;
+ aVariant.resize(cBits);
+ for (size_t i = 0; i < cBits; ++i)
+ aVariant[i] = (MediumVariant_T)(m->variant & RT_BIT(i));
+
+ return S_OK;
+}
+
+HRESULT Medium::getLocation(com::Utf8Str &aLocation)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aLocation = m->strLocationFull;
+
+ return S_OK;
+}
+
+HRESULT Medium::getName(com::Utf8Str &aName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aName = i_getName();
+
+ return S_OK;
+}
+
+HRESULT Medium::getDeviceType(DeviceType_T *aDeviceType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aDeviceType = m->devType;
+
+ return S_OK;
+}
+
+HRESULT Medium::getHostDrive(BOOL *aHostDrive)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aHostDrive = m->hostDrive;
+
+ return S_OK;
+}
+
+HRESULT Medium::getSize(LONG64 *aSize)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aSize = (LONG64)m->size;
+
+ return S_OK;
+}
+
+HRESULT Medium::getFormat(com::Utf8Str &aFormat)
+{
+ /* no need to lock, m->strFormat is const */
+
+ aFormat = m->strFormat;
+ return S_OK;
+}
+
+HRESULT Medium::getMediumFormat(ComPtr<IMediumFormat> &aMediumFormat)
+{
+ /* no need to lock, m->formatObj is const */
+ m->formatObj.queryInterfaceTo(aMediumFormat.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Medium::getType(AutoCaller &autoCaller, MediumType_T *aType)
+{
+ NOREF(autoCaller);
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aType = m->type;
+
+ return S_OK;
+}
+
+HRESULT Medium::setType(AutoCaller &autoCaller, MediumType_T aType)
+{
+ autoCaller.release();
+
+ /* It is possible that some previous/concurrent uninit has already cleared
+ * the pVirtualBox reference, see #uninit(). */
+ ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox);
+
+ // we access m->pParent
+ AutoReadLock treeLock(!pVirtualBox.isNull() ? &pVirtualBox->i_getMediaTreeLockHandle() : NULL COMMA_LOCKVAL_SRC_POS);
+
+ autoCaller.add();
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock mlock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Wait for a concurrently running Medium::i_queryInfo to complete. */
+ while (m->queryInfoRunning)
+ {
+ mlock.release();
+ autoCaller.release();
+ treeLock.release();
+ /* Must not hold the media tree lock, as Medium::i_queryInfo needs
+ * this lock and thus we would run into a deadlock here. */
+ Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+ /* must not hold the object lock now */
+ Assert(!isWriteLockOnCurrentThread());
+ {
+ AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS);
+ }
+ treeLock.acquire();
+ autoCaller.add();
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+ mlock.acquire();
+ }
+
+ switch (m->state)
+ {
+ case MediumState_Created:
+ case MediumState_Inaccessible:
+ break;
+ default:
+ return i_setStateError();
+ }
+
+ if (m->type == aType)
+ {
+ /* Nothing to do */
+ return S_OK;
+ }
+
+ DeviceType_T devType = i_getDeviceType();
+ // DVD media can only be readonly.
+ if (devType == DeviceType_DVD && aType != MediumType_Readonly)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot change the type of DVD medium '%s'"),
+ m->strLocationFull.c_str());
+ // Floppy media can only be writethrough or readonly.
+ if ( devType == DeviceType_Floppy
+ && aType != MediumType_Writethrough
+ && aType != MediumType_Readonly)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot change the type of floppy medium '%s'"),
+ m->strLocationFull.c_str());
+
+ /* cannot change the type of a differencing medium */
+ if (m->pParent)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot change the type of medium '%s' because it is a differencing medium"),
+ m->strLocationFull.c_str());
+
+ /* Cannot change the type of a medium being in use by more than one VM.
+ * If the change is to Immutable or MultiAttach then it must not be
+ * directly attached to any VM, otherwise the assumptions about indirect
+ * attachment elsewhere are violated and the VM becomes inaccessible.
+ * Attaching an immutable medium triggers the diff creation, and this is
+ * vital for the correct operation. */
+ if ( m->backRefs.size() > 1
+ || ( ( aType == MediumType_Immutable
+ || aType == MediumType_MultiAttach)
+ && m->backRefs.size() > 0))
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot change the type of medium '%s' because it is attached to %d virtual machines",
+ "", m->backRefs.size()),
+ m->strLocationFull.c_str(), m->backRefs.size());
+
+ switch (aType)
+ {
+ case MediumType_Normal:
+ case MediumType_Immutable:
+ case MediumType_MultiAttach:
+ {
+ /* normal can be easily converted to immutable and vice versa even
+ * if they have children as long as they are not attached to any
+ * machine themselves */
+ break;
+ }
+ case MediumType_Writethrough:
+ case MediumType_Shareable:
+ case MediumType_Readonly:
+ {
+ /* cannot change to writethrough, shareable or readonly
+ * if there are children */
+ if (i_getChildren().size() != 0)
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Cannot change type for medium '%s' since it has %d child media", "", i_getChildren().size()),
+ m->strLocationFull.c_str(), i_getChildren().size());
+ if (aType == MediumType_Shareable)
+ {
+ MediumVariant_T variant = i_getVariant();
+ if (!(variant & MediumVariant_Fixed))
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot change type for medium '%s' to 'Shareable' since it is a dynamic medium storage unit"),
+ m->strLocationFull.c_str());
+ }
+ else if (aType == MediumType_Readonly && devType == DeviceType_HardDisk)
+ {
+ // Readonly hard disks are not allowed, this medium type is reserved for
+ // DVDs and floppy images at the moment. Later we might allow readonly hard
+ // disks, but that's extremely unusual and many guest OSes will have trouble.
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot change type for medium '%s' to 'Readonly' since it is a hard disk"),
+ m->strLocationFull.c_str());
+ }
+ break;
+ }
+ default:
+ AssertFailedReturn(E_FAIL);
+ }
+
+ if (aType == MediumType_MultiAttach)
+ {
+ // This type is new with VirtualBox 4.0 and therefore requires settings
+ // version 1.11 in the settings backend. Unfortunately it is not enough to do
+ // the usual routine in MachineConfigFile::bumpSettingsVersionIfNeeded() for
+ // two reasons: The medium type is a property of the media registry tree, which
+ // can reside in the global config file (for pre-4.0 media); we would therefore
+ // possibly need to bump the global config version. We don't want to do that though
+ // because that might make downgrading to pre-4.0 impossible.
+ // As a result, we can only use these two new types if the medium is NOT in the
+ // global registry:
+ const Guid &uuidGlobalRegistry = m->pVirtualBox->i_getGlobalRegistryId();
+ if (i_isInRegistry(uuidGlobalRegistry))
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot change type for medium '%s': the media type 'MultiAttach' can only be used "
+ "on media registered with a machine that was created with VirtualBox 4.0 or later"),
+ m->strLocationFull.c_str());
+ }
+
+ m->type = aType;
+
+ // save the settings
+ mlock.release();
+ autoCaller.release();
+ treeLock.release();
+ i_markRegistriesModified();
+ m->pVirtualBox->i_saveModifiedRegistries();
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+
+ return S_OK;
+}
+
+HRESULT Medium::getAllowedTypes(std::vector<MediumType_T> &aAllowedTypes)
+{
+ NOREF(aAllowedTypes);
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ReturnComNotImplemented();
+}
+
+HRESULT Medium::getParent(AutoCaller &autoCaller, ComPtr<IMedium> &aParent)
+{
+ autoCaller.release();
+
+ /* It is possible that some previous/concurrent uninit has already cleared
+ * the pVirtualBox reference, see #uninit(). */
+ ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox);
+
+ /* we access m->pParent */
+ AutoReadLock treeLock(!pVirtualBox.isNull() ? &pVirtualBox->i_getMediaTreeLockHandle() : NULL COMMA_LOCKVAL_SRC_POS);
+
+ autoCaller.add();
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ m->pParent.queryInterfaceTo(aParent.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Medium::getChildren(AutoCaller &autoCaller, std::vector<ComPtr<IMedium> > &aChildren)
+{
+ autoCaller.release();
+
+ /* It is possible that some previous/concurrent uninit has already cleared
+ * the pVirtualBox reference, see #uninit(). */
+ ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox);
+
+ /* we access children */
+ AutoReadLock treeLock(!pVirtualBox.isNull() ? &pVirtualBox->i_getMediaTreeLockHandle() : NULL COMMA_LOCKVAL_SRC_POS);
+
+ autoCaller.add();
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ MediaList children(this->i_getChildren());
+ aChildren.resize(children.size());
+ size_t i = 0;
+ for (MediaList::const_iterator it = children.begin(); it != children.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aChildren[i].asOutParam());
+ return S_OK;
+}
+
+HRESULT Medium::getBase(AutoCaller &autoCaller, ComPtr<IMedium> &aBase)
+{
+ autoCaller.release();
+
+ /* i_getBase() will do callers/locking */
+ i_getBase().queryInterfaceTo(aBase.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Medium::getReadOnly(AutoCaller &autoCaller, BOOL *aReadOnly)
+{
+ autoCaller.release();
+
+ /* isReadOnly() will do locking */
+ *aReadOnly = i_isReadOnly();
+
+ return S_OK;
+}
+
+HRESULT Medium::getLogicalSize(LONG64 *aLogicalSize)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aLogicalSize = (LONG64)m->logicalSize;
+
+ return S_OK;
+}
+
+HRESULT Medium::getAutoReset(BOOL *aAutoReset)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->pParent.isNull())
+ *aAutoReset = FALSE;
+ else
+ *aAutoReset = m->autoReset;
+
+ return S_OK;
+}
+
+HRESULT Medium::setAutoReset(BOOL aAutoReset)
+{
+ AutoWriteLock mlock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->pParent.isNull())
+ return setError(VBOX_E_NOT_SUPPORTED,
+ tr("Medium '%s' is not differencing"),
+ m->strLocationFull.c_str());
+
+ if (m->autoReset != !!aAutoReset)
+ {
+ m->autoReset = !!aAutoReset;
+
+ // save the settings
+ mlock.release();
+ i_markRegistriesModified();
+ m->pVirtualBox->i_saveModifiedRegistries();
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+ }
+
+ return S_OK;
+}
+
+HRESULT Medium::getLastAccessError(com::Utf8Str &aLastAccessError)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aLastAccessError = m->strLastAccessError;
+
+ return S_OK;
+}
+
+HRESULT Medium::getMachineIds(std::vector<com::Guid> &aMachineIds)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->backRefs.size() != 0)
+ {
+ BackRefList brlist(m->backRefs);
+ aMachineIds.resize(brlist.size());
+ size_t i = 0;
+ for (BackRefList::const_iterator it = brlist.begin(); it != brlist.end(); ++it, ++i)
+ aMachineIds[i] = it->machineId;
+ }
+
+ return S_OK;
+}
+
+HRESULT Medium::setIds(AutoCaller &autoCaller,
+ BOOL aSetImageId,
+ const com::Guid &aImageId,
+ BOOL aSetParentId,
+ const com::Guid &aParentId)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Wait for a concurrently running Medium::i_queryInfo to complete. */
+ if (m->queryInfoRunning)
+ {
+ /* Must not hold the media tree lock, as Medium::i_queryInfo needs this
+ * lock and thus we would run into a deadlock here. */
+ Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+ while (m->queryInfoRunning)
+ {
+ alock.release();
+ /* must not hold the object lock now */
+ Assert(!isWriteLockOnCurrentThread());
+ {
+ AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS);
+ }
+ alock.acquire();
+ }
+ }
+
+ switch (m->state)
+ {
+ case MediumState_Created:
+ break;
+ default:
+ return i_setStateError();
+ }
+
+ Guid imageId, parentId;
+ if (aSetImageId)
+ {
+ if (aImageId.isZero())
+ imageId.create();
+ else
+ {
+ imageId = aImageId;
+ if (!imageId.isValid())
+ return setError(E_INVALIDARG, tr("Argument %s is invalid"), "aImageId");
+ }
+ }
+ if (aSetParentId)
+ {
+ if (aParentId.isZero())
+ parentId.create();
+ else
+ parentId = aParentId;
+ }
+
+ const Guid uPrevImage = m->uuidImage;
+ unconst(m->uuidImage) = imageId;
+ ComObjPtr<Medium> pPrevParent = i_getParent();
+ unconst(m->uuidParentImage) = parentId;
+
+ // must not hold any locks before calling Medium::i_queryInfo
+ alock.release();
+
+ HRESULT rc = i_queryInfo(!!aSetImageId /* fSetImageId */,
+ !!aSetParentId /* fSetParentId */,
+ autoCaller);
+
+ AutoReadLock arlock(this COMMA_LOCKVAL_SRC_POS);
+ const Guid uCurrImage = m->uuidImage;
+ ComObjPtr<Medium> pCurrParent = i_getParent();
+ arlock.release();
+
+ if (SUCCEEDED(rc))
+ {
+ if (uCurrImage != uPrevImage)
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+ if (pPrevParent != pCurrParent)
+ {
+ if (pPrevParent)
+ m->pVirtualBox->i_onMediumConfigChanged(pPrevParent);
+ if (pCurrParent)
+ m->pVirtualBox->i_onMediumConfigChanged(pCurrParent);
+ }
+ }
+
+ return rc;
+}
+
+HRESULT Medium::refreshState(AutoCaller &autoCaller, MediumState_T *aState)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ switch (m->state)
+ {
+ case MediumState_Created:
+ case MediumState_Inaccessible:
+ case MediumState_LockedRead:
+ {
+ // must not hold any locks before calling Medium::i_queryInfo
+ alock.release();
+
+ rc = i_queryInfo(false /* fSetImageId */, false /* fSetParentId */,
+ autoCaller);
+
+ alock.acquire();
+ break;
+ }
+ default:
+ break;
+ }
+
+ *aState = m->state;
+
+ return rc;
+}
+
+HRESULT Medium::getSnapshotIds(const com::Guid &aMachineId,
+ std::vector<com::Guid> &aSnapshotIds)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (BackRefList::const_iterator it = m->backRefs.begin();
+ it != m->backRefs.end(); ++it)
+ {
+ if (it->machineId == aMachineId)
+ {
+ size_t size = it->llSnapshotIds.size();
+
+ /* if the medium is attached to the machine in the current state, we
+ * return its ID as the first element of the array */
+ if (it->fInCurState)
+ ++size;
+
+ if (size > 0)
+ {
+ aSnapshotIds.resize(size);
+
+ size_t j = 0;
+ if (it->fInCurState)
+ aSnapshotIds[j++] = it->machineId.toUtf16();
+
+ for(std::list<SnapshotRef>::const_iterator jt = it->llSnapshotIds.begin(); jt != it->llSnapshotIds.end(); ++jt, ++j)
+ aSnapshotIds[j] = jt->snapshotId;
+ }
+
+ break;
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT Medium::lockRead(ComPtr<IToken> &aToken)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Wait for a concurrently running Medium::i_queryInfo to complete. */
+ if (m->queryInfoRunning)
+ {
+ /* Must not hold the media tree lock, as Medium::i_queryInfo needs this
+ * lock and thus we would run into a deadlock here. */
+ Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+ while (m->queryInfoRunning)
+ {
+ alock.release();
+ /* must not hold the object lock now */
+ Assert(!isWriteLockOnCurrentThread());
+ {
+ AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS);
+ }
+ alock.acquire();
+ }
+ }
+
+ HRESULT rc = S_OK;
+
+ switch (m->state)
+ {
+ case MediumState_Created:
+ case MediumState_Inaccessible:
+ case MediumState_LockedRead:
+ {
+ ++m->readers;
+
+ ComAssertMsgBreak(m->readers != 0, (tr("Counter overflow")), rc = E_FAIL);
+
+ /* Remember pre-lock state */
+ if (m->state != MediumState_LockedRead)
+ m->preLockState = m->state;
+
+ LogFlowThisFunc(("Okay - prev state=%d readers=%d\n", m->state, m->readers));
+ m->state = MediumState_LockedRead;
+
+ ComObjPtr<MediumLockToken> pToken;
+ rc = pToken.createObject();
+ if (SUCCEEDED(rc))
+ rc = pToken->init(this, false /* fWrite */);
+ if (FAILED(rc))
+ {
+ --m->readers;
+ if (m->readers == 0)
+ m->state = m->preLockState;
+ return rc;
+ }
+
+ pToken.queryInterfaceTo(aToken.asOutParam());
+ break;
+ }
+ default:
+ {
+ LogFlowThisFunc(("Failing - state=%d\n", m->state));
+ rc = i_setStateError();
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @note @a aState may be NULL if the state value is not needed (only for
+ * in-process calls).
+ */
+HRESULT Medium::i_unlockRead(MediumState_T *aState)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ switch (m->state)
+ {
+ case MediumState_LockedRead:
+ {
+ ComAssertMsgBreak(m->readers != 0, (tr("Counter underflow")), rc = E_FAIL);
+ --m->readers;
+
+ /* Reset the state after the last reader */
+ if (m->readers == 0)
+ {
+ m->state = m->preLockState;
+ /* There are cases where we inject the deleting state into
+ * a medium locked for reading. Make sure #unmarkForDeletion()
+ * gets the right state afterwards. */
+ if (m->preLockState == MediumState_Deleting)
+ m->preLockState = MediumState_Created;
+ }
+
+ LogFlowThisFunc(("new state=%d\n", m->state));
+ break;
+ }
+ default:
+ {
+ LogFlowThisFunc(("Failing - state=%d\n", m->state));
+ rc = setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium '%s' is not locked for reading"),
+ m->strLocationFull.c_str());
+ break;
+ }
+ }
+
+ /* return the current state after */
+ if (aState)
+ *aState = m->state;
+
+ return rc;
+}
+HRESULT Medium::lockWrite(ComPtr<IToken> &aToken)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Wait for a concurrently running Medium::i_queryInfo to complete. */
+ if (m->queryInfoRunning)
+ {
+ /* Must not hold the media tree lock, as Medium::i_queryInfo needs this
+ * lock and thus we would run into a deadlock here. */
+ Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+ while (m->queryInfoRunning)
+ {
+ alock.release();
+ /* must not hold the object lock now */
+ Assert(!isWriteLockOnCurrentThread());
+ {
+ AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS);
+ }
+ alock.acquire();
+ }
+ }
+
+ HRESULT rc = S_OK;
+
+ switch (m->state)
+ {
+ case MediumState_Created:
+ case MediumState_Inaccessible:
+ {
+ m->preLockState = m->state;
+
+ LogFlowThisFunc(("Okay - prev state=%d locationFull=%s\n", m->state, i_getLocationFull().c_str()));
+ m->state = MediumState_LockedWrite;
+
+ ComObjPtr<MediumLockToken> pToken;
+ rc = pToken.createObject();
+ if (SUCCEEDED(rc))
+ rc = pToken->init(this, true /* fWrite */);
+ if (FAILED(rc))
+ {
+ m->state = m->preLockState;
+ return rc;
+ }
+
+ pToken.queryInterfaceTo(aToken.asOutParam());
+ break;
+ }
+ default:
+ {
+ LogFlowThisFunc(("Failing - state=%d locationFull=%s\n", m->state, i_getLocationFull().c_str()));
+ rc = i_setStateError();
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @note @a aState may be NULL if the state value is not needed (only for
+ * in-process calls).
+ */
+HRESULT Medium::i_unlockWrite(MediumState_T *aState)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ switch (m->state)
+ {
+ case MediumState_LockedWrite:
+ {
+ m->state = m->preLockState;
+ /* There are cases where we inject the deleting state into
+ * a medium locked for writing. Make sure #unmarkForDeletion()
+ * gets the right state afterwards. */
+ if (m->preLockState == MediumState_Deleting)
+ m->preLockState = MediumState_Created;
+ LogFlowThisFunc(("new state=%d locationFull=%s\n", m->state, i_getLocationFull().c_str()));
+ break;
+ }
+ default:
+ {
+ LogFlowThisFunc(("Failing - state=%d locationFull=%s\n", m->state, i_getLocationFull().c_str()));
+ rc = setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium '%s' is not locked for writing"),
+ m->strLocationFull.c_str());
+ break;
+ }
+ }
+
+ /* return the current state after */
+ if (aState)
+ *aState = m->state;
+
+ return rc;
+}
+
+HRESULT Medium::close(AutoCaller &aAutoCaller)
+{
+ // make a copy of VirtualBox pointer which gets nulled by uninit()
+ ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox);
+
+ Guid uId = i_getId();
+ DeviceType_T devType = i_getDeviceType();
+ MultiResult mrc = i_close(aAutoCaller);
+
+ pVirtualBox->i_saveModifiedRegistries();
+
+ if (SUCCEEDED(mrc) && uId.isValid() && !uId.isZero())
+ pVirtualBox->i_onMediumRegistered(uId, devType, FALSE);
+
+ return mrc;
+}
+
+HRESULT Medium::getProperty(const com::Utf8Str &aName,
+ com::Utf8Str &aValue)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ settings::StringsMap::const_iterator it = m->mapProperties.find(aName);
+ if (it == m->mapProperties.end())
+ {
+ if (!aName.startsWith("Special/"))
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Property '%s' does not exist"), aName.c_str());
+ else
+ /* be more silent here */
+ return VBOX_E_OBJECT_NOT_FOUND;
+ }
+
+ aValue = it->second;
+
+ return S_OK;
+}
+
+HRESULT Medium::setProperty(const com::Utf8Str &aName,
+ const com::Utf8Str &aValue)
+{
+ AutoWriteLock mlock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Wait for a concurrently running Medium::i_queryInfo to complete. */
+ if (m->queryInfoRunning)
+ {
+ /* Must not hold the media tree lock, as Medium::i_queryInfo needs this
+ * lock and thus we would run into a deadlock here. */
+ Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+ while (m->queryInfoRunning)
+ {
+ mlock.release();
+ /* must not hold the object lock now */
+ Assert(!isWriteLockOnCurrentThread());
+ {
+ AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS);
+ }
+ mlock.acquire();
+ }
+ }
+
+ switch (m->state)
+ {
+ case MediumState_NotCreated:
+ case MediumState_Created:
+ case MediumState_Inaccessible:
+ break;
+ default:
+ return i_setStateError();
+ }
+
+ settings::StringsMap::iterator it = m->mapProperties.find(aName);
+ if ( !aName.startsWith("Special/")
+ && !i_isPropertyForFilter(aName))
+ {
+ if (it == m->mapProperties.end())
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Property '%s' does not exist"),
+ aName.c_str());
+ it->second = aValue;
+ }
+ else
+ {
+ if (it == m->mapProperties.end())
+ {
+ if (!aValue.isEmpty())
+ m->mapProperties[aName] = aValue;
+ }
+ else
+ {
+ if (!aValue.isEmpty())
+ it->second = aValue;
+ else
+ m->mapProperties.erase(it);
+ }
+ }
+
+ // save the settings
+ mlock.release();
+ i_markRegistriesModified();
+ m->pVirtualBox->i_saveModifiedRegistries();
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+
+ return S_OK;
+}
+
+HRESULT Medium::getProperties(const com::Utf8Str &aNames,
+ std::vector<com::Utf8Str> &aReturnNames,
+ std::vector<com::Utf8Str> &aReturnValues)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /// @todo make use of aNames according to the documentation
+ NOREF(aNames);
+
+ aReturnNames.resize(m->mapProperties.size());
+ aReturnValues.resize(m->mapProperties.size());
+ size_t i = 0;
+ for (settings::StringsMap::const_iterator it = m->mapProperties.begin();
+ it != m->mapProperties.end();
+ ++it, ++i)
+ {
+ aReturnNames[i] = it->first;
+ aReturnValues[i] = it->second;
+ }
+ return S_OK;
+}
+
+HRESULT Medium::setProperties(const std::vector<com::Utf8Str> &aNames,
+ const std::vector<com::Utf8Str> &aValues)
+{
+ AutoWriteLock mlock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* first pass: validate names */
+ for (size_t i = 0;
+ i < aNames.size();
+ ++i)
+ {
+ Utf8Str strName(aNames[i]);
+ if ( !strName.startsWith("Special/")
+ && !i_isPropertyForFilter(strName)
+ && m->mapProperties.find(strName) == m->mapProperties.end())
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Property '%s' does not exist"), strName.c_str());
+ }
+
+ /* second pass: assign */
+ for (size_t i = 0;
+ i < aNames.size();
+ ++i)
+ {
+ Utf8Str strName(aNames[i]);
+ Utf8Str strValue(aValues[i]);
+ settings::StringsMap::iterator it = m->mapProperties.find(strName);
+ if ( !strName.startsWith("Special/")
+ && !i_isPropertyForFilter(strName))
+ {
+ AssertReturn(it != m->mapProperties.end(), E_FAIL);
+ it->second = strValue;
+ }
+ else
+ {
+ if (it == m->mapProperties.end())
+ {
+ if (!strValue.isEmpty())
+ m->mapProperties[strName] = strValue;
+ }
+ else
+ {
+ if (!strValue.isEmpty())
+ it->second = strValue;
+ else
+ m->mapProperties.erase(it);
+ }
+ }
+ }
+
+ // save the settings
+ mlock.release();
+ i_markRegistriesModified();
+ m->pVirtualBox->i_saveModifiedRegistries();
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+
+ return S_OK;
+}
+
+HRESULT Medium::createBaseStorage(LONG64 aLogicalSize,
+ const std::vector<MediumVariant_T> &aVariant,
+ ComPtr<IProgress> &aProgress)
+{
+ if (aLogicalSize < 0)
+ return setError(E_INVALIDARG, tr("The medium size argument (%lld) is negative"), aLogicalSize);
+
+ HRESULT rc = S_OK;
+ ComObjPtr<Progress> pProgress;
+ Medium::Task *pTask = NULL;
+
+ try
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ULONG mediumVariantFlags = 0;
+
+ if (aVariant.size())
+ {
+ for (size_t i = 0; i < aVariant.size(); i++)
+ mediumVariantFlags |= (ULONG)aVariant[i];
+ }
+
+ mediumVariantFlags &= ((unsigned)~MediumVariant_Diff);
+
+ if ( !(mediumVariantFlags & MediumVariant_Fixed)
+ && !(m->formatObj->i_getCapabilities() & MediumFormatCapabilities_CreateDynamic))
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Medium format '%s' does not support dynamic storage creation"),
+ m->strFormat.c_str());
+
+ if ( (mediumVariantFlags & MediumVariant_Fixed)
+ && !(m->formatObj->i_getCapabilities() & MediumFormatCapabilities_CreateFixed))
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Medium format '%s' does not support fixed storage creation"),
+ m->strFormat.c_str());
+
+ if ( (mediumVariantFlags & MediumVariant_Formatted)
+ && i_getDeviceType() != DeviceType_Floppy)
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Medium variant 'formatted' applies to floppy images only"));
+
+ if (m->state != MediumState_NotCreated)
+ throw i_setStateError();
+
+ pProgress.createObject();
+ rc = pProgress->init(m->pVirtualBox,
+ static_cast<IMedium*>(this),
+ (mediumVariantFlags & MediumVariant_Fixed)
+ ? BstrFmt(tr("Creating fixed medium storage unit '%s'"), m->strLocationFull.c_str()).raw()
+ : BstrFmt(tr("Creating dynamic medium storage unit '%s'"), m->strLocationFull.c_str()).raw(),
+ TRUE /* aCancelable */);
+ if (FAILED(rc))
+ throw rc;
+
+ /* setup task object to carry out the operation asynchronously */
+ pTask = new Medium::CreateBaseTask(this, pProgress, (uint64_t)aLogicalSize,
+ (MediumVariant_T)mediumVariantFlags);
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+
+ m->state = MediumState_Creating;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ rc = pTask->createThread();
+ pTask = NULL;
+
+ if (SUCCEEDED(rc))
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+ }
+ else if (pTask != NULL)
+ delete pTask;
+
+ return rc;
+}
+
+HRESULT Medium::deleteStorage(ComPtr<IProgress> &aProgress)
+{
+ ComObjPtr<Progress> pProgress;
+
+ MultiResult mrc = i_deleteStorage(&pProgress,
+ false /* aWait */,
+ true /* aNotify */);
+ /* Must save the registries in any case, since an entry was removed. */
+ m->pVirtualBox->i_saveModifiedRegistries();
+
+ if (SUCCEEDED(mrc))
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+
+ return mrc;
+}
+
+HRESULT Medium::createDiffStorage(AutoCaller &autoCaller,
+ const ComPtr<IMedium> &aTarget,
+ const std::vector<MediumVariant_T> &aVariant,
+ ComPtr<IProgress> &aProgress)
+{
+ IMedium *aT = aTarget;
+ ComObjPtr<Medium> diff = static_cast<Medium*>(aT);
+
+ autoCaller.release();
+
+ /* It is possible that some previous/concurrent uninit has already cleared
+ * the pVirtualBox reference, see #uninit(). */
+ ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox);
+
+ // we access m->pParent
+ AutoReadLock treeLock(!pVirtualBox.isNull() ? &pVirtualBox->i_getMediaTreeLockHandle() : NULL COMMA_LOCKVAL_SRC_POS);
+
+ autoCaller.add();
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoMultiWriteLock2 alock(this, diff COMMA_LOCKVAL_SRC_POS);
+
+ if (m->type == MediumType_Writethrough)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium type of '%s' is Writethrough"),
+ m->strLocationFull.c_str());
+ else if (m->type == MediumType_Shareable)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium type of '%s' is Shareable"),
+ m->strLocationFull.c_str());
+ else if (m->type == MediumType_Readonly)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium type of '%s' is Readonly"),
+ m->strLocationFull.c_str());
+
+ /* Apply the normal locking logic to the entire chain. */
+ MediumLockList *pMediumLockList(new MediumLockList());
+ alock.release();
+ autoCaller.release();
+ treeLock.release();
+ HRESULT rc = diff->i_createMediumLockList(true /* fFailIfInaccessible */,
+ diff /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ this,
+ *pMediumLockList);
+ treeLock.acquire();
+ autoCaller.add();
+ if (FAILED(autoCaller.rc()))
+ rc = autoCaller.rc();
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ return rc;
+ }
+
+ alock.release();
+ autoCaller.release();
+ treeLock.release();
+ rc = pMediumLockList->Lock();
+ treeLock.acquire();
+ autoCaller.add();
+ if (FAILED(autoCaller.rc()))
+ rc = autoCaller.rc();
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+
+ return setError(rc, tr("Could not lock medium when creating diff '%s'"),
+ diff->i_getLocationFull().c_str());
+ }
+
+ Guid parentMachineRegistry;
+ if (i_getFirstRegistryMachineId(parentMachineRegistry))
+ {
+ /* since this medium has been just created it isn't associated yet */
+ diff->m->llRegistryIDs.push_back(parentMachineRegistry);
+ alock.release();
+ autoCaller.release();
+ treeLock.release();
+ diff->i_markRegistriesModified();
+ treeLock.acquire();
+ autoCaller.add();
+ alock.acquire();
+ }
+
+ alock.release();
+ autoCaller.release();
+ treeLock.release();
+
+ ComObjPtr<Progress> pProgress;
+
+ ULONG mediumVariantFlags = 0;
+
+ if (aVariant.size())
+ {
+ for (size_t i = 0; i < aVariant.size(); i++)
+ mediumVariantFlags |= (ULONG)aVariant[i];
+ }
+
+ if (mediumVariantFlags & MediumVariant_Formatted)
+ {
+ delete pMediumLockList;
+ return setError(VBOX_E_NOT_SUPPORTED,
+ tr("Medium variant 'formatted' applies to floppy images only"));
+ }
+
+ rc = i_createDiffStorage(diff, (MediumVariant_T)mediumVariantFlags, pMediumLockList,
+ &pProgress, false /* aWait */, true /* aNotify */);
+ if (FAILED(rc))
+ delete pMediumLockList;
+ else
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+
+ return rc;
+}
+
+HRESULT Medium::mergeTo(const ComPtr<IMedium> &aTarget,
+ ComPtr<IProgress> &aProgress)
+{
+ IMedium *aT = aTarget;
+
+ ComAssertRet(aT != this, E_INVALIDARG);
+
+ ComObjPtr<Medium> pTarget = static_cast<Medium*>(aT);
+
+ bool fMergeForward = false;
+ ComObjPtr<Medium> pParentForTarget;
+ MediumLockList *pChildrenToReparent = NULL;
+ MediumLockList *pMediumLockList = NULL;
+
+ HRESULT rc = S_OK;
+
+ rc = i_prepareMergeTo(pTarget, NULL, NULL, true, fMergeForward,
+ pParentForTarget, pChildrenToReparent, pMediumLockList);
+ if (FAILED(rc)) return rc;
+
+ ComObjPtr<Progress> pProgress;
+
+ rc = i_mergeTo(pTarget, fMergeForward, pParentForTarget, pChildrenToReparent,
+ pMediumLockList, &pProgress, false /* aWait */, true /* aNotify */);
+ if (FAILED(rc))
+ i_cancelMergeTo(pChildrenToReparent, pMediumLockList);
+ else
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+
+ return rc;
+}
+
+HRESULT Medium::cloneToBase(const ComPtr<IMedium> &aTarget,
+ const std::vector<MediumVariant_T> &aVariant,
+ ComPtr<IProgress> &aProgress)
+{
+ return cloneTo(aTarget, aVariant, NULL, aProgress);
+}
+
+HRESULT Medium::cloneTo(const ComPtr<IMedium> &aTarget,
+ const std::vector<MediumVariant_T> &aVariant,
+ const ComPtr<IMedium> &aParent,
+ ComPtr<IProgress> &aProgress)
+{
+ /** @todo r=jack: Remove redundancy. Call Medium::resizeAndCloneTo. */
+
+ /** @todo r=klaus The code below needs to be double checked with regard
+ * to lock order violations, it probably causes lock order issues related
+ * to the AutoCaller usage. */
+ ComAssertRet(aTarget != this, E_INVALIDARG);
+
+ IMedium *aT = aTarget;
+ ComObjPtr<Medium> pTarget = static_cast<Medium*>(aT);
+ ComObjPtr<Medium> pParent;
+ if (aParent)
+ {
+ IMedium *aP = aParent;
+ pParent = static_cast<Medium*>(aP);
+ }
+
+ HRESULT rc = S_OK;
+ ComObjPtr<Progress> pProgress;
+ Medium::Task *pTask = NULL;
+
+ try
+ {
+ // locking: we need the tree lock first because we access parent pointers
+ // and we need to write-lock the media involved
+ uint32_t cHandles = 3;
+ LockHandle* pHandles[4] = { &m->pVirtualBox->i_getMediaTreeLockHandle(),
+ this->lockHandle(),
+ pTarget->lockHandle() };
+ /* Only add parent to the lock if it is not null */
+ if (!pParent.isNull())
+ pHandles[cHandles++] = pParent->lockHandle();
+ AutoWriteLock alock(cHandles,
+ pHandles
+ COMMA_LOCKVAL_SRC_POS);
+
+ if ( pTarget->m->state != MediumState_NotCreated
+ && pTarget->m->state != MediumState_Created)
+ throw pTarget->i_setStateError();
+
+ /* Build the source lock list. */
+ MediumLockList *pSourceMediumLockList(new MediumLockList());
+ alock.release();
+ rc = i_createMediumLockList(true /* fFailIfInaccessible */,
+ NULL /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pSourceMediumLockList);
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ throw rc;
+ }
+
+ /* Build the target lock list (including the to-be parent chain). */
+ MediumLockList *pTargetMediumLockList(new MediumLockList());
+ alock.release();
+ rc = pTarget->i_createMediumLockList(true /* fFailIfInaccessible */,
+ pTarget /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ pParent,
+ *pTargetMediumLockList);
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ delete pTargetMediumLockList;
+ throw rc;
+ }
+
+ alock.release();
+ rc = pSourceMediumLockList->Lock();
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ delete pTargetMediumLockList;
+ throw setError(rc,
+ tr("Failed to lock source media '%s'"),
+ i_getLocationFull().c_str());
+ }
+ alock.release();
+ rc = pTargetMediumLockList->Lock();
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ delete pTargetMediumLockList;
+ throw setError(rc,
+ tr("Failed to lock target media '%s'"),
+ pTarget->i_getLocationFull().c_str());
+ }
+
+ pProgress.createObject();
+ rc = pProgress->init(m->pVirtualBox,
+ static_cast <IMedium *>(this),
+ BstrFmt(tr("Creating clone medium '%s'"), pTarget->m->strLocationFull.c_str()).raw(),
+ TRUE /* aCancelable */);
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ delete pTargetMediumLockList;
+ throw rc;
+ }
+
+ ULONG mediumVariantFlags = 0;
+
+ if (aVariant.size())
+ {
+ for (size_t i = 0; i < aVariant.size(); i++)
+ mediumVariantFlags |= (ULONG)aVariant[i];
+ }
+
+ if (mediumVariantFlags & MediumVariant_Formatted)
+ {
+ delete pSourceMediumLockList;
+ delete pTargetMediumLockList;
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Medium variant 'formatted' applies to floppy images only"));
+ }
+
+ /* setup task object to carry out the operation asynchronously */
+ pTask = new Medium::CloneTask(this, pProgress, pTarget,
+ (MediumVariant_T)mediumVariantFlags,
+ pParent, UINT32_MAX, UINT32_MAX,
+ pSourceMediumLockList, pTargetMediumLockList);
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+
+ if (pTarget->m->state == MediumState_NotCreated)
+ pTarget->m->state = MediumState_Creating;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(rc))
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+ }
+ else if (pTask != NULL)
+ delete pTask;
+
+ return rc;
+}
+
+/**
+ * This is a helper function that combines the functionality of
+ * Medium::cloneTo() and Medium::resize(). The target medium will take the
+ * contents of the calling medium.
+ *
+ * @param aTarget Medium to resize and clone to
+ * @param aLogicalSize Desired size for targer medium
+ * @param aVariant
+ * @param aParent
+ * @param aProgress
+ * @return HRESULT
+ */
+HRESULT Medium::resizeAndCloneTo(const ComPtr<IMedium> &aTarget,
+ LONG64 aLogicalSize,
+ const std::vector<MediumVariant_T> &aVariant,
+ const ComPtr<IMedium> &aParent,
+ ComPtr<IProgress> &aProgress)
+{
+ /* Check for valid args */
+ ComAssertRet(aTarget != this, E_INVALIDARG);
+ CheckComArgExpr(aLogicalSize, aLogicalSize >= 0);
+
+ /* Convert args to usable/needed types */
+ IMedium *aT = aTarget;
+ ComObjPtr<Medium> pTarget = static_cast<Medium*>(aT);
+ ComObjPtr<Medium> pParent;
+ if (aParent)
+ {
+ IMedium *aP = aParent;
+ pParent = static_cast<Medium*>(aP);
+ }
+
+ /* Set up variables. Fetch needed data in lockable blocks */
+ HRESULT rc = S_OK;
+ Medium::Task *pTask = NULL;
+
+ Utf8Str strSourceName;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ strSourceName = i_getName();
+ }
+
+ uint64_t uTargetExistingSize = 0;
+ Utf8Str strTargetName;
+ {
+ AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS);
+ uTargetExistingSize = pTarget->i_getLogicalSize();
+ strTargetName = pTarget->i_getName();
+ }
+
+ /* Set up internal multi-subprocess progress object */
+ ComObjPtr<Progress> pProgress;
+ pProgress.createObject();
+ rc = pProgress->init(m->pVirtualBox,
+ static_cast<IMedium*>(this),
+ BstrFmt(tr("Resizing medium and cloning into it")).raw(),
+ TRUE, /* aCancelable */
+ 2, /* Number of opearations */
+ BstrFmt(tr("Resizing medium before clone")).raw()
+ );
+
+ if (FAILED(rc))
+ return rc;
+
+ /* If target does not exist, handle resize. */
+ if (pTarget->m->state != MediumState_NotCreated && aLogicalSize > 0)
+ {
+ if ((LONG64)uTargetExistingSize != aLogicalSize) {
+ if (!i_isMediumFormatFile())
+ {
+ rc = setError(VBOX_E_NOT_SUPPORTED,
+ tr("Sizes of '%s' and '%s' are different and \
+ medium format does not support resing"),
+ strSourceName.c_str(), strTargetName.c_str());
+ return rc;
+ }
+
+ /**
+ * Need to lock the target medium as i_resize does do so
+ * automatically.
+ */
+
+ ComPtr<IToken> pToken;
+ rc = pTarget->LockWrite(pToken.asOutParam());
+
+ if (FAILED(rc)) return rc;
+
+ /**
+ * Have to make own lock list, because "resize" method resizes only
+ * last image in the lock chain.
+ */
+
+ MediumLockList* pMediumLockListForResize = new MediumLockList();
+ pMediumLockListForResize->Append(pTarget, pTarget->m->state == MediumState_LockedWrite);
+
+ rc = pMediumLockListForResize->Lock(true /* fSkipOverLockedMedia */);
+
+ if (FAILED(rc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ rc = setError(rc,
+ tr("Failed to lock the medium '%s' to resize before merge"),
+ strTargetName.c_str());
+ delete pMediumLockListForResize;
+ return rc;
+ }
+
+
+ rc = pTarget->i_resize((uint64_t)aLogicalSize, pMediumLockListForResize, &pProgress, true, false);
+
+ if (FAILED(rc))
+ {
+ /* No need to setError becasue i_resize and i_taskResizeHandler handle this automatically. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ delete pMediumLockListForResize;
+ return rc;
+ }
+
+ delete pMediumLockListForResize;
+
+ pTarget->m->logicalSize = (uint64_t)aLogicalSize;
+
+ pToken->Abandon();
+ pToken.setNull();
+ }
+ }
+
+ /* Report progress to supplied progress argument */
+ if (SUCCEEDED(rc))
+ {
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+ }
+
+ try
+ {
+ // locking: we need the tree lock first because we access parent pointers
+ // and we need to write-lock the media involved
+ uint32_t cHandles = 3;
+ LockHandle* pHandles[4] = { &m->pVirtualBox->i_getMediaTreeLockHandle(),
+ this->lockHandle(),
+ pTarget->lockHandle() };
+ /* Only add parent to the lock if it is not null */
+ if (!pParent.isNull())
+ pHandles[cHandles++] = pParent->lockHandle();
+ AutoWriteLock alock(cHandles,
+ pHandles
+ COMMA_LOCKVAL_SRC_POS);
+
+ if ( pTarget->m->state != MediumState_NotCreated
+ && pTarget->m->state != MediumState_Created)
+ throw pTarget->i_setStateError();
+
+ /* Build the source lock list. */
+ MediumLockList *pSourceMediumLockList(new MediumLockList());
+ alock.release();
+ rc = i_createMediumLockList(true /* fFailIfInaccessible */,
+ NULL /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pSourceMediumLockList);
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ throw rc;
+ }
+
+ /* Build the target lock list (including the to-be parent chain). */
+ MediumLockList *pTargetMediumLockList(new MediumLockList());
+ alock.release();
+ rc = pTarget->i_createMediumLockList(true /* fFailIfInaccessible */,
+ pTarget /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ pParent,
+ *pTargetMediumLockList);
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ delete pTargetMediumLockList;
+ throw rc;
+ }
+
+ alock.release();
+ rc = pSourceMediumLockList->Lock();
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ delete pTargetMediumLockList;
+ throw setError(rc,
+ tr("Failed to lock source media '%s'"),
+ i_getLocationFull().c_str());
+ }
+ alock.release();
+ rc = pTargetMediumLockList->Lock();
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ delete pTargetMediumLockList;
+ throw setError(rc,
+ tr("Failed to lock target media '%s'"),
+ pTarget->i_getLocationFull().c_str());
+ }
+
+ ULONG mediumVariantFlags = 0;
+
+ if (aVariant.size())
+ {
+ for (size_t i = 0; i < aVariant.size(); i++)
+ mediumVariantFlags |= (ULONG)aVariant[i];
+ }
+
+ if (mediumVariantFlags & MediumVariant_Formatted)
+ {
+ delete pSourceMediumLockList;
+ delete pTargetMediumLockList;
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Medium variant 'formatted' applies to floppy images only"));
+ }
+
+ if (pTarget->m->state != MediumState_NotCreated || aLogicalSize == 0)
+ {
+ /* setup task object to carry out the operation asynchronously */
+ pTask = new Medium::CloneTask(this, pProgress, pTarget,
+ (MediumVariant_T)mediumVariantFlags,
+ pParent, UINT32_MAX, UINT32_MAX,
+ pSourceMediumLockList, pTargetMediumLockList,
+ false, false, true, 0);
+ }
+ else
+ {
+ /* setup task object to carry out the operation asynchronously */
+ pTask = new Medium::CloneTask(this, pProgress, pTarget,
+ (MediumVariant_T)mediumVariantFlags,
+ pParent, UINT32_MAX, UINT32_MAX,
+ pSourceMediumLockList, pTargetMediumLockList,
+ false, false, true, (uint64_t)aLogicalSize);
+ }
+
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+
+ if (pTarget->m->state == MediumState_NotCreated)
+ pTarget->m->state = MediumState_Creating;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(rc))
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+ }
+ else if (pTask != NULL)
+ delete pTask;
+
+ return rc;
+}
+
+HRESULT Medium::moveTo(AutoCaller &autoCaller, const com::Utf8Str &aLocation, ComPtr<IProgress> &aProgress)
+{
+ ComObjPtr<Medium> pParent;
+ ComObjPtr<Progress> pProgress;
+ HRESULT rc = S_OK;
+ Medium::Task *pTask = NULL;
+
+ try
+ {
+ /// @todo NEWMEDIA for file names, add the default extension if no extension
+ /// is present (using the information from the VD backend which also implies
+ /// that one more parameter should be passed to moveTo() requesting
+ /// that functionality since it is only allowed when called from this method
+
+ /// @todo NEWMEDIA rename the file and set m->location on success, then save
+ /// the global registry (and local registries of portable VMs referring to
+ /// this medium), this will also require to add the mRegistered flag to data
+
+ autoCaller.release();
+
+ // locking: we need the tree lock first because we access parent pointers
+ // and we need to write-lock the media involved
+ AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* play with locations */
+ {
+ /* get source path and filename */
+ Utf8Str sourcePath = i_getLocationFull();
+ Utf8Str sourceFName = i_getName();
+
+ if (aLocation.isEmpty())
+ {
+ rc = setErrorVrc(VERR_PATH_ZERO_LENGTH,
+ tr("Medium '%s' can't be moved. Destination path is empty."),
+ i_getLocationFull().c_str());
+ throw rc;
+ }
+
+ /* extract destination path and filename */
+ Utf8Str destPath(aLocation);
+ Utf8Str destFName(destPath);
+ destFName.stripPath();
+
+ if (destFName.isNotEmpty() && !RTPathHasSuffix(destFName.c_str()))
+ {
+ /*
+ * The target path has no filename: Either "/path/to/new/location" or
+ * just "newname" (no trailing backslash or there is no filename extension).
+ */
+ if (destPath.equals(destFName))
+ {
+ /* new path contains only "newname", no path, no extension */
+ destFName.append(RTPathSuffix(sourceFName.c_str()));
+ destPath = destFName;
+ }
+ else
+ {
+ /* new path looks like "/path/to/new/location" */
+ destFName.setNull();
+ destPath.append(RTPATH_SLASH);
+ }
+ }
+
+ if (destFName.isEmpty())
+ {
+ /* No target name */
+ destPath.append(sourceFName);
+ }
+ else
+ {
+ if (destPath.equals(destFName))
+ {
+ /*
+ * The target path contains of only a filename without a directory.
+ * Move the medium within the source directory to the new name
+ * (actually rename operation).
+ * Scratches sourcePath!
+ */
+ destPath = sourcePath.stripFilename().append(RTPATH_SLASH).append(destFName);
+ }
+
+ const char *pszSuffix = RTPathSuffix(sourceFName.c_str());
+
+ /* Suffix is empty and one is deduced from the medium format */
+ if (pszSuffix == NULL)
+ {
+ Utf8Str strExt = i_getFormat();
+ if (strExt.compare("RAW", Utf8Str::CaseInsensitive) == 0)
+ {
+ DeviceType_T devType = i_getDeviceType();
+ switch (devType)
+ {
+ case DeviceType_DVD:
+ strExt = "iso";
+ break;
+ case DeviceType_Floppy:
+ strExt = "img";
+ break;
+ default:
+ rc = setErrorVrc(VERR_NOT_A_FILE, /** @todo r=bird: Mixing status codes again. */
+ tr("Medium '%s' has RAW type. \"Move\" operation isn't supported for this type."),
+ i_getLocationFull().c_str());
+ throw rc;
+ }
+ }
+ else if (strExt.compare("Parallels", Utf8Str::CaseInsensitive) == 0)
+ {
+ strExt = "hdd";
+ }
+
+ /* Set the target extension like on the source. Any conversions are prohibited */
+ strExt.toLower();
+ destPath.stripSuffix().append('.').append(strExt);
+ }
+ else
+ destPath.stripSuffix().append(pszSuffix);
+ }
+
+ /* Simple check for existence */
+ if (RTFileExists(destPath.c_str()))
+ {
+ rc = setError(VBOX_E_FILE_ERROR,
+ tr("The given path '%s' is an existing file. Delete or rename this file."),
+ destPath.c_str());
+ throw rc;
+ }
+
+ if (!i_isMediumFormatFile())
+ {
+ rc = setErrorVrc(VERR_NOT_A_FILE,
+ tr("Medium '%s' isn't a file object. \"Move\" operation isn't supported."),
+ i_getLocationFull().c_str());
+ throw rc;
+ }
+ /* Path must be absolute */
+ if (!RTPathStartsWithRoot(destPath.c_str()))
+ {
+ rc = setError(VBOX_E_FILE_ERROR,
+ tr("The given path '%s' is not fully qualified"),
+ destPath.c_str());
+ throw rc;
+ }
+ /* Check path for a new file object */
+ rc = VirtualBox::i_ensureFilePathExists(destPath, true);
+ if (FAILED(rc))
+ throw rc;
+
+ /* Set needed variables for "moving" procedure. It'll be used later in separate thread task */
+ rc = i_preparationForMoving(destPath);
+ if (FAILED(rc))
+ {
+ rc = setErrorVrc(VERR_NO_CHANGE,
+ tr("Medium '%s' is already in the correct location"),
+ i_getLocationFull().c_str());
+ throw rc;
+ }
+ }
+
+ /* Check VMs which have this medium attached to*/
+ std::vector<com::Guid> aMachineIds;
+ rc = getMachineIds(aMachineIds);
+ std::vector<com::Guid>::const_iterator currMachineID = aMachineIds.begin();
+ std::vector<com::Guid>::const_iterator lastMachineID = aMachineIds.end();
+
+ while (currMachineID != lastMachineID)
+ {
+ Guid id(*currMachineID);
+ ComObjPtr<Machine> aMachine;
+
+ alock.release();
+ autoCaller.release();
+ treeLock.release();
+ rc = m->pVirtualBox->i_findMachine(id, false, true, &aMachine);
+ treeLock.acquire();
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+ alock.acquire();
+
+ if (SUCCEEDED(rc))
+ {
+ ComObjPtr<SessionMachine> sm;
+ ComPtr<IInternalSessionControl> ctl;
+
+ alock.release();
+ autoCaller.release();
+ treeLock.release();
+ bool ses = aMachine->i_isSessionOpenVM(sm, &ctl);
+ treeLock.acquire();
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+ alock.acquire();
+
+ if (ses)
+ {
+ rc = setError(VBOX_E_INVALID_VM_STATE,
+ tr("At least the VM '%s' to whom this medium '%s' attached has currently an opened session. Stop all VMs before relocating this medium"),
+ id.toString().c_str(),
+ i_getLocationFull().c_str());
+ throw rc;
+ }
+ }
+ ++currMachineID;
+ }
+
+ /* Build the source lock list. */
+ MediumLockList *pMediumLockList(new MediumLockList());
+ alock.release();
+ autoCaller.release();
+ treeLock.release();
+ rc = i_createMediumLockList(true /* fFailIfInaccessible */,
+ this /* pToLockWrite */,
+ true /* fMediumLockWriteAll */,
+ NULL,
+ *pMediumLockList);
+ treeLock.acquire();
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw setError(rc,
+ tr("Failed to create medium lock list for '%s'"),
+ i_getLocationFull().c_str());
+ }
+ alock.release();
+ autoCaller.release();
+ treeLock.release();
+ rc = pMediumLockList->Lock();
+ treeLock.acquire();
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw setError(rc,
+ tr("Failed to lock media '%s'"),
+ i_getLocationFull().c_str());
+ }
+
+ pProgress.createObject();
+ rc = pProgress->init(m->pVirtualBox,
+ static_cast <IMedium *>(this),
+ BstrFmt(tr("Moving medium '%s'"), m->strLocationFull.c_str()).raw(),
+ TRUE /* aCancelable */);
+
+ /* Do the disk moving. */
+ if (SUCCEEDED(rc))
+ {
+ ULONG mediumVariantFlags = i_getVariant();
+
+ /* setup task object to carry out the operation asynchronously */
+ pTask = new Medium::MoveTask(this, pProgress,
+ (MediumVariant_T)mediumVariantFlags,
+ pMediumLockList);
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(rc))
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+ }
+ else
+ {
+ if (pTask)
+ delete pTask;
+ }
+
+ return rc;
+}
+
+HRESULT Medium::setLocation(const com::Utf8Str &aLocation)
+{
+ HRESULT rc = S_OK;
+
+ try
+ {
+ // locking: we need the tree lock first because we access parent pointers
+ // and we need to write-lock the media involved
+ AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ AutoCaller autoCaller(this);
+ AssertComRCThrowRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Utf8Str destPath(aLocation);
+
+ // some check for file based medium
+ if (i_isMediumFormatFile())
+ {
+ /* Path must be absolute */
+ if (!RTPathStartsWithRoot(destPath.c_str()))
+ {
+ rc = setError(VBOX_E_FILE_ERROR,
+ tr("The given path '%s' is not fully qualified"),
+ destPath.c_str());
+ throw rc;
+ }
+
+ /* Simple check for existence */
+ if (!RTFileExists(destPath.c_str()))
+ {
+ rc = setError(VBOX_E_FILE_ERROR,
+ tr("The given path '%s' is not an existing file. New location is invalid."),
+ destPath.c_str());
+ throw rc;
+ }
+ }
+
+ /* Check VMs which have this medium attached to*/
+ std::vector<com::Guid> aMachineIds;
+ rc = getMachineIds(aMachineIds);
+
+ // switch locks only if there are machines with this medium attached
+ if (!aMachineIds.empty())
+ {
+ std::vector<com::Guid>::const_iterator currMachineID = aMachineIds.begin();
+ std::vector<com::Guid>::const_iterator lastMachineID = aMachineIds.end();
+
+ alock.release();
+ autoCaller.release();
+ treeLock.release();
+
+ while (currMachineID != lastMachineID)
+ {
+ Guid id(*currMachineID);
+ ComObjPtr<Machine> aMachine;
+ rc = m->pVirtualBox->i_findMachine(id, false, true, &aMachine);
+ if (SUCCEEDED(rc))
+ {
+ ComObjPtr<SessionMachine> sm;
+ ComPtr<IInternalSessionControl> ctl;
+
+ bool ses = aMachine->i_isSessionOpenVM(sm, &ctl);
+ if (ses)
+ {
+ treeLock.acquire();
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+ alock.acquire();
+
+ rc = setError(VBOX_E_INVALID_VM_STATE,
+ tr("At least the VM '%s' to whom this medium '%s' attached has currently an opened session. Stop all VMs before set location for this medium"),
+ id.toString().c_str(),
+ i_getLocationFull().c_str());
+ throw rc;
+ }
+ }
+ ++currMachineID;
+ }
+
+ treeLock.acquire();
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+ alock.acquire();
+ }
+
+ m->strLocationFull = destPath;
+
+ // save the settings
+ alock.release();
+ autoCaller.release();
+ treeLock.release();
+
+ i_markRegistriesModified();
+ m->pVirtualBox->i_saveModifiedRegistries();
+
+ MediumState_T mediumState;
+ refreshState(autoCaller, &mediumState);
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ return rc;
+}
+
+HRESULT Medium::compact(ComPtr<IProgress> &aProgress)
+{
+ HRESULT rc = S_OK;
+ ComObjPtr<Progress> pProgress;
+ Medium::Task *pTask = NULL;
+
+ try
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Build the medium lock list. */
+ MediumLockList *pMediumLockList(new MediumLockList());
+ alock.release();
+ rc = i_createMediumLockList(true /* fFailIfInaccessible */ ,
+ this /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pMediumLockList);
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw rc;
+ }
+
+ alock.release();
+ rc = pMediumLockList->Lock();
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw setError(rc,
+ tr("Failed to lock media when compacting '%s'"),
+ i_getLocationFull().c_str());
+ }
+
+ pProgress.createObject();
+ rc = pProgress->init(m->pVirtualBox,
+ static_cast <IMedium *>(this),
+ BstrFmt(tr("Compacting medium '%s'"), m->strLocationFull.c_str()).raw(),
+ TRUE /* aCancelable */);
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw rc;
+ }
+
+ /* setup task object to carry out the operation asynchronously */
+ pTask = new Medium::CompactTask(this, pProgress, pMediumLockList);
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(rc))
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+ }
+ else if (pTask != NULL)
+ delete pTask;
+
+ return rc;
+}
+
+HRESULT Medium::resize(LONG64 aLogicalSize,
+ ComPtr<IProgress> &aProgress)
+{
+ CheckComArgExpr(aLogicalSize, aLogicalSize > 0);
+ HRESULT rc = S_OK;
+ ComObjPtr<Progress> pProgress;
+
+ /* Build the medium lock list. */
+ MediumLockList *pMediumLockList(new MediumLockList());
+
+ try
+ {
+ const char *pszError = NULL;
+
+ rc = i_createMediumLockList(true /* fFailIfInaccessible */ ,
+ this /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pMediumLockList);
+ if (FAILED(rc))
+ {
+ pszError = tr("Failed to create medium lock list when resizing '%s'");
+ }
+ else
+ {
+ rc = pMediumLockList->Lock();
+ if (FAILED(rc))
+ pszError = tr("Failed to lock media when resizing '%s'");
+ }
+
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (FAILED(rc))
+ {
+ throw setError(rc, pszError, i_getLocationFull().c_str());
+ }
+
+ pProgress.createObject();
+ rc = pProgress->init(m->pVirtualBox,
+ static_cast <IMedium *>(this),
+ BstrFmt(tr("Resizing medium '%s'"), m->strLocationFull.c_str()).raw(),
+ TRUE /* aCancelable */);
+ if (FAILED(rc))
+ {
+ throw rc;
+ }
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ rc = i_resize((uint64_t)aLogicalSize, pMediumLockList, &pProgress, false /* aWait */, true /* aNotify */);
+
+ if (SUCCEEDED(rc))
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+ else
+ delete pMediumLockList;
+
+ return rc;
+}
+
+HRESULT Medium::reset(AutoCaller &autoCaller, ComPtr<IProgress> &aProgress)
+{
+ HRESULT rc = S_OK;
+ ComObjPtr<Progress> pProgress;
+ Medium::Task *pTask = NULL;
+
+ try
+ {
+ autoCaller.release();
+
+ /* It is possible that some previous/concurrent uninit has already
+ * cleared the pVirtualBox reference, see #uninit(). */
+ ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox);
+
+ /* i_canClose() needs the tree lock */
+ AutoMultiWriteLock2 multilock(!pVirtualBox.isNull() ? &pVirtualBox->i_getMediaTreeLockHandle() : NULL,
+ this->lockHandle()
+ COMMA_LOCKVAL_SRC_POS);
+
+ autoCaller.add();
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFlowThisFunc(("ENTER for medium %s\n", m->strLocationFull.c_str()));
+
+ if (m->pParent.isNull())
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Medium type of '%s' is not differencing"),
+ m->strLocationFull.c_str());
+
+ rc = i_canClose();
+ if (FAILED(rc))
+ throw rc;
+
+ /* Build the medium lock list. */
+ MediumLockList *pMediumLockList(new MediumLockList());
+ multilock.release();
+ rc = i_createMediumLockList(true /* fFailIfInaccessible */,
+ this /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pMediumLockList);
+ multilock.acquire();
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw rc;
+ }
+
+ multilock.release();
+ rc = pMediumLockList->Lock();
+ multilock.acquire();
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw setError(rc,
+ tr("Failed to lock media when resetting '%s'"),
+ i_getLocationFull().c_str());
+ }
+
+ pProgress.createObject();
+ rc = pProgress->init(m->pVirtualBox,
+ static_cast<IMedium*>(this),
+ BstrFmt(tr("Resetting differencing medium '%s'"), m->strLocationFull.c_str()).raw(),
+ FALSE /* aCancelable */);
+ if (FAILED(rc))
+ throw rc;
+
+ /* setup task object to carry out the operation asynchronously */
+ pTask = new Medium::ResetTask(this, pProgress, pMediumLockList);
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(rc))
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+ }
+ else if (pTask != NULL)
+ delete pTask;
+
+ LogFlowThisFunc(("LEAVE, rc=%Rhrc\n", rc));
+
+ return rc;
+}
+
+HRESULT Medium::changeEncryption(const com::Utf8Str &aCurrentPassword, const com::Utf8Str &aCipher,
+ const com::Utf8Str &aNewPassword, const com::Utf8Str &aNewPasswordId,
+ ComPtr<IProgress> &aProgress)
+{
+ HRESULT rc = S_OK;
+ ComObjPtr<Progress> pProgress;
+ Medium::Task *pTask = NULL;
+
+ try
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ DeviceType_T devType = i_getDeviceType();
+ /* Cannot encrypt DVD or floppy images so far. */
+ if ( devType == DeviceType_DVD
+ || devType == DeviceType_Floppy)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot encrypt DVD or Floppy medium '%s'"),
+ m->strLocationFull.c_str());
+
+ /* Cannot encrypt media which are attached to more than one virtual machine. */
+ if (m->backRefs.size() > 1)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot encrypt medium '%s' because it is attached to %d virtual machines", "", m->backRefs.size()),
+ m->strLocationFull.c_str(), m->backRefs.size());
+
+ if (i_getChildren().size() != 0)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot encrypt medium '%s' because it has %d children", "", i_getChildren().size()),
+ m->strLocationFull.c_str(), i_getChildren().size());
+
+ /* Build the medium lock list. */
+ MediumLockList *pMediumLockList(new MediumLockList());
+ alock.release();
+ rc = i_createMediumLockList(true /* fFailIfInaccessible */ ,
+ this /* pToLockWrite */,
+ true /* fMediumLockAllWrite */,
+ NULL,
+ *pMediumLockList);
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw rc;
+ }
+
+ alock.release();
+ rc = pMediumLockList->Lock();
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw setError(rc,
+ tr("Failed to lock media for encryption '%s'"),
+ i_getLocationFull().c_str());
+ }
+
+ /*
+ * Check all media in the chain to not contain any branches or references to
+ * other virtual machines, we support encrypting only a list of differencing media at the moment.
+ */
+ MediumLockList::Base::const_iterator mediumListBegin = pMediumLockList->GetBegin();
+ MediumLockList::Base::const_iterator mediumListEnd = pMediumLockList->GetEnd();
+ for (MediumLockList::Base::const_iterator it = mediumListBegin;
+ it != mediumListEnd;
+ ++it)
+ {
+ const MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+ AutoReadLock mediumReadLock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ Assert(pMedium->m->state == MediumState_LockedWrite);
+
+ if (pMedium->m->backRefs.size() > 1)
+ {
+ rc = setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot encrypt medium '%s' because it is attached to %d virtual machines", "",
+ pMedium->m->backRefs.size()),
+ pMedium->m->strLocationFull.c_str(), pMedium->m->backRefs.size());
+ break;
+ }
+ else if (pMedium->i_getChildren().size() > 1)
+ {
+ rc = setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot encrypt medium '%s' because it has %d children", "", pMedium->i_getChildren().size()),
+ pMedium->m->strLocationFull.c_str(), pMedium->i_getChildren().size());
+ break;
+ }
+ }
+
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw rc;
+ }
+
+ const char *pszAction = tr("Encrypting medium");
+ if ( aCurrentPassword.isNotEmpty()
+ && aCipher.isEmpty())
+ pszAction = tr("Decrypting medium");
+
+ pProgress.createObject();
+ rc = pProgress->init(m->pVirtualBox,
+ static_cast <IMedium *>(this),
+ BstrFmt("%s '%s'", pszAction, m->strLocationFull.c_str()).raw(),
+ TRUE /* aCancelable */);
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw rc;
+ }
+
+ /* setup task object to carry out the operation asynchronously */
+ pTask = new Medium::EncryptTask(this, aNewPassword, aCurrentPassword,
+ aCipher, aNewPasswordId, pProgress, pMediumLockList);
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(rc))
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+ }
+ else if (pTask != NULL)
+ delete pTask;
+
+ return rc;
+}
+
+HRESULT Medium::getEncryptionSettings(AutoCaller &autoCaller, com::Utf8Str &aCipher, com::Utf8Str &aPasswordId)
+{
+#ifndef VBOX_WITH_EXTPACK
+ RT_NOREF(aCipher, aPasswordId);
+#endif
+ HRESULT rc = S_OK;
+
+ try
+ {
+ autoCaller.release();
+ ComObjPtr<Medium> pBase = i_getBase();
+ autoCaller.add();
+ if (FAILED(autoCaller.rc()))
+ throw rc;
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Check whether encryption is configured for this medium. */
+ settings::StringsMap::iterator it = pBase->m->mapProperties.find("CRYPT/KeyStore");
+ if (it == pBase->m->mapProperties.end())
+ throw VBOX_E_NOT_SUPPORTED;
+
+# ifdef VBOX_WITH_EXTPACK
+ ExtPackManager *pExtPackManager = m->pVirtualBox->i_getExtPackManager();
+ if (pExtPackManager->i_isExtPackUsable(ORACLE_PUEL_EXTPACK_NAME))
+ {
+ /* Load the plugin */
+ Utf8Str strPlugin;
+ rc = pExtPackManager->i_getLibraryPathForExtPack(g_szVDPlugin, ORACLE_PUEL_EXTPACK_NAME, &strPlugin);
+ if (SUCCEEDED(rc))
+ {
+ int vrc = VDPluginLoadFromFilename(strPlugin.c_str());
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc,
+ tr("Retrieving encryption settings of the image failed because the encryption plugin could not be loaded (%s)"),
+ i_vdError(vrc).c_str());
+ }
+ else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Encryption is not supported because the extension pack '%s' is missing the encryption plugin (old extension pack installed?)"),
+ ORACLE_PUEL_EXTPACK_NAME);
+ }
+ else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Encryption is not supported because the extension pack '%s' is missing"),
+ ORACLE_PUEL_EXTPACK_NAME);
+
+ PVDISK pDisk = NULL;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &pDisk);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ MediumCryptoFilterSettings CryptoSettings;
+
+ i_taskEncryptSettingsSetup(&CryptoSettings, NULL, it->second.c_str(), NULL, false /* fCreateKeyStore */);
+ vrc = VDFilterAdd(pDisk, "CRYPT", VD_FILTER_FLAGS_READ | VD_FILTER_FLAGS_INFO, CryptoSettings.vdFilterIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_INVALID_OBJECT_STATE, vrc,
+ tr("Failed to load the encryption filter: %s"),
+ i_vdError(vrc).c_str());
+
+ it = pBase->m->mapProperties.find("CRYPT/KeyId");
+ if (it == pBase->m->mapProperties.end())
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Image is configured for encryption but doesn't has a KeyId set"));
+
+ aPasswordId = it->second.c_str();
+ aCipher = CryptoSettings.pszCipherReturned;
+ RTStrFree(CryptoSettings.pszCipherReturned);
+
+ VDDestroy(pDisk);
+# else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Encryption is not supported because extension pack support is not built in"));
+# endif
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ return rc;
+}
+
+HRESULT Medium::checkEncryptionPassword(const com::Utf8Str &aPassword)
+{
+ HRESULT rc = S_OK;
+
+ try
+ {
+ ComObjPtr<Medium> pBase = i_getBase();
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ settings::StringsMap::iterator it = pBase->m->mapProperties.find("CRYPT/KeyStore");
+ if (it == pBase->m->mapProperties.end())
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("The image is not configured for encryption"));
+
+ if (aPassword.isEmpty())
+ throw setError(E_INVALIDARG,
+ tr("The given password must not be empty"));
+
+# ifdef VBOX_WITH_EXTPACK
+ ExtPackManager *pExtPackManager = m->pVirtualBox->i_getExtPackManager();
+ if (pExtPackManager->i_isExtPackUsable(ORACLE_PUEL_EXTPACK_NAME))
+ {
+ /* Load the plugin */
+ Utf8Str strPlugin;
+ rc = pExtPackManager->i_getLibraryPathForExtPack(g_szVDPlugin, ORACLE_PUEL_EXTPACK_NAME, &strPlugin);
+ if (SUCCEEDED(rc))
+ {
+ int vrc = VDPluginLoadFromFilename(strPlugin.c_str());
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc,
+ tr("Retrieving encryption settings of the image failed because the encryption plugin could not be loaded (%s)"),
+ i_vdError(vrc).c_str());
+ }
+ else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Encryption is not supported because the extension pack '%s' is missing the encryption plugin (old extension pack installed?)"),
+ ORACLE_PUEL_EXTPACK_NAME);
+ }
+ else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Encryption is not supported because the extension pack '%s' is missing"),
+ ORACLE_PUEL_EXTPACK_NAME);
+
+ PVDISK pDisk = NULL;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &pDisk);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ MediumCryptoFilterSettings CryptoSettings;
+
+ i_taskEncryptSettingsSetup(&CryptoSettings, NULL, it->second.c_str(), aPassword.c_str(),
+ false /* fCreateKeyStore */);
+ vrc = VDFilterAdd(pDisk, "CRYPT", VD_FILTER_FLAGS_READ, CryptoSettings.vdFilterIfaces);
+ if (vrc == VERR_VD_PASSWORD_INCORRECT)
+ throw setError(VBOX_E_PASSWORD_INCORRECT,
+ tr("The given password is incorrect"));
+ else if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_INVALID_OBJECT_STATE, vrc,
+ tr("Failed to load the encryption filter: %s"),
+ i_vdError(vrc).c_str());
+
+ VDDestroy(pDisk);
+# else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Encryption is not supported because extension pack support is not built in"));
+# endif
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ return rc;
+}
+
+HRESULT Medium::openForIO(BOOL aWritable, com::Utf8Str const &aPassword, ComPtr<IMediumIO> &aMediumIO)
+{
+ /*
+ * Input validation.
+ */
+ if (aWritable && i_isReadOnly())
+ return setError(E_ACCESSDENIED, tr("Write access denied: read-only"));
+
+ com::Utf8Str const strKeyId = i_getKeyId();
+ if (strKeyId.isEmpty() && aPassword.isNotEmpty())
+ return setError(E_INVALIDARG, tr("Password given for unencrypted medium"));
+ if (strKeyId.isNotEmpty() && aPassword.isEmpty())
+ return setError(E_INVALIDARG, tr("Password needed for encrypted medium"));
+
+ /*
+ * Create IO object and return it.
+ */
+ ComObjPtr<MediumIO> ptrIO;
+ HRESULT hrc = ptrIO.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = ptrIO->initForMedium(this, m->pVirtualBox, aWritable != FALSE, strKeyId, aPassword);
+ if (SUCCEEDED(hrc))
+ ptrIO.queryInterfaceTo(aMediumIO.asOutParam());
+ }
+ return hrc;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Medium public internal methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Internal method to return the medium's parent medium. Must have caller + locking!
+ * @return
+ */
+const ComObjPtr<Medium>& Medium::i_getParent() const
+{
+ return m->pParent;
+}
+
+/**
+ * Internal method to return the medium's list of child media. Must have caller + locking!
+ * @return
+ */
+const MediaList& Medium::i_getChildren() const
+{
+ return m->llChildren;
+}
+
+/**
+ * Internal method to return the medium's GUID. Must have caller + locking!
+ * @return
+ */
+const Guid& Medium::i_getId() const
+{
+ return m->id;
+}
+
+/**
+ * Internal method to return the medium's state. Must have caller + locking!
+ * @return
+ */
+MediumState_T Medium::i_getState() const
+{
+ return m->state;
+}
+
+/**
+ * Internal method to return the medium's variant. Must have caller + locking!
+ * @return
+ */
+MediumVariant_T Medium::i_getVariant() const
+{
+ return m->variant;
+}
+
+/**
+ * Internal method which returns true if this medium represents a host drive.
+ * @return
+ */
+bool Medium::i_isHostDrive() const
+{
+ return m->hostDrive;
+}
+
+/**
+ * Internal method to return the medium's full location. Must have caller + locking!
+ * @return
+ */
+const Utf8Str& Medium::i_getLocationFull() const
+{
+ return m->strLocationFull;
+}
+
+/**
+ * Internal method to return the medium's format string. Must have caller + locking!
+ * @return
+ */
+const Utf8Str& Medium::i_getFormat() const
+{
+ return m->strFormat;
+}
+
+/**
+ * Internal method to return the medium's format object. Must have caller + locking!
+ * @return
+ */
+const ComObjPtr<MediumFormat>& Medium::i_getMediumFormat() const
+{
+ return m->formatObj;
+}
+
+/**
+ * Internal method that returns true if the medium is represented by a file on the host disk
+ * (and not iSCSI or something).
+ * @return
+ */
+bool Medium::i_isMediumFormatFile() const
+{
+ if ( m->formatObj
+ && (m->formatObj->i_getCapabilities() & MediumFormatCapabilities_File)
+ )
+ return true;
+ return false;
+}
+
+/**
+ * Internal method to return the medium's size. Must have caller + locking!
+ * @return
+ */
+uint64_t Medium::i_getSize() const
+{
+ return m->size;
+}
+
+/**
+ * Internal method to return the medium's size. Must have caller + locking!
+ * @return
+ */
+uint64_t Medium::i_getLogicalSize() const
+{
+ return m->logicalSize;
+}
+
+/**
+ * Returns the medium device type. Must have caller + locking!
+ * @return
+ */
+DeviceType_T Medium::i_getDeviceType() const
+{
+ return m->devType;
+}
+
+/**
+ * Returns the medium type. Must have caller + locking!
+ * @return
+ */
+MediumType_T Medium::i_getType() const
+{
+ return m->type;
+}
+
+/**
+ * Returns a short version of the location attribute.
+ *
+ * @note Must be called from under this object's read or write lock.
+ */
+Utf8Str Medium::i_getName()
+{
+ Utf8Str name = RTPathFilename(m->strLocationFull.c_str());
+ return name;
+}
+
+/**
+ * Same as i_addRegistry() except that we don't check the object state, making
+ * it safe to call with initFromSettings() on the call stack.
+ */
+bool Medium::i_addRegistryNoCallerCheck(const Guid &id)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ bool fAdd = true;
+
+ // hard disks cannot be in more than one registry
+ if ( m->devType == DeviceType_HardDisk
+ && m->llRegistryIDs.size() > 0)
+ fAdd = false;
+
+ // no need to add the UUID twice
+ if (fAdd)
+ {
+ for (GuidList::const_iterator it = m->llRegistryIDs.begin();
+ it != m->llRegistryIDs.end();
+ ++it)
+ {
+ if ((*it) == id)
+ {
+ fAdd = false;
+ break;
+ }
+ }
+ }
+
+ if (fAdd)
+ m->llRegistryIDs.push_back(id);
+
+ return fAdd;
+}
+
+/**
+ * This adds the given UUID to the list of media registries in which this
+ * medium should be registered. The UUID can either be a machine UUID,
+ * to add a machine registry, or the global registry UUID as returned by
+ * VirtualBox::getGlobalRegistryId().
+ *
+ * Note that for hard disks, this method does nothing if the medium is
+ * already in another registry to avoid having hard disks in more than
+ * one registry, which causes trouble with keeping diff images in sync.
+ * See getFirstRegistryMachineId() for details.
+ *
+ * @param id
+ * @return true if the registry was added; false if the given id was already on the list.
+ */
+bool Medium::i_addRegistry(const Guid &id)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return false;
+ return i_addRegistryNoCallerCheck(id);
+}
+
+/**
+ * This adds the given UUID to the list of media registries in which this
+ * medium should be registered. The UUID can either be a machine UUID,
+ * to add a machine registry, or the global registry UUID as returned by
+ * VirtualBox::getGlobalRegistryId(). Thisis applied to all children.
+ *
+ * Note that for hard disks, this method does nothing if the medium is
+ * already in another registry to avoid having hard disks in more than
+ * one registry, which causes trouble with keeping diff images in sync.
+ * See getFirstRegistryMachineId() for details.
+ *
+ * @note the caller must hold the media tree lock for reading.
+ *
+ * @param id
+ * @return true if the registry was added; false if the given id was already on the list.
+ */
+bool Medium::i_addRegistryAll(const Guid &id)
+{
+ MediaList llMediaTodo;
+ llMediaTodo.push_back(this);
+
+ bool fAdd = false;
+
+ while (!llMediaTodo.empty())
+ {
+ ComObjPtr<Medium> pMedium = llMediaTodo.front();
+ llMediaTodo.pop_front();
+
+ AutoCaller mediumCaller(pMedium);
+ if (FAILED(mediumCaller.rc())) continue;
+
+ fAdd |= pMedium->i_addRegistryNoCallerCheck(id);
+
+ // protected by the medium tree lock held by our original caller
+ MediaList::const_iterator itBegin = pMedium->i_getChildren().begin();
+ MediaList::const_iterator itEnd = pMedium->i_getChildren().end();
+ for (MediaList::const_iterator it = itBegin; it != itEnd; ++it)
+ llMediaTodo.push_back(*it);
+ }
+
+ return fAdd;
+}
+
+/**
+ * Removes the given UUID from the list of media registry UUIDs of this medium.
+ *
+ * @param id
+ * @return true if the UUID was found or false if not.
+ */
+bool Medium::i_removeRegistry(const Guid &id)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return false;
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ bool fRemove = false;
+
+ /// @todo r=klaus eliminate this code, replace it by using find.
+ for (GuidList::iterator it = m->llRegistryIDs.begin();
+ it != m->llRegistryIDs.end();
+ ++it)
+ {
+ if ((*it) == id)
+ {
+ // getting away with this as the iterator isn't used after
+ m->llRegistryIDs.erase(it);
+ fRemove = true;
+ break;
+ }
+ }
+
+ return fRemove;
+}
+
+/**
+ * Removes the given UUID from the list of media registry UUIDs, for this
+ * medium and all its children.
+ *
+ * @note the caller must hold the media tree lock for reading.
+ *
+ * @param id
+ * @return true if the UUID was found or false if not.
+ */
+bool Medium::i_removeRegistryAll(const Guid &id)
+{
+ MediaList llMediaTodo;
+ llMediaTodo.push_back(this);
+
+ bool fRemove = false;
+
+ while (!llMediaTodo.empty())
+ {
+ ComObjPtr<Medium> pMedium = llMediaTodo.front();
+ llMediaTodo.pop_front();
+
+ AutoCaller mediumCaller(pMedium);
+ if (FAILED(mediumCaller.rc())) continue;
+
+ fRemove |= pMedium->i_removeRegistry(id);
+
+ // protected by the medium tree lock held by our original caller
+ MediaList::const_iterator itBegin = pMedium->i_getChildren().begin();
+ MediaList::const_iterator itEnd = pMedium->i_getChildren().end();
+ for (MediaList::const_iterator it = itBegin; it != itEnd; ++it)
+ llMediaTodo.push_back(*it);
+ }
+
+ return fRemove;
+}
+
+/**
+ * Returns true if id is in the list of media registries for this medium.
+ *
+ * Must have caller + read locking!
+ *
+ * @param id
+ * @return
+ */
+bool Medium::i_isInRegistry(const Guid &id)
+{
+ /// @todo r=klaus eliminate this code, replace it by using find.
+ for (GuidList::const_iterator it = m->llRegistryIDs.begin();
+ it != m->llRegistryIDs.end();
+ ++it)
+ {
+ if (*it == id)
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Internal method to return the medium's first registry machine (i.e. the machine in whose
+ * machine XML this medium is listed).
+ *
+ * Every attached medium must now (4.0) reside in at least one media registry, which is identified
+ * by a UUID. This is either a machine UUID if the machine is from 4.0 or newer, in which case
+ * machines have their own media registries, or it is the pseudo-UUID of the VirtualBox
+ * object if the machine is old and still needs the global registry in VirtualBox.xml.
+ *
+ * By definition, hard disks may only be in one media registry, in which all its children
+ * will be stored as well. Otherwise we run into problems with having keep multiple registries
+ * in sync. (This is the "cloned VM" case in which VM1 may link to the disks of VM2; in this
+ * case, only VM2's registry is used for the disk in question.)
+ *
+ * If there is no medium registry, particularly if the medium has not been attached yet, this
+ * does not modify uuid and returns false.
+ *
+ * ISOs and RAWs, by contrast, can be in more than one repository to make things easier for
+ * the user.
+ *
+ * Must have caller + locking!
+ *
+ * @param uuid Receives first registry machine UUID, if available.
+ * @return true if uuid was set.
+ */
+bool Medium::i_getFirstRegistryMachineId(Guid &uuid) const
+{
+ if (m->llRegistryIDs.size())
+ {
+ uuid = m->llRegistryIDs.front();
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Marks all the registries in which this medium is registered as modified.
+ */
+void Medium::i_markRegistriesModified()
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return;
+
+ // Get local copy, as keeping the lock over VirtualBox::markRegistryModified
+ // causes trouble with the lock order
+ GuidList llRegistryIDs;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ llRegistryIDs = m->llRegistryIDs;
+ }
+
+ autoCaller.release();
+
+ /* Save the error information now, the implicit restore when this goes
+ * out of scope will throw away spurious additional errors created below. */
+ ErrorInfoKeeper eik;
+ for (GuidList::const_iterator it = llRegistryIDs.begin();
+ it != llRegistryIDs.end();
+ ++it)
+ {
+ m->pVirtualBox->i_markRegistryModified(*it);
+ }
+}
+
+/**
+ * Adds the given machine and optionally the snapshot to the list of the objects
+ * this medium is attached to.
+ *
+ * @param aMachineId Machine ID.
+ * @param aSnapshotId Snapshot ID; when non-empty, adds a snapshot attachment.
+ */
+HRESULT Medium::i_addBackReference(const Guid &aMachineId,
+ const Guid &aSnapshotId /*= Guid::Empty*/)
+{
+ AssertReturn(aMachineId.isValid(), E_FAIL);
+
+ LogFlowThisFunc(("ENTER, aMachineId: {%RTuuid}, aSnapshotId: {%RTuuid}\n", aMachineId.raw(), aSnapshotId.raw()));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ switch (m->state)
+ {
+ case MediumState_Created:
+ case MediumState_Inaccessible:
+ case MediumState_LockedRead:
+ case MediumState_LockedWrite:
+ break;
+
+ default:
+ return i_setStateError();
+ }
+
+ if (m->numCreateDiffTasks > 0)
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Cannot attach medium '%s' {%RTuuid}: %u differencing child media are being created", "",
+ m->numCreateDiffTasks),
+ m->strLocationFull.c_str(),
+ m->id.raw(),
+ m->numCreateDiffTasks);
+
+ BackRefList::iterator it = std::find_if(m->backRefs.begin(),
+ m->backRefs.end(),
+ BackRef::EqualsTo(aMachineId));
+ if (it == m->backRefs.end())
+ {
+ BackRef ref(aMachineId, aSnapshotId);
+ m->backRefs.push_back(ref);
+
+ return S_OK;
+ }
+ bool fDvd = false;
+ {
+ AutoReadLock arlock(this COMMA_LOCKVAL_SRC_POS);
+ /*
+ * Check the medium is DVD and readonly. It's for the case if DVD
+ * will be able to be writable sometime in the future.
+ */
+ fDvd = m->type == MediumType_Readonly && m->devType == DeviceType_DVD;
+ }
+
+ // if the caller has not supplied a snapshot ID, then we're attaching
+ // to a machine a medium which represents the machine's current state,
+ // so set the flag
+
+ if (aSnapshotId.isZero())
+ {
+ // Allow DVD having MediumType_Readonly to be attached twice.
+ // (the medium already had been added to back reference)
+ if (fDvd)
+ {
+ it->iRefCnt++;
+ return S_OK;
+ }
+
+ /* sanity: no duplicate attachments */
+ if (it->fInCurState)
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Cannot attach medium '%s' {%RTuuid}: medium is already associated with the current state of machine uuid {%RTuuid}!"),
+ m->strLocationFull.c_str(),
+ m->id.raw(),
+ aMachineId.raw());
+ it->fInCurState = true;
+
+ return S_OK;
+ }
+
+ // otherwise: a snapshot medium is being attached
+
+ /* sanity: no duplicate attachments */
+ for (std::list<SnapshotRef>::iterator jt = it->llSnapshotIds.begin();
+ jt != it->llSnapshotIds.end();
+ ++jt)
+ {
+ const Guid &idOldSnapshot = jt->snapshotId;
+
+ if (idOldSnapshot == aSnapshotId)
+ {
+ if (fDvd)
+ {
+ jt->iRefCnt++;
+ return S_OK;
+ }
+#ifdef DEBUG
+ i_dumpBackRefs();
+#endif
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Cannot attach medium '%s' {%RTuuid} from snapshot '%RTuuid': medium is already in use by this snapshot!"),
+ m->strLocationFull.c_str(),
+ m->id.raw(),
+ aSnapshotId.raw());
+ }
+ }
+
+ it->llSnapshotIds.push_back(SnapshotRef(aSnapshotId));
+ // Do not touch fInCurState, as the image may be attached to the current
+ // state *and* a snapshot, otherwise we lose the current state association!
+
+ LogFlowThisFuncLeave();
+
+ return S_OK;
+}
+
+/**
+ * Removes the given machine and optionally the snapshot from the list of the
+ * objects this medium is attached to.
+ *
+ * @param aMachineId Machine ID.
+ * @param aSnapshotId Snapshot ID; when non-empty, removes the snapshot
+ * attachment.
+ */
+HRESULT Medium::i_removeBackReference(const Guid &aMachineId,
+ const Guid &aSnapshotId /*= Guid::Empty*/)
+{
+ AssertReturn(aMachineId.isValid(), E_FAIL);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ BackRefList::iterator it =
+ std::find_if(m->backRefs.begin(), m->backRefs.end(),
+ BackRef::EqualsTo(aMachineId));
+ AssertReturn(it != m->backRefs.end(), E_FAIL);
+
+ if (aSnapshotId.isZero())
+ {
+ it->iRefCnt--;
+ if (it->iRefCnt > 0)
+ return S_OK;
+
+ /* remove the current state attachment */
+ it->fInCurState = false;
+ }
+ else
+ {
+ /* remove the snapshot attachment */
+ std::list<SnapshotRef>::iterator jt =
+ std::find_if(it->llSnapshotIds.begin(),
+ it->llSnapshotIds.end(),
+ SnapshotRef::EqualsTo(aSnapshotId));
+
+ AssertReturn(jt != it->llSnapshotIds.end(), E_FAIL);
+
+ jt->iRefCnt--;
+ if (jt->iRefCnt > 0)
+ return S_OK;
+
+ it->llSnapshotIds.erase(jt);
+ }
+
+ /* if the backref becomes empty, remove it */
+ if (it->fInCurState == false && it->llSnapshotIds.size() == 0)
+ m->backRefs.erase(it);
+
+ return S_OK;
+}
+
+/**
+ * Internal method to return the medium's list of backrefs. Must have caller + locking!
+ * @return
+ */
+const Guid* Medium::i_getFirstMachineBackrefId() const
+{
+ if (!m->backRefs.size())
+ return NULL;
+
+ return &m->backRefs.front().machineId;
+}
+
+/**
+ * Internal method which returns a machine that either this medium or one of its children
+ * is attached to. This is used for finding a replacement media registry when an existing
+ * media registry is about to be deleted in VirtualBox::unregisterMachine().
+ *
+ * Must have caller + locking, *and* caller must hold the media tree lock!
+ * @param aId Id to ignore when looking for backrefs.
+ * @return
+ */
+const Guid* Medium::i_getAnyMachineBackref(const Guid &aId) const
+{
+ std::list<const Medium *> llMediaTodo;
+ llMediaTodo.push_back(this);
+
+ while (!llMediaTodo.empty())
+ {
+ const Medium *pMedium = llMediaTodo.front();
+ llMediaTodo.pop_front();
+
+ if (pMedium->m->backRefs.size())
+ {
+ if (pMedium->m->backRefs.front().machineId != aId)
+ return &pMedium->m->backRefs.front().machineId;
+ if (pMedium->m->backRefs.size() > 1)
+ {
+ BackRefList::const_iterator it = pMedium->m->backRefs.begin();
+ ++it;
+ return &it->machineId;
+ }
+ }
+
+ MediaList::const_iterator itBegin = pMedium->i_getChildren().begin();
+ MediaList::const_iterator itEnd = pMedium->i_getChildren().end();
+ for (MediaList::const_iterator it = itBegin; it != itEnd; ++it)
+ llMediaTodo.push_back(*it);
+ }
+
+ return NULL;
+}
+
+const Guid* Medium::i_getFirstMachineBackrefSnapshotId() const
+{
+ if (!m->backRefs.size())
+ return NULL;
+
+ const BackRef &ref = m->backRefs.front();
+ if (ref.llSnapshotIds.empty())
+ return NULL;
+
+ return &ref.llSnapshotIds.front().snapshotId;
+}
+
+size_t Medium::i_getMachineBackRefCount() const
+{
+ return m->backRefs.size();
+}
+
+#ifdef DEBUG
+/**
+ * Debugging helper that gets called after VirtualBox initialization that writes all
+ * machine backreferences to the debug log.
+ */
+void Medium::i_dumpBackRefs()
+{
+ AutoCaller autoCaller(this);
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("Dumping backrefs for medium '%s':\n", m->strLocationFull.c_str()));
+
+ for (BackRefList::iterator it2 = m->backRefs.begin();
+ it2 != m->backRefs.end();
+ ++it2)
+ {
+ const BackRef &ref = *it2;
+ LogFlowThisFunc((" Backref from machine {%RTuuid} (fInCurState: %d, iRefCnt: %d)\n", ref.machineId.raw(), ref.fInCurState, ref.iRefCnt));
+
+ for (std::list<SnapshotRef>::const_iterator jt2 = it2->llSnapshotIds.begin();
+ jt2 != it2->llSnapshotIds.end();
+ ++jt2)
+ {
+ const Guid &id = jt2->snapshotId;
+ LogFlowThisFunc((" Backref from snapshot {%RTuuid} (iRefCnt = %d)\n", id.raw(), jt2->iRefCnt));
+ }
+ }
+}
+#endif
+
+/**
+ * Checks if the given change of \a aOldPath to \a aNewPath affects the location
+ * of this media and updates it if necessary to reflect the new location.
+ *
+ * @param strOldPath Old path (full).
+ * @param strNewPath New path (full).
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Medium::i_updatePath(const Utf8Str &strOldPath, const Utf8Str &strNewPath)
+{
+ AssertReturn(!strOldPath.isEmpty(), E_FAIL);
+ AssertReturn(!strNewPath.isEmpty(), E_FAIL);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("locationFull.before='%s'\n", m->strLocationFull.c_str()));
+
+ const char *pcszMediumPath = m->strLocationFull.c_str();
+
+ if (RTPathStartsWith(pcszMediumPath, strOldPath.c_str()))
+ {
+ Utf8Str newPath(strNewPath);
+ newPath.append(pcszMediumPath + strOldPath.length());
+ unconst(m->strLocationFull) = newPath;
+
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+
+ LogFlowThisFunc(("locationFull.after='%s'\n", m->strLocationFull.c_str()));
+ // we changed something
+ return S_OK;
+ }
+
+ // no change was necessary, signal error which the caller needs to interpret
+ return VBOX_E_FILE_ERROR;
+}
+
+/**
+ * Returns the base medium of the media chain this medium is part of.
+ *
+ * The base medium is found by walking up the parent-child relationship axis.
+ * If the medium doesn't have a parent (i.e. it's a base medium), it
+ * returns itself in response to this method.
+ *
+ * @param aLevel Where to store the number of ancestors of this medium
+ * (zero for the base), may be @c NULL.
+ *
+ * @note Locks medium tree for reading.
+ */
+ComObjPtr<Medium> Medium::i_getBase(uint32_t *aLevel /*= NULL*/)
+{
+ ComObjPtr<Medium> pBase;
+
+ /* it is possible that some previous/concurrent uninit has already cleared
+ * the pVirtualBox reference, and in this case we don't need to continue */
+ ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox);
+ if (!pVirtualBox)
+ return pBase;
+
+ /* we access m->pParent */
+ AutoReadLock treeLock(pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ AutoCaller autoCaller(this);
+ AssertReturn(autoCaller.isOk(), pBase);
+
+ pBase = this;
+ uint32_t level = 0;
+
+ if (m->pParent)
+ {
+ for (;;)
+ {
+ AutoCaller baseCaller(pBase);
+ AssertReturn(baseCaller.isOk(), pBase);
+
+ if (pBase->m->pParent.isNull())
+ break;
+
+ pBase = pBase->m->pParent;
+ ++level;
+ }
+ }
+
+ if (aLevel != NULL)
+ *aLevel = level;
+
+ return pBase;
+}
+
+/**
+ * Returns the depth of this medium in the media chain.
+ *
+ * @note Locks medium tree for reading.
+ */
+uint32_t Medium::i_getDepth()
+{
+ /* it is possible that some previous/concurrent uninit has already cleared
+ * the pVirtualBox reference, and in this case we don't need to continue */
+ ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox);
+ if (!pVirtualBox)
+ return 1;
+
+ /* we access m->pParent */
+ AutoReadLock treeLock(pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ uint32_t cDepth = 0;
+ ComObjPtr<Medium> pMedium(this);
+ while (!pMedium.isNull())
+ {
+ AutoCaller autoCaller(this);
+ AssertReturn(autoCaller.isOk(), cDepth + 1);
+
+ pMedium = pMedium->m->pParent;
+ cDepth++;
+ }
+
+ return cDepth;
+}
+
+/**
+ * Returns @c true if this medium cannot be modified because it has
+ * dependents (children) or is part of the snapshot. Related to the medium
+ * type and posterity, not to the current media state.
+ *
+ * @note Locks this object and medium tree for reading.
+ */
+bool Medium::i_isReadOnly()
+{
+ /* it is possible that some previous/concurrent uninit has already cleared
+ * the pVirtualBox reference, and in this case we don't need to continue */
+ ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox);
+ if (!pVirtualBox)
+ return false;
+
+ /* we access children */
+ AutoReadLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), false);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ switch (m->type)
+ {
+ case MediumType_Normal:
+ {
+ if (i_getChildren().size() != 0)
+ return true;
+
+ for (BackRefList::const_iterator it = m->backRefs.begin();
+ it != m->backRefs.end(); ++it)
+ if (it->llSnapshotIds.size() != 0)
+ return true;
+
+ if (m->variant & MediumVariant_VmdkStreamOptimized)
+ return true;
+
+ return false;
+ }
+ case MediumType_Immutable:
+ case MediumType_MultiAttach:
+ return true;
+ case MediumType_Writethrough:
+ case MediumType_Shareable:
+ case MediumType_Readonly: /* explicit readonly media has no diffs */
+ return false;
+ default:
+ break;
+ }
+
+ AssertFailedReturn(false);
+}
+
+/**
+ * Internal method to update the medium's id. Must have caller + locking!
+ * @return
+ */
+void Medium::i_updateId(const Guid &id)
+{
+ unconst(m->id) = id;
+}
+
+/**
+ * Saves the settings of one medium.
+ *
+ * @note Caller MUST take care of the medium tree lock and caller.
+ *
+ * @param data Settings struct to be updated.
+ * @param strHardDiskFolder Folder for which paths should be relative.
+ */
+void Medium::i_saveSettingsOne(settings::Medium &data, const Utf8Str &strHardDiskFolder)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ data.uuid = m->id;
+
+ // make path relative if needed
+ if ( !strHardDiskFolder.isEmpty()
+ && RTPathStartsWith(m->strLocationFull.c_str(), strHardDiskFolder.c_str())
+ )
+ data.strLocation = m->strLocationFull.substr(strHardDiskFolder.length() + 1);
+ else
+ data.strLocation = m->strLocationFull;
+ data.strFormat = m->strFormat;
+
+ /* optional, only for diffs, default is false */
+ if (m->pParent)
+ data.fAutoReset = m->autoReset;
+ else
+ data.fAutoReset = false;
+
+ /* optional */
+ data.strDescription = m->strDescription;
+
+ /* optional properties */
+ data.properties.clear();
+
+ /* handle iSCSI initiator secrets transparently */
+ bool fHaveInitiatorSecretEncrypted = false;
+ Utf8Str strCiphertext;
+ settings::StringsMap::const_iterator itPln = m->mapProperties.find("InitiatorSecret");
+ if ( itPln != m->mapProperties.end()
+ && !itPln->second.isEmpty())
+ {
+ /* Encrypt the plain secret. If that does not work (i.e. no or wrong settings key
+ * specified), just use the encrypted secret (if there is any). */
+ int rc = m->pVirtualBox->i_encryptSetting(itPln->second, &strCiphertext);
+ if (RT_SUCCESS(rc))
+ fHaveInitiatorSecretEncrypted = true;
+ }
+ for (settings::StringsMap::const_iterator it = m->mapProperties.begin();
+ it != m->mapProperties.end();
+ ++it)
+ {
+ /* only save properties that have non-default values */
+ if (!it->second.isEmpty())
+ {
+ const Utf8Str &name = it->first;
+ const Utf8Str &value = it->second;
+ bool fCreateOnly = false;
+ for (MediumFormat::PropertyArray::const_iterator itf = m->formatObj->i_getProperties().begin();
+ itf != m->formatObj->i_getProperties().end();
+ ++itf)
+ {
+ if ( itf->strName.equals(name)
+ && (itf->flags & VD_CFGKEY_CREATEONLY))
+ {
+ fCreateOnly = true;
+ break;
+ }
+ }
+ if (!fCreateOnly)
+ /* do NOT store the plain InitiatorSecret */
+ if ( !fHaveInitiatorSecretEncrypted
+ || !name.equals("InitiatorSecret"))
+ data.properties[name] = value;
+ }
+ }
+ if (fHaveInitiatorSecretEncrypted)
+ data.properties["InitiatorSecretEncrypted"] = strCiphertext;
+
+ /* only for base media */
+ if (m->pParent.isNull())
+ data.hdType = m->type;
+}
+
+/**
+ * Saves medium data by putting it into the provided data structure.
+ * The settings of all children is saved, too.
+ *
+ * @param data Settings struct to be updated.
+ * @param strHardDiskFolder Folder for which paths should be relative.
+ *
+ * @note Locks this object, medium tree and children for reading.
+ */
+HRESULT Medium::i_saveSettings(settings::Medium &data,
+ const Utf8Str &strHardDiskFolder)
+{
+ /* we access m->pParent */
+ AutoReadLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ MediaList llMediaTodo;
+ llMediaTodo.push_back(this);
+ std::list<settings::Medium *> llSettingsTodo;
+ llSettingsTodo.push_back(&data);
+
+ while (!llMediaTodo.empty())
+ {
+ ComObjPtr<Medium> pMedium = llMediaTodo.front();
+ llMediaTodo.pop_front();
+ settings::Medium *current = llSettingsTodo.front();
+ llSettingsTodo.pop_front();
+
+ AutoCaller mediumCaller(pMedium);
+ if (FAILED(mediumCaller.rc())) return mediumCaller.rc();
+
+ pMedium->i_saveSettingsOne(*current, strHardDiskFolder);
+
+ /* save all children */
+ MediaList::const_iterator itBegin = pMedium->i_getChildren().begin();
+ MediaList::const_iterator itEnd = pMedium->i_getChildren().end();
+ for (MediaList::const_iterator it = itBegin; it != itEnd; ++it)
+ {
+ llMediaTodo.push_back(*it);
+ current->llChildren.push_back(settings::Medium::Empty);
+ llSettingsTodo.push_back(&current->llChildren.back());
+ }
+ }
+
+ return S_OK;
+}
+
+/**
+ * Constructs a medium lock list for this medium. The lock is not taken.
+ *
+ * @note Caller MUST NOT hold the media tree or medium lock.
+ *
+ * @param fFailIfInaccessible If true, this fails with an error if a medium is inaccessible. If false,
+ * inaccessible media are silently skipped and not locked (i.e. their state remains "Inaccessible");
+ * this is necessary for a VM's removable media VM startup for which we do not want to fail.
+ * @param pToLockWrite If not NULL, associate a write lock with this medium object.
+ * @param fMediumLockWriteAll Whether to associate a write lock to all other media too.
+ * @param pToBeParent Medium which will become the parent of this medium.
+ * @param mediumLockList Where to store the resulting list.
+ */
+HRESULT Medium::i_createMediumLockList(bool fFailIfInaccessible,
+ Medium *pToLockWrite,
+ bool fMediumLockWriteAll,
+ Medium *pToBeParent,
+ MediumLockList &mediumLockList)
+{
+ /** @todo r=klaus this needs to be reworked, as the code below uses
+ * i_getParent without holding the tree lock, and changing this is
+ * a significant amount of effort. */
+ Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+ Assert(!isWriteLockOnCurrentThread());
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ HRESULT rc = S_OK;
+
+ /* paranoid sanity checking if the medium has a to-be parent medium */
+ if (pToBeParent)
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ ComAssertRet(i_getParent().isNull(), E_FAIL);
+ ComAssertRet(i_getChildren().size() == 0, E_FAIL);
+ }
+
+ ErrorInfoKeeper eik;
+ MultiResult mrc(S_OK);
+
+ ComObjPtr<Medium> pMedium = this;
+ while (!pMedium.isNull())
+ {
+ AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ /* Accessibility check must be first, otherwise locking interferes
+ * with getting the medium state. Lock lists are not created for
+ * fun, and thus getting the medium status is no luxury. */
+ MediumState_T mediumState = pMedium->i_getState();
+ if (mediumState == MediumState_Inaccessible)
+ {
+ alock.release();
+ rc = pMedium->i_queryInfo(false /* fSetImageId */, false /* fSetParentId */,
+ autoCaller);
+ alock.acquire();
+ if (FAILED(rc)) return rc;
+
+ mediumState = pMedium->i_getState();
+ if (mediumState == MediumState_Inaccessible)
+ {
+ // ignore inaccessible ISO media and silently return S_OK,
+ // otherwise VM startup (esp. restore) may fail without good reason
+ if (!fFailIfInaccessible)
+ return S_OK;
+
+ // otherwise report an error
+ Bstr error;
+ rc = pMedium->COMGETTER(LastAccessError)(error.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ /* collect multiple errors */
+ eik.restore();
+ Assert(!error.isEmpty());
+ mrc = setError(E_FAIL,
+ "%ls",
+ error.raw());
+ // error message will be something like
+ // "Could not open the medium ... VD: error VERR_FILE_NOT_FOUND opening image file ... (VERR_FILE_NOT_FOUND).
+ eik.fetch();
+ }
+ }
+
+ if (pMedium == pToLockWrite)
+ mediumLockList.Prepend(pMedium, true);
+ else
+ mediumLockList.Prepend(pMedium, fMediumLockWriteAll);
+
+ pMedium = pMedium->i_getParent();
+ if (pMedium.isNull() && pToBeParent)
+ {
+ pMedium = pToBeParent;
+ pToBeParent = NULL;
+ }
+ }
+
+ return mrc;
+}
+
+/**
+ * Creates a new differencing storage unit using the format of the given target
+ * medium and the location. Note that @c aTarget must be NotCreated.
+ *
+ * The @a aMediumLockList parameter contains the associated medium lock list,
+ * which must be in locked state. If @a aWait is @c true then the caller is
+ * responsible for unlocking.
+ *
+ * If @a aProgress is not NULL but the object it points to is @c null then a
+ * new progress object will be created and assigned to @a *aProgress on
+ * success, otherwise the existing progress object is used. If @a aProgress is
+ * NULL, then no progress object is created/used at all.
+ *
+ * When @a aWait is @c false, this method will create a thread to perform the
+ * create operation asynchronously and will return immediately. Otherwise, it
+ * will perform the operation on the calling thread and will not return to the
+ * caller until the operation is completed. Note that @a aProgress cannot be
+ * NULL when @a aWait is @c false (this method will assert in this case).
+ *
+ * @param aTarget Target medium.
+ * @param aVariant Precise medium variant to create.
+ * @param aMediumLockList List of media which should be locked.
+ * @param aProgress Where to find/store a Progress object to track
+ * operation completion.
+ * @param aWait @c true if this method should block instead of
+ * creating an asynchronous thread.
+ * @param aNotify Notify about mediums which metadatð are changed
+ * during execution of the function.
+ *
+ * @note Locks this object and @a aTarget for writing.
+ */
+HRESULT Medium::i_createDiffStorage(ComObjPtr<Medium> &aTarget,
+ MediumVariant_T aVariant,
+ MediumLockList *aMediumLockList,
+ ComObjPtr<Progress> *aProgress,
+ bool aWait,
+ bool aNotify)
+{
+ AssertReturn(!aTarget.isNull(), E_FAIL);
+ AssertReturn(aMediumLockList, E_FAIL);
+ AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoCaller targetCaller(aTarget);
+ if (FAILED(targetCaller.rc())) return targetCaller.rc();
+
+ HRESULT rc = S_OK;
+ ComObjPtr<Progress> pProgress;
+ Medium::Task *pTask = NULL;
+
+ try
+ {
+ AutoMultiWriteLock2 alock(this, aTarget COMMA_LOCKVAL_SRC_POS);
+
+ ComAssertThrow( m->type != MediumType_Writethrough
+ && m->type != MediumType_Shareable
+ && m->type != MediumType_Readonly, E_FAIL);
+ ComAssertThrow(m->state == MediumState_LockedRead, E_FAIL);
+
+ if (aTarget->m->state != MediumState_NotCreated)
+ throw aTarget->i_setStateError();
+
+ /* Check that the medium is not attached to the current state of
+ * any VM referring to it. */
+ for (BackRefList::const_iterator it = m->backRefs.begin();
+ it != m->backRefs.end();
+ ++it)
+ {
+ if (it->fInCurState)
+ {
+ /* Note: when a VM snapshot is being taken, all normal media
+ * attached to the VM in the current state will be, as an
+ * exception, also associated with the snapshot which is about
+ * to create (see SnapshotMachine::init()) before deassociating
+ * them from the current state (which takes place only on
+ * success in Machine::fixupHardDisks()), so that the size of
+ * snapshotIds will be 1 in this case. The extra condition is
+ * used to filter out this legal situation. */
+ if (it->llSnapshotIds.size() == 0)
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium '%s' is attached to a virtual machine with UUID {%RTuuid}. No differencing media based on it may be created until it is detached"),
+ m->strLocationFull.c_str(), it->machineId.raw());
+
+ Assert(it->llSnapshotIds.size() == 1);
+ }
+ }
+
+ if (aProgress != NULL)
+ {
+ /* use the existing progress object... */
+ pProgress = *aProgress;
+
+ /* ...but create a new one if it is null */
+ if (pProgress.isNull())
+ {
+ pProgress.createObject();
+ rc = pProgress->init(m->pVirtualBox,
+ static_cast<IMedium*>(this),
+ BstrFmt(tr("Creating differencing medium storage unit '%s'"),
+ aTarget->m->strLocationFull.c_str()).raw(),
+ TRUE /* aCancelable */);
+ if (FAILED(rc))
+ throw rc;
+ }
+ }
+
+ /* setup task object to carry out the operation sync/async */
+ pTask = new Medium::CreateDiffTask(this, pProgress, aTarget, aVariant,
+ aMediumLockList,
+ aWait /* fKeepMediumLockList */,
+ aNotify);
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+
+ /* register a task (it will deregister itself when done) */
+ ++m->numCreateDiffTasks;
+ Assert(m->numCreateDiffTasks != 0); /* overflow? */
+
+ aTarget->m->state = MediumState_Creating;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ if (aWait)
+ {
+ rc = pTask->runNow();
+ delete pTask;
+ }
+ else
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(rc) && aProgress != NULL)
+ *aProgress = pProgress;
+ }
+ else if (pTask != NULL)
+ delete pTask;
+
+ return rc;
+}
+
+/**
+ * Returns a preferred format for differencing media.
+ */
+Utf8Str Medium::i_getPreferredDiffFormat()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), Utf8Str::Empty);
+
+ /* check that our own format supports diffs */
+ if (!(m->formatObj->i_getCapabilities() & MediumFormatCapabilities_Differencing))
+ {
+ /* use the default format if not */
+ Utf8Str tmp;
+ m->pVirtualBox->i_getDefaultHardDiskFormat(tmp);
+ return tmp;
+ }
+
+ /* m->strFormat is const, no need to lock */
+ return m->strFormat;
+}
+
+/**
+ * Returns a preferred variant for differencing media.
+ */
+MediumVariant_T Medium::i_getPreferredDiffVariant()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), MediumVariant_Standard);
+
+ /* check that our own format supports diffs */
+ if (!(m->formatObj->i_getCapabilities() & MediumFormatCapabilities_Differencing))
+ return MediumVariant_Standard;
+
+ /* m->variant is const, no need to lock */
+ ULONG mediumVariantFlags = (ULONG)m->variant;
+ mediumVariantFlags &= ~(ULONG)(MediumVariant_Fixed | MediumVariant_VmdkStreamOptimized | MediumVariant_VmdkESX | MediumVariant_VmdkRawDisk);
+ mediumVariantFlags |= MediumVariant_Diff;
+ return (MediumVariant_T)mediumVariantFlags;
+}
+
+/**
+ * Implementation for the public Medium::Close() with the exception of calling
+ * VirtualBox::saveRegistries(), in case someone wants to call this for several
+ * media.
+ *
+ * After this returns with success, uninit() has been called on the medium, and
+ * the object is no longer usable ("not ready" state).
+ *
+ * @param autoCaller AutoCaller instance which must have been created on the caller's
+ * stack for this medium. This gets released hereupon
+ * which the Medium instance gets uninitialized.
+ * @return
+ */
+HRESULT Medium::i_close(AutoCaller &autoCaller)
+{
+ // must temporarily drop the caller, need the tree lock first
+ autoCaller.release();
+
+ // we're accessing parent/child and backrefs, so lock the tree first, then ourselves
+ AutoMultiWriteLock2 multilock(&m->pVirtualBox->i_getMediaTreeLockHandle(),
+ this->lockHandle()
+ COMMA_LOCKVAL_SRC_POS);
+
+ autoCaller.add();
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ /* Wait for a concurrently running Medium::i_queryInfo to complete. */
+ while (m->queryInfoRunning)
+ {
+ autoCaller.release();
+ multilock.release();
+ /* Must not hold the media tree lock, as Medium::i_queryInfo needs
+ * this lock and thus we would run into a deadlock here. */
+ Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+ /* must not hold the object lock now */
+ Assert(!isWriteLockOnCurrentThread());
+ {
+ AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS);
+ }
+ multilock.acquire();
+ autoCaller.add();
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+ }
+
+ LogFlowFunc(("ENTER for %s\n", i_getLocationFull().c_str()));
+
+ bool wasCreated = true;
+
+ switch (m->state)
+ {
+ case MediumState_NotCreated:
+ wasCreated = false;
+ break;
+ case MediumState_Created:
+ case MediumState_Inaccessible:
+ break;
+ default:
+ return i_setStateError();
+ }
+
+ if (m->backRefs.size() != 0)
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Medium '%s' cannot be closed because it is still attached to %d virtual machines", "",
+ m->backRefs.size()),
+ m->strLocationFull.c_str(), m->backRefs.size());
+
+ // perform extra media-dependent close checks
+ HRESULT rc = i_canClose();
+ if (FAILED(rc)) return rc;
+
+ m->fClosing = true;
+
+ if (wasCreated)
+ {
+ // remove from the list of known media before performing actual
+ // uninitialization (to keep the media registry consistent on
+ // failure to do so)
+ rc = i_unregisterWithVirtualBox();
+ if (FAILED(rc)) return rc;
+
+ multilock.release();
+ // Release the AutoCaller now, as otherwise uninit() will simply hang.
+ // Needs to be done before mark the registries as modified and saving
+ // the registry, as otherwise there may be a deadlock with someone else
+ // closing this object while we're in i_saveModifiedRegistries(), which
+ // needs the media tree lock, which the other thread holds until after
+ // uninit() below.
+ autoCaller.release();
+ i_markRegistriesModified();
+ m->pVirtualBox->i_saveModifiedRegistries();
+ }
+ else
+ {
+ multilock.release();
+ // release the AutoCaller, as otherwise uninit() will simply hang
+ autoCaller.release();
+ }
+
+ // Keep the locks held until after uninit, as otherwise the consistency
+ // of the medium tree cannot be guaranteed.
+ uninit();
+
+ LogFlowFuncLeave();
+
+ return rc;
+}
+
+/**
+ * Deletes the medium storage unit.
+ *
+ * If @a aProgress is not NULL but the object it points to is @c null then a new
+ * progress object will be created and assigned to @a *aProgress on success,
+ * otherwise the existing progress object is used. If Progress is NULL, then no
+ * progress object is created/used at all.
+ *
+ * When @a aWait is @c false, this method will create a thread to perform the
+ * delete operation asynchronously and will return immediately. Otherwise, it
+ * will perform the operation on the calling thread and will not return to the
+ * caller until the operation is completed. Note that @a aProgress cannot be
+ * NULL when @a aWait is @c false (this method will assert in this case).
+ *
+ * @param aProgress Where to find/store a Progress object to track operation
+ * completion.
+ * @param aWait @c true if this method should block instead of creating
+ * an asynchronous thread.
+ * @param aNotify Notify about mediums which metadatð are changed
+ * during execution of the function.
+ *
+ * @note Locks mVirtualBox and this object for writing. Locks medium tree for
+ * writing.
+ */
+HRESULT Medium::i_deleteStorage(ComObjPtr<Progress> *aProgress,
+ bool aWait, bool aNotify)
+{
+ AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
+
+ HRESULT rc = S_OK;
+ ComObjPtr<Progress> pProgress;
+ Medium::Task *pTask = NULL;
+
+ try
+ {
+ /* we're accessing the media tree, and i_canClose() needs it too */
+ AutoWriteLock treelock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ AutoCaller autoCaller(this);
+ AssertComRCThrowRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("aWait=%RTbool locationFull=%s\n", aWait, i_getLocationFull().c_str() ));
+
+ if ( !(m->formatObj->i_getCapabilities() & ( MediumFormatCapabilities_CreateDynamic
+ | MediumFormatCapabilities_CreateFixed)))
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Medium format '%s' does not support storage deletion"),
+ m->strFormat.c_str());
+
+ /* Wait for a concurrently running Medium::i_queryInfo to complete. */
+ /** @todo r=klaus would be great if this could be moved to the async
+ * part of the operation as it can take quite a while */
+ while (m->queryInfoRunning)
+ {
+ alock.release();
+ autoCaller.release();
+ treelock.release();
+ /* Must not hold the media tree lock or the object lock, as
+ * Medium::i_queryInfo needs this lock and thus we would run
+ * into a deadlock here. */
+ Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+ Assert(!isWriteLockOnCurrentThread());
+ {
+ AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS);
+ }
+ treelock.acquire();
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+ alock.acquire();
+ }
+
+ /* Note that we are fine with Inaccessible state too: a) for symmetry
+ * with create calls and b) because it doesn't really harm to try, if
+ * it is really inaccessible, the delete operation will fail anyway.
+ * Accepting Inaccessible state is especially important because all
+ * registered media are initially Inaccessible upon VBoxSVC startup
+ * until COMGETTER(RefreshState) is called. Accept Deleting state
+ * because some callers need to put the medium in this state early
+ * to prevent races. */
+ switch (m->state)
+ {
+ case MediumState_Created:
+ case MediumState_Deleting:
+ case MediumState_Inaccessible:
+ break;
+ default:
+ throw i_setStateError();
+ }
+
+ if (m->backRefs.size() != 0)
+ {
+ Utf8Str strMachines;
+ for (BackRefList::const_iterator it = m->backRefs.begin();
+ it != m->backRefs.end();
+ ++it)
+ {
+ const BackRef &b = *it;
+ if (strMachines.length())
+ strMachines.append(", ");
+ strMachines.append(b.machineId.toString().c_str());
+ }
+#ifdef DEBUG
+ i_dumpBackRefs();
+#endif
+ throw setError(VBOX_E_OBJECT_IN_USE,
+ tr("Cannot delete storage: medium '%s' is still attached to the following %d virtual machine(s): %s",
+ "", m->backRefs.size()),
+ m->strLocationFull.c_str(),
+ m->backRefs.size(),
+ strMachines.c_str());
+ }
+
+ rc = i_canClose();
+ if (FAILED(rc))
+ throw rc;
+
+ /* go to Deleting state, so that the medium is not actually locked */
+ if (m->state != MediumState_Deleting)
+ {
+ rc = i_markForDeletion();
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ /* Build the medium lock list. */
+ MediumLockList *pMediumLockList(new MediumLockList());
+ alock.release();
+ autoCaller.release();
+ treelock.release();
+ rc = i_createMediumLockList(true /* fFailIfInaccessible */,
+ this /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pMediumLockList);
+ treelock.acquire();
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw rc;
+ }
+
+ alock.release();
+ autoCaller.release();
+ treelock.release();
+ rc = pMediumLockList->Lock();
+ treelock.acquire();
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pMediumLockList;
+ throw setError(rc,
+ tr("Failed to lock media when deleting '%s'"),
+ i_getLocationFull().c_str());
+ }
+
+ /* try to remove from the list of known media before performing
+ * actual deletion (we favor the consistency of the media registry
+ * which would have been broken if unregisterWithVirtualBox() failed
+ * after we successfully deleted the storage) */
+ rc = i_unregisterWithVirtualBox();
+ if (FAILED(rc))
+ throw rc;
+ // no longer need lock
+ alock.release();
+ autoCaller.release();
+ treelock.release();
+ i_markRegistriesModified();
+
+ if (aProgress != NULL)
+ {
+ /* use the existing progress object... */
+ pProgress = *aProgress;
+
+ /* ...but create a new one if it is null */
+ if (pProgress.isNull())
+ {
+ pProgress.createObject();
+ rc = pProgress->init(m->pVirtualBox,
+ static_cast<IMedium*>(this),
+ BstrFmt(tr("Deleting medium storage unit '%s'"), m->strLocationFull.c_str()).raw(),
+ FALSE /* aCancelable */);
+ if (FAILED(rc))
+ throw rc;
+ }
+ }
+
+ /* setup task object to carry out the operation sync/async */
+ pTask = new Medium::DeleteTask(this, pProgress, pMediumLockList, false, aNotify);
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ if (aWait)
+ {
+ rc = pTask->runNow();
+ delete pTask;
+ }
+ else
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(rc) && aProgress != NULL)
+ *aProgress = pProgress;
+ }
+ else
+ {
+ if (pTask)
+ delete pTask;
+
+ /* Undo deleting state if necessary. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ /* Make sure that any error signalled by unmarkForDeletion() is not
+ * ending up in the error list (if the caller uses MultiResult). It
+ * usually is spurious, as in most cases the medium hasn't been marked
+ * for deletion when the error was thrown above. */
+ ErrorInfoKeeper eik;
+ i_unmarkForDeletion();
+ }
+
+ return rc;
+}
+
+/**
+ * Mark a medium for deletion.
+ *
+ * @note Caller must hold the write lock on this medium!
+ */
+HRESULT Medium::i_markForDeletion()
+{
+ ComAssertRet(isWriteLockOnCurrentThread(), E_FAIL);
+ switch (m->state)
+ {
+ case MediumState_Created:
+ case MediumState_Inaccessible:
+ m->preLockState = m->state;
+ m->state = MediumState_Deleting;
+ return S_OK;
+ default:
+ return i_setStateError();
+ }
+}
+
+/**
+ * Removes the "mark for deletion".
+ *
+ * @note Caller must hold the write lock on this medium!
+ */
+HRESULT Medium::i_unmarkForDeletion()
+{
+ ComAssertRet(isWriteLockOnCurrentThread(), E_FAIL);
+ switch (m->state)
+ {
+ case MediumState_Deleting:
+ m->state = m->preLockState;
+ return S_OK;
+ default:
+ return i_setStateError();
+ }
+}
+
+/**
+ * Mark a medium for deletion which is in locked state.
+ *
+ * @note Caller must hold the write lock on this medium!
+ */
+HRESULT Medium::i_markLockedForDeletion()
+{
+ ComAssertRet(isWriteLockOnCurrentThread(), E_FAIL);
+ if ( ( m->state == MediumState_LockedRead
+ || m->state == MediumState_LockedWrite)
+ && m->preLockState == MediumState_Created)
+ {
+ m->preLockState = MediumState_Deleting;
+ return S_OK;
+ }
+ else
+ return i_setStateError();
+}
+
+/**
+ * Removes the "mark for deletion" for a medium in locked state.
+ *
+ * @note Caller must hold the write lock on this medium!
+ */
+HRESULT Medium::i_unmarkLockedForDeletion()
+{
+ ComAssertRet(isWriteLockOnCurrentThread(), E_FAIL);
+ if ( ( m->state == MediumState_LockedRead
+ || m->state == MediumState_LockedWrite)
+ && m->preLockState == MediumState_Deleting)
+ {
+ m->preLockState = MediumState_Created;
+ return S_OK;
+ }
+ else
+ return i_setStateError();
+}
+
+/**
+ * Queries the preferred merge direction from this to the other medium, i.e.
+ * the one which requires the least amount of I/O and therefore time and
+ * disk consumption.
+ *
+ * @returns Status code.
+ * @retval E_FAIL in case determining the merge direction fails for some reason,
+ * for example if getting the size of the media fails. There is no
+ * error set though and the caller is free to continue to find out
+ * what was going wrong later. Leaves fMergeForward unset.
+ * @retval VBOX_E_INVALID_OBJECT_STATE if both media are not related to each other
+ * An error is set.
+ * @param pOther The other medium to merge with.
+ * @param fMergeForward Resulting preferred merge direction (out).
+ */
+HRESULT Medium::i_queryPreferredMergeDirection(const ComObjPtr<Medium> &pOther,
+ bool &fMergeForward)
+{
+ AssertReturn(pOther != NULL, E_FAIL);
+ AssertReturn(pOther != this, E_FAIL);
+
+ HRESULT rc = S_OK;
+ bool fThisParent = false; /**<< Flag whether this medium is the parent of pOther. */
+
+ try
+ {
+ // locking: we need the tree lock first because we access parent pointers
+ AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ AutoCaller autoCaller(this);
+ AssertComRCThrowRC(autoCaller.rc());
+
+ AutoCaller otherCaller(pOther);
+ AssertComRCThrowRC(otherCaller.rc());
+
+ /* more sanity checking and figuring out the current merge direction */
+ ComObjPtr<Medium> pMedium = i_getParent();
+ while (!pMedium.isNull() && pMedium != pOther)
+ pMedium = pMedium->i_getParent();
+ if (pMedium == pOther)
+ fThisParent = false;
+ else
+ {
+ pMedium = pOther->i_getParent();
+ while (!pMedium.isNull() && pMedium != this)
+ pMedium = pMedium->i_getParent();
+ if (pMedium == this)
+ fThisParent = true;
+ else
+ {
+ Utf8Str tgtLoc;
+ {
+ AutoReadLock alock(pOther COMMA_LOCKVAL_SRC_POS);
+ tgtLoc = pOther->i_getLocationFull();
+ }
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Media '%s' and '%s' are unrelated"),
+ m->strLocationFull.c_str(), tgtLoc.c_str());
+ }
+ }
+
+ /*
+ * Figure out the preferred merge direction. The current way is to
+ * get the current sizes of file based images and select the merge
+ * direction depending on the size.
+ *
+ * Can't use the VD API to get current size here as the media might
+ * be write locked by a running VM. Resort to RTFileQuerySize().
+ */
+ int vrc = VINF_SUCCESS;
+ uint64_t cbMediumThis = 0;
+ uint64_t cbMediumOther = 0;
+
+ if (i_isMediumFormatFile() && pOther->i_isMediumFormatFile())
+ {
+ vrc = RTFileQuerySizeByPath(this->i_getLocationFull().c_str(), &cbMediumThis);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTFileQuerySizeByPath(pOther->i_getLocationFull().c_str(),
+ &cbMediumOther);
+ }
+
+ if (RT_FAILURE(vrc))
+ rc = E_FAIL;
+ else
+ {
+ /*
+ * Check which merge direction might be more optimal.
+ * This method is not bullet proof of course as there might
+ * be overlapping blocks in the images so the file size is
+ * not the best indicator but it is good enough for our purpose
+ * and everything else is too complicated, especially when the
+ * media are used by a running VM.
+ */
+
+ uint32_t mediumVariants = MediumVariant_Fixed | MediumVariant_VmdkStreamOptimized;
+ uint32_t mediumCaps = MediumFormatCapabilities_CreateDynamic | MediumFormatCapabilities_File;
+
+ bool fDynamicOther = pOther->i_getMediumFormat()->i_getCapabilities() & mediumCaps
+ && pOther->i_getVariant() & ~mediumVariants;
+ bool fDynamicThis = i_getMediumFormat()->i_getCapabilities() & mediumCaps
+ && i_getVariant() & ~mediumVariants;
+ bool fMergeIntoThis = (fDynamicThis && !fDynamicOther)
+ || (fDynamicThis == fDynamicOther && cbMediumThis > cbMediumOther);
+ fMergeForward = fMergeIntoThis != fThisParent;
+ }
+ }
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ return rc;
+}
+
+/**
+ * Prepares this (source) medium, target medium and all intermediate media
+ * for the merge operation.
+ *
+ * This method is to be called prior to calling the #mergeTo() to perform
+ * necessary consistency checks and place involved media to appropriate
+ * states. If #mergeTo() is not called or fails, the state modifications
+ * performed by this method must be undone by #i_cancelMergeTo().
+ *
+ * See #mergeTo() for more information about merging.
+ *
+ * @param pTarget Target medium.
+ * @param aMachineId Allowed machine attachment. NULL means do not check.
+ * @param aSnapshotId Allowed snapshot attachment. NULL or empty UUID means
+ * do not check.
+ * @param fLockMedia Flag whether to lock the medium lock list or not.
+ * If set to false and the medium lock list locking fails
+ * later you must call #i_cancelMergeTo().
+ * @param fMergeForward Resulting merge direction (out).
+ * @param pParentForTarget New parent for target medium after merge (out).
+ * @param aChildrenToReparent Medium lock list containing all children of the
+ * source which will have to be reparented to the target
+ * after merge (out).
+ * @param aMediumLockList Medium locking information (out).
+ *
+ * @note Locks medium tree for reading. Locks this object, aTarget and all
+ * intermediate media for writing.
+ */
+HRESULT Medium::i_prepareMergeTo(const ComObjPtr<Medium> &pTarget,
+ const Guid *aMachineId,
+ const Guid *aSnapshotId,
+ bool fLockMedia,
+ bool &fMergeForward,
+ ComObjPtr<Medium> &pParentForTarget,
+ MediumLockList * &aChildrenToReparent,
+ MediumLockList * &aMediumLockList)
+{
+ AssertReturn(pTarget != NULL, E_FAIL);
+ AssertReturn(pTarget != this, E_FAIL);
+
+ HRESULT rc = S_OK;
+ fMergeForward = false;
+ pParentForTarget.setNull();
+ Assert(aChildrenToReparent == NULL);
+ aChildrenToReparent = NULL;
+ Assert(aMediumLockList == NULL);
+ aMediumLockList = NULL;
+
+ try
+ {
+ // locking: we need the tree lock first because we access parent pointers
+ AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ AutoCaller autoCaller(this);
+ AssertComRCThrowRC(autoCaller.rc());
+
+ AutoCaller targetCaller(pTarget);
+ AssertComRCThrowRC(targetCaller.rc());
+
+ /* more sanity checking and figuring out the merge direction */
+ ComObjPtr<Medium> pMedium = i_getParent();
+ while (!pMedium.isNull() && pMedium != pTarget)
+ pMedium = pMedium->i_getParent();
+ if (pMedium == pTarget)
+ fMergeForward = false;
+ else
+ {
+ pMedium = pTarget->i_getParent();
+ while (!pMedium.isNull() && pMedium != this)
+ pMedium = pMedium->i_getParent();
+ if (pMedium == this)
+ fMergeForward = true;
+ else
+ {
+ Utf8Str tgtLoc;
+ {
+ AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS);
+ tgtLoc = pTarget->i_getLocationFull();
+ }
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Media '%s' and '%s' are unrelated"),
+ m->strLocationFull.c_str(), tgtLoc.c_str());
+ }
+ }
+
+ /* Build the lock list. */
+ aMediumLockList = new MediumLockList();
+ targetCaller.release();
+ autoCaller.release();
+ treeLock.release();
+ if (fMergeForward)
+ rc = pTarget->i_createMediumLockList(true /* fFailIfInaccessible */,
+ pTarget /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *aMediumLockList);
+ else
+ rc = i_createMediumLockList(true /* fFailIfInaccessible */,
+ pTarget /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *aMediumLockList);
+ treeLock.acquire();
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+ targetCaller.add();
+ AssertComRCThrowRC(targetCaller.rc());
+ if (FAILED(rc))
+ throw rc;
+
+ /* Sanity checking, must be after lock list creation as it depends on
+ * valid medium states. The medium objects must be accessible. Only
+ * do this if immediate locking is requested, otherwise it fails when
+ * we construct a medium lock list for an already running VM. Snapshot
+ * deletion uses this to simplify its life. */
+ if (fLockMedia)
+ {
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (m->state != MediumState_Created)
+ throw i_setStateError();
+ }
+ {
+ AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS);
+ if (pTarget->m->state != MediumState_Created)
+ throw pTarget->i_setStateError();
+ }
+ }
+
+ /* check medium attachment and other sanity conditions */
+ if (fMergeForward)
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (i_getChildren().size() > 1)
+ {
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium '%s' involved in the merge operation has more than one child medium (%d)"),
+ m->strLocationFull.c_str(), i_getChildren().size());
+ }
+ /* One backreference is only allowed if the machine ID is not empty
+ * and it matches the machine the medium is attached to (including
+ * the snapshot ID if not empty). */
+ if ( m->backRefs.size() != 0
+ && ( !aMachineId
+ || m->backRefs.size() != 1
+ || aMachineId->isZero()
+ || *i_getFirstMachineBackrefId() != *aMachineId
+ || ( (!aSnapshotId || !aSnapshotId->isZero())
+ && *i_getFirstMachineBackrefSnapshotId() != *aSnapshotId)))
+ throw setError(VBOX_E_OBJECT_IN_USE,
+ tr("Medium '%s' is attached to %d virtual machines", "", m->backRefs.size()),
+ m->strLocationFull.c_str(), m->backRefs.size());
+ if (m->type == MediumType_Immutable)
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium '%s' is immutable"),
+ m->strLocationFull.c_str());
+ if (m->type == MediumType_MultiAttach)
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium '%s' is multi-attach"),
+ m->strLocationFull.c_str());
+ }
+ else
+ {
+ AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS);
+ if (pTarget->i_getChildren().size() > 1)
+ {
+ throw setError(VBOX_E_OBJECT_IN_USE,
+ tr("Medium '%s' involved in the merge operation has more than one child medium (%d)"),
+ pTarget->m->strLocationFull.c_str(),
+ pTarget->i_getChildren().size());
+ }
+ if (pTarget->m->type == MediumType_Immutable)
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium '%s' is immutable"),
+ pTarget->m->strLocationFull.c_str());
+ if (pTarget->m->type == MediumType_MultiAttach)
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium '%s' is multi-attach"),
+ pTarget->m->strLocationFull.c_str());
+ }
+ ComObjPtr<Medium> pLast(fMergeForward ? (Medium *)pTarget : this);
+ ComObjPtr<Medium> pLastIntermediate = pLast->i_getParent();
+ for (pLast = pLastIntermediate;
+ !pLast.isNull() && pLast != pTarget && pLast != this;
+ pLast = pLast->i_getParent())
+ {
+ AutoReadLock alock(pLast COMMA_LOCKVAL_SRC_POS);
+ if (pLast->i_getChildren().size() > 1)
+ {
+ throw setError(VBOX_E_OBJECT_IN_USE,
+ tr("Medium '%s' involved in the merge operation has more than one child medium (%d)"),
+ pLast->m->strLocationFull.c_str(),
+ pLast->i_getChildren().size());
+ }
+ if (pLast->m->backRefs.size() != 0)
+ throw setError(VBOX_E_OBJECT_IN_USE,
+ tr("Medium '%s' is attached to %d virtual machines", "", pLast->m->backRefs.size()),
+ pLast->m->strLocationFull.c_str(),
+ pLast->m->backRefs.size());
+
+ }
+
+ /* Update medium states appropriately */
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->state == MediumState_Created)
+ {
+ rc = i_markForDeletion();
+ if (FAILED(rc))
+ throw rc;
+ }
+ else
+ {
+ if (fLockMedia)
+ throw i_setStateError();
+ else if ( m->state == MediumState_LockedWrite
+ || m->state == MediumState_LockedRead)
+ {
+ /* Either mark it for deletion in locked state or allow
+ * others to have done so. */
+ if (m->preLockState == MediumState_Created)
+ i_markLockedForDeletion();
+ else if (m->preLockState != MediumState_Deleting)
+ throw i_setStateError();
+ }
+ else
+ throw i_setStateError();
+ }
+ }
+
+ if (fMergeForward)
+ {
+ /* we will need parent to reparent target */
+ pParentForTarget = i_getParent();
+ }
+ else
+ {
+ /* we will need to reparent children of the source */
+ aChildrenToReparent = new MediumLockList();
+ for (MediaList::const_iterator it = i_getChildren().begin();
+ it != i_getChildren().end();
+ ++it)
+ {
+ pMedium = *it;
+ aChildrenToReparent->Append(pMedium, true /* fLockWrite */);
+ }
+ if (fLockMedia && aChildrenToReparent)
+ {
+ targetCaller.release();
+ autoCaller.release();
+ treeLock.release();
+ rc = aChildrenToReparent->Lock();
+ treeLock.acquire();
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+ targetCaller.add();
+ AssertComRCThrowRC(targetCaller.rc());
+ if (FAILED(rc))
+ throw rc;
+ }
+ }
+ for (pLast = pLastIntermediate;
+ !pLast.isNull() && pLast != pTarget && pLast != this;
+ pLast = pLast->i_getParent())
+ {
+ AutoWriteLock alock(pLast COMMA_LOCKVAL_SRC_POS);
+ if (pLast->m->state == MediumState_Created)
+ {
+ rc = pLast->i_markForDeletion();
+ if (FAILED(rc))
+ throw rc;
+ }
+ else
+ throw pLast->i_setStateError();
+ }
+
+ /* Tweak the lock list in the backward merge case, as the target
+ * isn't marked to be locked for writing yet. */
+ if (!fMergeForward)
+ {
+ MediumLockList::Base::iterator lockListBegin =
+ aMediumLockList->GetBegin();
+ MediumLockList::Base::iterator lockListEnd =
+ aMediumLockList->GetEnd();
+ ++lockListEnd;
+ for (MediumLockList::Base::iterator it = lockListBegin;
+ it != lockListEnd;
+ ++it)
+ {
+ MediumLock &mediumLock = *it;
+ if (mediumLock.GetMedium() == pTarget)
+ {
+ HRESULT rc2 = mediumLock.UpdateLock(true);
+ AssertComRC(rc2);
+ break;
+ }
+ }
+ }
+
+ if (fLockMedia)
+ {
+ targetCaller.release();
+ autoCaller.release();
+ treeLock.release();
+ rc = aMediumLockList->Lock();
+ treeLock.acquire();
+ autoCaller.add();
+ AssertComRCThrowRC(autoCaller.rc());
+ targetCaller.add();
+ AssertComRCThrowRC(targetCaller.rc());
+ if (FAILED(rc))
+ {
+ AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS);
+ throw setError(rc,
+ tr("Failed to lock media when merging to '%s'"),
+ pTarget->i_getLocationFull().c_str());
+ }
+ }
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (FAILED(rc))
+ {
+ if (aMediumLockList)
+ {
+ delete aMediumLockList;
+ aMediumLockList = NULL;
+ }
+ if (aChildrenToReparent)
+ {
+ delete aChildrenToReparent;
+ aChildrenToReparent = NULL;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Merges this medium to the specified medium which must be either its
+ * direct ancestor or descendant.
+ *
+ * Given this medium is SOURCE and the specified medium is TARGET, we will
+ * get two variants of the merge operation:
+ *
+ * forward merge
+ * ------------------------->
+ * [Extra] <- SOURCE <- Intermediate <- TARGET
+ * Any Del Del LockWr
+ *
+ *
+ * backward merge
+ * <-------------------------
+ * TARGET <- Intermediate <- SOURCE <- [Extra]
+ * LockWr Del Del LockWr
+ *
+ * Each diagram shows the involved media on the media chain where
+ * SOURCE and TARGET belong. Under each medium there is a state value which
+ * the medium must have at a time of the mergeTo() call.
+ *
+ * The media in the square braces may be absent (e.g. when the forward
+ * operation takes place and SOURCE is the base medium, or when the backward
+ * merge operation takes place and TARGET is the last child in the chain) but if
+ * they present they are involved too as shown.
+ *
+ * Neither the source medium nor intermediate media may be attached to
+ * any VM directly or in the snapshot, otherwise this method will assert.
+ *
+ * The #i_prepareMergeTo() method must be called prior to this method to place
+ * all involved to necessary states and perform other consistency checks.
+ *
+ * If @a aWait is @c true then this method will perform the operation on the
+ * calling thread and will not return to the caller until the operation is
+ * completed. When this method succeeds, all intermediate medium objects in
+ * the chain will be uninitialized, the state of the target medium (and all
+ * involved extra media) will be restored. @a aMediumLockList will not be
+ * deleted, whether the operation is successful or not. The caller has to do
+ * this if appropriate. Note that this (source) medium is not uninitialized
+ * because of possible AutoCaller instances held by the caller of this method
+ * on the current thread. It's therefore the responsibility of the caller to
+ * call Medium::uninit() after releasing all callers.
+ *
+ * If @a aWait is @c false then this method will create a thread to perform the
+ * operation asynchronously and will return immediately. If the operation
+ * succeeds, the thread will uninitialize the source medium object and all
+ * intermediate medium objects in the chain, reset the state of the target
+ * medium (and all involved extra media) and delete @a aMediumLockList.
+ * If the operation fails, the thread will only reset the states of all
+ * involved media and delete @a aMediumLockList.
+ *
+ * When this method fails (regardless of the @a aWait mode), it is a caller's
+ * responsibility to undo state changes and delete @a aMediumLockList using
+ * #i_cancelMergeTo().
+ *
+ * If @a aProgress is not NULL but the object it points to is @c null then a new
+ * progress object will be created and assigned to @a *aProgress on success,
+ * otherwise the existing progress object is used. If Progress is NULL, then no
+ * progress object is created/used at all. Note that @a aProgress cannot be
+ * NULL when @a aWait is @c false (this method will assert in this case).
+ *
+ * @param pTarget Target medium.
+ * @param fMergeForward Merge direction.
+ * @param pParentForTarget New parent for target medium after merge.
+ * @param aChildrenToReparent List of children of the source which will have
+ * to be reparented to the target after merge.
+ * @param aMediumLockList Medium locking information.
+ * @param aProgress Where to find/store a Progress object to track operation
+ * completion.
+ * @param aWait @c true if this method should block instead of creating
+ * an asynchronous thread.
+ * @param aNotify Notify about mediums which metadatð are changed
+ * during execution of the function.
+ *
+ * @note Locks the tree lock for writing. Locks the media from the chain
+ * for writing.
+ */
+HRESULT Medium::i_mergeTo(const ComObjPtr<Medium> &pTarget,
+ bool fMergeForward,
+ const ComObjPtr<Medium> &pParentForTarget,
+ MediumLockList *aChildrenToReparent,
+ MediumLockList *aMediumLockList,
+ ComObjPtr<Progress> *aProgress,
+ bool aWait, bool aNotify)
+{
+ AssertReturn(pTarget != NULL, E_FAIL);
+ AssertReturn(pTarget != this, E_FAIL);
+ AssertReturn(aMediumLockList != NULL, E_FAIL);
+ AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoCaller targetCaller(pTarget);
+ AssertComRCReturnRC(targetCaller.rc());
+
+ HRESULT rc = S_OK;
+ ComObjPtr<Progress> pProgress;
+ Medium::Task *pTask = NULL;
+
+ try
+ {
+ if (aProgress != NULL)
+ {
+ /* use the existing progress object... */
+ pProgress = *aProgress;
+
+ /* ...but create a new one if it is null */
+ if (pProgress.isNull())
+ {
+ Utf8Str tgtName;
+ {
+ AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS);
+ tgtName = pTarget->i_getName();
+ }
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ pProgress.createObject();
+ rc = pProgress->init(m->pVirtualBox,
+ static_cast<IMedium*>(this),
+ BstrFmt(tr("Merging medium '%s' to '%s'"),
+ i_getName().c_str(),
+ tgtName.c_str()).raw(),
+ TRUE, /* aCancelable */
+ 2, /* Number of opearations */
+ BstrFmt(tr("Resizing medium '%s' before merge"),
+ tgtName.c_str()).raw()
+ );
+ if (FAILED(rc))
+ throw rc;
+ }
+ }
+
+ /* setup task object to carry out the operation sync/async */
+ pTask = new Medium::MergeTask(this, pTarget, fMergeForward,
+ pParentForTarget, aChildrenToReparent,
+ pProgress, aMediumLockList,
+ aWait /* fKeepMediumLockList */,
+ aNotify);
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ if (aWait)
+ {
+ rc = pTask->runNow();
+ delete pTask;
+ }
+ else
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(rc) && aProgress != NULL)
+ *aProgress = pProgress;
+ }
+ else if (pTask != NULL)
+ delete pTask;
+
+ return rc;
+}
+
+/**
+ * Undoes what #i_prepareMergeTo() did. Must be called if #mergeTo() is not
+ * called or fails. Frees memory occupied by @a aMediumLockList and unlocks
+ * the medium objects in @a aChildrenToReparent.
+ *
+ * @param aChildrenToReparent List of children of the source which will have
+ * to be reparented to the target after merge.
+ * @param aMediumLockList Medium locking information.
+ *
+ * @note Locks the tree lock for writing. Locks the media from the chain
+ * for writing.
+ */
+void Medium::i_cancelMergeTo(MediumLockList *aChildrenToReparent,
+ MediumLockList *aMediumLockList)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AssertReturnVoid(aMediumLockList != NULL);
+
+ /* Revert media marked for deletion to previous state. */
+ HRESULT rc;
+ MediumLockList::Base::const_iterator mediumListBegin =
+ aMediumLockList->GetBegin();
+ MediumLockList::Base::const_iterator mediumListEnd =
+ aMediumLockList->GetEnd();
+ for (MediumLockList::Base::const_iterator it = mediumListBegin;
+ it != mediumListEnd;
+ ++it)
+ {
+ const MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+ AutoWriteLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ if (pMedium->m->state == MediumState_Deleting)
+ {
+ rc = pMedium->i_unmarkForDeletion();
+ AssertComRC(rc);
+ }
+ else if ( ( pMedium->m->state == MediumState_LockedWrite
+ || pMedium->m->state == MediumState_LockedRead)
+ && pMedium->m->preLockState == MediumState_Deleting)
+ {
+ rc = pMedium->i_unmarkLockedForDeletion();
+ AssertComRC(rc);
+ }
+ }
+
+ /* the destructor will do the work */
+ delete aMediumLockList;
+
+ /* unlock the children which had to be reparented, the destructor will do
+ * the work */
+ if (aChildrenToReparent)
+ delete aChildrenToReparent;
+}
+
+/**
+ * Resizes the media.
+ *
+ * If @a aWait is @c true then this method will perform the operation on the
+ * calling thread and will not return to the caller until the operation is
+ * completed. When this method succeeds, the state of the target medium (and all
+ * involved extra media) will be restored. @a aMediumLockList will not be
+ * deleted, whether the operation is successful or not. The caller has to do
+ * this if appropriate.
+ *
+ * If @a aWait is @c false then this method will create a thread to perform the
+ * operation asynchronously and will return immediately. The thread will reset
+ * the state of the target medium (and all involved extra media) and delete
+ * @a aMediumLockList.
+ *
+ * When this method fails (regardless of the @a aWait mode), it is a caller's
+ * responsibility to undo state changes and delete @a aMediumLockList.
+ *
+ * If @a aProgress is not NULL but the object it points to is @c null then a new
+ * progress object will be created and assigned to @a *aProgress on success,
+ * otherwise the existing progress object is used. If Progress is NULL, then no
+ * progress object is created/used at all. Note that @a aProgress cannot be
+ * NULL when @a aWait is @c false (this method will assert in this case).
+ *
+ * @param aLogicalSize New nominal capacity of the medium in bytes.
+ * @param aMediumLockList Medium locking information.
+ * @param aProgress Where to find/store a Progress object to track operation
+ * completion.
+ * @param aWait @c true if this method should block instead of creating
+ * an asynchronous thread.
+ * @param aNotify Notify about mediums which metadatð are changed
+ * during execution of the function.
+ *
+ * @note Locks the media from the chain for writing.
+ */
+
+HRESULT Medium::i_resize(uint64_t aLogicalSize,
+ MediumLockList *aMediumLockList,
+ ComObjPtr<Progress> *aProgress,
+ bool aWait,
+ bool aNotify)
+{
+ AssertReturn(aMediumLockList != NULL, E_FAIL);
+ AssertReturn(aProgress != NULL || aWait == true, E_FAIL);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ HRESULT rc = S_OK;
+ ComObjPtr<Progress> pProgress;
+ Medium::Task *pTask = NULL;
+
+ try
+ {
+ if (aProgress != NULL)
+ {
+ /* use the existing progress object... */
+ pProgress = *aProgress;
+
+ /* ...but create a new one if it is null */
+ if (pProgress.isNull())
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ pProgress.createObject();
+ rc = pProgress->init(m->pVirtualBox,
+ static_cast <IMedium *>(this),
+ BstrFmt(tr("Resizing medium '%s'"), m->strLocationFull.c_str()).raw(),
+ TRUE /* aCancelable */);
+ if (FAILED(rc))
+ throw rc;
+ }
+ }
+
+ /* setup task object to carry out the operation asynchronously */
+ pTask = new Medium::ResizeTask(this,
+ aLogicalSize,
+ pProgress,
+ aMediumLockList,
+ aWait /* fKeepMediumLockList */,
+ aNotify);
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ if (aWait)
+ {
+ rc = pTask->runNow();
+ delete pTask;
+ }
+ else
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(rc) && aProgress != NULL)
+ *aProgress = pProgress;
+ }
+ else if (pTask != NULL)
+ delete pTask;
+
+ return rc;
+}
+
+/**
+ * Fix the parent UUID of all children to point to this medium as their
+ * parent.
+ */
+HRESULT Medium::i_fixParentUuidOfChildren(MediumLockList *pChildrenToReparent)
+{
+ /** @todo r=klaus The code below needs to be double checked with regard
+ * to lock order violations, it probably causes lock order issues related
+ * to the AutoCaller usage. Likewise the code using this method seems
+ * problematic. */
+ Assert(!isWriteLockOnCurrentThread());
+ Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+ MediumLockList mediumLockList;
+ HRESULT rc = i_createMediumLockList(true /* fFailIfInaccessible */,
+ NULL /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ this,
+ mediumLockList);
+ AssertComRCReturnRC(rc);
+
+ try
+ {
+ PVDISK hdd;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ try
+ {
+ MediumLockList::Base::iterator lockListBegin =
+ mediumLockList.GetBegin();
+ MediumLockList::Base::iterator lockListEnd =
+ mediumLockList.GetEnd();
+ for (MediumLockList::Base::iterator it = lockListBegin;
+ it != lockListEnd;
+ ++it)
+ {
+ MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+ AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ // open the medium
+ vrc = VDOpen(hdd,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ VD_OPEN_FLAGS_READONLY | m->uOpenFlagsDef,
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+ }
+
+ MediumLockList::Base::iterator childrenBegin = pChildrenToReparent->GetBegin();
+ MediumLockList::Base::iterator childrenEnd = pChildrenToReparent->GetEnd();
+ for (MediumLockList::Base::iterator it = childrenBegin;
+ it != childrenEnd;
+ ++it)
+ {
+ Medium *pMedium = it->GetMedium();
+ /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
+ vrc = VDOpen(hdd,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ VD_OPEN_FLAGS_INFO | m->uOpenFlagsDef,
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+
+ vrc = VDSetParentUuid(hdd, VD_LAST_IMAGE, m->id.raw());
+ if (RT_FAILURE(vrc))
+ throw vrc;
+
+ vrc = VDClose(hdd, false /* fDelete */);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+ }
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+ catch (int aVRC)
+ {
+ rc = setErrorBoth(E_FAIL, aVRC,
+ tr("Could not update medium UUID references to parent '%s' (%s)"),
+ m->strLocationFull.c_str(),
+ i_vdError(aVRC).c_str());
+ }
+
+ VDDestroy(hdd);
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ return rc;
+}
+
+/**
+ *
+ * @note Similar code exists in i_taskExportHandler.
+ */
+HRESULT Medium::i_addRawToFss(const char *aFilename, SecretKeyStore *pKeyStore, RTVFSFSSTREAM hVfsFssDst,
+ const ComObjPtr<Progress> &aProgress, bool fSparse)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Get a readonly hdd for this medium.
+ */
+ MediumCryptoFilterSettings CryptoSettingsRead;
+ MediumLockList SourceMediumLockList;
+ PVDISK pHdd;
+ hrc = i_openForIO(false /*fWritable*/, pKeyStore, &pHdd, &SourceMediumLockList, &CryptoSettingsRead);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Create a VFS file interface to the HDD and attach a progress wrapper
+ * that monitors the progress reading of the raw image. The image will
+ * be read twice if hVfsFssDst does sparse processing.
+ */
+ RTVFSFILE hVfsFileDisk = NIL_RTVFSFILE;
+ int vrc = VDCreateVfsFileFromDisk(pHdd, 0 /*fFlags*/, &hVfsFileDisk);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSFILE hVfsFileProgress = NIL_RTVFSFILE;
+ vrc = RTVfsCreateProgressForFile(hVfsFileDisk, aProgress->i_iprtProgressCallback, &*aProgress,
+ RTVFSPROGRESS_F_CANCELABLE | RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ,
+ VDGetSize(pHdd, VD_LAST_IMAGE) * (fSparse ? 2 : 1) /*cbExpectedRead*/,
+ 0 /*cbExpectedWritten*/, &hVfsFileProgress);
+ RTVfsFileRelease(hVfsFileDisk);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSOBJ hVfsObj = RTVfsObjFromFile(hVfsFileProgress);
+ RTVfsFileRelease(hVfsFileProgress);
+
+ vrc = RTVfsFsStrmAdd(hVfsFssDst, aFilename, hVfsObj, 0 /*fFlags*/);
+ RTVfsObjRelease(hVfsObj);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to add '%s' to output (%Rrc)"), aFilename, vrc);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("RTVfsCreateProgressForFile failed when processing '%s' (%Rrc)"), aFilename, vrc);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("VDCreateVfsFileFromDisk failed for '%s' (%Rrc)"), aFilename, vrc);
+ VDDestroy(pHdd);
+ }
+ }
+ return hrc;
+}
+
+/**
+ * Used by IAppliance to export disk images.
+ *
+ * @param aFilename Filename to create (UTF8).
+ * @param aFormat Medium format for creating @a aFilename.
+ * @param aVariant Which exact image format variant to use for the
+ * destination image.
+ * @param pKeyStore The optional key store for decrypting the data for
+ * encrypted media during the export.
+ * @param hVfsIosDst The destination I/O stream object.
+ * @param aProgress Progress object to use.
+ * @return
+ *
+ * @note The source format is defined by the Medium instance.
+ */
+HRESULT Medium::i_exportFile(const char *aFilename,
+ const ComObjPtr<MediumFormat> &aFormat,
+ MediumVariant_T aVariant,
+ SecretKeyStore *pKeyStore,
+ RTVFSIOSTREAM hVfsIosDst,
+ const ComObjPtr<Progress> &aProgress)
+{
+ AssertPtrReturn(aFilename, E_INVALIDARG);
+ AssertReturn(aFormat.isNotNull(), E_INVALIDARG);
+ AssertReturn(aProgress.isNotNull(), E_INVALIDARG);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Setup VD interfaces.
+ */
+ PVDINTERFACE pVDImageIfaces = m->vdImageIfaces;
+ PVDINTERFACEIO pVfsIoIf;
+ int vrc = VDIfCreateFromVfsStream(hVfsIosDst, RTFILE_O_WRITE, &pVfsIoIf);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = VDInterfaceAdd(&pVfsIoIf->Core, "Medium::ExportTaskVfsIos", VDINTERFACETYPE_IO,
+ pVfsIoIf, sizeof(VDINTERFACEIO), &pVDImageIfaces);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Get a readonly hdd for this medium (source).
+ */
+ MediumCryptoFilterSettings CryptoSettingsRead;
+ MediumLockList SourceMediumLockList;
+ PVDISK pSrcHdd;
+ hrc = i_openForIO(false /*fWritable*/, pKeyStore, &pSrcHdd, &SourceMediumLockList, &CryptoSettingsRead);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Create the target medium.
+ */
+ Utf8Str strDstFormat(aFormat->i_getId());
+
+ /* ensure the target directory exists */
+ uint64_t fDstCapabilities = aFormat->i_getCapabilities();
+ if (fDstCapabilities & MediumFormatCapabilities_File)
+ {
+ Utf8Str strDstLocation(aFilename);
+ hrc = VirtualBox::i_ensureFilePathExists(strDstLocation.c_str(),
+ !(aVariant & MediumVariant_NoCreateDir) /* fCreate */);
+ }
+ if (SUCCEEDED(hrc))
+ {
+ PVDISK pDstHdd;
+ vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &pDstHdd);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Create an interface for getting progress callbacks.
+ */
+ VDINTERFACEPROGRESS ProgressIf = VDINTERFACEPROGRESS_INITALIZER(aProgress->i_vdProgressCallback);
+ PVDINTERFACE pProgress = NULL;
+ vrc = VDInterfaceAdd(&ProgressIf.Core, "export-progress", VDINTERFACETYPE_PROGRESS,
+ &*aProgress, sizeof(ProgressIf), &pProgress);
+ AssertRC(vrc);
+
+ /*
+ * Do the exporting.
+ */
+ vrc = VDCopy(pSrcHdd,
+ VD_LAST_IMAGE,
+ pDstHdd,
+ strDstFormat.c_str(),
+ aFilename,
+ false /* fMoveByRename */,
+ 0 /* cbSize */,
+ aVariant & ~(MediumVariant_NoCreateDir | MediumVariant_Formatted | MediumVariant_VmdkESX | MediumVariant_VmdkRawDisk),
+ NULL /* pDstUuid */,
+ VD_OPEN_FLAGS_NORMAL | VD_OPEN_FLAGS_SEQUENTIAL,
+ pProgress,
+ pVDImageIfaces,
+ NULL);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Could not create the exported medium '%s'%s"),
+ aFilename, i_vdError(vrc).c_str());
+ VDDestroy(pDstHdd);
+ }
+ else
+ hrc = setErrorVrc(vrc);
+ }
+ }
+ VDDestroy(pSrcHdd);
+ }
+ else
+ hrc = setErrorVrc(vrc, "VDInterfaceAdd -> %Rrc", vrc);
+ VDIfDestroyFromVfsStream(pVfsIoIf);
+ }
+ else
+ hrc = setErrorVrc(vrc, "VDIfCreateFromVfsStream -> %Rrc", vrc);
+ }
+ return hrc;
+}
+
+/**
+ * Used by IAppliance to import disk images.
+ *
+ * @param aFilename Filename to read (UTF8).
+ * @param aFormat Medium format for reading @a aFilename.
+ * @param aVariant Which exact image format variant to use
+ * for the destination image.
+ * @param aVfsIosSrc Handle to the source I/O stream.
+ * @param aParent Parent medium. May be NULL.
+ * @param aProgress Progress object to use.
+ * @param aNotify Notify about mediums which metadatð are changed
+ * during execution of the function.
+ * @return
+ * @note The destination format is defined by the Medium instance.
+ *
+ * @todo The only consumer of this method (Appliance::i_importOneDiskImage) is
+ * already on a worker thread, so perhaps consider bypassing the thread
+ * here and run in the task synchronously? VBoxSVC has enough threads as
+ * it is...
+ */
+HRESULT Medium::i_importFile(const char *aFilename,
+ const ComObjPtr<MediumFormat> &aFormat,
+ MediumVariant_T aVariant,
+ RTVFSIOSTREAM aVfsIosSrc,
+ const ComObjPtr<Medium> &aParent,
+ const ComObjPtr<Progress> &aProgress,
+ bool aNotify)
+{
+ /** @todo r=klaus The code below needs to be double checked with regard
+ * to lock order violations, it probably causes lock order issues related
+ * to the AutoCaller usage. */
+ AssertPtrReturn(aFilename, E_INVALIDARG);
+ AssertReturn(!aFormat.isNull(), E_INVALIDARG);
+ AssertReturn(!aProgress.isNull(), E_INVALIDARG);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ HRESULT rc = S_OK;
+ Medium::Task *pTask = NULL;
+
+ try
+ {
+ // locking: we need the tree lock first because we access parent pointers
+ // and we need to write-lock the media involved
+ uint32_t cHandles = 2;
+ LockHandle* pHandles[3] = { &m->pVirtualBox->i_getMediaTreeLockHandle(),
+ this->lockHandle() };
+ /* Only add parent to the lock if it is not null */
+ if (!aParent.isNull())
+ pHandles[cHandles++] = aParent->lockHandle();
+ AutoWriteLock alock(cHandles,
+ pHandles
+ COMMA_LOCKVAL_SRC_POS);
+
+ if ( m->state != MediumState_NotCreated
+ && m->state != MediumState_Created)
+ throw i_setStateError();
+
+ /* Build the target lock list. */
+ MediumLockList *pTargetMediumLockList(new MediumLockList());
+ alock.release();
+ rc = i_createMediumLockList(true /* fFailIfInaccessible */,
+ this /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ aParent,
+ *pTargetMediumLockList);
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pTargetMediumLockList;
+ throw rc;
+ }
+
+ alock.release();
+ rc = pTargetMediumLockList->Lock();
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pTargetMediumLockList;
+ throw setError(rc,
+ tr("Failed to lock target media '%s'"),
+ i_getLocationFull().c_str());
+ }
+
+ /* setup task object to carry out the operation asynchronously */
+ pTask = new Medium::ImportTask(this, aProgress, aFilename, aFormat, aVariant,
+ aVfsIosSrc, aParent, pTargetMediumLockList, false, aNotify);
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+
+ if (m->state == MediumState_NotCreated)
+ m->state = MediumState_Creating;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ rc = pTask->createThread();
+ pTask = NULL;
+ }
+ else if (pTask != NULL)
+ delete pTask;
+
+ return rc;
+}
+
+/**
+ * Internal version of the public CloneTo API which allows to enable certain
+ * optimizations to improve speed during VM cloning.
+ *
+ * @param aTarget Target medium
+ * @param aVariant Which exact image format variant to use
+ * for the destination image.
+ * @param aParent Parent medium. May be NULL.
+ * @param aProgress Progress object to use.
+ * @param idxSrcImageSame The last image in the source chain which has the
+ * same content as the given image in the destination
+ * chain. Use UINT32_MAX to disable this optimization.
+ * @param idxDstImageSame The last image in the destination chain which has the
+ * same content as the given image in the source chain.
+ * Use UINT32_MAX to disable this optimization.
+ * @param aNotify Notify about mediums which metadatð are changed
+ * during execution of the function.
+ * @return
+ */
+HRESULT Medium::i_cloneToEx(const ComObjPtr<Medium> &aTarget, MediumVariant_T aVariant,
+ const ComObjPtr<Medium> &aParent, IProgress **aProgress,
+ uint32_t idxSrcImageSame, uint32_t idxDstImageSame, bool aNotify)
+{
+ /** @todo r=klaus The code below needs to be double checked with regard
+ * to lock order violations, it probably causes lock order issues related
+ * to the AutoCaller usage. */
+ CheckComArgNotNull(aTarget);
+ CheckComArgOutPointerValid(aProgress);
+ ComAssertRet(aTarget != this, E_INVALIDARG);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ HRESULT rc = S_OK;
+ ComObjPtr<Progress> pProgress;
+ Medium::Task *pTask = NULL;
+
+ try
+ {
+ // locking: we need the tree lock first because we access parent pointers
+ // and we need to write-lock the media involved
+ uint32_t cHandles = 3;
+ LockHandle* pHandles[4] = { &m->pVirtualBox->i_getMediaTreeLockHandle(),
+ this->lockHandle(),
+ aTarget->lockHandle() };
+ /* Only add parent to the lock if it is not null */
+ if (!aParent.isNull())
+ pHandles[cHandles++] = aParent->lockHandle();
+ AutoWriteLock alock(cHandles,
+ pHandles
+ COMMA_LOCKVAL_SRC_POS);
+
+ if ( aTarget->m->state != MediumState_NotCreated
+ && aTarget->m->state != MediumState_Created)
+ throw aTarget->i_setStateError();
+
+ /* Build the source lock list. */
+ MediumLockList *pSourceMediumLockList(new MediumLockList());
+ alock.release();
+ rc = i_createMediumLockList(true /* fFailIfInaccessible */,
+ NULL /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pSourceMediumLockList);
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ throw rc;
+ }
+
+ /* Build the target lock list (including the to-be parent chain). */
+ MediumLockList *pTargetMediumLockList(new MediumLockList());
+ alock.release();
+ rc = aTarget->i_createMediumLockList(true /* fFailIfInaccessible */,
+ aTarget /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ aParent,
+ *pTargetMediumLockList);
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ delete pTargetMediumLockList;
+ throw rc;
+ }
+
+ alock.release();
+ rc = pSourceMediumLockList->Lock();
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ delete pTargetMediumLockList;
+ throw setError(rc,
+ tr("Failed to lock source media '%s'"),
+ i_getLocationFull().c_str());
+ }
+ alock.release();
+ rc = pTargetMediumLockList->Lock();
+ alock.acquire();
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ delete pTargetMediumLockList;
+ throw setError(rc,
+ tr("Failed to lock target media '%s'"),
+ aTarget->i_getLocationFull().c_str());
+ }
+
+ pProgress.createObject();
+ rc = pProgress->init(m->pVirtualBox,
+ static_cast <IMedium *>(this),
+ BstrFmt(tr("Creating clone medium '%s'"), aTarget->m->strLocationFull.c_str()).raw(),
+ TRUE /* aCancelable */);
+ if (FAILED(rc))
+ {
+ delete pSourceMediumLockList;
+ delete pTargetMediumLockList;
+ throw rc;
+ }
+
+ /* setup task object to carry out the operation asynchronously */
+ pTask = new Medium::CloneTask(this, pProgress, aTarget, aVariant,
+ aParent, idxSrcImageSame,
+ idxDstImageSame, pSourceMediumLockList,
+ pTargetMediumLockList, false, false, aNotify);
+ rc = pTask->rc();
+ AssertComRC(rc);
+ if (FAILED(rc))
+ throw rc;
+
+ if (aTarget->m->state == MediumState_NotCreated)
+ aTarget->m->state = MediumState_Creating;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(rc))
+ pProgress.queryInterfaceTo(aProgress);
+ }
+ else if (pTask != NULL)
+ delete pTask;
+
+ return rc;
+}
+
+/**
+ * Returns the key identifier for this medium if encryption is configured.
+ *
+ * @returns Key identifier or empty string if no encryption is configured.
+ */
+const Utf8Str& Medium::i_getKeyId()
+{
+ ComObjPtr<Medium> pBase = i_getBase();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ settings::StringsMap::const_iterator it = pBase->m->mapProperties.find("CRYPT/KeyId");
+ if (it == pBase->m->mapProperties.end())
+ return Utf8Str::Empty;
+
+ return it->second;
+}
+
+
+/**
+ * Returns all filter related properties.
+ *
+ * @returns COM status code.
+ * @param aReturnNames Where to store the properties names on success.
+ * @param aReturnValues Where to store the properties values on success.
+ */
+HRESULT Medium::i_getFilterProperties(std::vector<com::Utf8Str> &aReturnNames,
+ std::vector<com::Utf8Str> &aReturnValues)
+{
+ std::vector<com::Utf8Str> aPropNames;
+ std::vector<com::Utf8Str> aPropValues;
+ HRESULT hrc = getProperties(Utf8Str(""), aPropNames, aPropValues);
+
+ if (SUCCEEDED(hrc))
+ {
+ unsigned cReturnSize = 0;
+ aReturnNames.resize(0);
+ aReturnValues.resize(0);
+ for (unsigned idx = 0; idx < aPropNames.size(); idx++)
+ {
+ if (i_isPropertyForFilter(aPropNames[idx]))
+ {
+ aReturnNames.resize(cReturnSize + 1);
+ aReturnValues.resize(cReturnSize + 1);
+ aReturnNames[cReturnSize] = aPropNames[idx];
+ aReturnValues[cReturnSize] = aPropValues[idx];
+ cReturnSize++;
+ }
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * Preparation to move this medium to a new location
+ *
+ * @param aLocation Location of the storage unit. If the location is a FS-path,
+ * then it can be relative to the VirtualBox home directory.
+ *
+ * @note Must be called from under this object's write lock.
+ */
+HRESULT Medium::i_preparationForMoving(const Utf8Str &aLocation)
+{
+ HRESULT rc = E_FAIL;
+
+ if (i_getLocationFull() != aLocation)
+ {
+ m->strNewLocationFull = aLocation;
+ m->fMoveThisMedium = true;
+ rc = S_OK;
+ }
+
+ return rc;
+}
+
+/**
+ * Checking whether current operation "moving" or not
+ */
+bool Medium::i_isMoveOperation(const ComObjPtr<Medium> &aTarget) const
+{
+ RT_NOREF(aTarget);
+ return m->fMoveThisMedium;
+}
+
+bool Medium::i_resetMoveOperationData()
+{
+ m->strNewLocationFull.setNull();
+ m->fMoveThisMedium = false;
+ return true;
+}
+
+Utf8Str Medium::i_getNewLocationForMoving() const
+{
+ if (m->fMoveThisMedium == true)
+ return m->strNewLocationFull;
+ else
+ return Utf8Str();
+}
+////////////////////////////////////////////////////////////////////////////////
+//
+// Private methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Queries information from the medium.
+ *
+ * As a result of this call, the accessibility state and data members such as
+ * size and description will be updated with the current information.
+ *
+ * @note This method may block during a system I/O call that checks storage
+ * accessibility.
+ *
+ * @note Caller MUST NOT hold the media tree or medium lock.
+ *
+ * @note Locks m->pParent for reading. Locks this object for writing.
+ *
+ * @param fSetImageId Whether to reset the UUID contained in the image file
+ * to the UUID in the medium instance data (see SetIDs())
+ * @param fSetParentId Whether to reset the parent UUID contained in the image
+ * file to the parent UUID in the medium instance data (see
+ * SetIDs())
+ * @param autoCaller
+ * @return
+ */
+HRESULT Medium::i_queryInfo(bool fSetImageId, bool fSetParentId, AutoCaller &autoCaller)
+{
+ Assert(!isWriteLockOnCurrentThread());
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( ( m->state != MediumState_Created
+ && m->state != MediumState_Inaccessible
+ && m->state != MediumState_LockedRead)
+ || m->fClosing)
+ return E_FAIL;
+
+ HRESULT rc = S_OK;
+
+ int vrc = VINF_SUCCESS;
+
+ /* check if a blocking i_queryInfo() call is in progress on some other thread,
+ * and wait for it to finish if so instead of querying data ourselves */
+ if (m->queryInfoRunning)
+ {
+ Assert( m->state == MediumState_LockedRead
+ || m->state == MediumState_LockedWrite);
+
+ while (m->queryInfoRunning)
+ {
+ alock.release();
+ /* must not hold the object lock now */
+ Assert(!isWriteLockOnCurrentThread());
+ {
+ AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS);
+ }
+ alock.acquire();
+ }
+
+ return S_OK;
+ }
+
+ bool success = false;
+ Utf8Str lastAccessError;
+
+ /* are we dealing with a new medium constructed using the existing
+ * location? */
+ bool isImport = m->id.isZero();
+ unsigned uOpenFlags = VD_OPEN_FLAGS_INFO;
+
+ /* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new
+ * media because that would prevent necessary modifications
+ * when opening media of some third-party formats for the first
+ * time in VirtualBox (such as VMDK for which VDOpen() needs to
+ * generate an UUID if it is missing) */
+ if ( m->hddOpenMode == OpenReadOnly
+ || m->type == MediumType_Readonly
+ || (!isImport && !fSetImageId && !fSetParentId)
+ )
+ uOpenFlags |= VD_OPEN_FLAGS_READONLY;
+
+ /* Open shareable medium with the appropriate flags */
+ if (m->type == MediumType_Shareable)
+ uOpenFlags |= VD_OPEN_FLAGS_SHAREABLE;
+
+ /* Lock the medium, which makes the behavior much more consistent, must be
+ * done before dropping the object lock and setting queryInfoRunning. */
+ ComPtr<IToken> pToken;
+ if (uOpenFlags & (VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_SHAREABLE))
+ rc = LockRead(pToken.asOutParam());
+ else
+ rc = LockWrite(pToken.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ /* Copies of the input state fields which are not read-only,
+ * as we're dropping the lock. CAUTION: be extremely careful what
+ * you do with the contents of this medium object, as you will
+ * create races if there are concurrent changes. */
+ Utf8Str format(m->strFormat);
+ Utf8Str location(m->strLocationFull);
+ ComObjPtr<MediumFormat> formatObj = m->formatObj;
+
+ /* "Output" values which can't be set because the lock isn't held
+ * at the time the values are determined. */
+ Guid mediumId = m->id;
+ uint64_t mediumSize = 0;
+ uint64_t mediumLogicalSize = 0;
+
+ /* Flag whether a base image has a non-zero parent UUID and thus
+ * need repairing after it was closed again. */
+ bool fRepairImageZeroParentUuid = false;
+
+ ComObjPtr<VirtualBox> pVirtualBox = m->pVirtualBox;
+
+ /* must be set before leaving the object lock the first time */
+ m->queryInfoRunning = true;
+
+ /* must leave object lock now, because a lock from a higher lock class
+ * is needed and also a lengthy operation is coming */
+ alock.release();
+ autoCaller.release();
+
+ /* Note that taking the queryInfoSem after leaving the object lock above
+ * can lead to short spinning of the loops waiting for i_queryInfo() to
+ * complete. This is unavoidable since the other order causes a lock order
+ * violation: here it would be requesting the object lock (at the beginning
+ * of the method), then queryInfoSem, and below the other way round. */
+ AutoWriteLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS);
+
+ /* take the opportunity to have a media tree lock, released initially */
+ Assert(!isWriteLockOnCurrentThread());
+ Assert(!pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+ AutoWriteLock treeLock(pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+ treeLock.release();
+
+ /* re-take the caller, but not the object lock, to keep uninit away */
+ autoCaller.add();
+ if (FAILED(autoCaller.rc()))
+ {
+ m->queryInfoRunning = false;
+ return autoCaller.rc();
+ }
+
+ try
+ {
+ /* skip accessibility checks for host drives */
+ if (m->hostDrive)
+ {
+ success = true;
+ throw S_OK;
+ }
+
+ PVDISK hdd;
+ vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ try
+ {
+ /** @todo This kind of opening of media is assuming that diff
+ * media can be opened as base media. Should be documented that
+ * it must work for all medium format backends. */
+ vrc = VDOpen(hdd,
+ format.c_str(),
+ location.c_str(),
+ uOpenFlags | m->uOpenFlagsDef,
+ m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ {
+ lastAccessError = Utf8StrFmt(tr("Could not open the medium '%s'%s"),
+ location.c_str(), i_vdError(vrc).c_str());
+ throw S_OK;
+ }
+
+ if (formatObj->i_getCapabilities() & MediumFormatCapabilities_Uuid)
+ {
+ /* Modify the UUIDs if necessary. The associated fields are
+ * not modified by other code, so no need to copy. */
+ if (fSetImageId)
+ {
+ alock.acquire();
+ vrc = VDSetUuid(hdd, 0, m->uuidImage.raw());
+ alock.release();
+ if (RT_FAILURE(vrc))
+ {
+ lastAccessError = Utf8StrFmt(tr("Could not update the UUID of medium '%s'%s"),
+ location.c_str(), i_vdError(vrc).c_str());
+ throw S_OK;
+ }
+ mediumId = m->uuidImage;
+ }
+ if (fSetParentId)
+ {
+ alock.acquire();
+ vrc = VDSetParentUuid(hdd, 0, m->uuidParentImage.raw());
+ alock.release();
+ if (RT_FAILURE(vrc))
+ {
+ lastAccessError = Utf8StrFmt(tr("Could not update the parent UUID of medium '%s'%s"),
+ location.c_str(), i_vdError(vrc).c_str());
+ throw S_OK;
+ }
+ }
+ /* zap the information, these are no long-term members */
+ alock.acquire();
+ unconst(m->uuidImage).clear();
+ unconst(m->uuidParentImage).clear();
+ alock.release();
+
+ /* check the UUID */
+ RTUUID uuid;
+ vrc = VDGetUuid(hdd, 0, &uuid);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ if (isImport)
+ {
+ mediumId = uuid;
+
+ if (mediumId.isZero() && (m->hddOpenMode == OpenReadOnly))
+ // only when importing a VDMK that has no UUID, create one in memory
+ mediumId.create();
+ }
+ else
+ {
+ Assert(!mediumId.isZero());
+
+ if (mediumId != uuid)
+ {
+ /** @todo r=klaus this always refers to VirtualBox.xml as the medium registry, even for new VMs */
+ lastAccessError = Utf8StrFmt(
+ tr("UUID {%RTuuid} of the medium '%s' does not match the value {%RTuuid} stored in the media registry ('%s')"),
+ &uuid,
+ location.c_str(),
+ mediumId.raw(),
+ pVirtualBox->i_settingsFilePath().c_str());
+ throw S_OK;
+ }
+ }
+ }
+ else
+ {
+ /* the backend does not support storing UUIDs within the
+ * underlying storage so use what we store in XML */
+
+ if (fSetImageId)
+ {
+ /* set the UUID if an API client wants to change it */
+ alock.acquire();
+ mediumId = m->uuidImage;
+ alock.release();
+ }
+ else if (isImport)
+ {
+ /* generate an UUID for an imported UUID-less medium */
+ mediumId.create();
+ }
+ }
+
+ /* set the image uuid before the below parent uuid handling code
+ * might place it somewhere in the media tree, so that the medium
+ * UUID is valid at this point */
+ alock.acquire();
+ if (isImport || fSetImageId)
+ unconst(m->id) = mediumId;
+ alock.release();
+
+ /* get the medium variant */
+ unsigned uImageFlags;
+ vrc = VDGetImageFlags(hdd, 0, &uImageFlags);
+ ComAssertRCThrow(vrc, E_FAIL);
+ alock.acquire();
+ m->variant = (MediumVariant_T)uImageFlags;
+ alock.release();
+
+ /* check/get the parent uuid and update corresponding state */
+ if (uImageFlags & VD_IMAGE_FLAGS_DIFF)
+ {
+ RTUUID parentId;
+ vrc = VDGetParentUuid(hdd, 0, &parentId);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ /* streamOptimized VMDK images are only accepted as base
+ * images, as this allows automatic repair of OVF appliances.
+ * Since such images don't support random writes they will not
+ * be created for diff images. Only an overly smart user might
+ * manually create this case. Too bad for him. */
+ if ( (isImport || fSetParentId)
+ && !(uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED))
+ {
+ /* the parent must be known to us. Note that we freely
+ * call locking methods of mVirtualBox and parent, as all
+ * relevant locks must be already held. There may be no
+ * concurrent access to the just opened medium on other
+ * threads yet (and init() will fail if this method reports
+ * MediumState_Inaccessible) */
+
+ ComObjPtr<Medium> pParent;
+ if (RTUuidIsNull(&parentId))
+ rc = VBOX_E_OBJECT_NOT_FOUND;
+ else
+ rc = pVirtualBox->i_findHardDiskById(Guid(parentId), false /* aSetError */, &pParent);
+ if (FAILED(rc))
+ {
+ if (fSetImageId && !fSetParentId)
+ {
+ /* If the image UUID gets changed for an existing
+ * image then the parent UUID can be stale. In such
+ * cases clear the parent information. The parent
+ * information may/will be re-set later if the
+ * API client wants to adjust a complete medium
+ * hierarchy one by one. */
+ rc = S_OK;
+ alock.acquire();
+ RTUuidClear(&parentId);
+ vrc = VDSetParentUuid(hdd, 0, &parentId);
+ alock.release();
+ ComAssertRCThrow(vrc, E_FAIL);
+ }
+ else
+ {
+ lastAccessError = Utf8StrFmt(tr("Parent medium with UUID {%RTuuid} of the medium '%s' is not found in the media registry ('%s')"),
+ &parentId, location.c_str(),
+ pVirtualBox->i_settingsFilePath().c_str());
+ throw S_OK;
+ }
+ }
+
+ /* must drop the caller before taking the tree lock */
+ autoCaller.release();
+ /* we set m->pParent & children() */
+ treeLock.acquire();
+ autoCaller.add();
+ if (FAILED(autoCaller.rc()))
+ throw autoCaller.rc();
+
+ if (m->pParent)
+ i_deparent();
+
+ if (!pParent.isNull())
+ if (pParent->i_getDepth() >= SETTINGS_MEDIUM_DEPTH_MAX)
+ {
+ AutoReadLock plock(pParent COMMA_LOCKVAL_SRC_POS);
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot open differencing image for medium '%s', because it exceeds the medium tree depth limit. Please merge some images which you no longer need"),
+ pParent->m->strLocationFull.c_str());
+ }
+ i_setParent(pParent);
+
+ treeLock.release();
+ }
+ else
+ {
+ /* must drop the caller before taking the tree lock */
+ autoCaller.release();
+ /* we access m->pParent */
+ treeLock.acquire();
+ autoCaller.add();
+ if (FAILED(autoCaller.rc()))
+ throw autoCaller.rc();
+
+ /* check that parent UUIDs match. Note that there's no need
+ * for the parent's AutoCaller (our lifetime is bound to
+ * it) */
+
+ if (m->pParent.isNull())
+ {
+ /* Due to a bug in VDCopy() in VirtualBox 3.0.0-3.0.14
+ * and 3.1.0-3.1.8 there are base images out there
+ * which have a non-zero parent UUID. No point in
+ * complaining about them, instead automatically
+ * repair the problem. Later we can bring back the
+ * error message, but we should wait until really
+ * most users have repaired their images, either with
+ * VBoxFixHdd or this way. */
+#if 1
+ fRepairImageZeroParentUuid = true;
+#else /* 0 */
+ lastAccessError = Utf8StrFmt(
+ tr("Medium type of '%s' is differencing but it is not associated with any parent medium in the media registry ('%s')"),
+ location.c_str(),
+ pVirtualBox->settingsFilePath().c_str());
+ treeLock.release();
+ throw S_OK;
+#endif /* 0 */
+ }
+
+ {
+ autoCaller.release();
+ AutoReadLock parentLock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ autoCaller.add();
+ if (FAILED(autoCaller.rc()))
+ throw autoCaller.rc();
+
+ if ( !fRepairImageZeroParentUuid
+ && m->pParent->i_getState() != MediumState_Inaccessible
+ && m->pParent->i_getId() != parentId)
+ {
+ /** @todo r=klaus this always refers to VirtualBox.xml as the medium registry, even for new VMs */
+ lastAccessError = Utf8StrFmt(
+ tr("Parent UUID {%RTuuid} of the medium '%s' does not match UUID {%RTuuid} of its parent medium stored in the media registry ('%s')"),
+ &parentId, location.c_str(),
+ m->pParent->i_getId().raw(),
+ pVirtualBox->i_settingsFilePath().c_str());
+ parentLock.release();
+ treeLock.release();
+ throw S_OK;
+ }
+ }
+
+ /// @todo NEWMEDIA what to do if the parent is not
+ /// accessible while the diff is? Probably nothing. The
+ /// real code will detect the mismatch anyway.
+
+ treeLock.release();
+ }
+ }
+
+ mediumSize = VDGetFileSize(hdd, 0);
+ mediumLogicalSize = VDGetSize(hdd, 0);
+
+ success = true;
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ vrc = VDDestroy(hdd);
+ if (RT_FAILURE(vrc))
+ {
+ lastAccessError = Utf8StrFmt(tr("Could not update and close the medium '%s'%s"),
+ location.c_str(), i_vdError(vrc).c_str());
+ success = false;
+ throw S_OK;
+ }
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ autoCaller.release();
+ treeLock.acquire();
+ autoCaller.add();
+ if (FAILED(autoCaller.rc()))
+ {
+ m->queryInfoRunning = false;
+ return autoCaller.rc();
+ }
+ alock.acquire();
+
+ if (success)
+ {
+ m->size = mediumSize;
+ m->logicalSize = mediumLogicalSize;
+ m->strLastAccessError.setNull();
+ }
+ else
+ {
+ m->strLastAccessError = lastAccessError;
+ Log1WarningFunc(("'%s' is not accessible (error='%s', rc=%Rhrc, vrc=%Rrc)\n",
+ location.c_str(), m->strLastAccessError.c_str(), rc, vrc));
+ }
+
+ /* Set the proper state according to the result of the check */
+ if (success)
+ m->preLockState = MediumState_Created;
+ else
+ m->preLockState = MediumState_Inaccessible;
+
+ /* unblock anyone waiting for the i_queryInfo results */
+ qlock.release();
+ m->queryInfoRunning = false;
+
+ pToken->Abandon();
+ pToken.setNull();
+
+ if (FAILED(rc))
+ return rc;
+
+ /* If this is a base image which incorrectly has a parent UUID set,
+ * repair the image now by zeroing the parent UUID. This is only done
+ * when we have structural information from a config file, on import
+ * this is not possible. If someone would accidentally call openMedium
+ * with a diff image before the base is registered this would destroy
+ * the diff. Not acceptable. */
+ do
+ {
+ if (fRepairImageZeroParentUuid)
+ {
+ rc = LockWrite(pToken.asOutParam());
+ if (FAILED(rc))
+ break;
+
+ alock.release();
+
+ try
+ {
+ PVDISK hdd;
+ vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ try
+ {
+ vrc = VDOpen(hdd,
+ format.c_str(),
+ location.c_str(),
+ (uOpenFlags & ~VD_OPEN_FLAGS_READONLY) | m->uOpenFlagsDef,
+ m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw S_OK;
+
+ RTUUID zeroParentUuid;
+ RTUuidClear(&zeroParentUuid);
+ vrc = VDSetParentUuid(hdd, 0, &zeroParentUuid);
+ ComAssertRCThrow(vrc, E_FAIL);
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ VDDestroy(hdd);
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ pToken->Abandon();
+ pToken.setNull();
+ if (FAILED(rc))
+ break;
+ }
+ } while(0);
+
+ return rc;
+}
+
+/**
+ * Performs extra checks if the medium can be closed and returns S_OK in
+ * this case. Otherwise, returns a respective error message. Called by
+ * Close() under the medium tree lock and the medium lock.
+ *
+ * @note Also reused by Medium::Reset().
+ *
+ * @note Caller must hold the media tree write lock!
+ */
+HRESULT Medium::i_canClose()
+{
+ Assert(m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+
+ if (i_getChildren().size() != 0)
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Cannot close medium '%s' because it has %d child media", "", i_getChildren().size()),
+ m->strLocationFull.c_str(), i_getChildren().size());
+
+ return S_OK;
+}
+
+/**
+ * Unregisters this medium with mVirtualBox. Called by close() under the medium tree lock.
+ *
+ * @note Caller must have locked the media tree lock for writing!
+ */
+HRESULT Medium::i_unregisterWithVirtualBox()
+{
+ /* Note that we need to de-associate ourselves from the parent to let
+ * VirtualBox::i_unregisterMedium() properly save the registry */
+
+ /* we modify m->pParent and access children */
+ Assert(m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+
+ Medium *pParentBackup = m->pParent;
+ AssertReturn(i_getChildren().size() == 0, E_FAIL);
+ if (m->pParent)
+ i_deparent();
+
+ HRESULT rc = m->pVirtualBox->i_unregisterMedium(this);
+ if (FAILED(rc))
+ {
+ if (pParentBackup)
+ {
+ // re-associate with the parent as we are still relatives in the registry
+ i_setParent(pParentBackup);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Like SetProperty but do not trigger a settings store. Only for internal use!
+ */
+HRESULT Medium::i_setPropertyDirect(const Utf8Str &aName, const Utf8Str &aValue)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock mlock(this COMMA_LOCKVAL_SRC_POS);
+
+ switch (m->state)
+ {
+ case MediumState_Created:
+ case MediumState_Inaccessible:
+ break;
+ default:
+ return i_setStateError();
+ }
+
+ m->mapProperties[aName] = aValue;
+
+ return S_OK;
+}
+
+/**
+ * Sets the extended error info according to the current media state.
+ *
+ * @note Must be called from under this object's write or read lock.
+ */
+HRESULT Medium::i_setStateError()
+{
+ HRESULT rc = E_FAIL;
+
+ switch (m->state)
+ {
+ case MediumState_NotCreated:
+ {
+ rc = setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Storage for the medium '%s' is not created"),
+ m->strLocationFull.c_str());
+ break;
+ }
+ case MediumState_Created:
+ {
+ rc = setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Storage for the medium '%s' is already created"),
+ m->strLocationFull.c_str());
+ break;
+ }
+ case MediumState_LockedRead:
+ {
+ rc = setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium '%s' is locked for reading by another task"),
+ m->strLocationFull.c_str());
+ break;
+ }
+ case MediumState_LockedWrite:
+ {
+ rc = setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium '%s' is locked for writing by another task"),
+ m->strLocationFull.c_str());
+ break;
+ }
+ case MediumState_Inaccessible:
+ {
+ /* be in sync with Console::powerUpThread() */
+ if (!m->strLastAccessError.isEmpty())
+ rc = setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium '%s' is not accessible. %s"),
+ m->strLocationFull.c_str(), m->strLastAccessError.c_str());
+ else
+ rc = setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Medium '%s' is not accessible"),
+ m->strLocationFull.c_str());
+ break;
+ }
+ case MediumState_Creating:
+ {
+ rc = setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Storage for the medium '%s' is being created"),
+ m->strLocationFull.c_str());
+ break;
+ }
+ case MediumState_Deleting:
+ {
+ rc = setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Storage for the medium '%s' is being deleted"),
+ m->strLocationFull.c_str());
+ break;
+ }
+ default:
+ {
+ AssertFailed();
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Sets the value of m->strLocationFull. The given location must be a fully
+ * qualified path; relative paths are not supported here.
+ *
+ * As a special exception, if the specified location is a file path that ends with '/'
+ * then the file name part will be generated by this method automatically in the format
+ * '{\<uuid\>}.\<ext\>' where \<uuid\> is a fresh UUID that this method will generate
+ * and assign to this medium, and \<ext\> is the default extension for this
+ * medium's storage format. Note that this procedure requires the media state to
+ * be NotCreated and will return a failure otherwise.
+ *
+ * @param aLocation Location of the storage unit. If the location is a FS-path,
+ * then it can be relative to the VirtualBox home directory.
+ * @param aFormat Optional fallback format if it is an import and the format
+ * cannot be determined.
+ *
+ * @note Must be called from under this object's write lock.
+ */
+HRESULT Medium::i_setLocation(const Utf8Str &aLocation,
+ const Utf8Str &aFormat /* = Utf8Str::Empty */)
+{
+ AssertReturn(!aLocation.isEmpty(), E_FAIL);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /* formatObj may be null only when initializing from an existing path and
+ * no format is known yet */
+ AssertReturn( (!m->strFormat.isEmpty() && !m->formatObj.isNull())
+ || ( getObjectState().getState() == ObjectState::InInit
+ && m->state != MediumState_NotCreated
+ && m->id.isZero()
+ && m->strFormat.isEmpty()
+ && m->formatObj.isNull()),
+ E_FAIL);
+
+ /* are we dealing with a new medium constructed using the existing
+ * location? */
+ bool isImport = m->strFormat.isEmpty();
+
+ if ( isImport
+ || ( (m->formatObj->i_getCapabilities() & MediumFormatCapabilities_File)
+ && !m->hostDrive))
+ {
+ Guid id;
+
+ Utf8Str locationFull(aLocation);
+
+ if (m->state == MediumState_NotCreated)
+ {
+ /* must be a file (formatObj must be already known) */
+ Assert(m->formatObj->i_getCapabilities() & MediumFormatCapabilities_File);
+
+ if (RTPathFilename(aLocation.c_str()) == NULL)
+ {
+ /* no file name is given (either an empty string or ends with a
+ * slash), generate a new UUID + file name if the state allows
+ * this */
+
+ ComAssertMsgRet(!m->formatObj->i_getFileExtensions().empty(),
+ (tr("Must be at least one extension if it is MediumFormatCapabilities_File\n")),
+ E_FAIL);
+
+ Utf8Str strExt = m->formatObj->i_getFileExtensions().front();
+ ComAssertMsgRet(!strExt.isEmpty(),
+ (tr("Default extension must not be empty\n")),
+ E_FAIL);
+
+ id.create();
+
+ locationFull = Utf8StrFmt("%s{%RTuuid}.%s",
+ aLocation.c_str(), id.raw(), strExt.c_str());
+ }
+ }
+
+ // we must always have full paths now (if it refers to a file)
+ if ( ( m->formatObj.isNull()
+ || m->formatObj->i_getCapabilities() & MediumFormatCapabilities_File)
+ && !RTPathStartsWithRoot(locationFull.c_str()))
+ return setError(VBOX_E_FILE_ERROR,
+ tr("The given path '%s' is not fully qualified"),
+ locationFull.c_str());
+
+ /* detect the backend from the storage unit if importing */
+ if (isImport)
+ {
+ VDTYPE const enmDesiredType = i_convertDeviceType();
+ VDTYPE enmType = VDTYPE_INVALID;
+ char *backendName = NULL;
+
+ /* is it a file? */
+ RTFILE hFile;
+ int vrc = RTFileOpen(&hFile, locationFull.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(vrc))
+ {
+ RTFileClose(hFile);
+ vrc = VDGetFormat(NULL /* pVDIfsDisk */, NULL /* pVDIfsImage */,
+ locationFull.c_str(), enmDesiredType, &backendName, &enmType);
+ }
+ else if ( vrc != VERR_FILE_NOT_FOUND
+ && vrc != VERR_PATH_NOT_FOUND
+ && vrc != VERR_ACCESS_DENIED
+ && locationFull != aLocation)
+ {
+ /* assume it's not a file, restore the original location */
+ locationFull = aLocation;
+ vrc = VDGetFormat(NULL /* pVDIfsDisk */, NULL /* pVDIfsImage */,
+ locationFull.c_str(), enmDesiredType, &backendName, &enmType);
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_ACCESS_DENIED)
+ return setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Permission problem accessing the file for the medium '%s' (%Rrc)"),
+ locationFull.c_str(), vrc);
+ if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
+ return setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not find file for the medium '%s' (%Rrc)"),
+ locationFull.c_str(), vrc);
+ if (aFormat.isEmpty())
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not get the storage format of the medium '%s' (%Rrc)"),
+ locationFull.c_str(), vrc);
+ HRESULT rc = i_setFormat(aFormat);
+ /* setFormat() must not fail since we've just used the backend so
+ * the format object must be there */
+ AssertComRCReturnRC(rc);
+ }
+ else if ( enmType == VDTYPE_INVALID
+ || m->devType != i_convertToDeviceType(enmType))
+ {
+ /*
+ * The user tried to use a image as a device which is not supported
+ * by the backend.
+ */
+ RTStrFree(backendName);
+ return setError(E_FAIL,
+ tr("The medium '%s' can't be used as the requested device type (%s, detected %s)"),
+ locationFull.c_str(), getDeviceTypeName(m->devType), getVDTypeName(enmType));
+ }
+ else
+ {
+ ComAssertRet(backendName != NULL && *backendName != '\0', E_FAIL);
+
+ HRESULT rc = i_setFormat(backendName);
+ RTStrFree(backendName);
+
+ /* setFormat() must not fail since we've just used the backend so
+ * the format object must be there */
+ AssertComRCReturnRC(rc);
+ }
+ }
+
+ m->strLocationFull = locationFull;
+
+ /* is it still a file? */
+ if ( (m->formatObj->i_getCapabilities() & MediumFormatCapabilities_File)
+ && (m->state == MediumState_NotCreated)
+ )
+ /* assign a new UUID (this UUID will be used when calling
+ * VDCreateBase/VDCreateDiff as a wanted UUID). Note that we
+ * also do that if we didn't generate it to make sure it is
+ * either generated by us or reset to null */
+ unconst(m->id) = id;
+ }
+ else
+ m->strLocationFull = aLocation;
+
+ return S_OK;
+}
+
+/**
+ * Checks that the format ID is valid and sets it on success.
+ *
+ * Note that this method will caller-reference the format object on success!
+ * This reference must be released somewhere to let the MediumFormat object be
+ * uninitialized.
+ *
+ * @note Must be called from under this object's write lock.
+ */
+HRESULT Medium::i_setFormat(const Utf8Str &aFormat)
+{
+ /* get the format object first */
+ {
+ SystemProperties *pSysProps = m->pVirtualBox->i_getSystemProperties();
+ AutoReadLock propsLock(pSysProps COMMA_LOCKVAL_SRC_POS);
+
+ unconst(m->formatObj) = pSysProps->i_mediumFormat(aFormat);
+ if (m->formatObj.isNull())
+ return setError(E_INVALIDARG,
+ tr("Invalid medium storage format '%s'"),
+ aFormat.c_str());
+
+ /* get properties (preinsert them as keys in the map). Note that the
+ * map doesn't grow over the object life time since the set of
+ * properties is meant to be constant. */
+
+ Assert(m->mapProperties.empty());
+
+ for (MediumFormat::PropertyArray::const_iterator it = m->formatObj->i_getProperties().begin();
+ it != m->formatObj->i_getProperties().end();
+ ++it)
+ {
+ m->mapProperties.insert(std::make_pair(it->strName, Utf8Str::Empty));
+ }
+ }
+
+ unconst(m->strFormat) = aFormat;
+
+ return S_OK;
+}
+
+/**
+ * Converts the Medium device type to the VD type.
+ */
+VDTYPE Medium::i_convertDeviceType()
+{
+ VDTYPE enmType;
+
+ switch (m->devType)
+ {
+ case DeviceType_HardDisk:
+ enmType = VDTYPE_HDD;
+ break;
+ case DeviceType_DVD:
+ enmType = VDTYPE_OPTICAL_DISC;
+ break;
+ case DeviceType_Floppy:
+ enmType = VDTYPE_FLOPPY;
+ break;
+ default:
+ ComAssertFailedRet(VDTYPE_INVALID);
+ }
+
+ return enmType;
+}
+
+/**
+ * Converts from the VD type to the medium type.
+ */
+DeviceType_T Medium::i_convertToDeviceType(VDTYPE enmType)
+{
+ DeviceType_T devType;
+
+ switch (enmType)
+ {
+ case VDTYPE_HDD:
+ devType = DeviceType_HardDisk;
+ break;
+ case VDTYPE_OPTICAL_DISC:
+ devType = DeviceType_DVD;
+ break;
+ case VDTYPE_FLOPPY:
+ devType = DeviceType_Floppy;
+ break;
+ default:
+ ComAssertFailedRet(DeviceType_Null);
+ }
+
+ return devType;
+}
+
+/**
+ * Internal method which checks whether a property name is for a filter plugin.
+ */
+bool Medium::i_isPropertyForFilter(const com::Utf8Str &aName)
+{
+ /* If the name contains "/" use the part before as a filter name and lookup the filter. */
+ size_t offSlash;
+ if ((offSlash = aName.find("/", 0)) != aName.npos)
+ {
+ com::Utf8Str strFilter;
+ com::Utf8Str strKey;
+
+ HRESULT rc = strFilter.assignEx(aName, 0, offSlash);
+ if (FAILED(rc))
+ return false;
+
+ rc = strKey.assignEx(aName, offSlash + 1, aName.length() - offSlash - 1); /* Skip slash */
+ if (FAILED(rc))
+ return false;
+
+ VDFILTERINFO FilterInfo;
+ int vrc = VDFilterInfoOne(strFilter.c_str(), &FilterInfo);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Check that the property exists. */
+ PCVDCONFIGINFO paConfig = FilterInfo.paConfigInfo;
+ while (paConfig->pszKey)
+ {
+ if (strKey.equals(paConfig->pszKey))
+ return true;
+ paConfig++;
+ }
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Returns the last error message collected by the i_vdErrorCall callback and
+ * resets it.
+ *
+ * The error message is returned prepended with a dot and a space, like this:
+ * <code>
+ * ". <error_text> (%Rrc)"
+ * </code>
+ * to make it easily appendable to a more general error message. The @c %Rrc
+ * format string is given @a aVRC as an argument.
+ *
+ * If there is no last error message collected by i_vdErrorCall or if it is a
+ * null or empty string, then this function returns the following text:
+ * <code>
+ * " (%Rrc)"
+ * </code>
+ *
+ * @note Doesn't do any object locking; it is assumed that the caller makes sure
+ * the callback isn't called by more than one thread at a time.
+ *
+ * @param aVRC VBox error code to use when no error message is provided.
+ */
+Utf8Str Medium::i_vdError(int aVRC)
+{
+ Utf8Str error;
+
+ if (m->vdError.isEmpty())
+ error = Utf8StrFmt(" (%Rrc)", aVRC);
+ else
+ error = Utf8StrFmt(".\n%s", m->vdError.c_str());
+
+ m->vdError.setNull();
+
+ return error;
+}
+
+/**
+ * Error message callback.
+ *
+ * Puts the reported error message to the m->vdError field.
+ *
+ * @note Doesn't do any object locking; it is assumed that the caller makes sure
+ * the callback isn't called by more than one thread at a time.
+ *
+ * @param pvUser The opaque data passed on container creation.
+ * @param rc The VBox error code.
+ * @param SRC_POS Use RT_SRC_POS.
+ * @param pszFormat Error message format string.
+ * @param va Error message arguments.
+ */
+/*static*/
+DECLCALLBACK(void) Medium::i_vdErrorCall(void *pvUser, int rc, RT_SRC_POS_DECL,
+ const char *pszFormat, va_list va)
+{
+ NOREF(pszFile); NOREF(iLine); NOREF(pszFunction); /* RT_SRC_POS_DECL */
+
+ Medium *that = static_cast<Medium*>(pvUser);
+ AssertReturnVoid(that != NULL);
+
+ if (that->m->vdError.isEmpty())
+ that->m->vdError =
+ Utf8StrFmt("%s (%Rrc)", Utf8Str(pszFormat, va).c_str(), rc);
+ else
+ that->m->vdError =
+ Utf8StrFmt("%s.\n%s (%Rrc)", that->m->vdError.c_str(),
+ Utf8Str(pszFormat, va).c_str(), rc);
+}
+
+/* static */
+DECLCALLBACK(bool) Medium::i_vdConfigAreKeysValid(void *pvUser,
+ const char * /* pszzValid */)
+{
+ Medium *that = static_cast<Medium*>(pvUser);
+ AssertReturn(that != NULL, false);
+
+ /* we always return true since the only keys we have are those found in
+ * VDBACKENDINFO */
+ return true;
+}
+
+/* static */
+DECLCALLBACK(int) Medium::i_vdConfigQuerySize(void *pvUser,
+ const char *pszName,
+ size_t *pcbValue)
+{
+ AssertPtrReturn(pcbValue, VERR_INVALID_POINTER);
+
+ Medium *that = static_cast<Medium*>(pvUser);
+ AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
+
+ settings::StringsMap::const_iterator it = that->m->mapProperties.find(Utf8Str(pszName));
+ if (it == that->m->mapProperties.end())
+ return VERR_CFGM_VALUE_NOT_FOUND;
+
+ /* we interpret null values as "no value" in Medium */
+ if (it->second.isEmpty())
+ return VERR_CFGM_VALUE_NOT_FOUND;
+
+ *pcbValue = it->second.length() + 1 /* include terminator */;
+
+ return VINF_SUCCESS;
+}
+
+/* static */
+DECLCALLBACK(int) Medium::i_vdConfigQuery(void *pvUser,
+ const char *pszName,
+ char *pszValue,
+ size_t cchValue)
+{
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+
+ Medium *that = static_cast<Medium*>(pvUser);
+ AssertReturn(that != NULL, VERR_GENERAL_FAILURE);
+
+ settings::StringsMap::const_iterator it = that->m->mapProperties.find(Utf8Str(pszName));
+ if (it == that->m->mapProperties.end())
+ return VERR_CFGM_VALUE_NOT_FOUND;
+
+ /* we interpret null values as "no value" in Medium */
+ if (it->second.isEmpty())
+ return VERR_CFGM_VALUE_NOT_FOUND;
+
+ const Utf8Str &value = it->second;
+ if (value.length() >= cchValue)
+ return VERR_CFGM_NOT_ENOUGH_SPACE;
+
+ memcpy(pszValue, value.c_str(), value.length() + 1);
+
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(bool) Medium::i_vdCryptoConfigAreKeysValid(void *pvUser, const char *pszzValid)
+{
+ /* Just return always true here. */
+ NOREF(pvUser);
+ NOREF(pszzValid);
+ return true;
+}
+
+DECLCALLBACK(int) Medium::i_vdCryptoConfigQuerySize(void *pvUser, const char *pszName, size_t *pcbValue)
+{
+ MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser;
+ AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE);
+ AssertPtrReturn(pcbValue, VERR_INVALID_POINTER);
+
+ size_t cbValue = 0;
+ if (!strcmp(pszName, "Algorithm"))
+ cbValue = strlen(pSettings->pszCipher) + 1;
+ else if (!strcmp(pszName, "KeyId"))
+ cbValue = sizeof("irrelevant");
+ else if (!strcmp(pszName, "KeyStore"))
+ {
+ if (!pSettings->pszKeyStoreLoad)
+ return VERR_CFGM_VALUE_NOT_FOUND;
+ cbValue = strlen(pSettings->pszKeyStoreLoad) + 1;
+ }
+ else if (!strcmp(pszName, "CreateKeyStore"))
+ cbValue = 2; /* Single digit + terminator. */
+ else
+ return VERR_CFGM_VALUE_NOT_FOUND;
+
+ *pcbValue = cbValue + 1 /* include terminator */;
+
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) Medium::i_vdCryptoConfigQuery(void *pvUser, const char *pszName,
+ char *pszValue, size_t cchValue)
+{
+ MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser;
+ AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE);
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+
+ const char *psz = NULL;
+ if (!strcmp(pszName, "Algorithm"))
+ psz = pSettings->pszCipher;
+ else if (!strcmp(pszName, "KeyId"))
+ psz = "irrelevant";
+ else if (!strcmp(pszName, "KeyStore"))
+ psz = pSettings->pszKeyStoreLoad;
+ else if (!strcmp(pszName, "CreateKeyStore"))
+ {
+ if (pSettings->fCreateKeyStore)
+ psz = "1";
+ else
+ psz = "0";
+ }
+ else
+ return VERR_CFGM_VALUE_NOT_FOUND;
+
+ size_t cch = strlen(psz);
+ if (cch >= cchValue)
+ return VERR_CFGM_NOT_ENOUGH_SPACE;
+
+ memcpy(pszValue, psz, cch + 1);
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) Medium::i_vdConfigUpdate(void *pvUser,
+ bool fCreate,
+ const char *pszName,
+ const char *pszValue)
+{
+ Medium *that = (Medium *)pvUser;
+
+ // Detect if this runs inside i_queryInfo() on the current thread.
+ // Skip if not. Check does not need synchronization.
+ if (!that->m || !that->m->queryInfoRunning || !that->m->queryInfoSem.isWriteLockOnCurrentThread())
+ return VINF_SUCCESS;
+
+ // It's guaranteed that this code is executing inside Medium::i_queryInfo,
+ // can assume it took care of synchronization.
+ int rv = VINF_SUCCESS;
+ Utf8Str strName(pszName);
+ settings::StringsMap::const_iterator it = that->m->mapProperties.find(strName);
+ if (it == that->m->mapProperties.end() && !fCreate)
+ rv = VERR_CFGM_VALUE_NOT_FOUND;
+ else
+ that->m->mapProperties[strName] = Utf8Str(pszValue);
+ return rv;
+}
+
+DECLCALLBACK(int) Medium::i_vdCryptoKeyRetain(void *pvUser, const char *pszId,
+ const uint8_t **ppbKey, size_t *pcbKey)
+{
+ MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser;
+ NOREF(pszId);
+ NOREF(ppbKey);
+ NOREF(pcbKey);
+ AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE);
+ AssertMsgFailedReturn(("This method should not be called here!\n"), VERR_INVALID_STATE);
+}
+
+DECLCALLBACK(int) Medium::i_vdCryptoKeyRelease(void *pvUser, const char *pszId)
+{
+ MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser;
+ NOREF(pszId);
+ AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE);
+ AssertMsgFailedReturn(("This method should not be called here!\n"), VERR_INVALID_STATE);
+}
+
+DECLCALLBACK(int) Medium::i_vdCryptoKeyStorePasswordRetain(void *pvUser, const char *pszId, const char **ppszPassword)
+{
+ MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser;
+ AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE);
+
+ NOREF(pszId);
+ *ppszPassword = pSettings->pszPassword;
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) Medium::i_vdCryptoKeyStorePasswordRelease(void *pvUser, const char *pszId)
+{
+ MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser;
+ AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE);
+ NOREF(pszId);
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) Medium::i_vdCryptoKeyStoreSave(void *pvUser, const void *pvKeyStore, size_t cbKeyStore)
+{
+ MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser;
+ AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE);
+
+ pSettings->pszKeyStore = (char *)RTMemAllocZ(cbKeyStore);
+ if (!pSettings->pszKeyStore)
+ return VERR_NO_MEMORY;
+
+ memcpy(pSettings->pszKeyStore, pvKeyStore, cbKeyStore);
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) Medium::i_vdCryptoKeyStoreReturnParameters(void *pvUser, const char *pszCipher,
+ const uint8_t *pbDek, size_t cbDek)
+{
+ MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser;
+ AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE);
+
+ pSettings->pszCipherReturned = RTStrDup(pszCipher);
+ pSettings->pbDek = pbDek;
+ pSettings->cbDek = cbDek;
+
+ return pSettings->pszCipherReturned ? VINF_SUCCESS : VERR_NO_MEMORY;
+}
+
+/**
+ * Creates a VDISK instance for this medium.
+ *
+ * @note Caller should not hold any medium related locks as this method will
+ * acquire the medium lock for writing and others (VirtualBox).
+ *
+ * @returns COM status code.
+ * @param fWritable Whether to return a writable VDISK instance
+ * (true) or a read-only one (false).
+ * @param pKeyStore The key store.
+ * @param ppHdd Where to return the pointer to the VDISK on
+ * success.
+ * @param pMediumLockList The lock list to populate and lock. Caller
+ * is responsible for calling the destructor or
+ * MediumLockList::Clear() after destroying
+ * @a *ppHdd
+ * @param pCryptoSettings The crypto settings to use for setting up
+ * decryption/encryption of the VDISK. This object
+ * must be alive until the VDISK is destroyed!
+ */
+HRESULT Medium::i_openForIO(bool fWritable, SecretKeyStore *pKeyStore, PVDISK *ppHdd, MediumLockList *pMediumLockList,
+ MediumCryptoFilterSettings *pCryptoSettings)
+{
+ /*
+ * Create the media lock list and lock the media.
+ */
+ HRESULT hrc = i_createMediumLockList(true /* fFailIfInaccessible */,
+ fWritable ? this : NULL /* pToLockWrite */,
+ false /* fMediumLockWriteAll */,
+ NULL,
+ *pMediumLockList);
+ if (SUCCEEDED(hrc))
+ hrc = pMediumLockList->Lock();
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Get the base medium before write locking this medium.
+ */
+ ComObjPtr<Medium> pBase = i_getBase();
+ AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Create the VDISK instance.
+ */
+ PVDISK pHdd;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &pHdd);
+ AssertRCReturn(vrc, E_FAIL);
+
+ /*
+ * Goto avoidance using try/catch/throw(HRESULT).
+ */
+ try
+ {
+ settings::StringsMap::iterator itKeyStore = pBase->m->mapProperties.find("CRYPT/KeyStore");
+ if (itKeyStore != pBase->m->mapProperties.end())
+ {
+#ifdef VBOX_WITH_EXTPACK
+ settings::StringsMap::iterator itKeyId = pBase->m->mapProperties.find("CRYPT/KeyId");
+
+ ExtPackManager *pExtPackManager = m->pVirtualBox->i_getExtPackManager();
+ if (pExtPackManager->i_isExtPackUsable(ORACLE_PUEL_EXTPACK_NAME))
+ {
+ /* Load the plugin */
+ Utf8Str strPlugin;
+ hrc = pExtPackManager->i_getLibraryPathForExtPack(g_szVDPlugin, ORACLE_PUEL_EXTPACK_NAME, &strPlugin);
+ if (SUCCEEDED(hrc))
+ {
+ vrc = VDPluginLoadFromFilename(strPlugin.c_str());
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc,
+ tr("Retrieving encryption settings of the image failed because the encryption plugin could not be loaded (%s)"),
+ i_vdError(vrc).c_str());
+ }
+ else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Encryption is not supported because the extension pack '%s' is missing the encryption plugin (old extension pack installed?)"),
+ ORACLE_PUEL_EXTPACK_NAME);
+ }
+ else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Encryption is not supported because the extension pack '%s' is missing"),
+ ORACLE_PUEL_EXTPACK_NAME);
+
+ if (itKeyId == pBase->m->mapProperties.end())
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Image '%s' is configured for encryption but doesn't has a key identifier set"),
+ pBase->m->strLocationFull.c_str());
+
+ /* Find the proper secret key in the key store. */
+ if (!pKeyStore)
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Image '%s' is configured for encryption but there is no key store to retrieve the password from"),
+ pBase->m->strLocationFull.c_str());
+
+ SecretKey *pKey = NULL;
+ vrc = pKeyStore->retainSecretKey(itKeyId->second, &pKey);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_INVALID_OBJECT_STATE, vrc,
+ tr("Failed to retrieve the secret key with ID \"%s\" from the store (%Rrc)"),
+ itKeyId->second.c_str(), vrc);
+
+ i_taskEncryptSettingsSetup(pCryptoSettings, NULL, itKeyStore->second.c_str(), (const char *)pKey->getKeyBuffer(),
+ false /* fCreateKeyStore */);
+ vrc = VDFilterAdd(pHdd, "CRYPT", VD_FILTER_FLAGS_DEFAULT, pCryptoSettings->vdFilterIfaces);
+ pKeyStore->releaseSecretKey(itKeyId->second);
+ if (vrc == VERR_VD_PASSWORD_INCORRECT)
+ throw setErrorBoth(VBOX_E_PASSWORD_INCORRECT, vrc, tr("The password to decrypt the image is incorrect"));
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_INVALID_OBJECT_STATE, vrc, tr("Failed to load the decryption filter: %s"),
+ i_vdError(vrc).c_str());
+#else
+ RT_NOREF(pKeyStore, pCryptoSettings);
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Encryption is not supported because extension pack support is not built in"));
+#endif /* VBOX_WITH_EXTPACK */
+ }
+
+ /*
+ * Open all media in the source chain.
+ */
+ MediumLockList::Base::const_iterator sourceListBegin = pMediumLockList->GetBegin();
+ MediumLockList::Base::const_iterator sourceListEnd = pMediumLockList->GetEnd();
+ MediumLockList::Base::const_iterator mediumListLast = sourceListEnd;
+ --mediumListLast;
+
+ for (MediumLockList::Base::const_iterator it = sourceListBegin; it != sourceListEnd; ++it)
+ {
+ const MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+ AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ /* sanity check */
+ Assert(pMedium->m->state == (fWritable && it == mediumListLast ? MediumState_LockedWrite : MediumState_LockedRead));
+
+ /* Open all media in read-only mode. */
+ vrc = VDOpen(pHdd,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ m->uOpenFlagsDef | (fWritable && it == mediumListLast ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY),
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not open the medium storage unit '%s'%s"),
+ pMedium->m->strLocationFull.c_str(),
+ i_vdError(vrc).c_str());
+ }
+
+ Assert(m->state == (fWritable ? MediumState_LockedWrite : MediumState_LockedRead));
+
+ /*
+ * Done!
+ */
+ *ppHdd = pHdd;
+ return S_OK;
+ }
+ catch (HRESULT hrc2)
+ {
+ hrc = hrc2;
+ }
+
+ VDDestroy(pHdd);
+ return hrc;
+
+}
+
+/**
+ * Implementation code for the "create base" task.
+ *
+ * This only gets started from Medium::CreateBaseStorage() and always runs
+ * asynchronously. As a result, we always save the VirtualBox.xml file when
+ * we're done here.
+ *
+ * @param task
+ * @return
+ */
+HRESULT Medium::i_taskCreateBaseHandler(Medium::CreateBaseTask &task)
+{
+ /** @todo r=klaus The code below needs to be double checked with regard
+ * to lock order violations, it probably causes lock order issues related
+ * to the AutoCaller usage. */
+ HRESULT rc = S_OK;
+
+ /* these parameters we need after creation */
+ uint64_t size = 0, logicalSize = 0;
+ MediumVariant_T variant = MediumVariant_Standard;
+ bool fGenerateUuid = false;
+
+ try
+ {
+ AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* The object may request a specific UUID (through a special form of
+ * the moveTo() argument). Otherwise we have to generate it */
+ Guid id = m->id;
+
+ fGenerateUuid = id.isZero();
+ if (fGenerateUuid)
+ {
+ id.create();
+ /* VirtualBox::i_registerMedium() will need UUID */
+ unconst(m->id) = id;
+ }
+
+ Utf8Str format(m->strFormat);
+ Utf8Str location(m->strLocationFull);
+ uint64_t capabilities = m->formatObj->i_getCapabilities();
+ ComAssertThrow(capabilities & ( MediumFormatCapabilities_CreateFixed
+ | MediumFormatCapabilities_CreateDynamic), E_FAIL);
+ Assert(m->state == MediumState_Creating);
+
+ PVDISK hdd;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ /* unlock before the potentially lengthy operation */
+ thisLock.release();
+
+ try
+ {
+ /* ensure the directory exists */
+ if (capabilities & MediumFormatCapabilities_File)
+ {
+ rc = VirtualBox::i_ensureFilePathExists(location, !(task.mVariant & MediumVariant_NoCreateDir) /* fCreate */);
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ VDGEOMETRY geo = { 0, 0, 0 }; /* auto-detect */
+
+ vrc = VDCreateBase(hdd,
+ format.c_str(),
+ location.c_str(),
+ task.mSize,
+ task.mVariant & ~(MediumVariant_NoCreateDir | MediumVariant_Formatted),
+ NULL,
+ &geo,
+ &geo,
+ id.raw(),
+ VD_OPEN_FLAGS_NORMAL | m->uOpenFlagsDef,
+ m->vdImageIfaces,
+ task.mVDOperationIfaces);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_VD_INVALID_TYPE)
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Parameters for creating the medium storage unit '%s' are invalid%s"),
+ location.c_str(), i_vdError(vrc).c_str());
+ else
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not create the medium storage unit '%s'%s"),
+ location.c_str(), i_vdError(vrc).c_str());
+ }
+
+ if (task.mVariant & MediumVariant_Formatted)
+ {
+ RTVFSFILE hVfsFile;
+ vrc = VDCreateVfsFileFromDisk(hdd, 0 /*fFlags*/, &hVfsFile);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Opening medium storage unit '%s' failed%s"),
+ location.c_str(), i_vdError(vrc).c_str());
+ RTERRINFOSTATIC ErrInfo;
+ vrc = RTFsFatVolFormat(hVfsFile, 0 /* offVol */, 0 /* cbVol */, RTFSFATVOL_FMT_F_FULL,
+ 0 /* cbSector */, 0 /* cbSectorPerCluster */, RTFSFATTYPE_INVALID,
+ 0 /* cHeads */, 0 /* cSectorsPerTrack*/, 0 /* bMedia */,
+ 0 /* cRootDirEntries */, 0 /* cHiddenSectors */,
+ RTErrInfoInitStatic(&ErrInfo));
+ RTVfsFileRelease(hVfsFile);
+ if (RT_FAILURE(vrc) && RTErrInfoIsSet(&ErrInfo.Core))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Formatting medium storage unit '%s' failed: %s"),
+ location.c_str(), ErrInfo.Core.pszMsg);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Formatting medium storage unit '%s' failed%s"),
+ location.c_str(), i_vdError(vrc).c_str());
+ }
+
+ size = VDGetFileSize(hdd, 0);
+ logicalSize = VDGetSize(hdd, 0);
+ unsigned uImageFlags;
+ vrc = VDGetImageFlags(hdd, 0, &uImageFlags);
+ if (RT_SUCCESS(vrc))
+ variant = (MediumVariant_T)uImageFlags;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ VDDestroy(hdd);
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ /* register with mVirtualBox as the last step and move to
+ * Created state only on success (leaving an orphan file is
+ * better than breaking media registry consistency) */
+ AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+ ComObjPtr<Medium> pMedium;
+ rc = m->pVirtualBox->i_registerMedium(this, &pMedium, treeLock);
+ Assert(pMedium == NULL || this == pMedium);
+ }
+
+ // re-acquire the lock before changing state
+ AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (SUCCEEDED(rc))
+ {
+ m->state = MediumState_Created;
+
+ m->size = size;
+ m->logicalSize = logicalSize;
+ m->variant = variant;
+
+ thisLock.release();
+ i_markRegistriesModified();
+ if (task.isAsync())
+ {
+ // in asynchronous mode, save settings now
+ m->pVirtualBox->i_saveModifiedRegistries();
+ }
+ }
+ else
+ {
+ /* back to NotCreated on failure */
+ m->state = MediumState_NotCreated;
+
+ /* reset UUID to prevent it from being reused next time */
+ if (fGenerateUuid)
+ unconst(m->id).clear();
+ }
+
+ if (task.NotifyAboutChanges() && SUCCEEDED(rc))
+ {
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+ m->pVirtualBox->i_onMediumRegistered(m->id, m->devType, TRUE);
+ }
+
+ return rc;
+}
+
+/**
+ * Implementation code for the "create diff" task.
+ *
+ * This task always gets started from Medium::createDiffStorage() and can run
+ * synchronously or asynchronously depending on the "wait" parameter passed to
+ * that function. If we run synchronously, the caller expects the medium
+ * registry modification to be set before returning; otherwise (in asynchronous
+ * mode), we save the settings ourselves.
+ *
+ * @param task
+ * @return
+ */
+HRESULT Medium::i_taskCreateDiffHandler(Medium::CreateDiffTask &task)
+{
+ /** @todo r=klaus The code below needs to be double checked with regard
+ * to lock order violations, it probably causes lock order issues related
+ * to the AutoCaller usage. */
+ HRESULT rcTmp = S_OK;
+
+ const ComObjPtr<Medium> &pTarget = task.mTarget;
+
+ uint64_t size = 0, logicalSize = 0;
+ MediumVariant_T variant = MediumVariant_Standard;
+ bool fGenerateUuid = false;
+
+ try
+ {
+ if (i_getDepth() >= SETTINGS_MEDIUM_DEPTH_MAX)
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot create differencing image for medium '%s', because it exceeds the medium tree depth limit. Please merge some images which you no longer need"),
+ m->strLocationFull.c_str());
+ }
+
+ /* Lock both in {parent,child} order. */
+ AutoMultiWriteLock2 mediaLock(this, pTarget COMMA_LOCKVAL_SRC_POS);
+
+ /* The object may request a specific UUID (through a special form of
+ * the moveTo() argument). Otherwise we have to generate it */
+ Guid targetId = pTarget->m->id;
+
+ fGenerateUuid = targetId.isZero();
+ if (fGenerateUuid)
+ {
+ targetId.create();
+ /* VirtualBox::i_registerMedium() will need UUID */
+ unconst(pTarget->m->id) = targetId;
+ }
+
+ Guid id = m->id;
+
+ Utf8Str targetFormat(pTarget->m->strFormat);
+ Utf8Str targetLocation(pTarget->m->strLocationFull);
+ uint64_t capabilities = pTarget->m->formatObj->i_getCapabilities();
+ ComAssertThrow(capabilities & MediumFormatCapabilities_CreateDynamic, E_FAIL);
+
+ Assert(pTarget->m->state == MediumState_Creating);
+ Assert(m->state == MediumState_LockedRead);
+
+ PVDISK hdd;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ /* the two media are now protected by their non-default states;
+ * unlock the media before the potentially lengthy operation */
+ mediaLock.release();
+
+ try
+ {
+ /* Open all media in the target chain but the last. */
+ MediumLockList::Base::const_iterator targetListBegin =
+ task.mpMediumLockList->GetBegin();
+ MediumLockList::Base::const_iterator targetListEnd =
+ task.mpMediumLockList->GetEnd();
+ for (MediumLockList::Base::const_iterator it = targetListBegin;
+ it != targetListEnd;
+ ++it)
+ {
+ const MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+
+ AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ /* Skip over the target diff medium */
+ if (pMedium->m->state == MediumState_Creating)
+ continue;
+
+ /* sanity check */
+ Assert(pMedium->m->state == MediumState_LockedRead);
+
+ /* Open all media in appropriate mode. */
+ vrc = VDOpen(hdd,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO | m->uOpenFlagsDef,
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not open the medium storage unit '%s'%s"),
+ pMedium->m->strLocationFull.c_str(),
+ i_vdError(vrc).c_str());
+ }
+
+ /* ensure the target directory exists */
+ if (capabilities & MediumFormatCapabilities_File)
+ {
+ HRESULT rc = VirtualBox::i_ensureFilePathExists(targetLocation,
+ !(task.mVariant & MediumVariant_NoCreateDir) /* fCreate */);
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ vrc = VDCreateDiff(hdd,
+ targetFormat.c_str(),
+ targetLocation.c_str(),
+ (task.mVariant & ~(MediumVariant_NoCreateDir | MediumVariant_Formatted | MediumVariant_VmdkESX | MediumVariant_VmdkRawDisk))
+ | VD_IMAGE_FLAGS_DIFF,
+ NULL,
+ targetId.raw(),
+ id.raw(),
+ VD_OPEN_FLAGS_NORMAL | m->uOpenFlagsDef,
+ pTarget->m->vdImageIfaces,
+ task.mVDOperationIfaces);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_VD_INVALID_TYPE)
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Parameters for creating the differencing medium storage unit '%s' are invalid%s"),
+ targetLocation.c_str(), i_vdError(vrc).c_str());
+ else
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not create the differencing medium storage unit '%s'%s"),
+ targetLocation.c_str(), i_vdError(vrc).c_str());
+ }
+
+ size = VDGetFileSize(hdd, VD_LAST_IMAGE);
+ logicalSize = VDGetSize(hdd, VD_LAST_IMAGE);
+ unsigned uImageFlags;
+ vrc = VDGetImageFlags(hdd, 0, &uImageFlags);
+ if (RT_SUCCESS(vrc))
+ variant = (MediumVariant_T)uImageFlags;
+ }
+ catch (HRESULT aRC) { rcTmp = aRC; }
+
+ VDDestroy(hdd);
+ }
+ catch (HRESULT aRC) { rcTmp = aRC; }
+
+ MultiResult mrc(rcTmp);
+
+ if (SUCCEEDED(mrc))
+ {
+ AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ Assert(pTarget->m->pParent.isNull());
+
+ /* associate child with the parent, maximum depth was checked above */
+ pTarget->i_setParent(this);
+
+ /* diffs for immutable media are auto-reset by default */
+ bool fAutoReset;
+ {
+ ComObjPtr<Medium> pBase = i_getBase();
+ AutoReadLock block(pBase COMMA_LOCKVAL_SRC_POS);
+ fAutoReset = (pBase->m->type == MediumType_Immutable);
+ }
+ {
+ AutoWriteLock tlock(pTarget COMMA_LOCKVAL_SRC_POS);
+ pTarget->m->autoReset = fAutoReset;
+ }
+
+ /* register with mVirtualBox as the last step and move to
+ * Created state only on success (leaving an orphan file is
+ * better than breaking media registry consistency) */
+ ComObjPtr<Medium> pMedium;
+ mrc = m->pVirtualBox->i_registerMedium(pTarget, &pMedium, treeLock);
+ Assert(pTarget == pMedium);
+
+ if (FAILED(mrc))
+ /* break the parent association on failure to register */
+ i_deparent();
+ }
+
+ AutoMultiWriteLock2 mediaLock(this, pTarget COMMA_LOCKVAL_SRC_POS);
+
+ if (SUCCEEDED(mrc))
+ {
+ pTarget->m->state = MediumState_Created;
+
+ pTarget->m->size = size;
+ pTarget->m->logicalSize = logicalSize;
+ pTarget->m->variant = variant;
+ }
+ else
+ {
+ /* back to NotCreated on failure */
+ pTarget->m->state = MediumState_NotCreated;
+
+ pTarget->m->autoReset = false;
+
+ /* reset UUID to prevent it from being reused next time */
+ if (fGenerateUuid)
+ unconst(pTarget->m->id).clear();
+ }
+
+ // deregister the task registered in createDiffStorage()
+ Assert(m->numCreateDiffTasks != 0);
+ --m->numCreateDiffTasks;
+
+ mediaLock.release();
+ i_markRegistriesModified();
+ if (task.isAsync())
+ {
+ // in asynchronous mode, save settings now
+ m->pVirtualBox->i_saveModifiedRegistries();
+ }
+
+ /* Note that in sync mode, it's the caller's responsibility to
+ * unlock the medium. */
+
+ if (task.NotifyAboutChanges() && SUCCEEDED(mrc))
+ {
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+ m->pVirtualBox->i_onMediumRegistered(m->id, m->devType, TRUE);
+ }
+
+ return mrc;
+}
+
+/**
+ * Implementation code for the "merge" task.
+ *
+ * This task always gets started from Medium::mergeTo() and can run
+ * synchronously or asynchronously depending on the "wait" parameter passed to
+ * that function. If we run synchronously, the caller expects the medium
+ * registry modification to be set before returning; otherwise (in asynchronous
+ * mode), we save the settings ourselves.
+ *
+ * @param task
+ * @return
+ */
+HRESULT Medium::i_taskMergeHandler(Medium::MergeTask &task)
+{
+ /** @todo r=klaus The code below needs to be double checked with regard
+ * to lock order violations, it probably causes lock order issues related
+ * to the AutoCaller usage. */
+ HRESULT rcTmp = S_OK;
+
+ const ComObjPtr<Medium> &pTarget = task.mTarget;
+
+ try
+ {
+ if (!task.mParentForTarget.isNull())
+ if (task.mParentForTarget->i_getDepth() >= SETTINGS_MEDIUM_DEPTH_MAX)
+ {
+ AutoReadLock plock(task.mParentForTarget COMMA_LOCKVAL_SRC_POS);
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot merge image for medium '%s', because it exceeds the medium tree depth limit. Please merge some images which you no longer need"),
+ task.mParentForTarget->m->strLocationFull.c_str());
+ }
+
+ // Resize target to source size, if possible. Otherwise throw an error.
+ // It's offline resizing. Online resizing will be called in the
+ // SessionMachine::onlineMergeMedium.
+
+ uint64_t sourceSize = 0;
+ Utf8Str sourceName;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ sourceSize = i_getLogicalSize();
+ sourceName = i_getName();
+ }
+ uint64_t targetSize = 0;
+ Utf8Str targetName;
+ {
+ AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS);
+ targetSize = pTarget->i_getLogicalSize();
+ targetName = pTarget->i_getName();
+ }
+
+ //reducing vm disks are not implemented yet
+ if (sourceSize > targetSize)
+ {
+ if (i_isMediumFormatFile())
+ {
+ // Have to make own lock list, because "resize" method resizes only last image
+ // in the lock chain. The lock chain already in the task.mpMediumLockList, so
+ // just make new lock list based on it. In fact the own lock list neither makes
+ // double locking of mediums nor unlocks them during delete, because medium
+ // already locked by task.mpMediumLockList and own list is used just to specify
+ // what "resize" method should resize.
+
+ MediumLockList* pMediumLockListForResize = new MediumLockList();
+
+ for (MediumLockList::Base::iterator it = task.mpMediumLockList->GetBegin();
+ it != task.mpMediumLockList->GetEnd();
+ ++it)
+ {
+ ComObjPtr<Medium> pMedium = it->GetMedium();
+ pMediumLockListForResize->Append(pMedium, pMedium->m->state == MediumState_LockedWrite);
+ if (pMedium == pTarget)
+ break;
+ }
+
+ // just to switch internal state of the lock list to avoid errors during list deletion,
+ // because all meduims in the list already locked by task.mpMediumLockList
+ HRESULT rc = pMediumLockListForResize->Lock(true /* fSkipOverLockedMedia */);
+ if (FAILED(rc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ rc = setError(rc,
+ tr("Failed to lock the medium '%s' to resize before merge"),
+ targetName.c_str());
+ delete pMediumLockListForResize;
+ throw rc;
+ }
+
+ ComObjPtr<Progress> pProgress(task.GetProgressObject());
+ rc = pTarget->i_resize(sourceSize, pMediumLockListForResize, &pProgress, true, false);
+ if (FAILED(rc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ rc = setError(rc,
+ tr("Failed to set size of '%s' to size of '%s'"),
+ targetName.c_str(), sourceName.c_str());
+ delete pMediumLockListForResize;
+ throw rc;
+ }
+ delete pMediumLockListForResize;
+ }
+ else
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = setError(VBOX_E_NOT_SUPPORTED,
+ tr("Sizes of '%s' and '%s' are different and medium format does not support resing"),
+ sourceName.c_str(), targetName.c_str());
+ throw rc;
+ }
+ }
+
+ task.GetProgressObject()->SetNextOperation(BstrFmt(tr("Merging medium '%s' to '%s'"),
+ i_getName().c_str(),
+ targetName.c_str()).raw(),
+ 1);
+
+ PVDISK hdd;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ try
+ {
+ // Similar code appears in SessionMachine::onlineMergeMedium, so
+ // if you make any changes below check whether they are applicable
+ // in that context as well.
+
+ unsigned uTargetIdx = VD_LAST_IMAGE;
+ unsigned uSourceIdx = VD_LAST_IMAGE;
+ /* Open all media in the chain. */
+ MediumLockList::Base::iterator lockListBegin =
+ task.mpMediumLockList->GetBegin();
+ MediumLockList::Base::iterator lockListEnd =
+ task.mpMediumLockList->GetEnd();
+ unsigned i = 0;
+ for (MediumLockList::Base::iterator it = lockListBegin;
+ it != lockListEnd;
+ ++it)
+ {
+ MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+
+ if (pMedium == this)
+ uSourceIdx = i;
+ else if (pMedium == pTarget)
+ uTargetIdx = i;
+
+ AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * complex sanity (sane complexity)
+ *
+ * The current medium must be in the Deleting (medium is merged)
+ * or LockedRead (parent medium) state if it is not the target.
+ * If it is the target it must be in the LockedWrite state.
+ */
+ Assert( ( pMedium != pTarget
+ && ( pMedium->m->state == MediumState_Deleting
+ || pMedium->m->state == MediumState_LockedRead))
+ || ( pMedium == pTarget
+ && pMedium->m->state == MediumState_LockedWrite));
+ /*
+ * Medium must be the target, in the LockedRead state
+ * or Deleting state where it is not allowed to be attached
+ * to a virtual machine.
+ */
+ Assert( pMedium == pTarget
+ || pMedium->m->state == MediumState_LockedRead
+ || ( pMedium->m->backRefs.size() == 0
+ && pMedium->m->state == MediumState_Deleting));
+ /* The source medium must be in Deleting state. */
+ Assert( pMedium != this
+ || pMedium->m->state == MediumState_Deleting);
+
+ unsigned uOpenFlags = VD_OPEN_FLAGS_NORMAL;
+
+ if ( pMedium->m->state == MediumState_LockedRead
+ || pMedium->m->state == MediumState_Deleting)
+ uOpenFlags = VD_OPEN_FLAGS_READONLY;
+ if (pMedium->m->type == MediumType_Shareable)
+ uOpenFlags |= VD_OPEN_FLAGS_SHAREABLE;
+
+ /* Open the medium */
+ vrc = VDOpen(hdd,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ uOpenFlags | m->uOpenFlagsDef,
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+
+ i++;
+ }
+
+ ComAssertThrow( uSourceIdx != VD_LAST_IMAGE
+ && uTargetIdx != VD_LAST_IMAGE, E_FAIL);
+
+ vrc = VDMerge(hdd, uSourceIdx, uTargetIdx,
+ task.mVDOperationIfaces);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+
+ /* update parent UUIDs */
+ if (!task.mfMergeForward)
+ {
+ /* we need to update UUIDs of all source's children
+ * which cannot be part of the container at once so
+ * add each one in there individually */
+ if (task.mpChildrenToReparent)
+ {
+ MediumLockList::Base::iterator childrenBegin = task.mpChildrenToReparent->GetBegin();
+ MediumLockList::Base::iterator childrenEnd = task.mpChildrenToReparent->GetEnd();
+ for (MediumLockList::Base::iterator it = childrenBegin;
+ it != childrenEnd;
+ ++it)
+ {
+ Medium *pMedium = it->GetMedium();
+ /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
+ vrc = VDOpen(hdd,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ VD_OPEN_FLAGS_INFO | m->uOpenFlagsDef,
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+
+ vrc = VDSetParentUuid(hdd, VD_LAST_IMAGE,
+ pTarget->m->id.raw());
+ if (RT_FAILURE(vrc))
+ throw vrc;
+
+ vrc = VDClose(hdd, false /* fDelete */);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+ }
+ }
+ }
+ }
+ catch (HRESULT aRC) { rcTmp = aRC; }
+ catch (int aVRC)
+ {
+ rcTmp = setErrorBoth(VBOX_E_FILE_ERROR, aVRC,
+ tr("Could not merge the medium '%s' to '%s'%s"),
+ m->strLocationFull.c_str(),
+ pTarget->m->strLocationFull.c_str(),
+ i_vdError(aVRC).c_str());
+ }
+
+ VDDestroy(hdd);
+ }
+ catch (HRESULT aRC) { rcTmp = aRC; }
+
+ ErrorInfoKeeper eik;
+ MultiResult mrc(rcTmp);
+ HRESULT rc2;
+
+ std::set<ComObjPtr<Medium> > pMediumsForNotify;
+ std::map<Guid, DeviceType_T> uIdsForNotify;
+
+ if (SUCCEEDED(mrc))
+ {
+ /* all media but the target were successfully deleted by
+ * VDMerge; reparent the last one and uninitialize deleted media. */
+
+ AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ if (task.mfMergeForward)
+ {
+ /* first, unregister the target since it may become a base
+ * medium which needs re-registration */
+ rc2 = m->pVirtualBox->i_unregisterMedium(pTarget);
+ AssertComRC(rc2);
+
+ /* then, reparent it and disconnect the deleted branch at both ends
+ * (chain->parent() is source's parent). Depth check above. */
+ pTarget->i_deparent();
+ pTarget->i_setParent(task.mParentForTarget);
+ if (task.mParentForTarget)
+ {
+ i_deparent();
+ if (task.NotifyAboutChanges())
+ pMediumsForNotify.insert(task.mParentForTarget);
+ }
+
+ /* then, register again */
+ ComObjPtr<Medium> pMedium;
+ rc2 = m->pVirtualBox->i_registerMedium(pTarget, &pMedium,
+ treeLock);
+ AssertComRC(rc2);
+ }
+ else
+ {
+ Assert(pTarget->i_getChildren().size() == 1);
+ Medium *targetChild = pTarget->i_getChildren().front();
+
+ /* disconnect the deleted branch at the elder end */
+ targetChild->i_deparent();
+
+ /* reparent source's children and disconnect the deleted
+ * branch at the younger end */
+ if (task.mpChildrenToReparent)
+ {
+ /* obey {parent,child} lock order */
+ AutoWriteLock sourceLock(this COMMA_LOCKVAL_SRC_POS);
+
+ MediumLockList::Base::iterator childrenBegin = task.mpChildrenToReparent->GetBegin();
+ MediumLockList::Base::iterator childrenEnd = task.mpChildrenToReparent->GetEnd();
+ for (MediumLockList::Base::iterator it = childrenBegin;
+ it != childrenEnd;
+ ++it)
+ {
+ Medium *pMedium = it->GetMedium();
+ AutoWriteLock childLock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ pMedium->i_deparent(); // removes pMedium from source
+ // no depth check, reduces depth
+ pMedium->i_setParent(pTarget);
+
+ if (task.NotifyAboutChanges())
+ pMediumsForNotify.insert(pMedium);
+ }
+ }
+ pMediumsForNotify.insert(pTarget);
+ }
+
+ /* unregister and uninitialize all media removed by the merge */
+ MediumLockList::Base::iterator lockListBegin =
+ task.mpMediumLockList->GetBegin();
+ MediumLockList::Base::iterator lockListEnd =
+ task.mpMediumLockList->GetEnd();
+ for (MediumLockList::Base::iterator it = lockListBegin;
+ it != lockListEnd;
+ )
+ {
+ MediumLock &mediumLock = *it;
+ /* Create a real copy of the medium pointer, as the medium
+ * lock deletion below would invalidate the referenced object. */
+ const ComObjPtr<Medium> pMedium = mediumLock.GetMedium();
+
+ /* The target and all media not merged (readonly) are skipped */
+ if ( pMedium == pTarget
+ || pMedium->m->state == MediumState_LockedRead)
+ {
+ ++it;
+ continue;
+ }
+
+ uIdsForNotify[pMedium->i_getId()] = pMedium->i_getDeviceType();
+ rc2 = pMedium->m->pVirtualBox->i_unregisterMedium(pMedium);
+ AssertComRC(rc2);
+
+ /* now, uninitialize the deleted medium (note that
+ * due to the Deleting state, uninit() will not touch
+ * the parent-child relationship so we need to
+ * uninitialize each disk individually) */
+
+ /* note that the operation initiator medium (which is
+ * normally also the source medium) is a special case
+ * -- there is one more caller added by Task to it which
+ * we must release. Also, if we are in sync mode, the
+ * caller may still hold an AutoCaller instance for it
+ * and therefore we cannot uninit() it (it's therefore
+ * the caller's responsibility) */
+ if (pMedium == this)
+ {
+ Assert(i_getChildren().size() == 0);
+ Assert(m->backRefs.size() == 0);
+ task.mMediumCaller.release();
+ }
+
+ /* Delete the medium lock list entry, which also releases the
+ * caller added by MergeChain before uninit() and updates the
+ * iterator to point to the right place. */
+ rc2 = task.mpMediumLockList->RemoveByIterator(it);
+ AssertComRC(rc2);
+
+ if (task.isAsync() || pMedium != this)
+ {
+ treeLock.release();
+ pMedium->uninit();
+ treeLock.acquire();
+ }
+ }
+ }
+
+ i_markRegistriesModified();
+ if (task.isAsync())
+ {
+ // in asynchronous mode, save settings now
+ eik.restore();
+ m->pVirtualBox->i_saveModifiedRegistries();
+ eik.fetch();
+ }
+
+ if (FAILED(mrc))
+ {
+ /* Here we come if either VDMerge() failed (in which case we
+ * assume that it tried to do everything to make a further
+ * retry possible -- e.g. not deleted intermediate media
+ * and so on) or VirtualBox::saveRegistries() failed (where we
+ * should have the original tree but with intermediate storage
+ * units deleted by VDMerge()). We have to only restore states
+ * (through the MergeChain dtor) unless we are run synchronously
+ * in which case it's the responsibility of the caller as stated
+ * in the mergeTo() docs. The latter also implies that we
+ * don't own the merge chain, so release it in this case. */
+ if (task.isAsync())
+ i_cancelMergeTo(task.mpChildrenToReparent, task.mpMediumLockList);
+ }
+ else if (task.NotifyAboutChanges())
+ {
+ for (std::set<ComObjPtr<Medium> >::const_iterator it = pMediumsForNotify.begin();
+ it != pMediumsForNotify.end();
+ ++it)
+ {
+ if (it->isNotNull())
+ m->pVirtualBox->i_onMediumConfigChanged(*it);
+ }
+ for (std::map<Guid, DeviceType_T>::const_iterator it = uIdsForNotify.begin();
+ it != uIdsForNotify.end();
+ ++it)
+ {
+ m->pVirtualBox->i_onMediumRegistered(it->first, it->second, FALSE);
+ }
+ }
+
+ return mrc;
+}
+
+/**
+ * Implementation code for the "clone" task.
+ *
+ * This only gets started from Medium::CloneTo() and always runs asynchronously.
+ * As a result, we always save the VirtualBox.xml file when we're done here.
+ *
+ * @param task
+ * @return
+ */
+HRESULT Medium::i_taskCloneHandler(Medium::CloneTask &task)
+{
+ /** @todo r=klaus The code below needs to be double checked with regard
+ * to lock order violations, it probably causes lock order issues related
+ * to the AutoCaller usage. */
+ HRESULT rcTmp = S_OK;
+
+ const ComObjPtr<Medium> &pTarget = task.mTarget;
+ const ComObjPtr<Medium> &pParent = task.mParent;
+
+ bool fCreatingTarget = false;
+
+ uint64_t size = 0, logicalSize = 0;
+ MediumVariant_T variant = MediumVariant_Standard;
+ bool fGenerateUuid = false;
+
+ try
+ {
+ if (!pParent.isNull())
+ {
+
+ if (pParent->i_getDepth() >= SETTINGS_MEDIUM_DEPTH_MAX)
+ {
+ AutoReadLock plock(pParent COMMA_LOCKVAL_SRC_POS);
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot clone image for medium '%s', because it exceeds the medium tree depth limit. Please merge some images which you no longer need"),
+ pParent->m->strLocationFull.c_str());
+ }
+ }
+
+ /* Lock all in {parent,child} order. The lock is also used as a
+ * signal from the task initiator (which releases it only after
+ * RTThreadCreate()) that we can start the job. */
+ AutoMultiWriteLock3 thisLock(this, pTarget, pParent COMMA_LOCKVAL_SRC_POS);
+
+ fCreatingTarget = pTarget->m->state == MediumState_Creating;
+
+ /* The object may request a specific UUID (through a special form of
+ * the moveTo() argument). Otherwise we have to generate it */
+ Guid targetId = pTarget->m->id;
+
+ fGenerateUuid = targetId.isZero();
+ if (fGenerateUuid)
+ {
+ targetId.create();
+ /* VirtualBox::registerMedium() will need UUID */
+ unconst(pTarget->m->id) = targetId;
+ }
+
+ PVDISK hdd;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ try
+ {
+ /* Open all media in the source chain. */
+ MediumLockList::Base::const_iterator sourceListBegin =
+ task.mpSourceMediumLockList->GetBegin();
+ MediumLockList::Base::const_iterator sourceListEnd =
+ task.mpSourceMediumLockList->GetEnd();
+ for (MediumLockList::Base::const_iterator it = sourceListBegin;
+ it != sourceListEnd;
+ ++it)
+ {
+ const MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+ AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ /* sanity check */
+ Assert(pMedium->m->state == MediumState_LockedRead);
+
+ /** Open all media in read-only mode. */
+ vrc = VDOpen(hdd,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ VD_OPEN_FLAGS_READONLY | m->uOpenFlagsDef,
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not open the medium storage unit '%s'%s"),
+ pMedium->m->strLocationFull.c_str(),
+ i_vdError(vrc).c_str());
+ }
+
+ Utf8Str targetFormat(pTarget->m->strFormat);
+ Utf8Str targetLocation(pTarget->m->strLocationFull);
+ uint64_t capabilities = pTarget->m->formatObj->i_getCapabilities();
+
+ Assert( pTarget->m->state == MediumState_Creating
+ || pTarget->m->state == MediumState_LockedWrite);
+ Assert(m->state == MediumState_LockedRead);
+ Assert( pParent.isNull()
+ || pParent->m->state == MediumState_LockedRead);
+
+ /* unlock before the potentially lengthy operation */
+ thisLock.release();
+
+ /* ensure the target directory exists */
+ if (capabilities & MediumFormatCapabilities_File)
+ {
+ HRESULT rc = VirtualBox::i_ensureFilePathExists(targetLocation,
+ !(task.mVariant & MediumVariant_NoCreateDir) /* fCreate */);
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ PVDISK targetHdd;
+ vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &targetHdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ try
+ {
+ /* Open all media in the target chain. */
+ MediumLockList::Base::const_iterator targetListBegin =
+ task.mpTargetMediumLockList->GetBegin();
+ MediumLockList::Base::const_iterator targetListEnd =
+ task.mpTargetMediumLockList->GetEnd();
+ for (MediumLockList::Base::const_iterator it = targetListBegin;
+ it != targetListEnd;
+ ++it)
+ {
+ const MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+
+ /* If the target medium is not created yet there's no
+ * reason to open it. */
+ if (pMedium == pTarget && fCreatingTarget)
+ continue;
+
+ AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ /* sanity check */
+ Assert( pMedium->m->state == MediumState_LockedRead
+ || pMedium->m->state == MediumState_LockedWrite);
+
+ unsigned uOpenFlags = VD_OPEN_FLAGS_NORMAL;
+ if (pMedium->m->state != MediumState_LockedWrite)
+ uOpenFlags = VD_OPEN_FLAGS_READONLY;
+ if (pMedium->m->type == MediumType_Shareable)
+ uOpenFlags |= VD_OPEN_FLAGS_SHAREABLE;
+
+ /* Open all media in appropriate mode. */
+ vrc = VDOpen(targetHdd,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ uOpenFlags | m->uOpenFlagsDef,
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not open the medium storage unit '%s'%s"),
+ pMedium->m->strLocationFull.c_str(),
+ i_vdError(vrc).c_str());
+ }
+
+ /* target isn't locked, but no changing data is accessed */
+ if (task.midxSrcImageSame == UINT32_MAX)
+ {
+ vrc = VDCopy(hdd,
+ VD_LAST_IMAGE,
+ targetHdd,
+ targetFormat.c_str(),
+ (fCreatingTarget) ? targetLocation.c_str() : (char *)NULL,
+ false /* fMoveByRename */,
+ task.mTargetLogicalSize /* cbSize */,
+ task.mVariant & ~(MediumVariant_NoCreateDir | MediumVariant_Formatted | MediumVariant_VmdkESX | MediumVariant_VmdkRawDisk),
+ targetId.raw(),
+ VD_OPEN_FLAGS_NORMAL | m->uOpenFlagsDef,
+ NULL /* pVDIfsOperation */,
+ pTarget->m->vdImageIfaces,
+ task.mVDOperationIfaces);
+ }
+ else
+ {
+ vrc = VDCopyEx(hdd,
+ VD_LAST_IMAGE,
+ targetHdd,
+ targetFormat.c_str(),
+ (fCreatingTarget) ? targetLocation.c_str() : (char *)NULL,
+ false /* fMoveByRename */,
+ task.mTargetLogicalSize /* cbSize */,
+ task.midxSrcImageSame,
+ task.midxDstImageSame,
+ task.mVariant & ~(MediumVariant_NoCreateDir | MediumVariant_Formatted | MediumVariant_VmdkESX | MediumVariant_VmdkRawDisk),
+ targetId.raw(),
+ VD_OPEN_FLAGS_NORMAL | m->uOpenFlagsDef,
+ NULL /* pVDIfsOperation */,
+ pTarget->m->vdImageIfaces,
+ task.mVDOperationIfaces);
+ }
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not create the clone medium '%s'%s"),
+ targetLocation.c_str(), i_vdError(vrc).c_str());
+
+ size = VDGetFileSize(targetHdd, VD_LAST_IMAGE);
+ logicalSize = VDGetSize(targetHdd, VD_LAST_IMAGE);
+ unsigned uImageFlags;
+ vrc = VDGetImageFlags(targetHdd, 0, &uImageFlags);
+ if (RT_SUCCESS(vrc))
+ variant = (MediumVariant_T)uImageFlags;
+ }
+ catch (HRESULT aRC) { rcTmp = aRC; }
+
+ VDDestroy(targetHdd);
+ }
+ catch (HRESULT aRC) { rcTmp = aRC; }
+
+ VDDestroy(hdd);
+ }
+ catch (HRESULT aRC) { rcTmp = aRC; }
+
+ ErrorInfoKeeper eik;
+ MultiResult mrc(rcTmp);
+
+ /* Only do the parent changes for newly created media. */
+ if (SUCCEEDED(mrc) && fCreatingTarget)
+ {
+ /* we set m->pParent & children() */
+ AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ Assert(pTarget->m->pParent.isNull());
+
+ if (pParent)
+ {
+ /* Associate the clone with the parent and deassociate
+ * from VirtualBox. Depth check above. */
+ pTarget->i_setParent(pParent);
+
+ /* register with mVirtualBox as the last step and move to
+ * Created state only on success (leaving an orphan file is
+ * better than breaking media registry consistency) */
+ eik.restore();
+ ComObjPtr<Medium> pMedium;
+ mrc = pParent->m->pVirtualBox->i_registerMedium(pTarget, &pMedium,
+ treeLock);
+ Assert( FAILED(mrc)
+ || pTarget == pMedium);
+ eik.fetch();
+
+ if (FAILED(mrc))
+ /* break parent association on failure to register */
+ pTarget->i_deparent(); // removes target from parent
+ }
+ else
+ {
+ /* just register */
+ eik.restore();
+ ComObjPtr<Medium> pMedium;
+ mrc = m->pVirtualBox->i_registerMedium(pTarget, &pMedium,
+ treeLock);
+ Assert( FAILED(mrc)
+ || pTarget == pMedium);
+ eik.fetch();
+ }
+ }
+
+ if (fCreatingTarget)
+ {
+ AutoWriteLock mLock(pTarget COMMA_LOCKVAL_SRC_POS);
+
+ if (SUCCEEDED(mrc))
+ {
+ pTarget->m->state = MediumState_Created;
+
+ pTarget->m->size = size;
+ pTarget->m->logicalSize = logicalSize;
+ pTarget->m->variant = variant;
+ }
+ else
+ {
+ /* back to NotCreated on failure */
+ pTarget->m->state = MediumState_NotCreated;
+
+ /* reset UUID to prevent it from being reused next time */
+ if (fGenerateUuid)
+ unconst(pTarget->m->id).clear();
+ }
+ }
+
+ /* Copy any filter related settings over to the target. */
+ if (SUCCEEDED(mrc))
+ {
+ /* Copy any filter related settings over. */
+ ComObjPtr<Medium> pBase = i_getBase();
+ ComObjPtr<Medium> pTargetBase = pTarget->i_getBase();
+ std::vector<com::Utf8Str> aFilterPropNames;
+ std::vector<com::Utf8Str> aFilterPropValues;
+ mrc = pBase->i_getFilterProperties(aFilterPropNames, aFilterPropValues);
+ if (SUCCEEDED(mrc))
+ {
+ /* Go through the properties and add them to the target medium. */
+ for (unsigned idx = 0; idx < aFilterPropNames.size(); idx++)
+ {
+ mrc = pTargetBase->i_setPropertyDirect(aFilterPropNames[idx], aFilterPropValues[idx]);
+ if (FAILED(mrc)) break;
+ }
+
+ // now, at the end of this task (always asynchronous), save the settings
+ if (SUCCEEDED(mrc))
+ {
+ // save the settings
+ i_markRegistriesModified();
+ /* collect multiple errors */
+ eik.restore();
+ m->pVirtualBox->i_saveModifiedRegistries();
+ eik.fetch();
+
+ if (task.NotifyAboutChanges())
+ {
+ if (!fCreatingTarget)
+ {
+ if (!aFilterPropNames.empty())
+ m->pVirtualBox->i_onMediumConfigChanged(pTargetBase);
+ if (pParent)
+ m->pVirtualBox->i_onMediumConfigChanged(pParent);
+ }
+ else
+ {
+ m->pVirtualBox->i_onMediumRegistered(pTarget->i_getId(), pTarget->i_getDeviceType(), TRUE);
+ }
+ }
+ }
+ }
+ }
+
+ /* Everything is explicitly unlocked when the task exits,
+ * as the task destruction also destroys the source chain. */
+
+ /* Make sure the source chain is released early. It could happen
+ * that we get a deadlock in Appliance::Import when Medium::Close
+ * is called & the source chain is released at the same time. */
+ task.mpSourceMediumLockList->Clear();
+
+ return mrc;
+}
+
+/**
+ * Implementation code for the "move" task.
+ *
+ * This only gets started from Medium::MoveTo() and always
+ * runs asynchronously.
+ *
+ * @param task
+ * @return
+ */
+HRESULT Medium::i_taskMoveHandler(Medium::MoveTask &task)
+{
+ LogFlowFuncEnter();
+ HRESULT rcOut = S_OK;
+
+ /* pTarget is equal "this" in our case */
+ const ComObjPtr<Medium> &pTarget = task.mMedium;
+
+ uint64_t size = 0; NOREF(size);
+ uint64_t logicalSize = 0; NOREF(logicalSize);
+ MediumVariant_T variant = MediumVariant_Standard; NOREF(variant);
+
+ /*
+ * it's exactly moving, not cloning
+ */
+ if (!i_isMoveOperation(pTarget))
+ {
+ HRESULT rc = setError(VBOX_E_FILE_ERROR,
+ tr("Wrong preconditions for moving the medium %s"),
+ pTarget->m->strLocationFull.c_str());
+ LogFlowFunc(("LEAVE: rc=%Rhrc (early)\n", rc));
+ return rc;
+ }
+
+ try
+ {
+ /* Lock all in {parent,child} order. The lock is also used as a
+ * signal from the task initiator (which releases it only after
+ * RTThreadCreate()) that we can start the job. */
+
+ AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
+
+ PVDISK hdd;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ try
+ {
+ /* Open all media in the source chain. */
+ MediumLockList::Base::const_iterator sourceListBegin =
+ task.mpMediumLockList->GetBegin();
+ MediumLockList::Base::const_iterator sourceListEnd =
+ task.mpMediumLockList->GetEnd();
+ for (MediumLockList::Base::const_iterator it = sourceListBegin;
+ it != sourceListEnd;
+ ++it)
+ {
+ const MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+ AutoWriteLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ /* sanity check */
+ Assert(pMedium->m->state == MediumState_LockedWrite);
+
+ vrc = VDOpen(hdd,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ VD_OPEN_FLAGS_NORMAL,
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not open the medium storage unit '%s'%s"),
+ pMedium->m->strLocationFull.c_str(),
+ i_vdError(vrc).c_str());
+ }
+
+ /* we can directly use pTarget->m->"variables" but for better reading we use local copies */
+ Guid targetId = pTarget->m->id;
+ Utf8Str targetFormat(pTarget->m->strFormat);
+ uint64_t targetCapabilities = pTarget->m->formatObj->i_getCapabilities();
+
+ /*
+ * change target location
+ * m->strNewLocationFull has been set already together with m->fMoveThisMedium in
+ * i_preparationForMoving()
+ */
+ Utf8Str targetLocation = i_getNewLocationForMoving();
+
+ /* unlock before the potentially lengthy operation */
+ thisLock.release();
+
+ /* ensure the target directory exists */
+ if (targetCapabilities & MediumFormatCapabilities_File)
+ {
+ HRESULT rc = VirtualBox::i_ensureFilePathExists(targetLocation,
+ !(task.mVariant & MediumVariant_NoCreateDir) /* fCreate */);
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ try
+ {
+ vrc = VDCopy(hdd,
+ VD_LAST_IMAGE,
+ hdd,
+ targetFormat.c_str(),
+ targetLocation.c_str(),
+ true /* fMoveByRename */,
+ 0 /* cbSize */,
+ VD_IMAGE_FLAGS_NONE,
+ targetId.raw(),
+ VD_OPEN_FLAGS_NORMAL,
+ NULL /* pVDIfsOperation */,
+ pTarget->m->vdImageIfaces,
+ task.mVDOperationIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not move medium '%s'%s"),
+ targetLocation.c_str(), i_vdError(vrc).c_str());
+ size = VDGetFileSize(hdd, VD_LAST_IMAGE);
+ logicalSize = VDGetSize(hdd, VD_LAST_IMAGE);
+ unsigned uImageFlags;
+ vrc = VDGetImageFlags(hdd, 0, &uImageFlags);
+ if (RT_SUCCESS(vrc))
+ variant = (MediumVariant_T)uImageFlags;
+
+ /*
+ * set current location, because VDCopy\VDCopyEx doesn't do it.
+ * also reset moving flag
+ */
+ i_resetMoveOperationData();
+ m->strLocationFull = targetLocation;
+
+ }
+ catch (HRESULT aRC) { rcOut = aRC; }
+
+ }
+ catch (HRESULT aRC) { rcOut = aRC; }
+
+ VDDestroy(hdd);
+ }
+ catch (HRESULT aRC) { rcOut = aRC; }
+
+ ErrorInfoKeeper eik;
+ MultiResult mrc(rcOut);
+
+ // now, at the end of this task (always asynchronous), save the settings
+ if (SUCCEEDED(mrc))
+ {
+ // save the settings
+ i_markRegistriesModified();
+ /* collect multiple errors */
+ eik.restore();
+ m->pVirtualBox->i_saveModifiedRegistries();
+ eik.fetch();
+ }
+
+ /* Everything is explicitly unlocked when the task exits,
+ * as the task destruction also destroys the source chain. */
+
+ task.mpMediumLockList->Clear();
+
+ if (task.NotifyAboutChanges() && SUCCEEDED(mrc))
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+
+ LogFlowFunc(("LEAVE: mrc=%Rhrc\n", (HRESULT)mrc));
+ return mrc;
+}
+
+/**
+ * Implementation code for the "delete" task.
+ *
+ * This task always gets started from Medium::deleteStorage() and can run
+ * synchronously or asynchronously depending on the "wait" parameter passed to
+ * that function.
+ *
+ * @param task
+ * @return
+ */
+HRESULT Medium::i_taskDeleteHandler(Medium::DeleteTask &task)
+{
+ NOREF(task);
+ HRESULT rc = S_OK;
+
+ try
+ {
+ /* The lock is also used as a signal from the task initiator (which
+ * releases it only after RTThreadCreate()) that we can start the job */
+ AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
+
+ PVDISK hdd;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ Utf8Str format(m->strFormat);
+ Utf8Str location(m->strLocationFull);
+
+ /* unlock before the potentially lengthy operation */
+ Assert(m->state == MediumState_Deleting);
+ thisLock.release();
+
+ try
+ {
+ vrc = VDOpen(hdd,
+ format.c_str(),
+ location.c_str(),
+ VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO | m->uOpenFlagsDef,
+ m->vdImageIfaces);
+ if (RT_SUCCESS(vrc))
+ vrc = VDClose(hdd, true /* fDelete */);
+
+ if (RT_FAILURE(vrc) && vrc != VERR_FILE_NOT_FOUND)
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not delete the medium storage unit '%s'%s"),
+ location.c_str(), i_vdError(vrc).c_str());
+
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ VDDestroy(hdd);
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* go to the NotCreated state even on failure since the storage
+ * may have been already partially deleted and cannot be used any
+ * more. One will be able to manually re-open the storage if really
+ * needed to re-register it. */
+ m->state = MediumState_NotCreated;
+
+ /* Reset UUID to prevent Create* from reusing it again */
+ com::Guid uOldId = m->id;
+ unconst(m->id).clear();
+
+ if (task.NotifyAboutChanges() && SUCCEEDED(rc))
+ {
+ if (m->pParent.isNotNull())
+ m->pVirtualBox->i_onMediumConfigChanged(m->pParent);
+ m->pVirtualBox->i_onMediumRegistered(uOldId, m->devType, FALSE);
+ }
+
+ return rc;
+}
+
+/**
+ * Implementation code for the "reset" task.
+ *
+ * This always gets started asynchronously from Medium::Reset().
+ *
+ * @param task
+ * @return
+ */
+HRESULT Medium::i_taskResetHandler(Medium::ResetTask &task)
+{
+ HRESULT rc = S_OK;
+
+ uint64_t size = 0, logicalSize = 0;
+ MediumVariant_T variant = MediumVariant_Standard;
+
+ try
+ {
+ /* The lock is also used as a signal from the task initiator (which
+ * releases it only after RTThreadCreate()) that we can start the job */
+ AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
+
+ /// @todo Below we use a pair of delete/create operations to reset
+ /// the diff contents but the most efficient way will of course be
+ /// to add a VDResetDiff() API call
+
+ PVDISK hdd;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ Guid id = m->id;
+ Utf8Str format(m->strFormat);
+ Utf8Str location(m->strLocationFull);
+
+ Medium *pParent = m->pParent;
+ Guid parentId = pParent->m->id;
+ Utf8Str parentFormat(pParent->m->strFormat);
+ Utf8Str parentLocation(pParent->m->strLocationFull);
+
+ Assert(m->state == MediumState_LockedWrite);
+
+ /* unlock before the potentially lengthy operation */
+ thisLock.release();
+
+ try
+ {
+ /* Open all media in the target chain but the last. */
+ MediumLockList::Base::const_iterator targetListBegin =
+ task.mpMediumLockList->GetBegin();
+ MediumLockList::Base::const_iterator targetListEnd =
+ task.mpMediumLockList->GetEnd();
+ for (MediumLockList::Base::const_iterator it = targetListBegin;
+ it != targetListEnd;
+ ++it)
+ {
+ const MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+
+ AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ /* sanity check, "this" is checked above */
+ Assert( pMedium == this
+ || pMedium->m->state == MediumState_LockedRead);
+
+ /* Open all media in appropriate mode. */
+ vrc = VDOpen(hdd,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ VD_OPEN_FLAGS_READONLY | m->uOpenFlagsDef,
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not open the medium storage unit '%s'%s"),
+ pMedium->m->strLocationFull.c_str(),
+ i_vdError(vrc).c_str());
+
+ /* Done when we hit the media which should be reset */
+ if (pMedium == this)
+ break;
+ }
+
+ /* first, delete the storage unit */
+ vrc = VDClose(hdd, true /* fDelete */);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not delete the medium storage unit '%s'%s"),
+ location.c_str(), i_vdError(vrc).c_str());
+
+ /* next, create it again */
+ vrc = VDOpen(hdd,
+ parentFormat.c_str(),
+ parentLocation.c_str(),
+ VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO | m->uOpenFlagsDef,
+ m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not open the medium storage unit '%s'%s"),
+ parentLocation.c_str(), i_vdError(vrc).c_str());
+
+ vrc = VDCreateDiff(hdd,
+ format.c_str(),
+ location.c_str(),
+ /// @todo use the same medium variant as before
+ VD_IMAGE_FLAGS_NONE,
+ NULL,
+ id.raw(),
+ parentId.raw(),
+ VD_OPEN_FLAGS_NORMAL,
+ m->vdImageIfaces,
+ task.mVDOperationIfaces);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_VD_INVALID_TYPE)
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Parameters for creating the differencing medium storage unit '%s' are invalid%s"),
+ location.c_str(), i_vdError(vrc).c_str());
+ else
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not create the differencing medium storage unit '%s'%s"),
+ location.c_str(), i_vdError(vrc).c_str());
+ }
+
+ size = VDGetFileSize(hdd, VD_LAST_IMAGE);
+ logicalSize = VDGetSize(hdd, VD_LAST_IMAGE);
+ unsigned uImageFlags;
+ vrc = VDGetImageFlags(hdd, 0, &uImageFlags);
+ if (RT_SUCCESS(vrc))
+ variant = (MediumVariant_T)uImageFlags;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ VDDestroy(hdd);
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->size = size;
+ m->logicalSize = logicalSize;
+ m->variant = variant;
+
+ if (task.NotifyAboutChanges() && SUCCEEDED(rc))
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+
+ /* Everything is explicitly unlocked when the task exits,
+ * as the task destruction also destroys the media chain. */
+
+ return rc;
+}
+
+/**
+ * Implementation code for the "compact" task.
+ *
+ * @param task
+ * @return
+ */
+HRESULT Medium::i_taskCompactHandler(Medium::CompactTask &task)
+{
+ HRESULT rc = S_OK;
+
+ /* Lock all in {parent,child} order. The lock is also used as a
+ * signal from the task initiator (which releases it only after
+ * RTThreadCreate()) that we can start the job. */
+ AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
+
+ try
+ {
+ PVDISK hdd;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ try
+ {
+ /* Open all media in the chain. */
+ MediumLockList::Base::const_iterator mediumListBegin =
+ task.mpMediumLockList->GetBegin();
+ MediumLockList::Base::const_iterator mediumListEnd =
+ task.mpMediumLockList->GetEnd();
+ MediumLockList::Base::const_iterator mediumListLast =
+ mediumListEnd;
+ --mediumListLast;
+ for (MediumLockList::Base::const_iterator it = mediumListBegin;
+ it != mediumListEnd;
+ ++it)
+ {
+ const MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+ AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ /* sanity check */
+ if (it == mediumListLast)
+ Assert(pMedium->m->state == MediumState_LockedWrite);
+ else
+ Assert(pMedium->m->state == MediumState_LockedRead);
+
+ /* Open all media but last in read-only mode. Do not handle
+ * shareable media, as compaction and sharing are mutually
+ * exclusive. */
+ vrc = VDOpen(hdd,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ m->uOpenFlagsDef | (it == mediumListLast ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY),
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not open the medium storage unit '%s'%s"),
+ pMedium->m->strLocationFull.c_str(),
+ i_vdError(vrc).c_str());
+ }
+
+ Assert(m->state == MediumState_LockedWrite);
+
+ Utf8Str location(m->strLocationFull);
+
+ /* unlock before the potentially lengthy operation */
+ thisLock.release();
+
+ vrc = VDCompact(hdd, VD_LAST_IMAGE, task.mVDOperationIfaces);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_NOT_SUPPORTED)
+ throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc,
+ tr("Compacting is not yet supported for medium '%s'"),
+ location.c_str());
+ else if (vrc == VERR_NOT_IMPLEMENTED)
+ throw setErrorBoth(E_NOTIMPL, vrc,
+ tr("Compacting is not implemented, medium '%s'"),
+ location.c_str());
+ else
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not compact medium '%s'%s"),
+ location.c_str(),
+ i_vdError(vrc).c_str());
+ }
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ VDDestroy(hdd);
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (task.NotifyAboutChanges() && SUCCEEDED(rc))
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+
+ /* Everything is explicitly unlocked when the task exits,
+ * as the task destruction also destroys the media chain. */
+
+ return rc;
+}
+
+/**
+ * Implementation code for the "resize" task.
+ *
+ * @param task
+ * @return
+ */
+HRESULT Medium::i_taskResizeHandler(Medium::ResizeTask &task)
+{
+ HRESULT rc = S_OK;
+
+ uint64_t size = 0, logicalSize = 0;
+
+ try
+ {
+ /* The lock is also used as a signal from the task initiator (which
+ * releases it only after RTThreadCreate()) that we can start the job */
+ AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
+
+ PVDISK hdd;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ try
+ {
+ /* Open all media in the chain. */
+ MediumLockList::Base::const_iterator mediumListBegin =
+ task.mpMediumLockList->GetBegin();
+ MediumLockList::Base::const_iterator mediumListEnd =
+ task.mpMediumLockList->GetEnd();
+ MediumLockList::Base::const_iterator mediumListLast =
+ mediumListEnd;
+ --mediumListLast;
+ for (MediumLockList::Base::const_iterator it = mediumListBegin;
+ it != mediumListEnd;
+ ++it)
+ {
+ const MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+ AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ /* sanity check */
+ if (it == mediumListLast)
+ Assert(pMedium->m->state == MediumState_LockedWrite);
+ else
+ Assert(pMedium->m->state == MediumState_LockedRead ||
+ // Allow resize the target image during mergeTo in case
+ // of direction from parent to child because all intermediate
+ // images are marked to MediumState_Deleting and will be
+ // destroyed after successful merge
+ pMedium->m->state == MediumState_Deleting);
+
+ /* Open all media but last in read-only mode. Do not handle
+ * shareable media, as compaction and sharing are mutually
+ * exclusive. */
+ vrc = VDOpen(hdd,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ m->uOpenFlagsDef | (it == mediumListLast ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY),
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not open the medium storage unit '%s'%s"),
+ pMedium->m->strLocationFull.c_str(),
+ i_vdError(vrc).c_str());
+ }
+
+ Assert(m->state == MediumState_LockedWrite);
+
+ Utf8Str location(m->strLocationFull);
+
+ /* unlock before the potentially lengthy operation */
+ thisLock.release();
+
+ VDGEOMETRY geo = {0, 0, 0}; /* auto */
+ vrc = VDResize(hdd, task.mSize, &geo, &geo, task.mVDOperationIfaces);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_VD_SHRINK_NOT_SUPPORTED)
+ throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc,
+ tr("Shrinking is not yet supported for medium '%s'"),
+ location.c_str());
+ if (vrc == VERR_NOT_SUPPORTED)
+ throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc,
+ tr("Resizing to new size %llu is not yet supported for medium '%s'"),
+ task.mSize, location.c_str());
+ else if (vrc == VERR_NOT_IMPLEMENTED)
+ throw setErrorBoth(E_NOTIMPL, vrc,
+ tr("Resizing is not implemented, medium '%s'"),
+ location.c_str());
+ else
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not resize medium '%s'%s"),
+ location.c_str(),
+ i_vdError(vrc).c_str());
+ }
+ size = VDGetFileSize(hdd, VD_LAST_IMAGE);
+ logicalSize = VDGetSize(hdd, VD_LAST_IMAGE);
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ VDDestroy(hdd);
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (SUCCEEDED(rc))
+ {
+ AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
+ m->size = size;
+ m->logicalSize = logicalSize;
+
+ if (task.NotifyAboutChanges())
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+ }
+
+ /* Everything is explicitly unlocked when the task exits,
+ * as the task destruction also destroys the media chain. */
+
+ return rc;
+}
+
+/**
+ * Implementation code for the "import" task.
+ *
+ * This only gets started from Medium::importFile() and always runs
+ * asynchronously. It potentially touches the media registry, so we
+ * always save the VirtualBox.xml file when we're done here.
+ *
+ * @param task
+ * @return
+ */
+HRESULT Medium::i_taskImportHandler(Medium::ImportTask &task)
+{
+ /** @todo r=klaus The code below needs to be double checked with regard
+ * to lock order violations, it probably causes lock order issues related
+ * to the AutoCaller usage. */
+ HRESULT rcTmp = S_OK;
+
+ const ComObjPtr<Medium> &pParent = task.mParent;
+
+ bool fCreatingTarget = false;
+
+ uint64_t size = 0, logicalSize = 0;
+ MediumVariant_T variant = MediumVariant_Standard;
+ bool fGenerateUuid = false;
+
+ try
+ {
+ if (!pParent.isNull())
+ if (pParent->i_getDepth() >= SETTINGS_MEDIUM_DEPTH_MAX)
+ {
+ AutoReadLock plock(pParent COMMA_LOCKVAL_SRC_POS);
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot import image for medium '%s', because it exceeds the medium tree depth limit. Please merge some images which you no longer need"),
+ pParent->m->strLocationFull.c_str());
+ }
+
+ /* Lock all in {parent,child} order. The lock is also used as a
+ * signal from the task initiator (which releases it only after
+ * RTThreadCreate()) that we can start the job. */
+ AutoMultiWriteLock2 thisLock(this, pParent COMMA_LOCKVAL_SRC_POS);
+
+ fCreatingTarget = m->state == MediumState_Creating;
+
+ /* The object may request a specific UUID (through a special form of
+ * the moveTo() argument). Otherwise we have to generate it */
+ Guid targetId = m->id;
+
+ fGenerateUuid = targetId.isZero();
+ if (fGenerateUuid)
+ {
+ targetId.create();
+ /* VirtualBox::i_registerMedium() will need UUID */
+ unconst(m->id) = targetId;
+ }
+
+
+ PVDISK hdd;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ try
+ {
+ /* Open source medium. */
+ vrc = VDOpen(hdd,
+ task.mFormat->i_getId().c_str(),
+ task.mFilename.c_str(),
+ VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_SEQUENTIAL | m->uOpenFlagsDef,
+ task.mVDImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not open the medium storage unit '%s'%s"),
+ task.mFilename.c_str(),
+ i_vdError(vrc).c_str());
+
+ Utf8Str targetFormat(m->strFormat);
+ Utf8Str targetLocation(m->strLocationFull);
+ uint64_t capabilities = task.mFormat->i_getCapabilities();
+
+ Assert( m->state == MediumState_Creating
+ || m->state == MediumState_LockedWrite);
+ Assert( pParent.isNull()
+ || pParent->m->state == MediumState_LockedRead);
+
+ /* unlock before the potentially lengthy operation */
+ thisLock.release();
+
+ /* ensure the target directory exists */
+ if (capabilities & MediumFormatCapabilities_File)
+ {
+ HRESULT rc = VirtualBox::i_ensureFilePathExists(targetLocation,
+ !(task.mVariant & MediumVariant_NoCreateDir) /* fCreate */);
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ PVDISK targetHdd;
+ vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &targetHdd);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ try
+ {
+ /* Open all media in the target chain. */
+ MediumLockList::Base::const_iterator targetListBegin =
+ task.mpTargetMediumLockList->GetBegin();
+ MediumLockList::Base::const_iterator targetListEnd =
+ task.mpTargetMediumLockList->GetEnd();
+ for (MediumLockList::Base::const_iterator it = targetListBegin;
+ it != targetListEnd;
+ ++it)
+ {
+ const MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+
+ /* If the target medium is not created yet there's no
+ * reason to open it. */
+ if (pMedium == this && fCreatingTarget)
+ continue;
+
+ AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ /* sanity check */
+ Assert( pMedium->m->state == MediumState_LockedRead
+ || pMedium->m->state == MediumState_LockedWrite);
+
+ unsigned uOpenFlags = VD_OPEN_FLAGS_NORMAL;
+ if (pMedium->m->state != MediumState_LockedWrite)
+ uOpenFlags = VD_OPEN_FLAGS_READONLY;
+ if (pMedium->m->type == MediumType_Shareable)
+ uOpenFlags |= VD_OPEN_FLAGS_SHAREABLE;
+
+ /* Open all media in appropriate mode. */
+ vrc = VDOpen(targetHdd,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ uOpenFlags | m->uOpenFlagsDef,
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not open the medium storage unit '%s'%s"),
+ pMedium->m->strLocationFull.c_str(),
+ i_vdError(vrc).c_str());
+ }
+
+ vrc = VDCopy(hdd,
+ VD_LAST_IMAGE,
+ targetHdd,
+ targetFormat.c_str(),
+ (fCreatingTarget) ? targetLocation.c_str() : (char *)NULL,
+ false /* fMoveByRename */,
+ 0 /* cbSize */,
+ task.mVariant & ~(MediumVariant_NoCreateDir | MediumVariant_Formatted | MediumVariant_VmdkESX | MediumVariant_VmdkRawDisk),
+ targetId.raw(),
+ VD_OPEN_FLAGS_NORMAL,
+ NULL /* pVDIfsOperation */,
+ m->vdImageIfaces,
+ task.mVDOperationIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not create the imported medium '%s'%s"),
+ targetLocation.c_str(), i_vdError(vrc).c_str());
+
+ size = VDGetFileSize(targetHdd, VD_LAST_IMAGE);
+ logicalSize = VDGetSize(targetHdd, VD_LAST_IMAGE);
+ unsigned uImageFlags;
+ vrc = VDGetImageFlags(targetHdd, 0, &uImageFlags);
+ if (RT_SUCCESS(vrc))
+ variant = (MediumVariant_T)uImageFlags;
+ }
+ catch (HRESULT aRC) { rcTmp = aRC; }
+
+ VDDestroy(targetHdd);
+ }
+ catch (HRESULT aRC) { rcTmp = aRC; }
+
+ VDDestroy(hdd);
+ }
+ catch (HRESULT aRC) { rcTmp = aRC; }
+
+ ErrorInfoKeeper eik;
+ MultiResult mrc(rcTmp);
+
+ /* Only do the parent changes for newly created media. */
+ if (SUCCEEDED(mrc) && fCreatingTarget)
+ {
+ /* we set m->pParent & children() */
+ AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ Assert(m->pParent.isNull());
+
+ if (pParent)
+ {
+ /* Associate the imported medium with the parent and deassociate
+ * from VirtualBox. Depth check above. */
+ i_setParent(pParent);
+
+ /* register with mVirtualBox as the last step and move to
+ * Created state only on success (leaving an orphan file is
+ * better than breaking media registry consistency) */
+ eik.restore();
+ ComObjPtr<Medium> pMedium;
+ mrc = pParent->m->pVirtualBox->i_registerMedium(this, &pMedium,
+ treeLock);
+ Assert(this == pMedium);
+ eik.fetch();
+
+ if (FAILED(mrc))
+ /* break parent association on failure to register */
+ this->i_deparent(); // removes target from parent
+ }
+ else
+ {
+ /* just register */
+ eik.restore();
+ ComObjPtr<Medium> pMedium;
+ mrc = m->pVirtualBox->i_registerMedium(this, &pMedium, treeLock);
+ Assert(this == pMedium);
+ eik.fetch();
+ }
+ }
+
+ if (fCreatingTarget)
+ {
+ AutoWriteLock mLock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (SUCCEEDED(mrc))
+ {
+ m->state = MediumState_Created;
+
+ m->size = size;
+ m->logicalSize = logicalSize;
+ m->variant = variant;
+ }
+ else
+ {
+ /* back to NotCreated on failure */
+ m->state = MediumState_NotCreated;
+
+ /* reset UUID to prevent it from being reused next time */
+ if (fGenerateUuid)
+ unconst(m->id).clear();
+ }
+ }
+
+ // now, at the end of this task (always asynchronous), save the settings
+ {
+ // save the settings
+ i_markRegistriesModified();
+ /* collect multiple errors */
+ eik.restore();
+ m->pVirtualBox->i_saveModifiedRegistries();
+ eik.fetch();
+ }
+
+ /* Everything is explicitly unlocked when the task exits,
+ * as the task destruction also destroys the target chain. */
+
+ /* Make sure the target chain is released early, otherwise it can
+ * lead to deadlocks with concurrent IAppliance activities. */
+ task.mpTargetMediumLockList->Clear();
+
+ if (task.NotifyAboutChanges() && SUCCEEDED(mrc))
+ {
+ if (pParent)
+ m->pVirtualBox->i_onMediumConfigChanged(pParent);
+ if (fCreatingTarget)
+ m->pVirtualBox->i_onMediumConfigChanged(this);
+ else
+ m->pVirtualBox->i_onMediumRegistered(m->id, m->devType, TRUE);
+ }
+
+ return mrc;
+}
+
+/**
+ * Sets up the encryption settings for a filter.
+ */
+void Medium::i_taskEncryptSettingsSetup(MediumCryptoFilterSettings *pSettings, const char *pszCipher,
+ const char *pszKeyStore, const char *pszPassword,
+ bool fCreateKeyStore)
+{
+ pSettings->pszCipher = pszCipher;
+ pSettings->pszPassword = pszPassword;
+ pSettings->pszKeyStoreLoad = pszKeyStore;
+ pSettings->fCreateKeyStore = fCreateKeyStore;
+ pSettings->pbDek = NULL;
+ pSettings->cbDek = 0;
+ pSettings->vdFilterIfaces = NULL;
+
+ pSettings->vdIfCfg.pfnAreKeysValid = i_vdCryptoConfigAreKeysValid;
+ pSettings->vdIfCfg.pfnQuerySize = i_vdCryptoConfigQuerySize;
+ pSettings->vdIfCfg.pfnQuery = i_vdCryptoConfigQuery;
+ pSettings->vdIfCfg.pfnQueryBytes = NULL;
+
+ pSettings->vdIfCrypto.pfnKeyRetain = i_vdCryptoKeyRetain;
+ pSettings->vdIfCrypto.pfnKeyRelease = i_vdCryptoKeyRelease;
+ pSettings->vdIfCrypto.pfnKeyStorePasswordRetain = i_vdCryptoKeyStorePasswordRetain;
+ pSettings->vdIfCrypto.pfnKeyStorePasswordRelease = i_vdCryptoKeyStorePasswordRelease;
+ pSettings->vdIfCrypto.pfnKeyStoreSave = i_vdCryptoKeyStoreSave;
+ pSettings->vdIfCrypto.pfnKeyStoreReturnParameters = i_vdCryptoKeyStoreReturnParameters;
+
+ int vrc = VDInterfaceAdd(&pSettings->vdIfCfg.Core,
+ "Medium::vdInterfaceCfgCrypto",
+ VDINTERFACETYPE_CONFIG, pSettings,
+ sizeof(VDINTERFACECONFIG), &pSettings->vdFilterIfaces);
+ AssertRC(vrc);
+
+ vrc = VDInterfaceAdd(&pSettings->vdIfCrypto.Core,
+ "Medium::vdInterfaceCrypto",
+ VDINTERFACETYPE_CRYPTO, pSettings,
+ sizeof(VDINTERFACECRYPTO), &pSettings->vdFilterIfaces);
+ AssertRC(vrc);
+}
+
+/**
+ * Implementation code for the "encrypt" task.
+ *
+ * @param task
+ * @return
+ */
+HRESULT Medium::i_taskEncryptHandler(Medium::EncryptTask &task)
+{
+# ifndef VBOX_WITH_EXTPACK
+ RT_NOREF(task);
+# endif
+ HRESULT rc = S_OK;
+
+ /* Lock all in {parent,child} order. The lock is also used as a
+ * signal from the task initiator (which releases it only after
+ * RTThreadCreate()) that we can start the job. */
+ ComObjPtr<Medium> pBase = i_getBase();
+ AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS);
+
+ try
+ {
+# ifdef VBOX_WITH_EXTPACK
+ ExtPackManager *pExtPackManager = m->pVirtualBox->i_getExtPackManager();
+ if (pExtPackManager->i_isExtPackUsable(ORACLE_PUEL_EXTPACK_NAME))
+ {
+ /* Load the plugin */
+ Utf8Str strPlugin;
+ rc = pExtPackManager->i_getLibraryPathForExtPack(g_szVDPlugin, ORACLE_PUEL_EXTPACK_NAME, &strPlugin);
+ if (SUCCEEDED(rc))
+ {
+ int vrc = VDPluginLoadFromFilename(strPlugin.c_str());
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc,
+ tr("Encrypting the image failed because the encryption plugin could not be loaded (%s)"),
+ i_vdError(vrc).c_str());
+ }
+ else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Encryption is not supported because the extension pack '%s' is missing the encryption plugin (old extension pack installed?)"),
+ ORACLE_PUEL_EXTPACK_NAME);
+ }
+ else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Encryption is not supported because the extension pack '%s' is missing"),
+ ORACLE_PUEL_EXTPACK_NAME);
+
+ PVDISK pDisk = NULL;
+ int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &pDisk);
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ MediumCryptoFilterSettings CryptoSettingsRead;
+ MediumCryptoFilterSettings CryptoSettingsWrite;
+
+ void *pvBuf = NULL;
+ const char *pszPasswordNew = NULL;
+ try
+ {
+ /* Set up disk encryption filters. */
+ if (task.mstrCurrentPassword.isEmpty())
+ {
+ /*
+ * Query whether the medium property indicating that encryption is
+ * configured is existing.
+ */
+ settings::StringsMap::iterator it = pBase->m->mapProperties.find("CRYPT/KeyStore");
+ if (it != pBase->m->mapProperties.end())
+ throw setError(VBOX_E_PASSWORD_INCORRECT,
+ tr("The password given for the encrypted image is incorrect"));
+ }
+ else
+ {
+ settings::StringsMap::iterator it = pBase->m->mapProperties.find("CRYPT/KeyStore");
+ if (it == pBase->m->mapProperties.end())
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The image is not configured for encryption"));
+
+ i_taskEncryptSettingsSetup(&CryptoSettingsRead, NULL, it->second.c_str(), task.mstrCurrentPassword.c_str(),
+ false /* fCreateKeyStore */);
+ vrc = VDFilterAdd(pDisk, "CRYPT", VD_FILTER_FLAGS_READ, CryptoSettingsRead.vdFilterIfaces);
+ if (vrc == VERR_VD_PASSWORD_INCORRECT)
+ throw setError(VBOX_E_PASSWORD_INCORRECT,
+ tr("The password to decrypt the image is incorrect"));
+ else if (RT_FAILURE(vrc))
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Failed to load the decryption filter: %s"),
+ i_vdError(vrc).c_str());
+ }
+
+ if (task.mstrCipher.isNotEmpty())
+ {
+ if ( task.mstrNewPassword.isEmpty()
+ && task.mstrNewPasswordId.isEmpty()
+ && task.mstrCurrentPassword.isNotEmpty())
+ {
+ /* An empty password and password ID will default to the current password. */
+ pszPasswordNew = task.mstrCurrentPassword.c_str();
+ }
+ else if (task.mstrNewPassword.isEmpty())
+ throw setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("A password must be given for the image encryption"));
+ else if (task.mstrNewPasswordId.isEmpty())
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("A valid identifier for the password must be given"));
+ else
+ pszPasswordNew = task.mstrNewPassword.c_str();
+
+ i_taskEncryptSettingsSetup(&CryptoSettingsWrite, task.mstrCipher.c_str(), NULL,
+ pszPasswordNew, true /* fCreateKeyStore */);
+ vrc = VDFilterAdd(pDisk, "CRYPT", VD_FILTER_FLAGS_WRITE, CryptoSettingsWrite.vdFilterIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_INVALID_OBJECT_STATE, vrc,
+ tr("Failed to load the encryption filter: %s"),
+ i_vdError(vrc).c_str());
+ }
+ else if (task.mstrNewPasswordId.isNotEmpty() || task.mstrNewPassword.isNotEmpty())
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The password and password identifier must be empty if the output should be unencrypted"));
+
+ /* Open all media in the chain. */
+ MediumLockList::Base::const_iterator mediumListBegin =
+ task.mpMediumLockList->GetBegin();
+ MediumLockList::Base::const_iterator mediumListEnd =
+ task.mpMediumLockList->GetEnd();
+ MediumLockList::Base::const_iterator mediumListLast =
+ mediumListEnd;
+ --mediumListLast;
+ for (MediumLockList::Base::const_iterator it = mediumListBegin;
+ it != mediumListEnd;
+ ++it)
+ {
+ const MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+ AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ Assert(pMedium->m->state == MediumState_LockedWrite);
+
+ /* Open all media but last in read-only mode. Do not handle
+ * shareable media, as compaction and sharing are mutually
+ * exclusive. */
+ vrc = VDOpen(pDisk,
+ pMedium->m->strFormat.c_str(),
+ pMedium->m->strLocationFull.c_str(),
+ m->uOpenFlagsDef | (it == mediumListLast ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY),
+ pMedium->m->vdImageIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not open the medium storage unit '%s'%s"),
+ pMedium->m->strLocationFull.c_str(),
+ i_vdError(vrc).c_str());
+ }
+
+ Assert(m->state == MediumState_LockedWrite);
+
+ Utf8Str location(m->strLocationFull);
+
+ /* unlock before the potentially lengthy operation */
+ thisLock.release();
+
+ vrc = VDPrepareWithFilters(pDisk, task.mVDOperationIfaces);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Could not prepare disk images for encryption (%Rrc): %s"),
+ vrc, i_vdError(vrc).c_str());
+
+ thisLock.acquire();
+ /* If everything went well set the new key store. */
+ settings::StringsMap::iterator it = pBase->m->mapProperties.find("CRYPT/KeyStore");
+ if (it != pBase->m->mapProperties.end())
+ pBase->m->mapProperties.erase(it);
+
+ /* Delete KeyId if encryption is removed or the password did change. */
+ if ( task.mstrNewPasswordId.isNotEmpty()
+ || task.mstrCipher.isEmpty())
+ {
+ it = pBase->m->mapProperties.find("CRYPT/KeyId");
+ if (it != pBase->m->mapProperties.end())
+ pBase->m->mapProperties.erase(it);
+ }
+
+ if (CryptoSettingsWrite.pszKeyStore)
+ {
+ pBase->m->mapProperties["CRYPT/KeyStore"] = Utf8Str(CryptoSettingsWrite.pszKeyStore);
+ if (task.mstrNewPasswordId.isNotEmpty())
+ pBase->m->mapProperties["CRYPT/KeyId"] = task.mstrNewPasswordId;
+ }
+
+ if (CryptoSettingsRead.pszCipherReturned)
+ RTStrFree(CryptoSettingsRead.pszCipherReturned);
+
+ if (CryptoSettingsWrite.pszCipherReturned)
+ RTStrFree(CryptoSettingsWrite.pszCipherReturned);
+
+ thisLock.release();
+ pBase->i_markRegistriesModified();
+ m->pVirtualBox->i_saveModifiedRegistries();
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ if (pvBuf)
+ RTMemFree(pvBuf);
+
+ VDDestroy(pDisk);
+# else
+ throw setError(VBOX_E_NOT_SUPPORTED,
+ tr("Encryption is not supported because extension pack support is not built in"));
+# endif
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ /* Everything is explicitly unlocked when the task exits,
+ * as the task destruction also destroys the media chain. */
+
+ return rc;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/MediumLock.cpp b/src/VBox/Main/src-server/MediumLock.cpp
new file mode 100644
index 00000000..42610513
--- /dev/null
+++ b/src/VBox/Main/src-server/MediumLock.cpp
@@ -0,0 +1,401 @@
+/* $Id: MediumLock.cpp $ */
+/** @file
+ *
+ * Medium lock management helper classes
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "MediumLock.h"
+#include "MediumImpl.h"
+#include "MediumAttachmentImpl.h"
+
+
+MediumLock::MediumLock()
+ : mMedium(NULL), mMediumCaller(NULL), mLockWrite(false),
+ mIsLocked(false), mLockSkipped(false)
+{
+}
+
+MediumLock::~MediumLock()
+{
+ // destroying medium locks is routinely done as part of error handling
+ // and it's not expected to lose error info
+ ErrorInfoKeeper eik;
+ Unlock();
+}
+
+MediumLock::MediumLock(const MediumLock &aMediumLock)
+ : mMedium(aMediumLock.mMedium), mMediumCaller(NULL),
+ mLockWrite(aMediumLock.mLockWrite), mIsLocked(false), mLockSkipped(false)
+{
+}
+
+MediumLock::MediumLock(const ComObjPtr<Medium> &aMedium, bool aLockWrite)
+ : mMedium(aMedium), mMediumCaller(NULL), mLockWrite(aLockWrite),
+ mIsLocked(false), mLockSkipped(false)
+{
+}
+
+HRESULT MediumLock::UpdateLock(bool aLockWrite)
+{
+ bool fPrevLockWrite = mLockWrite;
+ if (mIsLocked)
+ {
+ Unlock();
+ mLockWrite = aLockWrite;
+ HRESULT rc = Lock();
+ if (FAILED(rc))
+ {
+ mLockWrite = fPrevLockWrite;
+ Lock();
+ return rc;
+ }
+ return S_OK;
+ }
+
+ mLockWrite = aLockWrite;
+ return S_OK;
+}
+
+const ComObjPtr<Medium> &MediumLock::GetMedium() const
+{
+ return mMedium;
+}
+
+bool MediumLock::GetLockRequest() const
+{
+ return mLockWrite;
+}
+
+bool MediumLock::IsLocked() const
+{
+ return mIsLocked;
+}
+
+HRESULT MediumLock::Lock(bool aIgnoreLockedMedia)
+{
+ if (mIsLocked)
+ return S_OK;
+
+ mMediumCaller.attach(mMedium);
+ if (FAILED(mMediumCaller.rc()))
+ {
+ mMediumCaller.attach(NULL);
+ return VBOX_E_INVALID_OBJECT_STATE;
+ }
+
+ HRESULT rc = S_OK;
+ MediumState_T state;
+ {
+ AutoReadLock alock(mMedium COMMA_LOCKVAL_SRC_POS);
+ state = mMedium->i_getState();
+ }
+ switch (state)
+ {
+ case MediumState_NotCreated:
+ case MediumState_Creating:
+ case MediumState_Deleting:
+ mLockSkipped = true;
+ break;
+ default:
+ if (mLockWrite)
+ {
+ if (aIgnoreLockedMedia && ( state == MediumState_LockedRead
+ || state == MediumState_LockedWrite))
+ return S_OK;
+ else
+ rc = mMedium->LockWrite(mToken.asOutParam());
+ }
+ else
+ {
+ if (aIgnoreLockedMedia && state == MediumState_LockedWrite)
+ return S_OK;
+ else
+ rc = mMedium->LockRead(mToken.asOutParam());
+ }
+ }
+ if (SUCCEEDED(rc))
+ {
+ mIsLocked = true;
+ return S_OK;
+ }
+ else
+ {
+ mMediumCaller.attach(NULL);
+ return VBOX_E_INVALID_OBJECT_STATE;
+ }
+}
+
+HRESULT MediumLock::Unlock()
+{
+ HRESULT rc = S_OK;
+ if (mIsLocked && !mLockSkipped && mToken)
+ {
+ mToken->Abandon();
+ mToken.setNull();
+ }
+ mMediumCaller.attach(NULL);
+ mLockSkipped = false;
+ mIsLocked = false;
+ return rc;
+}
+
+MediumLockList::MediumLockList()
+{
+ mIsLocked = false;
+}
+
+MediumLockList::~MediumLockList()
+{
+ // destroying medium lock lists is routinely done as part of error handling
+ // and it's not expected to lose error info
+ ErrorInfoKeeper eik;
+ Clear();
+ // rest is done by the list object's destructor
+}
+
+bool MediumLockList::IsEmpty()
+{
+ return mMediumLocks.empty();
+}
+
+HRESULT MediumLockList::Append(const ComObjPtr<Medium> &aMedium, bool aLockWrite)
+{
+ if (mIsLocked)
+ return VBOX_E_INVALID_OBJECT_STATE;
+ mMediumLocks.push_back(MediumLock(aMedium, aLockWrite));
+ return S_OK;
+}
+
+HRESULT MediumLockList::Prepend(const ComObjPtr<Medium> &aMedium, bool aLockWrite)
+{
+ if (mIsLocked)
+ return VBOX_E_INVALID_OBJECT_STATE;
+ mMediumLocks.push_front(MediumLock(aMedium, aLockWrite));
+ return S_OK;
+}
+
+HRESULT MediumLockList::Update(const ComObjPtr<Medium> &aMedium, bool aLockWrite)
+{
+ for (MediumLockList::Base::iterator it = mMediumLocks.begin();
+ it != mMediumLocks.end();
+ ++it)
+ {
+ if (it->GetMedium() == aMedium)
+ return it->UpdateLock(aLockWrite);
+ }
+ return VBOX_E_INVALID_OBJECT_STATE;
+}
+
+HRESULT MediumLockList::RemoveByIterator(Base::iterator &aIt)
+{
+ HRESULT rc = aIt->Unlock();
+ aIt = mMediumLocks.erase(aIt);
+ return rc;
+}
+
+HRESULT MediumLockList::Clear()
+{
+ HRESULT rc = Unlock();
+ mMediumLocks.clear();
+ return rc;
+}
+
+MediumLockList::Base::iterator MediumLockList::GetBegin()
+{
+ return mMediumLocks.begin();
+}
+
+MediumLockList::Base::iterator MediumLockList::GetEnd()
+{
+ return mMediumLocks.end();
+}
+
+HRESULT MediumLockList::Lock(bool fSkipOverLockedMedia /* = false */)
+{
+ if (mIsLocked)
+ return S_OK;
+ HRESULT rc = S_OK;
+ for (MediumLockList::Base::iterator it = mMediumLocks.begin();
+ it != mMediumLocks.end();
+ ++it)
+ {
+ rc = it->Lock(fSkipOverLockedMedia);
+ if (FAILED(rc))
+ {
+ for (MediumLockList::Base::iterator it2 = mMediumLocks.begin();
+ it2 != it;
+ ++it2)
+ {
+ HRESULT rc2 = it2->Unlock();
+ AssertComRC(rc2);
+ }
+ break;
+ }
+ }
+ if (SUCCEEDED(rc))
+ mIsLocked = true;
+ return rc;
+}
+
+HRESULT MediumLockList::Unlock()
+{
+ if (!mIsLocked)
+ return S_OK;
+ HRESULT rc = S_OK;
+ for (MediumLockList::Base::iterator it = mMediumLocks.begin();
+ it != mMediumLocks.end();
+ ++it)
+ {
+ HRESULT rc2 = it->Unlock();
+ if (SUCCEEDED(rc) && FAILED(rc2))
+ rc = rc2;
+ }
+ mIsLocked = false;
+ return rc;
+}
+
+
+MediumLockListMap::MediumLockListMap()
+{
+ mIsLocked = false;
+}
+
+MediumLockListMap::~MediumLockListMap()
+{
+ // destroying medium lock list maps is routinely done as part of
+ // error handling and it's not expected to lose error info
+ ErrorInfoKeeper eik;
+ Clear();
+ // rest is done by the map object's destructor
+}
+
+bool MediumLockListMap::IsEmpty()
+{
+ return mMediumLocks.empty();
+}
+
+HRESULT MediumLockListMap::Insert(const ComObjPtr<MediumAttachment> &aMediumAttachment,
+ MediumLockList *aMediumLockList)
+{
+ if (mIsLocked)
+ return VBOX_E_INVALID_OBJECT_STATE;
+ mMediumLocks[aMediumAttachment] = aMediumLockList;
+ return S_OK;
+}
+
+HRESULT MediumLockListMap::ReplaceKey(const ComObjPtr<MediumAttachment> &aMediumAttachmentOld,
+ const ComObjPtr<MediumAttachment> &aMediumAttachmentNew)
+{
+ MediumLockListMap::Base::iterator it = mMediumLocks.find(aMediumAttachmentOld);
+ if (it == mMediumLocks.end())
+ return VBOX_E_INVALID_OBJECT_STATE;
+ MediumLockList *pMediumLockList = it->second;
+ mMediumLocks.erase(it);
+ mMediumLocks[aMediumAttachmentNew] = pMediumLockList;
+ return S_OK;
+}
+
+HRESULT MediumLockListMap::Remove(const ComObjPtr<MediumAttachment> &aMediumAttachment)
+{
+ MediumLockListMap::Base::iterator it = mMediumLocks.find(aMediumAttachment);
+ if (it == mMediumLocks.end())
+ return VBOX_E_INVALID_OBJECT_STATE;
+ MediumLockList *pMediumLockList = it->second;
+ mMediumLocks.erase(it);
+ delete pMediumLockList;
+ return S_OK;
+}
+
+HRESULT MediumLockListMap::Clear()
+{
+ HRESULT rc = Unlock();
+ for (MediumLockListMap::Base::iterator it = mMediumLocks.begin();
+ it != mMediumLocks.end();
+ ++it)
+ {
+ MediumLockList *pMediumLockList = it->second;
+ delete pMediumLockList;
+ }
+ mMediumLocks.clear();
+ return rc;
+}
+
+HRESULT MediumLockListMap::Get(const ComObjPtr<MediumAttachment> &aMediumAttachment,
+ MediumLockList * &aMediumLockList)
+{
+ MediumLockListMap::Base::iterator it = mMediumLocks.find(aMediumAttachment);
+ if (it == mMediumLocks.end())
+ {
+ aMediumLockList = NULL;
+ return VBOX_E_INVALID_OBJECT_STATE;
+ }
+ aMediumLockList = it->second;
+ return S_OK;
+}
+
+HRESULT MediumLockListMap::Lock()
+{
+ if (mIsLocked)
+ return S_OK;
+ HRESULT rc = S_OK;
+ for (MediumLockListMap::Base::const_iterator it = mMediumLocks.begin();
+ it != mMediumLocks.end();
+ ++it)
+ {
+ rc = it->second->Lock();
+ if (FAILED(rc))
+ {
+ for (MediumLockListMap::Base::const_iterator it2 = mMediumLocks.begin();
+ it2 != it;
+ ++it2)
+ {
+ HRESULT rc2 = it2->second->Unlock();
+ AssertComRC(rc2);
+ }
+ break;
+ }
+ }
+ if (SUCCEEDED(rc))
+ mIsLocked = true;
+ return rc;
+}
+
+HRESULT MediumLockListMap::Unlock()
+{
+ if (!mIsLocked)
+ return S_OK;
+ HRESULT rc = S_OK;
+ for (MediumLockListMap::Base::const_iterator it = mMediumLocks.begin();
+ it != mMediumLocks.end();
+ ++it)
+ {
+ MediumLockList *pMediumLockList = it->second;
+ HRESULT rc2 = pMediumLockList->Unlock();
+ if (SUCCEEDED(rc) && FAILED(rc2))
+ rc = rc2;
+ }
+ mIsLocked = false;
+ return rc;
+}
diff --git a/src/VBox/Main/src-server/NATEngineImpl.cpp b/src/VBox/Main/src-server/NATEngineImpl.cpp
new file mode 100644
index 00000000..20189fbb
--- /dev/null
+++ b/src/VBox/Main/src-server/NATEngineImpl.cpp
@@ -0,0 +1,627 @@
+/* $Id: NATEngineImpl.cpp $ */
+/** @file
+ * Implementation of INATEngine in VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_NATENGINE
+#include "NATEngineImpl.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "MachineImpl.h"
+
+#include <iprt/string.h>
+#include <iprt/cpp/utils.h>
+
+#include <iprt/errcore.h>
+#include <VBox/settings.h>
+#include <VBox/com/array.h>
+
+struct NATEngine::Data
+{
+ Backupable<settings::NAT> m;
+};
+
+
+// constructor / destructor
+////////////////////////////////////////////////////////////////////////////////
+
+NATEngine::NATEngine():mData(NULL), mParent(NULL), mAdapter(NULL) {}
+NATEngine::~NATEngine(){}
+
+HRESULT NATEngine::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void NATEngine::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+
+HRESULT NATEngine::init(Machine *aParent, INetworkAdapter *aAdapter)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+ autoInitSpan.setSucceeded();
+ mData = new Data();
+ mData->m.allocate();
+ mData->m->strNetwork.setNull();
+ mData->m->strBindIP.setNull();
+ unconst(mParent) = aParent;
+ unconst(mAdapter) = aAdapter;
+ return S_OK;
+}
+
+HRESULT NATEngine::init(Machine *aParent, INetworkAdapter *aAdapter, NATEngine *aThat)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+ Log(("init that:%p this:%p\n", aThat, this));
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+
+ mData = new Data();
+ mData->m.share(aThat->mData->m);
+ unconst(mParent) = aParent;
+ unconst(mAdapter) = aAdapter;
+ unconst(mPeer) = aThat;
+ autoInitSpan.setSucceeded();
+ return S_OK;
+}
+
+HRESULT NATEngine::initCopy(Machine *aParent, INetworkAdapter *aAdapter, NATEngine *aThat)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ Log(("initCopy that:%p this:%p\n", aThat, this));
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+
+ mData = new Data();
+ mData->m.attachCopy(aThat->mData->m);
+ unconst(mAdapter) = aAdapter;
+ unconst(mParent) = aParent;
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+
+void NATEngine::uninit()
+{
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ mData->m.free();
+ delete mData;
+ mData = NULL;
+ unconst(mPeer) = NULL;
+ unconst(mParent) = NULL;
+}
+
+bool NATEngine::i_isModified()
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ bool fModified = mData->m.isBackedUp();
+ return fModified;
+}
+
+void NATEngine::i_rollback()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mData->m.rollback();
+}
+
+void NATEngine::i_commit()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(mPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(mPeer, this COMMA_LOCKVAL_SRC_POS);
+ if (mData->m.isBackedUp())
+ {
+ mData->m.commit();
+ if (mPeer)
+ mPeer->mData->m.attach(mData->m);
+ }
+}
+
+void NATEngine::i_copyFrom(NATEngine *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ mData->m.assignCopy(aThat->mData->m);
+}
+
+void NATEngine::i_applyDefaults()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mData->m->fLocalhostReachable = false; /* Applies to new VMs only, see @bugref{9896} */
+}
+
+bool NATEngine::i_hasDefaults()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), true);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return mData->m->areDefaultSettings(mParent->i_getSettingsVersion());
+}
+
+HRESULT NATEngine::getNetworkSettings(ULONG *aMtu, ULONG *aSockSnd, ULONG *aSockRcv, ULONG *aTcpWndSnd, ULONG *aTcpWndRcv)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aMtu)
+ *aMtu = mData->m->u32Mtu;
+ if (aSockSnd)
+ *aSockSnd = mData->m->u32SockSnd;
+ if (aSockRcv)
+ *aSockRcv = mData->m->u32SockRcv;
+ if (aTcpWndSnd)
+ *aTcpWndSnd = mData->m->u32TcpSnd;
+ if (aTcpWndRcv)
+ *aTcpWndRcv = mData->m->u32TcpRcv;
+
+ return S_OK;
+}
+
+HRESULT NATEngine::setNetworkSettings(ULONG aMtu, ULONG aSockSnd, ULONG aSockRcv, ULONG aTcpWndSnd, ULONG aTcpWndRcv)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if ( aMtu || aSockSnd || aSockRcv
+ || aTcpWndSnd || aTcpWndRcv)
+ {
+ mData->m.backup();
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ }
+ if (aMtu)
+ mData->m->u32Mtu = aMtu;
+ if (aSockSnd)
+ mData->m->u32SockSnd = aSockSnd;
+ if (aSockRcv)
+ mData->m->u32SockRcv = aSockSnd;
+ if (aTcpWndSnd)
+ mData->m->u32TcpSnd = aTcpWndSnd;
+ if (aTcpWndRcv)
+ mData->m->u32TcpRcv = aTcpWndRcv;
+
+ return S_OK;
+}
+
+
+HRESULT NATEngine::getRedirects(std::vector<com::Utf8Str> &aRedirects)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aRedirects.resize(mData->m->mapRules.size());
+ size_t i = 0;
+ settings::NATRulesMap::const_iterator it;
+ for (it = mData->m->mapRules.begin(); it != mData->m->mapRules.end(); ++it, ++i)
+ {
+ settings::NATRule r = it->second;
+ aRedirects[i] = Utf8StrFmt("%s,%d,%s,%d,%s,%d",
+ r.strName.c_str(),
+ r.proto,
+ r.strHostIP.c_str(),
+ r.u16HostPort,
+ r.strGuestIP.c_str(),
+ r.u16GuestPort);
+ }
+ return S_OK;
+}
+
+HRESULT NATEngine::addRedirect(const com::Utf8Str &aName, NATProtocol_T aProto, const com::Utf8Str &aHostIP,
+ USHORT aHostPort, const com::Utf8Str &aGuestIP, USHORT aGuestPort)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Utf8Str name = aName;
+ settings::NATRule r;
+ const char *proto;
+ switch (aProto)
+ {
+ case NATProtocol_TCP:
+ proto = "tcp";
+ break;
+ case NATProtocol_UDP:
+ proto = "udp";
+ break;
+ default:
+ return E_INVALIDARG;
+ }
+
+ if (name.isEmpty())
+ name = Utf8StrFmt("%s_%d_%d", proto, aHostPort, aGuestPort);
+ else
+ {
+ const char *s;
+ char c;
+
+ for (s = name.c_str(); (c = *s) != '\0'; ++s)
+ {
+ if (c == ',') /* we use csv in several places e.g. GetRedirects or natpf<N> argument */
+ return setError(E_INVALIDARG,
+ tr("'%c' - invalid character in NAT rule name"), c);
+ }
+ }
+
+ settings::NATRulesMap::iterator it;
+ for (it = mData->m->mapRules.begin(); it != mData->m->mapRules.end(); ++it)
+ {
+ r = it->second;
+ if (it->first == name)
+ return setError(E_INVALIDARG,
+ tr("A NAT rule of this name already exists"));
+ if ( r.strHostIP == aHostIP
+ && r.u16HostPort == aHostPort
+ && r.proto == aProto)
+ return setError(E_INVALIDARG,
+ tr("A NAT rule for this host port and this host IP already exists"));
+ }
+
+ mData->m.backup();
+ r.strName = name.c_str();
+ r.proto = aProto;
+ r.strHostIP = aHostIP;
+ r.u16HostPort = aHostPort;
+ r.strGuestIP = aGuestIP;
+ r.u16GuestPort = aGuestPort;
+ mData->m->mapRules.insert(std::make_pair(name, r));
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+
+ ULONG ulSlot;
+ mAdapter->COMGETTER(Slot)(&ulSlot);
+
+ alock.release();
+ mParent->i_onNATRedirectRuleChanged(ulSlot, FALSE, name, aProto, r.strHostIP, r.u16HostPort, r.strGuestIP, r.u16GuestPort);
+ return S_OK;
+}
+
+HRESULT NATEngine::removeRedirect(const com::Utf8Str &aName)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ settings::NATRulesMap::iterator it = mData->m->mapRules.find(aName);
+ if (it == mData->m->mapRules.end())
+ return E_INVALIDARG;
+ mData->m.backup();
+ /*
+ * NB: "it" may now point to the backup! In that case it's ok to
+ * get data from the backup copy of s.mapRules via it, but we can't
+ * erase(it) from potentially new s.mapRules.
+ */
+ settings::NATRule r = it->second;
+ ULONG ulSlot;
+ mAdapter->COMGETTER(Slot)(&ulSlot);
+
+ mData->m->mapRules.erase(aName); /* NB: erase by key, "it" may not be valid */
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ alock.release();
+ mParent->i_onNATRedirectRuleChanged(ulSlot, TRUE, aName, r.proto, r.strHostIP, r.u16HostPort, r.strGuestIP, r.u16GuestPort);
+ return S_OK;
+}
+
+HRESULT NATEngine::i_loadSettings(const settings::NAT &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData->m.assignCopy(&data);
+ return S_OK;
+}
+
+
+HRESULT NATEngine::i_saveSettings(settings::NAT &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = S_OK;
+ data = *mData->m.data();
+ return rc;
+}
+
+HRESULT NATEngine::setNetwork(const com::Utf8Str &aNetwork)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->m->strNetwork != aNetwork)
+ {
+ mData->m.backup();
+ mData->m->strNetwork = aNetwork;
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ }
+ return S_OK;
+}
+
+
+HRESULT NATEngine::getNetwork(com::Utf8Str &aNetwork)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (!mData->m->strNetwork.isEmpty())
+ {
+ aNetwork = mData->m->strNetwork;
+ Log(("Getter (this:%p) Network: %s\n", this, mData->m->strNetwork.c_str()));
+ }
+ return S_OK;
+}
+
+HRESULT NATEngine::setHostIP(const com::Utf8Str &aHostIP)
+{
+ if (aHostIP.isNotEmpty())
+ {
+ RTNETADDRIPV4 addr;
+
+ /* parses as an IPv4 address */
+ int rc = RTNetStrToIPv4Addr(aHostIP.c_str(), &addr);
+ if (RT_FAILURE(rc))
+ return setError(E_INVALIDARG, "Invalid IPv4 address \"%s\"", aHostIP.c_str());
+
+ /* is a unicast address */
+ if ((addr.u & RT_N2H_U32_C(0xe0000000)) == RT_N2H_U32_C(0xe0000000))
+ return setError(E_INVALIDARG, "Cannot bind to a multicast address %s", aHostIP.c_str());
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->m->strBindIP != aHostIP)
+ {
+ mData->m.backup();
+ mData->m->strBindIP = aHostIP;
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ }
+ return S_OK;
+}
+
+HRESULT NATEngine::getHostIP(com::Utf8Str &aBindIP)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mData->m->strBindIP.isEmpty())
+ aBindIP = mData->m->strBindIP;
+ return S_OK;
+}
+
+HRESULT NATEngine::setLocalhostReachable(BOOL fLocalhostReachable)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->m->fLocalhostReachable != RT_BOOL(fLocalhostReachable))
+ {
+ mData->m.backup();
+ mData->m->fLocalhostReachable = RT_BOOL(fLocalhostReachable);
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ }
+ return S_OK;
+}
+
+HRESULT NATEngine::getLocalhostReachable(BOOL *pfLocalhostReachable)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *pfLocalhostReachable = mData->m->fLocalhostReachable;
+ return S_OK;
+}
+
+HRESULT NATEngine::setTFTPPrefix(const com::Utf8Str &aTFTPPrefix)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->m->strTFTPPrefix != aTFTPPrefix)
+ {
+ mData->m.backup();
+ mData->m->strTFTPPrefix = aTFTPPrefix;
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ }
+ return S_OK;
+}
+
+
+HRESULT NATEngine::getTFTPPrefix(com::Utf8Str &aTFTPPrefix)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mData->m->strTFTPPrefix.isEmpty())
+ {
+ aTFTPPrefix = mData->m->strTFTPPrefix;
+ Log(("Getter (this:%p) TFTPPrefix: %s\n", this, mData->m->strTFTPPrefix.c_str()));
+ }
+ return S_OK;
+}
+
+HRESULT NATEngine::setTFTPBootFile(const com::Utf8Str &aTFTPBootFile)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->m->strTFTPBootFile != aTFTPBootFile)
+ {
+ mData->m.backup();
+ mData->m->strTFTPBootFile = aTFTPBootFile;
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ }
+ return S_OK;
+}
+
+
+HRESULT NATEngine::getTFTPBootFile(com::Utf8Str &aTFTPBootFile)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (!mData->m->strTFTPBootFile.isEmpty())
+ {
+ aTFTPBootFile = mData->m->strTFTPBootFile;
+ Log(("Getter (this:%p) BootFile: %s\n", this, mData->m->strTFTPBootFile.c_str()));
+ }
+ return S_OK;
+}
+
+
+HRESULT NATEngine::setTFTPNextServer(const com::Utf8Str &aTFTPNextServer)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mData->m->strTFTPNextServer != aTFTPNextServer)
+ {
+ mData->m.backup();
+ mData->m->strTFTPNextServer = aTFTPNextServer;
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ }
+ return S_OK;
+}
+
+HRESULT NATEngine::getTFTPNextServer(com::Utf8Str &aTFTPNextServer)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (!mData->m->strTFTPNextServer.isEmpty())
+ {
+ aTFTPNextServer = mData->m->strTFTPNextServer;
+ Log(("Getter (this:%p) NextServer: %s\n", this, mData->m->strTFTPNextServer.c_str()));
+ }
+ return S_OK;
+}
+
+/* DNS */
+HRESULT NATEngine::setDNSPassDomain(BOOL aDNSPassDomain)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->m->fDNSPassDomain != RT_BOOL(aDNSPassDomain))
+ {
+ mData->m.backup();
+ mData->m->fDNSPassDomain = RT_BOOL(aDNSPassDomain);
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ }
+ return S_OK;
+}
+
+HRESULT NATEngine::getDNSPassDomain(BOOL *aDNSPassDomain)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aDNSPassDomain = mData->m->fDNSPassDomain;
+ return S_OK;
+}
+
+
+HRESULT NATEngine::setDNSProxy(BOOL aDNSProxy)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->m->fDNSProxy != RT_BOOL(aDNSProxy))
+ {
+ mData->m.backup();
+ mData->m->fDNSProxy = RT_BOOL(aDNSProxy);
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ }
+ return S_OK;
+}
+
+HRESULT NATEngine::getDNSProxy(BOOL *aDNSProxy)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aDNSProxy = mData->m->fDNSProxy;
+ return S_OK;
+}
+
+
+HRESULT NATEngine::getDNSUseHostResolver(BOOL *aDNSUseHostResolver)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aDNSUseHostResolver = mData->m->fDNSUseHostResolver;
+ return S_OK;
+}
+
+
+HRESULT NATEngine::setDNSUseHostResolver(BOOL aDNSUseHostResolver)
+{
+ if (mData->m->fDNSUseHostResolver != RT_BOOL(aDNSUseHostResolver))
+ {
+ mData->m.backup();
+ mData->m->fDNSUseHostResolver = RT_BOOL(aDNSUseHostResolver);
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ }
+ return S_OK;
+}
+
+HRESULT NATEngine::setAliasMode(ULONG aAliasMode)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ ULONG uAliasMode = (mData->m->fAliasUseSamePorts ? NATAliasMode_AliasUseSamePorts : 0);
+ uAliasMode |= (mData->m->fAliasLog ? NATAliasMode_AliasLog : 0);
+ uAliasMode |= (mData->m->fAliasProxyOnly ? NATAliasMode_AliasProxyOnly : 0);
+ if (uAliasMode != aAliasMode)
+ {
+ mData->m.backup();
+ mData->m->fAliasUseSamePorts = RT_BOOL(aAliasMode & NATAliasMode_AliasUseSamePorts);
+ mData->m->fAliasLog = RT_BOOL(aAliasMode & NATAliasMode_AliasLog);
+ mData->m->fAliasProxyOnly = RT_BOOL(aAliasMode & NATAliasMode_AliasProxyOnly);
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ }
+ return S_OK;
+}
+
+HRESULT NATEngine::getAliasMode(ULONG *aAliasMode)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ ULONG uAliasMode = (mData->m->fAliasUseSamePorts ? NATAliasMode_AliasUseSamePorts : 0);
+ uAliasMode |= (mData->m->fAliasLog ? NATAliasMode_AliasLog : 0);
+ uAliasMode |= (mData->m->fAliasProxyOnly ? NATAliasMode_AliasProxyOnly : 0);
+ *aAliasMode = uAliasMode;
+ return S_OK;
+}
+
diff --git a/src/VBox/Main/src-server/NATNetworkImpl.cpp b/src/VBox/Main/src-server/NATNetworkImpl.cpp
new file mode 100644
index 00000000..52a7fee8
--- /dev/null
+++ b/src/VBox/Main/src-server/NATNetworkImpl.cpp
@@ -0,0 +1,1239 @@
+/* $Id: NATNetworkImpl.cpp $ */
+/** @file
+ * INATNetwork implementation.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_NATNETWORK
+#include "NetworkServiceRunner.h"
+#include "DHCPServerImpl.h"
+#include "NATNetworkImpl.h"
+#include "AutoCaller.h"
+
+#include <iprt/asm.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/net.h>
+#include <iprt/cidr.h>
+#include <iprt/net.h>
+#include <VBox/com/array.h>
+#include <VBox/com/ptr.h>
+#include <VBox/settings.h>
+
+#include "EventImpl.h"
+#include "LoggingNew.h"
+
+#include "VirtualBoxImpl.h"
+#include <algorithm>
+#include <list>
+
+#ifndef RT_OS_WINDOWS
+# include <netinet/in.h>
+#else
+# define IN_LOOPBACKNET 127
+#endif
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+struct NATNetwork::Data
+{
+ Data()
+ : pVirtualBox(NULL)
+ , offGateway(0)
+ , offDhcp(0)
+ {
+ }
+ virtual ~Data(){}
+ const ComObjPtr<EventSource> pEventSource;
+#ifdef VBOX_WITH_NAT_SERVICE
+ NATNetworkServiceRunner NATRunner;
+ ComObjPtr<IDHCPServer> dhcpServer;
+#endif
+ /** weak VirtualBox parent */
+ VirtualBox * const pVirtualBox;
+
+ /** NATNetwork settings */
+ settings::NATNetwork s;
+
+ com::Utf8Str IPv4Gateway;
+ com::Utf8Str IPv4NetworkMask;
+ com::Utf8Str IPv4DhcpServer;
+ com::Utf8Str IPv4DhcpServerLowerIp;
+ com::Utf8Str IPv4DhcpServerUpperIp;
+
+ uint32_t offGateway;
+ uint32_t offDhcp;
+
+ void recalculatePortForwarding(const RTNETADDRIPV4 &AddrNew, const RTNETADDRIPV4 &MaskNew);
+};
+
+
+NATNetwork::NATNetwork()
+ : m(NULL)
+{
+}
+
+
+NATNetwork::~NATNetwork()
+{
+}
+
+
+HRESULT NATNetwork::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+
+void NATNetwork::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+
+void NATNetwork::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+ unconst(m->pVirtualBox) = NULL;
+ delete m;
+ m = NULL;
+}
+
+HRESULT NATNetwork::init(VirtualBox *aVirtualBox, com::Utf8Str aName)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+ /* share VirtualBox weakly */
+ unconst(m->pVirtualBox) = aVirtualBox;
+ m->s.strNetworkName = aName;
+ m->s.strIPv4NetworkCidr = "10.0.2.0/24";
+ m->offGateway = 1;
+ i_recalculateIPv6Prefix(); /* set m->strIPv6Prefix based on IPv4 */
+
+ settings::NATHostLoopbackOffset off;
+ off.strLoopbackHostAddress = "127.0.0.1";
+ off.u32Offset = (uint32_t)2;
+ m->s.llHostLoopbackOffsetList.push_back(off);
+
+ i_recalculateIpv4AddressAssignments();
+
+ HRESULT hrc = unconst(m->pEventSource).createObject();
+ if (FAILED(hrc)) throw hrc;
+
+ hrc = m->pEventSource->init();
+ if (FAILED(hrc)) throw hrc;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+
+HRESULT NATNetwork::setErrorBusy()
+{
+ return setError(E_FAIL,
+ tr("Unable to change settings"
+ " while NATNetwork instance is running"));
+}
+
+
+HRESULT NATNetwork::i_loadSettings(const settings::NATNetwork &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->s = data;
+ if ( m->s.strIPv6Prefix.isEmpty()
+ /* also clean up bogus old default */
+ || m->s.strIPv6Prefix == "fe80::/64")
+ i_recalculateIPv6Prefix(); /* set m->strIPv6Prefix based on IPv4 */
+ i_recalculateIpv4AddressAssignments();
+
+ return S_OK;
+}
+
+HRESULT NATNetwork::i_saveSettings(settings::NATNetwork &data)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(!m->s.strNetworkName.isEmpty(), E_FAIL);
+ data = m->s;
+
+ m->pVirtualBox->i_onNATNetworkSetting(m->s.strNetworkName,
+ m->s.fEnabled,
+ m->s.strIPv4NetworkCidr,
+ m->IPv4Gateway,
+ m->s.fAdvertiseDefaultIPv6Route,
+ m->s.fNeedDhcpServer);
+
+ /* Notify listeners listening on this network only */
+ ::FireNATNetworkSettingEvent(m->pEventSource,
+ m->s.strNetworkName,
+ m->s.fEnabled,
+ m->s.strIPv4NetworkCidr,
+ m->IPv4Gateway,
+ m->s.fAdvertiseDefaultIPv6Route,
+ m->s.fNeedDhcpServer);
+
+ return S_OK;
+}
+
+HRESULT NATNetwork::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ /* event source is const, no need to lock */
+ m->pEventSource.queryInterfaceTo(aEventSource.asOutParam());
+ return S_OK;
+}
+
+HRESULT NATNetwork::getNetworkName(com::Utf8Str &aNetworkName)
+{
+ AssertReturn(!m->s.strNetworkName.isEmpty(), E_FAIL);
+ aNetworkName = m->s.strNetworkName;
+ return S_OK;
+}
+
+HRESULT NATNetwork::setNetworkName(const com::Utf8Str &aNetworkName)
+{
+ if (aNetworkName.isEmpty())
+ return setError(E_INVALIDARG,
+ tr("Network name cannot be empty"));
+
+ {
+ AutoReadLock alockNatNetList(m->pVirtualBox->i_getNatNetLock() COMMA_LOCKVAL_SRC_POS);
+ if (m->pVirtualBox->i_isNatNetStarted(m->s.strNetworkName))
+ return setErrorBusy();
+
+ /** @todo r=uwe who ensures there's no other network with that name? */
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aNetworkName == m->s.strNetworkName)
+ return S_OK;
+
+ m->s.strNetworkName = aNetworkName;
+ }
+
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+
+ return S_OK;
+}
+
+HRESULT NATNetwork::getEnabled(BOOL *aEnabled)
+{
+ *aEnabled = m->s.fEnabled;
+
+ i_recalculateIpv4AddressAssignments();
+ return S_OK;
+}
+
+HRESULT NATNetwork::setEnabled(const BOOL aEnabled)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (RT_BOOL(aEnabled) == m->s.fEnabled)
+ return S_OK;
+ m->s.fEnabled = RT_BOOL(aEnabled);
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ return S_OK;
+}
+
+HRESULT NATNetwork::getGateway(com::Utf8Str &aIPv4Gateway)
+{
+ aIPv4Gateway = m->IPv4Gateway;
+ return S_OK;
+}
+
+HRESULT NATNetwork::getNetwork(com::Utf8Str &aNetwork)
+{
+ aNetwork = m->s.strIPv4NetworkCidr;
+ return S_OK;
+}
+
+
+HRESULT NATNetwork::setNetwork(const com::Utf8Str &aIPv4NetworkCidr)
+{
+ RTNETADDRIPV4 Net, Mask;
+ int iPrefix;
+ int rc;
+
+ rc = RTNetStrToIPv4Cidr(aIPv4NetworkCidr.c_str(), &Net, &iPrefix);
+ if (RT_FAILURE(rc))
+ return setError(E_FAIL, tr("%s is not a valid IPv4 CIDR notation"),
+ aIPv4NetworkCidr.c_str());
+
+ /*
+ * /32 is a single address, not a network, /31 is the degenerate
+ * point-to-point case, so reject these. Larger values and
+ * negative values are already treated as errors by the
+ * conversion.
+ */
+ if (iPrefix > 30)
+ return setError(E_FAIL, tr("%s network is too small"), aIPv4NetworkCidr.c_str());
+
+ if (iPrefix == 0)
+ return setError(E_FAIL, tr("%s specifies zero prefix"), aIPv4NetworkCidr.c_str());
+
+ rc = RTNetPrefixToMaskIPv4(iPrefix, &Mask);
+ AssertRCReturn(rc, setError(E_FAIL,
+ "%s: internal error: failed to convert prefix %d to netmask: %Rrc",
+ aIPv4NetworkCidr.c_str(), iPrefix, rc));
+
+ if ((Net.u & ~Mask.u) != 0)
+ return setError(E_FAIL,
+ tr("%s: the specified address is longer than the specified prefix"),
+ aIPv4NetworkCidr.c_str());
+
+ /** @todo r=uwe Check the address is unicast, not a loopback, etc. */
+
+ /* normalized CIDR notation */
+ com::Utf8StrFmt strCidr("%RTnaipv4/%d", Net.u, iPrefix);
+
+ {
+ AutoReadLock alockNatNetList(m->pVirtualBox->i_getNatNetLock() COMMA_LOCKVAL_SRC_POS);
+ if (m->pVirtualBox->i_isNatNetStarted(m->s.strNetworkName))
+ return setErrorBusy();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->s.strIPv4NetworkCidr == strCidr)
+ return S_OK;
+
+ m->recalculatePortForwarding(Net, Mask);
+
+ m->s.strIPv4NetworkCidr = strCidr;
+ i_recalculateIpv4AddressAssignments();
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(hrc);
+ return S_OK;
+}
+
+
+/**
+ * Do best effort attempt at converting existing port forwarding rules
+ * from the old prefix to the new one. This might not be possible if
+ * the new prefix is longer (i.e. the network is smaller) or if a rule
+ * lists destination not from the network (though that rule wouldn't
+ * be terribly useful, at least currently).
+ */
+void NATNetwork::Data::recalculatePortForwarding(const RTNETADDRIPV4 &NetNew,
+ const RTNETADDRIPV4 &MaskNew)
+{
+ RTNETADDRIPV4 NetOld, MaskOld;
+ int iPrefixOld;
+ int rc;
+
+ if (s.mapPortForwardRules4.empty())
+ return; /* nothing to do */
+
+ rc = RTNetStrToIPv4Cidr(s.strIPv4NetworkCidr.c_str(), &NetOld, &iPrefixOld);
+ if (RT_FAILURE(rc))
+ return;
+
+ rc = RTNetPrefixToMaskIPv4(iPrefixOld, &MaskOld);
+ if (RT_FAILURE(rc))
+ return;
+
+ for (settings::NATRulesMap::iterator it = s.mapPortForwardRules4.begin();
+ it != s.mapPortForwardRules4.end();
+ ++it)
+ {
+ settings::NATRule &rule = it->second;
+
+ /* parse the old destination address */
+ RTNETADDRIPV4 AddrOld;
+ rc = RTNetStrToIPv4Addr(rule.strGuestIP.c_str(), &AddrOld);
+ if (RT_FAILURE(rc))
+ continue;
+
+ /* is it in the old network? (likely) */
+ if ((AddrOld.u & MaskOld.u) != NetOld.u)
+ continue;
+
+ uint32_t u32Host = (AddrOld.u & ~MaskOld.u);
+
+ /* does it fit into the new network? */
+ if ((u32Host & MaskNew.u) != 0)
+ continue;
+
+ rule.strGuestIP.printf("%RTnaipv4", NetNew.u | u32Host);
+ }
+}
+
+
+HRESULT NATNetwork::getIPv6Enabled(BOOL *aIPv6Enabled)
+{
+ *aIPv6Enabled = m->s.fIPv6Enabled;
+
+ return S_OK;
+}
+
+
+HRESULT NATNetwork::setIPv6Enabled(const BOOL aIPv6Enabled)
+{
+ {
+ AutoReadLock alockNatNetList(m->pVirtualBox->i_getNatNetLock() COMMA_LOCKVAL_SRC_POS);
+ if (m->pVirtualBox->i_isNatNetStarted(m->s.strNetworkName))
+ return setErrorBusy();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (RT_BOOL(aIPv6Enabled) == m->s.fIPv6Enabled)
+ return S_OK;
+
+ /*
+ * If we are enabling ipv6 and the prefix is not set, provide
+ * the default based on ipv4.
+ */
+ if (aIPv6Enabled && m->s.strIPv6Prefix.isEmpty())
+ i_recalculateIPv6Prefix();
+
+ m->s.fIPv6Enabled = RT_BOOL(aIPv6Enabled);
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+
+ return S_OK;
+}
+
+
+HRESULT NATNetwork::getIPv6Prefix(com::Utf8Str &aIPv6Prefix)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aIPv6Prefix = m->s.strIPv6Prefix;
+ return S_OK;
+}
+
+HRESULT NATNetwork::setIPv6Prefix(const com::Utf8Str &aIPv6Prefix)
+{
+ HRESULT hrc;
+ int rc;
+
+ /* Since we store it in text form, use canonical representation */
+ com::Utf8Str strNormalizedIPv6Prefix;
+
+ const char *pcsz = RTStrStripL(aIPv6Prefix.c_str());
+ if (*pcsz != '\0') /* verify it first if not empty/blank */
+ {
+ RTNETADDRIPV6 Net6;
+ int iPrefixLength;
+ rc = RTNetStrToIPv6Cidr(aIPv6Prefix.c_str(), &Net6, &iPrefixLength);
+ if (RT_FAILURE(rc))
+ return setError(E_INVALIDARG,
+ tr("%s is not a valid IPv6 prefix"),
+ aIPv6Prefix.c_str());
+
+ /* Accept both addr:: and addr::/64 */
+ if (iPrefixLength == 128) /* no length was specified after the address? */
+ iPrefixLength = 64; /* take it to mean /64 which we require anyway */
+ else if (iPrefixLength != 64)
+ return setError(E_INVALIDARG,
+ tr("Invalid IPv6 prefix length %d, must be 64"),
+ iPrefixLength);
+
+ /* Verify the address is unicast. */
+ if ( ((Net6.au8[0] & 0xe0) != 0x20) /* global 2000::/3 */
+ && ((Net6.au8[0] & 0xfe) != 0xfc)) /* local fc00::/7 */
+ return setError(E_INVALIDARG,
+ tr("IPv6 prefix %RTnaipv6 is not unicast"),
+ &Net6);
+
+ /* Verify the interfaces ID part is zero */
+ if (Net6.au64[1] != 0)
+ return setError(E_INVALIDARG,
+ tr("Non-zero bits in the interface ID part"
+ " of the IPv6 prefix %RTnaipv6/64"),
+ &Net6);
+
+ rc = strNormalizedIPv6Prefix.printfNoThrow("%RTnaipv6/64", &Net6);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MEMORY)
+ return setError(E_OUTOFMEMORY);
+ else
+ return setError(E_FAIL, tr("Internal error"));
+ }
+ }
+
+ {
+ AutoReadLock alockNatNetList(m->pVirtualBox->i_getNatNetLock() COMMA_LOCKVAL_SRC_POS);
+ if (m->pVirtualBox->i_isNatNetStarted(m->s.strNetworkName))
+ return setErrorBusy();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (strNormalizedIPv6Prefix == m->s.strIPv6Prefix)
+ return S_OK;
+
+ /* only allow prefix to be empty if IPv6 is disabled */
+ if (strNormalizedIPv6Prefix.isEmpty() && m->s.fIPv6Enabled)
+ return setError(E_FAIL, tr("Setting an empty IPv6 prefix when IPv6 is enabled"));
+
+ /**
+ * @todo
+ * silently ignore network IPv6 prefix update.
+ * todo: see similar todo in NATNetwork::COMSETTER(Network)(IN_BSTR)
+ */
+ if (!m->s.mapPortForwardRules6.empty())
+ return S_OK;
+
+ m->s.strIPv6Prefix = strNormalizedIPv6Prefix;
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ hrc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(hrc);
+
+ return S_OK;
+}
+
+
+HRESULT NATNetwork::getAdvertiseDefaultIPv6RouteEnabled(BOOL *aAdvertiseDefaultIPv6Route)
+{
+ *aAdvertiseDefaultIPv6Route = m->s.fAdvertiseDefaultIPv6Route;
+
+ return S_OK;
+}
+
+
+HRESULT NATNetwork::setAdvertiseDefaultIPv6RouteEnabled(const BOOL aAdvertiseDefaultIPv6Route)
+{
+ {
+ AutoReadLock alockNatNetList(m->pVirtualBox->i_getNatNetLock() COMMA_LOCKVAL_SRC_POS);
+ if (m->pVirtualBox->i_isNatNetStarted(m->s.strNetworkName))
+ return setErrorBusy();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (RT_BOOL(aAdvertiseDefaultIPv6Route) == m->s.fAdvertiseDefaultIPv6Route)
+ return S_OK;
+
+ m->s.fAdvertiseDefaultIPv6Route = RT_BOOL(aAdvertiseDefaultIPv6Route);
+
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+
+ return S_OK;
+}
+
+
+HRESULT NATNetwork::getNeedDhcpServer(BOOL *aNeedDhcpServer)
+{
+ *aNeedDhcpServer = m->s.fNeedDhcpServer;
+
+ return S_OK;
+}
+
+HRESULT NATNetwork::setNeedDhcpServer(const BOOL aNeedDhcpServer)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (RT_BOOL(aNeedDhcpServer) == m->s.fNeedDhcpServer)
+ return S_OK;
+
+ m->s.fNeedDhcpServer = RT_BOOL(aNeedDhcpServer);
+
+ i_recalculateIpv4AddressAssignments();
+
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+
+ return S_OK;
+}
+
+HRESULT NATNetwork::getLocalMappings(std::vector<com::Utf8Str> &aLocalMappings)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aLocalMappings.resize(m->s.llHostLoopbackOffsetList.size());
+ size_t i = 0;
+ for (settings::NATLoopbackOffsetList::const_iterator it = m->s.llHostLoopbackOffsetList.begin();
+ it != m->s.llHostLoopbackOffsetList.end(); ++it, ++i)
+ {
+ aLocalMappings[i] = Utf8StrFmt("%s=%d",
+ (*it).strLoopbackHostAddress.c_str(),
+ (*it).u32Offset);
+ }
+
+ return S_OK;
+}
+
+HRESULT NATNetwork::addLocalMapping(const com::Utf8Str &aHostId, LONG aOffset)
+{
+ RTNETADDRIPV4 addr, net, mask;
+
+ int rc = RTNetStrToIPv4Addr(Utf8Str(aHostId).c_str(), &addr);
+ if (RT_FAILURE(rc))
+ return E_INVALIDARG;
+
+ /* check against 127/8 */
+ if ((RT_N2H_U32(addr.u) >> IN_CLASSA_NSHIFT) != IN_LOOPBACKNET)
+ return E_INVALIDARG;
+
+ /* check against networkid vs network mask */
+ rc = RTCidrStrToIPv4(Utf8Str(m->s.strIPv4NetworkCidr).c_str(), &net, &mask);
+ if (RT_FAILURE(rc))
+ return E_INVALIDARG;
+
+ if (((net.u + (uint32_t)aOffset) & mask.u) != net.u)
+ return E_INVALIDARG;
+
+ settings::NATLoopbackOffsetList::iterator it;
+
+ it = std::find(m->s.llHostLoopbackOffsetList.begin(),
+ m->s.llHostLoopbackOffsetList.end(),
+ aHostId);
+ if (it != m->s.llHostLoopbackOffsetList.end())
+ {
+ if (aOffset == 0) /* erase */
+ m->s.llHostLoopbackOffsetList.erase(it, it);
+ else /* modify */
+ {
+ settings::NATLoopbackOffsetList::iterator it1;
+ it1 = std::find(m->s.llHostLoopbackOffsetList.begin(),
+ m->s.llHostLoopbackOffsetList.end(),
+ (uint32_t)aOffset);
+ if (it1 != m->s.llHostLoopbackOffsetList.end())
+ return E_INVALIDARG; /* this offset is already registered. */
+
+ (*it).u32Offset = (uint32_t)aOffset;
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ return m->pVirtualBox->i_saveSettings();
+ }
+
+ /* injection */
+ it = std::find(m->s.llHostLoopbackOffsetList.begin(),
+ m->s.llHostLoopbackOffsetList.end(),
+ (uint32_t)aOffset);
+
+ if (it != m->s.llHostLoopbackOffsetList.end())
+ return E_INVALIDARG; /* offset is already registered. */
+
+ settings::NATHostLoopbackOffset off;
+ off.strLoopbackHostAddress = aHostId;
+ off.u32Offset = (uint32_t)aOffset;
+ m->s.llHostLoopbackOffsetList.push_back(off);
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ return m->pVirtualBox->i_saveSettings();
+}
+
+
+HRESULT NATNetwork::getLoopbackIp6(LONG *aLoopbackIp6)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aLoopbackIp6 = (LONG)m->s.u32HostLoopback6Offset;
+ return S_OK;
+}
+
+
+HRESULT NATNetwork::setLoopbackIp6(LONG aLoopbackIp6)
+{
+ {
+ AutoReadLock alockNatNetList(m->pVirtualBox->i_getNatNetLock() COMMA_LOCKVAL_SRC_POS);
+ if (m->pVirtualBox->i_isNatNetStarted(m->s.strNetworkName))
+ return setErrorBusy();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aLoopbackIp6 < 0)
+ return E_INVALIDARG;
+
+ if (static_cast<uint32_t>(aLoopbackIp6) == m->s.u32HostLoopback6Offset)
+ return S_OK;
+
+ m->s.u32HostLoopback6Offset = (uint32_t)aLoopbackIp6;
+ }
+
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ return m->pVirtualBox->i_saveSettings();
+}
+
+
+HRESULT NATNetwork::getPortForwardRules4(std::vector<com::Utf8Str> &aPortForwardRules4)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ i_getPortForwardRulesFromMap(aPortForwardRules4,
+ m->s.mapPortForwardRules4);
+ return S_OK;
+}
+
+HRESULT NATNetwork::getPortForwardRules6(std::vector<com::Utf8Str> &aPortForwardRules6)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ i_getPortForwardRulesFromMap(aPortForwardRules6,
+ m->s.mapPortForwardRules6);
+ return S_OK;
+}
+
+HRESULT NATNetwork::addPortForwardRule(BOOL aIsIpv6,
+ const com::Utf8Str &aPortForwardRuleName,
+ NATProtocol_T aProto,
+ const com::Utf8Str &aHostIp,
+ USHORT aHostPort,
+ const com::Utf8Str &aGuestIp,
+ USHORT aGuestPort)
+{
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Utf8Str name = aPortForwardRuleName;
+ Utf8Str proto;
+ settings::NATRule r;
+ settings::NATRulesMap &mapRules = aIsIpv6 ? m->s.mapPortForwardRules6 : m->s.mapPortForwardRules4;
+ switch (aProto)
+ {
+ case NATProtocol_TCP:
+ proto = "tcp";
+ break;
+ case NATProtocol_UDP:
+ proto = "udp";
+ break;
+ default:
+ return E_INVALIDARG;
+ }
+ if (name.isEmpty())
+ name = Utf8StrFmt("%s_[%s]%%%d_[%s]%%%d", proto.c_str(),
+ aHostIp.c_str(), aHostPort,
+ aGuestIp.c_str(), aGuestPort);
+
+ for (settings::NATRulesMap::iterator it = mapRules.begin(); it != mapRules.end(); ++it)
+ {
+ r = it->second;
+ if (it->first == name)
+ return setError(E_INVALIDARG,
+ tr("A NAT rule of this name already exists"));
+ if ( r.strHostIP == aHostIp
+ && r.u16HostPort == aHostPort
+ && r.proto == aProto)
+ return setError(E_INVALIDARG,
+ tr("A NAT rule for this host port and this host IP already exists"));
+ }
+
+ r.strName = name.c_str();
+ r.proto = aProto;
+ r.strHostIP = aHostIp;
+ r.u16HostPort = aHostPort;
+ r.strGuestIP = aGuestIp;
+ r.u16GuestPort = aGuestPort;
+ mapRules.insert(std::make_pair(name, r));
+ }
+ {
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ }
+
+ m->pVirtualBox->i_onNATNetworkPortForward(m->s.strNetworkName, TRUE, aIsIpv6,
+ aPortForwardRuleName, aProto,
+ aHostIp, aHostPort,
+ aGuestIp, aGuestPort);
+
+ /* Notify listeners listening on this network only */
+ ::FireNATNetworkPortForwardEvent(m->pEventSource, m->s.strNetworkName, TRUE,
+ aIsIpv6, aPortForwardRuleName, aProto,
+ aHostIp, aHostPort,
+ aGuestIp, aGuestPort);
+
+ return S_OK;
+}
+
+HRESULT NATNetwork::removePortForwardRule(BOOL aIsIpv6, const com::Utf8Str &aPortForwardRuleName)
+{
+ Utf8Str strHostIP;
+ Utf8Str strGuestIP;
+ uint16_t u16HostPort;
+ uint16_t u16GuestPort;
+ NATProtocol_T proto;
+
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ settings::NATRulesMap &mapRules = aIsIpv6 ? m->s.mapPortForwardRules6 : m->s.mapPortForwardRules4;
+ settings::NATRulesMap::iterator it = mapRules.find(aPortForwardRuleName);
+
+ if (it == mapRules.end())
+ return E_INVALIDARG;
+
+ strHostIP = it->second.strHostIP;
+ strGuestIP = it->second.strGuestIP;
+ u16HostPort = it->second.u16HostPort;
+ u16GuestPort = it->second.u16GuestPort;
+ proto = it->second.proto;
+
+ mapRules.erase(it);
+ }
+
+ {
+ AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pVirtualBox->i_saveSettings();
+ ComAssertComRCRetRC(rc);
+ }
+
+ m->pVirtualBox->i_onNATNetworkPortForward(m->s.strNetworkName, FALSE, aIsIpv6, aPortForwardRuleName, proto,
+ strHostIP, u16HostPort, strGuestIP, u16GuestPort);
+
+ /* Notify listeners listening on this network only */
+ ::FireNATNetworkPortForwardEvent(m->pEventSource, m->s.strNetworkName, FALSE, aIsIpv6, aPortForwardRuleName, proto,
+ strHostIP, u16HostPort, strGuestIP, u16GuestPort);
+ return S_OK;
+}
+
+
+void NATNetwork::i_updateDomainNameOption(ComPtr<IHost> &host)
+{
+ com::Bstr domain;
+ if (FAILED(host->COMGETTER(DomainName)(domain.asOutParam())))
+ LogRel(("NATNetwork: Failed to get host's domain name\n"));
+ ComPtr<IDHCPGlobalConfig> pDHCPConfig;
+ HRESULT hrc = m->dhcpServer->COMGETTER(GlobalConfig)(pDHCPConfig.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NATNetwork: Failed to get global DHCP config when updating domain name option with %Rhrc\n", hrc));
+ return;
+ }
+ if (domain.isNotEmpty())
+ {
+ hrc = pDHCPConfig->SetOption(DHCPOption_DomainName, DHCPOptionEncoding_Normal, domain.raw());
+ if (FAILED(hrc))
+ LogRel(("NATNetwork: Failed to add domain name option with %Rhrc\n", hrc));
+ }
+ else
+ pDHCPConfig->RemoveOption(DHCPOption_DomainName);
+}
+
+void NATNetwork::i_updateDomainNameServerOption(ComPtr<IHost> &host)
+{
+ RTNETADDRIPV4 networkid, netmask;
+
+ int rc = RTCidrStrToIPv4(m->s.strIPv4NetworkCidr.c_str(), &networkid, &netmask);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("NATNetwork: Failed to parse cidr %s with %Rrc\n", m->s.strIPv4NetworkCidr.c_str(), rc));
+ return;
+ }
+
+ /* XXX: these are returned, surprisingly, in host order */
+ networkid.u = RT_H2N_U32(networkid.u);
+ netmask.u = RT_H2N_U32(netmask.u);
+
+ com::SafeArray<BSTR> nameServers;
+ HRESULT hrc = host->COMGETTER(NameServers)(ComSafeArrayAsOutParam(nameServers));
+ if (FAILED(hrc))
+ {
+ LogRel(("NATNetwork: Failed to get name servers from host with %Rhrc\n", hrc));
+ return;
+ }
+ ComPtr<IDHCPGlobalConfig> pDHCPConfig;
+ hrc = m->dhcpServer->COMGETTER(GlobalConfig)(pDHCPConfig.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NATNetwork: Failed to get global DHCP config when updating domain name server option with %Rhrc\n", hrc));
+ return;
+ }
+
+ size_t cAddresses = nameServers.size();
+ if (cAddresses)
+ {
+ RTCList<RTCString> lstServers;
+ /* The following code was copied (and adapted a bit) from VBoxNetDhcp::hostDnsServers */
+ /*
+ * Recent fashion is to run dnsmasq on 127.0.1.1 which we
+ * currently can't map. If that's the only nameserver we've got,
+ * we need to use DNS proxy for VMs to reach it.
+ */
+ bool fUnmappedLoopback = false;
+
+ for (size_t i = 0; i < cAddresses; ++i)
+ {
+ RTNETADDRIPV4 addr;
+
+ com::Utf8Str strNameServerAddress(nameServers[i]);
+ rc = RTNetStrToIPv4Addr(strNameServerAddress.c_str(), &addr);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("NATNetwork: Failed to parse IP address %s with %Rrc\n", strNameServerAddress.c_str(), rc));
+ continue;
+ }
+
+ if (addr.u == INADDR_ANY)
+ {
+ /*
+ * This doesn't seem to be very well documented except for
+ * RTFS of res_init.c, but INADDR_ANY is a valid value for
+ * for "nameserver".
+ */
+ addr.u = RT_H2N_U32_C(INADDR_LOOPBACK);
+ }
+
+ if (addr.au8[0] == 127)
+ {
+ settings::NATLoopbackOffsetList::const_iterator it;
+
+ it = std::find(m->s.llHostLoopbackOffsetList.begin(),
+ m->s.llHostLoopbackOffsetList.end(),
+ strNameServerAddress);
+ if (it == m->s.llHostLoopbackOffsetList.end())
+ {
+ fUnmappedLoopback = true;
+ continue;
+ }
+ addr.u = RT_H2N_U32(RT_N2H_U32(networkid.u) + it->u32Offset);
+ }
+ lstServers.append(RTCStringFmt("%RTnaipv4", addr));
+ }
+
+ if (lstServers.isEmpty() && fUnmappedLoopback)
+ lstServers.append(RTCStringFmt("%RTnaipv4", networkid.u | RT_H2N_U32_C(1U))); /* proxy */
+
+ hrc = pDHCPConfig->SetOption(DHCPOption_DomainNameServers, DHCPOptionEncoding_Normal, Bstr(RTCString::join(lstServers, " ")).raw());
+ if (FAILED(hrc))
+ LogRel(("NATNetwork: Failed to add domain name server option '%s' with %Rhrc\n", RTCString::join(lstServers, " ").c_str(), hrc));
+ }
+ else
+ pDHCPConfig->RemoveOption(DHCPOption_DomainNameServers);
+}
+
+void NATNetwork::i_updateDnsOptions()
+{
+ ComPtr<IHost> host;
+ if (SUCCEEDED(m->pVirtualBox->COMGETTER(Host)(host.asOutParam())))
+ {
+ i_updateDomainNameOption(host);
+ i_updateDomainNameServerOption(host);
+ }
+}
+
+
+HRESULT NATNetwork::start()
+{
+#ifdef VBOX_WITH_NAT_SERVICE
+ if (!m->s.fEnabled) return S_OK;
+ AssertReturn(!m->s.strNetworkName.isEmpty(), E_FAIL);
+
+ m->NATRunner.resetArguments();
+ m->NATRunner.addArgPair(NetworkServiceRunner::kpszKeyNetwork, Utf8Str(m->s.strNetworkName).c_str());
+
+ /* No portforwarding rules from command-line, all will be fetched via API */
+
+ if (m->s.fNeedDhcpServer)
+ {
+ /*
+ * Just to as idea... via API (on creation user pass the cidr of network and)
+ * and we calculate it's addreses (mutable?).
+ */
+
+ /*
+ * Configuration and running DHCP server:
+ * 1. find server first createDHCPServer
+ * 2. if return status is E_INVALARG => server already exists just find and start.
+ * 3. if return status neither E_INVALRG nor S_OK => return E_FAIL
+ * 4. if return status S_OK proceed to DHCP server configuration
+ * 5. call setConfiguration() and pass all required parameters
+ * 6. start dhcp server.
+ */
+ HRESULT hrc = m->pVirtualBox->FindDHCPServerByNetworkName(Bstr(m->s.strNetworkName).raw(),
+ m->dhcpServer.asOutParam());
+ switch (hrc)
+ {
+ case E_INVALIDARG:
+ /* server haven't beeen found let create it then */
+ hrc = m->pVirtualBox->CreateDHCPServer(Bstr(m->s.strNetworkName).raw(),
+ m->dhcpServer.asOutParam());
+ if (FAILED(hrc))
+ return E_FAIL;
+ /* breakthrough */
+
+ {
+ LogFunc(("gateway: %s, dhcpserver:%s, dhcplowerip:%s, dhcpupperip:%s\n",
+ m->IPv4Gateway.c_str(),
+ m->IPv4DhcpServer.c_str(),
+ m->IPv4DhcpServerLowerIp.c_str(),
+ m->IPv4DhcpServerUpperIp.c_str()));
+
+ hrc = m->dhcpServer->COMSETTER(Enabled)(true);
+
+ hrc = m->dhcpServer->SetConfiguration(Bstr(m->IPv4DhcpServer).raw(),
+ Bstr(m->IPv4NetworkMask).raw(),
+ Bstr(m->IPv4DhcpServerLowerIp).raw(),
+ Bstr(m->IPv4DhcpServerUpperIp).raw());
+ }
+ case S_OK:
+ break;
+
+ default:
+ return E_FAIL;
+ }
+
+#ifdef VBOX_WITH_DHCPD
+ i_updateDnsOptions();
+#endif /* VBOX_WITH_DHCPD */
+ /* XXX: AddGlobalOption(DhcpOpt_Router,) - enables attachement of DhcpServer to Main (no longer true with VBoxNetDhcpd). */
+ ComPtr<IDHCPGlobalConfig> pDHCPConfig;
+ hrc = m->dhcpServer->COMGETTER(GlobalConfig)(pDHCPConfig.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NATNetwork: Failed to get global DHCP config when updating IPv4 gateway option with %Rhrc\n", hrc));
+ m->dhcpServer.setNull();
+ return E_FAIL;
+ }
+ pDHCPConfig->SetOption(DHCPOption_Routers, DHCPOptionEncoding_Normal, Bstr(m->IPv4Gateway).raw());
+
+ hrc = m->dhcpServer->Start(Bstr::Empty.raw(), Bstr(TRUNKTYPE_WHATEVER).raw());
+ if (FAILED(hrc))
+ {
+ m->dhcpServer.setNull();
+ return E_FAIL;
+ }
+ }
+
+ if (RT_SUCCESS(m->NATRunner.start(false /* KillProcOnStop */)))
+ {
+ m->pVirtualBox->i_onNATNetworkStartStop(m->s.strNetworkName, TRUE);
+ return S_OK;
+ }
+ /** @todo missing setError()! */
+ return E_FAIL;
+#else
+ ReturnComNotImplemented();
+#endif
+}
+
+HRESULT NATNetwork::stop()
+{
+#ifdef VBOX_WITH_NAT_SERVICE
+ m->pVirtualBox->i_onNATNetworkStartStop(m->s.strNetworkName, FALSE);
+
+ if (!m->dhcpServer.isNull())
+ m->dhcpServer->Stop();
+
+ if (RT_SUCCESS(m->NATRunner.stop()))
+ return S_OK;
+
+ /** @todo missing setError()! */
+ return E_FAIL;
+#else
+ ReturnComNotImplemented();
+#endif
+}
+
+
+void NATNetwork::i_getPortForwardRulesFromMap(std::vector<com::Utf8Str> &aPortForwardRules, settings::NATRulesMap &aRules)
+{
+ aPortForwardRules.resize(aRules.size());
+ size_t i = 0;
+ for (settings::NATRulesMap::const_iterator it = aRules.begin();
+ it != aRules.end(); ++it, ++i)
+ {
+ settings::NATRule r = it->second;
+ aPortForwardRules[i] = Utf8StrFmt("%s:%s:[%s]:%d:[%s]:%d",
+ r.strName.c_str(),
+ (r.proto == NATProtocol_TCP ? "tcp" : "udp"),
+ r.strHostIP.c_str(),
+ r.u16HostPort,
+ r.strGuestIP.c_str(),
+ r.u16GuestPort);
+ }
+}
+
+
+int NATNetwork::i_findFirstAvailableOffset(ADDRESSLOOKUPTYPE addrType, uint32_t *poff)
+{
+ RTNETADDRIPV4 network, netmask;
+
+ int rc = RTCidrStrToIPv4(m->s.strIPv4NetworkCidr.c_str(),
+ &network,
+ &netmask);
+ AssertRCReturn(rc, rc);
+
+ uint32_t off;
+ for (off = 1; off < ~netmask.u; ++off)
+ {
+ bool skip = false;
+ for (settings::NATLoopbackOffsetList::iterator it = m->s.llHostLoopbackOffsetList.begin();
+ it != m->s.llHostLoopbackOffsetList.end();
+ ++it)
+ {
+ if ((*it).u32Offset == off)
+ {
+ skip = true;
+ break;
+ }
+
+ }
+
+ if (skip)
+ continue;
+
+ if (off == m->offGateway)
+ {
+ if (addrType == ADDR_GATEWAY)
+ break;
+ else
+ continue;
+ }
+
+ if (off == m->offDhcp)
+ {
+ if (addrType == ADDR_DHCP)
+ break;
+ else
+ continue;
+ }
+
+ if (!skip)
+ break;
+ }
+
+ if (poff)
+ *poff = off;
+
+ return VINF_SUCCESS;
+}
+
+int NATNetwork::i_recalculateIpv4AddressAssignments()
+{
+ RTNETADDRIPV4 network, netmask;
+ int rc = RTCidrStrToIPv4(m->s.strIPv4NetworkCidr.c_str(),
+ &network,
+ &netmask);
+ AssertRCReturn(rc, rc);
+
+ i_findFirstAvailableOffset(ADDR_GATEWAY, &m->offGateway);
+ if (m->s.fNeedDhcpServer)
+ i_findFirstAvailableOffset(ADDR_DHCP, &m->offDhcp);
+
+ /* I don't remember the reason CIDR calculated on the host. */
+ RTNETADDRIPV4 gateway = network;
+ gateway.u += m->offGateway;
+ gateway.u = RT_H2N_U32(gateway.u);
+ char szTmpIp[16];
+ RTStrPrintf(szTmpIp, sizeof(szTmpIp), "%RTnaipv4", gateway);
+ m->IPv4Gateway = szTmpIp;
+
+ if (m->s.fNeedDhcpServer)
+ {
+ RTNETADDRIPV4 dhcpserver = network;
+ dhcpserver.u += m->offDhcp;
+
+ /* XXX: adding more services should change the math here */
+ RTNETADDRIPV4 dhcplowerip = network;
+ uint32_t offDhcpLowerIp;
+ i_findFirstAvailableOffset(ADDR_DHCPLOWERIP, &offDhcpLowerIp);
+ dhcplowerip.u = RT_H2N_U32(dhcplowerip.u + offDhcpLowerIp);
+
+ RTNETADDRIPV4 dhcpupperip;
+ dhcpupperip.u = RT_H2N_U32((network.u | ~netmask.u) - 1);
+
+ dhcpserver.u = RT_H2N_U32(dhcpserver.u);
+ network.u = RT_H2N_U32(network.u);
+
+ RTStrPrintf(szTmpIp, sizeof(szTmpIp), "%RTnaipv4", dhcpserver);
+ m->IPv4DhcpServer = szTmpIp;
+ RTStrPrintf(szTmpIp, sizeof(szTmpIp), "%RTnaipv4", dhcplowerip);
+ m->IPv4DhcpServerLowerIp = szTmpIp;
+ RTStrPrintf(szTmpIp, sizeof(szTmpIp), "%RTnaipv4", dhcpupperip);
+ m->IPv4DhcpServerUpperIp = szTmpIp;
+
+ LogFunc(("network:%RTnaipv4, dhcpserver:%RTnaipv4, dhcplowerip:%RTnaipv4, dhcpupperip:%RTnaipv4\n",
+ network, dhcpserver, dhcplowerip, dhcpupperip));
+ }
+
+ /* we need IPv4NetworkMask for NAT's gw service start */
+ netmask.u = RT_H2N_U32(netmask.u);
+ RTStrPrintf(szTmpIp, 16, "%RTnaipv4", netmask);
+ m->IPv4NetworkMask = szTmpIp;
+
+ LogFlowFunc(("getaway:%RTnaipv4, netmask:%RTnaipv4\n", gateway, netmask));
+ return VINF_SUCCESS;
+}
+
+
+int NATNetwork::i_recalculateIPv6Prefix()
+{
+ int rc;
+
+ RTNETADDRIPV4 net, mask;
+ rc = RTCidrStrToIPv4(Utf8Str(m->s.strIPv4NetworkCidr).c_str(), &net, &mask);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ net.u = RT_H2N_U32(net.u); /* XXX: fix RTCidrStrToIPv4! */
+
+ /*
+ * [fd17:625c:f037:XXXX::/64] - RFC 4193 (ULA) Locally Assigned
+ * Global ID where XXXX, 16 bit Subnet ID, are two bytes from the
+ * middle of the IPv4 address, e.g. :dead: for 10.222.173.1
+ */
+ RTNETADDRIPV6 prefix;
+ RT_ZERO(prefix);
+
+ prefix.au8[0] = 0xFD;
+ prefix.au8[1] = 0x17;
+
+ prefix.au8[2] = 0x62;
+ prefix.au8[3] = 0x5C;
+
+ prefix.au8[4] = 0xF0;
+ prefix.au8[5] = 0x37;
+
+ prefix.au8[6] = net.au8[1];
+ prefix.au8[7] = net.au8[2];
+
+ char szBuf[32];
+ RTStrPrintf(szBuf, sizeof(szBuf), "%RTnaipv6/64", &prefix);
+
+ m->s.strIPv6Prefix = szBuf;
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/Main/src-server/NetworkAdapterImpl.cpp b/src/VBox/Main/src-server/NetworkAdapterImpl.cpp
new file mode 100644
index 00000000..28ef3b1a
--- /dev/null
+++ b/src/VBox/Main/src-server/NetworkAdapterImpl.cpp
@@ -0,0 +1,1616 @@
+/* $Id: NetworkAdapterImpl.cpp $ */
+/** @file
+ * Implementation of INetworkAdapter in VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_NETWORKADAPTER
+#include "NetworkAdapterImpl.h"
+#include "NATEngineImpl.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "MachineImpl.h"
+#include "GuestOSTypeImpl.h"
+#include "HostImpl.h"
+#include "SystemPropertiesImpl.h"
+#include "VirtualBoxImpl.h"
+
+#include <iprt/ctype.h>
+#include <iprt/string.h>
+#include <iprt/cpp/utils.h>
+
+#include <iprt/errcore.h>
+#include <VBox/settings.h>
+
+#include "AutoStateDep.h"
+
+// constructor / destructor
+////////////////////////////////////////////////////////////////////////////////
+
+NetworkAdapter::NetworkAdapter()
+ : mParent(NULL)
+{
+}
+
+NetworkAdapter::~NetworkAdapter()
+{
+}
+
+HRESULT NetworkAdapter::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void NetworkAdapter::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the network adapter object.
+ *
+ * @param aParent Handle of the parent object.
+ * @param uSlot Slot number this network adapter is plugged into.
+ */
+HRESULT NetworkAdapter::init(Machine *aParent, ULONG uSlot)
+{
+ LogFlowThisFunc(("aParent=%p, uSlot=%d\n", aParent, uSlot));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+ uint32_t maxNetworkAdapters = Global::getMaxNetworkAdapters(aParent->i_getChipsetType());
+ ComAssertRet(uSlot < maxNetworkAdapters, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+ unconst(mNATEngine).createObject();
+ mNATEngine->init(aParent, this);
+ /* mPeer is left null */
+
+ mData.allocate();
+
+ /* initialize data */
+ mData->ulSlot = uSlot;
+
+ /* default to Am79C973 */
+ mData->type = NetworkAdapterType_Am79C973;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the network adapter object given another network adapter object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @param aParent Parent object.
+ * @param aThat
+ * @param aReshare
+ * When false, the original object will remain a data owner.
+ * Otherwise, data ownership will be transferred from the original
+ * object to this one.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT NetworkAdapter::init(Machine *aParent, NetworkAdapter *aThat, bool aReshare /* = false */)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p, aReshare=%RTbool\n", aParent, aThat, aReshare));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+ /* mPeer is left null */
+
+ unconst(mNATEngine).createObject();
+ mNATEngine->init(aParent, this, aThat->mNATEngine);
+
+ /* sanity */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ if (aReshare)
+ {
+ AutoWriteLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+
+ unconst(aThat->mPeer) = this;
+ mData.attach(aThat->mData);
+ }
+ else
+ {
+ unconst(mPeer) = aThat;
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ mData.share(aThat->mData);
+ }
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the guest object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT NetworkAdapter::initCopy(Machine *aParent, NetworkAdapter *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+ /* mPeer is left null */
+
+ unconst(mNATEngine).createObject();
+ mNATEngine->initCopy(aParent, this, aThat->mNATEngine);
+
+ /* sanity */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ mData.attachCopy(aThat->mData);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void NetworkAdapter::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ mData.free();
+
+ unconst(mNATEngine).setNull();
+ unconst(mPeer) = NULL;
+ unconst(mParent) = NULL;
+}
+
+// wrapped INetworkAdapter properties
+////////////////////////////////////////////////////////////////////////////////
+HRESULT NetworkAdapter::getAdapterType(NetworkAdapterType_T *aAdapterType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAdapterType = mData->type;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::setAdapterType(NetworkAdapterType_T aAdapterType)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* make sure the value is allowed */
+ switch (aAdapterType)
+ {
+ case NetworkAdapterType_Am79C970A:
+ case NetworkAdapterType_Am79C973:
+ case NetworkAdapterType_Am79C960:
+#ifdef VBOX_WITH_E1000
+ case NetworkAdapterType_I82540EM:
+ case NetworkAdapterType_I82543GC:
+ case NetworkAdapterType_I82545EM:
+#endif
+#ifdef VBOX_WITH_VIRTIO
+ case NetworkAdapterType_Virtio:
+#endif
+ case NetworkAdapterType_NE1000:
+ case NetworkAdapterType_NE2000:
+ case NetworkAdapterType_WD8003:
+ case NetworkAdapterType_WD8013:
+ case NetworkAdapterType_ELNK2:
+ case NetworkAdapterType_ELNK1:
+ break;
+ default:
+ return setError(E_FAIL,
+ tr("Invalid network adapter type '%d'"),
+ aAdapterType);
+ }
+
+ if (mData->type != aAdapterType)
+ {
+ mData.backup();
+ mData->type = aAdapterType;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* Changing the network adapter type during runtime is not allowed,
+ * therefore no immediate change in CFGM logic => changeAdapter=FALSE. */
+ mParent->i_onNetworkAdapterChange(this, FALSE);
+ }
+
+ return S_OK;
+}
+
+
+HRESULT NetworkAdapter::getSlot(ULONG *uSlot)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *uSlot = mData->ulSlot;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::getEnabled(BOOL *aEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aEnabled = mData->fEnabled;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::setEnabled(BOOL aEnabled)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->fEnabled != RT_BOOL(aEnabled))
+ {
+ mData.backup();
+ mData->fEnabled = RT_BOOL(aEnabled);
+ if (RT_BOOL(aEnabled) && mData->strMACAddress.isEmpty())
+ i_generateMACAddress();
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* Disabling the network adapter during runtime is not allowed
+ * therefore no immediate change in CFGM logic => changeAdapter=FALSE. */
+ mParent->i_onNetworkAdapterChange(this, FALSE);
+ }
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::getMACAddress(com::Utf8Str &aMACAddress)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComAssertRet(!mData->fEnabled || !mData->strMACAddress.isEmpty(), E_FAIL);
+
+ aMACAddress = mData->strMACAddress;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::i_updateMacAddress(Utf8Str aMACAddress)
+{
+ HRESULT rc = S_OK;
+
+ /*
+ * Are we supposed to generate a MAC?
+ */
+ if (mData->fEnabled && aMACAddress.isEmpty())
+ i_generateMACAddress();
+ else
+ {
+ if (mData->strMACAddress != aMACAddress)
+ {
+ if (mData->fEnabled || !aMACAddress.isEmpty())
+ {
+ /*
+ * Verify given MAC address
+ */
+ char *macAddressStr = aMACAddress.mutableRaw();
+ int i = 0;
+ while ((i < 13) && macAddressStr && *macAddressStr && (rc == S_OK))
+ {
+ char c = *macAddressStr;
+ /* canonicalize hex digits to capital letters */
+ if (c >= 'a' && c <= 'f')
+ {
+ c = (char)RTLocCToUpper(c);
+ *macAddressStr = c;
+ }
+ /* we only accept capital letters */
+ if ( (c < '0' || c > '9')
+ && (c < 'A' || c > 'F'))
+ rc = setError(E_INVALIDARG, tr("Invalid MAC address format"));
+ /* the second digit must have even value for unicast addresses */
+ if ( (i == 1)
+ && (!!(c & 1) == (c >= '0' && c <= '9')))
+ rc = setError(E_INVALIDARG, tr("Invalid MAC address format"));
+
+ macAddressStr++;
+ i++;
+ }
+ /* we must have parsed exactly 12 characters */
+ if (i != 12)
+ rc = setError(E_INVALIDARG, tr("Invalid MAC address format"));
+ }
+
+ if (SUCCEEDED(rc))
+ mData->strMACAddress = aMACAddress;
+ }
+ }
+
+ return rc;
+}
+
+HRESULT NetworkAdapter::setMACAddress(const com::Utf8Str &aMACAddress)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.backup();
+
+ HRESULT rc = i_updateMacAddress(aMACAddress);
+ if (SUCCEEDED(rc))
+ {
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* Changing the MAC via the Main API during runtime is not allowed,
+ * therefore no immediate change in CFGM logic => changeAdapter=FALSE. */
+ mParent->i_onNetworkAdapterChange(this, FALSE);
+ }
+
+ return rc;
+}
+
+HRESULT NetworkAdapter::getAttachmentType(NetworkAttachmentType_T *aAttachmentType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAttachmentType = mData->mode;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::setAttachmentType(NetworkAttachmentType_T aAttachmentType)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->mode != aAttachmentType)
+ {
+ mData.backup();
+
+ /* there must an internal network name */
+ if (mData->strInternalNetworkName.isEmpty())
+ {
+ Log(("Internal network name not defined, setting to default \"intnet\"\n"));
+ mData->strInternalNetworkName = "intnet";
+ }
+
+ /* there must a NAT network name */
+ if (mData->strNATNetworkName.isEmpty())
+ {
+ Log(("NAT network name not defined, setting to default \"NatNetwork\"\n"));
+ mData->strNATNetworkName = "NatNetwork";
+ }
+
+ NetworkAttachmentType_T oldAttachmentType = mData->mode;
+ mData->mode = aAttachmentType;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ if (oldAttachmentType == NetworkAttachmentType_NATNetwork)
+ i_switchFromNatNetworking(mData->strNATNetworkName);
+
+ if (aAttachmentType == NetworkAttachmentType_NATNetwork)
+ i_switchToNatNetworking(mData->strNATNetworkName);
+
+ /* Adapt the CFGM logic and notify the guest => changeAdapter=TRUE. */
+ mParent->i_onNetworkAdapterChange(this, TRUE);
+ }
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::getBridgedInterface(com::Utf8Str &aBridgedInterface)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aBridgedInterface = mData->strBridgedName;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::setBridgedInterface(const com::Utf8Str &aBridgedInterface)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ Bstr canonicalName = aBridgedInterface;
+#ifdef RT_OS_DARWIN
+ com::SafeIfaceArray<IHostNetworkInterface> hostNetworkInterfaces;
+ ComPtr<IHost> host;
+ HRESULT rc = mParent->i_getVirtualBox()->COMGETTER(Host)(host.asOutParam());
+ if (SUCCEEDED(rc))
+ {
+ host->FindHostNetworkInterfacesOfType(HostNetworkInterfaceType_Bridged,
+ ComSafeArrayAsOutParam(hostNetworkInterfaces));
+ for (size_t i = 0; i < hostNetworkInterfaces.size(); ++i)
+ {
+ Bstr shortName;
+ ComPtr<IHostNetworkInterface> ni = hostNetworkInterfaces[i];
+ ni->COMGETTER(ShortName)(shortName.asOutParam());
+ if (shortName == aBridgedInterface)
+ {
+ ni->COMGETTER(Name)(canonicalName.asOutParam());
+ break;
+ }
+ }
+ }
+#endif /* RT_OS_DARWIN */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (Bstr(mData->strBridgedName) != canonicalName)
+ {
+ /* if an empty/null string is to be set, bridged interface must be
+ * turned off */
+ if ( canonicalName.isEmpty()
+ && mData->fEnabled
+ && mData->mode == NetworkAttachmentType_Bridged)
+ {
+ return setError(E_FAIL,
+ tr("Empty or null bridged interface name is not valid"));
+ }
+
+ mData.backup();
+ mData->strBridgedName = canonicalName;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* When changing the host adapter, adapt the CFGM logic to make this
+ * change immediately effect and to notify the guest that the network
+ * might have changed, therefore changeAdapter=TRUE. */
+ mParent->i_onNetworkAdapterChange(this, TRUE);
+ }
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::getHostOnlyInterface(com::Utf8Str &aHostOnlyInterface)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aHostOnlyInterface = mData->strHostOnlyName;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::setHostOnlyInterface(const com::Utf8Str &aHostOnlyInterface)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->strHostOnlyName != aHostOnlyInterface)
+ {
+ /* if an empty/null string is to be set, host only interface must be
+ * turned off */
+ if ( aHostOnlyInterface.isEmpty()
+ && mData->fEnabled
+ && mData->mode == NetworkAttachmentType_HostOnly)
+ {
+ return setError(E_FAIL,
+ tr("Empty or null host only interface name is not valid"));
+ }
+
+ mData.backup();
+ mData->strHostOnlyName = aHostOnlyInterface;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* When changing the host adapter, adapt the CFGM logic to make this
+ * change immediately effect and to notify the guest that the network
+ * might have changed, therefore changeAdapter=TRUE. */
+ mParent->i_onNetworkAdapterChange(this, TRUE);
+ }
+
+ return S_OK;
+}
+
+
+HRESULT NetworkAdapter::getHostOnlyNetwork(com::Utf8Str &aHostOnlyNetwork)
+{
+#ifdef VBOX_WITH_VMNET
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aHostOnlyNetwork = mData->strHostOnlyNetworkName;
+
+ return S_OK;
+#else /* !VBOX_WITH_VMNET */
+ NOREF(aHostOnlyNetwork);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_VMNET */
+}
+
+HRESULT NetworkAdapter::setHostOnlyNetwork(const com::Utf8Str &aHostOnlyNetwork)
+{
+#ifdef VBOX_WITH_VMNET
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->strHostOnlyNetworkName != aHostOnlyNetwork)
+ {
+ /* if an empty/null string is to be set, host only Network must be
+ * turned off */
+ if ( aHostOnlyNetwork.isEmpty()
+ && mData->fEnabled
+ && mData->mode == NetworkAttachmentType_HostOnly)
+ {
+ return setError(E_FAIL,
+ tr("Empty or null host only Network name is not valid"));
+ }
+
+ mData.backup();
+ mData->strHostOnlyNetworkName = aHostOnlyNetwork;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* When changing the host adapter, adapt the CFGM logic to make this
+ * change immediately effect and to notify the guest that the network
+ * might have changed, therefore changeAdapter=TRUE. */
+ mParent->i_onNetworkAdapterChange(this, TRUE);
+ }
+
+ return S_OK;
+#else /* !VBOX_WITH_VMNET */
+ NOREF(aHostOnlyNetwork);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_VMNET */
+}
+
+
+HRESULT NetworkAdapter::getInternalNetwork(com::Utf8Str &aInternalNetwork)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aInternalNetwork = mData->strInternalNetworkName;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::setInternalNetwork(const com::Utf8Str &aInternalNetwork)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->strInternalNetworkName != aInternalNetwork)
+ {
+ /* if an empty/null string is to be set, internal networking must be
+ * turned off */
+ if ( aInternalNetwork.isEmpty()
+ && mData->fEnabled
+ && mData->mode == NetworkAttachmentType_Internal)
+ {
+ return setError(E_FAIL,
+ tr("Empty or null internal network name is not valid"));
+ }
+ mData.backup();
+ mData->strInternalNetworkName = aInternalNetwork;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* When changing the internal network, adapt the CFGM logic to make this
+ * change immediately effect and to notify the guest that the network
+ * might have changed, therefore changeAdapter=TRUE. */
+ mParent->i_onNetworkAdapterChange(this, TRUE);
+ }
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::getNATNetwork(com::Utf8Str &aNATNetwork)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aNATNetwork = mData->strNATNetworkName;
+
+ return S_OK;
+}
+
+
+HRESULT NetworkAdapter::setNATNetwork(const com::Utf8Str &aNATNetwork)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->strNATNetworkName != aNATNetwork)
+ {
+ /* if an empty/null string is to be set, host only interface must be
+ * turned off */
+ if ( aNATNetwork.isEmpty()
+ && mData->fEnabled
+ && mData->mode == NetworkAttachmentType_NATNetwork)
+ return setError(E_FAIL,
+ tr("Empty or null NAT network name is not valid"));
+
+ mData.backup();
+
+ Bstr oldNatNetworkName = mData->strNATNetworkName;
+ mData->strNATNetworkName = aNATNetwork;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ if (mData->mode == NetworkAttachmentType_NATNetwork)
+ {
+ i_switchFromNatNetworking(oldNatNetworkName.raw());
+ i_switchToNatNetworking(aNATNetwork);
+ }
+
+ /* When changing the host adapter, adapt the CFGM logic to make this
+ * change immediately effect and to notify the guest that the network
+ * might have changed, therefore changeAdapter=TRUE. */
+ mParent->i_onNetworkAdapterChange(this, TRUE);
+ }
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::getGenericDriver(com::Utf8Str &aGenericDriver)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aGenericDriver = mData->strGenericDriver;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::setGenericDriver(const com::Utf8Str &aGenericDriver)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->strGenericDriver != aGenericDriver)
+ {
+ mData.backup();
+ mData->strGenericDriver = aGenericDriver;
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ mParent->i_onNetworkAdapterChange(this, FALSE);
+ }
+
+ return S_OK;
+}
+
+
+HRESULT NetworkAdapter::getCloudNetwork(com::Utf8Str &aCloudNetwork)
+{
+#ifdef VBOX_WITH_CLOUD_NET
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aCloudNetwork = mData->strCloudNetworkName;
+
+ return S_OK;
+#else /* !VBOX_WITH_CLOUD_NET */
+ NOREF(aCloudNetwork);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_CLOUD_NET */
+}
+
+HRESULT NetworkAdapter::setCloudNetwork(const com::Utf8Str &aCloudNetwork)
+{
+#ifdef VBOX_WITH_CLOUD_NET
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->strCloudNetworkName != aCloudNetwork)
+ {
+ /* if an empty/null string is to be set, Cloud networking must be
+ * turned off */
+ if ( aCloudNetwork.isEmpty()
+ && mData->fEnabled
+ && mData->mode == NetworkAttachmentType_Cloud)
+ {
+ return setError(E_FAIL,
+ tr("Empty or null Cloud network name is not valid"));
+ }
+ mData.backup();
+ mData->strCloudNetworkName = aCloudNetwork;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+#if 0
+ /// @todo Implement dynamic re-attachment of cloud network
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* When changing the internal network, adapt the CFGM logic to make this
+ * change immediately effect and to notify the guest that the network
+ * might have changed, therefore changeAdapter=TRUE. */
+ mParent->i_onNetworkAdapterChange(this, TRUE);
+#else
+ mParent->i_onNetworkAdapterChange(this, FALSE);
+#endif
+ }
+ return S_OK;
+#else /* !VBOX_WITH_CLOUD_NET */
+ NOREF(aCloudNetwork);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_CLOUD_NET */
+}
+
+
+HRESULT NetworkAdapter::getCableConnected(BOOL *aConnected)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aConnected = mData->fCableConnected;
+
+ return S_OK;
+}
+
+
+HRESULT NetworkAdapter::setCableConnected(BOOL aConnected)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (RT_BOOL(aConnected) != mData->fCableConnected)
+ {
+ mData.backup();
+ mData->fCableConnected = RT_BOOL(aConnected);
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* No change in CFGM logic => changeAdapter=FALSE. */
+ mParent->i_onNetworkAdapterChange(this, FALSE);
+ }
+
+ return S_OK;
+}
+
+
+HRESULT NetworkAdapter::getLineSpeed(ULONG *aSpeed)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aSpeed = mData->ulLineSpeed;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::setLineSpeed(ULONG aSpeed)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aSpeed != mData->ulLineSpeed)
+ {
+ mData.backup();
+ mData->ulLineSpeed = aSpeed;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* No change in CFGM logic => changeAdapter=FALSE. */
+ mParent->i_onNetworkAdapterChange(this, FALSE);
+ }
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::getPromiscModePolicy(NetworkAdapterPromiscModePolicy_T *aPromiscModePolicy)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aPromiscModePolicy = mData->enmPromiscModePolicy;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::setPromiscModePolicy(NetworkAdapterPromiscModePolicy_T aPromiscModePolicy)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ switch (aPromiscModePolicy)
+ {
+ case NetworkAdapterPromiscModePolicy_Deny:
+ case NetworkAdapterPromiscModePolicy_AllowNetwork:
+ case NetworkAdapterPromiscModePolicy_AllowAll:
+ break;
+ default:
+ return setError(E_INVALIDARG, tr("Invalid promiscuous mode policy (%d)"), aPromiscModePolicy);
+ }
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aPromiscModePolicy != mData->enmPromiscModePolicy)
+ {
+ mData.backup();
+ mData->enmPromiscModePolicy = aPromiscModePolicy;
+
+ alock.release();
+ mParent->i_setModifiedLock(Machine::IsModified_NetworkAdapters);
+ mParent->i_onNetworkAdapterChange(this, TRUE);
+ }
+ }
+
+ return hrc;
+}
+
+
+HRESULT NetworkAdapter::getTraceEnabled(BOOL *aEnabled)
+{
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aEnabled = mData->fTraceEnabled;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::setTraceEnabled(BOOL aEnabled)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (RT_BOOL(aEnabled) != mData->fTraceEnabled)
+ {
+ mData.backup();
+ mData->fTraceEnabled = RT_BOOL(aEnabled);
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* Adapt the CFGM logic changeAdapter=TRUE */
+ mParent->i_onNetworkAdapterChange(this, TRUE);
+ }
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::getTraceFile(com::Utf8Str &aTraceFile)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aTraceFile = mData->strTraceFile;
+
+ return S_OK;
+}
+
+
+HRESULT NetworkAdapter::setTraceFile(const com::Utf8Str &aTraceFile)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->strTraceFile != aTraceFile)
+ {
+ mData.backup();
+ mData->strTraceFile = aTraceFile;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* We change the 'File' => changeAdapter=TRUE. */
+ mParent->i_onNetworkAdapterChange(this, TRUE);
+ }
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::getNATEngine(ComPtr<INATEngine> &aNATEngine)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aNATEngine = mNATEngine;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::getBootPriority(ULONG *aBootPriority)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aBootPriority = mData->ulBootPriority;
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::setBootPriority(ULONG aBootPriority)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aBootPriority != mData->ulBootPriority)
+ {
+ mData.backup();
+ mData->ulBootPriority = aBootPriority;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, no need to lock
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* No change in CFGM logic => changeAdapter=FALSE. */
+ mParent->i_onNetworkAdapterChange(this, FALSE);
+ }
+
+ return S_OK;
+}
+
+// wrapped INetworkAdapter methods
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT NetworkAdapter::getProperty(const com::Utf8Str &aKey, com::Utf8Str &aValue)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aValue = "";
+ settings::StringsMap::const_iterator it = mData->genericProperties.find(aKey);
+ if (it != mData->genericProperties.end())
+ aValue = it->second; // source is a Utf8Str
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::setProperty(const com::Utf8Str &aKey, const com::Utf8Str &aValue)
+{
+ LogFlowThisFunc(("\n"));
+ /* The machine needs to be mutable. */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ bool fGenericChange = (mData->mode == NetworkAttachmentType_Generic);
+ /* Generic properties processing.
+ * Look up the old value first; if nothing's changed then do nothing.
+ */
+ Utf8Str strOldValue;
+ settings::StringsMap::const_iterator it = mData->genericProperties.find(aKey);
+ if (it != mData->genericProperties.end())
+ strOldValue = it->second;
+
+ if (strOldValue != aValue)
+ {
+ if (aValue.isEmpty())
+ mData->genericProperties.erase(aKey);
+ else
+ mData->genericProperties[aKey] = aValue;
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS);
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /* Avoid deadlock when the event triggers a call to a method of this
+ * interface. */
+ adep.release();
+
+ mParent->i_onNetworkAdapterChange(this, fGenericChange);
+ }
+
+ return S_OK;
+}
+
+HRESULT NetworkAdapter::getProperties(const com::Utf8Str &aNames,
+ std::vector<com::Utf8Str> &aReturnNames,
+ std::vector<com::Utf8Str> &aReturnValues)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /// @todo make use of aNames according to the documentation
+ NOREF(aNames);
+ aReturnNames.resize(mData->genericProperties.size());
+ aReturnValues.resize(mData->genericProperties.size());
+
+ size_t i = 0;
+
+ for (settings::StringsMap::const_iterator it = mData->genericProperties.begin();
+ it != mData->genericProperties.end();
+ ++it, ++i)
+ {
+ aReturnNames[i] = it->first;
+ aReturnValues[i] = it->second;
+ }
+
+ return S_OK;
+}
+
+
+
+// public methods only for internal purposes
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Loads settings from the given adapter node.
+ * May be called once right after this object creation.
+ *
+ * @param bwctl bandwidth control object.
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT NetworkAdapter::i_loadSettings(BandwidthControl *bwctl,
+ const settings::NetworkAdapter &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Note: we assume that the default values for attributes of optional
+ * nodes are assigned in the Data::Data() constructor and don't do it
+ * here. It implies that this method may only be called after constructing
+ * a new BIOSSettings object while all its data fields are in the default
+ * values. Exceptions are fields whose creation time defaults don't match
+ * values that should be applied when these fields are not explicitly set
+ * in the settings file (for backwards compatibility reasons). This takes
+ * place when a setting of a newly created object must default to A while
+ * the same setting of an object loaded from the old settings file must
+ * default to B. */
+
+ HRESULT rc = S_OK;
+
+ /* MAC address (can be null) */
+ rc = i_updateMacAddress(data.strMACAddress);
+ if (FAILED(rc)) return rc;
+
+ mData.assignCopy(&data);
+
+ if (mData->strBandwidthGroup.isNotEmpty())
+ {
+ ComObjPtr<BandwidthGroup> group;
+ rc = bwctl->i_getBandwidthGroupByName(data.strBandwidthGroup, group, true);
+ if (FAILED(rc)) return rc;
+ group->i_reference();
+ }
+
+ // Load NAT engine settings.
+ mNATEngine->i_loadSettings(data.nat);
+
+ // leave the lock before setting attachment type
+ alock.release();
+
+ rc = COMSETTER(AttachmentType)(data.mode);
+ if (FAILED(rc)) return rc;
+
+ return S_OK;
+}
+
+/**
+ * Saves settings to the given adapter node.
+ *
+ * Note that the given Adapter node is completely empty on input.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT NetworkAdapter::i_saveSettings(settings::NetworkAdapter &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ data = *mData.data();
+
+ mNATEngine->i_saveSettings(data.nat);
+
+ return S_OK;
+}
+
+/**
+ * Returns true if any setter method has modified settings of this instance.
+ * @return
+ */
+bool NetworkAdapter::i_isModified()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ bool fChanged = mData.isBackedUp();
+ fChanged |= mNATEngine->i_isModified();
+ return fChanged;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+void NetworkAdapter::i_rollback()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mNATEngine->i_rollback();
+
+ mData.rollback();
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object (also
+ * for writing) if there is one.
+ */
+void NetworkAdapter::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(mPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(mPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ mNATEngine->i_commit();
+
+ if (mData.isBackedUp())
+ {
+ mData.commit();
+ if (mPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ mPeer->mData.attach(mData);
+ }
+ }
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ */
+void NetworkAdapter::i_copyFrom(NetworkAdapter *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ mNATEngine->i_copyFrom(aThat->mNATEngine);
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ mData.assignCopy(aThat->mData);
+
+}
+
+/**
+ * Applies the defaults for this network adapter.
+ *
+ * @note This method currently assumes that the object is in the state after
+ * calling init(), it does not set defaults from an arbitrary state.
+ */
+void NetworkAdapter::i_applyDefaults(GuestOSType *aOsType)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ mNATEngine->i_applyDefaults();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ bool e1000enabled = false;
+#ifdef VBOX_WITH_E1000
+ e1000enabled = true;
+#endif // VBOX_WITH_E1000
+
+ NetworkAdapterType_T defaultType;
+ if (aOsType)
+ defaultType = aOsType->i_networkAdapterType();
+ else
+ {
+#ifdef VBOX_WITH_E1000
+ defaultType = NetworkAdapterType_I82540EM;
+#else
+ defaultType = NetworkAdapterType_Am79C973A;
+#endif
+ }
+
+
+ /* Set default network adapter for this OS type */
+ if (defaultType == NetworkAdapterType_I82540EM ||
+ defaultType == NetworkAdapterType_I82543GC ||
+ defaultType == NetworkAdapterType_I82545EM)
+ {
+ if (e1000enabled)
+ mData->type = defaultType;
+ }
+ else
+ mData->type = defaultType;
+
+ /* Enable the first one adapter and set it to NAT */
+ /** @todo r=klaus remove this long term, since a newly created VM should
+ * have no additional hardware components unless configured either
+ * explicitly or through Machine::applyDefaults. */
+ if (aOsType && mData->ulSlot == 0)
+ {
+ mData->fEnabled = true;
+ if (mData->strMACAddress.isEmpty())
+ i_generateMACAddress();
+ mData->mode = NetworkAttachmentType_NAT;
+ }
+ mData->fCableConnected = true;
+}
+
+bool NetworkAdapter::i_hasDefaults()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), true);
+
+ ComObjPtr<GuestOSType> pGuestOSType;
+ HRESULT rc = mParent->i_getVirtualBox()->i_findGuestOSType(mParent->i_getOSTypeId(),
+ pGuestOSType);
+ if (FAILED(rc))
+ return false;
+
+ NetworkAdapterType_T defaultType = pGuestOSType->i_networkAdapterType();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( !mData->fEnabled
+ && mData->strMACAddress.isEmpty()
+ && mData->type == defaultType
+ && mData->fCableConnected
+ && mData->ulLineSpeed == 0
+ && mData->enmPromiscModePolicy == NetworkAdapterPromiscModePolicy_Deny
+ && mData->mode == NetworkAttachmentType_Null
+ && mData->strBridgedName.isEmpty()
+ && mData->strInternalNetworkName.isEmpty()
+ && mData->strHostOnlyName.isEmpty()
+ && mData->strNATNetworkName.isEmpty()
+ && mData->strGenericDriver.isEmpty()
+ && mData->genericProperties.size() == 0)
+ {
+ /* Could be default, check NAT defaults. */
+ return mNATEngine->i_hasDefaults();
+ }
+
+ return false;
+}
+
+ComObjPtr<NetworkAdapter> NetworkAdapter::i_getPeer()
+{
+ return mPeer;
+}
+
+
+// private methods
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Generates a new unique MAC address based on our vendor ID and
+ * parts of a GUID.
+ *
+ * @note Must be called from under the object's write lock or within the init
+ * span.
+ */
+void NetworkAdapter::i_generateMACAddress()
+{
+ Utf8Str mac;
+ Host::i_generateMACAddress(mac);
+ LogFlowThisFunc(("generated MAC: '%s'\n", mac.c_str()));
+ mData->strMACAddress = mac;
+}
+
+HRESULT NetworkAdapter::getBandwidthGroup(ComPtr<IBandwidthGroup> &aBandwidthGroup)
+{
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->strBandwidthGroup.isNotEmpty())
+ {
+ ComObjPtr<BandwidthGroup> pBwGroup;
+ hrc = mParent->i_getBandwidthGroup(mData->strBandwidthGroup, pBwGroup, true /* fSetError */);
+
+ Assert(SUCCEEDED(hrc)); /* This is not allowed to fail because the existence
+ * of the group was checked when it was attached. */
+ if (SUCCEEDED(hrc))
+ pBwGroup.queryInterfaceTo(aBandwidthGroup.asOutParam());
+ }
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT NetworkAdapter::setBandwidthGroup(const ComPtr<IBandwidthGroup> &aBandwidthGroup)
+{
+ LogFlowThisFuncEnter();
+
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ IBandwidthGroup *iBw = aBandwidthGroup;
+ Utf8Str strBwGroup;
+ if (aBandwidthGroup)
+ strBwGroup = static_cast<BandwidthGroup *>(iBw)->i_getName();
+
+ if (mData->strBandwidthGroup != strBwGroup)
+ {
+ ComObjPtr<BandwidthGroup> pBwGroup;
+ if (!strBwGroup.isEmpty())
+ {
+ HRESULT hrc = mParent->i_getBandwidthGroup(strBwGroup, pBwGroup, false /* fSetError */);
+ NOREF(hrc);
+ Assert(SUCCEEDED(hrc)); /* This is not allowed to fail because the existence
+ of the group was checked when it was attached. */
+ }
+
+ i_updateBandwidthGroup(pBwGroup);
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS);
+ mParent->i_setModified(Machine::IsModified_NetworkAdapters);
+ mlock.release();
+
+ /** @todo changeAdapter=???. */
+ mParent->i_onNetworkAdapterChange(this, FALSE);
+ }
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+void NetworkAdapter::i_updateBandwidthGroup(BandwidthGroup *aBwGroup)
+{
+ LogFlowThisFuncEnter();
+ Assert(isWriteLockOnCurrentThread());
+
+ ComObjPtr<BandwidthGroup> pOldBwGroup;
+ if (!mData->strBandwidthGroup.isEmpty())
+ {
+ HRESULT hrc = mParent->i_getBandwidthGroup(mData->strBandwidthGroup, pOldBwGroup, false /* fSetError */);
+ NOREF(hrc);
+ Assert(SUCCEEDED(hrc)); /* This is not allowed to fail because the existence of
+ the group was checked when it was attached. */
+ }
+
+ mData.backup();
+ if (!pOldBwGroup.isNull())
+ {
+ pOldBwGroup->i_release();
+ mData->strBandwidthGroup = Utf8Str::Empty;
+ }
+
+ if (aBwGroup)
+ {
+ mData->strBandwidthGroup = aBwGroup->i_getName();
+ aBwGroup->i_reference();
+ }
+
+ LogFlowThisFuncLeave();
+}
+
+
+HRESULT NetworkAdapter::i_switchFromNatNetworking(const com::Utf8Str &networkName)
+{
+ HRESULT hrc;
+ MachineState_T state;
+
+ hrc = mParent->COMGETTER(State)(&state);
+ if (FAILED(hrc))
+ return hrc;
+
+ if ( state == MachineState_Running
+ || state == MachineState_Paused)
+ {
+ Bstr bstrName;
+ hrc = mParent->COMGETTER(Name)(bstrName.asOutParam());
+ LogRel(("VM '%ls' stops using NAT network '%s'\n", bstrName.raw(), networkName.c_str()));
+ int natCount = mParent->i_getVirtualBox()->i_natNetworkRefDec(Bstr(networkName).raw());
+ if (natCount == -1)
+ return E_INVALIDARG; /* no such network */
+ }
+
+ return S_OK;
+}
+
+
+HRESULT NetworkAdapter::i_switchToNatNetworking(const com::Utf8Str &aNatNetworkName)
+{
+ HRESULT hrc;
+ MachineState_T state;
+
+ hrc = mParent->COMGETTER(State)(&state);
+ if (FAILED(hrc))
+ return hrc;
+
+ if ( state == MachineState_Running
+ || state == MachineState_Paused)
+ {
+ Bstr bstrName;
+ hrc = mParent->COMGETTER(Name)(bstrName.asOutParam());
+ LogRel(("VM '%ls' starts using NAT network '%s'\n", bstrName.raw(), aNatNetworkName.c_str()));
+ int natCount = mParent->i_getVirtualBox()->i_natNetworkRefInc(Bstr(aNatNetworkName).raw());
+ if (natCount == -1)
+ return E_INVALIDARG; /* not found */
+ }
+
+ return S_OK;
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/NetworkServiceRunner.cpp b/src/VBox/Main/src-server/NetworkServiceRunner.cpp
new file mode 100644
index 00000000..a04849c7
--- /dev/null
+++ b/src/VBox/Main/src-server/NetworkServiceRunner.cpp
@@ -0,0 +1,307 @@
+/* $Id: NetworkServiceRunner.cpp $ */
+/** @file
+ * VirtualBox Main - interface for VBox DHCP server
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "NetworkServiceRunner.h"
+
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/process.h>
+#include <iprt/path.h>
+#include <iprt/param.h>
+#include <iprt/thread.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/*static*/ const char * const NetworkServiceRunner::kpszKeyNetwork = "--network";
+/*static*/ const char * const NetworkServiceRunner::kpszKeyTrunkType = "--trunk-type";
+/*static*/ const char * const NetworkServiceRunner::kpszTrunkName = "--trunk-name";
+/*static*/ const char * const NetworkServiceRunner::kpszMacAddress = "--mac-address";
+/*static*/ const char * const NetworkServiceRunner::kpszIpAddress = "--ip-address";
+/*static*/ const char * const NetworkServiceRunner::kpszIpNetmask = "--netmask";
+/*static*/ const char * const NetworkServiceRunner::kpszKeyNeedMain = "--need-main";
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Internal data the rest of the world does not need to be bothered with.
+ *
+ * @note no 'm' prefix here, as the runner is accessing it thru an 'm' member.
+ */
+struct NetworkServiceRunner::Data
+{
+ /** The process filename. */
+ const char *pszProcName;
+ /** Actual size of papszArgs. */
+ size_t cArgs;
+ /** Number of entries allocated for papszArgs. */
+ size_t cArgsAlloc;
+ /** The argument vector.
+ * Each entry is a string allocation via RTStrDup. The zero'th entry is
+ * filled in by start(). */
+ char **papszArgs;
+ /** The process ID. */
+ RTPROCESS Process;
+ /** Whether to kill the process on stopping. */
+ bool fKillProcessOnStop;
+
+ Data(const char* aProcName)
+ : pszProcName(aProcName)
+ , cArgs(0)
+ , cArgsAlloc(0)
+ , papszArgs(NULL)
+ , Process(NIL_RTPROCESS)
+ , fKillProcessOnStop(false)
+ {}
+
+ ~Data()
+ {
+ resetArguments();
+ }
+
+ void resetArguments()
+ {
+ for (size_t i = 0; i < cArgs; i++)
+ RTStrFree(papszArgs[i]);
+ RTMemFree(papszArgs);
+ cArgs = 0;
+ cArgsAlloc = 0;
+ papszArgs = NULL;
+ }
+};
+
+
+
+NetworkServiceRunner::NetworkServiceRunner(const char *aProcName)
+{
+ m = new NetworkServiceRunner::Data(aProcName);
+}
+
+
+NetworkServiceRunner::~NetworkServiceRunner()
+{
+ stop();
+ delete m;
+ m = NULL;
+}
+
+
+/**
+ * Adds one argument to the server command line.
+ *
+ * @returns IPRT status code.
+ * @param pszArgument The argument to add.
+ */
+int NetworkServiceRunner::addArgument(const char *pszArgument)
+{
+ AssertPtr(pszArgument);
+
+ /*
+ * Grow the argument vector as needed.
+ * Make sure unused space is NULL'ed and that we've got an extra entry for
+ * the NULL terminator. Arguments starts at 1 of course, 0 being the executable.
+ */
+ size_t const i = RT_MAX(m->cArgs, 1);
+ size_t const cAlloc = m->cArgsAlloc;
+ if (i + 1 /*NULL terminator*/ >= m->cArgsAlloc)
+ {
+ size_t cNewAlloc = cAlloc ? cAlloc : 2;
+ do
+ cNewAlloc *= 2;
+ while (cNewAlloc <= i + 1);
+ void *pvNew = RTMemRealloc(m->papszArgs, cNewAlloc * sizeof(m->papszArgs[0]));
+ AssertReturn(pvNew, VERR_NO_MEMORY);
+ m->papszArgs = (char **)pvNew;
+ RT_BZERO(&m->papszArgs[m->cArgsAlloc], (cNewAlloc - cAlloc) * sizeof(m->papszArgs[0]));
+ m->cArgsAlloc = cNewAlloc;
+ }
+
+ /*
+ * Add it.
+ */
+ m->papszArgs[i] = RTStrDup(pszArgument);
+ if (m->papszArgs[i])
+ {
+ m->cArgs = i + 1;
+ Assert(m->papszArgs[m->cArgs] == NULL);
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_STR_MEMORY;
+}
+
+
+/**
+ * Adds a pair of arguments, e.g. option + value.
+ *
+ * @returns IPRT status code.
+ */
+int NetworkServiceRunner::addArgPair(const char *pszOption, const char *pszValue)
+{
+ int rc = addArgument(pszOption);
+ if (RT_SUCCESS(rc))
+ rc = addArgument(pszValue);
+ return rc;
+}
+
+
+void NetworkServiceRunner::resetArguments()
+{
+ m->resetArguments();
+}
+
+
+void NetworkServiceRunner::detachFromServer()
+{
+ m->Process = NIL_RTPROCESS;
+}
+
+
+int NetworkServiceRunner::start(bool aKillProcessOnStop)
+{
+ if (isRunning())
+ return VINF_ALREADY_INITIALIZED;
+
+ /*
+ * Construct the path to the executable and put in into the argument vector.
+ * ASSUME it is relative to the directory that holds VBoxSVC.
+ */
+ char szExePath[RTPATH_MAX];
+ AssertReturn(RTProcGetExecutablePath(szExePath, RTPATH_MAX), VERR_FILENAME_TOO_LONG);
+ RTPathStripFilename(szExePath);
+ int vrc = RTPathAppend(szExePath, sizeof(szExePath), m->pszProcName);
+ AssertLogRelRCReturn(vrc, vrc);
+
+ if (m->cArgs == 0 && m->cArgsAlloc == 0)
+ {
+ m->cArgsAlloc = 2;
+ m->papszArgs = (char **)RTMemAllocZ(sizeof(m->papszArgs[0]) * 2);
+ AssertReturn(m->papszArgs, VERR_NO_MEMORY);
+ }
+ else
+ Assert(m->cArgsAlloc >= 2);
+ RTStrFree(m->papszArgs[0]);
+ m->papszArgs[0] = RTStrDup(szExePath);
+ AssertReturn(m->papszArgs[0], VERR_NO_MEMORY);
+ if (m->cArgs == 0)
+ m->cArgs = 1;
+
+ /*
+ * Start the process:
+ */
+ int rc = RTProcCreate(szExePath, m->papszArgs, RTENV_DEFAULT, 0, &m->Process);
+ if (RT_SUCCESS(rc))
+ LogRel(("NetworkServiceRunning: started '%s', pid %RTproc\n", m->pszProcName, m->Process));
+ else
+ m->Process = NIL_RTPROCESS;
+
+ m->fKillProcessOnStop = aKillProcessOnStop;
+
+ return rc;
+}
+
+
+int NetworkServiceRunner::stop()
+{
+ /*
+ * If the process already terminated, this function will also grab the exit
+ * status and transition the process out of zombie status.
+ */
+ if (!isRunning())
+ return VINF_OBJECT_DESTROYED;
+
+ bool fDoKillProc = true;
+
+ if (!m->fKillProcessOnStop)
+ {
+ /*
+ * This is a VBoxSVC Main client. Do NOT kill it but assume it was shut
+ * down politely. Wait up to 1 second until the process is killed before
+ * doing the final hard kill.
+ */
+ for (unsigned int i = 0; i < 100; i++)
+ {
+ if (!isRunning())
+ {
+ fDoKillProc = false;
+ break;
+ }
+ RTThreadSleep(10);
+ }
+ }
+
+ if (fDoKillProc)
+ {
+ LogRel(("NetworkServiceRunning: killing %s, pid %RTproc...\n", m->pszProcName, m->Process));
+ RTProcTerminate(m->Process);
+
+ int rc = RTProcWait(m->Process, RTPROCWAIT_FLAGS_BLOCK, NULL);
+ NOREF(rc);
+ }
+
+ m->Process = NIL_RTPROCESS;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Checks if the service process is still running.
+ *
+ * @returns true if running, false if not.
+ */
+bool NetworkServiceRunner::isRunning()
+{
+ RTPROCESS Process = m->Process;
+ if (Process != NIL_RTPROCESS)
+ {
+ RTPROCSTATUS ExitStatus;
+ int rc = RTProcWait(Process, RTPROCWAIT_FLAGS_NOBLOCK, &ExitStatus);
+ if (rc == VERR_PROCESS_RUNNING)
+ return true;
+ LogRel(("NetworkServiceRunning: %s (pid %RTproc) stopped: iStatus=%u enmReason=%d\n",
+ m->pszProcName, m->Process, ExitStatus.iStatus, ExitStatus.enmReason));
+ m->Process = NIL_RTPROCESS;
+ }
+ return false;
+}
+
+
+/**
+ * Gets the process ID of a running service, NIL_PROCESS if not running.
+ */
+RTPROCESS NetworkServiceRunner::getPid() const
+{
+ return m->Process;
+}
+
diff --git a/src/VBox/Main/src-server/ParallelPortImpl.cpp b/src/VBox/Main/src-server/ParallelPortImpl.cpp
new file mode 100644
index 00000000..35d65418
--- /dev/null
+++ b/src/VBox/Main/src-server/ParallelPortImpl.cpp
@@ -0,0 +1,572 @@
+/* $Id: ParallelPortImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_PARALLELPORT
+#include "ParallelPortImpl.h"
+#include "MachineImpl.h"
+#include "VirtualBoxImpl.h"
+
+#include <iprt/string.h>
+#include <iprt/cpp/utils.h>
+
+#include <VBox/settings.h>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ParallelPort private data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct ParallelPort::Data
+{
+ Data()
+ : fModified(false),
+ pMachine(NULL)
+ { }
+
+ bool fModified;
+
+ Machine * const pMachine;
+ const ComObjPtr<ParallelPort> pPeer;
+
+ Backupable<settings::ParallelPort> bd;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+DEFINE_EMPTY_CTOR_DTOR(ParallelPort)
+
+HRESULT ParallelPort::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void ParallelPort::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the Parallel Port object.
+ *
+ * @param aParent Handle of the parent object.
+ * @param aSlot Slotnumber this parallel port is plugged into.
+ */
+HRESULT ParallelPort::init(Machine *aParent, ULONG aSlot)
+{
+ LogFlowThisFunc(("aParent=%p, aSlot=%d\n", aParent, aSlot));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data;
+
+ unconst(m->pMachine) = aParent;
+ /* m->pPeer is left null */
+
+ m->bd.allocate();
+
+ /* initialize data */
+ m->bd->ulSlot = aSlot;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the Parallel Port object given another serial port object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT ParallelPort::init(Machine *aParent, ParallelPort *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data;
+
+ unconst(m->pMachine) = aParent;
+ unconst(m->pPeer) = aThat;
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.share(aThat->m->bd);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the guest object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT ParallelPort::initCopy(Machine *aParent, ParallelPort *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data;
+
+ unconst(m->pMachine) = aParent;
+ /* m->pPeer is left null */
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.attachCopy(aThat->m->bd);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void ParallelPort::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m->bd.free();
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pMachine) = NULL;
+
+ delete m;
+ m = NULL;
+}
+
+// IParallelPort properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT ParallelPort::getEnabled(BOOL *aEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aEnabled = m->bd->fEnabled;
+
+ return S_OK;
+}
+
+HRESULT ParallelPort::setEnabled(BOOL aEnabled)
+{
+ LogFlowThisFunc(("aEnabled=%RTbool\n", aEnabled));
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->fEnabled != RT_BOOL(aEnabled))
+ {
+ m->bd.backup();
+ m->bd->fEnabled = RT_BOOL(aEnabled);
+
+ m->fModified = true;
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_ParallelPorts);
+ mlock.release();
+
+ m->pMachine->i_onParallelPortChange(this);
+ }
+
+ return S_OK;
+}
+
+HRESULT ParallelPort::getSlot(ULONG *aSlot)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aSlot = m->bd->ulSlot;
+
+ return S_OK;
+}
+
+HRESULT ParallelPort::getIRQ(ULONG *aIRQ)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aIRQ = m->bd->ulIRQ;
+
+ return S_OK;
+}
+
+HRESULT ParallelPort::setIRQ(ULONG aIRQ)
+{
+ /* check IRQ limits
+ * (when changing this, make sure it corresponds to XML schema */
+ if (aIRQ > 255)
+ return setError(E_INVALIDARG,
+ tr("Invalid IRQ number of the parallel port %d: %lu (must be in range [0, %lu])"),
+ m->bd->ulSlot, aIRQ, 255);
+
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->ulIRQ != aIRQ)
+ {
+ m->bd.backup();
+ m->bd->ulIRQ = aIRQ;
+
+ m->fModified = true;
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_ParallelPorts);
+ mlock.release();
+
+ m->pMachine->i_onParallelPortChange(this);
+ }
+
+ return S_OK;
+}
+
+HRESULT ParallelPort::getIOBase(ULONG *aIOBase)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aIOBase = m->bd->ulIOBase;
+
+ return S_OK;
+}
+
+HRESULT ParallelPort::setIOBase(ULONG aIOBase)
+{
+ /* check IOBase limits
+ * (when changing this, make sure it corresponds to XML schema */
+ if (aIOBase > 0xFFFF)
+ return setError(E_INVALIDARG,
+ tr("Invalid I/O port base address of the parallel port %d: %lu (must be in range [0, 0x%X])"),
+ m->bd->ulSlot, aIOBase, 0, 0xFFFF);
+
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->ulIOBase != aIOBase)
+ {
+ m->bd.backup();
+ m->bd->ulIOBase = aIOBase;
+
+ m->fModified = true;
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_ParallelPorts);
+ mlock.release();
+
+ m->pMachine->i_onParallelPortChange(this);
+ }
+
+ return S_OK;
+}
+
+
+HRESULT ParallelPort::getPath(com::Utf8Str &aPath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aPath = m->bd->strPath;
+ return S_OK;
+}
+
+
+HRESULT ParallelPort::setPath(const com::Utf8Str &aPath)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aPath != m->bd->strPath)
+ {
+ m->bd.backup();
+ m->bd->strPath = aPath;
+
+ m->fModified = true;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_ParallelPorts);
+ mlock.release();
+
+ return m->pMachine->i_onParallelPortChange(this);
+ }
+
+ return S_OK;
+}
+
+// public methods only for internal purposes
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Loads settings from the given port node.
+ * May be called once right after this object creation.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT ParallelPort::i_loadSettings(const settings::ParallelPort &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // simply copy
+ *m->bd.data() = data;
+
+ return S_OK;
+}
+
+/**
+ * Saves settings to the given port node.
+ *
+ * Note that the given Port node is completely empty on input.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT ParallelPort::i_saveSettings(settings::ParallelPort &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // simply copy
+ data = *m->bd.data();
+
+ return S_OK;
+}
+
+/**
+ * Returns true if any setter method has modified settings of this instance.
+ * @return
+ */
+bool ParallelPort::i_isModified()
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return m->fModified;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+void ParallelPort::i_rollback()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.rollback();
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object (also
+ * for writing) if there is one.
+ */
+void ParallelPort::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (m->pPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ {
+ m->bd.commit();
+ if (m->pPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ m->pPeer->m->bd.attach(m->bd);
+ }
+ }
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ */
+void ParallelPort::i_copyFrom(ParallelPort *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ m->bd.assignCopy(aThat->m->bd);
+}
+
+/**
+ * Applies the defaults for this parallel port.
+ *
+ * @note This method currently assumes that the object is in the state after
+ * calling init(), it does not set defaults from an arbitrary state.
+ */
+void ParallelPort::i_applyDefaults()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Set some more defaults based on the slot. */
+ switch (m->bd->ulSlot)
+ {
+ case 0:
+ {
+ m->bd->ulIOBase = 0x378;
+ m->bd->ulIRQ = 7;
+ break;
+ }
+ case 1:
+ {
+ m->bd->ulIOBase = 0x278;
+ m->bd->ulIRQ = 5;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Parallel port slot %u exceeds limit\n", m->bd->ulSlot));
+ break;
+ }
+}
+
+bool ParallelPort::i_hasDefaults()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), true);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!m->bd->fEnabled)
+ {
+ /* Could be default, check the IO base and IRQ. */
+ switch (m->bd->ulSlot)
+ {
+ case 0:
+ if (m->bd->ulIOBase == 0x378 && m->bd->ulIRQ == 7)
+ return true;
+ break;
+ case 1:
+ if (m->bd->ulIOBase == 0x278 && m->bd->ulIRQ == 5)
+ return true;
+ break;
+ default:
+ AssertMsgFailed(("Parallel port slot %u exceeds limit\n", m->bd->ulSlot));
+ break;
+ }
+
+ /* Detect old-style defaults (0x378, irq 4) in any slot, they are still
+ * in place for many VMs created by old VirtualBox versions. */
+ if (m->bd->ulIOBase == 0x378 && m->bd->ulIRQ == 4)
+ return true;
+ }
+
+ return false;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/Performance.cpp b/src/VBox/Main/src-server/Performance.cpp
new file mode 100644
index 00000000..2ab42815
--- /dev/null
+++ b/src/VBox/Main/src-server/Performance.cpp
@@ -0,0 +1,1511 @@
+/* $Id: Performance.cpp $ */
+/** @file
+ * VBox Performance Classes implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**
+ * @todo list:
+ *
+ * 1) Detection of erroneous metric names
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_PERFORMANCECOLLECTOR
+#ifndef VBOX_COLLECTOR_TEST_CASE
+# include "VirtualBoxImpl.h"
+# include "MachineImpl.h"
+# include "MediumImpl.h"
+# include "AutoCaller.h"
+#endif
+#include "Performance.h"
+#include "HostNetworkInterfaceImpl.h"
+#include "netif.h"
+
+#include <VBox/com/array.h>
+#include <VBox/com/ptr.h>
+#include <VBox/com/string.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/mem.h>
+#include <iprt/cpuset.h>
+
+#include <algorithm>
+
+#include "LoggingNew.h"
+
+using namespace pm;
+
+// Stubs for non-pure virtual methods
+
+int CollectorHAL::getHostCpuLoad(ULONG * /* user */, ULONG * /* kernel */, ULONG * /* idle */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getProcessCpuLoad(RTPROCESS /* process */, ULONG * /* user */, ULONG * /* kernel */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getRawHostCpuLoad(uint64_t * /* user */, uint64_t * /* kernel */, uint64_t * /* idle */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getRawHostNetworkLoad(const char * /* name */, uint64_t * /* rx */, uint64_t * /* tx */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getRawHostDiskLoad(const char * /* name */, uint64_t * /* disk_ms */, uint64_t * /* total_ms */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getRawProcessCpuLoad(RTPROCESS /* process */, uint64_t * /* user */,
+ uint64_t * /* kernel */, uint64_t * /* total */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getHostMemoryUsage(ULONG * /* total */, ULONG * /* used */, ULONG * /* available */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getHostFilesystemUsage(const char * /* name */, ULONG * /* total */, ULONG * /* used */,
+ ULONG * /* available */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getHostDiskSize(const char * /* name */, uint64_t * /* size */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getProcessMemoryUsage(RTPROCESS /* process */, ULONG * /* used */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorHAL::getDiskListByFs(const char * /* name */, DiskList& /* listUsage */, DiskList& /* listLoad */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/* Generic implementations */
+
+int CollectorHAL::getHostCpuMHz(ULONG *mhz)
+{
+ unsigned cCpus = 0;
+ uint64_t u64TotalMHz = 0;
+ RTCPUSET OnlineSet;
+ RTMpGetOnlineSet(&OnlineSet);
+ for (int iCpu = 0; iCpu < RTCPUSET_MAX_CPUS; iCpu++)
+ {
+ Log7Func(("{%p}: Checking if CPU %d is member of online set...\n", this, (int)iCpu));
+ if (RTCpuSetIsMemberByIndex(&OnlineSet, iCpu))
+ {
+ Log7Func(("{%p}: Getting frequency for CPU %d...\n", this, (int)iCpu));
+ uint32_t uMHz = RTMpGetCurFrequency(RTMpCpuIdFromSetIndex(iCpu));
+ if (uMHz != 0)
+ {
+ Log7Func(("{%p}: CPU %d %u MHz\n", this, (int)iCpu, uMHz));
+ u64TotalMHz += uMHz;
+ cCpus++;
+ }
+ }
+ }
+
+ if (cCpus)
+ {
+ *mhz = (ULONG)(u64TotalMHz / cCpus);
+ return VINF_SUCCESS;
+ }
+
+ /* This is always the case on darwin, so don't assert there. */
+#ifndef RT_OS_DARWIN
+ AssertFailed();
+#endif
+ *mhz = 0;
+ return VERR_NOT_IMPLEMENTED;
+}
+
+#ifndef VBOX_COLLECTOR_TEST_CASE
+
+CollectorGuestQueue::CollectorGuestQueue()
+{
+ mEvent = NIL_RTSEMEVENT;
+ RTSemEventCreate(&mEvent);
+}
+
+CollectorGuestQueue::~CollectorGuestQueue()
+{
+ RTSemEventDestroy(mEvent);
+}
+
+void CollectorGuestQueue::push(CollectorGuestRequest* rq)
+{
+ RTCLock lock(mLockMtx);
+
+ mQueue.push(rq);
+ RTSemEventSignal(mEvent);
+}
+
+CollectorGuestRequest* CollectorGuestQueue::pop()
+{
+ int vrc = VINF_SUCCESS;
+ CollectorGuestRequest *rq = NULL;
+
+ do
+ {
+ {
+ RTCLock lock(mLockMtx);
+
+ if (!mQueue.empty())
+ {
+ rq = mQueue.front();
+ mQueue.pop();
+ }
+ }
+
+ if (rq)
+ return rq;
+ vrc = RTSemEventWaitNoResume(mEvent, RT_INDEFINITE_WAIT);
+ } while (RT_SUCCESS(vrc));
+
+ return NULL;
+}
+
+HRESULT CGRQEnable::execute()
+{
+ Assert(mCGuest);
+ return mCGuest->enableInternal(mMask);
+}
+
+void CGRQEnable::debugPrint(void *aObject, const char *aFunction, const char *aText)
+{
+ NOREF(aObject);
+ NOREF(aFunction);
+ NOREF(aText);
+ Log7((LOG_FN_FMT ": {%p}: CGRQEnable(mask=0x%x) %s\n", aObject, aFunction, mMask, aText));
+}
+
+HRESULT CGRQDisable::execute()
+{
+ Assert(mCGuest);
+ return mCGuest->disableInternal(mMask);
+}
+
+void CGRQDisable::debugPrint(void *aObject, const char *aFunction, const char *aText)
+{
+ NOREF(aObject);
+ NOREF(aFunction);
+ NOREF(aText);
+ Log7((LOG_FN_FMT ": {%p}: CGRQDisable(mask=0x%x) %s\n", aObject, aFunction, mMask, aText));
+}
+
+HRESULT CGRQAbort::execute()
+{
+ return E_ABORT;
+}
+
+void CGRQAbort::debugPrint(void *aObject, const char *aFunction, const char *aText)
+{
+ NOREF(aObject);
+ NOREF(aFunction);
+ NOREF(aText);
+ Log7((LOG_FN_FMT ": {%p}: CGRQAbort %s\n", aObject, aFunction, aText));
+}
+
+CollectorGuest::CollectorGuest(Machine *machine, RTPROCESS process) :
+ mUnregistered(false), mEnabled(false), mValid(false), mMachine(machine), mProcess(process),
+ mCpuUser(0), mCpuKernel(0), mCpuIdle(0),
+ mMemTotal(0), mMemFree(0), mMemBalloon(0), mMemShared(0), mMemCache(0), mPageTotal(0),
+ mAllocVMM(0), mFreeVMM(0), mBalloonedVMM(0), mSharedVMM(0), mVmNetRx(0), mVmNetTx(0)
+{
+ Assert(mMachine);
+ /* cannot use ComObjPtr<Machine> in Performance.h, do it manually */
+ mMachine->AddRef();
+}
+
+CollectorGuest::~CollectorGuest()
+{
+ /* cannot use ComObjPtr<Machine> in Performance.h, do it manually */
+ mMachine->Release();
+ // Assert(!cEnabled); why?
+}
+
+HRESULT CollectorGuest::enableVMMStats(bool mCollectVMMStats)
+{
+ HRESULT hrc = S_OK;
+
+ if (mGuest)
+ {
+ /** @todo replace this with a direct call to mGuest in trunk! */
+ AutoCaller autoCaller(mMachine);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ ComPtr<IInternalSessionControl> directControl;
+
+ hrc = mMachine->i_getDirectControl(&directControl);
+ if (hrc != S_OK)
+ return hrc;
+
+ /* enable statistics collection; this is a remote call (!) */
+ hrc = directControl->EnableVMMStatistics(mCollectVMMStats);
+ Log7Func(("{%p}: %sable VMM stats (%s)\n",
+ this, mCollectVMMStats ? "En" : "Dis", SUCCEEDED(hrc) ? "success" : "failed"));
+ }
+
+ return hrc;
+}
+
+HRESULT CollectorGuest::enable(ULONG mask)
+{
+ return enqueueRequest(new CGRQEnable(mask));
+}
+
+HRESULT CollectorGuest::disable(ULONG mask)
+{
+ return enqueueRequest(new CGRQDisable(mask));
+}
+
+HRESULT CollectorGuest::enableInternal(ULONG mask)
+{
+ HRESULT ret = S_OK;
+
+ if ((mEnabled & mask) == mask)
+ return E_UNEXPECTED;
+
+ if (!mEnabled)
+ {
+ /* Must make sure that the machine object does not get uninitialized
+ * in the middle of enabling this collector. Causes timing-related
+ * behavior otherwise, which we don't want. In particular the
+ * GetRemoteConsole call below can hang if the VM didn't completely
+ * terminate (the VM processes stop processing events shortly before
+ * closing the session). This avoids the hang. */
+ AutoCaller autoCaller(mMachine);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ mMachineName = mMachine->i_getName();
+
+ ComPtr<IInternalSessionControl> directControl;
+
+ ret = mMachine->i_getDirectControl(&directControl);
+ if (ret != S_OK)
+ return ret;
+
+ /* get the associated console; this is a remote call (!) */
+ ret = directControl->COMGETTER(RemoteConsole)(mConsole.asOutParam());
+ if (ret != S_OK)
+ return ret;
+
+ ret = mConsole->COMGETTER(Guest)(mGuest.asOutParam());
+ if (ret == S_OK)
+ {
+ ret = mGuest->COMSETTER(StatisticsUpdateInterval)(1 /* 1 sec */);
+ Log7Func(("{%p}: Set guest statistics update interval to 1 sec (%s)\n",
+ this, SUCCEEDED(ret) ? "success" : "failed"));
+ }
+ }
+ if ((mask & VMSTATS_VMM_RAM) == VMSTATS_VMM_RAM)
+ enableVMMStats(true);
+ mEnabled |= mask;
+
+ return ret;
+}
+
+HRESULT CollectorGuest::disableInternal(ULONG mask)
+{
+ if (!(mEnabled & mask))
+ return E_UNEXPECTED;
+
+ if ((mask & VMSTATS_VMM_RAM) == VMSTATS_VMM_RAM)
+ enableVMMStats(false);
+ mEnabled &= ~mask;
+ if (!mEnabled)
+ {
+ Assert(mGuest && mConsole);
+ HRESULT ret = mGuest->COMSETTER(StatisticsUpdateInterval)(0 /* off */);
+ NOREF(ret);
+ Log7Func(("{%p}: Set guest statistics update interval to 0 sec (%s)\n",
+ this, SUCCEEDED(ret) ? "success" : "failed"));
+ invalidate(VMSTATS_ALL);
+ }
+
+ return S_OK;
+}
+
+HRESULT CollectorGuest::enqueueRequest(CollectorGuestRequest *aRequest)
+{
+ if (mManager)
+ {
+ aRequest->setGuest(this);
+ return mManager->enqueueRequest(aRequest);
+ }
+
+ Log7Func(("{%p}: Attempted enqueue guest request when mManager is null\n", this));
+ return E_POINTER;
+}
+
+void CollectorGuest::updateStats(ULONG aValidStats, ULONG aCpuUser,
+ ULONG aCpuKernel, ULONG aCpuIdle,
+ ULONG aMemTotal, ULONG aMemFree,
+ ULONG aMemBalloon, ULONG aMemShared,
+ ULONG aMemCache, ULONG aPageTotal,
+ ULONG aAllocVMM, ULONG aFreeVMM,
+ ULONG aBalloonedVMM, ULONG aSharedVMM,
+ ULONG aVmNetRx, ULONG aVmNetTx)
+{
+ if ((aValidStats & VMSTATS_GUEST_CPULOAD) == VMSTATS_GUEST_CPULOAD)
+ {
+ mCpuUser = aCpuUser;
+ mCpuKernel = aCpuKernel,
+ mCpuIdle = aCpuIdle;
+ }
+ if ((aValidStats & VMSTATS_GUEST_RAMUSAGE) == VMSTATS_GUEST_RAMUSAGE)
+ {
+ mMemTotal = aMemTotal;
+ mMemFree = aMemFree;
+ mMemBalloon = aMemBalloon;
+ mMemShared = aMemShared;
+ mMemCache = aMemCache;
+ mPageTotal = aPageTotal;
+ }
+ if ((aValidStats & VMSTATS_VMM_RAM) == VMSTATS_VMM_RAM)
+ {
+ mAllocVMM = aAllocVMM;
+ mFreeVMM = aFreeVMM;
+ mBalloonedVMM = aBalloonedVMM;
+ mSharedVMM = aSharedVMM;
+ }
+ if ((aValidStats & VMSTATS_NET_RATE) == VMSTATS_NET_RATE)
+ {
+ mVmNetRx = aVmNetRx;
+ mVmNetTx = aVmNetTx;
+ }
+ mValid = aValidStats;
+}
+
+CollectorGuestManager::CollectorGuestManager()
+ : mVMMStatsProvider(NULL), mGuestBeingCalled(NULL)
+{
+ int vrc = RTThreadCreate(&mThread, CollectorGuestManager::requestProcessingThread,
+ this, 0, RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE,
+ "CGMgr");
+ NOREF(vrc);
+ Log7Func(("{%p}: RTThreadCreate returned %Rrc (mThread=%p)\n", this, vrc, mThread));
+}
+
+CollectorGuestManager::~CollectorGuestManager()
+{
+ Assert(mGuests.size() == 0);
+ int rcThread = 0;
+ HRESULT hrc = enqueueRequest(new CGRQAbort());
+ if (SUCCEEDED(hrc))
+ {
+ /* We wait only if we were able to put the abort request to a queue */
+ Log7Func(("{%p}: Waiting for CGM request processing thread to stop...\n", this));
+ int vrc = RTThreadWait(mThread, 1000 /* 1 sec */, &rcThread);
+ Log7Func(("{%p}: RTThreadWait returned %Rrc (thread exit code: %Rrc)\n", this, vrc, rcThread));
+ RT_NOREF(vrc);
+ }
+}
+
+void CollectorGuestManager::registerGuest(CollectorGuest* pGuest)
+{
+ pGuest->setManager(this);
+ mGuests.push_back(pGuest);
+ /*
+ * If no VMM stats provider was elected previously than this is our
+ * candidate.
+ */
+ if (!mVMMStatsProvider)
+ mVMMStatsProvider = pGuest;
+ Log7Func(("{%p}: Registered guest=%p provider=%p\n", this, pGuest, mVMMStatsProvider));
+}
+
+void CollectorGuestManager::unregisterGuest(CollectorGuest* pGuest)
+{
+ Log7Func(("{%p}: About to unregister guest=%p provider=%p\n", this, pGuest, mVMMStatsProvider));
+ //mGuests.remove(pGuest); => destroyUnregistered()
+ pGuest->unregister();
+ if (pGuest == mVMMStatsProvider)
+ {
+ /* This was our VMM stats provider, it is time to re-elect */
+ CollectorGuestList::iterator it;
+ /* Assume that nobody can provide VMM stats */
+ mVMMStatsProvider = NULL;
+
+ for (it = mGuests.begin(); it != mGuests.end(); ++it)
+ {
+ /* Skip unregistered as they are about to be destroyed */
+ if ((*it)->isUnregistered())
+ continue;
+
+ if ((*it)->isEnabled())
+ {
+ /* Found the guest already collecting stats, elect it */
+ mVMMStatsProvider = *it;
+ HRESULT hrc = mVMMStatsProvider->enqueueRequest(new CGRQEnable(VMSTATS_VMM_RAM));
+ if (FAILED(hrc))
+ {
+ /* This is not a good candidate -- try to find another */
+ mVMMStatsProvider = NULL;
+ continue;
+ }
+ break;
+ }
+ }
+ if (!mVMMStatsProvider)
+ {
+ /* If nobody collects stats, take the first registered */
+ for (it = mGuests.begin(); it != mGuests.end(); ++it)
+ {
+ /* Skip unregistered as they are about to be destroyed */
+ if ((*it)->isUnregistered())
+ continue;
+
+ mVMMStatsProvider = *it;
+ //mVMMStatsProvider->enable(VMSTATS_VMM_RAM);
+ HRESULT hrc = mVMMStatsProvider->enqueueRequest(new CGRQEnable(VMSTATS_VMM_RAM));
+ if (SUCCEEDED(hrc))
+ break;
+ /* This was not a good candidate -- try to find another */
+ mVMMStatsProvider = NULL;
+ }
+ }
+ }
+ Log7Func(("[%p}: LEAVE new provider=%p\n", this, mVMMStatsProvider));
+}
+
+void CollectorGuestManager::destroyUnregistered()
+{
+ CollectorGuestList::iterator it;
+
+ for (it = mGuests.begin(); it != mGuests.end();)
+ if ((*it)->isUnregistered())
+ {
+ delete *it;
+ it = mGuests.erase(it);
+ Log7Func(("{%p}: Number of guests after erasing unregistered is %d\n",
+ this, mGuests.size()));
+ }
+ else
+ ++it;
+}
+
+HRESULT CollectorGuestManager::enqueueRequest(CollectorGuestRequest *aRequest)
+{
+#ifdef DEBUG
+ aRequest->debugPrint(this, __PRETTY_FUNCTION__, "added to CGM queue");
+#endif /* DEBUG */
+ /*
+ * It is very unlikely that we will get high frequency calls to configure
+ * guest metrics collection, so we rely on this fact to detect blocked
+ * guests. If the guest has not finished processing the previous request
+ * after half a second we consider it blocked.
+ */
+ if (aRequest->getGuest() && aRequest->getGuest() == mGuestBeingCalled)
+ {
+ /*
+ * Before we can declare a guest blocked we need to wait for a while
+ * and then check again as it may never had a chance to process
+ * the previous request. Half a second is an eternity for processes
+ * and is barely noticable by humans.
+ */
+ Log7Func(("{%p}: Suspecting %s is stalled. Waiting for .5 sec...\n",
+ this, aRequest->getGuest()->getVMName().c_str()));
+ RTThreadSleep(500 /* ms */);
+ if (aRequest->getGuest() == mGuestBeingCalled) {
+ Log7Func(("{%p}: Request processing stalled for %s\n",
+ this, aRequest->getGuest()->getVMName().c_str()));
+ /* Request execution got stalled for this guest -- report an error */
+ return E_FAIL;
+ }
+ }
+ mQueue.push(aRequest);
+ return S_OK;
+}
+
+/* static */
+DECLCALLBACK(int) CollectorGuestManager::requestProcessingThread(RTTHREAD /* aThread */, void *pvUser)
+{
+ CollectorGuestRequest *pReq;
+ CollectorGuestManager *mgr = static_cast<CollectorGuestManager*>(pvUser);
+
+ HRESULT rc = S_OK;
+
+ Log7Func(("{%p}: Starting request processing loop...\n", mgr));
+ while ((pReq = mgr->mQueue.pop()) != NULL)
+ {
+#ifdef DEBUG
+ pReq->debugPrint(mgr, __PRETTY_FUNCTION__, "is being executed...");
+#endif /* DEBUG */
+ mgr->mGuestBeingCalled = pReq->getGuest();
+ rc = pReq->execute();
+ mgr->mGuestBeingCalled = NULL;
+ delete pReq;
+ if (rc == E_ABORT)
+ break;
+ if (FAILED(rc))
+ Log7Func(("{%p}: request::execute returned %u\n", mgr, rc));
+ }
+ Log7Func(("{%p}: Exiting request processing loop... rc=%u\n", mgr, rc));
+
+ return VINF_SUCCESS;
+}
+
+
+#endif /* !VBOX_COLLECTOR_TEST_CASE */
+
+bool BaseMetric::collectorBeat(uint64_t nowAt)
+{
+ if (isEnabled())
+ {
+ if (mLastSampleTaken == 0)
+ {
+ mLastSampleTaken = nowAt;
+ Log4Func(("{%p}: Collecting %s for obj(%p)...\n",
+ this, getName(), (void *)mObject));
+ return true;
+ }
+ /*
+ * We use low resolution timers which may fire just a little bit early.
+ * We compensate for that by jumping into the future by several
+ * milliseconds (see @bugref{6345}).
+ */
+ if (nowAt - mLastSampleTaken + PM_SAMPLER_PRECISION_MS >= mPeriod * 1000)
+ {
+ /*
+ * We don't want the beat to drift. This is why the timestamp of
+ * the last taken sample is not the actual time but the time we
+ * should have taken the measurement at.
+ */
+ mLastSampleTaken += mPeriod * 1000;
+ Log4Func(("{%p}: Collecting %s for obj(%p)...\n",
+ this, getName(), (void *)mObject));
+ return true;
+ }
+ Log4Func(("{%p}: Enabled but too early to collect %s for obj(%p)\n",
+ this, getName(), (void *)mObject));
+ }
+ return false;
+}
+
+void HostCpuLoad::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mUser->init(mLength);
+ mKernel->init(mLength);
+ mIdle->init(mLength);
+}
+
+void HostCpuLoad::collect()
+{
+ ULONG user, kernel, idle;
+ int vrc = mHAL->getHostCpuLoad(&user, &kernel, &idle);
+ if (RT_SUCCESS(vrc))
+ {
+ mUser->put(user);
+ mKernel->put(kernel);
+ mIdle->put(idle);
+ }
+}
+
+void HostCpuLoadRaw::init(ULONG period, ULONG length)
+{
+ HostCpuLoad::init(period, length);
+ mHAL->getRawHostCpuLoad(&mUserPrev, &mKernelPrev, &mIdlePrev);
+}
+
+void HostCpuLoadRaw::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectHostCpuLoad();
+}
+
+void HostCpuLoadRaw::collect()
+{
+ uint64_t user, kernel, idle;
+ uint64_t userDiff, kernelDiff, idleDiff, totalDiff;
+
+ int vrc = mHAL->getRawHostCpuLoad(&user, &kernel, &idle);
+ if (RT_SUCCESS(vrc))
+ {
+ userDiff = user - mUserPrev;
+ kernelDiff = kernel - mKernelPrev;
+ idleDiff = idle - mIdlePrev;
+ totalDiff = userDiff + kernelDiff + idleDiff;
+
+ if (totalDiff == 0)
+ {
+ /* This is only possible if none of counters has changed! */
+ LogFlowThisFunc(("Impossible! User, kernel and idle raw "
+ "counters has not changed since last sample.\n" ));
+ mUser->put(0);
+ mKernel->put(0);
+ mIdle->put(0);
+ }
+ else
+ {
+ mUser->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * userDiff / totalDiff));
+ mKernel->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * kernelDiff / totalDiff));
+ mIdle->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * idleDiff / totalDiff));
+ }
+
+ mUserPrev = user;
+ mKernelPrev = kernel;
+ mIdlePrev = idle;
+ }
+}
+
+#ifndef VBOX_COLLECTOR_TEST_CASE
+static bool getLinkSpeed(const char *szShortName, uint32_t *pSpeed)
+{
+# ifdef VBOX_WITH_HOSTNETIF_API
+ NETIFSTATUS enmState = NETIF_S_UNKNOWN;
+ int vrc = NetIfGetState(szShortName, &enmState);
+ if (RT_FAILURE(vrc))
+ return false;
+ if (enmState != NETIF_S_UP)
+ *pSpeed = 0;
+ else
+ {
+ vrc = NetIfGetLinkSpeed(szShortName, pSpeed);
+ if (RT_FAILURE(vrc))
+ return false;
+ }
+ return true;
+# else /* !VBOX_WITH_HOSTNETIF_API */
+ RT_NOREF(szShortName, pSpeed);
+ return false;
+# endif /* VBOX_WITH_HOSTNETIF_API */
+}
+
+void HostNetworkSpeed::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mLinkSpeed->init(length);
+ /*
+ * Retrieve the link speed now as it may be wrong if the metric was
+ * registered at boot (see @bugref{6613}).
+ */
+ getLinkSpeed(mShortName.c_str(), &mSpeed);
+}
+
+void HostNetworkLoadRaw::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mRx->init(mLength);
+ mTx->init(mLength);
+ /*
+ * Retrieve the link speed now as it may be wrong if the metric was
+ * registered at boot (see @bugref{6613}).
+ */
+ uint32_t uSpeedMbit = 65535;
+ if (getLinkSpeed(mShortName.c_str(), &uSpeedMbit))
+ mSpeed = (uint64_t)uSpeedMbit * (1000000/8); /* Convert to bytes/sec */
+ /*int vrc =*/ mHAL->getRawHostNetworkLoad(mShortName.c_str(), &mRxPrev, &mTxPrev);
+ //AssertRC(vrc);
+}
+
+void HostNetworkLoadRaw::preCollect(CollectorHints& /* hints */, uint64_t /* iTick */)
+{
+ if (RT_FAILURE(mRc))
+ {
+ ComPtr<IHostNetworkInterface> networkInterface;
+ ComPtr<IHost> host = getObject();
+ HRESULT hrc = host->FindHostNetworkInterfaceByName(com::Bstr(mInterfaceName).raw(), networkInterface.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ static uint32_t s_tsLogRelLast;
+ uint32_t tsNow = RTTimeProgramSecTS();
+ if ( tsNow < RT_SEC_1HOUR
+ || (tsNow - s_tsLogRelLast >= 60))
+ {
+ s_tsLogRelLast = tsNow;
+ LogRel(("Failed to collect network metrics for %s: %Rrc (%d). Max one msg/min.\n", mInterfaceName.c_str(), mRc, mRc));
+ }
+ mRc = VINF_SUCCESS;
+ }
+ }
+}
+
+void HostNetworkLoadRaw::collect()
+{
+ uint64_t rx = mRxPrev;
+ uint64_t tx = mTxPrev;
+
+ if (RT_UNLIKELY(mSpeed * getPeriod() == 0))
+ {
+ LogFlowThisFunc(("Check cable for %s! speed=%llu period=%d.\n", mShortName.c_str(), mSpeed, getPeriod()));
+ /* We do not collect host network metrics for unplugged interfaces! */
+ return;
+ }
+ mRc = mHAL->getRawHostNetworkLoad(mShortName.c_str(), &rx, &tx);
+ if (RT_SUCCESS(mRc))
+ {
+ uint64_t rxDiff = rx - mRxPrev;
+ uint64_t txDiff = tx - mTxPrev;
+
+ mRx->put((ULONG)(PM_NETWORK_LOAD_MULTIPLIER * rxDiff / (mSpeed * getPeriod())));
+ mTx->put((ULONG)(PM_NETWORK_LOAD_MULTIPLIER * txDiff / (mSpeed * getPeriod())));
+
+ mRxPrev = rx;
+ mTxPrev = tx;
+ }
+ else
+ LogFlowThisFunc(("Failed to collect data: %Rrc (%d)."
+ " Will update the list of interfaces...\n", mRc,mRc));
+}
+#endif /* !VBOX_COLLECTOR_TEST_CASE */
+
+void HostDiskLoadRaw::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mUtil->init(mLength);
+ int vrc = mHAL->getRawHostDiskLoad(mDiskName.c_str(), &mDiskPrev, &mTotalPrev);
+ AssertRC(vrc);
+}
+
+void HostDiskLoadRaw::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectHostCpuLoad();
+}
+
+void HostDiskLoadRaw::collect()
+{
+ uint64_t disk, total;
+
+ int vrc = mHAL->getRawHostDiskLoad(mDiskName.c_str(), &disk, &total);
+ if (RT_SUCCESS(vrc))
+ {
+ uint64_t diskDiff = disk - mDiskPrev;
+ uint64_t totalDiff = total - mTotalPrev;
+
+ if (RT_UNLIKELY(totalDiff == 0))
+ {
+ Assert(totalDiff);
+ LogFlowThisFunc(("Improbable! Less than millisecond passed! Disk=%s\n", mDiskName.c_str()));
+ mUtil->put(0);
+ }
+ else if (diskDiff > totalDiff)
+ {
+ /*
+ * It is possible that the disk spent more time than CPU because
+ * CPU measurements are taken during the pre-collect phase. We try
+ * to compensate for than by adding the extra to the next round of
+ * measurements.
+ */
+ mUtil->put(PM_NETWORK_LOAD_MULTIPLIER);
+ Assert((diskDiff - totalDiff) < mPeriod * 1000);
+ if ((diskDiff - totalDiff) > mPeriod * 1000)
+ {
+ LogRel(("Disk utilization time exceeds CPU time by more"
+ " than the collection period (%llu ms)\n", diskDiff - totalDiff));
+ }
+ else
+ {
+ disk = mDiskPrev + totalDiff;
+ LogFlowThisFunc(("Moved %u milliseconds to the next period.\n", (unsigned)(diskDiff - totalDiff)));
+ }
+ }
+ else
+ {
+ mUtil->put((ULONG)(PM_NETWORK_LOAD_MULTIPLIER * diskDiff / totalDiff));
+ }
+
+ mDiskPrev = disk;
+ mTotalPrev = total;
+ }
+ else
+ LogFlowThisFunc(("Failed to collect data: %Rrc (%d)\n", vrc, vrc));
+}
+
+void HostCpuMhz::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mMHz->init(mLength);
+}
+
+void HostCpuMhz::collect()
+{
+ ULONG mhz;
+ int vrc = mHAL->getHostCpuMHz(&mhz);
+ if (RT_SUCCESS(vrc))
+ mMHz->put(mhz);
+}
+
+void HostRamUsage::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mTotal->init(mLength);
+ mUsed->init(mLength);
+ mAvailable->init(mLength);
+}
+
+void HostRamUsage::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectHostRamUsage();
+}
+
+void HostRamUsage::collect()
+{
+ ULONG total, used, available;
+ int vrc = mHAL->getHostMemoryUsage(&total, &used, &available);
+ if (RT_SUCCESS(vrc))
+ {
+ mTotal->put(total);
+ mUsed->put(used);
+ mAvailable->put(available);
+ }
+}
+
+void HostFilesystemUsage::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mTotal->init(mLength);
+ mUsed->init(mLength);
+ mAvailable->init(mLength);
+}
+
+void HostFilesystemUsage::preCollect(CollectorHints& /* hints */, uint64_t /* iTick */)
+{
+}
+
+void HostFilesystemUsage::collect()
+{
+ ULONG total, used, available;
+ int vrc = mHAL->getHostFilesystemUsage(mFsName.c_str(), &total, &used, &available);
+ if (RT_SUCCESS(vrc))
+ {
+ mTotal->put(total);
+ mUsed->put(used);
+ mAvailable->put(available);
+ }
+}
+
+void HostDiskUsage::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mTotal->init(mLength);
+}
+
+void HostDiskUsage::preCollect(CollectorHints& /* hints */, uint64_t /* iTick */)
+{
+}
+
+void HostDiskUsage::collect()
+{
+ uint64_t total;
+ int vrc = mHAL->getHostDiskSize(mDiskName.c_str(), &total);
+ if (RT_SUCCESS(vrc))
+ mTotal->put((ULONG)(total / _1M));
+}
+
+#ifndef VBOX_COLLECTOR_TEST_CASE
+
+void HostRamVmm::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mAllocVMM->init(mLength);
+ mFreeVMM->init(mLength);
+ mBalloonVMM->init(mLength);
+ mSharedVMM->init(mLength);
+}
+
+HRESULT HostRamVmm::enable()
+{
+ HRESULT hrc = S_OK;
+ CollectorGuest *provider = mCollectorGuestManager->getVMMStatsProvider();
+ if (provider)
+ hrc = provider->enable(VMSTATS_VMM_RAM);
+ BaseMetric::enable();
+ return hrc;
+}
+
+HRESULT HostRamVmm::disable()
+{
+ HRESULT rc = S_OK;
+ BaseMetric::disable();
+ CollectorGuest *provider = mCollectorGuestManager->getVMMStatsProvider();
+ if (provider)
+ rc = provider->disable(VMSTATS_VMM_RAM);
+ return rc;
+}
+
+void HostRamVmm::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectHostRamVmm();
+}
+
+void HostRamVmm::collect()
+{
+ CollectorGuest *provider = mCollectorGuestManager->getVMMStatsProvider();
+ if (provider)
+ {
+ Log7Func(("{%p}: provider=%p enabled=%RTbool valid=%RTbool...\n",
+ this, provider, provider->isEnabled(), provider->isValid(VMSTATS_VMM_RAM) ));
+ if (provider->isValid(VMSTATS_VMM_RAM))
+ {
+ /* Provider is ready, get updated stats */
+ mAllocCurrent = provider->getAllocVMM();
+ mFreeCurrent = provider->getFreeVMM();
+ mBalloonedCurrent = provider->getBalloonedVMM();
+ mSharedCurrent = provider->getSharedVMM();
+ provider->invalidate(VMSTATS_VMM_RAM);
+ }
+ /*
+ * Note that if there are no new values from the provider we will use
+ * the ones most recently provided instead of zeros, which is probably
+ * a desirable behavior.
+ */
+ }
+ else
+ {
+ mAllocCurrent = 0;
+ mFreeCurrent = 0;
+ mBalloonedCurrent = 0;
+ mSharedCurrent = 0;
+ }
+ Log7Func(("{%p}: mAllocCurrent=%u mFreeCurrent=%u mBalloonedCurrent=%u mSharedCurrent=%u\n",
+ this, mAllocCurrent, mFreeCurrent, mBalloonedCurrent, mSharedCurrent));
+ mAllocVMM->put(mAllocCurrent);
+ mFreeVMM->put(mFreeCurrent);
+ mBalloonVMM->put(mBalloonedCurrent);
+ mSharedVMM->put(mSharedCurrent);
+}
+
+#endif /* !VBOX_COLLECTOR_TEST_CASE */
+
+
+
+void MachineCpuLoad::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mUser->init(mLength);
+ mKernel->init(mLength);
+}
+
+void MachineCpuLoad::collect()
+{
+ ULONG user, kernel;
+ int vrc = mHAL->getProcessCpuLoad(mProcess, &user, &kernel);
+ if (RT_SUCCESS(vrc))
+ {
+ mUser->put(user);
+ mKernel->put(kernel);
+ }
+}
+
+void MachineCpuLoadRaw::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectProcessCpuLoad(mProcess);
+}
+
+void MachineCpuLoadRaw::collect()
+{
+ uint64_t processUser, processKernel, hostTotal;
+
+ int vrc = mHAL->getRawProcessCpuLoad(mProcess, &processUser, &processKernel, &hostTotal);
+ if (RT_SUCCESS(vrc))
+ {
+ if (hostTotal == mHostTotalPrev)
+ {
+ /* Nearly impossible, but... */
+ mUser->put(0);
+ mKernel->put(0);
+ }
+ else
+ {
+ mUser->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * (processUser - mProcessUserPrev) / (hostTotal - mHostTotalPrev)));
+ mKernel->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * (processKernel - mProcessKernelPrev ) / (hostTotal - mHostTotalPrev)));
+ }
+
+ mHostTotalPrev = hostTotal;
+ mProcessUserPrev = processUser;
+ mProcessKernelPrev = processKernel;
+ }
+}
+
+void MachineRamUsage::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mUsed->init(mLength);
+}
+
+void MachineRamUsage::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectProcessRamUsage(mProcess);
+}
+
+void MachineRamUsage::collect()
+{
+ ULONG used;
+ int vrc = mHAL->getProcessMemoryUsage(mProcess, &used);
+ if (RT_SUCCESS(vrc))
+ mUsed->put(used);
+}
+
+
+#ifndef VBOX_COLLECTOR_TEST_CASE
+
+void MachineDiskUsage::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+ mUsed->init(mLength);
+}
+
+void MachineDiskUsage::preCollect(CollectorHints& /* hints */, uint64_t /* iTick */)
+{
+}
+
+void MachineDiskUsage::collect()
+{
+ ULONG used = 0;
+
+ for (MediaList::iterator it = mDisks.begin(); it != mDisks.end(); ++it)
+ {
+ ComObjPtr<Medium> pMedium = *it;
+
+ /* just in case */
+ AssertContinue(!pMedium.isNull());
+
+ AutoCaller localAutoCaller(pMedium);
+ if (FAILED(localAutoCaller.rc())) continue;
+
+ AutoReadLock local_alock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ used += (ULONG)(pMedium->i_getSize() / _1M);
+ }
+
+ mUsed->put(used);
+}
+
+void MachineNetRate::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+
+ mRx->init(mLength);
+ mTx->init(mLength);
+}
+
+void MachineNetRate::collect()
+{
+ if (mCGuest->isValid(VMSTATS_NET_RATE))
+ {
+ mRx->put(mCGuest->getVmNetRx());
+ mTx->put(mCGuest->getVmNetTx());
+ mCGuest->invalidate(VMSTATS_NET_RATE);
+ }
+}
+
+HRESULT MachineNetRate::enable()
+{
+ HRESULT rc = mCGuest->enable(VMSTATS_NET_RATE);
+ BaseMetric::enable();
+ return rc;
+}
+
+HRESULT MachineNetRate::disable()
+{
+ BaseMetric::disable();
+ return mCGuest->disable(VMSTATS_NET_RATE);
+}
+
+void MachineNetRate::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectGuestStats(mCGuest->getProcess());
+}
+
+void GuestCpuLoad::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+
+ mUser->init(mLength);
+ mKernel->init(mLength);
+ mIdle->init(mLength);
+}
+
+void GuestCpuLoad::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectGuestStats(mCGuest->getProcess());
+}
+
+void GuestCpuLoad::collect()
+{
+ if (mCGuest->isValid(VMSTATS_GUEST_CPULOAD))
+ {
+ mUser->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * mCGuest->getCpuUser()) / 100);
+ mKernel->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * mCGuest->getCpuKernel()) / 100);
+ mIdle->put((ULONG)(PM_CPU_LOAD_MULTIPLIER * mCGuest->getCpuIdle()) / 100);
+ mCGuest->invalidate(VMSTATS_GUEST_CPULOAD);
+ }
+}
+
+HRESULT GuestCpuLoad::enable()
+{
+ HRESULT rc = mCGuest->enable(VMSTATS_GUEST_CPULOAD);
+ BaseMetric::enable();
+ return rc;
+}
+
+HRESULT GuestCpuLoad::disable()
+{
+ BaseMetric::disable();
+ return mCGuest->disable(VMSTATS_GUEST_CPULOAD);
+}
+
+void GuestRamUsage::init(ULONG period, ULONG length)
+{
+ mPeriod = period;
+ mLength = length;
+
+ mTotal->init(mLength);
+ mFree->init(mLength);
+ mBallooned->init(mLength);
+ mShared->init(mLength);
+ mCache->init(mLength);
+ mPagedTotal->init(mLength);
+}
+
+void GuestRamUsage::collect()
+{
+ if (mCGuest->isValid(VMSTATS_GUEST_RAMUSAGE))
+ {
+ mTotal->put(mCGuest->getMemTotal());
+ mFree->put(mCGuest->getMemFree());
+ mBallooned->put(mCGuest->getMemBalloon());
+ mShared->put(mCGuest->getMemShared());
+ mCache->put(mCGuest->getMemCache());
+ mPagedTotal->put(mCGuest->getPageTotal());
+ mCGuest->invalidate(VMSTATS_GUEST_RAMUSAGE);
+ }
+}
+
+HRESULT GuestRamUsage::enable()
+{
+ HRESULT rc = mCGuest->enable(VMSTATS_GUEST_RAMUSAGE);
+ BaseMetric::enable();
+ return rc;
+}
+
+HRESULT GuestRamUsage::disable()
+{
+ BaseMetric::disable();
+ return mCGuest->disable(VMSTATS_GUEST_RAMUSAGE);
+}
+
+void GuestRamUsage::preCollect(CollectorHints& hints, uint64_t /* iTick */)
+{
+ hints.collectGuestStats(mCGuest->getProcess());
+}
+
+#endif /* !VBOX_COLLECTOR_TEST_CASE */
+
+void CircularBuffer::init(ULONG ulLength)
+{
+ if (mData)
+ RTMemFree(mData);
+ mLength = ulLength;
+ if (mLength)
+ mData = (ULONG*)RTMemAllocZ(ulLength * sizeof(ULONG));
+ else
+ mData = NULL;
+ mWrapped = false;
+ mEnd = 0;
+ mSequenceNumber = 0;
+}
+
+ULONG CircularBuffer::length()
+{
+ return mWrapped ? mLength : mEnd;
+}
+
+void CircularBuffer::put(ULONG value)
+{
+ if (mData)
+ {
+ mData[mEnd++] = value;
+ if (mEnd >= mLength)
+ {
+ mEnd = 0;
+ mWrapped = true;
+ }
+ ++mSequenceNumber;
+ }
+}
+
+void CircularBuffer::copyTo(ULONG *data)
+{
+ if (mWrapped)
+ {
+ memcpy(data, mData + mEnd, (mLength - mEnd) * sizeof(ULONG));
+ // Copy the wrapped part
+ if (mEnd)
+ memcpy(data + (mLength - mEnd), mData, mEnd * sizeof(ULONG));
+ }
+ else
+ memcpy(data, mData, mEnd * sizeof(ULONG));
+}
+
+void SubMetric::query(ULONG *data)
+{
+ copyTo(data);
+}
+
+void Metric::query(ULONG **data, ULONG *count, ULONG *sequenceNumber)
+{
+ ULONG length;
+ ULONG *tmpData;
+
+ length = mSubMetric->length();
+ *sequenceNumber = mSubMetric->getSequenceNumber() - length;
+ if (length)
+ {
+ tmpData = (ULONG*)RTMemAlloc(sizeof(*tmpData)*length);
+ mSubMetric->query(tmpData);
+ if (mAggregate)
+ {
+ *count = 1;
+ *data = (ULONG*)RTMemAlloc(sizeof(**data));
+ **data = mAggregate->compute(tmpData, length);
+ RTMemFree(tmpData);
+ }
+ else
+ {
+ *count = length;
+ *data = tmpData;
+ }
+ }
+ else
+ {
+ *count = 0;
+ *data = 0;
+ }
+}
+
+ULONG AggregateAvg::compute(ULONG *data, ULONG length)
+{
+ uint64_t tmp = 0;
+ for (ULONG i = 0; i < length; ++i)
+ tmp += data[i];
+ return (ULONG)(tmp / length);
+}
+
+const char * AggregateAvg::getName()
+{
+ return "avg";
+}
+
+ULONG AggregateMin::compute(ULONG *data, ULONG length)
+{
+ ULONG tmp = *data;
+ for (ULONG i = 0; i < length; ++i)
+ if (data[i] < tmp)
+ tmp = data[i];
+ return tmp;
+}
+
+const char * AggregateMin::getName()
+{
+ return "min";
+}
+
+ULONG AggregateMax::compute(ULONG *data, ULONG length)
+{
+ ULONG tmp = *data;
+ for (ULONG i = 0; i < length; ++i)
+ if (data[i] > tmp)
+ tmp = data[i];
+ return tmp;
+}
+
+const char * AggregateMax::getName()
+{
+ return "max";
+}
+
+Filter::Filter(const std::vector<com::Utf8Str> &metricNames,
+ const std::vector<ComPtr<IUnknown> > &objects)
+{
+ if (!objects.size())
+ {
+ if (metricNames.size())
+ {
+ for (size_t i = 0; i < metricNames.size(); ++i)
+ processMetricList(metricNames[i], ComPtr<IUnknown>());
+ }
+ else
+ processMetricList("*", ComPtr<IUnknown>());
+ }
+ else
+ {
+ for (size_t i = 0; i < objects.size(); ++i)
+ switch (metricNames.size())
+ {
+ case 0:
+ processMetricList("*", objects[i]);
+ break;
+ case 1:
+ processMetricList(metricNames[0], objects[i]);
+ break;
+ default:
+ processMetricList(metricNames[i], objects[i]);
+ break;
+ }
+ }
+}
+
+Filter::Filter(const com::Utf8Str &name, const ComPtr<IUnknown> &aObject)
+{
+ processMetricList(name, aObject);
+}
+
+void Filter::processMetricList(const com::Utf8Str &name, const ComPtr<IUnknown> object)
+{
+ size_t startPos = 0;
+
+ for (size_t pos = name.find(",");
+ pos != com::Utf8Str::npos;
+ pos = name.find(",", startPos))
+ {
+ mElements.push_back(std::make_pair(object, RTCString(name.substr(startPos, pos - startPos).c_str())));
+ startPos = pos + 1;
+ }
+ mElements.push_back(std::make_pair(object, RTCString(name.substr(startPos).c_str())));
+}
+
+/**
+ * The following method was borrowed from stamR3Match (VMM/STAM.cpp) and
+ * modified to handle the special case of trailing colon in the pattern.
+ *
+ * @returns True if matches, false if not.
+ * @param pszPat Pattern.
+ * @param pszName Name to match against the pattern.
+ * @param fSeenColon Seen colon (':').
+ */
+bool Filter::patternMatch(const char *pszPat, const char *pszName,
+ bool fSeenColon)
+{
+ /* ASSUMES ASCII */
+ for (;;)
+ {
+ char chPat = *pszPat;
+ switch (chPat)
+ {
+ default:
+ if (*pszName != chPat)
+ return false;
+ break;
+
+ case '*':
+ {
+ while ((chPat = *++pszPat) == '*' || chPat == '?')
+ /* nothing */;
+
+ /* Handle a special case, the mask terminating with a colon. */
+ if (chPat == ':')
+ {
+ if (!fSeenColon && !pszPat[1])
+ return !strchr(pszName, ':');
+ fSeenColon = true;
+ }
+
+ for (;;)
+ {
+ char ch = *pszName++;
+ if ( ch == chPat
+ && ( !chPat
+ || patternMatch(pszPat + 1, pszName, fSeenColon)))
+ return true;
+ if (!ch)
+ return false;
+ }
+ /* won't ever get here */
+ break;
+ }
+
+ case '?':
+ if (!*pszName)
+ return false;
+ break;
+
+ /* Handle a special case, the mask terminating with a colon. */
+ case ':':
+ if (!fSeenColon && !pszPat[1])
+ return !*pszName;
+ if (*pszName != ':')
+ return false;
+ fSeenColon = true;
+ break;
+
+ case '\0':
+ return !*pszName;
+ }
+ pszName++;
+ pszPat++;
+ }
+ /* not reached */
+}
+
+bool Filter::match(const ComPtr<IUnknown> object, const RTCString &name) const
+{
+ ElementList::const_iterator it;
+
+ //Log7(("Filter::match(%p, %s)\n", static_cast<const IUnknown*> (object), name.c_str()));
+ for (it = mElements.begin(); it != mElements.end(); ++it)
+ {
+ //Log7(("...matching against(%p, %s)\n", static_cast<const IUnknown*> ((*it).first), (*it).second.c_str()));
+ if ((*it).first.isNull() || (*it).first == object)
+ {
+ // Objects match, compare names
+ if (patternMatch((*it).second.c_str(), name.c_str()))
+ {
+ //LogFlowThisFunc(("...found!\n"));
+ return true;
+ }
+ }
+ }
+ //Log7(("...no matches!\n"));
+ return false;
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/PerformanceImpl.cpp b/src/VBox/Main/src-server/PerformanceImpl.cpp
new file mode 100644
index 00000000..0fa10141
--- /dev/null
+++ b/src/VBox/Main/src-server/PerformanceImpl.cpp
@@ -0,0 +1,884 @@
+/* $Id: PerformanceImpl.cpp $ */
+/** @file
+ * VBox Performance API COM Classes implementation
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * Rules of engagement:
+ * 1) All performance objects must be destroyed by PerformanceCollector only!
+ * 2) All public methods of PerformanceCollector must be protected with
+ * read or write lock.
+ * 3) samplerCallback only uses the write lock during the third phase
+ * which pulls data into SubMetric objects. This is where object destruction
+ * and all list modifications are done. The pre-collection phases are
+ * run without any locks which is only possible because:
+ * 4) Public methods of PerformanceCollector as well as pre-collection methods
+ cannot modify lists or destroy objects, and:
+ * 5) Pre-collection methods cannot modify metric data.
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_PERFORMANCECOLLECTOR
+#include "PerformanceImpl.h"
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+#include <iprt/process.h>
+
+#include <VBox/err.h>
+#include <VBox/settings.h>
+
+#include <vector>
+#include <algorithm>
+#include <functional>
+
+#include "Performance.h"
+
+static const char *g_papcszMetricNames[] =
+{
+ "CPU/Load/User",
+ "CPU/Load/User:avg",
+ "CPU/Load/User:min",
+ "CPU/Load/User:max",
+ "CPU/Load/Kernel",
+ "CPU/Load/Kernel:avg",
+ "CPU/Load/Kernel:min",
+ "CPU/Load/Kernel:max",
+ "CPU/Load/Idle",
+ "CPU/Load/Idle:avg",
+ "CPU/Load/Idle:min",
+ "CPU/Load/Idle:max",
+ "CPU/MHz",
+ "CPU/MHz:avg",
+ "CPU/MHz:min",
+ "CPU/MHz:max",
+ "Net/*/Load/Rx",
+ "Net/*/Load/Rx:avg",
+ "Net/*/Load/Rx:min",
+ "Net/*/Load/Rx:max",
+ "Net/*/Load/Tx",
+ "Net/*/Load/Tx:avg",
+ "Net/*/Load/Tx:min",
+ "Net/*/Load/Tx:max",
+ "RAM/Usage/Total",
+ "RAM/Usage/Total:avg",
+ "RAM/Usage/Total:min",
+ "RAM/Usage/Total:max",
+ "RAM/Usage/Used",
+ "RAM/Usage/Used:avg",
+ "RAM/Usage/Used:min",
+ "RAM/Usage/Used:max",
+ "RAM/Usage/Free",
+ "RAM/Usage/Free:avg",
+ "RAM/Usage/Free:min",
+ "RAM/Usage/Free:max",
+ "RAM/VMM/Used",
+ "RAM/VMM/Used:avg",
+ "RAM/VMM/Used:min",
+ "RAM/VMM/Used:max",
+ "RAM/VMM/Free",
+ "RAM/VMM/Free:avg",
+ "RAM/VMM/Free:min",
+ "RAM/VMM/Free:max",
+ "RAM/VMM/Ballooned",
+ "RAM/VMM/Ballooned:avg",
+ "RAM/VMM/Ballooned:min",
+ "RAM/VMM/Ballooned:max",
+ "RAM/VMM/Shared",
+ "RAM/VMM/Shared:avg",
+ "RAM/VMM/Shared:min",
+ "RAM/VMM/Shared:max",
+ "Guest/CPU/Load/User",
+ "Guest/CPU/Load/User:avg",
+ "Guest/CPU/Load/User:min",
+ "Guest/CPU/Load/User:max",
+ "Guest/CPU/Load/Kernel",
+ "Guest/CPU/Load/Kernel:avg",
+ "Guest/CPU/Load/Kernel:min",
+ "Guest/CPU/Load/Kernel:max",
+ "Guest/CPU/Load/Idle",
+ "Guest/CPU/Load/Idle:avg",
+ "Guest/CPU/Load/Idle:min",
+ "Guest/CPU/Load/Idle:max",
+ "Guest/RAM/Usage/Total",
+ "Guest/RAM/Usage/Total:avg",
+ "Guest/RAM/Usage/Total:min",
+ "Guest/RAM/Usage/Total:max",
+ "Guest/RAM/Usage/Free",
+ "Guest/RAM/Usage/Free:avg",
+ "Guest/RAM/Usage/Free:min",
+ "Guest/RAM/Usage/Free:max",
+ "Guest/RAM/Usage/Balloon",
+ "Guest/RAM/Usage/Balloon:avg",
+ "Guest/RAM/Usage/Balloon:min",
+ "Guest/RAM/Usage/Balloon:max",
+ "Guest/RAM/Usage/Shared",
+ "Guest/RAM/Usage/Shared:avg",
+ "Guest/RAM/Usage/Shared:min",
+ "Guest/RAM/Usage/Shared:max",
+ "Guest/RAM/Usage/Cache",
+ "Guest/RAM/Usage/Cache:avg",
+ "Guest/RAM/Usage/Cache:min",
+ "Guest/RAM/Usage/Cache:max",
+ "Guest/Pagefile/Usage/Total",
+ "Guest/Pagefile/Usage/Total:avg",
+ "Guest/Pagefile/Usage/Total:min",
+ "Guest/Pagefile/Usage/Total:max",
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// PerformanceCollector class
+////////////////////////////////////////////////////////////////////////////////
+
+// constructor / destructor
+////////////////////////////////////////////////////////////////////////////////
+
+PerformanceCollector::PerformanceCollector()
+ : mMagic(0), mUnknownGuest("unknown guest")
+{
+}
+
+PerformanceCollector::~PerformanceCollector() {}
+
+HRESULT PerformanceCollector::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+
+ return BaseFinalConstruct();
+}
+
+void PerformanceCollector::FinalRelease()
+{
+ LogFlowThisFunc(("\n"));
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the PerformanceCollector object.
+ */
+HRESULT PerformanceCollector::init()
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ LogFlowThisFuncEnter();
+
+ HRESULT rc = S_OK;
+
+ m.hal = pm::createHAL();
+ m.gm = new pm::CollectorGuestManager;
+
+ /* Let the sampler know it gets a valid collector. */
+ mMagic = PERFORMANCE_METRIC_MAGIC;
+
+ /* Start resource usage sampler */
+ int vrc = RTTimerLRCreate(&m.sampler, VBOX_USAGE_SAMPLER_MIN_INTERVAL,
+ &PerformanceCollector::staticSamplerCallback, this);
+ AssertMsgRC(vrc, ("Failed to create resource usage sampling timer(%Rra)\n", vrc));
+ if (RT_FAILURE(vrc))
+ rc = E_FAIL;
+
+ if (SUCCEEDED(rc))
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+
+ return rc;
+}
+
+/**
+ * Uninitializes the PerformanceCollector object.
+ *
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void PerformanceCollector::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ {
+ LogFlowThisFunc(("Already uninitialized.\n"));
+ LogFlowThisFuncLeave();
+ return;
+ }
+
+ /* Destroy resource usage sampler first, as the callback will access the metrics. */
+ int vrc = RTTimerLRDestroy(m.sampler);
+ AssertMsgRC(vrc, ("Failed to destroy resource usage sampling timer (%Rra)\n", vrc));
+ m.sampler = NULL;
+
+ /* Destroy unregistered metrics */
+ BaseMetricList::iterator it;
+ for (it = m.baseMetrics.begin(); it != m.baseMetrics.end();)
+ if ((*it)->isUnregistered())
+ {
+ delete *it;
+ it = m.baseMetrics.erase(it);
+ }
+ else
+ ++it;
+ Assert(m.baseMetrics.size() == 0);
+ /*
+ * Now when we have destroyed all base metrics that could
+ * try to pull data from unregistered CollectorGuest objects
+ * it is safe to destroy them as well.
+ */
+ m.gm->destroyUnregistered();
+
+ /* Invalidate the magic now. */
+ mMagic = 0;
+
+ //delete m.factory;
+ //m.factory = NULL;
+
+ delete m.gm;
+ m.gm = NULL;
+ delete m.hal;
+ m.hal = NULL;
+
+ LogFlowThisFuncLeave();
+}
+
+// IPerformanceCollector properties
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT PerformanceCollector::getMetricNames(std::vector<com::Utf8Str> &aMetricNames)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aMetricNames.resize(RT_ELEMENTS(g_papcszMetricNames));
+ for (size_t i = 0; i < RT_ELEMENTS(g_papcszMetricNames); i++)
+ aMetricNames[i] = g_papcszMetricNames[i];
+
+ return S_OK;
+}
+
+// IPerformanceCollector methods
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT PerformanceCollector::toIPerformanceMetric(pm::Metric *src, ComPtr<IPerformanceMetric> &dst)
+{
+ ComObjPtr<PerformanceMetric> metric;
+ HRESULT rc = metric.createObject();
+ if (SUCCEEDED(rc))
+ rc = metric->init(src);
+ AssertComRCReturnRC(rc);
+ dst = metric;
+ return rc;
+}
+
+HRESULT PerformanceCollector::toIPerformanceMetric(pm::BaseMetric *src, ComPtr<IPerformanceMetric> &dst)
+{
+ ComObjPtr<PerformanceMetric> metric;
+ HRESULT rc = metric.createObject();
+ if (SUCCEEDED(rc))
+ rc = metric->init(src);
+ AssertComRCReturnRC(rc);
+ dst = metric;
+ return rc;
+}
+
+const Utf8Str& PerformanceCollector::getFailedGuestName()
+{
+ pm::CollectorGuest *pGuest = m.gm->getBlockedGuest();
+ if (pGuest)
+ return pGuest->getVMName();
+ return mUnknownGuest;
+}
+
+HRESULT PerformanceCollector::getMetrics(const std::vector<com::Utf8Str> &aMetricNames,
+ const std::vector<ComPtr<IUnknown> > &aObjects,
+ std::vector<ComPtr<IPerformanceMetric> > &aMetrics)
+{
+ HRESULT rc = S_OK;
+
+ pm::Filter filter(aMetricNames, aObjects);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ MetricList filteredMetrics;
+ MetricList::iterator it;
+ for (it = m.metrics.begin(); it != m.metrics.end(); ++it)
+ if (filter.match((*it)->getObject(), (*it)->getName()))
+ filteredMetrics.push_back(*it);
+
+ aMetrics.resize(filteredMetrics.size());
+ size_t i = 0;
+ for (it = filteredMetrics.begin(); it != filteredMetrics.end(); ++it)
+ {
+ ComObjPtr<PerformanceMetric> metric;
+ rc = metric.createObject();
+ if (SUCCEEDED(rc))
+ rc = metric->init(*it);
+ AssertComRCReturnRC(rc);
+ LogFlow(("PerformanceCollector::GetMetrics() store a metric at retMetrics[%zu]...\n", i));
+ aMetrics[i++] = metric;
+ }
+ return rc;
+}
+
+HRESULT PerformanceCollector::setupMetrics(const std::vector<com::Utf8Str> &aMetricNames,
+ const std::vector<ComPtr<IUnknown> > &aObjects,
+ ULONG aPeriod,
+ ULONG aCount,
+ std::vector<ComPtr<IPerformanceMetric> > &aAffectedMetrics)
+{
+ pm::Filter filter(aMetricNames, aObjects);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+ BaseMetricList filteredMetrics;
+ BaseMetricList::iterator it;
+ for (it = m.baseMetrics.begin(); it != m.baseMetrics.end(); ++it)
+ if (filter.match((*it)->getObject(), (*it)->getName()))
+ {
+ LogFlow(("PerformanceCollector::SetupMetrics() setting period to %u, count to %u for %s\n",
+ aPeriod, aCount, (*it)->getName()));
+ (*it)->init(aPeriod, aCount);
+ if (aPeriod == 0 || aCount == 0)
+ {
+ LogFlow(("PerformanceCollector::SetupMetrics() disabling %s\n",
+ (*it)->getName()));
+ rc = (*it)->disable();
+ if (FAILED(rc))
+ break;
+ }
+ else
+ {
+ LogFlow(("PerformanceCollector::SetupMetrics() enabling %s\n",
+ (*it)->getName()));
+ rc = (*it)->enable();
+ if (FAILED(rc))
+ break;
+ }
+ filteredMetrics.push_back(*it);
+ }
+
+ aAffectedMetrics.resize(filteredMetrics.size());
+ size_t i = 0;
+ for (it = filteredMetrics.begin();
+ it != filteredMetrics.end() && SUCCEEDED(rc); ++it)
+ rc = toIPerformanceMetric(*it, aAffectedMetrics[i++]);
+
+ if (FAILED(rc))
+ return setError(E_FAIL, tr("Failed to setup metrics for '%s'"),
+ getFailedGuestName().c_str());
+ return rc;
+}
+
+HRESULT PerformanceCollector::enableMetrics(const std::vector<com::Utf8Str> &aMetricNames,
+ const std::vector<ComPtr<IUnknown> > &aObjects,
+ std::vector<ComPtr<IPerformanceMetric> > &aAffectedMetrics)
+{
+ pm::Filter filter(aMetricNames, aObjects);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Write lock is not needed atm since we are */
+ /* fiddling with enable bit only, but we */
+ /* care for those who come next :-). */
+
+ HRESULT rc = S_OK;
+ BaseMetricList filteredMetrics;
+ BaseMetricList::iterator it;
+ for (it = m.baseMetrics.begin(); it != m.baseMetrics.end(); ++it)
+ if (filter.match((*it)->getObject(), (*it)->getName()))
+ {
+ rc = (*it)->enable();
+ if (FAILED(rc))
+ break;
+ filteredMetrics.push_back(*it);
+ }
+
+ aAffectedMetrics.resize(filteredMetrics.size());
+ size_t i = 0;
+ for (it = filteredMetrics.begin();
+ it != filteredMetrics.end() && SUCCEEDED(rc); ++it)
+ rc = toIPerformanceMetric(*it, aAffectedMetrics[i++]);
+
+ LogFlowThisFuncLeave();
+
+ if (FAILED(rc))
+ return setError(E_FAIL, tr("Failed to enable metrics for '%s'"),
+ getFailedGuestName().c_str());
+ return rc;
+}
+
+HRESULT PerformanceCollector::disableMetrics(const std::vector<com::Utf8Str> &aMetricNames,
+ const std::vector<ComPtr<IUnknown> > &aObjects,
+ std::vector<ComPtr<IPerformanceMetric> > &aAffectedMetrics)
+{
+ pm::Filter filter(aMetricNames, aObjects);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Write lock is not needed atm since we are */
+ /* fiddling with enable bit only, but we */
+ /* care for those who come next :-). */
+
+ HRESULT rc = S_OK;
+ BaseMetricList filteredMetrics;
+ BaseMetricList::iterator it;
+ for (it = m.baseMetrics.begin(); it != m.baseMetrics.end(); ++it)
+ if (filter.match((*it)->getObject(), (*it)->getName()))
+ {
+ rc = (*it)->disable();
+ if (FAILED(rc))
+ break;
+ filteredMetrics.push_back(*it);
+ }
+
+ aAffectedMetrics.resize(filteredMetrics.size());
+ size_t i = 0;
+ for (it = filteredMetrics.begin();
+ it != filteredMetrics.end() && SUCCEEDED(rc); ++it)
+ rc = toIPerformanceMetric(*it, aAffectedMetrics[i++]);
+
+ LogFlowThisFuncLeave();
+
+ if (FAILED(rc))
+ return setError(E_FAIL, tr("Failed to disable metrics for '%s'"),
+ getFailedGuestName().c_str());
+ return rc;
+}
+
+HRESULT PerformanceCollector::queryMetricsData(const std::vector<com::Utf8Str> &aMetricNames,
+ const std::vector<ComPtr<IUnknown> > &aObjects,
+ std::vector<com::Utf8Str> &aReturnMetricNames,
+ std::vector<ComPtr<IUnknown> > &aReturnObjects,
+ std::vector<com::Utf8Str> &aReturnUnits,
+ std::vector<ULONG> &aReturnScales,
+ std::vector<ULONG> &aReturnSequenceNumbers,
+ std::vector<ULONG> &aReturnDataIndices,
+ std::vector<ULONG> &aReturnDataLengths,
+ std::vector<LONG> &aReturnData)
+{
+ pm::Filter filter(aMetricNames, aObjects);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Let's compute the size of the resulting flat array */
+ size_t flatSize = 0;
+ MetricList filteredMetrics;
+ MetricList::iterator it;
+ for (it = m.metrics.begin(); it != m.metrics.end(); ++it)
+ if (filter.match((*it)->getObject(), (*it)->getName()))
+ {
+ filteredMetrics.push_back(*it);
+ flatSize += (*it)->getLength();
+ }
+
+ size_t flatIndex = 0;
+ size_t numberOfMetrics = filteredMetrics.size();
+ aReturnMetricNames.resize(numberOfMetrics);
+ aReturnObjects.resize(numberOfMetrics);
+ aReturnUnits.resize(numberOfMetrics);
+ aReturnScales.resize(numberOfMetrics);
+ aReturnSequenceNumbers.resize(numberOfMetrics);
+ aReturnDataIndices.resize(numberOfMetrics);
+ aReturnDataLengths.resize(numberOfMetrics);
+ aReturnData.resize(flatSize);
+
+ size_t i = 0;
+ for (it = filteredMetrics.begin(); it != filteredMetrics.end(); ++it, ++i)
+ {
+ ULONG *values, length, sequenceNumber;
+ /** @todo We may want to revise the query method to get rid of excessive alloc/memcpy calls. */
+ (*it)->query(&values, &length, &sequenceNumber);
+ LogFlow(("PerformanceCollector::QueryMetricsData() querying metric %s returned %d values.\n",
+ (*it)->getName(), length));
+ memcpy(&aReturnData[flatIndex], values, length * sizeof(*values));
+ RTMemFree(values);
+ aReturnMetricNames[i] = (*it)->getName();
+ aReturnObjects[i] = (*it)->getObject();
+ aReturnUnits[i] = (*it)->getUnit();
+ aReturnScales[i] = (*it)->getScale();
+ aReturnSequenceNumbers[i] = sequenceNumber;
+ aReturnDataIndices[i] = (ULONG)flatIndex;
+ aReturnDataLengths[i] = length;
+ flatIndex += length;
+ }
+
+ return S_OK;
+}
+
+// public methods for internal purposes
+///////////////////////////////////////////////////////////////////////////////
+
+void PerformanceCollector::registerBaseMetric(pm::BaseMetric *baseMetric)
+{
+ //LogFlowThisFuncEnter();
+ AutoCaller autoCaller(this);
+ if (!SUCCEEDED(autoCaller.rc())) return;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Log7Func(("{%p}: obj=%p name=%s\n", this, (void *)baseMetric->getObject(), baseMetric->getName()));
+ m.baseMetrics.push_back(baseMetric);
+ //LogFlowThisFuncLeave();
+}
+
+void PerformanceCollector::registerMetric(pm::Metric *metric)
+{
+ //LogFlowThisFuncEnter();
+ AutoCaller autoCaller(this);
+ if (!SUCCEEDED(autoCaller.rc())) return;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Log7Func(("{%p}: obj=%p name=%s\n", this, (void *)metric->getObject(), metric->getName()));
+ m.metrics.push_back(metric);
+ //LogFlowThisFuncLeave();
+}
+
+void PerformanceCollector::unregisterBaseMetricsFor(const ComPtr<IUnknown> &aObject, const Utf8Str name)
+{
+ //LogFlowThisFuncEnter();
+ AutoCaller autoCaller(this);
+ if (!SUCCEEDED(autoCaller.rc())) return;
+
+ pm::Filter filter(name, aObject);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ int n = 0;
+ BaseMetricList::iterator it;
+ for (it = m.baseMetrics.begin(); it != m.baseMetrics.end(); ++it)
+ if (filter.match((*it)->getObject(), (*it)->getName()))
+ {
+ (*it)->unregister();
+ ++n;
+ }
+ Log7Func(("{%p}: obj=%p, name=%s, marked %d metrics\n", this, (void *)aObject, name.c_str(), n));
+ //LogFlowThisFuncLeave();
+}
+
+void PerformanceCollector::unregisterMetricsFor(const ComPtr<IUnknown> &aObject, const Utf8Str name)
+{
+ //LogFlowThisFuncEnter();
+ AutoCaller autoCaller(this);
+ if (!SUCCEEDED(autoCaller.rc())) return;
+
+ pm::Filter filter(name, aObject);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Log7Func(("{%p}: obj=%p, name=%s\n", this, (void *)aObject, name.c_str()));
+ MetricList::iterator it;
+ for (it = m.metrics.begin(); it != m.metrics.end();)
+ if (filter.match((*it)->getObject(), (*it)->getName()))
+ {
+ delete *it;
+ it = m.metrics.erase(it);
+ }
+ else
+ ++it;
+ //LogFlowThisFuncLeave();
+}
+
+void PerformanceCollector::registerGuest(pm::CollectorGuest* pGuest)
+{
+ AutoCaller autoCaller(this);
+ if (!SUCCEEDED(autoCaller.rc())) return;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m.gm->registerGuest(pGuest);
+}
+
+void PerformanceCollector::unregisterGuest(pm::CollectorGuest* pGuest)
+{
+ AutoCaller autoCaller(this);
+ if (!SUCCEEDED(autoCaller.rc())) return;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m.gm->unregisterGuest(pGuest);
+}
+
+void PerformanceCollector::suspendSampling()
+{
+ AutoCaller autoCaller(this);
+ if (!SUCCEEDED(autoCaller.rc())) return;
+
+ int rc = RTTimerLRStop(m.sampler);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_TIMER_SUSPENDED) /* calling suspendSampling() successively shouldn't assert. See @bugref{3495}. */
+ AssertMsgFailed(("PerformanceCollector::suspendSampling(): RTTimerLRStop returned %Rrc\n", rc));
+}
+
+void PerformanceCollector::resumeSampling()
+{
+ AutoCaller autoCaller(this);
+ if (!SUCCEEDED(autoCaller.rc())) return;
+
+ int rc = RTTimerLRStart(m.sampler, 0);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_TIMER_ACTIVE) /* calling resumeSampling() successively shouldn't assert. See @bugref{3495}. */
+ AssertMsgFailed(("PerformanceCollector::resumeSampling(): RTTimerLRStart returned %Rrc\n", rc));
+}
+
+
+// private methods
+///////////////////////////////////////////////////////////////////////////////
+
+/* static */
+DECLCALLBACK(void) PerformanceCollector::staticSamplerCallback(RTTIMERLR hTimerLR, void *pvUser,
+ uint64_t iTick)
+{
+ AssertReturnVoid(pvUser != NULL);
+ PerformanceCollector *collector = static_cast <PerformanceCollector *> (pvUser);
+ Assert(collector->mMagic == PERFORMANCE_METRIC_MAGIC);
+ if (collector->mMagic == PERFORMANCE_METRIC_MAGIC)
+ collector->samplerCallback(iTick);
+
+ NOREF(hTimerLR);
+}
+
+/*
+ * Metrics collection is a three stage process:
+ * 1) Pre-collection (hinting)
+ * At this stage we compose the list of all metrics to be collected
+ * If any metrics cannot be collected separately or if it is more
+ * efficient to collect several metric at once, these metrics should
+ * use hints to mark that they will need to be collected.
+ * 2) Pre-collection (bulk)
+ * Using hints set at stage 1 platform-specific HAL
+ * instance collects all marked host-related metrics.
+ * Hinted guest-related metrics then get collected by CollectorGuestManager.
+ * 3) Collection
+ * Metrics that are collected individually get collected and stored. Values
+ * saved in HAL and CollectorGuestManager are extracted and stored to
+ * individual metrics.
+ */
+void PerformanceCollector::samplerCallback(uint64_t iTick)
+{
+ Log4Func(("{%p}: ENTER\n", this));
+ /* No locking until stage 3!*/
+
+ pm::CollectorHints hints;
+ uint64_t timestamp = RTTimeMilliTS();
+ BaseMetricList toBeCollected;
+ BaseMetricList::iterator it;
+ /* Compose the list of metrics being collected at this moment */
+ for (it = m.baseMetrics.begin(); it != m.baseMetrics.end(); ++it)
+ if ((*it)->collectorBeat(timestamp))
+ {
+ (*it)->preCollect(hints, iTick);
+ toBeCollected.push_back(*it);
+ }
+
+ if (toBeCollected.size() == 0)
+ {
+ Log4Func(("{%p}: LEAVE (nothing to collect)\n", this));
+ return;
+ }
+
+ /* Let know the platform specific code what is being collected */
+ m.hal->preCollect(hints, iTick);
+#if 0
+ /* Guest stats are now pushed by guests themselves */
+ /* Collect the data in bulk from all hinted guests */
+ m.gm->preCollect(hints, iTick);
+#endif
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ /*
+ * Before we can collect data we need to go through both lists
+ * again to see if any base metrics are marked as unregistered.
+ * Those should be destroyed now.
+ */
+ Log7Func(("{%p}: before remove_if: toBeCollected.size()=%d\n", this, toBeCollected.size()));
+#if RT_CPLUSPLUS_PREREQ(201100) /* mem_fun is deprecated in C++11 and removed in C++17 */
+ toBeCollected.remove_if(std::mem_fn(&pm::BaseMetric::isUnregistered));
+#else
+ toBeCollected.remove_if(std::mem_fun(&pm::BaseMetric::isUnregistered));
+#endif
+ Log7Func(("{%p}: after remove_if: toBeCollected.size()=%d\n", this, toBeCollected.size()));
+ Log7Func(("{%p}: before remove_if: m.baseMetrics.size()=%d\n", this, m.baseMetrics.size()));
+ for (it = m.baseMetrics.begin(); it != m.baseMetrics.end();)
+ if ((*it)->isUnregistered())
+ {
+ delete *it;
+ it = m.baseMetrics.erase(it);
+ }
+ else
+ ++it;
+ Log7Func(("{%p}: after remove_if: m.baseMetrics.size()=%d\n", this, m.baseMetrics.size()));
+ /*
+ * Now when we have destroyed all base metrics that could
+ * try to pull data from unregistered CollectorGuest objects
+ * it is safe to destroy them as well.
+ */
+ m.gm->destroyUnregistered();
+
+ /* Finally, collect the data */
+#if RT_CPLUSPLUS_PREREQ(201100) /* mem_fun is deprecated in C++11 and removed in C++17 */
+ std::for_each(toBeCollected.begin(), toBeCollected.end(), std::mem_fn(&pm::BaseMetric::collect));
+#else
+ std::for_each(toBeCollected.begin(), toBeCollected.end(), std::mem_fun(&pm::BaseMetric::collect));
+#endif
+ Log4Func(("{%p}: LEAVE\n", this));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PerformanceMetric class
+////////////////////////////////////////////////////////////////////////////////
+
+// constructor / destructor
+////////////////////////////////////////////////////////////////////////////////
+
+PerformanceMetric::PerformanceMetric()
+{
+}
+
+PerformanceMetric::~PerformanceMetric()
+{
+}
+
+HRESULT PerformanceMetric::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+
+ return BaseFinalConstruct();
+}
+
+void PerformanceMetric::FinalRelease()
+{
+ LogFlowThisFunc(("\n"));
+
+ uninit();
+
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT PerformanceMetric::init(pm::Metric *aMetric)
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m.name = aMetric->getName();
+ m.object = aMetric->getObject();
+ m.description = aMetric->getDescription();
+ m.period = aMetric->getPeriod();
+ m.count = aMetric->getLength();
+ m.unit = aMetric->getUnit();
+ /** @todo r=bird: LONG/ULONG mixup. */
+ m.min = (LONG)aMetric->getMinValue();
+ m.max = (LONG)aMetric->getMaxValue();
+
+ autoInitSpan.setSucceeded();
+ return S_OK;
+}
+
+HRESULT PerformanceMetric::init(pm::BaseMetric *aMetric)
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m.name = aMetric->getName();
+ m.object = aMetric->getObject();
+ m.description = "";
+ m.period = aMetric->getPeriod();
+ m.count = aMetric->getLength();
+ m.unit = aMetric->getUnit();
+ /** @todo r=bird: LONG/ULONG mixup. */
+ m.min = (LONG)aMetric->getMinValue();
+ m.max = (LONG)aMetric->getMaxValue();
+
+ autoInitSpan.setSucceeded();
+ return S_OK;
+}
+
+void PerformanceMetric::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ {
+ LogFlowThisFunc(("Already uninitialized.\n"));
+ LogFlowThisFuncLeave();
+ return;
+ }
+}
+
+HRESULT PerformanceMetric::getMetricName(com::Utf8Str &aMetricName)
+{
+ /* this is const, no need to lock */
+ aMetricName = m.name;
+ return S_OK;
+}
+
+HRESULT PerformanceMetric::getObject(ComPtr<IUnknown> &aObject)
+{
+ /* this is const, no need to lock */
+ aObject = m.object;
+ return S_OK;
+}
+
+HRESULT PerformanceMetric::getDescription(com::Utf8Str &aDescription)
+{
+ /* this is const, no need to lock */
+ aDescription = m.description;
+ return S_OK;
+}
+
+HRESULT PerformanceMetric::getPeriod(ULONG *aPeriod)
+{
+ /* this is const, no need to lock */
+ *aPeriod = m.period;
+ return S_OK;
+}
+
+HRESULT PerformanceMetric::getCount(ULONG *aCount)
+{
+ /* this is const, no need to lock */
+ *aCount = m.count;
+ return S_OK;
+}
+
+HRESULT PerformanceMetric::getUnit(com::Utf8Str &aUnit)
+{
+ /* this is const, no need to lock */
+ aUnit = m.unit;
+ return S_OK;
+}
+
+HRESULT PerformanceMetric::getMinimumValue(LONG *aMinimumValue)
+{
+ /* this is const, no need to lock */
+ *aMinimumValue = m.min;
+ return S_OK;
+}
+
+HRESULT PerformanceMetric::getMaximumValue(LONG *aMaximumValue)
+{
+ /* this is const, no need to lock */
+ *aMaximumValue = m.max;
+ return S_OK;
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/ProgressProxyImpl.cpp b/src/VBox/Main/src-server/ProgressProxyImpl.cpp
new file mode 100644
index 00000000..79c42b70
--- /dev/null
+++ b/src/VBox/Main/src-server/ProgressProxyImpl.cpp
@@ -0,0 +1,709 @@
+/* $Id: ProgressProxyImpl.cpp $ */
+/** @file
+ * IProgress implementation for Machine::openRemoteSession in VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_PROGRESS
+#include <iprt/types.h>
+
+#include "ProgressProxyImpl.h"
+
+#include "VirtualBoxImpl.h"
+#include "VirtualBoxErrorInfoImpl.h"
+
+#include "LoggingNew.h"
+
+#include <iprt/time.h>
+#include <iprt/semaphore.h>
+#include <iprt/errcore.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// ProgressProxy class
+////////////////////////////////////////////////////////////////////////////////
+
+// constructor / destructor / uninitializer
+////////////////////////////////////////////////////////////////////////////////
+
+
+HRESULT ProgressProxy::FinalConstruct()
+{
+ mfMultiOperation = false;
+ muOtherProgressStartWeight = 0;
+ muOtherProgressWeight = 0;
+ muOtherProgressStartOperation = 0;
+
+ HRESULT rc = Progress::FinalConstruct();
+ return rc;
+}
+
+/**
+ * Initialize it as a one operation Progress object.
+ *
+ * This is used by SessionMachine::OnSessionEnd.
+ */
+HRESULT ProgressProxy::init(
+#if !defined (VBOX_COM_INPROC)
+ VirtualBox *pParent,
+#endif
+ IUnknown *pInitiator,
+ Utf8Str strDescription,
+ BOOL fCancelable)
+{
+ mfMultiOperation = false;
+ muOtherProgressStartWeight = 1;
+ muOtherProgressWeight = 1;
+ muOtherProgressStartOperation = 1;
+
+ return Progress::init(
+#if !defined (VBOX_COM_INPROC)
+ pParent,
+#endif
+ pInitiator,
+ strDescription,
+ fCancelable,
+ 1 /* cOperations */,
+ 1 /* ulTotalOperationsWeight */,
+ strDescription /* strFirstOperationDescription */,
+ 1 /* ulFirstOperationWeight */);
+}
+
+/**
+ * Initialize for proxying one other progress object.
+ *
+ * This is tailored explicitly for the openRemoteSession code, so we start out
+ * with one operation where we don't have any remote object (powerUp). Then a
+ * remote object is added and stays with us till the end.
+ *
+ * The user must do normal completion notification or risk leave the threads
+ * waiting forever!
+ */
+HRESULT ProgressProxy::init(
+#if !defined (VBOX_COM_INPROC)
+ VirtualBox *pParent,
+#endif
+ IUnknown *pInitiator,
+ Utf8Str strDescription,
+ BOOL fCancelable,
+ ULONG uTotalOperationsWeight,
+ Utf8Str strFirstOperationDescription,
+ ULONG uFirstOperationWeight,
+ ULONG cOtherProgressObjectOperations)
+{
+ mfMultiOperation = false;
+ muOtherProgressStartWeight = uFirstOperationWeight;
+ muOtherProgressWeight = uTotalOperationsWeight - uFirstOperationWeight;
+ muOtherProgressStartOperation = 1;
+
+ return Progress::init(
+#if !defined (VBOX_COM_INPROC)
+ pParent,
+#endif
+ pInitiator,
+ strDescription,
+ fCancelable,
+ 1 + cOtherProgressObjectOperations /* cOperations */,
+ uTotalOperationsWeight,
+ strFirstOperationDescription,
+ uFirstOperationWeight);
+}
+
+void ProgressProxy::FinalRelease()
+{
+ uninit();
+ mfMultiOperation = false;
+ muOtherProgressStartWeight = 0;
+ muOtherProgressWeight = 0;
+ muOtherProgressStartOperation = 0;
+
+ BaseFinalRelease();
+}
+
+void ProgressProxy::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ mptrOtherProgress.setNull();
+ Progress::uninit();
+}
+
+// Public methods
+////////////////////////////////////////////////////////////////////////////////
+
+/** Just a wrapper so we can automatically do the handover before setting
+ * the result locally. */
+HRESULT ProgressProxy::notifyComplete(HRESULT aResultCode)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ clearOtherProgressObjectInternal(true /* fEarly */);
+ HRESULT hrc = S_OK;
+ if (!mCompleted)
+ hrc = Progress::i_notifyComplete(aResultCode);
+ return hrc;
+}
+
+/** Just a wrapper so we can automatically do the handover before setting
+ * the result locally. */
+HRESULT ProgressProxy::notifyComplete(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const char *aText,
+ ...)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ clearOtherProgressObjectInternal(true /* fEarly */);
+
+ HRESULT hrc = S_OK;
+ if (!mCompleted)
+ {
+ va_list va;
+ va_start(va, aText);
+ hrc = Progress::i_notifyCompleteV(aResultCode, aIID, pcszComponent, aText, va);
+ va_end(va);
+ }
+ return hrc;
+}
+
+/**
+ * Sets the other progress object unless the operation has been completed /
+ * canceled already.
+ *
+ * @returns false if failed/canceled, true if not.
+ * @param pOtherProgress The other progress object. Must not be NULL.
+ */
+bool ProgressProxy::setOtherProgressObject(IProgress *pOtherProgress)
+{
+ LogFlowThisFunc(("setOtherProgressObject: %p\n", pOtherProgress));
+ ComPtr<IProgress> ptrOtherProgress = pOtherProgress;
+
+ /*
+ * Query information from the other progress object before we grab the
+ * lock.
+ */
+ ULONG cOperations;
+ HRESULT hrc = pOtherProgress->COMGETTER(OperationCount)(&cOperations);
+ if (FAILED(hrc))
+ cOperations = 1;
+
+ Bstr bstrOperationDescription;
+ hrc = pOtherProgress->COMGETTER(Description)(bstrOperationDescription.asOutParam());
+ if (FAILED(hrc))
+ bstrOperationDescription = "oops";
+
+
+ /*
+ * Take the lock and check for cancelation, cancel the other object if
+ * we've been canceled already.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ BOOL fCompletedOrCanceled = mCompleted || mCanceled;
+ if (!fCompletedOrCanceled)
+ {
+ /*
+ * Advance to the next object and operation. If the other object has
+ * more operations than anticipated, adjust our internal count.
+ */
+ mptrOtherProgress = ptrOtherProgress;
+ mfMultiOperation = cOperations > 1;
+
+ muOtherProgressStartWeight = m_ulOperationsCompletedWeight + m_ulCurrentOperationWeight;
+ muOtherProgressWeight = m_ulTotalOperationsWeight - muOtherProgressStartWeight;
+ Progress::SetNextOperation(bstrOperationDescription.raw(), muOtherProgressWeight);
+
+ muOtherProgressStartOperation = m_ulCurrentOperation;
+ m_cOperations = cOperations + m_ulCurrentOperation;
+
+ /*
+ * Check for cancelation and completion.
+ */
+ BOOL f;
+ hrc = ptrOtherProgress->COMGETTER(Completed)(&f);
+ fCompletedOrCanceled = FAILED(hrc) || f;
+
+ if (!fCompletedOrCanceled)
+ {
+ hrc = ptrOtherProgress->COMGETTER(Canceled)(&f);
+ fCompletedOrCanceled = SUCCEEDED(hrc) && f;
+ }
+
+ if (fCompletedOrCanceled)
+ {
+ LogFlowThisFunc(("Other object completed or canceled, clearing...\n"));
+ clearOtherProgressObjectInternal(false /*fEarly*/);
+ }
+ else
+ {
+ /*
+ * Finally, mirror the cancelable property.
+ * Note! Note necessary if we do passthru!
+ */
+ if (mCancelable)
+ {
+ hrc = ptrOtherProgress->COMGETTER(Cancelable)(&f);
+ if (SUCCEEDED(hrc) && !f)
+ {
+ LogFlowThisFunc(("The other progress object is not cancelable\n"));
+ mCancelable = FALSE;
+ }
+ }
+ }
+ }
+ else
+ {
+ LogFlowThisFunc(("mCompleted=%RTbool mCanceled=%RTbool - Canceling the other progress object!\n",
+ mCompleted, mCanceled));
+ hrc = ptrOtherProgress->Cancel();
+ LogFlowThisFunc(("Cancel -> %Rhrc", hrc));
+ }
+
+ LogFlowThisFunc(("Returns %RTbool\n", !fCompletedOrCanceled));
+ return !fCompletedOrCanceled;
+}
+
+// Internal methods.
+////////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * Clear the other progress object reference, first copying over its state.
+ *
+ * This is used internally when completion is signalled one way or another.
+ *
+ * @param fEarly Early clearing or not.
+ */
+void ProgressProxy::clearOtherProgressObjectInternal(bool fEarly)
+{
+ if (!mptrOtherProgress.isNull())
+ {
+ ComPtr<IProgress> ptrOtherProgress = mptrOtherProgress;
+ mptrOtherProgress.setNull();
+ copyProgressInfo(ptrOtherProgress, fEarly);
+ }
+}
+
+/**
+ * Called to copy over the progress information from @a pOtherProgress.
+ *
+ * @param pOtherProgress The source of the information.
+ * @param fEarly Early copy.
+ *
+ * @note The caller owns the write lock and as cleared mptrOtherProgress
+ * already (or we might recurse forever)!
+ */
+void ProgressProxy::copyProgressInfo(IProgress *pOtherProgress, bool fEarly)
+{
+ HRESULT hrc;
+ LogFlowThisFunc(("\n"));
+
+ NOREF(fEarly);
+
+ /*
+ * No point in doing this if the progress object was canceled already.
+ */
+ if (!mCanceled)
+ {
+ /* Detect if the other progress object was canceled. */
+ BOOL fCanceled;
+ hrc = pOtherProgress->COMGETTER(Canceled)(&fCanceled);
+ if (FAILED(hrc))
+ fCanceled = FALSE;
+ if (fCanceled)
+ {
+ LogFlowThisFunc(("Canceled\n"));
+ mCanceled = TRUE;
+ if (m_pfnCancelCallback)
+ m_pfnCancelCallback(m_pvCancelUserArg);
+ }
+ else
+ {
+ /* Has it completed? */
+ BOOL fCompleted;
+ hrc = pOtherProgress->COMGETTER(Completed)(&fCompleted);
+ if (FAILED(hrc))
+ fCompleted = TRUE;
+ Assert(fCompleted || fEarly);
+ if (fCompleted)
+ {
+ /* Check the result. */
+ LONG lResult;
+ hrc = pOtherProgress->COMGETTER(ResultCode)(&lResult);
+ if (FAILED(hrc))
+ lResult = (LONG)hrc;
+ if (SUCCEEDED((HRESULT)lResult))
+ LogFlowThisFunc(("Succeeded\n"));
+ else
+ {
+ /* Get the error information. */
+ ComPtr<IVirtualBoxErrorInfo> ptrErrorInfo;
+ hrc = pOtherProgress->COMGETTER(ErrorInfo)(ptrErrorInfo.asOutParam());
+ if (SUCCEEDED(hrc) && !ptrErrorInfo.isNull())
+ {
+ Bstr bstrIID;
+ hrc = ptrErrorInfo->COMGETTER(InterfaceID)(bstrIID.asOutParam()); AssertComRC(hrc);
+ if (FAILED(hrc))
+ bstrIID.setNull();
+
+ Bstr bstrComponent;
+ hrc = ptrErrorInfo->COMGETTER(Component)(bstrComponent.asOutParam()); AssertComRC(hrc);
+ if (FAILED(hrc))
+ bstrComponent = "failed";
+
+ Bstr bstrText;
+ hrc = ptrErrorInfo->COMGETTER(Text)(bstrText.asOutParam()); AssertComRC(hrc);
+ if (FAILED(hrc))
+ bstrText = "<failed>";
+
+ Utf8Str strText(bstrText);
+ LogFlowThisFunc(("Got ErrorInfo(%s); hrcResult=%Rhrc\n", strText.c_str(), (HRESULT)lResult));
+ Progress::i_notifyComplete((HRESULT)lResult,
+ Guid(bstrIID).ref(),
+ Utf8Str(bstrComponent).c_str(),
+ "%s", strText.c_str());
+ }
+ else
+ {
+ LogFlowThisFunc(("ErrorInfo failed with hrc=%Rhrc; hrcResult=%Rhrc\n", hrc, (HRESULT)lResult));
+ Progress::i_notifyComplete((HRESULT)lResult,
+ COM_IIDOF(IProgress),
+ "ProgressProxy",
+ tr("No error info"));
+ }
+ }
+ }
+ else
+ LogFlowThisFunc(("Not completed\n"));
+ }
+ }
+ else
+ LogFlowThisFunc(("Already canceled\n"));
+
+ /*
+ * Did cancelable state change (point of no return)?
+ */
+ if (mCancelable && !mCompleted && !mCanceled)
+ {
+ BOOL fCancelable;
+ hrc = pOtherProgress->COMGETTER(Cancelable)(&fCancelable); AssertComRC(hrc);
+ if (SUCCEEDED(hrc) && !fCancelable)
+ {
+ LogFlowThisFunc(("point-of-no-return reached\n"));
+ mCancelable = FALSE;
+ }
+ }
+}
+
+
+// IProgress properties
+////////////////////////////////////////////////////////////////////////////////
+
+STDMETHODIMP ProgressProxy::COMGETTER(Cancelable)(BOOL *aCancelable)
+{
+ CheckComArgOutPointerValid(aCancelable);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* ASSUME: The cancelable property can only change to FALSE. */
+ if (!mCancelable || mptrOtherProgress.isNull())
+ *aCancelable = mCancelable;
+ else
+ {
+ hrc = mptrOtherProgress->COMGETTER(Cancelable)(aCancelable);
+ if (SUCCEEDED(hrc) && !*aCancelable)
+ {
+ LogFlowThisFunc(("point-of-no-return reached\n"));
+ mCancelable = FALSE;
+ }
+ }
+ }
+ return hrc;
+}
+
+STDMETHODIMP ProgressProxy::COMGETTER(Percent)(ULONG *aPercent)
+{
+ CheckComArgOutPointerValid(aPercent);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mptrOtherProgress.isNull())
+ hrc = Progress::COMGETTER(Percent)(aPercent);
+ else
+ {
+ /*
+ * Get the overall percent of the other object and adjust it with
+ * the weighting given to the period before proxying started.
+ */
+ ULONG uPct;
+ hrc = mptrOtherProgress->COMGETTER(Percent)(&uPct);
+ if (SUCCEEDED(hrc))
+ {
+ double rdPercent = ((double)uPct / 100 * muOtherProgressWeight + muOtherProgressStartWeight)
+ / m_ulTotalOperationsWeight * 100;
+ *aPercent = RT_MIN((ULONG)rdPercent, 99); /* mptrOtherProgress is cleared when its completed,
+ so we can never return 100%. */
+ }
+ }
+ }
+ return hrc;
+}
+
+STDMETHODIMP ProgressProxy::COMGETTER(TimeRemaining)(LONG *aTimeRemaining)
+{
+ CheckComArgOutPointerValid(aTimeRemaining);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mptrOtherProgress.isNull())
+ hrc = Progress::COMGETTER(TimeRemaining)(aTimeRemaining);
+ else
+ hrc = mptrOtherProgress->COMGETTER(TimeRemaining)(aTimeRemaining);
+ }
+ return hrc;
+}
+
+STDMETHODIMP ProgressProxy::COMGETTER(Completed)(BOOL *aCompleted)
+{
+ /* Not proxied since we EXPECT a normal completion notification call. */
+ return Progress::COMGETTER(Completed)(aCompleted);
+}
+
+STDMETHODIMP ProgressProxy::COMGETTER(Canceled)(BOOL *aCanceled)
+{
+ CheckComArgOutPointerValid(aCanceled);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /* Check the local data first, then the other object. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ hrc = Progress::COMGETTER(Canceled)(aCanceled);
+ if ( SUCCEEDED(hrc)
+ && !*aCanceled
+ && !mptrOtherProgress.isNull()
+ && mCancelable)
+ {
+ hrc = mptrOtherProgress->COMGETTER(Canceled)(aCanceled);
+ if (SUCCEEDED(hrc) && *aCanceled)
+ /* This will not complete the object, only mark it as canceled. */
+ clearOtherProgressObjectInternal(false /*fEarly*/);
+ }
+ }
+ return hrc;
+}
+
+STDMETHODIMP ProgressProxy::COMGETTER(ResultCode)(LONG *aResultCode)
+{
+ /* Not proxied since we EXPECT a normal completion notification call. */
+ return Progress::COMGETTER(ResultCode)(aResultCode);
+}
+
+STDMETHODIMP ProgressProxy::COMGETTER(ErrorInfo)(IVirtualBoxErrorInfo **aErrorInfo)
+{
+ /* Not proxied since we EXPECT a normal completion notification call. */
+ return Progress::COMGETTER(ErrorInfo)(aErrorInfo);
+}
+
+STDMETHODIMP ProgressProxy::COMGETTER(Operation)(ULONG *aOperation)
+{
+ CheckComArgOutPointerValid(aOperation);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mptrOtherProgress.isNull())
+ hrc = Progress::COMGETTER(Operation)(aOperation);
+ else
+ {
+ ULONG uCurOtherOperation;
+ hrc = mptrOtherProgress->COMGETTER(Operation)(&uCurOtherOperation);
+ if (SUCCEEDED(hrc))
+ *aOperation = uCurOtherOperation + muOtherProgressStartOperation;
+ }
+ }
+ return hrc;
+}
+
+STDMETHODIMP ProgressProxy::COMGETTER(OperationDescription)(BSTR *aOperationDescription)
+{
+ CheckComArgOutPointerValid(aOperationDescription);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mptrOtherProgress.isNull() || !mfMultiOperation)
+ hrc = Progress::COMGETTER(OperationDescription)(aOperationDescription);
+ else
+ hrc = mptrOtherProgress->COMGETTER(OperationDescription)(aOperationDescription);
+ }
+ return hrc;
+}
+
+STDMETHODIMP ProgressProxy::COMGETTER(OperationPercent)(ULONG *aOperationPercent)
+{
+ CheckComArgOutPointerValid(aOperationPercent);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mptrOtherProgress.isNull() || !mfMultiOperation)
+ hrc = Progress::COMGETTER(OperationPercent)(aOperationPercent);
+ else
+ hrc = mptrOtherProgress->COMGETTER(OperationPercent)(aOperationPercent);
+ }
+ return hrc;
+}
+
+STDMETHODIMP ProgressProxy::COMSETTER(Timeout)(ULONG aTimeout)
+{
+ /* Not currently supported. */
+ NOREF(aTimeout);
+ AssertFailed();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP ProgressProxy::COMGETTER(Timeout)(ULONG *aTimeout)
+{
+ /* Not currently supported. */
+ CheckComArgOutPointerValid(aTimeout);
+
+ AssertFailed();
+ return E_NOTIMPL;
+}
+
+// IProgress methods
+/////////////////////////////////////////////////////////////////////////////
+
+STDMETHODIMP ProgressProxy::WaitForCompletion(LONG aTimeout)
+{
+ HRESULT hrc;
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aTimeout=%d\n", aTimeout));
+
+ /* No need to wait on the proxied object for these since we'll get the
+ normal completion notifications. */
+ hrc = Progress::WaitForCompletion(aTimeout);
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+STDMETHODIMP ProgressProxy::WaitForOperationCompletion(ULONG aOperation, LONG aTimeout)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aOperation=%d aTimeout=%d\n", aOperation, aTimeout));
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CheckComArgExpr(aOperation, aOperation < m_cOperations);
+
+ /*
+ * Check if we can wait locally.
+ */
+ if ( aOperation + 1 == m_cOperations /* final operation */
+ || mptrOtherProgress.isNull())
+ {
+ /* ASSUMES that Progress::WaitForOperationCompletion is using
+ AutoWriteLock::leave() as it saves us from duplicating the code! */
+ hrc = Progress::WaitForOperationCompletion(aOperation, aTimeout);
+ }
+ else
+ {
+ LogFlowThisFunc(("calling the other object...\n"));
+ ComPtr<IProgress> ptrOtherProgress = mptrOtherProgress;
+ alock.release();
+
+ hrc = ptrOtherProgress->WaitForOperationCompletion(aOperation, aTimeout);
+ }
+ }
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+STDMETHODIMP ProgressProxy::Cancel()
+{
+ LogFlowThisFunc(("\n"));
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.rc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mptrOtherProgress.isNull() || !mCancelable)
+ hrc = Progress::Cancel();
+ else
+ {
+ hrc = mptrOtherProgress->Cancel();
+ if (SUCCEEDED(hrc))
+ clearOtherProgressObjectInternal(false /*fEarly*/);
+ }
+ }
+
+ LogFlowThisFunc(("returns %Rhrc\n", hrc));
+ return hrc;
+}
+
+STDMETHODIMP ProgressProxy::SetCurrentOperationProgress(ULONG aPercent)
+{
+ /* Not supported - why do we actually expose this? */
+ NOREF(aPercent);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP ProgressProxy::SetNextOperation(IN_BSTR bstrNextOperationDescription, ULONG ulNextOperationsWeight)
+{
+ /* Not supported - why do we actually expose this? */
+ NOREF(bstrNextOperationDescription);
+ NOREF(ulNextOperationsWeight);
+ return E_NOTIMPL;
+}
+
+#ifdef VBOX_WITH_XPCOM
+NS_DECL_CLASSINFO(ProgressProxy)
+NS_IMPL_THREADSAFE_ISUPPORTS1_CI(ProgressProxy, IProgress)
+#endif
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/RecordingScreenSettingsImpl.cpp b/src/VBox/Main/src-server/RecordingScreenSettingsImpl.cpp
new file mode 100644
index 00000000..5381990f
--- /dev/null
+++ b/src/VBox/Main/src-server/RecordingScreenSettingsImpl.cpp
@@ -0,0 +1,1250 @@
+/* $Id: RecordingScreenSettingsImpl.cpp $ */
+/** @file
+ *
+ * VirtualBox COM class implementation - Recording settings of one virtual screen.
+ */
+
+/*
+ * Copyright (C) 2018-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_RECORDINGSCREENSETTINGS
+#include "LoggingNew.h"
+
+#include "RecordingScreenSettingsImpl.h"
+#include "RecordingSettingsImpl.h"
+#include "MachineImpl.h"
+
+#include <iprt/asm.h> /* For ASMAtomicXXX. */
+#include <iprt/path.h>
+#include <iprt/cpp/utils.h>
+#include <VBox/settings.h>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "Global.h"
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// RecordScreenSettings private data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct RecordingScreenSettings::Data
+{
+ Data()
+ : pParent(NULL)
+ , cRefs(0)
+ { }
+
+ RecordingSettings * const pParent;
+ const ComObjPtr<RecordingScreenSettings> pPeer;
+ uint32_t uScreenId;
+ /** Internal reference count to track sharing of this screen settings object among
+ * other recording settings objects. */
+ int32_t cRefs;
+
+ // use the XML settings structure in the members for simplicity
+ Backupable<settings::RecordingScreenSettings> bd;
+};
+
+// constructor / destructor
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(RecordingScreenSettings)
+
+HRESULT RecordingScreenSettings::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void RecordingScreenSettings::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the recording screen settings object.
+ *
+ * @returns COM result indicator
+ */
+HRESULT RecordingScreenSettings::init(RecordingSettings *aParent, uint32_t uScreenId,
+ const settings::RecordingScreenSettings& aThat)
+{
+ LogFlowThisFunc(("aParent: %p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ /* Share the parent & machine weakly. */
+ unconst(m->pParent) = aParent;
+ /* mPeer is left null. */
+
+ /* Simply copy the settings data. */
+ m->uScreenId = uScreenId;
+ m->bd.allocate();
+ m->bd->operator=(aThat);
+
+ HRESULT hrc = S_OK;
+
+ int vrc = i_initInternal();
+ if (RT_SUCCESS(vrc))
+ {
+ autoInitSpan.setSucceeded();
+ }
+ else
+ {
+ autoInitSpan.setFailed();
+ hrc = E_UNEXPECTED;
+ }
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * Initializes the recording settings object given another recording settings object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ */
+HRESULT RecordingScreenSettings::init(RecordingSettings *aParent, RecordingScreenSettings *aThat)
+{
+ LogFlowThisFunc(("aParent: %p, aThat: %p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pParent) = aParent;
+ unconst(m->pPeer) = aThat;
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS);
+
+ m->uScreenId = aThat->m->uScreenId;
+ m->bd.share(aThat->m->bd);
+
+ HRESULT hrc = S_OK;
+
+ int vrc = i_initInternal();
+ if (RT_SUCCESS(vrc))
+ {
+ autoInitSpan.setSucceeded();
+ }
+ else
+ {
+ autoInitSpan.setFailed();
+ hrc = E_UNEXPECTED;
+ }
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * Initializes the guest object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ */
+HRESULT RecordingScreenSettings::initCopy(RecordingSettings *aParent, RecordingScreenSettings *aThat)
+{
+ LogFlowThisFunc(("aParent: %p, aThat: %p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pParent) = aParent;
+ /* mPeer is left null. */
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS);
+
+ m->uScreenId = aThat->m->uScreenId;
+ m->bd.attachCopy(aThat->m->bd);
+
+ HRESULT hrc = S_OK;
+
+ int vrc = i_initInternal();
+ if (RT_SUCCESS(vrc))
+ {
+ autoInitSpan.setSucceeded();
+ }
+ else
+ {
+ autoInitSpan.setFailed();
+ hrc = E_UNEXPECTED;
+ }
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void RecordingScreenSettings::uninit()
+{
+ LogThisFunc(("%p\n", this));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ /* Make sure nobody holds an internal reference to it anymore. */
+ AssertReturnVoid(m->cRefs == 0);
+
+ m->bd.free();
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pParent) = NULL;
+
+ delete m;
+ m = NULL;
+
+ LogFlowThisFuncLeave();
+}
+
+HRESULT RecordingScreenSettings::isFeatureEnabled(RecordingFeature_T aFeature, BOOL *aEnabled)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ settings::RecordingFeatureMap::const_iterator itFeature = m->bd->featureMap.find(aFeature);
+
+ *aEnabled = ( itFeature != m->bd->featureMap.end()
+ && itFeature->second == true);
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getId(ULONG *id)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *id = m->uScreenId;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getEnabled(BOOL *enabled)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *enabled = m->bd->fEnabled ? TRUE : FALSE;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setEnabled(BOOL enabled)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFlowThisFunc(("Screen %RU32\n", m->uScreenId));
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change enabled state of screen while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->fEnabled != RT_BOOL(enabled))
+ {
+ m->bd.backup();
+ m->bd->fEnabled = RT_BOOL(enabled);
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ LogFlowThisFunc(("Screen %RU32\n", m->uScreenId));
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getFeatures(std::vector<RecordingFeature_T> &aFeatures)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aFeatures.clear();
+
+ settings::RecordingFeatureMap::const_iterator itFeature = m->bd->featureMap.begin();
+ while (itFeature != m->bd->featureMap.end())
+ {
+ if (itFeature->second) /* Is feature enable? */
+ aFeatures.push_back(itFeature->first);
+
+ ++itFeature;
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setFeatures(const std::vector<RecordingFeature_T> &aFeatures)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change features while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+
+ settings::RecordingFeatureMap featureMapOld = m->bd->featureMap;
+ m->bd->featureMap.clear();
+
+ for (size_t i = 0; i < aFeatures.size(); i++)
+ {
+ switch (aFeatures[i])
+ {
+ case RecordingFeature_Audio:
+ m->bd->featureMap[RecordingFeature_Audio] = true;
+ break;
+
+ case RecordingFeature_Video:
+ m->bd->featureMap[RecordingFeature_Video] = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (m->bd->featureMap != featureMapOld)
+ {
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getDestination(RecordingDestination_T *aDestination)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aDestination = m->bd->enmDest;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setDestination(RecordingDestination_T aDestination)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change destination type while recording is enabled"));
+
+ if (aDestination != RecordingDestination_File)
+ return setError(E_INVALIDARG, tr("Destination type invalid / not supported"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->enmDest != aDestination)
+ {
+ m->bd.backup();
+ m->bd->enmDest = aDestination;
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getFilename(com::Utf8Str &aFilename)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Get default file name if an empty string or a single "." is set. */
+ if ( m->bd->File.strName.isEmpty()
+ || m->bd->File.strName.equals("."))
+ {
+ int vrc = m->pParent->i_getDefaultFilename(aFilename, m->uScreenId, true /* fWithFileExtension */);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Error retrieving default file name"));
+
+ /* Important: Don't assign the default file name to File.strName, as this woulnd't be considered
+ * as default settings anymore! */
+ }
+ else /* Return custom file name. */
+ aFilename = m->bd->File.strName;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setFilename(const com::Utf8Str &aFilename)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change file name while recording is enabled"));
+
+ if (aFilename.isNotEmpty())
+ {
+ if (!RTPathStartsWithRoot(aFilename.c_str()))
+ return setError(E_INVALIDARG, tr("Recording file name '%s' is not absolute"), aFilename.c_str());
+ }
+
+ /** @todo Add more sanity? */
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Note: When setting an empty file name, this will return the screen's default file name when using ::getFileName(). */
+ if (m->bd->File.strName != aFilename)
+ {
+ Utf8Str strName;
+ int vrc = m->pParent->i_getFilename(strName, m->uScreenId, aFilename);
+ if (RT_SUCCESS(vrc))
+ {
+ m->bd.backup();
+ m->bd->File.strName = strName;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+ else
+ return setErrorBoth(E_ACCESSDENIED, vrc, tr("Could not set file name for recording screen"));
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getMaxTime(ULONG *aMaxTimeS)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aMaxTimeS = m->bd->ulMaxTimeS;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setMaxTime(ULONG aMaxTimeS)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change maximum time while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->ulMaxTimeS != aMaxTimeS)
+ {
+ m->bd.backup();
+ m->bd->ulMaxTimeS = aMaxTimeS;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getMaxFileSize(ULONG *aMaxFileSizeMB)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aMaxFileSizeMB = m->bd->File.ulMaxSizeMB;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setMaxFileSize(ULONG aMaxFileSize)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change maximum file size while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->File.ulMaxSizeMB != aMaxFileSize)
+ {
+ m->bd.backup();
+ m->bd->File.ulMaxSizeMB = aMaxFileSize;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getOptions(com::Utf8Str &aOptions)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aOptions = m->bd->strOptions;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setOptions(const com::Utf8Str &aOptions)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change options while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Note: Parsing and validation is done at codec level. */
+
+ m->bd.backup();
+ m->bd->strOptions = aOptions;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getAudioCodec(RecordingAudioCodec_T *aCodec)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCodec = m->bd->Audio.enmCodec;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setAudioCodec(RecordingAudioCodec_T aCodec)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change audio codec while recording is enabled"));
+
+ if (aCodec != RecordingAudioCodec_OggVorbis)
+ return setError(E_INVALIDARG, tr("Audio codec not supported"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->Audio.enmCodec != aCodec)
+ {
+ m->bd.backup();
+ m->bd->Audio.enmCodec = aCodec;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getAudioDeadline(RecordingCodecDeadline_T *aDeadline)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aDeadline = m->bd->Audio.enmDeadline;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setAudioDeadline(RecordingCodecDeadline_T aDeadline)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change audio deadline while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->Audio.enmDeadline != aDeadline)
+ {
+ m->bd.backup();
+ m->bd->Audio.enmDeadline = aDeadline;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getAudioRateControlMode(RecordingRateControlMode_T *aMode)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aMode = RecordingRateControlMode_VBR; /** @todo Implement CBR. */
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setAudioRateControlMode(RecordingRateControlMode_T aMode)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change audio rate control mode while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo Implement this. */
+ RT_NOREF(aMode);
+
+ return E_NOTIMPL;
+}
+
+HRESULT RecordingScreenSettings::getAudioHz(ULONG *aHz)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aHz = m->bd->Audio.uHz;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setAudioHz(ULONG aHz)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change audio Hertz rate while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->Audio.uHz != (uint16_t)aHz)
+ {
+ m->bd.backup();
+ m->bd->Audio.uHz = (uint16_t)aHz;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getAudioBits(ULONG *aBits)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aBits = m->bd->Audio.cBits;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setAudioBits(ULONG aBits)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change audio bits while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->Audio.cBits != (uint8_t)aBits)
+ {
+ m->bd.backup();
+ m->bd->Audio.cBits = (uint8_t)aBits;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getAudioChannels(ULONG *aChannels)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aChannels = m->bd->Audio.cChannels;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setAudioChannels(ULONG aChannels)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change audio channels while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->Audio.cChannels != (uint8_t)aChannels)
+ {
+ m->bd.backup();
+ m->bd->Audio.cChannels = (uint8_t)aChannels;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getVideoCodec(RecordingVideoCodec_T *aCodec)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCodec = m->bd->Video.enmCodec;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setVideoCodec(RecordingVideoCodec_T aCodec)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change video codec while recording is enabled"));
+
+ if (aCodec != RecordingVideoCodec_VP8)
+ return setError(E_INVALIDARG, tr("Video codec not supported"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->Video.enmCodec != aCodec)
+ {
+ m->bd.backup();
+ m->bd->Video.enmCodec = aCodec;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getVideoDeadline(RecordingCodecDeadline_T *aDeadline)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aDeadline = m->bd->Video.enmDeadline;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setVideoDeadline(RecordingCodecDeadline_T aDeadline)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change video deadline while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->Video.enmDeadline != aDeadline)
+ {
+ m->bd.backup();
+ m->bd->Video.enmDeadline = aDeadline;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getVideoWidth(ULONG *aVideoWidth)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aVideoWidth = m->bd->Video.ulWidth;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setVideoWidth(ULONG aVideoWidth)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change video width while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->Video.ulWidth != aVideoWidth)
+ {
+ m->bd.backup();
+ m->bd->Video.ulWidth = aVideoWidth;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getVideoHeight(ULONG *aVideoHeight)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aVideoHeight = m->bd->Video.ulHeight;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setVideoHeight(ULONG aVideoHeight)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change video height while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->Video.ulHeight != aVideoHeight)
+ {
+ m->bd.backup();
+ m->bd->Video.ulHeight = aVideoHeight;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getVideoRate(ULONG *aVideoRate)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aVideoRate = m->bd->Video.ulRate;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setVideoRate(ULONG aVideoRate)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change video rate while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->Video.ulRate != aVideoRate)
+ {
+ m->bd.backup();
+ m->bd->Video.ulRate = aVideoRate;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getVideoRateControlMode(RecordingRateControlMode_T *aMode)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aMode = RecordingRateControlMode_VBR; /** @todo Implement CBR. */
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setVideoRateControlMode(RecordingRateControlMode_T aMode)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change video rate control mode while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo Implement this. */
+ RT_NOREF(aMode);
+
+ return E_NOTIMPL;
+}
+
+HRESULT RecordingScreenSettings::getVideoFPS(ULONG *aVideoFPS)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aVideoFPS = m->bd->Video.ulFPS;
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setVideoFPS(ULONG aVideoFPS)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change video FPS while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->Video.ulFPS != aVideoFPS)
+ {
+ m->bd.backup();
+ m->bd->Video.ulFPS = aVideoFPS;
+
+ alock.release();
+
+ m->pParent->i_onSettingsChanged();
+ }
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::getVideoScalingMode(RecordingVideoScalingMode_T *aMode)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aMode = RecordingVideoScalingMode_None; /** @todo Implement this. */
+
+ return S_OK;
+}
+
+HRESULT RecordingScreenSettings::setVideoScalingMode(RecordingVideoScalingMode_T aMode)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (!m->pParent->i_canChangeSettings())
+ return setError(E_INVALIDARG, tr("Cannot change video scaling mode while recording is enabled"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo Implement this. */
+ RT_NOREF(aMode);
+
+ return E_NOTIMPL;
+}
+
+/**
+ * Initializes data, internal version.
+ *
+ * @returns VBox status code.
+ */
+int RecordingScreenSettings::i_initInternal(void)
+{
+ AssertPtrReturn(m, VERR_INVALID_POINTER);
+
+ i_reference();
+
+ switch (m->bd->enmDest)
+ {
+ case RecordingDestination_File:
+ {
+ /* Note: Leave the file name empty here, which means using the default setting.
+ * Important when comparing with the default settings! */
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+// public methods only for internal purposes
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Loads settings from the given machine node.
+ * May be called once right after this object creation.
+ *
+ * @returns HRESULT
+ * @param data Configuration settings to load.
+ */
+HRESULT RecordingScreenSettings::i_loadSettings(const settings::RecordingScreenSettings &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // simply copy
+ m->bd.assignCopy(&data);
+ return S_OK;
+}
+
+/**
+ * Saves settings to the given machine node.
+ *
+ * @returns HRESULT
+ * @param data Configuration settings to save to.
+ */
+HRESULT RecordingScreenSettings::i_saveSettings(settings::RecordingScreenSettings &data)
+{
+ LogThisFunc(("%p: Screen %RU32\n", this, m ? m->uScreenId : UINT32_MAX));
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ data = *m->bd.data();
+
+ return S_OK;
+}
+
+void RecordingScreenSettings::i_rollback(void)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->bd.rollback();
+}
+
+void RecordingScreenSettings::i_commit(void)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ {
+ m->bd.commit();
+ if (m->pPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ AutoWriteLock peerlock(m->pPeer COMMA_LOCKVAL_SRC_POS);
+ m->pPeer->m->bd.attach(m->bd);
+ }
+ }
+}
+
+void RecordingScreenSettings::i_copyFrom(RecordingScreenSettings *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ m->bd.assignCopy(aThat->m->bd);
+}
+
+/**
+ * Applies default screen recording settings.
+ *
+ * @note Locks this object for writing.
+ */
+void RecordingScreenSettings::i_applyDefaults(void)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd->applyDefaults();
+}
+
+settings::RecordingScreenSettings &RecordingScreenSettings::i_getData(void)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRC(autoCaller.rc());
+
+ AssertPtr(m);
+ return *m->bd.data();
+}
+
+/**
+ * Increments the reference count.
+ *
+ * @returns New reference count.
+ *
+ * @note Internal reference count, to track object sharing across different recording settings objects
+ * which share the same screen recording data.
+ */
+int32_t RecordingScreenSettings::i_reference(void)
+{
+ int cNewRefs = ASMAtomicIncS32(&m->cRefs); RT_NOREF(cNewRefs);
+ LogThisFunc(("%p: cRefs -> %RI32\n", this, cNewRefs));
+ return cNewRefs;
+}
+
+/**
+ * Decrements the reference count.
+ *
+ * @returns New reference count.
+ *
+ * @note Internal reference count, to track object sharing across different recording settings objects
+ * which share the same screen recording data.
+ */
+int32_t RecordingScreenSettings::i_release(void)
+{
+ int32_t cNewRefs = ASMAtomicDecS32(&m->cRefs); RT_NOREF(cNewRefs);
+ LogThisFunc(("%p: cRefs -> %RI32\n", this, cNewRefs));
+ AssertReturn(cNewRefs >= 0, 0);
+ return cNewRefs;
+}
+
+/**
+ * Returns the current reference count.
+ *
+ * @returns Current reference count.
+ *
+ * @note Internal reference count, to track object sharing across different recording settings objects
+ * which share the same screen recording data.
+ */
+int32_t RecordingScreenSettings::i_getReferences(void)
+{
+ return ASMAtomicReadS32(&m->cRefs);
+}
diff --git a/src/VBox/Main/src-server/RecordingSettingsImpl.cpp b/src/VBox/Main/src-server/RecordingSettingsImpl.cpp
new file mode 100644
index 00000000..9238d497
--- /dev/null
+++ b/src/VBox/Main/src-server/RecordingSettingsImpl.cpp
@@ -0,0 +1,866 @@
+/* $Id: RecordingSettingsImpl.cpp $ */
+/** @file
+ *
+ * VirtualBox COM class implementation - Machine capture settings.
+ */
+
+/*
+ * Copyright (C) 2018-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_RECORDINGSETTINGS
+#include "LoggingNew.h"
+
+#include "RecordingSettingsImpl.h"
+#include "RecordingScreenSettingsImpl.h"
+#include "MachineImpl.h"
+
+#include <iprt/cpp/utils.h>
+#include <VBox/settings.h>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "Global.h"
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// RecordSettings private data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct RecordingSettings::Data
+{
+ Data()
+ : pMachine(NULL)
+ { }
+
+ Machine * const pMachine;
+ const ComObjPtr<RecordingSettings> pPeer;
+ RecordingScreenSettingsObjMap mapScreenObj;
+
+ // use the XML settings structure in the members for simplicity
+ Backupable<settings::RecordingCommonSettings> bd;
+};
+
+DEFINE_EMPTY_CTOR_DTOR(RecordingSettings)
+
+HRESULT RecordingSettings::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void RecordingSettings::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+/**
+ * Initializes the recording settings object.
+ *
+ * @returns COM result indicator
+ */
+HRESULT RecordingSettings::init(Machine *aParent)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ /* share the parent weakly */
+ unconst(m->pMachine) = aParent;
+
+ m->bd.allocate();
+
+ i_applyDefaults();
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Initializes the capture settings object given another capture settings object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT RecordingSettings::init(Machine *aParent, RecordingSettings *aThat)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p, aThat: %p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ unconst(m->pPeer) = aThat;
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.share(aThat->m->bd);
+
+ /* Make sure to add a reference when sharing the screen objects with aThat. */
+ for (RecordingScreenSettingsObjMap::const_iterator itScreenThat = aThat->m->mapScreenObj.begin();
+ itScreenThat != aThat->m->mapScreenObj.end();
+ ++itScreenThat)
+ itScreenThat->second->i_reference();
+
+ m->mapScreenObj = aThat->m->mapScreenObj;
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Initializes the guest object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT RecordingSettings::initCopy(Machine *aParent, RecordingSettings *aThat)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p, aThat: %p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ // mPeer is left null
+
+ AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.attachCopy(aThat->m->bd);
+
+ HRESULT hrc = S_OK;
+
+ for (RecordingScreenSettingsObjMap::const_iterator itScreenThat = aThat->m->mapScreenObj.begin();
+ itScreenThat != aThat->m->mapScreenObj.end();
+ ++itScreenThat)
+ {
+ ComObjPtr<RecordingScreenSettings> pSettings;
+ pSettings.createObject();
+ hrc = pSettings->initCopy(this, itScreenThat->second);
+ if (FAILED(hrc)) return hrc;
+
+ try
+ {
+ m->mapScreenObj[itScreenThat->first] = pSettings;
+ }
+ catch (...)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void RecordingSettings::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ /* Make sure to destroy screen objects attached to this object.
+ * Note: This also decrements the refcount of a screens object, in case it's shared among other recording settings. */
+ i_destroyAllScreenObj(m->mapScreenObj);
+
+ m->bd.free();
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pMachine) = NULL;
+
+ delete m;
+ m = NULL;
+
+ LogFlowThisFuncLeave();
+}
+
+// IRecordSettings properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT RecordingSettings::getEnabled(BOOL *enabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *enabled = m->bd->fEnabled;
+
+ return S_OK;
+}
+
+HRESULT RecordingSettings::setEnabled(BOOL enable)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const bool fEnabled = RT_BOOL(enable);
+
+ HRESULT hrc = S_OK;
+
+ if (m->bd->fEnabled != fEnabled)
+ {
+ m->bd.backup();
+ m->bd->fEnabled = fEnabled;
+
+ alock.release();
+
+ hrc = m->pMachine->i_onRecordingChange(enable);
+ if (FAILED(hrc))
+ {
+ com::ErrorInfo errMachine; /* Get error info from machine call above. */
+
+ /*
+ * Normally we would do the actual change _after_ i_onRecordingChange() succeeded.
+ * We cannot do this because that function uses RecordSettings::GetEnabled to
+ * determine if it should start or stop capturing. Therefore we need to manually
+ * undo change.
+ */
+ alock.acquire();
+ m->bd->fEnabled = m->bd.backedUpData()->fEnabled;
+
+ if (errMachine.isBasicAvailable())
+ hrc = setError(errMachine);
+ }
+ else
+ {
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // pMachine is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_Recording);
+
+ /* Make sure to release the mutable dependency lock from above before
+ * actually saving the settings. */
+ adep.release();
+
+ /** Save settings if online - @todo why is this required? -- @bugref{6818} */
+ if (Global::IsOnline(m->pMachine->i_getMachineState()))
+ {
+ com::ErrorInfo errMachine;
+ hrc = m->pMachine->i_saveSettings(NULL, mlock);
+ if (FAILED(hrc))
+ {
+ /* Got error info from machine call above. */
+ if (errMachine.isBasicAvailable())
+ hrc = setError(errMachine);
+ }
+ }
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT RecordingSettings::getScreens(std::vector<ComPtr<IRecordingScreenSettings> > &aRecordScreenSettings)
+{
+ LogFlowThisFuncEnter();
+
+ AssertPtr(m->pMachine);
+ ComPtr<IGraphicsAdapter> pGraphicsAdapter;
+ m->pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
+ ULONG cMonitors = 0;
+ if (!pGraphicsAdapter.isNull())
+ pGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitors);
+
+ i_syncToMachineDisplays(cMonitors);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ try
+ {
+ aRecordScreenSettings.clear();
+ aRecordScreenSettings.resize(m->mapScreenObj.size());
+ }
+ catch (...)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+
+ if (FAILED(hrc))
+ return hrc;
+
+ RecordingScreenSettingsObjMap::const_iterator itScreenObj = m->mapScreenObj.begin();
+ size_t i = 0;
+ while (itScreenObj != m->mapScreenObj.end())
+ {
+ itScreenObj->second.queryInterfaceTo(aRecordScreenSettings[i].asOutParam());
+ AssertBreakStmt(aRecordScreenSettings[i].isNotNull(), hrc = E_POINTER);
+ ++i;
+ ++itScreenObj;
+ }
+
+ Assert(aRecordScreenSettings.size() == m->mapScreenObj.size());
+
+ return hrc;
+}
+
+HRESULT RecordingSettings::getScreenSettings(ULONG uScreenId, ComPtr<IRecordingScreenSettings> &aRecordScreenSettings)
+{
+ LogFlowThisFuncEnter();
+
+ AssertPtr(m->pMachine);
+ ComPtr<IGraphicsAdapter> pGraphicsAdapter;
+ m->pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
+ ULONG cMonitors = 0;
+ if (!pGraphicsAdapter.isNull())
+ pGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitors);
+
+ i_syncToMachineDisplays(cMonitors);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (uScreenId + 1 > m->mapScreenObj.size())
+ return setError(E_INVALIDARG, tr("Invalid screen ID specified"));
+
+ RecordingScreenSettingsObjMap::const_iterator itScreen = m->mapScreenObj.find(uScreenId);
+ if (itScreen != m->mapScreenObj.end())
+ {
+ itScreen->second.queryInterfaceTo(aRecordScreenSettings.asOutParam());
+ return S_OK;
+ }
+
+ return VBOX_E_OBJECT_NOT_FOUND;
+}
+
+// IRecordSettings methods
+/////////////////////////////////////////////////////////////////////////////
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Adds a screen settings object to a particular map.
+ *
+ * @returns IPRT status code. VERR_ALREADY_EXISTS if the object in question already exists.
+ * @param screenSettingsMap Map to add screen settings to.
+ * @param idScreen Screen ID to add settings for.
+ * @param data Recording screen settings to use for that screen.
+ */
+int RecordingSettings::i_createScreenObj(RecordingScreenSettingsObjMap &screenSettingsMap,
+ uint32_t idScreen, const settings::RecordingScreenSettings &data)
+{
+ AssertReturn(screenSettingsMap.find(idScreen) == screenSettingsMap.end(), VERR_ALREADY_EXISTS);
+
+ int vrc = VINF_SUCCESS;
+
+ ComObjPtr<RecordingScreenSettings> recordingScreenSettings;
+ HRESULT hrc = recordingScreenSettings.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = recordingScreenSettings->init(this, idScreen, data);
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ screenSettingsMap[idScreen] = recordingScreenSettings;
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ }
+
+ LogThisFunc(("%p: Screen %RU32 -> %Rrc\n", recordingScreenSettings.m_p, idScreen, vrc));
+ return vrc;
+}
+
+/**
+ * Removes a screen settings object from a particular map.
+ *
+ * If the internal reference count hits 0, the screen settings object will be destroyed.
+ * This means that this screen settings object is not being used anymore by other recording settings (as shared data).
+ *
+ * @returns IPRT status code.
+ * @retval VERR_NOT_FOUND if specified screen was not found.
+ * @param screenSettingsMap Map to remove screen settings from.
+ * @param idScreen ID of screen to remove.
+ */
+int RecordingSettings::i_destroyScreenObj(RecordingScreenSettingsObjMap &screenSettingsMap, uint32_t idScreen)
+{
+ AssertReturn(screenSettingsMap.find(idScreen) != screenSettingsMap.end(), VERR_NOT_FOUND);
+
+ RecordingScreenSettingsObjMap::iterator itScreen = screenSettingsMap.find(idScreen);
+
+ /* Make sure to consume the pointer before the one of the
+ * iterator gets released. */
+ ComObjPtr<RecordingScreenSettings> pScreenSettings = itScreen->second;
+
+ screenSettingsMap.erase(itScreen);
+
+ LogThisFunc(("%p: Screen %RU32, cRefs=%RI32\n", pScreenSettings.m_p, idScreen, pScreenSettings->i_getReferences()));
+
+ pScreenSettings->i_release();
+
+ /* Only destroy the object if nobody else keeps a reference to it anymore. */
+ if (pScreenSettings->i_getReferences() == 0)
+ {
+ LogThisFunc(("%p: Screen %RU32 -> Null\n", pScreenSettings.m_p, idScreen));
+ pScreenSettings.setNull();
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys all screen settings objects of a particular map.
+ *
+ * @returns IPRT status code.
+ * @param screenSettingsMap Map to destroy screen settings objects for.
+ */
+int RecordingSettings::i_destroyAllScreenObj(RecordingScreenSettingsObjMap &screenSettingsMap)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc = VINF_SUCCESS;
+
+ RecordingScreenSettingsObjMap::iterator itScreen = screenSettingsMap.begin();
+ while (itScreen != screenSettingsMap.end())
+ {
+ vrc = i_destroyScreenObj(screenSettingsMap, itScreen->first);
+ if (RT_FAILURE(vrc))
+ break;
+
+ itScreen = screenSettingsMap.begin();
+ }
+
+ Assert(screenSettingsMap.size() == 0);
+ return vrc;
+}
+
+/**
+ * Loads settings from the given settings.
+ * May be called once right after this object creation.
+ *
+ * @param data Capture settings to load from.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT RecordingSettings::i_loadSettings(const settings::RecordingSettings &data)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ LogFlowThisFunc(("Data has %zu screens\n", data.mapScreens.size()));
+
+ settings::RecordingScreenSettingsMap::const_iterator itScreenData = data.mapScreens.begin();
+ while (itScreenData != data.mapScreens.end())
+ {
+ RecordingScreenSettingsObjMap::iterator itScreen = m->mapScreenObj.find(itScreenData->first);
+ if (itScreen != m->mapScreenObj.end())
+ {
+ hrc = itScreen->second->i_loadSettings(itScreenData->second);
+ if (FAILED(hrc))
+ break;
+ }
+ else
+ {
+ int vrc = i_createScreenObj(m->mapScreenObj,
+ itScreenData->first /* uScreenId */, itScreenData->second /* Settings */);
+ if (RT_FAILURE(vrc))
+ {
+ hrc = E_OUTOFMEMORY; /* Most likely. */
+ break;
+ }
+ }
+
+ ++itScreenData;
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ ComAssertComRCRet(hrc, hrc);
+ AssertReturn(m->mapScreenObj.size() == data.mapScreens.size(), E_UNEXPECTED);
+
+ // simply copy
+ m->bd.assignCopy(&data.common);
+ }
+
+ LogFlowThisFunc(("Returning %Rhrc\n", hrc));
+ return hrc;
+}
+
+/**
+ * Resets the internal object state by destroying all screen settings objects.
+ */
+void RecordingSettings::i_reset(void)
+{
+ LogFlowThisFuncEnter();
+
+ i_destroyAllScreenObj(m->mapScreenObj);
+}
+
+/**
+ * Saves settings to the given settings.
+ *
+ * @param data Where to store the capture settings to.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT RecordingSettings::i_saveSettings(settings::RecordingSettings &data)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AssertPtr(m->pMachine);
+ ComPtr<IGraphicsAdapter> pGraphicsAdapter;
+ m->pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
+ ULONG cMonitors = 0;
+ if (!pGraphicsAdapter.isNull())
+ pGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitors);
+
+ int rc2 = i_syncToMachineDisplays(cMonitors);
+ AssertRC(rc2);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ data.common = *m->bd.data();
+
+ HRESULT hrc = S_OK;
+
+ for (RecordingScreenSettingsObjMap::const_iterator itScreen = m->mapScreenObj.begin();
+ itScreen != m->mapScreenObj.end();
+ ++itScreen)
+ {
+ hrc = itScreen->second->i_saveSettings(data.mapScreens[itScreen->first /* Screen ID */]);
+ if (FAILED(hrc))
+ break;
+ }
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+void RecordingSettings::i_rollback(void)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.rollback();
+
+ for (RecordingScreenSettingsObjMap::const_iterator itScreen = m->mapScreenObj.begin();
+ itScreen != m->mapScreenObj.end();
+ ++itScreen)
+ {
+ itScreen->second->i_rollback();
+ }
+}
+
+void RecordingSettings::i_commit(void)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ {
+ m->bd.commit();
+ if (m->pPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ m->pPeer->m->bd.attach(m->bd);
+ }
+
+ for (RecordingScreenSettingsObjMap::const_iterator itScreenObj = m->mapScreenObj.begin();
+ itScreenObj != m->mapScreenObj.end();
+ ++itScreenObj)
+ {
+ itScreenObj->second->i_commit();
+ if (m->pPeer)
+ m->pPeer->i_commit();
+ }
+ }
+}
+
+HRESULT RecordingSettings::i_copyFrom(RecordingSettings *aThat)
+{
+ AssertPtrReturn(aThat, E_INVALIDARG);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), VBOX_E_INVALID_OBJECT_STATE);
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturn(thatCaller.rc(), VBOX_E_INVALID_OBJECT_STATE);
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ m->bd.assignCopy(aThat->m->bd);
+
+ HRESULT hrc = S_OK;
+
+ for (RecordingScreenSettingsObjMap::const_iterator itScreenThat = aThat->m->mapScreenObj.begin();
+ itScreenThat != aThat->m->mapScreenObj.end();
+ ++itScreenThat)
+ {
+ RecordingScreenSettingsObjMap::iterator itScreen = m->mapScreenObj.find(itScreenThat->first);
+ if (itScreen != m->mapScreenObj.end())
+ {
+ itScreen->second->i_copyFrom(itScreenThat->second);
+ }
+ else
+ {
+ int vrc = i_createScreenObj(m->mapScreenObj,
+ itScreenThat->first /* uScreenId */, itScreenThat->second->i_getData() /* Settings */);
+ if (RT_FAILURE(vrc))
+ {
+ hrc = E_OUTOFMEMORY; /* Most likely. */
+ break;
+ }
+ }
+ }
+
+ return hrc;
+}
+
+void RecordingSettings::i_applyDefaults(void)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AssertPtr(m->pMachine);
+ ComPtr<IGraphicsAdapter> pGraphicsAdapter;
+ m->pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
+ ULONG cMonitors = 0;
+ if (!pGraphicsAdapter.isNull())
+ pGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitors);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Initialize default capturing settings here. */
+ m->bd->fEnabled = false;
+
+ /* First, do a reset so that all internal screen settings objects are destroyed. */
+ i_reset();
+ /* Second, sync (again) to configured machine displays to (re-)create screen settings objects. */
+ i_syncToMachineDisplays(cMonitors);
+}
+
+/**
+ * Returns the full path to the default recording file.
+ *
+ * @returns VBox status code.
+ * @param strFile Where to return the final file name on success.
+ * @param idScreen Screen ID the file is associated to.
+ * @param fWithFileExtension Whether to include the default file extension ('.webm') or not.
+ */
+int RecordingSettings::i_getDefaultFilename(Utf8Str &strFile, uint32_t idScreen, bool fWithFileExtension)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ strFile = m->pMachine->i_getSettingsFileFull(); // path/to/machinesfolder/vmname/vmname.vbox
+ strFile.stripSuffix();
+ strFile.append(Utf8StrFmt("-screen%RU32", idScreen));
+ if (fWithFileExtension)
+ strFile.append(".webm");
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Gets a standardized file name from a given template file name.
+ *
+ * @returns VBox status code.
+ * @param strFile Where to return the final file name on success.
+ * @param idScreen Screen ID the file is associated to.
+ * @param strTemplate Template file name to use.
+ * A default file name will be used when empty.
+ */
+int RecordingSettings::i_getFilename(Utf8Str &strFile, uint32_t idScreen, const Utf8Str &strTemplate)
+{
+ strFile = strTemplate;
+
+ if (strFile.isEmpty())
+ return i_getDefaultFilename(strFile, idScreen, true /* fWithFileExtension */);
+
+ /* We force adding a .webm suffix to (hopefully) not let the user overwrite other important stuff. */
+ strFile.stripSuffix();
+
+ Utf8Str strDotExt = ".webm";
+
+ /* We also force adding the screen id suffix, at least for the moment, as FE/Qt only offers settings a single file name
+ * for *all* enabled screens. */
+ char szSuffScreen[] = "-screen";
+ Utf8Str strSuff = Utf8StrFmt("%s%RU32", szSuffScreen, idScreen);
+ if (!strFile.endsWith(strSuff, Utf8Str::CaseInsensitive))
+ {
+ /** @todo The following line checks whether there already is a screen suffix, as FE/Qt currently always works with
+ * screen 0 as the file name. Remove the following if block when FE/Qt supports this properly. */
+ Utf8Str strSuffScreen0 = Utf8StrFmt("%s%RU32", szSuffScreen, 0);
+ if (strFile.endsWith(strSuffScreen0, Utf8Str::CaseInsensitive))
+ strFile.truncate(strFile.length() - strSuffScreen0.length());
+
+ strFile += strSuff; /* Add the suffix with the correct screen ID. */
+ }
+
+ strFile += strDotExt;
+
+ LogRel2(("Recording: File name '%s' -> '%s'\n", strTemplate.c_str(), strFile.c_str()));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Determines whether the recording settings currently can be changed or not.
+ *
+ * @returns \c true if the settings can be changed, \c false if not.
+ */
+bool RecordingSettings::i_canChangeSettings(void)
+{
+ AutoAnyStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc()))
+ return false;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Only allow settings to be changed when recording is disabled when the machine is running. */
+ if ( Global::IsOnline(adep.machineState())
+ && m->bd->fEnabled)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Gets called when the machine object needs to know that the recording settings
+ * have been changed.
+ */
+void RecordingSettings::i_onSettingsChanged(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_Recording);
+ mlock.release();
+
+ LogFlowThisFuncLeave();
+}
+
+/**
+ * Synchronizes the screen settings (COM) objects and configuration data
+ * to the number of the machine's configured displays.
+ *
+ * Note: This function ASSUMES that we always have configured VM displays
+ * as a consequtive sequence with no holes in between.
+ */
+int RecordingSettings::i_syncToMachineDisplays(uint32_t cDisplays)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogThisFunc(("%p: cDisplays=%RU32 vs. %zu\n", this, cDisplays, m->mapScreenObj.size()));
+
+ /* If counts match, take a shortcut. */
+ if (cDisplays == m->mapScreenObj.size())
+ return VINF_SUCCESS;
+
+ /* Create all new screen settings objects which are not there yet. */
+ for (ULONG i = 0; i < cDisplays; i++)
+ {
+ if (m->mapScreenObj.find(i) == m->mapScreenObj.end())
+ {
+ settings::RecordingScreenSettings defaultScreenSettings(i /* Screen ID */); /* Apply default settings. */
+
+ int vrc2 = i_createScreenObj(m->mapScreenObj, i /* Screen ID */, defaultScreenSettings);
+ AssertRC(vrc2);
+ }
+ }
+
+ /* Remove all left over screen settings objects which are not needed anymore. */
+ for (ULONG i = cDisplays; i < (ULONG)m->mapScreenObj.size(); i++)
+ {
+ int vrc2 = i_destroyScreenObj(m->mapScreenObj, i /* Screen ID */);
+ AssertRC(vrc2);
+ }
+
+ Assert(m->mapScreenObj.size() == cDisplays);
+
+ LogFlowThisFuncLeave();
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Main/src-server/SerialPortImpl.cpp b/src/VBox/Main/src-server/SerialPortImpl.cpp
new file mode 100644
index 00000000..b77940d6
--- /dev/null
+++ b/src/VBox/Main/src-server/SerialPortImpl.cpp
@@ -0,0 +1,787 @@
+/* $Id: SerialPortImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_SERIALPORT
+#include "SerialPortImpl.h"
+#include "MachineImpl.h"
+#include "VirtualBoxImpl.h"
+#include "GuestOSTypeImpl.h"
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/cpp/utils.h>
+
+#include <VBox/settings.h>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+//////////////////////////////////////////////////////////////////////////////////
+//
+// SerialPort private data definition
+//
+//////////////////////////////////////////////////////////////////////////////////
+
+struct SerialPort::Data
+{
+ Data()
+ : fModified(false),
+ pMachine(NULL)
+ { }
+
+ bool fModified;
+ Machine * const pMachine;
+ const ComObjPtr<SerialPort> pPeer;
+ Backupable<settings::SerialPort> bd;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(SerialPort)
+
+HRESULT SerialPort::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void SerialPort::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the Serial Port object.
+ *
+ * @param aParent Handle of the parent object.
+ * @param aSlot Slot number the serial port is plugged into.
+ */
+HRESULT SerialPort::init(Machine *aParent, ULONG aSlot)
+{
+ LogFlowThisFunc(("aParent=%p, aSlot=%d\n", aParent, aSlot));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ /* m->pPeer is left null */
+
+ m->bd.allocate();
+
+ /* initialize data */
+ m->bd->ulSlot = aSlot;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the Serial Port object given another serial port object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT SerialPort::init(Machine *aParent, SerialPort *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ unconst(m->pPeer) = aThat;
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.share(aThat->m->bd);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the guest object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT SerialPort::initCopy(Machine *aParent, SerialPort *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ /* pPeer is left null */
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.attachCopy(aThat->m->bd);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void SerialPort::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m->bd.free();
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pMachine) = NULL;
+
+ delete m;
+ m = NULL;
+}
+
+// ISerialPort properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT SerialPort::getEnabled(BOOL *aEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aEnabled = m->bd->fEnabled;
+
+ return S_OK;
+}
+
+
+HRESULT SerialPort::setEnabled(BOOL aEnabled)
+{
+ LogFlowThisFunc(("aEnabled=%RTbool\n", aEnabled));
+
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->fEnabled != RT_BOOL(aEnabled))
+ {
+ m->bd.backup();
+ m->bd->fEnabled = RT_BOOL(aEnabled);
+
+ m->fModified = true;
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
+ mlock.release();
+
+ m->pMachine->i_onSerialPortChange(this);
+ }
+
+ return S_OK;
+}
+
+
+HRESULT SerialPort::getHostMode(PortMode_T *aHostMode)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aHostMode = m->bd->portMode;
+
+ return S_OK;
+}
+
+HRESULT SerialPort::setHostMode(PortMode_T aHostMode)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->portMode != aHostMode)
+ {
+ switch (aHostMode)
+ {
+ case PortMode_RawFile:
+ if (m->bd->strPath.isEmpty())
+ return setError(E_INVALIDARG,
+ tr("Cannot set the raw file mode of the serial port %d "
+ "because the file path is empty or null"),
+ m->bd->ulSlot);
+ break;
+ case PortMode_HostPipe:
+ if (m->bd->strPath.isEmpty())
+ return setError(E_INVALIDARG,
+ tr("Cannot set the host pipe mode of the serial port %d "
+ "because the pipe path is empty or null"),
+ m->bd->ulSlot);
+ break;
+ case PortMode_HostDevice:
+ if (m->bd->strPath.isEmpty())
+ return setError(E_INVALIDARG,
+ tr("Cannot set the host device mode of the serial port %d "
+ "because the device path is empty or null"),
+ m->bd->ulSlot);
+ break;
+ case PortMode_TCP:
+ if (m->bd->strPath.isEmpty())
+ return setError(E_INVALIDARG,
+ tr("Cannot set the host device mode of the serial port %d "
+ "because the server address or TCP port is invalid"),
+ m->bd->ulSlot);
+ break;
+ case PortMode_Disconnected:
+ break;
+#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
+ case PortMode_32BitHack: /* (compiler warnings) */
+ AssertFailedBreak();
+#endif
+ }
+
+ m->bd.backup();
+ m->bd->portMode = aHostMode;
+
+ m->fModified = true;
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
+ mlock.release();
+
+ m->pMachine->i_onSerialPortChange(this);
+ }
+
+ return S_OK;
+}
+
+HRESULT SerialPort::getSlot(ULONG *aSlot)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aSlot = m->bd->ulSlot;
+
+ return S_OK;
+}
+
+
+HRESULT SerialPort::getIRQ(ULONG *aIRQ)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aIRQ = m->bd->ulIRQ;
+
+ return S_OK;
+}
+
+
+HRESULT SerialPort::setIRQ(ULONG aIRQ)
+{
+ /* check IRQ limits
+ * (when changing this, make sure it corresponds to XML schema */
+ if (aIRQ > 255)
+ return setError(E_INVALIDARG,
+ tr("Invalid IRQ number of the serial port %d: %lu (must be in range [0, %lu])"),
+ m->bd->ulSlot, aIRQ, 255);
+
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->ulIRQ != aIRQ)
+ {
+ m->bd.backup();
+ m->bd->ulIRQ = aIRQ;
+
+ m->fModified = true;
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
+ mlock.release();
+
+ m->pMachine->i_onSerialPortChange(this);
+ }
+
+ return S_OK;
+}
+
+
+HRESULT SerialPort::getIOBase(ULONG *aIOBase)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aIOBase = m->bd->ulIOBase;
+
+ return S_OK;
+}
+
+HRESULT SerialPort::setIOBase(ULONG aIOBase)
+{
+ /* check IOBase limits
+ * (when changing this, make sure it corresponds to XML schema */
+ if (aIOBase > 0xFFFF)
+ return setError(E_INVALIDARG,
+ tr("Invalid I/O port base address of the serial port %d: %lu (must be in range [0, 0x%X])"),
+ m->bd->ulSlot, aIOBase, 0, 0xFFFF);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ if (m->bd->ulIOBase != aIOBase)
+ {
+ m->bd.backup();
+ m->bd->ulIOBase = aIOBase;
+
+ m->fModified = true;
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
+ mlock.release();
+
+ m->pMachine->i_onSerialPortChange(this);
+ }
+
+ return rc;
+}
+
+HRESULT SerialPort::getPath(com::Utf8Str &aPath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aPath = m->bd->strPath;
+
+ return S_OK;
+}
+
+
+HRESULT SerialPort::setPath(const com::Utf8Str &aPath)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aPath != m->bd->strPath)
+ {
+ HRESULT rc = i_checkSetPath(aPath);
+ if (FAILED(rc)) return rc;
+
+ m->bd.backup();
+ m->bd->strPath = aPath;
+
+ m->fModified = true;
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
+ mlock.release();
+
+ m->pMachine->i_onSerialPortChange(this);
+ }
+
+ return S_OK;
+}
+
+HRESULT SerialPort::getServer(BOOL *aServer)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aServer = m->bd->fServer;
+
+ return S_OK;
+}
+
+HRESULT SerialPort::setServer(BOOL aServer)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->fServer != RT_BOOL(aServer))
+ {
+ m->bd.backup();
+ m->bd->fServer = RT_BOOL(aServer);
+
+ m->fModified = true;
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
+ mlock.release();
+
+ m->pMachine->i_onSerialPortChange(this);
+ }
+
+ return S_OK;
+}
+
+HRESULT SerialPort::getUartType(UartType_T *aUartType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aUartType = m->bd->uartType;
+
+ return S_OK;
+}
+
+HRESULT SerialPort::setUartType(UartType_T aUartType)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->uartType != aUartType)
+ {
+ m->bd.backup();
+ m->bd->uartType = aUartType;
+
+ m->fModified = true;
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ m->pMachine->i_setModified(Machine::IsModified_SerialPorts);
+ mlock.release();
+
+ m->pMachine->i_onSerialPortChange(this);
+ }
+
+ return S_OK;
+}
+
+// public methods only for internal purposes
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Loads settings from the given port node.
+ * May be called once right after this object creation.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT SerialPort::i_loadSettings(const settings::SerialPort &data)
+{
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // simply copy
+ *m->bd.data() = data;
+
+ return S_OK;
+}
+
+/**
+ * Saves the port settings to the given port node.
+ *
+ * Note that the given Port node is completely empty on input.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT SerialPort::i_saveSettings(settings::SerialPort &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // simply copy
+ data = *m->bd.data();
+
+ return S_OK;
+}
+
+/**
+ * Returns true if any setter method has modified settings of this instance.
+ * @return
+ */
+bool SerialPort::i_isModified()
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return m->fModified;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+void SerialPort::i_rollback()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.rollback();
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object (also
+ * for writing) if there is one.
+ */
+void SerialPort::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (pPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ {
+ m->bd.commit();
+ if (m->pPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ m->pPeer->m->bd.attach(m->bd);
+ }
+ }
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ */
+void SerialPort::i_copyFrom(SerialPort *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ m->bd.assignCopy(aThat->m->bd);
+}
+
+/**
+ * Applies the defaults for this serial port.
+ *
+ * @note This method currently assumes that the object is in the state after
+ * calling init(), it does not set defaults from an arbitrary state.
+ */
+void SerialPort::i_applyDefaults(GuestOSType *aOsType)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Set some more defaults. */
+ switch (m->bd->ulSlot)
+ {
+ case 0:
+ {
+ m->bd->ulIOBase = 0x3f8;
+ m->bd->ulIRQ = 4;
+ break;
+ }
+ case 1:
+ {
+ m->bd->ulIOBase = 0x2f8;
+ m->bd->ulIRQ = 3;
+ break;
+ }
+ case 2:
+ {
+ m->bd->ulIOBase = 0x3e8;
+ m->bd->ulIRQ = 4;
+ break;
+ }
+ case 3:
+ {
+ m->bd->ulIOBase = 0x2e8;
+ m->bd->ulIRQ = 3;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Serial port slot %u exceeds limit\n", m->bd->ulSlot));
+ break;
+ }
+
+ uint32_t numSerialEnabled = 0;
+ if (aOsType)
+ numSerialEnabled = aOsType->i_numSerialEnabled();
+
+ /* Enable port if requested */
+ if (m->bd->ulSlot < numSerialEnabled)
+ {
+ m->bd->fEnabled = true;
+ }
+}
+
+bool SerialPort::i_hasDefaults()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), true);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( !m->bd->fEnabled
+ && m->bd->portMode == PortMode_Disconnected
+ && !m->bd->fServer)
+ {
+ /* Could be default, check the IO base and IRQ. */
+ switch (m->bd->ulSlot)
+ {
+ case 0:
+ if (m->bd->ulIOBase == 0x3f8 && m->bd->ulIRQ == 4)
+ return true;
+ break;
+ case 1:
+ if (m->bd->ulIOBase == 0x2f8 && m->bd->ulIRQ == 3)
+ return true;
+ break;
+ case 2:
+ if (m->bd->ulIOBase == 0x3e8 && m->bd->ulIRQ == 4)
+ return true;
+ break;
+ case 3:
+ if (m->bd->ulIOBase == 0x2e8 && m->bd->ulIRQ == 3)
+ return true;
+ break;
+ default:
+ AssertMsgFailed(("Serial port slot %u exceeds limit\n", m->bd->ulSlot));
+ break;
+ }
+
+ /* Detect old-style defaults (0x3f8, irq 4) in any slot, they are still
+ * in place for many VMs created by old VirtualBox versions. */
+ if (m->bd->ulIOBase == 0x3f8 && m->bd->ulIRQ == 4)
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Validates COMSETTER(Path) arguments.
+ */
+HRESULT SerialPort::i_checkSetPath(const Utf8Str &str)
+{
+ AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
+
+ if ( ( m->bd->portMode == PortMode_HostDevice
+ || m->bd->portMode == PortMode_HostPipe
+ || m->bd->portMode == PortMode_TCP
+ || m->bd->portMode == PortMode_RawFile
+ ) && str.isEmpty()
+ )
+ return setError(E_INVALIDARG,
+ tr("Path of the serial port %d may not be empty or null in "
+ "host pipe, host device or TCP mode"),
+ m->bd->ulSlot);
+
+ return S_OK;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/SnapshotImpl.cpp b/src/VBox/Main/src-server/SnapshotImpl.cpp
new file mode 100644
index 00000000..9342896d
--- /dev/null
+++ b/src/VBox/Main/src-server/SnapshotImpl.cpp
@@ -0,0 +1,4330 @@
+/* $Id: SnapshotImpl.cpp $ */
+/** @file
+ * COM class implementation for Snapshot and SnapshotMachine in VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_SNAPSHOT
+#include <set>
+#include <map>
+
+#include "SnapshotImpl.h"
+#include "LoggingNew.h"
+
+#include "MachineImpl.h"
+#include "MediumImpl.h"
+#include "MediumFormatImpl.h"
+#include "ProgressImpl.h"
+#include "Global.h"
+#include "StringifyEnums.h"
+
+/// @todo these three includes are required for about one or two lines, try
+// to remove them and put that code in shared code in MachineImplcpp
+#include "SharedFolderImpl.h"
+#include "USBControllerImpl.h"
+#include "USBDeviceFiltersImpl.h"
+#include "VirtualBoxImpl.h"
+
+#include "AutoCaller.h"
+#include "VBox/com/MultiResult.h"
+
+#include <iprt/path.h>
+#include <iprt/cpp/utils.h>
+
+#include <VBox/param.h>
+#include <iprt/errcore.h>
+
+#include <VBox/settings.h>
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Snapshot private data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+
+typedef std::list< ComObjPtr<Snapshot> > SnapshotsList;
+
+struct Snapshot::Data
+{
+ Data()
+ : pVirtualBox(NULL)
+ {
+ RTTimeSpecSetMilli(&timeStamp, 0);
+ };
+
+ ~Data()
+ {}
+
+ const Guid uuid;
+ Utf8Str strName;
+ Utf8Str strDescription;
+ RTTIMESPEC timeStamp;
+ ComObjPtr<SnapshotMachine> pMachine;
+
+ /** weak VirtualBox parent */
+ VirtualBox * const pVirtualBox;
+
+ // pParent and llChildren are protected by the machine lock
+ ComObjPtr<Snapshot> pParent;
+ SnapshotsList llChildren;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Constructor / destructor
+//
+////////////////////////////////////////////////////////////////////////////////
+DEFINE_EMPTY_CTOR_DTOR(Snapshot)
+
+HRESULT Snapshot::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void Snapshot::FinalRelease()
+{
+ LogFlowThisFunc(("\n"));
+ uninit();
+ BaseFinalRelease();
+}
+
+/**
+ * Initializes the instance
+ *
+ * @param aVirtualBox VirtualBox object
+ * @param aId id of the snapshot
+ * @param aName name of the snapshot
+ * @param aDescription name of the snapshot (NULL if no description)
+ * @param aTimeStamp timestamp of the snapshot, in ms since 1970-01-01 UTC
+ * @param aMachine machine associated with this snapshot
+ * @param aParent parent snapshot (NULL if no parent)
+ */
+HRESULT Snapshot::init(VirtualBox *aVirtualBox,
+ const Guid &aId,
+ const Utf8Str &aName,
+ const Utf8Str &aDescription,
+ const RTTIMESPEC &aTimeStamp,
+ SnapshotMachine *aMachine,
+ Snapshot *aParent)
+{
+ LogFlowThisFunc(("uuid=%s aParent->uuid=%s\n", aId.toString().c_str(), (aParent) ? aParent->m->uuid.toString().c_str() : ""));
+
+ ComAssertRet(!aId.isZero() && aId.isValid() && aMachine, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data;
+
+ /* share parent weakly */
+ unconst(m->pVirtualBox) = aVirtualBox;
+
+ m->pParent = aParent;
+
+ unconst(m->uuid) = aId;
+ m->strName = aName;
+ m->strDescription = aDescription;
+ m->timeStamp = aTimeStamp;
+ m->pMachine = aMachine;
+
+ if (aParent)
+ aParent->m->llChildren.push_back(this);
+
+ /* Confirm a successful initialization when it's the case */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease(), by the parent when it gets destroyed,
+ * or by a third party when it decides this object is no more valid.
+ *
+ * Since this manipulates the snapshots tree, the caller must hold the
+ * machine lock in write mode (which protects the snapshots tree)!
+ *
+ * @note All children of this snapshot get uninitialized, too, in a stack
+ * friendly manner.
+ */
+void Snapshot::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ {
+ /* If "this" is already uninitialized or was never initialized, skip
+ * all activity since it makes no sense. Also would cause asserts with
+ * the automatic refcount updating with SnapshotList/ComPtr. Also,
+ * make sure that the possible fake error is undone. */
+ ErrorInfoKeeper eik;
+ AutoLimitedCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return;
+ }
+
+ SnapshotsList llSnapshotsTodo;
+ llSnapshotsTodo.push_back(this);
+ SnapshotsList llSnapshotsAll;
+
+ while (llSnapshotsTodo.size() > 0)
+ {
+ /* This also guarantees that the refcount doesn't actually drop to 0
+ * again while the uninit is already ongoing. */
+ ComObjPtr<Snapshot> pSnapshot = llSnapshotsTodo.front();
+ llSnapshotsTodo.pop_front();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(pSnapshot);
+ if (autoUninitSpan.uninitDone())
+ continue;
+
+ /* Remember snapshots (depth first), for associated SnapshotMachine
+ * uninitialization, which must be done in dept first order, otherwise
+ * the Medium object uninit is done in the wrong order. */
+ llSnapshotsAll.push_front(pSnapshot);
+
+ Assert(pSnapshot->m->pMachine->isWriteLockOnCurrentThread());
+
+ /* Remove initial snapshot from parent snapshot's list of children. */
+ if (pSnapshot == this)
+ pSnapshot->i_deparent();
+
+ /* Paranoia. Shouldn't be set any more at processing time. */
+ Assert(!pSnapshot->m || pSnapshot->m->pParent.isNull());
+
+ /* Process all children */
+ SnapshotsList::const_iterator itBegin = pSnapshot->m->llChildren.begin();
+ SnapshotsList::const_iterator itEnd = pSnapshot->m->llChildren.end();
+ for (SnapshotsList::const_iterator it = itBegin; it != itEnd; ++it)
+ {
+ Snapshot *pChild = *it;
+
+ if (!pChild || !pChild->m)
+ continue;
+
+ pChild->m->pParent.setNull();
+ llSnapshotsTodo.push_back(pChild);
+ }
+
+ /* Children information obsolete, will be processed anyway. */
+ pSnapshot->m->llChildren.clear();
+
+ autoUninitSpan.setSucceeded();
+ }
+
+ /* Now handle SnapshotMachine uninit and free memory. */
+ while (llSnapshotsAll.size() > 0)
+ {
+ ComObjPtr<Snapshot> pSnapshot = llSnapshotsAll.front();
+ llSnapshotsAll.pop_front();
+
+ if (pSnapshot->m->pMachine)
+ {
+ pSnapshot->m->pMachine->uninit();
+ pSnapshot->m->pMachine.setNull();
+ }
+
+ delete pSnapshot->m;
+ pSnapshot->m = NULL;
+ }
+}
+
+/**
+ * Delete the current snapshot by removing it from the tree of snapshots
+ * and reparenting its children.
+ *
+ * After this, the caller must call uninit() on the snapshot. We can't call
+ * that from here because if we do, the AutoUninitSpan waits forever for
+ * the number of callers to become 0 (it is 1 because of the AutoCaller in here).
+ *
+ * NOTE: this does NOT lock the snapshot, it is assumed that the machine state
+ * (and the snapshots tree) is protected by the caller having requested the machine
+ * lock in write mode AND the machine state must be DeletingSnapshot.
+ */
+void Snapshot::i_beginSnapshotDelete()
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc()))
+ return;
+
+ // caller must have acquired the machine's write lock
+ Assert( m->pMachine->mData->mMachineState == MachineState_DeletingSnapshot
+ || m->pMachine->mData->mMachineState == MachineState_DeletingSnapshotOnline
+ || m->pMachine->mData->mMachineState == MachineState_DeletingSnapshotPaused);
+ Assert(m->pMachine->isWriteLockOnCurrentThread());
+
+ // the snapshot must have only one child when being deleted or no children at all
+ AssertReturnVoid(m->llChildren.size() <= 1);
+
+ ComObjPtr<Snapshot> parentSnapshot = m->pParent;
+
+ /// @todo (dmik):
+ // when we introduce clones later, deleting the snapshot will affect
+ // the current and first snapshots of clones, if they are direct children
+ // of this snapshot. So we will need to lock machines associated with
+ // child snapshots as well and update mCurrentSnapshot and/or
+ // mFirstSnapshot fields.
+
+ if (this == m->pMachine->mData->mCurrentSnapshot)
+ {
+ m->pMachine->mData->mCurrentSnapshot = parentSnapshot;
+
+ /* we've changed the base of the current state so mark it as
+ * modified as it no longer guaranteed to be its copy */
+ m->pMachine->mData->mCurrentStateModified = TRUE;
+ }
+
+ if (this == m->pMachine->mData->mFirstSnapshot)
+ {
+ if (m->llChildren.size() == 1)
+ {
+ ComObjPtr<Snapshot> childSnapshot = m->llChildren.front();
+ m->pMachine->mData->mFirstSnapshot = childSnapshot;
+ }
+ else
+ m->pMachine->mData->mFirstSnapshot.setNull();
+ }
+
+ // reparent our children
+ for (SnapshotsList::const_iterator it = m->llChildren.begin();
+ it != m->llChildren.end();
+ ++it)
+ {
+ ComObjPtr<Snapshot> child = *it;
+ // no need to lock, snapshots tree is protected by machine lock
+ child->m->pParent = m->pParent;
+ if (m->pParent)
+ m->pParent->m->llChildren.push_back(child);
+ }
+
+ // clear our own children list (since we reparented the children)
+ m->llChildren.clear();
+}
+
+/**
+ * Internal helper that removes "this" from the list of children of its
+ * parent. Used in places when reparenting is necessary.
+ *
+ * The caller must hold the machine lock in write mode (which protects the snapshots tree)!
+ */
+void Snapshot::i_deparent()
+{
+ Assert(m->pMachine->isWriteLockOnCurrentThread());
+
+ if (m->pParent.isNull())
+ return;
+
+ Assert(m->pParent->m);
+
+ SnapshotsList &llParent = m->pParent->m->llChildren;
+ for (SnapshotsList::iterator it = llParent.begin();
+ it != llParent.end();
+ ++it)
+ {
+ Snapshot *pParentsChild = *it;
+ if (this == pParentsChild)
+ {
+ llParent.erase(it);
+ break;
+ }
+ }
+
+ m->pParent.setNull();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ISnapshot public methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT Snapshot::getId(com::Guid &aId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aId = m->uuid;
+
+ return S_OK;
+}
+
+HRESULT Snapshot::getName(com::Utf8Str &aName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aName = m->strName;
+ return S_OK;
+}
+
+/**
+ * @note Locks this object for writing, then calls Machine::onSnapshotChange()
+ * (see its lock requirements).
+ */
+HRESULT Snapshot::setName(const com::Utf8Str &aName)
+{
+ HRESULT rc = S_OK;
+
+ // prohibit setting a UUID only as the machine name, or else it can
+ // never be found by findMachine()
+ Guid test(aName);
+
+ if (!test.isZero() && test.isValid())
+ return setError(E_INVALIDARG, tr("A machine cannot have a UUID as its name"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->strName != aName)
+ {
+ m->strName = aName;
+ alock.release(); /* Important! (child->parent locks are forbidden) */
+ rc = m->pMachine->i_onSnapshotChange(this);
+ }
+
+ return rc;
+}
+
+HRESULT Snapshot::getDescription(com::Utf8Str &aDescription)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aDescription = m->strDescription;
+ return S_OK;
+}
+
+HRESULT Snapshot::setDescription(const com::Utf8Str &aDescription)
+{
+ HRESULT rc = S_OK;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (m->strDescription != aDescription)
+ {
+ m->strDescription = aDescription;
+ alock.release(); /* Important! (child->parent locks are forbidden) */
+ rc = m->pMachine->i_onSnapshotChange(this);
+ }
+
+ return rc;
+}
+
+HRESULT Snapshot::getTimeStamp(LONG64 *aTimeStamp)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aTimeStamp = RTTimeSpecGetMilli(&m->timeStamp);
+ return S_OK;
+}
+
+HRESULT Snapshot::getOnline(BOOL *aOnline)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aOnline = i_getStateFilePath().isNotEmpty();
+ return S_OK;
+}
+
+HRESULT Snapshot::getMachine(ComPtr<IMachine> &aMachine)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->pMachine.queryInterfaceTo(aMachine.asOutParam());
+
+ return S_OK;
+}
+
+
+HRESULT Snapshot::getParent(ComPtr<ISnapshot> &aParent)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->pParent.queryInterfaceTo(aParent.asOutParam());
+ return S_OK;
+}
+
+HRESULT Snapshot::getChildren(std::vector<ComPtr<ISnapshot> > &aChildren)
+{
+ // snapshots tree is protected by machine lock
+ AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ aChildren.resize(0);
+ for (SnapshotsList::const_iterator it = m->llChildren.begin();
+ it != m->llChildren.end();
+ ++it)
+ aChildren.push_back(*it);
+ return S_OK;
+}
+
+HRESULT Snapshot::getChildrenCount(ULONG *count)
+{
+ *count = i_getChildrenCount();
+
+ return S_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Snapshot public internal methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns the parent snapshot or NULL if there's none. Must have caller + locking!
+ * @return
+ */
+const ComObjPtr<Snapshot>& Snapshot::i_getParent() const
+{
+ return m->pParent;
+}
+
+/**
+ * Returns the first child snapshot or NULL if there's none. Must have caller + locking!
+ * @return
+ */
+const ComObjPtr<Snapshot> Snapshot::i_getFirstChild() const
+{
+ if (!m->llChildren.size())
+ return NULL;
+ return m->llChildren.front();
+}
+
+/**
+ * @note
+ * Must be called from under the object's lock!
+ */
+const Utf8Str& Snapshot::i_getStateFilePath() const
+{
+ return m->pMachine->mSSData->strStateFilePath;
+}
+
+/**
+ * Returns the depth in the snapshot tree for this snapshot.
+ *
+ * @note takes the snapshot tree lock
+ */
+
+uint32_t Snapshot::i_getDepth()
+{
+ AutoCaller autoCaller(this);
+ AssertComRC(autoCaller.rc());
+
+ // snapshots tree is protected by machine lock
+ AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+
+ uint32_t cDepth = 0;
+ ComObjPtr<Snapshot> pSnap(this);
+ while (!pSnap.isNull())
+ {
+ pSnap = pSnap->m->pParent;
+ cDepth++;
+ }
+
+ return cDepth;
+}
+
+/**
+ * Returns the number of direct child snapshots, without grandchildren.
+ * @return
+ */
+ULONG Snapshot::i_getChildrenCount()
+{
+ AutoCaller autoCaller(this);
+ AssertComRC(autoCaller.rc());
+
+ // snapshots tree is protected by machine lock
+ AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+
+ return (ULONG)m->llChildren.size();
+}
+
+/**
+ * Returns the number of child snapshots including all grandchildren.
+ * @return
+ */
+ULONG Snapshot::i_getAllChildrenCount()
+{
+ AutoCaller autoCaller(this);
+ AssertComRC(autoCaller.rc());
+
+ // snapshots tree is protected by machine lock
+ AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+
+ std::list<const Snapshot *> llSnapshotsTodo;
+ llSnapshotsTodo.push_back(this);
+
+ ULONG cChildren = 0;
+
+ while (llSnapshotsTodo.size() > 0)
+ {
+ const Snapshot *pSnapshot = llSnapshotsTodo.front();
+ llSnapshotsTodo.pop_front();
+
+ /* Check if snapshot is uninitialized already, can happen if an API
+ * client asks at an inconvenient time. */
+ if (!pSnapshot->m)
+ continue;
+
+ cChildren += (ULONG)pSnapshot->m->llChildren.size();
+
+ /* count all children */
+ SnapshotsList::const_iterator itBegin = pSnapshot->m->llChildren.begin();
+ SnapshotsList::const_iterator itEnd = pSnapshot->m->llChildren.end();
+ for (SnapshotsList::const_iterator it = itBegin; it != itEnd; ++it)
+ llSnapshotsTodo.push_back(*it);
+ }
+
+ return cChildren;
+}
+
+/**
+ * Returns the SnapshotMachine that this snapshot belongs to.
+ * Caller must hold the snapshot's object lock!
+ * @return
+ */
+const ComObjPtr<SnapshotMachine>& Snapshot::i_getSnapshotMachine() const
+{
+ return m->pMachine;
+}
+
+/**
+ * Returns the UUID of this snapshot.
+ * Caller must hold the snapshot's object lock!
+ * @return
+ */
+Guid Snapshot::i_getId() const
+{
+ return m->uuid;
+}
+
+/**
+ * Returns the name of this snapshot.
+ * Caller must hold the snapshot's object lock!
+ * @return
+ */
+const Utf8Str& Snapshot::i_getName() const
+{
+ return m->strName;
+}
+
+/**
+ * Returns the time stamp of this snapshot.
+ * Caller must hold the snapshot's object lock!
+ * @return
+ */
+RTTIMESPEC Snapshot::i_getTimeStamp() const
+{
+ return m->timeStamp;
+}
+
+/**
+ * Searches for a snapshot with the given ID among children, grand-children,
+ * etc. of this snapshot. This snapshot itself is also included in the search.
+ *
+ * Caller must hold the machine lock (which protects the snapshots tree!)
+ */
+ComObjPtr<Snapshot> Snapshot::i_findChildOrSelf(IN_GUID aId)
+{
+ ComObjPtr<Snapshot> child;
+
+ AutoCaller autoCaller(this);
+ AssertComRC(autoCaller.rc());
+
+ // no need to lock, uuid is const
+ if (m->uuid == aId)
+ child = this;
+ else
+ {
+ for (SnapshotsList::const_iterator it = m->llChildren.begin();
+ it != m->llChildren.end();
+ ++it)
+ {
+ if ((child = (*it)->i_findChildOrSelf(aId)))
+ break;
+ }
+ }
+
+ return child;
+}
+
+/**
+ * Searches for a first snapshot with the given name among children,
+ * grand-children, etc. of this snapshot. This snapshot itself is also included
+ * in the search.
+ *
+ * Caller must hold the machine lock (which protects the snapshots tree!)
+ */
+ComObjPtr<Snapshot> Snapshot::i_findChildOrSelf(const Utf8Str &aName)
+{
+ ComObjPtr<Snapshot> child;
+ AssertReturn(!aName.isEmpty(), child);
+
+ AutoCaller autoCaller(this);
+ AssertComRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->strName == aName)
+ child = this;
+ else
+ {
+ alock.release();
+ for (SnapshotsList::const_iterator it = m->llChildren.begin();
+ it != m->llChildren.end();
+ ++it)
+ {
+ if ((child = (*it)->i_findChildOrSelf(aName)))
+ break;
+ }
+ }
+
+ return child;
+}
+
+/**
+ * Internal implementation for Snapshot::updateSavedStatePaths (below).
+ * @param strOldPath
+ * @param strNewPath
+ */
+void Snapshot::i_updateSavedStatePathsImpl(const Utf8Str &strOldPath,
+ const Utf8Str &strNewPath)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const Utf8Str &path = m->pMachine->mSSData->strStateFilePath;
+ LogFlowThisFunc(("Snap[%s].statePath={%s}\n", m->strName.c_str(), path.c_str()));
+
+ /* state file may be NULL (for offline snapshots) */
+ if ( path.isNotEmpty()
+ && RTPathStartsWith(path.c_str(), strOldPath.c_str())
+ )
+ {
+ m->pMachine->mSSData->strStateFilePath = Utf8StrFmt("%s%s",
+ strNewPath.c_str(),
+ path.c_str() + strOldPath.length());
+ LogFlowThisFunc(("-> updated: {%s}\n", m->pMachine->mSSData->strStateFilePath.c_str()));
+ }
+
+ for (SnapshotsList::const_iterator it = m->llChildren.begin();
+ it != m->llChildren.end();
+ ++it)
+ {
+ Snapshot *pChild = *it;
+ pChild->i_updateSavedStatePathsImpl(strOldPath, strNewPath);
+ }
+}
+
+/**
+ * Checks if the specified path change affects the saved state file path of
+ * this snapshot or any of its (grand-)children and updates it accordingly.
+ *
+ * Intended to be called by Machine::openConfigLoader() only.
+ *
+ * @param strOldPath old path (full)
+ * @param strNewPath new path (full)
+ *
+ * @note Locks the machine (for the snapshots tree) + this object + children for writing.
+ */
+void Snapshot::i_updateSavedStatePaths(const Utf8Str &strOldPath,
+ const Utf8Str &strNewPath)
+{
+ LogFlowThisFunc(("aOldPath={%s} aNewPath={%s}\n", strOldPath.c_str(), strNewPath.c_str()));
+
+ AutoCaller autoCaller(this);
+ AssertComRC(autoCaller.rc());
+
+ // snapshots tree is protected by machine lock
+ AutoWriteLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+
+ // call the implementation under the tree lock
+ i_updateSavedStatePathsImpl(strOldPath, strNewPath);
+}
+
+/**
+ * Returns true if this snapshot or one of its children uses the given file,
+ * whose path must be fully qualified, as its saved state. When invoked on a
+ * machine's first snapshot, this can be used to check if a saved state file
+ * is shared with any snapshots.
+ *
+ * Caller must hold the machine lock, which protects the snapshots tree.
+ *
+ * @param strPath
+ * @param pSnapshotToIgnore If != NULL, this snapshot is ignored during the checks.
+ * @return
+ */
+bool Snapshot::i_sharesSavedStateFile(const Utf8Str &strPath,
+ Snapshot *pSnapshotToIgnore)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ std::list<const Snapshot *> llSnapshotsTodo;
+ llSnapshotsTodo.push_back(this);
+
+ while (llSnapshotsTodo.size() > 0)
+ {
+ const Snapshot *pSnapshot = llSnapshotsTodo.front();
+ llSnapshotsTodo.pop_front();
+ const Utf8Str &path = pSnapshot->m->pMachine->mSSData->strStateFilePath;
+
+ if ((!pSnapshotToIgnore || pSnapshotToIgnore != this) && path.isNotEmpty())
+ if (path == strPath)
+ return true;
+
+ /* check all children */
+ SnapshotsList::const_iterator itBegin = pSnapshot->m->llChildren.begin();
+ SnapshotsList::const_iterator itEnd = pSnapshot->m->llChildren.end();
+ for (SnapshotsList::const_iterator it = itBegin; it != itEnd; ++it)
+ llSnapshotsTodo.push_back(*it);
+ }
+
+ return false;
+}
+
+
+/**
+ * Internal implementation for Snapshot::updateNVRAMPaths (below).
+ * @param strOldPath
+ * @param strNewPath
+ */
+void Snapshot::i_updateNVRAMPathsImpl(const Utf8Str &strOldPath,
+ const Utf8Str &strNewPath)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const Utf8Str path = m->pMachine->mNvramStore->i_getNonVolatileStorageFile();
+ LogFlowThisFunc(("Snap[%s].nvramPath={%s}\n", m->strName.c_str(), path.c_str()));
+
+ /* NVRAM filename may be empty */
+ if ( path.isNotEmpty()
+ && RTPathStartsWith(path.c_str(), strOldPath.c_str())
+ )
+ {
+ m->pMachine->mNvramStore->i_updateNonVolatileStorageFile(Utf8StrFmt("%s%s",
+ strNewPath.c_str(),
+ path.c_str() + strOldPath.length()));
+ LogFlowThisFunc(("-> updated: {%s}\n", m->pMachine->mNvramStore->i_getNonVolatileStorageFile().c_str()));
+ }
+
+ for (SnapshotsList::const_iterator it = m->llChildren.begin();
+ it != m->llChildren.end();
+ ++it)
+ {
+ Snapshot *pChild = *it;
+ pChild->i_updateNVRAMPathsImpl(strOldPath, strNewPath);
+ }
+}
+
+/**
+ * Checks if the specified path change affects the NVRAM file path of
+ * this snapshot or any of its (grand-)children and updates it accordingly.
+ *
+ * Intended to be called by Machine::openConfigLoader() only.
+ *
+ * @param strOldPath old path (full)
+ * @param strNewPath new path (full)
+ *
+ * @note Locks the machine (for the snapshots tree) + this object + children for writing.
+ */
+void Snapshot::i_updateNVRAMPaths(const Utf8Str &strOldPath,
+ const Utf8Str &strNewPath)
+{
+ LogFlowThisFunc(("aOldPath={%s} aNewPath={%s}\n", strOldPath.c_str(), strNewPath.c_str()));
+
+ AutoCaller autoCaller(this);
+ AssertComRC(autoCaller.rc());
+
+ // snapshots tree is protected by machine lock
+ AutoWriteLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+
+ // call the implementation under the tree lock
+ i_updateSavedStatePathsImpl(strOldPath, strNewPath);
+}
+
+/**
+ * Saves the settings attributes of one snapshot.
+ *
+ * @param data Target for saving snapshot settings.
+ * @return
+ */
+HRESULT Snapshot::i_saveSnapshotOne(settings::Snapshot &data) const
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ data.uuid = m->uuid;
+ data.strName = m->strName;
+ data.timestamp = m->timeStamp;
+ data.strDescription = m->strDescription;
+
+ // state file (only if this snapshot is online)
+ if (i_getStateFilePath().isNotEmpty())
+ m->pMachine->i_copyPathRelativeToMachine(i_getStateFilePath(), data.strStateFile);
+ else
+ data.strStateFile.setNull();
+
+ return m->pMachine->i_saveHardware(data.hardware, &data.debugging, &data.autostart, data.recordingSettings);
+}
+
+/**
+ * Saves the given snapshot and all its children.
+ * It is assumed that the given node is empty.
+ *
+ * @param data Target for saving snapshot settings.
+ */
+HRESULT Snapshot::i_saveSnapshot(settings::Snapshot &data) const
+{
+ // snapshots tree is protected by machine lock
+ AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+
+ std::list<const Snapshot *> llSnapshotsTodo;
+ llSnapshotsTodo.push_back(this);
+ std::list<settings::Snapshot *> llSettingsTodo;
+ llSettingsTodo.push_back(&data);
+
+ while (llSnapshotsTodo.size() > 0)
+ {
+ const Snapshot *pSnapshot = llSnapshotsTodo.front();
+ llSnapshotsTodo.pop_front();
+ settings::Snapshot *current = llSettingsTodo.front();
+ llSettingsTodo.pop_front();
+
+ HRESULT rc = pSnapshot->i_saveSnapshotOne(*current);
+ if (FAILED(rc))
+ return rc;
+
+ /* save all children */
+ SnapshotsList::const_iterator itBegin = pSnapshot->m->llChildren.begin();
+ SnapshotsList::const_iterator itEnd = pSnapshot->m->llChildren.end();
+ for (SnapshotsList::const_iterator it = itBegin; it != itEnd; ++it)
+ {
+ AutoCaller autoCaller(*it);
+ if (FAILED(autoCaller.rc()))
+ continue;
+
+ llSnapshotsTodo.push_back(*it);
+ current->llChildSnapshots.push_back(settings::Snapshot::Empty);
+ llSettingsTodo.push_back(&current->llChildSnapshots.back());
+ }
+ }
+
+ return S_OK;
+}
+
+/**
+ * Part of the cleanup engine of Machine::Unregister().
+ *
+ * This removes all medium attachments from the snapshot's machine and returns
+ * the snapshot's saved state file name, if any, and then calls uninit().
+ *
+ * This processes children depth first, so the given MediaList receives child
+ * media first before their parents. If the caller wants to close all media,
+ * they should go thru the list from the beginning to the end because media
+ * cannot be closed if they have children.
+ *
+ * This calls uninit() on itself, so the snapshots tree (beginning with a machine's pFirstSnapshot) becomes invalid after this.
+ * It does not alter the main machine's snapshot pointers (pFirstSnapshot, pCurrentSnapshot).
+ *
+ * Caller must hold the machine write lock (which protects the snapshots tree!)
+ *
+ * @param writeLock Machine write lock, which can get released temporarily here.
+ * @param cleanupMode Cleanup mode; see Machine::detachAllMedia().
+ * @param llMedia List of media returned to caller, depending on cleanupMode.
+ * @param llFilenames
+ * @return
+ */
+HRESULT Snapshot::i_uninitAll(AutoWriteLock &writeLock,
+ CleanupMode_T cleanupMode,
+ MediaList &llMedia,
+ std::list<Utf8Str> &llFilenames)
+{
+ Assert(m->pMachine->isWriteLockOnCurrentThread());
+
+ HRESULT rc = S_OK;
+
+ SnapshotsList llSnapshotsTodo;
+ llSnapshotsTodo.push_front(this);
+ SnapshotsList llSnapshotsAll;
+
+ /* Enumerate all snapshots depth first, avoids trouble with updates. */
+ while (llSnapshotsTodo.size() > 0)
+ {
+ ComObjPtr<Snapshot> pSnapshot = llSnapshotsTodo.front();
+ llSnapshotsTodo.pop_front();
+
+ llSnapshotsAll.push_front(pSnapshot);
+
+ /* Process all children */
+ SnapshotsList::const_iterator itBegin = pSnapshot->m->llChildren.begin();
+ SnapshotsList::const_iterator itEnd = pSnapshot->m->llChildren.end();
+ for (SnapshotsList::const_iterator it = itBegin; it != itEnd; ++it)
+ {
+ Snapshot *pChild = *it;
+ pChild->m->pParent.setNull();
+ llSnapshotsTodo.push_front(pChild);
+ }
+ }
+
+ /* Process all snapshots in enumeration order. */
+ while (llSnapshotsAll.size() > 0)
+ {
+ /* This also guarantees that the refcount doesn't actually drop to 0
+ * again while the uninit is already ongoing. */
+ ComObjPtr<Snapshot> pSnapshot = llSnapshotsAll.front();
+ llSnapshotsAll.pop_front();
+
+ rc = pSnapshot->m->pMachine->i_detachAllMedia(writeLock,
+ pSnapshot,
+ cleanupMode,
+ llMedia);
+ if (SUCCEEDED(rc))
+ {
+ Utf8Str strFile;
+
+ // report the saved state file if it's not on the list yet
+ strFile = pSnapshot->m->pMachine->mSSData->strStateFilePath;
+ if (strFile.isNotEmpty())
+ {
+ std::list<Utf8Str>::const_iterator itFound = find(llFilenames.begin(), llFilenames.end(), strFile);
+
+ if (itFound == llFilenames.end())
+ llFilenames.push_back(strFile);
+ }
+
+ strFile = pSnapshot->m->pMachine->mNvramStore->i_getNonVolatileStorageFile();
+ if (strFile.isNotEmpty() && RTFileExists(strFile.c_str()))
+ llFilenames.push_back(strFile);
+ }
+
+ pSnapshot->m->pParent.setNull();
+ pSnapshot->m->llChildren.clear();
+ pSnapshot->uninit();
+ }
+
+ return S_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// SnapshotMachine implementation
+//
+////////////////////////////////////////////////////////////////////////////////
+
+SnapshotMachine::SnapshotMachine()
+ : mMachine(NULL)
+{}
+
+SnapshotMachine::~SnapshotMachine()
+{}
+
+HRESULT SnapshotMachine::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+
+ return BaseFinalConstruct();
+}
+
+void SnapshotMachine::FinalRelease()
+{
+ LogFlowThisFunc(("\n"));
+
+ uninit();
+
+ BaseFinalRelease();
+}
+
+/**
+ * Initializes the SnapshotMachine object when taking a snapshot.
+ *
+ * @param aSessionMachine machine to take a snapshot from
+ * @param aSnapshotId snapshot ID of this snapshot machine
+ * @param aStateFilePath file where the execution state will be later saved
+ * (or NULL for the offline snapshot)
+ *
+ * @note The aSessionMachine must be locked for writing.
+ */
+HRESULT SnapshotMachine::init(SessionMachine *aSessionMachine,
+ IN_GUID aSnapshotId,
+ const Utf8Str &aStateFilePath)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("mName={%s}\n", aSessionMachine->mUserData->s.strName.c_str()));
+
+ Guid l_guid(aSnapshotId);
+ AssertReturn(aSessionMachine && (!l_guid.isZero() && l_guid.isValid()), E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ AssertReturn(aSessionMachine->isWriteLockOnCurrentThread(), E_FAIL);
+
+ mSnapshotId = aSnapshotId;
+ ComObjPtr<Machine> pMachine = aSessionMachine->mPeer;
+
+ /* mPeer stays NULL */
+ /* memorize the primary Machine instance (i.e. not SessionMachine!) */
+ unconst(mMachine) = pMachine;
+ /* share the parent pointer */
+ unconst(mParent) = pMachine->mParent;
+
+ /* take the pointer to Data to share */
+ mData.share(pMachine->mData);
+
+ /* take the pointer to UserData to share (our UserData must always be the
+ * same as Machine's data) */
+ mUserData.share(pMachine->mUserData);
+
+ /* make a private copy of all other data */
+ mHWData.attachCopy(aSessionMachine->mHWData);
+
+ /* SSData is always unique for SnapshotMachine */
+ mSSData.allocate();
+ mSSData->strStateFilePath = aStateFilePath;
+
+ HRESULT rc = S_OK;
+
+ /* Create copies of all attachments (mMediaData after attaching a copy
+ * contains just references to original objects). Additionally associate
+ * media with the snapshot (Machine::uninitDataAndChildObjects() will
+ * deassociate at destruction). */
+ mMediumAttachments.allocate();
+ for (MediumAttachmentList::const_iterator
+ it = aSessionMachine->mMediumAttachments->begin();
+ it != aSessionMachine->mMediumAttachments->end();
+ ++it)
+ {
+ ComObjPtr<MediumAttachment> pAtt;
+ pAtt.createObject();
+ rc = pAtt->initCopy(this, *it);
+ if (FAILED(rc)) return rc;
+ mMediumAttachments->push_back(pAtt);
+
+ Medium *pMedium = pAtt->i_getMedium();
+ if (pMedium) // can be NULL for non-harddisk
+ {
+ rc = pMedium->i_addBackReference(mData->mUuid, mSnapshotId);
+ AssertComRC(rc);
+ }
+ }
+
+ /* create copies of all shared folders (mHWData after attaching a copy
+ * contains just references to original objects) */
+ for (HWData::SharedFolderList::iterator
+ it = mHWData->mSharedFolders.begin();
+ it != mHWData->mSharedFolders.end();
+ ++it)
+ {
+ ComObjPtr<SharedFolder> pFolder;
+ pFolder.createObject();
+ rc = pFolder->initCopy(this, *it);
+ if (FAILED(rc)) return rc;
+ *it = pFolder;
+ }
+
+ /* create copies of all PCI device assignments (mHWData after attaching
+ * a copy contains just references to original objects) */
+ for (HWData::PCIDeviceAssignmentList::iterator
+ it = mHWData->mPCIDeviceAssignments.begin();
+ it != mHWData->mPCIDeviceAssignments.end();
+ ++it)
+ {
+ ComObjPtr<PCIDeviceAttachment> pDev;
+ pDev.createObject();
+ rc = pDev->initCopy(this, *it);
+ if (FAILED(rc)) return rc;
+ *it = pDev;
+ }
+
+ /* create copies of all storage controllers (mStorageControllerData
+ * after attaching a copy contains just references to original objects) */
+ mStorageControllers.allocate();
+ for (StorageControllerList::const_iterator
+ it = aSessionMachine->mStorageControllers->begin();
+ it != aSessionMachine->mStorageControllers->end();
+ ++it)
+ {
+ ComObjPtr<StorageController> ctrl;
+ ctrl.createObject();
+ rc = ctrl->initCopy(this, *it);
+ if (FAILED(rc)) return rc;
+ mStorageControllers->push_back(ctrl);
+ }
+
+ /* create all other child objects that will be immutable private copies */
+
+ unconst(mBIOSSettings).createObject();
+ rc = mBIOSSettings->initCopy(this, pMachine->mBIOSSettings);
+ if (FAILED(rc)) return rc;
+
+ unconst(mRecordingSettings).createObject();
+ rc = mRecordingSettings->initCopy(this, pMachine->mRecordingSettings);
+ if (FAILED(rc)) return rc;
+
+ unconst(mTrustedPlatformModule).createObject();
+ rc = mTrustedPlatformModule->initCopy(this, pMachine->mTrustedPlatformModule);
+ if (FAILED(rc)) return rc;
+
+ unconst(mNvramStore).createObject();
+ rc = mNvramStore->initCopy(this, pMachine->mNvramStore);
+ if (FAILED(rc)) return rc;
+
+ unconst(mGraphicsAdapter).createObject();
+ rc = mGraphicsAdapter->initCopy(this, pMachine->mGraphicsAdapter);
+ if (FAILED(rc)) return rc;
+
+ unconst(mVRDEServer).createObject();
+ rc = mVRDEServer->initCopy(this, pMachine->mVRDEServer);
+ if (FAILED(rc)) return rc;
+
+ unconst(mAudioSettings).createObject();
+ rc = mAudioSettings->initCopy(this, pMachine->mAudioSettings);
+ if (FAILED(rc)) return rc;
+
+ /* create copies of all USB controllers (mUSBControllerData
+ * after attaching a copy contains just references to original objects) */
+ mUSBControllers.allocate();
+ for (USBControllerList::const_iterator
+ it = aSessionMachine->mUSBControllers->begin();
+ it != aSessionMachine->mUSBControllers->end();
+ ++it)
+ {
+ ComObjPtr<USBController> ctrl;
+ ctrl.createObject();
+ rc = ctrl->initCopy(this, *it);
+ if (FAILED(rc)) return rc;
+ mUSBControllers->push_back(ctrl);
+ }
+
+ unconst(mUSBDeviceFilters).createObject();
+ rc = mUSBDeviceFilters->initCopy(this, pMachine->mUSBDeviceFilters);
+ if (FAILED(rc)) return rc;
+
+ mNetworkAdapters.resize(pMachine->mNetworkAdapters.size());
+ for (ULONG slot = 0; slot < mNetworkAdapters.size(); slot++)
+ {
+ unconst(mNetworkAdapters[slot]).createObject();
+ rc = mNetworkAdapters[slot]->initCopy(this, pMachine->mNetworkAdapters[slot]);
+ if (FAILED(rc)) return rc;
+ }
+
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); slot++)
+ {
+ unconst(mSerialPorts[slot]).createObject();
+ rc = mSerialPorts[slot]->initCopy(this, pMachine->mSerialPorts[slot]);
+ if (FAILED(rc)) return rc;
+ }
+
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); slot++)
+ {
+ unconst(mParallelPorts[slot]).createObject();
+ rc = mParallelPorts[slot]->initCopy(this, pMachine->mParallelPorts[slot]);
+ if (FAILED(rc)) return rc;
+ }
+
+ unconst(mBandwidthControl).createObject();
+ rc = mBandwidthControl->initCopy(this, pMachine->mBandwidthControl);
+ if (FAILED(rc)) return rc;
+
+ unconst(mGuestDebugControl).createObject();
+ rc = mGuestDebugControl->initCopy(this, pMachine->mGuestDebugControl);
+ if (FAILED(rc)) return rc;
+
+ /* Confirm a successful initialization when it's the case */
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Initializes the SnapshotMachine object when loading from the settings file.
+ *
+ * @param aMachine machine the snapshot belongs to
+ * @param hardware hardware settings
+ * @param pDbg debuging settings
+ * @param pAutostart autostart settings
+ * @param recording recording settings
+ * @param aSnapshotId snapshot ID of this snapshot machine
+ * @param aStateFilePath file where the execution state is saved
+ * (or NULL for the offline snapshot)
+ *
+ * @note Doesn't lock anything.
+ */
+HRESULT SnapshotMachine::initFromSettings(Machine *aMachine,
+ const settings::Hardware &hardware,
+ const settings::Debugging *pDbg,
+ const settings::Autostart *pAutostart,
+ const settings::RecordingSettings &recording,
+ IN_GUID aSnapshotId,
+ const Utf8Str &aStateFilePath)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("mName={%s}\n", aMachine->mUserData->s.strName.c_str()));
+
+ Guid l_guid(aSnapshotId);
+ AssertReturn(aMachine && (!l_guid.isZero() && l_guid.isValid()), E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Don't need to lock aMachine when VirtualBox is starting up */
+
+ mSnapshotId = aSnapshotId;
+
+ /* mPeer stays NULL */
+ /* memorize the primary Machine instance (i.e. not SessionMachine!) */
+ unconst(mMachine) = aMachine;
+ /* share the parent pointer */
+ unconst(mParent) = aMachine->mParent;
+
+ /* take the pointer to Data to share */
+ mData.share(aMachine->mData);
+ /*
+ * take the pointer to UserData to share
+ * (our UserData must always be the same as Machine's data)
+ */
+ mUserData.share(aMachine->mUserData);
+ /* allocate private copies of all other data (will be loaded from settings) */
+ mHWData.allocate();
+ mMediumAttachments.allocate();
+ mStorageControllers.allocate();
+ mUSBControllers.allocate();
+
+ /* SSData is always unique for SnapshotMachine */
+ mSSData.allocate();
+ mSSData->strStateFilePath = aStateFilePath;
+
+ /* create all other child objects that will be immutable private copies */
+
+ unconst(mBIOSSettings).createObject();
+ mBIOSSettings->init(this);
+
+ unconst(mRecordingSettings).createObject();
+ mRecordingSettings->init(this);
+
+ unconst(mTrustedPlatformModule).createObject();
+ mTrustedPlatformModule->init(this);
+
+ unconst(mNvramStore).createObject();
+ mNvramStore->init(this);
+
+ unconst(mGraphicsAdapter).createObject();
+ mGraphicsAdapter->init(this);
+
+ unconst(mVRDEServer).createObject();
+ mVRDEServer->init(this);
+
+ unconst(mAudioSettings).createObject();
+ mAudioSettings->init(this);
+
+ unconst(mUSBDeviceFilters).createObject();
+ mUSBDeviceFilters->init(this);
+
+ mNetworkAdapters.resize(Global::getMaxNetworkAdapters(mHWData->mChipsetType));
+ for (ULONG slot = 0; slot < mNetworkAdapters.size(); slot++)
+ {
+ unconst(mNetworkAdapters[slot]).createObject();
+ mNetworkAdapters[slot]->init(this, slot);
+ }
+
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); slot++)
+ {
+ unconst(mSerialPorts[slot]).createObject();
+ mSerialPorts[slot]->init(this, slot);
+ }
+
+ for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); slot++)
+ {
+ unconst(mParallelPorts[slot]).createObject();
+ mParallelPorts[slot]->init(this, slot);
+ }
+
+ unconst(mBandwidthControl).createObject();
+ mBandwidthControl->init(this);
+
+ unconst(mGuestDebugControl).createObject();
+ mGuestDebugControl->init(this);
+
+ /* load hardware and storage settings */
+ HRESULT hrc = i_loadHardware(NULL, &mSnapshotId, hardware, pDbg, pAutostart, recording);
+ if (SUCCEEDED(hrc))
+ /* commit all changes made during the initialization */
+ i_commit(); /// @todo r=dj why do we need a commit in init?!? this is very expensive
+ /// @todo r=klaus for some reason the settings loading logic backs up
+ // the settings, and therefore a commit is needed. Should probably be changed.
+
+ /* Confirm a successful initialization when it's the case */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * Uninitializes this SnapshotMachine object.
+ */
+void SnapshotMachine::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ uninitDataAndChildObjects();
+
+ /* free the essential data structure last */
+ mData.free();
+
+ unconst(mMachine) = NULL;
+ unconst(mParent) = NULL;
+ unconst(mPeer) = NULL;
+
+ LogFlowThisFuncLeave();
+}
+
+/**
+ * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
+ * with the primary Machine instance (mMachine) if it exists.
+ */
+RWLockHandle *SnapshotMachine::lockHandle() const
+{
+ AssertReturn(mMachine != NULL, NULL);
+ return mMachine->lockHandle();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// SnapshotMachine public internal methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Called by the snapshot object associated with this SnapshotMachine when
+ * snapshot data such as name or description is changed.
+ *
+ * @warning Caller must hold no locks when calling this.
+ */
+HRESULT SnapshotMachine::i_onSnapshotChange(Snapshot *aSnapshot)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock slock(aSnapshot COMMA_LOCKVAL_SRC_POS);
+ Guid uuidMachine(mData->mUuid),
+ uuidSnapshot(aSnapshot->i_getId());
+ bool fNeedsGlobalSaveSettings = false;
+
+ /* Flag the machine as dirty or change won't get saved. We disable the
+ * modification of the current state flag, cause this snapshot data isn't
+ * related to the current state. */
+ mMachine->i_setModified(Machine::IsModified_Snapshots, false /* fAllowStateModification */);
+ slock.release();
+ HRESULT rc = mMachine->i_saveSettings(&fNeedsGlobalSaveSettings,
+ alock,
+ SaveS_Force); // we know we need saving, no need to check
+ alock.release();
+
+ if (SUCCEEDED(rc) && fNeedsGlobalSaveSettings)
+ {
+ // save the global settings
+ AutoWriteLock vboxlock(mParent COMMA_LOCKVAL_SRC_POS);
+ rc = mParent->i_saveSettings();
+ }
+
+ /* inform callbacks */
+ mParent->i_onSnapshotChanged(uuidMachine, uuidSnapshot);
+
+ return rc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// SessionMachine task records
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Still abstract base class for SessionMachine::TakeSnapshotTask,
+ * SessionMachine::RestoreSnapshotTask and SessionMachine::DeleteSnapshotTask.
+ */
+class SessionMachine::SnapshotTask
+ : public SessionMachine::Task
+{
+public:
+ SnapshotTask(SessionMachine *m,
+ Progress *p,
+ const Utf8Str &t,
+ Snapshot *s)
+ : Task(m, p, t),
+ m_pSnapshot(s)
+ {}
+
+ ComObjPtr<Snapshot> m_pSnapshot;
+};
+
+/** Take snapshot task */
+class SessionMachine::TakeSnapshotTask
+ : public SessionMachine::SnapshotTask
+{
+public:
+ TakeSnapshotTask(SessionMachine *m,
+ Progress *p,
+ const Utf8Str &t,
+ Snapshot *s,
+ const Utf8Str &strName,
+ const Utf8Str &strDescription,
+ const Guid &uuidSnapshot,
+ bool fPause,
+ uint32_t uMemSize,
+ bool fTakingSnapshotOnline)
+ : SnapshotTask(m, p, t, s)
+ , m_strName(strName)
+ , m_strDescription(strDescription)
+ , m_uuidSnapshot(uuidSnapshot)
+ , m_fPause(fPause)
+#if 0 /*unused*/
+ , m_uMemSize(uMemSize)
+#endif
+ , m_fTakingSnapshotOnline(fTakingSnapshotOnline)
+ {
+ RT_NOREF(uMemSize);
+ if (fTakingSnapshotOnline)
+ m_pDirectControl = m->mData->mSession.mDirectControl;
+ // If the VM is already paused then there's no point trying to pause
+ // again during taking an (always online) snapshot.
+ if (m_machineStateBackup == MachineState_Paused)
+ m_fPause = false;
+ }
+
+private:
+ void handler()
+ {
+ try
+ {
+ ((SessionMachine *)(Machine *)m_pMachine)->i_takeSnapshotHandler(*this);
+ }
+ catch(...)
+ {
+ LogRel(("Some exception in the function i_takeSnapshotHandler()\n"));
+ }
+ }
+
+ Utf8Str m_strName;
+ Utf8Str m_strDescription;
+ Guid m_uuidSnapshot;
+ Utf8Str m_strStateFilePath;
+ ComPtr<IInternalSessionControl> m_pDirectControl;
+ bool m_fPause;
+#if 0 /*unused*/
+ uint32_t m_uMemSize;
+#endif
+ bool m_fTakingSnapshotOnline;
+
+ friend HRESULT SessionMachine::i_finishTakingSnapshot(TakeSnapshotTask &task, AutoWriteLock &alock, bool aSuccess);
+ friend void SessionMachine::i_takeSnapshotHandler(TakeSnapshotTask &task);
+ friend void SessionMachine::i_takeSnapshotProgressCancelCallback(void *pvUser);
+};
+
+/** Restore snapshot task */
+class SessionMachine::RestoreSnapshotTask
+ : public SessionMachine::SnapshotTask
+{
+public:
+ RestoreSnapshotTask(SessionMachine *m,
+ Progress *p,
+ const Utf8Str &t,
+ Snapshot *s)
+ : SnapshotTask(m, p, t, s)
+ {}
+
+private:
+ void handler()
+ {
+ try
+ {
+ ((SessionMachine *)(Machine *)m_pMachine)->i_restoreSnapshotHandler(*this);
+ }
+ catch(...)
+ {
+ LogRel(("Some exception in the function i_restoreSnapshotHandler()\n"));
+ }
+ }
+};
+
+/** Delete snapshot task */
+class SessionMachine::DeleteSnapshotTask
+ : public SessionMachine::SnapshotTask
+{
+public:
+ DeleteSnapshotTask(SessionMachine *m,
+ Progress *p,
+ const Utf8Str &t,
+ bool fDeleteOnline,
+ Snapshot *s)
+ : SnapshotTask(m, p, t, s),
+ m_fDeleteOnline(fDeleteOnline)
+ {}
+
+private:
+ void handler()
+ {
+ try
+ {
+ ((SessionMachine *)(Machine *)m_pMachine)->i_deleteSnapshotHandler(*this);
+ }
+ catch(...)
+ {
+ LogRel(("Some exception in the function i_deleteSnapshotHandler()\n"));
+ }
+ }
+
+ bool m_fDeleteOnline;
+ friend void SessionMachine::i_deleteSnapshotHandler(DeleteSnapshotTask &task);
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// TakeSnapshot methods (Machine and related tasks)
+//
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT Machine::takeSnapshot(const com::Utf8Str &aName,
+ const com::Utf8Str &aDescription,
+ BOOL fPause,
+ com::Guid &aId,
+ ComPtr<IProgress> &aProgress)
+{
+ NOREF(aName);
+ NOREF(aDescription);
+ NOREF(fPause);
+ NOREF(aId);
+ NOREF(aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT SessionMachine::takeSnapshot(const com::Utf8Str &aName,
+ const com::Utf8Str &aDescription,
+ BOOL fPause,
+ com::Guid &aId,
+ ComPtr<IProgress> &aProgress)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aName='%s' mMachineState=%d\n", aName.c_str(), mData->mMachineState));
+
+ if (Global::IsTransient(mData->mMachineState))
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot take a snapshot of the machine while it is changing the state (machine state: %s)"),
+ Global::stringifyMachineState(mData->mMachineState));
+
+ HRESULT rc = i_checkStateDependency(MutableOrSavedOrRunningStateDep);
+ if (FAILED(rc))
+ return rc;
+
+ // prepare the progress object:
+ // a) count the no. of hard disk attachments to get a matching no. of progress sub-operations
+ ULONG cOperations = 2; // always at least setting up + finishing up
+ ULONG ulTotalOperationsWeight = 2; // one each for setting up + finishing up
+
+ for (MediumAttachmentList::iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ const ComObjPtr<MediumAttachment> pAtt(*it);
+ AutoReadLock attlock(pAtt COMMA_LOCKVAL_SRC_POS);
+ AutoCaller attCaller(pAtt);
+ if (pAtt->i_getType() == DeviceType_HardDisk)
+ {
+ ++cOperations;
+
+ // assume that creating a diff image takes as long as saving a 1MB state
+ ulTotalOperationsWeight += 1;
+ }
+ }
+
+ // b) one extra sub-operations for online snapshots OR offline snapshots that have a saved state (needs to be copied)
+ const bool fTakingSnapshotOnline = Global::IsOnline(mData->mMachineState);
+ LogFlowThisFunc(("fTakingSnapshotOnline = %d\n", fTakingSnapshotOnline));
+ if (fTakingSnapshotOnline)
+ {
+ ++cOperations;
+ ulTotalOperationsWeight += mHWData->mMemorySize;
+ }
+
+ // finally, create the progress object
+ ComObjPtr<Progress> pProgress;
+ pProgress.createObject();
+ rc = pProgress->init(mParent,
+ static_cast<IMachine *>(this),
+ Bstr(tr("Taking a snapshot of the virtual machine")).raw(),
+ fTakingSnapshotOnline /* aCancelable */,
+ cOperations,
+ ulTotalOperationsWeight,
+ Bstr(tr("Setting up snapshot operation")).raw(), // first sub-op description
+ 1); // ulFirstOperationWeight
+ if (FAILED(rc))
+ return rc;
+
+ /* create an ID for the snapshot */
+ Guid snapshotId;
+ snapshotId.create();
+
+ /* create and start the task on a separate thread (note that it will not
+ * start working until we release alock) */
+ TakeSnapshotTask *pTask = new TakeSnapshotTask(this,
+ pProgress,
+ "TakeSnap",
+ NULL /* pSnapshot */,
+ aName,
+ aDescription,
+ snapshotId,
+ !!fPause,
+ mHWData->mMemorySize,
+ fTakingSnapshotOnline);
+ MachineState_T const machineStateBackup = pTask->m_machineStateBackup;
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (FAILED(rc))
+ return rc;
+
+ /* set the proper machine state (note: after creating a Task instance) */
+ if (fTakingSnapshotOnline)
+ {
+ if (machineStateBackup != MachineState_Paused && !fPause)
+ i_setMachineState(MachineState_LiveSnapshotting);
+ else
+ i_setMachineState(MachineState_OnlineSnapshotting);
+ i_updateMachineStateOnClient();
+ }
+ else
+ i_setMachineState(MachineState_Snapshotting);
+
+ aId = snapshotId;
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+
+ return rc;
+}
+
+/**
+ * Task thread implementation for SessionMachine::TakeSnapshot(), called from
+ * SessionMachine::taskHandler().
+ *
+ * @note Locks this object for writing.
+ *
+ * @param task
+ * @return
+ */
+void SessionMachine::i_takeSnapshotHandler(TakeSnapshotTask &task)
+{
+ LogFlowThisFuncEnter();
+
+ // Taking a snapshot consists of the following:
+ // 1) creating a Snapshot object with the current state of the machine
+ // (hardware + storage)
+ // 2) creating a diff image for each virtual hard disk, into which write
+ // operations go after the snapshot has been created
+ // 3) if the machine is online: saving the state of the virtual machine
+ // (in the VM process)
+ // 4) reattach the hard disks
+ // 5) update the various snapshot/machine objects, save settings
+
+ HRESULT rc = S_OK;
+ AutoCaller autoCaller(this);
+ LogFlowThisFunc(("state=%d\n", getObjectState().getState()));
+ if (FAILED(autoCaller.rc()))
+ {
+ /* we might have been uninitialized because the session was accidentally
+ * closed by the client, so don't assert */
+ rc = setError(E_FAIL,
+ tr("The session has been accidentally closed"));
+ task.m_pProgress->i_notifyComplete(rc);
+ LogFlowThisFuncLeave();
+ return;
+ }
+
+ LogRel(("Taking snapshot %s\n", task.m_strName.c_str()));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ bool fBeganTakingSnapshot = false;
+ BOOL fSuspendedBySave = FALSE;
+
+ std::set<ComObjPtr<Medium> > pMediumsForNotify;
+ std::map<Guid, DeviceType_T> uIdsForNotify;
+
+ try
+ {
+ /// @todo at this point we have to be in the right state!!!!
+ AssertStmt( mData->mMachineState == MachineState_Snapshotting
+ || mData->mMachineState == MachineState_OnlineSnapshotting
+ || mData->mMachineState == MachineState_LiveSnapshotting, throw E_FAIL);
+ AssertStmt(task.m_machineStateBackup != mData->mMachineState, throw E_FAIL);
+ AssertStmt(task.m_pSnapshot.isNull(), throw E_FAIL);
+
+ if ( mData->mCurrentSnapshot
+ && mData->mCurrentSnapshot->i_getDepth() >= SETTINGS_SNAPSHOT_DEPTH_MAX)
+ {
+ throw setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Cannot take another snapshot for machine '%s', because it exceeds the maximum snapshot depth limit. Please delete some earlier snapshot which you no longer need"),
+ mUserData->s.strName.c_str());
+ }
+
+ /* save settings to ensure current changes are committed and
+ * hard disks are fixed up */
+ rc = i_saveSettings(NULL, alock); /******************1 */
+ // no need to check for whether VirtualBox.xml needs changing since
+ // we can't have a machine XML rename pending at this point
+ if (FAILED(rc))
+ throw rc;
+
+ /* task.m_strStateFilePath is "" when the machine is offline or saved */
+ if (task.m_fTakingSnapshotOnline)
+ {
+ Bstr value;
+ rc = GetExtraData(Bstr("VBoxInternal2/ForceTakeSnapshotWithoutState").raw(),
+ value.asOutParam());
+ if (FAILED(rc) || value != "1")
+ // creating a new online snapshot: we need a fresh saved state file
+ i_composeSavedStateFilename(task.m_strStateFilePath);
+ }
+ else if (task.m_machineStateBackup == MachineState_Saved || task.m_machineStateBackup == MachineState_AbortedSaved)
+ // taking an offline snapshot from machine in "saved" state: use existing state file
+ task.m_strStateFilePath = mSSData->strStateFilePath;
+
+ if (task.m_strStateFilePath.isNotEmpty())
+ {
+ // ensure the directory for the saved state file exists
+ rc = VirtualBox::i_ensureFilePathExists(task.m_strStateFilePath, true /* fCreate */);
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ /* STEP 1: create the snapshot object */
+
+ /* create a snapshot machine object */
+ ComObjPtr<SnapshotMachine> pSnapshotMachine;
+ pSnapshotMachine.createObject();
+ rc = pSnapshotMachine->init(this, task.m_uuidSnapshot.ref(), task.m_strStateFilePath);
+ AssertComRCThrowRC(rc);
+
+ /* create a snapshot object */
+ RTTIMESPEC time;
+ RTTimeNow(&time);
+ task.m_pSnapshot.createObject();
+ rc = task.m_pSnapshot->init(mParent,
+ task.m_uuidSnapshot,
+ task.m_strName,
+ task.m_strDescription,
+ time,
+ pSnapshotMachine,
+ mData->mCurrentSnapshot);
+ AssertComRCThrowRC(rc);
+
+ /* STEP 2: create the diff images */
+ LogFlowThisFunc(("Creating differencing hard disks (online=%d)...\n",
+ task.m_fTakingSnapshotOnline));
+
+ // Backup the media data so we can recover if something goes wrong.
+ // The matching commit() is in fixupMedia() during SessionMachine::i_finishTakingSnapshot()
+ i_setModified(IsModified_Storage);
+ mMediumAttachments.backup();
+
+ alock.release();
+ /* create new differencing hard disks and attach them to this machine */
+ rc = i_createImplicitDiffs(task.m_pProgress,
+ 1, // operation weight; must be the same as in Machine::TakeSnapshot()
+ task.m_fTakingSnapshotOnline);
+ if (FAILED(rc))
+ throw rc;
+ alock.acquire();
+
+ // MUST NOT save the settings or the media registry here, because
+ // this causes trouble with rolling back settings if the user cancels
+ // taking the snapshot after the diff images have been created.
+
+ fBeganTakingSnapshot = true;
+
+ // STEP 3: save the VM state (if online)
+ if (task.m_fTakingSnapshotOnline)
+ {
+ task.m_pProgress->SetNextOperation(Bstr(tr("Saving the machine state")).raw(),
+ mHWData->mMemorySize); // operation weight, same as computed
+ // when setting up progress object
+
+ if (task.m_strStateFilePath.isNotEmpty())
+ {
+ alock.release();
+ task.m_pProgress->i_setCancelCallback(i_takeSnapshotProgressCancelCallback, &task);
+ rc = task.m_pDirectControl->SaveStateWithReason(Reason_Snapshot,
+ task.m_pProgress,
+ task.m_pSnapshot,
+ Bstr(task.m_strStateFilePath).raw(),
+ task.m_fPause,
+ &fSuspendedBySave);
+ task.m_pProgress->i_setCancelCallback(NULL, NULL);
+ alock.acquire();
+ if (FAILED(rc))
+ throw rc;
+ }
+ else
+ LogRel(("Machine: skipped saving state as part of online snapshot\n"));
+
+ if (FAILED(task.m_pProgress->NotifyPointOfNoReturn()))
+ throw setError(E_FAIL, tr("Canceled"));
+
+ // STEP 4: reattach hard disks
+ LogFlowThisFunc(("Reattaching new differencing hard disks...\n"));
+
+ task.m_pProgress->SetNextOperation(Bstr(tr("Reconfiguring medium attachments")).raw(),
+ 1); // operation weight, same as computed when setting up progress object
+
+ com::SafeIfaceArray<IMediumAttachment> atts;
+ rc = COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(atts));
+ if (FAILED(rc))
+ throw rc;
+
+ alock.release();
+ rc = task.m_pDirectControl->ReconfigureMediumAttachments(ComSafeArrayAsInParam(atts));
+ alock.acquire();
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ // Handle NVRAM file snapshotting
+ Utf8Str strNVRAM = mNvramStore->i_getNonVolatileStorageFile();
+ Utf8Str strNVRAMSnap = pSnapshotMachine->i_getSnapshotNVRAMFilename();
+ if (strNVRAM.isNotEmpty() && strNVRAMSnap.isNotEmpty() && RTFileExists(strNVRAM.c_str()))
+ {
+ Utf8Str strNVRAMSnapAbs;
+ i_calculateFullPath(strNVRAMSnap, strNVRAMSnapAbs);
+ rc = VirtualBox::i_ensureFilePathExists(strNVRAMSnapAbs, true /* fCreate */);
+ if (FAILED(rc))
+ throw rc;
+ int vrc = RTFileCopy(strNVRAM.c_str(), strNVRAMSnapAbs.c_str());
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not copy NVRAM file '%s' to '%s' (%Rrc)"),
+ strNVRAM.c_str(), strNVRAMSnapAbs.c_str(), vrc);
+ pSnapshotMachine->mNvramStore->i_updateNonVolatileStorageFile(strNVRAMSnap);
+ }
+
+ // store parent of newly created diffs before commit for notify
+ {
+ MediumAttachmentList &oldAtts = *mMediumAttachments.backedUpData();
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ MediumAttachment *pAttach = *it;
+ Medium *pMedium = pAttach->i_getMedium();
+ if (!pMedium)
+ continue;
+
+ bool fFound = false;
+ /* was this medium attached before? */
+ for (MediumAttachmentList::iterator
+ oldIt = oldAtts.begin();
+ oldIt != oldAtts.end();
+ ++oldIt)
+ {
+ MediumAttachment *pOldAttach = *oldIt;
+ if (pOldAttach->i_getMedium() == pMedium)
+ {
+ fFound = true;
+ break;
+ }
+ }
+ if (!fFound)
+ {
+ pMediumsForNotify.insert(pMedium->i_getParent());
+ uIdsForNotify[pMedium->i_getId()] = pMedium->i_getDeviceType();
+ }
+ }
+ }
+
+ /*
+ * Finalize the requested snapshot object. This will reset the
+ * machine state to the state it had at the beginning.
+ */
+ rc = i_finishTakingSnapshot(task, alock, true /*aSuccess*/); /*******************2+3 */
+ // do not throw rc here because we can't call i_finishTakingSnapshot() twice
+ LogFlowThisFunc(("i_finishTakingSnapshot -> %Rhrc [mMachineState=%s]\n", rc, ::stringifyMachineState(mData->mMachineState)));
+ }
+ catch (HRESULT rcThrown)
+ {
+ rc = rcThrown;
+ LogThisFunc(("Caught %Rhrc [mMachineState=%s]\n", rc, ::stringifyMachineState(mData->mMachineState)));
+
+ /// @todo r=klaus check that the implicit diffs created above are cleaned up im the relevant error cases
+
+ /* preserve existing error info */
+ ErrorInfoKeeper eik;
+
+ if (fBeganTakingSnapshot)
+ i_finishTakingSnapshot(task, alock, false /*aSuccess*/);
+
+ // have to postpone this to the end as i_finishTakingSnapshot() needs
+ // it for various cleanup steps
+ if (task.m_pSnapshot)
+ {
+ task.m_pSnapshot->uninit();
+ task.m_pSnapshot.setNull();
+ }
+ }
+ Assert(alock.isWriteLockOnCurrentThread());
+
+ {
+ // Keep all error information over the cleanup steps
+ ErrorInfoKeeper eik;
+
+ /*
+ * Fix up the machine state.
+ *
+ * For offline snapshots we just update the local copy, for the other
+ * variants do the entire work. This ensures that the state is in sync
+ * with the VM process (in particular the VM execution state).
+ */
+ bool fNeedClientMachineStateUpdate = false;
+ if ( mData->mMachineState == MachineState_LiveSnapshotting
+ || mData->mMachineState == MachineState_OnlineSnapshotting
+ || mData->mMachineState == MachineState_Snapshotting)
+ {
+ if (!task.m_fTakingSnapshotOnline)
+ i_setMachineState(task.m_machineStateBackup); /**************** 4 Machine::i_saveStateSettings*/
+ else
+ {
+ MachineState_T enmMachineState = MachineState_Null;
+ HRESULT rc2 = task.m_pDirectControl->COMGETTER(NominalState)(&enmMachineState);
+ if (FAILED(rc2) || enmMachineState == MachineState_Null)
+ {
+ AssertMsgFailed(("state=%s\n", ::stringifyMachineState(enmMachineState)));
+ // pure nonsense, try to continue somehow
+ enmMachineState = MachineState_Aborted;
+ }
+ if (enmMachineState == MachineState_Paused)
+ {
+ if (fSuspendedBySave)
+ {
+ alock.release();
+ rc2 = task.m_pDirectControl->ResumeWithReason(Reason_Snapshot);
+ alock.acquire();
+ if (SUCCEEDED(rc2))
+ enmMachineState = task.m_machineStateBackup;
+ }
+ else
+ enmMachineState = task.m_machineStateBackup;
+ }
+ if (enmMachineState != mData->mMachineState)
+ {
+ fNeedClientMachineStateUpdate = true;
+ i_setMachineState(enmMachineState);
+ }
+ }
+ }
+
+ /* check the remote state to see that we got it right. */
+ MachineState_T enmMachineState = MachineState_Null;
+ if (!task.m_pDirectControl.isNull())
+ {
+ ComPtr<IConsole> pConsole;
+ task.m_pDirectControl->COMGETTER(RemoteConsole)(pConsole.asOutParam());
+ if (!pConsole.isNull())
+ pConsole->COMGETTER(State)(&enmMachineState);
+ }
+ LogFlowThisFunc(("local mMachineState=%s remote mMachineState=%s\n",
+ ::stringifyMachineState(mData->mMachineState), ::stringifyMachineState(enmMachineState)));
+
+ if (fNeedClientMachineStateUpdate)
+ i_updateMachineStateOnClient();
+ }
+
+ task.m_pProgress->i_notifyComplete(rc);
+
+ if (SUCCEEDED(rc))
+ mParent->i_onSnapshotTaken(mData->mUuid, task.m_uuidSnapshot);
+
+ if (SUCCEEDED(rc))
+ {
+ for (std::map<Guid, DeviceType_T>::const_iterator it = uIdsForNotify.begin();
+ it != uIdsForNotify.end();
+ ++it)
+ {
+ mParent->i_onMediumRegistered(it->first, it->second, TRUE);
+ }
+
+ for (std::set<ComObjPtr<Medium> >::const_iterator it = pMediumsForNotify.begin();
+ it != pMediumsForNotify.end();
+ ++it)
+ {
+ if (it->isNotNull())
+ mParent->i_onMediumConfigChanged(*it);
+ }
+ }
+ LogRel(("Finished taking snapshot %s\n", task.m_strName.c_str()));
+ LogFlowThisFuncLeave();
+}
+
+
+/**
+ * Progress cancelation callback employed by SessionMachine::i_takeSnapshotHandler.
+ */
+/*static*/
+void SessionMachine::i_takeSnapshotProgressCancelCallback(void *pvUser)
+{
+ TakeSnapshotTask *pTask = (TakeSnapshotTask *)pvUser;
+ AssertPtrReturnVoid(pTask);
+ AssertReturnVoid(!pTask->m_pDirectControl.isNull());
+ pTask->m_pDirectControl->CancelSaveStateWithReason();
+}
+
+
+/**
+ * Called by the Console when it's done saving the VM state into the snapshot
+ * (if online) and reconfiguring the hard disks. See BeginTakingSnapshot() above.
+ *
+ * This also gets called if the console part of snapshotting failed after the
+ * BeginTakingSnapshot() call, to clean up the server side.
+ *
+ * @note Locks VirtualBox and this object for writing.
+ *
+ * @param task
+ * @param alock
+ * @param aSuccess Whether Console was successful with the client-side
+ * snapshot things.
+ * @return
+ */
+HRESULT SessionMachine::i_finishTakingSnapshot(TakeSnapshotTask &task, AutoWriteLock &alock, bool aSuccess)
+{
+ LogFlowThisFunc(("\n"));
+
+ Assert(alock.isWriteLockOnCurrentThread());
+
+ AssertReturn( !aSuccess
+ || mData->mMachineState == MachineState_Snapshotting
+ || mData->mMachineState == MachineState_OnlineSnapshotting
+ || mData->mMachineState == MachineState_LiveSnapshotting, E_FAIL);
+
+ ComObjPtr<Snapshot> pOldFirstSnap = mData->mFirstSnapshot;
+ ComObjPtr<Snapshot> pOldCurrentSnap = mData->mCurrentSnapshot;
+
+ HRESULT rc = S_OK;
+
+ if (aSuccess)
+ {
+ // new snapshot becomes the current one
+ mData->mCurrentSnapshot = task.m_pSnapshot;
+
+ /* memorize the first snapshot if necessary */
+ if (!mData->mFirstSnapshot)
+ mData->mFirstSnapshot = mData->mCurrentSnapshot;
+
+ int flSaveSettings = SaveS_Force; // do not do a deep compare in machine settings,
+ // snapshots change, so we know we need to save
+ if (!task.m_fTakingSnapshotOnline)
+ /* the machine was powered off or saved when taking a snapshot, so
+ * reset the mCurrentStateModified flag */
+ flSaveSettings |= SaveS_ResetCurStateModified;
+
+ rc = i_saveSettings(NULL, alock, flSaveSettings); /******************2 */
+ }
+
+ if (aSuccess && SUCCEEDED(rc))
+ {
+ /* associate old hard disks with the snapshot and do locking/unlocking*/
+ i_commitMedia(task.m_fTakingSnapshotOnline);
+ alock.release();
+ }
+ else
+ {
+ /* delete all differencing hard disks created (this will also attach
+ * their parents back by rolling back mMediaData) */
+ alock.release();
+
+ i_rollbackMedia();
+
+ mData->mFirstSnapshot = pOldFirstSnap; // might have been changed above
+ mData->mCurrentSnapshot = pOldCurrentSnap; // might have been changed above
+
+ // delete the saved state file (it might have been already created)
+ if (task.m_fTakingSnapshotOnline)
+ // no need to test for whether the saved state file is shared: an online
+ // snapshot means that a new saved state file was created, which we must
+ // clean up now
+ RTFileDelete(task.m_pSnapshot->i_getStateFilePath().c_str());
+
+ alock.acquire();
+
+ task.m_pSnapshot->uninit();
+ alock.release();
+
+ }
+
+ /* clear out the snapshot data */
+ task.m_pSnapshot.setNull();
+
+ /* alock has been released already */
+ mParent->i_saveModifiedRegistries(); /**************3 */
+
+ alock.acquire();
+
+ return rc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// RestoreSnapshot methods (Machine and related tasks)
+//
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT Machine::restoreSnapshot(const ComPtr<ISnapshot> &aSnapshot,
+ ComPtr<IProgress> &aProgress)
+{
+ NOREF(aSnapshot);
+ NOREF(aProgress);
+ ReturnComNotImplemented();
+}
+
+/**
+ * Restoring a snapshot happens entirely on the server side, the machine cannot be running.
+ *
+ * This creates a new thread that does the work and returns a progress object to the client.
+ * Actual work then takes place in RestoreSnapshotTask::handler().
+ *
+ * @note Locks this + children objects for writing!
+ *
+ * @param aSnapshot in: the snapshot to restore.
+ * @param aProgress out: progress object to monitor restore thread.
+ * @return
+ */
+HRESULT SessionMachine::restoreSnapshot(const ComPtr<ISnapshot> &aSnapshot,
+ ComPtr<IProgress> &aProgress)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // machine must not be running
+ if (Global::IsOnlineOrTransient(mData->mMachineState))
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot delete the current state of the running machine (machine state: %s)"),
+ Global::stringifyMachineState(mData->mMachineState));
+
+ HRESULT rc = i_checkStateDependency(MutableOrSavedStateDep);
+ if (FAILED(rc))
+ return rc;
+
+ /* We need to explicitly check if the given snapshot is valid and bail out if not. */
+ if (aSnapshot.isNull())
+ {
+ if (aSnapshot == mData->mCurrentSnapshot)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("This VM does not have any current snapshot"));
+
+ return setError(E_INVALIDARG, tr("The given snapshot is invalid"));
+ }
+
+ ISnapshot* iSnapshot = aSnapshot;
+ ComObjPtr<Snapshot> pSnapshot(static_cast<Snapshot*>(iSnapshot));
+ ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->i_getSnapshotMachine();
+
+ // create a progress object. The number of operations is:
+ // 1 (preparing) + # of hard disks + 1 (if we need to copy the saved state file) */
+ LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
+
+ ULONG ulOpCount = 1; // one for preparations
+ ULONG ulTotalWeight = 1; // one for preparations
+ for (MediumAttachmentList::iterator
+ it = pSnapMachine->mMediumAttachments->begin();
+ it != pSnapMachine->mMediumAttachments->end();
+ ++it)
+ {
+ ComObjPtr<MediumAttachment> &pAttach = *it;
+ AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
+ if (pAttach->i_getType() == DeviceType_HardDisk)
+ {
+ ++ulOpCount;
+ ++ulTotalWeight; // assume one MB weight for each differencing hard disk to manage
+ Assert(pAttach->i_getMedium());
+ LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount,
+ pAttach->i_getMedium()->i_getName().c_str()));
+ }
+ }
+
+ ComObjPtr<Progress> pProgress;
+ pProgress.createObject();
+ pProgress->init(mParent, static_cast<IMachine*>(this),
+ BstrFmt(tr("Restoring snapshot '%s'"), pSnapshot->i_getName().c_str()).raw(),
+ FALSE /* aCancelable */,
+ ulOpCount,
+ ulTotalWeight,
+ Bstr(tr("Restoring machine settings")).raw(),
+ 1);
+
+ /* create and start the task on a separate thread (note that it will not
+ * start working until we release alock) */
+ RestoreSnapshotTask *pTask = new RestoreSnapshotTask(this,
+ pProgress,
+ "RestoreSnap",
+ pSnapshot);
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (FAILED(rc))
+ return rc;
+
+ /* set the proper machine state (note: after creating a Task instance) */
+ i_setMachineState(MachineState_RestoringSnapshot);
+
+ /* return the progress to the caller */
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+
+ LogFlowThisFuncLeave();
+
+ return S_OK;
+}
+
+/**
+ * Worker method for the restore snapshot thread created by SessionMachine::RestoreSnapshot().
+ * This method gets called indirectly through SessionMachine::taskHandler() which then
+ * calls RestoreSnapshotTask::handler().
+ *
+ * The RestoreSnapshotTask contains the progress object returned to the console by
+ * SessionMachine::RestoreSnapshot, through which progress and results are reported.
+ *
+ * @note Locks mParent + this object for writing.
+ *
+ * @param task Task data.
+ */
+void SessionMachine::i_restoreSnapshotHandler(RestoreSnapshotTask &task)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+
+ LogFlowThisFunc(("state=%d\n", getObjectState().getState()));
+ if (!autoCaller.isOk())
+ {
+ /* we might have been uninitialized because the session was accidentally
+ * closed by the client, so don't assert */
+ task.m_pProgress->i_notifyComplete(E_FAIL,
+ COM_IIDOF(IMachine),
+ getComponentName(),
+ tr("The session has been accidentally closed"));
+
+ LogFlowThisFuncLeave();
+ return;
+ }
+
+ HRESULT rc = S_OK;
+ Guid snapshotId;
+ std::set<ComObjPtr<Medium> > pMediumsForNotify;
+ std::map<Guid, std::pair<DeviceType_T, BOOL> > uIdsForNotify;
+
+ try
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Discard all current changes to mUserData (name, OSType etc.).
+ * Note that the machine is powered off, so there is no need to inform
+ * the direct session. */
+ if (mData->flModifications)
+ i_rollback(false /* aNotify */);
+
+ /* Delete the saved state file if the machine was Saved prior to this
+ * operation */
+ if (task.m_machineStateBackup == MachineState_Saved || task.m_machineStateBackup == MachineState_AbortedSaved)
+ {
+ Assert(!mSSData->strStateFilePath.isEmpty());
+
+ // release the saved state file AFTER unsetting the member variable
+ // so that releaseSavedStateFile() won't think it's still in use
+ Utf8Str strStateFile(mSSData->strStateFilePath);
+ mSSData->strStateFilePath.setNull();
+ i_releaseSavedStateFile(strStateFile, NULL /* pSnapshotToIgnore */ );
+
+ task.modifyBackedUpState(MachineState_PoweredOff);
+
+ rc = i_saveStateSettings(SaveSTS_StateFilePath);
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ RTTIMESPEC snapshotTimeStamp;
+ RTTimeSpecSetMilli(&snapshotTimeStamp, 0);
+
+ {
+ AutoReadLock snapshotLock(task.m_pSnapshot COMMA_LOCKVAL_SRC_POS);
+
+ /* remember the timestamp of the snapshot we're restoring from */
+ snapshotTimeStamp = task.m_pSnapshot->i_getTimeStamp();
+
+ // save the snapshot ID (paranoia, here we hold the lock)
+ snapshotId = task.m_pSnapshot->i_getId();
+
+ ComPtr<SnapshotMachine> pSnapshotMachine(task.m_pSnapshot->i_getSnapshotMachine());
+
+ /* copy all hardware data from the snapshot */
+ i_copyFrom(pSnapshotMachine);
+
+ LogFlowThisFunc(("Restoring hard disks from the snapshot...\n"));
+
+ // restore the attachments from the snapshot
+ i_setModified(IsModified_Storage);
+ mMediumAttachments.backup();
+ mMediumAttachments->clear();
+ for (MediumAttachmentList::const_iterator
+ it = pSnapshotMachine->mMediumAttachments->begin();
+ it != pSnapshotMachine->mMediumAttachments->end();
+ ++it)
+ {
+ ComObjPtr<MediumAttachment> pAttach;
+ pAttach.createObject();
+ pAttach->initCopy(this, *it);
+ mMediumAttachments->push_back(pAttach);
+ }
+
+ /* release the locks before the potentially lengthy operation */
+ snapshotLock.release();
+ alock.release();
+
+ rc = i_createImplicitDiffs(task.m_pProgress,
+ 1,
+ false /* aOnline */);
+ if (FAILED(rc))
+ throw rc;
+
+ alock.acquire();
+ snapshotLock.acquire();
+
+ /* Note: on success, current (old) hard disks will be
+ * deassociated/deleted on #commit() called from #i_saveSettings() at
+ * the end. On failure, newly created implicit diffs will be
+ * deleted by #rollback() at the end. */
+
+ /* should not have a saved state file associated at this point */
+ Assert(mSSData->strStateFilePath.isEmpty());
+
+ const Utf8Str &strSnapshotStateFile = task.m_pSnapshot->i_getStateFilePath();
+
+ if (strSnapshotStateFile.isNotEmpty())
+ // online snapshot: then share the state file
+ mSSData->strStateFilePath = strSnapshotStateFile;
+
+ const Utf8Str srcNVRAM(pSnapshotMachine->mNvramStore->i_getNonVolatileStorageFile());
+ const Utf8Str dstNVRAM(mNvramStore->i_getNonVolatileStorageFile());
+ if (dstNVRAM.isNotEmpty() && RTFileExists(dstNVRAM.c_str()))
+ RTFileDelete(dstNVRAM.c_str());
+ if (srcNVRAM.isNotEmpty() && dstNVRAM.isNotEmpty() && RTFileExists(srcNVRAM.c_str()))
+ RTFileCopy(srcNVRAM.c_str(), dstNVRAM.c_str());
+
+ LogFlowThisFunc(("Setting new current snapshot {%RTuuid}\n", task.m_pSnapshot->i_getId().raw()));
+ /* make the snapshot we restored from the current snapshot */
+ mData->mCurrentSnapshot = task.m_pSnapshot;
+ }
+
+ // store parent of newly created diffs for notify
+ {
+ MediumAttachmentList &oldAtts = *mMediumAttachments.backedUpData();
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments->begin();
+ it != mMediumAttachments->end();
+ ++it)
+ {
+ MediumAttachment *pAttach = *it;
+ Medium *pMedium = pAttach->i_getMedium();
+ if (!pMedium)
+ continue;
+
+ bool fFound = false;
+ /* was this medium attached before? */
+ for (MediumAttachmentList::iterator
+ oldIt = oldAtts.begin();
+ oldIt != oldAtts.end();
+ ++oldIt)
+ {
+ MediumAttachment *pOldAttach = *oldIt;
+ if (pOldAttach->i_getMedium() == pMedium)
+ {
+ fFound = true;
+ break;
+ }
+ }
+ if (!fFound)
+ {
+ pMediumsForNotify.insert(pMedium->i_getParent());
+ uIdsForNotify[pMedium->i_getId()] = std::pair<DeviceType_T, BOOL>(pMedium->i_getDeviceType(), TRUE);
+ }
+ }
+ }
+
+ /* grab differencing hard disks from the old attachments that will
+ * become unused and need to be auto-deleted */
+ std::list< ComObjPtr<MediumAttachment> > llDiffAttachmentsToDelete;
+
+ for (MediumAttachmentList::const_iterator
+ it = mMediumAttachments.backedUpData()->begin();
+ it != mMediumAttachments.backedUpData()->end();
+ ++it)
+ {
+ ComObjPtr<MediumAttachment> pAttach = *it;
+ ComObjPtr<Medium> pMedium = pAttach->i_getMedium();
+
+ /* while the hard disk is attached, the number of children or the
+ * parent cannot change, so no lock */
+ if ( !pMedium.isNull()
+ && pAttach->i_getType() == DeviceType_HardDisk
+ && !pMedium->i_getParent().isNull()
+ && pMedium->i_getChildren().size() == 0
+ )
+ {
+ LogFlowThisFunc(("Picked differencing image '%s' for deletion\n", pMedium->i_getName().c_str()));
+
+ llDiffAttachmentsToDelete.push_back(pAttach);
+ }
+ }
+
+ /* we have already deleted the current state, so set the execution
+ * state accordingly no matter of the delete snapshot result */
+ if (mSSData->strStateFilePath.isNotEmpty())
+ task.modifyBackedUpState(MachineState_Saved);
+ else
+ task.modifyBackedUpState(MachineState_PoweredOff);
+
+ /* Paranoia: no one must have saved the settings in the mean time. If
+ * it happens nevertheless we'll close our eyes and continue below. */
+ Assert(mMediumAttachments.isBackedUp());
+
+ /* assign the timestamp from the snapshot */
+ Assert(RTTimeSpecGetMilli(&snapshotTimeStamp) != 0);
+ mData->mLastStateChange = snapshotTimeStamp;
+
+ // detach the current-state diffs that we detected above and build a list of
+ // image files to delete _after_ i_saveSettings()
+
+ MediaList llDiffsToDelete;
+
+ for (std::list< ComObjPtr<MediumAttachment> >::iterator it = llDiffAttachmentsToDelete.begin();
+ it != llDiffAttachmentsToDelete.end();
+ ++it)
+ {
+ ComObjPtr<MediumAttachment> pAttach = *it; // guaranteed to have only attachments where medium != NULL
+ ComObjPtr<Medium> pMedium = pAttach->i_getMedium();
+
+ AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("Detaching old current state in differencing image '%s'\n", pMedium->i_getName().c_str()));
+
+ // Normally we "detach" the medium by removing the attachment object
+ // from the current machine data; i_saveSettings() below would then
+ // compare the current machine data with the one in the backup
+ // and actually call Medium::removeBackReference(). But that works only half
+ // the time in our case so instead we force a detachment here:
+ // remove from machine data
+ mMediumAttachments->remove(pAttach);
+ // Remove it from the backup or else i_saveSettings will try to detach
+ // it again and assert. The paranoia check avoids crashes (see
+ // assert above) if this code is buggy and saves settings in the
+ // wrong place.
+ if (mMediumAttachments.isBackedUp())
+ mMediumAttachments.backedUpData()->remove(pAttach);
+ // then clean up backrefs
+ pMedium->i_removeBackReference(mData->mUuid);
+
+ llDiffsToDelete.push_back(pMedium);
+ }
+
+ // save machine settings, reset the modified flag and commit;
+ bool fNeedsGlobalSaveSettings = false;
+ rc = i_saveSettings(&fNeedsGlobalSaveSettings, alock,
+ SaveS_ResetCurStateModified);
+ if (FAILED(rc))
+ throw rc;
+
+ // release the locks before updating registry and deleting image files
+ alock.release();
+
+ // unconditionally add the parent registry.
+ mParent->i_markRegistryModified(mParent->i_getGlobalRegistryId());
+
+ // from here on we cannot roll back on failure any more
+
+ for (MediaList::iterator it = llDiffsToDelete.begin();
+ it != llDiffsToDelete.end();
+ ++it)
+ {
+ ComObjPtr<Medium> &pMedium = *it;
+ LogFlowThisFunc(("Deleting old current state in differencing image '%s'\n", pMedium->i_getName().c_str()));
+
+ ComObjPtr<Medium> pParent = pMedium->i_getParent();
+ // store the id here because it becomes NULL after deleting storage.
+ com::Guid id = pMedium->i_getId();
+ HRESULT rc2 = pMedium->i_deleteStorage(NULL /* aProgress */,
+ true /* aWait */,
+ false /* aNotify */);
+ // ignore errors here because we cannot roll back after i_saveSettings() above
+ if (SUCCEEDED(rc2))
+ {
+ pMediumsForNotify.insert(pParent);
+ uIdsForNotify[id] = std::pair<DeviceType_T, BOOL>(pMedium->i_getDeviceType(), FALSE);
+ pMedium->uninit();
+ }
+ }
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ if (FAILED(rc))
+ {
+ /* preserve existing error info */
+ ErrorInfoKeeper eik;
+
+ /* undo all changes on failure */
+ i_rollback(false /* aNotify */);
+
+ }
+
+ mParent->i_saveModifiedRegistries();
+
+ /* restore the machine state */
+ i_setMachineState(task.m_machineStateBackup);
+
+ /* set the result (this will try to fetch current error info on failure) */
+ task.m_pProgress->i_notifyComplete(rc);
+
+ if (SUCCEEDED(rc))
+ {
+ mParent->i_onSnapshotRestored(mData->mUuid, snapshotId);
+ for (std::map<Guid, std::pair<DeviceType_T, BOOL> >::const_iterator it = uIdsForNotify.begin();
+ it != uIdsForNotify.end();
+ ++it)
+ {
+ mParent->i_onMediumRegistered(it->first, it->second.first, it->second.second);
+ }
+ for (std::set<ComObjPtr<Medium> >::const_iterator it = pMediumsForNotify.begin();
+ it != pMediumsForNotify.end();
+ ++it)
+ {
+ if (it->isNotNull())
+ mParent->i_onMediumConfigChanged(*it);
+ }
+ }
+
+ LogFlowThisFunc(("Done restoring snapshot (rc=%08X)\n", rc));
+
+ LogFlowThisFuncLeave();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// DeleteSnapshot methods (SessionMachine and related tasks)
+//
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT Machine::deleteSnapshot(const com::Guid &aId, ComPtr<IProgress> &aProgress)
+{
+ NOREF(aId);
+ NOREF(aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT SessionMachine::deleteSnapshot(const com::Guid &aId, ComPtr<IProgress> &aProgress)
+{
+ return i_deleteSnapshot(aId, aId,
+ FALSE /* fDeleteAllChildren */,
+ aProgress);
+}
+
+HRESULT Machine::deleteSnapshotAndAllChildren(const com::Guid &aId, ComPtr<IProgress> &aProgress)
+{
+ NOREF(aId);
+ NOREF(aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT SessionMachine::deleteSnapshotAndAllChildren(const com::Guid &aId, ComPtr<IProgress> &aProgress)
+{
+ return i_deleteSnapshot(aId, aId,
+ TRUE /* fDeleteAllChildren */,
+ aProgress);
+}
+
+HRESULT Machine::deleteSnapshotRange(const com::Guid &aStartId, const com::Guid &aEndId, ComPtr<IProgress> &aProgress)
+{
+ NOREF(aStartId);
+ NOREF(aEndId);
+ NOREF(aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT SessionMachine::deleteSnapshotRange(const com::Guid &aStartId, const com::Guid &aEndId, ComPtr<IProgress> &aProgress)
+{
+ return i_deleteSnapshot(aStartId, aEndId,
+ FALSE /* fDeleteAllChildren */,
+ aProgress);
+}
+
+
+/**
+ * Implementation for SessionMachine::i_deleteSnapshot().
+ *
+ * Gets called from SessionMachine::DeleteSnapshot(). Deleting a snapshot
+ * happens entirely on the server side if the machine is not running, and
+ * if it is running then the merges are done via internal session callbacks.
+ *
+ * This creates a new thread that does the work and returns a progress
+ * object to the client.
+ *
+ * Actual work then takes place in SessionMachine::i_deleteSnapshotHandler().
+ *
+ * @note Locks mParent + this + children objects for writing!
+ */
+HRESULT SessionMachine::i_deleteSnapshot(const com::Guid &aStartId,
+ const com::Guid &aEndId,
+ BOOL aDeleteAllChildren,
+ ComPtr<IProgress> &aProgress)
+{
+ LogFlowThisFuncEnter();
+
+ AssertReturn(!aStartId.isZero() && !aEndId.isZero() && aStartId.isValid() && aEndId.isValid(), E_INVALIDARG);
+
+ /** @todo implement the "and all children" and "range" variants */
+ if (aDeleteAllChildren || aStartId != aEndId)
+ ReturnComNotImplemented();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (Global::IsTransient(mData->mMachineState))
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot delete a snapshot of the machine while it is changing the state (machine state: %s)"),
+ Global::stringifyMachineState(mData->mMachineState));
+
+ // be very picky about machine states
+ if ( Global::IsOnlineOrTransient(mData->mMachineState)
+ && mData->mMachineState != MachineState_PoweredOff
+ && mData->mMachineState != MachineState_Saved
+ && mData->mMachineState != MachineState_Teleported
+ && mData->mMachineState != MachineState_Aborted
+ && mData->mMachineState != MachineState_AbortedSaved
+ && mData->mMachineState != MachineState_Running
+ && mData->mMachineState != MachineState_Paused)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Invalid machine state: %s"),
+ Global::stringifyMachineState(mData->mMachineState));
+
+ HRESULT rc = i_checkStateDependency(MutableOrSavedOrRunningStateDep);
+ if (FAILED(rc))
+ return rc;
+
+ ComObjPtr<Snapshot> pSnapshot;
+ rc = i_findSnapshotById(aStartId, pSnapshot, true /* aSetError */);
+ if (FAILED(rc))
+ return rc;
+
+ AutoWriteLock snapshotLock(pSnapshot COMMA_LOCKVAL_SRC_POS);
+ Utf8Str str;
+
+ size_t childrenCount = pSnapshot->i_getChildrenCount();
+ if (childrenCount > 1)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Snapshot '%s' of the machine '%s' cannot be deleted, because it has %d child snapshots, which is more than the one snapshot allowed for deletion",
+ "", childrenCount),
+ pSnapshot->i_getName().c_str(),
+ mUserData->s.strName.c_str(),
+ childrenCount);
+
+ if (pSnapshot == mData->mCurrentSnapshot && childrenCount >= 1)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("Snapshot '%s' of the machine '%s' cannot be deleted, because it is the current snapshot and has one child snapshot"),
+ pSnapshot->i_getName().c_str(),
+ mUserData->s.strName.c_str());
+
+ /* If the snapshot being deleted is the current one, ensure current
+ * settings are committed and saved.
+ */
+ if (pSnapshot == mData->mCurrentSnapshot)
+ {
+ if (mData->flModifications)
+ {
+ snapshotLock.release();
+ rc = i_saveSettings(NULL, alock);
+ snapshotLock.acquire();
+ // no need to change for whether VirtualBox.xml needs saving since
+ // we can't have a machine XML rename pending at this point
+ if (FAILED(rc)) return rc;
+ }
+ }
+
+ ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->i_getSnapshotMachine();
+
+ /* create a progress object. The number of operations is:
+ * 1 (preparing) + 1 if the snapshot is online + # of normal hard disks
+ */
+ LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
+
+ ULONG ulOpCount = 1; // one for preparations
+ ULONG ulTotalWeight = 1; // one for preparations
+
+ if (pSnapshot->i_getStateFilePath().isNotEmpty())
+ {
+ ++ulOpCount;
+ ++ulTotalWeight; // assume 1 MB for deleting the state file
+ }
+
+ bool fDeleteOnline = mData->mMachineState == MachineState_Running || mData->mMachineState == MachineState_Paused;
+
+ // count normal hard disks and add their sizes to the weight
+ for (MediumAttachmentList::iterator
+ it = pSnapMachine->mMediumAttachments->begin();
+ it != pSnapMachine->mMediumAttachments->end();
+ ++it)
+ {
+ ComObjPtr<MediumAttachment> &pAttach = *it;
+ AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
+ if (pAttach->i_getType() == DeviceType_HardDisk)
+ {
+ ComObjPtr<Medium> pHD = pAttach->i_getMedium();
+ Assert(pHD);
+ AutoReadLock mlock(pHD COMMA_LOCKVAL_SRC_POS);
+
+ MediumType_T type = pHD->i_getType();
+ // writethrough and shareable images are unaffected by snapshots,
+ // so do nothing for them
+ if ( type != MediumType_Writethrough
+ && type != MediumType_Shareable
+ && type != MediumType_Readonly)
+ {
+ // normal or immutable media need attention
+ ++ulOpCount;
+ // offline merge includes medium resizing
+ if (!fDeleteOnline)
+ ++ulOpCount;
+ ulTotalWeight += (ULONG)(pHD->i_getSize() / _1M);
+ }
+ LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount, pHD->i_getName().c_str()));
+ }
+ }
+
+ ComObjPtr<Progress> pProgress;
+ pProgress.createObject();
+ pProgress->init(mParent, static_cast<IMachine*>(this),
+ BstrFmt(tr("Deleting snapshot '%s'"), pSnapshot->i_getName().c_str()).raw(),
+ FALSE /* aCancelable */,
+ ulOpCount,
+ ulTotalWeight,
+ Bstr(tr("Setting up")).raw(),
+ 1);
+
+ /* create and start the task on a separate thread */
+ DeleteSnapshotTask *pTask = new DeleteSnapshotTask(this, pProgress,
+ "DeleteSnap",
+ fDeleteOnline,
+ pSnapshot);
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (FAILED(rc))
+ return rc;
+
+ // the task might start running but will block on acquiring the machine's write lock
+ // which we acquired above; once this function leaves, the task will be unblocked;
+ // set the proper machine state here now (note: after creating a Task instance)
+ if (mData->mMachineState == MachineState_Running)
+ {
+ i_setMachineState(MachineState_DeletingSnapshotOnline);
+ i_updateMachineStateOnClient();
+ }
+ else if (mData->mMachineState == MachineState_Paused)
+ {
+ i_setMachineState(MachineState_DeletingSnapshotPaused);
+ i_updateMachineStateOnClient();
+ }
+ else
+ i_setMachineState(MachineState_DeletingSnapshot);
+
+ /* return the progress to the caller */
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+
+ LogFlowThisFuncLeave();
+
+ return S_OK;
+}
+
+/**
+ * Helper struct for SessionMachine::deleteSnapshotHandler().
+ */
+struct MediumDeleteRec
+{
+ MediumDeleteRec()
+ : mfNeedsOnlineMerge(false),
+ mpMediumLockList(NULL)
+ {}
+
+ MediumDeleteRec(const ComObjPtr<Medium> &aHd,
+ const ComObjPtr<Medium> &aSource,
+ const ComObjPtr<Medium> &aTarget,
+ const ComObjPtr<MediumAttachment> &aOnlineMediumAttachment,
+ bool fMergeForward,
+ const ComObjPtr<Medium> &aParentForTarget,
+ MediumLockList *aChildrenToReparent,
+ bool fNeedsOnlineMerge,
+ MediumLockList *aMediumLockList,
+ const ComPtr<IToken> &aHDLockToken)
+ : mpHD(aHd),
+ mpSource(aSource),
+ mpTarget(aTarget),
+ mpOnlineMediumAttachment(aOnlineMediumAttachment),
+ mfMergeForward(fMergeForward),
+ mpParentForTarget(aParentForTarget),
+ mpChildrenToReparent(aChildrenToReparent),
+ mfNeedsOnlineMerge(fNeedsOnlineMerge),
+ mpMediumLockList(aMediumLockList),
+ mpHDLockToken(aHDLockToken)
+ {}
+
+ MediumDeleteRec(const ComObjPtr<Medium> &aHd,
+ const ComObjPtr<Medium> &aSource,
+ const ComObjPtr<Medium> &aTarget,
+ const ComObjPtr<MediumAttachment> &aOnlineMediumAttachment,
+ bool fMergeForward,
+ const ComObjPtr<Medium> &aParentForTarget,
+ MediumLockList *aChildrenToReparent,
+ bool fNeedsOnlineMerge,
+ MediumLockList *aMediumLockList,
+ const ComPtr<IToken> &aHDLockToken,
+ const Guid &aMachineId,
+ const Guid &aSnapshotId)
+ : mpHD(aHd),
+ mpSource(aSource),
+ mpTarget(aTarget),
+ mpOnlineMediumAttachment(aOnlineMediumAttachment),
+ mfMergeForward(fMergeForward),
+ mpParentForTarget(aParentForTarget),
+ mpChildrenToReparent(aChildrenToReparent),
+ mfNeedsOnlineMerge(fNeedsOnlineMerge),
+ mpMediumLockList(aMediumLockList),
+ mpHDLockToken(aHDLockToken),
+ mMachineId(aMachineId),
+ mSnapshotId(aSnapshotId)
+ {}
+
+ ComObjPtr<Medium> mpHD;
+ ComObjPtr<Medium> mpSource;
+ ComObjPtr<Medium> mpTarget;
+ ComObjPtr<MediumAttachment> mpOnlineMediumAttachment;
+ bool mfMergeForward;
+ ComObjPtr<Medium> mpParentForTarget;
+ MediumLockList *mpChildrenToReparent;
+ bool mfNeedsOnlineMerge;
+ MediumLockList *mpMediumLockList;
+ /** optional lock token, used only in case mpHD is not merged/deleted */
+ ComPtr<IToken> mpHDLockToken;
+ /* these are for reattaching the hard disk in case of a failure: */
+ Guid mMachineId;
+ Guid mSnapshotId;
+};
+
+typedef std::list<MediumDeleteRec> MediumDeleteRecList;
+
+/**
+ * Worker method for the delete snapshot thread created by
+ * SessionMachine::DeleteSnapshot(). This method gets called indirectly
+ * through SessionMachine::taskHandler() which then calls
+ * DeleteSnapshotTask::handler().
+ *
+ * The DeleteSnapshotTask contains the progress object returned to the console
+ * by SessionMachine::DeleteSnapshot, through which progress and results are
+ * reported.
+ *
+ * SessionMachine::DeleteSnapshot() has set the machine state to
+ * MachineState_DeletingSnapshot right after creating this task. Since we block
+ * on the machine write lock at the beginning, once that has been acquired, we
+ * can assume that the machine state is indeed that.
+ *
+ * @note Locks the machine + the snapshot + the media tree for writing!
+ *
+ * @param task Task data.
+ */
+void SessionMachine::i_deleteSnapshotHandler(DeleteSnapshotTask &task)
+{
+ LogFlowThisFuncEnter();
+
+ MultiResult mrc(S_OK);
+ AutoCaller autoCaller(this);
+ LogFlowThisFunc(("state=%d\n", getObjectState().getState()));
+ if (FAILED(autoCaller.rc()))
+ {
+ /* we might have been uninitialized because the session was accidentally
+ * closed by the client, so don't assert */
+ mrc = setError(E_FAIL,
+ tr("The session has been accidentally closed"));
+ task.m_pProgress->i_notifyComplete(mrc);
+ LogFlowThisFuncLeave();
+ return;
+ }
+
+ MediumDeleteRecList toDelete;
+ Guid snapshotId;
+ std::set<ComObjPtr<Medium> > pMediumsForNotify;
+ std::map<Guid,DeviceType_T> uIdsForNotify;
+
+ try
+ {
+ HRESULT rc = S_OK;
+
+ /* Locking order: */
+ AutoMultiWriteLock2 multiLock(this->lockHandle(), // machine
+ task.m_pSnapshot->lockHandle() // snapshot
+ COMMA_LOCKVAL_SRC_POS);
+ // once we have this lock, we know that SessionMachine::DeleteSnapshot()
+ // has exited after setting the machine state to MachineState_DeletingSnapshot
+
+ AutoWriteLock treeLock(mParent->i_getMediaTreeLockHandle()
+ COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<SnapshotMachine> pSnapMachine = task.m_pSnapshot->i_getSnapshotMachine();
+ // no need to lock the snapshot machine since it is const by definition
+ Guid machineId = pSnapMachine->i_getId();
+
+ // save the snapshot ID (for callbacks)
+ snapshotId = task.m_pSnapshot->i_getId();
+
+ // first pass:
+ LogFlowThisFunc(("1: Checking hard disk merge prerequisites...\n"));
+
+ // Go thru the attachments of the snapshot machine (the media in here
+ // point to the disk states _before_ the snapshot was taken, i.e. the
+ // state we're restoring to; for each such medium, we will need to
+ // merge it with its one and only child (the diff image holding the
+ // changes written after the snapshot was taken).
+ for (MediumAttachmentList::iterator
+ it = pSnapMachine->mMediumAttachments->begin();
+ it != pSnapMachine->mMediumAttachments->end();
+ ++it)
+ {
+ ComObjPtr<MediumAttachment> &pAttach = *it;
+ AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
+ if (pAttach->i_getType() != DeviceType_HardDisk)
+ continue;
+
+ ComObjPtr<Medium> pHD = pAttach->i_getMedium();
+ Assert(!pHD.isNull());
+
+ {
+ // writethrough, shareable and readonly images are
+ // unaffected by snapshots, skip them
+ AutoReadLock medlock(pHD COMMA_LOCKVAL_SRC_POS);
+ MediumType_T type = pHD->i_getType();
+ if ( type == MediumType_Writethrough
+ || type == MediumType_Shareable
+ || type == MediumType_Readonly)
+ continue;
+ }
+
+#ifdef DEBUG
+ pHD->i_dumpBackRefs();
+#endif
+
+ // needs to be merged with child or deleted, check prerequisites
+ ComObjPtr<Medium> pTarget;
+ ComObjPtr<Medium> pSource;
+ bool fMergeForward = false;
+ ComObjPtr<Medium> pParentForTarget;
+ MediumLockList *pChildrenToReparent = NULL;
+ bool fNeedsOnlineMerge = false;
+ bool fOnlineMergePossible = task.m_fDeleteOnline;
+ MediumLockList *pMediumLockList = NULL;
+ MediumLockList *pVMMALockList = NULL;
+ ComPtr<IToken> pHDLockToken;
+ ComObjPtr<MediumAttachment> pOnlineMediumAttachment;
+ if (fOnlineMergePossible)
+ {
+ // Look up the corresponding medium attachment in the currently
+ // running VM. Any failure prevents a live merge. Could be made
+ // a tad smarter by trying a few candidates, so that e.g. disks
+ // which are simply moved to a different controller slot do not
+ // prevent online merging in general.
+ pOnlineMediumAttachment =
+ i_findAttachment(*mMediumAttachments.data(),
+ pAttach->i_getControllerName(),
+ pAttach->i_getPort(),
+ pAttach->i_getDevice());
+ if (pOnlineMediumAttachment)
+ {
+ rc = mData->mSession.mLockedMedia.Get(pOnlineMediumAttachment,
+ pVMMALockList);
+ if (FAILED(rc))
+ fOnlineMergePossible = false;
+ }
+ else
+ fOnlineMergePossible = false;
+ }
+
+ // no need to hold the lock any longer
+ attachLock.release();
+
+ treeLock.release();
+ rc = i_prepareDeleteSnapshotMedium(pHD, machineId, snapshotId,
+ fOnlineMergePossible,
+ pVMMALockList, pSource, pTarget,
+ fMergeForward, pParentForTarget,
+ pChildrenToReparent,
+ fNeedsOnlineMerge,
+ pMediumLockList,
+ pHDLockToken);
+ treeLock.acquire();
+ if (FAILED(rc))
+ throw rc;
+
+ // For simplicity, prepareDeleteSnapshotMedium selects the merge
+ // direction in the following way: we merge pHD onto its child
+ // (forward merge), not the other way round, because that saves us
+ // from unnecessarily shuffling around the attachments for the
+ // machine that follows the snapshot (next snapshot or current
+ // state), unless it's a base image. Backwards merges of the first
+ // snapshot into the base image is essential, as it ensures that
+ // when all snapshots are deleted the only remaining image is a
+ // base image. Important e.g. for medium formats which do not have
+ // a file representation such as iSCSI.
+
+ // not going to merge a big source into a small target on online merge. Otherwise it will be resized
+ if (fNeedsOnlineMerge && pSource->i_getLogicalSize() > pTarget->i_getLogicalSize())
+ {
+ rc = setError(E_FAIL,
+ tr("Unable to merge storage '%s', because it is smaller than the source image. If you resize it to have a capacity of at least %lld bytes you can retry",
+ "", pSource->i_getLogicalSize()),
+ pTarget->i_getLocationFull().c_str(), pSource->i_getLogicalSize());
+ throw rc;
+ }
+
+ // a couple paranoia checks for backward merges
+ if (pMediumLockList != NULL && !fMergeForward)
+ {
+ // parent is null -> this disk is a base hard disk: we will
+ // then do a backward merge, i.e. merge its only child onto the
+ // base disk. Here we need then to update the attachment that
+ // refers to the child and have it point to the parent instead
+ Assert(pHD->i_getChildren().size() == 1);
+
+ ComObjPtr<Medium> pReplaceHD = pHD->i_getChildren().front();
+
+ ComAssertThrow(pReplaceHD == pSource, E_FAIL);
+ }
+
+ Guid replaceMachineId;
+ Guid replaceSnapshotId;
+
+ const Guid *pReplaceMachineId = pSource->i_getFirstMachineBackrefId();
+ // minimal sanity checking
+ Assert(!pReplaceMachineId || *pReplaceMachineId == mData->mUuid);
+ if (pReplaceMachineId)
+ replaceMachineId = *pReplaceMachineId;
+
+ const Guid *pSnapshotId = pSource->i_getFirstMachineBackrefSnapshotId();
+ if (pSnapshotId)
+ replaceSnapshotId = *pSnapshotId;
+
+ if (replaceMachineId.isValid() && !replaceMachineId.isZero())
+ {
+ // Adjust the backreferences, otherwise merging will assert.
+ // Note that the medium attachment object stays associated
+ // with the snapshot until the merge was successful.
+ HRESULT rc2 = S_OK;
+ rc2 = pSource->i_removeBackReference(replaceMachineId, replaceSnapshotId);
+ AssertComRC(rc2);
+
+ toDelete.push_back(MediumDeleteRec(pHD, pSource, pTarget,
+ pOnlineMediumAttachment,
+ fMergeForward,
+ pParentForTarget,
+ pChildrenToReparent,
+ fNeedsOnlineMerge,
+ pMediumLockList,
+ pHDLockToken,
+ replaceMachineId,
+ replaceSnapshotId));
+ }
+ else
+ toDelete.push_back(MediumDeleteRec(pHD, pSource, pTarget,
+ pOnlineMediumAttachment,
+ fMergeForward,
+ pParentForTarget,
+ pChildrenToReparent,
+ fNeedsOnlineMerge,
+ pMediumLockList,
+ pHDLockToken));
+ }
+
+ {
+ /* check available space on the storage */
+ RTFOFF pcbTotal = 0;
+ RTFOFF pcbFree = 0;
+ uint32_t pcbBlock = 0;
+ uint32_t pcbSector = 0;
+ std::multimap<uint32_t, uint64_t> neededStorageFreeSpace;
+ std::map<uint32_t, const char*> serialMapToStoragePath;
+
+ for (MediumDeleteRecList::const_iterator
+ it = toDelete.begin();
+ it != toDelete.end();
+ ++it)
+ {
+ uint64_t diskSize = 0;
+ uint32_t pu32Serial = 0;
+ ComObjPtr<Medium> pSource_local = it->mpSource;
+ ComObjPtr<Medium> pTarget_local = it->mpTarget;
+ ComPtr<IMediumFormat> pTargetFormat;
+
+ {
+ if ( pSource_local.isNull()
+ || pSource_local == pTarget_local)
+ continue;
+ }
+
+ rc = pTarget_local->COMGETTER(MediumFormat)(pTargetFormat.asOutParam());
+ if (FAILED(rc))
+ throw rc;
+
+ if (pTarget_local->i_isMediumFormatFile())
+ {
+ int vrc = RTFsQuerySerial(pTarget_local->i_getLocationFull().c_str(), &pu32Serial);
+ if (RT_FAILURE(vrc))
+ {
+ rc = setError(E_FAIL,
+ tr("Unable to merge storage '%s'. Can't get storage UID"),
+ pTarget_local->i_getLocationFull().c_str());
+ throw rc;
+ }
+
+ pSource_local->COMGETTER(Size)((LONG64*)&diskSize);
+
+ /** @todo r=klaus this is too pessimistic... should take
+ * the current size and maximum size of the target image
+ * into account, because a X GB image with Y GB capacity
+ * can only grow by Y-X GB (ignoring overhead, which
+ * unfortunately is hard to estimate, some have next to
+ * nothing, some have a certain percentage...) */
+ /* store needed free space in multimap */
+ neededStorageFreeSpace.insert(std::make_pair(pu32Serial, diskSize));
+ /* linking storage UID with snapshot path, it is a helper container (just for easy finding needed path) */
+ serialMapToStoragePath.insert(std::make_pair(pu32Serial, pTarget_local->i_getLocationFull().c_str()));
+ }
+ }
+
+ while (!neededStorageFreeSpace.empty())
+ {
+ std::pair<std::multimap<uint32_t,uint64_t>::iterator,std::multimap<uint32_t,uint64_t>::iterator> ret;
+ uint64_t commonSourceStoragesSize = 0;
+
+ /* find all records in multimap with identical storage UID */
+ ret = neededStorageFreeSpace.equal_range(neededStorageFreeSpace.begin()->first);
+ std::multimap<uint32_t,uint64_t>::const_iterator it_ns = ret.first;
+
+ for (; it_ns != ret.second ; ++it_ns)
+ {
+ commonSourceStoragesSize += it_ns->second;
+ }
+
+ /* find appropriate path by storage UID */
+ std::map<uint32_t,const char*>::const_iterator it_sm = serialMapToStoragePath.find(ret.first->first);
+ /* get info about a storage */
+ if (it_sm == serialMapToStoragePath.end())
+ {
+ LogFlowThisFunc(("Path to the storage wasn't found...\n"));
+
+ rc = setError(E_INVALIDARG,
+ tr("Unable to merge storage '%s'. Path to the storage wasn't found"),
+ it_sm->second);
+ throw rc;
+ }
+
+ int vrc = RTFsQuerySizes(it_sm->second, &pcbTotal, &pcbFree, &pcbBlock, &pcbSector);
+ if (RT_FAILURE(vrc))
+ {
+ rc = setError(E_FAIL,
+ tr("Unable to merge storage '%s'. Can't get the storage size"),
+ it_sm->second);
+ throw rc;
+ }
+
+ if (commonSourceStoragesSize > (uint64_t)pcbFree)
+ {
+ LogFlowThisFunc(("Not enough free space to merge...\n"));
+
+ rc = setError(E_OUTOFMEMORY,
+ tr("Unable to merge storage '%s'. Not enough free storage space"),
+ it_sm->second);
+ throw rc;
+ }
+
+ neededStorageFreeSpace.erase(ret.first, ret.second);
+ }
+
+ serialMapToStoragePath.clear();
+ }
+
+ // we can release the locks now since the machine state is MachineState_DeletingSnapshot
+ treeLock.release();
+ multiLock.release();
+
+ /* Now we checked that we can successfully merge all normal hard disks
+ * (unless a runtime error like end-of-disc happens). Now get rid of
+ * the saved state (if present), as that will free some disk space.
+ * The snapshot itself will be deleted as late as possible, so that
+ * the user can repeat the delete operation if he runs out of disk
+ * space or cancels the delete operation. */
+
+ /* second pass: */
+ LogFlowThisFunc(("2: Deleting saved state...\n"));
+
+ {
+ // saveAllSnapshots() needs a machine lock, and the snapshots
+ // tree is protected by the machine lock as well
+ AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
+
+ Utf8Str stateFilePath = task.m_pSnapshot->i_getStateFilePath();
+ if (!stateFilePath.isEmpty())
+ {
+ task.m_pProgress->SetNextOperation(Bstr(tr("Deleting the execution state")).raw(),
+ 1); // weight
+
+ i_releaseSavedStateFile(stateFilePath, task.m_pSnapshot /* pSnapshotToIgnore */);
+
+ // machine will need saving now
+ machineLock.release();
+ mParent->i_markRegistryModified(i_getId());
+ }
+ }
+
+ /* third pass: */
+ LogFlowThisFunc(("3: Performing actual hard disk merging...\n"));
+
+ /// @todo NEWMEDIA turn the following errors into warnings because the
+ /// snapshot itself has been already deleted (and interpret these
+ /// warnings properly on the GUI side)
+ for (MediumDeleteRecList::iterator it = toDelete.begin();
+ it != toDelete.end();)
+ {
+ const ComObjPtr<Medium> &pMedium(it->mpHD);
+ ULONG ulWeight;
+
+ {
+ AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
+ ulWeight = (ULONG)(pMedium->i_getSize() / _1M);
+ }
+
+ const char *pszOperationText = it->mfNeedsOnlineMerge ?
+ tr("Merging differencing image '%s'")
+ : tr("Resizing before merge differencing image '%s'");
+
+ task.m_pProgress->SetNextOperation(BstrFmt(pszOperationText,
+ pMedium->i_getName().c_str()).raw(),
+ ulWeight);
+
+ bool fNeedSourceUninit = false;
+ bool fReparentTarget = false;
+ if (it->mpMediumLockList == NULL)
+ {
+ /* no real merge needed, just updating state and delete
+ * diff files if necessary */
+ AutoMultiWriteLock2 mLock(&mParent->i_getMediaTreeLockHandle(), pMedium->lockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ Assert( !it->mfMergeForward
+ || pMedium->i_getChildren().size() == 0);
+
+ /* Delete the differencing hard disk (has no children). Two
+ * exceptions: if it's the last medium in the chain or if it's
+ * a backward merge we don't want to handle due to complexity.
+ * In both cases leave the image in place. If it's the first
+ * exception the user can delete it later if he wants. */
+ if (!pMedium->i_getParent().isNull())
+ {
+ Assert(pMedium->i_getState() == MediumState_Deleting);
+ /* No need to hold the lock any longer. */
+ mLock.release();
+ ComObjPtr<Medium> pParent = pMedium->i_getParent();
+ Guid uMedium = pMedium->i_getId();
+ DeviceType_T uMediumType = pMedium->i_getDeviceType();
+ rc = pMedium->i_deleteStorage(&task.m_pProgress,
+ true /* aWait */,
+ false /* aNotify */);
+ if (FAILED(rc))
+ throw rc;
+
+ pMediumsForNotify.insert(pParent);
+ uIdsForNotify[uMedium] = uMediumType;
+
+ // need to uninit the deleted medium
+ fNeedSourceUninit = true;
+ }
+ }
+ else
+ {
+ {
+ //store ids before merging for notify
+ pMediumsForNotify.insert(it->mpTarget);
+ if (it->mfMergeForward)
+ pMediumsForNotify.insert(it->mpSource->i_getParent());
+ else
+ {
+ //children which will be reparented to target
+ for (MediaList::const_iterator iit = it->mpSource->i_getChildren().begin();
+ iit != it->mpSource->i_getChildren().end();
+ ++iit)
+ {
+ pMediumsForNotify.insert(*iit);
+ }
+ }
+ if (it->mfMergeForward)
+ {
+ for (ComObjPtr<Medium> pTmpMedium = it->mpTarget->i_getParent();
+ pTmpMedium && pTmpMedium != it->mpSource;
+ pTmpMedium = pTmpMedium->i_getParent())
+ {
+ uIdsForNotify[pTmpMedium->i_getId()] = pTmpMedium->i_getDeviceType();
+ }
+ uIdsForNotify[it->mpSource->i_getId()] = it->mpSource->i_getDeviceType();
+ }
+ else
+ {
+ for (ComObjPtr<Medium> pTmpMedium = it->mpSource;
+ pTmpMedium && pTmpMedium != it->mpTarget;
+ pTmpMedium = pTmpMedium->i_getParent())
+ {
+ uIdsForNotify[pTmpMedium->i_getId()] = pTmpMedium->i_getDeviceType();
+ }
+ }
+ }
+
+ bool fNeedsSave = false;
+ if (it->mfNeedsOnlineMerge)
+ {
+ // Put the medium merge information (MediumDeleteRec) where
+ // SessionMachine::FinishOnlineMergeMedium can get at it.
+ // This callback will arrive while onlineMergeMedium is
+ // still executing, and there can't be two tasks.
+ /// @todo r=klaus this hack needs to go, and the logic needs to be "unconvoluted", putting SessionMachine in charge of coordinating the reconfig/resume.
+ mConsoleTaskData.mDeleteSnapshotInfo = (void *)&(*it);
+ // online medium merge, in the direction decided earlier
+ rc = i_onlineMergeMedium(it->mpOnlineMediumAttachment,
+ it->mpSource,
+ it->mpTarget,
+ it->mfMergeForward,
+ it->mpParentForTarget,
+ it->mpChildrenToReparent,
+ it->mpMediumLockList,
+ task.m_pProgress,
+ &fNeedsSave);
+ mConsoleTaskData.mDeleteSnapshotInfo = NULL;
+ }
+ else
+ {
+ // normal medium merge, in the direction decided earlier
+ rc = it->mpSource->i_mergeTo(it->mpTarget,
+ it->mfMergeForward,
+ it->mpParentForTarget,
+ it->mpChildrenToReparent,
+ it->mpMediumLockList,
+ &task.m_pProgress,
+ true /* aWait */,
+ false /* aNotify */);
+ }
+
+ // If the merge failed, we need to do our best to have a usable
+ // VM configuration afterwards. The return code doesn't tell
+ // whether the merge completed and so we have to check if the
+ // source medium (diff images are always file based at the
+ // moment) is still there or not. Be careful not to lose the
+ // error code below, before the "Delayed failure exit".
+ if (FAILED(rc))
+ {
+ AutoReadLock mlock(it->mpSource COMMA_LOCKVAL_SRC_POS);
+ if (!it->mpSource->i_isMediumFormatFile())
+ // Diff medium not backed by a file - cannot get status so
+ // be pessimistic.
+ throw rc;
+ const Utf8Str &loc = it->mpSource->i_getLocationFull();
+ // Source medium is still there, so merge failed early.
+ if (RTFileExists(loc.c_str()))
+ throw rc;
+
+ // Source medium is gone. Assume the merge succeeded and
+ // thus it's safe to remove the attachment. We use the
+ // "Delayed failure exit" below.
+ }
+
+ // need to change the medium attachment for backward merges
+ fReparentTarget = !it->mfMergeForward;
+
+ if (!it->mfNeedsOnlineMerge)
+ {
+ // need to uninit the medium deleted by the merge
+ fNeedSourceUninit = true;
+
+ // delete the no longer needed medium lock list, which
+ // implicitly handled the unlocking
+ delete it->mpMediumLockList;
+ it->mpMediumLockList = NULL;
+ }
+ }
+
+ // Now that the medium is successfully merged/deleted/whatever,
+ // remove the medium attachment from the snapshot. For a backwards
+ // merge the target attachment needs to be removed from the
+ // snapshot, as the VM will take it over. For forward merges the
+ // source medium attachment needs to be removed.
+ ComObjPtr<MediumAttachment> pAtt;
+ if (fReparentTarget)
+ {
+ pAtt = i_findAttachment(*(pSnapMachine->mMediumAttachments.data()),
+ it->mpTarget);
+ it->mpTarget->i_removeBackReference(machineId, snapshotId);
+ }
+ else
+ pAtt = i_findAttachment(*(pSnapMachine->mMediumAttachments.data()),
+ it->mpSource);
+ pSnapMachine->mMediumAttachments->remove(pAtt);
+
+ if (fReparentTarget)
+ {
+ // Search for old source attachment and replace with target.
+ // There can be only one child snapshot in this case.
+ ComObjPtr<Machine> pMachine = this;
+ Guid childSnapshotId;
+ ComObjPtr<Snapshot> pChildSnapshot = task.m_pSnapshot->i_getFirstChild();
+ if (pChildSnapshot)
+ {
+ pMachine = pChildSnapshot->i_getSnapshotMachine();
+ childSnapshotId = pChildSnapshot->i_getId();
+ }
+ pAtt = i_findAttachment(*(pMachine->mMediumAttachments).data(), it->mpSource);
+ if (pAtt)
+ {
+ AutoWriteLock attLock(pAtt COMMA_LOCKVAL_SRC_POS);
+ pAtt->i_updateMedium(it->mpTarget);
+ it->mpTarget->i_addBackReference(pMachine->mData->mUuid, childSnapshotId);
+ }
+ else
+ {
+ // If no attachment is found do not change anything. Maybe
+ // the source medium was not attached to the snapshot.
+ // If this is an online deletion the attachment was updated
+ // already to allow the VM continue execution immediately.
+ // Needs a bit of special treatment due to this difference.
+ if (it->mfNeedsOnlineMerge)
+ it->mpTarget->i_addBackReference(pMachine->mData->mUuid, childSnapshotId);
+ }
+ }
+
+ if (fNeedSourceUninit)
+ {
+ // make sure that the diff image to be deleted has no parent,
+ // even in error cases (where the deparenting may be missing)
+ if (it->mpSource->i_getParent())
+ it->mpSource->i_deparent();
+ it->mpSource->uninit();
+ }
+
+ // One attachment is merged, must save the settings
+ mParent->i_markRegistryModified(i_getId());
+
+ // prevent calling cancelDeleteSnapshotMedium() for this attachment
+ it = toDelete.erase(it);
+
+ // Delayed failure exit when the merge cleanup failed but the
+ // merge actually succeeded.
+ if (FAILED(rc))
+ throw rc;
+ }
+
+ /* 3a: delete NVRAM file if present. */
+ {
+ Utf8Str NVRAMPath = pSnapMachine->mNvramStore->i_getNonVolatileStorageFile();
+ if (NVRAMPath.isNotEmpty() && RTFileExists(NVRAMPath.c_str()))
+ RTFileDelete(NVRAMPath.c_str());
+ }
+
+ /* third pass: */
+ {
+ // beginSnapshotDelete() needs the machine lock, and the snapshots
+ // tree is protected by the machine lock as well
+ AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
+
+ task.m_pSnapshot->i_beginSnapshotDelete();
+ task.m_pSnapshot->uninit();
+
+ machineLock.release();
+ mParent->i_markRegistryModified(i_getId());
+ }
+ }
+ catch (HRESULT aRC) {
+ mrc = aRC;
+ }
+
+ if (FAILED(mrc))
+ {
+ // preserve existing error info so that the result can
+ // be properly reported to the progress object below
+ ErrorInfoKeeper eik;
+
+ AutoMultiWriteLock2 multiLock(this->lockHandle(), // machine
+ &mParent->i_getMediaTreeLockHandle() // media tree
+ COMMA_LOCKVAL_SRC_POS);
+
+ // un-prepare the remaining hard disks
+ for (MediumDeleteRecList::const_iterator it = toDelete.begin();
+ it != toDelete.end();
+ ++it)
+ i_cancelDeleteSnapshotMedium(it->mpHD, it->mpSource,
+ it->mpChildrenToReparent,
+ it->mfNeedsOnlineMerge,
+ it->mpMediumLockList, it->mpHDLockToken,
+ it->mMachineId, it->mSnapshotId);
+ }
+
+ // whether we were successful or not, we need to set the machine
+ // state and save the machine settings;
+ {
+ // preserve existing error info so that the result can
+ // be properly reported to the progress object below
+ ErrorInfoKeeper eik;
+
+ // restore the machine state that was saved when the
+ // task was started
+ i_setMachineState(task.m_machineStateBackup);
+ if (Global::IsOnline(mData->mMachineState))
+ i_updateMachineStateOnClient();
+
+ mParent->i_saveModifiedRegistries();
+ }
+
+ // report the result (this will try to fetch current error info on failure)
+ task.m_pProgress->i_notifyComplete(mrc);
+
+ if (SUCCEEDED(mrc))
+ {
+ mParent->i_onSnapshotDeleted(mData->mUuid, snapshotId);
+ for (std::map<Guid, DeviceType_T>::const_iterator it = uIdsForNotify.begin();
+ it != uIdsForNotify.end();
+ ++it)
+ {
+ mParent->i_onMediumRegistered(it->first, it->second, FALSE);
+ }
+ for (std::set<ComObjPtr<Medium> >::const_iterator it = pMediumsForNotify.begin();
+ it != pMediumsForNotify.end();
+ ++it)
+ {
+ if (it->isNotNull())
+ mParent->i_onMediumConfigChanged(*it);
+ }
+ }
+
+ LogFlowThisFunc(("Done deleting snapshot (rc=%08X)\n", (HRESULT)mrc));
+ LogFlowThisFuncLeave();
+}
+
+/**
+ * Checks that this hard disk (part of a snapshot) may be deleted/merged and
+ * performs necessary state changes. Must not be called for writethrough disks
+ * because there is nothing to delete/merge then.
+ *
+ * This method is to be called prior to calling #deleteSnapshotMedium().
+ * If #deleteSnapshotMedium() is not called or fails, the state modifications
+ * performed by this method must be undone by #cancelDeleteSnapshotMedium().
+ *
+ * @return COM status code
+ * @param aHD Hard disk which is connected to the snapshot.
+ * @param aMachineId UUID of machine this hard disk is attached to.
+ * @param aSnapshotId UUID of snapshot this hard disk is attached to. May
+ * be a zero UUID if no snapshot is applicable.
+ * @param fOnlineMergePossible Flag whether an online merge is possible.
+ * @param aVMMALockList Medium lock list for the medium attachment of this VM.
+ * Only used if @a fOnlineMergePossible is @c true, and
+ * must be non-NULL in this case.
+ * @param aSource Source hard disk for merge (out).
+ * @param aTarget Target hard disk for merge (out).
+ * @param aMergeForward Merge direction decision (out).
+ * @param aParentForTarget New parent if target needs to be reparented (out).
+ * @param aChildrenToReparent MediumLockList with children which have to be
+ * reparented to the target (out).
+ * @param fNeedsOnlineMerge Whether this merge needs to be done online (out).
+ * If this is set to @a true then the @a aVMMALockList
+ * parameter has been modified and is returned as
+ * @a aMediumLockList.
+ * @param aMediumLockList Where to store the created medium lock list (may
+ * return NULL if no real merge is necessary).
+ * @param aHDLockToken Where to store the write lock token for aHD, in case
+ * it is not merged or deleted (out).
+ *
+ * @note Caller must hold media tree lock for writing. This locks this object
+ * and every medium object on the merge chain for writing.
+ */
+HRESULT SessionMachine::i_prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
+ const Guid &aMachineId,
+ const Guid &aSnapshotId,
+ bool fOnlineMergePossible,
+ MediumLockList *aVMMALockList,
+ ComObjPtr<Medium> &aSource,
+ ComObjPtr<Medium> &aTarget,
+ bool &aMergeForward,
+ ComObjPtr<Medium> &aParentForTarget,
+ MediumLockList * &aChildrenToReparent,
+ bool &fNeedsOnlineMerge,
+ MediumLockList * &aMediumLockList,
+ ComPtr<IToken> &aHDLockToken)
+{
+ Assert(!mParent->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+ Assert(!fOnlineMergePossible || RT_VALID_PTR(aVMMALockList));
+
+ AutoWriteLock alock(aHD COMMA_LOCKVAL_SRC_POS);
+
+ // Medium must not be writethrough/shareable/readonly at this point
+ MediumType_T type = aHD->i_getType();
+ AssertReturn( type != MediumType_Writethrough
+ && type != MediumType_Shareable
+ && type != MediumType_Readonly, E_FAIL);
+
+ aChildrenToReparent = NULL;
+ aMediumLockList = NULL;
+ fNeedsOnlineMerge = false;
+
+ if (aHD->i_getChildren().size() == 0)
+ {
+ /* This technically is no merge, set those values nevertheless.
+ * Helps with updating the medium attachments. */
+ aSource = aHD;
+ aTarget = aHD;
+
+ /* special treatment of the last hard disk in the chain: */
+ if (aHD->i_getParent().isNull())
+ {
+ /* lock only, to prevent any usage until the snapshot deletion
+ * is completed */
+ alock.release();
+ return aHD->LockWrite(aHDLockToken.asOutParam());
+ }
+
+ /* the differencing hard disk w/o children will be deleted, protect it
+ * from attaching to other VMs (this is why Deleting) */
+ return aHD->i_markForDeletion();
+ }
+
+ /* not going multi-merge as it's too expensive */
+ if (aHD->i_getChildren().size() > 1)
+ return setError(E_FAIL,
+ tr("Hard disk '%s' has more than one child hard disk (%d)"),
+ aHD->i_getLocationFull().c_str(),
+ aHD->i_getChildren().size());
+
+ ComObjPtr<Medium> pChild = aHD->i_getChildren().front();
+
+ AutoWriteLock childLock(pChild COMMA_LOCKVAL_SRC_POS);
+
+ /* the rest is a normal merge setup */
+ if (aHD->i_getParent().isNull())
+ {
+ /* base hard disk, backward merge */
+ const Guid *pMachineId1 = pChild->i_getFirstMachineBackrefId();
+ const Guid *pMachineId2 = aHD->i_getFirstMachineBackrefId();
+ if (pMachineId1 && pMachineId2 && *pMachineId1 != *pMachineId2)
+ {
+ /* backward merge is too tricky, we'll just detach on snapshot
+ * deletion, so lock only, to prevent any usage */
+ childLock.release();
+ alock.release();
+ return aHD->LockWrite(aHDLockToken.asOutParam());
+ }
+
+ aSource = pChild;
+ aTarget = aHD;
+ }
+ else
+ {
+ /* Determine best merge direction. */
+ bool fMergeForward = true;
+
+ childLock.release();
+ alock.release();
+ HRESULT rc = aHD->i_queryPreferredMergeDirection(pChild, fMergeForward);
+ alock.acquire();
+ childLock.acquire();
+
+ if (FAILED(rc) && rc != E_FAIL)
+ return rc;
+
+ if (fMergeForward)
+ {
+ aSource = aHD;
+ aTarget = pChild;
+ LogFlowThisFunc(("Forward merging selected\n"));
+ }
+ else
+ {
+ aSource = pChild;
+ aTarget = aHD;
+ LogFlowThisFunc(("Backward merging selected\n"));
+ }
+ }
+
+ HRESULT rc;
+ childLock.release();
+ alock.release();
+ rc = aSource->i_prepareMergeTo(aTarget, &aMachineId, &aSnapshotId,
+ !fOnlineMergePossible /* fLockMedia */,
+ aMergeForward, aParentForTarget,
+ aChildrenToReparent, aMediumLockList);
+ alock.acquire();
+ childLock.acquire();
+ if (SUCCEEDED(rc) && fOnlineMergePossible)
+ {
+ /* Try to lock the newly constructed medium lock list. If it succeeds
+ * this can be handled as an offline merge, i.e. without the need of
+ * asking the VM to do the merging. Only continue with the online
+ * merging preparation if applicable. */
+ childLock.release();
+ alock.release();
+ rc = aMediumLockList->Lock();
+ alock.acquire();
+ childLock.acquire();
+ if (FAILED(rc))
+ {
+ /* Locking failed, this cannot be done as an offline merge. Try to
+ * combine the locking information into the lock list of the medium
+ * attachment in the running VM. If that fails or locking the
+ * resulting lock list fails then the merge cannot be done online.
+ * It can be repeated by the user when the VM is shut down. */
+ MediumLockList::Base::iterator lockListVMMABegin =
+ aVMMALockList->GetBegin();
+ MediumLockList::Base::iterator lockListVMMAEnd =
+ aVMMALockList->GetEnd();
+ MediumLockList::Base::iterator lockListBegin =
+ aMediumLockList->GetBegin();
+ MediumLockList::Base::iterator lockListEnd =
+ aMediumLockList->GetEnd();
+ for (MediumLockList::Base::iterator it = lockListVMMABegin,
+ it2 = lockListBegin;
+ it2 != lockListEnd;
+ ++it, ++it2)
+ {
+ if ( it == lockListVMMAEnd
+ || it->GetMedium() != it2->GetMedium())
+ {
+ fOnlineMergePossible = false;
+ break;
+ }
+ bool fLockReq = (it2->GetLockRequest() || it->GetLockRequest());
+ childLock.release();
+ alock.release();
+ rc = it->UpdateLock(fLockReq);
+ alock.acquire();
+ childLock.acquire();
+ if (FAILED(rc))
+ {
+ // could not update the lock, trigger cleanup below
+ fOnlineMergePossible = false;
+ break;
+ }
+ }
+
+ if (fOnlineMergePossible)
+ {
+ /* we will lock the children of the source for reparenting */
+ if (aChildrenToReparent && !aChildrenToReparent->IsEmpty())
+ {
+ /* Cannot just call aChildrenToReparent->Lock(), as one of
+ * the children is the one under which the current state of
+ * the VM is located, and this means it is already locked
+ * (for reading). Note that no special unlocking is needed,
+ * because cancelMergeTo will unlock everything locked in
+ * its context (using the unlock on destruction), and both
+ * cancelDeleteSnapshotMedium (in case something fails) and
+ * FinishOnlineMergeMedium re-define the read/write lock
+ * state of everything which the VM need, search for the
+ * UpdateLock method calls. */
+ childLock.release();
+ alock.release();
+ rc = aChildrenToReparent->Lock(true /* fSkipOverLockedMedia */);
+ alock.acquire();
+ childLock.acquire();
+ MediumLockList::Base::iterator childrenToReparentBegin = aChildrenToReparent->GetBegin();
+ MediumLockList::Base::iterator childrenToReparentEnd = aChildrenToReparent->GetEnd();
+ for (MediumLockList::Base::iterator it = childrenToReparentBegin;
+ it != childrenToReparentEnd;
+ ++it)
+ {
+ ComObjPtr<Medium> pMedium = it->GetMedium();
+ AutoReadLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
+ if (!it->IsLocked())
+ {
+ mediumLock.release();
+ childLock.release();
+ alock.release();
+ rc = aVMMALockList->Update(pMedium, true);
+ alock.acquire();
+ childLock.acquire();
+ mediumLock.acquire();
+ if (FAILED(rc))
+ throw rc;
+ }
+ }
+ }
+ }
+
+ if (fOnlineMergePossible)
+ {
+ childLock.release();
+ alock.release();
+ rc = aVMMALockList->Lock();
+ alock.acquire();
+ childLock.acquire();
+ if (FAILED(rc))
+ {
+ aSource->i_cancelMergeTo(aChildrenToReparent, aMediumLockList);
+ rc = setError(rc,
+ tr("Cannot lock hard disk '%s' for a live merge"),
+ aHD->i_getLocationFull().c_str());
+ }
+ else
+ {
+ delete aMediumLockList;
+ aMediumLockList = aVMMALockList;
+ fNeedsOnlineMerge = true;
+ }
+ }
+ else
+ {
+ aSource->i_cancelMergeTo(aChildrenToReparent, aMediumLockList);
+ rc = setError(rc,
+ tr("Failed to construct lock list for a live merge of hard disk '%s'"),
+ aHD->i_getLocationFull().c_str());
+ }
+
+ // fix the VM's lock list if anything failed
+ if (FAILED(rc))
+ {
+ lockListVMMABegin = aVMMALockList->GetBegin();
+ lockListVMMAEnd = aVMMALockList->GetEnd();
+ MediumLockList::Base::iterator lockListLast = lockListVMMAEnd;
+ --lockListLast;
+ for (MediumLockList::Base::iterator it = lockListVMMABegin;
+ it != lockListVMMAEnd;
+ ++it)
+ {
+ childLock.release();
+ alock.release();
+ it->UpdateLock(it == lockListLast);
+ alock.acquire();
+ childLock.acquire();
+ ComObjPtr<Medium> pMedium = it->GetMedium();
+ AutoWriteLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
+ // blindly apply this, only needed for medium objects which
+ // would be deleted as part of the merge
+ pMedium->i_unmarkLockedForDeletion();
+ }
+ }
+ }
+ }
+ else if (FAILED(rc))
+ {
+ aSource->i_cancelMergeTo(aChildrenToReparent, aMediumLockList);
+ rc = setError(rc,
+ tr("Cannot lock hard disk '%s' when deleting a snapshot"),
+ aHD->i_getLocationFull().c_str());
+ }
+
+ return rc;
+}
+
+/**
+ * Cancels the deletion/merging of this hard disk (part of a snapshot). Undoes
+ * what #prepareDeleteSnapshotMedium() did. Must be called if
+ * #deleteSnapshotMedium() is not called or fails.
+ *
+ * @param aHD Hard disk which is connected to the snapshot.
+ * @param aSource Source hard disk for merge.
+ * @param aChildrenToReparent Children to unlock.
+ * @param fNeedsOnlineMerge Whether this merge needs to be done online.
+ * @param aMediumLockList Medium locks to cancel.
+ * @param aHDLockToken Optional write lock token for aHD.
+ * @param aMachineId Machine id to attach the medium to.
+ * @param aSnapshotId Snapshot id to attach the medium to.
+ *
+ * @note Locks the medium tree and the hard disks in the chain for writing.
+ */
+void SessionMachine::i_cancelDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
+ const ComObjPtr<Medium> &aSource,
+ MediumLockList *aChildrenToReparent,
+ bool fNeedsOnlineMerge,
+ MediumLockList *aMediumLockList,
+ const ComPtr<IToken> &aHDLockToken,
+ const Guid &aMachineId,
+ const Guid &aSnapshotId)
+{
+ if (aMediumLockList == NULL)
+ {
+ AutoMultiWriteLock2 mLock(&mParent->i_getMediaTreeLockHandle(), aHD->lockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ Assert(aHD->i_getChildren().size() == 0);
+
+ if (aHD->i_getParent().isNull())
+ {
+ Assert(!aHDLockToken.isNull());
+ if (!aHDLockToken.isNull())
+ {
+ HRESULT rc = aHDLockToken->Abandon();
+ AssertComRC(rc);
+ }
+ }
+ else
+ {
+ HRESULT rc = aHD->i_unmarkForDeletion();
+ AssertComRC(rc);
+ }
+ }
+ else
+ {
+ if (fNeedsOnlineMerge)
+ {
+ // Online merge uses the medium lock list of the VM, so give
+ // an empty list to cancelMergeTo so that it works as designed.
+ aSource->i_cancelMergeTo(aChildrenToReparent, new MediumLockList());
+
+ // clean up the VM medium lock list ourselves
+ MediumLockList::Base::iterator lockListBegin =
+ aMediumLockList->GetBegin();
+ MediumLockList::Base::iterator lockListEnd =
+ aMediumLockList->GetEnd();
+ MediumLockList::Base::iterator lockListLast = lockListEnd;
+ --lockListLast;
+ for (MediumLockList::Base::iterator it = lockListBegin;
+ it != lockListEnd;
+ ++it)
+ {
+ ComObjPtr<Medium> pMedium = it->GetMedium();
+ AutoWriteLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
+ if (pMedium->i_getState() == MediumState_Deleting)
+ pMedium->i_unmarkForDeletion();
+ else
+ {
+ // blindly apply this, only needed for medium objects which
+ // would be deleted as part of the merge
+ pMedium->i_unmarkLockedForDeletion();
+ }
+ mediumLock.release();
+ it->UpdateLock(it == lockListLast);
+ mediumLock.acquire();
+ }
+ }
+ else
+ {
+ aSource->i_cancelMergeTo(aChildrenToReparent, aMediumLockList);
+ }
+ }
+
+ if (aMachineId.isValid() && !aMachineId.isZero())
+ {
+ // reattach the source media to the snapshot
+ HRESULT rc = aSource->i_addBackReference(aMachineId, aSnapshotId);
+ AssertComRC(rc);
+ }
+}
+
+/**
+ * Perform an online merge of a hard disk, i.e. the equivalent of
+ * Medium::mergeTo(), just for running VMs. If this fails you need to call
+ * #cancelDeleteSnapshotMedium().
+ *
+ * @return COM status code
+ * @param aMediumAttachment Identify where the disk is attached in the VM.
+ * @param aSource Source hard disk for merge.
+ * @param aTarget Target hard disk for merge.
+ * @param fMergeForward Merge direction.
+ * @param aParentForTarget New parent if target needs to be reparented.
+ * @param aChildrenToReparent Medium lock list with children which have to be
+ * reparented to the target.
+ * @param aMediumLockList Where to store the created medium lock list (may
+ * return NULL if no real merge is necessary).
+ * @param aProgress Progress indicator.
+ * @param pfNeedsMachineSaveSettings Whether the VM settings need to be saved (out).
+ */
+HRESULT SessionMachine::i_onlineMergeMedium(const ComObjPtr<MediumAttachment> &aMediumAttachment,
+ const ComObjPtr<Medium> &aSource,
+ const ComObjPtr<Medium> &aTarget,
+ bool fMergeForward,
+ const ComObjPtr<Medium> &aParentForTarget,
+ MediumLockList *aChildrenToReparent,
+ MediumLockList *aMediumLockList,
+ ComObjPtr<Progress> &aProgress,
+ bool *pfNeedsMachineSaveSettings)
+{
+ AssertReturn(aSource != NULL, E_FAIL);
+ AssertReturn(aTarget != NULL, E_FAIL);
+ AssertReturn(aSource != aTarget, E_FAIL);
+ AssertReturn(aMediumLockList != NULL, E_FAIL);
+ NOREF(fMergeForward);
+ NOREF(aParentForTarget);
+ NOREF(aChildrenToReparent);
+
+ HRESULT rc = S_OK;
+
+ try
+ {
+ // Similar code appears in Medium::taskMergeHandle, so
+ // if you make any changes below check whether they are applicable
+ // in that context as well.
+
+ unsigned uTargetIdx = (unsigned)-1;
+ unsigned uSourceIdx = (unsigned)-1;
+ /* Sanity check all hard disks in the chain. */
+ MediumLockList::Base::iterator lockListBegin =
+ aMediumLockList->GetBegin();
+ MediumLockList::Base::iterator lockListEnd =
+ aMediumLockList->GetEnd();
+ unsigned i = 0;
+ for (MediumLockList::Base::iterator it = lockListBegin;
+ it != lockListEnd;
+ ++it)
+ {
+ MediumLock &mediumLock = *it;
+ const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
+
+ if (pMedium == aSource)
+ uSourceIdx = i;
+ else if (pMedium == aTarget)
+ uTargetIdx = i;
+
+ // In Medium::taskMergeHandler there is lots of consistency
+ // checking which we cannot do here, as the state details are
+ // impossible to get outside the Medium class. The locking should
+ // have done the checks already.
+
+ i++;
+ }
+
+ ComAssertThrow( uSourceIdx != (unsigned)-1
+ && uTargetIdx != (unsigned)-1, E_FAIL);
+
+ ComPtr<IInternalSessionControl> directControl;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->mSession.mState != SessionState_Locked)
+ throw setError(VBOX_E_INVALID_VM_STATE,
+ tr("Machine is not locked by a session (session state: %s)"),
+ Global::stringifySessionState(mData->mSession.mState));
+ directControl = mData->mSession.mDirectControl;
+ }
+
+ // Must not hold any locks here, as this will call back to finish
+ // updating the medium attachment, chain linking and state.
+ rc = directControl->OnlineMergeMedium(aMediumAttachment,
+ uSourceIdx, uTargetIdx,
+ aProgress);
+ if (FAILED(rc))
+ throw rc;
+ }
+ catch (HRESULT aRC) { rc = aRC; }
+
+ // The callback mentioned above takes care of update the medium state
+
+ if (pfNeedsMachineSaveSettings)
+ *pfNeedsMachineSaveSettings = true;
+
+ return rc;
+}
+
+/**
+ * Implementation for IInternalMachineControl::finishOnlineMergeMedium().
+ *
+ * Gets called after the successful completion of an online merge from
+ * Console::onlineMergeMedium(), which gets invoked indirectly above in
+ * the call to IInternalSessionControl::onlineMergeMedium.
+ *
+ * This updates the medium information and medium state so that the VM
+ * can continue with the updated state of the medium chain.
+ */
+HRESULT SessionMachine::finishOnlineMergeMedium()
+{
+ HRESULT rc = S_OK;
+ MediumDeleteRec *pDeleteRec = (MediumDeleteRec *)mConsoleTaskData.mDeleteSnapshotInfo;
+ AssertReturn(pDeleteRec, E_FAIL);
+ bool fSourceHasChildren = false;
+
+ // all hard disks but the target were successfully deleted by
+ // the merge; reparent target if necessary and uninitialize media
+
+ AutoWriteLock treeLock(mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ // Declare this here to make sure the object does not get uninitialized
+ // before this method completes. Would normally happen as halfway through
+ // we delete the last reference to the no longer existing medium object.
+ ComObjPtr<Medium> targetChild;
+
+ if (pDeleteRec->mfMergeForward)
+ {
+ // first, unregister the target since it may become a base
+ // hard disk which needs re-registration
+ rc = mParent->i_unregisterMedium(pDeleteRec->mpTarget);
+ AssertComRC(rc);
+
+ // then, reparent it and disconnect the deleted branch at
+ // both ends (chain->parent() is source's parent)
+ pDeleteRec->mpTarget->i_deparent();
+ pDeleteRec->mpTarget->i_setParent(pDeleteRec->mpParentForTarget);
+ if (pDeleteRec->mpParentForTarget)
+ pDeleteRec->mpSource->i_deparent();
+
+ // then, register again
+ rc = mParent->i_registerMedium(pDeleteRec->mpTarget, &pDeleteRec->mpTarget, treeLock);
+ AssertComRC(rc);
+ }
+ else
+ {
+ Assert(pDeleteRec->mpTarget->i_getChildren().size() == 1);
+ targetChild = pDeleteRec->mpTarget->i_getChildren().front();
+
+ // disconnect the deleted branch at the elder end
+ targetChild->i_deparent();
+
+ // Update parent UUIDs of the source's children, reparent them and
+ // disconnect the deleted branch at the younger end
+ if (pDeleteRec->mpChildrenToReparent && !pDeleteRec->mpChildrenToReparent->IsEmpty())
+ {
+ fSourceHasChildren = true;
+ // Fix the parent UUID of the images which needs to be moved to
+ // underneath target. The running machine has the images opened,
+ // but only for reading since the VM is paused. If anything fails
+ // we must continue. The worst possible result is that the images
+ // need manual fixing via VBoxManage to adjust the parent UUID.
+ treeLock.release();
+ pDeleteRec->mpTarget->i_fixParentUuidOfChildren(pDeleteRec->mpChildrenToReparent);
+ // The childen are still write locked, unlock them now and don't
+ // rely on the destructor doing it very late.
+ pDeleteRec->mpChildrenToReparent->Unlock();
+ treeLock.acquire();
+
+ // obey {parent,child} lock order
+ AutoWriteLock sourceLock(pDeleteRec->mpSource COMMA_LOCKVAL_SRC_POS);
+
+ MediumLockList::Base::iterator childrenBegin = pDeleteRec->mpChildrenToReparent->GetBegin();
+ MediumLockList::Base::iterator childrenEnd = pDeleteRec->mpChildrenToReparent->GetEnd();
+ for (MediumLockList::Base::iterator it = childrenBegin;
+ it != childrenEnd;
+ ++it)
+ {
+ Medium *pMedium = it->GetMedium();
+ AutoWriteLock childLock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ pMedium->i_deparent(); // removes pMedium from source
+ pMedium->i_setParent(pDeleteRec->mpTarget);
+ }
+ }
+ }
+
+ /* unregister and uninitialize all hard disks removed by the merge */
+ MediumLockList *pMediumLockList = NULL;
+ rc = mData->mSession.mLockedMedia.Get(pDeleteRec->mpOnlineMediumAttachment, pMediumLockList);
+ const ComObjPtr<Medium> &pLast = pDeleteRec->mfMergeForward ? pDeleteRec->mpTarget : pDeleteRec->mpSource;
+ AssertReturn(SUCCEEDED(rc) && pMediumLockList, E_FAIL);
+ MediumLockList::Base::iterator lockListBegin =
+ pMediumLockList->GetBegin();
+ MediumLockList::Base::iterator lockListEnd =
+ pMediumLockList->GetEnd();
+ for (MediumLockList::Base::iterator it = lockListBegin;
+ it != lockListEnd;
+ )
+ {
+ MediumLock &mediumLock = *it;
+ /* Create a real copy of the medium pointer, as the medium
+ * lock deletion below would invalidate the referenced object. */
+ const ComObjPtr<Medium> pMedium = mediumLock.GetMedium();
+
+ /* The target and all images not merged (readonly) are skipped */
+ if ( pMedium == pDeleteRec->mpTarget
+ || pMedium->i_getState() == MediumState_LockedRead)
+ {
+ ++it;
+ }
+ else
+ {
+ rc = mParent->i_unregisterMedium(pMedium);
+ AssertComRC(rc);
+
+ /* now, uninitialize the deleted hard disk (note that
+ * due to the Deleting state, uninit() will not touch
+ * the parent-child relationship so we need to
+ * uninitialize each disk individually) */
+
+ /* note that the operation initiator hard disk (which is
+ * normally also the source hard disk) is a special case
+ * -- there is one more caller added by Task to it which
+ * we must release. Also, if we are in sync mode, the
+ * caller may still hold an AutoCaller instance for it
+ * and therefore we cannot uninit() it (it's therefore
+ * the caller's responsibility) */
+ if (pMedium == pDeleteRec->mpSource)
+ {
+ Assert(pDeleteRec->mpSource->i_getChildren().size() == 0);
+ Assert(pDeleteRec->mpSource->i_getFirstMachineBackrefId() == NULL);
+ }
+
+ /* Delete the medium lock list entry, which also releases the
+ * caller added by MergeChain before uninit() and updates the
+ * iterator to point to the right place. */
+ rc = pMediumLockList->RemoveByIterator(it);
+ AssertComRC(rc);
+
+ treeLock.release();
+ pMedium->uninit();
+ treeLock.acquire();
+ }
+
+ /* Stop as soon as we reached the last medium affected by the merge.
+ * The remaining images must be kept unchanged. */
+ if (pMedium == pLast)
+ break;
+ }
+
+ /* Could be in principle folded into the previous loop, but let's keep
+ * things simple. Update the medium locking to be the standard state:
+ * all parent images locked for reading, just the last diff for writing. */
+ lockListBegin = pMediumLockList->GetBegin();
+ lockListEnd = pMediumLockList->GetEnd();
+ MediumLockList::Base::iterator lockListLast = lockListEnd;
+ --lockListLast;
+ for (MediumLockList::Base::iterator it = lockListBegin;
+ it != lockListEnd;
+ ++it)
+ {
+ it->UpdateLock(it == lockListLast);
+ }
+
+ /* If this is a backwards merge of the only remaining snapshot (i.e. the
+ * source has no children) then update the medium associated with the
+ * attachment, as the previously associated one (source) is now deleted.
+ * Without the immediate update the VM could not continue running. */
+ if (!pDeleteRec->mfMergeForward && !fSourceHasChildren)
+ {
+ AutoWriteLock attLock(pDeleteRec->mpOnlineMediumAttachment COMMA_LOCKVAL_SRC_POS);
+ pDeleteRec->mpOnlineMediumAttachment->i_updateMedium(pDeleteRec->mpTarget);
+ }
+
+ return S_OK;
+}
diff --git a/src/VBox/Main/src-server/StorageControllerImpl.cpp b/src/VBox/Main/src-server/StorageControllerImpl.cpp
new file mode 100644
index 00000000..723217cf
--- /dev/null
+++ b/src/VBox/Main/src-server/StorageControllerImpl.cpp
@@ -0,0 +1,848 @@
+/* $Id: StorageControllerImpl.cpp $ */
+/** @file
+ * Implementation of IStorageController.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_STORAGECONTROLLER
+#include "StorageControllerImpl.h"
+#include "MachineImpl.h"
+#include "VirtualBoxImpl.h"
+#include "SystemPropertiesImpl.h"
+
+#include <iprt/string.h>
+#include <iprt/cpp/utils.h>
+
+#include <iprt/errcore.h>
+#include <VBox/settings.h>
+
+#include <algorithm>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+// defines
+/////////////////////////////////////////////////////////////////////////////
+
+struct StorageController::Data
+{
+ Data(Machine * const aMachine)
+ : pVirtualBox(NULL),
+ pSystemProperties(NULL),
+ pParent(aMachine)
+ {
+ unconst(pVirtualBox) = aMachine->i_getVirtualBox();
+ unconst(pSystemProperties) = pVirtualBox->i_getSystemProperties();
+ }
+
+ VirtualBox * const pVirtualBox;
+ SystemProperties * const pSystemProperties;
+ Machine * const pParent;
+ const ComObjPtr<StorageController> pPeer;
+
+ Backupable<settings::StorageController> bd;
+};
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(StorageController)
+
+HRESULT StorageController::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void StorageController::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the storage controller object.
+ *
+ * @returns COM result indicator.
+ * @param aParent Pointer to our parent object.
+ * @param aName Name of the storage controller.
+ * @param aStorageBus Type of the storage bus.
+ * @param aInstance Instance number of the storage controller.
+ * @param fBootable Bootable flag.
+ */
+HRESULT StorageController::init(Machine *aParent,
+ const Utf8Str &aName,
+ StorageBus_T aStorageBus,
+ ULONG aInstance, bool fBootable)
+{
+ LogFlowThisFunc(("aParent=%p aName=\"%s\" aInstance=%u\n",
+ aParent, aName.c_str(), aInstance));
+
+ ComAssertRet(aParent && !aName.isEmpty(), E_INVALIDARG);
+ if ( (aStorageBus <= StorageBus_Null)
+ || (aStorageBus > StorageBus_VirtioSCSI))
+ return setError(E_INVALIDARG,
+ tr("Invalid storage connection type"));
+
+ ULONG maxInstances;
+ ChipsetType_T chipsetType;
+ HRESULT rc = aParent->COMGETTER(ChipsetType)(&chipsetType);
+ if (FAILED(rc))
+ return rc;
+ rc = aParent->i_getVirtualBox()->i_getSystemProperties()->
+ GetMaxInstancesOfStorageBus(chipsetType, aStorageBus, &maxInstances);
+ if (FAILED(rc))
+ return rc;
+ if (aInstance >= maxInstances)
+ return setError(E_INVALIDARG,
+ tr("Too many storage controllers of this type"));
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+
+ /* m->pPeer is left null */
+
+ m->bd.allocate();
+
+ m->bd->strName = aName;
+ m->bd->ulInstance = aInstance;
+ m->bd->fBootable = fBootable;
+ m->bd->storageBus = aStorageBus;
+ if ( aStorageBus != StorageBus_IDE
+ && aStorageBus != StorageBus_Floppy)
+ m->bd->fUseHostIOCache = false;
+ else
+ m->bd->fUseHostIOCache = true;
+
+ switch (aStorageBus)
+ {
+ case StorageBus_IDE:
+ m->bd->ulPortCount = 2;
+ m->bd->controllerType = StorageControllerType_PIIX4;
+ break;
+ case StorageBus_SATA:
+ m->bd->ulPortCount = 30;
+ m->bd->controllerType = StorageControllerType_IntelAhci;
+ break;
+ case StorageBus_SCSI:
+ m->bd->ulPortCount = 16;
+ m->bd->controllerType = StorageControllerType_LsiLogic;
+ break;
+ case StorageBus_Floppy:
+ m->bd->ulPortCount = 1;
+ m->bd->controllerType = StorageControllerType_I82078;
+ break;
+ case StorageBus_SAS:
+ m->bd->ulPortCount = 8;
+ m->bd->controllerType = StorageControllerType_LsiLogicSas;
+ break;
+ case StorageBus_USB:
+ m->bd->ulPortCount = 8;
+ m->bd->controllerType = StorageControllerType_USB;
+ break;
+ case StorageBus_PCIe:
+ m->bd->ulPortCount = 1;
+ m->bd->controllerType = StorageControllerType_NVMe;
+ break;
+ case StorageBus_VirtioSCSI:
+ m->bd->ulPortCount = 1;
+ m->bd->controllerType = StorageControllerType_VirtioSCSI;
+ break;
+ case StorageBus_Null: break; /* Shut up MSC. */
+#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
+ case StorageBus_32BitHack: break; /* Shut up GCC. */
+#endif
+ }
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the object given another object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @param aParent Pointer to our parent object.
+ * @param aThat
+ * @param aReshare
+ * When false, the original object will remain a data owner.
+ * Otherwise, data ownership will be transferred from the original
+ * object to this one.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for writing if @a aReshare is @c true, or for
+ * reading if @a aReshare is false.
+ */
+HRESULT StorageController::init(Machine *aParent,
+ StorageController *aThat,
+ bool aReshare /* = false */)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p, aReshare=%RTbool\n",
+ aParent, aThat, aReshare));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+
+ /* sanity */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ if (aReshare)
+ {
+ AutoWriteLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+
+ unconst(aThat->m->pPeer) = this;
+ m->bd.attach(aThat->m->bd);
+ }
+ else
+ {
+ unconst(m->pPeer) = aThat;
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.share(aThat->m->bd);
+ }
+
+ /* Confirm successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the storage controller object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ */
+HRESULT StorageController::initCopy(Machine *aParent, StorageController *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+ /* m->pPeer is left null */
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatlock(aThat COMMA_LOCKVAL_SRC_POS);
+ m->bd.attachCopy(aThat->m->bd);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void StorageController::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m->bd.free();
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pParent) = NULL;
+
+ delete m;
+ m = NULL;
+}
+
+
+// IStorageController properties
+HRESULT StorageController::getName(com::Utf8Str &aName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aName = m->bd->strName;
+
+ return S_OK;
+}
+
+HRESULT StorageController::setName(const com::Utf8Str &aName)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoMultiWriteLock2 alock(m->pParent, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->strName != aName)
+ {
+ ComObjPtr<StorageController> ctrl;
+ HRESULT rc = m->pParent->i_getStorageControllerByName(aName, ctrl, false /* aSetError */);
+ if (SUCCEEDED(rc))
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("Storage controller named '%s' already exists"),
+ aName.c_str());
+
+ Machine::MediumAttachmentList atts;
+ rc = m->pParent->i_getMediumAttachmentsOfController(m->bd->strName, atts);
+ for (Machine::MediumAttachmentList::const_iterator
+ it = atts.begin();
+ it != atts.end();
+ ++it)
+ {
+ IMediumAttachment *iA = *it;
+ MediumAttachment *pAttach = static_cast<MediumAttachment *>(iA);
+ AutoWriteLock attlock(pAttach COMMA_LOCKVAL_SRC_POS);
+ pAttach->i_updateName(aName);
+ }
+
+
+ m->bd.backup();
+ m->bd->strName = aName;
+
+ m->pParent->i_setModified(Machine::IsModified_Storage);
+ alock.release();
+
+ m->pParent->i_onStorageControllerChange(m->pParent->i_getId(), aName);
+ }
+
+ return S_OK;
+}
+
+HRESULT StorageController::getBus(StorageBus_T *aBus)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aBus = m->bd->storageBus;
+
+ return S_OK;
+}
+
+HRESULT StorageController::getControllerType(StorageControllerType_T *aControllerType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aControllerType = m->bd->controllerType;
+
+ return S_OK;
+}
+
+HRESULT StorageController::setControllerType(StorageControllerType_T aControllerType)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ switch (m->bd->storageBus)
+ {
+ case StorageBus_IDE:
+ {
+ if ( (aControllerType != StorageControllerType_PIIX3)
+ && (aControllerType != StorageControllerType_PIIX4)
+ && (aControllerType != StorageControllerType_ICH6))
+ rc = E_INVALIDARG;
+ break;
+ }
+ case StorageBus_SATA:
+ {
+ if (aControllerType != StorageControllerType_IntelAhci)
+ rc = E_INVALIDARG;
+ break;
+ }
+ case StorageBus_SCSI:
+ {
+ if ( (aControllerType != StorageControllerType_LsiLogic)
+ && (aControllerType != StorageControllerType_BusLogic))
+ rc = E_INVALIDARG;
+ break;
+ }
+ case StorageBus_Floppy:
+ {
+ if (aControllerType != StorageControllerType_I82078)
+ rc = E_INVALIDARG;
+ break;
+ }
+ case StorageBus_SAS:
+ {
+ if (aControllerType != StorageControllerType_LsiLogicSas)
+ rc = E_INVALIDARG;
+ break;
+ }
+ case StorageBus_USB:
+ {
+ if (aControllerType != StorageControllerType_USB)
+ rc = E_INVALIDARG;
+ break;
+ }
+ case StorageBus_PCIe:
+ {
+ if (aControllerType != StorageControllerType_NVMe)
+ rc = E_INVALIDARG;
+ break;
+ }
+ case StorageBus_VirtioSCSI:
+ {
+ if (aControllerType != StorageControllerType_VirtioSCSI)
+ rc = E_INVALIDARG;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid controller type %d\n", m->bd->storageBus));
+ rc = E_INVALIDARG;
+ }
+
+ if (!SUCCEEDED(rc))
+ return setError(rc,
+ tr("Invalid controller type %d"),
+ aControllerType);
+
+ if (m->bd->controllerType != aControllerType)
+ {
+ m->bd.backup();
+ m->bd->controllerType = aControllerType;
+
+ alock.release();
+ AutoWriteLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS); // m->pParent is const, needs no locking
+ m->pParent->i_setModified(Machine::IsModified_Storage);
+ mlock.release();
+
+ m->pParent->i_onStorageControllerChange(m->pParent->i_getId(), m->bd->strName);
+ }
+
+ return S_OK;
+}
+
+HRESULT StorageController::getMaxDevicesPerPortCount(ULONG *aMaxDevicesPerPortCount)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = m->pSystemProperties->GetMaxDevicesPerPortForStorageBus(m->bd->storageBus, aMaxDevicesPerPortCount);
+
+ return rc;
+}
+
+HRESULT StorageController::getMinPortCount(ULONG *aMinPortCount)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = m->pSystemProperties->GetMinPortCountForStorageBus(m->bd->storageBus, aMinPortCount);
+ return rc;
+}
+
+HRESULT StorageController::getMaxPortCount(ULONG *aMaxPortCount)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = m->pSystemProperties->GetMaxPortCountForStorageBus(m->bd->storageBus, aMaxPortCount);
+
+ return rc;
+}
+
+HRESULT StorageController::getPortCount(ULONG *aPortCount)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aPortCount = m->bd->ulPortCount;
+
+ return S_OK;
+}
+
+HRESULT StorageController::setPortCount(ULONG aPortCount)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ switch (m->bd->storageBus)
+ {
+ case StorageBus_SATA:
+ {
+ /* AHCI SATA supports a maximum of 30 ports. */
+ if (aPortCount < 1 || aPortCount > 30)
+ return setError(E_INVALIDARG,
+ tr("Invalid port count: %lu (must be in range [%lu, %lu])"),
+ aPortCount, 1, 30);
+ break;
+ }
+ case StorageBus_SCSI:
+ {
+ /*
+ * SCSI does not support setting different ports.
+ * (doesn't make sense here either).
+ * The maximum and minimum is 16 and unless the callee
+ * tries to set a different value we return an error.
+ */
+ if (aPortCount != 16)
+ return setError(E_INVALIDARG,
+ tr("Invalid port count: %lu (must be in range [%lu, %lu])"),
+ aPortCount, 16, 16);
+ break;
+ }
+ case StorageBus_IDE:
+ {
+ /*
+ * The port count is fixed to 2.
+ */
+ if (aPortCount != 2)
+ return setError(E_INVALIDARG,
+ tr("Invalid port count: %lu (must be in range [%lu, %lu])"),
+ aPortCount, 2, 2);
+ break;
+ }
+ case StorageBus_Floppy:
+ {
+ /*
+ * The port count is fixed to 1.
+ */
+ if (aPortCount != 1)
+ return setError(E_INVALIDARG,
+ tr("Invalid port count: %lu (must be in range [%lu, %lu])"),
+ aPortCount, 1, 1);
+ break;
+ }
+ case StorageBus_SAS:
+ {
+ /* SAS supports a maximum of 255 ports. */
+ if (aPortCount < 1 || aPortCount > 255)
+ return setError(E_INVALIDARG,
+ tr("Invalid port count: %lu (must be in range [%lu, %lu])"),
+ aPortCount, 1, 255);
+ break;
+ }
+ case StorageBus_USB:
+ {
+ /*
+ * The port count is fixed to 8.
+ */
+ if (aPortCount != 8)
+ return setError(E_INVALIDARG,
+ tr("Invalid port count: %lu (must be in range [%lu, %lu])"),
+ aPortCount, 8, 8);
+ break;
+ }
+ case StorageBus_PCIe:
+ {
+ /*
+ * PCIe (NVMe in particular) supports theoretically 2^32 - 1
+ * different namespaces, limit the amount artifically here.
+ */
+ if (aPortCount < 1 || aPortCount > 255)
+ return setError(E_INVALIDARG,
+ tr("Invalid port count: %lu (must be in range [%lu, %lu])"),
+ aPortCount, 1, 255);
+ break;
+ }
+ case StorageBus_VirtioSCSI:
+ {
+ /*
+ * virtio-scsi supports 256 targets (with 16384 LUNs each).
+ */
+ if (aPortCount < 1 || aPortCount > 256)
+ return setError(E_INVALIDARG,
+ tr("Invalid port count: %lu (must be in range [%lu, %lu])"),
+ aPortCount, 1, 256);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid controller type %d\n", m->bd->storageBus));
+ }
+
+ if (m->bd->ulPortCount != aPortCount)
+ {
+ m->bd.backup();
+ m->bd->ulPortCount = aPortCount;
+
+ alock.release();
+ AutoWriteLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS); // m->pParent is const, needs no locking
+ m->pParent->i_setModified(Machine::IsModified_Storage);
+ mlock.release();
+
+ m->pParent->i_onStorageControllerChange(m->pParent->i_getId(), m->bd->strName);
+ }
+
+ return S_OK;
+}
+
+HRESULT StorageController::getInstance(ULONG *aInstance)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aInstance = m->bd->ulInstance;
+
+ return S_OK;
+}
+
+HRESULT StorageController::setInstance(ULONG aInstance)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->ulInstance != aInstance)
+ {
+ m->bd.backup();
+ m->bd->ulInstance = aInstance;
+
+ alock.release();
+ AutoWriteLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS); // m->pParent is const, needs no locking
+ m->pParent->i_setModified(Machine::IsModified_Storage);
+ mlock.release();
+
+ m->pParent->i_onStorageControllerChange(m->pParent->i_getId(), m->bd->strName);
+ }
+
+ return S_OK;
+}
+
+HRESULT StorageController::getUseHostIOCache(BOOL *fUseHostIOCache)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *fUseHostIOCache = m->bd->fUseHostIOCache;
+
+ return S_OK;
+}
+
+HRESULT StorageController::setUseHostIOCache(BOOL fUseHostIOCache)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->fUseHostIOCache != !!fUseHostIOCache)
+ {
+ m->bd.backup();
+ m->bd->fUseHostIOCache = !!fUseHostIOCache;
+
+ alock.release();
+ AutoWriteLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS); // m->pParent is const, needs no locking
+ m->pParent->i_setModified(Machine::IsModified_Storage);
+ mlock.release();
+
+ m->pParent->i_onStorageControllerChange(m->pParent->i_getId(), m->bd->strName);
+ }
+
+ return S_OK;
+}
+
+HRESULT StorageController::getBootable(BOOL *fBootable)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *fBootable = m->bd->fBootable;
+
+ return S_OK;
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+const Utf8Str& StorageController::i_getName() const
+{
+ return m->bd->strName;
+}
+
+StorageControllerType_T StorageController::i_getControllerType() const
+{
+ return m->bd->controllerType;
+}
+
+StorageBus_T StorageController::i_getStorageBus() const
+{
+ return m->bd->storageBus;
+}
+
+ULONG StorageController::i_getInstance() const
+{
+ return m->bd->ulInstance;
+}
+
+bool StorageController::i_getBootable() const
+{
+ return !!m->bd->fBootable;
+}
+
+/**
+ * Checks the validity of a port and device number.
+ *
+ * @retval S_OK If the given port and device numbers are within the range
+ * supported by this controller.
+ * @retval E_INVALIDARG If not. Sets an error.
+ * @param aControllerPort Controller port number.
+ * @param aDevice Device number.
+ */
+HRESULT StorageController::i_checkPortAndDeviceValid(LONG aControllerPort,
+ LONG aDevice)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ULONG portCount = m->bd->ulPortCount;
+ ULONG devicesPerPort;
+ HRESULT rc = m->pSystemProperties->GetMaxDevicesPerPortForStorageBus(m->bd->storageBus, &devicesPerPort);
+ if (FAILED(rc)) return rc;
+
+ if ( aControllerPort < 0
+ || aControllerPort >= (LONG)portCount
+ || aDevice < 0
+ || aDevice >= (LONG)devicesPerPort
+ )
+ return setError(E_INVALIDARG,
+ tr("The port and/or device parameter are out of range: port=%d (must be in range [0, %d]), device=%d (must be in range [0, %d])"),
+ (int)aControllerPort, (int)portCount-1, (int)aDevice, (int)devicesPerPort-1);
+
+ return S_OK;
+}
+
+/** @note Locks objects for writing! */
+void StorageController::i_setBootable(BOOL fBootable)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->fBootable = RT_BOOL(fBootable);
+}
+
+/** @note Locks objects for writing! */
+void StorageController::i_rollback()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.rollback();
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object (also
+ * for writing) if there is one.
+ */
+void StorageController::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (m->pPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ {
+ m->bd.commit();
+ if (m->pPeer)
+ {
+ // attach new data to the peer and reshare it
+ m->pPeer->m->bd.attach(m->bd);
+ }
+ }
+}
+
+/**
+ * Cancels sharing (if any) by making an independent copy of data.
+ * This operation also resets this object's peer to NULL.
+ *
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ */
+void StorageController::i_unshare()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* peer is not modified, lock it for reading (m->pPeer is "master" so locked
+ * first) */
+ AutoReadLock rl(m->pPeer COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isShared())
+ {
+ if (!m->bd.isBackedUp())
+ m->bd.backup();
+
+ m->bd.commit();
+ }
+
+ unconst(m->pPeer) = NULL;
+}
+
+Machine* StorageController::i_getMachine()
+{
+ return m->pParent;
+}
+
+ComObjPtr<StorageController> StorageController::i_getPeer()
+{
+ return m->pPeer;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/SystemPropertiesImpl.cpp b/src/VBox/Main/src-server/SystemPropertiesImpl.cpp
new file mode 100644
index 00000000..6ed056b9
--- /dev/null
+++ b/src/VBox/Main/src-server/SystemPropertiesImpl.cpp
@@ -0,0 +1,2402 @@
+/* $Id: SystemPropertiesImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_SYSTEMPROPERTIES
+#include "SystemPropertiesImpl.h"
+#include "VirtualBoxImpl.h"
+#include "MachineImpl.h"
+#ifdef VBOX_WITH_EXTPACK
+# include "ExtPackManagerImpl.h"
+#endif
+#include "CPUProfileImpl.h"
+#include "AutoCaller.h"
+#include "Global.h"
+#include "LoggingNew.h"
+#include "AutostartDb.h"
+#include "VirtualBoxTranslator.h"
+
+// generated header
+#include "SchemaDefs.h"
+
+#include <iprt/dir.h>
+#include <iprt/ldr.h>
+#include <iprt/locale.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/uri.h>
+#include <iprt/cpp/utils.h>
+
+#include <iprt/errcore.h>
+#include <VBox/param.h>
+#include <VBox/settings.h>
+#include <VBox/vd.h>
+#include <VBox/vmm/cpum.h>
+
+// defines
+/////////////////////////////////////////////////////////////////////////////
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+SystemProperties::SystemProperties()
+ : mParent(NULL)
+ , m(new settings::SystemProperties)
+ , m_fLoadedX86CPUProfiles(false)
+{
+}
+
+SystemProperties::~SystemProperties()
+{
+ delete m;
+}
+
+
+HRESULT SystemProperties::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void SystemProperties::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the system information object.
+ *
+ * @returns COM result indicator
+ */
+HRESULT SystemProperties::init(VirtualBox *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_FAIL);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ i_setDefaultMachineFolder(Utf8Str::Empty);
+ i_setLoggingLevel(Utf8Str::Empty);
+ i_setDefaultHardDiskFormat(Utf8Str::Empty);
+
+ i_setVRDEAuthLibrary(Utf8Str::Empty);
+ i_setDefaultVRDEExtPack(Utf8Str::Empty);
+ i_setDefaultCryptoExtPack(Utf8Str::Empty);
+
+ m->uLogHistoryCount = 3;
+
+
+ /* On Windows, OS X and Solaris, HW virtualization use isn't exclusive
+ * by default so that VT-x or AMD-V can be shared with other
+ * hypervisors without requiring user intervention.
+ * NB: See also SystemProperties constructor in settings.h
+ */
+#if defined(RT_OS_DARWIN) || defined(RT_OS_WINDOWS) || defined(RT_OS_SOLARIS)
+ m->fExclusiveHwVirt = false;
+#else
+ m->fExclusiveHwVirt = true;
+#endif
+
+ HRESULT rc = S_OK;
+
+ /* Fetch info of all available hd backends. */
+
+ /// @todo NEWMEDIA VDBackendInfo needs to be improved to let us enumerate
+ /// any number of backends
+
+ VDBACKENDINFO aVDInfo[100];
+ unsigned cEntries;
+ int vrc = VDBackendInfo(RT_ELEMENTS(aVDInfo), aVDInfo, &cEntries);
+ AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ {
+ for (unsigned i = 0; i < cEntries; ++ i)
+ {
+ ComObjPtr<MediumFormat> hdf;
+ rc = hdf.createObject();
+ if (FAILED(rc)) break;
+
+ rc = hdf->init(&aVDInfo[i]);
+ if (FAILED(rc)) break;
+
+ m_llMediumFormats.push_back(hdf);
+ }
+ }
+
+ /* Confirm a successful initialization */
+ if (SUCCEEDED(rc))
+ autoInitSpan.setSucceeded();
+
+ return rc;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void SystemProperties::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(mParent) = NULL;
+}
+
+// wrapped ISystemProperties properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT SystemProperties::getMinGuestRAM(ULONG *minRAM)
+
+{
+ /* no need to lock, this is const */
+ AssertCompile(MM_RAM_MIN_IN_MB >= SchemaDefs::MinGuestRAM);
+ *minRAM = MM_RAM_MIN_IN_MB;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getMaxGuestRAM(ULONG *maxRAM)
+{
+ /* no need to lock, this is const */
+ AssertCompile(MM_RAM_MAX_IN_MB <= SchemaDefs::MaxGuestRAM);
+ ULONG maxRAMSys = MM_RAM_MAX_IN_MB;
+ ULONG maxRAMArch = maxRAMSys;
+ *maxRAM = RT_MIN(maxRAMSys, maxRAMArch);
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getMinGuestVRAM(ULONG *minVRAM)
+{
+ /* no need to lock, this is const */
+ *minVRAM = SchemaDefs::MinGuestVRAM;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getMaxGuestVRAM(ULONG *maxVRAM)
+{
+ /* no need to lock, this is const */
+ *maxVRAM = SchemaDefs::MaxGuestVRAM;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getMinGuestCPUCount(ULONG *minCPUCount)
+{
+ /* no need to lock, this is const */
+ *minCPUCount = SchemaDefs::MinCPUCount; // VMM_MIN_CPU_COUNT
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getMaxGuestCPUCount(ULONG *maxCPUCount)
+{
+ /* no need to lock, this is const */
+ *maxCPUCount = SchemaDefs::MaxCPUCount; // VMM_MAX_CPU_COUNT
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getMaxGuestMonitors(ULONG *maxMonitors)
+{
+
+ /* no need to lock, this is const */
+ *maxMonitors = SchemaDefs::MaxGuestMonitors;
+
+ return S_OK;
+}
+
+
+HRESULT SystemProperties::getInfoVDSize(LONG64 *infoVDSize)
+{
+ /*
+ * The BIOS supports currently 32 bit LBA numbers (implementing the full
+ * 48 bit range is in theory trivial, but the crappy compiler makes things
+ * more difficult). This translates to almost 2 TiBytes (to be on the safe
+ * side, the reported limit is 1 MiByte less than that, as the total number
+ * of sectors should fit in 32 bits, too), which should be enough for the
+ * moment. Since the MBR partition tables support only 32bit sector numbers
+ * and thus the BIOS can only boot from disks smaller than 2T this is a
+ * rather hard limit.
+ *
+ * The virtual ATA/SATA disks support complete LBA48, and SCSI supports
+ * LBA64 (almost, more like LBA55 in practice), so the theoretical maximum
+ * disk size is 128 PiByte/16 EiByte. The GUI works nicely with 6 orders
+ * of magnitude, but not with 11..13 orders of magnitude.
+ */
+ /* no need to lock, this is const */
+ *infoVDSize = 2 * _1T - _1M;
+
+ return S_OK;
+}
+
+
+HRESULT SystemProperties::getSerialPortCount(ULONG *count)
+{
+ /* no need to lock, this is const */
+ *count = SchemaDefs::SerialPortCount;
+
+ return S_OK;
+}
+
+
+HRESULT SystemProperties::getParallelPortCount(ULONG *count)
+{
+ /* no need to lock, this is const */
+ *count = SchemaDefs::ParallelPortCount;
+
+ return S_OK;
+}
+
+
+HRESULT SystemProperties::getMaxBootPosition(ULONG *aMaxBootPosition)
+{
+ /* no need to lock, this is const */
+ *aMaxBootPosition = SchemaDefs::MaxBootPosition;
+
+ return S_OK;
+}
+
+
+HRESULT SystemProperties::getRawModeSupported(BOOL *aRawModeSupported)
+{
+ *aRawModeSupported = FALSE;
+ return S_OK;
+}
+
+
+HRESULT SystemProperties::getExclusiveHwVirt(BOOL *aExclusiveHwVirt)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aExclusiveHwVirt = m->fExclusiveHwVirt;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::setExclusiveHwVirt(BOOL aExclusiveHwVirt)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->fExclusiveHwVirt = !!aExclusiveHwVirt;
+ alock.release();
+
+ // VirtualBox::i_saveSettings() needs vbox write lock
+ AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = mParent->i_saveSettings();
+
+ return rc;
+}
+
+HRESULT SystemProperties::getMaxNetworkAdapters(ChipsetType_T aChipset, ULONG *aMaxNetworkAdapters)
+{
+ /* no need for locking, no state */
+ uint32_t uResult = Global::getMaxNetworkAdapters(aChipset);
+ if (uResult == 0)
+ AssertMsgFailed(("Invalid chipset type %d\n", aChipset));
+ *aMaxNetworkAdapters = uResult;
+ return S_OK;
+}
+
+HRESULT SystemProperties::getMaxNetworkAdaptersOfType(ChipsetType_T aChipset, NetworkAttachmentType_T aType, ULONG *count)
+{
+ /* no need for locking, no state */
+ uint32_t uResult = Global::getMaxNetworkAdapters(aChipset);
+ if (uResult == 0)
+ AssertMsgFailed(("Invalid chipset type %d\n", aChipset));
+
+ switch (aType)
+ {
+ case NetworkAttachmentType_NAT:
+ case NetworkAttachmentType_Internal:
+ case NetworkAttachmentType_NATNetwork:
+ /* chipset default is OK */
+ break;
+ case NetworkAttachmentType_Bridged:
+ /* Maybe use current host interface count here? */
+ break;
+ case NetworkAttachmentType_HostOnly:
+ uResult = RT_MIN(uResult, 8);
+ break;
+ default:
+ AssertMsgFailed(("Unhandled attachment type %d\n", aType));
+ }
+
+ *count = uResult;
+
+ return S_OK;
+}
+
+
+HRESULT SystemProperties::getMaxDevicesPerPortForStorageBus(StorageBus_T aBus,
+ ULONG *aMaxDevicesPerPort)
+{
+ /* no need to lock, this is const */
+ switch (aBus)
+ {
+ case StorageBus_SATA:
+ case StorageBus_SCSI:
+ case StorageBus_SAS:
+ case StorageBus_USB:
+ case StorageBus_PCIe:
+ case StorageBus_VirtioSCSI:
+ {
+ /* SATA and both SCSI controllers only support one device per port. */
+ *aMaxDevicesPerPort = 1;
+ break;
+ }
+ case StorageBus_IDE:
+ case StorageBus_Floppy:
+ {
+ /* The IDE and Floppy controllers support 2 devices. One as master
+ * and one as slave (or floppy drive 0 and 1). */
+ *aMaxDevicesPerPort = 2;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid bus type %d\n", aBus));
+ }
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getMinPortCountForStorageBus(StorageBus_T aBus,
+ ULONG *aMinPortCount)
+{
+ /* no need to lock, this is const */
+ switch (aBus)
+ {
+ case StorageBus_SATA:
+ case StorageBus_SAS:
+ case StorageBus_PCIe:
+ case StorageBus_VirtioSCSI:
+ {
+ *aMinPortCount = 1;
+ break;
+ }
+ case StorageBus_SCSI:
+ {
+ *aMinPortCount = 16;
+ break;
+ }
+ case StorageBus_IDE:
+ {
+ *aMinPortCount = 2;
+ break;
+ }
+ case StorageBus_Floppy:
+ {
+ *aMinPortCount = 1;
+ break;
+ }
+ case StorageBus_USB:
+ {
+ *aMinPortCount = 8;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid bus type %d\n", aBus));
+ }
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getMaxPortCountForStorageBus(StorageBus_T aBus,
+ ULONG *aMaxPortCount)
+{
+ /* no need to lock, this is const */
+ switch (aBus)
+ {
+ case StorageBus_SATA:
+ {
+ *aMaxPortCount = 30;
+ break;
+ }
+ case StorageBus_SCSI:
+ {
+ *aMaxPortCount = 16;
+ break;
+ }
+ case StorageBus_IDE:
+ {
+ *aMaxPortCount = 2;
+ break;
+ }
+ case StorageBus_Floppy:
+ {
+ *aMaxPortCount = 1;
+ break;
+ }
+ case StorageBus_SAS:
+ case StorageBus_PCIe:
+ {
+ *aMaxPortCount = 255;
+ break;
+ }
+ case StorageBus_USB:
+ {
+ *aMaxPortCount = 8;
+ break;
+ }
+ case StorageBus_VirtioSCSI:
+ {
+ *aMaxPortCount = 256;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid bus type %d\n", aBus));
+ }
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getMaxInstancesOfStorageBus(ChipsetType_T aChipset,
+ StorageBus_T aBus,
+ ULONG *aMaxInstances)
+{
+ ULONG cCtrs = 0;
+
+ /* no need to lock, this is const */
+ switch (aBus)
+ {
+ case StorageBus_SATA:
+ case StorageBus_SCSI:
+ case StorageBus_SAS:
+ case StorageBus_PCIe:
+ case StorageBus_VirtioSCSI:
+ cCtrs = aChipset == ChipsetType_ICH9 ? 8 : 1;
+ break;
+ case StorageBus_USB:
+ case StorageBus_IDE:
+ case StorageBus_Floppy:
+ {
+ cCtrs = 1;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid bus type %d\n", aBus));
+ }
+
+ *aMaxInstances = cCtrs;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getDeviceTypesForStorageBus(StorageBus_T aBus,
+ std::vector<DeviceType_T> &aDeviceTypes)
+{
+ aDeviceTypes.resize(0);
+
+ /* no need to lock, this is const */
+ switch (aBus)
+ {
+ case StorageBus_IDE:
+ case StorageBus_SATA:
+ case StorageBus_SCSI:
+ case StorageBus_SAS:
+ case StorageBus_USB:
+ case StorageBus_VirtioSCSI:
+ {
+ aDeviceTypes.resize(2);
+ aDeviceTypes[0] = DeviceType_DVD;
+ aDeviceTypes[1] = DeviceType_HardDisk;
+ break;
+ }
+ case StorageBus_Floppy:
+ {
+ aDeviceTypes.resize(1);
+ aDeviceTypes[0] = DeviceType_Floppy;
+ break;
+ }
+ case StorageBus_PCIe:
+ {
+ aDeviceTypes.resize(1);
+ aDeviceTypes[0] = DeviceType_HardDisk;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid bus type %d\n", aBus));
+ }
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getStorageBusForStorageControllerType(StorageControllerType_T aStorageControllerType,
+ StorageBus_T *aStorageBus)
+{
+ /* no need to lock, this is const */
+ switch (aStorageControllerType)
+ {
+ case StorageControllerType_LsiLogic:
+ case StorageControllerType_BusLogic:
+ *aStorageBus = StorageBus_SCSI;
+ break;
+ case StorageControllerType_IntelAhci:
+ *aStorageBus = StorageBus_SATA;
+ break;
+ case StorageControllerType_PIIX3:
+ case StorageControllerType_PIIX4:
+ case StorageControllerType_ICH6:
+ *aStorageBus = StorageBus_IDE;
+ break;
+ case StorageControllerType_I82078:
+ *aStorageBus = StorageBus_Floppy;
+ break;
+ case StorageControllerType_LsiLogicSas:
+ *aStorageBus = StorageBus_SAS;
+ break;
+ case StorageControllerType_USB:
+ *aStorageBus = StorageBus_USB;
+ break;
+ case StorageControllerType_NVMe:
+ *aStorageBus = StorageBus_PCIe;
+ break;
+ case StorageControllerType_VirtioSCSI:
+ *aStorageBus = StorageBus_VirtioSCSI;
+ break;
+ default:
+ return setError(E_FAIL, tr("Invalid storage controller type %d\n"), aStorageBus);
+ }
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getStorageControllerTypesForStorageBus(StorageBus_T aStorageBus,
+ std::vector<StorageControllerType_T> &aStorageControllerTypes)
+{
+ aStorageControllerTypes.resize(0);
+
+ /* no need to lock, this is const */
+ switch (aStorageBus)
+ {
+ case StorageBus_IDE:
+ aStorageControllerTypes.resize(3);
+ aStorageControllerTypes[0] = StorageControllerType_PIIX4;
+ aStorageControllerTypes[1] = StorageControllerType_PIIX3;
+ aStorageControllerTypes[2] = StorageControllerType_ICH6;
+ break;
+ case StorageBus_SATA:
+ aStorageControllerTypes.resize(1);
+ aStorageControllerTypes[0] = StorageControllerType_IntelAhci;
+ break;
+ case StorageBus_SCSI:
+ aStorageControllerTypes.resize(2);
+ aStorageControllerTypes[0] = StorageControllerType_LsiLogic;
+ aStorageControllerTypes[1] = StorageControllerType_BusLogic;
+ break;
+ case StorageBus_Floppy:
+ aStorageControllerTypes.resize(1);
+ aStorageControllerTypes[0] = StorageControllerType_I82078;
+ break;
+ case StorageBus_SAS:
+ aStorageControllerTypes.resize(1);
+ aStorageControllerTypes[0] = StorageControllerType_LsiLogicSas;
+ break;
+ case StorageBus_USB:
+ aStorageControllerTypes.resize(1);
+ aStorageControllerTypes[0] = StorageControllerType_USB;
+ break;
+ case StorageBus_PCIe:
+ aStorageControllerTypes.resize(1);
+ aStorageControllerTypes[0] = StorageControllerType_NVMe;
+ break;
+ case StorageBus_VirtioSCSI:
+ aStorageControllerTypes.resize(1);
+ aStorageControllerTypes[0] = StorageControllerType_VirtioSCSI;
+ break;
+ default:
+ return setError(E_FAIL, tr("Invalid storage bus %d\n"), aStorageBus);
+ }
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getDefaultIoCacheSettingForStorageController(StorageControllerType_T aControllerType,
+ BOOL *aEnabled)
+{
+ /* no need to lock, this is const */
+ switch (aControllerType)
+ {
+ case StorageControllerType_LsiLogic:
+ case StorageControllerType_BusLogic:
+ case StorageControllerType_IntelAhci:
+ case StorageControllerType_LsiLogicSas:
+ case StorageControllerType_USB:
+ case StorageControllerType_NVMe:
+ case StorageControllerType_VirtioSCSI:
+ *aEnabled = false;
+ break;
+ case StorageControllerType_PIIX3:
+ case StorageControllerType_PIIX4:
+ case StorageControllerType_ICH6:
+ case StorageControllerType_I82078:
+ *aEnabled = true;
+ break;
+ default:
+ AssertMsgFailed(("Invalid controller type %d\n", aControllerType));
+ }
+ return S_OK;
+}
+
+HRESULT SystemProperties::getStorageControllerHotplugCapable(StorageControllerType_T aControllerType,
+ BOOL *aHotplugCapable)
+{
+ switch (aControllerType)
+ {
+ case StorageControllerType_IntelAhci:
+ case StorageControllerType_USB:
+ *aHotplugCapable = true;
+ break;
+ case StorageControllerType_LsiLogic:
+ case StorageControllerType_LsiLogicSas:
+ case StorageControllerType_BusLogic:
+ case StorageControllerType_NVMe:
+ case StorageControllerType_VirtioSCSI:
+ case StorageControllerType_PIIX3:
+ case StorageControllerType_PIIX4:
+ case StorageControllerType_ICH6:
+ case StorageControllerType_I82078:
+ *aHotplugCapable = false;
+ break;
+ default:
+ AssertMsgFailedReturn(("Invalid controller type %d\n", aControllerType), E_FAIL);
+ }
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getMaxInstancesOfUSBControllerType(ChipsetType_T aChipset,
+ USBControllerType_T aType,
+ ULONG *aMaxInstances)
+{
+ NOREF(aChipset);
+ ULONG cCtrs = 0;
+
+ /* no need to lock, this is const */
+ switch (aType)
+ {
+ case USBControllerType_OHCI:
+ case USBControllerType_EHCI:
+ case USBControllerType_XHCI:
+ {
+ cCtrs = 1;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid bus type %d\n", aType));
+ }
+
+ *aMaxInstances = cCtrs;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getCPUProfiles(CPUArchitecture_T aArchitecture, const com::Utf8Str &aNamePattern,
+ std::vector<ComPtr<ICPUProfile> > &aProfiles)
+{
+ /*
+ * Validate and adjust the architecture.
+ */
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ CPUArchitecture_T enmSecondaryArch = aArchitecture;
+ bool fLoaded;
+ switch (aArchitecture)
+ {
+ case CPUArchitecture_Any:
+ aArchitecture = CPUArchitecture_AMD64;
+ RT_FALL_THROUGH();
+ case CPUArchitecture_AMD64:
+ enmSecondaryArch = CPUArchitecture_x86;
+ RT_FALL_THROUGH();
+ case CPUArchitecture_x86:
+ fLoaded = m_fLoadedX86CPUProfiles;
+ break;
+ default:
+ return setError(E_INVALIDARG, tr("Invalid or unsupported architecture value: %d"), aArchitecture);
+ }
+
+ /*
+ * Do we need to load the profiles?
+ */
+ HRESULT hrc;
+ if (fLoaded)
+ hrc = S_OK;
+ else
+ {
+ alock.release();
+ AutoWriteLock alockWrite(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Translate the architecture to a VMM module handle.
+ */
+ const char *pszVMM;
+ switch (aArchitecture)
+ {
+ case CPUArchitecture_AMD64:
+ case CPUArchitecture_x86:
+ pszVMM = "VBoxVMM";
+ fLoaded = m_fLoadedX86CPUProfiles;
+ break;
+ default:
+ AssertFailedReturn(E_INVALIDARG);
+ }
+ if (fLoaded)
+ hrc = S_OK;
+ else
+ {
+ char szPath[RTPATH_MAX];
+ int vrc = RTPathAppPrivateArch(szPath, sizeof(szPath));
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppend(szPath, sizeof(szPath), pszVMM);
+ if (RT_SUCCESS(vrc))
+ vrc = RTStrCat(szPath, sizeof(szPath), RTLdrGetSuff());
+ if (RT_SUCCESS(vrc))
+ {
+ RTLDRMOD hMod = NIL_RTLDRMOD;
+ vrc = RTLdrLoad(szPath, &hMod);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Resolve the CPUMDb APIs we need.
+ */
+ PFNCPUMDBGETENTRIES pfnGetEntries
+ = (PFNCPUMDBGETENTRIES)RTLdrGetFunction(hMod, "CPUMR3DbGetEntries");
+ PFNCPUMDBGETENTRYBYINDEX pfnGetEntryByIndex
+ = (PFNCPUMDBGETENTRYBYINDEX)RTLdrGetFunction(hMod, "CPUMR3DbGetEntryByIndex");
+ if (pfnGetEntries && pfnGetEntryByIndex)
+ {
+ size_t const cExistingProfiles = m_llCPUProfiles.size();
+
+ /*
+ * Instantate the profiles.
+ */
+ hrc = S_OK;
+ uint32_t const cEntries = pfnGetEntries();
+ for (uint32_t i = 0; i < cEntries; i++)
+ {
+ PCCPUMDBENTRY pDbEntry = pfnGetEntryByIndex(i);
+ AssertBreakStmt(pDbEntry, hrc = setError(E_UNEXPECTED, "CPUMR3DbGetEntryByIndex failed for %i", i));
+
+ ComObjPtr<CPUProfile> ptrProfile;
+ hrc = ptrProfile.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = ptrProfile->initFromDbEntry(pDbEntry);
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ m_llCPUProfiles.push_back(ptrProfile);
+ continue;
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ }
+ break;
+ }
+
+ /*
+ * On success update the flag and retake the read lock.
+ * If we fail, drop the profiles we added to the list.
+ */
+ if (SUCCEEDED(hrc))
+ {
+ switch (aArchitecture)
+ {
+ case CPUArchitecture_AMD64:
+ case CPUArchitecture_x86:
+ m_fLoadedX86CPUProfiles = true;
+ break;
+ default:
+ AssertFailedStmt(hrc = E_INVALIDARG);
+ }
+
+ alockWrite.release();
+ alock.acquire();
+ }
+ else
+ m_llCPUProfiles.resize(cExistingProfiles);
+ }
+ else
+ hrc = setErrorVrc(VERR_SYMBOL_NOT_FOUND,
+ tr("'%s' is missing symbols: CPUMR3DbGetEntries, CPUMR3DbGetEntryByIndex"), szPath);
+ RTLdrClose(hMod);
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Failed to construct load '%s': %Rrc"), szPath, vrc);
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Failed to construct path to the VMM DLL/Dylib/SharedObject: %Rrc"), vrc);
+ }
+ }
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Return the matching profiles.
+ */
+ /* Count matches: */
+ size_t cMatches = 0;
+ for (CPUProfileList_T::const_iterator it = m_llCPUProfiles.begin(); it != m_llCPUProfiles.end(); ++it)
+ if ((*it)->i_match(aArchitecture, enmSecondaryArch, aNamePattern))
+ cMatches++;
+
+ /* Resize the output array. */
+ try
+ {
+ aProfiles.resize(cMatches);
+ }
+ catch (std::bad_alloc &)
+ {
+ aProfiles.resize(0);
+ hrc = E_OUTOFMEMORY;
+ }
+
+ /* Get the return objects: */
+ if (SUCCEEDED(hrc) && cMatches > 0)
+ {
+ size_t iMatch = 0;
+ for (CPUProfileList_T::const_iterator it = m_llCPUProfiles.begin(); it != m_llCPUProfiles.end(); ++it)
+ if ((*it)->i_match(aArchitecture, enmSecondaryArch, aNamePattern))
+ {
+ AssertBreakStmt(iMatch < cMatches, hrc = E_UNEXPECTED);
+ hrc = (*it).queryInterfaceTo(aProfiles[iMatch].asOutParam());
+ if (SUCCEEDED(hrc))
+ iMatch++;
+ else
+ break;
+ }
+ AssertStmt(iMatch == cMatches || FAILED(hrc), hrc = E_UNEXPECTED);
+ }
+ }
+ return hrc;
+}
+
+
+HRESULT SystemProperties::getDefaultMachineFolder(com::Utf8Str &aDefaultMachineFolder)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aDefaultMachineFolder = m->strDefaultMachineFolder;
+ return S_OK;
+}
+
+HRESULT SystemProperties::setDefaultMachineFolder(const com::Utf8Str &aDefaultMachineFolder)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = i_setDefaultMachineFolder(aDefaultMachineFolder);
+ alock.release();
+ if (SUCCEEDED(rc))
+ {
+ // VirtualBox::i_saveSettings() needs vbox write lock
+ AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
+ rc = mParent->i_saveSettings();
+ }
+
+ return rc;
+}
+
+HRESULT SystemProperties::getLoggingLevel(com::Utf8Str &aLoggingLevel)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aLoggingLevel = m->strLoggingLevel;
+
+ if (aLoggingLevel.isEmpty())
+ aLoggingLevel = VBOXSVC_LOG_DEFAULT;
+
+ return S_OK;
+}
+
+
+HRESULT SystemProperties::setLoggingLevel(const com::Utf8Str &aLoggingLevel)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = i_setLoggingLevel(aLoggingLevel);
+ alock.release();
+
+ if (SUCCEEDED(rc))
+ {
+ AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
+ rc = mParent->i_saveSettings();
+ }
+ else
+ LogRel(("Cannot set passed logging level=%s, or the default one - Error=%Rhrc \n", aLoggingLevel.c_str(), rc));
+
+ return rc;
+}
+
+HRESULT SystemProperties::getMediumFormats(std::vector<ComPtr<IMediumFormat> > &aMediumFormats)
+{
+ MediumFormatList mediumFormats(m_llMediumFormats);
+ aMediumFormats.resize(mediumFormats.size());
+ size_t i = 0;
+ for (MediumFormatList::const_iterator it = mediumFormats.begin(); it != mediumFormats.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aMediumFormats[i].asOutParam());
+ return S_OK;
+}
+
+HRESULT SystemProperties::getDefaultHardDiskFormat(com::Utf8Str &aDefaultHardDiskFormat)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aDefaultHardDiskFormat = m->strDefaultHardDiskFormat;
+ return S_OK;
+}
+
+
+HRESULT SystemProperties::setDefaultHardDiskFormat(const com::Utf8Str &aDefaultHardDiskFormat)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = i_setDefaultHardDiskFormat(aDefaultHardDiskFormat);
+ alock.release();
+ if (SUCCEEDED(rc))
+ {
+ // VirtualBox::i_saveSettings() needs vbox write lock
+ AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
+ rc = mParent->i_saveSettings();
+ }
+
+ return rc;
+}
+
+HRESULT SystemProperties::getFreeDiskSpaceWarning(LONG64 *aFreeSpace)
+{
+ NOREF(aFreeSpace);
+ ReturnComNotImplemented();
+}
+
+HRESULT SystemProperties::setFreeDiskSpaceWarning(LONG64 /* aFreeSpace */)
+{
+ ReturnComNotImplemented();
+}
+
+HRESULT SystemProperties::getFreeDiskSpacePercentWarning(ULONG *aFreeSpacePercent)
+{
+ NOREF(aFreeSpacePercent);
+ ReturnComNotImplemented();
+}
+
+HRESULT SystemProperties::setFreeDiskSpacePercentWarning(ULONG /* aFreeSpacePercent */)
+{
+ ReturnComNotImplemented();
+}
+
+HRESULT SystemProperties::getFreeDiskSpaceError(LONG64 *aFreeSpace)
+{
+ NOREF(aFreeSpace);
+ ReturnComNotImplemented();
+}
+
+HRESULT SystemProperties::setFreeDiskSpaceError(LONG64 /* aFreeSpace */)
+{
+ ReturnComNotImplemented();
+}
+
+HRESULT SystemProperties::getFreeDiskSpacePercentError(ULONG *aFreeSpacePercent)
+{
+ NOREF(aFreeSpacePercent);
+ ReturnComNotImplemented();
+}
+
+HRESULT SystemProperties::setFreeDiskSpacePercentError(ULONG /* aFreeSpacePercent */)
+{
+ ReturnComNotImplemented();
+}
+
+HRESULT SystemProperties::getVRDEAuthLibrary(com::Utf8Str &aVRDEAuthLibrary)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aVRDEAuthLibrary = m->strVRDEAuthLibrary;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::setVRDEAuthLibrary(const com::Utf8Str &aVRDEAuthLibrary)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = i_setVRDEAuthLibrary(aVRDEAuthLibrary);
+ alock.release();
+ if (SUCCEEDED(rc))
+ {
+ // VirtualBox::i_saveSettings() needs vbox write lock
+ AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
+ rc = mParent->i_saveSettings();
+ }
+
+ return rc;
+}
+
+HRESULT SystemProperties::getWebServiceAuthLibrary(com::Utf8Str &aWebServiceAuthLibrary)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aWebServiceAuthLibrary = m->strWebServiceAuthLibrary;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::setWebServiceAuthLibrary(const com::Utf8Str &aWebServiceAuthLibrary)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = i_setWebServiceAuthLibrary(aWebServiceAuthLibrary);
+ alock.release();
+
+ if (SUCCEEDED(rc))
+ {
+ // VirtualBox::i_saveSettings() needs vbox write lock
+ AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
+ rc = mParent->i_saveSettings();
+ }
+
+ return rc;
+}
+
+HRESULT SystemProperties::getDefaultVRDEExtPack(com::Utf8Str &aExtPack)
+{
+ HRESULT hrc = S_OK;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Utf8Str strExtPack(m->strDefaultVRDEExtPack);
+ if (strExtPack.isNotEmpty())
+ {
+ if (strExtPack.equals(VBOXVRDP_KLUDGE_EXTPACK_NAME))
+ hrc = S_OK;
+ else
+#ifdef VBOX_WITH_EXTPACK
+ hrc = mParent->i_getExtPackManager()->i_checkVrdeExtPack(&strExtPack);
+#else
+ hrc = setError(E_FAIL, tr("The extension pack '%s' does not exist"), strExtPack.c_str());
+#endif
+ }
+ else
+ {
+#ifdef VBOX_WITH_EXTPACK
+ hrc = mParent->i_getExtPackManager()->i_getDefaultVrdeExtPack(&strExtPack);
+#endif
+ if (strExtPack.isEmpty())
+ {
+ /*
+ * Klugde - check if VBoxVRDP.dll/.so/.dylib is installed.
+ * This is hardcoded uglyness, sorry.
+ */
+ char szPath[RTPATH_MAX];
+ int vrc = RTPathAppPrivateArch(szPath, sizeof(szPath));
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppend(szPath, sizeof(szPath), "VBoxVRDP");
+ if (RT_SUCCESS(vrc))
+ vrc = RTStrCat(szPath, sizeof(szPath), RTLdrGetSuff());
+ if (RT_SUCCESS(vrc) && RTFileExists(szPath))
+ {
+ /* Illegal extpack name, so no conflict. */
+ strExtPack = VBOXVRDP_KLUDGE_EXTPACK_NAME;
+ }
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ aExtPack = strExtPack;
+
+ return S_OK;
+}
+
+
+HRESULT SystemProperties::setDefaultVRDEExtPack(const com::Utf8Str &aExtPack)
+{
+ HRESULT hrc = S_OK;
+ if (aExtPack.isNotEmpty())
+ {
+ if (aExtPack.equals(VBOXVRDP_KLUDGE_EXTPACK_NAME))
+ hrc = S_OK;
+ else
+#ifdef VBOX_WITH_EXTPACK
+ hrc = mParent->i_getExtPackManager()->i_checkVrdeExtPack(&aExtPack);
+#else
+ hrc = setError(E_FAIL, tr("The extension pack '%s' does not exist"), aExtPack.c_str());
+#endif
+ }
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ hrc = i_setDefaultVRDEExtPack(aExtPack);
+ if (SUCCEEDED(hrc))
+ {
+ /* VirtualBox::i_saveSettings() needs the VirtualBox write lock. */
+ alock.release();
+ AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
+ hrc = mParent->i_saveSettings();
+ }
+ }
+
+ return hrc;
+}
+
+
+HRESULT SystemProperties::getDefaultCryptoExtPack(com::Utf8Str &aExtPack)
+{
+ HRESULT hrc = S_OK;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Utf8Str strExtPack(m->strDefaultCryptoExtPack);
+ if (strExtPack.isNotEmpty())
+ {
+ if (strExtPack.equals(VBOXPUELCRYPTO_KLUDGE_EXTPACK_NAME))
+ hrc = S_OK;
+ else
+#ifdef VBOX_WITH_EXTPACK
+ hrc = mParent->i_getExtPackManager()->i_checkCryptoExtPack(&strExtPack);
+#else
+ hrc = setError(E_FAIL, tr("The extension pack '%s' does not exist"), strExtPack.c_str());
+#endif
+ }
+ else
+ {
+#ifdef VBOX_WITH_EXTPACK
+ hrc = mParent->i_getExtPackManager()->i_getDefaultCryptoExtPack(&strExtPack);
+#endif
+ if (strExtPack.isEmpty())
+ {
+ /*
+ * Klugde - check if VBoxPuelCrypto.dll/.so/.dylib is installed.
+ * This is hardcoded uglyness, sorry.
+ */
+ char szPath[RTPATH_MAX];
+ int vrc = RTPathAppPrivateArch(szPath, sizeof(szPath));
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppend(szPath, sizeof(szPath), "VBoxPuelCrypto");
+ if (RT_SUCCESS(vrc))
+ vrc = RTStrCat(szPath, sizeof(szPath), RTLdrGetSuff());
+ if (RT_SUCCESS(vrc) && RTFileExists(szPath))
+ {
+ /* Illegal extpack name, so no conflict. */
+ strExtPack = VBOXPUELCRYPTO_KLUDGE_EXTPACK_NAME;
+ }
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ aExtPack = strExtPack;
+
+ return S_OK;
+}
+
+
+HRESULT SystemProperties::setDefaultCryptoExtPack(const com::Utf8Str &aExtPack)
+{
+ HRESULT hrc = S_OK;
+ if (aExtPack.isNotEmpty())
+ {
+ if (aExtPack.equals(VBOXPUELCRYPTO_KLUDGE_EXTPACK_NAME))
+ hrc = S_OK;
+ else
+#ifdef VBOX_WITH_EXTPACK
+ hrc = mParent->i_getExtPackManager()->i_checkCryptoExtPack(&aExtPack);
+#else
+ hrc = setError(E_FAIL, tr("The extension pack '%s' does not exist"), aExtPack.c_str());
+#endif
+ }
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ hrc = i_setDefaultCryptoExtPack(aExtPack);
+ if (SUCCEEDED(hrc))
+ {
+ /* VirtualBox::i_saveSettings() needs the VirtualBox write lock. */
+ alock.release();
+ AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
+ hrc = mParent->i_saveSettings();
+ }
+ }
+
+ return hrc;
+}
+
+
+HRESULT SystemProperties::getLogHistoryCount(ULONG *count)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *count = m->uLogHistoryCount;
+
+ return S_OK;
+}
+
+
+HRESULT SystemProperties::setLogHistoryCount(ULONG count)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->uLogHistoryCount = count;
+ alock.release();
+
+ // VirtualBox::i_saveSettings() needs vbox write lock
+ AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = mParent->i_saveSettings();
+
+ return rc;
+}
+
+HRESULT SystemProperties::getDefaultAudioDriver(AudioDriverType_T *aAudioDriver)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAudioDriver = settings::MachineConfigFile::getHostDefaultAudioDriver();
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getAutostartDatabasePath(com::Utf8Str &aAutostartDbPath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aAutostartDbPath = m->strAutostartDatabasePath;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::setAutostartDatabasePath(const com::Utf8Str &aAutostartDbPath)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = i_setAutostartDatabasePath(aAutostartDbPath);
+ alock.release();
+
+ if (SUCCEEDED(rc))
+ {
+ // VirtualBox::i_saveSettings() needs vbox write lock
+ AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
+ rc = mParent->i_saveSettings();
+ }
+
+ return rc;
+}
+
+HRESULT SystemProperties::getDefaultAdditionsISO(com::Utf8Str &aDefaultAdditionsISO)
+{
+ return i_getDefaultAdditionsISO(aDefaultAdditionsISO);
+}
+
+HRESULT SystemProperties::setDefaultAdditionsISO(const com::Utf8Str &aDefaultAdditionsISO)
+{
+ RT_NOREF(aDefaultAdditionsISO);
+ /** @todo not yet implemented, settings handling is missing */
+ ReturnComNotImplemented();
+#if 0 /* not implemented */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = i_setDefaultAdditionsISO(aDefaultAdditionsISO);
+ alock.release();
+
+ if (SUCCEEDED(rc))
+ {
+ // VirtualBox::i_saveSettings() needs vbox write lock
+ AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
+ rc = mParent->i_saveSettings();
+ }
+
+ return rc;
+#endif
+}
+
+HRESULT SystemProperties::getDefaultFrontend(com::Utf8Str &aDefaultFrontend)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aDefaultFrontend = m->strDefaultFrontend;
+ return S_OK;
+}
+
+HRESULT SystemProperties::setDefaultFrontend(const com::Utf8Str &aDefaultFrontend)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (m->strDefaultFrontend == aDefaultFrontend)
+ return S_OK;
+ HRESULT rc = i_setDefaultFrontend(aDefaultFrontend);
+ alock.release();
+
+ if (SUCCEEDED(rc))
+ {
+ // VirtualBox::i_saveSettings() needs vbox write lock
+ AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
+ rc = mParent->i_saveSettings();
+ }
+
+ return rc;
+}
+
+HRESULT SystemProperties::getScreenShotFormats(std::vector<BitmapFormat_T> &aBitmapFormats)
+{
+ aBitmapFormats.push_back(BitmapFormat_BGR0);
+ aBitmapFormats.push_back(BitmapFormat_BGRA);
+ aBitmapFormats.push_back(BitmapFormat_RGBA);
+ aBitmapFormats.push_back(BitmapFormat_PNG);
+ return S_OK;
+}
+
+HRESULT SystemProperties::getProxyMode(ProxyMode_T *pProxyMode)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ ProxyMode_T enmMode = *pProxyMode = (ProxyMode_T)m->uProxyMode;
+ AssertMsgReturn(enmMode == ProxyMode_System || enmMode == ProxyMode_NoProxy || enmMode == ProxyMode_Manual,
+ ("enmMode=%d\n", enmMode), E_UNEXPECTED);
+ return S_OK;
+}
+
+HRESULT SystemProperties::setProxyMode(ProxyMode_T aProxyMode)
+{
+ /* Validate input. */
+ switch (aProxyMode)
+ {
+ case ProxyMode_System:
+ case ProxyMode_NoProxy:
+ case ProxyMode_Manual:
+ break;
+ default:
+ return setError(E_INVALIDARG, tr("Invalid ProxyMode value: %d"), (int)aProxyMode);
+ }
+
+ /* Set and write out settings. */
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->uProxyMode = aProxyMode;
+ }
+ AutoWriteLock alock(mParent COMMA_LOCKVAL_SRC_POS); /* required for saving. */
+ return mParent->i_saveSettings();
+}
+
+HRESULT SystemProperties::getProxyURL(com::Utf8Str &aProxyURL)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aProxyURL = m->strProxyUrl;
+ return S_OK;
+}
+
+HRESULT SystemProperties::setProxyURL(const com::Utf8Str &aProxyURL)
+{
+ /*
+ * Validate input.
+ */
+ Utf8Str const *pStrProxyUrl = &aProxyURL;
+ Utf8Str strTmp;
+ if (pStrProxyUrl->isNotEmpty())
+ {
+ /* RTUriParse requires a scheme, so append 'http://' if none seems present: */
+ if (pStrProxyUrl->find("://") == RTCString::npos)
+ {
+ strTmp.printf("http://%s", aProxyURL.c_str());
+ pStrProxyUrl = &strTmp;
+ }
+
+ /* Use RTUriParse to check the format. There must be a hostname, but nothing
+ can follow it and the port. */
+ RTURIPARSED Parsed;
+ int vrc = RTUriParse(pStrProxyUrl->c_str(), &Parsed);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Failed to parse proxy URL: %Rrc"), vrc);
+ if ( Parsed.cchAuthorityHost == 0
+ && !RTUriIsSchemeMatch(pStrProxyUrl->c_str(), "direct"))
+ return setError(E_INVALIDARG, tr("Proxy URL must include a hostname"));
+ if (Parsed.cchPath > 0)
+ return setError(E_INVALIDARG, tr("Proxy URL must not include a path component (%.*s)"),
+ Parsed.cchPath, pStrProxyUrl->c_str() + Parsed.offPath);
+ if (Parsed.cchQuery > 0)
+ return setError(E_INVALIDARG, tr("Proxy URL must not include a query component (?%.*s)"),
+ Parsed.cchQuery, pStrProxyUrl->c_str() + Parsed.offQuery);
+ if (Parsed.cchFragment > 0)
+ return setError(E_INVALIDARG, tr("Proxy URL must not include a fragment component (#%.*s)"),
+ Parsed.cchFragment, pStrProxyUrl->c_str() + Parsed.offFragment);
+ }
+
+ /*
+ * Set and write out settings.
+ */
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->strProxyUrl = *pStrProxyUrl;
+ }
+ AutoWriteLock alock(mParent COMMA_LOCKVAL_SRC_POS); /* required for saving. */
+ return mParent->i_saveSettings();
+}
+
+HRESULT SystemProperties::getSupportedParavirtProviders(std::vector<ParavirtProvider_T> &aSupportedParavirtProviders)
+{
+ static const ParavirtProvider_T aParavirtProviders[] =
+ {
+ ParavirtProvider_None,
+ ParavirtProvider_Default,
+ ParavirtProvider_Legacy,
+ ParavirtProvider_Minimal,
+ ParavirtProvider_HyperV,
+ ParavirtProvider_KVM,
+ };
+ aSupportedParavirtProviders.assign(aParavirtProviders,
+ aParavirtProviders + RT_ELEMENTS(aParavirtProviders));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedClipboardModes(std::vector<ClipboardMode_T> &aSupportedClipboardModes)
+{
+ static const ClipboardMode_T aClipboardModes[] =
+ {
+ ClipboardMode_Disabled,
+ ClipboardMode_HostToGuest,
+ ClipboardMode_GuestToHost,
+ ClipboardMode_Bidirectional,
+ };
+ aSupportedClipboardModes.assign(aClipboardModes,
+ aClipboardModes + RT_ELEMENTS(aClipboardModes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedDnDModes(std::vector<DnDMode_T> &aSupportedDnDModes)
+{
+ static const DnDMode_T aDnDModes[] =
+ {
+ DnDMode_Disabled,
+ DnDMode_HostToGuest,
+ DnDMode_GuestToHost,
+ DnDMode_Bidirectional,
+ };
+ aSupportedDnDModes.assign(aDnDModes,
+ aDnDModes + RT_ELEMENTS(aDnDModes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedFirmwareTypes(std::vector<FirmwareType_T> &aSupportedFirmwareTypes)
+{
+ static const FirmwareType_T aFirmwareTypes[] =
+ {
+ FirmwareType_BIOS,
+ FirmwareType_EFI,
+ FirmwareType_EFI32,
+ FirmwareType_EFI64,
+ FirmwareType_EFIDUAL,
+ };
+ aSupportedFirmwareTypes.assign(aFirmwareTypes,
+ aFirmwareTypes + RT_ELEMENTS(aFirmwareTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedPointingHIDTypes(std::vector<PointingHIDType_T> &aSupportedPointingHIDTypes)
+{
+ static const PointingHIDType_T aPointingHIDTypes[] =
+ {
+ PointingHIDType_PS2Mouse,
+#ifdef DEBUG
+ PointingHIDType_USBMouse,
+#endif
+ PointingHIDType_USBTablet,
+#ifdef DEBUG
+ PointingHIDType_ComboMouse,
+#endif
+ PointingHIDType_USBMultiTouch,
+ PointingHIDType_USBMultiTouchScreenPlusPad,
+ };
+ aSupportedPointingHIDTypes.assign(aPointingHIDTypes,
+ aPointingHIDTypes + RT_ELEMENTS(aPointingHIDTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedKeyboardHIDTypes(std::vector<KeyboardHIDType_T> &aSupportedKeyboardHIDTypes)
+{
+ static const KeyboardHIDType_T aKeyboardHIDTypes[] =
+ {
+ KeyboardHIDType_PS2Keyboard,
+ KeyboardHIDType_USBKeyboard,
+#ifdef DEBUG
+ KeyboardHIDType_ComboKeyboard,
+#endif
+ };
+ aSupportedKeyboardHIDTypes.assign(aKeyboardHIDTypes,
+ aKeyboardHIDTypes + RT_ELEMENTS(aKeyboardHIDTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedVFSTypes(std::vector<VFSType_T> &aSupportedVFSTypes)
+{
+ static const VFSType_T aVFSTypes[] =
+ {
+ VFSType_File,
+ VFSType_Cloud,
+ VFSType_S3,
+#ifdef DEBUG
+ VFSType_WebDav,
+#endif
+ };
+ aSupportedVFSTypes.assign(aVFSTypes,
+ aVFSTypes + RT_ELEMENTS(aVFSTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedImportOptions(std::vector<ImportOptions_T> &aSupportedImportOptions)
+{
+ static const ImportOptions_T aImportOptions[] =
+ {
+ ImportOptions_KeepAllMACs,
+ ImportOptions_KeepNATMACs,
+ ImportOptions_ImportToVDI,
+ };
+ aSupportedImportOptions.assign(aImportOptions,
+ aImportOptions + RT_ELEMENTS(aImportOptions));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedExportOptions(std::vector<ExportOptions_T> &aSupportedExportOptions)
+{
+ static const ExportOptions_T aExportOptions[] =
+ {
+ ExportOptions_CreateManifest,
+ ExportOptions_ExportDVDImages,
+ ExportOptions_StripAllMACs,
+ ExportOptions_StripAllNonNATMACs,
+ };
+ aSupportedExportOptions.assign(aExportOptions,
+ aExportOptions + RT_ELEMENTS(aExportOptions));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedRecordingFeatures(std::vector<RecordingFeature_T> &aSupportedRecordingFeatures)
+{
+#ifdef VBOX_WITH_RECORDING
+ static const RecordingFeature_T aRecordingFeatures[] =
+ {
+# ifdef VBOX_WITH_AUDIO_RECORDING
+ RecordingFeature_Audio,
+# endif
+ RecordingFeature_Video,
+ };
+ aSupportedRecordingFeatures.assign(aRecordingFeatures,
+ aRecordingFeatures + RT_ELEMENTS(aRecordingFeatures));
+#else /* !VBOX_WITH_RECORDING */
+ aSupportedRecordingFeatures.clear();
+#endif /* VBOX_WITH_RECORDING */
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedRecordingAudioCodecs(std::vector<RecordingAudioCodec_T> &aSupportedRecordingAudioCodecs)
+{
+ static const RecordingAudioCodec_T aRecordingAudioCodecs[] =
+ {
+ RecordingAudioCodec_None,
+#ifdef DEBUG
+ RecordingAudioCodec_WavPCM,
+#endif
+#ifdef VBOX_WITH_LIBVORBIS
+ RecordingAudioCodec_OggVorbis,
+#endif
+ };
+ aSupportedRecordingAudioCodecs.assign(aRecordingAudioCodecs,
+ aRecordingAudioCodecs + RT_ELEMENTS(aRecordingAudioCodecs));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedRecordingVideoCodecs(std::vector<RecordingVideoCodec_T> &aSupportedRecordingVideoCodecs)
+{
+ static const RecordingVideoCodec_T aRecordingVideoCodecs[] =
+ {
+ RecordingVideoCodec_None,
+#ifdef VBOX_WITH_LIBVPX
+ RecordingVideoCodec_VP8,
+#endif
+#ifdef DEBUG
+ RecordingVideoCodec_VP9,
+ RecordingVideoCodec_AV1,
+#endif
+ };
+ aSupportedRecordingVideoCodecs.assign(aRecordingVideoCodecs,
+ aRecordingVideoCodecs + RT_ELEMENTS(aRecordingVideoCodecs));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedRecordingVSModes(std::vector<RecordingVideoScalingMode_T> &aSupportedRecordingVideoScalingModes)
+{
+ static const RecordingVideoScalingMode_T aRecordingVideoScalingModes[] =
+ {
+ RecordingVideoScalingMode_None,
+#ifdef DEBUG
+ RecordingVideoScalingMode_NearestNeighbor,
+ RecordingVideoScalingMode_Bilinear,
+ RecordingVideoScalingMode_Bicubic,
+#endif
+ };
+ aSupportedRecordingVideoScalingModes.assign(aRecordingVideoScalingModes,
+ aRecordingVideoScalingModes + RT_ELEMENTS(aRecordingVideoScalingModes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedRecordingARCModes(std::vector<RecordingRateControlMode_T> &aSupportedRecordingAudioRateControlModes)
+{
+ static const RecordingRateControlMode_T aRecordingAudioRateControlModes[] =
+ {
+#ifdef DEBUG
+ RecordingRateControlMode_ABR,
+ RecordingRateControlMode_CBR,
+#endif
+ RecordingRateControlMode_VBR
+ };
+ aSupportedRecordingAudioRateControlModes.assign(aRecordingAudioRateControlModes,
+ aRecordingAudioRateControlModes + RT_ELEMENTS(aRecordingAudioRateControlModes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedRecordingVRCModes(std::vector<RecordingRateControlMode_T> &aSupportedRecordingVideoRateControlModes)
+{
+ static const RecordingRateControlMode_T aRecordingVideoRateControlModes[] =
+ {
+#ifdef DEBUG
+ RecordingRateControlMode_ABR,
+ RecordingRateControlMode_CBR,
+#endif
+ RecordingRateControlMode_VBR
+ };
+ aSupportedRecordingVideoRateControlModes.assign(aRecordingVideoRateControlModes,
+ aRecordingVideoRateControlModes + RT_ELEMENTS(aRecordingVideoRateControlModes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedGraphicsControllerTypes(std::vector<GraphicsControllerType_T> &aSupportedGraphicsControllerTypes)
+{
+ static const GraphicsControllerType_T aGraphicsControllerTypes[] =
+ {
+ GraphicsControllerType_VBoxVGA,
+ GraphicsControllerType_VMSVGA,
+ GraphicsControllerType_VBoxSVGA,
+ GraphicsControllerType_Null,
+ };
+ aSupportedGraphicsControllerTypes.assign(aGraphicsControllerTypes,
+ aGraphicsControllerTypes + RT_ELEMENTS(aGraphicsControllerTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedCloneOptions(std::vector<CloneOptions_T> &aSupportedCloneOptions)
+{
+ static const CloneOptions_T aCloneOptions[] =
+ {
+ CloneOptions_Link,
+ CloneOptions_KeepAllMACs,
+ CloneOptions_KeepNATMACs,
+ CloneOptions_KeepDiskNames,
+ CloneOptions_KeepHwUUIDs,
+ };
+ aSupportedCloneOptions.assign(aCloneOptions,
+ aCloneOptions + RT_ELEMENTS(aCloneOptions));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedAutostopTypes(std::vector<AutostopType_T> &aSupportedAutostopTypes)
+{
+ static const AutostopType_T aAutostopTypes[] =
+ {
+ AutostopType_Disabled,
+ AutostopType_SaveState,
+ AutostopType_PowerOff,
+ AutostopType_AcpiShutdown,
+ };
+ aSupportedAutostopTypes.assign(aAutostopTypes,
+ aAutostopTypes + RT_ELEMENTS(aAutostopTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedVMProcPriorities(std::vector<VMProcPriority_T> &aSupportedVMProcPriorities)
+{
+ static const VMProcPriority_T aVMProcPriorities[] =
+ {
+ VMProcPriority_Default,
+ VMProcPriority_Flat,
+ VMProcPriority_Low,
+ VMProcPriority_Normal,
+ VMProcPriority_High,
+ };
+ aSupportedVMProcPriorities.assign(aVMProcPriorities,
+ aVMProcPriorities + RT_ELEMENTS(aVMProcPriorities));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedNetworkAttachmentTypes(std::vector<NetworkAttachmentType_T> &aSupportedNetworkAttachmentTypes)
+{
+ static const NetworkAttachmentType_T aNetworkAttachmentTypes[] =
+ {
+ NetworkAttachmentType_NAT,
+ NetworkAttachmentType_Bridged,
+ NetworkAttachmentType_Internal,
+ NetworkAttachmentType_HostOnly,
+#ifdef VBOX_WITH_VMNET
+ NetworkAttachmentType_HostOnlyNetwork,
+#endif /* VBOX_WITH_VMNET */
+ NetworkAttachmentType_Generic,
+ NetworkAttachmentType_NATNetwork,
+#ifdef VBOX_WITH_CLOUD_NET
+ NetworkAttachmentType_Cloud,
+#endif
+ NetworkAttachmentType_Null,
+ };
+ aSupportedNetworkAttachmentTypes.assign(aNetworkAttachmentTypes,
+ aNetworkAttachmentTypes + RT_ELEMENTS(aNetworkAttachmentTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedNetworkAdapterTypes(std::vector<NetworkAdapterType_T> &aSupportedNetworkAdapterTypes)
+{
+ static const NetworkAdapterType_T aNetworkAdapterTypes[] =
+ {
+ NetworkAdapterType_Am79C970A,
+ NetworkAdapterType_Am79C973,
+ NetworkAdapterType_I82540EM,
+ NetworkAdapterType_I82543GC,
+ NetworkAdapterType_I82545EM,
+ NetworkAdapterType_Virtio,
+ };
+ aSupportedNetworkAdapterTypes.assign(aNetworkAdapterTypes,
+ aNetworkAdapterTypes + RT_ELEMENTS(aNetworkAdapterTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedPortModes(std::vector<PortMode_T> &aSupportedPortModes)
+{
+ static const PortMode_T aPortModes[] =
+ {
+ PortMode_Disconnected,
+ PortMode_HostPipe,
+ PortMode_HostDevice,
+ PortMode_RawFile,
+ PortMode_TCP,
+ };
+ aSupportedPortModes.assign(aPortModes,
+ aPortModes + RT_ELEMENTS(aPortModes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedUartTypes(std::vector<UartType_T> &aSupportedUartTypes)
+{
+ static const UartType_T aUartTypes[] =
+ {
+ UartType_U16450,
+ UartType_U16550A,
+ UartType_U16750,
+ };
+ aSupportedUartTypes.assign(aUartTypes,
+ aUartTypes + RT_ELEMENTS(aUartTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedUSBControllerTypes(std::vector<USBControllerType_T> &aSupportedUSBControllerTypes)
+{
+ static const USBControllerType_T aUSBControllerTypes[] =
+ {
+ USBControllerType_OHCI,
+ USBControllerType_EHCI,
+ USBControllerType_XHCI,
+ };
+ aSupportedUSBControllerTypes.assign(aUSBControllerTypes,
+ aUSBControllerTypes + RT_ELEMENTS(aUSBControllerTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedAudioDriverTypes(std::vector<AudioDriverType_T> &aSupportedAudioDriverTypes)
+{
+ static const AudioDriverType_T aAudioDriverTypes[] =
+ {
+ AudioDriverType_Default,
+#ifdef RT_OS_WINDOWS
+# if 0 /* deprecated for many years now */
+ AudioDriverType_WinMM,
+# endif
+ AudioDriverType_WAS,
+ AudioDriverType_DirectSound,
+#endif
+#ifdef RT_OS_DARWIN
+ AudioDriverType_CoreAudio,
+#endif
+#ifdef RT_OS_OS2
+ AudioDriverType_MMPM,
+#endif
+#ifdef RT_OS_SOLARIS
+# if 0 /* deprecated for many years now */
+ AudioDriverType_SolAudio,
+# endif
+#endif
+#ifdef VBOX_WITH_AUDIO_ALSA
+ AudioDriverType_ALSA,
+#endif
+#ifdef VBOX_WITH_AUDIO_OSS
+ AudioDriverType_OSS,
+#endif
+#ifdef VBOX_WITH_AUDIO_PULSE
+ AudioDriverType_Pulse,
+#endif
+ AudioDriverType_Null,
+ };
+ aSupportedAudioDriverTypes.assign(aAudioDriverTypes,
+ aAudioDriverTypes + RT_ELEMENTS(aAudioDriverTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedAudioControllerTypes(std::vector<AudioControllerType_T> &aSupportedAudioControllerTypes)
+{
+ static const AudioControllerType_T aAudioControllerTypes[] =
+ {
+ AudioControllerType_AC97,
+ AudioControllerType_SB16,
+ AudioControllerType_HDA,
+ };
+ aSupportedAudioControllerTypes.assign(aAudioControllerTypes,
+ aAudioControllerTypes + RT_ELEMENTS(aAudioControllerTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedStorageBuses(std::vector<StorageBus_T> &aSupportedStorageBuses)
+{
+ static const StorageBus_T aStorageBuses[] =
+ {
+ StorageBus_SATA,
+ StorageBus_IDE,
+ StorageBus_SCSI,
+ StorageBus_Floppy,
+ StorageBus_SAS,
+ StorageBus_USB,
+ StorageBus_PCIe,
+ StorageBus_VirtioSCSI,
+ };
+ aSupportedStorageBuses.assign(aStorageBuses,
+ aStorageBuses + RT_ELEMENTS(aStorageBuses));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedStorageControllerTypes(std::vector<StorageControllerType_T> &aSupportedStorageControllerTypes)
+{
+ static const StorageControllerType_T aStorageControllerTypes[] =
+ {
+ StorageControllerType_IntelAhci,
+ StorageControllerType_PIIX4,
+ StorageControllerType_PIIX3,
+ StorageControllerType_ICH6,
+ StorageControllerType_LsiLogic,
+ StorageControllerType_BusLogic,
+ StorageControllerType_I82078,
+ StorageControllerType_LsiLogicSas,
+ StorageControllerType_USB,
+ StorageControllerType_NVMe,
+ StorageControllerType_VirtioSCSI,
+ };
+ aSupportedStorageControllerTypes.assign(aStorageControllerTypes,
+ aStorageControllerTypes + RT_ELEMENTS(aStorageControllerTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedChipsetTypes(std::vector<ChipsetType_T> &aSupportedChipsetTypes)
+{
+ static const ChipsetType_T aChipsetTypes[] =
+ {
+ ChipsetType_PIIX3,
+ ChipsetType_ICH9,
+ };
+ aSupportedChipsetTypes.assign(aChipsetTypes,
+ aChipsetTypes + RT_ELEMENTS(aChipsetTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedIommuTypes(std::vector<IommuType_T> &aSupportedIommuTypes)
+{
+ static const IommuType_T aIommuTypes[] =
+ {
+ IommuType_None,
+ IommuType_Automatic,
+ IommuType_AMD,
+ /** @todo Add Intel when it's supported. */
+ };
+ aSupportedIommuTypes.assign(aIommuTypes,
+ aIommuTypes + RT_ELEMENTS(aIommuTypes));
+ return S_OK;
+}
+
+HRESULT SystemProperties::getSupportedTpmTypes(std::vector<TpmType_T> &aSupportedTpmTypes)
+{
+ static const TpmType_T aTpmTypes[] =
+ {
+ TpmType_None,
+ TpmType_v1_2,
+ TpmType_v2_0
+ };
+ aSupportedTpmTypes.assign(aTpmTypes,
+ aTpmTypes + RT_ELEMENTS(aTpmTypes));
+ return S_OK;
+}
+
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT SystemProperties::i_loadSettings(const settings::SystemProperties &data)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT rc = S_OK;
+ rc = i_setDefaultMachineFolder(data.strDefaultMachineFolder);
+ if (FAILED(rc)) return rc;
+
+ rc = i_setLoggingLevel(data.strLoggingLevel);
+ if (FAILED(rc)) return rc;
+
+ rc = i_setDefaultHardDiskFormat(data.strDefaultHardDiskFormat);
+ if (FAILED(rc)) return rc;
+
+ rc = i_setVRDEAuthLibrary(data.strVRDEAuthLibrary);
+ if (FAILED(rc)) return rc;
+
+ rc = i_setWebServiceAuthLibrary(data.strWebServiceAuthLibrary);
+ if (FAILED(rc)) return rc;
+
+ rc = i_setDefaultVRDEExtPack(data.strDefaultVRDEExtPack);
+ if (FAILED(rc)) return rc;
+
+ rc = i_setDefaultCryptoExtPack(data.strDefaultCryptoExtPack);
+ if (FAILED(rc)) return rc;
+
+ m->uLogHistoryCount = data.uLogHistoryCount;
+ m->fExclusiveHwVirt = data.fExclusiveHwVirt;
+ m->uProxyMode = data.uProxyMode;
+ m->strProxyUrl = data.strProxyUrl;
+
+ m->strLanguageId = data.strLanguageId;
+
+ rc = i_setAutostartDatabasePath(data.strAutostartDatabasePath);
+ if (FAILED(rc)) return rc;
+
+ {
+ /* must ignore errors signalled here, because the guest additions
+ * file may not exist, and in this case keep the empty string */
+ ErrorInfoKeeper eik;
+ (void)i_setDefaultAdditionsISO(data.strDefaultAdditionsISO);
+ }
+
+ rc = i_setDefaultFrontend(data.strDefaultFrontend);
+ if (FAILED(rc)) return rc;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::i_saveSettings(settings::SystemProperties &data)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ data = *m;
+
+ return S_OK;
+}
+
+/**
+ * Returns a medium format object corresponding to the given format
+ * identifier or null if no such format.
+ *
+ * @param aFormat Format identifier.
+ *
+ * @return ComObjPtr<MediumFormat>
+ */
+ComObjPtr<MediumFormat> SystemProperties::i_mediumFormat(const Utf8Str &aFormat)
+{
+ ComObjPtr<MediumFormat> format;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn (autoCaller.rc(), format);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (MediumFormatList::const_iterator it = m_llMediumFormats.begin();
+ it != m_llMediumFormats.end();
+ ++ it)
+ {
+ /* MediumFormat is all const, no need to lock */
+
+ if ((*it)->i_getId().compare(aFormat, Utf8Str::CaseInsensitive) == 0)
+ {
+ format = *it;
+ break;
+ }
+ }
+
+ return format;
+}
+
+/**
+ * Returns a medium format object corresponding to the given file extension or
+ * null if no such format.
+ *
+ * @param aExt File extension.
+ *
+ * @return ComObjPtr<MediumFormat>
+ */
+ComObjPtr<MediumFormat> SystemProperties::i_mediumFormatFromExtension(const Utf8Str &aExt)
+{
+ ComObjPtr<MediumFormat> format;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn (autoCaller.rc(), format);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ bool fFound = false;
+ for (MediumFormatList::const_iterator it = m_llMediumFormats.begin();
+ it != m_llMediumFormats.end() && !fFound;
+ ++it)
+ {
+ /* MediumFormat is all const, no need to lock */
+ MediumFormat::StrArray aFileList = (*it)->i_getFileExtensions();
+ for (MediumFormat::StrArray::const_iterator it1 = aFileList.begin();
+ it1 != aFileList.end();
+ ++it1)
+ {
+ if ((*it1).compare(aExt, Utf8Str::CaseInsensitive) == 0)
+ {
+ format = *it;
+ fFound = true;
+ break;
+ }
+ }
+ }
+
+ return format;
+}
+
+
+/**
+ * VD plugin load
+ */
+int SystemProperties::i_loadVDPlugin(const char *pszPluginLibrary)
+{
+ int vrc = VDPluginLoadFromFilename(pszPluginLibrary);
+ LogFlowFunc(("pszPluginLibrary='%s' -> %Rrc\n", pszPluginLibrary, vrc));
+ return vrc;
+}
+
+/**
+ * VD plugin unload
+ */
+int SystemProperties::i_unloadVDPlugin(const char *pszPluginLibrary)
+{
+ int vrc = VDPluginUnloadFromFilename(pszPluginLibrary);
+ LogFlowFunc(("pszPluginLibrary='%s' -> %Rrc\n", pszPluginLibrary, vrc));
+ return vrc;
+}
+
+/**
+ * Internally usable version of getDefaultAdditionsISO.
+ */
+HRESULT SystemProperties::i_getDefaultAdditionsISO(com::Utf8Str &aDefaultAdditionsISO)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (m->strDefaultAdditionsISO.isNotEmpty())
+ aDefaultAdditionsISO = m->strDefaultAdditionsISO;
+ else
+ {
+ /* no guest additions, check if it showed up in the mean time */
+ alock.release();
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+ if (m->strDefaultAdditionsISO.isEmpty())
+ {
+ ErrorInfoKeeper eik;
+ (void)i_setDefaultAdditionsISO("");
+ }
+ aDefaultAdditionsISO = m->strDefaultAdditionsISO;
+ }
+ return S_OK;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns the user's home directory. Wrapper around RTPathUserHome().
+ * @param strPath
+ * @return
+ */
+HRESULT SystemProperties::i_getUserHomeDirectory(Utf8Str &strPath)
+{
+ char szHome[RTPATH_MAX];
+ int vrc = RTPathUserHome(szHome, sizeof(szHome));
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc,
+ tr("Cannot determine user home directory (%Rrc)"),
+ vrc);
+ strPath = szHome;
+ return S_OK;
+}
+
+/**
+ * Internal implementation to set the default machine folder. Gets called
+ * from the public attribute setter as well as loadSettings(). With 4.0,
+ * the "default default" machine folder has changed, and we now require
+ * a full path always.
+ * @param strPath
+ * @return
+ */
+HRESULT SystemProperties::i_setDefaultMachineFolder(const Utf8Str &strPath)
+{
+ Utf8Str path(strPath); // make modifiable
+ if ( path.isEmpty() // used by API calls to reset the default
+ || path == "Machines" // this value (exactly like this, without path) is stored
+ // in VirtualBox.xml if user upgrades from before 4.0 and
+ // has not changed the default machine folder
+ )
+ {
+ // new default with VirtualBox 4.0: "$HOME/VirtualBox VMs"
+ HRESULT rc = i_getUserHomeDirectory(path);
+ if (FAILED(rc)) return rc;
+ path += RTPATH_SLASH_STR "VirtualBox VMs";
+ }
+
+ if (!RTPathStartsWithRoot(path.c_str()))
+ return setError(E_INVALIDARG,
+ tr("Given default machine folder '%s' is not fully qualified"),
+ path.c_str());
+
+ m->strDefaultMachineFolder = path;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::i_setLoggingLevel(const com::Utf8Str &aLoggingLevel)
+{
+ Utf8Str useLoggingLevel(aLoggingLevel);
+ if (useLoggingLevel.isEmpty())
+ useLoggingLevel = VBOXSVC_LOG_DEFAULT;
+ int rc = RTLogGroupSettings(RTLogRelGetDefaultInstance(), useLoggingLevel.c_str());
+ // If failed and not the default logging level - try to use the default logging level.
+ if (RT_FAILURE(rc))
+ {
+ // If failed write message to the release log.
+ LogRel(("Cannot set passed logging level=%s Error=%Rrc \n", useLoggingLevel.c_str(), rc));
+ // If attempted logging level not the default one then try the default one.
+ if (!useLoggingLevel.equals(VBOXSVC_LOG_DEFAULT))
+ {
+ rc = RTLogGroupSettings(RTLogRelGetDefaultInstance(), VBOXSVC_LOG_DEFAULT);
+ // If failed report this to the release log.
+ if (RT_FAILURE(rc))
+ LogRel(("Cannot set default logging level Error=%Rrc \n", rc));
+ }
+ // On any failure - set default level as the one to be stored.
+ useLoggingLevel = VBOXSVC_LOG_DEFAULT;
+ }
+ // Set to passed value or if default used/attempted (even if error condition) use empty string.
+ m->strLoggingLevel = (useLoggingLevel.equals(VBOXSVC_LOG_DEFAULT) ? "" : useLoggingLevel);
+ return RT_SUCCESS(rc) ? S_OK : E_FAIL;
+}
+
+HRESULT SystemProperties::i_setDefaultHardDiskFormat(const com::Utf8Str &aFormat)
+{
+ if (!aFormat.isEmpty())
+ m->strDefaultHardDiskFormat = aFormat;
+ else
+ m->strDefaultHardDiskFormat = "VDI";
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::i_setVRDEAuthLibrary(const com::Utf8Str &aPath)
+{
+ if (!aPath.isEmpty())
+ m->strVRDEAuthLibrary = aPath;
+ else
+ m->strVRDEAuthLibrary = "VBoxAuth";
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::i_setWebServiceAuthLibrary(const com::Utf8Str &aPath)
+{
+ if (!aPath.isEmpty())
+ m->strWebServiceAuthLibrary = aPath;
+ else
+ m->strWebServiceAuthLibrary = "VBoxAuth";
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::i_setDefaultVRDEExtPack(const com::Utf8Str &aExtPack)
+{
+ m->strDefaultVRDEExtPack = aExtPack;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::i_setDefaultCryptoExtPack(const com::Utf8Str &aExtPack)
+{
+ m->strDefaultCryptoExtPack = aExtPack;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::i_setAutostartDatabasePath(const com::Utf8Str &aPath)
+{
+ HRESULT rc = S_OK;
+ AutostartDb *autostartDb = this->mParent->i_getAutostartDb();
+
+ if (!aPath.isEmpty())
+ {
+ /* Update path in the autostart database. */
+ int vrc = autostartDb->setAutostartDbPath(aPath.c_str());
+ if (RT_SUCCESS(vrc))
+ m->strAutostartDatabasePath = aPath;
+ else
+ rc = setErrorBoth(E_FAIL, vrc,
+ tr("Cannot set the autostart database path (%Rrc)"),
+ vrc);
+ }
+ else
+ {
+ int vrc = autostartDb->setAutostartDbPath(NULL);
+ if (RT_SUCCESS(vrc) || vrc == VERR_NOT_SUPPORTED)
+ m->strAutostartDatabasePath = "";
+ else
+ rc = setErrorBoth(E_FAIL, vrc,
+ tr("Deleting the autostart database path failed (%Rrc)"),
+ vrc);
+ }
+
+ return rc;
+}
+
+HRESULT SystemProperties::i_setDefaultAdditionsISO(const com::Utf8Str &aPath)
+{
+ com::Utf8Str path(aPath);
+ if (path.isEmpty())
+ {
+ char strTemp[RTPATH_MAX];
+ int vrc = RTPathAppPrivateNoArch(strTemp, sizeof(strTemp));
+ AssertRC(vrc);
+ Utf8Str strSrc1 = Utf8Str(strTemp).append("/VBoxGuestAdditions.iso");
+
+ vrc = RTPathExecDir(strTemp, sizeof(strTemp));
+ AssertRC(vrc);
+ Utf8Str strSrc2 = Utf8Str(strTemp).append("/additions/VBoxGuestAdditions.iso");
+
+ vrc = RTPathUserHome(strTemp, sizeof(strTemp));
+ AssertRC(vrc);
+ Utf8Str strSrc3 = Utf8StrFmt("%s/VBoxGuestAdditions_%s.iso", strTemp, VirtualBox::i_getVersionNormalized().c_str());
+
+ /* Check the standard image locations */
+ if (RTFileExists(strSrc1.c_str()))
+ path = strSrc1;
+ else if (RTFileExists(strSrc2.c_str()))
+ path = strSrc2;
+ else if (RTFileExists(strSrc3.c_str()))
+ path = strSrc3;
+ else
+ return setError(E_FAIL,
+ tr("Cannot determine default Guest Additions ISO location. Most likely they are not available"));
+ }
+
+ if (!RTPathStartsWithRoot(path.c_str()))
+ return setError(E_INVALIDARG,
+ tr("Given default machine Guest Additions ISO file '%s' is not fully qualified"),
+ path.c_str());
+
+ if (!RTFileExists(path.c_str()))
+ return setError(E_INVALIDARG,
+ tr("Given default machine Guest Additions ISO file '%s' does not exist"),
+ path.c_str());
+
+ m->strDefaultAdditionsISO = path;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::i_setDefaultFrontend(const com::Utf8Str &aDefaultFrontend)
+{
+ m->strDefaultFrontend = aDefaultFrontend;
+
+ return S_OK;
+}
+
+HRESULT SystemProperties::getLanguageId(com::Utf8Str &aLanguageId)
+{
+#ifdef VBOX_WITH_MAIN_NLS
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aLanguageId = m->strLanguageId;
+ alock.release();
+
+ HRESULT hrc = S_OK;
+ if (aLanguageId.isEmpty())
+ {
+ char szLocale[256];
+ memset(szLocale, 0, sizeof(szLocale));
+ int vrc = RTLocaleQueryNormalizedBaseLocaleName(szLocale, sizeof(szLocale));
+ if (RT_SUCCESS(vrc))
+ aLanguageId = szLocale;
+ else
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+ }
+ return hrc;
+#else
+ aLanguageId = "C";
+ return S_OK;
+#endif
+}
+
+HRESULT SystemProperties::setLanguageId(const com::Utf8Str &aLanguageId)
+{
+#ifdef VBOX_WITH_MAIN_NLS
+ VirtualBoxTranslator *pTranslator = VirtualBoxTranslator::instance();
+ if (!pTranslator)
+ return E_FAIL;
+
+ HRESULT hrc = S_OK;
+ int vrc = pTranslator->i_loadLanguage(aLanguageId.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->strLanguageId = aLanguageId;
+ alock.release();
+
+ // VirtualBox::i_saveSettings() needs vbox write lock
+ AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
+ hrc = mParent->i_saveSettings();
+ }
+ else
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+
+ pTranslator->release();
+
+ if (SUCCEEDED(hrc))
+ mParent->i_onLanguageChanged(aLanguageId);
+
+ return hrc;
+#else
+ NOREF(aLanguageId);
+ return E_NOTIMPL;
+#endif
+}
diff --git a/src/VBox/Main/src-server/TokenImpl.cpp b/src/VBox/Main/src-server/TokenImpl.cpp
new file mode 100644
index 00000000..6ee6ec16
--- /dev/null
+++ b/src/VBox/Main/src-server/TokenImpl.cpp
@@ -0,0 +1,227 @@
+/* $Id: TokenImpl.cpp $ */
+/** @file
+ * Token COM class implementation - MachineToken and MediumLockToken
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_TOKEN
+#include "TokenImpl.h"
+#include "MachineImpl.h"
+#include "MediumImpl.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(MachineToken)
+
+HRESULT MachineToken::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void MachineToken::FinalRelease()
+{
+ uninit(false);
+
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the token object.
+ *
+ * @param pSessionMachine Pointer to a SessionMachine object.
+ */
+HRESULT MachineToken::init(const ComObjPtr<SessionMachine> &pSessionMachine)
+{
+ LogFlowThisFunc(("pSessionMachine=%p\n", &pSessionMachine));
+
+ ComAssertRet(!pSessionMachine.isNull(), E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m.pSessionMachine = pSessionMachine;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void MachineToken::uninit(bool fAbandon)
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ /* Destroy the SessionMachine object, check is paranoia */
+ if (!m.pSessionMachine.isNull())
+ {
+ m.pSessionMachine->uninit(fAbandon ? SessionMachine::Uninit::Normal : SessionMachine::Uninit::Abnormal);
+ m.pSessionMachine.setNull();
+ }
+}
+
+// IToken methods
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT MachineToken::abandon(AutoCaller &aAutoCaller)
+{
+ /* have to release the AutoCaller before calling uninit(), self-deadlock */
+ aAutoCaller.release();
+
+ /* uninit does everything we need */
+ uninit(true);
+ return S_OK;
+}
+
+HRESULT MachineToken::dummy()
+{
+ /* Remember, the wrapper contains the AutoCaller, which means that after
+ * uninit() this code won't be reached any more. */
+
+ /* this is a NOOP, no need to lock */
+
+ return S_OK;
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(MediumLockToken)
+
+HRESULT MediumLockToken::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void MediumLockToken::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the token object.
+ *
+ * @param pMedium Pointer to a Medium object.
+ * @param fWrite True if this is a write lock, false otherwise.
+ */
+HRESULT MediumLockToken::init(const ComObjPtr<Medium> &pMedium, bool fWrite)
+{
+ LogFlowThisFunc(("pMedium=%p\n", &pMedium));
+
+ ComAssertRet(!pMedium.isNull(), E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m.pMedium = pMedium;
+ m.fWrite = fWrite;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void MediumLockToken::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ /* Release the appropriate lock, check is paranoia */
+ if (!m.pMedium.isNull())
+ {
+ if (m.fWrite)
+ {
+ HRESULT rc = m.pMedium->i_unlockWrite(NULL);
+ AssertComRC(rc);
+ }
+ else
+ {
+ HRESULT rc = m.pMedium->i_unlockRead(NULL);
+ AssertComRC(rc);
+ }
+ m.pMedium.setNull();
+ }
+}
+
+// IToken methods
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT MediumLockToken::abandon(AutoCaller &aAutoCaller)
+{
+ /* have to release the AutoCaller before calling uninit(), self-deadlock */
+ aAutoCaller.release();
+
+ /* uninit does everything we need */
+ uninit();
+ return S_OK;
+}
+
+HRESULT MediumLockToken::dummy()
+{
+ /* Remember, the wrapper contains the AutoCaller, which means that after
+ * uninit() this code won't be reached any more. */
+
+ /* this is a NOOP, no need to lock */
+
+ return S_OK;
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/TrustedPlatformModuleImpl.cpp b/src/VBox/Main/src-server/TrustedPlatformModuleImpl.cpp
new file mode 100644
index 00000000..a6b849ad
--- /dev/null
+++ b/src/VBox/Main/src-server/TrustedPlatformModuleImpl.cpp
@@ -0,0 +1,367 @@
+/* $Id: TrustedPlatformModuleImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation - Machine Trusted Platform Module settings.
+ */
+
+/*
+ * Copyright (C) 2021-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_TRUSTEDPLATFORMMODULE
+#include "TrustedPlatformModuleImpl.h"
+#include "MachineImpl.h"
+#include "GuestOSTypeImpl.h"
+
+#include <iprt/cpp/utils.h>
+#include <VBox/settings.h>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// TrustedPlatformModule private data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct TrustedPlatformModule::Data
+{
+ Data()
+ : pMachine(NULL)
+ { }
+
+ Machine * const pMachine;
+ ComObjPtr<TrustedPlatformModule> pPeer;
+
+ // use the XML settings structure in the members for simplicity
+ Backupable<settings::TpmSettings> bd;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(TrustedPlatformModule)
+
+HRESULT TrustedPlatformModule::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void TrustedPlatformModule::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the BIOS settings object.
+ *
+ * @returns COM result indicator
+ */
+HRESULT TrustedPlatformModule::init(Machine *aParent)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ /* share the parent weakly */
+ unconst(m->pMachine) = aParent;
+
+ m->bd.allocate();
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Initializes the Trusted Platform Module settings object given another Trusted Platform Module settings object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ */
+HRESULT TrustedPlatformModule::init(Machine *aParent, TrustedPlatformModule *that)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p, that: %p\n", aParent, that));
+
+ ComAssertRet(aParent && that, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ m->pPeer = that;
+
+ AutoWriteLock thatlock(that COMMA_LOCKVAL_SRC_POS);
+ m->bd.share(that->m->bd);
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Initializes the guest object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ */
+HRESULT TrustedPlatformModule::initCopy(Machine *aParent, TrustedPlatformModule *that)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p, that: %p\n", aParent, that));
+
+ ComAssertRet(aParent && that, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ unconst(m->pMachine) = aParent;
+ // mPeer is left null
+
+ AutoWriteLock thatlock(that COMMA_LOCKVAL_SRC_POS);
+ m->bd.attachCopy(that->m->bd);
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void TrustedPlatformModule::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m->bd.free();
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pMachine) = NULL;
+
+ delete m;
+ m = NULL;
+
+ LogFlowThisFuncLeave();
+}
+
+// ITrustedPlatformModule properties
+/////////////////////////////////////////////////////////////////////////////
+
+
+HRESULT TrustedPlatformModule::getType(TpmType_T *aType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aType = m->bd->tpmType;
+
+ return S_OK;
+}
+
+HRESULT TrustedPlatformModule::setType(TpmType_T aType)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->tpmType = aType;
+
+ alock.release();
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_TrustedPlatformModule);
+
+ return S_OK;
+}
+
+HRESULT TrustedPlatformModule::getLocation(com::Utf8Str &location)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ location = m->bd->strLocation;
+ return S_OK;
+}
+
+HRESULT TrustedPlatformModule::setLocation(const com::Utf8Str &location)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->strLocation = location;
+
+ alock.release();
+ AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ m->pMachine->i_setModified(Machine::IsModified_TrustedPlatformModule);
+
+ return S_OK;
+}
+
+
+// ITrustedPlatformModule methods
+/////////////////////////////////////////////////////////////////////////////
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Loads settings from the given machine node.
+ * May be called once right after this object creation.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT TrustedPlatformModule::i_loadSettings(const settings::TpmSettings &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ // simply copy
+ m->bd.assignCopy(&data);
+ return S_OK;
+}
+
+/**
+ * Saves settings to the given machine node.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT TrustedPlatformModule::i_saveSettings(settings::TpmSettings &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ data = *m->bd.data();
+
+ return S_OK;
+}
+
+void TrustedPlatformModule::i_rollback()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->bd.rollback();
+}
+
+void TrustedPlatformModule::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ {
+ m->bd.commit();
+ if (m->pPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ AutoWriteLock peerlock(m->pPeer COMMA_LOCKVAL_SRC_POS);
+ m->pPeer->m->bd.attach(m->bd);
+ }
+ }
+}
+
+void TrustedPlatformModule::i_copyFrom(TrustedPlatformModule *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ m->bd.assignCopy(aThat->m->bd);
+}
+
+void TrustedPlatformModule::i_applyDefaults(GuestOSType *aOsType)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Initialize default TPM settings here */
+ if (aOsType)
+ m->bd->tpmType = aOsType->i_recommendedTpm2() ? TpmType_v2_0 : TpmType_None;
+ else
+ m->bd->tpmType = TpmType_None;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/USBControllerImpl.cpp b/src/VBox/Main/src-server/USBControllerImpl.cpp
new file mode 100644
index 00000000..97a60b98
--- /dev/null
+++ b/src/VBox/Main/src-server/USBControllerImpl.cpp
@@ -0,0 +1,459 @@
+/* $Id: USBControllerImpl.cpp $ */
+/** @file
+ * Implementation of IUSBController.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_USBCONTROLLER
+#include "USBControllerImpl.h"
+
+#include "Global.h"
+#include "MachineImpl.h"
+#include "VirtualBoxImpl.h"
+#include "HostImpl.h"
+
+#include <iprt/string.h>
+#include <iprt/cpp/utils.h>
+
+#include <iprt/errcore.h>
+#include <VBox/settings.h>
+#include <VBox/com/array.h>
+
+#include <algorithm>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+// defines
+/////////////////////////////////////////////////////////////////////////////
+
+struct USBController::Data
+{
+ Data(Machine *pMachine)
+ : pParent(pMachine)
+ { }
+
+ ~Data()
+ {};
+
+ Machine * const pParent;
+
+ // peer machine's USB controller
+ const ComObjPtr<USBController> pPeer;
+
+ Backupable<settings::USBController> bd;
+};
+
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(USBController)
+
+HRESULT USBController::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void USBController::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the USB controller object.
+ *
+ * @returns COM result indicator.
+ * @param aParent Pointer to our parent object.
+ * @param aName The name of the USB controller.
+ * @param enmType The USB controller type.
+ */
+HRESULT USBController::init(Machine *aParent, const Utf8Str &aName, USBControllerType_T enmType)
+{
+ LogFlowThisFunc(("aParent=%p aName=\"%s\"\n", aParent, aName.c_str()));
+
+ ComAssertRet(aParent && !aName.isEmpty(), E_INVALIDARG);
+ if ( (enmType <= USBControllerType_Null)
+ || (enmType > USBControllerType_XHCI))
+ return setError(E_INVALIDARG,
+ tr("Invalid USB controller type"));
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+
+ /* mPeer is left null */
+
+ m->bd.allocate();
+ m->bd->strName = aName;
+ m->bd->enmType = enmType;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the USB controller object given another USB controller object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @returns COM result indicator.
+ * @param aParent Pointer to our parent object.
+ * @param aPeer The object to share.
+ * @param fReshare
+ * When false, the original object will remain a data owner.
+ * Otherwise, data ownership will be transferred from the original
+ * object to this one.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for writing if @a aReshare is @c true, or for
+ * reading if @a aReshare is false.
+ */
+HRESULT USBController::init(Machine *aParent, USBController *aPeer,
+ bool fReshare /* = false */)
+{
+ LogFlowThisFunc(("aParent=%p, aPeer=%p, fReshare=%RTbool\n",
+ aParent, aPeer, fReshare));
+
+ ComAssertRet(aParent && aPeer, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+
+ /* sanity */
+ AutoCaller peerCaller(aPeer);
+ AssertComRCReturnRC(peerCaller.rc());
+
+ if (fReshare)
+ {
+ AutoWriteLock peerLock(aPeer COMMA_LOCKVAL_SRC_POS);
+
+ unconst(aPeer->m->pPeer) = this;
+ m->bd.attach(aPeer->m->bd);
+ }
+ else
+ {
+ unconst(m->pPeer) = aPeer;
+
+ AutoReadLock peerLock(aPeer COMMA_LOCKVAL_SRC_POS);
+ m->bd.share(aPeer->m->bd);
+ }
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+
+/**
+ * Initializes the USB controller object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ */
+HRESULT USBController::initCopy(Machine *aParent, USBController *aPeer)
+{
+ LogFlowThisFunc(("aParent=%p, aPeer=%p\n", aParent, aPeer));
+
+ ComAssertRet(aParent && aPeer, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+
+ /* mPeer is left null */
+
+ AutoWriteLock thatlock(aPeer COMMA_LOCKVAL_SRC_POS);
+ m->bd.attachCopy(aPeer->m->bd);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void USBController::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m->bd.free();
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pParent) = NULL;
+
+ delete m;
+ m = NULL;
+}
+
+
+// Wrapped IUSBController properties
+/////////////////////////////////////////////////////////////////////////////
+HRESULT USBController::getName(com::Utf8Str &aName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aName = m->bd->strName;
+
+ return S_OK;
+}
+
+HRESULT USBController::setName(const com::Utf8Str &aName)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoMultiWriteLock2 alock(m->pParent, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->strName != aName)
+ {
+ ComObjPtr<USBController> ctrl;
+ HRESULT rc = m->pParent->i_getUSBControllerByName(aName, ctrl, false /* aSetError */);
+ if (SUCCEEDED(rc))
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("USB controller named '%s' already exists"),
+ aName.c_str());
+
+ m->bd.backup();
+ m->bd->strName = aName;
+
+ m->pParent->i_setModified(Machine::IsModified_USB);
+ alock.release();
+
+ m->pParent->i_onUSBControllerChange();
+ }
+
+ return S_OK;
+}
+
+HRESULT USBController::getType(USBControllerType_T *aType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aType = m->bd->enmType;
+
+ return S_OK;
+}
+
+HRESULT USBController::setType(USBControllerType_T aType)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoMultiWriteLock2 alock(m->pParent, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd->enmType != aType)
+ {
+ m->bd.backup();
+ m->bd->enmType = aType;
+
+ m->pParent->i_setModified(Machine::IsModified_USB);
+ alock.release();
+
+ m->pParent->i_onUSBControllerChange();
+ }
+
+ return S_OK;
+}
+
+HRESULT USBController::getUSBStandard(USHORT *aUSBStandard)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ switch (m->bd->enmType)
+ {
+ case USBControllerType_OHCI:
+ *aUSBStandard = 0x0101;
+ break;
+ case USBControllerType_EHCI:
+ *aUSBStandard = 0x0200;
+ break;
+ case USBControllerType_XHCI:
+ *aUSBStandard = 0x0200;
+ break;
+ default:
+ AssertMsgFailedReturn(("Invalid controller type %d\n", m->bd->enmType),
+ E_FAIL);
+ }
+
+ return S_OK;
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/** @note Locks objects for writing! */
+void USBController::i_rollback()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* we need the machine state */
+ AutoAnyStateDependency adep(m->pParent);
+ AssertComRCReturnVoid(adep.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.rollback();
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object (also
+ * for writing) if there is one.
+ */
+void USBController::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ {
+ m->bd.commit();
+ if (m->pPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ AutoWriteLock peerlock(m->pPeer COMMA_LOCKVAL_SRC_POS);
+ m->pPeer->m->bd.attach(m->bd);
+ }
+ }
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ */
+void USBController::i_copyFrom(USBController *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* even more sanity */
+ AutoAnyStateDependency adep(m->pParent);
+ AssertComRCReturnVoid(adep.rc());
+ /* Machine::copyFrom() may not be called when the VM is running */
+ AssertReturnVoid(!Global::IsOnline(adep.machineState()));
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ m->bd.assignCopy(aThat->m->bd);
+}
+
+/**
+ * Cancels sharing (if any) by making an independent copy of data.
+ * This operation also resets this object's peer to NULL.
+ *
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ */
+void USBController::i_unshare()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* peer is not modified, lock it for reading (m->pPeer is "master" so locked
+ * first) */
+ AutoReadLock rl(m->pPeer COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isShared())
+ {
+ if (!m->bd.isBackedUp())
+ m->bd.backup();
+
+ m->bd.commit();
+ }
+
+ unconst(m->pPeer) = NULL;
+}
+
+const Utf8Str &USBController::i_getName() const
+{
+ return m->bd->strName;
+}
+
+const USBControllerType_T &USBController::i_getControllerType() const
+{
+ return m->bd->enmType;
+}
+
+ComObjPtr<USBController> USBController::i_getPeer()
+{
+ return m->pPeer;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/USBDeviceFilterImpl.cpp b/src/VBox/Main/src-server/USBDeviceFilterImpl.cpp
new file mode 100644
index 00000000..8c960717
--- /dev/null
+++ b/src/VBox/Main/src-server/USBDeviceFilterImpl.cpp
@@ -0,0 +1,1286 @@
+/* $Id: USBDeviceFilterImpl.cpp $ */
+/** @file
+ * Implementation of VirtualBox COM components: USBDeviceFilter and HostUSBDeviceFilter
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_USBDEVICEFILTER
+#include "USBDeviceFilterImpl.h"
+#include "USBDeviceFiltersImpl.h"
+#include "MachineImpl.h"
+#include "HostImpl.h"
+
+#include <iprt/cpp/utils.h>
+#include <VBox/settings.h>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// Internal Helpers
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Converts a USBFilter field into a string.
+ *
+ * (This function is also used by HostUSBDeviceFilter.)
+ *
+ * @param aFilter The filter.
+ * @param aIdx The field index.
+ * @param rstrOut The output string.
+ */
+static void i_usbFilterFieldToString(PCUSBFILTER aFilter, USBFILTERIDX aIdx, Utf8Str &rstrOut)
+{
+ const USBFILTERMATCH matchingMethod = USBFilterGetMatchingMethod(aFilter, aIdx);
+ Assert(matchingMethod != USBFILTERMATCH_INVALID);
+
+ if (USBFilterIsMethodNumeric(matchingMethod))
+ {
+ int value = USBFilterGetNum(aFilter, aIdx);
+ Assert(value >= 0 && value <= 0xffff);
+
+ rstrOut.printf("%04RX16", (uint16_t)value);
+ }
+ else if (USBFilterIsMethodString(matchingMethod))
+ rstrOut = USBFilterGetString(aFilter, aIdx);
+ else
+ rstrOut.setNull();
+}
+
+/*static*/
+const char* USBDeviceFilter::i_describeUSBFilterIdx(USBFILTERIDX aIdx)
+{
+ switch (aIdx)
+ {
+ case USBFILTERIDX_VENDOR_ID: return tr("Vendor ID");
+ case USBFILTERIDX_PRODUCT_ID: return tr("Product ID");
+ case USBFILTERIDX_DEVICE: return tr("Revision");
+ case USBFILTERIDX_MANUFACTURER_STR: return tr("Manufacturer");
+ case USBFILTERIDX_PRODUCT_STR: return tr("Product");
+ case USBFILTERIDX_SERIAL_NUMBER_STR: return tr("Serial number");
+ case USBFILTERIDX_PORT: return tr("Port number");
+ default: return "";
+ }
+ /* not reached. */
+}
+
+/**
+ * Interprets a string and assigns it to a USBFilter field.
+ *
+ * (This function is also used by HostUSBDeviceFilter.)
+ *
+ * @param aFilter The filter.
+ * @param aIdx The field index.
+ * @param aValue The input string.
+ * @param aErrStr Where to return the error string on failure.
+ *
+ * @return COM status code.
+ * @remark The idea was to have this as a static function, but tr() doesn't wanna work without a class :-/
+ */
+/*static*/ HRESULT USBDeviceFilter::i_usbFilterFieldFromString(PUSBFILTER aFilter,
+ USBFILTERIDX aIdx,
+ const Utf8Str &aValue,
+ Utf8Str &aErrStr)
+{
+ int vrc;
+ if (aValue.isEmpty())
+ vrc = USBFilterSetIgnore(aFilter, aIdx);
+ else
+ {
+ const char *pcszValue = aValue.c_str();
+ if (USBFilterIsNumericField(aIdx))
+ {
+ /* Is it a lonely number? */
+ char *pszNext;
+ uint64_t u64;
+ vrc = RTStrToUInt64Ex(pcszValue, &pszNext, 16, &u64);
+ if (RT_SUCCESS(vrc))
+ pszNext = RTStrStripL(pszNext);
+ if ( vrc == VINF_SUCCESS
+ && !*pszNext)
+ {
+ if (u64 > 0xffff)
+ {
+ // there was a bug writing out "-1" values in earlier versions, which got
+ // written as "FFFFFFFF"; make sure we don't fail on those
+ if (u64 == 0xffffffff)
+ u64 = 0xffff;
+ else
+ {
+ aErrStr.printf(tr("The %s value '%s' is too big (max 0xFFFF)"), i_describeUSBFilterIdx(aIdx), pcszValue);
+ return E_INVALIDARG;
+ }
+ }
+
+ vrc = USBFilterSetNumExact(aFilter, aIdx, (uint16_t)u64, true /* fMustBePresent */);
+ }
+ else
+ vrc = USBFilterSetNumExpression(aFilter, aIdx, pcszValue, true /* fMustBePresent */);
+ }
+ else
+ {
+ /* Any wildcard in the string? */
+ Assert(USBFilterIsStringField(aIdx));
+ if ( strchr(pcszValue, '*')
+ || strchr(pcszValue, '?')
+ /* || strchr (psz, '[') - later */
+ )
+ vrc = USBFilterSetStringPattern(aFilter, aIdx, pcszValue, true /*fMustBePresent*/);
+ else
+ vrc = USBFilterSetStringExact(aFilter, aIdx, pcszValue, true /*fMustBePresent*/, false /*fPurge*/);
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_INVALID_PARAMETER)
+ {
+ aErrStr.printf(tr("The %s filter expression '%s' is not valid"), i_describeUSBFilterIdx(aIdx), aValue.c_str());
+ return E_INVALIDARG;
+ }
+ if (vrc == VERR_BUFFER_OVERFLOW)
+ {
+ aErrStr.printf(tr("Insufficient expression space for the '%s' filter expression '%s'"),
+ i_describeUSBFilterIdx(aIdx), aValue.c_str());
+ return E_FAIL;
+ }
+ AssertRC(vrc);
+ aErrStr.printf(tr("Encountered unexpected status %Rrc when setting '%s' to '%s'"),
+ vrc, i_describeUSBFilterIdx(aIdx), aValue.c_str());
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// USBDeviceFilter
+////////////////////////////////////////////////////////////////////////////////
+
+// constructor / destructor
+////////////////////////////////////////////////////////////////////////////////
+
+USBDeviceFilter::USBDeviceFilter()
+ : mParent(NULL),
+ mPeer(NULL)
+{
+}
+
+USBDeviceFilter::~USBDeviceFilter()
+{
+}
+
+HRESULT USBDeviceFilter::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void USBDeviceFilter::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the USB device filter object.
+ *
+ * @param aParent Handle of the parent object.
+ * @param data Reference filter settings.
+ */
+HRESULT USBDeviceFilter::init(USBDeviceFilters *aParent,
+ const settings::USBDeviceFilter &data)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent && !data.strName.isEmpty(), E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+ /* mPeer is left null */
+
+ m_fModified = false;
+
+ bd.allocate();
+ bd->mData.strName = data.strName;
+ bd->mData.fActive = data.fActive;
+ bd->mData.ulMaskedInterfaces = 0;
+
+ /* initialize all filters to any match using null string */
+ USBFilterInit(&bd->mUSBFilter, USBFILTERTYPE_CAPTURE);
+ bd->mRemote = NULL;
+
+ mInList = false;
+
+ /* use setters for the attributes below to reuse parsing errors
+ * handling */
+
+ HRESULT rc = S_OK;
+ do
+ {
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_VENDOR_ID, data.strVendorId);
+ if (FAILED(rc)) break;
+
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_PRODUCT_ID, data.strProductId);
+ if (FAILED(rc)) break;
+
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_DEVICE, data.strRevision);
+ if (FAILED(rc)) break;
+
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_MANUFACTURER_STR, data.strManufacturer);
+ if (FAILED(rc)) break;
+
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_PRODUCT_STR, data.strProduct);
+ if (FAILED(rc)) break;
+
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_SERIAL_NUMBER_STR, data.strSerialNumber);
+ if (FAILED(rc)) break;
+
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_PORT, data.strPort);
+ if (FAILED(rc)) break;
+
+ rc = COMSETTER(Remote)(Bstr(data.strRemote).raw());
+ if (FAILED(rc)) break;
+
+ rc = COMSETTER(MaskedInterfaces)(data.ulMaskedInterfaces);
+ if (FAILED(rc)) break;
+ }
+ while (0);
+
+ /* Confirm successful initialization when it's the case */
+ if (SUCCEEDED(rc))
+ autoInitSpan.setSucceeded();
+
+ return rc;
+}
+
+/**
+ * Initializes the USB device filter object (short version).
+ *
+ * @param aParent Handle of the parent object.
+ * @param aName Name of the filter.
+ */
+HRESULT USBDeviceFilter::init(USBDeviceFilters *aParent, IN_BSTR aName)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent && aName && *aName, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+ /* mPeer is left null */
+
+ m_fModified = false;
+
+ bd.allocate();
+
+ bd->mData.strName = Utf8Str(aName);
+ bd->mData.fActive = FALSE;
+ bd->mData.ulMaskedInterfaces = 0;
+
+ /* initialize all filters to any match using null string */
+ USBFilterInit(&bd->mUSBFilter, USBFILTERTYPE_CAPTURE);
+ bd->mRemote = NULL;
+
+ mInList = false;
+
+ /* Confirm successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the object given another object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @param aParent Handle of the parent object.
+ * @param aThat
+ * @param aReshare
+ * When false, the original object will remain a data owner.
+ * Otherwise, data ownership will be transferred from the original
+ * object to this one.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for writing if @a aReshare is @c true, or for
+ * reading if @a aReshare is false.
+ */
+HRESULT USBDeviceFilter::init(USBDeviceFilters *aParent, USBDeviceFilter *aThat,
+ bool aReshare /* = false */)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p, aReshare=%RTbool\n",
+ aParent, aThat, aReshare));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ m_fModified = false;
+
+ /* sanity */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ if (aReshare)
+ {
+ AutoWriteLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+
+ unconst(aThat->mPeer) = this;
+ bd.attach(aThat->bd);
+ }
+ else
+ {
+ unconst(mPeer) = aThat;
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ bd.share(aThat->bd);
+ }
+
+ /* the arbitrary ID field is not reset because
+ * the copy is a shadow of the original */
+
+ mInList = aThat->mInList;
+
+ /* Confirm successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the guest object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT USBDeviceFilter::initCopy(USBDeviceFilters *aParent, USBDeviceFilter *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+ /* mPeer is left null */
+
+ m_fModified = false;
+
+ /* sanity */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ bd.attachCopy(aThat->bd);
+
+ /* reset the arbitrary ID field
+ * (this field is something unique that two distinct objects, even if they
+ * are deep copies of each other, should not share) */
+ bd->mId = NULL;
+
+ mInList = aThat->mInList;
+
+ /* Confirm successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void USBDeviceFilter::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ mInList = false;
+
+ bd.free();
+
+ unconst(mPeer) = NULL;
+ unconst(mParent) = NULL;
+}
+
+
+// IUSBDeviceFilter properties
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT USBDeviceFilter::getName(com::Utf8Str &aName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return aName.assignEx(bd->mData.strName);
+}
+
+HRESULT USBDeviceFilter::setName(const com::Utf8Str &aName)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent->i_getMachine());
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (bd->mData.strName != aName)
+ {
+ m_fModified = true;
+ ComObjPtr<Machine> pMachine = mParent->i_getMachine();
+
+ bd.backup();
+ bd->mData.strName = aName;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(pMachine COMMA_LOCKVAL_SRC_POS);
+ pMachine->i_setModified(Machine::IsModified_USB);
+ mlock.release();
+
+ return mParent->i_onDeviceFilterChange(this);
+ }
+
+ return S_OK;
+}
+
+HRESULT USBDeviceFilter::getActive(BOOL *aActive)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aActive = bd->mData.fActive;
+
+ return S_OK;
+}
+
+HRESULT USBDeviceFilter::setActive(const BOOL aActive)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent->i_getMachine());
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (bd->mData.fActive != RT_BOOL(aActive))
+ {
+ m_fModified = true;
+ ComObjPtr<Machine> pMachine = mParent->i_getMachine();
+
+ bd.backup();
+ bd->mData.fActive = RT_BOOL(aActive);
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(pMachine COMMA_LOCKVAL_SRC_POS);
+ pMachine->i_setModified(Machine::IsModified_USB);
+ mlock.release();
+
+ return mParent->i_onDeviceFilterChange(this, TRUE /* aActiveChanged */);
+ }
+
+ return S_OK;
+}
+
+HRESULT USBDeviceFilter::getVendorId(com::Utf8Str &aVendorId)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_VENDOR_ID, aVendorId);
+}
+
+HRESULT USBDeviceFilter::setVendorId(const com::Utf8Str &aVendorId)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_VENDOR_ID, aVendorId);
+}
+
+HRESULT USBDeviceFilter::getProductId(com::Utf8Str &aProductId)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_PRODUCT_ID, aProductId);
+}
+
+HRESULT USBDeviceFilter::setProductId(const com::Utf8Str &aProductId)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_PRODUCT_ID, aProductId);
+}
+
+HRESULT USBDeviceFilter::getRevision(com::Utf8Str &aRevision)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_DEVICE, aRevision);
+}
+
+HRESULT USBDeviceFilter::setRevision(const com::Utf8Str &aRevision)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_DEVICE, aRevision);
+}
+
+HRESULT USBDeviceFilter::getManufacturer(com::Utf8Str &aManufacturer)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_MANUFACTURER_STR, aManufacturer);
+}
+
+HRESULT USBDeviceFilter::setManufacturer(const com::Utf8Str &aManufacturer)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_MANUFACTURER_STR, aManufacturer);
+}
+
+HRESULT USBDeviceFilter::getProduct(com::Utf8Str &aProduct)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_PRODUCT_STR, aProduct);
+}
+
+HRESULT USBDeviceFilter::setProduct(const com::Utf8Str &aProduct)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_PRODUCT_STR, aProduct);
+}
+
+HRESULT USBDeviceFilter::getSerialNumber(com::Utf8Str &aSerialNumber)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_SERIAL_NUMBER_STR, aSerialNumber);
+}
+
+HRESULT USBDeviceFilter::setSerialNumber(const com::Utf8Str &aSerialNumber)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_SERIAL_NUMBER_STR, aSerialNumber);
+}
+
+HRESULT USBDeviceFilter::getPort(com::Utf8Str &aPort)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_PORT, aPort);
+}
+
+HRESULT USBDeviceFilter::setPort(const com::Utf8Str &aPort)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_PORT, aPort);
+}
+
+
+HRESULT USBDeviceFilter::getRemote(com::Utf8Str &aRemote)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aRemote = bd->mRemote.string();
+
+ return S_OK;
+}
+
+HRESULT USBDeviceFilter::setRemote(const com::Utf8Str &aRemote)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent->i_getMachine());
+ if (FAILED(adep.rc())) return adep.rc();
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Bstr bRemote = Bstr(aRemote).raw();
+
+ if (bd->mRemote.string() != bRemote)
+ {
+ BackupableUSBDeviceFilterData::BOOLFilter flt = bRemote;
+ ComAssertRet(!flt.isNull(), E_FAIL);
+ if (!flt.isValid())
+ return setError(E_INVALIDARG,
+ tr("Remote state filter string '%s' is not valid (error at position %d)"),
+ aRemote.c_str(), flt.errorPosition() + 1);
+
+ m_fModified = true;
+ ComObjPtr<Machine> pMachine = mParent->i_getMachine();
+
+ bd.backup();
+ bd->mRemote = flt;
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(pMachine COMMA_LOCKVAL_SRC_POS);
+ pMachine->i_setModified(Machine::IsModified_USB);
+ mlock.release();
+
+ return mParent->i_onDeviceFilterChange(this);
+ }
+ return S_OK;
+}
+
+
+HRESULT USBDeviceFilter::getMaskedInterfaces(ULONG *aMaskedIfs)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aMaskedIfs = bd->mData.ulMaskedInterfaces;
+
+ return S_OK;
+}
+
+HRESULT USBDeviceFilter::setMaskedInterfaces(ULONG aMaskedIfs)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent->i_getMachine());
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (bd->mData.ulMaskedInterfaces != aMaskedIfs)
+ {
+ m_fModified = true;
+ ComObjPtr<Machine> pMachine = mParent->i_getMachine();
+
+ bd.backup();
+ bd->mData.ulMaskedInterfaces = aMaskedIfs;
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(pMachine COMMA_LOCKVAL_SRC_POS);
+ pMachine->i_setModified(Machine::IsModified_USB);
+ mlock.release();
+
+ return mParent->i_onDeviceFilterChange(this);
+ }
+
+ return S_OK;
+}
+
+// public methods only for internal purposes
+////////////////////////////////////////////////////////////////////////////////
+
+bool USBDeviceFilter::i_isModified()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), false);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return m_fModified;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+void USBDeviceFilter::i_rollback()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ bd.rollback();
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object (also
+ * for writing) if there is one.
+ */
+void USBDeviceFilter::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(mPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(mPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (bd.isBackedUp())
+ {
+ bd.commit();
+ if (mPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ mPeer->bd.attach(bd);
+ }
+ }
+}
+
+/**
+ * Cancels sharing (if any) by making an independent copy of data.
+ * This operation also resets this object's peer to NULL.
+ *
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ */
+void USBDeviceFilter::unshare()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(mPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* peer is not modified, lock it for reading (mPeer is "master" so locked
+ * first) */
+ AutoReadLock rl(mPeer COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ if (bd.isShared())
+ {
+ if (!bd.isBackedUp())
+ bd.backup();
+
+ bd.commit();
+ }
+
+ unconst(mPeer) = NULL;
+}
+
+/**
+ * Generic USB filter field getter; converts the field value to UTF-16.
+ *
+ * @param aIdx The field index.
+ * @param aStr Where to store the value.
+ *
+ * @return COM status.
+ */
+HRESULT USBDeviceFilter::i_usbFilterFieldGetter(USBFILTERIDX aIdx, com::Utf8Str &aStr)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ i_usbFilterFieldToString(&bd->mUSBFilter, aIdx, aStr);
+ return S_OK;
+}
+
+/**
+ * Generic USB filter field setter, expects UTF-8 input.
+ *
+ * @param aIdx The field index.
+ * @param strNew The new value.
+ *
+ * @return COM status.
+ */
+HRESULT USBDeviceFilter::i_usbFilterFieldSetter(USBFILTERIDX aIdx,
+ const com::Utf8Str &strNew)
+{
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent->i_getMachine());
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+
+ com::Utf8Str strOld;
+ i_usbFilterFieldToString(&bd->mUSBFilter, aIdx, strOld);
+ if (strOld != strNew)
+ {
+ m_fModified = true;
+ ComObjPtr<Machine> pMachine = mParent->i_getMachine();
+
+ bd.backup();
+
+ com::Utf8Str errStr;
+ HRESULT rc = i_usbFilterFieldFromString(&bd->mUSBFilter, aIdx, strNew, errStr);
+ if (FAILED(rc))
+ {
+ bd.rollback();
+ return setError(rc, "%s", errStr.c_str());
+ }
+
+ // leave the lock before informing callbacks
+ alock.release();
+
+ AutoWriteLock mlock(pMachine COMMA_LOCKVAL_SRC_POS);
+ pMachine->i_setModified(Machine::IsModified_USB);
+ mlock.release();
+
+ return mParent->i_onDeviceFilterChange(this);
+ }
+
+ return S_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HostUSBDeviceFilter
+////////////////////////////////////////////////////////////////////////////////
+
+// constructor / destructor
+////////////////////////////////////////////////////////////////////////////////
+
+HostUSBDeviceFilter::HostUSBDeviceFilter()
+ : mParent(NULL)
+{
+}
+
+HostUSBDeviceFilter::~HostUSBDeviceFilter()
+{
+}
+
+
+HRESULT HostUSBDeviceFilter::FinalConstruct()
+{
+ return S_OK;
+}
+
+void HostUSBDeviceFilter::FinalRelease()
+{
+ uninit();
+}
+
+// public initializer/uninitializer for internal purposes only
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the USB device filter object.
+ *
+ * @param aParent Handle of the parent object.
+ * @param data Settings data.
+ */
+HRESULT HostUSBDeviceFilter::init(Host *aParent,
+ const settings::USBDeviceFilter &data)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent && !data.strName.isEmpty(), E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ /* register with parent early, since uninit() will unconditionally
+ * unregister on failure */
+ mParent->i_addChild(this);
+
+ bd.allocate();
+ bd->mData.strName = data.strName;
+ bd->mData.fActive = data.fActive;
+ USBFilterInit (&bd->mUSBFilter, USBFILTERTYPE_IGNORE);
+ bd->mRemote = NULL;
+ bd->mData.ulMaskedInterfaces = 0;
+
+ mInList = false;
+
+ /* use setters for the attributes below to reuse parsing errors
+ * handling */
+
+ HRESULT rc = S_OK;
+ do
+ {
+ rc = setAction(data.action);
+ if (FAILED(rc)) break;
+
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_VENDOR_ID, data.strVendorId);
+ if (FAILED(rc)) break;
+
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_PRODUCT_ID, data.strProductId);
+ if (FAILED(rc)) break;
+
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_DEVICE, data.strRevision);
+ if (FAILED(rc)) break;
+
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_MANUFACTURER_STR, data.strManufacturer);
+ if (FAILED(rc)) break;
+
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_PRODUCT_ID, data.strProduct);
+ if (FAILED(rc)) break;
+
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_SERIAL_NUMBER_STR, data.strSerialNumber);
+ if (FAILED(rc)) break;
+
+ rc = i_usbFilterFieldSetter(USBFILTERIDX_PORT, data.strPort);
+ if (FAILED(rc)) break;
+ }
+ while (0);
+
+ /* Confirm successful initialization when it's the case */
+ if (SUCCEEDED(rc))
+ autoInitSpan.setSucceeded();
+
+ return rc;
+}
+
+/**
+ * Initializes the USB device filter object (short version).
+ *
+ * @param aParent Handle of the parent object.
+ * @param aName Filter name.
+ */
+HRESULT HostUSBDeviceFilter::init(Host *aParent, IN_BSTR aName)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent && aName && *aName, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ /* register with parent early, since uninit() will unconditionally
+ * unregister on failure */
+ mParent->i_addChild(this);
+
+ bd.allocate();
+
+ bd->mData.strName = Utf8Str(aName);
+ bd->mData.fActive = FALSE;
+ mInList = false;
+ USBFilterInit(&bd->mUSBFilter, USBFILTERTYPE_IGNORE);
+ bd->mRemote = NULL;
+ bd->mData.ulMaskedInterfaces = 0;
+
+ /* Confirm successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void HostUSBDeviceFilter::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ mInList = false;
+
+ bd.free();
+
+ mParent->i_removeChild(this);
+
+ unconst(mParent) = NULL;
+}
+
+/**
+ * Most of the USB bits are protect by one lock to simplify things.
+ * This lock is currently the one of the Host object, which happens
+ * to be our parent.
+ */
+RWLockHandle *HostUSBDeviceFilter::lockHandle() const
+{
+ return mParent->lockHandle();
+}
+
+
+// IUSBDeviceFilter properties
+////////////////////////////////////////////////////////////////////////////////
+HRESULT HostUSBDeviceFilter::getName(com::Utf8Str &aName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aName = bd->mData.strName;
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDeviceFilter::setName(const com::Utf8Str &aName)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (bd->mData.strName != aName)
+ {
+ bd->mData.strName = aName;
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ return mParent->i_onUSBDeviceFilterChange(this);
+ }
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDeviceFilter::getActive(BOOL *aActive)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aActive = bd->mData.fActive;
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDeviceFilter::setActive(BOOL aActive)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (bd->mData.fActive != RT_BOOL(aActive))
+ {
+ bd->mData.fActive = RT_BOOL(aActive);
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ return mParent->i_onUSBDeviceFilterChange(this, TRUE /* aActiveChanged */);
+ }
+
+ return S_OK;
+}
+
+HRESULT HostUSBDeviceFilter::getVendorId(com::Utf8Str &aVendorId)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_VENDOR_ID, aVendorId);
+}
+
+HRESULT HostUSBDeviceFilter::setVendorId(const com::Utf8Str &aVendorId)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_VENDOR_ID, aVendorId);
+}
+
+HRESULT HostUSBDeviceFilter::getProductId(com::Utf8Str &aProductId)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_PRODUCT_ID, aProductId);
+}
+
+HRESULT HostUSBDeviceFilter::setProductId(const com::Utf8Str &aProductId)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_PRODUCT_ID, aProductId);
+}
+
+HRESULT HostUSBDeviceFilter::getRevision(com::Utf8Str &aRevision)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_DEVICE, aRevision);
+}
+
+HRESULT HostUSBDeviceFilter::setRevision(const com::Utf8Str &aRevision)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_DEVICE, aRevision);
+}
+
+HRESULT HostUSBDeviceFilter::getManufacturer(com::Utf8Str &aManufacturer)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_MANUFACTURER_STR, aManufacturer);
+}
+
+HRESULT HostUSBDeviceFilter::setManufacturer(const com::Utf8Str &aManufacturer)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_MANUFACTURER_STR, aManufacturer);
+}
+
+HRESULT HostUSBDeviceFilter::getProduct(com::Utf8Str &aProduct)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_PRODUCT_STR, aProduct);
+}
+
+HRESULT HostUSBDeviceFilter::setProduct(const com::Utf8Str &aProduct)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_PRODUCT_STR, aProduct);
+}
+
+HRESULT HostUSBDeviceFilter::getSerialNumber(com::Utf8Str &aSerialNumber)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_SERIAL_NUMBER_STR, aSerialNumber);
+}
+
+HRESULT HostUSBDeviceFilter::setSerialNumber(const com::Utf8Str &aSerialNumber)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_SERIAL_NUMBER_STR, aSerialNumber);
+}
+
+HRESULT HostUSBDeviceFilter::getPort(com::Utf8Str &aPort)
+{
+ return i_usbFilterFieldGetter(USBFILTERIDX_PORT, aPort);
+}
+
+HRESULT HostUSBDeviceFilter::setPort(const com::Utf8Str &aPort)
+{
+ return i_usbFilterFieldSetter(USBFILTERIDX_PORT, aPort);
+}
+
+HRESULT HostUSBDeviceFilter::getRemote(com::Utf8Str &aRemote)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aRemote = bd->mRemote.string();
+
+ return S_OK;
+}
+
+HRESULT HostUSBDeviceFilter::setRemote(const com::Utf8Str & /* aRemote */)
+{
+ return setError(E_NOTIMPL,
+ tr("The remote state filter is not supported by IHostUSBDeviceFilter objects"));
+}
+
+
+HRESULT HostUSBDeviceFilter::getMaskedInterfaces(ULONG *aMaskedIfs)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aMaskedIfs = bd->mData.ulMaskedInterfaces;
+
+ return S_OK;
+}
+HRESULT HostUSBDeviceFilter::setMaskedInterfaces(ULONG /* aMaskedIfs */)
+{
+ return setError(E_NOTIMPL,
+ tr("The masked interfaces property is not applicable to IHostUSBDeviceFilter objects"));
+}
+
+// wrapped IHostUSBDeviceFilter properties
+////////////////////////////////////////////////////////////////////////////////
+HRESULT HostUSBDeviceFilter::getAction(USBDeviceFilterAction_T *aAction)
+{
+ CheckComArgOutPointerValid(aAction);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ switch (USBFilterGetFilterType(&bd->mUSBFilter))
+ {
+ case USBFILTERTYPE_IGNORE: *aAction = USBDeviceFilterAction_Ignore; break;
+ case USBFILTERTYPE_CAPTURE: *aAction = USBDeviceFilterAction_Hold; break;
+ default: *aAction = USBDeviceFilterAction_Null; break;
+ }
+
+ return S_OK;
+}
+
+
+HRESULT HostUSBDeviceFilter::setAction(USBDeviceFilterAction_T aAction)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ USBFILTERTYPE filterType;
+ switch (aAction)
+ {
+ case USBDeviceFilterAction_Ignore: filterType = USBFILTERTYPE_IGNORE; break;
+ case USBDeviceFilterAction_Hold: filterType = USBFILTERTYPE_CAPTURE; break;
+ case USBDeviceFilterAction_Null:
+ return setError(E_INVALIDARG,
+ tr("Action value InvalidUSBDeviceFilterAction is not permitted"));
+ default:
+ return setError(E_INVALIDARG,
+ tr("Invalid action %d"),
+ aAction);
+ }
+ if (USBFilterGetFilterType(&bd->mUSBFilter) != filterType)
+ {
+ int vrc = USBFilterSetFilterType(&bd->mUSBFilter, filterType);
+ if (RT_FAILURE(vrc))
+ return setError(E_INVALIDARG,
+ tr("Unexpected error %Rrc"),
+ vrc);
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ return mParent->i_onUSBDeviceFilterChange(this);
+ }
+
+ return S_OK;
+}
+
+
+// IHostUSBDeviceFilter properties
+////////////////////////////////////////////////////////////////////////////////
+/**
+ * Generic USB filter field getter.
+ *
+ * @param aIdx The field index.
+ * @param aStr Where to store the value.
+ *
+ * @return COM status.
+ */
+HRESULT HostUSBDeviceFilter::i_usbFilterFieldGetter(USBFILTERIDX aIdx, com::Utf8Str &aStr)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ i_usbFilterFieldToString(&bd->mUSBFilter, aIdx, aStr);
+ return S_OK;
+}
+
+void HostUSBDeviceFilter::i_saveSettings(settings::USBDeviceFilter &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ data.strName = bd->mData.strName;
+ data.fActive = bd->mData.fActive;
+ i_usbFilterFieldToString(&bd->mUSBFilter, USBFILTERIDX_VENDOR_ID, data.strVendorId);
+ i_usbFilterFieldToString(&bd->mUSBFilter, USBFILTERIDX_PRODUCT_ID, data.strProductId);
+ i_usbFilterFieldToString(&bd->mUSBFilter, USBFILTERIDX_DEVICE, data.strRevision);
+ i_usbFilterFieldToString(&bd->mUSBFilter, USBFILTERIDX_MANUFACTURER_STR, data.strManufacturer);
+ i_usbFilterFieldToString(&bd->mUSBFilter, USBFILTERIDX_PRODUCT_STR, data.strProduct);
+ i_usbFilterFieldToString(&bd->mUSBFilter, USBFILTERIDX_SERIAL_NUMBER_STR, data.strSerialNumber);
+ i_usbFilterFieldToString(&bd->mUSBFilter, USBFILTERIDX_PORT, data.strPort);
+
+ COMGETTER(Action)(&data.action);
+}
+
+
+/**
+ * Generic USB filter field setter.
+ *
+ * @param aIdx The field index.
+ * @param aStr The new value.
+ *
+ * @return COM status.
+ */
+HRESULT HostUSBDeviceFilter::i_usbFilterFieldSetter(USBFILTERIDX aIdx, const com::Utf8Str &aStr)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Utf8Str strOld;
+ i_usbFilterFieldToString(&bd->mUSBFilter, aIdx, strOld);
+ if (strOld != aStr)
+ {
+ //bd.backup();
+ com::Utf8Str errStr;
+ HRESULT rc = USBDeviceFilter::i_usbFilterFieldFromString(&bd->mUSBFilter, aIdx, aStr, errStr);
+ if (FAILED(rc))
+ {
+ //bd.rollback();
+ return setError(rc, "%s", errStr.c_str());
+ }
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ return mParent->i_onUSBDeviceFilterChange(this);
+ }
+
+ return S_OK;
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/USBDeviceFiltersImpl.cpp b/src/VBox/Main/src-server/USBDeviceFiltersImpl.cpp
new file mode 100644
index 00000000..36e51896
--- /dev/null
+++ b/src/VBox/Main/src-server/USBDeviceFiltersImpl.cpp
@@ -0,0 +1,1091 @@
+/* $Id: USBDeviceFiltersImpl.cpp $ */
+/** @file
+ * Implementation of IUSBDeviceFilters.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_USBDEVICEFILTERS
+#include "USBDeviceFiltersImpl.h"
+
+#include "Global.h"
+#include "MachineImpl.h"
+#include "VirtualBoxImpl.h"
+#include "HostImpl.h"
+#ifdef VBOX_WITH_USB
+# include "USBDeviceImpl.h"
+# include "HostUSBDeviceImpl.h"
+# include "USBProxyService.h"
+# include "USBDeviceFilterImpl.h"
+#endif
+
+#include <iprt/string.h>
+#include <iprt/cpp/utils.h>
+
+#include <iprt/errcore.h>
+#include <VBox/settings.h>
+#include <VBox/com/array.h>
+
+#include <algorithm>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+// defines
+/////////////////////////////////////////////////////////////////////////////
+
+typedef std::list< ComObjPtr<USBDeviceFilter> > DeviceFilterList;
+
+struct USBDeviceFilters::Data
+{
+ Data(Machine *pMachine)
+ : pParent(pMachine),
+ pHost(pMachine->i_getVirtualBox()->i_host())
+ { }
+
+ ~Data()
+ {};
+
+ Machine * const pParent;
+ Host * const pHost;
+
+ // peer machine's USB device filters list
+ const ComObjPtr<USBDeviceFilters> pPeer;
+
+#ifdef VBOX_WITH_USB
+ // List of device filters.
+ Backupable<DeviceFilterList> llDeviceFilters;
+#endif
+};
+
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(USBDeviceFilters)
+
+HRESULT USBDeviceFilters::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void USBDeviceFilters::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the USB controller object.
+ *
+ * @returns COM result indicator.
+ * @param aParent Pointer to our parent object.
+ */
+HRESULT USBDeviceFilters::init(Machine *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+
+ /* mPeer is left null */
+#ifdef VBOX_WITH_USB
+ m->llDeviceFilters.allocate();
+#endif
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the USB devic filters object given another USB filters object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @returns COM result indicator.
+ * @param aParent Pointer to our parent object.
+ * @param aPeer The object to share.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ */
+HRESULT USBDeviceFilters::init(Machine *aParent, USBDeviceFilters *aPeer)
+{
+ LogFlowThisFunc(("aParent=%p, aPeer=%p\n", aParent, aPeer));
+
+ ComAssertRet(aParent && aPeer, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+
+ unconst(m->pPeer) = aPeer;
+
+ AutoWriteLock thatlock(aPeer COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_USB
+ /* create copies of all filters */
+ m->llDeviceFilters.allocate();
+ DeviceFilterList::const_iterator it = aPeer->m->llDeviceFilters->begin();
+ while (it != aPeer->m->llDeviceFilters->end())
+ {
+ ComObjPtr<USBDeviceFilter> pFilter;
+ pFilter.createObject();
+ pFilter->init(this, *it);
+ m->llDeviceFilters->push_back(pFilter);
+ ++it;
+ }
+#endif /* VBOX_WITH_USB */
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+
+/**
+ * Initializes the USB controller object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ */
+HRESULT USBDeviceFilters::initCopy(Machine *aParent, USBDeviceFilters *aPeer)
+{
+ LogFlowThisFunc(("aParent=%p, aPeer=%p\n", aParent, aPeer));
+
+ ComAssertRet(aParent && aPeer, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aParent);
+
+ /* mPeer is left null */
+
+ AutoWriteLock thatlock(aPeer COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_USB
+ /* create private copies of all filters */
+ m->llDeviceFilters.allocate();
+ DeviceFilterList::const_iterator it = aPeer->m->llDeviceFilters->begin();
+ while (it != aPeer->m->llDeviceFilters->end())
+ {
+ ComObjPtr<USBDeviceFilter> pFilter;
+ pFilter.createObject();
+ pFilter->initCopy(this, *it);
+ m->llDeviceFilters->push_back(pFilter);
+ ++it;
+ }
+#endif /* VBOX_WITH_USB */
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void USBDeviceFilters::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+#ifdef VBOX_WITH_USB
+ // uninit all device filters on the list (it's a standard std::list not an ObjectsList
+ // so we must uninit() manually)
+ for (DeviceFilterList::iterator it = m->llDeviceFilters->begin();
+ it != m->llDeviceFilters->end();
+ ++it)
+ (*it)->uninit();
+
+ m->llDeviceFilters.free();
+#endif
+
+ unconst(m->pPeer) = NULL;
+ unconst(m->pParent) = NULL;
+
+ delete m;
+ m = NULL;
+}
+
+
+// IUSBDeviceFilters properties
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef VBOX_WITH_USB
+/**
+ * Fake class for build without USB.
+ * We need an empty collection & enum for deviceFilters, that's all.
+ */
+class ATL_NO_VTABLE USBDeviceFilter :
+ public VirtualBoxBase,
+ VBOX_SCRIPTABLE_IMPL(IUSBDeviceFilter)
+{
+public:
+ DECLARE_NOT_AGGREGATABLE(USBDeviceFilter)
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+ BEGIN_COM_MAP(USBDeviceFilter)
+ COM_INTERFACE_ENTRY(ISupportErrorInfo)
+ COM_INTERFACE_ENTRY(IUSBDeviceFilter)
+ COM_INTERFACE_ENTRY2(IDispatch, IUSBDeviceFilter)
+ VBOX_TWEAK_INTERFACE_ENTRY(IUSBDeviceFilter)
+ END_COM_MAP()
+
+ DECLARE_COMMON_CLASS_METHODS(USBDeviceFilter)
+
+ // IUSBDeviceFilter properties
+ STDMETHOD(COMGETTER(Name))(BSTR *aName);
+ STDMETHOD(COMSETTER(Name))(IN_BSTR aName);
+ STDMETHOD(COMGETTER(Active))(BOOL *aActive);
+ STDMETHOD(COMSETTER(Active))(BOOL aActive);
+ STDMETHOD(COMGETTER(VendorId))(BSTR *aVendorId);
+ STDMETHOD(COMSETTER(VendorId))(IN_BSTR aVendorId);
+ STDMETHOD(COMGETTER(ProductId))(BSTR *aProductId);
+ STDMETHOD(COMSETTER(ProductId))(IN_BSTR aProductId);
+ STDMETHOD(COMGETTER(Revision))(BSTR *aRevision);
+ STDMETHOD(COMSETTER(Revision))(IN_BSTR aRevision);
+ STDMETHOD(COMGETTER(Manufacturer))(BSTR *aManufacturer);
+ STDMETHOD(COMSETTER(Manufacturer))(IN_BSTR aManufacturer);
+ STDMETHOD(COMGETTER(Product))(BSTR *aProduct);
+ STDMETHOD(COMSETTER(Product))(IN_BSTR aProduct);
+ STDMETHOD(COMGETTER(SerialNumber))(BSTR *aSerialNumber);
+ STDMETHOD(COMSETTER(SerialNumber))(IN_BSTR aSerialNumber);
+ STDMETHOD(COMGETTER(Port))(BSTR *aPort);
+ STDMETHOD(COMSETTER(Port))(IN_BSTR aPort);
+ STDMETHOD(COMGETTER(Remote))(BSTR *aRemote);
+ STDMETHOD(COMSETTER(Remote))(IN_BSTR aRemote);
+ STDMETHOD(COMGETTER(MaskedInterfaces))(ULONG *aMaskedIfs);
+ STDMETHOD(COMSETTER(MaskedInterfaces))(ULONG aMaskedIfs);
+};
+#endif /* !VBOX_WITH_USB */
+
+
+HRESULT USBDeviceFilters::getDeviceFilters(std::vector<ComPtr<IUSBDeviceFilter> > &aDeviceFilters)
+{
+#ifdef VBOX_WITH_USB
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aDeviceFilters.resize(m->llDeviceFilters.data()->size());
+ std::copy(m->llDeviceFilters.data()->begin(), m->llDeviceFilters.data()->end(), aDeviceFilters.begin());
+
+ return S_OK;
+#else
+ NOREF(aDeviceFilters);
+# ifndef RT_OS_WINDOWS
+ NOREF(aDeviceFilters);
+# endif
+ ReturnComNotImplemented();
+#endif
+}
+
+// wrapped IUSBDeviceFilters methods
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT USBDeviceFilters::createDeviceFilter(const com::Utf8Str &aName,
+ ComPtr<IUSBDeviceFilter> &aFilter)
+
+{
+#ifdef VBOX_WITH_USB
+
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<USBDeviceFilter> pFilter;
+ pFilter.createObject();
+ HRESULT rc = pFilter->init(this, Bstr(aName).raw());
+ ComAssertComRCRetRC(rc);
+ rc = pFilter.queryInterfaceTo(aFilter.asOutParam());
+ AssertComRCReturnRC(rc);
+
+ return S_OK;
+#else
+ NOREF(aName);
+ NOREF(aFilter);
+ ReturnComNotImplemented();
+#endif
+}
+
+
+HRESULT USBDeviceFilters::insertDeviceFilter(ULONG aPosition,
+ const ComPtr<IUSBDeviceFilter> &aFilter)
+{
+#ifdef VBOX_WITH_USB
+
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ IUSBDeviceFilter *iFilter = aFilter;
+ ComObjPtr<USBDeviceFilter> pFilter = static_cast<USBDeviceFilter*>(iFilter);
+
+ if (pFilter->mInList)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("The given USB device pFilter is already in the list"));
+
+ /* backup the list before modification */
+ m->llDeviceFilters.backup();
+
+ /* iterate to the position... */
+ DeviceFilterList::iterator it;
+ if (aPosition < m->llDeviceFilters->size())
+ {
+ it = m->llDeviceFilters->begin();
+ std::advance(it, aPosition);
+ }
+ else
+ it = m->llDeviceFilters->end();
+ /* ...and insert */
+ m->llDeviceFilters->insert(it, pFilter);
+ pFilter->mInList = true;
+
+ /* notify the proxy (only when it makes sense) */
+ if (pFilter->i_getData().mData.fActive && Global::IsOnline(adep.machineState())
+ && pFilter->i_getData().mRemote.isMatch(false))
+ {
+ USBProxyService *pProxySvc = m->pHost->i_usbProxyService();
+ ComAssertRet(pProxySvc, E_FAIL);
+
+ ComAssertRet(pFilter->i_getId() == NULL, E_FAIL);
+ pFilter->i_getId() = pProxySvc->insertFilter(&pFilter->i_getData().mUSBFilter);
+ }
+
+ alock.release();
+ AutoWriteLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ m->pParent->i_setModified(Machine::IsModified_USB);
+ mlock.release();
+
+ return S_OK;
+
+#else /* VBOX_WITH_USB */
+
+ NOREF(aPosition);
+ NOREF(aFilter);
+ ReturnComNotImplemented();
+
+#endif /* VBOX_WITH_USB */
+}
+
+HRESULT USBDeviceFilters::removeDeviceFilter(ULONG aPosition,
+ ComPtr<IUSBDeviceFilter> &aFilter)
+{
+#ifdef VBOX_WITH_USB
+ /* the machine needs to be mutable */
+ AutoMutableOrSavedOrRunningStateDependency adep(m->pParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!m->llDeviceFilters->size())
+ return setError(E_INVALIDARG,
+ tr("The USB device pFilter list is empty"));
+
+ if (aPosition >= m->llDeviceFilters->size())
+ return setError(E_INVALIDARG,
+ tr("Invalid position: %lu (must be in range [0, %lu])"),
+ aPosition, m->llDeviceFilters->size() - 1);
+
+ /* backup the list before modification */
+ m->llDeviceFilters.backup();
+
+ ComObjPtr<USBDeviceFilter> pFilter;
+ {
+ /* iterate to the position... */
+ DeviceFilterList::iterator it = m->llDeviceFilters->begin();
+ std::advance(it, aPosition);
+ /* ...get an element from there... */
+ pFilter = *it;
+ /* ...and remove */
+ pFilter->mInList = false;
+ m->llDeviceFilters->erase(it);
+ }
+
+ /* cancel sharing (make an independent copy of data) */
+ pFilter->unshare();
+ pFilter.queryInterfaceTo(aFilter.asOutParam());
+
+
+ /* notify the proxy (only when it makes sense) */
+ if (pFilter->i_getData().mData.fActive && Global::IsOnline(adep.machineState())
+ && pFilter->i_getData().mRemote.isMatch(false))
+ {
+ USBProxyService *pProxySvc = m->pHost->i_usbProxyService();
+ ComAssertRet(pProxySvc, E_FAIL);
+
+ ComAssertRet(pFilter->i_getId() != NULL, E_FAIL);
+ pProxySvc->removeFilter(pFilter->i_getId());
+ pFilter->i_getId() = NULL;
+ }
+
+ alock.release();
+ AutoWriteLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ m->pParent->i_setModified(Machine::IsModified_USB);
+ mlock.release();
+
+ return S_OK;
+
+#else /* VBOX_WITH_USB */
+
+ NOREF(aPosition);
+ NOREF(aFilter);
+ ReturnComNotImplemented();
+
+#endif /* VBOX_WITH_USB */
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Loads settings from the given machine node.
+ * May be called once right after this object creation.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Does not lock "this" as Machine::loadHardware, which calls this, does not lock either.
+ */
+HRESULT USBDeviceFilters::i_loadSettings(const settings::USB &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /* Note: we assume that the default values for attributes of optional
+ * nodes are assigned in the Data::Data() constructor and don't do it
+ * here. It implies that this method may only be called after constructing
+ * a new USBDeviceFilters object while all its data fields are in the default
+ * values. Exceptions are fields whose creation time defaults don't match
+ * values that should be applied when these fields are not explicitly set
+ * in the settings file (for backwards compatibility reasons). This takes
+ * place when a setting of a newly created object must default to A while
+ * the same setting of an object loaded from the old settings file must
+ * default to B. */
+
+#ifdef VBOX_WITH_USB
+ for (settings::USBDeviceFiltersList::const_iterator it = data.llDeviceFilters.begin();
+ it != data.llDeviceFilters.end();
+ ++it)
+ {
+ const settings::USBDeviceFilter &f = *it;
+ ComObjPtr<USBDeviceFilter> pFilter;
+ pFilter.createObject();
+ HRESULT rc = pFilter->init(this, // parent
+ f);
+ if (FAILED(rc)) return rc;
+
+ m->llDeviceFilters->push_back(pFilter);
+ pFilter->mInList = true;
+ }
+#else
+ RT_NOREF(data);
+#endif /* VBOX_WITH_USB */
+
+ return S_OK;
+}
+
+/**
+ * Saves settings to the given machine node.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT USBDeviceFilters::i_saveSettings(settings::USB &data)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_USB
+ data.llDeviceFilters.clear();
+
+ for (DeviceFilterList::const_iterator it = m->llDeviceFilters->begin();
+ it != m->llDeviceFilters->end();
+ ++it)
+ {
+ AutoWriteLock filterLock(*it COMMA_LOCKVAL_SRC_POS);
+ const USBDeviceFilter::BackupableUSBDeviceFilterData &filterData = (*it)->i_getData();
+
+ Bstr str;
+
+ settings::USBDeviceFilter f;
+ f.strName = filterData.mData.strName;
+ f.fActive = !!filterData.mData.fActive;
+ (*it)->COMGETTER(VendorId)(str.asOutParam());
+ f.strVendorId = str;
+ (*it)->COMGETTER(ProductId)(str.asOutParam());
+ f.strProductId = str;
+ (*it)->COMGETTER(Revision)(str.asOutParam());
+ f.strRevision = str;
+ (*it)->COMGETTER(Manufacturer)(str.asOutParam());
+ f.strManufacturer = str;
+ (*it)->COMGETTER(Product)(str.asOutParam());
+ f.strProduct = str;
+ (*it)->COMGETTER(SerialNumber)(str.asOutParam());
+ f.strSerialNumber = str;
+ (*it)->COMGETTER(Port)(str.asOutParam());
+ f.strPort = str;
+ f.strRemote = filterData.mRemote.string();
+ f.ulMaskedInterfaces = filterData.mData.ulMaskedInterfaces;
+
+ data.llDeviceFilters.push_back(f);
+ }
+#else
+ RT_NOREF(data);
+#endif /* VBOX_WITH_USB */
+
+ return S_OK;
+}
+
+/** @note Locks objects for writing! */
+void USBDeviceFilters::i_rollback()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* we need the machine state */
+ AutoAnyStateDependency adep(m->pParent);
+ AssertComRCReturnVoid(adep.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_USB
+
+ if (m->llDeviceFilters.isBackedUp())
+ {
+ USBProxyService *pProxySvc = m->pHost->i_usbProxyService();
+ Assert(pProxySvc);
+
+ /* uninitialize all new filters (absent in the backed up list) */
+ DeviceFilterList::const_iterator it = m->llDeviceFilters->begin();
+ DeviceFilterList *backedList = m->llDeviceFilters.backedUpData();
+ while (it != m->llDeviceFilters->end())
+ {
+ if (std::find(backedList->begin(), backedList->end(), *it) ==
+ backedList->end())
+ {
+ /* notify the proxy (only when it makes sense) */
+ if ((*it)->i_getData().mData.fActive &&
+ Global::IsOnline(adep.machineState())
+ && (*it)->i_getData().mRemote.isMatch(false))
+ {
+ USBDeviceFilter *pFilter = *it;
+ Assert(pFilter->i_getId() != NULL);
+ pProxySvc->removeFilter(pFilter->i_getId());
+ pFilter->i_getId() = NULL;
+ }
+
+ (*it)->uninit();
+ }
+ ++it;
+ }
+
+ if (Global::IsOnline(adep.machineState()))
+ {
+ /* find all removed old filters (absent in the new list)
+ * and insert them back to the USB proxy */
+ it = backedList->begin();
+ while (it != backedList->end())
+ {
+ if (std::find(m->llDeviceFilters->begin(), m->llDeviceFilters->end(), *it) ==
+ m->llDeviceFilters->end())
+ {
+ /* notify the proxy (only when necessary) */
+ if ((*it)->i_getData().mData.fActive
+ && (*it)->i_getData().mRemote.isMatch(false))
+ {
+ USBDeviceFilter *pFilter = *it; /* resolve ambiguity */
+ Assert(pFilter->i_getId() == NULL);
+ pFilter->i_getId() = pProxySvc->insertFilter(&pFilter->i_getData().mUSBFilter);
+ }
+ }
+ ++it;
+ }
+ }
+
+ /* restore the list */
+ m->llDeviceFilters.rollback();
+ }
+
+ /* here we don't depend on the machine state any more */
+ adep.release();
+
+ /* rollback any changes to filters after restoring the list */
+ DeviceFilterList::const_iterator it = m->llDeviceFilters->begin();
+ while (it != m->llDeviceFilters->end())
+ {
+ if ((*it)->i_isModified())
+ {
+ (*it)->i_rollback();
+ /* call this to notify the USB proxy about changes */
+ i_onDeviceFilterChange(*it);
+ }
+ ++it;
+ }
+
+#endif /* VBOX_WITH_USB */
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object (also
+ * for writing) if there is one.
+ */
+void USBDeviceFilters::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_USB
+ bool commitFilters = false;
+
+ if (m->llDeviceFilters.isBackedUp())
+ {
+ m->llDeviceFilters.commit();
+
+ /* apply changes to peer */
+ if (m->pPeer)
+ {
+ AutoWriteLock peerlock(m->pPeer COMMA_LOCKVAL_SRC_POS);
+
+ /* commit all changes to new filters (this will reshare data with
+ * peers for those who have peers) */
+ DeviceFilterList *newList = new DeviceFilterList();
+ DeviceFilterList::const_iterator it = m->llDeviceFilters->begin();
+ while (it != m->llDeviceFilters->end())
+ {
+ (*it)->i_commit();
+
+ /* look if this filter has a peer filter */
+ ComObjPtr<USBDeviceFilter> peer = (*it)->i_peer();
+ if (!peer)
+ {
+ /* no peer means the filter is a newly created one;
+ * create a peer owning data this filter share it with */
+ peer.createObject();
+ peer->init(m->pPeer, *it, true /* aReshare */);
+ }
+ else
+ {
+ /* remove peer from the old list */
+ m->pPeer->m->llDeviceFilters->remove(peer);
+ }
+ /* and add it to the new list */
+ newList->push_back(peer);
+
+ ++it;
+ }
+
+ /* uninit old peer's filters that are left */
+ it = m->pPeer->m->llDeviceFilters->begin();
+ while (it != m->pPeer->m->llDeviceFilters->end())
+ {
+ (*it)->uninit();
+ ++it;
+ }
+
+ /* attach new list of filters to our peer */
+ m->pPeer->m->llDeviceFilters.attach(newList);
+ }
+ else
+ {
+ /* we have no peer (our parent is the newly created machine);
+ * just commit changes to filters */
+ commitFilters = true;
+ }
+ }
+ else
+ {
+ /* the list of filters itself is not changed,
+ * just commit changes to filters themselves */
+ commitFilters = true;
+ }
+
+ if (commitFilters)
+ {
+ DeviceFilterList::const_iterator it = m->llDeviceFilters->begin();
+ while (it != m->llDeviceFilters->end())
+ {
+ (*it)->i_commit();
+ ++it;
+ }
+ }
+#endif /* VBOX_WITH_USB */
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ */
+void USBDeviceFilters::i_copyFrom(USBDeviceFilters *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* even more sanity */
+ AutoAnyStateDependency adep(m->pParent);
+ AssertComRCReturnVoid(adep.rc());
+ /* Machine::copyFrom() may not be called when the VM is running */
+ AssertReturnVoid(!Global::IsOnline(adep.machineState()));
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_USB
+
+ /* Note that we won't inform the USB proxy about new filters since the VM is
+ * not running when we are here and therefore no need to do so */
+
+ /* create private copies of all filters */
+ m->llDeviceFilters.backup();
+ m->llDeviceFilters->clear();
+ for (DeviceFilterList::const_iterator it = aThat->m->llDeviceFilters->begin();
+ it != aThat->m->llDeviceFilters->end();
+ ++it)
+ {
+ ComObjPtr<USBDeviceFilter> pFilter;
+ pFilter.createObject();
+ pFilter->initCopy(this, *it);
+ m->llDeviceFilters->push_back(pFilter);
+ }
+
+#endif /* VBOX_WITH_USB */
+}
+
+#ifdef VBOX_WITH_USB
+
+/**
+ * Called by setter methods of all USB device filters.
+ *
+ * @note Locks nothing.
+ */
+HRESULT USBDeviceFilters::i_onDeviceFilterChange(USBDeviceFilter *aFilter,
+ BOOL aActiveChanged /* = FALSE */)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /* we need the machine state */
+ AutoAnyStateDependency adep(m->pParent);
+ AssertComRCReturnRC(adep.rc());
+
+ /* nothing to do if the machine isn't running */
+ if (!Global::IsOnline(adep.machineState()))
+ return S_OK;
+
+ /* we don't modify our data fields -- no need to lock */
+
+ if ( aFilter->mInList
+ && m->pParent->i_isRegistered())
+ {
+ USBProxyService *pProxySvc = m->pHost->i_usbProxyService();
+ ComAssertRet(pProxySvc, E_FAIL);
+
+ if (aActiveChanged)
+ {
+ if (aFilter->i_getData().mRemote.isMatch(false))
+ {
+ /* insert/remove the filter from the proxy */
+ if (aFilter->i_getData().mData.fActive)
+ {
+ ComAssertRet(aFilter->i_getId() == NULL, E_FAIL);
+ aFilter->i_getId() = pProxySvc->insertFilter(&aFilter->i_getData().mUSBFilter);
+ }
+ else
+ {
+ ComAssertRet(aFilter->i_getId() != NULL, E_FAIL);
+ pProxySvc->removeFilter(aFilter->i_getId());
+ aFilter->i_getId() = NULL;
+ }
+ }
+ }
+ else
+ {
+ if (aFilter->i_getData().mData.fActive)
+ {
+ /* update the filter in the proxy */
+ ComAssertRet(aFilter->i_getId() != NULL, E_FAIL);
+ pProxySvc->removeFilter(aFilter->i_getId());
+ if (aFilter->i_getData().mRemote.isMatch(false))
+ {
+ aFilter->i_getId() = pProxySvc->insertFilter(&aFilter->i_getData().mUSBFilter);
+ }
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+/**
+ * Returns true if the given USB device matches to at least one of
+ * this controller's USB device filters.
+ *
+ * A HostUSBDevice specific version.
+ *
+ * @note Locks this object for reading.
+ */
+bool USBDeviceFilters::i_hasMatchingFilter(const ComObjPtr<HostUSBDevice> &aDevice, ULONG *aMaskedIfs)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), false);
+
+ /* It is not possible to work with USB device if there is no USB controller present. */
+ if (!m->pParent->i_isUSBControllerPresent())
+ return false;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* apply self filters */
+ for (DeviceFilterList::const_iterator it = m->llDeviceFilters->begin();
+ it != m->llDeviceFilters->end();
+ ++it)
+ {
+ AutoWriteLock filterLock(*it COMMA_LOCKVAL_SRC_POS);
+ if (aDevice->i_isMatch((*it)->i_getData()))
+ {
+ *aMaskedIfs = (*it)->i_getData().mData.ulMaskedInterfaces;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Returns true if the given USB device matches to at least one of
+ * this controller's USB device filters.
+ *
+ * A generic version that accepts any IUSBDevice on input.
+ *
+ * @note
+ * This method MUST correlate with HostUSBDevice::isMatch()
+ * in the sense of the device matching logic.
+ *
+ * @note Locks this object for reading.
+ */
+bool USBDeviceFilters::i_hasMatchingFilter(IUSBDevice *aUSBDevice, ULONG *aMaskedIfs)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), false);
+
+ /* It is not possible to work with USB device if there is no USB controller present. */
+ if (!m->pParent->i_isUSBControllerPresent())
+ return false;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ /* query fields */
+ USBFILTER dev;
+ USBFilterInit(&dev, USBFILTERTYPE_CAPTURE);
+
+ USHORT vendorId = 0;
+ rc = aUSBDevice->COMGETTER(VendorId)(&vendorId);
+ ComAssertComRCRet(rc, false);
+ ComAssertRet(vendorId, false);
+ int vrc = USBFilterSetNumExact(&dev, USBFILTERIDX_VENDOR_ID, vendorId, true); AssertRC(vrc);
+
+ USHORT productId = 0;
+ rc = aUSBDevice->COMGETTER(ProductId)(&productId);
+ ComAssertComRCRet(rc, false);
+ vrc = USBFilterSetNumExact(&dev, USBFILTERIDX_PRODUCT_ID, productId, true); AssertRC(vrc);
+
+ USHORT revision;
+ rc = aUSBDevice->COMGETTER(Revision)(&revision);
+ ComAssertComRCRet(rc, false);
+ vrc = USBFilterSetNumExact(&dev, USBFILTERIDX_DEVICE, revision, true); AssertRC(vrc);
+
+ Bstr manufacturer;
+ rc = aUSBDevice->COMGETTER(Manufacturer)(manufacturer.asOutParam());
+ ComAssertComRCRet(rc, false);
+ if (!manufacturer.isEmpty())
+ USBFilterSetStringExact(&dev, USBFILTERIDX_MANUFACTURER_STR, Utf8Str(manufacturer).c_str(),
+ true /*fMustBePresent*/, false /*fPurge*/);
+
+ Bstr product;
+ rc = aUSBDevice->COMGETTER(Product)(product.asOutParam());
+ ComAssertComRCRet(rc, false);
+ if (!product.isEmpty())
+ USBFilterSetStringExact(&dev, USBFILTERIDX_PRODUCT_STR, Utf8Str(product).c_str(),
+ true /*fMustBePresent*/, false /*fPurge*/);
+
+ Bstr serialNumber;
+ rc = aUSBDevice->COMGETTER(SerialNumber)(serialNumber.asOutParam());
+ ComAssertComRCRet(rc, false);
+ if (!serialNumber.isEmpty())
+ USBFilterSetStringExact(&dev, USBFILTERIDX_SERIAL_NUMBER_STR, Utf8Str(serialNumber).c_str(),
+ true /*fMustBePresent*/, false /*fPurge*/);
+
+ Bstr address;
+ rc = aUSBDevice->COMGETTER(Address)(address.asOutParam());
+ ComAssertComRCRet(rc, false);
+
+ USHORT port = 0;
+ rc = aUSBDevice->COMGETTER(Port)(&port);
+ ComAssertComRCRet(rc, false);
+ USBFilterSetNumExact(&dev, USBFILTERIDX_PORT, port, true);
+
+ BOOL remote = FALSE;
+ rc = aUSBDevice->COMGETTER(Remote)(&remote);
+ ComAssertComRCRet(rc, false);
+ ComAssertRet(remote == TRUE, false);
+
+ bool match = false;
+
+ /* apply self filters */
+ for (DeviceFilterList::const_iterator it = m->llDeviceFilters->begin();
+ it != m->llDeviceFilters->end();
+ ++it)
+ {
+ AutoWriteLock filterLock(*it COMMA_LOCKVAL_SRC_POS);
+ const USBDeviceFilter::BackupableUSBDeviceFilterData &aData = (*it)->i_getData();
+
+ if (!aData.mData.fActive)
+ continue;
+ if (!aData.mRemote.isMatch(remote))
+ continue;
+ if (!USBFilterMatch(&aData.mUSBFilter, &dev))
+ continue;
+
+ match = true;
+ *aMaskedIfs = aData.mData.ulMaskedInterfaces;
+ break;
+ }
+
+ LogFlowThisFunc(("returns: %d\n", match));
+ LogFlowThisFuncLeave();
+
+ return match;
+}
+
+/**
+ * Notifies the proxy pProxySvc about all filters as requested by the
+ * @a aInsertFilters argument.
+ *
+ * @param aInsertFilters @c true to insert filters, @c false to remove.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT USBDeviceFilters::i_notifyProxy(bool aInsertFilters)
+{
+ LogFlowThisFunc(("aInsertFilters=%RTbool\n", aInsertFilters));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), false);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ USBProxyService *pProxySvc = m->pHost->i_usbProxyService();
+ AssertReturn(pProxySvc, E_FAIL);
+
+ DeviceFilterList::const_iterator it = m->llDeviceFilters->begin();
+ while (it != m->llDeviceFilters->end())
+ {
+ USBDeviceFilter *pFilter = *it; /* resolve ambiguity (for ComPtr below) */
+
+ /* notify the proxy (only if the filter is active) */
+ if ( pFilter->i_getData().mData.fActive
+ && pFilter->i_getData().mRemote.isMatch(false) /* and if the filter is NOT remote */
+ )
+ {
+ if (aInsertFilters)
+ {
+ AssertReturn(pFilter->i_getId() == NULL, E_FAIL);
+ pFilter->i_getId() = pProxySvc->insertFilter(&pFilter->i_getData().mUSBFilter);
+ }
+ else
+ {
+ /* It's possible that the given filter was not inserted the proxy
+ * when this method gets called (as a result of an early VM
+ * process crash for example. So, don't assert that ID != NULL. */
+ if (pFilter->i_getId() != NULL)
+ {
+ pProxySvc->removeFilter(pFilter->i_getId());
+ pFilter->i_getId() = NULL;
+ }
+ }
+ }
+ ++it;
+ }
+
+ return S_OK;
+}
+
+Machine* USBDeviceFilters::i_getMachine()
+{
+ return m->pParent;
+}
+
+#endif /* VBOX_WITH_USB */
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/USBIdDatabaseGenerator.cpp b/src/VBox/Main/src-server/USBIdDatabaseGenerator.cpp
new file mode 100644
index 00000000..8e4edb0b
--- /dev/null
+++ b/src/VBox/Main/src-server/USBIdDatabaseGenerator.cpp
@@ -0,0 +1,488 @@
+/* $Id: USBIdDatabaseGenerator.cpp $ */
+/** @file
+ * USB device vendor and product ID database - generator.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <stdio.h>
+
+#include <algorithm>
+#include <map>
+#include <iprt/sanitized/string>
+#include <vector>
+
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+
+#include "../include/USBIdDatabase.h"
+
+
+/*
+ * Include the string table generator.
+ */
+#define BLDPROG_STRTAB_MAX_STRLEN (USB_ID_DATABASE_MAX_STRING - 1)
+#ifdef USB_ID_DATABASE_WITH_COMPRESSION
+# define BLDPROG_STRTAB_WITH_COMPRESSION
+#else
+# undef BLDPROG_STRTAB_WITH_COMPRESSION
+#endif
+#define BLDPROG_STRTAB_WITH_CAMEL_WORDS
+#undef BLDPROG_STRTAB_PURE_ASCII
+#include <iprt/bldprog-strtab-template.cpp.h>
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** For verbose output. */
+static bool g_fVerbose = false;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+// error codes (complements RTEXITCODE_XXX).
+#define ERROR_OPEN_FILE (12)
+#define ERROR_IN_PARSE_LINE (13)
+#define ERROR_DUPLICATE_ENTRY (14)
+#define ERROR_WRONG_FILE_FORMAT (15)
+#define ERROR_TOO_MANY_PRODUCTS (16)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+struct VendorRecord
+{
+ size_t vendorID;
+ size_t iProduct;
+ size_t cProducts;
+ std::string str;
+ BLDPROGSTRING StrRef;
+};
+
+struct ProductRecord
+{
+ size_t key;
+ size_t vendorID;
+ size_t productID;
+ std::string str;
+ BLDPROGSTRING StrRef;
+};
+
+typedef std::vector<ProductRecord> ProductsSet;
+typedef std::vector<VendorRecord> VendorsSet;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static ProductsSet g_products;
+static VendorsSet g_vendors;
+
+/** The size of all the raw strings, including terminators. */
+static size_t g_cbRawStrings = 0;
+
+
+
+bool operator < (const ProductRecord& lh, const ProductRecord& rh)
+{
+ return lh.key < rh.key;
+}
+
+bool operator < (const VendorRecord& lh, const VendorRecord& rh)
+{
+ return lh.vendorID < rh.vendorID;
+}
+
+bool operator == (const ProductRecord& lh, const ProductRecord& rh)
+{
+ return lh.key == rh.key;
+}
+
+bool operator == (const VendorRecord& lh, const VendorRecord& rh)
+{
+ return lh.vendorID == rh.vendorID;
+}
+
+
+/*
+ * Input file parsing.
+ */
+static int ParseAlias(char *pszLine, size_t& id, std::string& desc)
+{
+ /* First there's a hexadeciman number. */
+ uint32_t uVal;
+ char *pszNext;
+ int rc = RTStrToUInt32Ex(pszLine, &pszNext, 16, &uVal);
+ if ( rc == VWRN_TRAILING_CHARS
+ || rc == VWRN_TRAILING_SPACES
+ || rc == VINF_SUCCESS)
+ {
+ /* Skip the whipespace following it and at the end of the line. */
+ pszNext = RTStrStripL(pszNext);
+ if (*pszNext != '\0')
+ {
+ rc = RTStrValidateEncoding(pszNext);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchDesc = strlen(pszNext);
+ if (cchDesc <= USB_ID_DATABASE_MAX_STRING)
+ {
+ id = uVal;
+ desc = pszNext;
+ g_cbRawStrings += cchDesc + 1;
+ return RTEXITCODE_SUCCESS;
+ }
+ RTMsgError("String to long: %zu", cchDesc);
+ }
+ else
+ RTMsgError("Invalid encoding: '%s' (rc=%Rrc)", pszNext, rc);
+ }
+ else
+ RTMsgError("Error parsing '%s'", pszLine);
+ }
+ else
+ RTMsgError("Error converting number at the start of '%s': %Rrc", pszLine, rc);
+ return ERROR_IN_PARSE_LINE;
+}
+
+static int ParseUsbIds(PRTSTREAM pInStrm, const char *pszFile)
+{
+ /*
+ * State data.
+ */
+ VendorRecord vendor = { 0, 0, 0, "" };
+
+ /*
+ * Process the file line-by-line.
+ *
+ * The generic format is that we have top level entries (vendors) starting
+ * in position 0 with sub entries starting after one or more, depending on
+ * the level, tab characters.
+ *
+ * Specifically, the list of vendors and their products will always start
+ * with a vendor line followed by indented products. The first character
+ * on the vendor line is a hex digit (four in total) that makes up the
+ * vendor ID. The product lines equally starts with a 4 digit hex ID value.
+ *
+ * Other lists are assumed to have first lines that doesn't start with any
+ * lower case hex digit.
+ */
+ uint32_t iLine = 0;;
+ for (;;)
+ {
+ char szLine[_4K];
+ int rc = RTStrmGetLine(pInStrm, szLine, sizeof(szLine));
+ if (RT_SUCCESS(rc))
+ {
+ iLine++;
+
+ /* Check for vendor line. */
+ char chType = szLine[0];
+ if ( RT_C_IS_XDIGIT(chType)
+ && RT_C_IS_SPACE(szLine[4])
+ && RT_C_IS_XDIGIT(szLine[1])
+ && RT_C_IS_XDIGIT(szLine[2])
+ && RT_C_IS_XDIGIT(szLine[3]) )
+ {
+ if (ParseAlias(szLine, vendor.vendorID, vendor.str) == 0)
+ g_vendors.push_back(vendor);
+ else
+ return RTMsgErrorExit((RTEXITCODE)ERROR_IN_PARSE_LINE,
+ "%s(%d): Error in parsing vendor line: '%s'", pszFile, iLine, szLine);
+ }
+ /* Check for product line. */
+ else if (szLine[0] == '\t' && vendor.vendorID != 0)
+ {
+ ProductRecord product = { 0, vendor.vendorID, 0, "" };
+ if (ParseAlias(&szLine[1], product.productID, product.str) == 0)
+ {
+ product.key = RT_MAKE_U32(product.productID, product.vendorID);
+ Assert(product.vendorID == vendor.vendorID);
+ g_products.push_back(product);
+ }
+ else
+ return RTMsgErrorExit((RTEXITCODE)ERROR_IN_PARSE_LINE, "Error in parsing product line: '%s'", szLine);
+ }
+ /* If not a blank or comment line, it is some other kind of data.
+ So, make sure the vendor ID is cleared so we don't try process
+ the sub-items of in some other list as products. */
+ else if ( chType != '#'
+ && chType != '\0'
+ && *RTStrStripL(szLine) != '\0')
+ vendor.vendorID = 0;
+ }
+ else if (rc == VERR_EOF)
+ return RTEXITCODE_SUCCESS;
+ else
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTStrmGetLine failed: %Rrc", rc);
+ }
+}
+
+static void WriteSourceFile(FILE *pOut, const char *argv0, PBLDPROGSTRTAB pStrTab)
+{
+ fprintf(pOut,
+ "/** @file\n"
+ " * USB device vendor and product ID database - Autogenerated by %s\n"
+ " */\n"
+ "\n"
+ "/*\n"
+ " * Copyright (C) 2015-2022 Oracle and/or its affiliates.\n"
+ " *\n"
+ " * This file is part of VirtualBox base platform packages, as\n"
+ " * available from https://www.virtualbox.org.\n"
+ " *\n"
+ " * This program is free software; you can redistribute it and/or\n"
+ " * modify it under the terms of the GNU General Public License\n"
+ " * as published by the Free Software Foundation, in version 3 of the\n"
+ " * License.\n"
+ " *\n"
+ " * This program is distributed in the hope that it will be useful, but\n"
+ " * WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
+ " * General Public License for more details.\n"
+ " *\n"
+ " * You should have received a copy of the GNU General Public License\n"
+ " * along with this program; if not, see <https://www.gnu.org/licenses>.\n"
+ " *\n"
+ " * SPDX-License-Identifier: GPL-3.0-only\n"
+ " */"
+ "\n"
+ "\n"
+ "#include \"USBIdDatabase.h\"\n"
+ "\n",
+ argv0);
+
+ BldProgStrTab_WriteStringTable(pStrTab, pOut, "", "USBIdDatabase::s_", "StrTab");
+
+ fputs("/**\n"
+ " * USB devices aliases array.\n"
+ " * Format: VendorId, ProductId, Vendor Name, Product Name\n"
+ " * The source of the list is http://www.linux-usb.org/usb.ids\n"
+ " */\n"
+ "USBIDDBPROD const USBIdDatabase::s_aProducts[] =\n"
+ "{\n", pOut);
+ for (ProductsSet::iterator itp = g_products.begin(); itp != g_products.end(); ++itp)
+ fprintf(pOut, " { 0x%04x },\n", (unsigned)itp->productID);
+ fputs("};\n"
+ "\n"
+ "\n"
+ "const RTBLDPROGSTRREF USBIdDatabase::s_aProductNames[] =\n"
+ "{\n", pOut);
+ for (ProductsSet::iterator itp = g_products.begin(); itp != g_products.end(); ++itp)
+ fprintf(pOut, "{ 0x%06x, 0x%02x },\n", itp->StrRef.offStrTab, (unsigned)itp->StrRef.cchString);
+ fputs("};\n"
+ "\n"
+ "const size_t USBIdDatabase::s_cProducts = RT_ELEMENTS(USBIdDatabase::s_aProducts);\n"
+ "\n", pOut);
+
+ fputs("USBIDDBVENDOR const USBIdDatabase::s_aVendors[] =\n"
+ "{\n", pOut);
+ for (VendorsSet::iterator itv = g_vendors.begin(); itv != g_vendors.end(); ++itv)
+ fprintf(pOut, " { 0x%04x, 0x%04x, 0x%04x },\n", (unsigned)itv->vendorID, (unsigned)itv->iProduct, (unsigned)itv->cProducts);
+ fputs("};\n"
+ "\n"
+ "\n"
+ "const RTBLDPROGSTRREF USBIdDatabase::s_aVendorNames[] =\n"
+ "{\n", pOut);
+ for (VendorsSet::iterator itv = g_vendors.begin(); itv != g_vendors.end(); ++itv)
+ fprintf(pOut, "{ 0x%06x, 0x%02x },\n", itv->StrRef.offStrTab, (unsigned)itv->StrRef.cchString);
+ fputs("};\n"
+ "\n"
+ "const size_t USBIdDatabase::s_cVendors = RT_ELEMENTS(USBIdDatabase::s_aVendors);\n"
+ "\n", pOut);
+}
+
+static int usage(FILE *pOut, const char *argv0)
+{
+ fprintf(pOut, "Usage: %s [linux.org usb list file] [custom usb list file] [-o output file]\n", argv0);
+ return RTEXITCODE_SYNTAX;
+}
+
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Initialize IPRT and convert argv to UTF-8.
+ */
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse arguments and read input files.
+ */
+ if (argc < 4)
+ {
+ usage(stderr, argv[0]);
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Insufficient arguments.");
+ }
+ g_products.reserve(20000);
+ g_vendors.reserve(3500);
+
+ const char *pszOutFile = NULL;
+ for (int i = 1; i < argc; i++)
+ {
+ if (strcmp(argv[i], "-o") == 0)
+ {
+ pszOutFile = argv[++i];
+ continue;
+ }
+ if ( strcmp(argv[i], "-h") == 0
+ || strcmp(argv[i], "-?") == 0
+ || strcmp(argv[i], "--help") == 0)
+ {
+ usage(stdout, argv[0]);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ PRTSTREAM pInStrm;
+ rc = RTStrmOpen(argv[i], "r", &pInStrm);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE,
+ "Failed to open file '%s' for reading: %Rrc", argv[i], rc);
+
+ rc = ParseUsbIds(pInStrm, argv[i]);
+ RTStrmClose(pInStrm);
+ if (rc != 0)
+ {
+ RTMsgError("Failed parsing USB devices file '%s'", argv[i]);
+ return rc;
+ }
+ }
+
+ /*
+ * Due to USBIDDBVENDOR::iProduct, there is currently a max of 64KB products.
+ * (Not a problem as we've only have less that 54K products currently.)
+ */
+ if (g_products.size() > _64K)
+ return RTMsgErrorExit((RTEXITCODE)ERROR_TOO_MANY_PRODUCTS,
+ "More than 64K products is not supported: %u products", g_products.size());
+
+ /*
+ * Sort the IDs and fill in the iProduct and cProduct members.
+ */
+ sort(g_products.begin(), g_products.end());
+ sort(g_vendors.begin(), g_vendors.end());
+
+ size_t iProduct = 0;
+ for (size_t iVendor = 0; iVendor < g_vendors.size(); iVendor++)
+ {
+ size_t const idVendor = g_vendors[iVendor].vendorID;
+ g_vendors[iVendor].iProduct = iProduct;
+ if ( iProduct < g_products.size()
+ && g_products[iProduct].vendorID <= idVendor)
+ {
+ if (g_products[iProduct].vendorID == idVendor)
+ do
+ iProduct++;
+ while ( iProduct < g_products.size()
+ && g_products[iProduct].vendorID == idVendor);
+ else
+ return RTMsgErrorExit((RTEXITCODE)ERROR_IN_PARSE_LINE, "product without vendor after sorting. impossible!");
+ }
+ g_vendors[iVendor].cProducts = iProduct - g_vendors[iVendor].iProduct;
+ }
+
+ /*
+ * Verify that all IDs are unique.
+ */
+ ProductsSet::iterator ita = adjacent_find(g_products.begin(), g_products.end());
+ if (ita != g_products.end())
+ return RTMsgErrorExit((RTEXITCODE)ERROR_DUPLICATE_ENTRY, "Duplicate alias detected: idProduct=%#06x", ita->productID);
+
+ /*
+ * Build the string table.
+ * Do string compression and create the string table.
+ */
+ BLDPROGSTRTAB StrTab;
+ if (!BldProgStrTab_Init(&StrTab, g_products.size() + g_vendors.size()))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory!");
+
+ for (ProductsSet::iterator it = g_products.begin(); it != g_products.end(); ++it)
+ {
+ it->StrRef.pszString = (char *)it->str.c_str();
+ BldProgStrTab_AddString(&StrTab, &it->StrRef);
+ }
+ for (VendorsSet::iterator it = g_vendors.begin(); it != g_vendors.end(); ++it)
+ {
+ it->StrRef.pszString = (char *)it->str.c_str();
+ BldProgStrTab_AddString(&StrTab, &it->StrRef);
+ }
+
+ if (!BldProgStrTab_CompileIt(&StrTab, g_fVerbose))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "BldProgStrTab_CompileIt failed!\n");
+
+ /*
+ * Print stats. Making a little extra effort to get it all on one line.
+ */
+ size_t const cbVendorEntry = sizeof(USBIdDatabase::s_aVendors[0]) + sizeof(USBIdDatabase::s_aVendorNames[0]);
+ size_t const cbProductEntry = sizeof(USBIdDatabase::s_aProducts[0]) + sizeof(USBIdDatabase::s_aProductNames[0]);
+
+ size_t cbOldRaw = (g_products.size() + g_vendors.size()) * sizeof(const char *) * 2 + g_cbRawStrings;
+ size_t cbRaw = g_vendors.size() * cbVendorEntry + g_products.size() * cbProductEntry + g_cbRawStrings;
+ size_t cbActual = g_vendors.size() * cbVendorEntry + g_products.size() * cbProductEntry + StrTab.cchStrTab;
+#ifdef USB_ID_DATABASE_WITH_COMPRESSION
+ cbActual += sizeof(StrTab.aCompDict);
+#endif
+
+ char szMsg1[32];
+ RTStrPrintf(szMsg1, sizeof(szMsg1),"Total %zu bytes", cbActual);
+ char szMsg2[64];
+ RTStrPrintf(szMsg2, sizeof(szMsg2)," old version %zu bytes + relocs (%zu%% save)",
+ cbOldRaw, (cbOldRaw - cbActual) * 100 / cbOldRaw);
+ if (cbActual < cbRaw)
+ RTMsgInfo("%s - saving %zu%% (%zu bytes);%s", szMsg1, (cbRaw - cbActual) * 100 / cbRaw, cbRaw - cbActual, szMsg2);
+ else
+ RTMsgInfo("%s - wasting %zu bytes;%s", szMsg1, cbActual - cbRaw, szMsg2);
+
+ /*
+ * Produce the source file.
+ */
+ if (!pszOutFile)
+ return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE, "Output file is not specified.");
+
+ FILE *pOut = fopen(pszOutFile, "w");
+ if (!pOut)
+ return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE, "Error opening '%s' for writing", pszOutFile);
+
+ WriteSourceFile(pOut, argv[0], &StrTab);
+
+ if (ferror(pOut))
+ return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE, "Error writing '%s'!", pszOutFile);
+ if (fclose(pOut) != 0)
+ return RTMsgErrorExit((RTEXITCODE)ERROR_OPEN_FILE, "Error closing '%s'!", pszOutFile);
+
+ return RTEXITCODE_SUCCESS;
+}
+
diff --git a/src/VBox/Main/src-server/USBIdDatabaseStub.cpp b/src/VBox/Main/src-server/USBIdDatabaseStub.cpp
new file mode 100644
index 00000000..e150dcbf
--- /dev/null
+++ b/src/VBox/Main/src-server/USBIdDatabaseStub.cpp
@@ -0,0 +1,39 @@
+/* $Id: USBIdDatabaseStub.cpp $ */
+/** @file
+ * USB device vendor and product ID database - stub.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "USBIdDatabase.h"
+
+const RTBLDPROGSTRTAB USBIdDatabase::s_StrTab = { "", 0, 0 NULL };
+
+const size_t USBIdDatabase::s_cVendors = 0;
+const USBIDDBVENDOR USBIdDatabase::s_aVendors[] = { 0 };
+const RTBLDPROGSTRREF USBIdDatabase::s_aVendorNames[] = { {0,0} };
+
+const size_t USBIdDatabase::s_cProducts = 0;
+const USBIDDBPROD USBIdDatabase::s_aProducts[] = { 0 };
+const RTBLDPROGSTRREF USBIdDatabase::s_aProductNames[] = { {0,0} };
+
diff --git a/src/VBox/Main/src-server/USBProxyBackend.cpp b/src/VBox/Main/src-server/USBProxyBackend.cpp
new file mode 100644
index 00000000..8f95dcd9
--- /dev/null
+++ b/src/VBox/Main/src-server/USBProxyBackend.cpp
@@ -0,0 +1,759 @@
+/* $Id: USBProxyBackend.cpp $ */
+/** @file
+ * VirtualBox USB Proxy Service (base) class.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_USBPROXYBACKEND
+#include "USBProxyBackend.h"
+#include "USBProxyService.h"
+#include "HostUSBDeviceImpl.h"
+#include "HostImpl.h"
+#include "MachineImpl.h"
+#include "VirtualBoxImpl.h"
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+#include <VBox/com/array.h>
+#include <iprt/errcore.h>
+#include <iprt/asm.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+
+/**
+ * Empty constructor.
+ */
+USBProxyBackend::USBProxyBackend()
+{
+ LogFlowThisFunc(("\n"));
+}
+
+
+/**
+ * Empty destructor.
+ */
+USBProxyBackend::~USBProxyBackend()
+{
+}
+
+
+HRESULT USBProxyBackend::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void USBProxyBackend::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+/**
+ * Stub needed as long as the class isn't virtual
+ */
+int USBProxyBackend::init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings)
+{
+ RT_NOREF1(fLoadingSettings);
+
+ m_pUsbProxyService = pUsbProxyService;
+ mThread = NIL_RTTHREAD;
+ mTerminate = false;
+ unconst(m_strId) = strId;
+ m_cRefs = 0;
+ unconst(m_strAddress) = strAddress;
+
+ unconst(m_strBackend) = Utf8Str::Empty;
+
+ return VINF_SUCCESS;
+}
+
+
+void USBProxyBackend::uninit()
+{
+ LogFlowThisFunc(("\n"));
+ Assert(mThread == NIL_RTTHREAD);
+ mTerminate = true;
+ m_pUsbProxyService = NULL;
+ m_llDevices.clear();
+}
+
+/**
+ * Query if the service is active and working.
+ *
+ * @returns true if the service is up running.
+ * @returns false if the service isn't running.
+ */
+bool USBProxyBackend::isActive(void)
+{
+ return mThread != NIL_RTTHREAD;
+}
+
+
+/**
+ * Returns the ID of the instance.
+ *
+ * @returns ID string for the instance.
+ */
+const com::Utf8Str &USBProxyBackend::i_getId()
+{
+ return m_strId;
+}
+
+
+/**
+ * Returns the address of the instance.
+ *
+ * @returns ID string for the instance.
+ */
+const com::Utf8Str &USBProxyBackend::i_getAddress()
+{
+ return m_strAddress;
+}
+
+
+/**
+ * Returns the backend of the instance.
+ *
+ * @returns ID string for the instance.
+ */
+const com::Utf8Str &USBProxyBackend::i_getBackend()
+{
+ return m_strBackend;
+}
+
+/**
+ * Returns the current reference counter for the backend.
+ */
+uint32_t USBProxyBackend::i_getRefCount()
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return m_cRefs;
+}
+
+
+/**
+ * A filter was inserted / loaded.
+ *
+ * @param aFilter Pointer to the inserted filter.
+ * @return ID of the inserted filter
+ */
+void *USBProxyBackend::insertFilter(PCUSBFILTER aFilter)
+{
+ // return non-NULL to fake success.
+ NOREF(aFilter);
+ return (void *)1;
+}
+
+
+/**
+ * A filter was removed.
+ *
+ * @param aId ID of the filter to remove
+ */
+void USBProxyBackend::removeFilter(void *aId)
+{
+ NOREF(aId);
+}
+
+
+/**
+ * A VM is trying to capture a device, do necessary preparations.
+ *
+ * @returns VBox status code.
+ * @param aDevice The device in question.
+ */
+int USBProxyBackend::captureDevice(HostUSBDevice *aDevice)
+{
+ NOREF(aDevice);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * Notification that an async captureDevice() operation completed.
+ *
+ * This is used by the proxy to release temporary filters.
+ *
+ * @returns VBox status code.
+ * @param aDevice The device in question.
+ * @param aSuccess Whether it succeeded or failed.
+ */
+void USBProxyBackend::captureDeviceCompleted(HostUSBDevice *aDevice, bool aSuccess)
+{
+ NOREF(aDevice);
+ NOREF(aSuccess);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ incRef();
+}
+
+
+/**
+ * A VM is releasing a device back to the host.
+ *
+ * @returns VBox status code.
+ * @param aDevice The device in question.
+ */
+int USBProxyBackend::releaseDevice(HostUSBDevice *aDevice)
+{
+ NOREF(aDevice);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * Notification that an async releaseDevice() operation completed.
+ *
+ * This is used by the proxy to release temporary filters.
+ *
+ * @returns VBox status code.
+ * @param aDevice The device in question.
+ * @param aSuccess Whether it succeeded or failed.
+ */
+void USBProxyBackend::releaseDeviceCompleted(HostUSBDevice *aDevice, bool aSuccess)
+{
+ NOREF(aDevice);
+ NOREF(aSuccess);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ decRef();
+}
+
+
+bool USBProxyBackend::isFakeUpdateRequired()
+{
+ return false;
+}
+
+/**
+ * Returns whether devices reported by this backend go through a de/re-attach
+ * and device re-enumeration cycle when they are captured or released.
+ */
+bool USBProxyBackend::i_isDevReEnumerationRequired()
+{
+ return false;
+}
+
+// Internals
+/////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * Starts the service.
+ *
+ * @returns VBox status code.
+ */
+int USBProxyBackend::start(void)
+{
+ int rc = VINF_SUCCESS;
+ if (mThread == NIL_RTTHREAD)
+ {
+ /*
+ * Force update before starting the poller thread.
+ */
+ rc = wait(0);
+ if (rc == VERR_TIMEOUT || rc == VERR_INTERRUPTED || RT_SUCCESS(rc))
+ {
+ PUSBDEVICE pDevices = getDevices();
+ updateDeviceList(pDevices);
+
+ /*
+ * Create the poller thread which will look for changes.
+ */
+ mTerminate = false;
+ rc = RTThreadCreate(&mThread, USBProxyBackend::serviceThread, this,
+ 0, RTTHREADTYPE_INFREQUENT_POLLER, RTTHREADFLAGS_WAITABLE, "USBPROXY");
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ LogFlowThisFunc(("started mThread=%RTthrd\n", mThread));
+ else
+ mThread = NIL_RTTHREAD;
+ }
+ }
+ else
+ LogFlowThisFunc(("already running, mThread=%RTthrd\n", mThread));
+ return rc;
+}
+
+
+/**
+ * Stops the service.
+ *
+ * @returns VBox status code.
+ */
+int USBProxyBackend::stop(void)
+{
+ int rc = VINF_SUCCESS;
+ if (mThread != NIL_RTTHREAD)
+ {
+ /*
+ * Mark the thread for termination and kick it.
+ */
+ ASMAtomicXchgSize(&mTerminate, true);
+ rc = interruptWait();
+ AssertRC(rc);
+
+ /*
+ * Wait for the thread to finish and then update the state.
+ */
+ rc = RTThreadWait(mThread, 60000, NULL);
+ if (rc == VERR_INVALID_HANDLE)
+ rc = VINF_SUCCESS;
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowThisFunc(("stopped mThread=%RTthrd\n", mThread));
+ mThread = NIL_RTTHREAD;
+ mTerminate = false;
+ }
+ else
+ AssertRC(rc);
+ }
+ else
+ LogFlowThisFunc(("not active\n"));
+
+ /* Make sure there is no device from us in the list anymore. */
+ updateDeviceList(NULL);
+
+ return rc;
+}
+
+
+/**
+ * The service thread created by start().
+ *
+ * @param Thread The thread handle.
+ * @param pvUser Pointer to the USBProxyBackend instance.
+ */
+/*static*/ DECLCALLBACK(int) USBProxyBackend::serviceThread(RTTHREAD /* Thread */, void *pvUser)
+{
+ USBProxyBackend *pThis = (USBProxyBackend *)pvUser;
+ LogFlowFunc(("pThis=%p\n", pThis));
+ pThis->serviceThreadInit();
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Processing loop.
+ */
+ for (;;)
+ {
+ rc = pThis->wait(RT_INDEFINITE_WAIT);
+ if (RT_FAILURE(rc) && rc != VERR_INTERRUPTED && rc != VERR_TIMEOUT)
+ break;
+ if (pThis->mTerminate)
+ break;
+
+ PUSBDEVICE pDevices = pThis->getDevices();
+ pThis->updateDeviceList(pDevices);
+ }
+
+ pThis->serviceThreadTerm();
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * First call made on the service thread, use it to do
+ * thread initialization.
+ *
+ * The default implementation in USBProxyBackend just a dummy stub.
+ */
+void USBProxyBackend::serviceThreadInit(void)
+{
+}
+
+
+/**
+ * Last call made on the service thread, use it to do
+ * thread termination.
+ */
+void USBProxyBackend::serviceThreadTerm(void)
+{
+}
+
+
+/**
+ * Wait for a change in the USB devices attached to the host.
+ *
+ * The default implementation in USBProxyBackend just a dummy stub.
+ *
+ * @returns VBox status code. VERR_INTERRUPTED and VERR_TIMEOUT are considered
+ * harmless, while all other error status are fatal.
+ * @param aMillies Number of milliseconds to wait.
+ */
+int USBProxyBackend::wait(RTMSINTERVAL aMillies)
+{
+ return RTThreadSleep(RT_MIN(aMillies, 250));
+}
+
+
+/**
+ * Interrupt any wait() call in progress.
+ *
+ * The default implementation in USBProxyBackend just a dummy stub.
+ *
+ * @returns VBox status code.
+ */
+int USBProxyBackend::interruptWait(void)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * Get a list of USB device currently attached to the host.
+ *
+ * The default implementation in USBProxyBackend just a dummy stub.
+ *
+ * @returns Pointer to a list of USB devices.
+ * The list nodes are freed individually by calling freeDevice().
+ */
+PUSBDEVICE USBProxyBackend::getDevices(void)
+{
+ return NULL;
+}
+
+
+/**
+ * Increments the reference counter.
+ *
+ * @returns New reference count value.
+ */
+uint32_t USBProxyBackend::incRef()
+{
+ Assert(isWriteLockOnCurrentThread());
+
+ return ++m_cRefs;
+}
+
+/**
+ * Decrements the reference counter.
+ *
+ * @returns New reference count value.
+ */
+uint32_t USBProxyBackend::decRef()
+{
+ Assert(isWriteLockOnCurrentThread());
+
+ return --m_cRefs;
+}
+
+
+/**
+ * Free all the members of a USB device returned by getDevice().
+ *
+ * @param pDevice Pointer to the device.
+ */
+/*static*/ void
+USBProxyBackend::freeDeviceMembers(PUSBDEVICE pDevice)
+{
+ RTStrFree((char *)pDevice->pszManufacturer);
+ pDevice->pszManufacturer = NULL;
+ RTStrFree((char *)pDevice->pszProduct);
+ pDevice->pszProduct = NULL;
+ RTStrFree((char *)pDevice->pszSerialNumber);
+ pDevice->pszSerialNumber = NULL;
+
+ RTStrFree((char *)pDevice->pszAddress);
+ pDevice->pszAddress = NULL;
+ RTStrFree((char *)pDevice->pszBackend);
+ pDevice->pszBackend = NULL;
+#ifdef RT_OS_WINDOWS
+ RTStrFree(pDevice->pszAltAddress);
+ pDevice->pszAltAddress = NULL;
+ RTStrFree(pDevice->pszHubName);
+ pDevice->pszHubName = NULL;
+#elif defined(RT_OS_SOLARIS)
+ RTStrFree(pDevice->pszDevicePath);
+ pDevice->pszDevicePath = NULL;
+#endif
+}
+
+
+/**
+ * Free one USB device returned by getDevice().
+ *
+ * @param pDevice Pointer to the device.
+ */
+/*static*/ void
+USBProxyBackend::freeDevice(PUSBDEVICE pDevice)
+{
+ freeDeviceMembers(pDevice);
+ RTMemFree(pDevice);
+}
+
+void USBProxyBackend::deviceAdded(ComObjPtr<HostUSBDevice> &aDevice, PUSBDEVICE pDev)
+{
+ /* Nothing to do. */
+ NOREF(aDevice);
+ NOREF(pDev);
+}
+
+/**
+ * Initializes a filter with the data from the specified device.
+ *
+ * @param aFilter The filter to fill.
+ * @param aDevice The device to fill it with.
+ */
+/*static*/ void
+USBProxyBackend::initFilterFromDevice(PUSBFILTER aFilter, HostUSBDevice *aDevice)
+{
+ PCUSBDEVICE pDev = aDevice->i_getUsbData();
+ int vrc;
+
+ vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_VENDOR_ID, pDev->idVendor, true); AssertRC(vrc);
+ vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_PRODUCT_ID, pDev->idProduct, true); AssertRC(vrc);
+ vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_DEVICE_REV, pDev->bcdDevice, true); AssertRC(vrc);
+ vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_DEVICE_CLASS, pDev->bDeviceClass, true); AssertRC(vrc);
+ vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_DEVICE_SUB_CLASS, pDev->bDeviceSubClass, true); AssertRC(vrc);
+ vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_DEVICE_PROTOCOL, pDev->bDeviceProtocol, true); AssertRC(vrc);
+ vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_PORT, pDev->bPort, false); AssertRC(vrc);
+ vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_BUS, pDev->bBus, false); AssertRC(vrc);
+ if (pDev->pszSerialNumber)
+ {
+ vrc = USBFilterSetStringExact(aFilter, USBFILTERIDX_SERIAL_NUMBER_STR, pDev->pszSerialNumber,
+ true /*fMustBePresent*/, true /*fPurge*/);
+ AssertRC(vrc);
+ }
+ if (pDev->pszProduct)
+ {
+ vrc = USBFilterSetStringExact(aFilter, USBFILTERIDX_PRODUCT_STR, pDev->pszProduct,
+ true /*fMustBePresent*/, true /*fPurge*/);
+ AssertRC(vrc);
+ }
+ if (pDev->pszManufacturer)
+ {
+ vrc = USBFilterSetStringExact(aFilter, USBFILTERIDX_MANUFACTURER_STR, pDev->pszManufacturer,
+ true /*fMustBePresent*/, true /*fPurge*/);
+ AssertRC(vrc);
+ }
+}
+
+HRESULT USBProxyBackend::getName(com::Utf8Str &aName)
+{
+ /* strId is constant during life time, no need to lock */
+ aName = m_strId;
+ return S_OK;
+}
+
+HRESULT USBProxyBackend::getType(com::Utf8Str &aType)
+{
+ aType = Utf8Str::Empty;
+ return S_OK;
+}
+
+/**
+ * Sort a list of USB devices.
+ *
+ * @returns Pointer to the head of the sorted doubly linked list.
+ * @param pDevices Head pointer (can be both singly and doubly linked list).
+ */
+static PUSBDEVICE sortDevices(PUSBDEVICE pDevices)
+{
+ PUSBDEVICE pHead = NULL;
+ PUSBDEVICE pTail = NULL;
+ while (pDevices)
+ {
+ /* unlink head */
+ PUSBDEVICE pDev = pDevices;
+ pDevices = pDev->pNext;
+ if (pDevices)
+ pDevices->pPrev = NULL;
+
+ /* find location. */
+ PUSBDEVICE pCur = pTail;
+ while ( pCur
+ && HostUSBDevice::i_compare(pCur, pDev) > 0)
+ pCur = pCur->pPrev;
+
+ /* insert (after pCur) */
+ pDev->pPrev = pCur;
+ if (pCur)
+ {
+ pDev->pNext = pCur->pNext;
+ pCur->pNext = pDev;
+ if (pDev->pNext)
+ pDev->pNext->pPrev = pDev;
+ else
+ pTail = pDev;
+ }
+ else
+ {
+ pDev->pNext = pHead;
+ if (pHead)
+ pHead->pPrev = pDev;
+ else
+ pTail = pDev;
+ pHead = pDev;
+ }
+ }
+
+ LogFlowFuncLeave();
+ return pHead;
+}
+
+
+/**
+ * Process any relevant changes in the attached USB devices.
+ *
+ * This is called from any available USB proxy backends service thread when they discover
+ * a change.
+ */
+void USBProxyBackend::updateDeviceList(PUSBDEVICE pDevices)
+{
+ LogFlowThisFunc(("\n"));
+
+ pDevices = sortDevices(pDevices);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Compare previous list with the new list of devices
+ * and merge in any changes while notifying Host.
+ */
+ HostUSBDeviceList::iterator it = this->m_llDevices.begin();
+ while ( it != m_llDevices.end()
+ || pDevices)
+ {
+ ComObjPtr<HostUSBDevice> pHostDevice;
+
+ if (it != m_llDevices.end())
+ pHostDevice = *it;
+
+ /*
+ * Assert that the object is still alive (we still reference it in
+ * the collection and we're the only one who calls uninit() on it.
+ */
+ AutoCaller devCaller(pHostDevice.isNull() ? NULL : pHostDevice);
+ AssertComRC(devCaller.rc());
+
+ /*
+ * Lock the device object since we will read/write its
+ * properties. All Host callbacks also imply the object is locked.
+ */
+ AutoWriteLock devLock(pHostDevice.isNull() ? NULL : pHostDevice
+ COMMA_LOCKVAL_SRC_POS);
+
+ /* We should never get devices from other backends here. */
+ Assert(pHostDevice.isNull() || pHostDevice->i_getUsbProxyBackend() == this);
+
+ /*
+ * Compare.
+ */
+ int iDiff;
+ if (pHostDevice.isNull())
+ iDiff = 1;
+ else
+ {
+ if (!pDevices)
+ iDiff = -1;
+ else
+ iDiff = pHostDevice->i_compare(pDevices);
+ }
+ if (!iDiff)
+ {
+ /*
+ * The device still there, update the state and move on. The PUSBDEVICE
+ * structure is eaten by updateDeviceState / HostUSBDevice::updateState().
+ */
+ PUSBDEVICE pCur = pDevices;
+ pDevices = pDevices->pNext;
+ pCur->pPrev = pCur->pNext = NULL;
+
+ devLock.release();
+ alock.release();
+ m_pUsbProxyService->i_updateDeviceState(pHostDevice, pCur, isFakeUpdateRequired());
+ alock.acquire();
+ ++it;
+ }
+ else
+ {
+ if (iDiff > 0)
+ {
+ /*
+ * Head of pDevices was attached.
+ */
+ PUSBDEVICE pNew = pDevices;
+ pDevices = pDevices->pNext;
+ pNew->pPrev = pNew->pNext = NULL;
+
+ ComObjPtr<HostUSBDevice> NewObj;
+ NewObj.createObject();
+ NewObj->init(pNew, this);
+ LogFlowThisFunc(("attached %p {%s} %s / %p:{.idVendor=%#06x, .idProduct=%#06x, .pszProduct=\"%s\", .pszManufacturer=\"%s\"}\n",
+ (HostUSBDevice *)NewObj,
+ NewObj->i_getName().c_str(),
+ NewObj->i_getStateName(),
+ pNew,
+ pNew->idVendor,
+ pNew->idProduct,
+ pNew->pszProduct,
+ pNew->pszManufacturer));
+
+ m_llDevices.insert(it, NewObj);
+
+ devLock.release();
+ alock.release();
+ /* Do any backend specific work. */
+ deviceAdded(NewObj, pNew);
+ m_pUsbProxyService->i_deviceAdded(NewObj, pNew);
+ alock.acquire();
+ }
+ else
+ {
+ /*
+ * Check if the device was actually detached or logically detached
+ * as the result of a re-enumeration.
+ */
+ if (!pHostDevice->i_wasActuallyDetached())
+ ++it;
+ else
+ {
+ it = m_llDevices.erase(it);
+ devLock.release();
+ alock.release();
+ m_pUsbProxyService->i_deviceRemoved(pHostDevice);
+ LogFlowThisFunc(("detached %p {%s}\n",
+ (HostUSBDevice *)pHostDevice,
+ pHostDevice->i_getName().c_str()));
+
+ /* from now on, the object is no more valid,
+ * uninitialize to avoid abuse */
+ devCaller.release();
+ pHostDevice->uninit();
+ alock.acquire();
+ }
+ }
+ }
+ } /* while */
+
+ LogFlowThisFunc(("returns void\n"));
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/USBProxyService.cpp b/src/VBox/Main/src-server/USBProxyService.cpp
new file mode 100644
index 00000000..b072baad
--- /dev/null
+++ b/src/VBox/Main/src-server/USBProxyService.cpp
@@ -0,0 +1,971 @@
+/* $Id: USBProxyService.cpp $ */
+/** @file
+ * VirtualBox USB Proxy Service (base) class.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_USBPROXYBACKEND
+#include "USBProxyService.h"
+#include "HostUSBDeviceImpl.h"
+#include "HostImpl.h"
+#include "MachineImpl.h"
+#include "VirtualBoxImpl.h"
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+#include <VBox/com/array.h>
+#include <iprt/errcore.h>
+#include <iprt/asm.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+/** Pair of a USB proxy backend and the opaque filter data assigned by the backend. */
+typedef std::pair<ComObjPtr<USBProxyBackend> , void *> USBFilterPair;
+/** List of USB filter pairs. */
+typedef std::list<USBFilterPair> USBFilterList;
+
+/**
+ * Data for a USB device filter.
+ */
+struct USBFilterData
+{
+ USBFilterData()
+ : llUsbFilters()
+ { }
+
+ USBFilterList llUsbFilters;
+};
+
+/**
+ * Initialize data members.
+ */
+USBProxyService::USBProxyService(Host *aHost)
+ : mHost(aHost), mDevices(), mBackends()
+{
+ LogFlowThisFunc(("aHost=%p\n", aHost));
+}
+
+
+/**
+ * Stub needed as long as the class isn't virtual
+ */
+HRESULT USBProxyService::init(void)
+{
+# if defined(RT_OS_DARWIN)
+ ComObjPtr<USBProxyBackendDarwin> UsbProxyBackendHost;
+# elif defined(RT_OS_LINUX)
+ ComObjPtr<USBProxyBackendLinux> UsbProxyBackendHost;
+# elif defined(RT_OS_OS2)
+ ComObjPtr<USBProxyBackendOs2> UsbProxyBackendHost;
+# elif defined(RT_OS_SOLARIS)
+ ComObjPtr<USBProxyBackendSolaris> UsbProxyBackendHost;
+# elif defined(RT_OS_WINDOWS)
+ ComObjPtr<USBProxyBackendWindows> UsbProxyBackendHost;
+# elif defined(RT_OS_FREEBSD)
+ ComObjPtr<USBProxyBackendFreeBSD> UsbProxyBackendHost;
+# else
+ ComObjPtr<USBProxyBackend> UsbProxyBackendHost;
+# endif
+ UsbProxyBackendHost.createObject();
+ int vrc = UsbProxyBackendHost->init(this, Utf8Str("host"), Utf8Str(""), false /* fLoadingSettings */);
+ if (RT_FAILURE(vrc))
+ {
+ mLastError = vrc;
+ }
+ else
+ mBackends.push_back(static_cast<ComObjPtr<USBProxyBackend> >(UsbProxyBackendHost));
+
+ return S_OK;
+}
+
+
+/**
+ * Empty destructor.
+ */
+USBProxyService::~USBProxyService()
+{
+ LogFlowThisFunc(("\n"));
+ while (!mBackends.empty())
+ mBackends.pop_front();
+
+ mDevices.clear();
+ mBackends.clear();
+ mHost = NULL;
+}
+
+
+/**
+ * Query if the service is active and working.
+ *
+ * @returns true if the service is up running.
+ * @returns false if the service isn't running.
+ */
+bool USBProxyService::isActive(void)
+{
+ return mBackends.size() > 0;
+}
+
+
+/**
+ * Get last error.
+ * Can be used to check why the proxy !isActive() upon construction.
+ *
+ * @returns VBox status code.
+ */
+int USBProxyService::getLastError(void)
+{
+ return mLastError;
+}
+
+
+/**
+ * We're using the Host object lock.
+ *
+ * This is just a temporary measure until all the USB refactoring is
+ * done, probably... For now it help avoiding deadlocks we don't have
+ * time to fix.
+ *
+ * @returns Lock handle.
+ */
+RWLockHandle *USBProxyService::lockHandle() const
+{
+ return mHost->lockHandle();
+}
+
+
+void *USBProxyService::insertFilter(PCUSBFILTER aFilter)
+{
+ USBFilterData *pFilterData = new USBFilterData();
+
+ for (USBProxyBackendList::iterator it = mBackends.begin();
+ it != mBackends.end();
+ ++it)
+ {
+ ComObjPtr<USBProxyBackend> pUsbProxyBackend = *it;
+ void *pvId = pUsbProxyBackend->insertFilter(aFilter);
+
+ pFilterData->llUsbFilters.push_back(USBFilterPair(pUsbProxyBackend, pvId));
+ }
+
+ return pFilterData;
+}
+
+void USBProxyService::removeFilter(void *aId)
+{
+ USBFilterData *pFilterData = (USBFilterData *)aId;
+
+ for (USBFilterList::iterator it = pFilterData->llUsbFilters.begin();
+ it != pFilterData->llUsbFilters.end();
+ ++it)
+ {
+ ComObjPtr<USBProxyBackend> pUsbProxyBackend = it->first;
+ pUsbProxyBackend->removeFilter(it->second);
+ }
+
+ pFilterData->llUsbFilters.clear();
+ delete pFilterData;
+}
+
+/**
+ * Gets the collection of USB devices, slave of Host::USBDevices.
+ *
+ * This is an interface for the HostImpl::USBDevices property getter.
+ *
+ *
+ * @param aUSBDevices Where to store the pointer to the collection.
+ *
+ * @returns COM status code.
+ *
+ * @remarks The caller must own the write lock of the host object.
+ */
+HRESULT USBProxyService::getDeviceCollection(std::vector<ComPtr<IHostUSBDevice> > &aUSBDevices)
+{
+ AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aUSBDevices.resize(mDevices.size());
+ size_t i = 0;
+ for (HostUSBDeviceList::const_iterator it = mDevices.begin(); it != mDevices.end(); ++it, ++i)
+ aUSBDevices[i] = *it;
+
+ return S_OK;
+}
+
+
+HRESULT USBProxyService::addUSBDeviceSource(const com::Utf8Str &aBackend, const com::Utf8Str &aId, const com::Utf8Str &aAddress,
+ const std::vector<com::Utf8Str> &aPropertyNames, const std::vector<com::Utf8Str> &aPropertyValues)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = createUSBDeviceSource(aBackend, aId, aAddress, aPropertyNames,
+ aPropertyValues, false /* fLoadingSettings */);
+ if (SUCCEEDED(hrc))
+ {
+ alock.release();
+ AutoWriteLock vboxLock(mHost->i_parent() COMMA_LOCKVAL_SRC_POS);
+ return mHost->i_parent()->i_saveSettings();
+ }
+
+ return hrc;
+}
+
+HRESULT USBProxyService::removeUSBDeviceSource(const com::Utf8Str &aId)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (USBProxyBackendList::iterator it = mBackends.begin();
+ it != mBackends.end();
+ ++it)
+ {
+ ComObjPtr<USBProxyBackend> UsbProxyBackend = *it;
+
+ if (aId.equals(UsbProxyBackend->i_getId()))
+ {
+ mBackends.erase(it);
+
+ /*
+ * The proxy backend uninit method will be called when the pointer goes
+ * out of scope.
+ */
+
+ alock.release();
+ AutoWriteLock vboxLock(mHost->i_parent() COMMA_LOCKVAL_SRC_POS);
+ return mHost->i_parent()->i_saveSettings();
+ }
+ }
+
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("The USB device source \"%s\" could not be found"), aId.c_str());
+}
+
+/**
+ * Request capture of a specific device.
+ *
+ * This is in an interface for SessionMachine::CaptureUSBDevice(), which is
+ * an internal worker used by Console::AttachUSBDevice() from the VM process.
+ *
+ * When the request is completed, SessionMachine::onUSBDeviceAttach() will
+ * be called for the given machine object.
+ *
+ *
+ * @param aMachine The machine to attach the device to.
+ * @param aId The UUID of the USB device to capture and attach.
+ * @param aCaptureFilename
+ *
+ * @returns COM status code and error info.
+ *
+ * @remarks This method may operate synchronously as well as asynchronously. In the
+ * former case it will temporarily abandon locks because of IPC.
+ */
+HRESULT USBProxyService::captureDeviceForVM(SessionMachine *aMachine, IN_GUID aId, const com::Utf8Str &aCaptureFilename)
+{
+ ComAssertRet(aMachine, E_INVALIDARG);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Translate the device id into a device object.
+ */
+ ComObjPtr<HostUSBDevice> pHostDevice = findDeviceById(aId);
+ if (pHostDevice.isNull())
+ return setError(E_INVALIDARG,
+ tr("The USB device with UUID {%RTuuid} is not currently attached to the host"), Guid(aId).raw());
+
+ /*
+ * Try to capture the device
+ */
+ alock.release();
+ return pHostDevice->i_requestCaptureForVM(aMachine, true /* aSetError */, aCaptureFilename);
+}
+
+
+/**
+ * Notification from VM process about USB device detaching progress.
+ *
+ * This is in an interface for SessionMachine::DetachUSBDevice(), which is
+ * an internal worker used by Console::DetachUSBDevice() from the VM process.
+ *
+ * @param aMachine The machine which is sending the notification.
+ * @param aId The UUID of the USB device is concerns.
+ * @param aDone \a false for the pre-action notification (necessary
+ * for advancing the device state to avoid confusing
+ * the guest).
+ * \a true for the post-action notification. The device
+ * will be subjected to all filters except those of
+ * of \a Machine.
+ *
+ * @returns COM status code.
+ *
+ * @remarks When \a aDone is \a true this method may end up doing IPC to other
+ * VMs when running filters. In these cases it will temporarily
+ * abandon its locks.
+ */
+HRESULT USBProxyService::detachDeviceFromVM(SessionMachine *aMachine, IN_GUID aId, bool aDone)
+{
+ LogFlowThisFunc(("aMachine=%p{%s} aId={%RTuuid} aDone=%RTbool\n",
+ aMachine,
+ aMachine->i_getName().c_str(),
+ Guid(aId).raw(),
+ aDone));
+
+ // get a list of all running machines while we're outside the lock
+ // (getOpenedMachines requests locks which are incompatible with the lock of the machines list)
+ SessionMachinesList llOpenedMachines;
+ mHost->i_parent()->i_getOpenedMachines(llOpenedMachines);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<HostUSBDevice> pHostDevice = findDeviceById(aId);
+ ComAssertRet(!pHostDevice.isNull(), E_FAIL);
+ AutoWriteLock devLock(pHostDevice COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Work the state machine.
+ */
+ LogFlowThisFunc(("id={%RTuuid} state=%s aDone=%RTbool name={%s}\n",
+ pHostDevice->i_getId().raw(), pHostDevice->i_getStateName(), aDone, pHostDevice->i_getName().c_str()));
+ bool fRunFilters = false;
+ HRESULT hrc = pHostDevice->i_onDetachFromVM(aMachine, aDone, &fRunFilters);
+
+ /*
+ * Run filters if necessary.
+ */
+ if ( SUCCEEDED(hrc)
+ && fRunFilters)
+ {
+ Assert(aDone && pHostDevice->i_getUnistate() == kHostUSBDeviceState_HeldByProxy && pHostDevice->i_getMachine().isNull());
+ devLock.release();
+ alock.release();
+ HRESULT hrc2 = runAllFiltersOnDevice(pHostDevice, llOpenedMachines, aMachine);
+ ComAssertComRC(hrc2);
+ }
+ return hrc;
+}
+
+
+/**
+ * Apply filters for the machine to all eligible USB devices.
+ *
+ * This is in an interface for SessionMachine::CaptureUSBDevice(), which
+ * is an internal worker used by Console::AutoCaptureUSBDevices() from the
+ * VM process at VM startup.
+ *
+ * Matching devices will be attached to the VM and may result IPC back
+ * to the VM process via SessionMachine::onUSBDeviceAttach() depending
+ * on whether the device needs to be captured or not. If capture is
+ * required, SessionMachine::onUSBDeviceAttach() will be called
+ * asynchronously by the USB proxy service thread.
+ *
+ * @param aMachine The machine to capture devices for.
+ *
+ * @returns COM status code, perhaps with error info.
+ *
+ * @remarks Temporarily locks this object, the machine object and some USB
+ * device, and the called methods will lock similar objects.
+ */
+HRESULT USBProxyService::autoCaptureDevicesForVM(SessionMachine *aMachine)
+{
+ LogFlowThisFunc(("aMachine=%p{%s}\n",
+ aMachine,
+ aMachine->i_getName().c_str()));
+
+ /*
+ * Make a copy of the list because we cannot hold the lock protecting it.
+ * (This will not make copies of any HostUSBDevice objects, only reference them.)
+ */
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HostUSBDeviceList ListCopy = mDevices;
+ alock.release();
+
+ for (HostUSBDeviceList::iterator it = ListCopy.begin();
+ it != ListCopy.end();
+ ++it)
+ {
+ ComObjPtr<HostUSBDevice> pHostDevice = *it;
+ AutoReadLock devLock(pHostDevice COMMA_LOCKVAL_SRC_POS);
+ if ( pHostDevice->i_getUnistate() == kHostUSBDeviceState_HeldByProxy
+ || pHostDevice->i_getUnistate() == kHostUSBDeviceState_Unused
+ || pHostDevice->i_getUnistate() == kHostUSBDeviceState_Capturable)
+ {
+ devLock.release();
+ runMachineFilters(aMachine, pHostDevice);
+ }
+ }
+
+ return S_OK;
+}
+
+
+/**
+ * Detach all USB devices currently attached to a VM.
+ *
+ * This is in an interface for SessionMachine::DetachAllUSBDevices(), which
+ * is an internal worker used by Console::powerDown() from the VM process
+ * at VM startup, and SessionMachine::uninit() at VM abend.
+ *
+ * This is, like #detachDeviceFromVM(), normally a two stage journey
+ * where \a aDone indicates where we are. In addition we may be called
+ * to clean up VMs that have abended, in which case there will be no
+ * preparatory call. Filters will be applied to the devices in the final
+ * call with the risk that we have to do some IPC when attaching them
+ * to other VMs.
+ *
+ * @param aMachine The machine to detach devices from.
+ * @param aDone
+ * @param aAbnormal
+ *
+ * @returns COM status code, perhaps with error info.
+ *
+ * @remarks Write locks the host object and may temporarily abandon
+ * its locks to perform IPC.
+ */
+HRESULT USBProxyService::detachAllDevicesFromVM(SessionMachine *aMachine, bool aDone, bool aAbnormal)
+{
+ // get a list of all running machines while we're outside the lock
+ // (getOpenedMachines requests locks which are incompatible with the host object lock)
+ SessionMachinesList llOpenedMachines;
+ mHost->i_parent()->i_getOpenedMachines(llOpenedMachines);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Make a copy of the device list (not the HostUSBDevice objects, just
+ * the list) since we may end up performing IPC and temporarily have
+ * to abandon locks when applying filters.
+ */
+ HostUSBDeviceList ListCopy = mDevices;
+
+ for (HostUSBDeviceList::iterator it = ListCopy.begin();
+ it != ListCopy.end();
+ ++it)
+ {
+ ComObjPtr<HostUSBDevice> pHostDevice = *it;
+ AutoWriteLock devLock(pHostDevice COMMA_LOCKVAL_SRC_POS);
+ if (pHostDevice->i_getMachine() == aMachine)
+ {
+ /*
+ * Same procedure as in detachUSBDevice().
+ */
+ bool fRunFilters = false;
+ HRESULT hrc = pHostDevice->i_onDetachFromVM(aMachine, aDone, &fRunFilters, aAbnormal);
+ if ( SUCCEEDED(hrc)
+ && fRunFilters)
+ {
+ Assert( aDone
+ && pHostDevice->i_getUnistate() == kHostUSBDeviceState_HeldByProxy
+ && pHostDevice->i_getMachine().isNull());
+ devLock.release();
+ alock.release();
+ HRESULT hrc2 = runAllFiltersOnDevice(pHostDevice, llOpenedMachines, aMachine);
+ ComAssertComRC(hrc2);
+ alock.acquire();
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+
+// Internals
+/////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * Loads the given settings and constructs the additional USB device sources.
+ *
+ * @returns COM status code.
+ * @param llUSBDeviceSources The list of additional device sources.
+ */
+HRESULT USBProxyService::i_loadSettings(const settings::USBDeviceSourcesList &llUSBDeviceSources)
+{
+ HRESULT hrc = S_OK;
+
+ for (settings::USBDeviceSourcesList::const_iterator it = llUSBDeviceSources.begin();
+ it != llUSBDeviceSources.end() && SUCCEEDED(hrc);
+ ++it)
+ {
+ std::vector<com::Utf8Str> vecPropNames, vecPropValues;
+ const settings::USBDeviceSource &src = *it;
+ hrc = createUSBDeviceSource(src.strBackend, src.strName, src.strAddress,
+ vecPropNames, vecPropValues, true /* fLoadingSettings */);
+ }
+
+ return hrc;
+}
+
+/**
+ * Saves the additional device sources in the given settings.
+ *
+ * @returns COM status code.
+ * @param llUSBDeviceSources The list of additional device sources.
+ */
+HRESULT USBProxyService::i_saveSettings(settings::USBDeviceSourcesList &llUSBDeviceSources)
+{
+ for (USBProxyBackendList::iterator it = mBackends.begin();
+ it != mBackends.end();
+ ++it)
+ {
+ USBProxyBackend *pUsbProxyBackend = *it;
+
+ /* Host backends are not saved as they are always created during startup. */
+ if (!pUsbProxyBackend->i_getBackend().equals("host"))
+ {
+ settings::USBDeviceSource src;
+
+ src.strBackend = pUsbProxyBackend->i_getBackend();
+ src.strName = pUsbProxyBackend->i_getId();
+ src.strAddress = pUsbProxyBackend->i_getAddress();
+
+ llUSBDeviceSources.push_back(src);
+ }
+ }
+
+ return S_OK;
+}
+
+/**
+ * Performs the required actions when a device has been added.
+ *
+ * This means things like running filters and subsequent capturing and
+ * VM attaching. This may result in IPC and temporary lock abandonment.
+ *
+ * @param aDevice The device in question.
+ * @param pDev The USB device structure.
+ */
+void USBProxyService::i_deviceAdded(ComObjPtr<HostUSBDevice> &aDevice,
+ PUSBDEVICE pDev)
+{
+ /*
+ * Validate preconditions.
+ */
+ AssertReturnVoid(!isWriteLockOnCurrentThread());
+ AssertReturnVoid(!aDevice->isWriteLockOnCurrentThread());
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%p name={%s} state=%s id={%RTuuid}\n",
+ (HostUSBDevice *)aDevice,
+ aDevice->i_getName().c_str(),
+ aDevice->i_getStateName(),
+ aDevice->i_getId().raw()));
+
+ /* Add to our list. */
+ HostUSBDeviceList::iterator it = mDevices.begin();
+ while (it != mDevices.end())
+ {
+ ComObjPtr<HostUSBDevice> pHostDevice = *it;
+
+ /* Assert that the object is still alive. */
+ AutoCaller devCaller(pHostDevice);
+ AssertComRC(devCaller.rc());
+
+ AutoWriteLock curLock(pHostDevice COMMA_LOCKVAL_SRC_POS);
+ if ( pHostDevice->i_getUsbProxyBackend() == aDevice->i_getUsbProxyBackend()
+ && pHostDevice->i_compare(pDev) < 0)
+ break;
+
+ ++it;
+ }
+
+ mDevices.insert(it, aDevice);
+
+ /*
+ * Run filters on the device.
+ */
+ if (aDevice->i_isCapturableOrHeld())
+ {
+ devLock.release();
+ alock.release();
+ SessionMachinesList llOpenedMachines;
+ mHost->i_parent()->i_getOpenedMachines(llOpenedMachines);
+ HRESULT rc = runAllFiltersOnDevice(aDevice, llOpenedMachines, NULL /* aIgnoreMachine */);
+ AssertComRC(rc);
+ }
+}
+
+/**
+ * Remove device notification hook for the USB proxy service.
+ *
+ * @param aDevice The device in question.
+ */
+void USBProxyService::i_deviceRemoved(ComObjPtr<HostUSBDevice> &aDevice)
+{
+ /*
+ * Validate preconditions.
+ */
+ AssertReturnVoid(!isWriteLockOnCurrentThread());
+ AssertReturnVoid(!aDevice->isWriteLockOnCurrentThread());
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%p name={%s} state=%s id={%RTuuid}\n",
+ (HostUSBDevice *)aDevice,
+ aDevice->i_getName().c_str(),
+ aDevice->i_getStateName(),
+ aDevice->i_getId().raw()));
+
+ mDevices.remove(aDevice);
+
+ /*
+ * Detach the device from any machine currently using it,
+ * reset all data and uninitialize the device object.
+ */
+ devLock.release();
+ alock.release();
+ aDevice->i_onPhysicalDetached();
+}
+
+/**
+ * Updates the device state.
+ *
+ * This is responsible for calling HostUSBDevice::updateState().
+ *
+ * @returns true if there is a state change.
+ * @param aDevice The device in question.
+ * @param aUSBDevice The USB device structure for the last enumeration.
+ * @param fFakeUpdate Flag whether to fake updating state.
+ */
+void USBProxyService::i_updateDeviceState(ComObjPtr<HostUSBDevice> &aDevice, PUSBDEVICE aUSBDevice, bool fFakeUpdate)
+{
+ AssertReturnVoid(aDevice);
+ AssertReturnVoid(!aDevice->isWriteLockOnCurrentThread());
+
+ bool fRunFilters = false;
+ SessionMachine *pIgnoreMachine = NULL;
+ bool fDevChanged = false;
+ if (fFakeUpdate)
+ fDevChanged = aDevice->i_updateStateFake(aUSBDevice, &fRunFilters, &pIgnoreMachine);
+ else
+ fDevChanged = aDevice->i_updateState(aUSBDevice, &fRunFilters, &pIgnoreMachine);
+
+ if (fDevChanged)
+ deviceChanged(aDevice, fRunFilters, pIgnoreMachine);
+}
+
+
+/**
+ * Handle a device which state changed in some significant way.
+ *
+ * This means things like running filters and subsequent capturing and
+ * VM attaching. This may result in IPC and temporary lock abandonment.
+ *
+ * @param aDevice The device.
+ * @param fRunFilters Flag whether to run filters.
+ * @param aIgnoreMachine Machine to ignore when running filters.
+ */
+void USBProxyService::deviceChanged(ComObjPtr<HostUSBDevice> &aDevice, bool fRunFilters,
+ SessionMachine *aIgnoreMachine)
+{
+ /*
+ * Validate preconditions.
+ */
+ AssertReturnVoid(!isWriteLockOnCurrentThread());
+ AssertReturnVoid(!aDevice->isWriteLockOnCurrentThread());
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%p name={%s} state=%s id={%RTuuid} aRunFilters=%RTbool aIgnoreMachine=%p\n",
+ (HostUSBDevice *)aDevice,
+ aDevice->i_getName().c_str(),
+ aDevice->i_getStateName(),
+ aDevice->i_getId().raw(),
+ fRunFilters,
+ aIgnoreMachine));
+ devLock.release();
+
+ /*
+ * Run filters if requested to do so.
+ */
+ if (fRunFilters)
+ {
+ SessionMachinesList llOpenedMachines;
+ mHost->i_parent()->i_getOpenedMachines(llOpenedMachines);
+ HRESULT rc = runAllFiltersOnDevice(aDevice, llOpenedMachines, aIgnoreMachine);
+ AssertComRC(rc);
+ }
+}
+
+
+/**
+ * Runs all the filters on the specified device.
+ *
+ * All filters mean global and active VM, with the exception of those
+ * belonging to \a aMachine. If a global ignore filter matched or if
+ * none of the filters matched, the device will be released back to
+ * the host.
+ *
+ * The device calling us here will be in the HeldByProxy, Unused, or
+ * Capturable state. The caller is aware that locks held might have
+ * to be abandond because of IPC and that the device might be in
+ * almost any state upon return.
+ *
+ *
+ * @returns COM status code (only parameter & state checks will fail).
+ * @param aDevice The USB device to apply filters to.
+ * @param llOpenedMachines The list of opened machines.
+ * @param aIgnoreMachine The machine to ignore filters from (we've just
+ * detached the device from this machine).
+ *
+ * @note The caller is expected to own no locks.
+ */
+HRESULT USBProxyService::runAllFiltersOnDevice(ComObjPtr<HostUSBDevice> &aDevice,
+ SessionMachinesList &llOpenedMachines,
+ SessionMachine *aIgnoreMachine)
+{
+ LogFlowThisFunc(("{%s} ignoring=%p\n", aDevice->i_getName().c_str(), aIgnoreMachine));
+
+ /*
+ * Verify preconditions.
+ */
+ AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), E_FAIL);
+
+ /*
+ * Get the lists we'll iterate.
+ */
+ Host::USBDeviceFilterList globalFilters;
+ mHost->i_getUSBFilters(&globalFilters);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ AssertMsgReturn(aDevice->i_isCapturableOrHeld(), ("{%s} %s\n", aDevice->i_getName().c_str(),
+ aDevice->i_getStateName()), E_FAIL);
+
+ /*
+ * Run global filters filters first.
+ */
+ bool fHoldIt = false;
+ for (Host::USBDeviceFilterList::const_iterator it = globalFilters.begin();
+ it != globalFilters.end();
+ ++it)
+ {
+ AutoWriteLock filterLock(*it COMMA_LOCKVAL_SRC_POS);
+ const HostUSBDeviceFilter::BackupableUSBDeviceFilterData &data = (*it)->i_getData();
+ if (aDevice->i_isMatch(data))
+ {
+ USBDeviceFilterAction_T action = USBDeviceFilterAction_Null;
+ (*it)->COMGETTER(Action)(&action);
+ if (action == USBDeviceFilterAction_Ignore)
+ {
+ /*
+ * Release the device to the host and we're done.
+ */
+ filterLock.release();
+ devLock.release();
+ alock.release();
+ aDevice->i_requestReleaseToHost();
+ return S_OK;
+ }
+ if (action == USBDeviceFilterAction_Hold)
+ {
+ /*
+ * A device held by the proxy needs to be subjected
+ * to the machine filters.
+ */
+ fHoldIt = true;
+ break;
+ }
+ AssertMsgFailed(("action=%d\n", action));
+ }
+ }
+ globalFilters.clear();
+
+ /*
+ * Run the per-machine filters.
+ */
+ for (SessionMachinesList::const_iterator it = llOpenedMachines.begin();
+ it != llOpenedMachines.end();
+ ++it)
+ {
+ ComObjPtr<SessionMachine> pMachine = *it;
+
+ /* Skip the machine the device was just detached from. */
+ if ( aIgnoreMachine
+ && pMachine == aIgnoreMachine)
+ continue;
+
+ /* runMachineFilters takes care of checking the machine state. */
+ devLock.release();
+ alock.release();
+ if (runMachineFilters(pMachine, aDevice))
+ {
+ LogFlowThisFunc(("{%s} attached to %p\n", aDevice->i_getName().c_str(), (void *)pMachine));
+ return S_OK;
+ }
+ alock.acquire();
+ devLock.acquire();
+ }
+
+ /*
+ * No matching machine, so request hold or release depending
+ * on global filter match.
+ */
+ devLock.release();
+ alock.release();
+ if (fHoldIt)
+ aDevice->i_requestHold();
+ else
+ aDevice->i_requestReleaseToHost();
+ return S_OK;
+}
+
+
+/**
+ * Runs the USB filters of the machine on the device.
+ *
+ * If a match is found we will request capture for VM. This may cause
+ * us to temporary abandon locks while doing IPC.
+ *
+ * @param aMachine Machine whose filters are to be run.
+ * @param aDevice The USB device in question.
+ * @returns @c true if the device has been or is being attached to the VM, @c false otherwise.
+ *
+ * @note Locks several objects temporarily for reading or writing.
+ */
+bool USBProxyService::runMachineFilters(SessionMachine *aMachine, ComObjPtr<HostUSBDevice> &aDevice)
+{
+ LogFlowThisFunc(("{%s} aMachine=%p \n", aDevice->i_getName().c_str(), aMachine));
+
+ /*
+ * Validate preconditions.
+ */
+ AssertReturn(aMachine, false);
+ AssertReturn(!isWriteLockOnCurrentThread(), false);
+ AssertReturn(!aMachine->isWriteLockOnCurrentThread(), false);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), false);
+ /* Let HostUSBDevice::requestCaptureToVM() validate the state. */
+
+ /*
+ * Do the job.
+ */
+ ULONG ulMaskedIfs;
+ if (aMachine->i_hasMatchingUSBFilter(aDevice, &ulMaskedIfs))
+ {
+ /* try to capture the device */
+ HRESULT hrc = aDevice->i_requestCaptureForVM(aMachine, false /* aSetError */, Utf8Str(), ulMaskedIfs);
+ return SUCCEEDED(hrc)
+ || hrc == E_UNEXPECTED /* bad device state, give up */;
+ }
+
+ return false;
+}
+
+
+/**
+ * Searches the list of devices (mDevices) for the given device.
+ *
+ *
+ * @returns Smart pointer to the device on success, NULL otherwise.
+ * @param aId The UUID of the device we're looking for.
+ */
+ComObjPtr<HostUSBDevice> USBProxyService::findDeviceById(IN_GUID aId)
+{
+ Guid Id(aId);
+ ComObjPtr<HostUSBDevice> Dev;
+ for (HostUSBDeviceList::iterator it = mDevices.begin();
+ it != mDevices.end();
+ ++it)
+ if ((*it)->i_getId() == Id)
+ {
+ Dev = (*it);
+ break;
+ }
+
+ return Dev;
+}
+
+/**
+ * Creates a new USB device source.
+ *
+ * @returns COM status code.
+ * @param aBackend The backend to use.
+ * @param aId The ID of the source.
+ * @param aAddress The backend specific address.
+ * @param aPropertyNames Vector of optional property keys the backend supports.
+ * @param aPropertyValues Vector of optional property values the backend supports.
+ * @param fLoadingSettings Flag whether the USB device source is created while the
+ * settings are loaded or through the Main API.
+ */
+HRESULT USBProxyService::createUSBDeviceSource(const com::Utf8Str &aBackend, const com::Utf8Str &aId,
+ const com::Utf8Str &aAddress, const std::vector<com::Utf8Str> &aPropertyNames,
+ const std::vector<com::Utf8Str> &aPropertyValues,
+ bool fLoadingSettings)
+{
+ HRESULT hrc = S_OK;
+
+ AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
+
+ /** @todo */
+ NOREF(aPropertyNames);
+ NOREF(aPropertyValues);
+
+ /* Check whether the ID is used first. */
+ for (USBProxyBackendList::iterator it = mBackends.begin();
+ it != mBackends.end();
+ ++it)
+ {
+ USBProxyBackend *pUsbProxyBackend = *it;
+
+ if (aId.equals(pUsbProxyBackend->i_getId()))
+ return setError(VBOX_E_OBJECT_IN_USE,
+ tr("The USB device source \"%s\" exists already"), aId.c_str());
+ }
+
+ /* Create appropriate proxy backend. */
+ if (aBackend.equalsIgnoreCase("USBIP"))
+ {
+ ComObjPtr<USBProxyBackendUsbIp> UsbProxyBackend;
+
+ UsbProxyBackend.createObject();
+ int vrc = UsbProxyBackend->init(this, aId, aAddress, fLoadingSettings);
+ if (RT_FAILURE(vrc))
+ hrc = setError(E_FAIL,
+ tr("Creating the USB device source \"%s\" using backend \"%s\" failed with %Rrc"),
+ aId.c_str(), aBackend.c_str(), vrc);
+ else
+ mBackends.push_back(static_cast<ComObjPtr<USBProxyBackend> >(UsbProxyBackend));
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("The USB backend \"%s\" is not supported"), aBackend.c_str());
+
+ return hrc;
+}
+
+/*static*/
+HRESULT USBProxyService::setError(HRESULT aResultCode, const char *aText, ...)
+{
+ va_list va;
+ va_start(va, aText);
+ HRESULT rc = VirtualBoxBase::setErrorInternalV(aResultCode,
+ COM_IIDOF(IHost),
+ "USBProxyService",
+ aText, va,
+ false /* aWarning*/,
+ true /* aLogIt*/);
+ va_end(va);
+ return rc;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/UefiVariableStoreImpl.cpp b/src/VBox/Main/src-server/UefiVariableStoreImpl.cpp
new file mode 100644
index 00000000..6e50151e
--- /dev/null
+++ b/src/VBox/Main/src-server/UefiVariableStoreImpl.cpp
@@ -0,0 +1,960 @@
+/* $Id: UefiVariableStoreImpl.cpp $ */
+/** @file
+ * VirtualBox COM NVRAM store class implementation
+ */
+
+/*
+ * Copyright (C) 2021-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_UEFIVARIABLESTORE
+#include "LoggingNew.h"
+
+#include "UefiVariableStoreImpl.h"
+#include "NvramStoreImpl.h"
+#include "MachineImpl.h"
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+
+#include "TrustAnchorsAndCerts.h"
+
+#include <VBox/com/array.h>
+
+#include <iprt/cpp/utils.h>
+#include <iprt/efi.h>
+#include <iprt/file.h>
+#include <iprt/vfs.h>
+
+#include <iprt/formats/efi-varstore.h>
+#include <iprt/formats/efi-signature.h>
+
+// defines
+////////////////////////////////////////////////////////////////////////////////
+
+// globals
+////////////////////////////////////////////////////////////////////////////////
+
+/////////////////////////////////////////////////////////////////////////////
+// UefiVariableStore::Data structure
+/////////////////////////////////////////////////////////////////////////////
+
+struct UefiVariableStore::Data
+{
+ Data()
+ : pParent(NULL),
+ pMachine(NULL),
+ hVfsUefiVarStore(NIL_RTVFS)
+ { }
+
+ /** The NVRAM store owning this UEFI variable store intstance. */
+ NvramStore * const pParent;
+ /** The machine this UEFI variable store belongs to. */
+ Machine * const pMachine;
+ /** VFS handle to the UEFI variable store. */
+ RTVFS hVfsUefiVarStore;
+};
+
+// constructor / destructor
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(UefiVariableStore)
+
+HRESULT UefiVariableStore::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void UefiVariableStore::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the UEFI variable store object.
+ *
+ * @returns COM result indicator.
+ * @param aParent The NVRAM store owning the UEFI NVRAM content.
+ * @param pMachine
+ */
+HRESULT UefiVariableStore::init(NvramStore *aParent, Machine *pMachine)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data();
+
+ /* share the parent weakly */
+ unconst(m->pParent) = aParent;
+ unconst(m->pMachine) = pMachine;
+ m->hVfsUefiVarStore = NIL_RTVFS;
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void UefiVariableStore::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ Assert(m->hVfsUefiVarStore == NIL_RTVFS);
+
+ unconst(m->pParent) = NULL;
+ unconst(m->pMachine) = NULL;
+
+ delete m;
+ m = NULL;
+
+ LogFlowThisFuncLeave();
+}
+
+
+HRESULT UefiVariableStore::getSecureBootEnabled(BOOL *pfEnabled)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ HRESULT hrc = i_retainUefiVariableStore(true /*fReadonly*/);
+ if (FAILED(hrc)) return hrc;
+
+ AutoReadLock rlock(this COMMA_LOCKVAL_SRC_POS);
+
+ uint64_t cbVar = 0;
+ int vrc = i_uefiVarStoreQueryVarSz("PK", &cbVar);
+ if (RT_SUCCESS(vrc))
+ {
+ *pfEnabled = TRUE;
+
+ /* Check the SecureBootEnable variable for the override. */
+ vrc = i_uefiVarStoreQueryVarSz("SecureBootEnable", &cbVar);
+ if (RT_SUCCESS(vrc))
+ {
+ if (cbVar == sizeof(uint8_t))
+ {
+ uint8_t bVar = 0;
+ hrc = i_uefiVarStoreQueryVar("SecureBootEnable", &bVar, sizeof(bVar));
+ if (SUCCEEDED(hrc))
+ *pfEnabled = bVar == 0x0 ? FALSE : TRUE;
+ }
+ else
+ hrc = setError(E_FAIL, tr("The 'SecureBootEnable' variable size is bogus (expected 1, got %llu)"), cbVar);
+ }
+ else if (vrc != VERR_FILE_NOT_FOUND)
+ hrc = setError(E_FAIL, tr("Failed to query the 'SecureBootEnable' variable size: %Rrc"), vrc);
+ }
+ else if (vrc == VERR_FILE_NOT_FOUND) /* No platform key means no secure boot. */
+ *pfEnabled = FALSE;
+ else
+ hrc = setError(E_FAIL, tr("Failed to query the platform key variable size: %Rrc"), vrc);
+
+ i_releaseUefiVariableStore();
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::setSecureBootEnabled(BOOL fEnabled)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ HRESULT hrc = i_retainUefiVariableStore(false /*fReadonly*/);
+ if (FAILED(hrc)) return hrc;
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ EFI_GUID GuidSecureBootEnable = EFI_SECURE_BOOT_ENABLE_DISABLE_GUID;
+ uint64_t cbVar = 0;
+ int vrc = i_uefiVarStoreQueryVarSz("PK", &cbVar);
+ if (RT_SUCCESS(vrc))
+ {
+ uint8_t bVar = fEnabled ? 0x1 : 0x0;
+ hrc = i_uefiVarStoreSetVar(&GuidSecureBootEnable, "SecureBootEnable",
+ EFI_VAR_HEADER_ATTR_NON_VOLATILE
+ | EFI_VAR_HEADER_ATTR_BOOTSERVICE_ACCESS
+ | EFI_VAR_HEADER_ATTR_RUNTIME_ACCESS,
+ &bVar, sizeof(bVar));
+ }
+ else if (vrc == VERR_FILE_NOT_FOUND) /* No platform key means no secure boot support. */
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("Secure boot is not available because the platform key (PK) is not enrolled"));
+ else
+ hrc = setError(E_FAIL, tr("Failed to query the platform key variable size: %Rrc"), vrc);
+
+ i_releaseUefiVariableStore();
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::addVariable(const com::Utf8Str &aName, const com::Guid &aOwnerUuid,
+ const std::vector<UefiVariableAttributes_T> &aAttributes,
+ const std::vector<BYTE> &aData)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ HRESULT hrc = i_retainUefiVariableStore(false /*fReadonly*/);
+ if (FAILED(hrc)) return hrc;
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ uint32_t fAttr = i_uefiVarAttrToMask(aAttributes);
+ EFI_GUID OwnerGuid;
+ RTEfiGuidFromUuid(&OwnerGuid, aOwnerUuid.raw());
+ hrc = i_uefiVarStoreSetVar(&OwnerGuid, aName.c_str(), fAttr, &aData.front(), aData.size());
+
+ i_releaseUefiVariableStore();
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::deleteVariable(const com::Utf8Str &aName, const com::Guid &aOwnerUuid)
+{
+ RT_NOREF(aOwnerUuid);
+
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ HRESULT hrc = i_retainUefiVariableStore(false /*fReadonly*/);
+ if (FAILED(hrc)) return hrc;
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ char szVarPath[_1K];
+ ssize_t cch = RTStrPrintf2(szVarPath, sizeof(szVarPath), "/raw/%s", aName.c_str());
+ if (cch > 0)
+ {
+ RTVFSDIR hVfsDirRoot = NIL_RTVFSDIR;
+ int vrc = RTVfsOpenRoot(m->hVfsUefiVarStore, &hVfsDirRoot);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsDirRemoveDir(hVfsDirRoot, szVarPath, 0 /*fFlags*/);
+ RTVfsDirRelease(hVfsDirRoot);
+ if (RT_FAILURE(vrc))
+ hrc = setError(VBOX_E_IPRT_ERROR, tr("Failed to remove variable '%s' (%Rrc)"), aName.c_str(), vrc);
+ }
+ else
+ hrc = setError(VBOX_E_IPRT_ERROR, tr("Failed to open the variable store root (%Rrc)"), vrc);
+ }
+ else
+ hrc = setError(E_FAIL, tr("The variable name is too long"));
+
+ i_releaseUefiVariableStore();
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::changeVariable(const com::Utf8Str &aName, const std::vector<BYTE> &aData)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ HRESULT hrc = i_retainUefiVariableStore(false /*fReadonly*/);
+ if (FAILED(hrc)) return hrc;
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ hrc = i_uefiVarStoreOpenVar(aName.c_str(), &hVfsFile);
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = RTVfsFileSetSize(hVfsFile, aData.size(), RTVFSFILE_SIZE_F_NORMAL);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsFileWriteAt(hVfsFile, 0 /*off*/, &aData.front(), aData.size(), NULL /*pcbWritten*/);
+ if (RT_FAILURE(vrc))
+ hrc = setError(VBOX_E_IPRT_ERROR, tr("Failed to data for variable '%s' (%Rrc)"), aName.c_str(), vrc);
+ }
+ else
+ hrc = setError(VBOX_E_IPRT_ERROR, tr("Failed to allocate space for the variable '%s' (%Rrc)"), aName.c_str(), vrc);
+
+ RTVfsFileRelease(hVfsFile);
+ }
+
+ i_releaseUefiVariableStore();
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::queryVariableByName(const com::Utf8Str &aName, com::Guid &aOwnerUuid,
+ std::vector<UefiVariableAttributes_T> &aAttributes,
+ std::vector<BYTE> &aData)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ HRESULT hrc = i_retainUefiVariableStore(true /*fReadonly*/);
+ if (FAILED(hrc)) return hrc;
+
+ AutoReadLock rlock(this COMMA_LOCKVAL_SRC_POS);
+
+ uint32_t fAttr;
+ int vrc = i_uefiVarStoreQueryVarAttr(aName.c_str(), &fAttr);
+ if (RT_SUCCESS(vrc))
+ {
+ RTUUID OwnerUuid;
+ vrc = i_uefiVarStoreQueryVarOwnerUuid(aName.c_str(), &OwnerUuid);
+ if (RT_SUCCESS(vrc))
+ {
+ uint64_t cbVar = 0;
+ vrc = i_uefiVarStoreQueryVarSz(aName.c_str(), &cbVar);
+ if (RT_SUCCESS(vrc))
+ {
+ aData.resize(cbVar);
+ hrc = i_uefiVarStoreQueryVar(aName.c_str(), &aData.front(), aData.size());
+ if (SUCCEEDED(hrc))
+ {
+ aOwnerUuid = com::Guid(OwnerUuid);
+ i_uefiAttrMaskToVec(fAttr, aAttributes);
+ }
+ }
+ else
+ hrc = setError(VBOX_E_IPRT_ERROR, tr("Failed to query the size of variable '%s': %Rrc"), aName.c_str(), vrc);
+ }
+ else
+ hrc = setError(VBOX_E_IPRT_ERROR, tr("Failed to query the owner UUID of variable '%s': %Rrc"), aName.c_str(), vrc);
+ }
+ else
+ hrc = setError(VBOX_E_IPRT_ERROR, tr("Failed to query the attributes of variable '%s': %Rrc"), aName.c_str(), vrc);
+
+ i_releaseUefiVariableStore();
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::queryVariables(std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Guid> &aOwnerUuids)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ HRESULT hrc = i_retainUefiVariableStore(true /*fReadonly*/);
+ if (FAILED(hrc)) return hrc;
+
+ AutoReadLock rlock(this COMMA_LOCKVAL_SRC_POS);
+
+ RTVFSDIR hVfsDir = NIL_RTVFSDIR;
+ int vrc = RTVfsDirOpen(m->hVfsUefiVarStore, "by-name", 0 /*fFlags*/, &hVfsDir);
+ if (RT_SUCCESS(vrc))
+ {
+ RTDIRENTRYEX DirEntry;
+
+ vrc = RTVfsDirReadEx(hVfsDir, &DirEntry, NULL, RTFSOBJATTRADD_NOTHING);
+ for (;;)
+ {
+ RTUUID OwnerUuid;
+ vrc = i_uefiVarStoreQueryVarOwnerUuid(DirEntry.szName, &OwnerUuid);
+ if (RT_FAILURE(vrc))
+ break;
+
+ aNames.push_back(Utf8Str(DirEntry.szName));
+ aOwnerUuids.push_back(com::Guid(OwnerUuid));
+
+ vrc = RTVfsDirReadEx(hVfsDir, &DirEntry, NULL, RTFSOBJATTRADD_NOTHING);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ if (vrc == VERR_NO_MORE_FILES)
+ vrc = VINF_SUCCESS;
+
+ RTVfsDirRelease(hVfsDir);
+ }
+
+ i_releaseUefiVariableStore();
+
+ if (RT_FAILURE(vrc))
+ return setError(VBOX_E_IPRT_ERROR, tr("Failed to query the variables: %Rrc"), vrc);
+
+ return S_OK;
+}
+
+
+HRESULT UefiVariableStore::enrollOraclePlatformKey(void)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ HRESULT hrc = i_retainUefiVariableStore(false /*fReadonly*/);
+ if (FAILED(hrc)) return hrc;
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ EFI_GUID GuidGlobalVar = EFI_GLOBAL_VARIABLE_GUID;
+
+ /** @todo This conversion from EFI GUID -> IPRT UUID -> Com GUID is nuts... */
+ EFI_GUID GuidOwnerVBox = EFI_SIGNATURE_OWNER_GUID_VBOX;
+ RTUUID UuidVBox;
+ RTEfiGuidToUuid(&UuidVBox, &GuidOwnerVBox);
+
+ const com::Guid GuidVBox(UuidVBox);
+
+ hrc = i_uefiVarStoreAddSignatureToDb(&GuidGlobalVar, "PK", g_abUefiOracleDefPk, g_cbUefiOracleDefPk,
+ GuidVBox, SignatureType_X509);
+
+ i_releaseUefiVariableStore();
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::enrollPlatformKey(const std::vector<BYTE> &aData, const com::Guid &aOwnerUuid)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ HRESULT hrc = i_retainUefiVariableStore(false /*fReadonly*/);
+ if (FAILED(hrc)) return hrc;
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ EFI_GUID GuidGlobalVar = EFI_GLOBAL_VARIABLE_GUID;
+ hrc = i_uefiVarStoreAddSignatureToDbVec(&GuidGlobalVar, "PK", aData, aOwnerUuid, SignatureType_X509);
+
+ i_releaseUefiVariableStore();
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::addKek(const std::vector<BYTE> &aData, const com::Guid &aOwnerUuid, SignatureType_T enmSignatureType)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ HRESULT hrc = i_retainUefiVariableStore(false /*fReadonly*/);
+ if (FAILED(hrc)) return hrc;
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ EFI_GUID GuidGlobalVar = EFI_GLOBAL_VARIABLE_GUID;
+ hrc = i_uefiVarStoreAddSignatureToDbVec(&GuidGlobalVar, "KEK", aData, aOwnerUuid, enmSignatureType);
+
+ i_releaseUefiVariableStore();
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::addSignatureToDb(const std::vector<BYTE> &aData, const com::Guid &aOwnerUuid, SignatureType_T enmSignatureType)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ HRESULT hrc = i_retainUefiVariableStore(false /*fReadonly*/);
+ if (FAILED(hrc)) return hrc;
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ EFI_GUID GuidSecurityDb = EFI_GLOBAL_VARIABLE_GUID;
+ hrc = i_uefiVarStoreAddSignatureToDbVec(&GuidSecurityDb, "db", aData, aOwnerUuid, enmSignatureType);
+
+ i_releaseUefiVariableStore();
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::addSignatureToDbx(const std::vector<BYTE> &aData, const com::Guid &aOwnerUuid, SignatureType_T enmSignatureType)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ HRESULT hrc = i_retainUefiVariableStore(false /*fReadonly*/);
+ if (FAILED(hrc)) return hrc;
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ EFI_GUID GuidSecurityDb = EFI_IMAGE_SECURITY_DATABASE_GUID;
+ hrc = i_uefiVarStoreAddSignatureToDbVec(&GuidSecurityDb, "dbx", aData, aOwnerUuid, enmSignatureType);
+
+ i_releaseUefiVariableStore();
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::enrollDefaultMsSignatures(void)
+{
+ AutoMutableStateDependency adep(m->pMachine);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ HRESULT hrc = i_retainUefiVariableStore(false /*fReadonly*/);
+ if (FAILED(hrc)) return hrc;
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ EFI_GUID EfiGuidSecurityDb = EFI_IMAGE_SECURITY_DATABASE_GUID;
+ EFI_GUID EfiGuidGlobalVar = EFI_GLOBAL_VARIABLE_GUID;
+
+ /** @todo This conversion from EFI GUID -> IPRT UUID -> Com GUID is nuts... */
+ EFI_GUID EfiGuidMs = EFI_SIGNATURE_OWNER_GUID_MICROSOFT;
+ RTUUID UuidMs;
+ RTEfiGuidToUuid(&UuidMs, &EfiGuidMs);
+
+ const com::Guid GuidMs(UuidMs);
+
+ hrc = i_uefiVarStoreAddSignatureToDb(&EfiGuidGlobalVar, "KEK", g_abUefiMicrosoftKek, g_cbUefiMicrosoftKek,
+ GuidMs, SignatureType_X509);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = i_uefiVarStoreAddSignatureToDb(&EfiGuidSecurityDb, "db", g_abUefiMicrosoftCa, g_cbUefiMicrosoftCa,
+ GuidMs, SignatureType_X509);
+ if (SUCCEEDED(hrc))
+ hrc = i_uefiVarStoreAddSignatureToDb(&EfiGuidSecurityDb, "db", g_abUefiMicrosoftProPca, g_cbUefiMicrosoftProPca,
+ GuidMs, SignatureType_X509);
+ }
+
+ i_releaseUefiVariableStore();
+ return hrc;
+}
+
+
+/**
+ * Sets the given attributes for the given EFI variable store variable.
+ *
+ * @returns IPRT status code.
+ * @param pszVar The variable to set the attributes for.
+ * @param fAttr The attributes to set, see EFI_VAR_HEADER_ATTR_XXX.
+ */
+int UefiVariableStore::i_uefiVarStoreSetVarAttr(const char *pszVar, uint32_t fAttr)
+{
+ char szVarPath[_1K];
+ ssize_t cch = RTStrPrintf2(szVarPath, sizeof(szVarPath), "/raw/%s/attr", pszVar);
+ Assert(cch > 0); RT_NOREF(cch);
+
+ RTVFSFILE hVfsFileAttr = NIL_RTVFSFILE;
+ int vrc = RTVfsFileOpen(m->hVfsUefiVarStore, szVarPath,
+ RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ &hVfsFileAttr);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t fAttrLe = RT_H2LE_U32(fAttr);
+ vrc = RTVfsFileWrite(hVfsFileAttr, &fAttrLe, sizeof(fAttrLe), NULL /*pcbWritten*/);
+ RTVfsFileRelease(hVfsFileAttr);
+ }
+
+ return vrc;
+}
+
+
+/**
+ * Queries the attributes for the given EFI variable store variable.
+ *
+ * @returns IPRT status code.
+ * @param pszVar The variable to query the attributes for.
+ * @param pfAttr Where to store the attributes on success.
+ */
+int UefiVariableStore::i_uefiVarStoreQueryVarAttr(const char *pszVar, uint32_t *pfAttr)
+{
+ char szVarPath[_1K];
+ ssize_t cch = RTStrPrintf2(szVarPath, sizeof(szVarPath), "/raw/%s/attr", pszVar);
+ Assert(cch > 0); RT_NOREF(cch);
+
+ RTVFSFILE hVfsFileAttr = NIL_RTVFSFILE;
+ int vrc = RTVfsFileOpen(m->hVfsUefiVarStore, szVarPath,
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ &hVfsFileAttr);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t fAttrLe = 0;
+ vrc = RTVfsFileRead(hVfsFileAttr, &fAttrLe, sizeof(fAttrLe), NULL /*pcbRead*/);
+ RTVfsFileRelease(hVfsFileAttr);
+ if (RT_SUCCESS(vrc))
+ *pfAttr = RT_LE2H_U32(fAttrLe);
+ }
+
+ return vrc;
+}
+
+
+/**
+ * Queries the data size for the given variable.
+ *
+ * @returns IPRT status code.
+ * @param pszVar The variable to query the size for.
+ * @param pcbVar Where to store the size of the variable data on success.
+ */
+int UefiVariableStore::i_uefiVarStoreQueryVarSz(const char *pszVar, uint64_t *pcbVar)
+{
+ char szVarPath[_1K];
+ ssize_t cch = RTStrPrintf2(szVarPath, sizeof(szVarPath), "/by-name/%s", pszVar);
+ Assert(cch > 0); RT_NOREF(cch);
+
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ int vrc = RTVfsFileOpen(m->hVfsUefiVarStore, szVarPath,
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsFileQuerySize(hVfsFile, pcbVar);
+ RTVfsFileRelease(hVfsFile);
+ }
+ else if (vrc == VERR_PATH_NOT_FOUND)
+ vrc = VERR_FILE_NOT_FOUND;
+
+ return vrc;
+}
+
+
+/**
+ * Returns the owner UUID of the given variable.
+ *
+ * @returns IPRT status code.
+ * @param pszVar The variable to query the owner UUID for.
+ * @param pUuid Where to store the owner UUID on success.
+ */
+int UefiVariableStore::i_uefiVarStoreQueryVarOwnerUuid(const char *pszVar, PRTUUID pUuid)
+{
+ char szVarPath[_1K];
+ ssize_t cch = RTStrPrintf2(szVarPath, sizeof(szVarPath), "/raw/%s/uuid", pszVar);
+ Assert(cch > 0); RT_NOREF(cch);
+
+ RTVFSFILE hVfsFileAttr = NIL_RTVFSFILE;
+ int vrc = RTVfsFileOpen(m->hVfsUefiVarStore, szVarPath,
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ &hVfsFileAttr);
+ if (RT_SUCCESS(vrc))
+ {
+ EFI_GUID OwnerGuid;
+ vrc = RTVfsFileRead(hVfsFileAttr, &OwnerGuid, sizeof(OwnerGuid), NULL /*pcbRead*/);
+ RTVfsFileRelease(hVfsFileAttr);
+ if (RT_SUCCESS(vrc))
+ RTEfiGuidToUuid(pUuid, &OwnerGuid);
+ }
+
+ return vrc;
+}
+
+
+/**
+ * Converts the given vector of variables attributes to a bitmask used internally.
+ *
+ * @returns Mask of UEFI variable attributes.
+ * @param vecAttributes Vector of variable atttributes.
+ */
+uint32_t UefiVariableStore::i_uefiVarAttrToMask(const std::vector<UefiVariableAttributes_T> &vecAttributes)
+{
+ uint32_t fAttr = 0;
+
+ for (size_t i = 0; i < vecAttributes.size(); i++)
+ fAttr |= (ULONG)vecAttributes[i];
+
+ return fAttr;
+}
+
+
+/**
+ * Converts the given aatribute mask to the attribute vector used externally.
+ *
+ * @returns nothing.
+ * @param fAttr The attribute mask.
+ * @param aAttributes The vector to store the attibutes in.
+ */
+void UefiVariableStore::i_uefiAttrMaskToVec(uint32_t fAttr, std::vector<UefiVariableAttributes_T> &aAttributes)
+{
+ if (fAttr & EFI_VAR_HEADER_ATTR_NON_VOLATILE)
+ aAttributes.push_back(UefiVariableAttributes_NonVolatile);
+ if (fAttr & EFI_VAR_HEADER_ATTR_BOOTSERVICE_ACCESS)
+ aAttributes.push_back(UefiVariableAttributes_BootServiceAccess);
+ if (fAttr & EFI_VAR_HEADER_ATTR_RUNTIME_ACCESS)
+ aAttributes.push_back(UefiVariableAttributes_RuntimeAccess);
+ if (fAttr & EFI_VAR_HEADER_ATTR_HW_ERROR_RECORD)
+ aAttributes.push_back(UefiVariableAttributes_HwErrorRecord);
+ if (fAttr & EFI_AUTH_VAR_HEADER_ATTR_AUTH_WRITE_ACCESS)
+ aAttributes.push_back(UefiVariableAttributes_AuthWriteAccess);
+ if (fAttr & EFI_AUTH_VAR_HEADER_ATTR_TIME_BASED_AUTH_WRITE_ACCESS)
+ aAttributes.push_back(UefiVariableAttributes_AuthTimeBasedWriteAccess);
+ if (fAttr & EFI_AUTH_VAR_HEADER_ATTR_APPEND_WRITE)
+ aAttributes.push_back(UefiVariableAttributes_AuthAppendWrite);
+}
+
+
+/**
+ * Retains the reference of the variable store from the parent.
+ *
+ * @returns COM status code.
+ * @param fReadonly Flag whether the access is readonly.
+ */
+HRESULT UefiVariableStore::i_retainUefiVariableStore(bool fReadonly)
+{
+ Assert(m->hVfsUefiVarStore = NIL_RTVFS);
+ return m->pParent->i_retainUefiVarStore(&m->hVfsUefiVarStore, fReadonly);
+}
+
+
+/**
+ * Releases the reference of the variable store from the parent.
+ *
+ * @returns COM status code.
+ */
+HRESULT UefiVariableStore::i_releaseUefiVariableStore(void)
+{
+ RTVFS hVfs = m->hVfsUefiVarStore;
+
+ m->hVfsUefiVarStore = NIL_RTVFS;
+ return m->pParent->i_releaseUefiVarStore(hVfs);
+}
+
+
+/**
+ * Adds the given variable to the variable store.
+ *
+ * @returns IPRT status code.
+ * @param pGuid The EFI GUID of the variable.
+ * @param pszVar The variable name.
+ * @param fAttr Attributes for the variable.
+ * @param phVfsFile Where to return the VFS file handle to the created variable on success.
+ */
+HRESULT UefiVariableStore::i_uefiVarStoreAddVar(PCEFI_GUID pGuid, const char *pszVar, uint32_t fAttr, PRTVFSFILE phVfsFile)
+{
+ RTUUID UuidVar;
+ RTEfiGuidToUuid(&UuidVar, pGuid);
+
+ char szVarPath[_1K];
+ ssize_t cch = RTStrPrintf2(szVarPath, sizeof(szVarPath), "/by-uuid/%RTuuid/%s", &UuidVar, pszVar);
+ Assert(cch > 0); RT_NOREF(cch);
+
+ HRESULT hrc = S_OK;
+ int vrc = RTVfsFileOpen(m->hVfsUefiVarStore, szVarPath,
+ RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ phVfsFile);
+ if ( vrc == VERR_PATH_NOT_FOUND
+ || vrc == VERR_FILE_NOT_FOUND)
+ {
+ /*
+ * Try to create the owner GUID of the variable by creating the appropriate directory,
+ * ignore error if it exists already.
+ */
+ RTVFSDIR hVfsDirRoot = NIL_RTVFSDIR;
+ vrc = RTVfsOpenRoot(m->hVfsUefiVarStore, &hVfsDirRoot);
+ if (RT_SUCCESS(vrc))
+ {
+ char szGuidPath[_1K];
+ cch = RTStrPrintf2(szGuidPath, sizeof(szGuidPath), "by-uuid/%RTuuid", &UuidVar);
+ Assert(cch > 0);
+
+ RTVFSDIR hVfsDirGuid = NIL_RTVFSDIR;
+ vrc = RTVfsDirCreateDir(hVfsDirRoot, szGuidPath, 0755, 0 /*fFlags*/, &hVfsDirGuid);
+ if (RT_SUCCESS(vrc))
+ RTVfsDirRelease(hVfsDirGuid);
+ else if (vrc == VERR_ALREADY_EXISTS)
+ vrc = VINF_SUCCESS;
+
+ RTVfsDirRelease(hVfsDirRoot);
+ }
+ else
+ hrc = setError(E_FAIL, tr("Opening variable storage root directory failed: %Rrc"), vrc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsFileOpen(m->hVfsUefiVarStore, szVarPath,
+ RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_CREATE,
+ phVfsFile);
+ if (RT_SUCCESS(vrc))
+ vrc = i_uefiVarStoreSetVarAttr(pszVar, fAttr);
+ }
+
+ if (RT_FAILURE(vrc))
+ hrc = setError(E_FAIL, tr("Creating the variable '%s' failed: %Rrc"), pszVar, vrc);
+ }
+
+ return hrc;
+}
+
+
+/**
+ * Tries to open the given variable from the variable store and returns a file handle.
+ *
+ * @returns IPRT status code.
+ * @param pszVar The variable name.
+ * @param phVfsFile Where to return the VFS file handle to the created variable on success.
+ */
+HRESULT UefiVariableStore::i_uefiVarStoreOpenVar(const char *pszVar, PRTVFSFILE phVfsFile)
+{
+ char szVarPath[_1K];
+ ssize_t cch = RTStrPrintf2(szVarPath, sizeof(szVarPath), "/by-name/%s", pszVar);
+ Assert(cch > 0); RT_NOREF(cch);
+
+ HRESULT hrc = S_OK;
+ int vrc = RTVfsFileOpen(m->hVfsUefiVarStore, szVarPath,
+ RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ phVfsFile);
+ if ( vrc == VERR_PATH_NOT_FOUND
+ || vrc == VERR_FILE_NOT_FOUND)
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("The variable '%s' could not be found"), pszVar);
+ else if (RT_FAILURE(vrc))
+ hrc = setError(VBOX_E_IPRT_ERROR, tr("Couldn't open variable '%s' (%Rrc)"), pszVar, vrc);
+
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::i_uefiVarStoreSetVar(PCEFI_GUID pGuid, const char *pszVar, uint32_t fAttr, const void *pvData, size_t cbData)
+{
+ RTVFSFILE hVfsFileVar = NIL_RTVFSFILE;
+
+ HRESULT hrc = i_uefiVarStoreAddVar(pGuid, pszVar, fAttr, &hVfsFileVar);
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = RTVfsFileWrite(hVfsFileVar, pvData, cbData, NULL /*pcbWritten*/);
+ if (RT_FAILURE(vrc))
+ hrc = setError(E_FAIL, tr("Setting the variable '%s' failed: %Rrc"), pszVar, vrc);
+
+ RTVfsFileRelease(hVfsFileVar);
+ }
+
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::i_uefiVarStoreQueryVar(const char *pszVar, void *pvData, size_t cbData)
+{
+ HRESULT hrc = S_OK;
+
+ char szVarPath[_1K];
+ ssize_t cch = RTStrPrintf2(szVarPath, sizeof(szVarPath), "/by-name/%s", pszVar);
+ Assert(cch > 0); RT_NOREF(cch);
+
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ int vrc = RTVfsFileOpen(m->hVfsUefiVarStore, szVarPath,
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsFileRead(hVfsFile, pvData, cbData, NULL /*pcbRead*/);
+ if (RT_FAILURE(vrc))
+ hrc = setError(E_FAIL, tr("Failed to read data of variable '%s': %Rrc"), pszVar, vrc);
+
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ hrc = setError(E_FAIL, tr("Failed to open variable '%s' for reading: %Rrc"), pszVar, vrc);
+
+ return hrc;
+}
+
+HRESULT UefiVariableStore::i_uefiSigDbAddSig(RTEFISIGDB hEfiSigDb, const void *pvData, size_t cbData,
+ const com::Guid &aOwnerUuid, SignatureType_T enmSignatureType)
+{
+ RTEFISIGTYPE enmSigType = RTEFISIGTYPE_INVALID;
+
+ switch (enmSignatureType)
+ {
+ case SignatureType_X509:
+ enmSigType = RTEFISIGTYPE_X509;
+ break;
+ case SignatureType_Sha256:
+ enmSigType = RTEFISIGTYPE_SHA256;
+ break;
+ default:
+ return setError(E_FAIL, tr("The given signature type is not supported"));
+ }
+
+ int vrc = RTEfiSigDbAddSignatureFromBuf(hEfiSigDb, enmSigType, aOwnerUuid.raw(), pvData, cbData);
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+
+ return setError(E_FAIL, tr("Failed to add signature to the database (%Rrc)"), vrc);
+}
+
+
+HRESULT UefiVariableStore::i_uefiVarStoreAddSignatureToDb(PCEFI_GUID pGuid, const char *pszDb, const void *pvData, size_t cbData,
+ const com::Guid &aOwnerUuid, SignatureType_T enmSignatureType)
+{
+ RTVFSFILE hVfsFileSigDb = NIL_RTVFSFILE;
+
+ HRESULT hrc = i_uefiVarStoreAddVar(pGuid, pszDb,
+ EFI_VAR_HEADER_ATTR_NON_VOLATILE
+ | EFI_VAR_HEADER_ATTR_BOOTSERVICE_ACCESS
+ | EFI_VAR_HEADER_ATTR_RUNTIME_ACCESS
+ | EFI_AUTH_VAR_HEADER_ATTR_TIME_BASED_AUTH_WRITE_ACCESS,
+ &hVfsFileSigDb);
+ if (SUCCEEDED(hrc))
+ {
+ RTEFISIGDB hEfiSigDb;
+
+ int vrc = RTEfiSigDbCreate(&hEfiSigDb);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTEfiSigDbAddFromExistingDb(hEfiSigDb, hVfsFileSigDb);
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = i_uefiSigDbAddSig(hEfiSigDb, pvData, cbData, aOwnerUuid, enmSignatureType);
+ if (SUCCEEDED(hrc))
+ {
+ vrc = RTVfsFileSeek(hVfsFileSigDb, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(vrc);
+
+ vrc = RTEfiSigDbWriteToFile(hEfiSigDb, hVfsFileSigDb);
+ if (RT_FAILURE(vrc))
+ hrc = setError(E_FAIL, tr("Writing updated signature database failed: %Rrc"), vrc);
+ }
+ }
+ else
+ hrc = setError(E_FAIL, tr("Loading signature database failed: %Rrc"), vrc);
+
+ RTEfiSigDbDestroy(hEfiSigDb);
+ }
+ else
+ hrc = setError(E_FAIL, tr("Creating signature database failed: %Rrc"), vrc);
+
+ RTVfsFileRelease(hVfsFileSigDb);
+ }
+
+ return hrc;
+}
+
+
+HRESULT UefiVariableStore::i_uefiVarStoreAddSignatureToDbVec(PCEFI_GUID pGuid, const char *pszDb, const std::vector<BYTE> &aData,
+ const com::Guid &aOwnerUuid, SignatureType_T enmSignatureType)
+{
+ return i_uefiVarStoreAddSignatureToDb(pGuid, pszDb, &aData.front(), aData.size(), aOwnerUuid, enmSignatureType);
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/UnattendedImpl.cpp b/src/VBox/Main/src-server/UnattendedImpl.cpp
new file mode 100644
index 00000000..f40e7e68
--- /dev/null
+++ b/src/VBox/Main/src-server/UnattendedImpl.cpp
@@ -0,0 +1,4292 @@
+/* $Id: UnattendedImpl.cpp $ */
+/** @file
+ * Unattended class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
+#include "LoggingNew.h"
+#include "VirtualBoxBase.h"
+#include "UnattendedImpl.h"
+#include "UnattendedInstaller.h"
+#include "UnattendedScript.h"
+#include "VirtualBoxImpl.h"
+#include "SystemPropertiesImpl.h"
+#include "MachineImpl.h"
+#include "Global.h"
+#include "StringifyEnums.h"
+
+#include <VBox/err.h>
+#include <iprt/cpp/xml.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#ifndef RT_OS_WINDOWS
+# include <iprt/formats/mz.h>
+# include <iprt/formats/pecoff.h>
+#endif
+#include <iprt/formats/wim.h>
+#include <iprt/fsvfs.h>
+#include <iprt/inifile.h>
+#include <iprt/locale.h>
+#include <iprt/path.h>
+#include <iprt/vfs.h>
+
+using namespace std;
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Controller slot for a DVD drive.
+ *
+ * The slot can be free and needing a drive to be attached along with the ISO
+ * image, or it may already be there and only need mounting the ISO. The
+ * ControllerSlot::fFree member indicates which it is.
+ */
+struct ControllerSlot
+{
+ StorageBus_T enmBus;
+ Utf8Str strControllerName;
+ LONG iPort;
+ LONG iDevice;
+ bool fFree;
+
+ ControllerSlot(StorageBus_T a_enmBus, const Utf8Str &a_rName, LONG a_iPort, LONG a_iDevice, bool a_fFree)
+ : enmBus(a_enmBus), strControllerName(a_rName), iPort(a_iPort), iDevice(a_iDevice), fFree(a_fFree)
+ {}
+
+ bool operator<(const ControllerSlot &rThat) const
+ {
+ if (enmBus == rThat.enmBus)
+ {
+ if (strControllerName == rThat.strControllerName)
+ {
+ if (iPort == rThat.iPort)
+ return iDevice < rThat.iDevice;
+ return iPort < rThat.iPort;
+ }
+ return strControllerName < rThat.strControllerName;
+ }
+
+ /*
+ * Bus comparsion in boot priority order.
+ */
+ /* IDE first. */
+ if (enmBus == StorageBus_IDE)
+ return true;
+ if (rThat.enmBus == StorageBus_IDE)
+ return false;
+ /* SATA next */
+ if (enmBus == StorageBus_SATA)
+ return true;
+ if (rThat.enmBus == StorageBus_SATA)
+ return false;
+ /* SCSI next */
+ if (enmBus == StorageBus_SCSI)
+ return true;
+ if (rThat.enmBus == StorageBus_SCSI)
+ return false;
+ /* numerical */
+ return (int)enmBus < (int)rThat.enmBus;
+ }
+
+ bool operator==(const ControllerSlot &rThat) const
+ {
+ return enmBus == rThat.enmBus
+ && strControllerName == rThat.strControllerName
+ && iPort == rThat.iPort
+ && iDevice == rThat.iDevice;
+ }
+};
+
+/**
+ * Installation disk.
+ *
+ * Used when reconfiguring the VM.
+ */
+typedef struct UnattendedInstallationDisk
+{
+ StorageBus_T enmBusType; /**< @todo nobody is using this... */
+ Utf8Str strControllerName;
+ DeviceType_T enmDeviceType;
+ AccessMode_T enmAccessType;
+ LONG iPort;
+ LONG iDevice;
+ bool fMountOnly;
+ Utf8Str strImagePath;
+ bool fAuxiliary;
+
+ UnattendedInstallationDisk(StorageBus_T a_enmBusType, Utf8Str const &a_rBusName, DeviceType_T a_enmDeviceType,
+ AccessMode_T a_enmAccessType, LONG a_iPort, LONG a_iDevice, bool a_fMountOnly,
+ Utf8Str const &a_rImagePath, bool a_fAuxiliary)
+ : enmBusType(a_enmBusType), strControllerName(a_rBusName), enmDeviceType(a_enmDeviceType), enmAccessType(a_enmAccessType)
+ , iPort(a_iPort), iDevice(a_iDevice), fMountOnly(a_fMountOnly), strImagePath(a_rImagePath), fAuxiliary(a_fAuxiliary)
+ {
+ Assert(strControllerName.length() > 0);
+ }
+
+ UnattendedInstallationDisk(std::list<ControllerSlot>::const_iterator const &itDvdSlot, Utf8Str const &a_rImagePath,
+ bool a_fAuxiliary)
+ : enmBusType(itDvdSlot->enmBus), strControllerName(itDvdSlot->strControllerName), enmDeviceType(DeviceType_DVD)
+ , enmAccessType(AccessMode_ReadOnly), iPort(itDvdSlot->iPort), iDevice(itDvdSlot->iDevice)
+ , fMountOnly(!itDvdSlot->fFree), strImagePath(a_rImagePath), fAuxiliary(a_fAuxiliary)
+ {
+ Assert(strControllerName.length() > 0);
+ }
+} UnattendedInstallationDisk;
+
+
+/**
+ * OS/2 syslevel file header.
+ */
+#pragma pack(1)
+typedef struct OS2SYSLEVELHDR
+{
+ uint16_t uMinusOne; /**< 0x00: UINT16_MAX */
+ char achSignature[8]; /**< 0x02: "SYSLEVEL" */
+ uint8_t abReserved1[5]; /**< 0x0a: Usually zero. Ignore. */
+ uint16_t uSyslevelFileVer; /**< 0x0f: The syslevel file version: 1. */
+ uint8_t abReserved2[16]; /**< 0x11: Zero. Ignore. */
+ uint32_t offTable; /**< 0x21: Offset of the syslevel table. */
+} OS2SYSLEVELHDR;
+#pragma pack()
+AssertCompileSize(OS2SYSLEVELHDR, 0x25);
+
+/**
+ * OS/2 syslevel table entry.
+ */
+#pragma pack(1)
+typedef struct OS2SYSLEVELENTRY
+{
+ uint16_t id; /**< 0x00: ? */
+ uint8_t bEdition; /**< 0x02: The OS/2 edition: 0=standard, 1=extended, x=component defined */
+ uint8_t bVersion; /**< 0x03: 0x45 = 4.5 */
+ uint8_t bModify; /**< 0x04: Lower nibble is added to bVersion, so 0x45 0x02 => 4.52 */
+ uint8_t abReserved1[2]; /**< 0x05: Zero. Ignore. */
+ char achCsdLevel[8]; /**< 0x07: The current CSD level. */
+ char achCsdPrior[8]; /**< 0x0f: The prior CSD level. */
+ char szName[80]; /**< 0x5f: System/component name. */
+ char achId[9]; /**< 0x67: System/component ID. */
+ uint8_t bRefresh; /**< 0x70: Single digit refresh version, ignored if zero. */
+ char szType[9]; /**< 0x71: Some kind of type string. Optional */
+ uint8_t abReserved2[6]; /**< 0x7a: Zero. Ignore. */
+} OS2SYSLEVELENTRY;
+#pragma pack()
+AssertCompileSize(OS2SYSLEVELENTRY, 0x80);
+
+
+
+/**
+ * Concatenate image name and version strings and return.
+ *
+ * A possible output would be "Windows 10 Home (10.0.19041.330 / x64)".
+ *
+ * @returns Name string to use.
+ * @param r_strName String object that can be formatted into and returned.
+ */
+const Utf8Str &WIMImage::formatName(Utf8Str &r_strName) const
+{
+ /* We skip the mFlavor as it's typically part of the description already. */
+
+ if (mVersion.isEmpty() && mArch.isEmpty() && mDefaultLanguage.isEmpty() && mLanguages.size() == 0)
+ return mName;
+
+ r_strName = mName;
+ bool fFirst = true;
+ if (mVersion.isNotEmpty())
+ {
+ r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mVersion.c_str());
+ fFirst = false;
+ }
+ if (mArch.isNotEmpty())
+ {
+ r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mArch.c_str());
+ fFirst = false;
+ }
+ if (mDefaultLanguage.isNotEmpty())
+ {
+ r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mDefaultLanguage.c_str());
+ fFirst = false;
+ }
+ else
+ for (size_t i = 0; i < mLanguages.size(); i++)
+ {
+ r_strName.appendPrintf(fFirst ? " (%s" : " / %s", mLanguages[i].c_str());
+ fFirst = false;
+ }
+ r_strName.append(")");
+ return r_strName;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+*
+*
+* Implementation Unattended functions
+*
+*/
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
+Unattended::Unattended()
+ : mhThreadReconfigureVM(NIL_RTNATIVETHREAD), mfRtcUseUtc(false), mfGuestOs64Bit(false)
+ , mpInstaller(NULL), mpTimeZoneInfo(NULL), mfIsDefaultAuxiliaryBasePath(true), mfDoneDetectIsoOS(false)
+ , mfAvoidUpdatesOverNetwork(false)
+{ }
+
+Unattended::~Unattended()
+{
+ if (mpInstaller)
+ {
+ delete mpInstaller;
+ mpInstaller = NULL;
+ }
+}
+
+HRESULT Unattended::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void Unattended::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+void Unattended::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(mParent) = NULL;
+ mMachine.setNull();
+}
+
+/**
+ * Initializes the unattended object.
+ *
+ * @param aParent Pointer to the parent object.
+ */
+HRESULT Unattended::initUnattended(VirtualBox *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ /*
+ * Fill public attributes (IUnattended) with useful defaults.
+ */
+ try
+ {
+ mStrUser = "vboxuser";
+ mStrPassword = "changeme";
+ mfInstallGuestAdditions = false;
+ mfInstallTestExecService = false;
+ midxImage = 1;
+
+ HRESULT hrc = mParent->i_getSystemProperties()->i_getDefaultAdditionsISO(mStrAdditionsIsoPath);
+ ComAssertComRCRet(hrc, hrc);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Confirm a successful initialization
+ */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+HRESULT Unattended::detectIsoOS()
+{
+ HRESULT hrc;
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+/** @todo once UDF is implemented properly and we've tested this code a lot
+ * more, replace E_NOTIMPL with E_FAIL. */
+
+ /*
+ * Reset output state before we start
+ */
+ mStrDetectedOSTypeId.setNull();
+ mStrDetectedOSVersion.setNull();
+ mStrDetectedOSFlavor.setNull();
+ mDetectedOSLanguages.clear();
+ mStrDetectedOSHints.setNull();
+ mDetectedImages.clear();
+
+ /*
+ * Open the ISO.
+ */
+ RTVFSFILE hVfsFileIso;
+ int vrc = RTVfsFileOpenNormal(mStrIsoPath.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFileIso);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_NOTIMPL, vrc, tr("Failed to open '%s' (%Rrc)"), mStrIsoPath.c_str(), vrc);
+
+ RTERRINFOSTATIC ErrInfo;
+ RTVFS hVfsIso;
+ vrc = RTFsIso9660VolOpen(hVfsFileIso, 0 /*fFlags*/, &hVfsIso, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Try do the detection. Repeat for different file system variations (nojoliet, noudf).
+ */
+ hrc = i_innerDetectIsoOS(hVfsIso);
+
+ RTVfsRelease(hVfsIso);
+ if (hrc == S_FALSE) /** @todo Finish the linux and windows detection code. Only OS/2 returns S_OK right now. */
+ hrc = E_NOTIMPL;
+ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ hrc = setErrorBoth(E_NOTIMPL, vrc, tr("Failed to open '%s' as ISO FS (%Rrc) - %s"),
+ mStrIsoPath.c_str(), vrc, ErrInfo.Core.pszMsg);
+ else
+ hrc = setErrorBoth(E_NOTIMPL, vrc, tr("Failed to open '%s' as ISO FS (%Rrc)"), mStrIsoPath.c_str(), vrc);
+ RTVfsFileRelease(hVfsFileIso);
+
+ /*
+ * Just fake up some windows installation media locale (for <UILanguage>).
+ * Note! The translation here isn't perfect. Feel free to send us a patch.
+ */
+ if (mDetectedOSLanguages.size() == 0)
+ {
+ char szTmp[16];
+ const char *pszFilename = RTPathFilename(mStrIsoPath.c_str());
+ if ( pszFilename
+ && RT_C_IS_ALPHA(pszFilename[0])
+ && RT_C_IS_ALPHA(pszFilename[1])
+ && (pszFilename[2] == '-' || pszFilename[2] == '_') )
+ {
+ szTmp[0] = (char)RT_C_TO_LOWER(pszFilename[0]);
+ szTmp[1] = (char)RT_C_TO_LOWER(pszFilename[1]);
+ szTmp[2] = '-';
+ if (szTmp[0] == 'e' && szTmp[1] == 'n')
+ strcpy(&szTmp[3], "US");
+ else if (szTmp[0] == 'a' && szTmp[1] == 'r')
+ strcpy(&szTmp[3], "SA");
+ else if (szTmp[0] == 'd' && szTmp[1] == 'a')
+ strcpy(&szTmp[3], "DK");
+ else if (szTmp[0] == 'e' && szTmp[1] == 't')
+ strcpy(&szTmp[3], "EE");
+ else if (szTmp[0] == 'e' && szTmp[1] == 'l')
+ strcpy(&szTmp[3], "GR");
+ else if (szTmp[0] == 'h' && szTmp[1] == 'e')
+ strcpy(&szTmp[3], "IL");
+ else if (szTmp[0] == 'j' && szTmp[1] == 'a')
+ strcpy(&szTmp[3], "JP");
+ else if (szTmp[0] == 's' && szTmp[1] == 'v')
+ strcpy(&szTmp[3], "SE");
+ else if (szTmp[0] == 'u' && szTmp[1] == 'k')
+ strcpy(&szTmp[3], "UA");
+ else if (szTmp[0] == 'c' && szTmp[1] == 's')
+ strcpy(szTmp, "cs-CZ");
+ else if (szTmp[0] == 'n' && szTmp[1] == 'o')
+ strcpy(szTmp, "nb-NO");
+ else if (szTmp[0] == 'p' && szTmp[1] == 'p')
+ strcpy(szTmp, "pt-PT");
+ else if (szTmp[0] == 'p' && szTmp[1] == 't')
+ strcpy(szTmp, "pt-BR");
+ else if (szTmp[0] == 'c' && szTmp[1] == 'n')
+ strcpy(szTmp, "zh-CN");
+ else if (szTmp[0] == 'h' && szTmp[1] == 'k')
+ strcpy(szTmp, "zh-HK");
+ else if (szTmp[0] == 't' && szTmp[1] == 'w')
+ strcpy(szTmp, "zh-TW");
+ else if (szTmp[0] == 's' && szTmp[1] == 'r')
+ strcpy(szTmp, "sr-Latn-CS"); /* hmm */
+ else
+ {
+ szTmp[3] = (char)RT_C_TO_UPPER(pszFilename[0]);
+ szTmp[4] = (char)RT_C_TO_UPPER(pszFilename[1]);
+ szTmp[5] = '\0';
+ }
+ }
+ else
+ strcpy(szTmp, "en-US");
+ try
+ {
+ mDetectedOSLanguages.append(szTmp);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ /** @todo implement actual detection logic. */
+ return hrc;
+}
+
+HRESULT Unattended::i_innerDetectIsoOS(RTVFS hVfsIso)
+{
+ DETECTBUFFER uBuf;
+ mEnmOsType = VBOXOSTYPE_Unknown;
+ HRESULT hrc = i_innerDetectIsoOSWindows(hVfsIso, &uBuf);
+ if (hrc == S_FALSE && mEnmOsType == VBOXOSTYPE_Unknown)
+ hrc = i_innerDetectIsoOSLinux(hVfsIso, &uBuf);
+ if (hrc == S_FALSE && mEnmOsType == VBOXOSTYPE_Unknown)
+ hrc = i_innerDetectIsoOSOs2(hVfsIso, &uBuf);
+ if (hrc == S_FALSE && mEnmOsType == VBOXOSTYPE_Unknown)
+ hrc = i_innerDetectIsoOSFreeBsd(hVfsIso, &uBuf);
+ if (mEnmOsType != VBOXOSTYPE_Unknown)
+ {
+ try { mStrDetectedOSTypeId = Global::OSTypeId(mEnmOsType); }
+ catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; }
+ }
+ return hrc;
+}
+
+/**
+ * Tries to parse a LANGUAGES element, with the following structure.
+ * @verbatim
+ * <LANGUAGES>
+ * <LANGUAGE>
+ * en-US
+ * </LANGUAGE>
+ * <DEFAULT>
+ * en-US
+ * </DEFAULT>
+ * </LANGUAGES>
+ * @endverbatim
+ *
+ * Will set mLanguages and mDefaultLanguage success.
+ *
+ * @param pElmLanguages Points to the LANGUAGES XML node.
+ * @param rImage Out reference to an WIMImage instance.
+ */
+static void parseLangaguesElement(const xml::ElementNode *pElmLanguages, WIMImage &rImage)
+{
+ /*
+ * The languages.
+ */
+ ElementNodesList children;
+ int cChildren = pElmLanguages->getChildElements(children, "LANGUAGE");
+ if (cChildren == 0)
+ cChildren = pElmLanguages->getChildElements(children, "language");
+ if (cChildren == 0)
+ cChildren = pElmLanguages->getChildElements(children, "Language");
+ for (ElementNodesList::iterator iterator = children.begin(); iterator != children.end(); ++iterator)
+ {
+ const ElementNode * const pElmLanguage = *(iterator);
+ if (pElmLanguage)
+ {
+ const char *pszValue = pElmLanguage->getValue();
+ if (pszValue && *pszValue != '\0')
+ rImage.mLanguages.append(pszValue);
+ }
+ }
+
+ /*
+ * Default language.
+ */
+ const xml::ElementNode *pElmDefault;
+ if ( (pElmDefault = pElmLanguages->findChildElement("DEFAULT")) != NULL
+ || (pElmDefault = pElmLanguages->findChildElement("default")) != NULL
+ || (pElmDefault = pElmLanguages->findChildElement("Default")) != NULL)
+ rImage.mDefaultLanguage = pElmDefault->getValue();
+}
+
+
+/**
+ * Tries to set the image architecture.
+ *
+ * Input examples (x86 and amd64 respectively):
+ * @verbatim
+ * <ARCH>0</ARCH>
+ * <ARCH>9</ARCH>
+ * @endverbatim
+ *
+ * Will set mArch and update mOSType on success.
+ *
+ * @param pElmArch Points to the ARCH XML node.
+ * @param rImage Out reference to an WIMImage instance.
+ */
+static void parseArchElement(const xml::ElementNode *pElmArch, WIMImage &rImage)
+{
+ /* These are from winnt.h */
+ static struct { const char *pszArch; VBOXOSTYPE enmArch; } s_aArches[] =
+ {
+ /* PROCESSOR_ARCHITECTURE_INTEL / [0] = */ { "x86", VBOXOSTYPE_x86 },
+ /* PROCESSOR_ARCHITECTURE_MIPS / [1] = */ { "mips", VBOXOSTYPE_UnknownArch },
+ /* PROCESSOR_ARCHITECTURE_ALPHA / [2] = */ { "alpha", VBOXOSTYPE_UnknownArch },
+ /* PROCESSOR_ARCHITECTURE_PPC / [3] = */ { "ppc", VBOXOSTYPE_UnknownArch },
+ /* PROCESSOR_ARCHITECTURE_SHX / [4] = */ { "shx", VBOXOSTYPE_UnknownArch },
+ /* PROCESSOR_ARCHITECTURE_ARM / [5] = */ { "arm32", VBOXOSTYPE_arm32 },
+ /* PROCESSOR_ARCHITECTURE_IA64 / [6] = */ { "ia64", VBOXOSTYPE_UnknownArch },
+ /* PROCESSOR_ARCHITECTURE_ALPHA64 / [7] = */ { "alpha64", VBOXOSTYPE_UnknownArch },
+ /* PROCESSOR_ARCHITECTURE_MSIL / [8] = */ { "msil", VBOXOSTYPE_UnknownArch },
+ /* PROCESSOR_ARCHITECTURE_AMD64 / [9] = */ { "x64", VBOXOSTYPE_x64 },
+ /* PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 / [10] = */ { "x86-on-x64", VBOXOSTYPE_UnknownArch },
+ /* PROCESSOR_ARCHITECTURE_NEUTRAL / [11] = */ { "noarch", VBOXOSTYPE_UnknownArch },
+ /* PROCESSOR_ARCHITECTURE_ARM64 / [12] = */ { "arm64", VBOXOSTYPE_arm64 },
+ /* PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64/ [13] = */ { "arm32-on-arm64", VBOXOSTYPE_UnknownArch },
+ /* PROCESSOR_ARCHITECTURE_IA32_ON_ARM64 / [14] = */ { "x86-on-arm32", VBOXOSTYPE_UnknownArch },
+ };
+ const char *pszArch = pElmArch->getValue();
+ if (pszArch && *pszArch)
+ {
+ uint32_t uArch;
+ int vrc = RTStrToUInt32Ex(pszArch, NULL, 10 /*uBase*/, &uArch);
+ if ( RT_SUCCESS(vrc)
+ && vrc != VWRN_NUMBER_TOO_BIG
+ && vrc != VWRN_NEGATIVE_UNSIGNED
+ && uArch < RT_ELEMENTS(s_aArches))
+ {
+ rImage.mArch = s_aArches[uArch].pszArch;
+ rImage.mOSType = (VBOXOSTYPE)(s_aArches[uArch].enmArch | (rImage.mOSType & VBOXOSTYPE_OsTypeMask));
+ }
+ else
+ LogRel(("Unattended: bogus ARCH element value: '%s'\n", pszArch));
+ }
+}
+
+/**
+ * Parses XML Node assuming a structure as follows
+ * @verbatim
+ * <VERSION>
+ * <MAJOR>10</MAJOR>
+ * <MINOR>0</MINOR>
+ * <BUILD>19041</BUILD>
+ * <SPBUILD>1</SPBUILD>
+ * </VERSION>
+ * @endverbatim
+ *
+ * Will update mOSType, mEnmOsType as well as setting mVersion on success.
+ *
+ * @param pNode Points to the vesion XML node,
+ * @param image Out reference to an WIMImage instance.
+ */
+static void parseVersionElement(const xml::ElementNode *pNode, WIMImage &image)
+{
+ /* Major part: */
+ const xml::ElementNode *pElmMajor;
+ if ( (pElmMajor = pNode->findChildElement("MAJOR")) != NULL
+ || (pElmMajor = pNode->findChildElement("major")) != NULL
+ || (pElmMajor = pNode->findChildElement("Major")) != NULL)
+ if (pElmMajor)
+ {
+ const char * const pszMajor = pElmMajor->getValue();
+ if (pszMajor && *pszMajor)
+ {
+ /* Minor part: */
+ const ElementNode *pElmMinor;
+ if ( (pElmMinor = pNode->findChildElement("MINOR")) != NULL
+ || (pElmMinor = pNode->findChildElement("minor")) != NULL
+ || (pElmMinor = pNode->findChildElement("Minor")) != NULL)
+ {
+ const char * const pszMinor = pElmMinor->getValue();
+ if (pszMinor && *pszMinor)
+ {
+ /* Build: */
+ const ElementNode *pElmBuild;
+ if ( (pElmBuild = pNode->findChildElement("BUILD")) != NULL
+ || (pElmBuild = pNode->findChildElement("build")) != NULL
+ || (pElmBuild = pNode->findChildElement("Build")) != NULL)
+ {
+ const char * const pszBuild = pElmBuild->getValue();
+ if (pszBuild && *pszBuild)
+ {
+ /* SPBuild: */
+ const ElementNode *pElmSpBuild;
+ if ( ( (pElmSpBuild = pNode->findChildElement("SPBUILD")) != NULL
+ || (pElmSpBuild = pNode->findChildElement("spbuild")) != NULL
+ || (pElmSpBuild = pNode->findChildElement("Spbuild")) != NULL
+ || (pElmSpBuild = pNode->findChildElement("SpBuild")) != NULL)
+ && pElmSpBuild->getValue()
+ && *pElmSpBuild->getValue() != '\0')
+ image.mVersion.printf("%s.%s.%s.%s", pszMajor, pszMinor, pszBuild, pElmSpBuild->getValue());
+ else
+ image.mVersion.printf("%s.%s.%s", pszMajor, pszMinor, pszBuild);
+
+ /*
+ * Convert that to a version windows OS ID (newest first!).
+ */
+ image.mEnmOsType = VBOXOSTYPE_Unknown;
+ if (RTStrVersionCompare(image.mVersion.c_str(), "10.0.22000.0") >= 0)
+ image.mEnmOsType = VBOXOSTYPE_Win11_x64;
+ else if (RTStrVersionCompare(image.mVersion.c_str(), "10.0") >= 0)
+ image.mEnmOsType = VBOXOSTYPE_Win10;
+ else if (RTStrVersionCompare(image.mVersion.c_str(), "6.3") >= 0)
+ image.mEnmOsType = VBOXOSTYPE_Win81;
+ else if (RTStrVersionCompare(image.mVersion.c_str(), "6.2") >= 0)
+ image.mEnmOsType = VBOXOSTYPE_Win8;
+ else if (RTStrVersionCompare(image.mVersion.c_str(), "6.1") >= 0)
+ image.mEnmOsType = VBOXOSTYPE_Win7;
+ else if (RTStrVersionCompare(image.mVersion.c_str(), "6.0") >= 0)
+ image.mEnmOsType = VBOXOSTYPE_WinVista;
+ if (image.mFlavor.contains("server", Utf8Str::CaseInsensitive))
+ {
+ if (RTStrVersionCompare(image.mVersion.c_str(), "10.0.20348") >= 0)
+ image.mEnmOsType = VBOXOSTYPE_Win2k22_x64;
+ else if (RTStrVersionCompare(image.mVersion.c_str(), "10.0.17763") >= 0)
+ image.mEnmOsType = VBOXOSTYPE_Win2k19_x64;
+ else if (RTStrVersionCompare(image.mVersion.c_str(), "10.0") >= 0)
+ image.mEnmOsType = VBOXOSTYPE_Win2k16_x64;
+ else if (RTStrVersionCompare(image.mVersion.c_str(), "6.2") >= 0)
+ image.mEnmOsType = VBOXOSTYPE_Win2k12_x64;
+ else if (RTStrVersionCompare(image.mVersion.c_str(), "6.0") >= 0)
+ image.mEnmOsType = VBOXOSTYPE_Win2k8;
+ }
+ if (image.mEnmOsType != VBOXOSTYPE_Unknown)
+ image.mOSType = (VBOXOSTYPE)( (image.mOSType & VBOXOSTYPE_ArchitectureMask)
+ | (image.mEnmOsType & VBOXOSTYPE_OsTypeMask));
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ Log(("Unattended: Warning! Bogus/missing version info for image #%u / %s\n", image.mImageIndex, image.mName.c_str()));
+}
+
+/**
+ * Parses XML tree assuming th following structure
+ * @verbatim
+ * <WIM>
+ * ...
+ * <IMAGE INDEX="1">
+ * ...
+ * <DISPLAYNAME>Windows 10 Home</DISPLAYNAME>
+ * <WINDOWS>
+ * <ARCH>NN</ARCH>
+ * <VERSION>
+ * ...
+ * </VERSION>
+ * <LANGUAGES>
+ * <LANGUAGE>
+ * en-US
+ * </LANGUAGE>
+ * <DEFAULT>
+ * en-US
+ * </DEFAULT>
+ * </LANGUAGES>
+ * </WINDOWS>
+ * </IMAGE>
+ * </WIM>
+ * @endverbatim
+ *
+ * @param pElmRoot Pointer to the root node of the tree,
+ * @param imageList Detected images are appended to this list.
+ */
+static void parseWimXMLData(const xml::ElementNode *pElmRoot, RTCList<WIMImage> &imageList)
+{
+ if (!pElmRoot)
+ return;
+
+ ElementNodesList children;
+ int cChildren = pElmRoot->getChildElements(children, "IMAGE");
+ if (cChildren == 0)
+ cChildren = pElmRoot->getChildElements(children, "image");
+ if (cChildren == 0)
+ cChildren = pElmRoot->getChildElements(children, "Image");
+
+ for (ElementNodesList::iterator iterator = children.begin(); iterator != children.end(); ++iterator)
+ {
+ const ElementNode *pChild = *(iterator);
+ if (!pChild)
+ continue;
+
+ WIMImage newImage;
+
+ if ( !pChild->getAttributeValue("INDEX", &newImage.mImageIndex)
+ && !pChild->getAttributeValue("index", &newImage.mImageIndex)
+ && !pChild->getAttributeValue("Index", &newImage.mImageIndex))
+ continue;
+
+ const ElementNode *pElmName;
+ if ( (pElmName = pChild->findChildElement("DISPLAYNAME")) == NULL
+ && (pElmName = pChild->findChildElement("displayname")) == NULL
+ && (pElmName = pChild->findChildElement("Displayname")) == NULL
+ && (pElmName = pChild->findChildElement("DisplayName")) == NULL
+ /* Early vista images didn't have DISPLAYNAME. */
+ && (pElmName = pChild->findChildElement("NAME")) == NULL
+ && (pElmName = pChild->findChildElement("name")) == NULL
+ && (pElmName = pChild->findChildElement("Name")) == NULL)
+ continue;
+ newImage.mName = pElmName->getValue();
+ if (newImage.mName.isEmpty())
+ continue;
+
+ const ElementNode *pElmWindows;
+ if ( (pElmWindows = pChild->findChildElement("WINDOWS")) != NULL
+ || (pElmWindows = pChild->findChildElement("windows")) != NULL
+ || (pElmWindows = pChild->findChildElement("Windows")) != NULL)
+ {
+ /* Do edition/flags before the version so it can better determin
+ the OS version enum value. Old windows version (vista) typically
+ doesn't have an EDITIONID element, so fall back on the FLAGS element
+ under IMAGE as it is pretty similar (case differences). */
+ const ElementNode *pElmEditionId;
+ if ( (pElmEditionId = pElmWindows->findChildElement("EDITIONID")) != NULL
+ || (pElmEditionId = pElmWindows->findChildElement("editionid")) != NULL
+ || (pElmEditionId = pElmWindows->findChildElement("Editionid")) != NULL
+ || (pElmEditionId = pElmWindows->findChildElement("EditionId")) != NULL
+ || (pElmEditionId = pChild->findChildElement("FLAGS")) != NULL
+ || (pElmEditionId = pChild->findChildElement("flags")) != NULL
+ || (pElmEditionId = pChild->findChildElement("Flags")) != NULL)
+ if ( pElmEditionId->getValue()
+ && *pElmEditionId->getValue() != '\0')
+ newImage.mFlavor = pElmEditionId->getValue();
+
+ const ElementNode *pElmVersion;
+ if ( (pElmVersion = pElmWindows->findChildElement("VERSION")) != NULL
+ || (pElmVersion = pElmWindows->findChildElement("version")) != NULL
+ || (pElmVersion = pElmWindows->findChildElement("Version")) != NULL)
+ parseVersionElement(pElmVersion, newImage);
+
+ /* The ARCH element contains a number from the
+ PROCESSOR_ARCHITECTURE_XXX set of defines in winnt.h: */
+ const ElementNode *pElmArch;
+ if ( (pElmArch = pElmWindows->findChildElement("ARCH")) != NULL
+ || (pElmArch = pElmWindows->findChildElement("arch")) != NULL
+ || (pElmArch = pElmWindows->findChildElement("Arch")) != NULL)
+ parseArchElement(pElmArch, newImage);
+
+ /* Extract languages and default language: */
+ const ElementNode *pElmLang;
+ if ( (pElmLang = pElmWindows->findChildElement("LANGUAGES")) != NULL
+ || (pElmLang = pElmWindows->findChildElement("languages")) != NULL
+ || (pElmLang = pElmWindows->findChildElement("Languages")) != NULL)
+ parseLangaguesElement(pElmLang, newImage);
+ }
+
+
+ imageList.append(newImage);
+ }
+}
+
+/**
+ * Detect Windows ISOs.
+ *
+ * @returns COM status code.
+ * @retval S_OK if detected
+ * @retval S_FALSE if not fully detected.
+ *
+ * @param hVfsIso The ISO file system.
+ * @param pBuf Read buffer.
+ */
+HRESULT Unattended::i_innerDetectIsoOSWindows(RTVFS hVfsIso, DETECTBUFFER *pBuf)
+{
+ /** @todo The 'sources/' path can differ. */
+
+ // globalinstallorder.xml - vista beta2
+ // sources/idwbinfo.txt - ditto.
+ // sources/lang.ini - ditto.
+
+ /*
+ * The install.wim file contains an XML document describing the install
+ * images it contains. This includes all the info we need for a successful
+ * detection.
+ */
+ RTVFSFILE hVfsFile;
+ int vrc = RTVfsFileOpen(hVfsIso, "sources/install.wim", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ WIMHEADERV1 header;
+ size_t cbRead = 0;
+ vrc = RTVfsFileRead(hVfsFile, &header, sizeof(header), &cbRead);
+ if (RT_SUCCESS(vrc) && cbRead == sizeof(header))
+ {
+ /* If the xml data is not compressed, xml data is not empty, and not too big. */
+ if ( (header.XmlData.bFlags & RESHDR_FLAGS_METADATA)
+ && !(header.XmlData.bFlags & RESHDR_FLAGS_COMPRESSED)
+ && header.XmlData.cbOriginal >= 32
+ && header.XmlData.cbOriginal < _32M
+ && header.XmlData.cbOriginal == header.XmlData.cb)
+ {
+ size_t const cbXmlData = (size_t)header.XmlData.cbOriginal;
+ char *pachXmlBuf = (char *)RTMemTmpAlloc(cbXmlData);
+ if (pachXmlBuf)
+ {
+ vrc = RTVfsFileReadAt(hVfsFile, (RTFOFF)header.XmlData.off, pachXmlBuf, cbXmlData, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel2(("XML Data (%#zx bytes):\n%32.*Rhxd\n", cbXmlData, cbXmlData, pachXmlBuf));
+
+ /* Parse the XML: */
+ xml::Document doc;
+ xml::XmlMemParser parser;
+ try
+ {
+ RTCString strFileName = "source/install.wim";
+ parser.read(pachXmlBuf, cbXmlData, strFileName, doc);
+ }
+ catch (xml::XmlError &rErr)
+ {
+ LogRel(("Unattended: An error has occured during XML parsing: %s\n", rErr.what()));
+ vrc = VERR_XAR_TOC_XML_PARSE_ERROR;
+ }
+ catch (std::bad_alloc &)
+ {
+ LogRel(("Unattended: std::bad_alloc\n"));
+ vrc = VERR_NO_MEMORY;
+ }
+ catch (...)
+ {
+ LogRel(("Unattended: An unknown error has occured during XML parsing.\n"));
+ vrc = VERR_UNEXPECTED_EXCEPTION;
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ /* Extract the information we need from the XML document: */
+ xml::ElementNode *pElmRoot = doc.getRootElement();
+ if (pElmRoot)
+ {
+ Assert(mDetectedImages.size() == 0);
+ try
+ {
+ mDetectedImages.clear(); /* debugging convenience */
+ parseWimXMLData(pElmRoot, mDetectedImages);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ /*
+ * If we found images, update the detected info attributes.
+ */
+ if (RT_SUCCESS(vrc) && mDetectedImages.size() > 0)
+ {
+ size_t i;
+ for (i = 0; i < mDetectedImages.size(); i++)
+ if (mDetectedImages[i].mImageIndex == midxImage)
+ break;
+ if (i >= mDetectedImages.size())
+ i = 0; /* use the first one if midxImage wasn't found */
+ if (i_updateDetectedAttributeForImage(mDetectedImages[i]))
+ {
+ LogRel2(("Unattended: happy with mDetectedImages[%u]\n", i));
+ mEnmOsType = mDetectedImages[i].mOSType;
+ return S_OK;
+ }
+ }
+ }
+ else
+ LogRel(("Unattended: No root element found in XML Metadata of install.wim\n"));
+ }
+ }
+ else
+ LogRel(("Unattended: Failed during reading XML Metadata out of install.wim\n"));
+ RTMemTmpFree(pachXmlBuf);
+ }
+ else
+ {
+ LogRel(("Unattended: Failed to allocate %#zx bytes for XML Metadata\n", cbXmlData));
+ vrc = VERR_NO_TMP_MEMORY;
+ }
+ }
+ else
+ LogRel(("Unattended: XML Metadata of install.wim is either compressed, empty, or too big (bFlags=%#x cbOriginal=%#RX64 cb=%#RX64)\n",
+ header.XmlData.bFlags, header.XmlData.cbOriginal, header.XmlData.cb));
+ }
+ RTVfsFileRelease(hVfsFile);
+
+ /* Bail out if we ran out of memory here. */
+ if (vrc == VERR_NO_MEMORY || vrc == VERR_NO_TMP_MEMORY)
+ return setErrorBoth(E_OUTOFMEMORY, vrc, tr("Out of memory"));
+ }
+
+ const char *pszVersion = NULL;
+ const char *pszProduct = NULL;
+ /*
+ * Try look for the 'sources/idwbinfo.txt' file containing windows build info.
+ * This file appeared with Vista beta 2 from what we can tell. Before windows 10
+ * it contains easily decodable branch names, after that things goes weird.
+ */
+ vrc = RTVfsFileOpen(hVfsIso, "sources/idwbinfo.txt", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ mEnmOsType = VBOXOSTYPE_WinNT_x64;
+
+ RTINIFILE hIniFile;
+ vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
+ RTVfsFileRelease(hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTIniFileQueryValue(hIniFile, "BUILDINFO", "BuildArch", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRelFlow(("Unattended: sources/idwbinfo.txt: BuildArch=%s\n", pBuf->sz));
+ if ( RTStrNICmp(pBuf->sz, RT_STR_TUPLE("amd64")) == 0
+ || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("x64")) == 0 /* just in case */ )
+ mEnmOsType = VBOXOSTYPE_WinNT_x64;
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("x86")) == 0)
+ mEnmOsType = VBOXOSTYPE_WinNT;
+ else
+ {
+ LogRel(("Unattended: sources/idwbinfo.txt: Unknown: BuildArch=%s\n", pBuf->sz));
+ mEnmOsType = VBOXOSTYPE_WinNT_x64;
+ }
+ }
+
+ vrc = RTIniFileQueryValue(hIniFile, "BUILDINFO", "BuildBranch", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRelFlow(("Unattended: sources/idwbinfo.txt: BuildBranch=%s\n", pBuf->sz));
+ if ( RTStrNICmp(pBuf->sz, RT_STR_TUPLE("vista")) == 0
+ || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("winmain_beta")) == 0)
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinVista);
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("lh_sp2rtm")) == 0)
+ {
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinVista);
+ pszVersion = "sp2";
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("longhorn_rtm")) == 0)
+ {
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinVista);
+ pszVersion = "sp1";
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("win7")) == 0)
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win7);
+ else if ( RTStrNICmp(pBuf->sz, RT_STR_TUPLE("winblue")) == 0
+ || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("winmain_blue")) == 0
+ || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("win81")) == 0 /* not seen, but just in case its out there */ )
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win81);
+ else if ( RTStrNICmp(pBuf->sz, RT_STR_TUPLE("win8")) == 0
+ || RTStrNICmp(pBuf->sz, RT_STR_TUPLE("winmain_win8")) == 0 )
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win8);
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("th1")) == 0)
+ {
+ pszVersion = "1507"; // aka. GA, retroactively 1507
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("th2")) == 0)
+ {
+ pszVersion = "1511"; // aka. threshold 2
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs1_release")) == 0)
+ {
+ pszVersion = "1607"; // aka. anniversay update; rs=redstone
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs2_release")) == 0)
+ {
+ pszVersion = "1703"; // aka. creators update
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs3_release")) == 0)
+ {
+ pszVersion = "1709"; // aka. fall creators update
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs4_release")) == 0)
+ {
+ pszVersion = "1803";
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("rs5_release")) == 0)
+ {
+ pszVersion = "1809";
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("19h1_release")) == 0)
+ {
+ pszVersion = "1903";
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("19h2_release")) == 0)
+ {
+ pszVersion = "1909"; // ??
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("20h1_release")) == 0)
+ {
+ pszVersion = "2003"; // ??
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("vb_release")) == 0)
+ {
+ pszVersion = "2004"; // ?? vb=Vibranium
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("20h2_release")) == 0)
+ {
+ pszVersion = "2009"; // ??
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("21h1_release")) == 0)
+ {
+ pszVersion = "2103"; // ??
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("21h2_release")) == 0)
+ {
+ pszVersion = "2109"; // ??
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win10);
+ }
+ else if (RTStrNICmp(pBuf->sz, RT_STR_TUPLE("co_release")) == 0)
+ {
+ pszVersion = "21H2"; // ??
+ mEnmOsType = VBOXOSTYPE_Win11_x64;
+ }
+ else
+ LogRel(("Unattended: sources/idwbinfo.txt: Unknown: BuildBranch=%s\n", pBuf->sz));
+ }
+ RTIniFileRelease(hIniFile);
+ }
+ }
+ bool fClarifyProd = false;
+ if (RT_FAILURE(vrc))
+ {
+ /*
+ * Check a INF file with a DriverVer that is updated with each service pack.
+ * DriverVer=10/01/2002,5.2.3790.3959
+ */
+ vrc = RTVfsFileOpen(hVfsIso, "AMD64/HIVESYS.INF", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ mEnmOsType = VBOXOSTYPE_WinNT_x64;
+ else
+ {
+ vrc = RTVfsFileOpen(hVfsIso, "I386/HIVESYS.INF", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ mEnmOsType = VBOXOSTYPE_WinNT;
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ RTINIFILE hIniFile;
+ vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
+ RTVfsFileRelease(hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTIniFileQueryValue(hIniFile, "Version", "DriverVer", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRelFlow(("Unattended: HIVESYS.INF: DriverVer=%s\n", pBuf->sz));
+ const char *psz = strchr(pBuf->sz, ',');
+ psz = psz ? psz + 1 : pBuf->sz;
+ if (RTStrVersionCompare(psz, "6.0.0") >= 0)
+ LogRel(("Unattended: HIVESYS.INF: unknown: DriverVer=%s\n", psz));
+ else if (RTStrVersionCompare(psz, "5.2.0") >= 0) /* W2K3, XP64 */
+ {
+ fClarifyProd = true;
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win2k3);
+ if (RTStrVersionCompare(psz, "5.2.3790.3959") >= 0)
+ pszVersion = "sp2";
+ else if (RTStrVersionCompare(psz, "5.2.3790.1830") >= 0)
+ pszVersion = "sp1";
+ }
+ else if (RTStrVersionCompare(psz, "5.1.0") >= 0) /* XP */
+ {
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinXP);
+ if (RTStrVersionCompare(psz, "5.1.2600.5512") >= 0)
+ pszVersion = "sp3";
+ else if (RTStrVersionCompare(psz, "5.1.2600.2180") >= 0)
+ pszVersion = "sp2";
+ else if (RTStrVersionCompare(psz, "5.1.2600.1105") >= 0)
+ pszVersion = "sp1";
+ }
+ else if (RTStrVersionCompare(psz, "5.0.0") >= 0)
+ {
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win2k);
+ if (RTStrVersionCompare(psz, "5.0.2195.6717") >= 0)
+ pszVersion = "sp4";
+ else if (RTStrVersionCompare(psz, "5.0.2195.5438") >= 0)
+ pszVersion = "sp3";
+ else if (RTStrVersionCompare(psz, "5.0.2195.1620") >= 0)
+ pszVersion = "sp1";
+ }
+ else
+ LogRel(("Unattended: HIVESYS.INF: unknown: DriverVer=%s\n", psz));
+ }
+ RTIniFileRelease(hIniFile);
+ }
+ }
+ }
+ if (RT_FAILURE(vrc) || fClarifyProd)
+ {
+ /*
+ * NT 4 and older does not have DriverVer entries, we consult the PRODSPEC.INI, which
+ * works for NT4 & W2K. It does usually not reflect the service pack.
+ */
+ vrc = RTVfsFileOpen(hVfsIso, "AMD64/PRODSPEC.INI", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ mEnmOsType = VBOXOSTYPE_WinNT_x64;
+ else
+ {
+ vrc = RTVfsFileOpen(hVfsIso, "I386/PRODSPEC.INI", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ mEnmOsType = VBOXOSTYPE_WinNT;
+ }
+ if (RT_SUCCESS(vrc))
+ {
+
+ RTINIFILE hIniFile;
+ vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
+ RTVfsFileRelease(hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTIniFileQueryValue(hIniFile, "Product Specification", "Version", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRelFlow(("Unattended: PRODSPEC.INI: Version=%s\n", pBuf->sz));
+ if (RTStrVersionCompare(pBuf->sz, "5.1") >= 0) /* Shipped with XP + W2K3, but version stuck at 5.0. */
+ LogRel(("Unattended: PRODSPEC.INI: unknown: DriverVer=%s\n", pBuf->sz));
+ else if (RTStrVersionCompare(pBuf->sz, "5.0") >= 0) /* 2000 */
+ {
+ vrc = RTIniFileQueryValue(hIniFile, "Product Specification", "Product", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_SUCCESS(vrc) && RTStrNICmp(pBuf->sz, RT_STR_TUPLE("Windows XP")) == 0)
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_WinXP);
+ else if (RT_SUCCESS(vrc) && RTStrNICmp(pBuf->sz, RT_STR_TUPLE("Windows Server 2003")) == 0)
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win2k3);
+ else
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Win2k);
+
+ if (RT_SUCCESS(vrc) && (strstr(pBuf->sz, "Server") || strstr(pBuf->sz, "server")))
+ pszProduct = "Server";
+ }
+ else if (RTStrVersionCompare(pBuf->sz, "4.0") >= 0) /* NT4 */
+ mEnmOsType = VBOXOSTYPE_WinNT4;
+ else
+ LogRel(("Unattended: PRODSPEC.INI: unknown: DriverVer=%s\n", pBuf->sz));
+
+ vrc = RTIniFileQueryValue(hIniFile, "Product Specification", "ProductType", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_SUCCESS(vrc))
+ pszProduct = strcmp(pBuf->sz, "0") == 0 ? "Workstation" : /* simplification: */ "Server";
+ }
+ RTIniFileRelease(hIniFile);
+ }
+ }
+ if (fClarifyProd)
+ vrc = VINF_SUCCESS;
+ }
+ if (RT_FAILURE(vrc))
+ {
+ /*
+ * NT 3.x we look at the LoadIdentifier (boot manager) string in TXTSETUP.SIF/TXT.
+ */
+ vrc = RTVfsFileOpen(hVfsIso, "I386/TXTSETUP.SIF", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_FAILURE(vrc))
+ vrc = RTVfsFileOpen(hVfsIso, "I386/TXTSETUP.INF", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ mEnmOsType = VBOXOSTYPE_WinNT;
+
+ RTINIFILE hIniFile;
+ vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
+ RTVfsFileRelease(hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTIniFileQueryValue(hIniFile, "SetupData", "ProductType", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_SUCCESS(vrc))
+ pszProduct = strcmp(pBuf->sz, "0") == 0 ? "Workstation" : /* simplification: */ "Server";
+
+ vrc = RTIniFileQueryValue(hIniFile, "SetupData", "LoadIdentifier", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRelFlow(("Unattended: TXTSETUP.SIF: LoadIdentifier=%s\n", pBuf->sz));
+ char *psz = pBuf->sz;
+ while (!RT_C_IS_DIGIT(*psz) && *psz)
+ psz++;
+ char *psz2 = psz;
+ while (RT_C_IS_DIGIT(*psz2) || *psz2 == '.')
+ psz2++;
+ *psz2 = '\0';
+ if (RTStrVersionCompare(psz, "6.0") >= 0)
+ LogRel(("Unattended: TXTSETUP.SIF: unknown: LoadIdentifier=%s\n", pBuf->sz));
+ else if (RTStrVersionCompare(psz, "4.0") >= 0)
+ mEnmOsType = VBOXOSTYPE_WinNT4;
+ else if (RTStrVersionCompare(psz, "3.1") >= 0)
+ {
+ mEnmOsType = VBOXOSTYPE_WinNT3x;
+ pszVersion = psz;
+ }
+ else
+ LogRel(("Unattended: TXTSETUP.SIF: unknown: LoadIdentifier=%s\n", pBuf->sz));
+ }
+ RTIniFileRelease(hIniFile);
+ }
+ }
+ }
+
+ if (pszVersion)
+ try { mStrDetectedOSVersion = pszVersion; }
+ catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
+ if (pszProduct)
+ try { mStrDetectedOSFlavor = pszProduct; }
+ catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
+
+ /*
+ * Look for sources/lang.ini and try parse it to get the languages out of it.
+ */
+ /** @todo We could also check sources/??-* and boot/??-* if lang.ini is not
+ * found or unhelpful. */
+ vrc = RTVfsFileOpen(hVfsIso, "sources/lang.ini", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ RTINIFILE hIniFile;
+ vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
+ RTVfsFileRelease(hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ mDetectedOSLanguages.clear();
+
+ uint32_t idxPair;
+ for (idxPair = 0; idxPair < 256; idxPair++)
+ {
+ size_t cbHalf = sizeof(*pBuf) / 2;
+ char *pszKey = pBuf->sz;
+ char *pszValue = &pBuf->sz[cbHalf];
+ vrc = RTIniFileQueryPair(hIniFile, "Available UI Languages", idxPair,
+ pszKey, cbHalf, NULL, pszValue, cbHalf, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ try
+ {
+ mDetectedOSLanguages.append(pszKey);
+ }
+ catch (std::bad_alloc &)
+ {
+ RTIniFileRelease(hIniFile);
+ return E_OUTOFMEMORY;
+ }
+ }
+ else if (vrc == VERR_NOT_FOUND)
+ break;
+ else
+ Assert(vrc == VERR_BUFFER_OVERFLOW);
+ }
+ if (idxPair == 0)
+ LogRel(("Unattended: Warning! Empty 'Available UI Languages' section in sources/lang.ini\n"));
+ RTIniFileRelease(hIniFile);
+ }
+ }
+
+ return S_FALSE;
+}
+
+/**
+ * Architecture strings for Linux and the like.
+ */
+static struct { const char *pszArch; uint32_t cchArch; VBOXOSTYPE fArch; } const g_aLinuxArches[] =
+{
+ { RT_STR_TUPLE("amd64"), VBOXOSTYPE_x64 },
+ { RT_STR_TUPLE("x86_64"), VBOXOSTYPE_x64 },
+ { RT_STR_TUPLE("x86-64"), VBOXOSTYPE_x64 }, /* just in case */
+ { RT_STR_TUPLE("x64"), VBOXOSTYPE_x64 }, /* ditto */
+
+ { RT_STR_TUPLE("x86"), VBOXOSTYPE_x86 },
+ { RT_STR_TUPLE("i386"), VBOXOSTYPE_x86 },
+ { RT_STR_TUPLE("i486"), VBOXOSTYPE_x86 },
+ { RT_STR_TUPLE("i586"), VBOXOSTYPE_x86 },
+ { RT_STR_TUPLE("i686"), VBOXOSTYPE_x86 },
+ { RT_STR_TUPLE("i786"), VBOXOSTYPE_x86 },
+ { RT_STR_TUPLE("i886"), VBOXOSTYPE_x86 },
+ { RT_STR_TUPLE("i986"), VBOXOSTYPE_x86 },
+};
+
+/**
+ * Detects linux architecture.
+ *
+ * @returns true if detected, false if not.
+ * @param pszArch The architecture string.
+ * @param penmOsType Where to return the arch and type on success.
+ * @param enmBaseOsType The base (x86) OS type to return.
+ */
+static bool detectLinuxArch(const char *pszArch, VBOXOSTYPE *penmOsType, VBOXOSTYPE enmBaseOsType)
+{
+ for (size_t i = 0; i < RT_ELEMENTS(g_aLinuxArches); i++)
+ if (RTStrNICmp(pszArch, g_aLinuxArches[i].pszArch, g_aLinuxArches[i].cchArch) == 0)
+ {
+ *penmOsType = (VBOXOSTYPE)(enmBaseOsType | g_aLinuxArches[i].fArch);
+ return true;
+ }
+ /** @todo check for 'noarch' since source CDs have been seen to use that. */
+ return false;
+}
+
+/**
+ * Detects linux architecture by searching for the architecture substring in @p pszArch.
+ *
+ * @returns true if detected, false if not.
+ * @param pszArch The architecture string.
+ * @param penmOsType Where to return the arch and type on success.
+ * @param enmBaseOsType The base (x86) OS type to return.
+ * @param ppszHit Where to return the pointer to the architecture
+ * specifier. Optional.
+ * @param ppszNext Where to return the pointer to the char
+ * following the architecuture specifier. Optional.
+ */
+static bool detectLinuxArchII(const char *pszArch, VBOXOSTYPE *penmOsType, VBOXOSTYPE enmBaseOsType,
+ char **ppszHit = NULL, char **ppszNext = NULL)
+{
+ for (size_t i = 0; i < RT_ELEMENTS(g_aLinuxArches); i++)
+ {
+ const char *pszHit = RTStrIStr(pszArch, g_aLinuxArches[i].pszArch);
+ if (pszHit != NULL)
+ {
+ if (ppszHit)
+ *ppszHit = (char *)pszHit;
+ if (ppszNext)
+ *ppszNext = (char *)pszHit + g_aLinuxArches[i].cchArch;
+ *penmOsType = (VBOXOSTYPE)(enmBaseOsType | g_aLinuxArches[i].fArch);
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool detectLinuxDistroName(const char *pszOsAndVersion, VBOXOSTYPE *penmOsType, const char **ppszNext)
+{
+ bool fRet = true;
+
+ if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Red")) == 0
+ && !RT_C_IS_ALNUM(pszOsAndVersion[3]))
+
+ {
+ pszOsAndVersion = RTStrStripL(pszOsAndVersion + 3);
+ if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Hat")) == 0
+ && !RT_C_IS_ALNUM(pszOsAndVersion[3]))
+ {
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
+ pszOsAndVersion = RTStrStripL(pszOsAndVersion + 3);
+ }
+ else
+ fRet = false;
+ }
+ else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("OpenSUSE")) == 0
+ && !RT_C_IS_ALNUM(pszOsAndVersion[8]))
+ {
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_OpenSUSE);
+ pszOsAndVersion = RTStrStripL(pszOsAndVersion + 8);
+ }
+ else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Oracle")) == 0
+ && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
+ {
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Oracle);
+ pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
+ }
+ else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("CentOS")) == 0
+ && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
+ {
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
+ pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
+ }
+ else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Fedora")) == 0
+ && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
+ {
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_FedoraCore);
+ pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
+ }
+ else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Ubuntu")) == 0
+ && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
+ {
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
+ pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
+ }
+ else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Linux Mint")) == 0
+ && !RT_C_IS_ALNUM(pszOsAndVersion[10]))
+ {
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
+ pszOsAndVersion = RTStrStripL(pszOsAndVersion + 10);
+ }
+ else if ( ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Xubuntu")) == 0
+ || RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Kubuntu")) == 0
+ || RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Lubuntu")) == 0)
+ && !RT_C_IS_ALNUM(pszOsAndVersion[7]))
+ {
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
+ pszOsAndVersion = RTStrStripL(pszOsAndVersion + 7);
+ }
+ else if ( RTStrNICmp(pszOsAndVersion, RT_STR_TUPLE("Debian")) == 0
+ && !RT_C_IS_ALNUM(pszOsAndVersion[6]))
+ {
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Debian);
+ pszOsAndVersion = RTStrStripL(pszOsAndVersion + 6);
+ }
+ else
+ fRet = false;
+
+ /*
+ * Skip forward till we get a number.
+ */
+ if (ppszNext)
+ {
+ *ppszNext = pszOsAndVersion;
+ char ch;
+ for (const char *pszVersion = pszOsAndVersion; (ch = *pszVersion) != '\0'; pszVersion++)
+ if (RT_C_IS_DIGIT(ch))
+ {
+ *ppszNext = pszVersion;
+ break;
+ }
+ }
+ return fRet;
+}
+
+static bool detectLinuxDistroNameII(const char *pszOsAndVersion, VBOXOSTYPE *penmOsType, const char **ppszNext)
+{
+ bool fRet = true;
+ if ( RTStrIStr(pszOsAndVersion, "RedHat") != NULL
+ || RTStrIStr(pszOsAndVersion, "Red Hat") != NULL)
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
+ else if (RTStrIStr(pszOsAndVersion, "Oracle") != NULL)
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Oracle);
+ else if (RTStrIStr(pszOsAndVersion, "CentOS") != NULL)
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
+ else if (RTStrIStr(pszOsAndVersion, "Fedora") != NULL)
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_FedoraCore);
+ else if (RTStrIStr(pszOsAndVersion, "Ubuntu") != NULL)
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
+ else if (RTStrIStr(pszOsAndVersion, "Mint") != NULL)
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Ubuntu);
+ else if (RTStrIStr(pszOsAndVersion, "Debian"))
+ *penmOsType = (VBOXOSTYPE)((*penmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_Debian);
+ else
+ fRet = false;
+
+ /*
+ * Skip forward till we get a number.
+ */
+ if (ppszNext)
+ {
+ *ppszNext = pszOsAndVersion;
+ char ch;
+ for (const char *pszVersion = pszOsAndVersion; (ch = *pszVersion) != '\0'; pszVersion++)
+ if (RT_C_IS_DIGIT(ch))
+ {
+ *ppszNext = pszVersion;
+ break;
+ }
+ }
+ return fRet;
+}
+
+
+/**
+ * Helps detecting linux distro flavor by finding substring position of non numerical
+ * part of the disk name.
+ *
+ * @returns true if detected, false if not.
+ * @param pszDiskName Name of the disk as it is read from .disk/info or
+ * README.diskdefines file.
+ * @param poffVersion String position where first numerical character is
+ * found. We use substring upto this position as OS flavor
+ */
+static bool detectLinuxDistroFlavor(const char *pszDiskName, size_t *poffVersion)
+{
+ Assert(poffVersion);
+ if (!pszDiskName)
+ return false;
+ char ch;
+ while ((ch = *pszDiskName) != '\0' && !RT_C_IS_DIGIT(ch))
+ {
+ ++pszDiskName;
+ *poffVersion += 1;
+ }
+ return true;
+}
+
+/**
+ * Detect Linux distro ISOs.
+ *
+ * @returns COM status code.
+ * @retval S_OK if detected
+ * @retval S_FALSE if not fully detected.
+ *
+ * @param hVfsIso The ISO file system.
+ * @param pBuf Read buffer.
+ */
+HRESULT Unattended::i_innerDetectIsoOSLinux(RTVFS hVfsIso, DETECTBUFFER *pBuf)
+{
+ /*
+ * Redhat and derivatives may have a .treeinfo (ini-file style) with useful info
+ * or at least a barebone .discinfo file.
+ */
+
+ /*
+ * Start with .treeinfo: https://release-engineering.github.io/productmd/treeinfo-1.0.html
+ */
+ RTVFSFILE hVfsFile;
+ int vrc = RTVfsFileOpen(hVfsIso, ".treeinfo", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ RTINIFILE hIniFile;
+ vrc = RTIniFileCreateFromVfsFile(&hIniFile, hVfsFile, RTINIFILE_F_READONLY);
+ RTVfsFileRelease(hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Try figure the architecture first (like with windows). */
+ vrc = RTIniFileQueryValue(hIniFile, "tree", "arch", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_FAILURE(vrc) || !pBuf->sz[0])
+ vrc = RTIniFileQueryValue(hIniFile, "general", "arch", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_FAILURE(vrc))
+ LogRel(("Unattended: .treeinfo: No 'arch' property.\n"));
+ else
+ {
+ LogRelFlow(("Unattended: .treeinfo: arch=%s\n", pBuf->sz));
+ if (detectLinuxArch(pBuf->sz, &mEnmOsType, VBOXOSTYPE_RedHat))
+ {
+ /* Try figure the release name, it doesn't have to be redhat. */
+ vrc = RTIniFileQueryValue(hIniFile, "release", "name", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_FAILURE(vrc) || !pBuf->sz[0])
+ vrc = RTIniFileQueryValue(hIniFile, "product", "name", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_FAILURE(vrc) || !pBuf->sz[0])
+ vrc = RTIniFileQueryValue(hIniFile, "general", "family", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRelFlow(("Unattended: .treeinfo: name/family=%s\n", pBuf->sz));
+ if (!detectLinuxDistroName(pBuf->sz, &mEnmOsType, NULL))
+ {
+ LogRel(("Unattended: .treeinfo: Unknown: name/family='%s', assuming Red Hat\n", pBuf->sz));
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_RedHat);
+ }
+ }
+
+ /* Try figure the version. */
+ vrc = RTIniFileQueryValue(hIniFile, "release", "version", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_FAILURE(vrc) || !pBuf->sz[0])
+ vrc = RTIniFileQueryValue(hIniFile, "product", "version", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_FAILURE(vrc) || !pBuf->sz[0])
+ vrc = RTIniFileQueryValue(hIniFile, "general", "version", pBuf->sz, sizeof(*pBuf), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRelFlow(("Unattended: .treeinfo: version=%s\n", pBuf->sz));
+ try { mStrDetectedOSVersion = RTStrStrip(pBuf->sz); }
+ catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
+
+ size_t cchVersionPosition = 0;
+ if (detectLinuxDistroFlavor(pBuf->sz, &cchVersionPosition))
+ {
+ try { mStrDetectedOSFlavor = Utf8Str(pBuf->sz, cchVersionPosition); }
+ catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
+ }
+ }
+ }
+ else
+ LogRel(("Unattended: .treeinfo: Unknown: arch='%s'\n", pBuf->sz));
+ }
+
+ RTIniFileRelease(hIniFile);
+ }
+
+ if (mEnmOsType != VBOXOSTYPE_Unknown)
+ return S_FALSE;
+ }
+
+ /*
+ * Try .discinfo next: https://release-engineering.github.io/productmd/discinfo-1.0.html
+ * We will probably need additional info here...
+ */
+ vrc = RTVfsFileOpen(hVfsIso, ".discinfo", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ size_t cchIgn;
+ vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn);
+ pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0';
+ RTVfsFileRelease(hVfsFile);
+
+ /* Parse and strip the first 5 lines. */
+ const char *apszLines[5];
+ char *psz = pBuf->sz;
+ for (unsigned i = 0; i < RT_ELEMENTS(apszLines); i++)
+ {
+ apszLines[i] = psz;
+ if (*psz)
+ {
+ char *pszEol = (char *)strchr(psz, '\n');
+ if (!pszEol)
+ psz = strchr(psz, '\0');
+ else
+ {
+ *pszEol = '\0';
+ apszLines[i] = RTStrStrip(psz);
+ psz = pszEol + 1;
+ }
+ }
+ }
+
+ /* Do we recognize the architecture? */
+ LogRelFlow(("Unattended: .discinfo: arch=%s\n", apszLines[2]));
+ if (detectLinuxArch(apszLines[2], &mEnmOsType, VBOXOSTYPE_RedHat))
+ {
+ /* Do we recognize the release string? */
+ LogRelFlow(("Unattended: .discinfo: product+version=%s\n", apszLines[1]));
+ const char *pszVersion = NULL;
+ if (!detectLinuxDistroName(apszLines[1], &mEnmOsType, &pszVersion))
+ LogRel(("Unattended: .discinfo: Unknown: release='%s'\n", apszLines[1]));
+
+ if (*pszVersion)
+ {
+ LogRelFlow(("Unattended: .discinfo: version=%s\n", pszVersion));
+ try { mStrDetectedOSVersion = RTStrStripL(pszVersion); }
+ catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
+
+ /* CentOS likes to call their release 'Final' without mentioning the actual version
+ number (e.g. CentOS-4.7-x86_64-binDVD.iso), so we need to go look elsewhere.
+ This is only important for centos 4.x and 3.x releases. */
+ if (RTStrNICmp(pszVersion, RT_STR_TUPLE("Final")) == 0)
+ {
+ static const char * const s_apszDirs[] = { "CentOS/RPMS/", "RedHat/RPMS", "Server", "Workstation" };
+ for (unsigned iDir = 0; iDir < RT_ELEMENTS(s_apszDirs); iDir++)
+ {
+ RTVFSDIR hVfsDir;
+ vrc = RTVfsDirOpen(hVfsIso, s_apszDirs[iDir], 0, &hVfsDir);
+ if (RT_FAILURE(vrc))
+ continue;
+ char szRpmDb[128];
+ char szReleaseRpm[128];
+ szRpmDb[0] = '\0';
+ szReleaseRpm[0] = '\0';
+ for (;;)
+ {
+ RTDIRENTRYEX DirEntry;
+ size_t cbDirEntry = sizeof(DirEntry);
+ vrc = RTVfsDirReadEx(hVfsDir, &DirEntry, &cbDirEntry, RTFSOBJATTRADD_NOTHING);
+ if (RT_FAILURE(vrc))
+ break;
+
+ /* redhat-release-4WS-2.4.i386.rpm
+ centos-release-4-7.x86_64.rpm, centos-release-4-4.3.i386.rpm
+ centos-release-5-3.el5.centos.1.x86_64.rpm */
+ if ( (psz = strstr(DirEntry.szName, "-release-")) != NULL
+ || (psz = strstr(DirEntry.szName, "-RELEASE-")) != NULL)
+ {
+ psz += 9;
+ if (RT_C_IS_DIGIT(*psz))
+ RTStrCopy(szReleaseRpm, sizeof(szReleaseRpm), psz);
+ }
+ /* rpmdb-redhat-4WS-2.4.i386.rpm,
+ rpmdb-CentOS-4.5-0.20070506.i386.rpm,
+ rpmdb-redhat-3.9-0.20070703.i386.rpm. */
+ else if ( ( RTStrStartsWith(DirEntry.szName, "rpmdb-")
+ || RTStrStartsWith(DirEntry.szName, "RPMDB-"))
+ && RT_C_IS_DIGIT(DirEntry.szName[6]) )
+ RTStrCopy(szRpmDb, sizeof(szRpmDb), &DirEntry.szName[6]);
+ }
+ RTVfsDirRelease(hVfsDir);
+
+ /* Did we find anything relvant? */
+ psz = szRpmDb;
+ if (!RT_C_IS_DIGIT(*psz))
+ psz = szReleaseRpm;
+ if (RT_C_IS_DIGIT(*psz))
+ {
+ /* Convert '-' to '.' and strip stuff which doesn't look like a version string. */
+ char *pszCur = psz + 1;
+ for (char ch = *pszCur; ch != '\0'; ch = *++pszCur)
+ if (ch == '-')
+ *pszCur = '.';
+ else if (ch != '.' && !RT_C_IS_DIGIT(ch))
+ {
+ *pszCur = '\0';
+ break;
+ }
+ while (&pszCur[-1] != psz && pszCur[-1] == '.')
+ *--pszCur = '\0';
+
+ /* Set it and stop looking. */
+ try { mStrDetectedOSVersion = psz; }
+ catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
+ break;
+ }
+ }
+ }
+ }
+ size_t cchVersionPosition = 0;
+ if (detectLinuxDistroFlavor(apszLines[1], &cchVersionPosition))
+ {
+ try { mStrDetectedOSFlavor = Utf8Str(apszLines[1], cchVersionPosition); }
+ catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
+ }
+ }
+ else
+ LogRel(("Unattended: .discinfo: Unknown: arch='%s'\n", apszLines[2]));
+
+ if (mEnmOsType != VBOXOSTYPE_Unknown)
+ return S_FALSE;
+ }
+
+ /*
+ * Ubuntu has a README.diskdefines file on their ISO (already on 4.10 / warty warthog).
+ * Example content:
+ * #define DISKNAME Ubuntu 4.10 "Warty Warthog" - Preview amd64 Binary-1
+ * #define TYPE binary
+ * #define TYPEbinary 1
+ * #define ARCH amd64
+ * #define ARCHamd64 1
+ * #define DISKNUM 1
+ * #define DISKNUM1 1
+ * #define TOTALNUM 1
+ * #define TOTALNUM1 1
+ */
+ vrc = RTVfsFileOpen(hVfsIso, "README.diskdefines", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ size_t cchIgn;
+ vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn);
+ pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0';
+ RTVfsFileRelease(hVfsFile);
+
+ /* Find the DISKNAME and ARCH defines. */
+ const char *pszDiskName = NULL;
+ const char *pszArch = NULL;
+ char *psz = pBuf->sz;
+ for (unsigned i = 0; *psz != '\0'; i++)
+ {
+ while (RT_C_IS_BLANK(*psz))
+ psz++;
+
+ /* Match #define: */
+ static const char s_szDefine[] = "#define";
+ if ( strncmp(psz, s_szDefine, sizeof(s_szDefine) - 1) == 0
+ && RT_C_IS_BLANK(psz[sizeof(s_szDefine) - 1]))
+ {
+ psz = &psz[sizeof(s_szDefine) - 1];
+ while (RT_C_IS_BLANK(*psz))
+ psz++;
+
+ /* Match the identifier: */
+ char *pszIdentifier = psz;
+ if (RT_C_IS_ALPHA(*psz) || *psz == '_')
+ {
+ do
+ psz++;
+ while (RT_C_IS_ALNUM(*psz) || *psz == '_');
+ size_t cchIdentifier = (size_t)(psz - pszIdentifier);
+
+ /* Skip to the value. */
+ while (RT_C_IS_BLANK(*psz))
+ psz++;
+ char *pszValue = psz;
+
+ /* Skip to EOL and strip the value. */
+ char *pszEol = psz = strchr(psz, '\n');
+ if (psz)
+ *psz++ = '\0';
+ else
+ pszEol = strchr(pszValue, '\0');
+ while (pszEol > pszValue && RT_C_IS_SPACE(pszEol[-1]))
+ *--pszEol = '\0';
+
+ LogRelFlow(("Unattended: README.diskdefines: %.*s=%s\n", cchIdentifier, pszIdentifier, pszValue));
+
+ /* Do identifier matching: */
+ if (cchIdentifier == sizeof("DISKNAME") - 1 && strncmp(pszIdentifier, RT_STR_TUPLE("DISKNAME")) == 0)
+ pszDiskName = pszValue;
+ else if (cchIdentifier == sizeof("ARCH") - 1 && strncmp(pszIdentifier, RT_STR_TUPLE("ARCH")) == 0)
+ pszArch = pszValue;
+ else
+ continue;
+ if (pszDiskName == NULL || pszArch == NULL)
+ continue;
+ break;
+ }
+ }
+
+ /* Next line: */
+ psz = strchr(psz, '\n');
+ if (!psz)
+ break;
+ psz++;
+ }
+
+ /* Did we find both of them? */
+ if (pszDiskName && pszArch)
+ {
+ if (detectLinuxArch(pszArch, &mEnmOsType, VBOXOSTYPE_Ubuntu))
+ {
+ const char *pszVersion = NULL;
+ if (detectLinuxDistroName(pszDiskName, &mEnmOsType, &pszVersion))
+ {
+ LogRelFlow(("Unattended: README.diskdefines: version=%s\n", pszVersion));
+ try { mStrDetectedOSVersion = RTStrStripL(pszVersion); }
+ catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
+
+ size_t cchVersionPosition = 0;
+ if (detectLinuxDistroFlavor(pszDiskName, &cchVersionPosition))
+ {
+ try { mStrDetectedOSFlavor = Utf8Str(pszDiskName, cchVersionPosition); }
+ catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
+ }
+ }
+ else
+ LogRel(("Unattended: README.diskdefines: Unknown: diskname='%s'\n", pszDiskName));
+ }
+ else
+ LogRel(("Unattended: README.diskdefines: Unknown: arch='%s'\n", pszArch));
+ }
+ else
+ LogRel(("Unattended: README.diskdefines: Did not find both DISKNAME and ARCH. :-/\n"));
+
+ if (mEnmOsType != VBOXOSTYPE_Unknown)
+ return S_FALSE;
+ }
+
+ /*
+ * All of the debian based distro versions I checked have a single line ./disk/info
+ * file. Only info I could find related to .disk folder is:
+ * https://lists.debian.org/debian-cd/2004/01/msg00069.html
+ *
+ * Some example content from several install ISOs is as follows:
+ * Ubuntu 4.10 "Warty Warthog" - Preview amd64 Binary-1 (20041020)
+ * Linux Mint 20.3 "Una" - Release amd64 20220104
+ * Debian GNU/Linux 11.2.0 "Bullseye" - Official amd64 NETINST 20211218-11:12
+ * Debian GNU/Linux 9.13.0 "Stretch" - Official amd64 DVD Binary-1 20200718-11:07
+ * Xubuntu 20.04.2.0 LTS "Focal Fossa" - Release amd64 (20210209.1)
+ * Ubuntu 17.10 "Artful Aardvark" - Release amd64 (20180105.1)
+ * Ubuntu 16.04.6 LTS "Xenial Xerus" - Release i386 (20190227.1)
+ * Debian GNU/Linux 8.11.1 "Jessie" - Official amd64 CD Binary-1 20190211-02:10
+ * Kali GNU/Linux 2021.3a "Kali-last-snapshot" - Official amd64 BD Binary-1 with firmware 20211015-16:55
+ * Official Debian GNU/Linux Live 10.10.0 cinnamon 2021-06-19T12:13
+ */
+ vrc = RTVfsFileOpen(hVfsIso, ".disk/info", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ size_t cchIgn;
+ vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(*pBuf) - 1, &cchIgn);
+ pBuf->sz[RT_SUCCESS(vrc) ? cchIgn : 0] = '\0';
+
+ pBuf->sz[sizeof(*pBuf) - 1] = '\0';
+ RTVfsFileRelease(hVfsFile);
+
+ char *psz = pBuf->sz;
+ char *pszDiskName = psz;
+ char *pszArch = NULL;
+
+ /* Only care about the first line of the file even if it is multi line and assume disk name ended with ' - '.*/
+ psz = RTStrStr(pBuf->sz, " - ");
+ if (psz && memchr(pBuf->sz, '\n', (size_t)(psz - pBuf->sz)) == NULL)
+ {
+ *psz = '\0';
+ psz += 3;
+ if (*psz)
+ pszArch = psz;
+ }
+
+ /* Some Debian Live ISO's have info file content as follows:
+ * Official Debian GNU/Linux Live 10.10.0 cinnamon 2021-06-19T12:13
+ * thus pszArch stays empty. Try Volume Id (label) if we get lucky and get architecture from that. */
+ if (!pszArch)
+ {
+ char szVolumeId[128];
+ vrc = RTVfsQueryLabel(hVfsIso, false /*fAlternative*/, szVolumeId, sizeof(szVolumeId), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!detectLinuxArchII(szVolumeId, &mEnmOsType, VBOXOSTYPE_Ubuntu))
+ LogRel(("Unattended: .disk/info: Unknown: arch='%s'\n", szVolumeId));
+ }
+ else
+ LogRel(("Unattended: .disk/info No Volume Label found\n"));
+ }
+ else
+ {
+ if (!detectLinuxArchII(pszArch, &mEnmOsType, VBOXOSTYPE_Ubuntu))
+ LogRel(("Unattended: .disk/info: Unknown: arch='%s'\n", pszArch));
+ }
+
+ if (pszDiskName)
+ {
+ const char *pszVersion = NULL;
+ if (detectLinuxDistroNameII(pszDiskName, &mEnmOsType, &pszVersion))
+ {
+ LogRelFlow(("Unattended: .disk/info: version=%s\n", pszVersion));
+ try { mStrDetectedOSVersion = RTStrStripL(pszVersion); }
+ catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
+
+ size_t cchVersionPosition = 0;
+ if (detectLinuxDistroFlavor(pszDiskName, &cchVersionPosition))
+ {
+ try { mStrDetectedOSFlavor = Utf8Str(pszDiskName, cchVersionPosition); }
+ catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
+ }
+ }
+ else
+ LogRel(("Unattended: .disk/info: Unknown: diskname='%s'\n", pszDiskName));
+ }
+
+ if (mEnmOsType == VBOXOSTYPE_Unknown)
+ LogRel(("Unattended: .disk/info: Did not find DISKNAME or/and ARCH. :-/\n"));
+ else
+ return S_FALSE;
+ }
+
+ /*
+ * Fedora live iso should be recognizable from the primary volume ID (the
+ * joliet one is usually truncated). We set fAlternative = true here to
+ * get the primary volume ID.
+ */
+ char szVolumeId[128];
+ vrc = RTVfsQueryLabel(hVfsIso, true /*fAlternative*/, szVolumeId, sizeof(szVolumeId), NULL);
+ if (RT_SUCCESS(vrc) && RTStrStartsWith(szVolumeId, "Fedora-"))
+ return i_innerDetectIsoOSLinuxFedora(hVfsIso, pBuf, &szVolumeId[sizeof("Fedora-") - 1]);
+ return S_FALSE;
+}
+
+
+/**
+ * Continues working a Fedora ISO image after the caller found a "Fedora-*"
+ * volume ID.
+ *
+ * Sample Volume IDs:
+ * - Fedora-WS-Live-34-1-2 (joliet: Fedora-WS-Live-3)
+ * - Fedora-S-dvd-x86_64-34 (joliet: Fedora-S-dvd-x86)
+ * - Fedora-WS-dvd-i386-25 (joliet: Fedora-WS-dvd-i3)
+ */
+HRESULT Unattended::i_innerDetectIsoOSLinuxFedora(RTVFS hVfsIso, DETECTBUFFER *pBuf, char *pszVolId)
+{
+ char * const pszFlavor = pszVolId;
+ char * psz = pszVolId;
+
+ /* The volume id may or may not include an arch, component.
+ We ASSUME that it includes a numeric part with the version, or at least
+ part of it. */
+ char *pszVersion = NULL;
+ char *pszArch = NULL;
+ if (detectLinuxArchII(psz, &mEnmOsType, VBOXOSTYPE_FedoraCore, &pszArch, &pszVersion))
+ {
+ while (*pszVersion == '-')
+ pszVersion++;
+ *pszArch = '\0';
+ }
+ else
+ {
+ mEnmOsType = (VBOXOSTYPE)(VBOXOSTYPE_FedoraCore | VBOXOSTYPE_UnknownArch);
+
+ char ch;
+ while ((ch = *psz) != '\0' && (!RT_C_IS_DIGIT(ch) || !RT_C_IS_PUNCT(psz[-1])))
+ psz++;
+ if (ch != '\0')
+ pszVersion = psz;
+ }
+
+ /*
+ * Replace '-' with '.' in the version part and use it as the version.
+ */
+ if (pszVersion)
+ {
+ psz = pszVersion;
+ while ((psz = strchr(psz, '-')) != NULL)
+ *psz++ = '.';
+ try { mStrDetectedOSVersion = RTStrStrip(pszVersion); }
+ catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
+
+ *pszVersion = '\0'; /* don't include in flavor */
+ }
+
+ /*
+ * Split up the pre-arch/version bits into words and use them as the flavor.
+ */
+ psz = pszFlavor;
+ while ((psz = strchr(psz, '-')) != NULL)
+ *psz++ = ' ';
+ try { mStrDetectedOSFlavor = RTStrStrip(pszFlavor); }
+ catch (std::bad_alloc &) { return E_OUTOFMEMORY; }
+
+ /*
+ * If we don't have an architecture, we look at the vmlinuz file as the x86
+ * and AMD64 versions starts with a MZ+PE header giving the architecture.
+ */
+ if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) == VBOXOSTYPE_UnknownArch)
+ {
+ static const char * const s_apszVmLinuz[] = { "images/pxeboot/vmlinuz", "isolinux/vmlinuz" };
+ for (size_t i = 0; i < RT_ELEMENTS(s_apszVmLinuz); i++)
+ {
+ RTVFSFILE hVfsFileLinuz = NIL_RTVFSFILE;
+ int vrc = RTVfsFileOpen(hVfsIso, s_apszVmLinuz[i], RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ &hVfsFileLinuz);
+ if (RT_SUCCESS(vrc))
+ {
+ /* DOS signature: */
+ PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)&pBuf->ab[0];
+ AssertCompile(sizeof(*pBuf) > sizeof(*pDosHdr));
+ vrc = RTVfsFileReadAt(hVfsFileLinuz, 0, pDosHdr, sizeof(*pDosHdr), NULL);
+ if (RT_SUCCESS(vrc) && pDosHdr->e_magic == IMAGE_DOS_SIGNATURE)
+ {
+ /* NT signature - only need magic + file header, so use the 64 version for better debugging: */
+ PIMAGE_NT_HEADERS64 pNtHdrs = (PIMAGE_NT_HEADERS64)&pBuf->ab[0];
+ vrc = RTVfsFileReadAt(hVfsFileLinuz, pDosHdr->e_lfanew, pNtHdrs, sizeof(*pNtHdrs), NULL);
+ AssertCompile(sizeof(*pBuf) > sizeof(*pNtHdrs));
+ if (RT_SUCCESS(vrc) && pNtHdrs->Signature == IMAGE_NT_SIGNATURE)
+ {
+ if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_x86);
+ else if (pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64)
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | VBOXOSTYPE_x64);
+ else
+ AssertFailed();
+ }
+ }
+
+ RTVfsFileRelease(hVfsFileLinuz);
+ if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_UnknownArch)
+ break;
+ }
+ }
+ }
+
+ /*
+ * If that failed, look for other files that gives away the arch.
+ */
+ if ((mEnmOsType & VBOXOSTYPE_ArchitectureMask) == VBOXOSTYPE_UnknownArch)
+ {
+ static struct { const char *pszFile; VBOXOSTYPE fArch; } const s_aArchSpecificFiles[] =
+ {
+ { "EFI/BOOT/grubaa64.efi", VBOXOSTYPE_arm64 },
+ { "EFI/BOOT/BOOTAA64.EFI", VBOXOSTYPE_arm64 },
+ };
+ PRTFSOBJINFO pObjInfo = (PRTFSOBJINFO)&pBuf->ab[0];
+ AssertCompile(sizeof(*pBuf) > sizeof(*pObjInfo));
+ for (size_t i = 0; i < RT_ELEMENTS(s_aArchSpecificFiles); i++)
+ {
+ int vrc = RTVfsQueryPathInfo(hVfsIso, s_aArchSpecificFiles[i].pszFile, pObjInfo,
+ RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(vrc) && RTFS_IS_FILE(pObjInfo->Attr.fMode))
+ {
+ mEnmOsType = (VBOXOSTYPE)((mEnmOsType & ~VBOXOSTYPE_ArchitectureMask) | s_aArchSpecificFiles[i].fArch);
+ break;
+ }
+ }
+ }
+
+ /*
+ * If we like, we could parse grub.conf to look for fullly spelled out
+ * flavor, though the menu items typically only contains the major version
+ * number, so little else to add, really.
+ */
+
+ return (mEnmOsType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_UnknownArch ? S_OK : S_FALSE;
+}
+
+
+/**
+ * Detect OS/2 installation ISOs.
+ *
+ * Mainly aiming at ACP2/MCP2 as that's what we currently use in our testing.
+ *
+ * @returns COM status code.
+ * @retval S_OK if detected
+ * @retval S_FALSE if not fully detected.
+ *
+ * @param hVfsIso The ISO file system.
+ * @param pBuf Read buffer.
+ */
+HRESULT Unattended::i_innerDetectIsoOSOs2(RTVFS hVfsIso, DETECTBUFFER *pBuf)
+{
+ /*
+ * The OS2SE20.SRC contains the location of the tree with the diskette
+ * images, typically "\OS2IMAGE".
+ */
+ RTVFSFILE hVfsFile;
+ int vrc = RTVfsFileOpen(hVfsIso, "OS2SE20.SRC", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ size_t cbRead = 0;
+ vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
+ RTVfsFileRelease(hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ pBuf->sz[cbRead] = '\0';
+ RTStrStrip(pBuf->sz);
+ vrc = RTStrValidateEncoding(pBuf->sz);
+ if (RT_SUCCESS(vrc))
+ LogRelFlow(("Unattended: OS2SE20.SRC=%s\n", pBuf->sz));
+ else
+ LogRel(("Unattended: OS2SE20.SRC invalid encoding: %Rrc, %.*Rhxs\n", vrc, cbRead, pBuf->sz));
+ }
+ else
+ LogRel(("Unattended: Error reading OS2SE20.SRC: %\n", vrc));
+ }
+ /*
+ * ArcaOS has dropped the file, assume it's \OS2IMAGE and see if it's there.
+ */
+ else if (vrc == VERR_FILE_NOT_FOUND)
+ RTStrCopy(pBuf->sz, sizeof(pBuf->sz), "\\OS2IMAGE");
+ else
+ return S_FALSE;
+
+ /*
+ * Check that the directory directory exists and has a DISK_0 under it
+ * with an OS2LDR on it.
+ */
+ size_t const cchOs2Image = strlen(pBuf->sz);
+ vrc = RTPathAppend(pBuf->sz, sizeof(pBuf->sz), "DISK_0/OS2LDR");
+ RTFSOBJINFO ObjInfo = {0};
+ vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (vrc == VERR_FILE_NOT_FOUND)
+ {
+ RTStrCat(pBuf->sz, sizeof(pBuf->sz), "."); /* eCS 2.0 image includes the dot from the 8.3 name. */
+ vrc = RTVfsQueryPathInfo(hVfsIso, pBuf->sz, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ }
+ if ( RT_FAILURE(vrc)
+ || !RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ {
+ LogRel(("Unattended: RTVfsQueryPathInfo(, '%s' (from OS2SE20.SRC),) -> %Rrc, fMode=%#x\n",
+ pBuf->sz, vrc, ObjInfo.Attr.fMode));
+ return S_FALSE;
+ }
+
+ /*
+ * So, it's some kind of OS/2 2.x or later ISO alright.
+ */
+ mEnmOsType = VBOXOSTYPE_OS2;
+ mStrDetectedOSHints.printf("OS2SE20.SRC=%.*s", cchOs2Image, pBuf->sz);
+
+ /*
+ * ArcaOS ISOs seems to have a AOSBOOT dir on them.
+ * This contains a ARCANOAE.FLG file with content we can use for the version:
+ * ArcaOS 5.0.7 EN
+ * Built 2021-12-07 18:34:34
+ * We drop the "ArcaOS" bit, as it's covered by mEnmOsType. Then we pull up
+ * the second line.
+ *
+ * Note! Yet to find a way to do unattended install of ArcaOS, as it comes
+ * with no CD-boot floppy images, only simple .PF archive files for
+ * unpacking onto the ram disk or whatever. Modifying these is
+ * possible (ibsen's aPLib v0.36 compression with some simple custom
+ * headers), but it would probably be a royal pain. Could perhaps
+ * cook something from OS2IMAGE\DISK_0 thru 3...
+ */
+ vrc = RTVfsQueryPathInfo(hVfsIso, "AOSBOOT", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if ( RT_SUCCESS(vrc)
+ && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ {
+ mEnmOsType = VBOXOSTYPE_ArcaOS;
+
+ /* Read the version file: */
+ vrc = RTVfsFileOpen(hVfsIso, "SYS/ARCANOAE.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ size_t cbRead = 0;
+ vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
+ RTVfsFileRelease(hVfsFile);
+ pBuf->sz[cbRead] = '\0';
+ if (RT_SUCCESS(vrc))
+ {
+ /* Strip the OS name: */
+ char *pszVersion = RTStrStrip(pBuf->sz);
+ static char s_szArcaOS[] = "ArcaOS";
+ if (RTStrStartsWith(pszVersion, s_szArcaOS))
+ pszVersion = RTStrStripL(pszVersion + sizeof(s_szArcaOS) - 1);
+
+ /* Pull up the 2nd line if it, condensing the \r\n into a single space. */
+ char *pszNewLine = strchr(pszVersion, '\n');
+ if (pszNewLine && RTStrStartsWith(pszNewLine + 1, "Built 20"))
+ {
+ size_t offRemove = 0;
+ while (RT_C_IS_SPACE(pszNewLine[-1 - (ssize_t)offRemove]))
+ offRemove++;
+ if (offRemove > 0)
+ {
+ pszNewLine -= offRemove;
+ memmove(pszNewLine, pszNewLine + offRemove, strlen(pszNewLine + offRemove) - 1);
+ }
+ *pszNewLine = ' ';
+ }
+
+ /* Drop any additional lines: */
+ pszNewLine = strchr(pszVersion, '\n');
+ if (pszNewLine)
+ *pszNewLine = '\0';
+ RTStrStripR(pszVersion);
+
+ /* Done (hope it makes some sense). */
+ mStrDetectedOSVersion = pszVersion;
+ }
+ else
+ LogRel(("Unattended: failed to read AOSBOOT/ARCANOAE.FLG: %Rrc\n", vrc));
+ }
+ else
+ LogRel(("Unattended: failed to open AOSBOOT/ARCANOAE.FLG for reading: %Rrc\n", vrc));
+ }
+ /*
+ * Similarly, eCS has an ECS directory and it typically contains a
+ * ECS_INST.FLG file with the version info. Content differs a little:
+ * eComStation 2.0 EN_US Thu May 13 10:27:54 pm 2010
+ * Built on ECS60441318
+ * Here we drop the "eComStation" bit and leave the 2nd line as it.
+ *
+ * Note! At least 2.0 has a DISKIMGS folder with what looks like boot
+ * disks, so we could probably get something going here without
+ * needing to write an OS2 boot sector...
+ */
+ else
+ {
+ vrc = RTVfsQueryPathInfo(hVfsIso, "ECS", &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if ( RT_SUCCESS(vrc)
+ && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ {
+ mEnmOsType = VBOXOSTYPE_ECS;
+
+ /* Read the version file: */
+ vrc = RTVfsFileOpen(hVfsIso, "ECS/ECS_INST.FLG", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ size_t cbRead = 0;
+ vrc = RTVfsFileRead(hVfsFile, pBuf->sz, sizeof(pBuf->sz) - 1, &cbRead);
+ RTVfsFileRelease(hVfsFile);
+ pBuf->sz[cbRead] = '\0';
+ if (RT_SUCCESS(vrc))
+ {
+ /* Strip the OS name: */
+ char *pszVersion = RTStrStrip(pBuf->sz);
+ static char s_szECS[] = "eComStation";
+ if (RTStrStartsWith(pszVersion, s_szECS))
+ pszVersion = RTStrStripL(pszVersion + sizeof(s_szECS) - 1);
+
+ /* Drop any additional lines: */
+ char *pszNewLine = strchr(pszVersion, '\n');
+ if (pszNewLine)
+ *pszNewLine = '\0';
+ RTStrStripR(pszVersion);
+
+ /* Done (hope it makes some sense). */
+ mStrDetectedOSVersion = pszVersion;
+ }
+ else
+ LogRel(("Unattended: failed to read ECS/ECS_INST.FLG: %Rrc\n", vrc));
+ }
+ else
+ LogRel(("Unattended: failed to open ECS/ECS_INST.FLG for reading: %Rrc\n", vrc));
+ }
+ else
+ {
+ /*
+ * Official IBM OS/2 builds doesn't have any .FLG file on them,
+ * so need to pry the information out in some other way. Best way
+ * is to read the SYSLEVEL.OS2 file, which is typically on disk #2,
+ * though on earlier versions (warp3) it was disk #1.
+ */
+ vrc = RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1,
+ "/DISK_2/SYSLEVEL.OS2");
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (vrc == VERR_FILE_NOT_FOUND)
+ {
+ RTPathJoin(pBuf->sz, sizeof(pBuf->sz), strchr(mStrDetectedOSHints.c_str(), '=') + 1, "/DISK_1/SYSLEVEL.OS2");
+ vrc = RTVfsFileOpen(hVfsIso, pBuf->sz, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ RT_ZERO(pBuf->ab);
+ size_t cbRead = 0;
+ vrc = RTVfsFileRead(hVfsFile, pBuf->ab, sizeof(pBuf->ab), &cbRead);
+ RTVfsFileRelease(hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Check the header. */
+ OS2SYSLEVELHDR const *pHdr = (OS2SYSLEVELHDR const *)&pBuf->ab[0];
+ if ( pHdr->uMinusOne == UINT16_MAX
+ && pHdr->uSyslevelFileVer == 1
+ && memcmp(pHdr->achSignature, RT_STR_TUPLE("SYSLEVEL")) == 0
+ && pHdr->offTable < cbRead
+ && pHdr->offTable + sizeof(OS2SYSLEVELENTRY) <= cbRead)
+ {
+ OS2SYSLEVELENTRY *pEntry = (OS2SYSLEVELENTRY *)&pBuf->ab[pHdr->offTable];
+ if ( RT_SUCCESS(RTStrValidateEncodingEx(pEntry->szName, sizeof(pEntry->szName),
+ RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED))
+ && RT_SUCCESS(RTStrValidateEncodingEx(pEntry->achCsdLevel, sizeof(pEntry->achCsdLevel), 0))
+ && pEntry->bVersion != 0
+ && ((pEntry->bVersion >> 4) & 0xf) < 10
+ && (pEntry->bVersion & 0xf) < 10
+ && pEntry->bModify < 10
+ && pEntry->bRefresh < 10)
+ {
+ /* Flavor: */
+ char *pszName = RTStrStrip(pEntry->szName);
+ if (pszName)
+ mStrDetectedOSFlavor = pszName;
+
+ /* Version: */
+ if (pEntry->bRefresh != 0)
+ mStrDetectedOSVersion.printf("%d.%d%d.%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf,
+ pEntry->bModify, pEntry->bRefresh);
+ else
+ mStrDetectedOSVersion.printf("%d.%d%d", pEntry->bVersion >> 4, pEntry->bVersion & 0xf,
+ pEntry->bModify);
+ pEntry->achCsdLevel[sizeof(pEntry->achCsdLevel) - 1] = '\0';
+ char *pszCsd = RTStrStrip(pEntry->achCsdLevel);
+ if (*pszCsd != '\0')
+ {
+ mStrDetectedOSVersion.append(' ');
+ mStrDetectedOSVersion.append(pszCsd);
+ }
+ if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.50") >= 0)
+ mEnmOsType = VBOXOSTYPE_OS2Warp45;
+ else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.00") >= 0)
+ mEnmOsType = VBOXOSTYPE_OS2Warp4;
+ else if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "3.00") >= 0)
+ mEnmOsType = VBOXOSTYPE_OS2Warp3;
+ }
+ else
+ LogRel(("Unattended: bogus SYSLEVEL.OS2 file entry: %.128Rhxd\n", pEntry));
+ }
+ else
+ LogRel(("Unattended: bogus SYSLEVEL.OS2 file header: uMinusOne=%#x uSyslevelFileVer=%#x achSignature=%.8Rhxs offTable=%#x vs cbRead=%#zx\n",
+ pHdr->uMinusOne, pHdr->uSyslevelFileVer, pHdr->achSignature, pHdr->offTable, cbRead));
+ }
+ else
+ LogRel(("Unattended: failed to read SYSLEVEL.OS2: %Rrc\n", vrc));
+ }
+ else
+ LogRel(("Unattended: failed to open '%s' for reading: %Rrc\n", pBuf->sz, vrc));
+ }
+ }
+ }
+
+ /** @todo language detection? */
+
+ /*
+ * Only tested ACP2, so only return S_OK for it.
+ */
+ if ( mEnmOsType == VBOXOSTYPE_OS2Warp45
+ && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "4.52") >= 0
+ && mStrDetectedOSFlavor.contains("Server", RTCString::CaseInsensitive))
+ return S_OK;
+
+ return S_FALSE;
+}
+
+
+/**
+ * Detect FreeBSD distro ISOs.
+ *
+ * @returns COM status code.
+ * @retval S_OK if detected
+ * @retval S_FALSE if not fully detected.
+ *
+ * @param hVfsIso The ISO file system.
+ * @param pBuf Read buffer.
+ */
+HRESULT Unattended::i_innerDetectIsoOSFreeBsd(RTVFS hVfsIso, DETECTBUFFER *pBuf)
+{
+ RT_NOREF(pBuf);
+
+ /*
+ * FreeBSD since 10.0 has a .profile file in the root which can be used to determine that this is FreeBSD
+ * along with the version.
+ */
+
+ RTVFSFILE hVfsFile;
+ HRESULT hrc = S_FALSE;
+ int vrc = RTVfsFileOpen(hVfsIso, ".profile", RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ static const uint8_t s_abFreeBsdHdr[] = "# $FreeBSD: releng/";
+ char abRead[32];
+
+ vrc = RTVfsFileRead(hVfsFile, &abRead[0], sizeof(abRead), NULL /*pcbRead*/);
+ if ( RT_SUCCESS(vrc)
+ && !memcmp(&abRead[0], &s_abFreeBsdHdr[0], sizeof(s_abFreeBsdHdr) - 1)) /* Skip terminator */
+ {
+ abRead[sizeof(abRead) - 1] = '\0';
+
+ /* Detect the architecture using the volume label. */
+ char szVolumeId[128];
+ size_t cchVolumeId;
+ vrc = RTVfsQueryLabel(hVfsIso, false /*fAlternative*/, szVolumeId, 128, &cchVolumeId);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Can re-use the Linux code here. */
+ if (!detectLinuxArchII(szVolumeId, &mEnmOsType, VBOXOSTYPE_FreeBSD))
+ LogRel(("Unattended/FBSD: Unknown: arch='%s'\n", szVolumeId));
+
+ /* Detect the version from the string coming after the needle in .profile. */
+ AssertCompile(sizeof(s_abFreeBsdHdr) - 1 < sizeof(abRead));
+
+ char *pszVersionStart = &abRead[sizeof(s_abFreeBsdHdr) - 1];
+ char *pszVersionEnd = pszVersionStart;
+
+ while (RT_C_IS_DIGIT(*pszVersionEnd))
+ pszVersionEnd++;
+ if (*pszVersionEnd == '.')
+ {
+ pszVersionEnd++; /* Skip the . */
+
+ while (RT_C_IS_DIGIT(*pszVersionEnd))
+ pszVersionEnd++;
+
+ /* Terminate the version string. */
+ *pszVersionEnd = '\0';
+
+ try { mStrDetectedOSVersion = pszVersionStart; }
+ catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; }
+ }
+ else
+ LogRel(("Unattended/FBSD: Unknown: version='%s'\n", &abRead[0]));
+ }
+ else
+ {
+ LogRel(("Unattended/FBSD: No Volume Label found\n"));
+ mEnmOsType = VBOXOSTYPE_FreeBSD;
+ }
+
+ hrc = S_OK;
+ }
+
+ RTVfsFileRelease(hVfsFile);
+ }
+
+ return hrc;
+}
+
+
+HRESULT Unattended::prepare()
+{
+ LogFlow(("Unattended::prepare: enter\n"));
+
+ /*
+ * Must have a machine.
+ */
+ ComPtr<Machine> ptrMachine;
+ Guid MachineUuid;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ ptrMachine = mMachine;
+ if (ptrMachine.isNull())
+ return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("No machine associated with this IUnatteded instance"));
+ MachineUuid = mMachineUuid;
+ }
+
+ /*
+ * Before we write lock ourselves, we must get stuff from Machine and
+ * VirtualBox because their locks have higher priorities than ours.
+ */
+ Utf8Str strGuestOsTypeId;
+ Utf8Str strMachineName;
+ Utf8Str strDefaultAuxBasePath;
+ HRESULT hrc;
+ try
+ {
+ Bstr bstrTmp;
+ hrc = ptrMachine->COMGETTER(OSTypeId)(bstrTmp.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ strGuestOsTypeId = bstrTmp;
+ hrc = ptrMachine->COMGETTER(Name)(bstrTmp.asOutParam());
+ if (SUCCEEDED(hrc))
+ strMachineName = bstrTmp;
+ }
+ int vrc = ptrMachine->i_calculateFullPath(Utf8StrFmt("Unattended-%RTuuid-", MachineUuid.raw()), strDefaultAuxBasePath);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ bool const fIs64Bit = i_isGuestOSArchX64(strGuestOsTypeId);
+
+ BOOL fRtcUseUtc = FALSE;
+ hrc = ptrMachine->COMGETTER(RTCUseUTC)(&fRtcUseUtc);
+ if (FAILED(hrc))
+ return hrc;
+
+ FirmwareType_T enmFirmware = FirmwareType_BIOS;
+ hrc = ptrMachine->COMGETTER(FirmwareType)(&enmFirmware);
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Write lock this object and set attributes we got from IMachine.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mStrGuestOsTypeId = strGuestOsTypeId;
+ mfGuestOs64Bit = fIs64Bit;
+ mfRtcUseUtc = RT_BOOL(fRtcUseUtc);
+ menmFirmwareType = enmFirmware;
+
+ /*
+ * Do some state checks.
+ */
+ if (mpInstaller != NULL)
+ return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The prepare method has been called (must call done to restart)"));
+ if ((Machine *)ptrMachine != (Machine *)mMachine)
+ return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("The 'machine' while we were using it - please don't do that"));
+
+ /*
+ * Check if the specified ISOs and files exist.
+ */
+ if (!RTFileExists(mStrIsoPath.c_str()))
+ return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the installation ISO file '%s'"),
+ mStrIsoPath.c_str());
+ if (mfInstallGuestAdditions && !RTFileExists(mStrAdditionsIsoPath.c_str()))
+ return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the Guest Additions ISO file '%s'"),
+ mStrAdditionsIsoPath.c_str());
+ if (mfInstallTestExecService && !RTFileExists(mStrValidationKitIsoPath.c_str()))
+ return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate the validation kit ISO file '%s'"),
+ mStrValidationKitIsoPath.c_str());
+ if (mStrScriptTemplatePath.isNotEmpty() && !RTFileExists(mStrScriptTemplatePath.c_str()))
+ return setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Could not locate unattended installation script template '%s'"),
+ mStrScriptTemplatePath.c_str());
+
+ /*
+ * Do media detection if it haven't been done yet.
+ */
+ if (!mfDoneDetectIsoOS)
+ {
+ hrc = detectIsoOS();
+ if (FAILED(hrc) && hrc != E_NOTIMPL)
+ return hrc;
+ }
+
+ /*
+ * We can now check midxImage against mDetectedImages, since the latter is
+ * populated during the detectIsoOS call. We ignore midxImage if no images
+ * were detected, assuming that it's not relevant or used for different purposes.
+ */
+ if (mDetectedImages.size() > 0)
+ {
+ bool fImageFound = false;
+ for (size_t i = 0; i < mDetectedImages.size(); ++i)
+ if (midxImage == mDetectedImages[i].mImageIndex)
+ {
+ i_updateDetectedAttributeForImage(mDetectedImages[i]);
+ fImageFound = true;
+ break;
+ }
+ if (!fImageFound)
+ return setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("imageIndex value %u not found in detectedImageIndices"), midxImage);
+ }
+
+ /*
+ * Get the ISO's detect guest OS type info and make it's a known one (just
+ * in case the above step doesn't work right).
+ */
+ uint32_t const idxIsoOSType = Global::getOSTypeIndexFromId(mStrDetectedOSTypeId.c_str());
+ VBOXOSTYPE const enmIsoOSType = idxIsoOSType < Global::cOSTypes ? Global::sOSTypes[idxIsoOSType].osType : VBOXOSTYPE_Unknown;
+ if ((enmIsoOSType & VBOXOSTYPE_OsTypeMask) == VBOXOSTYPE_Unknown)
+ return setError(E_FAIL, tr("The supplied ISO file does not contain an OS currently supported for unattended installation"));
+
+ /*
+ * Get the VM's configured guest OS type info.
+ */
+ uint32_t const idxMachineOSType = Global::getOSTypeIndexFromId(mStrGuestOsTypeId.c_str());
+ VBOXOSTYPE const enmMachineOSType = idxMachineOSType < Global::cOSTypes
+ ? Global::sOSTypes[idxMachineOSType].osType : VBOXOSTYPE_Unknown;
+
+ /*
+ * Check that the detected guest OS type for the ISO is compatible with
+ * that of the VM, boardly speaking.
+ */
+ if (idxMachineOSType != idxIsoOSType)
+ {
+ /* Check that the architecture is compatible: */
+ if ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != (enmMachineOSType & VBOXOSTYPE_ArchitectureMask)
+ && ( (enmIsoOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x86
+ || (enmMachineOSType & VBOXOSTYPE_ArchitectureMask) != VBOXOSTYPE_x64))
+ return setError(E_FAIL, tr("The supplied ISO file is incompatible with the guest OS type of the VM: CPU architecture mismatch"));
+
+ /** @todo check BIOS/EFI requirement */
+ }
+
+ /*
+ * Do some default property stuff and check other properties.
+ */
+ try
+ {
+ char szTmp[128];
+
+ if (mStrLocale.isEmpty())
+ {
+ int vrc = RTLocaleQueryNormalizedBaseLocaleName(szTmp, sizeof(szTmp));
+ if ( RT_SUCCESS(vrc)
+ && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(szTmp))
+ mStrLocale.assign(szTmp, 5);
+ else
+ mStrLocale = "en_US";
+ Assert(RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale));
+ }
+
+ if (mStrLanguage.isEmpty())
+ {
+ if (mDetectedOSLanguages.size() > 0)
+ mStrLanguage = mDetectedOSLanguages[0];
+ else
+ mStrLanguage.assign(mStrLocale).findReplace('_', '-');
+ }
+
+ if (mStrCountry.isEmpty())
+ {
+ int vrc = RTLocaleQueryUserCountryCode(szTmp);
+ if (RT_SUCCESS(vrc))
+ mStrCountry = szTmp;
+ else if ( mStrLocale.isNotEmpty()
+ && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(mStrLocale))
+ mStrCountry.assign(mStrLocale, 3, 2);
+ else
+ mStrCountry = "US";
+ }
+
+ if (mStrTimeZone.isEmpty())
+ {
+ int vrc = RTTimeZoneGetCurrent(szTmp, sizeof(szTmp));
+ if ( RT_SUCCESS(vrc)
+ && strcmp(szTmp, "localtime") != 0 /* Typcial solaris TZ that isn't very helpful. */)
+ mStrTimeZone = szTmp;
+ else
+ mStrTimeZone = "Etc/UTC";
+ Assert(mStrTimeZone.isNotEmpty());
+ }
+ mpTimeZoneInfo = RTTimeZoneGetInfoByUnixName(mStrTimeZone.c_str());
+ if (!mpTimeZoneInfo)
+ mpTimeZoneInfo = RTTimeZoneGetInfoByWindowsName(mStrTimeZone.c_str());
+ Assert(mpTimeZoneInfo || mStrTimeZone != "Etc/UTC");
+ if (!mpTimeZoneInfo)
+ LogRel(("Unattended::prepare: warning: Unknown time zone '%s'\n", mStrTimeZone.c_str()));
+
+ if (mStrHostname.isEmpty())
+ {
+ /* Mangle the VM name into a valid hostname. */
+ for (size_t i = 0; i < strMachineName.length(); i++)
+ {
+ char ch = strMachineName[i];
+ if ( (unsigned)ch < 127
+ && RT_C_IS_ALNUM(ch))
+ mStrHostname.append(ch);
+ else if (mStrHostname.isNotEmpty() && RT_C_IS_PUNCT(ch) && !mStrHostname.endsWith("-"))
+ mStrHostname.append('-');
+ }
+ if (mStrHostname.length() == 0)
+ mStrHostname.printf("%RTuuid-vm", MachineUuid.raw());
+ else if (mStrHostname.length() < 3)
+ mStrHostname.append("-vm");
+ mStrHostname.append(".myguest.virtualbox.org");
+ }
+
+ if (mStrAuxiliaryBasePath.isEmpty())
+ {
+ mStrAuxiliaryBasePath = strDefaultAuxBasePath;
+ mfIsDefaultAuxiliaryBasePath = true;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Instatiate the guest installer matching the ISO.
+ */
+ mpInstaller = UnattendedInstaller::createInstance(enmIsoOSType, mStrDetectedOSTypeId, mStrDetectedOSVersion,
+ mStrDetectedOSFlavor, mStrDetectedOSHints, this);
+ if (mpInstaller != NULL)
+ {
+ hrc = mpInstaller->initInstaller();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Do the script preps (just reads them).
+ */
+ hrc = mpInstaller->prepareUnattendedScripts();
+ if (SUCCEEDED(hrc))
+ {
+ LogFlow(("Unattended::prepare: returns S_OK\n"));
+ return S_OK;
+ }
+ }
+
+ /* Destroy the installer instance. */
+ delete mpInstaller;
+ mpInstaller = NULL;
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, VERR_NOT_FOUND,
+ tr("Unattended installation is not supported for guest type '%s'"), mStrGuestOsTypeId.c_str());
+ LogRelFlow(("Unattended::prepare: failed with %Rhrc\n", hrc));
+ return hrc;
+}
+
+HRESULT Unattended::constructMedia()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlow(("===========================================================\n"));
+ LogFlow(("Call Unattended::constructMedia()\n"));
+
+ if (mpInstaller == NULL)
+ return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, "prepare() not yet called");
+
+ return mpInstaller->prepareMedia();
+}
+
+HRESULT Unattended::reconfigureVM()
+{
+ LogFlow(("===========================================================\n"));
+ LogFlow(("Call Unattended::reconfigureVM()\n"));
+
+ /*
+ * Interrogate VirtualBox/IGuestOSType before we lock stuff and create ordering issues.
+ */
+ StorageBus_T enmRecommendedStorageBus = StorageBus_IDE;
+ {
+ Bstr bstrGuestOsTypeId;
+ Bstr bstrDetectedOSTypeId;
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mpInstaller == NULL)
+ return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
+ bstrGuestOsTypeId = mStrGuestOsTypeId;
+ bstrDetectedOSTypeId = mStrDetectedOSTypeId;
+ }
+ ComPtr<IGuestOSType> ptrGuestOSType;
+ HRESULT hrc = mParent->GetGuestOSType(bstrGuestOsTypeId.raw(), ptrGuestOSType.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ if (!ptrGuestOSType.isNull())
+ hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus);
+ }
+ if (FAILED(hrc))
+ return hrc;
+
+ /* If the detected guest OS type differs, log a warning if their DVD storage
+ bus recommendations differ. */
+ if (bstrGuestOsTypeId != bstrDetectedOSTypeId)
+ {
+ StorageBus_T enmRecommendedStorageBus2 = StorageBus_IDE;
+ hrc = mParent->GetGuestOSType(bstrDetectedOSTypeId.raw(), ptrGuestOSType.asOutParam());
+ if (SUCCEEDED(hrc) && !ptrGuestOSType.isNull())
+ hrc = ptrGuestOSType->COMGETTER(RecommendedDVDStorageBus)(&enmRecommendedStorageBus2);
+ if (FAILED(hrc))
+ return hrc;
+
+ if (enmRecommendedStorageBus != enmRecommendedStorageBus2)
+ LogRel(("Unattended::reconfigureVM: DVD storage bus recommendations differs for the VM and the ISO guest OS types: VM: %s (%ls), ISO: %s (%ls)\n",
+ ::stringifyStorageBus(enmRecommendedStorageBus), bstrGuestOsTypeId.raw(),
+ ::stringifyStorageBus(enmRecommendedStorageBus2), bstrDetectedOSTypeId.raw() ));
+ }
+ }
+
+ /*
+ * Take write lock (for lock order reasons, write lock our parent object too)
+ * then make sure we're the only caller of this method.
+ */
+ AutoMultiWriteLock2 alock(mMachine, this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc;
+ if (mhThreadReconfigureVM == NIL_RTNATIVETHREAD)
+ {
+ RTNATIVETHREAD const hNativeSelf = RTThreadNativeSelf();
+ mhThreadReconfigureVM = hNativeSelf;
+
+ /*
+ * Create a new session, lock the machine and get the session machine object.
+ * Do the locking without pinning down the write locks, just to be on the safe side.
+ */
+ ComPtr<ISession> ptrSession;
+ try
+ {
+ hrc = ptrSession.createInprocObject(CLSID_Session);
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ alock.release();
+ hrc = mMachine->LockMachine(ptrSession, LockType_Shared);
+ alock.acquire();
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<IMachine> ptrSessionMachine;
+ hrc = ptrSession->COMGETTER(Machine)(ptrSessionMachine.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Hand the session to the inner work and let it do it job.
+ */
+ try
+ {
+ hrc = i_innerReconfigureVM(alock, enmRecommendedStorageBus, ptrSessionMachine);
+ }
+ catch (...)
+ {
+ hrc = E_UNEXPECTED;
+ }
+ }
+
+ /* Paranoia: release early in case we it a bump below. */
+ Assert(mhThreadReconfigureVM == hNativeSelf);
+ mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
+
+ /*
+ * While unlocking the machine we'll have to drop the locks again.
+ */
+ alock.release();
+
+ ptrSessionMachine.setNull();
+ HRESULT hrc2 = ptrSession->UnlockMachine();
+ AssertLogRelMsg(SUCCEEDED(hrc2), ("UnlockMachine -> %Rhrc\n", hrc2));
+
+ ptrSession.setNull();
+
+ alock.acquire();
+ }
+ else
+ mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
+ }
+ else
+ mhThreadReconfigureVM = NIL_RTNATIVETHREAD;
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("reconfigureVM running on other thread"));
+ return hrc;
+}
+
+
+HRESULT Unattended::i_innerReconfigureVM(AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus,
+ ComPtr<IMachine> const &rPtrSessionMachine)
+{
+ if (mpInstaller == NULL)
+ return setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("prepare() not yet called"));
+
+ // Fetch all available storage controllers
+ com::SafeIfaceArray<IStorageController> arrayOfControllers;
+ HRESULT hrc = rPtrSessionMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(arrayOfControllers));
+ AssertComRCReturn(hrc, hrc);
+
+ /*
+ * Figure out where the images are to be mounted, adding controllers/ports as needed.
+ */
+ std::vector<UnattendedInstallationDisk> vecInstallationDisks;
+ if (mpInstaller->isAuxiliaryFloppyNeeded())
+ {
+ hrc = i_reconfigureFloppy(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock);
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ hrc = i_reconfigureIsos(arrayOfControllers, vecInstallationDisks, rPtrSessionMachine, rAutoLock, enmRecommendedStorageBus);
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Mount the images.
+ */
+ for (size_t idxImage = 0; idxImage < vecInstallationDisks.size(); idxImage++)
+ {
+ UnattendedInstallationDisk const *pImage = &vecInstallationDisks.at(idxImage);
+ Assert(pImage->strImagePath.isNotEmpty());
+ hrc = i_attachImage(pImage, rPtrSessionMachine, rAutoLock);
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /*
+ * Set the boot order.
+ *
+ * ASSUME that the HD isn't bootable when we start out, but it will be what
+ * we boot from after the first stage of the installation is done. Setting
+ * it first prevents endless reboot cylces.
+ */
+ /** @todo consider making 100% sure the disk isn't bootable (edit partition
+ * table active bits and EFI stuff). */
+ Assert( mpInstaller->getBootableDeviceType() == DeviceType_DVD
+ || mpInstaller->getBootableDeviceType() == DeviceType_Floppy);
+ hrc = rPtrSessionMachine->SetBootOrder(1, DeviceType_HardDisk);
+ if (SUCCEEDED(hrc))
+ hrc = rPtrSessionMachine->SetBootOrder(2, mpInstaller->getBootableDeviceType());
+ if (SUCCEEDED(hrc))
+ hrc = rPtrSessionMachine->SetBootOrder(3, mpInstaller->getBootableDeviceType() == DeviceType_DVD
+ ? DeviceType_Floppy : DeviceType_DVD);
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Essential step.
+ *
+ * HACK ALERT! We have to release the lock here or we'll get into trouble with
+ * the VirtualBox lock (via i_saveHardware/NetworkAdaptger::i_hasDefaults/VirtualBox::i_findGuestOSType).
+ */
+ if (SUCCEEDED(hrc))
+ {
+ rAutoLock.release();
+ hrc = rPtrSessionMachine->SaveSettings();
+ rAutoLock.acquire();
+ }
+
+ return hrc;
+}
+
+/**
+ * Makes sure we've got a floppy drive attached to a floppy controller, adding
+ * the auxiliary floppy image to the installation disk vector.
+ *
+ * @returns COM status code.
+ * @param rControllers The existing controllers.
+ * @param rVecInstallatationDisks The list of image to mount.
+ * @param rPtrSessionMachine The session machine smart pointer.
+ * @param rAutoLock The lock.
+ */
+HRESULT Unattended::i_reconfigureFloppy(com::SafeIfaceArray<IStorageController> &rControllers,
+ std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
+ ComPtr<IMachine> const &rPtrSessionMachine,
+ AutoMultiWriteLock2 &rAutoLock)
+{
+ Assert(mpInstaller->isAuxiliaryFloppyNeeded());
+
+ /*
+ * Look for a floppy controller with a primary drive (A:) we can "insert"
+ * the auxiliary floppy image. Add a controller and/or a drive if necessary.
+ */
+ bool fFoundPort0Dev0 = false;
+ Bstr bstrControllerName;
+ Utf8Str strControllerName;
+
+ for (size_t i = 0; i < rControllers.size(); ++i)
+ {
+ StorageBus_T enmStorageBus;
+ HRESULT hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
+ AssertComRCReturn(hrc, hrc);
+ if (enmStorageBus == StorageBus_Floppy)
+ {
+
+ /*
+ * Found a floppy controller.
+ */
+ hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
+ AssertComRCReturn(hrc, hrc);
+
+ /*
+ * Check the attchments to see if we've got a device 0 attached on port 0.
+ *
+ * While we're at it we eject flppies from all floppy drives we encounter,
+ * we don't want any confusion at boot or during installation.
+ */
+ com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
+ hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
+ ComSafeArrayAsOutParam(arrayOfMediumAttachments));
+ AssertComRCReturn(hrc, hrc);
+ strControllerName = bstrControllerName;
+ AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
+
+ for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
+ {
+ LONG iPort = -1;
+ hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
+ AssertComRCReturn(hrc, hrc);
+
+ LONG iDevice = -1;
+ hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
+ AssertComRCReturn(hrc, hrc);
+
+ DeviceType_T enmType;
+ hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
+ AssertComRCReturn(hrc, hrc);
+
+ if (enmType == DeviceType_Floppy)
+ {
+ ComPtr<IMedium> ptrMedium;
+ hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
+ AssertComRCReturn(hrc, hrc);
+
+ if (ptrMedium.isNotNull())
+ {
+ ptrMedium.setNull();
+ rAutoLock.release();
+ hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
+ rAutoLock.acquire();
+ }
+
+ if (iPort == 0 && iDevice == 0)
+ fFoundPort0Dev0 = true;
+ }
+ else if (iPort == 0 && iDevice == 0)
+ return setError(E_FAIL,
+ tr("Found non-floppy device attached to port 0 device 0 on the floppy controller '%ls'"),
+ bstrControllerName.raw());
+ }
+ }
+ }
+
+ /*
+ * Add a floppy controller if we need to.
+ */
+ if (strControllerName.isEmpty())
+ {
+ bstrControllerName = strControllerName = "Floppy";
+ ComPtr<IStorageController> ptrControllerIgnored;
+ HRESULT hrc = rPtrSessionMachine->AddStorageController(bstrControllerName.raw(), StorageBus_Floppy,
+ ptrControllerIgnored.asOutParam());
+ LogRelFunc(("Machine::addStorageController(Floppy) -> %Rhrc \n", hrc));
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /*
+ * Adding a floppy drive (if needed) and mounting the auxiliary image is
+ * done later together with the ISOs.
+ */
+ rVecInstallatationDisks.push_back(UnattendedInstallationDisk(StorageBus_Floppy, strControllerName,
+ DeviceType_Floppy, AccessMode_ReadWrite,
+ 0, 0,
+ fFoundPort0Dev0 /*fMountOnly*/,
+ mpInstaller->getAuxiliaryFloppyFilePath(), false));
+ return S_OK;
+}
+
+/**
+ * Reconfigures DVD drives of the VM to mount all the ISOs we need.
+ *
+ * This will umount all DVD media.
+ *
+ * @returns COM status code.
+ * @param rControllers The existing controllers.
+ * @param rVecInstallatationDisks The list of image to mount.
+ * @param rPtrSessionMachine The session machine smart pointer.
+ * @param rAutoLock The lock.
+ * @param enmRecommendedStorageBus The recommended storage bus type for adding
+ * DVD drives on.
+ */
+HRESULT Unattended::i_reconfigureIsos(com::SafeIfaceArray<IStorageController> &rControllers,
+ std::vector<UnattendedInstallationDisk> &rVecInstallatationDisks,
+ ComPtr<IMachine> const &rPtrSessionMachine,
+ AutoMultiWriteLock2 &rAutoLock, StorageBus_T enmRecommendedStorageBus)
+{
+ /*
+ * Enumerate the attachements of every controller, looking for DVD drives,
+ * ASSUMEING all drives are bootable.
+ *
+ * Eject the medium from all the drives (don't want any confusion) and look
+ * for the recommended storage bus in case we need to add more drives.
+ */
+ HRESULT hrc;
+ std::list<ControllerSlot> lstControllerDvdSlots;
+ Utf8Str strRecommendedControllerName; /* non-empty if recommended bus found. */
+ Utf8Str strControllerName;
+ Bstr bstrControllerName;
+ for (size_t i = 0; i < rControllers.size(); ++i)
+ {
+ hrc = rControllers[i]->COMGETTER(Name)(bstrControllerName.asOutParam());
+ AssertComRCReturn(hrc, hrc);
+ strControllerName = bstrControllerName;
+
+ /* Look for recommended storage bus. */
+ StorageBus_T enmStorageBus;
+ hrc = rControllers[i]->COMGETTER(Bus)(&enmStorageBus);
+ AssertComRCReturn(hrc, hrc);
+ if (enmStorageBus == enmRecommendedStorageBus)
+ {
+ strRecommendedControllerName = bstrControllerName;
+ AssertLogRelReturn(strControllerName.isNotEmpty(), setErrorBoth(E_UNEXPECTED, VERR_INTERNAL_ERROR_2));
+ }
+
+ /* Scan the controller attachments. */
+ com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
+ hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(bstrControllerName.raw(),
+ ComSafeArrayAsOutParam(arrayOfMediumAttachments));
+ AssertComRCReturn(hrc, hrc);
+
+ for (size_t j = 0; j < arrayOfMediumAttachments.size(); j++)
+ {
+ DeviceType_T enmType;
+ hrc = arrayOfMediumAttachments[j]->COMGETTER(Type)(&enmType);
+ AssertComRCReturn(hrc, hrc);
+ if (enmType == DeviceType_DVD)
+ {
+ LONG iPort = -1;
+ hrc = arrayOfMediumAttachments[j]->COMGETTER(Port)(&iPort);
+ AssertComRCReturn(hrc, hrc);
+
+ LONG iDevice = -1;
+ hrc = arrayOfMediumAttachments[j]->COMGETTER(Device)(&iDevice);
+ AssertComRCReturn(hrc, hrc);
+
+ /* Remeber it. */
+ lstControllerDvdSlots.push_back(ControllerSlot(enmStorageBus, strControllerName, iPort, iDevice, false /*fFree*/));
+
+ /* Eject the medium, if any. */
+ ComPtr<IMedium> ptrMedium;
+ hrc = arrayOfMediumAttachments[j]->COMGETTER(Medium)(ptrMedium.asOutParam());
+ AssertComRCReturn(hrc, hrc);
+ if (ptrMedium.isNotNull())
+ {
+ ptrMedium.setNull();
+
+ rAutoLock.release();
+ hrc = rPtrSessionMachine->UnmountMedium(bstrControllerName.raw(), iPort, iDevice, TRUE /*fForce*/);
+ rAutoLock.acquire();
+ }
+ }
+ }
+ }
+
+ /*
+ * How many drives do we need? Add more if necessary.
+ */
+ ULONG cDvdDrivesNeeded = 0;
+ if (mpInstaller->isAuxiliaryIsoNeeded())
+ cDvdDrivesNeeded++;
+ if (mpInstaller->isOriginalIsoNeeded())
+ cDvdDrivesNeeded++;
+#if 0 /* These are now in the AUX VISO. */
+ if (mpInstaller->isAdditionsIsoNeeded())
+ cDvdDrivesNeeded++;
+ if (mpInstaller->isValidationKitIsoNeeded())
+ cDvdDrivesNeeded++;
+#endif
+ Assert(cDvdDrivesNeeded > 0);
+ if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
+ {
+ /* Do we need to add the recommended controller? */
+ if (strRecommendedControllerName.isEmpty())
+ {
+ switch (enmRecommendedStorageBus)
+ {
+ case StorageBus_IDE: strRecommendedControllerName = "IDE"; break;
+ case StorageBus_SATA: strRecommendedControllerName = "SATA"; break;
+ case StorageBus_SCSI: strRecommendedControllerName = "SCSI"; break;
+ case StorageBus_SAS: strRecommendedControllerName = "SAS"; break;
+ case StorageBus_USB: strRecommendedControllerName = "USB"; break;
+ case StorageBus_PCIe: strRecommendedControllerName = "PCIe"; break;
+ default:
+ return setError(E_FAIL, tr("Support for recommended storage bus %d not implemented"),
+ (int)enmRecommendedStorageBus);
+ }
+ ComPtr<IStorageController> ptrControllerIgnored;
+ hrc = rPtrSessionMachine->AddStorageController(Bstr(strRecommendedControllerName).raw(), enmRecommendedStorageBus,
+ ptrControllerIgnored.asOutParam());
+ LogRelFunc(("Machine::addStorageController(%s) -> %Rhrc \n", strRecommendedControllerName.c_str(), hrc));
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /* Add free controller slots, maybe raising the port limit on the controller if we can. */
+ hrc = i_findOrCreateNeededFreeSlots(strRecommendedControllerName, enmRecommendedStorageBus, rPtrSessionMachine,
+ cDvdDrivesNeeded, lstControllerDvdSlots);
+ if (FAILED(hrc))
+ return hrc;
+ if (cDvdDrivesNeeded > lstControllerDvdSlots.size())
+ {
+ /* We could in many cases create another controller here, but it's not worth the effort. */
+ return setError(E_FAIL, tr("Not enough free slots on controller '%s' to add %u DVD drive(s)", "",
+ cDvdDrivesNeeded - lstControllerDvdSlots.size()),
+ strRecommendedControllerName.c_str(), cDvdDrivesNeeded - lstControllerDvdSlots.size());
+ }
+ Assert(cDvdDrivesNeeded == lstControllerDvdSlots.size());
+ }
+
+ /*
+ * Sort the DVD slots in boot order.
+ */
+ lstControllerDvdSlots.sort();
+
+ /*
+ * Prepare ISO mounts.
+ *
+ * Boot order depends on bootFromAuxiliaryIso() and we must grab DVD slots
+ * according to the boot order.
+ */
+ std::list<ControllerSlot>::const_iterator itDvdSlot = lstControllerDvdSlots.begin();
+ if (mpInstaller->isAuxiliaryIsoNeeded() && mpInstaller->bootFromAuxiliaryIso())
+ {
+ rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath(), true));
+ ++itDvdSlot;
+ }
+
+ if (mpInstaller->isOriginalIsoNeeded())
+ {
+ rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getIsoPath(), false));
+ ++itDvdSlot;
+ }
+
+ if (mpInstaller->isAuxiliaryIsoNeeded() && !mpInstaller->bootFromAuxiliaryIso())
+ {
+ rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, mpInstaller->getAuxiliaryIsoFilePath(), true));
+ ++itDvdSlot;
+ }
+
+#if 0 /* These are now in the AUX VISO. */
+ if (mpInstaller->isAdditionsIsoNeeded())
+ {
+ rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getAdditionsIsoPath(), false));
+ ++itDvdSlot;
+ }
+
+ if (mpInstaller->isValidationKitIsoNeeded())
+ {
+ rVecInstallatationDisks.push_back(UnattendedInstallationDisk(itDvdSlot, i_getValidationKitIsoPath(), false));
+ ++itDvdSlot;
+ }
+#endif
+
+ return S_OK;
+}
+
+/**
+ * Used to find more free slots for DVD drives during VM reconfiguration.
+ *
+ * This may modify the @a portCount property of the given controller.
+ *
+ * @returns COM status code.
+ * @param rStrControllerName The name of the controller to find/create
+ * free slots on.
+ * @param enmStorageBus The storage bus type.
+ * @param rPtrSessionMachine Reference to the session machine.
+ * @param cSlotsNeeded Total slots needed (including those we've
+ * already found).
+ * @param rDvdSlots The slot collection for DVD drives to add
+ * free slots to as we find/create them.
+ */
+HRESULT Unattended::i_findOrCreateNeededFreeSlots(const Utf8Str &rStrControllerName, StorageBus_T enmStorageBus,
+ ComPtr<IMachine> const &rPtrSessionMachine, uint32_t cSlotsNeeded,
+ std::list<ControllerSlot> &rDvdSlots)
+{
+ Assert(cSlotsNeeded > rDvdSlots.size());
+
+ /*
+ * Get controlleer stats.
+ */
+ ComPtr<IStorageController> pController;
+ HRESULT hrc = rPtrSessionMachine->GetStorageControllerByName(Bstr(rStrControllerName).raw(), pController.asOutParam());
+ AssertComRCReturn(hrc, hrc);
+
+ ULONG cMaxDevicesPerPort = 1;
+ hrc = pController->COMGETTER(MaxDevicesPerPortCount)(&cMaxDevicesPerPort);
+ AssertComRCReturn(hrc, hrc);
+ AssertLogRelReturn(cMaxDevicesPerPort > 0, E_UNEXPECTED);
+
+ ULONG cPorts = 0;
+ hrc = pController->COMGETTER(PortCount)(&cPorts);
+ AssertComRCReturn(hrc, hrc);
+
+ /*
+ * Get the attachment list and turn into an internal list for lookup speed.
+ */
+ com::SafeIfaceArray<IMediumAttachment> arrayOfMediumAttachments;
+ hrc = rPtrSessionMachine->GetMediumAttachmentsOfController(Bstr(rStrControllerName).raw(),
+ ComSafeArrayAsOutParam(arrayOfMediumAttachments));
+ AssertComRCReturn(hrc, hrc);
+
+ std::vector<ControllerSlot> arrayOfUsedSlots;
+ for (size_t i = 0; i < arrayOfMediumAttachments.size(); i++)
+ {
+ LONG iPort = -1;
+ hrc = arrayOfMediumAttachments[i]->COMGETTER(Port)(&iPort);
+ AssertComRCReturn(hrc, hrc);
+
+ LONG iDevice = -1;
+ hrc = arrayOfMediumAttachments[i]->COMGETTER(Device)(&iDevice);
+ AssertComRCReturn(hrc, hrc);
+
+ arrayOfUsedSlots.push_back(ControllerSlot(enmStorageBus, Utf8Str::Empty, iPort, iDevice, false /*fFree*/));
+ }
+
+ /*
+ * Iterate thru all possible slots, adding those not found in arrayOfUsedSlots.
+ */
+ for (int32_t iPort = 0; iPort < (int32_t)cPorts; iPort++)
+ for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
+ {
+ bool fFound = false;
+ for (size_t i = 0; i < arrayOfUsedSlots.size(); i++)
+ if ( arrayOfUsedSlots[i].iPort == iPort
+ && arrayOfUsedSlots[i].iDevice == iDevice)
+ {
+ fFound = true;
+ break;
+ }
+ if (!fFound)
+ {
+ rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
+ if (rDvdSlots.size() >= cSlotsNeeded)
+ return S_OK;
+ }
+ }
+
+ /*
+ * Okay we still need more ports. See if increasing the number of controller
+ * ports would solve it.
+ */
+ ULONG cMaxPorts = 1;
+ hrc = pController->COMGETTER(MaxPortCount)(&cMaxPorts);
+ AssertComRCReturn(hrc, hrc);
+ if (cMaxPorts <= cPorts)
+ return S_OK;
+ size_t cNewPortsNeeded = (cSlotsNeeded - rDvdSlots.size() + cMaxDevicesPerPort - 1) / cMaxDevicesPerPort;
+ if (cPorts + cNewPortsNeeded > cMaxPorts)
+ return S_OK;
+
+ /*
+ * Raise the port count and add the free slots we've just created.
+ */
+ hrc = pController->COMSETTER(PortCount)(cPorts + (ULONG)cNewPortsNeeded);
+ AssertComRCReturn(hrc, hrc);
+ int32_t const cPortsNew = (int32_t)(cPorts + cNewPortsNeeded);
+ for (int32_t iPort = (int32_t)cPorts; iPort < cPortsNew; iPort++)
+ for (int32_t iDevice = 0; iDevice < (int32_t)cMaxDevicesPerPort; iDevice++)
+ {
+ rDvdSlots.push_back(ControllerSlot(enmStorageBus, rStrControllerName, iPort, iDevice, true /*fFree*/));
+ if (rDvdSlots.size() >= cSlotsNeeded)
+ return S_OK;
+ }
+
+ /* We should not get here! */
+ AssertLogRelFailedReturn(E_UNEXPECTED);
+}
+
+HRESULT Unattended::done()
+{
+ LogFlow(("Unattended::done\n"));
+ if (mpInstaller)
+ {
+ LogRelFlow(("Unattended::done: Deleting installer object (%p)\n", mpInstaller));
+ delete mpInstaller;
+ mpInstaller = NULL;
+ }
+ return S_OK;
+}
+
+HRESULT Unattended::getIsoPath(com::Utf8Str &isoPath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ isoPath = mStrIsoPath;
+ return S_OK;
+}
+
+HRESULT Unattended::setIsoPath(const com::Utf8Str &isoPath)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrIsoPath = isoPath;
+ mfDoneDetectIsoOS = false;
+ return S_OK;
+}
+
+HRESULT Unattended::getUser(com::Utf8Str &user)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ user = mStrUser;
+ return S_OK;
+}
+
+
+HRESULT Unattended::setUser(const com::Utf8Str &user)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrUser = user;
+ return S_OK;
+}
+
+HRESULT Unattended::getPassword(com::Utf8Str &password)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ password = mStrPassword;
+ return S_OK;
+}
+
+HRESULT Unattended::setPassword(const com::Utf8Str &password)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrPassword = password;
+ return S_OK;
+}
+
+HRESULT Unattended::getFullUserName(com::Utf8Str &fullUserName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ fullUserName = mStrFullUserName;
+ return S_OK;
+}
+
+HRESULT Unattended::setFullUserName(const com::Utf8Str &fullUserName)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrFullUserName = fullUserName;
+ return S_OK;
+}
+
+HRESULT Unattended::getProductKey(com::Utf8Str &productKey)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ productKey = mStrProductKey;
+ return S_OK;
+}
+
+HRESULT Unattended::setProductKey(const com::Utf8Str &productKey)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrProductKey = productKey;
+ return S_OK;
+}
+
+HRESULT Unattended::getAdditionsIsoPath(com::Utf8Str &additionsIsoPath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ additionsIsoPath = mStrAdditionsIsoPath;
+ return S_OK;
+}
+
+HRESULT Unattended::setAdditionsIsoPath(const com::Utf8Str &additionsIsoPath)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrAdditionsIsoPath = additionsIsoPath;
+ return S_OK;
+}
+
+HRESULT Unattended::getInstallGuestAdditions(BOOL *installGuestAdditions)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *installGuestAdditions = mfInstallGuestAdditions;
+ return S_OK;
+}
+
+HRESULT Unattended::setInstallGuestAdditions(BOOL installGuestAdditions)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mfInstallGuestAdditions = installGuestAdditions != FALSE;
+ return S_OK;
+}
+
+HRESULT Unattended::getValidationKitIsoPath(com::Utf8Str &aValidationKitIsoPath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aValidationKitIsoPath = mStrValidationKitIsoPath;
+ return S_OK;
+}
+
+HRESULT Unattended::setValidationKitIsoPath(const com::Utf8Str &aValidationKitIsoPath)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrValidationKitIsoPath = aValidationKitIsoPath;
+ return S_OK;
+}
+
+HRESULT Unattended::getInstallTestExecService(BOOL *aInstallTestExecService)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aInstallTestExecService = mfInstallTestExecService;
+ return S_OK;
+}
+
+HRESULT Unattended::setInstallTestExecService(BOOL aInstallTestExecService)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mfInstallTestExecService = aInstallTestExecService != FALSE;
+ return S_OK;
+}
+
+HRESULT Unattended::getTimeZone(com::Utf8Str &aTimeZone)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aTimeZone = mStrTimeZone;
+ return S_OK;
+}
+
+HRESULT Unattended::setTimeZone(const com::Utf8Str &aTimezone)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrTimeZone = aTimezone;
+ return S_OK;
+}
+
+HRESULT Unattended::getLocale(com::Utf8Str &aLocale)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aLocale = mStrLocale;
+ return S_OK;
+}
+
+HRESULT Unattended::setLocale(const com::Utf8Str &aLocale)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ if ( aLocale.isEmpty() /* use default */
+ || ( aLocale.length() == 5
+ && RT_C_IS_LOWER(aLocale[0])
+ && RT_C_IS_LOWER(aLocale[1])
+ && aLocale[2] == '_'
+ && RT_C_IS_UPPER(aLocale[3])
+ && RT_C_IS_UPPER(aLocale[4])) )
+ {
+ mStrLocale = aLocale;
+ return S_OK;
+ }
+ return setError(E_INVALIDARG, tr("Expected two lower cased letters, an underscore, and two upper cased letters"));
+}
+
+HRESULT Unattended::getLanguage(com::Utf8Str &aLanguage)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aLanguage = mStrLanguage;
+ return S_OK;
+}
+
+HRESULT Unattended::setLanguage(const com::Utf8Str &aLanguage)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrLanguage = aLanguage;
+ return S_OK;
+}
+
+HRESULT Unattended::getCountry(com::Utf8Str &aCountry)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aCountry = mStrCountry;
+ return S_OK;
+}
+
+HRESULT Unattended::setCountry(const com::Utf8Str &aCountry)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ if ( aCountry.isEmpty()
+ || ( aCountry.length() == 2
+ && RT_C_IS_UPPER(aCountry[0])
+ && RT_C_IS_UPPER(aCountry[1])) )
+ {
+ mStrCountry = aCountry;
+ return S_OK;
+ }
+ return setError(E_INVALIDARG, tr("Expected two upper cased letters"));
+}
+
+HRESULT Unattended::getProxy(com::Utf8Str &aProxy)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aProxy = mStrProxy; /// @todo turn schema map into string or something.
+ return S_OK;
+}
+
+HRESULT Unattended::setProxy(const com::Utf8Str &aProxy)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ if (aProxy.isEmpty())
+ {
+ /* set default proxy */
+ /** @todo BUGBUG! implement this */
+ }
+ else if (aProxy.equalsIgnoreCase("none"))
+ {
+ /* clear proxy config */
+ mStrProxy.setNull();
+ }
+ else
+ {
+ /** @todo Parse and set proxy config into a schema map or something along those lines. */
+ /** @todo BUGBUG! implement this */
+ // return E_NOTIMPL;
+ mStrProxy = aProxy;
+ }
+ return S_OK;
+}
+
+HRESULT Unattended::getPackageSelectionAdjustments(com::Utf8Str &aPackageSelectionAdjustments)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aPackageSelectionAdjustments = RTCString::join(mPackageSelectionAdjustments, ";");
+ return S_OK;
+}
+
+HRESULT Unattended::setPackageSelectionAdjustments(const com::Utf8Str &aPackageSelectionAdjustments)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ if (aPackageSelectionAdjustments.isEmpty())
+ mPackageSelectionAdjustments.clear();
+ else
+ {
+ RTCList<RTCString, RTCString *> arrayStrSplit = aPackageSelectionAdjustments.split(";");
+ for (size_t i = 0; i < arrayStrSplit.size(); i++)
+ {
+ if (arrayStrSplit[i].equals("minimal"))
+ { /* okay */ }
+ else
+ return setError(E_INVALIDARG, tr("Unknown keyword: %s"), arrayStrSplit[i].c_str());
+ }
+ mPackageSelectionAdjustments = arrayStrSplit;
+ }
+ return S_OK;
+}
+
+HRESULT Unattended::getHostname(com::Utf8Str &aHostname)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aHostname = mStrHostname;
+ return S_OK;
+}
+
+HRESULT Unattended::setHostname(const com::Utf8Str &aHostname)
+{
+ /*
+ * Validate input.
+ */
+ if (aHostname.length() > (aHostname.endsWith(".") ? 254U : 253U))
+ return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
+ tr("Hostname '%s' is %zu bytes long, max is 253 (excluding trailing dot)", "", aHostname.length()),
+ aHostname.c_str(), aHostname.length());
+ size_t cLabels = 0;
+ const char *pszSrc = aHostname.c_str();
+ for (;;)
+ {
+ size_t cchLabel = 1;
+ char ch = *pszSrc++;
+ if (RT_C_IS_ALNUM(ch))
+ {
+ cLabels++;
+ while ((ch = *pszSrc++) != '.' && ch != '\0')
+ {
+ if (RT_C_IS_ALNUM(ch) || ch == '-')
+ {
+ if (cchLabel < 63)
+ cchLabel++;
+ else
+ return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
+ tr("Invalid hostname '%s' - label %u is too long, max is 63."),
+ aHostname.c_str(), cLabels);
+ }
+ else
+ return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
+ tr("Invalid hostname '%s' - illegal char '%c' at position %zu"),
+ aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
+ }
+ if (cLabels == 1 && cchLabel < 2)
+ return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
+ tr("Invalid hostname '%s' - the name part must be at least two characters long"),
+ aHostname.c_str());
+ if (ch == '\0')
+ break;
+ }
+ else if (ch != '\0')
+ return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
+ tr("Invalid hostname '%s' - illegal lead char '%c' at position %zu"),
+ aHostname.c_str(), ch, pszSrc - aHostname.c_str() - 1);
+ else
+ return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
+ tr("Invalid hostname '%s' - trailing dot not permitted"), aHostname.c_str());
+ }
+ if (cLabels < 2)
+ return setErrorBoth(E_INVALIDARG, VERR_INVALID_NAME,
+ tr("Incomplete hostname '%s' - must include both a name and a domain"), aHostname.c_str());
+
+ /*
+ * Make the change.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrHostname = aHostname;
+ return S_OK;
+}
+
+HRESULT Unattended::getAuxiliaryBasePath(com::Utf8Str &aAuxiliaryBasePath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aAuxiliaryBasePath = mStrAuxiliaryBasePath;
+ return S_OK;
+}
+
+HRESULT Unattended::setAuxiliaryBasePath(const com::Utf8Str &aAuxiliaryBasePath)
+{
+ if (aAuxiliaryBasePath.isEmpty())
+ return setError(E_INVALIDARG, tr("Empty base path is not allowed"));
+ if (!RTPathStartsWithRoot(aAuxiliaryBasePath.c_str()))
+ return setError(E_INVALIDARG, tr("Base path must be absolute"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrAuxiliaryBasePath = aAuxiliaryBasePath;
+ mfIsDefaultAuxiliaryBasePath = mStrAuxiliaryBasePath.isEmpty();
+ return S_OK;
+}
+
+HRESULT Unattended::getImageIndex(ULONG *index)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *index = midxImage;
+ return S_OK;
+}
+
+HRESULT Unattended::setImageIndex(ULONG index)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+
+ /* Validate the selection if detection was done already: */
+ if (mDetectedImages.size() > 0)
+ {
+ for (size_t i = 0; i < mDetectedImages.size(); i++)
+ if (mDetectedImages[i].mImageIndex == index)
+ {
+ midxImage = index;
+ i_updateDetectedAttributeForImage(mDetectedImages[i]);
+ return S_OK;
+ }
+ LogRel(("Unattended: Setting invalid index=%u\n", index)); /** @todo fail? */
+ }
+
+ midxImage = index;
+ return S_OK;
+}
+
+HRESULT Unattended::getMachine(ComPtr<IMachine> &aMachine)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return mMachine.queryInterfaceTo(aMachine.asOutParam());
+}
+
+HRESULT Unattended::setMachine(const ComPtr<IMachine> &aMachine)
+{
+ /*
+ * Lookup the VM so we can safely get the Machine instance.
+ * (Don't want to test how reliable XPCOM and COM are with finding
+ * the local object instance when a client passes a stub back.)
+ */
+ Bstr bstrUuidMachine;
+ HRESULT hrc = aMachine->COMGETTER(Id)(bstrUuidMachine.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Guid UuidMachine(bstrUuidMachine);
+ ComObjPtr<Machine> ptrMachine;
+ hrc = mParent->i_findMachine(UuidMachine, false /*fPermitInaccessible*/, true /*aSetError*/, &ptrMachine);
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER,
+ tr("Cannot change after prepare() has been called")));
+ mMachine = ptrMachine;
+ mMachineUuid = UuidMachine;
+ if (mfIsDefaultAuxiliaryBasePath)
+ mStrAuxiliaryBasePath.setNull();
+ hrc = S_OK;
+ }
+ }
+ return hrc;
+}
+
+HRESULT Unattended::getScriptTemplatePath(com::Utf8Str &aScriptTemplatePath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if ( mStrScriptTemplatePath.isNotEmpty()
+ || mpInstaller == NULL)
+ aScriptTemplatePath = mStrScriptTemplatePath;
+ else
+ aScriptTemplatePath = mpInstaller->getTemplateFilePath();
+ return S_OK;
+}
+
+HRESULT Unattended::setScriptTemplatePath(const com::Utf8Str &aScriptTemplatePath)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrScriptTemplatePath = aScriptTemplatePath;
+ return S_OK;
+}
+
+HRESULT Unattended::getPostInstallScriptTemplatePath(com::Utf8Str &aPostInstallScriptTemplatePath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if ( mStrPostInstallScriptTemplatePath.isNotEmpty()
+ || mpInstaller == NULL)
+ aPostInstallScriptTemplatePath = mStrPostInstallScriptTemplatePath;
+ else
+ aPostInstallScriptTemplatePath = mpInstaller->getPostTemplateFilePath();
+ return S_OK;
+}
+
+HRESULT Unattended::setPostInstallScriptTemplatePath(const com::Utf8Str &aPostInstallScriptTemplatePath)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrPostInstallScriptTemplatePath = aPostInstallScriptTemplatePath;
+ return S_OK;
+}
+
+HRESULT Unattended::getPostInstallCommand(com::Utf8Str &aPostInstallCommand)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aPostInstallCommand = mStrPostInstallCommand;
+ return S_OK;
+}
+
+HRESULT Unattended::setPostInstallCommand(const com::Utf8Str &aPostInstallCommand)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrPostInstallCommand = aPostInstallCommand;
+ return S_OK;
+}
+
+HRESULT Unattended::getExtraInstallKernelParameters(com::Utf8Str &aExtraInstallKernelParameters)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if ( mStrExtraInstallKernelParameters.isNotEmpty()
+ || mpInstaller == NULL)
+ aExtraInstallKernelParameters = mStrExtraInstallKernelParameters;
+ else
+ aExtraInstallKernelParameters = mpInstaller->getDefaultExtraInstallKernelParameters();
+ return S_OK;
+}
+
+HRESULT Unattended::setExtraInstallKernelParameters(const com::Utf8Str &aExtraInstallKernelParameters)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mStrExtraInstallKernelParameters = aExtraInstallKernelParameters;
+ return S_OK;
+}
+
+HRESULT Unattended::getDetectedOSTypeId(com::Utf8Str &aDetectedOSTypeId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aDetectedOSTypeId = mStrDetectedOSTypeId;
+ return S_OK;
+}
+
+HRESULT Unattended::getDetectedOSVersion(com::Utf8Str &aDetectedOSVersion)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aDetectedOSVersion = mStrDetectedOSVersion;
+ return S_OK;
+}
+
+HRESULT Unattended::getDetectedOSFlavor(com::Utf8Str &aDetectedOSFlavor)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aDetectedOSFlavor = mStrDetectedOSFlavor;
+ return S_OK;
+}
+
+HRESULT Unattended::getDetectedOSLanguages(com::Utf8Str &aDetectedOSLanguages)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aDetectedOSLanguages = RTCString::join(mDetectedOSLanguages, " ");
+ return S_OK;
+}
+
+HRESULT Unattended::getDetectedOSHints(com::Utf8Str &aDetectedOSHints)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aDetectedOSHints = mStrDetectedOSHints;
+ return S_OK;
+}
+
+HRESULT Unattended::getDetectedImageNames(std::vector<com::Utf8Str> &aDetectedImageNames)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aDetectedImageNames.clear();
+ for (size_t i = 0; i < mDetectedImages.size(); ++i)
+ {
+ Utf8Str strTmp;
+ aDetectedImageNames.push_back(mDetectedImages[i].formatName(strTmp));
+ }
+ return S_OK;
+}
+
+HRESULT Unattended::getDetectedImageIndices(std::vector<ULONG> &aDetectedImageIndices)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aDetectedImageIndices.clear();
+ for (size_t i = 0; i < mDetectedImages.size(); ++i)
+ aDetectedImageIndices.push_back(mDetectedImages[i].mImageIndex);
+ return S_OK;
+}
+
+HRESULT Unattended::getIsUnattendedInstallSupported(BOOL *aIsUnattendedInstallSupported)
+{
+ /*
+ * Take the initial position that it's not supported, so we can return
+ * right away when we decide it's not possible.
+ */
+ *aIsUnattendedInstallSupported = false;
+
+ /* Unattended is disabled by default if we could not detect OS type. */
+ if (mStrDetectedOSTypeId.isEmpty())
+ return S_OK;
+
+ const VBOXOSTYPE enmOsTypeMasked = (VBOXOSTYPE)(mEnmOsType & VBOXOSTYPE_OsTypeMask);
+
+ /* We require a version to have been detected, except for windows where the
+ field is generally only used for the service pack number at present and
+ will be empty for RTMs isos. */
+ if ( ( enmOsTypeMasked <= VBOXOSTYPE_WinNT
+ || enmOsTypeMasked >= VBOXOSTYPE_OS2)
+ && mStrDetectedOSVersion.isEmpty())
+ return S_OK;
+
+ /*
+ * Sort out things that we know doesn't work. Order by VBOXOSTYPE value.
+ */
+
+ /* We do not support any of the DOS based windows version, nor DOS, in case
+ any of that gets detected (it shouldn't): */
+ if (enmOsTypeMasked >= VBOXOSTYPE_DOS && enmOsTypeMasked < VBOXOSTYPE_WinNT)
+ return S_OK;
+
+ /* Windows NT 3.x doesn't work, also skip unknown windows NT version: */
+ if (enmOsTypeMasked >= VBOXOSTYPE_WinNT && enmOsTypeMasked < VBOXOSTYPE_WinNT4)
+ return S_OK;
+
+ /* For OS/2 we only support OS2 4.5 (actually only 4.52 server has been
+ tested, but we'll get to the others eventually): */
+ if ( enmOsTypeMasked >= VBOXOSTYPE_OS2
+ && enmOsTypeMasked < VBOXOSTYPE_Linux
+ && enmOsTypeMasked != VBOXOSTYPE_OS2Warp45 /* probably works */ )
+ return S_OK;
+
+ /* Old Debians fail since package repos have been move to some other mirror location. */
+ if ( enmOsTypeMasked == VBOXOSTYPE_Debian
+ && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "9.0") < 0)
+ return S_OK;
+
+ /* Skip all OpenSUSE variants for now. */
+ if (enmOsTypeMasked == VBOXOSTYPE_OpenSUSE)
+ return S_OK;
+
+ if (enmOsTypeMasked == VBOXOSTYPE_Ubuntu)
+ {
+ /* We cannot install Ubuntus older than 11.04. */
+ if (RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "11.04") < 0)
+ return S_OK;
+ /* Lubuntu, starting with 20.04, has switched to calamares, which cannot be automated. */
+ if ( RTStrIStr(mStrDetectedOSFlavor.c_str(), "lubuntu")
+ && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "20.04") > 0)
+ return S_OK;
+ }
+
+ /* Earlier than OL 6.4 cannot be installed. OL 6.x fails with unsupported hardware error (CPU family). */
+ if ( enmOsTypeMasked == VBOXOSTYPE_Oracle
+ && RTStrVersionCompare(mStrDetectedOSVersion.c_str(), "6.4") < 0)
+ return S_OK;
+
+ /* Fredora ISOs cannot be installed at present. */
+ if (enmOsTypeMasked == VBOXOSTYPE_FedoraCore)
+ return S_OK;
+
+ /*
+ * Assume the rest works.
+ */
+ *aIsUnattendedInstallSupported = true;
+ return S_OK;
+}
+
+HRESULT Unattended::getAvoidUpdatesOverNetwork(BOOL *aAvoidUpdatesOverNetwork)
+{
+ *aAvoidUpdatesOverNetwork = mfAvoidUpdatesOverNetwork;
+ return S_OK;
+}
+
+HRESULT Unattended::setAvoidUpdatesOverNetwork(BOOL aAvoidUpdatesOverNetwork)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mpInstaller == NULL, setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("Cannot change after prepare() has been called")));
+ mfAvoidUpdatesOverNetwork = RT_BOOL(aAvoidUpdatesOverNetwork);
+ return S_OK;
+}
+
+/*
+ * Getters that the installer and script classes can use.
+ */
+Utf8Str const &Unattended::i_getIsoPath() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrIsoPath;
+}
+
+Utf8Str const &Unattended::i_getUser() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrUser;
+}
+
+Utf8Str const &Unattended::i_getPassword() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrPassword;
+}
+
+Utf8Str const &Unattended::i_getFullUserName() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrFullUserName.isNotEmpty() ? mStrFullUserName : mStrUser;
+}
+
+Utf8Str const &Unattended::i_getProductKey() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrProductKey;
+}
+
+Utf8Str const &Unattended::i_getProxy() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrProxy;
+}
+
+Utf8Str const &Unattended::i_getAdditionsIsoPath() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrAdditionsIsoPath;
+}
+
+bool Unattended::i_getInstallGuestAdditions() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mfInstallGuestAdditions;
+}
+
+Utf8Str const &Unattended::i_getValidationKitIsoPath() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrValidationKitIsoPath;
+}
+
+bool Unattended::i_getInstallTestExecService() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mfInstallTestExecService;
+}
+
+Utf8Str const &Unattended::i_getTimeZone() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrTimeZone;
+}
+
+PCRTTIMEZONEINFO Unattended::i_getTimeZoneInfo() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mpTimeZoneInfo;
+}
+
+Utf8Str const &Unattended::i_getLocale() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrLocale;
+}
+
+Utf8Str const &Unattended::i_getLanguage() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrLanguage;
+}
+
+Utf8Str const &Unattended::i_getCountry() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrCountry;
+}
+
+bool Unattended::i_isMinimalInstallation() const
+{
+ size_t i = mPackageSelectionAdjustments.size();
+ while (i-- > 0)
+ if (mPackageSelectionAdjustments[i].equals("minimal"))
+ return true;
+ return false;
+}
+
+Utf8Str const &Unattended::i_getHostname() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrHostname;
+}
+
+Utf8Str const &Unattended::i_getAuxiliaryBasePath() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrAuxiliaryBasePath;
+}
+
+ULONG Unattended::i_getImageIndex() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return midxImage;
+}
+
+Utf8Str const &Unattended::i_getScriptTemplatePath() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrScriptTemplatePath;
+}
+
+Utf8Str const &Unattended::i_getPostInstallScriptTemplatePath() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrPostInstallScriptTemplatePath;
+}
+
+Utf8Str const &Unattended::i_getPostInstallCommand() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrPostInstallCommand;
+}
+
+Utf8Str const &Unattended::i_getAuxiliaryInstallDir() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ /* Only the installer knows, forward the call. */
+ AssertReturn(mpInstaller != NULL, Utf8Str::Empty);
+ return mpInstaller->getAuxiliaryInstallDir();
+}
+
+Utf8Str const &Unattended::i_getExtraInstallKernelParameters() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrExtraInstallKernelParameters;
+}
+
+bool Unattended::i_isRtcUsingUtc() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mfRtcUseUtc;
+}
+
+bool Unattended::i_isGuestOs64Bit() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mfGuestOs64Bit;
+}
+
+bool Unattended::i_isFirmwareEFI() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return menmFirmwareType != FirmwareType_BIOS;
+}
+
+Utf8Str const &Unattended::i_getDetectedOSVersion()
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mStrDetectedOSVersion;
+}
+
+bool Unattended::i_getAvoidUpdatesOverNetwork() const
+{
+ Assert(isReadLockedOnCurrentThread());
+ return mfAvoidUpdatesOverNetwork;
+}
+
+HRESULT Unattended::i_attachImage(UnattendedInstallationDisk const *pImage, ComPtr<IMachine> const &rPtrSessionMachine,
+ AutoMultiWriteLock2 &rLock)
+{
+ /*
+ * Attach the disk image
+ * HACK ALERT! Temporarily release the Unattended lock.
+ */
+ rLock.release();
+
+ ComPtr<IMedium> ptrMedium;
+ HRESULT hrc = mParent->OpenMedium(Bstr(pImage->strImagePath).raw(),
+ pImage->enmDeviceType,
+ pImage->enmAccessType,
+ true,
+ ptrMedium.asOutParam());
+ LogRelFlowFunc(("VirtualBox::openMedium -> %Rhrc\n", hrc));
+ if (SUCCEEDED(hrc))
+ {
+ if (pImage->fAuxiliary && pImage->strImagePath.endsWith(".viso"))
+ {
+ hrc = ptrMedium->SetProperty(Bstr("UnattendedInstall").raw(), Bstr("1").raw());
+ LogRelFlowFunc(("Medium::SetProperty -> %Rhrc\n", hrc));
+ }
+ if (pImage->fMountOnly)
+ {
+ // mount the opened disk image
+ hrc = rPtrSessionMachine->MountMedium(Bstr(pImage->strControllerName).raw(), pImage->iPort,
+ pImage->iDevice, ptrMedium, TRUE /*fForce*/);
+ LogRelFlowFunc(("Machine::MountMedium -> %Rhrc\n", hrc));
+ }
+ else
+ {
+ //attach the opened disk image to the controller
+ hrc = rPtrSessionMachine->AttachDevice(Bstr(pImage->strControllerName).raw(), pImage->iPort,
+ pImage->iDevice, pImage->enmDeviceType, ptrMedium);
+ LogRelFlowFunc(("Machine::AttachDevice -> %Rhrc\n", hrc));
+ }
+ }
+
+ rLock.acquire();
+ return hrc;
+}
+
+bool Unattended::i_isGuestOSArchX64(Utf8Str const &rStrGuestOsTypeId)
+{
+ ComPtr<IGuestOSType> pGuestOSType;
+ HRESULT hrc = mParent->GetGuestOSType(Bstr(rStrGuestOsTypeId).raw(), pGuestOSType.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ BOOL fIs64Bit = FALSE;
+ if (!pGuestOSType.isNull())
+ hrc = pGuestOSType->COMGETTER(Is64Bit)(&fIs64Bit);
+ if (SUCCEEDED(hrc))
+ return fIs64Bit != FALSE;
+ }
+ return false;
+}
+
+
+bool Unattended::i_updateDetectedAttributeForImage(WIMImage const &rImage)
+{
+ bool fRet = true;
+
+ /*
+ * If the image doesn't have a valid value, we don't change it.
+ * This is obviously a little bit bogus, but what can we do...
+ */
+ const char *pszOSTypeId = Global::OSTypeId(rImage.mOSType);
+ if (pszOSTypeId && strcmp(pszOSTypeId, "Other") != 0)
+ mStrDetectedOSTypeId = pszOSTypeId;
+ else
+ fRet = false;
+
+ if (rImage.mVersion.isNotEmpty())
+ mStrDetectedOSVersion = rImage.mVersion;
+ else
+ fRet = false;
+
+ if (rImage.mFlavor.isNotEmpty())
+ mStrDetectedOSFlavor = rImage.mFlavor;
+ else
+ fRet = false;
+
+ if (rImage.mLanguages.size() > 0)
+ mDetectedOSLanguages = rImage.mLanguages;
+ else
+ fRet = false;
+
+ mEnmOsType = rImage.mEnmOsType;
+
+ return fRet;
+}
diff --git a/src/VBox/Main/src-server/UnattendedInstaller.cpp b/src/VBox/Main/src-server/UnattendedInstaller.cpp
new file mode 100644
index 00000000..79c5ad19
--- /dev/null
+++ b/src/VBox/Main/src-server/UnattendedInstaller.cpp
@@ -0,0 +1,1590 @@
+/* $Id: UnattendedInstaller.cpp $ */
+/** @file
+ * UnattendedInstaller class and it's descendants implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
+#include "LoggingNew.h"
+#include "VirtualBoxBase.h"
+#include "VirtualBoxErrorInfoImpl.h"
+#include "AutoCaller.h"
+#include <VBox/com/ErrorInfo.h>
+
+#include "UnattendedImpl.h"
+#include "UnattendedInstaller.h"
+#include "UnattendedScript.h"
+
+#include <VBox/err.h>
+#include <iprt/ctype.h>
+#include <iprt/fsisomaker.h>
+#include <iprt/fsvfs.h>
+#include <iprt/getopt.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/vfs.h>
+#ifdef RT_OS_SOLARIS
+# undef ES /* Workaround for someone dragging the namespace pollutor sys/regset.h. Sigh. */
+#endif
+#include <iprt/formats/iso9660.h>
+#include <iprt/cpp/path.h>
+
+
+using namespace std;
+
+
+/* static */ UnattendedInstaller *
+UnattendedInstaller::createInstance(VBOXOSTYPE enmDetectedOSType, const Utf8Str &strDetectedOSType,
+ const Utf8Str &strDetectedOSVersion, const Utf8Str &strDetectedOSFlavor,
+ const Utf8Str &strDetectedOSHints, Unattended *pParent)
+{
+ UnattendedInstaller *pUinstaller = NULL;
+
+ if (strDetectedOSType.find("Windows") != RTCString::npos)
+ {
+ if (enmDetectedOSType >= VBOXOSTYPE_WinVista)
+ pUinstaller = new UnattendedWindowsXmlInstaller(pParent);
+ else
+ pUinstaller = new UnattendedWindowsSifInstaller(pParent);
+ }
+ else if (enmDetectedOSType >= VBOXOSTYPE_OS2 && enmDetectedOSType < VBOXOSTYPE_Linux)
+ pUinstaller = new UnattendedOs2Installer(pParent, strDetectedOSHints);
+ else
+ {
+ if (enmDetectedOSType >= VBOXOSTYPE_Debian && enmDetectedOSType <= VBOXOSTYPE_Debian_latest_x64)
+ pUinstaller = new UnattendedDebianInstaller(pParent);
+ else if (enmDetectedOSType >= VBOXOSTYPE_Ubuntu && enmDetectedOSType <= VBOXOSTYPE_Ubuntu_latest_x64)
+ pUinstaller = new UnattendedUbuntuInstaller(pParent);
+ else if (enmDetectedOSType >= VBOXOSTYPE_RedHat && enmDetectedOSType <= VBOXOSTYPE_RedHat_latest_x64)
+ {
+ if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "8") >= 0)
+ pUinstaller = new UnattendedRhel8Installer(pParent);
+ else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "7") >= 0)
+ pUinstaller = new UnattendedRhel7Installer(pParent);
+ else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "6") >= 0)
+ pUinstaller = new UnattendedRhel6Installer(pParent);
+ else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "5") >= 0)
+ pUinstaller = new UnattendedRhel5Installer(pParent);
+ else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "4") >= 0)
+ pUinstaller = new UnattendedRhel4Installer(pParent);
+ else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "3") >= 0)
+ pUinstaller = new UnattendedRhel3Installer(pParent);
+ else
+ pUinstaller = new UnattendedRhel6Installer(pParent);
+ }
+ else if (enmDetectedOSType >= VBOXOSTYPE_FedoraCore && enmDetectedOSType <= VBOXOSTYPE_FedoraCore_x64)
+ pUinstaller = new UnattendedFedoraInstaller(pParent);
+ else if (enmDetectedOSType >= VBOXOSTYPE_Oracle && enmDetectedOSType <= VBOXOSTYPE_Oracle_latest_x64)
+ {
+ if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "8") >= 0)
+ pUinstaller = new UnattendedOracleLinux8Installer(pParent);
+ else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "7") >= 0)
+ pUinstaller = new UnattendedOracleLinux7Installer(pParent);
+ else if (RTStrVersionCompare(strDetectedOSVersion.c_str(), "6") >= 0)
+ pUinstaller = new UnattendedOracleLinux6Installer(pParent);
+ else
+ pUinstaller = new UnattendedOracleLinux6Installer(pParent);
+ }
+ else if (enmDetectedOSType >= VBOXOSTYPE_FreeBSD && enmDetectedOSType <= VBOXOSTYPE_FreeBSD_x64)
+ pUinstaller = new UnattendedFreeBsdInstaller(pParent);
+#if 0 /* doesn't work, so convert later. */
+ else if (enmDetectedOSType == VBOXOSTYPE_OpenSUSE || enmDetectedOSType == VBOXOSTYPE_OpenSUSE_x64)
+ pUinstaller = new UnattendedSuseInstaller(new UnattendedSUSEXMLScript(pParent), pParent);
+#endif
+ }
+ RT_NOREF_PV(strDetectedOSFlavor);
+ RT_NOREF_PV(strDetectedOSHints);
+ return pUinstaller;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+*
+*
+* Implementation Unattended functions
+*
+*/
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*
+ *
+ * UnattendedInstaller public methods
+ *
+ */
+UnattendedInstaller::UnattendedInstaller(Unattended *pParent,
+ const char *pszMainScriptTemplateName, const char *pszPostScriptTemplateName,
+ const char *pszMainScriptFilename, const char *pszPostScriptFilename,
+ DeviceType_T enmBootDevice /*= DeviceType_DVD */)
+ : mMainScript(pParent, pszMainScriptTemplateName, pszMainScriptFilename)
+ , mPostScript(pParent, pszPostScriptTemplateName, pszPostScriptFilename)
+ , mpParent(pParent)
+ , meBootDevice(enmBootDevice)
+{
+ AssertPtr(pParent);
+ Assert(*pszMainScriptTemplateName);
+ Assert(*pszMainScriptFilename);
+ Assert(*pszPostScriptTemplateName);
+ Assert(*pszPostScriptFilename);
+ Assert(enmBootDevice == DeviceType_DVD || enmBootDevice == DeviceType_Floppy);
+}
+
+UnattendedInstaller::~UnattendedInstaller()
+{
+ mpParent = NULL;
+}
+
+HRESULT UnattendedInstaller::initInstaller()
+{
+ /*
+ * Calculate the full main script template location.
+ */
+ if (mpParent->i_getScriptTemplatePath().isNotEmpty())
+ mStrMainScriptTemplate = mpParent->i_getScriptTemplatePath();
+ else
+ {
+ int vrc = RTPathAppPrivateNoArchCxx(mStrMainScriptTemplate);
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppendCxx(mStrMainScriptTemplate, "UnattendedTemplates");
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppendCxx(mStrMainScriptTemplate, mMainScript.getDefaultTemplateFilename());
+ if (RT_FAILURE(vrc))
+ return mpParent->setErrorBoth(E_FAIL, vrc,
+ tr("Failed to construct path to the unattended installer script templates (%Rrc)"),
+ vrc);
+ }
+
+ /*
+ * Calculate the full post script template location.
+ */
+ if (mpParent->i_getPostInstallScriptTemplatePath().isNotEmpty())
+ mStrPostScriptTemplate = mpParent->i_getPostInstallScriptTemplatePath();
+ else
+ {
+ int vrc = RTPathAppPrivateNoArchCxx(mStrPostScriptTemplate);
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppendCxx(mStrPostScriptTemplate, "UnattendedTemplates");
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppendCxx(mStrPostScriptTemplate, mPostScript.getDefaultTemplateFilename());
+ if (RT_FAILURE(vrc))
+ return mpParent->setErrorBoth(E_FAIL, vrc,
+ tr("Failed to construct path to the unattended installer script templates (%Rrc)"),
+ vrc);
+ }
+
+ /*
+ * Construct paths we need.
+ */
+ if (isAuxiliaryFloppyNeeded())
+ {
+ mStrAuxiliaryFloppyFilePath = mpParent->i_getAuxiliaryBasePath();
+ mStrAuxiliaryFloppyFilePath.append("aux-floppy.img");
+ }
+ if (isAuxiliaryIsoNeeded())
+ {
+ mStrAuxiliaryIsoFilePath = mpParent->i_getAuxiliaryBasePath();
+ if (!isAuxiliaryIsoIsVISO())
+ mStrAuxiliaryIsoFilePath.append("aux-iso.iso");
+ else
+ mStrAuxiliaryIsoFilePath.append("aux-iso.viso");
+ }
+
+ /*
+ * Check that we've got the minimum of data available.
+ */
+ if (mpParent->i_getIsoPath().isEmpty())
+ return mpParent->setError(E_INVALIDARG, tr("Cannot proceed with an empty installation ISO path"));
+ if (mpParent->i_getUser().isEmpty())
+ return mpParent->setError(E_INVALIDARG, tr("Empty user name is not allowed"));
+ if (mpParent->i_getPassword().isEmpty())
+ return mpParent->setError(E_INVALIDARG, tr("Empty password is not allowed"));
+
+ LogRelFunc(("UnattendedInstaller::savePassedData(): \n"));
+ return S_OK;
+}
+
+#if 0 /* Always in AUX ISO */
+bool UnattendedInstaller::isAdditionsIsoNeeded() const
+{
+ /* In the VISO case, we'll add the additions to the VISO in a subdir. */
+ return !isAuxiliaryIsoIsVISO() && mpParent->i_getInstallGuestAdditions();
+}
+
+bool UnattendedInstaller::isValidationKitIsoNeeded() const
+{
+ /* In the VISO case, we'll add the validation kit to the VISO in a subdir. */
+ return !isAuxiliaryIsoIsVISO() && mpParent->i_getInstallTestExecService();
+}
+#endif
+
+bool UnattendedInstaller::isAuxiliaryIsoNeeded() const
+{
+ /* In the VISO case we use the AUX ISO for GAs and TXS. */
+ return isAuxiliaryIsoIsVISO()
+ && ( mpParent->i_getInstallGuestAdditions()
+ || mpParent->i_getInstallTestExecService());
+}
+
+
+HRESULT UnattendedInstaller::prepareUnattendedScripts()
+{
+ LogFlow(("UnattendedInstaller::prepareUnattendedScripts()\n"));
+
+ /*
+ * The script template editor calls setError, so status codes just needs to
+ * be passed on to the caller. Do the same for both scripts.
+ */
+ HRESULT hrc = mMainScript.read(getTemplateFilePath());
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mMainScript.parse();
+ if (SUCCEEDED(hrc))
+ {
+ /* Ditto for the post script. */
+ hrc = mPostScript.read(getPostTemplateFilePath());
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mPostScript.parse();
+ if (SUCCEEDED(hrc))
+ {
+ LogFlow(("UnattendedInstaller::prepareUnattendedScripts: returns S_OK\n"));
+ return S_OK;
+ }
+ LogFlow(("UnattendedInstaller::prepareUnattendedScripts: parse failed on post script (%Rhrc)\n", hrc));
+ }
+ else
+ LogFlow(("UnattendedInstaller::prepareUnattendedScripts: error reading post install script template file (%Rhrc)\n", hrc));
+ }
+ else
+ LogFlow(("UnattendedInstaller::prepareUnattendedScripts: parse failed (%Rhrc)\n", hrc));
+ }
+ else
+ LogFlow(("UnattendedInstaller::prepareUnattendedScripts: error reading installation script template file (%Rhrc)\n", hrc));
+ return hrc;
+}
+
+HRESULT UnattendedInstaller::prepareMedia(bool fOverwrite /*=true*/)
+{
+ LogRelFlow(("UnattendedInstaller::prepareMedia:\n"));
+ HRESULT hrc = S_OK;
+ if (isAuxiliaryFloppyNeeded())
+ hrc = prepareAuxFloppyImage(fOverwrite);
+ if (SUCCEEDED(hrc))
+ {
+ if (isAuxiliaryIsoNeeded())
+ {
+ hrc = prepareAuxIsoImage(fOverwrite);
+ if (FAILED(hrc))
+ {
+ LogRelFlow(("UnattendedInstaller::prepareMedia: prepareAuxIsoImage failed\n"));
+
+ /* Delete the floppy image if we created one. */
+ if (isAuxiliaryFloppyNeeded())
+ RTFileDelete(getAuxiliaryFloppyFilePath().c_str());
+ }
+ }
+ }
+ LogRelFlow(("UnattendedInstaller::prepareMedia: returns %Rrc\n", hrc));
+ return hrc;
+}
+
+/*
+ *
+ * UnattendedInstaller protected methods
+ *
+ */
+HRESULT UnattendedInstaller::prepareAuxFloppyImage(bool fOverwrite)
+{
+ Assert(isAuxiliaryFloppyNeeded());
+
+ /*
+ * Create the image.
+ */
+ RTVFSFILE hVfsFile;
+ HRESULT hrc = newAuxFloppyImage(getAuxiliaryFloppyFilePath().c_str(), fOverwrite, &hVfsFile);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Open the FAT file system so we can copy files onto the floppy.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ RTVFS hVfs;
+ int vrc = RTFsFatVolOpen(hVfsFile, false /*fReadOnly*/, 0 /*offBootSector*/, &hVfs, RTErrInfoInitStatic(&ErrInfo));
+ RTVfsFileRelease(hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Call overridable method to copies the files onto it.
+ */
+ hrc = copyFilesToAuxFloppyImage(hVfs);
+
+ /*
+ * Release the VFS. On failure, delete the floppy image so the operation can
+ * be repeated in non-overwrite mode and so that we don't leave any mess behind.
+ */
+ RTVfsRelease(hVfs);
+ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ hrc = mpParent->setErrorBoth(E_FAIL, vrc,
+ tr("Failed to open FAT file system on newly created floppy image '%s': %Rrc: %s"),
+ getAuxiliaryFloppyFilePath().c_str(), vrc, ErrInfo.Core.pszMsg);
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, vrc,
+ tr("Failed to open FAT file system onnewly created floppy image '%s': %Rrc"),
+ getAuxiliaryFloppyFilePath().c_str(), vrc);
+ if (FAILED(hrc))
+ RTFileDelete(getAuxiliaryFloppyFilePath().c_str());
+ }
+ return hrc;
+}
+
+HRESULT UnattendedInstaller::newAuxFloppyImage(const char *pszFilename, bool fOverwrite, PRTVFSFILE phVfsFile)
+{
+ /*
+ * Open the image file.
+ */
+ HRESULT hrc;
+ RTVFSFILE hVfsFile;
+ uint64_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_ALL | (0660 << RTFILE_O_CREATE_MODE_SHIFT);
+ if (fOverwrite)
+ fOpen |= RTFILE_O_CREATE_REPLACE;
+ else
+ fOpen |= RTFILE_O_OPEN;
+ int vrc = RTVfsFileOpenNormal(pszFilename, fOpen, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Format it.
+ */
+ vrc = RTFsFatVolFormat144(hVfsFile, false /*fQuick*/);
+ if (RT_SUCCESS(vrc))
+ {
+ *phVfsFile = hVfsFile;
+ LogRelFlow(("UnattendedInstaller::newAuxFloppyImage: created and formatted '%s'\n", pszFilename));
+ return S_OK;
+ }
+
+ hrc = mpParent->setErrorBoth(E_FAIL, vrc, tr("Failed to format floppy image '%s': %Rrc"), pszFilename, vrc);
+ RTVfsFileRelease(hVfsFile);
+ RTFileDelete(pszFilename);
+ }
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, vrc, tr("Failed to create floppy image '%s': %Rrc"), pszFilename, vrc);
+ return hrc;
+}
+
+HRESULT UnattendedInstaller::copyFilesToAuxFloppyImage(RTVFS hVfs)
+{
+ HRESULT hrc = addScriptToFloppyImage(&mMainScript, hVfs);
+ if (SUCCEEDED(hrc))
+ hrc = addScriptToFloppyImage(&mPostScript, hVfs);
+ return hrc;
+}
+
+HRESULT UnattendedInstaller::addScriptToFloppyImage(BaseTextScript *pEditor, RTVFS hVfs)
+{
+ /*
+ * Open the destination file.
+ */
+ HRESULT hrc;
+ RTVFSFILE hVfsFileDst;
+ int vrc = RTVfsFileOpen(hVfs, pEditor->getDefaultFilename(),
+ RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_ALL
+ | (0660 << RTFILE_O_CREATE_MODE_SHIFT),
+ &hVfsFileDst);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Save the content to a string.
+ */
+ Utf8Str strScript;
+ hrc = pEditor->saveToString(strScript);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Write the string.
+ */
+ vrc = RTVfsFileWrite(hVfsFileDst, strScript.c_str(), strScript.length(), NULL);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK; /* done */
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, vrc,
+ tr("Error writing %zu bytes to '%s' in floppy image '%s': %Rrc",
+ "", strScript.length()),
+ strScript.length(), pEditor->getDefaultFilename(),
+ getAuxiliaryFloppyFilePath().c_str());
+ }
+ RTVfsFileRelease(hVfsFileDst);
+ }
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, vrc,
+ tr("Error creating '%s' in floppy image '%s': %Rrc"),
+ pEditor->getDefaultFilename(), getAuxiliaryFloppyFilePath().c_str());
+ return hrc;
+}
+
+HRESULT UnattendedInstaller::addFileToFloppyImage(RTVFS hVfs, const char *pszSrc, const char *pszDst)
+{
+ HRESULT hrc;
+
+ /*
+ * Open the source file.
+ */
+ RTVFSIOSTREAM hVfsIosSrc;
+ int vrc = RTVfsIoStrmOpenNormal(pszSrc, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsIosSrc);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Open the destination file.
+ */
+ RTVFSFILE hVfsFileDst;
+ vrc = RTVfsFileOpen(hVfs, pszDst,
+ RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_ALL | (0660 << RTFILE_O_CREATE_MODE_SHIFT),
+ &hVfsFileDst);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Do the copying.
+ */
+ RTVFSIOSTREAM hVfsIosDst = RTVfsFileToIoStream(hVfsFileDst);
+ vrc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, 0);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error writing copying '%s' to floppy image '%s': %Rrc"),
+ pszSrc, getAuxiliaryFloppyFilePath().c_str(), vrc);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ RTVfsFileRelease(hVfsFileDst);
+ }
+ else
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error opening '%s' on floppy image '%s' for writing: %Rrc"),
+ pszDst, getAuxiliaryFloppyFilePath().c_str(), vrc);
+
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ }
+ else
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error opening '%s' for copying onto floppy image '%s': %Rrc"),
+ pszSrc, getAuxiliaryFloppyFilePath().c_str(), vrc);
+ return hrc;
+}
+
+HRESULT UnattendedInstaller::prepareAuxIsoImage(bool fOverwrite)
+{
+ /*
+ * Open the original installation ISO.
+ */
+ RTVFS hVfsOrgIso;
+ HRESULT hrc = openInstallIsoImage(&hVfsOrgIso);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * The next steps depends on the kind of image we're making.
+ */
+ if (!isAuxiliaryIsoIsVISO())
+ {
+ RTFSISOMAKER hIsoMaker;
+ hrc = newAuxIsoImageMaker(&hIsoMaker);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = addFilesToAuxIsoImageMaker(hIsoMaker, hVfsOrgIso);
+ if (SUCCEEDED(hrc))
+ hrc = finalizeAuxIsoImage(hIsoMaker, getAuxiliaryIsoFilePath().c_str(), fOverwrite);
+ RTFsIsoMakerRelease(hIsoMaker);
+ }
+ }
+ else
+ {
+ RTCList<RTCString> vecFiles(0);
+ RTCList<RTCString> vecArgs(0);
+ try
+ {
+ vecArgs.append() = "--iprt-iso-maker-file-marker-bourne-sh";
+ RTUUID Uuid;
+ int vrc = RTUuidCreate(&Uuid); AssertRC(vrc);
+ char szTmp[RTUUID_STR_LENGTH + 1];
+ vrc = RTUuidToStr(&Uuid, szTmp, sizeof(szTmp)); AssertRC(vrc);
+ vecArgs.append() = szTmp;
+ vecArgs.append() = "--file-mode=0444";
+ vecArgs.append() = "--dir-mode=0555";
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ hrc = addFilesToAuxVisoVectors(vecArgs, vecFiles, hVfsOrgIso, fOverwrite);
+ if (SUCCEEDED(hrc))
+ hrc = finalizeAuxVisoFile(vecArgs, getAuxiliaryIsoFilePath().c_str(), fOverwrite);
+
+ if (FAILED(hrc))
+ for (size_t i = 0; i < vecFiles.size(); i++)
+ RTFileDelete(vecFiles[i].c_str());
+ }
+ }
+ RTVfsRelease(hVfsOrgIso);
+ }
+ return hrc;
+}
+
+HRESULT UnattendedInstaller::openInstallIsoImage(PRTVFS phVfsIso, uint32_t fFlags /*= 0*/)
+{
+ /* Open the file. */
+ const char *pszIsoPath = mpParent->i_getIsoPath().c_str();
+ RTVFSFILE hOrgIsoFile;
+ int vrc = RTVfsFileOpenNormal(pszIsoPath, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hOrgIsoFile);
+ if (RT_FAILURE(vrc))
+ return mpParent->setErrorBoth(E_FAIL, vrc, tr("Failed to open ISO image '%s' (%Rrc)"), pszIsoPath, vrc);
+
+ /* Pass the file to the ISO file system interpreter. */
+ RTERRINFOSTATIC ErrInfo;
+ vrc = RTFsIso9660VolOpen(hOrgIsoFile, fFlags, phVfsIso, RTErrInfoInitStatic(&ErrInfo));
+ RTVfsFileRelease(hOrgIsoFile);
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+ if (RTErrInfoIsSet(&ErrInfo.Core))
+ return mpParent->setErrorBoth(E_FAIL, vrc, tr("ISO reader fail to open '%s' (%Rrc): %s"),
+ pszIsoPath, vrc, ErrInfo.Core.pszMsg);
+ return mpParent->setErrorBoth(E_FAIL, vrc, tr("ISO reader fail to open '%s' (%Rrc)"), pszIsoPath, vrc);
+}
+
+HRESULT UnattendedInstaller::newAuxIsoImageMaker(PRTFSISOMAKER phIsoMaker)
+{
+ int vrc = RTFsIsoMakerCreate(phIsoMaker);
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+ return mpParent->setErrorBoth(E_FAIL, vrc, tr("RTFsIsoMakerCreate failed (%Rrc)"), vrc);
+}
+
+HRESULT UnattendedInstaller::addFilesToAuxIsoImageMaker(RTFSISOMAKER hIsoMaker, RTVFS hVfsOrgIso)
+{
+ RT_NOREF(hVfsOrgIso);
+
+ /*
+ * Add the two scripts to the image with default names.
+ */
+ HRESULT hrc = addScriptToIsoMaker(&mMainScript, hIsoMaker);
+ if (SUCCEEDED(hrc))
+ hrc = addScriptToIsoMaker(&mPostScript, hIsoMaker);
+ return hrc;
+}
+
+HRESULT UnattendedInstaller::addScriptToIsoMaker(BaseTextScript *pEditor, RTFSISOMAKER hIsoMaker,
+ const char *pszDstFilename /*= NULL*/)
+{
+ /*
+ * Calc default destination filename if desired.
+ */
+ RTCString strDstNameBuf;
+ if (!pszDstFilename)
+ {
+ try
+ {
+ strDstNameBuf = RTPATH_SLASH_STR;
+ strDstNameBuf.append(pEditor->getDefaultTemplateFilename());
+ pszDstFilename = strDstNameBuf.c_str();
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ /*
+ * Create a memory file for the script.
+ */
+ Utf8Str strScript;
+ HRESULT hrc = pEditor->saveToString(strScript);
+ if (SUCCEEDED(hrc))
+ {
+ RTVFSFILE hVfsScriptFile;
+ size_t cchScript = strScript.length();
+ int vrc = RTVfsFileFromBuffer(RTFILE_O_READ, strScript.c_str(), strScript.length(), &hVfsScriptFile);
+ strScript.setNull();
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Add it to the ISO.
+ */
+ vrc = RTFsIsoMakerAddFileWithVfsFile(hIsoMaker, pszDstFilename, hVfsScriptFile, NULL);
+ RTVfsFileRelease(hVfsScriptFile);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, vrc,
+ tr("RTFsIsoMakerAddFileWithVfsFile failed on the script '%s' (%Rrc)"),
+ pszDstFilename, vrc);
+ }
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, vrc,
+ tr("RTVfsFileFromBuffer failed on the %zu byte script '%s' (%Rrc)", "", cchScript),
+ cchScript, pszDstFilename, vrc);
+ }
+ return hrc;
+}
+
+HRESULT UnattendedInstaller::finalizeAuxIsoImage(RTFSISOMAKER hIsoMaker, const char *pszFilename, bool fOverwrite)
+{
+ /*
+ * Finalize the image.
+ */
+ int vrc = RTFsIsoMakerFinalize(hIsoMaker);
+ if (RT_FAILURE(vrc))
+ return mpParent->setErrorBoth(E_FAIL, vrc, tr("RTFsIsoMakerFinalize failed (%Rrc)"), vrc);
+
+ /*
+ * Open the destination file.
+ */
+ uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_ALL;
+ if (fOverwrite)
+ fOpen |= RTFILE_O_CREATE_REPLACE;
+ else
+ fOpen |= RTFILE_O_CREATE;
+ RTVFSFILE hVfsDstFile;
+ vrc = RTVfsFileOpenNormal(pszFilename, fOpen, &hVfsDstFile);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_ALREADY_EXISTS)
+ return mpParent->setErrorBoth(E_FAIL, vrc, tr("The auxiliary ISO image file '%s' already exists"),
+ pszFilename);
+ return mpParent->setErrorBoth(E_FAIL, vrc, tr("Failed to open the auxiliary ISO image file '%s' for writing (%Rrc)"),
+ pszFilename, vrc);
+ }
+
+ /*
+ * Get the source file from the image maker.
+ */
+ HRESULT hrc;
+ RTVFSFILE hVfsSrcFile;
+ vrc = RTFsIsoMakerCreateVfsOutputFile(hIsoMaker, &hVfsSrcFile);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hVfsSrcIso = RTVfsFileToIoStream(hVfsSrcFile);
+ RTVFSIOSTREAM hVfsDstIso = RTVfsFileToIoStream(hVfsDstFile);
+ if ( hVfsSrcIso != NIL_RTVFSIOSTREAM
+ && hVfsDstIso != NIL_RTVFSIOSTREAM)
+ {
+ vrc = RTVfsUtilPumpIoStreams(hVfsSrcIso, hVfsDstIso, 0 /*cbBufHint*/);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, vrc, tr("Error writing auxiliary ISO image '%s' (%Rrc)"),
+ pszFilename, vrc);
+ }
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, VERR_INTERNAL_ERROR_2,
+ tr("Internal Error: Failed to case VFS file to VFS I/O stream"));
+ RTVfsIoStrmRelease(hVfsSrcIso);
+ RTVfsIoStrmRelease(hVfsDstIso);
+ }
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, vrc, tr("RTFsIsoMakerCreateVfsOutputFile failed (%Rrc)"), vrc);
+ RTVfsFileRelease(hVfsSrcFile);
+ RTVfsFileRelease(hVfsDstFile);
+ if (FAILED(hrc))
+ RTFileDelete(pszFilename);
+ return hrc;
+}
+
+HRESULT UnattendedInstaller::addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
+ RTVFS hVfsOrgIso, bool fOverwrite)
+{
+ RT_NOREF(hVfsOrgIso);
+
+ /*
+ * Save and add the scripts.
+ */
+ HRESULT hrc = addScriptToVisoVectors(&mMainScript, rVecArgs, rVecFiles, fOverwrite);
+ if (SUCCEEDED(hrc))
+ hrc = addScriptToVisoVectors(&mPostScript, rVecArgs, rVecFiles, fOverwrite);
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ /*
+ * If we've got a Guest Additions ISO, add its content to a /vboxadditions dir.
+ */
+ if (mpParent->i_getInstallGuestAdditions())
+ {
+ rVecArgs.append().append("--push-iso=").append(mpParent->i_getAdditionsIsoPath());
+ rVecArgs.append() = "/vboxadditions=/";
+ rVecArgs.append() = "--pop";
+ }
+
+ /*
+ * If we've got a Validation Kit ISO, add its content to a /vboxvalidationkit dir.
+ */
+ if (mpParent->i_getInstallTestExecService())
+ {
+ rVecArgs.append().append("--push-iso=").append(mpParent->i_getValidationKitIsoPath());
+ rVecArgs.append() = "/vboxvalidationkit=/";
+ rVecArgs.append() = "--pop";
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ return hrc;
+}
+
+HRESULT UnattendedInstaller::addScriptToVisoVectors(BaseTextScript *pEditor, RTCList<RTCString> &rVecArgs,
+ RTCList<RTCString> &rVecFiles, bool fOverwrite)
+{
+ /*
+ * Calc the aux script file name.
+ */
+ RTCString strScriptName;
+ try
+ {
+ strScriptName = mpParent->i_getAuxiliaryBasePath();
+ strScriptName.append(pEditor->getDefaultFilename());
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Save it.
+ */
+ HRESULT hrc = pEditor->save(strScriptName.c_str(), fOverwrite);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Add it to the vectors.
+ */
+ try
+ {
+ rVecArgs.append().append('/').append(pEditor->getDefaultFilename()).append('=').append(strScriptName);
+ rVecFiles.append(strScriptName);
+ }
+ catch (std::bad_alloc &)
+ {
+ RTFileDelete(strScriptName.c_str());
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ return hrc;
+}
+
+HRESULT UnattendedInstaller::finalizeAuxVisoFile(RTCList<RTCString> const &rVecArgs, const char *pszFilename, bool fOverwrite)
+{
+ /*
+ * Create a C-style argument vector and turn that into a command line string.
+ */
+ size_t const cArgs = rVecArgs.size();
+ const char **papszArgs = (const char **)RTMemTmpAlloc((cArgs + 1) * sizeof(const char *));
+ if (!papszArgs)
+ return E_OUTOFMEMORY;
+ for (size_t i = 0; i < cArgs; i++)
+ papszArgs[i] = rVecArgs[i].c_str();
+ papszArgs[cArgs] = NULL;
+
+ char *pszCmdLine;
+ int vrc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+ RTMemTmpFree(papszArgs);
+ if (RT_FAILURE(vrc))
+ return mpParent->setErrorBoth(E_FAIL, vrc, tr("RTGetOptArgvToString failed (%Rrc)"), vrc);
+
+ /*
+ * Open the file.
+ */
+ HRESULT hrc;
+ uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_DENY_READ;
+ if (fOverwrite)
+ fOpen |= RTFILE_O_CREATE_REPLACE;
+ else
+ fOpen |= RTFILE_O_CREATE;
+ RTFILE hFile;
+ vrc = RTFileOpen(&hFile, pszFilename, fOpen);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTFileWrite(hFile, pszCmdLine, strlen(pszCmdLine), NULL);
+ if (RT_SUCCESS(vrc))
+ vrc = RTFileClose(hFile);
+ else
+ RTFileClose(hFile);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error writing '%s' (%Rrc)"), pszFilename, vrc);
+ }
+ else
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to create '%s' (%Rrc)"), pszFilename, vrc);
+
+ RTStrFree(pszCmdLine);
+ return hrc;
+}
+
+HRESULT UnattendedInstaller::loadAndParseFileFromIso(RTVFS hVfsOrgIso, const char *pszFilename, AbstractScript *pEditor)
+{
+ HRESULT hrc;
+ RTVFSFILE hVfsFile;
+ int vrc = RTVfsFileOpen(hVfsOrgIso, pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = pEditor->readFromHandle(hVfsFile, pszFilename);
+ RTVfsFileRelease(hVfsFile);
+ if (SUCCEEDED(hrc))
+ hrc = pEditor->parse();
+ }
+ else
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to open '%s' on the ISO '%s' (%Rrc)"),
+ pszFilename, mpParent->i_getIsoPath().c_str(), vrc);
+ return hrc;
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+*
+*
+* Implementation UnattendedLinuxInstaller functions
+*
+*/
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT UnattendedLinuxInstaller::editIsoLinuxCfg(GeneralTextScript *pEditor)
+{
+ try
+ {
+ /* Comment out 'display <filename>' directives that's used for displaying files at boot time. */
+ std::vector<size_t> vecLineNumbers = pEditor->findTemplate("display", RTCString::CaseInsensitive);
+ for (size_t i = 0; i < vecLineNumbers.size(); ++i)
+ if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("display", RTCString::CaseInsensitive))
+ {
+ HRESULT hrc = pEditor->prependToLine(vecLineNumbers.at(i), "#");
+ if (FAILED(hrc))
+ return hrc;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return editIsoLinuxCommon(pEditor);
+}
+
+HRESULT UnattendedLinuxInstaller::editIsoLinuxCommon(GeneralTextScript *pEditor)
+{
+ try
+ {
+ /* Set timeouts to 4 seconds. */
+ std::vector<size_t> vecLineNumbers = pEditor->findTemplate("timeout", RTCString::CaseInsensitive);
+ for (size_t i = 0; i < vecLineNumbers.size(); ++i)
+ if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("timeout", RTCString::CaseInsensitive))
+ {
+ HRESULT hrc = pEditor->setContentOfLine(vecLineNumbers.at(i), "timeout 4");
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /* Modify kernel parameters. */
+ vecLineNumbers = pEditor->findTemplate("append", RTCString::CaseInsensitive);
+ if (vecLineNumbers.size() > 0)
+ {
+ Utf8Str const &rStrAppend = mpParent->i_getExtraInstallKernelParameters().isNotEmpty()
+ ? mpParent->i_getExtraInstallKernelParameters()
+ : mStrDefaultExtraInstallKernelParameters;
+
+ for (size_t i = 0; i < vecLineNumbers.size(); ++i)
+ if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("append", RTCString::CaseInsensitive))
+ {
+ Utf8Str strLine = pEditor->getContentOfLine(vecLineNumbers[i]);
+
+ /* Do removals. */
+ if (mArrStrRemoveInstallKernelParameters.size() > 0)
+ {
+ size_t offStart = strLine.find("append") + 5;
+ while (offStart < strLine.length() && !RT_C_IS_SPACE(strLine[offStart]))
+ offStart++;
+ while (offStart < strLine.length() && RT_C_IS_SPACE(strLine[offStart]))
+ offStart++;
+ if (offStart < strLine.length())
+ {
+ for (size_t iRemove = 0; iRemove < mArrStrRemoveInstallKernelParameters.size(); iRemove++)
+ {
+ RTCString const &rStrRemove = mArrStrRemoveInstallKernelParameters[iRemove];
+ for (size_t off = offStart; off < strLine.length(); )
+ {
+ Assert(!RT_C_IS_SPACE(strLine[off]));
+
+ /* Find the end of word. */
+ size_t offEnd = off + 1;
+ while (offEnd < strLine.length() && !RT_C_IS_SPACE(strLine[offEnd]))
+ offEnd++;
+
+ /* Check if it matches. */
+ if (RTStrSimplePatternNMatch(rStrRemove.c_str(), rStrRemove.length(),
+ strLine.c_str() + off, offEnd - off))
+ {
+ while (off > 0 && RT_C_IS_SPACE(strLine[off - 1]))
+ off--;
+ strLine.erase(off, offEnd - off);
+ }
+
+ /* Advance to the next word. */
+ off = offEnd;
+ while (off < strLine.length() && RT_C_IS_SPACE(strLine[off]))
+ off++;
+ }
+ }
+ }
+ }
+
+ /* Do the appending. */
+ if (rStrAppend.isNotEmpty())
+ {
+ if (!rStrAppend.startsWith(" ") && !strLine.endsWith(" "))
+ strLine.append(' ');
+ strLine.append(rStrAppend);
+ }
+
+ /* Update line. */
+ HRESULT hrc = pEditor->setContentOfLine(vecLineNumbers.at(i), strLine);
+ if (FAILED(hrc))
+ return hrc;
+ }
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+*
+*
+* Implementation UnattendedDebianInstaller functions
+*
+*/
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Helper for checking if a file exists.
+ * @todo promote to IPRT?
+ */
+static bool hlpVfsFileExists(RTVFS hVfs, const char *pszPath)
+{
+ RTFSOBJINFO ObjInfo;
+ int vrc = RTVfsQueryPathInfo(hVfs, pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ return RT_SUCCESS(vrc) && RTFS_IS_FILE(ObjInfo.Attr.fMode);
+}
+
+HRESULT UnattendedDebianInstaller::addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
+ RTVFS hVfsOrgIso, bool fOverwrite)
+{
+ /*
+ * Figure out the name of the menu config file that we have to edit.
+ */
+ bool fMenuConfigIsGrub = false;
+ const char *pszMenuConfigFilename = "/isolinux/txt.cfg";
+ if (!hlpVfsFileExists(hVfsOrgIso, pszMenuConfigFilename))
+ {
+ /* On Debian Live ISOs (at least from 9 to 11) the there is only menu.cfg. */
+ if (hlpVfsFileExists(hVfsOrgIso, "/isolinux/menu.cfg"))
+ pszMenuConfigFilename = "/isolinux/menu.cfg";
+ /* On Linux Mint 20.3, 21, and 19 (at least) there is only isolinux.cfg. */
+ else if (hlpVfsFileExists(hVfsOrgIso, "/isolinux/isolinux.cfg"))
+ pszMenuConfigFilename = "/isolinux/isolinux.cfg";
+ /* Ubuntus 21.10+ are UEFI only. No isolinux directory. We modify grub.cfg. */
+ else if (hlpVfsFileExists(hVfsOrgIso, "/boot/grub/grub.cfg"))
+ {
+ pszMenuConfigFilename = "/boot/grub/grub.cfg";
+ fMenuConfigIsGrub = true;
+ }
+ }
+
+ /* Check for existence of isolinux.cfg since UEFI-only ISOs do not have this file. */
+ bool const fIsoLinuxCfgExists = hlpVfsFileExists(hVfsOrgIso, "isolinux/isolinux.cfg");
+ Assert(!fIsoLinuxCfgExists || !fMenuConfigIsGrub); /** @todo r=bird: Perhaps prefix the hlpVfsFileExists call with 'fIsoLinuxCfgExists &&' above ? */
+
+ /*
+ * VISO bits and filenames.
+ */
+ RTCString strIsoLinuxCfg;
+ RTCString strTxtCfg;
+ try
+ {
+ /* Remaster ISO. */
+ rVecArgs.append() = "--no-file-mode";
+ rVecArgs.append() = "--no-dir-mode";
+
+ rVecArgs.append() = "--import-iso";
+ rVecArgs.append(mpParent->i_getIsoPath());
+
+ rVecArgs.append() = "--file-mode=0444";
+ rVecArgs.append() = "--dir-mode=0555";
+
+ /* Replace the isolinux.cfg configuration file. */
+ if (fIsoLinuxCfgExists)
+ {
+ /* First remove. */
+ rVecArgs.append() = "isolinux/isolinux.cfg=:must-remove:";
+ /* Then add the modified file. */
+ strIsoLinuxCfg = mpParent->i_getAuxiliaryBasePath();
+ strIsoLinuxCfg.append("isolinux-isolinux.cfg");
+ rVecArgs.append().append("isolinux/isolinux.cfg=").append(strIsoLinuxCfg);
+ }
+
+ /*
+ * Replace menu configuration file as well.
+ * Some distros (Linux Mint) has only isolinux.cfg. No menu.cfg or txt.cfg.
+ */
+ if (RTStrICmp(pszMenuConfigFilename, "/isolinux/isolinux.cfg") != 0)
+ {
+
+ /* Replace menu configuration file as well. */
+ rVecArgs.append().assign(pszMenuConfigFilename).append("=:must-remove:");
+ strTxtCfg = mpParent->i_getAuxiliaryBasePath();
+ if (fMenuConfigIsGrub)
+ strTxtCfg.append("grub.cfg");
+ else
+ strTxtCfg.append("isolinux-txt.cfg");
+ rVecArgs.append().assign(pszMenuConfigFilename).append("=").append(strTxtCfg);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Edit the isolinux.cfg file if it is there.
+ */
+ if (fIsoLinuxCfgExists)
+ {
+ GeneralTextScript Editor(mpParent);
+ HRESULT hrc = loadAndParseFileFromIso(hVfsOrgIso, "/isolinux/isolinux.cfg", &Editor);
+ if (SUCCEEDED(hrc))
+ hrc = editIsoLinuxCfg(&Editor, RTPathFilename(pszMenuConfigFilename));
+ if (SUCCEEDED(hrc))
+ {
+ hrc = Editor.save(strIsoLinuxCfg, fOverwrite);
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ rVecFiles.append(strIsoLinuxCfg);
+ }
+ catch (std::bad_alloc &)
+ {
+ RTFileDelete(strIsoLinuxCfg.c_str());
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ }
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /*
+ * Edit the menu config file.
+ * Some distros (Linux Mint) has only isolinux.cfg. No menu.cfg or txt.cfg.
+ */
+ if (RTStrICmp(pszMenuConfigFilename, "/isolinux/isolinux.cfg") != 0)
+ {
+ GeneralTextScript Editor(mpParent);
+ HRESULT hrc = loadAndParseFileFromIso(hVfsOrgIso, pszMenuConfigFilename, &Editor);
+ if (SUCCEEDED(hrc))
+ {
+ if (fMenuConfigIsGrub)
+ hrc = editDebianGrubCfg(&Editor);
+ else
+ hrc = editDebianMenuCfg(&Editor);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = Editor.save(strTxtCfg, fOverwrite);
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ rVecFiles.append(strTxtCfg);
+ }
+ catch (std::bad_alloc &)
+ {
+ RTFileDelete(strTxtCfg.c_str());
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ }
+ }
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /*
+ * Call parent to add the preseed file from mAlg.
+ */
+ return UnattendedLinuxInstaller::addFilesToAuxVisoVectors(rVecArgs, rVecFiles, hVfsOrgIso, fOverwrite);
+}
+
+HRESULT UnattendedDebianInstaller::editIsoLinuxCfg(GeneralTextScript *pEditor, const char *pszMenuConfigFileName)
+{
+ try
+ {
+ /* Include menu config file. Since it can be txt.cfg, menu.cfg or something else we need to parametrize this. */
+ if (pszMenuConfigFileName && pszMenuConfigFileName[0] != '\0')
+ {
+ std::vector<size_t> vecLineNumbers = pEditor->findTemplate("include", RTCString::CaseInsensitive);
+ for (size_t i = 0; i < vecLineNumbers.size(); ++i)
+ {
+ if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("include", RTCString::CaseInsensitive))
+ {
+ Utf8Str strIncludeLine("include ");
+ strIncludeLine.append(pszMenuConfigFileName);
+ HRESULT hrc = pEditor->setContentOfLine(vecLineNumbers.at(i), strIncludeLine);
+ if (FAILED(hrc))
+ return hrc;
+ }
+ }
+ }
+
+ /* Comment out default directives since in Debian case default is handled in menu config file. */
+ std::vector<size_t> vecLineNumbers = pEditor->findTemplate("default", RTCString::CaseInsensitive);
+ for (size_t i = 0; i < vecLineNumbers.size(); ++i)
+ if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("default", RTCString::CaseInsensitive)
+ && !pEditor->getContentOfLine(vecLineNumbers[i]).contains("default vesa", RTCString::CaseInsensitive))
+ {
+ HRESULT hrc = pEditor->prependToLine(vecLineNumbers.at(i), "#");
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /* Comment out "ui gfxboot bootlogo" line as it somehow messes things up on Kubuntu 20.04 (possibly others as well). */
+ vecLineNumbers = pEditor->findTemplate("ui gfxboot", RTCString::CaseInsensitive);
+ for (size_t i = 0; i < vecLineNumbers.size(); ++i)
+ if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("ui gfxboot", RTCString::CaseInsensitive))
+ {
+ HRESULT hrc = pEditor->prependToLine(vecLineNumbers.at(i), "#");
+ if (FAILED(hrc))
+ return hrc;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return UnattendedLinuxInstaller::editIsoLinuxCfg(pEditor);
+}
+
+HRESULT UnattendedDebianInstaller::editDebianMenuCfg(GeneralTextScript *pEditor)
+{
+ /*
+ * Unlike Redhats, Debian variants define boot menu not in isolinux.cfg but some other
+ * menu configuration files. They are mostly called txt.cfg and/or menu.cfg (and possibly some other names)
+ * In this functions we attempt to set menu's default label (default menu item) to the one containing the word 'install',
+ * failing to find such a label (on Kubuntu 20.04 for example) we pick the first label with name 'live'.
+ */
+ try
+ {
+ HRESULT hrc = S_OK;
+ std::vector<size_t> vecLineNumbers = pEditor->findTemplate("label", RTCString::CaseInsensitive);
+ const char *pszNewLabelName = "VBoxUnatendedInstall";
+ bool fLabelFound = modifyLabelLine(pEditor, vecLineNumbers, "install", pszNewLabelName);
+ if (!fLabelFound)
+ fLabelFound = modifyLabelLine(pEditor, vecLineNumbers, "live", pszNewLabelName);
+
+ if (!fLabelFound)
+ hrc = E_FAIL;;
+
+ if (SUCCEEDED(hrc))
+ {
+ /* Modify the content of default lines so that they point to label we have chosen above. */
+ Utf8Str strNewContent("default ");
+ strNewContent.append(pszNewLabelName);
+
+ std::vector<size_t> vecDefaultLineNumbers = pEditor->findTemplate("default", RTCString::CaseInsensitive);
+ if (!vecDefaultLineNumbers.empty())
+ {
+ for (size_t j = 0; j < vecDefaultLineNumbers.size(); ++j)
+ {
+ hrc = pEditor->setContentOfLine(vecDefaultLineNumbers[j], strNewContent);
+ if (FAILED(hrc))
+ break;
+ }
+ }
+ /* Add a defaul label line. */
+ else
+ hrc = pEditor->appendLine(strNewContent);
+ }
+ if (FAILED(hrc))
+ return hrc;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return UnattendedLinuxInstaller::editIsoLinuxCommon(pEditor);
+}
+
+bool UnattendedDebianInstaller::modifyLabelLine(GeneralTextScript *pEditor, const std::vector<size_t> &vecLineNumbers,
+ const char *pszKeyWord, const char *pszNewLabelName)
+{
+ if (!pEditor)
+ return false;
+ Utf8Str strNewLabel("label ");
+ strNewLabel.append(pszNewLabelName);
+ HRESULT hrc = S_OK;
+ for (size_t i = 0; i < vecLineNumbers.size(); ++i)
+ {
+ RTCString const &rContent = pEditor->getContentOfLine(vecLineNumbers[i]);
+ /* Skip this line if it does not start with the word 'label'. */
+ if (!RTStrIStartsWith(rContent.c_str(), "label"))
+ continue;
+ /* Use the first menu item starting with word label and includes pszKeyWord.*/
+ if (RTStrIStr(rContent.c_str(), pszKeyWord) != NULL)
+ {
+ /* Set the content of the line. It looks like multiple word labels (like label Debian Installer)
+ * does not work very well in some cases. */
+ hrc = pEditor->setContentOfLine(vecLineNumbers[i], strNewLabel);
+ if (SUCCEEDED(hrc))
+ return true;
+ }
+ }
+ return false;
+}
+
+HRESULT UnattendedDebianInstaller::editDebianGrubCfg(GeneralTextScript *pEditor)
+{
+ /* Default menu entry of grub.cfg is set in /etc/deafult/grub file. */
+ try
+ {
+ /* Set timeouts to 4 seconds. */
+ std::vector<size_t> vecLineNumbers = pEditor->findTemplate("set timeout", RTCString::CaseInsensitive);
+ for (size_t i = 0; i < vecLineNumbers.size(); ++i)
+ if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("set timeout", RTCString::CaseInsensitive))
+ {
+ HRESULT hrc = pEditor->setContentOfLine(vecLineNumbers.at(i), "set timeout=4");
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /* Modify kernel lines assuming that they starts with 'linux' keyword and 2nd word is the kernel command.*
+ * we remove whatever comes after command and add our own command line options. */
+ vecLineNumbers = pEditor->findTemplate("linux", RTCString::CaseInsensitive);
+ if (vecLineNumbers.size() > 0)
+ {
+ Utf8Str const &rStrAppend = mpParent->i_getExtraInstallKernelParameters().isNotEmpty()
+ ? mpParent->i_getExtraInstallKernelParameters()
+ : mStrDefaultExtraInstallKernelParameters;
+
+ for (size_t i = 0; i < vecLineNumbers.size(); ++i)
+ {
+ HRESULT hrc = S_OK;
+ if (pEditor->getContentOfLine(vecLineNumbers[i]).startsWithWord("linux", RTCString::CaseInsensitive))
+ {
+ Utf8Str strLine = pEditor->getContentOfLine(vecLineNumbers[i]);
+ size_t cbPos = strLine.find("linux") + strlen("linux");
+ bool fSecondWord = false;
+ /* Find the end of 2nd word assuming that it is kernel command. */
+ while (cbPos < strLine.length())
+ {
+ if (!fSecondWord)
+ {
+ if (strLine[cbPos] != '\t' && strLine[cbPos] != ' ')
+ fSecondWord = true;
+ }
+ else
+ {
+ if (strLine[cbPos] == '\t' || strLine[cbPos] == ' ')
+ break;
+ }
+ ++cbPos;
+ }
+ if (!fSecondWord)
+ hrc = E_FAIL;
+
+ if (SUCCEEDED(hrc))
+ {
+ strLine.erase(cbPos, strLine.length() - cbPos);
+
+ /* Do the appending. */
+ if (rStrAppend.isNotEmpty())
+ {
+ if (!rStrAppend.startsWith(" ") && !strLine.endsWith(" "))
+ strLine.append(' ');
+ strLine.append(rStrAppend);
+ }
+
+ /* Update line. */
+ hrc = pEditor->setContentOfLine(vecLineNumbers.at(i), strLine);
+ }
+ if (FAILED(hrc))
+ return hrc;
+ }
+ }
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+*
+*
+* Implementation UnattendedRhel6Installer functions
+*
+*/
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT UnattendedRhel6Installer::addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
+ RTVFS hVfsOrgIso, bool fOverwrite)
+{
+ Utf8Str strIsoLinuxCfg;
+ try
+ {
+#if 1
+ /* Remaster ISO. */
+ rVecArgs.append() = "--no-file-mode";
+ rVecArgs.append() = "--no-dir-mode";
+
+ rVecArgs.append() = "--import-iso";
+ rVecArgs.append(mpParent->i_getIsoPath());
+
+ rVecArgs.append() = "--file-mode=0444";
+ rVecArgs.append() = "--dir-mode=0555";
+
+ /* We replace isolinux.cfg with our edited version (see further down). */
+ rVecArgs.append() = "isolinux/isolinux.cfg=:must-remove:";
+ strIsoLinuxCfg = mpParent->i_getAuxiliaryBasePath();
+ strIsoLinuxCfg.append("isolinux-isolinux.cfg");
+ rVecArgs.append().append("isolinux/isolinux.cfg=").append(strIsoLinuxCfg);
+
+#else
+ /** @todo Maybe we should just remaster the ISO for redhat derivatives too?
+ * One less CDROM to mount. */
+ /* Name the ISO. */
+ rVecArgs.append() = "--volume-id=VBox Unattended Boot";
+
+ /* Copy the isolinux directory from the original install ISO. */
+ rVecArgs.append().append("--push-iso=").append(mpParent->i_getIsoPath());
+ rVecArgs.append() = "/isolinux=/isolinux";
+ rVecArgs.append() = "--pop";
+
+ /* We replace isolinux.cfg with our edited version (see further down). */
+ rVecArgs.append() = "/isolinux/isolinux.cfg=:must-remove:";
+
+ strIsoLinuxCfg = mpParent->i_getAuxiliaryBasePath();
+ strIsoLinuxCfg.append("isolinux-isolinux.cfg");
+ rVecArgs.append().append("/isolinux/isolinux.cfg=").append(strIsoLinuxCfg);
+
+ /* Configure booting /isolinux/isolinux.bin. */
+ rVecArgs.append() = "--eltorito-boot";
+ rVecArgs.append() = "/isolinux/isolinux.bin";
+ rVecArgs.append() = "--no-emulation-boot";
+ rVecArgs.append() = "--boot-info-table";
+ rVecArgs.append() = "--boot-load-seg=0x07c0";
+ rVecArgs.append() = "--boot-load-size=4";
+
+ /* Make the boot catalog visible in the file system. */
+ rVecArgs.append() = "--boot-catalog=/isolinux/vboxboot.cat";
+#endif
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Edit isolinux.cfg and save it.
+ */
+ {
+ GeneralTextScript Editor(mpParent);
+ HRESULT hrc = loadAndParseFileFromIso(hVfsOrgIso, "/isolinux/isolinux.cfg", &Editor);
+ if (SUCCEEDED(hrc))
+ hrc = editIsoLinuxCfg(&Editor);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = Editor.save(strIsoLinuxCfg, fOverwrite);
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ rVecFiles.append(strIsoLinuxCfg);
+ }
+ catch (std::bad_alloc &)
+ {
+ RTFileDelete(strIsoLinuxCfg.c_str());
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ }
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /*
+ * Call parent to add the ks.cfg file from mAlg.
+ */
+ return UnattendedLinuxInstaller::addFilesToAuxVisoVectors(rVecArgs, rVecFiles, hVfsOrgIso, fOverwrite);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+*
+*
+* Implementation UnattendedSuseInstaller functions
+*
+*/
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+#if 0 /* doesn't work, so convert later */
+/*
+ *
+ * UnattendedSuseInstaller protected methods
+ *
+*/
+HRESULT UnattendedSuseInstaller::setUserData()
+{
+ HRESULT rc = S_OK;
+ //here base class function must be called first
+ //because user home directory is set after user name
+ rc = UnattendedInstaller::setUserData();
+
+ rc = mAlg->setField(USERHOMEDIR_ID, "");
+ if (FAILED(rc))
+ return rc;
+
+ return rc;
+}
+
+/*
+ *
+ * UnattendedSuseInstaller private methods
+ *
+*/
+
+HRESULT UnattendedSuseInstaller::iv_initialPhase()
+{
+ Assert(isAuxiliaryIsoNeeded());
+ if (mParent->i_isGuestOs64Bit())
+ mFilesAndDirsToExtractFromIso.append("boot/x86_64/loader/ ");
+ else
+ mFilesAndDirsToExtractFromIso.append("boot/i386/loader/ ");
+ return extractOriginalIso(mFilesAndDirsToExtractFromIso);
+}
+
+
+HRESULT UnattendedSuseInstaller::setupScriptOnAuxiliaryCD(const Utf8Str &path)
+{
+ HRESULT rc = S_OK;
+
+ GeneralTextScript isoSuseCfgScript(mpParent);
+ rc = isoSuseCfgScript.read(path);
+ rc = isoSuseCfgScript.parse();
+ //fix linux core bootable parameters: add path to the preseed script
+
+ std::vector<size_t> listOfLines = isoSuseCfgScript.findTemplate("append");
+ for(unsigned int i=0; i<listOfLines.size(); ++i)
+ {
+ isoSuseCfgScript.appendToLine(listOfLines.at(i),
+ " auto=true priority=critical autoyast=default instmode=cd quiet splash noprompt noshell --");
+ }
+
+ //find all lines with "label" inside
+ listOfLines = isoSuseCfgScript.findTemplate("label");
+ for(unsigned int i=0; i<listOfLines.size(); ++i)
+ {
+ Utf8Str content = isoSuseCfgScript.getContentOfLine(listOfLines.at(i));
+
+ //suppose general string looks like "label linux", two words separated by " ".
+ RTCList<RTCString> partsOfcontent = content.split(" ");
+
+ if (partsOfcontent.at(1).contains("linux"))
+ {
+ std::vector<size_t> listOfDefault = isoSuseCfgScript.findTemplate("default");
+ //handle the lines more intelligently
+ for(unsigned int j=0; j<listOfDefault.size(); ++j)
+ {
+ Utf8Str newContent("default ");
+ newContent.append(partsOfcontent.at(1));
+ isoSuseCfgScript.setContentOfLine(listOfDefault.at(j), newContent);
+ }
+ }
+ }
+
+ rc = isoSuseCfgScript.save(path, true);
+
+ LogRelFunc(("UnattendedSuseInstaller::setupScriptsOnAuxiliaryCD(): The file %s has been changed\n", path.c_str()));
+
+ return rc;
+}
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+*
+*
+* Implementation UnattendedFreeBsdInstaller functions
+*
+*/
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT UnattendedFreeBsdInstaller::addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
+ RTVFS hVfsOrgIso, bool fOverwrite)
+{
+ try
+ {
+ RTCString strScriptName;
+ strScriptName = mpParent->i_getAuxiliaryBasePath();
+ strScriptName.append(mMainScript.getDefaultFilename());
+
+ /* Need to retain the original file permissions for executables. */
+ rVecArgs.append() = "--no-file-mode";
+ rVecArgs.append() = "--no-dir-mode";
+
+ rVecArgs.append() = "--import-iso";
+ rVecArgs.append(mpParent->i_getIsoPath());
+
+ rVecArgs.append() = "--file-mode=0444";
+ rVecArgs.append() = "--dir-mode=0555";
+
+ /* Remaster ISO, the installer config has to go into /etc. */
+ rVecArgs.append().append("/etc/installerconfig=").append(strScriptName);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Call parent to add the remaining files
+ */
+ return UnattendedInstaller::addFilesToAuxVisoVectors(rVecArgs, rVecFiles, hVfsOrgIso, fOverwrite);
+}
diff --git a/src/VBox/Main/src-server/UnattendedOs2Installer.cpp b/src/VBox/Main/src-server/UnattendedOs2Installer.cpp
new file mode 100644
index 00000000..961a0c4c
--- /dev/null
+++ b/src/VBox/Main/src-server/UnattendedOs2Installer.cpp
@@ -0,0 +1,1228 @@
+/* $Id: UnattendedOs2Installer.cpp $ */
+/** @file
+ * UnattendedOs2Installer implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
+#include "LoggingNew.h"
+#include "VirtualBoxBase.h"
+#include "VirtualBoxErrorInfoImpl.h"
+#include "AutoCaller.h"
+#include <VBox/com/ErrorInfo.h>
+
+#include "UnattendedImpl.h"
+#include "UnattendedInstaller.h"
+#include "UnattendedScript.h"
+
+#include <VBox/err.h>
+#include <iprt/ctype.h>
+#include <iprt/fsisomaker.h>
+#include <iprt/fsvfs.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/vfs.h>
+#ifdef RT_OS_SOLARIS
+# undef ES /* Workaround for someone dragging the namespace pollutor sys/regset.h. Sigh. */
+#endif
+#include <iprt/formats/fat.h>
+#include <iprt/cpp/path.h>
+
+
+using namespace std;
+
+
+
+UnattendedOs2Installer::UnattendedOs2Installer(Unattended *pParent, Utf8Str const &rStrHints)
+ : UnattendedInstaller(pParent,
+ "os2_response_files.rsp", "os2_cid_install.cmd",
+ "os2_response_files.rsp", "VBOXCID.CMD",
+ DeviceType_Floppy)
+{
+ Assert(!isOriginalIsoNeeded());
+ Assert(isAuxiliaryFloppyNeeded());
+ Assert(isAuxiliaryIsoIsVISO());
+ Assert(bootFromAuxiliaryIso());
+ mStrAuxiliaryInstallDir = "S:\\";
+
+ /* Extract the info from the hints variable: */
+ RTCList<RTCString, RTCString *> Pairs = rStrHints.split(" ");
+ size_t i = Pairs.size();
+ Assert(i > 0);
+ while (i -- > 0)
+ {
+ RTCString const rStr = Pairs[i];
+ if (rStr.startsWith("OS2SE20.SRC="))
+ mStrOs2Images = rStr.substr(sizeof("OS2SE20.SRC=") - 1);
+ else
+ AssertMsgFailed(("Unknown hint: %s\n", rStr.c_str()));
+ }
+}
+
+
+HRESULT UnattendedOs2Installer::replaceAuxFloppyImageBootSector(RTVFSFILE hVfsFile) RT_NOEXCEPT
+{
+ /*
+ * Find the bootsector. Because the ArcaOS ISOs doesn't contain any floppy
+ * images, we cannot just lift it off one of those. Instead we'll locate it
+ * in the SYSINSTX.COM utility, i.e. the tool which installs it onto floppies
+ * and harddisks. The SYSINSTX.COM utility is a NE executable and we don't
+ * have issues with compressed pages like with LX images.
+ *
+ * The utility seems always to be located on disk 0.
+ */
+ RTVFS hVfsOrgIso;
+ HRESULT hrc = openInstallIsoImage(&hVfsOrgIso);
+ if (SUCCEEDED(hrc))
+ {
+ char szPath[256];
+ int vrc = RTPathJoin(szPath, sizeof(szPath), mStrOs2Images.c_str(), "DISK_0/SYSINSTX.COM");
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSFILE hVfsSysInstX;
+ vrc = RTVfsFileOpen(hVfsOrgIso, szPath, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, &hVfsSysInstX);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Scan the image looking for a 512 block ending with a DOS signature
+ * and starting with a three byte jump followed by an OEM name string.
+ */
+ uint8_t *pbBootSector = NULL;
+ RTFOFF off = 0;
+ bool fEof = false;
+ uint8_t abBuf[_8K] = {0};
+ do
+ {
+ /* Read the next chunk. */
+ memmove(abBuf, &abBuf[sizeof(abBuf) - 512], 512); /* Move up the last 512 (all zero the first time around). */
+ size_t cbRead = 0;
+ vrc = RTVfsFileReadAt(hVfsSysInstX, off, &abBuf[512], sizeof(abBuf) - 512, &cbRead);
+ if (RT_FAILURE(vrc))
+ break;
+ fEof = cbRead != sizeof(abBuf) - 512;
+ off += cbRead;
+
+ /* Scan it. */
+ size_t cbLeft = sizeof(abBuf);
+ uint8_t *pbCur = abBuf;
+ while (cbLeft >= 512)
+ {
+ /* Look for the DOS signature (0x55 0xaa) at the end of the sector: */
+ uint8_t *pbHit = (uint8_t *)memchr(pbCur + 510, 0x55, cbLeft - 510 - 1);
+ if (!pbHit)
+ break;
+ if (pbHit[1] == 0xaa)
+ {
+ uint8_t *pbStart = pbHit - 510;
+ if ( pbStart[0] == 0xeb /* JMP imm8 */
+ && pbStart[1] >= 3 + 8 + sizeof(FATEBPB) - 2 /* must jump after the FATEBPB */
+ && RT_C_IS_ALNUM(pbStart[3]) /* ASSUME OEM string starts with two letters (typically 'IBM x.y')*/
+ && RT_C_IS_ALNUM(pbStart[4]))
+ {
+ FATEBPB *pBpb = (FATEBPB *)&pbStart[3 + 8];
+ if ( pBpb->bExtSignature == FATEBPB_SIGNATURE
+ && ( memcmp(pBpb->achType, "FAT ", sizeof(pBpb->achType)) == 0
+ || memcmp(pBpb->achType, FATEBPB_TYPE_FAT12, sizeof(pBpb->achType)) == 0))
+ {
+ pbBootSector = pbStart;
+ break;
+ }
+ }
+ }
+
+ /* skip */
+ pbCur = pbHit - 510 + 1;
+ cbLeft = (uintptr_t)&abBuf[sizeof(abBuf)] - (uintptr_t)pbCur;
+ }
+ } while (!fEof);
+
+ if (pbBootSector)
+ {
+ if (pbBootSector != abBuf)
+ pbBootSector = (uint8_t *)memmove(abBuf, pbBootSector, 512);
+
+ /*
+ * We've now got a bootsector. So, we need to copy the EBPB
+ * from the destination image before replacing it.
+ */
+ vrc = RTVfsFileReadAt(hVfsFile, 0, &abBuf[512], 512, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ memcpy(&pbBootSector[3 + 8], &abBuf[512 + 3 + 8], sizeof(FATEBPB));
+
+ /*
+ * Write it.
+ */
+ vrc = RTVfsFileWriteAt(hVfsFile, 0, pbBootSector, 512, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowFunc(("Successfully installed new bootsector\n"));
+ hrc = S_OK;
+ }
+ else
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to write bootsector: %Rrc"), vrc);
+ }
+ else
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to read bootsector: %Rrc"), vrc);
+ }
+ else if (RT_FAILURE(vrc))
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error reading SYSINSTX.COM: %Rrc"), vrc);
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, VERR_NOT_FOUND,
+ tr("Unable to locate bootsector template in SYSINSTX.COM"));
+ RTVfsFileRelease(hVfsSysInstX);
+ }
+ else
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to open SYSINSTX.COM: %Rrc"), vrc);
+ }
+ else
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to construct SYSINSTX.COM path"));
+ RTVfsRelease(hVfsOrgIso);
+ }
+ return hrc;
+
+}
+
+HRESULT UnattendedOs2Installer::newAuxFloppyImage(const char *pszFilename, bool fOverwrite, PRTVFSFILE phVfsFile)
+{
+ /*
+ * Open the image file.
+ */
+ HRESULT hrc;
+ RTVFSFILE hVfsFile;
+ uint64_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_ALL | (0660 << RTFILE_O_CREATE_MODE_SHIFT);
+ if (fOverwrite)
+ fOpen |= RTFILE_O_CREATE_REPLACE;
+ else
+ fOpen |= RTFILE_O_OPEN;
+ int vrc = RTVfsFileOpenNormal(pszFilename, fOpen, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Format it.
+ */
+ vrc = RTFsFatVolFormat288(hVfsFile, false /*fQuick*/);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Now we install the OS/2 boot sector on it.
+ */
+ hrc = replaceAuxFloppyImageBootSector(hVfsFile);
+ if (SUCCEEDED(hrc))
+ {
+ *phVfsFile = hVfsFile;
+ LogRelFlow(("UnattendedInstaller::newAuxFloppyImage: created and formatted '%s'\n", pszFilename));
+ return S_OK;
+ }
+ }
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, vrc, tr("Failed to format floppy image '%s': %Rrc"), pszFilename, vrc);
+ RTVfsFileRelease(hVfsFile);
+ RTFileDelete(pszFilename);
+ }
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, vrc, tr("Failed to create floppy image '%s': %Rrc"), pszFilename, vrc);
+ return hrc;
+}
+
+
+HRESULT UnattendedOs2Installer::splitResponseFile() RT_NOEXCEPT
+{
+ if (mVecSplitFiles.size() == 0)
+ {
+#if 0
+ Utf8Str strResponseFile;
+ int vrc = strResponseFile.assignNoThrow(mpParent->i_getAuxiliaryBasePath());
+ if (RT_SUCCESS(vrc))
+ vrc = strResponseFile.appendNoThrow(mMainScript.getDefaultFilename());
+ if (RT_SUCCESS(vrc))
+ return splitFile(strResponseFile.c_str(), mVecSplitFiles);
+ return mpParent->setErrorVrc(vrc);
+#else
+ return splitFile(&mMainScript, mVecSplitFiles);
+#endif
+ }
+ return S_OK;
+}
+
+/**
+ * OS/2 code pattern.
+ */
+typedef struct OS2CODEPATTERN
+{
+ /** The code pattern. */
+ uint8_t const *pbPattern;
+ /** The mask to apply when using the pattern (ignore 0x00 bytes, compare 0xff
+ * bytes). */
+ uint8_t const *pbMask;
+ /** The pattern size. */
+ size_t cb;
+ /** User info \#1. */
+ uintptr_t uUser1;
+ /** User info \#2. */
+ uint32_t uUser2;
+ /** User info \#3. */
+ uint32_t uUser3;
+ /** User info \#4. */
+ uint32_t uUser4;
+ /** User info \#5. */
+ uint32_t uUser5;
+} OS2CODEPATTERN;
+/** Pointer to an OS/2 code pattern. */
+typedef OS2CODEPATTERN const *PCOS2CODEPATTERN;
+
+
+/**
+ * Search @a pbCode for the code patterns in @a paPatterns.
+ *
+ * @returns pointer within @a pbCode to matching code, NULL if no match.
+ */
+static uint8_t *findCodePattern(PCOS2CODEPATTERN paPatterns, size_t cPatterns, uint8_t *pbCode, size_t cbCode,
+ PCOS2CODEPATTERN *ppMatch)
+{
+ for (size_t i = 0; i < cPatterns; i++)
+ {
+ size_t const cbPattern = paPatterns[i].cb;
+ uint8_t const *pbPattern = paPatterns[i].pbPattern;
+ uint8_t const *pbMask = paPatterns[i].pbMask;
+ Assert(pbMask[0] == 0xff); /* ASSUME the we can use the first byte with memchr. */
+ uint8_t const bFirst = *pbPattern;
+ size_t off = 0;
+ while (off + cbPattern <= cbCode)
+ {
+ uint8_t *pbHit = (uint8_t *)memchr(&pbCode[off], bFirst, cbCode - off - cbPattern + 1);
+ if (!pbHit)
+ break;
+
+ size_t offPattern = 1;
+ while ( offPattern < cbPattern
+ && (pbPattern[offPattern] & pbMask[offPattern]) == (pbHit[offPattern] & pbMask[offPattern]))
+ offPattern++;
+ if (offPattern == cbPattern)
+ {
+ *ppMatch = &paPatterns[i];
+ return pbHit;
+ }
+
+ /* next */
+ off++;
+ }
+ }
+ return NULL;
+}
+
+#if 0
+/**
+ * Patcher callback for TESTCFG.SYS.
+ *
+ * This is for debugging a mysterious DS corruption issue happening on an AMD
+ * 3990x host.
+ *
+ * @verbatim
+dbgf event/0: xcpt_gp - #GP (general protection fault)! arg=0x1d8
+VBoxDbg> r
+eax=00000001 ebx=00dc0000 ecx=56d80000 edx=178b0000 esi=ffde0100 edi=feff44e4
+eip=00000124 esp=00000f76 ebp=0000dbf3 iopl=3 nv up ei pl nz na po nc
+cs=0763 ds=01db es=0130 fs=0000 gs=0000 ss=001f eflags=00003206
+0763:00000124 cb retf
+VBoxDbg> dw ss:sp
+001f:00000f76: 0549 075b 03e4 0000-0fb8 04b9 44e4 0130
+VBoxDbg> u cs:fc
+0763:000000fc 55 push bp
+0763:000000fd 8b ec mov bp, sp
+0763:000000ff 53 push bx
+0763:00000100 51 push cx
+0763:00000101 52 push dx
+0763:00000102 1e push DS
+0763:00000103 33 c9 xor cx, cx
+0763:00000105 b0 10 mov AL, 010h
+0763:00000107 b2 24 mov DL, 024h
+0763:00000109 ff 1e 22 00 call far [00022h]
+0763:0000010d 72 0e jc +00eh (0011dh)
+0763:0000010f 50 push ax
+0763:00000110 1f pop DS
+0763:00000111 f7 47 06 03 00 test word [bx+006h], 00003h
+0763:00000116 74 05 je +005h (0011dh)
+0763:00000118 b8 01 00 mov ax, 00001h
+0763:0000011b eb 02 jmp +002h (0011fh)
+0763:0000011d 33 c0 xor ax, ax
+0763:0000011f 1f pop DS
+0763:00000120 5a pop dx
+0763:00000121 59 pop cx
+0763:00000122 5b pop bx
+0763:00000123 5d pop bp
+0763:00000124 cb retf
+VBoxDbg> dw ss:sp - 5*2 L8
+001f:00000f6c: 0750 082a 220e 44e4-0f7e 0549 075b 03e4
+ * @endverbatim
+ *
+ * We end up with a \#GP on the RETF, but the stack frame is a valid 075b:0549
+ * return address (in TESTCFG's first code segment). The error code is 0x1d8,
+ * which makes no sense. DS contains 0x1db, which could be related, however it
+ * is the *wrong* value as seen by the stack restore frame above, it was just
+ * restored as 0750 (TESTCFG data segment).
+ *
+ * The patching here aim at modifying to code to try figure out what might
+ * trigger the bogus DS and \#GP(0x1d8).
+ *
+ * P.S. There are no exits or event injections taking place when DS gets
+ * corrupt, the last exit was a CR0 read in OS2KRNL's DOSSEG (0120:1798)
+ * probably related to we comming back to protected mode from real mode as we
+ * just made an APM BIOS call.
+ *
+ * Update: The values loaded off the stack aren't the ones ending up the
+ * registers, so that might explain why this goes south.
+ *
+ * @sa ticketref:20625
+ */
+/*static*/
+int UnattendedOs2Installer::patchTestCfg(uint8_t *pbFile, size_t cbFile, const char *pszFilename, UnattendedOs2Installer *pThis)
+{
+ RT_NOREF(pThis, pszFilename);
+
+ static uint8_t const s_abVariant1[] =
+ {
+ /*0763:00fc*/ 0x55, /* push bp */
+ /*0763:00fd*/ 0x8b, 0xec, /* mov bp, sp */
+ /*0763:00ff*/ 0x53, /* push bx */
+ /*0763:0100*/ 0x51, /* push cx */
+ /*0763:0101*/ 0x52, /* push dx */
+ /*0763:0102*/ 0x1e, /* push DS */
+ /*0763:0103*/ 0x33, 0xc9, /* xor cx, cx */
+ /*0763:0105*/ 0xb0, 0x10, /* mov AL, 010h */
+ /*0763:0107*/ 0xb2, 0x24, /* mov DL, 024h */
+ /*0763:0109*/ 0xff, 0x1e, 0x22, 0x00, /* call far [00022h] */
+ /*0763:010d*/ 0x72, 0x0e, /* jc +00eh (0011dh) */
+ /*0763:010f*/ 0x50, /* push ax */
+ /*0763:0110*/ 0x1f, /* pop DS */
+ /*0763:0111*/ 0xf7, 0x47, 0x06, 0x03, 0x00, /* test word [bx+006h], 00003h */
+ /*0763:0116*/ 0x74, 0x05, /* je +005h (0011dh) */
+ /*0763:0118*/ 0xb8, 0x01, 0x00, /* mov ax, 00001h */
+ /*0763:011b*/ 0xeb, 0x02, /* jmp +002h (0011fh) */
+ /*0763:011d*/ 0x33, 0xc0, /* xor ax, ax */
+ /*0763:011f*/ 0x1f, /* pop DS */
+ /*0763:0120*/ 0x5a, /* pop dx */
+ /*0763:0121*/ 0x59, /* pop cx */
+ /*0763:0122*/ 0x5b, /* pop bx */
+ /*0763:0123*/ 0x5d, /* pop bp */
+ /*0763:0124*/ 0xcb, /* retf */
+ };
+ static uint8_t const s_abVariant1Mask[] =
+ {
+ /*0763:00fc*/ 0xff, /* push bp */
+ /*0763:00fd*/ 0xff, 0xec, /* mov bp, sp */
+ /*0763:00ff*/ 0xff, /* push bx */
+ /*0763:0100*/ 0xff, /* push cx */
+ /*0763:0101*/ 0xff, /* push dx */
+ /*0763:0102*/ 0xff, /* push DS */
+ /*0763:0103*/ 0xff, 0xff, /* xor cx, cx */
+ /*0763:0105*/ 0xff, 0xff, /* mov AL, 010h */
+ /*0763:0107*/ 0xff, 0xff, /* mov DL, 024h */
+ /*0763:0109*/ 0xff, 0xff, 0x00, 0x00, /* call far [00022h] */
+ /*0763:010d*/ 0xff, 0xff, /* jc +00eh (0011dh) */
+ /*0763:010f*/ 0xff, /* push ax */
+ /*0763:0110*/ 0xff, /* pop DS */
+ /*0763:0111*/ 0xff, 0xff, 0xff, 0xff, 0xff, /* test word [bx+006h], 00003h */
+ /*0763:0116*/ 0xff, 0xff, /* je +005h (0011dh) */
+ /*0763:0118*/ 0xff, 0xff, 0xff, /* mov ax, 00001h */
+ /*0763:011b*/ 0xff, 0xff, /* jmp +002h (0011fh) */
+ /*0763:011d*/ 0xff, 0xff, /* xor ax, ax */
+ /*0763:011f*/ 0xff, /* pop DS */
+ /*0763:0120*/ 0xff, /* pop dx */
+ /*0763:0121*/ 0xff, /* pop cx */
+ /*0763:0122*/ 0xff, /* pop bx */
+ /*0763:0123*/ 0xff, /* pop bp */
+ /*0763:0124*/ 0xff, /* retf */
+ };
+ AssertCompile(sizeof(s_abVariant1Mask) == sizeof(s_abVariant1));
+
+ /* uUser1 = off to start modifying the code; */
+ static const OS2CODEPATTERN s_aPatterns[] =
+ {
+ { s_abVariant1, s_abVariant1Mask, sizeof(s_abVariant1Mask), 0x010d - 0x00fc, 0, 0, 0, 0 },
+ };
+
+ PCOS2CODEPATTERN pPattern;
+ uint8_t *pbHit = findCodePattern(&s_aPatterns[0], RT_ELEMENTS(s_aPatterns), pbFile, cbFile, &pPattern);
+ if (pPattern)
+ {
+ /* We've got */
+ uint8_t *pbPatch = &pbHit[pPattern->uUser1];
+#if 0 /* this seems to fix the issue */
+ *pbPatch++ = 0xe6; /* out 78h, al - triggers an exit */
+ *pbPatch++ = 0x78;
+#elif 0 /* this seems to fix it too */
+ *pbPatch++ = 0xf3; /* pause */
+ *pbPatch++ = 0x90;
+#elif 0 /* still reproducible with normal nops. */
+ *pbPatch++ = 0x90;
+ *pbPatch++ = 0x90;
+#else
+# if 0
+ /*0763:010d*/ 0x72, 0x0e, /* jc +00eh (0011dh) */
+ /*0763:010f*/ 0x50, /* push ax */
+ /*0763:0110*/ 0x1f, /* pop DS */
+ /*0763:0111*/ 0xf7, 0x47, 0x06, 0x03, 0x00, /* test word [bx+006h], 00003h */
+ /*0763:0116*/ 0x74, 0x05, /* je +005h (0011dh) */
+ /*0763:0118*/ 0xb8, 0x01, 0x00, /* mov ax, 00001h */
+ /*0763:011b*/ 0xeb, 0x02, /* jmp +002h (0011fh) */
+ /*0763:011d*/ 0x33, 0xc0, /* xor ax, ax */
+ /*0763:011f*/ 0x1f, /* pop DS */
+ /*0763:0120*/ 0x5a, /* pop dx */
+ /*0763:0121*/ 0x59, /* pop cx */
+ /*0763:0122*/ 0x5b, /* pop bx */
+ /*0763:0123*/ 0x5d, /* pop bp */
+ /*0763:0124*/ 0xcb, /* retf */
+# endif
+ /* Try straigthen out the code and mabye load DS into AX (we don't care about the return value) */
+ *pbPatch++ = 0x50; /* push ax */
+ *pbPatch++ = 0x1f; /* pop DS */
+
+ *pbPatch++ = 0xf7; /* test word [bx+006h], 00003h */
+ *pbPatch++ = 0x47;
+ *pbPatch++ = 0x06;
+ *pbPatch++ = 0x03;
+ *pbPatch++ = 0x00;
+ /* not je */
+ *pbPatch++ = 0xb8; /* mov ax, 00001h */
+ *pbPatch++ = 0x01;
+ *pbPatch++ = 0x00;
+
+# if 0 /* try reload SS */
+ *pbPatch++ = 0x8c; /* mov ax, ss */
+ *pbPatch++ = 0xd0;
+ *pbPatch++ = 0x8e; /* mov ss, ax */
+ *pbPatch++ = 0xd0;
+# endif
+# if 0 /* try reload CR3 to flush everything - not possible, we're in ring-3 */
+ *pbPatch++ = 0x0f; /* mov eax, cr3 */
+ *pbPatch++ = 0x20;
+ *pbPatch++ = 0xd8;
+ *pbPatch++ = 0x0f; /* mov cr3, eax */
+ *pbPatch++ = 0x22;
+ *pbPatch++ = 0xd8;
+# endif
+
+ *pbPatch++ = 0x1f; /* pop DS */
+# if 0
+ *pbPatch++ = 0x8c; /* mov ax, ds */
+ *pbPatch++ = 0xd8;
+# endif
+ *pbPatch++ = 0x5a; /* pop dx */
+ *pbPatch++ = 0x59; /* pop cx */
+ *pbPatch++ = 0x5b; /* pop bx */
+ *pbPatch++ = 0x5d; /* pop bp */
+ *pbPatch++ = 0xcb; /* retf */
+
+#endif
+ }
+ else
+ {
+ LogRelFunc(("No patch pattern match!\n"));
+ return VERR_NOT_FOUND;
+ }
+
+ return VINF_SUCCESS;
+}
+#endif
+
+
+/**
+ * Patcher callback for OS2LDR.
+ *
+ * There are one or two delay calibration loops here that doesn't work well on
+ * fast CPUs. Typically ends up with division by chainsaw, which in a BIOS
+ * context means an unending loop as the BIOS \#DE handler doesn't do much.
+ *
+ * The patching is simplictic, in that it just returns a constant value. We
+ * could rewrite this to use RDTSC and some secret MSR/whatever for converting
+ * that to a decent loop count.
+ */
+/*static*/
+int UnattendedOs2Installer::patchOs2Ldr(uint8_t *pbFile, size_t cbFile, const char *pszFilename, UnattendedOs2Installer *pThis)
+{
+ RT_NOREF(pThis, pszFilename);
+
+ /*
+ * This first variant is from ACP2:
+ *
+ * VBoxDbg> r
+ * eax=00001000 ebx=00010000 ecx=56d8ffd5 edx=178b0000 esi=00000000 edi=0000b750
+ * eip=0000847a esp=0000cfe8 ebp=00000000 iopl=0 nv up ei pl zr na po nc
+ * cs=2000 ds=2000 es=2000 fs=0000 gs=0000 ss=2000 eflags=00000246
+ * 2000:0000847a f7 fb idiv bx
+ * VBoxDbg> ucfg 2000:840a
+ *
+ * This is a little annoying because it stores the result in a global variable,
+ * so we cannot just do an early return, instead we have to have to jump to the
+ * end of the function so it can be stored correctly.
+ */
+ static uint8_t const s_abVariant1[] =
+ {
+ /*2000:840a*/ 0x60, /* pushaw */
+ /*2000:840b*/ 0x1e, /* push DS */
+ /*2000:840c*/ 0x0e, /* push CS */
+ /*2000:840d*/ 0x1f, /* pop DS */
+ /*2000:840e*/ 0x9c, /* pushfw */
+ /*2000:840f*/ 0xfa, /* cli */
+ /*2000:8410*/ 0xb0, 0x34, /* mov AL, 034h */
+ /*2000:8412*/ 0xe6, 0x43, /* out 043h, AL */
+ /*2000:8414*/ 0xe8, 0x75, 0xfc, /* call 0808ch */
+ /*2000:8417*/ 0x32, 0xc0, /* xor al, al */
+ /*2000:8419*/ 0xe6, 0x40, /* out 040h, AL */
+ /*2000:841b*/ 0xe8, 0x6e, 0xfc, /* call 0808ch */
+ /*2000:841e*/ 0xe6, 0x40, /* out 040h, AL */
+ /*2000:8420*/ 0xe8, 0x69, 0xfc, /* call 0808ch */
+ /*2000:8423*/ 0xb0, 0x00, /* mov AL, 000h */
+ /*2000:8425*/ 0xe6, 0x43, /* out 043h, AL */
+ /*2000:8427*/ 0xe8, 0x62, 0xfc, /* call 0808ch */
+ /*2000:842a*/ 0xe4, 0x40, /* in AL, 040h */
+ /*2000:842c*/ 0xe8, 0x5d, 0xfc, /* call 0808ch */
+ /*2000:842f*/ 0x8a, 0xd8, /* mov bl, al */
+ /*2000:8431*/ 0xe4, 0x40, /* in AL, 040h */
+ /*2000:8433*/ 0x8a, 0xf8, /* mov bh, al */
+ /*2000:8435*/ 0xb0, 0x00, /* mov AL, 000h */
+ /*2000:8437*/ 0xe6, 0x43, /* out 043h, AL */
+ /*2000:8439*/ 0xe8, 0x50, 0xfc, /* call 0808ch */
+ /*2000:843c*/ 0xe4, 0x40, /* in AL, 040h */
+ /*2000:843e*/ 0xe8, 0x4b, 0xfc, /* call 0808ch */
+ /*2000:8441*/ 0x8a, 0xc8, /* mov cl, al */
+ /*2000:8443*/ 0xe4, 0x40, /* in AL, 040h */
+ /*2000:8445*/ 0x8a, 0xe8, /* mov ch, al */
+ /*2000:8447*/ 0xbe, 0x00, 0x10, /* mov si, 01000h */
+ /*2000:844a*/ 0x87, 0xdb, /* xchg bx, bx */
+ /*2000:844c*/ 0x4e, /* dec si */
+ /*2000:844d*/ 0x75, 0xfd, /* jne -003h (0844ch) */
+ /*2000:844f*/ 0xb0, 0x00, /* mov AL, 000h */
+ /*2000:8451*/ 0xe6, 0x43, /* out 043h, AL */
+ /*2000:8453*/ 0xe8, 0x36, 0xfc, /* call 0808ch */
+ /*2000:8456*/ 0xe4, 0x40, /* in AL, 040h */
+ /*2000:8458*/ 0xe8, 0x31, 0xfc, /* call 0808ch */
+ /*2000:845b*/ 0x8a, 0xd0, /* mov dl, al */
+ /*2000:845d*/ 0xe4, 0x40, /* in AL, 040h */
+ /*2000:845f*/ 0x8a, 0xf0, /* mov dh, al */
+ /*2000:8461*/ 0x9d, /* popfw */
+ /*2000:8462*/ 0x2b, 0xd9, /* sub bx, cx */
+ /*2000:8464*/ 0x2b, 0xca, /* sub cx, dx */
+ /*2000:8466*/ 0x2b, 0xcb, /* sub cx, bx */
+ /*2000:8468*/ 0x87, 0xca, /* xchg dx, cx */
+ /*2000:846a*/ 0xb8, 0x28, 0x00, /* mov ax, 00028h */
+ /*2000:846d*/ 0xf7, 0xea, /* imul dx */
+ /*2000:846f*/ 0xbb, 0x18, 0x00, /* mov bx, 00018h */
+ /*2000:8472*/ 0xf7, 0xfb, /* idiv bx */
+ /*2000:8474*/ 0x33, 0xd2, /* xor dx, dx */
+ /*2000:8476*/ 0xbb, 0x00, 0x10, /* mov bx, 01000h */
+ /*2000:8479*/ 0x93, /* xchg bx, ax */
+ /*2000:847a*/ 0xf7, 0xfb, /* idiv bx */
+ /*2000:847c*/ 0x0b, 0xd2, /* or dx, dx */
+ /*2000:847e*/ 0x74, 0x01, /* je +001h (08481h) */
+ /*2000:8480*/ 0x40, /* inc ax */
+ /*2000:8481*/ 0x40, /* inc ax */
+ /*2000:8482*/ 0xa3, 0x4d, 0xac, /* mov word [0ac4dh], ax */
+ /*2000:8485*/ 0x1f, /* pop DS */
+ /*2000:8486*/ 0x61, /* popaw */
+ /*2000:8487*/ 0xc3, /* retn */
+ };
+ static uint8_t const s_abVariant1Mask[] =
+ {
+ /*2000:840a*/ 0xff, /* pushaw */
+ /*2000:840b*/ 0xff, /* push DS */
+ /*2000:840c*/ 0xff, /* push CS */
+ /*2000:840d*/ 0xff, /* pop DS */
+ /*2000:840e*/ 0xff, /* pushfw */
+ /*2000:840f*/ 0xff, /* cli */
+ /*2000:8410*/ 0xff, 0xff, /* mov AL, 034h */
+ /*2000:8412*/ 0xff, 0xff, /* out 043h, AL */
+ /*2000:8414*/ 0xff, 0x00, 0x00, /* call 0808ch - ignore offset */
+ /*2000:8417*/ 0xff, 0xff, /* xor al, al */
+ /*2000:8419*/ 0xff, 0xff, /* out 040h, AL */
+ /*2000:841b*/ 0xff, 0x00, 0x00, /* call 0808ch - ignore offset */
+ /*2000:841e*/ 0xff, 0xff, /* out 040h, AL */
+ /*2000:8420*/ 0xff, 0x00, 0x00, /* call 0808ch - ignore offset */
+ /*2000:8423*/ 0xff, 0xff, /* mov AL, 000h */
+ /*2000:8425*/ 0xff, 0xff, /* out 043h, AL */
+ /*2000:8427*/ 0xff, 0x00, 0x00, /* call 0808ch - ignore offset */
+ /*2000:842a*/ 0xff, 0xff, /* in AL, 040h */
+ /*2000:842c*/ 0xff, 0x00, 0x00, /* call 0808ch - ignore offset */
+ /*2000:842f*/ 0xff, 0xff, /* mov bl, al */
+ /*2000:8431*/ 0xff, 0xff, /* in AL, 040h */
+ /*2000:8433*/ 0xff, 0xff, /* mov bh, al */
+ /*2000:8435*/ 0xff, 0xff, /* mov AL, 000h */
+ /*2000:8437*/ 0xff, 0xff, /* out 043h, AL */
+ /*2000:8439*/ 0xff, 0x00, 0x00, /* call 0808ch - ignore offset */
+ /*2000:843c*/ 0xff, 0x40, /* in AL, 040h */
+ /*2000:843e*/ 0xff, 0x00, 0x00, /* call 0808ch - ignore offset */
+ /*2000:8441*/ 0xff, 0xff, /* mov cl, al */
+ /*2000:8443*/ 0xff, 0xff, /* in AL, 040h */
+ /*2000:8445*/ 0xff, 0xff, /* mov ch, al */
+ /*2000:8447*/ 0xff, 0x00, 0x00, /* mov si, 01000h - ignore loop count */
+ /*2000:844a*/ 0xff, 0xff, /* xchg bx, bx */
+ /*2000:844c*/ 0xff, /* dec si */
+ /*2000:844d*/ 0xff, 0xfd, /* jne -003h (0844ch) */
+ /*2000:844f*/ 0xff, 0xff, /* mov AL, 000h */
+ /*2000:8451*/ 0xff, 0xff, /* out 043h, AL */
+ /*2000:8453*/ 0xff, 0x00, 0x00, /* call 0808ch - ignore offset */
+ /*2000:8456*/ 0xff, 0xff, /* in AL, 040h */
+ /*2000:8458*/ 0xff, 0x00, 0x00, /* call 0808ch - ignore offset */
+ /*2000:845b*/ 0xff, 0xff, /* mov dl, al */
+ /*2000:845d*/ 0xff, 0xff, /* in AL, 040h */
+ /*2000:845f*/ 0xff, 0xff, /* mov dh, al */
+ /*2000:8461*/ 0xff, /* popfw */
+ /*2000:8462*/ 0xff, 0xff, /* sub bx, cx */
+ /*2000:8464*/ 0xff, 0xff, /* sub cx, dx */
+ /*2000:8466*/ 0xff, 0xff, /* sub cx, bx */
+ /*2000:8468*/ 0xff, 0xff, /* xchg dx, cx */
+ /*2000:846a*/ 0xff, 0xff, 0xff, /* mov ax, 00028h */
+ /*2000:846d*/ 0xff, 0xff, /* imul dx */
+ /*2000:846f*/ 0xff, 0xff, 0xff, /* mov bx, 00018h */
+ /*2000:8472*/ 0xff, 0xff, /* idiv bx */
+ /*2000:8474*/ 0xff, 0xff, /* xor dx, dx */
+ /*2000:8476*/ 0xff, 0x00, 0x00, /* mov bx, 01000h - ignore loop count */
+ /*2000:8479*/ 0xff, /* xchg bx, ax */
+ /*2000:847a*/ 0xff, 0xff, /* idiv bx */
+ /*2000:847c*/ 0xff, 0xff, /* or dx, dx */
+ /*2000:847e*/ 0xff, 0xff, /* je +001h (08481h) */
+ /*2000:8480*/ 0xff, /* inc ax */
+ /*2000:8481*/ 0xff, /* inc ax */
+ /*2000:8482*/ 0xff, 0x00, 0x00, /* mov word [0ac4dh], ax */
+ /*2000:8485*/ 0xff, /* pop DS */
+ /*2000:8486*/ 0xff, /* popaw */
+ /*2000:8487*/ 0xff, /* retn */
+ };
+ AssertCompile(sizeof(s_abVariant1Mask) == sizeof(s_abVariant1));
+
+ /* uUser1 = off to start injecting code; uUser2 = jump target offset from start of pattern */
+ static const OS2CODEPATTERN s_aPatterns[] =
+ {
+ { s_abVariant1, s_abVariant1Mask, sizeof(s_abVariant1Mask), 0x840e - 0x840a, 0x8482 - 0x840a, 0, 0, 0 },
+ };
+
+ PCOS2CODEPATTERN pPattern;
+ uint8_t *pbHit = findCodePattern(&s_aPatterns[0], RT_ELEMENTS(s_aPatterns), pbFile, cbFile, &pPattern);
+ if (pPattern)
+ {
+ uint8_t *pbJmpTarget = &pbHit[pPattern->uUser2];
+ uint8_t *pbPatch = &pbHit[pPattern->uUser1];
+ *pbPatch++ = 0xb8; /* mov ax, 01000h */
+ *pbPatch++ = 0x00;
+ *pbPatch++ = 0x10;
+#if 0
+ *pbPatch++ = 0xfa; /* cli */
+ *pbPatch++ = 0xf4; /* hlt */
+#endif
+ uint16_t offRel16 = (uint16_t)(pbJmpTarget - &pbPatch[3]);
+ *pbPatch++ = 0xe9; /* jmp rel16 */
+ *pbPatch++ = (uint8_t)offRel16;
+ *pbPatch++ = (uint8_t)(offRel16 >> 8);
+ *pbPatch++ = 0xcc;
+ *pbPatch++ = 0xcc;
+ }
+ else
+ LogRelFunc(("No patch pattern match!\n"));
+
+ return VINF_SUCCESS;
+}
+
+HRESULT UnattendedOs2Installer::copyFilesToAuxFloppyImage(RTVFS hVfs)
+{
+ /*
+ * Make sure we've split the files already.
+ */
+ HRESULT hrc = splitResponseFile();
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * We need to copy over the files needed to boot OS/2.
+ */
+ static struct
+ {
+ bool fMandatory;
+ const char *apszNames[2]; /**< Will always copy it over using the first name. */
+ const char *apszDisks[3];
+ const char *pszMinVer;
+ const char *pszMaxVer;
+ int (*pfnPatcher)(uint8_t *pbFile, size_t cbFile, const char *pszFilename, UnattendedOs2Installer *pThis);
+ } const s_aFiles[] =
+ {
+ { true, { "OS2BOOT", NULL }, { "DISK_0", NULL, NULL }, "2.1", NULL, NULL }, /* 2.0 did not have OS2BOOT */
+ { true, { "OS2LDR", NULL }, { "DISK_0", NULL, NULL }, NULL, NULL, patchOs2Ldr },
+ { true, { "OS2LDR.MSG", NULL }, { "DISK_0", NULL, NULL }, NULL, NULL, NULL },
+ { true, { "OS2KRNL", "OS2KRNLI" }, { "DISK_0", NULL, NULL }, NULL, NULL, NULL }, /* OS2KRNLI seems to trigger question for 2nd floppy */
+ { true, { "OS2DUMP", NULL }, { "DISK_0", NULL, NULL }, NULL, NULL, NULL },
+
+ { true, { "ANSICALL.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "BKSCALLS.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "BMSCALLS.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "BVHINIT.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "BVSCALLS.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "CDFS.IFS", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "CLOCK01.SYS", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "COUNT437.SYS", "COUNTRY.SYS" }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "DOS.SYS", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "DOSCALL1.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "IBM1FLPY.ADD", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "IBM1S506.ADD", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "IBMIDECD.FLT", NULL }, { "DISK_1", "DISK_2", NULL }, "4.0", NULL, NULL }, /* not in 2.1 & Warp3 */
+ { true, { "IBMKBD.SYS", "KBD01.SYS"/*?*/}, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+#if 1 /* Sometimes takes forever. (Bad IODelay count? Fixed by OS2LDR patching?) Removing seems to cause testcfg.sys to crash. */
+ { true, { "ISAPNP.SNP", NULL }, { "DISK_1", "DISK_2", NULL }, "4.0", NULL, NULL }, /* not in 2.1 */
+#endif
+ { true, { "KBDBASE.SYS", NULL }, { "DISK_1", "DISK_2", NULL }, "3.0", NULL, NULL }, /* not in 2.1 */
+ { true, { "KBDCALLS.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "KEYBOARD.DCP", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "MOUCALLS.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "MSG.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "NAMPIPES.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "NLS.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "OS2CDROM.DMD", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "OS2CHAR.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "OS2DASD.DMD", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "OS2LVM.DMD", NULL }, { "DISK_1", "DISK_2", NULL }, "4.5", NULL, NULL },
+ { true, { "OS2VER", NULL }, { "DISK_0", NULL, NULL }, NULL, NULL, NULL },
+ { true, { "PNP.SYS", NULL }, { "DISK_1", "DISK_2", NULL }, "4.0", NULL, NULL },
+ { true, { "QUECALLS.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "RESOURCE.SYS", NULL }, { "DISK_1", "DISK_2", NULL }, "3.0", NULL, NULL }, /* not in 2.1*/
+ { true, { "SCREEN01.SYS", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "SESMGR.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "TESTCFG.SYS", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL, /*patchTestCfg*/ },
+ { true, { "VIO437.DCP", "VTBL850.DCP" }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ { true, { "VIOCALLS.DLL", NULL }, { "DISK_1", "DISK_2", NULL }, NULL, NULL, NULL },
+ };
+
+
+ RTVFS hVfsOrgIso;
+ hrc = openInstallIsoImage(&hVfsOrgIso);
+ if (SUCCEEDED(hrc))
+ {
+ for (size_t i = 0; i < RT_ELEMENTS(s_aFiles); i++)
+ {
+ bool fCopied = false;
+ for (size_t iDisk = 0; iDisk < RT_ELEMENTS(s_aFiles[i].apszDisks) && s_aFiles[i].apszDisks[iDisk] && !fCopied; iDisk++)
+ {
+ for (size_t iName = 0; iName < RT_ELEMENTS(s_aFiles[i].apszNames) && s_aFiles[i].apszNames[iName]; iName++)
+ {
+ char szPath[256];
+ int vrc = RTPathJoin(szPath, sizeof(szPath), mStrOs2Images.c_str(), s_aFiles[i].apszDisks[iDisk]);
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppend(szPath, sizeof(szPath), s_aFiles[i].apszNames[iName]);
+ AssertRCBreakStmt(vrc, hrc = mpParent->setErrorBoth(E_FAIL, vrc, tr("RTPathJoin/Append failed for %s: %Rrc"),
+ s_aFiles[i].apszNames[iName], vrc));
+ RTVFSFILE hVfsSrc;
+ vrc = RTVfsFileOpen(hVfsOrgIso, szPath, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, &hVfsSrc);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSFILE hVfsDst;
+ vrc = RTVfsFileOpen(hVfs, s_aFiles[i].apszNames[0],
+ RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE
+ | (0755 << RTFILE_O_CREATE_MODE_SHIFT), &hVfsDst);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!s_aFiles[i].pfnPatcher)
+ {
+ /*
+ * Not patching this file, so just pump it thru and close it.
+ */
+ RTVFSIOSTREAM hVfsIosSrc = RTVfsFileToIoStream(hVfsSrc);
+ RTVFSIOSTREAM hVfsIosDst = RTVfsFileToIoStream(hVfsDst);
+ vrc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, 0);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ if (RT_FAILURE(vrc))
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Failed to write %s to the floppy: %Rrc"),
+ s_aFiles[i].apszNames, vrc);
+ }
+ else
+ {
+ /*
+ * Read the file into memory, do the patching and writed
+ * the patched content to the floppy.
+ */
+ uint64_t cbFile = 0;
+ vrc = RTVfsFileQuerySize(hVfsSrc, &cbFile);
+ if (RT_SUCCESS(vrc) && cbFile < _32M)
+ {
+ uint8_t *pbFile = (uint8_t *)RTMemTmpAllocZ((size_t)cbFile);
+ if (pbFile)
+ {
+ vrc = RTVfsFileRead(hVfsSrc, pbFile, (size_t)cbFile, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = s_aFiles[i].pfnPatcher(pbFile, (size_t)cbFile, s_aFiles[i].apszNames[0], this);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsFileWrite(hVfsDst, pbFile, (size_t)cbFile, NULL);
+ if (RT_FAILURE(vrc))
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Failed to write %s to the floppy: %Rrc"),
+ s_aFiles[i].apszNames, vrc);
+ }
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, vrc, tr("Patcher failed for '%s': %Rrc"),
+ s_aFiles[i].apszNames, vrc);
+ }
+ else
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Error reading '%s' into memory for patching: %Rrc"),
+ s_aFiles[i].apszNames, vrc);
+ RTMemTmpFree(pbFile);
+ }
+ else
+ hrc = mpParent->setError(E_OUTOFMEMORY, tr("Failed to allocate %zu bytes for '%s'"),
+ (size_t)cbFile, s_aFiles[i].apszNames);
+ }
+ else if (RT_FAILURE(vrc))
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("Failed to query the size of '%s': %Rrc"),
+ s_aFiles[i].apszNames, vrc);
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, VERR_OUT_OF_RANGE, tr("File too big to patch: '%s'"),
+ s_aFiles[i].apszNames);
+ }
+ RTVfsFileRelease(hVfsDst);
+ }
+ else
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to open %s on floppy: %Rrc"),
+ s_aFiles[i].apszNames, vrc);
+
+ RTVfsFileRelease(hVfsSrc);
+ fCopied = true;
+ break;
+ }
+ }
+ }
+ if (FAILED(hrc))
+ break;
+ if (!fCopied)
+ {
+ /** @todo do version filtering. */
+ hrc = mpParent->setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND,
+ tr("Failed to locate '%s' needed for the install floppy"), s_aFiles[i].apszNames[0]);
+ break;
+ }
+ }
+ RTVfsRelease(hVfsOrgIso);
+ }
+
+ /*
+ * In addition, we need to add a CONFIG.SYS and the startup script.
+ */
+ if (SUCCEEDED(hrc))
+ {
+ Utf8Str strSrc;
+ try
+ {
+ strSrc = mpParent->i_getAuxiliaryBasePath();
+ strSrc.append("CONFIG.SYS");
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ hrc = addFileToFloppyImage(hVfs, strSrc.c_str(), "CONFIG.SYS");
+ }
+
+ /*
+ * We also want a ALTF2ON.$$$ file so we can see which drivers are loaded
+ * and where it might get stuck.
+ */
+ if (SUCCEEDED(hrc))
+ {
+ RTVFSFILE hVfsFile;
+ int vrc = RTVfsFileOpen(hVfs, "ALTF2ON.$$$",
+ RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE
+ | (0755 << RTFILE_O_CREATE_MODE_SHIFT), &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ /** @todo buggy fat vfs: cannot write empty files */
+ RTVfsFileWrite(hVfsFile, RT_STR_TUPLE("\r\n"), NULL);
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ hrc = mpParent->setErrorBoth(E_FAIL, VERR_FILE_NOT_FOUND, tr("Failed to create 'ALTF2ON.$$$' on the install floppy"));
+ }
+
+ return hrc;
+}
+
+HRESULT UnattendedOs2Installer::addFilesToAuxVisoVectors(RTCList<RTCString> &rVecArgs, RTCList<RTCString> &rVecFiles,
+ RTVFS hVfsOrgIso, bool fOverwrite)
+{
+ /*
+ * Make sure we've split the files already.
+ */
+ HRESULT hrc = splitResponseFile();
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Add our stuff to the vectors.
+ *
+ * Note! Typcially OS/2 ISOs are without joliet or UDF namespaces, given
+ * their age and tools used to produce them, but more recent ones
+ * like ArcaOS have joliet present. So, to avoid ending up with an
+ * almost empty CDROM in Phase2 because UDF.IFS is loaded and
+ * presenting the joliet namespace, the --name-setup-from-import
+ * option was added to the ISO maker. It will look at the files that
+ * were imported and adjust the --name-setup accordingly (logged).
+ */
+ try
+ {
+ /* Remaster ISO. */
+ rVecArgs.append() = "--no-file-mode";
+ rVecArgs.append() = "--no-dir-mode";
+
+ rVecArgs.append() = "--import-iso";
+ rVecArgs.append(mpParent->i_getIsoPath());
+ rVecArgs.append() = "--name-setup-from-import"; /* */
+
+ /** @todo these enables rock-ridge... */
+ rVecArgs.append() = "--file-mode=0444";
+ rVecArgs.append() = "--dir-mode=0555";
+
+ /* Add the boot floppy to the ISO: */
+ rVecArgs.append() = "--eltorito-new-entry";
+ rVecArgs.append() = "--eltorito-add-image";
+ rVecArgs.append().assign("VBoxBootFloppy.img=").append(mStrAuxiliaryFloppyFilePath);
+ rVecArgs.append() = "--eltorito-floppy-288";
+
+ /* Add the response files and postinstall files to the ISO: */
+ Utf8Str const &rStrAuxPrefix = mpParent->i_getAuxiliaryBasePath();
+ size_t i = mVecSplitFiles.size();
+ while (i-- > 0)
+ {
+ RTCString const &rStrFile = mVecSplitFiles[i];
+ rVecArgs.append().assign("VBoxCID/").append(rStrFile).append('=').append(rStrAuxPrefix).append(rStrFile);
+ }
+
+ /* Add the os2_util.exe to the ISO: */
+ Utf8Str strUnattendedTemplates;
+ int vrc = RTPathAppPrivateNoArchCxx(strUnattendedTemplates);
+ AssertRCReturn(vrc, mpParent->setErrorVrc(vrc));
+ vrc = RTPathAppendCxx(strUnattendedTemplates, "UnattendedTemplates");
+ AssertRCReturn(vrc, mpParent->setErrorVrc(vrc));
+ rVecArgs.append().assign("VBoxCID/os2_util.exe=").append(strUnattendedTemplates).append("/os2_util.exe");
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Call parent.
+ */
+ return UnattendedInstaller::addFilesToAuxVisoVectors(rVecArgs, rVecFiles, hVfsOrgIso, fOverwrite);
+}
+
+/**
+ * Helper for splitFile.
+ */
+const char *splitFileLocateSubstring(const char *pszSrc, size_t cchSrc, const char *pszSubstring, size_t cchSubstring)
+{
+ char const ch0 = *pszSubstring;
+ while (cchSrc >= cchSubstring)
+ {
+ const char *pszHit0 = (const char *)memchr(pszSrc, ch0, cchSrc - cchSubstring + 1);
+ if (pszHit0)
+ {
+ if (memcmp(pszHit0, pszSubstring, cchSubstring) == 0)
+ return pszHit0;
+ }
+ else
+ break;
+ cchSrc -= (size_t)(pszHit0 - pszSrc) + 1;
+ pszSrc = pszHit0 + 1;
+ }
+ return NULL;
+}
+
+/**
+ * Worker for splitFile().
+ */
+HRESULT UnattendedOs2Installer::splitFileInner(const char *pszFileToSplit, RTCList<RTCString> &rVecSplitFiles,
+ const char *pszSrc, size_t cbLeft) RT_NOEXCEPT
+{
+ static const char s_szPrefix[] = "@@VBOX_SPLITTER_";
+ const char * const pszStart = pszSrc;
+ const char * const pszEnd = &pszSrc[cbLeft];
+ while (cbLeft > 0)
+ {
+ /*
+ * Locate the next split start marker (everything before it is ignored).
+ */
+ const char *pszMarker = splitFileLocateSubstring(pszSrc, cbLeft, s_szPrefix, sizeof(s_szPrefix) - 1);
+ if (pszMarker)
+ pszMarker += sizeof(s_szPrefix) - 1;
+ else
+ break;
+ if (strncmp(pszMarker, RT_STR_TUPLE("START[")) != 0)
+ return mpParent->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
+ tr("Unexpected splitter tag in '%s' at offset %p: @@VBOX_SPLITTER_%.64s"),
+ pszFileToSplit, pszMarker - pszStart, pszMarker);
+ pszMarker += sizeof("START[") - 1;
+ const char *pszTail = splitFileLocateSubstring(pszMarker, (size_t)(pszEnd - pszMarker), RT_STR_TUPLE("]@@"));
+ size_t const cchFilename = (size_t)(pszTail - pszMarker);
+ if ( !pszTail
+ || cchFilename > 64
+ || memchr(pszMarker, '\\', cchFilename)
+ || memchr(pszMarker, '/', cchFilename)
+ || memchr(pszMarker, ':', cchFilename)
+ || memchr(pszMarker, '\0', cchFilename) )
+ return mpParent->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
+ tr("Malformed splitter tag in '%s' at offset %p: @@VBOX_SPLITTER_START[%.64s"),
+ pszFileToSplit, cchFilename, pszMarker);
+ int vrc = RTStrValidateEncodingEx(pszMarker, cchFilename, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_FAILURE(vrc))
+ return mpParent->setErrorBoth(E_FAIL, vrc,
+ tr("Malformed splitter tag in '%s' at offset %p: @@VBOX_SPLITTER_START[%.*Rhxs"),
+ pszFileToSplit, cchFilename, pszTail - pszMarker, pszMarker);
+ const char *pszFilename;
+ try
+ {
+ pszFilename = rVecSplitFiles.append().assign(pszMarker, cchFilename).c_str();
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ const char *pszDocStart = pszTail + sizeof("]@@") - 1;
+ while (RT_C_IS_SPACE(*pszDocStart))
+ if (*pszDocStart++ == '\n')
+ break;
+
+ /* Advance. */
+ pszSrc = pszDocStart;
+ cbLeft = (size_t)(pszEnd - pszDocStart);
+
+ /*
+ * Locate the matching end marker (there cannot be any other markers inbetween).
+ */
+ const char * const pszDocEnd = pszMarker = splitFileLocateSubstring(pszSrc, cbLeft, s_szPrefix, sizeof(s_szPrefix) - 1);
+ if (pszMarker)
+ pszMarker += sizeof(s_szPrefix) - 1;
+ else
+ return mpParent->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
+ tr("No END splitter tag for '%s' in '%s'"), pszFilename, pszFileToSplit);
+ if (strncmp(pszMarker, RT_STR_TUPLE("END[")) != 0)
+ return mpParent->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
+ tr("Unexpected splitter tag in '%s' at offset %p: @@VBOX_SPLITTER_%.64s"),
+ pszFileToSplit, (size_t)(pszEnd - pszMarker), pszMarker);
+ pszMarker += sizeof("END[") - 1;
+ if ( strncmp(pszMarker, pszFilename, cchFilename) != 0
+ || pszMarker[cchFilename] != ']'
+ || pszMarker[cchFilename + 1] != '@'
+ || pszMarker[cchFilename + 2] != '@')
+ return mpParent->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
+ tr("Mismatching splitter tag for '%s' in '%s' at offset %p: @@VBOX_SPLITTER_END[%.64Rhxs"),
+ pszFilename, pszFileToSplit, (size_t)(pszEnd - pszMarker), pszMarker);
+
+ /* Advance. */
+ pszSrc = pszMarker + cchFilename + sizeof("]@@") - 1;
+ cbLeft = (size_t)(pszEnd - pszSrc);
+
+ /*
+ * Write out the file.
+ */
+ Utf8Str strDstFilename;
+ vrc = strDstFilename.assignNoThrow(mpParent->i_getAuxiliaryBasePath());
+ if (RT_SUCCESS(vrc))
+ vrc = strDstFilename.appendNoThrow(pszFilename);
+ if (RT_SUCCESS(vrc))
+ {
+ RTFILE hFile;
+ vrc = RTFileOpen(&hFile, strDstFilename.c_str(), RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTFileWrite(hFile, pszDocStart, (size_t)(pszDocEnd - pszDocStart), NULL);
+ if (RT_SUCCESS(vrc))
+ vrc = RTFileClose(hFile);
+ else
+ RTFileClose(hFile);
+ if (RT_FAILURE(vrc))
+ return mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Error writing '%s' (split out from '%s'): %Rrc"),
+ strDstFilename.c_str(), pszFileToSplit, vrc);
+ }
+ else
+ return mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("File splitter failed to open output file '%s' in '%s': %Rrc (%s)"),
+ pszFilename, pszFileToSplit, vrc, strDstFilename.c_str());
+ }
+ else
+ return mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("File splitter failed to construct path for '%s' in '%s': %Rrc"),
+ pszFilename, pszFileToSplit, vrc);
+ }
+
+ return S_OK;
+}
+
+HRESULT UnattendedOs2Installer::splitFile(const char *pszFileToSplit, RTCList<RTCString> &rVecSplitFiles) RT_NOEXCEPT
+{
+ /*
+ * Read the whole source file into memory, making sure it's zero terminated.
+ */
+ HRESULT hrc;
+ void *pvSrc;
+ size_t cbSrc;
+ int vrc = RTFileReadAllEx(pszFileToSplit, 0 /*off*/, _16M /*cbMax*/,
+ RTFILE_RDALL_F_TRAILING_ZERO_BYTE | RTFILE_RDALL_F_FAIL_ON_MAX_SIZE | RTFILE_RDALL_O_DENY_WRITE,
+ &pvSrc, &cbSrc);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Do the actual splitting in a worker function to avoid needing to
+ * thing about calling RTFileReadAllFree in error paths.
+ */
+ hrc = splitFileInner(pszFileToSplit, rVecSplitFiles, (const char *)pvSrc, cbSrc);
+ RTFileReadAllFree(pvSrc, cbSrc);
+ }
+ else
+ hrc = mpParent->setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to read '%s' for splitting up: %Rrc"),
+ pszFileToSplit, vrc);
+ return hrc;
+}
+
+HRESULT UnattendedOs2Installer::splitFile(BaseTextScript *pEditor, RTCList<RTCString> &rVecSplitFiles) RT_NOEXCEPT
+{
+ /*
+ * Get the output from the editor.
+ */
+ Utf8Str strSrc;
+ HRESULT hrc = pEditor->saveToString(strSrc);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Do the actual splitting.
+ */
+ hrc = splitFileInner(pEditor->getDefaultFilename(), rVecSplitFiles, strSrc.c_str(), strSrc.length());
+ }
+ return hrc;
+}
+
diff --git a/src/VBox/Main/src-server/UnattendedScript.cpp b/src/VBox/Main/src-server/UnattendedScript.cpp
new file mode 100644
index 00000000..29900e1c
--- /dev/null
+++ b/src/VBox/Main/src-server/UnattendedScript.cpp
@@ -0,0 +1,957 @@
+/* $Id: UnattendedScript.cpp $ */
+/** @file
+ * Classes for reading/parsing/saving scripts for unattended installation.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
+#include "LoggingNew.h"
+#include "VirtualBoxBase.h"
+#include "AutoCaller.h"
+#include <VBox/com/ErrorInfo.h>
+
+#include "UnattendedScript.h"
+#include "UnattendedImpl.h"
+
+#include <iprt/err.h>
+
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/vfs.h>
+#include <iprt/getopt.h>
+#include <iprt/path.h>
+
+using namespace std;
+
+#ifdef VBOX_WITH_UNATTENDED
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+static const char g_szPrefix[] = "@@VBOX_";
+static const char g_szPrefixInsert[] = "@@VBOX_INSERT";
+static const char g_szPrefixInsertXxx[] = "@@VBOX_INSERT_";
+static const char g_szPrefixInsertExpr[] = "@@VBOX_INSERT[";
+static const char g_szPrefixCond[] = "@@VBOX_COND";
+static const char g_szPrefixCondXxx[] = "@@VBOX_COND_";
+static const char g_szPrefixCondExpr[] = "@@VBOX_COND[";
+static const char g_szPrefixCondElse[] = "@@VBOX_COND_ELSE@@";
+static const char g_szPrefixCondEnd[] = "@@VBOX_COND_END@@";
+static const char g_szPrefixSplitter[] = "@@VBOX_SPLITTER";
+
+
+/*********************************************************************************************************************************
+* UnattendedScriptTemplate Implementation *
+*********************************************************************************************************************************/
+
+UnattendedScriptTemplate::UnattendedScriptTemplate(Unattended *pUnattended, const char *pszDefaultTemplateFilename,
+ const char *pszDefaultFilename)
+ : BaseTextScript(pUnattended, pszDefaultTemplateFilename, pszDefaultFilename), mpUnattended(pUnattended)
+{
+}
+
+HRESULT UnattendedScriptTemplate::saveToString(Utf8Str &rStrDst)
+{
+ RTEXPREVAL hEvaluator = NIL_RTEXPREVAL;
+ int vrc = RTExprEvalCreate(&hEvaluator, 0, "unattended", this, UnattendedScriptTemplate::queryVariableForExpr);
+ AssertRCReturn(vrc, mpSetError->setErrorVrc(vrc));
+
+ struct
+ {
+ bool fSavedOutputting;
+ } aConds[8];
+ unsigned cConds = 0;
+ bool fOutputting = true;
+ HRESULT hrc = E_FAIL;
+ size_t offTemplate = 0;
+ size_t cchTemplate = mStrScriptFullContent.length();
+ rStrDst.setNull();
+ for (;;)
+ {
+ /*
+ * Find the next placeholder and add any text before it to the output.
+ */
+ size_t offPlaceholder = mStrScriptFullContent.find(g_szPrefix, offTemplate);
+ size_t cchToCopy = offPlaceholder != RTCString::npos ? offPlaceholder - offTemplate : cchTemplate - offTemplate;
+ if (cchToCopy > 0)
+ {
+ if (fOutputting)
+ {
+ try
+ {
+ rStrDst.append(mStrScriptFullContent, offTemplate , cchToCopy);
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ break;
+ }
+ }
+ offTemplate += cchToCopy;
+ }
+
+ /*
+ * Process placeholder.
+ */
+ if (offPlaceholder != RTCString::npos)
+ {
+ /*
+ * First we must find the end of the placeholder string.
+ */
+ size_t const cchMaxPlaceholder = RT_MIN(cchTemplate - offPlaceholder, _1K);
+ const char *pszPlaceholder = mStrScriptFullContent.c_str() + offPlaceholder;
+ size_t cchPlaceholder = sizeof(g_szPrefix) - 1;
+ char ch;
+ while ( cchPlaceholder < cchMaxPlaceholder
+ && (ch = pszPlaceholder[cchPlaceholder]) != '\0'
+ && (RT_C_IS_PRINT(ch) || RT_C_IS_SPACE(ch))
+ && ch != '@')
+ cchPlaceholder++;
+
+ if ( offPlaceholder + cchPlaceholder < cchTemplate
+ && pszPlaceholder[cchPlaceholder] == '@')
+ {
+ cchPlaceholder++;
+ if ( offPlaceholder + cchPlaceholder < cchTemplate
+ && pszPlaceholder[cchPlaceholder] == '@')
+ cchPlaceholder++;
+ }
+
+ if ( pszPlaceholder[cchPlaceholder - 1] != '@'
+ || pszPlaceholder[cchPlaceholder - 2] != '@'
+ || ( strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixInsert)) != 0
+ && strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCond)) != 0
+ && strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixSplitter)) != 0 ) )
+ {
+ hrc = mpSetError->setError(E_FAIL, tr("Malformed or too long template placeholder '%.*s'"),
+ cchPlaceholder, pszPlaceholder);
+ break;
+ }
+
+ offTemplate += cchPlaceholder;
+
+ /*
+ * @@VBOX_INSERT_XXX@@:
+ */
+ if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixInsertXxx)) == 0)
+ {
+ /*
+ * Get the placeholder value and add it to the output.
+ */
+ RTCString strValue;
+ hrc = getReplacement(pszPlaceholder, cchPlaceholder, fOutputting, strValue);
+ if (SUCCEEDED(hrc))
+ {
+ if (fOutputting)
+ {
+ try
+ {
+ rStrDst.append(strValue);
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ break;
+ }
+ }
+ }
+ else
+ break;
+ }
+ /*
+ * @@VBOX_INSERT[expr]@@:
+ * @@VBOX_INSERT[expr]SH@@:
+ * @@VBOX_INSERT[expr]ELEMENT@@:
+ * @@VBOX_INSERT[expr]ATTRIB_DQ@@:
+ */
+ else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixInsertExpr)) == 0)
+ {
+ /*
+ * Get the placeholder value and add it to the output.
+ */
+ char *pszValue = NULL;
+ hrc = getReplacementForExpr(hEvaluator, pszPlaceholder, cchPlaceholder, fOutputting, &pszValue);
+ if (SUCCEEDED(hrc))
+ {
+ if (fOutputting && pszValue)
+ {
+ try
+ {
+ rStrDst.append(pszValue);
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ break;
+ }
+ }
+ RTStrFree(pszValue);
+ }
+ else
+ break;
+ }
+ /*
+ * @@VBOX_COND_END@@: Pop one item of the conditional stack.
+ */
+ else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondEnd)) == 0)
+ {
+ if (cConds > 0)
+ {
+ cConds--;
+ fOutputting = aConds[cConds].fSavedOutputting;
+ }
+ else
+ {
+ hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
+ tr("%s without @@VBOX_COND_XXX@@ at offset %zu (%#zx)"),
+ g_szPrefixCondEnd, offPlaceholder, offPlaceholder);
+ break;
+ }
+ }
+ /*
+ * @@VBOX_COND_ELSE@@: Flip the output setting of the current condition.
+ */
+ else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondElse)) == 0)
+ {
+ if (cConds > 0)
+ fOutputting = !fOutputting;
+ else
+ {
+ hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
+ tr("%s without @@VBOX_COND_XXX@@ at offset %zu (%#zx)"),
+ g_szPrefixCondElse, offPlaceholder, offPlaceholder);
+ break;
+ }
+ }
+ /*
+ * @@VBOX_COND_XXX@@: Push the previous outputting state and combine it with the
+ * one from the condition.
+ */
+ else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondXxx)) == 0)
+ {
+ if (cConds + 1 < RT_ELEMENTS(aConds))
+ {
+ aConds[cConds].fSavedOutputting = fOutputting;
+ bool fNewOutputting = fOutputting;
+ hrc = getConditional(pszPlaceholder, cchPlaceholder, &fNewOutputting);
+ if (SUCCEEDED(hrc))
+ fOutputting = fOutputting && fNewOutputting;
+ else
+ break;
+ cConds++;
+ }
+ else
+ {
+ hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
+ tr("Too deep conditional nesting at offset %zu (%#zx)"),
+ offPlaceholder, offPlaceholder);
+ break;
+ }
+ }
+ /*
+ * @@VBOX_COND[expr]@@: Push the previous outputting state and combine it with the
+ * one from the condition.
+ */
+ else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondExpr)) == 0)
+ {
+ if (cConds + 1 < RT_ELEMENTS(aConds))
+ {
+ aConds[cConds].fSavedOutputting = fOutputting;
+ bool fNewOutputting = fOutputting;
+ hrc = resolveConditionalExpr(hEvaluator, pszPlaceholder, cchPlaceholder, &fNewOutputting);
+ if (SUCCEEDED(hrc))
+ fOutputting = fOutputting && fNewOutputting;
+ else
+ break;
+ cConds++;
+ }
+ else
+ {
+ hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
+ tr("Too deep conditional nesting at offset %zu (%#zx)"),
+ offPlaceholder, offPlaceholder);
+ break;
+ }
+ }
+ /*
+ * @@VBOX_SPLITTER_START/END[filename]@@: Ignored in this pass.
+ */
+ else
+ {
+ Assert(strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixSplitter)) == 0);
+ if (fOutputting)
+ {
+ try
+ {
+ rStrDst.append(pszPlaceholder, cchPlaceholder);
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ * Done?
+ */
+ if (offTemplate >= cchTemplate)
+ {
+ if (cConds == 0)
+ {
+ RTExprEvalRelease(hEvaluator);
+ return S_OK;
+ }
+ if (cConds == 1)
+ hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Missing @@VBOX_COND_END@@"));
+ else
+ hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Missing %u @@VBOX_COND_END@@"), cConds);
+ break;
+ }
+ }
+
+ /* failed */
+ rStrDst.setNull();
+ RTExprEvalRelease(hEvaluator);
+ return hrc;
+}
+
+HRESULT UnattendedScriptTemplate::getReplacement(const char *pachPlaceholder, size_t cchPlaceholder,
+ bool fOutputting, RTCString &rValue)
+{
+ /*
+ * Check for an escaping suffix. Drop the '@@'.
+ */
+ kEvalEscaping_T enmEscaping;
+#define PLACEHOLDER_ENDS_WITH(a_szSuffix) \
+ ( cchPlaceholder > sizeof(a_szSuffix) - 1U \
+ && memcmp(&pachPlaceholder[cchPlaceholder - sizeof(a_szSuffix) + 1U], a_szSuffix, sizeof(a_szSuffix) - 1U) == 0)
+ if (PLACEHOLDER_ENDS_WITH("_SH@@"))
+ {
+ cchPlaceholder -= 3 + 2;
+ enmEscaping = kValueEscaping_Bourne;
+ }
+ else if (PLACEHOLDER_ENDS_WITH("_ELEMENT@@"))
+ {
+ cchPlaceholder -= 8 + 2;
+ enmEscaping = kValueEscaping_XML_Element;
+ }
+ else if (PLACEHOLDER_ENDS_WITH("_ATTRIB_DQ@@"))
+ {
+ cchPlaceholder -= 10 + 2;
+ enmEscaping = kValueEscaping_XML_Attribute_Double_Quotes;
+ }
+ else
+ {
+ Assert(PLACEHOLDER_ENDS_WITH("@@"));
+ cchPlaceholder -= 2;
+ enmEscaping = kValueEscaping_None;
+ }
+#undef PLACEHOLDER_ENDS_WITH
+
+ /*
+ * Resolve and escape the value.
+ */
+ HRESULT hrc;
+ try
+ {
+ Utf8Str strTmp;
+ const char *pszReadOnlyValue = NULL;
+ int vrc = queryVariable(pachPlaceholder + sizeof(g_szPrefixInsertXxx) - 1,
+ cchPlaceholder - sizeof(g_szPrefixInsertXxx) + 1,
+ strTmp, fOutputting ? &pszReadOnlyValue : NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ if (fOutputting)
+ {
+ Assert(pszReadOnlyValue != NULL);
+ switch (enmEscaping)
+ {
+ case kValueEscaping_None:
+ rValue = pszReadOnlyValue;
+ return S_OK;
+
+ case kValueEscaping_Bourne:
+ case kValueEscaping_XML_Element:
+ case kValueEscaping_XML_Attribute_Double_Quotes:
+ {
+ switch (enmEscaping)
+ {
+ case kValueEscaping_Bourne:
+ {
+ const char * const papszArgs[2] = { pszReadOnlyValue, NULL };
+ char *pszEscaped = NULL;
+ vrc = RTGetOptArgvToString(&pszEscaped, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+ if (RT_SUCCESS(vrc))
+ {
+ try
+ {
+ rValue = pszEscaped;
+ RTStrFree(pszEscaped);
+ return S_OK;
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ RTStrFree(pszEscaped);
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc);
+ break;
+ }
+
+ case kValueEscaping_XML_Element:
+ rValue.printf("%RMes", pszReadOnlyValue);
+ return S_OK;
+
+ case kValueEscaping_XML_Attribute_Double_Quotes:
+ {
+ RTCString strTmp2;
+ strTmp2.printf("%RMas", pszReadOnlyValue);
+ rValue = RTCString(strTmp2, 1, strTmp2.length() - 2);
+ return S_OK;
+ }
+
+ default:
+ hrc = E_FAIL;
+ break;
+ }
+ break;
+ }
+
+ default:
+ AssertFailedStmt(hrc = E_FAIL);
+ break;
+ }
+ }
+ else
+ hrc = S_OK;
+ }
+ else
+ hrc = E_FAIL;
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ rValue.setNull();
+ return hrc;
+}
+
+HRESULT UnattendedScriptTemplate::getReplacementForExpr(RTEXPREVAL hEvaluator, const char *pachPlaceholder, size_t cchPlaceholder,
+ bool fOutputting, char **ppszValue) RT_NOEXCEPT
+{
+ /*
+ * Process the tail of the placeholder to figure out the escaping rules.
+ *
+ * @@VBOX_INSERT[expr]@@:
+ * @@VBOX_INSERT[expr]SH@@:
+ * @@VBOX_INSERT[expr]ELEMENT@@:
+ * @@VBOX_INSERT[expr]ATTRIB_DQ@@:
+ */
+ kEvalEscaping_T enmEscaping;
+#define PLACEHOLDER_ENDS_WITH(a_szSuffix) \
+ ( cchPlaceholder > sizeof(a_szSuffix) - 1U \
+ && memcmp(&pachPlaceholder[cchPlaceholder - sizeof(a_szSuffix) + 1U], a_szSuffix, sizeof(a_szSuffix) - 1U) == 0)
+ if (PLACEHOLDER_ENDS_WITH("]SH@@"))
+ {
+ cchPlaceholder -= sizeof("]SH@@") - 1;
+ enmEscaping = kValueEscaping_Bourne;
+ }
+ else if (PLACEHOLDER_ENDS_WITH("]ELEMENT@@"))
+ {
+ cchPlaceholder -= sizeof("]ELEMENT@@") - 1;
+ enmEscaping = kValueEscaping_XML_Element;
+ }
+ else if (PLACEHOLDER_ENDS_WITH("]ATTRIB_DQ@@"))
+ {
+ cchPlaceholder -= sizeof("]ATTRIB_DQ@@") - 1;
+ enmEscaping = kValueEscaping_XML_Attribute_Double_Quotes;
+ }
+ else if (PLACEHOLDER_ENDS_WITH("]@@"))
+ {
+ cchPlaceholder -= sizeof("]@@") - 1;
+ enmEscaping = kValueEscaping_None;
+ }
+ else
+ return mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Malformed @@VBOX_INSERT[expr]@@: Missing ']' (%.*s)"),
+ cchPlaceholder, pachPlaceholder);
+#undef PLACEHOLDER_ENDS_WITH
+
+ /* The placeholder prefix length. The expression is from cchPrefix to cchPlaceholder. */
+ size_t const cchPrefix = sizeof(g_szPrefixInsertExpr) - 1;
+ Assert(pachPlaceholder[cchPrefix - 1] == '[');
+
+ /*
+ * Evaluate the expression. We do this regardless of fOutput for now.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ char *pszValue = NULL;
+ int vrc = RTExprEvalToString(hEvaluator, &pachPlaceholder[cchPrefix], cchPlaceholder - cchPrefix, &pszValue,
+ RTErrInfoInitStatic(&ErrInfo));
+ LogFlowFunc(("RTExprEvalToString(%.*s) -> %Rrc pszValue=%s\n",
+ cchPlaceholder - cchPrefix, &pachPlaceholder[cchPrefix], vrc, pszValue));
+ if (RT_SUCCESS(vrc))
+ {
+ if (fOutputting)
+ {
+ switch (enmEscaping)
+ {
+ case kValueEscaping_None:
+ *ppszValue = pszValue;
+ pszValue = NULL;
+ break;
+
+ case kValueEscaping_Bourne:
+ {
+ const char * const papszArgs[2] = { pszValue, NULL };
+ vrc = RTGetOptArgvToString(ppszValue, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+ break;
+ }
+
+ case kValueEscaping_XML_Element:
+ vrc = RTStrAPrintf(ppszValue, "%RMes", pszValue);
+ break;
+
+ case kValueEscaping_XML_Attribute_Double_Quotes:
+ vrc = RTStrAPrintf(ppszValue, "%RMas", pszValue);
+ if (RT_SUCCESS(vrc))
+ {
+ /* drop the quotes */
+ char *pszRet = *ppszValue;
+ size_t const cchRet = strlen(pszRet) - 2;
+ memmove(pszRet, &pszRet[1], cchRet);
+ pszRet[cchRet] = '\0';
+ }
+ break;
+
+ default:
+ AssertFailedStmt(vrc = VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ break;
+ }
+ RTStrFree(pszValue);
+ if (RT_FAILURE(vrc))
+ return mpSetError->setErrorVrc(vrc);
+ }
+ else
+ {
+ *ppszValue = NULL;
+ RTStrFree(pszValue);
+ }
+ }
+ else
+ return mpSetError->setErrorBoth(E_FAIL, vrc, tr("Expression evaluation error for '%.*s': %#RTeic"),
+ cchPlaceholder, pachPlaceholder, &ErrInfo.Core);
+ return S_OK;
+}
+
+HRESULT UnattendedScriptTemplate::resolveConditionalExpr(RTEXPREVAL hEvaluator, const char *pachPlaceholder,
+ size_t cchPlaceholder, bool *pfOutputting) RT_NOEXCEPT
+{
+ /*
+ * Check the placeholder tail: @@VBOX_COND[expr]@@
+ */
+ static const char s_szTail[] = "]@@";
+ if (memcmp(&pachPlaceholder[cchPlaceholder - sizeof(s_szTail) + 1], RT_STR_TUPLE(s_szTail)) != 0)
+ return mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Malformed @@VBOX_COND[expr]@@: Missing ']' (%.*s)"),
+ cchPlaceholder, pachPlaceholder);
+ Assert(pachPlaceholder[sizeof(g_szPrefixCondExpr) - 2 ] == '[');
+
+ /*
+ * Evaluate the expression.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ const char * const pchExpr = &pachPlaceholder[sizeof(g_szPrefixCondExpr) - 1];
+ size_t const cchExpr = cchPlaceholder - sizeof(g_szPrefixCondExpr) + 1 - sizeof(s_szTail) + 1;
+ int vrc = RTExprEvalToBool(hEvaluator, pchExpr, cchExpr, pfOutputting, RTErrInfoInitStatic(&ErrInfo));
+ LogFlowFunc(("RTExprEvalToBool(%.*s) -> %Rrc *pfOutputting=%s\n", cchExpr, pchExpr, vrc, *pfOutputting));
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+ return mpSetError->setErrorBoth(E_FAIL, vrc, tr("Expression evaluation error for '%.*s': %#RTeic"),
+ cchPlaceholder, pachPlaceholder, &ErrInfo.Core);
+}
+
+/*static */ DECLCALLBACK(int)
+UnattendedScriptTemplate::queryVariableForExpr(const char *pchName, size_t cchName, void *pvUser, char **ppszValue) RT_NOEXCEPT
+{
+ UnattendedScriptTemplate *pThis = (UnattendedScriptTemplate *)pvUser;
+ int vrc;
+ try
+ {
+ const char *pszReadOnlyValue = NULL;
+ Utf8Str strTmp;
+ vrc = pThis->queryVariable(pchName, cchName, strTmp, ppszValue ? &pszReadOnlyValue : NULL);
+ if (ppszValue)
+ {
+ if (RT_SUCCESS(vrc))
+ vrc = RTStrDupEx(ppszValue, pszReadOnlyValue);
+ else
+ *ppszValue = NULL;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ *ppszValue = NULL;
+ }
+ return vrc;
+}
+
+int UnattendedScriptTemplate::queryVariable(const char *pchName, size_t cchName, Utf8Str &rstrTmp, const char **ppszValue)
+{
+#define IS_MATCH(a_szMatch) \
+ (cchName == sizeof(a_szMatch) - 1U && memcmp(pchName, a_szMatch, sizeof(a_szMatch) - 1U) == 0)
+
+ const char *pszValue;
+
+ /*
+ * Variables
+ */
+ if (IS_MATCH("USER_LOGIN"))
+ pszValue = mpUnattended->i_getUser().c_str();
+ else if (IS_MATCH("USER_PASSWORD"))
+ pszValue = mpUnattended->i_getPassword().c_str();
+ else if (IS_MATCH("ROOT_PASSWORD"))
+ pszValue = mpUnattended->i_getPassword().c_str();
+ else if (IS_MATCH("USER_FULL_NAME"))
+ pszValue = mpUnattended->i_getFullUserName().c_str();
+ else if (IS_MATCH("PRODUCT_KEY"))
+ pszValue = mpUnattended->i_getProductKey().c_str();
+ else if (IS_MATCH("POST_INSTALL_COMMAND"))
+ pszValue = mpUnattended->i_getPostInstallCommand().c_str();
+ else if (IS_MATCH("AUXILIARY_INSTALL_DIR"))
+ pszValue = mpUnattended->i_getAuxiliaryInstallDir().c_str();
+ else if (IS_MATCH("IMAGE_INDEX"))
+ pszValue = rstrTmp.printf("%u", mpUnattended->i_getImageIndex()).c_str();
+ else if (IS_MATCH("OS_ARCH"))
+ pszValue = mpUnattended->i_isGuestOs64Bit() ? "amd64" : "x86";
+ else if (IS_MATCH("OS_ARCH2"))
+ pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "x86";
+ else if (IS_MATCH("OS_ARCH3"))
+ pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "i386";
+ else if (IS_MATCH("OS_ARCH4"))
+ pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "i486";
+ else if (IS_MATCH("OS_ARCH6"))
+ pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "i686";
+ else if (IS_MATCH("GUEST_OS_VERSION"))
+ pszValue = mpUnattended->i_getDetectedOSVersion().c_str();
+ else if (IS_MATCH("GUEST_OS_MAJOR_VERSION"))
+ {
+ Utf8Str const &rstrOsVer = mpUnattended->i_getDetectedOSVersion();
+ size_t offDot = rstrOsVer.find('.');
+ if (offDot > 0 && offDot != Utf8Str::npos)
+ pszValue = rstrTmp.assign(rstrOsVer, 0, offDot).c_str(); /* caller catches std::bad_alloc */
+ else if (!ppszValue)
+ return VERR_NOT_FOUND;
+ else
+ {
+ mpSetError->setErrorBoth(E_FAIL, VERR_NO_DATA, tr("Unknown guest OS major version '%s'"), rstrOsVer.c_str());
+ return VERR_NO_DATA;
+ }
+ }
+ else if (IS_MATCH("TIME_ZONE_UX"))
+ pszValue = mpUnattended->i_getTimeZoneInfo()
+ ? mpUnattended->i_getTimeZoneInfo()->pszUnixName : mpUnattended->i_getTimeZone().c_str();
+ else if (IS_MATCH("TIME_ZONE_WIN_NAME"))
+ {
+ PCRTTIMEZONEINFO pInfo = mpUnattended->i_getTimeZoneInfo();
+ if (pInfo)
+ pszValue = pInfo->pszWindowsName ? pInfo->pszWindowsName : "GMT";
+ else
+ pszValue = mpUnattended->i_getTimeZone().c_str();
+ }
+ else if (IS_MATCH("TIME_ZONE_WIN_INDEX"))
+ {
+ PCRTTIMEZONEINFO pInfo = mpUnattended->i_getTimeZoneInfo();
+ if (pInfo)
+ pszValue = rstrTmp.printf("%u", pInfo->idxWindows ? pInfo->idxWindows : 85 /*GMT*/).c_str();
+ else
+ pszValue = mpUnattended->i_getTimeZone().c_str();
+ }
+ else if (IS_MATCH("LOCALE"))
+ pszValue = mpUnattended->i_getLocale().c_str();
+ else if (IS_MATCH("DASH_LOCALE"))
+ {
+ Assert(mpUnattended->i_getLocale()[2] == '_');
+ pszValue = rstrTmp.assign(mpUnattended->i_getLocale()).replace(2, 1, "-").c_str();
+ }
+ else if (IS_MATCH("LANGUAGE"))
+ pszValue = mpUnattended->i_getLanguage().c_str();
+ else if (IS_MATCH("COUNTRY"))
+ pszValue = mpUnattended->i_getCountry().c_str();
+ else if (IS_MATCH("HOSTNAME_FQDN"))
+ pszValue = mpUnattended->i_getHostname().c_str();
+ else if (IS_MATCH("HOSTNAME_WITHOUT_DOMAIN"))
+ pszValue = rstrTmp.assign(mpUnattended->i_getHostname(), 0, mpUnattended->i_getHostname().find(".")).c_str();
+ else if (IS_MATCH("HOSTNAME_WITHOUT_DOMAIN_MAX_15"))
+ pszValue = rstrTmp.assign(mpUnattended->i_getHostname(), 0, RT_MIN(mpUnattended->i_getHostname().find("."), 15)).c_str();
+ else if (IS_MATCH("HOSTNAME_DOMAIN"))
+ pszValue = rstrTmp.assign(mpUnattended->i_getHostname(), mpUnattended->i_getHostname().find(".") + 1).c_str();
+ else if (IS_MATCH("PROXY"))
+ pszValue = mpUnattended->i_getProxy().c_str();
+ /*
+ * Indicator variables.
+ */
+ else if (IS_MATCH("IS_INSTALLING_ADDITIONS"))
+ pszValue = mpUnattended->i_getInstallGuestAdditions() ? "1" : "0";
+ else if (IS_MATCH("IS_USER_LOGIN_ADMINISTRATOR"))
+ pszValue = mpUnattended->i_getUser().compare("Administrator", RTCString::CaseInsensitive) == 0 ? "1" : "0";
+ else if (IS_MATCH("IS_INSTALLING_TEST_EXEC_SERVICE"))
+ pszValue = mpUnattended->i_getInstallTestExecService() ? "1" : "0";
+ else if (IS_MATCH("HAS_POST_INSTALL_COMMAND"))
+ pszValue = mpUnattended->i_getPostInstallCommand().isNotEmpty() ? "1" : "0";
+ else if (IS_MATCH("HAS_PRODUCT_KEY"))
+ pszValue = mpUnattended->i_getProductKey().isNotEmpty() ? "1" : "0";
+ else if (IS_MATCH("IS_MINIMAL_INSTALLATION"))
+ pszValue = mpUnattended->i_isMinimalInstallation() ? "1" : "0";
+ else if (IS_MATCH("IS_FIRMWARE_UEFI"))
+ pszValue = mpUnattended->i_isFirmwareEFI() ? "1" : "0";
+ else if (IS_MATCH("IS_RTC_USING_UTC"))
+ pszValue = mpUnattended->i_isRtcUsingUtc() ? "1" : "0";
+ else if (IS_MATCH("HAS_PROXY"))
+ pszValue = mpUnattended->i_getProxy().isNotEmpty() ? "1" : "0";
+ /*
+ * Unknown variable.
+ */
+ else if (!ppszValue)
+ return VERR_NOT_FOUND;
+ else
+ {
+ mpSetError->setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("Unknown variable '%.*s'"), cchName, pchName);
+ return VERR_NO_DATA;
+ }
+ if (ppszValue)
+ *ppszValue = pszValue;
+ return VINF_SUCCESS;
+}
+
+HRESULT UnattendedScriptTemplate::getConditional(const char *pachPlaceholder, size_t cchPlaceholder, bool *pfOutputting)
+{
+#define IS_PLACEHOLDER_MATCH(a_szMatch) \
+ ( cchPlaceholder == sizeof("@@VBOX_COND_" a_szMatch "@@") - 1U \
+ && memcmp(pachPlaceholder, "@@VBOX_COND_" a_szMatch "@@", sizeof("@@VBOX_COND_" a_szMatch "@@") - 1U) == 0)
+
+ /* Install Guest Additions: */
+ if (IS_PLACEHOLDER_MATCH("IS_INSTALLING_ADDITIONS"))
+ *pfOutputting = mpUnattended->i_getInstallGuestAdditions();
+ else if (IS_PLACEHOLDER_MATCH("IS_NOT_INSTALLING_ADDITIONS"))
+ *pfOutputting = !mpUnattended->i_getInstallGuestAdditions();
+ /* User == Administrator: */
+ else if (IS_PLACEHOLDER_MATCH("IS_USER_LOGIN_ADMINISTRATOR"))
+ *pfOutputting = mpUnattended->i_getUser().compare("Administrator", RTCString::CaseInsensitive) == 0;
+ else if (IS_PLACEHOLDER_MATCH("IS_USER_LOGIN_NOT_ADMINISTRATOR"))
+ *pfOutputting = mpUnattended->i_getUser().compare("Administrator", RTCString::CaseInsensitive) != 0;
+ /* Install TXS: */
+ else if (IS_PLACEHOLDER_MATCH("IS_INSTALLING_TEST_EXEC_SERVICE"))
+ *pfOutputting = mpUnattended->i_getInstallTestExecService();
+ else if (IS_PLACEHOLDER_MATCH("IS_NOT_INSTALLING_TEST_EXEC_SERVICE"))
+ *pfOutputting = !mpUnattended->i_getInstallTestExecService();
+ /* Post install command: */
+ else if (IS_PLACEHOLDER_MATCH("HAS_POST_INSTALL_COMMAND"))
+ *pfOutputting = mpUnattended->i_getPostInstallCommand().isNotEmpty();
+ else if (IS_PLACEHOLDER_MATCH("HAS_NO_POST_INSTALL_COMMAND"))
+ *pfOutputting = mpUnattended->i_getPostInstallCommand().isEmpty();
+ /* Product key: */
+ else if (IS_PLACEHOLDER_MATCH("HAS_PRODUCT_KEY"))
+ *pfOutputting = mpUnattended->i_getProductKey().isNotEmpty();
+ else if (IS_PLACEHOLDER_MATCH("HAS_NO_PRODUCT_KEY"))
+ *pfOutputting = mpUnattended->i_getProductKey().isEmpty();
+ /* Minimal installation: */
+ else if (IS_PLACEHOLDER_MATCH("IS_MINIMAL_INSTALLATION"))
+ *pfOutputting = mpUnattended->i_isMinimalInstallation();
+ else if (IS_PLACEHOLDER_MATCH("IS_NOT_MINIMAL_INSTALLATION"))
+ *pfOutputting = !mpUnattended->i_isMinimalInstallation();
+ /* Is firmware UEFI: */
+ else if (IS_PLACEHOLDER_MATCH("IS_FIRMWARE_UEFI"))
+ *pfOutputting = mpUnattended->i_isFirmwareEFI();
+ else if (IS_PLACEHOLDER_MATCH("IS_NOT_FIRMWARE_UEFI"))
+ *pfOutputting = !mpUnattended->i_isFirmwareEFI();
+ /* Is RTC using UTC (i.e. set to UTC time on startup): */
+ else if (IS_PLACEHOLDER_MATCH("IS_RTC_USING_UTC"))
+ *pfOutputting = mpUnattended->i_isRtcUsingUtc();
+ else if (IS_PLACEHOLDER_MATCH("IS_NOT_RTC_USING_UTC"))
+ *pfOutputting = !mpUnattended->i_isRtcUsingUtc();
+ else if (IS_PLACEHOLDER_MATCH("HAS_PROXY"))
+ *pfOutputting = mpUnattended->i_getProxy().isNotEmpty();
+ else if (IS_PLACEHOLDER_MATCH("AVOID_UPDATES_OVER_NETWORK"))
+ *pfOutputting = mpUnattended->i_getAvoidUpdatesOverNetwork();
+ else
+ return mpSetError->setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("Unknown conditional placeholder '%.*s'"),
+ cchPlaceholder, pachPlaceholder);
+ return S_OK;
+#undef IS_PLACEHOLDER_MATCH
+}
+
+#endif /* VBOX_WITH_UNATTENDED */
+#if 0 /* Keeping this a reference */
+
+
+/*********************************************************************************************************************************
+* UnattendedSUSEXMLScript Implementation *
+*********************************************************************************************************************************/
+
+HRESULT UnattendedSUSEXMLScript::parse()
+{
+ HRESULT hrc = UnattendedXMLScript::parse();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Check that we've got the right root element type.
+ */
+ const xml::ElementNode *pelmRoot = mDoc.getRootElement();
+ if ( pelmRoot
+ && strcmp(pelmRoot->getName(), "profile") == 0)
+ {
+ /*
+ * Work thought the sections.
+ */
+ try
+ {
+ LoopThruSections(pelmRoot);
+ hrc = S_OK;
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ else if (pelmRoot)
+ hrc = mpSetError->setError(E_FAIL, tr("XML document root element is '%s' instead of 'profile'"),
+ pelmRoot->getName());
+ else
+ hrc = mpSetError->setError(E_FAIL, tr("Missing XML root element"));
+ }
+ return hrc;
+}
+
+HRESULT UnattendedSUSEXMLScript::setFieldInElement(xml::ElementNode *pElement, const DataId enmDataId, const Utf8Str &rStrValue)
+{
+ /*
+ * Don't set empty values.
+ */
+ if (rStrValue.isEmpty())
+ {
+ Utf8Str strProbableValue;
+ try
+ {
+ strProbableValue = createProbableValue(enmDataId, pElement);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return UnattendedXMLScript::setFieldInElement(pElement, enmDataId, strProbableValue);
+ }
+ return UnattendedXMLScript::setFieldInElement(pElement, enmDataId, rStrValue);
+}
+
+HRESULT UnattendedSUSEXMLScript::LoopThruSections(const xml::ElementNode *pelmRoot)
+{
+ xml::NodesLoop loopChildren(*pelmRoot);
+ const xml::ElementNode *pelmOuterLoop;
+ while ((pelmOuterLoop = loopChildren.forAllNodes()) != NULL)
+ {
+ const char *pcszElemName = pelmOuterLoop->getName();
+ if (!strcmp(pcszElemName, "users"))
+ {
+ xml::NodesLoop loopUsers(*pelmOuterLoop);
+ const xml::ElementNode *pelmUser;
+ while ((pelmUser = loopUsers.forAllNodes()) != NULL)
+ {
+ HRESULT hrc = HandleUserAccountsSection(pelmUser);
+ if (FAILED(hrc))
+ return hrc;
+ }
+ }
+ }
+ return S_OK;
+}
+
+HRESULT UnattendedSUSEXMLScript::HandleUserAccountsSection(const xml::ElementNode *pelmSection)
+{
+ xml::NodesLoop loopUser(*pelmSection);
+
+ const xml::ElementNode *pelmCur;
+ while ((pelmCur = loopUser.forAllNodes()) != NULL)
+ {
+ const char *pszValue = pelmCur->getValue();
+#ifdef LOG_ENABLED
+ if (!RTStrCmp(pelmCur->getName(), "uid"))
+ LogRelFunc(("UnattendedSUSEXMLScript::HandleUserAccountsSection profile/users/%s/%s = %s\n",
+ pelmSection->getName(), pelmCur->getName(), pszValue));
+#endif
+
+ if (!RTStrCmp(pszValue, "$homedir"))
+ mNodesForCorrectionMap.insert(make_pair(USERHOMEDIR_ID, pelmCur));
+
+ if (!RTStrCmp(pszValue, "$user"))
+ mNodesForCorrectionMap.insert(make_pair(USERNAME_ID, pelmCur));
+
+ if (!RTStrCmp(pszValue, "$password"))
+ mNodesForCorrectionMap.insert(make_pair(USERPASSWORD_ID, pelmCur));
+ }
+ return S_OK;
+}
+
+Utf8Str UnattendedSUSEXMLScript::createProbableValue(const DataId enmDataId, const xml::ElementNode *pCurElem)
+{
+ const xml::ElementNode *pElem = pCurElem;
+
+ switch (enmDataId)
+ {
+ case USERHOMEDIR_ID:
+// if ((pElem = pElem->findChildElement("home")))
+// {
+ return createProbableUserHomeDir(pElem);
+// }
+ break;
+ default:
+ break;
+ }
+
+ return Utf8Str::Empty;
+}
+
+Utf8Str UnattendedSUSEXMLScript::createProbableUserHomeDir(const xml::ElementNode *pCurElem)
+{
+ Utf8Str strCalcValue;
+ const xml::ElementNode *pElem = pCurElem->findNextSibilingElement("username");
+ if (pElem)
+ {
+ const char *pszValue = pElem->getValue();
+ strCalcValue = "/home/";
+ strCalcValue.append(pszValue);
+ }
+
+ return strCalcValue;
+}
+#endif /* just for reference */
diff --git a/src/VBox/Main/src-server/UpdateAgentImpl.cpp b/src/VBox/Main/src-server/UpdateAgentImpl.cpp
new file mode 100644
index 00000000..73c877ce
--- /dev/null
+++ b/src/VBox/Main/src-server/UpdateAgentImpl.cpp
@@ -0,0 +1,1176 @@
+/* $Id: UpdateAgentImpl.cpp $ */
+/** @file
+ * IUpdateAgent COM class implementations.
+ */
+
+/*
+ * Copyright (C) 2020-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#define LOG_GROUP LOG_GROUP_MAIN_UPDATEAGENT
+
+#include <iprt/cpp/utils.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/http.h>
+#include <iprt/system.h>
+#include <iprt/message.h>
+#include <iprt/pipe.h>
+#include <iprt/env.h>
+#include <iprt/process.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/stream.h>
+#include <iprt/time.h>
+#include <VBox/com/defs.h>
+#include <VBox/err.h>
+#include <VBox/version.h>
+
+#include "HostImpl.h"
+#include "UpdateAgentImpl.h"
+#include "ProgressImpl.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "VirtualBoxImpl.h"
+#include "VBoxEvents.h"
+#include "SystemPropertiesImpl.h"
+#include "ThreadTask.h"
+#include "VirtualBoxImpl.h"
+#include "VirtualBoxBase.h"
+
+
+/*********************************************************************************************************************************
+* Update agent task implementation *
+*********************************************************************************************************************************/
+
+/**
+ * Base task class for asynchronous update agent tasks.
+ */
+class UpdateAgentTask : public ThreadTask
+{
+public:
+ UpdateAgentTask(UpdateAgentBase *aThat, Progress *aProgress)
+ : m_pParent(aThat)
+ , m_pProgress(aProgress)
+ {
+ m_strTaskName = "UpdateAgentTask";
+ }
+ virtual ~UpdateAgentTask(void) { }
+
+private:
+ void handler(void);
+
+ /** Weak pointer to parent (update agent). */
+ UpdateAgentBase *m_pParent;
+ /** Smart pointer to the progress object for this job. */
+ ComObjPtr<Progress> m_pProgress;
+
+ friend class UpdateAgent; // allow member functions access to private data
+};
+
+void UpdateAgentTask::handler(void)
+{
+ UpdateAgentBase *pUpdateAgent = this->m_pParent;
+ AssertPtr(pUpdateAgent);
+
+ /** @todo Differentiate tasks once we have more stuff to do (downloading, installing, ++). */
+
+ HRESULT rc = pUpdateAgent->i_checkForUpdateTask(this);
+
+ if (!m_pProgress.isNull())
+ m_pProgress->i_notifyComplete(rc);
+
+ LogFlowFunc(("rc=%Rhrc\n", rc)); RT_NOREF(rc);
+}
+
+
+/*********************************************************************************************************************************
+* Update agent base class implementation *
+*********************************************************************************************************************************/
+
+/**
+ * Returns platform information as a string.
+ *
+ * @returns Platform information as string.
+ */
+/* static */
+Utf8Str UpdateAgentBase::i_getPlatformInfo(void)
+{
+ /* Prepare platform report: */
+ Utf8Str strPlatform;
+
+# if defined (RT_OS_WINDOWS)
+ strPlatform = "win";
+# elif defined (RT_OS_LINUX)
+ strPlatform = "linux";
+# elif defined (RT_OS_DARWIN)
+ strPlatform = "macosx";
+# elif defined (RT_OS_OS2)
+ strPlatform = "os2";
+# elif defined (RT_OS_FREEBSD)
+ strPlatform = "freebsd";
+# elif defined (RT_OS_SOLARIS)
+ strPlatform = "solaris";
+# else
+ strPlatform = "unknown";
+# endif
+
+ /* The format is <system>.<bitness>: */
+ strPlatform.appendPrintf(".%lu", ARCH_BITS);
+
+ /* Add more system information: */
+ int vrc;
+# ifdef RT_OS_LINUX
+ // WORKAROUND:
+ // On Linux we try to generate information using script first of all..
+
+ /* Get script path: */
+ char szAppPrivPath[RTPATH_MAX];
+ vrc = RTPathAppPrivateNoArch(szAppPrivPath, sizeof(szAppPrivPath));
+ AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppend(szAppPrivPath, sizeof(szAppPrivPath), "/VBoxSysInfo.sh");
+ AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ {
+ RTPIPE hPipeR;
+ RTHANDLE hStdOutPipe;
+ hStdOutPipe.enmType = RTHANDLETYPE_PIPE;
+ vrc = RTPipeCreate(&hPipeR, &hStdOutPipe.u.hPipe, RTPIPE_C_INHERIT_WRITE);
+ AssertLogRelRC(vrc);
+
+ char const *szAppPrivArgs[2];
+ szAppPrivArgs[0] = szAppPrivPath;
+ szAppPrivArgs[1] = NULL;
+ RTPROCESS hProc = NIL_RTPROCESS;
+
+ /* Run script: */
+ vrc = RTProcCreateEx(szAppPrivPath, szAppPrivArgs, RTENV_DEFAULT, 0 /*fFlags*/, NULL /*phStdin*/, &hStdOutPipe,
+ NULL /*phStderr*/, NULL /*pszAsUser*/, NULL /*pszPassword*/, NULL /*pvExtraData*/, &hProc);
+
+ (void) RTPipeClose(hStdOutPipe.u.hPipe);
+ hStdOutPipe.u.hPipe = NIL_RTPIPE;
+
+ if (RT_SUCCESS(vrc))
+ {
+ RTPROCSTATUS ProcStatus;
+ size_t cbStdOutBuf = 0;
+ size_t offStdOutBuf = 0;
+ char *pszStdOutBuf = NULL;
+ do
+ {
+ if (hPipeR != NIL_RTPIPE)
+ {
+ char achBuf[1024];
+ size_t cbRead;
+ vrc = RTPipeReadBlocking(hPipeR, achBuf, sizeof(achBuf), &cbRead);
+ if (RT_SUCCESS(vrc))
+ {
+ /* grow the buffer? */
+ size_t cbBufReq = offStdOutBuf + cbRead + 1;
+ if ( cbBufReq > cbStdOutBuf
+ && cbBufReq < _256K)
+ {
+ size_t cbNew = RT_ALIGN_Z(cbBufReq, 16); // 1024
+ void *pvNew = RTMemRealloc(pszStdOutBuf, cbNew);
+ if (pvNew)
+ {
+ pszStdOutBuf = (char *)pvNew;
+ cbStdOutBuf = cbNew;
+ }
+ }
+
+ /* append if we've got room. */
+ if (cbBufReq <= cbStdOutBuf)
+ {
+ (void) memcpy(&pszStdOutBuf[offStdOutBuf], achBuf, cbRead);
+ offStdOutBuf = offStdOutBuf + cbRead;
+ pszStdOutBuf[offStdOutBuf] = '\0';
+ }
+ }
+ else
+ {
+ AssertLogRelMsg(vrc == VERR_BROKEN_PIPE, ("%Rrc\n", vrc));
+ RTPipeClose(hPipeR);
+ hPipeR = NIL_RTPIPE;
+ }
+ }
+
+ /*
+ * Service the process. Block if we have no pipe.
+ */
+ if (hProc != NIL_RTPROCESS)
+ {
+ vrc = RTProcWait(hProc,
+ hPipeR == NIL_RTPIPE ? RTPROCWAIT_FLAGS_BLOCK : RTPROCWAIT_FLAGS_NOBLOCK,
+ &ProcStatus);
+ if (RT_SUCCESS(vrc))
+ hProc = NIL_RTPROCESS;
+ else
+ AssertLogRelMsgStmt(vrc == VERR_PROCESS_RUNNING, ("%Rrc\n", vrc), hProc = NIL_RTPROCESS);
+ }
+ } while ( hPipeR != NIL_RTPIPE
+ || hProc != NIL_RTPROCESS);
+
+ if ( ProcStatus.enmReason == RTPROCEXITREASON_NORMAL
+ && ProcStatus.iStatus == 0) {
+ pszStdOutBuf[offStdOutBuf-1] = '\0'; // remove trailing newline
+ Utf8Str pszStdOutBufUTF8(pszStdOutBuf);
+ strPlatform.appendPrintf(" [%s]", pszStdOutBufUTF8.strip().c_str());
+ // For testing, here is some sample output:
+ //strPlatform.appendPrintf(" [Distribution: Redhat | Version: 7.6.1810 | Kernel: Linux version 3.10.0-952.27.2.el7.x86_64 (gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) ) #1 SMP Mon Jul 29 17:46:05 UTC 2019]");
+ }
+ }
+ else
+ vrc = VERR_TRY_AGAIN; /* (take the fallback path) */
+ }
+
+ if (RT_FAILURE(vrc))
+# endif /* RT_OS_LINUX */
+ {
+ /* Use RTSystemQueryOSInfo: */
+ char szTmp[256];
+
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp));
+ if ((RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) && szTmp[0] != '\0')
+ strPlatform.appendPrintf(" [Product: %s", szTmp);
+
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp));
+ if ((RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) && szTmp[0] != '\0')
+ strPlatform.appendPrintf(" %sRelease: %s", strlen(szTmp) == 0 ? "[" : "| ", szTmp);
+
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp));
+ if ((RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) && szTmp[0] != '\0')
+ strPlatform.appendPrintf(" %sVersion: %s", strlen(szTmp) == 0 ? "[" : "| ", szTmp);
+
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(szTmp));
+ if ((RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) && szTmp[0] != '\0')
+ strPlatform.appendPrintf(" %sSP: %s]", strlen(szTmp) == 0 ? "[" : "| ", szTmp);
+
+ if (!strPlatform.endsWith("]"))
+ strPlatform.append("]");
+ }
+
+ LogRel2(("UpdateAgent: Platform is '%s'\n", strPlatform.c_str()));
+
+ return strPlatform;
+}
+
+/**
+ * Returns the proxy mode as a string.
+ *
+ * @returns Proxy mode as string.
+ * @param enmMode Proxy mode to return as string.
+ */
+/* static */
+const char *UpdateAgentBase::i_proxyModeToStr(ProxyMode_T enmMode)
+{
+ switch (enmMode)
+ {
+ case ProxyMode_System: return "System";
+ case ProxyMode_Manual: return "Manual";
+ case ProxyMode_NoProxy: return "None";
+ default: break;
+ }
+
+ AssertFailed();
+ return "<Invalid>";
+}
+
+/**
+ * Returns whether a given URL's scheme is supported or not.
+ *
+ * @returns \c true if scheme is supported, or \c false if not.
+ * @param strUrl URL to check scheme for.
+ *
+ * @note Empty URL are considered as being supported for convenience.
+ */
+bool UpdateAgentBase::i_urlSchemeIsSupported(const Utf8Str &strUrl) const
+{
+ if (strUrl.isEmpty())
+ return true;
+ return strUrl.startsWith("https://", com::Utf8Str::CaseInsensitive);
+}
+
+
+/*********************************************************************************************************************************
+* Update agent class implementation *
+*********************************************************************************************************************************/
+UpdateAgent::UpdateAgent()
+{
+}
+
+UpdateAgent::~UpdateAgent()
+{
+}
+
+HRESULT UpdateAgent::FinalConstruct(void)
+{
+ return BaseFinalConstruct();
+}
+
+void UpdateAgent::FinalRelease(void)
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+HRESULT UpdateAgent::init(VirtualBox *aVirtualBox)
+{
+ /* Weak reference to a VirtualBox object */
+ unconst(m_VirtualBox) = aVirtualBox;
+
+ HRESULT hr = unconst(m_EventSource).createObject();
+ if (SUCCEEDED(hr))
+ hr = m_EventSource->init();
+
+ return hr;
+}
+
+void UpdateAgent::uninit(void)
+{
+ // Enclose the state transition Ready->InUninit->NotReady.
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(m_EventSource).setNull();
+}
+
+HRESULT UpdateAgent::checkFor(ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aProgress);
+
+ return VBOX_E_NOT_SUPPORTED;
+}
+
+HRESULT UpdateAgent::download(ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aProgress);
+
+ return VBOX_E_NOT_SUPPORTED;
+}
+
+HRESULT UpdateAgent::install(ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aProgress);
+
+ return VBOX_E_NOT_SUPPORTED;
+}
+
+HRESULT UpdateAgent::rollback(void)
+{
+ return VBOX_E_NOT_SUPPORTED;
+}
+
+HRESULT UpdateAgent::getName(com::Utf8Str &aName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aName = mData.m_strName;
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ LogFlowThisFuncEnter();
+
+ /* No need to lock - lifetime constant. */
+ m_EventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ LogFlowFuncLeaveRC(S_OK);
+ return S_OK;
+}
+
+HRESULT UpdateAgent::getOrder(ULONG *aOrder)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aOrder = 0; /* 0 means no order / disabled. */
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::getDependsOn(std::vector<com::Utf8Str> &aDeps)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aDeps.resize(0); /* No dependencies by default. */
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::getVersion(com::Utf8Str &aVer)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aVer = mData.m_lastResult.strVer;
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::getDownloadUrl(com::Utf8Str &aUrl)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aUrl = mData.m_lastResult.strDownloadUrl;
+
+ return S_OK;
+}
+
+
+HRESULT UpdateAgent::getWebUrl(com::Utf8Str &aUrl)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aUrl = mData.m_lastResult.strWebUrl;
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::getReleaseNotes(com::Utf8Str &aRelNotes)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aRelNotes = mData.m_lastResult.strReleaseNotes;
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::getEnabled(BOOL *aEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aEnabled = m->fEnabled;
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::setEnabled(const BOOL aEnabled)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->fEnabled = aEnabled;
+
+ return i_commitSettings(alock);
+}
+
+
+HRESULT UpdateAgent::getHidden(BOOL *aHidden)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aHidden = mData.m_fHidden;
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::getState(UpdateState_T *aState)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aState = mData.m_enmState;
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::getCheckFrequency(ULONG *aFreqSeconds)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aFreqSeconds = m->uCheckFreqSeconds;
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::setCheckFrequency(ULONG aFreqSeconds)
+{
+ if (aFreqSeconds < RT_SEC_1DAY) /* Don't allow more frequent checks for now. */
+ return setError(E_INVALIDARG, tr("Frequency too small; one day is the minimum"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->uCheckFreqSeconds = aFreqSeconds;
+
+ return i_commitSettings(alock);
+}
+
+HRESULT UpdateAgent::getChannel(UpdateChannel_T *aChannel)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aChannel = m->enmChannel;
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::setChannel(UpdateChannel_T aChannel)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->enmChannel = aChannel;
+
+ return i_commitSettings(alock);
+}
+
+HRESULT UpdateAgent::getCheckCount(ULONG *aCount)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCount = m->uCheckCount;
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::getRepositoryURL(com::Utf8Str &aRepo)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aRepo = m->strRepoUrl;
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::setRepositoryURL(const com::Utf8Str &aRepo)
+{
+ if (!i_urlSchemeIsSupported(aRepo))
+ return setError(E_INVALIDARG, tr("Invalid URL scheme specified!"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->strRepoUrl = aRepo;
+
+ return i_commitSettings(alock);
+}
+
+HRESULT UpdateAgent::getLastCheckDate(com::Utf8Str &aDate)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aDate = m->strLastCheckDate;
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::getIsCheckNeeded(BOOL *aCheckNeeded)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Is update checking enabled at all?
+ */
+ if (!m->fEnabled)
+ {
+ *aCheckNeeded = FALSE;
+ return S_OK;
+ }
+
+ /*
+ * When was the last update?
+ */
+ if (m->strLastCheckDate.isEmpty()) /* No prior update check performed -- do so now. */
+ {
+ *aCheckNeeded = TRUE;
+ return S_OK;
+ }
+
+ RTTIMESPEC LastCheckTime;
+ if (!RTTimeSpecFromString(&LastCheckTime, Utf8Str(m->strLastCheckDate).c_str()))
+ {
+ *aCheckNeeded = TRUE; /* Invalid date set or error? Perform check. */
+ return S_OK;
+ }
+
+ /*
+ * Compare last update with how often we are supposed to check for updates.
+ */
+ if ( !m->uCheckFreqSeconds /* Paranoia */
+ || m->uCheckFreqSeconds < RT_SEC_1DAY) /* This is the minimum we currently allow. */
+ {
+ /* Consider config (enable, 0 day interval) as checking once but never again.
+ We've already check since we've got a date. */
+ *aCheckNeeded = FALSE;
+ return S_OK;
+ }
+
+ uint64_t const cCheckFreqDays = m->uCheckFreqSeconds / RT_SEC_1DAY_64;
+
+ RTTIMESPEC TimeDiff;
+ RTTimeSpecSub(RTTimeNow(&TimeDiff), &LastCheckTime);
+
+ int64_t const diffLastCheckSecs = RTTimeSpecGetSeconds(&TimeDiff);
+ int64_t const diffLastCheckDays = diffLastCheckSecs / (int64_t)RT_SEC_1DAY_64;
+
+ /* Be as accurate as possible. */
+ *aCheckNeeded = diffLastCheckSecs >= (int64_t)m->uCheckFreqSeconds ? TRUE : FALSE;
+
+ LogRel2(("Update agent (%s): Last update %RI64 days (%RI64 seconds) ago, check frequency is every %RU64 days (%RU64 seconds) -> Check %s\n",
+ mData.m_strName.c_str(), diffLastCheckDays, diffLastCheckSecs, cCheckFreqDays, m->uCheckFreqSeconds,
+ *aCheckNeeded ? "needed" : "not needed"));
+
+ return S_OK;
+}
+
+HRESULT UpdateAgent::getSupportedChannels(std::vector<UpdateChannel_T> &aSupportedChannels)
+{
+ /* No need to take the read lock, as m_enmChannels is const. */
+
+ aSupportedChannels = mData.m_enmChannels;
+
+ return S_OK;
+}
+
+
+/*********************************************************************************************************************************
+* Internal helper methods of update agent class *
+*********************************************************************************************************************************/
+
+/**
+ * Loads the settings of the update agent base class.
+ *
+ * @returns HRESULT
+ * @retval E_INVALIDARG if to-load settings are invalid / not supported.
+ * @param data Where to load the settings from.
+ */
+HRESULT UpdateAgent::i_loadSettings(const settings::UpdateAgent &data)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->fEnabled = data.fEnabled;
+ m->enmChannel = data.enmChannel;
+ m->uCheckFreqSeconds = data.uCheckFreqSeconds;
+ if (data.strRepoUrl.isNotEmpty()) /* Prevent overwriting the agent's default URL when XML settings are empty. */
+ m->strRepoUrl = data.strRepoUrl;
+ m->strLastCheckDate = data.strLastCheckDate;
+ m->uCheckCount = data.uCheckCount;
+
+ /* Sanity checks. */
+ if (!i_urlSchemeIsSupported(data.strRepoUrl))
+ return setError(E_INVALIDARG, tr("Invalid URL scheme specified!"));
+
+ return S_OK;
+}
+
+/**
+ * Saves the settings of the update agent base class.
+ *
+ * @returns HRESULT
+ * @param data Where to save the settings to.
+ */
+HRESULT UpdateAgent::i_saveSettings(settings::UpdateAgent &data)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ data = *m;
+
+ return S_OK;
+}
+
+/**
+ * Sets the update check count.
+ *
+ * @returns HRESULT
+ * @param aCount Update check count to set.
+ */
+HRESULT UpdateAgent::i_setCheckCount(ULONG aCount)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->uCheckCount = aCount;
+
+ return i_commitSettings(alock);
+}
+
+/**
+ * Sets the last update check date.
+ *
+ * @returns HRESULT
+ * @param aDate Last update check date to set.
+ * Must be in ISO 8601 format (e.g. 2020-05-11T21:13:39.348416000Z).
+ */
+HRESULT UpdateAgent::i_setLastCheckDate(const com::Utf8Str &aDate)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->strLastCheckDate = aDate;
+
+ return i_commitSettings(alock);
+}
+
+/**
+ * Internal helper function to commit modified settings.
+ *
+ * @returns HRESULT
+ * @param aLock Write lock to release before committing settings.
+ */
+HRESULT UpdateAgent::i_commitSettings(AutoWriteLock &aLock)
+{
+ aLock.release();
+
+ m_VirtualBox->i_onUpdateAgentSettingsChanged(this, "" /** @todo Include attribute hints */);
+
+ AutoWriteLock vboxLock(m_VirtualBox COMMA_LOCKVAL_SRC_POS);
+ return m_VirtualBox->i_saveSettings();
+}
+
+/**
+ * Returns the proxy mode to use.
+ *
+ * @returns HRESULT
+ * @param aMode Where to return the proxy mode.
+ */
+HRESULT UpdateAgent::i_getProxyMode(ProxyMode_T *aMode)
+{
+ ComPtr<ISystemProperties> pSystemProperties;
+ HRESULT hrc = m_VirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ if (SUCCEEDED(hrc))
+ hrc = pSystemProperties->COMGETTER(ProxyMode)(aMode);
+
+ return hrc;
+}
+
+/**
+ * Returns the proxy URL to use.
+ *
+ * @returns HRESULT
+ * @param aUrl Where to return the proxy URL to use.
+ */
+HRESULT UpdateAgent::i_getProxyURL(com::Utf8Str &aUrl)
+{
+ ComPtr<ISystemProperties> pSystemProperties;
+ HRESULT hrc = m_VirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ com::Bstr bstrVal;
+ hrc = pSystemProperties->COMGETTER(ProxyURL)(bstrVal.asOutParam());
+ if (SUCCEEDED(hrc))
+ aUrl = bstrVal;
+ }
+
+ return hrc;
+}
+
+/**
+ * Configures a HTTP client's proxy.
+ *
+ * @returns HRESULT
+ * @param hHttp HTTP client to configure proxy for.
+ */
+HRESULT UpdateAgent::i_configureProxy(RTHTTP hHttp)
+{
+ HRESULT rc;
+
+ ProxyMode_T enmProxyMode;
+ rc = i_getProxyMode(&enmProxyMode);
+ ComAssertComRCRetRC(rc);
+ Utf8Str strProxyUrl;
+ rc = i_getProxyURL(strProxyUrl);
+ ComAssertComRCRetRC(rc);
+
+ if (enmProxyMode == ProxyMode_Manual)
+ {
+ int vrc = RTHttpSetProxyByUrl(hHttp, strProxyUrl.c_str());
+ if (RT_FAILURE(vrc))
+ return i_reportError(vrc, tr("RTHttpSetProxyByUrl() failed: %Rrc"), vrc);
+ }
+ else if (enmProxyMode == ProxyMode_System)
+ {
+ int vrc = RTHttpUseSystemProxySettings(hHttp);
+ if (RT_FAILURE(vrc))
+ return i_reportError(vrc, tr("RTHttpUseSystemProxySettings() failed: %Rrc"), vrc);
+ }
+ else
+ Assert(enmProxyMode == ProxyMode_NoProxy);
+
+ LogRel2(("Update agent (%s): Using proxy mode = '%s', URL = '%s'\n",
+ mData.m_strName.c_str(), UpdateAgentBase::i_proxyModeToStr(enmProxyMode), strProxyUrl.c_str()));
+
+ return S_OK;
+}
+
+/**
+ * Reports an error by setting the error info and also informs subscribed listeners.
+ *
+ * @returns HRESULT
+ * @param vrc Result code (IPRT-style) to report.
+ * @param pcszMsgFmt Error message to report.
+ * @param ... Format string for \a pcszMsgFmt.
+ */
+HRESULT UpdateAgent::i_reportError(int vrc, const char *pcszMsgFmt, ...)
+{
+ AssertReturn(pcszMsgFmt && *pcszMsgFmt != '\0', E_INVALIDARG);
+
+ va_list va;
+ va_start(va, pcszMsgFmt);
+
+ Utf8Str strMsg;
+ int const vrc2 = strMsg.printfVNoThrow(pcszMsgFmt, va);
+ if (RT_FAILURE(vrc2))
+ {
+ va_end(va);
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc2, tr("Failed to format update agent error string (%Rrc)"), vrc2);
+ }
+
+ va_end(va);
+
+ LogRel(("Update agent (%s): %s\n", mData.m_strName.c_str(), strMsg.c_str()));
+
+ m_VirtualBox->i_onUpdateAgentError(this, strMsg.c_str(), vrc);
+
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, strMsg.c_str());
+}
+
+
+/*********************************************************************************************************************************
+* Host update implementation *
+*********************************************************************************************************************************/
+
+HostUpdateAgent::HostUpdateAgent(void)
+{
+}
+
+HostUpdateAgent::~HostUpdateAgent(void)
+{
+}
+
+
+HRESULT HostUpdateAgent::FinalConstruct(void)
+{
+ return BaseFinalConstruct();
+}
+
+void HostUpdateAgent::FinalRelease(void)
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+HRESULT HostUpdateAgent::init(VirtualBox *aVirtualBox)
+{
+ // Enclose the state transition NotReady->InInit->Ready.
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Initialize the bare minimum to get things going.
+ ** @todo Add more stuff later here. */
+ mData.m_strName = "VirtualBox";
+ mData.m_fHidden = false;
+
+ const UpdateChannel_T aChannels[] =
+ {
+ UpdateChannel_Stable,
+ UpdateChannel_All,
+ UpdateChannel_WithBetas
+ /** @todo Add UpdateChannel_WithTesting once it's implemented on the backend. */
+ };
+ unconst(mData.m_enmChannels).assign(aChannels, aChannels + RT_ELEMENTS(aChannels));
+
+ /* Set default repository. */
+ m->strRepoUrl = "https://update.virtualbox.org";
+
+ HRESULT hr = UpdateAgent::init(aVirtualBox);
+ if (SUCCEEDED(hr))
+ autoInitSpan.setSucceeded();
+
+ return hr;
+}
+
+void HostUpdateAgent::uninit(void)
+{
+ // Enclose the state transition Ready->InUninit->NotReady.
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+}
+
+HRESULT HostUpdateAgent::checkFor(ComPtr<IProgress> &aProgress)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComObjPtr<Progress> pProgress;
+ HRESULT rc = pProgress.createObject();
+ if (FAILED(rc))
+ return rc;
+
+ rc = pProgress->init(m_VirtualBox,
+ static_cast<IUpdateAgent*>(this),
+ tr("Checking for update for %s ...", this->mData.m_strName.c_str()),
+ TRUE /* aCancelable */);
+ if (FAILED(rc))
+ return rc;
+
+ /* initialize the worker task */
+ UpdateAgentTask *pTask = new UpdateAgentTask(this, pProgress);
+ rc = pTask->createThread();
+ pTask = NULL;
+ if (FAILED(rc))
+ return rc;
+
+ return pProgress.queryInterfaceTo(aProgress.asOutParam());
+}
+
+
+/*********************************************************************************************************************************
+* Host update internal functions *
+*********************************************************************************************************************************/
+
+/**
+ * Task callback to perform an update check for the VirtualBox host (core).
+ *
+ * @returns HRESULT
+ * @param pTask Associated update agent task to use.
+ */
+DECLCALLBACK(HRESULT) HostUpdateAgent::i_checkForUpdateTask(UpdateAgentTask *pTask)
+{
+ RT_NOREF(pTask);
+
+ AssertReturn(m->strRepoUrl.isNotEmpty(), E_INVALIDARG);
+
+ // Following the sequence of steps in UIUpdateStepVirtualBox::sltStartStep()
+ // Build up our query URL starting with the configured repository.
+ Utf8Str strUrl;
+ strUrl.appendPrintf("%s/query.php/?", m->strRepoUrl.c_str());
+
+ // Add platform ID.
+ Bstr platform;
+ HRESULT rc = m_VirtualBox->COMGETTER(PackageType)(platform.asOutParam());
+ AssertComRCReturn(rc, rc);
+ strUrl.appendPrintf("platform=%ls", platform.raw()); // e.g. SOLARIS_64BITS_GENERIC
+
+ // Get the complete current version string for the query URL
+ Bstr versionNormalized;
+ rc = m_VirtualBox->COMGETTER(VersionNormalized)(versionNormalized.asOutParam());
+ AssertComRCReturn(rc, rc);
+ strUrl.appendPrintf("&version=%ls", versionNormalized.raw()); // e.g. 6.1.1
+#ifdef DEBUG // Comment out previous line and uncomment this one for testing.
+// strUrl.appendPrintf("&version=6.0.12");
+#endif
+
+ ULONG revision = 0;
+ rc = m_VirtualBox->COMGETTER(Revision)(&revision);
+ AssertComRCReturn(rc, rc);
+ strUrl.appendPrintf("_%u", revision); // e.g. 135618
+
+ // Update the last update check timestamp.
+ RTTIME Time;
+ RTTIMESPEC TimeNow;
+ char szTimeStr[RTTIME_STR_LEN];
+ RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&TimeNow)), szTimeStr, sizeof(szTimeStr));
+ LogRel2(("Update agent (%s): Setting last update check timestamp to '%s'\n", mData.m_strName.c_str(), szTimeStr));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->strLastCheckDate = szTimeStr;
+ m->uCheckCount++;
+
+ rc = i_commitSettings(alock);
+ AssertComRCReturn(rc, rc);
+
+ strUrl.appendPrintf("&count=%RU32", m->uCheckCount);
+
+ // Update the query URL (if necessary) with the 'channel' information.
+ switch (m->enmChannel)
+ {
+ case UpdateChannel_All:
+ strUrl.appendPrintf("&branch=allrelease"); // query.php expects 'allrelease' and not 'allreleases'
+ break;
+ case UpdateChannel_WithBetas:
+ strUrl.appendPrintf("&branch=withbetas");
+ break;
+ /** @todo Handle UpdateChannel_WithTesting once implemented on the backend. */
+ case UpdateChannel_Stable:
+ RT_FALL_THROUGH();
+ default:
+ strUrl.appendPrintf("&branch=stable");
+ break;
+ }
+
+ LogRel2(("Update agent (%s): Using URL '%s'\n", mData.m_strName.c_str(), strUrl.c_str()));
+
+ /*
+ * Compose the User-Agent header for the GET request.
+ */
+ Bstr version;
+ rc = m_VirtualBox->COMGETTER(Version)(version.asOutParam()); // e.g. 6.1.0_RC1
+ AssertComRCReturn(rc, rc);
+
+ Utf8StrFmt const strUserAgent("VirtualBox %ls <%s>", version.raw(), UpdateAgent::i_getPlatformInfo().c_str());
+ LogRel2(("Update agent (%s): Using user agent '%s'\n", mData.m_strName.c_str(), strUserAgent.c_str()));
+
+ /*
+ * Create the HTTP client instance and pass it to a inner worker method to
+ * ensure proper cleanup.
+ */
+ RTHTTP hHttp = NIL_RTHTTP;
+ int vrc = RTHttpCreate(&hHttp);
+ if (RT_SUCCESS(vrc))
+ {
+ try
+ {
+ rc = i_checkForUpdateInner(hHttp, strUrl, strUserAgent);
+ }
+ catch (...)
+ {
+ AssertFailed();
+ rc = E_UNEXPECTED;
+ }
+ RTHttpDestroy(hHttp);
+ }
+ else
+ rc = i_reportError(vrc, tr("RTHttpCreate() failed: %Rrc"), vrc);
+
+ return rc;
+}
+
+/**
+ * Inner function of the actual update checking mechanism.
+ *
+ * @returns HRESULT
+ * @param hHttp HTTP client instance to use for checking.
+ * @param strUrl URL of repository to check.
+ * @param strUserAgent HTTP user agent to use for checking.
+ */
+HRESULT HostUpdateAgent::i_checkForUpdateInner(RTHTTP hHttp, Utf8Str const &strUrl, Utf8Str const &strUserAgent)
+{
+ /*
+ * Configure the proxy (if any).
+ */
+ HRESULT rc = i_configureProxy(hHttp);
+ if (FAILED(rc))
+ return rc;
+
+ /** @todo Are there any other headers needed to be added first via RTHttpSetHeaders()? */
+ int vrc = RTHttpAddHeader(hHttp, "User-Agent", strUserAgent.c_str(), strUserAgent.length(), RTHTTPADDHDR_F_BACK);
+ if (RT_FAILURE(vrc))
+ return i_reportError(vrc, tr("RTHttpAddHeader() failed: %Rrc (user agent)"), vrc);
+
+ /*
+ * Perform the GET request, returning raw binary stuff.
+ */
+ void *pvResponse = NULL;
+ size_t cbResponse = 0;
+ vrc = RTHttpGetBinary(hHttp, strUrl.c_str(), &pvResponse, &cbResponse);
+ if (RT_FAILURE(vrc))
+ return i_reportError(vrc, tr("RTHttpGetBinary() failed: %Rrc"), vrc);
+
+ /* Note! We can do nothing that might throw exceptions till we call RTHttpFreeResponse! */
+
+ /*
+ * If url is platform=DARWIN_64BITS_GENERIC&version=6.0.12&branch=stable for example, the reply is:
+ * 6.0.14<SPACE>https://download.virtualbox.org/virtualbox/6.0.14/VirtualBox-6.0.14-133895-OSX.dmg
+ * If no update required, 'UPTODATE' is returned.
+ */
+ /* Parse out the two first words of the response, ignoring whatever follows: */
+ const char *pchResponse = (const char *)pvResponse;
+ while (cbResponse > 0 && *pchResponse == ' ')
+ cbResponse--, pchResponse++;
+
+ char ch;
+ const char *pchWord0 = pchResponse;
+ while (cbResponse > 0 && (ch = *pchResponse) != ' ' && ch != '\0')
+ cbResponse--, pchResponse++;
+ size_t const cchWord0 = (size_t)(pchResponse - pchWord0);
+
+ while (cbResponse > 0 && *pchResponse == ' ')
+ cbResponse--, pchResponse++;
+ const char *pchWord1 = pchResponse;
+ while (cbResponse > 0 && (ch = *pchResponse) != ' ' && ch != '\0')
+ cbResponse--, pchResponse++;
+ size_t const cchWord1 = (size_t)(pchResponse - pchWord1);
+
+ /* Decode the two word: */
+ static char const s_szUpToDate[] = "UPTODATE";
+ if ( cchWord0 == sizeof(s_szUpToDate) - 1
+ && memcmp(pchWord0, s_szUpToDate, sizeof(s_szUpToDate) - 1) == 0)
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mData.m_enmState = UpdateState_NotAvailable;
+ rc = S_OK;
+
+ alock.release(); /* Release lock before firing off event. */
+
+ m_VirtualBox->i_onUpdateAgentStateChanged(this, UpdateState_NotAvailable);
+ }
+ else
+ {
+ mData.m_enmState = UpdateState_Error; /* Play safe by default. */
+
+ vrc = RTStrValidateEncodingEx(pchWord0, cchWord0, 0 /*fFlags*/);
+ if (RT_SUCCESS(vrc))
+ vrc = RTStrValidateEncodingEx(pchWord1, cchWord1, 0 /*fFlags*/);
+ if (RT_SUCCESS(vrc))
+ {
+ /** @todo Any additional sanity checks we could perform here? */
+ rc = mData.m_lastResult.strVer.assignEx(pchWord0, cchWord0);
+ if (SUCCEEDED(rc))
+ rc = mData.m_lastResult.strDownloadUrl.assignEx(pchWord1, cchWord1);
+
+ if (SUCCEEDED(rc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo Implement this on the backend first.
+ * We also could do some guessing based on the installed version vs. reported update version? */
+ mData.m_lastResult.enmSeverity = UpdateSeverity_Invalid;
+ mData.m_enmState = UpdateState_Available;
+
+ alock.release(); /* Release lock before firing off events. */
+
+ m_VirtualBox->i_onUpdateAgentStateChanged(this, UpdateState_Available);
+ m_VirtualBox->i_onUpdateAgentAvailable(this, mData.m_lastResult.strVer, m->enmChannel,
+ mData.m_lastResult.enmSeverity, mData.m_lastResult.strDownloadUrl,
+ mData.m_lastResult.strWebUrl, mData.m_lastResult.strReleaseNotes);
+ }
+ else
+ rc = i_reportError(VERR_GENERAL_FAILURE /** @todo Use a better rc */,
+ tr("Invalid server response [1]: %Rhrc (%.*Rhxs -- %.*Rhxs)"),
+ rc, cchWord0, pchWord0, cchWord1, pchWord1);
+
+ LogRel2(("Update agent (%s): HTTP server replied: %.*s %.*s\n",
+ mData.m_strName.c_str(), cchWord0, pchWord0, cchWord1, pchWord1));
+ }
+ else
+ rc = i_reportError(vrc, tr("Invalid server response [2]: %Rrc (%.*Rhxs -- %.*Rhxs)"),
+ vrc, cchWord0, pchWord0, cchWord1, pchWord1);
+ }
+
+ RTHttpFreeResponse(pvResponse);
+
+ return rc;
+}
+
diff --git a/src/VBox/Main/src-server/VFSExplorerImpl.cpp b/src/VBox/Main/src-server/VFSExplorerImpl.cpp
new file mode 100644
index 00000000..209bad40
--- /dev/null
+++ b/src/VBox/Main/src-server/VFSExplorerImpl.cpp
@@ -0,0 +1,547 @@
+/* $Id: VFSExplorerImpl.cpp $ */
+/** @file
+ * IVFSExplorer COM class implementations.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_VFSEXPLORER
+#include <iprt/dir.h>
+#include <iprt/path.h>
+#include <iprt/file.h>
+#include <iprt/s3.h>
+#include <iprt/cpp/utils.h>
+
+#include <VBox/com/array.h>
+
+#include <VBox/param.h>
+#include <VBox/version.h>
+
+#include "VFSExplorerImpl.h"
+#include "VirtualBoxImpl.h"
+#include "ProgressImpl.h"
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "ThreadTask.h"
+
+#include <memory>
+
+struct VFSExplorer::Data
+{
+ struct DirEntry
+ {
+ DirEntry(Utf8Str strName, FsObjType_T fileType, RTFOFF cbSize, uint32_t fMode)
+ : name(strName)
+ , type(fileType)
+ , size(cbSize)
+ , mode(fMode) {}
+
+ Utf8Str name;
+ FsObjType_T type;
+ RTFOFF size;
+ uint32_t mode;
+ };
+
+ VFSType_T storageType;
+ Utf8Str strUsername;
+ Utf8Str strPassword;
+ Utf8Str strHostname;
+ Utf8Str strPath;
+ Utf8Str strBucket;
+ std::list<DirEntry> entryList;
+};
+
+
+VFSExplorer::VFSExplorer()
+ : mVirtualBox(NULL)
+{
+}
+
+VFSExplorer::~VFSExplorer()
+{
+}
+
+
+/**
+ * VFSExplorer COM initializer.
+ * @param aType VFS type.
+ * @param aFilePath File path.
+ * @param aHostname Host name.
+ * @param aUsername User name.
+ * @param aPassword Password.
+ * @param aVirtualBox VirtualBox object.
+ * @return
+ */
+HRESULT VFSExplorer::init(VFSType_T aType, Utf8Str aFilePath, Utf8Str aHostname, Utf8Str aUsername,
+ Utf8Str aPassword, VirtualBox *aVirtualBox)
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Weak reference to a VirtualBox object */
+ unconst(mVirtualBox) = aVirtualBox;
+
+ /* initialize data */
+ m = new Data;
+
+ m->storageType = aType;
+ m->strPath = aFilePath;
+ m->strHostname = aHostname;
+ m->strUsername = aUsername;
+ m->strPassword = aPassword;
+
+ if (m->storageType == VFSType_S3)
+ {
+ size_t bpos = aFilePath.find("/", 1);
+ if (bpos != Utf8Str::npos)
+ {
+ m->strBucket = aFilePath.substr(1, bpos - 1); /* The bucket without any slashes */
+ aFilePath = aFilePath.substr(bpos); /* The rest of the file path */
+ }
+ }
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * VFSExplorer COM uninitializer.
+ * @return
+ */
+void VFSExplorer::uninit()
+{
+ delete m;
+ m = NULL;
+}
+
+/**
+ * Public method implementation.
+ * @param aPath Where to store the path.
+ * @return
+ */
+HRESULT VFSExplorer::getPath(com::Utf8Str &aPath)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aPath = m->strPath;
+
+ return S_OK;
+}
+
+
+HRESULT VFSExplorer::getType(VFSType_T *aType)
+{
+ if (!aType)
+ return E_POINTER;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aType = m->storageType;
+
+ return S_OK;
+}
+
+class VFSExplorer::TaskVFSExplorer : public ThreadTask
+{
+public:
+ enum TaskType
+ {
+ Update,
+ Delete
+ };
+
+ TaskVFSExplorer(TaskType aTaskType, VFSExplorer *aThat, Progress *aProgress)
+ : m_taskType(aTaskType),
+ m_pVFSExplorer(aThat),
+ m_ptrProgress(aProgress),
+ m_rc(S_OK)
+ {
+ m_strTaskName = "Explorer::Task";
+ }
+ ~TaskVFSExplorer() {}
+
+private:
+ void handler();
+
+#if 0 /* unused */
+ static DECLCALLBACK(int) uploadProgress(unsigned uPercent, void *pvUser);
+#endif
+
+ TaskType m_taskType;
+ VFSExplorer *m_pVFSExplorer;
+
+ ComObjPtr<Progress> m_ptrProgress;
+ HRESULT m_rc;
+
+ /* task data */
+ std::list<Utf8Str> m_lstFilenames;
+
+ friend class VFSExplorer;
+};
+
+/* static */
+void VFSExplorer::TaskVFSExplorer::handler()
+{
+ VFSExplorer *pVFSExplorer = this->m_pVFSExplorer;
+
+ LogFlowFuncEnter();
+ LogFlowFunc(("VFSExplorer %p\n", pVFSExplorer));
+
+ HRESULT rc = S_OK;
+
+ switch (this->m_taskType)
+ {
+ case TaskVFSExplorer::Update:
+ {
+ if (pVFSExplorer->m->storageType == VFSType_File)
+ rc = pVFSExplorer->i_updateFS(this);
+ else if (pVFSExplorer->m->storageType == VFSType_S3)
+ rc = E_NOTIMPL;
+ break;
+ }
+ case TaskVFSExplorer::Delete:
+ {
+ if (pVFSExplorer->m->storageType == VFSType_File)
+ rc = pVFSExplorer->i_deleteFS(this);
+ else if (pVFSExplorer->m->storageType == VFSType_S3)
+ rc = E_NOTIMPL;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid task type %u specified!\n", this->m_taskType));
+ break;
+ }
+
+ LogFlowFunc(("rc=%Rhrc\n", rc)); NOREF(rc);
+ LogFlowFuncLeave();
+}
+
+#if 0 /* unused */
+/* static */
+DECLCALLBACK(int) VFSExplorer::TaskVFSExplorer::uploadProgress(unsigned uPercent, void *pvUser)
+{
+ VFSExplorer::TaskVFSExplorer* pTask = *(VFSExplorer::TaskVFSExplorer**)pvUser;
+
+ if (pTask &&
+ !pTask->m_ptrProgress.isNull())
+ {
+ BOOL fCanceled;
+ pTask->m_ptrProgress->COMGETTER(Canceled)(&fCanceled);
+ if (fCanceled)
+ return -1;
+ pTask->m_ptrProgress->SetCurrentOperationProgress(uPercent);
+ }
+ return VINF_SUCCESS;
+}
+#endif
+
+FsObjType_T VFSExplorer::i_iprtToVfsObjType(RTFMODE aType) const
+{
+ switch (aType & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY: return FsObjType_Directory;
+ case RTFS_TYPE_FILE: return FsObjType_File;
+ case RTFS_TYPE_SYMLINK: return FsObjType_Symlink;
+ case RTFS_TYPE_FIFO: return FsObjType_Fifo;
+ case RTFS_TYPE_DEV_CHAR: return FsObjType_DevChar;
+ case RTFS_TYPE_DEV_BLOCK: return FsObjType_DevBlock;
+ case RTFS_TYPE_SOCKET: return FsObjType_Socket;
+ case RTFS_TYPE_WHITEOUT: return FsObjType_WhiteOut;
+ default: return FsObjType_Unknown;
+ }
+}
+
+HRESULT VFSExplorer::i_updateFS(TaskVFSExplorer *aTask)
+{
+ LogFlowFuncEnter();
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ std::list<VFSExplorer::Data::DirEntry> fileList;
+ RTDIR hDir;
+ int vrc = RTDirOpen(&hDir, m->strPath.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ try
+ {
+ if (aTask->m_ptrProgress)
+ aTask->m_ptrProgress->SetCurrentOperationProgress(33);
+ RTDIRENTRYEX entry;
+ while (RT_SUCCESS(vrc))
+ {
+ vrc = RTDirReadEx(hDir, &entry, NULL, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(vrc))
+ {
+ Utf8Str name(entry.szName);
+ if ( name != "."
+ && name != "..")
+ fileList.push_back(VFSExplorer::Data::DirEntry(name, i_iprtToVfsObjType(entry.Info.Attr.fMode),
+ entry.Info.cbObject,
+ entry.Info.Attr.fMode
+ & (RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG | RTFS_UNIX_IRWXO)));
+ }
+ }
+ if (aTask->m_ptrProgress)
+ aTask->m_ptrProgress->SetCurrentOperationProgress(66);
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ /* Clean up */
+ RTDirClose(hDir);
+ }
+ else
+ rc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr ("Can't open directory '%s' (%Rrc)"), m->strPath.c_str(), vrc);
+
+ if (aTask->m_ptrProgress)
+ aTask->m_ptrProgress->SetCurrentOperationProgress(99);
+
+ /* Assign the result on success (this clears the old list) */
+ if (rc == S_OK)
+ m->entryList.assign(fileList.begin(), fileList.end());
+
+ aTask->m_rc = rc;
+
+ if (!aTask->m_ptrProgress.isNull())
+ aTask->m_ptrProgress->i_notifyComplete(rc);
+
+ LogFlowFunc(("rc=%Rhrc\n", rc));
+ LogFlowFuncLeave();
+
+ return S_OK; /** @todo ??? */
+}
+
+HRESULT VFSExplorer::i_deleteFS(TaskVFSExplorer *aTask)
+{
+ LogFlowFuncEnter();
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ float fPercentStep = 100.0f / (float)aTask->m_lstFilenames.size();
+ try
+ {
+ char szPath[RTPATH_MAX];
+ std::list<Utf8Str>::const_iterator it;
+ size_t i = 0;
+ for (it = aTask->m_lstFilenames.begin();
+ it != aTask->m_lstFilenames.end();
+ ++it, ++i)
+ {
+ int vrc = RTPathJoin(szPath, sizeof(szPath), m->strPath.c_str(), (*it).c_str());
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(E_FAIL, vrc, tr("Internal Error (%Rrc)"), vrc);
+ vrc = RTFileDelete(szPath);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Can't delete file '%s' (%Rrc)"), szPath, vrc);
+ if (aTask->m_ptrProgress)
+ aTask->m_ptrProgress->SetCurrentOperationProgress((ULONG)(fPercentStep * (float)i));
+ }
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ aTask->m_rc = rc;
+
+ if (aTask->m_ptrProgress.isNotNull())
+ aTask->m_ptrProgress->i_notifyComplete(rc);
+
+ LogFlowFunc(("rc=%Rhrc\n", rc));
+ LogFlowFuncLeave();
+
+ return VINF_SUCCESS;
+}
+
+HRESULT VFSExplorer::update(ComPtr<IProgress> &aProgress)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ ComObjPtr<Progress> progress;
+ try
+ {
+ Bstr progressDesc = BstrFmt(tr("Update directory info for '%s'"),
+ m->strPath.c_str());
+ /* Create the progress object */
+ progress.createObject();
+
+ rc = progress->init(mVirtualBox,
+ static_cast<IVFSExplorer*>(this),
+ progressDesc.raw(),
+ TRUE /* aCancelable */);
+ if (FAILED(rc)) throw rc;
+
+ /* Initialize our worker task */
+ TaskVFSExplorer* pTask = new TaskVFSExplorer(TaskVFSExplorer::Update, this, progress);
+
+ //this function delete task in case of exceptions, so there is no need in the call of delete operator
+ rc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER);
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ if (SUCCEEDED(rc))
+ /* Return progress to the caller */
+ progress.queryInterfaceTo(aProgress.asOutParam());
+
+ return rc;
+}
+
+HRESULT VFSExplorer::cd(const com::Utf8Str &aDir, ComPtr<IProgress> &aProgress)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->strPath = aDir;
+ return update(aProgress);
+}
+
+HRESULT VFSExplorer::cdUp(ComPtr<IProgress> &aProgress)
+{
+ Utf8Str strUpPath;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ /* Remove lowest dir entry in a platform neutral way. */
+ char *pszNewPath = RTStrDup(m->strPath.c_str());
+ RTPathStripTrailingSlash(pszNewPath);
+ RTPathStripFilename(pszNewPath);
+ strUpPath = pszNewPath;
+ RTStrFree(pszNewPath);
+ }
+
+ return cd(strUpPath, aProgress);
+}
+
+HRESULT VFSExplorer::entryList(std::vector<com::Utf8Str> &aNames,
+ std::vector<ULONG> &aTypes,
+ std::vector<LONG64> &aSizes,
+ std::vector<ULONG> &aModes)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aNames.resize(m->entryList.size());
+ aTypes.resize(m->entryList.size());
+ aSizes.resize(m->entryList.size());
+ aModes.resize(m->entryList.size());
+
+ std::list<VFSExplorer::Data::DirEntry>::const_iterator it;
+ size_t i = 0;
+ for (it = m->entryList.begin();
+ it != m->entryList.end();
+ ++it, ++i)
+ {
+ const VFSExplorer::Data::DirEntry &entry = (*it);
+ aNames[i] = entry.name;
+ aTypes[i] = entry.type;
+ aSizes[i] = entry.size;
+ aModes[i] = entry.mode;
+ }
+
+ return S_OK;
+}
+
+HRESULT VFSExplorer::exists(const std::vector<com::Utf8Str> &aNames,
+ std::vector<com::Utf8Str> &aExists)
+{
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aExists.resize(0);
+ for (size_t i=0; i < aNames.size(); ++i)
+ {
+ std::list<VFSExplorer::Data::DirEntry>::const_iterator it;
+ for (it = m->entryList.begin();
+ it != m->entryList.end();
+ ++it)
+ {
+ const VFSExplorer::Data::DirEntry &entry = (*it);
+ if (entry.name == RTPathFilename(aNames[i].c_str()))
+ aExists.push_back(aNames[i]);
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT VFSExplorer::remove(const std::vector<com::Utf8Str> &aNames,
+ ComPtr<IProgress> &aProgress)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ ComObjPtr<Progress> progress;
+ try
+ {
+ /* Create the progress object */
+ progress.createObject();
+
+ rc = progress->init(mVirtualBox, static_cast<IVFSExplorer*>(this),
+ Bstr(tr("Delete files")).raw(),
+ TRUE /* aCancelable */);
+ if (FAILED(rc)) throw rc;
+
+ /* Initialize our worker task */
+ TaskVFSExplorer* pTask = new TaskVFSExplorer(TaskVFSExplorer::Delete, this, progress);
+
+ /* Add all filenames to delete as task data */
+ for (size_t i = 0; i < aNames.size(); ++i)
+ pTask->m_lstFilenames.push_back(aNames[i]);
+
+ //this function delete task in case of exceptions, so there is no need in the call of delete operator
+ rc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER);
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+
+ if (SUCCEEDED(rc))
+ /* Return progress to the caller */
+ progress.queryInterfaceTo(aProgress.asOutParam());
+
+ return rc;
+}
+
diff --git a/src/VBox/Main/src-server/VRDEServerImpl.cpp b/src/VBox/Main/src-server/VRDEServerImpl.cpp
new file mode 100644
index 00000000..9c21f7f0
--- /dev/null
+++ b/src/VBox/Main/src-server/VRDEServerImpl.cpp
@@ -0,0 +1,975 @@
+/* $Id: VRDEServerImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_VRDESERVER
+#include "VRDEServerImpl.h"
+#include "MachineImpl.h"
+#include "VirtualBoxImpl.h"
+#ifdef VBOX_WITH_EXTPACK
+# include "ExtPackManagerImpl.h"
+#endif
+
+#include <iprt/cpp/utils.h>
+#include <iprt/ctype.h>
+#include <iprt/ldr.h>
+#include <iprt/path.h>
+
+#include <VBox/err.h>
+#include <VBox/sup.h>
+#include <VBox/com/array.h>
+
+#include <VBox/RemoteDesktop/VRDE.h>
+
+#include "AutoStateDep.h"
+#include "AutoCaller.h"
+#include "Global.h"
+#include "LoggingNew.h"
+
+// defines
+/////////////////////////////////////////////////////////////////////////////
+#define VRDP_DEFAULT_PORT_STR "3389"
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+VRDEServer::VRDEServer()
+ : mParent(NULL)
+{
+}
+
+VRDEServer::~VRDEServer()
+{
+}
+
+HRESULT VRDEServer::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void VRDEServer::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the VRDP server object.
+ *
+ * @param aParent Handle of the parent object.
+ */
+HRESULT VRDEServer::init(Machine *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+ /* mPeer is left null */
+
+ mData.allocate();
+
+ mData->fEnabled = false;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the object given another object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT VRDEServer::init(Machine *aParent, VRDEServer *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+ unconst(mPeer) = aThat;
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ mData.share(aThat->mData);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the guest object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ *
+ * @note Locks @a aThat object for reading.
+ */
+HRESULT VRDEServer::initCopy(Machine *aParent, VRDEServer *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+ /* mPeer is left null */
+
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnRC(thatCaller.rc());
+
+ AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);
+ mData.attachCopy(aThat->mData);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void VRDEServer::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ mData.free();
+
+ unconst(mPeer) = NULL;
+ unconst(mParent) = NULL;
+}
+
+/**
+ * Loads settings from the given machine node.
+ * May be called once right after this object creation.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT VRDEServer::i_loadSettings(const settings::VRDESettings &data)
+{
+ using namespace settings;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.assignCopy(&data);
+
+ return S_OK;
+}
+
+/**
+ * Saves settings to the given machine node.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for reading.
+ */
+HRESULT VRDEServer::i_saveSettings(settings::VRDESettings &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ data = *mData.data();
+
+ return S_OK;
+}
+
+// IVRDEServer properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT VRDEServer::getEnabled(BOOL *aEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aEnabled = mData->fEnabled;
+
+ return S_OK;
+}
+
+HRESULT VRDEServer::setEnabled(BOOL aEnabled)
+{
+ /* the machine can also be in saved state for this property to change */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ if (mData->fEnabled != RT_BOOL(aEnabled))
+ {
+ mData.backup();
+ mData->fEnabled = RT_BOOL(aEnabled);
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ mParent->i_setModified(Machine::IsModified_VRDEServer);
+ mlock.release();
+
+ /* Avoid deadlock when i_onVRDEServerChange eventually calls SetExtraData. */
+ adep.release();
+
+ rc = mParent->i_onVRDEServerChange(/* aRestart */ TRUE);
+ if (FAILED(rc))
+ {
+ /* Failed to enable/disable the server. Revert the internal state. */
+ adep.add();
+ if (SUCCEEDED(adep.rc()))
+ {
+ alock.acquire();
+ mData->fEnabled = !RT_BOOL(aEnabled);
+ alock.release();
+ mlock.acquire();
+ mParent->i_setModified(Machine::IsModified_VRDEServer);
+ }
+ }
+ }
+
+ return rc;
+}
+
+static int i_portParseNumber(uint16_t *pu16Port, const char *pszStart, const char *pszEnd)
+{
+ /* Gets a string of digits, converts to 16 bit port number.
+ * Note: pszStart <= pszEnd is expected, the string contains
+ * only digits and pszEnd points to the char after last
+ * digit.
+ */
+ size_t cch = (size_t)(pszEnd - pszStart);
+ if (cch > 0 && cch <= 5) /* Port is up to 5 decimal digits. */
+ {
+ unsigned uPort = 0;
+ while (pszStart != pszEnd)
+ {
+ uPort = uPort * 10 + (unsigned)(*pszStart - '0');
+ pszStart++;
+ }
+
+ if (uPort != 0 && uPort < 0x10000)
+ {
+ if (pu16Port)
+ *pu16Port = (uint16_t)uPort;
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_INVALID_PARAMETER;
+}
+
+static int i_vrdpServerVerifyPortsString(const com::Utf8Str &aPortRange)
+{
+ const char *pszPortRange = aPortRange.c_str();
+
+ if (!pszPortRange || *pszPortRange == 0) /* Reject empty string. */
+ return VERR_INVALID_PARAMETER;
+
+ /* The string should be like "1000-1010,1020,2000-2003" */
+ while (*pszPortRange)
+ {
+ const char *pszStart = pszPortRange;
+ const char *pszDash = NULL;
+ const char *pszEnd = pszStart;
+
+ while (*pszEnd && *pszEnd != ',')
+ {
+ if (*pszEnd == '-')
+ {
+ if (pszDash != NULL)
+ return VERR_INVALID_PARAMETER; /* More than one '-'. */
+
+ pszDash = pszEnd;
+ }
+ else if (!RT_C_IS_DIGIT(*pszEnd))
+ return VERR_INVALID_PARAMETER;
+
+ pszEnd++;
+ }
+
+ /* Update the next range pointer. */
+ pszPortRange = pszEnd;
+ if (*pszPortRange == ',')
+ {
+ pszPortRange++;
+ }
+
+ /* A probably valid range. Verify and parse it. */
+ int rc;
+ if (pszDash)
+ {
+ rc = i_portParseNumber(NULL, pszStart, pszDash);
+ if (RT_SUCCESS(rc))
+ rc = i_portParseNumber(NULL, pszDash + 1, pszEnd);
+ }
+ else
+ rc = i_portParseNumber(NULL, pszStart, pszEnd);
+
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+HRESULT VRDEServer::setVRDEProperty(const com::Utf8Str &aKey, const com::Utf8Str &aValue)
+{
+ LogFlowThisFunc(("\n"));
+
+ /* the machine can also be in saved state for this property to change */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Special processing for some "standard" properties. */
+ if (aKey == "TCP/Ports")
+ {
+ /* Verify the string. "0" means the default port. */
+ Utf8Str strPorts = aValue == "0"?
+ VRDP_DEFAULT_PORT_STR:
+ aValue;
+ int vrc = i_vrdpServerVerifyPortsString(strPorts);
+ if (RT_FAILURE(vrc))
+ return E_INVALIDARG;
+
+ if (strPorts != mData->mapProperties["TCP/Ports"])
+ {
+ /* Port value is not verified here because it is up to VRDP transport to
+ * use it. Specifying a wrong port number will cause a running server to
+ * stop. There is no fool proof here.
+ */
+ mData.backup();
+ mData->mapProperties["TCP/Ports"] = strPorts;
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ mParent->i_setModified(Machine::IsModified_VRDEServer);
+ mlock.release();
+
+ /* Avoid deadlock when i_onVRDEServerChange eventually calls SetExtraData. */
+ adep.release();
+
+ mParent->i_onVRDEServerChange(/* aRestart */ TRUE);
+ }
+ }
+ else
+ {
+ /* Generic properties processing.
+ * Look up the old value first; if nothing's changed then do nothing.
+ */
+ Utf8Str strOldValue;
+
+ settings::StringsMap::const_iterator it = mData->mapProperties.find(aKey);
+ if (it != mData->mapProperties.end())
+ strOldValue = it->second;
+
+ if (strOldValue != aValue)
+ {
+ if (aValue.isEmpty())
+ mData->mapProperties.erase(aKey);
+ else
+ mData->mapProperties[aKey] = aValue;
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS);
+ mParent->i_setModified(Machine::IsModified_VRDEServer);
+ mlock.release();
+
+ /* Avoid deadlock when i_onVRDEServerChange eventually calls SetExtraData. */
+ adep.release();
+
+ mParent->i_onVRDEServerChange(/* aRestart */ TRUE);
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT VRDEServer::getVRDEProperty(const com::Utf8Str &aKey, com::Utf8Str &aValue)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ settings::StringsMap::const_iterator it = mData->mapProperties.find(aKey);
+ if (it != mData->mapProperties.end())
+ aValue = it->second; // source is a Utf8Str
+ else if (aKey == "TCP/Ports")
+ aValue = VRDP_DEFAULT_PORT_STR;
+
+ return S_OK;
+}
+
+/*
+ * Work around clang being unhappy about PFNVRDESUPPORTEDPROPERTIES
+ * ("exception specifications are not allowed beyond a single level of
+ * indirection"). The original comment for 13.0 check said: "assuming
+ * this issue will be fixed eventually". Well, 13.0 is now out, and
+ * it was not.
+ */
+#define CLANG_EXCEPTION_SPEC_HACK (RT_CLANG_PREREQ(11, 0) /* && !RT_CLANG_PREREQ(13, 0) */)
+
+#if CLANG_EXCEPTION_SPEC_HACK
+static int loadVRDELibrary(const char *pszLibraryName, RTLDRMOD *phmod, void *ppfn)
+#else
+static int loadVRDELibrary(const char *pszLibraryName, RTLDRMOD *phmod, PFNVRDESUPPORTEDPROPERTIES *ppfn)
+#endif
+{
+ int rc = VINF_SUCCESS;
+
+ RTLDRMOD hmod = NIL_RTLDRMOD;
+
+ RTERRINFOSTATIC ErrInfo;
+ RTErrInfoInitStatic(&ErrInfo);
+ if (RTPathHavePath(pszLibraryName))
+ rc = SUPR3HardenedLdrLoadPlugIn(pszLibraryName, &hmod, &ErrInfo.Core);
+ else
+ rc = SUPR3HardenedLdrLoadAppPriv(pszLibraryName, &hmod, RTLDRLOAD_FLAGS_LOCAL, &ErrInfo.Core);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hmod, "VRDESupportedProperties", (void **)ppfn);
+
+ if (RT_FAILURE(rc) && rc != VERR_SYMBOL_NOT_FOUND)
+ LogRel(("VRDE: Error resolving symbol '%s', rc %Rrc.\n", "VRDESupportedProperties", rc));
+ }
+ else
+ {
+ if (RTErrInfoIsSet(&ErrInfo.Core))
+ LogRel(("VRDE: Error loading the library '%s': %s (%Rrc)\n", pszLibraryName, ErrInfo.Core.pszMsg, rc));
+ else
+ LogRel(("VRDE: Error loading the library '%s' rc = %Rrc.\n", pszLibraryName, rc));
+
+ hmod = NIL_RTLDRMOD;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *phmod = hmod;
+ }
+ else
+ {
+ if (hmod != NIL_RTLDRMOD)
+ {
+ RTLdrClose(hmod);
+ hmod = NIL_RTLDRMOD;
+ }
+ }
+
+ return rc;
+}
+
+HRESULT VRDEServer::getVRDEProperties(std::vector<com::Utf8Str> &aProperties)
+{
+ size_t cProperties = 0;
+ aProperties.resize(0);
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (!mData->fEnabled)
+ {
+ return S_OK;
+ }
+ alock.release();
+
+ /*
+ * Check that a VRDE extension pack name is set and resolve it into a
+ * library path.
+ */
+ Bstr bstrExtPack;
+ HRESULT hrc = COMGETTER(VRDEExtPack)(bstrExtPack.asOutParam());
+ Log(("VRDEPROP: get extpack hrc 0x%08X, isEmpty %d\n", hrc, bstrExtPack.isEmpty()));
+ if (FAILED(hrc))
+ return hrc;
+ if (bstrExtPack.isEmpty())
+ return E_FAIL;
+
+ Utf8Str strExtPack(bstrExtPack);
+ Utf8Str strVrdeLibrary;
+ int vrc = VINF_SUCCESS;
+ if (strExtPack.equals(VBOXVRDP_KLUDGE_EXTPACK_NAME))
+ strVrdeLibrary = "VBoxVRDP";
+ else
+ {
+#ifdef VBOX_WITH_EXTPACK
+ VirtualBox *pVirtualBox = mParent->i_getVirtualBox();
+ ExtPackManager *pExtPackMgr = pVirtualBox->i_getExtPackManager();
+ vrc = pExtPackMgr->i_getVrdeLibraryPathForExtPack(&strExtPack, &strVrdeLibrary);
+#else
+ vrc = VERR_FILE_NOT_FOUND;
+#endif
+ }
+ Log(("VRDEPROP: library get rc %Rrc\n", vrc));
+
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Load the VRDE library and start the server, if it is enabled.
+ */
+ PFNVRDESUPPORTEDPROPERTIES pfn = NULL;
+ RTLDRMOD hmod = NIL_RTLDRMOD;
+#if CLANG_EXCEPTION_SPEC_HACK
+ vrc = loadVRDELibrary(strVrdeLibrary.c_str(), &hmod, (void **)&pfn);
+#else
+ vrc = loadVRDELibrary(strVrdeLibrary.c_str(), &hmod, &pfn);
+#endif
+ Log(("VRDEPROP: load library [%s] rc %Rrc\n", strVrdeLibrary.c_str(), vrc));
+ if (RT_SUCCESS(vrc))
+ {
+ const char * const *papszNames = pfn();
+
+ if (papszNames)
+ {
+ size_t i;
+ for (i = 0; papszNames[i] != NULL; ++i)
+ {
+ cProperties++;
+ }
+ }
+ Log(("VRDEPROP: %d properties\n", cProperties));
+
+ if (cProperties > 0)
+ {
+ aProperties.resize(cProperties);
+ for (size_t i = 0; i < cProperties && papszNames[i] != NULL; ++i)
+ {
+ aProperties[i] = papszNames[i];
+ }
+ }
+
+ /* Do not forget to unload the library. */
+ RTLdrClose(hmod);
+ hmod = NIL_RTLDRMOD;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+
+HRESULT VRDEServer::getAuthType(AuthType_T *aType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aType = mData->authType;
+
+ return S_OK;
+}
+
+HRESULT VRDEServer::setAuthType(AuthType_T aType)
+{
+ /* the machine can also be in saved state for this property to change */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->authType != aType)
+ {
+ mData.backup();
+ mData->authType = aType;
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ mParent->i_setModified(Machine::IsModified_VRDEServer);
+ mlock.release();
+
+ mParent->i_onVRDEServerChange(/* aRestart */ TRUE);
+ }
+
+ return S_OK;
+}
+
+HRESULT VRDEServer::getAuthTimeout(ULONG *aTimeout)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aTimeout = mData->ulAuthTimeout;
+
+ return S_OK;
+}
+
+
+HRESULT VRDEServer::setAuthTimeout(ULONG aTimeout)
+{
+ /* the machine can also be in saved state for this property to change */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aTimeout != mData->ulAuthTimeout)
+ {
+ mData.backup();
+ mData->ulAuthTimeout = aTimeout;
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ mParent->i_setModified(Machine::IsModified_VRDEServer);
+ mlock.release();
+
+ /* sunlover 20060131: This setter does not require the notification
+ * really */
+#if 0
+ mParent->onVRDEServerChange();
+#endif
+ }
+
+ return S_OK;
+}
+
+HRESULT VRDEServer::getAuthLibrary(com::Utf8Str &aLibrary)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aLibrary = mData->strAuthLibrary;
+ alock.release();
+
+ if (aLibrary.isEmpty())
+ {
+ /* Get the global setting. */
+ ComPtr<ISystemProperties> systemProperties;
+ HRESULT hrc = mParent->i_getVirtualBox()->COMGETTER(SystemProperties)(systemProperties.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Bstr strlib;
+ hrc = systemProperties->COMGETTER(VRDEAuthLibrary)(strlib.asOutParam());
+ if (SUCCEEDED(hrc))
+ aLibrary = Utf8Str(strlib).c_str();
+ }
+
+ if (FAILED(hrc))
+ return setError(hrc, tr("failed to query the library setting\n"));
+ }
+
+ return S_OK;
+}
+
+
+HRESULT VRDEServer::setAuthLibrary(const com::Utf8Str &aLibrary)
+{
+ /* the machine can also be in saved state for this property to change */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->strAuthLibrary != aLibrary)
+ {
+ mData.backup();
+ mData->strAuthLibrary = aLibrary;
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS);
+ mParent->i_setModified(Machine::IsModified_VRDEServer);
+ mlock.release();
+
+ mParent->i_onVRDEServerChange(/* aRestart */ TRUE);
+ }
+
+ return S_OK;
+}
+
+
+HRESULT VRDEServer::getAllowMultiConnection(BOOL *aAllowMultiConnection)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAllowMultiConnection = mData->fAllowMultiConnection;
+
+ return S_OK;
+}
+
+
+HRESULT VRDEServer::setAllowMultiConnection(BOOL aAllowMultiConnection)
+{
+ /* the machine can also be in saved state for this property to change */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->fAllowMultiConnection != RT_BOOL(aAllowMultiConnection))
+ {
+ mData.backup();
+ mData->fAllowMultiConnection = RT_BOOL(aAllowMultiConnection);
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ mParent->i_setModified(Machine::IsModified_VRDEServer);
+ mlock.release();
+
+ mParent->i_onVRDEServerChange(/* aRestart */ TRUE); /// @todo does it need a restart?
+ }
+
+ return S_OK;
+}
+
+HRESULT VRDEServer::getReuseSingleConnection(BOOL *aReuseSingleConnection)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aReuseSingleConnection = mData->fReuseSingleConnection;
+
+ return S_OK;
+}
+
+
+HRESULT VRDEServer::setReuseSingleConnection(BOOL aReuseSingleConnection)
+{
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ if (FAILED(adep.rc())) return adep.rc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData->fReuseSingleConnection != RT_BOOL(aReuseSingleConnection))
+ {
+ mData.backup();
+ mData->fReuseSingleConnection = RT_BOOL(aReuseSingleConnection);
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS); // mParent is const, needs no locking
+ mParent->i_setModified(Machine::IsModified_VRDEServer);
+ mlock.release();
+
+ mParent->i_onVRDEServerChange(/* aRestart */ TRUE); /// @todo needs a restart?
+ }
+
+ return S_OK;
+}
+
+HRESULT VRDEServer::getVRDEExtPack(com::Utf8Str &aExtPack)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Utf8Str strExtPack = mData->strVrdeExtPack;
+ alock.release();
+ HRESULT hrc = S_OK;
+
+ if (strExtPack.isNotEmpty())
+ {
+ if (strExtPack.equals(VBOXVRDP_KLUDGE_EXTPACK_NAME))
+ hrc = S_OK;
+ else
+ {
+#ifdef VBOX_WITH_EXTPACK
+ ExtPackManager *pExtPackMgr = mParent->i_getVirtualBox()->i_getExtPackManager();
+ hrc = pExtPackMgr->i_checkVrdeExtPack(&strExtPack);
+#else
+ hrc = setError(E_FAIL, tr("Extension pack '%s' does not exist"), strExtPack.c_str());
+#endif
+ }
+ if (SUCCEEDED(hrc))
+ aExtPack = strExtPack;
+ }
+ else
+ {
+ /* Get the global setting. */
+ ComPtr<ISystemProperties> systemProperties;
+ hrc = mParent->i_getVirtualBox()->COMGETTER(SystemProperties)(systemProperties.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Bstr bstr;
+ hrc = systemProperties->COMGETTER(DefaultVRDEExtPack)(bstr.asOutParam());
+ if (SUCCEEDED(hrc))
+ aExtPack = bstr;
+ }
+ }
+ return hrc;
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+HRESULT VRDEServer::setVRDEExtPack(const com::Utf8Str &aExtPack)
+{
+ HRESULT hrc = S_OK;
+ /* the machine can also be in saved state for this property to change */
+ AutoMutableOrSavedOrRunningStateDependency adep(mParent);
+ hrc = adep.rc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * If not empty, check the specific extension pack.
+ */
+ if (!aExtPack.isEmpty())
+ {
+ if (aExtPack.equals(VBOXVRDP_KLUDGE_EXTPACK_NAME))
+ hrc = S_OK;
+ else
+ {
+#ifdef VBOX_WITH_EXTPACK
+ ExtPackManager *pExtPackMgr = mParent->i_getVirtualBox()->i_getExtPackManager();
+ hrc = pExtPackMgr->i_checkVrdeExtPack(&aExtPack);
+#else
+ hrc = setError(E_FAIL, tr("Extension pack '%s' does not exist"), aExtPack.c_str());
+#endif
+ }
+ }
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Update the setting if there is an actual change, post an
+ * change event to trigger a VRDE server restart.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aExtPack != mData->strVrdeExtPack)
+ {
+ mData.backup();
+ mData->strVrdeExtPack = aExtPack;
+
+ /* leave the lock before informing callbacks */
+ alock.release();
+
+ AutoWriteLock mlock(mParent COMMA_LOCKVAL_SRC_POS);
+ mParent->i_setModified(Machine::IsModified_VRDEServer);
+ mlock.release();
+
+ mParent->i_onVRDEServerChange(/* aRestart */ TRUE);
+ }
+ }
+ }
+
+ return hrc;
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * @note Locks this object for writing.
+ */
+void VRDEServer::i_rollback()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mData.rollback();
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object (also
+ * for writing) if there is one.
+ */
+void VRDEServer::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller peerCaller(mPeer);
+ AssertComRCReturnVoid(peerCaller.rc());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(mPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData.isBackedUp())
+ {
+ mData.commit();
+ if (mPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ mPeer->mData.attach(mData);
+ }
+ }
+}
+
+/**
+ * @note Locks this object for writing, together with the peer object
+ * represented by @a aThat (locked for reading).
+ */
+void VRDEServer::i_copyFrom(VRDEServer *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertComRCReturnVoid(thatCaller.rc());
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ mData.assignCopy(aThat->mData);
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/VirtualBoxImpl.cpp b/src/VBox/Main/src-server/VirtualBoxImpl.cpp
new file mode 100644
index 00000000..b1950353
--- /dev/null
+++ b/src/VBox/Main/src-server/VirtualBoxImpl.cpp
@@ -0,0 +1,6616 @@
+/* $Id: VirtualBoxImpl.cpp $ */
+/** @file
+ * Implementation of IVirtualBox in VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_VIRTUALBOX
+#include <iprt/asm.h>
+#include <iprt/base64.h>
+#include <iprt/buildconfig.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/rand.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/system.h>
+#include <iprt/thread.h>
+#include <iprt/uuid.h>
+#include <iprt/cpp/xml.h>
+#include <iprt/ctype.h>
+
+#include <VBox/com/com.h>
+#include <VBox/com/array.h>
+#include "VBox/com/EventQueue.h"
+#include "VBox/com/MultiResult.h"
+
+#include <VBox/err.h>
+#include <VBox/param.h>
+#include <VBox/settings.h>
+#include <VBox/sup.h>
+#include <VBox/version.h>
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+# include <VBox/GuestHost/SharedClipboard-transfers.h>
+#endif
+
+#include <package-generated.h>
+
+#include <algorithm>
+#include <set>
+#include <vector>
+#include <memory> // for auto_ptr
+
+#include "VirtualBoxImpl.h"
+
+#include "Global.h"
+#include "MachineImpl.h"
+#include "MediumImpl.h"
+#include "SharedFolderImpl.h"
+#include "ProgressImpl.h"
+#include "HostImpl.h"
+#include "USBControllerImpl.h"
+#include "SystemPropertiesImpl.h"
+#include "GuestOSTypeImpl.h"
+#include "NetworkServiceRunner.h"
+#include "DHCPServerImpl.h"
+#include "NATNetworkImpl.h"
+#ifdef VBOX_WITH_VMNET
+#include "HostOnlyNetworkImpl.h"
+#endif /* VBOX_WITH_VMNET */
+#ifdef VBOX_WITH_CLOUD_NET
+#include "CloudNetworkImpl.h"
+#endif /* VBOX_WITH_CLOUD_NET */
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+# include "PerformanceImpl.h"
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+#ifdef VBOX_WITH_UPDATE_AGENT
+# include "UpdateAgentImpl.h"
+#endif
+#include "EventImpl.h"
+#ifdef VBOX_WITH_EXTPACK
+# include "ExtPackManagerImpl.h"
+#endif
+#ifdef VBOX_WITH_UNATTENDED
+# include "UnattendedImpl.h"
+#endif
+#include "AutostartDb.h"
+#include "ClientWatcher.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "CloudProviderManagerImpl.h"
+#include "ThreadTask.h"
+#include "VBoxEvents.h"
+
+#include <QMTranslator.h>
+
+#ifdef RT_OS_WINDOWS
+# include "win/svchlp.h"
+# include "tchar.h"
+#endif
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Definitions
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#define VBOX_GLOBAL_SETTINGS_FILE "VirtualBox.xml"
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Global variables
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// static
+com::Utf8Str VirtualBox::sVersion;
+
+// static
+com::Utf8Str VirtualBox::sVersionNormalized;
+
+// static
+ULONG VirtualBox::sRevision;
+
+// static
+com::Utf8Str VirtualBox::sPackageType;
+
+// static
+com::Utf8Str VirtualBox::sAPIVersion;
+
+// static
+std::map<com::Utf8Str, int> VirtualBox::sNatNetworkNameToRefCount;
+
+// static leaked (todo: find better place to free it.)
+RWLockHandle *VirtualBox::spMtxNatNetworkNameToRefCountLock;
+
+
+#if 0 /* obsoleted by AsyncEvent */
+////////////////////////////////////////////////////////////////////////////////
+//
+// CallbackEvent class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Abstract callback event class to asynchronously call VirtualBox callbacks
+ * on a dedicated event thread. Subclasses reimplement #prepareEventDesc()
+ * to initialize the event depending on the event to be dispatched.
+ *
+ * @note The VirtualBox instance passed to the constructor is strongly
+ * referenced, so that the VirtualBox singleton won't be released until the
+ * event gets handled by the event thread.
+ */
+class VirtualBox::CallbackEvent : public Event
+{
+public:
+
+ CallbackEvent(VirtualBox *aVirtualBox, VBoxEventType_T aWhat)
+ : mVirtualBox(aVirtualBox), mWhat(aWhat)
+ {
+ Assert(aVirtualBox);
+ }
+
+ void *handler();
+
+ virtual HRESULT prepareEventDesc(IEventSource* aSource, VBoxEventDesc& aEvDesc) = 0;
+
+private:
+
+ /**
+ * Note that this is a weak ref -- the CallbackEvent handler thread
+ * is bound to the lifetime of the VirtualBox instance, so it's safe.
+ */
+ VirtualBox *mVirtualBox;
+protected:
+ VBoxEventType_T mWhat;
+};
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AsyncEvent class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * For firing off an event on asynchronously on an event thread.
+ */
+class VirtualBox::AsyncEvent : public Event
+{
+public:
+ AsyncEvent(VirtualBox *a_pVirtualBox, ComPtr<IEvent> const &a_rEvent)
+ : mVirtualBox(a_pVirtualBox), mEvent(a_rEvent)
+ {
+ Assert(a_pVirtualBox);
+ }
+
+ void *handler() RT_OVERRIDE;
+
+private:
+ /**
+ * @note This is a weak ref -- the CallbackEvent handler thread is bound to the
+ * lifetime of the VirtualBox instance, so it's safe.
+ */
+ VirtualBox *mVirtualBox;
+ /** The event. */
+ ComPtr<IEvent> mEvent;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// VirtualBox private member data definition
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#if defined(RT_OS_WINDOWS) && defined(VBOXSVC_WITH_CLIENT_WATCHER)
+/**
+ * Client process watcher data.
+ */
+class WatchedClientProcess
+{
+public:
+ WatchedClientProcess(RTPROCESS a_pid, HANDLE a_hProcess) RT_NOEXCEPT
+ : m_pid(a_pid)
+ , m_cRefs(1)
+ , m_hProcess(a_hProcess)
+ {
+ }
+
+ ~WatchedClientProcess()
+ {
+ if (m_hProcess != NULL)
+ {
+ ::CloseHandle(m_hProcess);
+ m_hProcess = NULL;
+ }
+ m_pid = NIL_RTPROCESS;
+ }
+
+ /** The client PID. */
+ RTPROCESS m_pid;
+ /** Number of references to this structure. */
+ uint32_t volatile m_cRefs;
+ /** Handle of the client process.
+ * Ideally, we've got full query privileges, but we'll settle for waiting. */
+ HANDLE m_hProcess;
+};
+typedef std::map<RTPROCESS, WatchedClientProcess *> WatchedClientProcessMap;
+#endif
+
+
+typedef ObjectsList<Medium> MediaOList;
+typedef ObjectsList<GuestOSType> GuestOSTypesOList;
+typedef ObjectsList<SharedFolder> SharedFoldersOList;
+typedef ObjectsList<DHCPServer> DHCPServersOList;
+typedef ObjectsList<NATNetwork> NATNetworksOList;
+#ifdef VBOX_WITH_VMNET
+typedef ObjectsList<HostOnlyNetwork> HostOnlyNetworksOList;
+#endif /* VBOX_WITH_VMNET */
+#ifdef VBOX_WITH_CLOUD_NET
+typedef ObjectsList<CloudNetwork> CloudNetworksOList;
+#endif /* VBOX_WITH_CLOUD_NET */
+
+typedef std::map<Guid, ComPtr<IProgress> > ProgressMap;
+typedef std::map<Guid, ComObjPtr<Medium> > HardDiskMap;
+
+/**
+ * Main VirtualBox data structure.
+ * @note |const| members are persistent during lifetime so can be accessed
+ * without locking.
+ */
+struct VirtualBox::Data
+{
+ Data()
+ : pMainConfigFile(NULL)
+ , uuidMediaRegistry("48024e5c-fdd9-470f-93af-ec29f7ea518c")
+ , uRegistryNeedsSaving(0)
+ , lockMachines(LOCKCLASS_LISTOFMACHINES)
+ , allMachines(lockMachines)
+ , lockGuestOSTypes(LOCKCLASS_LISTOFOTHEROBJECTS)
+ , allGuestOSTypes(lockGuestOSTypes)
+ , lockMedia(LOCKCLASS_LISTOFMEDIA)
+ , allHardDisks(lockMedia)
+ , allDVDImages(lockMedia)
+ , allFloppyImages(lockMedia)
+ , lockSharedFolders(LOCKCLASS_LISTOFOTHEROBJECTS)
+ , allSharedFolders(lockSharedFolders)
+ , lockDHCPServers(LOCKCLASS_LISTOFOTHEROBJECTS)
+ , allDHCPServers(lockDHCPServers)
+ , lockNATNetworks(LOCKCLASS_LISTOFOTHEROBJECTS)
+ , allNATNetworks(lockNATNetworks)
+#ifdef VBOX_WITH_VMNET
+ , lockHostOnlyNetworks(LOCKCLASS_LISTOFOTHEROBJECTS)
+ , allHostOnlyNetworks(lockHostOnlyNetworks)
+#endif /* VBOX_WITH_VMNET */
+#ifdef VBOX_WITH_CLOUD_NET
+ , lockCloudNetworks(LOCKCLASS_LISTOFOTHEROBJECTS)
+ , allCloudNetworks(lockCloudNetworks)
+#endif /* VBOX_WITH_CLOUD_NET */
+ , mtxProgressOperations(LOCKCLASS_PROGRESSLIST)
+ , pClientWatcher(NULL)
+ , threadAsyncEvent(NIL_RTTHREAD)
+ , pAsyncEventQ(NULL)
+ , pAutostartDb(NULL)
+ , fSettingsCipherKeySet(false)
+#ifdef VBOX_WITH_MAIN_NLS
+ , pVBoxTranslator(NULL)
+ , pTrComponent(NULL)
+#endif
+#if defined(RT_OS_WINDOWS) && defined(VBOXSVC_WITH_CLIENT_WATCHER)
+ , fWatcherIsReliable(RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0))
+#endif
+ , hLdrModCrypto(NIL_RTLDRMOD)
+ , cRefsCrypto(0)
+ , pCryptoIf(NULL)
+ {
+#if defined(RT_OS_WINDOWS) && defined(VBOXSVC_WITH_CLIENT_WATCHER)
+ RTCritSectRwInit(&WatcherCritSect);
+#endif
+ }
+
+ ~Data()
+ {
+ if (pMainConfigFile)
+ {
+ delete pMainConfigFile;
+ pMainConfigFile = NULL;
+ }
+ };
+
+ // const data members not requiring locking
+ const Utf8Str strHomeDir;
+
+ // VirtualBox main settings file
+ const Utf8Str strSettingsFilePath;
+ settings::MainConfigFile *pMainConfigFile;
+
+ // constant pseudo-machine ID for global media registry
+ const Guid uuidMediaRegistry;
+
+ // counter if global media registry needs saving, updated using atomic
+ // operations, without requiring any locks
+ uint64_t uRegistryNeedsSaving;
+
+ // const objects not requiring locking
+ const ComObjPtr<Host> pHost;
+ const ComObjPtr<SystemProperties> pSystemProperties;
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ const ComObjPtr<PerformanceCollector> pPerformanceCollector;
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+
+ // Each of the following lists use a particular lock handle that protects the
+ // list as a whole. As opposed to version 3.1 and earlier, these lists no
+ // longer need the main VirtualBox object lock, but only the respective list
+ // lock. In each case, the locking order is defined that the list must be
+ // requested before object locks of members of the lists (see the order definitions
+ // in AutoLock.h; e.g. LOCKCLASS_LISTOFMACHINES before LOCKCLASS_MACHINEOBJECT).
+ RWLockHandle lockMachines;
+ MachinesOList allMachines;
+
+ RWLockHandle lockGuestOSTypes;
+ GuestOSTypesOList allGuestOSTypes;
+
+ // All the media lists are protected by the following locking handle:
+ RWLockHandle lockMedia;
+ MediaOList allHardDisks, // base images only!
+ allDVDImages,
+ allFloppyImages;
+ // the hard disks map is an additional map sorted by UUID for quick lookup
+ // and contains ALL hard disks (base and differencing); it is protected by
+ // the same lock as the other media lists above
+ HardDiskMap mapHardDisks;
+
+ // list of pending machine renames (also protected by media tree lock;
+ // see VirtualBox::rememberMachineNameChangeForMedia())
+ struct PendingMachineRename
+ {
+ Utf8Str strConfigDirOld;
+ Utf8Str strConfigDirNew;
+ };
+ typedef std::list<PendingMachineRename> PendingMachineRenamesList;
+ PendingMachineRenamesList llPendingMachineRenames;
+
+ RWLockHandle lockSharedFolders;
+ SharedFoldersOList allSharedFolders;
+
+ RWLockHandle lockDHCPServers;
+ DHCPServersOList allDHCPServers;
+
+ RWLockHandle lockNATNetworks;
+ NATNetworksOList allNATNetworks;
+
+#ifdef VBOX_WITH_VMNET
+ RWLockHandle lockHostOnlyNetworks;
+ HostOnlyNetworksOList allHostOnlyNetworks;
+#endif /* VBOX_WITH_VMNET */
+#ifdef VBOX_WITH_CLOUD_NET
+ RWLockHandle lockCloudNetworks;
+ CloudNetworksOList allCloudNetworks;
+#endif /* VBOX_WITH_CLOUD_NET */
+
+ RWLockHandle mtxProgressOperations;
+ ProgressMap mapProgressOperations;
+
+ ClientWatcher * const pClientWatcher;
+
+ // the following are data for the async event thread
+ const RTTHREAD threadAsyncEvent;
+ EventQueue * const pAsyncEventQ;
+ const ComObjPtr<EventSource> pEventSource;
+
+#ifdef VBOX_WITH_EXTPACK
+ /** The extension pack manager object lives here. */
+ const ComObjPtr<ExtPackManager> ptrExtPackManager;
+#endif
+
+ /** The reference to the cloud provider manager singleton. */
+ const ComObjPtr<CloudProviderManager> pCloudProviderManager;
+
+ /** The global autostart database for the user. */
+ AutostartDb * const pAutostartDb;
+
+ /** Settings secret */
+ bool fSettingsCipherKeySet;
+ uint8_t SettingsCipherKey[RTSHA512_HASH_SIZE];
+#ifdef VBOX_WITH_MAIN_NLS
+ VirtualBoxTranslator *pVBoxTranslator;
+ PTRCOMPONENT pTrComponent;
+#endif
+#if defined(RT_OS_WINDOWS) && defined(VBOXSVC_WITH_CLIENT_WATCHER)
+ /** Critical section protecting WatchedProcesses. */
+ RTCRITSECTRW WatcherCritSect;
+ /** Map of processes being watched, key is the PID. */
+ WatchedClientProcessMap WatchedProcesses;
+ /** Set if the watcher is reliable, otherwise cleared.
+ * The watcher goes unreliable when we run out of memory, fail open a client
+ * process, or if the watcher thread gets messed up. */
+ bool fWatcherIsReliable;
+#endif
+
+ /** @name Members related to the cryptographic support interface.
+ * @{ */
+ /** The loaded module handle if loaded. */
+ RTLDRMOD hLdrModCrypto;
+ /** Reference counter tracking how many users of the cryptographic support
+ * are there currently. */
+ volatile uint32_t cRefsCrypto;
+ /** Pointer to the cryptographic support interface. */
+ PCVBOXCRYPTOIF pCryptoIf;
+ /** Critical section protecting the module handle. */
+ RTCRITSECT CritSectModCrypto;
+ /** @} */
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(VirtualBox)
+
+HRESULT VirtualBox::FinalConstruct()
+{
+ LogRelFlowThisFuncEnter();
+ LogRel(("VirtualBox: object creation starts\n"));
+
+ BaseFinalConstruct();
+
+ HRESULT rc = init();
+
+ LogRelFlowThisFuncLeave();
+ LogRel(("VirtualBox: object created\n"));
+
+ return rc;
+}
+
+void VirtualBox::FinalRelease()
+{
+ LogRelFlowThisFuncEnter();
+ LogRel(("VirtualBox: object deletion starts\n"));
+
+ uninit();
+
+ BaseFinalRelease();
+
+ LogRel(("VirtualBox: object deleted\n"));
+ LogRelFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the VirtualBox object.
+ *
+ * @return COM result code
+ */
+HRESULT VirtualBox::init()
+{
+ LogRelFlowThisFuncEnter();
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Locking this object for writing during init sounds a bit paradoxical,
+ * but in the current locking mess this avoids that some code gets a
+ * read lock and later calls code which wants the same write lock. */
+ AutoWriteLock lock(this COMMA_LOCKVAL_SRC_POS);
+
+ // allocate our instance data
+ m = new Data;
+
+ LogFlow(("===========================================================\n"));
+ LogFlowThisFuncEnter();
+
+ if (sVersion.isEmpty())
+ sVersion = RTBldCfgVersion();
+ if (sVersionNormalized.isEmpty())
+ {
+ Utf8Str tmp(RTBldCfgVersion());
+ if (tmp.endsWith(VBOX_BUILD_PUBLISHER))
+ tmp = tmp.substr(0, tmp.length() - strlen(VBOX_BUILD_PUBLISHER));
+ sVersionNormalized = tmp;
+ }
+ sRevision = RTBldCfgRevision();
+ if (sPackageType.isEmpty())
+ sPackageType = VBOX_PACKAGE_STRING;
+ if (sAPIVersion.isEmpty())
+ sAPIVersion = VBOX_API_VERSION_STRING;
+ if (!spMtxNatNetworkNameToRefCountLock)
+ spMtxNatNetworkNameToRefCountLock = new RWLockHandle(LOCKCLASS_VIRTUALBOXOBJECT);
+
+ LogFlowThisFunc(("Version: %s, Package: %s, API Version: %s\n", sVersion.c_str(), sPackageType.c_str(), sAPIVersion.c_str()));
+
+ /* Important: DO NOT USE any kind of "early return" (except the single
+ * one above, checking the init span success) in this method. It is vital
+ * for correct error handling that it has only one point of return, which
+ * does all the magic on COM to signal object creation success and
+ * reporting the error later for every API method. COM translates any
+ * unsuccessful object creation to REGDB_E_CLASSNOTREG errors or similar
+ * unhelpful ones which cause us a lot of grief with troubleshooting. */
+
+ HRESULT rc = S_OK;
+ bool fCreate = false;
+ try
+ {
+ /* Create the event source early as we may fire async event during settings loading (media). */
+ rc = unconst(m->pEventSource).createObject();
+ if (FAILED(rc)) throw rc;
+ rc = m->pEventSource->init();
+ if (FAILED(rc)) throw rc;
+
+
+ /* Get the VirtualBox home directory. */
+ {
+ char szHomeDir[RTPATH_MAX];
+ int vrc = com::GetVBoxUserHomeDirectory(szHomeDir, sizeof(szHomeDir));
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(E_FAIL, vrc,
+ tr("Could not create the VirtualBox home directory '%s' (%Rrc)"),
+ szHomeDir, vrc);
+
+ unconst(m->strHomeDir) = szHomeDir;
+ }
+
+ LogRel(("Home directory: '%s'\n", m->strHomeDir.c_str()));
+
+ i_reportDriverVersions();
+
+ /* Create the critical section protecting the cryptographic module handle. */
+ {
+ int vrc = RTCritSectInit(&m->CritSectModCrypto);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(E_FAIL, vrc,
+ tr("Could not create the cryptographic module critical section (%Rrc)"),
+ vrc);
+
+ }
+
+ /* compose the VirtualBox.xml file name */
+ unconst(m->strSettingsFilePath) = Utf8StrFmt("%s%c%s",
+ m->strHomeDir.c_str(),
+ RTPATH_DELIMITER,
+ VBOX_GLOBAL_SETTINGS_FILE);
+ // load and parse VirtualBox.xml; this will throw on XML or logic errors
+ try
+ {
+ m->pMainConfigFile = new settings::MainConfigFile(&m->strSettingsFilePath);
+ }
+ catch (xml::EIPRTFailure &e)
+ {
+ // this is thrown by the XML backend if the RTOpen() call fails;
+ // only if the main settings file does not exist, create it,
+ // if there's something more serious, then do fail!
+ if (e.rc() == VERR_FILE_NOT_FOUND)
+ fCreate = true;
+ else
+ throw;
+ }
+
+ if (fCreate)
+ m->pMainConfigFile = new settings::MainConfigFile(NULL);
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ /* create the performance collector object BEFORE host */
+ unconst(m->pPerformanceCollector).createObject();
+ rc = m->pPerformanceCollector->init();
+ ComAssertComRCThrowRC(rc);
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+
+ /* create the host object early, machines will need it */
+ unconst(m->pHost).createObject();
+ rc = m->pHost->init(this);
+ ComAssertComRCThrowRC(rc);
+
+ rc = m->pHost->i_loadSettings(m->pMainConfigFile->host);
+ if (FAILED(rc)) throw rc;
+
+ /*
+ * Create autostart database object early, because the system properties
+ * might need it.
+ */
+ unconst(m->pAutostartDb) = new AutostartDb;
+
+ /* create the system properties object, someone may need it too */
+ rc = unconst(m->pSystemProperties).createObject();
+ if (SUCCEEDED(rc))
+ rc = m->pSystemProperties->init(this);
+ ComAssertComRCThrowRC(rc);
+
+ rc = m->pSystemProperties->i_loadSettings(m->pMainConfigFile->systemProperties);
+ if (FAILED(rc)) throw rc;
+#ifdef VBOX_WITH_MAIN_NLS
+ m->pVBoxTranslator = VirtualBoxTranslator::instance();
+ /* Do not throw an exception on language errors.
+ * Just do not use translation. */
+ if (m->pVBoxTranslator)
+ {
+
+ char szNlsPath[RTPATH_MAX];
+ int vrc = RTPathAppPrivateNoArch(szNlsPath, sizeof(szNlsPath));
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppend(szNlsPath, sizeof(szNlsPath), "nls" RTPATH_SLASH_STR "VirtualBoxAPI");
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = m->pVBoxTranslator->registerTranslation(szNlsPath, true, &m->pTrComponent);
+ if (RT_SUCCESS(vrc))
+ {
+ com::Utf8Str strLocale;
+ HRESULT hrc = m->pSystemProperties->getLanguageId(strLocale);
+ if (SUCCEEDED(hrc))
+ {
+ vrc = m->pVBoxTranslator->i_loadLanguage(strLocale.c_str());
+ if (RT_FAILURE(vrc))
+ {
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+ LogRel(("Load language failed (%Rhrc).\n", hrc));
+ }
+ }
+ else
+ {
+ LogRel(("Getting language settings failed (%Rhrc).\n", hrc));
+ m->pVBoxTranslator->release();
+ m->pVBoxTranslator = NULL;
+ m->pTrComponent = NULL;
+ }
+ }
+ else
+ {
+ HRESULT hrc = Global::vboxStatusCodeToCOM(vrc);
+ LogRel(("Register translation failed (%Rhrc).\n", hrc));
+ m->pVBoxTranslator->release();
+ m->pVBoxTranslator = NULL;
+ m->pTrComponent = NULL;
+ }
+ }
+ else
+ {
+ HRESULT hrc = Global::vboxStatusCodeToCOM(vrc);
+ LogRel(("Path constructing failed (%Rhrc).\n", hrc));
+ m->pVBoxTranslator->release();
+ m->pVBoxTranslator = NULL;
+ m->pTrComponent = NULL;
+ }
+ }
+ else
+ LogRel(("Translator creation failed.\n"));
+#endif
+
+#ifdef VBOX_WITH_EXTPACK
+ /*
+ * Initialize extension pack manager before system properties because
+ * it is required for the VD plugins.
+ */
+ rc = unconst(m->ptrExtPackManager).createObject();
+ if (SUCCEEDED(rc))
+ rc = m->ptrExtPackManager->initExtPackManager(this, VBOXEXTPACKCTX_PER_USER_DAEMON);
+ if (FAILED(rc))
+ throw rc;
+#endif
+ /* guest OS type objects, needed by machines */
+ for (size_t i = 0; i < Global::cOSTypes; ++i)
+ {
+ ComObjPtr<GuestOSType> guestOSTypeObj;
+ rc = guestOSTypeObj.createObject();
+ if (SUCCEEDED(rc))
+ {
+ rc = guestOSTypeObj->init(Global::sOSTypes[i]);
+ if (SUCCEEDED(rc))
+ m->allGuestOSTypes.addChild(guestOSTypeObj);
+ }
+ ComAssertComRCThrowRC(rc);
+ }
+
+ /* all registered media, needed by machines */
+ if (FAILED(rc = initMedia(m->uuidMediaRegistry,
+ m->pMainConfigFile->mediaRegistry,
+ Utf8Str::Empty))) // const Utf8Str &machineFolder
+ throw rc;
+
+ /* machines */
+ if (FAILED(rc = initMachines()))
+ throw rc;
+
+#ifdef DEBUG
+ LogFlowThisFunc(("Dumping media backreferences\n"));
+ i_dumpAllBackRefs();
+#endif
+
+ /* net services - dhcp services */
+ for (settings::DHCPServersList::const_iterator it = m->pMainConfigFile->llDhcpServers.begin();
+ it != m->pMainConfigFile->llDhcpServers.end();
+ ++it)
+ {
+ const settings::DHCPServer &data = *it;
+
+ ComObjPtr<DHCPServer> pDhcpServer;
+ if (SUCCEEDED(rc = pDhcpServer.createObject()))
+ rc = pDhcpServer->init(this, data);
+ if (FAILED(rc)) throw rc;
+
+ rc = i_registerDHCPServer(pDhcpServer, false /* aSaveRegistry */);
+ if (FAILED(rc)) throw rc;
+ }
+
+ /* net services - nat networks */
+ for (settings::NATNetworksList::const_iterator it = m->pMainConfigFile->llNATNetworks.begin();
+ it != m->pMainConfigFile->llNATNetworks.end();
+ ++it)
+ {
+ const settings::NATNetwork &net = *it;
+
+ ComObjPtr<NATNetwork> pNATNetwork;
+ rc = pNATNetwork.createObject();
+ AssertComRCThrowRC(rc);
+ rc = pNATNetwork->init(this, "");
+ AssertComRCThrowRC(rc);
+ rc = pNATNetwork->i_loadSettings(net);
+ AssertComRCThrowRC(rc);
+ rc = i_registerNATNetwork(pNATNetwork, false /* aSaveRegistry */);
+ AssertComRCThrowRC(rc);
+ }
+
+#ifdef VBOX_WITH_VMNET
+ /* host-only networks */
+ for (settings::HostOnlyNetworksList::const_iterator it = m->pMainConfigFile->llHostOnlyNetworks.begin();
+ it != m->pMainConfigFile->llHostOnlyNetworks.end();
+ ++it)
+ {
+ ComObjPtr<HostOnlyNetwork> pHostOnlyNetwork;
+ rc = pHostOnlyNetwork.createObject();
+ AssertComRCThrowRC(rc);
+ rc = pHostOnlyNetwork->init(this, "TODO???");
+ AssertComRCThrowRC(rc);
+ rc = pHostOnlyNetwork->i_loadSettings(*it);
+ AssertComRCThrowRC(rc);
+ m->allHostOnlyNetworks.addChild(pHostOnlyNetwork);
+ AssertComRCThrowRC(rc);
+ }
+#endif /* VBOX_WITH_VMNET */
+
+#ifdef VBOX_WITH_CLOUD_NET
+ /* net services - cloud networks */
+ for (settings::CloudNetworksList::const_iterator it = m->pMainConfigFile->llCloudNetworks.begin();
+ it != m->pMainConfigFile->llCloudNetworks.end();
+ ++it)
+ {
+ ComObjPtr<CloudNetwork> pCloudNetwork;
+ rc = pCloudNetwork.createObject();
+ AssertComRCThrowRC(rc);
+ rc = pCloudNetwork->init(this, "");
+ AssertComRCThrowRC(rc);
+ rc = pCloudNetwork->i_loadSettings(*it);
+ AssertComRCThrowRC(rc);
+ m->allCloudNetworks.addChild(pCloudNetwork);
+ AssertComRCThrowRC(rc);
+ }
+#endif /* VBOX_WITH_CLOUD_NET */
+
+ /* cloud provider manager */
+ rc = unconst(m->pCloudProviderManager).createObject();
+ if (SUCCEEDED(rc))
+ rc = m->pCloudProviderManager->init(this);
+ ComAssertComRCThrowRC(rc);
+ if (FAILED(rc)) throw rc;
+ }
+ catch (HRESULT err)
+ {
+ /* we assume that error info is set by the thrower */
+ rc = err;
+ }
+ catch (...)
+ {
+ rc = VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS);
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ /* set up client monitoring */
+ try
+ {
+ unconst(m->pClientWatcher) = new ClientWatcher(this);
+ if (!m->pClientWatcher->isReady())
+ {
+ delete m->pClientWatcher;
+ unconst(m->pClientWatcher) = NULL;
+ rc = E_FAIL;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = E_OUTOFMEMORY;
+ }
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ try
+ {
+ /* start the async event handler thread */
+ int vrc = RTThreadCreate(&unconst(m->threadAsyncEvent),
+ AsyncEventHandler,
+ &unconst(m->pAsyncEventQ),
+ 0,
+ RTTHREADTYPE_MAIN_WORKER,
+ RTTHREADFLAGS_WAITABLE,
+ "EventHandler");
+ ComAssertRCThrow(vrc, E_FAIL);
+
+ /* wait until the thread sets m->pAsyncEventQ */
+ RTThreadUserWait(m->threadAsyncEvent, RT_INDEFINITE_WAIT);
+ ComAssertThrow(m->pAsyncEventQ, E_FAIL);
+ }
+ catch (HRESULT aRC)
+ {
+ rc = aRC;
+ }
+ }
+
+#ifdef VBOX_WITH_EXTPACK
+ /* Let the extension packs have a go at things. */
+ if (SUCCEEDED(rc))
+ {
+ lock.release();
+ m->ptrExtPackManager->i_callAllVirtualBoxReadyHooks();
+ }
+#endif
+
+ /* Confirm a successful initialization when it's the case. Must be last,
+ * as on failure it will uninitialize the object. */
+ if (SUCCEEDED(rc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed(rc);
+
+ LogFlowThisFunc(("rc=%Rhrc\n", rc));
+ LogFlowThisFuncLeave();
+ LogFlow(("===========================================================\n"));
+ /* Unconditionally return success, because the error return is delayed to
+ * the attribute/method calls through the InitFailed object state. */
+ return S_OK;
+}
+
+HRESULT VirtualBox::initMachines()
+{
+ for (settings::MachinesRegistry::const_iterator it = m->pMainConfigFile->llMachines.begin();
+ it != m->pMainConfigFile->llMachines.end();
+ ++it)
+ {
+ HRESULT rc = S_OK;
+ const settings::MachineRegistryEntry &xmlMachine = *it;
+ Guid uuid = xmlMachine.uuid;
+
+ /* Check if machine record has valid parameters. */
+ if (xmlMachine.strSettingsFile.isEmpty() || uuid.isZero())
+ {
+ LogRel(("Skipped invalid machine record.\n"));
+ continue;
+ }
+
+ ComObjPtr<Machine> pMachine;
+ com::Utf8Str strPassword;
+ if (SUCCEEDED(rc = pMachine.createObject()))
+ {
+ rc = pMachine->initFromSettings(this,
+ xmlMachine.strSettingsFile,
+ &uuid,
+ strPassword);
+ if (SUCCEEDED(rc))
+ rc = i_registerMachine(pMachine);
+ if (FAILED(rc))
+ return rc;
+ }
+ }
+
+ return S_OK;
+}
+
+/**
+ * Loads a media registry from XML and adds the media contained therein to
+ * the global lists of known media.
+ *
+ * This now (4.0) gets called from two locations:
+ *
+ * -- VirtualBox::init(), to load the global media registry from VirtualBox.xml;
+ *
+ * -- Machine::loadMachineDataFromSettings(), to load the per-machine registry
+ * from machine XML, for machines created with VirtualBox 4.0 or later.
+ *
+ * In both cases, the media found are added to the global lists so the
+ * global arrays of media (including the GUI's virtual media manager)
+ * continue to work as before.
+ *
+ * @param uuidRegistry The UUID of the media registry. This is either the
+ * transient UUID created at VirtualBox startup for the global registry or
+ * a machine ID.
+ * @param mediaRegistry The XML settings structure to load, either from VirtualBox.xml
+ * or a machine XML.
+ * @param strMachineFolder The folder of the machine.
+ * @return
+ */
+HRESULT VirtualBox::initMedia(const Guid &uuidRegistry,
+ const settings::MediaRegistry &mediaRegistry,
+ const Utf8Str &strMachineFolder)
+{
+ LogFlow(("VirtualBox::initMedia ENTERING, uuidRegistry=%s, strMachineFolder=%s\n",
+ uuidRegistry.toString().c_str(),
+ strMachineFolder.c_str()));
+
+ AutoWriteLock treeLock(i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ // the order of notification is critical for GUI, so use std::list<std::pair> instead of map
+ std::list<std::pair<Guid, DeviceType_T> > uIdsForNotify;
+
+ HRESULT rc = S_OK;
+ settings::MediaList::const_iterator it;
+ for (it = mediaRegistry.llHardDisks.begin();
+ it != mediaRegistry.llHardDisks.end();
+ ++it)
+ {
+ const settings::Medium &xmlHD = *it;
+
+ rc = Medium::initFromSettings(this,
+ DeviceType_HardDisk,
+ uuidRegistry,
+ strMachineFolder,
+ xmlHD,
+ treeLock,
+ uIdsForNotify);
+ if (FAILED(rc)) return rc;
+ }
+
+ for (it = mediaRegistry.llDvdImages.begin();
+ it != mediaRegistry.llDvdImages.end();
+ ++it)
+ {
+ const settings::Medium &xmlDvd = *it;
+
+ rc = Medium::initFromSettings(this,
+ DeviceType_DVD,
+ uuidRegistry,
+ strMachineFolder,
+ xmlDvd,
+ treeLock,
+ uIdsForNotify);
+ if (FAILED(rc)) return rc;
+ }
+
+ for (it = mediaRegistry.llFloppyImages.begin();
+ it != mediaRegistry.llFloppyImages.end();
+ ++it)
+ {
+ const settings::Medium &xmlFloppy = *it;
+
+ rc = Medium::initFromSettings(this,
+ DeviceType_Floppy,
+ uuidRegistry,
+ strMachineFolder,
+ xmlFloppy,
+ treeLock,
+ uIdsForNotify);
+ if (FAILED(rc)) return rc;
+ }
+
+ for (std::list<std::pair<Guid, DeviceType_T> >::const_iterator itItem = uIdsForNotify.begin();
+ itItem != uIdsForNotify.end();
+ ++itItem)
+ {
+ i_onMediumRegistered(itItem->first, itItem->second, TRUE);
+ }
+
+ LogFlow(("VirtualBox::initMedia LEAVING\n"));
+
+ return S_OK;
+}
+
+void VirtualBox::uninit()
+{
+ /* Must be done outside the AutoUninitSpan, as it expects AutoCaller to
+ * be successful. This needs additional checks to protect against double
+ * uninit, as then the pointer is NULL. */
+ if (RT_VALID_PTR(m))
+ {
+ Assert(!m->uRegistryNeedsSaving);
+ if (m->uRegistryNeedsSaving)
+ i_saveSettings();
+ }
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlow(("===========================================================\n"));
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("initFailed()=%d\n", autoUninitSpan.initFailed()));
+
+ /* tell all our child objects we've been uninitialized */
+
+ LogFlowThisFunc(("Uninitializing machines (%d)...\n", m->allMachines.size()));
+ if (m->pHost)
+ {
+ /* It is necessary to hold the VirtualBox and Host locks here because
+ we may have to uninitialize SessionMachines. */
+ AutoMultiWriteLock2 multilock(this, m->pHost COMMA_LOCKVAL_SRC_POS);
+ m->allMachines.uninitAll();
+ }
+ else
+ m->allMachines.uninitAll();
+ m->allFloppyImages.uninitAll();
+ m->allDVDImages.uninitAll();
+ m->allHardDisks.uninitAll();
+ m->allDHCPServers.uninitAll();
+
+ m->mapProgressOperations.clear();
+
+ m->allGuestOSTypes.uninitAll();
+
+ /* Note that we release singleton children after we've all other children.
+ * In some cases this is important because these other children may use
+ * some resources of the singletons which would prevent them from
+ * uninitializing (as for example, mSystemProperties which owns
+ * MediumFormat objects which Medium objects refer to) */
+ if (m->pCloudProviderManager)
+ {
+ m->pCloudProviderManager->uninit();
+ unconst(m->pCloudProviderManager).setNull();
+ }
+
+ if (m->pSystemProperties)
+ {
+ m->pSystemProperties->uninit();
+ unconst(m->pSystemProperties).setNull();
+ }
+
+ if (m->pHost)
+ {
+ m->pHost->uninit();
+ unconst(m->pHost).setNull();
+ }
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ if (m->pPerformanceCollector)
+ {
+ m->pPerformanceCollector->uninit();
+ unconst(m->pPerformanceCollector).setNull();
+ }
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+
+ /*
+ * Unload the cryptographic module if loaded before the extension
+ * pack manager is torn down.
+ */
+ Assert(!m->cRefsCrypto);
+ if (m->hLdrModCrypto != NIL_RTLDRMOD)
+ {
+ m->pCryptoIf = NULL;
+
+ int vrc = RTLdrClose(m->hLdrModCrypto);
+ AssertRC(vrc);
+ m->hLdrModCrypto = NIL_RTLDRMOD;
+ }
+
+ RTCritSectDelete(&m->CritSectModCrypto);
+
+#ifdef VBOX_WITH_EXTPACK
+ if (m->ptrExtPackManager)
+ {
+ m->ptrExtPackManager->uninit();
+ unconst(m->ptrExtPackManager).setNull();
+ }
+#endif
+
+ LogFlowThisFunc(("Terminating the async event handler...\n"));
+ if (m->threadAsyncEvent != NIL_RTTHREAD)
+ {
+ /* signal to exit the event loop */
+ if (RT_SUCCESS(m->pAsyncEventQ->interruptEventQueueProcessing()))
+ {
+ /*
+ * Wait for thread termination (only after we've successfully
+ * interrupted the event queue processing!)
+ */
+ int vrc = RTThreadWait(m->threadAsyncEvent, 60000, NULL);
+ if (RT_FAILURE(vrc))
+ Log1WarningFunc(("RTThreadWait(%RTthrd) -> %Rrc\n", m->threadAsyncEvent, vrc));
+ }
+ else
+ {
+ AssertMsgFailed(("interruptEventQueueProcessing() failed\n"));
+ RTThreadWait(m->threadAsyncEvent, 0, NULL);
+ }
+
+ unconst(m->threadAsyncEvent) = NIL_RTTHREAD;
+ unconst(m->pAsyncEventQ) = NULL;
+ }
+
+ LogFlowThisFunc(("Releasing event source...\n"));
+ if (m->pEventSource)
+ {
+ // Must uninit the event source here, because it makes no sense that
+ // it survives longer than the base object. If someone gets an event
+ // with such an event source then that's life and it has to be dealt
+ // with appropriately on the API client side.
+ m->pEventSource->uninit();
+ unconst(m->pEventSource).setNull();
+ }
+
+ LogFlowThisFunc(("Terminating the client watcher...\n"));
+ if (m->pClientWatcher)
+ {
+ delete m->pClientWatcher;
+ unconst(m->pClientWatcher) = NULL;
+ }
+
+ delete m->pAutostartDb;
+#ifdef VBOX_WITH_MAIN_NLS
+ if (m->pVBoxTranslator)
+ m->pVBoxTranslator->release();
+#endif
+ // clean up our instance data
+ delete m;
+ m = NULL;
+
+ /* Unload hard disk plugin backends. */
+ VDShutdown();
+
+ LogFlowThisFuncLeave();
+ LogFlow(("===========================================================\n"));
+}
+
+// Wrapped IVirtualBox properties
+/////////////////////////////////////////////////////////////////////////////
+HRESULT VirtualBox::getVersion(com::Utf8Str &aVersion)
+{
+ aVersion = sVersion;
+ return S_OK;
+}
+
+HRESULT VirtualBox::getVersionNormalized(com::Utf8Str &aVersionNormalized)
+{
+ aVersionNormalized = sVersionNormalized;
+ return S_OK;
+}
+
+HRESULT VirtualBox::getRevision(ULONG *aRevision)
+{
+ *aRevision = sRevision;
+ return S_OK;
+}
+
+HRESULT VirtualBox::getPackageType(com::Utf8Str &aPackageType)
+{
+ aPackageType = sPackageType;
+ return S_OK;
+}
+
+HRESULT VirtualBox::getAPIVersion(com::Utf8Str &aAPIVersion)
+{
+ aAPIVersion = sAPIVersion;
+ return S_OK;
+}
+
+HRESULT VirtualBox::getAPIRevision(LONG64 *aAPIRevision)
+{
+ AssertCompile(VBOX_VERSION_MAJOR < 128 && VBOX_VERSION_MAJOR > 0);
+ AssertCompile((uint64_t)VBOX_VERSION_MINOR < 256);
+ uint64_t uRevision = ((uint64_t)VBOX_VERSION_MAJOR << 56)
+ | ((uint64_t)VBOX_VERSION_MINOR << 48)
+ | ((uint64_t)VBOX_VERSION_BUILD << 40);
+
+ /** @todo This needs to be the same in OSE and non-OSE, preferrably
+ * only changing when actual API changes happens. */
+ uRevision |= 1;
+
+ *aAPIRevision = (LONG64)uRevision;
+
+ return S_OK;
+}
+
+HRESULT VirtualBox::getHomeFolder(com::Utf8Str &aHomeFolder)
+{
+ /* mHomeDir is const and doesn't need a lock */
+ aHomeFolder = m->strHomeDir;
+ return S_OK;
+}
+
+HRESULT VirtualBox::getSettingsFilePath(com::Utf8Str &aSettingsFilePath)
+{
+ /* mCfgFile.mName is const and doesn't need a lock */
+ aSettingsFilePath = m->strSettingsFilePath;
+ return S_OK;
+}
+
+HRESULT VirtualBox::getHost(ComPtr<IHost> &aHost)
+{
+ /* mHost is const, no need to lock */
+ m->pHost.queryInterfaceTo(aHost.asOutParam());
+ return S_OK;
+}
+
+HRESULT VirtualBox::getSystemProperties(ComPtr<ISystemProperties> &aSystemProperties)
+{
+ /* mSystemProperties is const, no need to lock */
+ m->pSystemProperties.queryInterfaceTo(aSystemProperties.asOutParam());
+ return S_OK;
+}
+
+HRESULT VirtualBox::getMachines(std::vector<ComPtr<IMachine> > &aMachines)
+{
+ AutoReadLock al(m->allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ aMachines.resize(m->allMachines.size());
+ size_t i = 0;
+ for (MachinesOList::const_iterator it= m->allMachines.begin();
+ it!= m->allMachines.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aMachines[i].asOutParam());
+ return S_OK;
+}
+
+HRESULT VirtualBox::getMachineGroups(std::vector<com::Utf8Str> &aMachineGroups)
+{
+ std::list<com::Utf8Str> allGroups;
+
+ /* get copy of all machine references, to avoid holding the list lock */
+ MachinesOList::MyList allMachines;
+ {
+ AutoReadLock al(m->allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ allMachines = m->allMachines.getList();
+ }
+ for (MachinesOList::MyList::const_iterator it = allMachines.begin();
+ it != allMachines.end();
+ ++it)
+ {
+ const ComObjPtr<Machine> &pMachine = *it;
+ AutoCaller autoMachineCaller(pMachine);
+ if (FAILED(autoMachineCaller.rc()))
+ continue;
+ AutoReadLock mlock(pMachine COMMA_LOCKVAL_SRC_POS);
+
+ if (pMachine->i_isAccessible())
+ {
+ const StringsList &thisGroups = pMachine->i_getGroups();
+ for (StringsList::const_iterator it2 = thisGroups.begin();
+ it2 != thisGroups.end(); ++it2)
+ allGroups.push_back(*it2);
+ }
+ }
+
+ /* throw out any duplicates */
+ allGroups.sort();
+ allGroups.unique();
+ aMachineGroups.resize(allGroups.size());
+ size_t i = 0;
+ for (std::list<com::Utf8Str>::const_iterator it = allGroups.begin();
+ it != allGroups.end(); ++it, ++i)
+ aMachineGroups[i] = (*it);
+ return S_OK;
+}
+
+HRESULT VirtualBox::getHardDisks(std::vector<ComPtr<IMedium> > &aHardDisks)
+{
+ AutoReadLock al(m->allHardDisks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ aHardDisks.resize(m->allHardDisks.size());
+ size_t i = 0;
+ for (MediaOList::const_iterator it = m->allHardDisks.begin();
+ it != m->allHardDisks.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aHardDisks[i].asOutParam());
+ return S_OK;
+}
+
+HRESULT VirtualBox::getDVDImages(std::vector<ComPtr<IMedium> > &aDVDImages)
+{
+ AutoReadLock al(m->allDVDImages.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ aDVDImages.resize(m->allDVDImages.size());
+ size_t i = 0;
+ for (MediaOList::const_iterator it = m->allDVDImages.begin();
+ it!= m->allDVDImages.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aDVDImages[i].asOutParam());
+ return S_OK;
+}
+
+HRESULT VirtualBox::getFloppyImages(std::vector<ComPtr<IMedium> > &aFloppyImages)
+{
+ AutoReadLock al(m->allFloppyImages.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ aFloppyImages.resize(m->allFloppyImages.size());
+ size_t i = 0;
+ for (MediaOList::const_iterator it = m->allFloppyImages.begin();
+ it != m->allFloppyImages.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aFloppyImages[i].asOutParam());
+ return S_OK;
+}
+
+HRESULT VirtualBox::getProgressOperations(std::vector<ComPtr<IProgress> > &aProgressOperations)
+{
+ /* protect mProgressOperations */
+ AutoReadLock safeLock(m->mtxProgressOperations COMMA_LOCKVAL_SRC_POS);
+ ProgressMap pmap(m->mapProgressOperations);
+ /* Can release lock now. The following code works on a copy of the map. */
+ safeLock.release();
+ aProgressOperations.resize(pmap.size());
+ size_t i = 0;
+ for (ProgressMap::iterator it = pmap.begin(); it != pmap.end(); ++it, ++i)
+ it->second.queryInterfaceTo(aProgressOperations[i].asOutParam());
+ return S_OK;
+}
+
+HRESULT VirtualBox::getGuestOSTypes(std::vector<ComPtr<IGuestOSType> > &aGuestOSTypes)
+{
+ AutoReadLock al(m->allGuestOSTypes.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ aGuestOSTypes.resize(m->allGuestOSTypes.size());
+ size_t i = 0;
+ for (GuestOSTypesOList::const_iterator it = m->allGuestOSTypes.begin();
+ it != m->allGuestOSTypes.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aGuestOSTypes[i].asOutParam());
+ return S_OK;
+}
+
+HRESULT VirtualBox::getSharedFolders(std::vector<ComPtr<ISharedFolder> > &aSharedFolders)
+{
+ NOREF(aSharedFolders);
+
+ return setError(E_NOTIMPL, tr("Not yet implemented"));
+}
+
+HRESULT VirtualBox::getPerformanceCollector(ComPtr<IPerformanceCollector> &aPerformanceCollector)
+{
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+ /* mPerformanceCollector is const, no need to lock */
+ m->pPerformanceCollector.queryInterfaceTo(aPerformanceCollector.asOutParam());
+
+ return S_OK;
+#else /* !VBOX_WITH_RESOURCE_USAGE_API */
+ NOREF(aPerformanceCollector);
+ ReturnComNotImplemented();
+#endif /* !VBOX_WITH_RESOURCE_USAGE_API */
+}
+
+HRESULT VirtualBox::getDHCPServers(std::vector<ComPtr<IDHCPServer> > &aDHCPServers)
+{
+ AutoReadLock al(m->allDHCPServers.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ aDHCPServers.resize(m->allDHCPServers.size());
+ size_t i = 0;
+ for (DHCPServersOList::const_iterator it= m->allDHCPServers.begin();
+ it!= m->allDHCPServers.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aDHCPServers[i].asOutParam());
+ return S_OK;
+}
+
+
+HRESULT VirtualBox::getNATNetworks(std::vector<ComPtr<INATNetwork> > &aNATNetworks)
+{
+#ifdef VBOX_WITH_NAT_SERVICE
+ AutoReadLock al(m->allNATNetworks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ aNATNetworks.resize(m->allNATNetworks.size());
+ size_t i = 0;
+ for (NATNetworksOList::const_iterator it= m->allNATNetworks.begin();
+ it!= m->allNATNetworks.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aNATNetworks[i].asOutParam());
+ return S_OK;
+#else
+ NOREF(aNATNetworks);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT VirtualBox::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ /* event source is const, no need to lock */
+ m->pEventSource.queryInterfaceTo(aEventSource.asOutParam());
+ return S_OK;
+}
+
+HRESULT VirtualBox::getExtensionPackManager(ComPtr<IExtPackManager> &aExtensionPackManager)
+{
+ HRESULT hrc = S_OK;
+#ifdef VBOX_WITH_EXTPACK
+ /* The extension pack manager is const, no need to lock. */
+ hrc = m->ptrExtPackManager.queryInterfaceTo(aExtensionPackManager.asOutParam());
+#else
+ hrc = E_NOTIMPL;
+ NOREF(aExtensionPackManager);
+#endif
+ return hrc;
+}
+
+/**
+ * Host Only Network
+ */
+HRESULT VirtualBox::createHostOnlyNetwork(const com::Utf8Str &aNetworkName,
+ ComPtr<IHostOnlyNetwork> &aNetwork)
+{
+#ifdef VBOX_WITH_VMNET
+ ComObjPtr<HostOnlyNetwork> HostOnlyNetwork;
+ HostOnlyNetwork.createObject();
+ HRESULT rc = HostOnlyNetwork->init(this, aNetworkName);
+ if (FAILED(rc)) return rc;
+
+ m->allHostOnlyNetworks.addChild(HostOnlyNetwork);
+
+ {
+ AutoWriteLock vboxLock(this COMMA_LOCKVAL_SRC_POS);
+ rc = i_saveSettings();
+ vboxLock.release();
+
+ if (FAILED(rc))
+ m->allHostOnlyNetworks.removeChild(HostOnlyNetwork);
+ else
+ HostOnlyNetwork.queryInterfaceTo(aNetwork.asOutParam());
+ }
+
+ return rc;
+#else /* !VBOX_WITH_VMNET */
+ NOREF(aNetworkName);
+ NOREF(aNetwork);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_VMNET */
+}
+
+HRESULT VirtualBox::findHostOnlyNetworkByName(const com::Utf8Str &aNetworkName,
+ ComPtr<IHostOnlyNetwork> &aNetwork)
+{
+#ifdef VBOX_WITH_VMNET
+ Bstr bstrNameToFind(aNetworkName);
+
+ AutoReadLock alock(m->allHostOnlyNetworks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ for (HostOnlyNetworksOList::const_iterator it = m->allHostOnlyNetworks.begin();
+ it != m->allHostOnlyNetworks.end();
+ ++it)
+ {
+ Bstr bstrHostOnlyNetworkName;
+ HRESULT hrc = (*it)->COMGETTER(NetworkName)(bstrHostOnlyNetworkName.asOutParam());
+ if (FAILED(hrc)) return hrc;
+
+ if (bstrHostOnlyNetworkName == bstrNameToFind)
+ {
+ it->queryInterfaceTo(aNetwork.asOutParam());
+ return S_OK;
+ }
+ }
+ return VBOX_E_OBJECT_NOT_FOUND;
+#else /* !VBOX_WITH_VMNET */
+ NOREF(aNetworkName);
+ NOREF(aNetwork);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_VMNET */
+}
+
+HRESULT VirtualBox::findHostOnlyNetworkById(const com::Guid &aId,
+ ComPtr<IHostOnlyNetwork> &aNetwork)
+{
+#ifdef VBOX_WITH_VMNET
+ ComObjPtr<HostOnlyNetwork> network;
+ AutoReadLock alock(m->allHostOnlyNetworks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ for (HostOnlyNetworksOList::const_iterator it = m->allHostOnlyNetworks.begin();
+ it != m->allHostOnlyNetworks.end();
+ ++it)
+ {
+ Bstr bstrHostOnlyNetworkId;
+ HRESULT hrc = (*it)->COMGETTER(Id)(bstrHostOnlyNetworkId.asOutParam());
+ if (FAILED(hrc)) return hrc;
+
+ if (Guid(bstrHostOnlyNetworkId) == aId)
+ {
+ it->queryInterfaceTo(aNetwork.asOutParam());;
+ return S_OK;
+ }
+ }
+ return VBOX_E_OBJECT_NOT_FOUND;
+#else /* !VBOX_WITH_VMNET */
+ NOREF(aId);
+ NOREF(aNetwork);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_VMNET */
+}
+
+HRESULT VirtualBox::removeHostOnlyNetwork(const ComPtr<IHostOnlyNetwork> &aNetwork)
+{
+#ifdef VBOX_WITH_VMNET
+ Bstr name;
+ HRESULT rc = aNetwork->COMGETTER(NetworkName)(name.asOutParam());
+ if (FAILED(rc))
+ return rc;
+ IHostOnlyNetwork *p = aNetwork;
+ HostOnlyNetwork *network = static_cast<HostOnlyNetwork *>(p);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoCaller HostOnlyNetworkCaller(network);
+ AssertComRCReturnRC(HostOnlyNetworkCaller.rc());
+
+ m->allHostOnlyNetworks.removeChild(network);
+
+ {
+ AutoWriteLock vboxLock(this COMMA_LOCKVAL_SRC_POS);
+ rc = i_saveSettings();
+ vboxLock.release();
+
+ if (FAILED(rc))
+ m->allHostOnlyNetworks.addChild(network);
+ }
+ return rc;
+#else /* !VBOX_WITH_VMNET */
+ NOREF(aNetwork);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_VMNET */
+}
+
+HRESULT VirtualBox::getHostOnlyNetworks(std::vector<ComPtr<IHostOnlyNetwork> > &aHostOnlyNetworks)
+{
+#ifdef VBOX_WITH_VMNET
+ AutoReadLock al(m->allHostOnlyNetworks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ aHostOnlyNetworks.resize(m->allHostOnlyNetworks.size());
+ size_t i = 0;
+ for (HostOnlyNetworksOList::const_iterator it = m->allHostOnlyNetworks.begin();
+ it != m->allHostOnlyNetworks.end(); ++it)
+ (*it).queryInterfaceTo(aHostOnlyNetworks[i++].asOutParam());
+ return S_OK;
+#else /* !VBOX_WITH_VMNET */
+ NOREF(aHostOnlyNetworks);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_VMNET */
+}
+
+
+HRESULT VirtualBox::getInternalNetworks(std::vector<com::Utf8Str> &aInternalNetworks)
+{
+ std::list<com::Utf8Str> allInternalNetworks;
+
+ /* get copy of all machine references, to avoid holding the list lock */
+ MachinesOList::MyList allMachines;
+ {
+ AutoReadLock al(m->allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ allMachines = m->allMachines.getList();
+ }
+ for (MachinesOList::MyList::const_iterator it = allMachines.begin();
+ it != allMachines.end(); ++it)
+ {
+ const ComObjPtr<Machine> &pMachine = *it;
+ AutoCaller autoMachineCaller(pMachine);
+ if (FAILED(autoMachineCaller.rc()))
+ continue;
+ AutoReadLock mlock(pMachine COMMA_LOCKVAL_SRC_POS);
+
+ if (pMachine->i_isAccessible())
+ {
+ uint32_t cNetworkAdapters = Global::getMaxNetworkAdapters(pMachine->i_getChipsetType());
+ for (ULONG i = 0; i < cNetworkAdapters; i++)
+ {
+ ComPtr<INetworkAdapter> pNet;
+ HRESULT rc = pMachine->GetNetworkAdapter(i, pNet.asOutParam());
+ if (FAILED(rc) || pNet.isNull())
+ continue;
+ Bstr strInternalNetwork;
+ rc = pNet->COMGETTER(InternalNetwork)(strInternalNetwork.asOutParam());
+ if (FAILED(rc) || strInternalNetwork.isEmpty())
+ continue;
+
+ allInternalNetworks.push_back(Utf8Str(strInternalNetwork));
+ }
+ }
+ }
+
+ /* throw out any duplicates */
+ allInternalNetworks.sort();
+ allInternalNetworks.unique();
+ size_t i = 0;
+ aInternalNetworks.resize(allInternalNetworks.size());
+ for (std::list<com::Utf8Str>::const_iterator it = allInternalNetworks.begin();
+ it != allInternalNetworks.end();
+ ++it, ++i)
+ aInternalNetworks[i] = *it;
+ return S_OK;
+}
+
+HRESULT VirtualBox::getGenericNetworkDrivers(std::vector<com::Utf8Str> &aGenericNetworkDrivers)
+{
+ std::list<com::Utf8Str> allGenericNetworkDrivers;
+
+ /* get copy of all machine references, to avoid holding the list lock */
+ MachinesOList::MyList allMachines;
+ {
+ AutoReadLock al(m->allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ allMachines = m->allMachines.getList();
+ }
+ for (MachinesOList::MyList::const_iterator it = allMachines.begin();
+ it != allMachines.end();
+ ++it)
+ {
+ const ComObjPtr<Machine> &pMachine = *it;
+ AutoCaller autoMachineCaller(pMachine);
+ if (FAILED(autoMachineCaller.rc()))
+ continue;
+ AutoReadLock mlock(pMachine COMMA_LOCKVAL_SRC_POS);
+
+ if (pMachine->i_isAccessible())
+ {
+ uint32_t cNetworkAdapters = Global::getMaxNetworkAdapters(pMachine->i_getChipsetType());
+ for (ULONG i = 0; i < cNetworkAdapters; i++)
+ {
+ ComPtr<INetworkAdapter> pNet;
+ HRESULT rc = pMachine->GetNetworkAdapter(i, pNet.asOutParam());
+ if (FAILED(rc) || pNet.isNull())
+ continue;
+ Bstr strGenericNetworkDriver;
+ rc = pNet->COMGETTER(GenericDriver)(strGenericNetworkDriver.asOutParam());
+ if (FAILED(rc) || strGenericNetworkDriver.isEmpty())
+ continue;
+
+ allGenericNetworkDrivers.push_back(Utf8Str(strGenericNetworkDriver).c_str());
+ }
+ }
+ }
+
+ /* throw out any duplicates */
+ allGenericNetworkDrivers.sort();
+ allGenericNetworkDrivers.unique();
+ aGenericNetworkDrivers.resize(allGenericNetworkDrivers.size());
+ size_t i = 0;
+ for (std::list<com::Utf8Str>::const_iterator it = allGenericNetworkDrivers.begin();
+ it != allGenericNetworkDrivers.end(); ++it, ++i)
+ aGenericNetworkDrivers[i] = *it;
+
+ return S_OK;
+}
+
+/**
+ * Cloud Network
+ */
+#ifdef VBOX_WITH_CLOUD_NET
+HRESULT VirtualBox::i_findCloudNetworkByName(const com::Utf8Str &aNetworkName,
+ ComObjPtr<CloudNetwork> *aNetwork)
+{
+ HRESULT rc = VBOX_E_OBJECT_NOT_FOUND;
+ ComPtr<CloudNetwork> found;
+ Bstr bstrNameToFind(aNetworkName);
+
+ AutoReadLock alock(m->allCloudNetworks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ for (CloudNetworksOList::const_iterator it = m->allCloudNetworks.begin();
+ it != m->allCloudNetworks.end();
+ ++it)
+ {
+ Bstr bstrCloudNetworkName;
+ HRESULT hrc = (*it)->COMGETTER(NetworkName)(bstrCloudNetworkName.asOutParam());
+ if (FAILED(hrc)) return hrc;
+
+ if (bstrCloudNetworkName == bstrNameToFind)
+ {
+ *aNetwork = *it;
+ rc = S_OK;
+ break;
+ }
+ }
+ return rc;
+}
+#endif /* VBOX_WITH_CLOUD_NET */
+
+HRESULT VirtualBox::createCloudNetwork(const com::Utf8Str &aNetworkName,
+ ComPtr<ICloudNetwork> &aNetwork)
+{
+#ifdef VBOX_WITH_CLOUD_NET
+ ComObjPtr<CloudNetwork> cloudNetwork;
+ cloudNetwork.createObject();
+ HRESULT rc = cloudNetwork->init(this, aNetworkName);
+ if (FAILED(rc)) return rc;
+
+ m->allCloudNetworks.addChild(cloudNetwork);
+
+ {
+ AutoWriteLock vboxLock(this COMMA_LOCKVAL_SRC_POS);
+ rc = i_saveSettings();
+ vboxLock.release();
+
+ if (FAILED(rc))
+ m->allCloudNetworks.removeChild(cloudNetwork);
+ else
+ cloudNetwork.queryInterfaceTo(aNetwork.asOutParam());
+ }
+
+ return rc;
+#else /* !VBOX_WITH_CLOUD_NET */
+ NOREF(aNetworkName);
+ NOREF(aNetwork);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_CLOUD_NET */
+}
+
+HRESULT VirtualBox::findCloudNetworkByName(const com::Utf8Str &aNetworkName,
+ ComPtr<ICloudNetwork> &aNetwork)
+{
+#ifdef VBOX_WITH_CLOUD_NET
+ ComObjPtr<CloudNetwork> network;
+ HRESULT hrc = i_findCloudNetworkByName(aNetworkName, &network);
+ if (SUCCEEDED(hrc))
+ network.queryInterfaceTo(aNetwork.asOutParam());
+ return hrc;
+#else /* !VBOX_WITH_CLOUD_NET */
+ NOREF(aNetworkName);
+ NOREF(aNetwork);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_CLOUD_NET */
+}
+
+HRESULT VirtualBox::removeCloudNetwork(const ComPtr<ICloudNetwork> &aNetwork)
+{
+#ifdef VBOX_WITH_CLOUD_NET
+ Bstr name;
+ HRESULT rc = aNetwork->COMGETTER(NetworkName)(name.asOutParam());
+ if (FAILED(rc))
+ return rc;
+ ICloudNetwork *p = aNetwork;
+ CloudNetwork *network = static_cast<CloudNetwork *>(p);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoCaller cloudNetworkCaller(network);
+ AssertComRCReturnRC(cloudNetworkCaller.rc());
+
+ m->allCloudNetworks.removeChild(network);
+
+ {
+ AutoWriteLock vboxLock(this COMMA_LOCKVAL_SRC_POS);
+ rc = i_saveSettings();
+ vboxLock.release();
+
+ if (FAILED(rc))
+ m->allCloudNetworks.addChild(network);
+ }
+ return rc;
+#else /* !VBOX_WITH_CLOUD_NET */
+ NOREF(aNetwork);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_CLOUD_NET */
+}
+
+HRESULT VirtualBox::getCloudNetworks(std::vector<ComPtr<ICloudNetwork> > &aCloudNetworks)
+{
+#ifdef VBOX_WITH_CLOUD_NET
+ AutoReadLock al(m->allCloudNetworks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ aCloudNetworks.resize(m->allCloudNetworks.size());
+ size_t i = 0;
+ for (CloudNetworksOList::const_iterator it = m->allCloudNetworks.begin();
+ it != m->allCloudNetworks.end(); ++it)
+ (*it).queryInterfaceTo(aCloudNetworks[i++].asOutParam());
+ return S_OK;
+#else /* !VBOX_WITH_CLOUD_NET */
+ NOREF(aCloudNetworks);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_CLOUD_NET */
+}
+
+#ifdef VBOX_WITH_CLOUD_NET
+HRESULT VirtualBox::i_getEventSource(ComPtr<IEventSource>& aSource)
+{
+ m->pEventSource.queryInterfaceTo(aSource.asOutParam());
+ return S_OK;
+}
+#endif /* VBOX_WITH_CLOUD_NET */
+
+HRESULT VirtualBox::getCloudProviderManager(ComPtr<ICloudProviderManager> &aCloudProviderManager)
+{
+ HRESULT hrc = m->pCloudProviderManager.queryInterfaceTo(aCloudProviderManager.asOutParam());
+ return hrc;
+}
+
+HRESULT VirtualBox::checkFirmwarePresent(FirmwareType_T aFirmwareType,
+ const com::Utf8Str &aVersion,
+ com::Utf8Str &aUrl,
+ com::Utf8Str &aFile,
+ BOOL *aResult)
+{
+ NOREF(aVersion);
+
+ static const struct
+ {
+ FirmwareType_T enmType;
+ bool fBuiltIn;
+ const char *pszFileName;
+ const char *pszUrl;
+ }
+ firmwareDesc[] =
+ {
+ { FirmwareType_BIOS, true, NULL, NULL },
+#ifdef VBOX_WITH_EFI_IN_DD2
+ { FirmwareType_EFI32, true, "VBoxEFI32.fd", NULL },
+ { FirmwareType_EFI64, true, "VBoxEFI64.fd", NULL },
+ { FirmwareType_EFIDUAL, true, "VBoxEFIDual.fd", NULL },
+#else
+ { FirmwareType_EFI32, false, "VBoxEFI32.fd", "http://virtualbox.org/firmware/VBoxEFI32.fd" },
+ { FirmwareType_EFI64, false, "VBoxEFI64.fd", "http://virtualbox.org/firmware/VBoxEFI64.fd" },
+ { FirmwareType_EFIDUAL, false, "VBoxEFIDual.fd", "http://virtualbox.org/firmware/VBoxEFIDual.fd" },
+#endif
+ };
+
+ for (size_t i = 0; i < sizeof(firmwareDesc) / sizeof(firmwareDesc[0]); i++)
+ {
+ if (aFirmwareType != firmwareDesc[i].enmType)
+ continue;
+
+ /* compiled-in firmware */
+ if (firmwareDesc[i].fBuiltIn)
+ {
+ aFile = firmwareDesc[i].pszFileName;
+ *aResult = TRUE;
+ break;
+ }
+
+ Utf8Str fullName;
+ Utf8StrFmt shortName("Firmware%c%s", RTPATH_DELIMITER, firmwareDesc[i].pszFileName);
+ int rc = i_calculateFullPath(shortName, fullName);
+ AssertRCReturn(rc, VBOX_E_IPRT_ERROR);
+ if (RTFileExists(fullName.c_str()))
+ {
+ *aResult = TRUE;
+ aFile = fullName;
+ break;
+ }
+
+ char szVBoxPath[RTPATH_MAX];
+ rc = RTPathExecDir(szVBoxPath, RTPATH_MAX);
+ AssertRCReturn(rc, VBOX_E_IPRT_ERROR);
+ rc = RTPathAppend(szVBoxPath, sizeof(szVBoxPath), firmwareDesc[i].pszFileName);
+ if (RTFileExists(szVBoxPath))
+ {
+ *aResult = TRUE;
+ aFile = szVBoxPath;
+ break;
+ }
+
+ /** @todo account for version in the URL */
+ aUrl = firmwareDesc[i].pszUrl;
+ *aResult = FALSE;
+
+ /* Assume single record per firmware type */
+ break;
+ }
+
+ return S_OK;
+}
+// Wrapped IVirtualBox methods
+/////////////////////////////////////////////////////////////////////////////
+
+/* Helper for VirtualBox::ComposeMachineFilename */
+static void sanitiseMachineFilename(Utf8Str &aName);
+
+HRESULT VirtualBox::composeMachineFilename(const com::Utf8Str &aName,
+ const com::Utf8Str &aGroup,
+ const com::Utf8Str &aCreateFlags,
+ const com::Utf8Str &aBaseFolder,
+ com::Utf8Str &aFile)
+{
+ if (RT_UNLIKELY(aName.isEmpty()))
+ return setError(E_INVALIDARG, tr("Machine name is invalid, must not be empty"));
+
+ Utf8Str strBase = aBaseFolder;
+ Utf8Str strName = aName;
+
+ LogFlowThisFunc(("aName=\"%s\",aBaseFolder=\"%s\"\n", strName.c_str(), strBase.c_str()));
+
+ com::Guid id;
+ bool fDirectoryIncludesUUID = false;
+ if (!aCreateFlags.isEmpty())
+ {
+ size_t uPos = 0;
+ com::Utf8Str strKey;
+ com::Utf8Str strValue;
+ while ((uPos = aCreateFlags.parseKeyValue(strKey, strValue, uPos)) != com::Utf8Str::npos)
+ {
+ if (strKey == "UUID")
+ id = strValue.c_str();
+ else if (strKey == "directoryIncludesUUID")
+ fDirectoryIncludesUUID = (strValue == "1");
+ }
+ }
+
+ if (id.isZero())
+ fDirectoryIncludesUUID = false;
+ else if (!id.isValid())
+ {
+ /* do something else */
+ return setError(E_INVALIDARG,
+ tr("'%s' is not a valid Guid"),
+ id.toStringCurly().c_str());
+ }
+
+ Utf8Str strGroup(aGroup);
+ if (strGroup.isEmpty())
+ strGroup = "/";
+ HRESULT rc = i_validateMachineGroup(strGroup, true);
+ if (FAILED(rc))
+ return rc;
+
+ /* Compose the settings file name using the following scheme:
+ *
+ * <base_folder><group>/<machine_name>/<machine_name>.xml
+ *
+ * If a non-null and non-empty base folder is specified, the default
+ * machine folder will be used as a base folder.
+ * We sanitise the machine name to a safe white list of characters before
+ * using it.
+ */
+ Utf8Str strDirName(strName);
+ if (fDirectoryIncludesUUID)
+ strDirName += Utf8StrFmt(" (%RTuuid)", id.raw());
+ sanitiseMachineFilename(strName);
+ sanitiseMachineFilename(strDirName);
+
+ if (strBase.isEmpty())
+ /* we use the non-full folder value below to keep the path relative */
+ i_getDefaultMachineFolder(strBase);
+
+ i_calculateFullPath(strBase, strBase);
+
+ /* eliminate toplevel group to avoid // in the result */
+ if (strGroup == "/")
+ strGroup.setNull();
+ aFile = com::Utf8StrFmt("%s%s%c%s%c%s.vbox",
+ strBase.c_str(),
+ strGroup.c_str(),
+ RTPATH_DELIMITER,
+ strDirName.c_str(),
+ RTPATH_DELIMITER,
+ strName.c_str());
+ return S_OK;
+}
+
+/**
+ * Remove characters from a machine file name which can be problematic on
+ * particular systems.
+ * @param strName The file name to sanitise.
+ */
+void sanitiseMachineFilename(Utf8Str &strName)
+{
+ if (strName.isEmpty())
+ return;
+
+ /* Set of characters which should be safe for use in filenames: some basic
+ * ASCII, Unicode from Latin-1 alphabetic to the end of Hangul. We try to
+ * skip anything that could count as a control character in Windows or
+ * *nix, or be otherwise difficult for shells to handle (I would have
+ * preferred to remove the space and brackets too). We also remove all
+ * characters which need UTF-16 surrogate pairs for Windows's benefit.
+ */
+ static RTUNICP const s_uszValidRangePairs[] =
+ {
+ ' ', ' ',
+ '(', ')',
+ '-', '.',
+ '0', '9',
+ 'A', 'Z',
+ 'a', 'z',
+ '_', '_',
+ 0xa0, 0xd7af,
+ '\0'
+ };
+
+ char *pszName = strName.mutableRaw();
+ ssize_t cReplacements = RTStrPurgeComplementSet(pszName, s_uszValidRangePairs, '_');
+ Assert(cReplacements >= 0);
+ NOREF(cReplacements);
+
+ /* No leading dot or dash. */
+ if (pszName[0] == '.' || pszName[0] == '-')
+ pszName[0] = '_';
+
+ /* No trailing dot. */
+ if (pszName[strName.length() - 1] == '.')
+ pszName[strName.length() - 1] = '_';
+
+ /* Mangle leading and trailing spaces. */
+ for (size_t i = 0; pszName[i] == ' '; ++i)
+ pszName[i] = '_';
+ for (size_t i = strName.length() - 1; i && pszName[i] == ' '; --i)
+ pszName[i] = '_';
+}
+
+#ifdef DEBUG
+typedef DECLCALLBACKTYPE(void, FNTESTPRINTF,(const char *, ...));
+/** Simple unit test/operation examples for sanitiseMachineFilename(). */
+static unsigned testSanitiseMachineFilename(FNTESTPRINTF *pfnPrintf)
+{
+ unsigned cErrors = 0;
+
+ /** Expected results of sanitising given file names. */
+ static struct
+ {
+ /** The test file name to be sanitised (Utf-8). */
+ const char *pcszIn;
+ /** The expected sanitised output (Utf-8). */
+ const char *pcszOutExpected;
+ } aTest[] =
+ {
+ { "OS/2 2.1", "OS_2 2.1" },
+ { "-!My VM!-", "__My VM_-" },
+ { "\xF0\x90\x8C\xB0", "____" },
+ { " My VM ", "__My VM__" },
+ { ".My VM.", "_My VM_" },
+ { "My VM", "My VM" }
+ };
+ for (unsigned i = 0; i < RT_ELEMENTS(aTest); ++i)
+ {
+ Utf8Str str(aTest[i].pcszIn);
+ sanitiseMachineFilename(str);
+ if (str.compare(aTest[i].pcszOutExpected))
+ {
+ ++cErrors;
+ pfnPrintf("%s: line %d, expected %s, actual %s\n",
+ __PRETTY_FUNCTION__, i, aTest[i].pcszOutExpected,
+ str.c_str());
+ }
+ }
+ return cErrors;
+}
+
+/** @todo Proper testcase. */
+/** @todo Do we have a better method of doing init functions? */
+namespace
+{
+ class TestSanitiseMachineFilename
+ {
+ public:
+ TestSanitiseMachineFilename(void)
+ {
+ Assert(!testSanitiseMachineFilename(RTAssertMsg2));
+ }
+ };
+ TestSanitiseMachineFilename s_TestSanitiseMachineFilename;
+}
+#endif
+
+/** @note Locks mSystemProperties object for reading. */
+HRESULT VirtualBox::createMachine(const com::Utf8Str &aSettingsFile,
+ const com::Utf8Str &aName,
+ const std::vector<com::Utf8Str> &aGroups,
+ const com::Utf8Str &aOsTypeId,
+ const com::Utf8Str &aFlags,
+ const com::Utf8Str &aCipher,
+ const com::Utf8Str &aPasswordId,
+ const com::Utf8Str &aPassword,
+ ComPtr<IMachine> &aMachine)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aSettingsFile=\"%s\", aName=\"%s\", aOsTypeId =\"%s\", aCreateFlags=\"%s\"\n",
+ aSettingsFile.c_str(), aName.c_str(), aOsTypeId.c_str(), aFlags.c_str()));
+
+ StringsList llGroups;
+ HRESULT rc = i_convertMachineGroups(aGroups, &llGroups);
+ if (FAILED(rc))
+ return rc;
+
+ /** @todo r=bird: Would be goot to rewrite this parsing using offset into
+ * aFlags and drop all the C pointers, strchr, misguided RTStrStr and
+ * tedious copying of substrings. */
+ Utf8Str strCreateFlags(aFlags); /** @todo r=bird: WTF is the point of this copy? */
+ Guid id;
+ bool fForceOverwrite = false;
+ bool fDirectoryIncludesUUID = false;
+ if (!strCreateFlags.isEmpty())
+ {
+ const char *pcszNext = strCreateFlags.c_str();
+ while (*pcszNext != '\0')
+ {
+ Utf8Str strFlag;
+ const char *pcszComma = strchr(pcszNext, ','); /*clueless version: RTStrStr(pcszNext, ","); */
+ if (!pcszComma)
+ strFlag = pcszNext;
+ else
+ strFlag.assign(pcszNext, (size_t)(pcszComma - pcszNext));
+
+ const char *pcszEqual = strchr(strFlag.c_str(), '='); /* more cluelessness: RTStrStr(strFlag.c_str(), "="); */
+ /* skip over everything which doesn't contain '=' */
+ if (pcszEqual && pcszEqual != strFlag.c_str())
+ {
+ Utf8Str strKey(strFlag.c_str(), (size_t)(pcszEqual - strFlag.c_str()));
+ Utf8Str strValue(strFlag.c_str() + (pcszEqual - strFlag.c_str() + 1));
+
+ if (strKey == "UUID")
+ id = strValue.c_str();
+ else if (strKey == "forceOverwrite")
+ fForceOverwrite = (strValue == "1");
+ else if (strKey == "directoryIncludesUUID")
+ fDirectoryIncludesUUID = (strValue == "1");
+ }
+
+ if (!pcszComma)
+ pcszNext += strFlag.length(); /* you can just 'break' out here... */
+ else
+ pcszNext += strFlag.length() + 1;
+ }
+ }
+
+ /* Create UUID if none was specified. */
+ if (id.isZero())
+ id.create();
+ else if (!id.isValid())
+ {
+ /* do something else */
+ return setError(E_INVALIDARG,
+ tr("'%s' is not a valid Guid"),
+ id.toStringCurly().c_str());
+ }
+
+ /* NULL settings file means compose automatically */
+ Utf8Str strSettingsFile(aSettingsFile);
+ if (strSettingsFile.isEmpty())
+ {
+ Utf8Str strNewCreateFlags(Utf8StrFmt("UUID=%RTuuid", id.raw()));
+ if (fDirectoryIncludesUUID)
+ strNewCreateFlags += ",directoryIncludesUUID=1";
+
+ com::Utf8Str blstr;
+ rc = composeMachineFilename(aName,
+ llGroups.front(),
+ strNewCreateFlags,
+ blstr /* aBaseFolder */,
+ strSettingsFile);
+ if (FAILED(rc)) return rc;
+ }
+
+ /* create a new object */
+ ComObjPtr<Machine> machine;
+ rc = machine.createObject();
+ if (FAILED(rc)) return rc;
+
+ ComObjPtr<GuestOSType> osType;
+ if (!aOsTypeId.isEmpty())
+ i_findGuestOSType(aOsTypeId, osType);
+
+ /* initialize the machine object */
+ rc = machine->init(this,
+ strSettingsFile,
+ aName,
+ llGroups,
+ aOsTypeId,
+ osType,
+ id,
+ fForceOverwrite,
+ fDirectoryIncludesUUID,
+ aCipher,
+ aPasswordId,
+ aPassword);
+ if (SUCCEEDED(rc))
+ {
+ /* set the return value */
+ machine.queryInterfaceTo(aMachine.asOutParam());
+ AssertComRC(rc);
+
+#ifdef VBOX_WITH_EXTPACK
+ /* call the extension pack hooks */
+ m->ptrExtPackManager->i_callAllVmCreatedHooks(machine);
+#endif
+ }
+
+ LogFlowThisFuncLeave();
+
+ return rc;
+}
+
+HRESULT VirtualBox::openMachine(const com::Utf8Str &aSettingsFile,
+ const com::Utf8Str &aPassword,
+ ComPtr<IMachine> &aMachine)
+{
+ HRESULT rc = E_FAIL;
+
+ /* create a new object */
+ ComObjPtr<Machine> machine;
+ rc = machine.createObject();
+ if (SUCCEEDED(rc))
+ {
+ /* initialize the machine object */
+ rc = machine->initFromSettings(this,
+ aSettingsFile,
+ NULL, /* const Guid *aId */
+ aPassword);
+ if (SUCCEEDED(rc))
+ {
+ /* set the return value */
+ machine.queryInterfaceTo(aMachine.asOutParam());
+ ComAssertComRC(rc);
+ }
+ }
+
+ return rc;
+}
+
+/** @note Locks objects! */
+HRESULT VirtualBox::registerMachine(const ComPtr<IMachine> &aMachine)
+{
+ HRESULT rc;
+
+ Bstr name;
+ rc = aMachine->COMGETTER(Name)(name.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ /* We can safely cast child to Machine * here because only Machine
+ * implementations of IMachine can be among our children. */
+ IMachine *aM = aMachine;
+ Machine *pMachine = static_cast<Machine*>(aM);
+
+ AutoCaller machCaller(pMachine);
+ ComAssertComRCRetRC(machCaller.rc());
+
+ rc = i_registerMachine(pMachine);
+ /* fire an event */
+ if (SUCCEEDED(rc))
+ i_onMachineRegistered(pMachine->i_getId(), TRUE);
+
+ return rc;
+}
+
+/** @note Locks this object for reading, then some machine objects for reading. */
+HRESULT VirtualBox::findMachine(const com::Utf8Str &aSettingsFile,
+ ComPtr<IMachine> &aMachine)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aSettingsFile=\"%s\", aMachine={%p}\n", aSettingsFile.c_str(), &aMachine));
+
+ /* start with not found */
+ HRESULT rc = S_OK;
+ ComObjPtr<Machine> pMachineFound;
+
+ Guid id(aSettingsFile);
+ Utf8Str strFile(aSettingsFile);
+ if (id.isValid() && !id.isZero())
+
+ rc = i_findMachine(id,
+ true /* fPermitInaccessible */,
+ true /* setError */,
+ &pMachineFound);
+ // returns VBOX_E_OBJECT_NOT_FOUND if not found and sets error
+ else
+ {
+ rc = i_findMachineByName(strFile,
+ true /* setError */,
+ &pMachineFound);
+ // returns VBOX_E_OBJECT_NOT_FOUND if not found and sets error
+ }
+
+ /* this will set (*machine) to NULL if machineObj is null */
+ pMachineFound.queryInterfaceTo(aMachine.asOutParam());
+
+ LogFlowThisFunc(("aName=\"%s\", aMachine=%p, rc=%08X\n", aSettingsFile.c_str(), &aMachine, rc));
+ LogFlowThisFuncLeave();
+
+ return rc;
+}
+
+HRESULT VirtualBox::getMachinesByGroups(const std::vector<com::Utf8Str> &aGroups,
+ std::vector<ComPtr<IMachine> > &aMachines)
+{
+ StringsList llGroups;
+ HRESULT rc = i_convertMachineGroups(aGroups, &llGroups);
+ if (FAILED(rc))
+ return rc;
+
+ /* we want to rely on sorted groups during compare, to save time */
+ llGroups.sort();
+
+ /* get copy of all machine references, to avoid holding the list lock */
+ MachinesOList::MyList allMachines;
+ AutoReadLock al(m->allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ allMachines = m->allMachines.getList();
+
+ std::vector<ComObjPtr<IMachine> > saMachines;
+ saMachines.resize(0);
+ for (MachinesOList::MyList::const_iterator it = allMachines.begin();
+ it != allMachines.end();
+ ++it)
+ {
+ const ComObjPtr<Machine> &pMachine = *it;
+ AutoCaller autoMachineCaller(pMachine);
+ if (FAILED(autoMachineCaller.rc()))
+ continue;
+ AutoReadLock mlock(pMachine COMMA_LOCKVAL_SRC_POS);
+
+ if (pMachine->i_isAccessible())
+ {
+ const StringsList &thisGroups = pMachine->i_getGroups();
+ for (StringsList::const_iterator it2 = thisGroups.begin();
+ it2 != thisGroups.end();
+ ++it2)
+ {
+ const Utf8Str &group = *it2;
+ bool fAppended = false;
+ for (StringsList::const_iterator it3 = llGroups.begin();
+ it3 != llGroups.end();
+ ++it3)
+ {
+ int order = it3->compare(group);
+ if (order == 0)
+ {
+ saMachines.push_back(static_cast<IMachine *>(pMachine));
+ fAppended = true;
+ break;
+ }
+ else if (order > 0)
+ break;
+ else
+ continue;
+ }
+ /* avoid duplicates and save time */
+ if (fAppended)
+ break;
+ }
+ }
+ }
+ aMachines.resize(saMachines.size());
+ size_t i = 0;
+ for(i = 0; i < saMachines.size(); ++i)
+ saMachines[i].queryInterfaceTo(aMachines[i].asOutParam());
+
+ return S_OK;
+}
+
+HRESULT VirtualBox::getMachineStates(const std::vector<ComPtr<IMachine> > &aMachines,
+ std::vector<MachineState_T> &aStates)
+{
+ com::SafeIfaceArray<IMachine> saMachines(aMachines);
+ aStates.resize(aMachines.size());
+ for (size_t i = 0; i < saMachines.size(); i++)
+ {
+ ComPtr<IMachine> pMachine = saMachines[i];
+ MachineState_T state = MachineState_Null;
+ if (!pMachine.isNull())
+ {
+ HRESULT rc = pMachine->COMGETTER(State)(&state);
+ if (rc == E_ACCESSDENIED)
+ rc = S_OK;
+ AssertComRC(rc);
+ }
+ aStates[i] = state;
+ }
+ return S_OK;
+}
+
+HRESULT VirtualBox::createUnattendedInstaller(ComPtr<IUnattended> &aUnattended)
+{
+#ifdef VBOX_WITH_UNATTENDED
+ ComObjPtr<Unattended> ptrUnattended;
+ HRESULT hrc = ptrUnattended.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock wlock(this COMMA_LOCKVAL_SRC_POS);
+ hrc = ptrUnattended->initUnattended(this);
+ if (SUCCEEDED(hrc))
+ hrc = ptrUnattended.queryInterfaceTo(aUnattended.asOutParam());
+ }
+ return hrc;
+#else
+ NOREF(aUnattended);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT VirtualBox::createMedium(const com::Utf8Str &aFormat,
+ const com::Utf8Str &aLocation,
+ AccessMode_T aAccessMode,
+ DeviceType_T aDeviceType,
+ ComPtr<IMedium> &aMedium)
+{
+ NOREF(aAccessMode); /**< @todo r=klaus make use of access mode */
+
+ HRESULT rc = S_OK;
+
+ ComObjPtr<Medium> medium;
+ medium.createObject();
+ com::Utf8Str format = aFormat;
+
+ switch (aDeviceType)
+ {
+ case DeviceType_HardDisk:
+ {
+
+ /* we don't access non-const data members so no need to lock */
+ if (format.isEmpty())
+ i_getDefaultHardDiskFormat(format);
+
+ rc = medium->init(this,
+ format,
+ aLocation,
+ Guid::Empty /* media registry: none yet */,
+ aDeviceType);
+ }
+ break;
+
+ case DeviceType_DVD:
+ case DeviceType_Floppy:
+ {
+
+ if (format.isEmpty())
+ return setError(E_INVALIDARG, tr("Format must be Valid Type%s"), format.c_str());
+
+ // enforce read-only for DVDs even if caller specified ReadWrite
+ if (aDeviceType == DeviceType_DVD)
+ aAccessMode = AccessMode_ReadOnly;
+
+ rc = medium->init(this,
+ format,
+ aLocation,
+ Guid::Empty /* media registry: none yet */,
+ aDeviceType);
+
+ }
+ break;
+
+ default:
+ return setError(E_INVALIDARG, tr("Device type must be HardDisk, DVD or Floppy %d"), aDeviceType);
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ medium.queryInterfaceTo(aMedium.asOutParam());
+ com::Guid uMediumId = medium->i_getId();
+ if (uMediumId.isValid() && !uMediumId.isZero())
+ i_onMediumRegistered(uMediumId, medium->i_getDeviceType(), TRUE);
+ }
+
+ return rc;
+}
+
+HRESULT VirtualBox::openMedium(const com::Utf8Str &aLocation,
+ DeviceType_T aDeviceType,
+ AccessMode_T aAccessMode,
+ BOOL aForceNewUuid,
+ ComPtr<IMedium> &aMedium)
+{
+ HRESULT rc = S_OK;
+ Guid id(aLocation);
+ ComObjPtr<Medium> pMedium;
+
+ // have to get write lock as the whole find/update sequence must be done
+ // in one critical section, otherwise there are races which can lead to
+ // multiple Medium objects with the same content
+ AutoWriteLock treeLock(i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ // check if the device type is correct, and see if a medium for the
+ // given path has already initialized; if so, return that
+ switch (aDeviceType)
+ {
+ case DeviceType_HardDisk:
+ if (id.isValid() && !id.isZero())
+ rc = i_findHardDiskById(id, false /* setError */, &pMedium);
+ else
+ rc = i_findHardDiskByLocation(aLocation,
+ false, /* aSetError */
+ &pMedium);
+ break;
+
+ case DeviceType_Floppy:
+ case DeviceType_DVD:
+ if (id.isValid() && !id.isZero())
+ rc = i_findDVDOrFloppyImage(aDeviceType, &id, Utf8Str::Empty,
+ false /* setError */, &pMedium);
+ else
+ rc = i_findDVDOrFloppyImage(aDeviceType, NULL, aLocation,
+ false /* setError */, &pMedium);
+
+ // enforce read-only for DVDs even if caller specified ReadWrite
+ if (aDeviceType == DeviceType_DVD)
+ aAccessMode = AccessMode_ReadOnly;
+ break;
+
+ default:
+ return setError(E_INVALIDARG, tr("Device type must be HardDisk, DVD or Floppy %d"), aDeviceType);
+ }
+
+ bool fMediumRegistered = false;
+ if (pMedium.isNull())
+ {
+ pMedium.createObject();
+ treeLock.release();
+ rc = pMedium->init(this,
+ aLocation,
+ (aAccessMode == AccessMode_ReadWrite) ? Medium::OpenReadWrite : Medium::OpenReadOnly,
+ !!aForceNewUuid,
+ aDeviceType);
+ treeLock.acquire();
+
+ if (SUCCEEDED(rc))
+ {
+ rc = i_registerMedium(pMedium, &pMedium, treeLock);
+
+ treeLock.release();
+
+ /* Note that it's important to call uninit() on failure to register
+ * because the differencing hard disk would have been already associated
+ * with the parent and this association needs to be broken. */
+
+ if (FAILED(rc))
+ {
+ pMedium->uninit();
+ rc = VBOX_E_OBJECT_NOT_FOUND;
+ }
+ else
+ {
+ fMediumRegistered = true;
+ }
+ }
+ else
+ {
+ if (rc != VBOX_E_INVALID_OBJECT_STATE)
+ rc = VBOX_E_OBJECT_NOT_FOUND;
+ }
+ }
+
+ if (SUCCEEDED(rc))
+ {
+ pMedium.queryInterfaceTo(aMedium.asOutParam());
+ if (fMediumRegistered)
+ i_onMediumRegistered(pMedium->i_getId(), pMedium->i_getDeviceType() ,TRUE);
+ }
+
+ return rc;
+}
+
+
+/** @note Locks this object for reading. */
+HRESULT VirtualBox::getGuestOSType(const com::Utf8Str &aId,
+ ComPtr<IGuestOSType> &aType)
+{
+ ComObjPtr<GuestOSType> pType;
+ HRESULT rc = i_findGuestOSType(aId, pType);
+ pType.queryInterfaceTo(aType.asOutParam());
+ return rc;
+}
+
+HRESULT VirtualBox::createSharedFolder(const com::Utf8Str &aName,
+ const com::Utf8Str &aHostPath,
+ BOOL aWritable,
+ BOOL aAutomount,
+ const com::Utf8Str &aAutoMountPoint)
+{
+ NOREF(aName);
+ NOREF(aHostPath);
+ NOREF(aWritable);
+ NOREF(aAutomount);
+ NOREF(aAutoMountPoint);
+
+ return setError(E_NOTIMPL, tr("Not yet implemented"));
+}
+
+HRESULT VirtualBox::removeSharedFolder(const com::Utf8Str &aName)
+{
+ NOREF(aName);
+ return setError(E_NOTIMPL, tr("Not yet implemented"));
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT VirtualBox::getExtraDataKeys(std::vector<com::Utf8Str> &aKeys)
+{
+ using namespace settings;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aKeys.resize(m->pMainConfigFile->mapExtraDataItems.size());
+ size_t i = 0;
+ for (StringsMap::const_iterator it = m->pMainConfigFile->mapExtraDataItems.begin();
+ it != m->pMainConfigFile->mapExtraDataItems.end(); ++it, ++i)
+ aKeys[i] = it->first;
+
+ return S_OK;
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+HRESULT VirtualBox::getExtraData(const com::Utf8Str &aKey,
+ com::Utf8Str &aValue)
+{
+ settings::StringsMap::const_iterator it = m->pMainConfigFile->mapExtraDataItems.find(aKey);
+ if (it != m->pMainConfigFile->mapExtraDataItems.end())
+ // found:
+ aValue = it->second; // source is a Utf8Str
+
+ /* return the result to caller (may be empty) */
+
+ return S_OK;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+HRESULT VirtualBox::setExtraData(const com::Utf8Str &aKey,
+ const com::Utf8Str &aValue)
+{
+ Utf8Str strKey(aKey);
+ Utf8Str strValue(aValue);
+ Utf8Str strOldValue; // empty
+ HRESULT rc = S_OK;
+
+ /* Because control characters in aKey have caused problems in the settings
+ * they are rejected unless the key should be deleted. */
+ if (!strValue.isEmpty())
+ {
+ for (size_t i = 0; i < strKey.length(); ++i)
+ {
+ char ch = strKey[i];
+ if (RTLocCIsCntrl(ch))
+ return E_INVALIDARG;
+ }
+ }
+
+ // locking note: we only hold the read lock briefly to look up the old value,
+ // then release it and call the onExtraCanChange callbacks. There is a small
+ // chance of a race insofar as the callback might be called twice if two callers
+ // change the same key at the same time, but that's a much better solution
+ // than the deadlock we had here before. The actual changing of the extradata
+ // is then performed under the write lock and race-free.
+
+ // look up the old value first; if nothing has changed then we need not do anything
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); // hold read lock only while looking up
+ settings::StringsMap::const_iterator it = m->pMainConfigFile->mapExtraDataItems.find(strKey);
+ if (it != m->pMainConfigFile->mapExtraDataItems.end())
+ strOldValue = it->second;
+ }
+
+ bool fChanged;
+ if ((fChanged = (strOldValue != strValue)))
+ {
+ // ask for permission from all listeners outside the locks;
+ // onExtraDataCanChange() only briefly requests the VirtualBox
+ // lock to copy the list of callbacks to invoke
+ Bstr error;
+
+ if (!i_onExtraDataCanChange(Guid::Empty, Bstr(aKey).raw(), Bstr(aValue).raw(), error))
+ {
+ const char *sep = error.isEmpty() ? "" : ": ";
+ Log1WarningFunc(("Someone vetoed! Change refused%s%ls\n", sep, error.raw()));
+ return setError(E_ACCESSDENIED,
+ tr("Could not set extra data because someone refused the requested change of '%s' to '%s'%s%ls"),
+ strKey.c_str(),
+ strValue.c_str(),
+ sep,
+ error.raw());
+ }
+
+ // data is changing and change not vetoed: then write it out under the lock
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (strValue.isEmpty())
+ m->pMainConfigFile->mapExtraDataItems.erase(strKey);
+ else
+ m->pMainConfigFile->mapExtraDataItems[strKey] = strValue;
+ // creates a new key if needed
+
+ /* save settings on success */
+ rc = i_saveSettings();
+ if (FAILED(rc)) return rc;
+ }
+
+ // fire notification outside the lock
+ if (fChanged)
+ i_onExtraDataChanged(Guid::Empty, Bstr(aKey).raw(), Bstr(aValue).raw());
+
+ return rc;
+}
+
+/**
+ *
+ */
+HRESULT VirtualBox::setSettingsSecret(const com::Utf8Str &aPassword)
+{
+ i_storeSettingsKey(aPassword);
+ i_decryptSettings();
+ return S_OK;
+}
+
+int VirtualBox::i_decryptMediumSettings(Medium *pMedium)
+{
+ Bstr bstrCipher;
+ HRESULT hrc = pMedium->GetProperty(Bstr("InitiatorSecretEncrypted").raw(),
+ bstrCipher.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Utf8Str strPlaintext;
+ int rc = i_decryptSetting(&strPlaintext, bstrCipher);
+ if (RT_SUCCESS(rc))
+ pMedium->i_setPropertyDirect("InitiatorSecret", strPlaintext);
+ else
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Decrypt all encrypted settings.
+ *
+ * So far we only have encrypted iSCSI initiator secrets so we just go through
+ * all hard disk mediums and determine the plain 'InitiatorSecret' from
+ * 'InitiatorSecretEncrypted. The latter is stored as Base64 because medium
+ * properties need to be null-terminated strings.
+ */
+int VirtualBox::i_decryptSettings()
+{
+ bool fFailure = false;
+ AutoReadLock al(m->allHardDisks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ for (MediaList::const_iterator mt = m->allHardDisks.begin();
+ mt != m->allHardDisks.end();
+ ++mt)
+ {
+ ComObjPtr<Medium> pMedium = *mt;
+ AutoCaller medCaller(pMedium);
+ if (FAILED(medCaller.rc()))
+ continue;
+ AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
+ int vrc = i_decryptMediumSettings(pMedium);
+ if (RT_FAILURE(vrc))
+ fFailure = true;
+ }
+ if (!fFailure)
+ {
+ for (MediaList::const_iterator mt = m->allHardDisks.begin();
+ mt != m->allHardDisks.end();
+ ++mt)
+ {
+ i_onMediumConfigChanged(*mt);
+ }
+ }
+ return fFailure ? VERR_INVALID_PARAMETER : VINF_SUCCESS;
+}
+
+/**
+ * Encode.
+ *
+ * @param aPlaintext plaintext to be encrypted
+ * @param aCiphertext resulting ciphertext (base64-encoded)
+ */
+int VirtualBox::i_encryptSetting(const Utf8Str &aPlaintext, Utf8Str *aCiphertext)
+{
+ uint8_t abCiphertext[32];
+ char szCipherBase64[128];
+ size_t cchCipherBase64;
+ int rc = i_encryptSettingBytes((uint8_t*)aPlaintext.c_str(), abCiphertext,
+ aPlaintext.length()+1, sizeof(abCiphertext));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTBase64Encode(abCiphertext, sizeof(abCiphertext),
+ szCipherBase64, sizeof(szCipherBase64),
+ &cchCipherBase64);
+ if (RT_SUCCESS(rc))
+ *aCiphertext = szCipherBase64;
+ }
+ return rc;
+}
+
+/**
+ * Decode.
+ *
+ * @param aPlaintext resulting plaintext
+ * @param aCiphertext ciphertext (base64-encoded) to decrypt
+ */
+int VirtualBox::i_decryptSetting(Utf8Str *aPlaintext, const Utf8Str &aCiphertext)
+{
+ uint8_t abPlaintext[64];
+ uint8_t abCiphertext[64];
+ size_t cbCiphertext;
+ int rc = RTBase64Decode(aCiphertext.c_str(),
+ abCiphertext, sizeof(abCiphertext),
+ &cbCiphertext, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = i_decryptSettingBytes(abPlaintext, abCiphertext, cbCiphertext);
+ if (RT_SUCCESS(rc))
+ {
+ for (unsigned i = 0; i < cbCiphertext; i++)
+ {
+ /* sanity check: null-terminated string? */
+ if (abPlaintext[i] == '\0')
+ {
+ /* sanity check: valid UTF8 string? */
+ if (RTStrIsValidEncoding((const char*)abPlaintext))
+ {
+ *aPlaintext = Utf8Str((const char*)abPlaintext);
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ rc = VERR_INVALID_MAGIC;
+ }
+ }
+ return rc;
+}
+
+/**
+ * Encrypt secret bytes. Use the m->SettingsCipherKey as key.
+ *
+ * @param aPlaintext clear text to be encrypted
+ * @param aCiphertext resulting encrypted text
+ * @param aPlaintextSize size of the plaintext
+ * @param aCiphertextSize size of the ciphertext
+ */
+int VirtualBox::i_encryptSettingBytes(const uint8_t *aPlaintext, uint8_t *aCiphertext,
+ size_t aPlaintextSize, size_t aCiphertextSize) const
+{
+ unsigned i, j;
+ uint8_t aBytes[64];
+
+ if (!m->fSettingsCipherKeySet)
+ return VERR_INVALID_STATE;
+
+ if (aCiphertextSize > sizeof(aBytes))
+ return VERR_BUFFER_OVERFLOW;
+
+ if (aCiphertextSize < 32)
+ return VERR_INVALID_PARAMETER;
+
+ AssertCompile(sizeof(m->SettingsCipherKey) >= 32);
+
+ /* store the first 8 bytes of the cipherkey for verification */
+ for (i = 0, j = 0; i < 8; i++, j++)
+ aCiphertext[i] = m->SettingsCipherKey[j];
+
+ for (unsigned k = 0; k < aPlaintextSize && i < aCiphertextSize; i++, k++)
+ {
+ aCiphertext[i] = (aPlaintext[k] ^ m->SettingsCipherKey[j]);
+ if (++j >= sizeof(m->SettingsCipherKey))
+ j = 0;
+ }
+
+ /* fill with random data to have a minimal length (salt) */
+ if (i < aCiphertextSize)
+ {
+ RTRandBytes(aBytes, aCiphertextSize - i);
+ for (int k = 0; i < aCiphertextSize; i++, k++)
+ {
+ aCiphertext[i] = aBytes[k] ^ m->SettingsCipherKey[j];
+ if (++j >= sizeof(m->SettingsCipherKey))
+ j = 0;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Decrypt secret bytes. Use the m->SettingsCipherKey as key.
+ *
+ * @param aPlaintext resulting plaintext
+ * @param aCiphertext ciphertext to be decrypted
+ * @param aCiphertextSize size of the ciphertext == size of the plaintext
+ */
+int VirtualBox::i_decryptSettingBytes(uint8_t *aPlaintext,
+ const uint8_t *aCiphertext, size_t aCiphertextSize) const
+{
+ unsigned i, j;
+
+ if (!m->fSettingsCipherKeySet)
+ return VERR_INVALID_STATE;
+
+ if (aCiphertextSize < 32)
+ return VERR_INVALID_PARAMETER;
+
+ /* key verification */
+ for (i = 0, j = 0; i < 8; i++, j++)
+ if (aCiphertext[i] != m->SettingsCipherKey[j])
+ return VERR_INVALID_MAGIC;
+
+ /* poison */
+ memset(aPlaintext, 0xff, aCiphertextSize);
+ for (int k = 0; i < aCiphertextSize; i++, k++)
+ {
+ aPlaintext[k] = aCiphertext[i] ^ m->SettingsCipherKey[j];
+ if (++j >= sizeof(m->SettingsCipherKey))
+ j = 0;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Store a settings key.
+ *
+ * @param aKey the key to store
+ */
+void VirtualBox::i_storeSettingsKey(const Utf8Str &aKey)
+{
+ RTSha512(aKey.c_str(), aKey.length(), m->SettingsCipherKey);
+ m->fSettingsCipherKeySet = true;
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+#ifdef DEBUG
+void VirtualBox::i_dumpAllBackRefs()
+{
+ {
+ AutoReadLock al(m->allHardDisks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ for (MediaList::const_iterator mt = m->allHardDisks.begin();
+ mt != m->allHardDisks.end();
+ ++mt)
+ {
+ ComObjPtr<Medium> pMedium = *mt;
+ pMedium->i_dumpBackRefs();
+ }
+ }
+ {
+ AutoReadLock al(m->allDVDImages.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ for (MediaList::const_iterator mt = m->allDVDImages.begin();
+ mt != m->allDVDImages.end();
+ ++mt)
+ {
+ ComObjPtr<Medium> pMedium = *mt;
+ pMedium->i_dumpBackRefs();
+ }
+ }
+}
+#endif
+
+/**
+ * Posts an event to the event queue that is processed asynchronously
+ * on a dedicated thread.
+ *
+ * Posting events to the dedicated event queue is useful to perform secondary
+ * actions outside any object locks -- for example, to iterate over a list
+ * of callbacks and inform them about some change caused by some object's
+ * method call.
+ *
+ * @param event event to post; must have been allocated using |new|, will
+ * be deleted automatically by the event thread after processing
+ *
+ * @note Doesn't lock any object.
+ */
+HRESULT VirtualBox::i_postEvent(Event *event)
+{
+ AssertReturn(event, E_FAIL);
+
+ HRESULT rc;
+ AutoCaller autoCaller(this);
+ if (SUCCEEDED((rc = autoCaller.rc())))
+ {
+ if (getObjectState().getState() != ObjectState::Ready)
+ Log1WarningFunc(("VirtualBox has been uninitialized (state=%d), the event is discarded!\n",
+ getObjectState().getState()));
+ // return S_OK
+ else if ( (m->pAsyncEventQ)
+ && (m->pAsyncEventQ->postEvent(event))
+ )
+ return S_OK;
+ else
+ rc = E_FAIL;
+ }
+
+ // in any event of failure, we must clean up here, or we'll leak;
+ // the caller has allocated the object using new()
+ delete event;
+ return rc;
+}
+
+/**
+ * Adds a progress to the global collection of pending operations.
+ * Usually gets called upon progress object initialization.
+ *
+ * @param aProgress Operation to add to the collection.
+ *
+ * @note Doesn't lock objects.
+ */
+HRESULT VirtualBox::i_addProgress(IProgress *aProgress)
+{
+ CheckComArgNotNull(aProgress);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ Bstr id;
+ HRESULT rc = aProgress->COMGETTER(Id)(id.asOutParam());
+ AssertComRCReturnRC(rc);
+
+ /* protect mProgressOperations */
+ AutoWriteLock safeLock(m->mtxProgressOperations COMMA_LOCKVAL_SRC_POS);
+
+ m->mapProgressOperations.insert(ProgressMap::value_type(Guid(id), aProgress));
+ return S_OK;
+}
+
+/**
+ * Removes the progress from the global collection of pending operations.
+ * Usually gets called upon progress completion.
+ *
+ * @param aId UUID of the progress operation to remove
+ *
+ * @note Doesn't lock objects.
+ */
+HRESULT VirtualBox::i_removeProgress(IN_GUID aId)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ ComPtr<IProgress> progress;
+
+ /* protect mProgressOperations */
+ AutoWriteLock safeLock(m->mtxProgressOperations COMMA_LOCKVAL_SRC_POS);
+
+ size_t cnt = m->mapProgressOperations.erase(aId);
+ Assert(cnt == 1);
+ NOREF(cnt);
+
+ return S_OK;
+}
+
+#ifdef RT_OS_WINDOWS
+
+class StartSVCHelperClientData : public ThreadTask
+{
+public:
+ StartSVCHelperClientData()
+ {
+ LogFlowFuncEnter();
+ m_strTaskName = "SVCHelper";
+ threadVoidData = NULL;
+ initialized = false;
+ }
+
+ virtual ~StartSVCHelperClientData()
+ {
+ LogFlowFuncEnter();
+ if (threadVoidData!=NULL)
+ {
+ delete threadVoidData;
+ threadVoidData=NULL;
+ }
+ };
+
+ void handler()
+ {
+ VirtualBox::i_SVCHelperClientThreadTask(this);
+ }
+
+ const ComPtr<Progress>& GetProgressObject() const {return progress;}
+
+ bool init(VirtualBox* aVbox,
+ Progress* aProgress,
+ bool aPrivileged,
+ VirtualBox::PFN_SVC_HELPER_CLIENT_T aFunc,
+ void *aUser)
+ {
+ LogFlowFuncEnter();
+ that = aVbox;
+ progress = aProgress;
+ privileged = aPrivileged;
+ func = aFunc;
+ user = aUser;
+
+ initThreadVoidData();
+
+ initialized = true;
+
+ return initialized;
+ }
+
+ bool isOk() const{ return initialized;}
+
+ bool initialized;
+ ComObjPtr<VirtualBox> that;
+ ComObjPtr<Progress> progress;
+ bool privileged;
+ VirtualBox::PFN_SVC_HELPER_CLIENT_T func;
+ void *user;
+ ThreadVoidData *threadVoidData;
+
+private:
+ bool initThreadVoidData()
+ {
+ LogFlowFuncEnter();
+ threadVoidData = static_cast<ThreadVoidData*>(user);
+ return true;
+ }
+};
+
+/**
+ * Helper method that starts a worker thread that:
+ * - creates a pipe communication channel using SVCHlpClient;
+ * - starts an SVC Helper process that will inherit this channel;
+ * - executes the supplied function by passing it the created SVCHlpClient
+ * and opened instance to communicate to the Helper process and the given
+ * Progress object.
+ *
+ * The user function is supposed to communicate to the helper process
+ * using the \a aClient argument to do the requested job and optionally expose
+ * the progress through the \a aProgress object. The user function should never
+ * call notifyComplete() on it: this will be done automatically using the
+ * result code returned by the function.
+ *
+ * Before the user function is started, the communication channel passed to
+ * the \a aClient argument is fully set up, the function should start using
+ * its write() and read() methods directly.
+ *
+ * The \a aVrc parameter of the user function may be used to return an error
+ * code if it is related to communication errors (for example, returned by
+ * the SVCHlpClient members when they fail). In this case, the correct error
+ * message using this value will be reported to the caller. Note that the
+ * value of \a aVrc is inspected only if the user function itself returns
+ * success.
+ *
+ * If a failure happens anywhere before the user function would be normally
+ * called, it will be called anyway in special "cleanup only" mode indicated
+ * by \a aClient, \a aProgress and \a aVrc arguments set to NULL. In this mode,
+ * all the function is supposed to do is to cleanup its aUser argument if
+ * necessary (it's assumed that the ownership of this argument is passed to
+ * the user function once #startSVCHelperClient() returns a success, thus
+ * making it responsible for the cleanup).
+ *
+ * After the user function returns, the thread will send the SVCHlpMsg::Null
+ * message to indicate a process termination.
+ *
+ * @param aPrivileged |true| to start the SVC Helper process as a privileged
+ * user that can perform administrative tasks
+ * @param aFunc user function to run
+ * @param aUser argument to the user function
+ * @param aProgress progress object that will track operation completion
+ *
+ * @note aPrivileged is currently ignored (due to some unsolved problems in
+ * Vista) and the process will be started as a normal (unprivileged)
+ * process.
+ *
+ * @note Doesn't lock anything.
+ */
+HRESULT VirtualBox::i_startSVCHelperClient(bool aPrivileged,
+ PFN_SVC_HELPER_CLIENT_T aFunc,
+ void *aUser, Progress *aProgress)
+{
+ LogFlowFuncEnter();
+ AssertReturn(aFunc, E_POINTER);
+ AssertReturn(aProgress, E_POINTER);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ /* create the i_SVCHelperClientThreadTask() argument */
+
+ HRESULT hr = S_OK;
+ StartSVCHelperClientData *pTask = NULL;
+ try
+ {
+ pTask = new StartSVCHelperClientData();
+
+ pTask->init(this, aProgress, aPrivileged, aFunc, aUser);
+
+ if (!pTask->isOk())
+ {
+ delete pTask;
+ LogRel(("Could not init StartSVCHelperClientData object \n"));
+ throw E_FAIL;
+ }
+
+ //this function delete pTask in case of exceptions, so there is no need in the call of delete operator
+ hr = pTask->createThreadWithType(RTTHREADTYPE_MAIN_WORKER);
+
+ }
+ catch(std::bad_alloc &)
+ {
+ hr = setError(E_OUTOFMEMORY);
+ }
+ catch(...)
+ {
+ LogRel(("Could not create thread for StartSVCHelperClientData \n"));
+ hr = E_FAIL;
+ }
+
+ return hr;
+}
+
+/**
+ * Worker thread for startSVCHelperClient().
+ */
+/* static */
+void VirtualBox::i_SVCHelperClientThreadTask(StartSVCHelperClientData *pTask)
+{
+ LogFlowFuncEnter();
+ HRESULT rc = S_OK;
+ bool userFuncCalled = false;
+
+ do
+ {
+ AssertBreakStmt(pTask, rc = E_POINTER);
+ AssertReturnVoid(!pTask->progress.isNull());
+
+ /* protect VirtualBox from uninitialization */
+ AutoCaller autoCaller(pTask->that);
+ if (!autoCaller.isOk())
+ {
+ /* it's too late */
+ rc = autoCaller.rc();
+ break;
+ }
+
+ int vrc = VINF_SUCCESS;
+
+ Guid id;
+ id.create();
+ SVCHlpClient client;
+ vrc = client.create(Utf8StrFmt("VirtualBox\\SVCHelper\\{%RTuuid}",
+ id.raw()).c_str());
+ if (RT_FAILURE(vrc))
+ {
+ rc = pTask->that->setErrorBoth(E_FAIL, vrc, tr("Could not create the communication channel (%Rrc)"), vrc);
+ break;
+ }
+
+ /* get the path to the executable */
+ char exePathBuf[RTPATH_MAX];
+ char *exePath = RTProcGetExecutablePath(exePathBuf, RTPATH_MAX);
+ if (!exePath)
+ {
+ rc = pTask->that->setError(E_FAIL, tr("Cannot get executable name"));
+ break;
+ }
+
+ Utf8Str argsStr = Utf8StrFmt("/Helper %s", client.name().c_str());
+
+ LogFlowFunc(("Starting '\"%s\" %s'...\n", exePath, argsStr.c_str()));
+
+ RTPROCESS pid = NIL_RTPROCESS;
+
+ if (pTask->privileged)
+ {
+ /* Attempt to start a privileged process using the Run As dialog */
+
+ Bstr file = exePath;
+ Bstr parameters = argsStr;
+
+ SHELLEXECUTEINFO shExecInfo;
+
+ shExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
+
+ shExecInfo.fMask = NULL;
+ shExecInfo.hwnd = NULL;
+ shExecInfo.lpVerb = L"runas";
+ shExecInfo.lpFile = file.raw();
+ shExecInfo.lpParameters = parameters.raw();
+ shExecInfo.lpDirectory = NULL;
+ shExecInfo.nShow = SW_NORMAL;
+ shExecInfo.hInstApp = NULL;
+
+ if (!ShellExecuteEx(&shExecInfo))
+ {
+ int vrc2 = RTErrConvertFromWin32(GetLastError());
+ /* hide excessive details in case of a frequent error
+ * (pressing the Cancel button to close the Run As dialog) */
+ if (vrc2 == VERR_CANCELLED)
+ rc = pTask->that->setErrorBoth(E_FAIL, vrc, tr("Operation canceled by the user"));
+ else
+ rc = pTask->that->setErrorBoth(E_FAIL, vrc, tr("Could not launch a privileged process '%s' (%Rrc)"), exePath, vrc2);
+ break;
+ }
+ }
+ else
+ {
+ const char *args[] = { exePath, "/Helper", client.name().c_str(), 0 };
+ vrc = RTProcCreate(exePath, args, RTENV_DEFAULT, 0, &pid);
+ if (RT_FAILURE(vrc))
+ {
+ rc = pTask->that->setErrorBoth(E_FAIL, vrc, tr("Could not launch a process '%s' (%Rrc)"), exePath, vrc);
+ break;
+ }
+ }
+
+ /* wait for the client to connect */
+ vrc = client.connect();
+ if (RT_SUCCESS(vrc))
+ {
+ /* start the user supplied function */
+ rc = pTask->func(&client, pTask->progress, pTask->user, &vrc);
+ userFuncCalled = true;
+ }
+
+ /* send the termination signal to the process anyway */
+ {
+ int vrc2 = client.write(SVCHlpMsg::Null);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ }
+
+ if (SUCCEEDED(rc) && RT_FAILURE(vrc))
+ {
+ rc = pTask->that->setErrorBoth(E_FAIL, vrc, tr("Could not operate the communication channel (%Rrc)"), vrc);
+ break;
+ }
+ }
+ while (0);
+
+ if (FAILED(rc) && !userFuncCalled)
+ {
+ /* call the user function in the "cleanup only" mode
+ * to let it free resources passed to in aUser */
+ pTask->func(NULL, NULL, pTask->user, NULL);
+ }
+
+ pTask->progress->i_notifyComplete(rc);
+
+ LogFlowFuncLeave();
+}
+
+#endif /* RT_OS_WINDOWS */
+
+/**
+ * Sends a signal to the client watcher to rescan the set of machines
+ * that have open sessions.
+ *
+ * @note Doesn't lock anything.
+ */
+void VirtualBox::i_updateClientWatcher()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AssertPtrReturnVoid(m->pClientWatcher);
+ m->pClientWatcher->update();
+}
+
+/**
+ * Adds the given child process ID to the list of processes to be reaped.
+ * This call should be followed by #i_updateClientWatcher() to take the effect.
+ *
+ * @note Doesn't lock anything.
+ */
+void VirtualBox::i_addProcessToReap(RTPROCESS pid)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ AssertPtrReturnVoid(m->pClientWatcher);
+ m->pClientWatcher->addProcess(pid);
+}
+
+/**
+ * VD plugin load
+ */
+int VirtualBox::i_loadVDPlugin(const char *pszPluginLibrary)
+{
+ return m->pSystemProperties->i_loadVDPlugin(pszPluginLibrary);
+}
+
+/**
+ * VD plugin unload
+ */
+int VirtualBox::i_unloadVDPlugin(const char *pszPluginLibrary)
+{
+ return m->pSystemProperties->i_unloadVDPlugin(pszPluginLibrary);
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onMediumRegistered(const Guid &aMediumId, const DeviceType_T aDevType, const BOOL aRegistered)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateMediumRegisteredEvent(ptrEvent.asOutParam(), m->pEventSource,
+ aMediumId.toString(), aDevType, aRegistered);
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+void VirtualBox::i_onMediumConfigChanged(IMedium *aMedium)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateMediumConfigChangedEvent(ptrEvent.asOutParam(), m->pEventSource, aMedium);
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+void VirtualBox::i_onMediumChanged(IMediumAttachment *aMediumAttachment)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateMediumChangedEvent(ptrEvent.asOutParam(), m->pEventSource, aMediumAttachment);
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onStorageControllerChanged(const Guid &aMachineId, const com::Utf8Str &aControllerName)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateStorageControllerChangedEvent(ptrEvent.asOutParam(), m->pEventSource,
+ aMachineId.toString(), aControllerName);
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+void VirtualBox::i_onStorageDeviceChanged(IMediumAttachment *aStorageDevice, const BOOL fRemoved, const BOOL fSilent)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateStorageDeviceChangedEvent(ptrEvent.asOutParam(), m->pEventSource, aStorageDevice, fRemoved, fSilent);
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onMachineStateChanged(const Guid &aId, MachineState_T aState)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateMachineStateChangedEvent(ptrEvent.asOutParam(), m->pEventSource, aId.toString(), aState);
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onMachineDataChanged(const Guid &aId, BOOL aTemporary)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateMachineDataChangedEvent(ptrEvent.asOutParam(), m->pEventSource, aId.toString(), aTemporary);
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onMachineGroupsChanged(const Guid &aId)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateMachineGroupsChangedEvent(ptrEvent.asOutParam(), m->pEventSource, aId.toString(), FALSE /*aDummy*/);
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+/**
+ * @note Locks this object for reading.
+ */
+BOOL VirtualBox::i_onExtraDataCanChange(const Guid &aId, const Utf8Str &aKey, const Utf8Str &aValue, Bstr &aError)
+{
+ LogFlowThisFunc(("machine={%RTuuid} aKey={%s} aValue={%s}\n", aId.raw(), aKey.c_str(), aValue.c_str()));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), FALSE);
+
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateExtraDataCanChangeEvent(ptrEvent.asOutParam(), m->pEventSource, aId.toString(), aKey, aValue);
+ AssertComRCReturn(hrc, TRUE);
+
+ VBoxEventDesc EvtDesc(ptrEvent, m->pEventSource);
+ BOOL fDelivered = EvtDesc.fire(3000); /* Wait up to 3 secs for delivery */
+ //Assert(fDelivered);
+ BOOL fAllowChange = TRUE;
+ if (fDelivered)
+ {
+ ComPtr<IExtraDataCanChangeEvent> ptrCanChangeEvent = ptrEvent;
+ Assert(ptrCanChangeEvent);
+
+ BOOL fVetoed = FALSE;
+ ptrCanChangeEvent->IsVetoed(&fVetoed);
+ fAllowChange = !fVetoed;
+
+ if (!fAllowChange)
+ {
+ SafeArray<BSTR> aVetos;
+ ptrCanChangeEvent->GetVetos(ComSafeArrayAsOutParam(aVetos));
+ if (aVetos.size() > 0)
+ aError = aVetos[0];
+ }
+ }
+
+ LogFlowThisFunc(("fAllowChange=%RTbool\n", fAllowChange));
+ return fAllowChange;
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onExtraDataChanged(const Guid &aId, const Utf8Str &aKey, const Utf8Str &aValue)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateExtraDataChangedEvent(ptrEvent.asOutParam(), m->pEventSource, aId.toString(), aKey, aValue);
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onMachineRegistered(const Guid &aId, BOOL aRegistered)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateMachineRegisteredEvent(ptrEvent.asOutParam(), m->pEventSource, aId.toString(), aRegistered);
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onSessionStateChanged(const Guid &aId, SessionState_T aState)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateSessionStateChangedEvent(ptrEvent.asOutParam(), m->pEventSource, aId.toString(), aState);
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onSnapshotTaken(const Guid &aMachineId, const Guid &aSnapshotId)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateSnapshotTakenEvent(ptrEvent.asOutParam(), m->pEventSource,
+ aMachineId.toString(), aSnapshotId.toString());
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onSnapshotDeleted(const Guid &aMachineId, const Guid &aSnapshotId)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateSnapshotDeletedEvent(ptrEvent.asOutParam(), m->pEventSource,
+ aMachineId.toString(), aSnapshotId.toString());
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onSnapshotRestored(const Guid &aMachineId, const Guid &aSnapshotId)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateSnapshotRestoredEvent(ptrEvent.asOutParam(), m->pEventSource,
+ aMachineId.toString(), aSnapshotId.toString());
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onSnapshotChanged(const Guid &aMachineId, const Guid &aSnapshotId)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateSnapshotChangedEvent(ptrEvent.asOutParam(), m->pEventSource,
+ aMachineId.toString(), aSnapshotId.toString());
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onGuestPropertyChanged(const Guid &aMachineId, const Utf8Str &aName, const Utf8Str &aValue,
+ const Utf8Str &aFlags, const BOOL fWasDeleted)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateGuestPropertyChangedEvent(ptrEvent.asOutParam(), m->pEventSource,
+ aMachineId.toString(), aName, aValue, aFlags, fWasDeleted);
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onNatRedirectChanged(const Guid &aMachineId, ULONG ulSlot, bool fRemove, const Utf8Str &aName,
+ NATProtocol_T aProto, const Utf8Str &aHostIp, uint16_t aHostPort,
+ const Utf8Str &aGuestIp, uint16_t aGuestPort)
+{
+ ::FireNATRedirectEvent(m->pEventSource, aMachineId.toString(), ulSlot, fRemove, aName, aProto, aHostIp,
+ aHostPort, aGuestIp, aGuestPort);
+}
+
+/** @todo Unused!! */
+void VirtualBox::i_onNATNetworkChanged(const Utf8Str &aName)
+{
+ ::FireNATNetworkChangedEvent(m->pEventSource, aName);
+}
+
+void VirtualBox::i_onNATNetworkStartStop(const Utf8Str &aName, BOOL fStart)
+{
+ ::FireNATNetworkStartStopEvent(m->pEventSource, aName, fStart);
+}
+
+void VirtualBox::i_onNATNetworkSetting(const Utf8Str &aNetworkName, BOOL aEnabled,
+ const Utf8Str &aNetwork, const Utf8Str &aGateway,
+ BOOL aAdvertiseDefaultIpv6RouteEnabled,
+ BOOL fNeedDhcpServer)
+{
+ ::FireNATNetworkSettingEvent(m->pEventSource, aNetworkName, aEnabled, aNetwork, aGateway,
+ aAdvertiseDefaultIpv6RouteEnabled, fNeedDhcpServer);
+}
+
+void VirtualBox::i_onNATNetworkPortForward(const Utf8Str &aNetworkName, BOOL create, BOOL fIpv6,
+ const Utf8Str &aRuleName, NATProtocol_T proto,
+ const Utf8Str &aHostIp, LONG aHostPort,
+ const Utf8Str &aGuestIp, LONG aGuestPort)
+{
+ ::FireNATNetworkPortForwardEvent(m->pEventSource, aNetworkName, create, fIpv6, aRuleName, proto,
+ aHostIp, aHostPort, aGuestIp, aGuestPort);
+}
+
+
+void VirtualBox::i_onHostNameResolutionConfigurationChange()
+{
+ if (m->pEventSource)
+ ::FireHostNameResolutionConfigurationChangeEvent(m->pEventSource);
+}
+
+
+int VirtualBox::i_natNetworkRefInc(const Utf8Str &aNetworkName)
+{
+ AutoWriteLock safeLock(*spMtxNatNetworkNameToRefCountLock COMMA_LOCKVAL_SRC_POS);
+
+ if (!sNatNetworkNameToRefCount[aNetworkName])
+ {
+ ComPtr<INATNetwork> nat;
+ HRESULT rc = findNATNetworkByName(aNetworkName, nat);
+ if (FAILED(rc)) return -1;
+
+ rc = nat->Start();
+ if (SUCCEEDED(rc))
+ LogRel(("Started NAT network '%s'\n", aNetworkName.c_str()));
+ else
+ LogRel(("Error %Rhrc starting NAT network '%s'\n", rc, aNetworkName.c_str()));
+ AssertComRCReturn(rc, -1);
+ }
+
+ sNatNetworkNameToRefCount[aNetworkName]++;
+
+ return sNatNetworkNameToRefCount[aNetworkName];
+}
+
+
+int VirtualBox::i_natNetworkRefDec(const Utf8Str &aNetworkName)
+{
+ AutoWriteLock safeLock(*spMtxNatNetworkNameToRefCountLock COMMA_LOCKVAL_SRC_POS);
+
+ if (!sNatNetworkNameToRefCount[aNetworkName])
+ return 0;
+
+ sNatNetworkNameToRefCount[aNetworkName]--;
+
+ if (!sNatNetworkNameToRefCount[aNetworkName])
+ {
+ ComPtr<INATNetwork> nat;
+ HRESULT rc = findNATNetworkByName(aNetworkName, nat);
+ if (FAILED(rc)) return -1;
+
+ rc = nat->Stop();
+ if (SUCCEEDED(rc))
+ LogRel(("Stopped NAT network '%s'\n", aNetworkName.c_str()));
+ else
+ LogRel(("Error %Rhrc stopping NAT network '%s'\n", rc, aNetworkName.c_str()));
+ AssertComRCReturn(rc, -1);
+ }
+
+ return sNatNetworkNameToRefCount[aNetworkName];
+}
+
+
+/*
+ * Export this to NATNetwork so that its setters can refuse to change
+ * essential network settings when an VBoxNatNet instance is running.
+ */
+RWLockHandle *VirtualBox::i_getNatNetLock() const
+{
+ return spMtxNatNetworkNameToRefCountLock;
+}
+
+
+/*
+ * Export this to NATNetwork so that its setters can refuse to change
+ * essential network settings when an VBoxNatNet instance is running.
+ * The caller is expected to hold a read lock on i_getNatNetLock().
+ */
+bool VirtualBox::i_isNatNetStarted(const Utf8Str &aNetworkName) const
+{
+ return sNatNetworkNameToRefCount[aNetworkName] > 0;
+}
+
+
+void VirtualBox::i_onCloudProviderListChanged(BOOL aRegistered)
+{
+ ::FireCloudProviderListChangedEvent(m->pEventSource, aRegistered);
+}
+
+
+void VirtualBox::i_onCloudProviderRegistered(const Utf8Str &aProviderId, BOOL aRegistered)
+{
+ ::FireCloudProviderRegisteredEvent(m->pEventSource, aProviderId, aRegistered);
+}
+
+
+void VirtualBox::i_onCloudProviderUninstall(const Utf8Str &aProviderId)
+{
+ HRESULT hrc;
+
+ ComPtr<IEvent> pEvent;
+ hrc = CreateCloudProviderUninstallEvent(pEvent.asOutParam(),
+ m->pEventSource, aProviderId);
+ if (FAILED(hrc))
+ return;
+
+ BOOL fDelivered = FALSE;
+ hrc = m->pEventSource->FireEvent(pEvent, /* :timeout */ 10000, &fDelivered);
+ if (FAILED(hrc))
+ return;
+}
+
+void VirtualBox::i_onLanguageChanged(const Utf8Str &aLanguageId)
+{
+ ComPtr<IEvent> ptrEvent;
+ HRESULT hrc = ::CreateLanguageChangedEvent(ptrEvent.asOutParam(), m->pEventSource, aLanguageId);
+ AssertComRCReturnVoid(hrc);
+ i_postEvent(new AsyncEvent(this, ptrEvent));
+}
+
+void VirtualBox::i_onProgressCreated(const Guid &aId, BOOL aCreated)
+{
+ ::FireProgressCreatedEvent(m->pEventSource, aId.toString(), aCreated);
+}
+
+#ifdef VBOX_WITH_UPDATE_AGENT
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onUpdateAgentAvailable(IUpdateAgent *aAgent,
+ const Utf8Str &aVer, UpdateChannel_T aChannel, UpdateSeverity_T aSev,
+ const Utf8Str &aDownloadURL, const Utf8Str &aWebURL, const Utf8Str &aReleaseNotes)
+{
+ ::FireUpdateAgentAvailableEvent(m->pEventSource, aAgent, aVer, aChannel, aSev,
+ aDownloadURL, aWebURL, aReleaseNotes);
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onUpdateAgentError(IUpdateAgent *aAgent, const Utf8Str &aErrMsg, LONG aRc)
+{
+ ::FireUpdateAgentErrorEvent(m->pEventSource, aAgent, aErrMsg, aRc);
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onUpdateAgentStateChanged(IUpdateAgent *aAgent, UpdateState_T aState)
+{
+ ::FireUpdateAgentStateChangedEvent(m->pEventSource, aAgent, aState);
+}
+
+/**
+ * @note Doesn't lock any object.
+ */
+void VirtualBox::i_onUpdateAgentSettingsChanged(IUpdateAgent *aAgent, const Utf8Str &aAttributeHint)
+{
+ ::FireUpdateAgentSettingsChangedEvent(m->pEventSource, aAgent, aAttributeHint);
+}
+#endif /* VBOX_WITH_UPDATE_AGENT */
+
+/**
+ * @note Locks the list of other objects for reading.
+ */
+ComObjPtr<GuestOSType> VirtualBox::i_getUnknownOSType()
+{
+ ComObjPtr<GuestOSType> type;
+
+ /* unknown type must always be the first */
+ ComAssertRet(m->allGuestOSTypes.size() > 0, type);
+
+ return m->allGuestOSTypes.front();
+}
+
+/**
+ * Returns the list of opened machines (machines having VM sessions opened,
+ * ignoring other sessions) and optionally the list of direct session controls.
+ *
+ * @param aMachines Where to put opened machines (will be empty if none).
+ * @param aControls Where to put direct session controls (optional).
+ *
+ * @note The returned lists contain smart pointers. So, clear it as soon as
+ * it becomes no more necessary to release instances.
+ *
+ * @note It can be possible that a session machine from the list has been
+ * already uninitialized, so do a usual AutoCaller/AutoReadLock sequence
+ * when accessing unprotected data directly.
+ *
+ * @note Locks objects for reading.
+ */
+void VirtualBox::i_getOpenedMachines(SessionMachinesList &aMachines,
+ InternalControlList *aControls /*= NULL*/)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ aMachines.clear();
+ if (aControls)
+ aControls->clear();
+
+ AutoReadLock alock(m->allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ for (MachinesOList::iterator it = m->allMachines.begin();
+ it != m->allMachines.end();
+ ++it)
+ {
+ ComObjPtr<SessionMachine> sm;
+ ComPtr<IInternalSessionControl> ctl;
+ if ((*it)->i_isSessionOpenVM(sm, &ctl))
+ {
+ aMachines.push_back(sm);
+ if (aControls)
+ aControls->push_back(ctl);
+ }
+ }
+}
+
+/**
+ * Gets a reference to the machine list. This is the real thing, not a copy,
+ * so bad things will happen if the caller doesn't hold the necessary lock.
+ *
+ * @returns reference to machine list
+ *
+ * @note Caller must hold the VirtualBox object lock at least for reading.
+ */
+VirtualBox::MachinesOList &VirtualBox::i_getMachinesList(void)
+{
+ return m->allMachines;
+}
+
+/**
+ * Searches for a machine object with the given ID in the collection
+ * of registered machines.
+ *
+ * @param aId Machine UUID to look for.
+ * @param fPermitInaccessible If true, inaccessible machines will be found;
+ * if false, this will fail if the given machine is inaccessible.
+ * @param aSetError If true, set errorinfo if the machine is not found.
+ * @param aMachine Returned machine, if found.
+ * @return
+ */
+HRESULT VirtualBox::i_findMachine(const Guid &aId,
+ bool fPermitInaccessible,
+ bool aSetError,
+ ComObjPtr<Machine> *aMachine /* = NULL */)
+{
+ HRESULT rc = VBOX_E_OBJECT_NOT_FOUND;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ {
+ AutoReadLock al(m->allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ for (MachinesOList::iterator it = m->allMachines.begin();
+ it != m->allMachines.end();
+ ++it)
+ {
+ ComObjPtr<Machine> pMachine = *it;
+
+ if (!fPermitInaccessible)
+ {
+ // skip inaccessible machines
+ AutoCaller machCaller(pMachine);
+ if (FAILED(machCaller.rc()))
+ continue;
+ }
+
+ if (pMachine->i_getId() == aId)
+ {
+ rc = S_OK;
+ if (aMachine)
+ *aMachine = pMachine;
+ break;
+ }
+ }
+ }
+
+ if (aSetError && FAILED(rc))
+ rc = setError(rc,
+ tr("Could not find a registered machine with UUID {%RTuuid}"),
+ aId.raw());
+
+ return rc;
+}
+
+/**
+ * Searches for a machine object with the given name or location in the
+ * collection of registered machines.
+ *
+ * @param aName Machine name or location to look for.
+ * @param aSetError If true, set errorinfo if the machine is not found.
+ * @param aMachine Returned machine, if found.
+ * @return
+ */
+HRESULT VirtualBox::i_findMachineByName(const Utf8Str &aName,
+ bool aSetError,
+ ComObjPtr<Machine> *aMachine /* = NULL */)
+{
+ HRESULT rc = VBOX_E_OBJECT_NOT_FOUND;
+
+ AutoReadLock al(m->allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ for (MachinesOList::iterator it = m->allMachines.begin();
+ it != m->allMachines.end();
+ ++it)
+ {
+ ComObjPtr<Machine> &pMachine = *it;
+ AutoCaller machCaller(pMachine);
+ if (!machCaller.isOk())
+ continue; // we can't ask inaccessible machines for their names
+
+ AutoReadLock machLock(pMachine COMMA_LOCKVAL_SRC_POS);
+ if (pMachine->i_getName() == aName)
+ {
+ rc = S_OK;
+ if (aMachine)
+ *aMachine = pMachine;
+ break;
+ }
+ if (!RTPathCompare(pMachine->i_getSettingsFileFull().c_str(), aName.c_str()))
+ {
+ rc = S_OK;
+ if (aMachine)
+ *aMachine = pMachine;
+ break;
+ }
+ }
+
+ if (aSetError && FAILED(rc))
+ rc = setError(rc,
+ tr("Could not find a registered machine named '%s'"), aName.c_str());
+
+ return rc;
+}
+
+static HRESULT i_validateMachineGroupHelper(const Utf8Str &aGroup, bool fPrimary, VirtualBox *pVirtualBox)
+{
+ /* empty strings are invalid */
+ if (aGroup.isEmpty())
+ return E_INVALIDARG;
+ /* the toplevel group is valid */
+ if (aGroup == "/")
+ return S_OK;
+ /* any other strings of length 1 are invalid */
+ if (aGroup.length() == 1)
+ return E_INVALIDARG;
+ /* must start with a slash */
+ if (aGroup.c_str()[0] != '/')
+ return E_INVALIDARG;
+ /* must not end with a slash */
+ if (aGroup.c_str()[aGroup.length() - 1] == '/')
+ return E_INVALIDARG;
+ /* check the group components */
+ const char *pStr = aGroup.c_str() + 1; /* first char is /, skip it */
+ while (pStr)
+ {
+ char *pSlash = RTStrStr(pStr, "/");
+ if (pSlash)
+ {
+ /* no empty components (or // sequences in other words) */
+ if (pSlash == pStr)
+ return E_INVALIDARG;
+ /* check if the machine name rules are violated, because that means
+ * the group components are too close to the limits. */
+ Utf8Str tmp((const char *)pStr, (size_t)(pSlash - pStr));
+ Utf8Str tmp2(tmp);
+ sanitiseMachineFilename(tmp);
+ if (tmp != tmp2)
+ return E_INVALIDARG;
+ if (fPrimary)
+ {
+ HRESULT rc = pVirtualBox->i_findMachineByName(tmp,
+ false /* aSetError */);
+ if (SUCCEEDED(rc))
+ return VBOX_E_VM_ERROR;
+ }
+ pStr = pSlash + 1;
+ }
+ else
+ {
+ /* check if the machine name rules are violated, because that means
+ * the group components is too close to the limits. */
+ Utf8Str tmp(pStr);
+ Utf8Str tmp2(tmp);
+ sanitiseMachineFilename(tmp);
+ if (tmp != tmp2)
+ return E_INVALIDARG;
+ pStr = NULL;
+ }
+ }
+ return S_OK;
+}
+
+/**
+ * Validates a machine group.
+ *
+ * @param aGroup Machine group.
+ * @param fPrimary Set if this is the primary group.
+ *
+ * @return S_OK or E_INVALIDARG
+ */
+HRESULT VirtualBox::i_validateMachineGroup(const Utf8Str &aGroup, bool fPrimary)
+{
+ HRESULT rc = i_validateMachineGroupHelper(aGroup, fPrimary, this);
+ if (FAILED(rc))
+ {
+ if (rc == VBOX_E_VM_ERROR)
+ rc = setError(E_INVALIDARG,
+ tr("Machine group '%s' conflicts with a virtual machine name"),
+ aGroup.c_str());
+ else
+ rc = setError(rc,
+ tr("Invalid machine group '%s'"),
+ aGroup.c_str());
+ }
+ return rc;
+}
+
+/**
+ * Takes a list of machine groups, and sanitizes/validates it.
+ *
+ * @param aMachineGroups Array with the machine groups.
+ * @param pllMachineGroups Pointer to list of strings for the result.
+ *
+ * @return S_OK or E_INVALIDARG
+ */
+HRESULT VirtualBox::i_convertMachineGroups(const std::vector<com::Utf8Str> aMachineGroups, StringsList *pllMachineGroups)
+{
+ pllMachineGroups->clear();
+ if (aMachineGroups.size())
+ {
+ for (size_t i = 0; i < aMachineGroups.size(); i++)
+ {
+ Utf8Str group(aMachineGroups[i]);
+ if (group.length() == 0)
+ group = "/";
+
+ HRESULT rc = i_validateMachineGroup(group, i == 0);
+ if (FAILED(rc))
+ return rc;
+
+ /* no duplicates please */
+ if ( find(pllMachineGroups->begin(), pllMachineGroups->end(), group)
+ == pllMachineGroups->end())
+ pllMachineGroups->push_back(group);
+ }
+ if (pllMachineGroups->size() == 0)
+ pllMachineGroups->push_back("/");
+ }
+ else
+ pllMachineGroups->push_back("/");
+
+ return S_OK;
+}
+
+/**
+ * Searches for a Medium object with the given ID in the list of registered
+ * hard disks.
+ *
+ * @param aId ID of the hard disk. Must not be empty.
+ * @param aSetError If @c true , the appropriate error info is set in case
+ * when the hard disk is not found.
+ * @param aHardDisk Where to store the found hard disk object (can be NULL).
+ *
+ * @return S_OK, E_INVALIDARG or VBOX_E_OBJECT_NOT_FOUND when not found.
+ *
+ * @note Locks the media tree for reading.
+ */
+HRESULT VirtualBox::i_findHardDiskById(const Guid &aId,
+ bool aSetError,
+ ComObjPtr<Medium> *aHardDisk /*= NULL*/)
+{
+ AssertReturn(!aId.isZero(), E_INVALIDARG);
+
+ // we use the hard disks map, but it is protected by the
+ // hard disk _list_ lock handle
+ AutoReadLock alock(m->allHardDisks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ HardDiskMap::const_iterator it = m->mapHardDisks.find(aId);
+ if (it != m->mapHardDisks.end())
+ {
+ if (aHardDisk)
+ *aHardDisk = (*it).second;
+ return S_OK;
+ }
+
+ if (aSetError)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Could not find an open hard disk with UUID {%RTuuid}"),
+ aId.raw());
+
+ return VBOX_E_OBJECT_NOT_FOUND;
+}
+
+/**
+ * Searches for a Medium object with the given ID or location in the list of
+ * registered hard disks. If both ID and location are specified, the first
+ * object that matches either of them (not necessarily both) is returned.
+ *
+ * @param strLocation Full location specification. Must not be empty.
+ * @param aSetError If @c true , the appropriate error info is set in case
+ * when the hard disk is not found.
+ * @param aHardDisk Where to store the found hard disk object (can be NULL).
+ *
+ * @return S_OK, E_INVALIDARG or VBOX_E_OBJECT_NOT_FOUND when not found.
+ *
+ * @note Locks the media tree for reading.
+ */
+HRESULT VirtualBox::i_findHardDiskByLocation(const Utf8Str &strLocation,
+ bool aSetError,
+ ComObjPtr<Medium> *aHardDisk /*= NULL*/)
+{
+ AssertReturn(!strLocation.isEmpty(), E_INVALIDARG);
+
+ // we use the hard disks map, but it is protected by the
+ // hard disk _list_ lock handle
+ AutoReadLock alock(m->allHardDisks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ for (HardDiskMap::const_iterator it = m->mapHardDisks.begin();
+ it != m->mapHardDisks.end();
+ ++it)
+ {
+ const ComObjPtr<Medium> &pHD = (*it).second;
+
+ AutoCaller autoCaller(pHD);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+ AutoWriteLock mlock(pHD COMMA_LOCKVAL_SRC_POS);
+
+ Utf8Str strLocationFull = pHD->i_getLocationFull();
+
+ if (0 == RTPathCompare(strLocationFull.c_str(), strLocation.c_str()))
+ {
+ if (aHardDisk)
+ *aHardDisk = pHD;
+ return S_OK;
+ }
+ }
+
+ if (aSetError)
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Could not find an open hard disk with location '%s'"),
+ strLocation.c_str());
+
+ return VBOX_E_OBJECT_NOT_FOUND;
+}
+
+/**
+ * Searches for a Medium object with the given ID or location in the list of
+ * registered DVD or floppy images, depending on the @a mediumType argument.
+ * If both ID and file path are specified, the first object that matches either
+ * of them (not necessarily both) is returned.
+ *
+ * @param mediumType Must be either DeviceType_DVD or DeviceType_Floppy.
+ * @param aId ID of the image file (unused when NULL).
+ * @param aLocation Full path to the image file (unused when NULL).
+ * @param aSetError If @c true, the appropriate error info is set in case when
+ * the image is not found.
+ * @param aImage Where to store the found image object (can be NULL).
+ *
+ * @return S_OK when found or E_INVALIDARG or VBOX_E_OBJECT_NOT_FOUND when not found.
+ *
+ * @note Locks the media tree for reading.
+ */
+HRESULT VirtualBox::i_findDVDOrFloppyImage(DeviceType_T mediumType,
+ const Guid *aId,
+ const Utf8Str &aLocation,
+ bool aSetError,
+ ComObjPtr<Medium> *aImage /* = NULL */)
+{
+ AssertReturn(aId || !aLocation.isEmpty(), E_INVALIDARG);
+
+ Utf8Str location;
+ if (!aLocation.isEmpty())
+ {
+ int vrc = i_calculateFullPath(aLocation, location);
+ if (RT_FAILURE(vrc))
+ return setError(VBOX_E_FILE_ERROR,
+ tr("Invalid image file location '%s' (%Rrc)"),
+ aLocation.c_str(),
+ vrc);
+ }
+
+ MediaOList *pMediaList;
+
+ switch (mediumType)
+ {
+ case DeviceType_DVD:
+ pMediaList = &m->allDVDImages;
+ break;
+
+ case DeviceType_Floppy:
+ pMediaList = &m->allFloppyImages;
+ break;
+
+ default:
+ return E_INVALIDARG;
+ }
+
+ AutoReadLock alock(pMediaList->getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ bool found = false;
+
+ for (MediaList::const_iterator it = pMediaList->begin();
+ it != pMediaList->end();
+ ++it)
+ {
+ // no AutoCaller, registered image life time is bound to this
+ Medium *pMedium = *it;
+ AutoReadLock imageLock(pMedium COMMA_LOCKVAL_SRC_POS);
+ const Utf8Str &strLocationFull = pMedium->i_getLocationFull();
+
+ found = ( aId
+ && pMedium->i_getId() == *aId)
+ || ( !aLocation.isEmpty()
+ && RTPathCompare(location.c_str(),
+ strLocationFull.c_str()) == 0);
+ if (found)
+ {
+ if (pMedium->i_getDeviceType() != mediumType)
+ {
+ if (mediumType == DeviceType_DVD)
+ return setError(E_INVALIDARG,
+ tr("Cannot mount DVD medium '%s' as floppy"), strLocationFull.c_str());
+ else
+ return setError(E_INVALIDARG,
+ tr("Cannot mount floppy medium '%s' as DVD"), strLocationFull.c_str());
+ }
+
+ if (aImage)
+ *aImage = pMedium;
+ break;
+ }
+ }
+
+ HRESULT rc = found ? S_OK : VBOX_E_OBJECT_NOT_FOUND;
+
+ if (aSetError && !found)
+ {
+ if (aId)
+ setError(rc,
+ tr("Could not find an image file with UUID {%RTuuid} in the media registry ('%s')"),
+ aId->raw(),
+ m->strSettingsFilePath.c_str());
+ else
+ setError(rc,
+ tr("Could not find an image file with location '%s' in the media registry ('%s')"),
+ aLocation.c_str(),
+ m->strSettingsFilePath.c_str());
+ }
+
+ return rc;
+}
+
+/**
+ * Searches for an IMedium object that represents the given UUID.
+ *
+ * If the UUID is empty (indicating an empty drive), this sets pMedium
+ * to NULL and returns S_OK.
+ *
+ * If the UUID refers to a host drive of the given device type, this
+ * sets pMedium to the object from the list in IHost and returns S_OK.
+ *
+ * If the UUID is an image file, this sets pMedium to the object that
+ * findDVDOrFloppyImage() returned.
+ *
+ * If none of the above apply, this returns VBOX_E_OBJECT_NOT_FOUND.
+ *
+ * @param mediumType Must be DeviceType_DVD or DeviceType_Floppy.
+ * @param uuid UUID to search for; must refer to a host drive or an image file or be null.
+ * @param fRefresh Whether to refresh the list of host drives in IHost (see Host::getDrives())
+ * @param aSetError
+ * @param pMedium out: IMedium object found.
+ * @return
+ */
+HRESULT VirtualBox::i_findRemoveableMedium(DeviceType_T mediumType,
+ const Guid &uuid,
+ bool fRefresh,
+ bool aSetError,
+ ComObjPtr<Medium> &pMedium)
+{
+ if (uuid.isZero())
+ {
+ // that's easy
+ pMedium.setNull();
+ return S_OK;
+ }
+ else if (!uuid.isValid())
+ {
+ /* handling of case invalid GUID */
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Guid '%s' is invalid"),
+ uuid.toString().c_str());
+ }
+
+ // first search for host drive with that UUID
+ HRESULT rc = m->pHost->i_findHostDriveById(mediumType,
+ uuid,
+ fRefresh,
+ pMedium);
+ if (rc == VBOX_E_OBJECT_NOT_FOUND)
+ // then search for an image with that UUID
+ rc = i_findDVDOrFloppyImage(mediumType, &uuid, Utf8Str::Empty, aSetError, &pMedium);
+
+ return rc;
+}
+
+/* Look for a GuestOSType object */
+HRESULT VirtualBox::i_findGuestOSType(const Utf8Str &strOSType,
+ ComObjPtr<GuestOSType> &guestOSType)
+{
+ guestOSType.setNull();
+
+ AssertMsg(m->allGuestOSTypes.size() != 0,
+ ("Guest OS types array must be filled"));
+
+ AutoReadLock alock(m->allGuestOSTypes.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ for (GuestOSTypesOList::const_iterator it = m->allGuestOSTypes.begin();
+ it != m->allGuestOSTypes.end();
+ ++it)
+ {
+ const Utf8Str &typeId = (*it)->i_id();
+ AssertMsg(!typeId.isEmpty(), ("ID must not be NULL"));
+ if (strOSType.compare(typeId, Utf8Str::CaseInsensitive) == 0)
+ {
+ guestOSType = *it;
+ return S_OK;
+ }
+ }
+
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("'%s' is not a valid Guest OS type"),
+ strOSType.c_str());
+}
+
+/**
+ * Returns the constant pseudo-machine UUID that is used to identify the
+ * global media registry.
+ *
+ * Starting with VirtualBox 4.0 each medium remembers in its instance data
+ * in which media registry it is saved (if any): this can either be a machine
+ * UUID, if it's in a per-machine media registry, or this global ID.
+ *
+ * This UUID is only used to identify the VirtualBox object while VirtualBox
+ * is running. It is a compile-time constant and not saved anywhere.
+ *
+ * @return
+ */
+const Guid& VirtualBox::i_getGlobalRegistryId() const
+{
+ return m->uuidMediaRegistry;
+}
+
+const ComObjPtr<Host>& VirtualBox::i_host() const
+{
+ return m->pHost;
+}
+
+SystemProperties* VirtualBox::i_getSystemProperties() const
+{
+ return m->pSystemProperties;
+}
+
+CloudProviderManager *VirtualBox::i_getCloudProviderManager() const
+{
+ return m->pCloudProviderManager;
+}
+
+#ifdef VBOX_WITH_EXTPACK
+/**
+ * Getter that SystemProperties and others can use to talk to the extension
+ * pack manager.
+ */
+ExtPackManager* VirtualBox::i_getExtPackManager() const
+{
+ return m->ptrExtPackManager;
+}
+#endif
+
+/**
+ * Getter that machines can talk to the autostart database.
+ */
+AutostartDb* VirtualBox::i_getAutostartDb() const
+{
+ return m->pAutostartDb;
+}
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+const ComObjPtr<PerformanceCollector>& VirtualBox::i_performanceCollector() const
+{
+ return m->pPerformanceCollector;
+}
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+
+/**
+ * Returns the default machine folder from the system properties
+ * with proper locking.
+ * @return
+ */
+void VirtualBox::i_getDefaultMachineFolder(Utf8Str &str) const
+{
+ AutoReadLock propsLock(m->pSystemProperties COMMA_LOCKVAL_SRC_POS);
+ str = m->pSystemProperties->m->strDefaultMachineFolder;
+}
+
+/**
+ * Returns the default hard disk format from the system properties
+ * with proper locking.
+ * @return
+ */
+void VirtualBox::i_getDefaultHardDiskFormat(Utf8Str &str) const
+{
+ AutoReadLock propsLock(m->pSystemProperties COMMA_LOCKVAL_SRC_POS);
+ str = m->pSystemProperties->m->strDefaultHardDiskFormat;
+}
+
+const Utf8Str& VirtualBox::i_homeDir() const
+{
+ return m->strHomeDir;
+}
+
+/**
+ * Calculates the absolute path of the given path taking the VirtualBox home
+ * directory as the current directory.
+ *
+ * @param strPath Path to calculate the absolute path for.
+ * @param aResult Where to put the result (used only on success, can be the
+ * same Utf8Str instance as passed in @a aPath).
+ * @return IPRT result.
+ *
+ * @note Doesn't lock any object.
+ */
+int VirtualBox::i_calculateFullPath(const Utf8Str &strPath, Utf8Str &aResult)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.rc(), VERR_GENERAL_FAILURE);
+
+ /* no need to lock since strHomeDir is const */
+
+ char szFolder[RTPATH_MAX];
+ size_t cbFolder = sizeof(szFolder);
+ int vrc = RTPathAbsEx(m->strHomeDir.c_str(),
+ strPath.c_str(),
+ RTPATH_STR_F_STYLE_HOST,
+ szFolder,
+ &cbFolder);
+ if (RT_SUCCESS(vrc))
+ aResult = szFolder;
+
+ return vrc;
+}
+
+/**
+ * Copies strSource to strTarget, making it relative to the VirtualBox config folder
+ * if it is a subdirectory thereof, or simply copying it otherwise.
+ *
+ * @param strSource Path to evalue and copy.
+ * @param strTarget Buffer to receive target path.
+ */
+void VirtualBox::i_copyPathRelativeToConfig(const Utf8Str &strSource,
+ Utf8Str &strTarget)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.rc());
+
+ // no need to lock since mHomeDir is const
+
+ // use strTarget as a temporary buffer to hold the machine settings dir
+ strTarget = m->strHomeDir;
+ if (RTPathStartsWith(strSource.c_str(), strTarget.c_str()))
+ // is relative: then append what's left
+ strTarget.append(strSource.c_str() + strTarget.length()); // include '/'
+ else
+ // is not relative: then overwrite
+ strTarget = strSource;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Checks if there is a hard disk, DVD or floppy image with the given ID or
+ * location already registered.
+ *
+ * On return, sets @a aConflict to the string describing the conflicting medium,
+ * or sets it to @c Null if no conflicting media is found. Returns S_OK in
+ * either case. A failure is unexpected.
+ *
+ * @param aId UUID to check.
+ * @param aLocation Location to check.
+ * @param aConflict Where to return parameters of the conflicting medium.
+ * @param ppMedium Medium reference in case this is simply a duplicate.
+ *
+ * @note Locks the media tree and media objects for reading.
+ */
+HRESULT VirtualBox::i_checkMediaForConflicts(const Guid &aId,
+ const Utf8Str &aLocation,
+ Utf8Str &aConflict,
+ ComObjPtr<Medium> *ppMedium)
+{
+ AssertReturn(!aId.isZero() && !aLocation.isEmpty(), E_FAIL);
+ AssertReturn(ppMedium, E_INVALIDARG);
+
+ aConflict.setNull();
+ ppMedium->setNull();
+
+ AutoReadLock alock(i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+
+ ComObjPtr<Medium> pMediumFound;
+ const char *pcszType = NULL;
+
+ if (aId.isValid() && !aId.isZero())
+ rc = i_findHardDiskById(aId, false /* aSetError */, &pMediumFound);
+ if (FAILED(rc) && !aLocation.isEmpty())
+ rc = i_findHardDiskByLocation(aLocation, false /* aSetError */, &pMediumFound);
+ if (SUCCEEDED(rc))
+ pcszType = tr("hard disk");
+
+ if (!pcszType)
+ {
+ rc = i_findDVDOrFloppyImage(DeviceType_DVD, &aId, aLocation, false /* aSetError */, &pMediumFound);
+ if (SUCCEEDED(rc))
+ pcszType = tr("CD/DVD image");
+ }
+
+ if (!pcszType)
+ {
+ rc = i_findDVDOrFloppyImage(DeviceType_Floppy, &aId, aLocation, false /* aSetError */, &pMediumFound);
+ if (SUCCEEDED(rc))
+ pcszType = tr("floppy image");
+ }
+
+ if (pcszType && pMediumFound)
+ {
+ /* Note: no AutoCaller since bound to this */
+ AutoReadLock mlock(pMediumFound COMMA_LOCKVAL_SRC_POS);
+
+ Utf8Str strLocFound = pMediumFound->i_getLocationFull();
+ Guid idFound = pMediumFound->i_getId();
+
+ if ( (RTPathCompare(strLocFound.c_str(), aLocation.c_str()) == 0)
+ && (idFound == aId)
+ )
+ *ppMedium = pMediumFound;
+
+ aConflict = Utf8StrFmt(tr("%s '%s' with UUID {%RTuuid}"),
+ pcszType,
+ strLocFound.c_str(),
+ idFound.raw());
+ }
+
+ return S_OK;
+}
+
+/**
+ * Checks whether the given UUID is already in use by one medium for the
+ * given device type.
+ *
+ * @returns true if the UUID is already in use
+ * fale otherwise
+ * @param aId The UUID to check.
+ * @param deviceType The device type the UUID is going to be checked for
+ * conflicts.
+ */
+bool VirtualBox::i_isMediaUuidInUse(const Guid &aId, DeviceType_T deviceType)
+{
+ /* A zero UUID is invalid here, always claim that it is already used. */
+ AssertReturn(!aId.isZero(), true);
+
+ AutoReadLock alock(i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT rc = S_OK;
+ bool fInUse = false;
+
+ ComObjPtr<Medium> pMediumFound;
+
+ switch (deviceType)
+ {
+ case DeviceType_HardDisk:
+ rc = i_findHardDiskById(aId, false /* aSetError */, &pMediumFound);
+ break;
+ case DeviceType_DVD:
+ rc = i_findDVDOrFloppyImage(DeviceType_DVD, &aId, Utf8Str::Empty, false /* aSetError */, &pMediumFound);
+ break;
+ case DeviceType_Floppy:
+ rc = i_findDVDOrFloppyImage(DeviceType_Floppy, &aId, Utf8Str::Empty, false /* aSetError */, &pMediumFound);
+ break;
+ default:
+ AssertMsgFailed(("Invalid device type %d\n", deviceType));
+ }
+
+ if (SUCCEEDED(rc) && pMediumFound)
+ fInUse = true;
+
+ return fInUse;
+}
+
+/**
+ * Called from Machine::prepareSaveSettings() when it has detected
+ * that a machine has been renamed. Such renames will require
+ * updating the global media registry during the
+ * VirtualBox::i_saveSettings() that follows later.
+*
+ * When a machine is renamed, there may well be media (in particular,
+ * diff images for snapshots) in the global registry that will need
+ * to have their paths updated. Before 3.2, Machine::saveSettings
+ * used to call VirtualBox::i_saveSettings implicitly, which was both
+ * unintuitive and caused locking order problems. Now, we remember
+ * such pending name changes with this method so that
+ * VirtualBox::i_saveSettings() can process them properly.
+ */
+void VirtualBox::i_rememberMachineNameChangeForMedia(const Utf8Str &strOldConfigDir,
+ const Utf8Str &strNewConfigDir)
+{
+ AutoWriteLock mediaLock(i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ Data::PendingMachineRename pmr;
+ pmr.strConfigDirOld = strOldConfigDir;
+ pmr.strConfigDirNew = strNewConfigDir;
+ m->llPendingMachineRenames.push_back(pmr);
+}
+
+static DECLCALLBACK(int) fntSaveMediaRegistries(void *pvUser);
+
+class SaveMediaRegistriesDesc : public ThreadTask
+{
+
+public:
+ SaveMediaRegistriesDesc()
+ {
+ m_strTaskName = "SaveMediaReg";
+ }
+ virtual ~SaveMediaRegistriesDesc(void) { }
+
+private:
+ void handler()
+ {
+ try
+ {
+ fntSaveMediaRegistries(this);
+ }
+ catch(...)
+ {
+ LogRel(("Exception in the function fntSaveMediaRegistries()\n"));
+ }
+ }
+
+ MediaList llMedia;
+ ComObjPtr<VirtualBox> pVirtualBox;
+
+ friend DECLCALLBACK(int) fntSaveMediaRegistries(void *pvUser);
+ friend void VirtualBox::i_saveMediaRegistry(settings::MediaRegistry &mediaRegistry,
+ const Guid &uuidRegistry,
+ const Utf8Str &strMachineFolder);
+};
+
+DECLCALLBACK(int) fntSaveMediaRegistries(void *pvUser)
+{
+ SaveMediaRegistriesDesc *pDesc = (SaveMediaRegistriesDesc *)pvUser;
+ if (!pDesc)
+ {
+ LogRelFunc(("Thread for saving media registries lacks parameters\n"));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ for (MediaList::const_iterator it = pDesc->llMedia.begin();
+ it != pDesc->llMedia.end();
+ ++it)
+ {
+ Medium *pMedium = *it;
+ pMedium->i_markRegistriesModified();
+ }
+
+ pDesc->pVirtualBox->i_saveModifiedRegistries();
+
+ pDesc->llMedia.clear();
+ pDesc->pVirtualBox.setNull();
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Goes through all known media (hard disks, floppies and DVDs) and saves
+ * those into the given settings::MediaRegistry structures whose registry
+ * ID match the given UUID.
+ *
+ * Before actually writing to the structures, all media paths (not just the
+ * ones for the given registry) are updated if machines have been renamed
+ * since the last call.
+ *
+ * This gets called from two contexts:
+ *
+ * -- VirtualBox::i_saveSettings() with the UUID of the global registry
+ * (VirtualBox::Data.uuidRegistry); this will save those media
+ * which had been loaded from the global registry or have been
+ * attached to a "legacy" machine which can't save its own registry;
+ *
+ * -- Machine::saveSettings() with the UUID of a machine, if a medium
+ * has been attached to a machine created with VirtualBox 4.0 or later.
+ *
+ * Media which have only been temporarily opened without having been
+ * attached to a machine have a NULL registry UUID and therefore don't
+ * get saved.
+ *
+ * This locks the media tree. Throws HRESULT on errors!
+ *
+ * @param mediaRegistry Settings structure to fill.
+ * @param uuidRegistry The UUID of the media registry; either a machine UUID
+ * (if machine registry) or the UUID of the global registry.
+ * @param strMachineFolder The machine folder for relative paths, if machine registry, or an empty string otherwise.
+ */
+void VirtualBox::i_saveMediaRegistry(settings::MediaRegistry &mediaRegistry,
+ const Guid &uuidRegistry,
+ const Utf8Str &strMachineFolder)
+{
+ // lock all media for the following; use a write lock because we're
+ // modifying the PendingMachineRenamesList, which is protected by this
+ AutoWriteLock mediaLock(i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ // if a machine was renamed, then we'll need to refresh media paths
+ if (m->llPendingMachineRenames.size())
+ {
+ // make a single list from the three media lists so we don't need three loops
+ MediaList llAllMedia;
+ // with hard disks, we must use the map, not the list, because the list only has base images
+ for (HardDiskMap::iterator it = m->mapHardDisks.begin(); it != m->mapHardDisks.end(); ++it)
+ llAllMedia.push_back(it->second);
+ for (MediaList::iterator it = m->allDVDImages.begin(); it != m->allDVDImages.end(); ++it)
+ llAllMedia.push_back(*it);
+ for (MediaList::iterator it = m->allFloppyImages.begin(); it != m->allFloppyImages.end(); ++it)
+ llAllMedia.push_back(*it);
+
+ SaveMediaRegistriesDesc *pDesc = new SaveMediaRegistriesDesc();
+ for (MediaList::iterator it = llAllMedia.begin();
+ it != llAllMedia.end();
+ ++it)
+ {
+ Medium *pMedium = *it;
+ for (Data::PendingMachineRenamesList::iterator it2 = m->llPendingMachineRenames.begin();
+ it2 != m->llPendingMachineRenames.end();
+ ++it2)
+ {
+ const Data::PendingMachineRename &pmr = *it2;
+ HRESULT rc = pMedium->i_updatePath(pmr.strConfigDirOld,
+ pmr.strConfigDirNew);
+ if (SUCCEEDED(rc))
+ {
+ // Remember which medium objects has been changed,
+ // to trigger saving their registries later.
+ pDesc->llMedia.push_back(pMedium);
+ } else if (rc == VBOX_E_FILE_ERROR)
+ /* nothing */;
+ else
+ AssertComRC(rc);
+ }
+ }
+ // done, don't do it again until we have more machine renames
+ m->llPendingMachineRenames.clear();
+
+ if (pDesc->llMedia.size())
+ {
+ // Handle the media registry saving in a separate thread, to
+ // avoid giant locking problems and passing up the list many
+ // levels up to whoever triggered saveSettings, as there are
+ // lots of places which would need to handle saving more settings.
+ pDesc->pVirtualBox = this;
+
+ //the function createThread() takes ownership of pDesc
+ //so there is no need to use delete operator for pDesc
+ //after calling this function
+ HRESULT hr = pDesc->createThread();
+ pDesc = NULL;
+
+ if (FAILED(hr))
+ {
+ // failure means that settings aren't saved, but there isn't
+ // much we can do besides avoiding memory leaks
+ LogRelFunc(("Failed to create thread for saving media registries (%Rhr)\n", hr));
+ }
+ }
+ else
+ delete pDesc;
+ }
+
+ struct {
+ MediaOList &llSource;
+ settings::MediaList &llTarget;
+ } s[] =
+ {
+ // hard disks
+ { m->allHardDisks, mediaRegistry.llHardDisks },
+ // CD/DVD images
+ { m->allDVDImages, mediaRegistry.llDvdImages },
+ // floppy images
+ { m->allFloppyImages, mediaRegistry.llFloppyImages }
+ };
+
+ HRESULT rc;
+
+ for (size_t i = 0; i < RT_ELEMENTS(s); ++i)
+ {
+ MediaOList &llSource = s[i].llSource;
+ settings::MediaList &llTarget = s[i].llTarget;
+ llTarget.clear();
+ for (MediaList::const_iterator it = llSource.begin();
+ it != llSource.end();
+ ++it)
+ {
+ Medium *pMedium = *it;
+ AutoCaller autoCaller(pMedium);
+ if (FAILED(autoCaller.rc())) throw autoCaller.rc();
+ AutoReadLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ if (pMedium->i_isInRegistry(uuidRegistry))
+ {
+ llTarget.push_back(settings::Medium::Empty);
+ rc = pMedium->i_saveSettings(llTarget.back(), strMachineFolder); // this recurses into child hard disks
+ if (FAILED(rc))
+ {
+ llTarget.pop_back();
+ throw rc;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Helper function which actually writes out VirtualBox.xml, the main configuration file.
+ * Gets called from the public VirtualBox::SaveSettings() as well as from various other
+ * places internally when settings need saving.
+ *
+ * @note Caller must have locked the VirtualBox object for writing and must not hold any
+ * other locks since this locks all kinds of member objects and trees temporarily,
+ * which could cause conflicts.
+ */
+HRESULT VirtualBox::i_saveSettings()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
+ AssertReturn(!m->strSettingsFilePath.isEmpty(), E_FAIL);
+
+ i_unmarkRegistryModified(i_getGlobalRegistryId());
+
+ HRESULT rc = S_OK;
+
+ try
+ {
+ // machines
+ m->pMainConfigFile->llMachines.clear();
+ {
+ AutoReadLock machinesLock(m->allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ for (MachinesOList::iterator it = m->allMachines.begin();
+ it != m->allMachines.end();
+ ++it)
+ {
+ Machine *pMachine = *it;
+ // save actual machine registry entry
+ settings::MachineRegistryEntry mre;
+ rc = pMachine->i_saveRegistryEntry(mre);
+ m->pMainConfigFile->llMachines.push_back(mre);
+ }
+ }
+
+ i_saveMediaRegistry(m->pMainConfigFile->mediaRegistry,
+ m->uuidMediaRegistry, // global media registry ID
+ Utf8Str::Empty); // strMachineFolder
+
+ m->pMainConfigFile->llDhcpServers.clear();
+ {
+ AutoReadLock dhcpLock(m->allDHCPServers.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ for (DHCPServersOList::const_iterator it = m->allDHCPServers.begin();
+ it != m->allDHCPServers.end();
+ ++it)
+ {
+ settings::DHCPServer d;
+ rc = (*it)->i_saveSettings(d);
+ if (FAILED(rc)) throw rc;
+ m->pMainConfigFile->llDhcpServers.push_back(d);
+ }
+ }
+
+#ifdef VBOX_WITH_NAT_SERVICE
+ /* Saving NAT Network configuration */
+ m->pMainConfigFile->llNATNetworks.clear();
+ {
+ AutoReadLock natNetworkLock(m->allNATNetworks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ for (NATNetworksOList::const_iterator it = m->allNATNetworks.begin();
+ it != m->allNATNetworks.end();
+ ++it)
+ {
+ settings::NATNetwork n;
+ rc = (*it)->i_saveSettings(n);
+ if (FAILED(rc)) throw rc;
+ m->pMainConfigFile->llNATNetworks.push_back(n);
+ }
+ }
+#endif
+
+#ifdef VBOX_WITH_VMNET
+ m->pMainConfigFile->llHostOnlyNetworks.clear();
+ {
+ AutoReadLock hostOnlyNetworkLock(m->allHostOnlyNetworks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ for (HostOnlyNetworksOList::const_iterator it = m->allHostOnlyNetworks.begin();
+ it != m->allHostOnlyNetworks.end();
+ ++it)
+ {
+ settings::HostOnlyNetwork n;
+ rc = (*it)->i_saveSettings(n);
+ if (FAILED(rc)) throw rc;
+ m->pMainConfigFile->llHostOnlyNetworks.push_back(n);
+ }
+ }
+#endif /* VBOX_WITH_VMNET */
+
+#ifdef VBOX_WITH_CLOUD_NET
+ m->pMainConfigFile->llCloudNetworks.clear();
+ {
+ AutoReadLock cloudNetworkLock(m->allCloudNetworks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ for (CloudNetworksOList::const_iterator it = m->allCloudNetworks.begin();
+ it != m->allCloudNetworks.end();
+ ++it)
+ {
+ settings::CloudNetwork n;
+ rc = (*it)->i_saveSettings(n);
+ if (FAILED(rc)) throw rc;
+ m->pMainConfigFile->llCloudNetworks.push_back(n);
+ }
+ }
+#endif /* VBOX_WITH_CLOUD_NET */
+ // leave extra data alone, it's still in the config file
+
+ // host data (USB filters)
+ rc = m->pHost->i_saveSettings(m->pMainConfigFile->host);
+ if (FAILED(rc)) throw rc;
+
+ rc = m->pSystemProperties->i_saveSettings(m->pMainConfigFile->systemProperties);
+ if (FAILED(rc)) throw rc;
+
+ // and write out the XML, still under the lock
+ m->pMainConfigFile->write(m->strSettingsFilePath);
+ }
+ catch (HRESULT err)
+ {
+ /* we assume that error info is set by the thrower */
+ rc = err;
+ }
+ catch (...)
+ {
+ rc = VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS);
+ }
+
+ return rc;
+}
+
+/**
+ * Helper to register the machine.
+ *
+ * When called during VirtualBox startup, adds the given machine to the
+ * collection of registered machines. Otherwise tries to mark the machine
+ * as registered, and, if succeeded, adds it to the collection and
+ * saves global settings.
+ *
+ * @note The caller must have added itself as a caller of the @a aMachine
+ * object if calls this method not on VirtualBox startup.
+ *
+ * @param aMachine machine to register
+ *
+ * @note Locks objects!
+ */
+HRESULT VirtualBox::i_registerMachine(Machine *aMachine)
+{
+ ComAssertRet(aMachine, E_INVALIDARG);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ HRESULT rc = S_OK;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ {
+ ComObjPtr<Machine> pMachine;
+ rc = i_findMachine(aMachine->i_getId(),
+ true /* fPermitInaccessible */,
+ false /* aDoSetError */,
+ &pMachine);
+ if (SUCCEEDED(rc))
+ {
+ /* sanity */
+ AutoLimitedCaller machCaller(pMachine);
+ AssertComRC(machCaller.rc());
+
+ return setError(E_INVALIDARG,
+ tr("Registered machine with UUID {%RTuuid} ('%s') already exists"),
+ aMachine->i_getId().raw(),
+ pMachine->i_getSettingsFileFull().c_str());
+ }
+
+ ComAssertRet(rc == VBOX_E_OBJECT_NOT_FOUND, rc);
+ rc = S_OK;
+ }
+
+ if (getObjectState().getState() != ObjectState::InInit)
+ {
+ rc = aMachine->i_prepareRegister();
+ if (FAILED(rc)) return rc;
+ }
+
+ /* add to the collection of registered machines */
+ m->allMachines.addChild(aMachine);
+
+ if (getObjectState().getState() != ObjectState::InInit)
+ rc = i_saveSettings();
+
+ return rc;
+}
+
+/**
+ * Remembers the given medium object by storing it in either the global
+ * medium registry or a machine one.
+ *
+ * @note Caller must hold the media tree lock for writing; in addition, this
+ * locks @a pMedium for reading
+ *
+ * @param pMedium Medium object to remember.
+ * @param ppMedium Actually stored medium object. Can be different if due
+ * to an unavoidable race there was a duplicate Medium object
+ * created.
+ * @param mediaTreeLock Reference to the AutoWriteLock holding the media tree
+ * lock, necessary to release it in the right spot.
+ * @param fCalledFromMediumInit Flag whether this is called from Medium::init().
+ * @return
+ */
+HRESULT VirtualBox::i_registerMedium(const ComObjPtr<Medium> &pMedium,
+ ComObjPtr<Medium> *ppMedium,
+ AutoWriteLock &mediaTreeLock,
+ bool fCalledFromMediumInit)
+{
+ AssertReturn(pMedium != NULL, E_INVALIDARG);
+ AssertReturn(ppMedium != NULL, E_INVALIDARG);
+
+ // caller must hold the media tree write lock
+ Assert(i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoCaller mediumCaller(pMedium);
+ AssertComRCReturnRC(mediumCaller.rc());
+
+ bool fAddToGlobalRegistry = false;
+ const char *pszDevType = NULL;
+ Guid regId;
+ ObjectsList<Medium> *pall = NULL;
+ DeviceType_T devType;
+ {
+ AutoReadLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
+ devType = pMedium->i_getDeviceType();
+
+ if (!pMedium->i_getFirstRegistryMachineId(regId))
+ fAddToGlobalRegistry = true;
+ }
+ switch (devType)
+ {
+ case DeviceType_HardDisk:
+ pall = &m->allHardDisks;
+ pszDevType = tr("hard disk");
+ break;
+ case DeviceType_DVD:
+ pszDevType = tr("DVD image");
+ pall = &m->allDVDImages;
+ break;
+ case DeviceType_Floppy:
+ pszDevType = tr("floppy image");
+ pall = &m->allFloppyImages;
+ break;
+ default:
+ AssertMsgFailedReturn(("invalid device type %d", devType), E_INVALIDARG);
+ }
+
+ Guid id;
+ Utf8Str strLocationFull;
+ ComObjPtr<Medium> pParent;
+ {
+ AutoReadLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
+ id = pMedium->i_getId();
+ strLocationFull = pMedium->i_getLocationFull();
+ pParent = pMedium->i_getParent();
+ }
+
+ HRESULT rc;
+
+ Utf8Str strConflict;
+ ComObjPtr<Medium> pDupMedium;
+ rc = i_checkMediaForConflicts(id,
+ strLocationFull,
+ strConflict,
+ &pDupMedium);
+ if (FAILED(rc)) return rc;
+
+ if (pDupMedium.isNull())
+ {
+ if (strConflict.length())
+ return setError(E_INVALIDARG,
+ tr("Cannot register the %s '%s' {%RTuuid} because a %s already exists"),
+ pszDevType,
+ strLocationFull.c_str(),
+ id.raw(),
+ strConflict.c_str(),
+ m->strSettingsFilePath.c_str());
+
+ // add to the collection if it is a base medium
+ if (pParent.isNull())
+ pall->getList().push_back(pMedium);
+
+ // store all hard disks (even differencing images) in the map
+ if (devType == DeviceType_HardDisk)
+ m->mapHardDisks[id] = pMedium;
+
+ mediumCaller.release();
+ mediaTreeLock.release();
+ *ppMedium = pMedium;
+ }
+ else
+ {
+ // pMedium may be the last reference to the Medium object, and the
+ // caller may have specified the same ComObjPtr as the output parameter.
+ // In this case the assignment will uninit the object, and we must not
+ // have a caller pending.
+ mediumCaller.release();
+ // release media tree lock, must not be held at uninit time.
+ mediaTreeLock.release();
+ // must not hold the media tree write lock any more
+ Assert(!i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+ *ppMedium = pDupMedium;
+ }
+
+ if (fAddToGlobalRegistry)
+ {
+ AutoWriteLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
+ if ( fCalledFromMediumInit
+ ? (*ppMedium)->i_addRegistryNoCallerCheck(m->uuidMediaRegistry)
+ : (*ppMedium)->i_addRegistry(m->uuidMediaRegistry))
+ i_markRegistryModified(m->uuidMediaRegistry);
+ }
+
+ // Restore the initial lock state, so that no unexpected lock changes are
+ // done by this method, which would need adjustments everywhere.
+ mediaTreeLock.acquire();
+
+ return rc;
+}
+
+/**
+ * Removes the given medium from the respective registry.
+ *
+ * @param pMedium Hard disk object to remove.
+ *
+ * @note Caller must hold the media tree lock for writing; in addition, this locks @a pMedium for reading
+ */
+HRESULT VirtualBox::i_unregisterMedium(Medium *pMedium)
+{
+ AssertReturn(pMedium != NULL, E_INVALIDARG);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoCaller mediumCaller(pMedium);
+ AssertComRCReturnRC(mediumCaller.rc());
+
+ // caller must hold the media tree write lock
+ Assert(i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
+
+ Guid id;
+ ComObjPtr<Medium> pParent;
+ DeviceType_T devType;
+ {
+ AutoReadLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
+ id = pMedium->i_getId();
+ pParent = pMedium->i_getParent();
+ devType = pMedium->i_getDeviceType();
+ }
+
+ ObjectsList<Medium> *pall = NULL;
+ switch (devType)
+ {
+ case DeviceType_HardDisk:
+ pall = &m->allHardDisks;
+ break;
+ case DeviceType_DVD:
+ pall = &m->allDVDImages;
+ break;
+ case DeviceType_Floppy:
+ pall = &m->allFloppyImages;
+ break;
+ default:
+ AssertMsgFailedReturn(("invalid device type %d", devType), E_INVALIDARG);
+ }
+
+ // remove from the collection if it is a base medium
+ if (pParent.isNull())
+ pall->getList().remove(pMedium);
+
+ // remove all hard disks (even differencing images) from map
+ if (devType == DeviceType_HardDisk)
+ {
+ size_t cnt = m->mapHardDisks.erase(id);
+ Assert(cnt == 1);
+ NOREF(cnt);
+ }
+
+ return S_OK;
+}
+
+/**
+ * Unregisters all Medium objects which belong to the given machine registry.
+ * Gets called from Machine::uninit() just before the machine object dies
+ * and must only be called with a machine UUID as the registry ID.
+ *
+ * Locks the media tree.
+ *
+ * @param uuidMachine Medium registry ID (always a machine UUID)
+ * @return
+ */
+HRESULT VirtualBox::i_unregisterMachineMedia(const Guid &uuidMachine)
+{
+ Assert(!uuidMachine.isZero() && uuidMachine.isValid());
+
+ LogFlowFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ MediaList llMedia2Close;
+
+ {
+ AutoWriteLock tlock(i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ for (MediaOList::iterator it = m->allHardDisks.getList().begin();
+ it != m->allHardDisks.getList().end();
+ ++it)
+ {
+ ComObjPtr<Medium> pMedium = *it;
+ AutoCaller medCaller(pMedium);
+ if (FAILED(medCaller.rc())) return medCaller.rc();
+ AutoReadLock medlock(pMedium COMMA_LOCKVAL_SRC_POS);
+ Log(("Looking at medium %RTuuid\n", pMedium->i_getId().raw()));
+
+ /* If the medium is still in the registry then either some code is
+ * seriously buggy (unregistering a VM removes it automatically),
+ * or the reference to a Machine object is destroyed without ever
+ * being registered. The second condition checks if a medium is
+ * in no registry, which indicates (set by unregistering) that a
+ * medium is not used by any other VM and thus can be closed. */
+ Guid dummy;
+ if ( pMedium->i_isInRegistry(uuidMachine)
+ || !pMedium->i_getFirstRegistryMachineId(dummy))
+ {
+ /* Collect all medium objects into llMedia2Close,
+ * in right order for closing. */
+ MediaList llMediaTodo;
+ llMediaTodo.push_back(pMedium);
+
+ while (llMediaTodo.size() > 0)
+ {
+ ComObjPtr<Medium> pCurrent = llMediaTodo.front();
+ llMediaTodo.pop_front();
+
+ /* Add to front, order must be children then parent. */
+ Log(("Pushing medium %RTuuid (front)\n", pCurrent->i_getId().raw()));
+ llMedia2Close.push_front(pCurrent);
+
+ /* process all children */
+ MediaList::const_iterator itBegin = pCurrent->i_getChildren().begin();
+ MediaList::const_iterator itEnd = pCurrent->i_getChildren().end();
+ for (MediaList::const_iterator it2 = itBegin; it2 != itEnd; ++it2)
+ llMediaTodo.push_back(*it2);
+ }
+ }
+ }
+ }
+
+ for (MediaList::iterator it = llMedia2Close.begin();
+ it != llMedia2Close.end();
+ ++it)
+ {
+ ComObjPtr<Medium> pMedium = *it;
+ Log(("Closing medium %RTuuid\n", pMedium->i_getId().raw()));
+ AutoCaller mac(pMedium);
+ pMedium->i_close(mac);
+ }
+
+ LogFlowFuncLeave();
+
+ return S_OK;
+}
+
+/**
+ * Removes the given machine object from the internal list of registered machines.
+ * Called from Machine::Unregister().
+ * @param pMachine
+ * @param aCleanupMode How to handle medium attachments. For
+ * CleanupMode_UnregisterOnly the associated medium objects will be
+ * closed when the Machine object is uninitialized, otherwise they will
+ * go to the global registry if no better registry is found.
+ * @param id UUID of the machine. Must be passed by caller because machine may be dead by this time.
+ * @return
+ */
+HRESULT VirtualBox::i_unregisterMachine(Machine *pMachine,
+ CleanupMode_T aCleanupMode,
+ const Guid &id)
+{
+ // remove from the collection of registered machines
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->allMachines.removeChild(pMachine);
+ // save the global registry
+ HRESULT rc = i_saveSettings();
+ alock.release();
+
+ /*
+ * Now go over all known media and checks if they were registered in the
+ * media registry of the given machine. Each such medium is then moved to
+ * a different media registry to make sure it doesn't get lost since its
+ * media registry is about to go away.
+ *
+ * This fixes the following use case: Image A.vdi of machine A is also used
+ * by machine B, but registered in the media registry of machine A. If machine
+ * A is deleted, A.vdi must be moved to the registry of B, or else B will
+ * become inaccessible.
+ */
+ {
+ AutoReadLock tlock(i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
+ // iterate over the list of *base* images
+ for (MediaOList::iterator it = m->allHardDisks.getList().begin();
+ it != m->allHardDisks.getList().end();
+ ++it)
+ {
+ ComObjPtr<Medium> &pMedium = *it;
+ AutoCaller medCaller(pMedium);
+ if (FAILED(medCaller.rc())) return medCaller.rc();
+ AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
+
+ if (pMedium->i_removeRegistryAll(id))
+ {
+ // machine ID was found in base medium's registry list:
+ // move this base image and all its children to another registry then
+ // 1) first, find a better registry to add things to
+ const Guid *puuidBetter = pMedium->i_getAnyMachineBackref(id);
+ if (puuidBetter)
+ {
+ // 2) better registry found: then use that
+ pMedium->i_addRegistryAll(*puuidBetter);
+ // 3) and make sure the registry is saved below
+ mlock.release();
+ tlock.release();
+ i_markRegistryModified(*puuidBetter);
+ tlock.acquire();
+ mlock.acquire();
+ }
+ else if (aCleanupMode != CleanupMode_UnregisterOnly)
+ {
+ pMedium->i_addRegistryAll(i_getGlobalRegistryId());
+ mlock.release();
+ tlock.release();
+ i_markRegistryModified(i_getGlobalRegistryId());
+ tlock.acquire();
+ mlock.acquire();
+ }
+ }
+ }
+ }
+
+ i_saveModifiedRegistries();
+
+ /* fire an event */
+ i_onMachineRegistered(id, FALSE);
+
+ return rc;
+}
+
+/**
+ * Marks the registry for @a uuid as modified, so that it's saved in a later
+ * call to saveModifiedRegistries().
+ *
+ * @param uuid
+ */
+void VirtualBox::i_markRegistryModified(const Guid &uuid)
+{
+ if (uuid == i_getGlobalRegistryId())
+ ASMAtomicIncU64(&m->uRegistryNeedsSaving);
+ else
+ {
+ ComObjPtr<Machine> pMachine;
+ HRESULT rc = i_findMachine(uuid,
+ false /* fPermitInaccessible */,
+ false /* aSetError */,
+ &pMachine);
+ if (SUCCEEDED(rc))
+ {
+ AutoCaller machineCaller(pMachine);
+ if (SUCCEEDED(machineCaller.rc()) && pMachine->i_isAccessible())
+ ASMAtomicIncU64(&pMachine->uRegistryNeedsSaving);
+ }
+ }
+}
+
+/**
+ * Marks the registry for @a uuid as unmodified, so that it's not saved in
+ * a later call to saveModifiedRegistries().
+ *
+ * @param uuid
+ */
+void VirtualBox::i_unmarkRegistryModified(const Guid &uuid)
+{
+ uint64_t uOld;
+ if (uuid == i_getGlobalRegistryId())
+ {
+ for (;;)
+ {
+ uOld = ASMAtomicReadU64(&m->uRegistryNeedsSaving);
+ if (!uOld)
+ break;
+ if (ASMAtomicCmpXchgU64(&m->uRegistryNeedsSaving, 0, uOld))
+ break;
+ ASMNopPause();
+ }
+ }
+ else
+ {
+ ComObjPtr<Machine> pMachine;
+ HRESULT rc = i_findMachine(uuid,
+ false /* fPermitInaccessible */,
+ false /* aSetError */,
+ &pMachine);
+ if (SUCCEEDED(rc))
+ {
+ AutoCaller machineCaller(pMachine);
+ if (SUCCEEDED(machineCaller.rc()))
+ {
+ for (;;)
+ {
+ uOld = ASMAtomicReadU64(&pMachine->uRegistryNeedsSaving);
+ if (!uOld)
+ break;
+ if (ASMAtomicCmpXchgU64(&pMachine->uRegistryNeedsSaving, 0, uOld))
+ break;
+ ASMNopPause();
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Saves all settings files according to the modified flags in the Machine
+ * objects and in the VirtualBox object.
+ *
+ * This locks machines and the VirtualBox object as necessary, so better not
+ * hold any locks before calling this.
+ *
+ * @return
+ */
+void VirtualBox::i_saveModifiedRegistries()
+{
+ HRESULT rc = S_OK;
+ bool fNeedsGlobalSettings = false;
+ uint64_t uOld;
+
+ {
+ AutoReadLock alock(m->allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ for (MachinesOList::iterator it = m->allMachines.begin();
+ it != m->allMachines.end();
+ ++it)
+ {
+ const ComObjPtr<Machine> &pMachine = *it;
+
+ for (;;)
+ {
+ uOld = ASMAtomicReadU64(&pMachine->uRegistryNeedsSaving);
+ if (!uOld)
+ break;
+ if (ASMAtomicCmpXchgU64(&pMachine->uRegistryNeedsSaving, 0, uOld))
+ break;
+ ASMNopPause();
+ }
+ if (uOld)
+ {
+ AutoCaller autoCaller(pMachine);
+ if (FAILED(autoCaller.rc()))
+ continue;
+ /* object is already dead, no point in saving settings */
+ if (getObjectState().getState() != ObjectState::Ready)
+ continue;
+ AutoWriteLock mlock(pMachine COMMA_LOCKVAL_SRC_POS);
+ rc = pMachine->i_saveSettings(&fNeedsGlobalSettings, mlock,
+ Machine::SaveS_Force); // caller said save, so stop arguing
+ }
+ }
+ }
+
+ for (;;)
+ {
+ uOld = ASMAtomicReadU64(&m->uRegistryNeedsSaving);
+ if (!uOld)
+ break;
+ if (ASMAtomicCmpXchgU64(&m->uRegistryNeedsSaving, 0, uOld))
+ break;
+ ASMNopPause();
+ }
+ if (uOld || fNeedsGlobalSettings)
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ rc = i_saveSettings();
+ }
+ NOREF(rc); /* XXX */
+}
+
+
+/* static */
+const com::Utf8Str &VirtualBox::i_getVersionNormalized()
+{
+ return sVersionNormalized;
+}
+
+/**
+ * Checks if the path to the specified file exists, according to the path
+ * information present in the file name. Optionally the path is created.
+ *
+ * Note that the given file name must contain the full path otherwise the
+ * extracted relative path will be created based on the current working
+ * directory which is normally unknown.
+ *
+ * @param strFileName Full file name which path is checked/created.
+ * @param fCreate Flag if the path should be created if it doesn't exist.
+ *
+ * @return Extended error information on failure to check/create the path.
+ */
+/* static */
+HRESULT VirtualBox::i_ensureFilePathExists(const Utf8Str &strFileName, bool fCreate)
+{
+ Utf8Str strDir(strFileName);
+ strDir.stripFilename();
+ if (!RTDirExists(strDir.c_str()))
+ {
+ if (fCreate)
+ {
+ int vrc = RTDirCreateFullPath(strDir.c_str(), 0700);
+ if (RT_FAILURE(vrc))
+ return i_setErrorStaticBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not create the directory '%s' (%Rrc)"),
+ strDir.c_str(),
+ vrc);
+ }
+ else
+ return i_setErrorStaticBoth(VBOX_E_IPRT_ERROR, VERR_FILE_NOT_FOUND,
+ tr("Directory '%s' does not exist"), strDir.c_str());
+ }
+
+ return S_OK;
+}
+
+const Utf8Str& VirtualBox::i_settingsFilePath()
+{
+ return m->strSettingsFilePath;
+}
+
+/**
+ * Returns the lock handle which protects the machines list. As opposed
+ * to version 3.1 and earlier, these lists are no longer protected by the
+ * VirtualBox lock, but by this more specialized lock. Mind the locking
+ * order: always request this lock after the VirtualBox object lock but
+ * before the locks of any machine object. See AutoLock.h.
+ */
+RWLockHandle& VirtualBox::i_getMachinesListLockHandle()
+{
+ return m->lockMachines;
+}
+
+/**
+ * Returns the lock handle which protects the media trees (hard disks,
+ * DVDs, floppies). As opposed to version 3.1 and earlier, these lists
+ * are no longer protected by the VirtualBox lock, but by this more
+ * specialized lock. Mind the locking order: always request this lock
+ * after the VirtualBox object lock but before the locks of the media
+ * objects contained in these lists. See AutoLock.h.
+ */
+RWLockHandle& VirtualBox::i_getMediaTreeLockHandle()
+{
+ return m->lockMedia;
+}
+
+/**
+ * Thread function that handles custom events posted using #i_postEvent().
+ */
+// static
+DECLCALLBACK(int) VirtualBox::AsyncEventHandler(RTTHREAD thread, void *pvUser)
+{
+ LogFlowFuncEnter();
+
+ AssertReturn(pvUser, VERR_INVALID_POINTER);
+
+ HRESULT hr = com::Initialize();
+ if (FAILED(hr))
+ return VERR_COM_UNEXPECTED;
+
+ int rc = VINF_SUCCESS;
+
+ try
+ {
+ /* Create an event queue for the current thread. */
+ EventQueue *pEventQueue = new EventQueue();
+ AssertPtr(pEventQueue);
+
+ /* Return the queue to the one who created this thread. */
+ *(static_cast <EventQueue **>(pvUser)) = pEventQueue;
+
+ /* signal that we're ready. */
+ RTThreadUserSignal(thread);
+
+ /*
+ * In case of spurious wakeups causing VERR_TIMEOUTs and/or other return codes
+ * we must not stop processing events and delete the pEventQueue object. This must
+ * be done ONLY when we stop this loop via interruptEventQueueProcessing().
+ * See @bugref{5724}.
+ */
+ for (;;)
+ {
+ rc = pEventQueue->processEventQueue(RT_INDEFINITE_WAIT);
+ if (rc == VERR_INTERRUPTED)
+ {
+ LogFlow(("Event queue processing ended with rc=%Rrc\n", rc));
+ rc = VINF_SUCCESS; /* Set success when exiting. */
+ break;
+ }
+ }
+
+ delete pEventQueue;
+ }
+ catch (std::bad_alloc &ba)
+ {
+ rc = VERR_NO_MEMORY;
+ NOREF(ba);
+ }
+
+ com::Shutdown();
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+#if 0 /* obsoleted by AsyncEvent */
+/**
+ * Prepare the event using the overwritten #prepareEventDesc method and fire.
+ *
+ * @note Locks the managed VirtualBox object for reading but leaves the lock
+ * before iterating over callbacks and calling their methods.
+ */
+void *VirtualBox::CallbackEvent::handler()
+{
+ if (!mVirtualBox)
+ return NULL;
+
+ AutoCaller autoCaller(mVirtualBox);
+ if (!autoCaller.isOk())
+ {
+ Log1WarningFunc(("VirtualBox has been uninitialized (state=%d), the callback event is discarded!\n",
+ mVirtualBox->getObjectState().getState()));
+ /* We don't need mVirtualBox any more, so release it */
+ mVirtualBox = NULL;
+ return NULL;
+ }
+
+ {
+ VBoxEventDesc evDesc;
+ prepareEventDesc(mVirtualBox->m->pEventSource, evDesc);
+
+ evDesc.fire(/* don't wait for delivery */0);
+ }
+
+ mVirtualBox = NULL; /* Not needed any longer. Still make sense to do this? */
+ return NULL;
+}
+#endif
+
+/**
+ * Called on the event handler thread.
+ *
+ * @note Locks the managed VirtualBox object for reading but leaves the lock
+ * before iterating over callbacks and calling their methods.
+ */
+void *VirtualBox::AsyncEvent::handler()
+{
+ if (mVirtualBox)
+ {
+ AutoCaller autoCaller(mVirtualBox);
+ if (autoCaller.isOk())
+ {
+ VBoxEventDesc EvtDesc(mEvent, mVirtualBox->m->pEventSource);
+ EvtDesc.fire(/* don't wait for delivery */0);
+ }
+ else
+ Log1WarningFunc(("VirtualBox has been uninitialized (state=%d), the callback event is discarded!\n",
+ mVirtualBox->getObjectState().getState()));
+ mVirtualBox = NULL; /* Old code did this, not really necessary, but whatever. */
+ }
+ mEvent.setNull();
+ return NULL;
+}
+
+//STDMETHODIMP VirtualBox::CreateDHCPServerForInterface(/*IHostNetworkInterface * aIinterface,*/ IDHCPServer ** aServer)
+//{
+// return E_NOTIMPL;
+//}
+
+HRESULT VirtualBox::createDHCPServer(const com::Utf8Str &aName,
+ ComPtr<IDHCPServer> &aServer)
+{
+ ComObjPtr<DHCPServer> dhcpServer;
+ dhcpServer.createObject();
+ HRESULT rc = dhcpServer->init(this, aName);
+ if (FAILED(rc)) return rc;
+
+ rc = i_registerDHCPServer(dhcpServer, true);
+ if (FAILED(rc)) return rc;
+
+ dhcpServer.queryInterfaceTo(aServer.asOutParam());
+
+ return rc;
+}
+
+HRESULT VirtualBox::findDHCPServerByNetworkName(const com::Utf8Str &aName,
+ ComPtr<IDHCPServer> &aServer)
+{
+ HRESULT rc = S_OK;
+ ComPtr<DHCPServer> found;
+
+ AutoReadLock alock(m->allDHCPServers.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ for (DHCPServersOList::const_iterator it = m->allDHCPServers.begin();
+ it != m->allDHCPServers.end();
+ ++it)
+ {
+ Bstr bstrNetworkName;
+ rc = (*it)->COMGETTER(NetworkName)(bstrNetworkName.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ if (Utf8Str(bstrNetworkName) == aName)
+ {
+ found = *it;
+ break;
+ }
+ }
+
+ if (!found)
+ return E_INVALIDARG;
+
+ rc = found.queryInterfaceTo(aServer.asOutParam());
+
+ return rc;
+}
+
+HRESULT VirtualBox::removeDHCPServer(const ComPtr<IDHCPServer> &aServer)
+{
+ IDHCPServer *aP = aServer;
+
+ HRESULT rc = i_unregisterDHCPServer(static_cast<DHCPServer *>(aP));
+
+ return rc;
+}
+
+/**
+ * Remembers the given DHCP server in the settings.
+ *
+ * @param aDHCPServer DHCP server object to remember.
+ * @param aSaveSettings @c true to save settings to disk (default).
+ *
+ * When @a aSaveSettings is @c true, this operation may fail because of the
+ * failed #i_saveSettings() method it calls. In this case, the dhcp server object
+ * will not be remembered. It is therefore the responsibility of the caller to
+ * call this method as the last step of some action that requires registration
+ * in order to make sure that only fully functional dhcp server objects get
+ * registered.
+ *
+ * @note Locks this object for writing and @a aDHCPServer for reading.
+ */
+HRESULT VirtualBox::i_registerDHCPServer(DHCPServer *aDHCPServer,
+ bool aSaveSettings /*= true*/)
+{
+ AssertReturn(aDHCPServer != NULL, E_INVALIDARG);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ // Acquire a lock on the VirtualBox object early to avoid lock order issues
+ // when we call i_saveSettings() later on.
+ AutoWriteLock vboxLock(this COMMA_LOCKVAL_SRC_POS);
+ // need it below, in findDHCPServerByNetworkName (reading) and in
+ // m->allDHCPServers.addChild, so need to get it here to avoid lock
+ // order trouble with dhcpServerCaller
+ AutoWriteLock alock(m->allDHCPServers.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ AutoCaller dhcpServerCaller(aDHCPServer);
+ AssertComRCReturnRC(dhcpServerCaller.rc());
+
+ Bstr bstrNetworkName;
+ HRESULT rc = S_OK;
+ rc = aDHCPServer->COMGETTER(NetworkName)(bstrNetworkName.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ ComPtr<IDHCPServer> existing;
+ rc = findDHCPServerByNetworkName(Utf8Str(bstrNetworkName), existing);
+ if (SUCCEEDED(rc))
+ return E_INVALIDARG;
+ rc = S_OK;
+
+ m->allDHCPServers.addChild(aDHCPServer);
+ // we need to release the list lock before we attempt to acquire locks
+ // on other objects in i_saveSettings (see @bugref{7500})
+ alock.release();
+
+ if (aSaveSettings)
+ {
+ // we acquired the lock on 'this' earlier to avoid lock order issues
+ rc = i_saveSettings();
+
+ if (FAILED(rc))
+ {
+ alock.acquire();
+ m->allDHCPServers.removeChild(aDHCPServer);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Removes the given DHCP server from the settings.
+ *
+ * @param aDHCPServer DHCP server object to remove.
+ *
+ * This operation may fail because of the failed #i_saveSettings() method it
+ * calls. In this case, the DHCP server will NOT be removed from the settings
+ * when this method returns.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT VirtualBox::i_unregisterDHCPServer(DHCPServer *aDHCPServer)
+{
+ AssertReturn(aDHCPServer != NULL, E_INVALIDARG);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoCaller dhcpServerCaller(aDHCPServer);
+ AssertComRCReturnRC(dhcpServerCaller.rc());
+
+ AutoWriteLock vboxLock(this COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock alock(m->allDHCPServers.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+ m->allDHCPServers.removeChild(aDHCPServer);
+ // we need to release the list lock before we attempt to acquire locks
+ // on other objects in i_saveSettings (see @bugref{7500})
+ alock.release();
+
+ HRESULT rc = i_saveSettings();
+
+ // undo the changes if we failed to save them
+ if (FAILED(rc))
+ {
+ alock.acquire();
+ m->allDHCPServers.addChild(aDHCPServer);
+ }
+
+ return rc;
+}
+
+
+/**
+ * NAT Network
+ */
+HRESULT VirtualBox::createNATNetwork(const com::Utf8Str &aNetworkName,
+ ComPtr<INATNetwork> &aNetwork)
+{
+#ifdef VBOX_WITH_NAT_SERVICE
+ ComObjPtr<NATNetwork> natNetwork;
+ natNetwork.createObject();
+ HRESULT rc = natNetwork->init(this, aNetworkName);
+ if (FAILED(rc)) return rc;
+
+ rc = i_registerNATNetwork(natNetwork, true);
+ if (FAILED(rc)) return rc;
+
+ natNetwork.queryInterfaceTo(aNetwork.asOutParam());
+
+ ::FireNATNetworkCreationDeletionEvent(m->pEventSource, aNetworkName, TRUE);
+
+ return rc;
+#else
+ NOREF(aNetworkName);
+ NOREF(aNetwork);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT VirtualBox::findNATNetworkByName(const com::Utf8Str &aNetworkName,
+ ComPtr<INATNetwork> &aNetwork)
+{
+#ifdef VBOX_WITH_NAT_SERVICE
+
+ HRESULT rc = S_OK;
+ ComPtr<NATNetwork> found;
+
+ AutoReadLock alock(m->allNATNetworks.getLockHandle() COMMA_LOCKVAL_SRC_POS);
+
+ for (NATNetworksOList::const_iterator it = m->allNATNetworks.begin();
+ it != m->allNATNetworks.end();
+ ++it)
+ {
+ Bstr bstrNATNetworkName;
+ rc = (*it)->COMGETTER(NetworkName)(bstrNATNetworkName.asOutParam());
+ if (FAILED(rc)) return rc;
+
+ if (Utf8Str(bstrNATNetworkName) == aNetworkName)
+ {
+ found = *it;
+ break;
+ }
+ }
+
+ if (!found)
+ return E_INVALIDARG;
+ found.queryInterfaceTo(aNetwork.asOutParam());
+ return rc;
+#else
+ NOREF(aNetworkName);
+ NOREF(aNetwork);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT VirtualBox::removeNATNetwork(const ComPtr<INATNetwork> &aNetwork)
+{
+#ifdef VBOX_WITH_NAT_SERVICE
+ Bstr name;
+ HRESULT rc = aNetwork->COMGETTER(NetworkName)(name.asOutParam());
+ if (FAILED(rc))
+ return rc;
+ INATNetwork *p = aNetwork;
+ NATNetwork *network = static_cast<NATNetwork *>(p);
+ rc = i_unregisterNATNetwork(network, true);
+ ::FireNATNetworkCreationDeletionEvent(m->pEventSource, name.raw(), FALSE);
+ return rc;
+#else
+ NOREF(aNetwork);
+ return E_NOTIMPL;
+#endif
+
+}
+/**
+ * Remembers the given NAT network in the settings.
+ *
+ * @param aNATNetwork NAT Network object to remember.
+ * @param aSaveSettings @c true to save settings to disk (default).
+ *
+ *
+ * @note Locks this object for writing and @a aNATNetwork for reading.
+ */
+HRESULT VirtualBox::i_registerNATNetwork(NATNetwork *aNATNetwork,
+ bool aSaveSettings /*= true*/)
+{
+#ifdef VBOX_WITH_NAT_SERVICE
+ AssertReturn(aNATNetwork != NULL, E_INVALIDARG);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoCaller natNetworkCaller(aNATNetwork);
+ AssertComRCReturnRC(natNetworkCaller.rc());
+
+ Bstr name;
+ HRESULT rc;
+ rc = aNATNetwork->COMGETTER(NetworkName)(name.asOutParam());
+ AssertComRCReturnRC(rc);
+
+ /* returned value isn't 0 and aSaveSettings is true
+ * means that we create duplicate, otherwise we just load settings.
+ */
+ if ( sNatNetworkNameToRefCount[name]
+ && aSaveSettings)
+ AssertComRCReturnRC(E_INVALIDARG);
+
+ rc = S_OK;
+
+ sNatNetworkNameToRefCount[name] = 0;
+
+ m->allNATNetworks.addChild(aNATNetwork);
+
+ if (aSaveSettings)
+ {
+ AutoWriteLock vboxLock(this COMMA_LOCKVAL_SRC_POS);
+ rc = i_saveSettings();
+ vboxLock.release();
+
+ if (FAILED(rc))
+ i_unregisterNATNetwork(aNATNetwork, false /* aSaveSettings */);
+ }
+
+ return rc;
+#else
+ NOREF(aNATNetwork);
+ NOREF(aSaveSettings);
+ /* No panic please (silently ignore) */
+ return S_OK;
+#endif
+}
+
+/**
+ * Removes the given NAT network from the settings.
+ *
+ * @param aNATNetwork NAT network object to remove.
+ * @param aSaveSettings @c true to save settings to disk (default).
+ *
+ * When @a aSaveSettings is @c true, this operation may fail because of the
+ * failed #i_saveSettings() method it calls. In this case, the DHCP server
+ * will NOT be removed from the settingsi when this method returns.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT VirtualBox::i_unregisterNATNetwork(NATNetwork *aNATNetwork,
+ bool aSaveSettings /*= true*/)
+{
+#ifdef VBOX_WITH_NAT_SERVICE
+ AssertReturn(aNATNetwork != NULL, E_INVALIDARG);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoCaller natNetworkCaller(aNATNetwork);
+ AssertComRCReturnRC(natNetworkCaller.rc());
+
+ Bstr name;
+ HRESULT rc = aNATNetwork->COMGETTER(NetworkName)(name.asOutParam());
+ /* Hm, there're still running clients. */
+ if (FAILED(rc) || sNatNetworkNameToRefCount[name])
+ AssertComRCReturnRC(E_INVALIDARG);
+
+ m->allNATNetworks.removeChild(aNATNetwork);
+
+ if (aSaveSettings)
+ {
+ AutoWriteLock vboxLock(this COMMA_LOCKVAL_SRC_POS);
+ rc = i_saveSettings();
+ vboxLock.release();
+
+ if (FAILED(rc))
+ i_registerNATNetwork(aNATNetwork, false /* aSaveSettings */);
+ }
+
+ return rc;
+#else
+ NOREF(aNATNetwork);
+ NOREF(aSaveSettings);
+ return E_NOTIMPL;
+#endif
+}
+
+
+HRESULT VirtualBox::findProgressById(const com::Guid &aId,
+ ComPtr<IProgress> &aProgressObject)
+{
+ if (!aId.isValid())
+ return setError(E_INVALIDARG,
+ tr("The provided progress object GUID is invalid"));
+
+ /* protect mProgressOperations */
+ AutoReadLock safeLock(m->mtxProgressOperations COMMA_LOCKVAL_SRC_POS);
+
+ ProgressMap::const_iterator it = m->mapProgressOperations.find(aId);
+ if (it != m->mapProgressOperations.end())
+ {
+ aProgressObject = it->second;
+ return S_OK;
+ }
+ return setError(E_INVALIDARG,
+ tr("The progress object with the given GUID could not be found"));
+}
+
+
+/**
+ * Retains a reference to the default cryptographic interface.
+ *
+ * @returns COM status code.
+ * @param ppCryptoIf Where to store the pointer to the cryptographic interface on success.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT VirtualBox::i_retainCryptoIf(PCVBOXCRYPTOIF *ppCryptoIf)
+{
+ AssertReturn(ppCryptoIf != NULL, E_INVALIDARG);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ /*
+ * No object lock due to some lock order fun with Machine objects.
+ * There is a dedicated critical section to protect against concurrency
+ * issues when loading the module.
+ */
+ RTCritSectEnter(&m->CritSectModCrypto);
+
+ /* Try to load the extension pack module if it isn't currently. */
+ HRESULT hrc = S_OK;
+ if (m->hLdrModCrypto == NIL_RTLDRMOD)
+ {
+#ifdef VBOX_WITH_EXTPACK
+ /*
+ * Check that a crypto extension pack name is set and resolve it into a
+ * library path.
+ */
+ Utf8Str strExtPack;
+ hrc = m->pSystemProperties->getDefaultCryptoExtPack(strExtPack);
+ if (FAILED(hrc))
+ {
+ RTCritSectLeave(&m->CritSectModCrypto);
+ return hrc;
+ }
+ if (strExtPack.isEmpty())
+ {
+ RTCritSectLeave(&m->CritSectModCrypto);
+ return setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Ńo extension pack providing a cryptographic support module could be found"));
+ }
+
+ Utf8Str strCryptoLibrary;
+ int vrc = m->ptrExtPackManager->i_getCryptoLibraryPathForExtPack(&strExtPack, &strCryptoLibrary);
+ if (RT_SUCCESS(vrc))
+ {
+ RTERRINFOSTATIC ErrInfo;
+ vrc = SUPR3HardenedLdrLoadPlugIn(strCryptoLibrary.c_str(), &m->hLdrModCrypto, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ /* Resolve the entry point and query the pointer to the cryptographic interface. */
+ PFNVBOXCRYPTOENTRY pfnCryptoEntry = NULL;
+ vrc = RTLdrGetSymbol(m->hLdrModCrypto, VBOX_CRYPTO_MOD_ENTRY_POINT, (void **)&pfnCryptoEntry);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pfnCryptoEntry(&m->pCryptoIf);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Failed to query the interface callback table from the cryptographic support module '%s' from extension pack '%s'"),
+ strCryptoLibrary.c_str(), strExtPack.c_str());
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Failed to resolve the entry point for the cryptographic support module '%s' from extension pack '%s'"),
+ strCryptoLibrary.c_str(), strExtPack.c_str());
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Couldn't load the cryptographic support module '%s' from extension pack '%s' (error: '%s')"),
+ strCryptoLibrary.c_str(), strExtPack.c_str(), ErrInfo.Core.pszMsg);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Couldn't resolve the library path of the crpytographic support module for extension pack '%s'"),
+ strExtPack.c_str());
+#else
+ hrc = setError(VBOX_E_NOT_SUPPORTED,
+ tr("The cryptographic support module is not supported in this build because extension packs are not supported"));
+#endif
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ ASMAtomicIncU32(&m->cRefsCrypto);
+ *ppCryptoIf = m->pCryptoIf;
+ }
+
+ RTCritSectLeave(&m->CritSectModCrypto);
+
+ return hrc;
+}
+
+
+/**
+ * Releases the reference of the given cryptographic interface.
+ *
+ * @returns COM status code.
+ * @param pCryptoIf Pointer to the cryptographic interface to release.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT VirtualBox::i_releaseCryptoIf(PCVBOXCRYPTOIF pCryptoIf)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AssertReturn(pCryptoIf == m->pCryptoIf, E_INVALIDARG);
+
+ ASMAtomicDecU32(&m->cRefsCrypto);
+ return S_OK;
+}
+
+
+/**
+ * Tries to unload any loaded cryptographic support module if it is not in use currently.
+ *
+ * @returns COM status code.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT VirtualBox::i_unloadCryptoIfModule(void)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.rc());
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->cRefsCrypto)
+ return setError(E_ACCESSDENIED,
+ tr("The cryptographic support module is in use and can't be unloaded"));
+
+ RTCritSectEnter(&m->CritSectModCrypto);
+ if (m->hLdrModCrypto != NIL_RTLDRMOD)
+ {
+ int vrc = RTLdrClose(m->hLdrModCrypto);
+ AssertRC(vrc);
+ m->hLdrModCrypto = NIL_RTLDRMOD;
+ }
+ RTCritSectLeave(&m->CritSectModCrypto);
+
+ return S_OK;
+}
+
+
+#ifdef RT_OS_WINDOWS
+#include <psapi.h>
+
+/**
+ * Report versions of installed drivers to release log.
+ */
+void VirtualBox::i_reportDriverVersions()
+{
+ /** @todo r=klaus this code is very confusing, as it uses TCHAR (and
+ * randomly also _TCHAR, which sounds to me like asking for trouble),
+ * the "sz" variable prefix but "%ls" for the format string - so the whole
+ * thing is better compiled with UNICODE and _UNICODE defined. Would be
+ * far easier to read if it would be coded explicitly for the unicode
+ * case, as it won't work otherwise. */
+ DWORD err;
+ HRESULT hrc;
+ LPVOID aDrivers[1024];
+ LPVOID *pDrivers = aDrivers;
+ UINT cNeeded = 0;
+ TCHAR szSystemRoot[MAX_PATH];
+ TCHAR *pszSystemRoot = szSystemRoot;
+ LPVOID pVerInfo = NULL;
+ DWORD cbVerInfo = 0;
+
+ do
+ {
+ cNeeded = GetWindowsDirectory(szSystemRoot, RT_ELEMENTS(szSystemRoot));
+ if (cNeeded == 0)
+ {
+ err = GetLastError();
+ hrc = HRESULT_FROM_WIN32(err);
+ AssertLogRelMsgFailed(("GetWindowsDirectory failed, hr=%Rhrc (0x%x) err=%u\n",
+ hrc, hrc, err));
+ break;
+ }
+ else if (cNeeded > RT_ELEMENTS(szSystemRoot))
+ {
+ /* The buffer is too small, allocate big one. */
+ pszSystemRoot = (TCHAR *)RTMemTmpAlloc(cNeeded * sizeof(_TCHAR));
+ if (!pszSystemRoot)
+ {
+ AssertLogRelMsgFailed(("RTMemTmpAlloc failed to allocate %d bytes\n", cNeeded));
+ break;
+ }
+ if (GetWindowsDirectory(pszSystemRoot, cNeeded) == 0)
+ {
+ err = GetLastError();
+ hrc = HRESULT_FROM_WIN32(err);
+ AssertLogRelMsgFailed(("GetWindowsDirectory failed, hr=%Rhrc (0x%x) err=%u\n",
+ hrc, hrc, err));
+ break;
+ }
+ }
+
+ DWORD cbNeeded = 0;
+ if (!EnumDeviceDrivers(aDrivers, sizeof(aDrivers), &cbNeeded) || cbNeeded > sizeof(aDrivers))
+ {
+ pDrivers = (LPVOID *)RTMemTmpAlloc(cbNeeded);
+ if (!EnumDeviceDrivers(pDrivers, cbNeeded, &cbNeeded))
+ {
+ err = GetLastError();
+ hrc = HRESULT_FROM_WIN32(err);
+ AssertLogRelMsgFailed(("EnumDeviceDrivers failed, hr=%Rhrc (0x%x) err=%u\n",
+ hrc, hrc, err));
+ break;
+ }
+ }
+
+ LogRel(("Installed Drivers:\n"));
+
+ TCHAR szDriver[1024];
+ int cDrivers = cbNeeded / sizeof(pDrivers[0]);
+ for (int i = 0; i < cDrivers; i++)
+ {
+ if (GetDeviceDriverBaseName(pDrivers[i], szDriver, sizeof(szDriver) / sizeof(szDriver[0])))
+ {
+ if (_tcsnicmp(TEXT("vbox"), szDriver, 4))
+ continue;
+ }
+ else
+ continue;
+ if (GetDeviceDriverFileName(pDrivers[i], szDriver, sizeof(szDriver) / sizeof(szDriver[0])))
+ {
+ _TCHAR szTmpDrv[1024];
+ _TCHAR *pszDrv = szDriver;
+ if (!_tcsncmp(TEXT("\\SystemRoot"), szDriver, 11))
+ {
+ _tcscpy_s(szTmpDrv, pszSystemRoot);
+ _tcsncat_s(szTmpDrv, szDriver + 11, sizeof(szTmpDrv) / sizeof(szTmpDrv[0]) - _tclen(pszSystemRoot));
+ pszDrv = szTmpDrv;
+ }
+ else if (!_tcsncmp(TEXT("\\??\\"), szDriver, 4))
+ pszDrv = szDriver + 4;
+
+ /* Allocate a buffer for version info. Reuse if large enough. */
+ DWORD cbNewVerInfo = GetFileVersionInfoSize(pszDrv, NULL);
+ if (cbNewVerInfo > cbVerInfo)
+ {
+ if (pVerInfo)
+ RTMemTmpFree(pVerInfo);
+ cbVerInfo = cbNewVerInfo;
+ pVerInfo = RTMemTmpAlloc(cbVerInfo);
+ if (!pVerInfo)
+ {
+ AssertLogRelMsgFailed(("RTMemTmpAlloc failed to allocate %d bytes\n", cbVerInfo));
+ break;
+ }
+ }
+
+ if (GetFileVersionInfo(pszDrv, NULL, cbVerInfo, pVerInfo))
+ {
+ UINT cbSize = 0;
+ LPBYTE lpBuffer = NULL;
+ if (VerQueryValue(pVerInfo, TEXT("\\"), (VOID FAR* FAR*)&lpBuffer, &cbSize))
+ {
+ if (cbSize)
+ {
+ VS_FIXEDFILEINFO *pFileInfo = (VS_FIXEDFILEINFO *)lpBuffer;
+ if (pFileInfo->dwSignature == 0xfeef04bd)
+ {
+ LogRel((" %ls (Version: %d.%d.%d.%d)\n", pszDrv,
+ (pFileInfo->dwFileVersionMS >> 16) & 0xffff,
+ (pFileInfo->dwFileVersionMS >> 0) & 0xffff,
+ (pFileInfo->dwFileVersionLS >> 16) & 0xffff,
+ (pFileInfo->dwFileVersionLS >> 0) & 0xffff));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+ while (0);
+
+ if (pVerInfo)
+ RTMemTmpFree(pVerInfo);
+
+ if (pDrivers != aDrivers)
+ RTMemTmpFree(pDrivers);
+
+ if (pszSystemRoot != szSystemRoot)
+ RTMemTmpFree(pszSystemRoot);
+}
+#else /* !RT_OS_WINDOWS */
+void VirtualBox::i_reportDriverVersions(void)
+{
+}
+#endif /* !RT_OS_WINDOWS */
+
+#if defined(RT_OS_WINDOWS) && defined(VBOXSVC_WITH_CLIENT_WATCHER)
+
+# include <psapi.h> /* for GetProcessImageFileNameW */
+
+/**
+ * Callout from the wrapper.
+ */
+void VirtualBox::i_callHook(const char *a_pszFunction)
+{
+ RT_NOREF(a_pszFunction);
+
+ /*
+ * Let'see figure out who is calling.
+ * Note! Requires Vista+, so skip this entirely on older systems.
+ */
+ if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0))
+ {
+ RPC_CALL_ATTRIBUTES_V2_W CallAttribs = { RPC_CALL_ATTRIBUTES_VERSION, RPC_QUERY_CLIENT_PID | RPC_QUERY_IS_CLIENT_LOCAL };
+ RPC_STATUS rcRpc = RpcServerInqCallAttributesW(NULL, &CallAttribs);
+ if ( rcRpc == RPC_S_OK
+ && CallAttribs.ClientPID != 0)
+ {
+ RTPROCESS const pidClient = (RTPROCESS)(uintptr_t)CallAttribs.ClientPID;
+ if (pidClient != RTProcSelf())
+ {
+ /** @todo LogRel2 later: */
+ LogRel(("i_callHook: %Rfn [ClientPID=%#zx/%zu IsClientLocal=%d ProtocolSequence=%#x CallStatus=%#x CallType=%#x OpNum=%#x InterfaceUuid=%RTuuid]\n",
+ a_pszFunction, CallAttribs.ClientPID, CallAttribs.ClientPID, CallAttribs.IsClientLocal,
+ CallAttribs.ProtocolSequence, CallAttribs.CallStatus, CallAttribs.CallType, CallAttribs.OpNum,
+ &CallAttribs.InterfaceUuid));
+
+ /*
+ * Do we know this client PID already?
+ */
+ RTCritSectRwEnterShared(&m->WatcherCritSect);
+ WatchedClientProcessMap::iterator It = m->WatchedProcesses.find(pidClient);
+ if (It != m->WatchedProcesses.end())
+ RTCritSectRwLeaveShared(&m->WatcherCritSect); /* Known process, nothing to do. */
+ else
+ {
+ /* This is a new client process, start watching it. */
+ RTCritSectRwLeaveShared(&m->WatcherCritSect);
+ i_watchClientProcess(pidClient, a_pszFunction);
+ }
+ }
+ }
+ else
+ LogRel(("i_callHook: %Rfn - rcRpc=%#x ClientPID=%#zx/%zu !! [IsClientLocal=%d ProtocolSequence=%#x CallStatus=%#x CallType=%#x OpNum=%#x InterfaceUuid=%RTuuid]\n",
+ a_pszFunction, rcRpc, CallAttribs.ClientPID, CallAttribs.ClientPID, CallAttribs.IsClientLocal,
+ CallAttribs.ProtocolSequence, CallAttribs.CallStatus, CallAttribs.CallType, CallAttribs.OpNum,
+ &CallAttribs.InterfaceUuid));
+ }
+}
+
+
+/**
+ * Watches @a a_pidClient for termination.
+ *
+ * @returns true if successfully enabled watching of it, false if not.
+ * @param a_pidClient The PID to watch.
+ * @param a_pszFunction The function we which we detected the client in.
+ */
+bool VirtualBox::i_watchClientProcess(RTPROCESS a_pidClient, const char *a_pszFunction)
+{
+ RT_NOREF_PV(a_pszFunction);
+
+ /*
+ * Open the client process.
+ */
+ HANDLE hClient = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, FALSE /*fInherit*/, a_pidClient);
+ if (hClient == NULL)
+ hClient = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE , a_pidClient);
+ if (hClient == NULL)
+ hClient = OpenProcess(SYNCHRONIZE, FALSE , a_pidClient);
+ AssertLogRelMsgReturn(hClient != NULL, ("pidClient=%d (%#x) err=%d\n", a_pidClient, a_pidClient, GetLastError()),
+ m->fWatcherIsReliable = false);
+
+ /*
+ * Create a new watcher structure and try add it to the map.
+ */
+ bool fRet = true;
+ WatchedClientProcess *pWatched = new (std::nothrow) WatchedClientProcess(a_pidClient, hClient);
+ if (pWatched)
+ {
+ RTCritSectRwEnterExcl(&m->WatcherCritSect);
+
+ WatchedClientProcessMap::iterator It = m->WatchedProcesses.find(a_pidClient);
+ if (It == m->WatchedProcesses.end())
+ {
+ try
+ {
+ m->WatchedProcesses.insert(WatchedClientProcessMap::value_type(a_pidClient, pWatched));
+ }
+ catch (std::bad_alloc &)
+ {
+ fRet = false;
+ }
+ if (fRet)
+ {
+ /*
+ * Schedule it on a watcher thread.
+ */
+ /** @todo later. */
+ RTCritSectRwLeaveExcl(&m->WatcherCritSect);
+ }
+ else
+ {
+ RTCritSectRwLeaveExcl(&m->WatcherCritSect);
+ delete pWatched;
+ LogRel(("VirtualBox::i_watchClientProcess: out of memory inserting into client map!\n"));
+ }
+ }
+ else
+ {
+ /*
+ * Someone raced us here, we lost.
+ */
+ RTCritSectRwLeaveExcl(&m->WatcherCritSect);
+ delete pWatched;
+ }
+ }
+ else
+ {
+ LogRel(("VirtualBox::i_watchClientProcess: out of memory!\n"));
+ CloseHandle(hClient);
+ m->fWatcherIsReliable = fRet = false;
+ }
+ return fRet;
+}
+
+
+/** Logs the RPC caller info to the release log. */
+/*static*/ void VirtualBox::i_logCaller(const char *a_pszFormat, ...)
+{
+ if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0))
+ {
+ char szTmp[80];
+ va_list va;
+ va_start(va, a_pszFormat);
+ RTStrPrintfV(szTmp, sizeof(szTmp), a_pszFormat, va);
+ va_end(va);
+
+ RPC_CALL_ATTRIBUTES_V2_W CallAttribs = { RPC_CALL_ATTRIBUTES_VERSION, RPC_QUERY_CLIENT_PID | RPC_QUERY_IS_CLIENT_LOCAL };
+ RPC_STATUS rcRpc = RpcServerInqCallAttributesW(NULL, &CallAttribs);
+
+ RTUTF16 wszProcName[256];
+ wszProcName[0] = '\0';
+ if (rcRpc == 0 && CallAttribs.ClientPID != 0)
+ {
+ HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, (DWORD)(uintptr_t)CallAttribs.ClientPID);
+ if (hProcess)
+ {
+ RT_ZERO(wszProcName);
+ GetProcessImageFileNameW(hProcess, wszProcName, RT_ELEMENTS(wszProcName) - 1);
+ CloseHandle(hProcess);
+ }
+ }
+ LogRel(("%s [rcRpc=%#x ClientPID=%#zx/%zu (%ls) IsClientLocal=%d ProtocolSequence=%#x CallStatus=%#x CallType=%#x OpNum=%#x InterfaceUuid=%RTuuid]\n",
+ szTmp, rcRpc, CallAttribs.ClientPID, CallAttribs.ClientPID, wszProcName, CallAttribs.IsClientLocal,
+ CallAttribs.ProtocolSequence, CallAttribs.CallStatus, CallAttribs.CallType, CallAttribs.OpNum,
+ &CallAttribs.InterfaceUuid));
+ }
+}
+
+#endif /* RT_OS_WINDOWS && VBOXSVC_WITH_CLIENT_WATCHER */
+
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-server/custom.ids b/src/VBox/Main/src-server/custom.ids
new file mode 100644
index 00000000..f3ec2df8
--- /dev/null
+++ b/src/VBox/Main/src-server/custom.ids
@@ -0,0 +1,10 @@
+# Vendors, devices and interfaces. Please keep sorted.
+
+# Syntax:
+# vendor vendor_name
+# device device_name <-- single tab
+# interface interface_name <-- two tabs
+
+#02 Custom vendor
+# 01 Custom product
+
diff --git a/src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp b/src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp
new file mode 100644
index 00000000..9ef12ce8
--- /dev/null
+++ b/src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp
@@ -0,0 +1,281 @@
+/* $Id: HostDnsServiceDarwin.cpp $ */
+/** @file
+ * Darwin specific DNS information fetching.
+ */
+
+/*
+ * Copyright (C) 2004-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/com/string.h>
+#include <VBox/com/ptr.h>
+
+
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <SystemConfiguration/SCDynamicStore.h>
+
+#include <iprt/sanitized/string>
+#include <vector>
+#include "../HostDnsService.h"
+
+
+struct HostDnsServiceDarwin::Data
+{
+ Data()
+ : m_fStop(false) { }
+
+ SCDynamicStoreRef m_store;
+ CFRunLoopSourceRef m_DnsWatcher;
+ CFRunLoopRef m_RunLoopRef;
+ CFRunLoopSourceRef m_SourceStop;
+ volatile bool m_fStop;
+ RTSEMEVENT m_evtStop;
+ static void performShutdownCallback(void *);
+};
+
+
+static const CFStringRef kStateNetworkGlobalDNSKey = CFSTR("State:/Network/Global/DNS");
+
+
+HostDnsServiceDarwin::HostDnsServiceDarwin()
+ : HostDnsServiceBase(true /* fThreaded */)
+ , m(NULL)
+{
+ m = new HostDnsServiceDarwin::Data();
+}
+
+HostDnsServiceDarwin::~HostDnsServiceDarwin()
+{
+ if (m != NULL)
+ delete m;
+}
+
+HRESULT HostDnsServiceDarwin::init(HostDnsMonitorProxy *pProxy)
+{
+ SCDynamicStoreContext ctx;
+ RT_ZERO(ctx);
+
+ ctx.info = this;
+
+ m->m_store = SCDynamicStoreCreate(NULL, CFSTR("org.virtualbox.VBoxSVC.HostDNS"),
+ (SCDynamicStoreCallBack)HostDnsServiceDarwin::hostDnsServiceStoreCallback,
+ &ctx);
+ AssertReturn(m->m_store, E_FAIL);
+
+ m->m_DnsWatcher = SCDynamicStoreCreateRunLoopSource(NULL, m->m_store, 0);
+ if (!m->m_DnsWatcher)
+ return E_OUTOFMEMORY;
+
+ int rc = RTSemEventCreate(&m->m_evtStop);
+ AssertRCReturn(rc, E_FAIL);
+
+ CFRunLoopSourceContext sctx;
+ RT_ZERO(sctx);
+ sctx.info = this;
+ sctx.perform = HostDnsServiceDarwin::Data::performShutdownCallback;
+
+ m->m_SourceStop = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &sctx);
+ AssertReturn(m->m_SourceStop, E_FAIL);
+
+ HRESULT hrc = HostDnsServiceBase::init(pProxy);
+ return hrc;
+}
+
+void HostDnsServiceDarwin::uninit(void)
+{
+ HostDnsServiceBase::uninit();
+
+ CFRelease(m->m_SourceStop);
+ CFRelease(m->m_RunLoopRef);
+ CFRelease(m->m_DnsWatcher);
+ CFRelease(m->m_store);
+
+ RTSemEventDestroy(m->m_evtStop);
+}
+
+int HostDnsServiceDarwin::monitorThreadShutdown(RTMSINTERVAL uTimeoutMs)
+{
+ RTCLock grab(m_LockMtx);
+ if (!m->m_fStop)
+ {
+ ASMAtomicXchgBool(&m->m_fStop, true);
+ CFRunLoopSourceSignal(m->m_SourceStop);
+ CFRunLoopStop(m->m_RunLoopRef);
+
+ RTSemEventWait(m->m_evtStop, uTimeoutMs);
+ }
+
+ return VINF_SUCCESS;
+}
+
+int HostDnsServiceDarwin::monitorThreadProc(void)
+{
+ m->m_RunLoopRef = CFRunLoopGetCurrent();
+ AssertReturn(m->m_RunLoopRef, VERR_INTERNAL_ERROR);
+
+ CFRetain(m->m_RunLoopRef);
+
+ CFRunLoopAddSource(m->m_RunLoopRef, m->m_SourceStop, kCFRunLoopCommonModes);
+
+ CFArrayRef watchingArrayRef = CFArrayCreate(NULL,
+ (const void **)&kStateNetworkGlobalDNSKey,
+ 1, &kCFTypeArrayCallBacks);
+ if (!watchingArrayRef)
+ {
+ CFRelease(m->m_DnsWatcher);
+ return VERR_NO_MEMORY;
+ }
+
+ if (SCDynamicStoreSetNotificationKeys(m->m_store, watchingArrayRef, NULL))
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), m->m_DnsWatcher, kCFRunLoopCommonModes);
+
+ CFRelease(watchingArrayRef);
+
+ onMonitorThreadInitDone();
+
+ /* Trigger initial update. */
+ int rc = updateInfo();
+ AssertRC(rc); /* Not fatal in release builds. */ /** @todo r=bird: The function always returns VINF_SUCCESS. */
+
+ while (!ASMAtomicReadBool(&m->m_fStop))
+ {
+ CFRunLoopRun();
+ }
+
+ CFRunLoopRemoveSource(m->m_RunLoopRef, m->m_SourceStop, kCFRunLoopCommonModes);
+
+ /* We're notifying stopper thread. */
+ RTSemEventSignal(m->m_evtStop);
+
+ return VINF_SUCCESS;
+}
+
+int HostDnsServiceDarwin::updateInfo(void)
+{
+ CFPropertyListRef propertyRef = SCDynamicStoreCopyValue(m->m_store, kStateNetworkGlobalDNSKey);
+ /**
+ * # scutil
+ * \> get State:/Network/Global/DNS
+ * \> d.show
+ * \<dictionary\> {
+ * DomainName : vvl-domain
+ * SearchDomains : \<array\> {
+ * 0 : vvl-domain
+ * 1 : de.vvl-domain.com
+ * }
+ * ServerAddresses : \<array\> {
+ * 0 : 192.168.1.4
+ * 1 : 192.168.1.1
+ * 2 : 8.8.4.4
+ * }
+ * }
+ */
+
+ if (!propertyRef)
+ return VINF_SUCCESS;
+
+ HostDnsInformation info;
+ CFStringRef domainNameRef = (CFStringRef)CFDictionaryGetValue(static_cast<CFDictionaryRef>(propertyRef), CFSTR("DomainName"));
+ if (domainNameRef)
+ {
+ const char *pszDomainName = CFStringGetCStringPtr(domainNameRef, CFStringGetSystemEncoding());
+ if (pszDomainName)
+ info.domain = pszDomainName;
+ }
+
+ CFArrayRef serverArrayRef = (CFArrayRef)CFDictionaryGetValue(static_cast<CFDictionaryRef>(propertyRef),
+ CFSTR("ServerAddresses"));
+ if (serverArrayRef)
+ {
+ CFIndex const cItems = CFArrayGetCount(serverArrayRef);
+ for (CFIndex i = 0; i < cItems; ++i)
+ {
+ CFStringRef serverAddressRef = (CFStringRef)CFArrayGetValueAtIndex(serverArrayRef, i);
+ if (!serverArrayRef)
+ continue;
+
+ /** @todo r=bird: This code is messed up as CFStringGetCStringPtr is documented
+ * to return NULL even if the string is valid. Furthermore, we must have
+ * UTF-8 - some joker might decide latin-1 is better here for all we know
+ * and we'll end up with evil invalid UTF-8 sequences. */
+ const char *pszServerAddress = CFStringGetCStringPtr(serverAddressRef, CFStringGetSystemEncoding());
+ if (!pszServerAddress)
+ continue;
+
+ /** @todo r=bird: Why on earth are we using std::string and not Utf8Str? */
+ info.servers.push_back(std::string(pszServerAddress));
+ }
+ }
+
+ CFArrayRef searchArrayRef = (CFArrayRef)CFDictionaryGetValue(static_cast<CFDictionaryRef>(propertyRef),
+ CFSTR("SearchDomains"));
+ if (searchArrayRef)
+ {
+ CFIndex const cItems = CFArrayGetCount(searchArrayRef);
+ for (CFIndex i = 0; i < cItems; ++i)
+ {
+ CFStringRef searchStringRef = (CFStringRef)CFArrayGetValueAtIndex(searchArrayRef, i);
+ if (!searchArrayRef)
+ continue;
+
+ /** @todo r=bird: This code is messed up as CFStringGetCStringPtr is documented
+ * to return NULL even if the string is valid. Furthermore, we must have
+ * UTF-8 - some joker might decide latin-1 is better here for all we know
+ * and we'll end up with evil invalid UTF-8 sequences. */
+ const char *pszSearchString = CFStringGetCStringPtr(searchStringRef, CFStringGetSystemEncoding());
+ if (!pszSearchString)
+ continue;
+
+ /** @todo r=bird: Why on earth are we using std::string and not Utf8Str? */
+ info.searchList.push_back(std::string(pszSearchString));
+ }
+ }
+
+ CFRelease(propertyRef);
+
+ setInfo(info);
+
+ return VINF_SUCCESS;
+}
+
+void HostDnsServiceDarwin::hostDnsServiceStoreCallback(void *, void *, void *pInfo)
+{
+ HostDnsServiceDarwin *pThis = (HostDnsServiceDarwin *)pInfo;
+ AssertPtrReturnVoid(pThis);
+
+ RTCLock grab(pThis->m_LockMtx);
+ pThis->updateInfo();
+}
+
+void HostDnsServiceDarwin::Data::performShutdownCallback(void *pInfo)
+{
+ HostDnsServiceDarwin *pThis = (HostDnsServiceDarwin *)pInfo;
+ AssertPtrReturnVoid(pThis);
+
+ AssertPtrReturnVoid(pThis->m);
+ ASMAtomicXchgBool(&pThis->m->m_fStop, true);
+}
+
diff --git a/src/VBox/Main/src-server/darwin/HostPowerDarwin.cpp b/src/VBox/Main/src-server/darwin/HostPowerDarwin.cpp
new file mode 100644
index 00000000..e1e8faa1
--- /dev/null
+++ b/src/VBox/Main/src-server/darwin/HostPowerDarwin.cpp
@@ -0,0 +1,254 @@
+/* $Id: HostPowerDarwin.cpp $ */
+/** @file
+ * VirtualBox interface to host's power notification service, darwin specifics.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOST
+#include "HostPower.h"
+#include "LoggingNew.h"
+#include <iprt/errcore.h>
+
+#include <IOKit/IOMessage.h>
+#include <IOKit/ps/IOPowerSources.h>
+#include <IOKit/ps/IOPSKeys.h>
+
+#define POWER_SOURCE_OUTLET 1
+#define POWER_SOURCE_BATTERY 2
+
+HostPowerServiceDarwin::HostPowerServiceDarwin(VirtualBox *aVirtualBox)
+ : HostPowerService(aVirtualBox)
+ , mThread(NULL)
+ , mRootPort(MACH_PORT_NULL)
+ , mNotifyPort(nil)
+ , mRunLoop(nil)
+ , mCritical(false)
+{
+ /* Create the new worker thread. */
+ int rc = RTThreadCreate(&mThread, HostPowerServiceDarwin::powerChangeNotificationThread, this, 65536,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "MainPower");
+
+ if (RT_FAILURE(rc))
+ LogFlow(("RTThreadCreate failed with %Rrc\n", rc));
+}
+
+HostPowerServiceDarwin::~HostPowerServiceDarwin()
+{
+ /* Jump out of the run loop. */
+ CFRunLoopStop(mRunLoop);
+ /* Remove the sleep notification port from the application runloop. */
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(),
+ IONotificationPortGetRunLoopSource(mNotifyPort),
+ kCFRunLoopCommonModes);
+ /* Deregister for system sleep notifications. */
+ IODeregisterForSystemPower(&mNotifierObject);
+ /* IORegisterForSystemPower implicitly opens the Root Power Domain
+ * IOService so we close it here. */
+ IOServiceClose(mRootPort);
+ /* Destroy the notification port allocated by IORegisterForSystemPower */
+ IONotificationPortDestroy(mNotifyPort);
+}
+
+
+DECLCALLBACK(int) HostPowerServiceDarwin::powerChangeNotificationThread(RTTHREAD /* ThreadSelf */, void *pInstance)
+{
+ HostPowerServiceDarwin *pPowerObj = static_cast<HostPowerServiceDarwin *>(pInstance);
+
+ /* We have to initial set the critical state of the battery, cause we want
+ * not the HostPowerService to inform about that state when a VM starts.
+ * See lowPowerHandler for more info. */
+ pPowerObj->checkBatteryCriticalLevel();
+
+ /* Register to receive system sleep notifications */
+ pPowerObj->mRootPort = IORegisterForSystemPower(pPowerObj, &pPowerObj->mNotifyPort,
+ HostPowerServiceDarwin::powerChangeNotificationHandler,
+ &pPowerObj->mNotifierObject);
+ if (pPowerObj->mRootPort == MACH_PORT_NULL)
+ {
+ LogFlow(("IORegisterForSystemPower failed\n"));
+ return VERR_NOT_SUPPORTED;
+ }
+ pPowerObj->mRunLoop = CFRunLoopGetCurrent();
+ /* Add the notification port to the application runloop */
+ CFRunLoopAddSource(pPowerObj->mRunLoop,
+ IONotificationPortGetRunLoopSource(pPowerObj->mNotifyPort),
+ kCFRunLoopCommonModes);
+
+ /* Register for all battery change events. The handler will check for low
+ * power events itself. */
+ CFRunLoopSourceRef runLoopSource = IOPSNotificationCreateRunLoopSource(HostPowerServiceDarwin::lowPowerHandler,
+ pPowerObj);
+ CFRunLoopAddSource(pPowerObj->mRunLoop,
+ runLoopSource,
+ kCFRunLoopCommonModes);
+
+ /* Start the run loop. This blocks. */
+ CFRunLoopRun();
+ return VINF_SUCCESS;
+}
+
+void HostPowerServiceDarwin::powerChangeNotificationHandler(void *pvData, io_service_t /* service */, natural_t messageType, void *pMessageArgument)
+{
+ HostPowerServiceDarwin *pPowerObj = static_cast<HostPowerServiceDarwin *>(pvData);
+ Log(( "powerChangeNotificationHandler: messageType %08lx, arg %08lx\n", (long unsigned int)messageType, (long unsigned int)pMessageArgument));
+
+ switch (messageType)
+ {
+ case kIOMessageCanSystemSleep:
+ {
+ /* Idle sleep is about to kick in. This message will not be
+ * sent for forced sleep. Applications have a chance to prevent
+ * sleep by calling IOCancelPowerChange. Most applications
+ * should not prevent idle sleep. Power Management waits up to
+ * 30 seconds for you to either allow or deny idle sleep. If
+ * you don't acknowledge this power change by calling either
+ * IOAllowPowerChange or IOCancelPowerChange, the system will
+ * wait 30 seconds then go to sleep. */
+ IOAllowPowerChange(pPowerObj->mRootPort, reinterpret_cast<long>(pMessageArgument));
+ break;
+ }
+ case kIOMessageSystemWillSleep:
+ {
+ /* The system will go for sleep. */
+ pPowerObj->notify(Reason_HostSuspend);
+ /* If you do not call IOAllowPowerChange or IOCancelPowerChange to
+ * acknowledge this message, sleep will be delayed by 30 seconds.
+ * NOTE: If you call IOCancelPowerChange to deny sleep it returns
+ * kIOReturnSuccess, however the system WILL still go to sleep. */
+ IOAllowPowerChange(pPowerObj->mRootPort, reinterpret_cast<long>(pMessageArgument));
+ break;
+ }
+ case kIOMessageSystemWillPowerOn:
+ {
+ /* System has started the wake up process. */
+ break;
+ }
+ case kIOMessageSystemHasPoweredOn:
+ {
+ /* System has finished the wake up process. */
+ pPowerObj->notify(Reason_HostResume);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void HostPowerServiceDarwin::lowPowerHandler(void *pvData)
+{
+ HostPowerServiceDarwin *pPowerObj = static_cast<HostPowerServiceDarwin *>(pvData);
+
+ /* Following role for sending the BatteryLow event(5% is critical):
+ * - Not at VM start even if the battery is in an critical state already.
+ * - When the power cord is removed so the power supply change from AC to
+ * battery & the battery is in an critical state nothing is triggered.
+ * This has to be discussed.
+ * - When the power supply is the battery & the state of the battery level
+ * changed from normal to critical. The state transition from critical to
+ * normal triggers nothing. */
+ bool fCriticalStateChanged = false;
+ pPowerObj->checkBatteryCriticalLevel(&fCriticalStateChanged);
+ if (fCriticalStateChanged)
+ pPowerObj->notify(Reason_HostBatteryLow);
+}
+
+void HostPowerServiceDarwin::checkBatteryCriticalLevel(bool *pfCriticalChanged)
+{
+ CFTypeRef pBlob = IOPSCopyPowerSourcesInfo();
+ CFArrayRef pSources = IOPSCopyPowerSourcesList(pBlob);
+
+ CFDictionaryRef pSource = NULL;
+ const void *psValue;
+ bool result;
+ int powerSource = POWER_SOURCE_OUTLET;
+ bool critical = false;
+
+ if (CFArrayGetCount(pSources) > 0)
+ {
+ for (int i = 0; i < CFArrayGetCount(pSources); ++i)
+ {
+ pSource = IOPSGetPowerSourceDescription(pBlob, CFArrayGetValueAtIndex(pSources, i));
+ /* If the source is empty skip over to the next one. */
+ if (!pSource)
+ continue;
+ /* Skip all power sources which are currently not present like a
+ * second battery. */
+ if (CFDictionaryGetValue(pSource, CFSTR(kIOPSIsPresentKey)) == kCFBooleanFalse)
+ continue;
+ /* Only internal power types are of interest. */
+ result = CFDictionaryGetValueIfPresent(pSource, CFSTR(kIOPSTransportTypeKey), &psValue);
+ if (result &&
+ CFStringCompare((CFStringRef)psValue, CFSTR(kIOPSInternalType), 0) == kCFCompareEqualTo)
+ {
+ /* First check which power source we are connect on. */
+ result = CFDictionaryGetValueIfPresent(pSource, CFSTR(kIOPSPowerSourceStateKey), &psValue);
+ if (result &&
+ CFStringCompare((CFStringRef)psValue, CFSTR(kIOPSACPowerValue), 0) == kCFCompareEqualTo)
+ powerSource = POWER_SOURCE_OUTLET;
+ else if (result &&
+ CFStringCompare((CFStringRef)psValue, CFSTR(kIOPSBatteryPowerValue), 0) == kCFCompareEqualTo)
+ powerSource = POWER_SOURCE_BATTERY;
+
+
+ /* Fetch the current capacity value of the power source */
+ int curCapacity = 0;
+ result = CFDictionaryGetValueIfPresent(pSource, CFSTR(kIOPSCurrentCapacityKey), &psValue);
+ if (result)
+ CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &curCapacity);
+
+ /* Fetch the maximum capacity value of the power source */
+ int maxCapacity = 1;
+ result = CFDictionaryGetValueIfPresent(pSource, CFSTR(kIOPSMaxCapacityKey), &psValue);
+ if (result)
+ CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &maxCapacity);
+
+ /* Calculate the remaining capacity in percent */
+ float remCapacity = ((float)curCapacity/(float)maxCapacity * 100.0f);
+
+ /* Check for critical. 5 percent is default. */
+ int criticalValue = 5;
+ result = CFDictionaryGetValueIfPresent(pSource, CFSTR(kIOPSDeadWarnLevelKey), &psValue);
+ if (result)
+ CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &criticalValue);
+ critical = remCapacity < criticalValue;
+
+ /* We have to take action only if we are on battery, the
+ * previous state wasn't critical, the state has changed & the
+ * user requested that info. */
+ if (powerSource == POWER_SOURCE_BATTERY &&
+ mCritical == false &&
+ mCritical != critical &&
+ pfCriticalChanged)
+ *pfCriticalChanged = true;
+ Log(("checkBatteryCriticalLevel: Remains: %d.%d%% Critical: %d Critical State Changed: %d\n", (int)remCapacity, (int)(remCapacity * 10) % 10, critical, pfCriticalChanged?*pfCriticalChanged:-1));
+ }
+ }
+ }
+ /* Save the new state */
+ mCritical = critical;
+
+ CFRelease(pBlob);
+ CFRelease(pSources);
+}
+
diff --git a/src/VBox/Main/src-server/darwin/Makefile.kup b/src/VBox/Main/src-server/darwin/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-server/darwin/Makefile.kup
diff --git a/src/VBox/Main/src-server/darwin/NetIf-darwin.cpp b/src/VBox/Main/src-server/darwin/NetIf-darwin.cpp
new file mode 100644
index 00000000..a714069f
--- /dev/null
+++ b/src/VBox/Main/src-server/darwin/NetIf-darwin.cpp
@@ -0,0 +1,558 @@
+/* $Id: NetIf-darwin.cpp $ */
+/** @file
+ * Main - NetIfList, Darwin implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_HOST
+
+/*
+ * Deal with conflicts first.
+ * PVM - BSD mess, that FreeBSD has correct a long time ago.
+ * iprt/types.h before sys/param.h - prevents UINT32_C and friends.
+ */
+#include <iprt/types.h>
+#include <sys/param.h>
+#undef PVM
+
+#include <iprt/errcore.h>
+#include <iprt/alloc.h>
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+#include <ifaddrs.h>
+#include <errno.h>
+#include <unistd.h>
+#include <list>
+
+#include "HostNetworkInterfaceImpl.h"
+#include "netif.h"
+#include "iokit.h"
+#include "LoggingNew.h"
+
+#if 0
+int NetIfList(std::list <ComObjPtr<HostNetworkInterface> > &list)
+{
+ int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sock < 0)
+ {
+ Log(("NetIfList: socket() -> %d\n", errno));
+ return NULL;
+ }
+ struct ifaddrs *IfAddrs, *pAddr;
+ int rc = getifaddrs(&IfAddrs);
+ if (rc)
+ {
+ close(sock);
+ Log(("NetIfList: getifaddrs() -> %d\n", rc));
+ return VERR_INTERNAL_ERROR;
+ }
+
+ PDARWINETHERNIC pEtherNICs = DarwinGetEthernetControllers();
+ while (pEtherNICs)
+ {
+ size_t cbNameLen = strlen(pEtherNICs->szName) + 1;
+ PNETIFINFO pNew = (PNETIFINFO)RTMemAllocZ(RT_OFFSETOF(NETIFINFO, szName[cbNameLen]));
+ pNew->MACAddress = pEtherNICs->Mac;
+ pNew->enmMediumType = NETIF_T_ETHERNET;
+ pNew->Uuid = pEtherNICs->Uuid;
+ Assert(sizeof(pNew->szShortName) > sizeof(pEtherNICs->szBSDName));
+ memcpy(pNew->szShortName, pEtherNICs->szBSDName, sizeof(pEtherNICs->szBSDName));
+ pNew->szShortName[sizeof(pEtherNICs->szBSDName)] = '\0';
+ memcpy(pNew->szName, pEtherNICs->szName, cbNameLen);
+
+ struct ifreq IfReq;
+ RTStrCopy(IfReq.ifr_name, sizeof(IfReq.ifr_name), pNew->szShortName);
+ if (ioctl(sock, SIOCGIFFLAGS, &IfReq) < 0)
+ {
+ Log(("NetIfList: ioctl(SIOCGIFFLAGS) -> %d\n", errno));
+ pNew->enmStatus = NETIF_S_UNKNOWN;
+ }
+ else
+ pNew->enmStatus = (IfReq.ifr_flags & IFF_UP) ? NETIF_S_UP : NETIF_S_DOWN;
+
+ for (pAddr = IfAddrs; pAddr != NULL; pAddr = pAddr->ifa_next)
+ {
+ if (strcmp(pNew->szShortName, pAddr->ifa_name))
+ continue;
+
+ struct sockaddr_in *pIPAddr, *pIPNetMask;
+ struct sockaddr_in6 *pIPv6Addr, *pIPv6NetMask;
+
+ switch (pAddr->ifa_addr->sa_family)
+ {
+ case AF_INET:
+ if (pNew->IPAddress.u)
+ break;
+ pIPAddr = (struct sockaddr_in *)pAddr->ifa_addr;
+ Assert(sizeof(pNew->IPAddress) == sizeof(pIPAddr->sin_addr));
+ pNew->IPAddress.u = pIPAddr->sin_addr.s_addr;
+ pIPNetMask = (struct sockaddr_in *)pAddr->ifa_netmask;
+ Assert(pIPNetMask->sin_family == AF_INET);
+ Assert(sizeof(pNew->IPNetMask) == sizeof(pIPNetMask->sin_addr));
+ pNew->IPNetMask.u = pIPNetMask->sin_addr.s_addr;
+ break;
+ case AF_INET6:
+ if (pNew->IPv6Address.s.Lo || pNew->IPv6Address.s.Hi)
+ break;
+ pIPv6Addr = (struct sockaddr_in6 *)pAddr->ifa_addr;
+ Assert(sizeof(pNew->IPv6Address) == sizeof(pIPv6Addr->sin6_addr));
+ memcpy(pNew->IPv6Address.au8,
+ pIPv6Addr->sin6_addr.__u6_addr.__u6_addr8,
+ sizeof(pNew->IPv6Address));
+ pIPv6NetMask = (struct sockaddr_in6 *)pAddr->ifa_netmask;
+ Assert(pIPv6NetMask->sin6_family == AF_INET6);
+ Assert(sizeof(pNew->IPv6NetMask) == sizeof(pIPv6NetMask->sin6_addr));
+ memcpy(pNew->IPv6NetMask.au8,
+ pIPv6NetMask->sin6_addr.__u6_addr.__u6_addr8,
+ sizeof(pNew->IPv6NetMask));
+ break;
+ }
+ }
+
+ ComObjPtr<HostNetworkInterface> IfObj;
+ IfObj.createObject();
+ if (SUCCEEDED(IfObj->init(Bstr(pEtherNICs->szName), HostNetworkInterfaceType_Bridged, pNew)))
+ list.push_back(IfObj);
+ RTMemFree(pNew);
+
+ /* next, free current */
+ void *pvFree = pEtherNICs;
+ pEtherNICs = pEtherNICs->pNext;
+ RTMemFree(pvFree);
+ }
+
+ freeifaddrs(IfAddrs);
+ close(sock);
+ return VINF_SUCCESS;
+}
+#else
+
+#define ROUNDUP(a) \
+ (((a) & (sizeof(u_long) - 1)) ? (1 + ((a) | (sizeof(u_long) - 1))) : (a))
+#define ADVANCE(x, n) (x += (n)->sa_len ? ROUNDUP((n)->sa_len) : sizeof(u_long))
+
+void extractAddresses(int iAddrMask, caddr_t cp, caddr_t cplim, struct sockaddr **pAddresses)
+{
+ struct sockaddr *sa;
+
+ for (int i = 0; i < RTAX_MAX && cp < cplim; i++) {
+ if (iAddrMask & (1 << i))
+ {
+ sa = (struct sockaddr *)cp;
+
+ pAddresses[i] = sa;
+
+ ADVANCE(cp, sa);
+ }
+ else
+ pAddresses[i] = NULL;
+ }
+}
+
+void extractAddressesToNetInfo(int iAddrMask, caddr_t cp, caddr_t cplim, PNETIFINFO pInfo)
+{
+ struct sockaddr *addresses[RTAX_MAX];
+
+ extractAddresses(iAddrMask, cp, cplim, addresses);
+ switch (addresses[RTAX_IFA]->sa_family)
+ {
+ case AF_INET:
+ if (!pInfo->IPAddress.u)
+ {
+ pInfo->IPAddress.u = ((struct sockaddr_in *)addresses[RTAX_IFA])->sin_addr.s_addr;
+ pInfo->IPNetMask.u = ((struct sockaddr_in *)addresses[RTAX_NETMASK])->sin_addr.s_addr;
+ }
+ break;
+ case AF_INET6:
+ if (!pInfo->IPv6Address.s.Lo && !pInfo->IPv6Address.s.Hi)
+ {
+ memcpy(pInfo->IPv6Address.au8,
+ ((struct sockaddr_in6 *)addresses[RTAX_IFA])->sin6_addr.__u6_addr.__u6_addr8,
+ sizeof(pInfo->IPv6Address));
+ memcpy(pInfo->IPv6NetMask.au8,
+ ((struct sockaddr_in6 *)addresses[RTAX_NETMASK])->sin6_addr.__u6_addr.__u6_addr8,
+ sizeof(pInfo->IPv6NetMask));
+ }
+ break;
+ default:
+ Log(("NetIfList: Unsupported address family: %u\n", addresses[RTAX_IFA]->sa_family));
+ break;
+ }
+}
+
+static int getDefaultIfaceIndex(unsigned short *pu16Index)
+{
+ size_t cbNeeded;
+ char *pBuf, *pNext;
+ int aiMib[6];
+ struct sockaddr *addresses[RTAX_MAX];
+
+ aiMib[0] = CTL_NET;
+ aiMib[1] = PF_ROUTE;
+ aiMib[2] = 0;
+ aiMib[3] = PF_INET; /* address family */
+ aiMib[4] = NET_RT_DUMP;
+ aiMib[5] = 0;
+
+ if (sysctl(aiMib, 6, NULL, &cbNeeded, NULL, 0) < 0)
+ {
+ Log(("getDefaultIfaceIndex: Failed to get estimate for list size (errno=%d).\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+ if ((pBuf = (char *)RTMemAlloc(cbNeeded)) == NULL)
+ return VERR_NO_MEMORY;
+ if (sysctl(aiMib, 6, pBuf, &cbNeeded, NULL, 0) < 0)
+ {
+ RTMemFree(pBuf);
+ Log(("getDefaultIfaceIndex: Failed to retrieve interface table (errno=%d).\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+
+ char *pEnd = pBuf + cbNeeded;
+ struct rt_msghdr *pRtMsg;
+ for (pNext = pBuf; pNext < pEnd; pNext += pRtMsg->rtm_msglen)
+ {
+ pRtMsg = (struct rt_msghdr *)pNext;
+
+ if (pRtMsg->rtm_type != RTM_GET)
+ {
+ Log(("getDefaultIfaceIndex: Got message %u while expecting %u.\n",
+ pRtMsg->rtm_type, RTM_GET));
+ //rc = VERR_INTERNAL_ERROR;
+ continue;
+ }
+ if ((char*)(pRtMsg + 1) < pEnd)
+ {
+ /* Extract addresses from the message. */
+ extractAddresses(pRtMsg->rtm_addrs, (char *)(pRtMsg + 1),
+ pRtMsg->rtm_msglen + 1 + (char *)pRtMsg, addresses);
+ if ((pRtMsg->rtm_addrs & RTA_DST)
+ && (pRtMsg->rtm_addrs & RTA_NETMASK))
+ {
+ if (addresses[RTAX_DST]->sa_family != AF_INET)
+ continue;
+ struct sockaddr_in *addr = (struct sockaddr_in *)addresses[RTAX_DST];
+ struct sockaddr_in *mask = (struct sockaddr_in *)addresses[RTAX_NETMASK];
+ if ((addr->sin_addr.s_addr == INADDR_ANY) &&
+ mask &&
+ (ntohl(mask->sin_addr.s_addr) == 0L ||
+ mask->sin_len == 0))
+ {
+ *pu16Index = pRtMsg->rtm_index;
+ RTMemFree(pBuf);
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ RTMemFree(pBuf);
+ return 0; /* Failed to find default interface, take the first one in the list. */
+}
+
+int NetIfList(std::list <ComObjPtr<HostNetworkInterface> > &list)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbNeeded;
+ char *pBuf, *pNext;
+ int aiMib[6];
+ unsigned short u16DefaultIface = 0; /* initialized to shut up gcc */
+
+ /* Get the index of the interface associated with default route. */
+ rc = getDefaultIfaceIndex(&u16DefaultIface);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ aiMib[0] = CTL_NET;
+ aiMib[1] = PF_ROUTE;
+ aiMib[2] = 0;
+ aiMib[3] = 0; /* address family */
+ aiMib[4] = NET_RT_IFLIST;
+ aiMib[5] = 0;
+
+ if (sysctl(aiMib, 6, NULL, &cbNeeded, NULL, 0) < 0)
+ {
+ Log(("NetIfList: Failed to get estimate for list size (errno=%d).\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+ if ((pBuf = (char*)RTMemAlloc(cbNeeded)) == NULL)
+ return VERR_NO_MEMORY;
+ if (sysctl(aiMib, 6, pBuf, &cbNeeded, NULL, 0) < 0)
+ {
+ RTMemFree(pBuf);
+ Log(("NetIfList: Failed to retrieve interface table (errno=%d).\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+
+ int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sock < 0)
+ {
+ RTMemFree(pBuf);
+ Log(("NetIfList: socket() -> %d\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+
+ PDARWINETHERNIC pNIC;
+ PDARWINETHERNIC pEtherNICs = DarwinGetEthernetControllers();
+
+ char *pEnd = pBuf + cbNeeded;
+ for (pNext = pBuf; pNext < pEnd;)
+ {
+ struct if_msghdr *pIfMsg = (struct if_msghdr *)pNext;
+
+ if (pIfMsg->ifm_type != RTM_IFINFO)
+ {
+ Log(("NetIfList: Got message %u while expecting %u.\n",
+ pIfMsg->ifm_type, RTM_IFINFO));
+ rc = VERR_INTERNAL_ERROR;
+ break;
+ }
+ struct sockaddr_dl *pSdl = (struct sockaddr_dl *)(pIfMsg + 1);
+
+ size_t cbNameLen = pSdl->sdl_nlen + 1;
+ Assert(pSdl->sdl_nlen < sizeof(pNIC->szBSDName));
+ for (pNIC = pEtherNICs; pNIC; pNIC = pNIC->pNext)
+ if ( !strncmp(pSdl->sdl_data, pNIC->szBSDName, pSdl->sdl_nlen)
+ && pNIC->szBSDName[pSdl->sdl_nlen] == '\0')
+ {
+ cbNameLen = strlen(pNIC->szName) + 1;
+ break;
+ }
+ PNETIFINFO pNew = (PNETIFINFO)RTMemAllocZ(RT_UOFFSETOF_DYN(NETIFINFO, szName[cbNameLen]));
+ if (!pNew)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ memcpy(pNew->MACAddress.au8, LLADDR(pSdl), sizeof(pNew->MACAddress.au8));
+ pNew->enmMediumType = NETIF_T_ETHERNET;
+ Assert(sizeof(pNew->szShortName) > pSdl->sdl_nlen);
+ memcpy(pNew->szShortName, pSdl->sdl_data, RT_MIN(pSdl->sdl_nlen, sizeof(pNew->szShortName) - 1));
+
+ /*
+ * If we found the adapter in the list returned by
+ * DarwinGetEthernetControllers() copy the name and UUID from there.
+ */
+ if (pNIC)
+ {
+ memcpy(pNew->szName, pNIC->szName, cbNameLen);
+ pNew->Uuid = pNIC->Uuid;
+ pNew->fWireless = pNIC->fWireless;
+ }
+ else
+ {
+ memcpy(pNew->szName, pSdl->sdl_data, pSdl->sdl_nlen);
+ /* Generate UUID from name and MAC address. */
+ RTUUID uuid;
+ RTUuidClear(&uuid);
+ memcpy(&uuid, pNew->szShortName, RT_MIN(cbNameLen, sizeof(uuid)));
+ uuid.Gen.u8ClockSeqHiAndReserved = (uuid.Gen.u8ClockSeqHiAndReserved & 0x3f) | 0x80;
+ uuid.Gen.u16TimeHiAndVersion = (uuid.Gen.u16TimeHiAndVersion & 0x0fff) | 0x4000;
+ memcpy(uuid.Gen.au8Node, pNew->MACAddress.au8, sizeof(uuid.Gen.au8Node));
+ pNew->Uuid = uuid;
+ }
+
+ pNext += pIfMsg->ifm_msglen;
+ while (pNext < pEnd)
+ {
+ struct ifa_msghdr *pIfAddrMsg = (struct ifa_msghdr *)pNext;
+
+ if (pIfAddrMsg->ifam_type != RTM_NEWADDR)
+ break;
+ extractAddressesToNetInfo(pIfAddrMsg->ifam_addrs,
+ (char *)(pIfAddrMsg + 1),
+ pIfAddrMsg->ifam_msglen + (char *)pIfAddrMsg,
+ pNew);
+ pNext += pIfAddrMsg->ifam_msglen;
+ }
+
+ if (pSdl->sdl_type == IFT_ETHER)
+ {
+ struct ifreq IfReq;
+ RTStrCopy(IfReq.ifr_name, sizeof(IfReq.ifr_name), pNew->szShortName);
+ if (ioctl(sock, SIOCGIFFLAGS, &IfReq) < 0)
+ {
+ Log(("NetIfList: ioctl(SIOCGIFFLAGS) -> %d\n", errno));
+ pNew->enmStatus = NETIF_S_UNKNOWN;
+ }
+ else
+ pNew->enmStatus = (IfReq.ifr_flags & IFF_UP) ? NETIF_S_UP : NETIF_S_DOWN;
+
+ HostNetworkInterfaceType_T enmType;
+ if (strncmp(pNew->szName, RT_STR_TUPLE("vboxnet")))
+ enmType = HostNetworkInterfaceType_Bridged;
+ else
+ enmType = HostNetworkInterfaceType_HostOnly;
+
+ ComObjPtr<HostNetworkInterface> IfObj;
+ IfObj.createObject();
+ if (SUCCEEDED(IfObj->init(Bstr(pNew->szName), enmType, pNew)))
+ {
+ /* Make sure the default interface gets to the beginning. */
+ if (pIfMsg->ifm_index == u16DefaultIface)
+ list.push_front(IfObj);
+ else
+ list.push_back(IfObj);
+ }
+ }
+ RTMemFree(pNew);
+ }
+ for (pNIC = pEtherNICs; pNIC;)
+ {
+ void *pvFree = pNIC;
+ pNIC = pNIC->pNext;
+ RTMemFree(pvFree);
+ }
+ close(sock);
+ RTMemFree(pBuf);
+ return rc;
+}
+
+int NetIfGetConfigByName(PNETIFINFO pInfo)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbNeeded;
+ char *pBuf, *pNext;
+ int aiMib[6];
+
+ aiMib[0] = CTL_NET;
+ aiMib[1] = PF_ROUTE;
+ aiMib[2] = 0;
+ aiMib[3] = 0; /* address family */
+ aiMib[4] = NET_RT_IFLIST;
+ aiMib[5] = 0;
+
+ if (sysctl(aiMib, 6, NULL, &cbNeeded, NULL, 0) < 0)
+ {
+ Log(("NetIfList: Failed to get estimate for list size (errno=%d).\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+ if ((pBuf = (char*)RTMemAlloc(cbNeeded)) == NULL)
+ return VERR_NO_MEMORY;
+ if (sysctl(aiMib, 6, pBuf, &cbNeeded, NULL, 0) < 0)
+ {
+ RTMemFree(pBuf);
+ Log(("NetIfList: Failed to retrieve interface table (errno=%d).\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+
+ int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sock < 0)
+ {
+ RTMemFree(pBuf);
+ Log(("NetIfList: socket() -> %d\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+
+ char *pEnd = pBuf + cbNeeded;
+ for (pNext = pBuf; pNext < pEnd;)
+ {
+ struct if_msghdr *pIfMsg = (struct if_msghdr *)pNext;
+
+ if (pIfMsg->ifm_type != RTM_IFINFO)
+ {
+ Log(("NetIfList: Got message %u while expecting %u.\n",
+ pIfMsg->ifm_type, RTM_IFINFO));
+ rc = VERR_INTERNAL_ERROR;
+ break;
+ }
+ struct sockaddr_dl *pSdl = (struct sockaddr_dl *)(pIfMsg + 1);
+
+ bool fSkip = !!strncmp(pInfo->szShortName, pSdl->sdl_data, pSdl->sdl_nlen)
+ || pInfo->szShortName[pSdl->sdl_nlen] != '\0';
+
+ pNext += pIfMsg->ifm_msglen;
+ while (pNext < pEnd)
+ {
+ struct ifa_msghdr *pIfAddrMsg = (struct ifa_msghdr *)pNext;
+
+ if (pIfAddrMsg->ifam_type != RTM_NEWADDR)
+ break;
+ if (!fSkip)
+ extractAddressesToNetInfo(pIfAddrMsg->ifam_addrs,
+ (char *)(pIfAddrMsg + 1),
+ pIfAddrMsg->ifam_msglen + (char *)pIfAddrMsg,
+ pInfo);
+ pNext += pIfAddrMsg->ifam_msglen;
+ }
+
+ if (!fSkip && pSdl->sdl_type == IFT_ETHER)
+ {
+ size_t cbNameLen = pSdl->sdl_nlen + 1;
+ memcpy(pInfo->MACAddress.au8, LLADDR(pSdl), sizeof(pInfo->MACAddress.au8));
+ pInfo->enmMediumType = NETIF_T_ETHERNET;
+ /* Generate UUID from name and MAC address. */
+ RTUUID uuid;
+ RTUuidClear(&uuid);
+ memcpy(&uuid, pInfo->szShortName, RT_MIN(cbNameLen, sizeof(uuid)));
+ uuid.Gen.u8ClockSeqHiAndReserved = (uuid.Gen.u8ClockSeqHiAndReserved & 0x3f) | 0x80;
+ uuid.Gen.u16TimeHiAndVersion = (uuid.Gen.u16TimeHiAndVersion & 0x0fff) | 0x4000;
+ memcpy(uuid.Gen.au8Node, pInfo->MACAddress.au8, sizeof(uuid.Gen.au8Node));
+ pInfo->Uuid = uuid;
+
+ struct ifreq IfReq;
+ RTStrCopy(IfReq.ifr_name, sizeof(IfReq.ifr_name), pInfo->szShortName);
+ if (ioctl(sock, SIOCGIFFLAGS, &IfReq) < 0)
+ {
+ Log(("NetIfList: ioctl(SIOCGIFFLAGS) -> %d\n", errno));
+ pInfo->enmStatus = NETIF_S_UNKNOWN;
+ }
+ else
+ pInfo->enmStatus = (IfReq.ifr_flags & IFF_UP) ? NETIF_S_UP : NETIF_S_DOWN;
+
+ return VINF_SUCCESS;
+ }
+ }
+ close(sock);
+ RTMemFree(pBuf);
+ return rc;
+}
+
+/**
+ * Retrieve the physical link speed in megabits per second. If the interface is
+ * not up or otherwise unavailable the zero speed is returned.
+ *
+ * @returns VBox status code.
+ *
+ * @param pcszIfName Interface name.
+ * @param puMbits Where to store the link speed.
+ */
+int NetIfGetLinkSpeed(const char *pcszIfName, uint32_t *puMbits)
+{
+ RT_NOREF(pcszIfName, puMbits);
+ return VERR_NOT_IMPLEMENTED;
+}
+#endif
diff --git a/src/VBox/Main/src-server/darwin/PerformanceDarwin.cpp b/src/VBox/Main/src-server/darwin/PerformanceDarwin.cpp
new file mode 100644
index 00000000..335e68bf
--- /dev/null
+++ b/src/VBox/Main/src-server/darwin/PerformanceDarwin.cpp
@@ -0,0 +1,191 @@
+/* $Id: PerformanceDarwin.cpp $ */
+/** @file
+ * VBox Darwin-specific Performance Classes implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <mach/mach_error.h>
+#include <mach/mach_host.h>
+#include <mach/mach_init.h>
+#include <mach/mach_time.h>
+#include <mach/vm_statistics.h>
+#include <sys/sysctl.h>
+#include <sys/errno.h>
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#include <iprt/mp.h>
+#include <iprt/param.h>
+#include <iprt/system.h>
+#include "Performance.h"
+
+/* The following declarations are missing in 10.4.x SDK */
+/** @todo Replace them with libproc.h and sys/proc_info.h when 10.4 is no longer supported */
+extern "C" int proc_pidinfo(int pid, int flavor, uint64_t arg, void *buffer, int buffersize);
+struct proc_taskinfo {
+ uint64_t pti_virtual_size; /* virtual memory size (bytes) */
+ uint64_t pti_resident_size; /* resident memory size (bytes) */
+ uint64_t pti_total_user; /* total time */
+ uint64_t pti_total_system;
+ uint64_t pti_threads_user; /* existing threads only */
+ uint64_t pti_threads_system;
+ int32_t pti_policy; /* default policy for new threads */
+ int32_t pti_faults; /* number of page faults */
+ int32_t pti_pageins; /* number of actual pageins */
+ int32_t pti_cow_faults; /* number of copy-on-write faults */
+ int32_t pti_messages_sent; /* number of messages sent */
+ int32_t pti_messages_received; /* number of messages received */
+ int32_t pti_syscalls_mach; /* number of mach system calls */
+ int32_t pti_syscalls_unix; /* number of unix system calls */
+ int32_t pti_csw; /* number of context switches */
+ int32_t pti_threadnum; /* number of threads in the task */
+ int32_t pti_numrunning; /* number of running threads */
+ int32_t pti_priority; /* task priority*/
+};
+#define PROC_PIDTASKINFO 4
+
+namespace pm {
+
+class CollectorDarwin : public CollectorHAL
+{
+public:
+ CollectorDarwin();
+ virtual int getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle);
+ virtual int getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available);
+ virtual int getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total);
+ virtual int getProcessMemoryUsage(RTPROCESS process, ULONG *used);
+private:
+ ULONG totalRAM;
+ uint32_t nCpus;
+};
+
+CollectorHAL *createHAL()
+{
+ return new CollectorDarwin();
+}
+
+CollectorDarwin::CollectorDarwin()
+{
+ uint64_t cb;
+ int rc = RTSystemQueryTotalRam(&cb);
+ if (RT_FAILURE(rc))
+ totalRAM = 0;
+ else
+ totalRAM = (ULONG)(cb / 1024);
+ nCpus = RTMpGetOnlineCount();
+ Assert(nCpus);
+ if (nCpus == 0)
+ {
+ /* It is rather unsual to have no CPUs, but the show must go on. */
+ nCpus = 1;
+ }
+}
+
+int CollectorDarwin::getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle)
+{
+ kern_return_t krc;
+ mach_msg_type_number_t count;
+ host_cpu_load_info_data_t info;
+
+ count = HOST_CPU_LOAD_INFO_COUNT;
+
+ krc = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&info, &count);
+ if (krc != KERN_SUCCESS)
+ {
+ Log(("host_statistics() -> %s", mach_error_string(krc)));
+ return RTErrConvertFromDarwinKern(krc);
+ }
+
+ *user = (uint64_t)info.cpu_ticks[CPU_STATE_USER]
+ + info.cpu_ticks[CPU_STATE_NICE];
+ *kernel = (uint64_t)info.cpu_ticks[CPU_STATE_SYSTEM];
+ *idle = (uint64_t)info.cpu_ticks[CPU_STATE_IDLE];
+ return VINF_SUCCESS;
+}
+
+int CollectorDarwin::getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available)
+{
+ AssertReturn(totalRAM, VERR_INTERNAL_ERROR);
+ uint64_t cb;
+ int rc = RTSystemQueryAvailableRam(&cb);
+ if (RT_SUCCESS(rc))
+ {
+ *total = totalRAM;
+ cb /= 1024;
+ *available = cb < ~(ULONG)0 ? (ULONG)cb : ~(ULONG)0;
+ *used = *total - *available;
+ }
+ return rc;
+}
+
+static int getProcessInfo(RTPROCESS process, struct proc_taskinfo *tinfo)
+{
+ Log7(("getProcessInfo() getting info for %d", process));
+ int cbRet = proc_pidinfo((pid_t)process, PROC_PIDTASKINFO, 0, tinfo, sizeof(*tinfo));
+ if (cbRet <= 0)
+ {
+ int iErrNo = errno;
+ Log(("proc_pidinfo() -> %s", strerror(iErrNo)));
+ return RTErrConvertFromDarwin(iErrNo);
+ }
+ if ((unsigned int)cbRet < sizeof(*tinfo))
+ {
+ Log(("proc_pidinfo() -> too few bytes %d", cbRet));
+ return VERR_INTERNAL_ERROR;
+ }
+ return VINF_SUCCESS;
+}
+
+int CollectorDarwin::getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total)
+{
+ struct proc_taskinfo tinfo;
+
+ int rc = getProcessInfo(process, &tinfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Adjust user and kernel values so 100% is when ALL cores are fully
+ * utilized (see @bugref{6345}).
+ */
+ *user = tinfo.pti_total_user / nCpus;
+ *kernel = tinfo.pti_total_system / nCpus;
+ *total = mach_absolute_time();
+ }
+ return rc;
+}
+
+int CollectorDarwin::getProcessMemoryUsage(RTPROCESS process, ULONG *used)
+{
+ struct proc_taskinfo tinfo;
+
+ int rc = getProcessInfo(process, &tinfo);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cKbResident = tinfo.pti_resident_size / 1024;
+ *used = cKbResident < ~(ULONG)0 ? (ULONG)cKbResident : ~(ULONG)0;
+ }
+ return rc;
+}
+
+}
+
diff --git a/src/VBox/Main/src-server/darwin/USBProxyBackendDarwin.cpp b/src/VBox/Main/src-server/darwin/USBProxyBackendDarwin.cpp
new file mode 100644
index 00000000..d232f5ce
--- /dev/null
+++ b/src/VBox/Main/src-server/darwin/USBProxyBackendDarwin.cpp
@@ -0,0 +1,195 @@
+/* $Id: USBProxyBackendDarwin.cpp $ */
+/** @file
+ * VirtualBox USB Proxy Service (in VBoxSVC), Darwin Specialization.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_USBPROXYBACKEND
+#include "USBProxyBackend.h"
+#include "LoggingNew.h"
+#include "iokit.h"
+
+#include <VBox/usb.h>
+#include <VBox/usblib.h>
+#include <iprt/errcore.h>
+
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/errcore.h>
+#include <iprt/asm.h>
+
+
+/**
+ * Initialize data members.
+ */
+USBProxyBackendDarwin::USBProxyBackendDarwin()
+ : USBProxyBackend(), mServiceRunLoopRef(NULL), mNotifyOpaque(NULL), mWaitABitNextTime(false)
+{
+}
+
+USBProxyBackendDarwin::~USBProxyBackendDarwin()
+{
+}
+
+/**
+ * Initializes the object (called right after construction).
+ *
+ * @returns VBox status code.
+ */
+int USBProxyBackendDarwin::init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings)
+{
+ USBProxyBackend::init(pUsbProxyService, strId, strAddress, fLoadingSettings);
+
+ unconst(m_strBackend) = Utf8Str("host");
+
+ /*
+ * Start the poller thread.
+ */
+ start();
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Stop all service threads and free the device chain.
+ */
+void USBProxyBackendDarwin::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /*
+ * Stop the service.
+ */
+ if (isActive())
+ stop();
+
+ USBProxyBackend::uninit();
+}
+
+
+int USBProxyBackendDarwin::captureDevice(HostUSBDevice *aDevice)
+{
+ /*
+ * Check preconditions.
+ */
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_Capturing);
+
+ devLock.release();
+ interruptWait();
+ return VINF_SUCCESS;
+}
+
+
+int USBProxyBackendDarwin::releaseDevice(HostUSBDevice *aDevice)
+{
+ /*
+ * Check preconditions.
+ */
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_ReleasingToHost);
+
+ devLock.release();
+ interruptWait();
+ return VINF_SUCCESS;
+}
+
+
+bool USBProxyBackendDarwin::isFakeUpdateRequired()
+{
+ return true;
+}
+
+
+int USBProxyBackendDarwin::wait(RTMSINTERVAL aMillies)
+{
+ SInt32 rc = CFRunLoopRunInMode(CFSTR(VBOX_IOKIT_MODE_STRING),
+ mWaitABitNextTime && aMillies >= 1000
+ ? 1.0 /* seconds */
+ : aMillies >= 5000 /* Temporary measure to poll for status changes (MSD). */
+ ? 5.0 /* seconds */
+ : aMillies / 1000.0,
+ true);
+ mWaitABitNextTime = rc != kCFRunLoopRunTimedOut;
+
+ return VINF_SUCCESS;
+}
+
+
+int USBProxyBackendDarwin::interruptWait(void)
+{
+ if (mServiceRunLoopRef)
+ CFRunLoopStop(mServiceRunLoopRef);
+ return 0;
+}
+
+
+PUSBDEVICE USBProxyBackendDarwin::getDevices(void)
+{
+ /* call iokit.cpp */
+ return DarwinGetUSBDevices();
+}
+
+
+void USBProxyBackendDarwin::serviceThreadInit(void)
+{
+ mServiceRunLoopRef = CFRunLoopGetCurrent();
+ mNotifyOpaque = DarwinSubscribeUSBNotifications();
+}
+
+
+void USBProxyBackendDarwin::serviceThreadTerm(void)
+{
+ DarwinUnsubscribeUSBNotifications(mNotifyOpaque);
+ mServiceRunLoopRef = NULL;
+}
+
+
+/**
+ * Wrapper called from iokit.cpp.
+ *
+ * @param pCur The USB device to free.
+ */
+void DarwinFreeUSBDeviceFromIOKit(PUSBDEVICE pCur)
+{
+ USBProxyBackend::freeDevice(pCur);
+}
+
diff --git a/src/VBox/Main/src-server/darwin/iokit.cpp b/src/VBox/Main/src-server/darwin/iokit.cpp
new file mode 100644
index 00000000..13d86421
--- /dev/null
+++ b/src/VBox/Main/src-server/darwin/iokit.cpp
@@ -0,0 +1,1992 @@
+/* $Id: iokit.cpp $ */
+/** @file
+ * Main - Darwin IOKit Routines.
+ *
+ * Because IOKit makes use of COM like interfaces, it does not mix very
+ * well with COM/XPCOM and must therefore be isolated from it using a
+ * simpler C interface.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN
+#ifdef STANDALONE_TESTCASE
+# define VBOX_WITH_USB
+#endif
+
+#include <mach/mach.h>
+#include <Carbon/Carbon.h>
+#include <CoreFoundation/CFBase.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/IOBSD.h>
+#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
+#include <IOKit/storage/IOBlockStorageDevice.h>
+#include <IOKit/storage/IOMedia.h>
+#include <IOKit/storage/IOCDMedia.h>
+#include <IOKit/scsi/SCSITaskLib.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <mach/mach_error.h>
+#include <sys/param.h>
+#include <paths.h>
+#ifdef VBOX_WITH_USB
+# include <IOKit/usb/IOUSBLib.h>
+# include <IOKit/IOCFPlugIn.h>
+#endif
+
+#include <VBox/log.h>
+#include <VBox/usblib.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/process.h>
+#include <iprt/assert.h>
+#include <iprt/system.h>
+#include <iprt/thread.h>
+#include <iprt/uuid.h>
+#ifdef STANDALONE_TESTCASE
+# include <iprt/initterm.h>
+# include <iprt/stream.h>
+#endif
+
+#include "iokit.h"
+
+/* A small hack... */
+#ifdef STANDALONE_TESTCASE
+# define DarwinFreeUSBDeviceFromIOKit(a) do { } while (0)
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** An attempt at catching reference leaks. */
+#define MY_CHECK_CREFS(cRefs) do { AssertMsg(cRefs < 25, ("%ld\n", cRefs)); NOREF(cRefs); } while (0)
+
+/** Contains the pid of the current client. If 0, the kernel is the current client. */
+#define VBOXUSB_CLIENT_KEY "VBoxUSB-Client"
+/** Contains the pid of the filter owner (i.e. the VBoxSVC pid). */
+#define VBOXUSB_OWNER_KEY "VBoxUSB-Owner"
+/** The VBoxUSBDevice class name. */
+#define VBOXUSBDEVICE_CLASS_NAME "org_virtualbox_VBoxUSBDevice"
+
+/** Define the constant for the IOUSBHostDevice class name added in El Capitan. */
+#ifndef kIOUSBHostDeviceClassName
+# define kIOUSBHostDeviceClassName "IOUSBHostDevice"
+#endif
+
+/** The major darwin version indicating OS X El Captian, used to take care of the USB changes. */
+#define VBOX_OSX_EL_CAPTIAN_VER 15
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The IO Master Port. */
+static mach_port_t g_MasterPort = MACH_PORT_NULL;
+/** Major darwin version as returned by uname -r. */
+static uint32_t g_uMajorDarwin = 0;
+
+
+/**
+ * Lazily opens the master port.
+ *
+ * @returns true if the port is open, false on failure (very unlikely).
+ */
+static bool darwinOpenMasterPort(void)
+{
+ if (!g_MasterPort)
+ {
+ kern_return_t krc = IOMasterPort(MACH_PORT_NULL, &g_MasterPort);
+ AssertReturn(krc == KERN_SUCCESS, false);
+
+ /* Get the darwin version we are running on. */
+ char szVersion[64];
+ int rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, &szVersion[0], sizeof(szVersion));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrToUInt32Ex(&szVersion[0], NULL, 10, &g_uMajorDarwin);
+ AssertLogRelMsg(rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS,
+ ("Failed to convert the major part of the version string '%s' into an integer: %Rrc\n",
+ szVersion, rc));
+ }
+ else
+ AssertLogRelMsgFailed(("Failed to query the OS release version with %Rrc\n", rc));
+ }
+ return true;
+}
+
+
+/**
+ * Checks whether the value exists.
+ *
+ * @returns true / false accordingly.
+ * @param DictRef The dictionary.
+ * @param KeyStrRef The key name.
+ */
+static bool darwinDictIsPresent(CFDictionaryRef DictRef, CFStringRef KeyStrRef)
+{
+ return !!CFDictionaryGetValue(DictRef, KeyStrRef);
+}
+
+
+/**
+ * Gets a boolean value.
+ *
+ * @returns Success indicator (true/false).
+ * @param DictRef The dictionary.
+ * @param KeyStrRef The key name.
+ * @param pf Where to store the key value.
+ */
+static bool darwinDictGetBool(CFDictionaryRef DictRef, CFStringRef KeyStrRef, bool *pf)
+{
+ CFTypeRef BoolRef = CFDictionaryGetValue(DictRef, KeyStrRef);
+ if ( BoolRef
+ && CFGetTypeID(BoolRef) == CFBooleanGetTypeID())
+ {
+ *pf = CFBooleanGetValue((CFBooleanRef)BoolRef);
+ return true;
+ }
+ *pf = false;
+ return false;
+}
+
+
+/**
+ * Gets an unsigned 8-bit integer value.
+ *
+ * @returns Success indicator (true/false).
+ * @param DictRef The dictionary.
+ * @param KeyStrRef The key name.
+ * @param pu8 Where to store the key value.
+ */
+static bool darwinDictGetU8(CFDictionaryRef DictRef, CFStringRef KeyStrRef, uint8_t *pu8)
+{
+ CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef);
+ if (ValRef)
+ {
+ if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt8Type, pu8))
+ return true;
+ }
+ *pu8 = 0;
+ return false;
+}
+
+
+/**
+ * Gets an unsigned 16-bit integer value.
+ *
+ * @returns Success indicator (true/false).
+ * @param DictRef The dictionary.
+ * @param KeyStrRef The key name.
+ * @param pu16 Where to store the key value.
+ */
+static bool darwinDictGetU16(CFDictionaryRef DictRef, CFStringRef KeyStrRef, uint16_t *pu16)
+{
+ CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef);
+ if (ValRef)
+ {
+ if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt16Type, pu16))
+ return true;
+ }
+ *pu16 = 0;
+ return false;
+}
+
+
+/**
+ * Gets an unsigned 32-bit integer value.
+ *
+ * @returns Success indicator (true/false).
+ * @param DictRef The dictionary.
+ * @param KeyStrRef The key name.
+ * @param pu32 Where to store the key value.
+ */
+static bool darwinDictGetU32(CFDictionaryRef DictRef, CFStringRef KeyStrRef, uint32_t *pu32)
+{
+ CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef);
+ if (ValRef)
+ {
+ if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt32Type, pu32))
+ return true;
+ }
+ *pu32 = 0;
+ return false;
+}
+
+
+/**
+ * Gets an unsigned 64-bit integer value.
+ *
+ * @returns Success indicator (true/false).
+ * @param DictRef The dictionary.
+ * @param KeyStrRef The key name.
+ * @param pu64 Where to store the key value.
+ */
+static bool darwinDictGetU64(CFDictionaryRef DictRef, CFStringRef KeyStrRef, uint64_t *pu64)
+{
+ CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef);
+ if (ValRef)
+ {
+ if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt64Type, pu64))
+ return true;
+ }
+ *pu64 = 0;
+ return false;
+}
+
+
+/**
+ * Gets a RTPROCESS value.
+ *
+ * @returns Success indicator (true/false).
+ * @param DictRef The dictionary.
+ * @param KeyStrRef The key name.
+ * @param pProcess Where to store the key value.
+ */
+static bool darwinDictGetProcess(CFMutableDictionaryRef DictRef, CFStringRef KeyStrRef, PRTPROCESS pProcess)
+{
+ switch (sizeof(*pProcess))
+ {
+ case sizeof(uint16_t): return darwinDictGetU16(DictRef, KeyStrRef, (uint16_t *)pProcess);
+ case sizeof(uint32_t): return darwinDictGetU32(DictRef, KeyStrRef, (uint32_t *)pProcess);
+ case sizeof(uint64_t): return darwinDictGetU64(DictRef, KeyStrRef, (uint64_t *)pProcess);
+ default:
+ AssertMsgFailedReturn(("%d\n", sizeof(*pProcess)), false);
+ }
+}
+
+
+/**
+ * Gets string value, converted to UTF-8 and put in user buffer.
+ *
+ * @returns Success indicator (true/false).
+ * @param DictRef The dictionary.
+ * @param KeyStrRef The key name.
+ * @param psz The string buffer. On failure this will be an empty string ("").
+ * @param cch The size of the buffer.
+ */
+static bool darwinDictGetString(CFDictionaryRef DictRef, CFStringRef KeyStrRef, char *psz, size_t cch)
+{
+ CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef);
+ if (ValRef)
+ {
+ if (CFStringGetCString((CFStringRef)ValRef, psz, (CFIndex)cch, kCFStringEncodingUTF8))
+ return true;
+ }
+ Assert(cch > 0);
+ *psz = '\0';
+ return false;
+}
+
+
+/**
+ * Gets string value, converted to UTF-8 and put in a IPRT string buffer.
+ *
+ * @returns Success indicator (true/false).
+ * @param DictRef The dictionary.
+ * @param KeyStrRef The key name.
+ * @param ppsz Where to store the key value. Free with RTStrFree. Set to NULL on failure.
+ */
+static bool darwinDictDupString(CFDictionaryRef DictRef, CFStringRef KeyStrRef, char **ppsz)
+{
+ char szBuf[512];
+ if (darwinDictGetString(DictRef, KeyStrRef, szBuf, sizeof(szBuf)))
+ {
+ USBLibPurgeEncoding(szBuf);
+ *ppsz = RTStrDup(szBuf);
+ if (*ppsz)
+ return true;
+ }
+ *ppsz = NULL;
+ return false;
+}
+
+
+/**
+ * Gets a byte string (data) of a specific size.
+ *
+ * @returns Success indicator (true/false).
+ * @param DictRef The dictionary.
+ * @param KeyStrRef The key name.
+ * @param pvBuf The buffer to store the bytes in.
+ * @param cbBuf The size of the buffer. This must exactly match the data size.
+ */
+static bool darwinDictGetData(CFDictionaryRef DictRef, CFStringRef KeyStrRef, void *pvBuf, size_t cbBuf)
+{
+ CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef);
+ if (ValRef)
+ {
+ CFIndex cbActual = CFDataGetLength((CFDataRef)ValRef);
+ if (cbActual >= 0 && cbBuf == (size_t)cbActual)
+ {
+ CFDataGetBytes((CFDataRef)ValRef, CFRangeMake(0, (CFIndex)cbBuf), (uint8_t *)pvBuf);
+ return true;
+ }
+ }
+ memset(pvBuf, '\0', cbBuf);
+ return false;
+}
+
+
+#if 1 && !defined(STANDALONE_TESTCASE) /* dumping disabled */
+# define DARWIN_IOKIT_LOG(a) Log(a)
+# define DARWIN_IOKIT_LOG_FLUSH() do {} while (0)
+# define DARWIN_IOKIT_DUMP_OBJ(o) do {} while (0)
+#else
+# if defined(STANDALONE_TESTCASE)
+# include <iprt/stream.h>
+# define DARWIN_IOKIT_LOG(a) RTPrintf a
+# define DARWIN_IOKIT_LOG_FLUSH() RTStrmFlush(g_pStdOut)
+# else
+# define DARWIN_IOKIT_LOG(a) RTLogPrintf a
+# define DARWIN_IOKIT_LOG_FLUSH() RTLogFlush(NULL)
+# endif
+# define DARWIN_IOKIT_DUMP_OBJ(o) darwinDumpObj(o)
+
+/**
+ * Callback for dumping a dictionary key.
+ *
+ * @param pvKey The key name.
+ * @param pvValue The key value
+ * @param pvUser The recursion depth.
+ */
+static void darwinDumpDictCallback(const void *pvKey, const void *pvValue, void *pvUser)
+{
+ /* display the key name. */
+ char *pszKey = (char *)RTMemTmpAlloc(1024);
+ if (!CFStringGetCString((CFStringRef)pvKey, pszKey, 1024, kCFStringEncodingUTF8))
+ strcpy(pszKey, "CFStringGetCString failure");
+ DARWIN_IOKIT_LOG(("%+*s%s", (int)(uintptr_t)pvUser, "", pszKey));
+ RTMemTmpFree(pszKey);
+
+ /* display the value type */
+ CFTypeID Type = CFGetTypeID(pvValue);
+ DARWIN_IOKIT_LOG((" [%d-", Type));
+
+ /* display the value */
+ if (Type == CFDictionaryGetTypeID())
+ {
+ DARWIN_IOKIT_LOG(("dictionary] =\n"
+ "%-*s{\n", (int)(uintptr_t)pvUser, ""));
+ CFDictionaryApplyFunction((CFDictionaryRef)pvValue, darwinDumpDictCallback, (void *)((uintptr_t)pvUser + 4));
+ DARWIN_IOKIT_LOG(("%-*s}\n", (int)(uintptr_t)pvUser, ""));
+ }
+ else if (Type == CFBooleanGetTypeID())
+ DARWIN_IOKIT_LOG(("bool] = %s\n", CFBooleanGetValue((CFBooleanRef)pvValue) ? "true" : "false"));
+ else if (Type == CFNumberGetTypeID())
+ {
+ union
+ {
+ SInt8 s8;
+ SInt16 s16;
+ SInt32 s32;
+ SInt64 s64;
+ Float32 rf32;
+ Float64 rd64;
+ char ch;
+ short s;
+ int i;
+ long l;
+ long long ll;
+ float rf;
+ double rd;
+ CFIndex iCF;
+ } u;
+ RT_ZERO(u);
+ CFNumberType NumType = CFNumberGetType((CFNumberRef)pvValue);
+ if (CFNumberGetValue((CFNumberRef)pvValue, NumType, &u))
+ {
+ switch (CFNumberGetType((CFNumberRef)pvValue))
+ {
+ case kCFNumberSInt8Type: DARWIN_IOKIT_LOG(("SInt8] = %RI8 (%#RX8)\n", NumType, u.s8, u.s8)); break;
+ case kCFNumberSInt16Type: DARWIN_IOKIT_LOG(("SInt16] = %RI16 (%#RX16)\n", NumType, u.s16, u.s16)); break;
+ case kCFNumberSInt32Type: DARWIN_IOKIT_LOG(("SInt32] = %RI32 (%#RX32)\n", NumType, u.s32, u.s32)); break;
+ case kCFNumberSInt64Type: DARWIN_IOKIT_LOG(("SInt64] = %RI64 (%#RX64)\n", NumType, u.s64, u.s64)); break;
+ case kCFNumberFloat32Type: DARWIN_IOKIT_LOG(("float32] = %#lx\n", NumType, u.l)); break;
+ case kCFNumberFloat64Type: DARWIN_IOKIT_LOG(("float64] = %#llx\n", NumType, u.ll)); break;
+ case kCFNumberFloatType: DARWIN_IOKIT_LOG(("float] = %#lx\n", NumType, u.l)); break;
+ case kCFNumberDoubleType: DARWIN_IOKIT_LOG(("double] = %#llx\n", NumType, u.ll)); break;
+ case kCFNumberCharType: DARWIN_IOKIT_LOG(("char] = %hhd (%hhx)\n", NumType, u.ch, u.ch)); break;
+ case kCFNumberShortType: DARWIN_IOKIT_LOG(("short] = %hd (%hx)\n", NumType, u.s, u.s)); break;
+ case kCFNumberIntType: DARWIN_IOKIT_LOG(("int] = %d (%#x)\n", NumType, u.i, u.i)); break;
+ case kCFNumberLongType: DARWIN_IOKIT_LOG(("long] = %ld (%#lx)\n", NumType, u.l, u.l)); break;
+ case kCFNumberLongLongType: DARWIN_IOKIT_LOG(("long long] = %lld (%#llx)\n", NumType, u.ll, u.ll)); break;
+ case kCFNumberCFIndexType: DARWIN_IOKIT_LOG(("CFIndex] = %lld (%#llx)\n", NumType, (long long)u.iCF, (long long)u.iCF)); break;
+ break;
+ default: DARWIN_IOKIT_LOG(("%d?] = %lld (%llx)\n", NumType, u.ll, u.ll)); break;
+ }
+ }
+ else
+ DARWIN_IOKIT_LOG(("number] = CFNumberGetValue failed\n"));
+ }
+ else if (Type == CFBooleanGetTypeID())
+ DARWIN_IOKIT_LOG(("boolean] = %RTbool\n", CFBooleanGetValue((CFBooleanRef)pvValue)));
+ else if (Type == CFStringGetTypeID())
+ {
+ DARWIN_IOKIT_LOG(("string] = "));
+ char *pszValue = (char *)RTMemTmpAlloc(16*_1K);
+ if (!CFStringGetCString((CFStringRef)pvValue, pszValue, 16*_1K, kCFStringEncodingUTF8))
+ strcpy(pszValue, "CFStringGetCString failure");
+ DARWIN_IOKIT_LOG(("\"%s\"\n", pszValue));
+ RTMemTmpFree(pszValue);
+ }
+ else if (Type == CFDataGetTypeID())
+ {
+ CFIndex cb = CFDataGetLength((CFDataRef)pvValue);
+ DARWIN_IOKIT_LOG(("%zu bytes] =", (size_t)cb));
+ void *pvData = RTMemTmpAlloc(cb + 8);
+ CFDataGetBytes((CFDataRef)pvValue, CFRangeMake(0, cb), (uint8_t *)pvData);
+ if (!cb)
+ DARWIN_IOKIT_LOG((" \n"));
+ else if (cb <= 32)
+ DARWIN_IOKIT_LOG((" %.*Rhxs\n", cb, pvData));
+ else
+ DARWIN_IOKIT_LOG(("\n%.*Rhxd\n", cb, pvData));
+ RTMemTmpFree(pvData);
+ }
+ else
+ DARWIN_IOKIT_LOG(("??] = %p\n", pvValue));
+}
+
+
+/**
+ * Dumps a dictionary to the log.
+ *
+ * @param DictRef The dictionary to dump.
+ */
+static void darwinDumpDict(CFDictionaryRef DictRef, unsigned cIndents)
+{
+ CFDictionaryApplyFunction(DictRef, darwinDumpDictCallback, (void *)(uintptr_t)cIndents);
+ DARWIN_IOKIT_LOG_FLUSH();
+}
+
+
+/**
+ * Dumps an I/O kit registry object and all it children.
+ * @param Object The object to dump.
+ * @param cIndents The number of indents to use.
+ */
+static void darwinDumpObjInt(io_object_t Object, unsigned cIndents)
+{
+ static io_string_t s_szPath;
+ kern_return_t krc = IORegistryEntryGetPath(Object, kIOServicePlane, s_szPath);
+ if (krc != KERN_SUCCESS)
+ strcpy(s_szPath, "IORegistryEntryGetPath failed");
+ DARWIN_IOKIT_LOG(("Dumping %p - %s:\n", (const void *)Object, s_szPath));
+
+ CFMutableDictionaryRef PropsRef = 0;
+ krc = IORegistryEntryCreateCFProperties(Object, &PropsRef, kCFAllocatorDefault, kNilOptions);
+ if (krc == KERN_SUCCESS)
+ {
+ darwinDumpDict(PropsRef, cIndents + 4);
+ CFRelease(PropsRef);
+ }
+
+ /*
+ * Children.
+ */
+ io_iterator_t Children;
+ krc = IORegistryEntryGetChildIterator(Object, kIOServicePlane, &Children);
+ if (krc == KERN_SUCCESS)
+ {
+ io_object_t Child;
+ while ((Child = IOIteratorNext(Children)) != IO_OBJECT_NULL)
+ {
+ darwinDumpObjInt(Child, cIndents + 4);
+ IOObjectRelease(Child);
+ }
+ IOObjectRelease(Children);
+ }
+ else
+ DARWIN_IOKIT_LOG(("IORegistryEntryGetChildIterator -> %#x\n", krc));
+}
+
+/**
+ * Dumps an I/O kit registry object and all it children.
+ * @param Object The object to dump.
+ */
+static void darwinDumpObj(io_object_t Object)
+{
+ darwinDumpObjInt(Object, 0);
+}
+
+#endif /* helpers for dumping registry dictionaries */
+
+
+#ifdef VBOX_WITH_USB
+
+/**
+ * Notification data created by DarwinSubscribeUSBNotifications, used by
+ * the callbacks and finally freed by DarwinUnsubscribeUSBNotifications.
+ */
+typedef struct DARWINUSBNOTIFY
+{
+ /** The notification port.
+ * It's shared between the notification callbacks. */
+ IONotificationPortRef NotifyPort;
+ /** The run loop source for NotifyPort. */
+ CFRunLoopSourceRef NotifyRLSrc;
+ /** The attach notification iterator. */
+ io_iterator_t AttachIterator;
+ /** The 2nd attach notification iterator. */
+ io_iterator_t AttachIterator2;
+ /** The detach notification iterator. */
+ io_iterator_t DetachIterator;
+} DARWINUSBNOTIFY, *PDARWINUSBNOTIFY;
+
+
+/**
+ * Run thru an iterator.
+ *
+ * The docs says this is necessary to start getting notifications,
+ * so this function is called in the callbacks and right after
+ * registering the notification.
+ *
+ * @param pIterator The iterator reference.
+ */
+static void darwinDrainIterator(io_iterator_t pIterator)
+{
+ io_object_t Object;
+ while ((Object = IOIteratorNext(pIterator)) != IO_OBJECT_NULL)
+ {
+ DARWIN_IOKIT_DUMP_OBJ(Object);
+ IOObjectRelease(Object);
+ }
+}
+
+
+/**
+ * Callback for the 1st attach notification.
+ *
+ * @param pvNotify Our data.
+ * @param NotifyIterator The notification iterator.
+ */
+static void darwinUSBAttachNotification1(void *pvNotify, io_iterator_t NotifyIterator)
+{
+ DARWIN_IOKIT_LOG(("USB Attach Notification1\n"));
+ NOREF(pvNotify); //PDARWINUSBNOTIFY pNotify = (PDARWINUSBNOTIFY)pvNotify;
+ darwinDrainIterator(NotifyIterator);
+}
+
+
+/**
+ * Callback for the 2nd attach notification.
+ *
+ * @param pvNotify Our data.
+ * @param NotifyIterator The notification iterator.
+ */
+static void darwinUSBAttachNotification2(void *pvNotify, io_iterator_t NotifyIterator)
+{
+ DARWIN_IOKIT_LOG(("USB Attach Notification2\n"));
+ NOREF(pvNotify); //PDARWINUSBNOTIFY pNotify = (PDARWINUSBNOTIFY)pvNotify;
+ darwinDrainIterator(NotifyIterator);
+}
+
+
+/**
+ * Callback for the detach notifications.
+ *
+ * @param pvNotify Our data.
+ * @param NotifyIterator The notification iterator.
+ */
+static void darwinUSBDetachNotification(void *pvNotify, io_iterator_t NotifyIterator)
+{
+ DARWIN_IOKIT_LOG(("USB Detach Notification\n"));
+ NOREF(pvNotify); //PDARWINUSBNOTIFY pNotify = (PDARWINUSBNOTIFY)pvNotify;
+ darwinDrainIterator(NotifyIterator);
+}
+
+
+/**
+ * Subscribes the run loop to USB notification events relevant to
+ * device attach/detach.
+ *
+ * The source mode for these events is defined as VBOX_IOKIT_MODE_STRING
+ * so that the caller can listen to events from this mode only and
+ * re-evalutate the list of attached devices whenever an event arrives.
+ *
+ * @returns opaque for passing to the unsubscribe function. If NULL
+ * something unexpectedly failed during subscription.
+ */
+void *DarwinSubscribeUSBNotifications(void)
+{
+ AssertReturn(darwinOpenMasterPort(), NULL);
+
+ PDARWINUSBNOTIFY pNotify = (PDARWINUSBNOTIFY)RTMemAllocZ(sizeof(*pNotify));
+ AssertReturn(pNotify, NULL);
+
+ /*
+ * Create the notification port, bake it into a runloop source which we
+ * then add to our run loop.
+ */
+ pNotify->NotifyPort = IONotificationPortCreate(g_MasterPort);
+ Assert(pNotify->NotifyPort);
+ if (pNotify->NotifyPort)
+ {
+ pNotify->NotifyRLSrc = IONotificationPortGetRunLoopSource(pNotify->NotifyPort);
+ Assert(pNotify->NotifyRLSrc);
+ if (pNotify->NotifyRLSrc)
+ {
+ CFRunLoopRef RunLoopRef = CFRunLoopGetCurrent();
+ CFRetain(RunLoopRef); /* Workaround for crash when cleaning up the TLS / runloop((sub)mode). See @bugref{2807}. */
+ CFRunLoopAddSource(RunLoopRef, pNotify->NotifyRLSrc, CFSTR(VBOX_IOKIT_MODE_STRING));
+
+ /*
+ * Create the notification callbacks.
+ */
+ kern_return_t rc = IOServiceAddMatchingNotification(pNotify->NotifyPort,
+ kIOPublishNotification,
+ IOServiceMatching(kIOUSBDeviceClassName),
+ darwinUSBAttachNotification1,
+ pNotify,
+ &pNotify->AttachIterator);
+ if (rc == KERN_SUCCESS)
+ {
+ darwinDrainIterator(pNotify->AttachIterator);
+ rc = IOServiceAddMatchingNotification(pNotify->NotifyPort,
+ kIOMatchedNotification,
+ IOServiceMatching(kIOUSBDeviceClassName),
+ darwinUSBAttachNotification2,
+ pNotify,
+ &pNotify->AttachIterator2);
+ if (rc == KERN_SUCCESS)
+ {
+ darwinDrainIterator(pNotify->AttachIterator2);
+ rc = IOServiceAddMatchingNotification(pNotify->NotifyPort,
+ kIOTerminatedNotification,
+ IOServiceMatching(kIOUSBDeviceClassName),
+ darwinUSBDetachNotification,
+ pNotify,
+ &pNotify->DetachIterator);
+ {
+ darwinDrainIterator(pNotify->DetachIterator);
+ return pNotify;
+ }
+ IOObjectRelease(pNotify->AttachIterator2);
+ }
+ IOObjectRelease(pNotify->AttachIterator);
+ }
+ CFRunLoopRemoveSource(RunLoopRef, pNotify->NotifyRLSrc, CFSTR(VBOX_IOKIT_MODE_STRING));
+ }
+ IONotificationPortDestroy(pNotify->NotifyPort);
+ }
+
+ RTMemFree(pNotify);
+ return NULL;
+}
+
+
+/**
+ * Unsubscribe the run loop from USB notification subscribed to
+ * by DarwinSubscribeUSBNotifications.
+ *
+ * @param pvOpaque The return value from DarwinSubscribeUSBNotifications.
+ */
+void DarwinUnsubscribeUSBNotifications(void *pvOpaque)
+{
+ PDARWINUSBNOTIFY pNotify = (PDARWINUSBNOTIFY)pvOpaque;
+ if (!pNotify)
+ return;
+
+ IOObjectRelease(pNotify->AttachIterator);
+ pNotify->AttachIterator = IO_OBJECT_NULL;
+ IOObjectRelease(pNotify->AttachIterator2);
+ pNotify->AttachIterator2 = IO_OBJECT_NULL;
+ IOObjectRelease(pNotify->DetachIterator);
+ pNotify->DetachIterator = IO_OBJECT_NULL;
+
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), pNotify->NotifyRLSrc, CFSTR(VBOX_IOKIT_MODE_STRING));
+ IONotificationPortDestroy(pNotify->NotifyPort);
+ pNotify->NotifyRLSrc = NULL;
+ pNotify->NotifyPort = NULL;
+
+ RTMemFree(pNotify);
+}
+
+
+/**
+ * Descends recursively into a IORegistry tree locating the first object of a given class.
+ *
+ * The search is performed depth first.
+ *
+ * @returns Object reference if found, NULL if not.
+ * @param Object The current tree root.
+ * @param pszClass The name of the class we're looking for.
+ * @param pszNameBuf A scratch buffer for query the class name in to avoid
+ * wasting 128 bytes on an io_name_t object for every recursion.
+ */
+static io_object_t darwinFindObjectByClass(io_object_t Object, const char *pszClass, io_name_t pszNameBuf)
+{
+ io_iterator_t Children;
+ kern_return_t krc = IORegistryEntryGetChildIterator(Object, kIOServicePlane, &Children);
+ if (krc != KERN_SUCCESS)
+ return IO_OBJECT_NULL;
+ io_object_t Child;
+ while ((Child = IOIteratorNext(Children)) != IO_OBJECT_NULL)
+ {
+ krc = IOObjectGetClass(Child, pszNameBuf);
+ if ( krc == KERN_SUCCESS
+ && !strcmp(pszNameBuf, pszClass))
+ break;
+
+ io_object_t GrandChild = darwinFindObjectByClass(Child, pszClass, pszNameBuf);
+ IOObjectRelease(Child);
+ if (GrandChild)
+ {
+ Child = GrandChild;
+ break;
+ }
+ }
+ IOObjectRelease(Children);
+ return Child;
+}
+
+
+/**
+ * Descends recursively into IOUSBMassStorageClass tree to check whether
+ * the MSD is mounted or not.
+ *
+ * The current heuristic is to look for the IOMedia class.
+ *
+ * @returns true if mounted, false if not.
+ * @param MSDObj The IOUSBMassStorageClass object.
+ * @param pszNameBuf A scratch buffer for query the class name in to avoid
+ * wasting 128 bytes on an io_name_t object for every recursion.
+ */
+static bool darwinIsMassStorageInterfaceInUse(io_object_t MSDObj, io_name_t pszNameBuf)
+{
+ io_object_t MediaObj = darwinFindObjectByClass(MSDObj, kIOMediaClass, pszNameBuf);
+ if (MediaObj)
+ {
+ CFMutableDictionaryRef pProperties;
+ kern_return_t krc;
+ bool fInUse = true;
+
+ krc = IORegistryEntryCreateCFProperties(MediaObj, &pProperties, kCFAllocatorDefault, kNilOptions);
+ if (krc == KERN_SUCCESS)
+ {
+ CFBooleanRef pBoolValue = (CFBooleanRef)CFDictionaryGetValue(pProperties, CFSTR(kIOMediaOpenKey));
+ if (pBoolValue)
+ fInUse = CFBooleanGetValue(pBoolValue);
+
+ CFRelease(pProperties);
+ }
+
+ /* more checks? */
+ IOObjectRelease(MediaObj);
+ return fInUse;
+ }
+
+ return false;
+}
+
+
+/**
+ * Finds the matching IOUSBHostDevice registry entry for the given legacy USB device interface (IOUSBDevice).
+ *
+ * @returns kern_return_t error code.
+ * @param USBDeviceLegacy The legacy device I/O Kit object.
+ * @param pUSBDevice Where to store the IOUSBHostDevice object on success.
+ */
+static kern_return_t darwinGetUSBHostDeviceFromLegacyDevice(io_object_t USBDeviceLegacy, io_object_t *pUSBDevice)
+{
+ kern_return_t krc = KERN_SUCCESS;
+ uint64_t uIoRegEntryId = 0;
+
+ *pUSBDevice = 0;
+
+ /* Get the registry entry ID to match against. */
+ krc = IORegistryEntryGetRegistryEntryID(USBDeviceLegacy, &uIoRegEntryId);
+ if (krc != KERN_SUCCESS)
+ return krc;
+
+ /*
+ * Create a matching dictionary for searching for USB Devices in the IOKit.
+ */
+ CFMutableDictionaryRef RefMatchingDict = IOServiceMatching(kIOUSBHostDeviceClassName);
+ AssertReturn(RefMatchingDict, KERN_FAILURE);
+
+ /*
+ * Perform the search and get a collection of USB Device back.
+ */
+ io_iterator_t USBDevices = IO_OBJECT_NULL;
+ IOReturn rc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &USBDevices);
+ AssertMsgReturn(rc == kIOReturnSuccess, ("rc=%d\n", rc), KERN_FAILURE);
+ RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */
+
+ /*
+ * Walk the devices and check for the matching alternate registry entry ID.
+ */
+ io_object_t USBDevice;
+ while ((USBDevice = IOIteratorNext(USBDevices)) != IO_OBJECT_NULL)
+ {
+ DARWIN_IOKIT_DUMP_OBJ(USBDevice);
+
+ CFMutableDictionaryRef PropsRef = 0;
+ krc = IORegistryEntryCreateCFProperties(USBDevice, &PropsRef, kCFAllocatorDefault, kNilOptions);
+ if (krc == KERN_SUCCESS)
+ {
+ uint64_t uAltRegId = 0;
+ if ( darwinDictGetU64(PropsRef, CFSTR("AppleUSBAlternateServiceRegistryID"), &uAltRegId)
+ && uAltRegId == uIoRegEntryId)
+ {
+ *pUSBDevice = USBDevice;
+ CFRelease(PropsRef);
+ break;
+ }
+
+ CFRelease(PropsRef);
+ }
+ IOObjectRelease(USBDevice);
+ }
+ IOObjectRelease(USBDevices);
+
+ return krc;
+}
+
+
+static bool darwinUSBDeviceIsGrabbedDetermineState(PUSBDEVICE pCur, io_object_t USBDevice)
+{
+ /*
+ * Iterate the interfaces (among the children of the IOUSBDevice object).
+ */
+ io_iterator_t Interfaces;
+ kern_return_t krc = IORegistryEntryGetChildIterator(USBDevice, kIOServicePlane, &Interfaces);
+ if (krc != KERN_SUCCESS)
+ return false;
+
+ bool fHaveOwner = false;
+ RTPROCESS Owner = NIL_RTPROCESS;
+ bool fHaveClient = false;
+ RTPROCESS Client = NIL_RTPROCESS;
+ io_object_t Interface;
+ while ((Interface = IOIteratorNext(Interfaces)) != IO_OBJECT_NULL)
+ {
+ io_name_t szName;
+ krc = IOObjectGetClass(Interface, szName);
+ if ( krc == KERN_SUCCESS
+ && !strcmp(szName, VBOXUSBDEVICE_CLASS_NAME))
+ {
+ CFMutableDictionaryRef PropsRef = 0;
+ krc = IORegistryEntryCreateCFProperties(Interface, &PropsRef, kCFAllocatorDefault, kNilOptions);
+ if (krc == KERN_SUCCESS)
+ {
+ fHaveOwner = darwinDictGetProcess(PropsRef, CFSTR(VBOXUSB_OWNER_KEY), &Owner);
+ fHaveClient = darwinDictGetProcess(PropsRef, CFSTR(VBOXUSB_CLIENT_KEY), &Client);
+ CFRelease(PropsRef);
+ }
+ }
+
+ IOObjectRelease(Interface);
+ }
+ IOObjectRelease(Interfaces);
+
+ /*
+ * Calc the status.
+ */
+ if (fHaveOwner)
+ {
+ if (Owner == RTProcSelf())
+ pCur->enmState = !fHaveClient || Client == NIL_RTPROCESS || !Client
+ ? USBDEVICESTATE_HELD_BY_PROXY
+ : USBDEVICESTATE_USED_BY_GUEST;
+ else
+ pCur->enmState = USBDEVICESTATE_USED_BY_HOST;
+ }
+
+ return fHaveOwner;
+}
+
+
+/**
+ * Worker for determining the USB device state for devices which are not captured by the VBoxUSB driver
+ * Works for both, IOUSBDevice (legacy on release >= El Capitan) and IOUSBHostDevice (available on >= El Capitan).
+ *
+ * @returns nothing.
+ * @param pCur The USB device data.
+ * @param USBDevice I/O Kit USB device object (either IOUSBDevice or IOUSBHostDevice).
+ */
+static void darwinDetermineUSBDeviceStateWorker(PUSBDEVICE pCur, io_object_t USBDevice)
+{
+ /*
+ * Iterate the interfaces (among the children of the IOUSBDevice object).
+ */
+ io_iterator_t Interfaces;
+ kern_return_t krc = IORegistryEntryGetChildIterator(USBDevice, kIOServicePlane, &Interfaces);
+ if (krc != KERN_SUCCESS)
+ return;
+
+ bool fUserClientOnly = true;
+ bool fConfigured = false;
+ bool fInUse = false;
+ bool fSeizable = true;
+ io_object_t Interface;
+ while ((Interface = IOIteratorNext(Interfaces)) != IO_OBJECT_NULL)
+ {
+ io_name_t szName;
+ krc = IOObjectGetClass(Interface, szName);
+ if ( krc == KERN_SUCCESS
+ && ( !strcmp(szName, "IOUSBInterface")
+ || !strcmp(szName, "IOUSBHostInterface")))
+ {
+ fConfigured = true;
+
+ /*
+ * Iterate the interface children looking for stuff other than
+ * IOUSBUserClientInit objects.
+ */
+ io_iterator_t Children1;
+ krc = IORegistryEntryGetChildIterator(Interface, kIOServicePlane, &Children1);
+ if (krc == KERN_SUCCESS)
+ {
+ io_object_t Child1;
+ while ((Child1 = IOIteratorNext(Children1)) != IO_OBJECT_NULL)
+ {
+ krc = IOObjectGetClass(Child1, szName);
+ if ( krc == KERN_SUCCESS
+ && strcmp(szName, "IOUSBUserClientInit"))
+ {
+ fUserClientOnly = false;
+
+ if ( !strcmp(szName, "IOUSBMassStorageClass")
+ || !strcmp(szName, "IOUSBMassStorageInterfaceNub"))
+ {
+ /* Only permit capturing MSDs that aren't mounted, at least
+ until the GUI starts poping up warnings about data loss
+ and such when capturing a busy device. */
+ fSeizable = false;
+ fInUse |= darwinIsMassStorageInterfaceInUse(Child1, szName);
+ }
+ else if (!strcmp(szName, "IOUSBHIDDriver")
+ || !strcmp(szName, "AppleHIDMouse")
+ /** @todo more? */)
+ {
+ /* For now, just assume that all HID devices are inaccessible
+ because of the greedy HID service. */
+ fSeizable = false;
+ fInUse = true;
+ }
+ else
+ fInUse = true;
+ }
+ IOObjectRelease(Child1);
+ }
+ IOObjectRelease(Children1);
+ }
+ }
+
+ IOObjectRelease(Interface);
+ }
+ IOObjectRelease(Interfaces);
+
+ /*
+ * Calc the status.
+ */
+ if (!fInUse)
+ pCur->enmState = USBDEVICESTATE_UNUSED;
+ else
+ pCur->enmState = fSeizable
+ ? USBDEVICESTATE_USED_BY_HOST_CAPTURABLE
+ : USBDEVICESTATE_USED_BY_HOST;
+}
+
+
+/**
+ * Worker function for DarwinGetUSBDevices() that tries to figure out
+ * what state the device is in and set enmState.
+ *
+ * This is mostly a matter of distinguishing between devices that nobody
+ * uses, devices that can be seized and devices that cannot be grabbed.
+ *
+ * @param pCur The USB device data.
+ * @param USBDevice The USB device object.
+ * @param PropsRef The USB device properties.
+ */
+static void darwinDeterminUSBDeviceState(PUSBDEVICE pCur, io_object_t USBDevice, CFMutableDictionaryRef /* PropsRef */)
+{
+
+ if (!darwinUSBDeviceIsGrabbedDetermineState(pCur, USBDevice))
+ {
+ /*
+ * The USB stack was completely reworked on El Capitan and the IOUSBDevice and IOUSBInterface
+ * are deprecated and don't return the information required for the additional checks below.
+ * We also can't directly make use of the new classes (IOUSBHostDevice and IOUSBHostInterface)
+ * because VBoxUSB only exposes the legacy interfaces. Trying to use the new classes results in errors
+ * because the I/O Kit USB library wants to use the new interfaces. The result is us losing the device
+ * form the list when VBoxUSB has attached to the USB device.
+ *
+ * To make the checks below work we have to get hold of the IOUSBHostDevice and IOUSBHostInterface
+ * instances for the current device. Fortunately the IOUSBHostDevice instance contains a
+ * "AppleUSBAlternateServiceRegistryID" which points to the legacy class instance for the same device.
+ * So just iterate over the list of IOUSBHostDevice instances and check whether the
+ * AppleUSBAlternateServiceRegistryID property matches with the legacy instance.
+ *
+ * The upside is that we can keep VBoxUSB untouched and still compatible with older OS X releases.
+ */
+ if (g_uMajorDarwin >= VBOX_OSX_EL_CAPTIAN_VER)
+ {
+ io_object_t IOUSBDeviceNew = IO_OBJECT_NULL;
+ kern_return_t krc = darwinGetUSBHostDeviceFromLegacyDevice(USBDevice, &IOUSBDeviceNew);
+ if ( krc == KERN_SUCCESS
+ && IOUSBDeviceNew != IO_OBJECT_NULL)
+ {
+ darwinDetermineUSBDeviceStateWorker(pCur, IOUSBDeviceNew);
+ IOObjectRelease(IOUSBDeviceNew);
+ }
+ }
+ else
+ darwinDetermineUSBDeviceStateWorker(pCur, USBDevice);
+ }
+}
+
+
+/**
+ * Enumerate the USB devices returning a FIFO of them.
+ *
+ * @returns Pointer to the head.
+ * USBProxyService::freeDevice is expected to free each of the list elements.
+ */
+PUSBDEVICE DarwinGetUSBDevices(void)
+{
+ AssertReturn(darwinOpenMasterPort(), NULL);
+ //DARWIN_IOKIT_LOG(("DarwinGetUSBDevices\n"));
+
+ /*
+ * Create a matching dictionary for searching for USB Devices in the IOKit.
+ */
+ CFMutableDictionaryRef RefMatchingDict = IOServiceMatching(kIOUSBDeviceClassName);
+ AssertReturn(RefMatchingDict, NULL);
+
+ /*
+ * Perform the search and get a collection of USB Device back.
+ */
+ io_iterator_t USBDevices = IO_OBJECT_NULL;
+ IOReturn rc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &USBDevices);
+ AssertMsgReturn(rc == kIOReturnSuccess, ("rc=%d\n", rc), NULL);
+ RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */
+
+ /*
+ * Enumerate the USB Devices.
+ */
+ PUSBDEVICE pHead = NULL;
+ PUSBDEVICE pTail = NULL;
+ unsigned i = 0;
+ io_object_t USBDevice;
+ while ((USBDevice = IOIteratorNext(USBDevices)) != IO_OBJECT_NULL)
+ {
+ DARWIN_IOKIT_DUMP_OBJ(USBDevice);
+
+ /*
+ * Query the device properties from the registry.
+ *
+ * We could alternatively use the device and such, but that will be
+ * slower and we would have to resort to the registry for the three
+ * string anyway.
+ */
+ CFMutableDictionaryRef PropsRef = 0;
+ kern_return_t krc = IORegistryEntryCreateCFProperties(USBDevice, &PropsRef, kCFAllocatorDefault, kNilOptions);
+ if (krc == KERN_SUCCESS)
+ {
+ bool fOk = false;
+ PUSBDEVICE pCur = (PUSBDEVICE)RTMemAllocZ(sizeof(*pCur));
+ do /* loop for breaking out of on failure. */
+ {
+ AssertBreak(pCur);
+
+ /*
+ * Mandatory
+ */
+ pCur->bcdUSB = 0; /* we've no idea. */
+ pCur->enmState = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE; /* just a default, we'll try harder in a bit. */
+
+ /* Skip hubs. On 10.11 beta 3, the root hub simulations does not have a USBDeviceClass property, so
+ simply ignore failures to retrieve it. */
+ if (!darwinDictGetU8(PropsRef, CFSTR(kUSBDeviceClass), &pCur->bDeviceClass))
+ {
+#ifdef VBOX_STRICT
+ char szTmp[80];
+ Assert( darwinDictGetString(PropsRef, CFSTR("IOClassNameOverride"), szTmp, sizeof(szTmp))
+ && strcmp(szTmp, "IOUSBRootHubDevice") == 0);
+#endif
+ break;
+ }
+ if (pCur->bDeviceClass == 0x09 /* hub, find a define! */)
+ break;
+ AssertBreak(darwinDictGetU8(PropsRef, CFSTR(kUSBDeviceSubClass), &pCur->bDeviceSubClass));
+ AssertBreak(darwinDictGetU8(PropsRef, CFSTR(kUSBDeviceProtocol), &pCur->bDeviceProtocol));
+ AssertBreak(darwinDictGetU16(PropsRef, CFSTR(kUSBVendorID), &pCur->idVendor));
+ AssertBreak(darwinDictGetU16(PropsRef, CFSTR(kUSBProductID), &pCur->idProduct));
+ AssertBreak(darwinDictGetU16(PropsRef, CFSTR(kUSBDeviceReleaseNumber), &pCur->bcdDevice));
+ uint32_t u32LocationId;
+ AssertBreak(darwinDictGetU32(PropsRef, CFSTR(kUSBDevicePropertyLocationID), &u32LocationId));
+ uint64_t u64SessionId;
+ AssertBreak(darwinDictGetU64(PropsRef, CFSTR("sessionID"), &u64SessionId));
+ char szAddress[64];
+ RTStrPrintf(szAddress, sizeof(szAddress), "p=0x%04RX16;v=0x%04RX16;s=0x%016RX64;l=0x%08RX32",
+ pCur->idProduct, pCur->idVendor, u64SessionId, u32LocationId);
+ pCur->pszAddress = RTStrDup(szAddress);
+ AssertBreak(pCur->pszAddress);
+ pCur->bBus = u32LocationId >> 24;
+ darwinDictGetU8(PropsRef, CFSTR("PortNum"), &pCur->bPort); /* Not present in 10.11 beta 3, so ignore failure. (Is set to zero.) */
+ uint8_t bSpeed;
+ AssertBreak(darwinDictGetU8(PropsRef, CFSTR(kUSBDevicePropertySpeed), &bSpeed));
+ Assert(bSpeed <= 3);
+ pCur->enmSpeed = bSpeed == 3 ? USBDEVICESPEED_SUPER
+ : bSpeed == 2 ? USBDEVICESPEED_HIGH
+ : bSpeed == 1 ? USBDEVICESPEED_FULL
+ : bSpeed == 0 ? USBDEVICESPEED_LOW
+ : USBDEVICESPEED_UNKNOWN;
+
+ /*
+ * Optional.
+ * There are some nameless device in the iMac, apply names to them.
+ */
+ darwinDictDupString(PropsRef, CFSTR("USB Vendor Name"), (char **)&pCur->pszManufacturer);
+ if ( !pCur->pszManufacturer
+ && pCur->idVendor == kIOUSBVendorIDAppleComputer)
+ pCur->pszManufacturer = RTStrDup("Apple Computer, Inc.");
+ darwinDictDupString(PropsRef, CFSTR("USB Product Name"), (char **)&pCur->pszProduct);
+ if ( !pCur->pszProduct
+ && pCur->bDeviceClass == 224 /* Wireless */
+ && pCur->bDeviceSubClass == 1 /* Radio Frequency */
+ && pCur->bDeviceProtocol == 1 /* Bluetooth */)
+ pCur->pszProduct = RTStrDup("Bluetooth");
+ darwinDictDupString(PropsRef, CFSTR("USB Serial Number"), (char **)&pCur->pszSerialNumber);
+
+ pCur->pszBackend = RTStrDup("host");
+ AssertBreak(pCur->pszBackend);
+
+#if 0 /* leave the remainder as zero for now. */
+ /*
+ * Create a plugin interface for the service and query its USB Device interface.
+ */
+ SInt32 Score = 0;
+ IOCFPlugInInterface **ppPlugInInterface = NULL;
+ rc = IOCreatePlugInInterfaceForService(USBDevice, kIOUSBDeviceUserClientTypeID,
+ kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score);
+ if (rc == kIOReturnSuccess)
+ {
+ IOUSBDeviceInterface245 **ppUSBDevI = NULL;
+ HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface,
+ CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245),
+ (LPVOID *)&ppUSBDevI);
+ rc = IODestroyPlugInInterface(ppPlugInInterface); Assert(rc == kIOReturnSuccess);
+ ppPlugInInterface = NULL;
+ if (hrc == S_OK)
+ {
+ /** @todo enumerate configurations and interfaces if we actually need them. */
+ //IOReturn (*GetNumberOfConfigurations)(void *self, UInt8 *numConfig);
+ //IOReturn (*GetConfigurationDescriptorPtr)(void *self, UInt8 configIndex, IOUSBConfigurationDescriptorPtr *desc);
+ //IOReturn (*CreateInterfaceIterator)(void *self, IOUSBFindInterfaceRequest *req, io_iterator_t *iter);
+ }
+ long cReft = (*ppUSBDeviceInterface)->Release(ppUSBDeviceInterface); MY_CHECK_CREFS(cRefs);
+ }
+#endif
+ /*
+ * Try determine the state.
+ */
+ darwinDeterminUSBDeviceState(pCur, USBDevice, PropsRef);
+
+ /*
+ * We're good. Link the device.
+ */
+ pCur->pPrev = pTail;
+ if (pTail)
+ pTail = pTail->pNext = pCur;
+ else
+ pTail = pHead = pCur;
+ fOk = true;
+ } while (0);
+
+ /* cleanup on failure / skipped device. */
+ if (!fOk && pCur)
+ DarwinFreeUSBDeviceFromIOKit(pCur);
+
+ CFRelease(PropsRef);
+ }
+ else
+ AssertMsgFailed(("krc=%#x\n", krc));
+
+ IOObjectRelease(USBDevice);
+ i++;
+ }
+
+ IOObjectRelease(USBDevices);
+ //DARWIN_IOKIT_LOG_FLUSH();
+
+ /*
+ * Some post processing. There are a couple of things we have to
+ * make 100% sure about, and that is that the (Apple) keyboard
+ * and mouse most likely to be in use by the user aren't available
+ * for capturing. If there is no Apple mouse or keyboard we'll
+ * take the first one from another vendor.
+ */
+ /* As it turns out, the HID service will take all keyboards and mice
+ and we're not currently able to seize them. */
+ PUSBDEVICE pMouse = NULL;
+ PUSBDEVICE pKeyboard = NULL;
+ for (PUSBDEVICE pCur = pHead; pCur; pCur = pCur->pNext)
+ if (pCur->idVendor == kIOUSBVendorIDAppleComputer)
+ {
+ /*
+ * This test is a bit rough, should check device class/protocol but
+ * we don't have interface info yet so that might be a bit tricky.
+ */
+ if ( ( !pKeyboard
+ || pKeyboard->idVendor != kIOUSBVendorIDAppleComputer)
+ && pCur->pszProduct
+ && strstr(pCur->pszProduct, " Keyboard"))
+ pKeyboard = pCur;
+ else if ( ( !pMouse
+ || pMouse->idVendor != kIOUSBVendorIDAppleComputer)
+ && pCur->pszProduct
+ && strstr(pCur->pszProduct, " Mouse")
+ )
+ pMouse = pCur;
+ }
+ else if (!pKeyboard || !pMouse)
+ {
+ if ( pCur->bDeviceClass == 3 /* HID */
+ && pCur->bDeviceProtocol == 1 /* Keyboard */)
+ pKeyboard = pCur;
+ else if ( pCur->bDeviceClass == 3 /* HID */
+ && pCur->bDeviceProtocol == 2 /* Mouse */)
+ pMouse = pCur;
+ /** @todo examin interfaces */
+ }
+
+ if (pKeyboard)
+ pKeyboard->enmState = USBDEVICESTATE_USED_BY_HOST;
+ if (pMouse)
+ pMouse->enmState = USBDEVICESTATE_USED_BY_HOST;
+
+ return pHead;
+}
+
+#endif /* VBOX_WITH_USB */
+
+
+/**
+ * Enumerate the CD, DVD and BlueRay drives returning a FIFO of device name strings.
+ *
+ * @returns Pointer to the head.
+ * The caller is responsible for calling RTMemFree() on each of the nodes.
+ */
+PDARWINDVD DarwinGetDVDDrives(void)
+{
+ AssertReturn(darwinOpenMasterPort(), NULL);
+
+ /*
+ * Create a matching dictionary for searching for CD, DVD and BlueRay services in the IOKit.
+ *
+ * The idea is to find all the devices which are of class IOCDBlockStorageDevice.
+ * CD devices are represented by IOCDBlockStorageDevice class itself, while DVD and BlueRay ones
+ * have it as a parent class.
+ */
+ CFMutableDictionaryRef RefMatchingDict = IOServiceMatching("IOCDBlockStorageDevice");
+ AssertReturn(RefMatchingDict, NULL);
+
+ /*
+ * Perform the search and get a collection of DVD services.
+ */
+ io_iterator_t DVDServices = IO_OBJECT_NULL;
+ IOReturn rc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &DVDServices);
+ AssertMsgReturn(rc == kIOReturnSuccess, ("rc=%d\n", rc), NULL);
+ RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */
+
+ /*
+ * Enumerate the matching services.
+ * (This enumeration must be identical to the one performed in DrvHostBase.cpp.)
+ */
+ PDARWINDVD pHead = NULL;
+ PDARWINDVD pTail = NULL;
+ unsigned i = 0;
+ io_object_t DVDService;
+ while ((DVDService = IOIteratorNext(DVDServices)) != IO_OBJECT_NULL)
+ {
+ DARWIN_IOKIT_DUMP_OBJ(DVDService);
+
+ /*
+ * Get the properties we use to identify the DVD drive.
+ *
+ * While there is a (weird 12 byte) GUID, it isn't persistent
+ * across boots. So, we have to use a combination of the
+ * vendor name and product name properties with an optional
+ * sequence number for identification.
+ */
+ CFMutableDictionaryRef PropsRef = 0;
+ kern_return_t krc = IORegistryEntryCreateCFProperties(DVDService, &PropsRef, kCFAllocatorDefault, kNilOptions);
+ if (krc == KERN_SUCCESS)
+ {
+ /* Get the Device Characteristics dictionary. */
+ CFDictionaryRef DevCharRef = (CFDictionaryRef)CFDictionaryGetValue(PropsRef, CFSTR(kIOPropertyDeviceCharacteristicsKey));
+ if (DevCharRef)
+ {
+ /* The vendor name. */
+ char szVendor[128];
+ char *pszVendor = &szVendor[0];
+ CFTypeRef ValueRef = CFDictionaryGetValue(DevCharRef, CFSTR(kIOPropertyVendorNameKey));
+ if ( ValueRef
+ && CFGetTypeID(ValueRef) == CFStringGetTypeID()
+ && CFStringGetCString((CFStringRef)ValueRef, szVendor, sizeof(szVendor), kCFStringEncodingUTF8))
+ pszVendor = RTStrStrip(szVendor);
+ else
+ *pszVendor = '\0';
+
+ /* The product name. */
+ char szProduct[128];
+ char *pszProduct = &szProduct[0];
+ ValueRef = CFDictionaryGetValue(DevCharRef, CFSTR(kIOPropertyProductNameKey));
+ if ( ValueRef
+ && CFGetTypeID(ValueRef) == CFStringGetTypeID()
+ && CFStringGetCString((CFStringRef)ValueRef, szProduct, sizeof(szProduct), kCFStringEncodingUTF8))
+ pszProduct = RTStrStrip(szProduct);
+ else
+ *pszProduct = '\0';
+
+ /* Construct the name and check for duplicates. */
+ char szName[256 + 32];
+ if (*pszVendor || *pszProduct)
+ {
+ if (*pszVendor && *pszProduct)
+ RTStrPrintf(szName, sizeof(szName), "%s %s", pszVendor, pszProduct);
+ else
+ strcpy(szName, *pszVendor ? pszVendor : pszProduct);
+
+ for (PDARWINDVD pCur = pHead; pCur; pCur = pCur->pNext)
+ {
+ if (!strcmp(szName, pCur->szName))
+ {
+ if (*pszVendor && *pszProduct)
+ RTStrPrintf(szName, sizeof(szName), "%s %s (#%u)", pszVendor, pszProduct, i);
+ else
+ RTStrPrintf(szName, sizeof(szName), "%s (#%u)", *pszVendor ? pszVendor : pszProduct, i);
+ break;
+ }
+ }
+ }
+ else
+ RTStrPrintf(szName, sizeof(szName), "(#%u)", i);
+
+ /* Create the device. */
+ size_t cbName = strlen(szName) + 1;
+ PDARWINDVD pNew = (PDARWINDVD)RTMemAlloc(RT_UOFFSETOF_DYN(DARWINDVD, szName[cbName]));
+ if (pNew)
+ {
+ pNew->pNext = NULL;
+ memcpy(pNew->szName, szName, cbName);
+ if (pTail)
+ pTail = pTail->pNext = pNew;
+ else
+ pTail = pHead = pNew;
+ }
+ }
+ CFRelease(PropsRef);
+ }
+ else
+ AssertMsgFailed(("krc=%#x\n", krc));
+
+ IOObjectRelease(DVDService);
+ i++;
+ }
+
+ IOObjectRelease(DVDServices);
+
+ return pHead;
+}
+
+
+/**
+ * Enumerate the fixed drives (HDDs, SSD, ++) returning a FIFO of device paths
+ * strings and model strings separated by ':'.
+ *
+ * @returns Pointer to the head.
+ * The caller is responsible for calling RTMemFree() on each of the nodes.
+ */
+PDARWINFIXEDDRIVE DarwinGetFixedDrives(void)
+{
+ AssertReturn(darwinOpenMasterPort(), NULL);
+
+ /*
+ * Create a matching dictionary for searching drives in the IOKit.
+ *
+ * The idea is to find all the IOMedia objects with "Whole"="True" which identify the disks but
+ * not partitions.
+ */
+ CFMutableDictionaryRef RefMatchingDict = IOServiceMatching("IOMedia");
+ AssertReturn(RefMatchingDict, NULL);
+ CFDictionaryAddValue(RefMatchingDict, CFSTR(kIOMediaWholeKey), kCFBooleanTrue);
+
+ /*
+ * Perform the search and get a collection of IOMedia objects.
+ */
+ io_iterator_t MediaServices = IO_OBJECT_NULL;
+ IOReturn rc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &MediaServices);
+ AssertMsgReturn(rc == kIOReturnSuccess, ("rc=%d\n", rc), NULL);
+ RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */
+
+ /*
+ * Enumerate the matching services.
+ * (This enumeration must be identical to the one performed in DrvHostBase.cpp.)
+ */
+ PDARWINFIXEDDRIVE pHead = NULL;
+ PDARWINFIXEDDRIVE pTail = NULL;
+ unsigned i = 0;
+ io_object_t MediaService;
+ while ((MediaService = IOIteratorNext(MediaServices)) != IO_OBJECT_NULL)
+ {
+ DARWIN_IOKIT_DUMP_OBJ(MediaService);
+
+ /*
+ * Find the IOMedia parents having the IOBlockStorageDevice type and check they have "device-type" = "Generic".
+ * If the IOMedia object hasn't IOBlockStorageDevices with such device-type in parents the one is not general
+ * disk but either CDROM-like device or some another device which has no interest for the function.
+ */
+
+ /*
+ * Just avoid parents enumeration if the IOMedia is IOCDMedia, i.e. CDROM-like disk
+ */
+ if (IOObjectConformsTo(MediaService, kIOCDMediaClass))
+ {
+ IOObjectRelease(MediaService);
+ continue;
+ }
+
+ bool fIsGenericStorage = false;
+ io_registry_entry_t ChildEntry = MediaService;
+ io_registry_entry_t ParentEntry = IO_OBJECT_NULL;
+ kern_return_t krc = KERN_SUCCESS;
+ while ( !fIsGenericStorage
+ && (krc = IORegistryEntryGetParentEntry(ChildEntry, kIOServicePlane, &ParentEntry)) == KERN_SUCCESS)
+ {
+ if (!IOObjectIsEqualTo(ChildEntry, MediaService))
+ IOObjectRelease(ChildEntry);
+
+ DARWIN_IOKIT_DUMP_OBJ(ParentEntry);
+ if (IOObjectConformsTo(ParentEntry, kIOBlockStorageDeviceClass))
+ {
+ CFTypeRef DeviceTypeValueRef = IORegistryEntryCreateCFProperty(ParentEntry,
+ CFSTR("device-type"),
+ kCFAllocatorDefault, 0);
+ if ( DeviceTypeValueRef
+ && CFGetTypeID(DeviceTypeValueRef) == CFStringGetTypeID()
+ && CFStringCompare((CFStringRef)DeviceTypeValueRef, CFSTR("Generic"),
+ kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ fIsGenericStorage = true;
+
+ if (DeviceTypeValueRef != NULL)
+ CFRelease(DeviceTypeValueRef);
+ }
+ ChildEntry = ParentEntry;
+ }
+ if (ChildEntry != IO_OBJECT_NULL && !IOObjectIsEqualTo(ChildEntry, MediaService))
+ IOObjectRelease(ChildEntry);
+
+ if (!fIsGenericStorage)
+ {
+ IOObjectRelease(MediaService);
+ continue;
+ }
+
+ CFTypeRef DeviceName;
+ DeviceName = IORegistryEntryCreateCFProperty(MediaService,
+ CFSTR(kIOBSDNameKey),
+ kCFAllocatorDefault,0);
+ if (DeviceName)
+ {
+ char szDeviceFilePath[MAXPATHLEN];
+ strcpy(szDeviceFilePath, _PATH_DEV);
+ size_t cchPathSize = strlen(szDeviceFilePath);
+ if (CFStringGetCString((CFStringRef)DeviceName,
+ &szDeviceFilePath[cchPathSize],
+ (CFIndex)(sizeof(szDeviceFilePath) - cchPathSize),
+ kCFStringEncodingUTF8))
+ {
+ PDARWINFIXEDDRIVE pDuplicate = pHead;
+ while (pDuplicate && strcmp(szDeviceFilePath, pDuplicate->szName) != 0)
+ pDuplicate = pDuplicate->pNext;
+ if (pDuplicate == NULL)
+ {
+ /* Get model for the IOMedia object.
+ *
+ * Due to vendor and product property names are different and
+ * depend on interface and device type, the best way to get a drive
+ * model is get IORegistry name for the IOMedia object. Usually,
+ * it takes "<vendor> <product> <revision> Media" form. Noticed,
+ * such naming are used by only IOMedia objects having
+ * "Whole" = True and "BSDName" properties set.
+ */
+ io_name_t szEntryName = { 0 };
+ if ((krc = IORegistryEntryGetName(MediaService, szEntryName)) == KERN_SUCCESS)
+ {
+ /* remove " Media" from the end of the name */
+ char *pszMedia = strrchr(szEntryName, ' ');
+ if ( pszMedia != NULL
+ && (uintptr_t)pszMedia < (uintptr_t)&szEntryName[sizeof(szEntryName)]
+ && strcmp(pszMedia, " Media") == 0)
+ {
+ *pszMedia = '\0';
+ RTStrPurgeEncoding(szEntryName);
+ }
+ }
+ /* Create the device path and model name in form "/device/path:model". */
+ cchPathSize = strlen(szDeviceFilePath);
+ size_t const cchModelSize = strlen(szEntryName);
+ size_t const cbExtra = cchPathSize + 1 + cchModelSize + !!cchModelSize;
+ PDARWINFIXEDDRIVE pNew = (PDARWINFIXEDDRIVE)RTMemAlloc(RT_UOFFSETOF_DYN(DARWINFIXEDDRIVE, szName[cbExtra]));
+ if (pNew)
+ {
+ pNew->pNext = NULL;
+ memcpy(pNew->szName, szDeviceFilePath, cchPathSize + 1);
+ pNew->pszModel = NULL;
+ if (cchModelSize)
+ pNew->pszModel = (const char *)memcpy(&pNew->szName[cchPathSize + 1], szEntryName, cchModelSize + 1);
+
+ if (pTail)
+ pTail = pTail->pNext = pNew;
+ else
+ pTail = pHead = pNew;
+ }
+ }
+ }
+ CFRelease(DeviceName);
+ }
+ IOObjectRelease(MediaService);
+ i++;
+ }
+ IOObjectRelease(MediaServices);
+
+ return pHead;
+}
+
+
+/**
+ * Enumerate the ethernet capable network devices returning a FIFO of them.
+ *
+ * @returns Pointer to the head.
+ */
+PDARWINETHERNIC DarwinGetEthernetControllers(void)
+{
+ AssertReturn(darwinOpenMasterPort(), NULL);
+
+ /*
+ * Create a matching dictionary for searching for ethernet controller
+ * services in the IOKit.
+ *
+ * For some really stupid reason I don't get all the controllers if I look for
+ * objects that are instances of IOEthernetController or its descendants (only
+ * get the AirPort on my mac pro). But fortunately using IOEthernetInterface
+ * seems to work. Weird s**t!
+ */
+ //CFMutableDictionaryRef RefMatchingDict = IOServiceMatching("IOEthernetController"); - this doesn't work :-(
+ CFMutableDictionaryRef RefMatchingDict = IOServiceMatching("IOEthernetInterface");
+ AssertReturn(RefMatchingDict, NULL);
+
+ /*
+ * Perform the search and get a collection of ethernet controller services.
+ */
+ io_iterator_t EtherIfServices = IO_OBJECT_NULL;
+ IOReturn rc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &EtherIfServices);
+ AssertMsgReturn(rc == kIOReturnSuccess, ("rc=%d\n", rc), NULL);
+ RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */
+
+ /*
+ * Get a copy of the current network interfaces from the system configuration service.
+ * We'll use this for looking up the proper interface names.
+ */
+ CFArrayRef IfsRef = SCNetworkInterfaceCopyAll();
+ CFIndex cIfs = IfsRef ? CFArrayGetCount(IfsRef) : 0;
+
+ /*
+ * Get the current preferences and make a copy of the network services so we
+ * can look up the right interface names. The IfsRef is just for fallback.
+ */
+ CFArrayRef ServicesRef = NULL;
+ CFIndex cServices = 0;
+ SCPreferencesRef PrefsRef = SCPreferencesCreate(kCFAllocatorDefault, CFSTR("org.virtualbox.VBoxSVC"), NULL);
+ if (PrefsRef)
+ {
+ SCNetworkSetRef SetRef = SCNetworkSetCopyCurrent(PrefsRef);
+ CFRelease(PrefsRef);
+ if (SetRef)
+ {
+ ServicesRef = SCNetworkSetCopyServices(SetRef);
+ CFRelease(SetRef);
+ cServices = ServicesRef ? CFArrayGetCount(ServicesRef) : 0;
+ }
+ }
+
+ /*
+ * Enumerate the ethernet controller services.
+ */
+ PDARWINETHERNIC pHead = NULL;
+ PDARWINETHERNIC pTail = NULL;
+ io_object_t EtherIfService;
+ while ((EtherIfService = IOIteratorNext(EtherIfServices)) != IO_OBJECT_NULL)
+ {
+ /*
+ * Dig up the parent, meaning the IOEthernetController.
+ */
+ io_object_t EtherNICService;
+ kern_return_t krc = IORegistryEntryGetParentEntry(EtherIfService, kIOServicePlane, &EtherNICService);
+ /*krc = IORegistryEntryGetChildEntry(EtherNICService, kIOServicePlane, &EtherIfService); */
+ if (krc == KERN_SUCCESS)
+ {
+ DARWIN_IOKIT_DUMP_OBJ(EtherNICService);
+ /*
+ * Get the properties we use to identify and name the Ethernet NIC.
+ * We need the both the IOEthernetController and it's IONetworkInterface child.
+ */
+ CFMutableDictionaryRef PropsRef = 0;
+ krc = IORegistryEntryCreateCFProperties(EtherNICService, &PropsRef, kCFAllocatorDefault, kNilOptions);
+ if (krc == KERN_SUCCESS)
+ {
+ CFMutableDictionaryRef IfPropsRef = 0;
+ krc = IORegistryEntryCreateCFProperties(EtherIfService, &IfPropsRef, kCFAllocatorDefault, kNilOptions);
+ if (krc == KERN_SUCCESS)
+ {
+ /*
+ * Gather the required data.
+ * We'll create a UUID from the MAC address and the BSD name.
+ */
+ char szTmp[256];
+ do
+ {
+ /* Check if airport (a bit heuristical - it's com.apple.driver.AirPortBrcm43xx here). */
+ darwinDictGetString(PropsRef, CFSTR("CFBundleIdentifier"), szTmp, sizeof(szTmp));
+ bool fWireless;
+ bool fAirPort = fWireless = strstr(szTmp, ".AirPort") != NULL;
+
+ /* Check if it's USB. */
+ darwinDictGetString(PropsRef, CFSTR("IOProviderClass"), szTmp, sizeof(szTmp));
+ bool fUSB = strstr(szTmp, "USB") != NULL;
+
+
+ /* Is it builtin? */
+ bool fBuiltin;
+ darwinDictGetBool(IfPropsRef, CFSTR("IOBuiltin"), &fBuiltin);
+
+ /* Is it the primary interface */
+ bool fPrimaryIf;
+ darwinDictGetBool(IfPropsRef, CFSTR("IOPrimaryInterface"), &fPrimaryIf);
+
+ /* Get the MAC address. */
+ RTMAC Mac;
+ AssertBreak(darwinDictGetData(PropsRef, CFSTR("IOMACAddress"), &Mac, sizeof(Mac)));
+
+ /* The BSD Name from the interface dictionary. No assert here as the belkin USB-C gadget
+ does not always end up with a BSD name, typically requiring replugging. */
+ char szBSDName[RT_SIZEOFMEMB(DARWINETHERNIC, szBSDName)];
+ if (RT_UNLIKELY(!darwinDictGetString(IfPropsRef, CFSTR("BSD Name"), szBSDName, sizeof(szBSDName))))
+ {
+ LogRelMax(32, ("DarwinGetEthernetControllers: Warning! Failed to get 'BSD Name'; provider class %s\n", szTmp));
+ break;
+ }
+
+ /* Check if it's really wireless. */
+ if ( darwinDictIsPresent(IfPropsRef, CFSTR("IO80211CountryCode"))
+ || darwinDictIsPresent(IfPropsRef, CFSTR("IO80211DriverVersion"))
+ || darwinDictIsPresent(IfPropsRef, CFSTR("IO80211HardwareVersion"))
+ || darwinDictIsPresent(IfPropsRef, CFSTR("IO80211Locale")))
+ fWireless = true;
+ else
+ fAirPort = fWireless = false;
+
+ /** @todo IOPacketFilters / IONetworkFilterGroup? */
+ /*
+ * Create the interface name.
+ *
+ * Note! The ConsoleImpl2.cpp code ASSUMES things about the name. It is also
+ * stored in the VM config files. (really bright idea)
+ */
+ strcpy(szTmp, szBSDName);
+ char *psz = strchr(szTmp, '\0');
+ *psz++ = ':';
+ *psz++ = ' ';
+ size_t cchLeft = sizeof(szTmp) - (size_t)(psz - &szTmp[0]) - (sizeof(" (Wireless)") - 1);
+ bool fFound = false;
+ CFIndex i;
+
+ /* look it up among the current services */
+ for (i = 0; i < cServices; i++)
+ {
+ SCNetworkServiceRef ServiceRef = (SCNetworkServiceRef)CFArrayGetValueAtIndex(ServicesRef, i);
+ SCNetworkInterfaceRef IfRef = SCNetworkServiceGetInterface(ServiceRef);
+ if (IfRef)
+ {
+ CFStringRef BSDNameRef = SCNetworkInterfaceGetBSDName(IfRef);
+ if ( BSDNameRef
+ && CFStringGetCString(BSDNameRef, psz, (CFIndex)cchLeft, kCFStringEncodingUTF8)
+ && !strcmp(psz, szBSDName))
+ {
+ CFStringRef ServiceNameRef = SCNetworkServiceGetName(ServiceRef);
+ if ( ServiceNameRef
+ && CFStringGetCString(ServiceNameRef, psz, (CFIndex)cchLeft, kCFStringEncodingUTF8))
+ {
+ fFound = true;
+ break;
+ }
+ }
+ }
+ }
+ /* Look it up in the interface list. */
+ if (!fFound)
+ for (i = 0; i < cIfs; i++)
+ {
+ SCNetworkInterfaceRef IfRef = (SCNetworkInterfaceRef)CFArrayGetValueAtIndex(IfsRef, i);
+ CFStringRef BSDNameRef = SCNetworkInterfaceGetBSDName(IfRef);
+ if ( BSDNameRef
+ && CFStringGetCString(BSDNameRef, psz, (CFIndex)cchLeft, kCFStringEncodingUTF8)
+ && !strcmp(psz, szBSDName))
+ {
+ CFStringRef DisplayNameRef = SCNetworkInterfaceGetLocalizedDisplayName(IfRef);
+ if ( DisplayNameRef
+ && CFStringGetCString(DisplayNameRef, psz, (CFIndex)cchLeft, kCFStringEncodingUTF8))
+ {
+ fFound = true;
+ break;
+ }
+ }
+ }
+ /* Generate a half plausible name if we for some silly reason didn't find the interface. */
+ if (!fFound)
+ RTStrPrintf(szTmp, sizeof(szTmp), "%s: %s%s(?)",
+ szBSDName,
+ fUSB ? "USB " : "",
+ fWireless ? fAirPort ? "AirPort " : "Wireless" : "Ethernet");
+ /* If we did find it and it's wireless but without "AirPort" or "Wireless", fix it */
+ else if ( fWireless
+ && !strstr(psz, "AirPort")
+ && !strstr(psz, "Wireless"))
+ strcat(szTmp, fAirPort ? " (AirPort)" : " (Wireless)");
+
+ /*
+ * Create the list entry.
+ */
+ DARWIN_IOKIT_LOG(("Found: if=%s mac=%.6Rhxs fWireless=%RTbool fAirPort=%RTbool fBuiltin=%RTbool fPrimaryIf=%RTbool fUSB=%RTbool\n",
+ szBSDName, &Mac, fWireless, fAirPort, fBuiltin, fPrimaryIf, fUSB));
+
+ size_t cchName = strlen(szTmp);
+ PDARWINETHERNIC pNew = (PDARWINETHERNIC)RTMemAlloc(RT_UOFFSETOF_DYN(DARWINETHERNIC, szName[cchName + 1]));
+ if (pNew)
+ {
+ strncpy(pNew->szBSDName, szBSDName, sizeof(pNew->szBSDName)); /* the '\0' padding is intentional! */
+
+ RTUuidClear(&pNew->Uuid);
+ memcpy(&pNew->Uuid, pNew->szBSDName, RT_MIN(sizeof(pNew->szBSDName), sizeof(pNew->Uuid)));
+ pNew->Uuid.Gen.u8ClockSeqHiAndReserved = (pNew->Uuid.Gen.u8ClockSeqHiAndReserved & 0x3f) | 0x80;
+ pNew->Uuid.Gen.u16TimeHiAndVersion = (pNew->Uuid.Gen.u16TimeHiAndVersion & 0x0fff) | 0x4000;
+ pNew->Uuid.Gen.au8Node[0] = Mac.au8[0];
+ pNew->Uuid.Gen.au8Node[1] = Mac.au8[1];
+ pNew->Uuid.Gen.au8Node[2] = Mac.au8[2];
+ pNew->Uuid.Gen.au8Node[3] = Mac.au8[3];
+ pNew->Uuid.Gen.au8Node[4] = Mac.au8[4];
+ pNew->Uuid.Gen.au8Node[5] = Mac.au8[5];
+
+ pNew->Mac = Mac;
+ pNew->fWireless = fWireless;
+ pNew->fAirPort = fAirPort;
+ pNew->fBuiltin = fBuiltin;
+ pNew->fUSB = fUSB;
+ pNew->fPrimaryIf = fPrimaryIf;
+ memcpy(pNew->szName, szTmp, cchName + 1);
+
+ /*
+ * Link it into the list, keep the list sorted by fPrimaryIf and the BSD name.
+ */
+ if (pTail)
+ {
+ PDARWINETHERNIC pPrev = pTail;
+ if (strcmp(pNew->szBSDName, pPrev->szBSDName) < 0)
+ {
+ pPrev = NULL;
+ for (PDARWINETHERNIC pCur = pHead; pCur; pPrev = pCur, pCur = pCur->pNext)
+ if ( (int)pNew->fPrimaryIf - (int)pCur->fPrimaryIf > 0
+ || ( (int)pNew->fPrimaryIf - (int)pCur->fPrimaryIf == 0
+ && strcmp(pNew->szBSDName, pCur->szBSDName) >= 0))
+ break;
+ }
+ if (pPrev)
+ {
+ /* tail or in list. */
+ pNew->pNext = pPrev->pNext;
+ pPrev->pNext = pNew;
+ if (pPrev == pTail)
+ pTail = pNew;
+ }
+ else
+ {
+ /* head */
+ pNew->pNext = pHead;
+ pHead = pNew;
+ }
+ }
+ else
+ {
+ /* empty list */
+ pNew->pNext = NULL;
+ pTail = pHead = pNew;
+ }
+ }
+ } while (0);
+
+ CFRelease(IfPropsRef);
+ }
+ CFRelease(PropsRef);
+ }
+ IOObjectRelease(EtherNICService);
+ }
+ else
+ AssertMsgFailed(("krc=%#x\n", krc));
+ IOObjectRelease(EtherIfService);
+ }
+
+ IOObjectRelease(EtherIfServices);
+ if (ServicesRef)
+ CFRelease(ServicesRef);
+ if (IfsRef)
+ CFRelease(IfsRef);
+ return pHead;
+}
+
+#ifdef STANDALONE_TESTCASE
+/**
+ * This file can optionally be compiled into a testcase, this is the main function.
+ * To build:
+ * g++ -I ../../../../include -D IN_RING3 iokit.cpp ../../../../out/darwin.x86/debug/lib/RuntimeR3.a ../../../../out/darwin.x86/debug/lib/SUPR3.a ../../../../out/darwin.x86/debug/lib/RuntimeR3.a ../../../../out/darwin.x86/debug/lib/VBox-kStuff.a ../../../../out/darwin.x86/debug/lib/RuntimeR3.a -framework CoreFoundation -framework IOKit -framework SystemConfiguration -liconv -D STANDALONE_TESTCASE -o iokit -g && ./iokit
+ */
+int main(int argc, char **argv)
+{
+ RTR3InitExe(argc, &argv, 0);
+
+ if (1)
+ {
+ /*
+ * Network preferences.
+ */
+ RTPrintf("Preferences: Network Services\n");
+ SCPreferencesRef PrefsRef = SCPreferencesCreate(kCFAllocatorDefault, CFSTR("org.virtualbox.VBoxSVC"), NULL);
+ if (PrefsRef)
+ {
+ CFDictionaryRef NetworkServiceRef = (CFDictionaryRef)SCPreferencesGetValue(PrefsRef, kSCPrefNetworkServices);
+ darwinDumpDict(NetworkServiceRef, 4);
+ CFRelease(PrefsRef);
+ }
+ }
+
+ if (1)
+ {
+ /*
+ * Network services interfaces in the current config.
+ */
+ RTPrintf("Preferences: Network Service Interfaces\n");
+ SCPreferencesRef PrefsRef = SCPreferencesCreate(kCFAllocatorDefault, CFSTR("org.virtualbox.VBoxSVC"), NULL);
+ if (PrefsRef)
+ {
+ SCNetworkSetRef SetRef = SCNetworkSetCopyCurrent(PrefsRef);
+ if (SetRef)
+ {
+ CFArrayRef ServicesRef = SCNetworkSetCopyServices(SetRef);
+ CFIndex cServices = CFArrayGetCount(ServicesRef);
+ for (CFIndex i = 0; i < cServices; i++)
+ {
+ SCNetworkServiceRef ServiceRef = (SCNetworkServiceRef)CFArrayGetValueAtIndex(ServicesRef, i);
+ char szServiceName[128] = {0};
+ CFStringGetCString(SCNetworkServiceGetName(ServiceRef), szServiceName, sizeof(szServiceName), kCFStringEncodingUTF8);
+
+ SCNetworkInterfaceRef IfRef = SCNetworkServiceGetInterface(ServiceRef);
+ char szBSDName[16] = {0};
+ if (SCNetworkInterfaceGetBSDName(IfRef))
+ CFStringGetCString(SCNetworkInterfaceGetBSDName(IfRef), szBSDName, sizeof(szBSDName), kCFStringEncodingUTF8);
+ char szDisplayName[128] = {0};
+ if (SCNetworkInterfaceGetLocalizedDisplayName(IfRef))
+ CFStringGetCString(SCNetworkInterfaceGetLocalizedDisplayName(IfRef), szDisplayName, sizeof(szDisplayName), kCFStringEncodingUTF8);
+
+ RTPrintf(" #%u ServiceName=\"%s\" IfBSDName=\"%s\" IfDisplayName=\"%s\"\n",
+ i, szServiceName, szBSDName, szDisplayName);
+ }
+
+ CFRelease(ServicesRef);
+ CFRelease(SetRef);
+ }
+
+ CFRelease(PrefsRef);
+ }
+ }
+
+ if (1)
+ {
+ /*
+ * Network interfaces.
+ */
+ RTPrintf("Preferences: Network Interfaces\n");
+ CFArrayRef IfsRef = SCNetworkInterfaceCopyAll();
+ if (IfsRef)
+ {
+ CFIndex cIfs = CFArrayGetCount(IfsRef);
+ for (CFIndex i = 0; i < cIfs; i++)
+ {
+ SCNetworkInterfaceRef IfRef = (SCNetworkInterfaceRef)CFArrayGetValueAtIndex(IfsRef, i);
+ char szBSDName[16] = {0};
+ if (SCNetworkInterfaceGetBSDName(IfRef))
+ CFStringGetCString(SCNetworkInterfaceGetBSDName(IfRef), szBSDName, sizeof(szBSDName), kCFStringEncodingUTF8);
+ char szDisplayName[128] = {0};
+ if (SCNetworkInterfaceGetLocalizedDisplayName(IfRef))
+ CFStringGetCString(SCNetworkInterfaceGetLocalizedDisplayName(IfRef), szDisplayName, sizeof(szDisplayName), kCFStringEncodingUTF8);
+ RTPrintf(" #%u BSDName=\"%s\" DisplayName=\"%s\"\n",
+ i, szBSDName, szDisplayName);
+ }
+
+ CFRelease(IfsRef);
+ }
+ }
+
+ if (1)
+ {
+ /*
+ * Get and display the ethernet controllers.
+ */
+ RTPrintf("Ethernet controllers:\n");
+ PDARWINETHERNIC pEtherNICs = DarwinGetEthernetControllers();
+ for (PDARWINETHERNIC pCur = pEtherNICs; pCur; pCur = pCur->pNext)
+ {
+ RTPrintf("%s\n", pCur->szName);
+ RTPrintf(" szBSDName=%s\n", pCur->szBSDName);
+ RTPrintf(" UUID=%RTuuid\n", &pCur->Uuid);
+ RTPrintf(" Mac=%.6Rhxs\n", &pCur->Mac);
+ RTPrintf(" fWireless=%RTbool\n", pCur->fWireless);
+ RTPrintf(" fAirPort=%RTbool\n", pCur->fAirPort);
+ RTPrintf(" fBuiltin=%RTbool\n", pCur->fBuiltin);
+ RTPrintf(" fUSB=%RTbool\n", pCur->fUSB);
+ RTPrintf(" fPrimaryIf=%RTbool\n", pCur->fPrimaryIf);
+ }
+ }
+
+
+ return 0;
+}
+#endif
diff --git a/src/VBox/Main/src-server/darwin/iokit.h b/src/VBox/Main/src-server/darwin/iokit.h
new file mode 100644
index 00000000..fca335d7
--- /dev/null
+++ b/src/VBox/Main/src-server/darwin/iokit.h
@@ -0,0 +1,117 @@
+/* $Id: iokit.h $ */
+/** @file
+ * Main - Darwin IOKit Routines.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SRC_src_server_darwin_iokit_h
+#define MAIN_INCLUDED_SRC_src_server_darwin_iokit_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+#include <iprt/types.h>
+#include <iprt/cpp/ministring.h>
+#ifdef VBOX_WITH_USB
+# include <VBox/usb.h>
+#endif
+
+/**
+ * Darwin DVD descriptor as returned by DarwinGetDVDDrives().
+ */
+typedef struct DARWINDVD
+{
+ /** Pointer to the next DVD. */
+ struct DARWINDVD *pNext;
+ /** Variable length name / identifier. */
+ char szName[1];
+} DARWINDVD;
+/** Pointer to a Darwin DVD descriptor. */
+typedef DARWINDVD *PDARWINDVD;
+
+/** Darwin fixed drive (SSD, HDD, ++) descriptor as returned by
+ * DarwinGetFixedDrives(). */
+typedef struct DARWINFIXEDDRIVE
+{
+ /** Pointer to the next DVD. */
+ struct DARWINFIXEDDRIVE *pNext;
+ /** Pointer to the model name, NULL if none.
+ * This points after szName and needs not be freed separately. */
+ const char *pszModel;
+ /** Variable length name / identifier. */
+ char szName[1];
+} DARWINFIXEDDRIVE;
+/** Pointer to a Darwin fixed drive. */
+typedef DARWINFIXEDDRIVE *PDARWINFIXEDDRIVE;
+
+
+/**
+ * Darwin ethernet controller descriptor as returned by DarwinGetEthernetControllers().
+ */
+typedef struct DARWINETHERNIC
+{
+ /** Pointer to the next NIC. */
+ struct DARWINETHERNIC *pNext;
+ /** The BSD name. (like en0)*/
+ char szBSDName[16];
+ /** The fake unique identifier. */
+ RTUUID Uuid;
+ /** The MAC address. */
+ RTMAC Mac;
+ /** Whether it's wireless (true) or wired (false). */
+ bool fWireless;
+ /** Whether it is an AirPort device. */
+ bool fAirPort;
+ /** Whether it's built in or not. */
+ bool fBuiltin;
+ /** Whether it's a USB device or not. */
+ bool fUSB;
+ /** Whether it's the primary interface. */
+ bool fPrimaryIf;
+ /** A variable length descriptive name if possible. */
+ char szName[1];
+} DARWINETHERNIC;
+/** Pointer to a Darwin ethernet controller descriptor. */
+typedef DARWINETHERNIC *PDARWINETHERNIC;
+
+
+/** The run loop mode string used by iokit.cpp when it registers
+ * notifications events. */
+#define VBOX_IOKIT_MODE_STRING "VBoxIOKitMode"
+
+RT_C_DECLS_BEGIN
+#ifdef VBOX_WITH_USB
+void * DarwinSubscribeUSBNotifications(void);
+void DarwinUnsubscribeUSBNotifications(void *pvOpaque);
+PUSBDEVICE DarwinGetUSBDevices(void);
+void DarwinFreeUSBDeviceFromIOKit(PUSBDEVICE pCur);
+int DarwinReEnumerateUSBDevice(PCUSBDEVICE pCur);
+#endif /* VBOX_WITH_USB */
+PDARWINDVD DarwinGetDVDDrives(void);
+PDARWINFIXEDDRIVE DarwinGetFixedDrives(void);
+PDARWINETHERNIC DarwinGetEthernetControllers(void);
+RT_C_DECLS_END
+
+#endif /* !MAIN_INCLUDED_SRC_src_server_darwin_iokit_h */
diff --git a/src/VBox/Main/src-server/freebsd/HostHardwareFreeBSD.cpp b/src/VBox/Main/src-server/freebsd/HostHardwareFreeBSD.cpp
new file mode 100644
index 00000000..5ca5f0ad
--- /dev/null
+++ b/src/VBox/Main/src-server/freebsd/HostHardwareFreeBSD.cpp
@@ -0,0 +1,560 @@
+/* $Id: HostHardwareFreeBSD.cpp $ */
+/** @file
+ * VirtualBox Main - Code for handling hardware detection under FreeBSD, VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN
+#include "HostHardwareLinux.h"
+
+#include <VBox/log.h>
+
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <cam/cam.h>
+#include <cam/cam_ccb.h>
+#include <camlib.h>
+#include <cam/scsi/scsi_pass.h>
+
+#include <vector>
+
+
+/*********************************************************************************************************************************
+* Typedefs and Defines *
+*********************************************************************************************************************************/
+typedef enum DriveType_T
+{
+ Fixed,
+ DVD,
+ Any
+} DriveType_T;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int getDriveInfoFromEnv(const char *pcszVar, DriveInfoList *pList, bool isDVD, bool *pfSuccess) RT_NOTHROW_DEF;
+static int getDriveInfoFromCAM(DriveInfoList *pList, DriveType_T enmDriveType, bool *pfSuccess) RT_NOTHROW_DEF;
+
+
+/** Find the length of a string, ignoring trailing non-ascii or control
+ * characters
+ * @note Code duplicated in HostHardwareLinux.cpp */
+static size_t strLenStripped(const char *pcsz) RT_NOTHROW_DEF
+{
+ size_t cch = 0;
+ for (size_t i = 0; pcsz[i] != '\0'; ++i)
+ if (pcsz[i] > 32 /*space*/ && pcsz[i] < 127 /*delete*/)
+ cch = i;
+ return cch + 1;
+}
+
+
+/**
+ * Initialize the device description for a drive based on vendor and model name
+ * strings.
+ *
+ * @param pcszVendor The raw vendor ID string.
+ * @param pcszModel The raw product ID string.
+ * @param pszDesc Where to store the description string (optional)
+ * @param cbDesc The size of the buffer in @pszDesc
+ *
+ * @note Used for disks as well as DVDs.
+ */
+/* static */
+void dvdCreateDeviceString(const char *pcszVendor, const char *pcszModel, char *pszDesc, size_t cbDesc) RT_NOTHROW_DEF
+{
+ AssertPtrReturnVoid(pcszVendor);
+ AssertPtrReturnVoid(pcszModel);
+ AssertPtrNullReturnVoid(pszDesc);
+ AssertReturnVoid(!pszDesc || cbDesc > 0);
+ size_t cchVendor = strLenStripped(pcszVendor);
+ size_t cchModel = strLenStripped(pcszModel);
+
+ /* Construct the description string as "Vendor Product" */
+ if (pszDesc)
+ {
+ if (cchVendor > 0)
+ RTStrPrintf(pszDesc, cbDesc, "%.*s %s", cchVendor, pcszVendor,
+ cchModel > 0 ? pcszModel : "(unknown drive model)");
+ else
+ RTStrPrintf(pszDesc, cbDesc, "%s", pcszModel);
+ RTStrPurgeEncoding(pszDesc);
+ }
+}
+
+
+int VBoxMainDriveInfo::updateDVDs() RT_NOEXCEPT
+{
+ LogFlowThisFunc(("entered\n"));
+ int rc;
+ try
+ {
+ mDVDList.clear();
+ /* Always allow the user to override our auto-detection using an
+ * environment variable. */
+ bool fSuccess = false; /* Have we succeeded in finding anything yet? */
+ rc = getDriveInfoFromEnv("VBOX_CDROM", &mDVDList, true /* isDVD */, &fSuccess);
+ if (RT_SUCCESS(rc) && !fSuccess)
+ rc = getDriveInfoFromCAM(&mDVDList, DVD, &fSuccess);
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = VERR_NO_MEMORY;
+ }
+ LogFlowThisFunc(("rc=%Rrc\n", rc));
+ return rc;
+}
+
+int VBoxMainDriveInfo::updateFloppies() RT_NOEXCEPT
+{
+ LogFlowThisFunc(("entered\n"));
+ int rc;
+ try
+ {
+ /* Only got the enviornment variable here... */
+ mFloppyList.clear();
+ bool fSuccess = false; /* ignored */
+ rc = getDriveInfoFromEnv("VBOX_FLOPPY", &mFloppyList, false /* isDVD */, &fSuccess);
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = VERR_NO_MEMORY;
+ }
+ LogFlowThisFunc(("rc=%Rrc\n", rc));
+ return rc;
+}
+
+int VBoxMainDriveInfo::updateFixedDrives() RT_NOEXCEPT
+{
+ LogFlowThisFunc(("entered\n"));
+ int rc;
+ try
+ {
+ mFixedDriveList.clear();
+ bool fSuccess = false; /* ignored */
+ rc = getDriveInfoFromCAM(&mFixedDriveList, Fixed, &fSuccess);
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = VERR_NO_MEMORY;
+ }
+ LogFlowThisFunc(("rc=%Rrc\n", rc));
+ return rc;
+}
+
+static void strDeviceStringSCSI(device_match_result *pDevResult, char *pszDesc, size_t cbDesc) RT_NOTHROW_DEF
+{
+ char szVendor[128];
+ cam_strvis((uint8_t *)szVendor, (const uint8_t *)pDevResult->inq_data.vendor,
+ sizeof(pDevResult->inq_data.vendor), sizeof(szVendor));
+ char szProduct[128];
+ cam_strvis((uint8_t *)szProduct, (const uint8_t *)pDevResult->inq_data.product,
+ sizeof(pDevResult->inq_data.product), sizeof(szProduct));
+ dvdCreateDeviceString(szVendor, szProduct, pszDesc, cbDesc);
+}
+
+static void strDeviceStringATA(device_match_result *pDevResult, char *pszDesc, size_t cbDesc) RT_NOTHROW_DEF
+{
+ char szProduct[256];
+ cam_strvis((uint8_t *)szProduct, (const uint8_t *)pDevResult->ident_data.model,
+ sizeof(pDevResult->ident_data.model), sizeof(szProduct));
+ dvdCreateDeviceString("", szProduct, pszDesc, cbDesc);
+}
+
+static void strDeviceStringSEMB(device_match_result *pDevResult, char *pszDesc, size_t cbDesc) RT_NOTHROW_DEF
+{
+ sep_identify_data *pSid = (sep_identify_data *)&pDevResult->ident_data;
+
+ char szVendor[128];
+ cam_strvis((uint8_t *)szVendor, (const uint8_t *)pSid->vendor_id,
+ sizeof(pSid->vendor_id), sizeof(szVendor));
+ char szProduct[128];
+ cam_strvis((uint8_t *)szProduct, (const uint8_t *)pSid->product_id,
+ sizeof(pSid->product_id), sizeof(szProduct));
+ dvdCreateDeviceString(szVendor, szProduct, pszDesc, cbDesc);
+}
+
+static void strDeviceStringMMCSD(device_match_result *pDevResult, char *pszDesc, size_t cbDesc) RT_NOTHROW_DEF
+{
+ struct cam_device *pDev = cam_open_btl(pDevResult->path_id, pDevResult->target_id,
+ pDevResult->target_lun, O_RDWR, NULL);
+ if (pDev == NULL)
+ {
+ Log(("Error while opening drive device. Error: %s\n", cam_errbuf));
+ return;
+ }
+
+ union ccb *pCcb = cam_getccb(pDev);
+ if (pCcb != NULL)
+ {
+ struct mmc_params mmcIdentData;
+ RT_ZERO(mmcIdentData);
+
+ struct ccb_dev_advinfo *pAdvi = &pCcb->cdai;
+ pAdvi->ccb_h.flags = CAM_DIR_IN;
+ pAdvi->ccb_h.func_code = XPT_DEV_ADVINFO;
+ pAdvi->flags = CDAI_FLAG_NONE;
+ pAdvi->buftype = CDAI_TYPE_MMC_PARAMS;
+ pAdvi->bufsiz = sizeof(mmcIdentData);
+ pAdvi->buf = (uint8_t *)&mmcIdentData;
+
+ if (cam_send_ccb(pDev, pCcb) >= 0)
+ {
+ if (strlen((char *)mmcIdentData.model) > 0)
+ dvdCreateDeviceString("", (const char *)mmcIdentData.model, pszDesc, cbDesc);
+ else
+ dvdCreateDeviceString("", mmcIdentData.card_features & CARD_FEATURE_SDIO ? "SDIO card" : "Unknown card",
+ pszDesc, cbDesc);
+ }
+ else
+ Log(("error sending XPT_DEV_ADVINFO CCB\n"));
+
+ cam_freeccb(pCcb);
+ }
+ else
+ Log(("Could not allocate CCB\n"));
+ cam_close_device(pDev);
+}
+
+/** @returns boolean success indicator (true/false). */
+static int nvmeGetCData(struct cam_device *pDev, struct nvme_controller_data *pCData) RT_NOTHROW_DEF
+{
+ bool fSuccess = false;
+ union ccb *pCcb = cam_getccb(pDev);
+ if (pCcb != NULL)
+ {
+ struct ccb_dev_advinfo *pAdvi = &pCcb->cdai;
+ pAdvi->ccb_h.flags = CAM_DIR_IN;
+ pAdvi->ccb_h.func_code = XPT_DEV_ADVINFO;
+ pAdvi->flags = CDAI_FLAG_NONE;
+ pAdvi->buftype = CDAI_TYPE_NVME_CNTRL;
+ pAdvi->bufsiz = sizeof(struct nvme_controller_data);
+ pAdvi->buf = (uint8_t *)pCData;
+ RT_BZERO(pAdvi->buf, pAdvi->bufsiz);
+
+ if (cam_send_ccb(pDev, pCcb) >= 0)
+ {
+ if (pAdvi->ccb_h.status == CAM_REQ_CMP)
+ fSuccess = true;
+ else
+ Log(("Got CAM error %#x\n", pAdvi->ccb_h.status));
+ }
+ else
+ Log(("Error sending XPT_DEV_ADVINFO CC\n"));
+ cam_freeccb(pCcb);
+ }
+ else
+ Log(("Could not allocate CCB\n"));
+ return fSuccess;
+}
+
+static void strDeviceStringNVME(device_match_result *pDevResult, char *pszDesc, size_t cbDesc) RT_NOTHROW_DEF
+{
+ struct cam_device *pDev = cam_open_btl(pDevResult->path_id, pDevResult->target_id,
+ pDevResult->target_lun, O_RDWR, NULL);
+ if (pDev)
+ {
+ struct nvme_controller_data CData;
+ if (nvmeGetCData(pDev, &CData))
+ {
+ char szVendor[128];
+ cam_strvis((uint8_t *)szVendor, CData.mn, sizeof(CData.mn), sizeof(szVendor));
+ char szProduct[128];
+ cam_strvis((uint8_t *)szProduct, CData.fr, sizeof(CData.fr), sizeof(szProduct));
+ dvdCreateDeviceString(szVendor, szProduct, pszDesc, cbDesc);
+ }
+ else
+ Log(("Error while getting NVME drive info\n"));
+ cam_close_device(pDev);
+ }
+ else
+ Log(("Error while opening drive device. Error: %s\n", cam_errbuf));
+}
+
+
+/**
+ * Search for available drives using the CAM layer.
+ *
+ * @returns iprt status code
+ * @param pList the list to append the drives found to
+ * @param enmDriveType search drives of specified type
+ * @param pfSuccess this will be set to true if we found at least one drive
+ * and to false otherwise. Optional.
+ */
+static int getDriveInfoFromCAM(DriveInfoList *pList, DriveType_T enmDriveType, bool *pfSuccess) RT_NOTHROW_DEF
+{
+ RTFILE hFileXpt = NIL_RTFILE;
+ int rc = RTFileOpen(&hFileXpt, "/dev/xpt0", RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ union ccb DeviceCCB;
+ struct dev_match_pattern DeviceMatchPattern;
+ struct dev_match_result *paMatches = NULL;
+
+ RT_ZERO(DeviceCCB);
+ RT_ZERO(DeviceMatchPattern);
+
+ /* We want to get all devices. */
+ DeviceCCB.ccb_h.func_code = XPT_DEV_MATCH;
+ DeviceCCB.ccb_h.path_id = CAM_XPT_PATH_ID;
+ DeviceCCB.ccb_h.target_id = CAM_TARGET_WILDCARD;
+ DeviceCCB.ccb_h.target_lun = CAM_LUN_WILDCARD;
+
+ /* Setup the pattern */
+ DeviceMatchPattern.type = DEV_MATCH_DEVICE;
+ DeviceMatchPattern.pattern.device_pattern.path_id = CAM_XPT_PATH_ID;
+ DeviceMatchPattern.pattern.device_pattern.target_id = CAM_TARGET_WILDCARD;
+ DeviceMatchPattern.pattern.device_pattern.target_lun = CAM_LUN_WILDCARD;
+ DeviceMatchPattern.pattern.device_pattern.flags = DEV_MATCH_INQUIRY;
+
+#if __FreeBSD_version >= 900000
+# define INQ_PAT data.inq_pat
+#else
+ #define INQ_PAT inq_pat
+#endif
+ DeviceMatchPattern.pattern.device_pattern.INQ_PAT.type = enmDriveType == Fixed ? T_DIRECT
+ : enmDriveType == DVD ? T_CDROM : T_ANY;
+ DeviceMatchPattern.pattern.device_pattern.INQ_PAT.media_type = SIP_MEDIA_REMOVABLE | SIP_MEDIA_FIXED;
+ DeviceMatchPattern.pattern.device_pattern.INQ_PAT.vendor[0] = '*'; /* Matches anything */
+ DeviceMatchPattern.pattern.device_pattern.INQ_PAT.product[0] = '*'; /* Matches anything */
+ DeviceMatchPattern.pattern.device_pattern.INQ_PAT.revision[0] = '*'; /* Matches anything */
+#undef INQ_PAT
+ DeviceCCB.cdm.num_patterns = 1;
+ DeviceCCB.cdm.pattern_buf_len = sizeof(struct dev_match_result);
+ DeviceCCB.cdm.patterns = &DeviceMatchPattern;
+
+ /*
+ * Allocate the buffer holding the matches.
+ * We will allocate for 10 results and call
+ * CAM multiple times if we have more results.
+ */
+ paMatches = (struct dev_match_result *)RTMemAllocZ(10 * sizeof(struct dev_match_result));
+ if (paMatches)
+ {
+ DeviceCCB.cdm.num_matches = 0;
+ DeviceCCB.cdm.match_buf_len = 10 * sizeof(struct dev_match_result);
+ DeviceCCB.cdm.matches = paMatches;
+
+ do
+ {
+ rc = RTFileIoCtl(hFileXpt, CAMIOCOMMAND, &DeviceCCB, sizeof(union ccb), NULL);
+ if (RT_FAILURE(rc))
+ {
+ Log(("Error while querying available CD/DVD devices rc=%Rrc\n", rc));
+ break;
+ }
+
+ for (unsigned i = 0; i < DeviceCCB.cdm.num_matches; i++)
+ {
+ if (paMatches[i].type == DEV_MATCH_DEVICE)
+ {
+ /*
+ * The result list can contain some empty entries with DEV_RESULT_UNCONFIGURED
+ * flag set, e.g. in case of T_DIRECT. Ignore them.
+ */
+ if ( (paMatches[i].result.device_result.flags & DEV_RESULT_UNCONFIGURED)
+ == DEV_RESULT_UNCONFIGURED)
+ continue;
+
+ /* We have the drive now but need the appropriate device node */
+ struct device_match_result *pDevResult = &paMatches[i].result.device_result;
+ union ccb PeriphCCB;
+ struct dev_match_pattern PeriphMatchPattern;
+ struct dev_match_result aPeriphMatches[2];
+ struct periph_match_result *pPeriphResult = NULL;
+ unsigned iPeriphMatch = 0;
+
+ RT_ZERO(PeriphCCB);
+ RT_ZERO(PeriphMatchPattern);
+ RT_ZERO(aPeriphMatches);
+
+ /* This time we only want the specific nodes for the device. */
+ PeriphCCB.ccb_h.func_code = XPT_DEV_MATCH;
+ PeriphCCB.ccb_h.path_id = paMatches[i].result.device_result.path_id;
+ PeriphCCB.ccb_h.target_id = paMatches[i].result.device_result.target_id;
+ PeriphCCB.ccb_h.target_lun = paMatches[i].result.device_result.target_lun;
+
+ /* Setup the pattern */
+ PeriphMatchPattern.type = DEV_MATCH_PERIPH;
+ PeriphMatchPattern.pattern.periph_pattern.path_id = paMatches[i].result.device_result.path_id;
+ PeriphMatchPattern.pattern.periph_pattern.target_id = paMatches[i].result.device_result.target_id;
+ PeriphMatchPattern.pattern.periph_pattern.target_lun = paMatches[i].result.device_result.target_lun;
+ PeriphMatchPattern.pattern.periph_pattern.flags = (periph_pattern_flags)( PERIPH_MATCH_PATH
+ | PERIPH_MATCH_TARGET
+ | PERIPH_MATCH_LUN);
+ PeriphCCB.cdm.num_patterns = 1;
+ PeriphCCB.cdm.pattern_buf_len = sizeof(struct dev_match_result);
+ PeriphCCB.cdm.patterns = &PeriphMatchPattern;
+ PeriphCCB.cdm.num_matches = 0;
+ PeriphCCB.cdm.match_buf_len = sizeof(aPeriphMatches);
+ PeriphCCB.cdm.matches = aPeriphMatches;
+
+ do
+ {
+ rc = RTFileIoCtl(hFileXpt, CAMIOCOMMAND, &PeriphCCB, sizeof(union ccb), NULL);
+ if (RT_FAILURE(rc))
+ {
+ Log(("Error while querying available periph devices rc=%Rrc\n", rc));
+ break;
+ }
+
+ for (iPeriphMatch = 0; iPeriphMatch < PeriphCCB.cdm.num_matches; iPeriphMatch++)
+ {
+ /* Ignore "passthrough mode" paths */
+ if ( aPeriphMatches[iPeriphMatch].type == DEV_MATCH_PERIPH
+ && strcmp(aPeriphMatches[iPeriphMatch].result.periph_result.periph_name, "pass"))
+ {
+ pPeriphResult = &aPeriphMatches[iPeriphMatch].result.periph_result;
+ break; /* We found the periph device */
+ }
+ }
+
+ if (iPeriphMatch < PeriphCCB.cdm.num_matches)
+ break;
+
+ } while ( DeviceCCB.ccb_h.status == CAM_REQ_CMP
+ && DeviceCCB.cdm.status == CAM_DEV_MATCH_MORE);
+
+ if (pPeriphResult)
+ {
+ char szPath[RTPATH_MAX];
+ RTStrPrintf(szPath, sizeof(szPath), "/dev/%s%d",
+ pPeriphResult->periph_name, pPeriphResult->unit_number);
+
+ char szDesc[256] = { 0 };
+ switch (pDevResult->protocol)
+ {
+ case PROTO_SCSI: strDeviceStringSCSI( pDevResult, szDesc, sizeof(szDesc)); break;
+ case PROTO_ATA: strDeviceStringATA( pDevResult, szDesc, sizeof(szDesc)); break;
+ case PROTO_MMCSD: strDeviceStringMMCSD(pDevResult, szDesc, sizeof(szDesc)); break;
+ case PROTO_SEMB: strDeviceStringSEMB( pDevResult, szDesc, sizeof(szDesc)); break;
+ case PROTO_NVME: strDeviceStringNVME( pDevResult, szDesc, sizeof(szDesc)); break;
+ default: break;
+ }
+
+ try
+ {
+ pList->push_back(DriveInfo(szPath, "", szDesc));
+ }
+ catch (std::bad_alloc &)
+ {
+ pList->clear();
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ if (pfSuccess)
+ *pfSuccess = true;
+ }
+ }
+ }
+ } while ( DeviceCCB.ccb_h.status == CAM_REQ_CMP
+ && DeviceCCB.cdm.status == CAM_DEV_MATCH_MORE
+ && RT_SUCCESS(rc));
+
+ RTMemFree(paMatches);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTFileClose(hFileXpt);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Extract the names of drives from an environment variable and add them to a
+ * list if they are valid.
+ *
+ * @returns iprt status code
+ * @param pcszVar the name of the environment variable. The variable
+ * value should be a list of device node names, separated
+ * by ':' characters.
+ * @param pList the list to append the drives found to
+ * @param isDVD are we looking for DVD drives or for floppies?
+ * @param pfSuccess this will be set to true if we found at least one drive
+ * and to false otherwise. Optional.
+ *
+ * @note This is duplicated in HostHardwareLinux.cpp.
+ */
+static int getDriveInfoFromEnv(const char *pcszVar, DriveInfoList *pList, bool isDVD, bool *pfSuccess) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pcszVar, VERR_INVALID_POINTER);
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER);
+ LogFlowFunc(("pcszVar=%s, pList=%p, isDVD=%d, pfSuccess=%p\n", pcszVar, pList, isDVD, pfSuccess));
+ int rc = VINF_SUCCESS;
+ bool success = false;
+ char *pszFreeMe = RTEnvDupEx(RTENV_DEFAULT, pcszVar);
+
+ try
+ {
+ char *pszCurrent = pszFreeMe;
+ while (pszCurrent && *pszCurrent != '\0')
+ {
+ char *pszNext = strchr(pszCurrent, ':');
+ if (pszNext)
+ *pszNext++ = '\0';
+
+ char szReal[RTPATH_MAX];
+ char szDesc[1] = "", szUdi[1] = ""; /* differs on freebsd because no devValidateDevice */
+ if ( RT_SUCCESS(RTPathReal(pszCurrent, szReal, sizeof(szReal)))
+ /*&& devValidateDevice(szReal, isDVD, NULL, szDesc, sizeof(szDesc), szUdi, sizeof(szUdi)) - linux only */)
+ {
+ pList->push_back(DriveInfo(szReal, szUdi, szDesc));
+ success = true;
+ }
+ pszCurrent = pszNext;
+ }
+ if (pfSuccess != NULL)
+ *pfSuccess = success;
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = VERR_NO_MEMORY;
+ }
+ RTStrFree(pszFreeMe);
+ LogFlowFunc(("rc=%Rrc, success=%d\n", rc, success));
+ return rc;
+}
+
diff --git a/src/VBox/Main/src-server/freebsd/Makefile.kup b/src/VBox/Main/src-server/freebsd/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-server/freebsd/Makefile.kup
diff --git a/src/VBox/Main/src-server/freebsd/NetIf-freebsd.cpp b/src/VBox/Main/src-server/freebsd/NetIf-freebsd.cpp
new file mode 100644
index 00000000..5ce72ff8
--- /dev/null
+++ b/src/VBox/Main/src-server/freebsd/NetIf-freebsd.cpp
@@ -0,0 +1,460 @@
+/* $Id: NetIf-freebsd.cpp $ */
+/** @file
+ * Main - NetIfList, FreeBSD implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * Original (C) 2009 Fredrik Lindberg <fli@shapeshifter.se>. Contributed
+ * to VirtualBox under the MIT license by the author.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_HOST
+#include <sys/types.h>
+
+#include <sys/sysctl.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net80211/ieee80211_ioctl.h>
+
+#include <net/route.h>
+/*
+ * route.h includes net/radix.h which for some reason defines Free as a wrapper
+ * around free. This collides with Free defined in xpcom/include/nsIMemory.h
+ * Undefine it and hope for the best
+ */
+#undef Free
+
+#include <net/if_dl.h>
+#include <netinet/in.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <list>
+
+#include "HostNetworkInterfaceImpl.h"
+#include "netif.h"
+#include "LoggingNew.h"
+
+#define ROUNDUP(a) \
+ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
+#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
+
+void extractAddresses(int iAddrMask, caddr_t cp, caddr_t cplim, struct sockaddr **pAddresses)
+{
+ struct sockaddr *sa;
+
+ for (int i = 0; i < RTAX_MAX && cp < cplim; i++) {
+ if (!(iAddrMask & (1 << i)))
+ continue;
+
+ sa = (struct sockaddr *)cp;
+
+ pAddresses[i] = sa;
+
+ ADVANCE(cp, sa);
+ }
+}
+
+static int getDefaultIfaceIndex(unsigned short *pu16Index, int family)
+{
+ size_t cbNeeded;
+ char *pBuf, *pNext;
+ int aiMib[6];
+ struct sockaddr *addresses[RTAX_MAX];
+ aiMib[0] = CTL_NET;
+ aiMib[1] = PF_ROUTE;
+ aiMib[2] = 0;
+ aiMib[3] = family; /* address family */
+ aiMib[4] = NET_RT_DUMP;
+ aiMib[5] = 0;
+
+ if (sysctl(aiMib, 6, NULL, &cbNeeded, NULL, 0) < 0)
+ {
+ Log(("getDefaultIfaceIndex: Failed to get estimate for list size (errno=%d).\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+ if ((pBuf = (char*)malloc(cbNeeded)) == NULL)
+ return VERR_NO_MEMORY;
+ if (sysctl(aiMib, 6, pBuf, &cbNeeded, NULL, 0) < 0)
+ {
+ free(pBuf);
+ Log(("getDefaultIfaceIndex: Failed to retrieve interface table (errno=%d).\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+
+ char *pEnd = pBuf + cbNeeded;
+ struct rt_msghdr *pRtMsg;
+ for (pNext = pBuf; pNext < pEnd; pNext += pRtMsg->rtm_msglen)
+ {
+ pRtMsg = (struct rt_msghdr *)pNext;
+
+ if (pRtMsg->rtm_type != RTM_GET)
+ {
+ Log(("getDefaultIfaceIndex: Got message %u while expecting %u.\n",
+ pRtMsg->rtm_type, RTM_GET));
+ //rc = VERR_INTERNAL_ERROR;
+ continue;
+ }
+ if ((char*)(pRtMsg + 1) < pEnd)
+ {
+ /* Extract addresses from the message. */
+ extractAddresses(pRtMsg->rtm_addrs, (char *)(pRtMsg + 1),
+ pRtMsg->rtm_msglen + (char *)pRtMsg, addresses);
+ if ((pRtMsg->rtm_addrs & RTA_DST))
+ {
+ if (addresses[RTAX_DST]->sa_family != AF_INET)
+ continue;
+ struct sockaddr_in *addr = (struct sockaddr_in *)addresses[RTAX_DST];
+ struct sockaddr_in *mask = (struct sockaddr_in *)addresses[RTAX_NETMASK];
+ if ((addr->sin_addr.s_addr == INADDR_ANY) &&
+ mask &&
+ (ntohl(mask->sin_addr.s_addr) == 0L ||
+ mask->sin_len == 0))
+ {
+ *pu16Index = pRtMsg->rtm_index;
+ free(pBuf);
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ free(pBuf);
+ return VERR_INTERNAL_ERROR;
+
+}
+
+void extractAddressesToNetInfo(int iAddrMask, caddr_t cp, caddr_t cplim, PNETIFINFO pInfo)
+{
+ struct sockaddr *addresses[RTAX_MAX];
+
+ extractAddresses(iAddrMask, cp, cplim, addresses);
+ switch (addresses[RTAX_IFA]->sa_family)
+ {
+ case AF_INET:
+ if (!pInfo->IPAddress.u)
+ {
+ pInfo->IPAddress.u = ((struct sockaddr_in *)addresses[RTAX_IFA])->sin_addr.s_addr;
+ pInfo->IPNetMask.u = ((struct sockaddr_in *)addresses[RTAX_NETMASK])->sin_addr.s_addr;
+ }
+ break;
+ case AF_INET6:
+ if (!pInfo->IPv6Address.s.Lo && !pInfo->IPv6Address.s.Hi)
+ {
+ memcpy(pInfo->IPv6Address.au8,
+ ((struct sockaddr_in6 *)addresses[RTAX_IFA])->sin6_addr.__u6_addr.__u6_addr8,
+ sizeof(pInfo->IPv6Address));
+ memcpy(pInfo->IPv6NetMask.au8,
+ ((struct sockaddr_in6 *)addresses[RTAX_NETMASK])->sin6_addr.__u6_addr.__u6_addr8,
+ sizeof(pInfo->IPv6NetMask));
+ }
+ break;
+ default:
+ Log(("NetIfList: Unsupported address family: %u\n", addresses[RTAX_IFA]->sa_family));
+ break;
+ }
+}
+
+
+static bool isWireless(const char *pszName)
+{
+ bool fWireless = false;
+ int iSock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (iSock >= 0)
+ {
+ struct ieee80211req WReq;
+ uint8_t abData[32];
+
+ RT_ZERO(WReq);
+ strncpy(WReq.i_name, pszName, sizeof(WReq.i_name));
+ WReq.i_type = IEEE80211_IOC_SSID;
+ WReq.i_val = -1;
+ WReq.i_data = abData;
+ WReq.i_len = sizeof(abData);
+
+ fWireless = ioctl(iSock, SIOCG80211, &WReq) >= 0;
+ close(iSock);
+ }
+
+ return fWireless;
+}
+
+int NetIfList(std::list <ComObjPtr<HostNetworkInterface> > &list)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbNeeded;
+ char *pBuf, *pNext;
+ int aiMib[6];
+ unsigned short u16DefaultIface = 0; /* shut up gcc. */
+ bool fDefaultIfaceExistent = true;
+
+ /* Get the index of the interface associated with default route. */
+ rc = getDefaultIfaceIndex(&u16DefaultIface, PF_INET);
+ if (RT_FAILURE(rc))
+ {
+ fDefaultIfaceExistent = false;
+ rc = VINF_SUCCESS;
+ }
+
+ aiMib[0] = CTL_NET;
+ aiMib[1] = PF_ROUTE;
+ aiMib[2] = 0;
+ aiMib[3] = 0; /* address family */
+ aiMib[4] = NET_RT_IFLIST;
+ aiMib[5] = 0;
+
+ if (sysctl(aiMib, 6, NULL, &cbNeeded, NULL, 0) < 0)
+ {
+ Log(("NetIfList: Failed to get estimate for list size (errno=%d).\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+ if ((pBuf = (char*)malloc(cbNeeded)) == NULL)
+ return VERR_NO_MEMORY;
+ if (sysctl(aiMib, 6, pBuf, &cbNeeded, NULL, 0) < 0)
+ {
+ free(pBuf);
+ Log(("NetIfList: Failed to retrieve interface table (errno=%d).\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+
+ int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sock < 0)
+ {
+ free(pBuf);
+ Log(("NetIfList: socket() -> %d\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+
+ char *pEnd = pBuf + cbNeeded;
+ for (pNext = pBuf; pNext < pEnd;)
+ {
+ struct if_msghdr *pIfMsg = (struct if_msghdr *)pNext;
+
+ if (pIfMsg->ifm_type != RTM_IFINFO)
+ {
+ Log(("NetIfList: Got message %u while expecting %u.\n",
+ pIfMsg->ifm_type, RTM_IFINFO));
+ rc = VERR_INTERNAL_ERROR;
+ break;
+ }
+ struct sockaddr_dl *pSdl = (struct sockaddr_dl *)(pIfMsg + 1);
+
+ size_t cbNameLen = pSdl->sdl_nlen + 1;
+ PNETIFINFO pNew = (PNETIFINFO)RTMemAllocZ(RT_UOFFSETOF_DYN(NETIFINFO, szName[cbNameLen]));
+ if (!pNew)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ memcpy(pNew->MACAddress.au8, LLADDR(pSdl), sizeof(pNew->MACAddress.au8));
+ pNew->enmMediumType = NETIF_T_ETHERNET;
+ Assert(sizeof(pNew->szShortName) >= cbNameLen);
+ strlcpy(pNew->szShortName, pSdl->sdl_data, cbNameLen);
+ strlcpy(pNew->szName, pSdl->sdl_data, cbNameLen);
+ /* Generate UUID from name and MAC address. */
+ RTUUID uuid;
+ RTUuidClear(&uuid);
+ memcpy(&uuid, pNew->szShortName, RT_MIN(cbNameLen, sizeof(uuid)));
+ uuid.Gen.u8ClockSeqHiAndReserved = (uuid.Gen.u8ClockSeqHiAndReserved & 0x3f) | 0x80;
+ uuid.Gen.u16TimeHiAndVersion = (uuid.Gen.u16TimeHiAndVersion & 0x0fff) | 0x4000;
+ memcpy(uuid.Gen.au8Node, pNew->MACAddress.au8, sizeof(uuid.Gen.au8Node));
+ pNew->Uuid = uuid;
+
+ pNext += pIfMsg->ifm_msglen;
+ while (pNext < pEnd)
+ {
+ struct ifa_msghdr *pIfAddrMsg = (struct ifa_msghdr *)pNext;
+
+ if (pIfAddrMsg->ifam_type != RTM_NEWADDR)
+ break;
+ extractAddressesToNetInfo(pIfAddrMsg->ifam_addrs,
+ (char *)(pIfAddrMsg + 1),
+ pIfAddrMsg->ifam_msglen + (char *)pIfAddrMsg,
+ pNew);
+ pNext += pIfAddrMsg->ifam_msglen;
+ }
+
+ if (pSdl->sdl_type == IFT_ETHER || pSdl->sdl_type == IFT_L2VLAN)
+ {
+ struct ifreq IfReq;
+ RTStrCopy(IfReq.ifr_name, sizeof(IfReq.ifr_name), pNew->szShortName);
+ if (ioctl(sock, SIOCGIFFLAGS, &IfReq) < 0)
+ {
+ Log(("NetIfList: ioctl(SIOCGIFFLAGS) -> %d\n", errno));
+ pNew->enmStatus = NETIF_S_UNKNOWN;
+ }
+ else
+ pNew->enmStatus = (IfReq.ifr_flags & IFF_UP) ? NETIF_S_UP : NETIF_S_DOWN;
+
+ HostNetworkInterfaceType_T enmType;
+ if (strncmp(pNew->szName, RT_STR_TUPLE("vboxnet")))
+ enmType = HostNetworkInterfaceType_Bridged;
+ else
+ enmType = HostNetworkInterfaceType_HostOnly;
+
+ pNew->fWireless = isWireless(pNew->szName);
+
+ ComObjPtr<HostNetworkInterface> IfObj;
+ IfObj.createObject();
+ if (SUCCEEDED(IfObj->init(Bstr(pNew->szName), enmType, pNew)))
+ {
+ /* Make sure the default interface gets to the beginning. */
+ if ( fDefaultIfaceExistent
+ && pIfMsg->ifm_index == u16DefaultIface)
+ list.push_front(IfObj);
+ else
+ list.push_back(IfObj);
+ }
+ }
+ RTMemFree(pNew);
+ }
+
+ close(sock);
+ free(pBuf);
+ return rc;
+
+
+}
+
+int NetIfGetConfigByName(PNETIFINFO pInfo)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbNeeded;
+ char *pBuf, *pNext;
+ int aiMib[6];
+
+ aiMib[0] = CTL_NET;
+ aiMib[1] = PF_ROUTE;
+ aiMib[2] = 0;
+ aiMib[3] = 0; /* address family */
+ aiMib[4] = NET_RT_IFLIST;
+ aiMib[5] = 0;
+
+ if (sysctl(aiMib, 6, NULL, &cbNeeded, NULL, 0) < 0)
+ {
+ Log(("NetIfList: Failed to get estimate for list size (errno=%d).\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+ if ((pBuf = (char*)malloc(cbNeeded)) == NULL)
+ return VERR_NO_MEMORY;
+ if (sysctl(aiMib, 6, pBuf, &cbNeeded, NULL, 0) < 0)
+ {
+ free(pBuf);
+ Log(("NetIfList: Failed to retrieve interface table (errno=%d).\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+
+ int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sock < 0)
+ {
+ free(pBuf);
+ Log(("NetIfList: socket() -> %d\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+
+ char *pEnd = pBuf + cbNeeded;
+ for (pNext = pBuf; pNext < pEnd;)
+ {
+ struct if_msghdr *pIfMsg = (struct if_msghdr *)pNext;
+
+ if (pIfMsg->ifm_type != RTM_IFINFO)
+ {
+ Log(("NetIfList: Got message %u while expecting %u.\n",
+ pIfMsg->ifm_type, RTM_IFINFO));
+ rc = VERR_INTERNAL_ERROR;
+ break;
+ }
+ struct sockaddr_dl *pSdl = (struct sockaddr_dl *)(pIfMsg + 1);
+
+ bool fSkip = !!strcmp(pInfo->szShortName, pSdl->sdl_data);
+
+ pNext += pIfMsg->ifm_msglen;
+ while (pNext < pEnd)
+ {
+ struct ifa_msghdr *pIfAddrMsg = (struct ifa_msghdr *)pNext;
+
+ if (pIfAddrMsg->ifam_type != RTM_NEWADDR)
+ break;
+ if (!fSkip)
+ extractAddressesToNetInfo(pIfAddrMsg->ifam_addrs,
+ (char *)(pIfAddrMsg + 1),
+ pIfAddrMsg->ifam_msglen + (char *)pIfAddrMsg,
+ pInfo);
+ pNext += pIfAddrMsg->ifam_msglen;
+ }
+
+ if (!fSkip && (pSdl->sdl_type == IFT_ETHER || pSdl->sdl_type == IFT_L2VLAN))
+ {
+ size_t cbNameLen = pSdl->sdl_nlen + 1;
+ memcpy(pInfo->MACAddress.au8, LLADDR(pSdl), sizeof(pInfo->MACAddress.au8));
+ pInfo->enmMediumType = NETIF_T_ETHERNET;
+ /* Generate UUID from name and MAC address. */
+ RTUUID uuid;
+ RTUuidClear(&uuid);
+ memcpy(&uuid, pInfo->szShortName, RT_MIN(cbNameLen, sizeof(uuid)));
+ uuid.Gen.u8ClockSeqHiAndReserved = (uuid.Gen.u8ClockSeqHiAndReserved & 0x3f) | 0x80;
+ uuid.Gen.u16TimeHiAndVersion = (uuid.Gen.u16TimeHiAndVersion & 0x0fff) | 0x4000;
+ memcpy(uuid.Gen.au8Node, pInfo->MACAddress.au8, sizeof(uuid.Gen.au8Node));
+ pInfo->Uuid = uuid;
+
+ struct ifreq IfReq;
+ RTStrCopy(IfReq.ifr_name, sizeof(IfReq.ifr_name), pInfo->szShortName);
+ if (ioctl(sock, SIOCGIFFLAGS, &IfReq) < 0)
+ {
+ Log(("NetIfList: ioctl(SIOCGIFFLAGS) -> %d\n", errno));
+ pInfo->enmStatus = NETIF_S_UNKNOWN;
+ }
+ else
+ pInfo->enmStatus = (IfReq.ifr_flags & IFF_UP) ? NETIF_S_UP : NETIF_S_DOWN;
+
+ return VINF_SUCCESS;
+ }
+ }
+ close(sock);
+ free(pBuf);
+ return rc;
+}
+
+/**
+ * Retrieve the physical link speed in megabits per second. If the interface is
+ * not up or otherwise unavailable the zero speed is returned.
+ *
+ * @returns VBox status code.
+ *
+ * @param pcszIfName Interface name.
+ * @param puMbits Where to store the link speed.
+ */
+int NetIfGetLinkSpeed(const char * /*pcszIfName*/, uint32_t * /*puMbits*/)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
diff --git a/src/VBox/Main/src-server/freebsd/PerformanceFreeBSD.cpp b/src/VBox/Main/src-server/freebsd/PerformanceFreeBSD.cpp
new file mode 100644
index 00000000..9c20fa75
--- /dev/null
+++ b/src/VBox/Main/src-server/freebsd/PerformanceFreeBSD.cpp
@@ -0,0 +1,128 @@
+/* $Id: PerformanceFreeBSD.cpp $ */
+/** @file
+ * VirtualBox Performance Collector, FreeBSD Specialization.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include "Performance.h"
+
+namespace pm {
+
+class CollectorFreeBSD : public CollectorHAL
+{
+public:
+ virtual int getHostCpuLoad(ULONG *user, ULONG *kernel, ULONG *idle);
+ virtual int getHostCpuMHz(ULONG *mhz);
+ virtual int getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available);
+ virtual int getProcessCpuLoad(RTPROCESS process, ULONG *user, ULONG *kernel);
+ virtual int getProcessMemoryUsage(RTPROCESS process, ULONG *used);
+};
+
+
+CollectorHAL *createHAL()
+{
+ return new CollectorFreeBSD();
+}
+
+int CollectorFreeBSD::getHostCpuLoad(ULONG *user, ULONG *kernel, ULONG *idle)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorFreeBSD::getHostCpuMHz(ULONG *mhz)
+{
+ int CpuMHz = 0;
+ size_t cbParameter = sizeof(CpuMHz);
+
+ /** @todo Howto support more than one CPU? */
+ if (sysctlbyname("dev.cpu.0.freq", &CpuMHz, &cbParameter, NULL, 0))
+ return VERR_NOT_SUPPORTED;
+
+ *mhz = CpuMHz;
+
+ return VINF_SUCCESS;
+}
+
+int CollectorFreeBSD::getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available)
+{
+ int rc = VINF_SUCCESS;
+ u_long cbMemPhys = 0;
+ u_int cPagesMemFree = 0;
+ u_int cPagesMemInactive = 0;
+ u_int cPagesMemCached = 0;
+ u_int cPagesMemUsed = 0;
+ int cbPage = 0;
+ size_t cbParameter = sizeof(cbMemPhys);
+ int cProcessed = 0;
+
+ if (!sysctlbyname("hw.physmem", &cbMemPhys, &cbParameter, NULL, 0))
+ cProcessed++;
+
+ cbParameter = sizeof(cPagesMemFree);
+ if (!sysctlbyname("vm.stats.vm.v_free_count", &cPagesMemFree, &cbParameter, NULL, 0))
+ cProcessed++;
+ cbParameter = sizeof(cPagesMemUsed);
+ if (!sysctlbyname("vm.stats.vm.v_active_count", &cPagesMemUsed, &cbParameter, NULL, 0))
+ cProcessed++;
+ cbParameter = sizeof(cPagesMemInactive);
+ if (!sysctlbyname("vm.stats.vm.v_inactive_count", &cPagesMemInactive, &cbParameter, NULL, 0))
+ cProcessed++;
+ cbParameter = sizeof(cPagesMemCached);
+ if (!sysctlbyname("vm.stats.vm.v_cache_count", &cPagesMemCached, &cbParameter, NULL, 0))
+ cProcessed++;
+ cbParameter = sizeof(cbPage);
+ if (!sysctlbyname("hw.pagesize", &cbPage, &cbParameter, NULL, 0))
+ cProcessed++;
+
+ if (cProcessed == 6)
+ {
+ *total = cbMemPhys / _1K;
+ *used = cPagesMemUsed * (cbPage / _1K);
+ *available = (cPagesMemFree + cPagesMemInactive + cPagesMemCached ) * (cbPage / _1K);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ return rc;
+}
+
+int CollectorFreeBSD::getProcessCpuLoad(RTPROCESS process, ULONG *user, ULONG *kernel)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorFreeBSD::getProcessMemoryUsage(RTPROCESS process, ULONG *used)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int getDiskListByFs(const char *name, DiskList& list)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+} /* namespace pm */
+
diff --git a/src/VBox/Main/src-server/freebsd/USBProxyBackendFreeBSD.cpp b/src/VBox/Main/src-server/freebsd/USBProxyBackendFreeBSD.cpp
new file mode 100644
index 00000000..6542c34f
--- /dev/null
+++ b/src/VBox/Main/src-server/freebsd/USBProxyBackendFreeBSD.cpp
@@ -0,0 +1,353 @@
+/* $Id: USBProxyBackendFreeBSD.cpp $ */
+/** @file
+ * VirtualBox USB Proxy Service, FreeBSD Specialization.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_USBPROXYBACKEND
+#include "USBProxyBackend.h"
+#include "LoggingNew.h"
+
+#include <VBox/usb.h>
+#include <VBox/usblib.h>
+#include <iprt/errcore.h>
+
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/poll.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_ioctl.h>
+
+
+/**
+ * Initialize data members.
+ */
+USBProxyBackendFreeBSD::USBProxyBackendFreeBSD()
+ : USBProxyBackend(), mNotifyEventSem(NIL_RTSEMEVENT)
+{
+ LogFlowThisFunc(("\n"));
+}
+
+USBProxyBackendFreeBSD::~USBProxyBackendFreeBSD()
+{
+ LogFlowThisFunc(("\n"));
+}
+
+/**
+ * Initializes the object (called right after construction).
+ *
+ * @returns S_OK on success and non-fatal failures, some COM error otherwise.
+ */
+int USBProxyBackendFreeBSD::init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings)
+{
+ USBProxyBackend::init(pUsbProxyService, strId, strAddress, fLoadingSettings);
+
+ unconst(m_strBackend) = Utf8Str("host");
+
+ /*
+ * Create semaphore.
+ */
+ int rc = RTSemEventCreate(&mNotifyEventSem);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Start the poller thread.
+ */
+ start();
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Stop all service threads and free the device chain.
+ */
+void USBProxyBackendFreeBSD::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /*
+ * Stop the service.
+ */
+ if (isActive())
+ stop();
+
+ RTSemEventDestroy(mNotifyEventSem);
+ mNotifyEventSem = NULL;
+ USBProxyBackend::uninit();
+}
+
+
+int USBProxyBackendFreeBSD::captureDevice(HostUSBDevice *aDevice)
+{
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ /*
+ * Don't think we need to do anything when the device is held... fake it.
+ */
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_Capturing);
+ devLock.release();
+ interruptWait();
+
+ return VINF_SUCCESS;
+}
+
+
+int USBProxyBackendFreeBSD::releaseDevice(HostUSBDevice *aDevice)
+{
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ /*
+ * We're not really holding it atm., just fake it.
+ */
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_ReleasingToHost);
+ devLock.release();
+ interruptWait();
+
+ return VINF_SUCCESS;
+}
+
+
+bool USBProxyBackendFreeBSD::isFakeUpdateRequired()
+{
+ return true;
+}
+
+
+int USBProxyBackendFreeBSD::wait(RTMSINTERVAL aMillies)
+{
+ return RTSemEventWait(mNotifyEventSem, aMillies < 1000 ? 1000 : 5000);
+}
+
+
+int USBProxyBackendFreeBSD::interruptWait(void)
+{
+ return RTSemEventSignal(mNotifyEventSem);
+}
+
+
+/**
+ * Dumps a USBDEVICE structure to the log using LogLevel 3.
+ * @param pDev The structure to log.
+ * @todo This is really common code.
+ */
+DECLINLINE(void) usbLogDevice(PUSBDEVICE pDev)
+{
+ NOREF(pDev);
+
+ Log3(("USB device:\n"));
+ Log3(("Product: %s (%x)\n", pDev->pszProduct, pDev->idProduct));
+ Log3(("Manufacturer: %s (Vendor ID %x)\n", pDev->pszManufacturer, pDev->idVendor));
+ Log3(("Serial number: %s (%llx)\n", pDev->pszSerialNumber, pDev->u64SerialHash));
+ Log3(("Device revision: %d\n", pDev->bcdDevice));
+ Log3(("Device class: %x\n", pDev->bDeviceClass));
+ Log3(("Device subclass: %x\n", pDev->bDeviceSubClass));
+ Log3(("Device protocol: %x\n", pDev->bDeviceProtocol));
+ Log3(("USB version number: %d\n", pDev->bcdUSB));
+ Log3(("Device speed: %s\n",
+ pDev->enmSpeed == USBDEVICESPEED_UNKNOWN ? "unknown"
+ : pDev->enmSpeed == USBDEVICESPEED_LOW ? "1.5 MBit/s"
+ : pDev->enmSpeed == USBDEVICESPEED_FULL ? "12 MBit/s"
+ : pDev->enmSpeed == USBDEVICESPEED_HIGH ? "480 MBit/s"
+ : pDev->enmSpeed == USBDEVICESPEED_VARIABLE ? "variable"
+ : "invalid"));
+ Log3(("Number of configurations: %d\n", pDev->bNumConfigurations));
+ Log3(("Bus number: %d\n", pDev->bBus));
+ Log3(("Port number: %d\n", pDev->bPort));
+ Log3(("Device number: %d\n", pDev->bDevNum));
+ Log3(("Device state: %s\n",
+ pDev->enmState == USBDEVICESTATE_UNSUPPORTED ? "unsupported"
+ : pDev->enmState == USBDEVICESTATE_USED_BY_HOST ? "in use by host"
+ : pDev->enmState == USBDEVICESTATE_USED_BY_HOST_CAPTURABLE ? "in use by host, possibly capturable"
+ : pDev->enmState == USBDEVICESTATE_UNUSED ? "not in use"
+ : pDev->enmState == USBDEVICESTATE_HELD_BY_PROXY ? "held by proxy"
+ : pDev->enmState == USBDEVICESTATE_USED_BY_GUEST ? "used by guest"
+ : "invalid"));
+ Log3(("OS device address: %s\n", pDev->pszAddress));
+}
+
+
+PUSBDEVICE USBProxyBackendFreeBSD::getDevices(void)
+{
+ PUSBDEVICE pDevices = NULL;
+ int FileUsb = 0;
+ int iBus = 0;
+ int iAddr = 1;
+ int rc = VINF_SUCCESS;
+ char *pszDevicePath = NULL;
+ uint32_t PlugTime = 0;
+
+ for (;;)
+ {
+ rc = RTStrAPrintf(&pszDevicePath, "/dev/%s%d.%d", USB_GENERIC_NAME, iBus, iAddr);
+ if (RT_FAILURE(rc))
+ break;
+
+ LogFlowFunc((": Opening %s\n", pszDevicePath));
+
+ FileUsb = open(pszDevicePath, O_RDONLY);
+ if (FileUsb < 0)
+ {
+ RTStrFree(pszDevicePath);
+
+ if ((errno == ENOENT) && (iAddr > 1))
+ {
+ iAddr = 1;
+ iBus++;
+ continue;
+ }
+ else if (errno == EACCES)
+ {
+ /* Skip devices without the right permission. */
+ iAddr++;
+ continue;
+ }
+ else
+ break;
+ }
+
+ LogFlowFunc((": %s opened successfully\n", pszDevicePath));
+
+ struct usb_device_info UsbDevInfo;
+ RT_ZERO(UsbDevInfo);
+
+ rc = ioctl(FileUsb, USB_GET_DEVICEINFO, &UsbDevInfo);
+ if (rc < 0)
+ {
+ LogFlowFunc((": Error querying device info rc=%Rrc\n", RTErrConvertFromErrno(errno)));
+ close(FileUsb);
+ RTStrFree(pszDevicePath);
+ break;
+ }
+
+ /* Filter out hubs */
+ if (UsbDevInfo.udi_class != 0x09)
+ {
+ PUSBDEVICE pDevice = (PUSBDEVICE)RTMemAllocZ(sizeof(USBDEVICE));
+ if (!pDevice)
+ {
+ close(FileUsb);
+ RTStrFree(pszDevicePath);
+ break;
+ }
+
+ pDevice->enmState = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE;
+ pDevice->bBus = UsbDevInfo.udi_bus;
+ pDevice->bPort = UsbDevInfo.udi_hubport;
+ pDevice->bDeviceClass = UsbDevInfo.udi_class;
+ pDevice->bDeviceSubClass = UsbDevInfo.udi_subclass;
+ pDevice->bDeviceProtocol = UsbDevInfo.udi_protocol;
+ pDevice->bNumConfigurations = UsbDevInfo.udi_config_no;
+ pDevice->idVendor = UsbDevInfo.udi_vendorNo;
+ pDevice->idProduct = UsbDevInfo.udi_productNo;
+ pDevice->bDevNum = UsbDevInfo.udi_index;
+
+ switch (UsbDevInfo.udi_speed)
+ {
+ case USB_SPEED_LOW:
+ pDevice->enmSpeed = USBDEVICESPEED_LOW;
+ break;
+ case USB_SPEED_FULL:
+ pDevice->enmSpeed = USBDEVICESPEED_FULL;
+ break;
+ case USB_SPEED_HIGH:
+ pDevice->enmSpeed = USBDEVICESPEED_HIGH;
+ break;
+ case USB_SPEED_SUPER:
+ pDevice->enmSpeed = USBDEVICESPEED_SUPER;
+ break;
+ case USB_SPEED_VARIABLE:
+ pDevice->enmSpeed = USBDEVICESPEED_VARIABLE;
+ break;
+ default:
+ pDevice->enmSpeed = USBDEVICESPEED_UNKNOWN;
+ break;
+ }
+
+ if (UsbDevInfo.udi_vendor[0] != '\0')
+ {
+ USBLibPurgeEncoding(UsbDevInfo.udi_vendor);
+ pDevice->pszManufacturer = RTStrDupN(UsbDevInfo.udi_vendor, sizeof(UsbDevInfo.udi_vendor));
+ }
+
+ if (UsbDevInfo.udi_product[0] != '\0')
+ {
+ USBLibPurgeEncoding(UsbDevInfo.udi_product);
+ pDevice->pszProduct = RTStrDupN(UsbDevInfo.udi_product, sizeof(UsbDevInfo.udi_product));
+ }
+
+ if (UsbDevInfo.udi_serial[0] != '\0')
+ {
+ USBLibPurgeEncoding(UsbDevInfo.udi_serial);
+ pDevice->pszSerialNumber = RTStrDupN(UsbDevInfo.udi_serial, sizeof(UsbDevInfo.udi_serial));
+ pDevice->u64SerialHash = USBLibHashSerial(UsbDevInfo.udi_serial);
+ }
+ rc = ioctl(FileUsb, USB_GET_PLUGTIME, &PlugTime);
+ if (rc == 0)
+ pDevice->u64SerialHash += PlugTime;
+
+ pDevice->pszAddress = RTStrDup(pszDevicePath);
+ pDevice->pszBackend = RTStrDup("host");
+
+ usbLogDevice(pDevice);
+
+ pDevice->pNext = pDevices;
+ if (pDevices)
+ pDevices->pPrev = pDevice;
+ pDevices = pDevice;
+ }
+ close(FileUsb);
+ RTStrFree(pszDevicePath);
+ iAddr++;
+ }
+
+ return pDevices;
+}
diff --git a/src/VBox/Main/src-server/generic/AutostartDb-generic.cpp b/src/VBox/Main/src-server/generic/AutostartDb-generic.cpp
new file mode 100644
index 00000000..5d97088e
--- /dev/null
+++ b/src/VBox/Main/src-server/generic/AutostartDb-generic.cpp
@@ -0,0 +1,272 @@
+/* $Id: AutostartDb-generic.cpp $ */
+/** @file
+ * VirtualBox Main - Autostart implementation.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/process.h>
+#include <iprt/path.h>
+#include <iprt/mem.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+
+#include "AutostartDb.h"
+
+#if defined(RT_OS_LINUX)
+/**
+ * Modifies the autostart database.
+ *
+ * @returns VBox status code.
+ * @param fAutostart Flag whether the autostart or autostop database is modified.
+ * @param fAddVM Flag whether a VM is added or removed from the database.
+ */
+int AutostartDb::autostartModifyDb(bool fAutostart, bool fAddVM)
+{
+ int rc = VINF_SUCCESS;
+ char *pszUser = NULL;
+
+ /* Check if the path is set. */
+ if (!m_pszAutostartDbPath)
+ return VERR_PATH_NOT_FOUND;
+
+ rc = RTProcQueryUsernameA(RTProcSelf(), &pszUser);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszFile;
+ uint64_t fOpen = RTFILE_O_DENY_ALL | RTFILE_O_READWRITE;
+ RTFILE hAutostartFile;
+
+ AssertPtr(pszUser);
+
+ if (fAddVM)
+ fOpen |= RTFILE_O_OPEN_CREATE;
+ else
+ fOpen |= RTFILE_O_OPEN;
+
+ rc = RTStrAPrintf(&pszFile, "%s/%s.%s",
+ m_pszAutostartDbPath, pszUser, fAutostart ? "start" : "stop");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileOpen(&hAutostartFile, pszFile, fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbFile;
+
+ /*
+ * Files with more than 16 bytes are rejected because they just contain
+ * a number of the amount of VMs with autostart configured, so they
+ * should be really really small. Anything else is bogus.
+ */
+ rc = RTFileQuerySize(hAutostartFile, &cbFile);
+ if ( RT_SUCCESS(rc)
+ && cbFile <= 16)
+ {
+ char abBuf[16 + 1]; /* trailing \0 */
+ uint32_t cAutostartVms = 0;
+
+ RT_ZERO(abBuf);
+
+ /* Check if the file was just created. */
+ if (cbFile)
+ {
+ rc = RTFileRead(hAutostartFile, abBuf, (size_t)cbFile, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrToUInt32Ex(abBuf, NULL, 10 /* uBase */, &cAutostartVms);
+ if ( rc == VWRN_TRAILING_CHARS
+ || rc == VWRN_TRAILING_SPACES)
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbBuf;
+
+ /* Modify VM counter and write back. */
+ if (fAddVM)
+ cAutostartVms++;
+ else
+ cAutostartVms--;
+
+ if (cAutostartVms > 0)
+ {
+ cbBuf = RTStrPrintf(abBuf, sizeof(abBuf), "%u", cAutostartVms);
+ rc = RTFileSetSize(hAutostartFile, cbBuf);
+ if (RT_SUCCESS(rc))
+ rc = RTFileWriteAt(hAutostartFile, 0, abBuf, cbBuf, NULL);
+ }
+ else
+ {
+ /* Just delete the file if there are no VMs left. */
+ RTFileClose(hAutostartFile);
+ RTFileDelete(pszFile);
+ hAutostartFile = NIL_RTFILE;
+ }
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_FILE_TOO_BIG;
+
+ if (hAutostartFile != NIL_RTFILE)
+ RTFileClose(hAutostartFile);
+ }
+ RTStrFree(pszFile);
+ }
+
+ RTStrFree(pszUser);
+ }
+
+ return rc;
+}
+
+#endif
+
+AutostartDb::AutostartDb()
+{
+#ifdef RT_OS_LINUX
+ int rc = RTCritSectInit(&this->CritSect);
+ NOREF(rc);
+ m_pszAutostartDbPath = NULL;
+#endif
+}
+
+AutostartDb::~AutostartDb()
+{
+#ifdef RT_OS_LINUX
+ RTCritSectDelete(&this->CritSect);
+ if (m_pszAutostartDbPath)
+ RTStrFree(m_pszAutostartDbPath);
+#endif
+}
+
+int AutostartDb::setAutostartDbPath(const char *pszAutostartDbPathNew)
+{
+#if defined(RT_OS_LINUX)
+ char *pszAutostartDbPathTmp = NULL;
+
+ if (pszAutostartDbPathNew)
+ {
+ pszAutostartDbPathTmp = RTStrDup(pszAutostartDbPathNew);
+ if (!pszAutostartDbPathTmp)
+ return VERR_NO_MEMORY;
+ }
+
+ RTCritSectEnter(&this->CritSect);
+ if (m_pszAutostartDbPath)
+ RTStrFree(m_pszAutostartDbPath);
+
+ m_pszAutostartDbPath = pszAutostartDbPathTmp;
+ RTCritSectLeave(&this->CritSect);
+ return VINF_SUCCESS;
+#else
+ NOREF(pszAutostartDbPathNew);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+int AutostartDb::addAutostartVM(const char *pszVMId)
+{
+ int rc = VERR_NOT_SUPPORTED;
+
+#if defined(RT_OS_LINUX)
+ NOREF(pszVMId); /* Not needed */
+
+ RTCritSectEnter(&this->CritSect);
+ rc = autostartModifyDb(true /* fAutostart */, true /* fAddVM */);
+ RTCritSectLeave(&this->CritSect);
+#elif defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS)
+ NOREF(pszVMId); /* Not needed */
+ rc = VINF_SUCCESS;
+#else
+ NOREF(pszVMId);
+ rc = VERR_NOT_SUPPORTED;
+#endif
+
+ return rc;
+}
+
+int AutostartDb::removeAutostartVM(const char *pszVMId)
+{
+ int rc = VINF_SUCCESS;
+
+#if defined(RT_OS_LINUX)
+ NOREF(pszVMId); /* Not needed */
+ RTCritSectEnter(&this->CritSect);
+ rc = autostartModifyDb(true /* fAutostart */, false /* fAddVM */);
+ RTCritSectLeave(&this->CritSect);
+#elif defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS)
+ NOREF(pszVMId); /* Not needed */
+ rc = VINF_SUCCESS;
+#else
+ NOREF(pszVMId);
+ rc = VERR_NOT_SUPPORTED;
+#endif
+
+ return rc;
+}
+
+int AutostartDb::addAutostopVM(const char *pszVMId)
+{
+ int rc = VINF_SUCCESS;
+
+#if defined(RT_OS_LINUX)
+ NOREF(pszVMId); /* Not needed */
+ RTCritSectEnter(&this->CritSect);
+ rc = autostartModifyDb(false /* fAutostart */, true /* fAddVM */);
+ RTCritSectLeave(&this->CritSect);
+#elif defined(RT_OS_DARWIN) || defined(RT_OS_WINDOWS)
+ NOREF(pszVMId); /* Not needed */
+ rc = VINF_SUCCESS;
+#else
+ NOREF(pszVMId);
+ rc = VERR_NOT_SUPPORTED;
+#endif
+
+ return rc;
+}
+
+int AutostartDb::removeAutostopVM(const char *pszVMId)
+{
+ int rc = VINF_SUCCESS;
+
+#if defined(RT_OS_LINUX)
+ NOREF(pszVMId); /* Not needed */
+ RTCritSectEnter(&this->CritSect);
+ rc = autostartModifyDb(false /* fAutostart */, false /* fAddVM */);
+ RTCritSectLeave(&this->CritSect);
+#elif defined(RT_OS_DARWIN) || defined (RT_OS_WINDOWS)
+ NOREF(pszVMId); /* Not needed */
+ rc = VINF_SUCCESS;
+#else
+ NOREF(pszVMId);
+ rc = VERR_NOT_SUPPORTED;
+#endif
+
+ return rc;
+}
+
diff --git a/src/VBox/Main/src-server/generic/Makefile.kup b/src/VBox/Main/src-server/generic/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-server/generic/Makefile.kup
diff --git a/src/VBox/Main/src-server/generic/NetIf-generic.cpp b/src/VBox/Main/src-server/generic/NetIf-generic.cpp
new file mode 100644
index 00000000..2245c049
--- /dev/null
+++ b/src/VBox/Main/src-server/generic/NetIf-generic.cpp
@@ -0,0 +1,432 @@
+/* $Id: NetIf-generic.cpp $ */
+/** @file
+ * VirtualBox Main - Generic NetIf implementation.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/process.h>
+#include <iprt/env.h>
+#include <iprt/path.h>
+#include <iprt/param.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <errno.h>
+#include <unistd.h>
+
+#if defined(RT_OS_SOLARIS)
+# include <sys/sockio.h>
+#endif
+
+#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN)
+# include <cstdio>
+#endif
+
+#include "HostNetworkInterfaceImpl.h"
+#include "ProgressImpl.h"
+#include "VirtualBoxImpl.h"
+#include "VBoxNls.h"
+#include "Global.h"
+#include "netif.h"
+
+#define VBOXNETADPCTL_NAME "VBoxNetAdpCtl"
+
+DECLARE_TRANSLATION_CONTEXT(NetIfGeneric);
+
+
+static int NetIfAdpCtl(const char * pcszIfName, const char *pszAddr, const char *pszOption, const char *pszMask)
+{
+ const char *args[] = { NULL, pcszIfName, pszAddr, pszOption, pszMask, NULL };
+
+ char szAdpCtl[RTPATH_MAX];
+ int rc = RTPathExecDir(szAdpCtl, sizeof(szAdpCtl) - sizeof("/" VBOXNETADPCTL_NAME));
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("NetIfAdpCtl: failed to get program path, rc=%Rrc.\n", rc));
+ return rc;
+ }
+ strcat(szAdpCtl, "/" VBOXNETADPCTL_NAME);
+ args[0] = szAdpCtl;
+ if (!RTPathExists(szAdpCtl))
+ {
+ LogRel(("NetIfAdpCtl: path %s does not exist. Failed to run " VBOXNETADPCTL_NAME " helper.\n",
+ szAdpCtl));
+ return VERR_FILE_NOT_FOUND;
+ }
+
+ RTPROCESS pid;
+ rc = RTProcCreate(szAdpCtl, args, RTENV_DEFAULT, 0, &pid);
+ if (RT_SUCCESS(rc))
+ {
+ RTPROCSTATUS Status;
+ rc = RTProcWait(pid, 0, &Status);
+ if (RT_SUCCESS(rc))
+ {
+ if ( Status.iStatus == 0
+ && Status.enmReason == RTPROCEXITREASON_NORMAL)
+ return VINF_SUCCESS;
+ LogRel(("NetIfAdpCtl: failed to create process for %s: iStats=%d enmReason=%d\n",
+ szAdpCtl, Status.iStatus, Status.enmReason));
+ rc = -Status.iStatus;
+ }
+ }
+ else
+ LogRel(("NetIfAdpCtl: failed to create process for %s: %Rrc\n", szAdpCtl, rc));
+ return rc;
+}
+
+static int NetIfAdpCtl(HostNetworkInterface * pIf, const char *pszAddr, const char *pszOption, const char *pszMask)
+{
+ Bstr interfaceName;
+ pIf->COMGETTER(Name)(interfaceName.asOutParam());
+ Utf8Str strName(interfaceName);
+ return NetIfAdpCtl(strName.c_str(), pszAddr, pszOption, pszMask);
+}
+
+int NetIfAdpCtlOut(const char * pcszName, const char * pcszCmd, char *pszBuffer, size_t cBufSize)
+{
+ char szAdpCtl[RTPATH_MAX];
+ int rc = RTPathExecDir(szAdpCtl, sizeof(szAdpCtl) - sizeof("/" VBOXNETADPCTL_NAME " ") - strlen(pcszCmd));
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("NetIfAdpCtlOut: Failed to get program path, rc=%Rrc\n", rc));
+ return VERR_INVALID_PARAMETER;
+ }
+ strcat(szAdpCtl, "/" VBOXNETADPCTL_NAME " ");
+ if (pcszName && strlen(pcszName) <= RTPATH_MAX - strlen(szAdpCtl) - 1 - strlen(pcszCmd))
+ {
+ strcat(szAdpCtl, pcszName);
+ strcat(szAdpCtl, " ");
+ strcat(szAdpCtl, pcszCmd);
+ }
+ else
+ {
+ LogRel(("NetIfAdpCtlOut: Command line is too long: %s%s %s\n", szAdpCtl, pcszName, pcszCmd));
+ return VERR_INVALID_PARAMETER;
+ }
+ if (strlen(szAdpCtl) < RTPATH_MAX - sizeof(" 2>&1"))
+ strcat(szAdpCtl, " 2>&1");
+ FILE *fp = popen(szAdpCtl, "r");
+ if (fp)
+ {
+ if (fgets(pszBuffer, (int)cBufSize, fp))
+ {
+ if (!strncmp(VBOXNETADPCTL_NAME ":", pszBuffer, sizeof(VBOXNETADPCTL_NAME)))
+ {
+ LogRel(("NetIfAdpCtlOut: %s", pszBuffer));
+ rc = VERR_INTERNAL_ERROR;
+ }
+ }
+ else
+ {
+ LogRel(("NetIfAdpCtlOut: No output from " VBOXNETADPCTL_NAME));
+ rc = VERR_INTERNAL_ERROR;
+ }
+ pclose(fp);
+ }
+ return rc;
+}
+
+int NetIfEnableStaticIpConfig(VirtualBox * /* vBox */, HostNetworkInterface * pIf, ULONG aOldIp, ULONG aNewIp, ULONG aMask)
+{
+ const char *pszOption, *pszMask;
+ char szAddress[16]; /* 4*3 + 3*1 + 1 */
+ char szNetMask[16]; /* 4*3 + 3*1 + 1 */
+ uint8_t *pu8Addr = (uint8_t *)&aNewIp;
+ uint8_t *pu8Mask = (uint8_t *)&aMask;
+ if (aNewIp == 0)
+ {
+ pu8Addr = (uint8_t *)&aOldIp;
+ pszOption = "remove";
+ pszMask = NULL;
+ }
+ else
+ {
+ pszOption = "netmask";
+ pszMask = szNetMask;
+ RTStrPrintf(szNetMask, sizeof(szNetMask), "%d.%d.%d.%d",
+ pu8Mask[0], pu8Mask[1], pu8Mask[2], pu8Mask[3]);
+ }
+ RTStrPrintf(szAddress, sizeof(szAddress), "%d.%d.%d.%d",
+ pu8Addr[0], pu8Addr[1], pu8Addr[2], pu8Addr[3]);
+ return NetIfAdpCtl(pIf, szAddress, pszOption, pszMask);
+}
+
+int NetIfEnableStaticIpConfigV6(VirtualBox * /* vBox */, HostNetworkInterface * pIf, const Utf8Str &aOldIPV6Address,
+ const Utf8Str &aIPV6Address, ULONG aIPV6MaskPrefixLength)
+{
+ char szAddress[5*8 + 1 + 5 + 1];
+ if (aIPV6Address.length())
+ {
+ RTStrPrintf(szAddress, sizeof(szAddress), "%s/%d",
+ aIPV6Address.c_str(), aIPV6MaskPrefixLength);
+ return NetIfAdpCtl(pIf, szAddress, NULL, NULL);
+ }
+ else
+ {
+ RTStrPrintf(szAddress, sizeof(szAddress), "%s",
+ aOldIPV6Address.c_str());
+ return NetIfAdpCtl(pIf, szAddress, "remove", NULL);
+ }
+}
+
+int NetIfEnableDynamicIpConfig(VirtualBox * /* vBox */, HostNetworkInterface * /* pIf */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+int NetIfCreateHostOnlyNetworkInterface(VirtualBox *pVirtualBox,
+ IHostNetworkInterface **aHostNetworkInterface,
+ IProgress **aProgress,
+ const char *pcszName)
+{
+#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD)
+ /* create a progress object */
+ ComObjPtr<Progress> progress;
+ HRESULT hrc = progress.createObject();
+ AssertComRCReturn(hrc, Global::vboxStatusCodeFromCOM(hrc));
+
+ /* Note vrc and hrc are competing about tracking the error state here. */
+ int vrc = VINF_SUCCESS;
+ ComPtr<IHost> host;
+ hrc = pVirtualBox->COMGETTER(Host)(host.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ hrc = progress->init(pVirtualBox, host,
+ NetIfGeneric::tr("Creating host only network interface"),
+ FALSE /* aCancelable */);
+ if (SUCCEEDED(hrc))
+ {
+ progress.queryInterfaceTo(aProgress);
+
+ char szAdpCtl[RTPATH_MAX];
+ vrc = RTPathExecDir(szAdpCtl, sizeof(szAdpCtl) - sizeof("/" VBOXNETADPCTL_NAME " add"));
+ if (RT_FAILURE(vrc))
+ {
+ progress->i_notifyComplete(E_FAIL,
+ COM_IIDOF(IHostNetworkInterface),
+ HostNetworkInterface::getStaticComponentName(),
+ NetIfGeneric::tr("Failed to get program path, vrc=%Rrc\n"), vrc);
+ return vrc;
+ }
+ strcat(szAdpCtl, "/" VBOXNETADPCTL_NAME " ");
+ if (pcszName && strlen(pcszName) <= RTPATH_MAX - strlen(szAdpCtl) - sizeof(" add"))
+ {
+ strcat(szAdpCtl, pcszName);
+ strcat(szAdpCtl, " add");
+ }
+ else
+ strcat(szAdpCtl, "add");
+ if (strlen(szAdpCtl) < RTPATH_MAX - sizeof(" 2>&1"))
+ strcat(szAdpCtl, " 2>&1");
+
+ FILE *fp = popen(szAdpCtl, "r");
+ if (fp)
+ {
+ char szBuf[128]; /* We are not interested in long error messages. */
+ if (fgets(szBuf, sizeof(szBuf), fp))
+ {
+ /* Remove trailing new line characters. */
+ char *pLast = szBuf + strlen(szBuf) - 1;
+ if (pLast >= szBuf && *pLast == '\n')
+ *pLast = 0;
+
+ if (!strncmp(VBOXNETADPCTL_NAME ":", szBuf, sizeof(VBOXNETADPCTL_NAME)))
+ {
+ progress->i_notifyComplete(E_FAIL,
+ COM_IIDOF(IHostNetworkInterface),
+ HostNetworkInterface::getStaticComponentName(),
+ "%s", szBuf);
+ pclose(fp);
+ return Global::vboxStatusCodeFromCOM(E_FAIL);
+ }
+
+ size_t cbNameLen = strlen(szBuf) + 1;
+ PNETIFINFO pInfo = (PNETIFINFO)RTMemAllocZ(RT_UOFFSETOF_DYN(NETIFINFO, szName[cbNameLen]));
+ if (!pInfo)
+ vrc = VERR_NO_MEMORY;
+ else
+ {
+ strcpy(pInfo->szShortName, szBuf);
+ strcpy(pInfo->szName, szBuf);
+ vrc = NetIfGetConfigByName(pInfo);
+ if (RT_FAILURE(vrc))
+ {
+ progress->i_notifyComplete(E_FAIL,
+ COM_IIDOF(IHostNetworkInterface),
+ HostNetworkInterface::getStaticComponentName(),
+ NetIfGeneric::tr("Failed to get config info for %s (as reported by 'VBoxNetAdpCtl add')\n"),
+ szBuf);
+ }
+ else
+ {
+ Utf8Str IfName(szBuf);
+ /* create a new uninitialized host interface object */
+ ComObjPtr<HostNetworkInterface> iface;
+ iface.createObject();
+ iface->init(IfName, HostNetworkInterfaceType_HostOnly, pInfo);
+ iface->i_setVirtualBox(pVirtualBox);
+ iface.queryInterfaceTo(aHostNetworkInterface);
+ }
+ RTMemFree(pInfo);
+ }
+ if ((vrc = pclose(fp)) != 0)
+ {
+ progress->i_notifyComplete(E_FAIL,
+ COM_IIDOF(IHostNetworkInterface),
+ HostNetworkInterface::getStaticComponentName(),
+ NetIfGeneric::tr("Failed to execute '%s' - exit status: %d"), szAdpCtl, vrc);
+ vrc = VERR_INTERNAL_ERROR;
+ }
+ }
+ else
+ {
+ /* Failed to add an interface */
+ progress->i_notifyComplete(E_FAIL,
+ COM_IIDOF(IHostNetworkInterface),
+ HostNetworkInterface::getStaticComponentName(),
+ NetIfGeneric::tr("Failed to execute '%s' (errno %d). Check permissions!"),
+ szAdpCtl, errno);
+ pclose(fp);
+ vrc = VERR_PERMISSION_DENIED;
+ }
+ }
+ else
+ {
+ vrc = RTErrConvertFromErrno(errno);
+ progress->i_notifyComplete(E_FAIL,
+ COM_IIDOF(IHostNetworkInterface),
+ HostNetworkInterface::getStaticComponentName(),
+ NetIfGeneric::tr("Failed to execute '%s' (errno %d / %Rrc). Check permissions!"),
+ szAdpCtl, errno, vrc);
+ }
+ if (RT_SUCCESS(vrc))
+ progress->i_notifyComplete(S_OK);
+ else
+ hrc = E_FAIL;
+ }
+ }
+
+ return RT_FAILURE(vrc) ? vrc : SUCCEEDED(hrc) ? VINF_SUCCESS : Global::vboxStatusCodeFromCOM(hrc);
+
+#else
+ NOREF(pVirtualBox);
+ NOREF(aHostNetworkInterface);
+ NOREF(aProgress);
+ NOREF(pcszName);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+int NetIfRemoveHostOnlyNetworkInterface(VirtualBox *pVirtualBox, const Guid &aId,
+ IProgress **aProgress)
+{
+#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD)
+ /* create a progress object */
+ ComObjPtr<Progress> progress;
+ HRESULT hrc = progress.createObject();
+ AssertComRCReturn(hrc, Global::vboxStatusCodeFromCOM(hrc));
+
+ ComPtr<IHost> host;
+ int vrc = VINF_SUCCESS;
+ hrc = pVirtualBox->COMGETTER(Host)(host.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<IHostNetworkInterface> iface;
+ if (FAILED(host->FindHostNetworkInterfaceById(aId.toUtf16().raw(), iface.asOutParam())))
+ return VERR_INVALID_PARAMETER;
+
+ Bstr ifname;
+ iface->COMGETTER(Name)(ifname.asOutParam());
+ if (ifname.isEmpty())
+ return VERR_INTERNAL_ERROR;
+ Utf8Str strIfName(ifname);
+
+ hrc = progress->init(pVirtualBox, host, NetIfGeneric::tr("Removing host network interface"), FALSE /* aCancelable */);
+ if (SUCCEEDED(hrc))
+ {
+ progress.queryInterfaceTo(aProgress);
+ vrc = NetIfAdpCtl(strIfName.c_str(), "remove", NULL, NULL);
+ if (RT_FAILURE(vrc))
+ progress->i_notifyComplete(E_FAIL,
+ COM_IIDOF(IHostNetworkInterface),
+ HostNetworkInterface::getStaticComponentName(),
+ NetIfGeneric::tr("Failed to execute 'VBoxNetAdpCtl %s remove' (%Rrc)"),
+ strIfName.c_str(), vrc);
+ else
+ progress->i_notifyComplete(S_OK);
+ }
+ else
+ vrc = Global::vboxStatusCodeFromCOM(hrc);
+ }
+ else
+ vrc = Global::vboxStatusCodeFromCOM(hrc);
+ return vrc;
+#else
+ NOREF(pVirtualBox);
+ NOREF(aId);
+ NOREF(aProgress);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+int NetIfGetConfig(HostNetworkInterface * /* pIf */, NETIFINFO *)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int NetIfDhcpRediscover(VirtualBox * /* pVBox */, HostNetworkInterface * /* pIf */)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * Obtain the current state of the interface.
+ *
+ * @returns VBox status code.
+ *
+ * @param pcszIfName Interface name.
+ * @param penmState Where to store the retrieved state.
+ */
+int NetIfGetState(const char *pcszIfName, NETIFSTATUS *penmState)
+{
+ int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sock < 0)
+ return VERR_OUT_OF_RESOURCES;
+ struct ifreq Req;
+ RT_ZERO(Req);
+ RTStrCopy(Req.ifr_name, sizeof(Req.ifr_name), pcszIfName);
+ if (ioctl(sock, SIOCGIFFLAGS, &Req) < 0)
+ {
+ Log(("NetIfGetState: ioctl(SIOCGIFFLAGS) -> %d\n", errno));
+ *penmState = NETIF_S_UNKNOWN;
+ }
+ else
+ *penmState = (Req.ifr_flags & IFF_UP) ? NETIF_S_UP : NETIF_S_DOWN;
+ close(sock);
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/Main/src-server/generic/USBProxyBackendUsbIp.cpp b/src/VBox/Main/src-server/generic/USBProxyBackendUsbIp.cpp
new file mode 100644
index 00000000..16eb1802
--- /dev/null
+++ b/src/VBox/Main/src-server/generic/USBProxyBackendUsbIp.cpp
@@ -0,0 +1,1088 @@
+/* $Id: USBProxyBackendUsbIp.cpp $ */
+/** @file
+ * VirtualBox USB Proxy Backend, USB/IP.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_USBPROXYBACKEND
+#include "USBProxyService.h"
+#include "USBGetDevices.h"
+#include "LoggingNew.h"
+
+#include <VBox/usb.h>
+#include <VBox/usblib.h>
+#include <VBox/err.h>
+
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/tcp.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/pipe.h>
+#include <iprt/asm.h>
+#include <iprt/cdefs.h>
+#include <iprt/time.h>
+
+/** The USB/IP default port to connect to. */
+#define USBIP_PORT_DEFAULT 3240
+/** The USB version number used for the protocol. */
+#define USBIP_VERSION UINT16_C(0x0111)
+/** Request indicator in the command code. */
+#define USBIP_INDICATOR_REQ RT_BIT(15)
+
+/** Command/Reply code for OP_REQ/RET_DEVLIST. */
+#define USBIP_REQ_RET_DEVLIST UINT16_C(5)
+
+/** @todo Duplicate code in USBProxyDevice-usbip.cpp */
+/**
+ * Exported device entry in the OP_RET_DEVLIST reply.
+ */
+#pragma pack(1)
+typedef struct UsbIpExportedDevice
+{
+ /** Path of the device, zero terminated string. */
+ char szPath[256];
+ /** Bus ID of the exported device, zero terminated string. */
+ char szBusId[32];
+ /** Bus number. */
+ uint32_t u32BusNum;
+ /** Device number. */
+ uint32_t u32DevNum;
+ /** Speed indicator of the device. */
+ uint32_t u32Speed;
+ /** Vendor ID of the device. */
+ uint16_t u16VendorId;
+ /** Product ID of the device. */
+ uint16_t u16ProductId;
+ /** Device release number. */
+ uint16_t u16BcdDevice;
+ /** Device class. */
+ uint8_t bDeviceClass;
+ /** Device Subclass. */
+ uint8_t bDeviceSubClass;
+ /** Device protocol. */
+ uint8_t bDeviceProtocol;
+ /** Configuration value. */
+ uint8_t bConfigurationValue;
+ /** Current configuration value of the device. */
+ uint8_t bNumConfigurations;
+ /** Number of interfaces for the device. */
+ uint8_t bNumInterfaces;
+} UsbIpExportedDevice;
+/** Pointer to a exported device entry. */
+typedef UsbIpExportedDevice *PUsbIpExportedDevice;
+#pragma pack()
+AssertCompileSize(UsbIpExportedDevice, 312);
+
+/**
+ * Interface descriptor entry for an exported device.
+ */
+#pragma pack(1)
+typedef struct UsbIpDeviceInterface
+{
+ /** Intefrace class. */
+ uint8_t bInterfaceClass;
+ /** Interface sub class. */
+ uint8_t bInterfaceSubClass;
+ /** Interface protocol identifier. */
+ uint8_t bInterfaceProtocol;
+ /** Padding byte for alignment. */
+ uint8_t bPadding;
+} UsbIpDeviceInterface;
+/** Pointer to an interface descriptor entry. */
+typedef UsbIpDeviceInterface *PUsbIpDeviceInterface;
+#pragma pack()
+
+/**
+ * USB/IP device list request.
+ */
+#pragma pack(1)
+typedef struct UsbIpReqDevList
+{
+ /** Protocol version number. */
+ uint16_t u16Version;
+ /** Command code. */
+ uint16_t u16Cmd;
+ /** Status field, unused. */
+ int32_t i32Status;
+} UsbIpReqDevList;
+/** Pointer to a device list request. */
+typedef UsbIpReqDevList *PUsbIpReqDevList;
+#pragma pack()
+
+/**
+ * USB/IP Import reply.
+ *
+ * This is only the header, for successful
+ * requests the device details are sent to as
+ * defined in UsbIpExportedDevice.
+ */
+#pragma pack(1)
+typedef struct UsbIpRetDevList
+{
+ /** Protocol version number. */
+ uint16_t u16Version;
+ /** Command code. */
+ uint16_t u16Cmd;
+ /** Status field, unused. */
+ int32_t i32Status;
+ /** Number of exported devices. */
+ uint32_t u32DevicesExported;
+} UsbIpRetDevList;
+/** Pointer to a import reply. */
+typedef UsbIpRetDevList *PUsbIpRetDevList;
+#pragma pack()
+
+/** Pollset id of the socket. */
+#define USBIP_POLL_ID_SOCKET 0
+/** Pollset id of the pipe. */
+#define USBIP_POLL_ID_PIPE 1
+
+/** @name USB/IP error codes.
+ * @{ */
+/** Success indicator. */
+#define USBIP_STATUS_SUCCESS INT32_C(0)
+/** @} */
+
+/** @name USB/IP device speeds.
+ * @{ */
+/** Unknown speed. */
+#define USBIP_SPEED_UNKNOWN 0
+/** Low (1.0) speed. */
+#define USBIP_SPEED_LOW 1
+/** Full (1.1) speed. */
+#define USBIP_SPEED_FULL 2
+/** High (2.0) speed. */
+#define USBIP_SPEED_HIGH 3
+/** Variable (CWUSB) speed. */
+#define USBIP_SPEED_WIRELESS 4
+/** Super (3.0) speed. */
+#define USBIP_SPEED_SUPER 5
+/** @} */
+
+/**
+ * Private USB/IP proxy backend data.
+ */
+struct USBProxyBackendUsbIp::Data
+{
+ Data()
+ : hSocket(NIL_RTSOCKET),
+ hWakeupPipeR(NIL_RTPIPE),
+ hWakeupPipeW(NIL_RTPIPE),
+ hPollSet(NIL_RTPOLLSET),
+ uPort(USBIP_PORT_DEFAULT),
+ pszHost(NULL),
+ hMtxDevices(NIL_RTSEMFASTMUTEX),
+ cUsbDevicesCur(0),
+ pUsbDevicesCur(NULL),
+ enmRecvState(kUsbIpRecvState_Invalid),
+ cbResidualRecv(0),
+ pbRecvBuf(NULL),
+ cDevicesLeft(0),
+ pHead(NULL),
+ ppNext(&pHead)
+ { }
+
+ /** Socket handle to the server. */
+ RTSOCKET hSocket;
+ /** Pipe used to interrupt wait(), the read end. */
+ RTPIPE hWakeupPipeR;
+ /** Pipe used to interrupt wait(), the write end. */
+ RTPIPE hWakeupPipeW;
+ /** Pollset for the socket and wakeup pipe. */
+ RTPOLLSET hPollSet;
+ /** Port of the USB/IP host to connect to. */
+ uint32_t uPort;
+ /** USB/IP host address. */
+ char *pszHost;
+ /** Mutex protecting the device list against concurrent access. */
+ RTSEMFASTMUTEX hMtxDevices;
+ /** Number of devices in the list. */
+ uint32_t cUsbDevicesCur;
+ /** The current list of devices to compare with. */
+ PUSBDEVICE pUsbDevicesCur;
+ /** Current receive state. */
+ USBIPRECVSTATE enmRecvState;
+ /** Scratch space for holding the data until it was completely received.
+ * Which one to access is based on the current receive state. */
+ union
+ {
+ UsbIpRetDevList RetDevList;
+ UsbIpExportedDevice ExportedDevice;
+ UsbIpDeviceInterface DeviceInterface;
+ /** Byte view. */
+ uint8_t abRecv[1];
+ } Scratch;
+ /** Residual number of bytes to receive before we can work with the data. */
+ size_t cbResidualRecv;
+ /** Current pointer into the scratch buffer. */
+ uint8_t *pbRecvBuf;
+ /** Number of devices left to receive for the current request. */
+ uint32_t cDevicesLeft;
+ /** Number of interfaces to skip during receive. */
+ uint32_t cInterfacesLeft;
+ /** The current head pointer for the new device list. */
+ PUSBDEVICE pHead;
+ /** The next pointer to add a device to. */
+ PUSBDEVICE *ppNext;
+ /** Current amount of devices in the list. */
+ uint32_t cDevicesCur;
+ /** Timestamp of the last time we successfully connected. */
+ uint64_t tsConnectSuccessLast;
+};
+
+/**
+ * Convert the given exported device structure from host to network byte order.
+ *
+ * @returns nothing.
+ * @param pDevice The device structure to convert.
+ */
+DECLINLINE(void) usbProxyBackendUsbIpExportedDeviceN2H(PUsbIpExportedDevice pDevice)
+{
+ pDevice->u32BusNum = RT_N2H_U32(pDevice->u32BusNum);
+ pDevice->u32DevNum = RT_N2H_U32(pDevice->u32DevNum);
+ pDevice->u32Speed = RT_N2H_U32(pDevice->u32Speed);
+ pDevice->u16VendorId = RT_N2H_U16(pDevice->u16VendorId);
+ pDevice->u16ProductId = RT_N2H_U16(pDevice->u16ProductId);
+ pDevice->u16BcdDevice = RT_N2H_U16(pDevice->u16BcdDevice);
+}
+
+/**
+ * Initialize data members.
+ */
+USBProxyBackendUsbIp::USBProxyBackendUsbIp()
+ : USBProxyBackend()
+{
+}
+
+USBProxyBackendUsbIp::~USBProxyBackendUsbIp()
+{
+
+}
+
+/**
+ * Initializes the object (called right after construction).
+ *
+ * @returns S_OK on success and non-fatal failures, some COM error otherwise.
+ */
+int USBProxyBackendUsbIp::init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings)
+{
+ int rc = VINF_SUCCESS;
+
+ USBProxyBackend::init(pUsbProxyService, strId, strAddress, fLoadingSettings);
+
+ unconst(m_strBackend) = Utf8Str("USBIP");
+
+ m = new Data;
+
+ m->tsConnectSuccessLast = 0;
+
+ /* Split address into hostname and port. */
+ RTCList<RTCString> lstAddress = strAddress.split(":");
+ if (lstAddress.size() < 1)
+ return VERR_INVALID_PARAMETER;
+ m->pszHost = RTStrDup(lstAddress[0].c_str());
+ if (!m->pszHost)
+ return VERR_NO_STR_MEMORY;
+ if (lstAddress.size() == 2)
+ {
+ m->uPort = lstAddress[1].toUInt32();
+ if (!m->uPort)
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Setup wakeup pipe and poll set first. */
+ rc = RTSemFastMutexCreate(&m->hMtxDevices);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPipeCreate(&m->hWakeupPipeR, &m->hWakeupPipeW, 0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPollSetCreate(&m->hPollSet);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPollSetAddPipe(m->hPollSet, m->hWakeupPipeR,
+ RTPOLL_EVT_READ, USBIP_POLL_ID_PIPE);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Connect to the USB/IP host. Be more graceful to connection errors
+ * if we are instantiated while the settings are loaded to let
+ * VBoxSVC start.
+ *
+ * The worker thread keeps trying to connect every few seconds until
+ * either the USB source is removed by the user or the USB server is
+ * reachable.
+ */
+ rc = reconnect();
+ if (RT_SUCCESS(rc) || fLoadingSettings)
+ rc = start(); /* Start service thread. */
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTPollSetRemove(m->hPollSet, USBIP_POLL_ID_PIPE);
+ int rc2 = RTPollSetDestroy(m->hPollSet);
+ AssertRC(rc2);
+ m->hPollSet = NIL_RTPOLLSET;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = RTPipeClose(m->hWakeupPipeR);
+ AssertRC(rc2);
+ rc2 = RTPipeClose(m->hWakeupPipeW);
+ AssertRC(rc2);
+ m->hWakeupPipeR = m->hWakeupPipeW = NIL_RTPIPE;
+ }
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTSemFastMutexDestroy(m->hMtxDevices);
+ m->hMtxDevices = NIL_RTSEMFASTMUTEX;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Stop all service threads and free the device chain.
+ */
+void USBProxyBackendUsbIp::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /*
+ * Stop the service.
+ */
+ if (isActive())
+ stop();
+
+ /*
+ * Free resources.
+ */
+ if (m->hPollSet != NIL_RTPOLLSET)
+ {
+ disconnect();
+
+ int rc = RTPollSetRemove(m->hPollSet, USBIP_POLL_ID_PIPE);
+ AssertRC(rc);
+ rc = RTPollSetDestroy(m->hPollSet);
+ AssertRC(rc);
+ rc = RTPipeClose(m->hWakeupPipeR);
+ AssertRC(rc);
+ rc = RTPipeClose(m->hWakeupPipeW);
+ AssertRC(rc);
+
+ m->hPollSet = NIL_RTPOLLSET;
+ m->hWakeupPipeR = NIL_RTPIPE;
+ m->hWakeupPipeW = NIL_RTPIPE;
+ }
+
+ if (m->pszHost)
+ RTStrFree(m->pszHost);
+ if (m->hMtxDevices != NIL_RTSEMFASTMUTEX)
+ {
+ RTSemFastMutexDestroy(m->hMtxDevices);
+ m->hMtxDevices = NIL_RTSEMFASTMUTEX;
+ }
+
+ delete m;
+ USBProxyBackend::uninit();
+}
+
+
+int USBProxyBackendUsbIp::captureDevice(HostUSBDevice *aDevice)
+{
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ /*
+ * We don't need to do anything when the device is held... fake it.
+ */
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_Capturing);
+ devLock.release();
+
+ return VINF_SUCCESS;
+}
+
+
+int USBProxyBackendUsbIp::releaseDevice(HostUSBDevice *aDevice)
+{
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ /*
+ * We're not really holding it atm., just fake it.
+ */
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_ReleasingToHost);
+ devLock.release();
+
+ return VINF_SUCCESS;
+}
+
+
+bool USBProxyBackendUsbIp::isFakeUpdateRequired()
+{
+ return true;
+}
+
+
+int USBProxyBackendUsbIp::wait(RTMSINTERVAL aMillies)
+{
+ int rc = VINF_SUCCESS;
+ bool fDeviceListChangedOrWokenUp = false;
+
+ /* Don't start any possibly lengthy operation if we are supposed to return immediately again. */
+ if (!aMillies)
+ return VINF_SUCCESS;
+
+ /* Try to reconnect once when we enter if we lost the connection earlier. */
+ if (m->hSocket == NIL_RTSOCKET)
+ reconnect();
+
+ /* Query a new device list upon entering. */
+ if ( m->hSocket != NIL_RTSOCKET
+ && m->enmRecvState == kUsbIpRecvState_None)
+ {
+ rc = startListExportedDevicesReq();
+ if (RT_FAILURE(rc))
+ disconnect();
+ }
+
+ /*
+ * Because the USB/IP protocol doesn't specify a way to get notified about
+ * new or removed exported devices we have to poll the host periodically for
+ * a new device list and compare it with the previous one notifying the proxy
+ * service about changes.
+ */
+ while ( !fDeviceListChangedOrWokenUp
+ && (aMillies == RT_INDEFINITE_WAIT || aMillies > 0)
+ && RT_SUCCESS(rc))
+ {
+ RTMSINTERVAL msWait = aMillies;
+ uint64_t msPollStart = RTTimeMilliTS();
+ uint32_t uIdReady = 0;
+ uint32_t fEventsRecv = 0;
+
+ /* Limit the waiting time to 3sec so we can either reconnect or get a new device list. */
+ if (m->hSocket == NIL_RTSOCKET || m->enmRecvState == kUsbIpRecvState_None)
+ msWait = RT_MIN(3000, aMillies);
+
+ rc = RTPoll(m->hPollSet, msWait, &fEventsRecv, &uIdReady);
+ if (RT_SUCCESS(rc))
+ {
+ if (uIdReady == USBIP_POLL_ID_PIPE)
+ {
+ /* Drain the wakeup pipe. */
+ char bRead = 0;
+ size_t cbRead = 0;
+
+ rc = RTPipeRead(m->hWakeupPipeR, &bRead, 1, &cbRead);
+ Assert(RT_SUCCESS(rc) && cbRead == 1);
+ fDeviceListChangedOrWokenUp = true;
+ }
+ else if (uIdReady == USBIP_POLL_ID_SOCKET)
+ {
+ if (fEventsRecv & RTPOLL_EVT_READ)
+ rc = receiveData();
+ if ( RT_SUCCESS(rc)
+ && (fEventsRecv & RTPOLL_EVT_ERROR))
+ rc = VERR_NET_SHUTDOWN;
+
+ /*
+ * If we are in the none state again we received the previous request
+ * and have a new device list to compare the old against.
+ */
+ if (m->enmRecvState == kUsbIpRecvState_None)
+ {
+ if (hasDevListChanged(m->pHead))
+ fDeviceListChangedOrWokenUp = true;
+
+ /* Update to the new list in any case now that we have it anyway. */
+ RTSemFastMutexRequest(m->hMtxDevices);
+ freeDeviceList(m->pUsbDevicesCur);
+ m->cUsbDevicesCur = m->cDevicesCur;
+ m->pUsbDevicesCur = m->pHead;
+ RTSemFastMutexRelease(m->hMtxDevices);
+
+ m->pHead = NULL;
+ resetRecvState();
+ }
+
+ /* Current USB/IP server closes the connection after each request, don't abort but try again. */
+ if (rc == VERR_NET_SHUTDOWN || rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_RESET_BY_PEER)
+ {
+ Log(("USB/IP: Lost connection to host \"%s\", trying to reconnect...\n", m->pszHost));
+ disconnect();
+ rc = VINF_SUCCESS;
+ }
+ }
+ else
+ {
+ AssertMsgFailed(("Invalid poll ID returned\n"));
+ rc = VERR_INVALID_STATE;
+ }
+ aMillies -= (RTMSINTERVAL)(RTTimeMilliTS() - msPollStart);
+ }
+ else if (rc == VERR_TIMEOUT)
+ {
+ aMillies -= msWait;
+ if (aMillies)
+ {
+ /* Try to reconnect and start a new request if we lost the connection before. */
+ if (m->hSocket == NIL_RTSOCKET)
+ {
+ rc = reconnect();
+ if (RT_SUCCESS(rc))
+ rc = startListExportedDevicesReq();
+ else if ( rc == VERR_NET_SHUTDOWN
+ || rc == VERR_BROKEN_PIPE
+ || rc == VERR_NET_CONNECTION_RESET_BY_PEER
+ || rc == VERR_NET_CONNECTION_REFUSED)
+ {
+ if (hasDevListChanged(m->pHead))
+ fDeviceListChangedOrWokenUp = true;
+ rc = VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ }
+
+ LogFlowFunc(("return rc=%Rrc\n", rc));
+ return rc;
+}
+
+
+int USBProxyBackendUsbIp::interruptWait(void)
+{
+ AssertReturn(!isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int rc = RTPipeWriteBlocking(m->hWakeupPipeW, "", 1, NULL);
+ if (RT_SUCCESS(rc))
+ RTPipeFlush(m->hWakeupPipeW);
+ LogFlowFunc(("returning %Rrc\n", rc));
+ return rc;
+}
+
+
+PUSBDEVICE USBProxyBackendUsbIp::getDevices(void)
+{
+ PUSBDEVICE pFirst = NULL;
+ PUSBDEVICE *ppNext = &pFirst;
+
+ LogFlowThisFunc(("\n"));
+
+ /* Create a deep copy of the device list. */
+ RTSemFastMutexRequest(m->hMtxDevices);
+ PUSBDEVICE pCur = m->pUsbDevicesCur;
+ while (pCur)
+ {
+ PUSBDEVICE pNew = (PUSBDEVICE)RTMemAllocZ(sizeof(USBDEVICE));
+ if (pNew)
+ {
+ pNew->pszManufacturer = RTStrDup(pCur->pszManufacturer);
+ pNew->pszProduct = RTStrDup(pCur->pszProduct);
+ if (pCur->pszSerialNumber)
+ pNew->pszSerialNumber = RTStrDup(pCur->pszSerialNumber);
+ pNew->pszBackend = RTStrDup(pCur->pszBackend);
+ pNew->pszAddress = RTStrDup(pCur->pszAddress);
+
+ pNew->idVendor = pCur->idVendor;
+ pNew->idProduct = pCur->idProduct;
+ pNew->bcdDevice = pCur->bcdDevice;
+ pNew->bcdUSB = pCur->bcdUSB;
+ pNew->bDeviceClass = pCur->bDeviceClass;
+ pNew->bDeviceSubClass = pCur->bDeviceSubClass;
+ pNew->bDeviceProtocol = pCur->bDeviceProtocol;
+ pNew->bNumConfigurations = pCur->bNumConfigurations;
+ pNew->enmState = pCur->enmState;
+ pNew->u64SerialHash = pCur->u64SerialHash;
+ pNew->bBus = pCur->bBus;
+ pNew->bPort = pCur->bPort;
+ pNew->enmSpeed = pCur->enmSpeed;
+
+ /* link it */
+ pNew->pNext = NULL;
+ pNew->pPrev = *ppNext;
+ *ppNext = pNew;
+ ppNext = &pNew->pNext;
+ }
+
+ pCur = pCur->pNext;
+ }
+ RTSemFastMutexRelease(m->hMtxDevices);
+
+ LogFlowThisFunc(("returning %#p\n", pFirst));
+ return pFirst;
+}
+
+/**
+ * Frees a given device list.
+ *
+ * @returns nothing.
+ * @param pHead The head of the device list to free.
+ */
+void USBProxyBackendUsbIp::freeDeviceList(PUSBDEVICE pHead)
+{
+ PUSBDEVICE pNext = pHead;
+ while (pNext)
+ {
+ PUSBDEVICE pFree = pNext;
+ pNext = pNext->pNext;
+ freeDevice(pFree);
+ }
+}
+
+/**
+ * Resets the receive state to the idle state.
+ *
+ * @returns nothing.
+ */
+void USBProxyBackendUsbIp::resetRecvState()
+{
+ LogFlowFunc(("\n"));
+ freeDeviceList(m->pHead);
+ m->pHead = NULL;
+ m->ppNext = &m->pHead;
+ m->cDevicesCur = 0;
+ m->enmRecvState = kUsbIpRecvState_None;
+ m->cbResidualRecv = 0;
+ m->pbRecvBuf = &m->Scratch.abRecv[0];
+ m->cDevicesLeft = 0;
+ LogFlowFunc(("\n"));
+}
+
+/**
+ * Disconnects from the host and resets the receive state.
+ *
+ * @returns nothing.
+ */
+void USBProxyBackendUsbIp::disconnect()
+{
+ LogFlowFunc(("\n"));
+
+ if (m->hSocket != NIL_RTSOCKET)
+ {
+ int rc = RTPollSetRemove(m->hPollSet, USBIP_POLL_ID_SOCKET);
+ NOREF(rc);
+ Assert(RT_SUCCESS(rc) || rc == VERR_POLL_HANDLE_ID_NOT_FOUND);
+
+ RTTcpClientCloseEx(m->hSocket, false /*fGracefulShutdown*/);
+ m->hSocket = NIL_RTSOCKET;
+ }
+
+ resetRecvState();
+ LogFlowFunc(("returns\n"));
+}
+
+/**
+ * Tries to reconnect to the USB/IP host.
+ *
+ * @returns VBox status code.
+ */
+int USBProxyBackendUsbIp::reconnect()
+{
+ LogFlowFunc(("\n"));
+
+ /* Make sure we are disconnected. */
+ disconnect();
+
+ /* Connect to the USB/IP host. */
+ int rc = RTTcpClientConnect(m->pszHost, m->uPort, &m->hSocket);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTTcpSetSendCoalescing(m->hSocket, false);
+ if (RT_FAILURE(rc))
+ LogRelMax(5, ("USB/IP: Disabling send coalescing failed (rc=%Rrc), continuing nevertheless but expect increased latency\n", rc));
+
+ rc = RTPollSetAddSocket(m->hPollSet, m->hSocket, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR,
+ USBIP_POLL_ID_SOCKET);
+ if (RT_FAILURE(rc))
+ {
+ RTTcpClientCloseEx(m->hSocket, false /*fGracefulShutdown*/);
+ m->hSocket = NIL_RTSOCKET;
+ }
+ else
+ {
+ LogFlowFunc(("Connected to host \"%s\"\n", m->pszHost));
+ m->tsConnectSuccessLast = RTTimeMilliTS();
+ }
+ }
+ else if (m->tsConnectSuccessLast + 10 * RT_MS_1SEC < RTTimeMilliTS())
+ {
+ /* Make sure the device list is clear if we failed to reconnect for some time. */
+ RTSemFastMutexRequest(m->hMtxDevices);
+ if (m->pUsbDevicesCur)
+ {
+ freeDeviceList(m->pUsbDevicesCur);
+ m->cUsbDevicesCur = 0;
+ m->pUsbDevicesCur = NULL;
+ }
+ RTSemFastMutexRelease(m->hMtxDevices);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Initiates a new List Exported Devices request.
+ *
+ * @returns VBox status code.
+ */
+int USBProxyBackendUsbIp::startListExportedDevicesReq()
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("\n"));
+
+ /*
+ * Reset the current state and reconnect in case we were called in the middle
+ * of another transfer (which should not happen).
+ */
+ Assert(m->enmRecvState == kUsbIpRecvState_None);
+ if (m->enmRecvState != kUsbIpRecvState_None)
+ rc = reconnect();
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Send of the request. */
+ UsbIpReqDevList ReqDevList;
+ ReqDevList.u16Version = RT_H2N_U16(USBIP_VERSION);
+ ReqDevList.u16Cmd = RT_H2N_U16(USBIP_INDICATOR_REQ | USBIP_REQ_RET_DEVLIST);
+ ReqDevList.i32Status = RT_H2N_S32(0);
+
+ rc = RTTcpWrite(m->hSocket, &ReqDevList, sizeof(ReqDevList));
+ if (RT_SUCCESS(rc))
+ advanceState(kUsbIpRecvState_Hdr);
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Advances the state machine to the given state.
+ *
+ * @returns nothing.
+ * @param enmRecvState The new receive state.
+ */
+void USBProxyBackendUsbIp::advanceState(USBIPRECVSTATE enmRecvState)
+{
+ LogFlowFunc(("enmRecvState=%u\n", enmRecvState));
+
+ switch (enmRecvState)
+ {
+ case kUsbIpRecvState_None:
+ break;
+ case kUsbIpRecvState_Hdr:
+ {
+ m->cbResidualRecv = sizeof(UsbIpRetDevList);
+ m->pbRecvBuf = (uint8_t *)&m->Scratch.RetDevList;
+ break;
+ }
+ case kUsbIpRecvState_ExportedDevice:
+ {
+ m->cbResidualRecv = sizeof(UsbIpExportedDevice);
+ m->pbRecvBuf = (uint8_t *)&m->Scratch.ExportedDevice;
+ break;
+ }
+ case kUsbIpRecvState_DeviceInterface:
+ {
+ m->cbResidualRecv = sizeof(UsbIpDeviceInterface);
+ m->pbRecvBuf = (uint8_t *)&m->Scratch.DeviceInterface;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid USB/IP receive state %d\n", enmRecvState));
+ return;
+ }
+
+ m->enmRecvState = enmRecvState;
+ LogFlowFunc(("returns\n"));
+}
+
+/**
+ * Receives data from the USB/IP host and processes it when everything for the current
+ * state was received.
+ *
+ * @returns VBox status code.
+ */
+int USBProxyBackendUsbIp::receiveData()
+{
+ int rc = VINF_SUCCESS;
+ size_t cbRecvd = 0;
+
+ LogFlowFunc(("\n"));
+
+ do
+ {
+ rc = RTTcpReadNB(m->hSocket, m->pbRecvBuf, m->cbResidualRecv, &cbRecvd);
+
+ LogFlowFunc(("RTTcpReadNB(%#p, %#p, %zu, %zu) -> %Rrc\n",
+ m->hSocket, m->pbRecvBuf, m->cbResidualRecv, cbRecvd, rc));
+
+ if ( rc == VINF_SUCCESS
+ && cbRecvd > 0)
+ {
+ m->cbResidualRecv -= cbRecvd;
+ m->pbRecvBuf += cbRecvd;
+ /* In case we received everything for the current state process the data. */
+ if (!m->cbResidualRecv)
+ {
+ rc = processData();
+ if ( RT_SUCCESS(rc)
+ && m->enmRecvState == kUsbIpRecvState_None)
+ break;
+ }
+ }
+ else if (rc == VINF_TRY_AGAIN)
+ Assert(!cbRecvd);
+
+ } while (rc == VINF_SUCCESS && cbRecvd > 0);
+
+ if (rc == VINF_TRY_AGAIN)
+ rc = VINF_SUCCESS;
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Processes the data in the scratch buffer based on the current state.
+ *
+ * @returns VBox status code.
+ */
+int USBProxyBackendUsbIp::processData()
+{
+ int rc = VINF_SUCCESS;
+
+ switch (m->enmRecvState)
+ {
+ case kUsbIpRecvState_Hdr:
+ {
+ /* Check that the reply matches our expectations. */
+ if ( RT_N2H_U16(m->Scratch.RetDevList.u16Version) == USBIP_VERSION
+ && RT_N2H_U16(m->Scratch.RetDevList.u16Cmd) == USBIP_REQ_RET_DEVLIST
+ && RT_N2H_S32(m->Scratch.RetDevList.i32Status) == USBIP_STATUS_SUCCESS)
+
+ {
+ /* Populate the number of exported devices in the list and go to the next state. */
+ m->cDevicesLeft = RT_N2H_U32(m->Scratch.RetDevList.u32DevicesExported);
+ if (m->cDevicesLeft)
+ advanceState(kUsbIpRecvState_ExportedDevice);
+ else
+ advanceState(kUsbIpRecvState_None);
+ }
+ else
+ {
+ LogRelMax(10, ("USB/IP: Host sent an invalid reply to the list exported device request (Version: %#x Cmd: %#x Status: %#x)\n",
+ RT_N2H_U16(m->Scratch.RetDevList.u16Version), RT_N2H_U16(m->Scratch.RetDevList.u16Cmd),
+ RT_N2H_S32(m->Scratch.RetDevList.i32Status)));
+ /* Disconnect and start over. */
+ advanceState(kUsbIpRecvState_None);
+ disconnect();
+ rc = VERR_NET_SHUTDOWN;
+ }
+ break;
+ }
+ case kUsbIpRecvState_ExportedDevice:
+ {
+ /* Create a new device and add it to the list. */
+ usbProxyBackendUsbIpExportedDeviceN2H(&m->Scratch.ExportedDevice);
+ rc = addDeviceToList(&m->Scratch.ExportedDevice);
+ if (RT_SUCCESS(rc))
+ {
+ m->cInterfacesLeft = m->Scratch.ExportedDevice.bNumInterfaces;
+ if (m->cInterfacesLeft)
+ advanceState(kUsbIpRecvState_DeviceInterface);
+ else
+ {
+ m->cDevicesLeft--;
+ if (m->cDevicesLeft)
+ advanceState(kUsbIpRecvState_ExportedDevice);
+ else
+ advanceState(kUsbIpRecvState_None);
+ }
+ }
+ break;
+ }
+ case kUsbIpRecvState_DeviceInterface:
+ {
+ /*
+ * If all interfaces for the current device were received receive the next device
+ * if there is another one left, if not we are done with the current request.
+ */
+ m->cInterfacesLeft--;
+ if (m->cInterfacesLeft)
+ advanceState(kUsbIpRecvState_DeviceInterface);
+ else
+ {
+ m->cDevicesLeft--;
+ if (m->cDevicesLeft)
+ advanceState(kUsbIpRecvState_ExportedDevice);
+ else
+ advanceState(kUsbIpRecvState_None);
+ }
+ break;
+ }
+ case kUsbIpRecvState_None:
+ default:
+ AssertMsgFailed(("Invalid USB/IP receive state %d\n", m->enmRecvState));
+ return VERR_INVALID_STATE;
+ }
+
+ return rc;
+}
+
+/**
+ * Creates a new USB device and adds it to the list.
+ *
+ * @returns VBox status code.
+ * @param pDev Pointer to the USB/IP exported device structure to take
+ * the information for the new device from.
+ */
+int USBProxyBackendUsbIp::addDeviceToList(PUsbIpExportedDevice pDev)
+{
+ int rc = VINF_SUCCESS;
+ PUSBDEVICE pNew = (PUSBDEVICE)RTMemAllocZ(sizeof(USBDEVICE));
+ if (!pNew)
+ return VERR_NO_MEMORY;
+
+ pNew->pszManufacturer = RTStrDup("");
+ pNew->pszProduct = RTStrDup("");
+ pNew->pszSerialNumber = NULL;
+ pNew->pszBackend = RTStrDup("usbip");
+
+ /* Make sure the Bus id is 0 terminated. */
+ pDev->szBusId[31] = '\0';
+ pNew->pszAddress = RTStrAPrintf2("usbip://%s:%u:%s", m->pszHost, m->uPort, &pDev->szBusId[0]);
+ if (RT_LIKELY(pNew->pszAddress))
+ {
+ pNew->idVendor = pDev->u16VendorId;
+ pNew->idProduct = pDev->u16ProductId;
+ pNew->bcdDevice = pDev->u16BcdDevice;
+ pNew->bDeviceClass = pDev->bDeviceClass;
+ pNew->bDeviceSubClass = pDev->bDeviceSubClass;
+ pNew->bDeviceProtocol = pDev->bDeviceProtocol;
+ pNew->bNumConfigurations = pDev->bNumConfigurations;
+ pNew->enmState = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE;
+ pNew->u64SerialHash = 0;
+ /** @todo The following is not correct but is required to to get USB testing working
+ * because only the port can be part of a filter (adding the required attributes for the bus
+ * breaks API and ABI compatibility).
+ * Filtering by port number is required for USB testing to connect to the correct device
+ * in case there are multiple ones.
+ */
+ pNew->bBus = (uint8_t)pDev->u32DevNum;
+ pNew->bPort = (uint8_t)pDev->u32BusNum;
+
+ switch (pDev->u32Speed)
+ {
+ case USBIP_SPEED_LOW:
+ pNew->enmSpeed = USBDEVICESPEED_LOW;
+ pNew->bcdUSB = 1 << 8;
+ break;
+ case USBIP_SPEED_FULL:
+ pNew->enmSpeed = USBDEVICESPEED_FULL;
+ pNew->bcdUSB = 1 << 8;
+ break;
+ case USBIP_SPEED_HIGH:
+ pNew->enmSpeed = USBDEVICESPEED_HIGH;
+ pNew->bcdUSB = 2 << 8;
+ break;
+ case USBIP_SPEED_WIRELESS:
+ pNew->enmSpeed = USBDEVICESPEED_VARIABLE;
+ pNew->bcdUSB = 1 << 8;
+ break;
+ case USBIP_SPEED_SUPER:
+ pNew->enmSpeed = USBDEVICESPEED_SUPER;
+ pNew->bcdUSB = 3 << 8;
+ break;
+ case USBIP_SPEED_UNKNOWN:
+ default:
+ pNew->bcdUSB = 1 << 8;
+ pNew->enmSpeed = USBDEVICESPEED_UNKNOWN;
+ }
+
+ /* link it */
+ pNew->pNext = NULL;
+ pNew->pPrev = *m->ppNext;
+ *m->ppNext = pNew;
+ m->ppNext = &pNew->pNext;
+ m->cDevicesCur++;
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+
+ if (RT_FAILURE(rc))
+ {
+ if (pNew->pszManufacturer)
+ RTStrFree((char *)pNew->pszManufacturer);
+ if (pNew->pszProduct)
+ RTStrFree((char *)pNew->pszProduct);
+ if (pNew->pszBackend)
+ RTStrFree((char *)pNew->pszBackend);
+ if (pNew->pszAddress)
+ RTStrFree((char *)pNew->pszAddress);
+ RTMemFree(pNew);
+ }
+
+ return rc;
+}
+
+/**
+ * Compares the given device list with the current one and returns whether it has
+ * changed.
+ *
+ * @returns flag whether the device list has changed compared to the current one.
+ * @param pDevices The device list to compare the current one against.
+ */
+bool USBProxyBackendUsbIp::hasDevListChanged(PUSBDEVICE pDevices)
+{
+ /** @todo */
+ NOREF(pDevices);
+ return true;
+}
+
diff --git a/src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp b/src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp
new file mode 100644
index 00000000..736b19b6
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp
@@ -0,0 +1,252 @@
+/* $Id: HostDnsServiceLinux.cpp $ */
+/** @file
+ * Linux specific DNS information fetching.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+
+#include <errno.h>
+#include <poll.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+
+#include <linux/limits.h>
+
+/* Workaround for <sys/cdef.h> defining __flexarr to [] which beats us in
+ * struct inotify_event (char name __flexarr). */
+#include <sys/cdefs.h>
+#undef __flexarr
+#define __flexarr [0]
+#include <sys/inotify.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <iprt/sanitized/string>
+#include <vector>
+#include "../HostDnsService.h"
+
+
+static int g_DnsMonitorStop[2];
+
+static const std::string g_EtcFolder = "/etc";
+static const std::string g_ResolvConf = "resolv.conf";
+static const std::string g_ResolvConfFullPath = "/etc/resolv.conf";
+
+class FileDescriptor
+{
+ public:
+ FileDescriptor(int d = -1):fd(d){}
+
+ virtual ~FileDescriptor() {
+ if (fd != -1)
+ close(fd);
+ }
+
+ int fileDescriptor() const {return fd;}
+
+ protected:
+ int fd;
+};
+
+
+class AutoNotify:public FileDescriptor
+{
+ public:
+ AutoNotify()
+ {
+ FileDescriptor::fd = inotify_init();
+ AssertReturnVoid(FileDescriptor::fd != -1);
+ }
+};
+
+struct InotifyEventWithName
+{
+ struct inotify_event e;
+ char name[NAME_MAX];
+};
+
+HostDnsServiceLinux::~HostDnsServiceLinux()
+{
+}
+
+HRESULT HostDnsServiceLinux::init(HostDnsMonitorProxy *pProxy)
+{
+ return HostDnsServiceResolvConf::init(pProxy, "/etc/resolv.conf");
+}
+
+int HostDnsServiceLinux::monitorThreadShutdown(RTMSINTERVAL uTimeoutMs)
+{
+ RT_NOREF(uTimeoutMs);
+
+ send(g_DnsMonitorStop[0], "", 1, 0);
+
+ /** @todo r=andy Do we have to wait for something here? Can this fail? */
+ return VINF_SUCCESS;
+}
+
+int HostDnsServiceLinux::monitorThreadProc(void)
+{
+ AutoNotify a;
+
+ int rc = socketpair(AF_LOCAL, SOCK_DGRAM, 0, g_DnsMonitorStop);
+ AssertMsgReturn(rc == 0, ("socketpair: failed (%d: %s)\n", errno, strerror(errno)), E_FAIL);
+
+ FileDescriptor stopper0(g_DnsMonitorStop[0]);
+ FileDescriptor stopper1(g_DnsMonitorStop[1]);
+
+ pollfd polls[2];
+ RT_ZERO(polls);
+
+ polls[0].fd = a.fileDescriptor();
+ polls[0].events = POLLIN;
+
+ polls[1].fd = g_DnsMonitorStop[1];
+ polls[1].events = POLLIN;
+
+ onMonitorThreadInitDone();
+
+ int wd[2];
+ wd[0] = wd[1] = -1;
+ /* inotify inialization */
+ wd[0] = inotify_add_watch(a.fileDescriptor(),
+ g_ResolvConfFullPath.c_str(), IN_CLOSE_WRITE|IN_DELETE_SELF);
+
+ /**
+ * If /etc/resolv.conf exists we want to listen for movements: because
+ * # mv /etc/resolv.conf ...
+ * won't arm IN_DELETE_SELF on wd[0] instead it will fire IN_MOVE_FROM on wd[1].
+ *
+ * Because on some distributions /etc/resolv.conf is link, wd[0] can't detect deletion,
+ * it's recognizible on directory level (wd[1]) only.
+ */
+ wd[1] = inotify_add_watch(a.fileDescriptor(), g_EtcFolder.c_str(),
+ wd[0] == -1 ? IN_MOVED_TO|IN_CREATE : IN_MOVED_FROM|IN_DELETE);
+
+ struct InotifyEventWithName combo;
+ while(true)
+ {
+ rc = poll(polls, 2, -1);
+ if (rc == -1)
+ continue;
+
+ AssertMsgReturn( ((polls[0].revents & (POLLERR|POLLNVAL)) == 0)
+ && ((polls[1].revents & (POLLERR|POLLNVAL)) == 0),
+ ("Debug Me"), VERR_INTERNAL_ERROR);
+
+ if (polls[1].revents & POLLIN)
+ return VINF_SUCCESS; /* time to shutdown */
+
+ if (polls[0].revents & POLLIN)
+ {
+ RT_ZERO(combo);
+ ssize_t r = read(polls[0].fd, static_cast<void *>(&combo), sizeof(combo));
+ RT_NOREF(r);
+
+ if (combo.e.wd == wd[0])
+ {
+ if (combo.e.mask & IN_CLOSE_WRITE)
+ {
+ readResolvConf();
+ }
+ else if (combo.e.mask & IN_DELETE_SELF)
+ {
+ inotify_rm_watch(a.fileDescriptor(), wd[0]); /* removes file watcher */
+ inotify_add_watch(a.fileDescriptor(), g_EtcFolder.c_str(),
+ IN_MOVED_TO|IN_CREATE); /* alter folder watcher */
+ }
+ else if (combo.e.mask & IN_IGNORED)
+ {
+ wd[0] = -1; /* we want receive any events on this watch */
+ }
+ else
+ {
+ /**
+ * It shouldn't happen, in release we will just ignore in debug
+ * we will have to chance to look at into inotify_event
+ */
+ AssertMsgFailed(("Debug Me!!!"));
+ }
+ }
+ else if (combo.e.wd == wd[1])
+ {
+ if ( combo.e.mask & IN_MOVED_FROM
+ || combo.e.mask & IN_DELETE)
+ {
+ if (g_ResolvConf == combo.e.name)
+ {
+ /**
+ * Our file has been moved so we should change watching mode.
+ */
+ inotify_rm_watch(a.fileDescriptor(), wd[0]);
+ wd[1] = inotify_add_watch(a.fileDescriptor(), g_EtcFolder.c_str(),
+ IN_MOVED_TO|IN_CREATE);
+ AssertMsg(wd[1] != -1,
+ ("It shouldn't happen, further investigation is needed\n"));
+ }
+ }
+ else
+ {
+ AssertMsg(combo.e.mask & (IN_MOVED_TO|IN_CREATE),
+ ("%RX32 event isn't expected, we are waiting for IN_MOVED|IN_CREATE\n",
+ combo.e.mask));
+ if (g_ResolvConf == combo.e.name)
+ {
+ AssertMsg(wd[0] == -1, ("We haven't removed file watcher first\n"));
+
+ /* alter folder watcher*/
+ wd[1] = inotify_add_watch(a.fileDescriptor(), g_EtcFolder.c_str(),
+ IN_MOVED_FROM|IN_DELETE);
+ AssertMsg(wd[1] != -1, ("It shouldn't happen.\n"));
+
+ wd[0] = inotify_add_watch(a.fileDescriptor(),
+ g_ResolvConfFullPath.c_str(),
+ IN_CLOSE_WRITE | IN_DELETE_SELF);
+ AssertMsg(wd[0] != -1, ("Adding watcher to file (%s) has been failed!\n",
+ g_ResolvConfFullPath.c_str()));
+
+ /* Notify our listeners */
+ readResolvConf();
+ }
+ }
+ }
+ else
+ {
+ /* It shouldn't happen */
+ AssertMsgFailed(("Shouldn't happen! Please debug me!"));
+ }
+ }
+ }
+}
+
diff --git a/src/VBox/Main/src-server/linux/HostHardwareLinux.cpp b/src/VBox/Main/src-server/linux/HostHardwareLinux.cpp
new file mode 100644
index 00000000..74d6d4cd
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/HostHardwareLinux.cpp
@@ -0,0 +1,1369 @@
+/* $Id: HostHardwareLinux.cpp $ */
+/** @file
+ * VirtualBox Main - Code for handling hardware detection under Linux, VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN
+#include "HostHardwareLinux.h"
+
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <iprt/asm.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+#include <linux/cdrom.h>
+#include <linux/fd.h>
+#include <linux/major.h>
+
+#include <linux/version.h>
+#include <scsi/scsi.h>
+
+#include <iprt/linux/sysfs.h>
+
+#ifdef VBOX_USB_WITH_SYSFS
+# ifdef VBOX_USB_WITH_INOTIFY
+# include <dlfcn.h>
+# include <fcntl.h>
+# include <poll.h>
+# include <signal.h>
+# include <unistd.h>
+# endif
+#endif
+
+#include <vector>
+
+#include <errno.h>
+#include <dirent.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/sysmacros.h>
+
+/*
+ * Define NVME constant here to allow building
+ * on several kernel versions even if the
+ * building host doesn't contain certain NVME
+ * includes
+ */
+#define NVME_IOCTL_ID _IO('N', 0x40)
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef TESTCASE
+static bool testing() { return true; }
+static bool fNoProbe = false;
+static bool noProbe() { return fNoProbe; }
+static void setNoProbe(bool val) { fNoProbe = val; }
+#else
+static bool testing() { return false; }
+static bool noProbe() { return false; }
+static void setNoProbe(bool val) { (void)val; }
+#endif
+
+
+/*********************************************************************************************************************************
+* Typedefs and Defines *
+*********************************************************************************************************************************/
+typedef enum SysfsWantDevice_T
+{
+ DVD,
+ Floppy,
+ FixedDisk
+} SysfsWantDevice_T;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int getDriveInfoFromEnv(const char *pcszVar, DriveInfoList *pList, bool isDVD, bool *pfSuccess) RT_NOTHROW_PROTO;
+static int getDriveInfoFromSysfs(DriveInfoList *pList, SysfsWantDevice_T wantDevice, bool *pfSuccess) RT_NOTHROW_PROTO;
+
+
+/** Find the length of a string, ignoring trailing non-ascii or control
+ * characters
+ * @note Code duplicated in HostHardwareFreeBSD.cpp */
+static size_t strLenStripped(const char *pcsz) RT_NOTHROW_DEF
+{
+ size_t cch = 0;
+ for (size_t i = 0; pcsz[i] != '\0'; ++i)
+ if (pcsz[i] > 32 /*space*/ && pcsz[i] < 127 /*delete*/)
+ cch = i;
+ return cch + 1;
+}
+
+
+/**
+ * Get the name of a floppy drive according to the Linux floppy driver.
+ * @returns true on success, false if the name was not available (i.e. the
+ * device was not readable, or the file name wasn't a PC floppy
+ * device)
+ * @param pcszNode the path to the device node for the device
+ * @param Number the Linux floppy driver number for the drive. Required.
+ * @param pszName where to store the name retrieved
+ */
+static bool floppyGetName(const char *pcszNode, unsigned Number, floppy_drive_name pszName) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pcszNode, false);
+ AssertPtrReturn(pszName, false);
+ AssertReturn(Number <= 7, false);
+ RTFILE File;
+ int rc = RTFileOpen(&File, pcszNode, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK);
+ if (RT_SUCCESS(rc))
+ {
+ int rcIoCtl;
+ rc = RTFileIoCtl(File, FDGETDRVTYP, pszName, 0, &rcIoCtl);
+ RTFileClose(File);
+ if (RT_SUCCESS(rc) && rcIoCtl >= 0)
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Create a UDI and a description for a floppy drive based on a number and the
+ * driver's name for it. We deliberately return an ugly sequence of
+ * characters as the description rather than an English language string to
+ * avoid translation issues.
+ *
+ * @returns true if we know the device to be valid, false otherwise
+ * @param pcszName the floppy driver name for the device (optional)
+ * @param Number the number of the floppy (0 to 3 on FDC 0, 4 to 7 on
+ * FDC 1)
+ * @param pszDesc where to store the device description (optional)
+ * @param cbDesc the size of the buffer in @a pszDesc
+ * @param pszUdi where to store the device UDI (optional)
+ * @param cbUdi the size of the buffer in @a pszUdi
+ */
+static void floppyCreateDeviceStrings(const floppy_drive_name pcszName, unsigned Number,
+ char *pszDesc, size_t cbDesc, char *pszUdi, size_t cbUdi) RT_NOTHROW_DEF
+{
+ AssertPtrNullReturnVoid(pcszName);
+ AssertPtrNullReturnVoid(pszDesc);
+ AssertReturnVoid(!pszDesc || cbDesc > 0);
+ AssertPtrNullReturnVoid(pszUdi);
+ AssertReturnVoid(!pszUdi || cbUdi > 0);
+ AssertReturnVoid(Number <= 7);
+ if (pcszName)
+ {
+ const char *pcszSize;
+ switch(pcszName[0])
+ {
+ case 'd': case 'q': case 'h':
+ pcszSize = "5.25\"";
+ break;
+ case 'D': case 'H': case 'E': case 'u':
+ pcszSize = "3.5\"";
+ break;
+ default:
+ pcszSize = "(unknown)";
+ }
+ if (pszDesc)
+ RTStrPrintf(pszDesc, cbDesc, "%s %s K%s", pcszSize, &pcszName[1],
+ Number > 3 ? ", FDC 2" : "");
+ }
+ else
+ {
+ if (pszDesc)
+ RTStrPrintf(pszDesc, cbDesc, "FDD %d%s", (Number & 4) + 1,
+ Number > 3 ? ", FDC 2" : "");
+ }
+ if (pszUdi)
+ RTStrPrintf(pszUdi, cbUdi,
+ "/org/freedesktop/Hal/devices/platform_floppy_%u_storage",
+ Number);
+}
+
+
+/**
+ * Check whether a device number might correspond to a CD-ROM device according
+ * to Documentation/devices.txt in the Linux kernel source.
+ *
+ * @returns true if it might, false otherwise
+ * @param Number the device number (major and minor combination)
+ */
+static bool isCdromDevNum(dev_t Number) RT_NOTHROW_DEF
+{
+ int major = major(Number);
+ int minor = minor(Number);
+ if (major == IDE0_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == SCSI_CDROM_MAJOR)
+ return true;
+ if (major == CDU31A_CDROM_MAJOR)
+ return true;
+ if (major == GOLDSTAR_CDROM_MAJOR)
+ return true;
+ if (major == OPTICS_CDROM_MAJOR)
+ return true;
+ if (major == SANYO_CDROM_MAJOR)
+ return true;
+ if (major == MITSUMI_X_CDROM_MAJOR)
+ return true;
+ if (major == IDE1_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == MITSUMI_CDROM_MAJOR)
+ return true;
+ if (major == CDU535_CDROM_MAJOR)
+ return true;
+ if (major == MATSUSHITA_CDROM_MAJOR)
+ return true;
+ if (major == MATSUSHITA_CDROM2_MAJOR)
+ return true;
+ if (major == MATSUSHITA_CDROM3_MAJOR)
+ return true;
+ if (major == MATSUSHITA_CDROM4_MAJOR)
+ return true;
+ if (major == AZTECH_CDROM_MAJOR)
+ return true;
+ if (major == 30 /* CM205_CDROM_MAJOR */) /* no #define for some reason */
+ return true;
+ if (major == CM206_CDROM_MAJOR)
+ return true;
+ if (major == IDE3_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == 46 /* Parallel port ATAPI CD-ROM */) /* no #define */
+ return true;
+ if (major == IDE4_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == IDE5_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == IDE6_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == IDE7_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == IDE8_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == IDE9_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == 113 /* VIOCD_MAJOR */)
+ return true;
+ return false;
+}
+
+
+/**
+ * Send an SCSI INQUIRY command to a device and return selected information.
+ *
+ * @returns iprt status code
+ * @retval VERR_TRY_AGAIN if the query failed but might succeed next time
+ * @param pcszNode the full path to the device node
+ * @param pbType where to store the SCSI device type on success (optional)
+ * @param pszVendor where to store the vendor id string on success (optional)
+ * @param cbVendor the size of the @a pszVendor buffer
+ * @param pszModel where to store the product id string on success (optional)
+ * @param cbModel the size of the @a pszModel buffer
+ * @note check documentation on the SCSI INQUIRY command and the Linux kernel
+ * SCSI headers included above if you want to understand what is going
+ * on in this method.
+ */
+static int cdromDoInquiry(const char *pcszNode, uint8_t *pbType, char *pszVendor, size_t cbVendor,
+ char *pszModel, size_t cbModel) RT_NOTHROW_DEF
+{
+ LogRelFlowFunc(("pcszNode=%s, pbType=%p, pszVendor=%p, cbVendor=%zu, pszModel=%p, cbModel=%zu\n",
+ pcszNode, pbType, pszVendor, cbVendor, pszModel, cbModel));
+ AssertPtrReturn(pcszNode, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pbType, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pszVendor, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pszModel, VERR_INVALID_POINTER);
+
+ RTFILE hFile = NIL_RTFILE;
+ int rc = RTFileOpen(&hFile, pcszNode, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK);
+ if (RT_SUCCESS(rc))
+ {
+ int rcIoCtl = 0;
+ unsigned char auchResponse[96] = { 0 };
+ struct cdrom_generic_command CdromCommandReq;
+ RT_ZERO(CdromCommandReq);
+ CdromCommandReq.cmd[0] = INQUIRY;
+ CdromCommandReq.cmd[4] = sizeof(auchResponse);
+ CdromCommandReq.buffer = auchResponse;
+ CdromCommandReq.buflen = sizeof(auchResponse);
+ CdromCommandReq.data_direction = CGC_DATA_READ;
+ CdromCommandReq.timeout = 5000; /* ms */
+ rc = RTFileIoCtl(hFile, CDROM_SEND_PACKET, &CdromCommandReq, 0, &rcIoCtl);
+ if (RT_SUCCESS(rc) && rcIoCtl < 0)
+ rc = RTErrConvertFromErrno(-CdromCommandReq.stat);
+ RTFileClose(hFile);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pbType)
+ *pbType = auchResponse[0] & 0x1f;
+ if (pszVendor)
+ {
+ RTStrPrintf(pszVendor, cbVendor, "%.8s", &auchResponse[8] /* vendor id string */);
+ RTStrPurgeEncoding(pszVendor);
+ }
+ if (pszModel)
+ {
+ RTStrPrintf(pszModel, cbModel, "%.16s", &auchResponse[16] /* product id string */);
+ RTStrPurgeEncoding(pszModel);
+ }
+ LogRelFlowFunc(("returning success: type=%u, vendor=%.8s, product=%.16s\n",
+ auchResponse[0] & 0x1f, &auchResponse[8], &auchResponse[16]));
+ return VINF_SUCCESS;
+ }
+ }
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Initialise the device strings (description and UDI) for a DVD drive based on
+ * vendor and model name strings.
+ * @param pcszVendor the vendor ID string
+ * @param pcszModel the product ID string
+ * @param pszDesc where to store the description string (optional)
+ * @param cbDesc the size of the buffer in @a pszDesc
+ * @param pszUdi where to store the UDI string (optional)
+ * @param cbUdi the size of the buffer in @a pszUdi
+ *
+ * @note Used for more than DVDs these days.
+ */
+static void dvdCreateDeviceStrings(const char *pcszVendor, const char *pcszModel,
+ char *pszDesc, size_t cbDesc, char *pszUdi, size_t cbUdi) RT_NOEXCEPT
+{
+ AssertPtrReturnVoid(pcszVendor);
+ AssertPtrReturnVoid(pcszModel);
+ AssertPtrNullReturnVoid(pszDesc);
+ AssertReturnVoid(!pszDesc || cbDesc > 0);
+ AssertPtrNullReturnVoid(pszUdi);
+ AssertReturnVoid(!pszUdi || cbUdi > 0);
+
+ size_t cchModel = strLenStripped(pcszModel);
+ /*
+ * Vendor and Model strings can contain trailing spaces.
+ * Create trimmed copy of them because we should not modify
+ * original strings.
+ */
+ char* pszStartTrimmed = RTStrStripL(pcszVendor);
+ char* pszVendor = RTStrDup(pszStartTrimmed);
+ RTStrStripR(pszVendor);
+ pszStartTrimmed = RTStrStripL(pcszModel);
+ char* pszModel = RTStrDup(pszStartTrimmed);
+ RTStrStripR(pszModel);
+
+ size_t cbVendor = strlen(pszVendor);
+
+ /* Create a cleaned version of the model string for the UDI string. */
+ char szCleaned[128];
+ for (unsigned i = 0; i < sizeof(szCleaned) && pcszModel[i] != '\0'; ++i)
+ if ( (pcszModel[i] >= '0' && pcszModel[i] <= '9')
+ || (pcszModel[i] >= 'A' && pcszModel[i] <= 'z'))
+ szCleaned[i] = pcszModel[i];
+ else
+ szCleaned[i] = '_';
+ szCleaned[RT_MIN(cchModel, sizeof(szCleaned) - 1)] = '\0';
+
+ /* Construct the description string as "Vendor Product" */
+ if (pszDesc)
+ {
+ if (cbVendor > 0)
+ {
+ RTStrPrintf(pszDesc, cbDesc, "%.*s %s", cbVendor, pszVendor, strlen(pszModel) > 0 ? pszModel : "(unknown drive model)");
+ RTStrPurgeEncoding(pszDesc);
+ }
+ else
+ RTStrCopy(pszDesc, cbDesc, pszModel);
+ }
+ /* Construct the UDI string */
+ if (pszUdi)
+ {
+ if (cchModel > 0)
+ RTStrPrintf(pszUdi, cbUdi, "/org/freedesktop/Hal/devices/storage_model_%s", szCleaned);
+ else
+ pszUdi[0] = '\0';
+ }
+}
+
+
+/**
+ * Check whether the device is the NVME device.
+ * @returns true on success, false if the name was not available (i.e. the
+ * device was not readable, or the file name wasn't a NVME device)
+ * @param pcszNode the path to the device node for the device
+ */
+static bool probeNVME(const char *pcszNode) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pcszNode, false);
+ RTFILE File;
+ int rc = RTFileOpen(&File, pcszNode, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK);
+ if (RT_SUCCESS(rc))
+ {
+ int rcIoCtl;
+ rc = RTFileIoCtl(File, NVME_IOCTL_ID, NULL, 0, &rcIoCtl);
+ RTFileClose(File);
+ if (RT_SUCCESS(rc) && rcIoCtl >= 0)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Check whether a device node points to a valid device and create a UDI and
+ * a description for it, and store the device number, if it does.
+ *
+ * @returns true if the device is valid, false otherwise
+ * @param pcszNode the path to the device node
+ * @param isDVD are we looking for a DVD device (or a floppy device)?
+ * @param pDevice where to store the device node (optional)
+ * @param pszDesc where to store the device description (optional)
+ * @param cbDesc the size of the buffer in @a pszDesc
+ * @param pszUdi where to store the device UDI (optional)
+ * @param cbUdi the size of the buffer in @a pszUdi
+ */
+static bool devValidateDevice(const char *pcszNode, bool isDVD, dev_t *pDevice,
+ char *pszDesc, size_t cbDesc, char *pszUdi, size_t cbUdi) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pcszNode, false);
+ AssertPtrNullReturn(pDevice, false);
+ AssertPtrNullReturn(pszDesc, false);
+ AssertReturn(!pszDesc || cbDesc > 0, false);
+ AssertPtrNullReturn(pszUdi, false);
+ AssertReturn(!pszUdi || cbUdi > 0, false);
+
+ RTFSOBJINFO ObjInfo;
+ if (RT_FAILURE(RTPathQueryInfo(pcszNode, &ObjInfo, RTFSOBJATTRADD_UNIX)))
+ return false;
+ if (!RTFS_IS_DEV_BLOCK(ObjInfo.Attr.fMode))
+ return false;
+ if (pDevice)
+ *pDevice = ObjInfo.Attr.u.Unix.Device;
+
+ if (isDVD)
+ {
+ char szVendor[128], szModel[128];
+ uint8_t u8Type;
+ if (!isCdromDevNum(ObjInfo.Attr.u.Unix.Device))
+ return false;
+ if (RT_FAILURE(cdromDoInquiry(pcszNode, &u8Type,
+ szVendor, sizeof(szVendor),
+ szModel, sizeof(szModel))))
+ return false;
+ if (u8Type != TYPE_ROM)
+ return false;
+ dvdCreateDeviceStrings(szVendor, szModel, pszDesc, cbDesc, pszUdi, cbUdi);
+ }
+ else
+ {
+ /* Floppies on Linux are legacy devices with hardcoded majors and minors */
+ if (major(ObjInfo.Attr.u.Unix.Device) != FLOPPY_MAJOR)
+ return false;
+
+ unsigned Number;
+ switch (minor(ObjInfo.Attr.u.Unix.Device))
+ {
+ case 0: case 1: case 2: case 3:
+ Number = minor(ObjInfo.Attr.u.Unix.Device);
+ break;
+ case 128: case 129: case 130: case 131:
+ Number = minor(ObjInfo.Attr.u.Unix.Device) - 128 + 4;
+ break;
+ default:
+ return false;
+ }
+
+ floppy_drive_name szName;
+ if (!floppyGetName(pcszNode, Number, szName))
+ return false;
+ floppyCreateDeviceStrings(szName, Number, pszDesc, cbDesc, pszUdi, cbUdi);
+ }
+ return true;
+}
+
+
+int VBoxMainDriveInfo::updateDVDs() RT_NOEXCEPT
+{
+ LogFlowThisFunc(("entered\n"));
+ int rc;
+ try
+ {
+ mDVDList.clear();
+ /* Always allow the user to override our auto-detection using an
+ * environment variable. */
+ bool fSuccess = false; /* Have we succeeded in finding anything yet? */
+ rc = getDriveInfoFromEnv("VBOX_CDROM", &mDVDList, true /* isDVD */, &fSuccess);
+ setNoProbe(false);
+ if (RT_SUCCESS(rc) && (!fSuccess || testing()))
+ rc = getDriveInfoFromSysfs(&mDVDList, DVD, &fSuccess);
+ if (RT_SUCCESS(rc) && testing())
+ {
+ setNoProbe(true);
+ rc = getDriveInfoFromSysfs(&mDVDList, DVD, &fSuccess);
+ }
+ }
+ catch (std::bad_alloc &e)
+ {
+ rc = VERR_NO_MEMORY;
+ }
+ LogFlowThisFunc(("rc=%Rrc\n", rc));
+ return rc;
+}
+
+int VBoxMainDriveInfo::updateFloppies() RT_NOEXCEPT
+{
+ LogFlowThisFunc(("entered\n"));
+ int rc;
+ try
+ {
+ mFloppyList.clear();
+ bool fSuccess = false; /* Have we succeeded in finding anything yet? */
+ rc = getDriveInfoFromEnv("VBOX_FLOPPY", &mFloppyList, false /* isDVD */, &fSuccess);
+ setNoProbe(false);
+ if (RT_SUCCESS(rc) && (!fSuccess || testing()))
+ rc = getDriveInfoFromSysfs(&mFloppyList, Floppy, &fSuccess);
+ if (RT_SUCCESS(rc) && testing())
+ {
+ setNoProbe(true);
+ rc = getDriveInfoFromSysfs(&mFloppyList, Floppy, &fSuccess);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = VERR_NO_MEMORY;
+ }
+ LogFlowThisFunc(("rc=%Rrc\n", rc));
+ return rc;
+}
+
+int VBoxMainDriveInfo::updateFixedDrives() RT_NOEXCEPT
+{
+ LogFlowThisFunc(("entered\n"));
+ int vrc;
+ try
+ {
+ mFixedDriveList.clear();
+ setNoProbe(false);
+ bool fSuccess = false; /* Have we succeeded in finding anything yet? */
+ vrc = getDriveInfoFromSysfs(&mFixedDriveList, FixedDisk, &fSuccess);
+ if (RT_SUCCESS(vrc) && testing())
+ {
+ setNoProbe(true);
+ vrc = getDriveInfoFromSysfs(&mFixedDriveList, FixedDisk, &fSuccess);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ LogFlowThisFunc(("vrc=%Rrc\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * Extract the names of drives from an environment variable and add them to a
+ * list if they are valid.
+ *
+ * @returns iprt status code
+ * @param pcszVar the name of the environment variable. The variable
+ * value should be a list of device node names, separated
+ * by ':' characters.
+ * @param pList the list to append the drives found to
+ * @param isDVD are we looking for DVD drives or for floppies?
+ * @param pfSuccess this will be set to true if we found at least one drive
+ * and to false otherwise. Optional.
+ *
+ * @note This is duplicated in HostHardwareFreeBSD.cpp.
+ */
+static int getDriveInfoFromEnv(const char *pcszVar, DriveInfoList *pList, bool isDVD, bool *pfSuccess) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pcszVar, VERR_INVALID_POINTER);
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER);
+ LogFlowFunc(("pcszVar=%s, pList=%p, isDVD=%d, pfSuccess=%p\n", pcszVar, pList, isDVD, pfSuccess));
+ int rc = VINF_SUCCESS;
+ bool success = false;
+ char *pszFreeMe = RTEnvDupEx(RTENV_DEFAULT, pcszVar);
+
+ try
+ {
+ char *pszCurrent = pszFreeMe;
+ while (pszCurrent && *pszCurrent != '\0')
+ {
+ char *pszNext = strchr(pszCurrent, ':');
+ if (pszNext)
+ *pszNext++ = '\0';
+
+ char szReal[RTPATH_MAX];
+ char szDesc[256], szUdi[256];
+ if ( RT_SUCCESS(RTPathReal(pszCurrent, szReal, sizeof(szReal)))
+ && devValidateDevice(szReal, isDVD, NULL, szDesc, sizeof(szDesc), szUdi, sizeof(szUdi)))
+ {
+ pList->push_back(DriveInfo(szReal, szUdi, szDesc));
+ success = true;
+ }
+ pszCurrent = pszNext;
+ }
+ if (pfSuccess != NULL)
+ *pfSuccess = success;
+ }
+ catch (std::bad_alloc &)
+ {
+ rc = VERR_NO_MEMORY;
+ }
+ RTStrFree(pszFreeMe);
+ LogFlowFunc(("rc=%Rrc, success=%d\n", rc, success));
+ return rc;
+}
+
+
+class SysfsBlockDev
+{
+public:
+ SysfsBlockDev(const char *pcszName, SysfsWantDevice_T wantDevice) RT_NOEXCEPT
+ : mpcszName(pcszName), mWantDevice(wantDevice), misConsistent(true), misValid(false)
+ {
+ if (findDeviceNode())
+ {
+ switch (mWantDevice)
+ {
+ case DVD: validateAndInitForDVD(); break;
+ case Floppy: validateAndInitForFloppy(); break;
+ default: validateAndInitForFixedDisk(); break;
+ }
+ }
+ }
+private:
+ /** The name of the subdirectory of /sys/block for this device */
+ const char *mpcszName;
+ /** Are we looking for a floppy, a DVD or a fixed disk device? */
+ SysfsWantDevice_T mWantDevice;
+ /** The device node for the device */
+ char mszNode[RTPATH_MAX];
+ /** Does the sysfs entry look like we expect it too? This is a canary
+ * for future sysfs ABI changes. */
+ bool misConsistent;
+ /** Is this entry a valid specimen of what we are looking for? */
+ bool misValid;
+ /** Human readable drive description string */
+ char mszDesc[256];
+ /** Unique identifier for the drive. Should be identical to hal's UDI for
+ * the device. May not be unique for two identical drives. */
+ char mszUdi[256];
+private:
+ /* Private methods */
+
+ /**
+ * Fill in the device node member based on the /sys/block subdirectory.
+ * @returns boolean success value
+ */
+ bool findDeviceNode() RT_NOEXCEPT
+ {
+ dev_t dev = 0;
+ int rc = RTLinuxSysFsReadDevNumFile(&dev, "block/%s/dev", mpcszName);
+ if (RT_FAILURE(rc) || dev == 0)
+ {
+ misConsistent = false;
+ return false;
+ }
+ rc = RTLinuxCheckDevicePath(dev, RTFS_TYPE_DEV_BLOCK, mszNode, sizeof(mszNode), "%s", mpcszName);
+ return RT_SUCCESS(rc);
+ }
+
+ /** Check whether the sysfs block entry is valid for a DVD device and
+ * initialise the string data members for the object. We try to get all
+ * the information we need from sysfs if possible, to avoid unnecessarily
+ * poking the device, and if that fails we fall back to an SCSI INQUIRY
+ * command. */
+ void validateAndInitForDVD() RT_NOEXCEPT
+ {
+ int64_t type = 0;
+ int rc = RTLinuxSysFsReadIntFile(10, &type, "block/%s/device/type", mpcszName);
+ if (RT_SUCCESS(rc) && type != TYPE_ROM)
+ return;
+ if (type == TYPE_ROM)
+ {
+ char szVendor[128];
+ rc = RTLinuxSysFsReadStrFile(szVendor, sizeof(szVendor), NULL, "block/%s/device/vendor", mpcszName);
+ if (RT_SUCCESS(rc))
+ {
+ char szModel[128];
+ rc = RTLinuxSysFsReadStrFile(szModel, sizeof(szModel), NULL, "block/%s/device/model", mpcszName);
+ if (RT_SUCCESS(rc))
+ {
+ misValid = true;
+ dvdCreateDeviceStrings(szVendor, szModel, mszDesc, sizeof(mszDesc), mszUdi, sizeof(mszUdi));
+ return;
+ }
+ }
+ }
+ if (!noProbe())
+ probeAndInitForDVD();
+ }
+
+ /** Try to find out whether a device is a DVD drive by sending it an
+ * SCSI INQUIRY command. If it is, initialise the string and validity
+ * data members for the object based on the returned data.
+ */
+ void probeAndInitForDVD() RT_NOEXCEPT
+ {
+ AssertReturnVoid(mszNode[0] != '\0');
+ uint8_t bType = 0;
+ char szVendor[128] = "";
+ char szModel[128] = "";
+ int rc = cdromDoInquiry(mszNode, &bType, szVendor, sizeof(szVendor), szModel, sizeof(szModel));
+ if (RT_SUCCESS(rc) && bType == TYPE_ROM)
+ {
+ misValid = true;
+ dvdCreateDeviceStrings(szVendor, szModel, mszDesc, sizeof(mszDesc), mszUdi, sizeof(mszUdi));
+ }
+ }
+
+ /** Check whether the sysfs block entry is valid for a floppy device and
+ * initialise the string data members for the object. Since we only
+ * support floppies using the basic "floppy" driver, we check the driver
+ * using the entry name and a driver-specific ioctl. */
+ void validateAndInitForFloppy() RT_NOEXCEPT
+ {
+ floppy_drive_name szName;
+ char szDriver[8];
+ if ( mpcszName[0] != 'f'
+ || mpcszName[1] != 'd'
+ || mpcszName[2] < '0'
+ || mpcszName[2] > '7'
+ || mpcszName[3] != '\0')
+ return;
+ bool fHaveName = false;
+ if (!noProbe())
+ fHaveName = floppyGetName(mszNode, mpcszName[2] - '0', szName);
+ int rc = RTLinuxSysFsGetLinkDest(szDriver, sizeof(szDriver), NULL, "block/%s/%s",
+ mpcszName, "device/driver");
+ if (RT_SUCCESS(rc))
+ {
+ if (RTStrCmp(szDriver, "floppy"))
+ return;
+ }
+ else if (!fHaveName)
+ return;
+ floppyCreateDeviceStrings(fHaveName ? szName : NULL,
+ mpcszName[2] - '0', mszDesc,
+ sizeof(mszDesc), mszUdi, sizeof(mszUdi));
+ misValid = true;
+ }
+
+ void validateAndInitForFixedDisk() RT_NOEXCEPT
+ {
+ /*
+ * For current task only device path is needed. Therefore, device probing
+ * is skipped and other fields are empty if there aren't files in the
+ * device entry.
+ */
+ int64_t type = 0;
+ int rc = RTLinuxSysFsReadIntFile(10, &type, "block/%s/device/type", mpcszName);
+ if (!RT_SUCCESS(rc) || type != TYPE_DISK)
+ {
+ if (noProbe() || !probeNVME(mszNode))
+ {
+ char szDriver[16];
+ rc = RTLinuxSysFsGetLinkDest(szDriver, sizeof(szDriver), NULL, "block/%s/%s",
+ mpcszName, "device/device/driver");
+ if (RT_FAILURE(rc) || RTStrCmp(szDriver, "nvme"))
+ return;
+ }
+ }
+ char szVendor[128];
+ char szModel[128];
+ size_t cbRead = 0;
+ rc = RTLinuxSysFsReadStrFile(szVendor, sizeof(szVendor), &cbRead, "block/%s/device/vendor", mpcszName);
+ szVendor[cbRead] = '\0';
+ /* Assume the model is always present. Vendor is not present for NVME disks */
+ cbRead = 0;
+ rc = RTLinuxSysFsReadStrFile(szModel, sizeof(szModel), &cbRead, "block/%s/device/model", mpcszName);
+ szModel[cbRead] = '\0';
+ if (RT_SUCCESS(rc))
+ {
+ misValid = true;
+ dvdCreateDeviceStrings(szVendor, szModel, mszDesc, sizeof(mszDesc), mszUdi, sizeof(mszUdi));
+ }
+ }
+
+public:
+ bool isConsistent() const RT_NOEXCEPT
+ {
+ return misConsistent;
+ }
+ bool isValid() const RT_NOEXCEPT
+ {
+ return misValid;
+ }
+ const char *getDesc() const RT_NOEXCEPT
+ {
+ return mszDesc;
+ }
+ const char *getUdi() const RT_NOEXCEPT
+ {
+ return mszUdi;
+ }
+ const char *getNode() const RT_NOEXCEPT
+ {
+ return mszNode;
+ }
+};
+
+
+/**
+ * Helper function to query the sysfs subsystem for information about DVD
+ * drives attached to the system.
+ * @returns iprt status code
+ * @param pList where to add information about the drives detected
+ * @param wantDevice The kind of devices we're looking for.
+ * @param pfSuccess Did we find anything?
+ *
+ * @returns IPRT status code
+ * @throws Nothing.
+ */
+static int getDriveInfoFromSysfs(DriveInfoList *pList, SysfsWantDevice_T wantDevice, bool *pfSuccess) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER); /* Valid or Null */
+ LogFlowFunc (("pList=%p, wantDevice=%u, pfSuccess=%p\n",
+ pList, (unsigned)wantDevice, pfSuccess));
+ if (!RTPathExists("/sys"))
+ return VINF_SUCCESS;
+
+ bool fSuccess = true;
+ unsigned cFound = 0;
+ RTDIR hDir = NIL_RTDIR;
+ int rc = RTDirOpen(&hDir, "/sys/block");
+ /* This might mean that sysfs semantics have changed */
+ AssertReturn(rc != VERR_FILE_NOT_FOUND, VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ {
+ for (;;)
+ {
+ RTDIRENTRY entry;
+ rc = RTDirRead(hDir, &entry, NULL);
+ Assert(rc != VERR_BUFFER_OVERFLOW); /* Should never happen... */
+ if (RT_FAILURE(rc)) /* Including overflow and no more files */
+ break;
+ if (entry.szName[0] == '.')
+ continue;
+ SysfsBlockDev dev(entry.szName, wantDevice);
+ /* This might mean that sysfs semantics have changed */
+ AssertBreakStmt(dev.isConsistent(), fSuccess = false);
+ if (!dev.isValid())
+ continue;
+ try
+ {
+ pList->push_back(DriveInfo(dev.getNode(), dev.getUdi(), dev.getDesc()));
+ }
+ catch (std::bad_alloc &e)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ ++cFound;
+ }
+ RTDirClose(hDir);
+ }
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ else if (RT_FAILURE(rc))
+ /* Clean up again */
+ while (cFound-- > 0)
+ pList->pop_back();
+ if (pfSuccess)
+ *pfSuccess = fSuccess;
+ LogFlow (("rc=%Rrc, fSuccess=%u\n", rc, (unsigned)fSuccess));
+ return rc;
+}
+
+
+/** Helper for readFilePathsFromDir(). Adds a path to the vector if it is not
+ * NULL and not a dotfile (".", "..", ".*"). */
+static int maybeAddPathToVector(const char *pcszPath, const char *pcszEntry, VECTOR_PTR(char *) *pvecpchDevs) RT_NOTHROW_DEF
+{
+ if (!pcszPath)
+ return 0;
+ if (pcszEntry[0] == '.')
+ return 0;
+ char *pszPath = RTStrDup(pcszPath);
+ if (pszPath)
+ {
+ int vrc = VEC_PUSH_BACK_PTR(pvecpchDevs, char *, pszPath);
+ if (RT_SUCCESS(vrc))
+ return 0;
+ }
+ return ENOMEM;
+}
+
+/**
+ * Helper for readFilePaths().
+ *
+ * Adds the entries from the open directory @a pDir to the vector @a pvecpchDevs
+ * using either the full path or the realpath() and skipping hidden files
+ * and files on which realpath() fails.
+ */
+static int readFilePathsFromDir(const char *pcszPath, DIR *pDir, VECTOR_PTR(char *) *pvecpchDevs, int withRealPath) RT_NOTHROW_DEF
+{
+ struct dirent entry, *pResult;
+ int err;
+
+#if RT_GNUC_PREREQ(4, 6)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ for (err = readdir_r(pDir, &entry, &pResult);
+ pResult != NULL && err == 0;
+ err = readdir_r(pDir, &entry, &pResult))
+#if RT_GNUC_PREREQ(4, 6)
+# pragma GCC diagnostic pop
+#endif
+ {
+ /* We (implicitly) require that PATH_MAX be defined */
+ char szPath[PATH_MAX + 1], szRealPath[PATH_MAX + 1], *pszPath;
+ if (snprintf(szPath, sizeof(szPath), "%s/%s", pcszPath,
+ entry.d_name) < 0)
+ return errno;
+ if (withRealPath)
+ pszPath = realpath(szPath, szRealPath);
+ else
+ pszPath = szPath;
+ if ((err = maybeAddPathToVector(pszPath, entry.d_name, pvecpchDevs)))
+ return err;
+ }
+ return err;
+}
+
+
+/**
+ * Helper for walkDirectory to dump the names of a directory's entries into a
+ * vector of char pointers.
+ *
+ * @returns zero on success or (positive) posix error value.
+ * @param pcszPath the path to dump.
+ * @param pvecpchDevs an empty vector of char pointers - must be cleaned up
+ * by the caller even on failure.
+ * @param withRealPath whether to canonicalise the filename with realpath
+ */
+static int readFilePaths(const char *pcszPath, VECTOR_PTR(char *) *pvecpchDevs, int withRealPath) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pvecpchDevs, EINVAL);
+ AssertReturn(VEC_SIZE_PTR(pvecpchDevs) == 0, EINVAL);
+ AssertPtrReturn(pcszPath, EINVAL);
+
+ DIR *pDir = opendir(pcszPath);
+ if (!pDir)
+ return RTErrConvertFromErrno(errno);
+ int err = readFilePathsFromDir(pcszPath, pDir, pvecpchDevs, withRealPath);
+ if (closedir(pDir) < 0 && !err)
+ err = errno;
+ return RTErrConvertFromErrno(err);
+}
+
+
+class hotplugNullImpl : public VBoxMainHotplugWaiterImpl
+{
+public:
+ hotplugNullImpl(const char *) {}
+ virtual ~hotplugNullImpl (void) {}
+ /** @copydoc VBoxMainHotplugWaiter::Wait */
+ virtual int Wait (RTMSINTERVAL cMillies)
+ {
+ NOREF(cMillies);
+ return VERR_NOT_SUPPORTED;
+ }
+ /** @copydoc VBoxMainHotplugWaiter::Interrupt */
+ virtual void Interrupt (void) {}
+ virtual int getStatus(void)
+ {
+ return VERR_NOT_SUPPORTED;
+ }
+
+};
+
+#ifdef VBOX_USB_WITH_SYSFS
+# ifdef VBOX_USB_WITH_INOTIFY
+/** Class wrapper around an inotify watch (or a group of them to be precise).
+ */
+typedef struct inotifyWatch
+{
+ /** Pointer to the inotify_add_watch() glibc function/Linux API */
+ int (*inotify_add_watch)(int, const char *, uint32_t);
+ /** The native handle of the inotify fd. */
+ int mhInotify;
+} inotifyWatch;
+
+/** The flags we pass to inotify - modify, create, delete, change permissions
+ */
+#define IN_FLAGS 0x306
+
+static int iwAddWatch(inotifyWatch *pSelf, const char *pcszPath)
+{
+ errno = 0;
+ if ( pSelf->inotify_add_watch(pSelf->mhInotify, pcszPath, IN_FLAGS) >= 0
+ || (errno == EACCES))
+ return VINF_SUCCESS;
+ /* Other errors listed in the manpage can be treated as fatal */
+ return RTErrConvertFromErrno(errno);
+}
+
+/** Object initialisation */
+static int iwInit(inotifyWatch *pSelf)
+{
+ int (*inotify_init)(void);
+ int fd, flags;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pSelf);
+ pSelf->mhInotify = -1;
+ errno = 0;
+ *(void **)(&inotify_init) = dlsym(RTLD_DEFAULT, "inotify_init");
+ if (!inotify_init)
+ return VERR_LDR_IMPORTED_SYMBOL_NOT_FOUND;
+ *(void **)(&pSelf->inotify_add_watch)
+ = dlsym(RTLD_DEFAULT, "inotify_add_watch");
+ if (!pSelf->inotify_add_watch)
+ return VERR_LDR_IMPORTED_SYMBOL_NOT_FOUND;
+ fd = inotify_init();
+ if (fd < 0)
+ {
+ Assert(errno > 0);
+ return RTErrConvertFromErrno(errno);
+ }
+ Assert(errno == 0);
+
+ flags = fcntl(fd, F_GETFL, NULL);
+ if ( flags < 0
+ || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0
+ || fcntl(fd, F_SETFD, FD_CLOEXEC) < 0 /* race here */)
+ {
+ Assert(errno > 0);
+ rc = RTErrConvertFromErrno(errno);
+ }
+ if (RT_FAILURE(rc))
+ close(fd);
+ else
+ {
+ Assert(errno == 0);
+ pSelf->mhInotify = fd;
+ }
+ return rc;
+}
+
+static void iwTerm(inotifyWatch *pSelf)
+{
+ AssertPtrReturnVoid(pSelf);
+ if (pSelf->mhInotify != -1)
+ {
+ close(pSelf->mhInotify);
+ pSelf->mhInotify = -1;
+ }
+}
+
+static int iwGetFD(inotifyWatch *pSelf)
+{
+ AssertPtrReturn(pSelf, -1);
+ return pSelf->mhInotify;
+}
+
+# define SYSFS_WAKEUP_STRING "Wake up!"
+
+class hotplugInotifyImpl : public VBoxMainHotplugWaiterImpl
+{
+ /** Pipe used to interrupt wait(), the read end. */
+ int mhWakeupPipeR;
+ /** Pipe used to interrupt wait(), the write end. */
+ int mhWakeupPipeW;
+ /** The inotify watch set */
+ inotifyWatch mWatches;
+ /** Flag to mark that the Wait() method is currently being called, and to
+ * ensure that it isn't called multiple times in parallel. */
+ volatile uint32_t mfWaiting;
+ /** The root of the USB devices tree. */
+ const char *mpcszDevicesRoot;
+ /** iprt result code from object initialisation. Should be AssertReturn-ed
+ * on at the start of all methods. I went this way because I didn't want
+ * to deal with exceptions. */
+ int mStatus;
+ /** ID values associates with the wakeup pipe and the FAM socket for polling
+ */
+ enum
+ {
+ RPIPE_ID = 0,
+ INOTIFY_ID,
+ MAX_POLLID
+ };
+
+ /** Clean up any resources in use, gracefully skipping over any which have
+ * not yet been allocated or already cleaned up. Intended to be called
+ * from the destructor or after a failed initialisation. */
+ void term(void);
+
+ int drainInotify();
+
+ /** Read the wakeup string from the wakeup pipe */
+ int drainWakeupPipe(void);
+public:
+ hotplugInotifyImpl(const char *pcszDevicesRoot);
+ virtual ~hotplugInotifyImpl(void)
+ {
+ term();
+#ifdef DEBUG
+ /** The first call to term should mark all resources as freed, so this
+ * should be a semantic no-op. */
+ term();
+#endif
+ }
+ /** Is inotify available and working on this system? If so we expect that
+ * this implementation will be usable. */
+ /** @todo test the "inotify in glibc but not in the kernel" case. */
+ static bool Available(void)
+ {
+ int (*inotify_init)(void);
+
+ *(void **)(&inotify_init) = dlsym(RTLD_DEFAULT, "inotify_init");
+ if (!inotify_init)
+ return false;
+ int fd = inotify_init();
+ if (fd == -1)
+ return false;
+ close(fd);
+ return true;
+ }
+
+ virtual int getStatus(void)
+ {
+ return mStatus;
+ }
+
+ /** @copydoc VBoxMainHotplugWaiter::Wait */
+ virtual int Wait(RTMSINTERVAL);
+ /** @copydoc VBoxMainHotplugWaiter::Interrupt */
+ virtual void Interrupt(void);
+};
+
+/** Simplified version of RTPipeCreate */
+static int pipeCreateSimple(int *phPipeRead, int *phPipeWrite)
+{
+ AssertPtrReturn(phPipeRead, VERR_INVALID_POINTER);
+ AssertPtrReturn(phPipeWrite, VERR_INVALID_POINTER);
+
+ /*
+ * Create the pipe and set the close-on-exec flag.
+ */
+ int aFds[2] = {-1, -1};
+ if (pipe(aFds))
+ return RTErrConvertFromErrno(errno);
+ if ( fcntl(aFds[0], F_SETFD, FD_CLOEXEC) < 0
+ || fcntl(aFds[1], F_SETFD, FD_CLOEXEC) < 0)
+ {
+ int rc = RTErrConvertFromErrno(errno);
+ close(aFds[0]);
+ close(aFds[1]);
+ return rc;
+ }
+
+ *phPipeRead = aFds[0];
+ *phPipeWrite = aFds[1];
+
+ /*
+ * Before we leave, make sure to shut up SIGPIPE.
+ */
+ signal(SIGPIPE, SIG_IGN);
+ return VINF_SUCCESS;
+}
+
+hotplugInotifyImpl::hotplugInotifyImpl(const char *pcszDevicesRoot) :
+ mhWakeupPipeR(-1), mhWakeupPipeW(-1), mfWaiting(0),
+ mpcszDevicesRoot(pcszDevicesRoot), mStatus(VERR_WRONG_ORDER)
+{
+# ifdef DEBUG
+ /* Excercise the code path (term() on a not-fully-initialised object) as
+ * well as we can. On an uninitialised object this method is a semantic
+ * no-op. */
+ mWatches.mhInotify = -1; /* term will access this variable */
+ term();
+ /* For now this probing method should only be used if nothing else is
+ * available */
+# endif
+ int rc;
+ do {
+ if (RT_FAILURE(rc = iwInit(&mWatches)))
+ break;
+ if (RT_FAILURE(rc = iwAddWatch(&mWatches, mpcszDevicesRoot)))
+ break;
+ if (RT_FAILURE(rc = pipeCreateSimple(&mhWakeupPipeR, &mhWakeupPipeW)))
+ break;
+ } while (0);
+ mStatus = rc;
+ if (RT_FAILURE(rc))
+ term();
+}
+
+void hotplugInotifyImpl::term(void)
+{
+ /** This would probably be a pending segfault, so die cleanly */
+ AssertRelease(!mfWaiting);
+ if (mhWakeupPipeR != -1)
+ {
+ close(mhWakeupPipeR);
+ mhWakeupPipeR = -1;
+ }
+ if (mhWakeupPipeW != -1)
+ {
+ close(mhWakeupPipeW);
+ mhWakeupPipeW = -1;
+ }
+ iwTerm(&mWatches);
+}
+
+int hotplugInotifyImpl::drainInotify()
+{
+ char chBuf[RTPATH_MAX + 256]; /* Should always be big enough */
+ ssize_t cchRead;
+
+ AssertRCReturn(mStatus, VERR_WRONG_ORDER);
+ errno = 0;
+ do {
+ cchRead = read(iwGetFD(&mWatches), chBuf, sizeof(chBuf));
+ } while (cchRead > 0);
+ if (cchRead == 0)
+ return VINF_SUCCESS;
+ if ( cchRead < 0
+ && ( errno == EAGAIN
+#if EAGAIN != EWOULDBLOCK
+ || errno == EWOULDBLOCK
+#endif
+ ))
+ return VINF_SUCCESS;
+ Assert(errno > 0);
+ return RTErrConvertFromErrno(errno);
+}
+
+int hotplugInotifyImpl::drainWakeupPipe(void)
+{
+ char szBuf[sizeof(SYSFS_WAKEUP_STRING)];
+ ssize_t cbRead;
+
+ AssertRCReturn(mStatus, VERR_WRONG_ORDER);
+ cbRead = read(mhWakeupPipeR, szBuf, sizeof(szBuf));
+ Assert(cbRead > 0);
+ NOREF(cbRead);
+ return VINF_SUCCESS;
+}
+
+int hotplugInotifyImpl::Wait(RTMSINTERVAL aMillies)
+{
+ int rc;
+ char **ppszEntry;
+ VECTOR_PTR(char *) vecpchDevs;
+
+ AssertRCReturn(mStatus, VERR_WRONG_ORDER);
+ bool fEntered = ASMAtomicCmpXchgU32(&mfWaiting, 1, 0);
+ AssertReturn(fEntered, VERR_WRONG_ORDER);
+ VEC_INIT_PTR(&vecpchDevs, char *, RTStrFree);
+ do {
+ struct pollfd pollFD[MAX_POLLID];
+
+ rc = readFilePaths(mpcszDevicesRoot, &vecpchDevs, false);
+ if (RT_SUCCESS(rc))
+ VEC_FOR_EACH(&vecpchDevs, char *, ppszEntry)
+ if (RT_FAILURE(rc = iwAddWatch(&mWatches, *ppszEntry)))
+ break;
+ if (RT_FAILURE(rc))
+ break;
+ pollFD[RPIPE_ID].fd = mhWakeupPipeR;
+ pollFD[RPIPE_ID].events = POLLIN;
+ pollFD[INOTIFY_ID].fd = iwGetFD(&mWatches);
+ pollFD[INOTIFY_ID].events = POLLIN | POLLERR | POLLHUP;
+ errno = 0;
+ int cPolled = poll(pollFD, RT_ELEMENTS(pollFD), aMillies);
+ if (cPolled < 0)
+ {
+ Assert(errno > 0);
+ rc = RTErrConvertFromErrno(errno);
+ }
+ else if (pollFD[RPIPE_ID].revents)
+ {
+ rc = drainWakeupPipe();
+ if (RT_SUCCESS(rc))
+ rc = VERR_INTERRUPTED;
+ break;
+ }
+ else if (!(pollFD[INOTIFY_ID].revents))
+ {
+ AssertBreakStmt(cPolled == 0, rc = VERR_INTERNAL_ERROR);
+ rc = VERR_TIMEOUT;
+ }
+ Assert(errno == 0 || (RT_FAILURE(rc) && rc != VERR_TIMEOUT));
+ if (RT_FAILURE(rc))
+ break;
+ AssertBreakStmt(cPolled == 1, rc = VERR_INTERNAL_ERROR);
+ if (RT_FAILURE(rc = drainInotify()))
+ break;
+ } while (false);
+ mfWaiting = 0;
+ VEC_CLEANUP_PTR(&vecpchDevs);
+ return rc;
+}
+
+void hotplugInotifyImpl::Interrupt(void)
+{
+ AssertRCReturnVoid(mStatus);
+ ssize_t cbWritten = write(mhWakeupPipeW, SYSFS_WAKEUP_STRING,
+ sizeof(SYSFS_WAKEUP_STRING));
+ if (cbWritten > 0)
+ fsync(mhWakeupPipeW);
+}
+
+# endif /* VBOX_USB_WITH_INOTIFY */
+#endif /* VBOX_USB_WTH_SYSFS */
+
+VBoxMainHotplugWaiter::VBoxMainHotplugWaiter(const char *pcszDevicesRoot)
+{
+ try
+ {
+#ifdef VBOX_USB_WITH_SYSFS
+# ifdef VBOX_USB_WITH_INOTIFY
+ if (hotplugInotifyImpl::Available())
+ {
+ mImpl = new hotplugInotifyImpl(pcszDevicesRoot);
+ return;
+ }
+# endif /* VBOX_USB_WITH_INOTIFY */
+#endif /* VBOX_USB_WITH_SYSFS */
+ mImpl = new hotplugNullImpl(pcszDevicesRoot);
+ }
+ catch (std::bad_alloc &e)
+ { }
+}
diff --git a/src/VBox/Main/src-server/linux/HostPowerLinux.cpp b/src/VBox/Main/src-server/linux/HostPowerLinux.cpp
new file mode 100644
index 00000000..b8189f67
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/HostPowerLinux.cpp
@@ -0,0 +1,199 @@
+/* $Id: HostPowerLinux.cpp $ */
+/** @file
+ * VirtualBox interface to host's power notification service
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOST
+#include "HostPower.h"
+#include "LoggingNew.h"
+
+#include <iprt/asm.h>
+#include <iprt/power.h>
+#include <iprt/time.h>
+
+static bool checkDBusError(DBusError *pError, DBusConnection **pConnection)
+{
+ if (dbus_error_is_set(pError))
+ {
+ LogRel(("HostPowerServiceLinux: DBus connection Error (%s)\n", pError->message));
+ dbus_error_free(pError);
+ if (*pConnection)
+ {
+ /* Close the socket or whatever underlying the connection. */
+ dbus_connection_close(*pConnection);
+ /* Free in-process resources used for the now-closed connection. */
+ dbus_connection_unref(*pConnection);
+ *pConnection = NULL;
+ }
+ return true;
+ }
+ return false;
+}
+
+HostPowerServiceLinux::HostPowerServiceLinux(VirtualBox *aVirtualBox)
+ : HostPowerService(aVirtualBox)
+ , mThread(NIL_RTTHREAD)
+ , mpConnection(NULL)
+{
+ DBusError error;
+ int rc;
+
+ rc = RTDBusLoadLib();
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("HostPowerServiceLinux: DBus library not found. Service not available.\n"));
+ return;
+ }
+ dbus_error_init(&error);
+ /* Connect to the DBus. The connection will be not shared with any other
+ * in-process callers of dbus_bus_get(). This is considered wasteful (see
+ * API documentation) but simplifies our code, specifically shutting down.
+ * The session bus allows up to 100000 connections per user as it "is just
+ * running as the user anyway" (see session.conf.in in the DBus sources). */
+ mpConnection = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
+ if (checkDBusError(&error, &mpConnection))
+ return;
+ /* We do not want to exit(1) if the connection is broken. */
+ dbus_connection_set_exit_on_disconnect(mpConnection, FALSE);
+ /* Tell the bus to wait for the sleep signal(s). */
+ /* The current systemd-logind interface. */
+ dbus_bus_add_match(mpConnection, "type='signal',interface='org.freedesktop.login1.Manager'", &error);
+ /* The previous UPower interfaces (2010 - ca 2013). */
+ dbus_bus_add_match(mpConnection, "type='signal',interface='org.freedesktop.UPower'", &error);
+ dbus_connection_flush(mpConnection);
+ if (checkDBusError(&error, &mpConnection))
+ return;
+
+ /* Grab another reference so that both the destruct and thread each has one: */
+ DBusConnection *pForAssert = dbus_connection_ref(mpConnection);
+ Assert(pForAssert == mpConnection); RT_NOREF(pForAssert);
+
+ /* Create the new worker thread. */
+ rc = RTThreadCreate(&mThread, HostPowerServiceLinux::powerChangeNotificationThread, this, 0 /* cbStack */,
+ RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "MainPower");
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("HostPowerServiceLinux: RTThreadCreate failed with %Rrc\n", rc));
+ dbus_connection_unref(mpConnection);
+ }
+}
+
+
+HostPowerServiceLinux::~HostPowerServiceLinux()
+{
+ /* Closing the connection should cause the event loop to exit. */
+ LogFunc((": Stopping thread\n"));
+ if (mpConnection)
+ {
+ dbus_connection_close(mpConnection);
+ dbus_connection_unref(mpConnection);
+ mpConnection = NULL;
+ }
+
+ if (mThread != NIL_RTTHREAD)
+ {
+ /* HACK ALERT! This poke call should _not_ be necessary as dbus_connection_close()
+ should close the socket and force the poll/dbus_connection_read_write
+ call to return with POLLHUP/FALSE. It does so when stepping it in the
+ debugger, but not in real life (asan build; dbus-1.12.20-1.fc32; linux 5.8).
+
+ Poking the thread is a crude crude way to wake it up from whatever
+ stuff it's actually blocked on and realize that the connection has
+ been dropped. */
+
+ uint64_t msElapsed = RTTimeMilliTS();
+ int vrc = RTThreadWait(mThread, 10 /*ms*/, NULL);
+ if (RT_FAILURE(vrc))
+ {
+ RTThreadPoke(mThread);
+ vrc = RTThreadWait(mThread, RT_MS_5SEC, NULL);
+ }
+ msElapsed = RTTimeMilliTS() - msElapsed;
+ if (vrc != VINF_SUCCESS)
+ LogRelThisFunc(("RTThreadWait() failed after %llu ms: %Rrc\n", msElapsed, vrc));
+ mThread = NIL_RTTHREAD;
+ }
+}
+
+
+DECLCALLBACK(int) HostPowerServiceLinux::powerChangeNotificationThread(RTTHREAD hThreadSelf, void *pInstance)
+{
+ NOREF(hThreadSelf);
+ HostPowerServiceLinux *pPowerObj = static_cast<HostPowerServiceLinux *>(pInstance);
+ DBusConnection *pConnection = pPowerObj->mpConnection;
+
+ Log(("HostPowerServiceLinux: Thread started\n"));
+ while (dbus_connection_read_write(pConnection, -1))
+ {
+ DBusMessage *pMessage = NULL;
+
+ for (;;)
+ {
+ pMessage = dbus_connection_pop_message(pConnection);
+ if (pMessage == NULL)
+ break;
+
+ /* The systemd-logind interface notification. */
+ DBusMessageIter args;
+ if ( dbus_message_is_signal(pMessage, "org.freedesktop.login1.Manager", "PrepareForSleep")
+ && dbus_message_iter_init(pMessage, &args)
+ && dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_BOOLEAN)
+ {
+ dbus_bool_t fSuspend;
+ dbus_message_iter_get_basic(&args, &fSuspend);
+
+ /* Trinary operator does not work here as Reason_... is an
+ * anonymous enum. */
+ if (fSuspend)
+ pPowerObj->notify(Reason_HostSuspend);
+ else
+ pPowerObj->notify(Reason_HostResume);
+ }
+
+ /* The UPowerd interface notifications. Sleeping is the older one,
+ * NotifySleep the newer. This gives us one second grace before the
+ * suspend triggers. */
+ if ( dbus_message_is_signal(pMessage, "org.freedesktop.UPower", "Sleeping")
+ || dbus_message_is_signal(pMessage, "org.freedesktop.UPower", "NotifySleep"))
+ pPowerObj->notify(Reason_HostSuspend);
+ if ( dbus_message_is_signal(pMessage, "org.freedesktop.UPower", "Resuming")
+ || dbus_message_is_signal(pMessage, "org.freedesktop.UPower", "NotifyResume"))
+ pPowerObj->notify(Reason_HostResume);
+
+ /* Free local resources held for the message. */
+ dbus_message_unref(pMessage);
+ }
+ }
+
+ /* Close the socket or whatever underlying the connection. */
+ dbus_connection_close(pConnection);
+
+ /* Free in-process resources used for the now-closed connection. */
+ dbus_connection_unref(pConnection);
+
+ Log(("HostPowerServiceLinux: Exiting thread\n"));
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Main/src-server/linux/Makefile.kup b/src/VBox/Main/src-server/linux/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/Makefile.kup
diff --git a/src/VBox/Main/src-server/linux/NetIf-linux.cpp b/src/VBox/Main/src-server/linux/NetIf-linux.cpp
new file mode 100644
index 00000000..3d16f6ed
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/NetIf-linux.cpp
@@ -0,0 +1,330 @@
+/* $Id: NetIf-linux.cpp $ */
+/** @file
+ * Main - NetIfList, Linux implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_HOST
+
+#include <iprt/errcore.h>
+#include <list>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <linux/wireless.h>
+#include <net/if_arp.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <iprt/asm.h>
+
+#include "HostNetworkInterfaceImpl.h"
+#include "netif.h"
+#include "LoggingNew.h"
+
+/**
+ * Obtain the name of the interface used for default routing.
+ *
+ * NOTE: There is a copy in Devices/Network/testcase/tstIntNet-1.cpp.
+ *
+ * @returns VBox status code.
+ *
+ * @param pszName The buffer where to put the name.
+ * @param cbName Size of of the destination buffer.
+ */
+static int getDefaultIfaceName(char *pszName, size_t cbName)
+{
+ FILE *fp = fopen("/proc/net/route", "r");
+ char szBuf[1024];
+ char szIfName[17];
+ uint32_t uAddr;
+ uint32_t uGateway;
+ uint32_t uMask;
+ int iTmp;
+ unsigned uFlags;
+
+ if (fp)
+ {
+ while (fgets(szBuf, sizeof(szBuf)-1, fp))
+ {
+ int n = sscanf(szBuf, "%16s %x %x %x %d %d %d %x %d %d %d\n",
+ szIfName, &uAddr, &uGateway, &uFlags, &iTmp, &iTmp, &iTmp,
+ &uMask, &iTmp, &iTmp, &iTmp);
+ if (n < 10 || !(uFlags & RTF_UP))
+ continue;
+
+ if (uAddr == 0 && uMask == 0)
+ {
+ fclose(fp);
+ szIfName[sizeof(szIfName) - 1] = '\0';
+ return RTStrCopy(pszName, cbName, szIfName);
+ }
+ }
+ fclose(fp);
+ }
+ return VERR_INTERNAL_ERROR;
+}
+
+static uint32_t getInterfaceSpeed(const char *pszName)
+{
+ /*
+ * I wish I could do simple ioctl here, but older kernels require root
+ * privileges for any ethtool commands.
+ */
+ char szBuf[256];
+ uint32_t uSpeed = 0;
+ /* First, we try to retrieve the speed via sysfs. */
+ RTStrPrintf(szBuf, sizeof(szBuf), "/sys/class/net/%s/speed", pszName);
+ FILE *fp = fopen(szBuf, "r");
+ if (fp)
+ {
+ if (fscanf(fp, "%u", &uSpeed) != 1)
+ uSpeed = 0;
+ fclose(fp);
+ }
+ if (uSpeed == 10)
+ {
+ /* Check the cable is plugged in at all */
+ unsigned uCarrier = 0;
+ RTStrPrintf(szBuf, sizeof(szBuf), "/sys/class/net/%s/carrier", pszName);
+ fp = fopen(szBuf, "r");
+ if (fp)
+ {
+ if (fscanf(fp, "%u", &uCarrier) != 1 || uCarrier == 0)
+ uSpeed = 0;
+ fclose(fp);
+ }
+ }
+
+ if (uSpeed == 0)
+ {
+ /* Failed to get speed via sysfs, go to plan B. */
+ int rc = NetIfAdpCtlOut(pszName, "speed", szBuf, sizeof(szBuf));
+ if (RT_SUCCESS(rc))
+ uSpeed = RTStrToUInt32(szBuf);
+ }
+ return uSpeed;
+}
+
+static int getInterfaceInfo(int iSocket, const char *pszName, PNETIFINFO pInfo)
+{
+ // Zeroing out pInfo is a bad idea as it should contain both short and long names at
+ // this point. So make sure the structure is cleared by the caller if necessary!
+ // memset(pInfo, 0, sizeof(*pInfo));
+ struct ifreq Req;
+ RT_ZERO(Req);
+ RTStrCopy(Req.ifr_name, sizeof(Req.ifr_name), pszName);
+ if (ioctl(iSocket, SIOCGIFHWADDR, &Req) >= 0)
+ {
+ switch (Req.ifr_hwaddr.sa_family)
+ {
+ case ARPHRD_ETHER:
+ pInfo->enmMediumType = NETIF_T_ETHERNET;
+ break;
+ default:
+ pInfo->enmMediumType = NETIF_T_UNKNOWN;
+ break;
+ }
+ /* Generate UUID from name and MAC address. */
+ RTUUID uuid;
+ RTUuidClear(&uuid);
+ memcpy(&uuid, Req.ifr_name, RT_MIN(sizeof(Req.ifr_name), sizeof(uuid)));
+ uuid.Gen.u8ClockSeqHiAndReserved = (uint8_t)((uuid.Gen.u8ClockSeqHiAndReserved & 0x3f) | 0x80);
+ uuid.Gen.u16TimeHiAndVersion = (uint16_t)((uuid.Gen.u16TimeHiAndVersion & 0x0fff) | 0x4000);
+ memcpy(uuid.Gen.au8Node, &Req.ifr_hwaddr.sa_data, sizeof(uuid.Gen.au8Node));
+ pInfo->Uuid = uuid;
+
+ memcpy(&pInfo->MACAddress, Req.ifr_hwaddr.sa_data, sizeof(pInfo->MACAddress));
+
+ if (ioctl(iSocket, SIOCGIFADDR, &Req) >= 0)
+ memcpy(pInfo->IPAddress.au8,
+ &((struct sockaddr_in *)&Req.ifr_addr)->sin_addr.s_addr,
+ sizeof(pInfo->IPAddress.au8));
+
+ if (ioctl(iSocket, SIOCGIFNETMASK, &Req) >= 0)
+ memcpy(pInfo->IPNetMask.au8,
+ &((struct sockaddr_in *)&Req.ifr_addr)->sin_addr.s_addr,
+ sizeof(pInfo->IPNetMask.au8));
+
+ if (ioctl(iSocket, SIOCGIFFLAGS, &Req) >= 0)
+ pInfo->enmStatus = Req.ifr_flags & IFF_UP ? NETIF_S_UP : NETIF_S_DOWN;
+
+ struct iwreq WRq;
+ RT_ZERO(WRq);
+ RTStrCopy(WRq.ifr_name, sizeof(WRq.ifr_name), pszName);
+ pInfo->fWireless = ioctl(iSocket, SIOCGIWNAME, &WRq) >= 0;
+
+ FILE *fp = fopen("/proc/net/if_inet6", "r");
+ if (fp)
+ {
+ RTNETADDRIPV6 IPv6Address;
+ unsigned uIndex, uLength, uScope, uTmp;
+ char szName[30];
+ for (;;)
+ {
+ RT_ZERO(szName);
+ int n = fscanf(fp,
+ "%08x%08x%08x%08x"
+ " %02x %02x %02x %02x %20s\n",
+ &IPv6Address.au32[0], &IPv6Address.au32[1],
+ &IPv6Address.au32[2], &IPv6Address.au32[3],
+ &uIndex, &uLength, &uScope, &uTmp, szName);
+ if (n == EOF)
+ break;
+ if (n != 9 || uLength > 128)
+ {
+ Log(("getInterfaceInfo: Error while reading /proc/net/if_inet6, n=%d uLength=%u\n",
+ n, uLength));
+ break;
+ }
+ if (!strcmp(Req.ifr_name, szName))
+ {
+ pInfo->IPv6Address.au32[0] = htonl(IPv6Address.au32[0]);
+ pInfo->IPv6Address.au32[1] = htonl(IPv6Address.au32[1]);
+ pInfo->IPv6Address.au32[2] = htonl(IPv6Address.au32[2]);
+ pInfo->IPv6Address.au32[3] = htonl(IPv6Address.au32[3]);
+ RTNetPrefixToMaskIPv6(uLength, &pInfo->IPv6NetMask);
+ }
+ }
+ fclose(fp);
+ }
+ /*
+ * Don't even try to get speed for non-Ethernet interfaces, it only
+ * produces errors.
+ */
+ if (pInfo->enmMediumType == NETIF_T_ETHERNET)
+ pInfo->uSpeedMbits = getInterfaceSpeed(pszName);
+ else
+ pInfo->uSpeedMbits = 0;
+ }
+ return VINF_SUCCESS;
+}
+
+int NetIfList(std::list <ComObjPtr<HostNetworkInterface> > &list)
+{
+ char szDefaultIface[256];
+ int rc = getDefaultIfaceName(szDefaultIface, sizeof(szDefaultIface));
+ if (RT_FAILURE(rc))
+ {
+ Log(("NetIfList: Failed to find default interface.\n"));
+ szDefaultIface[0] = '\0';
+ }
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock >= 0)
+ {
+ FILE *fp = fopen("/proc/net/dev", "r");
+ if (fp)
+ {
+ char buf[256];
+ while (fgets(buf, sizeof(buf), fp))
+ {
+ char *pszEndOfName = strchr(buf, ':');
+ if (!pszEndOfName)
+ continue;
+ *pszEndOfName = 0;
+ size_t iFirstNonWS = strspn(buf, " ");
+ char *pszName = buf + iFirstNonWS;
+ NETIFINFO Info;
+ RT_ZERO(Info);
+ rc = getInterfaceInfo(sock, pszName, &Info);
+ if (RT_FAILURE(rc))
+ break;
+ if (Info.enmMediumType == NETIF_T_ETHERNET)
+ {
+ ComObjPtr<HostNetworkInterface> IfObj;
+ IfObj.createObject();
+
+ HostNetworkInterfaceType_T enmType;
+ if (strncmp(pszName, RT_STR_TUPLE("vboxnet")))
+ enmType = HostNetworkInterfaceType_Bridged;
+ else
+ enmType = HostNetworkInterfaceType_HostOnly;
+
+ if (SUCCEEDED(IfObj->init(pszName, enmType, &Info)))
+ {
+ if (strcmp(pszName, szDefaultIface) == 0)
+ list.push_front(IfObj);
+ else
+ list.push_back(IfObj);
+ }
+ }
+
+ }
+ fclose(fp);
+ }
+ close(sock);
+ }
+ else
+ rc = VERR_INTERNAL_ERROR;
+
+ return rc;
+}
+
+int NetIfGetConfigByName(PNETIFINFO pInfo)
+{
+ int rc = VINF_SUCCESS;
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ return VERR_NOT_IMPLEMENTED;
+ rc = getInterfaceInfo(sock, pInfo->szShortName, pInfo);
+ close(sock);
+ return rc;
+}
+
+/**
+ * Retrieve the physical link speed in megabits per second. If the interface is
+ * not up or otherwise unavailable the zero speed is returned.
+ *
+ * @returns VBox status code.
+ *
+ * @param pcszIfName Interface name.
+ * @param puMbits Where to store the link speed.
+ */
+int NetIfGetLinkSpeed(const char *pcszIfName, uint32_t *puMbits)
+{
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ return VERR_OUT_OF_RESOURCES;
+ struct ifreq Req;
+ RT_ZERO(Req);
+ RTStrCopy(Req.ifr_name, sizeof(Req.ifr_name), pcszIfName);
+ if (ioctl(sock, SIOCGIFHWADDR, &Req) >= 0)
+ {
+ if (ioctl(sock, SIOCGIFFLAGS, &Req) >= 0)
+ if (Req.ifr_flags & IFF_UP)
+ {
+ close(sock);
+ *puMbits = getInterfaceSpeed(pcszIfName);
+ return VINF_SUCCESS;
+ }
+ }
+ close(sock);
+ *puMbits = 0;
+ return VWRN_NOT_FOUND;
+}
diff --git a/src/VBox/Main/src-server/linux/PerformanceLinux.cpp b/src/VBox/Main/src-server/linux/PerformanceLinux.cpp
new file mode 100644
index 00000000..91deb518
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/PerformanceLinux.cpp
@@ -0,0 +1,610 @@
+/* $Id: PerformanceLinux.cpp $ */
+/** @file
+ * VBox Linux-specific Performance Classes implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_PERFORMANCECOLLECTOR
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/statvfs.h>
+#include <errno.h>
+#include <mntent.h>
+#include <iprt/alloc.h>
+#include <iprt/cdefs.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/mp.h>
+#include <iprt/linux/sysfs.h>
+
+#include <map>
+#include <vector>
+
+#include "LoggingNew.h"
+#include "Performance.h"
+
+#define VBOXVOLINFO_NAME "VBoxVolInfo"
+
+namespace pm {
+
+class CollectorLinux : public CollectorHAL
+{
+public:
+ CollectorLinux();
+ virtual int preCollect(const CollectorHints& hints, uint64_t /* iTick */);
+ virtual int getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available);
+ virtual int getHostFilesystemUsage(const char *name, ULONG *total, ULONG *used, ULONG *available);
+ virtual int getHostDiskSize(const char *name, uint64_t *size);
+ virtual int getProcessMemoryUsage(RTPROCESS process, ULONG *used);
+
+ virtual int getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle);
+ virtual int getRawHostNetworkLoad(const char *name, uint64_t *rx, uint64_t *tx);
+ virtual int getRawHostDiskLoad(const char *name, uint64_t *disk_ms, uint64_t *total_ms);
+ virtual int getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total);
+
+ virtual int getDiskListByFs(const char *name, DiskList& listUsage, DiskList& listLoad);
+private:
+ virtual int _getRawHostCpuLoad();
+ int getRawProcessStats(RTPROCESS process, uint64_t *cpuUser, uint64_t *cpuKernel, ULONG *memPagesUsed);
+ void getDiskName(char *pszDiskName, size_t cbDiskName, const char *pszDevName, bool fTrimDigits);
+ void addVolumeDependencies(const char *pcszVolume, DiskList& listDisks);
+ void addRaidDisks(const char *pcszDevice, DiskList& listDisks);
+ char *trimTrailingDigits(char *pszName);
+ char *trimNewline(char *pszName);
+
+ struct VMProcessStats
+ {
+ uint64_t cpuUser;
+ uint64_t cpuKernel;
+ ULONG pagesUsed;
+ };
+
+ typedef std::map<RTPROCESS, VMProcessStats> VMProcessMap;
+
+ VMProcessMap mProcessStats;
+ uint64_t mUser, mKernel, mIdle;
+ uint64_t mSingleUser, mSingleKernel, mSingleIdle;
+ uint32_t mHZ;
+ ULONG mTotalRAM;
+};
+
+CollectorHAL *createHAL()
+{
+ return new CollectorLinux();
+}
+
+// Collector HAL for Linux
+
+CollectorLinux::CollectorLinux()
+{
+ long hz = sysconf(_SC_CLK_TCK);
+ if (hz == -1)
+ {
+ LogRel(("CollectorLinux failed to obtain HZ from kernel, assuming 100.\n"));
+ mHZ = 100;
+ }
+ else
+ mHZ = (uint32_t)hz;
+ LogFlowThisFunc(("mHZ=%u\n", mHZ));
+
+ uint64_t cb;
+ int rc = RTSystemQueryTotalRam(&cb);
+ if (RT_FAILURE(rc))
+ mTotalRAM = 0;
+ else
+ mTotalRAM = (ULONG)(cb / 1024);
+}
+
+int CollectorLinux::preCollect(const CollectorHints& hints, uint64_t /* iTick */)
+{
+ std::vector<RTPROCESS> processes;
+ hints.getProcesses(processes);
+
+ std::vector<RTPROCESS>::iterator it;
+ for (it = processes.begin(); it != processes.end(); ++it)
+ {
+ VMProcessStats vmStats;
+ int rc = getRawProcessStats(*it, &vmStats.cpuUser, &vmStats.cpuKernel, &vmStats.pagesUsed);
+ /* On failure, do NOT stop. Just skip the entry. Having the stats for
+ * one (probably broken) process frozen/zero is a minor issue compared
+ * to not updating many process stats and the host cpu stats. */
+ if (RT_SUCCESS(rc))
+ mProcessStats[*it] = vmStats;
+ }
+ if (hints.isHostCpuLoadCollected() || !mProcessStats.empty())
+ {
+ _getRawHostCpuLoad();
+ }
+ return VINF_SUCCESS;
+}
+
+int CollectorLinux::_getRawHostCpuLoad()
+{
+ int rc = VINF_SUCCESS;
+ long long unsigned uUser, uNice, uKernel, uIdle, uIowait, uIrq, uSoftirq;
+ FILE *f = fopen("/proc/stat", "r");
+
+ if (f)
+ {
+ char szBuf[128];
+ if (fgets(szBuf, sizeof(szBuf), f))
+ {
+ if (sscanf(szBuf, "cpu %llu %llu %llu %llu %llu %llu %llu",
+ &uUser, &uNice, &uKernel, &uIdle, &uIowait,
+ &uIrq, &uSoftirq) == 7)
+ {
+ mUser = uUser + uNice;
+ mKernel = uKernel + uIrq + uSoftirq;
+ mIdle = uIdle + uIowait;
+ }
+ /* Try to get single CPU stats. */
+ if (fgets(szBuf, sizeof(szBuf), f))
+ {
+ if (sscanf(szBuf, "cpu0 %llu %llu %llu %llu %llu %llu %llu",
+ &uUser, &uNice, &uKernel, &uIdle, &uIowait,
+ &uIrq, &uSoftirq) == 7)
+ {
+ mSingleUser = uUser + uNice;
+ mSingleKernel = uKernel + uIrq + uSoftirq;
+ mSingleIdle = uIdle + uIowait;
+ }
+ else
+ {
+ /* Assume that this is not an SMP system. */
+ Assert(RTMpGetCount() == 1);
+ mSingleUser = mUser;
+ mSingleKernel = mKernel;
+ mSingleIdle = mIdle;
+ }
+ }
+ else
+ rc = VERR_FILE_IO_ERROR;
+ }
+ else
+ rc = VERR_FILE_IO_ERROR;
+ fclose(f);
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+
+ return rc;
+}
+
+int CollectorLinux::getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle)
+{
+ *user = mUser;
+ *kernel = mKernel;
+ *idle = mIdle;
+ return VINF_SUCCESS;
+}
+
+int CollectorLinux::getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total)
+{
+ VMProcessMap::const_iterator it = mProcessStats.find(process);
+
+ if (it == mProcessStats.end())
+ {
+ Log (("No stats pre-collected for process %x\n", process));
+ return VERR_INTERNAL_ERROR;
+ }
+ *user = it->second.cpuUser;
+ *kernel = it->second.cpuKernel;
+ *total = mUser + mKernel + mIdle;
+ return VINF_SUCCESS;
+}
+
+int CollectorLinux::getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available)
+{
+ AssertReturn(mTotalRAM, VERR_INTERNAL_ERROR);
+ uint64_t cb;
+ int rc = RTSystemQueryAvailableRam(&cb);
+ if (RT_SUCCESS(rc))
+ {
+ *total = mTotalRAM;
+ *available = (ULONG)(cb / 1024);
+ *used = *total - *available;
+ }
+ return rc;
+}
+
+int CollectorLinux::getHostFilesystemUsage(const char *path, ULONG *total, ULONG *used, ULONG *available)
+{
+ struct statvfs stats;
+
+ if (statvfs(path, &stats) == -1)
+ {
+ LogRel(("Failed to collect %s filesystem usage: errno=%d.\n", path, errno));
+ return VERR_ACCESS_DENIED;
+ }
+ uint64_t cbBlock = stats.f_frsize ? stats.f_frsize : stats.f_bsize;
+ *total = (ULONG)(cbBlock * stats.f_blocks / _1M);
+ *used = (ULONG)(cbBlock * (stats.f_blocks - stats.f_bfree) / _1M);
+ *available = (ULONG)(cbBlock * stats.f_bavail / _1M);
+
+ return VINF_SUCCESS;
+}
+
+int CollectorLinux::getHostDiskSize(const char *pszFile, uint64_t *size)
+{
+ char *pszPath = NULL;
+
+ RTStrAPrintf(&pszPath, "/sys/block/%s/size", pszFile);
+ Assert(pszPath);
+
+ int rc = VINF_SUCCESS;
+ if (!RTLinuxSysFsExists(pszPath))
+ rc = VERR_FILE_NOT_FOUND;
+ else
+ {
+ int64_t cSize = 0;
+ rc = RTLinuxSysFsReadIntFile(0, &cSize, pszPath);
+ if (RT_SUCCESS(rc))
+ *size = cSize * 512;
+ }
+ RTStrFree(pszPath);
+ return rc;
+}
+
+int CollectorLinux::getProcessMemoryUsage(RTPROCESS process, ULONG *used)
+{
+ VMProcessMap::const_iterator it = mProcessStats.find(process);
+
+ if (it == mProcessStats.end())
+ {
+ Log (("No stats pre-collected for process %x\n", process));
+ return VERR_INTERNAL_ERROR;
+ }
+ *used = it->second.pagesUsed * (PAGE_SIZE / 1024);
+ return VINF_SUCCESS;
+}
+
+int CollectorLinux::getRawProcessStats(RTPROCESS process, uint64_t *cpuUser, uint64_t *cpuKernel, ULONG *memPagesUsed)
+{
+ int rc = VINF_SUCCESS;
+ char *pszName;
+ pid_t pid2;
+ char c;
+ int iTmp;
+ long long unsigned int u64Tmp;
+ unsigned uTmp;
+ unsigned long ulTmp;
+ signed long ilTmp;
+ ULONG u32user, u32kernel;
+ char buf[80]; /** @todo this should be tied to max allowed proc name. */
+
+ RTStrAPrintf(&pszName, "/proc/%d/stat", process);
+ FILE *f = fopen(pszName, "r");
+ RTStrFree(pszName);
+
+ if (f)
+ {
+ if (fscanf(f, "%d %79s %c %d %d %d %d %d %u %lu %lu %lu %lu %u %u "
+ "%ld %ld %ld %ld %ld %ld %llu %lu %u",
+ &pid2, buf, &c, &iTmp, &iTmp, &iTmp, &iTmp, &iTmp, &uTmp,
+ &ulTmp, &ulTmp, &ulTmp, &ulTmp, &u32user, &u32kernel,
+ &ilTmp, &ilTmp, &ilTmp, &ilTmp, &ilTmp, &ilTmp, &u64Tmp,
+ &ulTmp, memPagesUsed) == 24)
+ {
+ Assert((pid_t)process == pid2);
+ *cpuUser = u32user;
+ *cpuKernel = u32kernel;
+ }
+ else
+ rc = VERR_FILE_IO_ERROR;
+ fclose(f);
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+
+ return rc;
+}
+
+int CollectorLinux::getRawHostNetworkLoad(const char *pszFile, uint64_t *rx, uint64_t *tx)
+{
+ char szIfName[/*IFNAMSIZ*/ 16 + 36];
+
+ RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/net/%s/statistics/rx_bytes", pszFile);
+ if (!RTLinuxSysFsExists(szIfName))
+ return VERR_FILE_NOT_FOUND;
+
+ int64_t cSize = 0;
+ int rc = RTLinuxSysFsReadIntFile(0, &cSize, szIfName);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ *rx = cSize;
+
+ RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/net/%s/statistics/tx_bytes", pszFile);
+ if (!RTLinuxSysFsExists(szIfName))
+ return VERR_FILE_NOT_FOUND;
+
+ rc = RTLinuxSysFsReadIntFile(0, &cSize, szIfName);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ *tx = cSize;
+ return VINF_SUCCESS;
+}
+
+int CollectorLinux::getRawHostDiskLoad(const char *name, uint64_t *disk_ms, uint64_t *total_ms)
+{
+#if 0
+ int rc = VINF_SUCCESS;
+ char szIfName[/*IFNAMSIZ*/ 16 + 36];
+ long long unsigned int u64Busy, tmp;
+
+ RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/block/%s/stat", name);
+ FILE *f = fopen(szIfName, "r");
+ if (f)
+ {
+ if (fscanf(f, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
+ &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &u64Busy, &tmp) == 11)
+ {
+ *disk_ms = u64Busy;
+ *total_ms = (uint64_t)(mSingleUser + mSingleKernel + mSingleIdle) * 1000 / mHZ;
+ }
+ else
+ rc = VERR_FILE_IO_ERROR;
+ fclose(f);
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+#else
+ int rc = VERR_MISSING;
+ FILE *f = fopen("/proc/diskstats", "r");
+ if (f)
+ {
+ char szBuf[128];
+ while (fgets(szBuf, sizeof(szBuf), f))
+ {
+ char *pszBufName = szBuf;
+ while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
+ while (RT_C_IS_DIGIT(*pszBufName)) ++pszBufName; /* Skip major */
+ while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
+ while (RT_C_IS_DIGIT(*pszBufName)) ++pszBufName; /* Skip minor */
+ while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
+
+ char *pszBufData = strchr(pszBufName, ' ');
+ if (!pszBufData)
+ {
+ LogRel(("CollectorLinux::getRawHostDiskLoad() failed to parse disk stats: %s\n", szBuf));
+ continue;
+ }
+ *pszBufData++ = '\0';
+ if (!strcmp(name, pszBufName))
+ {
+ long long unsigned int u64Busy, tmp;
+
+ if (sscanf(pszBufData, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
+ &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &u64Busy, &tmp) == 11)
+ {
+ *disk_ms = u64Busy;
+ *total_ms = (uint64_t)(mSingleUser + mSingleKernel + mSingleIdle) * 1000 / mHZ;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_FILE_IO_ERROR;
+ break;
+ }
+ }
+ fclose(f);
+ }
+#endif
+
+ return rc;
+}
+
+char *CollectorLinux::trimNewline(char *pszName)
+{
+ size_t cbName = strlen(pszName);
+ if (cbName == 0)
+ return pszName;
+
+ char *pszEnd = pszName + cbName - 1;
+ while (pszEnd > pszName && *pszEnd == '\n')
+ pszEnd--;
+ pszEnd[1] = '\0';
+
+ return pszName;
+}
+
+char *CollectorLinux::trimTrailingDigits(char *pszName)
+{
+ size_t cbName = strlen(pszName);
+ if (cbName == 0)
+ return pszName;
+
+ char *pszEnd = pszName + cbName - 1;
+ while (pszEnd > pszName && (RT_C_IS_DIGIT(*pszEnd) || *pszEnd == '\n'))
+ pszEnd--;
+ pszEnd[1] = '\0';
+
+ return pszName;
+}
+
+/**
+ * Use the partition name to get the name of the disk. Any path component is stripped.
+ * if fTrimDigits is true, trailing digits are stripped as well, for example '/dev/sda5'
+ * is converted to 'sda'.
+ *
+ * @param pszDiskName Where to store the name of the disk.
+ * @param cbDiskName The size of the buffer pszDiskName points to.
+ * @param pszDevName The device name used to get the disk name.
+ * @param fTrimDigits Trim trailing digits (e.g. /dev/sda5)
+ */
+void CollectorLinux::getDiskName(char *pszDiskName, size_t cbDiskName, const char *pszDevName, bool fTrimDigits)
+{
+ unsigned cbName = 0;
+ size_t cbDevName = strlen(pszDevName);
+ const char *pszEnd = pszDevName + cbDevName - 1;
+ if (fTrimDigits)
+ while (pszEnd > pszDevName && RT_C_IS_DIGIT(*pszEnd))
+ pszEnd--;
+ while (pszEnd > pszDevName && *pszEnd != '/')
+ {
+ cbName++;
+ pszEnd--;
+ }
+ RTStrCopy(pszDiskName, RT_MIN(cbName + 1, cbDiskName), pszEnd + 1);
+}
+
+void CollectorLinux::addRaidDisks(const char *pcszDevice, DiskList& listDisks)
+{
+ FILE *f = fopen("/proc/mdstat", "r");
+ if (f)
+ {
+ char szBuf[128];
+ while (fgets(szBuf, sizeof(szBuf), f))
+ {
+ char *pszBufName = szBuf;
+
+ char *pszBufData = strchr(pszBufName, ' ');
+ if (!pszBufData)
+ {
+ LogRel(("CollectorLinux::addRaidDisks() failed to parse disk stats: %s\n", szBuf));
+ continue;
+ }
+ *pszBufData++ = '\0';
+ if (!strcmp(pcszDevice, pszBufName))
+ {
+ while (*pszBufData == ':') ++pszBufData; /* Skip delimiter */
+ while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
+ while (RT_C_IS_ALNUM(*pszBufData)) ++pszBufData; /* Skip status */
+ while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
+ while (RT_C_IS_ALNUM(*pszBufData)) ++pszBufData; /* Skip type */
+
+ while (*pszBufData != '\0')
+ {
+ while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
+ char *pszDisk = pszBufData;
+ while (RT_C_IS_ALPHA(*pszBufData))
+ ++pszBufData;
+ if (*pszBufData)
+ {
+ *pszBufData++ = '\0';
+ listDisks.push_back(RTCString(pszDisk));
+ while (*pszBufData != '\0' && *pszBufData != ' ')
+ ++pszBufData;
+ }
+ else
+ listDisks.push_back(RTCString(pszDisk));
+ }
+ break;
+ }
+ }
+ fclose(f);
+ }
+}
+
+void CollectorLinux::addVolumeDependencies(const char *pcszVolume, DiskList& listDisks)
+{
+ char szVolInfo[RTPATH_MAX];
+ int rc = RTPathAppPrivateArch(szVolInfo,
+ sizeof(szVolInfo) - sizeof("/" VBOXVOLINFO_NAME " ") - strlen(pcszVolume));
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("VolInfo: Failed to get program path, rc=%Rrc\n", rc));
+ return;
+ }
+ strcat(szVolInfo, "/" VBOXVOLINFO_NAME " ");
+ strcat(szVolInfo, pcszVolume);
+
+ FILE *fp = popen(szVolInfo, "r");
+ if (fp)
+ {
+ char szBuf[128];
+
+ while (fgets(szBuf, sizeof(szBuf), fp))
+ if (strncmp(szBuf, RT_STR_TUPLE("dm-")))
+ listDisks.push_back(RTCString(trimTrailingDigits(szBuf)));
+ else
+ listDisks.push_back(RTCString(trimNewline(szBuf)));
+
+ pclose(fp);
+ }
+ else
+ listDisks.push_back(RTCString(pcszVolume));
+}
+
+int CollectorLinux::getDiskListByFs(const char *pszPath, DiskList& listUsage, DiskList& listLoad)
+{
+ FILE *mtab = setmntent("/etc/mtab", "r");
+ if (mtab)
+ {
+ struct mntent *mntent;
+ while ((mntent = getmntent(mtab)))
+ {
+ /* Skip rootfs entry, there must be another root mount. */
+ if (strcmp(mntent->mnt_fsname, "rootfs") == 0)
+ continue;
+ if (strcmp(pszPath, mntent->mnt_dir) == 0)
+ {
+ char szDevName[128];
+ char szFsName[1024];
+ /* Try to resolve symbolic link if necessary. Yes, we access the file system here! */
+ int rc = RTPathReal(mntent->mnt_fsname, szFsName, sizeof(szFsName));
+ if (RT_FAILURE(rc))
+ continue; /* something got wrong, just ignore this path */
+ /* check against the actual mtab entry, NOT the real path as /dev/mapper/xyz is
+ * often a symlink to something else */
+ if (!strncmp(mntent->mnt_fsname, RT_STR_TUPLE("/dev/mapper")))
+ {
+ /* LVM */
+ getDiskName(szDevName, sizeof(szDevName), mntent->mnt_fsname, false /*=fTrimDigits*/);
+ addVolumeDependencies(szDevName, listUsage);
+ listLoad = listUsage;
+ }
+ else if (!strncmp(szFsName, RT_STR_TUPLE("/dev/md")))
+ {
+ /* Software RAID */
+ getDiskName(szDevName, sizeof(szDevName), szFsName, false /*=fTrimDigits*/);
+ listUsage.push_back(RTCString(szDevName));
+ addRaidDisks(szDevName, listLoad);
+ }
+ else
+ {
+ /* Plain disk partition. Trim the trailing digits to get the drive name */
+ getDiskName(szDevName, sizeof(szDevName), szFsName, true /*=fTrimDigits*/);
+ listUsage.push_back(RTCString(szDevName));
+ listLoad.push_back(RTCString(szDevName));
+ }
+ if (listUsage.empty() || listLoad.empty())
+ {
+ LogRel(("Failed to retrive disk info: getDiskName(%s) --> %s\n",
+ mntent->mnt_fsname, szDevName));
+ }
+ break;
+ }
+ }
+ endmntent(mtab);
+ }
+ return VINF_SUCCESS;
+}
+
+}
+
diff --git a/src/VBox/Main/src-server/linux/USBGetDevices.cpp b/src/VBox/Main/src-server/linux/USBGetDevices.cpp
new file mode 100644
index 00000000..93aa1945
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/USBGetDevices.cpp
@@ -0,0 +1,1800 @@
+/* $Id: USBGetDevices.cpp $ */
+/** @file
+ * VirtualBox Linux host USB device enumeration.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define VBOX_USB_WITH_USBFS
+#include "USBGetDevices.h"
+
+#include <VBox/err.h>
+#include <VBox/usb.h>
+#include <VBox/usblib.h>
+
+#include <iprt/linux/sysfs.h>
+#include <iprt/cdefs.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/fs.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include "vector.h"
+
+#ifdef VBOX_WITH_LINUX_COMPILER_H
+# include <linux/compiler.h>
+#endif
+#include <linux/usbdevice_fs.h>
+
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+
+#include <dirent.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Structure describing a host USB device */
+typedef struct USBDeviceInfo
+{
+ /** The device node of the device. */
+ char *mDevice;
+ /** The system identifier of the device. Specific to the probing
+ * method. */
+ char *mSysfsPath;
+ /** List of interfaces as sysfs paths */
+ VECTOR_PTR(char *) mvecpszInterfaces;
+} USBDeviceInfo;
+
+
+/**
+ * Does some extra checks to improve the detected device state.
+ *
+ * We cannot distinguish between USED_BY_HOST_CAPTURABLE and
+ * USED_BY_GUEST, HELD_BY_PROXY all that well and it shouldn't be
+ * necessary either.
+ *
+ * We will however, distinguish between the device we have permissions
+ * to open and those we don't. This is necessary for two reasons.
+ *
+ * Firstly, because it's futile to even attempt opening a device which we
+ * don't have access to, it only serves to confuse the user. (That said,
+ * it might also be a bit confusing for the user to see that a USB device
+ * is grayed out with no further explanation, and no way of generating an
+ * error hinting at why this is the case.)
+ *
+ * Secondly and more importantly, we're racing against udevd with respect
+ * to permissions and group settings on newly plugged devices. When we
+ * detect a new device that we cannot access we will poll on it for a few
+ * seconds to give udevd time to fix it. The polling is actually triggered
+ * in the 'new device' case in the compare loop.
+ *
+ * The USBDEVICESTATE_USED_BY_HOST state is only used for this no-access
+ * case, while USBDEVICESTATE_UNSUPPORTED is only used in the 'hub' case.
+ * When it's neither of these, we set USBDEVICESTATE_UNUSED or
+ * USBDEVICESTATE_USED_BY_HOST_CAPTURABLE depending on whether there is
+ * a driver associated with any of the interfaces.
+ *
+ * All except the access check and a special idVendor == 0 precaution
+ * is handled at parse time.
+ *
+ * @returns The adjusted state.
+ * @param pDevice The device.
+ */
+static USBDEVICESTATE usbDeterminState(PCUSBDEVICE pDevice)
+{
+ /*
+ * If it's already flagged as unsupported, there is nothing to do.
+ */
+ USBDEVICESTATE enmState = pDevice->enmState;
+ if (enmState == USBDEVICESTATE_UNSUPPORTED)
+ return USBDEVICESTATE_UNSUPPORTED;
+
+ /*
+ * Root hubs and similar doesn't have any vendor id, just
+ * refuse these device.
+ */
+ if (!pDevice->idVendor)
+ return USBDEVICESTATE_UNSUPPORTED;
+
+ /*
+ * Check if we've got access to the device, if we haven't flag
+ * it as used-by-host.
+ */
+#ifndef VBOX_USB_WITH_SYSFS
+ const char *pszAddress = pDevice->pszAddress;
+#else
+ if (pDevice->pszAddress == NULL)
+ /* We can't do much with the device without an address. */
+ return USBDEVICESTATE_UNSUPPORTED;
+ const char *pszAddress = strstr(pDevice->pszAddress, "//device:");
+ pszAddress = pszAddress != NULL
+ ? pszAddress + sizeof("//device:") - 1
+ : pDevice->pszAddress;
+#endif
+ if ( access(pszAddress, R_OK | W_OK) != 0
+ && errno == EACCES)
+ return USBDEVICESTATE_USED_BY_HOST;
+
+#ifdef VBOX_USB_WITH_SYSFS
+ /**
+ * @todo Check that any other essential fields are present and mark as
+ * invalid if not. Particularly to catch the case where the device was
+ * unplugged while we were reading in its properties.
+ */
+#endif
+
+ return enmState;
+}
+
+
+/**
+ * Dumps a USBDEVICE structure to the log using LogLevel 3.
+ * @param pDev The structure to log.
+ * @todo This is really common code.
+ */
+static void usbLogDevice(PUSBDEVICE pDev)
+{
+ NOREF(pDev);
+ if (LogIs3Enabled())
+ {
+ Log3(("USB device:\n"));
+ Log3(("Product: %s (%x)\n", pDev->pszProduct, pDev->idProduct));
+ Log3(("Manufacturer: %s (Vendor ID %x)\n", pDev->pszManufacturer, pDev->idVendor));
+ Log3(("Serial number: %s (%llx)\n", pDev->pszSerialNumber, pDev->u64SerialHash));
+ Log3(("Device revision: %d\n", pDev->bcdDevice));
+ Log3(("Device class: %x\n", pDev->bDeviceClass));
+ Log3(("Device subclass: %x\n", pDev->bDeviceSubClass));
+ Log3(("Device protocol: %x\n", pDev->bDeviceProtocol));
+ Log3(("USB version number: %d\n", pDev->bcdUSB));
+ Log3(("Device speed: %s\n",
+ pDev->enmSpeed == USBDEVICESPEED_UNKNOWN ? "unknown"
+ : pDev->enmSpeed == USBDEVICESPEED_LOW ? "1.5 MBit/s"
+ : pDev->enmSpeed == USBDEVICESPEED_FULL ? "12 MBit/s"
+ : pDev->enmSpeed == USBDEVICESPEED_HIGH ? "480 MBit/s"
+ : pDev->enmSpeed == USBDEVICESPEED_SUPER ? "5.0 GBit/s"
+ : pDev->enmSpeed == USBDEVICESPEED_VARIABLE ? "variable"
+ : "invalid"));
+ Log3(("Number of configurations: %d\n", pDev->bNumConfigurations));
+ Log3(("Bus number: %d\n", pDev->bBus));
+ Log3(("Port number: %d\n", pDev->bPort));
+ Log3(("Device number: %d\n", pDev->bDevNum));
+ Log3(("Device state: %s\n",
+ pDev->enmState == USBDEVICESTATE_UNSUPPORTED ? "unsupported"
+ : pDev->enmState == USBDEVICESTATE_USED_BY_HOST ? "in use by host"
+ : pDev->enmState == USBDEVICESTATE_USED_BY_HOST_CAPTURABLE ? "in use by host, possibly capturable"
+ : pDev->enmState == USBDEVICESTATE_UNUSED ? "not in use"
+ : pDev->enmState == USBDEVICESTATE_HELD_BY_PROXY ? "held by proxy"
+ : pDev->enmState == USBDEVICESTATE_USED_BY_GUEST ? "used by guest"
+ : "invalid"));
+ Log3(("OS device address: %s\n", pDev->pszAddress));
+ }
+}
+
+
+#ifdef VBOX_USB_WITH_USBFS
+
+/**
+ * "reads" the number suffix.
+ *
+ * It's more like validating it and skipping the necessary number of chars.
+ */
+static int usbfsReadSkipSuffix(char **ppszNext)
+{
+ char *pszNext = *ppszNext;
+ if (!RT_C_IS_SPACE(*pszNext) && *pszNext)
+ {
+ /* skip unit */
+ if (pszNext[0] == 'm' && pszNext[1] == 's')
+ pszNext += 2;
+ else if (pszNext[0] == 'm' && pszNext[1] == 'A')
+ pszNext += 2;
+
+ /* skip parenthesis */
+ if (*pszNext == '(')
+ {
+ pszNext = strchr(pszNext, ')');
+ if (!pszNext++)
+ {
+ AssertMsgFailed(("*ppszNext=%s\n", *ppszNext));
+ return VERR_PARSE_ERROR;
+ }
+ }
+
+ /* blank or end of the line. */
+ if (!RT_C_IS_SPACE(*pszNext) && *pszNext)
+ {
+ AssertMsgFailed(("pszNext=%s\n", pszNext));
+ return VERR_PARSE_ERROR;
+ }
+
+ /* it's ok. */
+ *ppszNext = pszNext;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reads a USB number returning the number and the position of the next character to parse.
+ */
+static int usbfsReadNum(const char *pszValue, unsigned uBase, uint32_t u32Mask, void *pvNum, char **ppszNext)
+{
+ /*
+ * Initialize return value to zero and strip leading spaces.
+ */
+ switch (u32Mask)
+ {
+ case 0xff: *(uint8_t *)pvNum = 0; break;
+ case 0xffff: *(uint16_t *)pvNum = 0; break;
+ case 0xffffffff: *(uint32_t *)pvNum = 0; break;
+ }
+ pszValue = RTStrStripL(pszValue);
+ if (*pszValue)
+ {
+ /*
+ * Try convert the number.
+ */
+ char *pszNext;
+ uint32_t u32 = 0;
+ RTStrToUInt32Ex(pszValue, &pszNext, uBase, &u32);
+ if (pszNext == pszValue)
+ {
+ AssertMsgFailed(("pszValue=%d\n", pszValue));
+ return VERR_NO_DATA;
+ }
+
+ /*
+ * Check the range.
+ */
+ if (u32 & ~u32Mask)
+ {
+ AssertMsgFailed(("pszValue=%d u32=%#x lMask=%#x\n", pszValue, u32, u32Mask));
+ return VERR_OUT_OF_RANGE;
+ }
+
+ int rc = usbfsReadSkipSuffix(&pszNext);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ *ppszNext = pszNext;
+
+ /*
+ * Set the value.
+ */
+ switch (u32Mask)
+ {
+ case 0xff: *(uint8_t *)pvNum = (uint8_t)u32; break;
+ case 0xffff: *(uint16_t *)pvNum = (uint16_t)u32; break;
+ case 0xffffffff: *(uint32_t *)pvNum = (uint32_t)u32; break;
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+static int usbfsRead8(const char *pszValue, unsigned uBase, uint8_t *pu8, char **ppszNext)
+{
+ return usbfsReadNum(pszValue, uBase, 0xff, pu8, ppszNext);
+}
+
+
+static int usbfsRead16(const char *pszValue, unsigned uBase, uint16_t *pu16, char **ppszNext)
+{
+ return usbfsReadNum(pszValue, uBase, 0xffff, pu16, ppszNext);
+}
+
+
+/**
+ * Reads a USB BCD number returning the number and the position of the next character to parse.
+ * The returned number contains the integer part in the high byte and the decimal part in the low byte.
+ */
+static int usbfsReadBCD(const char *pszValue, unsigned uBase, uint16_t *pu16, char **ppszNext)
+{
+ /*
+ * Initialize return value to zero and strip leading spaces.
+ */
+ *pu16 = 0;
+ pszValue = RTStrStripL(pszValue);
+ if (*pszValue)
+ {
+ /*
+ * Try convert the number.
+ */
+ /* integer part */
+ char *pszNext;
+ uint32_t u32Int = 0;
+ RTStrToUInt32Ex(pszValue, &pszNext, uBase, &u32Int);
+ if (pszNext == pszValue)
+ {
+ AssertMsgFailed(("pszValue=%s\n", pszValue));
+ return VERR_NO_DATA;
+ }
+ if (u32Int & ~0xff)
+ {
+ AssertMsgFailed(("pszValue=%s u32Int=%#x (int)\n", pszValue, u32Int));
+ return VERR_OUT_OF_RANGE;
+ }
+
+ /* skip dot and read decimal part */
+ if (*pszNext != '.')
+ {
+ AssertMsgFailed(("pszValue=%s pszNext=%s (int)\n", pszValue, pszNext));
+ return VERR_PARSE_ERROR;
+ }
+ char *pszValue2 = RTStrStripL(pszNext + 1);
+ uint32_t u32Dec = 0;
+ RTStrToUInt32Ex(pszValue2, &pszNext, uBase, &u32Dec);
+ if (pszNext == pszValue)
+ {
+ AssertMsgFailed(("pszValue=%s\n", pszValue));
+ return VERR_NO_DATA;
+ }
+ if (u32Dec & ~0xff)
+ {
+ AssertMsgFailed(("pszValue=%s u32Dec=%#x\n", pszValue, u32Dec));
+ return VERR_OUT_OF_RANGE;
+ }
+
+ /*
+ * Validate and skip stuff following the number.
+ */
+ int rc = usbfsReadSkipSuffix(&pszNext);
+ if (RT_FAILURE(rc))
+ return rc;
+ *ppszNext = pszNext;
+
+ /*
+ * Set the value.
+ */
+ *pu16 = (uint16_t)((u32Int << 8) | (uint16_t)u32Dec);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reads a string, i.e. allocates memory and copies it.
+ *
+ * We assume that a string is Utf8 and if that's not the case
+ * (pre-2.6.32-kernels used Latin-1, but so few devices return non-ASCII that
+ * this usually goes unnoticed) then we mercilessly force it to be so.
+ */
+static int usbfsReadStr(const char *pszValue, const char **ppsz)
+{
+ char *psz;
+
+ if (*ppsz)
+ RTStrFree((char *)*ppsz);
+ psz = RTStrDup(pszValue);
+ if (psz)
+ {
+ USBLibPurgeEncoding(psz);
+ *ppsz = psz;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Skips the current property.
+ */
+static char *usbfsReadSkip(char *pszValue)
+{
+ char *psz = strchr(pszValue, '=');
+ if (psz)
+ psz = strchr(psz + 1, '=');
+ if (!psz)
+ return strchr(pszValue, '\0');
+ while (psz > pszValue && !RT_C_IS_SPACE(psz[-1]))
+ psz--;
+ Assert(psz > pszValue);
+ return psz;
+}
+
+
+/**
+ * Determine the USB speed.
+ */
+static int usbfsReadSpeed(const char *pszValue, USBDEVICESPEED *pSpd, char **ppszNext)
+{
+ pszValue = RTStrStripL(pszValue);
+ /* verified with Linux 2.4.0 ... Linux 2.6.25 */
+ if (!strncmp(pszValue, RT_STR_TUPLE("1.5")))
+ *pSpd = USBDEVICESPEED_LOW;
+ else if (!strncmp(pszValue, RT_STR_TUPLE("12 ")))
+ *pSpd = USBDEVICESPEED_FULL;
+ else if (!strncmp(pszValue, RT_STR_TUPLE("480")))
+ *pSpd = USBDEVICESPEED_HIGH;
+ else if (!strncmp(pszValue, RT_STR_TUPLE("5000")))
+ *pSpd = USBDEVICESPEED_SUPER;
+ else
+ *pSpd = USBDEVICESPEED_UNKNOWN;
+ while (pszValue[0] != '\0' && !RT_C_IS_SPACE(pszValue[0]))
+ pszValue++;
+ *ppszNext = (char *)pszValue;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Compare a prefix and returns pointer to the char following it if it matches.
+ */
+static char *usbfsPrefix(char *psz, const char *pszPref, size_t cchPref)
+{
+ if (strncmp(psz, pszPref, cchPref))
+ return NULL;
+ return psz + cchPref;
+}
+
+
+/** Just a worker for USBProxyServiceLinux::getDevices that avoids some code duplication. */
+static int usbfsAddDeviceToChain(PUSBDEVICE pDev, PUSBDEVICE *ppFirst, PUSBDEVICE **pppNext, const char *pszUsbfsRoot,
+ bool fUnsupportedDevicesToo, int rc)
+{
+ /* usbDeterminState requires the address. */
+ PUSBDEVICE pDevNew = (PUSBDEVICE)RTMemDup(pDev, sizeof(*pDev));
+ if (pDevNew)
+ {
+ RTStrAPrintf((char **)&pDevNew->pszAddress, "%s/%03d/%03d", pszUsbfsRoot, pDevNew->bBus, pDevNew->bDevNum);
+ if (pDevNew->pszAddress)
+ {
+ pDevNew->enmState = usbDeterminState(pDevNew);
+ if (pDevNew->enmState != USBDEVICESTATE_UNSUPPORTED || fUnsupportedDevicesToo)
+ {
+ if (*pppNext)
+ **pppNext = pDevNew;
+ else
+ *ppFirst = pDevNew;
+ *pppNext = &pDevNew->pNext;
+ }
+ else
+ deviceFree(pDevNew);
+ }
+ else
+ {
+ deviceFree(pDevNew);
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ {
+ rc = VERR_NO_MEMORY;
+ deviceFreeMembers(pDev);
+ }
+
+ return rc;
+}
+
+
+static int usbfsOpenDevicesFile(const char *pszUsbfsRoot, FILE **ppFile)
+{
+ char *pszPath;
+ FILE *pFile;
+ RTStrAPrintf(&pszPath, "%s/devices", pszUsbfsRoot);
+ if (!pszPath)
+ return VERR_NO_MEMORY;
+ pFile = fopen(pszPath, "r");
+ RTStrFree(pszPath);
+ if (!pFile)
+ return RTErrConvertFromErrno(errno);
+ *ppFile = pFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * USBProxyService::getDevices() implementation for usbfs.
+ *
+ * The @a fUnsupportedDevicesToo flag tells the function to return information
+ * about unsupported devices as well. This is used as a sanity test to check
+ * that a devices file is really what we expect.
+ */
+static PUSBDEVICE usbfsGetDevices(const char *pszUsbfsRoot, bool fUnsupportedDevicesToo)
+{
+ PUSBDEVICE pFirst = NULL;
+ FILE *pFile = NULL;
+ int rc;
+ rc = usbfsOpenDevicesFile(pszUsbfsRoot, &pFile);
+ if (RT_SUCCESS(rc))
+ {
+ PUSBDEVICE *ppNext = NULL;
+ int cHits = 0;
+ char szLine[1024];
+ USBDEVICE Dev;
+ RT_ZERO(Dev);
+ Dev.enmState = USBDEVICESTATE_UNUSED;
+
+ /* Set close on exit and hope no one is racing us. */
+ rc = fcntl(fileno(pFile), F_SETFD, FD_CLOEXEC) >= 0
+ ? VINF_SUCCESS
+ : RTErrConvertFromErrno(errno);
+ while ( RT_SUCCESS(rc)
+ && fgets(szLine, sizeof(szLine), pFile))
+ {
+ char *psz;
+ char *pszValue;
+
+ /* validate and remove the trailing newline. */
+ psz = strchr(szLine, '\0');
+ if (psz[-1] != '\n' && !feof(pFile))
+ {
+ AssertMsgFailed(("Line too long. (cch=%d)\n", strlen(szLine)));
+ continue;
+ }
+
+ /* strip */
+ psz = RTStrStrip(szLine);
+ if (!*psz)
+ continue;
+
+ /*
+ * Interpret the line.
+ * (Ordered by normal occurrence.)
+ */
+ char ch = psz[0];
+ if (psz[1] != ':')
+ continue;
+ psz = RTStrStripL(psz + 3);
+#define PREFIX(str) ( (pszValue = usbfsPrefix(psz, str, sizeof(str) - 1)) != NULL )
+ switch (ch)
+ {
+ /*
+ * T: Bus=dd Lev=dd Prnt=dd Port=dd Cnt=dd Dev#=ddd Spd=ddd MxCh=dd
+ * | | | | | | | | |__MaxChildren
+ * | | | | | | | |__Device Speed in Mbps
+ * | | | | | | |__DeviceNumber
+ * | | | | | |__Count of devices at this level
+ * | | | | |__Connector/Port on Parent for this device
+ * | | | |__Parent DeviceNumber
+ * | | |__Level in topology for this bus
+ * | |__Bus number
+ * |__Topology info tag
+ */
+ case 'T':
+ /* add */
+ AssertMsg(cHits >= 3 || cHits == 0, ("cHits=%d\n", cHits));
+ if (cHits >= 3)
+ rc = usbfsAddDeviceToChain(&Dev, &pFirst, &ppNext, pszUsbfsRoot, fUnsupportedDevicesToo, rc);
+ else
+ deviceFreeMembers(&Dev);
+
+ /* Reset device state */
+ RT_ZERO(Dev);
+ Dev.enmState = USBDEVICESTATE_UNUSED;
+ cHits = 1;
+
+ /* parse the line. */
+ while (*psz && RT_SUCCESS(rc))
+ {
+ if (PREFIX("Bus="))
+ rc = usbfsRead8(pszValue, 10, &Dev.bBus, &psz);
+ else if (PREFIX("Port="))
+ rc = usbfsRead8(pszValue, 10, &Dev.bPort, &psz);
+ else if (PREFIX("Spd="))
+ rc = usbfsReadSpeed(pszValue, &Dev.enmSpeed, &psz);
+ else if (PREFIX("Dev#="))
+ rc = usbfsRead8(pszValue, 10, &Dev.bDevNum, &psz);
+ else
+ psz = usbfsReadSkip(psz);
+ psz = RTStrStripL(psz);
+ }
+ break;
+
+ /*
+ * Bandwidth info:
+ * B: Alloc=ddd/ddd us (xx%), #Int=ddd, #Iso=ddd
+ * | | | |__Number of isochronous requests
+ * | | |__Number of interrupt requests
+ * | |__Total Bandwidth allocated to this bus
+ * |__Bandwidth info tag
+ */
+ case 'B':
+ break;
+
+ /*
+ * D: Ver=x.xx Cls=xx(sssss) Sub=xx Prot=xx MxPS=dd #Cfgs=dd
+ * | | | | | | |__NumberConfigurations
+ * | | | | | |__MaxPacketSize of Default Endpoint
+ * | | | | |__DeviceProtocol
+ * | | | |__DeviceSubClass
+ * | | |__DeviceClass
+ * | |__Device USB version
+ * |__Device info tag #1
+ */
+ case 'D':
+ while (*psz && RT_SUCCESS(rc))
+ {
+ if (PREFIX("Ver="))
+ rc = usbfsReadBCD(pszValue, 16, &Dev.bcdUSB, &psz);
+ else if (PREFIX("Cls="))
+ {
+ rc = usbfsRead8(pszValue, 16, &Dev.bDeviceClass, &psz);
+ if (RT_SUCCESS(rc) && Dev.bDeviceClass == 9 /* HUB */)
+ Dev.enmState = USBDEVICESTATE_UNSUPPORTED;
+ }
+ else if (PREFIX("Sub="))
+ rc = usbfsRead8(pszValue, 16, &Dev.bDeviceSubClass, &psz);
+ else if (PREFIX("Prot="))
+ rc = usbfsRead8(pszValue, 16, &Dev.bDeviceProtocol, &psz);
+ //else if (PREFIX("MxPS="))
+ // rc = usbRead16(pszValue, 10, &Dev.wMaxPacketSize, &psz);
+ else if (PREFIX("#Cfgs="))
+ rc = usbfsRead8(pszValue, 10, &Dev.bNumConfigurations, &psz);
+ else
+ psz = usbfsReadSkip(psz);
+ psz = RTStrStripL(psz);
+ }
+ cHits++;
+ break;
+
+ /*
+ * P: Vendor=xxxx ProdID=xxxx Rev=xx.xx
+ * | | | |__Product revision number
+ * | | |__Product ID code
+ * | |__Vendor ID code
+ * |__Device info tag #2
+ */
+ case 'P':
+ while (*psz && RT_SUCCESS(rc))
+ {
+ if (PREFIX("Vendor="))
+ rc = usbfsRead16(pszValue, 16, &Dev.idVendor, &psz);
+ else if (PREFIX("ProdID="))
+ rc = usbfsRead16(pszValue, 16, &Dev.idProduct, &psz);
+ else if (PREFIX("Rev="))
+ rc = usbfsReadBCD(pszValue, 16, &Dev.bcdDevice, &psz);
+ else
+ psz = usbfsReadSkip(psz);
+ psz = RTStrStripL(psz);
+ }
+ cHits++;
+ break;
+
+ /*
+ * String.
+ */
+ case 'S':
+ if (PREFIX("Manufacturer="))
+ rc = usbfsReadStr(pszValue, &Dev.pszManufacturer);
+ else if (PREFIX("Product="))
+ rc = usbfsReadStr(pszValue, &Dev.pszProduct);
+ else if (PREFIX("SerialNumber="))
+ {
+ rc = usbfsReadStr(pszValue, &Dev.pszSerialNumber);
+ if (RT_SUCCESS(rc))
+ Dev.u64SerialHash = USBLibHashSerial(pszValue);
+ }
+ break;
+
+ /*
+ * C:* #Ifs=dd Cfg#=dd Atr=xx MPwr=dddmA
+ * | | | | | |__MaxPower in mA
+ * | | | | |__Attributes
+ * | | | |__ConfiguratioNumber
+ * | | |__NumberOfInterfaces
+ * | |__ "*" indicates the active configuration (others are " ")
+ * |__Config info tag
+ */
+ case 'C':
+ break;
+
+ /*
+ * I: If#=dd Alt=dd #EPs=dd Cls=xx(sssss) Sub=xx Prot=xx Driver=ssss
+ * | | | | | | | |__Driver name
+ * | | | | | | | or "(none)"
+ * | | | | | | |__InterfaceProtocol
+ * | | | | | |__InterfaceSubClass
+ * | | | | |__InterfaceClass
+ * | | | |__NumberOfEndpoints
+ * | | |__AlternateSettingNumber
+ * | |__InterfaceNumber
+ * |__Interface info tag
+ */
+ case 'I':
+ {
+ /* Check for thing we don't support. */
+ while (*psz && RT_SUCCESS(rc))
+ {
+ if (PREFIX("Driver="))
+ {
+ const char *pszDriver = NULL;
+ rc = usbfsReadStr(pszValue, &pszDriver);
+ if ( !pszDriver
+ || !*pszDriver
+ || !strcmp(pszDriver, "(none)")
+ || !strcmp(pszDriver, "(no driver)"))
+ /* no driver */;
+ else if (!strcmp(pszDriver, "hub"))
+ Dev.enmState = USBDEVICESTATE_UNSUPPORTED;
+ else if (Dev.enmState == USBDEVICESTATE_UNUSED)
+ Dev.enmState = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE;
+ RTStrFree((char *)pszDriver);
+ break; /* last attrib */
+ }
+ else if (PREFIX("Cls="))
+ {
+ uint8_t bInterfaceClass;
+ rc = usbfsRead8(pszValue, 16, &bInterfaceClass, &psz);
+ if (RT_SUCCESS(rc) && bInterfaceClass == 9 /* HUB */)
+ Dev.enmState = USBDEVICESTATE_UNSUPPORTED;
+ }
+ else
+ psz = usbfsReadSkip(psz);
+ psz = RTStrStripL(psz);
+ }
+ break;
+ }
+
+
+ /*
+ * E: Ad=xx(s) Atr=xx(ssss) MxPS=dddd Ivl=dddms
+ * | | | | |__Interval (max) between transfers
+ * | | | |__EndpointMaxPacketSize
+ * | | |__Attributes(EndpointType)
+ * | |__EndpointAddress(I=In,O=Out)
+ * |__Endpoint info tag
+ */
+ case 'E':
+ break;
+
+ }
+#undef PREFIX
+ } /* parse loop */
+ fclose(pFile);
+
+ /*
+ * Add the current entry.
+ */
+ AssertMsg(cHits >= 3 || cHits == 0, ("cHits=%d\n", cHits));
+ if (cHits >= 3)
+ rc = usbfsAddDeviceToChain(&Dev, &pFirst, &ppNext, pszUsbfsRoot, fUnsupportedDevicesToo, rc);
+
+ /*
+ * Success?
+ */
+ if (RT_FAILURE(rc))
+ {
+ while (pFirst)
+ {
+ PUSBDEVICE pFree = pFirst;
+ pFirst = pFirst->pNext;
+ deviceFree(pFree);
+ }
+ }
+ }
+ if (RT_FAILURE(rc))
+ LogFlow(("USBProxyServiceLinux::getDevices: rc=%Rrc\n", rc));
+ return pFirst;
+}
+
+#endif /* VBOX_USB_WITH_USBFS */
+#ifdef VBOX_USB_WITH_SYSFS
+
+static void usbsysfsCleanupDevInfo(USBDeviceInfo *pSelf)
+{
+ RTStrFree(pSelf->mDevice);
+ RTStrFree(pSelf->mSysfsPath);
+ pSelf->mDevice = pSelf->mSysfsPath = NULL;
+ VEC_CLEANUP_PTR(&pSelf->mvecpszInterfaces);
+}
+
+
+static int usbsysfsInitDevInfo(USBDeviceInfo *pSelf, const char *aDevice, const char *aSystemID)
+{
+ pSelf->mDevice = aDevice ? RTStrDup(aDevice) : NULL;
+ pSelf->mSysfsPath = aSystemID ? RTStrDup(aSystemID) : NULL;
+ VEC_INIT_PTR(&pSelf->mvecpszInterfaces, char *, RTStrFree);
+ if ((aDevice && !pSelf->mDevice) || (aSystemID && ! pSelf->mSysfsPath))
+ {
+ usbsysfsCleanupDevInfo(pSelf);
+ return 0;
+ }
+ return 1;
+}
+
+# define USBDEVICE_MAJOR 189
+
+/**
+ * Calculate the bus (a.k.a root hub) number of a USB device from it's sysfs
+ * path.
+ *
+ * sysfs nodes representing root hubs have file names of the form
+ * usb<n>, where n is the bus number; other devices start with that number.
+ * See [http://www.linux-usb.org/FAQ.html#i6] and
+ * [http://www.kernel.org/doc/Documentation/usb/proc_usb_info.txt] for
+ * equivalent information about usbfs.
+ *
+ * @returns a bus number greater than 0 on success or 0 on failure.
+ */
+static unsigned usbsysfsGetBusFromPath(const char *pszPath)
+{
+ const char *pszFile = strrchr(pszPath, '/');
+ if (!pszFile)
+ return 0;
+ unsigned bus = RTStrToUInt32(pszFile + 1);
+ if ( !bus
+ && pszFile[1] == 'u' && pszFile[2] == 's' && pszFile[3] == 'b')
+ bus = RTStrToUInt32(pszFile + 4);
+ return bus;
+}
+
+
+/**
+ * Calculate the device number of a USB device.
+ *
+ * See drivers/usb/core/hub.c:usb_new_device as of Linux 2.6.20.
+ */
+static dev_t usbsysfsMakeDevNum(unsigned bus, unsigned device)
+{
+ AssertReturn(bus > 0, 0);
+ AssertReturn(((device - 1) & ~127) == 0, 0);
+ AssertReturn(device > 0, 0);
+ return makedev(USBDEVICE_MAJOR, ((bus - 1) << 7) + device - 1);
+}
+
+
+/**
+ * If a file @a pszNode from /sys/bus/usb/devices is a device rather than an
+ * interface add an element for the device to @a pvecDevInfo.
+ */
+static int usbsysfsAddIfDevice(const char *pszDevicesRoot, const char *pszNode, VECTOR_OBJ(USBDeviceInfo) *pvecDevInfo)
+{
+ const char *pszFile = strrchr(pszNode, '/');
+ if (!pszFile)
+ return VERR_INVALID_PARAMETER;
+ if (strchr(pszFile, ':'))
+ return VINF_SUCCESS;
+
+ unsigned bus = usbsysfsGetBusFromPath(pszNode);
+ if (!bus)
+ return VINF_SUCCESS;
+
+ int64_t device;
+ int rc = RTLinuxSysFsReadIntFile(10, &device, "%s/devnum", pszNode);
+ if (RT_FAILURE(rc))
+ return VINF_SUCCESS;
+
+ dev_t devnum = usbsysfsMakeDevNum(bus, (int)device);
+ if (!devnum)
+ return VINF_SUCCESS;
+
+ char szDevPath[RTPATH_MAX];
+ rc = RTLinuxCheckDevicePath(devnum, RTFS_TYPE_DEV_CHAR,
+ szDevPath, sizeof(szDevPath),
+ "%s/%.3d/%.3d",
+ pszDevicesRoot, bus, device);
+ if (RT_FAILURE(rc))
+ return VINF_SUCCESS;
+
+ USBDeviceInfo info;
+ if (usbsysfsInitDevInfo(&info, szDevPath, pszNode))
+ {
+ rc = VEC_PUSH_BACK_OBJ(pvecDevInfo, USBDeviceInfo, &info);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ usbsysfsCleanupDevInfo(&info);
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * The logic for testing whether a sysfs address corresponds to an interface of
+ * a device.
+ *
+ * Both must be referenced by their canonical sysfs paths. This is not tested,
+ * as the test requires file-system interaction.
+ */
+static bool usbsysfsMuiIsAnInterfaceOf(const char *pszIface, const char *pszDev)
+{
+ size_t cchDev = strlen(pszDev);
+
+ AssertPtr(pszIface);
+ AssertPtr(pszDev);
+ Assert(pszIface[0] == '/');
+ Assert(pszDev[0] == '/');
+ Assert(pszDev[cchDev - 1] != '/');
+
+ /* If this passes, pszIface is at least cchDev long */
+ if (strncmp(pszIface, pszDev, cchDev))
+ return false;
+
+ /* If this passes, pszIface is longer than cchDev */
+ if (pszIface[cchDev] != '/')
+ return false;
+
+ /* In sysfs an interface is an immediate subdirectory of the device */
+ if (strchr(pszIface + cchDev + 1, '/'))
+ return false;
+
+ /* And it always has a colon in its name */
+ if (!strchr(pszIface + cchDev + 1, ':'))
+ return false;
+
+ /* And hopefully we have now elimitated everything else */
+ return true;
+}
+
+
+# ifdef DEBUG
+# ifdef __cplusplus
+/** Unit test the logic in muiIsAnInterfaceOf in debug builds. */
+class testIsAnInterfaceOf
+{
+public:
+ testIsAnInterfaceOf()
+ {
+ Assert(usbsysfsMuiIsAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0",
+ "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
+ Assert(!usbsysfsMuiIsAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-1",
+ "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
+ Assert(!usbsysfsMuiIsAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0/driver",
+ "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
+ }
+};
+static testIsAnInterfaceOf testIsAnInterfaceOfInst;
+# endif /* __cplusplus */
+# endif /* DEBUG */
+
+
+/**
+ * Tell whether a file in /sys/bus/usb/devices is an interface rather than a
+ * device.
+ */
+static int usbsysfsAddIfInterfaceOf(const char *pszNode, USBDeviceInfo *pInfo)
+{
+ if (!usbsysfsMuiIsAnInterfaceOf(pszNode, pInfo->mSysfsPath))
+ return VINF_SUCCESS;
+
+ char *pszDup = (char *)RTStrDup(pszNode);
+ if (pszDup)
+ {
+ int rc = VEC_PUSH_BACK_PTR(&pInfo->mvecpszInterfaces, char *, pszDup);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ RTStrFree(pszDup);
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Helper for usbsysfsReadFilePaths().
+ *
+ * Adds the entries from the open directory @a pDir to the vector @a pvecpchDevs
+ * using either the full path or the realpath() and skipping hidden files and
+ * files on which realpath() fails.
+ */
+static int usbsysfsReadFilePathsFromDir(const char *pszPath, DIR *pDir, VECTOR_PTR(char *) *pvecpchDevs)
+{
+ struct dirent entry, *pResult;
+ int err, rc;
+
+#if RT_GNUC_PREREQ(4, 6)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ for (err = readdir_r(pDir, &entry, &pResult); pResult;
+ err = readdir_r(pDir, &entry, &pResult))
+#if RT_GNUC_PREREQ(4, 6)
+# pragma GCC diagnostic pop
+#endif
+ {
+ char szPath[RTPATH_MAX + 1];
+ char szRealPath[RTPATH_MAX + 1];
+ if (entry.d_name[0] == '.')
+ continue;
+ if (snprintf(szPath, sizeof(szPath), "%s/%s", pszPath, entry.d_name) < 0)
+ return RTErrConvertFromErrno(errno); /** @todo r=bird: snprintf isn't document to set errno. Also, wouldn't it be better to continue on errors? Finally, you don't need to copy pszPath each time... */
+ if (!realpath(szPath, szRealPath))
+ return RTErrConvertFromErrno(errno);
+ char *pszPathCopy = RTStrDup(szRealPath);
+ if (!pszPathCopy)
+ return VERR_NO_MEMORY;
+ if (RT_FAILURE(rc = VEC_PUSH_BACK_PTR(pvecpchDevs, char *, pszPathCopy)))
+ return rc;
+ }
+ return RTErrConvertFromErrno(err);
+}
+
+
+/**
+ * Dump the names of a directory's entries into a vector of char pointers.
+ *
+ * @returns zero on success or (positive) posix error value.
+ * @param pszPath the path to dump.
+ * @param pvecpchDevs an empty vector of char pointers - must be cleaned up
+ * by the caller even on failure.
+ * @param withRealPath whether to canonicalise the filename with realpath
+ */
+static int usbsysfsReadFilePaths(const char *pszPath, VECTOR_PTR(char *) *pvecpchDevs)
+{
+ AssertPtrReturn(pvecpchDevs, EINVAL);
+ AssertReturn(VEC_SIZE_PTR(pvecpchDevs) == 0, EINVAL);
+ AssertPtrReturn(pszPath, EINVAL);
+
+ DIR *pDir = opendir(pszPath);
+ if (!pDir)
+ return RTErrConvertFromErrno(errno);
+ int rc = usbsysfsReadFilePathsFromDir(pszPath, pDir, pvecpchDevs);
+ if (closedir(pDir) < 0 && RT_SUCCESS(rc))
+ rc = RTErrConvertFromErrno(errno);
+ return rc;
+}
+
+
+/**
+ * Logic for USBSysfsEnumerateHostDevices.
+ *
+ * @param pvecDevInfo vector of device information structures to add device
+ * information to
+ * @param pvecpchDevs empty scratch vector which will be freed by the caller,
+ * to simplify exit logic
+ */
+static int usbsysfsEnumerateHostDevicesWorker(const char *pszDevicesRoot,
+ VECTOR_OBJ(USBDeviceInfo) *pvecDevInfo,
+ VECTOR_PTR(char *) *pvecpchDevs)
+{
+
+ AssertPtrReturn(pvecDevInfo, VERR_INVALID_POINTER);
+ LogFlowFunc (("pvecDevInfo=%p\n", pvecDevInfo));
+
+ int rc = usbsysfsReadFilePaths("/sys/bus/usb/devices", pvecpchDevs);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ char **ppszEntry;
+ VEC_FOR_EACH(pvecpchDevs, char *, ppszEntry)
+ {
+ rc = usbsysfsAddIfDevice(pszDevicesRoot, *ppszEntry, pvecDevInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ USBDeviceInfo *pInfo;
+ VEC_FOR_EACH(pvecDevInfo, USBDeviceInfo, pInfo)
+ VEC_FOR_EACH(pvecpchDevs, char *, ppszEntry)
+ {
+ rc = usbsysfsAddIfInterfaceOf(*ppszEntry, pInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+static int usbsysfsEnumerateHostDevices(const char *pszDevicesRoot, VECTOR_OBJ(USBDeviceInfo) *pvecDevInfo)
+{
+ VECTOR_PTR(char *) vecpchDevs;
+
+ AssertReturn(VEC_SIZE_OBJ(pvecDevInfo) == 0, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("entered\n"));
+ VEC_INIT_PTR(&vecpchDevs, char *, RTStrFree);
+ int rc = usbsysfsEnumerateHostDevicesWorker(pszDevicesRoot, pvecDevInfo, &vecpchDevs);
+ VEC_CLEANUP_PTR(&vecpchDevs);
+ LogFlowFunc(("rc=%Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Helper function for extracting the port number on the parent device from
+ * the sysfs path value.
+ *
+ * The sysfs path is a chain of elements separated by forward slashes, and for
+ * USB devices, the last element in the chain takes the form
+ * <port>-<port>.[...].<port>[:<config>.<interface>]
+ * where the first <port> is the port number on the root hub, and the following
+ * (optional) ones are the port numbers on any other hubs between the device
+ * and the root hub. The last part (:<config.interface>) is only present for
+ * interfaces, not for devices. This API should only be called for devices.
+ * For compatibility with usbfs, which enumerates from zero up, we subtract one
+ * from the port number.
+ *
+ * For root hubs, the last element in the chain takes the form
+ * usb<hub number>
+ * and usbfs always returns port number zero.
+ *
+ * @returns VBox status code. pu8Port is set on success.
+ * @param pszPath The sysfs path to parse.
+ * @param pu8Port Where to store the port number.
+ */
+static int usbsysfsGetPortFromStr(const char *pszPath, uint8_t *pu8Port)
+{
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertPtrReturn(pu8Port, VERR_INVALID_POINTER);
+
+ /*
+ * This should not be possible until we get PCs with USB as their primary bus.
+ * Note: We don't assert this, as we don't expect the caller to validate the
+ * sysfs path.
+ */
+ const char *pszLastComp = strrchr(pszPath, '/');
+ if (!pszLastComp)
+ {
+ Log(("usbGetPortFromSysfsPath(%s): failed [1]\n", pszPath));
+ return VERR_INVALID_PARAMETER;
+ }
+ pszLastComp++; /* skip the slash */
+
+ /*
+ * This API should not be called for interfaces, so the last component
+ * of the path should not contain a colon. We *do* assert this, as it
+ * might indicate a caller bug.
+ */
+ AssertMsgReturn(strchr(pszLastComp, ':') == NULL, ("%s\n", pszPath), VERR_INVALID_PARAMETER);
+
+ /*
+ * Look for the start of the last number.
+ */
+ const char *pchDash = strrchr(pszLastComp, '-');
+ const char *pchDot = strrchr(pszLastComp, '.');
+ if (!pchDash && !pchDot)
+ {
+ /* No -/. so it must be a root hub. Check that it's usb<something>. */
+ if (strncmp(pszLastComp, RT_STR_TUPLE("usb")) != 0)
+ {
+ Log(("usbGetPortFromSysfsPath(%s): failed [2]\n", pszPath));
+ return VERR_INVALID_PARAMETER;
+ }
+ return VERR_NOT_SUPPORTED;
+ }
+
+ const char *pszLastPort = pchDot != NULL
+ ? pchDot + 1
+ : pchDash + 1;
+ int rc = RTStrToUInt8Full(pszLastPort, 10, pu8Port);
+ if (rc != VINF_SUCCESS)
+ {
+ Log(("usbGetPortFromSysfsPath(%s): failed [3], rc=%Rrc\n", pszPath, rc));
+ return VERR_INVALID_PARAMETER;
+ }
+ if (*pu8Port == 0)
+ {
+ Log(("usbGetPortFromSysfsPath(%s): failed [4]\n", pszPath));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* usbfs compatibility, 0-based port number. */
+ *pu8Port = (uint8_t)(*pu8Port - 1);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Converts a sysfs BCD value into a uint16_t.
+ *
+ * In contrast to usbReadBCD() this function can handle BCD values without
+ * a decimal separator. This is necessary for parsing bcdDevice.
+ *
+ * @param pszBuf Pointer to the string buffer.
+ * @param pu15 Pointer to the return value.
+ * @returns IPRT status code.
+ */
+static int usbsysfsConvertStrToBCD(const char *pszBuf, uint16_t *pu16)
+{
+ char *pszNext;
+ int32_t i32;
+
+ pszBuf = RTStrStripL(pszBuf);
+ int rc = RTStrToInt32Ex(pszBuf, &pszNext, 16, &i32);
+ if ( RT_FAILURE(rc)
+ || rc == VWRN_NUMBER_TOO_BIG
+ || i32 < 0)
+ return VERR_NUMBER_TOO_BIG;
+ if (*pszNext == '.')
+ {
+ if (i32 > 255)
+ return VERR_NUMBER_TOO_BIG;
+ int32_t i32Lo;
+ rc = RTStrToInt32Ex(pszNext+1, &pszNext, 16, &i32Lo);
+ if ( RT_FAILURE(rc)
+ || rc == VWRN_NUMBER_TOO_BIG
+ || i32Lo > 255
+ || i32Lo < 0)
+ return VERR_NUMBER_TOO_BIG;
+ i32 = (i32 << 8) | i32Lo;
+ }
+ if ( i32 > 65535
+ || (*pszNext != '\0' && *pszNext != ' '))
+ return VERR_NUMBER_TOO_BIG;
+
+ *pu16 = (uint16_t)i32;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Returns the byte value for the given device property or sets the given default if an
+ * error occurs while obtaining it.
+ *
+ * @returns uint8_t value of the given property.
+ * @param uBase The base of the number in the sysfs property.
+ * @param bDef The default to set on error.
+ * @param pszFormat The format string for the property.
+ * @param ... Arguments for the format string.
+ */
+static uint8_t usbsysfsReadDevicePropertyU8Def(unsigned uBase, uint8_t bDef, const char *pszFormat, ...)
+{
+ int64_t i64Tmp = 0;
+
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = RTLinuxSysFsReadIntFileV(uBase, &i64Tmp, pszFormat, va);
+ va_end(va);
+ if (RT_SUCCESS(rc))
+ return (uint8_t)i64Tmp;
+ else
+ return bDef;
+}
+
+
+/**
+ * Returns the uint16_t value for the given device property or sets the given default if an
+ * error occurs while obtaining it.
+ *
+ * @returns uint16_t value of the given property.
+ * @param uBase The base of the number in the sysfs property.
+ * @param u16Def The default to set on error.
+ * @param pszFormat The format string for the property.
+ * @param ... Arguments for the format string.
+ */
+static uint16_t usbsysfsReadDevicePropertyU16Def(unsigned uBase, uint16_t u16Def, const char *pszFormat, ...)
+{
+ int64_t i64Tmp = 0;
+
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = RTLinuxSysFsReadIntFileV(uBase, &i64Tmp, pszFormat, va);
+ va_end(va);
+ if (RT_SUCCESS(rc))
+ return (uint16_t)i64Tmp;
+ else
+ return u16Def;
+}
+
+
+static void usbsysfsFillInDevice(USBDEVICE *pDev, USBDeviceInfo *pInfo)
+{
+ int rc;
+ const char *pszSysfsPath = pInfo->mSysfsPath;
+
+ /* Fill in the simple fields */
+ pDev->enmState = USBDEVICESTATE_UNUSED;
+ pDev->bBus = (uint8_t)usbsysfsGetBusFromPath(pszSysfsPath);
+ pDev->bDeviceClass = usbsysfsReadDevicePropertyU8Def(16, 0, "%s/bDeviceClass", pszSysfsPath);
+ pDev->bDeviceSubClass = usbsysfsReadDevicePropertyU8Def(16, 0, "%s/bDeviceSubClass", pszSysfsPath);
+ pDev->bDeviceProtocol = usbsysfsReadDevicePropertyU8Def(16, 0, "%s/bDeviceProtocol", pszSysfsPath);
+ pDev->bNumConfigurations = usbsysfsReadDevicePropertyU8Def(10, 0, "%s/bNumConfigurations", pszSysfsPath);
+ pDev->idVendor = usbsysfsReadDevicePropertyU16Def(16, 0, "%s/idVendor", pszSysfsPath);
+ pDev->idProduct = usbsysfsReadDevicePropertyU16Def(16, 0, "%s/idProduct", pszSysfsPath);
+ pDev->bDevNum = usbsysfsReadDevicePropertyU8Def(10, 0, "%s/devnum", pszSysfsPath);
+
+ /* Now deal with the non-numeric bits. */
+ char szBuf[1024]; /* Should be larger than anything a sane device
+ * will need, and insane devices can be unsupported
+ * until further notice. */
+ size_t cchRead;
+
+ /* For simplicity, we just do strcmps on the next one. */
+ rc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/speed", pszSysfsPath);
+ if (RT_FAILURE(rc) || cchRead == sizeof(szBuf))
+ pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
+ else
+ pDev->enmSpeed = !strcmp(szBuf, "1.5") ? USBDEVICESPEED_LOW
+ : !strcmp(szBuf, "12") ? USBDEVICESPEED_FULL
+ : !strcmp(szBuf, "480") ? USBDEVICESPEED_HIGH
+ : !strcmp(szBuf, "5000") ? USBDEVICESPEED_SUPER
+ : USBDEVICESPEED_UNKNOWN;
+
+ rc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/version", pszSysfsPath);
+ if (RT_FAILURE(rc) || cchRead == sizeof(szBuf))
+ pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
+ else
+ {
+ rc = usbsysfsConvertStrToBCD(szBuf, &pDev->bcdUSB);
+ if (RT_FAILURE(rc))
+ {
+ pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
+ pDev->bcdUSB = UINT16_MAX;
+ }
+ }
+
+ rc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/bcdDevice", pszSysfsPath);
+ if (RT_FAILURE(rc) || cchRead == sizeof(szBuf))
+ pDev->bcdDevice = UINT16_MAX;
+ else
+ {
+ rc = usbsysfsConvertStrToBCD(szBuf, &pDev->bcdDevice);
+ if (RT_FAILURE(rc))
+ pDev->bcdDevice = UINT16_MAX;
+ }
+
+ /* Now do things that need string duplication */
+ rc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/product", pszSysfsPath);
+ if (RT_SUCCESS(rc) && cchRead < sizeof(szBuf))
+ {
+ USBLibPurgeEncoding(szBuf);
+ pDev->pszProduct = RTStrDup(szBuf);
+ }
+
+ rc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/serial", pszSysfsPath);
+ if (RT_SUCCESS(rc) && cchRead < sizeof(szBuf))
+ {
+ USBLibPurgeEncoding(szBuf);
+ pDev->pszSerialNumber = RTStrDup(szBuf);
+ pDev->u64SerialHash = USBLibHashSerial(szBuf);
+ }
+
+ rc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/manufacturer", pszSysfsPath);
+ if (RT_SUCCESS(rc) && cchRead < sizeof(szBuf))
+ {
+ USBLibPurgeEncoding(szBuf);
+ pDev->pszManufacturer = RTStrDup(szBuf);
+ }
+
+ /* Work out the port number */
+ if (RT_FAILURE(usbsysfsGetPortFromStr(pszSysfsPath, &pDev->bPort)))
+ pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
+
+ /* Check the interfaces to see if we can support the device. */
+ char **ppszIf;
+ VEC_FOR_EACH(&pInfo->mvecpszInterfaces, char *, ppszIf)
+ {
+ rc = RTLinuxSysFsGetLinkDest(szBuf, sizeof(szBuf), NULL, "%s/driver", *ppszIf);
+ if (RT_SUCCESS(rc) && pDev->enmState != USBDEVICESTATE_UNSUPPORTED)
+ pDev->enmState = (strcmp(szBuf, "hub") == 0)
+ ? USBDEVICESTATE_UNSUPPORTED
+ : USBDEVICESTATE_USED_BY_HOST_CAPTURABLE;
+ if (usbsysfsReadDevicePropertyU8Def(16, 9 /* bDev */, "%s/bInterfaceClass", *ppszIf) == 9 /* hub */)
+ pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
+ }
+
+ /* We use a double slash as a separator in the pszAddress field. This is
+ * alright as the two paths can't contain a slash due to the way we build
+ * them. */
+ char *pszAddress = NULL;
+ RTStrAPrintf(&pszAddress, "sysfs:%s//device:%s", pszSysfsPath, pInfo->mDevice);
+ pDev->pszAddress = pszAddress;
+ pDev->pszBackend = RTStrDup("host");
+
+ /* Work out from the data collected whether we can support this device. */
+ pDev->enmState = usbDeterminState(pDev);
+ usbLogDevice(pDev);
+}
+
+
+/**
+ * USBProxyService::getDevices() implementation for sysfs.
+ */
+static PUSBDEVICE usbsysfsGetDevices(const char *pszDevicesRoot, bool fUnsupportedDevicesToo)
+{
+ /* Add each of the devices found to the chain. */
+ PUSBDEVICE pFirst = NULL;
+ PUSBDEVICE pLast = NULL;
+ VECTOR_OBJ(USBDeviceInfo) vecDevInfo;
+ USBDeviceInfo *pInfo;
+ int rc;
+
+ VEC_INIT_OBJ(&vecDevInfo, USBDeviceInfo, usbsysfsCleanupDevInfo);
+ rc = usbsysfsEnumerateHostDevices(pszDevicesRoot, &vecDevInfo);
+ if (RT_FAILURE(rc))
+ return NULL;
+ VEC_FOR_EACH(&vecDevInfo, USBDeviceInfo, pInfo)
+ {
+ USBDEVICE *pDev = (USBDEVICE *)RTMemAllocZ(sizeof(USBDEVICE));
+ if (!pDev)
+ rc = VERR_NO_MEMORY;
+ if (RT_SUCCESS(rc))
+ usbsysfsFillInDevice(pDev, pInfo);
+ if ( RT_SUCCESS(rc)
+ && ( pDev->enmState != USBDEVICESTATE_UNSUPPORTED
+ || fUnsupportedDevicesToo)
+ && pDev->pszAddress != NULL
+ )
+ {
+ if (pLast != NULL)
+ {
+ pLast->pNext = pDev;
+ pLast = pLast->pNext;
+ }
+ else
+ pFirst = pLast = pDev;
+ }
+ else
+ deviceFree(pDev);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (RT_FAILURE(rc))
+ deviceListFree(&pFirst);
+
+ VEC_CLEANUP_OBJ(&vecDevInfo);
+ return pFirst;
+}
+
+#endif /* VBOX_USB_WITH_SYSFS */
+#ifdef UNIT_TEST
+
+/* Set up mock functions for USBProxyLinuxCheckDeviceRoot - here dlsym and close
+ * for the inotify presence check. */
+static int testInotifyInitGood(void) { return 0; }
+static int testInotifyInitBad(void) { return -1; }
+static bool s_fHaveInotifyLibC = true;
+static bool s_fHaveInotifyKernel = true;
+
+static void *testDLSym(void *handle, const char *symbol)
+{
+ RT_NOREF(handle, symbol);
+ Assert(handle == RTLD_DEFAULT);
+ Assert(!RTStrCmp(symbol, "inotify_init"));
+ if (!s_fHaveInotifyLibC)
+ return NULL;
+ if (s_fHaveInotifyKernel)
+ return (void *)(uintptr_t)testInotifyInitGood;
+ return (void *)(uintptr_t)testInotifyInitBad;
+}
+
+void TestUSBSetInotifyAvailable(bool fHaveInotifyLibC, bool fHaveInotifyKernel)
+{
+ s_fHaveInotifyLibC = fHaveInotifyLibC;
+ s_fHaveInotifyKernel = fHaveInotifyKernel;
+}
+# define dlsym testDLSym
+# define close(a) do {} while (0)
+
+#endif /* UNIT_TEST */
+
+/**
+ * Is inotify available and working on this system?
+ *
+ * This is a requirement for using USB with sysfs
+ */
+static bool usbsysfsInotifyAvailable(void)
+{
+ int (*inotify_init)(void);
+
+ *(void **)(&inotify_init) = dlsym(RTLD_DEFAULT, "inotify_init");
+ if (!inotify_init)
+ return false;
+ int fd = inotify_init();
+ if (fd == -1)
+ return false;
+ close(fd);
+ return true;
+}
+
+#ifdef UNIT_TEST
+
+# undef dlsym
+# undef close
+
+/** Unit test list of usbfs addresses of connected devices. */
+static const char **g_papszUsbfsDeviceAddresses = NULL;
+
+static PUSBDEVICE testGetUsbfsDevices(const char *pszUsbfsRoot, bool fUnsupportedDevicesToo)
+{
+ RT_NOREF(pszUsbfsRoot, fUnsupportedDevicesToo);
+ const char **psz;
+ PUSBDEVICE pList = NULL, pTail = NULL;
+ for (psz = g_papszUsbfsDeviceAddresses; psz && *psz; ++psz)
+ {
+ PUSBDEVICE pNext = (PUSBDEVICE)RTMemAllocZ(sizeof(USBDEVICE));
+ if (pNext)
+ pNext->pszAddress = RTStrDup(*psz);
+ if (!pNext || !pNext->pszAddress)
+ {
+ if (pNext)
+ RTMemFree(pNext);
+ deviceListFree(&pList);
+ return NULL;
+ }
+ if (pTail)
+ pTail->pNext = pNext;
+ else
+ pList = pNext;
+ pTail = pNext;
+ }
+ return pList;
+}
+# define usbfsGetDevices testGetUsbfsDevices
+
+/**
+ * Specify the list of devices that will appear to be available through
+ * usbfs during unit testing (of USBProxyLinuxGetDevices)
+ * @param pacszDeviceAddresses NULL terminated array of usbfs device addresses
+ */
+void TestUSBSetAvailableUsbfsDevices(const char **papszDeviceAddresses)
+{
+ g_papszUsbfsDeviceAddresses = papszDeviceAddresses;
+}
+
+/** Unit test list of files reported as accessible by access(3). We only do
+ * accessible or not accessible. */
+static const char **g_papszAccessibleFiles = NULL;
+
+static int testAccess(const char *pszPath, int mode)
+{
+ RT_NOREF(mode);
+ const char **psz;
+ for (psz = g_papszAccessibleFiles; psz && *psz; ++psz)
+ if (!RTStrCmp(pszPath, *psz))
+ return 0;
+ return -1;
+}
+# define access testAccess
+
+
+/**
+ * Specify the list of files that access will report as accessible (at present
+ * we only do accessible or not accessible) during unit testing (of
+ * USBProxyLinuxGetDevices)
+ * @param papszAccessibleFiles NULL terminated array of file paths to be
+ * reported accessible
+ */
+void TestUSBSetAccessibleFiles(const char **papszAccessibleFiles)
+{
+ g_papszAccessibleFiles = papszAccessibleFiles;
+}
+
+
+/** The path we pretend the usbfs root is located at, or NULL. */
+const char *s_pszTestUsbfsRoot;
+/** Should usbfs be accessible to the current user? */
+bool s_fTestUsbfsAccessible;
+/** The path we pretend the device node tree root is located at, or NULL. */
+const char *s_pszTestDevicesRoot;
+/** Should the device node tree be accessible to the current user? */
+bool s_fTestDevicesAccessible;
+/** The result of the usbfs/inotify-specific init */
+int s_rcTestMethodInitResult;
+/** The value of the VBOX_USB environment variable. */
+const char *s_pszTestEnvUsb;
+/** The value of the VBOX_USB_ROOT environment variable. */
+const char *s_pszTestEnvUsbRoot;
+
+
+/** Select which access methods will be available to the @a init method
+ * during unit testing, and (hack!) what return code it will see from
+ * the access method-specific initialisation. */
+void TestUSBSetupInit(const char *pszUsbfsRoot, bool fUsbfsAccessible,
+ const char *pszDevicesRoot, bool fDevicesAccessible,
+ int rcMethodInitResult)
+{
+ s_pszTestUsbfsRoot = pszUsbfsRoot;
+ s_fTestUsbfsAccessible = fUsbfsAccessible;
+ s_pszTestDevicesRoot = pszDevicesRoot;
+ s_fTestDevicesAccessible = fDevicesAccessible;
+ s_rcTestMethodInitResult = rcMethodInitResult;
+}
+
+
+/** Specify the environment that the @a init method will see during unit
+ * testing. */
+void TestUSBSetEnv(const char *pszEnvUsb, const char *pszEnvUsbRoot)
+{
+ s_pszTestEnvUsb = pszEnvUsb;
+ s_pszTestEnvUsbRoot = pszEnvUsbRoot;
+}
+
+/* For testing we redefine anything that accesses the outside world to
+ * return test values. */
+# define RTEnvGet(a) \
+ ( !RTStrCmp(a, "VBOX_USB") ? s_pszTestEnvUsb \
+ : !RTStrCmp(a, "VBOX_USB_ROOT") ? s_pszTestEnvUsbRoot \
+ : NULL)
+# define USBProxyLinuxCheckDeviceRoot(pszPath, fUseNodes) \
+ ( ((fUseNodes) && s_fTestDevicesAccessible \
+ && !RTStrCmp(pszPath, s_pszTestDevicesRoot)) \
+ || (!(fUseNodes) && s_fTestUsbfsAccessible \
+ && !RTStrCmp(pszPath, s_pszTestUsbfsRoot)))
+# define RTDirExists(pszDir) \
+ ( (pszDir) \
+ && ( !RTStrCmp(pszDir, s_pszTestDevicesRoot) \
+ || !RTStrCmp(pszDir, s_pszTestUsbfsRoot)))
+# define RTFileExists(pszFile) \
+ ( (pszFile) \
+ && s_pszTestUsbfsRoot \
+ && !RTStrNCmp(pszFile, s_pszTestUsbfsRoot, strlen(s_pszTestUsbfsRoot)) \
+ && !RTStrCmp(pszFile + strlen(s_pszTestUsbfsRoot), "/devices"))
+
+#endif /* UNIT_TEST */
+
+/**
+ * Use USBFS-like or sysfs/device node-like access method?
+ *
+ * Selects the access method that will be used to access USB devices based on
+ * what is available on the host and what if anything the user has specified
+ * in the environment.
+ *
+ * @returns iprt status value
+ * @param pfUsingUsbfsDevices on success this will be set to true if
+ * the prefered access method is USBFS-like and to
+ * false if it is sysfs/device node-like
+ * @param ppszDevicesRoot on success the root of the tree of USBFS-like
+ * device nodes will be stored here
+ */
+int USBProxyLinuxChooseMethod(bool *pfUsingUsbfsDevices, const char **ppszDevicesRoot)
+{
+ /*
+ * We have two methods available for getting host USB device data - using
+ * USBFS and using sysfs. The default choice is sysfs; if that is not
+ * available we fall back to USBFS.
+ * In the event of both failing, an appropriate error will be returned.
+ * The user may also specify a method and root using the VBOX_USB and
+ * VBOX_USB_ROOT environment variables. In this case we don't check
+ * the root they provide for validity.
+ */
+ bool fUsbfsChosen = false;
+ bool fSysfsChosen = false;
+ const char *pszUsbFromEnv = RTEnvGet("VBOX_USB");
+ const char *pszUsbRoot = NULL;
+ if (pszUsbFromEnv)
+ {
+ bool fValidVBoxUSB = true;
+
+ pszUsbRoot = RTEnvGet("VBOX_USB_ROOT");
+ if (!RTStrICmp(pszUsbFromEnv, "USBFS"))
+ {
+ LogRel(("Default USB access method set to \"usbfs\" from environment\n"));
+ fUsbfsChosen = true;
+ }
+ else if (!RTStrICmp(pszUsbFromEnv, "SYSFS"))
+ {
+ LogRel(("Default USB method set to \"sysfs\" from environment\n"));
+ fSysfsChosen = true;
+ }
+ else
+ {
+ LogRel(("Invalid VBOX_USB environment variable setting \"%s\"\n", pszUsbFromEnv));
+ fValidVBoxUSB = false;
+ pszUsbFromEnv = NULL;
+ }
+ if (!fValidVBoxUSB && pszUsbRoot)
+ pszUsbRoot = NULL;
+ }
+ if (!pszUsbRoot)
+ {
+ if ( !fUsbfsChosen
+ && USBProxyLinuxCheckDeviceRoot("/dev/vboxusb", true))
+ {
+ fSysfsChosen = true;
+ pszUsbRoot = "/dev/vboxusb";
+ }
+ else if ( !fSysfsChosen
+ && USBProxyLinuxCheckDeviceRoot("/proc/bus/usb", false))
+ {
+ fUsbfsChosen = true;
+ pszUsbRoot = "/proc/bus/usb";
+ }
+ }
+ else if (!USBProxyLinuxCheckDeviceRoot(pszUsbRoot, fSysfsChosen))
+ pszUsbRoot = NULL;
+ if (pszUsbRoot)
+ {
+ *pfUsingUsbfsDevices = fUsbfsChosen;
+ *ppszDevicesRoot = pszUsbRoot;
+ return VINF_SUCCESS;
+ }
+ /* else */
+ return pszUsbFromEnv ? VERR_NOT_FOUND
+ : RTDirExists("/dev/vboxusb") ? VERR_VUSB_USB_DEVICE_PERMISSION
+ : RTFileExists("/proc/bus/usb/devices") ? VERR_VUSB_USBFS_PERMISSION
+ : VERR_NOT_FOUND;
+}
+
+#ifdef UNIT_TEST
+# undef RTEnvGet
+# undef USBProxyLinuxCheckDeviceRoot
+# undef RTDirExists
+# undef RTFileExists
+#endif
+
+/**
+ * Check whether a USB device tree root is usable.
+ *
+ * @param pszRoot the path to the root of the device tree
+ * @param fIsDeviceNodes whether this is a device node (or usbfs) tree
+ * @note returns a pointer into a static array so it will stay valid
+ */
+bool USBProxyLinuxCheckDeviceRoot(const char *pszRoot, bool fIsDeviceNodes)
+{
+ bool fOK = false;
+ if (!fIsDeviceNodes) /* usbfs */
+ {
+#ifdef VBOX_USB_WITH_USBFS
+ if (!access(pszRoot, R_OK | X_OK))
+ {
+ fOK = true;
+ PUSBDEVICE pDevices = usbfsGetDevices(pszRoot, true);
+ if (pDevices)
+ {
+ PUSBDEVICE pDevice;
+ for (pDevice = pDevices; pDevice && fOK; pDevice = pDevice->pNext)
+ if (access(pDevice->pszAddress, R_OK | W_OK))
+ fOK = false;
+ deviceListFree(&pDevices);
+ }
+ }
+#endif
+ }
+#ifdef VBOX_USB_WITH_SYSFS
+ /* device nodes */
+ else if (usbsysfsInotifyAvailable() && !access(pszRoot, R_OK | X_OK))
+ fOK = true;
+#endif
+ return fOK;
+}
+
+#ifdef UNIT_TEST
+# undef usbfsGetDevices
+# undef access
+#endif
+
+/**
+ * Get the list of USB devices supported by the system.
+ *
+ * Result should be freed using #deviceFree or something equivalent.
+ *
+ * @param pszDevicesRoot the path to the root of the device tree
+ * @param fUseSysfs whether to use sysfs (or usbfs) for enumeration
+ */
+PUSBDEVICE USBProxyLinuxGetDevices(const char *pszDevicesRoot, bool fUseSysfs)
+{
+ if (!fUseSysfs)
+ {
+#ifdef VBOX_USB_WITH_USBFS
+ return usbfsGetDevices(pszDevicesRoot, false);
+#else
+ return NULL;
+#endif
+ }
+
+#ifdef VBOX_USB_WITH_SYSFS
+ return usbsysfsGetDevices(pszDevicesRoot, false);
+#else
+ return NULL;
+#endif
+}
+
diff --git a/src/VBox/Main/src-server/linux/USBProxyBackendLinux.cpp b/src/VBox/Main/src-server/linux/USBProxyBackendLinux.cpp
new file mode 100644
index 00000000..0347f438
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/USBProxyBackendLinux.cpp
@@ -0,0 +1,399 @@
+/* $Id: USBProxyBackendLinux.cpp $ */
+/** @file
+ * VirtualBox USB Proxy Service, Linux Specialization.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_USBPROXYBACKEND
+#include "USBProxyService.h"
+#include "USBGetDevices.h"
+#include "LoggingNew.h"
+
+#include <VBox/usb.h>
+#include <VBox/usblib.h>
+#include <iprt/errcore.h>
+
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/pipe.h>
+#include <iprt/stream.h>
+#include <iprt/linux/sysfs.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/statfs.h>
+#include <sys/poll.h>
+#ifdef VBOX_WITH_LINUX_COMPILER_H
+# include <linux/compiler.h>
+#endif
+#include <linux/usbdevice_fs.h>
+
+
+/**
+ * Initialize data members.
+ */
+USBProxyBackendLinux::USBProxyBackendLinux()
+ : USBProxyBackend(), mhFile(NIL_RTFILE), mhWakeupPipeR(NIL_RTPIPE), mhWakeupPipeW(NIL_RTPIPE), mpWaiter(NULL)
+{
+ LogFlowThisFunc(("\n"));
+}
+
+
+/**
+ * Stop all service threads and free the device chain.
+ */
+USBProxyBackendLinux::~USBProxyBackendLinux()
+{
+ LogFlowThisFunc(("\n"));
+}
+
+/**
+ * Initializes the object (called right after construction).
+ *
+ * @returns VBox status code.
+ */
+int USBProxyBackendLinux::init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings)
+{
+ USBProxyBackend::init(pUsbProxyService, strId, strAddress, fLoadingSettings);
+
+ unconst(m_strBackend) = Utf8Str("host");
+
+ const char *pcszDevicesRoot;
+ int rc = USBProxyLinuxChooseMethod(&mUsingUsbfsDevices, &pcszDevicesRoot);
+ if (RT_SUCCESS(rc))
+ {
+ mDevicesRoot = pcszDevicesRoot;
+ rc = mUsingUsbfsDevices ? initUsbfs() : initSysfs();
+ /* For the day when we have VBoxSVC release logging... */
+ LogRel((RT_SUCCESS(rc) ? "Successfully initialised host USB using %s\n"
+ : "Failed to initialise host USB using %s\n",
+ mUsingUsbfsDevices ? "USBFS" : "sysfs"));
+ }
+
+ return rc;
+}
+
+void USBProxyBackendLinux::uninit()
+{
+ /*
+ * Stop the service.
+ */
+ if (isActive())
+ stop();
+
+ /*
+ * Free resources.
+ */
+ doUsbfsCleanupAsNeeded();
+#ifdef VBOX_USB_WITH_SYSFS
+ if (mpWaiter)
+ delete mpWaiter;
+#endif
+
+ USBProxyBackend::uninit();
+}
+
+
+/**
+ * Initialization routine for the usbfs based operation.
+ *
+ * @returns iprt status code.
+ */
+int USBProxyBackendLinux::initUsbfs(void)
+{
+ Assert(mUsingUsbfsDevices);
+
+ /*
+ * Open the devices file.
+ */
+ int rc;
+ char *pszDevices = RTPathJoinA(mDevicesRoot.c_str(), "devices");
+ if (pszDevices)
+ {
+ rc = RTFileOpen(&mhFile, pszDevices, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPipeCreate(&mhWakeupPipeR, &mhWakeupPipeW, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Start the poller thread.
+ */
+ rc = start();
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pszDevices);
+ LogFlowThisFunc(("returns successfully\n"));
+ return VINF_SUCCESS;
+ }
+
+ RTPipeClose(mhWakeupPipeR);
+ RTPipeClose(mhWakeupPipeW);
+ mhWakeupPipeW = mhWakeupPipeR = NIL_RTPIPE;
+ }
+ else
+ Log(("USBProxyBackendLinux::USBProxyBackendLinux: RTFilePipe failed with rc=%Rrc\n", rc));
+ RTFileClose(mhFile);
+ }
+
+ RTStrFree(pszDevices);
+ }
+ else
+ {
+ rc = VERR_NO_MEMORY;
+ Log(("USBProxyBackendLinux::USBProxyBackendLinux: out of memory!\n"));
+ }
+
+ LogFlowThisFunc(("returns failure!!! (rc=%Rrc)\n", rc));
+ return rc;
+}
+
+
+/**
+ * Initialization routine for the sysfs based operation.
+ *
+ * @returns iprt status code
+ */
+int USBProxyBackendLinux::initSysfs(void)
+{
+ Assert(!mUsingUsbfsDevices);
+
+#ifdef VBOX_USB_WITH_SYSFS
+ try
+ {
+ mpWaiter = new VBoxMainHotplugWaiter(mDevicesRoot.c_str());
+ }
+ catch(std::bad_alloc &e)
+ {
+ return VERR_NO_MEMORY;
+ }
+ int rc = mpWaiter->getStatus();
+ if (RT_SUCCESS(rc) || rc == VERR_TIMEOUT || rc == VERR_TRY_AGAIN)
+ rc = start();
+ else if (rc == VERR_NOT_SUPPORTED)
+ /* This can legitimately happen if hal or DBus are not running, but of
+ * course we can't start in this case. */
+ rc = VINF_SUCCESS;
+ return rc;
+
+#else /* !VBOX_USB_WITH_SYSFS */
+ return VERR_NOT_IMPLEMENTED;
+#endif /* !VBOX_USB_WITH_SYSFS */
+}
+
+
+/**
+ * If any Usbfs-related resources are currently allocated, then free them
+ * and mark them as freed.
+ */
+void USBProxyBackendLinux::doUsbfsCleanupAsNeeded()
+{
+ /*
+ * Free resources.
+ */
+ if (mhFile != NIL_RTFILE)
+ RTFileClose(mhFile);
+ mhFile = NIL_RTFILE;
+
+ if (mhWakeupPipeR != NIL_RTPIPE)
+ RTPipeClose(mhWakeupPipeR);
+ if (mhWakeupPipeW != NIL_RTPIPE)
+ RTPipeClose(mhWakeupPipeW);
+ mhWakeupPipeW = mhWakeupPipeR = NIL_RTPIPE;
+}
+
+
+int USBProxyBackendLinux::captureDevice(HostUSBDevice *aDevice)
+{
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ /*
+ * Don't think we need to do anything when the device is held... fake it.
+ */
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_Capturing);
+ devLock.release();
+ interruptWait();
+
+ return VINF_SUCCESS;
+}
+
+
+int USBProxyBackendLinux::releaseDevice(HostUSBDevice *aDevice)
+{
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ /*
+ * We're not really holding it atm., just fake it.
+ */
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_ReleasingToHost);
+ devLock.release();
+ interruptWait();
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * A device was added, we need to adjust mUdevPolls.
+ */
+void USBProxyBackendLinux::deviceAdded(ComObjPtr<HostUSBDevice> &aDevice, PUSBDEVICE pDev)
+{
+ AssertReturnVoid(aDevice);
+ AssertReturnVoid(!aDevice->isWriteLockOnCurrentThread());
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ if (pDev->enmState == USBDEVICESTATE_USED_BY_HOST)
+ {
+ LogRel(("USBProxyBackendLinux: Device %04x:%04x (%s) isn't accessible. giving udev a few seconds to fix this...\n",
+ pDev->idVendor, pDev->idProduct, pDev->pszAddress));
+ mUdevPolls = 10; /* (10 * 500ms = 5s) */
+ }
+
+ devLock.release();
+}
+
+
+bool USBProxyBackendLinux::isFakeUpdateRequired()
+{
+ return true;
+}
+
+int USBProxyBackendLinux::wait(RTMSINTERVAL aMillies)
+{
+ int rc;
+ if (mUsingUsbfsDevices)
+ rc = waitUsbfs(aMillies);
+ else
+ rc = waitSysfs(aMillies);
+ return rc;
+}
+
+
+/** String written to the wakeup pipe. */
+#define WAKE_UP_STRING "WakeUp!"
+/** Length of the string written. */
+#define WAKE_UP_STRING_LEN ( sizeof(WAKE_UP_STRING) - 1 )
+
+int USBProxyBackendLinux::waitUsbfs(RTMSINTERVAL aMillies)
+{
+ struct pollfd PollFds[2];
+
+ /* Cap the wait interval if we're polling for udevd changing device permissions. */
+ if (aMillies > 500 && mUdevPolls > 0)
+ {
+ mUdevPolls--;
+ aMillies = 500;
+ }
+
+ RT_ZERO(PollFds);
+ PollFds[0].fd = (int)RTFileToNative(mhFile);
+ PollFds[0].events = POLLIN;
+ PollFds[1].fd = (int)RTPipeToNative(mhWakeupPipeR);
+ PollFds[1].events = POLLIN | POLLERR | POLLHUP;
+
+ int rc = poll(&PollFds[0], 2, aMillies);
+ if (rc == 0)
+ return VERR_TIMEOUT;
+ if (rc > 0)
+ {
+ /* drain the pipe */
+ if (PollFds[1].revents & POLLIN)
+ {
+ char szBuf[WAKE_UP_STRING_LEN];
+ rc = RTPipeReadBlocking(mhWakeupPipeR, szBuf, sizeof(szBuf), NULL);
+ AssertRC(rc);
+ }
+ return VINF_SUCCESS;
+ }
+ return RTErrConvertFromErrno(errno);
+}
+
+
+int USBProxyBackendLinux::waitSysfs(RTMSINTERVAL aMillies)
+{
+#ifdef VBOX_USB_WITH_SYSFS
+ int rc = mpWaiter->Wait(aMillies);
+ if (rc == VERR_TRY_AGAIN)
+ {
+ RTThreadYield();
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+#else /* !VBOX_USB_WITH_SYSFS */
+ return USBProxyService::wait(aMillies);
+#endif /* !VBOX_USB_WITH_SYSFS */
+}
+
+
+int USBProxyBackendLinux::interruptWait(void)
+{
+ AssertReturn(!isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+#ifdef VBOX_USB_WITH_SYSFS
+ LogFlowFunc(("mUsingUsbfsDevices=%d\n", mUsingUsbfsDevices));
+ if (!mUsingUsbfsDevices)
+ {
+ mpWaiter->Interrupt();
+ LogFlowFunc(("Returning VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+ }
+#endif /* VBOX_USB_WITH_SYSFS */
+ int rc = RTPipeWriteBlocking(mhWakeupPipeW, WAKE_UP_STRING, WAKE_UP_STRING_LEN, NULL);
+ if (RT_SUCCESS(rc))
+ RTPipeFlush(mhWakeupPipeW);
+ LogFlowFunc(("returning %Rrc\n", rc));
+ return rc;
+}
+
+
+PUSBDEVICE USBProxyBackendLinux::getDevices(void)
+{
+ return USBProxyLinuxGetDevices(mDevicesRoot.c_str(), !mUsingUsbfsDevices);
+}
diff --git a/src/VBox/Main/src-server/linux/vbox-libhal.cpp b/src/VBox/Main/src-server/linux/vbox-libhal.cpp
new file mode 100644
index 00000000..6bbe076a
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/vbox-libhal.cpp
@@ -0,0 +1,105 @@
+/* $Id: vbox-libhal.cpp $ */
+/** @file
+ *
+ * Module to dynamically load libhal and libdbus and load all symbols
+ * which are needed by VirtualBox.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "vbox-libhal.h"
+
+#include <iprt/errcore.h>
+#include <iprt/ldr.h>
+
+/**
+ * Whether we have tried to load libdbus and libhal yet. This flag should only be set
+ * to "true" after we have either loaded both libraries and all symbols which we need,
+ * or failed to load something and unloaded and set back to zero pLibDBus and pLibHal.
+ */
+static bool gCheckedForLibHal = false;
+/**
+ * Pointer to the libhal shared object. This should only be set once all needed libraries
+ * and symbols have been successfully loaded.
+ */
+static RTLDRMOD ghLibHal = 0;
+
+/** The following are the symbols which we need from libdbus and libhal. */
+void (*gDBusErrorInit)(DBusError *);
+DBusConnection *(*gDBusBusGet)(DBusBusType, DBusError *);
+void (*gDBusErrorFree)(DBusError *);
+void (*gDBusConnectionUnref)(DBusConnection *);
+LibHalContext *(*gLibHalCtxNew)(void);
+dbus_bool_t (*gLibHalCtxSetDBusConnection)(LibHalContext *, DBusConnection *);
+dbus_bool_t (*gLibHalCtxInit)(LibHalContext *, DBusError *);
+char **(*gLibHalFindDeviceStringMatch)(LibHalContext *, const char *, const char *, int *,
+ DBusError *);
+char *(*gLibHalDeviceGetPropertyString)(LibHalContext *, const char *, const char *, DBusError *);
+void (*gLibHalFreeString)(char *);
+void (*gLibHalFreeStringArray)(char **);
+dbus_bool_t (*gLibHalCtxShutdown)(LibHalContext *, DBusError *);
+dbus_bool_t (*gLibHalCtxFree)(LibHalContext *);
+
+bool gLibHalCheckPresence(void)
+{
+ RTLDRMOD hLibHal;
+
+ if (ghLibHal != 0 && gCheckedForLibHal == true)
+ return true;
+ if (gCheckedForLibHal == true)
+ return false;
+ if (!RT_SUCCESS(RTLdrLoad(LIB_HAL, &hLibHal)))
+ {
+ return false;
+ }
+ if ( RT_SUCCESS(RTLdrGetSymbol(hLibHal, "dbus_error_init", (void **) &gDBusErrorInit))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "dbus_bus_get", (void **) &gDBusBusGet))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "dbus_error_free", (void **) &gDBusErrorFree))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "dbus_connection_unref",
+ (void **) &gDBusConnectionUnref))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_ctx_new", (void **) &gLibHalCtxNew))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_ctx_set_dbus_connection",
+ (void **) &gLibHalCtxSetDBusConnection))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_ctx_init", (void **) &gLibHalCtxInit))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_manager_find_device_string_match",
+ (void **) &gLibHalFindDeviceStringMatch))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_device_get_property_string",
+ (void **) &gLibHalDeviceGetPropertyString))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_free_string", (void **) &gLibHalFreeString))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_free_string_array",
+ (void **) &gLibHalFreeStringArray))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_ctx_shutdown", (void **) &gLibHalCtxShutdown))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_ctx_free", (void **) &gLibHalCtxFree))
+ )
+ {
+ ghLibHal = hLibHal;
+ gCheckedForLibHal = true;
+ return true;
+ }
+ else
+ {
+ RTLdrClose(hLibHal);
+ gCheckedForLibHal = true;
+ return false;
+ }
+}
diff --git a/src/VBox/Main/src-server/os2/Makefile.kup b/src/VBox/Main/src-server/os2/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-server/os2/Makefile.kup
diff --git a/src/VBox/Main/src-server/os2/NetIf-os2.cpp b/src/VBox/Main/src-server/os2/NetIf-os2.cpp
new file mode 100644
index 00000000..cc50893d
--- /dev/null
+++ b/src/VBox/Main/src-server/os2/NetIf-os2.cpp
@@ -0,0 +1,66 @@
+/* $Id: NetIf-os2.cpp $ */
+/** @file
+ * Main - NetIfList, OS/2 implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN
+
+#include <iprt/errcore.h>
+#include <list>
+
+#include "HostNetworkInterfaceImpl.h"
+#include "netif.h"
+
+int NetIfList(std::list <ComObjPtr<HostNetworkInterface> > &list)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int NetIfEnableStaticIpConfig(VirtualBox *pVBox, HostNetworkInterface * pIf, ULONG ip, ULONG mask)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int NetIfEnableStaticIpConfigV6(VirtualBox *pVBox, HostNetworkInterface * pIf, const Utf8Str &aIPV6Address, ULONG aIPV6MaskPrefixLength)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int NetIfEnableDynamicIpConfig(VirtualBox *pVBox, HostNetworkInterface * pIf)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+int NetIfDhcpRediscover(VirtualBox *pVBox, HostNetworkInterface * pIf)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
diff --git a/src/VBox/Main/src-server/os2/PerformanceOs2.cpp b/src/VBox/Main/src-server/os2/PerformanceOs2.cpp
new file mode 100644
index 00000000..b416e684
--- /dev/null
+++ b/src/VBox/Main/src-server/os2/PerformanceOs2.cpp
@@ -0,0 +1,75 @@
+/* $Id: PerformanceOs2.cpp $ */
+
+/** @file
+ *
+ * VBox OS/2-specific Performance Classes implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "Performance.h"
+
+namespace pm {
+
+class CollectorOS2 : public CollectorHAL
+{
+public:
+ virtual int getHostCpuLoad(ULONG *user, ULONG *kernel, ULONG *idle);
+ virtual int getHostCpuMHz(ULONG *mhz);
+ virtual int getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available);
+ virtual int getProcessCpuLoad(RTPROCESS process, ULONG *user, ULONG *kernel);
+ virtual int getProcessMemoryUsage(RTPROCESS process, ULONG *used);
+};
+
+
+CollectorHAL *createHAL()
+{
+ return new CollectorOS2();
+}
+
+int CollectorOS2::getHostCpuLoad(ULONG *user, ULONG *kernel, ULONG *idle)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorOS2::getHostCpuMHz(ULONG *mhz)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorOS2::getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorOS2::getProcessCpuLoad(RTPROCESS process, ULONG *user, ULONG *kernel)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorOS2::getProcessMemoryUsage(RTPROCESS process, ULONG *used)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+}
diff --git a/src/VBox/Main/src-server/os2/USBProxyBackendOs2.cpp b/src/VBox/Main/src-server/os2/USBProxyBackendOs2.cpp
new file mode 100644
index 00000000..6cd26531
--- /dev/null
+++ b/src/VBox/Main/src-server/os2/USBProxyBackendOs2.cpp
@@ -0,0 +1,291 @@
+/* $Id: USBProxyBackendOs2.cpp $ */
+/** @file
+ * VirtualBox USB Proxy Service, OS/2 Specialization.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_USBPROXYBACKEND
+#define INCL_BASE
+#define INCL_ERRORS
+#include "USBProxyBackend.h"
+#include "LoggingNew.h"
+
+#include <VBox/usb.h>
+#include <iprt/errcore.h>
+
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/errcore.h>
+
+
+/**
+ * Initialize data members.
+ */
+USBProxyBackendOs2::USBProxyBackendOs2(USBProxyService *aUsbProxyService, const com::Utf8Str &strId)
+ : USBProxyBackend(aUsbProxyService, strId), mhev(NULLHANDLE), mhmod(NULLHANDLE),
+ mpfnUsbRegisterChangeNotification(NULL), mpfnUsbDeregisterNotification(NULL),
+ mpfnUsbQueryNumberDevices(NULL), mpfnUsbQueryDeviceReport(NULL)
+{
+ LogFlowThisFunc(("aUsbProxyService=%p\n", aUsbProxyService));
+
+ /*
+ * Try initialize the usbcalls stuff.
+ */
+ int rc = DosCreateEventSem(NULL, &mhev, 0, FALSE);
+ rc = RTErrConvertFromOS2(rc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = DosLoadModule(NULL, 0, (PCSZ)"usbcalls", &mhmod);
+ rc = RTErrConvertFromOS2(rc);
+ if (RT_SUCCESS(rc))
+ {
+ if ( (rc = DosQueryProcAddr(mhmod, 0, (PCSZ)"UsbQueryNumberDevices", (PPFN)&mpfnUsbQueryNumberDevices)) == NO_ERROR
+ && (rc = DosQueryProcAddr(mhmod, 0, (PCSZ)"UsbQueryDeviceReport", (PPFN)&mpfnUsbQueryDeviceReport)) == NO_ERROR
+ && (rc = DosQueryProcAddr(mhmod, 0, (PCSZ)"UsbRegisterChangeNotification", (PPFN)&mpfnUsbRegisterChangeNotification)) == NO_ERROR
+ && (rc = DosQueryProcAddr(mhmod, 0, (PCSZ)"UsbDeregisterNotification", (PPFN)&mpfnUsbDeregisterNotification)) == NO_ERROR
+ )
+ {
+ rc = mpfnUsbRegisterChangeNotification(&mNotifyId, mhev, mhev);
+ if (!rc)
+ {
+ /*
+ * Start the poller thread.
+ */
+ rc = start();
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowThisFunc(("returns successfully - mNotifyId=%d\n", mNotifyId));
+ mLastError = VINF_SUCCESS;
+ return;
+ }
+ }
+
+ LogRel(("USBProxyBackendOs2: failed to register change notification, rc=%d\n", rc));
+ }
+ else
+ LogRel(("USBProxyBackendOs2: failed to load usbcalls\n"));
+
+ DosFreeModule(mhmod);
+ }
+ else
+ LogRel(("USBProxyBackendOs2: failed to load usbcalls, rc=%d\n", rc));
+ mhmod = NULLHANDLE;
+ }
+ else
+ mhev = NULLHANDLE;
+
+ mLastError = rc;
+ LogFlowThisFunc(("returns failure!!! (rc=%Rrc)\n", rc));
+}
+
+
+/**
+ * Stop all service threads and free the device chain.
+ */
+USBProxyBackendOs2::~USBProxyBackendOs2()
+{
+ LogFlowThisFunc(("\n"));
+
+ /*
+ * Stop the service.
+ */
+ if (isActive())
+ stop();
+
+ /*
+ * Free resources.
+ */
+ if (mhmod)
+ {
+ if (mpfnUsbDeregisterNotification)
+ mpfnUsbDeregisterNotification(mNotifyId);
+
+ mpfnUsbRegisterChangeNotification = NULL;
+ mpfnUsbDeregisterNotification = NULL;
+ mpfnUsbQueryNumberDevices = NULL;
+ mpfnUsbQueryDeviceReport = NULL;
+
+ DosFreeModule(mhmod);
+ mhmod = NULLHANDLE;
+ }
+}
+
+
+int USBProxyBackendOs2::captureDevice(HostUSBDevice *aDevice)
+{
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->getName().c_str()));
+
+ /*
+ * Don't think we need to do anything when the device is held... fake it.
+ */
+ Assert(aDevice->isStatePending());
+ devLock.release();
+ interruptWait();
+
+ return VINF_SUCCESS;
+}
+
+
+int USBProxyBackendOs2::releaseDevice(HostUSBDevice *aDevice)
+{
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->getName().c_str()));
+
+ /*
+ * We're not really holding it atm., just fake it.
+ */
+ Assert(aDevice->isStatePending());
+ devLock.release();
+ interruptWait();
+
+ return VINF_SUCCESS;
+}
+
+
+#if 0
+bool USBProxyBackendOs2::updateDeviceState(HostUSBDevice *aDevice, PUSBDEVICE aUSBDevice, bool *aRunFilters,
+ SessionMachine **aIgnoreMachine)
+{
+ AssertReturn(aDevice, false);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), false);
+ return updateDeviceStateFake(aDevice, aUSBDevice, aRunFilters, aIgnoreMachine);
+}
+#endif
+
+
+
+int USBProxyBackendOs2::wait(RTMSINTERVAL aMillies)
+{
+ int rc = DosWaitEventSem(mhev, aMillies);
+ return RTErrConvertFromOS2(rc);
+}
+
+
+int USBProxyBackendOs2::interruptWait(void)
+{
+ int rc = DosPostEventSem(mhev);
+ return rc == NO_ERROR || rc == ERROR_ALREADY_POSTED
+ ? VINF_SUCCESS
+ : RTErrConvertFromOS2(rc);
+}
+
+#include <stdio.h>
+
+PUSBDEVICE USBProxyBackendOs2::getDevices(void)
+{
+ /*
+ * Count the devices.
+ */
+ ULONG cDevices = 0;
+ int rc = mpfnUsbQueryNumberDevices((PULONG)&cDevices); /* Thanks to com/xpcom, PULONG and ULONG * aren't the same. */
+ if (rc)
+ return NULL;
+
+ /*
+ * Retrieve information about each device.
+ */
+ PUSBDEVICE pFirst = NULL;
+ PUSBDEVICE *ppNext = &pFirst;
+ for (ULONG i = 0; i < cDevices; i++)
+ {
+ /*
+ * Query the device and config descriptors.
+ */
+ uint8_t abBuf[1024];
+ ULONG cb = sizeof(abBuf);
+ rc = mpfnUsbQueryDeviceReport(i + 1, (PULONG)&cb, &abBuf[0]); /* see above (PULONG) */
+ if (rc)
+ continue;
+ PUSBDEVICEDESC pDevDesc = (PUSBDEVICEDESC)&abBuf[0];
+ if ( cb < sizeof(*pDevDesc)
+ || pDevDesc->bDescriptorType != USB_DT_DEVICE
+ || pDevDesc->bLength < sizeof(*pDevDesc)
+ || pDevDesc->bLength > sizeof(*pDevDesc) * 2)
+ continue;
+ PUSBCONFIGDESC pCfgDesc = (PUSBCONFIGDESC)&abBuf[pDevDesc->bLength];
+ if ( pCfgDesc->bDescriptorType != USB_DT_CONFIG
+ || pCfgDesc->bLength >= sizeof(*pCfgDesc))
+ pCfgDesc = NULL;
+
+ /*
+ * Skip it if it's some kind of hub.
+ */
+ if (pDevDesc->bDeviceClass == USB_HUB_CLASSCODE)
+ continue;
+
+ /*
+ * Allocate a new device node and initialize it with the basic stuff.
+ */
+ PUSBDEVICE pCur = (PUSBDEVICE)RTMemAlloc(sizeof(*pCur));
+ pCur->bcdUSB = pDevDesc->bcdUSB;
+ pCur->bDeviceClass = pDevDesc->bDeviceClass;
+ pCur->bDeviceSubClass = pDevDesc->bDeviceSubClass;
+ pCur->bDeviceProtocol = pDevDesc->bDeviceProtocol;
+ pCur->idVendor = pDevDesc->idVendor;
+ pCur->idProduct = pDevDesc->idProduct;
+ pCur->bcdDevice = pDevDesc->bcdDevice;
+ pCur->pszManufacturer = RTStrDup("");
+ pCur->pszProduct = RTStrDup("");
+ pCur->pszSerialNumber = NULL;
+ pCur->u64SerialHash = 0;
+ //pCur->bNumConfigurations = pDevDesc->bNumConfigurations;
+ pCur->bNumConfigurations = 0;
+ pCur->paConfigurations = NULL;
+ pCur->enmState = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE;
+ pCur->enmSpeed = USBDEVICESPEED_UNKNOWN;
+ pCur->pszAddress = NULL;
+ RTStrAPrintf((char **)&pCur->pszAddress, "p=0x%04RX16;v=0x%04RX16;r=0x%04RX16;e=0x%08RX32",
+ pDevDesc->idProduct, pDevDesc->idVendor, pDevDesc->bcdDevice, i);
+
+ pCur->bBus = 0;
+ pCur->bLevel = 0;
+ pCur->bDevNum = 0;
+ pCur->bDevNumParent = 0;
+ pCur->bPort = 0;
+ pCur->bNumDevices = 0;
+ pCur->bMaxChildren = 0;
+
+ /* link it */
+ pCur->pNext = NULL;
+ pCur->pPrev = *ppNext;
+ *ppNext = pCur;
+ ppNext = &pCur->pNext;
+ }
+
+ return pFirst;
+}
+
diff --git a/src/VBox/Main/src-server/solaris/DynLoadLibSolaris.cpp b/src/VBox/Main/src-server/solaris/DynLoadLibSolaris.cpp
new file mode 100644
index 00000000..1b7634dc
--- /dev/null
+++ b/src/VBox/Main/src-server/solaris/DynLoadLibSolaris.cpp
@@ -0,0 +1,85 @@
+/* $Id: DynLoadLibSolaris.cpp $ */
+/** @file
+ * Dynamically load libraries for Solaris hosts.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "DynLoadLibSolaris.h"
+
+#include <iprt/errcore.h>
+#include <iprt/ldr.h>
+
+
+/** -=-=-=-=-= LIB DLPI -=-=-=-=-=-=- **/
+
+/**
+ * Global pointer to the libdlpi module. This should only be set once all needed libraries
+ * and symbols have been successfully loaded.
+ */
+static RTLDRMOD g_hLibDlpi = NIL_RTLDRMOD;
+
+/**
+ * Whether we have tried to load libdlpi yet. This flag should only be set
+ * to "true" after we have either loaded both libraries and all symbols which we need,
+ * or failed to load something and unloaded.
+ */
+static bool g_fCheckedForLibDlpi = false;
+
+/** All the symbols we need from libdlpi.
+ * @{
+ */
+int (*g_pfnLibDlpiWalk)(dlpi_walkfunc_t *, void *, uint_t);
+int (*g_pfnLibDlpiOpen)(const char *, dlpi_handle_t *, uint_t);
+void (*g_pfnLibDlpiClose)(dlpi_handle_t);
+/** @} */
+
+bool VBoxSolarisLibDlpiFound(void)
+{
+ RTLDRMOD hLibDlpi;
+
+ if (g_fCheckedForLibDlpi)
+ return g_hLibDlpi != NIL_RTLDRMOD;
+ g_fCheckedForLibDlpi = true;
+ int rc = RTLdrLoad(LIB_DLPI, &hLibDlpi);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Unfortunately; we cannot make use of dlpi_get_physaddr because it requires us to
+ * open the VNIC/link which requires root permissions :/
+ */
+ rc = RTLdrGetSymbol(hLibDlpi, "dlpi_walk", (void **)&g_pfnLibDlpiWalk);
+ rc |= RTLdrGetSymbol(hLibDlpi, "dlpi_close", (void **)&g_pfnLibDlpiClose);
+ rc |= RTLdrGetSymbol(hLibDlpi, "dlpi_open", (void **)&g_pfnLibDlpiOpen);
+ if (RT_SUCCESS(rc))
+ {
+ g_hLibDlpi = hLibDlpi;
+ return true;
+ }
+
+ RTLdrClose(hLibDlpi);
+ }
+ hLibDlpi = NIL_RTLDRMOD;
+ return false;
+}
+
diff --git a/src/VBox/Main/src-server/solaris/DynLoadLibSolaris.h b/src/VBox/Main/src-server/solaris/DynLoadLibSolaris.h
new file mode 100644
index 00000000..3ac3055c
--- /dev/null
+++ b/src/VBox/Main/src-server/solaris/DynLoadLibSolaris.h
@@ -0,0 +1,50 @@
+/* $Id: DynLoadLibSolaris.h $ */
+/** @file
+ * Dynamically loaded libraries for Solaris hosts, Internal header.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SRC_src_server_solaris_DynLoadLibSolaris_h
+#define MAIN_INCLUDED_SRC_src_server_solaris_DynLoadLibSolaris_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define LIB_DLPI "libdlpi.so.1"
+#ifdef RT_OS_SOLARIS_10
+#include <sys/dlpi.h>
+#else
+#include <libdlpi.h>
+#endif
+
+typedef boolean_t dlpi_walkfunc_t(const char*, void *);
+
+extern int (*g_pfnLibDlpiWalk)(dlpi_walkfunc_t *, void *, uint_t);
+extern int (*g_pfnLibDlpiOpen)(const char *, dlpi_handle_t *, uint_t);
+extern void (*g_pfnLibDlpiClose)(dlpi_handle_t);
+
+extern bool VBoxSolarisLibDlpiFound(void);
+
+#endif /* !MAIN_INCLUDED_SRC_src_server_solaris_DynLoadLibSolaris_h */
+
diff --git a/src/VBox/Main/src-server/solaris/Makefile.kup b/src/VBox/Main/src-server/solaris/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-server/solaris/Makefile.kup
diff --git a/src/VBox/Main/src-server/solaris/NetIf-solaris.cpp b/src/VBox/Main/src-server/solaris/NetIf-solaris.cpp
new file mode 100644
index 00000000..77337463
--- /dev/null
+++ b/src/VBox/Main/src-server/solaris/NetIf-solaris.cpp
@@ -0,0 +1,559 @@
+/* $Id: NetIf-solaris.cpp $ */
+/** @file
+ * Main - NetIfList, Solaris implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_HOST
+
+#include <iprt/errcore.h>
+#include <iprt/ctype.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <list>
+
+#include "LoggingNew.h"
+#include "HostNetworkInterfaceImpl.h"
+#include "netif.h"
+
+#ifdef VBOX_WITH_HOSTNETIF_API
+
+#include <map>
+#include <iprt/sanitized/string>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stropts.h>
+#include <limits.h>
+#include <stdio.h>
+#include <libdevinfo.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <net/if_arp.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <kstat.h>
+
+#include "DynLoadLibSolaris.h"
+
+/** @todo Unify this define with VBoxNetFltBow-solaris.c */
+#define VBOXBOW_VNIC_TEMPLATE_NAME "vboxvnic_template"
+
+
+static uint32_t getInstance(const char *pszIfaceName, char *pszDevName)
+{
+ /*
+ * Get the instance number from the interface name, then clip it off.
+ */
+ int cbInstance = 0;
+ size_t cbIface = strlen(pszIfaceName);
+ const char *pszEnd = pszIfaceName + cbIface - 1;
+ for (size_t i = 0; i < cbIface - 1; i++)
+ {
+ if (!RT_C_IS_DIGIT(*pszEnd))
+ break;
+ cbInstance++;
+ pszEnd--;
+ }
+
+ uint32_t uInstance = RTStrToUInt32(pszEnd + 1);
+ strncpy(pszDevName, pszIfaceName, cbIface - cbInstance);
+ pszDevName[cbIface - cbInstance] = '\0';
+ return uInstance;
+}
+
+static uint32_t kstatGet(const char *name)
+{
+ kstat_ctl_t *kc;
+ uint32_t uSpeed = 0;
+
+ if ((kc = kstat_open()) == 0)
+ {
+ LogRel(("kstat_open() -> %d\n", errno));
+ return 0;
+ }
+
+ kstat_t *ksAdapter = kstat_lookup(kc, (char *)"link", -1, (char *)name);
+ if (ksAdapter == 0)
+ {
+ char szModule[KSTAT_STRLEN];
+ uint32_t uInstance = getInstance(name, szModule);
+ ksAdapter = kstat_lookup(kc, szModule, uInstance, (char *)"phys");
+ if (ksAdapter == 0)
+ ksAdapter = kstat_lookup(kc, szModule, uInstance, (char*)name);
+ }
+ if (ksAdapter == 0)
+ LogRel(("Failed to get network statistics for %s\n", name));
+ else if (kstat_read(kc, ksAdapter, 0) == -1)
+ LogRel(("kstat_read(%s) -> %d\n", name, errno));
+ else
+ {
+ kstat_named_t *kn;
+ if ((kn = (kstat_named_t *)kstat_data_lookup(ksAdapter, (char *)"ifspeed")) != NULL)
+ uSpeed = (uint32_t)(kn->value.ul / 1000000); /* bits -> Mbits */
+ else
+ LogRel(("kstat_data_lookup(ifspeed) -> %d, name=%s\n", errno, name));
+ }
+ kstat_close(kc);
+ LogFlow(("kstatGet(%s) -> %u Mbit/s\n", name, uSpeed));
+ return uSpeed;
+}
+
+static void queryIfaceSpeed(PNETIFINFO pInfo)
+{
+ /* Don't query interface speed for inactive interfaces (see @bugref{6345}). */
+ if (pInfo->enmStatus == NETIF_S_UP)
+ pInfo->uSpeedMbits = kstatGet(pInfo->szShortName);
+ else
+ pInfo->uSpeedMbits = 0;
+ LogFlow(("queryIfaceSpeed(%s) -> %u\n", pInfo->szShortName, pInfo->uSpeedMbits));
+}
+
+static void vboxSolarisAddHostIface(char *pszIface, int Instance, void *pvHostNetworkInterfaceList)
+{
+ std::list<ComObjPtr<HostNetworkInterface> > *pList =
+ (std::list<ComObjPtr<HostNetworkInterface> > *)pvHostNetworkInterfaceList;
+ Assert(pList);
+
+ typedef std::map <std::string, std::string> NICMap;
+ typedef std::pair <std::string, std::string> NICPair;
+ static NICMap SolarisNICMap;
+ if (SolarisNICMap.empty())
+ {
+ SolarisNICMap.insert(NICPair("afe", "ADMtek Centaur/Comet Fast Ethernet"));
+ SolarisNICMap.insert(NICPair("atge", "Atheros/Attansic Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("aggr", "Link Aggregation Interface"));
+ SolarisNICMap.insert(NICPair("bfe", "Broadcom BCM4401 Fast Ethernet"));
+ SolarisNICMap.insert(NICPair("bge", "Broadcom BCM57xx Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("bnx", "Broadcom NetXtreme Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("bnxe", "Broadcom NetXtreme II 10 Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("ce", "Cassini Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("chxge", "Chelsio Ethernet"));
+ SolarisNICMap.insert(NICPair("dmfe", "Davicom 9102 Fast Ethernet"));
+ SolarisNICMap.insert(NICPair("dnet", "DEC 21040/41 21140 Ethernet"));
+ SolarisNICMap.insert(NICPair("e1000", "Intel PRO/1000 Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("e1000g", "Intel PRO/1000 Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("elx", "3COM Etherlink III Ethernet"));
+ SolarisNICMap.insert(NICPair("elxl", "3COM Etherlink XL Ethernet"));
+ SolarisNICMap.insert(NICPair("eri", "eri Fast Ethernet"));
+ SolarisNICMap.insert(NICPair("ge", "GEM Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("hme", "SUNW,hme Fast-Ethernet"));
+ SolarisNICMap.insert(NICPair("hxge", "Sun Blade 10 Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("igb", "Intel 82575 PCI-E Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("ipge", "PCI-E Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("iprb", "Intel 82557/58/59 Ethernet"));
+ SolarisNICMap.insert(NICPair("ixgb", "Intel 82597ex 10 Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("ixgbe", "Intel 10 Gigabit PCI-E Ethernet"));
+ SolarisNICMap.insert(NICPair("mcxe", "Mellanox ConnectX-2 10 Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("mxfe", "Macronix 98715 Fast Ethernet"));
+ SolarisNICMap.insert(NICPair("nfo", "Nvidia Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("nge", "Nvidia Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("ntxn", "NetXen 10/1 Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("nxge", "Sun 10/1 Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("pcelx", "3COM EtherLink III PCMCIA Ethernet"));
+ SolarisNICMap.insert(NICPair("pcn", "AMD PCnet Ethernet"));
+ SolarisNICMap.insert(NICPair("qfe", "SUNW,qfe Quad Fast-Ethernet"));
+ SolarisNICMap.insert(NICPair("rge", "Realtek Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("rtls", "Realtek 8139 Fast Ethernet"));
+ SolarisNICMap.insert(NICPair("sfe", "SiS900 Fast Ethernet"));
+ SolarisNICMap.insert(NICPair("skge", "SksKonnect Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("spwr", "SMC EtherPower II 10/100 (9432) Ethernet"));
+ SolarisNICMap.insert(NICPair("vboxnet", "VirtualBox Host Ethernet"));
+ SolarisNICMap.insert(NICPair(VBOXBOW_VNIC_TEMPLATE_NAME, "VirtualBox VNIC Template"));
+ SolarisNICMap.insert(NICPair("vlan", "Virtual LAN Ethernet"));
+ SolarisNICMap.insert(NICPair("vr", "VIA Rhine Fast Ethernet"));
+ SolarisNICMap.insert(NICPair("vnic", "Virtual Network Interface Ethernet"));
+ SolarisNICMap.insert(NICPair("xge", "Neterior Xframe 10Gigabit Ethernet"));
+ SolarisNICMap.insert(NICPair("yge", "Marvell Yukon 2 Fast Ethernet"));
+ }
+
+ /*
+ * Try picking up description from our NIC map.
+ */
+ char szNICInstance[128];
+ RTStrPrintf(szNICInstance, sizeof(szNICInstance), "%s%d", pszIface, Instance);
+ char szNICDesc[256];
+ std::string Description = SolarisNICMap[pszIface];
+ if (Description != "VirtualBox Host Ethernet")
+ {
+ if (Description != "")
+ RTStrPrintf(szNICDesc, sizeof(szNICDesc), "%s - %s", szNICInstance, Description.c_str());
+ else if (!strncmp(szNICInstance, RT_STR_TUPLE(VBOXBOW_VNIC_TEMPLATE_NAME)))
+ {
+ /*
+ * We want prefix matching only for "vboxvnic_template" as it's possible to create "vboxvnic_template_abcd123",
+ * which our Solaris Crossbow NetFilter driver will interpret as a VNIC template.
+ */
+ Description = SolarisNICMap[VBOXBOW_VNIC_TEMPLATE_NAME];
+ RTStrPrintf(szNICDesc, sizeof(szNICDesc), "%s - %s", szNICInstance, Description.c_str());
+ }
+ else
+ RTStrPrintf(szNICDesc, sizeof(szNICDesc), "%s - Ethernet", szNICInstance);
+ }
+ else
+ RTStrPrintf(szNICDesc, sizeof(szNICDesc), "%s", szNICInstance);
+
+ /*
+ * Try to get IP V4 address and netmask as well as Ethernet address.
+ */
+ NETIFINFO Info;
+ RT_ZERO(Info);
+ int Sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (Sock > 0)
+ {
+ struct lifreq IfReq;
+ RTStrCopy(IfReq.lifr_name, sizeof(IfReq.lifr_name), szNICInstance);
+ if (ioctl(Sock, SIOCGLIFADDR, &IfReq) >= 0)
+ {
+ memcpy(Info.IPAddress.au8, &((struct sockaddr_in *)&IfReq.lifr_addr)->sin_addr.s_addr,
+ sizeof(Info.IPAddress.au8));
+ struct arpreq ArpReq;
+ memcpy(&ArpReq.arp_pa, &IfReq.lifr_addr, sizeof(struct sockaddr_in));
+
+ /*
+ * We might fail if the interface has not been assigned an IP address.
+ * That doesn't matter; as long as it's plumbed we can pick it up.
+ * But, if it has not acquired an IP address we cannot obtain it's MAC
+ * address this way, so we just use all zeros there.
+ */
+ if (ioctl(Sock, SIOCGARP, &ArpReq) >= 0)
+ {
+ memcpy(&Info.MACAddress, ArpReq.arp_ha.sa_data, sizeof(Info.MACAddress));
+ }
+
+ }
+
+ if (ioctl(Sock, SIOCGLIFNETMASK, &IfReq) >= 0)
+ {
+ memcpy(Info.IPNetMask.au8, &((struct sockaddr_in *)&IfReq.lifr_addr)->sin_addr.s_addr,
+ sizeof(Info.IPNetMask.au8));
+ }
+ if (ioctl(Sock, SIOCGLIFFLAGS, &IfReq) >= 0)
+ {
+ Info.enmStatus = IfReq.lifr_flags & IFF_UP ? NETIF_S_UP : NETIF_S_DOWN;
+ }
+ close(Sock);
+ }
+ /*
+ * Try to get IP V6 address and netmask.
+ */
+ Sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
+ if (Sock > 0)
+ {
+ struct lifreq IfReq;
+ RTStrCopy(IfReq.lifr_name, sizeof(IfReq.lifr_name), szNICInstance);
+ if (ioctl(Sock, SIOCGLIFADDR, &IfReq) >= 0)
+ {
+ memcpy(Info.IPv6Address.au8, ((struct sockaddr_in6 *)&IfReq.lifr_addr)->sin6_addr.s6_addr,
+ sizeof(Info.IPv6Address.au8));
+ }
+ if (ioctl(Sock, SIOCGLIFNETMASK, &IfReq) >= 0)
+ {
+ memcpy(Info.IPv6NetMask.au8, ((struct sockaddr_in6 *)&IfReq.lifr_addr)->sin6_addr.s6_addr,
+ sizeof(Info.IPv6NetMask.au8));
+ }
+ close(Sock);
+ }
+
+ /*
+ * Construct UUID with interface name and the MAC address if available.
+ */
+ RTUUID Uuid;
+ RTUuidClear(&Uuid);
+ memcpy(&Uuid, szNICInstance, RT_MIN(strlen(szNICInstance), sizeof(Uuid)));
+ Uuid.Gen.u8ClockSeqHiAndReserved = (Uuid.Gen.u8ClockSeqHiAndReserved & 0x3f) | 0x80;
+ Uuid.Gen.u16TimeHiAndVersion = (Uuid.Gen.u16TimeHiAndVersion & 0x0fff) | 0x4000;
+ Uuid.Gen.au8Node[0] = Info.MACAddress.au8[0];
+ Uuid.Gen.au8Node[1] = Info.MACAddress.au8[1];
+ Uuid.Gen.au8Node[2] = Info.MACAddress.au8[2];
+ Uuid.Gen.au8Node[3] = Info.MACAddress.au8[3];
+ Uuid.Gen.au8Node[4] = Info.MACAddress.au8[4];
+ Uuid.Gen.au8Node[5] = Info.MACAddress.au8[5];
+ Info.Uuid = Uuid;
+ Info.enmMediumType = NETIF_T_ETHERNET;
+ strncpy(Info.szShortName, szNICInstance, sizeof(Info.szShortName) - 1);
+
+ HostNetworkInterfaceType_T enmType;
+ if (strncmp(szNICInstance, RT_STR_TUPLE("vboxnet")))
+ enmType = HostNetworkInterfaceType_Bridged;
+ else
+ enmType = HostNetworkInterfaceType_HostOnly;
+ queryIfaceSpeed(&Info);
+ ComObjPtr<HostNetworkInterface> IfObj;
+ IfObj.createObject();
+ if (SUCCEEDED(IfObj->init(szNICDesc, enmType, &Info)))
+ pList->push_back(IfObj);
+}
+
+static boolean_t vboxSolarisAddLinkHostIface(const char *pszIface, void *pvHostNetworkInterfaceList)
+{
+ /*
+ * Skip IPSEC interfaces. It's at IP level.
+ */
+ if (!strncmp(pszIface, RT_STR_TUPLE("ip.tun")))
+ return _B_FALSE;
+
+ /*
+ * Skip our own dynamic VNICs but don't skip VNIC templates.
+ * These names originate from VBoxNetFltBow-solaris.c, hardcoded here for now.
+ * .
+ * ASSUMES template name is longer than 'vboxvnic'.
+ */
+ if ( strncmp(pszIface, RT_STR_TUPLE(VBOXBOW_VNIC_TEMPLATE_NAME))
+ && !strncmp(pszIface, RT_STR_TUPLE("vboxvnic")))
+ return _B_FALSE;
+
+ /*
+ * Clip off the zone instance number from the interface name (if any).
+ */
+ char szIfaceName[128];
+ strcpy(szIfaceName, pszIface);
+ char *pszColon = (char *)memchr(szIfaceName, ':', sizeof(szIfaceName));
+ if (pszColon)
+ *pszColon = '\0';
+
+ /*
+ * Get the instance number from the interface name, then clip it off.
+ */
+ int cbInstance = 0;
+ size_t cbIface = strlen(szIfaceName);
+ const char *pszEnd = pszIface + cbIface - 1;
+ for (size_t i = 0; i < cbIface - 1; i++)
+ {
+ if (!RT_C_IS_DIGIT(*pszEnd))
+ break;
+ cbInstance++;
+ pszEnd--;
+ }
+
+ int Instance = atoi(pszEnd + 1);
+ strncpy(szIfaceName, pszIface, cbIface - cbInstance);
+ szIfaceName[cbIface - cbInstance] = '\0';
+
+ /*
+ * Add the interface.
+ */
+ vboxSolarisAddHostIface(szIfaceName, Instance, pvHostNetworkInterfaceList);
+
+ /*
+ * Continue walking...
+ */
+ return _B_FALSE;
+}
+
+static bool vboxSolarisSortNICList(const ComObjPtr<HostNetworkInterface> Iface1, const ComObjPtr<HostNetworkInterface> Iface2)
+{
+ Bstr Iface1Str;
+ (*Iface1).COMGETTER(Name) (Iface1Str.asOutParam());
+
+ Bstr Iface2Str;
+ (*Iface2).COMGETTER(Name) (Iface2Str.asOutParam());
+
+ return Iface1Str < Iface2Str;
+}
+
+static bool vboxSolarisSameNIC(const ComObjPtr<HostNetworkInterface> Iface1, const ComObjPtr<HostNetworkInterface> Iface2)
+{
+ Bstr Iface1Str;
+ (*Iface1).COMGETTER(Name) (Iface1Str.asOutParam());
+
+ Bstr Iface2Str;
+ (*Iface2).COMGETTER(Name) (Iface2Str.asOutParam());
+
+ return (Iface1Str == Iface2Str);
+}
+
+static int vboxSolarisAddPhysHostIface(di_node_t Node, di_minor_t Minor, void *pvHostNetworkInterfaceList)
+{
+ NOREF(Minor);
+
+ char *pszDriverName = di_driver_name(Node);
+ int Instance = di_instance(Node);
+
+ /*
+ * Skip aggregations.
+ */
+ if (!strcmp(pszDriverName, "aggr"))
+ return DI_WALK_CONTINUE;
+
+ /*
+ * Skip softmacs.
+ */
+ if (!strcmp(pszDriverName, "softmac"))
+ return DI_WALK_CONTINUE;
+
+ /*
+ * Driver names doesn't always imply the same link name probably since
+ * S11's vanity names by default (e.g. highly descriptive "net0") names
+ * was introduced. Try opening the link to find out if it really exists.
+ *
+ * This weeds out listing of "e1000g0" as a valid interface on my S11.2
+ * Dell Optiplex box.
+ */
+ if (VBoxSolarisLibDlpiFound())
+ {
+ /** @todo should we try also opening "linkname+instance"? */
+ dlpi_handle_t hLink;
+ if (g_pfnLibDlpiOpen(pszDriverName, &hLink, 0) != DLPI_SUCCESS)
+ return DI_WALK_CONTINUE;
+ g_pfnLibDlpiClose(hLink);
+ }
+
+ vboxSolarisAddHostIface(pszDriverName, Instance, pvHostNetworkInterfaceList);
+ return DI_WALK_CONTINUE;
+}
+
+int NetIfList(std::list<ComObjPtr<HostNetworkInterface> > &list)
+{
+ /*
+ * Use libdevinfo for determining all physical interfaces.
+ */
+ di_node_t Root;
+ Root = di_init("/", DINFOCACHE);
+ if (Root != DI_NODE_NIL)
+ {
+ di_walk_minor(Root, DDI_NT_NET, 0 /* flag */, &list, vboxSolarisAddPhysHostIface);
+ di_fini(Root);
+ }
+
+ /*
+ * Use libdlpi for determining all DLPI interfaces.
+ */
+ if (VBoxSolarisLibDlpiFound())
+ g_pfnLibDlpiWalk(vboxSolarisAddLinkHostIface, &list, 0);
+
+ /*
+ * This gets only the list of all plumbed logical interfaces.
+ * This is needed for zones which cannot access the device tree
+ * and in this case we just let them use the list of plumbed interfaces
+ * on the zone.
+ */
+ int Sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (Sock > 0)
+ {
+ struct lifnum IfNum;
+ RT_ZERO(IfNum);
+ IfNum.lifn_family = AF_INET;
+ int rc = ioctl(Sock, SIOCGLIFNUM, &IfNum);
+ if (!rc)
+ {
+ int cIfaces = RT_MIN(1024, IfNum.lifn_count); /* sane limit */
+ size_t cbIfaces = (unsigned)RT_MAX(cIfaces, 1) * sizeof(struct lifreq);
+ struct lifreq *paIfaces = (struct lifreq *)RTMemTmpAlloc(cbIfaces);
+ if (paIfaces)
+ {
+ struct lifconf IfConfig;
+ RT_ZERO(IfConfig);
+ IfConfig.lifc_family = AF_INET;
+ IfConfig.lifc_len = (int)cbIfaces;
+ IfConfig.lifc_buf = (caddr_t)paIfaces;
+ rc = ioctl(Sock, SIOCGLIFCONF, &IfConfig);
+ if (!rc)
+ {
+ for (int i = 0; i < cIfaces; i++)
+ {
+ /*
+ * Skip loopback interfaces.
+ */
+ if (!strncmp(paIfaces[i].lifr_name, RT_STR_TUPLE("lo")))
+ continue;
+
+#if 0
+ rc = ioctl(Sock, SIOCGLIFADDR, &(paIfaces[i]));
+ if (rc >= 0)
+ {
+ memcpy(Info.IPAddress.au8, ((struct sockaddr *)&paIfaces[i].lifr_addr)->sa_data,
+ sizeof(Info.IPAddress.au8));
+ // SIOCGLIFNETMASK
+ struct arpreq ArpReq;
+ memcpy(&ArpReq.arp_pa, &paIfaces[i].lifr_addr, sizeof(struct sockaddr_in));
+
+ /*
+ * We might fail if the interface has not been assigned an IP address.
+ * That doesn't matter; as long as it's plumbed we can pick it up.
+ * But, if it has not acquired an IP address we cannot obtain it's MAC
+ * address this way, so we just use all zeros there.
+ */
+ rc = ioctl(Sock, SIOCGARP, &ArpReq);
+ if (rc >= 0)
+ memcpy(&Info.MACAddress, ArpReq.arp_ha.sa_data, sizeof(Info.MACAddress));
+
+ char szNICDesc[LIFNAMSIZ + 256];
+ char *pszIface = paIfaces[i].lifr_name;
+ strcpy(szNICDesc, pszIface);
+
+ vboxSolarisAddLinkHostIface(pszIface, &list);
+ }
+#endif
+
+ vboxSolarisAddLinkHostIface(paIfaces[i].lifr_name, &list);
+ }
+ }
+ RTMemTmpFree(paIfaces);
+ }
+ }
+ close(Sock);
+ }
+
+ /*
+ * Weed out duplicates caused by dlpi_walk inconsistencies across Nevadas.
+ */
+ list.sort(vboxSolarisSortNICList);
+ list.unique(vboxSolarisSameNIC);
+
+ return VINF_SUCCESS;
+}
+
+#else
+int NetIfList(std::list <ComObjPtr<HostNetworkInterface> > &list)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+#endif
+
+int NetIfGetConfigByName(PNETIFINFO pInfo)
+{
+ NOREF(pInfo);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * Retrieve the physical link speed in megabits per second. If the interface is
+ * not up or otherwise unavailable the zero speed is returned.
+ *
+ * @returns VBox status code.
+ *
+ * @param pcszIfName Interface name.
+ * @param puMbits Where to store the link speed.
+ */
+int NetIfGetLinkSpeed(const char *pcszIfName, uint32_t *puMbits)
+{
+ *puMbits = kstatGet(pcszIfName);
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/Main/src-server/solaris/PerformanceSolaris.cpp b/src/VBox/Main/src-server/solaris/PerformanceSolaris.cpp
new file mode 100644
index 00000000..54ce7dbf
--- /dev/null
+++ b/src/VBox/Main/src-server/solaris/PerformanceSolaris.cpp
@@ -0,0 +1,743 @@
+/* $Id: PerformanceSolaris.cpp $ */
+/** @file
+ * VBox Solaris-specific Performance Classes implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_PERFORMANCECOLLECTOR
+#undef _FILE_OFFSET_BITS
+#include <procfs.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <kstat.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/statvfs.h>
+
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/system.h>
+
+#include "LoggingNew.h"
+#include "Performance.h"
+
+#include <dlfcn.h>
+
+#include <libzfs.h>
+#include <libnvpair.h>
+
+#include <map>
+
+namespace pm {
+
+ typedef libzfs_handle_t *(*PFNZFSINIT)(void);
+ typedef void (*PFNZFSFINI)(libzfs_handle_t *);
+ typedef zfs_handle_t *(*PFNZFSOPEN)(libzfs_handle_t *, const char *, int);
+ typedef void (*PFNZFSCLOSE)(zfs_handle_t *);
+ typedef uint64_t (*PFNZFSPROPGETINT)(zfs_handle_t *, zfs_prop_t);
+ typedef zpool_handle_t *(*PFNZPOOLOPEN)(libzfs_handle_t *, const char *);
+ typedef void (*PFNZPOOLCLOSE)(zpool_handle_t *);
+ typedef nvlist_t *(*PFNZPOOLGETCONFIG)(zpool_handle_t *, nvlist_t **);
+ typedef char *(*PFNZPOOLVDEVNAME)(libzfs_handle_t *, zpool_handle_t *, nvlist_t *, boolean_t);
+
+ typedef std::map<RTCString,RTCString> FsMap;
+
+class CollectorSolaris : public CollectorHAL
+{
+public:
+ CollectorSolaris();
+ virtual ~CollectorSolaris();
+ virtual int getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available);
+ virtual int getHostFilesystemUsage(const char *name, ULONG *total, ULONG *used, ULONG *available);
+ virtual int getHostDiskSize(const char *name, uint64_t *size);
+ virtual int getProcessMemoryUsage(RTPROCESS process, ULONG *used);
+
+ virtual int getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle);
+ virtual int getRawHostNetworkLoad(const char *name, uint64_t *rx, uint64_t *tx);
+ virtual int getRawHostDiskLoad(const char *name, uint64_t *disk_ms, uint64_t *total_ms);
+ virtual int getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total);
+
+ virtual int getDiskListByFs(const char *name, DiskList& listUsage, DiskList& listLoad);
+private:
+ static uint32_t getInstance(const char *pszIfaceName, char *pszDevName);
+ uint64_t getZfsTotal(uint64_t cbTotal, const char *szFsType, const char *szFsName);
+ void updateFilesystemMap(void);
+ RTCString physToInstName(const char *pcszPhysName);
+ RTCString pathToInstName(const char *pcszDevPathName);
+ uint64_t wrapCorrection(uint32_t cur, uint64_t prev, const char *name);
+ uint64_t wrapDetection(uint64_t cur, uint64_t prev, const char *name);
+
+ kstat_ctl_t *mKC;
+ kstat_t *mSysPages;
+ kstat_t *mZFSCache;
+
+ void *mZfsSo;
+ libzfs_handle_t *mZfsLib;
+ PFNZFSINIT mZfsInit;
+ PFNZFSFINI mZfsFini;
+ PFNZFSOPEN mZfsOpen;
+ PFNZFSCLOSE mZfsClose;
+ PFNZFSPROPGETINT mZfsPropGetInt;
+ PFNZPOOLOPEN mZpoolOpen;
+ PFNZPOOLCLOSE mZpoolClose;
+ PFNZPOOLGETCONFIG mZpoolGetConfig;
+ PFNZPOOLVDEVNAME mZpoolVdevName;
+
+ FsMap mFsMap;
+ uint32_t mCpus;
+ ULONG totalRAM;
+};
+
+CollectorHAL *createHAL()
+{
+ return new CollectorSolaris();
+}
+
+// Collector HAL for Solaris
+
+
+CollectorSolaris::CollectorSolaris()
+ : mKC(0),
+ mSysPages(0),
+ mZFSCache(0),
+ mZfsLib(0),
+ mCpus(0)
+{
+ if ((mKC = kstat_open()) == 0)
+ {
+ Log(("kstat_open() -> %d\n", errno));
+ return;
+ }
+
+ if ((mSysPages = kstat_lookup(mKC, (char *)"unix", 0, (char *)"system_pages")) == 0)
+ {
+ Log(("kstat_lookup(system_pages) -> %d\n", errno));
+ return;
+ }
+
+ if ((mZFSCache = kstat_lookup(mKC, (char *)"zfs", 0, (char *)"arcstats")) == 0)
+ {
+ Log(("kstat_lookup(system_pages) -> %d\n", errno));
+ }
+
+ /* Try to load libzfs dynamically, it may be missing. */
+ mZfsSo = dlopen("libzfs.so", RTLD_LAZY);
+ if (mZfsSo)
+ {
+ mZfsInit = (PFNZFSINIT)(uintptr_t)dlsym(mZfsSo, "libzfs_init");
+ mZfsFini = (PFNZFSFINI)(uintptr_t)dlsym(mZfsSo, "libzfs_fini");
+ mZfsOpen = (PFNZFSOPEN)(uintptr_t)dlsym(mZfsSo, "zfs_open");
+ mZfsClose = (PFNZFSCLOSE)(uintptr_t)dlsym(mZfsSo, "zfs_close");
+ mZfsPropGetInt = (PFNZFSPROPGETINT)(uintptr_t)dlsym(mZfsSo, "zfs_prop_get_int");
+ mZpoolOpen = (PFNZPOOLOPEN)(uintptr_t)dlsym(mZfsSo, "zpool_open");
+ mZpoolClose = (PFNZPOOLCLOSE)(uintptr_t)dlsym(mZfsSo, "zpool_close");
+ mZpoolGetConfig = (PFNZPOOLGETCONFIG)(uintptr_t)dlsym(mZfsSo, "zpool_get_config");
+ mZpoolVdevName = (PFNZPOOLVDEVNAME)(uintptr_t)dlsym(mZfsSo, "zpool_vdev_name");
+
+ if ( mZfsInit
+ && mZfsOpen
+ && mZfsClose
+ && mZfsPropGetInt
+ && mZpoolOpen
+ && mZpoolClose
+ && mZpoolGetConfig
+ && mZpoolVdevName)
+ mZfsLib = mZfsInit();
+ else
+ LogRel(("Incompatible libzfs? libzfs_init=%p zfs_open=%p zfs_close=%p zfs_prop_get_int=%p\n",
+ mZfsInit, mZfsOpen, mZfsClose, mZfsPropGetInt));
+ }
+
+ updateFilesystemMap();
+ /* Notice that mCpus member will be initialized by HostCpuLoadRaw::init() */
+
+ uint64_t cb;
+ int rc = RTSystemQueryTotalRam(&cb);
+ if (RT_FAILURE(rc))
+ totalRAM = 0;
+ else
+ totalRAM = (ULONG)(cb / 1024);
+}
+
+CollectorSolaris::~CollectorSolaris()
+{
+ if (mKC)
+ kstat_close(mKC);
+ /* Not calling libzfs_fini() causes file descriptor leaks (#6788). */
+ if (mZfsFini && mZfsLib)
+ mZfsFini(mZfsLib);
+ if (mZfsSo)
+ dlclose(mZfsSo);
+}
+
+int CollectorSolaris::getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle)
+{
+ int rc = VINF_SUCCESS;
+ kstat_t *ksp;
+ uint64_t tmpUser, tmpKernel, tmpIdle;
+ int cpus;
+ cpu_stat_t cpu_stats;
+
+ if (mKC == 0)
+ return VERR_INTERNAL_ERROR;
+
+ tmpUser = tmpKernel = tmpIdle = cpus = 0;
+ for (ksp = mKC->kc_chain; ksp != NULL; ksp = ksp->ks_next) {
+ if (strcmp(ksp->ks_module, "cpu_stat") == 0) {
+ if (kstat_read(mKC, ksp, &cpu_stats) == -1)
+ {
+ Log(("kstat_read() -> %d\n", errno));
+ return VERR_INTERNAL_ERROR;
+ }
+ ++cpus;
+ tmpUser += cpu_stats.cpu_sysinfo.cpu[CPU_USER];
+ tmpKernel += cpu_stats.cpu_sysinfo.cpu[CPU_KERNEL];
+ tmpIdle += cpu_stats.cpu_sysinfo.cpu[CPU_IDLE];
+ }
+ }
+
+ if (cpus == 0)
+ {
+ Log(("no cpu stats found!\n"));
+ return VERR_INTERNAL_ERROR;
+ }
+ else
+ mCpus = cpus;
+
+ if (user) *user = tmpUser;
+ if (kernel) *kernel = tmpKernel;
+ if (idle) *idle = tmpIdle;
+
+ return rc;
+}
+
+int CollectorSolaris::getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total)
+{
+ int rc = VINF_SUCCESS;
+ char *pszName;
+ prusage_t prusage;
+
+ RTStrAPrintf(&pszName, "/proc/%d/usage", process);
+ Log(("Opening %s...\n", pszName));
+ int h = open(pszName, O_RDONLY);
+ RTStrFree(pszName);
+
+ if (h != -1)
+ {
+ if (read(h, &prusage, sizeof(prusage)) == sizeof(prusage))
+ {
+ //Assert((pid_t)process == pstatus.pr_pid);
+ //Log(("user=%u kernel=%u total=%u\n", prusage.pr_utime.tv_sec, prusage.pr_stime.tv_sec, prusage.pr_tstamp.tv_sec));
+ /*
+ * The CPU time spent must be adjusted by the number of cores for compatibility with
+ * other platforms (see @bugref{6345}).
+ */
+ Assert(mCpus);
+ if (mCpus)
+ {
+ *user = ((uint64_t)prusage.pr_utime.tv_sec * 1000000000 + prusage.pr_utime.tv_nsec) / mCpus;
+ *kernel = ((uint64_t)prusage.pr_stime.tv_sec * 1000000000 + prusage.pr_stime.tv_nsec) / mCpus;
+ }
+ else
+ *user = *kernel = 0;
+ *total = (uint64_t)prusage.pr_tstamp.tv_sec * 1000000000 + prusage.pr_tstamp.tv_nsec;
+ //Log(("user=%llu kernel=%llu total=%llu\n", *user, *kernel, *total));
+ }
+ else
+ {
+ Log(("read() -> %d\n", errno));
+ rc = VERR_FILE_IO_ERROR;
+ }
+ close(h);
+ }
+ else
+ {
+ Log(("open() -> %d\n", errno));
+ rc = VERR_ACCESS_DENIED;
+ }
+
+ return rc;
+}
+
+int CollectorSolaris::getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available)
+{
+ AssertReturn(totalRAM, VERR_INTERNAL_ERROR);
+ uint64_t cb;
+ int rc = RTSystemQueryAvailableRam(&cb);
+ if (RT_SUCCESS(rc))
+ {
+ *total = totalRAM;
+ *available = (ULONG)RT_MIN(cb / 1024, ~(ULONG)0);
+ *used = *total - *available;
+ }
+ return rc;
+}
+
+int CollectorSolaris::getProcessMemoryUsage(RTPROCESS process, ULONG *used)
+{
+ int rc = VINF_SUCCESS;
+ char *pszName = NULL;
+ psinfo_t psinfo;
+
+ RTStrAPrintf(&pszName, "/proc/%d/psinfo", process);
+ Log(("Opening %s...\n", pszName));
+ int h = open(pszName, O_RDONLY);
+ RTStrFree(pszName);
+
+ if (h != -1)
+ {
+ /* psinfo_t keeps growing, so only read what we need to maximize
+ * cross-version compatibility. The structures are compatible. */
+ ssize_t cb = RT_UOFFSETOF(psinfo_t, pr_rssize) + RT_SIZEOFMEMB(psinfo_t, pr_rssize);
+ AssertCompile(RTASSERT_OFFSET_OF(psinfo_t, pr_rssize) > RTASSERT_OFFSET_OF(psinfo_t, pr_pid));
+ if (read(h, &psinfo, cb) == cb)
+ {
+ Assert((pid_t)process == psinfo.pr_pid);
+ *used = (ULONG)RT_MIN(psinfo.pr_rssize, ~(ULONG)0);
+ }
+ else
+ {
+ Log(("read() -> %d\n", errno));
+ rc = VERR_FILE_IO_ERROR;
+ }
+ close(h);
+ }
+ else
+ {
+ Log(("open() -> %d\n", errno));
+ rc = VERR_ACCESS_DENIED;
+ }
+
+ return rc;
+}
+
+uint32_t CollectorSolaris::getInstance(const char *pszIfaceName, char *pszDevName)
+{
+ /*
+ * Get the instance number from the interface name, then clip it off.
+ */
+ int cbInstance = 0;
+ size_t cbIface = strlen(pszIfaceName);
+ const char *pszEnd = pszIfaceName + cbIface - 1;
+ for (size_t i = 0; i < cbIface - 1; i++)
+ {
+ if (!RT_C_IS_DIGIT(*pszEnd))
+ break;
+ cbInstance++;
+ pszEnd--;
+ }
+
+ uint32_t uInstance = RTStrToUInt32(pszEnd + 1);
+ strncpy(pszDevName, pszIfaceName, cbIface - cbInstance);
+ pszDevName[cbIface - cbInstance] = '\0';
+ return uInstance;
+}
+
+uint64_t CollectorSolaris::wrapCorrection(uint32_t cur, uint64_t prev, const char *name)
+{
+ NOREF(name);
+ uint64_t corrected = (prev & 0xffffffff00000000) + cur;
+ if (cur < (prev & 0xffffffff))
+ {
+ /* wrap has occurred */
+ corrected += 0x100000000;
+ LogFlowThisFunc(("Corrected wrap on %s (%u < %u), returned %llu.\n",
+ name, cur, (uint32_t)prev, corrected));
+ }
+ return corrected;
+}
+
+uint64_t CollectorSolaris::wrapDetection(uint64_t cur, uint64_t prev, const char *name)
+{
+ if (cur < prev)
+ LogRelMax(2, ("Detected wrap on %s (%llu < %llu).\n", name, cur, prev));
+ return cur;
+}
+
+/*
+ * WARNING! This function expects the previous values of rx and tx counter to
+ * be passed in as well as returnes new values in the same parameters. This is
+ * needed to provide a workaround for 32-bit counter wrapping.
+ */
+int CollectorSolaris::getRawHostNetworkLoad(const char *name, uint64_t *rx, uint64_t *tx)
+{
+ static bool g_fNotReported = true;
+ AssertReturn(strlen(name) < KSTAT_STRLEN, VERR_INVALID_PARAMETER);
+ LogFlowThisFunc(("m=%s i=%d n=%s\n", "link", -1, name));
+ kstat_t *ksAdapter = kstat_lookup(mKC, (char *)"link", -1, (char *)name);
+ if (ksAdapter == 0)
+ {
+ char szModule[KSTAT_STRLEN];
+ uint32_t uInstance = getInstance(name, szModule);
+ LogFlowThisFunc(("m=%s i=%u n=%s\n", szModule, uInstance, "phys"));
+ ksAdapter = kstat_lookup(mKC, szModule, uInstance, (char *)"phys");
+ if (ksAdapter == 0)
+ {
+ LogFlowThisFunc(("m=%s i=%u n=%s\n", szModule, uInstance, name));
+ ksAdapter = kstat_lookup(mKC, szModule, uInstance, (char *)name);
+ if (ksAdapter == 0)
+ {
+ static uint32_t s_tsLogRelLast;
+ uint32_t tsNow = RTTimeProgramSecTS();
+ if ( tsNow < RT_SEC_1HOUR
+ || (tsNow - s_tsLogRelLast >= 60))
+ {
+ s_tsLogRelLast = tsNow;
+ LogRel(("Failed to get network statistics for %s. Max one msg/min.\n", name));
+ }
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+ }
+ if (kstat_read(mKC, ksAdapter, 0) == -1)
+ {
+ LogRel(("kstat_read(adapter) -> %d\n", errno));
+ return VERR_INTERNAL_ERROR;
+ }
+ kstat_named_t *kn;
+ if ((kn = (kstat_named_t *)kstat_data_lookup(ksAdapter, (char *)"rbytes64")) == NULL)
+ {
+ if ((kn = (kstat_named_t *)kstat_data_lookup(ksAdapter, (char *)"rbytes")) == NULL)
+ {
+ LogRel(("kstat_data_lookup(rbytes) -> %d, name=%s\n", errno, name));
+ return VERR_INTERNAL_ERROR;
+ }
+#if ARCH_BITS == 32
+ if (g_fNotReported)
+ {
+ g_fNotReported = false;
+ LogRel(("Failed to locate rbytes64, falling back to 32-bit counters...\n"));
+ }
+ *rx = wrapCorrection(kn->value.ul, *rx, "rbytes");
+#else
+ AssertCompile(sizeof(kn->value.ul) == sizeof(uint64_t));
+ *rx = wrapDetection(kn->value.ul, *rx, "rbytes");
+#endif
+ }
+ else
+ *rx = wrapDetection(kn->value.ull, *rx, "rbytes64");
+ if ((kn = (kstat_named_t *)kstat_data_lookup(ksAdapter, (char *)"obytes64")) == NULL)
+ {
+ if ((kn = (kstat_named_t *)kstat_data_lookup(ksAdapter, (char *)"obytes")) == NULL)
+ {
+ LogRel(("kstat_data_lookup(obytes) -> %d\n", errno));
+ return VERR_INTERNAL_ERROR;
+ }
+#if ARCH_BITS == 32
+ if (g_fNotReported)
+ {
+ g_fNotReported = false;
+ LogRel(("Failed to locate obytes64, falling back to 32-bit counters...\n"));
+ }
+ *tx = wrapCorrection(kn->value.ul, *tx, "obytes");
+#else
+ AssertCompile(sizeof(kn->value.ul) == sizeof(uint64_t));
+ *tx = wrapDetection(kn->value.ul, *tx, "obytes");
+#endif
+ }
+ else
+ *tx = wrapDetection(kn->value.ull, *tx, "obytes64");
+ return VINF_SUCCESS;
+}
+
+int CollectorSolaris::getRawHostDiskLoad(const char *name, uint64_t *disk_ms, uint64_t *total_ms)
+{
+ int rc = VINF_SUCCESS;
+ AssertReturn(strlen(name) < KSTAT_STRLEN, VERR_INVALID_PARAMETER);
+ LogFlowThisFunc(("n=%s\n", name));
+ kstat_t *ksDisk = kstat_lookup(mKC, NULL, -1, (char *)name);
+ if (ksDisk != 0)
+ {
+ if (kstat_read(mKC, ksDisk, 0) == -1)
+ {
+ LogRel(("kstat_read(%s) -> %d\n", name, errno));
+ rc = VERR_INTERNAL_ERROR;
+ }
+ else
+ {
+ kstat_io_t *ksIo = KSTAT_IO_PTR(ksDisk);
+ /*
+ * We do not care for wrap possibility here, although we may
+ * reconsider in about 300 years (9223372036854775807 ns).
+ */
+ *disk_ms = ksIo->rtime / 1000000;
+ *total_ms = ksDisk->ks_snaptime / 1000000;
+ }
+ }
+ else
+ {
+ LogRel(("kstat_lookup(%s) -> %d\n", name, errno));
+ rc = VERR_INTERNAL_ERROR;
+ }
+
+ return rc;
+}
+
+uint64_t CollectorSolaris::getZfsTotal(uint64_t cbTotal, const char *szFsType, const char *szFsName)
+{
+ if (strcmp(szFsType, "zfs"))
+ return cbTotal;
+ FsMap::iterator it = mFsMap.find(szFsName);
+ if (it == mFsMap.end())
+ return cbTotal;
+
+ char *pszDataset = strdup(it->second.c_str());
+ char *pszEnd = pszDataset + strlen(pszDataset);
+ uint64_t uAvail = 0;
+ while (pszEnd)
+ {
+ zfs_handle_t *hDataset;
+
+ *pszEnd = 0;
+ hDataset = mZfsOpen(mZfsLib, pszDataset, ZFS_TYPE_DATASET);
+ if (!hDataset)
+ break;
+
+ if (uAvail == 0)
+ {
+ uAvail = mZfsPropGetInt(hDataset, ZFS_PROP_REFQUOTA);
+ if (uAvail == 0)
+ uAvail = UINT64_MAX;
+ }
+
+ uint64_t uQuota = mZfsPropGetInt(hDataset, ZFS_PROP_QUOTA);
+ if (uQuota && uAvail > uQuota)
+ uAvail = uQuota;
+
+ pszEnd = strrchr(pszDataset, '/');
+ if (!pszEnd)
+ {
+ uint64_t uPoolSize = mZfsPropGetInt(hDataset, ZFS_PROP_USED) +
+ mZfsPropGetInt(hDataset, ZFS_PROP_AVAILABLE);
+ if (uAvail > uPoolSize)
+ uAvail = uPoolSize;
+ }
+ mZfsClose(hDataset);
+ }
+ free(pszDataset);
+
+ return uAvail ? uAvail : cbTotal;
+}
+
+int CollectorSolaris::getHostFilesystemUsage(const char *path, ULONG *total, ULONG *used, ULONG *available)
+{
+ struct statvfs64 stats;
+
+ if (statvfs64(path, &stats) == -1)
+ {
+ LogRel(("Failed to collect %s filesystem usage: errno=%d.\n", path, errno));
+ return VERR_ACCESS_DENIED;
+ }
+ uint64_t cbBlock = stats.f_frsize ? stats.f_frsize : stats.f_bsize;
+ *total = (ULONG)(getZfsTotal(cbBlock * stats.f_blocks, stats.f_basetype, path) / _1M);
+ LogFlowThisFunc(("f_blocks=%llu.\n", stats.f_blocks));
+ *used = (ULONG)(cbBlock * (stats.f_blocks - stats.f_bfree) / _1M);
+ *available = (ULONG)(cbBlock * stats.f_bavail / _1M);
+
+ return VINF_SUCCESS;
+}
+
+int CollectorSolaris::getHostDiskSize(const char *name, uint64_t *size)
+{
+ int rc = VINF_SUCCESS;
+ AssertReturn(strlen(name) + 5 < KSTAT_STRLEN, VERR_INVALID_PARAMETER);
+ LogFlowThisFunc(("n=%s\n", name));
+ char szName[KSTAT_STRLEN];
+ strcpy(szName, name);
+ strcat(szName, ",err");
+ kstat_t *ksDisk = kstat_lookup(mKC, NULL, -1, szName);
+ if (ksDisk != 0)
+ {
+ if (kstat_read(mKC, ksDisk, 0) == -1)
+ {
+ LogRel(("kstat_read(%s) -> %d\n", name, errno));
+ rc = VERR_INTERNAL_ERROR;
+ }
+ else
+ {
+ kstat_named_t *kn;
+ if ((kn = (kstat_named_t *)kstat_data_lookup(ksDisk, (char *)"Size")) == 0)
+ {
+ LogRel(("kstat_data_lookup(rbytes) -> %d, name=%s\n", errno, name));
+ return VERR_INTERNAL_ERROR;
+ }
+ *size = kn->value.ull;
+ }
+ }
+ else
+ {
+ LogRel(("kstat_lookup(%s) -> %d\n", szName, errno));
+ rc = VERR_INTERNAL_ERROR;
+ }
+
+
+ return rc;
+}
+
+RTCString CollectorSolaris::physToInstName(const char *pcszPhysName)
+{
+ FILE *fp = fopen("/etc/path_to_inst", "r");
+ if (!fp)
+ return RTCString();
+
+ RTCString strInstName;
+ size_t cbName = strlen(pcszPhysName);
+ char szBuf[RTPATH_MAX];
+ while (fgets(szBuf, sizeof(szBuf), fp))
+ {
+ if (szBuf[0] == '"' && strncmp(szBuf + 1, pcszPhysName, cbName) == 0)
+ {
+ char *pszDriver, *pszInstance;
+ pszDriver = strrchr(szBuf, '"');
+ if (pszDriver)
+ {
+ *pszDriver = '\0';
+ pszDriver = strrchr(szBuf, '"');
+ if (pszDriver)
+ {
+ *pszDriver++ = '\0';
+ pszInstance = strrchr(szBuf, ' ');
+ if (pszInstance)
+ {
+ *pszInstance = '\0';
+ pszInstance = strrchr(szBuf, ' ');
+ if (pszInstance)
+ {
+ *pszInstance++ = '\0';
+ strInstName = pszDriver;
+ strInstName += pszInstance;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ fclose(fp);
+
+ return strInstName;
+}
+
+RTCString CollectorSolaris::pathToInstName(const char *pcszDevPathName)
+{
+ char szLink[RTPATH_MAX];
+ if (readlink(pcszDevPathName, szLink, sizeof(szLink)) != -1)
+ {
+ char *pszStart, *pszEnd;
+ pszStart = strstr(szLink, "/devices/");
+ pszEnd = strrchr(szLink, ':');
+ if (pszStart && pszEnd)
+ {
+ pszStart += 8; // Skip "/devices"
+ *pszEnd = '\0'; // Trim partition
+ return physToInstName(pszStart);
+ }
+ }
+
+ return RTCString(pcszDevPathName);
+}
+
+int CollectorSolaris::getDiskListByFs(const char *name, DiskList& listUsage, DiskList& listLoad)
+{
+ FsMap::iterator it = mFsMap.find(name);
+ if (it == mFsMap.end())
+ return VERR_INVALID_PARAMETER;
+
+ RTCString strName = it->second.substr(0, it->second.find("/"));
+ if (mZpoolOpen && mZpoolClose && mZpoolGetConfig && !strName.isEmpty())
+ {
+ zpool_handle_t *zh = mZpoolOpen(mZfsLib, strName.c_str());
+ if (zh)
+ {
+ unsigned int cChildren = 0;
+ nvlist_t **nvChildren = NULL;
+ nvlist_t *nvRoot = NULL;
+ nvlist_t *nvConfig = mZpoolGetConfig(zh, NULL);
+ if ( !nvlist_lookup_nvlist(nvConfig, ZPOOL_CONFIG_VDEV_TREE, &nvRoot)
+ && !nvlist_lookup_nvlist_array(nvRoot, ZPOOL_CONFIG_CHILDREN, &nvChildren, &cChildren))
+ {
+ for (unsigned int i = 0; i < cChildren; ++i)
+ {
+ uint64_t fHole = 0;
+ uint64_t fLog = 0;
+
+ nvlist_lookup_uint64(nvChildren[i], ZPOOL_CONFIG_IS_HOLE, &fHole);
+ nvlist_lookup_uint64(nvChildren[i], ZPOOL_CONFIG_IS_LOG, &fLog);
+
+ if (!fHole && !fLog)
+ {
+ char *pszChildName = mZpoolVdevName(mZfsLib, zh, nvChildren[i], _B_FALSE);
+ Assert(pszChildName);
+ RTCString strDevPath("/dev/dsk/");
+ strDevPath += pszChildName;
+ char szLink[RTPATH_MAX];
+ if (readlink(strDevPath.c_str(), szLink, sizeof(szLink)) != -1)
+ {
+ char *pszStart, *pszEnd;
+ pszStart = strstr(szLink, "/devices/");
+ pszEnd = strrchr(szLink, ':');
+ if (pszStart && pszEnd)
+ {
+ pszStart += 8; // Skip "/devices"
+ *pszEnd = '\0'; // Trim partition
+ listUsage.push_back(physToInstName(pszStart));
+ }
+ }
+ free(pszChildName);
+ }
+ }
+ }
+ mZpoolClose(zh);
+ }
+ }
+ else
+ listUsage.push_back(pathToInstName(it->second.c_str()));
+ listLoad = listUsage;
+ return VINF_SUCCESS;
+}
+
+void CollectorSolaris::updateFilesystemMap(void)
+{
+ FILE *fp = fopen("/etc/mnttab", "r");
+ if (fp)
+ {
+ struct mnttab Entry;
+ int rc = 0;
+ resetmnttab(fp);
+ while ((rc = getmntent(fp, &Entry)) == 0)
+ mFsMap[Entry.mnt_mountp] = Entry.mnt_special;
+ fclose(fp);
+ if (rc != -1)
+ LogRel(("Error while reading mnttab: %d\n", rc));
+ }
+}
+
+}
diff --git a/src/VBox/Main/src-server/solaris/USBProxyBackendSolaris.cpp b/src/VBox/Main/src-server/solaris/USBProxyBackendSolaris.cpp
new file mode 100644
index 00000000..6631a04f
--- /dev/null
+++ b/src/VBox/Main/src-server/solaris/USBProxyBackendSolaris.cpp
@@ -0,0 +1,496 @@
+/* $Id: USBProxyBackendSolaris.cpp $ */
+/** @file
+ * VirtualBox USB Proxy Service, Solaris Specialization.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_USBPROXYBACKEND
+#include "USBProxyBackend.h"
+#include "LoggingNew.h"
+
+#include <VBox/usb.h>
+#include <VBox/usblib.h>
+#include <iprt/errcore.h>
+#include <iprt/semaphore.h>
+#include <iprt/path.h>
+
+#include <sys/usb/usba.h>
+#include <syslog.h>
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int solarisWalkDeviceNode(di_node_t Node, void *pvArg);
+static void solarisFreeUSBDevice(PUSBDEVICE pDevice);
+static USBDEVICESTATE solarisDetermineUSBDeviceState(PUSBDEVICE pDevice, di_node_t Node);
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct USBDEVICELIST
+{
+ PUSBDEVICE pHead;
+ PUSBDEVICE pTail;
+} USBDEVICELIST;
+typedef USBDEVICELIST *PUSBDEVICELIST;
+
+
+/**
+ * Initialize data members.
+ */
+USBProxyBackendSolaris::USBProxyBackendSolaris()
+ : USBProxyBackend(), mNotifyEventSem(NIL_RTSEMEVENT), mUSBLibInitialized(false)
+{
+ LogFlowThisFunc(("\n"));
+}
+
+USBProxyBackendSolaris::~USBProxyBackendSolaris()
+{
+}
+
+/**
+ * Initializes the object (called right after construction).
+ *
+ * @returns VBox status code.
+ */
+int USBProxyBackendSolaris::init(USBProxyService *aUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings)
+{
+ USBProxyBackend::init(aUsbProxyService, strId, strAddress, fLoadingSettings);
+
+ unconst(m_strBackend) = Utf8Str("host");
+
+ /*
+ * Create semaphore.
+ */
+ int rc = RTSemEventCreate(&mNotifyEventSem);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Initialize the USB library.
+ */
+ rc = USBLibInit();
+ if (RT_FAILURE(rc))
+ {
+ /* mNotifyEventSem will be destroyed in uninit */
+ return rc;
+ }
+
+ mUSBLibInitialized = true;
+
+ /*
+ * Start the poller thread.
+ */
+ start();
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Stop all service threads and free the device chain.
+ */
+void USBProxyBackendSolaris::uninit()
+{
+ LogFlowThisFunc(("destruct\n"));
+
+ /*
+ * Stop the service.
+ */
+ if (isActive())
+ stop();
+
+ /*
+ * Terminate the USB library
+ */
+ if (mUSBLibInitialized)
+ {
+ USBLibTerm();
+ mUSBLibInitialized = false;
+ }
+
+ if (mNotifyEventSem != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(mNotifyEventSem);
+ mNotifyEventSem = NIL_RTSEMEVENT;
+ }
+}
+
+
+void *USBProxyBackendSolaris::insertFilter(PCUSBFILTER aFilter)
+{
+ return USBLibAddFilter(aFilter);
+}
+
+
+void USBProxyBackendSolaris::removeFilter(void *pvID)
+{
+ USBLibRemoveFilter(pvID);
+}
+
+
+int USBProxyBackendSolaris::wait(RTMSINTERVAL aMillies)
+{
+ return RTSemEventWait(mNotifyEventSem, aMillies < 1000 ? 1000 : RT_MIN(aMillies, 5000));
+}
+
+
+int USBProxyBackendSolaris::interruptWait(void)
+{
+ return RTSemEventSignal(mNotifyEventSem);
+}
+
+
+PUSBDEVICE USBProxyBackendSolaris::getDevices(void)
+{
+ USBDEVICELIST DevList;
+ DevList.pHead = NULL;
+ DevList.pTail = NULL;
+ di_node_t RootNode = di_init("/", DINFOCPYALL);
+ if (RootNode != DI_NODE_NIL)
+ di_walk_node(RootNode, DI_WALK_CLDFIRST, &DevList, solarisWalkDeviceNode);
+
+ di_fini(RootNode);
+ return DevList.pHead;
+}
+
+
+static int solarisWalkDeviceNode(di_node_t Node, void *pvArg)
+{
+ PUSBDEVICELIST pList = (PUSBDEVICELIST)pvArg;
+ AssertPtrReturn(pList, DI_WALK_TERMINATE);
+
+ /*
+ * Check if it's a USB device in the first place.
+ */
+ bool fUSBDevice = false;
+ char *pszCompatNames = NULL;
+ int cCompatNames = di_compatible_names(Node, &pszCompatNames);
+ for (int i = 0; i < cCompatNames; i++, pszCompatNames += strlen(pszCompatNames) + 1)
+ if (!strncmp(pszCompatNames, RT_STR_TUPLE("usb")))
+ {
+ fUSBDevice = true;
+ break;
+ }
+
+ if (!fUSBDevice)
+ return DI_WALK_CONTINUE;
+
+ /*
+ * Check if it's a device node or interface.
+ */
+ int *pInt = NULL;
+ char *pStr = NULL;
+ int rc = DI_WALK_CONTINUE;
+ if (di_prop_lookup_ints(DDI_DEV_T_ANY, Node, "interface", &pInt) < 0)
+ {
+ /* It's a device node. */
+ char *pszDevicePath = di_devfs_path(Node);
+ PUSBDEVICE pCur = (PUSBDEVICE)RTMemAllocZ(sizeof(*pCur));
+ if (!pCur)
+ {
+ LogRel(("USBService: failed to allocate %d bytes for PUSBDEVICE.\n", sizeof(*pCur)));
+ return DI_WALK_TERMINATE;
+ }
+
+ bool fValidDevice = false;
+ do
+ {
+ AssertBreak(pszDevicePath);
+
+ char *pszDriverName = di_driver_name(Node);
+
+ /*
+ * Skip hubs
+ */
+ if ( pszDriverName
+ && !strcmp(pszDriverName, "hubd"))
+ {
+ break;
+ }
+
+ /*
+ * Mandatory.
+ * snv_85 and above have usb-dev-descriptor node properties, but older one's do not.
+ * So if we cannot obtain the entire device descriptor, we try falling back to the
+ * individual properties (those must not fail, if it does we drop the device).
+ */
+ uchar_t *pDevData = NULL;
+ int cbProp = di_prop_lookup_bytes(DDI_DEV_T_ANY, Node, "usb-dev-descriptor", &pDevData);
+ if ( cbProp > 0
+ && pDevData)
+ {
+ usb_dev_descr_t *pDeviceDescriptor = (usb_dev_descr_t *)pDevData;
+ pCur->bDeviceClass = pDeviceDescriptor->bDeviceClass;
+ pCur->bDeviceSubClass = pDeviceDescriptor->bDeviceSubClass;
+ pCur->bDeviceProtocol = pDeviceDescriptor->bDeviceProtocol;
+ pCur->idVendor = pDeviceDescriptor->idVendor;
+ pCur->idProduct = pDeviceDescriptor->idProduct;
+ pCur->bcdDevice = pDeviceDescriptor->bcdDevice;
+ pCur->bcdUSB = pDeviceDescriptor->bcdUSB;
+ pCur->bNumConfigurations = pDeviceDescriptor->bNumConfigurations;
+ pCur->fPartialDescriptor = false;
+ }
+ else
+ {
+ AssertBreak(di_prop_lookup_ints(DDI_DEV_T_ANY, Node, "usb-vendor-id", &pInt) > 0);
+ pCur->idVendor = (uint16_t)*pInt;
+
+ AssertBreak(di_prop_lookup_ints(DDI_DEV_T_ANY, Node, "usb-product-id", &pInt) > 0);
+ pCur->idProduct = (uint16_t)*pInt;
+
+ AssertBreak(di_prop_lookup_ints(DDI_DEV_T_ANY, Node, "usb-revision-id", &pInt) > 0);
+ pCur->bcdDevice = (uint16_t)*pInt;
+
+ AssertBreak(di_prop_lookup_ints(DDI_DEV_T_ANY, Node, "usb-release", &pInt) > 0);
+ pCur->bcdUSB = (uint16_t)*pInt;
+
+ pCur->fPartialDescriptor = true;
+ }
+
+ char *pszPortAddr = di_bus_addr(Node);
+ if (pszPortAddr)
+ pCur->bPort = RTStrToUInt8(pszPortAddr); /* Bus & Port are mixed up (kernel driver/userland) */
+ else
+ pCur->bPort = 0;
+
+ char szBuf[PATH_MAX + 48];
+ RTStrPrintf(szBuf, sizeof(szBuf), "%#x:%#x:%d:%s", pCur->idVendor, pCur->idProduct, pCur->bcdDevice, pszDevicePath);
+ pCur->pszAddress = RTStrDup(szBuf);
+ AssertBreak(pCur->pszAddress);
+
+ pCur->pszDevicePath = RTStrDup(pszDevicePath);
+ AssertBreak(pCur->pszDevicePath);
+
+ pCur->pszBackend = RTStrDup("host");
+ AssertBreak(pCur->pszBackend);
+
+ /*
+ * Optional (some devices don't have all these)
+ */
+ char *pszCopy;
+ if (di_prop_lookup_strings(DDI_DEV_T_ANY, Node, "usb-product-name", &pStr) > 0)
+ {
+ pCur->pszProduct = pszCopy = RTStrDup(pStr);
+ USBLibPurgeEncoding(pszCopy);
+ }
+
+ if (di_prop_lookup_strings(DDI_DEV_T_ANY, Node, "usb-vendor-name", &pStr) > 0)
+ {
+ pCur->pszManufacturer = pszCopy = RTStrDup(pStr);
+ USBLibPurgeEncoding(pszCopy);
+ }
+
+ if (di_prop_lookup_strings(DDI_DEV_T_ANY, Node, "usb-serialno", &pStr) > 0)
+ {
+ pCur->pszSerialNumber = pszCopy = RTStrDup(pStr);
+ USBLibPurgeEncoding(pszCopy);
+ }
+
+ if (pCur->bcdUSB == 0x300)
+ pCur->enmSpeed = USBDEVICESPEED_SUPER;
+ else if (di_prop_lookup_ints(DDI_DEV_T_ANY, Node, "low-speed", &pInt) >= 0)
+ pCur->enmSpeed = USBDEVICESPEED_LOW;
+ else if (di_prop_lookup_ints(DDI_DEV_T_ANY, Node, "high-speed", &pInt) >= 0)
+ pCur->enmSpeed = USBDEVICESPEED_HIGH;
+ else
+ pCur->enmSpeed = USBDEVICESPEED_FULL;
+
+ /* Determine state of the USB device. */
+ pCur->enmState = solarisDetermineUSBDeviceState(pCur, Node);
+
+ /*
+ * Valid device, add it to the list.
+ */
+ fValidDevice = true;
+ pCur->pPrev = pList->pTail;
+ if (pList->pTail)
+ pList->pTail = pList->pTail->pNext = pCur;
+ else
+ pList->pTail = pList->pHead = pCur;
+
+ rc = DI_WALK_CONTINUE;
+ } while (0);
+
+ di_devfs_path_free(pszDevicePath);
+ if (!fValidDevice)
+ solarisFreeUSBDevice(pCur);
+ }
+ return rc;
+}
+
+
+static USBDEVICESTATE solarisDetermineUSBDeviceState(PUSBDEVICE pDevice, di_node_t Node)
+{
+ char *pszDriverName = di_driver_name(Node);
+
+ /* Not possible unless a user explicitly unbinds the default driver. */
+ if (!pszDriverName)
+ return USBDEVICESTATE_UNUSED;
+
+ if (!strncmp(pszDriverName, RT_STR_TUPLE(VBOXUSB_DRIVER_NAME)))
+ return USBDEVICESTATE_HELD_BY_PROXY;
+
+ NOREF(pDevice);
+ return USBDEVICESTATE_USED_BY_HOST_CAPTURABLE;
+}
+
+
+int USBProxyBackendSolaris::captureDevice(HostUSBDevice *aDevice)
+{
+ /*
+ * Check preconditions.
+ */
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_Capturing);
+ AssertReturn(aDevice->i_getUsbData(), VERR_INVALID_POINTER);
+
+ /*
+ * Create a one-shot capture filter for the device and reset the device.
+ */
+ USBFILTER Filter;
+ USBFilterInit(&Filter, USBFILTERTYPE_ONESHOT_CAPTURE);
+ initFilterFromDevice(&Filter, aDevice);
+
+ void *pvId = USBLibAddFilter(&Filter);
+ if (!pvId)
+ {
+ LogRel(("USBService: failed to add filter\n"));
+ return VERR_GENERAL_FAILURE;
+ }
+
+ PUSBDEVICE pDev = aDevice->i_getUsbData();
+ int rc = USBLibResetDevice(pDev->pszDevicePath, true);
+ if (RT_SUCCESS(rc))
+ aDevice->i_setBackendUserData(pvId);
+ else
+ {
+ USBLibRemoveFilter(pvId);
+ pvId = NULL;
+ }
+ LogFlowThisFunc(("returns %Rrc pvId=%p\n", rc, pvId));
+ return rc;
+}
+
+
+void USBProxyBackendSolaris::captureDeviceCompleted(HostUSBDevice *aDevice, bool aSuccess)
+{
+ AssertReturnVoid(aDevice->isWriteLockOnCurrentThread());
+ /*
+ * Remove the one-shot filter if necessary.
+ */
+ LogFlowThisFunc(("aDevice=%s aSuccess=%RTbool mOneShotId=%p\n", aDevice->i_getName().c_str(), aSuccess, aDevice->i_getBackendUserData()));
+ if (!aSuccess && aDevice->i_getBackendUserData())
+ USBLibRemoveFilter(aDevice->i_getBackendUserData());
+ aDevice->i_setBackendUserData(NULL);
+ USBProxyBackend::captureDeviceCompleted(aDevice, aSuccess);
+}
+
+
+int USBProxyBackendSolaris::releaseDevice(HostUSBDevice *aDevice)
+{
+ /*
+ * Check preconditions.
+ */
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_ReleasingToHost);
+ AssertReturn(aDevice->i_getUsbData(), VERR_INVALID_POINTER);
+
+ /*
+ * Create a one-shot ignore filter for the device and reset it.
+ */
+ USBFILTER Filter;
+ USBFilterInit(&Filter, USBFILTERTYPE_ONESHOT_IGNORE);
+ initFilterFromDevice(&Filter, aDevice);
+
+ void *pvId = USBLibAddFilter(&Filter);
+ if (!pvId)
+ {
+ LogRel(("USBService: Adding ignore filter failed!\n"));
+ return VERR_GENERAL_FAILURE;
+ }
+
+ PUSBDEVICE pDev = aDevice->i_getUsbData();
+ int rc = USBLibResetDevice(pDev->pszDevicePath, true /* Re-attach */);
+ if (RT_SUCCESS(rc))
+ aDevice->i_setBackendUserData(pvId);
+ else
+ {
+ USBLibRemoveFilter(pvId);
+ pvId = NULL;
+ }
+ LogFlowThisFunc(("returns %Rrc pvId=%p\n", rc, pvId));
+ return rc;
+}
+
+
+void USBProxyBackendSolaris::releaseDeviceCompleted(HostUSBDevice *aDevice, bool aSuccess)
+{
+ AssertReturnVoid(aDevice->isWriteLockOnCurrentThread());
+ /*
+ * Remove the one-shot filter if necessary.
+ */
+ LogFlowThisFunc(("aDevice=%s aSuccess=%RTbool mOneShotId=%p\n", aDevice->i_getName().c_str(), aSuccess, aDevice->i_getBackendUserData()));
+ if (!aSuccess && aDevice->i_getBackendUserData())
+ USBLibRemoveFilter(aDevice->i_getBackendUserData());
+ aDevice->i_setBackendUserData(NULL);
+ USBProxyBackend::releaseDeviceCompleted(aDevice, aSuccess);
+}
+
+
+/**
+ * Returns whether devices reported by this backend go through a de/re-attach
+ * and device re-enumeration cycle when they are captured or released.
+ */
+bool USBProxyBackendSolaris::i_isDevReEnumerationRequired()
+{
+ return true;
+}
+
+
+/**
+ * Wrapper called by walkDeviceNode.
+ *
+ * @param pDevice The USB device to free.
+ */
+void solarisFreeUSBDevice(PUSBDEVICE pDevice)
+{
+ USBProxyBackend::freeDevice(pDevice);
+}
diff --git a/src/VBox/Main/src-server/usb.ids b/src/VBox/Main/src-server/usb.ids
new file mode 100644
index 00000000..292affb2
--- /dev/null
+++ b/src/VBox/Main/src-server/usb.ids
@@ -0,0 +1,20663 @@
+#
+# List of USB ID's
+#
+# Maintained by Stephen J. Gowdy <linux.usb.ids@gmail.com>
+# If you have any new entries, please submit them via
+# http://www.linux-usb.org/usb-ids.html
+# or send entries as patches (diff -u old new) in the
+# body of your email (a bot will attempt to deal with it).
+# The latest version can be obtained from
+# http://www.linux-usb.org/usb.ids
+#
+# Version: 2017.02.12
+# Date: 2017-02-12 20:34:05
+#
+
+# Vendors, devices and interfaces. Please keep sorted.
+
+# Syntax:
+# vendor vendor_name
+# device device_name <-- single tab
+# interface interface_name <-- two tabs
+
+0001 Fry's Electronics
+ 7778 Counterfeit flash drive [Kingston]
+0002 Ingram
+0003 Club Mac
+0004 Nebraska Furniture Mart
+0011 Unknown
+ 7788 counterfeit flash drive
+0053 Planex
+ 5301 GW-US54ZGL 802.11bg
+0079 DragonRise Inc.
+ 0006 PC TWIN SHOCK Gamepad
+ 0011 Gamepad
+0105 Trust International B.V.
+ 145f NW-3100 802.11b/g 54Mbps Wireless Network Adapter [zd1211]
+0127 IBP
+ 0002 HDM Interface
+ 0127 ibp
+0145 Unknown
+ 0112 Card Reader
+017c MLK
+ 145f Trust Deskset
+0200 TP-Link
+ 0201 MA180 UMTS Modem
+0204 Chipsbank Microelectronics Co., Ltd
+ 6025 CBM2080 / CBM2090 Flash drive controller
+ 6026 CBM1180 Flash drive controller
+0218 Hangzhou Worlde
+ 0301 MIDI Port
+02ad HUMAX Co., Ltd.
+ 138c PVR Mass Storage
+0300 MM300 eBook Reader
+0324 OCZ Technology Inc
+ bc06 OCZ ATV USB 2.0 Flash Drive
+ bc08 OCZ Rally2/ATV USB 2.0 Flash Drive
+0325 OCZ Technology Inc
+ ac02 ATV Turbo / Rally2 Dual Channel USB 2.0 Flash Drive
+0386 LTS
+ 0001 PSX for USB Converter
+03d9 Shenzhen Sinote Tech-Electron Co., Ltd
+ 0499 SE340D PC Remote Control
+03da Bernd Walter Computer Technology
+ 0002 HD44780 LCD interface
+03e8 EndPoints, Inc.
+ 0004 SE401 Webcam
+ 0008 101 Ethernet [klsi]
+ 0015 ATAPI Enclosure
+ 2123 SiPix StyleCam Deluxe
+ 8004 Aox 99001
+03e9 Thesys Microelectronics
+03ea Data Broadcasting Corp.
+03eb Atmel Corp.
+ 0902 4-Port Hub
+ 2002 Mass Storage Device
+ 2015 at90usbkey sample firmware (HID keyboard)
+ 2018 at90usbkey sample firmware (CDC ACM)
+ 2019 stk525 sample firmware (microphone)
+ 201c at90usbkey sample firmware (HID mouse)
+ 201d at90usbkey sample firmware (HID generic)
+ 2022 at90usbkey sample firmware (composite device)
+ 2040 LUFA Test PID
+ 2041 LUFA Mouse Demo Application
+ 2042 LUFA Keyboard Demo Application
+ 2043 LUFA Joystick Demo Application
+ 2044 LUFA CDC Demo Application
+ 2045 LUFA Mass Storage Demo Application
+ 2046 LUFA Audio Output Demo Application
+ 2047 LUFA Audio Input Demo Application
+ 2048 LUFA MIDI Demo Application
+ 2049 Stripe Snoop Magnetic Stripe Reader
+ 204a LUFA CDC Class Bootloader
+ 204b LUFA USB to Serial Adapter Project
+ 204c LUFA RNDIS Demo Application
+ 204d LUFA Combined Mouse and Keyboard Demo Application
+ 204e LUFA Dual CDC Demo Application
+ 204f LUFA Generic HID Demo Application
+ 2060 Benito Programmer Project
+ 2061 LUFA Combined Mass Storage and Keyboard Demo Application
+ 2062 LUFA Combined CDC and Mouse Demo Application
+ 2063 LUFA Datalogger Device
+ 2064 Interfaceless Control-Only LUFA Devices
+ 2065 LUFA Test and Measurement Demo Application
+ 2066 LUFA Multiple Report HID Demo
+ 2068 LUFA Virtual Serial/Mass Storage Demo
+ 2069 LUFA Webserver Project
+ 2103 JTAG ICE mkII
+ 2104 AVR ISP mkII
+ 2105 AVRONE!
+ 2106 STK600 development board
+ 2107 AVR Dragon
+ 2109 STK541 ZigBee Development Board
+ 210d XPLAIN evaluation kit (CDC ACM)
+ 2110 AVR JTAGICE3 Debugger and Programmer
+ 2111 Xplained Pro board debugger and programmer
+ 2122 XMEGA-A1 Explained evaluation kit
+ 2140 AVR JTAGICE3 (v3.x) Debugger and Programmer
+ 2141 ICE debugger
+ 2145 ATMEGA328P-XMINI (CDC ACM)
+ 2310 EVK11xx evaluation board
+ 2404 The Micro
+ 2fe4 ATxmega32A4U DFU bootloader
+ 2fe6 Cactus V6 (DFU)
+ 2fea Cactus RF60 (DFU)
+ 2fee atmega8u2 DFU bootloader
+ 2fef atmega16u2 DFU bootloader
+ 2ff0 atmega32u2 DFU bootloader
+ 2ff1 at32uc3a3 DFU bootloader
+ 2ff3 atmega16u4 DFU bootloader
+ 2ff4 atmega32u4 DFU bootloader
+ 2ff6 at32uc3b0/1 DFU bootloader
+ 2ff7 at90usb82 DFU bootloader
+ 2ff8 at32uc3a0/1 DFU bootloader
+ 2ff9 at90usb646/647 DFU bootloader
+ 2ffa at90usb162 DFU bootloader
+ 2ffb at90usb AVR DFU bootloader
+ 2ffd at89c5130/c5131 DFU bootloader
+ 2fff at89c5132/c51snd1c DFU bootloader
+ 3301 at43301 4-Port Hub
+ 3312 4-Port Hub
+ 4102 AirVast W-Buddie WN210
+ 5601 at76c510 Prism-II 802.11b Access Point
+ 5603 Cisco 7920 WiFi IP Phone
+ 6119 AT91SAM CDC Demo Application
+ 6124 at91sam SAMBA bootloader
+ 6127 AT91SAM HID Keyboard Demo Application
+ 6129 AT91SAM Mass Storage Demo Application
+ 6200 AT91SAM HID Mouse Demo Application
+ 7603 D-Link DWL-120 802.11b Wireless Adapter [Atmel at76c503a]
+ 7604 at76c503a 802.11b Adapter
+ 7605 at76c503a 802.11b Adapter
+ 7606 at76c505 802.11b Adapter
+ 7611 at76c510 rfmd2948 802.11b Access Point
+ 7613 WL-1130 USB
+ 7614 AT76c505a Wireless Adapter
+ 7615 AT76C505AMX Wireless Adapter
+ 7617 AT76C505AS Wireless Adapter
+ 7800 Mini Album
+ ff07 Tux Droid fish dongle
+03ec Iwatsu America, Inc.
+03ed Mitel Corp.
+03ee Mitsumi
+ 0000 CD-R/RW Drive
+ 2501 eHome Infrared Receiver
+ 2502 eHome Infrared Receiver
+ 5609 Japanese Keyboard
+ 641f WIF-0402C Bluetooth Adapter
+ 6438 Bluetooth Device
+ 6440 WML-C52APR Bluetooth Adapter
+ 6901 SmartDisk FDD
+ 6902 Floppy Disk Drive
+ 7500 CD-R/RW
+ ffff Dongle with BlueCore in DFU mode
+03f0 Hewlett-Packard
+ 0004 DeskJet 895c
+ 0011 OfficeJet G55
+ 0012 DeskJet 1125C Printer Port
+ 0024 KU-0316 Keyboard
+ 002a LaserJet P1102
+ 0101 ScanJet 4100c
+ 0102 PhotoSmart S20
+ 0104 DeskJet 880c/970c
+ 0105 ScanJet 4200c
+ 0107 CD-Writer Plus
+ 010c Multimedia Keyboard Hub
+ 0111 G55xi Printer/Scanner/Copier
+ 0117 LaserJet 3200
+ 011c hn210w 802.11b Adapter
+ 011d Bluetooth 1.2 Interface [Broadcom BCM2035]
+ 0121 HP 39g+ [F2224A], 39gs [F2223A], 40gs [F2225A], 48gII [F2226A], 49g+ [F2228A], 50g [F2229A, NW240AA]
+ 0122 HID Internet Keyboard
+ 0125 DAT72 Tape
+ 0139 Barcode Scanner 4430
+ 0201 ScanJet 6200c
+ 0202 PhotoSmart S20
+ 0204 DeskJet 815c
+ 0205 ScanJet 3300c
+ 0207 CD-Writer Plus 8200e
+ 020c Multimedia Keyboard
+ 0211 OfficeJet G85
+ 0212 DeskJet 1220C
+ 0217 LaserJet 2200
+ 0218 APOLLO P2500/2600
+ 0221 StreamSmart 400 [F2235AA]
+ 022a Laserjet CP1525nw
+ 0241 Link-5 micro dongle
+ 0304 DeskJet 810c/812c
+ 0305 ScanJet 4300c
+ 0307 CD-Writer+ CD-4e
+ 0311 OfficeJet G85xi
+ 0312 Color Inkjet CP1700
+ 0314 designjet 30/130 series
+ 0317 LaserJet 1200
+ 0324 SK-2885 keyboard
+ 034a Elite Keyboard
+ 0401 ScanJet 5200c
+ 0404 DeskJet 830c/832c
+ 0405 ScanJet 3400cse
+ 0411 OfficeJet G95
+ 0412 Printing Support
+ 0417 LaserJet 1200 series
+ 0423 HS-COMBO Cardreader
+ 042a LaserJet M1132 MFP
+ 0441 Prime [NW280AA, G8X92AA]
+ 0504 DeskJet 885c
+ 0505 ScanJet 2100c
+ 0507 DVD+RW
+ 050c 5219 Wireless Keyboard
+ 0511 OfficeJet K60
+ 0512 DeckJet 450
+ 0517 LaserJet 1000
+ 051d Bluetooth Interface
+ 0601 ScanJet 6300c
+ 0604 DeskJet 840c
+ 0605 ScanJet 2200c
+ 0611 OfficeJet K60xi
+ 0612 business inkjet 3000
+ 0624 Bluetooth Dongle
+ 0701 ScanJet 5300c/5370c
+ 0704 DeskJet 825c
+ 0705 ScanJet 4400c
+ 070c Personal Media Drive
+ 0711 OfficeJet K80
+ 0712 DeskJet 1180c
+ 0714 Printing Support
+ 0741 Prime Wireless Kit [FOK65AA]
+ 0801 ScanJet 7400c
+ 0804 DeskJet 816c
+ 0805 HP4470C
+ 0811 OfficeJet K80xi
+ 0817 LaserJet 3300
+ 0901 ScanJet 2300c
+ 0904 DeskJet 845c
+ 0912 Printing Support
+ 0917 LaserJet 3330
+ 0924 Modular Smartcard Keyboard
+ 094a Optical Mouse [672662-001]
+ 0a01 ScanJet 2400c
+ 0a17 color LaserJet 3700
+ 0b01 ScanJet 82x0C
+ 0b0c Wireless Keyboard and Optical Mouse receiver
+ 0b17 LaserJet 2300d
+ 0c17 LaserJet 1010
+ 0c24 Bluetooth Dongle
+ 0d12 OfficeJet 9100 series
+ 0d17 LaserJet 1012
+ 0d4a SK-2025 Keyboard
+ 0e17 LaserJet 1015
+ 0f0c Wireless Keyboard and Optical Mouse receiver
+ 0f11 OfficeJet V40
+ 0f12 Printing Support
+ 0f17 LaserJet 1150
+ 0f2a LaserJet 400 color M451dn
+ 1001 Photo Scanner 1000
+ 1002 PhotoSmart 140 series
+ 1004 DeskJet 970c/970cse
+ 1005 ScanJet 5400c
+ 1011 OfficeJet V40xi
+ 1016 Jornada 548 / iPAQ HW6515 Pocket PC
+ 1017 LaserJet 1300
+ 1024 Smart Card Keyboard
+ 1027 Virtual keyboard and mouse
+ 102a LaserJet Professional P 1102w
+ 1102 PhotoSmart 240 series
+ 1104 DeskJet 959c
+ 1105 ScanJet 5470c/5490c
+ 1111 OfficeJet v60
+ 1116 Jornada 568 Pocket PC
+ 1117 LaserJet 1300n
+ 1151 PSC-750xi Printer/Scanner/Copier
+ 1198 HID-compliant mouse
+ 1202 PhotoSmart 320 series
+ 1204 DeskJet 930c
+ 1205 ScanJet 4500C/5550C
+ 1211 OfficeJet v60xi
+ 1217 LaserJet 2300L
+ 1227 Virtual CD-ROM
+ 1302 PhotoSmart 370 series
+ 1305 ScanJet 4570c
+ 1311 OfficeJet V30
+ 1312 DeskJet 460
+ 1317 LaserJet 1005
+ 1327 iLO Virtual Hub
+ 134a Optical Mouse
+ 1405 ScanJet 3670
+ 1411 PSC 750
+ 1424 f2105 Monitor Hub
+ 1502 PhotoSmart 420 series
+ 1504 DeskJet 920c
+ 150c Mood Lighting (Microchip Technology Inc.)
+ 1511 PSC 750xi
+ 1512 Printing Support
+ 1517 color LaserJet 3500
+ 1524 Smart Card Keyboard - KR
+ 1539 Mini Magnetic Stripe Reader
+ 1541 Prime [G8X92AA]
+ 1602 PhotoSmart 330 series
+ 1604 DeskJet 940c
+ 1605 ScanJet 5530C PhotoSmart
+ 1611 psc 780
+ 1617 LaserJet 3015
+ 161d Wireless Rechargeable Optical Mouse (HID)
+ 1624 Smart Card Keyboard - JP
+ 1702 PhotoSmart 380 series
+ 1704 DeskJet 948C
+ 1705 ScanJet 5590
+ 1711 psc 780xi
+ 1712 Printing Support
+ 1717 LaserJet 3020
+ 171d Bluetooth 2.0 Interface [Broadcom BCM2045]
+ 1801 Inkjet P-2000U
+ 1802 PhotoSmart 470 series
+ 1804 DeskJet 916C
+ 1805 ScanJet 7650
+ 1811 PSC 720
+ 1812 OfficeJet Pro K550
+ 1817 LaserJet 3030
+ 181d Bluetooth 2.0 Interface
+ 1902 PhotoSmart A430 series
+ 1904 DeskJet 3820
+ 1911 OfficeJet V45
+ 1917 LaserJet 3380
+ 1a02 PhotoSmart A510 series
+ 1a11 OfficeJet 5100 series
+ 1a17 color LaserJet 4650
+ 1b02 PhotoSmart A610 series
+ 1b04 DeskJet 3810
+ 1b05 ScanJet 4850C/4890C
+ 1b07 Premium Starter Webcam
+ 1c02 PhotoSmart A710 series
+ 1c17 Color LaserJet 2550l
+ 1d02 PhotoSmart A310 series
+ 1d17 LaserJet 1320
+ 1d24 Barcode scanner
+ 1e02 PhotoSmart A320 Printer series
+ 1e11 PSC-950
+ 1e17 LaserJet 1160 series
+ 1f02 PhotoSmart A440 Printer series
+ 1f11 PSC 920
+ 1f12 OfficeJet Pro K5300
+ 1f17 color LaserJet 5550
+ 1f1d un2400 Gobi Wireless Modem
+ 2001 Floppy
+ 2002 Hub
+ 2004 DeskJet 640c
+ 2005 ScanJet 3570c
+ 2012 OfficeJet Pro K5400
+ 201d un2400 Gobi Wireless Modem (QDL mode)
+ 2039 Cashdrawer
+ 2102 PhotoSmart 7345
+ 2104 DeskJet 630c
+ 2112 OfficeJet Pro L7500
+ 211d Sierra MC5725 [ev2210]
+ 2202 PhotoSmart 7600 series
+ 2205 ScanJet 3500c
+ 2212 OfficeJet Pro L7600
+ 2217 color LaserJet 9500 MFP
+ 222a LaserJet Pro MFP M125nw
+ 2302 PhotoSmart 7600 series
+ 2304 DeskJet 656c
+ 2305 ScanJet 3970c
+ 2311 OfficeJet d series
+ 2312 OfficeJet Pro L7700
+ 2317 LaserJet 4350
+ 231d Broadcom 2070 Bluetooth Combo
+ 2402 PhotoSmart 7700 series
+ 2404 Deskjet F2280 series
+ 2405 ScanJet 4070 PhotoSmart
+ 2417 LaserJet 4250
+ 241d Gobi 2000 Wireless Modem (QDL mode)
+ 2424 LP1965 19" Monitor Hub
+ 2502 PhotoSmart 7700 series
+ 2504 DeskJet F4200 series
+ 2505 ScanJet 3770
+ 2512 OfficeJet Pro L7300 / Compaq LA2405 series monitor
+ 2514 4-port hub
+ 2517 LaserJet 2410
+ 251d Gobi 2000 Wireless Modem
+ 2524 LP3065 30" Monitor Hub
+ 2602 PhotoSmart A520 series
+ 2605 ScanJet 3800c
+ 2611 OfficeJet 7100 series
+ 2617 Color LaserJet 2820 series
+ 2624 Pole Display (HP522 2 x 20 Line Display)
+ 2702 PhotoSmart A620 series
+ 2704 DeskJet 915
+ 2717 Color LaserJet 2830
+ 2724 Magnetic Stripe Reader IDRA-334133-HP
+ 2805 Scanjet G2710
+ 2811 PSC-2100
+ 2817 Color LaserJet 2840
+ 2902 PhotoSmart A820 series
+ 2911 PSC 2200
+ 2917 LaserJet 2420
+ 2a11 PSC 2150 series
+ 2a17 LaserJet 2430
+ 2a1d Integrated Module with Bluetooth 2.1 Wireless technology
+ 2b11 PSC 2170 series
+ 2b17 LaserJet 1020
+ 2c12 Officejet J4680
+ 2c17 LaserJet 1022
+ 2c24 Logitech M-UAL-96 Mouse
+ 2d05 Scanjet 7000
+ 2d11 OfficeJet 6110
+ 2d17 Printing Support
+ 2e11 PSC 1000
+ 2e17 LaserJet 2600n
+ 2e24 LP2275w Monitor Hub
+ 2f11 PSC 1200
+ 2f17 Color LaserJet 2605dn
+ 2f24 LP2475w Monitor Hub
+ 3002 PhotoSmart P1000
+ 3004 DeskJet 980c
+ 3005 ScanJet 4670v
+ 3011 PSC 1100 series
+ 3017 Printing Support
+ 3102 PhotoSmart P1100 Printer w/ Card Reader
+ 3104 DeskJet 960c
+ 3111 OfficeJet 4100 series
+ 3117 EWS 2605dtn
+ 311d Atheros AR9285 Malbec Bluetooth Adapter
+ 3202 PhotoSmart 1215
+ 3207 4 GB flash drive
+ 3211 OfficeJet 4105 series
+ 3217 LaserJet 3050
+ 3302 PhotoSmart 1218
+ 3304 DeskJet 990c
+ 3307 v125w Stick
+ 3312 OfficeJet J6410
+ 3317 LaserJet 3052
+ 3402 PhotoSmart 1115
+ 3404 DeskJet 6122
+ 3417 LaserJet 3055
+ 3502 PhotoSmart 230
+ 3504 DeskJet 6127c
+ 3511 PSC 2300
+ 3517 LaserJet 3390
+ 3602 PhotoSmart 1315
+ 3611 PSC 2410 PhotoSmart
+ 3617 Color LaserJet 2605
+ 3711 PSC 2500
+ 3717 EWS UPD
+ 3724 Webcam
+ 3802 PhotoSmart 100
+ 3807 c485w Flash Drive
+ 3817 LaserJet P2015 series
+ 3902 PhotoSmart 130
+ 3912 Officejet Pro 8500
+ 3917 LaserJet P2014
+ 3a02 PhotoSmart 7150
+ 3a11 OfficeJet 5500 series
+ 3a17 Printing Support
+ 3b02 PhotoSmart 7150~
+ 3b05 Scanjet N8460
+ 3b11 PSC 1300 series
+ 3b17 LaserJet M1005 MFP
+ 3c02 PhotoSmart 7350
+ 3c05 Scanjet Professional 1000 Mobile Scanner
+ 3c11 PSC 1358
+ 3c17 EWS UPD
+ 3d02 PhotoSmart 7350~
+ 3d11 OfficeJet 4215
+ 3d17 LaserJet P1005
+ 3e02 PhotoSmart 7550
+ 3e17 LaserJet P1006
+ 3f02 PhotoSmart 7550~
+ 3f11 PSC-1315/PSC-1317
+ 4002 PhotoSmart 635/715/720/735/935/E337 (storage)
+ 4004 CP1160
+ 4102 PhotoSmart 618
+ 4105 ScanJet 4370
+ 4111 OfficeJet 7200 series
+ 4117 LaserJet 1018
+ 4202 PhotoSmart 812
+ 4205 ScanJet G3010
+ 4211 OfficeJet 7300 series
+ 4217 EWS CM1015
+ 4302 PhotoSmart 850 (ptp)
+ 4305 ScanJet G3110
+ 4311 OfficeJet 7400 series
+ 4317 Color LaserJet CM1017
+ 4402 PhotoSmart 935 (ptp)
+ 4417 EWS UPD
+ 4502 PhotoSmart 945 (PTP mode)
+ 4505 ScanJet G4010
+ 4507 External HDD
+ 4511 PhotoSmart 2600
+ 4512 E709n [Officejet 6500 Wireless]
+ 4517 EWS UPD
+ 4605 ScanJet G4050
+ 4611 PhotoSmart 2700
+ 4717 Color LaserJet CP1215
+ 4811 PSC 1600
+ 4911 PSC 2350
+ 4b11 OfficeJet 6200
+ 4c11 PSC 1500 series
+ 4c17 EWS UPD
+ 4d11 PSC 1400
+ 4d17 EWS UPD
+ 4e11 PhotoSmart 2570 series
+ 4f11 OfficeJet 5600 (USBHUB)
+ 4f17 Color LaserJet CM1312 MFP
+ 5004 DeskJet 995c
+ 5011 PhotoSmart 3100 series
+ 5017 EWS UPD
+ 5111 PhotoSmart 3200 series
+ 5211 PhotoSmart 3300 series
+ 5307 v165w Stick
+ 5311 OfficeJet 6300
+ 5312 Officejet Pro 8500A
+ 5411 OfficeJet 4300
+ 5511 DeskJet F300 series
+ 5611 PhotoSmart C3180
+ 5617 LaserJet M1120 MFP
+ 5711 PhotoSmart C4100 series
+ 5717 LaserJet M1120n MFP
+ 5811 PhotoSmart C5100 series
+ 5817 LaserJet M1319f MFP
+ 581d lt4112 Gobi 4G Module Network Device
+ 5911 PhotoSmart C6180
+ 5912 Officejet Pro 8600
+ 5a11 PhotoSmart C7100 series
+ 5b11 OfficeJet J2100 series
+ 5b12 Officejet Pro 8100
+ 5c11 PhotoSmart C4200 Printer series
+ 5c12 OfficeJet 6700
+ 5c17 LaserJet P2055 series
+ 5d11 PhotoSmart C5200 series
+ 5e11 PhotoSmart D7400 series
+ 6004 DeskJet 5550
+ 6102 Hewlett Packard Digital Camera
+ 6104 DeskJet 5650c
+ 6117 color LaserJet 3550
+ 6202 PhotoSmart 215
+ 6204 DeskJet 5150c
+ 6217 Color LaserJet 4700
+ 6302 PhotoSmart 318/612
+ 6317 Color LaserJet 4730mfp
+ 6402 PhotoSmart 715 (ptp)
+ 6411 PhotoSmart C8100 series
+ 6417 LaserJet 5200
+ 6502 PhotoSmart 120 (ptp)
+ 6511 PhotoSmart C7200 series
+ 6602 PhotoSmart 320
+ 6611 PhotoSmart C4380 series
+ 6617 LaserJet 5200L
+ 6702 PhotoSmart 720 (ptp)
+ 6717 Color LaserJet 3000
+ 6802 PhotoSmart 620 (ptp)
+ 6811 PhotoSmart D5300 series
+ 6817 Color LaserJet 3800
+ 6911 PhotoSmart D7200 series
+ 6917 Color LaserJet 3600
+ 6a02 PhotoSmart 735 (ptp)
+ 6a11 PhotoSmart C6200 series
+ 6a17 LaserJet 4240
+ 6b02 PhotoSmart R707 (PTP mode)
+ 6b11 Photosmart C4500 series
+ 6c11 Photosmart C4480
+ 6c17 Color LaserJet 4610
+ 6f17 Color LaserJet CP6015 series
+ 7004 DeskJet 3320c
+ 7102 PhotoSmart 635 (PTP mode)
+ 7104 DeskJet 3420c
+ 7117 CM8060 Color MFP with Edgeline Technology
+ 7202 PhotoSmart 43x (ptp)
+ 7204 DeskJet 36xx
+ 7217 LaserJet M5035 MFP
+ 7302 PhotoSmart M307 (PTP mode)
+ 7304 DeskJet 35xx
+ 7311 Photosmart Premium C309
+ 7317 LaserJet P3005
+ 7404 Printing Support
+ 7417 LaserJet M4345 MFP
+ 7504 Printing Support
+ 7517 LaserJet M3035 MFP
+ 7604 DeskJet 3940
+ 7611 DeskJet F2492 All-in-One
+ 7617 LaserJet P3004
+ 7702 PhotoSmart R817 (PTP mode)
+ 7704 DeskJet D4100
+ 7717 CM8050 Color MFP with Edgeline Technology
+ 7804 DeskJet D1360
+ 7817 Color LaserJet CP3505
+ 7917 LaserJet M5025 MFP
+ 7a02 PhotoSmart M415 (PTP mode)
+ 7a04 DeskJet D2460
+ 7a17 LaserJet M3027 MFP
+ 7b02 PhotoSmart M23 (PTP mode)
+ 7b17 Color LaserJet CP4005
+ 7c17 Color LaserJet CM6040 series
+ 7d04 DeskJet F2100 Printer series
+ 7d17 Color LaserJet CM4730 MFP
+ 7e04 DeskJet F4100 Printer series
+ 8017 LaserJet P4515
+ 8104 Printing Support
+ 8117 LaserJet P4015
+ 811c Ethernet HN210E
+ 8204 Printing Support
+ 8207 FHA-3510 2.4GHz Wireless Optical Mobile Mouse
+ 8217 LaserJet P4014
+ 8317 LaserJet M9050 MFP
+ 8404 DeskJet 6800 series
+ 8417 LaserJet M9040 MFP
+ 8504 DeskJet 6600 series
+ 8604 DeskJet 5440
+ 8607 Optical Mobile Mouse
+ 8704 DeskJet 5940
+ 8711 Deskjet 2050 J510
+ 8804 DeskJet 6980 series
+ 8904 DeskJet 6940 series
+ 8911 Deskjet 1050 J410
+ 8c07 Digital Stereo Headset
+ 8c11 Deskjet F4500 series
+ 9002 PhotoSmart M437
+ 9102 PhotoSmart M537
+ 9207 HD-4110 Webcam
+ 9302 PhotoSmart R930 series
+ 9402 PhotoSmart R837
+ 9502 PhotoSmart R840 series
+ 9602 PhotoSmart M730 series
+ 9702 PhotoSmart R740 series
+ 9802 PhotoSmart Mz60 series
+ 9902 PhotoSmart M630 series
+ 9a02 PhotoSmart E330 series
+ 9b02 PhotoSmart M540 series
+ 9b07 Portable Drive
+ 9c02 PhotoSmart M440 series
+ a004 DeskJet 5850c
+ a011 Deskjet 3050A
+ b002 PhotoSmart 7200 series
+ b102 PhotoSmart 7200 series
+ b107 v255w/c310w Flash Drive
+ b116 Webcam
+ b202 PhotoSmart 7600 series
+ b302 PhotoSmart 7600 series
+ b402 PhotoSmart 7700 series
+ b502 PhotoSmart 7700 series
+ b602 PhotoSmart 7900 series
+ b702 PhotoSmart 7900 series
+ b802 PhotoSmart 7400 series
+ b902 PhotoSmart 7800 series
+ ba02 PhotoSmart 8100 series
+ bb02 PhotoSmart 8400 series
+ bc02 PhotoSmart 8700 series
+ bd02 PhotoSmart Pro B9100 series
+ bef4 NEC Picty760
+ c002 PhotoSmart 7800 series
+ c102 PhotoSmart 8000 series
+ c111 Deskjet 1510
+ c202 PhotoSmart 8200 series
+ c302 DeskJet D2300
+ c402 PhotoSmart D5100 series
+ c502 PhotoSmart D6100 series
+ c602 PhotoSmart D7100 series
+ c702 PhotoSmart D7300 series
+ c802 PhotoSmart D5060 Printer
+ d104 Bluetooth Dongle
+ d507 39gII [NW249AA]
+ efbe NEC Picty900
+ f0be NEC Picty920
+ f1be NEC Picty800
+03f1 Genoa Technology
+03f2 Oak Technology, Inc.
+03f3 Adaptec, Inc.
+ 0020 AWN-8020 WLAN [Intersil PRISM 2.5]
+ 0080 AVC-1100 Audio Capture
+ 0083 AVC-2200 Device
+ 0087 AVC-2210 Loader
+ 0088 AVC-2210 Device
+ 008b AVC-2310 Loader
+ 008c AVC-2310 Device
+ 0094 eHome Infrared Receiver
+ 009b AVC-1410 GameBridge TV NTSC
+ 2000 USBXchange
+ 2001 USBXchange Adapter
+ 2002 USB2-Xchange
+ 2003 USB2-Xchange Adapter
+ 4000 4-port hub
+ adcc Composite Device Support
+03f4 Diebold, Inc.
+03f5 Siemens Electromechanical
+03f8 Epson Imaging Technology Center
+03f9 KeyTronic Corp.
+ 0100 KT-2001 Keyboard
+ 0101 Keyboard
+ 0102 Keyboard Mouse
+03fb OPTi, Inc.
+03fc Elitegroup Computer Systems
+03fd Xilinx, Inc.
+ 0008 Platform Cable USB II
+ 0050 dfu downloader
+03fe Farallon Comunications
+0400 National Semiconductor Corp.
+ 05dc Rigol Technologies DS1000USB Oscilloscope
+ 0807 Bluetooth Dongle
+ 080a Bluetooth Device
+ 09c4 Rigol Technologies DG1022 Arbitrary Waveform Generator
+ 1000 Mustek BearPaw 1200 Scanner
+ 1001 Mustek BearPaw 2400 Scanner
+ 1237 Hub
+ a000 Smart Display Reference Device
+ c359 Logitech Harmony
+ c35b Printing Support
+ c55d Rigol Technologies DS5000USB Oscilloscope
+0401 National Registry, Inc.
+0402 ALi Corp.
+ 5462 M5462 IDE Controller
+ 5602 M5602 Video Camera Controller
+ 5603 M5603 Video Camera Controller
+ 5606 M5606 Video Camera Controller [UVC]
+ 5621 M5621 High-Speed IDE Controller
+ 5623 M5623 Scanner Controller
+ 5627 Welland ME-740PS USB2 3.5" Power Saving Enclosure
+ 5632 M5632 Host-to-Host Link
+ 5635 M5635 Flash Card Reader
+ 5636 USB 2.0 Storage Device
+ 5637 M5637 IDE Controller
+ 5642 Storage Device
+ 5661 M5661 MP3 player
+ 5667 M5667 MP3 player
+ 9665 Gateway Webcam
+0403 Future Technology Devices International, Ltd
+ 0000 H4SMK 7 Port Hub / Bricked Counterfeit FT232 Serial (UART) IC
+ 0232 Serial Converter
+ 1060 JTAG adapter
+ 1234 IronLogic RFID Adapter [Z-2 USB]
+ 1235 Iron Logic Z-397 RS-485/422 converter
+ 6001 FT232 Serial (UART) IC
+ 6002 Lumel PD12
+ 6007 Serial Converter
+ 6008 Serial Converter
+ 6009 Serial Converter
+ 6010 FT2232C/D/H Dual UART/FIFO IC
+ 6011 FT4232H Quad HS USB-UART/FIFO IC
+ 6014 FT232H Single HS USB-UART/FIFO IC
+ 6015 Bridge(I2C/SPI/UART/FIFO)
+ 8028 Dev board JTAG (FT232H based)
+ 8040 4 Port Hub
+ 8070 7 Port Hub
+ 8140 Vehicle Explorer Interface
+ 8210 MGTimer - MGCC (Vic) Timing System
+ 8370 7 Port Hub
+ 8371 PS/2 Keyboard And Mouse
+ 8372 FT8U100AX Serial Port
+ 8a28 Rainforest Automation ZigBee Controller
+ 8a98 TIAO Multi-Protocol Adapter
+ 8b28 Alpermann+Velte TCI70
+ 8b29 Alpermann+Velte TC60 CLS
+ 8b2a Alpermann+Velte Rubidium Q1
+ 8b2b Alpermann+Velte TCD
+ 8b2c Alpermann+Velte TCC70
+ 9090 SNAP Stick 200
+ 9132 LCD and Temperature Interface
+ 9133 CallerID
+ 9135 Rotary Pub alarm
+ 9136 Pulsecounter
+ 9e90 Marvell OpenRD Base/Client
+ 9f80 Ewert Energy Systems CANdapter
+ a6d0 Texas Instruments XDS100v2 JTAG / BeagleBone A3
+ a951 HCP HIT GSM/GPRS modem [Cinterion MC55i]
+ a9a0 FT2232D - Dual UART/FIFO IC - FTDI
+ abb8 Lego Mindstorms NXTCam
+ b810 US Interface Navigator (CAT and 2nd PTT lines)
+ b811 US Interface Navigator (WKEY and FSK lines)
+ b812 US Interface Navigator (RS232 and CONFIG lines)
+ b9b0 Fujitsu SK-16FX-100PMC V1.1
+ baf8 Amontec JTAGkey
+ bcd8 Stellaris Development Board
+ bcd9 Stellaris Evaluation Board
+ bcda Stellaris ICDI Board
+ bdc8 Egnite GmbH - JTAG/RS-232 adapter
+ bfd8 OpenDCC
+ bfd9 OpenDCC (Sniffer)
+ bfda OpenDCC (Throttle)
+ bfdb OpenDCC (Gateway)
+ bfdc OpenDCC (GBM)
+ c580 HID UNIKEY dongle [F-Response]
+ c630 lcd2usb interface
+ c631 i2c-tiny-usb interface
+ c632 xu1541 c64 floppy drive interface
+ c633 TinyCrypt dongle
+ c634 glcd2usb interface
+ c7d0 RR-CirKits LocoBuffer-USB
+ c8b8 Alpermann+Velte MTD TCU
+ c8b9 Alpermann+Velte MTD TCU 1HE
+ c8ba Alpermann+Velte Rubidium H1
+ c8bb Alpermann+Velte Rubidium H3
+ c8bc Alpermann+Velte Rubidium S1
+ c8bd Alpermann+Velte Rubidium T1
+ c8be Alpermann+Velte Rubidium D1
+ c8bf Alpermann+Velte TC60 RLV
+ cc48 Tactrix OpenPort 1.3 Mitsubishi
+ cc49 Tactrix OpenPort 1.3 Subaru
+ cc4a Tactrix OpenPort 1.3 Universal
+ cff8 Amontec JTAGkey
+ d010 SCS PTC-IIusb
+ d011 SCS Position-Tracker/TNC
+ d012 SCS DRAGON 1
+ d013 SCS DRAGON 1
+ d388 Xsens converter
+ d389 Xsens Wireless Receiver
+ d38a Xsens serial converter
+ d38b Xsens serial converter
+ d38c Xsens Wireless Receiver
+ d38d Xsens Awinda Station
+ d38e Xsens serial converter
+ d38f Xsens serial converter
+ d491 Zolix Omni 1509 monochromator
+ d578 Accesio USB-COM-4SM
+ d6f8 UNI Black BOX
+ d738 Propox JTAGcable II
+ d739 Propox ISPcable III
+ d9a9 Actisense USG-1 NMEA Serial Gateway
+ d9aa Actisense NGT-1 NMEA2000 PC Interface
+ d9ab Actisense NGT-1 NMEA2000 Gateway
+ daf4 Qundis Serial Infrared Head
+ e0d0 Total Phase Aardvark I2C/SPI Host Adapter
+ e521 EVER Sinline XL Series UPS
+ e6c8 PYRAMID Computer GmbH LCD
+ e700 Elster Unicom III Optical Probe
+ e729 Segway Robotic Mobility Platforms 200
+ e888 Expert ISDN Control USB
+ e889 USB-RS232 OptoBridge
+ e88a Expert mouseCLOCK USB II
+ e88b Precision Clock MSF USB
+ e88c Expert mouseCLOCK USB II HBG
+ e8d8 Aaronia AG Spectran Spectrum Analyzer
+ e8dc Aaronia AG UBBV Preamplifier
+ ea90 Eclo 1-Wire Adapter
+ ecd9 miControl miCan-Stick
+ ed71 HAMEG HO870 Serial Port
+ ed72 HAMEG HO720 Serial Port
+ ed73 HAMEG HO730 Serial Port
+ ed74 HAMEG HO820 Serial Port
+ ef10 FT1245BL
+ f070 Serial Converter 422/485 [Vardaan VEUSB422R3]
+ f0c8 SPROG Decoder Programmer
+ f0c9 SPROG-DCC CAN-USB
+ f0e9 Tagsys L-P101
+ f1a0 Asix PRESTO Programmer
+ f208 Papenmeier Braille-Display
+ f3c0 4N-GALAXY Serial Converter
+ f608 CTI USB-485-Mini
+ f60b CTI USB-Nano-485
+ f680 Suunto Sports Instrument
+ f758 GW Instek GDS-8x0 Oscilloscope
+ f7c0 ZeitControl Cardsystems TagTracer MIFARE
+ f850 USB-UIRT (Universal Infrared Receiver+Transmitter)
+ f918 Ant8 Logic Probe
+ fa00 Matrix Orbital USB Serial
+ fa01 Matrix Orbital MX2 or MX3
+ fa02 Matrix Orbital MX4 or MX5
+ fa03 Matrix Orbital VK/LK202 Family
+ fa04 Matrix Orbital VK/LK204 Family
+ fa20 Ross-Tech HEX-USB
+ fc08 Crystalfontz CFA-632 USB LCD
+ fc09 Crystalfontz CFA-634 USB LCD
+ fc0b Crystalfontz CFA-633 USB LCD
+ fc0c Crystalfontz CFA-631 USB LCD
+ fc0d Crystalfontz CFA-635 USB LCD
+ fc82 SEMC DSS-20/DSS-25 SyncStation
+ fd48 ShipModul MiniPlex-4xUSB NMEA Multiplexer
+ fd49 ShipModul MiniPlex-4xUSB-AIS NMEA Multiplexer
+ fd4b ShipModul MiniPlex NMEA Multiplexer
+ ff08 ToolHouse LoopBack Adapter
+ ff18 ScienceScope Logbook ML
+ ff19 Logbook Bus
+ ff1a Logbook Bus
+ ff1b Logbook Bus
+ ff1c ScienceScope Logbook LS
+ ff1d ScienceScope Logbook HS
+ ff1e Logbook Bus
+ ff1f Logbook Bus
+0404 NCR Corp.
+ 0202 78XX Scanner
+ 0203 78XX Scanner - Embedded System
+ 0310 K590 Printer, Self-Service
+ 0311 7167 Printer, Receipt/Slip
+ 0312 7197 Printer Receipt
+ 0320 5932-USB Keyboard
+ 0321 5953-USB Dynakey
+ 0322 5932-USB Enhanced Keyboard
+ 0323 5932-USB Enhanced Keyboard, Flash-Recovery/Download
+ 0324 5953-USB Enhanced Dynakey
+ 0325 5953-USB Enhanced Dynakey Flash-Recovery/Download
+ 0328 K016: USB-MSR ISO 3-track MSR: POS Standard (See HID pages)
+ 0329 K018: USB-MSR JIS 2-Track MSR: POS Standard
+ 032a K016: USB-MSR ISO 3-Track MSR: HID Keyboard Mode
+ 032b K016/K018: USB-MSR Flash-Recovery/Download
+0405 Synopsys, Inc.
+0406 Fujitsu-ICL Computers
+0407 Fujitsu Personal Systems, Inc.
+0408 Quanta Computer, Inc.
+ 0103 FV TouchCam N1 (Audio)
+ 030c HP Webcam
+ 03b2 HP Webcam
+ 1030 FV TouchCam N1 (Video)
+ 3000 Optical dual-touch panel
+ 3001 Optical Touch Screen
+0409 NEC Corp.
+ 0011 PC98 Series Layout Keyboard Mouse
+ 0012 ATerm IT75DSU ISDN TA
+ 0014 Japanese Keyboard
+ 0019 109 Japanese Keyboard with Bus-Powered Hub
+ 001a PC98 Series Layout Keyboard with Bus-Powered Hub
+ 0025 Mini Keyboard with Bus-Powered Hub
+ 0027 MultiSync Monitor
+ 002c Clik!-USB Drive
+ 0034 109 Japanese Keyboard with One-touch start buttons
+ 003f Wireless Keyboard with One-touch start buttons
+ 0040 Floppy
+ 004e SuperScript 1400 Series
+ 004f Wireless Keyboard with One-touch start buttons
+ 0050 7-port hub
+ 0058 HighSpeed Hub
+ 0059 HighSpeed Hub
+ 005a HighSpeed Hub
+ 006a Conceptronic USB Harddisk Box
+ 007d MINICUBE2
+ 007e PG-FP5 Flash Memory Programmer
+ 0081 SuperScript 1400 Series
+ 0082 SuperScript 1400 Series
+ 0094 Japanese Keyboard with One-touch start buttons
+ 0095 Japanese Keyboard
+ 00a9 AtermIT21L 128K Support Standard
+ 00aa AtermITX72 128K Support Standard
+ 00ab AtermITX62 128K Support Standard
+ 00ac AtermIT42 128K Support Standard
+ 00ae INSMATEV70G-MAX Standard
+ 00af AtermITX70 128K Support Standard
+ 00b0 AtermITX80 128K Support Standard
+ 00b2 AtermITX80D 128K Support Standard
+ 00c0 Wireless Remocon
+ 00f7 Smart Display PK-SD10
+ 011d e228 Mobile Phone
+ 0203 HID Audio Controls
+ 021d Aterm WL54SU2 802.11g Wireless Adapter [Atheros AR5523]
+ 0248 Aterm PA-WL54GU
+ 0249 Aterm WL300NU-G
+ 02b4 Aterm WL300NU-AG
+ 02b6 Aterm WL300NU-GS 802.11n Wireless Adapter
+ 02bc Computer Monitor
+ 0300 LifeTouch Note
+ 0301 LifeTouch Note (debug mode)
+ 55aa Hub
+ 55ab Hub [iMac/iTouch kbd]
+ 8010 Intellibase Hub
+ 8011 Intellibase Hub
+ efbe P!cty 900 [HP DJ]
+ f0be P!cty 920 [HP DJ 812c]
+040a Kodak Co.
+ 0001 DVC-323
+ 0002 DVC-325
+ 0100 DC-220
+ 0110 DC-260
+ 0111 DC-265
+ 0112 DC-290
+ 0120 DC-240
+ 0121 DC-240 (PTP firmware)
+ 0130 DC-280
+ 0131 DC-5000
+ 0132 DC-3400
+ 0140 DC-4800
+ 0160 DC4800
+ 0170 DX3900
+ 0200 Digital Camera
+ 0300 EZ-200
+ 0400 MC3
+ 0402 Digital Camera
+ 0403 Z7590
+ 0500 DX3500
+ 0510 DX3600
+ 0525 DX3215
+ 0530 DX3700
+ 0535 EasyShare CX4230 Camera
+ 0540 LS420
+ 0550 DX4900
+ 0555 DX4330
+ 0560 CX4200
+ 0565 CX4210
+ 0566 CX4300
+ 0567 LS753
+ 0568 LS443
+ 0569 LS663
+ 0570 DX6340
+ 0571 CX6330
+ 0572 DX6440
+ 0573 CX6230
+ 0574 CX6200
+ 0575 DX6490
+ 0576 DX4530
+ 0577 DX7630
+ 0578 CX7300/CX7310
+ 0579 CX7220
+ 057a CX7330
+ 057b CX7430
+ 057c CX7530
+ 057d DX7440
+ 057e C300
+ 057f DX7590
+ 0580 Z730
+ 0581 Digital Camera
+ 0582 Digital Camera
+ 0583 Digital Camera
+ 0584 CX6445
+ 0585 Digital Camera
+ 0586 CX7525
+ 0587 Digital Camera
+ 0588 Digital Camera
+ 0589 EasyShare C360
+ 058a C310
+ 058b Digital Camera
+ 058c C330
+ 058d C340
+ 058e V530
+ 058f V550
+ 0590 Digital Camera
+ 0591 Digital Camera
+ 0592 Digital Camera
+ 0593 Digital Camera
+ 0594 Digital Camera
+ 0595 Digital Camera
+ 0596 Digital Camera
+ 0597 Digital Camera
+ 0598 EASYSHARE M1033 digital camera
+ 0599 Digital Camera
+ 059a Digital Camera
+ 059b Digital Camera
+ 059c Digital Camera
+ 059d Digital Camera
+ 059e Digital Camera
+ 059f Digital Camera
+ 05a0 Digital Camera
+ 05a1 Digital Camera
+ 05a2 Digital Camera
+ 05a3 Digital Camera
+ 05a4 Digital Camera
+ 05a5 Digital Camera
+ 05a6 Digital Camera
+ 05a7 Digital Camera
+ 05a8 Digital Camera
+ 05a9 Digital Camera
+ 05aa Digital Camera
+ 05ab Digital Camera
+ 05ac Digital Camera
+ 05ad Digital Camera
+ 05ae Digital Camera
+ 05af Digital Camera
+ 05b0 Digital Camera
+ 05b1 Digital Camera
+ 05b2 Digital Camera
+ 05b3 EasyShare Z710 Camera
+ 05b4 Digital Camera
+ 05b5 Digital Camera
+ 05b6 Digital Camera
+ 05b7 Digital Camera
+ 05b8 Digital Camera
+ 05b9 Digital Camera
+ 05ba Digital Camera
+ 05bb Digital Camera
+ 05bc Digital Camera
+ 05bd Digital Camera
+ 05be Digital Camera
+ 05bf Digital Camera
+ 05c0 Digital Camera
+ 05c1 Digital Camera
+ 05c2 Digital Camera
+ 05c3 Digital Camera
+ 05c4 Digital Camera
+ 05c5 Digital Camera
+ 05c8 EASYSHARE Z1485 IS Digital Camera
+ 05d3 EasyShare M320 Camera
+ 05d4 EasyShare C180 Digital Camera
+ 1001 EasyShare SV811 Digital Picture Frame
+ 4000 InkJet Color Printer
+ 4021 Photo Printer 6800
+ 4022 1400 Digital Photo Printer
+ 402b Photo Printer 6850
+ 402e 605 Photo Printer
+ 4034 805 Photo Printer
+ 404f 305 Photo Printer
+ 4056 ESP 7200 Series AiO
+ 4109 EasyShare Printer Dock Series 3
+ 410d EasyShare G600 Printer Dock
+ 5010 Wireless Adapter
+ 5012 DBT-220 Bluetooth Adapter
+ 6001 i30
+ 6002 i40
+ 6003 i50
+ 6004 i60
+ 6005 i80
+ 6029 i900
+ 602a i900
+040b Weltrend Semiconductor
+ 0a68 Func MS-3 gaming mouse [WT6573F MCU]
+ 6510 Weltrend Bar Code Reader
+ 6520 XBOX Xploder
+ 6533 Speed-Link Competition Pro
+ 6543 Manhattan Magnetic Card Strip Reader
+040c VTech Computers, Ltd
+040d VIA Technologies, Inc.
+ 3184 VNT VT6656 USB-802.11 Wireless LAN Adapter
+ 6205 USB 2.0 Card Reader
+040e MCCI
+040f Echo Speech Corp.
+0411 BUFFALO INC. (formerly MelCo., Inc.)
+ 0001 LUA-TX Ethernet [pegasus]
+ 0005 LUA-TX Ethernet
+ 0006 WLI-USB-L11 Wireless LAN Adapter
+ 0009 LUA2-TX Ethernet
+ 000b WLI-USB-L11G-WR Wireless LAN Adapter
+ 000d WLI-USB-L11G Wireless LAN Adapter
+ 0012 LUA-KTX Ethernet
+ 0013 USB2-IDE Adapter
+ 0016 WLI-USB-S11 802.11b Adapter
+ 0018 USB2-IDE Adapter
+ 001c USB-IDE Bridge: DUB-PxxG
+ 0027 WLI-USB-KS11G 802.11b Adapter
+ 002a SMSC USB97C202 "HD-HB300V2-EU"
+ 003d LUA-U2-KTX Ethernet
+ 0044 WLI-USB-KB11 Wireless LAN Adapter
+ 004b WLI-USB-G54 802.11g Adapter [Broadcom 4320 USB]
+ 004d WLI-USB-B11 Wireless LAN Adapter
+ 0050 WLI2-USB2-G54 Wireless LAN Adapter
+ 005e WLI-U2-KG54-YB WLAN
+ 0065 Python2 WDM Encoder
+ 0066 WLI-U2-KG54 WLAN
+ 0067 WLI-U2-KG54-AI WLAN
+ 006e LUA-U2-GT 10/100/1000 Ethernet Adapter
+ 0089 RUF-C/U2 Flash Drive
+ 008b Nintendo Wi-Fi
+ 0091 WLI-U2-KAMG54 Wireless LAN Adapter
+ 0092 WLI-U2-KAMG54 Bootloader
+ 0097 WLI-U2-KG54-BB
+ 00a9 WLI-U2-AMG54HP Wireless LAN Adapter
+ 00aa WLI-U2-AMG54HP Bootloader
+ 00b3 PC-OP-RS1 RemoteStation
+ 00bc WLI-U2-KG125S 802.11g Adapter [Broadcom 4320 USB]
+ 00ca 802.11n Network Adapter
+ 00cb WLI-U2-G300N 802.11n Adapter
+ 00d8 WLI-U2-SG54HP
+ 00d9 WLI-U2-G54HP
+ 00da WLI-U2-KG54L 802.11bg [ZyDAS ZD1211B]
+ 00db External Hard Drive HD-PF32OU2 [Buffalo Ministation]
+ 00e8 WLI-UC-G300N Wireless LAN Adapter [Ralink RT2870]
+ 0105 External Hard Drive HD-CEU2 [Drive Station]
+ 012c SATA Bridge
+ 012e WLI-UC-AG300N Wireless LAN Adapter
+ 0148 WLI-UC-G300HP Wireless LAN Adapter
+ 0150 WLP-UC-AG300 Wireless LAN Adapter
+ 0157 External Hard Drive HD-PEU2
+ 0158 WLI-UC-GNHP Wireless LAN Adapter
+ 015d WLI-UC-GN Wireless LAN Adapter [Ralink RT3070]
+ 016f WLI-UC-G301N Wireless LAN Adapter [Ralink RT3072]
+ 017f Sony UWA-BR100 802.11abgn Wireless Adapter [Atheros AR7010+AR9280]
+ 019e WLI-UC-GNP Wireless LAN Adapter
+ 01a1 MiniStation Metro
+ 01a2 WLI-UC-GNM Wireless LAN Adapter [Ralink RT8070]
+ 01dc Ultra-Slim Portable DVD Writer (DVSM-PC58U2V)
+ 01de External Hard Drive HD-PCTU3 [Buffalo MiniStation]
+ 01ee WLI-UC-GNM2 Wireless LAN Adapter [Ralink RT3070]
+ 01f1 SATA Adapter [HD-LBU3]
+ 01fd WLI-UC-G450 Wireless LAN Adapter
+0412 Award Software International
+0413 Leadtek Research, Inc.
+ 1310 WinFast TV - NTSC + FM
+ 1311 WinFast TV - NTSC + MTS + FM
+ 1312 WinFast TV - PAL BG + FM
+ 1313 WinFast TV - PAL BG+TXT + FM
+ 1314 WinFast TV Audio - PHP PAL I
+ 1315 WinFast TV Audio - PHP PAL I+TXT
+ 1316 WinFast TV Audio - PHP PAL DK
+ 1317 WinFast TV Audio - PHP PAL DK+TXT
+ 1318 WinFast TV - PAL I/DK + FM
+ 1319 WinFast TV - PAL N + FM
+ 131a WinFast TV Audio - PHP SECAM LL
+ 131b WinFast TV Audio - PHP SECAM LL+TXT
+ 131c WinFast TV Audio - PHP SECAM DK
+ 131d WinFast TV - SECAM DK + TXT + FM
+ 131e WinFast TV - NTSC Japan + FM
+ 1320 WinFast TV - NTSC
+ 1321 WinFast TV - NTSC + MTS
+ 1322 WinFast TV - PAL BG
+ 1323 WinFast TV - PAL BG+TXT
+ 1324 WinFast TV Audio - PHP PAL I
+ 1325 WinFast TV Audio - PHP PAL I+TXT
+ 1326 WinFast TV Audio - PHP PAL DK
+ 1327 WinFast TV Audio - PHP PAL DK+TXT
+ 1328 WinFast TV - PAL I/DK
+ 1329 WinFast TV - PAL N
+ 132a WinFast TV Audio - PHP SECAM LL
+ 132b WinFast TV Audio - PHP SECAM LL+TXT
+ 132c WinFast TV Audio - PHP SECAM DK
+ 132d WinFast TV - SECAM DK + TXT
+ 132e WinFast TV - NTSC Japan
+ 6023 EMP Audio Device
+ 6024 WinFast PalmTop/Novo TV Video
+ 6025 WinFast DTV Dongle (cold state)
+ 6026 WinFast DTV Dongle (warm state)
+ 6029 WinFast DTV Dongle Gold
+ 6125 WinFast DTV Dongle
+ 6126 WinFast DTV Dongle BDA Driver
+ 6a03 RTL2832 [WinFast DTV Dongle Mini]
+ 6f00 WinFast DTV Dongle (STK7700P based)
+0414 Giga-Byte Technology Co., Ltd
+0416 Winbond Electronics Corp.
+ 0035 W89C35 802.11bg WLAN Adapter
+ 0101 Hub
+ 0961 AVL Flash Card Reader
+ 3810 Smart Card Controller
+ 3811 Generic Controller - Single interface
+ 3812 Smart Card Controller_2Interface
+ 3813 Panel Display
+ 5011 Virtual Com Port
+ 5518 4-Port Hub
+ 551a PC Sync Keypad
+ 551b PC Async Keypad
+ 551c Sync Tenkey
+ 551d Async Tenkey
+ 551e Keyboard
+ 551f Keyboard w/ Sys and Media
+ 5521 Keyboard
+ 6481 16-bit Scanner
+ 7721 Memory Stick Reader/Writer
+ 7722 Memory Stick Reader/Writer
+ 7723 SD Card Reader
+0417 Symbios Logic
+0418 AST Research
+0419 Samsung Info. Systems America, Inc.
+ 0001 IrDA Remote Controller / Creative Cordless Mouse
+ 0600 Desktop Wireless 6000
+ 3001 Xerox P1202 Laser Printer
+ 3003 Olivetti PG L12L
+ 3201 Docuprint P8ex
+ 3404 SCX-5x12 series
+ 3406 MFP 830 series
+ 3407 ML-912
+ 3601 InkJet Color Printer
+ 3602 InkJet Color Printer
+ 4602 Remote NDIS Network Device
+ 8001 Hub
+ 8002 SyncMaster HID Monitor Control
+ aa03 SDAS-3 MP3 Player
+041a Phoenix Technologies, Ltd
+041b d'TV
+041d S3, Inc.
+041e Creative Technology, Ltd
+ 1002 Nomad II
+ 1003 Blaster GamePad Cobra
+ 1050 GamePad Cobra
+ 1053 Mouse Gamer HD7600L
+ 200c MuVo V100
+ 2020 Zen X-Fi 2
+ 2029 ZiiO
+ 2801 Prodikeys PC-MIDI multifunction keyboard
+ 3000 SoundBlaster Extigy
+ 3002 SB External Composite Device
+ 3010 SoundBlaster MP3+
+ 3014 SB External Composite Device
+ 3015 Sound Blaster Digital Music LX
+ 3020 SoundBlaster Audigy 2 NX
+ 3030 SB External Composite Device
+ 3040 SoundBlaster Live! 24-bit External SB0490
+ 3060 Sound Blaster Audigy 2 ZS External
+ 3061 SoundBlaster Audigy 2 ZS Video Editor
+ 3090 Sound Blaster Digital Music SX
+ 30d0 Xmod
+ 30d3 Sound Blaster Play!
+ 3100 IR Receiver (SB0540)
+ 3121 WoW tap chat
+ 3220 Sound Blaster Tactic(3D) Sigma sound card
+ 3f00 E-Mu Xboard 25 MIDI Controller
+ 3f02 E-Mu 0202
+ 3f04 E-Mu 0404
+ 3f07 E-Mu Xmidi 1x1
+ 3f0e Xmidi 1x1 Tab
+ 4003 VideoBlaster Webcam Go Plus [W9967CF]
+ 4004 Nomad II MG
+ 4005 Webcam Blaster Go ES
+ 4007 Go Mini
+ 400a PC-Cam 300
+ 400b PC-Cam 600
+ 400c Webcam 5 [pwc]
+ 400d Webcam PD1001
+ 400f PC-CAM 550 (Composite)
+ 4011 Webcam PRO eX
+ 4012 PC-CAM350
+ 4013 PC-Cam 750
+ 4015 CardCam Value
+ 4016 CardCam
+ 4017 Webcam Mobile [PD1090]
+ 4018 Webcam Vista [PD1100]
+ 4019 Audio Device
+ 401a Webcam Vista [PD1100]
+ 401c Webcam NX [PD1110]
+ 401d Webcam NX Ultra
+ 401e Webcam NX Pro
+ 401f Webcam Notebook [PD1171]
+ 4020 Webcam NX
+ 4021 Webcam NX Ultra
+ 4022 Webcam NX Pro
+ 4028 Vista Plus cam [VF0090]
+ 4029 Webcam Live!
+ 402f DC-CAM 3000Z
+ 4034 Webcam Instant
+ 4035 Webcam Instant
+ 4036 Webcam Live!/Live! Pro
+ 4037 Webcam Live!
+ 4038 ORITE CCD Webcam [PC370R]
+ 4039 Webcam Live! Effects
+ 403a Webcam NX Pro 2
+ 403b Creative Webcam Vista [VF0010]
+ 403c Webcam Live! Ultra
+ 403d Webcam Notebook Ultra
+ 403e Webcam Vista Plus
+ 4041 Webcam Live! Motion
+ 4043 Vibra Plus Webcam
+ 4045 Live! Cam Voice
+ 4049 Live! Cam Voice
+ 4051 Live! Cam Notebook Pro [VF0250]
+ 4052 Live! Cam Vista IM
+ 4053 Live! Cam Video IM
+ 4054 Live! Cam Video IM
+ 4055 Live! Cam Video IM Pro
+ 4056 Live! Cam Video IM Pro
+ 4057 Live! Cam Optia
+ 4058 Live! Cam Optia AF
+ 405f WebCam Vista (VF0330)
+ 4061 Live! Cam Notebook Pro [VF0400]
+ 4063 Live! Cam Video IM Pro
+ 4068 Live! Cam Notebook [VF0470]
+ 406c Live! Cam Sync [VF0520]
+ 4083 Live! Cam Socialize [VF0640]
+ 4087 Live! Cam Socialize HD 1080 [VF0680]
+ 4088 Live! Cam Chat HD [VF0700]
+ 4095 Live! Cam Sync HD [VF0770]
+ 4097 Live! Cam Chat HD [VF0700]
+ 4100 Nomad Jukebox 2
+ 4101 Nomad Jukebox 3
+ 4102 NOMAD MuVo^2
+ 4106 Nomad MuVo
+ 4107 NOMAD MuVo
+ 4108 Nomad Jukebox Zen
+ 4109 Nomad Jukebox Zen NX
+ 410b Nomad Jukebox Zen USB 2.0
+ 410c Nomad MuVo NX
+ 410f NOMAD MuVo^2 (Flash)
+ 4110 Nomad Jukebox Zen Xtra
+ 4111 Dell Digital Jukebox
+ 4116 MuVo^2
+ 4117 Nomad MuVo TX
+ 411b Zen Touch
+ 411c Nomad MuVo USB 2.0
+ 411d Zen
+ 411e Zen Micro
+ 4120 Nomad MuVo TX FM
+ 4123 Zen Portable Media Center
+ 4124 MuVo^2 FM (uHDD)
+ 4126 Dell DJ (2nd gen)
+ 4127 Dell DJ
+ 4128 NOMAD Jukebox Zen Xtra (mtp)
+ 412b MuVo N200 with FM radio
+ 412f Dell Digital Jukebox 2.Gen
+ 4130 Zen Micro (mtp)
+ 4131 DAP-HD0014 [Zen Touch] (MTP)
+ 4133 Mass Storage Device
+ 4134 Zen Neeon
+ 4136 Zen Sleek
+ 4137 Zen Sleek (mtp)
+ 4139 Zen Nano Plus
+ 413c Zen MicroPhoto
+ 4150 Zen V (MTP)
+ 4151 Zen Vision:M (mtp)
+ 4152 Zen V Plus
+ 4153 Zen Vision W
+ 4154 Zen Stone
+ 4155 Zen Stone plus
+ 4157 Zen (MTP)
+ 500f Broadband Blaster 8012U-V
+ 5015 TECOM Bluetooth Device
+ ffff Webcam Live! Ultra
+041f LCS Telegraphics
+0420 Chips and Technologies
+ 1307 Celly SIM Card Reader
+0421 Nokia Mobile Phones
+ 0001 E61i (PC Suite mode)
+ 0018 6288 GSM Smartphone
+ 0019 6288 GSM Smartphone (imaging mode)
+ 001a 6288 GSM Smartphone (file transfer mode)
+ 0024 5610 XpressMusic (Storage mode)
+ 0025 5610 XpressMusic (PC Suite mode)
+ 0028 5610 XpressMusic (Imaging mode)
+ 002d 6120 Phone (Mass storage mode)
+ 002e 6120 Phone (Media-Player mode)
+ 002f 6120 Phone (PC-Suite mode)
+ 0042 E51 (PC Suite mode)
+ 0064 3109c GSM Phone
+ 006b 5310 Xpress Music (PC Suite mode)
+ 006c 5310 Xpress music (Storage mode)
+ 006d N95 (Storage mode)
+ 006e N95 (Multimedia mode)
+ 006f N95 (Printing mode)
+ 0070 N95 (PC Suite mode)
+ 0096 N810 Internet Tablet
+ 00aa E71 (Mass storage mode)
+ 00ab E71 (PC Suite mode)
+ 00e4 E71 (Media transfer mode)
+ 0103 ADL Flashing Engine AVALON Parent
+ 0104 ADL Re-Flashing Engine Parent
+ 0105 Nokia Firmware Upgrade Mode
+ 0106 ROM Parent
+ 0154 5800 XpressMusic (PC Suite mode)
+ 0155 5800 XpressMusic (Multimedia mode)
+ 0156 5800 XpressMusic (Storage mode)
+ 0157 5800 XpressMusic (Imaging mode)
+ 0199 6700 Classic (msc)
+ 019a 6700 Classic (PC Suite)
+ 019b 6700 Classic (mtp)
+ 01b0 6303 classic Phone (PC Suite mode)
+ 01b1 6303 classic Phone (Mass storage mode)
+ 01b2 6303 classic Phone (Printing and media mode)
+ 01c7 N900 (Storage Mode)
+ 01c8 N900 (PC-Suite Mode)
+ 0228 5530 XpressMusic
+ 023a 6730 Classic
+ 026a N97 (mass storage)
+ 026b N97 (Multimedia)
+ 026c N97 (PC Suite)
+ 026d N97 (Pictures)
+ 0295 660i/6600i Slide Phone (Mass Storage)
+ 0297 660i/6600i Slide Phone (Still Image)
+ 02e1 5230 (Storage mode)
+ 02e2 5230 (Multimedia mode)
+ 02e3 5230 (PC-Suite mode)
+ 02e4 5230 (Imaging mode)
+ 0360 C1-01 Ovi Suite Mode
+ 0396 C7-00 (Modem mode)
+ 03a4 C5 (Storage mode)
+ 03c0 C7-00 (Mass storage mode)
+ 03c1 C7-00 (Media transfer mode)
+ 03cd C7-00 (Nokia Suite mode)
+ 03d1 N950
+ 0400 7600 Phone Parent
+ 0401 6650 GSM Phone
+ 0402 6255 Phone Parent
+ 0404 5510
+ 0405 9500 GSM Communicator
+ 0407 Music Player HDR-1(tm)
+ 040b N-Gage GSM Phone
+ 040d 6620 Phone Parent
+ 040e 6651 Phone Parent
+ 040f 6230 GSM Phone
+ 0410 6630 Imaging Smartphone
+ 0411 7610 Phone Parent
+ 0413 6260 Phone Parent
+ 0414 7370
+ 0415 9300 GSM Smartphone
+ 0416 6170 Phone Parent
+ 0417 7270 Phone Parent
+ 0418 E70 (PC Suite mode)
+ 0419 E60 (PC Suite mode)
+ 041a 9500 GSM Communicator (RNDIS)
+ 041b 9300 GSM Smartphone (RNDIS)
+ 041c 7710 Phone Parent
+ 041d 6670 Phone Parent
+ 041e 6680
+ 041f 6235 Phone Parent
+ 0421 3230 Phone Parent
+ 0422 6681 Phone Parent
+ 0423 6682 Phone Parent
+ 0428 6230i Modem
+ 0429 6230i MultiMedia Card
+ 0431 770 Internet Tablet
+ 0432 N90 Phone Parent
+ 0435 E70 (IP Passthrough/RNDIS mode)
+ 0436 E60 (IP Passthrough/RNDIS mode)
+ 0437 6265 Phone Parent
+ 043a N70 USB Phone Parent
+ 043b 3155 Phone Parent
+ 043c 6155 Phone Parent
+ 043d 6270 Phone Parent
+ 0443 N70 Phone Parent
+ 0444 N91
+ 044c NM850iG Phone Parent
+ 044d E61 (PC Suite mode)
+ 044e E61 (Data Exchange mode)
+ 044f E61 (IP Passthrough/RNDIS mode)
+ 0453 9300 Phone Parent
+ 0456 6111 Phone Parent
+ 0457 6111 Phone (Printing mode)
+ 045a 6280 Phone Parent
+ 045d 6282 Phone Parent
+ 046e 6110 Navigator
+ 0471 6110 Navigator
+ 0485 MTP Device
+ 04b9 5300
+ 04bc 5200 (Nokia mode)
+ 04bd 5200 (Storage mode)
+ 04be 5200 (MTP mode)
+ 04c3 N800 Internet Tablet
+ 04ce E90 Communicator (PC Suite mode)
+ 04cf E90 Communicator (Storage mode)
+ 04f0 Nokia N95 (PC Suite mode)
+ 04f9 6300 (PC Suite mode)
+ 0508 E65 (PC Suite mode)
+ 0509 E65 (Storage mode)
+ 0518 N9 Phone
+ 054d C2-01
+ 0600 Digital Pen SU-1B
+ 0610 CS-15 (Internet Stick 3G modem)
+ 0661 Lumia 620/920
+ 0662 301 Dual SIM (Mass Storage)
+ 0663 301 Dual SIM
+ 069a 130 [RM-1035] (Charging only)
+ 06fc Lumia 640 Phone
+ 0720 X (RM-980)
+ 0800 Connectivity Cable DKU-5
+ 0801 Data Cable DKU-6
+ 0802 CA-42 Phone Parent
+0422 ADI Systems, Inc.
+0423 Computer Access Technology Corp.
+ 000a NetMate Ethernet
+ 000c NetMate2 Ethernet
+ 000d USB Chief Analyzer
+ 0100 Generic Universal Protocol Analyzer
+ 0101 UPA USBTracer
+ 0200 Generic 10K Universal Protocol Analyzer
+ 020a PETracer ML
+ 0300 Generic Universal Protocol Analyzer
+ 0301 2500H Tracer Trainer
+ 030a PETracer x1
+ 1237 Andromeda Hub
+0424 Standard Microsystems Corp.
+ 0001 Integrated Hub
+ 0140 LPC47M14x hub
+ 0acd Sitecom Internal Multi Memory reader/writer MD-005
+ 0fdc Floppy
+ 10cd Sitecom Internal Multi Memory reader/writer MD-005
+ 2020 USB Hub
+ 20cd Sitecom Internal Multi Memory reader/writer MD-005
+ 20fc 6-in-1 Card Reader
+ 2134 Hub
+ 2228 9-in-2 Card Reader
+ 223a 8-in-1 Card Reader
+ 2503 USB 2.0 Hub
+ 2504 USB 2.0 Hub
+ 2507 hub
+ 2512 USB 2.0 Hub
+ 2513 2.0 Hub
+ 2514 USB 2.0 Hub
+ 2517 Hub
+ 2524 USB MultiSwitch Hub
+ 2602 USB 2.0 Hub
+ 2640 USB 2.0 Hub
+ 2660 Hub
+ 4060 Ultra Fast Media Reader
+ 4064 Ultra Fast Media Reader
+ 5434 Hub
+ 5534 Hub
+ 7500 LAN7500 Ethernet 10/100/1000 Adapter
+ 9512 SMC9512/9514 USB Hub
+ 9514 SMC9514 Hub
+ 9904 LAN9512/LAN9514 Ethernet 10/100 Adapter (SAL10)
+ a700 2 Port Hub
+ ec00 SMSC9512/9514 Fast Ethernet Adapter
+0425 Motorola Semiconductors HK, Ltd
+ 0101 G-Tech Wireless Mouse & Keyboard
+ f102 G-Tech U+P Wireless Mouse
+0426 Integrated Device Technology, Inc.
+ 0426 WDM Driver
+0427 Motorola Electronics Taiwan, Ltd
+0428 Advanced Gravis Computer Tech, Ltd
+ 4001 GamePad Pro
+0429 Cirrus Logic
+042a Ericsson Austrian, AG
+042b Intel Corp.
+ 9316 8x931Hx Customer Hub
+042c Innovative Semiconductors, Inc.
+042d Micronics
+042e Acer, Inc.
+ 0380 MP3 Player
+042f Molex, Inc.
+0430 Sun Microsystems, Inc.
+ 0002 109 Keyboard
+ 0005 Type 6 Keyboard
+ 000a 109 Japanese Keyboard
+ 000b 109 Japanese Keyboard
+ 0082 109 Japanese Keyboard
+ 0083 109 Japanese Keyboard
+ 00a2 Type 7 Keyboard
+ 0100 3-button Mouse
+ 100e 24.1" LCD Monitor v4 / FID-638 Mouse
+ 36ba Bus Powered Hub
+ a101 remote key/mouse for P3 chip
+ a102 remote key/mouse/storage for P3 chip
+ a103 remote storage for P3 chip
+ a4a2 Ethernet (RNDIS and CDC ethernet)
+ cdab Raritan KVM dongle
+0431 Itac Systems, Inc.
+ 0100 Mouse-Trak 3-button Track Ball
+0432 Unisys Corp.
+ 0031 Document Processor
+0433 Alps Electric, Inc.
+ 1101 IBM Game Controller
+ abab Keyboard
+0434 Samsung Info. Systems America, Inc.
+0435 Hyundai Electronics America
+0436 Taugagreining HF
+ 0005 CameraMate (DPCM_USB)
+0437 Framatome Connectors USA
+0438 Advanced Micro Devices, Inc.
+0439 Voice Technologies Group
+043d Lexmark International, Inc.
+ 0001 Laser Printer
+ 0002 Optra E310 Printer
+ 0003 Laser Printer
+ 0004 Laser Printer
+ 0005 Laser Printer
+ 0006 Laser Printer
+ 0007 Laser Printer
+ 0008 Inkjet Color Printer
+ 0009 Optra S2450 Printer
+ 000a Laser Printer
+ 000b Inkjet Color Printer
+ 000c Optra E312 Printer
+ 000d Laser Printer
+ 000e Laser Printer
+ 000f Laser Printer
+ 0010 Laser Printer
+ 0011 Laser Printer
+ 0012 Inkjet Color Printer
+ 0013 Inkjet Color Printer
+ 0014 InkJet Color Printer
+ 0015 InkJet Color Printer
+ 0016 Z12 Color Jetprinter
+ 0017 Z32 printer
+ 0018 Z52 Printer
+ 0019 Forms Printer
+ 001a Z65 Printer
+ 001b InkJet Photo Printer
+ 001c Kodak Personal Picture Maker 200 Printer
+ 001d InkJet Color Printer
+ 001e InkJet Photo Printer
+ 001f Kodak Personal Picture Maker 200 Card Reader
+ 0020 Z51 Printer
+ 0021 Z33 Printer
+ 0022 InkJet Color Printer
+ 0023 Laser Printer
+ 0024 Laser Printer
+ 0025 InkJet Color Printer
+ 0026 InkJet Color Printer
+ 0027 InkJet Color Printer
+ 0028 InkJet Color Printer
+ 0029 Scan Print Copy
+ 002a Scan Print Copy
+ 002b Scan Print Copy
+ 002c Scan Print Copy
+ 002d X70/X73 Scan/Print/Copy
+ 002e Scan Print Copy
+ 002f Scan Print Copy
+ 0030 Scan Print Copy
+ 0031 Scan Print Copy
+ 0032 Scan Print Copy
+ 0033 Scan Print Copy
+ 0034 Scan Print Copy
+ 0035 Scan Print Copy
+ 0036 Scan Print Copy
+ 0037 Scan Print Copy
+ 0038 Scan Print Copy
+ 0039 Scan Print Copy
+ 003a Scan Print Copy
+ 003b Scan Print Copy
+ 003c Scan Print Copy
+ 003d X83 Scan/Print/Copy
+ 003e Scan Print Copy
+ 003f Scan Print Copy
+ 0040 Scan Print Copy
+ 0041 Scan Print Copy
+ 0042 Scan Print Copy
+ 0043 Scan Print Copy
+ 0044 Scan Print Copy
+ 0045 Scan Print Copy
+ 0046 Scan Print Copy
+ 0047 Scan Print Copy
+ 0048 Scan Print Copy
+ 0049 Scan Print Copy
+ 004a Scan Print Copy
+ 004b Scan Print Copy
+ 004c Scan Print Copy
+ 004d Laser Printer
+ 004e Laser Printer
+ 004f InkJet Color Printer
+ 0050 InkJet Color Printer
+ 0051 Laser Printer
+ 0052 Laser Printer
+ 0053 InkJet Color Printer
+ 0054 InkJet Color Printer
+ 0057 Z35 Printer
+ 0058 Laser Printer
+ 005a X63
+ 005c InkJet Color Printer
+ 0060 X74/X75 Scanner
+ 0061 X74 Hub
+ 0065 X5130
+ 0069 X74/X75 Printer
+ 006d X125
+ 006e C510
+ 0072 X6170 Printer
+ 0073 InkJet Color Printer
+ 0078 InkJet Color Printer
+ 0079 InkJet Color Printer
+ 007a Generic Hub
+ 007b InkJet Color Printer
+ 007c X1110/X1130/X1140/X1150/X1170/X1180/X1185
+ 007d Photo 3150
+ 008a 4200 series
+ 008b InkJet Color Printer
+ 008c to CF/SM/SD/MS Card Reader
+ 008e InkJet Color Printer
+ 008f X422
+ 0093 X5250
+ 0095 E220 Printer
+ 0096 2200 series
+ 0097 P6250
+ 0098 7100 series
+ 009e P910 series Human Interface Device
+ 009f InkJet Color Printer
+ 00a9 IBM Infoprint 1410 MFP
+ 00ab InkJet Color Printer
+ 00b2 3300 series
+ 00b8 7300 series
+ 00b9 8300 series
+ 00ba InkJet Color Printer
+ 00bb 2300 series
+ 00bd Printing Support
+ 00be Printing Support
+ 00bf Printing Support
+ 00c0 6300 series
+ 00c1 4300 series
+ 00c7 Printing Support
+ 00c8 Printing Support
+ 00c9 Printing Support
+ 00cb Printing Support
+ 00cc E120(n)
+ 00d0 9300 series
+ 00d3 X340 Scanner
+ 00d4 X342n Scanner
+ 00d5 Printing Support
+ 00d6 X340 Scanner
+ 00e8 X642e
+ 00e9 2400 series
+ 00f6 3400 series
+ 00f7 InkJet Color Printer
+ 00ff InkJet Color Printer
+ 010b 2500 series
+ 010d 3500-4500 series
+ 010f 6500 series
+ 0142 X3650 (Printer, Scanner, Copier)
+ 01fa S310 series
+ 4303 Xerox WorkCentre Pro 412
+043e LG Electronics USA, Inc.
+ 3001 AN-WF100 802.11abgn Wireless Adapter [Broadcom BCM4323]
+ 42bd Flatron 795FT Plus Monitor
+ 4a4d Flatron 915FT Plus Monitor
+ 7001 MF-PD100 Soul Digital MP3 Player
+ 7013 MP3 Player
+ 70d7 Mouse Scanner LSM-150 [LG Smart Scan Mouse]
+ 70f5 External HDD
+ 8484 LPC-U30 Webcam II
+ 8585 LPC-UC35 Webcam
+ 8888 Electronics VCS Camera II(LPC-U20)
+ 9800 Remote Control Receiver_iMON
+ 9803 eHome Infrared Receiver
+ 9804 DMB Receiver Control
+ 9c01 LGE Sync
+043f RadiSys Corp.
+0440 Eizo Nanao Corp.
+0441 Winbond Systems Lab.
+ 1456 Hub
+0442 Ericsson, Inc.
+ abba Bluetooth Device
+0443 Gateway, Inc.
+ 000e Multimedia Keyboard
+ 002e Millennium Keyboard
+0445 Lucent Technologies, Inc.
+0446 NMB Technologies Corp.
+ 6781 Keyboard with PS/2 Mouse Port
+ 6782 Keyboard
+0447 Momentum Microsystems
+0449 Duta Multi Robotik
+ 0128 Menengah
+ 0210 Dasar
+ 0612 Lanjutan
+044a Shamrock Tech. Co., Ltd
+044b WSI
+044c CCL/ITRI
+044d Siemens Nixdorf AG
+044e Alps Electric Co., Ltd
+ 1104 Japanese Keyboard
+ 2002 MD-5500 Printer
+ 2014 Bluetooth Device
+ 3001 UGTZ4 Bluetooth
+ 3002 Bluetooth Device
+ 3003 Bluetooth Device
+ 3004 Bluetooth Adapter
+ 3005 Integrated Bluetooth Device
+ 3006 Bluetooth Adapter
+ 3007 Bluetooth Controller (ALPS/UGX)
+ 300c Bluetooth Controller (ALPS/UGPZ6)
+ 300d Bluetooth Controller (ALPS/UGPZ6)
+ 3010 Bluetooth Adapter
+ 3017 BCM2046 Bluetooth Device
+ ffff Compaq Bluetooth Multiport Module
+044f ThrustMaster, Inc.
+ 0400 HOTAS Cougar
+ 0402 HOTAS Warthog Joystick
+ 0404 HOTAS Warthog Throttle
+ 044f GP XID
+ a003 Rage 3D Game Pad
+ a01b PK-GP301 Driving Wheel
+ a0a0 Top Gun Joystick
+ a0a1 Top Gun Joystick (rev2)
+ a0a3 Fusion Digital GamePad
+ a201 PK-GP201 PlayStick
+ b108 T-Flight Hotas X Flight Stick
+ b10a T.16000M Joystick
+ b203 360 Modena Pro Wheel
+ b300 Firestorm Dual Power
+ b303 FireStorm Dual Analog 2
+ b304 Firestorm Dual Power
+ b307 vibrating Upad
+ b30b Wireless VibrationPad
+ b315 Firestorm Dual Analog 3
+ b323 Dual Trigger 3-in-1 (PC Mode)
+ b324 Dual Trigger 3-in-1 (PS3 Mode)
+ b603 force feedback Wheel
+ b605 force feedback Racing Wheel
+ b651 Ferrari GT Rumble Force Wheel
+ b653 RGT Force Feedback Clutch Racing Wheel
+ b654 Ferrari GT Force Feedback Wheel
+ b687 TWCS Throttle
+ b700 Tacticalboard
+0450 DFI, Inc.
+0451 Texas Instruments, Inc.
+ 1234 Bluetooth Device
+ 1428 Hub
+ 1446 TUSB2040/2070 Hub
+ 16a6 BM-USBD1 BlueRobin RF heart rate sensor receiver
+ 2036 TUSB2036 Hub
+ 2046 TUSB2046 Hub
+ 2077 TUSB2077 Hub
+ 2f90 SM-USB-DIG
+ 3410 TUSB3410 Microcontroller
+ 3f00 OMAP1610
+ 3f02 SMC WSKP100 Wi-Fi Phone
+ 5409 Frontier Labs NEX IA+ Digital Audio Player
+ 6000 AU5 ADSL Modem (pre-reenum)
+ 6001 AU5 ADSL Modem
+ 6060 RNDIS/BeWAN ADSL2+
+ 6070 RNDIS/BeWAN ADSL2+
+ 625f TUSB6250 ATA Bridge
+ 8041 Hub
+ 8042 Hub
+ 8043 Hub
+ 8140 TUSB8041 4-Port Hub
+ 8142 TUSB8041 4-Port Hub
+ 926b TUSB9260 Boot Loader
+ dbc0 Device Bay Controller
+ e001 GraphLink [SilverLink]
+ e003 TI-84 Plus Calculator
+ e004 TI-89 Titanium Calculator
+ e008 TI-84 Plus Silver Calculator
+ e012 TI-Nspire Calculator
+ f430 MSP-FET430UIF JTAG Tool
+ f432 eZ430 Development Tool
+ ffff Bluetooth Device
+0452 Mitsubishi Electronics America, Inc.
+ 0021 HID Monitor Controls
+ 0050 Diamond Pro 900u CRT Monitor
+ 0051 Integrated Hub
+ 0100 Control Panel for Leica TCS SP5
+0453 CMD Technology
+ 6781 NMB Keyboard
+ 6783 Chicony Composite Keyboard
+0454 Vobis Microcomputer AG
+0455 Telematics International, Inc.
+0456 Analog Devices, Inc.
+ f000 FT2232 JTAG ICE [gnICE]
+ f001 FT2232H Hi-Speed JTAG ICE [gnICE+]
+0457 Silicon Integrated Systems Corp.
+ 0150 Super Talent 1GB Flash Drive
+ 0151 Super Flash 1GB / GXT 64MB Flash Drive
+ 0162 SiS162 usb Wireless LAN Adapter
+ 0163 SiS163U 802.11 Wireless LAN Adapter
+ 0817 SiS-184-ASUS-4352.17 touch panel
+ 5401 Wireless Adapter RO80211GS-USB
+0458 KYE Systems Corp. (Mouse Systems)
+ 0001 Mouse
+ 0002 Genius NetMouse Pro
+ 0003 Genius NetScroll+
+ 0006 Easy Mouse+
+ 000b NetMouse Wheel(P+U)
+ 000c TACOMA Fingerprint V1.06.01
+ 000e Genius NetScroll Optical
+ 0013 TACOMA Fingerprint Mouse V1.06.01
+ 001a Genius WebScroll+
+ 002e NetScroll + Traveler / NetScroll 110
+ 0036 Pocket Mouse LE
+ 0039 NetScroll+ Superior
+ 003a NetScroll+ Mini Traveler / Genius NetScroll 120
+ 004c Slimstar Pro Keyboard
+ 0056 Ergo 300 Mouse
+ 0057 Enhanced Gaming Device
+ 0059 Enhanced Laser Device
+ 005a Enhanced Device
+ 005b Enhanced Device
+ 005c Enhanced Laser Gaming Device
+ 005d Enhanced Device
+ 0061 Bluetooth Dongle
+ 0066 Genius Traveler 1000 Wireless Mouse
+ 0072 Navigator 335
+ 0083 Bluetooth Dongle
+ 0087 Ergo 525V Laser Mouse
+ 0089 Genius Traveler 350
+ 00ca Pen Mouse
+ 0100 EasyPen Tablet
+ 0101 CueCat
+ 011b NetScroll T220
+ 1001 Joystick
+ 1002 Game Pad
+ 1003 Genius VideoCam
+ 1004 Flight2000 F-23 Joystick
+ 100a Aashima Technology Trust Sight Fighter Vibration Feedback Joystick
+ 2001 ColorPage-Vivid Pro Scanner
+ 2004 ColorPage-HR6 V1 Scanner
+ 2005 ColorPage-HR6/Vivid3
+ 2007 ColorPage-HR6 V2 Scanner
+ 2008 ColorPage-HR6 V2 Scanner
+ 2009 ColorPage-HR6A Scanner
+ 2011 ColorPage-Vivid3x Scanner
+ 2012 Plustek Scanner
+ 2013 ColorPage-HR7 Scanner
+ 2014 ColorPage-Vivid4
+ 2015 ColorPage-HR7LE Scanner
+ 2016 ColorPage-HR6X Scanner
+ 2017 ColorPage-Vivid3xe
+ 2018 ColorPage-HR7X
+ 2019 ColorPage-HR6X Slim
+ 201a ColorPage-Vivid4xe
+ 201b ColorPage-Vivid4x
+ 201c ColorPage-HR8
+ 201d ColorPage-Vivid 1200 X
+ 201e ColorPage-Slim 1200
+ 201f ColorPage-Vivid 1200 XE
+ 2020 ColorPage-Slim 1200 USB2
+ 2021 ColorPage-SF600
+ 3017 SPEED WHEEL 3 Vibration
+ 3018 Wireless 2.4Ghz Game Pad
+ 3019 10-Button USB Joystick with Vibration
+ 301a MaxFire G-12U Vibration
+ 301d Genius MaxFire MiniPad
+ 400f Genius TVGo DVB-T02Q MCE
+ 4012 TVGo DVB-T03 [AF9015]
+ 5003 G-pen 560 Tablet
+ 5004 G-pen Tablet
+ 505e Genius iSlim 330
+ 6001 GF3000F Ethernet Adapter
+ 7004 VideoCAM Express V2
+ 7006 Dsc 1.3 Smart Camera Device
+ 7007 VideoCAM Web
+ 7009 G-Shot G312 Still Camera Device
+ 700c VideoCAM Web V3
+ 700d G-Shot G511 Composite Device
+ 700f VideoCAM Web
+ 7012 WebCAM USB2.0
+ 7014 VideoCAM Live V3
+ 701c G-Shot G512 Still Camera
+ 7020 Sim 321C
+ 7025 Eye 311Q Camera
+ 7029 Genius Look 320s (SN9C201 + HV7131R)
+ 702f Genius Slim 322
+ 7035 i-Look 325T Camera
+ 7045 Genius Look 1320 V2
+ 704c Genius i-Look 1321
+ 704d Slim 1322AF
+ 7055 Slim 2020AF camera
+ 705a Asus USB2.0 Webcam
+ 705c Genius iSlim 1300AF
+ 7061 Genius iLook 1321 V2
+ 7066 Acer Crystal Eye Webcam
+ 7067 Genius iSlim 1300AF V2
+ 7068 Genius eFace 1325R
+ 706d Genius iSlim 2000AF V2
+ 7076 Genius FaceCam 312
+ 7079 FaceCam 2025R
+ 707f TVGo DVB-T03 [RTL2832]
+ 7088 WideCam 1050
+ 7089 Genius FaceCam 320
+ 708c Genius WideCam F100
+0459 Adobe Systems, Inc.
+045a SONICblue, Inc.
+ 07da Supra Express 56K modem
+ 0b4a SupraMax 2890 56K Modem [Lucent Atlas]
+ 0b68 SupraMax 56K Modem
+ 5001 Rio 600 MP3 Player
+ 5002 Rio 800 MP3 Player
+ 5003 Nike Psa/Play MP3 Player
+ 5005 Rio S10 MP3 Player
+ 5006 Rio S50 MP3 Player
+ 5007 Rio S35 MP3 Player
+ 5008 Rio 900 MP3 Player
+ 5009 Rio S30 MP3 Player
+ 500d Fuse MP3 Player
+ 500e Chiba MP3 Player
+ 500f Cali MP3 Player
+ 5010 Rio S11 MP3 Player
+ 501c Virgin MPF-1000
+ 501d Rio Fuse
+ 501e Rio Chiba
+ 501f Rio Cali
+ 503f Cali256 MP3 Player
+ 5202 Rio Riot MP3 Player
+ 5210 Rio Karma Music Player
+ 5220 Rio Nitrus MP3 Player
+ 5221 Rio Eigen
+045b Hitachi, Ltd
+ 0053 RX610 RX-Stick
+045d Nortel Networks, Ltd
+045e Microsoft Corp.
+ 0007 SideWinder Game Pad
+ 0008 SideWinder Precision Pro
+ 0009 IntelliMouse
+ 000b Natural Keyboard Elite
+ 000e SideWinder® Freestyle Pro
+ 0014 Digital Sound System 80
+ 001a SideWinder Precision Racing Wheel
+ 001b SideWinder Force Feedback 2 Joystick
+ 001c Internet Keyboard Pro
+ 001d Natural Keyboard Pro
+ 001e IntelliMouse Explorer
+ 0023 Trackball Optical
+ 0024 Trackball Explorer
+ 0025 IntelliEye Mouse
+ 0026 SideWinder GamePad Pro
+ 0027 SideWinder PnP GamePad
+ 0028 SideWinder Dual Strike
+ 0029 IntelliMouse Optical
+ 002b Internet Keyboard Pro
+ 002d Internet Keyboard
+ 002f Integrated Hub
+ 0033 Sidewinder Strategic Commander
+ 0034 SideWinder Force Feedback Wheel
+ 0038 SideWinder Precision 2
+ 0039 IntelliMouse Optical
+ 003b SideWinder Game Voice
+ 003c SideWinder Joystick
+ 0040 Wheel Mouse Optical
+ 0047 IntelliMouse Explorer 3.0
+ 0048 Office Keyboard 1.0A
+ 0053 Optical Mouse
+ 0059 Wireless IntelliMouse Explorer
+ 005c Office Keyboard (106/109)
+ 005f Wireless MultiMedia Keyboard
+ 0061 Wireless MultiMedia Keyboard (106/109)
+ 0063 Wireless Natural MultiMedia Keyboard
+ 0065 Wireless Natural MultiMedia Keyboard (106/109)
+ 006a Wireless Optical Mouse (IntelliPoint)
+ 006d eHome Remote Control Keyboard keys
+ 006e MN-510 802.11b Wireless Adapter [Intersil ISL3873B]
+ 006f Smart Display Reference Device
+ 0070 Wireless MultiMedia Keyboard
+ 0071 Wireless MultiMedia Keyboard (106/109)
+ 0072 Wireless Natural MultiMedia Keyboard
+ 0073 Wireless Natural MultiMedia Keyboard (106/109)
+ 0079 IXI Ogo CT-17 handheld device
+ 007a 10/100 USB NIC
+ 007d Notebook Optical Mouse
+ 007e Wireless Transceiver for Bluetooth
+ 0080 Digital Media Pro Keyboard
+ 0083 Basic Optical Mouse
+ 0084 Basic Optical Mouse
+ 008a Wireless Optical Desktop Receiver 2.0A
+ 008b Dual Receiver Wireless Mouse (IntelliPoint)
+ 008c Wireless Intellimouse Explorer 2.0
+ 0095 IntelliMouse Explorer 4.0 (IntelliPoint)
+ 009c Wireless Transceiver for Bluetooth 2.0
+ 009d Wireless Optical Desktop 3.0
+ 00a0 eHome Infrared Receiver
+ 00a4 Compact Optical Mouse, model 1016
+ 00b0 Digital Media Pro Keyboard
+ 00b4 Digital Media Keyboard 1.0A
+ 00b9 Wireless Optical Mouse 3.0
+ 00bb Fingerprint Reader
+ 00bc Fingerprint Reader
+ 00bd Fingerprint Reader
+ 00c2 MN-710 802.11g Wireless Adapter [Intersil ISL3886]
+ 00c9 MTP Device
+ 00ca Fingerprint Reader
+ 00cb Basic Optical Mouse v2.0
+ 00ce Generic PPC Flash device
+ 00d1 Optical Mouse with Tilt Wheel
+ 00da eHome Infrared Receiver
+ 00db Natural Ergonomic Keyboard 4000 V1.0
+ 00dd Comfort Curve Keyboard 2000 V1.0
+ 00e1 Wireless Laser Mouse 6000 Receiver
+ 00f4 LifeCam VX-6000 (SN9C20x + OV9650)
+ 00f5 LifeCam VX-3000
+ 00f6 Comfort Optical Mouse 1000
+ 00f7 LifeCam VX-1000
+ 00f8 LifeCam NX-6000
+ 00f9 Wireless Desktop Receiver 3.1
+ 0202 Xbox Controller
+ 0280 Xbox Memory Unit (8MB)
+ 0283 Xbox Communicator
+ 0284 Xbox DVD Playback Kit
+ 0285 Xbox Controller S
+ 0288 Xbox Controller S Hub
+ 0289 Xbox Controller S
+ 028b Xbox360 DVD Emulator
+ 028d Xbox360 Memory Unit 64MB
+ 028e Xbox360 Controller
+ 028f Xbox360 Wireless Controller
+ 0290 Xbox360 Performance Pipe (PIX)
+ 0291 Xbox 360 Wireless Receiver for Windows
+ 0292 Xbox360 Wireless Networking Adapter
+ 029c Xbox360 HD-DVD Drive
+ 029d Xbox360 HD-DVD Drive
+ 029e Xbox360 HD-DVD Memory Unit
+ 02a0 Xbox360 Big Button IR
+ 02a1 Xbox 360 Wireless Receiver for Windows
+ 02a8 Xbox360 Wireless N Networking Adapter [Atheros AR7010+AR9280]
+ 02ad Xbox NUI Audio
+ 02ae Xbox NUI Camera
+ 02b0 Xbox NUI Motor
+ 02b6 Xbox360 Bluetooth Wireless Headset
+ 02be Kinect for Windows NUI Audio
+ 02bf Kinect for Windows NUI Camera
+ 02c2 Kinect for Windows NUI Motor
+ 02d1 Xbox One Controller
+ 02d5 Xbox One Digital TV Tuner
+ 02dd Xbox One Controller (Covert Forces/Firmware 2015)
+ 02e3 Xbox One Elite Controller
+ 02e6 Wireless XBox Controller Dongle
+ 02ea Xbox One S Controller
+ 0400 Windows Powered Pocket PC 2002
+ 0401 Windows Powered Pocket PC 2002
+ 0402 Windows Powered Pocket PC 2002
+ 0403 Windows Powered Pocket PC 2002
+ 0404 Windows Powered Pocket PC 2002
+ 0405 Windows Powered Pocket PC 2002
+ 0406 Windows Powered Pocket PC 2002
+ 0407 Windows Powered Pocket PC 2002
+ 0408 Windows Powered Pocket PC 2002
+ 0409 Windows Powered Pocket PC 2002
+ 040a Windows Powered Pocket PC 2002
+ 040b Windows Powered Pocket PC 2002
+ 040c Windows Powered Pocket PC 2002
+ 040d Windows Powered Pocket PC 2002
+ 040e Windows Powered Pocket PC 2002
+ 040f Windows Powered Pocket PC 2002
+ 0410 Windows Powered Pocket PC 2002
+ 0411 Windows Powered Pocket PC 2002
+ 0412 Windows Powered Pocket PC 2002
+ 0413 Windows Powered Pocket PC 2002
+ 0414 Windows Powered Pocket PC 2002
+ 0415 Windows Powered Pocket PC 2002
+ 0416 Windows Powered Pocket PC 2002
+ 0417 Windows Powered Pocket PC 2002
+ 0432 Windows Powered Pocket PC 2003
+ 0433 Windows Powered Pocket PC 2003
+ 0434 Windows Powered Pocket PC 2003
+ 0435 Windows Powered Pocket PC 2003
+ 0436 Windows Powered Pocket PC 2003
+ 0437 Windows Powered Pocket PC 2003
+ 0438 Windows Powered Pocket PC 2003
+ 0439 Windows Powered Pocket PC 2003
+ 043a Windows Powered Pocket PC 2003
+ 043b Windows Powered Pocket PC 2003
+ 043c Windows Powered Pocket PC 2003
+ 043d Becker Traffic Assist Highspeed 7934
+ 043e Windows Powered Pocket PC 2003
+ 043f Windows Powered Pocket PC 2003
+ 0440 Windows Powered Pocket PC 2003
+ 0441 Windows Powered Pocket PC 2003
+ 0442 Windows Powered Pocket PC 2003
+ 0443 Windows Powered Pocket PC 2003
+ 0444 Windows Powered Pocket PC 2003
+ 0445 Windows Powered Pocket PC 2003
+ 0446 Windows Powered Pocket PC 2003
+ 0447 Windows Powered Pocket PC 2003
+ 0448 Windows Powered Pocket PC 2003
+ 0449 Windows Powered Pocket PC 2003
+ 044a Windows Powered Pocket PC 2003
+ 044b Windows Powered Pocket PC 2003
+ 044c Windows Powered Pocket PC 2003
+ 044d Windows Powered Pocket PC 2003
+ 044e Windows Powered Pocket PC 2003
+ 044f Windows Powered Pocket PC 2003
+ 0450 Windows Powered Pocket PC 2003
+ 0451 Windows Powered Pocket PC 2003
+ 0452 Windows Powered Pocket PC 2003
+ 0453 Windows Powered Pocket PC 2003
+ 0454 Windows Powered Pocket PC 2003
+ 0455 Windows Powered Pocket PC 2003
+ 0456 Windows Powered Pocket PC 2003
+ 0457 Windows Powered Pocket PC 2003
+ 0458 Windows Powered Pocket PC 2003
+ 0459 Windows Powered Pocket PC 2003
+ 045a Windows Powered Pocket PC 2003
+ 045b Windows Powered Pocket PC 2003
+ 045c Windows Powered Pocket PC 2003
+ 045d Windows Powered Pocket PC 2003
+ 045e Windows Powered Pocket PC 2003
+ 045f Windows Powered Pocket PC 2003
+ 0460 Windows Powered Pocket PC 2003
+ 0461 Windows Powered Pocket PC 2003
+ 0462 Windows Powered Pocket PC 2003
+ 0463 Windows Powered Pocket PC 2003
+ 0464 Windows Powered Pocket PC 2003
+ 0465 Windows Powered Pocket PC 2003
+ 0466 Windows Powered Pocket PC 2003
+ 0467 Windows Powered Pocket PC 2003
+ 0468 Windows Powered Pocket PC 2003
+ 0469 Windows Powered Pocket PC 2003
+ 046a Windows Powered Pocket PC 2003
+ 046b Windows Powered Pocket PC 2003
+ 046c Windows Powered Pocket PC 2003
+ 046d Windows Powered Pocket PC 2003
+ 046e Windows Powered Pocket PC 2003
+ 046f Windows Powered Pocket PC 2003
+ 0470 Windows Powered Pocket PC 2003
+ 0471 Windows Powered Pocket PC 2003
+ 0472 Windows Powered Pocket PC 2003
+ 0473 Windows Powered Pocket PC 2003
+ 0474 Windows Powered Pocket PC 2003
+ 0475 Windows Powered Pocket PC 2003
+ 0476 Windows Powered Pocket PC 2003
+ 0477 Windows Powered Pocket PC 2003
+ 0478 Windows Powered Pocket PC 2003
+ 0479 Windows Powered Pocket PC 2003
+ 047a Windows Powered Pocket PC 2003
+ 047b Windows Powered Pocket PC 2003
+ 04c8 Windows Powered Smartphone 2002
+ 04c9 Windows Powered Smartphone 2002
+ 04ca Windows Powered Smartphone 2002
+ 04cb Windows Powered Smartphone 2002
+ 04cc Windows Powered Smartphone 2002
+ 04cd Windows Powered Smartphone 2002
+ 04ce Windows Powered Smartphone 2002
+ 04d7 Windows Powered Smartphone 2003
+ 04d8 Windows Powered Smartphone 2003
+ 04d9 Windows Powered Smartphone 2003
+ 04da Windows Powered Smartphone 2003
+ 04db Windows Powered Smartphone 2003
+ 04dc Windows Powered Smartphone 2003
+ 04dd Windows Powered Smartphone 2003
+ 04de Windows Powered Smartphone 2003
+ 04df Windows Powered Smartphone 2003
+ 04e0 Windows Powered Smartphone 2003
+ 04e1 Windows Powered Smartphone 2003
+ 04e2 Windows Powered Smartphone 2003
+ 04e3 Windows Powered Smartphone 2003
+ 04e4 Windows Powered Smartphone 2003
+ 04e5 Windows Powered Smartphone 2003
+ 04e6 Windows Powered Smartphone 2003
+ 04e7 Windows Powered Smartphone 2003
+ 04e8 Windows Powered Smartphone 2003
+ 04e9 Windows Powered Smartphone 2003
+ 04ea Windows Powered Smartphone 2003
+ 04ec Windows Phone (Zune)
+ 063e Zune HD Media Player
+ 0640 KIN Phone
+ 0641 KIN Phone
+ 0642 KIN Phone
+ 0707 Wireless Laser Mouse 8000
+ 0708 Transceiver v 3.0 for Bluetooth
+ 070a Charon Bluetooth Dongle (DFU)
+ 070f LifeChat LX-3000 Headset
+ 0710 Zune Media Player
+ 0713 Wireless Presenter Mouse 8000
+ 0719 Xbox 360 Wireless Adapter
+ 071f Mouse/Keyboard 2.4GHz Transceiver V2.0
+ 0721 LifeCam NX-3000 (UVC-compliant)
+ 0723 LifeCam VX-7000 (UVC-compliant)
+ 0724 SideWinder Mouse
+ 0728 LifeCam VX-5000
+ 0730 Digital Media Keyboard 3000
+ 0734 Wireless Optical Desktop 700
+ 0736 Sidewinder X5 Mouse
+ 0737 Compact Optical Mouse 500
+ 0745 Nano Transceiver v1.0 for Bluetooth
+ 0750 Wired Keyboard 600
+ 0752 Wired Keyboard 400
+ 075d LifeCam Cinema
+ 0761 LifeCam VX-2000
+ 0766 LifeCam VX-800
+ 0768 Sidewinder X4
+ 076c Comfort Mouse 4500
+ 076d LifeCam HD-5000
+ 0772 LifeCam Studio
+ 0779 LifeCam HD-3000
+ 077f LifeChat LX-6000 Headset
+ 0780 Comfort Curve Keyboard 3000
+ 0797 Optical Mouse 200
+ 07a5 Wireless Receiver 1461C
+ 07b9 Wired Keyboard 200
+ 07ca Surface Pro 3 Docking Station Audio Device
+ 07f8 Wired Keyboard 600 (model 1576)
+ 07fd Nano Transceiver 1.1
+ 930a ISOUSB.SYS Intel 82930 Isochronous IO Test Board
+ ffca Catalina
+ fff8 Keyboard
+ ffff Windows CE Mass Storage
+0460 Ace Cad Enterprise Co., Ltd
+ 0004 Tablet (5x3.75)
+ 0006 LCD Tablet (12x9)
+ 0008 Tablet (3x2.25)
+0461 Primax Electronics, Ltd
+ 0010 HP PR1101U / Primax PMX-KPR1101U Keyboard
+ 0300 G2-300 Scanner
+ 0301 G2E-300 Scanner
+ 0302 G2-300 #2 Scanner
+ 0303 G2E-300 #2 Scanner
+ 0340 Colorado 9600 Scanner
+ 0341 Colorado 600u Scanner
+ 0345 Visioneer 6200 Scanner
+ 0346 Memorex Maxx 6136u Scanner
+ 0347 Primascan Colorado 2600u/Visioneer 4400 Scanner
+ 0360 Colorado 19200 Scanner
+ 0361 Colorado 1200u Scanner
+ 0363 VistaScan Astra 3600(ENG)
+ 0364 LG Electronics Scanworks 600U Scanner
+ 0365 VistaScan Astra 3600(ENG)
+ 0366 6400
+ 0367 VistaScan Astra 3600(ENG)
+ 0371 Visioneer Onetouch 8920 Scanner
+ 0374 UMAX Astra 2500
+ 0375 VistaScan Astra 3600(ENG)
+ 0377 Medion MD 5345 Scanner
+ 0378 VistaScan Astra 3600(ENG)
+ 037b Medion MD 6190 Scanner
+ 037c VistaScan Astra 3600(ENG)
+ 0380 G2-600 Scanner
+ 0381 ReadyScan 636i Scanner
+ 0382 G2-600 #2 Scanner
+ 0383 G2E-600 Scanner
+ 038a UMAX Astra 3000/3600
+ 038b Xerox 2400 Onetouch
+ 038c UMAX Astra 4100
+ 0392 Medion/Lifetec/Tevion/Cytron MD 6190
+ 03a8 9420M
+ 0813 IBM UltraPort Camera
+ 0815 Micro Innovations IC200 Webcam
+ 0819 Fujifilm IX-30 Camera [webcam mode]
+ 081a Fujifilm IX-30 Camera [storage mode]
+ 081c Elitegroup ECS-C11 Camera
+ 081d Elitegroup ECS-C11 Storage
+ 0a00 Micro Innovations Web Cam 320
+ 4d01 Comfort Keyboard
+ 4d02 Mouse-in-a-Box
+ 4d03 Kensington Mouse-in-a-box
+ 4d04 Mouse
+ 4d06 Balless Mouse (HID)
+ 4d0f HP Optical Mouse
+ 4d15 Dell Optical Mouse
+ 4d17 Optical Mouse
+ 4d20 HP Optical Mouse
+ 4d2a PoPo Elixir Mouse (HID)
+ 4d2b Wireless Laser Mini Mouse (HID)
+ 4d2c PoPo Mini Pointer Mouse (HID)
+ 4d2e Optical Mobile Mouse (HID)
+ 4d51 0Y357C PMX-MMOCZUL (B) [Dell Laser Mouse]
+ 4d62 HP Laser Mobile Mini Mouse
+ 4d75 Rocketfish RF-FLBTAD Bluetooth Adapter
+ 4d81 Dell N889 Optical Mouse
+ 4de7 webcam
+0463 MGE UPS Systems
+ 0001 UPS
+ ffff UPS
+0464 AMP/Tycoelectronics Corp.
+0467 AT&T Paradyne
+0468 Wieson Technologies Co., Ltd
+046a Cherry GmbH
+ 0001 Keyboard
+ 0003 My3000 Hub
+ 0004 CyBoard Keyboard
+ 0005 XX33 SmartCard Reader Keyboard
+ 0008 Wireless Keyboard and Mouse
+ 0010 SmartBoard XX44
+ 0011 G83 (RS 6000) Keyboard
+ 0021 CyMotion Expert Combo
+ 0023 CyMotion Master Linux Keyboard G230
+ 0027 CyMotion Master Solar Keyboard
+ 002a Wireless Mouse & Keyboard
+ 002d SmartTerminal XX44
+ 003c Raptor Gaming Keyboard
+ 003d Raptor Gaming Keyboard Integrated Hub
+ 003e SmartTerminal ST-2xxx
+ 0041 G86 6240 Keyboard
+ 0080 eHealth Terminal ST 1503
+ 0081 eHealth Keyboard G87 1504
+ 0106 R-300 Wireless Mouse Receiver
+ 010d MX-Board 3.0 Keyboard
+ b090 Keyboard
+ b091 Mouse
+046b American Megatrends, Inc.
+ 0001 Keyboard
+ 0101 PS/2 Keyboard, Mouse & Joystick Ports
+ 0301 USB 1.0 Hub
+ 0500 Serial & Parallel Ports
+ ff10 Virtual Keyboard and Mouse
+046c Toshiba Corp., Digital Media Equipment
+046d Logitech, Inc.
+ 0082 Acer Aspire 5672 Webcam
+ 0200 WingMan Extreme Joystick
+ 0203 M2452 Keyboard
+ 0301 M4848 Mouse
+ 0401 HP PageScan
+ 0402 NEC PageScan
+ 040f Logitech/Storm PageScan
+ 0430 Mic (Cordless)
+ 0801 QuickCam Home
+ 0802 Webcam C200
+ 0804 Webcam C250
+ 0805 Webcam C300
+ 0807 Webcam B500
+ 0808 Webcam C600
+ 0809 Webcam Pro 9000
+ 080a Portable Webcam C905
+ 080f Webcam C120
+ 0810 QuickCam Pro
+ 0819 Webcam C210
+ 081b Webcam C310
+ 081d HD Webcam C510
+ 0820 QuickCam VC
+ 0821 HD Webcam C910
+ 0825 Webcam C270
+ 0826 HD Webcam C525
+ 0828 HD Webcam B990
+ 082b Webcam C170
+ 082d HD Pro Webcam C920
+ 0830 QuickClip
+ 0836 B525 HD Webcam
+ 0837 BCC950 ConferenceCam
+ 0840 QuickCam Express
+ 0843 Webcam C930e
+ 0850 QuickCam Web
+ 0870 QuickCam Express
+ 0890 QuickCam Traveler
+ 0892 OrbiCam
+ 0894 CrystalCam
+ 0895 QuickCam for Dell Notebooks
+ 0896 OrbiCam
+ 0897 QuickCam for Dell Notebooks
+ 0899 QuickCam for Dell Notebooks
+ 089d QuickCam E2500 series
+ 08a0 QuickCam IM
+ 08a1 QuickCam IM with sound
+ 08a2 Labtec Webcam Pro
+ 08a3 QuickCam QuickCam Chat
+ 08a6 QuickCam IM
+ 08a7 QuickCam Image
+ 08a9 Notebook Deluxe
+ 08aa Labtec Notebooks
+ 08ac QuickCam Cool
+ 08ad QuickCam Communicate STX
+ 08ae QuickCam for Notebooks
+ 08af QuickCam Easy/Cool
+ 08b0 QuickCam 3000 Pro [pwc]
+ 08b1 QuickCam Notebook Pro
+ 08b2 QuickCam Pro 4000
+ 08b3 QuickCam Zoom
+ 08b4 QuickCam Zoom
+ 08b5 QuickCam Sphere
+ 08b9 QuickCam IM
+ 08bd Microphone (Pro 4000)
+ 08c0 QuickCam Pro 3000
+ 08c1 QuickCam Fusion
+ 08c2 QuickCam PTZ
+ 08c3 Camera (Notebooks Pro)
+ 08c5 QuickCam Pro 5000
+ 08c6 QuickCam for DELL Notebooks
+ 08c7 QuickCam OEM Cisco VT Camera II
+ 08c9 QuickCam Ultra Vision
+ 08ca Mic (Fusion)
+ 08cb Mic (Notebooks Pro)
+ 08cc Mic (PTZ)
+ 08ce QuickCam Pro 5000
+ 08cf QuickCam UpdateMe
+ 08d0 QuickCam Express
+ 08d7 QuickCam Communicate STX
+ 08d8 QuickCam for Notebook Deluxe
+ 08d9 QuickCam IM/Connect
+ 08da QuickCam Messanger
+ 08dd QuickCam for Notebooks
+ 08e0 QuickCam Express
+ 08e1 Labtec Webcam
+ 08f0 QuickCam Messenger
+ 08f1 QuickCam Express
+ 08f2 Microphone (Messenger)
+ 08f3 QuickCam Express
+ 08f4 Labtec Webcam
+ 08f5 QuickCam Messenger Communicate
+ 08f6 QuickCam Messenger Plus
+ 0900 ClickSmart 310
+ 0901 ClickSmart 510
+ 0903 ClickSmart 820
+ 0905 ClickSmart 820
+ 0910 QuickCam Cordless
+ 0920 QuickCam Express
+ 0921 Labtec Webcam
+ 0922 QuickCam Live
+ 0928 QuickCam Express
+ 0929 Labtec Webcam Pro
+ 092a QuickCam for Notebooks
+ 092b Labtec Webcam Plus
+ 092c QuickCam Chat
+ 092d QuickCam Express / Go
+ 092e QuickCam Chat
+ 092f QuickCam Express Plus
+ 0950 Pocket Camera
+ 0960 ClickSmart 420
+ 0970 Pocket750
+ 0990 QuickCam Pro 9000
+ 0991 QuickCam Pro for Notebooks
+ 0992 QuickCam Communicate Deluxe
+ 0994 QuickCam Orbit/Sphere AF
+ 09a1 QuickCam Communicate MP/S5500
+ 09a2 QuickCam Communicate Deluxe/S7500
+ 09a4 QuickCam E 3500
+ 09a5 Quickcam 3000 For Business
+ 09a6 QuickCam Vision Pro
+ 09b0 Acer OrbiCam
+ 09b2 Fujitsu Webcam
+ 09c0 QuickCam for Dell Notebooks Mic
+ 09c1 QuickCam Deluxe for Notebooks
+ 0a01 USB Headset
+ 0a02 Premium Stereo USB Headset 350
+ 0a03 Logitech USB Microphone
+ 0a04 V20 portable speakers (USB powered)
+ 0a07 Z-10 Speakers
+ 0a0b ClearChat Pro USB
+ 0a0c Clear Chat Comfort USB Headset
+ 0a13 Z-5 Speakers
+ 0a14 USB Headset
+ 0a15 G35 Headset
+ 0a17 G330 Headset
+ 0a1f G930
+ 0a29 H600 [Wireless Headset]
+ 0a37 USB Headset H540
+ 0a38 Headset H340
+ 0a44 Headset H390
+ 0a45 960 Headset
+ 0a4d G430 Surround Sound Gaming Headset
+ 0a5b G933 Wireless Headset Dongle
+ 0b02 C-UV35 [Bluetooth Mini-Receiver] (HID proxy mode)
+ 8801 Video Camera
+ b014 Bluetooth Mouse M336/M337/M535
+ b305 BT Mini-Receiver
+ bfe4 Premium Optical Wheel Mouse
+ c000 N43 [Pilot Mouse]
+ c001 N48/M-BB48/M-UK96A [FirstMouse Plus]
+ c002 M-BA47 [MouseMan Plus]
+ c003 MouseMan
+ c004 WingMan Gaming Mouse
+ c005 WingMan Gaming Wheel Mouse
+ c00b MouseMan Wheel
+ c00c Optical Wheel Mouse
+ c00d MouseMan Wheel+
+ c00e M-BJ58/M-BJ69 Optical Wheel Mouse
+ c00f MouseMan Traveler/Mobile
+ c011 Optical MouseMan
+ c012 Mouseman Dual Optical
+ c014 Corded Workstation Mouse
+ c015 Corded Workstation Mouse
+ c016 Optical Wheel Mouse
+ c018 Optical Wheel Mouse
+ c019 Optical Tilt Wheel Mouse
+ c01a M-BQ85 Optical Wheel Mouse
+ c01b MX310 Optical Mouse
+ c01c Optical Mouse
+ c01d MX510 Optical Mouse
+ c01e MX518 Optical Mouse
+ c024 MX300 Optical Mouse
+ c025 MX500 Optical Mouse
+ c030 iFeel Mouse
+ c031 iFeel Mouse+
+ c032 MouseMan iFeel
+ c033 iFeel MouseMan+
+ c034 MouseMan Optical
+ c035 Mouse
+ c036 Mouse
+ c037 Mouse
+ c038 Mouse
+ c03d M-BT96a Pilot Optical Mouse
+ c03e Premium Optical Wheel Mouse (M-BT58)
+ c03f M-BT85 [UltraX Optical Mouse]
+ c040 Corded Tilt-Wheel Mouse
+ c041 G5 Laser Mouse
+ c042 G3 Laser Mouse
+ c043 MX320/MX400 Laser Mouse
+ c044 LX3 Optical Mouse
+ c045 Optical Mouse
+ c046 RX1000 Laser Mouse
+ c047 Laser Mouse M-UAL120
+ c048 G9 Laser Mouse
+ c049 G5 Laser Mouse
+ c050 RX 250 Optical Mouse
+ c051 G3 (MX518) Optical Mouse
+ c053 Laser Mouse
+ c054 Bluetooth mini-receiver
+ c058 M115 Mouse
+ c05a M90/M100 Optical Mouse
+ c05b M-U0004 810-001317 [B110 Optical USB Mouse]
+ c05d Optical Mouse
+ c05f M115 Optical Mouse
+ c061 RX1500 Laser Mouse
+ c062 M-UAS144 [LS1 Laser Mouse]
+ c063 DELL Laser Mouse
+ c064 M110 corded optical mouse (M-B0001)
+ c066 G9x Laser Mouse
+ c068 G500 Laser Mouse
+ c069 M-U0007 [Corded Mouse M500]
+ c06a USB Optical Mouse
+ c06b G700 Wireless Gaming Mouse
+ c06c Optical Mouse
+ c077 M105 Optical Mouse
+ c07c M-R0017 [G700s Rechargeable Gaming Mouse]
+ c07d G502 Mouse
+ c07e G402 Gaming Mouse
+ c101 UltraX Media Remote
+ c110 Harmony 785/880/885 Remote
+ c111 Harmony 525 Remote
+ c112 Harmony 890 Remote
+ c11f Harmony 900/1100 Remote
+ c121 Harmony One Remote
+ c122 Harmony 650/700 Remote
+ c124 Harmony 300/350 Remote
+ c125 Harmony 200 Remote
+ c126 Harmony Link
+ c129 Harmony Hub
+ c12b Harmony Touch/Ultimate Remote
+ c201 WingMan Extreme Joystick with Throttle
+ c202 WingMan Formula
+ c207 WingMan Extreme Digital 3D
+ c208 WingMan Gamepad Extreme
+ c209 WingMan Gamepad
+ c20a WingMan RumblePad
+ c20b WingMan Action Pad
+ c20c WingMan Precision
+ c20d WingMan Attack 2
+ c20e WingMan Formula GP
+ c211 iTouch Cordless Receiver
+ c212 WingMan Extreme Digital 3D
+ c213 J-UH16 (Freedom 2.4 Cordless Joystick)
+ c214 ATK3 (Attack III Joystick)
+ c215 Extreme 3D Pro
+ c216 Dual Action Gamepad
+ c218 Logitech RumblePad 2 USB
+ c219 Cordless RumblePad 2
+ c21a Precision Gamepad
+ c21c G13 Advanced Gameboard
+ c21d F310 Gamepad [XInput Mode]
+ c21e F510 Gamepad [XInput Mode]
+ c21f F710 Wireless Gamepad [XInput Mode]
+ c221 G11/G15 Keyboard / Keyboard
+ c222 G15 Keyboard / LCD
+ c223 G11/G15 Keyboard / USB Hub
+ c225 G11/G15 Keyboard / G keys
+ c226 G15 Refresh Keyboard
+ c227 G15 Refresh Keyboard
+ c228 G19 Gaming Keyboard
+ c229 G19 Gaming Keyboard Macro Interface
+ c22a Gaming Keyboard G110
+ c22b Gaming Keyboard G110 G-keys
+ c22d G510 Gaming Keyboard
+ c22e G510 Gaming Keyboard onboard audio
+ c231 G13 Virtual Mouse
+ c245 G400 Optical Mouse
+ c246 Gaming Mouse G300
+ c248 G105 Gaming Keyboard
+ c24a G600 Gaming Mouse
+ c24c G400s Optical Mouse
+ c24d G710 Gaming Keyboard
+ c24e G500s Laser Gaming Mouse
+ c281 WingMan Force
+ c283 WingMan Force 3D
+ c285 WingMan Strike Force 3D
+ c286 Force 3D Pro
+ c287 Flight System G940
+ c291 WingMan Formula Force
+ c293 WingMan Formula Force GP
+ c294 Driving Force
+ c295 Momo Force Steering Wheel
+ c298 Driving Force Pro
+ c299 G25 Racing Wheel
+ c29b G27 Racing Wheel
+ c29c Speed Force Wireless Wheel for Wii
+ c2a0 Wingman Force Feedback Mouse
+ c2a1 WingMan Force Feedback Mouse
+ c2ab G13 Joystick
+ c301 iTouch Keyboard
+ c302 iTouch Pro Keyboard
+ c303 iTouch Keyboard
+ c305 Internet Keyboard
+ c307 Internet Keyboard
+ c308 Internet Navigator Keyboard
+ c309 Y-BF37 [Internet Navigator Keyboard]
+ c30a iTouch Composite
+ c30b NetPlay Keyboard
+ c30c Internet Keys (X)
+ c30d Internet Keys
+ c30e UltraX Keyboard (Y-BL49)
+ c30f Logicool HID-Compliant Keyboard (106 key)
+ c311 Y-UF49 [Internet Pro Keyboard]
+ c312 DeLuxe 250 Keyboard
+ c313 Internet 350 Keyboard
+ c315 Classic Keyboard 200
+ c316 HID-Compliant Keyboard
+ c317 Wave Corded Keyboard
+ c318 Illuminated Keyboard
+ c31a Comfort Wave 450
+ c31b Compact Keyboard K300
+ c31c Keyboard K120
+ c31d Media Keyboard K200
+ c332 G502 Proteus Spectrum Optical Mouse
+ c401 TrackMan Marble Wheel
+ c402 Marble Mouse (2-button)
+ c403 Turbo TrackMan Marble FX
+ c404 TrackMan Wheel
+ c408 Marble Mouse (4-button)
+ c501 Cordless Mouse Receiver
+ c502 Cordless Mouse & iTouch Keys
+ c503 Cordless Mouse+Keyboard Receiver
+ c504 Cordless Mouse+Keyboard Receiver
+ c505 Cordless Mouse+Keyboard Receiver
+ c506 MX700 Cordless Mouse Receiver
+ c508 Cordless Trackball
+ c509 Cordless Keyboard & Mouse
+ c50a Cordless Mouse
+ c50b Cordless Desktop Optical
+ c50c Cordless Desktop S510
+ c50d Cordless Mouse
+ c50e Cordless Mouse Receiver
+ c510 Cordless Mouse
+ c512 LX-700 Cordless Desktop Receiver
+ c513 MX3000 Cordless Desktop Receiver
+ c514 Cordless Mouse
+ c515 Cordless 2.4 GHz Presenter Presentation remote control
+ c517 LX710 Cordless Desktop Laser
+ c518 MX610 Laser Cordless Mouse
+ c51a MX Revolution/G7 Cordless Mouse
+ c51b V220 Cordless Optical Mouse for Notebooks
+ c521 Cordless Mouse Receiver
+ c525 MX Revolution Cordless Mouse
+ c526 Nano Receiver
+ c529 Logitech Keyboard + Mice
+ c52b Unifying Receiver
+ c52d R700 Remote Presenter receiver
+ c52e MK260 Wireless Combo Receiver
+ c52f Unifying Receiver
+ c531 C-U0007 [Unifying Receiver]
+ c532 Unifying Receiver
+ c534 Unifying Receiver
+ c603 3Dconnexion Spacemouse Plus XT
+ c605 3Dconnexion CADman
+ c606 3Dconnexion Spacemouse Classic
+ c621 3Dconnexion Spaceball 5000
+ c623 3Dconnexion Space Traveller 3D Mouse
+ c625 3Dconnexion Space Pilot 3D Mouse
+ c626 3Dconnexion Space Navigator 3D Mouse
+ c627 3Dconnexion Space Explorer 3D Mouse
+ c628 3Dconnexion Space Navigator for Notebooks
+ c629 3Dconnexion SpacePilot Pro 3D Mouse
+ c62b 3Dconnexion Space Mouse Pro
+ c640 NuLOOQ navigator
+ c702 Cordless Presenter
+ c703 Elite Keyboard Y-RP20 + Mouse MX900 (Bluetooth)
+ c704 diNovo Wireless Desktop
+ c705 MX900 Bluetooth Wireless Hub (C-UJ16A)
+ c707 Bluetooth wireless hub
+ c708 Bluetooth wireless hub
+ c709 BT Mini-Receiver (HCI mode)
+ c70a MX5000 Cordless Desktop
+ c70b BT Mini-Receiver (HID proxy mode)
+ c70c BT Mini-Receiver (HID proxy mode)
+ c70d Bluetooth wireless hub
+ c70e MX1000 Bluetooth Laser Mouse
+ c70f Bluetooth wireless hub
+ c712 Bluetooth wireless hub
+ c714 diNovo Edge Keyboard
+ c715 Bluetooth wireless hub
+ c71a Bluetooth wireless hub
+ c71d Bluetooth wireless hub
+ c71f diNovo Mini Wireless Keyboard
+ c720 Bluetooth wireless hub
+ ca03 MOMO Racing
+ ca04 Formula Vibration Feedback Wheel
+ cab1 Cordless Keyboard for Wii HID Receiver
+ d001 QuickCam Pro
+046e Behavior Tech. Computer Corp.
+ 0100 Keyboard
+ 3001 Mass Storage Device
+ 3002 Mass Storage Device
+ 3003 Mass Storage Device
+ 3005 Mass Storage Device
+ 3008 Mass Storage Device
+ 5250 KeyMaestro Multimedia Keyboard
+ 5273 KeyMaestro Multimedia Keyboard
+ 52e6 Cordless Mouse
+ 5308 KeyMaestro Keyboard
+ 5408 KeyMaestro Multimedia Keyboard/Hub
+ 5500 Portable Keyboard 86+9 keys (Model 6100C US)
+ 5550 5 button optical mouse model M873U
+ 5720 Smart Card Reader
+ 6782 BTC 7932 mouse+keyboard
+046f Crystal Semiconductor
+0471 Philips (or NXP)
+ 0101 DSS350 Digital Speaker System
+ 0104 DSS330 Digital Speaker System [uda1321]
+ 0105 UDA1321
+ 014f GoGear SA9200
+ 0160 MP3 Player
+ 0161 MP3 Player
+ 0163 GoGear SA1100
+ 0164 GoGear SA1110/02
+ 0165 GoGear SA1330
+ 0201 Hub
+ 0222 Creative Nomad Jukebox
+ 0302 PCA645VC Webcam [pwc]
+ 0303 PCA646VC Webcam [pwc]
+ 0304 Askey VC010 Webcam [pwc]
+ 0307 PCVC675K Webcam [pwc]
+ 0308 PCVC680K Webcam [pwc]
+ 030b PC VGA Camera (Vesta Fun)
+ 030c PCVC690K Webcam [pwc]
+ 0310 PCVC730K Webcam [pwc]
+ 0311 PCVC740K ToUcam Pro [pwc]
+ 0312 PCVC750K Webcam [pwc]
+ 0314 DMVC 1000K
+ 0316 DMVC 2000K Video Capture
+ 0321 FunCam
+ 0322 DMVC1300K PC Camera
+ 0325 SPC 200NC PC Camera
+ 0326 SPC 300NC PC Camera
+ 0327 Webcam SPC 6000 NC (Webcam w/ mic)
+ 0328 SPC 700NC PC Camera
+ 0329 SPC 900NC PC Camera / ORITE CCD Webcam(PC370R)
+ 032d SPC 210NC PC Camera
+ 032e SPC 315NC PC Camera
+ 0330 SPC 710NC PC Camera
+ 0331 SPC 1300NC PC Camera
+ 0332 SPC 1000NC PC Camera
+ 0333 SPC 620NC PC Camera
+ 0334 SPC 520/525NC PC Camera
+ 0401 Semiconductors CICT Keyboard
+ 0402 PS/2 Mouse on Semiconductors CICT Keyboard
+ 0406 15 inch Detachable Monitor
+ 0407 10 inch Mobile Monitor
+ 0408 SG3WA1/74 802.11b WLAN Adapter [Atmel AT76C503A]
+ 0471 Digital Speaker System
+ 0601 OVU1020 IR Dongle (Kbd+Mouse)
+ 0602 ATI Remote Wonder II Input Device
+ 0603 ATI Remote Wonder II Controller
+ 0608 eHome Infrared Receiver
+ 060a TSU9600 Remote Control
+ 060c Consumer Infrared Transceiver (HP)
+ 060d Consumer Infrared Transceiver (SRM5100)
+ 060e RF Dongle
+ 060f Consumer Infrared Transceiver
+ 0613 Infrared Transceiver
+ 0617 IEEE802.15.4 RF Dongle
+ 0619 TSU9400 Remote Control
+ 0666 Hantek DDS-3005 Arbitrary Waveform Generator
+ 0700 Semiconductors CICT Hub
+ 0701 150P1 TFT Display
+ 0809 AVNET Bluetooth Device
+ 0811 JR24 CDRW
+ 0814 DCCX38/P data cable
+ 0815 eHome Infrared Receiver
+ 0844 SA2111/02 1GB Flash Audio Player
+ 084a GoGear SA3125
+ 084e GoGear SA60xx (mtp)
+ 0888 Hantek DDS-3005 Arbitrary Waveform Generator
+ 1103 Digital Speaker System
+ 1120 Creative Rhomba MP3 player
+ 1125 Nike psa[128max Player
+ 1137 HDD065 MP3 player
+ 1201 Arima Bluetooth Device
+ 1230 Wireless Adapter 11g
+ 1232 SNU6500 Wireless Adapter
+ 1233 Wireless Adapter Bootloader Download
+ 1236 SNU5600 802.11bg
+ 1237 TalkTalk SNU5630NS/05 802.11bg
+ 1552 ISP 1581 Hi-Speed USB MPEG2 Encoder Reference Kit
+ 1801 Diva MP3 player
+ 200a Wireless Network Adapter
+ 200f 802.11n Wireless Adapter
+ 2021 SDE3273FC/97 2.5" SATA HDD Enclosure [INIC-1608L]
+ 2022 GoGear SA52XX
+ 2034 Webcam SPC530NC
+ 2036 Webcam SPC1030NC
+ 203f TSU9200 Remote Control
+ 2046 TSU9800 Remote Control
+ 204e GoGear RaGa (SA1942/02)
+ 205e TSU9300 Remote Control
+ 206c MCE IR Receiver - Spinel plusf0r ASUS
+ 2070 GoGear Mix
+ 2076 GoGear Aria
+ 2079 GoGear Opus
+ 2088 MCE IR Receiver with ALS- Spinel plus for ASUS
+ 209e PTA01 Wireless Adapter
+ 20b6 GoGear Vibe
+ 20d0 SPZ2000 Webcam [PixArt PAC7332]
+ 20e3 GoGear Raga
+ 20e4 GoGear ViBE 8GB
+ 2160 Mio LINK Heart Rate Monitor
+ 262c SPC230NC Webcam
+ 485d Senselock SenseIV v2.x
+ df55 LPCXpresso LPC-Link
+0472 Chicony Electronics Co., Ltd
+ 0065 PFU-65 Keyboard [Chicony]
+ b086 Asus USB2.0 Webcam
+ b091 Webcam
+0473 Sanyo Information Business Co., Ltd
+0474 Sanyo Electric Co., Ltd
+ 0110 Digital Voice Recorder R200
+ 0217 Xacti J2
+ 022f C5 Digital Media Camera (mass storage mode)
+ 0230 C5 Digital Media Camera (PictBridge mode)
+ 0231 C5 Digital Media Camera (PC control mode)
+ 0401 Optical Drive
+ 0701 SCP-4900 Cellphone
+ 071f Usb Com Port Enumerator
+ 0722 W33SA Camera
+0475 Relisys/Teco Information System
+ 0100 NEC Petiscan
+ 0103 Eclipse 1200U/Episode
+ 0210 Scorpio Ultra 3
+0476 AESP
+0477 Seagate Technology, Inc.
+0478 Connectix Corp.
+ 0001 QuickCam
+ 0002 QuickClip
+ 0003 QuickCam Pro
+0479 Advanced Peripheral Laboratories
+047a Semtech Corp.
+ 0004 ScreenCoder UR7HCTS2-USB
+047b Silitek Corp.
+ 0001 Keyboard
+ 0002 Keyboard and Mouse
+ 0011 SK-1688U Keyboard
+ 00f9 SK-1789u Keyboard
+ 0101 BlueTooth Keyboard and Mouse
+ 020b SK-3105 SmartCard Reader
+ 050e Internet Compact Keyboard
+ 1000 Trust Office Scan USB 19200
+ 1002 HP ScanJet 4300c Parallel Port
+047c Dell Computer Corp.
+ ffff UPS Tower 500W LV
+047d Kensington
+ 1001 Mouse*in*a*Box
+ 1002 Expert Mouse Pro
+ 1003 Orbit TrackBall
+ 1004 MouseWorks
+ 1005 TurboBall
+ 1006 TurboRing
+ 1009 Orbit TrackBall for Mac
+ 1012 PocketMouse
+ 1013 Mouse*in*a*Box Optical Pro
+ 1014 Expert Mouse Pro Wireless
+ 1015 Expert Mouse
+ 1016 ADB/USB Orbit
+ 1018 Studio Mouse
+ 101d Mouse*in*a*Box Optical Pro
+ 101e Studio Mouse Wireless
+ 101f PocketMouse Pro
+ 1020 Expert Mouse Trackball
+ 1021 Expert Mouse Wireless
+ 1022 Orbit Optical
+ 1023 Pocket Mouse Pro Wireless
+ 1024 PocketMouse
+ 1025 Mouse*in*a*Box Optical Elite Wireless
+ 1026 Pocket Mouse Pro
+ 1027 StudioMouse
+ 1028 StudioMouse Wireless
+ 1029 Mouse*in*a*Box Optical Elite
+ 102a Mouse*in*a*Box Optical
+ 102b PocketMouse
+ 102c Iridio
+ 102d Pilot Optical
+ 102e Pilot Optical Pro
+ 102f Pilot Optical Pro Wireless
+ 1042 Ci25m Notebook Optical Mouse [Diamond Eye Precision]
+ 1043 Ci65m Wireless Notebook Optical Mouse
+ 104a PilotMouse Mini Retractable
+ 105d PocketMouse Bluetooth
+ 105e Bluetooth EDR Dongle
+ 1061 PocketMouse Grip
+ 1062 PocketMouse Max
+ 1063 PocketMouse Max Wireless
+ 1064 PocketMouse 2.0 Wireless
+ 1065 PocketMouse 2.0
+ 1066 PocketMouse Max Glow
+ 1067 ValueMouse
+ 1068 ValueOpt White
+ 1069 ValueOpt Black
+ 106a PilotMouse Laser Wireless Mini
+ 106b PilotMouse Laser - 3 Button
+ 106c PilotMouse Laser - Gaming
+ 106d PilotMouse Laser - Wired
+ 106e PilotMouse Micro Laser
+ 1070 ValueOpt Travel
+ 1071 ValueOpt RF TX
+ 1072 PocketMouse Colour
+ 1073 PilotMouse Laser - 6 Button
+ 1074 PilotMouse Laser Wireless Mini
+ 1075 SlimBlade Presenter Media Mouse
+ 1076 SlimBlade Media Mouse
+ 1077 SlimBlade Presenter Mouse
+ 1152 Bluetooth EDR Dongle
+ 2002 Optical Elite Wireless
+ 2010 Wireless Presentation Remote
+ 2012 Wireless Presenter with Laser Pointer
+ 2021 PilotBoard Wireless
+ 2030 PilotBoard Wireless
+ 2034 SlimBlade Media Notebook Set
+ 2041 SlimBlade Trackball
+ 2048 Orbit Trackball with Scroll Ring
+ 4003 Gravis Xterminator Digital Gamepad
+ 4005 Gravis Eliminator GamePad Pro
+ 4006 Gravis Eliminator AfterShock
+ 4007 Gravis Xterminator Force
+ 4008 Gravis Destroyer TiltPad
+ 5001 Cabo I Camera
+ 5002 VideoCam CABO II
+ 5003 VideoCam
+047e Agere Systems, Inc. (Lucent)
+ 0300 ORiNOCO Card
+ 1001 USS720 Parallel Port
+ 2892 Systems Soft Modem
+ bad1 Lucent 56k Modem
+ f101 Atlas Modem
+047f Plantronics, Inc.
+ 0101 Bulk Driver
+ 0301 Bulk Driver
+ 0411 Savi Office Base Station
+ 0ca1 USB DSP v4 Audio Interface
+ 4254 BUA-100 Bluetooth Adapter
+ ac01 Savi 7xx
+ ad01 GameCom 777 5.1 Headset
+ c008 Audio 655 DSP
+ c00e Blackwire C310 headset
+0480 Toshiba America Inc
+ 0001 InTouch Module
+ 0004 InTouch Module
+ 0011 InTouch Module
+ 0014 InTouch Module
+ 0100 Stor.E Slim USB 3.0
+ 0200 External Disk
+ a006 External Disk 1.5TB
+ a007 External Disk USB 3.0
+ a009 Stor.E Basics
+ a00d STOR.E BASICS 500GB
+ a100 Canvio Alu 2TB 2.5" Black External Disk Model HDTH320EK3CA
+ a202 Canvio Basics HDD
+ a208 Canvio Basics 2TB USB 3.0 Portable Hard Drive
+ b001 Stor.E Partner
+ b207 Canvio Ready
+ d000 External Disk 2TB Model DT01ABA200
+ d010 External Disk 3TB
+ d011 Canvio Desk
+0481 Zenith Data Systems
+0482 Kyocera Corp.
+ 000e FS-1020D Printer
+ 000f FS-1920 Mono Printer
+ 0015 FS-1030D printer
+ 0100 Finecam S3x
+ 0101 Finecam S4
+ 0103 Finecam S5
+ 0105 Finecam L3
+ 0106 Finecam
+ 0107 Digital Camera Device
+ 0108 Digital Camera Device
+ 0203 AH-K3001V
+ 0204 iBurst Terminal
+ 0408 FS-1320D Printer
+0483 STMicroelectronics
+ 0137 BeWAN ADSL USB ST (blue or green)
+ 0138 Unicorn II (ST70138B + MTC-20174TQ chipset)
+ 1307 Cytronix 6in1 Card Reader
+ 163d Cool Icam Digi-MP3
+ 2015 TouchChip® Fingerprint Reader
+ 2016 Fingerprint Reader
+ 2017 Biometric Smart Card Reader
+ 2018 BioSimKey
+ 2302 Portable Flash Device (PFD)
+ 3744 ST-LINK/V1
+ 3747 ST Micro Connect Lite
+ 3748 ST-LINK/V2
+ 374b ST-LINK/V2.1
+ 4810 ISDN adapter
+ 481d BT Digital Access adapter
+ 5000 ST Micro/Ergenic ERG BT-002 Bluetooth Adapter
+ 5001 ST Micro Bluetooth Device
+ 5710 Joystick in FS Mode
+ 5720 STM microSD Flash Device
+ 5721 Hantek DDS-3X25 Arbitrary Waveform Generator
+ 5730 STM32 Audio Streaming
+ 5740 STM32F407
+ 7270 ST Micro Serial Bridge
+ 7554 56k SoftModem
+ 91d1 Sensor Hub
+ df11 STM Device in DFU Mode
+ ff10 Swann ST56 Modem
+0484 Specialix
+0485 Nokia Monitors
+0486 ASUS Computers, Inc.
+ 0185 EeePC T91MT HID Touch Panel
+0487 Stewart Connector
+0488 Cirque Corp.
+0489 Foxconn / Hon Hai
+ 0502 SmartMedia Card Reader Firmware Loader
+ 0503 SmartMedia Card Reader
+ d00c Rollei Compactline (Storage Mode)
+ d00e Rollei Compactline (Video Mode)
+ e000 T-Com TC 300
+ e003 Pirelli DP-L10
+ e00d Broadcom Bluetooth 2.1 Device
+ e00f Foxconn T77H114 BCM2070 [Single-Chip Bluetooth 2.1 + EDR Adapter]
+ e011 Acer Bluetooth module
+ e016 Ubee PXU1900 WiMAX Adapter [Beceem BCSM250]
+ e02c Atheros AR5BBU12 Bluetooth Device
+ e032 Broadcom BCM20702 Bluetooth
+ e042 Broadcom BCM20702 Bluetooth
+ e04d Atheros AR3012 Bluetooth
+048a S-MOS Systems, Inc.
+048c Alps Electric Ireland, Ltd
+048d Integrated Technology Express, Inc.
+ 1165 IT1165 Flash Controller
+ 1172 Flash Drive
+ 1336 SD/MMC Cardreader
+ 1345 Multi Cardreader
+ 9006 IT9135 BDA Afatech DVB-T HDTV Dongle
+ 9009 Zolid HD DVD Maker
+ 9135 Zolid Mini DVB-T Stick
+ 9306 IT930x DVB stick
+ 9503 ITE it9503 feature-limited DVB-T transmission chip [ccHDtv]
+ 9507 ITE it9507 full featured DVB-T transmission chip [ccHDtv]
+048f Eicon Tech.
+0490 United Microelectronics Corp.
+0491 Capetronic
+ 0003 Taxan Monitor Control
+0492 Samsung SemiConductor, Inc.
+ 0140 MP3 player
+ 0141 MP3 Player
+0493 MAG Technology Co., Ltd
+0495 ESS Technology, Inc.
+0496 Micron Electronics
+0497 Smile International
+ c001 Camera Device
+0498 Capetronic (Kaohsiung) Corp.
+0499 Yamaha Corp.
+ 1000 UX256 MIDI I/F
+ 1001 MU1000
+ 1002 MU2000
+ 1003 MU500
+ 1004 UW500
+ 1005 MOTIF6
+ 1006 MOTIF7
+ 1007 MOTIF8
+ 1008 UX96 MIDI I/F
+ 1009 UX16 MIDI I/F
+ 100a EOS BX
+ 100c UC-MX
+ 100d UC-KX
+ 100e S08
+ 100f CLP-150
+ 1010 CLP-170
+ 1011 P-250
+ 1012 TYROS
+ 1013 PF-500
+ 1014 S90
+ 1015 MOTIF-R
+ 1016 MDP-5
+ 1017 CVP-204
+ 1018 CVP-206
+ 1019 CVP-208
+ 101a CVP-210
+ 101b PSR-1100
+ 101c PSR-2100
+ 101d CLP-175
+ 101e PSR-K1
+ 101f EZ-J24
+ 1020 EZ-250i
+ 1021 MOTIF ES 6
+ 1022 MOTIF ES 7
+ 1023 MOTIF ES 8
+ 1024 CVP-301
+ 1025 CVP-303
+ 1026 CVP-305
+ 1027 CVP-307
+ 1028 CVP-309
+ 1029 CVP-309GP
+ 102a PSR-1500
+ 102b PSR-3000
+ 102e ELS-01/01C
+ 1030 PSR-295/293
+ 1031 DGX-205/203
+ 1032 DGX-305
+ 1033 DGX-505
+ 1037 PSR-E403
+ 103c MOTIF-RACK ES
+ 1054 S90XS Keyboard/Music Synthesizer
+ 160f P-105
+ 1613 Clavinova CLP535
+ 2000 DGP-7
+ 2001 DGP-5
+ 3001 YST-MS55D USB Speaker
+ 3003 YST-M45D USB Speaker
+ 4000 NetVolante RTA54i Broadband&ISDN Router
+ 4001 NetVolante RTW65b Broadband Wireless Router
+ 4002 NetVolante RTW65i Broadband&ISDN Wireless Router
+ 4004 NetVolante RTA55i Broadband VoIP Router
+ 5000 CS1D
+ 5001 DSP1D
+ 5002 DME32
+ 5003 DM2000
+ 5004 02R96
+ 5005 ACU16-C
+ 5006 NHB32-C
+ 5007 DM1000
+ 5008 01V96
+ 5009 SPX2000
+ 500a PM5D
+ 500b DME64N
+ 500c DME24N
+ 6001 CRW2200UX Lightspeed 2 External CD-RW Drive
+ 7000 DTX
+ 7010 UB99
+049a Gandalf Technologies, Ltd
+049b Curtis Computer Products
+049c Acer Advanced Labs, Inc.
+ 0002 Keyboard (???)
+049d VLSI Technology
+049f Compaq Computer Corp.
+ 0002 InkJet Color Printer
+ 0003 iPAQ PocketPC
+ 000e Internet Keyboard
+ 0012 InkJet Color Printer
+ 0018 PA-1/PA-2 MP3 Player
+ 0019 InkJet Color Printer
+ 001a S4 100 Scanner
+ 001e IJ650 Inkjet Printer
+ 001f WL215 Adapter
+ 0021 S200 Scanner
+ 0027 Bluetooth Multiport Module by Compaq
+ 002a 1400P Inkjet Printer
+ 002b A3000
+ 002c Lexmark X125
+ 0032 802.11b Adapter [ipaq h5400]
+ 0033 Wireless LAN MultiPort W100 [Intersil PRISM 2.5]
+ 0036 Bluetooth Multiport Module
+ 0051 KU-0133 Easy Access Interner Keyboard
+ 0076 Wireless LAN MultiPort W200
+ 0080 GPRS Multiport
+ 0086 Bluetooth Device
+ 504a Personal Jukebox PJB100
+ 505a Linux-USB "CDC Subset" Device, or Itsy (experimental)
+ 8511 iPAQ Networking 10/100 Ethernet [pegasus2]
+04a0 Digital Equipment Corp.
+04a1 SystemSoft Corp.
+ fff0 Telex Composite Device
+04a2 FirePower Systems
+04a3 Trident Microsystems, Inc.
+04a4 Hitachi, Ltd
+ 0004 DVD-CAM DZ-MV100A Camcorder
+ 001e DVDCAM USB HS Interface
+04a5 Acer Peripherals Inc. (now BenQ Corp.)
+ 0001 Keyboard
+ 0002 API Ergo K/B
+ 0003 API Generic K/B Mouse
+ 12a6 AcerScan C310U
+ 1a20 Prisa 310U
+ 1a2a Prisa 620U
+ 2022 Prisa 320U/340U
+ 2040 Prisa 620UT
+ 205e ScanPrisa 640BU
+ 2060 Prisa 620U+/640U
+ 207e Prisa 640BU
+ 209e ScanPrisa 640BT
+ 20ae S2W 3000U
+ 20b0 S2W 3300U/4300U
+ 20be Prisa 640BT
+ 20c0 Prisa 1240UT
+ 20de S2W 4300U+
+ 20f8 Benq 5000
+ 20fc Benq 5000
+ 20fe SW2 5300U
+ 2137 Benq 5150/5250
+ 2202 Benq 7400UT
+ 2311 Benq 5560
+ 3003 Benq Webcam
+ 3008 Benq 1500
+ 300a Benq 3410
+ 300c Benq 1016
+ 3019 Benq DC C40
+ 4000 P30 Composite Device
+ 4013 BenQ-Siemens EF82/SL91
+ 4044 BenQ-Siemens SF71
+ 4045 BenQ-Siemens E81
+ 4048 BenQ M7
+ 6001 Mass Storage Device
+ 6002 Mass Storage Device
+ 6003 ATA/ATAPI Adapter
+ 6004 Mass Storage Device
+ 6005 Mass Storage Device
+ 6006 Mass Storage Device
+ 6007 Mass Storage Device
+ 6008 Mass Storage Device
+ 6009 Mass Storage Device
+ 600a Mass Storage Device
+ 600b Mass Storage Device
+ 600c Mass Storage Device
+ 600d Mass Storage Device
+ 600e Mass Storage Device
+ 600f Mass Storage Device
+ 6010 Mass Storage Device
+ 6011 Mass Storage Device
+ 6012 Mass Storage Device
+ 6013 Mass Storage Device
+ 6014 Mass Storage Device
+ 6015 Mass Storage Device
+ 6125 MP3 Player
+ 6180 MP3 Player
+ 6200 MP3 Player
+ 7500 Hi-Speed Mass Storage Device
+ 9000 AWL300 Wireless Adapter
+ 9001 AWL400 Wireless Adapter
+ 9213 Kbd Hub
+04a6 Nokia Display Products
+ 00b9 Audio
+ 0180 Hub Type P
+ 0181 HID Monitor Controls
+04a7 Visioneer
+ 0100 StrobePro
+ 0101 Strobe Pro Scanner (1.01)
+ 0102 StrobePro Scanner
+ 0211 OneTouch 7600 Scanner
+ 0221 OneTouch 5300 Scanner
+ 0223 OneTouch 8200
+ 0224 OneTouch 4800 USB/Microtek Scanport 3000
+ 0225 VistaScan Astra 3600(ENG)
+ 0226 OneTouch 5300 USB
+ 0229 OneTouch 7100
+ 022a OneTouch 6600
+ 022c OneTouch 9000/9020
+ 0231 6100 Scanner
+ 0311 6200 EPP/USB Scanner
+ 0321 OneTouch 8100 EPP/USB Scanner
+ 0331 OneTouch 8600 EPP/USB Scanner
+ 0341 6400
+ 0361 VistaScan Astra 3600(ENG)
+ 0362 OneTouch 9320
+ 0371 OneTouch 8700/8920
+ 0380 OneTouch 7700
+ 0382 Photo Port 7700
+ 0390 9650
+ 03a0 Xerox 4800 One Touch
+ 0410 OneTouch Pro 8800/8820
+ 0421 9450 USB
+ 0423 9750 Scanner
+ 0424 Strobe XP 450
+ 0425 Strobe XP 100
+ 0426 Strobe XP 200
+ 0427 Strobe XP 100
+ 0444 OneTouch 7300
+ 0445 CardReader 100
+ 0446 Xerox DocuMate 510
+ 0447 XEROX DocuMate 520
+ 0448 XEROX DocuMate 250
+ 0449 Xerox DocuMate 252
+ 044a Xerox 6400
+ 044c Xerox DocuMate 262
+ 0474 Strobe XP 300
+ 0475 Xerox DocuMate 272
+ 0478 Strobe XP 220
+ 0479 Strobe XP 470
+ 047a 9450
+ 047b 9650
+ 047d 9420
+ 0480 9520
+ 048f Strobe XP 470
+ 0491 Strobe XP 450
+ 0493 9750
+ 0494 Strobe XP 120
+ 0497 Patriot 430
+ 0498 Patriot 680
+ 0499 Patriot 780
+ 049b Strobe XP 100
+ 04a0 7400
+ 04ac Xerox Travel Scanner 100
+ 04cd Xerox Travel Scanner 150
+04a8 Multivideo Labs, Inc.
+ 0101 Hub
+ 0303 Peripheral Switch
+ 0404 Peripheral Switch
+04a9 Canon, Inc.
+ 1005 BJ Printer Hub
+ 1035 PD Printer Storage
+ 1050 BJC-8200
+ 1051 BJC-3000 Color Printer
+ 1052 BJC-6100
+ 1053 BJC-6200
+ 1054 BJC-6500
+ 1055 BJC-85
+ 1056 BJC-2110 Color Printer
+ 1057 LR1
+ 105a BJC-55
+ 105b S600 Printer
+ 105c S400
+ 105d S450 Printer
+ 105e S800
+ 1062 S500 Printer
+ 1063 S4500
+ 1064 S300 Printer
+ 1065 S100
+ 1066 S630
+ 1067 S900
+ 1068 S9000
+ 1069 S820
+ 106a S200 Printer
+ 106b S520 Printer
+ 106d S750 Printer
+ 106e S820D
+ 1070 S530D
+ 1072 I850 Printer
+ 1073 I550 Printer
+ 1074 S330 Printer
+ 1076 i70
+ 1077 i950
+ 107a S830D
+ 107b i320
+ 107c i470D
+ 107d i9100
+ 107e i450
+ 107f i860
+ 1082 i350
+ 1084 i250
+ 1085 i255
+ 1086 i560
+ 1088 i965
+ 108a i455
+ 108b i900D
+ 108c i475D
+ 108d PIXMA iP2000
+ 108f i80
+ 1090 i9900 Photo Printer
+ 1091 PIXMA iP1500
+ 1093 PIXMA iP4000
+ 1094 PIXMA iP3000x Printer
+ 1095 PIXMA iP6000D
+ 1097 PIXMA iP5000
+ 1098 PIXMA iP1000
+ 1099 PIXMA iP8500
+ 109c PIXMA iP4000R
+ 109d iP90
+ 10a0 PIXMA iP1600 Printer
+ 10a2 iP4200
+ 10a4 iP5200R
+ 10a5 iP5200
+ 10a7 iP6210D
+ 10a8 iP6220D
+ 10a9 iP6600D
+ 10b6 PIXMA iP4300 Printer
+ 10b7 PIXMA iP5300 Printer
+ 10c2 PIXMA iP1800 Printer
+ 10c4 Pixma iP4500 Printer
+ 10c9 PIXMA iP4600 Printer
+ 10ca PIXMA iP3600 Printer
+ 10e3 PIXMA iX6850 Printer
+ 1404 W6400PG
+ 1405 W8400PG
+ 150f BIJ2350 PCL
+ 1510 BIJ1350 PCL
+ 1512 BIJ1350D PCL
+ 1601 DR-2080C Scanner
+ 1607 DR-6080 Scanner
+ 1608 DR-2580C Scanner
+ 1700 PIXMA MP110 Scanner
+ 1701 PIXMA MP130 Scanner
+ 1702 MP410 Composite
+ 1703 MP430 Composite
+ 1704 MP330 Composite
+ 1706 PIXMA MP750 Scanner
+ 1707 PIXMA MP780 Scanner
+ 1708 PIXMA MP760 Scanner
+ 1709 PIXMA MP150 Scanner
+ 170a PIXMA MP170 Scanner
+ 170b PIXMA MP450 Scanner
+ 170c PIXMA MP500 Scanner
+ 170d PIXMA MP800 Scanner
+ 170e MP800R
+ 1710 MP950
+ 1712 MP530
+ 1713 PIXMA MP830 Scanner
+ 1714 MP160
+ 1715 MP180 Storage
+ 1716 MP460 Composite
+ 1717 MP510
+ 1718 MP600 Storage
+ 171a MP810 Storage
+ 171b MP960
+ 1721 MP210 ser
+ 1723 MP470 ser
+ 1724 PIXMA MP520 series
+ 1725 MP610 ser
+ 1726 MP970 ser
+ 1727 MX300 ser
+ 1728 PIXMA MX310 series
+ 1729 MX700 ser
+ 172b MP140 ser
+ 1736 PIXMA MX320 series
+ 173a MP250 series printer
+ 173b PIXMA MP270 All-In-One Printer
+ 173e MP560
+ 173f Pixma MP640 Multifunction device
+ 1748 Pixma MG5150
+ 174d MX360 ser
+ 176d PIXMA MG2550
+ 178d PIXMA MG6853
+ 1900 CanoScan LiDE 90
+ 1901 CanoScan 8800F
+ 1904 CanoScan LiDE 100
+ 1905 CanoScan LiDE 200
+ 1906 CanoScan 5600F
+ 1907 CanoScan LiDE 700F
+ 1909 CanoScan LiDE 110
+ 190a CanoScan LiDE 210
+ 190d CanoScan 9000F Mark II
+ 190e CanoScan LiDE 120
+ 190f CanoScan LiDE 220
+ 2200 CanoScan LiDE 25
+ 2201 CanoScan FB320U
+ 2202 CanoScan FB620U
+ 2204 CanoScan FB630U
+ 2205 CanoScan FB1210U
+ 2206 CanoScan N650U/N656U
+ 2207 CanoScan 1220U
+ 2208 CanoScan D660U
+ 220a CanoScan D2400UF
+ 220b CanoScan D646U
+ 220c CanoScan D1250U2
+ 220d CanoScan N670U/N676U/LiDE 20
+ 220e CanoScan N1240U/LiDE 30
+ 220f CanoScan 8000F
+ 2210 CanoScan 9900F
+ 2212 CanoScan 5000F
+ 2213 CanoScan LiDE 50/LiDE 35/LiDE 40
+ 2214 CanoScan LiDE 80
+ 2215 CanoScan 3000/3000F/3000ex
+ 2216 CanoScan 3200F
+ 2217 CanoScan 5200F
+ 2219 CanoScan 9950F
+ 221b CanoScan 4200F
+ 221c CanoScan LiDE 60
+ 221e CanoScan 8400F
+ 221f CanoScan LiDE 500F
+ 2220 CanoScan LIDE 25
+ 2224 CanoScan LiDE 600F
+ 2225 CanoScan LiDE 70
+ 2228 CanoScan 4400F
+ 2229 CanoScan 8600F
+ 2602 MultiPASS C555
+ 2603 MultiPASS C755
+ 260a CAPT Printer
+ 260e LBP-2000
+ 2610 MPC600F
+ 2611 SmartBase MPC400
+ 2612 MultiPASS C855
+ 2617 CAPT Printer
+ 261a iR1600
+ 261b iR1610
+ 261c iC2300
+ 261f MPC200 Printer
+ 2621 iR2000
+ 2622 iR2010
+ 2623 FAX-B180C
+ 2629 FAXPHONE L75
+ 262b LaserShot LBP-1120 Printer
+ 262d iR C3200
+ 262f MultiPASS MP730
+ 2630 MultiPASS MP700
+ 2631 LASER CLASS 700
+ 2632 FAX-L2000
+ 2635 MPC190
+ 2637 iR C6800
+ 2638 iR C3100
+ 263c Smartbase MP360
+ 263d MP370
+ 263e MP390 FAX
+ 263f MP375
+ 2646 MF5530 Scanner Device V1.9.1
+ 2647 MF5550 Composite
+ 264d PIXMA MP710
+ 264e MF5630
+ 264f MF5650 (FAX)
+ 2650 iR 6800C EUR
+ 2651 iR 3100C EUR
+ 2655 FP-L170/MF350/L380/L398
+ 2656 iR1510-1670 CAPT Printer
+ 2659 MF8100
+ 265b CAPT Printer
+ 265c iR C3220
+ 265d MF5730
+ 265e MF5750
+ 265f MF5770
+ 2660 MF3110
+ 2663 iR3570/iR4570
+ 2664 iR2270/iR2870
+ 2665 iR C2620
+ 2666 iR C5800
+ 2667 iR85PLUS
+ 2669 iR105PLUS
+ 266a CAPT Device
+ 266b iR8070
+ 266c iR9070
+ 266d iR 5800C EUR
+ 266e CAPT Device
+ 266f iR2230
+ 2670 iR3530
+ 2671 iR5570/iR6570
+ 2672 iR C3170
+ 2673 iR 3170C EUR
+ 2674 L120
+ 2675 iR2830
+ 2676 CAPT Device
+ 2677 iR C2570
+ 2678 iR 2570C EUR
+ 2679 CAPT Device
+ 267a iR2016
+ 267b iR2020
+ 267d MF7100 series
+ 2684 MF3200 series
+ 2686 MF6500 series
+ 2687 iR4530
+ 2688 LBP3460
+ 268c iR C6870
+ 268d iR 6870C EUR
+ 268e iR C5870
+ 268f iR 5870C EUR
+ 2691 iR7105
+ 26a3 MF4100 series
+ 26b0 MF4600 series
+ 26b4 MF4010 series
+ 26b5 MF4200 series
+ 26da LBP3010B printer
+ 26e6 iR1024
+ 2736 I-SENSYS MF4550d
+ 2737 MF4410
+ 3041 PowerShot S10
+ 3042 CanoScan FS4000US Film Scanner
+ 3043 PowerShot S20
+ 3044 EOS D30
+ 3045 PowerShot S100
+ 3046 IXY Digital
+ 3047 Digital IXUS
+ 3048 PowerShot G1
+ 3049 PowerShot Pro90 IS
+ 304a CP-10
+ 304b IXY Digital 300
+ 304c PowerShot S300
+ 304d Digital IXUS 300
+ 304e PowerShot A20
+ 304f PowerShot A10
+ 3050 PowerShot unknown 1
+ 3051 PowerShot S110
+ 3052 Digital IXUS V
+ 3055 PowerShot G2
+ 3056 PowerShot S40
+ 3057 PowerShot S30
+ 3058 PowerShot A40
+ 3059 PowerShot A30
+ 305b ZR45MC Digital Camcorder
+ 305c PowerShot unknown 2
+ 3060 EOS D60
+ 3061 PowerShot A100
+ 3062 PowerShot A200
+ 3063 CP-100
+ 3065 PowerShot S200
+ 3066 Digital IXUS 330
+ 3067 MV550i Digital Video Camera
+ 3069 PowerShot G3
+ 306a Digital unknown 3
+ 306b MVX2i Digital Video Camera
+ 306c PowerShot S45
+ 306d PowerShot S45 PtP Mode
+ 306e PowerShot G3 (normal mode)
+ 306f PowerShot G3 (ptp)
+ 3070 PowerShot S230
+ 3071 PowerShot S230 (ptp)
+ 3072 PowerShot SD100 / Digital IXUS II (ptp)
+ 3073 PowerShot A70 (ptp)
+ 3074 PowerShot A60 (ptp)
+ 3075 IXUS 400 Camera
+ 3076 PowerShot A300
+ 3077 PowerShot S50
+ 3078 ZR70MC Digital Camcorder
+ 307a MV650i (normal mode)
+ 307b MV630i Digital Video Camera
+ 307c CP-200
+ 307d CP-300
+ 307f Optura 20
+ 3080 MVX150i (normal mode) / Optura 20 (normal mode)
+ 3081 Optura 10
+ 3082 MVX100i / Optura 10
+ 3083 EOS 10D
+ 3084 EOS 300D / EOS Digital Rebel
+ 3085 PowerShot G5
+ 3087 Elura 50 (PTP mode)
+ 3088 Elura 50 (normal mode)
+ 308d MVX3i
+ 308e FV M1 (normal mode) / MVX 3i (normal mode) / Optura Xi (normal mode)
+ 3093 Optura 300
+ 3096 IXY DV M2 (normal mode) / MVX 10i (normal mode)
+ 3099 EOS 300D (ptp)
+ 309a PowerShot A80
+ 309b Digital IXUS (ptp)
+ 309c PowerShot S1 IS
+ 309d Powershot Pro 1
+ 309f Camera
+ 30a0 Camera
+ 30a1 Camera
+ 30a2 Camera
+ 30a8 Elura 60E/Optura 40 (ptp)
+ 30a9 MVX25i (normal mode) / Optura 40 (normal mode)
+ 30b1 PowerShot S70 (normal mode) / PowerShot S70 (PTP mode)
+ 30b2 PowerShot S60 (normal mode) / PowerShot S60 (PTP mode)
+ 30b3 PowerShot G6 (normal mode) / PowerShot G6 (PTP mode)
+ 30b4 PowerShot S500
+ 30b5 PowerShot A75
+ 30b6 Digital IXUS II2 / Digital IXUS II2 (PTP mode) / PowerShot SD110 (PTP mode) / PowerShot SD110 Digital ELPH
+ 30b7 PowerShot A400 / PowerShot A400 (PTP mode)
+ 30b8 PowerShot A310 / PowerShot A310 (PTP mode)
+ 30b9 Powershot A85
+ 30ba PowerShot S410 Digital Elph
+ 30bb PowerShot A95
+ 30bd CP-220
+ 30be CP-330
+ 30bf Digital IXUS 40
+ 30c0 Digital IXUS 30 (PTP mode) / PowerShot SD200 (PTP mode)
+ 30c1 Digital IXUS 50 (normal mode) / IXY Digital 55 (normal mode) / PowerShot A520 (PTP mode) / PowerShot SD400 (normal mode)
+ 30c2 PowerShot A510 (normal mode) / PowerShot A510 (PTP mode)
+ 30c4 Digital IXUS i5 (normal mode) / IXY Digital L2 (normal mode) / PowerShot SD20 (normal mode)
+ 30ea EOS 1D Mark II (PTP mode)
+ 30eb EOS 20D
+ 30ec EOS 20D (ptp)
+ 30ee EOS 350D
+ 30ef EOS 350D (ptp)
+ 30f0 PowerShot S2 IS (PTP mode)
+ 30f2 Digital IXUS 700 (normal mode) / Digital IXUS 700 (PTP mode) / IXY Digital 600 (normal mode) / PowerShot SD500 (normal mode) / PowerShot SD500 (PTP mode)
+ 30f4 PowerShot SD30 / Ixus iZoom / IXY DIGITAL L3
+ 30f5 SELPHY CP500
+ 30f6 SELPHY CP400
+ 30f8 Powershot A430
+ 30f9 PowerShot A410 (PTP mode)
+ 30fa PowerShot S80
+ 30fc PowerShot A620 (PTP mode)
+ 30fd PowerShot A610 (normal mode)/PowerShot A610 (PTP mode)
+ 30fe Digital IXUS 65 (PTP mode)/PowerShot SD630 (PTP mode)
+ 30ff Digital IXUS 55 (PTP mode)/PowerShot SD450 (PTP mode)
+ 3100 PowerShot TX1
+ 310b SELPHY CP600
+ 310e Digital IXUS 50 (PTP mode)
+ 310f PowerShot A420
+ 3110 EOS Digital Rebel XTi
+ 3115 PowerShot SD900 / Digital IXUS 900 Ti / IXY DIGITAL 1000
+ 3116 Digital IXUS 750 / PowerShot SD550 (PTP mode)
+ 3117 PowerShot A700
+ 3119 PowerShot SD700 IS / Digital IXUS 800 IS / IXY Digital 800 IS
+ 311a PowerShot S3 IS
+ 311b PowerShot A540
+ 311c PowerShot SD600 DIGITAL ELPH / DIGITAL IXUS 60 / IXY DIGITAL 70
+ 3125 PowerShot G7
+ 3126 PowerShot A530
+ 3127 SELPHY CP710
+ 3128 SELPHY CP510
+ 312d Elura 100
+ 3136 PowerShot SD800 IS / Digital IXUS 850 IS / IXY DIGITAL 900 IS
+ 3137 PowerShot SD40 / Digital IXUS i7 IXY / DIGITAL L4
+ 3138 PowerShot A710 IS
+ 3139 PowerShot A640
+ 313a PowerShot A630
+ 3141 SELPHY ES1
+ 3142 SELPHY CP730
+ 3143 SELPHY CP720
+ 3145 EOS 450D
+ 3146 EOS 40D
+ 3147 EOS 1Ds Mark III
+ 3148 PowerShot S5 IS
+ 3149 PowerShot A460
+ 314b PowerShot SD850 IS DIGITAL ELPH / Digital IXUS 950 IS / IXY DIGITAL 810 IS
+ 314c PowerShot A570 IS
+ 314d PowerShot A560
+ 314e PowerShot SD750 DIGITAL ELPH / DIGITAL IXUS 75 / IXY DIGITAL 90
+ 314f PowerShot SD1000 DIGITAL ELPH / DIGITAL IXUS 70 / IXY DIGITAL 10
+ 3150 PowerShot A550
+ 3155 PowerShot A450
+ 315a PowerShot G9
+ 315b PowerShot A650 IS
+ 315d PowerShot A720
+ 315e PowerShot SX100 IS
+ 315f PowerShot SD950 IS DIGITAL ELPH / DIGITAL IXUS 960 IS / IXY DIGITAL 2000 IS
+ 3160 Digital IXUS 860 IS
+ 3170 SELPHY CP750
+ 3171 SELPHY CP740
+ 3172 SELPHY CP520
+ 3173 PowerShot SD890 IS DIGITAL ELPH / Digital IXUS 970 IS / IXY DIGITAL 820 IS
+ 3174 PowerShot SD790 IS DIGITAL ELPH / Digital IXUS 90 IS / IXY DIGITAL 95 IS
+ 3175 IXY Digital 25 IS
+ 3176 PowerShot A590
+ 3177 PowerShot A580
+ 317a PC1267 [Powershot A470]
+ 3184 Digital IXUS 80 IS (PTP mode)
+ 3185 SELPHY ES2
+ 3186 SELPHY ES20
+ 318d PowerShot SX100 IS
+ 318e PowerShot A1000 IS
+ 318f PowerShot G10
+ 3191 PowerShot A2000 IS
+ 3192 PowerShot SX110 IS
+ 3193 PowerShot SD990 IS DIGITAL ELPH / Digital IXUS 980 IS / IXY DIGITAL 3000 IS
+ 3195 PowerShot SX1 IS
+ 3196 PowerShot SD880 IS DIGITAL ELPH / Digital IXUS 870 IS / IXY DIGITAL 920 IS
+ 319a EOS 7D
+ 319b EOS 50D
+ 31aa SELPHY CP770
+ 31ab SELPHY CP760
+ 31ad PowerShot E1
+ 31af SELPHY ES3
+ 31b0 SELPHY ES30
+ 31b1 SELPHY CP530
+ 31bc PowerShot D10
+ 31bd PowerShot SD960 IS DIGITAL ELPH / Digital IXUS 110 IS / IXY DIGITAL 510 IS
+ 31be PowerShot A2100 IS
+ 31bf PowerShot A480
+ 31c0 PowerShot SX200 IS
+ 31c1 PowerShot SD970 IS DIGITAL ELPH / Digital IXUS 990 IS / IXY DIGITAL 830 IS
+ 31c2 PowerShot SD780 IS DIGITAL ELPH / Digital IXUS 100 IS / IXY DIGITAL 210 IS
+ 31c3 PowerShot A1100 IS
+ 31c4 PowerShot SD1200 IS DIGITAL ELPH / Digital IXUS 95 IS / IXY DIGITAL 110 IS
+ 31cf EOS Rebel T1i / EOS 500D / EOS Kiss X3
+ 31dd SELPHY CP780
+ 31df PowerShot G11
+ 31e0 PowerShot SX120 IS
+ 31e1 PowerShot S90
+ 31e4 PowerShot SX20 IS
+ 31e5 Digital IXUS 200 IS
+ 31e6 PowerShot SD940 IS DIGITAL ELPH / Digital IXUS 120 IS / IXY DIGITAL 220 IS
+ 31e7 SELPHY CP790
+ 31ea EOS Rebel T2i / EOS 550D / EOS Kiss X4
+ 31ee SELPHY ES40
+ 31ef PowerShot A495
+ 31f0 PowerShot A490
+ 31f1 PowerShot A3100 IS / PowerShot A3150 IS
+ 31f2 PowerShot A3000 IS
+ 31f3 PowerShot Digital ELPH SD1400 IS
+ 31f4 PowerShot SD1300 IS / IXUS 105
+ 31f5 Powershot SD3500 IS / IXUS 210 IS
+ 31f6 PowerShot SX210 IS
+ 31f7 Powershot SD4000 IS / IXUS 300 HS / IXY 30S
+ 31f8 Powershot SD4500 IS / IXUS 1000 HS / IXY 50S
+ 31ff Digital IXUS 55
+ 3209 Vixia HF S21 A
+ 320f PowerShot G12
+ 3210 Powershot SX30 IS
+ 3211 PowerShot SX130 IS
+ 3212 Powershot S95
+ 3214 SELPHY CP800
+ 3215 EOS 60D
+ 3218 EOS 600D / Rebel T3i (ptp)
+ 3219 EOS 1D X
+ 3223 PowerShot A3300 IS
+ 3224 PowerShot A3200 IS
+ 3225 PowerShot ELPH 500 HS / IXUS 310 HS
+ 3226 PowerShow A800
+ 3227 PowerShot ELPH 100 HS / IXUS 115 HS
+ 3228 PowerShot SX230 HS
+ 3229 PowerShot ELPH 300 HS / IXUS 220 HS
+ 322a PowerShot A2200
+ 322b Powershot A1200
+ 322c PowerShot SX220 HS
+ 3233 PowerShot G1 X
+ 3234 PowerShot SX150 IS
+ 3235 PowerShot ELPH 510 HS / IXUS 1100 HS
+ 3236 PowerShot S100
+ 3237 PowerShot ELPH 310 HS / IXUS 230 HS
+ 3238 PowerShot SX40 HS
+ 323a EOS 5D Mark III
+ 323b EOS Rebel T4i
+ 323d EOS M
+ 323e PowerShot A1300
+ 323f PowerShot A810
+ 3240 PowerShot ELPH 320 HS / IXUS 240 HS
+ 3241 PowerShot ELPH 110 HS / IXUS 125 HS
+ 3242 PowerShot D20
+ 3243 PowerShot A4000 IS
+ 3244 PowerShot SX260 HS
+ 3245 PowerShot SX240 HS
+ 3246 PowerShot ELPH 530 HS / IXUS 510 HS
+ 3247 PowerShot ELPH 520 HS / IXUS 500 HS
+ 3248 PowerShot A3400 IS
+ 3249 PowerShot A2400 IS
+ 324a PowerShot A2300
+ 3250 EOS 6D
+ 3252 EOS 1D C
+ 3253 EOS 70D
+ 3255 SELPHY CP900
+ 3256 SELPHY CP810
+ 3258 PowerShot G15
+ 3259 PowerShot SX50 HS
+ 325a PowerShot SX160 IS
+ 325b PowerShot S110
+ 325c PowerShot SX500 IS
+ 325e PowerShot N
+ 325f PowerShot SX280 HS
+ 3260 PowerShot SX270 HS
+ 3261 PowerShot A3500 IS
+ 3262 PowerShot A2600
+ 3263 PowerShot SX275 HS
+ 3264 PowerShot A1400
+ 3265 Powershot ELPH 130 IS / IXUS 140
+ 3266 Powershot ELPH 120 IS / IXUS 135
+ 3268 PowerShot ELPH 330 HS / IXUS 255 HS
+ 326f EOS 7D Mark II
+ 3270 EOS 100D
+ 3271 PowerShot A2500
+ 3272 EOS 700D
+ 3274 PowerShot G16
+ 3275 PowerShot S120
+ 3276 PowerShot SX170 IS
+ 3277 PowerShot SX510 HS
+ 3278 PowerShot S200
+ 327a SELPHY CP910
+ 327d Powershot ELPH 115 IS / IXUS 132
+ 327f EOS Rebel T5 / EOS 1200D / EOS Kiss X70
+ 3284 PowerShot D30
+ 3285 PowerShot SX700 HS
+ 3286 PowerShot SX600 HS
+ 3287 PowerShot ELPH 140 IS / IXUS 150
+ 3288 Powershot ELPH 135 / IXUS 145
+ 3289 PowerShot ELPH 340 HS / IXUS 265 HS
+ 328a PowerShot ELPH 150 IS / IXUS 155
+ 328b PowerShot N Facebook(R) Ready
+ 3299 EOS M3
+ 329a PowerShot SX60 HS
+ 329b PowerShot SX520 HS
+ 329c PowerShot SX400 IS
+ 329d PowerShot G7 X
+ 329f PowerShot SX530 HS
+ 32a6 PowerShot SX710 HS
+ 32aa Powershot ELPH 160 / IXUS 160
+ 32ab PowerShot ELPH 350HS / IXUS 275 HS
+ 32ac PowerShot ELPH 170 IS / IXUS 170
+ 32ad PowerShot SX410 IS
+ 32b1 SELPHY CP1200
+ 32b2 PowerShot G9 X
+ 32bb EOS M5
+ 32c1 PowerShot ELPH 180 / IXUS 175
+ 32c2 PowerShot SX720 HS
+04aa DaeWoo Telecom, Ltd
+04ab Chromatic Research
+04ac Micro Audiometrics Corp.
+04ad Dooin Electronics
+ 2501 Bluetooth Device
+04af Winnov L.P.
+04b0 Nikon Corp.
+ 0102 Coolpix 990
+ 0103 Coolpix 880
+ 0104 Coolpix 995
+ 0106 Coolpix 775
+ 0107 Coolpix 5000
+ 0108 Coolpix 2500
+ 0109 Coolpix 2500 (ptp)
+ 010a Coolpix 4500
+ 010b Coolpix 4500 (ptp)
+ 010d Coolpix 5700 (ptp)
+ 010e Coolpix 4300 (storage)
+ 010f Coolpix 4300 (ptp)
+ 0110 Coolpix 3500 (Sierra Mode)
+ 0111 Coolpix 3500 (ptp)
+ 0112 Coolpix 885 (ptp)
+ 0113 Coolpix 5000 (ptp)
+ 0114 Coolpix 3100 (storage)
+ 0115 Coolpix 3100 (ptp)
+ 0117 Coolpix 2100 (ptp)
+ 0119 Coolpix 5400 (ptp)
+ 011d Coolpix 3700 (ptp)
+ 0121 Coolpix 3200 (ptp)
+ 0122 Coolpix 2200 (ptp)
+ 0124 Coolpix 8400 (mass storage mode)
+ 0125 Coolpix 8400 (ptp)
+ 0126 Coolpix 8800
+ 0129 Coolpix 4800 (ptp)
+ 012c Coolpix 4100 (storage)
+ 012d Coolpix 4100 (ptp)
+ 012e Coolpix 5600 (ptp)
+ 0130 Coolpix 4600 (ptp)
+ 0135 Coolpix 5900 (ptp)
+ 0136 Coolpix 7900 (storage)
+ 0137 Coolpix 7900 (ptp)
+ 013a Coolpix 100 (storage)
+ 013b Coolpix 100 (ptp)
+ 0141 Coolpix P2 (storage)
+ 0142 Coolpix P2 (ptp)
+ 0163 Coolpix P5100 (ptp)
+ 0169 Coolpix P50 (ptp)
+ 0202 Coolpix SQ (ptp)
+ 0203 Coolpix 4200 (mass storage mode)
+ 0204 Coolpix 4200 (ptp)
+ 0205 Coolpix 5200 (storage)
+ 0206 Coolpix 5200 (ptp)
+ 0301 Coolpix 2000 (storage)
+ 0302 Coolpix 2000 (ptp)
+ 0317 Coolpix L20 (ptp)
+ 0402 DSC D100 (ptp)
+ 0403 D2H (mass storage mode)
+ 0404 D2H SLR (ptp)
+ 0405 D70 (mass storage mode)
+ 0406 DSC D70 (ptp)
+ 0408 D2X SLR (ptp)
+ 0409 D50 digital camera
+ 040a D50 (ptp)
+ 040c D2Hs
+ 040e DSC D70s (ptp)
+ 040f D200 (mass storage mode)
+ 0410 D200 (ptp)
+ 0413 D40 (mass storage mode)
+ 041e D60 digital camera (mass storage mode)
+ 0422 D700 (ptp)
+ 0423 D5000
+ 0424 D3000
+ 0425 D300S
+ 0428 D7000
+ 0429 D5100
+ 042a D800 (ptp)
+ 0f03 PD-10 Wireless Printer Adapter
+ 4000 Coolscan LS 40 ED
+ 4001 LS 50 ED/Coolscan V ED
+ 4002 Super Coolscan LS-5000 ED
+04b1 Pan International
+04b3 IBM Corp.
+ 3003 Rapid Access III Keyboard
+ 3004 Media Access Pro Keyboard
+ 300a Rapid Access IIIe Keyboard
+ 3016 UltraNav Keyboard Hub
+ 3018 UltraNav Keyboard
+ 301a 2-port low-power hub
+ 301b SK-8815 Keyboard
+ 301c Enhanced Performance Keyboard
+ 3020 Enhanced Performance Keyboard
+ 3025 NetVista Full Width Keyboard
+ 3100 NetVista Mouse
+ 3103 ScrollPoint Pro Mouse
+ 3104 ScrollPoint Wireless Mouse
+ 3105 ScrollPoint Optical (HID)
+ 3107 ThinkPad 800dpi Optical Travel Mouse
+ 3108 800dpi Optical Mouse w/ Scroll Point
+ 3109 Optical ScrollPoint Pro Mouse
+ 310b Red Wheel Mouse
+ 310c Wheel Mouse
+ 4427 Portable CD ROM
+ 4482 Serial Converter
+ 4484 SMSC USB20H04 3-Port Hub [ThinkPad X4 UltraBase, Wistron S Note-3 Media Slice]
+ 4485 ThinkPad Dock Hub
+ 4524 40 Character Vacuum Fluorescent Display
+ 4525 Double sided CRT
+ 4535 4610 Suremark Printer
+ 4550 NVRAM (128 KB)
+ 4554 Cash Drawer
+ 4580 Hub w/ NVRAM
+ 4581 4800-2xx Hub w/ Cash Drawer
+ 4604 Keyboard w/ Card Reader
+ 4671 4820 LCD w/ MSR/KB
+04b4 Cypress Semiconductor Corp.
+ 0001 Mouse
+ 0002 CY7C63x0x Thermometer
+ 0033 Mouse
+ 0060 Wireless optical mouse
+ 0100 Cino FuzzyScan F760-B
+ 0101 Keyboard/Hub
+ 0102 Keyboard with APM
+ 0130 MyIRC Remote Receiver
+ 0306 Telephone Receiver
+ 0407 Optical Skype Mouse
+ 0bad MetaGeek Wi-Spy
+ 1002 CY7C63001 R100 FM Radio
+ 1006 Human Interface Device
+ 2050 hub
+ 2830 Opera1 DVB-S (cold state)
+ 3813 NANO BIOS Programmer
+ 4235 Monitor 02 Driver
+ 4381 SCAPS USC-1 Scanner Controller
+ 4611 Storage Adapter FX2 (CY)
+ 4616 Flash Disk (TPP)
+ 4624 DS-Xtreme Flash Card
+ 5201 Combi Keyboard-Hub (Hub)
+ 5202 Combi Keyboard-Hub (Keyboard)
+ 5500 HID->COM RS232 Adapter
+ 5a9b Dacal CD/DVD Library D-101/DC-300/DC-016RW
+ 6370 ViewMate Desktop Mouse CC2201
+ 6560 CY7C65640 USB-2.0 "TetraHub"
+ 6830 CY7C68300A EZ-USB AT2 USB 2.0 to ATA/ATAPI
+ 6831 Storage Adapter ISD-300LP (CY)
+ 7417 Wireless PC Lock/Ultra Mouse
+ 8329 USB To keyboard/Mouse Converter
+ 8613 CY7C68013 EZ-USB FX2 USB 2.0 Development Kit
+ 8614 DTV-DVB UDST7020BDA DVB-S Box(DVBS for MCE2005)
+ 861f Anysee E30 USB 2.0 DVB-T Receiver
+ bca1 Barcode Reader
+ cc04 Centor USB RACIA-ALVAR USB PORT
+ cc06 Centor-P RACIA-ALVAR USB PORT
+ d5d5 CY7C63x0x Zoltrix Z-Boxer GamePad
+ de61 Barcode Reader
+ de64 Barcode Reader
+ f000 CY30700 Licorice evaluation board
+ f111 CY8CKIT-002 PSoC MiniProg3 Rev A Program and debug kit
+ f115 PSoC FirstTouch Programmer
+ f232 Mono embedded computer
+ fd13 Programmable power socket
+04b5 ROHM LSI Systems USA, LLC
+ 3064 Hantek DSO-3064
+04b6 Hint Corp.
+04b7 Compal Electronics, Inc.
+04b8 Seiko Epson Corp.
+ 0001 Stylus Color 740 / Photo 750
+ 0002 ISD Smart Cable for Mac
+ 0003 ISD Smart Cable
+ 0004 Printer
+ 0005 Printer
+ 0006 Printer
+ 0007 Printer
+ 0015 Stylus Photo R3000
+ 0101 GT-7000U [Perfection 636]
+ 0102 GT-2200
+ 0103 GT-6600U [Perfection 610]
+ 0104 GT-7600UF [Perfection 1200U/1200U Photo]
+ 0105 Stylus Scan 2000
+ 0106 Stylus Scan 2500
+ 0107 ES-2000 [Expression 1600U]
+ 0108 CC-700
+ 0109 ES-8500 [Expression 1640 XL]
+ 010a GT-8700/GT-8700F [Perfection 1640SU/1640SU PHOTO]
+ 010b GT-7700U [Perfection 1240U]
+ 010c GT-6700U [Perfection 640]
+ 010d CC-500L
+ 010e ES-2200 [Perfection 1680]
+ 010f GT-7200U [Perfection 1250/1250 PHOTO]
+ 0110 GT-8200U/GT-8200UF [Perfection 1650/1650 PHOTO]
+ 0112 GT-9700F [Perfection 2450 PHOTO]
+ 0114 Perfection 660
+ 0116 GT-9400UF [Perfection 3170]
+ 0118 GT-F600 [Perfection 4180]
+ 0119 GT-X750 [Perfection 4490 Photo]
+ 011a CC-550L [1000 ICS]
+ 011b GT-9300UF [Perfection 2400 PHOTO]
+ 011c GT-9800F [Perfection 3200]
+ 011d GT-7300U [Perfection 1260/1260 PHOTO]
+ 011e GT-8300UF [Perfection 1660 PHOTO]
+ 011f GT-8400UF [Perfection 1670/1670 PHOTO]
+ 0120 GT-7400U [Perfection 1270]
+ 0121 GT-F500/GT-F550 [Perfection 2480/2580 PHOTO]
+ 0122 GT-F520/GT-F570 [Perfection 3590 PHOTO]
+ 0126 ES-7000H [GT-15000]
+ 0128 GT-X700 [Perfection 4870]
+ 0129 ES-10000G [Expression 10000XL]
+ 012a GT-X800 [Perfection 4990 PHOTO]
+ 012b ES-H300 [GT-2500]
+ 012c GT-X900 [Perfection V700/V750 Photo]
+ 012d GT-F650 [GT-S600/Perfection V10/V100]
+ 012e GT-F670 [Perfection V200 Photo]
+ 012f GT-F700 [Perfection V350]
+ 0130 GT-X770 [Perfection V500]
+ 0131 GT-F720 [GT-S620/Perfection V30/V300 Photo]
+ 0133 GT-1500 [GT-D1000]
+ 0135 GT-X970
+ 0136 ES-D400 [GT-S80]
+ 0137 ES-D200 [GT-S50]
+ 0138 ES-H7200 [GT-20000]
+ 013a GT-X820 [Perfection V600 Photo]
+ 0142 GT-F730 [GT-S630/Perfection V33/V330 Photo]
+ 0143 GT-S55
+ 0144 GT-S85
+ 0151 Perfection V800 Photo
+ 0202 Receipt Printer M129C/TM-T70
+ 0401 CP 800 Digital Camera
+ 0402 PhotoPC 850z
+ 0403 PhotoPC 3000z
+ 0509 JVC PIX-MC10
+ 0601 Stylus Photo 875DC Card Reader
+ 0602 Stylus Photo 895 Card Reader
+ 0801 CC-600PX [Stylus CX5200/CX5400/CX6600]
+ 0802 CC-570L [Stylus CX3100/CX3200]
+ 0803 Printer (Composite Device)
+ 0804 Storage Device
+ 0805 Stylus CX6300/CX6400
+ 0806 PM-A850 [Stylus Photo RX600/610]
+ 0807 Stylus Photo RX500/510
+ 0808 Stylus CX5200/CX5300/CX5400
+ 0809 Storage Device
+ 080a F-3200
+ 080c ME100 [Stylus CX1500]
+ 080d Stylus CX4500/4600
+ 080e PX-A550 [CX-3500/3600/3650 MFP]
+ 080f Stylus Photo RX420/RX425/RX430
+ 0810 PM-A900 [Stylus Photo RX700]
+ 0811 PM-A870 [Stylus Photo RX620/RX630]
+ 0812 MFP Composite Device
+ 0813 Stylus CX6500/6600
+ 0814 PM-A700
+ 0815 LP-A500 [AcuLaser CX1]
+ 0816 Printer (Composite Device)
+ 0817 LP-M5500/LP-M5500F
+ 0818 Stylus CX3700/CX3800/DX3800
+ 0819 PX-A650 [Stylus CX4700/CX4800/DX4800/DX4850]
+ 081a PM-A750 [Stylus Photo RX520/RX530]
+ 081b MFP Composite Device
+ 081c PM-A890 [Stylus Photo RX640/RX650]
+ 081d PM-A950
+ 081e MFP Composite Device
+ 081f Stylus CX7700/7800
+ 0820 Stylus CX4100/CX4200/DX4200
+ 0821 Stylus CX5700F/CX5800F
+ 0822 Storage Device
+ 0823 MFP Composite Device
+ 0824 Storage Device
+ 0825 MFP Composite Device
+ 0826 Storage Device
+ 0827 PM-A820 [Stylus Photo RX560/RX580/RX585/RX590]
+ 0828 PM-A970
+ 0829 PM-T990
+ 082a PM-A920
+ 082b Stylus CX5900/CX5000/DX5000/DX5050
+ 082c Storage Device
+ 082d Storage Device
+ 082e PX-A720 [Stylus CX5900/CX6000/DX6000]
+ 082f PX-A620 [Stylus CX3900/DX4000/DX4050]
+ 0830 ME 200 [Stylus CX2800/CX2900]
+ 0831 Stylus CX6900F/CX7000F/DX7000F
+ 0832 MFP Composite Device
+ 0833 LP-M5600
+ 0834 LP-M6000
+ 0835 AcuLaser CX21
+ 0836 PM-T960
+ 0837 PM-A940 [Stylus Photo RX680/RX685/RX690]
+ 0838 PX-A640 [CX7300/CX7400/DX7400]
+ 0839 PX-A740 [CX8300/CX8400/DX8400]
+ 083a PX-FA700 [CX9300F/CX9400Fax/DX9400F]
+ 083b MFP Composite Device
+ 083c PM-A840S [Stylus Photo RX595/RX610]
+ 083d MFP Composite Device
+ 083e MFP Composite Device
+ 083f Stylus CX4300/CX4400/CX5500/CX5600/DX4400/DX4450
+ 0841 PX-401A [ME 300/Stylus NX100]
+ 0843 LP-M5000
+ 0844 EP-901A/EP-901F [Artisan 800/Stylus Photo PX800FW]
+ 0846 EP-801A [Artisan 700/Stylus Photo PX700W/TX700W]
+ 0847 PX-601F [ME Office 700FW/Stylus Office BX600FW/TX600FW]
+ 0848 ME Office 600F/Stylus Office BX300F/TX300F
+ 0849 Stylus SX205
+ 084a PX-501A [Stylus NX400]
+ 084d PX-402A [Stylus SX115/Stylus NX110 Series]
+ 084f ME OFFICE 510
+ 0850 EP-702A [Stylus Photo PX650/TX650 Series]
+ 0851 Stylus SX410
+ 0852 EP-802A [Artisan 710 Series/Stylus Photo PX710W/TX720W Series]
+ 0853 EP-902A [Artisan 810 Series/Stylus Photo PX810FW Series]
+ 0854 ME OFFICE 650FN Series/Stylus Office BX310FN/TX520FN Series
+ 0855 PX-602F [Stylus Office BX610FW/TX620FW Series]
+ 0856 PX-502A [Stylus SX515W]
+ 085c ME 320/330 Series [Stylus SX125]
+ 085d PX-603F [ME OFFICE 960FWD Series/Stylus Office BX625FWD/TX620FWD Series]
+ 085e PX-503A [ME OFFICE 900WD Series/Stylus Office BX525WD]
+ 085f Stylus Office BX320FW/TX525FW Series
+ 0860 EP-903A/EP-903F [Artisan 835/Stylus Photo PX820FWD Series]
+ 0861 EP-803A/EP-803AW [Artisan 725/Stylus Photo PX720WD/TX720WD Series]
+ 0862 EP-703A [Stylus Photo PX660 Series]
+ 0863 ME OFFICE 620F Series/Stylus Office BX305F/BX305FW/TX320F
+ 0864 ME OFFICE 560W Series
+ 0865 ME OFFICE 520 Series
+ 0866 AcuLaser MX20DN/MX20DNF/MX21DNF
+ 0869 PX-1600F
+ 086a PX-673F [Stylus Office BX925FWD]
+ 0870 Stylus Office BX305FW Plus
+ 0871 K200 Series
+ 0872 K300 Series
+ 0873 L200 Series
+ 0878 EP-704A
+ 0879 EP-904A/EP-904F [Artisan 837/Stylus Photo PX830FWD Series]
+ 087b EP-804A/EP-804AR/EP-804AW [Stylus Photo PX730WD/Artisan 730 Series]
+ 087c PX-1700F
+ 087d PX-B750F/WP-4525 Series
+ 087f PX-403A
+ 0880 PX-434A [Stylus NX330 Series]
+ 0881 PX-404A [ME OFFICE 535]
+ 0883 ME 340 Series/Stylus NX130 Series
+ 0884 Stylus NX430W Series
+ 0885 Stylus NX230/SX235W Series
+ 088f Stylus Office BX635FWD
+ 0890 ME OFFICE 940FW Series/Stylus Office BX630FW Series
+ 0891 Stylus Office BX535WD
+ 0892 Stylus Office BX935FWD
+ 0893 EP-774A
+04b9 Rainbow Technologies, Inc.
+ 0300 SafeNet USB SuperPro/UltraPro
+ 1000 iKey 1000 Token
+ 1001 iKey 1200 Token
+ 1002 iKey Token
+ 1003 iKey Token
+ 1004 iKey Token
+ 1005 iKey Token
+ 1006 iKey Token
+ 1200 iKey 2000 Token
+ 1201 iKey Token
+ 1202 iKey 2032 Token
+ 1203 iKey Token
+ 1204 iKey Token
+ 1205 iKey Token
+ 1206 iKey 4000 Token
+ 1300 iKey 3000 Token
+ 1301 iKey 3000
+ 1302 iKey Token
+ 1303 iKey Token
+ 1304 iKey Token
+ 1305 iKey Token
+ 1306 iKey Token
+ 8000 SafeNet Sentinel Hardware Key
+04ba Toucan Systems, Ltd
+04bb I-O Data Device, Inc.
+ 0101 USB2-IDE/ATAPI Bridge Adapter
+ 0201 USB2-IDE/ATAPI Bridge Adapter
+ 0204 DVD Multi-plus unit iU-CD2
+ 0206 DVD Multi-plus unit DVR-UEH8
+ 0301 Storage Device
+ 0314 USB-SSMRW SD-card
+ 0319 USB2-IDE/ATAPI Bridge Adapter
+ 031a USB2-IDE/ATAPI Bridge Adapter
+ 031b USB2-IDE/ATAPI Bridge Adapter
+ 031e USB-SDRW SD-card
+ 0502 Nogatech Live! (BT)
+ 0528 GV-USB Video Capture
+ 0901 USB ETT
+ 0904 ET/TX Ethernet [pegasus]
+ 0913 ET/TX-S Ethernet [pegasus2]
+ 0919 USB WN-B11
+ 0922 IOData AirPort WN-B11/USBS 802.11b
+ 0930 ETG-US2
+ 0937 WN-WAG/USL Wireless LAN Adapter
+ 0938 WN-G54/USL Wireless LAN Adapter
+ 093b WN-GDN/USB
+ 093f WNGDNUS2 802.11n
+ 0944 WHG-AGDN/US Wireless LAN Adapter
+ 0945 WN-GDN/US3 Wireless LAN Adapter
+ 0947 WN-G150U Wireless LAN Adapter
+ 0948 WN-G300U Wireless LAN Adapter
+ 0a03 Serial USB-RSAQ1
+ 0a07 USB2-iCN Adapter
+ 0a08 USB2-iCN Adapter
+ 0c01 FM-10 Pro Disk
+04bd Toshiba Electronics Taiwan Corp.
+04be Telia Research AB
+04bf TDK Corp.
+ 0100 MediaReader CF
+ 0115 USB-PDC Adapter UPA9664
+ 0116 USB-cdmaOne Adapter UCA1464
+ 0117 USB-PHS Adapter UHA6400
+ 0118 USB-PHS Adapter UPA6400
+ 0135 MediaReader Dual
+ 0202 73S1121F Smart Card Reader-
+ 0309 Bluetooth USB dongle
+ 030a IBM Bluetooth Ultraport Module
+ 030b Bluetooth Device
+ 030c Ultraport Bluetooth Device
+ 0310 Integrated Bluetooth
+ 0311 Integrated Bluetooth Device
+ 0317 Bluetooth UltraPort Module from IBM
+ 0318 IBM Integrated Bluetooth
+ 0319 Bluetooth Adapter
+ 0320 Bluetooth Adapter
+ 0321 Bluetooth Device
+ 0a28 INDI AV-IN Device
+04c1 U.S. Robotics (3Com)
+ 0020 56K Voice Pro
+ 0022 56K Voice Pro
+ 007e ISDN TA
+ 0082 OfficeConnect Analog Modem
+ 008f Pro ISDN TA
+ 0097 OfficeConnect Analog
+ 009d HomeConnect Webcam [vicam]
+ 00a9 ISDN Pro TA-U
+ 00b9 HomeConnect IDSL Modem
+ 3021 56k Voice FaxModem Pro
+04c2 Methode Electronics Far East PTE, Ltd
+04c3 Maxi Switch, Inc.
+ 1102 Mouse
+ 2102 Mouse
+04c4 Lockheed Martin Energy Research
+04c5 Fujitsu, Ltd
+ 1029 fi-4010c Scanner
+ 1033 fi-4110CU
+ 1041 fi-4120c Scanner
+ 1042 fi-4220c Scanner
+ 105b AH-F401U Air H device
+ 1084 PalmSecure Sensor V2
+ 1096 fi-5110EOX
+ 1097 fi-5110C
+ 10ae fi-4120C2
+ 10af fi-4220C2
+ 10c7 fi-60f scanner
+ 10e0 fi-5120c Scanner
+ 10e1 fi-5220C
+ 10e7 fi-5900C
+ 10fe S500
+ 1150 fi-6230
+ 125a PalmSecure Sensor Device - MP
+ 201d SATA 3.0 6Gbit/s Adaptor [GROOVY]
+04c6 Toshiba America Electronic Components
+04c7 Micro Macro Technologies
+04c8 Konica Corp.
+ 0720 Digital Color Camera
+ 0721 e-miniD Camera
+ 0722 e-mini
+ 0723 KD-200Z Camera
+ 0726 KD-310Z Camera
+ 0728 Revio C2 Mass Storage Device
+ 0729 Revio C2 Digital Camera
+ 072c Revio KD20M
+ 072d Revio KD410Z
+04ca Lite-On Technology Corp.
+ 004b Keyboard
+ 004f SK-9020 keyboard
+ 1766 HID Monitor Controls
+ 2004 Bluetooth 4.0 [Broadcom BCM20702A0]
+ 2006 Broadcom BCM43142A0 Bluetooth Device
+ 2007 Broadcom BCM43142A0 Bluetooth Device
+ 3005 Atheros Bluetooth
+ 300b Atheros AR3012 Bluetooth
+ 300d Atheros AR3012 Bluetooth
+ 300f Atheros AR3012 Bluetooth
+ 3014 Qualcomm Atheros Bluetooth
+ 7025 HP HD Webcam
+ 7046 TOSHIBA Web Camera - HD
+ 9304 Hub
+ f01c TT1280DA DVB-T TV Tuner
+04cb Fuji Photo Film Co., Ltd
+ 0100 FinePix 30i/40i/50i, A101/201, 1300/2200, 1400/2400/2600/2800/4500/4700/4800/4900/6800/6900 Zoom
+ 0103 FinePix NX-500/NX-700 printer
+ 0104 FinePix A101, 2600/2800/4800/6800 Zoom (PC CAM)
+ 0108 FinePix F601 Zoom (DSC)
+ 0109 FinePix F601 Zoom (PC CAM)
+ 010a FinePix S602 (Pro) Zoom (DSC)
+ 010b FinePix S602 (Pro) Zoom (PC CAM)
+ 010d FinePix Digital Camera 020531
+ 010e FinePix F402 Zoom (DSC)
+ 010f FinePix F402 Zoom (PC CAM)
+ 0110 FinePix M603 Zoom (DSC)
+ 0111 FinePix M603 Zoom (PC CAM)
+ 0112 FinePix A202, A200 Zoom (DSC)
+ 0113 FinePix A202, A200 Zoom (PC CAM)
+ 0114 FinePix F401 Zoom (DSC)
+ 0115 FinePix F401 Zoom (PC CAM)
+ 0116 FinePix A203 Zoom (DSC)
+ 0117 FinePix A203 Zoom (PC CAM)
+ 0118 FinePix A303 Zoom (DSC)
+ 0119 FinePix A303 Zoom (PC CAM)
+ 011a FinePix S304/3800 Zoom (DSC)
+ 011b FinePix S304/3800 Zoom (PC CAM)
+ 011c FinePix A204/2650 Zoom (DSC)
+ 011d FinePix A204/2650 Zoom (PC CAM)
+ 0120 FinePix F700 Zoom (DSC)
+ 0121 FinePix F700 Zoom (PC CAM)
+ 0122 FinePix F410 Zoom (DSC)
+ 0123 FinePix F410 Zoom (PC CAM)
+ 0124 FinePix A310 Zoom (DSC)
+ 0125 FinePix A310 Zoom (PC CAM)
+ 0126 FinePix A210 Zoom (DSC)
+ 0127 FinePix A210 Zoom (PC CAM)
+ 0128 FinePix A205(S) Zoom (DSC)
+ 0129 FinePix A205(S) Zoom (PC CAM)
+ 012a FinePix F610 Zoom (DSC)
+ 012b FinePix Digital Camera 030513
+ 012c FinePix S7000 Zoom (DSC)
+ 012d FinePix S7000 Zoom (PC CAM)
+ 012f FinePix Digital Camera 030731
+ 0130 FinePix S5000 Zoom (DSC)
+ 0131 FinePix S5000 Zoom (PC CAM)
+ 013b FinePix Digital Camera 030722
+ 013c FinePix S3000 Zoom (DSC)
+ 013d FinePix S3000 Zoom (PC CAM)
+ 013e FinePix F420 Zoom (DSC)
+ 013f FinePix F420 Zoom (PC CAM)
+ 0142 FinePix S7000 Zoom (PTP)
+ 0148 FinePix A330 Zoom (DSC)
+ 0149 FinePix A330 Zoom (UVC)
+ 014a FinePix A330 Zoom (PTP)
+ 014b FinePix A340 Zoom (DSC)
+ 014c FinePix A340 Zoom (UVC)
+ 0159 FinePix F710 Zoom (DSC)
+ 0165 FinePix S3500 Zoom (DSC)
+ 0168 FinePix E500 Zoom (DSC)
+ 0169 FinePix E500 Zoom (UVC)
+ 016b FinePix E510 Zoom (DSC)
+ 016c FinePix E510 Zoom (PC CAM)
+ 016e FinePix S5500 Zoom (DSC)
+ 016f FinePix S5500 Zoom (UVC)
+ 0171 FinePix E550 Zoom (DSC)
+ 0172 FinePix E550 Zoom (UVC)
+ 0177 FinePix F10 (DSC)
+ 0179 Finepix F10 (PTP)
+ 0186 FinePix S5200/S5600 Zoom (DSC)
+ 0188 FinePix S5200/S5600 Zoom (PTP)
+ 018e FinePix S9500 Zoom (DSC)
+ 018f FinePix S9500 Zoom (PTP)
+ 0192 FinePix E900 Zoom (DSC)
+ 0193 FinePix E900 Zoom (PTP)
+ 019b FinePix F30 (PTP)
+ 01af FinePix A700 (PTP)
+ 01bf FinePix F6000fd/S6500fd Zoom (PTP)
+ 01c0 FinePix F20 (PTP)
+ 01c1 FinePix F31fd (PTP)
+ 01c4 FinePix S5700 Zoom (PTP)
+ 01c5 FinePix F40fd (PTP)
+ 01c6 FinePix A820 Zoom (PTP)
+ 01d2 FinePix A800 Zoom (PTP)
+ 01d3 FinePix A920 (PTP)
+ 01d4 FinePix F50fd (PTP)
+ 01d5 FinePix F47 (PTP)
+ 01f7 FinePix J250 (PTP)
+ 01fd A160
+ 023e FinePix AX300
+ 0240 FinePix S2950 Digital Camera
+ 0241 FinePix S3200 Digital Camera
+ 0278 FinePix JV300
+04cc ST-Ericsson
+ 1122 Hub
+ 1520 USB 2.0 Hub (Avocent KVM)
+ 1521 USB 2.0 Hub
+ 1a62 GW Instek GSP-830 Spectrum Analyzer (HID)
+ 2323 Ux500 serial debug port
+ 2533 NFC device (PN533)
+ 8116 Camera
+04cd Tatung Co. Of America
+04ce ScanLogic Corp.
+ 0002 SL11R-IDE IDE Bridge
+ 0100 USB2PRN Printer Class
+ 0300 Phantom 336CX - C3 scanner
+ 04ce SL11DEMO, VID: 0x4ce, PID: 0x4ce
+ 07d1 SL11R, VID: 0x4ce, PID: 0x07D1
+04cf Myson Century, Inc.
+ 0022 OCZ Alchemy Series Elixir II Keyboard
+ 0800 MTP800 Mass Storage Device
+ 8810 CS8810 Mass Storage Device
+ 8811 CS8811 Mass Storage Device
+ 8813 CS8813 Mass Storage Device
+ 8818 USB2.0 to ATAPI Bridge Controller
+ 8819 USB 2.0 SD/MMC Reader
+ 9920 CS8819A2-114 Mass Storage Device
+04d0 Digi International
+04d1 ITT Canon
+04d2 Altec Lansing Technologies
+ 0070 ADA70 Speakers
+ 0305 Non-Compliant Audio Device
+ 0311 ADA-310 Speakers
+ 2060 Claritel-i750 - vp
+ ff05 ADA-305 Speakers
+ ff47 Lansing HID Audio Controls
+ ff49 Lansing HID Audio Controls
+04d3 VidUS, Inc.
+04d4 LSI Logic, Inc.
+04d5 Forte Technologies, Inc.
+04d6 Mentor Graphics
+04d7 Oki Semiconductor
+ 1be4 Bluetooth Device
+04d8 Microchip Technology, Inc.
+ 0002 PicoLCD 20x2
+ 0003 PICkit 2 Microcontroller Programmer
+ 000a CDC RS-232 Emulation Demo
+ 000b PIC18F2550 (32K Flashable 10 Channel, 10 Bit A/D USB Microcontroller)
+ 0032 PICkit1
+ 0033 PICkit2
+ 0036 PICkit Serial Analyzer
+ 00e0 PIC32 Starter Board
+ 04cd 28Cxxx EEPROM Programmer
+ 0a04 AGP LIN Serial Analyzer
+ 8000 In-Circuit Debugger
+ 8001 ICD2 in-circuit debugger
+ 8101 PIC24F Starter Kit
+ 8107 Microstick II
+ 8108 ChipKit Pro MX7 (PIC32MX)
+ 9004 Microchip REAL ICE
+ 900a PICkit3
+ c001 PicoLCD 20x4
+ e11c TL866CS EEPROM Programmer [MiniPRO]
+ f2c4 Macareux-labs Hygrometry Temperature Sensor
+ f2f7 Yepkit YKUSH
+ f3aa Macareux-labs Usbce Bootloader mode
+ f437 SBE Tech Ultrasonic Anemometer
+ f4b5 SmartScope
+ f8da Hughski Ltd. ColorHug
+ f8e8 Harmony 300/350 Remote
+ f91c SPROG IIv3
+ faff Dangerous Prototypes BusPirate v4 Bootloader mode
+ fb00 Dangerous Prototypes BusPirate v4
+ fbb2 GCUSB-nStep stepper motor controller
+ fbba DiscFerret Magnetic Disc Analyser (bootloader mode)
+ fbbb DiscFerret Magnetic Disc Analyser (active mode)
+ fc1e Bachrus Speedometer Interface
+ fc92 Open Bench Logic Sniffer
+ ffee Devantech USB-ISS
+ ffef PICoPLC [APStech]
+04d9 Holtek Semiconductor, Inc.
+ 0022 Portable Keyboard
+ 048e Optical Mouse
+ 0499 Optical Mouse
+ 1203 Keyboard
+ 1400 PS/2 keyboard + mouse controller
+ 1503 Keyboard
+ 1603 Keyboard
+ 1702 Keyboard LKS02
+ 1818 Keyboard [Diatec Filco Majestouch 2]
+ 2011 Keyboard [Diatec Filco Majestouch 1]
+ 2013 Keyboard [Das Keyboard]
+ 2206 Fujitsu Siemens Mouse Esprimo Q
+ 2221 Keyboard
+ 2323 Keyboard
+ 2519 Shenzhen LogoTech 2.4GHz receiver
+ 2832 HT82A832R Audio MCU
+ 2834 HT82A834R Audio MCU
+ a01c wireless multimedia keyboard with trackball [Trust ADURA 17911]
+ a050 Chatman V1
+ a055 Keyboard
+ a11b Mouse [MX-3200]
+04da Panasonic (Matsushita)
+ 0901 LS-120 Camera
+ 0912 SDR-S10
+ 0b01 CD-R/RW Drive
+ 0b03 SuperDisk 240MB
+ 0d01 CD-R Drive KXL-840AN
+ 0d09 CD-R Drive KXL-RW32AN
+ 0d0a CD-R Drive KXL-CB20AN
+ 0d0d CDRCB03
+ 0d0e DVD-ROM & CD-R/RW
+ 0f07 KX-MB2030 Multifunction Laser Printer
+ 0f40 Printer
+ 104d Elite Panaboard UB-T880 (HID)
+ 104e Elite Panaboard Pen Adaptor (HID)
+ 1500 MFSUSB Driver
+ 1800 DY-WL10 802.11abgn Adapter [Broadcom BCM4323]
+ 1b00 MultiMediaCard
+ 2121 EB-VS6
+ 2316 DVC Mass Storage Device
+ 2317 DVC USB-SERIAL Driver for WinXP
+ 2318 NV-GS11/230/250 (webcam mode)
+ 2319 NV-GS15 (webcam mode)
+ 231a NV-GS11/230/250 (DV mode)
+ 231d DVC Web Camera Device
+ 231e DVC DV Stream Device
+ 2372 Lumix Camera (Storage mode)
+ 2374 Lumix Camera (PTP mode)
+ 2451 HDC-SD9
+ 245b HC-X920K (3MOS Full HD video camcorder)
+ 2477 SDR-H85 Camcorder (PC mode)
+ 2478 SDR-H85 Camcorder (recorder mode - SD card)
+ 2479 SDR-H85 Camcorder (recorder mode - HDD)
+ 2497 HDC-TM700
+ 250c Gobi Wireless Modem (QDL mode)
+ 250d Gobi Wireless Modem
+ 3904 N5HBZ0000055 802.11abgn Wireless Adapter [Atheros AR7010+AR9280]
+ 3c04 JT-P100MR-20 [ePassport Reader]
+04db Hypertec Pty, Ltd
+04dc Huan Hsin Holdings, Ltd
+04dd Sharp Corp.
+ 13a6 MFC2000
+ 6006 AL-1216
+ 6007 AL-1045
+ 6008 AL-1255
+ 6009 AL-1530CS
+ 600a AL-1540CS
+ 600b AL-1456
+ 600c AL-1555
+ 600d AL-1225
+ 600e AL-1551CS
+ 600f AR-122E
+ 6010 AR-152E
+ 6011 AR-157E
+ 6012 SN-1045
+ 6013 SN-1255
+ 6014 SN-1456
+ 6015 SN-1555
+ 6016 AR-153E
+ 6017 AR-122E N
+ 6018 AR-153E N
+ 6019 AR-152E N
+ 601a AR-157E N
+ 601b AL-1217
+ 601c AL-1226
+ 601d AR-123E
+ 6021 IS01
+ 7002 DVC Ver.1.0
+ 7004 VE-CG40U Digital Still Camera
+ 7005 VE-CG30 Digital Still Camera
+ 7007 VL-Z7S Digital Camcorder
+ 8004 Zaurus SL-5000D/SL-5500 PDA
+ 8005 Zaurus A-300
+ 8006 Zaurus SL-B500/SL-5600 PDA
+ 8007 Zaurus C-700 PDA
+ 9009 AR-M160
+ 9014 IM-DR80 Portable NetMD Player
+ 9031 Zaurus C-750/C-760/C-860/SL-C3000 PDA
+ 9032 Zaurus SL-6000
+ 903a GSM GPRS
+ 9050 Zaurus C-860 PDA
+ 9056 Viewcam Z
+ 9073 AM-900
+ 9074 GSM GPRS
+ 90a9 Sharp Composite
+ 90d0 USB-to-Serial Comm. Port
+ 90f2 Sharp 3G GSM USB Control
+ 9120 WS004SH
+ 9122 WS007SH
+ 9123 W-ZERO3 ES Smartphone
+ 91a3 922SH Internet Machine
+ 939a IS03
+04de MindShare, Inc.
+04df Interlink Electronics
+04e1 Iiyama North America, Inc.
+ 0201 Monitor Hub
+04e2 Exar Corp.
+ 1410 XR21V1410 USB-UART IC
+04e3 Zilog, Inc.
+04e4 ACC Microelectronics
+04e5 Promise Technology
+04e6 SCM Microsystems, Inc.
+ 0001 E-USB ATA Bridge
+ 0002 eUSCSI SCSI Bridge
+ 0003 eUSB SmartMedia Card Reader
+ 0005 eUSB SmartMedia/CompactFlash Card Reader
+ 0006 eUSB SmartMedia Card Reader
+ 0007 Hifd
+ 0009 eUSB ATA/ATAPI Adapter
+ 000a eUSB CompactFlash Adapter
+ 000b eUSCSI Bridge
+ 000c eUSCSI Bridge
+ 000d Dazzle MS
+ 0012 Dazzle SD/MMC
+ 0101 eUSB ATA Bridge (Sony Spressa USB CDRW)
+ 0311 Dazzle DM-CF
+ 0312 Dazzle DM-SD/MMC
+ 0313 Dazzle SM
+ 0314 Dazzle MS
+ 0322 e-Film Reader-5
+ 0325 eUSB ORCA Quad Reader
+ 0327 Digital Media Reader
+ 03fe DMHS2 DFU Adapter
+ 0406 eUSB SmartDM Reader
+ 04e6 eUSB DFU Adapter
+ 04e7 STCII DFU Adapter
+ 04e8 eUSBDM DFU Adapter
+ 04e9 DM-E DFU Adapter
+ 0500 Veridicom 5thSense Fingerprint Sensor and eUSB SmartCard
+ 0701 DCS200 Loader Device
+ 0702 DVD Creation Station 200
+ 0703 DVC100 Loader Device
+ 0704 Digital Video Creator 100
+ 1001 SCR300 Smart Card Reader
+ 1010 USBAT-2 CompactFlash Card Reader
+ 1014 e-Film Reader-3
+ 1020 USBAT ATA/ATAPI Adapter
+ 2007 RSA SecurID ComboReader
+ 2009 Citibank Smart Card Reader
+ 200a Reflex v.2 Smart Card Reader
+ 200d STR391 Reader
+ 5111 SCR331-DI SmartCard Reader
+ 5113 SCR333 SmartCard Reader
+ 5114 SCR331-DI SmartCard Reader
+ 5115 SCR335 SmartCard Reader
+ 5116 SCR331-LC1 / SCR3310 SmartCard Reader
+ 5117 SCR3320 - Smart Card Reader
+ 5118 Expresscard SIM Card Reader
+ 5119 SCR3340 - ExpressCard54 Smart Card Reader
+ 511b SmartCard Reader
+ 511d SCR3311 Smart Card Reader
+ 5120 SCR331-DI SmartCard Reader
+ 5121 SDI010 Smart Card Reader
+ 5151 SCR338 Keyboard Smart Card Reader
+ 5292 SCL011 RFID reader
+ 5410 SCR35xx Smart Card Reader
+ 5591 SCL3711-NFC&RW
+ e000 SCRx31 Reader
+ e001 SCR331 SmartCard Reader
+ e003 SPR532 PinPad SmartCard Reader
+04e7 Elo TouchSystems
+ 0001 TouchScreen
+ 0002 Touchmonitor Interface 2600 Rev 2
+ 0004 4000U CarrollTouch® Touchmonitor Interface
+ 0007 2500U IntelliTouch® Touchmonitor Interface
+ 0008 3000U AccuTouch® Touchmonitor Interface
+ 0009 4000U CarrollTouch® Touchmonitor Interface
+ 0020 Touchscreen Interface (2700)
+ 0021 Touchmonitor Interface
+ 0030 4500U CarrollTouch® Touchmonitor Interface
+ 0032 Touchmonitor Interface
+ 0033 Touchmonitor Interface
+ 0041 5010 Surface Capacitive Touchmonitor Interface
+ 0042 Touchmonitor Interface
+ 0050 2216 AccuTouch® Touchmonitor Interface
+ 0071 Touchmonitor Interface
+ 0072 Touchmonitor Interface
+ 0081 Touchmonitor Interface
+ 0082 Touchmonitor Interface
+ 00ff Touchmonitor Interface
+04e8 Samsung Electronics Co., Ltd
+ 0001 Printer Bootloader
+ 0100 Kingston Flash Drive (128MB)
+ 0110 Connect3D Flash Drive
+ 0111 Connect3D Flash Drive
+ 0300 E2530 / GT-C3350 Phones (Mass storage mode)
+ 1003 MP3 Player and Recorder
+ 1006 SDC-200Z
+ 130c NX100
+ 1323 WB700 Camera
+ 1f05 S2 Portable [JMicron] (500GB)
+ 1f06 HX-MU064DA portable harddisk
+ 2018 WIS09ABGN LinkStick Wireless LAN Adapter
+ 2035 Digital Photo Frame Mass Storage
+ 2036 Digital Photo Frame Mini Monitor
+ 3004 ML-4600
+ 3005 Docuprint P1210
+ 3008 ML-6060 laser printer
+ 300c ML-1210 Printer
+ 300e Laser Printer
+ 3104 ML-3550N
+ 3210 ML-5200A Laser Printer
+ 3226 Laser Printer
+ 3228 Laser Printer
+ 322a Laser Printer
+ 322c Laser Printer
+ 3230 ML-1440
+ 3232 Laser Printer
+ 3236 ML-1450
+ 3238 ML-1430
+ 323a ML-1710 Printer
+ 323b Phaser 3130
+ 323c Laser Printer
+ 323d Phaser 3120
+ 323e Laser Printer
+ 3240 Laser Printer
+ 3242 ML-1510 Laser Printer
+ 3248 Color Laser Printer
+ 324a Laser Printer
+ 324c ML-1740 Printer
+ 324d Phaser 3121
+ 3256 ML-1520 Laser Printer
+ 325b Xerox Phaser 3117 Laser Printer
+ 325f Phaser 3425 Laser Printer
+ 3260 CLP-510 Color Laser Printer
+ 3268 ML-1610 Mono Laser Printer
+ 326c ML-2010P Mono Laser Printer
+ 3276 ML-3050/ML-3051 Laser Printer
+ 328e CLP-310 Color Laser Printer
+ 3292 ML-1640 Series Laser Printer
+ 3296 ML-2580N Mono Laser Printer
+ 3297 ML-191x/ML-252x Laser Printer
+ 329f CLP-325 Color Laser Printer
+ 3301 ML-1660 Series
+ 330c ML-1865
+ 3310 ML-331x Series Laser Printer
+ 3315 ML-2540 Series Laser Printer
+ 331e M262x/M282x Xpress Series Laser Printer
+ 3409 SCX-4216F Scanner
+ 340c SCX-5x15 series
+ 340d SCX-6x20 series
+ 340e MFP 560 series
+ 340f Printing Support
+ 3412 SCX-4x20 series
+ 3413 SCX-4100 Scanner
+ 3415 Composite Device
+ 3419 Composite Device
+ 341a Printing Support
+ 341b SCX-4200 series
+ 341c Composite Device
+ 341d Composite Device
+ 341f Composite Device
+ 3420 Composite Device
+ 3426 SCX-4500 Laser Printer
+ 342d SCX-4x28 Series
+ 344f SCX-3400 Series
+ 3605 InkJet Color Printer
+ 3606 InkJet Color Printer
+ 3609 InkJet Color Printer
+ 3902 InkJet Color Printer
+ 3903 Xerox WorkCentre XK50cx
+ 390f InkJet Color Printer
+ 3911 SCX-1020 series
+ 4005 GT-S8000 Jet (msc)
+ 4f1f GT-S8000 Jet (mtp)
+ 5000 YP-MF series
+ 5001 YP-100
+ 5002 YP-30
+ 5003 YP-700
+ 5004 YP-30
+ 5005 YP-300
+ 5006 YP-750
+ 500d MP3 Player
+ 5010 Yepp YP-35
+ 5011 YP-780
+ 5013 YP-60
+ 5015 yepp upgrade
+ 501b MP3 Player
+ 5021 Yepp YP-ST5
+ 5026 YP-MT6V
+ 5027 YP-T7
+ 502b YP-F1
+ 5032 YP-J70
+ 503b YP-U1 MP3 Player
+ 503d YP-T7F
+ 5041 YP-Z5
+ 5050 YP-U2 MP3 Player
+ 5051 YP-F2R
+ 5055 YP-T9
+ 507d YP-U3 (mtp)
+ 507f YP-T9J
+ 5080 Yepp YP-K3 (msc)
+ 5081 Yepp YP-K3 (mtp)
+ 5082 YP-P2 (msc)
+ 5083 YP-P2 (mtp)
+ 508a YP-T10
+ 508b YP-S5 MP3 Player
+ 508c YP-S5
+ 5090 YP-S3 (msc)
+ 5091 YP-S3 (mtp)
+ 5092 YP-U4 (msc)
+ 5093 YP-U4 (mtp)
+ 5095 YP-S2
+ 510f YP-R1
+ 5119 Yepp YP-P3
+ 511c YP-Q2
+ 5121 YP-U5
+ 5123 Yepp YP-M1
+ 5a00 YP-NEU
+ 5a01 YP-NDU
+ 5a03 Yepp MP3 Player
+ 5a04 YP-800
+ 5a08 YP-90
+ 5a0f Meizu M6 MiniPlayer
+ 5b01 Memory Stick Reader/Writer
+ 5b02 Memory Stick Reader/Writer
+ 5b03 Memory Stick Reader/Writer
+ 5b04 Memory Stick Reader/Writer
+ 5b05 Memory Stick Reader/Writer
+ 5b11 SEW-2001u Card
+ 5f00 NEXiO Sync
+ 5f01 NEXiO Sync
+ 5f02 NEXiO Sync
+ 5f03 NEXiO Sync
+ 5f04 NEXiO Sync
+ 5f05 STORY Station 1TB
+ 6032 G2 Portable hard drive
+ 6033 G2 Portable device
+ 6034 G2 Portable hard drive
+ 60b3 M2 Portable Hard Drive
+ 60c4 M2 Portable Hard Drive USB 3.0
+ 6124 D3 Station External Hard Drive
+ 6125 D3 Station External Hard Drive
+ 61b5 M3 Portable Hard Drive 2TB
+ 61b6 M3 Portable Hard Drive 1TB
+ 61f3 Portable SSD T3 (MU-PT250B, MU-PT500B)
+ 6601 Mobile Phone
+ 6602 Galaxy
+ 6603 Galaxy
+ 6611 MITs Sync
+ 6613 MITs Sync
+ 6615 MITs Sync
+ 6617 MITs Sync
+ 6619 MITs Sync
+ 661b MITs Sync
+ 661e Handheld
+ 6620 Handheld
+ 6622 Handheld
+ 6624 Handheld
+ 662e MITs Sync
+ 6630 MITs Sync
+ 6632 MITs Sync
+ 663e D900e/B2100 Phone
+ 663f SGH-E720/SGH-E840
+ 6640 Usb Modem Enumerator
+ 6651 i8510 Innov8
+ 6702 X830
+ 6708 U600 Phone
+ 6709 U600
+ 6734 Juke
+ 6759 D900e/B2100 Media Player
+ 675a D900e/B2100 Mass Storage
+ 675b D900e Camera
+ 6772 Standalone LTE device (Trial)
+ 6795 S5230
+ 6802 Standalone HSPA device
+ 6806 Composite LTE device (Trial)
+ 6807 Composite HSPA device
+ 681c Galaxy Portal/Spica/S
+ 681d Galaxy Portal/Spica Android Phone
+ 6843 E2530 Phone (Samsung Kies mode)
+ 684e Wave (GT-S8500)
+ 685b GT-I9100 Phone [Galaxy S II] (mass storage mode)
+ 685c GT-I9250 Phone [Galaxy Nexus] (Mass storage mode)
+ 685d GT-I9100 Phone [Galaxy S II] (Download mode)
+ 685e GT-I9100 / GT-C3350 Phones (USB Debugging mode)
+ 6860 Galaxy (MTP)
+ 6863 GT-I9500 [Galaxy S4] / GT-I9250 [Galaxy Nexus] (network tethering)
+ 6864 GT-I9070 (network tethering, USB debugging enabled)
+ 6865 Galaxy (PTP mode)
+ 6866 Galaxy (debugging mode)
+ 6868 Escape Composite driver for Android Phones: Modem+Diagnostic+ADB
+ 6875 GT-B3710 Standalone LTE device (Commercial)
+ 6876 GT-B3710 LTE Modem
+ 6877 Galaxy S
+ 687a GT-E2370 mobile phone
+ 6888 GT-B3730 Composite LTE device (Commercial)
+ 6889 GT-B3730 Composite LTE device (Commercial)
+ 689a LTE Storage Driver [CMC2xx]
+ 689e GT-S5670 [Galaxy Fit]
+ 68aa Reality
+ 7011 SEW-2003U Card
+ 7021 Bluetooth Device
+ 7061 eHome Infrared Receiver
+ 7080 Anycall SCH-W580
+ 7081 Human Interface Device
+ 8001 Handheld
+ e020 SERI E02 SCOM 6200 UMTS Phone
+ e021 SERI E02 SCOM 6200 Virtual UARTs
+ e022 SERI E02 SCOM 6200 Flash Load Disk
+ f000 Intensity 3 (Mass Storage Mode)
+ ff30 SG_iMON
+04e9 PC-Tel, Inc.
+04ea Brooktree Corp.
+04eb Northstar Systems, Inc.
+ e004 eHome Infrared Transceiver
+04ec Tokyo Electron Device, Ltd
+04ed Annabooks
+04ef Pacific Electronic International, Inc.
+04f0 Daewoo Electronics Co., Ltd
+04f1 Victor Company of Japan, Ltd
+ 0001 GC-QX3 Digital Still Camera
+ 0004 GR-DVL815U Digital Video Camera
+ 0006 DV Camera Storage
+ 0008 GZ-MG30AA/MC500E Digital Video Camera
+ 0009 GR-DX25EK Digital Video Camera
+ 000a GR-D72 Digital Video Camera
+ 1001 GC-A50 Camera Device
+ 3008 MP-PRX1 Ethernet
+ 3009 MP-XP7250 WLAN Adapter
+04f2 Chicony Electronics Co., Ltd
+ 0001 KU-8933 Keyboard
+ 0002 NT68P81 Keyboard
+ 0110 KU-2971 Keyboard
+ 0111 KU-9908 Keyboard
+ 0112 KU-8933 Keyboard with PS/2 Mouse port
+ 0116 KU-2971/KU-0325 Keyboard
+ 0200 KBR-0108
+ 0201 Gaming Keyboard KPD0250
+ 0220 Wireless HID Receiver
+ 0402 Genius LuxeMate i200 Keyboard
+ 0403 KU-0420 keyboard
+ 0418 KU-0418 Tactical Pad
+ 0618 RG-0618U Wireless HID Receiver & KG-0609 Wireless Keyboard with Touchpad
+ 0718 wired mouse
+ 0760 Acer KU-0760 Keyboard
+ 0841 HP Multimedia Keyboard
+ 0860 2.4G Multimedia Wireless Kit
+ 1061 HP KG-1061 Wireless Keyboard+Mouse
+ 1121 Periboard 717 Mini Wireless Keyboard
+ a001 E-Video DC-100 Camera
+ a120 ORITE CCD Webcam(PC370R)
+ a121 ORITE CCD Webcam(PC370R)
+ a122 ORITE CCD Webcam(PC370R)
+ a123 ORITE CCD Webcam(PC370R)
+ a124 ORITE CCD Webcam(PC370R)
+ a128 PC Camera (SN9C202 + OV7663 + EEPROM)
+ a133 Gateway Webcam
+ a136 LabTec Webcam 5500
+ a147 Medion Webcam
+ a204 DSC WIA Device (1300)
+ a208 DSC WIA Device (2320)
+ a209 Labtec DC-2320
+ a20a DSC WIA Device (3310)
+ a20c DSC WIA Device (3320)
+ a210 Audio Device
+ b008 USB 2.0 Camera
+ b009 Integrated Camera
+ b010 Integrated Camera
+ b012 1.3 MPixel UVC Webcam
+ b013 USB 2.0 Camera
+ b015 VGA 24fps UVC Webcam
+ b016 VGA 30fps UVC Webcam
+ b018 2M UVC Webcam
+ b021 ViewSonic 1.3M, USB2.0 Webcam
+ b022 Gateway USB 2.0 Webcam
+ b023 Gateway USB 2.0 Webcam
+ b024 USB 2.0 Webcam
+ b025 Camera
+ b027 Gateway USB 2.0 Webcam
+ b028 VGA UVC Webcam
+ b029 1.3M UVC Webcam
+ b036 Asus Integrated 0.3M UVC Webcam
+ b044 Acer CrystalEye Webcam
+ b057 integrated USB webcam
+ b059 CKF7037 HP webcam
+ b064 CNA7137 Integrated Webcam
+ b070 Camera
+ b071 2.0M UVC Webcam / CNF7129
+ b083 CKF7063 Webcam (HP)
+ b091 Webcam
+ b104 CNF7069 Webcam
+ b107 CNF7070 Webcam
+ b14c CNF8050 Webcam
+ b15c Sony Vaio Integrated Camera
+ b175 4-Port Hub
+ b1aa Webcam-101
+ b1b4 Lenovo Integrated Camera
+ b1b9 Asus Integrated Webcam
+ b1cf Lenovo Integrated Camera
+ b1d6 CNF9055 Toshiba Webcam
+ b1d8 1.3M Webcam
+ b1e4 Toshiba Integrated Webcam
+ b213 Fujitsu Integrated Camera
+ b217 Lenovo Integrated Camera (0.3MP)
+ b221 integrated camera
+ b230 Integrated HP HD Webcam
+ b257 Lenovo Integrated Camera
+ b26b Sony Visual Communication Camera
+ b272 Lenovo EasyCamera
+ b2b0 Camera
+ b2b9 Lenovo Integrated Camera UVC
+ b2da thinkpad t430s camera
+ b2ea Integrated Camera [ThinkPad]
+ b330 Asus 720p CMOS webcam
+ b354 UVC 1.00 device HD UVC WebCam
+ b394 Integrated Camera
+ b3f6 HD WebCam (Acer)
+ b40e HP Truevision HD camera
+ b444 Lenovo Integrated Webcam
+04f3 Elan Microelectronics Corp.
+ 000a Touchscreen
+ 0103 ActiveJet K-2024 Multimedia Keyboard
+ 01a4 Wireless Keyboard
+ 0201 Touchscreen
+ 0210 Optical Mouse
+ 0212 Laser Mouse
+ 0214 Lynx M9 Optical Mouse
+ 0230 3D Optical Mouse
+ 0232 Mouse
+ 0234 Optical Mouse
+ 02f4 2.4G Cordless Mouse
+ 0381 Touchscreen
+ 04a0 Dream Cheeky Stress/Panic Button
+04f4 Harting Elektronik, Inc.
+04f5 Fujitsu-ICL Systems, Inc.
+04f6 Norand Corp.
+04f7 Newnex Technology Corp.
+04f8 FuturePlus Systems
+04f9 Brother Industries, Ltd
+ 0002 HL-1050 Laser Printer
+ 0005 Printer
+ 0006 HL-1240 Laser Printer
+ 0007 HL-1250 Laser Printer
+ 0008 HL-1270 Laser Printer
+ 0009 Printer
+ 000a P2500 series
+ 000b Printer
+ 000c Printer
+ 000d HL-1440 Laser Printer
+ 000e HL-1450 series
+ 000f HL-1470N series
+ 0010 Printer
+ 0011 Printer
+ 0012 Printer
+ 0013 Printer
+ 0014 Printer
+ 0015 Printer
+ 0016 Printer
+ 0017 Printer
+ 0018 Printer
+ 001a HL-1430 Laser Printer
+ 001c Printer
+ 001e Printer
+ 0020 HL-5130 series
+ 0021 HL-5140 series
+ 0022 HL-5150D series
+ 0023 HL-5170DN series
+ 0024 Printer
+ 0025 Printer
+ 0027 HL-2030 Laser Printer
+ 0028 Printer
+ 0029 Printer
+ 002a HL-52x0 series
+ 002b HL-5250DN Printer
+ 002c Printer
+ 002d Printer
+ 0039 HL-5340 series
+ 0042 HL-2270DW Laser Printer
+ 0100 MFC8600/9650 series
+ 0101 MFC9600/9870 series
+ 0102 MFC9750/1200 series
+ 0104 MFC-8300J
+ 0105 MFC-9600J
+ 0106 MFC-7300C
+ 0107 MFC-7400C
+ 0108 MFC-9200C
+ 0109 MFC-830
+ 010a MFC-840
+ 010b MFC-860
+ 010c MFC-7400J
+ 010d MFC-9200J
+ 010e MFC-3100C Scanner
+ 010f MFC-5100C
+ 0110 MFC-4800 Scanner
+ 0111 MFC-6800
+ 0112 DCP1000 Port(FaxModem)
+ 0113 MFC-8500
+ 0114 MFC9700 Port(FaxModem)
+ 0115 MFC-9800 Scanner
+ 0116 DCP1400 Scanner
+ 0119 MFC-9660
+ 011a MFC-9860
+ 011b MFC-9880
+ 011c MFC-9760
+ 011d MFC-9070
+ 011e MFC-9180
+ 011f MFC-9160
+ 0120 MFC580 Port(FaxModem)
+ 0121 MFC-590
+ 0122 MFC-5100J
+ 0124 MFC-4800J
+ 0125 MFC-6800J
+ 0127 MFC-9800J
+ 0128 MFC-8500J
+ 0129 Imagistics 2500 (MFC-8640D clone)
+ 012b MFC-9030
+ 012e FAX4100e IntelliFax 4100e
+ 012f FAX-4750e
+ 0130 FAX-5750e
+ 0132 MFC-5200C RemovableDisk
+ 0135 MFC-100 Scanner
+ 0136 MFC-150CL Scanner
+ 013c MFC-890 Port
+ 013d MFC-5200J
+ 013e MFC-4420C RemovableDisk
+ 013f MFC-4820C RemovableDisk
+ 0140 DCP-8020
+ 0141 DCP-8025D
+ 0142 MFC-8420
+ 0143 MFC-8820D
+ 0144 DCP-4020C RemovableDisk
+ 0146 MFC-3220C
+ 0147 FAX-1820C Printer
+ 0148 MFC-3320CN
+ 0149 FAX-1920CN Printer
+ 014a MFC-3420C
+ 014b MFC-3820CN
+ 014c DCP-3020C
+ 014d FAX-1815C Printer
+ 014e MFC-8820J
+ 014f DCP-8025J
+ 0150 MFC-8220 Port(FaxModem)
+ 0151 MFC-8210J
+ 0153 DCP-1000J
+ 0157 MFC-3420J Printer
+ 0158 MFC-3820JN Port(FaxModem)
+ 015d MFC Composite Device
+ 015e DCP-8045D
+ 015f MFC-8440
+ 0160 MFC-8840D
+ 0161 MFC-210C
+ 0162 MFC-420CN Remote Setup Port
+ 0163 MFC-410CN RemovableDisk
+ 0165 MFC-620CN
+ 0166 MFC-610CLN RemovableDisk
+ 0168 MFC-620CLN
+ 0169 DCP-110C RemovableDisk
+ 016b DCP-310CN RemovableDisk
+ 016c FAX-2440C Printer
+ 016d MFC-5440CN
+ 016e MFC-5840CN Remote Setup Port
+ 0170 FAX-1840C Printer
+ 0171 FAX-1835C Printer
+ 0172 FAX-1940CN Printer
+ 0173 MFC-3240C Remote Setup Port
+ 0174 MFC-3340CN RemovableDisk
+ 017b Imagistics sx2100
+ 0180 MFC-7420
+ 0181 MFC-7820N Port(FaxModem)
+ 0182 DCP-7010
+ 0183 DCP-7020
+ 0184 DCP-7025 Printer
+ 0185 MFC-7220 Printer
+ 0186 Composite Device
+ 0187 FAX-2820 Printer
+ 0188 FAX-2920 Printer
+ 018a MFC-9420CN
+ 018c DCP-115C
+ 018d DCP-116C
+ 018e DCP-117C
+ 018f DCP-118C
+ 0190 DCP-120C
+ 0191 DCP-315CN
+ 0192 DCP-340CW
+ 0193 MFC-215C
+ 0194 MFC-425CN
+ 0195 MFC-820CW Remote Setup Port
+ 0196 MFC-820CN Remote Setup Port
+ 0197 MFC-640CW
+ 019a MFC-840CLN Remote Setup Port
+ 01a2 MFC-8640D
+ 01a3 Composite Device
+ 01a4 DCP-8065DN Printer
+ 01a5 MFC-8460N Port(FaxModem)
+ 01a6 MFC-8860DN Port(FaxModem)
+ 01a7 MFC-8870DW Printer
+ 01a8 DCP-130C
+ 01a9 DCP-330C
+ 01aa DCP-540CN
+ 01ab MFC-240C
+ 01ae DCP-750CW RemovableDisk
+ 01af MFC-440CN
+ 01b0 MFC-660CN
+ 01b1 MFC-665CW
+ 01b2 MFC-845CW
+ 01b4 MFC-460CN
+ 01b5 MFC-630CD
+ 01b6 MFC-850CDN
+ 01b7 MFC-5460CN
+ 01b8 MFC-5860CN
+ 01ba MFC-3360C
+ 01bd MFC-8660DN
+ 01be DCP-750CN RemovableDisk
+ 01bf MFC-860CDN
+ 01c0 DCP-128C
+ 01c1 DCP-129C
+ 01c2 DCP-131C
+ 01c3 DCP-329C
+ 01c4 DCP-331C
+ 01c5 MFC-239C
+ 01c9 DCP-9040CN
+ 01ca MFC-9440CN
+ 01cb DCP-9045CDN
+ 01cc MFC-9840CDW
+ 01ce DCP-135C
+ 01cf DCP-150C
+ 01d0 DCP-350C
+ 01d1 DCP-560CN
+ 01d2 DCP-770CW
+ 01d3 DCP-770CN
+ 01d4 MFC-230C
+ 01d5 MFC-235C
+ 01d6 MFC-260C
+ 01d7 MFC-465CN
+ 01d8 MFC-680CN
+ 01d9 MFC-685CW
+ 01da MFC-885CW
+ 01db MFC-480CN
+ 01dc MFC-650CD
+ 01dd MFC-870CDN
+ 01de MFC-880CDN
+ 01df DCP-155C
+ 01e0 MFC-265C
+ 01e1 DCP-153C
+ 01e2 DCP-157C
+ 01e3 DCP-353C
+ 01e4 DCP-357C
+ 01e7 MFC-7340
+ 01e9 DCP-7040
+ 01ea DCP-7030
+ 01eb MFC-7320
+ 01ec MFC-9640CW
+ 01f4 MFC-5890CN
+ 020a MFC-8670DN
+ 020c DCP-9042CDN
+ 020d MFC-9450CDN
+ 0216 MFC-8880DN
+ 0217 MFC-8480DN
+ 0219 MFC-8380DN
+ 021a MFC-8370DN
+ 021b DCP-8070D
+ 021c MFC-9320CW
+ 021d MFC-9120CN
+ 021e DCP-9010CN
+ 0220 MFC-9010CN
+ 0222 DCP-195C
+ 0223 DCP-365CN
+ 0224 DCP-375CW
+ 0225 DCP-395CN
+ 0227 DCP-595CN
+ 0228 MFC-255CW
+ 0229 MFC-295CN
+ 022a MFC-495CW
+ 022b MFC-495CN
+ 022c MFC-795CW
+ 022d MFC-675CD
+ 022e MFC-695CDN
+ 022f MFC-735CD
+ 0230 MFC-935CDN
+ 0234 DCP-373CW
+ 0235 DCP-377CW
+ 0236 DCP-390CN
+ 0239 MFC-253CW
+ 023a MFC-257CW
+ 023e DCP-197C
+ 023f MFC-8680DN
+ 0240 MFC-J950DN
+ 0248 DCP-7055 scanner/printer
+ 0253 DCP-J125
+ 0254 DCP-J315W
+ 0255 DCP-J515W
+ 0256 DCP-J515N
+ 0257 DCP-J715W
+ 0258 DCP-J715N
+ 0259 MFC-J220
+ 025a MFC-J410
+ 025b MFC-J265W
+ 025c MFC-J415W
+ 025d MFC-J615W
+ 025e MFC-J615N
+ 025f MFC-J700D
+ 0260 MFC-J800D
+ 0261 MFC-J850DN
+ 026b MFC-J630W
+ 026d MFC-J805D
+ 026e MFC-J855DN
+ 026f MFC-J270W
+ 0273 DCP-7057 scanner/printer
+ 0276 MFC-5895CW
+ 0278 MFC-J410W
+ 0279 DCP-J525W
+ 027a DCP-J525N
+ 027b DCP-J725DW
+ 027c DCP-J725N
+ 027d DCP-J925DW
+ 027e MFC-J955DN
+ 027f MFC-J280W
+ 0280 MFC-J435W
+ 0281 MFC-J430W
+ 0282 MFC-J625DW
+ 0283 MFC-J825DW
+ 0284 MFC-J825N
+ 0285 MFC-J705D
+ 0287 MFC-J860DN
+ 0288 MFC-J5910DW
+ 0289 MFC-J5910CDW
+ 028a DCP-J925N
+ 028d MFC-J835DW
+ 028f MFC-J425W
+ 0290 MFC-J432W
+ 0291 DCP-8110DN
+ 0292 DCP-8150DN
+ 0293 DCP-8155DN
+ 0294 DCP-8250DN
+ 0295 MFC-8510DN
+ 0296 MFC-8520DN
+ 0298 MFC-8910DW
+ 0299 MFC-8950DW
+ 029a MFC-8690DW
+ 029c MFC-8515DN
+ 029e MFC-9125CN
+ 029f MFC-9325CW
+ 02a0 DCP-J140W
+ 02a5 MFC-7240
+ 02a6 FAX-2940
+ 02a7 FAX-2950
+ 02a8 MFC-7290
+ 02ab FAX-2990
+ 02ac DCP-8110D
+ 02ad MFC-9130CW
+ 02ae MFC-9140CDN
+ 02af MFC-9330CDW
+ 02b0 MFC-9340CDW
+ 02b1 DCP-9020CDN
+ 02b2 MFC-J810DN
+ 02b3 MFC-J4510DW
+ 02b4 MFC-J4710DW
+ 02b5 DCP-8112DN
+ 02b6 DCP-8152DN
+ 02b7 DCP-8157DN
+ 02b8 MFC-8512DN
+ 02ba MFC-8912DW
+ 02bb MFC-8952DW
+ 02bc DCP-J540N
+ 02bd DCP-J740N
+ 02be MFC-J710D
+ 02bf MFC-J840N
+ 02c0 DCP-J940N
+ 02c1 MFC-J960DN
+ 02c2 DCP-J4110DW
+ 02c3 MFC-J4310DW
+ 02c4 MFC-J4410DW
+ 02c5 MFC-J4610DW
+ 02c6 DCP-J4210N
+ 02c7 MFC-J4510N
+ 02c8 MFC-J4910CDW
+ 02c9 MFC-J4810DN
+ 02ca MFC-8712DW
+ 02cb MFC-8710DW
+ 02cc MFC-J2310
+ 02cd MFC-J2510
+ 02ce DCP-7055W
+ 02cf DCP-7057W
+ 02d0 DCP-1510
+ 02d1 MFC-1810
+ 02d3 DCP-9020CDW
+ 02d4 MFC-8810DW
+ 02dd DCP-J4215N
+ 02de DCP-J132W
+ 02df DCP-J152W
+ 02e0 DCP-J152N
+ 02e1 DCP-J172W
+ 02e2 DCP-J552DW
+ 02e3 DCP-J552N
+ 02e4 DCP-J752DW
+ 02e5 DCP-J752N
+ 02e6 DCP-J952N
+ 02e7 MFC-J245
+ 02e8 MFC-J470DW
+ 02e9 MFC-J475DW
+ 02ea MFC-J285DW
+ 02eb MFC-J650DW
+ 02ec MFC-J870DW
+ 02ed MFC-J870N
+ 02ee MFC-J720D
+ 02ef MFC-J820DN
+ 02f0 MFC-J980DN
+ 02f1 MFC-J890DN
+ 02f2 MFC-J6520DW
+ 02f3 MFC-J6570CDW
+ 02f4 MFC-J6720DW
+ 02f5 MFC-J6920DW
+ 02f6 MFC-J6970CDW
+ 02f7 MFC-J6975CDW
+ 02f8 MFC-J6770CDW
+ 02f9 DCP-J132N
+ 02fa MFC-J450DW
+ 02fb MFC-J875DW
+ 02fc DCP-J100
+ 02fd DCP-J105
+ 02fe MFC-J200
+ 02ff MFC-J3520
+ 0300 MFC-J3720
+ 030f DCP-L8400CDN
+ 0310 DCP-L8450CDW
+ 0311 MFC-L8600CDW
+ 0312 MFC-L8650CDW
+ 0313 MFC-L8850CDW
+ 0314 MFC-L9550CDW
+ 0318 MFC-7365DN
+ 0320 MFC-L2740DW
+ 0321 DCP-L2500D
+ 0322 DCP-L2520DW
+ 0324 DCP-L2520D
+ 0326 DCP-L2540DN
+ 0328 DCP-L2540DW
+ 0329 DCP-L2560DW
+ 0330 HL-L2380DW
+ 0331 MFC-L2700DW
+ 0335 FAX-L2700DN
+ 0337 MFC-L2720DW
+ 0338 MFC-L2720DN
+ 0339 DCP-J4120DW
+ 033a MFC-J4320DW
+ 033c MFC-J2320
+ 033d MFC-J4420DW
+ 0340 MFC-J4620DW
+ 0341 MFC-J2720
+ 0342 MFC-J4625DW
+ 0343 MFC-J5320DW
+ 0346 MFC-J5620DW
+ 0347 MFC-J5720DW
+ 0349 DCP-J4220N
+ 034b MFC-J4720N
+ 034e MFC-J5720CDW
+ 034f MFC-J5820DN
+ 0350 MFC-J5620CDW
+ 0351 DCP-J137N
+ 0353 DCP-J557N
+ 0354 DCP-J757N
+ 0355 DCP-J957N
+ 0356 MFC-J877N
+ 0357 MFC-J727D
+ 0358 MFC-J987DN
+ 0359 MFC-J827DN
+ 035a MFC-J897DN
+ 035b DCP-1610W
+ 035c DCP-1610NW
+ 035d MFC-1910W
+ 035e MFC-1910NW
+ 0360 DCP-1618W
+ 0361 MFC-1919NW
+ 0364 MFC-J5625DW
+ 0365 MFC-J4520DW
+ 0366 MFC-J5520DW
+ 0367 DCP-7080D
+ 0368 DCP-7080
+ 0369 DCP-7180DN
+ 036a DCP-7189DW
+ 036b MFC-7380
+ 036c MFC-7480D
+ 036d MFC-7880DN
+ 036e MFC-7889DW
+ 036f DCP-9022CDW
+ 0370 MFC-9142CDN
+ 0371 MFC-9332CDW
+ 0372 MFC-9342CDW
+ 0373 MFC-L2700D
+ 0376 DCP-1600
+ 0377 MFC-1900
+ 0378 DCP-1608
+ 0379 DCP-1619
+ 037a MFC-1906
+ 037b MFC-1908
+ 037c ADS-2000e
+ 037d ADS-2100e
+ 037e ADS-2500We
+ 037f ADS-2600We
+ 0380 DCP-J562DW
+ 0381 DCP-J562N
+ 0383 DCP-J962N
+ 0384 MFC-J480DW
+ 0385 MFC-J485DW
+ 0386 MFC-J460DW
+ 0388 MFC-J680DW
+ 0389 MFC-J880DW
+ 038a MFC-J885DW
+ 038b MFC-J880N
+ 038c MFC-J730DN
+ 038d MFC-J990DN
+ 038e MFC-J830DN
+ 038f MFC-J900DN
+ 0390 MFC-J5920DW
+ 0392 MFC-L2705DW
+ 0393 DCP-T300
+ 0394 DCP-T500W
+ 0395 DCP-T700W
+ 0396 MFC-T800W
+ 0397 DCP-J963N
+ 03b3 MFC-J6925DW
+ 03b4 MFC-J6573CDW
+ 03b5 MFC-J6973CDW
+ 03b6 MFC-J6990CDW
+ 03bb MFC-L2680W
+ 03bc MFC-L2700DN
+ 03bd DCP-J762N
+ 1000 Printer
+ 1002 Printer
+ 2002 PTUSB Printing
+ 2004 PT-2300/2310 p-Touch Laber Printer
+ 2015 QL-500 P-touch label printer
+ 2016 QL-550 P-touch label printer
+ 201a PT-18R P-touch label printer
+ 201b QL-650TD P-touch Label Printer
+ 2027 QL-560 P-touch Label Printer
+ 2028 QL-570 P-touch Label Printer
+ 202b PT-7600 P-touch Label Printer
+ 2061 PT-P700 P-touch Label Printer
+ 2064 PT-P700 P-touch Label Printer RemovableDisk
+ 2100 Card Reader Writer
+ 2102 Sewing machine
+ 60a0 ADS-2000
+ 60a1 ADS-2100
+ 60a4 ADS-2500W
+ 60a5 ADS-2600W
+ 60a6 ADS-1000W
+ 60a7 ADS-1100W
+ 60a8 ADS-1500W
+ 60a9 ADS-1600W
+04fa Dallas Semiconductor
+ 2490 DS1490F 2-in-1 Fob, 1-Wire adapter
+ 4201 DS4201 Audio DAC
+04fb Biostar Microtech International Corp.
+04fc Sunplus Technology Co., Ltd
+ 0003 CM1092 / Wintech CM-5098 Optical Mouse
+ 0005 USB OpticalWheel Mouse
+ 0013 ViewMate Desktop Mouse CC2201
+ 0015 ViewMate Desktop Mouse CC2201
+ 00d3 00052486 / Laser Mouse M1052 [hama]
+ 0171 SPCA1527A/SPCA1528 SD card camera (Mass Storage mode)
+ 0201 SPCP825 RS232C Adapter
+ 0232 Fingerprint
+ 0538 Wireless Optical Mouse 2.4G [Bright]
+ 0561 Flexcam 100
+ 05d8 Wireless keyboard/mouse
+ 05da SPEEDLINK SNAPPY Wireless Mouse Nano
+ 0c15 SPIF215A SATA bridge
+ 0c25 SATALink SPIF225A
+ 1528 SPCA1527A/SPCA1528 SD card camera (webcam mode)
+ 1533 Mass Storage
+ 2080 ASUS Webcam
+ 500c CA500C Digital Camera
+ 504a Aiptek Mini PenCam 1.3
+ 504b Aiptek Mega PockerCam 1.3/Maxell MaxPocket LE 1.3
+ 5330 Digitrex 2110
+ 5331 Vivitar Vivicam 10
+ 5360 Sunplus Generic Digital Camera
+ 5563 Digital Media Player MP3/WMA [The Sharper Image]
+ 5720 Card Reader Driver
+ 6333 Siri A9 UVC chipset
+ 7333 Finet Technology Palmpix DC-85
+ 757a Aiptek, MP315 MP3 Player
+ ffff PureDigital Ritz Disposable
+04fd Soliton Systems, K.K.
+ 0003 Smart Card Reader II
+04fe PFU, Ltd
+04ff E-CMOS Corp.
+0500 Siam United Hi-Tech
+ 0001 DART Keyboard Mouse
+ 0002 DART-2 Keyboard
+0501 Fujikura DDK, Ltd
+0502 Acer, Inc.
+ 0001 Handheld
+ 0736 Handheld
+ 15b1 PDA n311
+ 1631 c10 Series
+ 1632 c20 Series
+ 16e1 n10 Handheld Sync
+ 16e2 n20 Pocket PC Sync
+ 16e3 n30 Handheld Sync
+ 2008 Liquid Gallant Duo E350 (preloader)
+ 3202 Liquid
+ 3203 Liquid (Debug mode)
+ 3230 BeTouch E120
+ 3317 Liquid
+ 3325 Iconia tablet A500
+ 3341 Iconia tablet A500
+ 33c3 Liquid Gallant Duo E350
+ 33c4 Liquid Gallant Duo E350 (debug mode)
+ 33c7 Liquid Gallant Duo E350 (USB tethering)
+ 33c8 Liquid Gallant Duo E350 (debug mode, USB tethering)
+ d001 Divio NW801/DVC-V6+ Digital Camera
+0503 Hitachi America, Ltd
+0504 Hayes Microcomputer Products
+0506 3Com Corp.
+ 009d HomeConnect Camera
+ 00a0 3CREB96 Bluetooth Adapter
+ 00a1 Bluetooth Device
+ 00a2 Bluetooth Device
+ 00df 3Com Home Connect lite
+ 0100 HomeConnect ADSL Modem Driver
+ 03e8 3C19250 Ethernet [klsi]
+ 0a01 3CRSHEW696 Wireless Adapter
+ 0a11 3CRWE254G72 802.11g Adapter
+ 11f8 HomeConnect 3C460
+ 2922 HomeConnect Cable Modem External with
+ 3021 U.S.Robotics 56000 Voice FaxModem Pro
+ 4601 3C460B 10/100 Ethernet Adapter
+ f002 3CP4218 ADSL Modem (pre-init)
+ f003 3CP4218 ADSL Modem
+ f100 3CP4218 ADSL Modem (pre-init)
+0507 Hosiden Corp.
+ 0011 Konami ParaParaParadise Controller
+0508 Clarion Co., Ltd
+0509 Aztech Systems, Ltd
+ 0801 ADSL Modem
+ 0802 ADSL Modem (RFC1483)
+ 0806 DSL Modem
+ 080f Binatone ADSL500 Modem Network Interface
+ 0812 Pirelli ADSL Modem Network Interface
+050a Cinch Connectors
+050b Cable System International
+050c InnoMedia, Inc.
+050d Belkin Components
+ 0004 Direct Connect
+ 0012 F8T012 Bluetooth Adapter
+ 0013 F8T013 Bluetooth Adapter
+ 0017 B8T017 Bluetooth+EDR 2.1 / F4U017 USB 2.0 7-port Hub
+ 003a Universal Media Reader
+ 0050 F5D6050 802.11b Wireless Adapter v2000 [Atmel at76c503a]
+ 0081 F8T001v2 Bluetooth
+ 0083 Bluetooth Device
+ 0084 F8T003v2 Bluetooth
+ 0102 Flip KVM
+ 0103 F5U103 Serial Adapter [etek]
+ 0106 VideoBus II Adapter, Video
+ 0108 F1DE108B KVM
+ 0109 F5U109/F5U409 PDA Adapter
+ 0115 SCSI Adapter
+ 0119 F5U120-PC Dual PS/2 Ports / F5U118-UNV ADB Adapter
+ 0121 F5D5050 100Mbps Ethernet
+ 0122 Ethernet Adapter
+ 0131 Bluetooth Device with trace filter
+ 016a Bluetooth Mini Dongle
+ 0200 Nostromo SpeedPad n52te Gaming Keyboard
+ 0201 Peripheral Switch
+ 0208 USBView II Video Adapter [nt1004]
+ 0210 F5U228 Hi-Speed USB 2.0 DVD Creator
+ 0211 F5U211 USB 2.0 15-in-1 Media Reader & Writer
+ 0224 F5U224 USB 2.0 4-Port Hub
+ 0234 F5U234 USB 2.0 4-Port Hub
+ 0237 F5U237 USB 2.0 7-Port Hub
+ 0240 F5U240 USB 2.0 CF Card Reader
+ 0249 USB 2 Flash Media Device
+ 0257 F5U257 Serial
+ 0304 FSU304 USB 2.0 - 4 Ports Hub
+ 0307 USB 2.0 - 7 ports Hub [FSU307]
+ 0409 F5U409 Serial
+ 0416 Staples 12416 7 port desktop hub
+ 0551 F6C550-AVR UPS
+ 065a F8T065BF Mini Bluetooth 4.0 Adapter
+ 0706 2-N-1 7-Port Hub (Lower half)
+ 0802 Nostromo n40 Gamepad
+ 0803 Nostromo 1745 GamePad
+ 0805 Nostromo N50 GamePad
+ 0815 Nostromo n52 HID SpeedPad Mouse Wheel
+ 0826 ErgoFit Wireless Optical Mouse (HID)
+ 0980 HID UPS Battery
+ 1004 F9L1004 802.11n Surf N300 XR Wireless Adapter [Realtek RTL8192CU]
+ 1102 F7D1102 N150/Surf Micro Wireless Adapter v1000 [Realtek RTL8188CUS]
+ 1103 F9L1103 N750 DB 802.11abgn 2x3:3 [Ralink RT3573]
+ 1106 F9L1106v1 802.11a/b/g/n/ac Wireless Adapter [Broadcom BCM43526]
+ 1109 F9L1109v1 802.11a/b/g/n/ac Wireless Adapter [Realtek RTL8812AU]
+ 110a F9L1101v2 802.11abgn Wireless Adapter [Realtek RTL8192DU]
+ 11f2 ISY Wireless Micro Adapter IWL 2000 [RTL8188CUS]
+ 1202 F5U120-PC Parallel Printer Port
+ 1203 F5U120-PC Serial Port
+ 2103 F7D2102 802.11n N300 Micro Wireless Adapter v3000 [Realtek RTL8192CU]
+ 21f1 N300 WLAN N Adapter [ISY]
+ 21f2 RTL8192CU 802.11n WLAN Adapter [ISY IWL 4000]
+ 258a F5U258 Host to Host cable
+ 3101 F1DF102U/F1DG102U Flip Hub
+ 3201 F1DF102U/F1DG102U Flip KVM
+ 4050 ZD1211B
+ 5055 F5D5055 Gigabit Network Adapter [AX88xxx]
+ 6050 F6D6050 802.11abgn Wireless Adapter [Broadcom BCM4323]
+ 6051 F5D6051 802.11b Wireless Network Adapter [ZyDAS ZD1201]
+ 615a F7D4101 / F9L1101v1 802.11abgn Wireless Adapter [Broadcom BCM4323]
+ 7050 F5D7050 Wireless G Adapter v1000/v2000 [Intersil ISL3887]
+ 7051 F5D7051 802.11g Adapter v1000 [Broadcom 4320 USB]
+ 705a F5D7050 Wireless G Adapter v3000 [Ralink RT2571W]
+ 705b Wireless G Adapter
+ 705c F5D7050 Wireless G Adapter v4000 [Zydas ZD1211B]
+ 705e F5D7050 Wireless G Adapter v5000 [Realtek RTL8187B]
+ 706a 2-N-1 7-Port Hub (Upper half)
+ 8053 F5D8053 N Wireless USB Adapter v1000/v4000 [Ralink RT2870]
+ 805c F5D8053 N Wireless Adapter v3000 [Ralink RT2870]
+ 805e F5D8053 N Wireless USB Adapter v5000 [Realtek RTL8192U]
+ 815c F5D8053 N Wireless USB Adapter v3000 [Ralink RT2870]
+ 815f F5D8053 N Wireless USB Adapter v6000 [Realtek RTL8192SU]
+ 825a F5D8055 N+ Wireless Adapter v1000 [Ralink RT2870]
+ 825b F5D8055 N+ Wireless Adapter v2000 [Ralink RT3072]
+ 845a F7D2101 802.11n Surf & Share Wireless Adapter v1000 [Realtek RTL8192SU]
+ 905b F5D9050 Wireless G+ MIMO Network Adapter v3000 [Ralink RT2573]
+ 905c F5D9050 Wireless G+ MIMO Network Adapter v4000 [Ralink RT2573]
+ 935a F6D4050 N150 Enhanced Wireless Network Adapter v1000 [Ralink RT3070]
+ 935b F6D4050 N150 Enhanced Wireless Network Adapter v2000 [Ralink RT3070]
+ 945a F7D1101 v1 Basic Wireless Adapter [Realtek RTL8188SU]
+ 945b F7D1101 v2 Basic Wireless Adapter [Ralink RT3370]
+ d321 Dynex DX-NUSB 802.11bgn Wireless Adapter [Broadcom BCM43231]
+050e Neon Technology, Inc.
+050f KC Technology, Inc.
+ 0001 Hub
+ 0003 KC82C160S Hub
+ 0180 KC-180 IrDA Dongle
+ 0190 KC2190 USB Host-to-Host cable
+0510 Sejin Electron, Inc.
+ 0001 Keyboard
+ 1000 Keyboard with PS/2 Mouse Port
+ e001 Mouse
+0511 N'Able (DataBook) Technologies, Inc.
+ 002b AOC DVB
+0512 Hualon Microelectronics Corp.
+0513 digital-X, Inc.
+0514 FCI Electronics
+0515 ACTC
+0516 Longwell Electronics
+0517 Butterfly Communications
+0518 EzKEY Corp.
+ 0001 USB to PS2 Adaptor v1.09
+ 0002 EZ-9900C Keyboard
+0519 Star Micronics Co., Ltd
+ 0003 TSP100ECO/TSP100II
+ c002 Xlive Bluetooth XBM-100S MP3 Player
+051a WYSE Technology
+ a005 Smart Display Version 9973
+051b Silicon Graphics
+051c Shuttle, Inc.
+ 0005 VFD Module
+ c001 eHome Infrared Receiver
+ c002 eHome Infrared Receiver
+051d American Power Conversion
+ 0001 UPS
+ 0002 Uninterruptible Power Supply
+ 0003 UPS
+051e Scientific Atlanta, Inc.
+051f IO Systems (Elite Electronics), Inc.
+0520 Taiwan Semiconductor Manufacturing Co.
+0521 Airborn Connectors
+0522 Advanced Connectek, Inc.
+0523 ATEN GmbH
+0524 Sola Electronics
+0525 Netchip Technology, Inc.
+ 100d RFMD Bluetooth Device
+ 1080 NET1080 USB-USB Bridge
+ 1200 SSDC Adapter II
+ 1265 File-backed Storage Gadget
+ 3424 Lumidigm Venus fingerprint sensor
+ a0f0 Cambridge Electronic Devices Power1401 mk 2
+ a140 USB Clik! 40
+ a141 (OME) PocketZip 40 MP3 Player Driver
+ a220 GVC Bluetooth Wireless Adapter
+ a4a0 Linux-USB "Gadget Zero"
+ a4a1 Linux-USB Ethernet Gadget
+ a4a2 Linux-USB Ethernet/RNDIS Gadget
+ a4a3 Linux-USB user-mode isochronous source/sink
+ a4a4 Linux-USB user-mode bulk source/sink
+ a4a5 Pocketbook Pro 903
+ a4a6 Linux-USB Serial Gadget
+ a4a7 Linux-USB Serial Gadget (CDC ACM mode)
+ a4a8 Linux-USB Printer Gadget
+ a4a9 Linux-USB OBEX Gadget
+ a4aa Linux-USB CDC Composite Gadge (Ethernet and ACM)
+0526 Temic MHS S.A.
+0527 ALTRA
+0528 ATI Technologies, Inc.
+ 7561 TV Wonder
+ 7562 TV Wonder, Edition (FN5)
+ 7563 TV Wonder, Edition (FI)
+ 7564 TV Wonder, Edition (FQ)
+ 7565 TV Wonder, Edition (NTSC+)
+ 7566 TV Wonder, Edition (FN5)
+ 7567 TV Wonder, Edition (FI)
+ 7568 TV Wonder, Edition (FQ)
+ 7569 Live! Pro (A)
+ 756a Live! Pro Audio (O)
+0529 Aladdin Knowledge Systems
+ 0001 HASP copy protection dongle
+ 030b eToken R1 v3.1.3.x
+ 0313 eToken R1 v3.2.3.x
+ 031b eToken R1 v3.3.3.x
+ 0323 eToken R1 v3.4.3.x
+ 0412 eToken R2 v2.2.4.x
+ 041a eToken R2 v2.2.4.x
+ 0422 eToken R2 v2.4.4.x
+ 042a eToken R2 v2.5.4.x
+ 050c eToken Pro v4.1.5.x
+ 0514 eToken Pro v4.2.5.4
+ 0600 eToken Pro 64k (4.2)
+ 0620 Token JC
+052a Crescent Heart Software
+052b Tekom Technologies, Inc.
+ 0102 Ca508A HP1020 Camera v.1.3.1.6
+ 0801 Yakumo MegaImage 37
+ 1512 Yakumo MegaImage IV
+ 1513 Aosta CX100 Webcam
+ 1514 Aosta CX100 Webcam Storage
+ 1905 Yakumo MegaImage 47
+ 1911 Yakumo MegaImage 47 SL
+ 2202 WDM Still Image Capture
+ 2203 Sound Vision Stream Driver
+ 3a06 DigiLife DDV-5120A
+ d001 P35U Camera Capture
+052c Canon Information Systems, Inc.
+052d Avid Electronics Corp.
+052e Standard Microsystems Corp.
+052f Unicore Software, Inc.
+0530 American Microsystems, Inc.
+0531 Wacom Technology Corp.
+0532 Systech Corp.
+0533 Alcatel Mobile Phones
+0534 Motorola, Inc.
+0535 LIH TZU Electric Co., Ltd
+0536 Hand Held Products (Welch Allyn, Inc.)
+ 01a0 PDT
+0537 Inventec Corp.
+0538 Caldera International, Inc. (SCO)
+0539 Shyh Shiun Terminals Co., Ltd
+053a PrehKeyTec GmbH
+ 0b00 Hub
+ 0b01 Preh MCI 3100
+053b Global Village Communication
+053c Institut of Microelectronic & Mechatronic Systems
+053d Silicon Architect
+053e Mobility Electronics
+053f Synopsys, Inc.
+0540 UniAccess AB
+ 0101 Panache Surf ISDN TA
+0541 Sirf Technology, Inc.
+0543 ViewSonic Corp.
+ 00fe G773 Monitor Hub
+ 00ff P815 Monitor Hub
+ 0bf2 airpanel V150 Wireless Smart Display
+ 0bf3 airpanel V110 Wireless Smart Display
+ 0ed9 Color Pocket PC V35
+ 0f01 airsync Wi-Fi Wireless Adapter
+ 1527 Color Pocket PC V36
+ 1529 Color Pocket PC V37
+ 152b Color Pocket PC V38
+ 152e Pocket PC
+ 1921 Communicator Pocket PC
+ 1922 Smartphone
+ 1923 Pocket PC V30
+ 1a11 Wireless 802.11g Adapter
+ 1e60 TA310 - ATSC/NTSC/PAL Driver(PCM4)
+ 4153 ViewSonic G773 Control (?)
+0544 Cristie Electronics, Ltd
+0545 Xirlink, Inc.
+ 7333 Trution Web Camera
+ 8002 IBM NetCamera
+ 8009 Veo PC Camera
+ 800c Veo Stingray
+ 800d Veo PC Camera
+ 8080 IBM C-It Webcam
+ 808a Veo PC Camera
+ 808b Veo Stingray
+ 808d Veo PC Camera
+ 810a Veo Advanced Connect Webcam
+ 810b Veo PC Camera
+ 810c Veo PC Camera
+ 8135 Veo Mobile/Advanced Web Camera
+ 813a Veo PC Camera
+ 813b Veo PC Camera
+ 813c Veo Mobile/Advanced Web Camera
+ 8333 Veo Stingray/Connect Web Camera
+ 888c eVision 123 digital camera
+ 888d eVision 123 digital camera
+0546 Polaroid Corp.
+ 0daf PDC 2300Z
+ 1bed PDC 1320 Camera
+ 3097 PDC 310
+ 3155 PDC 3070 Camera
+ 3187 Digital Camera
+ 3191 Ion 80 Camera
+ 3273 PDC 2030 Camera
+ 3304 a500 Digital Camera
+ dccf Sound Vision Stream Driver
+0547 Anchor Chips, Inc.
+ 0001 ICSI Bluetooth Device
+ 1002 Python2 WDM Encoder
+ 1006 Hantek DSO-2100 UF
+ 2131 AN2131 EZUSB Microcontroller
+ 2235 AN2235 EZUSB-FX Microcontroller
+ 2710 EZ-Link Loader (EZLNKLDR.SYS)
+ 2720 AN2720 USB-USB Bridge
+ 2727 Xircom PGUNET USB-USB Bridge
+ 2750 EZ-Link (EZLNKUSB.SYS)
+ 2810 Cypress ATAPI Bridge
+ 4d90 AmScope MD1900 camera
+ 6510 Touptek UCMOS05100KPA
+ 7000 PowerSpec MCE460 Front Panel LED Display
+ 7777 Bluetooth Device
+ 9999 AN2131 uninitialized (?)
+0548 Tyan Computer Corp.
+ 1005 EZ Cart II GameBoy Flash Programmer
+0549 Pixera Corp.
+054a Fujitsu Microelectronics, Inc.
+054b New Media Corp.
+054c Sony Corp.
+ 0001 HUB
+ 0002 Standard HUB
+ 0010 DSC-S30/S70/S75/F505V/F505/FD92/W1 Cybershot/Mavica Digital Camera
+ 0014 Nogatech USBVision (SY)
+ 0022 Storage Adapter V2 (TPP)
+ 0023 CD Writer
+ 0024 Mavica CD-1000 Camera
+ 0025 NW-MS7 Walkman MemoryStick Reader
+ 002b Portable USB Harddrive V2
+ 002c USB Floppy Disk Drive
+ 002d MSAC-US1 MemoryStick Reader
+ 002e HandyCam MemoryStick Reader
+ 0030 Storage Adapter V2 (TPP)
+ 0032 MemoryStick MSC-U01 Reader
+ 0035 Network Walkman (E)
+ 0036 Net MD
+ 0037 MG Memory Stick Reader/Writer
+ 0038 Clie PEG-S300/D PalmOS PDA
+ 0039 Network Walkman (MS)
+ 003c VAIO-MX LCD Control
+ 0045 Digital Imaging Video
+ 0046 Network Walkman
+ 004a Memory Stick Hi-Fi System
+ 004b Memory Stick Reader/Writer
+ 004e DSC-xxx (ptp)
+ 0056 MG Memory Stick Reader/Writer
+ 0058 Clie PEG-N7x0C PalmOS PDA Mass Storage
+ 0066 Clie PEG-N7x0C/PEG-T425 PalmOS PDA Serial
+ 0067 CMR-PC3 Webcam
+ 0069 Memorystick MSC-U03 Reader
+ 006c FeliCa S310 [PaSoRi]
+ 006d Clie PEG-T425 PDA Mass Storage
+ 006f Network Walkman (EV)
+ 0073 Storage CRX1750U
+ 0075 Net MD
+ 0076 Storage Adapter ACR-U20
+ 007c Net MD
+ 007f IC Recorder (MS)
+ 0080 Net MD
+ 0081 Net MD
+ 0084 Net MD
+ 0085 Net MD
+ 0086 Net MD
+ 008b Micro Vault 64M Mass Storage
+ 0095 Clie s360
+ 0099 Clie NR70 PDA Mass Storage
+ 009a Clie NR70 PDA Serial
+ 00ab Visual Communication Camera (PCGA-UVC10)
+ 00af DPP-EX Series Digital Photo Printer
+ 00bf IC Recorder (S)
+ 00c0 Handycam DCR-30
+ 00c6 Net MD
+ 00c7 Net MD
+ 00c8 MZ-N710 Minidisc Walkman
+ 00c9 Net MD
+ 00ca MZ-DN430 Minidisc Walkman
+ 00cb MSAC-US20 Memory Stick Reader
+ 00da Clie nx60
+ 00e8 Network Walkman (MS)
+ 00e9 Handheld
+ 00eb Net MD
+ 0101 Net MD
+ 0103 IC Recorder (ST)
+ 0105 Micro Vault Hub
+ 0107 VCC-U01 Visual Communication Camera
+ 0110 Digital Imaging Video
+ 0113 Net MD
+ 0116 IC Recorder (P)
+ 0144 Clie PEG-TH55 PDA
+ 0147 Visual Communication Camera (PCGA-UVC11)
+ 014c Aiwa AM-NX9 Net MD Music Recorder MDLP
+ 014d Memory Stick Reader/Writer
+ 0154 Eyetoy Audio Device
+ 015f IC Recorder (BM)
+ 0169 Clie PEG-TJ35 PDA Serial
+ 016a Clie PEG-TJ35 PDA Mass Storage
+ 016b Mobile HDD
+ 016d IC Recorder (SX)
+ 016e DPP-EX50 Digital Photo Printer
+ 0171 Fingerprint Sensor 3500
+ 017e Net MD
+ 017f Hi-MD WALKMAN
+ 0180 Net MD
+ 0181 Hi-MD WALKMAN
+ 0182 Net MD
+ 0183 Hi-MD WALKMAN
+ 0184 Net MD
+ 0185 Hi-MD WALKMAN
+ 0186 Net MD
+ 0187 Hi-MD MZ-NH600 WALKMAN
+ 0188 Net MD
+ 018a Net MD
+ 018b Hi-MD SOUND GATE
+ 019e Micro Vault 1.0G Mass Storage
+ 01ad ATRAC HDD PA
+ 01bb FeliCa S320 [PaSoRi]
+ 01bd MRW62E Multi-Card Reader/Writer
+ 01c3 NW-E55 Network Walkman
+ 01c6 MEMORY P-AUDIO
+ 01c7 Printing Support
+ 01c8 PSP Type A
+ 01c9 PSP Type B
+ 01d0 DVD+RW External Drive DRU-700A
+ 01d5 IC RECORDER
+ 01de VRD-VC10 [Video Capture]
+ 01e8 UP-DR150 Photo Printer
+ 01e9 Net MD
+ 01ea Hi-MD WALKMAN
+ 01ee IC RECORDER
+ 01fa IC Recorder (P)
+ 01fb NW-E405 Network Walkman
+ 020f Device
+ 0210 ATRAC HDD PA
+ 0219 Net MD
+ 021a Hi-MD WALKMAN
+ 021b Net MD
+ 021c Hi-MD WALKMAN
+ 021d Net MD
+ 0226 UP-CR10L
+ 0227 Printing Support
+ 022c Net MD
+ 022d Hi-MD AUDIO
+ 0233 ATRAC HDD PA
+ 0236 Mobile HDD
+ 023b DVD+RW External Drive DRU-800UL
+ 023c Net MD
+ 023d Hi-MD WALKMAN
+ 0243 MicroVault Flash Drive
+ 024b Vaio VGX Mouse
+ 0257 IFU-WLM2 USB Wireless LAN Module (Wireless Mode)
+ 0258 IFU-WLM2 USB Wireless LAN Module (Memory Mode)
+ 0259 IC RECORDER
+ 0267 Tachikoma Device
+ 0268 Batoh Device / PlayStation 3 Controller
+ 0269 HDD WALKMAN
+ 026a HDD WALKMAN
+ 0271 IC Recorder (P)
+ 027c NETWORK WALKMAN
+ 027e SONY Communicator
+ 027f IC RECORDER
+ 0286 Net MD
+ 0287 Hi-MD WALKMAN
+ 0290 VGP-UVC100 Visual Communication Camera
+ 029b PRS-500 eBook reader
+ 02a5 MicroVault Flash Drive
+ 02af Handycam DCR-DVD306E
+ 02c4 Device
+ 02d1 DVD RW
+ 02d2 PSP Slim
+ 02d8 SBAC-US10 SxS PRO memory card reader/writer
+ 02e1 FeliCa S330 [PaSoRi]
+ 02ea PlayStation 3 Memory Card Adaptor
+ 02f9 DSC-H9
+ 0317 WALKMAN
+ 031a Walkman NWD-B103F
+ 031e PRS-300/PRS-505 eBook reader
+ 0325 NWZ-A818
+ 033e DSC-W120/W290
+ 0346 Handycam DCR-SR55E
+ 0348 HandyCam HDR-TG3E
+ 035b Walkman NWZ-A828
+ 035c NWZ-A726/A728/A729
+ 035f UP-DR200 Photo Printer
+ 0382 Memory Stick PRO-HG Duo Adaptor (MSAC-UAH1)
+ 0385 Walkman NWZ-E436F
+ 0387 IC Recorder (P)
+ 03bc Webbie HD - MHS-CM1
+ 03d1 DPF-X95
+ 03d3 DR-BT100CX
+ 03d5 PlayStation Move motion controller
+ 03fc WALKMAN [NWZ-E345]
+ 03fd Walkman NWZ-E443
+ 042f PlayStation Move navigation controller
+ 0440 DSC-H55
+ 0485 MHS-PM5 HD camcorder
+ 04cb WALKMAN NWZ-E354
+ 0541 DSC-HX100V [Cybershot Digital Still Camera]
+ 05c4 DualShock 4
+ 0689 Walkman NWZ-B173F
+ 06bb WALKMAN NWZ-F805
+ 06c3 RC-S380
+ 07c4 ILCE-6000 (aka Alpha-6000) in Mass Storage mode
+ 088c Portable Headphone Amplifier
+ 08b7 ILCE-6000 (aka Alpha-6000) in MTP mode
+ 094e ILCE-6000 (aka Alpha-6000) in PC Remote mode
+ 0994 ILCE-6000 (aka Alpha-6000) in charging mode
+ 0bb5 Headset MDR-1000X
+ 1000 Wireless Buzz! Receiver
+054d Try Corp.
+054e Proside Corp.
+054f WYSE Technology Taiwan
+0550 Fuji Xerox Co., Ltd
+ 0002 InkJet Color Printer
+ 0004 InkJet Color Printer
+ 0005 InkJet Color Printer
+ 000b Workcentre 24
+ 014e CM215b Printer
+ 0165 DocuPrint M215b
+0551 CompuTrend Systems, Inc.
+0552 Philips Monitors
+0553 STMicroelectronics Imaging Division (VLSI Vision)
+ 0001 TerraCAM
+ 0002 CPiA Webcam
+ 0100 STV0672 Camera
+ 0140 Video Camera
+ 0150 CDE CAM 100
+ 0151 Digital Blue QX5 Microscope
+ 0200 Dual-mode Camera0
+ 0201 Dual-mode Camera1
+ 0202 STV0680 Camera
+ 0674 Multi-mode Camera
+ 0679 NMS Video Camera (Webcam)
+ 1002 Che-ez! Splash
+0554 Dictaphone Corp.
+0555 ANAM S&T Co., Ltd
+0556 Asahi Kasei Microsystems Co., Ltd
+ 0001 AK5370 I/F A/D Converter
+0557 ATEN International Co., Ltd
+ 2001 UC-1284 Printer Port
+ 2002 10Mbps Ethernet [klsi]
+ 2004 UC-100KM PS/2 Mouse and Keyboard adapter
+ 2006 UC-1284B Printer Port
+ 2007 UC-110T 100Mbps Ethernet [pegasus]
+ 2008 UC-232A Serial Port [pl2303]
+ 2009 UC-210T Ethernet
+ 2011 UC-2324 4xSerial Ports [mos7840]
+ 2202 CS124U Miniview II KVM Switch
+ 2212 Keyboard/Mouse
+ 2213 CS682 2-Port USB 2.0 DVI KVM Switch
+ 2221 Winbond Hermon
+ 2404 4-port switch
+ 2600 IDE Bridge
+ 2701 CE700A KVM Extender
+ 4000 DSB-650 10Mbps Ethernet [klsi]
+ 7000 Hub
+ 7820 UC-2322 2xSerial Ports [mos7820]
+ 8021 Hub
+0558 Truevision, Inc.
+ 1009 GW Instek GDS-1000 Oscilloscope
+ 100a GW Instek GDS-1000A Oscilloscope
+ 2009 GW Instek GDS-2000 Oscilloscope
+0559 Cadence Design Systems, Inc.
+055a Kenwood USA
+055b KnowledgeTek, Inc.
+055c Proton Electronic Ind.
+055d Samsung Electro-Mechanics Co.
+ 0001 Keyboard
+ 0bb1 Bluetooth Device
+ 1030 Optical Wheel Mouse (OMS3CB/OMGB30)
+ 1031 Optical Wheel Mouse (OMA3CB/OMGI30)
+ 1040 Mouse HID Device
+ 1050 E-Mail Optical Wheel Mouse (OMS3CE)
+ 1080 Optical Wheel Mouse (OMS3CH)
+ 2020 Floppy Disk Drive
+ 6780 Keyboard V1
+ 6781 Keyboard Mouse
+ 8001 E.M. Hub
+ 9000 AnyCam [pwc]
+ 9001 MPC-C30 AnyCam Premium for Notebooks [pwc]
+ a000 SWL-2100U
+ a010 WLAN Adapter(SWL-2300)
+ a011 Boot Device
+ a012 WLAN Adapter(SWL-2300)
+ a013 WLAN Adapter(SWL-2350)
+ a230 Boot Device
+ b000 11Mbps WLAN Mini Adapter
+ b230 Netopia 802.11b WLAN Adapter
+ b231 LG Wireless LAN 11b Adapter
+055e CTX Opto-Electronics Corp.
+055f Mustek Systems, Inc.
+ 0001 ScanExpress 1200 CU
+ 0002 ScanExpress 600 CU
+ 0003 ScanExpress 1200 USB
+ 0006 ScanExpress 1200 UB
+ 0007 ScanExpress 1200 USB Plus
+ 0008 ScanExpress 1200 CU Plus
+ 0010 BearPaw 1200F
+ 0210 ScanExpress A3 USB
+ 0218 BearPaw 2400 TA
+ 0219 BearPaw 2400 TA Plus
+ 021a BearPaw 2448 TA Plus
+ 021b BearPaw 1200 CU Plus
+ 021c BearPaw 1200 CU Plus
+ 021d BearPaw 2400 CU Plus
+ 021e BearPaw 1200 TA/CS
+ 021f SNAPSCAN e22
+ 0400 BearPaw 2400 TA Pro
+ 0401 P 3600 A3 Pro
+ 0408 BearPaw 2448 CU Pro
+ 0409 BearPaw 2448 TA Pro
+ 040b ScanExpress A3 USB 1200 PRO
+ 0873 ScanExpress 600 USB
+ 1000 BearPaw 4800 TA Pro
+ a350 gSmart 350 Camera
+ a800 MDC 800 Camera
+ b500 MDC 3000 Camera
+ c005 PC CAM 300A
+ c200 gSmart 300
+ c211 Kowa Bs888e Microcamera
+ c220 gSmart mini
+ c230 Digicam 330K
+ c232 MDC3500 Camera
+ c360 DV 4000 Camera
+ c420 gSmart mini 2 Camera
+ c430 gSmart LCD 2 Camera
+ c440 DV 3000 Camera
+ c520 gSmart mini 3 Camera
+ c530 gSmart LCD 2 Camera
+ c540 gSmart D30 Camera
+ c630 MDC 4000 Camera
+ c631 MDC 4000 Camera
+ c650 MDC 5500Z Camera
+ d001 WCam 300
+ d003 WCam 300A
+ d004 WCam 300AN
+0560 Interface Corp.
+0561 Oasis Design, Inc.
+0562 Telex Communications, Inc.
+ 0001 Enhanced Microphone
+ 0002 Telex Microphone
+0563 Immersion Corp.
+0564 Kodak Digital Product Center, Japan Ltd. (formerly Chinon Industries Inc.)
+0565 Peracom Networks, Inc.
+ 0001 Serial Port [etek]
+ 0002 Enet Ethernet [klsi]
+ 0003 @Home Networks Ethernet [klsi]
+ 0005 Enet2 Ethernet [klsi]
+ 0041 Peracom Remote NDIS Ethernet Adapter
+0566 Monterey International Corp.
+ 0110 ViewMate Desktop Mouse CC2201
+ 1001 ViewMate Desktop Mouse CC2201
+ 1002 ViewMate Desktop Mouse CC2201
+ 1003 ViewMate Desktop Mouse CC2201
+ 1004 ViewMate Desktop Mouse CC2201
+ 1005 ViewMate Desktop Mouse CC2201
+ 1006 ViewMate Desktop Mouse CC2201
+ 1007 ViewMate Desktop Mouse CC2201
+ 2800 MIC K/B
+ 2801 MIC K/B Mouse
+ 2802 Kbd Hub
+ 3002 Keyboard
+ 3004 Genius KB-29E
+ 3107 Keyboard
+0567 Xyratex International, Ltd
+0568 Quartz Ingenierie
+0569 SegaSoft
+056a Wacom Co., Ltd
+ 0000 PenPartner
+ 0001 PenPartner 4x5
+ 0002 PenPartner 6x8
+ 0003 PTU-600 [Cintiq Partner]
+ 0010 ET-0405 [Graphire]
+ 0011 ET-0405A [Graphire2 (4x5)]
+ 0012 ET-0507A [Graphire2 (5x7)]
+ 0013 CTE-430 [Graphire3 (4x5)]
+ 0014 CTE-630 [Graphire3 (6x8)]
+ 0015 CTE-440 [Graphire4 (4x5)]
+ 0016 CTE-640 [Graphire4 (6x8)]
+ 0017 CTE-450 [Bamboo Fun (small)]
+ 0018 CTE-650 [Bamboo Fun (medium)]
+ 0019 CTE-631 [Bamboo One]
+ 0020 GD-0405 [Intuos (4x5)]
+ 0021 GD-0608 [Intuos (6x8)]
+ 0022 GD-0912 [Intuos (9x12)]
+ 0023 GD-1212 [Intuos (12x12)]
+ 0024 GD-1218 [Intuos (12x18)]
+ 0026 PTH-450 [Intuos5 touch (S)]
+ 0027 PTH-650 [Intuos5 touch (M)]
+ 0028 PTH-850 [Intuos5 touch (L)]
+ 0029 PTK-450 [Intuos5 (S)]
+ 002a PTK-650 [Intuos5 (M)]
+ 0030 PL400
+ 0031 PL500
+ 0032 PL600
+ 0033 PL600SX
+ 0034 PL550
+ 0035 PL800
+ 0037 PL700
+ 0038 PL510
+ 0039 DTU-710
+ 003f DTZ-2100 [Cintiq 21UX]
+ 0041 XD-0405-U [Intuos2 (4x5)]
+ 0042 XD-0608-U [Intuos2 (6x8)]
+ 0043 XD-0912-U [Intuos2 (9x12)]
+ 0044 XD-1212-U [Intuos2 (12x12)]
+ 0045 XD-1218-U [Intuos2 (12x18)]
+ 0047 Intuos2 6x8
+ 0057 DTK-2241
+ 0059 DTH-2242 tablet
+ 005b DTH-2200 [Cintiq 22HD Touch] tablet
+ 005d DTH-2242 touchscreen
+ 005e DTH-2200 [Cintiq 22HD Touch] touchscreen
+ 0060 FT-0405 [Volito, PenPartner, PenStation (4x5)]
+ 0061 FT-0203 [Volito, PenPartner, PenStation (2x3)]
+ 0062 CTF-420 [Volito2]
+ 0063 CTF-220 [BizTablet]
+ 0064 CTF-221 [PenPartner2]
+ 0065 MTE-450 [Bamboo]
+ 0069 CTF-430 [Bamboo One]
+ 006a CTE-460 [Bamboo One Pen (S)]
+ 006b CTE-660 [Bamboo One Pen (M)]
+ 0081 CTE-630BT [Graphire Wireless (6x8)]
+ 0084 Wireless adapter for Bamboo tablets
+ 0090 TPC90
+ 0093 TPC93
+ 0097 TPC97
+ 009a TPC9A
+ 00b0 PTZ-430 [Intuos3 (4x5)]
+ 00b1 PTZ-630 [Intuos3 (6x8)]
+ 00b2 PTZ-930 [Intuos3 (9x12)]
+ 00b3 PTZ-1230 [Intuos3 (12x12)]
+ 00b4 PTZ-1231W [Intuos3 (12x19)]
+ 00b5 PTZ-631W [Intuos3 (6x11)]
+ 00b7 PTZ-431W [Intuos3 (4x6)]
+ 00b8 PTK-440 [Intuos4 (4x6)]
+ 00b9 PTK-640 [Intuos4 (6x9)]
+ 00ba PTK-840 [Intuos4 (8x13)]
+ 00bb PTK-1240 [Intuos4 (12x19)]
+ 00c0 DTF-521
+ 00c4 DTF-720
+ 00c5 DTZ-2000W [Cintiq 20WSX]
+ 00c6 DTZ-1200W [Cintiq 12WX]
+ 00c7 DTU-1931
+ 00cc DTK-2100 [Cintiq 21UX]
+ 00ce DTU-2231
+ 00d0 CTT-460 [Bamboo Touch]
+ 00d1 CTH-460 [Bamboo Pen & Touch]
+ 00d2 CTH-461 [Bamboo Fun/Craft/Comic Pen & Touch (S)]
+ 00d3 CTH-661 [Bamboo Fun/Comic Pen & Touch (M)]
+ 00d4 CTL-460 [Bamboo Pen (S)]
+ 00d5 CTL-660 [Bamboo Pen (M)]
+ 00d6 CTH-460 [Bamboo Pen & Touch]
+ 00d7 CTH-461 [Bamboo Fun/Craft/Comic Pen & Touch (S)]
+ 00d8 CTH-661 [Bamboo Fun/Comic Pen & Touch (M)]
+ 00d9 CTT-460 [Bamboo Touch]
+ 00da CTH-461SE [Bamboo Pen & Touch Special Edition (S)]
+ 00db CTH-661SE [Bamboo Pen & Touch Special Edition (M)]
+ 00dc CTT-470 [Bamboo Touch]
+ 00dd CTL-470 [Bamboo Connect]
+ 00de CTH-470 [Bamboo Fun Pen & Touch]
+ 00df CTH-670 [Bamboo Create/Fun]
+ 00e2 TPCE2
+ 00e3 TPCE3
+ 00e5 TPCE5
+ 00e6 TPCE6
+ 00ec TPCEC
+ 00ed TPCED
+ 00ef TPCEF
+ 00f4 DTK-2400 [Cintiq 24HD] tablet
+ 00f6 DTH-2400 [Cintiq 24HD touch] touchscreen
+ 00f8 DTH-2400 [Cintiq 24HD touch] tablet
+ 00fa DTK-2200 [Cintiq 22HD] tablet
+ 00fb DTU-1031
+ 0100 TPC100
+ 0101 TPC101
+ 010d TPC10D
+ 010e TPC10E
+ 010f TPC10F
+ 0116 TPC116
+ 012c TPC12C
+ 0221 MDP-123 [Inkling]
+ 0300 CTL-471 [Bamboo Splash, One by Wacom (S)]
+ 0301 CTL-671 [One by Wacom (M)]
+ 0302 CTH-480 [Intuos Pen & Touch (S)]
+ 0303 CTH-680 [Intuos Pen & Touch (M)]
+ 0304 DTK-1300 [Cintiq 13HD]
+ 0307 DTH-A1300 [Cintiq Companion Hybrid] tablet
+ 0309 DTH-A1300 [Cintiq Companion Hybrid] touchscreen
+ 030e CTL-480 [Intuos Pen (S)]
+ 0314 PTH-451 [Intuos pro (S)]
+ 0315 PTH-651 [Intuos pro (M)]
+ 0317 PTH-851 [Intuos pro (L)]
+ 0318 CTH-301 [Bamboo]
+ 032f DTU-1031X
+ 0347 Integrated Hub
+ 0348 Integrated Hub
+ 034a DTH-W1320 [MobileStudio Pro 13] touchscreen
+ 034b DTH-W1620 [MobileStudio Pro 16] touchscreen
+ 034d DTH-W1320 [MobileStudio Pro 13] tablet
+ 034e DTH-W1620 [MobileStudio Pro 16] tablet
+ 0400 PenPartner 4x5
+ 4001 TPC4001
+ 4004 TPC4004
+ 4850 PenPartner 6x8
+ 5000 TPC5000
+ 5002 TPC5002
+ 5010 TPC5010
+056b Decicon, Inc.
+056c eTEK Labs
+ 0006 KwikLink Host-Host Connector
+ 8007 Kwik232 Serial Port
+ 8100 KwikLink Host-Host Connector
+ 8101 KwikLink USB-USB Bridge
+056d EIZO Corp.
+ 0000 Hub
+ 0001 Monitor
+ 0002 HID Monitor Controls
+ 0003 Device Bay Controller
+056e Elecom Co., Ltd
+ 0002 29UO Mouse
+ 0072 Mouse
+ 200c LD-USB/TX
+ 4002 Laneed 100Mbps Ethernet LD-USB/TX [pegasus]
+ 4005 LD-USBL/TX
+ 400b LD-USB/TX
+ 4010 LD-USB20
+ 5003 UC-SGT
+ 5004 UC-SGT
+ 6008 Flash Disk
+ abc1 LD-USB/TX
+056f Korea Data Systems Co., Ltd
+ cd00 CDM-751 CD organizer
+0570 Epson America
+0571 Interex, Inc.
+ 0002 echoFX InterView Lite
+0572 Conexant Systems (Rockwell), Inc.
+ 0001 Ezcam II Webcam
+ 0002 Ezcam II Webcam
+ 0040 Wondereye CP-115 Webcam
+ 0041 Webcam Notebook
+ 0042 Webcam Notebook
+ 0320 DVBSky T330 DVB-T2/C tuner
+ 1232 V.90 modem
+ 1234 Typhoon Redfun Modem V90 56k
+ 1252 HCF V90 Data Fax Voice Modem
+ 1253 Zoom V.92 Faxmodem
+ 1300 SoftK56 Data Fax Voice CARP
+ 1301 Modem Enumerator
+ 1328 TrendNet TFM-561 modem
+ 1804 HP Dock Audio
+ 2000 SoftGate 802.11 Adapter
+ 2002 SoftGate 802.11 Adapter
+ 262a tm5600 Video & Audio Grabber Capture
+ 680c DVBSky T680C DVB-T2/C tuner
+ 6831 DVBSky S960 DVB-S2 tuner
+ 8390 WinFast PalmTop/Novo TV Video
+ 8392 WinFast PalmTop/Novo TV Video
+ 960c DVBSky S960C DVB-S2 tuner
+ c686 Geniatech T220A DVB-T2 TV Stick
+ c688 Geniatech T230 DVB-T2 TV Stick
+ cafc CX861xx ROM Boot Loader
+ cafd CX82310 ROM Boot Loader
+ cafe AccessRunner ADSL Modem
+ cb00 ADSL Modem
+ cb01 ADSL Modem
+ cb06 StarModem Network Interface
+0573 Zoran Co. Personal Media Division (Nogatech)
+ 0003 USBGear USBG-V1
+ 0400 D-Link V100
+ 0600 Dazzle USBVision (1006)
+ 1300 leadtek USBVision (1006)
+ 2000 X10 va10a Wireless Camera
+ 2001 Dazzle EmMe (2001)
+ 2101 Zoran Co. PMD (Nogatech) AV-grabber Manhattan
+ 2d00 Osprey 50
+ 2d01 Hauppauge USB-Live Model 600
+ 3000 Dazzle MicroCam (NTSC)
+ 3001 Dazzle MicroCam (PAL)
+ 4000 Nogatech TV! (NTSC)
+ 4001 Nogatech TV! (PAL)
+ 4002 Nogatech TV! (PAL-I-)
+ 4003 Nogatech TV! (MF-)
+ 4008 Nogatech TV! (NTSC) (T)
+ 4009 Nogatech TV! (PAL) (T)
+ 4010 Nogatech TV! (NTSC) (A)
+ 4100 USB-TV FM (NTSC)
+ 4110 PNY USB-TV (NTSC) FM
+ 4400 Nogatech TV! Pro (NTSC)
+ 4401 Nogatech TV! Pro (PAL)
+ 4450 PixelView PlayTv-USB PRO (PAL) FM
+ 4451 Nogatech TV! Pro (PAL+)
+ 4452 Nogatech TV! Pro (PAL-I+)
+ 4500 Nogatech TV! Pro (NTSC)
+ 4501 Nogatech TV! Pro (PAL)
+ 4550 ZTV ZT-721 2.4GHz A/V Receiver
+ 4551 Dazzle TV! Pro Audio (P+)
+ 4d00 Hauppauge WinTV-USB USA
+ 4d01 Hauppauge WinTV-USB
+ 4d02 Hauppauge WinTV-USB UK
+ 4d03 Hauppauge WinTV-USB France
+ 4d04 Hauppauge WinTV (PAL D/K)
+ 4d10 Hauppauge WinTV-USB with FM USA radio
+ 4d11 Hauppauge WinTV-USB (PAL) with FM radio
+ 4d12 Hauppauge WinTV-USB UK with FM Radio
+ 4d14 Hauppauge WinTV (PAL D/K FM)
+ 4d20 Hauppauge WinTV-USB II (PAL) with FM radio
+ 4d21 Hauppauge WinTV-USB II (PAL)
+ 4d22 Hauppauge WinTV-USB II (PAL) Model 566
+ 4d23 Hauppauge WinTV-USB France 4D23
+ 4d24 Hauppauge WinTV Pro (PAL D/K)
+ 4d25 Hauppauge WinTV-USB Model 40209 rev B234
+ 4d26 Hauppauge WinTV-USB Model 40209 rev B243
+ 4d27 Hauppauge WinTV-USB Model 40204 Rev B281
+ 4d28 Hauppauge WinTV-USB Model 40204 rev B283
+ 4d29 Hauppauge WinTV-USB Model 40205 rev B298
+ 4d2a Hauppague WinTV-USB Model 602 Rev B285
+ 4d2b Hauppague WinTV-USB Model 602 Rev B282
+ 4d2c Hauppauge WinTV Pro (PAL/SECAM)
+ 4d30 Hauppauge WinTV-USB FM Model 40211 Rev B123
+ 4d31 Hauppauge WinTV-USB III (PAL) with FM radio Model 568
+ 4d32 Hauppauge WinTV-USB III (PAL) FM Model 573
+ 4d34 Hauppauge WinTV Pro (PAL D/K FM)
+ 4d35 Hauppauge WinTV-USB III (PAL) FM Model 597
+ 4d36 Hauppauge WinTV Pro (PAL B/G FM)
+ 4d37 Hauppauge WinTV-USB Model 40219 rev E189
+ 4d38 Hauppauge WinTV Pro (NTSC FM)
+0574 City University of Hong Kong
+0575 Philips Creative Display Solutions
+0576 BAFO/Quality Computer Accessories
+0577 ELSA
+0578 Intrinsix Corp.
+0579 GVC Corp.
+057a Samsung Electronics America
+057b Y-E Data, Inc.
+ 0000 FlashBuster-U Floppy
+ 0001 Tri-Media Reader Floppy
+ 0006 Tri-Media Reader Card Reader
+ 0010 Memory Stick Reader Writer
+ 0020 HEXA Media Drive 6-in-1 Card Reader Writer
+ 0030 Memory Card Viewer (TV)
+057c AVM GmbH
+ 0b00 ISDN-Controller B1 Family
+ 0c00 ISDN-Controller FRITZ!Card
+ 1000 ISDN-Controller FRITZ!Card v2.0
+ 1900 ISDN-Controller FRITZ!Card v2.1
+ 2000 ISDN-Connector FRITZ!X
+ 2200 BlueFRITZ!
+ 2300 Teledat X130 DSL
+ 2800 ISDN-Connector TA
+ 3200 Teledat X130 DSL
+ 3500 FRITZ!Card DSL SL
+ 3701 FRITZ!Box SL
+ 3702 FRITZ!Box
+ 3800 BlueFRITZ! Bluetooth Stick
+ 3a00 FRITZ!Box Fon
+ 3c00 FRITZ!Box WLAN
+ 3d00 Fritz!Box
+ 3e01 FRITZ!Box (Annex A)
+ 4001 FRITZ!Box Fon (Annex A)
+ 4101 FRITZ!Box WLAN (Annex A)
+ 4201 FRITZ!Box Fon WLAN (Annex A)
+ 4601 Eumex 5520PC (WinXP/2000)
+ 4602 Eumex 400 (WinXP/2000)
+ 4701 AVM FRITZ!Box Fon ata
+ 5401 Eumex 300 IP
+ 5601 AVM Fritz!WLAN [Texas Instruments TNETW1450]
+ 6201 AVM Fritz!WLAN v1.1 [Texas Instruments TNETW1450]
+ 62ff AVM Fritz!WLAN USB (in CD-ROM-mode)
+ 8401 Fritz!WLAN N [Atheros AR9001U]
+ 8402 Fritz!WLAN N 2.4 [Atheros AR9001U]
+ 8403 Fritz!WLAN N v2 [Atheros AR9271]
+ 84ff AVM Fritz!WLAN USB N (in CD-ROM-mode)
+ 8501 FRITZ WLAN N v2 [RT5572/rt2870.bin]
+057d Shark Multimedia, Inc.
+057e Nintendo Co., Ltd
+ 0305 Broadcom BCM2045A Bluetooth Radio [Nintendo Wii]
+ 0306 Wii Remote Controller RVL-003
+057f QuickShot, Ltd
+ 6238 USB StrikePad
+0580 Denron, Inc.
+0581 Racal Data Group
+0582 Roland Corp.
+ 0000 UA-100(G)
+ 0002 UM-4/MPU-64 MIDI Interface
+ 0003 SoundCanvas SC-8850
+ 0004 U-8
+ 0005 UM-2(C/EX)
+ 0007 SoundCanvas SC-8820
+ 0008 PC-300
+ 0009 UM-1(E/S/X)
+ 000b SK-500
+ 000c SC-D70
+ 0010 EDIROL UA-5
+ 0011 Edirol UA-5 Sound Capture
+ 0012 XV-5050
+ 0013 XV-5050
+ 0014 EDIROL UM-880 MIDI I/F (native)
+ 0015 EDIROL UM-880 MIDI I/F (generic)
+ 0016 EDIROL SD-90
+ 0017 EDIROL SD-90
+ 0018 UA-1A
+ 001b MMP-2
+ 001c MMP-2
+ 001d V-SYNTH
+ 001e V-SYNTH
+ 0023 EDIROL UM-550
+ 0024 EDIROL UM-550
+ 0025 EDIROL UA-20
+ 0026 EDIROL UA-20
+ 0027 EDIROL SD-20
+ 0028 EDIROL SD-20
+ 0029 EDIROL SD-80
+ 002a EDIROL SD-80
+ 002b EDIROL UA-700
+ 002c EDIROL UA-700
+ 002d XV-2020 Synthesizer
+ 002e XV-2020 Synthesizer
+ 002f VariOS
+ 0030 VariOS
+ 0033 EDIROL PCR
+ 0034 EDIROL PCR
+ 0035 M-1000
+ 0037 Digital Piano
+ 0038 Digital Piano
+ 003b BOSS GS-10
+ 003c BOSS GS-10
+ 0040 GI-20
+ 0041 GI-20
+ 0042 RS-70
+ 0043 RS-70
+ 0044 EDIROL UA-1000
+ 0047 EDIROL UR-80 WAVE
+ 0048 EDIROL UR-80 MIDI
+ 0049 EDIROL UR-80 WAVE
+ 004a EDIROL UR-80 MIDI
+ 004b EDIROL M-100FX
+ 004c EDIROL PCR-A WAVE
+ 004d EDIROL PCR-A MIDI
+ 004e EDIROL PCR-A WAVE
+ 004f EDIROL PCR-A MIDI
+ 0050 EDIROL UA-3FX
+ 0052 EDIROL UM-1SX
+ 0054 Digital Piano
+ 0060 EXR Series
+ 0064 EDIROL PCR-1 WAVE
+ 0065 EDIROL PCR-1 MIDI
+ 0066 EDIROL PCR-1 WAVE
+ 0067 EDIROL PCR-1 MIDI
+ 006a SP-606
+ 006b SP-606
+ 006d FANTOM-X
+ 006e FANTOM-X
+ 0073 EDIROL UA-25
+ 0074 EDIROL UA-25
+ 0075 BOSS DR-880
+ 0076 BOSS DR-880
+ 007a RD
+ 007b RD
+ 007d EDIROL UA-101
+ 0080 G-70
+ 0081 G-70
+ 0084 V-SYNTH XT
+ 0089 BOSS GT-PRO
+ 008b EDIROL PC-50
+ 008c EDIROL PC-50
+ 008d EDIROL UA-101 USB1
+ 0092 EDIROL PC-80 WAVE
+ 0093 EDIROL PC-80 MIDI
+ 0096 EDIROL UA-1EX
+ 009a EDIROL UM-3EX
+ 009d EDIROL UM-1
+ 00a0 MD-P1
+ 00a2 Digital Piano
+ 00a3 EDIROL UA-4FX
+ 00a6 Juno-G
+ 00a9 MC-808
+ 00ad SH-201
+ 00b2 VG-99
+ 00b3 VG-99
+ 00b7 BK-7m/VIMA JM-5/8
+ 00c2 SonicCell
+ 00c4 EDIROL M-16DX
+ 00c5 SP-555
+ 00c7 V-Synth GT
+ 00d1 Music Atelier
+ 00d3 M-380/400
+ 00da BOSS GT-10
+ 00db BOSS GT-10 Guitar Effects Processor
+ 00dc BOSS GT-10B
+ 00de Fantom G
+ 00e6 EDIROL UA-25EX (Advanced mode)
+ 00e7 EDIROL UA-25EX
+ 00e9 UA-1G
+ 00eb VS-100
+ 00f6 GW-8/AX-Synth
+ 00f8 JUNO Series
+ 00fc VS-700C
+ 00fd VS-700
+ 00fe VS-700 M1
+ 00ff VS-700 M2
+ 0100 VS-700
+ 0101 VS-700 M2
+ 0102 VB-99
+ 0104 UM-1G
+ 0106 UM-2G
+ 0108 UM-3G
+ 0109 eBand JS-8
+ 010d A-500S
+ 010f A-PRO
+ 0110 A-PRO
+ 0111 GAIA SH-01
+ 0113 ME-25
+ 0114 SD-50
+ 0116 WAVE/MP3 RECORDER R-05
+ 0117 VS-20
+ 0119 OCTAPAD SPD-30
+ 011c Lucina AX-09
+ 011e BR-800
+ 0120 OCTA-CAPTURE
+ 0121 OCTA-CAPTURE
+ 0123 JUNO-Gi
+ 0124 M-300
+ 0127 GR-55
+ 012a UM-ONE
+ 012b DUO-CAPTURE
+ 012f QUAD-CAPTURE
+ 0130 MICRO BR BR-80
+ 0132 TRI-CAPTURE
+ 0134 V-Mixer
+ 0138 Boss RC-300 (Audio mode)
+ 0139 Boss RC-300 (Storage mode)
+ 013a JUPITER-80
+ 013e R-26
+ 0145 SPD-SX
+ 014b eBand JS-10
+ 014d GT-100
+ 0150 TD-15
+ 0151 TD-11
+ 0154 JUPITER-50
+ 0156 A-Series
+ 0158 TD-30
+ 0159 DUO-CAPTURE EX
+ 015b INTEGRA-7
+ 015d R-88
+ 0505 EDIROL UA-101
+0583 Padix Co., Ltd (Rockfire)
+ 0001 4 Axis 12 button +POV
+ 0002 4 Axis 12 button +POV
+ 2030 RM-203 USB Nest [mode 1]
+ 2031 RM-203 USB Nest [mode 2]
+ 2032 RM-203 USB Nest [mode 3]
+ 2033 RM-203 USB Nest [mode 4]
+ 2050 PX-205 PSX Bridge
+ 205f PSX/USB converter
+ 206f USB, 2-axis 8-button gamepad
+ 3050 QF-305u Gamepad
+ 3379 Rockfire X-Force
+ 337f Rockfire USB RacingStar Vibra
+ 509f USB,4-Axis,12-Button with POV
+ 5259 Rockfire USB SkyShuttle Vibra
+ 525f USB Vibration Pad
+ 5308 USB Wireless VibrationPad
+ 5359 Rockfire USB SkyShuttle Pro
+ 535f USB,real VibrationPad
+ 5659 Rockfire USB SkyShuttle Vibra
+ 565f USB VibrationPad
+ 6009 Revenger
+ 600f USB,GameBoard II
+ 6258 USB, 4-axis, 6-button joystick w/view finder
+ 6889 Windstorm Pro
+ 688f QF-688uv Windstorm Pro Joystick
+ 7070 QF-707u Bazooka Joystick
+ a000 MaxFire G-08XU Gamepad
+ a015 4-Axis,16-Button with POV
+ a019 USB, Vibration ,4-axis, 8-button joystick w/view finder
+ a020 USB,4-Axis,10-Button with POV
+ a021 USB,4-Axis,12-Button with POV
+ a022 USB,4-Axis,14-Button with POV
+ a023 USB,4-Axis,16-Button with POV
+ a024 4axis,12button vibrition audio gamepad
+ a025 4axis,12button vibrition audio gamepad
+ a130 USB Wireless 2.4GHz Gamepad
+ a131 USB Wireless 2.4GHz Joystick
+ a132 USB Wireless 2.4GHz Wheelpad
+ a133 USB Wireless 2.4GHz Wheel&Gamepad
+ a202 ForceFeedbackWheel
+ a209 MetalStrike FF
+ b000 USB,4-Axis,12-Button with POV
+ b001 USB,4-Axis,12-Button with POV
+ b002 Vibration,12-Button USB Wheel
+ b005 USB,12-Button Wheel
+ b008 USB Wireless 2.4GHz Wheel
+ b009 USB,12-Button Wheel
+ b00a PSX/USB converter
+ b00b PSX/USB converter
+ b00c PSX/USB converter
+ b00d PSX/USB converter
+ b00e 4-Axis,12-Button with POV
+ b00f USB,5-Axis,10-Button with POV
+ b010 MetalStrike Pro
+ b012 Wireless MetalStrike
+ b013 USB,Wiress 2.4GHZ Joystick
+ b016 USB,5-Axis,10-Button with POV
+ b018 TW6 Wheel
+ ff60 USB Wireless VibrationPad
+0584 RATOC System, Inc.
+ 0008 Fujifilm MemoryCard ReaderWriter
+ 0220 U2SCX SCSI Converter
+ 0304 U2SCX-LVD (SCSI Converter)
+ b000 REX-USB60
+ b020 REX-USB60F
+0585 FlashPoint Technology, Inc.
+ 0001 Digital Camera
+ 0002 Digital Camera
+ 0003 Digital Camera
+ 0004 Digital Camera
+ 0005 Digital Camera
+ 0006 Digital Camera
+ 0007 Digital Camera
+ 0008 Digital Camera
+ 0009 Digital Camera
+ 000a Digital Camera
+ 000b Digital Camera
+ 000c Digital Camera
+ 000d Digital Camera
+ 000e Digital Camera
+ 000f Digital Camera
+0586 ZyXEL Communications Corp.
+ 0025 802.11b/g/n USB Wireless Network Adapter
+ 0100 omni.net
+ 0102 omni.net II ISDN TA [HFC-S]
+ 0110 omni.net Plus
+ 1000 omni.net LCD Plus - ISDN TA
+ 1500 Omni 56K Plus
+ 2011 Scorpion-980N keyboard
+ 3304 LAN Modem
+ 3309 ADSL Modem Prestige 600 series
+ 330a ADSL Modem Interface
+ 330e USB Broadband ADSL Modem Rev 1.10
+ 3400 ZyAIR B-220 IEEE 802.11b Adapter
+ 3401 ZyAIR G-220 802.11bg
+ 3402 ZyAIR G-220F 802.11bg
+ 3403 AG-200 802.11abg Wireless Adapter [Atheros AR5523]
+ 3407 G-200 v2 802.11bg
+ 3408 G-260 802.11bg
+ 3409 AG-225H 802.11bg
+ 340a M-202 802.11bg
+ 340c G-270S 802.11bg Wireless Adapter [Atheros AR5523]
+ 340f G-220 v2 802.11bg
+ 3410 ZyAIR G-202 802.11bg
+ 3412 802.11bg
+ 3413 ZyAIR AG-225H v2 802.11bg
+ 3415 G-210H 802.11g Wireless Adapter
+ 3416 NWD-210N 802.11b/g/n-draft wireless adapter
+ 3417 NWD271N 802.11n Wireless Adapter [Atheros AR9001U-(2)NG]
+ 3418 NWD211AN 802.11abgn Wireless Adapter [Ralink RT2870]
+ 3419 G-220 v3 802.11bg Wireless Adapter [ZyDAS ZD1211B]
+ 341a NWD-270N Wireless N-lite USB Adapter
+ 341e NWD2105 802.11bgn Wireless Adapter [Ralink RT3070]
+ 341f NWD2205 802.11n Wireless N Adapter [Realtek RTL8192CU]
+ 3425 NWD6505 802.11a/b/g/n/ac Wireless Adapter [MediaTek MT7610U]
+ 343e N220 802.11bgn Wireless Adapter
+0587 America Kotobuki Electronics Industries, Inc.
+0588 Sapien Design
+0589 Victron
+058a Nohau Corp.
+058b Infineon Technologies
+ 0015 Flash Loader utility
+ 001c Flash Drive
+ 0041 Flash Loader utility
+058c In Focus Systems
+ 0007 Flash
+ 0008 LP130
+ 000a LP530
+ 0010 Projector
+ 0011 Projector
+ 0012 Projector
+ 0013 Projector
+ 0014 Projector
+ 0015 Projector
+ 0016 Projector
+ 0017 Projector
+ 0018 Projector
+ 0019 Projector
+ 001a Projector
+ 001b Projector
+ 001c Projector
+ 001d Projector
+ 001e Projector
+ 001f Projector
+ ffe5 IN34 Projector
+ ffeb Projector IN76
+058d Micrel Semiconductor
+058e Tripath Technology, Inc.
+058f Alcor Micro Corp.
+ 1234 Flash Drive
+ 2412 SCard R/W CSR-145
+ 2802 Monterey Keyboard
+ 5492 Hub
+ 6232 Hi-Speed 16-in-1 Flash Card Reader/Writer
+ 6254 USB Hub
+ 6331 SD/MMC/MS Card Reader
+ 6332 Multi-Function Card Reader
+ 6335 SD/MMC Card Reader
+ 6360 Multimedia Card Reader
+ 6361 Multimedia Card Reader
+ 6362 Flash Card Reader/Writer
+ 6364 AU6477 Card Reader Controller
+ 6366 Multi Flash Reader
+ 6377 AU6375 4-LUN card reader
+ 6386 Memory Card
+ 6387 Flash Drive
+ 6390 USB 2.0-IDE bridge
+ 6391 IDE Bridge
+ 9213 MacAlly Kbd Hub
+ 9215 AU9814 Hub
+ 9254 Hub
+ 9310 Mass Storage (UID4/5A & UID7A)
+ 9320 Micro Storage Driver for Win98
+ 9321 Micro Storage Driver for Win98
+ 9330 SD Reader
+ 9331 Micro Storage Driver for Win98
+ 9340 Delkin eFilm Reader-32
+ 9350 Delkin eFilm Reader-32
+ 9360 8-in-1 Media Card Reader
+ 9361 Multimedia Card Reader
+ 9368 Multimedia Card Reader
+ 9380 Flash Drive
+ 9381 Flash Drive
+ 9382 Acer/Sweex Flash drive
+ 9384 qdi U2Disk T209M
+ 9410 Keyboard
+ 9472 Keyboard Hub
+ 9510 ChunghwaTL USB02 Smartcard Reader
+ 9520 Watchdata W 1981
+ 9540 AU9540 Smartcard Reader
+ 9720 USB-Serial Adapter
+ a014 Asus Integrated Webcam
+ b002 Acer Integrated Webcam
+0590 Omron Corp.
+ 0004 Cable Modem
+ 000b MR56SVS
+ 0028 HJ-720IT / HEM-7080IT-E / HEM-790IT
+0591 Questra Consulting
+0592 Powerware Corp.
+ 0002 UPS (X-Slot)
+0593 Incite
+0594 Princeton Graphic Systems
+0595 Zoran Microelectronics, Ltd
+ 1001 Digitrex DSC-1300/DSC-2100 (mass storage mode)
+ 2002 DIGITAL STILL CAMERA 6M 4X
+ 4343 Digital Camera EX-20 DSC
+0596 MicroTouch Systems, Inc.
+ 0001 Touchscreen
+ 0002 Touch Screen Controller
+ 0500 PCT Multitouch HID Controller
+ 0543 DELL XPS touchscreen
+0597 Trisignal Communications
+0598 Niigata Canotec Co., Inc.
+0599 Brilliance Semiconductor, Inc.
+059a Spectrum Signal Processing, Inc.
+059b Iomega Corp.
+ 0001 Zip 100 (Type 1)
+ 000b Zip 100 (Type 2)
+ 0021 Win98 Disk Controller
+ 0030 Zip 250 (Ver 1)
+ 0031 Zip 100 (Type 3)
+ 0032 Zip 250 (Ver 2)
+ 0034 Zip 100 Driver
+ 0037 Zip 750 MB
+ 0040 SCSI Bridge
+ 0042 Rev 70 GB
+ 0050 Zip CD 650 Writer
+ 0053 CDRW55292EXT CD-RW External Drive
+ 0056 External CD-RW Drive Enclosure
+ 0057 Mass Storage Device
+ 005d Mass Storage Device
+ 005f CDRW64892EXT3-C CD-RW 52x24x52x External Drive
+ 0060 PCMCIA PocketZip Dock
+ 0061 Varo PocketZip 40 MP3 Player
+ 006d HipZip MP3 Player
+ 0070 eGo Portable Hard Drive
+ 007c Ultra Max USB/1394
+ 007d HTC42606 0G9AT00 [Iomega HDD]
+ 007e Mini 256MB/512MB Flash Drive [IOM2D5]
+ 00db FotoShow Zip 250 Driver
+ 0150 Mass Storage Device
+ 015d Super DVD Writer
+ 0173 Hi-Speed USB-to-IDE Bridge Controller
+ 0174 Hi-Speed USB-to-IDE Bridge Controller
+ 0176 Hi-Speed USB-to-IDE Bridge Controller
+ 0177 Hi-Speed USB-to-IDE Bridge Controller
+ 0178 Hi-Speed USB-to-IDE Bridge Controller
+ 0179 Hi-Speed USB-to-IDE Bridge Controller
+ 017a HDD
+ 017b HDD/1394A
+ 017c HDD/1394B
+ 0251 Optical
+ 0252 Optical
+ 0275 ST332082 0A
+ 0278 LDHD-UPS [Professional Desktop Hard Drive eSATA / USB2.0]
+ 027a LPHD250-U [Portable Hard Drive Silver Series 250 Go]
+ 0470 Prestige Portable Hard Drive
+ 047a Select Portable Hard Drive
+ 0571 Prestige Portable Hard Drive
+ 0579 eGo Portable Hard Drive
+ 1052 DVD+RW External Drive
+059c A-Trend Technology Co., Ltd
+059d Advanced Input Devices
+059e Intelligent Instrumentation
+059f LaCie, Ltd
+ 0201 StudioDrive USB2
+ 0202 StudioDrive USB2
+ 0203 StudioDrive USB2
+ 0211 PocketDrive
+ 0212 PocketDrive
+ 0213 PocketDrive USB2
+ 0323 LaCie d2 Drive USB2
+ 0421 Big Disk G465
+ 0525 BigDisk Extreme 500
+ 0641 Mobile Hard Drive
+ 0829 BigDisk Extreme+
+ 100c Rugged Triple Interface Mobile Hard Drive
+ 1010 Desktop Hard Drive
+ 1016 Desktop Hard Drive
+ 1018 Desktop Hard Drive
+ 1019 Desktop Hard Drive
+ 1021 Little Disk
+ 1027 iamaKey V2
+ 102a Rikiki Hard Drive
+ 1049 rikiki Harddrive
+ 1052 P'9220 Mobile Drive
+ 1064 Rugged 16 and 32 GB
+ 106d Porsche Design Mobile Drive
+ 106e Porsche Design Desktop Drive
+ a601 HardDrive
+ a602 CD R/W
+05a0 Vetronix Corp.
+05a1 USC Corp.
+05a2 Fuji Film Microdevices Co., Ltd
+05a3 ARC International
+ 8388 Marvell 88W8388 802.11a/b/g WLAN
+05a4 Ortek Technology, Inc.
+ 1000 WKB-1000S Wireless Ergo Keyboard with Touchpad
+ 2000 WKB-2000 Wireless Keyboard with Touchpad
+ 9720 Keyboard Mouse
+ 9722 Keyboard
+ 9731 MCK-600W/MCK-800USB Keyboard
+ 9783 Wireless Keypad
+ 9837 Targus Number Keypad
+ 9862 Targus Number Keypad (Composite Device)
+ 9881 IR receiver [VRC-1100 Vista MCE Remote Control]
+05a5 Sampo Technology Corp.
+05a6 Cisco Systems, Inc.
+ 0001 CVA124 Cable Voice Adapter (WDM)
+ 0002 CVA122 Cable Voice Adapter (WDM)
+ 0003 CVA124E Cable Voice Adapter (WDM)
+ 0004 CVA122E Cable Voice Adapter (WDM)
+05a7 Bose Corp.
+ 4000 Bluetooth Headset
+ 4001 Bluetooth Headset in DFU mode
+ 4002 Bluetooth Headset Series 2
+ 4003 Bluetooth Headset Series 2 in DFU mode
+ bc50 SoundLink Wireless Mobile speaker
+ bc51 SoundLink Wireless Mobile speaker in DFU mode
+05a8 Spacetec IMC Corp.
+05a9 OmniVision Technologies, Inc.
+ 0511 OV511 Webcam
+ 0518 OV518 Webcam
+ 0519 OV519 Microphone
+ 1550 VEHO Filmscanner
+ 2640 OV2640 Webcam
+ 2643 Monitor Webcam
+ 264b Monitor Webcam
+ 2800 SuperCAM
+ 4519 Webcam Classic
+ 7670 OV7670 Webcam
+ 8065 GAIA Sensor FPGA Demo Board
+ 8519 OV519 Webcam
+ a511 OV511+ Webcam
+ a518 D-Link DSB-C310 Webcam
+05aa Utilux South China, Ltd
+05ab In-System Design
+ 0002 Parallel Port
+ 0030 Storage Adapter V2 (TPP)
+ 0031 ATA Bridge
+ 0060 USB 2.0 ATA Bridge
+ 0061 Storage Adapter V3 (TPP-I)
+ 0101 Storage Adapter (TPP)
+ 0130 Compact Flash and Microdrive Reader (TPP)
+ 0200 USS725 ATA Bridge
+ 0201 Storage Adapter (TPP)
+ 0202 ATA Bridge
+ 0300 Portable Hard Drive (TPP)
+ 0301 Portable Hard Drive V2
+ 0350 Portable Hard Drive (TPP)
+ 0351 Portable Hard Drive V2
+ 081a ATA Bridge
+ 0cda ATA Bridge for CD-R/RW
+ 1001 BAYI Printer Class Support
+ 5700 Storage Adapter V2 (TPP)
+ 5701 USB Storage Adapter V2
+ 5901 Smart Board (TPP)
+ 5a01 ATI Storage Adapter (TPP)
+ 5d01 DataBook Adapter (TPP)
+05ac Apple, Inc.
+ 0201 USB Keyboard [Alps or Logitech, M2452]
+ 0202 Keyboard [ALPS]
+ 0205 Extended Keyboard [Mitsumi]
+ 0206 Extended Keyboard [Mitsumi]
+ 020b Pro Keyboard [Mitsumi, A1048/US layout]
+ 020c Extended Keyboard [Mitsumi]
+ 020d Pro Keyboard [Mitsumi, A1048/JIS layout]
+ 020e Internal Keyboard/Trackpad (ANSI)
+ 020f Internal Keyboard/Trackpad (ISO)
+ 0214 Internal Keyboard/Trackpad (ANSI)
+ 0215 Internal Keyboard/Trackpad (ISO)
+ 0216 Internal Keyboard/Trackpad (JIS)
+ 0217 Internal Keyboard/Trackpad (ANSI)
+ 0218 Internal Keyboard/Trackpad (ISO)
+ 0219 Internal Keyboard/Trackpad (JIS)
+ 021a Internal Keyboard/Trackpad (ANSI)
+ 021b Internal Keyboard/Trackpad (ISO)
+ 021c Internal Keyboard/Trackpad (JIS)
+ 021d Aluminum Mini Keyboard (ANSI)
+ 021e Aluminum Mini Keyboard (ISO)
+ 021f Aluminum Mini Keyboard (JIS)
+ 0220 Aluminum Keyboard (ANSI)
+ 0221 Aluminum Keyboard (ISO)
+ 0222 Aluminum Keyboard (JIS)
+ 0223 Internal Keyboard/Trackpad (ANSI)
+ 0224 Internal Keyboard/Trackpad (ISO)
+ 0225 Internal Keyboard/Trackpad (JIS)
+ 0229 Internal Keyboard/Trackpad (ANSI)
+ 022a Internal Keyboard/Trackpad (MacBook Pro) (ISO)
+ 022b Internal Keyboard/Trackpad (MacBook Pro) (JIS)
+ 0230 Internal Keyboard/Trackpad (MacBook Pro 4,1) (ANSI)
+ 0231 Internal Keyboard/Trackpad (MacBook Pro 4,1) (ISO)
+ 0232 Internal Keyboard/Trackpad (MacBook Pro 4,1) (JIS)
+ 0236 Internal Keyboard/Trackpad (ANSI)
+ 0237 Internal Keyboard/Trackpad (ISO)
+ 0238 Internal Keyboard/Trackpad (JIS)
+ 023f Internal Keyboard/Trackpad (ANSI)
+ 0240 Internal Keyboard/Trackpad (ISO)
+ 0241 Internal Keyboard/Trackpad (JIS)
+ 0242 Internal Keyboard/Trackpad (ANSI)
+ 0243 Internal Keyboard/Trackpad (ISO)
+ 0244 Internal Keyboard/Trackpad (JIS)
+ 0245 Internal Keyboard/Trackpad (ANSI)
+ 0246 Internal Keyboard/Trackpad (ISO)
+ 0247 Internal Keyboard/Trackpad (JIS)
+ 024a Internal Keyboard/Trackpad (MacBook Air) (ISO)
+ 024d Internal Keyboard/Trackpad (MacBook Air) (ISO)
+ 0250 Aluminium Keyboard (ISO)
+ 0252 Internal Keyboard/Trackpad (ANSI)
+ 0253 Internal Keyboard/Trackpad (ISO)
+ 0254 Internal Keyboard/Trackpad (JIS)
+ 0259 Internal Keyboard/Trackpad
+ 0263 Apple Internal Keyboard / Trackpad (MacBook Retina)
+ 0267 Magic Keyboard A1644
+ 0269 Magic Mouse 2 (Lightning connector)
+ 0273 Internal Keyboard/Trackpad (ISO)
+ 0301 USB Mouse [Mitsumi, M4848]
+ 0302 Optical Mouse [Fujitsu]
+ 0304 Mighty Mouse [Mitsumi, M1152]
+ 0306 Optical USB Mouse [Fujitsu]
+ 030a Internal Trackpad
+ 030b Internal Trackpad
+ 030d Magic Mouse
+ 030e MC380Z/A [Magic Trackpad]
+ 1000 Bluetooth HCI MacBookPro (HID mode)
+ 1001 Keyboard Hub [ALPS]
+ 1002 Extended Keyboard Hub [Mitsumi]
+ 1003 Hub in Pro Keyboard [Mitsumi, A1048]
+ 1006 Hub in Aluminum Keyboard
+ 1008 Mini DisplayPort to Dual-Link DVI Adapter
+ 1101 Speakers
+ 1105 Audio in LED Cinema Display
+ 1107 Thunderbolt Display Audio
+ 1112 FaceTime HD Camera (Display)
+ 1201 3G iPod
+ 1202 iPod 2G
+ 1203 iPod 4.Gen Grayscale 40G
+ 1204 iPod [Photo]
+ 1205 iPod Mini 1.Gen/2.Gen
+ 1206 iPod '06'
+ 1207 iPod '07'
+ 1208 iPod '08'
+ 1209 iPod Video
+ 120a iPod Nano
+ 1223 iPod Classic/Nano 3.Gen (DFU mode)
+ 1224 iPod Nano 3.Gen (DFU mode)
+ 1225 iPod Nano 4.Gen (DFU mode)
+ 1227 Mobile Device (DFU Mode)
+ 1231 iPod Nano 5.Gen (DFU mode)
+ 1240 iPod Nano 2.Gen (DFU mode)
+ 1242 iPod Nano 3.Gen (WTF mode)
+ 1243 iPod Nano 4.Gen (WTF mode)
+ 1245 iPod Classic 3.Gen (WTF mode)
+ 1246 iPod Nano 5.Gen (WTF mode)
+ 1255 iPod Nano 4.Gen (DFU mode)
+ 1260 iPod Nano 2.Gen
+ 1261 iPod Classic
+ 1262 iPod Nano 3.Gen
+ 1263 iPod Nano 4.Gen
+ 1265 iPod Nano 5.Gen
+ 1266 iPod Nano 6.Gen
+ 1267 iPod Nano 7.Gen
+ 1281 Apple Mobile Device [Recovery Mode]
+ 1290 iPhone
+ 1291 iPod Touch 1.Gen
+ 1292 iPhone 3G
+ 1293 iPod Touch 2.Gen
+ 1294 iPhone 3GS
+ 1296 iPod Touch 3.Gen (8GB)
+ 1297 iPhone 4
+ 1299 iPod Touch 3.Gen
+ 129a iPad
+ 129c iPhone 4(CDMA)
+ 129e iPod Touch 4.Gen
+ 129f iPad 2
+ 12a0 iPhone 4S
+ 12a2 iPad 2 (3G; 64GB)
+ 12a3 iPad 2 (CDMA)
+ 12a4 iPad 3 (wifi)
+ 12a5 iPad 3 (CDMA)
+ 12a6 iPad 3 (3G, 16 GB)
+ 12a8 iPhone5/5C/5S/6
+ 12a9 iPad 2
+ 12aa iPod Touch 5.Gen [A1421]
+ 12ab iPad 4/Mini1
+ 1300 iPod Shuffle
+ 1301 iPod Shuffle 2.Gen
+ 1302 iPod Shuffle 3.Gen
+ 1303 iPod Shuffle 4.Gen
+ 1401 Modem
+ 1402 Ethernet Adapter [A1277]
+ 1500 SuperDrive [A1379]
+ 8005 OHCI Root Hub Simulation
+ 8006 EHCI Root Hub Simulation
+ 8007 XHCI Root Hub USB 2.0 Simulation
+ 8202 HCF V.90 Data/Fax Modem
+ 8203 Bluetooth HCI
+ 8204 Built-in Bluetooth 2.0+EDR HCI
+ 8205 Bluetooth HCI
+ 8206 Bluetooth HCI
+ 820a Bluetooth HID Keyboard
+ 820b Bluetooth HID Mouse
+ 820f Bluetooth HCI
+ 8213 Bluetooth Host Controller
+ 8215 Built-in Bluetooth 2.0+EDR HCI
+ 8216 Bluetooth USB Host Controller
+ 8217 Bluetooth USB Host Controller
+ 8218 Bluetooth Host Controller
+ 821a Bluetooth Host Controller
+ 821f Built-in Bluetooth 2.0+EDR HCI
+ 8240 Built-in IR Receiver
+ 8241 Built-in IR Receiver
+ 8242 Built-in IR Receiver
+ 8281 Bluetooth Host Controller
+ 8286 Bluetooth Host Controller
+ 828c Bluetooth Host Controller
+ 8290 Bluetooth Host Controller
+ 8300 Built-in iSight (no firmware loaded)
+ 8403 Internal Memory Card Reader
+ 8404 Internal Memory Card Reader
+ 8501 Built-in iSight [Micron]
+ 8502 Built-in iSight
+ 8505 Built-in iSight
+ 8507 Built-in iSight
+ 8508 iSight in LED Cinema Display
+ 8509 FaceTime HD Camera
+ 850a FaceTime Camera
+ 8510 FaceTime HD Camera (Built-in)
+ 911c Hub in A1082 [Cinema HD Display 23"]
+ 9127 Hub in Thunderbolt Display
+ 912f Hub in 30" Cinema Display
+ 9215 Studio Display 15"
+ 9217 Studio Display 17"
+ 9218 Cinema Display 23"
+ 9219 Cinema Display 20"
+ 921c A1082 [Cinema HD Display 23"]
+ 921e Cinema Display 24"
+ 9221 30" Cinema Display
+ 9226 LED Cinema Display
+ 9227 Thunderbolt Display
+ 9232 Cinema HD Display 30"
+ ffff Bluetooth in DFU mode - Driver
+05ad Y.C. Cable U.S.A., Inc.
+05ae Synopsys, Inc.
+05af Jing-Mold Enterprise Co., Ltd
+ 0806 HP SK806A Keyboard
+ 0809 Wireless Keyboard and Mouse
+ 0821 IDE to
+ 3062 Cordless Keyboard
+ 9167 KB 9151B - 678
+ 9267 KB 9251B - 678 Mouse
+05b0 Fountain Technologies, Inc.
+05b1 First International Computer, Inc.
+ 1389 Bluetooth Wireless Adapter
+05b4 LG Semicon Co., Ltd
+ 4857 M-Any DAH-210
+ 6001 HYUNDAI GDS30C6001 SSFDC / MMC I/F Controller
+05b5 Dialogic Corp.
+05b6 Proxima Corp.
+05b7 Medianix Semiconductor, Inc.
+05b8 Agiler, Inc.
+ 3002 Scroll Mouse
+ 3223 ISY Wireless Presenter
+05b9 Philips Research Laboratories
+05ba DigitalPersona, Inc.
+ 0007 Fingerprint Reader
+ 0008 Fingerprint Reader
+ 000a Fingerprint Reader
+05bb Grey Cell Systems
+05bc 3G Green Green Globe Co., Ltd
+ 0004 Trackball
+05bd RAFI GmbH & Co. KG
+05be Tyco Electronics (Raychem)
+05bf S & S Research
+05c0 Keil Software
+05c1 Kawasaki Microelectronics, Inc.
+05c2 Media Phonics (Suisse) S.A.
+05c5 Digi International, Inc.
+ 0002 AccelePort USB 2
+ 0004 AccelePort USB 4
+ 0008 AccelePort USB 8
+05c6 Qualcomm, Inc.
+ 0114 Select RW-200 CDMA Wireless Modem
+ 1000 Mass Storage Device
+ 3100 CDMA Wireless Modem/Phone
+ 3196 CDMA Wireless Modem
+ 3197 CDMA Wireless Modem/Phone
+ 6000 Siemens SG75
+ 6503 AnyData APE-540H
+ 6613 Onda H600/N501HS ZTE MF330
+ 6764 A0001 Phone [OnePlus One]
+ 9000 SIMCom SIM5218 modem
+ 9001 Gobi Wireless Modem
+ 9002 Gobi Wireless Modem
+ 9003 Quectel UC20
+ 9008 Gobi Wireless Modem (QDL mode)
+ 9018 Qualcomm HSUSB Device
+ 9025 Qualcomm HSUSB Device
+ 9201 Gobi Wireless Modem (QDL mode)
+ 9202 Gobi Wireless Modem
+ 9203 Gobi Wireless Modem
+ 9205 Gobi 2000
+ 9211 Acer Gobi Wireless Modem (QDL mode)
+ 9212 Acer Gobi Wireless Modem
+ 9214 Acer Gobi 2000 Wireless Modem (QDL mode)
+ 9215 Acer Gobi 2000 Wireless Modem
+ 9221 Gobi Wireless Modem (QDL mode)
+ 9222 Gobi Wireless Modem
+ 9224 Sony Gobi 2000 Wireless Modem (QDL mode)
+ 9225 Sony Gobi 2000 Wireless Modem
+ 9231 Gobi Wireless Modem (QDL mode)
+ 9234 Top Global Gobi 2000 Wireless Modem (QDL mode)
+ 9235 Top Global Gobi 2000 Wireless Modem
+ 9244 Samsung Gobi 2000 Wireless Modem (QDL mode)
+ 9245 Samsung Gobi 2000 Wireless Modem
+ 9264 Asus Gobi 2000 Wireless Modem (QDL mode)
+ 9265 Asus Gobi 2000 Wireless Modem
+ 9274 iRex Technologies Gobi 2000 Wireless Modem (QDL mode)
+ 9275 iRex Technologies Gobi 2000 Wireless Modem
+05c7 Qtronix Corp.
+ 0113 PC Line Mouse
+ 1001 Lynx Mouse
+ 2001 Keyboard
+ 2011 SCorpius Keyboard
+ 6001 Ten-Keypad
+05c8 Cheng Uei Precision Industry Co., Ltd (Foxlink)
+ 0103 FO13FF-65 PC-CAM
+ 010b Webcam (UVC)
+ 021a HP Webcam
+ 0318 Webcam
+ 0361 SunplusIT INC. HP Truevision HD Webcam
+ 036e Webcam
+ 0403 Webcam
+ 041b HP 2.0MP High Definition Webcam
+05c9 Semtech Corp.
+05ca Ricoh Co., Ltd
+ 0101 RDC-5300 Camera
+ 0325 Caplio GX (ptp)
+ 032d Caplio GX 8 (ptp)
+ 032f Caplio R3 (ptp)
+ 03a1 IS200e
+ 0403 Printing Support
+ 0405 Type 101
+ 0406 Type 102
+ 1803 V5 camera [R5U870]
+ 1810 Pavilion Webcam [R5U870]
+ 1812 Pavilion Webcam
+ 1814 HD Webcam
+ 1815 Dell Laptop Integrated Webcam
+ 1820 Integrated Webcam
+ 1830 Visual Communication Camera VGP-VCC2 [R5U870]
+ 1832 Visual Communication Camera VGP-VCC3 [R5U870]
+ 1833 Visual Communication Camera VGP-VCC2 [R5U870]
+ 1834 Visual Communication Camera VGP-VCC2 [R5U870]
+ 1835 Visual Communication Camera VGP-VCC5 [R5U870]
+ 1836 Visual Communication Camera VGP-VCC4 [R5U870]
+ 1837 Visual Communication Camera VGP-VCC4 [R5U870]
+ 1839 Visual Communication Camera VGP-VCC6 [R5U870]
+ 183a Visual Communication Camera VGP-VCC7 [R5U870]
+ 183b Visual Communication Camera VGP-VCC8 [R5U870]
+ 183d Sony Vaio Integrated Webcam
+ 183e Visual Communication Camera VGP-VCC9 [R5U870]
+ 1841 Fujitsu F01/ Lifebook U810 [R5U870]
+ 1870 Webcam 1000
+ 18b0 Sony Vaio Integrated Webcam
+ 18b1 Sony Vaio Integrated Webcam
+ 18b3 Sony Vaio Integrated Webcam
+ 18b5 Sony Vaio Integrated Webcam
+ 2201 RDC-7 Camera
+ 2202 Caplio RR30
+ 2203 Caplio 300G
+ 2204 Caplio G3
+ 2205 Caplio RR30 / Medion MD 6126 Camera
+ 2206 Konica DG-3Z
+ 2207 Caplio Pro G3
+ 2208 Caplio G4
+ 2209 Caplio 400G wide
+ 220a KONICA MINOLTA DG-4Wide
+ 220b Caplio RX
+ 220c Caplio GX
+ 220d Caplio R1/RZ1
+ 220e Sea & Sea 5000G
+ 220f Rollei dr5 / Rollei dr5 (PTP mode)
+ 2211 Caplio R1S
+ 2212 Caplio R1v Camera
+ 2213 Caplio R2
+ 2214 Caplio GX 8
+ 2215 DSC 725
+ 2216 Caplio R3
+ 2222 RDC-i500
+05cb PowerVision Technologies, Inc.
+ 1483 PV8630 interface (scanners, webcams)
+05cc ELSA AG
+ 2100 MicroLink ISDN Office
+ 2219 MicroLink ISDN
+ 2265 MicroLink 56k
+ 2267 MicroLink 56k (V.250)
+ 2280 MicroLink 56k Fun
+ 3000 Micolink USB2Ethernet [pegasus]
+ 3100 AirLancer USB-11
+ 3363 MicroLink ADSL Fun
+05cd Silicom, Ltd
+05ce sci-worx GmbH
+05cf Sung Forn Co., Ltd
+05d0 GE Medical Systems Lunar
+05d1 Brainboxes, Ltd
+ 0003 Bluetooth Adapter BL-554
+05d2 Wave Systems Corp.
+05d3 Tohoku Ricoh Co., Ltd
+05d5 Super Gate Technology Co., Ltd
+05d6 Philips Semiconductors, CICT
+05d7 Thomas & Betts Corp.
+ 0099 10Mbps Ethernet [klsi]
+05d8 Ultima Electronics Corp.
+ 4001 Artec Ultima 2000
+ 4002 Artec Ultima 2000 (GT6801 based)/Lifetec LT9385/ScanMagic 1200 UB Plus Scanner
+ 4003 Artec E+ 48U
+ 4004 Artec E+ Pro
+ 4005 MEM48U
+ 4006 TRUST EASY WEBSCAN 19200
+ 4007 TRUST 240H EASY WEBSCAN GOLD
+ 4008 Trust Easy Webscan 19200
+ 4009 Umax Astraslim
+ 4013 IT Scan 1200
+ 8105 Artec T1 USB TVBOX (cold)
+ 8106 Artec T1 USB TVBOX (warm)
+ 8107 Artec T1 USB TVBOX with AN2235 (cold)
+ 8108 Artec T1 USB TVBOX with AN2235 (warm)
+ 8109 Artec T1 USB2.0 TVBOX (cold
+05d9 Axiohm Transaction Solutions
+ a225 A225 Printer
+ a758 A758 Printer
+ a794 A794 Printer
+05da Microtek International, Inc.
+ 0091 ScanMaker X6u
+ 0093 ScanMaker V6USL
+ 0094 Phantom 336CX/C3
+ 0099 ScanMaker X6/X6U
+ 009a Phantom C6
+ 00a0 Phantom 336CX/C3 (#2)
+ 00a3 ScanMaker V6USL
+ 00ac ScanMaker V6UL
+ 00b6 ScanMaker V6UPL
+ 00ef ScanMaker V6UPL
+ 1006 Jenoptik JD350 entrance
+ 1011 NHJ Che-ez! Kiss Digital Camera
+ 1018 Digital Dream Enigma 1.3
+ 1020 Digital Dream l'espion xtra
+ 1025 Take-it Still Camera Device
+ 1026 Take-it
+ 1043 Take-It 1300 DSC Bulk Driver
+ 1045 Take-it D1
+ 1047 Take-it Camera Composite Device
+ 1048 Take-it Q3
+ 1049 3M Still Camera Device
+ 1051 Camcorder Series
+ 1052 Mass Storage Device
+ 1053 Take-it DV Composite Device
+ 1054 Mass Storage Device
+ 1055 Digital Camera Series(536)
+ 1056 Mass Storage Device
+ 1057 Take-it DSC Camera Device(536)
+ 1058 Mass Storage Device
+ 1059 Camcorder DSC Series
+ 1060 Microtek Take-it MV500
+ 2007 ArtixScan DI 1210
+ 200c 1394_USB2 Scanner
+ 200e ArtixScan DI 810
+ 2017 UF ICE Scanner
+ 201c 4800 Scanner
+ 201d ArtixScan DI 1610
+ 201f 4800 Scanner-ICE
+ 202e ArtixScan DI 2020
+ 208b ScanMaker 6800
+ 208f ArtixScan DI 2010
+ 209e ScanMaker 4700LP
+ 20a7 ScanMaker 5600
+ 20b0 ScanMaker X12USL
+ 20b1 ScanMaker 8700
+ 20b4 ScanMaker 4700
+ 20bd ScanMaker 5700
+ 20c9 ScanMaker 6700
+ 20d2 Microtek ArtixScan 1800f
+ 20d6 PS4000
+ 20de ScanMaker 9800XL
+ 20e0 ScanMaker 9700XL
+ 20ed ScanMaker 4700
+ 20ee Micortek ScanMaker X12USL
+ 2838 RT2832U
+ 3008 Scanner
+ 300a 4800 ICE Scanner
+ 300b 4800 Scanner
+ 300f MiniScan C5
+ 3020 4800dpi Scanner
+ 3021 1200dpi Scanner
+ 3022 Scanner 4800dpi
+ 3023 USB1200II Scanner
+ 30c1 USB600 Scanner
+ 30ce ScanMaker 3800
+ 30cf ScanMaker 4800
+ 30d4 USB1200 Scanner
+ 30d8 Scanner
+ 30d9 USB2400 Scanner
+ 30e4 ScanMaker 4100
+ 30e5 USB3200 Scanner
+ 30e6 ScanMaker i320
+ 40b3 ScanMaker 3600
+ 40b8 ScanMaker 3700
+ 40c7 ScanMaker 4600
+ 40ca ScanMaker 3600
+ 40cb ScanMaker 3700
+ 40dd ScanMaker 3750i
+ 40ff ScanMaker 3600
+ 5003 Goya
+ 5013 3200 Scanner
+ 6072 XT-3500 A4 HD Scanner
+ 80a3 ScanMaker V6USL (#2)
+ 80ac ScanMaker V6UL/SpicyU
+05db Sun Corp. (Suntac?)
+ 0003 SUNTAC U-Cable type D2
+ 0005 SUNTAC U-Cable type P1
+ 0009 SUNTAC Slipper U
+ 000a SUNTAC Ir-Trinity
+ 000b SUNTAC U-Cable type A3
+ 0011 SUNTAC U-Cable type A4
+05dc Lexar Media, Inc.
+ 0001 jumpSHOT CompactFlash Reader
+ 0002 JumpShot
+ 0003 JumpShot
+ 0080 Jumpdrive Secure 64MB
+ 0081 RBC Compact Flash Drive
+ 00a7 JumpDrive Impact
+ 0100 JumpDrive PRO
+ 0200 JumpDrive 2.0 Pro
+ 0300 Jumpdrive Geysr
+ 0301 JumpDrive Classic
+ 0302 JD Micro
+ 0303 JD Micro Pro
+ 0304 JD Secure II
+ 0310 JumpDrive
+ 0311 JumpDrive Classic
+ 0312 JD Micro
+ 0313 JD Micro Pro
+ 0320 JumpDrive
+ 0321 JD Micro
+ 0322 JD Micro Pro
+ 0323 UFC
+ 0330 JumpDrive Expression
+ 0340 JumpDrive TAD
+ 0350 Express Card
+ 0400 UFDC
+ 0401 UFDC
+ 0403 Locked B Device
+ 0405 Locked C Device
+ 0407 Locked D Device
+ 0409 Locked E Device
+ 040b Locked F Device
+ 040d Locked G Device
+ 040f Locked H Device
+ 0410 JumpDrive
+ 0411 JumpDrive
+ 0413 Locked J Device
+ 0415 Locked K Device
+ 0417 Locked L Device
+ 0419 Locked M Device
+ 041b Locked N Device
+ 041d Locked O Device
+ 041f Locked P Device
+ 0420 JumpDrive
+ 0421 JumpDrive
+ 0423 Locked R Device
+ 0425 Locked S Device
+ 0427 Locked T Device
+ 0429 Locked U Device
+ 042b Locked V Device
+ 042d Locked W Device
+ 042f Locked X Device
+ 0431 Locked Y Device
+ 0433 Locked Z Device
+ 4d02 MP3 Player
+ 4d12 MP3 Player
+ 4d30 MP3 Player
+ a209 JumpDrive S70
+ a300 JumpDrive2
+ a400 JumpDrive trade; Pro 40-501
+ a410 JumpDrive 128MB/256MB
+ a411 JumpDrive Traveler
+ a420 JumpDrive Pro
+ a421 JumpDrive Pro II
+ a422 JumpDrive Micro Pro
+ a430 JumpDrive Secure
+ a431 JumpDrive Secure II
+ a432 JumpDrive Classic
+ a440 JumpDrive Lightning
+ a450 JumpDrive TouchGuard
+ a460 JD Mercury
+ a501 JumpDrive Classic
+ a510 JumpDrive Sport
+ a530 JumpDrive Expression
+ a531 JumpDrive Secure II
+ a560 JumpDrive FireFly
+ a701 JumpDrive FireFly
+ a731 JumpDrive FireFly
+ a762 JumpDrive FireFly
+ a768 JumpDrive Retrax
+ a790 JumpDrive 2GB
+ a811 16GB Gizmo!
+ a813 16gB flash thumb drive
+ a815 JumpDrive V10
+ a833 JumpDrive S23 64GB
+ b002 USB CF Reader
+ b018 Multi-Card Reader
+ b047 SDHC Reader [RW047-7000]
+ b051 microSD RDR UHS-I Card Reader [LRWM03U-7000]
+ ba02 Workflow CFR1
+ ba0a Workflow DD512
+ c753 JumpDrive TwistTurn
+ c75c JumpDrive V10
+05dd Delta Electronics, Inc.
+ ff31 AWU-120
+ ff32 FriendlyNET AeroLAN AL2011
+ ff35 PCW 100 - Wireless 802.11b Adapter
+ ff91 2Wire PC Port Phoneline 10Mbps Adapter
+05df Silicon Vision, Inc.
+05e0 Symbol Technologies
+ 0700 Bar Code Scanner (CS1504)
+ 0800 Spectrum24 Wireless LAN Adapter
+ 1200 Bar Code Scanner
+ 1701 Bar Code Scanner (CDC)
+ 1900 SNAPI Imaging Device
+ 2000 MC3090 Rugged Mobile Computer
+ 200d MC70 Rugged Mobile Computer
+05e1 Syntek Semiconductor Co., Ltd
+ 0100 802.11g + Bluetooth Wireless Adapter
+ 0408 STK1160 Video Capture Device
+ 0500 DC-112X Webcam
+ 0501 DC-1125 Webcam
+ 0890 STK011 Camera
+ 0892 STK013 Camera
+ 0895 STK016 Camera
+ 0896 STK017 Camera
+ 2010 ARCTIC Sound P261 Headphones
+05e2 ElecVision, Inc.
+05e3 Genesys Logic, Inc.
+ 000a Keyboard with PS/2 Port
+ 000b Mouse
+ 0100 Nintendo Game Boy Advance SP
+ 0120 Pacific Image Electronics PrimeFilm 1800u slide/negative scanner
+ 0131 CF/SM Reader/Writer
+ 0142 Multiple Slides Scanner-3600
+ 0143 Multiple Frames Film Scanner-36series
+ 0145 Reflecta CrystalScan 7200 Photo-Scanner
+ 0180 Plustek Scanner
+ 0182 Wize Media 1000
+ 0189 ScanJet 4600 series
+ 018a Xerox 6400
+ 0300 GLUSB98PT Parallel Port
+ 0301 USB2LPT Cable Release2
+ 0406 Hub
+ 0501 GL620USB Host-Host interface
+ 0502 GL620USB-A GeneLink USB-USB Bridge
+ 0503 Webcam
+ 0504 HID Keyboard Filter
+ 0604 USB 1.1 Hub
+ 0605 USB 2.0 Hub
+ 0606 USB 2.0 Hub / D-Link DUB-H4 USB 2.0 Hub
+ 0607 Logitech G110 Hub
+ 0608 Hub
+ 0610 4-port hub
+ 0612 Hub
+ 0616 hub
+ 0660 USB 2.0 Hub
+ 0700 SIIG US2256 CompactFlash Card Reader
+ 0701 USB 2.0 IDE Adapter
+ 0702 USB 2.0 IDE Adapter [GL811E]
+ 0703 Card Reader
+ 0704 Card Reader
+ 0705 Card Reader
+ 0706 Card Reader
+ 0707 Card Reader
+ 0708 Card Reader
+ 0709 Card Reader
+ 070a Pen Flash
+ 070b DMHS1B Rev 3 DFU Adapter
+ 070e USB 2.0 Card Reader
+ 070f Pen Flash
+ 0710 USB 2.0 33-in-1 Card Reader
+ 0711 Card Reader
+ 0712 Delkin Mass Storage Device
+ 0715 USB 2.0 microSD Reader
+ 0716 USB 2.0 Multislot Card Reader/Writer
+ 0717 All-in-1 Card Reader
+ 0718 IDE/SATA Adapter
+ 0719 SATA adapter
+ 0722 SD/MMC card reader
+ 0723 GL827L SD/MMC/MS Flash Card Reader
+ 0726 SD Card Reader
+ 0727 microSD Reader/Writer
+ 0731 GL3310 SATA 3Gb/s Bridge Controller
+ 0732 All-in-One Cardreader
+ 0736 microSD Reader/Writer
+ 0738 Card reader
+ 0741 microSD Card Reader
+ 0743 SDXC and microSDXC CardReader
+ 0745 Logilink CR0012
+ 0748 All-in-One Cardreader
+ 0751 microSD Card Reader
+ 0760 USB 2.0 Card Reader/Writer
+ 0761 Genesys Mass Storage Device
+ 0780 USBFS DFU Adapter
+ 07a0 Pen Flash
+ 0880 Wasp (SL-6612)
+ 0927 Card Reader
+ 1205 Afilias Optical Mouse H3003 / Trust Optical USB MultiColour Mouse MI-2330
+ a700 Pen Flash
+ f102 VX7012 TV Box
+ f103 VX7012 TV Box
+ f104 VX7012 TV Box
+ fd21 3M TL20 Temperature Logger
+ fe00 Razer Mouse
+05e4 Red Wing Corp.
+05e5 Fuji Electric Co., Ltd
+05e6 Keithley Instruments
+05e8 ICC, Inc.
+05e9 Kawasaki LSI
+ 0008 KL5KUSB101B Ethernet [klsi]
+ 0009 Sony 10Mbps Ethernet [pegasus]
+ 000c USB-to-RS-232
+ 000d USB-to-RS-232
+ 0014 RS-232 J104
+ 0040 Ethernet Adapter
+ 2008 Ethernet Adapter
+05eb FFC, Ltd
+05ec COM21, Inc.
+05ee Cytechinfo Inc.
+05ef AVB, Inc. [anko?]
+ 020a Top Shot Pegasus Joystick
+ 8884 Mag Turbo Force Wheel
+ 8888 Top Shot Force Feedback Racing Wheel
+05f0 Canopus Co., Ltd
+ 0101 DA-Port DAC
+05f1 Compass Communications
+05f2 Dexin Corp., Ltd
+ 0010 AQ Mouse
+05f3 PI Engineering, Inc.
+ 0007 Kinesis Advantage PRO MPC/USB Keyboard
+ 0081 Kinesis Integrated Hub
+ 00ff VEC Footpedal
+ 0203 Y-mouse Keyboard & Mouse Adapter
+ 020b PS2 Adapter
+ 0232 X-Keys Switch Interface, Programming Mode
+ 0261 X-Keys Switch Interface, SPLAT Mode
+ 0264 X-Keys Switch Interface, Composite Mode
+05f5 Unixtar Technology, Inc.
+05f6 AOC International
+05f7 RFC Distribution(s) PTE, Ltd
+05f9 PSC Scanning, Inc.
+ 1104 Magellan 2200VS
+ 1206 Gryphon series (OEM mode)
+ 2202 Point of Sale Handheld Scanner
+ 2206 Gryphon series (keyboard emulation mode)
+ 220c Datalogic Gryphon GD4430
+ 2601 Datalogic Magellan 1000i Barcode Scanner
+ 2602 Datalogic Magellan 1100i Barcode Scanner
+ 4204 Gryphon series (RS-232 emulation mode)
+ 5204 Datalogic Gryphon GFS4170 (config mode)
+05fa Siemens Telecommunications Systems, Ltd
+ 3301 Keyboard with PS/2 Mouse Port
+ 3302 Keyboard
+ 3303 Keyboard with PS/2 Mouse Port
+05fc Harman
+ 0001 Soundcraft Si Multi Digital Card
+ 7849 Harman/Kardon SoundSticks
+05fd InterAct, Inc.
+ 0239 SV-239 HammerHead Digital
+ 0251 Raider Pro
+ 0253 ProPad 8 Digital
+ 0286 SV-286 Cyclone Digital
+ 107a PowerPad Pro X-Box pad
+ 262a 3dfx HammerHead FX
+ 262f HammerHead Fx
+ daae Game Shark
+ dbae Datel XBoxMC
+05fe Chic Technology Corp.
+ 0001 Mouse
+ 0003 Cypress USB Mouse
+ 0005 Viewmaster 4D Browser Mouse
+ 0007 Twinhead Mouse
+ 0009 Inland Pro 4500/5000 Mouse
+ 0011 Browser Mouse
+ 0014 Gamepad
+ 1010 Optical Wireless
+ 2001 Microsoft Wireless Receiver 700
+05ff LeCroy Corp.
+0600 Barco Display Systems
+0601 Jazz Hipster Corp.
+ 0003 Internet Security Co., Ltd. SecureKey
+0602 Vista Imaging, Inc.
+ 1001 ViCam Webcam
+0603 Novatek Microelectronics Corp.
+ 00f1 Keyboard (Labtec Ultra Flat Keyboard)
+ 00f2 Keyboard (Labtec Ultra Flat Keyboard)
+ 6871 Mouse
+0604 Jean Co., Ltd
+0605 Anchor C&C Co., Ltd
+0606 Royal Information Electronics Co., Ltd
+0607 Bridge Information Co., Ltd
+0608 Genrad Ads
+0609 SMK Manufacturing, Inc.
+ 031d eHome Infrared Receiver
+ 0322 eHome Infrared Receiver
+ 0334 eHome Infrared Receiver
+ ff12 SMK Bluetooth Device
+060a Worthington Data Solutions, Inc.
+060b Solid Year
+ 0001 MacAlly Keyboard
+ 0230 KSK-8003 UX Keyboard
+ 0540 DeltaCo TB-106U Keyboard
+ 1006 Japanese Keyboard - 260U
+ 2101 Keyboard
+ 2231 KSK-6001 UELX Keyboard
+ 2270 Gigabyte K8100 Aivia Gaming Keyboard
+ 5253 Thermaltake MEKA G-Unit Gaming Keyboard
+ 5811 ACK-571U Wireless Keyboard
+ 5903 Japanese Keyboard - 595U
+ 6001 SolidTek USB 2p HUB
+ 6002 SolidTek USB Keyboard
+ 6003 Japanese Keyboard - 600HM
+ 6231 Thermaltake eSPORTS Meka Keyboard
+ 8007 P-W1G1F12 VER:1 [Macally MegaCam]
+ a001 Maxwell Compact Pc PM3
+060c EEH Datalink GmbH
+060d Auctor Corp.
+060e Transmonde Technologies, Inc.
+060f Joinsoon Electronics Mfg. Co., Ltd
+0610 Costar Electronics, Inc.
+0611 Totoku Electric Co., Ltd
+0613 TransAct Technologies, Inc.
+0614 Bio-Rad Laboratories
+0615 Quabbin Wire & Cable Co., Inc.
+0616 Future Techno Designs PVT, Ltd
+0617 Swiss Federal Insitute of Technology
+ 000a Thymio-II
+ 000c Thymio-II Wireless
+0618 MacAlly
+ 0101 Mouse
+0619 Seiko Instruments, Inc.
+ 0101 SLP-100 Driver
+ 0102 SLP-200 Driver
+ 0103 SLP-100N Driver
+ 0104 SLP-200N Driver
+ 0105 SLP-240 Driver
+ 0501 SLP-440 Driver
+ 0502 SLP-450 Driver
+061a Veridicom International, Inc.
+ 0110 5thSense Fingerprint Sensor
+ 0200 FPS200 Fingerprint Sensor
+ 8200 VKI-A Fingerprint Sensor/Flash Storage (dumb)
+ 9200 VKI-B Fingerprint Sensor/Flash Storage (smart)
+061b Promptus Communications, Inc.
+061c Act Labs, Ltd
+061d Quatech, Inc.
+ c020 SSU-100
+061e Nissei Electric Co.
+ 0001 nissei 128DE-USB -
+ 0010 nissei 128DE-PNA -
+0620 Alaris, Inc.
+ 0004 QuickVideo weeCam
+ 0007 QuickVideo weeCam
+ 000a QuickVideo weeCam
+ 000b QuickVideo weeCam
+0621 ODU-Steckverbindungssysteme GmbH & Co. KG
+0622 Iotech, Inc.
+0623 Littelfuse, Inc.
+0624 Avocent Corp.
+ 0248 Virtual Hub
+ 0249 Virtual Keyboard/Mouse
+ 0251 Virtual Mass Storage
+ 0294 Dell 03R874 KVM dongle
+ 0402 Cisco Virtual Keyboard and Mouse
+ 0403 Cisco Virtual Mass Storage
+0625 TiMedia Technology Co., Ltd
+0626 Nippon Systems Development Co., Ltd
+0627 Adomax Technology Co., Ltd
+0628 Tasking Software, Inc.
+0629 Zida Technologies, Ltd
+062a Creative Labs
+ 0000 Optical mouse
+ 0001 Notebook Optical Mouse
+ 0102 Wireless Keyboard/Mouse Combo [MK1152WC]
+ 0201 Defender Office Keyboard (K7310) S Zodiak KM-9010
+ 0252 Emerge Uni-retractable Laser Mouse
+ 2410 Wireless PS3 gamepad
+ 3286 Nano Receiver [Sandstrom Laser Mouse SMWLL11]
+ 4101 Wireless Keyboard/Mouse
+ 6301 Trust Wireless Optical Mouse MI-4150K
+ 9003 VoIP Conference Hub (A16GH)
+ 9004 USR9602 USB Internet Mini Phone
+062b Greatlink Electronics Taiwan, Ltd
+062c Institute for Information Industry
+062d Taiwan Tai-Hao Enterprises Co., Ltd
+062e Mainsuper Enterprises Co., Ltd
+062f Sin Sheng Terminal & Machine, Inc.
+0631 JUJO Electronics Corp.
+0633 Cyrix Corp.
+0634 Micron Technology, Inc.
+ 0655 Embedded Mass Storage Drive [RealSSD]
+0635 Methode Electronics, Inc.
+0636 Sierra Imaging, Inc.
+ 0003 Vivicam 35Xx
+0638 Avision, Inc.
+ 0268 iVina 1200U Scanner
+ 026a Minolta Dimage Scan Dual II AF-2820U (2886)
+ 0a10 iVina FB1600/UMAX Astra 4500
+ 0a13 AV600U
+ 0a15 Konica Minolta SC-110
+ 0a16 Konica Minolta SC-215
+ 0a30 UMAX Astra 6700 Scanner
+ 0a41 Avision AM3000/MF3000 Series
+ 0f01 fi-4010CU
+# typo?
+ 4004 Minolta Dimage Scan Elite II AF-2920 (2888)
+0639 Chrontel, Inc.
+063a Techwin Corp.
+063b Taugagreining HF
+063c Yamaichi Electronics Co., Ltd (Sakura)
+063d Fong Kai Industrial Co., Ltd
+063e RealMedia Technology, Inc.
+063f New Technology Cable, Ltd
+0640 Hitex Development Tools
+ 0026 LPC-Stick
+0641 Woods Industries, Inc.
+0642 VIA Medical Corp.
+0644 TEAC Corp.
+ 0000 Floppy
+ 0200 All-In-One Multi-Card Reader CA200/B/S
+ 1000 CD-ROM Drive
+ 800d TASCAM Portastudio DP-01FX
+ 800e TASCAM US-122L
+ 801d TASCAM DR-100
+ 8021 TASCAM US-122mkII
+ d001 CD-R/RW Unit
+ d002 CD-R/RW Unit
+ d010 CD-RW/DVD Unit
+0645 Who? Vision Systems, Inc.
+0646 UMAX
+0647 Acton Research Corp.
+ 0100 ARC SpectraPro UV/VIS/IR Monochromator/Spectrograph
+ 0101 ARC AM-VM Mono Airpath/Vacuum Monochromator/Spectrograph
+ 0102 ARC Inspectrum Mono
+ 0103 ARC Filterwheel
+ 03e9 Inspectrum 128x1024 F VIS Spectrograph
+ 03ea Inspectrum 256x1024 F VIS Spectrograph
+ 03eb Inspectrum 128x1024 B VIS Spectrograph
+ 03ec Inspectrum 256x1024 B VIS Spectrograph
+0648 Inside Out Networks
+0649 Weli Science Co., Ltd
+064b Analog Devices, Inc. (White Mountain DSP)
+ 0165 Blackfin 535 [ADZS HPUSB ICE]
+064c Ji-Haw Industrial Co., Ltd
+064d TriTech Microelectronics, Ltd
+064e Suyin Corp.
+ 2100 Sony Visual Communication Camera
+ 9700 Asus Integrated Webcam
+ a100 Acer OrbiCam
+ a101 Acer CrystalEye Webcam
+ a102 Acer/Lenovo Webcam [CN0316]
+ a103 Acer/HP Integrated Webcam [CN0314]
+ a110 HP Webcam
+ a114 Lemote Webcam
+ a116 UVC 1.3MPixel WebCam
+ a136 Asus Integrated Webcam [CN031B]
+ a219 1.3M WebCam (notebook emachines E730, Acer sub-brand)
+ c107 HP webcam [dv6-1190en]
+ c335 HP TrueVision HD
+ d101 Acer CrystalEye Webcam
+ d213 UVC HD Webcam
+ d217 HP TrueVision HD
+ e201 Lenovo Integrated Webcam
+ e203 Lenovo Integrated Webcam
+ e258 HP TrueVision HD Integrated Webcam
+ e263 HP TrueVision HD Integrated Webcam
+ f102 Lenovo Integrated Webcam [R5U877]
+ f103 Lenovo Integrated Webcam [R5U877]
+ f209 HP Webcam
+ f300 UVC 0.3M Webcam
+064f WIBU-Systems AG
+ 03e9 CmStick (article no. 1001)
+ 03f2 CmStick/M (article no. 1010)
+ 03f3 CmStick/M (article no. 1011)
+ 0bd7 BOX/U
+ 0bd8 BOX/RU
+0650 Dynapro Systems
+0651 Likom Technology Sdn. Bhd.
+0652 Stargate Solutions, Inc.
+0653 CNF, Inc.
+0654 Granite Microsystems, Inc.
+ 0005 Device Bay Controller
+ 0006 Hub
+ 0007 Device Bay Controller
+ 0016 Hub
+0655 Space Shuttle Hi-Tech Co., Ltd
+0656 Glory Mark Electronic, Ltd
+0657 Tekcon Electronics Corp.
+0658 Sigma Designs, Inc.
+0659 Aethra
+065a Optoelectronics Co., Ltd
+ 0001 Opticon OPR-2001 / NLV-1001 (keyboard mode)
+ 0009 NLV-1001 (serial mode) / OPN-2001 [Opticon]
+065b Tracewell Systems
+065e Silicon Graphics
+065f Good Way Technology Co., Ltd & GWC technology Inc.
+0660 TSAY-E (BVI) International, Inc.
+0661 Hamamatsu Photonics K.K.
+0662 Kansai Electric Co., Ltd
+0663 Topmax Electronic Co., Ltd
+ 0103 CobraPad
+0664 ET&T Technology Co., Ltd.
+ 0301 Groovy Technology Corp. GTouch Touch Screen
+ 0302 Groovy Technology Corp. GTouch Touch Screen
+ 0303 Groovy Technology Corp. GTouch Touch Screen
+ 0304 Groovy Technology Corp. GTouch Touch Screen
+ 0305 Groovy Technology Corp. GTouch Touch Screen
+ 0306 Groovy Technology Corp. GTouch Touch Screen
+ 0307 Groovy Technology Corp. GTouch Touch Screen
+ 0309 Groovy Technology Corp. GTouch Touch Screen
+0665 Cypress Semiconductor
+ 5161 USB to Serial
+0667 Aiwa Co., Ltd
+ 0fa1 TD-U8000 Tape Drive
+0668 WordWand
+0669 Oce' Printing Systems GmbH
+066a Total Technologies, Ltd
+066b Linksys, Inc.
+ 0105 SCM eUSB SmartMedia Card Reader
+ 010a Melco MCR-U2 SmartMedia / CompactFlash Reader
+ 200c USB10TX
+ 2202 USB10TX Ethernet [pegasus]
+ 2203 USB100TX Ethernet [pegasus]
+ 2204 USB100TX HomePNA Ethernet [pegasus]
+ 2206 USB Ethernet [pegasus]
+ 2207 HomeLink Phoneline 10M Network Adapter
+ 2211 WUSB11 802.11b Adapter
+ 2212 WUSB11v2.5 802.11b Adapter
+ 2213 WUSB12v1.1 802.11b Adapter
+ 2219 Instant Wireless Network Adapter
+ 400b USB10TX
+066d Entrega, Inc.
+066e Acer Semiconductor America, Inc.
+066f SigmaTel, Inc.
+ 003b MP3 Player
+ 003e MP3 Player
+ 003f MP3 Player
+ 0040 MP3 Player
+ 0041 MP3 Player
+ 0042 MP3 Player
+ 0043 MP3 Player
+ 004b A-Max PA11 MP3 Player
+ 3400 STMP3400 D-Major MP3 Player
+ 3410 STMP3410 D-Major MP3 Player
+ 3500 Player Recovery Device
+ 3780 STMP3780/i.MX23 SystemOnChip in RecoveryMode
+ 4200 STIr4200 IrDA Bridge
+ 4210 STIr4210 IrDA Bridge
+ 8000 MSCN MP3 Player
+ 8001 SigmaTel MSCN Audio Player
+ 8004 MSCNMMC MP3 Player
+ 8008 i-Bead 100 MP3 Player
+ 8020 MP3 Player
+ 8034 MP3 Player
+ 8036 MP3 Player
+ 8038 MP3 Player
+ 8056 MP3 Player
+ 8060 MP3 Player
+ 8066 MP3 Player
+ 807e MP3 Player
+ 8092 MP3 Player
+ 8096 MP3 Player
+ 809a MP3 Player
+ 80aa MP3 Player
+ 80ac MP3 Player
+ 80b8 MP3 Player
+ 80ba MP3 Player
+ 80bc MP3 Player
+ 80bf MP3 Player
+ 80c5 MP3 Player
+ 80c8 MP3 Player
+ 80ca MP3 Player
+ 80cc MP3 Player
+ 8104 MP3 Player
+ 8106 MP3 Player
+ 8108 MP3 Player
+ 810a MP3 Player
+ 810c MP3 Player
+ 8122 MP3 Player
+ 8124 MP3 Player
+ 8126 MP3 Player
+ 8128 MP3 Player
+ 8134 MP3 Player
+ 8136 MP3 Player
+ 8138 MP3 Player
+ 813a MP3 Player
+ 813e MP3 Player
+ 8140 MP3 Player
+ 8142 MP3 Player
+ 8144 MP3 Player
+ 8146 MP3 Player
+ 8148 MP3 Player
+ 814c MP3 Player
+ 8201 MP3 Player
+ 8202 Jens of Sweden / I-BEAD 150M/150H MP3 player
+ 8203 MP3 Player
+ 8204 MP3 Player
+ 8205 MP3 Player
+ 8206 Digital MP3 Music Player
+ 8207 MP3 Player
+ 8208 MP3 Player
+ 8209 MP3 Player
+ 820a MP3 Player
+ 820b MP3 Player
+ 820c MP3 Player
+ 820d MP3 Player
+ 820e MP3 Player
+ 820f MP3 Player
+ 8210 MP3 Player
+ 8211 MP3 Player
+ 8212 MP3 Player
+ 8213 MP3 Player
+ 8214 MP3 Player
+ 8215 MP3 Player
+ 8216 MP3 Player
+ 8217 MP3 Player
+ 8218 MP3 Player
+ 8219 MP3 Player
+ 821a MP3 Player
+ 821b MP3 Player
+ 821c MP3 Player
+ 821d MP3 Player
+ 821e MP3 Player
+ 821f MP3 Player
+ 8220 MP3 Player
+ 8221 MP3 Player
+ 8222 MP3 Player
+ 8223 MP3 Player
+ 8224 MP3 Player
+ 8225 MP3 Player
+ 8226 MP3 Player
+ 8227 MP3 Player
+ 8228 MP3 Player
+ 8229 MP3 Player
+ 8230 MP3 Player
+ 829c MP3 Player
+ 82e0 MP3 Player
+ 8320 TrekStor i.Beat fun
+ 835d MP3 Player
+ 9000 MP3 Player
+ 9001 MP3 Player
+ 9002 MP3 Player
+0670 Sequel Imaging
+ 0001 Calibrator
+ 0005 Enable Cable
+0672 Labtec, Inc.
+ 1041 LCS1040 Speaker System
+ 5000 SpaceBall 4000 FLX
+0673 HCL
+ 5000 Keyboard
+0674 Key Mouse Electronic Enterprise Co., Ltd
+0675 DrayTek Corp.
+ 0110 Vigor 128 ISDN TA
+ 0530 Vigor530 IEEE 802.11G Adapter (ISL3880+NET2280)
+ 0550 Vigor550
+ 1688 miniVigor 128 ISDN TA [HFC-S]
+ 6694 miniVigor 128 ISDN TA
+0676 Teles AG
+0677 Aiwa Co., Ltd
+ 07d5 TM-ED1285(USB)
+ 0fa1 TD-U8000 Tape Drive
+0678 ACard Technology Corp.
+067b Prolific Technology, Inc.
+ 0000 PL2301 USB-USB Bridge
+ 0001 PL2302 USB-USB Bridge
+ 0307 Motorola Serial Adapter
+ 04bb PL2303 Serial (IODATA USB-RSAQ2)
+ 0600 IDE Bridge
+ 0610 Onext EG210U MODEM
+ 0611 AlDiga AL-11U Quad-band GSM/GPRS/EDGE modem
+ 2303 PL2303 Serial Port
+ 2305 PL2305 Parallel Port
+ 2306 Raylink Bridge Controller
+ 2307 PL2307 USB-ATAPI4 Bridge
+ 2313 FITEL PHS U Cable Adaptor
+ 2315 Flash Disk Embedded Hub
+ 2316 Flash Disk Security Device
+ 2317 Mass Storage Device
+ 2501 PL2501 USB-USB Bridge (USB 2.0)
+ 2506 Kaser 8gB micro hard drive
+ 2507 PL2507 Hi-speed USB to IDE bridge controller
+ 2515 Flash Disk Embedded Hub
+ 2517 Flash Disk Mass Storage Device
+ 2528 Storage device (8gB thumb drive)
+ 25a1 PL25A1 Host-Host Bridge
+ 2773 PL2773 SATAII bridge controller
+ 3400 Hi-Speed Flash Disk with TruePrint AES3400
+ 3500 Hi-Speed Flash Disk with TruePrint AES3500
+ 3507 PL3507 ATAPI6 Bridge
+ aaa0 Prolific Pharos
+ aaa2 PL2303 Serial Adapter (IODATA USB-RSAQ3)
+ aaa3 PL2303x Serial Adapter
+067c Efficient Networks, Inc.
+ 1001 Siemens SpeedStream 100MBps Ethernet
+ 1022 Siemens SpeedStream 1022 802.11b Adapter
+ 1023 SpeedStream Wireless
+ 4020 SpeedStream 4020 ATM/ADSL Installer
+ 4031 Efficient ADSL Modem
+ 4032 SpeedStream 4031 ATM/ADSL Installer
+ 4033 SpeedStream 4031 ATM/ADSL Installer
+ 4060 Alcatel Speedstream 4060 ADSL Modem
+ 4062 Efficient Networks 4060 Loader
+ 5667 Efficient Networks Virtual Bus for ADSL Modem
+ c031 SpeedStream 4031 ATM/ADSL Installer
+ c032 SpeedStream 4031 ATM/ADSL Installer
+ c033 SpeedStream 4031 ATM/ADSL Installer
+ c060 SpeedStream 4060 Miniport ATM/ADSL Adapter
+ d667 Efficient Networks Virtual Bus for ADSL Modem
+ e240 Speedstream Ethernet Adapter E240
+ e540 Speedstream Ethernet Adapter E240
+067d Hohner Corp.
+067e Intermec Technologies Corp.
+ 0801 HID Keyboard, Barcode scanner
+ 0803 VCP, Barcode scanner
+ 0805 VCP + UVC, Barcode scanner
+ 1001 Mobile Computer
+067f Virata, Ltd
+ 4552 DSL-200 ADSL Modem
+ 6542 DSL Modem
+ 6549 DSL Modem
+ 7541 DSL Modem
+0680 Realtek Semiconductor Corp., CPP Div. (Avance Logic)
+ 0002 Arowana Optical Wheel Mouse MSOP-01
+0681 Siemens Information and Communication Products
+ 0001 Dect Base
+ 0002 Gigaset 3075 Passive ISDN
+ 0005 ID-Mouse with Fingerprint Reader
+ 0012 I-Gate 802.11b Adapter
+ 001b WLL013
+ 001d Hipath 1000
+ 0022 Gigaset SX353 ISDN
+ 0026 DECT Data - Gigaset M34
+ 002b A-100-I ADSL Modem
+ 002e ADSL Router_S-141
+ 0034 GSM module MC35/ES75 USB Modem
+ 3c06 54g USB Network Adapter
+0682 Victor Company of Japan, Ltd
+0684 Actiontec Electronics, Inc.
+0685 ZD Incorporated
+ 7000 HSDPA Modem
+0686 Minolta Co., Ltd
+ 2001 PagePro 4110W
+ 2004 PagePro 1200W
+ 2005 Magicolor 2300 DL
+ 3001 PagePro 4100
+ 3005 PagePro 1250E
+ 3006 PagePro 1250W
+ 3009 Magicolor 2300W
+ 300b PagePro 1350W
+ 300c PagePro 1300W
+ 302e Develop D 1650iD PCL
+ 3034 Develop D 2050iD PCL
+ 4001 Dimage 2300
+ 4003 Dimage 2330 Zoom Camera
+ 4004 Dimage Scan Elite II AF-2920 (2888)
+ 4005 Minolta DiMAGE E201 Mass Storage Device
+ 4006 Dimage 7 Camera
+ 4007 Dimage S304 Camera
+ 4008 Dimage 5 Camera
+ 4009 Dimage X Camera
+ 400a Dimage S404 Camera
+ 400b Dimage 7i Camera
+ 400c Dimage F100 Camera
+ 400d Dimage Scan Dual III AF-2840 (2889)
+ 400e Dimage Scan Elite 5400 (2890)
+ 400f Dimage 7Hi Camera
+ 4010 Dimage Xi Camera
+ 4011 Dimage F300 Camera
+ 4012 Dimage F200 Camera
+ 4014 Dimage S414 Camera
+ 4015 Dimage XT Camera [storage]
+ 4016 Dimage XT Camera [remote mode]
+ 4017 Dimage E223
+ 4018 Dimage Z1 Camera
+ 4019 Dimage A1 Camera [remote mode]
+ 401a Dimage A1 Camera [storage]
+ 401c Dimage X20 Camera
+ 401e Dimage E323 Camera
+068a Pertech, Inc.
+068b Potrans International, Inc.
+068e CH Products, Inc.
+ 00d3 OEM 3 axis 5 button joystick
+ 00e2 HFX OEM Joystick
+ 00f0 Multi-Function Panel
+ 00f1 Pro Throttle
+ 00f2 Flight Sim Pedals
+ 00f3 Fighterstick
+ 00f4 Combatstick
+ 00fa Ch Throttle Quadrant
+ 00ff Flight Sim Yoke
+ 0500 GameStick 3D
+ 0501 CH Pro Pedals
+ 0504 F-16 Combat Stick
+0690 Golden Bridge Electech, Inc.
+0693 Hagiwara Sys-Com Co., Ltd
+ 0002 FlashGate SmartMedia Card Reader
+ 0003 FlashGate CompactFlash Card Reader
+ 0005 FlashGate
+ 0006 SM PCCard R/W and SPD
+ 0007 FlashGate ME (Authenticated)
+ 000a SDCard/MMC Reader/Writer
+0694 Lego Group
+ 0001 Mindstorms Tower
+ 0002 Mindstorms NXT
+ 0005 Mindstorms EV3
+ 0006 Mindstorms EV3 Firmware Update
+0698 Chuntex (CTX)
+ 1786 1300ex Monitor
+ 2003 CTX M730V built in Camera
+ 9999 VLxxxx Monitor+Hub
+0699 Tektronix, Inc.
+ 0347 AFG 3022B
+ 036a TDS 2024B
+069a Askey Computer Corp.
+ 0001 VC010 Webcam [pwc]
+ 0303 Cable Modem
+ 0311 ADSL Router Remote NDIS Device
+ 0318 Remote NDIS Device
+ 0319 220V Remote NDIS Device
+ 0320 IEEE 802.11b Wireless LAN Card
+ 0321 Dynalink WLL013 / Compex WLU11A 802.11b Adapter
+ 0402 Scientific Atlanta WebSTAR 100 & 200 series Cable Modem
+ 0811 BT Virtual Bus for Helium
+ 0821 BT Voyager 1010 802.11b Adapter
+ 4402 Scientific Atlanta WebSTAR 2000 series Cable Modem
+ 4403 Scientific Atlanta WebSTAR 300 series Cable Modem
+ 4501 Scientific-Atlanta WebSTAR 2000 series Cable Modem
+069b Thomson, Inc.
+ 0704 DCM245 Cable Modem
+ 0705 THG540K Cable Modem
+ 0709 Lyra PDP2424
+ 070c MP3 Player
+ 070d MP3 Player
+ 070e MP3 Player
+ 070f RCA Lyra RD1071 MP3 Player
+ 0731 Lyra M200E256
+ 0761 RCA H100A
+ 0778 PEARL USB Device
+ 2220 RCA Kazoo RD1000 MP3 Player
+ 300a RCA Lyra MP3 Player
+ 3012 MP3 Player
+ 3013 MP3 Player
+ 5557 RCA CDS6300
+069d Hughes Network Systems (HNS)
+ 0001 Satellite Receiver Device
+ 0002 Satellite Device
+069e Welcat Inc.
+ 0005 Marx CryptoBox v1.2
+069f Allied Data Technologies BV
+ 0010 Tornado Speakerphone FaxModem 56.0
+ 0011 Tornado Speakerphone FaxModem 56.0
+ 1000 ADT VvBus for CopperJet
+ 1004 CopperJet 821 RouterPlus
+06a2 Topro Technology, Inc.
+ 0033 USB Mouse
+06a3 Saitek PLC
+ 0006 Cyborg Gold Joystick
+ 0109 P880 Pad
+ 0160 ST290 Pro
+ 0200 Xbox Adrenalin Hub
+ 0241 Xbox Adrenalin Gamepad
+ 0255 X52 Flight Controller
+ 040b P990 Dual Analog Pad
+ 040c P2900 Wireless Pad
+ 0422 ST90 Joystick
+ 0460 ST290 Pro Flight Stick
+ 0463 ST290
+ 0464 Cyborg Evo
+ 0471 Cyborg Graphite Stick
+ 0501 R100 Sports Wheel
+ 0502 ST200 Stick
+ 0506 R220 Digital Wheel
+ 051e Cyborg Digital II Stick
+ 052d P750 Gamepad
+ 053c X45 Flight Controller
+ 053f X36F Flightstick
+ 056c P2000 Tilt Pad
+ 056f P2000 Tilt Pad
+ 05d2 PC Dash 2
+ 075c X52 Flight Controller
+ 0762 Saitek X52 Pro Flight Control System
+ 0763 Pro Flight Rudder Pedals
+ 0764 Flight Pro Combat Rudder
+ 0805 R440 Force Wheel
+ 0b4e Pro Flight Backlit Information Panel
+ 0bac Pro Flight Yoke
+ 0c2d Pro Flight Quadrant
+ 0d05 Pro Flight Radio Panel
+ 0d06 Flight Pro Multi Panel
+ 0d67 Pro Flight Switch Panel
+ 1003 GM2 Action Pad
+ 1009 Action Pad
+ 100a SP550 Pad and Joystick Combo
+ 100b SP550 Pad
+ 1509 P3000 Wireless Pad
+ 1589 P3000 Wireless Pad
+ 2541 X45 Flight Controller
+ 3509 P3000 RF GamePad
+ 353e Cyborg Evo Wireless
+ 3589 P3000 Wireless Pad
+ 35be Cyborg Evo
+ 5509 P3000 Wireless Pad
+ 712c Pro Flight Yoke integrated hub
+ 8000 Gamers' Keyboard
+ 801e Cyborg 3D Digital Stick II
+ 8020 Eclipse Keyboard
+ 8021 Eclipse II Keyboard
+ 802d P750 Pad
+ 803f X36 Flight Controller
+ 806f P2000 Tilt Pad
+ 80c0 Pro Gamer Command Unit
+ 80c1 Cyborg Command Pad Unit
+ a2ae Pro Flight Instrument Panel
+ a502 Gaming Mouse
+ f518 P3200 Rumble Force Game Pad
+ ff04 R440 Force Wheel
+ ff0c Cyborg Force Rumble Pad
+ ff0d P2600 Rumble Force Pad
+ ff12 Cyborg 3D Force Stick
+ ff17 ST 330 Rumble Force Stick
+ ff52 Cyborg 3D Rumble Force Joystick
+ ffb5 Cyborg Evo Force Joystick
+06a4 Xiamen Doowell Electron Co., Ltd
+06a5 Divio
+ 0000 Typhoon Webcam 100k [nw8000]
+ d001 ProLink DS3303u Webcam
+ d800 Chicony TwinkleCam
+ d820 Wize Media 1000
+06a7 MicroStore, Inc.
+06a8 Topaz Systems, Inc.
+ 0042 SignatureGem 1X5 Pad
+ 0043 SignatureGem 1X5-HID Pad
+06a9 Westell
+ 0005 WireSpeed Dual Connect Modem
+ 0006 WireSpeed Dual Connect Modem
+ 000a WireSpeed Dual Connect Modem
+ 000b WireSpeed Dual Connect Modem
+ 000e A90-211WG-01 802.11g Adapter [Intersil ISL3887]
+06aa Sysgration, Ltd
+06ac Fujitsu Laboratories of America, Inc.
+06ad Greatland Electronics Taiwan, Ltd
+06ae Professional Multimedia Testing Centre
+06af Harting, Inc. of North America
+06b8 Pixela Corp.
+06b9 Alcatel Telecom
+ 0120 SpeedTouch 120g 802.11g Wireless Adapter [Intersil ISL3886]
+ 0121 SpeedTouch 121g Wireless Dongle
+ 2001 SPEED TOUCH Card
+ 4061 SpeedTouch ISDN or ADSL Modem
+ 4062 SpeedTouch ISDN or ADSL router
+ a5a5 DynaMiTe Modem
+06ba Smooth Cord & Connector Co., Ltd
+06bb EDA, Inc.
+06bc Oki Data Corp.
+ 000b Okipage 14ex Printer
+ 0027 Okipage 14e
+ 00f7 OKI B4600 Mono Printer
+ 015e OKIPOS 411/412 POS Printer
+ 01c9 OKI B430 Mono Printer
+ 020b OKI ES4140 Mono Printer
+ 02bb OKI PT390 POS Printer
+ 0a91 B2500MFP (printer+scanner)
+ 3801 B6100 Laser Printer
+06bd AGFA-Gevaert NV
+ 0001 SnapScan 1212U
+ 0002 SnapScan 1236U
+ 0100 SnapScan Touch
+ 0101 SNAPSCAN ELITE
+ 0200 ScanMaker 8700
+ 02bf DUOSCAN f40
+ 0400 CL30
+ 0401 Mass Storage
+ 0403 ePhoto CL18 Camera
+ 0404 ePhoto CL20 Camera
+ 2061 SnapScan 1212U (?)
+ 208d Snapscan e40
+ 208f SnapScan e50
+ 2091 SnapScan e20
+ 2093 SnapScan e10
+ 2095 SnapScan e25
+ 2097 SnapScan e26
+ 20fd SnapScan e52
+ 20ff SnapScan e42
+06be AME Optimedia Technology Co., Ltd
+ 0800 Optimedia Camera
+ 1005 Dazzle DPVM! (1005)
+ d001 P35U Camera Capture
+06bf Leoco Corp.
+06c2 Phidgets Inc. (formerly GLAB)
+ 0030 PhidgetRFID
+ 0031 RFID reader
+ 0038 4-Motor PhidgetServo v3.0
+ 0039 1-Motor PhidgetServo v3.0
+ 003a 8-Motor PhidgetAvancedServo
+ 0040 PhidgetInterface Kit 0-0-4
+ 0044 PhidgetInterface Kit 0-16-16
+ 0045 PhidgetInterface Kit 8-8-8
+ 0048 PhidgetStepper (Under Development)
+ 0049 PhidgetTextLED Ver 1.0
+ 004a PhidgetLED Ver 1.0
+ 004b PhidgetEncoder Ver 1.0
+ 0051 PhidgetInterface Kit 0-5-7 (Custom)
+ 0052 PhidgetTextLCD
+ 0053 PhidgetInterfaceKit 0-8-8
+ 0058 PhidgetMotorControl Ver 1.0
+ 0070 PhidgetTemperatureSensor Ver 1.0
+ 0071 PhidgetAccelerometer Ver 1.0
+ 0072 PhidgetWeightSensor Ver 1.0
+ 0073 PhidgetHumiditySensor
+ 0074 PhidgetPHSensor
+ 0075 PhidgetGyroscope
+06c4 Bizlink International Corp.
+06c5 Hagenuk, GmbH
+06c6 Infowave Software, Inc.
+06c8 SIIG, Inc.
+06c9 Taxan (Europe), Ltd
+ 0005 Monitor Control
+ 0007 Monitor Control
+ 0009 Monitor Control
+06ca Newer Technology, Inc.
+ 2003 uSCSI
+06cb Synaptics, Inc.
+ 0001 TouchPad
+ 0002 Integrated TouchPad
+ 0003 cPad
+ 0005 Touchpad/FPS
+ 0006 TouchScreen
+ 0007 USB Styk
+ 0008 WheelPad
+ 0009 Composite TouchPad and TrackPoint
+ 000e HID Device
+ 0010 Wireless TouchPad
+ 0013 DisplayPad
+ 2970 touchpad
+06cc Terayon Communication Systems
+ 0101 Cable Modem
+ 0102 Cable Modem
+ 0103 Cable Modem
+ 0104 Cable Modem
+ 0304 Cable Modem
+06cd Keyspan
+ 0101 USA-28 PDA [no firmware]
+ 0102 USA-28X PDA [no firmware]
+ 0103 USA-19 PDA [no firmware]
+ 0104 PDA [prerenum]
+ 0105 USA-18X PDA [no firmware]
+ 0106 USA-19W PDA [no firmware]
+ 0107 USA-19 PDA
+ 0108 USA-19W PDA
+ 0109 USA-49W serial adapter [no firmware]
+ 010a USA-49W serial adapter
+ 010b USA-19Qi serial adapter [no firmware]
+ 010c USA-19Qi serial adapter
+ 010d USA-19Q serial Adapter (no firmware)
+ 010e USA-19Q serial Adapter
+ 010f USA-28 PDA
+ 0110 USA-28Xb PDA
+ 0111 USA-18 serial Adapter
+ 0112 USA-18X PDA
+ 0113 USA-28Xb PDA [no firmware]
+ 0114 USA-28Xa PDA [no firmware]
+ 0115 USA-28Xa PDA
+ 0116 USA-18XA serial Adapter (no firmware)
+ 0117 USA-18XA serial Adapter
+ 0118 USA-19QW PDA [no firmware]
+ 0119 USA-19QW PDA
+ 011a USA-49Wlc serial adapter [no firmware]
+ 011b MPR Serial Preloader (MPRQI)
+ 011c MPR Serial (MPRQI)
+ 011d MPR Serial Preloader (MPRQ)
+ 011e MPR Serial (MPRQ)
+ 0121 USA-19hs serial adapter
+ 012a USA-49Wlc serial adapter
+ 0201 UIA-10 Digital Media Remote [Cypress AN2131SC]
+ 0202 UIA-11 Digital Media Remote
+06ce Contec
+ 8311 COM-1(USB)H
+06cf SpheronVR AG
+ 1010 PanoCam 10
+ 1012 PanoCam 12/12X
+06d0 LapLink, Inc.
+ 0622 LapLink Gold USB-USB Bridge [net1080]
+06d1 Daewoo Electronics Co., Ltd
+06d3 Mitsubishi Electric Corp.
+ 0284 FX-USB-AW/-BD RS482 Converters
+ 0380 CP8000D Port
+ 0381 CP770D Port
+ 0385 CP900D Port
+ 0387 CP980D Port
+ 038b CP3020D Port
+ 038c CP900DW(ID) Port
+ 0393 CP9500D/DW Port
+ 0394 CP9000D/DW Port
+ 03a1 CP9550D/DW Port
+ 03a5 CP9550DW-S
+ 03a9 CP-9600DW
+ 03aa CP3020DA
+ 03ad CP-9800DW-S
+ 03ae CP-9800DW-S
+ 3b10 P95D
+ 3b30 CP-D70DW / CP-D707DW
+ 3b31 CP-K60DW-S
+06d4 Cisco Systems
+06d5 Toshiba
+ 4000 Japanese Keyboard
+06d6 Aashima Technology B.V.
+ 0025 Gamepad
+ 0026 Predator TH 400 Gamepad
+ 002d Trust PowerC@m 350FT
+ 002e Trust PowerC@m 350FS
+ 0030 Trust 710 LCD POWERC@M ZOOM - MSD
+ 0031 Trust 610/710 LCD POWERC@M ZOOM
+ 003a Trust PowerC@m 770Z (mass storage mode)
+ 003b Trust PowerC@m 770Z (webcam mode)
+ 003c Trust 910z PowerC@m
+ 003f Trust 735S POWERC@M ZOOM, WDM DSC Bulk Driver
+ 0050 Trust 738AV LCD PV Digital Camera
+ 0062 TRUST 782AV LCD P. V. Video Capture
+ 0066 TRUST Digital PCTV and Movie Editor
+ 0067 Trust 350FS POWERC@M FLASH
+ 006b TRUST AUDIO VIDEO EDITOR
+06d7 Network Computing Devices (NCD)
+06d8 Technical Marketing Research, Inc.
+06da Phoenixtec Power Co., Ltd
+ 0002 UPS
+ 0003 1300VA UPS
+06db Paradyne
+06dc Foxlink Image Technology Co., Ltd
+ 0012 Scan 1200c Scanner
+ 0014 Prolink Winscan Pro 2448U
+06de Heisei Electronics Co., Ltd
+06e0 Multi-Tech Systems, Inc.
+ 0319 MT9234ZBA-USB MultiModem ZBA
+ f101 MT5634ZBA-USB MultiModemUSB (old firmware)
+ f103 MT5634MU MultiMobileUSB
+ f104 MT5634ZBA-USB MultiModemUSB (new firmware)
+ f107 MT5634ZBA-USB-V92 MultiModemUSB
+ f120 MT9234ZBA-USB-CDC-ACM-XR MultiModem ZBA CDC-ACM-XR
+06e1 ADS Technologies, Inc.
+ 0008 UBS-10BT Ethernet [klsi]
+ 0009 UBS-10BT Ethernet
+ 0833 Mass Storage Device
+ a155 FM Radio Receiver/Instant FM Music (RDX-155-EF)
+ a160 Instant Video-To-Go RDX-160 (no firmware)
+ a161 Instant Video-To-Go RDX-160
+ a190 Instand VCD Capture
+ a191 Instant VideoXpress
+ a337 Mini DigitalTV
+ a701 DVD Xpress
+ a708 saa7114H video input card (Instant VideoMPX)
+ b337 Mini DigitalTV
+ b701 DVD Xpress B
+06e4 Alcatel Microelectronics
+06e6 Tiger Jet Network, Inc.
+ 0200 Internet Phone
+ 0201 Internet Phone
+ 0202 Composite Device
+ 0203 Internet Phone
+ 0210 Composite Device
+ 0211 Internet Phone
+ 0212 Internet Phone
+ 031c Internet Phone
+ 031d Internet Phone
+ 031e Internet Phone
+ 3200 Composite Device
+ 3201 Internet Phone
+ 3202 Composite Device
+ 3203 Composite Device
+ 7200 Composite Device
+ 7210 Composite Device
+ 7250 Composite Device
+ 825c Internet Phone
+ 831c Internet Phone
+ 831d Composite Device
+ 831e Composite Device
+ b200 Composite Device
+ b201 Composite Device
+ b202 Internet Phone
+ b210 Internet Phone
+ b211 Composite Device
+ b212 Composite Device
+ b250 Composite Device
+ b251 Internet Phone
+ b252 Internet Phone
+ c200 Internet Phone
+ c201 Internet Phone
+ c202 Composite Device
+ c203 Internet Phone
+ c210 Personal PhoneGateway
+ c211 Personal PhoneGateway
+ c212 Personal PhoneGateway
+ c213 PPG Device
+ c25c Composite Device
+ c290 PPG Device
+ c291 PPG Device
+ c292 PPG Device
+ c293 Personal PhoneGateway
+ c31c Composite Device
+ c39c Personal PhoneGateway
+ c39d PPG Device
+ c39e PPG Device
+ c39f PPG Device
+ c700 Internet Phone
+ c701 Internet Phone
+ c702 Composite Device
+ c703 Internet Phone
+ c710 VoIP Combo Device
+ c711 VoIP Combo
+ c712 VoIP Combo Device
+ c713 VoIP Combo Device
+ cf00 Composite Device
+ cf01 Internet Phone
+ cf02 Internet Phone
+ cf03 Composite Device
+ d210 Personal PhoneGateway
+ d211 PPG Device
+ d212 PPG Device
+ d213 Personal PhoneGateway
+ d700 Composite Device
+ d701 Composite Device
+ d702 Internet Phone
+ d703 Composite Device
+ d710 VoIP Combo
+ d711 VoIP Combo Device
+ d712 VoIP Combo
+ d713 VoIP Combo
+ df00 Composite Device
+ df01 Composite Device
+ df02 Internet Phone
+ df03 Internet Phone
+ f200 Internet Phone
+ f201 Internet Phone
+ f202 Composite Device
+ f203 Composite Device
+ f210 Internet Phone
+ f250 Composite Device
+ f252 Internet Phone
+ f310 Internet Phone
+ f350 Composite Device
+06ea Sirius Technologies
+ 0001 NetCom Roadster II 56k
+ 0002 Roadster II 56k
+06eb PC Expert Tech. Co., Ltd
+06ef I.A.C. Geometrische Ingenieurs B.V.
+06f0 T.N.C Industrial Co., Ltd
+ de01 DualCam Video Camera
+ de02 DualCam Still Camera
+06f1 Opcode Systems, Inc.
+ a011 SonicPort
+ a021 SonicPort Optical
+06f2 Emine Technology Co.
+ 0011 KVM Switch Keyboard
+06f6 Wintrend Technology Co., Ltd
+06f7 Wailly Technology Ltd
+ 0003 USB->Din 4 Adaptor
+06f8 Guillemot Corp.
+ 3002 Hercules Blog Webcam
+ 3004 Hercules Classic Silver
+ 3005 Hercules Dualpix Exchange
+ 3007 Hercules Dualpix Chat and Show
+ 3020 Hercules Webcam EC300
+ a300 Dual Analog Leader GamePad
+ b000 Hercules DJ Console
+ c000 Hercules Muse Pocket
+ d002 Hercules DJ Console
+ e000 HWGUSB2-54 WLAN
+ e010 HWGUSB2-54-LB
+ e020 HWGUSB2-54V2-AP
+ e031 Hercules HWNUm-300 Wireless N mini [Realtek RTL8191SU]
+ e032 HWGUm-54 [Hercules Wireless G Ultra Mini Key]
+ e033 Hercules HWNUp-150 802.11n Wireless N Pico [Realtek RTL8188CUS]
+06f9 ASYST electronic d.o.o.
+06fa HSD S.r.L
+06fc Motorola Semiconductor Products Sector
+06fd Boston Acoustics
+ 0101 Audio Device
+ 0102 Audio Device
+ 0201 2-piece Audio Device
+06fe Gallant Computer, Inc.
+0701 Supercomal Wire & Cable SDN. BHD.
+0703 Bvtech Industry, Inc.
+0705 NKK Corp.
+0706 Ariel Corp.
+0707 Standard Microsystems Corp.
+ 0100 2202 Ethernet [klsi]
+ 0200 2202 Ethernet [pegasus]
+ 0201 EZ Connect USB Ethernet
+ ee04 SMCWUSB32 802.11b Wireless LAN Card
+ ee06 SMC2862W-G v1 EZ Connect 802.11g Adapter [Intersil ISL3886]
+ ee13 SMC2862W-G v2 EZ Connect 802.11g Adapter [Intersil ISL3887]
+0708 Putercom Co., Ltd
+ 047e USB-1284 BRIDGE
+0709 Silicon Systems, Ltd (SSL)
+070a Oki Electric Industry Co., Ltd
+ 4002 Bluetooth Device
+ 4003 Bluetooth Device
+070d Comoss Electronic Co., Ltd
+070e Excel Cell Electronic Co., Ltd
+0710 Connect Tech, Inc.
+ 0001 WhiteHeat (fake ID)
+ 8001 WhiteHeat
+0711 Magic Control Technology Corp.
+ 0100 Hub
+ 0180 IRXpress Infrared Device
+ 0181 IRXpress Infrared Device
+ 0200 BAY-3U1S1P Serial Port
+ 0210 MCT1S Serial Port
+ 0230 MCT-232 Serial Port
+ 0231 PS/2 Mouse Port
+ 0232 Serial On Port
+ 0240 PS/2 to USB Converter
+ 0300 BAY-3U1S1P Parallel Port
+ 0302 Parallel Port
+ 0900 SVGA Adapter
+ 5001 Trigger UV-002BD[Startech USBVGAE]
+ 5100 Magic Control Technology Corp. (USB2VGA dongle)
+0713 Interval Research Corp.
+0714 NewMotion, Inc.
+ 0003 ADB converter
+0717 ZNK Corp.
+0718 Imation Corp.
+ 0002 SuperDisk 120MB
+ 0003 SuperDisk 120MB (Authenticated)
+ 0060 Flash Drive
+ 0061 Flash Drive
+ 0062 Flash Drive
+ 0063 Swivel Flash Drive
+ 0064 Flash Drive
+ 0065 Flash Drive
+ 0066 Flash Drive
+ 0067 Flash Drive
+ 0068 Flash Drive
+ 0084 Flash Drive Mini
+ 043c Flash drive 16GB [Nano Pro]
+ 0582 Revo Flash Drive
+ 0622 TDK Trans-It 4GB
+ 0624 TDK Trans-It 16GB
+ 1120 RDX External dock (redbud)
+ 4006 8x Slim DVD Multi-Format Recorder External
+ d000 Disc Stakka CD/DVD Manager
+0719 Tremon Enterprises Co., Ltd
+071b Domain Technologies, Inc.
+ 0002 DTI-56362-USB Digital Interface Unit
+ 0101 Audio4-USB DSP Data Acquisition Unit
+ 0184 Archos 2 8GB EM184RB
+ 0201 Audio4-5410 DSP Data Acquisition Unit
+ 0301 SB-USB JTAG Emulator
+ 3203 Rockchip Media Player
+ 32bb Music Mediatouch
+071c Xionics Document Technologies, Inc.
+071d Eicon Networks Corp.
+ 1000 Diva 2.01 S/T [PSB2115F]
+ 1003 Diva ISDN 2.0
+ 1005 Diva ISDN 4.0 [HFC-S]
+ 2000 Teledat Surf
+071e Ariston Technologies
+0723 Centillium Communications Corp.
+ 0002 Palladia 300/400 Adsl Modem
+0726 Vanguard International Semiconductor-America
+0729 Amitm
+ 1000 USC-1000 Serial Port
+072e Sunix Co., Ltd
+072f Advanced Card Systems, Ltd
+ 0001 AC1030-based SmartCard Reader
+ 0008 ACR 80 Smart Card Reader
+ 0100 AET65
+ 0101 AET65
+ 0102 AET62
+ 0103 AET62
+ 0901 ACR1281U-C4 (BSI)
+ 1000 PLDT Drive
+ 1001 PLDT Drive
+ 2011 ACR88U
+ 2100 ACR128U
+ 2200 ACR122U
+ 220a ACR1281U-C5 (BSI)
+ 220c ACR1283 Bootloader
+ 220f ACR1281U-C2 (qPBOC)
+ 2211 ACR1261 1S Dual Reader
+ 2214 ACR1222 1SAM PICC Reader
+ 2215 ACR1281 2S CL Reader
+ 221a ACR1251U-A1
+ 221b ACR1251U-C
+ 2224 ACR1281 1S Dual Reader
+ 222b ACR1222U-C8
+ 222c ACR1283L-D2
+ 222d [OEM Reader]
+ 222e ACR123U
+ 2242 ACR1251 1S Dual Reader
+ 8002 AET63 BioTRUSTKey
+ 8003 ACR120
+ 8103 ACR120
+ 8201 APG8201
+ 8900 ACR89U-A1
+ 8901 ACR89U-A2
+ 8902 ACR89U-A3
+ 9000 ACR38 AC1038-based Smart Card Reader
+ 9006 CryptoMate
+ 90cc ACR38 SmartCard Reader
+ 90ce [OEM Reader]
+ 90cf ACR38 SAM Smart Card Reader
+ 90d0 PertoSmart EMV - Card Reader
+ 90d2 ACR83U
+ 90d8 ACR3801
+ 90db CryptoMate64
+ b000 ACR3901U
+ b100 ACR39U
+ b101 ACR39K
+ b102 ACR39T
+ b103 ACR39F
+ b104 ACR39U-SAM
+ b106 ACOS5T2
+ b200 ACOS5T1
+ b301 ACR32-A1
+0731 Susteen, Inc.
+ 0528 SonyEricsson DCU-11 Cable
+0732 Goldfull Electronics & Telecommunications Corp.
+0733 ViewQuest Technologies, Inc.
+ 0101 Digital Video Camera
+ 0110 VQ110 Video Camera
+ 0401 CS330 Webcam
+ 0402 M-318B Webcam
+ 0430 Intel Pro Share Webcam
+ 0630 VQ630 Dual Mode Digital Camera(Bulk)
+ 0631 Hercules Dualpix
+ 0780 Smart Cam Deluxe(composite)
+ 1310 Epsilon 1.3/Jenoptik JD C1.3/UMAX AstraPix 470 (mass storage mode)
+ 1311 Epsilon 1.3/Jenoptik JD C1.3/UMAX AstraPix 470 (PC Cam mode)
+ 1314 Mercury 2.1MEG Deluxe Classic Cam
+ 2211 Jenoptik jdc 21 LCD Camera
+ 2221 Mercury Digital Pro 3.1p
+ 3261 Concord 3045 spca536a Camera
+ 3281 Cyberpix S550V
+0734 Lasat Communications A/S
+ 0001 560V Modem
+ 0002 Lasat 560V Modem
+ 043a DVS Audio
+ 043b 3DeMon USB Capture
+0735 Asuscom Network
+ 2100 ISDN Adapter
+ 2101 ISDN Adapter
+ 6694 ISDNlink 128K
+ c541 ISDN TA 280
+0736 Lorom Industrial Co., Ltd
+0738 Mad Catz, Inc.
+ 4507 XBox Device
+ 4516 XBox Device
+ 4520 XBox Device
+ 4526 XBox Device
+ 4536 XBox Device
+ 4540 XBox Device
+ 4556 XBox Device
+ 4566 XBox Device
+ 4576 XBox Device
+ 4586 XBox Device
+ 4588 XBox Device
+ 8818 Street Fighter IV Arcade FightStick (PS3)
+073a Chaplet Systems, Inc.
+ 2230 infrared dongle for remote
+073b Suncom Technologies
+073c Industrial Electronic Engineers, Inc.
+ 0305 Pole Display (PC305-3415 2 x 20 Line Display)
+ 0322 Pole Display (PC322-3415 2 x 20 Line Display)
+ 0324 Pole Display (LB324-USB 4 x 20 Line Display)
+ 0330 Pole Display (P330-3415 2 x 20 Line Display)
+ 0424 Pole Display (SP324-4415 4 x 20 Line Display)
+ 0450 Pole Display (L450-USB Graphic Line Display)
+ 0505 Pole Display (SPC505-3415 2 x 20 Line Display)
+ 0522 Pole Display (SPC522-3415 2 x 20 Line Display)
+ 0624 Pole Display (SP324-3415 4 x 20 Line Display)
+073d Eutron S.p.a.
+ 0005 Crypto Token
+ 0007 CryptoIdentity CCID
+ 0025 SmartKey 3
+ 0c00 Pocket Reader
+ 0d00 StarSign Bio Token 3.0 EU
+073e NEC, Inc.
+ 0301 Game Pad
+0742 Stollmann
+ 2008 ISDN TA [HFC-S]
+ 2009 ISDN TA [HFC-S]
+ 200a ISDN TA [HFC-S]
+0745 Syntech Information Co., Ltd
+0746 Onkyo Corp.
+ 5500 SE-U55 Audio Device
+0747 Labway Corp.
+0748 Strong Man Enterprise Co., Ltd
+0749 EVer Electronics Corp.
+074a Ming Fortune Industry Co., Ltd
+074b Polestar Tech. Corp.
+074c C-C-C Group PLC
+074d Micronas GmbH
+ 3553 Composite USB-Device
+ 3554 Composite USB-Device
+ 3556 Composite USB-Device
+074e Digital Stream Corp.
+ 0001 PS/2 Adapter
+ 0002 PS/2 Adapter
+0755 Aureal Semiconductor
+0757 Network Technologies, Inc.
+075b Sophisticated Circuits, Inc.
+ 0001 Kick-off! Watchdog
+0763 Midiman
+ 0115 O2 / KeyRig 25
+ 0117 Trigger Finger
+ 0119 MidAir
+ 0150 M-Audio Uno
+ 0160 M-Audio 1x1
+ 0192 M-Audio Keystation 88es
+ 0193 ProKeys 88
+ 0194 ProKeys 88sx
+ 0195 Oxygen 8 v2
+ 0196 Oxygen 49
+ 0197 Oxygen 61
+ 0198 Axiom 25
+ 0199 Axiom 49
+ 019a Axiom 61
+ 019b KeyRig 49
+ 019c KeyStudio
+ 1001 MidiSport 2x2
+ 1002 MidiSport 2x2
+ 1003 MidiSport 2x2
+ 1010 MidiSport 1x1
+ 1011 MidiSport 1x1
+ 1014 M-Audio Keystation Loader
+ 1015 M-Audio Keystation
+ 1020 Midisport 4x4
+ 1021 MidiSport 4x4
+ 1030 M-Audio MIDISPORT 8x8
+ 1031 MidiSport 8x8/s Loader
+ 1033 MidiSport 8x8/s
+ 1040 M-Audio MidiSport 2x4 Loader
+ 1041 M-Audio MidiSport 2x4
+ 1110 MidiSport 1x1
+ 2001 M Audio Quattro
+ 2002 M Audio Duo
+ 2003 M Audio AudioPhile
+ 2004 M-Audio MobilePre
+ 2006 M-Audio Transit
+ 2007 M-Audio Sonica Theater
+ 2008 M-Audio Ozone
+ 200d M-Audio OmniStudio
+ 200f M-Audio MobilePre
+ 2010 M-Audio Fast Track
+ 2012 M-Audio Fast Track Pro
+ 2013 M-Audio JamLab
+ 2015 M-Audio RunTime DFU
+ 2016 M-Audio RunTime DFU
+ 2019 M-Audio Ozone Academic
+ 201a M-Audio Micro
+ 201b M-Audio RunTime DFU
+ 201d M-Audio Producer
+ 2024 M-Audio Fast Track MKII
+ 2080 M-Audio Fast Track Ultra
+ 2081 M-Audio RunTime DFU / Fast Track Ultra 8R
+ 2803 M-Audio Audiophile DFU
+ 2804 M-Audio MobilePre DFU
+ 2806 M-Audio Transit DFU
+ 2815 M-Audio DFU
+ 2816 M-Audio DFU
+ 281b M-Audio DFU
+ 2880 M-Audio DFU
+ 2881 M-Audio DFU
+0764 Cyber Power System, Inc.
+ 0005 Cyber Power UPS
+ 0501 CP1500 AVR UPS
+ 0601 PR1500LCDRT2U UPS
+0765 X-Rite, Inc.
+ 5001 Huey PRO Colorimeter
+ 5010 X-Rite Pantone Color Sensor
+ 5020 i1 Display Pro
+ 6003 ColorMunki Smile
+ d094 X-Rite DTP94 [Quato Silver Haze Pro]
+0766 Jess-Link Products Co., Ltd
+ 001b Packard Bell Go
+ 0204 TopSpeed Cyberlink Remote Control
+0767 Tokheim Corp.
+0768 Camtel Technology Corp.
+ 0006 Camtel Technology USB TV Genie Pro FM Model TVB330
+ 0023 eHome Infrared Receiver
+0769 Surecom Technology Corp.
+ 11f2 EP-9001-g 802.11g 54M WLAN Adapter
+ 11f3 RT2570
+ 11f7 802.11g 54M WLAN Adapter
+ 31f3 RT2573
+076a Smart Technology Enablers, Inc.
+076b OmniKey AG
+ 0596 CardMan 2020
+ 1021 CardMan 1021
+ 1221 CardMan 1221
+ 1784 CardMan 6020
+ 3021 CardMan 3121
+ 3022 CardMan 3021
+ 3610 CardMan 3620
+ 3621 CardMan 3621
+ 3821 CardMan 3821
+ 4321 CardMan 4321
+ 5121 CardMan 5121
+ 5125 CardMan 5125
+ 5321 CardMan 5321
+ 5340 CardMan 5021 CL
+ 6622 CardMan 6121
+ a011 CCID Smart Card Reader Keyboard
+ a021 CCID Smart Card Reader
+ a022 CardMan Smart@Link
+ c000 CardMan 3x21 CS
+ c001 CardMan 5121 CS
+076c Partner Tech
+076d Denso Corp.
+076e Kuan Tech Enterprise Co., Ltd
+076f Jhen Vei Electronic Co., Ltd
+0770 Welch Allyn, Inc - Medical Division
+0771 Observator Instruments BV
+ 4455 OMC45III
+ ae0f OMC45III
+0772 Your data Our Care
+0774 AmTRAN Technology Co., Ltd
+0775 Longshine Electronics Corp.
+0776 Inalways Corp.
+0777 Comda Enterprise Corp.
+0778 Volex, Inc.
+0779 Fairchild Semiconductor
+077a Sankyo Seiki Mfg. Co., Ltd
+077b Linksys
+ 08be BEFCMU10 v4 Cable Modem
+ 2219 WUSB11 V2.6 802.11b Adapter
+ 2226 USB200M 100baseTX Adapter
+ 2227 Network Everywhere NWU11B
+077c Forward Electronics Co., Ltd
+ 0005 NEC Keyboard
+077d Griffin Technology
+ 0223 IMic Audio In/Out
+ 0405 iMate, ADB Adapter
+ 0410 PowerMate
+ 041a PowerWave
+ 04aa SoundKnob
+ 07af iMic
+ 1016 AirClick
+ 627a Radio SHARK
+077f Well Excellent & Most Corp.
+0780 Sagem Monetel GmbH
+ 1202 ORGA 900 Smart Card Terminal Virtual Com Port
+ 1302 ORGA 6000 Smart Card Terminal Virtual Com Port
+ 1303 ORGA 6000 Smart Card Terminal USB RNDIS
+ df55 ORGA 900/6000 Smart Card Terminal DFU
+0781 SanDisk Corp.
+ 0001 SDDR-05a ImageMate CompactFlash Reader
+ 0002 SDDR-31 ImageMate II CompactFlash Reader
+ 0005 SDDR-05b (CF II) ImageMate CompactFlash Reader
+ 0100 ImageMate SDDR-12
+ 0200 SDDR-09 (SSFDC) ImageMate SmartMedia Reader [eusb]
+ 0400 SecureMate SD/MMC Reader
+ 0621 SDDR-86 Imagemate 6-in-1 Reader
+ 0720 Sansa C200 series in recovery mode
+ 0729 Sansa E200 series in recovery mode
+ 0810 SDDR-75 ImageMate CF-SM Reader
+ 0830 ImageMate CF/MMC/SD Reader
+ 1234 Cruzer Mini Flash Drive
+ 5150 SDCZ2 Cruzer Mini Flash Drive (thin)
+ 5151 Cruzer Micro Flash Drive
+ 5153 Cruzer Flash Drive
+ 5204 Cruzer Crossfire
+ 5402 U3 Cruzer Micro
+ 5406 Cruzer Micro U3
+ 5408 Cruzer Titanium U3
+ 540e Cruzer Contour Flash Drive
+ 5530 Cruzer
+ 5567 Cruzer Blade
+ 556b Cruzer Edge
+ 556c Ultra
+ 556d Memory Vault
+ 5571 Cruzer Fit
+ 5575 Cruzer Glide
+ 5576 Cruzer Facet
+ 5577 Cruzer Pop (8GB)
+ 557d Cruzer Force (64GB)
+ 5580 SDCZ80 Flash Drive
+ 5581 Ultra
+ 5583 Ultra Fit
+ 5590 Ultra Dual
+ 5591 Ultra Flair
+ 5e10 Encrypted
+ 6100 Ultra II SD Plus 2GB
+ 7100 Cruzer Mini
+ 7101 Pen Flash
+ 7102 Cruzer Mini
+ 7103 Cruzer Mini
+ 7104 Cruzer Micro Mini 256MB Flash Drive
+ 7105 Cruzer Mini
+ 7106 Cruzer Mini
+ 7112 Cruzer Micro 128MB Flash Drive
+ 7113 Cruzer Micro 256MB Flash Drive
+ 7114 Cruzer Mini
+ 7115 Cruzer Mini
+ 7301 Sansa e100 series (mtp)
+ 7302 Sansa e100 series (msc)
+ 7400 Sansa M200 series (mtp)
+ 7401 Sansa M200 series (msc)
+ 7420 Sansa E200 series (mtp)
+ 7421 Sansa E200 Series (msc)
+ 7422 Sansa E200 series v2 (mtp)
+ 7423 Sansa E200 series v2 (msc)
+ 7430 Sansa M200 series
+ 7431 Sansa M200 series V4 (msc)
+ 7432 Sansa Clip (mtp)
+ 7433 Sansa Clip (msc)
+ 7434 Sansa Clip V2 (mtp)
+ 7435 Sansa Clip V2 (msc)
+ 7450 Sansa C250
+ 7451 Sansa C240
+ 7460 Sansa Express
+ 7480 Sansa Connect
+ 7481 Sansa Connect (in recovery mode)
+ 74b0 Sansa View (msc)
+ 74b1 Sansa View (mtp)
+ 74c0 Sansa Fuze (mtp)
+ 74c1 Sansa Fuze (msc)
+ 74c2 Sansa Fuze V2 (mtp)
+ 74c3 Sansa Fuze V2 (msc)
+ 74d0 Sansa Clip+ (mtp)
+ 74d1 Sansa Clip+ (msc)
+ 74e5 Sansa Clip Zip
+ 8181 Pen Flash
+ 8183 Hi-Speed Mass Storage Device
+ 8185 SDCZ2 Cruzer Mini Flash Drive (older, thick)
+ 8888 Card Reader
+ 8889 SDDR-88 Imagemate 8-in-1 Reader
+ 8919 Card Reader
+ 8989 ImageMate 12-in-1 Reader
+ 9191 ImageMate CF
+ 9219 Card Reader
+ 9292 ImageMate CF Reader/Writer
+ 9393 ImageMate SD-MMC
+ 9595 ImageMate xD-SM
+ 9797 ImageMate MS-PRO
+ 9919 Card Reader
+ 9999 SDDR-99 5-in-1 Reader
+ a7c1 Storage device (SD card reader)
+ a7e8 SDDR-113 MicroMate SDHC Reader
+ b2b3 SDDR-103 MobileMate SD+ Reader
+ b4b5 SDDR-89 V4 ImageMate 12-in-1 Reader
+ b6ba CF SDDR-289
+0782 Trackerball
+0783 C3PO
+ 0003 LTC31 SmartCard Reader
+ 0006 LTC31v2
+ 0009 KBR36
+ 0010 LTC32
+0784 Vivitar, Inc.
+ 0100 Vivicam 2655
+ 1310 Vivicam 3305
+ 1688 Vivicam 3665
+ 1689 Gateway DC-M42/Labtec DC-505/Vivitar Vivicam 3705
+ 2620 AOL Photocam Plus
+ 2888 Polaroid DC700
+ 3330 Nytec ND-3200 Camera
+ 4300 Traveler D1
+ 5260 Werlisa Sport PX 100 / JVC GC-A33 Camera
+ 5300 Pretec dc530
+0785 NTT-ME
+ 0001 MN128mini-V ISDN TA
+ 0003 MN128mini-J ISDN TA
+0789 Logitec Corp.
+ 0026 LHD Device
+ 0033 DVD Multi-plus unit LDR-H443SU2
+ 0063 LDR Device
+ 0064 LDR-R Device
+ 00b3 DVD Multi-plus unit LDR-H443U2
+ 0105 LAN-TX/U1H2 10/100 Ethernet Adapter [pegasus II]
+ 010c Realtek RTL8187 Wireless 802.11g 54Mbps Network Adapter
+ 0160 LAN-GTJ/U2A
+ 0162 LAN-WN22/U2 Wireless LAN Adapter
+ 0163 LAN-WN12/U2 Wireless LAN Adapter
+ 0164 LAN-W150/U2M Wireless LAN Adapter
+ 0166 LAN-W300N/U2 Wireless LAN Adapter
+ 0168 LAN-W150N/U2 Wireless LAN Adapter
+ 0170 LAN-W300AN/U2 Wireless LAN Adapter
+078b Happ Controls, Inc.
+ 0010 Driving UGCI
+ 0020 Flying UGCI
+ 0030 Fighting UGCI
+078c GTCO/CalComp
+ 0090 Tablet Adapter
+ 0100 Tablet Adapter
+ 0200 Tablet Adapter
+ 0300 Tablet Adapter
+ 0400 Digitizer (Whiteboard)
+078e Brincom, Inc.
+0790 Pro-Image Manufacturing Co., Ltd
+0791 Copartner Wire and Cable Mfg. Corp.
+0792 Axis Communications AB
+0793 Wha Yu Industrial Co., Ltd
+0794 ABL Electronics Corp.
+0795 RealChip, Inc.
+0796 Certicom Corp.
+0797 Grandtech Semiconductor Corp.
+ 6801 Flatbed Scanner
+ 6802 InkJet Color Printer
+ 8001 SmartCam
+ 801a Typhoon StyloCam
+ 801c Meade Binoculars/Camera
+ 8901 ScanHex SX-35a
+ 8909 ScanHex SX-35b
+ 8911 ScanHex SX-35c
+0798 Optelec
+ 0001 Braille Voyager
+ 0640 BC640
+ 0680 BC680
+0799 Altera
+ 7651 Programming Unit
+079b Sagem
+ 0024 MSO300/MSO301 Fingerprint Sensor
+ 0026 MSO350/MSO351 Fingerprint Sensor & SmartCard Reader
+ 0027 USB-Serial Controller
+ 002f Mobile
+ 0030 Mobile Communication Device
+ 0042 Mobile
+ 0047 CBM/MSO1300 Fingerprint Sensor
+ 004a XG-760A 802.11bg
+ 004b Wi-Fi 11g adapter
+ 0052 MSO1350 Fingerprint Sensor & SmartCard Reader
+ 0056 Agfa AP1100 Photo Printer
+ 005d Mobile Mass Storage
+ 0062 XG-76NA 802.11bg
+ 0078 Laser Pro Monochrome MFP
+079d Alfadata Computer Corp.
+ 0201 GamePort Adapter
+07a1 Digicom S.p.A.
+ d952 Palladio USB V.92 Modem
+07a2 National Technical Systems
+07a3 Onnto Corp.
+07a4 Be, Inc.
+07a6 ADMtek, Inc.
+ 07c2 AN986A Ethernet
+ 0986 AN986 Pegasus Ethernet
+ 8266 Infineon WildCard-USB Wireless LAN Adapter
+ 8511 ADM8511 Pegasus II Ethernet
+ 8513 AN8513 Ethernet
+ 8515 AN8515 Ethernet
+07aa Corega K.K.
+ 0001 Ether USB-T Ethernet [klsi]
+ 0004 FEther USB-TX Ethernet [pegasus]
+ 000c WirelessLAN USB-11
+ 000d FEther USB-TXS
+ 0011 Wireless LAN USB-11 mini
+ 0012 Stick-11 802.11b Adapter
+ 0017 FEther USB2-TX
+ 0018 Wireless LAN USB-11 mini 2
+ 001a ULUSB-11 Key
+ 001c CG-WLUSB2GT 802.11g Wireless Adapter [Intersil ISL3880]
+ 0020 CG-WLUSB2GTST 802.11g Wireless Adapter [Intersil ISL3887]
+ 002e CG-WLUSB2GPX [Ralink RT2571W]
+ 002f CG-WLUSB2GNL
+ 0031 CG-WLUSB2GS 802.11bg [Atheros AR5523]
+ 003c CG-WLUSB2GNL
+ 003f CG-WLUSB300AGN
+ 0041 CG-WLUSB300GNS
+ 0042 CG-WLUSB300GNM
+ 0043 CG-WLUSB300N rev A2 [Realtek RTL8192U]
+ 0047 CG-WLUSBNM
+ 0051 CG-WLUSB300NM
+ 7613 Stick-11 V2 802.11b Adapter
+ 9601 FEther USB-TXC
+07ab Freecom Technologies
+ fc01 IDE bridge
+ fc02 Cable II USB-2
+ fc03 USB2-IDE IDE bridge
+ fcd6 Freecom HD Classic
+ fcf6 DataBar
+ fcf8 Freecom Classic SL Network Drive
+ fcfe Hard Drive 80GB
+07af Microtech
+ 0004 SCSI-DB25 SCSI Bridge [shuttle]
+ 0005 SCSI-HD50 SCSI Bridge [shuttle]
+ 0006 CameraMate SmartMedia and CompactFlash Card Reader [eusb/shuttle]
+ fc01 Freecom USB-IDE
+07b0 Trust Technologies
+ 0001 ISDN TA
+ 0002 ISDN TA128 Plus
+ 0003 ISDN TA128 Deluxe
+ 0005 ISDN TA128 SE
+ 0006 ISDN TA 128 [HFC-S]
+ 0007 ISDN TA [HFC-S]
+ 0008 ISDN TA
+07b1 IMP, Inc.
+07b2 Motorola BCS, Inc.
+ 0100 SURFboard Voice over IP Cable Modem
+ 0900 SURFboard Gateway
+ 0950 SURFboard SBG950 Gateway
+ 1000 SURFboard SBG1000 Gateway
+ 4100 SurfBoard SB4100 Cable Modem
+ 4200 SurfBoard SB4200 Cable Modem
+ 4210 SurfBoard 4210 Cable Modem
+ 4220 SURFboard SB4220 Cable Modem
+ 4500 CG4500 Communications Gateway
+ 450b CG4501 Communications Gateway
+ 450e CG4500E Communications Gateway
+ 5100 SurfBoard SB5100 Cable Modem
+ 5101 SurfBoard SB5101 Cable Modem
+ 5120 SurfBoard SB5120 Cable Modem (RNDIS)
+ 5121 Surfboard 5121 Cable Modem
+ 7030 WU830G 802.11bg Wireless Adapter [Envara WiND512]
+07b3 Plustek, Inc.
+ 0001 OpticPro 1212U Scanner
+ 0003 Scanner
+ 0010 OpticPro U12 Scanner
+ 0011 OpticPro U24 Scanner
+ 0013 OpticPro UT12 Scanner
+ 0014 Scanner
+ 0015 OpticPro U24 Scanner
+ 0017 OpticPro UT12/16/24 Scanner
+ 0204 Scanner
+ 0400 OpticPro 1248U Scanner
+ 0401 OpticPro 1248U Scanner #2
+ 0403 OpticPro U16B Scanner
+ 0404 Scanner
+ 0405 A8 Namecard-s Controller
+ 0406 A8 Namecard-D Controller
+ 0410 Scanner
+ 0412 Scanner
+ 0413 OpticSlim 1200 Scanner
+ 0601 OpticPro ST24 Scanner
+ 0800 OpticPro ST48 Scanner
+ 0900 OpticBook 3600 Scanner
+ 090c OpticBook 3600 Plus Scanner
+ 0a06 TVcam VD100
+ 0b00 SmartPhoto F50
+ 0c00 OpticPro ST64 Scanner
+ 0c03 OpticPro ST64+ Scanner
+ 0c04 Optic Film 7200i scanner
+ 0c0c PL806 Scanner
+ 0c26 OpticBook 4600 Scanner
+ 0c2b Mobile Office D428 Scanner
+ 0e08 OpticBook A300 Scanner
+ 1300 OpticBook 3800 Scanner
+ 1301 OpticBook 4800 Scanner
+07b4 Olympus Optical Co., Ltd
+ 0100 Camedia C-2100/C-3000 Ultra Zoom Camera
+ 0102 Camedia E-10/C-220/C-50 Camera
+ 0105 Camedia C-310Z/C-700/C-750UZ/C-755/C-765UZ/C-3040/C-4000/C-5050Z/D-560/C-3020Z Zoom Camera
+ 0109 C-370Z/C-500Z/D-535Z/X-450
+ 010a MAUSB-10 xD and SmartMedia Card Reader
+ 0112 MAUSB-100 xD Card Reader
+ 0113 Mju 500 / Stylus Digital Camera (PTP)
+ 0114 C-350Z Camera
+ 0118 Mju Mini Digital/Mju Digital 500 Camera / Stylus 850 SW
+ 0125 Tough TG-1 Camera
+ 0184 P-S100 port
+ 0202 Foot Switch RS-26
+ 0203 Digital Voice Recorder DW-90
+ 0206 Digital Voice Recorder DS-330
+ 0207 Digital Voice Recorder & Camera W-10
+ 0209 Digital Voice Recorder DM-20
+ 020b Digital Voice Recorder DS-4000
+ 020d Digital Voice Recorder VN-240PC
+ 0211 Digital Voice Recorder DS-2300
+ 0218 Foot Switch RS-28
+ 0244 Digital Voice Recorder VN-8500PC
+ 024f Digital Voice Recorder DS-7000
+ 0280 m:robe 100
+07b5 Mega World International, Ltd
+ 0017 Joystick
+ 0213 Thrustmaster Firestorm Digital 3 Gamepad
+ 0312 Gamepad
+ 9902 GamePad
+07b6 Marubun Corp.
+07b7 TIME Interconnect, Ltd
+07b8 AboCom Systems Inc
+ 110c XX1
+ 1201 IEEE 802.11b Adapter
+ 200c XX2
+ 2573 Wireless LAN Card
+ 2770 802.11n/b/g Mini Wireless LAN USB2.0 Adapter
+ 2870 802.11n/b/g Wireless LAN USB2.0 Adapter
+ 3070 802.11n/b/g Mini Wireless LAN USB2.0 Adapter
+ 3071 802.11n/b/g Mini Wireless LAN USB2.0 Adapter
+ 3072 802.11n/b/g Mini Wireless LAN USB2.0 Adapter
+ 4000 DU-E10 Ethernet [klsi]
+ 4002 DU-E100 Ethernet [pegasus]
+ 4003 1/10/100 Ethernet Adapter
+ 4004 XX4
+ 4007 XX5
+ 400b XX6
+ 400c XX7
+ 401a RTL8151
+ 4102 USB 1.1 10/100M Fast Ethernet Adapter
+ 4104 XX9
+ 420a UF200 Ethernet
+ 5301 GW-US54ZGL 802.11bg
+ 6001 802.11bg
+ 8188 AboCom Systems Inc [WN2001 Prolink Wireless-N Nano Adapter]
+ a001 WUG2200 802.11g Wireless Adapter [Envara WiND512]
+ abc1 DU-E10 Ethernet [pegasus]
+ b000 BWU613
+ b02a AboCom Bluetooth Device
+ b02b Bluetooth dongle
+ b02c BCM92045DG-Flash with trace filter
+ b02d BCM92045DG-Flash with trace filter
+ b02e BCM92045DG-Flash with trace filter
+ b030 BCM92045DG-Flash with trace filter
+ b031 BCM92045DG-Flash with trace filter
+ b032 BCM92045DG-Flash with trace filter
+ b033 BCM92045DG-Flash with trace filter
+ b21a WUG2400 802.11g Wireless Adapter [Texas Instruments TNETW1450]
+ b21b HWU54DM
+ b21c RT2573
+ b21d RT2573
+ b21e RT2573
+ b21f WUG2700
+ d011 MP3 Player
+ e001 Mass Storage Device
+ e002 Mass Storage Device
+ e003 Mass Storage Device
+ e004 Mass Storage Device
+ e005 Mass Storage Device
+ e006 Mass Storage Device
+ e007 Mass Storage Device
+ e008 Mass Storage Device
+ e009 Mass Storage Device
+ e00a Mass Storage Device
+ e4f0 Card Reader Driver
+ f101 DSB-560 Modem [atlas]
+07bc Canon Computer Systems, Inc.
+07bd Webgear, Inc.
+07be Veridicom
+07c0 Code Mercenaries Hard- und Software GmbH
+ 1113 JoyWarrior24F8
+ 1116 JoyWarrior24F14
+ 1121 The Claw
+ 1500 IO-Warrior 40
+ 1501 IO-Warrior 24
+ 1502 IO-Warrior 48
+ 1503 IO-Warrior 28
+ 1511 IO-Warrior 24 Power Vampire
+ 1512 IO-Warrior 24 Power Vampire
+07c1 Keisokugiken
+ 0068 HKS-0200 USBDAQ
+07c4 Datafab Systems, Inc.
+ 0102 USB to LS120
+ 0103 USB to IDE
+ 1234 USB to ATAPI
+ a000 CompactFlash Card Reader
+ a001 CompactFlash & SmartMedia Card Reader [eusb]
+ a002 Disk Drive
+ a003 Datafab-based Reader
+ a004 USB to MMC Class Drive
+ a005 CompactFlash & SmartMedia Card Reader
+ a006 SmartMedia Card Reader
+ a007 Memory Stick Class Drive
+ a103 MDSM-B reader
+ a107 USB to Memory Stick (LC1) Drive
+ a109 LC1 CompactFlash & SmartMedia Card Reader
+ a10b USB to CF+MS(LC1)
+ a200 DF-UT-06 Hama MMC/SD Reader
+ a400 CompactFlash & Microdrive Reader
+ a600 Card Reader
+ a604 12-in-1 Card Reader
+ ad01 Mass Storage Device
+ ae01 Mass Storage Device
+ af01 Mass Storage Device
+ b000 USB to CF(LC1)
+ b001 USB to CF+PCMCIA
+ b004 MMC/SD Reader
+ b006 USB to PCMCIA
+ b00a USB to CF+SD Drive(LC1)
+ b00b USB to Memory Stick(LC1)
+ c010 Kingston FCR-HS2/ATA Card Reader
+07c5 APG Cash Drawer
+ 0500 Cash Drawer
+07c6 ShareWave, Inc.
+ 0002 Bodega Wireless Access Point
+ 0003 Bodega Wireless Network Adapter
+07c7 Powertech Industrial Co., Ltd
+07c8 B.U.G., Inc.
+ 0202 MN128-SOHO PAL
+07c9 Allied Telesyn International
+ b100 AT-USB100
+07ca AVerMedia Technologies, Inc.
+ 0002 AVerTV PVR USB/EZMaker Pro Device
+ 0026 AVerTV
+ 0337 A867 DVB-T dongle
+ 0837 H837 Hybrid ATSC/QAM
+ 1228 MPEG-2 Capture Device (M038)
+ 1830 AVerTV Volar Video Capture (H830)
+ 3835 AVerTV Volar Green HD (A835B)
+ 850a AverTV Volar Black HD (A850)
+ 850b AverTV Red HD+ (A850T)
+ a309 AVerTV DVB-T (A309)
+ a801 AVerTV DVB-T (A800)
+ a815 AVerTV DVB-T Volar X (A815)
+ a827 AVerTV Hybrid Volar HX (A827)
+ a867 AVerTV DVB-T (A867)
+ b300 A300 DVB-T TV receiver
+ b800 MR800 FM Radio
+ e880 MPEG-2 Capture Device (E880)
+ e882 MPEG-2 Capture Device (E882)
+07cb Kingmax Technology, Inc.
+07cc Carry Computer Eng., Co., Ltd
+ 0000 CF Card Reader
+ 0001 Reader (UICSE)
+ 0002 Reader (UIS)
+ 0003 SM Card Reader
+ 0004 SM/CF/PCMCIA Card Reader
+ 0005 Reader (UISA2SE)
+ 0006 SM/CF/PCMCIA Card Reader
+ 0007 Reader (UISA6SE)
+ 000c SM/CF Card Reader
+ 000d SM/CF Card Reader
+ 000e Reader (UISDA)
+ 000f Reader (UICLIK)
+ 0010 Reader (UISMA)
+ 0012 Reader (UISC6SE-FLASH)
+ 0014 Litronic Fortezza Reader
+ 0030 Mass Storage (UISDMC12S)
+ 0040 Mass Storage (UISDMC13S)
+ 0100 Reader (UID)
+ 0101 Reader (UIM)
+ 0102 Reader (UISDMA)
+ 0103 Reader (UISDMC)
+ 0104 Reader (UISDM)
+ 0200 6-in-1 Card Reader
+ 0201 Mass Storage (UISDMC1S & UISDMC3S)
+ 0202 Mass Storage (UISDMC5S)
+ 0203 Mass Storage (UISMC5S)
+ 0204 Mass Storage (UIM4/5S & UIM7S)
+ 0205 Mass Storage (UIS4/5S & UIS7S)
+ 0206 Mass Storage (UISDMC10S & UISDMC11S)
+ 0207 Mass Storage (UPIDMA)
+ 0208 Mass Storage (UCFC II)
+ 0210 Mass Storage (UPIXXA)
+ 0213 Mass Storage (UPIDA)
+ 0214 Mass Storage (UPIMA)
+ 0215 Mass Storage (UPISA)
+ 0217 Mass Storage (UPISDMA)
+ 0223 Mass Storage (UCIDA)
+ 0224 Mass Storage (UCIMA)
+ 0225 Mass Storage (UIS7S)
+ 0227 Mass Storage (UCIDMA)
+ 0234 Mass Storage (UIM7S)
+ 0235 Mass Storage (UIS4S-S)
+ 0237 Velper (UISDMC4S)
+ 0300 6-in-1 Card Reader
+ 0301 6-in-1 Card Reader
+ 0303 Mass Storage (UID10W)
+ 0304 Mass Storage (UIM10W)
+ 0305 Mass Storage (UIS10W)
+ 0308 Mass Storage (UIC10W)
+ 0309 Mass Storage (UISC3W)
+ 0310 Mass Storage (UISDMA2W)
+ 0311 Mass Storage (UISDMC14W)
+ 0320 Mass Storage (UISDMC4W)
+ 0321 Mass Storage (UISDMC37W)
+ 0330 WINTERREADER Reader
+ 0350 9-in-1 Card Reader
+ 0500 Mass Storage
+ 0501 Mass Storage
+07cd Elektor
+ 0001 USBuart Serial Port
+07cf Casio Computer Co., Ltd
+ 1001 QV-8000SX/5700/3000EX Digicam; Exilim EX-M20
+ 1003 Exilim EX-S500
+ 1004 Exilim EX-Z120
+ 1011 USB-CASIO PC CAMERA
+ 1116 EXILIM EX-Z19
+ 1125 Exilim EX-H10 Digital Camera (mass storage mode)
+ 1133 Exilim EX-Z350 Digital Camera (mass storage mode)
+ 1225 Exilim EX-H10 Digital Camera (PictBridge mode)
+ 1233 Exilim EX-Z350 Digital Camera (PictBridge mode)
+ 2002 E-125 Cassiopeia Pocket PC
+ 3801 WMP-1 MP3-Watch
+ 4001 Label Printer KL-P1000
+ 4007 CW50 Device
+ 4104 Cw75 Device
+ 4107 CW-L300 Device
+ 4500 LV-20 Digital Camera
+ 6101 fx-9750gII
+ 6102 fx-CP400
+ 6801 PL-40R
+ 6802 MIDI Keyboard
+07d0 Dazzle
+ 0001 Digital Video Creator I
+ 0002 Global Village VideoFX Grabber
+ 0003 Fusion Model DVC-50 Rev 1 (NTSC)
+ 0004 DVC-800 (PAL) Grabber
+ 0005 Fusion Video and Audio Ports
+ 0006 DVC 150 Loader Device
+ 0007 DVC 150
+ 0327 Fusion Digital Media Reader
+ 1001 DM-FLEX DFU Adapter
+ 1002 DMHS2 DFU Adapter
+ 1102 CF Reader/Writer
+ 1103 SD Reader/Writer
+ 1104 SM Reader/Writer
+ 1105 MS Reader/Writer
+ 1106 xD/SM Reader/Writer
+ 1202 MultiSlot Reader/Writer
+ 2000 FX2 DFU Adapter
+ 2001 eUSB CompactFlash Reader
+ 4100 Kingsun SF-620 Infrared Adapter
+ 4101 Connectivity Cable (CA-42 clone)
+ 4959 Kingsun KS-959 Infrared Adapter
+07d1 D-Link System
+ 13ec VvBus for Helium 2xx
+ 13ed VvBus for Helium 2xx
+ 13f1 DSL-302G Modem
+ 13f2 DSL-502G Router
+ 3300 DWA-130 802.11n Wireless N Adapter(rev.E) [Realtek RTL8191SU]
+ 3302 DWA-130 802.11n Wireless N Adapter(rev.C2) [Realtek RTL8191SU]
+ 3303 DWA-131 802.11n Wireless N Nano Adapter(rev.A1) [Realtek RTL8192SU]
+ 3304 FR-300USB 802.11bgn Wireless Adapter
+ 3a07 WUA-2340 RangeBooster G Adapter(rev.A) [Atheros AR5523]
+ 3a08 WUA-2340 RangeBooster G Adapter(rev.A) (no firmware) [Atheros AR5523]
+ 3a09 DWA-160 802.11abgn Xtreme N Dual Band Adapter(rev.A2) [Atheros AR9170+AR9104]
+ 3a0d DWA-120 802.11g Wireless 108G Adapter [Atheros AR5523]
+ 3a0f DWA-130 802.11n Wireless N Adapter(rev.D) [Atheros AR9170+AR9102]
+ 3a10 DWA-126 802.11n Wireless Adapter [Atheros AR9271]
+ 3b01 AirPlus G DWL-G122 Wireless Adapter(rev.D) [Marvell 88W8338+88W8010]
+ 3b10 DWA-142 RangeBooster N Adapter [Marvell 88W8362+88W8060]
+ 3b11 DWA-130 802.11n Wireless N Adapter(rev.A1) [Marvell 88W8362+88W8060]
+ 3c03 AirPlus G DWL-G122 Wireless Adapter(rev.C1) [Ralink RT2571W]
+ 3c04 WUA-1340
+ 3c05 EH103 Wireless G Adapter
+ 3c06 DWA-111 802.11bg Wireless Adapter [Ralink RT2571W]
+ 3c07 DWA-110 Wireless G Adapter(rev.A1) [Ralink RT2571W]
+ 3c09 DWA-140 RangeBooster N Adapter(rev.B1) [Ralink RT2870]
+ 3c0a DWA-140 RangeBooster N Adapter(rev.B2) [Ralink RT3072]
+ 3c0b DWA-110 Wireless G Adapter(rev.B) [Ralink RT2870]
+ 3c0d DWA-125 Wireless N 150 Adapter(rev.A1) [Ralink RT3070]
+ 3c0e WUA-2340 RangeBooster G Adapter(rev.B) [Ralink RT2070]
+ 3c0f AirPlus G DWL-G122 Wireless Adapter(rev.E1) [Ralink RT2070]
+ 3c10 DWA-160 802.11abgn Xtreme N Dual Band Adapter(rev.A1) [Atheros AR9170+AR9104]
+ 3c11 DWA-160 Xtreme N Dual Band USB Adapter(rev.B) [Ralink RT2870]
+ 3c13 DWA-130 802.11n Wireless N Adapter(rev.B) [Ralink RT2870]
+ 3c15 DWA-140 RangeBooster N Adapter(rev.B3) [Ralink RT2870]
+ 3c16 DWA-125 Wireless N 150 Adapter(rev.A2) [Ralink RT3070]
+ 3e02 DWM-156 3.75G HSUPA Adapter
+ 5100 Remote NDIS Device
+ a800 DWM-152 3.75G HSUPA Adapter
+ f101 DBT-122 Bluetooth
+ fc01 DBT-120 Bluetooth Adapter
+07d2 Aptio Products, Inc.
+07d3 Cyberdata Corp.
+07d5 Radiant Systems
+07d7 GCC Technologies, Inc.
+07da Arasan Chip Systems
+07de Diamond Multimedia
+ 2820 VC500 Video Capture Dongle
+07df David Electronics Co., Ltd
+07e0 NCP engineering GmbH
+ 4742 VPN GovNet Box
+07e1 Ambient Technologies, Inc.
+ 5201 V.90 Modem
+07e2 Elmeg GmbH & Co., Ltd
+07e3 Planex Communications, Inc.
+07e4 Movado Enterprise Co., Ltd
+ 0967 SCard R/W CSR-145
+ 0968 SCard R/W CSR-145
+07e5 QPS, Inc.
+ 05c2 IDE-to-USB2.0 PCA
+ 5c01 Que! CDRW
+07e6 Allied Cable Corp.
+07e7 Mirvo Toys, Inc.
+07e8 Labsystems
+07ea Iwatsu Electric Co., Ltd
+07eb Double-H Technology Co., Ltd
+07ec Taiyo Electric Wire & Cable Co., Ltd
+07ee Torex Retail (formerly Logware)
+ 0002 Cash Drawer I/F
+07ef STSN
+ 0001 Internet Access Device
+07f2 Microcomputer Applications, Inc.
+ 0001 KEYLOK II
+07f6 Circuit Assembly Corp.
+07f7 Century Corp.
+ 0005 ScanLogic/Century Corporation uATA
+ 011e Century USB Disk Enclosure
+07f9 Dotop Technology, Inc.
+07fa DrayTek Corp.
+ 0778 miniVigor 128 ISDN TA
+ 0846 ISDN TA [HFC-S]
+ 0847 ISDN TA [HFC-S]
+ 1012 BeWAN ADSL USB ST (grey)
+ 1196 BWIFI-USB54AR 802.11bg
+ a904 BeWAN ADSL
+ a905 BeWAN ADSL ST
+07fc Thomann
+ 1113 SWISSONIC EasyKeys61 Midikeyboard
+07fd Mark of the Unicorn
+ 0000 FastLane MIDI Interface
+ 0001 MIDI Interface
+ 0002 MOTU Audio for 64 bit
+07ff Unknown
+ 00ff Portable Hard Drive
+0801 MagTek
+ 0001 Mini Swipe Reader (Keyboard Emulation)
+ 0002 Mini Swipe Reader
+ 0003 Magstripe Insert Reader
+0802 Mako Technologies, LLC
+0803 Zoom Telephonics, Inc.
+ 1300 V92 Faxmodem
+ 3095 V.92 56K Mini External Modem Model 3095
+ 4310 4410a Wireless-G Adapter [Intersil ISL3887]
+ 4410 4410b Wireless-G Adapter [ZyDAS ZD1211B]
+ 5241 Cable Modem
+ 5551 DSL Modem
+ 9700 2986L FaxModem
+ 9800 Cable Modem
+ a312 Wireless-G
+0809 Genicom Technology, Inc.
+080a Evermuch Technology Co., Ltd
+080b Cross Match Technologies
+ 0002 Fingerprint Scanner (After ReNumeration)
+ 0010 300LC Series Fingerprint Scanner (Before ReNumeration)
+080c Datalogic S.p.A.
+ 0300 Gryphon D120 Barcode Scanner
+ 0400 Gryphon D120 Barcode Scanner
+ 0500 Gryphon D120 Barcode Scanner
+ 0600 Gryphon M100 Barcode Scanner
+080d Teco Image Systems Co., Ltd
+ 0102 Hercules Scan@home 48
+ 0104 3.2Slim
+ 0110 UMAX AstraSlim 1200 Scanner
+0810 Personal Communication Systems, Inc.
+ 0001 Dual PSX Adaptor
+ 0002 Dual PCS Adaptor
+ 0003 PlayStation Gamepad
+ e501 SNES Gamepad
+0813 Mattel, Inc.
+ 0001 Intel Play QX3 Microscope
+ 0002 Dual Mode Camera Plus
+0819 eLicenser
+ 0101 License Management and Copy Protection
+081a MG Logic
+ 1000 Duo Pen Tablet
+081b Indigita Corp.
+ 0600 Storage Adapter
+ 0601 Storage Adapter
+081c Mipsys
+081e AlphaSmart, Inc.
+ df00 Handheld
+0822 Reudo Corp.
+ 2001 IRXpress Infrared Device
+0825 GC Protronics
+0826 Data Transit
+0827 BroadLogic, Inc.
+0828 Sato Corp.
+0829 DirecTV Broadband, Inc. (Telocity)
+082d Handspring
+ 0100 Visor
+ 0200 Treo
+ 0300 Treo 600
+ 0400 Handheld
+ 0500 Handheld
+ 0600 Handheld
+0830 Palm, Inc.
+ 0001 m500
+ 0002 m505
+ 0003 m515
+ 0004 Handheld
+ 0005 Handheld
+ 0006 Handheld
+ 0010 Handheld
+ 0011 Handheld
+ 0012 Handheld
+ 0013 Handheld
+ 0014 Handheld
+ 0020 i705
+ 0021 Handheld
+ 0022 Handheld
+ 0023 Handheld
+ 0024 Handheld
+ 0030 Handheld
+ 0031 Tungsten W
+ 0032 Handheld
+ 0033 Handheld
+ 0034 Handheld
+ 0040 m125
+ 0041 Handheld
+ 0042 Handheld
+ 0043 Handheld
+ 0044 Handheld
+ 0050 m130
+ 0051 Handheld
+ 0052 Handheld
+ 0053 Handheld
+ 0054 Handheld
+ 0060 Tungsten C/E/T/T2/T3 / Zire 71
+ 0061 Lifedrive / Treo 650/680 / Tunsten E2/T5/TX / Centro / Zire 21/31/72 / Z22
+ 0062 Handheld
+ 0063 Handheld
+ 0064 Handheld
+ 0070 Zire
+ 0071 Handheld
+ 0072 Handheld
+ 0080 Serial Adapter [for Palm III]
+ 0081 Handheld
+ 0082 Handheld
+ 00a0 Treo 800w
+ 0101 Pre
+0832 Kouwell Electronics Corp.
+ 5850 Cable
+0833 Sourcenext Corp.
+ 012e KeikaiDenwa 8 with charger
+ 039f KeikaiDenwa 8
+0835 Action Star Enterprise Co., Ltd
+0836 TrekStor
+ 2836 i.Beat mood
+0839 Samsung Techwin Co., Ltd
+ 0005 Digimax Camera
+ 0008 Digimax 230 Camera
+ 0009 Digimax 340
+ 000a Digimax 410
+ 000e Digimax 360
+ 0010 Digimax 300
+ 1003 Digimax 210SE
+ 1005 Digimax 220
+ 1009 Digimax V4
+ 1012 6500 Document Camera
+ 1058 S730 Camera
+ 1064 Digimax D830 Camera
+ 1542 Digimax 50 Duo
+ 3000 Digimax 35 MP3
+083a Accton Technology Corp.
+ 1046 10/100 Ethernet [pegasus]
+ 1060 HomeLine Adapter
+ 1f4d SMC8013WG Broadband Remote NDIS Device
+ 3046 10/100 Series Adapter
+ 3060 1/10/100 Adapter
+ 3501 2664W
+ 3502 WN3501D Wireless Adapter
+ 3503 T-Sinus 111 Wireless Adapter
+ 4501 T-Sinus 154data
+ 4502 Siemens S30853-S1016-R107 802.11g Wireless Adapter [Intersil ISL3886]
+ 4505 SMCWUSB-G 802.11bg
+ 4507 SMCWUSBT-G2 802.11g Wireless Adapter [Atheros AR5523]
+ 4521 Siemens S30863-S1016-R107-2 802.11g Wireless Adapter [Intersil ISL3887]
+ 4531 T-Com Sinus 154 data II [Intersil ISL3887]
+ 5046 SpeedStream 10/100 Ethernet [pegasus]
+ 5501 Wireless Adapter 11g
+ 6500 Cable Modem
+ 6618 802.11n Wireless Adapter
+ 7511 Arcadyan 802.11N Wireless Adapter
+ 7512 Arcadyan 802.11N Wireless Adapter
+ 7522 Arcadyan 802.11N Wireless Adapter
+ 8522 Arcadyan 802.11N Wireless Adapter
+ 8541 WN4501F 802.11g Wireless Adapter [Intersil ISL3887]
+ a512 Arcadyan 802.11N Wireless Adapter
+ a618 SMCWUSBS-N EZ Connect N Draft 11n Wireless Adapter [Ralink RT2870]
+ a701 SMCWUSBS-N3 EZ Connect N Wireless Adapter [Ralink RT3070]
+ b004 CPWUE001 USB/Ethernet Adapter
+ b522 SMCWUSBS-N2 EZ Connect N Wireless Adapter [Ralink RT2870]
+ bb01 BlueExpert Bluetooth Device
+ c003 802.11b Wireless Adapter
+ c501 Zoom 4410 Wireless-G [Intersil ISL3887]
+ c561 802.11a/g Wireless Adapter
+ d522 Speedport W 102 Stick IEEE 802.11n USB 2.0 Adapter
+ e501 ZD1211B
+ e503 Arcadyan WN4501 802.11b/g
+ e506 WUS-201 802.11bg
+ f501 802.11g Wireless Adapter
+ f502 802.11g Wireless Adapter
+ f522 Arcadyan WN7512 802.11n
+083f Global Village
+ b100 TelePort V.90 Fax/Modem
+0840 Argosy Research, Inc.
+ 0060 Storage Adapter Bridge Module
+0841 Rioport.com, Inc.
+ 0001 Rio 500
+0844 Welland Industrial Co., Ltd
+0846 NetGear, Inc.
+ 1001 EA101 10 Mbps 10BASE-T Ethernet [Kawasaki LSI KL5KLUSB101B]
+ 1002 Ethernet
+ 1020 FA101 Fast Ethernet USB 1.1
+ 1040 FA120 Fast Ethernet USB 2.0 [Asix AX88172 / AX8817x]
+ 1100 Managed Switch M4100 series, M5300 series, M7100 series
+ 4110 MA111(v1) 802.11b Wireless [Intersil Prism 3.0]
+ 4200 WG121(v1) 54 Mbps Wireless [Intersil ISL3886]
+ 4210 WG121(v2) 54 Mbps Wireless [Intersil ISL3886]
+ 4220 WG111(v1) 54 Mbps Wireless [Intersil ISL3886]
+ 4230 MA111(v2) 802.11b Wireless [SIS SIS 162]
+ 4240 WG111(v1) rev 2 54 Mbps Wireless [Intersil ISL3887]
+ 4260 WG111v3 54 Mbps Wireless [realtek RTL8187B]
+ 4300 WG111U Double 108 Mbps Wireless [Atheros AR5004X / AR5005UX]
+ 4301 WG111U (no firmware) Double 108 Mbps Wireless [Atheros AR5004X / AR5005UX]
+ 5f00 WPN111 802.11g Wireless Adapter [Atheros AR5523]
+ 6a00 WG111v2 54 Mbps Wireless [RealTek RTL8187L]
+ 7100 WN121T RangeMax Next Wireless-N [Marvell TopDog]
+ 9000 WN111(v1) RangeMax Next Wireless [Marvell 88W8362+88W8060]
+ 9001 WN111(v2) RangeMax Next Wireless [Atheros AR9170+AR9101]
+ 9010 WNDA3100v1 802.11abgn [Atheros AR9170+AR9104]
+ 9011 WNDA3100v2 802.11abgn [Broadcom BCM4323]
+ 9012 WNDA4100 802.11abgn 3x3:3 [Ralink RT3573]
+ 9014 WNDA3100v3 802.11abgn 2x2:2 [MediaTek MT7632U]
+ 9018 WNDA3200 802.11abgn Wireless Adapter [Atheros AR7010+AR9280]
+ 9020 WNA3100(v1) Wireless-N 300 [Broadcom BCM43231]
+ 9021 WNA3100M(v1) Wireless-N 300 [Realtek RTL8192CU]
+ 9030 WNA1100 Wireless-N 150 [Atheros AR9271]
+ 9040 WNA1000 Wireless-N 150 [Atheros AR9170+AR9101]
+ 9041 WNA1000M 802.11bgn [Realtek RTL8188CUS]
+ 9042 On Networks N150MA 802.11bgn [Realtek RTL8188CUS]
+ 9043 WNA1000Mv2 802.11bgn [Realtek RTL8188CUS?]
+ 9050 A6200 802.11a/b/g/n/ac Wireless Adapter [Broadcom BCM43526]
+ 9052 A6100 AC600 DB Wireless Adapter [Realtek RTL8811AU]
+ a001 PA101 10 Mbps HPNA Home Phoneline RJ-1
+ f001 On Networks N300MA 802.11bgn [Realtek RTL8192CU]
+084d Minton Optic Industry Co., Inc.
+ 0001 Jenoptik JD800i
+ 0003 S-Cam F5/D-Link DSC-350 Digital Camera
+ 0011 Argus DC3500 Digital Camera
+ 0014 Praktica DC 32
+ 0019 Praktica DPix3000
+ 0025 Praktica DC 60
+ 1001 ScanHex SX-35d
+084e KB Gear
+ 0001 JamCam Camera
+ 1001 Jam Studio Tablet
+ 1002 Pablo Tablet
+084f Empeg
+ 0001 Empeg-Car Mark I/II Player
+0850 Fast Point Technologies, Inc.
+0851 Macronix International Co., Ltd
+ 1542 SiPix Blink
+ 1543 Maxell WS30 Slim Digital Camera, or Pandigital PI8004W01 digital photo frame
+ a168 MXIC
+0852 CSEM
+0853 Topre Corporation
+ 0100 HHKB Professional
+0854 ActiveWire, Inc.
+ 0100 I/O Board
+ 0101 I/O Board, rev1
+0856 B&B Electronics
+ ac01 uLinks USOTL4 RS422/485 Adapter
+0858 Hitachi Maxell, Ltd
+ 3102 Bluetooth Device
+ ffff Maxell module with BlueCore in DFU mode
+0859 Minolta Systems Laboratory, Inc.
+085a Xircom
+ 0001 Portstation Dual Serial Port
+ 0003 Portstation Paraller Port
+ 0008 Ethernet
+ 0009 Ethernet
+ 000b Portstation Dual PS/2 Port
+ 0021 1 port to Serial Converter
+ 0022 Parallel Port
+ 0023 2 port to Serial Converter
+ 0024 Parallel Port
+ 0026 PortGear SCSI
+ 0027 1 port to Serial Converter
+ 0028 PortGear to SCSI Converter
+ 0032 PortStation SCSI Module
+ 003c Bluetooth Adapter
+ 0299 Colorvision, Inc. Monitor Spyder
+ 8021 1 port to Serial
+ 8023 2 port to Serial
+ 8027 PGSDB9 Serial Port
+085c ColorVision, Inc.
+ 0100 Spyder 1
+ 0200 Spyder 2
+ 0300 Spyder 3
+ 0400 Spyder 4
+0862 Teletrol Systems, Inc.
+0863 Filanet Corp.
+0864 NetGear, Inc.
+ 4100 MA101 802.11b Adapter
+ 4102 MA101 802.11b Adapter
+0867 Data Translation, Inc.
+ 9812 ECON Data acquisition unit
+ 9816 DT9816 ECON data acquisition module
+ 9836 DT9836 data acquisition card
+086a Emagic Soft- und Hardware GmbH
+ 0001 Unitor8
+ 0002 AMT8
+ 0003 MT4
+086c DeTeWe - Deutsche Telephonwerke AG & Co.
+ 1001 Eumex 504PC ISDN TA
+ 1002 Eumex 504PC (FlashLoad)
+ 1003 TA33 ISDN TA
+ 1004 TA33 (FlashLoad)
+ 1005 Eumex 604PC HomeNet
+ 1006 Eumex 604PC HomeNet (FlashLoad)
+ 1007 Eumex 704PC DSL
+ 1008 Eumex 704PC DSL (FlashLoad)
+ 1009 Eumex 724PC DSL
+ 100a Eumex 724PC DSL (FlashLoad)
+ 100b OpenCom 30
+ 100c OpenCom 30 (FlashLoad)
+ 100d BeeTel Home 100
+ 100e BeeTel Home 100 (FlashLoad)
+ 1011 USB2DECT
+ 1012 USB2DECT (FlashLoad)
+ 1013 Eumex 704PC LAN
+ 1014 Eumex 704PC LAN (FlashLoad)
+ 1019 Eumex 504 SE
+ 101a Eumex 504 SE (Flash-Mode)
+ 1021 OpenCom 40
+ 1022 OpenCom 40 (FlashLoad)
+ 1023 OpenCom 45
+ 1024 OpenCom 45 (FlashLoad)
+ 1025 Sinus 61 data
+ 1029 dect BOX
+ 102c Eumex 604PC HomeNet [FlashLoad]
+ 1030 Eumex 704PC DSL [FlashLoad]
+ 1032 OpenCom 40 [FlashLoad]
+ 1033 OpenCom 30 plus
+ 1034 OpenCom 30 plus (FlashLoad)
+ 1041 Eumex 220PC
+ 1042 Eumex 220PC (FlashMode)
+ 1055 Eumex 220 Version 2 ISDN TA
+ 1056 Eumex 220 Version 2 ISDN TA (Flash-Mode)
+ 2000 OpenCom 1000
+086e System TALKS, Inc.
+ 1920 SGC-X2UL
+086f MEC IMEX, Inc.
+0870 Metricom
+ 0001 Ricochet GS
+0871 SanDisk, Inc.
+ 0001 SDDR-01 Compact Flash Reader
+ 0002 SDDR-31 Compact Flash Reader
+ 0005 SDDR-05 Compact Flash Reader
+0873 Xpeed, Inc.
+0874 A-Tec Subsystem, Inc.
+0879 Comtrol Corp.
+087c Adesso/Kbtek America, Inc.
+087d Jaton Corp.
+ 5704 Ethernet
+087e Fujitsu Computer Products of America
+087f QualCore Logic Inc.
+0880 APT Technologies, Inc.
+0883 Recording Industry Association of America (RIAA)
+0885 Boca Research, Inc.
+0886 XAC Automation Corp.
+ 0630 Intel PC Camera CS630
+0887 Hannstar Electronics Corp.
+088a TechTools
+ 1002 DigiView DV3100
+088b MassWorks, Inc.
+ 4944 MassWorks ID-75 TouchScreen
+088c Swecoin AB
+ 2030 Ticket Printer TTP 2030
+088e iLok
+ 5036 Portable secure storage for software licenses
+0892 DioGraphy, Inc.
+ 0101 Smartdio Reader/Writer
+0894 TSI Incorporated
+ 0010 Remote NDIS Network Device
+0897 Lauterbach
+ 0002 Power Debug/Power Debug II
+089c United Technologies Research Cntr.
+089d Icron Technologies Corp.
+089e NST Co., Ltd
+089f Primex Aerospace Co.
+08a5 e9, Inc.
+08a6 Toshiba TEC
+ 0051 B-SV4
+08a8 Andrea Electronics
+08a9 CWAV Inc.
+ 0005 USBee ZX
+ 0009 USBee SX
+ 0012 USBee AX-Standard
+ 0013 USBee AX-Plus
+ 0014 USBee AX-Pro
+ 0015 USBee DX
+08ac Macraigor Systems LLC
+ 2024 usbWiggler
+08ae Macally (Mace Group, Inc.)
+08b0 Metrohm
+ 0006 814 Sample Processor
+ 0015 857 Titrando
+ 001a 852 Titrando
+08b4 Sorenson Vision, Inc.
+08b7 NATSU
+ 0001 Playstation adapter
+08b8 J. Gordon Electronic Design, Inc.
+ 01f4 USBSIMM1
+08b9 RadioShack Corp. (Tandy)
+08bb Texas Instruments
+ 2702 PCM2702 16-bit stereo audio DAC
+ 2704 PCM2704 16-bit stereo audio DAC
+ 2705 PCM2705 stereo audio DAC
+ 2706 PCM2706 stereo audio DAC
+ 2707 PCM2707 stereo audio DAC
+ 27c4 PCM2704C stereo audio DAC
+ 27c5 PCM2705C stereo audio DAC
+ 27c6 PCM2706C stereo audio DAC
+ 27c7 PCM2707C stereo audio DAC
+ 2900 PCM2900 Audio Codec
+ 2901 PCM2901 Audio Codec
+ 2902 PCM2902 Audio Codec
+ 2904 PCM2904 Audio Codec
+ 2910 PCM2912 Audio Codec
+ 2912 PCM2912A Audio Codec
+ 29b0 PCM2900B Audio CODEC
+ 29b2 PCM2902 Audio CODEC
+ 29b3 PCM2903B Audio CODEC
+ 29b6 PCM2906B Audio CODEC
+ 29c0 PCM2900C Audio CODEC
+ 29c2 PCM2902C Audio CODEC
+ 29c3 PCM2903C Audio CODEC
+ 29c6 PCM2906C Audio CODEC
+08bd Citizen Watch Co., Ltd
+ 0208 CLP-521 Label Printer
+ 1100 X1-USB Floppy
+08c3 Precise Biometrics
+ 0001 100 SC
+ 0002 100 A
+ 0003 100 SC BioKeyboard
+ 0006 100 A BioKeyboard
+ 0100 100 MC ISP
+ 0101 100 MC FingerPrint and SmartCard Reader
+ 0300 100 AX
+ 0400 100 SC
+ 0401 150 MC
+ 0402 200 MC FingerPrint and SmartCard Reader
+ 0404 100 SC Upgrade
+ 0405 150 MC Upgrade
+ 0406 100 MC Upgrade
+08c4 Proxim, Inc.
+ 0100 Skyline 802.11b Wireless Adapter
+ 02f2 Farallon Home Phoneline Adapter
+08c7 Key Nice Enterprise Co., Ltd
+08c8 2Wire, Inc.
+08c9 Nippon Telegraph and Telephone Corp.
+08ca Aiptek International, Inc.
+ 0001 Tablet
+ 0010 Tablet
+ 0020 APT-6000U Tablet
+ 0021 APT-2 Tablet
+ 0022 Tablet
+ 0023 Tablet
+ 0024 Tablet
+ 0100 Pen Drive
+ 0102 DualCam
+ 0103 Pocket DV Digital Camera
+ 0104 Pocket DVII
+ 0105 Mega DV(Disk)
+ 0106 Pocket DV3100+
+ 0107 Pocket DV3100
+ 0109 Nisis DV4 Digital Camera
+ 010a Trust 738AV LCD PV Mass Storage
+ 0111 PenCam VGA Plus
+ 2008 Mini PenCam 2
+ 2010 Pocket CAM 3 Mega (webcam)
+ 2011 Pocket CAM 3 Mega (storage)
+ 2016 PocketCam 2 Mega
+ 2018 Pencam SD 2M
+ 2019 Pencam SD 2M (mass storage mode)
+ 2020 Slim 3000F
+ 2022 Slim 3200
+ 2024 Pocket DV3500
+ 2028 Pocket Cam4M
+ 2040 Pocket DV4100M
+ 2042 Pocket DV5100M Composite Device
+ 2043 Pocket DV5100M (Disk)
+ 2060 Pocket DV5300
+08cd Jue Hsun Ind. Corp.
+08ce Long Well Electronics Corp.
+08cf Productivity Enhancement Products
+08d1 smartBridges, Inc.
+ 0001 smartNIC Ethernet [catc]
+ 0003 smartNIC 2 PnP Ethernet
+08d3 Virtual Ink
+08d4 Fujitsu Siemens Computers
+ 0009 SCR SmartCard Reader
+08d8 IXXAT Automation GmbH
+ 0002 USB-to-CAN compact
+ 0003 USB-to-CAN II
+ 0100 USB-to-CAN
+08d9 Increment P Corp.
+08dd Billionton Systems, Inc.
+ 0112 Wireless LAN Adapter
+ 0113 Wireless LAN Adapter
+ 0986 USB-100N Ethernet [pegasus]
+ 0987 USBLP-100 HomePNA Ethernet [pegasus]
+ 0988 USBEL-100 Ethernet [pegasus]
+ 1986 10/100 LAN Adapter
+ 2103 DVB-T TV-Tuner Card-R
+ 8511 USBE-100 Ethernet [pegasus2]
+ 90ff USB2AR Ethernet
+08de ???
+ 7a01 802.11b Adapter
+08df Spyrus, Inc.
+ 0001 Rosetta Token V1
+ 0002 Rosetta Token V2
+ 0003 Rosetta Token V3
+ 0a00 Lynks Interface
+08e3 Olitec, Inc.
+ 0002 USB-RS232 Bridge
+ 0100 Interface ADSL
+ 0101 Interface ADSL
+ 0102 ADSL
+ 0301 RNIS ISDN TA [HFC-S]
+08e4 Pioneer Corp.
+ 0184 DDJ-WeGO
+ 0185 DDJ-WeGO2
+08e5 Litronic
+08e6 Gemalto (was Gemplus)
+ 0001 GemPC-Touch 430
+ 0430 GemPC430 SmartCard Reader
+ 0432 GemPC432 SmartCard Reader
+ 0435 GemPC435 SmartCard Reader
+ 0437 GemPC433 SL SmartCard Reader
+ 1359 UA SECURE STORAGE TOKEN
+ 2202 Gem e-Seal Pro Token
+ 3437 GemPC Twin SmartCard Reader
+ 3438 GemPC Key SmartCard Reader
+ 3478 PinPad Smart Card Reader
+ 34ec Compact Smart Card Reader Writer
+ 4433 GemPC433-Swap
+ 5501 GemProx-PU Contactless Smart Card Reader
+ 5503 Prox-DU Contactless Interface
+ ace0 UA HYBRID TOKEN
+08e7 Pan-International Wire & Cable
+08e8 Integrated Memory Logic
+08e9 Extended Systems, Inc.
+ 0100 XTNDAccess IrDA Dongle
+08ea Ericsson, Inc., Blue Ridge Labs
+ 00c9 ADSL Modem HM120dp Loader
+ 00ca ADSL WAN Modem HM120dp
+ 00ce HM230d Virtual Bus for Helium
+ abba USB Driver for Bluetooth Wireless Technology
+ abbb Bluetooth Device in DFU State
+08ec M-Systems Flash Disk Pioneers
+ 0001 TravelDrive 2C
+ 0002 TravelDrive 2C
+ 0005 TravelDrive 2C
+ 0008 TravelDrive 2C
+ 0010 DiskOnKey
+ 0011 DiskOnKey
+ 0012 TravelDrive 2C
+ 0014 TravelDrive 2C
+ 0015 Kingston DataTraveler ELITE
+ 0016 Kingston DataTraveler U3
+ 0020 TravelDrive Intuix U3 2GB
+ 0021 TravelDrive
+ 0022 TravelDrive
+ 0023 TravelDrive
+ 0024 TravelDrive
+ 0025 TravelDrive
+ 0026 TravelDrive
+ 0027 TravelDrive
+ 0028 TravelDrive
+ 0029 TravelDrive
+ 0030 TravelDrive
+ 0822 TravelDrive 2C
+ 0832 Hi-Speed Mass Storage Device
+ 0834 M-Disk 220
+ 0998 Kingston Data Traveler2.0 Disk Driver
+ 0999 Kingston Data Traveler2.0 Disk Driver
+ 1000 TravelDrive 2C
+ 2000 TravelDrive 2C
+ 2038 TravelDrive
+ 2039 TravelDrive
+ 204a TravelDrive
+ 204b TravelDrive
+08ed MediaTek Inc.
+ 0002 CECT M800 memory card
+08ee CCSI/Hesso
+08f0 Corex Technologies
+ 0005 CardScan 800c
+08f1 CTI Electronics Corp.
+08f2 Gotop Information Inc.
+ 007f Super Q2 Tablet
+08f5 SysTec Co., Ltd
+08f6 Logic 3 International, Ltd
+08f7 Vernier
+ 0001 LabPro
+ 0002 EasyTemp/Go!Temp
+ 0003 Go!Link
+ 0004 Go!Motion
+08f8 Keen Top International Enterprise Co., Ltd
+08f9 Wipro Technologies
+08fa Caere
+08fb Socket Communications
+08fc Sicon Cable Technology Co., Ltd
+08fd Digianswer A/S
+ 0001 Bluetooth Device
+08ff AuthenTec, Inc.
+ 1600 AES1600
+ 1610 AES1600
+ 1660 AES1660 Fingerprint Sensor
+ 1680 AES1660 Fingerprint Sensor
+ 168f AES1660 Fingerprint Sensor
+ 2500 AES2501
+ 2501 AES2501
+ 2502 AES2501
+ 2503 AES2501
+ 2504 AES2501
+ 2505 AES2501
+ 2506 AES2501
+ 2507 AES2501
+ 2508 AES2501
+ 2509 AES2501
+ 250a AES2501
+ 250b AES2501
+ 250c AES2501
+ 250d AES2501
+ 250e AES2501
+ 250f AES2501
+ 2510 AES2510
+ 2550 AES2550 Fingerprint Sensor
+ 2580 AES2501 Fingerprint Sensor
+ 2588 AES2501
+ 2589 AES2501
+ 258a AES2501
+ 258b AES2501
+ 258c AES2501
+ 258d AES2501
+ 258e AES2501
+ 258f AES2501
+ 2660 AES2660 Fingerprint Sensor
+ 2680 AES2660 Fingerprint Sensor
+ 268f AES2660 Fingerprint Sensor
+ 2810 AES2810
+ 3400 AES3400 TruePrint Sensor
+ 3401 AES3400 Sensor
+ 3402 AES3400 Sensor
+ 3403 AES3400 Sensor
+ 3404 AES3400 TruePrint Sensor
+ 3405 AES3400 TruePrint Sensor
+ 3406 AES3400 TruePrint Sensor
+ 3407 AES3400 TruePrint Sensor
+ 4902 BioMV with TruePrint AES3500
+ 4903 BioMV with TruePrint AES3400
+ 5500 AES4000
+ 5501 AES4000 TruePrint Sensor
+ 5503 AES4000 TruePrint Sensor
+ 5505 AES4000 TruePrint Sensor
+ 5507 AES4000 TruePrint Sensor
+ 55ff AES4000 TruePrint Sensor.
+ 5700 AES3500 Fingerprint Reader
+ 5701 AES3500 TruePrint Sensor
+ 5702 AES3500 TruePrint Sensor
+ 5703 AES3500 TruePrint Sensor
+ 5704 AES3500-BZ TruePrint Sensor
+ 5705 AES3500-BZ TruePrint Sensor
+ 5706 AES3500-BZ TruePrint Sensor
+ 5707 AES3500-BZ TruePrint Sensor
+ 5710 AES3500 TruePrint Sensor
+ 5711 AES3500 TruePrint Sensor
+ 5712 AES3500 TruePrint Sensor
+ 5713 AES3500 TruePrint Sensor
+ 5714 AES3500-BZ TruePrint Sensor
+ 5715 AES3500-BZ TruePrint Sensor
+ 5716 AES3500-BZ TruePrint Sensor
+ 5717 AES3500-BZ TruePrint Sensor
+ 5730 AES3500 TruePrint Sensor
+ 5731 AES3500 TruePrint Sensor
+ 5732 AES3500 TruePrint Sensor
+ 5733 AES3500 TruePrint Sensor
+ 5734 AES3500-BZ TruePrint Sensor
+ 5735 AES3500-BZ TruePrint Sensor
+ 5736 AES3500-BZ TruePrint Sensor
+ 5737 AES3500-BZ TruePrint Sensor
+ afe3 FingerLoc Sensor Module (Anchor)
+ afe4 FingerLoc Sensor Module (Anchor)
+ afe5 FingerLoc Sensor Module (Anchor)
+ afe6 FingerLoc Sensor Module (Anchor)
+ fffd AES2510 Sensor (USB Emulator)
+ ffff Sensor (Emulator)
+0900 Pinnacle Systems, Inc.
+0901 VST Technologies
+ 0001 Hard Drive Adapter (TPP)
+ 0002 SigmaDrive Adapter (TPP)
+0906 Faraday Technology Corp.
+0908 Siemens AG
+ 01f4 SIMATIC NET CP 5711
+ 01fe SIMATIC NET PC Adapter A2
+ 04b1 MediSET
+ 04b2 NC interface
+ 2701 ShenZhen SANZHAI Technology Co.,Ltd Spy Pen VGA
+0909 Audio-Technica Corp.
+090a Trumpion Microelectronics, Inc.
+ 1001 T33520 Flash Card Controller
+ 1100 Comotron C3310 MP3 player
+ 1200 MP3 player
+ 1540 Digitex Container Flash Disk
+090b Neurosmith
+090c Silicon Motion, Inc. - Taiwan (formerly Feiya Technology Corp.)
+ 0371 Silicon Motion SM371 Camera
+ 0373 Silicon Motion Camera
+ 037a Silicon Motion Camera
+ 037b Silicon Motion Camera
+ 037c 300k Pixel Camera
+ 1000 Flash Drive
+ 1132 5-in-1 Card Reader
+ 337b Silicon Motion Camera
+ 3710 Silicon Motion Camera
+ 3720 Silicon Motion Camera
+ 37bc HP Webcam-101 Integrated Camera
+ 37c0 Silicon Motion Camera
+ 6000 SD/SDHC Card Reader (SG365 / FlexiDrive XC+)
+ 6200 microSD card reader
+ 71b3 SM731 Camera
+ 837b Silicon Motion Camera
+ 937b Silicon Motion Camera
+ b370 Silicon Motion SM370 Camera
+ b371 Silicon Motion SM371 Camera
+ f37d Endoscope camera
+090d Multiport Computer Vertriebs GmbH
+090e Shining Technology, Inc.
+090f Fujitsu Devices, Inc.
+0910 Alation Systems, Inc.
+0911 Philips Speech Processing
+ 149a SpeechMike II Pro Plus LFH5276
+ 2512 SpeechMike Pro
+0912 Voquette, Inc.
+0915 GlobeSpan, Inc.
+ 0001 DSL Modem
+ 0002 ADSL ATM Modem
+ 0005 LAN Modem
+ 2000 802.11 Adapter
+ 2002 802.11 Adapter
+ 8000 ADSL LAN Modem
+ 8005 DSL-302G Modem
+ 8101 ADSL WAN Modem
+ 8102 DSL-200 ADSL Modem
+ 8103 DSL-200 ADSL Modem
+ 8104 DSL-200 Modem
+ 8400 DSL Modem
+ 8401 DSL Modem
+ 8402 DSL Modem
+ 8500 DSL Modem
+ 8501 DSL Modem
+0917 SmartDisk Corp.
+ 0001 eFilm Reader-11 SM/CF
+ 0002 eFilm Reader-11 SM
+ 0003 eFilm Reader-11 CF
+ 0200 FireFly
+ 0201 FireLite
+ 0202 STORAGE ADAPTER (FirePower)
+ 0204 FlashTrax Storage
+ 0205 STORAGE ADAPTER (CrossFire)
+ 0206 FireFly 20G HDD
+ 0207 FireLite
+ 020f STORAGE ADAPTER (FireLite)
+ da01 eFilm Reader-11 Test
+ ffff eFilm Reader-11 (Class/PDR)
+0919 Tiger Electronics
+ 0100 Fast Flicks Digital Camera
+091e Garmin International
+ 0003 GPS (various models)
+ 0004 iQue 3600
+ 0200 Data Card Programmer (install)
+ 086e Forerunner 735XT
+ 097f Forerunner 235
+ 1200 Data Card Programmer
+ 21a5 etrex Cx (msc)
+ 2236 nuvi 360
+ 2271 Edge 605/705
+ 2295 Colorado 300
+ 22b6 eTrex Vista HCx (Mass Storage mode)
+ 231b Oregon 400t
+ 2353 Nüvi 205T
+ 2380 Oregon series
+ 23cc nüvi 1350
+ 2459 GPSmap 62/78 series
+ 2491 Edge 800
+ 2519 eTrex 30
+ 2535 Edge 800
+ 253c GPSmap 62sc
+ 255b Nuvi 2505LM
+ 26a1 Nuvi 55
+ 47fb nuviCam
+0920 Echelon Co.
+ 7500 Network Interface
+0921 GoHubs, Inc.
+ 1001 GoCOM232 Serial
+0922 Dymo-CoStar Corp.
+ 0007 LabelWriter 330
+ 0009 LabelWriter 310
+ 0019 LabelWriter 400
+ 001a LabelWriter 400 Turbo
+ 0020 LabelWriter 450
+ 1001 LabelManager PnP
+ 8004 M25 Digital Postal Scale
+0923 IC Media Corp.
+ 010f SIIG MobileCam
+0924 Xerox
+ 23dd DocuPrint M760 (X760_USB)
+ 3ce8 Phaser 3428 Printer
+ 3cea Phaser 3125
+ 3cec Phaser 3250
+ 3d5b Phaser 6115MFP TWAIN Scanner
+ 3d6d WorkCentre 6015N/NI
+ 420f WorkCentre PE220 Series
+ 421f M20 Scanner
+ 423b Printing Support
+ 4274 Xerox Phaser 3635MFPX
+ ffef WorkCenter M15
+ fffb DocuPrint M750 (X750_USB)
+0925 Lakeview Research
+ 0005 Gamtec.,Ltd SmartJoy PLUS Adapter
+ 03e8 Wii Classic Controller Adapter
+ 3881 Saleae Logic
+ 8101 Phidgets, Inc., 1-Motor PhidgetServo v2.0
+ 8104 Phidgets, Inc., 4-Motor PhidgetServo v2.0
+ 8800 WiseGroup Ltd, MP-8800 Quad Joypad
+ 8866 WiseGroup Ltd, MP-8866 Dual Joypad
+0927 Summus, Ltd
+0928 PLX Technology, Inc. (formerly Oxford Semiconductor, Ltd)
+ 8000 Firmware uploader
+ ffff Blank Oxford Device
+0929 American Biometric Co.
+092a Toshiba Information & Industrial Sys. And Services
+092b Sena Technologies, Inc.
+092f Northern Embedded Science/CAVNEX
+ 0004 JTAG-4
+ 0005 JTAG-5
+0930 Toshiba Corp.
+ 0009 Gigabeat F/X (HDD audio player)
+ 000c Gigabeat F (mtp)
+ 0010 Gigabeat S (mtp)
+ 01bf 2.5"External Hard Disk
+ 0200 Integrated Bluetooth (Taiyo Yuden)
+ 021c Atheros AR3012 Bluetooth
+ 0301 PCX1100U Cable Modem (WDM)
+ 0302 PCX2000 Cable Modem (WDM)
+ 0305 Cable Modem PCX3000
+ 0307 Cable Modem PCX2500
+ 0308 PCX2200 Cable Modem (WDM)
+ 0309 PCX5000 Cable Modem (WDM)
+ 030b Cable Modem PCX2600
+ 0501 Bluetooth Controller
+ 0502 Integrated Bluetooth
+ 0503 Bluetooth Controller
+ 0505 Integrated Bluetooth
+ 0506 Integrated Bluetooth
+ 0507 Bluetooth Adapter
+ 0508 Integrated Bluetooth HCI
+ 0509 BT EDR Dongle
+ 0706 PocketPC e740
+ 0707 Pocket PC e330 Series
+ 0708 Pocket PC e350 Series
+ 0709 Pocket PC e750 Series
+ 070a Pocket PC e400 Series
+ 070b Pocket PC e800 Series
+ 0a07 WLM-10U1 802.11abgn Wireless Adapter [Ralink RT3572]
+ 0a08 WLM-20U2/GN-1080 802.11abgn Wireless Adapter [Atheros AR7010+AR9280]
+ 0a13 AX88179 Gigabit Ethernet [Toshiba]
+ 0b05 PX1220E-1G25 External hard drive
+ 0b09 PX1396E-3T01 External hard drive
+ 0b1a STOR.E ALU 2S
+ 1300 Wireless Broadband (CDMA EV-DO) SM-Bus Minicard Status Port
+ 1301 Wireless Broadband (CDMA EV-DO) Minicard Status Port
+ 1302 Wireless Broadband (3G HSDPA) SM-Bus Minicard Status Port
+ 1303 Wireless Broadband (3G HSDPA) Minicard Status Port
+ 1308 Broadband (3G HSDPA) SM-Bus Minicard Diagnostics Port
+ 130b F3507g Mobile Broadband Module
+ 130c F3607gw Mobile Broadband Module
+ 1311 F3607gw v2 Mobile Broadband Module
+ 1400 Memory Stick 2GB
+ 642f TravelDrive
+ 6506 TravelDrive 2C
+ 6507 TravelDrive 2C
+ 6508 TravelDrive 2C
+ 6509 TravelDrive 2C
+ 6510 TravelDrive 2C
+ 6517 TravelDrive 2C
+ 6518 TravelDrive 2C
+ 6519 Kingston DataTraveler 2.0 USB Stick
+ 651a TravelDrive 2C
+ 651b TravelDrive 2C
+ 651c TravelDrive 2C
+ 651d TravelDrive 2C
+ 651e TravelDrive 2C
+ 651f TravelDrive 2C
+ 6520 TravelDrive 2C
+ 6521 TravelDrive 2C
+ 6522 TravelDrive 2C
+ 6523 TravelDrive
+ 6524 TravelDrive
+ 6525 TravelDrive
+ 6526 TravelDrive
+ 6527 TravelDrive
+ 6528 TravelDrive
+ 6529 TravelDrive
+ 652a TravelDrive
+ 652b TravelDrive
+ 652c TravelDrive
+ 652d TravelDrive
+ 652f TravelDrive
+ 6530 TravelDrive
+ 6531 TravelDrive
+ 6532 256M Stick
+ 6533 512M Stick
+ 6534 TravelDrive
+ 653c Kingston DataTraveler 2.0 Stick (512M)
+ 653d Kingston DataTraveler 2.0 Stick (1GB)
+ 653e Flash Memory
+ 6540 TransMemory Flash Memory
+ 6544 TransMemory-Mini / Kingston DataTraveler 2.0 Stick
+ 6545 Kingston DataTraveler 102/2.0 / HEMA Flash Drive 2 GB / PNY Attache 4GB Stick
+0931 Harmonic Data Systems, Ltd
+0932 Crescentec Corp.
+ 0300 VideoAdvantage
+ 0302 Syntek DC-112X
+ 0320 VideoAdvantage
+ 0482 USB2.0 TVBOX
+ 1100 DC-1100 Video Enhamcement Device
+ 1112 Veo Web Camera
+ a311 Video Enhancement Device
+0933 Quantum Corp.
+0934 Spirent Communications
+0936 NuTesla
+ 000a Moebius
+ 000b iMoebius
+ 000c Rhythmedics 6 BioData Integrator
+ 000d Hypurius
+ 000e Millennius
+ 000f Purius
+ 0030 Composite Device, Mass Storage Device (Flash Drive) amd HID
+ 003c Rhythmedics HID Bootloader
+0939 Lumberg, Inc.
+ 0b15 Toshiba Stor.E Alu 2
+093a Pixart Imaging, Inc.
+ 0007 CMOS 100K-R Rev. 1.90
+ 010e Digital camera, CD302N/Elta Medi@ digi-cam/HE-501A
+ 010f Argus DC-1610/DC-1620/Emprex PCD3600/Philips P44417B keychain camera/Precision Mini,Model HA513A/Vivitar Vivicam 55
+ 020f Bullet Line Photo Viewer
+ 050f Mars-Semi Pc-Camera
+ 2460 Q-TEC WEBCAM 100
+ 2468 SoC PC-Camera
+ 2470 SoC PC-Camera
+ 2471 SoC PC-Camera
+ 2500 USB Optical Mouse
+ 2510 Optical Mouse
+ 2521 Optical Mouse
+ 2600 Typhoon Easycam USB 330K (newer)/Typhoon Easycam USB 2.0 VGA 1.3M/Sansun SN-508
+ 2601 SPC 610NC Laptop Camera
+ 2603 PAC7312 Camera
+ 2608 PAC7311 Trust WB-3300p
+ 260e PAC7311 Gigaware VGA PC Camera:Trust WB-3350p:SIGMA cam 2350
+ 260f PAC7311 SnakeCam
+ 2621 PAC731x Trust Webcam
+ 2622 Webcam Genius
+ 2624 Webcam
+093b Plextor Corp.
+ 0010 Storage Adapter
+ 0011 PlexWriter 40/12/40U
+ 0041 PX-708A DVD RW
+ 0042 PX-712UF DVD RW
+ a002 ConvertX M402U XLOADER
+ a003 ConvertX AV100U A/V Capture Audio
+ a004 ConvertX TV402U XLOADER
+ a005 ConvertX TV100U A/V Capture
+ a102 ConvertX M402U A/V Capture
+ a104 ConvertX PX-TV402U/NA
+093c Intrepid Control Systems, Inc.
+ 0601 ValueCAN
+ 0701 NeoVI Blue vehicle bus interface
+093d InnoSync, Inc.
+093e J.S.T. Mfg. Co., Ltd
+093f Olympia Telecom Vertriebs GmbH
+0940 Japan Storage Battery Co., Ltd
+0941 Photobit Corp.
+0942 i2Go.com, LLC
+0943 HCL Technologies India Private, Ltd
+0944 KORG, Inc.
+ 0001 PXR4 4-Track Digital Recorder
+ 0020 KAOSS Pad KP3 Dynamic Effect/Sampler
+ 0023 KAOSSILATOR PRO Dynamic Phrase Synthesizer
+ 010d nanoKEY MIDI keyboard
+ 010e nanoPAD pad controller
+ 010f nanoKONTROL studio controller
+ 0117 nanoKONTROL2 MIDI Controller
+ 0f03 K-Series K61P MIDI studio controller
+0945 Pasco Scientific
+0948 Kronauer music in digital
+ 0301 USB Pro (24/48)
+ 0302 USB Pro (24/96 playback)
+ 0303 USB Pro (24/96 record)
+ 0304 USB Pro (16/48)
+ 1105 USB One
+094b Linkup Systems Corp.
+ 0001 neonode N2
+094d Cable Television Laboratories
+094f Yano
+ 0101 U640MO-03
+ 05fc METALWEAR-HDD
+0951 Kingston Technology
+ 0008 Ethernet
+ 000a KNU101TX 100baseTX Ethernet
+ 1600 DataTraveler II Pen Drive
+ 1601 DataTraveler II+ Pen Drive
+ 1602 DataTraveler Mini
+ 1603 DataTraveler 1GB/2GB Pen Drive
+ 1606 Eee PC 701 SD Card Reader [ENE UB6225]
+ 1607 DataTraveler 100
+ 160b DataTraveler 2.0 (2GB)
+ 160d DataTraveler Vault Privacy
+ 160e DT110P/1GB Capless
+ 1613 DataTraveler DT101C Flash Drive
+ 1616 DataTraveler Locker 4GB
+ 161a Dell HyperVisor internal flash drive
+ 1621 DataTraveler 150 (32GB)
+ 1624 DataTraveler G2
+ 1625 DataTraveler 101 II
+ 162a DataTraveler 112 4GB Pen Drive
+ 162d DataTraveler 102
+ 1630 DataTraveler 200 (32GB)
+ 1642 DT101 G2
+ 1643 DataTraveler G3
+ 1653 Data Traveler 100 G2 8 GiB
+ 1656 DataTraveler Ultimate G2
+ 1660 Data Traveller 108
+ 1665 Digital DataTraveler SE9 64GB
+ 1666 DataTraveler 100 G3/G4/SE9 G2
+ 1689 DataTraveler SE9
+ 168a DataTraveler Micro
+ 168c DT Elite 3.0
+0954 RPM Systems Corp.
+0955 NVidia Corp.
+ 7018 APX
+ 7030 Tegra 3 (recovery mode)
+ 7100 Tegra Device
+ 7210 SHIELD Controller
+ 7820 Tegra 2 AC100 developer mode
+ b400 SHIELD (debug)
+ b401 SHIELD
+ cf05 SHIELD Tablet (debug)
+ cf06 SHIELD Tablet
+ cf07 SHIELD Tablet
+ cf08 SHIELD Tablet
+ cf09 SHIELD Tablet
+0956 BSquare Corp.
+0957 Agilent Technologies, Inc.
+ 0200 E-Video DC-350 Camera
+ 0202 E-Video DC-350 Camera
+ 0407 33220A Waveform Generator
+ 0518 82357B GPIB Interface
+ 0a07 34411A Multimeter
+ 1507 33210A Waveform Generator
+ 1745 Test and Measurement Device (IVI)
+ 2918 U2702A oscilloscope
+ fb18 LC Device
+0958 CompuLink Research, Inc.
+0959 Cologne Chip AG
+ 2bd0 Intelligent ISDN (Ver. 3.60.04) [HFC-S]
+095a Portsmith
+ 3003 Express Ethernet
+095b Medialogic Corp.
+095c K-Tec Electronics
+095d Polycom, Inc.
+ 0001 Polycom ViaVideo
+0967 Acer NeWeb Corp.
+ 0204 WarpLink 802.11b Adapter
+0968 Catalyst Enterprises, Inc.
+096e Feitian Technologies, Inc.
+ 0005 ePass2000
+ 0120 Microcosm Ltd Dinkey
+ 0305 ePass2000Auto
+ 0309 ePass3000GM
+ 0401 ePass3000
+ 0702 ePass3003
+ 0703 ePass3003Auto
+ 0802 ePass2000 (G&D STARCOS SPK 2.4)
+ 0807 ePass2003
+0971 Gretag-Macbeth AG
+ 2000 i1 Pro
+ 2001 i1 Monitor
+ 2003 Eye-One display
+ 2005 Huey
+ 2007 ColorMunki Photo
+0973 Schlumberger
+ 0001 e-gate Smart Card
+0974 Datagraphix, a business unit of Anacomp
+0975 OL'E Communications, Inc.
+0976 Adirondack Wire & Cable
+0977 Lightsurf Technologies
+0978 Beckhoff GmbH
+0979 Jeilin Technology Corp., Ltd
+ 0222 Keychain Display
+ 0224 JL2005A Toy Camera
+ 0226 JL2005A Toy Camera
+ 0227 JL2005B/C/D Toy Camera
+097a Minds At Work LLC
+ 0001 Digital Wallet
+097b Knudsen Engineering, Ltd
+097c Marunix Co., Ltd
+097d Rosun Technologies, Inc.
+097e Biopac Systems Inc.
+ 0035 MP35 v1.0
+097f Barun Electronics Co., Ltd
+0981 Oak Technology, Ltd
+0984 Apricorn
+ 0040 SATA Wire (2.5")
+ 0200 Hard Drive Storage (TPP)
+0985 cab Produkttechnik GmbH & Co KG
+ 0045 Mach4/200 Label Printer
+ 00a3 A3/200 or A3/300 Label Printer
+0986 Matsushita Electric Works, Ltd.
+098c Vitana Corp.
+098d INDesign
+098e Integrated Intellectual Property, Inc.
+098f Kenwood TMI Corp.
+0993 Gemstar eBook Group, Ltd
+ 0001 REB1100 eBook Reader
+ 0002 eBook
+0996 Integrated Telecom Express, Inc.
+099a Zippy Technology Corp.
+ 0638 Sanwa Supply Inc. Small Keyboard
+ 610c EL-610 Super Mini Electron luminescent Keyboard
+ 713a WK-713 Multimedia Keyboard
+ 7160 Hyper Slim Keyboard
+099e Trimble Navigation, Ltd
+09a3 PairGain Technologies
+09a4 Contech Research, Inc.
+09a5 VCON Telecommunications
+09a6 Poinchips
+ 8001 Mass Storage Device
+09a7 Data Transmission Network Corp.
+09a8 Lin Shiung Enterprise Co., Ltd
+09a9 Smart Card Technologies Co., Ltd
+09aa Intersil Corp.
+ 1000 Prism GT 802.11b/g Adapter
+ 3642 Prism 2.x 802.11b Adapter
+09ab Japan Cash Machine Co., Ltd.
+09ae Tripp Lite
+09b2 Franklin Electronic Publishers, Inc.
+ 0001 eBookman Palm Computer
+09b3 Altius Solutions, Inc.
+09b4 MDS Telephone Systems
+09b5 Celltrix Technology Co., Ltd
+09bc Grundig
+ 0002 MPaxx MP150 MP3 Player
+09be MySmart.Com
+ 0001 MySmartPad
+09bf Auerswald GmbH & Co. KG
+ 00c0 COMpact 2104 ISDN PBX
+ 00db COMpact 4410/2206 ISDN
+ 00dc COMpact 4406 DSL (PBX)
+ 00dd COMpact 2204 (PBX)
+ 00de COMpact 2104 (Rev.2 PBX)
+ 00e0 COMmander Business (PBX)
+ 00e2 COMmander Basic.2 (PBX)
+ 00f1 COMfort 2000 (System telephone)
+ 00f2 COMfort 1200 (System telephone)
+ 00f5 COMfortel 2500 (System telephone)
+ 8000 COMpact 2104 DSL (DSL modem)
+ 8001 COMpact 4406 DSL (DSL modem)
+ 8002 Analog/ISDN Converter (Line converter)
+ 8005 WG-640 (Automatic event dialer)
+09c0 Genpix Electronics, LLC
+ 0136 Axon CNS, MultiClamp 700B
+ 0202 8PSK DVB-S tuner
+ 0203 Skywalker-1 DVB-S tuner
+ 0204 Skywalker-CW3K DVB-S tuner
+ 0205 Skywalker-CW3K DVB-S tuner
+ 0206 Skywalker-2 DVB-S tuner
+09c1 Arris Interactive LLC
+ 1337 TOUCHSTONE DEVICE
+09c2 Nisca Corp.
+09c3 ActivCard, Inc.
+ 0007 Reader V2
+ 0008 ZFG-9800-AC SmartCard Reader
+ 0014 ActivIdentity ActivKey SIM USB Token
+09c4 ACTiSYS Corp.
+ 0011 ACT-IR2000U IrDA Dongle
+09c5 Memory Corp.
+09ca BMC Messsysteme GmbH
+ 5544 PIO
+09cb FLIR Systems
+ 1001 Network Adapter
+ 1002 Ex-Series RNDIS interface
+ 1004 Ex-Series UVC interface
+ 1005 Ex-Series RNDIS and UVC interface
+ 1006 Ex-Series RNDIS and MSD interface
+ 1007 Ex-Series UVC and MSD interface
+ 1008 Serial Port
+ 1996 FLIR ONE Camera
+09cc Workbit Corp.
+ 0404 BAFO USB-ATA/ATAPI Bridge Controller
+09cd Psion Dacom Home Networks, Ltd
+ 2001 Psion WaveFinder DAB radio receiver
+09ce City Electronics, Ltd
+09cf Electronics Testing Center, Taiwan
+09d1 NeoMagic, Inc.
+09d2 Vreelin Engineering, Inc.
+09d3 Com One
+ 0001 ISDN TA / Light Rider 128K
+ 000b Bluetooth Adapter class 1 [BlueLight]
+09d7 NovAtel Inc.
+ 0100 NovAtel FlexPack GPS receiver
+09d8 ELATEC
+ 0406 TWN4 MIFARE NFC
+09d9 KRF Tech, Ltd
+09da A4Tech Co., Ltd.
+ 0006 Optical Mouse WOP-35 / Trust 450L Optical Mouse
+ 000a Optical Mouse Opto 510D / OP-620D
+ 000e X-F710F Optical Mouse 3xFire Gaming Mouse
+ 0018 Trust Human Interface Device
+ 001a Wireless Mouse & RXM-15 Receiver
+ 002a Wireless Optical Mouse NB-30
+ 022b Wireless Mouse (Battery Free)
+ 024f RF Receiver and G6-20D Wireless Optical Mouse
+ 0260 KV-300H Isolation Keyboard
+ 032b Wireless Mouse (Battery Free)
+ 1068 Bloody A90 Mouse
+ 8090 X-718BK Oscar Optical Gaming Mouse
+ 9033 X-718BK Optical Mouse
+ 9066 F3 V-Track Gaming Mouse
+ 9090 XL-730K / XL-750BK / XL-755BK Mice
+09db Measurement Computing Corp.
+ 0075 MiniLab 1008
+ 0076 PMD-1024
+ 007a PMD-1208LS
+ 0081 USB-1616FS
+ 0082 USB-1208FS
+ 0088 USB-1616FS internal hub
+09dc Aimex Corp.
+09dd Fellowes, Inc.
+09df Addonics Technologies Corp.
+09e1 Intellon Corp.
+ 5121 MicroLink dLAN
+09e5 Jo-Dan International, Inc.
+09e6 Silutia, Inc.
+09e7 Real 3D, Inc.
+09e8 AKAI Professional M.I. Corp.
+ 0062 MPD16 MIDI Pad Controller Unit
+ 006d EWI electronic wind instrument
+ 0071 MPK25 MIDI Keyboard
+ 0076 LPK25 MIDI Keyboard
+09e9 Chen-Source, Inc.
+09eb IM Networks, Inc.
+ 4331 iRhythm Tuner Remote
+09ef Xitel
+ 0101 MD-Port DG2 MiniDisc Interface
+09f3 GoFlight, Inc.
+ 0018 GF-46 Multi-Mode Display Module
+ 0028 RP-48 Combination Pushbutton-Rotary Module
+ 0048 LGTII - Landing Gear and Trim Control Module
+ 0064 MCPPro - Airliner Mode Control Panel (Autopilot)
+ 0300 EFIS - Electronic Flight Information System
+09f5 AresCom
+ 0168 Network Adapter
+ 0188 LAN Adapter
+ 0850 Adapter
+09f6 RocketChips, Inc.
+09f7 Edu-Science (H.K.), Ltd
+09f8 SoftConnex Technologies, Inc.
+09f9 Bay Associates
+09fa Mtek Vision
+09fb Altera
+ 6001 Blaster
+09ff Gain Technology Corp.
+0a00 Liquid Audio
+0a01 ViA, Inc.
+0a05 Unknown Manufacturer
+ 0001 Hub
+ 7211 hub
+0a07 Ontrak Control Systems Inc.
+ 0064 ADU100 Data Acquisition Interface
+ 0078 ADU120 Data Acquisition Interface
+ 0082 ADU130 Data Acquisition Interface
+ 00c8 ADU200 Relay I/O Interface
+ 00d0 ADU208 Relay I/O Interface
+ 00da ADU218 Solid-State Relay I/O Interface
+0a0b Cybex Computer Products Co.
+0a0d Servergy, Inc
+ 2514 CTS-1000 Internal Hub
+0a11 Xentec, Inc.
+0a12 Cambridge Silicon Radio, Ltd
+ 0001 Bluetooth Dongle (HCI mode)
+ 0002 Frontline Test Equipment Bluetooth Device
+ 0003 Nanosira
+ 0004 Nanosira WHQL Reference Radio
+ 0005 Nanosira-Multimedia
+ 0006 Nanosira-Multimedia WHQL Reference Radio
+ 0007 Nanosira3-ROM
+ 0008 Nanosira3-ROM
+ 0009 Nanosira4-EDR WHQL Reference Radio
+ 000a Nanosira4-EDR-ROM
+ 000b Nanosira5-ROM
+ 0042 SPI Converter
+ 0043 Bluetooth Device
+ 0100 Casira with BlueCore2-External Module
+ 0101 Casira with BlueCore2-Flash Module
+ 0102 Casira with BlueCore3-Multimedia Module
+ 0103 Casira with BlueCore3-Flash Module
+ 0104 Casira with BlueCore4-External Module
+ 0105 Casira with BlueCore4-Multimedia Module
+ 1000 Bluetooth Dongle (HID proxy mode)
+ 1010 Bluetooth Device
+ 1011 Bluetooth Device
+ 1012 Bluetooth Device
+ ffff USB Bluetooth Device in DFU State
+0a13 Telebyte, Inc.
+0a14 Spacelabs Medical, Inc.
+0a15 Scalar Corp.
+0a16 Trek Technology (S) PTE, Ltd
+ 1111 ThumbDrive
+ 8888 IBM USB Memory Key
+ 9988 Trek2000 TD-G2
+0a17 Pentax Corp.
+ 0004 Optio 330
+ 0006 Optio S / S4
+ 0007 Optio 550
+ 0009 Optio 33WR
+ 000a Optio 555
+ 000c Optio 43WR (mass storage mode)
+ 000d Optio 43WR
+ 0015 Optio S40/S5i
+ 003b Optio 50 (mass storage mode)
+ 003d Optio S55
+ 0041 Optio S5z
+ 0043 *ist DL
+ 0047 Optio S60
+ 0052 Optio 60 Digital Camera
+ 006e K10D
+ 0070 K100D
+ 0093 K200D
+ 00a7 Optio E50
+ 1001 EI2000 Camera powered by Digita!
+0a18 Heidelberger Druckmaschinen AG
+0a19 Hua Geng Technologies, Inc.
+0a21 Medtronic Physio Control Corp.
+ 8001 MMT-7305WW [Medtronic Minimed CareLink]
+0a22 Century Semiconductor USA, Inc.
+0a27 Datacard Group
+ 0102 SP35
+0a2c AK-Modul-Bus Computer GmbH
+ 0008 GPIO Ports
+0a34 TG3 Electronics, Inc.
+ 0101 TG82tp
+ 0110 Deck 82-key backlit keyboard
+0a35 Radikal Technologies
+ 002a SAC - Software Assigned Controller
+ 008a SAC Hub
+0a39 Gilat Satellite Networks, Ltd
+0a3a PentaMedia Co., Ltd
+ 0163 KN-W510U 1.0 Wireless LAN Adapter
+0a3c NTT DoCoMo, Inc.
+0a3d Varo Vision
+0a3f Swissonic AG
+0a43 Boca Systems, Inc.
+0a46 Davicom Semiconductor, Inc.
+ 0268 ST268
+ 6688 ZT6688 Fast Ethernet Adapter
+ 8515 ADMtek ADM8515 NIC
+ 9000 DM9000E Fast Ethernet Adapter
+ 9601 DM9601 Fast Ethernet Adapter
+0a47 Hirose Electric
+0a48 I/O Interconnect
+ 3233 Multimedia Card Reader
+ 3239 Multimedia Card Reader
+ 3258 Dane Elec zMate SD Reader
+ 3259 Dane Elec zMate CF Reader
+ 5000 MediaGear xD-SM
+ 500a Mass Storage Device
+ 500f Mass Storage Device
+ 5010 Mass Storage Device
+ 5011 Mass Storage Device
+ 5014 Mass Storage Device
+ 5020 Mass Storage Device
+ 5021 Mass Storage Device
+ 5022 Mass Storage Device
+ 5023 Mass Storage Device
+ 5024 Mass Storage Device
+ 5025 Mass Storage Device
+0a4a Ploytec GmbH
+0a4b Fujitsu Media Devices, Ltd
+0a4c Computex Co., Ltd
+ 15d9 OPTICAL MOUSE
+0a4d Evolution Electronics, Ltd
+ 0064 MK-225 Driver
+ 0065 MK-225C Driver
+ 0066 MK-225C Driver
+ 0067 MK-425C Driver
+ 0078 MK-37 Driver
+ 0079 MK-37C Driver
+ 007a MK-37C Driver
+ 008c TerraTec MIDI MASTER
+ 008d MK-249C Driver
+ 008e MK-249C MIDI Keyboard
+ 008f MK-449C Driver
+ 0090 Keystation 49e Driver
+ 0091 Keystation 61es Driver
+ 00a0 MK-361 Driver
+ 00a1 MK-361C Driver
+ 00a2 MK-361C Driver
+ 00a3 MK-461C MIDI Keyboard
+ 00b5 Keystation Pro 88 Driver
+ 00d2 E-Keys Driver
+ 00f0 UC-16 Driver
+ 00f1 X-Session Driver
+ 00f5 UC-33e MIDI Controller
+0a4e Steinberg Soft-und Hardware GmbH
+0a4f Litton Systems, Inc.
+0a50 Mimaki Engineering Co., Ltd
+0a51 Sony Electronics, Inc.
+0a52 Jebsee Electronics Co., Ltd
+0a53 Portable Peripheral Co., Ltd
+ 1000 Scanner
+ 2000 Q-Scan A6 Scanner
+ 2001 Q-Scan A6 Scanner
+ 2013 Media Drive A6 Scanner
+ 2014 Media Drive A6 Scanner
+ 2015 BizCardReader 600C
+ 2016 BizCardReader 600C
+ 202a Scanshell-CSSN
+ 3000 Q-Scan A8 Scanner
+ 3002 Q-Scan A8 Reader
+ 3015 BizCardReader 300G
+ 302a LM9832 - PA570 Mini Business Card Scanner [Targus]
+ 5001 BizCardReader 900C
+0a5a Electronics For Imaging, Inc.
+0a5b EAsics NV
+0a5c Broadcom Corp.
+ 0201 iLine10(tm) Network Adapter
+ 2000 Bluetooth Device
+ 2001 Bluetooth Device
+ 2009 BCM2035 Bluetooth
+ 200a BCM2035 Bluetooth dongle
+ 200f Bluetooth Controller
+ 201d Bluetooth Device
+ 201e IBM Integrated Bluetooth IV
+ 2020 Bluetooth dongle
+ 2021 BCM2035B3 Bluetooth Adapter
+ 2033 BCM2033 Bluetooth
+ 2035 BCM2035 Bluetooth
+ 2038 Blutonium Device
+ 2039 BCM2045 Bluetooth
+ 2045 Bluetooth Controller
+ 2046 Bluetooth Device
+ 2047 Bluetooth Device
+ 205e Bluetooth Device
+ 2100 Bluetooth 2.0+eDR dongle
+ 2101 BCM2045 Bluetooth
+ 2102 ANYCOM Blue USB-200/250
+ 2110 BCM2045B (BDC-2) [Bluetooth Controller]
+ 2111 ANYCOM Blue USB-UHE 200/250
+ 2120 2045 Bluetooth 2.0 USB-UHE Device with trace filter
+ 2121 BCM2210 Bluetooth
+ 2122 Bluetooth 2.0+EDR dongle
+ 2123 Bluetooth dongle
+ 2130 2045 Bluetooth 2.0 USB-UHE Device with trace filter
+ 2131 2045 Bluetooth 2.0 Device with trace filter
+ 2145 BCM2045B (BDC-2.1) [Bluetooth Controller]
+ 2148 BCM92046DG-CL1ROM Bluetooth 2.1 Adapter
+ 2150 BCM2046 Bluetooth Device
+ 2151 Bluetooth
+ 2154 BCM92046DG-CL1ROM Bluetooth 2.1 UHE Dongle
+ 216a BCM43142A0 Bluetooth
+ 216c BCM43142A0 Bluetooth Device
+ 216d BCM43142A0 Bluetooth 4.0
+ 216f BCM20702A0 Bluetooth
+ 217d HP Bluethunder
+ 217f BCM2045B (BDC-2.1)
+ 2198 Bluetooth 3.0 Device
+ 219b Bluetooth 2.1 Device
+ 21b1 HP Bluetooth Module
+ 21b4 BCM2070 Bluetooth 2.1 + EDR
+ 21b9 BCM2070 Bluetooth 2.1 + EDR
+ 21ba BCM2070 Bluetooth 2.1 + EDR
+ 21bb BCM2070 Bluetooth 2.1 + EDR
+ 21bc BCM2070 Bluetooth 2.1 + EDR
+ 21bd BCM2070 Bluetooth 2.1 + EDR
+ 21d7 BCM43142 Bluetooth 4.0
+ 21e1 HP Portable SoftSailing
+ 21e3 HP Portable Valentine
+ 21e6 BCM20702 Bluetooth 4.0 [ThinkPad]
+ 21e8 BCM20702A0 Bluetooth 4.0
+ 21f1 HP Portable Bumble Bee
+ 22be BCM2070 Bluetooth 3.0 + HS
+ 4500 BCM2046B1 USB 2.0 Hub (part of BCM2046 Bluetooth)
+ 4502 Keyboard (Boot Interface Subclass)
+ 4503 Mouse (Boot Interface Subclass)
+ 5800 BCM5880 Secure Applications Processor
+ 5801 BCM5880 Secure Applications Processor with fingerprint swipe sensor
+ 5802 BCM5880 Secure Applications Processor with fingerprint touch sensor
+ 5803 BCM5880 Secure Applications Processor with secure keyboard
+ 5804 BCM5880 Secure Applications Processor with fingerprint swipe sensor
+ 6300 Pirelli Remote NDIS Device
+ 6410 BCM20703A1 Bluetooth 4.1 + LE
+ bd11 TiVo AG0100 802.11bg Wireless Adapter [Broadcom BCM4320]
+ bd13 BCM4323 802.11abgn Wireless Adapter
+ bd16 BCM4319 802.11bgn Wireless Adapter
+ bd17 BCM43236 802.11abgn Wireless Adapter
+ d11b Eminent EM4045 [Broadcom 4320 USB]
+0a5d Diatrend Corp.
+0a5f Zebra
+ 0009 LP2844 Printer
+ 0081 GK420t Label Printer
+ 008b HC100 wristbands Printer
+ 008c ZP 450 Printer
+ 00d1 Zebra GC420d Label Printer
+ 930a Printer
+0a62 MPMan
+ 0010 MPMan MP-F40 MP3 Player
+0a66 ClearCube Technology
+0a67 Medeli Electronics Co., Ltd
+0a68 Comaide Corp.
+0a69 Chroma ate, Inc.
+0a6b Green House Co., Ltd
+ 0001 Compact Flash R/W with MP3 player
+ 000f FlashDisk
+0a6c Integrated Circuit Systems, Inc.
+0a6d UPS Manufacturing
+0a6e Benwin
+0a6f Core Technology, Inc.
+ 0400 Xanboo
+0a70 International Game Technology
+0a71 VIPColor Technologies USA, Inc.
+ 0001 VP485 Printer
+0a72 Sanwa Denshi
+0a73 Mackie Designs
+ 0002 XD-2 [Spike]
+0a7d NSTL, Inc.
+0a7e Octagon Systems Corp.
+0a80 Rexon Technology Corp., Ltd
+0a81 Chesen Electronics Corp.
+ 0101 Keyboard
+ 0103 Keyboard
+ 0203 Mouse
+ 0205 PS/2 Keyboard+Mouse Adapter
+ 0701 USB Missile Launcher
+ ff01 Wireless Missile Launcher
+0a82 Syscan
+ 4600 TravelScan 460/464
+0a83 NextComm, Inc.
+0a84 Maui Innovative Peripherals
+0a85 Idexx Labs
+0a86 NITGen Co., Ltd
+0a89 Aktiv
+ 0001 Guardant Stealth/Net
+ 0002 Guardant ID
+ 0003 Guardant Stealth 2
+ 0004 Rutoken
+ 0005 Guardant Fidus
+ 0006 Guardant Stealth 3
+ 0007 Guardant Stealth 2
+ 0008 Guardant Stealth 3 Sign/Time
+ 0009 Guardant Code
+ 000a Guardant Sign Pro
+ 000b Guardant Sign Pro HID
+ 000c Guardant Stealth 3 Sign/Time
+ 000d Guardant Code HID
+ 000f Guardant System Firmware Update
+ 0020 Rutoken S
+ 0025 Rutoken lite
+ 0026 Rutoken lite HID
+ 002a Rutoken Mass Storage
+ 002b Guardant Mass Storage
+ 0030 Rutoken ECP
+ 0040 Rutoken ECP HID
+ 0060 Rutoken Magistra
+ 0061 Rutoken Magistra
+ 0069 Reader
+ 0080 Rutoken PinPad Ex
+ 0081 Rutoken PinPad In
+ 0082 Rutoken PinPad 2
+0a8d Picturetel
+0a8e Japan Aviation Electronics Industry, Ltd
+ 2011 Filter Driver For JAE XMC R/W
+0a90 Candy Technology Co., Ltd
+0a91 Globlink Technology, Inc.
+ 3801 Targus PAKP003 Mouse
+0a92 EGO SYStems, Inc.
+ 0011 SYS WaveTerminal U2A
+ 0021 GIGAPort
+ 0031 GIGAPortAG
+ 0053 AudioTrak Optoplay
+ 0061 Waveterminal U24
+ 0071 MAYA EX7
+ 0091 Maya 44
+ 00b1 MAYA EX5
+ 1000 MIDI Mate
+ 1010 RoMI/O
+ 1020 M4U
+ 1030 M8U
+ 1090 KeyControl49
+ 10a0 KeyControl25
+0a93 C Technologies AB
+ 0002 C-Pen 10
+ 0005 MyPen Light
+ 000d Input Pen
+ 0010 C-Pen 20
+ 0a93 PayPen
+0a94 Intersense
+0aa3 Lava Computer Mfg., Inc.
+0aa4 Develco Elektronik
+0aa5 First International Digital
+ 0002 irock! 500 Series
+ 0801 MP3 Player
+0aa6 Perception Digital, Ltd
+ 0101 Hercules Jukebox
+ 1501 Store 'n' Go HD Drive
+0aa7 Wincor Nixdorf International GmbH
+ 0100 POS Keyboard, TA58P-USB
+ 0101 POS Keyboard, TA85P-USB
+ 0102 POS Keyboard, TA59-USB
+ 0103 POS Keyboard, TA60-USB
+ 0104 SNIkey Keyboard, SNIKey-KB-USB
+ 0200 Operator Display, BA63-USB
+ 0201 Operator Display, BA66-USB
+ 0202 Operator Display & Scanner, XiCheck-BA63
+ 0203 Operator Display & Scanner, XiCheck-BA66
+ 0204 Graphics Operator Display, BA63GV
+ 0300 POS Printer (printer class mode), TH210
+ 0301 POS Printer (native mode), TH210
+ 0302 POS Printer (printer class mode), TH220
+ 0303 POS Printer (native mode), TH220
+ 0304 POS Printer, TH230
+ 0305 Lottery Printer, XiPrintPlus
+ 0306 POS Printer (printer class mode), TH320
+ 0307 POS Printer (native mode), TH320
+ 0308 POS Printer (printer class mode), TH420
+ 0309 POS Printer (native mode), TH420
+ 030a POS Printer, TH200B
+ 0400 Lottery Scanner, Xiscan S
+ 0401 Lottery Scanner, Xiscan 3
+ 0402 Programmable Magnetic Swipe Card Reader, MSRP-USB
+ 0500 IDE Adapter
+ 0501 Hub Printer Interface
+ 0502 Hub SNIKey Keyboard
+ 4304 Banking Printer TP07
+ 4305 Banking Printer TP07c
+ 4500 WN Central Special Electronics
+0aa8 TriGem Computer, Inc.
+ 0060 TG 11Mbps WLAN Mini Adapter
+ 1001 DreamComboM4100
+ 3002 InkJet Color Printer
+ 8001 TG_iMON
+ 8002 TG_KLOSS
+ a001 TG_X2
+ a002 TGVFD_KLOSS
+ ffda iMON_VFD
+0aa9 Baromtec Co.
+ f01b Medion MD 6242 MP3 Player
+0aaa Japan CBM Corp.
+0aab Vision Shape Europe SA
+0aac iCompression, Inc.
+0aad Rohde & Schwarz GmbH & Co. KG
+ 0003 NRP-Z21
+ 000c NRP-Z11
+ 0013 NRP-Z22
+ 0014 NRP-Z23
+ 0015 NRP-Z24
+ 0016 NRP-Z51
+ 0017 NRP-Z52
+ 0018 NRP-Z55
+ 0019 NRP-Z56
+ 0021 NRP-Z91
+ 0023 NRP-Z81
+ 002c NRP-Z31
+ 002d NRP-Z37
+ 002f NRP-Z27
+ 0051 NRP-Z28
+ 0052 NRP-Z98
+ 0062 NRP-Z92
+ 0070 NRP-Z57
+ 0083 NRP-Z85
+ 0095 NRP-Z86
+0aae NEC infrontia Corp. (Nitsuko)
+0aaf Digitalway Co., Ltd
+0ab0 Arrow Strong Electronics Co., Ltd
+0ab1 FEIG ELECTRONIC GmbH
+ 0002 OBID RFID-Reader
+ 0004 OBID classic-pro
+0aba Ellisys
+ 8001 Tracker 110 Protocol Analyzer
+ 8002 Explorer 200 Protocol Analyzer
+0abe Stereo-Link
+ 0101 SL1200 DAC
+0abf Diolan
+ 3370 I2C/SPI Adapter - U2C-12
+0ac3 Sanyo Semiconductor Company Micro
+0ac4 Leco Corp.
+0ac5 I & C Corp.
+0ac6 Singing Electrons, Inc.
+0ac7 Panwest Corp.
+0ac8 Z-Star Microelectronics Corp.
+ 0301 Web Camera
+ 0302 ZC0302 Webcam
+ 0321 Vimicro generic vc0321 Camera
+ 0323 Luxya WC-1200 USB 2.0 Webcam
+ 0328 A4Tech PK-130MG
+ 0336 Elecom UCAM-DLQ30
+ 301b ZC0301 Webcam
+ 303b ZC0303 Webcam
+ 305b ZC0305 Webcam
+ 307b USB 1.1 Webcam
+ 332d Vega USB 2.0 Camera
+ 3343 Sirius USB 2.0 Camera
+ 3370 Traveler TV 6500 SF Dia-scanner
+ 3420 Venus USB2.0 Camera
+ c001 Sony embedded vimicro Camera
+ c002 Visual Communication Camera VGP-VCC1
+ c302 Vega USB 2.0 Camera
+ c303 Saturn USB 2.0 Camera
+ c326 Namuga 1.3M Webcam
+ c33f Webcam
+ c429 Lenovo ThinkCentre Web Camera
+ c42d Lenovo IdeaCentre Web Camera
+0ac9 Micro Solutions, Inc.
+ 0000 Backpack CD-ReWriter
+ 0001 BACKPACK 2 Cable
+ 0010 BACKPACK
+ 0011 Backpack 40GB Hard Drive
+ 0110 BACKPACK
+ 0111 BackPack
+ 1234 BACKPACK
+0aca OPEN Networks Ltd
+ 1060 OPEN NT1 Plus II
+0acc Koga Electronics Co.
+0acd ID Tech
+ 0300 IDT1221U RS-232 Adapter
+ 0401 Spectrum III Hybrid Smartcard Reader
+ 0630 Spectrum III Mag-Only Insert Reader (SPT3-355 Series) USB-CDC
+ 0810 SecurePIN (IDPA-506100Y) PIN Pad
+ 2030 ValueMag Magnetic Stripe Reader
+0ace ZyDAS
+ 1201 ZD1201 802.11b
+ 1211 ZD1211 802.11g
+ 1215 ZD1211B 802.11g
+ 1221 ZD1221 802.11n
+ 1602 ZyXEL Omni FaxModem 56K
+ 1608 ZyXEL Omni FaxModem 56K UNO
+ 1611 ZyXEL Omni FaxModem 56K Plus
+ 2011 Virtual media for 802.11bg
+ 20ff Virtual media for 802.11bg
+ a211 ZD1211 802.11b/g Wireless Adapter
+ b215 802.11bg
+0acf Intoto, Inc.
+0ad0 Intellix Corp.
+0ad1 Remotec Technology, Ltd
+0ad2 Service & Quality Technology Co., Ltd
+0ada Data Encryption Systems Ltd.
+ 0005 DK2
+0ae3 Allion Computer, Inc.
+0ae4 Taito Corp.
+0ae7 Neodym Systems, Inc.
+0ae8 System Support Co., Ltd
+0ae9 North Shore Circuit Design L.L.P.
+0aea SciEssence, LLC
+0aeb TTP Communications, Ltd
+0aec Neodio Technologies Corp.
+ 2101 SmartMedia Card Reader
+ 2102 CompactFlash Card Reader
+ 2103 MMC/SD Card Reader
+ 2104 MemoryStick Card Reader
+ 2201 SmartMedia+CompactFlash Card Reader
+ 2202 SmartMedia+MMC/SD Card Reader
+ 2203 SmartMedia+MemoryStick Card Reader
+ 2204 CompactFlash+MMC/SD Card Reader
+ 2205 CompactFlash+MemoryStick Card Reader
+ 2206 MMC/SD+MemoryStick Card Reader
+ 2301 SmartMedia+CompactFlash+MMC/SD Card Reader
+ 2302 SmartMedia+CompactFlash+MemoryStick Card Reader
+ 2303 SmartMedia+MMC/SD+MemoryStick Card Reader
+ 2304 CompactFlash+MMC/SD+MemoryStick Card Reader
+ 3016 MMC/SD+Memory Stick Card Reader
+ 3050 ND3050 8-in-1 Card Reader
+ 3060 1.1 FS Card Reader
+ 3101 MMC/SD Card Reader
+ 3102 MemoryStick Card Reader
+ 3201 MMC/SD+MemoryStick Card Reader
+ 3216 HS Card Reader
+ 3260 7-in-1 Card Reader
+ 5010 ND5010 Card Reader
+0af0 Option
+ 5000 UMTS Card
+ 6000 GlobeTrotter 3G datacard
+ 6300 GT 3G Quad UMTS/GPRS Card
+ 6600 GlobeTrotter 3G+ datacard
+ 6711 GlobeTrotter Express 7.2 v2
+ 6971 Globetrotter HSDPA Modem
+ 7251 Globetrotter HSUPA Modem (aka iCON HSUPA E)
+ 7501 Globetrotter HSUPA Modem (icon 411 aka "Vodafone K3760")
+ 7601 Globetrotter MO40x 3G Modem (GTM 382)
+ 7701 Globetrotter HSUPA Modem (aka icon 451)
+ d055 Globetrotter GI0505 [iCON 505]
+0af6 Silver I Co., Ltd
+0af7 B2C2, Inc.
+ 0101 Digital TV USB Receiver (DVB-S/T/C / ATSC)
+0af9 Hama, Inc.
+ 0010 USB SightCam 100
+ 0011 Micro Innovations IC50C Webcam
+0afa DMC Co., Ltd.
+ 07d2 Controller Board for Projected Capacitive Touch Screen DUS3000
+0afc Zaptronix Ltd
+0afd Tateno Dennou, Inc.
+0afe Cummins Engine Co.
+0aff Jump Zone Network Products, Inc.
+0b00 INGENICO
+0b05 ASUSTek Computer, Inc.
+ 0001 MeMO Pad HD 7 (CD-ROM mode)
+ 1101 Mass Storage (UISDMC4S)
+ 1706 WL-167G v1 802.11g Adapter [Ralink RT2571]
+ 1707 WL-167G v1 802.11g Adapter [Ralink RT2571]
+ 1708 Mass Storage Device
+ 170b Multi card reader
+ 170c WL-159g 802.11bg
+ 170d 802.11b/g Wireless Network Adapter
+ 1712 BT-183 Bluetooth 2.0+EDR adapter
+ 1715 2045 Bluetooth 2.0 Device with trace filter
+ 1716 Bluetooth Device
+ 1717 WL169gE 802.11g Adapter [Broadcom 4320 USB]
+ 171b A9T wireless 802.11bg
+ 171c 802.11b/g Wireless Network Adapter
+ 171f My Cinema U3000 Mini [DiBcom DiB7700P]
+ 1723 WL-167G v2 802.11g Adapter [Ralink RT2571W]
+ 1724 RT2573
+ 1726 Laptop OLED Display
+ 172a ASUS 802.11n Network Adapter
+ 172b 802.11n Network Adapter
+ 1731 802.11n Network Adapter
+ 1732 802.11n Network Adapter
+ 1734 ASUS AF-200
+ 173c BT-183 Bluetooth 2.0
+ 173f My Cinema U3100 Mini
+ 1742 802.11n Network Adapter
+ 1743 Xonar U1 Audio Station
+ 1751 BT-253 Bluetooth Adapter
+ 175b Laptop OLED Display
+ 1760 802.11n Network Adapter
+ 1761 USB-N11 802.11n Network Adapter [Ralink RT2870]
+ 1774 Gobi Wireless Modem (QDL mode)
+ 1776 Gobi Wireless Modem
+ 1779 My Cinema U3100 Mini Plus [AF9035A]
+ 1784 USB-N13 802.11n Network Adapter (rev. A1) [Ralink RT3072]
+ 1786 USB-N10 802.11n Network Adapter [Realtek RTL8188SU]
+ 1788 BT-270 Bluetooth Adapter
+ 1791 WL-167G v3 802.11n Adapter [Realtek RTL8188SU]
+ 179d USB-N53 802.11abgn Network Adapter [Ralink RT3572]
+ 179e Eee Note EA800 (network mode)
+ 179f Eee Note EA800 (tablet mode)
+ 17a0 Xonar U3 sound card
+ 17a1 Eee Note EA800 (mass storage mode)
+ 17ab USB-N13 802.11n Network Adapter (rev. B1) [Realtek RTL8192CU]
+ 17ba N10 Nano 802.11n Network Adapter [Realtek RTL8192CU]
+ 17c7 WL-330NUL
+ 17c9 USB-AC53 802.11a/b/g/n/ac Wireless Adapter [Broadcom BCM43526]
+ 17cb Broadcom BCM20702A0 Bluetooth
+ 17d1 AC51 802.11a/b/g/n/ac Wireless Adapter [Mediatek MT7610/Ralink RT2870]
+ 180a Broadcom BCM20702 Single-Chip Bluetooth 4.0 + LE
+ 1825 Qualcomm Bluetooth 4.1
+ 4c80 Transformer Pad TF300TG
+ 4c90 Transformer Pad Infinity TF700
+ 4c91 Transformer Pad Infinity TF700 (Debug mode)
+ 4ca0 Transformer Pad TF701T
+ 4ca1 Transformer Pad TF701T (Debug mode)
+ 4d00 Transformer Prime TF201
+ 4d01 Transformer Prime TF201 (debug mode)
+ 4daf Transformer Pad Infinity TF700 (Fastboot)
+ 5410 MeMO Pad HD 7 (MTP mode)
+ 5412 MeMO Pad HD 7 (PTP mode)
+ 550f Fonepad 7
+ 6101 Cable Modem
+ 620a Remote NDIS Device
+ 7772 ASUS Zenfone GO (ZB500KL) (MTP mode)
+ 7773 ASUS Zenfone GO (ZB500KL) (Debug, MTP mode)
+ 7774 ASUS Zenfone GO (ZB500KL) (RNDIS mode)
+ 7775 ASUS Zenfone GO (ZB500KL) (Debug, RNDIS mode)
+ 7776 ASUS Zenfone GO (ZB500KL) (PTP mode)
+ 7777 ASUS Zenfone GO (ZB500KL) (Debug, PTP mode)
+ b700 Broadcom Bluetooth 2.1
+0b0b Datamax-O'Neil
+ 106e Datamax E-4304
+0b0c Todos AB
+ 0009 Todos Argos Mini II Smart Card Reader
+ 001e e.dentifier2 (ABN AMRO electronic banking card reader NL)
+ 002e C200 smartcard controller (Nordea card reader)
+ 003f Todos C400 smartcard controller (Handelsbanken card reader)
+ 0050 Argos Mini II Smart Card Reader (CCID)
+0b0d ProjectLab
+ 0000 CenturyCD
+0b0e GN Netcom
+ 0348 Jabra UC VOICE 550a MS
+ 034c Jabra UC Voice 750 MS
+ 0410 Jabra SPEAK 410
+ 0420 Jabra SPEAK 510
+ 094d GN Netcom / Jabra REVO Wireless
+ 1017 Jabra PRO 930
+ 1022 Jabra PRO 9450, Type 9400BS (DECT Headset)
+ 1041 Jabra PRO 9460
+ 1900 Jabra Biz 1900
+ 2007 GN 2000 Stereo Corded Headset
+ 620c Jabra BT620s
+ 9330 Jabra GN9330 Headset
+0b0f AVID Technology
+0b10 Pcally
+0b11 I Tech Solutions Co., Ltd
+0b1e Electronic Warfare Assoc., Inc. (EWA)
+ 8007 Blackhawk USB560-BP JTAG Emulator
+0b1f Insyde Software Corp.
+0b20 TransDimension, Inc.
+0b21 Yokogawa Electric Corp.
+0b22 Japan System Development Co., Ltd
+0b23 Pan-Asia Electronics Co., Ltd
+0b24 Link Evolution Corp.
+0b27 Ritek Corp.
+0b28 Kenwood Corp.
+0b2c Village Center, Inc.
+0b30 PNY Technologies, Inc.
+ 0006 SM Media-Shuttle Card Reader
+0b33 Contour Design, Inc.
+ 0020 ShuttleXpress
+ 0030 ShuttlePro v2
+ 0700 RollerMouse Pro
+0b37 Hitachi ULSI Systems Co., Ltd
+0b38 Gear Head
+ 0003 Keyboard
+ 0010 107-Key Keyboard
+0b39 Omnidirectional Control Technology, Inc.
+ 0001 Composite USB PS2 Converter
+ 0109 USB TO Ethernet
+ 0421 Serial
+ 0801 USB-Parallel Bridge
+ 0901 OCT To Fast Ethernet Converter
+ 0c03 LAN DOCK Serial Converter
+0b3a IPaxess
+0b3b Tekram Technology Co., Ltd
+ 0163 TL-WN320G 1.0 WLAN Adapter
+ 1601 Allnet 0193 802.11b Adapter
+ 1602 ZyXEL ZyAIR B200 802.11b Adapter
+ 1612 AIR.Mate 2@net 802.11b Adapter
+ 1613 802.11b Wireless LAN Adapter
+ 1620 Allnet Wireless Network Adapter [Envara WiND512]
+ 1630 QuickWLAN 802.11bg
+ 5630 802.11bg
+ 6630 ZD1211
+0b3c Olivetti Techcenter
+ a010 Simple_Way Printer/Scanner/Copier
+ c000 Olicard 100
+ c700 Olicard 100 (Mass Storage mode)
+0b3e Kikusui Electronics Corp.
+0b41 Hal Corp.
+ 0011 Crossam2+USB IR commander
+0b43 Play.com, Inc.
+ 0003 PS2 Controller Converter
+ 0005 GameCube Adaptor
+0b47 Sportbug.com, Inc.
+0b48 TechnoTrend AG
+ 1003 Technotrend/Hauppauge USB-Nova
+ 1004 TT-PCline
+ 1005 Technotrend/Hauppauge USB-Nova
+ 1006 Technotrend/Hauppauge DEC3000-s
+ 1007 TT-micro plus Device
+ 1008 Technotrend/Hauppauge DEC2000-t
+ 1009 Technotrend/Hauppauge DEC2540-t
+ 3001 DVB-S receiver
+ 3002 DVB-C receiver
+ 3003 DVB-T receiver
+ 3004 TT TV-Stick
+ 3005 TT TV-Stick (8kB EEPROM)
+ 3006 TT-connect S-2400 DVB-S receiver
+ 3007 TT-connect S2-3600
+ 3008 TT-connect
+ 3009 TT-connect S-2400 DVB-S receiver (8kB EEPROM)
+ 300a TT-connect S2-3650 CI
+ 300b TT-connect C-3650 CI
+ 300c TT-connect T-3650 CI
+ 300d TT-connect CT-3650 CI
+ 300e TT-connect C-2400
+ 3011 TT-connect S2-4600
+ 3012 TT-connect CT2-4650 CI
+ 3014 TT-TVStick CT2-4400
+ 3015 TT-connect CT2-4650 CI
+ 3017 TT-connect S2-4650 CI
+0b49 ASCII Corp.
+ 064f Trance Vibrator
+0b4b Pine Corp. Ltd.
+ 0100 D'music MP3 Player
+0b4d Graphtec America, Inc.
+ 110a Graphtec CC200-20
+0b4e Musical Electronics, Ltd
+ 6500 MP3 Player
+ 8028 MP3 Player
+ 8920 MP3 Player
+0b50 Dumpries Co., Ltd
+0b51 Comfort Keyboard Co.
+ 0020 Comfort Keyboard
+0b52 Colorado MicroDisplay, Inc.
+0b54 Sinbon Electronics Co., Ltd
+0b56 TYI Systems, Ltd
+0b57 Beijing HanwangTechnology Co., Ltd
+0b59 Lake Communications, Ltd
+0b5a Corel Corp.
+0b5f Green Electronics Co., Ltd
+0b60 Nsine, Ltd
+0b61 NEC Viewtechnology, Ltd
+0b62 Orange Micro, Inc.
+ 000b Bluetooth Device
+ 0059 iBOT2 Webcam
+0b63 ADLink Technology, Inc.
+0b64 Wonderful Wire Cable Co., Ltd
+0b65 Expert Magnetics Corp.
+0b66 Cybiko Inc.
+ 0041 Xtreme
+0b67 Fairbanks Scales
+ 555e SCB-R9000
+0b69 CacheVision
+0b6a Maxim Integrated Products
+ a132 WUP-005 [Nintendo Wii U Pro Controller]
+0b6f Nagano Japan Radio Co., Ltd
+0b70 PortalPlayer, Inc.
+ 00ba iRiver H10 20GB
+0b71 SHIN-EI Sangyo Co., Ltd
+0b72 Embedded Wireless Technology Co., Ltd
+0b73 Computone Corp.
+0b75 Roland DG Corp.
+0b79 Sunrise Telecom, Inc.
+0b7a Zeevo, Inc.
+ 07d0 Bluetooth Dongle
+0b7b Taiko Denki Co., Ltd
+0b7c ITRAN Communications, Ltd
+0b7d Astrodesign, Inc.
+0b81 id3 Technologies
+ 0001 Biothentic II smartcard reader with fingerprint sensor
+ 0002 DFU-Enabled Devices (DFU)
+ 0012 BioPAD biometric module (DFU + CDC)
+ 0102 Certis V1 fingerprint reader
+ 0103 Certis V2 fingerprint reader
+ 0200 CL1356T / CL1356T5 / CL1356A smartcard readers (CCID)
+ 0201 CL1356T / CL1356T5 / CL1356A smartcard readers (DFU + CCID)
+ 0220 CL1356A FFPJP smartcard reader (CCID + HID)
+ 0221 CL1356A smartcard reader (DFU + CCID + HID)
+0b84 Rextron Technology, Inc.
+0b85 Elkat Electronics, Sdn., Bhd.
+0b86 Exputer Systems, Inc.
+ 5100 XMC5100 Zippy Drive
+ 5110 XMC5110 Flash Drive
+ 5200 XMC5200 Zippy Drive
+ 5201 XMC5200 Zippy Drive
+ 5202 XMC5200 Zippy Drive
+ 5280 XMC5280 Storage Drive
+ fff0 ISP5200 Debugger
+0b87 Plus-One I & T, Inc.
+0b88 Sigma Koki Co., Ltd, Technology Center
+0b89 Advanced Digital Broadcast, Ltd
+0b8c SMART Technologies Inc.
+ 0001 Interactive Whiteboard Controller (SB6) (HID)
+ 00c3 Sympodium ID350
+0b95 ASIX Electronics Corp.
+ 1720 10/100 Ethernet
+ 1780 AX88178
+ 1790 AX88179 Gigabit Ethernet
+ 7720 AX88772
+ 772a AX88772A Fast Ethernet
+ 772b AX88772B
+ 7e2b AX88772B Fast Ethernet Controller
+0b96 Sewon Telecom
+0b97 O2 Micro, Inc.
+ 7732 Smart Card Reader
+ 7761 Oz776 1.1 Hub
+ 7762 Oz776 SmartCard Reader
+ 7772 OZ776 CCID Smartcard Reader
+0b98 Playmates Toys, Inc.
+0b99 Audio International, Inc.
+0b9b Dipl.-Ing. Stefan Kunde
+ 4012 Reflex RC-controller Interface
+0b9d Softprotec Co.
+0b9f Chippo Technologies
+0baf U.S. Robotics
+ 00e5 USR6000
+ 00eb USR1120 802.11b Adapter
+ 00ec 56K Faxmodem
+ 00f1 SureConnect ADSL ATM Adapter
+ 00f2 SureConnect ADSL Loader
+ 00f5 SureConnect ADSL ATM Adapter
+ 00f6 SureConnect ADSL Loader
+ 00f7 SureConnect ADSL ATM Adapter
+ 00f8 SureConnect ADSL Loader
+ 00f9 SureConnect ADSL ATM Adapter
+ 00fa SureConnect ADSL Loader
+ 00fb SureConnect ADSL Ethernet/USB Router
+ 0111 USR5420 802.11g Adapter [Broadcom 4320 USB]
+ 0118 U5 802.11g Adapter
+ 011b Wireless MAXg Adapter [Broadcom 4320]
+ 0121 USR5423 802.11bg Wireless Adapter [ZyDAS ZD1211B]
+ 0303 USR5637 56K Faxmodem
+ 6112 FaxModem Model 5633
+0bb0 Concord Camera Corp.
+ 0100 Sound Vision Stream
+ 5007 3340z/Rollei DC3100
+0bb1 Infinilink Corp.
+0bb2 Ambit Microsystems Corp.
+ 0302 U10H010 802.11b Wireless Adapter [Intersil PRISM 3]
+ 6098 USB Cable Modem
+0bb3 Ofuji Technology
+0bb4 HTC (High Tech Computer Corp.)
+ 0001 Android Phone via mass storage [Wiko Cink Peax 2]
+ 00ce mmO2 XDA GSM/GPRS Pocket PC
+ 00cf SPV C500 Smart Phone
+ 0a01 PocketPC Sync
+ 0a02 Himalaya GSM/GPRS Pocket PC
+ 0a03 PocketPC Sync
+ 0a04 PocketPC Sync
+ 0a05 PocketPC Sync
+ 0a06 PocketPC Sync
+ 0a07 Magician PocketPC SmartPhone / O2 XDA
+ 0a08 PocketPC Sync
+ 0a09 PocketPC Sync
+ 0a0a PocketPC Sync
+ 0a0b PocketPC Sync
+ 0a0c PocketPC Sync
+ 0a0d PocketPC Sync
+ 0a0e PocketPC Sync
+ 0a0f PocketPC Sync
+ 0a10 PocketPC Sync
+ 0a11 PocketPC Sync
+ 0a12 PocketPC Sync
+ 0a13 PocketPC Sync
+ 0a14 PocketPC Sync
+ 0a15 PocketPC Sync
+ 0a16 PocketPC Sync
+ 0a17 PocketPC Sync
+ 0a18 PocketPC Sync
+ 0a19 PocketPC Sync
+ 0a1a PocketPC Sync
+ 0a1b PocketPC Sync
+ 0a1c PocketPC Sync
+ 0a1d PocketPC Sync
+ 0a1e PocketPC Sync
+ 0a1f PocketPC Sync
+ 0a20 PocketPC Sync
+ 0a21 PocketPC Sync
+ 0a22 PocketPC Sync
+ 0a23 PocketPC Sync
+ 0a24 PocketPC Sync
+ 0a25 PocketPC Sync
+ 0a26 PocketPC Sync
+ 0a27 PocketPC Sync
+ 0a28 PocketPC Sync
+ 0a29 PocketPC Sync
+ 0a2a PocketPC Sync
+ 0a2b PocketPC Sync
+ 0a2c PocketPC Sync
+ 0a2d PocketPC Sync
+ 0a2e PocketPC Sync
+ 0a2f PocketPC Sync
+ 0a30 PocketPC Sync
+ 0a31 PocketPC Sync
+ 0a32 PocketPC Sync
+ 0a33 PocketPC Sync
+ 0a34 PocketPC Sync
+ 0a35 PocketPC Sync
+ 0a36 PocketPC Sync
+ 0a37 PocketPC Sync
+ 0a38 PocketPC Sync
+ 0a39 PocketPC Sync
+ 0a3a PocketPC Sync
+ 0a3b PocketPC Sync
+ 0a3c PocketPC Sync
+ 0a3d PocketPC Sync
+ 0a3e PocketPC Sync
+ 0a3f PocketPC Sync
+ 0a40 PocketPC Sync
+ 0a41 PocketPC Sync
+ 0a42 PocketPC Sync
+ 0a43 PocketPC Sync
+ 0a44 PocketPC Sync
+ 0a45 PocketPC Sync
+ 0a46 PocketPC Sync
+ 0a47 PocketPC Sync
+ 0a48 PocketPC Sync
+ 0a49 PocketPC Sync
+ 0a4a PocketPC Sync
+ 0a4b PocketPC Sync
+ 0a4c PocketPC Sync
+ 0a4d PocketPC Sync
+ 0a4e PocketPC Sync
+ 0a4f PocketPC Sync
+ 0a50 SmartPhone (MTP)
+ 0a51 SPV C400 / T-Mobile SDA GSM/GPRS Pocket PC
+ 0a52 SmartPhone Sync
+ 0a53 SmartPhone Sync
+ 0a54 SmartPhone Sync
+ 0a55 SmartPhone Sync
+ 0a56 SmartPhone Sync
+ 0a57 SmartPhone Sync
+ 0a58 SmartPhone Sync
+ 0a59 SmartPhone Sync
+ 0a5a SmartPhone Sync
+ 0a5b SmartPhone Sync
+ 0a5c SmartPhone Sync
+ 0a5d SmartPhone Sync
+ 0a5e SmartPhone Sync
+ 0a5f SmartPhone Sync
+ 0a60 SmartPhone Sync
+ 0a61 SmartPhone Sync
+ 0a62 SmartPhone Sync
+ 0a63 SmartPhone Sync
+ 0a64 SmartPhone Sync
+ 0a65 SmartPhone Sync
+ 0a66 SmartPhone Sync
+ 0a67 SmartPhone Sync
+ 0a68 SmartPhone Sync
+ 0a69 SmartPhone Sync
+ 0a6a SmartPhone Sync
+ 0a6b SmartPhone Sync
+ 0a6c SmartPhone Sync
+ 0a6d SmartPhone Sync
+ 0a6e SmartPhone Sync
+ 0a6f SmartPhone Sync
+ 0a70 SmartPhone Sync
+ 0a71 SmartPhone Sync
+ 0a72 SmartPhone Sync
+ 0a73 SmartPhone Sync
+ 0a74 SmartPhone Sync
+ 0a75 SmartPhone Sync
+ 0a76 SmartPhone Sync
+ 0a77 SmartPhone Sync
+ 0a78 SmartPhone Sync
+ 0a79 SmartPhone Sync
+ 0a7a SmartPhone Sync
+ 0a7b SmartPhone Sync
+ 0a7c SmartPhone Sync
+ 0a7d SmartPhone Sync
+ 0a7e SmartPhone Sync
+ 0a7f SmartPhone Sync
+ 0a80 SmartPhone Sync
+ 0a81 SmartPhone Sync
+ 0a82 SmartPhone Sync
+ 0a83 SmartPhone Sync
+ 0a84 SmartPhone Sync
+ 0a85 SmartPhone Sync
+ 0a86 SmartPhone Sync
+ 0a87 SmartPhone Sync
+ 0a88 SmartPhone Sync
+ 0a89 SmartPhone Sync
+ 0a8a SmartPhone Sync
+ 0a8b SmartPhone Sync
+ 0a8c SmartPhone Sync
+ 0a8d SmartPhone Sync
+ 0a8e SmartPhone Sync
+ 0a8f SmartPhone Sync
+ 0a90 SmartPhone Sync
+ 0a91 SmartPhone Sync
+ 0a92 SmartPhone Sync
+ 0a93 SmartPhone Sync
+ 0a94 SmartPhone Sync
+ 0a95 SmartPhone Sync
+ 0a96 SmartPhone Sync
+ 0a97 SmartPhone Sync
+ 0a98 SmartPhone Sync
+ 0a99 SmartPhone Sync
+ 0a9a SmartPhone Sync
+ 0a9b SmartPhone Sync
+ 0a9c SmartPhone Sync
+ 0a9d SmartPhone Sync
+ 0a9e SmartPhone Sync
+ 0a9f SmartPhone Sync
+ 0b03 Ozone Mobile Broadband
+ 0b04 Hermes / TyTN / T-Mobile MDA Vario II / O2 Xda Trion
+ 0b05 P3600
+ 0b06 Athena / Advantage x7500 / Dopod U1000 / T-Mobile AMEO
+ 0b0c Elf / Touch / P3450 / T-Mobile MDA Touch / O2 Xda Nova / Dopod S1
+ 0b1f Sony Ericsson XPERIA X1
+ 0b2f Rhodium
+ 0b51 Qtek 8310 mobile phone [Tornado Noble]
+ 0bce Vario MDA
+ 0c01 Dream / ADP1 / G1 / Magic / Tattoo
+ 0c02 Dream / ADP1 / G1 / Magic / Tattoo (Debug)
+ 0c03 Android Phone [Fairphone First Edition (FP1)]
+ 0c13 Diamond
+ 0c1f Sony Ericsson XPERIA X1
+ 0c5f Snap
+ 0c86 Sensation
+ 0c87 Desire (debug)
+ 0c8d EVO 4G (debug)
+ 0c91 Vision
+ 0c94 Vision
+ 0c97 Legend
+ 0c99 Desire (debug)
+ 0c9e Incredible
+ 0ca2 Desire HD (debug mode)
+ 0ca5 Android Phone [Evo Shift 4G]
+ 0cae T-Mobile MyTouch 4G Slide [Doubleshot]
+ 0de5 One (M7)
+ 0dea M7_UL [HTC One]
+ 0f25 One M8
+ 0f63 Desire 610 Via MTP
+ 0f64 Desire 601
+ 0fb4 Remote NDIS based Device
+ 0ff8 Desire HD (Tethering Mode)
+ 0ff9 Desire / Desire HD / Hero / Thunderbolt (Charge Mode)
+ 0ffe Desire HD (modem mode)
+ 0fff Android Fastboot Bootloader
+ 2008 Android Phone via MTP [Wiko Cink Peax 2]
+ 200b Android Phone via PTP [Wiko Cink Peax 2]
+0bb5 Murata Manufacturing Co., Ltd
+0bb6 Network Alchemy
+0bb7 Joytech Computer Co., Ltd
+0bb8 Hitachi Semiconductor and Devices Sales Co., Ltd
+0bb9 Eiger M&C Co., Ltd
+0bba ZAccess Systems
+0bbb General Meters Corp.
+0bbc Assistive Technology, Inc.
+0bbd System Connection, Inc.
+0bc0 Knilink Technology, Inc.
+0bc1 Fuw Yng Electronics Co., Ltd
+0bc2 Seagate RSS LLC
+ 0502 ST3300601CB-RK 300 GB External Hard Drive
+ 0503 ST3250824A [Barracuda 7200.9]
+ 2000 Storage Adapter V3 (TPP)
+ 2100 FreeAgent Go
+ 2200 FreeAgent Go FW
+ 2300 Expansion Portable
+ 231a Expansion Portable
+ 2320 USB 3.0 bridge [Portable Expansion Drive]
+ 2321 Expansion Portable
+ 2322 SRD0NF1 Expansion Portable (STEA)
+ 2340 FreeAgent External Hard Drive
+ 3000 FreeAgent Desktop
+ 3008 FreeAgent Desk 1TB
+ 3101 FreeAgent XTreme 640GB
+ 3312 SRD00F2 Expansion Desktop Drive (STBV)
+ 3320 SRD00F2 [Expansion Desktop Drive]
+ 3332 Expansion
+ 5020 FreeAgent GoFlex
+ 5021 FreeAgent GoFlex USB 2.0
+ 5030 FreeAgent GoFlex Upgrade Cable STAE104
+ 5031 FreeAgent GoFlex USB 3.0
+ 5032 SATA cable
+ 5070 FreeAgent GoFlex Desk
+ 5071 FreeAgent GoFlex Desk
+ 50a1 FreeAgent GoFlex Desk
+ 50a5 FreeAgent GoFlex Desk USB 3.0
+ 5121 FreeAgent GoFlex
+ 5161 FreeAgent GoFlex dock
+ 61b7 Maxtor M3 Portable
+ a003 Backup Plus
+ a0a1 Backup Plus Desktop
+ a0a4 Backup Plus Desktop Drive
+ ab00 Slim Portable Drive
+ ab20 Backup Plus Portable Drive
+ ab21 Backup Plus Slim
+ ab24 Backup Plus Portable Drive
+ ab31 Backup Plus Desktop Drive (5TB)
+ ab34 Backup Plus
+ ab38 Backup Plus Hub
+0bc3 IPWireless, Inc.
+ 0001 UMTS-TDD (TD-CDMA) modem
+0bc4 Microcube Corp.
+0bc5 JCN Co., Ltd
+0bc6 ExWAY, Inc.
+0bc7 X10 Wireless Technology, Inc.
+ 0001 ActiveHome (ACPI-compliant)
+ 0002 Firecracker Interface (ACPI-compliant)
+ 0003 VGA Video Sender (ACPI-compliant)
+ 0004 X10 Receiver
+ 0005 Wireless Transceiver (ACPI-compliant)
+ 0006 Wireless Transceiver (ACPI-compliant)
+ 0007 Wireless Transceiver (ACPI-compliant)
+ 0008 Wireless Transceiver (ACPI-compliant)
+ 0009 Wireless Transceiver (ACPI-compliant)
+ 000a Wireless Transceiver (ACPI-compliant)
+ 000b Transceiver (ACPI-compliant)
+ 000c Transceiver (ACPI-compliant)
+ 000d Transceiver (ACPI-compliant)
+ 000e Transceiver (ACPI-compliant)
+ 000f Transceiver (ACPI-compliant)
+0bc8 Telmax Communications
+0bc9 ECI Telecom, Ltd
+0bca Startek Engineering, Inc.
+0bcb Perfect Technic Enterprise Co., Ltd
+0bd7 Andrew Pargeter & Associates
+ a021 Amptek DP4 multichannel signal analyzer
+0bda Realtek Semiconductor Corp.
+ 0103 USB 2.0 Card Reader
+ 0104 Mass Storage Device
+ 0106 Mass Storage Device
+ 0107 Mass Storage Device
+ 0108 Mass Storage Device
+ 0109 microSDXC Card Reader [Hama 00091047]
+ 0111 RTS5111 Card Reader Controller
+ 0113 Mass Storage Device
+ 0115 Mass Storage Device (Multicard Reader)
+ 0116 RTS5116 Card Reader Controller
+ 0117 Mass Storage Device
+ 0118 Mass Storage Device
+ 0119 Storage Device (SD card reader)
+ 0129 RTS5129 Card Reader Controller
+ 0138 RTS5138 Card Reader Controller
+ 0139 RTS5139 Card Reader Controller
+ 0151 Mass Storage Device (Multicard Reader)
+ 0152 Mass Storage Device
+ 0153 3-in-1 (SD/SDHC/SDXC) Card Reader
+ 0156 Mass Storage Device
+ 0157 Mass Storage Device
+ 0158 USB 2.0 multicard reader
+ 0159 RTS5159 Card Reader Controller
+ 0161 Mass Storage Device
+ 0168 Mass Storage Device
+ 0169 Mass Storage Device
+ 0171 Mass Storage Device
+ 0176 Mass Storage Device
+ 0178 Mass Storage Device
+ 0179 RTL8188ETV Wireless LAN 802.11n Network Adapter
+ 0184 RTS5182 Card Reader
+ 0186 Card Reader
+ 0301 multicard reader
+ 0307 Card Reader
+ 1724 RTL8723AU 802.11n WLAN Adapter
+ 2831 RTL2831U DVB-T
+ 2832 RTL2832U DVB-T
+ 2838 RTL2838 DVB-T
+ 5401 RTL 8153 USB 3.0 hub with gigabit ethernet
+ 570c Asus laptop camera
+ 5730 HP 2.0MP High Definition Webcam
+ 5751 Integrated Webcam
+ 5775 HP "Truevision HD" laptop camera
+ 57b3 Acer 640 × 480 laptop camera
+ 57da Built-In Video Camera
+ 8150 RTL8150 Fast Ethernet Adapter
+ 8151 RTL8151 Adapteon Business Mobile Networks BV
+ 8152 RTL8152 Fast Ethernet Adapter
+ 8153 RTL8153 Gigabit Ethernet Adapter
+ 8171 RTL8188SU 802.11n WLAN Adapter
+ 8172 RTL8191SU 802.11n WLAN Adapter
+ 8174 RTL8192SU 802.11n WLAN Adapter
+ 8176 RTL8188CUS 802.11n WLAN Adapter
+ 8178 RTL8192CU 802.11n WLAN Adapter
+ 8179 RTL8188EUS 802.11n Wireless Network Adapter
+ 817f RTL8188RU 802.11n WLAN Adapter
+ 8187 RTL8187 Wireless Adapter
+ 8189 RTL8187B Wireless 802.11g 54Mbps Network Adapter
+ 818b ACT-WNP-UA-005 802.11b/g/n WLAN Adapter
+ 8192 RTL8191SU 802.11n Wireless Adapter
+ 8193 RTL8192DU 802.11an WLAN Adapter
+ 8197 RTL8187B Wireless Adapter
+ 8198 RTL8187B Wireless Adapter
+ 8199 RTL8187SU 802.11g WLAN Adapter
+ 8812 RTL8812AU 802.11a/b/g/n/ac WLAN Adapter
+0bdb Ericsson Business Mobile Networks BV
+ 1000 BV Bluetooth Device
+ 1002 Bluetooth Device 1.2
+ 1049 C3607w Mobile Broadband Module
+ 1900 F3507g Mobile Broadband Module
+ 1902 F3507g v2 Mobile Broadband Module
+ 1904 F3607gw Mobile Broadband Module
+ 1905 F3607gw v2 Mobile Broadband Module
+ 1906 F3607gw v3 Mobile Broadband Module
+ 1909 F3307 v2 Mobile Broadband Module
+ 190a F3307 Mobile Broadband Module
+ 190b C3607w v2 Mobile Broadband Module
+ 1926 H5321 gw Mobile Broadband Driver
+0bdc Y Media Corp.
+0bdd Orange PCS
+0be2 Kanda Tsushin Kogyo Co., Ltd
+0be3 TOYO Corp.
+0be4 Elka International, Ltd
+0be5 DOME imaging systems, Inc.
+0be6 Dong Guan Humen Wonderful Wire Cable Factory
+0bed MEI
+ 1100 CASHFLOW SC
+ 1101 Series 2000 Combo Acceptor
+0bee LTK Industries, Ltd
+0bef Way2Call Communications
+0bf0 Pace Micro Technology PLC
+0bf1 Intracom S.A.
+ 0001 netMod Driver Ver 2.4.17 (CAPI)
+ 0002 netMod Driver Ver 2.4 (CAPI)
+ 0003 netMod Driver Ver 2.4 (CAPI)
+0bf2 Konexx
+0bf6 Addonics Technologies, Inc.
+ 0103 Storage Device
+ 1234 Storage Device
+ a000 Cable 205 (TPP)
+ a001 Cable 205
+ a002 IDE Bridge
+0bf7 Sunny Giken, Inc.
+0bf8 Fujitsu Siemens Computers
+ 1001 Fujitsu Pocket Loox 600 PDA
+ 1006 SmartCard Reader 2A
+ 1007 Connect2Air E-5400 802.11g Wireless Adapter
+ 1009 Connect2Air E-5400 D1700 802.11g Wireless Adapter [Intersil ISL3887]
+ 100c Keyboard FSC KBPC PX
+ 100f miniCard D2301 802.11bg Wireless Module [SiS 163U]
+ 1017 Keyboard KB SCR
+ 101f Fujitsu Full HD Pro Webcam
+0bfd Kvaser AB
+ 0004 USBcan II
+ 000b Leaf Light HS
+ 000e Leaf SemiPro HS
+0c00 FireFly Mouse Mat
+ 1607 Apex M500
+0c04 MOTO Development Group, Inc.
+0c05 Appian Graphics
+0c06 Hasbro Games, Inc.
+0c07 Infinite Data Storage, Ltd
+0c08 Agate
+ 0378 Q 16MB Storage Device
+0c09 Comjet Information System
+ a5a5 Litto Version USB2.0
+0c0a Highpoint Technologies, Inc.
+0c0b Dura Micro, Inc. (Acomdata)
+ 27cb 6-in-1 Flash Reader and Writer
+ 27d7 Multi Memory reader/writer MD-005
+ 27da Multi Memory reader/writer MD-005
+ 27dc Multi Memory reader/writer MD-005
+ 27e7 3,5'' HDD case MD-231
+ 27ee 3,5'' HDD case MD-231
+ 2814 3,5'' HDD case MD-231
+ 2815 3,5'' HDD case MD-231
+ 281d 3,5'' HDD case MD-231
+ 5fab Storage Adaptor
+ a109 CF/SM Reader and Writer
+ a10c SD/MS Reader and Writer
+ b001 USB 2.0 Mass Storage IDE adapter
+ b004 MMC/SD Reader and Writer
+0c12 Zeroplus
+ 0005 PSX Vibration Feedback Converter
+ 0030 PSX Vibration Feedback Converter
+ 700e Logic Analyzer (LAP-C-16032)
+ 8801 Xbox Controller
+ 8802 Xbox Controller
+ 8809 Red Octane Ignition Xbox DDR Pad
+ 880a Pelican Eclipse PL-2023
+ 8810 Xbox Controller
+ 9902 VibraX
+0c15 Iris Graphics
+0c16 Gyration, Inc.
+ 0002 RF Technology Receiver
+ 0003 RF Technology Receiver
+ 0008 RF Technology Receiver
+ 0080 eHome Infrared Receiver
+ 0081 eHome Infrared Receiver
+0c17 Cyberboard A/S
+0c18 SynerTek Korea, Inc.
+0c19 cyberPIXIE, Inc.
+0c1a Silicon Motion, Inc.
+0c1b MIPS Technologies
+0c1c Hang Zhou Silan Electronics Co., Ltd
+0c22 Tally Printer Corp.
+0c23 Lernout + Hauspie
+0c24 Taiyo Yuden
+ 0001 Bluetooth Adaptor
+ 0002 Bluetooth Device2
+ 0005 Bluetooth Device(BC04-External)
+ 000b Bluetooth Device(BC04-External)
+ 000c Bluetooth Adaptor
+ 000e Bluetooth Device(BC04-External)
+ 000f Bluetooth Device (V2.0+EDR)
+ 0010 Bluetooth Device(BC04-External)
+ 0012 Bluetooth Device(BC04-External)
+ 0018 Bluetooth Device(BC04-External)
+ 0019 Bluetooth Device
+ 0021 Bluetooth Device (V2.1+EDR)
+ 0c24 Bluetooth Device(SAMPLE)
+ ffff Bluetooth module with BlueCore in DFU mode
+0c25 Sampo Corp.
+ 0310 Scream Cam
+0c26 Prolific Technology Inc.
+ 0018 USB-Serial Controller [Icom Inc. OPC-478UC]
+0c27 RFIDeas, Inc
+ 3bfa pcProx Card Reader
+0c2e Metrologic Instruments
+ 0007 Metrologic MS7120 Barcode Scanner (IBM SurePOS mode)
+ 0200 MS7120 Barcode Scanner
+ 0204 Metrologic MS7120 Barcode Scanner (keyboard mode)
+ 0206 Metrologic MS4980 Barcode Scanner
+ 0700 Metrologic MS7120 Barcode Scanner (uni-directional serial mode)
+ 0720 Metrologic MS7120 Barcode Scanner (bi-directional serial mode)
+ 0b61 Vuquest 3310g
+ 0b6a Vuquest 3310 Area-Imaging Scanner
+ 0b81 Barcode scanner Voyager 1400g Series
+0c35 Eagletron, Inc.
+0c36 E Ink Corp.
+0c37 e.Digital
+0c38 Der An Electric Wire & Cable Co., Ltd
+0c39 IFR
+0c3a Furui Precise Component (Kunshan) Co., Ltd
+0c3b Komatsu, Ltd
+0c3c Radius Co., Ltd
+0c3d Innocom, Inc.
+0c3e Nextcell, Inc.
+0c44 Motorola iDEN
+ 0021 iDEN P2k0 Device
+ 0022 iDEN P2k1 Device
+ 03a2 iDEN Smartphone
+ 41d9 i1 phone
+0c45 Microdia
+ 0011 EBUDDY
+ 0520 MaxTrack Wireless Mouse
+ 1018 Compact Flash storage memory card reader
+ 1020 Mass Storage Reader
+ 1028 Mass Storage Reader
+ 1030 Mass Storage Reader
+ 1031 Sonix Mass Storage Device
+ 1032 Mass Storage Reader
+ 1033 Sonix Mass Storage Device
+ 1034 Mass Storage Reader
+ 1035 Mass Storage Reader
+ 1036 Mass Storage Reader
+ 1037 Sonix Mass Storage Device
+ 1050 CF Card Reader
+ 1058 HDD Reader
+ 1060 iFlash SM-Direct Card Reader
+ 1061 Mass Storage Reader
+ 1062 Mass Storage Reader
+ 1063 Sonix Mass Storage Device
+ 1064 Mass Storage Reader
+ 1065 Mass Storage Reader
+ 1066 Mass Storage Reader
+ 1067 Mass Storage Reader
+ 1158 A56AK
+ 184c VoIP Phone
+ 6001 Genius VideoCAM NB
+ 6005 Sweex Mini Webcam
+ 6007 VideoCAM Eye
+ 6009 VideoCAM ExpressII
+ 600d TwinkleCam USB camera
+ 6011 PC Camera (SN9C102)
+ 6019 PC Camera (SN9C102)
+ 6024 VideoCAM ExpressII
+ 6025 VideoCAM ExpressII
+ 6028 Typhoon Easycam USB 330K (older)
+ 6029 Triplex i-mini PC Camera
+ 602a Meade ETX-105EC Camera
+ 602b VideoCAM NB 300
+ 602c Clas Ohlson TWC-30XOP Webcam
+ 602d VideoCAM ExpressII
+ 602e VideoCAM Messenger
+ 6030 VideoCAM ExpressII
+ 603f VideoCAM ExpressII
+ 6040 CCD PC Camera (PC390A)
+ 606a CCD PC Camera (PC390A)
+ 607a CCD PC Camera (PC390A)
+ 607b Win2 PC Camera
+ 607c CCD PC Camera (PC390A)
+ 607e CCD PC Camera (PC390A)
+ 6080 Audio (Microphone)
+ 6082 VideoCAM Look
+ 6083 VideoCAM Look
+ 608c VideoCAM Look
+ 608e VideoCAM Look
+ 608f PC Camera (SN9C103 + OV7630)
+ 60a8 VideoCAM Look
+ 60aa VideoCAM Look
+ 60ab PC Camera
+ 60af VideoCAM Look
+ 60b0 Genius VideoCam Look
+ 60c0 PC Camera with Mic (SN9C105)
+ 60c8 Win2 PC Camera
+ 60cc PC Camera with Mic (SN9C105)
+ 60ec PC Camera with Mic (SN9C105)
+ 60ef Win2 PC Camera
+ 60fa PC Camera with Mic (SN9C105)
+ 60fb Composite Device
+ 60fc PC Camera with Mic (SN9C105)
+ 60fe Audio (Microphone)
+ 6108 Win2 PC Camera
+ 6122 PC Camera (SN9C110)
+ 6123 PC Camera (SN9C110)
+ 6128 PC Camera (SN9C325 + OM6802)
+ 612a PC Camera (SN9C325)
+ 612c PC Camera (SN9C110)
+ 612e PC Camera (SN9C110)
+ 612f PC Camera (SN9C110)
+ 6130 PC Camera (SN9C120)
+ 6138 Win2 PC Camera
+ 613a PC Camera (SN9C120)
+ 613b Win2 PC Camera
+ 613c PC Camera (SN9C120)
+ 613e PC Camera (SN9C120)
+ 6143 PC Camera (SN9C120 + SP80708)
+ 6240 PC Camera (SN9C201 + MI1300)
+ 6242 PC Camera (SN9C201 + MI1310)
+ 6243 PC Camera (SN9C201 + S5K4AAFX)
+ 6248 PC Camera (SN9C201 + OV9655)
+ 624b PC Camera (SN9C201 + CX1332)
+ 624c PC Camera (SN9C201 + MI1320)
+ 624e PC Camera (SN9C201 + SOI968)
+ 624f PC Camera (SN9C201 + OV9650)
+ 6251 PC Camera (SN9C201 + OV9650)
+ 6253 PC Camera (SN9C201 + OV9650)
+ 6260 PC Camera (SN9C201 + OV7670ISP)
+ 6262 PC Camera (SN9C201 + OM6802)
+ 6270 PC Camera (SN9C201 + MI0360/MT9V011 or MI0360SOC/MT9V111) U-CAM PC Camera NE878, Whitcom WHC017, ...
+ 627a PC Camera (SN9C201 + S5K53BEB)
+ 627b PC Camera (SN9C201 + OV7660)
+ 627c PC Camera (SN9C201 + HV7131R)
+ 627f PC Camera (SN9C201 + OV965x + EEPROM)
+ 6280 PC Camera with Microphone (SN9C202 + MI1300)
+ 6282 PC Camera with Microphone (SN9C202 + MI1310)
+ 6283 PC Camera with Microphone (SN9C202 + S5K4AAFX)
+ 6288 PC Camera with Microphone (SN9C202 + OV9655)
+ 628a PC Camera with Microphone (SN9C202 + ICM107)
+ 628b PC Camera with Microphone (SN9C202 + CX1332)
+ 628c PC Camera with Microphone (SN9C202 + MI1320)
+ 628e PC Camera with Microphone (SN9C202 + SOI968)
+ 628f PC Camera with Microphone (SN9C202 + OV9650)
+ 62a0 PC Camera with Microphone (SN9C202 + OV7670ISP)
+ 62a2 PC Camera with Microphone (SN9C202 + OM6802)
+ 62b0 PC Camera with Microphone (SN9C202 + MI0360/MT9V011 or MI0360SOC/MT9V111)
+ 62b3 PC Camera with Microphone (SN9C202 + OV9655)
+ 62ba PC Camera with Microphone (SN9C202 + S5K53BEB)
+ 62bb PC Camera with Microphone (SN9C202 + OV7660)
+ 62bc PC Camera with Microphone (SN9C202 + HV7131R)
+ 62be PC Camera with Microphone (SN9C202 + OV7663)
+ 62c0 Sonix USB 2.0 Camera
+ 62e0 MSI Starcam Racer
+ 6300 PC Microscope camera
+ 6310 Sonix USB 2.0 Camera
+ 6340 Camera
+ 6341 Defender G-Lens 2577 HD720p Camera
+ 63e0 Sonix Integrated Webcam
+ 63f1 Integrated Webcam
+ 63f8 Sonix Integrated Webcam
+ 6409 Webcam
+ 6413 Integrated Webcam
+ 6417 Integrated Webcam
+ 6419 Integrated Webcam
+ 641d 1.3 MPixel Integrated Webcam
+ 643f Dell Integrated HD Webcam
+ 644d 1.3 MPixel Integrated Webcam
+ 6480 Sonix 1.3 MP Laptop Integrated Webcam
+ 648b Integrated Webcam
+ 64bd Sony Visual Communication Camera
+ 64d0 Integrated Webcam
+ 64d2 Integrated Webcam
+ 651b HP Webcam
+ 6705 Integrated HD Webcam
+ 6710 Integrated Webcam
+ 7401 TEMPer Temperature Sensor
+ 7402 TEMPerHUM Temperature & Humidity Sensor
+ 7403 Foot Switch
+ 8000 DC31VC
+ 8006 Dual Mode Camera (8006 VGA)
+ 800a Vivitar Vivicam3350B
+0c46 WaveRider Communications, Inc.
+0c4a ALGE-TIMING GmbH
+ 0889 Timy
+ 088a Timy 2
+0c4b Reiner SCT Kartensysteme GmbH
+ 0100 cyberJack e-com/pinpad
+ 0300 cyberJack pinpad(a)
+ 0400 cyberJack e-com(a)
+ 0401 cyberJack pinpad(a2)
+ 0500 cyberJack RFID standard dual interface smartcard reader
+ 0501 cyberJack RFID comfort dual interface smartcard reader
+ 0502 cyberJack compact
+ 0504 cyberJack go / go plus
+ 0505 cyberJack wave
+ 9102 cyberJack RFID basis contactless smartcard reader
+0c4c Needham's Electronics
+ 0021 EMP-21 Universal Programmer
+0c52 Sealevel Systems, Inc.
+ 2101 SeaLINK+232
+ 2102 SeaLINK+485
+ 2103 SeaLINK+232I
+ 2104 SeaLINK+485I
+ 2211 SeaPORT+2/232 (Port 1)
+ 2212 SeaPORT+2/485 (Port 1)
+ 2213 SeaPORT+2 (Port 1)
+ 2221 SeaPORT+2/232 (Port 2)
+ 2222 SeaPORT+2/485 (Port 2)
+ 2223 SeaPORT+2 (Port 2)
+ 2411 SeaPORT+4/232 (Port 1)
+ 2412 SeaPORT+4/485 (Port 1)
+ 2413 SeaPORT+4 (Port 1)
+ 2421 SeaPORT+4/232 (Port 2)
+ 2422 SeaPORT+4/485 (Port 2)
+ 2423 SeaPORT+4 (Port 2)
+ 2431 SeaPORT+4/232 (Port 3)
+ 2432 SeaPORT+4/485 (Port 3)
+ 2433 SeaPORT+4 (Port 3)
+ 2441 SeaPORT+4/232 (Port 4)
+ 2442 SeaPORT+4/485 (Port 4)
+ 2443 SeaPORT+4 (Port 4)
+ 2811 SeaLINK+8/232 (Port 1)
+ 2812 SeaLINK+8/485 (Port 1)
+ 2813 SeaLINK+8 (Port 1)
+ 2821 SeaLINK+8/232 (Port 2)
+ 2822 SeaLINK+8/485 (Port 2)
+ 2823 SeaLINK+8 (Port 2)
+ 2831 SeaLINK+8/232 (Port 3)
+ 2832 SeaLINK+8/485 (Port 3)
+ 2833 SeaLINK+8 (Port 3)
+ 2841 SeaLINK+8/232 (Port 4)
+ 2842 SeaLINK+8/485 (Port 4)
+ 2843 SeaLINK+8 (Port 4)
+ 2851 SeaLINK+8/232 (Port 5)
+ 2852 SeaLINK+8/485 (Port 5)
+ 2853 SeaLINK+8 (Port 5)
+ 2861 SeaLINK+8/232 (Port 6)
+ 2862 SeaLINK+8/485 (Port 6)
+ 2863 SeaLINK+8 (Port 6)
+ 2871 SeaLINK+8/232 (Port 7)
+ 2872 SeaLINK+8/485 (Port 7)
+ 2873 SeaLINK+8 (Port 7)
+ 2881 SeaLINK+8/232 (Port 8)
+ 2882 SeaLINK+8/485 (Port 8)
+ 2883 SeaLINK+8 (Port 8)
+ 9020 SeaLINK+422
+ a02a SeaLINK+8 (Port 1+2)
+ a02b SeaLINK+8 (Port 3+4)
+ a02c SeaLINK+8 (Port 5+6)
+ a02d SeaLINK+8 (Port 7+8)
+0c53 ViewPLUS, Inc.
+0c54 Glory, Ltd
+0c55 Spectrum Digital, Inc.
+ 0510 Spectrum Digital XDS510 JTAG Debugger
+ 0540 SPI540
+ 5416 TMS320C5416 DSK
+ 6416 TMS320C6416 DDB
+0c56 Billion Bright, Ltd
+0c57 Imaginative Design Operation Co., Ltd
+0c58 Vidar Systems Corp.
+0c59 Dong Guan Shinko Wire Co., Ltd
+0c5a TRS International Mfg., Inc.
+0c5e Xytronix Research & Design
+0c60 Apogee Electronics Corp.
+ 0001 MiniMe
+ 0002 MiniDAC
+ 0003 ONE
+ 0004 GiO
+ 0007 Duet
+ 0009 Jam
+ 000a Jam Bootloader
+ 000b MiC
+ 000c MiC Bootloader
+ 8007 Duet DFU Mode
+0c62 Chant Sincere Co., Ltd
+0c63 Toko, Inc.
+0c64 Signality System Engineering Co., Ltd
+0c65 Eminence Enterprise Co., Ltd
+0c66 Rexon Electronics Corp.
+0c67 Concept Telecom, Ltd
+0c6a ACS
+ 0005 Color 320 x 240 LCD Display Terminal with Touchscreen
+0c6c JETI Technische Instrumente GmbH
+ 04b2 Specbos 1201
+0c70 MCT Elektronikladen
+ 0000 USB08 Development board
+ 0747 Eye Movement Recorder [Visagraph]/[ReadAlyzer]
+0c72 PEAK System
+ 000c PCAN-USB
+ 000d PCAN Pro
+0c74 Optronic Laboratories Inc.
+ 0002 OL 700-30 Goniometer
+0c76 JMTek, LLC.
+ 0001 Mass Storage Controller
+ 0002 Mass Storage Controller
+ 0003 USBdisk
+ 0004 Mass Storage Controller
+ 0005 Transcend Flash disk
+ 0006 Transcend JetFlash
+ 0007 Mass Storage Device
+ 1600 Ion Quick Play LP turntable
+ 1605 SSS Headphone Set
+ 1607 audio controller
+0c77 Sipix Group, Ltd
+ 1001 SiPix Web2
+ 1002 SiPix SC2100
+ 1010 SiPix Snap
+ 1011 SiPix Blink 2
+ 1015 SiPix CAMeleon
+0c78 Detto Corp.
+0c79 NuConnex Technologies Pte., Ltd
+0c7a Wing-Span Enterprise Co., Ltd
+0c86 NDA Technologies, Inc.
+0c88 Kyocera Wireless Corp.
+ 0021 Handheld
+ 17da Qualcomm Kyocera CDMA Technologies MSM
+0c89 Honda Tsushin Kogyo Co., Ltd
+0c8a Pathway Connectivity, Inc.
+0c8b Wavefly Corp.
+0c8c Coactive Networks
+0c8d Tempo
+0c8e Cesscom Co., Ltd
+ 6000 Luxian Series
+0c8f Applied Microsystems
+0c94 Cryptera
+ a000 EPP 1217
+0c98 Berkshire Products, Inc.
+ 1140 USB PC Watchdog
+0c99 Innochips Co., Ltd
+0c9a Hanwool Robotics Corp.
+0c9b Jobin Yvon, Inc.
+0c9d SemTek
+ 0170 3873 Manual Insert card reader
+0ca2 Zyfer
+0ca3 Sega Corp.
+0ca4 ST&T Instrument Corp.
+0ca5 BAE Systems Canada, Inc.
+0ca6 Castles Technology Co., Ltd
+ 0010 EZUSB PC/SC Smart Card Reader
+ 0050 EZ220PU Reader Controller
+ 1077 Bludrive Family Smart Card Reader
+ 107e Reader Controller
+ 2010 myPad110 PC/SC Smart Card Reader
+ 3050 EZ710 Smart Card Reader
+0ca7 Information Systems Laboratories
+0cad Motorola CGISS
+ 1007 APX Series Consolette
+ 1030 APX Series Radio (Portable)
+ 1031 APX Series Radio (Mobile)
+ 1602 IMPRES Battery Data Reader
+ 9001 PowerPad Pocket PC Device
+0cae Ascom Business Systems, Ltd
+0caf Buslink
+ 2507 Hi-Speed USB-to-IDE Bridge Controller
+ 2515 Flash Disk Embedded Hub
+ 2516 Flash Disk Security Device
+ 2517 Flash Disk Mass Storage Device
+ 25c7 Hi-Speed USB-to-IDE Bridge Controller
+ 3a00 Hard Drive
+ 3a20 Mass Storage Device
+ 3acd Mass Storage Device
+0cb0 Flying Pig Systems
+0cb1 Innovonics, Inc.
+0cb6 Celestix Networks, Pte., Ltd
+0cb7 Singatron Enterprise Co., Ltd
+0cb8 Opticis Co., Ltd
+0cba Trust Electronic (Shanghai) Co., Ltd
+0cbb Shanghai Darong Electronics Co., Ltd
+0cbc Palmax Technology Co., Ltd
+ 0101 Pocket PC P6C
+ 0201 Personal Digital Assistant
+ 0301 Personal Digital Assistant P6M+
+ 0401 Pocket PC
+0cbd Pentel Co., Ltd (Electronics Equipment Div.)
+0cbe Keryx Technologies, Inc.
+0cbf Union Genius Computer Co., Ltd
+0cc0 Kuon Yi Industrial Corp.
+0cc1 Given Imaging, Ltd
+0cc2 Timex Corp.
+0cc3 Rimage Corp.
+0cc4 emsys GmbH
+0cc5 Sendo
+0cc6 Intermagic Corp.
+0cc8 Technotools Corp.
+0cc9 BroadMAX Technologies, Inc.
+0cca Amphenol
+0ccb SKNet Co., Ltd
+0ccc Domex Technology Corp.
+0ccd TerraTec Electronic GmbH
+ 0012 PHASE 26
+ 0013 PHASE 26
+ 0014 PHASE 26
+ 0015 Flash Update for TerraTec PHASE 26
+ 0021 Cameo Grabster 200
+ 0023 Mystify Claw
+ 0028 Aureon 5.1 MkII
+ 0032 MIDI HUBBLE
+ 0035 Miditech Play'n Roll
+ 0036 Cinergy 250 Audio
+ 0037 Cinergy 250 Audio
+ 0038 Cinergy T² DVB-T Receiver
+ 0039 Grabster AV 400
+ 003b Cinergy 400
+ 003c Grabster AV 250
+ 0042 Cinergy Hybrid T XS
+ 0043 Cinergy T XS
+ 004e Cinergy T XS
+ 004f Cinergy Analog XS
+ 0055 Cinergy T XE (Version 1, AF9005)
+ 005c Cinergy T²
+ 0069 Cinergy T XE (Version 2, AF9015)
+ 006b Cinergy HT PVR (EU)
+ 0072 Cinergy Hybrid T
+ 0077 Aureon Dual USB
+ 0078 Cinergy T XXS
+ 0086 Cinergy Hybrid XE
+ 008e Cinergy HTC XS
+ 0096 Grabby
+ 0097 Cinergy T RC MKII
+ 0099 AfaTech 9015 [Cinergy T Stick Dual]
+ 00a5 Cinergy Hybrid Stick
+ 00a9 RTL2838 DVB-T COFDM Demodulator [TerraTec Cinergy T Stick Black]
+ 00b3 NOXON DAB/DAB+ Stick
+ 00e0 NOXON DAB/DAB+ Stick V2
+ 0102 Cinergy S2 Stick
+ 0105 Cinergy S2 Box
+ 10a7 TerraTec G3
+0cd4 Bang Olufsen
+ 0101 BeolinkPC2
+0cd5 LabJack Corporation
+ 0003 U3
+ 0009 UE9
+0cd6 Scheidt & Bachmann
+ 000c S&B TPU
+ 000e S&B BKV
+ 0011 Money Coin Unit
+0cd7 NewChip S.r.l.
+0cd8 JS Digitech, Inc.
+ 2007 Smart Card Reader/JSTU-9700
+0cd9 Hitachi Shin Din Cable, Ltd
+0cde Z-Com
+ 0001 XI-750 802.11b Wireless Adapter [Atmel AT76C503A]
+ 0002 XI-725/726 Prism2.5 802.11b Adapter
+ 0003 Sagem 802.11b Dongle
+ 0004 Sagem 802.11b Dongle
+ 0005 XI-735 Prism3 802.11b Adapter
+ 0006 XG-300 802.11b Adapter
+ 0008 XG-703A 802.11g Wireless Adapter [Intersil ISL3887]
+ 0009 (ZD1211)IEEE 802.11b+g Adapter
+ 0011 ZD1211
+ 0012 AR5523
+ 0013 AR5523 driver (no firmware)
+ 0014 NB 802.11g Wireless LAN Adapter(3887A)
+ 0015 XG-705A 802.11g Wireless Adapter [Intersil ISL3887]
+ 0016 NB 802.11g Wireless LAN Adapter(3887A)
+ 0018 NB 802.11a/b/g Wireless LAN Adapter(3887A)
+ 001a 802.11bg
+ 001c 802.11b/g Wireless Network Adapter
+ 0020 AG-760A 802.11abg Wireless Adapter [ZyDAS ZD1211B]
+ 0022 802.11b/g/n Wireless Network Adapter
+ 0023 UB81 802.11bgn
+ 0025 802.11b/g/n USB Wireless Network Adapter
+ 0026 UB82 802.11abgn
+ 0027 Sphairon Homelink 1202 802.11n Wireless Adapter [Atheros AR9170]
+0ce5 Validation Technologies International
+ 0003 Matrix
+0ce9 Pico Technology
+ 1001 PicoScope3000 series PC Oscilloscope
+ 1007 PicoScope 2000 series PC Oscilloscope
+ 1008 PicoScope 5000 series PC Oscilloscope
+ 1009 PicoScope 4000 series PC Oscilloscope
+ 100e PicoScope 6000 series PC Oscilloscope
+ 1012 PicoScope 3000A series PC Oscilloscope
+ 1016 PicoScope 2000A series PC Oscilloscope
+ 1018 PicoScope 4000A series PC Oscilloscope
+ 1200 PicoScope 2000 series PC Oscilloscope
+ 1201 PicoScope 3000 series PC Oscilloscope
+ 1202 PicoScope 4000 series PC Oscilloscope
+ 1203 PicoScope 5000 series PC Oscilloscope
+ 1204 PicoScope 6000 series PC Oscilloscope
+ 1211 PicoScope 3000 series PC Oscilloscope
+ 1212 PicoScope 4000 series PC Oscilloscope
+0cf1 e-Conn Electronic Co., Ltd
+0cf2 ENE Technology, Inc.
+ 6220 SD Card Reader (SG361)
+ 6225 SD card reader (UB6225)
+ 6230 SD Card Reader (UB623X)
+ 6250 SD card reader (UB6250)
+0cf3 Qualcomm Atheros Communications
+ 0001 AR5523
+ 0002 AR5523 (no firmware)
+ 0003 AR5523
+ 0004 AR5523 (no firmware)
+ 0005 AR5523
+ 0006 AR5523 (no firmware)
+ 1001 Thomson TG121N [Atheros AR9001U-(2)NG]
+ 1002 TP-Link TL-WN821N v2 / TL-WN822N v1 802.11n [Atheros AR9170]
+ 1006 TP-Link TL-WN322G v3 / TL-WN422G v2 802.11g [Atheros AR9271]
+ 1010 3Com 3CRUSBN275 802.11abgn Wireless Adapter [Atheros AR9170]
+ 20ff AR7010 (no firmware)
+ 3000 AR3011 Bluetooth (no firmware)
+ 3002 AR3011 Bluetooth
+ 3004 AR3012 Bluetooth 4.0
+ 3005 AR3011 Bluetooth
+ 3007 AR3012 Bluetooth 4.0 (no firmware)
+ 3008 Bluetooth (AR3011)
+ 311f AR3012 Bluetooth
+ 7015 TP-Link TL-WN821N v3 / TL-WN822N v2 802.11n [Atheros AR7010+AR9287]
+ 9170 AR9170 802.11n
+ 9271 AR9271 802.11n
+ b002 Ubiquiti WiFiStation 802.11n [Atheros AR9271]
+ b003 Ubiquiti WiFiStationEXT 802.11n [Atheros AR9271]
+ e006 Dell Wireless 1802 Bluetooth 4.0 LE
+0cf4 Fomtex Corp.
+0cf5 Cellink Co., Ltd
+0cf6 Compucable Corp.
+0cf7 ishoni Networks
+0cf8 Clarisys, Inc.
+ 0750 Claritel-i750 - vp
+0cf9 Central System Research Co., Ltd
+0cfa Inviso, Inc.
+0cfc Minolta-QMS, Inc.
+ 2301 Magicolor 2300 DL
+ 2350 Magicolor 2350EN/3300
+ 3100 Magicolor 3100
+ 7300 Magicolor 5450/5550
+0cff SAFA MEDIA Co., Ltd.
+ 0320 SR-380N
+0d06 telos EDV Systementwicklung GmbH
+0d08 UTStarcom
+ 0602 DV007 [serial]
+ 0603 DV007 [storage]
+0d0b Contemporary Controls
+0d0c Astron Electronics Co., Ltd
+0d0d MKNet Corp.
+0d0e Hybrid Networks, Inc.
+0d0f Feng Shin Cable Co., Ltd
+0d10 Elastic Networks
+ 0001 StormPort (WDM)
+0d11 Maspro Denkoh Corp.
+0d12 Hansol Electronics, Inc.
+0d13 BMF Corp.
+0d14 Array Comm, Inc.
+0d15 OnStream b.v.
+0d16 Hi-Touch Imaging Technologies Co., Ltd
+ 0001 PhotoShuttle
+ 0002 Photo Printer 730 series
+ 0004 Photo Printer 63xPL/PS
+ 000e P910L
+ 0100 Photo Printer 63xPL/PS
+ 0102 Photo Printer 64xPS
+ 0103 Photo Printer 730 series
+ 0104 Photo Printer 63xPL/PS
+ 0105 Photo Printer 64xPS
+ 0200 Photo Printer 64xDL
+0d17 NALTEC, Inc.
+0d18 coaXmedia
+0d19 Hank Connection Industrial Co., Ltd
+0d28 NXP
+ 0204 LPC1768
+0d32 Leo Hui Electric Wire & Cable Co., Ltd
+0d33 AirSpeak, Inc.
+0d34 Rearden Steel Technologies
+0d35 Dah Kun Co., Ltd
+0d3a Posiflex Technologies, Inc.
+ 0206 Series 3xxx Cash Drawer
+ 0207 Series 3xxx Cash Drawer
+ 0500 Magnetic Stripe Reader
+0d3c Sri Cable Technology, Ltd
+0d3d Tangtop Technology Co., Ltd
+ 0001 HID Keyboard
+ 0040 PS/2 Adapter
+0d3e Fitcom, inc.
+0d3f MTS Systems Corp.
+0d40 Ascor, Inc.
+0d41 Ta Yun Terminals Industrial Co., Ltd
+0d42 Full Der Co., Ltd
+0d46 Kobil Systems GmbH
+ 2012 KAAN Standard Plus (Smartcard reader)
+ 3003 mIDentity Light / KAAN SIM III
+ 4000 mIDentity (mass storage)
+ 4001 mIDentity Basic/Classic (composite device)
+ 4081 mIDentity Basic/Classic (installationless)
+0d48 Promethean Limited
+ 0001 ACTIVboard
+ 0004 ACTIVboard
+ 0100 Audio
+0d49 Maxtor
+ 3000 Drive
+ 3010 3000LE Drive
+ 3100 Hi-Speed USB-IDE Bridge Controller
+ 3200 Personal Storage 3200
+ 5000 5000XT Drive
+ 5010 5000LE Drive
+ 5020 Mobile Hard Disk Drive
+ 7000 OneTouch
+ 7010 OneTouch
+ 7100 OneTouch II 300GB External Hard Disk
+ 7310 OneTouch 4
+ 7410 Mobile Hard Disk Drive (1TB)
+ 7450 Basics Portable USB Device
+0d4a NF Corp.
+0d4b Grape Systems, Inc.
+0d4c Tedas AG
+0d4d Coherent, Inc.
+0d4e Agere Systems Netherland BV
+ 047a WLAN Card
+ 1000 Wireless Card Model 0801
+ 1001 Wireless Card Model 0802
+0d4f EADS Airbus France
+0d50 Cleware GmbH
+ 0011 USB-Temp2 Thermometer
+ 0040 F4 foot switch
+0d51 Volex (Asia) Pte., Ltd
+0d53 HMI Co., Ltd
+0d54 Holon Corp.
+0d55 ASKA Technologies, Inc.
+0d56 AVLAB Technology, Inc.
+0d57 Solomon Microtech, Ltd
+0d5c SMC Networks, Inc.
+ a001 SMC2662W (v1) EZ Connect 802.11b Wireless Adapter [Atmel AT76C503A]
+ a002 SMC2662W v2 / SMC2662W-AR / Belkin F5D6050 [Atmel at76c503a]
+0d5e Myacom, Ltd
+ 2346 BT Digital Access adapter
+0d5f CSI, Inc.
+0d60 IVL Technologies, Ltd
+0d61 Meilu Electronics (Shenzhen) Co., Ltd
+0d62 Darfon Electronics Corp.
+ 0003 Smartcard Reader
+ 0004 Keyboard
+ 001b Keyboard
+ 001c Benq X120 Internet Keyboard Pro
+ 0306 M530 Mouse
+ 0800 Magic Wheel
+ 2021 AM805 Keyboard
+ 2026 TECOM Bluetooth Device
+ 2050 Mouse
+ 2106 Dell L20U Multimedia Keyboard
+ a100 Optical Mouse
+0d63 Fritz Gegauf AG
+0d64 DXG Technology Corp.
+ 0105 Dual Mode Digital Camera 1.3M
+ 0107 Horus MT-409 Camera
+ 0108 Dual Mode Digital Camera
+ 0202 Dual Mode Video Camera Device
+ 0303 DXG-305V Camera
+ 1001 SiPix Stylecam/UMAX AstraPix 320s
+ 1002 Fashion Cam 01 Dual-Mode DSC (Video Camera)
+ 1003 Fashion Cam Dual-Mode DSC (Controller)
+ 1021 D-Link DSC 350F
+ 1208 Dual Mode Still Camera Device
+ 2208 Mass Storage
+ 3105 Dual Mode Digital Camera Disk
+ 3108 Digicam Mass Storage Device
+0d65 KMJP Co., Ltd
+0d66 TMT
+0d67 Advanet, Inc.
+0d68 Super Link Electronics Co., Ltd
+0d69 NSI
+0d6a Megapower International Corp.
+0d6b And-Or Logic
+0d70 Try Computer Co., Ltd
+0d71 Hirakawa Hewtech Corp.
+0d72 Winmate Communication, Inc.
+0d73 Hit's Communications, Inc.
+0d76 MFP Korea, Inc.
+0d77 Power Sentry/Newpoint
+0d78 Japan Distributor Corp.
+0d7a MARX Datentechnik GmbH
+ 0001 CrypToken
+0d7b Wellco Technology Co., Ltd
+0d7c Taiwan Line Tek Electronic Co., Ltd
+0d7d Phison Electronics Corp.
+ 0100 PS1001/1011/1006/1026 Flash Disk
+ 0110 Gigabyte FlexDrive
+ 0120 Disk Pro 64MB
+ 0124 GIGABYTE Disk
+ 0240 I/O-Magic/Transcend 6-in-1 Card Reader
+ 110e NEC uPD720121/130 USB-ATA/ATAPI Bridge
+ 1240 Apacer 6-in-1 Card Reader 2.0
+ 1270 Wolverine SixPac 6000
+ 1300 Flash Disk
+ 1320 PS2031 Flash Disk
+ 1400 Attache 256MB USB 2.0 Flash Drive
+ 1420 PS2044 Pen Drive
+ 1470 Vosonic X's-Drive II+ VP2160
+ 1620 USB Disk Pro
+ 1900 USB Thumb Drive
+0d7e American Computer & Digital Components
+ 2507 Hi-Speed USB-to-IDE Bridge Controller
+ 2517 Hi-Speed Mass Storage Device
+ 25c7 Hi-Speed USB-to-IDE Bridge Controller
+0d7f Essential Reality LLC
+ 0100 P5 Glove glove controller
+0d80 H.R. Silvine Electronics, Inc.
+0d81 TechnoVision
+0d83 Think Outside, Inc.
+0d87 Dolby Laboratories Inc.
+0d89 Oz Software
+0d8a King Jim Co., Ltd
+ 0101 TEPRA PRO
+0d8b Ascom Telecommunications, Ltd
+0d8c C-Media Electronics, Inc.
+ 0001 Audio Device
+ 0002 Composite Device
+ 0003 Sound Device
+ 0006 Storm HP-USB500 5.1 Headset
+ 000c Audio Adapter
+ 000d Composite Device
+ 000e Audio Adapter (Planet UP-100, Genius G-Talk)
+ 001f CM108 Audio Controller
+ 0102 CM106 Like Sound Device
+ 0103 CM102-A+/102S+ Audio Controller
+ 0104 CM103+ Audio Controller
+ 0105 CM108 Audio Controller
+ 0107 CM108 Audio Controller
+ 010f CM108 Audio Controller
+ 0115 CM108 Audio Controller
+ 0139 Multimedia Headset [Gigaware by Ignition L.P.]
+ 013c CM108 Audio Controller
+ 0201 CM6501
+ 5000 Mass Storage Controller
+ 5200 Mass Storage Controller(0D8C,5200)
+ b213 USB Phone CM109 (aka CT2000,VPT1000)
+0d8d Promotion & Display Technology, Ltd
+ 0234 V-234 Composite Device
+ 0550 V-550 Composite Device
+ 0551 V-551 Composite Device
+ 0552 V-552 Composite Device
+ 0651 V-651 Composite Device
+ 0652 V-652 Composite Device
+ 0653 V-653 Composite Device
+ 0654 V-654 Composite Device
+ 0655 V-655 Composite Device
+ 0656 V-656 Composite Device
+ 0657 V-657 Composite Device
+ 0658 V-658 Composite Device
+ 0659 V-659 Composite Device
+ 0660 V-660 Composite Device
+ 0661 V-661 Composite Device
+ 0662 V-662 Composite Device
+ 0850 V-850 Composite Device
+ 0851 V-851 Composite Device
+ 0852 V-852 Composite Device
+ 0901 V-901 Composite Device
+ 0902 V-902 Composite Device
+ 0903 V-903 Composite Device
+ 4754 Voyager DMP Composite Device
+ bb00 Bloomberg Composite Device
+ bb01 Bloomberg Composite Device
+ bb02 Bloomberg Composite Device
+ bb03 Bloomberg Composite Device
+ bb04 Bloomberg Composite Device
+ bb05 Bloomberg Composite Device
+ fffe Global Tuner Composite Device
+ ffff Voyager DMP Composite Device
+0d8e Global Sun Technology, Inc.
+ 0163 802.11g 54 Mbps Wireless Dongle
+ 1621 802.11b Wireless Adapter
+ 3762 Cohiba 802.11g Wireless Mini adapter [Intersil ISL3887]
+ 3763 802.11g Wireless dongle
+ 7100 802.11b Adapter
+ 7110 WL-210 / WU210P 802.11b Wireless Adapter [Atmel AT76C503A]
+ 7605 TRENDnet TEW-224UB 802.11b Wireless Adapter [Atmel AT76C503A]
+ 7801 AR5523
+ 7802 AR5523 (no firmware)
+ 7811 AR5523
+ 7812 AR5523 (no firmware)
+ 7a01 PRISM25 802.11b Adapter
+0d8f Pitney Bowes
+0d90 Sure-Fire Electrical Corp.
+0d96 Skanhex Technology, Inc.
+ 0000 Jenoptik JD350 video
+ 3300 SX330z Camera
+ 4100 SX410z Camera
+ 4102 MD 9700 Camera
+ 4104 Jenoptik JD-4100z3s
+ 410a Medion 9801/Novatech SX-410z
+ 5200 SX-520z Camera
+0d97 Santa Barbara Instrument Group
+ 0001 SBIG Astronomy Camera (without firmware)
+ 0101 SBIG Astronomy Camera (with firmware)
+0d98 Mars Semiconductor Corp.
+ 0300 Avaya Wireless Card
+ 1007 Discovery Kids Digital Camera
+0d99 Trazer Technologies, Inc.
+0d9a RTX AS
+ 0001 Bluetooth Device
+0d9b Tat Shing Electrical Co.
+0d9c Chee Chen Hi-Technology Co., Ltd
+0d9d Sanwa Supply, Inc.
+0d9e Avaya
+ 0300 Wireless Card
+0d9f Powercom Co., Ltd
+ 0001 Uninterruptible Power Supply
+ 0002 Black Knight PRO / WOW Uninterruptible Power Supply (Cypress HID->COM RS232)
+ 00a2 Imperial Uninterruptible Power Supply (HID PDC)
+ 00a3 Smart King PRO Uninterruptible Power Supply (HID PDC)
+ 00a4 WOW Uninterruptible Power Supply (HID PDC)
+ 00a5 Vanguard Uninterruptible Power Supply (HID PDC)
+ 00a6 Black Knight PRO Uninterruptible Power Supply (HID PDC)
+0da0 Danger Research
+0da1 Suzhou Peter's Precise Industrial Co., Ltd
+0da2 Land Instruments International, Ltd
+0da3 Nippon Electro-Sensory Devices Corp.
+0da4 Polar Electro Oy
+ 0001 Interface
+ 0008 Loop
+0da7 IOGear, Inc.
+0da8 softDSP Co., Ltd
+ 0001 SDS 200A Oscilloscope
+0dab Cubig Group
+ 0100 DVR/CVR-M140 MP3 Player
+0dad Westover Scientific
+0db0 Micro Star International
+ 1020 PC2PC WLAN Card
+ 1967 Bluetooth Dongle
+ 3713 Primo 73
+ 3801 Motorola Bluetooth 2.1+EDR Device
+ 4011 Medion Flash XL V2.0 Card Reader
+ 4023 Lexar Mobile Card Reader
+ 4600 802.11b/g Turbo Wireless Adapter
+ 5501 Mass Storage Device
+ 5502 Mass Storage Device
+ 5513 MP3 Player
+ 5515 MP3 Player
+ 5516 MP3 Player
+ 5580 Mega Sky 580 DVB-T Tuner [M902x]
+ 5581 Mega Sky 580 DVB-T Tuner [GL861]
+ 6823 UB11B/MS-6823 802.11b Wi-Fi adapter
+ 6826 IEEE 802.11g Wireless Network Adapter
+ 6855 Bluetooth Device
+ 6861 MSI-6861 802.11g WiFi adapter
+ 6865 RT2570
+ 6869 RT2570
+ 6874 RT2573
+ 6877 RT2573
+ 6881 Bluetooth Class I EDR Device
+ 688a Bluetooth Class I EDR Device
+ 6899 802.11bgn 1T1R Mini Card Wireless Adapter
+ 6970 MS-6970 BToes Bluetooth adapter
+ 697a Bluetooth Dongle
+ 6982 Medion Flash XL Card Reader
+ a861 RT2573
+ a874 RT2573
+ a970 Bluetooth dongle
+ a97a Bluetooth EDR Device
+ b970 Bluetooth EDR Device
+ b97a Bluetooth EDR Device
+0db1 Wen Te Electronics Co., Ltd
+0db2 Shian Hwi Plug Parts, Plastic Factory
+0db3 Tekram Technology Co., Ltd
+0db4 Chung Fu Chen Yeh Enterprise Corp.
+0db5 Access IS
+ 0139 Barcode Module - CDC serial
+ 013a Barcode Module - Virtual Keyboard
+ 013b Barcode Module - HID
+ 0160 NFC and Smartcard Module (NSM)
+0db7 ELCON Systemtechnik
+ 0002 Goldpfeil P-LAN
+0dba Digidesign
+ 1000 Mbox 1 [Mbox]
+ 3000 Mbox 2
+ b011 Eleven Rack
+0dbc A&D Medical
+ 0003 AND Serial Cable [AND Smart Cable]
+0dbe Jiuh Shiuh Precision Industry Co., Ltd
+0dbf Jess-Link International
+ 0002 SmartDongle Security Key
+ 0200 HDD Storage Solution
+ 021b USB-2.0 IDE Adapter
+ 0300 Storage Adapter
+ 0333 Storage Adapter
+ 0502 FSC Storagebird XL hard disk
+ 0707 ZIV Drive
+0dc0 G7 Solutions (formerly Great Notions)
+0dc1 Tamagawa Seiki Co., Ltd
+0dc3 Athena Smartcard Solutions, Inc.
+ 0801 ASEDrive III
+ 0802 ASEDrive IIIe
+ 1104 ASEDrive IIIe KB
+ 1701 ASEKey
+ 1702 ASEKey
+0dc4 inXtron, Inc.
+ 0040 Mass Storage Device
+ 0041 Mass Storage Device
+ 0042 Mass Storage Device
+ 0101 Hi-Speed Mass Storage Device
+ 0209 SK-3500 S2
+ 020a Oyen Digital MiniPro 2.5" hard drive enclosure
+0dc5 SDK Co., Ltd
+0dc6 Precision Squared Technology Corp.
+ 2301 Wireless Touchpad Keyboard
+0dc7 First Cable Line, Inc.
+0dcd NetworkFab Corp.
+ 0001 Remote Interface Adapter
+ 0002 High Bandwidth Codec
+0dd0 Access Solutions
+ 1002 Triple Talk Speech Synthesizer
+0dd1 Contek Electronics Co., Ltd
+0dd2 Power Quotient International Co., Ltd
+ 0003 Mass Storage (P)
+0dd3 MediaQ
+0dd4 Custom Engineering SPA
+0dd5 California Micro Devices
+0dd7 Kocom Co., Ltd
+0dd8 Netac Technology Co., Ltd
+ 1060 USB-CF-Card
+ e007 OnlyDisk U222 Pendrive
+ f607 OnlyDisk U208 1G flash drive [U-SAFE]
+0dd9 HighSpeed Surfing
+0dda Integrated Circuit Solution, Inc.
+ 0001 Multi-Card Reader 6in1
+ 0002 Multi-Card Reader 7in1
+ 0003 Flash Disk
+ 0005 Internal Multi-Card Reader 6in1
+ 0008 SD single card reader
+ 0009 MS single card reader
+ 000a MS+SD Dual Card Reader
+ 000b SM single card reader
+ 0101 All-In-One Card Reader
+ 0102 All-In-One Card Reader
+ 0301 MP3 Player
+ 0302 Multi-Card MP3 Player
+ 1001 Multi-Flash Disk
+ 2001 Multi-Card Reader
+ 2002 Q018 default PID
+ 2003 Multi-Card Reader
+ 2005 Datalux DLX-1611 16in1 Card Reader
+ 2006 All-In-One Card Reader
+ 2007 USB to ATAPI bridge
+ 2008 All-In-One Card Reader
+ 2013 SD/MS Combo Card Reader
+ 2014 SD/MS Single Card Reader
+ 2023 card reader SD/MS DEMO board with ICSI brand name (MaskROM version)
+ 2024 card reader SD/MS DEMO board with Generic brand name (MaskROM version)
+ 2026 USB2.0 Card Reader
+ 2027 USB 2.0 Card Reader
+ 2315 UFD MP3 player (model 2)
+ 2318 UFD MP3 player (model 1)
+ 2321 UFD MP3 player
+0ddb Tamarack, Inc.
+0ddd Datelink Technology Co., Ltd
+0dde Ubicom, Inc.
+0de0 BD Consumer Healthcare
+0de7 USBmicro
+ 0191 U401 Interface card
+ 01a5 U421 interface card
+ 01c3 U451 relay interface card
+0dea UTECH Electronic (D.G.) Co., Ltd.
+0ded Novasonics
+0dee Lifetime Memory Products
+ 4010 Storage Adapter
+0def Full Rise Electronic Co., Ltd
+0df4 NET&SYS
+ 0201 MNG-2005
+0df6 Sitecom Europe B.V.
+ 0001 C-Media VOIP Device
+ 0004 Bluetooth 2.0 Adapter 100m
+ 0007 Bluetooth 2.0 Adapter 10m
+ 000b Bluetooth 2.0 Adapter DFU
+ 000d WL-168 Wireless Network Adapter 54g
+ 0017 WL-182 Wireless-N Network USB Card
+ 0019 Bluetooth 2.0 adapter 10m CN-512v2 001
+ 001a Bluetooth 2.0 adapter 100m CN-521v2 001
+ 002b WL-188 Wireless Network 300N USB Adapter
+ 002c WL-301 Wireless Network 300N USB Adapter
+ 002d WL-302 Wireless Network 300N USB dongle
+ 0036 WL-603 Wireless Adapter
+ 0039 WL-315 Wireless-N USB Adapter
+ 003b WL-321 Wireless USB Gaming Adapter 300N
+ 003c WL-323 Wireless-N USB Adapter
+ 003d WL-324 Wireless USB Adapter 300N
+ 003e WL-343 Wireless USB Adapter 150N X1
+ 003f WL-608 Wireless USB Adapter 54g
+ 0040 WL-344 Wireless Adapter 300N X2 [Ralink RT3071]
+ 0041 WL-329 Wireless Dualband USB adapter 300N
+ 0042 WL-345 Wireless USB adapter 300N X3
+ 0045 WL-353 Wireless USB Adapter 150N Nano
+ 0047 WL-352v1 Wireless USB Adapter 300N 002
+ 0048 WL-349v1 Wireless Adapter 150N 002 [Ralink RT3070]
+ 0049 WL-356 Wireless Adapter 300N
+ 004a WL-358v1 Wireless Micro USB Adapter 300N X3 002
+ 004b WL-349v3 Wireless Micro Adapter 150N X1 [Realtek RTL8192SU]
+ 004c WL-352 802.11n Adapter [Realtek RTL8191SU]
+ 0050 WL-349v4 Wireless Micro Adapter 150N X1 [Ralink RT3370]
+ 0056 LN-031 10/100/1000 Ethernet Adapter
+ 005d WLA-2000 v1.001 WLAN [RTL8191SU]
+ 0060 WLA-4000 802.11bgn [Ralink RT3072]
+ 0062 WLA-5000 802.11abgn [Ralink RT3572]
+ 006f WLA-5100
+ 0072 AX88179 Gigabit Ethernet [Sitecom]
+ 061c LN-028 Network USB 2.0 Adapter
+ 214a IDE/SATA Combo Adapter [CN-330]
+ 21f4 44 St Bluetooth Device
+ 2200 Sitecom bluetooth2.0 class 2 dongle CN-512
+ 2208 Sitecom bluetooth2.0 class 2 dongle CN-520
+ 2209 Sitecom bluetooth2.0 class 1 dongle CN-521
+ 3068 DC-104v2 ISDN Adapter [HFC-S]
+ 9071 WL-113 rev 1 Wireless Network USB Adapter
+ 9075 WL-117 Hi-Speed USB Adapter
+ 90ac WL-172 Wireless Network USB Adapter 54g Turbo
+ 9712 WL-113 rev 2 Wireless Network USB Adapter
+0df7 Mobile Action Technology, Inc.
+ 0620 MA-620 Infrared Adapter
+ 0700 MA-700 Bluetooth Adapter
+ 0720 MA-720 Bluetooth Adapter
+ 0722 Bluetooth Dongle
+ 0730 MA-730/MA-730G Bluetooth Adapter
+ 0800 Data Cable
+ 0820 Data Cable
+ 0900 MA i-gotU Travel Logger GPS
+ 1800 Generic Card Reader
+ 1802 Card Reader
+0dfa Toyo Communication Equipment Co., Ltd
+0dfc GeneralTouch Technology Co., Ltd
+ 0001 Touchscreen
+ 0101 5-point Touch Screen
+0e03 Nippon Systemware Co., Ltd
+0e08 Winbest Technology Co., Ltd
+0e0b Amigo Technology Inc.
+ 9031 802.11n Wireless USB Card
+ 9041 802.11n Wireless USB Card
+0e0c Gesytec
+ 0101 LonUSB LonTalk Network Adapter
+0e0d PicoQuant GmbH
+ 0003 PicoHarp 300
+0e0f VMware, Inc.
+ 0001 Device
+ 0002 Virtual USB Hub
+ 0003 Virtual Mouse
+ 0004 Virtual CCID
+ 0005 Virtual Mass Storage
+ 0006 Virtual Keyboard
+ f80a Smoker FX2
+0e16 JMTek, LLC
+0e17 Walex Electronic, Ltd
+0e1a Unisys
+0e1b Crewave
+0e20 Pegasus Technologies Ltd.
+ 0101 NoteTaker
+ 0200 Seiko Instruments InkLink Handwriting System
+0e21 Cowon Systems, Inc.
+ 0300 iAudio CW200
+ 0400 MP3 Player
+ 0500 iAudio M3
+ 0510 iAudio X5, subpack USB port
+ 0513 iAudio X5, side USB port
+ 0520 iAudio M5, side USB port
+ 0601 iAudio G3
+ 0681 iAUDIO E2
+ 0700 iAudio U3
+ 0751 iAudio 7
+ 0760 iAUDIO U5 / iAUDIO G2
+ 0800 Cowon D2 (UMS mode)
+ 0801 Cowon D2 (MTP mode)
+ 0910 iAUDIO 9
+ 0920 J3
+0e22 Symbian Ltd.
+0e23 Liou Yuane Enterprise Co., Ltd
+0e25 VinChip Systems, Inc.
+0e26 J-Phone East Co., Ltd
+0e30 HeartMath LLC
+0e34 Micro Computer Control Corp.
+0e35 3Pea Technologies, Inc.
+0e36 TiePie engineering
+ 0009 Handyscope HS3
+ 000b Handyscope HS4
+ 000f Handyscope HS4-DIFF (br)
+ 0010 Handyscope HS2
+ 0011 TiePieSCOPE HS805 (br)
+ 0012 TiePieSCOPE HS805
+ 0013 Handyprobe HP3
+ 0014 Handyprobe HP3
+ 0018 Handyprobe HP2
+ 001b Handyscope HS5
+ 0042 TiePieSCOPE HS801
+ 00fd USB To Parallel adapter
+ 00fe USB To Parallel adapter
+0e38 Stratitec, Inc.
+0e39 Smart Modular Technologies, Inc.
+ 0137 Bluetooth Device
+0e3a Neostar Technology Co., Ltd
+ 1100 CW-1100 Wireless Network Adapter
+0e3b Mansella, Ltd
+0e41 Line6, Inc.
+ 4147 TonePort GX
+ 414d Pod HD500
+ 4156 POD HD Desktop
+ 4250 BassPODxt
+ 4252 BassPODxt Pro
+ 4642 BassPODxt Live
+ 4650 PODxt Live
+ 4750 GuitarPort
+ 5044 PODxt
+ 5050 PODxt Pro
+ 534d SeaMonkey
+0e44 Sun-Riseful Technology Co., Ltd.
+0e48 Julia Corp., Ltd
+ 0100 CardPro SmartCard Reader
+0e4a Shenzhen Bao Hing Electric Wire & Cable Mfr. Co.
+0e4c Radica Games, Ltd
+ 1097 Gamester Controller
+ 2390 Games Jtech Controller
+ 7288 funkey reader
+0e50 TechnoData Interware
+ 0002 Matrixlock Dongle (HID)
+0e55 Speed Dragon Multimedia, Ltd
+ 110a Tanic S110-SG1 + ISSC IS1002N [Slow Infra-Red (SIR) & Bluetooth 1.2 (Class 2) Adapter]
+ 110b MS3303H USB-to-Serial Bridge
+0e56 Kingston Technology Company, Inc.
+ 6021 K-PEX 100
+0e5a Active Co., Ltd
+0e5b Union Power Information Industrial Co., Ltd
+0e5c Bitland Information Technology Co., Ltd
+ 6118 LCD Device
+ 6119 remote receive and control device
+ 6441 C-Media Sound Device
+0e5d Neltron Industrial Co., Ltd
+0e5e Conwise Technology Co., Ltd.
+ 6622 CW6622
+0e66 Hawking Technologies
+ 0001 HWUN1 Hi-Gain Wireless-300N Adapter w/ Upgradable Antenna [Ralink RT2870]
+ 0003 HWDN1 Hi-Gain Wireless-300N Dish Adapter [Ralink RT2870]
+ 0009 HWUN2 Hi-Gain Wireless-150N Adapter w/ Upgradable Antenna [Ralink RT2770]
+ 000b HWDN2 Hi-Gain Wireless-150N Dish Adapter [Ralink RT2770]
+ 0013 HWUN3 Hi-Gain Wireless-N Adapter [Ralink RT3070]
+ 0015 HWDN2 Rev. E Hi-Gain Wireless-150N Dish Adapter [Realtek RTL8191SU]
+ 0017 HAWNU1 Hi-Gain Wireless-150N Network Adapter with Range Amplifier [Ralink RT3070]
+ 0018 Wireless-N Network Adapter [Ralink RT2870]
+ 400b UF100 10/100 Network Adapter
+ 400c UF100 Ethernet [pegasus2]
+0e67 Fossil, Inc.
+ 0002 Wrist PDA
+0e6a Megawin Technology Co., Ltd
+ 0101 MA100 [USB-UART Bridge IC]
+ 030b Truly Ergonomic Computer Keyboard (Device Firmware Update mode)
+ 030c Truly Ergonomic Computer Keyboard
+ 6001 GEMBIRD Flexible keyboard KB-109F-B-DE
+0e6f Logic3
+ 0003 Freebird wireless Controller
+ 0005 Eclipse wireless Controller
+ 0006 Edge wireless Controller
+ 0128 Wireless PS3 Controller
+0e70 Tokyo Electronic Industry Co., Ltd
+0e72 Hsi-Chin Electronics Co., Ltd
+0e75 TVS Electronics, Ltd
+0e79 Archos, Inc.
+ 1106 Pocket Media Assistant - PMA400
+ 1204 Gmini XS 200
+ 1306 504 Portable Multimedia Player
+ 1330 5 Tablet
+ 1332 5 IMT
+ 1416 32 IT
+ 1417 A43 IT
+ 14ad 97 Titanium HD
+ 150e 80 G9
+ 3001 40 Titanium
+0e7b On-Tech Industry Co., Ltd
+0e7e Gmate, Inc.
+ 0001 Yopy 3000 PDA
+ 1001 YP3X00 PDA
+0e82 Ching Tai Electric Wire & Cable Co., Ltd
+0e83 Shin An Wire & Cable Co.
+0e8c Well Force Electronic Co., Ltd
+0e8d MediaTek Inc.
+ 0003 MT6227 phone
+ 0004 MT6227 phone
+ 0023 S103
+ 00a5 GSM modem [Medion Surfstick Model:S4222]
+ 1806 Samsung SE-208 Slim Portable DVD Writer
+ 1836 Samsung SE-S084 Super WriteMaster Slim External DVD writer
+ 1956 Samsung SE-506 Portable BluRay Disc Writer
+ 2000 MT65xx Preloader
+ 3329 Qstarz BT-Q1000XT
+ 763e MT7630e Bluetooth Adapter
+0e8f GreenAsia Inc.
+ 0003 MaxFire Blaze2
+ 0012 Joystick/Gamepad
+ 0016 4 port USB 1.1 hub UH-174
+ 0020 USB to PS/2 Adapter
+ 0021 Multimedia Keyboard Controller
+ 0022 multimedia keyboard controller
+ 0201 SmartJoy Frag Xpad/PS2 adaptor
+0e90 WiebeTech, LLC
+ 0100 Storage Adapter V1
+0e91 VTech Engineering Canada, Ltd
+0e92 C's Glory Enterprise Co., Ltd
+0e93 eM Technics Co., Ltd
+0e95 Future Technology Co., Ltd
+0e96 Aplux Communications, Ltd
+ c001 TRUST 380 USB2 SPACEC@M
+0e97 Fingerworks, Inc.
+ 0908 Composite HID (Keyboard and Mouse)
+0e98 Advanced Analogic Technologies, Inc.
+0e99 Parallel Dice Co., Ltd
+0e9a TA HSING Industries, Ltd
+0e9b ADTEC Corp.
+0e9c Streamzap, Inc.
+ 0000 Streamzap Remote Control
+0e9f Tamura Corp.
+0ea0 Ours Technology, Inc.
+ 2126 7-in-1 Card Reader
+ 2153 SD Card Reader Key
+ 2168 Transcend JetFlash 2.0 / Astone USB Drive / Intellegent Stick 2.0
+ 6803 OTI-6803 Flash Disk
+ 6808 OTI-6808 Flash Disk
+ 6828 OTI-6828 Flash Disk
+ 6858 OTi-6858 serial adapter
+0ea6 Nihon Computer Co., Ltd
+0ea7 MSL Enterprises Corp.
+0ea8 CenDyne, Inc.
+0ead Humax Co., Ltd
+0eb0 NovaTech
+ 9020 NovaTech NV-902W
+ 9021 RT2573
+0eb1 WIS Technologies, Inc.
+ 6666 WinFast WalkieTV TV Loader
+ 6668 WinFast WalkieTV TV Loader
+ 7007 WinFast WalkieTV WDM Capture
+0eb2 Y-S Electronic Co., Ltd
+0eb3 Saint Technology Corp.
+0eb7 Endor AG
+0eb8 Mettler Toledo
+ 2200 Ariva Scale
+ f000 PS60 Scale
+0ebb Thermo Fisher Scientific
+ 0002 FT-IR Spectrometer
+0ebe VWeb Corp.
+0ebf Omega Technology of Taiwan, Inc.
+0ec0 LHI Technology (China) Co., Ltd
+0ec1 Abit Computer Corp.
+0ec2 Sweetray Industrial, Ltd
+0ec3 Axell Co., Ltd
+0ec4 Ballracing Developments, Ltd
+0ec5 GT Information System Co., Ltd
+0ec6 InnoVISION Multimedia, Ltd
+0ec7 Theta Link Corp.
+ 1008 So., Show 301 Digital Camera
+0ecd Lite-On IT Corp.
+ 1400 CD\RW 40X
+ a100 LDW-411SX DVD/CD Rewritable Drive
+0ece TaiSol Electronics Co., Ltd
+0ecf Phogenix Imaging, LLC
+0ed1 WinMaxGroup
+ 6660 Flash Disk 64M-C
+ 6680 Flash Disk 64M-B
+ 7634 MP3 Player
+0ed2 Kyoto Micro Computer Co., Ltd
+0ed3 Wing-Tech Enterprise Co., Ltd
+0ed5 Fiberbyte
+ e000 USB-inSync Device
+ f000 Fiberbyte USB-inSync Device
+ f201 Fiberbyte USB-inSync DAQ-2500X
+0eda Noriake Itron Corp.
+0edf e-MDT Co., Ltd
+ 2060 FID irock! 100 Series
+0ee0 Shima Seiki Mfg., Ltd
+0ee1 Sarotech Co., Ltd
+0ee2 AMI Semiconductor, Inc.
+0ee3 ComTrue Technology Corp.
+ 1000 Image Tank 1.5
+0ee4 Sunrich Technology, Ltd
+ 0690 SATA 3 Adapter
+0eee Digital Stream Technology, Inc.
+ 8810 Mass Storage Drive
+0eef D-WAV Scientific Co., Ltd
+ 0001 eGalax TouchScreen
+ 0002 Touchscreen Controller(Professional)
+ 7200 Touchscreen Controller
+ a802 eGalaxTouch EXC7920
+0ef0 Hitachi Cable, Ltd
+0ef1 Aichi Micro Intelligent Corp.
+0ef2 I/O Magic Corp.
+0ef3 Lynn Products, Inc.
+0ef4 DSI Datotech
+0ef5 PointChips
+ 2202 Flash Disk
+ 2366 Flash Disk
+0ef6 Yield Microelectronics Corp.
+0ef7 SM Tech Co., Ltd (Tulip)
+0efd Oasis Semiconductor
+0efe Wem Technology, Inc.
+0f03 Unitek UPS Systems
+ 0001 Alpha 1200Sx
+0f06 Visual Frontier Enterprise Co., Ltd
+0f08 CSL Wire & Plug (Shen Zhen) Co.
+0f0c CAS Corp.
+0f0d Hori Co., Ltd
+ 0011 Real Arcade Pro 3
+0f0e Energy Full Corp.
+0f0f Silego Technology Inc
+ 0006 GreenPak Universal Dev Board (Active Mode)
+ 8006 GreenPak Universal Dev Board (Reset Mode)
+0f11 LD Didactic GmbH
+ 1000 CASSY-S
+ 1010 Pocket-CASSY
+ 1020 Mobile-CASSY
+ 1080 Joule and Wattmeter
+ 1081 Digital Multimeter P
+ 1090 UMI P
+ 1100 X-Ray Apparatus
+ 1101 X-Ray Apparatus
+ 1200 VideoCom
+ 2000 COM3LAB
+ 2010 Terminal Adapter
+ 2020 Network Analyser
+ 2030 Converter Control Unit
+ 2040 Machine Test System
+0f12 Mars Engineering Corp.
+0f13 Acetek Technology Co., Ltd
+0f14 Ingenico
+ 0012 Vital'Act 3S
+ 0038 XIRING Smart Card Terminal LEO V2
+0f18 Finger Lakes Instrumentation
+ 0002 CCD
+ 0006 Focuser
+ 0007 Filter Wheel
+ 000a ProLine CCD
+ 000b Color Filter Wheel 4
+ 000c PDF2
+ 000d Guider
+0f19 Oracom Co., Ltd
+0f1b Onset Computer Corp.
+0f1c Funai Electric Co., Ltd
+0f1d Iwill Corp.
+0f21 IOI Technology Corp.
+0f22 Senior Industries, Inc.
+0f23 Leader Tech Manufacturer Co., Ltd
+0f24 Flex-P Industries, Snd., Bhd.
+0f2d ViPower, Inc.
+0f2e Geniality Maple Technology Co., Ltd
+0f2f Priva Design Services
+0f30 Jess Technology Co., Ltd
+ 001c PS3 Guitar Controller Dongle
+ 0110 Dual Analog Rumble Pad
+ 0111 Colour Rumble Pad
+ 0208 Xbox & PC Gamepad
+0f31 Chrysalis Development
+0f32 YFC-BonEagle Electric Co., Ltd
+0f37 Kokuyo Co., Ltd
+0f38 Nien-Yi Industrial Corp.
+0f39 TG3 Electronics
+ 0876 Keyboard [87 Francium Pro]
+ 1086 DK2108SZ Keyboard [Ducky Zero]
+0f3d Airprime, Incorporated
+ 0112 CDMA 1xEVDO PC Card, PC 5220
+0f41 RDC Semiconductor Co., Ltd
+0f42 Nital Consulting Services, Inc.
+0f44 Polhemus
+ ef11 Patriot (firmware not loaded)
+ ef12 Patriot
+ ff11 Liberty (firmware not loaded)
+ ff12 Liberty
+0f4b St. John Technology Co., Ltd
+0f4c WorldWide Cable Opto Corp.
+0f4d Microtune, Inc.
+ 1000 Bluetooth Dongle
+0f4e Freedom Scientific
+0f52 Wing Key Electrical Co., Ltd
+0f53 Dongguan White Horse Cable Factory, Ltd
+0f54 Kawai Musical Instruments Mfg. Co., Ltd
+ 0101 MP6 Stage Piano
+0f55 AmbiCom, Inc.
+0f5c Prairiecomm, Inc.
+0f5d NewAge International, LLC
+ 9455 Compact Drive
+0f5f Key Technology Corp.
+0f60 NTK, Ltd
+0f61 Varian, Inc.
+0f62 Acrox Technologies Co., Ltd
+ 1001 Targus Mini Trackball Optical Mouse
+0f63 LeapFrog Enterprises
+ 0010 Leapster Explorer
+ 0022 Leap Reader
+ 0500 Fly Fusion
+ 0600 Leap Port Turbo
+ 0700 POGO
+ 0800 Didj
+ 0900 TAGSchool
+ 0a00 Leapster 2
+ 0b00 Crammer
+ 0c00 Tag Jr
+ 0d00 My Pal Scout
+ 0e00 Tag32
+ 0f00 Tag64
+ 1000 Kiwi16
+ 1100 Leapster L2x
+ 1111 Fly Fusion
+ 1300 Didj UK/France (Leapster Advance)
+0f68 Kobe Steel, Ltd
+0f69 Dionex Corp.
+0f6a Vibren Technologies, Inc.
+0f6e INTELLIGENT SYSTEMS
+ 0100 GameBoy Color Emulator
+ 0201 GameBoy Advance Flash Gang Writer
+ 0202 GameBoy Advance Capture
+ 0300 Gamecube DOL Viewer
+ 0400 NDS Emulator
+ 0401 NDS UIC
+ 0402 NDS Writer
+ 0403 NDS Capture
+ 0404 NDS Emulator (Lite)
+0f73 DFI
+0f78 Guntermann & Drunck GmbH
+0f7c DQ Technology, Inc.
+0f7d NetBotz, Inc.
+0f7e Fluke Corp.
+0f88 VTech Holdings, Ltd
+ 3012 RT2570
+ 3014 ZD1211B
+0f8b Yazaki Corp.
+0f8c Young Generation International Corp.
+0f8d Uniwill Computer Corp.
+0f8e Kingnet Technology Co., Ltd
+0f8f Soma Networks
+0f97 CviLux Corp.
+0f98 CyberBank Corp.
+0f9c Hyun Won, Inc.
+ 0301 M-Any Premium DAH-610 MP3/WMA Player
+ 0332 mobiBLU DAH-1200 MP3/Ogg Player
+0f9e Lucent Technologies
+0fa3 Starconn Electronic Co., Ltd
+0fa4 ATL Technology
+0fa5 Sotec Co., Ltd
+0fa7 Epox Computer Co., Ltd
+0fa8 Logic Controls, Inc.
+0faf Winpoint Electronic Corp.
+0fb0 Haurtian Wire & Cable Co., Ltd
+0fb1 Inclose Design, Inc.
+0fb2 Juan-Chern Industrial Co., Ltd
+0fb6 Heber Ltd
+ 3fc3 Firefly X10i I/O Board (with firmware)
+ 3fc4 Firefly X10i I/O Board (without firmware)
+0fb8 Wistron Corp.
+ 0002 eHome Infrared Receiver
+0fb9 AACom Corp.
+0fba San Shing Electronics Co., Ltd
+0fbb Bitwise Systems, Inc.
+0fc1 Mitac Internatinal Corp.
+0fc2 Plug and Jack Industrial, Inc.
+0fc5 Delcom Engineering
+ 1222 I/O Development Board
+0fc6 Dataplus Supplies, Inc.
+0fca Research In Motion, Ltd.
+ 0001 Blackberry Handheld
+ 0004 Blackberry Handheld
+ 0006 Blackberry Pearl
+ 0008 Blackberry Pearl
+ 8001 Blackberry Handheld
+ 8004 Blackberry
+ 8007 Blackberry Handheld
+ 8010 Blackberry Playbook (Connect to Windows mode)
+ 8011 Blackberry Playbook (Connect to Mac mode)
+ 8020 Blackberry Playbook (CD-Rom mode)
+ 8037 Blackberry PRIV
+0fce Sony Ericsson Mobile Communications AB
+ 0076 W910i (Multimedia mode)
+ 00af V640i Phone [PTP Camera]
+ 00d4 C902 [MTP]
+ 00d9 C702 Phone
+ 0112 W995 Walkman Phone
+ 014e J108i Cedar (MTP mode)
+ 015a Xperia Pro [Media Transfer Protocol]
+ 0166 Xperia Mini Pro
+ 0167 ST15i (Xperia mini)
+ 0169 Xperia S
+ 0172 Xperia P
+ 0177 Xperia Ion [Mass Storage]
+ 0188 ST26i
+ 019c C6833
+ 019e C6903
+ 01a5 SO-04F
+ 01a7 D5503
+ 01ba D6603 [Xperia Z3]
+ 01bb D5803 [Xperia Z3 Compact] (MTP mode)
+ 0dde Xperia Mini Pro Bootloader
+ 1010 WMC Modem
+ 10af V640i Phone [PictBridge]
+ 10d4 C902 Phone [PictBridge]
+ 2105 W715 Phone
+ 2137 Xperia X10 mini (USB debug)
+ 2138 Xperia X10 mini pro (Debug)
+ 2149 Xperia X8 (debug)
+ 214e J108i Cedar (Windows-driver mode)
+ 3137 Xperia X10 mini
+ 3138 Xperia X10 mini pro
+ 3149 Xperia X8
+ 514f Xperia arc S [Adb-Enable Mode]
+ 5169 Xperia S [Adb-Enable Mode]
+ 5177 Xperia Ion [Debug Mode]
+ 518c C1605 [Xperia E dual] MTD mode
+ 51a7 D5503 (Xperia Z1 Compact)
+ 614f Xperia X12 (debug mode)
+ 6166 Xperia Mini Pro
+ 618c C1605 [Xperia E dual] MSC mode
+ 715a Xperia Pro [Tethering]
+ 7166 Xperia Mini Pro (Tethering mode)
+ 7177 Xperia Ion [Tethering]
+ 8004 9000 Phone [Mass Storage]
+ adde C2005 (Xperia M dual) in service mode
+ d008 V800-Vodafone 802SE Phone
+ d016 K750i Phone
+ d017 K608i Phone
+ d019 VDC EGPRS Modem
+ d025 520 WMC Data Modem
+ d028 W800i
+ d038 W850i Phone
+ d039 K800i (phone mode)
+ d041 K510i Phone
+ d042 W810i Phone
+ d043 V630i Phone
+ d046 K610i Phone
+ d065 W960i Phone (PC Suite)
+ d076 W910i (Phone mode)
+ d089 W580i Phone (mass storage)
+ d0a1 K810
+ d0af V640i Phone
+ d0cf MD300 Mobile Broadband Modem
+ d0d4 C902 Phone [Modem]
+ d0e1 MD400 Mobile Broadband Modem
+ d12a U100i Yari Phone
+ d12e Xperia X10
+ d14e J108i Cedar (modem mode)
+ e000 K810 (PictBridge mode)
+ e039 K800i (msc mode)
+ e042 W810i Phone
+ e043 V630i Phone [Mass Storage]
+ e075 K850i
+ e076 W910i (Mass storage)
+ e089 W580i Phone
+ e090 W200 Phone (Mass Storage)
+ e0a1 K810 (Mass Storage mode)
+ e0a3 W660i
+ e0af V640i Phone [Mass Storage]
+ e0d4 C902 Phone [Mass Storage]
+ e0ef C905 Phone [Mass Storage]
+ e0f3 W595
+ e105 W705
+ e112 W995 Phone (Mass Storage)
+ e12e X10i Phone
+ e133 Vivaz
+ e14e J108i Cedar (mass-storage mode)
+ e14f Xperia Arc/X12
+ e15a Xperia Pro [Mass Storage Class]
+ e161 Xperia Ray
+ e166 Xperia Mini Pro
+ e167 XPERIA mini
+ e19b C2005 [Xperia M dual] (Mass Storage)
+ e1a9 D5303
+ e1aa D2303
+ e1ad D5103
+ e1b0 D6708
+ e1b5 D2004
+ e1ba D6683
+ e1bb SO-02G
+ e1bc D2203
+ e1c0 SGP621
+ e1c2 D2533
+ e1c9 E6553
+ e1cf SGP771
+ f0fa MN800 / Smartwatch 2 (DFU mode)
+0fcf Dynastream Innovations, Inc.
+ 1003 ANT Development Board
+ 1004 ANTUSB Stick
+ 1006 ANT Development Board
+ 1008 ANTUSB2 Stick
+ 1009 ANTUSB-m Stick
+0fd0 Tulip Computers B.V.
+0fd1 Giant Electronics Ltd.
+0fd2 Seac Banche
+ 0001 RDS 6000
+0fd4 Tenovis GmbH & Co., KG
+0fd5 Direct Access Technology, Inc.
+0fd9 Elgato Systems GmbH
+ 0011 EyeTV Diversity
+ 0018 EyeTV Hybrid
+ 0020 EyeTV DTT Deluxe
+ 0021 EyeTV DTT
+ 002a EyeTV Sat
+ 002c EyeTV DTT Deluxe v2
+ 0033 Video Capture
+ 0037 Video Capture v2
+0fda Quantec Networks GmbH
+ 0100 quanton flight control
+0fdc Micro Plus
+0fde Oregon Scientific
+ ca01 WMRS200 weather station
+ ca05 CM160
+0fe0 Osterhout Design Group
+ 0100 Bluetooth Mouse
+ 0101 Bluetooth IMU
+ 0200 Bluetooth Keypad
+0fe2 Air Techniques
+0fe4 IN-Tech Electronics, Ltd
+0fe5 Greenconn (U.S.A.), Inc.
+0fe6 ICS Advent
+ 8101 DM9601 Fast Ethernet Adapter
+ 811e Parallel Adapter
+ 9700 DM9601 Fast Ethernet Adapter
+0fe9 DVICO
+ 4020 TViX M-6500
+ 9010 FusionRemote IR receiver
+ db00 FusionHDTV DVB-T (MT352+LgZ201) (uninitialized)
+ db01 FusionHDTV DVB-T (MT352+LgZ201) (initialized)
+ db10 FusionHDTV DVB-T (MT352+Thomson7579) (uninitialized)
+ db11 FusionHDTV DVB-T (MT352+Thomson7579) (initialized)
+ db78 FusionHDTV DVB-T Dual Digital 4 (ZL10353+xc2028/xc3028) (initialized)
+0fea United Computer Accessories
+0feb CRS Electronic Co., Ltd
+0fec UMC Electronics Co., Ltd
+0fed Access Co., Ltd
+0fee Xsido Corp.
+0fef MJ Research, Inc.
+0ff6 Core Valley Co., Ltd
+0ff7 CHI SHING Computer Accessories Co., Ltd
+0ffc Clavia DMI AB
+ 0021 Nord Stage 2
+0ffd EarlySense
+ ff00 OEM
+0fff Aopen, Inc.
+1000 Speed Tech Corp.
+ 153b TerraTec Electronic GmbH
+1001 Ritronics Components (S) Pte., Ltd
+1003 Sigma Corp.
+ 0003 SD14
+ 0100 SD9/SD10
+1004 LG Electronics, Inc.
+ 1fae U8120 3G Cellphone
+ 6000 Various Mobile Phones
+ 6005 T5100
+ 6018 GM360/GD510/GW520/KP501
+ 618e Ally/Optimus One/Vortex (debug mode)
+ 618f Ally/Optimus One
+ 61c5 P880 / Charge only
+ 61c6 Vortex (msc)
+ 61cc Optimus S
+ 61da G2 Android Phone [tethering mode]
+ 61f1 Optimus Android Phone [LG Software mode]
+ 61f9 Optimus (Various Models) MTP Mode
+ 61fc Optimus 3
+ 61fe Optimus Android Phone [USB tethering mode]
+ 627f G3 (VS985) Android Phone (MTP/Download mode)
+ 6300 G2/Optimus Android Phone [Charge mode]
+ 631c G2/Optimus Android Phone [MTP mode]
+ 631d Optimus Android Phone (Camera/PTP Mode)
+ 631e G2/Optimus Android Phone [Camera/PTP mode]
+ 631f Optimus Android Phone (Charge Mode)
+ 633a Ultimate 2 Android Phone L41C
+ 633e G2/G3 Android Phone [MTP/PTP/Download mode]
+ 6344 G2 Android Phone [tethering mode]
+ 6356 Optimus Android Phone [Virtual CD mode]
+ 6800 CDMA Modem
+ 7000 LG LDP-7024D(LD)USB
+ 91c8 P880 / USB tethering
+ a400 Renoir (KC910)
+1005 Apacer Technology, Inc.
+ 1001 MP3 Player
+ 1004 MP3 Player
+ 1006 MP3 Player
+ b113 Handy Steno/AH123 / Handy Steno 2.0/HT203
+ b223 CD-RW + 6in1 Card Reader Digital Storage / Converter
+1006 iRiver, Ltd.
+ 3001 iHP-100
+ 3002 iHP-120/140 MP3 Player
+ 3003 H320/H340
+ 3004 H340 (mtp)
+1009 Emuzed, Inc.
+ 000e eHome Infrared Receiver
+ 0013 Angel MPEG Device
+ 0015 Lumanate Wave PAL SECAM DVBT Device
+ 0016 Lumanate Wave NTSC/ATSC Combo Device
+100a AV Chaseway, Ltd
+ 2402 MP3 Player
+ 2404 MP3 Player
+ 2405 MP3 Player
+ 2406 MP3 Player
+ a0c0 MP3 Player
+100b Chou Chin Industrial Co., Ltd
+100d Netopia, Inc.
+ 3342 Cayman 3352 DSL Modem
+ 3382 3380 Series Network Interface
+ 6072 DSL Modem
+ 9031 Motorola 802.11n Dualband USB Wireless Adapter
+ 9032 Motorola 802.11n 5G USB Wireless Adapter
+ cb01 Cayman 3341 Ethernet DSL Router
+1010 Fukuda Denshi Co., Ltd
+1011 Mobile Media Tech.
+ 0001 AccFast Mp3
+1012 SDKM Fibres, Wires & Cables Berhad
+1013 TST-Touchless Sensor Technology AG
+1014 Densitron Technologies PLC
+1015 Softronics Pty., Ltd
+1016 Xiamen Hung's Enterprise Co., Ltd
+1017 Speedy Industrial Supplies, Pte., Ltd
+1019 Elitegroup Computer Systems (ECS)
+ 0c55 Flash Reader, Desknote UCR-61S2B
+ 0f38 Infrared Receiver
+1020 Labtec
+ 0006 Wireless Keyboard
+ 000a Wireless Optical Mouse
+ 0106 Wireless Optical Mouse
+1022 Shinko Shoji Co., Ltd
+1025 Hyper-Paltek
+ 005e USB DVB-T device
+ 005f USB DVB-T device
+ 0300 MP3 Player
+ 0350 MP3 Player
+1026 Newly Corp.
+1027 Time Domain
+1028 Inovys Corp.
+1029 Atlantic Coast Telesys
+102a Ramos Technology Co., Ltd
+102b Infotronic America, Inc.
+102c Etoms Electronics Corp.
+ 6151 Q-Cam Sangha CIF
+ 6251 Q-Cam VGA
+102d Winic Corp.
+1031 Comax Technology, Inc.
+1032 C-One Technology Corp.
+1033 Nucam Corp.
+ 0068 3,5'' HDD case MD-231
+1038 SteelSeries ApS
+ 0100 Ideazon Zboard
+ 1361 Ideazon Sensei
+1039 devolo AG
+ 0824 1866 802.11bg [Texas Instruments TNETW1450]
+ 2140 dsl+ 1100 duo
+103a PSA
+ f000 Actia Evo XS
+103d Stanton
+ 0100 ScratchAmp
+ 0101 ScratchAmp
+1043 iCreate Technologies Corp.
+ 160f Wireless Network Adapter
+ 4901 AV-836 Video Capture Device
+ 8006 Flash Disk 32-256 MB
+ 8012 Flash Disk 256 MB
+1044 Chu Yuen Enterprise Co., Ltd
+ 7001 Gigabyte U7000 DVB-T tuner
+ 7002 Gigabyte U8000 DVB-T tuner
+ 7004 Gigabyte U7100 DVB-T tuner
+ 7005 Gigabyte U7200 DVB-T tuner [AF9035]
+ 7006 Gigabyte U6000 DVB-T tuner [em2863]
+ 8001 GN-54G
+ 8002 GN-BR402W
+ 8003 GN-WLBM101
+ 8004 GN-WLBZ101 802.11b Adapter
+ 8005 GN-WLBZ201 802.11b Adapter
+ 8006 GN-WBZB-M 802.11b Adapter
+ 8007 GN-WBKG
+ 8008 GN-WB01GS
+ 800a GN-WI05GS
+ 800b GN-WB30N 802.11n WLAN Card
+ 800c GN-WB31N 802.11n USB WLAN Card
+ 800d GN-WB32L 802.11n USB WLAN Card
+1046 Winbond Electronics Corp. [hex]
+ 6694 Generic W6694 USB
+ 8901 Bluetooth Device
+ 9967 W9967CF/W9968CF Webcam IC
+1048 Targus Group International
+ 2010 4-Port hub
+104b Mylex / Buslogic
+104c AMCO TEC International, Inc.
+104d Newport Corporation
+ 1003 Model-52 LED Light Source Power Supply and Driver
+104f WB Electronics
+ 0001 Infinity Phoenix
+ 0002 Smartmouse
+ 0003 FunProgrammer
+ 0004 Infinity Unlimited
+ 0006 Infinity Smart
+ 0007 Infinity Smart module
+ 0008 Infinity CryptoKey
+ 0009 RE-BL PlayStation 3 IR-to-Bluetooth converter
+1050 Yubico.com
+ 0010 Yubikey (v1 or v2)
+ 0110 Yubikey NEO(-N) OTP
+ 0111 Yubikey NEO(-N) OTP+CCID
+ 0112 Yubikey NEO(-N) CCID
+ 0113 Yubikey NEO(-N) U2F
+ 0114 Yubikey NEO(-N) OTP+U2F
+ 0115 Yubikey NEO(-N) U2F+CCID
+ 0116 Yubikey NEO(-N) OTP+U2F+CCID
+ 0120 Yubikey Touch U2F Security Key
+ 0200 Gnubby U2F
+ 0211 Gnubby
+ 0401 Yubikey 4 OTP
+ 0402 Yubikey 4 U2F
+ 0403 Yubikey 4 OTP+U2F
+ 0404 Yubikey 4 CCID
+ 0405 Yubikey 4 OTP+CCID
+ 0406 Yubikey 4 U2F+CCID
+ 0407 Yubikey 4 OTP+U2F+CCID
+ 0410 Yubikey plus OTP+U2F
+1053 Immanuel Electronics Co., Ltd
+1054 BMS International Beheer N.V.
+ 5004 DSL 7420 Loader
+ 5005 DSL 7420 LAN Modem
+1055 Complex Micro Interconnection Co., Ltd
+1056 Hsin Chen Ent Co., Ltd
+1057 ON Semiconductor
+1058 Western Digital Technologies, Inc.
+ 0200 FireWire USB Combo
+ 0400 External HDD
+ 0500 hub
+ 0701 WD Passport (WDXMS)
+ 0702 WD Passport (WDXMS)
+ 0704 My Passport Essential (WDME)
+ 0705 My Passport Elite (WDML)
+ 070a My Passport Essential (WDBAAA), My Passport for Mac (WDBAAB), My Passport Essential SE (WDBABM), My Passport SE for Mac (WDBABW)
+ 070b My Passport Elite (WDBAAC)
+ 070c My Passport Studio (WDBAAE)
+ 071a My Passport Essential (WDBAAA)
+ 071d My Passport Studio (WDBALG)
+ 0730 My Passport Essential (WDBACY)
+ 0732 My Passport Essential SE (WDBGYS)
+ 0740 My Passport Essential (WDBACY)
+ 0741 My Passport Ultra
+ 0742 My Passport Essential SE (WDBGYS)
+ 0748 My Passport (WDBKXH, WDBY8L)
+ 07a8 My Passport (WDBBEP), My Passport for Mac (WDBLUZ)
+ 07ae My Passport Edge for Mac (WDBJBH)
+ 07ba PiDrive (WDLB)
+ 0810 My Passport Ultra (WDBZFP)
+ 0816 My Passport Air (WDBBLW)
+ 0820 My Passport Ultra (WDBMWV, WDBZFP)
+ 0822 My Passport Ultra (WDBBUZ)
+ 0824 My Passport Slim (WDBPDZ)
+ 0830 My Passport Ultra (WDBZFP)
+ 0837 My Passport Ultra (WDBBKD)
+ 0900 MyBook Essential External HDD
+ 0901 My Book Essential Edition (Green Ring) (WDG1U)
+ 0902 My Book Pro Edition (WDG1T)
+ 0903 My Book Premium Edition
+ 0905 My Book Pro Edition II (WD10000C033-001)
+ 0910 My Book Essential Edition (Green Ring) (WDG1U)
+ 1001 Elements Desktop (WDE1U)
+ 1003 WD Elements Desktop (WDE1UBK)
+ 1010 Elements Portable (WDBAAR)
+ 1021 Elements Desktop (WDBAAU)
+ 1023 Elements SE Portable (WDBABV)
+ 1042 Elements SE Portable (WDBPCK)
+ 1048 Elements Portable (WDBU6Y)
+ 1078 Elements Portable (WDBUZG)
+ 107c Elements Desktop (WDBWLG)
+ 10a2 Elements SE Portable (WDBPCK)
+ 10a8 Elements Portable (WDBUZG)
+ 10b8 Elements Portable (WDBU6Y, WDBUZG)
+ 1100 My Book Essential Edition 2.0 (WDH1U)
+ 1102 My Book Home Edition (WDH1CS)
+ 1103 My Book Studio
+ 1104 My Book Mirror Edition (WDH2U)
+ 1105 My Book Studio II
+ 1110 My Book Essential (WDBAAF), My Book for Mac (WDBAAG)
+ 1111 My Book Elite (WDBAAH)
+ 1112 My Book Studio (WDBAAJ), My Book Studio LX (WDBACH)
+ 1123 My Book 3.0 (WDBABP)
+ 1130 My Book Essential (WDBACW)
+ 1140 My Book Essential (WDBACW)
+ 1230 My Book (WDBFJK)
+ 1235 My Book (WDBFJK0040HBK)
+ 2599 My Passport Ultra (WD40NMZW)
+ 259d My Passport Ultra (WDBBKD)
+ 259f My Passport Ultra (WD10JMVW)
+1059 Giesecke & Devrient GmbH
+ 000b StarSign Bio Token 3.0
+105b Foxconn International, Inc.
+ e065 BCM43142A0 Bluetooth module
+105c Hong Ji Electric Wire & Cable (Dongguan) Co., Ltd
+105d Delkin Devices, Inc.
+105e Valence Semiconductor Design, Ltd
+105f Chin Shong Enterprise Co., Ltd
+1060 Easthome Industrial Co., Ltd
+1063 Motorola Electronics Taiwan, Ltd [hex]
+ 1555 MC141555 Hub
+ 4100 SB4100 USB Cable Modem
+1065 CCYU Technology
+ 0020 USB-DVR2 Dev Board
+ 2136 EasyDisk ED1064
+106a Loyal Legend, Ltd
+106c Curitel Communications, Inc.
+ 1101 CDMA 2000 1xRTT USB modem (HX-550C)
+ 1102 Packet Service
+ 1103 Packet Service Diagnostic Serial Port (WDM)
+ 1104 Packet Service Diagnostic Serial Port (WDM)
+ 1105 Composite Device
+ 1106 Packet Service Diagnostic Serial Port (WDM)
+ 1301 Composite Device
+ 1302 Packet Service Diagnostic Serial Port (WDM)
+ 1303 Packet Service
+ 1304 Packet Service
+ 1401 Composite Device
+ 1402 Packet Service
+ 1403 Packet Service Diagnostic Serial Port (WDM)
+ 1501 Packet Service
+ 1502 Packet Service Diagnostic Serial Port (WDM)
+ 1503 Packet Service
+ 1601 Packet Service
+ 1602 Packet Service Diagnostic Serial Port (WDM)
+ 1603 Packet Service
+ 2101 AudioVox 8900 Cell Phone
+ 2102 Packet Service
+ 2103 Packet Service Diagnostic Serial Port (WDM)
+ 2301 Packet Service
+ 2302 Packet Service Diagnostic Serial Port (WDM)
+ 2303 Packet Service
+ 2401 Packet Service Diagnostic Serial Port (WDM)
+ 2402 Packet Service
+ 2403 Packet Service Diagnostic Serial Port (WDM)
+ 2501 Packet Service
+ 2502 Packet Service Diagnostic Serial Port (WDM)
+ 2503 Packet Service
+ 2601 Packet Service
+ 2602 Packet Service Diagnostic Serial Port (WDM)
+ 2603 Packet Service
+ 3701 Broadband Wireless modem
+ 3702 Pantech PX-500
+ 3714 PANTECH USB MODEM [UM175]
+ 3716 UMW190 Modem
+ 3721 Option Beemo (GI0801) LTE surfstick
+ 3b14 Option Beemo (GI0801) LTE surfstick
+ 3eb4 Packet Service Diagnostic Serial Port (WDM)
+ 4101 Packet Service Diagnostic Serial Port (WDM)
+ 4102 Packet Service
+ 4301 Composite Device
+ 4302 Packet Service Diagnostic Serial Port (WDM)
+ 4401 Composite Device
+ 4402 Packet Service
+ 4501 Packet Service
+ 4502 Packet Service Diagnostic Serial Port (WDM)
+ 4601 Composite Device
+ 4602 Packet Service Diagnostic Serial Port (WDM)
+ 5101 Packet Service
+ 5102 Packet Service Diagnostic Serial Port (WDM)
+ 5301 Packet Service Diagnostic Serial Port (WDM)
+ 5302 Packet Service
+ 5401 Packet Service
+ 5402 Packet Service Diagnostic Serial Port (WDM)
+ 5501 Packet Service Diagnostic Serial Port (WDM)
+ 5502 Packet Service
+ 5601 Packet Service Diagnostic Serial Port (WDM)
+ 5602 Packet Service
+ 7101 Composite Device
+ 7102 Packet Service
+ a000 Packet Service
+ a001 Packet Service Diagnostic Serial Port (WDM)
+ c100 Packet Service
+ c200 Packet Service
+ c500 Packet Service Diagnostic Serial Port (WDM)
+ e200 Packet Service
+106d San Chieh Manufacturing, Ltd
+106e ConectL
+106f Money Controls
+ 0009 CT10x Coin Transaction
+ 000a CR10x Coin Recycler
+ 000c Xchange
+1076 GCT Semiconductor, Inc.
+ 0031 Bluetooth Device
+ 0032 Bluetooth Device
+ 8002 LU150 LTE Modem [Yota LU150]
+107b Gateway, Inc.
+ 3009 eHome Infrared Transceiver
+ 55b2 WBU-110 802.11b Wireless Adapter [Intersil PRISM 3]
+ 55f2 WGU-210 802.11g Adapter [Intersil ISL3886]
+107d Arlec Australia, Ltd
+107e Midoriya Electric Co., Ltd
+107f KidzMouse, Inc.
+1082 Shin-Etsukaken Co., Ltd
+1083 Canon Electronics, Inc.
+ 161b DR-2010C Scanner
+ 162c P-150 Scanner
+1084 Pantech Co., Ltd
+108a Chloride Power Protection
+108b Grand-tek Technology Co., Ltd
+ 0005 HID Keyboard/Mouse PS/2 Translator
+108c Robert Bosch GmbH
+108e Lotes Co., Ltd.
+1099 Surface Optics Corp.
+109a DATASOFT Systems GmbH
+109b Hisense
+ 9118 Medion P4013 Mobile
+109f eSOL Co., Ltd
+ 3163 Trigem Mobile SmartDisplay84
+ 3164 Trigem Mobile SmartDisplay121
+10a0 Hirotech, Inc.
+10a3 Mitsubishi Materials Corp.
+10a9 SK Teletech Co., Ltd
+ 1102 Sky Love Actually IM-U460K
+ 1104 Sky Vega IM-A650S
+ 1105 VEGA Android composite
+ 1106 VEGA Android composite
+ 1107 VEGA Android composite
+ 1108 VEGA Android composite
+ 1109 VEGA Android composite
+ 6021 SIRIUS alpha
+ 6031 Pantech Android composite
+ 6032 Pantech Android composite
+ 6033 Pantech Android composite
+ 6034 Pantech Android composite
+ 6035 Pantech Android composite
+ 6036 Pantech Android composite
+ 6037 Pantech Android composite
+ 6050 Pantech Android composite
+ 6051 Pantech Android composite
+ 6052 Pantech Android composite
+ 6053 Pantech Android composite
+ 6054 Pantech Android composite
+ 6055 Pantech Android composite
+ 6056 Pantech Android composite
+ 6057 Pantech Android composite
+ 6058 Pantech Android composite
+ 6059 Pantech Android composite
+ 6080 MHS291LVW LTE Modem [Verizon Jetpack 4G LTE Mobile Hotspot MHS291L] (Zero CD Mode)
+ 6085 MHS291LVW LTE Modem [Verizon Jetpack 4G LTE Mobile Hotspot MHS291L] (Modem Mode)
+ 7031 Pantech Android composite
+ 7032 Pantech Android composite
+ 7033 Pantech Android composite
+ 7034 Pantech Android composite
+ 7035 Pantech Android composite
+ 7036 Pantech Android composite
+ 7037 Pantech Android composite
+10aa Cables To Go
+10ab USI Co., Ltd
+ 1002 Bluetooth Device
+ 1003 BC02-EXT in DFU
+ 1005 Bluetooth Adptr
+ 1006 BC04-EXT in DFU
+ 10c5 Sony-Ericsson / Samsung DataCable
+10ac Honeywell, Inc.
+10ae Princeton Technology Corp.
+10af Liebert Corp.
+ 0000 UPS
+ 0001 PowerSure PSA UPS
+ 0002 PowerSure PST UPS
+ 0003 PowerSure PSP UPS
+ 0004 PowerSure PSI UPS
+ 0005 UPStation GXT 2U UPS
+ 0006 UPStation GXT UPS
+ 0007 Nfinity Power Systems UPS
+ 0008 PowerSure Interactive UPS
+10b5 Comodo (PLX?)
+ 9060 Test Board
+10b8 DiBcom
+ 0bb8 DiBcom USB DVB-T reference design (MOD300) (cold)
+ 0bb9 DiBcom USB DVB-T reference design (MOD300) (warm)
+ 0bc6 DiBcom USB2.0 DVB-T reference design (MOD3000P) (cold)
+ 0bc7 DiBcom USB2.0 DVB-T reference design (MOD3000P) (warm)
+10bb TM Technology, Inc.
+10bc Dinging Technology Co., Ltd
+10bd TMT Technology, Inc.
+ 1427 Ethernet
+10bf SmartHome
+ 0001 SmartHome PowerLinc
+10c3 Universal Laser Systems, Inc.
+ 00a4 ULS PLS Series Laser Engraver Firmware Loader
+ 00a5 ULS Print Support
+10c4 Cygnal Integrated Products, Inc.
+ 0002 F32x USBXpress Device
+ 0003 CommandIR
+ 8030 K4JRG Ham Radio devices
+ 8044 USB Debug Adapter
+ 804e Software Bisque Paramount ME
+ 80a9 CP210x to UART Bridge Controller
+ 80ca ATM2400 Sensor Device
+ 813f tams EasyControl
+ 8149 West Mountain Radio Computerized Battery Analyzer
+ 814a West Mountain Radio RIGblaster P&P
+ 814b West Mountain Radio RIGtalk
+ 818a Silicon Labs FM Radio Reference Design
+ 81e8 Zephyr BioHarness
+ 8460 Sangoma Wanpipe VoiceTime
+ 8461 Sangoma U100
+ 8477 Balluff RFID Reader
+ 8496 SiLabs Cypress FW downloader
+ 8497 SiLabs Cypress EVB
+ 8605 dilitronics ESoLUX solar lighting controller
+ 86bc C8051F34x AudioDelay [AD-340]
+ 8789 C8051F34x Extender & EDID MGR [EMX-DVI]
+ 87be C8051F34x HDMI Audio Extractor [EMX-HD-AUD]
+ 8863 C8051F34x Bootloader
+ 8897 C8051F38x HDMI Splitter [UHBX]
+ 8918 C8051F38x HDMI Audio Extractor [VSA-HA-DP]
+ 8973 C8051F38x HDMI Extender [UHBX-8X]
+ 89e1 C8051F38x HDMI Extender [UHBX-SW3-WP]
+ ea60 CP210x UART Bridge / myAVR mySmartUSB light
+ ea61 CP210x UART Bridge
+ ea70 CP210x UART Bridge
+ ea80 CP210x UART Bridge
+10c5 Sanei Electric, Inc.
+ 819a FM Radio
+10c6 Intec, Inc.
+10cb Eratech
+10cc GBM Connector Co., Ltd
+ 1101 MP3 Player
+10cd Kycon, Inc.
+10ce Silicon Labs
+ 000e Shinko/Sinfonia CHC-S2145
+ ea6a MobiData EDGE USB Modem
+10cf Velleman Components, Inc.
+ 2011 R-Engine MPEG2 encoder/decoder
+ 5500 8055 Experiment Interface Board (address=0)
+ 5501 8055 Experiment Interface Board (address=1)
+ 5502 8055 Experiment Interface Board (address=2)
+ 5503 8055 Experiment Interface Board (address=3)
+10d1 Hottinger Baldwin Measurement
+ 0101 USB-Module for Spider8, CP32
+ 0202 CP22 - Communication Processor
+ 0301 CP42 - Communication Processor
+10d2 RayComposer - R. Adams
+ 5243 RayComposer
+10d4 Man Boon Manufactory, Ltd
+10d5 Uni Class Technology Co., Ltd
+ 0004 PS/2 Converter
+ 5552 KVM Human Interface Composite Device (Keyboard/Mouse ports)
+ 55a2 2Port KVMSwitcher
+10d6 Actions Semiconductor Co., Ltd
+ 0c02 BioniQ 1001 Tablet
+ 1000 MP3 Player
+ 1100 MPMan MP-Ki 128 MP3 Player/Recorder
+ 1101 D-Wave 2GB MP4 Player / AK1025 MP3/MP4 Player
+ 2200 Acer MP-120 MP3 player
+ 8888 ADFU Device
+ ff51 ADFU Device
+ ff61 MP4 Player
+ ff66 Craig 2GB MP3/Video Player
+10de Authenex, Inc.
+10df In-Win Development, Inc.
+ 0500 iAPP CR-e500 Card reader
+10e0 Post-Op Video, Inc.
+10e1 CablePlus, Ltd
+10e2 Nada Electronics, Ltd
+10ec Vast Technologies, Inc.
+10f0 Nexio Co., Ltd
+ 2002 iNexio Touchscreen controller
+10f1 Importek
+ 1a08 Internal Webcam
+ 1a1e Laptop Integrated Webcam 1.3M
+ 1a2a Laptop Integrated Webcam
+10f5 Turtle Beach
+ 0200 Audio Advantage Roadie
+10fb Pictos Technologies, Inc.
+10fd Anubis Electronics, Ltd
+ 7e50 FlyCam Usb 100
+ 804d Typhoon Webshot II Webcam [zc0301]
+ 8050 FlyCAM-USB 300 XP2
+ de00 WinFast WalkieTV WDM Capture Driver.
+10fe Thrane & Thrane
+ 000c TT-3750 BGAN-XL Radio Module
+1100 VirTouch, Ltd
+ 0001 VTPlayer VTP-1 Braille Mouse
+1101 EasyPass Industrial Co., Ltd
+ 0001 FSK Electronics Super GSM Reader
+1108 Brightcom Technologies, Ltd
+110a Moxa Technologies Co., Ltd.
+ 1250 UPort 1250 2-Port RS-232/422/485
+ 1251 UPort 1250I 2-Port RS-232/422/485 with Isolation
+ 1410 UPort 1410 4-Port RS-232
+ 1450 UPort 1450 4-Port RS-232/422/485
+ 1451 UPort 1450I 4-Port RS-232/422/485 with Isolation
+ 1613 UPort 1610-16 16-Port RS-232
+ 1618 UPort 1610-8 8-Port RS-232
+ 1653 UPort 1650-16 16-Port RS-232/422/485
+ 1658 UPort 1650-8 8-Port RS-232/422/485
+1110 Analog Devices Canada, Ltd (Allied Telesyn)
+ 5c01 Huawei MT-882 Remote NDIS Network Device
+ 6489 ADSL ETH/USB RTR
+ 9000 ADSL LAN Adapter
+ 9001 ADSL Loader
+ 900f AT-AR215 DSL Modem
+ 9010 AT-AR215 DSL Modem
+ 9021 ADSL WAN Adapter
+ 9022 ADSL Loader
+ 9023 ADSL WAN Adapter
+ 9024 ADSL Loader
+ 9031 ADSL LAN Adapter
+ 9032 ADSL Loader
+1111 Pandora International Ltd.
+ 8888 Evolution Device
+1112 YM ELECTRIC CO., Ltd
+1113 Medion AG
+ a0a2 Active Sync device
+111e VSO Electric Co., Ltd
+112a RedRat
+ 0001 RedRat3 IR Transceiver
+ 0005 RedRat3II IR Transceiver
+112e Master Hill Electric Wire and Cable Co., Ltd
+112f Cellon International, Inc.
+1130 Tenx Technology, Inc.
+ 0001 BlyncLight
+ 0002 iBuddy
+ 0202 Rocket Launcher
+ 6604 MCE IR-Receiver
+ 660c Foot Pedal/Thermometer
+ 6806 Keychain photo frame
+ c301 Digital Photo viewer [Wallet Pix]
+ f211 TP6911 Audio Headset
+1131 Integrated System Solution Corp.
+ 1001 KY-BT100 Bluetooth Adapter
+ 1002 Bluetooth Device
+ 1003 Bluetooth Device
+ 1004 Bluetooth Device
+1132 Toshiba Corp., Digital Media Equipment [hex]
+ 4331 PDR-M4/M5/M70 Digital Camera
+ 4332 PDR-M60 Digital Camera
+ 4333 PDR-M2300/PDR-M700
+ 4334 PDR-M65
+ 4335 PDR-M61
+ 4337 PDR-M11
+ 4338 PDR-M25
+1136 CTS Electronincs
+ 3131 CTS LS515
+113c Arin Tech Co., Ltd
+113d Mapower Electronics Co., Ltd
+1141 V One Multimedia, Pte., Ltd
+1142 CyberScan Technologies, Inc.
+ 0709 Cyberview High Speed Scanner
+1145 Japan Radio Company
+ 0001 AirH PHONE AH-J3001V/J3002V
+1146 Shimane SANYO Electric Co., Ltd.
+1147 Ever Great Electric Wire and Cable Co., Ltd
+114b Sphairon Access Systems GmbH
+ 0110 Turbolink UB801R WLAN Adapter
+ 0150 Turbolink UB801RE Wireless 802.11g 54Mbps Network Adapter [RTL8187]
+114c Tinius Olsen Testing Machine Co., Inc.
+114d Alpha Imaging Technology Corp.
+114f Wavecom
+ 1234 Fastrack Xtend FXT001 Modem
+115b Salix Technology Co., Ltd.
+1162 Secugen Corp.
+1163 DeLorme Publishing, Inc.
+ 0100 Earthmate GPS (orig)
+ 0200 Earthmate GPS (LT-20, LT-40)
+ 2020 Earthmate GPS (PN-40)
+1164 YUAN High-Tech Development Co., Ltd
+ 0300 ELSAVISION 460D
+ 0601 Analog TV Tuner
+ 0900 TigerBird BMP837 USB2.0 WDM Encoder
+ 0bc7 Digital TV Tuner
+ 521b MC521A mini Card ATSC Tuner
+ 6601 Digital TV Tuner Card [RTL2832U]
+1165 Telson Electronics Co., Ltd
+1166 Bantam Interactive Technologies
+1167 Salient Systems Corp.
+1168 BizConn International Corp.
+116e Gigastorage Corp.
+116f Silicon 10 Technology Corp.
+ 0005 Flash Card Reader
+ c108 Flash Card Reader
+ c109 Flash Card Reader
+1175 Shengyih Steel Mold Co., Ltd
+117d Santa Electronic, Inc.
+117e JNC, Inc.
+1182 Venture Corp., Ltd
+1183 Compaq Computer Corp. [hex] (Digital Dream ??)
+ 0001 DigitalDream l'espion XS
+ 19c7 ISDN TA
+ 4008 56k FaxModem
+ 504a PJB-100 Personal Jukebox
+1184 Kyocera Elco Corp.
+1188 Bloomberg L.P.
+1189 Acer Communications & Multimedia
+ 0893 EP-1427X-2 Ethernet Adapter [Acer]
+118f You Yang Technology Co., Ltd
+1190 Tripace
+1191 Loyalty Founder Enterprise Co., Ltd
+1196 Yankee Robotics, LLC
+ 0010 Trifid Camera without code
+ 0011 Trifid Camera
+1197 Technoimagia Co., Ltd
+1198 StarShine Technology Corp.
+1199 Sierra Wireless, Inc.
+ 0019 AC595U
+ 0021 AC597E
+ 0024 MC5727 CDMA modem
+ 0110 Composite Device
+ 0112 CDMA 1xEVDO PC Card, AirCard 580
+ 0120 AC595U
+ 0218 MC5720 Wireless Modem
+ 6467 MP Series Network Adapter
+ 6468 MP Series Network Adapter
+ 6469 MP Series Network Adapter
+ 6802 MC8755 Device
+ 6803 MC8765 Device
+ 6804 MC8755 Device
+ 6805 MC8765 Device
+ 6812 MC8775 Device
+ 6820 AC875 Device
+ 6832 MC8780 Device
+ 6833 MC8781 Device
+ 683a MC8785 Device
+ 683c Mobile Broadband 3G/UMTS (MC8790 Device)
+ 6850 AirCard 880 Device
+ 6851 AirCard 881 Device
+ 6852 AirCard 880E Device
+ 6853 AirCard 881E Device
+ 6854 AirCard 885 Device
+ 6856 ATT "USB Connect 881"
+ 6870 MC8780 Device
+ 6871 MC8781 Device
+ 6893 MC8777 Device
+ 68a3 MC8700 Modem
+ 68aa 4G LTE adapter
+ 9000 Gobi 2000 Wireless Modem (QDL mode)
+ 9001 Gobi 2000 Wireless Modem
+ 9002 Gobi 2000 Wireless Modem
+ 9003 Gobi 2000 Wireless Modem
+ 9004 Gobi 2000 Wireless Modem
+ 9005 Gobi 2000 Wireless Modem
+ 9006 Gobi 2000 Wireless Modem
+ 9007 Gobi 2000 Wireless Modem
+ 9008 Gobi 2000 Wireless Modem
+ 9009 Gobi 2000 Wireless Modem
+ 900a Gobi 2000 Wireless Modem
+ 9055 Gobi 9x15 Multimode 3G/4G LTE Modem (NAT mode)
+ 9057 Gobi 9x15 Multimode 3G/4G LTE Modem (IP passthrough mode)
+119a ZHAN QI Technology Co., Ltd
+119b ruwido austria GmbH
+ 0400 Infrared Keyboard V2.01
+11a0 Chipcon AS
+ eb11 CC2400EB 2.0 ZigBee Sniffer
+11a3 Technovas Co., Ltd
+ 8031 MP3 Player
+ 8032 MP3 Player
+11aa GlobalMedia Group, LLC
+ 1518 iREZ K2
+11ab Exito Electronics Co., Ltd
+11ac Nike
+ 6565 FuelBand
+11b0 ATECH FLASH TECHNOLOGY
+ 6208 PRO-28U
+11be R&D International NV
+ f0a0 Martin Maxxyz DMX
+11c5 Inmax
+ 0521 IMT-0521 Smartcard Reader
+11ca VeriFone Inc
+ 0207 PIN Pad VX 810
+ 0220 PIN Pad VX 805
+11db Topfield Co., Ltd.
+ 1000 PVR
+ 1100 PVR
+11e6 K.I. Technology Co. Ltd.
+11f5 Siemens AG
+ 0001 SX1
+ 0003 Mobile phone USB cable
+ 0004 X75
+ 0005 SXG75/EF81
+ 0008 UMTS/HSDPA Data Card
+ 0101 RCU Connect
+11f6 Prolific
+ 2001 Willcom WSIM
+11f7 Alcatel (?)
+ 02df Serial cable (v2) for TD-10 Mobile Phone
+1203 TSC Auto ID Technology Co., Ltd
+ 0140 TTP-245C
+1209 InterBiometrics
+ 1001 USB Hub
+ 1002 USB Relais
+ 1003 IBSecureCam-P
+ 1004 IBSecureCam-O
+ 1005 IBSecureCam-N
+ 1006 Mini IO-Board
+ 2000 Zygmunt Krynicki Lantern Brightness Sensor
+ 2048 Housedillon.com MRF49XA Transciever
+ 2222 LabConnect Signalgenerator
+ 2300 Keyboardio Keyboardio Model 01 Bootloader
+ 2301 Keyboardio Keyboardio Model 01
+ 2337 /Dev or SlashDev /Net
+ 3000 lloyd3000
+ 3333 LabConnect Digitalnetzteil
+ 5222 telavivmakers attami
+ 5a22 ikari_01 sd2snes
+ 7bd0 pokey9000 Tiny Bit Dingus
+ abd0 tibounise ADB converter
+ beef Modal MC-USB
+ c0f5 unethi PERswitch
+ ca1c KnightOS Hub
+ ca1d KnightOS MTP Device
+ cafe ii iigadget
+ dada Rebel Technology OWL
+ dead chaosfield.at AVR-Ruler
+ fa11 moonglow OpenXHC
+ feed ProgramGyar AVR-IR Sender
+120e Hudson Soft Co., Ltd
+120f Magellan
+ 524e RoadMate 1475T
+ 5260 Triton Handheld GPS Receiver (300/400/500/1500/2000)
+1210 DigiTech
+ 0016 RP500 Guitar Multi-Effects Processor
+ 001b RP155 Guitar Multi-Effects Processor
+ 001c RP255 Guitar Multi-Effects Processor
+121e Jungsoft Co., Ltd
+ 3403 Muzio JM250 Audio Player
+1221 Unknown manufacturer
+ 3234 Disk (Thumb drive)
+1223 SKYCABLE ENTERPRISE. CO., LTD.
+1228 Datapaq Limited
+ 0012 Q18 Data Logger
+ 0015 TPaq21/MPaq21 Datalogger
+ 584c XL2 Logger
+1230 Chipidea-Microelectronica, S.A.
+1233 Denver Electronics
+ 5677 FUSB200 mp3 player
+1234 Brain Actuated Technologies
+ 0000 Neural Impulse Actuator Prototype 1.0 [NIA]
+ 4321 Human Interface Device
+ ed02 Emotiv EPOC Developer Headset Wireless Dongle
+1235 Focusrite-Novation
+ 0001 ReMOTE Audio/XStation First Edition
+ 0002 Speedio
+ 0003 RemoteSL + ZeroSL
+ 0004 ReMOTE LE
+ 0005 XIOSynth [First Edition]
+ 0006 XStation
+ 0007 XIOSynth
+ 0008 ReMOTE SL Compact
+ 0009 nIO
+ 000a Nocturn
+ 000b ReMOTE SL MkII
+ 000c ZeRO MkII
+ 000e Launchpad
+ 0010 Saffire 6
+ 0011 Ultranova
+ 0012 Nocturn Keyboard
+ 0013 VRM Box
+ 0014 VRM Box Audio Class (2-out)
+ 0015 Dicer
+ 0016 Ultranova
+ 0018 Twitch
+ 0019 Impulse 25
+ 001a Impulse 49
+ 001b Impulse 61
+ 4661 ReMOTE25
+ 8000 Scarlett 18i6
+ 8002 Scarlett 8i6
+ 8006 Focusrite Scarlett 2i2
+ 8008 Saffire 6
+ 800a Scarlett 2i4
+ 800c Scarlett 18i20
+ 800e iTrack Solo
+ 8010 Forte
+ 8012 Scarlett 6i6
+ 8014 Scarlett 18i8
+1241 Belkin
+ 0504 Wireless Trackball Keyboard
+ 1111 Mouse
+ 1122 Typhoon Stream Optical Mouse USB+PS/2
+ 1155 Memorex Optical ScrollPro Mouse SE MX4600
+ 1166 MI-2150 Trust Mouse
+ 1177 Mouse [HT82M21A]
+ 1503 Keyboard
+ 1603 Keyboard
+ f767 Keyboard
+124a AirVast
+ 168b PRISM3 WLAN Adapter
+ 4017 PC-Chips 802.11b Adapter
+ 4023 WM168g 802.11bg Wireless Adapter [Intersil ISL3886]
+ 4025 IOGear GWU513 v2 802.11bg Wireless Adapter [Intersil ISL3887]
+124b Nyko (Honey Bee)
+ 4d01 Airflo EX Joystick
+124c MXI - Memory Experts International, Inc.
+ 3200 Stealth MXP 1GB
+125c Apogee Inc.
+ 0010 Alta series CCD
+125f A-DATA Technology Co., Ltd.
+ 312a Superior S102
+ 312b Superior S102 Pro
+ a15a DashDrive Durable HD710 portable HDD various size
+ a22a DashDrive Elite HE720 500GB
+ a91a Portable HDD CH91
+ c08a C008 Flash Drive
+ c81a Flash drive
+ c93a 4GB Pen Drive
+ c96a C906 Flash Drive
+ cb10 Dash Drive UV100
+1260 Standard Microsystems Corp.
+ ee22 SMC2862W-G v3 EZ Connect 802.11g Adapter [Intersil ISL3887]
+1264 Covidien Energy-based Devices
+1266 Pirelli Broadband Solutions
+ 6302 Fastweb DRG A226M ADSL Router
+1267 Logic3 / SpectraVideo plc
+ 0103 G-720 Keyboard
+ 0201 A4Tech SWOP-3 Mouse
+ 0210 LG Optical Mouse 3D-310
+ a001 JP260 PC Game Pad
+ c002 Wireless Optical Mouse
+126c Aristocrat Technologies
+126d Bel Stewart
+126e Strobe Data, Inc.
+126f TwinMOS
+ 0163 Storage device (2gB thumb drive)
+ 1325 Mobile Disk
+ 2168 Mobile Disk III
+ a006 G240 802.11bg
+1274 Ensoniq
+1275 Xaxero Marine Software Engineering, Ltd.
+ 0002 WeatherFax 2000 Demodulator
+ 0080 SkyEye Weather Satellite Receiver
+1278 Starlight Xpress
+ 0105 SXV-M5
+ 0107 SXV-M7
+ 0109 SXV-M9
+ 0110 SXVF-H16
+ 0115 SXVF-H5
+ 0119 SXV-H9
+ 0135 SXVF-H35
+ 0136 SXVF-H36
+ 0200 SXV interface for paraller MX cameras
+ 0305 SXV-M5C
+ 0307 SXV-M7C
+ 0319 SXV-H9C
+ 0325 SXV-M25C
+ 0326 SXVR-M26C
+ 0507 Lodestar autoguider
+ 0517 CoStar
+1283 zebris Medical GmbH
+ 0100 USB-RS232 Adaptor
+ 0110 CMS20
+ 0111 CMS 10
+ 0112 CMS 05
+ 0114 ARCUS digma PC-Interface
+ 0115 SAM Axioquick recorder
+ 0116 SAM Axioquick recorder
+ 0120 emed-X
+ 0121 emed-AT
+ 0130 PDM
+ 0150 CMS10GI (Golf)
+1286 Marvell Semiconductor, Inc.
+ 00bc Marvell JTAG Probe
+ 1fab 88W8338 [Libertas] 802.11g
+ 2001 88W8388 802.11a/b/g WLAN
+ 2006 88W8362 802.11n WLAN
+ 8001 BLOB boot loader firmware
+1291 Qualcomm Flarion Technologies, Inc. / Leadtek Research, Inc.
+ 0010 FDM 2xxx Flash-OFDM modem
+ 0011 LR7F06/LR7F14 Flash-OFDM modem
+1292 Innomedia
+ 0258 Creative Labs VoIP Blaster
+1293 Belkin Components [hex]
+ 0002 F5U002 Parallel Port [uss720]
+ 2101 104-key keyboard
+1294 RISO KAGAKU CORP.
+ 1320 Webmail Notifier
+129b CyberTAN Technology
+ 160b Siemens S30853-S1031-R351 802.11g Wireless Adapter [Atheros AR5523]
+ 160c Siemens S30853-S1038-R351 802.11g Wireless Adapter [Atheros AR5523]
+ 1666 TG54USB 802.11bg
+ 1667 802.11bg
+ 1828 Gigaset USB Adapter 300
+12a7 Trendchip Technologies Corp.
+12ab Honey Bee Electronic International Ltd.
+12b8 Zhejiang Xinya Electronic Technology Co., Ltd.
+12b9 E28
+12ba Licensed by Sony Computer Entertainment America
+ 00ff Rocksmith Guitar Adapter
+ 0100 RedOctane Guitar for PlayStation(R)3
+ 0120 RedOctane Drum Kit for PlayStation(R)3
+ 0200 Harmonix Guitar for PlayStation(R)3
+ 0210 Harmonix Drum Kit for PlayStation(R)3
+12bd Gembird
+ d012 JPD Shockforce gamepad
+12c4 Autocue Group Ltd
+ 0006 Teleprompter Two-button Hand Control (v1)
+ 0008 Teleprompter Foot Control (v1)
+12cf DEXIN
+ 0170 Tt eSPORTS BLACK Gaming mouse
+12d1 Huawei Technologies Co., Ltd.
+ 1001 E169/E620/E800 HSDPA Modem
+ 1003 E220 HSDPA Modem / E230/E270/E870 HSDPA/HSUPA Modem
+ 1004 E220 (bis)
+ 1009 U120
+ 1010 ETS2252+ CDMA Fixed Wireless Terminal
+ 1021 U8520
+ 1035 U8120
+ 1037 Ideos
+ 1038 Ideos (debug mode)
+ 1039 Ideos (tethering mode)
+ 1404 EM770W miniPCI WCDMA Modem
+ 1406 E1750
+ 140b EC1260 Wireless Data Modem HSD USB Card
+ 140c E180v
+ 1412 EC168c
+ 1436 Broadband stick
+ 1446 Broadband stick (modem on)
+ 1465 K3765 HSPA
+ 14c3 K5005 Vodafone LTE/UMTS/GSM Modem/Networkcard
+ 14c8 K5005 Vodafone LTE/UMTS/GSM MOdem/Networkcard
+ 14c9 K3770 3G Modem
+ 14cf K3772
+ 14d1 K3770 3G Modem (Mass Storage Mode)
+ 14db E353/E3131
+ 14f1 Gobi 3000 HSPA+ Modem
+ 14fe Modem (Mass Storage Mode)
+ 1501 Pulse
+ 1505 E398 LTE/UMTS/GSM Modem/Networkcard
+ 1506 Modem/Networkcard
+ 150a E398 LTE/UMTS/GSM Modem/Networkcard
+ 1520 K3765 HSPA
+ 1521 K4505 HSPA+
+ 155a R205 Mobile WiFi (CD-ROM mode)
+ 1575 K5150 LTE modem
+ 15ca E3131 3G/UMTS/HSPA+ Modem (Mass Storage Mode)
+ 1805 AT&T Go Phone U2800A phone
+ 1c05 Broadband stick (modem on)
+ 1c0b E173s 3G broadband stick (modem off)
+ 1c20 R205 Mobile WiFi (Charging)
+ 1d50 ET302s TD-SCDMA/TD-HSDPA Mobile Broadband
+ 1f01 E353/E3131 (Mass storage mode)
+ 1f16 K5150 LTE modem (Mass Storage Mode)
+ 380b WiMAX USB modem(s)
+12d2 LINE TECH INDUSTRIAL CO., LTD.
+12d6 EMS Dr. Thomas Wuensche
+ 0444 CPC-USB/ARM7
+ 0888 CPC-USB/M16C
+12d7 BETTER WIRE FACTORY CO., LTD.
+12d8 Araneus Information Systems Oy
+ 0001 Alea I True Random Number Generator
+12e6 Waldorf Music GmbH
+ 0013 Blofeld
+12ef Tapwave, Inc.
+ 0100 Tapwave Handheld [Tapwave Zodiac]
+12f5 Dynamic System Electronics Corp.
+12f7 Memorex Products, Inc.
+ 1a00 TD Classic 003B
+ 1e23 TravelDrive 2007 Flash Drive
+12fd AIN Comm. Technology Co., Ltd
+ 1001 AWU2000b 802.11b Stick
+12ff Fascinating Electronics, Inc.
+ 0101 Advanced RC Servo Controller
+1307 Transcend Information, Inc.
+ 0163 256MB/512MB/1GB Flash Drive
+ 0165 2GB/4GB/8GB Flash Drive
+ 0190 Ut190 8 GB Flash Drive with MicroSD reader
+ 0310 SD/MicroSD CardReader [hama]
+ 0330 63-in-1 Multi-Card Reader/Writer
+ 0361 CR-75: 51-in-1 Card Reader/Writer [Sakar]
+ 1169 TS2GJF210 JetFlash 210 2GB
+ 1171 Fingerprint Reader
+1308 Shuttle, Inc.
+ 0003 VFD Module
+ c001 eHome Infrared Transceiver
+1310 Roper
+ 0001 Class 1 Bluetooth Dongle
+1312 ICS Electronics
+1313 ThorLabs
+ 0010 LC1 Linear Camera (Jungo)
+ 0011 SP1 Spectrometer (Jungo)
+ 0012 SP2 Spectrometer (Jungo)
+ 0110 LC1 Linear Camera (VISA)
+ 0111 SP1 Spectrometer (VISA)
+ 0112 SP2 Spectrometer (VISA)
+ 8001 TXP-Series Slot (TXP5001, TXP5004)
+ 8012 BC106 Camera Beam Profiler
+ 8013 WFS10 Wavefront Sensor
+ 8017 BC206 Camera Beam Profiler
+ 8019 BP2 Multi Slit Beam Profiler
+ 8020 PM300 Optical Power Meter
+ 8021 PM300E Optical Power and Energy Meter
+ 8022 PM320E Optical Power and Energy Meter
+ 8030 ER100 Extinction Ratio Meter
+ 8070 PM100D
+131d Natural Point
+ 0155 TrackIR 3 Pro Head Tracker
+ 0156 TrackIR 4 Pro Head Tracker
+132a Envara Inc.
+ 1502 WiND 802.11abg / 802.11bg WLAN
+132b Konica Minolta
+ 0000 Dimage A2 Camera
+ 0001 Minolta DiMAGE A2 (ptp)
+ 0003 Dimage Xg Camera
+ 0006 Dimage Z2 Camera
+ 0007 Minolta DiMAGE Z2 (PictBridge mode)
+ 0008 Dimage X21 Camera
+ 000a Dimage Scan Dual IV AF-3200 (2891)
+ 000b Dimage Z10 Camera
+ 000d Dimage X50 Camera [storage?]
+ 000f Dimage X50 Camera [p2p?]
+ 0010 Dimage G600 Camera
+ 0012 Dimage Scan Elite 5400 II (2892)
+ 0013 Dimage X31 Camera
+ 0015 Dimage G530 Camera
+ 0017 Dimage Z3 Camera
+ 0018 Minolta DiMAGE Z3 (PictBridge mode)
+ 0019 Dimage A200 Camera
+ 0021 Dimage Z5 Camera
+ 0022 Minolta DiMAGE Z5 (PictBridge mode)
+ 002c Dynax 5D camera
+ 2001 Magicolor 2400w
+ 2004 Magicolor 5430DL
+ 2005 Magicolor 2430 DL
+ 2029 Magicolor 5440DL
+ 2030 PagePro 1350E(N)
+ 2033 PagePro 1400W
+ 2043 Magicolor 2530DL
+ 2045 Magicolor 2500W
+ 2049 Magicolor 2490MF
+133e Kemper Digital GmbH
+ 0815 Virus TI Desktop
+1342 Mobility
+ 0200 EasiDock 200 Hub
+ 0201 EasiDock 200 Keyboard and Mouse Port
+ 0202 EasiDock 200 Serial Port
+ 0203 EasiDock 200 Printer Port
+ 0204 Ethernet
+ 0304 EasiDock Ethernet
+1343 Citizen Systems
+ 0003 CX / DNP DS40
+ 0004 CX-W / DNP DS80
+ 0005 CY / DNP DSRX
+1345 Sino Lite Technology Corp.
+ 001c Xbox Controller Hub
+ 6006 Defender Wireless Controller
+1347 Moravian Instruments
+ 0400 G2CCD USB 1.1 obsolete
+ 0401 G2CCD-S with Sony ICX285 CCD
+ 0402 G2CCD2
+ 0403 G2/G3CCD-I KAI CCD
+ 0404 G2/G3/G4 CCD-F KAF CCD
+ 0405 Gx CCD-I CCD
+ 0406 Gx CCD-F CCD
+ 0410 G1-0400 CCD
+ 0411 G1-0800 CCD
+ 0412 G1-0300 CCD
+ 0413 G1-2000 CCD
+ 0414 G1-1400 CCD
+1348 Katsuragawa Electric Co., Ltd.
+134c PanJit International Inc.
+ 0001 Touch Panel Controller
+ 0002 Touch Panel Controller
+ 0003 Touch Panel Controller
+ 0004 Touch Panel Controller
+134e Digby's Bitpile, Inc. DBA D Bit
+1357 P&E Microcomputer Systems
+ 0089 OpenSDA - CDC Serial Port
+ 0503 USB-ML-12 HCS08/HCS12 Multilink
+ 0504 DEMOJM
+135f Control Development Inc.
+ 0110 Linear Spectrograph
+ 0111 Spectrograph - Renumerated
+ 0200 Linear Spectrograph
+ 0201 Spectrograph - Renumerated
+ 0240 MPP Spectrograph
+1366 SEGGER
+ 0101 J-Link PLUS
+136b STEC
+136e Andor Technology Ltd.
+ 0014 Zyla 5.5 sCMOS camera
+1370 Swissbit
+ 0323 Swissmemory cirrusWHITE
+ 6828 Victorinox Flash Drive
+1371 CNet Technology Inc.
+ 0001 CNUSB-611AR Wireless Adapter-G [AT76C503]
+ 0002 CNUSB-611AR Wireless Adapter-G [AT76C503] (FiberLine WL-240U)
+ 0013 CNUSB-611 Wireless Adapter [AT76C505]
+ 0014 CNUSB-611 Wireless Adapter [AT76C505] (FiberLine WL-240U)
+ 5743 CNUSB-611 (D) Wireless Adapter [AT76C503]
+ 9022 CWD-854 [RT2573]
+ 9032 CWD-854 rev F
+ 9401 CWD-854 Wireless 802.11g 54Mbps Network Adapter [RTL8187]
+1376 Vimtron Electronics Co., Ltd.
+137b SCAPS GmbH
+ 0002 SCAPS USC-2 Scanner Controller
+1385 Netgear, Inc
+ 4250 WG111T
+ 4251 WG111T (no firmware)
+ 5f00 WPN111 RangeMax(TM) Wireless USB 2.0 Adapter
+ 5f01 WPN111 (no firmware)
+ 5f02 WPN111 (no firmware)
+ 6e00 WPNT121 802.11g 240Mbps Wireless Adapter [Airgo AGN300]
+138a Validity Sensors, Inc.
+ 0001 VFS101 Fingerprint Reader
+ 0005 VFS301 Fingerprint Reader
+ 0007 VFS451 Fingerprint Reader
+ 0008 VFS300 Fingerprint Reader
+ 0010 VFS Fingerprint sensor
+ 0011 VFS5011 Fingerprint Reader
+ 0017 Fingerprint Reader
+ 0018 Fingerprint scanner
+ 003c VFS471 Fingerprint Reader
+ 003d VFS491
+ 003f VFS495 Fingerprint Reader
+ 0050 Swipe Fingerprint Sensor
+138e Jungo LTD
+ 9000 Raisonance S.A. STM32 ARM evaluation board
+1390 TOMTOM B.V.
+ 0001 GO 520 T/GO 630/ONE XL (v9)
+ 5454 Blue & Me 2
+ 7474 GPS Sport Watch [Runner, Multi-Sport]
+1391 IdealTEK, Inc.
+ 1000 URTC-1000
+1395 Sennheiser Communications
+ 3556 USB Headset
+1397 BEHRINGER International GmbH
+ 00bc BCF2000
+1398 Q-tec
+ 2103 USB 2.0 Storage Device
+13ad Baltech
+ 9999 Card reader
+13b0 PerkinElmer Optoelectronics
+ 000a Alesis Photon X25 MIDI Controller
+13b1 Linksys
+ 000a WUSB54G v2 802.11g Adapter [Intersil ISL3887]
+ 000b WUSB11 v4.0 802.11b Adapter [ALi M4301]
+ 000c WUSB54AG 802.11a/g Adapter [Intersil ISL3887]
+ 000d WUSB54G v4 802.11g Adapter [Ralink RT2500USB]
+ 000e WUSB54GS v1 802.11g Adapter [Broadcom 4320 USB]
+ 0011 WUSB54GP v4.0 802.11g Adapter [Ralink RT2500USB]
+ 0014 WUSB54GS v2 802.11g Adapter [Broadcom 4320 USB]
+ 0018 USB200M 10/100 Ethernet Adapter
+ 001a HU200TS Wireless Adapter
+ 001e WUSBF54G 802.11bg
+ 0020 WUSB54GC v1 802.11g Adapter [Ralink RT73]
+ 0022 WUSB54GX4 802.11g 240Mbps Wireless Adapter [Airgo AGN300]
+ 0023 WUSB54GR
+ 0024 WUSBF54G v1.1 802.11bg
+ 0026 WUSB54GSC v1 802.11g Adapter [Broadcom 4320 USB]
+ 0028 WUSB200 802.11g Adapter [Ralink RT2671]
+ 0029 WUSB300N 802.11bgn Wireless Adapter [Marvell 88W8362+88W8060]
+ 002f AE1000 v1 802.11n [Ralink RT3572]
+ 0031 AM10 v1 802.11n [Ralink RT3072]
+ 0039 AE1200 802.11bgn Wireless Adapter [Broadcom BCM43235]
+ 003a AE2500 802.11abgn Wireless Adapter [Broadcom BCM43236]
+ 003b AE3000 802.11abgn (3x3) Wireless Adapter [Ralink RT3573]
+ 003e AE6000 802.11a/b/g/n/ac Wireless Adapter [MediaTek MT7610U]
+ 003f WUSB6300 802.11a/b/g/n/ac Wireless Adapter [Realtek RTL8812AU]
+ 13b1 WUSB200: Wireless-G Business Network Adapter with Rangebooster
+13b2 Alesis
+ 0030 Multimix 8
+13b3 Nippon Dics Co., Ltd.
+13ba PCPlay
+ 0001 Konig Electronic CMP-KEYPAD12 Numeric Keypad
+ 0017 PS/2 Keyboard+Mouse Adapter
+ 0018 Barcode PCP-BCG4209
+13be Ricoh Printing Systems, Ltd.
+13ca JyeTai Precision Industrial Co., Ltd.
+13cf Wisair Ltd.
+ 1200 Olidata Wireless Multimedia Adapter
+13d0 Techsan Electronics Co., Ltd.
+ 2282 TechniSat DVB-PC TV Star 2
+13d1 A-Max Technology Macao Commercial Offshore Co. Ltd.
+ 7019 MD 82288
+ abe6 Wireless 802.11g 54Mbps Network Adapter [RTL8187]
+13d2 Shark Multimedia
+ 0400 Pocket Ethernet [klsi]
+13d3 IMC Networks
+ 3201 VisionDTV USB-Ter/HAMA USB DVB-T device cold
+ 3202 VisionDTV USB-Ter/HAMA USB DVB-T device warm
+ 3203 DTV-DVB UDST7020BDA DVB-S Box(DVBS for MCE2005)
+ 3204 DTV-DVB UDST7020BDA DVB-S Box(DVBS for MCE2005)
+ 3205 DNTV Live! Tiny USB2 BDA (No Remote)
+ 3206 DNTV Live! Tiny USB2 BDA (No Remote)
+ 3207 DTV-DVB UDST7020BDA DVB-S Box(DVBS for MCE2005)
+ 3208 DTV-DVB UDST7020BDA DVB-S Box(DVBS for MCE2005)
+ 3209 DTV-DVB UDST7022BDA DVB-S Box(Without HID)
+ 3211 DTV-DVB Hybrid Analog/Capture / Pinnacle PCTV 310e
+ 3212 DTV-DVB UDTT704C - DVBT/NTSC/PAL Driver(PCM4)
+ 3213 DTV-DVB UDTT704D - DVBT/NTSC/PAL Driver (PCM4)
+ 3214 DTV-DVB UDTT704F -(MiniCard) DVBT/NTSC/PAL Driver(Without HID)
+ 3215 DTV-DVB UDAT7240 - ATSC/NTSC/PAL Driver(PCM4)
+ 3216 DTV-DVB UDTT 7047-USB 2.0 DVB-T Driver
+ 3217 Digital-TV Receiver.
+ 3219 DTV-DVB UDTT7049 - DVB-T Driver(Without HID)
+ 3220 DTV-DVB UDTT 7047M-USB 2.0 DVB-T Driver
+ 3223 DNTV Live! Tiny USB2 BDA (No Remote)
+ 3224 DNTV Live! Tiny USB2 BDA (No Remote)
+ 3226 DigitalNow TinyTwin DVB-T Receiver
+ 3234 DVB-T FTA Half Minicard [RTL2832U]
+ 3236 DTV-DVB UDTT 7047A-USB 2.0 DVB-T Driver
+ 3237 DTV-DVB UDTT 704J - dual DVB-T Driver
+ 3239 DTV-DVB UDTT704D - DVBT/NTSC/PAL Driver(Without HID)
+ 3240 DTV-DVB UDXTTM6010 - A/D Driver(Without HID)
+ 3241 DTV-DVB UDXTTM6010 - A/D Driver(Without HID)
+ 3242 DTV-DVB UDAT7240LP - ATSC/NTSC/PAL Driver(Without HID)
+ 3243 DTV-DVB UDXTTM6010 - A/D Driver(Without HID)
+ 3244 DTV-DVB UDTT 7047Z-USB 2.0 DVB-T Driver
+ 3247 802.11 n/g/b Wireless LAN Adapter
+ 3249 Internal Bluetooth
+ 3262 802.11 n/g/b Wireless LAN USB Adapter
+ 3273 802.11 n/g/b Wireless LAN USB Mini-Card
+ 3274 DVB-T Dongle [RTL2832U]
+ 3282 DVB-T + GPS Minicard [RTL2832U]
+ 3284 Wireless LAN USB Mini-Card
+ 3304 Asus Integrated Bluetooth module [AR3011]
+ 3306 Mediao 802.11n WLAN [Realtek RTL8191SU]
+ 3315 Bluetooth module
+ 3362 Atheros AR3012 Bluetooth 4.0 Adapter
+ 3375 Atheros AR3012 Bluetooth 4.0 Adapter
+ 3392 Azurewave 43228+20702
+ 3394 Bluetooth
+ 3474 Atheros AR3012 Bluetooth
+ 5070 Webcam
+ 5111 Integrated Webcam
+ 5115 Integrated Webcam
+ 5116 Integrated Webcam
+ 5122 2M Integrated Webcam
+ 5126 PC Cam
+ 5130 Integrated Webcam
+ 5702 UVC VGA Webcam
+ 5710 UVC VGA Webcam
+ 5716 UVC VGA Webcam
+ 7020 DTV-DVB UDST7020BDA DVB-S Box(DVBS for MCE2005)
+ 7022 DTV-DVB UDST7022BDA DVB-S Box(Without HID)
+13d7 Guidance Software, Inc.
+ 0001 T5 PATA forensic bridge
+13dc ALEREON, INC.
+13dd i.Tech Dynamic Limited
+13e1 Kaibo Wire & Cable (Shenzhen) Co., Ltd.
+13e5 Rane
+ 0001 SL-1
+ 0003 TTM 57SL
+13e6 TechnoScope Co., Ltd.
+13ea Hengstler
+ 0001 C-56 Thermal Printer
+13ec Zydacron
+ 0006 HID Remote Control
+13ee MosArt
+ 0001 Optical Mouse
+ 0003 Optical Mouse
+13fd Initio Corporation
+ 0840 INIC-1618L SATA
+ 0841 Samsung SE-T084M DVD-RW
+ 1040 INIC-1511L PATA Bridge
+ 1340 Hi-Speed USB to SATA Bridge
+ 160f RocketFish SATA Bridge [INIC-1611]
+ 1640 INIC-1610L SATA Bridge
+ 1669 INIC-1609PN
+ 1840 INIC-1608 SATA bridge
+ 1e40 INIC-1610P SATA bridge
+13fe Kingston Technology Company Inc.
+ 1a00 512MB/1GB Flash Drive
+ 1a23 512MB Flash Drive
+ 1d00 DataTraveler 2.0 1GB/4GB Flash Drive / Patriot Xporter 4GB Flash Drive
+ 1e00 Flash Drive 2 GB [ICIDU 2 GB]
+ 1e50 U3 Smart Drive
+ 1f00 Kingston DataTraveler / Patriot Xporter
+ 1f23 PS2232 flash drive controller
+ 2240 microSD card reader
+ 3100 2/4 GB stick
+ 3123 Verbatim STORE N GO 4GB
+ 3600 flash drive (4GB, EMTEC)
+ 3800 Rage XT Flash Drive
+ 3e00 Flash Drive
+ 4100 Flash drive
+ 5000 USB flash drive (32 GB SHARKOON Accelerate)
+ 5100 Flash Drive
+1400 Axxion Group Corp.
+1402 Bowe Bell & Howell
+1403 Sitronix
+ 0001 Digital Photo Frame
+1409 IDS Imaging Development Systems GmbH
+ 1000 generic (firmware not loaded yet)
+ 1485 uEye UI1485
+140e Telechips, Inc.
+ b011 TCC780X-based player (USB Boot mode)
+ b021 TCC77X-based players (USB Boot mode)
+1410 Novatel Wireless
+ 1110 Merlin S620
+ 1120 Merlin EX720
+ 1130 Merlin S720
+ 1400 Merlin U730/U740 (Vodafone)
+ 1410 Merlin U740 (non-Vodafone)
+ 1430 Merlin XU870
+ 1450 Merlin X950D
+ 2110 Ovation U720/MCD3000
+ 2410 Expedite EU740
+ 2420 Expedite EU850D/EU860D/EU870D
+ 4100 U727
+ 4400 Ovation MC930D/MC950D
+ 9010 Expedite E362
+ a001 Gobi Wireless Modem
+ a008 Gobi Wireless Modem (QDL mode)
+ b001 Ovation MC551
+1415 Nam Tai E&E Products Ltd. or OmniVision Technologies, Inc.
+ 0000 Sony SingStar USBMIC
+ 0020 Sony Wireless SingStar
+ 2000 Sony Playstation Eye
+1419 ABILITY ENTERPRISE CO., LTD.
+1421 Sensor Technology
+ 0605 Sentech Camera
+1429 Vega Technologies Industrial (Austria) Co.
+142a Thales E-Transactions
+ 0003 Artema Hybrid
+ 0005 Artema Modular
+ 0043 medCompact
+142b Arbiter Systems, Inc.
+ 03a5 933A Portable Power Sentinel
+1430 RedOctane
+ 0150 wireless receiver for skylanders wii
+ 4734 Guitar Hero4 hub
+ 474b Guitar Hero MIDI interface
+1431 Pertech Resources, Inc.
+1435 Wistron NeWeb
+ 0427 UR054g 802.11g Wireless Adapter [Intersil ISL3887]
+ 0711 UR055G 802.11bg
+ 0804 AR9170+AR9104 802.11abgn Wireless Adapter
+ 0826 AR5523
+ 0827 AR5523 (no firmware)
+ 0828 AR5523
+ 0829 AR5523 (no firmware)
+1436 Denali Software, Inc.
+143c Altek Corporation
+1443 Digilent
+ 0007 Development board JTAG
+1446 X.J.GROUP
+ 6a73 Stamps.com Model 510 5LB Scale
+ 6a78 DYMO Endicia 75lb Digital Scale
+1453 Radio Shack
+ 4026 26-183 Serial Cable
+1456 Extending Wire & Cable Co., Ltd.
+1457 First International Computer, Inc.
+ 5117 OpenMoko Neo1973 kernel usbnet (g_ether, CDC Ethernet) mode
+ 5118 OpenMoko Neo1973 Debug board (V2+)
+ 5119 OpenMoko Neo1973 u-boot cdc_acm serial port
+ 511a HXD8 u-boot usbtty CDC ACM Mode
+ 511b SMDK2440 u-boot usbtty CDC ACM mode
+ 511c SMDK2443 u-boot usbtty CDC ACM mode
+ 511d QT2410 u-boot usbtty CDC ACM mode
+ 5120 OpenMoko Neo1973 u-boot usbtty generic serial
+ 5121 OpenMoko Neo1973 kernel mass storage (g_storage) mode
+ 5122 OpenMoko Neo1973 / Neo Freerunner kernel cdc_ether USB network
+ 5123 OpenMoko Neo1973 internal USB CSR4 module
+ 5124 OpenMoko Neo1973 Bluetooth Device ID service
+145f Trust
+ 0106 Trust K56 V92 USB Modem
+ 013d PC Camera (SN9C201 + OV7660)
+ 013f Megapixel Auto Focus Webcam
+ 0142 WB-6250X Webcam
+ 015a WB-8300X 2MP Webcam
+ 0161 15901 802.11bg Wireless Adapter [Realtek RTL8187L]
+ 0167 Widescreen 3MP Webcam
+ 0176 Isla Keyboard
+1460 Tatung Co.
+ 9150 eHome Infrared Transceiver
+1461 Staccato Communications
+1462 Micro Star International
+ 5512 MegaStick-1 Flash Stick
+ 8807 DIGIVOX mini III [af9015]
+1472 Huawei-3Com
+ 0007 Aolynk WUB300g [ZyDAS ZD1211]
+ 0009 Aolynk WUB320g
+147a Formosa Industrial Computing, Inc.
+ e015 eHome Infrared Receiver
+ e016 eHome Infrared Receiver
+ e017 eHome Infrared Receiver
+ e018 eHome Infrared Receiver
+ e02c Infrared Receiver
+ e03a eHome Infrared Receiver
+ e03c eHome Infrared Receiver
+ e03d 2 Channel Audio
+ e03e Infrared Receiver [IR605A/Q]
+147e Upek
+ 1000 Biometric Touchchip/Touchstrip Fingerprint Sensor
+ 1001 TCS5B Fingerprint sensor
+ 1002 Biometric Touchchip/Touchstrip Fingerprint Sensor
+ 2016 Biometric Touchchip/Touchstrip Fingerprint Sensor
+ 2020 TouchChip Fingerprint Coprocessor (WBF advanced mode)
+ 3000 TCS1C EIM/Cypress Fingerprint sensor
+ 3001 TCS1C EIM/STM32 Fingerprint sensor
+147f Hama GmbH & Co., KG
+1482 Vaillant
+ 1005 VRD PC-Interface
+1484 Elsa AG [hex]
+ 1746 Ecomo 19H99 Monitor
+ 7616 Elsa Hub
+1485 Silicom
+ 0001 U2E
+ 0002 Psion Gold Port Ethernet
+1487 DSP Group, Ltd.
+148e EVATRONIX SA
+148f Ralink Technology, Corp.
+ 1000 Motorola BC4 Bluetooth 3.0+HS Adapter
+ 1706 RT2500USB Wireless Adapter
+ 2070 RT2070 Wireless Adapter
+ 2570 RT2570 Wireless Adapter
+ 2573 RT2501/RT2573 Wireless Adapter
+ 2671 RT2601/RT2671 Wireless Adapter
+ 2770 RT2770 Wireless Adapter
+ 2870 RT2870 Wireless Adapter
+ 3070 RT2870/RT3070 Wireless Adapter
+ 3071 RT3071 Wireless Adapter
+ 3072 RT3072 Wireless Adapter
+ 3370 RT3370 Wireless Adapter
+ 3572 RT3572 Wireless Adapter
+ 3573 RT3573 Wireless Adapter
+ 5370 RT5370 Wireless Adapter
+ 5372 RT5372 Wireless Adapter
+ 5572 RT5572 Wireless Adapter
+ 7601 MT7601U Wireless Adapter
+ 760b MT7601U Wireless Adapter
+ 9020 RT2500USB Wireless Adapter
+ 9021 RT2501USB Wireless Adapter
+1491 Futronic Technology Co. Ltd.
+ 0020 FS81 Fingerprint Scanner Module
+1493 Suunto
+ 0010 Bluebird [Ambit]
+ 0019 Duck [Ambit2]
+ 001a Colibri [Ambit2 S]
+ 001b Emu [Ambit3 Peak]
+ 001c Finch [Ambit3 Sport]
+ 001d Greentit [Ambit2 R]
+1497 Panstrong Company Ltd.
+1498 Microtek International Inc.
+ a090 DVB-T Tuner
+149a Imagination Technologies
+ 2107 DBX1 DSP core
+14aa WideView Technology Inc.
+ 0001 Avermedia AverTV DVBT USB1.1 (cold)
+ 0002 Avermedia AverTV DVBT USB1.1 (warm)
+ 0201 AVermedia/Yakumo/Hama/Typhoon DVB-T USB2.0 (cold)
+ 0221 WT-220U DVB-T dongle
+ 022b WT-220U DVB-T dongle
+ 0301 AVermedia/Yakumo/Hama/Typhoon DVB-T USB2.0 (warm)
+14ad CTK Corporation
+14ae Printronix Inc.
+14af ATP Electronics Inc.
+14b0 StarTech.com Ltd.
+14b2 Ralink Technology, Corp.
+ 3a93 Topcom 802.11bg Wireless Adapter [Atheros AR5523]
+ 3a95 Toshiba WUS-G06G-JT 802.11bg Wireless Adapter [Atheros AR5523]
+ 3a98 Airlink101 AWLL4130 802.11bg Wireless Adapter [Atheros AR5523]
+ 3c02 Conceptronic C54RU v2 802.11bg Wireless Adapter [Ralink RT2571]
+ 3c05 rt2570 802.11g WLAN
+ 3c06 Conceptronic C300RU v1 802.11bgn Wireless Adapter [Ralink RT2870]
+ 3c07 802.11n adapter
+ 3c09 802.11n adapter
+ 3c22 Conceptronic C54RU v3 802.11bg Wireless Adapter [Ralink RT2571W]
+ 3c23 Airlink101 AWLL6080 802.11bgn Wireless Adapter [Ralink RT2870]
+ 3c24 NEC NP01LM 802.11abg Wireless Adapter [Ralink RT2571W]
+ 3c25 DrayTek Vigor N61 802.11bgn Wireless Adapter [Ralink RT2870]
+ 3c27 Airlink101 AWLL6070 802.11bgn Wireless Adapter [Ralink RT2770]
+ 3c28 Conceptronic C300RU v2 802.11bgn Wireless Adapter [Ralink RT2770]
+ 3c2b NEC NP02LM 802.11bgn Wireless Adapter [Ralink RT3072]
+ 3c2c Keebox W150NU 802.11bgn Wireless Adapter [Ralink RT3070]
+14c0 Rockwell Automation, Inc.
+14c2 Gemlight Computer, Ltd
+ 0250 Storage Adapter V2
+ 0350 Storage Adapter V2
+14c8 Zytronic
+14cd Super Top
+ 1212 microSD card reader (SY-T18)
+ 121c microSD card reader
+ 121f microSD CardReader SY-T18
+ 123a SD/MMC/RS-MMC Card Reader
+ 125c SD card reader
+ 127b SDXC Reader
+ 6116 M6116 SATA Bridge
+ 6600 M110E PATA bridge
+ 6700 Card Reader
+ 6900 Card Reader
+ 8123 SD MMC Reader
+ 8125 SD MMC Reader
+14d8 JAMER INDUSTRIES CO., LTD.
+14dd Raritan Computer, Inc.
+ 1007 D2CIM-VUSB KVM connector
+14e0 WiNRADiO Communications
+ 0501 WR-G528e 'CHEETAH'
+14e1 Dialogue Technology Corp.
+ 5000 PenMount 5000 Touch Controller
+14e5 SAIN Information & Communications Co., Ltd.
+14ea Planex Communications
+ ab10 GW-US54GZ
+ ab11 GU-1000T
+ ab13 GW-US54Mini 802.11bg
+14ed Shure Inc.
+ 29b6 X2u Adapter
+14f7 TechniSat Digital GmbH
+ 0001 SkyStar 2 HD CI
+ 0002 SkyStar 2 HD CI
+ 0003 CableStar Combo HD CI
+ 0004 AirStar TeleStick 2
+ 0500 DVB-PC TV Star HD
+1500 Ellisys
+1501 Pine-Tum Enterprise Co., Ltd.
+1509 First International Computer, Inc.
+ 0a01 LI-3100 Area Meter
+ 0a02 LI-7000 CO2/H2O Gas Analyzer
+ 0a03 C-DiGit Blot Scanner
+ 9242 eHome Infrared Transceiver
+1513 medMobile
+ 0444 medMobile
+1514 Actel
+ 2003 FlashPro3 Programmer
+ 2004 FlashPro3 Programmer
+ 2005 FlashPro3 Programmer
+1516 CompUSA
+ 1603 Flash Drive
+ 8628 Pen Drive
+1518 Cheshire Engineering Corp.
+ 0001 HDReye High Dynamic Range Camera
+ 0002 HDReye (before firmware loads)
+1519 Comneon
+ 0020 HSIC Device
+1520 Bitwire Corp.
+1524 ENE Technology Inc
+ 6680 UTS 6680
+1527 Silicon Portals
+ 0200 YAP Phone (no firmware)
+ 0201 YAP Phone
+1529 UBIQUAM Co., Ltd.
+ 3100 CDMA 1xRTT USB Modem (U-100/105/200/300/520)
+152a Thesycon Systemsoftware & Consulting GmbH
+ 8350 NET Gmbh iCube Camera
+ 8400 INI DVS128
+ 840d INI DAViS
+ 841a INI DAViS FX3
+152b MIR Srl
+ 0001 spirobank II
+ 0002 spirolab III
+ 0003 MiniSpir
+ 0004 Oxi
+ 0005 spiros II
+ 0006 smiths spirobank II
+ 0007 smiths spirobank G-USB
+ 0008 smiths MiniSpir
+ 0009 spirobank G-USB
+ 000a smiths Oxi
+ 000b smiths spirolab III
+ 000c chorus III
+ 000d spirolab III Bw
+ 000e spirolab III
+ 000f easySpiro
+ 0010 Spirotel converter
+ 0011 spirobank
+ 0012 spiro3 Zimmer
+ 0013 spirotel serial
+ 0014 spirotel II
+ 0015 spirodoc
+152d JMicron Technology Corp. / JMicron USA Technology Corp.
+ 0539 JMS539/567 SuperSpeed SATA II/III 3.0G/6.0G Bridge
+ 0567 JMS567 SATA 6Gb/s bridge
+ 0770 Alienware Integrated Webcam
+ 2329 JM20329 SATA Bridge
+ 2335 ATA/ATAPI Bridge
+ 2336 Hard Disk Drive
+ 2337 ATA/ATAPI Bridge
+ 2338 JM20337 Hi-Speed USB to SATA & PATA Combo Bridge
+ 2339 JM20339 SATA Bridge
+ 2352 ATA/ATAPI Bridge
+ 2509 JMS539 SuperSpeed SATA II 3.0G Bridge
+ 2551 JMS551 SATA 3Gb/s bridge
+ 2566 JMS566 SATA 3Gb/s bridge
+ 2590 Seatay ATA/ATAPI Bridge
+ 3562 JMS567 SATA 6Gb/s bridge
+ 3569 JMS566 SATA 3Gb/s bridge
+152e LG (HLDS)
+ 2507 PL-2507 IDE Controller
+ e001 GSA-5120D DVD-RW
+1532 Razer USA, Ltd
+ 0001 RZ01-020300 Optical Mouse [Diamondback]
+ 0003 Krait Mouse
+ 0007 DeathAdder Mouse
+ 0013 Orochi mouse
+ 0015 Naga Mouse
+ 0016 DeathAdder Mouse
+ 0017 RZ01-0035 Laser Gaming Mouse [Imperator]
+ 001c RZ01-0036 Optical Gaming Mouse [Abyssus]
+ 0024 Razer Mamba
+ 002e RZ01-0058 Gaming Mouse [Naga]
+ 0036 RZ01-0075, Gaming Mouse [Naga Hex]
+ 0101 Copperhead Mouse
+ 0102 Tarantula Keyboard
+ 0109 Lycosa Keyboard
+ 0113 RZ07-0074 Gaming Keypad [Orbweaver]
+ 0300 RZ06-0063 Motion Sensing Controllers [Hydra]
+153b TerraTec Electronic GmbH
+ 1181 Cinergy S2 PCIe Dual Port 1
+ 1182 Cinergy S2 PCIe Dual Port 2
+1546 U-Blox AG
+ 01a5 NL-402U
+1547 SG Intec Ltd & Co KG
+ 1000 SG-Lock[U2]
+154a Celectronic GmbH
+ 8180 CARD STAR/medic2
+154b PNY
+ 0010 USB 2.0 Flash Drive
+ 0048 Flash Drive
+ 004d 8 GB Flash Drive
+ 0053 Flash Drive
+ 0057 32GB Micro Slide Attache Flash Drive
+ 005b Flash Drive
+ 0062 Flash Drive
+ 007a Classic Attache Flash Drive
+ 6545 FD Device
+ fa05 Flash Drive
+154d ConnectCounty Holdings Berhad
+154e D&M Holdings, Inc. (Denon/Marantz)
+ 3000 Marantz RC9001 Remote Control
+154f SNBC CO., Ltd
+1554 Prolink Microsystems Corp.
+ 5010 PV-D231U(RN)-F [PixelView PlayTV SBTVD Full-Seg]
+1557 OQO
+ 0002 model 01 WiFi interface
+ 0003 model 01 Bluetooth interface
+ 0a80 Gobi Wireless Modem (QDL mode)
+ 7720 model 01+ Ethernet
+ 8150 model 01 Ethernet interface
+1568 Sunf Pu Technology Co., Ltd
+156f Quantum Corporation
+1570 ALLTOP TECHNOLOGY CO., LTD.
+157b Ketron SRL
+157e TRENDnet
+ 3006 TEW-444UB EU [TRENDnet]
+ 3007 TEW-444UB EU (no firmware)
+ 300a TEW-429UB 802.11bg
+ 300b TEW-429UB 802.11bg
+ 300c TEW-429UF A1 802.11bg Wireless Adapter [ZyDAS ZD1211B]
+ 300d TEW-429UB C1 802.11bg
+ 300e SMC SMCWUSB-N 802.11bgn 2x2:2 Wireless Adapter [Ralink RT2870]
+ 3012 TEW-604UB 802.11bg Wireless Adapter [Atheros AR5523]
+ 3013 TEW-645UB 802.11bgn 1x2:2 Wireless Adapter [Ralink RT2770]
+ 3204 Allnet ALL0298 v2 802.11bg
+ 3205 Allnet ALL0283 [AR5523]
+ 3206 Allnet ALL0283 [AR5523](no firmware)
+ 3207 TEW-509UB A1 802.11abg Wireless Adapter [ZyDAS ZD1211]
+ 3208 TEW-509UB 1.1R 802.11abg Wireless Adapter
+1582 Fiberline
+ 6003 WL-430U 802.11bg
+1587 SMA Technologie AG
+158d Oakley Inc.
+158e JDS Uniphase Corporation (JDSU)
+ 0820 SmartPocket Class Device
+1598 Kunshan Guoji Electronics Co., Ltd.
+15a2 Freescale Semiconductor, Inc.
+ 0038 9S08JS Bootloader
+ 003b USB2CAN Application for ColdFire DEMOJM board
+ 0042 OSBDM - Debug Port
+ 004f i.MX28 SystemOnChip in RecoveryMode
+ 0052 i.MX50 SystemOnChip in RecoveryMode
+ 0054 i.MX 6Dual/6Quad SystemOnChip in RecoveryMode
+ 0061 i.MX 6Solo/6DualLite SystemOnChip in RecoveryMode
+15a4 Afatech Technologies, Inc.
+ 1000 AF9015/AF9035 DVB-T stick
+ 1001 AF9015/AF9035 DVB-T stick
+ 1336 SDHC/MicroSD/MMC/MS/M2/CF/XD Flash Card Reader
+ 9015 AF9015 DVB-T USB2.0 stick
+ 9016 AF9015 DVB-T USB2.0 stick
+15a8 Teams Power Limited
+15a9 Gemtek
+ 0002 SparkLAN WL-682 802.11bg Wireless Adapter [Intersil ISL3887]
+ 0004 WUBR-177G [Ralink RT2571W]
+ 0006 Wireless 11n USB Adapter
+ 0010 802.11n USB Wireless Card
+ 0012 WUBR-208N 802.11abgn Wireless Adapter [Ralink RT2870]
+ 002d WLTUBA-107 [Yota 4G LTE]
+15aa Gearway Electronics (Dong Guan) Co., Ltd.
+15ad VMware Inc.
+15ba Olimex Ltd.
+ 0003 OpenOCD JTAG
+ 0004 OpenOCD JTAG TINY
+ 002a ARM-USB-TINY-H JTAG interface
+ 002b ARM-USB-OCD-H JTAG+RS232
+15c0 XL Imaging
+ 0001 2M pixel Microscope Camera
+ 0002 3M pixel Microscope Camera
+ 0003 1.3M pixel Microscope Camera (mono)
+ 0004 1.3M pixel Microscope Camera (colour)
+ 0005 3M pixel Microscope Camera (Mk 2)
+ 0006 2M pixel Microscope Camera (with capture button)
+ 0007 3M pixel Microscope Camera (with capture button)
+ 0008 1.3M pixel Microscope Camera (colour, with capture button)
+ 0009 1.3M pixel Microscope Camera (colour, with capture button)
+ 000a 2M pixel Microscope Camera (Mk 2)
+ 0010 1.3M pixel "Tinycam"
+ 0101 3M pixel Microscope Camera
+15c2 SoundGraph Inc.
+ 0036 LC16M VFD Display/IR Receiver
+ 0038 GD01 MX LCD Display/IR Receiver
+ 0042 Antec Veris Multimedia Station E-Z IR Receiver
+ ffda iMON PAD Remote Controller
+ ffdc iMON PAD Remote Controller
+15c5 Advance Multimedia Internet Technology Inc. (AMIT)
+ 0008 WL532U 802.11g Adapter
+15c6 Laboratoires MXM
+ 1000 DigistimSP (cold)
+ 1001 DigistimSP (warm)
+ 1002 DigimapSP USB (cold)
+ 1003 DigimapSP USB (warm)
+ 1004 DigistimSP (cold)
+ 1005 DigistimSP (warm)
+ 1100 Odyssee (cold)
+ 1101 Odyssee (warm)
+ 1200 Digispy
+15c8 KTF Technologies
+ 3201 EVER EV-W100/EV-W250
+15c9 D-Box Technologies
+15ca Textech International Ltd.
+ 00c3 Mini Optical Mouse
+ 0101 MIDI Interface cable
+ 1806 MIDI Interface cable
+15d5 Coulomb Electronics Ltd.
+15d9 Trust International B.V.
+ 0a33 Optical Mouse
+ 0a37 Mouse
+ 0a41 MI-2540D [Optical mouse]
+ 0a4c USB+PS/2 Optical Mouse
+ 0a4d Optical Mouse
+ 0a4f Optical Mouse
+15dc Hynix Semiconductor Inc.
+15e0 Seong Ji Industrial Co., Ltd.
+15e1 RSA
+ 2007 RSA SecurID (R) Authenticator
+15e4 Numark
+ 0024 Mixtrack
+ 0140 ION VCR 2 PC / Video 2 PC
+15e8 SohoWare
+ 9100 NUB100 Ethernet [pegasus]
+ 9110 10/100 USB Ethernet
+15e9 Pacific Digital Corp.
+ 04ce MemoryFrame MF-570
+ 1968 MemoryFrame MF-570
+ 1969 Digital Frame
+15ec Belcarra Technologies Corp.
+15f4 HanfTek
+ 0001 HanfTek UMT-010 USB2.0 DVB-T (cold)
+ 0025 HanfTek UMT-010 USB2.0 DVB-T (warm)
+1604 Tascam
+ 8000 US-428 Audio/Midi Controller (without fw)
+ 8001 US-428 Audio/Midi Controller
+ 8004 US-224 Audio/Midi Controller (without fw)
+ 8005 US-224 Audio/Midi Controller
+ 8006 US-122 Audio/Midi Interface (without fw)
+ 8007 US-122 Audio/Midi Interface
+1606 Umax
+ 0002 Astra 1236U Scanner
+ 0010 Astra 1220U
+ 0030 Astra 1600U/2000U
+ 0050 Scanner
+ 0060 Astra 3400/3450
+ 0070 Astra 4400/4450
+ 0130 Astra 2100U
+ 0160 Astra 5400U
+ 0170 Uniscan D50
+ 0230 Astra 2200/2200SU
+ 0350 Astra 4800/4850 Scanner
+ 1030 Astra 4000U
+ 1220 Genesys Logic Scanner Controller NT5.0
+ 2010 AstraCam Digital Camera
+ 2020 AstraCam 1000
+ 2030 AstraCam 1800 Digital Camera
+1608 Inside Out Networks [hex]
+ 0001 EdgePort/4 Serial Port
+ 0002 Edgeport/8
+ 0003 Rapidport/4
+ 0004 Edgeport/4
+ 0005 Edgeport/2
+ 0006 Edgeport/4i
+ 0007 Edgeport/2i
+ 0008 Edgeport/8
+ 000c Edgeport/421
+ 000d Edgeport/21
+ 000e Edgeport/4
+ 000f Edgeport/8
+ 0010 Edgeport/2
+ 0011 Edgeport/4
+ 0012 Edgeport/416
+ 0014 Edgeport/8i
+ 0018 Edgeport/412
+ 0019 Edgeport/412
+ 001a Edgeport/2+2i
+ 0101 Edgeport/4
+ 0105 Edgeport/2
+ 0106 Edgeport/4i
+ 0107 Edgeport/2i
+ 010c Edgeport/421
+ 010d Edgeport/21
+ 0110 Edgeport/2
+ 0111 Edgeport/4
+ 0112 Edgeport/416
+ 0114 Edgeport/8i
+ 0201 Edgeport/4
+ 0203 Rapidport/4
+ 0204 Edgeport/4
+ 0205 Edgeport/2
+ 0206 Edgeport/4i
+ 0207 Edgeport/2i
+ 020c Edgeport/421
+ 020d Edgeport/21
+ 020e Edgeport/4
+ 020f Edgeport/8
+ 0210 Edgeport/2
+ 0211 Edgeport/4
+ 0212 Edgeport/416
+ 0214 Edgeport/8i
+ 0215 Edgeport/1
+ 0216 EPOS/44
+ 0217 Edgeport/42
+ 021a Edgeport/2+2i
+ 021b Edgeport/2c
+ 021c Edgeport/221c
+ 021d Edgeport/22c
+ 021e Edgeport/21c
+ 021f Edgeport/62
+ 0240 Edgeport/1
+ 0241 Edgeport/1i
+ 0242 Edgeport/4s
+ 0243 Edgeport/8s
+ 0244 Edgeport/8
+ 0245 Edgeport/22c
+ 0301 Watchport/P
+ 0302 Watchport/M
+ 0303 Watchport/W
+ 0304 Watchport/T
+ 0305 Watchport/H
+ 0306 Watchport/E
+ 0307 Watchport/L
+ 0308 Watchport/R
+ 0309 Watchport/A
+ 030a Watchport/D
+ 030b Watchport/D
+ 030c Power Management Port
+ 030e Power Management Port
+ 030f Watchport/G
+ 0310 Watchport/Tc
+ 0311 Watchport/Hc
+ 1403 MultiTech Systems MT4X56 Modem
+ 1a17 Agilent Technologies (E6473)
+160a VIA Technologies, Inc.
+ 3184 VIA VNT-6656 [WiFi 802.11b/g USB Dongle]
+160e INRO
+ 0001 E2USBKey
+1614 Amoi Electronics
+ 0404 WMA9109 UMTS Phone
+ 0600 Vodafone VDA GPS / Toschiba Protege G710
+ 0804 WP-S1 Phone
+1617 Sony Corp.
+ 2002 NVX-P1 Personal Navigation System
+1619 L & K Precision Technology Co., Ltd.
+1621 Wionics Research
+1628 Stonestreet One, Inc.
+162a Airgo Networks Inc.
+162f WiQuest Communications, Inc.
+1630 2Wire, Inc.
+ 0005 802.11g Wireless Adapter [Intersil ISL3886]
+ 0011 PC Port 10 Mps Adapter
+ ff81 802.11b Wireless Adapter [Lucent/Agere Hermes I]
+1631 Good Way Technology
+ 6200 GWUSB2E
+ c019 RT2573
+1645 Entrega [hex]
+ 0001 1S Serial Port
+ 0002 2S Serial Port
+ 0003 1S25 Serial Port
+ 0004 4S Serial Port
+ 0005 E45 Ethernet [klsi]
+ 0006 Parallel Port
+ 0007 U1-SC25 SCSI
+ 0008 Ethernet
+ 0016 Bi-directional to Parallel Printer Converter
+ 0080 1 port to Serial Converter
+ 0081 1 port to Serial Converter
+ 0093 1S9 Serial Port
+ 8000 EZ-USB
+ 8001 1 port to Serial
+ 8002 2x Serial Port
+ 8003 1 port to Serial
+ 8004 2U4S serial/usb hub
+ 8005 Ethernet
+ 8080 1 port to Serial
+ 8081 1 port to Serial
+ 8093 PortGear Serial Port
+1649 SofTec Microsystems
+ 0102 uDART In-Circuit Debugger
+ 0200 SpYder USBSPYDER08
+164a ChipX
+164c Matrix Vision GmbH
+ 0101 mvBlueFOX camera (no firmware)
+ 0103 mvBlueFOX camera
+ 0201 mvBlueLYNX-X intelligent camera (bootloader)
+ 0203 mvBlueLYNX-X intelligent camera
+1657 Struck Innovative Systeme GmbH
+ 3150 SIS3150 USB2.0 to VME interface
+165b Frontier Design Group
+ 8101 Tranzport Control Surface
+ fad1 Alphatrack Control Surface
+165c Kondo Kagaku
+ 0002 Serial Adapter
+1660 Creatix Polymedia GmbH
+1667 GIGA-TMS INC.
+ 0005 PCR330A RFID Reader (125 kHz, keyboard emulation)
+1668 Actiontec Electronics, Inc. [hex]
+ 0009 Gateway
+ 0333 Modem
+ 0358 InternetPhoneWizard
+ 0405 Gateway
+ 0408 Prism2.5 802.11b Adapter
+ 0413 Gateway
+ 0421 Prism2.5 802.11b Adapter
+ 0441 IBM Integrated Bluetooth II
+ 0500 BTM200B BlueTooth Adapter
+ 1050 802UIG-1 802.11g Wireless Mini Adapter [Intersil ISL3887]
+ 1200 802AIN Wireless N Network Adapter [Atheros AR9170+AR9101]
+ 1441 IBM Integrated Bluetooth II
+ 2441 BMDC-2 IBM Bluetooth III w.56k
+ 3441 IBM Integrated Bluetooth III
+ 6010 Gateway
+ 6097 802.11b Wireless Adapter
+ 6106 802UI3(B) 802.11b Wireless Adapter [Intersil PRISM 3]
+ 7605 UAT1 Wireless Ethernet Adapter
+1669 PiKRON Ltd. [hex]
+ 1001 uLan2USB Converter - PS1 protocol
+166a Clipsal
+ 0101 C-Bus Multi-room Audio Matrix Switcher
+ 0201 C-Bus Pascal Automation Controller
+ 0301 C-Bus Wireless PC Interface
+ 0303 C-Bus interface
+ 0304 C-Bus Black and White Touchscreen
+ 0305 C-Bus Spectrum Colour Touchscreen
+ 0401 C-Bus Architectural Dimmer
+1677 China Huada Integrated Circuit Design (Group) Co., Ltd. (CIDC Group)
+ 0103 Token
+1679 Total Phase
+ 2001 Beagle Protocol Analyzer
+ 2002 Cheetah SPI Host Adapter
+1680 Golden Bridge Electech Inc.
+ a332 DVB-T Dongle [RTL2832U]
+1681 Prevo Technologies, Inc.
+ 0001 Tuner's Dashboard
+ 0002 Tubachron
+1682 Maxwise Production Enterprise Ltd.
+1684 Godspeed Computer Corp.
+1685 Delock
+ 0200 Infrared adapter
+1686 ZOOM Corporation
+ 0045 H4 Digital Recorder
+1687 Kingmax Digital Inc.
+ 5289 FlashDisk
+ 6211 FlashDisk
+ 6213 FlashDisk
+1688 Saab AB
+1689 Razer USA, Ltd
+ fd00 Onza Tournament Edition controller
+168c Atheros Communications
+ 0001 AR5523
+ 0002 AR5523 (no firmware)
+1690 Askey Computer Corp. [hex]
+ 0001 Arcaze Gamepad
+ 0101 Creative Modem Blaster DE5670
+ 0102 V1456 VQE-R2 Modem [conexant]
+ 0103 1456 VQE-R3 Modem [conexant]
+ 0104 HCF V90 Data Fax RTAD Modem
+ 0107 HCF V.90 Data,Fax,RTAD Modem
+ 0109 MagicXpress V.90 Pocket Modem [conexant]
+ 0203 Voyager ADSL Modem Loader
+ 0204 Voyager ADSL Modem
+ 0205 DSL Modem
+ 0206 GlobeSpan ADSL WAN Modem
+ 0208 DSL Modem
+ 0209 Voyager 100 ADSL Modem
+ 0211 Globespan Virata ADSL LAN Modem
+ 0212 DSL Modem
+ 0213 HM121d DSL Modem
+ 0214 HM121d DSL Modem
+ 0215 Voyager 105 ADSL Modem
+ 0701 WLAN
+ 0710 SMCWUSBT-G
+ 0711 SMCWUSBT-G (no firmware)
+ 0712 AR5523
+ 0713 AR5523 (no firmware)
+ 0715 Name: Voyager 1055 Laptop 802.11g Adapter [Broadcom 4320]
+ 0722 RT2573
+ 0726 Wi-Fi Wireless LAN Adapter
+ 0740 802.11n Wireless LAN Card
+ 0901 Voyager 205 ADSL Router
+ 2000 naturaSign Pad Standard
+ 2001 naturaSign Pad Standard
+ fe12 Bootloader
+1696 Hitachi Video and Information System, Inc.
+1697 VTec Test, Inc.
+16a5 Shenzhen Zhengerya Cable Co., Ltd.
+16a6 Unigraf
+ 3000 VTG-3xxx Video Test Generator family
+ 4000 VTG-4xxx Video Test Generator family
+ 5000 VTG-5xxx Video Test Generator family
+ 5001 VTG-5xxx Special (update) mode of VTG-5xxx family
+16ab Global Sun Technology
+ 7801 AR5523
+ 7802 AR5523 (no firmware)
+ 7811 AR5523
+ 7812 AR5523 (no firmware)
+16ac Dongguan ChingLung Wire & Cable Co., Ltd.
+16b4 iStation
+ 0801 U43
+16b5 Persentec, Inc.
+ 0002 Otto driving companion
+16c0 Van Ooijen Technische Informatica
+ 03e8 free for internal lab use 1000
+ 03e9 free for internal lab use 1001
+ 03ea free for internal lab use 1002
+ 03eb free for internal lab use 1003
+ 03ec free for internal lab use 1004
+ 03ed free for internal lab use 1005
+ 03ee free for internal lab use 1006
+ 03ef free for internal lab use 1007
+ 03f0 free for internal lab use 1008
+ 03f1 free for internal lab use 1009
+ 0477 Teensy Rebootor
+ 0478 Teensy Halfkay Bootloader
+ 0479 Teensy Debug
+ 047a Teensy Serial
+ 047b Teensy Serial+Debug
+ 047c Teensy Keyboard
+ 047d Teensy Keyboard+Debug
+ 047e Teensy Mouse
+ 047f Teensy Mouse+Debug
+ 0480 Teensy RawHID
+ 0481 Teensy RawHID+Debug
+ 0482 Teensyduino Keyboard+Mouse+Joystick
+ 0483 Teensyduino Serial
+ 0484 Teensyduino Disk
+ 0485 Teensyduino MIDI
+ 0486 Teensyduino RawHID
+ 0487 Teensyduino Serial+Keyboard+Mouse+Joystick
+ 0488 Teensyduino Flight Sim Controls
+ 05dc shared ID for use with libusb
+ 05dd BlackcatUSB2
+ 05df HID device except mice, keyboards, and joysticks
+ 05e1 Free shared USB VID/PID pair for CDC devices
+ 05e4 Free shared USB VID/PID pair for MIDI devices
+ 06b4 USB2LPT with 2 interfaces
+ 06b5 USB2LPT with 3 interfaces (native, HID, printer)
+ 074e DSP-Weuffen USB-HPI-Programmer
+ 074f DSP-Weuffen USB2-HPI-Programmer
+ 0762 Osmocom SIMtrace
+ 076b OpenPCD 13.56MHz RFID Reader
+ 076c OpenPICC 13.56MHz RFID Simulator (native)
+ 08ac OpenBeacon USB stick
+ 08ca Alpermann+Velte Universal Display
+ 08cb Alpermann+Velte Studio Clock
+ 08cc Alpermann+Velte SAM7S MT Boot Loader
+ 08cd Alpermann+Velte SAM7X MT Boot Loader
+ 0a32 jbmedia Light-Manager Pro
+ 27d8 libusb-bound devices
+ 27d9 HID device except mice, keyboards, and joysticks
+ 27da Mouse
+ 27db Keyboard
+ 27dc Joystick
+ 27dd CDC-ACM class devices (modems)
+ 27de MIDI class devices
+ 294a Eye Movement Recorder [Visagraph]
+ 294b Eye Movement Recorder [ReadAlyzer]
+16ca Wireless Cables, Inc.
+ 1502 Bluetooth Dongle
+16cc silex technology, Inc.
+16d0 MCS
+ 0498 Braintechnology USB-LPS
+ 0504 RETRO Innovations ZoomFloppy
+ 054b GrauTec ReelBox OLED Display (external)
+ 05be EasyLogic Board
+ 06f9 Gabotronics Xminilab
+ 0753 Digistump DigiSpark
+ 075c AB-1.x UAC1 [Audio Widget]
+ 075d AB-1.x UAC2 [Audio Widget]
+ 080a S2E1 Interface
+ 0870 Kaufmann Automotive GmbH, RKS+CAN Interface
+16d1 Suprema Inc.
+ 0401 SUP-SFR400(A) BioMini Fingerprint Reader
+16d3 Frontline Test Equipment, Inc.
+16d5 AnyDATA Corporation
+ 6202 CDMA/UMTS/GPRS modem
+ 6501 CDMA 2000 1xRTT/EV-DO Modem
+ 6502 CDMA/UMTS/GPRS modem
+ 6603 ADU-890WH modem
+16d6 JABLOCOM s.r.o.
+ 8000 GDP-04 desktop phone
+ 8001 EYE-02
+ 8003 GDP-04 modem
+ 8004 Bootloader
+ 8005 GDP-04i
+ 8007 BTP-06 modem
+16d8 CMOTECH Co., Ltd.
+ 5141 CMOTECH CDMA Technologies modem
+ 5533 CCU-550 CDMA EV-DO modem
+ 5543 CDMA 2000 1xRTT/1xEVDO modem
+ 6280 CMOTECH CDMA Technologies modem
+ 6803 CNU-680 CDMA EV-DO modem
+ 8001 Gobi 2000 Wireless Modem (QDL mode)
+ 8002 Gobi 2000 Wireless Modem
+16dc Wiener, Plein & Baus
+ 0001 CC
+ 000b VM
+ 0010 PL512 Power Supply System
+ 0011 MARATON Power Supply System
+ 0012 MPOD Multi Channel Power Supply System
+ 0015 CML Control, Measurement and Data Logging System
+16df King Billion Electronics Co., Ltd.
+16f0 GN ReSound A/S
+ 0001 Speedlink Programming Interface
+ 0003 Airlink Wireless Programming Interface
+16f5 Futurelogic Inc.
+1706 BlueView Technologies, Inc.
+1707 ARTIMI
+170b Swissonic
+ 0011 MIDI-USB 1x1
+170d Avnera
+1711 Leica Microsystems
+ 0101 DFC-365FX camera
+ 3020 IC80 HD Camera
+1724 Meyer Instruments (MIS)
+ 0115 PAXcam5
+1725 Vitesse Semiconductor
+1726 Axesstel, Inc.
+ 1000 wireless modem
+ 2000 wireless modem
+ 3000 wireless modem
+172f Waltop International Corp.
+ 0022 Tablet
+ 0024 Tablet
+ 0025 Tablet
+ 0026 Tablet
+ 0031 Slim Tablet 12.1"
+ 0032 Slim Tablet 5.8"
+ 0034 Slim Tablet 12.1"
+ 0038 Genius G-Pen F509
+ 0500 Media Tablet 14.1"
+ 0501 Media Tablet 10.6"
+ 0502 Sirius Battery Free Tablet
+1733 Cellink Technology Co., Ltd
+ 0101 RF Wireless Optical Mouse OP-701
+1736 CANON IMAGING SYSTEM TECHNOLOGIES INC.
+1737 Linksys
+ 0039 USB1000 Gigabit Notebook Adapter
+ 0070 WUSB100 v1 RangePlus Wireless Network Adapter [Ralink RT2870]
+ 0071 WUSB600N v1 Dual-Band Wireless-N Network Adapter [Ralink RT2870]
+ 0073 WUSB54GC v2 802.11g Adapter [Realtek RTL8187B]
+ 0075 WUSB54GSC v2 802.11g Adapter [Broadcom 4326U]
+ 0077 WUSB54GC v3 802.11g Adapter [Ralink RT2070L]
+ 0078 WUSB100 v2 RangePlus Wireless Network Adapter [Ralink RT3070]
+ 0079 WUSB600N v2 Dual-Band Wireless-N Network Adapter [Ralink RT3572]
+173d QSENN
+ 0002 GP-K7000 keyboard
+1740 Senao
+ 0100 EUB1200AC AC1200 DB Wireless Adapter [Realtek RTL8812AU]
+ 0600 EUB600v1 802.11abgn Wireless Adapter [Ralink RT3572]
+ 0605 LevelOne WUA-0605 N_Max Wireless USB Adapter
+ 0615 LevelOne WUA-0615 N_Max Wireless USB Adapter
+ 1000 NUB-350 802.11g Wireless Adapter [Intersil ISL3887]
+ 2000 NUB-8301 802.11bg
+ 3701 EUB-3701 EXT 802.11g Wireless Adapter [Ralink RT2571W]
+ 9603 RTL8188S WLAN Adapter
+ 9701 EnGenius 802.11n Wireless USB Adapter
+ 9702 EnGenius 802.11n Wireless USB Adapter
+ 9703 EnGenius 802.11n Wireless USB Adapter
+ 9705 EnGenius 802.11n Wireless USB Adapter
+ 9706 EUB9706 802.11n Wireless Adapter [Ralink RT3072]
+ 9801 EUB9801 802.11abgn Wireless Adapter [Ralink RT3572]
+1743 General Atomics
+1748 MQP Electronics
+ 0101 Packet-Master USB12
+174c ASMedia Technology Inc.
+ 1153 ASM2115 SATA 6Gb/s bridge
+ 2074 ASM1074 High-Speed hub
+ 3074 ASM1074 SuperSpeed hub
+ 5106 ASM1051 SATA 3Gb/s bridge
+ 5136 ASM1053 SATA 6Gb/s bridge
+ 55aa ASM1051E SATA 6Gb/s bridge, ASM1053E SATA 6Gb/s bridge, ASM1153 SATA 3Gb/s bridge
+174f Syntek
+ 1105 SM-MS/Pro-MMC-XD Card Reader
+ 110b HP Webcam
+ 1403 Integrated Webcam
+ 1404 USB Camera device, 1.3 MPixel Web Cam
+ 5212 USB 2.0 UVC PC Camera
+ 5a11 PC Camera
+ 5a31 Sonix USB 2.0 Camera
+ 5a35 Sonix 1.3MPixel USB 2.0 Camera
+ 6a31 Web Cam - Asus A8J, F3S, F5R, VX2S, V1S
+ 6a33 Web Cam - Asus F3SA, F9J, F9S
+ 6a51 2.0MPixel Web Cam - Asus Z96J, Z96S, S96S
+ 6a54 Web Cam
+ 6d51 2.0Mpixel Web Cam - Eurocom D900C
+ 8a12 Syntek 0.3MPixel USB 2.0 UVC PC Camera
+ 8a33 Syntek USB 2.0 UVC PC Camera
+ a311 1.3MPixel Web Cam - Asus A3A, A6J, A6K, A6M, A6R, A6T, A6V, A7T, A7sv, A7U
+ a312 1.3MPixel Web Cam
+ a821 Web Cam - Packard Bell BU45, PB Easynote MX66-208W
+ aa11 Web Cam
+1753 GERTEC Telecomunicacoes Ltda.
+ c901 PPC900 Pinpad Terminal
+1756 ENENSYS Technologies
+ 0006 DiviPitch
+1759 LucidPort Technology, Inc.
+1761 ASUSTek Computer, Inc. (wrong ID)
+ 0b05 802.11n Network Adapter (wrong ID - swapped vendor and device)
+1772 System Level Solutions, Inc.
+1776 Arowana
+ 501c 300K CMOS Camera
+177f Sweex
+ 0004 MM004V5 Photo Key Chain (Digital Photo Frame) 1.5"
+ 0153 LW153 802.11n Adapter [ralink rt3070]
+ 0154 LW154 802.11bgn (1x1:1) Wireless Adapter [Realtek RTL8188SU]
+ 0313 LW313 802.11n Adapter [ralink rt2770 + rt2720]
+1781 Multiple Vendors
+ 083e MetaGeek Wi-Spy
+ 083f MetaGeek Wi-Spy 2.4x
+ 0938 Iguanaworks USB IR Transceiver
+ 0a96 raphnet.net usb_game12
+ 0a97 raphnet.net SNES mouse adapter
+ 0a98 raphnet.net USBTenki
+ 0a99 raphnet.net NES
+ 0a9a raphnet.net Gamecube/N64 controller
+ 0a9b raphnet.net DB9Joy
+ 0a9c raphnet.net Intellivision
+ 0a9d raphnet.net 4nes4snes
+ 0a9e raphnet.net Megadrive multitap
+ 0a9f raphnet.net MultiDB9joy
+ 0c30 Telldus TellStick
+ 0c31 Telldus TellStick Duo
+ 0c9f USBtiny
+ 1eef OpenAPC SecuKey
+ 1ef0 E1701 Modular Controller Card
+ 1ef1 E1701 Modular Controller Card
+1782 Spreadtrum Communications Inc.
+1784 TopSeed Technology Corp.
+ 0001 eHome Infrared Transceiver
+ 0004 RF Combo Device
+ 0006 eHome Infrared Transceiver
+ 0007 eHome Infrared Transceiver
+ 0008 eHome Infrared Transceiver
+ 000a eHome Infrared Transceiver
+ 0011 eHome Infrared Transceiver
+1787 ATI AIB
+1788 ShenZhen Litkconn Technology Co., Ltd.
+1796 Printrex, Inc.
+1797 JALCO CO., LTD.
+1799 Thales Norway A/S
+ 7051 Belkin F5D7051 802.11g Adapter v1000 [Broadcom 4320]
+ 8051 Belkin F5D8051 v2 802.11bgn Wireless Adapter [Marvell 88W8362]
+179d Ricavision International, Inc.
+ 0010 Internal Infrared Transceiver
+17a0 Samson Technologies Corp.
+ 0001 C01U condenser microphone
+ 0002 Q1U dynamic microphone
+ 0100 C03U multi-pattern microphone
+ 0101 UB1 boundary microphone
+ 0120 Meteorite condenser microphone
+ 0200 StudioDock monitors (internal hub)
+ 0201 StudioDock monitors (audio)
+ 0210 StudioGT monitors
+ 0301 Q2U handheld microphone with XLR
+ 0302 GoMic compact condenser microphone
+ 0303 C01U Pro condenser microphone
+ 0304 Q2U handheld mic with XLR
+ 0305 GoMic compact condenser mic
+ 0310 Meteor condenser microphone
+17a4 Concept2
+ 0001 Performance Monitor 3
+ 0002 Performance Monitor 4
+17a5 Advanced Connection Technology Inc.
+17a7 MICOMSOFT CO., LTD.
+17a8 Kamstrup A/S
+ 0001 Optical Eye/3-wire
+ 0005 M-Bus Master MultiPort 250D
+17b3 Grey Innovation
+ 0004 Linux-USB Midi Gadget
+17b5 Lunatone
+ 0010 MFT Sensor
+17ba SAURIS GmbH
+ 0001 SAU510-USB [no firmware]
+ 0510 SAU510-USB and SAU510-USB plus JTAG Emulators
+ 0511 SAU510-USB Iso Plus JTAG Emulator
+ 0520 SAU510-USB Nano JTAG Emulator
+ 1511 Onboard Emulator on SAUModule development kit
+17c3 Singim International Corp.
+17cc Native Instruments
+ 041c Audio 2 DJ
+ 0808 Maschine Controller
+ 0815 Audio Kontrol 1
+ 0839 Audio 4 DJ
+ 0d8d Guitarrig Mobile
+ 1915 Session I/O
+ 1940 RigKontrol3
+ 1969 RigKontrol2
+ 1978 Audio 8 DJ
+ 2280 Medion MDPNA1500 in card reader mode
+ 2305 Traktor Kontrol X1
+ 4711 Kore Controller
+ 4712 Kore Controller 2
+ baff Traktor Kontrol S4
+17cf Hip Hing Cable & Plug Mfy. Ltd.
+17d0 Sanford L.P.
+17d3 Korea Techtron Co., Ltd.
+17e9 DisplayLink
+ 0051 USB VGA Adaptor
+ 030b HP T100
+ 0377 Plugable UD-160-A (M)
+ 0378 Plugable UGA-2K-A
+ 0379 Plugable UGA-125
+ 037a Plugable UGA-165
+ 037b Plugable USB-VGA-165
+ 037c Plugable DC-125
+ 037d Plugable USB2-HDMI-165
+ 410a HDMI Adapter
+ 430a HP Port Replicator (Composite Device)
+ 4312 S2340T
+17eb Cornice, Inc.
+17ef Lenovo
+ 1000 Hub
+ 1003 Integrated Smart Card Reader
+ 1004 Integrated Webcam
+ 1008 Hub
+ 100a ThinkPad Mini Dock Plus Series 3
+ 304b AX88179 Gigabit Ethernet [ThinkPad OneLink GigaLAN]
+ 3815 ChipsBnk 2GB USB Stick
+ 4802 Lenovo Vc0323+MI1310_SOC Camera
+ 4807 UVC Camera
+ 480c Integrated Webcam
+ 480d Integrated Webcam [R5U877]
+ 480e Integrated Webcam [R5U877]
+ 480f Integrated Webcam [R5U877]
+ 4810 Integrated Webcam [R5U877]
+ 4811 Integrated Webcam [R5U877]
+ 4812 Integrated Webcam [R5U877]
+ 4813 Integrated Webcam [R5U877]
+ 4814 Integrated Webcam [R5U877]
+ 4815 Integrated Webcam [R5U877]
+ 4816 Integrated Webcam
+ 481c Integrated Webcam
+ 481d Integrated Webcam
+ 6004 ISD-V4 Tablet Pen
+ 6007 Smartcard Keyboard
+ 6009 ThinkPad Keyboard with TrackPoint
+ 6014 Mini Wireless Keyboard N5901
+ 6025 ThinkPad Travel Mouse
+ 7203 Ethernet adapter [U2L 100P-Y1]
+ 7423 IdeaPad A1 Tablet
+ 7435 A789 (Mass Storage mode, with debug)
+ 743a A789 (Mass Storage mode)
+ 7497 A789 (MTP mode)
+ 7498 A789 (MTP mode, with debug)
+ 749a A789 (PTP mode)
+ 749b A789 (PTP mode, with debug)
+17f4 WaveSense
+ aaaa Jazz Blood Glucose Meter
+17f5 K.K. Rocky
+17f6 Unicomp, Inc
+ 0709 Model M Keyboard
+1809 Advantech
+ 4604 USB-4604
+ 4761 USB-4761 Portable Data Acquisition Module
+1822 Twinhan
+ 3201 VisionDTV USB-Ter/HAMA USB DVB-T device cold
+ 3202 VisionDTV USB-Ter/HAMA USB DVB-T device warm
+1831 Gwo Jinn Industries Co., Ltd.
+1832 Huizhou Shenghua Industrial Co., Ltd.
+183d VIVOphone
+ 0010 VoiceKey
+1843 Vaisala
+1849 ASRock Incorporation
+1852 GYROCOM C&C Co., LTD
+ 7922 Audiotrak DR.DAC2 DX [GYROCOM C&C]
+1854 Memory Devices Ltd.
+185b Compro
+ 3020 K100 Infrared Receiver
+ 3082 K100 Infrared Receiver v2
+ d000 Compro Videomate DVB-U2000 - DVB-T USB cold
+ d001 Compro Videomate DVB-U2000 - DVB-T USB warm
+1861 Tech Technology Industrial Company
+1862 Teridian Semiconductor Corp.
+1870 Nexio Co., Ltd
+ 0001 iNexio Touchscreen controller
+1871 Aveo Technology Corp.
+ 0101 UVC camera (Bresser microscope)
+ 0141 Camera
+ 0d01 USB2.0 Camera
+1873 Navilock
+ ee93 EasyLogger
+187c Alienware Corporation
+ 0511 AlienFX Mobile lighting
+ 0600 Dual Compatible Game Pad
+187f Siano Mobile Silicon
+ 0010 Stallar Board
+ 0100 Stallar Board
+ 0200 Nova A
+ 0201 Nova B
+ 0202 Nice
+ 0300 Vega
+ 0301 VeNice
+1892 Vast Technologies, Inc.
+1894 Topseed
+ 5632 Atek Tote Remote
+ 5641 TSAM-004 Presentation Remote
+1897 Evertop Wire Cable Co.
+189f 3Shape A/S
+ 0002 Legato2 3D Scanner
+18a4 CSSN
+ 0001 Snapshell IDR
+18a5 Verbatim, Ltd
+ 0214 Portable Hard Drive
+ 0216 External Hard Drive
+ 0218 External Hard Drive
+ 0224 Store 'n' Go Micro Plus
+ 0227 Pocket Hard Drive
+ 022b Portable Hard Drive (Store'n'Go)
+ 0237 Portable Harddrive
+ 0243 Flash Drive (Store'n'Go)
+ 0302 Flash Drive
+ 0304 Store 'n' Go
+ 4123 Store N Go
+18b1 Petalynx
+ 0037 Maxter Remote Control
+18b4 e3C Technologies
+ 1001 DUTV007
+ 1002 EC168 (v5) based USB DVB-T receiver
+ 1689 DUTV009
+ fffa EC168 (v2) based USB DVB-T receiver
+ fffb EC168 (v3) based USB DVB-T receiver
+18b6 Mikkon Technology Limited
+18b7 Zotek Electronic Co., Ltd.
+18c5 AMIT Technology, Inc.
+ 0002 CG-WLUSB2GO
+ 0008 CG-WLUSB2GNR Corega Wireless USB Adapter
+ 0012 CG-WLUSB10 Corega Wireless USB Adapter
+18cd Ecamm
+ cafe Pico iMage
+18d1 Google Inc.
+ 0001 Onda V972 (storage access)
+ 0003 Android-powered device using AllWinner Technology SoC
+ 0006 Onda V972 MTP
+ 0008 Onda V972 PTP (camera)
+ 0d02 Celkon A88
+ 2d00 Android-powered device in accessory mode
+ 2d01 Android-powered device in accessory mode with ADB support
+ 4e11 Nexus One
+ 4e12 Nexus One (debug)
+ 4e13 Nexus One (tether)
+ 4e20 Nexus S (fastboot)
+ 4e21 Nexus S
+ 4e22 Nexus S (debug)
+ 4e24 Nexus S (tether)
+ 4e30 Galaxy Nexus (fastboot)
+ 4e40 Nexus 7 (fastboot)
+ 4e41 Nexus 7 (MTP)
+ 4e42 Nexus 7 (debug)
+ 4e43 Nexus 7 (PTP)
+ 4e44 Nexus 7 2012 (PTP)
+ 4ee0 Nexus 4 (bootloader)
+ 4ee1 Nexus Device (MTP)
+ 4ee2 Nexus Device (debug)
+ 4ee3 Nexus 4/5/7/10 (tether)
+ 4ee4 Nexus 4/5/7/10 (debug + tether)
+ 4ee5 Nexus 4 (PTP)
+ 4ee6 Nexus 4/5 (PTP + debug)
+ 7102 Toshiba Thrive tablet
+ b004 Pandigital / B&N Novel 9" tablet
+ d001 Nexus 4 (fastboot)
+ d002 Nexus 4 (debug)
+ d109 LG G2x MTP
+ d10a LG G2x MTP (debug)
+18d5 Starline International Group Limited
+18d9 Kaba
+ 01a0 B-Net 91 07
+18dc LKC Technologies, Inc.
+18dd Planon System Solutions Inc.
+ 1000 DocuPen RC800
+18e3 Fitipower Integrated Technology Inc
+ 7102 Multi Card Reader (Internal)
+ 9101 All-in-1 Card Reader
+ 9102 Multi Card Reader
+ 9512 Webcam
+18e8 Qcom
+ 6144 LR802UA 802.11b Wireless Adapter [ALi M4301AU]
+ 6196 RT2573
+ 6229 RT2573
+ 6232 Wireless 802.11g 54Mbps Network Adapter [RTL8187]
+18ea Matrox Graphics, Inc.
+ 0002 DualHead2Go [Analog Edition]
+ 0004 TripleHead2Go [Digital Edition]
+18ec Arkmicro Technologies Inc.
+ 3118 USB to IrDA adapter [ARK3116T]
+ 3188 ARK3188 UVC Webcam
+ 3299 Webcam Carrefour
+ 3366 Bresser Biolux NV
+18f8 [Maxxter]
+ 0f99 Optical gaming mouse
+18fb Scriptel Corporation
+ 01c0 ST1501-STN
+ 01c1 ST1526-STN
+ 01c2 ST1501-PYJ
+ 01c3 ST1501B-PYJ
+ 01c4 ST1501-PUN
+ 01c5 ST1401-STN
+ 01c7 ST1526-PYJ
+ 01c8 ST1501-ECA
+ 01c9 ST1476-STN
+ 01cb ST1571-STN
+ 0200 ST1500
+ 0201 ST1550
+ 0202 ST1525
+ 0204 ST1400
+ 0206 ST1475
+ 0207 ST1570
+18fd FineArch Inc.
+1901 GE Healthcare
+ 0015 Nemo Tracker
+1908 GEMBIRD
+ 1320 PhotoFrame PF-15-1
+190d Motorola GSG
+1914 Alco Digital Devices Limited
+1915 Nordic Semiconductor ASA
+ 000c Wireless Desktop nRF24L01 CX-1766
+ 2233 Linksys WUSB11 v2.8 802.11b Adapter [Atmel AT76C505]
+ 2234 Linksys WUSB54G v1 OEM 802.11g Adapter [Intersil ISL3886]
+ 2235 Linksys WUSB54GP v1 OEM 802.11g Adapter [Intersil ISL3886]
+ 2236 Linksys WUSB11 v3.0 802.11b Adapter [Intersil PRISM 3]
+191c Innovative Technology LTD
+ 4104 Banknote validator NV-150
+1923 FitLinxx
+ 0002 Personal SyncPoint
+1926 NextWindow
+ 0003 1900 HID Touchscreen
+ 0006 1950 HID Touchscreen
+ 0064 1950 HID Touchscreen
+ 0065 1950 HID Touchscreen
+ 0066 1950 HID Touchscreen
+ 0067 1950 HID Touchscreen
+ 0068 1950 HID Touchscreen
+ 0069 1950 HID Touchscreen
+ 0071 1950 HID Touchscreen
+ 0072 1950 HID Touchscreen
+ 0073 1950 HID Touchscreen
+ 0074 1950 HID Touchscreen
+ 0075 1950 HID Touchscreen
+ 0076 1950 HID Touchscreen
+ 0077 1950 HID Touchscreen
+ 0078 1950 HID Touchscreen
+ 0079 1950 HID Touchscreen
+ 007a 1950 HID Touchscreen
+ 007e 1950 HID Touchscreen
+ 007f 1950 HID Touchscreen
+ 0080 1950 HID Touchscreen
+ 0081 1950 HID Touchscreen
+ 0082 1950 HID Touchscreen
+ 0083 1950 HID Touchscreen
+ 0084 1950 HID Touchscreen
+ 0085 1950 HID Touchscreen
+ 0086 1950 HID Touchscreen
+ 0087 1950 HID Touchscreen
+ 0dc2 HID Touchscreen
+192f Avago Technologies, Pte.
+ 0000 Mouse
+ 0416 ADNS-5700 Optical Mouse Controller (3-button)
+ 0616 ADNS-5700 Optical Mouse Controller (5-button)
+1930 Shenzhen Xianhe Technology Co., Ltd.
+1931 Ningbo Broad Telecommunication Co., Ltd.
+1934 Feature Integration Technology Inc. (Fintek)
+ 0602 F71610 or F71612 Consumer Infrared Receiver/Transceiver
+ 0702 Integrated Consumer Infrared Receiver/Transceiver
+ 5168 F71610A or F71612A Consumer Infrared Receiver/Transceiver
+1938 Meinberg Funkuhren GmbH & Co. KG
+ 0501 TCR51USB IRIG Time Code Reader
+1941 Dream Link
+ 8021 WH1080 Weather Station / USB Missile Launcher
+1943 Sensoray Co., Inc.
+ 2250 Model 2250 MPEG and JPEG Capture Card
+ 2253 Model 2253 Audio/Video Codec Card
+ 2255 Model 2255 4 Channel Capture Card
+ 2257 Model 2257 4 Channel Capture Card
+ a250 Model 2250 MPEG and JPEG Capture Card (cold)
+ a253 Model 2253 Audio/Video Codec Card (cold)
+1949 Lab126, Inc.
+ 0002 Amazon Kindle
+ 0004 Amazon Kindle 3/4/Paperwhite
+ 0006 Kindle Fire
+ 0008 Amazon Kindle Fire HD 8.9"
+194f PreSonus Audio Electronics, Inc.
+ 0101 AudioBox 22 VSL
+ 0102 AudioBox 44 VSL
+ 0103 AudioBox 1818 VSL
+ 0301 AudioBox
+1951 Hyperstone AG
+1953 Ironkey Inc.
+ 0202 S200 2GB Rev. 1
+1954 Radiient Technologies
+195d Itron Technology iONE
+ 7002 Libra-Q11 IR remote
+ 7006 Libra-Q26 / 1.0 Remote
+ 7777 Scorpius wireless keyboard
+ 7779 Scorpius-P20MT
+1965 Uniden Corporation
+ 0016 HomePatrol-1
+1967 CASIO HITACHI Mobile Communications Co., Ltd.
+196b Wispro Technology Inc.
+1970 Dane-Elec Corp. USA
+ 0000 Z Mate 16GB
+1975 Dongguan Guneetal Wire & Cable Co., Ltd.
+1976 Chipsbrand Microelectronics (HK) Co., Ltd.
+ 6025 Flash Drive 512 MB
+1977 T-Logic
+ 0111 TL203 MP3 Player and Voice Recorder
+197d Leuze electronic
+ 0222 BCL 508i
+1989 Nuconn Technology Corp.
+198f Beceem Communications Inc.
+ 0210 BCS200 WiMAX Adapter
+ 0220 BCSM250 WiMAX Adapter
+1990 Acron Precision Industrial Co., Ltd.
+1995 Trillium Technology Pty. Ltd.
+ 3202 REC-ADPT-USB (recorder)
+ 3203 REC-A-ADPT-USB (recorder)
+1996 PixeLINK
+ 3010 Camera Release 4
+ 3011 OEM Camera
+ 3012 e-ImageData Corp. ScanPro
+199b MicroStrain, Inc.
+ 3065 3DM-GX3-25 Orientation Sensor
+199e The Imaging Source Europe GmbH
+ 8101 DFx 21BU04 Camera
+199f Benica Corporation
+19a8 Biforst Technology Inc.
+19ab Bodelin
+ 1000 ProScope HR
+19af S Life
+ 6611 Celestia VoIP Phone
+19b2 Batronix
+ 0010 BX32 Batupo
+ 0011 BX32P Barlino
+ 0012 BX40 Bagero
+ 0013 BX48 Batego
+19b4 Celestron
+ 0002 SkyScout Personal Planetarium
+ 0101 Handheld Digital Microscope 44302
+19b5 B & W Group
+19b6 Infotech Logistic, LLC
+19b9 Data Robotics
+ 8d20 Drobo Elite
+19c2 Futuba
+ 6a11 MDM166A Fluorescent Display
+19ca Mindtribe
+ 0001 Sandio 3D HID Mouse
+19cf Parrot SA
+19d2 ZTE WCDMA Technologies MSM
+ 0001 CDMA Wireless Modem
+ 0002 MF632/ONDA ET502HS/MT505UP
+ 0007 TU25 WiMAX Adapter [Beceem BCS200]
+ 0031 MF110/MF627/MF636
+ 0063 K3565-Z HSDPA
+ 0064 MF627 AU
+ 0083 MF190
+ 0103 MF112
+ 0104 K4505-Z
+ 0146 MF 195E (HSPA+ Modem)
+ 0167 MF820 4G LTE
+ 0172 AX226 WIMAX MODEM (After Modeswitch)
+ 0325 LTE4G O2 ZTE MF821D LTE/UMTS/GSM Modem/Networkcard
+ 0326 LTE4G O2 ZTE MF821D LTE/UMTS/GSM Modem/Networkcard
+ 1008 K3570-Z
+ 1010 K3571-Z
+ 1017 K5006-Z vodafone LTE/UMTS/GSM Modem/Networkcard
+ 1018 K5006-Z vodafone LTE/UMTS/GSM Modem/Networkcard
+ 1203 MF691 [ T-Mobile webConnect Rocket 2.0]
+ 1217 MF652
+ 1218 MF652
+ 2000 MF627/MF628/MF628+/MF636+ HSDPA/HSUPA
+ fff2 Gobi Wireless Modem (QDL mode)
+ fff3 Gobi Wireless Modem
+19db KFI Printers
+ 02f1 NAUT324C
+19e1 WeiDuan Electronic Accessory (S.Z.) Co., Ltd.
+19e8 Industrial Technology Research Institute
+19ef Pak Heng Technology (Shenzhen) Co., Ltd.
+19f7 RODE Microphones
+ 0001 Podcaster
+19fa Gampaq Co.Ltd
+ 0703 Steering Wheel
+19ff Dynex
+ 0102 1.3MP Webcam
+ 0201 Rocketfish Wireless 2.4G Laser Mouse
+ 0238 DX-WRM1401 Mouse
+1a08 Bellwood International, Inc.
+1a0a USB-IF non-workshop
+ badd USB OTG Compliance test device
+1a12 KES Co., Ltd.
+1a1d Veho
+ 0407 Mimi WiFi speakers
+1a25 Amphenol East Asia Ltd.
+1a2a Seagate Branded Solutions
+1a2c China Resource Semico Co., Ltd
+ 0021 Keyboard
+ 0024 Multimedia Keyboard
+1a32 Quanta Microsystems, Inc.
+ 0304 802.11n Wireless LAN Card
+1a34 ACRUX
+ 0802 Gamepad
+1a36 Biwin Technology Ltd.
+1a40 Terminus Technology Inc.
+ 0101 Hub
+ 0201 FE 2.1 7-port Hub
+1a41 Action Electronics Co., Ltd.
+1a44 VASCO Data Security International
+ 0001 Digipass 905 SmartCard Reader
+1a4a Silicon Image
+1a4b SafeBoot International B.V.
+1a5a Tandberg Data
+1a61 Abbott Diabetes Care
+ 3410 CoPilot System Cable
+1a6a Spansion Inc.
+1a6d SamYoung Electronics Co., Ltd
+1a6e Global Unichip Corp.
+1a6f Sagem Orga GmbH
+1a72 Physik Instrumente
+ 1008 E-861 PiezoWalk NEXACT Controller
+1a79 Bayer Health Care LLC
+ 6002 Contour
+ 7410 Contour Next
+1a7b Lumberg Connect GmbH & Co. KG
+1a7c Evoluent
+ 0068 VerticalMouse 3
+ 0168 VerticalMouse 3 Wireless
+ 0191 VerticalMouse 4
+1a81 Holtek Semiconductor, Inc.
+ 2203 Laser Gaming mouse
+ 2204 Optical Mouse
+ 2205 Laser Mouse
+1a86 QinHeng Electronics
+ 5512 CH341 in EPP/MEM/I2C mode, EPP/I2C adapter
+ 5523 CH341 in serial mode, usb to serial port converter
+ 5584 CH341 in parallel mode, usb to printer port converter
+ 7523 HL-340 USB-Serial adapter
+ 752d CH345 MIDI adapter
+ 7584 CH340S
+ e008 HID-based serial adapater
+1a89 Dynalith Systems Co., Ltd.
+1a8b SGS Taiwan Ltd.
+1a8d BandRich, Inc.
+ 1002 BandLuxe 3.5G HSDPA Adapter
+ 1009 BandLuxe 3.5G HSPA Adapter
+ 100d 4G LTE adapter
+1a98 Leica Camera AG
+1aa4 Data Drive Thru, Inc.
+1aa5 UBeacon Technologies, Inc.
+1aa6 eFortune Technology Corp.
+1aad KeeTouch
+ 0001 Touchscreen
+1ab1 Rigol Technologies
+ 0588 DS1000 SERIES
+1acb Salcomp Plc
+1acc Midiplus Co, Ltd.
+ 0103 AudioLink plus 4x4 2.9.28
+1ad1 Desay Wire Co., Ltd.
+1ad4 APS
+ 0002 KM290-HRS
+1adb SEL C662 Serial Cable
+1ae4 ic-design Reinhard Gottinger GmbH
+1ae7 X-TENSIONS
+ 0381 VS-DVB-T 380U (af9015 based)
+ 2001 SpeedLink Snappy Mic webcam (SL-6825-SBK)
+ 9003 SpeedLink Vicious And Devine Laplace webcam, white (VD-1504-SWT)
+ 9004 SpeedLink Vicious And Devine Laplace webcam, black (VD-1504-SBK)
+1aed High Top Precision Electronic Co., Ltd.
+1aef Conntech Electronic (Suzhou) Corporation
+1af1 Connect One Ltd.
+1afe A. Eberle GmbH & Co. KG
+ 0001 PQ Box 100
+1b04 Meilhaus Electronic GmbH
+ 0630 ME-630
+ 0940 ME-94
+ 0950 ME-95
+ 0960 ME-96
+ 1000 ME-1000
+ 100a ME-1000
+ 100b ME-1000
+ 1400 ME-1400
+ 140a ME-1400A
+ 140b ME-1400B
+ 140c ME-1400C
+ 140d ME-1400D
+ 140e ME-1400E
+ 14ea ME-1400EA
+ 14eb ME-1400EB
+ 1604 ME-1600/4U
+ 1608 ME-1600/8U
+ 160c ME-1600/12U
+ 160f ME-1600/16U
+ 168f ME-1600/16U8I
+ 4610 ME-4610
+ 4650 ME-4650
+ 4660 ME-4660
+ 4661 ME-4660I
+ 4662 ME-4660
+ 4663 ME-4660I
+ 4670 ME-4670
+ 4671 ME-4670I
+ 4672 ME-4670S
+ 4673 ME-4670IS
+ 4680 ME-4680
+ 4681 ME-4680I
+ 4682 ME-4680S
+ 4683 ME-4680IS
+ 6004 ME-6000/4
+ 6008 ME-6000/8
+ 600f ME-6000/16
+ 6014 ME-6000I/4
+ 6018 ME-6000I/8
+ 601f ME-6000I/16
+ 6034 ME-6000ISLE/4
+ 6038 ME-6000ISLE/8
+ 603f ME-6000ISLE/16
+ 6044 ME-6000/4/DIO
+ 6048 ME-6000/8/DIO
+ 604f ME-6000/16/DIO
+ 6054 ME-6000I/4/DIO
+ 6058 ME-6000I/8/DIO
+ 605f ME-6000I/16/DIO
+ 6074 ME-6000ISLE/4/DIO
+ 6078 ME-6000ISLE/8/DIO
+ 607f ME-6000ISLE/16/DIO
+ 6104 ME-6100/4
+ 6108 ME-6100/8
+ 610f ME-6100/16
+ 6114 ME-6100I/4
+ 6118 ME-6100I/8
+ 611f ME-6100I/16
+ 6134 ME-6100ISLE/4
+ 6138 ME-6100ISLE/8
+ 613f ME-6100ISLE/16
+ 6144 ME-6100/4/DIO
+ 6148 ME-6100/8/DIO
+ 614f ME-6100/16/DIO
+ 6154 ME-6100I/4/DIO
+ 6158 ME-6100I/8/DIO
+ 615f ME-6100I/16/DIO
+ 6174 ME-6100ISLE/4/DIO
+ 6178 ME-6100ISLE/8/DIO
+ 617f ME-6100ISLE/16/DIO
+ 6259 ME-6200I/9/DIO
+ 6359 ME-6300I/9/DIO
+ 810a ME-8100A
+ 810b ME-8100B
+ 820a ME-8200A
+ 820b ME-8200B
+1b0e BLUTRONICS S.r.l.
+ 1078 BLUDRIVE II CCID
+ 1079 BLUDRIVE II CCID
+ 1080 WRITECHIP II CCID
+1b1c Corsair
+ 0890 Flash Padlock
+ 0a00 SP2500 Speakers
+ 0a60 Vengeance K60 Keyboard
+ 0c04 Link Cooling Node
+ 1a01 Flash Voyager GT
+ 1a03 Voyager 3.0
+ 1a09 Voyager GT 3.0
+ 1a0a Survivor Stealth Flash Drive
+ 1a0b Flash Voyager LS
+ 1a15 Voyager Slider Flash Drive
+ 1a90 Flash Voyager GT
+ 1ab1 Voyager
+ 1b04 Raptor K50 Keyboard
+ 1b07 Vengeance K65 Gaming Keyboard
+ 1b08 Vengeance K95 Keyboard
+ 1b09 Vengeance K70R keyboard
+ 1b11 K95 RGB Mechanical Gaming Keyboard
+ 1b13 Vengeance K70RGB keyboard
+ 1c00 Controller for Corsair Link
+ 1c0c RM850i Power Supply
+1b1f eQ-3 Entwicklung GmbH
+ c00f HM-CFG-USB/HM-CFG-USB-2 [HomeMatic Configuration adapter]
+1b20 MStar Semiconductor, Inc.
+1b22 WiLinx Corp.
+1b26 Cellex Power Products, Inc.
+1b27 Current Electronics Inc.
+1b28 NAVIsis Inc.
+1b32 Ugobe Life Forms, Inc.
+ 0064 Pleo robotic dinosaur
+1b36 ViXS Systems, Inc.
+1b3b iPassion Technology Inc.
+ 2933 PC Camera/Webcam controller
+ 2935 PC Camera/Webcam controller
+ 2936 PC Camera/Webcam controller
+ 2937 PC Camera/Webcam controller
+ 2938 PC Camera/Webcam controller
+ 2939 PC Camera/Webcam controller
+ 2950 PC Camera/Webcam controller
+ 2951 PC Camera/Webcam controller
+ 2952 PC Camera/Webcam controller
+ 2953 PC Camera/Webcam controller
+ 2955 PC Camera/Webcam controller
+ 2956 PC Camera/Webcam controller
+ 2957 PC Camera/Webcam controller
+ 2958 PC Camera/Webcam controller
+ 2959 PC Camera/Webcam controller
+ 2960 PC Camera/Webcam controller
+ 2961 PC Camera/Webcam controller
+ 2962 PC Camera/Webcam controller
+ 2963 PC Camera/Webcam controller
+ 2965 PC Camera/Webcam controller
+ 2966 PC Camera/Webcam controller
+ 2967 PC Camera/Webcam controller
+ 2968 PC Camera/Webcam controller
+ 2969 PC Camera/Webcam controller
+1b3f Generalplus Technology Inc.
+ 0c52 808 Camera #9 (mass storage mode)
+ 2002 808 Camera #9 (web-cam mode)
+1b47 Energizer Holdings, Inc.
+ 0001 CHUSB Duo Charger (NiMH AA/AAA USB smart charger)
+1b48 Plastron Precision Co., Ltd.
+1b52 ARH Inc.
+ 2101 FXMC Neural Network Controller
+ 2102 FXMC Neural Network Controller V2
+ 2103 FXMC Neural Network Controller V3
+ 4101 Passport Reader CLR device
+ 4201 Passport Reader PRM device
+ 4202 Passport Reader PRM extension device
+ 4203 Passport Reader PRM DSP device
+ 4204 Passport Reader PRMC device
+ 4205 Passport Reader CSHR device
+ 4206 Passport Reader PRMC V2 device
+ 4301 Passport Reader MRZ device
+ 4302 Passport Reader MRZ DSP device
+ 4303 Passport Reader CSLR device
+ 4401 Card Reader
+ 4501 Passport Reader RFID device
+ 4502 Passport Reader RFID AIG device
+ 6101 Neural Network Controller
+ 6202 Fingerprint Reader device
+ 6203 Fingerprint Scanner device
+ 8101 Camera V1
+ 8102 Recovery / Camera V2
+ 8103 Camera V3
+1b59 K.S. Terminals Inc.
+1b5a Chao Zhou Kai Yuan Electric Co., Ltd.
+1b65 The Hong Kong Standards and Testing Centre Ltd.
+1b71 Fushicai
+ 3002 USBTV007 Video Grabber [EasyCAP]
+1b72 ATERGI TECHNOLOGY CO., LTD.
+1b73 Fresco Logic
+ 1000 xHC1 Controller
+1b75 Ovislink Corp.
+ 3072 AirLive WN-360USB adapter
+ 8171 WN-370USB 802.11bgn Wireless Adapter [Realtek RTL8188SU]
+ 8187 AirLive WL-1600USB 802.11g Adapter [Realtek RTL8187L]
+ 9170 AirLive X.USB 802.11abgn [Atheros AR9170+AR9104]
+ a200 AirLive WN-200USB wireless 11b/g/n dongle
+1b76 Legend Silicon Corp.
+1b80 Afatech
+ c810 MC810 [af9015]
+ d393 DVB-T receiver [RTL2832U]
+ d396 UB396-T [RTL2832U]
+ d397 DVB-T receiver [RTL2832U]
+ d398 DVB-T receiver [RTL2832U]
+ d700 FM Radio SnapMusic Mobile 700 (FM700)
+ e297 Conceptronic DVB-T CTVDIGRCU V3.0
+ e383 DVB-T UB383-T [af9015]
+ e385 DVB-T UB385-T [af9015]
+ e386 DVB-T UB385-T [af9015]
+ e399 DVB-T KWorld PlusTV 399U [af9015]
+ e39a DVB-T395U [af9015]
+ e39b DVB-T395U [af9015]
+ e401 Sveon STV22 DVB-T [af9015]
+ e409 IT9137FN Dual DVB-T [KWorld UB499-2T]
+1b86 Dongguan Guanshang Electronics Co., Ltd.
+1b88 ShenMing Electron (Dong Guan) Co., Ltd.
+1b8c Altium Limited
+1b8d e-MOVE Technology Co., Ltd.
+1b8e Amlogic, Inc.
+1b8f MA LABS, Inc.
+1b96 N-Trig
+ 0001 Duosense Transparent Electromagnetic Digitizer
+1b98 YMax Communications Corp.
+1b99 Shenzhen Yuanchuan Electronic
+1ba1 JINQ CHERN ENTERPRISE CO., LTD.
+1ba2 Lite Metals & Plastic (Shenzhen) Co., Ltd.
+1ba4 Ember Corporation
+ 0001 InSight USB Link
+1ba6 Abilis Systems
+1ba8 China Telecommunication Technology Labs
+1bad Harmonix Music
+ 0002 Guitar for Xbox 360
+ 0003 Drum Kit for Xbox 360
+1bae Vuzix Corporation
+ 0002 VR920 Immersive Eyewear
+1bbb T & A Mobile Phones
+ 011e Alcatel One Touch L100V / Telekom Speedstick LTE II
+ f017 Alcatel One Touch L100V / Telekom Speedstick LTE II
+1bc4 Ford Motor Co.
+1bc5 AVIXE Technology (China) Ltd.
+1bc7 Telit Wireless Solutions
+ 0020 HE863
+ 0021 HE910
+ 0023 HE910-D ECM
+ 1003 UC864-E
+ 1004 UC864-G
+ 1005 CC864-DUAL
+ 1006 CC864-SINGLE
+ 1010 DE910-DUAL
+ 1011 CE910-DUAL
+ 1200 LE920
+1bce Contac Cable Industrial Limited
+1bcf Sunplus Innovation Technology Inc.
+ 0005 Optical Mouse
+ 0007 Optical Mouse
+ 053a Targa Silvercrest OMC807-C optische Funkmaus
+ 05c5 SPRF2413A [2.4GHz Wireless Keyboard/Mouse Receiver]
+ 05cf Micro keyboard & mouse receiver
+ 0c31 SPIF30x Serial-ATA bridge
+ 2880 Dell HD Webcam
+ 2885 ASUS Webcam
+ 2888 HP Universal Camera
+ 28a2 Dell Integrated Webcam
+ 28a6 DELL XPS Integrated Webcam
+ 28ae Laptop Integrated Webcam HD
+ 28bd Dell Integrated HD Webcam
+ 2985 Laptop Integrated Webcam HD
+ 2b83 Laptop Integrated Webcam FHD
+1bd0 Hangzhou Riyue Electronic Co., Ltd.
+1bd5 BG Systems, Inc.
+1bde P-TWO INDUSTRIES, INC.
+1bef Shenzhen Tongyuan Network-Communication Cables Co., Ltd
+1bf0 RealVision Inc.
+1bf5 Extranet Systems Inc.
+1bf6 Orient Semiconductor Electronics, Ltd.
+1bfd TouchPack
+ 1268 Touch Screen
+ 1368 Touch Screen
+ 1568 Capacitive Touch Screen
+ 1668 IR Touch Screen
+ 1688 Resistive Touch Screen
+ 2968 Touch Screen
+ 5968 Touch Screen
+ 6968 Touch Screen
+1c02 Kreton Corporation
+1c04 QNAP System Inc.
+1c0c Ionics EMS, Inc.
+ 0102 Plug Computer
+1c0d Relm Wireless
+1c10 Lanterra Industrial Co., Ltd.
+1c13 ALECTRONIC LIMITED
+1c1a Datel Electronics Ltd.
+1c1b Volkswagen of America, Inc.
+1c1f Goldvish S.A.
+1c20 Fuji Electric Device Technology Co., Ltd.
+1c21 ADDMM LLC
+1c22 ZHONGSHAN CHIANG YU ELECTRIC CO., LTD.
+1c26 Shanghai Haiying Electronics Co., Ltd.
+1c27 HuiYang D & S Cable Co., Ltd.
+1c29 Elster GmbH
+ 0001 ExMFE5 Simulator
+ 10fc enCore device
+1c31 LS Cable Ltd.
+1c34 SpringCard
+ 7241 Prox'N'Roll RFID Scanner
+1c37 Authorizer Technologies, Inc.
+1c3d NONIN MEDICAL INC.
+1c3e Wep Peripherals
+1c40 EZPrototypes
+ 0533 TiltStick
+ 0534 i2c-tiny-usb interface
+ 0535 glcd2usb interface
+ 0536 Swiss ColorPAL
+1c49 Cherng Weei Technology Corp.
+1c4f SiGma Micro
+ 0002 Keyboard TRACER Gamma Ivory
+ 0003 HID controller
+ 000e Genius KB-120 Keyboard
+ 0026 Keyboard
+ 3000 Micro USB Web Camera
+ 3002 WebCam SiGma Micro
+1c6b Philips & Lite-ON Digital Solutions Corporation
+ a222 DVD Writer Slimtype eTAU108
+1c6c Skydigital Inc.
+1c73 AMT
+ 861f Anysee E30 USB 2.0 DVB-T Receiver
+1c77 Kaetat Industrial Co., Ltd.
+1c78 Datascope Corp.
+1c79 Unigen Corporation
+1c7a LighTuning Technology Inc.
+ 0801 Fingerprint Reader
+1c7b LUXSHARE PRECISION INDUSTRY (SHENZHEN) CO., LTD.
+1c83 Schomaecker GmbH
+ 0001 RS150 V2
+1c87 2N TELEKOMUNIKACE a.s.
+1c88 Somagic, Inc.
+ 0007 SMI Grabber (EasyCAP DC60+ clone) (no firmware) [SMI-2021CBE]
+ 003c SMI Grabber (EasyCAP DC60+ clone) [SMI-2021CBE]
+1c89 HONGKONG WEIDIDA ELECTRON LIMITED
+1c8e ASTRON INTERNATIONAL CORP.
+1c98 ALPINE ELECTRONICS, INC.
+1c9e OMEGA TECHNOLOGY
+ 6061 WL-72B 3.5G MODEM
+1ca0 ACCARIO Inc.
+1ca1 Symwave
+ 18ab SATA bridge
+1cac Kinstone
+ a332 C8 Webcam
+ b288 C18 Webcam
+1cb3 Aces Electronic Co., Ltd.
+1cb4 OPEX CORPORATION
+1cb6 IdeaCom Technology Inc.
+ 6681 IDC6681
+1cbe Luminary Micro Inc.
+ 00fd In-Circuit Debug Interface
+ 00ff Stellaris ROM DFU Bootloader
+ 0166 CANAL USB2CAN
+1cbf FORTAT SKYMARK INDUSTRIAL COMPANY
+1cc0 PlantSense
+1cca NextWave Broadband Inc.
+1ccd Bodatong Technology (Shenzhen) Co., Ltd.
+1cd4 adp corporation
+1cd5 Firecomms Ltd.
+1cd6 Antonio Precise Products Manufactory Ltd.
+1cde Telecommunications Technology Association (TTA)
+1cdf WonTen Technology Co., Ltd.
+1ce0 EDIMAX TECHNOLOGY CO., LTD.
+1ce1 Amphenol KAE
+1cf1 Dresden Elektronik
+ 0001 Sensor Terminal Board
+ 0004 Wireless Handheld Terminal
+ 0017 deRFusbSniffer 2.4 GHz
+ 0018 deRFusb24E001
+ 0019 deRFusb14E001
+ 001a deRFusb23E00
+ 001b deRFusb13E00
+ 001c deRFnode
+ 001d deRFnode / gateway
+ 0022 deUSB level shifter
+ 0023 deRFusbSniffer Sub-GHz
+ 0025 deRFusb23E06
+ 0027 deRFusb13E06
+1cfc ANDES TECHNOLOGY CORPORATION
+1cfd Flextronics Digital Design Japan, LTD.
+1d03 iCON
+ 0028 iCreativ MIDI Controller
+1d07 Solid-Motion
+1d08 NINGBO HENTEK DRAGON ELECTRONICS CO., LTD.
+1d09 TechFaith Wireless Technology Limited
+ 1026 HSUPA Modem FLYING-LARK46-VER0.07 [Flying Angel]
+1d0a Johnson Controls, Inc. The Automotive Business Unit
+1d0b HAN HUA CABLE & WIRE TECHNOLOGY (J.X.) CO., LTD.
+1d0f Sonix Technology Co., Ltd.
+1d14 ALPHA-SAT TECHNOLOGY LIMITED
+1d17 C-Thru Music Ltd.
+ 0001 AXiS-49 Harmonic Table MIDI Keyboard
+1d19 Dexatek Technology Ltd.
+ 1101 DK DVB-T Dongle
+ 1102 DK mini DVB-T Dongle
+ 1103 DK 5217 DVB-T Dongle
+ 6105 Video grabber
+ 8202 DK DVBC/T DONGLE
+1d1f Diostech Co., Ltd.
+1d20 SAMTACK INC.
+1d27 ASUS
+1d34 Dream Cheeky
+ 0001 Dream Cheeky Fidget
+ 0004 Dream Cheeky Webmail Notifier
+ 0008 Dream Cheeky button
+ 000a Dream Cheeky Mailbox Friends Alert
+ 000d Dream Cheeky Big Red Button
+ 0013 Dream Cheeky LED Message Board
+1d45 Touch
+ 1d45 Foxlink Optical touch sensor
+1d4d PEGATRON CORPORATION
+ 0002 Ralink RT2770/2720 802.11b/g/n Wireless LAN Mini-USB Device
+ 000c Ralink RT3070 802.11b/g/n Wireless Lan USB Device
+ 000e Ralink RT3070 802.11b/g/n Wireless Lan USB Device
+1d50 OpenMoko, Inc.
+ 1db5 IDBG (DFU)
+ 1db6 IDBG
+ 5117 Neo1973/FreeRunner kernel usbnet (g_ether, CDC Ethernet) mode
+ 5118 Neo1973/FreeRunner Debug board (V2+)
+ 5119 Neo1973/FreeRunner u-boot cdc_acm serial port
+ 511a HXD8 u-boot usbtty CDC ACM Mode
+ 511b SMDK2440 u-boot usbtty CDC ACM mode
+ 511c SMDK2443 u-boot usbtty CDC ACM mode
+ 511d QT2410 u-boot usbtty CDC ACM mode
+ 5120 Neo1973/FreeRunner u-boot usbtty generic serial
+ 5121 Neo1973/FreeRunner kernel mass storage (g_storage) mode
+ 5122 Neo1973/FreeRunner kernel cdc_ether USB network
+ 5123 Neo1973/FreeRunner internal USB CSR4 module
+ 5124 Neo1973/FreeRunner Bluetooth Device ID service
+ 5300 Rockbox
+ 6000 Ubertooth Zero
+ 6001 Ubertooth Zero (DFU)
+ 6002 Ubertooth One
+ 6003 Ubertooth One (DFU)
+ 6004 LeoLipo
+ 6005 LED Flower S
+ 6006 LED Cube
+ 6007 LED Flower
+ 6008 Kisbee 802.15.4 transceiver
+ 6009 Adjacent Reality Tracker
+ 600a AVR Programmer
+ 600b Hypna Go Go
+ 600c CatNip LPC1343 development board
+ 600d Enhanced RoboBrrd Brain board
+ 600e OpenRISC Ordb2a-ep4ce22 development board
+ 600f Paparazzi Lisa/M (DFU)
+ 6010 OpenPipe: OSHW Bagpipes MIDI controller
+ 6011 LeoLipo (DFU)
+ 6012 Universal C64 Cartridge
+ 6013 DiscFerret magnetic disc analyser (bootloader)
+ 6014 DiscFerret magnetic disc analyser
+ 6015 Smoothieboard
+ 6016 phInterface
+ 6017 Black Magic Debug Probe (DFU)
+ 6018 Black Magic Debug Probe (Application)
+ 6019 4pi 5 axis motion controller
+ 601a Paparazzi Lisa/M
+ 601b IST-2 chronograph for bullet speeds
+ 601c EPOSMote II
+ 601e 5x5 STM32 prototyping board
+ 601f uNSF
+ 6020 Toad3
+ 6021 AlphaSphere
+ 6022 LightPack
+ 6023 Pixelkit
+ 6024 Illucia
+ 6025 Keyglove (HID)
+ 6027 Key64 Keyboard
+ 6028 Teensy 2.0 Development Board [ErgoDox Keyboard]
+ 602a Marlin 2.0 (Mass Storage)
+ 602b FPGALink
+ 602c 5nes5snes (5x8)
+ 602d 5nes5snes (4x12)
+ 602e Flexibity
+ 602f K-copter
+ 6030 USB-oscope
+ 6031 Handmade GSM GPS tracker
+ 6033 frobiac / adnw keyboard
+ 6034 Tiflomag Ergo 2
+ 6035 FreeLaserTag Gun
+ 6036 FreeLaserTag Big Brother
+ 6037 FreeLaserTag Node
+ 6038 Monaka
+ 6039 eXtreme Feedback Device
+ 603a TiLDA
+ 603b Raspiface
+ 603c Paparazzi (bootloader)
+ 603d Paparazzi (Serial)
+ 603e Paparazzi (Mass Storage)
+ 603f airGuitar
+ 6040 moco
+ 6041 AlphaSphere (bootloader)
+ 6042 Dspace robot controller
+ 6043 pc-power
+ 6044 open-usb-can (DFU)
+ 6045 open-usb-can
+ 6046 mimus-weigand
+ 6047 RfCat Chronos Dongle
+ 6048 RfCat Dons Dongle
+ 6049 RfCat Chronos bootloader
+ 604a RfCat Dons bootloader
+ 604b HackRF Jawbreaker Software-Defined Radio
+ 604c Makibox A6
+ 604d Paella Pulse height analyzer
+ 604e Miniscope v2b
+ 604f Miniscope v2c
+ 6050 GoodFET
+ 6051 pinocc.io
+ 6052 APB Team Robotic Development Board
+ 6053 Darkgame Controller
+ 6054 Satlab/AAUSAT3 BlueBox
+ 6056 The Glitch
+ 605b RfCat YARD Stick One
+ 605c YARD Stick One bootloader
+ 605d Funky Sensor v2
+ 605e Blinkiverse Analog LED Fader
+ 605f Small DIP package Cypress FX2
+ 6060 Data logger using the Cypress FX2
+ 6061 Power Manager
+ 6063 CPC FPGA
+ 6064 CPC FPGA (DFU)
+ 6065 CPC FPGA (Serial)
+ 6066 Nuand BladeRF
+ 6067 Orbotron 9000 (Serial)
+ 6068 Orbotron 9000 (HID)
+ 6069 xser (DFU)
+ 606a xser (legacy)
+ 606b S08-245, urJtag compatible firmware for S08JS
+ 606c Blinkytape full-color light tape
+ 606d TinyG open source motion controller
+ 606e Reefangel Evolution 1.0
+ 6070 Open Pinball Project
+ 6071 The Glitch HID
+ 6072 The Glitch Disk
+ 6073 The Glitch Serial
+ 6074 The Glitch MIDI
+ 6075 The Glitch RawHID
+ 6076 Vultureprog BIOS chip programmer
+ 6077 PaintDuino
+ 6078 DTplug
+ 607a Fadecandy
+ 607b RCDongle for IR remote control
+ 607c OpenVizsla USB sniffer/analyzer
+ 607d Spark Core Arduino-compatible board with WiFi
+ 607f Spark Core Arduino-compatible board with WiFi (bootloader)
+ 6080 arcin arcade controller
+ 6081 BladeRF (bootloader)
+ 6082 Facecandy (DFU)
+ 6083 LightUp (bootloader)
+ 6084 arcin arcade controller (DFU)
+ 6085 IRKit for controlloing home electronics from iOS devices
+ 6086 OneRNG entropy device
+ 6088 picp PIC16F145x based PIC16F145x programmer
+ 6089 Great Scott Gadgets HackRF One SDR
+ 608a BLEduino
+ 608b Loctronix ASR-2300 SDR/motion sensing module
+ 608c Fx2lafw
+ 608d Fx2lafw
+ 608e Fx2lafw
+ 608f Fx2lafw
+ 6090 Fx2lafw
+ 6091 Fx2lafw
+ 6092 Fx2lafw
+ 6093 Fx2lafw
+ 6094 Fx2lafw
+ 6095 Fx2lafw
+ 6096 LightUp (sketch)
+ 6097 Tessel JavaScript enabled Microcontroller with built-in WiFi
+ 6098 RFIDler
+ 6099 RASDR Radio Astronomy SDR Rx Interface
+ 609a RASDR Radio Astronomy SDR Tx Interface
+ 609b RASDR Radio Astronomy SDR (bootloader)
+ 609c antiAFK keyboard
+ 609d PIC16F145x bootloader
+ 609e Clyde Lamp by Fabule (bootloader)
+ 609f Clyde Lamp by Fabule (sketch)
+ 60a0 Smoothiepanel robotic control interface
+ 60a1 Airspy
+ 60a2 barebox (DFU)
+ 60a3 keyboard (bootloader)
+ 60a4 Papilio Duo (AVR)
+ 60a5 Papilio Duo (FPGA)
+ 60a6 HydraBus/HydraNFC (bootloader)
+ 60a7 HydraBus/HydraNFC
+ 60a8 reserved
+ 60a9 Blinky Light Controller (DFU)
+ 60aa Blinky Light Controller
+ 60ab AllPixel
+ 60ac OpenBLT generic microcontroller (bootloader)
+ 60b0 Waterott Arduino based Clock (caterina bootloader)
+ 60b1 Drinkbot (processing)
+ 60b2 Drinkbot (OTG-tablet support)
+ 60b3 calc.pw password generator device (standard)
+ 60b4 calc.pw password generator device (enhanced)
+ 60b5 TimVideos' HDMI2USB (FX2) - Unconfigured device
+ 60b6 TimVideos' HDMI2USB (FX2) - Firmware load/upgrade
+ 60b7 TimVideos' HDMI2USB (FX2) - HDMI/DVI Capture Device
+ 60b8 TimVideos' HDMI2USB (Soft+UTMI) - Unconfigured device
+ 60b9 TimVideos' HDMI2USB (Soft+UTMI) - Firmware upgrade
+ 60ba TimVideos' HDMI2USB (Soft+UTMI) - HDMI/DVI Capture Device
+ 60bc Simple CC25xx programmer / serial board
+ 60bd Open Source control interface for multimedia applications
+ 60be Pixelmatix Aurora (bootloader)
+ 60bf Pixelmatix Aurora
+ 60c1 BrewBit Model-T pOSHW temperature controller for homebrewers (bootloader)
+ 60c2 BrewBit Model-T pOSHW temperature controller for homebrewers
+ 60c3 X Antenna Tracker arduino board
+ 60c6 USBtrng hardware random number generator
+ 60c7 Zubax GNSS positioning module for light UAV systems
+ 60c8 Xlink data transfer and control system for Commodore C64
+ 60c9 random number generator
+ 60ca FinalKey password manager
+ 60cb PteroDAQ Data Acquisition on FRDM-KL25Z and future boards
+ 60cc LamDiNao
+ 60de Cryptech.is random number generator
+ 60df Numato Opsis HDMI2USB board (unconfigured)
+ 60e0 Numato Opsis HDMI2USB board (JTAG Programming Mode)
+ 60e1 Numato Opsis HDMI2USB board (User Mode)
+ 60e2 Osmocom SIMtrace 2 (DFU)
+ 60e3 Osmocom SIMtrace 2
+ 60e4 3D printed racing game - (Catalina CDC bootloader)
+ 60e5 3D printed racing game
+ 60e6 replacement for GoodFET/FaceDancer - GreatFet
+ 60e7 replacement for GoodFET/FaceDancer - GreatFet target
+ 60e8 Alpen Clack keyboard
+ 60e9 keyman64 keyboard itercepter
+ 60ea Wiggleport FPGA-based I/O board
+ 60ec Duet 3D Printer Controller
+ 60f0 UDAD-T1 data aquisition device (boot)
+ 60f1 UDAD-T1 data aquisition device
+ 60f2 UDAD-T2 data aquisition device (boot)
+ 60f3 UDAD-T2 data aquisition device
+ 60f4 Uniti ARC motor controller
+ 60f5 EightByEight Blinky Badge (DFU)
+ 60f6 EightByEight Blinky Badge
+ 60f7 cardio NFC/RFID card reader (bootloader)
+ 60f8 cardio NFC/RFID card reader
+ 60fc OnlyKey Two-factor Authentication and Password Solution
+ 6100 overlay64 video overlay module
+ 6104 ScopeFun open source instrumentation
+ 6108 Myriad-RF LimeSDR
+ 610c Magic Keys (boot)
+ 610d Magic Keys
+ 8085 Box0 (box0-v5)
+ cc15 rad1o badge for CCC congress 2015
+1d57 Xenta
+ 0005 Wireless Receiver (Keyboard and Mouse)
+ 0006 Wireless Receiver (RC Laser Pointer)
+ 000c Optical Mouse
+ 2400 Wireless Mouse Receiver
+ 32da 2.4GHz Receiver (Keyboard and Mouse)
+ 83d0 Click-mouse!
+ ac01 Wireless Receiver (Keyboard and Mouse)
+ ad02 SE340D PC Remote Control
+ af01 AUVIO Universal Remote Receiver for PlayStation 3
+1d5b Smartronix, Inc.
+1d6b Linux Foundation
+ 0001 1.1 root hub
+ 0002 2.0 root hub
+ 0003 3.0 root hub
+ 0100 PTP Gadget
+ 0101 Audio Gadget
+ 0102 EEM Gadget
+ 0103 NCM (Ethernet) Gadget
+ 0104 Multifunction Composite Gadget
+ 0105 FunctionFS Gadget
+ 0200 Qemu Audio Device
+1d90 Citizen
+ 201e PPU-700
+1d9d Sigma Sport
+ 1010 Docking Station Topline 2009
+ 1011 Docking Station Topline 2012
+1de1 Actions Microelectronics Co.
+ 1101 Generic Display Device (Mass storage mode)
+ c101 Generic Display Device
+1e0e Qualcomm / Option
+ f000 iCON 210 UMTS Surfstick
+1e10 Point Grey Research, Inc.
+ 2004 Sony 1.3MP 1/3" ICX445 IIDC video camera [Chameleon]
+1e17 Mirion Technologies Dosimetry Services Division
+ 0001 instadose dosimeter
+1e1d Lumension Security
+ 0165 Secure Pen drive
+1e1f INVIA
+1e29 Festo AG & Co. KG
+ 0101 CPX Adapter
+ 0102 CPX Adapter >=HW10.09 [CP2102]
+ 0401 iL3-TP [AT90USB646]
+ 0402 FTDI232 [EasyPort]
+ 0403 FTDI232 [EasyPort Mini]
+ 0404 FTDI232 [Netzteil-GL]
+ 0405 FTDI232 [MotorPrüfstand]
+ 0406 STM32F103 [EasyKit]
+ 0407 LPC2378 [Robotino]
+ 0408 LPC2378 [Robotino-Arm]
+ 0409 LPC2378 [Robotino-Arm Bootloader]
+ 040a LPC2378 [Robotino Bootloader]
+ 040b LPC2378 [Robotino XT]
+ 040c LPC2378 [Robotino XT Bootloader]
+ 040d LPC2378 [Robotino 3]
+ 040e LPC2378 [Robotino 3 Bootloader]
+ 0501 CP2102 [CMSP]
+ 0601 CMMP-AS
+1e3d Chipsbank Microelectronics Co., Ltd
+ 2093 CBM209x Flash Drive (OEM)
+ 4082 CBM4082 SD Card Reader
+1e41 Cleverscope
+ 0001 CS328A PC Oscilloscope
+1e4e Cubeternet
+ 0100 WebCam
+ 0102 GL-UPC822 UVC WebCam
+1e54 TypeMatrix
+ 2030 2030 USB Keyboard
+1e68 TrekStor GmbH & Co. KG
+ 001b DataStation maxi g.u
+ 0050 DataStation maxi light
+1e71 NZXT
+ 0001 Avatar Optical Mouse
+1e74 Coby Electronics Corporation
+ 2211 MP300
+ 2647 2 GB 2 Go Video MP3 Player [MP601-2G]
+ 2659 Coby 4GB Go Video MP3 Player [MP620-4G]
+ 4641 A8705 MP3/Video Player
+ 6511 MP705-8G MP3 player
+ 6512 MP705-4G
+ 7111 MP957 Music and Video Player
+1e7d ROCCAT
+ 2c24 Pyra Mouse (wired)
+ 2ced Kone Mouse
+ 2cf6 Pyra Mouse (wireless)
+ 2d50 Kova+ Mouse
+ 2d51 Kone+ Mouse
+ 30d4 Arvo Keyboard
+1ebb NuCORE Technology, Inc.
+1eda AirTies Wireless Networks
+ 2012 Air2210 54 Mbps Wireless Adapter
+ 2210 Air2210 54 Mbps Wireless Adapter
+ 2310 Air2310 150 Mbps Wireless Adapter
+ 2410 Air2410 300 Mbps Wireless Adapter
+1edb Blackmagic design
+ bd3b Intensity Shuttle
+1ee8 ONDA COMMUNICATION S.p.a.
+ 0014 MT833UP
+1ef6 EADS Deutschland GmbH
+ 2233 Cassidian NH90 STTE
+ 5064 FDR Interface
+ 5523 Cassidian SSDC Adapter II
+ 5545 Cassidian SSDC Adapter III
+ 5648 RIU CSMU/BSD
+ 564a Cassidian RIU CSMU/BSD Simulator
+1f28 Cal-Comp
+ 0020 CDMA USB Modem A600
+ 0021 CD INSTALLER USB Device
+1f3a Onda (unverified)
+ efe8 V972 tablet in flashing mode
+1f44 The Neat Company
+ 0001 NM-1000 scanner
+1f48 H-TRONIC GmbH
+ 0627 Data capturing system
+ 0628 Data capturing and control module
+1f4d G-Tek Electronics Group
+ b803 Lifeview LV5TDLX DVB-T [RTL2832U]
+ d220 Geniatech T220 DVB-T2 TV Stick
+1f6f Aliph
+ 0023 Jawbone Jambox
+ 8000 Jawbone Jambox - Updating
+1f75 Innostor Technology Corporation
+ 0888 IS888 SATA Storage Controller
+ 0902 IS902 UFD controller
+1f82 TANDBERG
+ 0001 PrecisionHD Camera
+1f84 Alere, Inc.
+1f87 Stantum
+ 0002 Multi-touch HID Controller
+1f9b Ubiquiti Networks, Inc.
+ 0241 AirView2-EXT
+1fab Samsung Opto-Electroncs Co., Ltd.
+ 104d ES65
+1fbd Delphin Technology AG
+ 0001 Expert Key - Data aquisition system
+1fc9 NXP Semiconductors
+ 0003 LPC1343
+ 010b PR533
+1fde ILX Lightwave Corporation
+ 0001 UART Bridge
+1fe7 Vertex Wireless Co., Ltd.
+ 1000 VW100 series CDMA EV-DO Rev.A modem
+1ff7 CVT Electronics.Co.,Ltd
+ 0013 CVTouch Screen (HID)
+ 001a Human Interface Device
+1fff Ideofy Inc.
+2001 D-Link Corp.
+ 0001 DWL-120 WIRELESS ADAPTER
+ 0201 DHN-120 10Mb Home Phoneline Adapter
+ 1a00 DUB-E100 Fast Ethernet Adapter(rev.A) [ASIX AX88172]
+ 1a02 DUB-E100 Fast Ethernet Adapter(rev.C1) [ASIX AX88772]
+ 200c 10/100 Ethernet
+ 3200 DWL-120 802.11b Wireless Adapter(rev.E1) [Atmel at76c503a]
+ 3301 DWA-130 802.11n Wireless N Adapter(rev.C1) [Realtek RTL8192U]
+ 3306 DWL-G122 Wireless Adapter(rev.F1) [Realtek RTL8188SU]
+ 3308 DWA-121 802.11n Wireless N 150 Pico Adapter [Realtek RTL8188CUS]
+ 3309 DWA-135 802.11n Wireless N Adapter(rev.A1) [Realtek RTL8192CU]
+ 330a DWA-133 802.11n Wireless N Adapter [Realtek RTL8192CU]
+ 3500 Elitegroup Computer Systems WLAN card WL-162
+ 3700 DWL-122 802.11b [Intersil Prism 3]
+ 3701 DWL-G120 Spinnaker 802.11g [Intersil ISL3886]
+ 3702 DWL-120 802.11b Wireless Adapter(rev.F) [Intersil ISL3871]
+ 3703 AirPlus G DWL-G122 Wireless Adapter(rev.A1) [Intersil ISL3880]
+ 3704 AirPlus G DWL-G122 Wireless Adapter(rev.A2) [Intersil ISL3887]
+ 3705 AirPlus G DWL-G120 Wireless Adapter(rev.C) [Intersil ISL3887]
+ 3761 IEEE 802.11g USB2.0 Wireless Network Adapter-PN
+ 3a00 DWL-AG132 [Atheros AR5523]
+ 3a01 DWL-AG132 (no firmware) [Atheros AR5523]
+ 3a02 DWL-G132 [Atheros AR5523]
+ 3a03 DWL-G132 (no firmware) [Atheros AR5523]
+ 3a04 DWL-AG122 [Atheros AR5523]
+ 3a05 DWL-AG122 (no firmware) [Atheros AR5523]
+ 3a80 AirPlus Xtreme G DWL-G132 Wireless Adapter
+ 3a81 predator Bootloader Download
+ 3a82 AirPremier AG DWL-AG132 Wireless Adapter
+ 3a83 predator Bootloader Download
+ 3b00 AirPlus DWL-120+ Wireless Adapter [Texas Instruments ACX100USB]
+ 3b01 WLAN Boot Device
+ 3c00 AirPlus G DWL-G122 Wireless Adapter(rev.B1) [Ralink RT2571]
+ 3c01 AirPlus AG DWL-AG122 Wireless Adapter
+ 3c02 AirPlus G DWL-G122 Wireless Adapter
+ 3c05 DUB-E100 Fast Ethernet Adapter(rev.B1) [ASIX AX88772]
+ 3c15 DWA-140 RangeBooster N Adapter(rev.B3) [Ralink RT5372]
+ 3c17 DWA-123 Wireless N 150 Adapter(rev.A1) [Ralink RT3370]
+ 3c19 DWA-125 Wireless N 150 Adapter(rev.A3) [Ralink RT5370]
+ 3c1a DWA-160 802.11abgn Xtreme N Dual Band Adapter(rev.B2) [Ralink RT5572]
+ 3c1b DWA-127 Wireless N 150 High-Gain Adapter(rev.A1) [Ralink RT3070]
+ 4000 DSB-650C Ethernet [klsi]
+ 4001 DSB-650TX Ethernet [pegasus]
+ 4002 DSB-650TX Ethernet [pegasus]
+ 4003 DSB-650TX-PNA Ethernet [pegasus]
+ 400b 10/100 Ethernet
+ 4102 10/100 Ethernet
+ 5100 DSL-200 ADSL ATM Modem
+ 5102 DSL-200 ADSL Loader
+ 5b00 Remote NDIS Network Device
+ 9414 Cable Modem
+ 9b00 Broadband Cable Modem Remote NDIS Device
+ abc1 DSB-650 Ethernet [pegasus]
+ f013 DLink 7 port USB2.0 Hub
+ f103 DUB-H7 7-port USB 2.0 hub
+ f10d Accent Communications Modem
+ f110 DUB-AV300 A/V Capture
+ f111 DBT-122 Bluetooth adapter
+ f112 DUB-T210 Audio Device
+ f116 Formosa 2
+ f117 Formosa 3
+ f118 Formosa 4
+2002 DAP Technologies
+2003 detectomat
+ ea61 dc3500
+200c Reloop
+ 100b Play audio soundcard
+2013 PCTV Systems
+ 0245 PCTV 73ESE
+ 0246 PCTV 74E
+ 0248 PCTV 282E
+ 024f nanoStick T2 290e
+2019 PLANEX
+ 3220 GW-US11S WLAN [Atmel AT76C503A]
+ 4901 GW-USSuper300 802.11bgn Wireless Adapter [Realtek RTL8191SU]
+ 4903 GW-USFang300 802.11abgn Wireless Adapter [Realtek RTL8192DU]
+ 4904 GW-USUltra300 802.11abgn Wireless Adapter [Realtek RTL8192DU]
+ 5303 GW-US54GXS 802.11bg
+ 5304 GWUS300 802.11n
+ ab01 GW-US54HP
+ ab24 GW-US300MiniS
+ ab25 GW-USMini2N 802.11n Wireless Adapter [Ralink RT2870]
+ ab28 GW-USNano
+ ab29 GW-USMicro300
+ ab2a GW-USNano2 802.11n Wireless Adapter [Realtek RTL8188CUS]
+ ab2b GW-USEco300 802.11bgn Wireless Adapter [Realtek RTL8192CU]
+ ab2c GW-USDual300 802.11abgn Wireless Adapter [Realtek RTL8192DU]
+ ab50 GW-US54Mini2
+ c002 GW-US54SG
+ c007 GW-US54GZL
+ ed02 GW-USMM
+ ed06 GW-US300MiniW 802.11bgn Wireless Adapter
+ ed10 GW-US300Mini2
+ ed14 GW-USMicroN
+ ed16 GW-USMicroN2W 802.11bgn Wireless Adapter [Realtek RTL8188SU]
+ ed17 GW-USValue-EZ 802.11n Wireless Adapter [Realtek RTL8188CUS]
+ ed18 GW-USHyper300 / GW-USH300N 802.11bgn Wireless Adapter [Realtek RTL8191SU]
+203d Encore Electronics Inc.
+ 1480 ENUWI-N3 [802.11n Wireless N150 Adapter]
+2040 Hauppauge
+ 0c80 Windham
+ 0c90 Windham
+ 1700 CataMount
+ 1800 Okemo A
+ 1801 Okemo B
+ 2000 Tiger Minicard
+ 2009 Tiger Minicard R2
+ 200a Tiger Minicard
+ 2010 Tiger Minicard
+ 2011 WinTV MiniCard [Dell Digital TV Receiver]
+ 2019 Tiger Minicard
+ 2400 WinTV PVR USB2 (Model 24019)
+ 4700 WinTV Nova-S-USB2
+ 4902 HD PVR
+ 4903 HS PVR
+ 4982 HD PVR
+ 5500 Windham
+ 5510 Windham
+ 5520 Windham
+ 5530 Windham
+ 5580 Windham
+ 5590 Windham
+ 6500 WinTV HVR-900
+ 6502 WinTV HVR-900
+ 6503 WinTV HVR-930
+ 6513 WinTV HVR-980
+ 7050 Nova-T Stick
+ 7060 Nova-T Stick 2
+ 7070 Nova-T Stick 3
+ 7240 WinTV HVR-850
+ 8400 WinTV Nova-T-500
+ 9300 WinTV NOVA-T USB2 (cold)
+ 9301 WinTV NOVA-T USB2 (warm)
+ 9941 WinTV Nova-T-500
+ 9950 WinTV Nova-T-500
+ b910 Windham
+ b980 Windham
+ b990 Windham
+ c000 Windham
+ c010 Windham
+2047 Texas Instruments
+ 0200 MSP430 USB HID Bootstrap Loader
+ 0855 Invensense Embedded MotionApp HID Sensor
+ 0964 Inventio Software MSP430
+2058 Nano River Technology
+ 2058 ViperBoard I2C, SPI, GPIO interface
+2077 Taicang T&W Electronics Co. Ltd
+ 9002 W1M100 HSPA/WCDMA Module
+2080 Barnes & Noble
+ 0001 nook
+ 0002 NOOKcolor
+ 0003 NOOK Simple Touch
+ 0004 NOOK Tablet
+2086 SIMPASS
+2087 Cando
+ 0a01 Multi Touch Panel
+ 0a02 Multi Touch Panel
+ 0b03 Multi Touch Panel
+20a0 Clay Logic
+ 4123 IKALOGIC SCANALOGIC 2
+ 414a MDE SPI Interface
+ 415a OpenPilot
+ 415b CopterControl
+ 415c PipXtreme
+20b1 XMOS Ltd
+ 10ad XUSB Loader
+ f7d1 XTAG2 - JTAG Adapter
+20b3 Hanvon
+ 0a18 10.1 Touch screen overlay
+20b7 Qi Hardware
+ 0713 Milkymist JTAG/serial
+ 1540 ben-wpan, AT86RF230-based
+ 1db5 IDBG in DFU mode
+ 1db6 IDBG in normal mode
+ c25b C2 Dongle
+ cb72 ben-wpan, cntr
+20ce Minicircuits
+ 0012 RF Sythesizer 250-4200MHz model SSG-4000LH
+ 0021 RF Switch Matrix
+ 0022 I/O Controller
+20df Simtec Electronics
+ 0001 Entropy Key [UDEKEY01]
+20f1 NET New Electronic Technology GmbH
+ 0101 iCube3 Camera
+20f4 TRENDnet
+ 648b TEW-648UBM 802.11n 150Mbps Micro Wireless N Adapter [Realtek RTL8188CUS]
+20f7 XIMEA
+ 3001 Camera with CMOS sensor [MQ]
+ 3021 Camera with CCD sensor [MD]
+ 30b3 Camera with CMOS sensor in Vision mode [MQ]
+ a003 Subminiature 5Mpix B/W Camera, MU9PM-MH
+2100 RT Systems
+ 9e52 Yaesu VX-7
+ 9e54 CT29B Radio Cable
+ 9e57 RTS01 Radio Cable
+ 9e5d K4Y Radio Cable
+ 9e5f FT232RL [RTS05 Serial Cable]
+2101 ActionStar
+ 0201 SIIG 4-to-2 Printer Switch
+2109 VIA Labs, Inc.
+ 0700 VL700 SATA 3Gb/s bridge
+ 0701 VL701 SATA 3Gb/s bridge
+ 0810 VL81x Hub
+ 0811 Hub
+ 0812 VL812 Hub
+ 2811 Hub
+ 2812 VL812 Hub
+ 3431 Hub
+ 8110 Hub
+2113 Softkinetic
+ 0137 DepthSense 311 (3D)
+ 0145 DepthSense 325
+ 8000 DepthSense 311 (Color)
+2149 Advanced Silicon S.A.
+ 211b Touchscreen Controller
+ 2703 TS58xxA/TC56xxA [CoolTouch]
+2162 Creative (?)
+ 2031 Network Blaster Wireless Adapter
+ 500c DE5771 Modem Blaster
+ 8001 Broadxent BritePort DSL Bridge 8010U
+2184 GW Instek
+ 0005 GDS-3000 Oscilloscope
+ 0006 GDS-3000 Oscilloscope
+ 0011 AFG Function Generator (CDC)
+21a1 Emotiv Systems Pty. Ltd.
+ 0001 EPOC Consumer Headset Wireless Dongle
+21d6 Agecodagis SARL
+ 0002 Seismic recorder [Tellus]
+2222 MacAlly
+ 0004 iWebKey Keyboard
+ 2520 Mini Tablet
+ 4050 AirStick joystick
+2227 SAMWOO Enterprise
+ 3105 SKYDATA SKD-U100
+2232 Silicon Motion
+ 1005 WebCam SCB-0385N
+ 1028 WebCam SC-03FFL11939N
+ 1029 WebCam SC-13HDL11939N
+ 1037 WebCam SC-03FFM12339N
+2233 RadioShack Corporation
+ 6323 USB Electronic Scale
+2237 Kobo Inc.
+ 4161 eReader White
+225d Morpho
+ 0001 FINGER VP Multimodal Biometric Sensor
+ 0008 CBM-E3 Fingerprint Sensor
+ 0009 CBM Fingerprint Sensor [CBM-V3]
+ 000a MSO1300-E3 Fingerprint Sensor
+ 000b MSO1300 Fingerprint Sensor [MSO1300-V3]
+ 000c MSO1350-E3 Fingerprint Sensor & SmartCard Reader
+ 000d MSO1350 Fingerprint Sensor & SmartCard Reader [MSO1350-V3]
+ 000e MorphoAccess SIGMA Biometric Access Control Terminal
+228d 8D Technologies inc.
+ 0001 Terminal Bike Key Reader
+22a6 Pie Digital, Inc.
+ ffff PieKey "beta" 4GB model 4E4F41482E4F5247 (SM3251Q BB)
+22b8 Motorola PCS
+ 0001 Wally 2.2 chipset
+ 0002 Wally 2.4 chipset
+ 0005 V.60c/V.60i GSM Phone
+ 0830 2386C-HT820
+ 0833 2386C-HT820 [Flash Mode]
+ 0850 Bluetooth Device
+ 1001 Patriot 1.0 (GSM) chipset
+ 1002 Patriot 2.0 chipset
+ 1005 T280e GSM/GPRS Phone
+ 1101 Patriot 1.0 (TDMA) chipset
+ 1801 Rainbow chipset flash
+ 2035 Bluetooth Device
+ 2805 GSM Modem
+ 2821 T720 GSM Phone
+ 2822 V.120e GSM Phone
+ 2823 Flash Interface
+ 2a01 MSM6050 chipset
+ 2a02 CDMA modem
+ 2a03 MSM6050 chipset flash
+ 2a21 V710 GSM Phone (P2K)
+ 2a22 V710 GSM Phone (AT)
+ 2a23 MSM6100 chipset flash
+ 2a41 MSM6300 chipset
+ 2a42 Usb Modem
+ 2a43 MSM6300 chipset flash
+ 2a61 E815 GSM Phone (P2K)
+ 2a62 E815 GSM Phone (AT)
+ 2a63 MSM6500 chipset flash
+ 2a81 MSM6025 chipset
+ 2a83 MSM6025 chipset flash
+ 2ac1 MSM6100 chipset
+ 2ac3 MSM6100 chipset flash
+ 2d78 XT300[SPICE]
+ 3001 A835/E1000 GSM Phone (P2K)
+ 3002 A835/E1000 GSM Phone (AT)
+ 3801 C350L/C450 (P2K)
+ 3802 C330/C350L/C450/EZX GSM Phone (AT)
+ 3803 Neptune LT chipset flash
+ 4001 OMAP 1.0 chipset
+ 4002 A920/A925 UMTS Phone
+ 4003 OMAP 1.0 chipset flash
+ 4008 OMAP 1.0 chipset RDL
+ 41d6 Droid X (Windows media mode)
+ 41d9 Droid/Milestone
+ 41db Droid/Milestone (Debug mode)
+ 41de Droid X (PC mode)
+ 4204 MPx200 Smartphone
+ 4214 MPc GSM
+ 4224 MPx220 Smartphone
+ 4234 MPc CDMA
+ 4244 MPx100 Smartphone
+ 4285 Droid X (Mass storage)
+ 4801 Neptune LTS chipset
+ 4803 Neptune LTS chipset flash
+ 4810 Triplet GSM Phone (storage)
+ 4901 Triplet GSM Phone (P2K)
+ 4902 Triplet GSM Phone (AT)
+ 4903 Neptune LTE chipset flash
+ 4a01 Neptune LTX chipset
+ 4a03 Neptune LTX chipset flash
+ 4a32 L6-imode Phone
+ 5801 Neptune ULS chipset
+ 5803 Neptune ULS chipset flash
+ 5901 Neptune VLT chipset
+ 5903 Neptune VLT chipset flash
+ 6001 Dalhart EZX
+ 6003 Dalhart flash
+ 6004 EZX GSM Phone (CDC Net)
+ 6006 MOTOROKR E6
+ 6008 Dalhart RDL
+ 6009 EZX GSM Phone (P2K)
+ 600a Dalhart EZX config 17
+ 600b Dalhart EZX config 18
+ 600c EZX GSM Phone (USBLAN)
+ 6021 JUIX chipset
+ 6023 JUIX chipset flash
+ 6026 Flash RAM Downloader/miniOS
+ 6027 USBLAN
+ 604c EZX GSM Phone (Storage)
+ 6101 Talon integrated chipset
+ 6401 Argon chipset
+ 6403 Argon chipset flash
+ 6415 ROKR Z6 (MTP mode)
+ 6604 Washington CDMA Phone
+ 6631 CDC Modem
+ 7001 Q Smartphone
+ fe01 StarTAC III MS900
+22b9 eTurboTouch Technology, Inc.
+ 0006 Touch Screen
+22ba Technology Innovation Holdings, Ltd
+2304 Pinnacle Systems, Inc.
+ 0109 Studio PCTV USB (SECAM)
+ 0110 Studio PCTV USB (PAL)
+ 0111 Miro PCTV USB
+ 0112 Studio PCTV USB (NTSC) with FM radio
+ 0201 Systems MovieBox Device
+ 0204 MovieBox USB_B
+ 0205 DVC 150B
+ 0206 Systems MovieBox Deluxe Device
+ 0207 Dazzle DVC90 Video Device
+ 0208 Studio PCTV USB2
+ 020e PCTV 200e
+ 020f PCTV 400e BDA Device
+ 0210 Studio PCTV USB (PAL) with FM radio
+ 0212 Studio PCTV USB (NTSC)
+ 0213 500-USB Device
+ 0214 Studio PCTV USB (PAL) with FM radio
+ 0216 PCTV 60e
+ 0219 PCTV 260e
+ 021a Dazzle DVC100 Audio Device
+ 021b Dazzle DVC130/DVC170
+ 021d Dazzle DVC130
+ 021e Dazzle DVC170
+ 021f PCTV Sat HDTV Pro BDA Device
+ 0222 PCTV Sat Pro BDA Device
+ 0223 DazzleTV Sat BDA Device
+ 0225 Remote Kit Infrared Transceiver
+ 0226 PCTV 330e
+ 0227 PCTV for Mac, HD Stick
+ 0228 PCTV DVB-T Flash Stick
+ 0229 PCTV Dual DVB-T 2001e
+ 022a PCTV 160e
+ 022b PCTV 71e [Afatech AF9015]
+ 0232 PCTV 170e
+ 0236 PCTV 72e [DiBcom DiB7000PC]
+ 0237 PCTV 73e [DiBcom DiB7000PC]
+ 023a PCTV 801e
+ 023b PCTV 801e SE
+ 023d PCTV 340e
+ 023e PCTV 340e SE
+ 0300 Studio Linx Video input cable (NTSC)
+ 0301 Studio Linx Video input cable (PAL)
+ 0302 Dazzle DVC120
+ 0419 PCTV Bungee USB (PAL) with FM radio
+ 061d PCTV Deluxe (NTSC) Device
+ 061e PCTV Deluxe (PAL) Device
+2318 Shining Technologies, Inc. [hex]
+ 0011 CitiDISK Jr. IDE Enclosure
+2341 Arduino SA
+ 0001 Uno (CDC ACM)
+ 0010 Mega 2560 (CDC ACM)
+ 003b Serial Adapter (CDC ACM)
+ 003f Mega ADK (CDC ACM)
+ 0042 Mega 2560 R3 (CDC ACM)
+ 0043 Uno R3 (CDC ACM)
+ 0044 Mega ADK R3 (CDC ACM)
+ 0045 Serial R3 (CDC ACM)
+ 8036 Leonardo (CDC ACM, HID)
+2373 Pumatronix Ltda
+ 0001 5 MegaPixel Digital Still Camera [DSC5M]
+2375 Digit@lway, Inc.
+ 0001 Digital Audio Player
+2406 SANHO Digital Electronics Co., Ltd.
+ 6688 PD7X Portable Storage
+2443 Aessent Technology Ltd
+ 00dc aes220 FPGA Mini-Module
+2478 Tripp-Lite
+ 2008 U209-000-R Serial Port
+248a Maxxter
+ 8366 Wireless Optical Mouse ACT-MUSW-002
+249c M2Tech s.r.l.
+24e1 Paratronic
+ 3001 Adp-usb
+ 3005 Radius
+2632 TwinMOS
+ 3209 7-in-1 Card Reader
+2639 Xsens
+ 0001 MTi-10 IMU
+ 0002 MTi-20 VRU
+ 0003 MTi-30 AHRS
+ 0011 MTi-100 IMU
+ 0012 MTi-200 VRU
+ 0013 MTi-300 AHRS
+ 0017 MTi-G 7xx GNSS/INS
+ 0100 Body Pack
+ 0101 Awinda Station
+ 0102 Awinda Dongle
+ 0103 Sync Station
+ 0200 MTw
+ d00d Wireless Receiver
+2650 Electronics For Imaging, Inc. [hex]
+2659 Sundtek
+ 1101 TNT DVB-T/DAB/DAB+/FM
+ 1201 FM Transmitter/Receiver
+ 1202 MediaTV Analog/FM/DVB-T
+ 1203 MediaTV Analog/FM/DVB-T MiniPCIe
+ 1204 MediaTV Analog/FM/ATSC
+ 1205 SkyTV Ultimate V
+ 1206 MediaTV DVB-T MiniPCIe
+ 1207 Sundtek HD Capture
+ 1208 Sundtek SkyTV Ultimate III
+ 1209 MediaTV Analog/FM/ATSC MiniPCIe
+ 1210 MediaTV Pro III (EU)
+ 1211 MediaTV Pro III (US)
+ 1212 MediaTV Pro III MiniPCIe (EU)
+ 1213 MediaTV Pro III MiniPCIe (US)
+2676 Basler AG
+ ba02 ace
+2730 Citizen
+ 200f CT-S310 Label printer
+2735 DigitalWay
+ 0003 MPIO HS100
+ 1001 MPIO FY200
+ 1002 MPIO FL100
+ 1003 MPIO FD100
+ 1004 MPIO HD200
+ 1005 MPIO HD300
+ 1006 MPIO FG100
+ 1007 MPIO FG130
+ 1008 MPIO FY300
+ 1009 MPIO FY400
+ 100a MPIO FL300
+ 100b MPIO HS200
+ 100c MPIO FL350
+ 100d MPIO FY500
+ 100e MPIO FY500
+ 100f MPIO FY600
+ 1012 MPIO FL400
+ 1013 MPIO HD400
+ 1014 MPIO HD400
+ 1016 MPIO FY700
+ 1017 MPIO FY700
+ 1018 MPIO FY800
+ 1019 MPIO FY800
+ 101a MPIO FY900
+ 101b MPIO FY900
+ 102b MPIO FL500
+ 102c MPIO FL500
+ 103f MPIO FY570
+ 1040 MPIO FY570
+ 1041 MPIO FY670
+ 1042 MPIO FY670
+ 1043 HCT HMD-180A
+ 1044 HCT HMD-180A
+273f Hughski Limited
+ 1000 ColorHug bootloader
+ 1001 ColorHug
+ 1002 ColorHug+
+ 1003 ColorHug+ Bootloader
+ 1004 ColorHug2
+ 1005 ColorHug2 bootloader
+2770 NHJ, Ltd
+ 0a01 ScanJet 4600 series
+ 905c Che-Ez Snap SNAP-U/Digigr8/Soundstar TDC-35
+ 9060 A130
+ 9120 Che-ez! Snap / iClick Tiny VGA Digital Camera
+ 9130 TCG 501
+ 913c Argus DC-1730
+ 9150 Mini Cam
+ 9153 iClick 5X
+ 915d Cyberpix S-210S / Little Tikes My Real Digital Camera
+ 930b CCD Webcam(PC370R)
+ 930c CCD Webcam(PC370R)
+27b8 ThingM
+ 01ed blink(1)
+2821 ASUSTek Computer Inc.
+ 0161 WL-161 802.11b Wireless Adapter [SiS 162U]
+ 160f WL-160g 802.11g Wireless Adapter [Envara WiND512]
+ 3300 WL-140 / Hawking HWU36D 802.11b Wireless Adapter [Intersil PRISM 3]
+2899 Toptronic Industrial Co., Ltd
+ 012c Camera Device
+289b Dracal/Raphnet technologies
+ 0001 Gamecube/N64 controller v2.2
+ 0002 2nes2snes
+ 0003 4nes4snes
+ 0004 Gamecube/N64 controller v2.3
+ 0005 Saturn (Joystick mode)
+ 0006 Saturn (Mouse mode)
+ 0007 Famicom controller
+ 0008 Dreamcast (Joystick mode)
+ 0009 Dreamcast (Mouse mode)
+ 000a Dreamcast (Keyboard mode)
+ 000b Gamecube/N64 controller v2.9 (Keyboard mode)
+ 000c Gamecube/N64 controller v2.9 (Joystick mode)
+ 0100 Dual-relay board
+ 0500 Energy meter
+ 0502 Precision barometer
+2931 Jolla Oy
+ 0a01 Jolla Phone MTP
+ 0a02 Jolla Phone Developer
+ 0a05 Jolla PC connection
+ 0afe Jolla charging only
+2a03 dog hunter AG
+ 0001 Linino ONE (bootloader)
+ 0036 Arduino Leonardo (bootloader)
+ 0037 Arduino Micro (bootloader)
+ 0038 Arduino Robot Control (bootloader)
+ 0039 Arduino Robot Motor (bootloader)
+ 003a Arduino Micro ADK rev3 (bootloader)
+ 003b Arduino Serial
+ 003c Arduino Explora (bootloader)
+ 003d Arduino Due (usb2serial)
+ 003e Arduino Due
+ 0041 Arduino Yun (bootloader)
+ 0042 Arduino Mega 2560 Rev3
+ 0043 Arduino Uno Rev3
+ 004d Arduino Zero Pro (bootloader)
+ 8001 Linino ONE (CDC ACM)
+ 8036 Arduino Leonardo (CDC ACM)
+ 8037 Arduino Micro (CDC ACM)
+ 8038 Arduino Robot Control (CDC ACM)
+ 8039 Arduino Robot Motor (CDC ACM)
+ 803a Arduino Micro ADK rev3 (CDC ACM)
+ 803c Arduino Explora (CDC ACM)
+ 8041 Arduino Yun (CDC ACM)
+ 804d Arduino Zero Pro (CDC ACM)
+2a37 RTD Embedded Technologies, Inc.
+ 5110 UPS35110/UPS25110
+2a45 Meizu Corp.
+ 0001 MX Phone (BICR)
+ 0c02 MX Phone (MTP & ADB)
+ 0c03 MX Phone (BICR & ADB)
+ 2008 MX Phone (MTP)
+ 200a MX Phone (MTP & ACM & ADB)
+ 200b MX Phone (PTP)
+ 200c MX Phone (PTP & ADB)
+ 2012 MX Phone (MTP & ACM)
+2b24 KeepKey LLC
+ 0001 Bitcoin hardware wallet
+2c02 Planex Communications
+ 14ea GW-US11H WLAN
+2c1a Dolphin Peripherals
+ 0000 Wireless Optical Mouse
+2dcf Dialog Semiconductor
+ c952 Audio Class 2.0 Devices
+2fb2 Fujitsu, Ltd
+3125 Eagletron
+ 0001 TrackerPod Camera Stand
+3136 Navini Networks
+3176 Whanam Electronics Co., Ltd
+3195 Link Instruments
+ f190 MSO-19
+ f280 MSO-28
+ f281 MSO-28
+3275 VidzMedia Pte Ltd
+ 4fb1 MonsterTV P2H
+3333 InLine
+ 3333 2 port KVM switch model 60652K
+3334 AEI
+ 1701 Fast Ethernet
+3340 Yakumo
+ 043a Mio A701 DigiWalker PPCPhone
+ 0e3a Pocket PC 300 GPS SL / Typhoon MyGuide 3500
+ a0a3 deltaX 5 BT (D) PDA
+3344 Leaguer Microelectronics (LME)
+ 3744 OEM PC Remote
+3504 Micro Star
+ f110 Security Key
+3538 Power Quotient International Co., Ltd
+ 0001 Travel Flash
+ 0015 Mass Storge Device
+ 0022 Hi-Speed Mass Storage Device
+ 0042 Cool Drive U339 Flash Disk
+ 0054 Flash Drive (2GB)
+3579 DIVA
+ 6901 Media Reader
+357d Sharkoon
+ 7788 QuickPort XT
+3636 InVibro
+3838 WEM
+ 0001 5-in-1 Card Reader
+3923 National Instruments Corp.
+ 12c0 DAQPad-6020E
+ 12d0 DAQPad-6507
+ 12e0 NI 4350
+ 12f0 NI 5102
+ 1750 DAQPad-6508
+ 17b0 USB-ISA-Bridge
+ 1820 DAQPad-6020E (68 pin I/O)
+ 1830 DAQPad-6020E (BNC)
+ 1f00 DAQPad-6024E
+ 1f10 DAQPad-6024E
+ 1f20 DAQPad-6025E
+ 1f30 DAQPad-6025E
+ 1f40 DAQPad-6036E
+ 1f50 DAQPad-6036E
+ 2f80 DAQPad-6052E
+ 2f90 DAQPad-6052E
+ 702b GPIB-USB-B
+ 703c USB-485 RS485 Cable
+ 709b GPIB-USB-HS
+ 7254 NI MIO (data acquisition card) firmware updater
+ 729e USB-6251 (OEM) data acquisition card
+40bb I-O Data
+ 0a09 USB2.0-SCSI Bridge USB2-SC
+4101 i-rocks
+ 1301 IR-2510 usb phone
+4102 iRiver, Ltd.
+ 1001 iFP-100 series mp3 player
+ 1003 iFP-300 series mp3 player
+ 1005 iFP-500 series mp3 player
+ 1007 iFP-700 series mp3/ogg vorbis player
+ 1008 iFP-800 series mp3/ogg vorbis player
+ 100a iFP-1000 series mp3/ogg vorbis player
+ 1014 T20 series mp3/ogg vorbis player (ums firmware)
+ 1019 T30
+ 1034 T60
+ 1040 M1Player
+ 1041 E100 (ums)
+ 1101 iFP-100 series mp3 player (ums firmware)
+ 1103 iFP-300 series mp3 player (ums firmware)
+ 1105 iFP-500 series mp3 player (ums firmware)
+ 1113 T10 (alternate)
+ 1117 T10
+ 1119 T30 series mp3/ogg/wma player
+ 1141 E100 (mtp)
+ 2002 H10 6GB
+ 2101 H10 20GB (mtp)
+ 2102 H10 5GB (mtp)
+ 2105 H10 5/6GB (mtp)
+413c Dell Computer Corp.
+ 0000 DRAC 5 Virtual Keyboard and Mouse
+ 0001 DRAC 5 Virtual Media
+ 0058 Port Replicator
+ 1001 Keyboard Hub
+ 1002 Keyboard Hub
+ 1003 Keyboard Hub
+ 1005 Multimedia Pro Keyboard Hub
+ 2001 Keyboard HID Support
+ 2002 SK-8125 Keyboard
+ 2003 Keyboard
+ 2005 RT7D50 Keyboard
+ 2010 Keyboard
+ 2011 Multimedia Pro Keyboard
+ 2100 SK-3106 Keyboard
+ 2101 SmartCard Reader Keyboard
+ 2105 Model L100 Keyboard
+ 2106 Dell QuietKey Keyboard
+ 2500 DRAC4 Remote Access Card
+ 2513 internal USB Hub of E-Port Replicator
+ 3010 Optical Wheel Mouse
+ 3012 Optical Wheel Mouse
+ 3016 Optical 5-Button Wheel Mouse
+ 3200 Mouse
+ 4001 Axim X5
+ 4002 Axim X3
+ 4003 Axim X30
+ 4004 Axim Sync
+ 4005 Axim Sync
+ 4006 Axim Sync
+ 4007 Axim Sync
+ 4008 Axim Sync
+ 4009 Axim Sync
+ 4011 Axim X51v
+ 5103 AIO Printer A940
+ 5105 AIO Printer A920
+ 5107 AIO Printer A960
+ 5109 Photo AIO Printer 922
+ 5110 Photo AIO Printer 962
+ 5111 Photo AIO Printer 942
+ 5112 Photo AIO Printer 924
+ 5113 Photo AIO Printer 944
+ 5114 Photo AIO Printer 964
+ 5115 Photo AIO Printer 926
+ 5116 AIO Printer 946
+ 5117 Photo AIO Printer 966
+ 5118 AIO 810
+ 5124 Laser MFP 1815
+ 5128 Photo AIO 928
+ 5200 Laser Printer
+ 5202 Printing Support
+ 5203 Printing Support
+ 5210 Printing Support
+ 5211 1110 Laser Printer
+ 5220 Laser MFP 1600n
+ 5225 Printing Support
+ 5226 Printing Support
+ 5300 Laser Printer
+ 5400 Laser Printer
+ 5401 Laser Printer
+ 5513 WLA3310 Wireless Adapter [Intersil ISL3887]
+ 5601 Laser Printer 3100cn
+ 5602 Laser Printer 3000cn
+ 5631 Laser Printer 5100cn
+ 5905 Printing Support
+ 8000 BC02 Bluetooth Adapter
+ 8010 TrueMobile Bluetooth Module in
+ 8100 TrueMobile 1180 802.11b Adapter [Intersil PRISM 3]
+ 8102 TrueMobile 1300 802.11g Wireless Adapter [Intersil ISL3880]
+ 8103 Wireless 350 Bluetooth
+ 8104 Wireless 1450 Dual-band (802.11a/b/g) Adapter [Intersil ISL3887]
+ 8105 U2 in HID - Driver
+ 8106 Wireless 350 Bluetooth Internal Card in
+ 8110 Wireless 3xx Bluetooth Internal Card
+ 8111 Wireless 3xx Bluetooth Internal Card in
+ 8114 Wireless 5700 Mobile Broadband (CDMA EV-DO) Minicard Modem
+ 8115 Wireless 5500 Mobile Broadband (3G HSDPA) Minicard Modem
+ 8116 Wireless 5505 Mobile Broadband (3G HSDPA) Minicard Modem
+ 8117 Wireless 5700 Mobile Broadband (CDMA EV-DO) Expresscard Modem
+ 8118 Wireless 5510 Mobile Broadband (3G HSDPA) Expresscard Status Port
+ 8120 Bluetooth adapter
+ 8121 Eastfold in HID
+ 8122 Eastfold in DFU
+ 8123 eHome Infrared Receiver
+ 8124 eHome Infrared Receiver
+ 8126 Wireless 355 Bluetooth
+ 8127 Wireless 355 Module with Bluetooth 2.0 + EDR Technology.
+ 8128 Wireless 5700-Sprint Mobile Broadband (CDMA EV-DO) Mini-Card Status Port
+ 8129 Wireless 5700-Telus Mobile Broadband (CDMA EV-DO) Mini-Card Status Port
+ 8131 Wireless 360 Bluetooth 2.0 + EDR module.
+ 8133 Wireless 5720 VZW Mobile Broadband (EVDO Rev-A) Minicard GPS Port
+ 8134 Wireless 5720 Sprint Mobile Broadband (EVDO Rev-A) Minicard Status Port
+ 8135 Wireless 5720 TELUS Mobile Broadband (EVDO Rev-A) Minicard Diagnostics Port
+ 8136 Wireless 5520 Cingular Mobile Broadband (3G HSDPA) Minicard Diagnostics Port
+ 8137 Wireless 5520 Voda L Mobile Broadband (3G HSDPA) Minicard Status Port
+ 8138 Wireless 5520 Voda I Mobile Broadband (3G HSDPA) Minicard EAP-SIM Port
+ 8140 Wireless 360 Bluetooth
+ 8142 Mobile 360 in DFU
+ 8147 F3507g Mobile Broadband Module
+ 8156 Wireless 370 Bluetooth Mini-card
+ 8157 Integrated Keyboard
+ 8158 Integrated Touchpad / Trackstick
+ 8160 Wireless 365 Bluetooth
+ 8161 Integrated Keyboard
+ 8162 Integrated Touchpad [Synaptics]
+ 8171 Gobi Wireless Modem (QDL mode)
+ 8172 Gobi Wireless Modem
+ 8183 F3607gw Mobile Broadband Module
+ 8184 F3607gw v2 Mobile Broadband Module
+ 8185 Gobi 2000 Wireless Modem (QDL mode)
+ 8186 Gobi 2000 Wireless Modem
+ 8187 DW375 Bluetooth Module
+ 8501 Bluetooth Adapter
+ 9500 USB CP210x UART Bridge Controller [DW700]
+ a001 Hub
+ a005 Internal 2.0 Hub
+ a700 Hub (in 1905FP LCD Monitor)
+4146 USBest Technology
+ 9281 Iomega Micro Mini 128MB Flash Drive
+ ba01 Intuix Flash Drive
+4168 Targus
+ 1010 Wireless Compact Laser Mouse
+4242 USB Design by Example
+ 4201 Buttons and Lights HID device
+ 4220 Echo 1 Camera
+4255 GoPro
+ 1000 9FF2 [Digital Photo Display]
+ 2000 HD2-14 [Hero 2 Camera]
+4317 Broadcom Corp.
+ 0700 U.S. Robotics USR5426 802.11g Adapter
+ 0701 U.S. Robotics USR5425 Wireless MAXg Adapter
+ 0711 Belkin F5D7051 v3000 802.11g
+ 0720 Dynex DX-BUSB
+4348 WinChipHead
+ 5523 USB->RS 232 adapter with Prolifec PL 2303 chipset
+ 5537 13.56Mhz RFID Card Reader and Writer
+ 5584 CH34x printer adapter cable
+4572 Shuttle, Inc.
+ 4572 Shuttle PN31 Remote
+4586 Panram
+ 1026 Crystal Bar Flash Drive
+4670 EMS Production
+ 9394 Game Cube USB Memory Adaptor 64M
+4752 Miditech
+ 0011 Midistart-2
+4757 GW Instek
+ 2009 PEL-2000 Series Electronic Load (CDC)
+ 2010 PEL-2000 Series Electronic Load (CDC)
+4766 Aceeca
+ 0001 MEZ1000 RDA
+4855 Memorex
+ 7288 Ultra Traveldrive 160G 2.5" HDD
+4971 SimpleTech
+ cb01 SP-U25/120G
+ ce17 1TB SimpleDrive II USB External Hard Drive
+4d46 Musical Fidelity
+ 0001 V-Link
+ 0002 V-DAC II
+5032 Grandtec
+ 0bb8 Grandtec USB1.1 DVB-T (cold)
+ 0bb9 Grandtec USB1.1 DVB-T (warm)
+ 0fa0 Grandtec USB1.1 DVB-T (cold)
+ 0fa1 Grandtec USB1.1 DVB-T (warm)
+5041 Linksys (?)
+ 2234 WUSB54G v1 802.11g Adapter [Intersil ISL3886]
+ 2235 WUSB54GP v1 802.11g Adapter [Intersil ISL3886]
+50c2 Averatec (?)
+ 4013 WLAN Adapter
+5173 Sweex
+ 1809 ZD1211
+5219 I-Tetra
+ 1001 Cetus CDC Device
+5345 Owon
+ 1234 PDS6062T Oscilloscope
+534c SatoshiLabs
+ 0001 Bitcoin Wallet [TREZOR]
+5354 Meyer Instruments (MIS)
+ 0017 PAXcam2
+544d Transmeta Corp.
+5543 UC-Logic Technology Corp.
+ 0002 SuperPen WP3325U Tablet
+ 0003 Tablet WP4030U
+ 0004 Tablet WP5540U
+ 0005 Tablet WP8060U
+ 0041 Genius PenSketch 6x8 Tablet
+ 0042 Tablet PF1209
+ 0064 Aiptek HyperPen 10000U
+5555 Epiphan Systems Inc.
+ 1110 VGA2USB
+ 1120 KVM2USB
+ 2222 DVI2USB
+ 3333 VGA2USB Pro
+ 3337 KVM2USB Pro
+ 3340 VGA2USB LR
+ 3344 KVM2USB LR
+ 3411 DVI2USB Solo
+ 3422 DVI2USB Duo
+55aa OnSpec Electronic, Inc.
+ 0015 Hard Drive
+ 0102 SuperDisk
+ 0103 IDE Hard Drive
+ 0201 DDI to Reader-19
+ 1234 ATAPI Bridge
+ a103 Sandisk SDDR-55 SmartMedia Card Reader
+ b000 USB to CompactFlash Card Reader
+ b004 OnSpec MMC/SD Reader/Writer
+ b00b USB to Memory Stick Card Reader
+ b00c USB to SmartMedia Card Reader
+ b012 Mitsumi FA402M 8-in-2 Card Reader
+ b200 Compact Flash Reader
+ b204 MMC/ SD Reader
+ b207 Memory Stick Reader
+5654 Gotview
+ ca42 MasterHD 3
+5656 Uni-Trend Group Limited
+ 0832 UT2000/UT3000 Digital Storage Oscilloscope
+595a IRTOUCHSYSTEMS Co. Ltd.
+ 0001 Touchscreen
+5986 Acer, Inc
+ 0100 Orbicam
+ 0101 USB2.0 Camera
+ 0102 Crystal Eye Webcam
+ 01a6 Lenovo Integrated Webcam
+ 01a7 Lenovo Integrated Webcam
+ 01a9 Lenovo Integrated Webcam
+ 0200 OrbiCam
+ 0203 BisonCam NB Pro 1300
+ 0241 BisonCam, NB Pro
+ 02d0 Lenovo Integrated Webcam [R5U877]
+ 03d0 Lenovo Integrated Webcam [R5U877]
+59e3 Nonolith Labs
+5a57 Zinwell
+ 0260 RT2570
+ 0280 802.11a/b/g/n USB Wireless LAN Card
+ 0282 802.11b/g/n USB Wireless LAN Card
+ 0283 802.11b/g/n USB Wireless LAN Card
+ 0284 802.11a/b/g/n USB Wireless LAN Card
+ 0290 ZW-N290 802.11n [Realtek RTL8192SU]
+ 5257 Metronic 495257 wifi 802.11ng
+6000 Beholder International Ltd.
+ dec0 TV Wander
+ dec1 TV Voyage
+601a Ingenic Semiconductor Ltd.
+ 4740 XBurst Jz4740 boot mode
+6189 Sitecom
+ 182d USB 2.0 Ethernet
+ 2068 USB to serial cable (v2)
+6244 LightingSoft AG
+ 0101 Intelligent Usb Dmx Interface SIUDI5A
+ 0201 Intelligent Usb Dmx Interface SIUDI5C
+ 0300 Intelligent Usb Dmx Interface SIUDI6 Firmware download
+ 0301 Intelligent Usb Dmx Interface SIUDI6C
+ 0302 Intelligent Usb Dmx Interface SIUDI6A
+ 0303 Intelligent Usb Dmx Interface SIUDI6D
+ 0400 Touch Sensitive Intelligent Control Keypad STICK1A
+ 0401 Touch Sensitive Intelligent Control Keypad STICK1A
+ 0410 Intelligent Usb Dmx Interface SIUDI7 Firmware Download
+ 0411 Intelligent Usb Dmx Interface SIUDI7A
+ 0420 Intelligent Usb Dmx Interface SIUDI8A Firmware Download
+ 0421 Intelligent Usb Dmx Interface SIUDI8A
+ 0430 Intelligent Usb Dmx Interface SIUDI8C Firmware Download
+ 0431 Intelligent Usb Dmx Interface SIUDI8C
+ 0440 Intelligent Usb Dmx Interface SIUDI9A Firmware Download
+ 0441 Intelligent Usb Dmx Interface SIUDI9A
+ 0450 Intelligent Usb Dmx Interface SIUDI9C Firmware Download
+ 0451 Intelligent Usb Dmx Interface SIUDI9C
+ 0460 Touch Sensitive Intelligent Control Keypad STICK2 Firmware download
+ 0461 Touch Sensitive Intelligent Control Keypad STICK2
+ 0470 Touch Sensitive Intelligent Control Keypad STICK1B Firmware download
+ 0471 Touch Sensitive Intelligent Control Keypad STICK1B
+ 0480 Touch Sensitive Intelligent Control Keypad STICK3 Firmware download
+ 0481 Touch Sensitive Intelligent Control Keypad STICK3
+ 0490 Intelligent Usb Dmx Interface SIUDI9D Firmware Download
+ 0491 Intelligent Usb Dmx Interface SIUDI9D
+ 0500 Touch Sensitive Intelligent Control Keypad STICK2B Firmware download
+ 0501 Touch Sensitive Intelligent Control Keypad STICK2B
+6253 TwinHan Technology Co., Ltd
+ 0100 Ir reciver f. remote control
+636c CoreLogic, Inc.
+6472 Unknown (Sony?)
+ 01c8 PlayStation Portable [Mass Storage]
+6547 Arkmicro Technologies Inc.
+ 0232 ARK3116 Serial
+6615 IRTOUCHSYSTEMS Co. Ltd.
+ 0001 Touchscreen
+6666 Prototype product Vendor ID
+ 0667 WiseGroup Smart Joy PSX, PS-PC Smart JoyPad
+ 2667 JCOP BlueZ Smartcard reader
+ 8802 SmartJoy Dual Plus PS2 converter
+ 8804 WiseGroup SuperJoy Box 5
+6677 WiseGroup, Ltd.
+ 8802 SmartJoy Dual Plus PS2 converter
+ 8811 Deluxe Dance Mat
+6891 3Com
+ a727 3CRUSB10075 802.11bg [ZyDAS ZD1211]
+695c Opera1
+ 3829 Opera1 DVB-S (warm state)
+6993 Yealink Network Technology Co., Ltd.
+ b001 VoIP Phone
+6a75 Shanghai Jujo Electronics Co., Ltd
+7104 CME (Central Music Co.)
+ 2202 UF5/UF6/UF7/UF8 MIDI Master Keyboard
+726c StackFoundry LLC
+ 2149 EntropyKing Random Number Generator
+734c TBS Technologies China
+ 5920 Q-Box II DVB-S2 HD
+ 5928 Q-Box II DVB-S2 HD
+7373 Beijing STONE Technology Co. Ltd.
+ 5740 Intelligent TFT-LCD Module
+7392 Edimax Technology Co., Ltd
+ 7711 EW-7711UTn nLite Wireless Adapter [Ralink RT2870]
+ 7717 EW-7717UN 802.11n Wireless Adapter [Ralink RT2870]
+ 7718 EW-7718UN 802.11n Wireless Adapter [Ralink RT2870]
+ 7722 EW-7722UTn 802.11n Wireless Adapter [Ralink RT307x]
+ 7811 EW-7811Un 802.11n Wireless Adapter [Realtek RTL8188CUS]
+8086 Intel Corp.
+ 0001 AnyPoint (TM) Home Network 1.6 Mbps Wireless Adapter
+ 0044 CPU DRAM Controller
+ 0046 HD Graphics
+ 0100 Personal Audio Player 3000
+ 0101 Personal Audio Player 3000
+ 0110 Easy PC Camera
+ 0120 PC Camera CS120
+ 0180 WiMAX Connection 2400m
+ 0181 WiMAX Connection 2400m
+ 0182 WiMAX Connection 2400m
+ 0186 WiMAX Connection 2400m
+ 0188 WiMAX Connection 2400m
+ 0200 AnyPoint(TM) Wireless II Network 11Mbps Adapter [Atmel AT76C503A]
+ 0431 Intel Pro Video PC Camera
+ 0510 Digital Movie Creator
+ 0630 Pocket PC Camera
+ 0780 CS780 Microphone Input
+ 07d3 BLOB boot loader firmware
+ 0dad Cherry MiniatureCard Keyboard
+ 1010 AnyPoint(TM) Home Network 10 Mbps Phoneline Adapter
+ 110a Bluetooth Controller from (Ericsson P4A)
+ 110b Bluetooth Controller from (Intel/CSR)
+ 1110 PRO/Wireless LAN Module
+ 1111 PRO/Wireless 2011B 802.11b Adapter [Intersil PRISM 2.5]
+ 1134 Hollister Mobile Monitor
+ 1139 In-Target Probe (ITP)
+ 1234 Prototype Reader/Writer
+ 1403 WiMAX Connection 2400m
+ 1405 WiMAX Connection 2400m
+ 1406 WiMAX Connection 2400m
+ 2448 82801 PCI Bridge
+ 3100 PRO/DSL 3220 Modem - WAN
+ 3101 PRO/DSL 3220 Modem
+ 3240 AnyPoint® 3240 Modem - WAN
+ 3241 AnyPoint® 3240 Modem
+ 8602 Miniature Card Slot
+ 9303 Intel 8x930Hx Hub
+ 9500 CE 9500 DVB-T
+ 9890 82930 Test Board
+ beef SCM Miniature Card Reader/Writer
+ c013 Wireless HID Station
+ f001 XScale PXA27x Bulverde flash
+ f1a5 Z-U130 [Value Solid State Drive]
+8087 Intel Corp.
+ 0020 Integrated Rate Matching Hub
+ 0024 Integrated Rate Matching Hub
+80ee VirtualBox
+ 0021 USB Tablet
+8282 Keio
+ 3201 Retro Adapter
+ 3301 Retro Adapter Mouse
+8341 EGO Systems, Inc.
+ 2000 Flashdisk
+8564 Transcend Information, Inc.
+ 1000 JetFlash
+ 4000 RDF8
+8644 Intenso GmbG
+ 8003 Micro Line
+ 800b Micro Line (4GB)
+8e06 CH Products, Inc.
+ f700 DT225 Trackball
+9016 Sitecom
+ 182d WL-022 802.11b Adapter
+9022 TeVii Technology Ltd.
+ d630 DVB-S S630
+ d650 DVB-S2 S650
+ d660 DVB-S2 S660
+9148 GeoLab, Ltd
+# All of GeoLab's devices share the same ID 0004.
+ 0004 R3 Compatible Device
+9710 MosChip Semiconductor
+ 7703 MCS7703 Serial Port Adapter
+ 7705 MCS7705 Parallel port adapter
+ 7715 MCS7715 Parallel and serial port adapter
+ 7717 MCS7717 3-port hub with serial and parallel adapter
+ 7720 MCS7720 Dual serial port adapter
+ 7730 MCS7730 10/100 Mbps Ethernet adapter
+ 7780 MCS7780 4Mbps Fast IrDA Adapter
+ 7830 MCS7830 10/100 Mbps Ethernet adapter
+ 7832 MCS7832 10/100 Mbps Ethernet adapter
+ 7840 MCS7820/MCS7840 2/4 port serial adapter
+9849 Bestmedia CD Recordable GmbH & Co. KG
+ 0701 Platinum MyDrive HP
+9999 Odeon
+ 0001 JAF Mobile Phone Flasher Interface
+99fa Grandtec
+ 8988 V.cap Camera Device
+9ac4 J. Westhues
+ 4b8f ProxMark-3 RFID Instrument
+9e88 Marvell Semiconductor, Inc.
+ 9e8f Plug Computer Basic [SheevaPlug]
+a128 AnMo Electronics Corp. / Dino-Lite (?)
+ 0610 Dino-Lite Digital Microscope (SN9C201 + HV7131R)
+ 0611 Dino-Lite Digital Microscope (SN9C201 + HV7131R)
+ 0612 Dino-Lite Digital Microscope (SN9C120 + HV7131R)
+ 0613 Dino-Lite Digital Microscope (SN9C201 + HV7131R)
+ 0614 Dino-Lite Digital Microscope (SN9C201 + MI1310/MT9M111)
+ 0615 Dino-Lite Digital Microscope (SN9C201 + MI1310/MT9M111)
+ 0616 Dino-Lite Digital Microscope (SN9C120 + HV7131R)
+ 0617 Dino-Lite Digital Microscope (SN9C201 + MI1310/MT9M111)
+ 0618 Dino-Lite Digital Microscope (SN9C201 + HV7131R)
+a168 AnMo Electronics Corporation
+ 0610 Dino-Lite Digital Microscope
+ 0611 Dino-Lite Digital Microscope
+ 0613 Dino-Lite Digital Microscope
+ 0614 Dino-Lite Pro Digital Microscope
+ 0615 Dino-Lite Pro Digital Microscope
+ 0617 Dino-Lite Pro Digital Microscope
+ 0618 Dino-Lite Digital Microscope
+a600 Asix
+ e110 OK1ZIA Davac 4.x
+a727 3Com
+ 6893 3CRUSB20075 OfficeConnect Wireless 108Mbps 11g Adapter [Atheros AR5523]
+ 6895 AR5523
+ 6897 AR5523
+aaaa MXT
+ 8815 microSD CardReader
+abcd Unknown
+ cdee Petcam
+b58e Blue Microphones
+ 9e84 Yeti Stereo Microphone
+c216 Card Device Expert Co., LTD
+ 0180 MSR90 MagStripe reader
+c251 Keil Software, Inc.
+ 2710 ULink
+cace CACE Technologies Inc.
+ 0002 AirPCAP Classic 802.11 packet capture adapter
+ 0300 AirPcap NX [Atheros AR9001U-(2)NG]
+cd12 SMART TECHNOLOGY INDUSTRIAL LTD.
+d208 Ultimarc
+ 0310 Mini-PAC Arcade Control Interface
+d209 Ultimarc
+ 0301 I-PAC Arcade Control Interface
+ 0501 Ultra-Stik Ultimarc Ultra-Stik Player 1
+d904 LogiLink
+ 0003 Laser Mouse (ID0009A)
+e4e4 Xorcom Ltd.
+ 1130 Astribank series
+ 1131 Astribank series
+ 1132 Astribank series
+ 1140 Astribank series
+ 1141 Astribank series
+ 1142 Astribank series
+ 1150 Astribank series
+ 1151 Astribank series
+ 1152 Astribank series
+ 1160 Astribank 2 series
+ 1161 Astribank 2 series
+ 1162 Astribank 2 series
+eb03 MakingThings
+ 0920 Make Controller Kit
+eb1a eMPIA Technology, Inc.
+ 17de KWorld V-Stream XPERT DTV - DVB-T USB cold
+ 17df KWorld V-Stream XPERT DTV - DVB-T USB warm
+ 2571 M035 Compact Web Cam
+ 2710 SilverCrest Webcam
+ 2750 ECS Elitegroup G220 integrated Webcam
+ 2761 EeePC 701 integrated Webcam
+ 2776 Combined audio and video input device
+ 2800 Terratec Cinergy 200
+ 2801 GrabBeeX+ Video Encoder
+ 2863 Video Grabber
+ 2870 Pinnacle PCTV Stick
+ 2881 EM2881 Video Controller
+ 50a3 Gadmei UTV380 TV Box
+ 50a6 Gadmei UTV330 TV Box
+ e355 KWorld DVB-T 355U Digital TV Dongle
+eb2a KWorld
+ef18 SMART TECHNOLOGY INDUSTRIAL LTD.
+f003 Hewlett Packard
+ 6002 PhotoSmart C500
+f182 Leap Motion
+ 0003 Controller
+f4ec Atten Electronics / Siglent Technologies
+ ee38 Digital Storage Oscilloscope
+f4ed Shenzhen Siglent Co., Ltd.
+ ee37 SDG1010 Waveform Generator
+ ee3a SDG1010 Waveform Generator (TMC mode)
+f766 Hama
+ 0001 PC-Gamepad "Greystorm"
+fc08 Conrad Electronic SE
+ 0101 MIDI Cable UA0037
+ffee FNK Tech
+ 0100 Card Reader Controller RTS5101/RTS5111/RTS5116
+
+# List of known device classes, subclasses and protocols
+
+# Syntax:
+# C class class_name
+# subclass subclass_name <-- single tab
+# protocol protocol_name <-- two tabs
+
+C 00 (Defined at Interface level)
+C 01 Audio
+ 01 Control Device
+ 02 Streaming
+ 03 MIDI Streaming
+C 02 Communications
+ 01 Direct Line
+ 02 Abstract (modem)
+ 00 None
+ 01 AT-commands (v.25ter)
+ 02 AT-commands (PCCA101)
+ 03 AT-commands (PCCA101 + wakeup)
+ 04 AT-commands (GSM)
+ 05 AT-commands (3G)
+ 06 AT-commands (CDMA)
+ fe Defined by command set descriptor
+ ff Vendor Specific (MSFT RNDIS?)
+ 03 Telephone
+ 04 Multi-Channel
+ 05 CAPI Control
+ 06 Ethernet Networking
+ 07 ATM Networking
+ 08 Wireless Handset Control
+ 09 Device Management
+ 0a Mobile Direct Line
+ 0b OBEX
+ 0c Ethernet Emulation
+ 07 Ethernet Emulation (EEM)
+C 03 Human Interface Device
+ 00 No Subclass
+ 00 None
+ 01 Keyboard
+ 02 Mouse
+ 01 Boot Interface Subclass
+ 00 None
+ 01 Keyboard
+ 02 Mouse
+C 05 Physical Interface Device
+C 06 Imaging
+ 01 Still Image Capture
+ 01 Picture Transfer Protocol (PIMA 15470)
+C 07 Printer
+ 01 Printer
+ 00 Reserved/Undefined
+ 01 Unidirectional
+ 02 Bidirectional
+ 03 IEEE 1284.4 compatible bidirectional
+ ff Vendor Specific
+C 08 Mass Storage
+ 01 RBC (typically Flash)
+ 00 Control/Bulk/Interrupt
+ 01 Control/Bulk
+ 50 Bulk-Only
+ 02 SFF-8020i, MMC-2 (ATAPI)
+ 03 QIC-157
+ 04 Floppy (UFI)
+ 00 Control/Bulk/Interrupt
+ 01 Control/Bulk
+ 50 Bulk-Only
+ 05 SFF-8070i
+ 06 SCSI
+ 00 Control/Bulk/Interrupt
+ 01 Control/Bulk
+ 50 Bulk-Only
+C 09 Hub
+ 00 Unused
+ 00 Full speed (or root) hub
+ 01 Single TT
+ 02 TT per port
+C 0a CDC Data
+ 00 Unused
+ 30 I.430 ISDN BRI
+ 31 HDLC
+ 32 Transparent
+ 50 Q.921M
+ 51 Q.921
+ 52 Q.921TM
+ 90 V.42bis
+ 91 Q.932 EuroISDN
+ 92 V.120 V.24 rate ISDN
+ 93 CAPI 2.0
+ fd Host Based Driver
+ fe CDC PUF
+ ff Vendor specific
+C 0b Chip/SmartCard
+C 0d Content Security
+C 0e Video
+ 00 Undefined
+ 01 Video Control
+ 02 Video Streaming
+ 03 Video Interface Collection
+C 58 Xbox
+ 42 Controller
+C dc Diagnostic
+ 01 Reprogrammable Diagnostics
+ 01 USB2 Compliance
+C e0 Wireless
+ 01 Radio Frequency
+ 01 Bluetooth
+ 02 Ultra WideBand Radio Control
+ 03 RNDIS
+ 02 Wireless USB Wire Adapter
+ 01 Host Wire Adapter Control/Data Streaming
+ 02 Device Wire Adapter Control/Data Streaming
+ 03 Device Wire Adapter Isochronous Streaming
+C ef Miscellaneous Device
+ 01 ?
+ 01 Microsoft ActiveSync
+ 02 Palm Sync
+ 02 ?
+ 01 Interface Association
+ 02 Wire Adapter Multifunction Peripheral
+ 03 ?
+ 01 Cable Based Association
+ 05 USB3 Vision
+C fe Application Specific Interface
+ 01 Device Firmware Update
+ 02 IRDA Bridge
+ 03 Test and Measurement
+ 01 TMC
+ 02 USB488
+C ff Vendor Specific Class
+ ff Vendor Specific Subclass
+ ff Vendor Specific Protocol
+
+# List of Audio Class Terminal Types
+
+# Syntax:
+# AT terminal_type terminal_type_name
+
+AT 0100 USB Undefined
+AT 0101 USB Streaming
+AT 01ff USB Vendor Specific
+AT 0200 Input Undefined
+AT 0201 Microphone
+AT 0202 Desktop Microphone
+AT 0203 Personal Microphone
+AT 0204 Omni-directional Microphone
+AT 0205 Microphone Array
+AT 0206 Processing Microphone Array
+AT 0300 Output Undefined
+AT 0301 Speaker
+AT 0302 Headphones
+AT 0303 Head Mounted Display Audio
+AT 0304 Desktop Speaker
+AT 0305 Room Speaker
+AT 0306 Communication Speaker
+AT 0307 Low Frequency Effects Speaker
+AT 0400 Bidirectional Undefined
+AT 0401 Handset
+AT 0402 Headset
+AT 0403 Speakerphone, no echo reduction
+AT 0404 Echo-suppressing speakerphone
+AT 0405 Echo-canceling speakerphone
+AT 0500 Telephony Undefined
+AT 0501 Phone line
+AT 0502 Telephone
+AT 0503 Down Line Phone
+AT 0600 External Undefined
+AT 0601 Analog Connector
+AT 0602 Digital Audio Interface
+AT 0603 Line Connector
+AT 0604 Legacy Audio Connector
+AT 0605 SPDIF interface
+AT 0606 1394 DA stream
+AT 0607 1394 DV stream soundtrack
+AT 0700 Embedded Undefined
+AT 0701 Level Calibration Noise Source
+AT 0702 Equalization Noise
+AT 0703 CD Player
+AT 0704 DAT
+AT 0705 DCC
+AT 0706 MiniDisc
+AT 0707 Analog Tape
+AT 0708 Phonograph
+AT 0709 VCR Audio
+AT 070a Video Disc Audio
+AT 070b DVD Audio
+AT 070c TV Tuner Audio
+AT 070d Satellite Receiver Audio
+AT 070e Cable Tuner Audio
+AT 070f DSS Audio
+AT 0710 Radio Receiver
+AT 0711 Radio Transmitter
+AT 0712 Multitrack Recorder
+AT 0713 Synthesizer
+
+# List of HID Descriptor Types
+
+# Syntax:
+# HID descriptor_type descriptor_type_name
+
+HID 21 HID
+HID 22 Report
+HID 23 Physical
+
+# List of HID Descriptor Item Types
+# Note: 2 bits LSB encode data length following
+
+# Syntax:
+# R item_type item_type_name
+
+R 04 Usage Page
+R 08 Usage
+R 14 Logical Minimum
+R 18 Usage Minimum
+R 24 Logical Maximum
+R 28 Usage Maximum
+R 34 Physical Minimum
+R 38 Designator Index
+R 44 Physical Maximum
+R 48 Designator Minimum
+R 54 Unit Exponent
+R 58 Designator Maximum
+R 64 Unit
+R 74 Report Size
+R 78 String Index
+R 80 Input
+R 84 Report ID
+R 88 String Minimum
+R 90 Output
+R 94 Report Count
+R 98 String Maximum
+R a0 Collection
+R a4 Push
+R a8 Delimiter
+R b0 Feature
+R b4 Pop
+R c0 End Collection
+
+# List of Physical Descriptor Bias Types
+
+# Syntax:
+# BIAS item_type item_type_name
+
+BIAS 0 Not Applicable
+BIAS 1 Right Hand
+BIAS 2 Left Hand
+BIAS 3 Both Hands
+BIAS 4 Either Hand
+
+# List of Physical Descriptor Item Types
+
+# Syntax:
+# PHY item_type item_type_name
+
+PHY 00 None
+PHY 01 Hand
+PHY 02 Eyeball
+PHY 03 Eyebrow
+PHY 04 Eyelid
+PHY 05 Ear
+PHY 06 Nose
+PHY 07 Mouth
+PHY 08 Upper Lip
+PHY 09 Lower Lip
+PHY 0a Jaw
+PHY 0b Neck
+PHY 0c Upper Arm
+PHY 0d Elbow
+PHY 0e Forearm
+PHY 0f Wrist
+PHY 10 Palm
+PHY 11 Thumb
+PHY 12 Index Finger
+PHY 13 Middle Finger
+PHY 14 Ring Finger
+PHY 15 Little Finger
+PHY 16 Head
+PHY 17 Shoulder
+PHY 18 Hip
+PHY 19 Waist
+PHY 1a Thigh
+PHY 1b Knee
+PHY 1c calf
+PHY 1d Ankle
+PHY 1e Foot
+PHY 1f Heel
+PHY 20 Ball of Foot
+PHY 21 Big Toe
+PHY 22 Second Toe
+PHY 23 Third Toe
+PHY 24 Fourth Toe
+PHY 25 Fifth Toe
+PHY 26 Brow
+PHY 27 Cheek
+
+# List of HID Usages
+
+# Syntax:
+# HUT hi _usage_page hid_usage_page_name
+# hid_usage hid_usage_name
+
+HUT 00 Undefined
+HUT 01 Generic Desktop Controls
+ 000 Undefined
+ 001 Pointer
+ 002 Mouse
+ 004 Joystick
+ 005 Gamepad
+ 006 Keyboard
+ 007 Keypad
+ 008 Multi-Axis Controller
+ 030 Direction-X
+ 031 Direction-Y
+ 032 Direction-Z
+ 033 Rotate-X
+ 034 Rotate-Y
+ 035 Rotate-Z
+ 036 Slider
+ 037 Dial
+ 038 Wheel
+ 039 Hat Switch
+ 03a Counted Buffer
+ 03b Byte Count
+ 03c Motion Wakeup
+ 03d Start
+ 03e Select
+ 040 Vector-X
+ 041 Vector-Y
+ 042 Vector-Z
+ 043 Vector-X relative Body
+ 044 Vector-Y relative Body
+ 045 Vector-Z relative Body
+ 046 Vector
+ 080 System Control
+ 081 System Power Down
+ 082 System Sleep
+ 083 System Wake Up
+ 084 System Context Menu
+ 085 System Main Menu
+ 086 System App Menu
+ 087 System Menu Help
+ 088 System Menu Exit
+ 089 System Menu Select
+ 08a System Menu Right
+ 08b System Menu Left
+ 08c System Menu Up
+ 08d System Menu Down
+ 090 Direction Pad Up
+ 091 Direction Pad Down
+ 092 Direction Pad Right
+ 093 Direction Pad Left
+HUT 02 Simulation Controls
+ 000 Undefined
+ 001 Flight Simulation Device
+ 002 Automobile Simulation Device
+ 003 Tank Simulation Device
+ 004 Spaceship Simulation Device
+ 005 Submarine Simulation Device
+ 006 Sailing Simulation Device
+ 007 Motorcycle Simulation Device
+ 008 Sports Simulation Device
+ 009 Airplane Simualtion Device
+ 00a Helicopter Simulation Device
+ 00b Magic Carpet Simulation Device
+ 00c Bicycle Simulation Device
+ 020 Flight Control Stick
+ 021 Flight Stick
+ 022 Cyclic Control
+ 023 Cyclic Trim
+ 024 Flight Yoke
+ 025 Track Control
+ 0b0 Aileron
+ 0b1 Aileron Trim
+ 0b2 Anti-Torque Control
+ 0b3 Autopilot Enable
+ 0b4 Chaff Release
+ 0b5 Collective Control
+ 0b6 Dive Break
+ 0b7 Electronic Countermeasures
+ 0b8 Elevator
+ 0b9 Elevator Trim
+ 0ba Rudder
+ 0bb Throttle
+ 0bc Flight COmmunications
+ 0bd Flare Release
+ 0be Landing Gear
+ 0bf Toe Break
+ 0c0 Trigger
+ 0c1 Weapon Arm
+ 0c2 Weapons Select
+ 0c3 Wing Flaps
+ 0c4 Accelerator
+ 0c5 Brake
+ 0c6 Clutch
+ 0c7 Shifter
+ 0c8 Steering
+ 0c9 Turret Direction
+ 0ca Barrel Elevation
+ 0cb Drive Plane
+ 0cc Ballast
+ 0cd Bicylce Crank
+ 0ce Handle Bars
+ 0cf Front Brake
+ 0d0 Rear Brake
+HUT 03 VR Controls
+ 000 Unidentified
+ 001 Belt
+ 002 Body Suit
+ 003 Flexor
+ 004 Glove
+ 005 Head Tracker
+ 006 Head Mounted Display
+ 007 Hand Tracker
+ 008 Oculometer
+ 009 Vest
+ 00a Animatronic Device
+ 020 Stereo Enable
+ 021 Display Enable
+HUT 04 Sport Controls
+ 000 Unidentified
+ 001 Baseball Bat
+ 002 Golf Club
+ 003 Rowing Machine
+ 004 Treadmill
+ 030 Oar
+ 031 Slope
+ 032 Rate
+ 033 Stick Speed
+ 034 Stick Face Angle
+ 035 Stick Heel/Toe
+ 036 Stick Follow Through
+ 038 Stick Type
+ 039 Stick Height
+ 047 Stick Temp
+ 050 Putter
+ 051 1 Iron
+ 052 2 Iron
+ 053 3 Iron
+ 054 4 Iron
+ 055 5 Iron
+ 056 6 Iron
+ 057 7 Iron
+ 058 8 Iron
+ 059 9 Iron
+ 05a 10 Iron
+ 05b 11 Iron
+ 05c Sand Wedge
+ 05d Loft Wedge
+ 05e Power Wedge
+ 05f 1 Wood
+ 060 3 Wood
+ 061 5 Wood
+ 062 7 Wood
+ 063 9 Wood
+HUT 05 Game Controls
+ 000 Undefined
+ 001 3D Game Controller
+ 002 Pinball Device
+ 003 Gun Device
+ 020 Point Of View
+ 021 Turn Right/Left
+ 022 Pitch Right/Left
+ 023 Roll Forward/Backward
+ 024 Move Right/Left
+ 025 Move Forward/Backward
+ 026 Move Up/Down
+ 027 Lean Right/Left
+ 028 Lean Forward/Backward
+ 029 Height of POV
+ 02a Flipper
+ 02b Secondary Flipper
+ 02c Bump
+ 02d New Game
+ 02e Shoot Ball
+ 02f Player
+ 030 Gun Bolt
+ 031 Gun Clip
+ 032 Gun Selector
+ 033 Gun Single Shot
+ 034 Gun Burst
+ 035 Gun Automatic
+ 036 Gun Safety
+ 037 Gamepad Fire/Jump
+ 038 Gamepad Fun
+ 039 Gamepad Trigger
+HUT 07 Keyboard
+ 000 No Event
+ 001 Keyboard ErrorRollOver
+ 002 Keyboard POSTfail
+ 003 Keyboard Error Undefined
+ 004 A
+ 005 B
+ 006 C
+ 007 D
+ 008 E
+ 009 F
+ 00a G
+ 00b H
+ 00c I
+ 00d J
+ 00e K
+ 00f L
+ 010 M
+ 011 N
+ 012 O
+ 013 P
+ 014 Q
+ 015 R
+ 016 S
+ 017 T
+ 018 U
+ 019 V
+ 01a W
+ 01b X
+ 01c Y
+ 01d Z
+ 01e 1 and ! (One and Exclamation)
+ 01f 2 and @ (2 and at)
+ 020 3 and # (3 and Hash)
+ 021 4 and $ (4 and Dollar Sign)
+ 022 5 and % (5 and Percent Sign)
+ 023 6 and ^ (6 and circumflex)
+ 024 7 and & (Seven and Ampersand)
+ 025 8 and * (Eight and asterisk)
+ 026 9 and ( (Nine and Parenthesis Left)
+ 027 0 and ) (Zero and Parenthesis Right)
+ 028 Return (Enter)
+ 029 Escape
+ 02a Delete (Backspace)
+ 02b Tab
+ 02c Space Bar
+ 02d - and _ (Minus and underscore)
+ 02e = and + (Equal and Plus)
+ 02f [ and { (Bracket and Braces Left)
+ 030 ] and } (Bracket and Braces Right)
+ 031 \ and | (Backslash and Bar)
+ 032 # and ~ (Hash and Tilde, Non-US Keyboard near right shift)
+ 033 ; and : (Semicolon and Colon)
+ 034 ´ and " (Accent Acute and Double Quotes)
+ 035 ` and ~ (Accent Grace and Tilde)
+ 036 , and < (Comma and Less)
+ 037 . and > (Period and Greater)
+ 038 / and ? (Slash and Question Mark)
+ 039 Caps Lock
+ 03a F1
+ 03b F2
+ 03c F3
+ 03d F4
+ 03e F5
+ 03f F6
+ 040 F7
+ 041 F8
+ 042 F9
+ 043 F10
+ 044 F11
+ 045 F12
+ 046 Print Screen
+ 047 Scroll Lock
+ 048 Pause
+ 049 Insert
+ 04a Home
+ 04b Page Up
+ 04c Delete Forward (without Changing Position)
+ 04d End
+ 04e Page Down
+ 04f Right Arrow
+ 050 Left Arrow
+ 051 Down Arrow
+ 052 Up Arrow
+ 053 Num Lock and Clear
+ 054 Keypad / (Division Sign)
+ 055 Keypad * (Multiplication Sign)
+ 056 Keypad - (Subtraction Sign)
+ 057 Keypad + (Addition Sign)
+ 058 Keypad Enter
+ 059 Keypad 1 and END
+ 05a Keypad 2 and Down Arrow
+ 05b Keypad 3 and Page Down
+ 05c Keypad 4 and Left Arrow
+ 05d Keypad 5 (Tactilei Raised)
+ 05f Keypad 6 and Right Arrow
+ 060 Keypad 7 and Home
+ 061 Keypad 8 and Up Arrow
+ 062 Keypad 8 and Page Up
+ 063 Keypad . (decimal delimiter) and Delete
+ 064 \ and | (Backslash and Bar, UK and Non-US Keyboard near left shift)
+ 065 Keyboard Application (Windows Key for Win95 or Compose)
+ 066 Power (not a key)
+ 067 Keypad = (Equal Sign)
+ 068 F13
+ 069 F14
+ 06a F15
+ 06b F16
+ 06c F17
+ 06d F18
+ 06e F19
+ 06f F20
+ 070 F21
+ 071 F22
+ 072 F23
+ 073 F24
+ 074 Execute
+ 075 Help
+ 076 Menu
+ 077 Select
+ 078 Stop
+ 079 Again
+ 07a Undo
+ 07b Cut
+ 07c Copy
+ 07d Paste
+ 07e Find
+ 07f Mute
+ 080 Volume Up
+ 081 Volume Down
+ 082 Locking Caps Lock
+ 083 Locking Num Lock
+ 084 Locking Scroll Lock
+ 085 Keypad Comma
+ 086 Keypad Equal Sign (AS/400)
+ 087 International 1 (PC98)
+ 088 International 2 (PC98)
+ 089 International 3 (PC98)
+ 08a International 4 (PC98)
+ 08b International 5 (PC98)
+ 08c International 6 (PC98)
+ 08d International 7 (Toggle Single/Double Byte Mode)
+ 08e International 8
+ 08f International 9
+ 090 LANG 1 (Hangul/English Toggle, Korea)
+ 091 LANG 2 (Hanja Conversion, Korea)
+ 092 LANG 3 (Katakana, Japan)
+ 093 LANG 4 (Hiragana, Japan)
+ 094 LANG 5 (Zenkaku/Hankaku, Japan)
+ 095 LANG 6
+ 096 LANG 7
+ 097 LANG 8
+ 098 LANG 9
+ 099 Alternate Erase
+ 09a SysReq/Attention
+ 09b Cancel
+ 09c Clear
+ 09d Prior
+ 09e Return
+ 09f Separator
+ 0a0 Out
+ 0a1 Open
+ 0a2 Clear/Again
+ 0a3 CrSel/Props
+ 0a4 ExSel
+ 0e0 Control Left
+ 0e1 Shift Left
+ 0e2 Alt Left
+ 0e3 GUI Left
+ 0e4 Control Right
+ 0e5 Shift Right
+ 0e6 Alt Rigth
+ 0e7 GUI Right
+HUT 08 LEDs
+ 000 Undefined
+ 001 NumLock
+ 002 CapsLock
+ 003 Scroll Lock
+ 004 Compose
+ 005 Kana
+ 006 Power
+ 007 Shift
+ 008 Do not disturb
+ 009 Mute
+ 00a Tone Enabke
+ 00b High Cut Filter
+ 00c Low Cut Filter
+ 00d Equalizer Enable
+ 00e Sound Field ON
+ 00f Surround On
+ 010 Repeat
+ 011 Stereo
+ 012 Sampling Rate Detect
+ 013 Spinning
+ 014 CAV
+ 015 CLV
+ 016 Recording Format Detect
+ 017 Off-Hook
+ 018 Ring
+ 019 Message Waiting
+ 01a Data Mode
+ 01b Battery Operation
+ 01c Battery OK
+ 01d Battery Low
+ 01e Speaker
+ 01f Head Set
+ 020 Hold
+ 021 Microphone
+ 022 Coverage
+ 023 Night Mode
+ 024 Send Calls
+ 025 Call Pickup
+ 026 Conference
+ 027 Stand-by
+ 028 Camera On
+ 029 Camera Off
+ 02a On-Line
+ 02b Off-Line
+ 02c Busy
+ 02d Ready
+ 02e Paper-Out
+ 02f Paper-Jam
+ 030 Remote
+ 031 Forward
+ 032 Reverse
+ 033 Stop
+ 034 Rewind
+ 035 Fast Forward
+ 036 Play
+ 037 Pause
+ 038 Record
+ 039 Error
+ 03a Usage Selected Indicator
+ 03b Usage In Use Indicator
+ 03c Usage Multi Indicator
+ 03d Indicator On
+ 03e Indicator Flash
+ 03f Indicator Slow Blink
+ 040 Indicator Fast Blink
+ 041 Indicator Off
+ 042 Flash On Time
+ 043 Slow Blink On Time
+ 044 Slow Blink Off Time
+ 045 Fast Blink On Time
+ 046 Fast Blink Off Time
+ 047 Usage Color Indicator
+ 048 Indicator Red
+ 049 Indicator Green
+ 04a Indicator Amber
+ 04b Generic Indicator
+ 04c System Suspend
+ 04d External Power Connected
+HUT 09 Buttons
+ 000 No Button Pressed
+ 001 Button 1 (Primary)
+ 002 Button 2 (Secondary)
+ 003 Button 3 (Tertiary)
+ 004 Button 4
+ 005 Button 5
+HUT 0a Ordinal
+ 001 Instance 1
+ 002 Instance 2
+ 003 Instance 3
+HUT 0b Telephony
+ 000 Unassigned
+ 001 Phone
+ 002 Answering Machine
+ 003 Message Controls
+ 004 Handset
+ 005 Headset
+ 006 Telephony Key Pad
+ 007 Programmable Button
+ 020 Hook Switch
+ 021 Flash
+ 022 Feature
+ 023 Hold
+ 024 Redial
+ 025 Transfer
+ 026 Drop
+ 027 Park
+ 028 Forward Calls
+ 029 Alternate Function
+ 02a Line
+ 02b Speaker Phone
+ 02c Conference
+ 02d Ring Enable
+ 02e Ring Select
+ 02f Phone Mute
+ 030 Caller ID
+ 050 Speed Dial
+ 051 Store Number
+ 052 Recall Number
+ 053 Phone Directory
+ 070 Voice Mail
+ 071 Screen Calls
+ 072 Do Not Disturb
+ 073 Message
+ 074 Answer On/Offf
+ 090 Inside Dial Tone
+ 091 Outside Dial Tone
+ 092 Inside Ring Tone
+ 093 Outside Ring Tone
+ 094 Priority Ring Tone
+ 095 Inside Ringback
+ 096 Priority Ringback
+ 097 Line Busy Tone
+ 098 Recorder Tone
+ 099 Call Waiting Tone
+ 09a Confirmation Tone 1
+ 09b Confirmation Tone 2
+ 09c Tones Off
+ 09d Outside Ringback
+ 0b0 Key 1
+ 0b1 Key 2
+ 0b3 Key 3
+ 0b4 Key 4
+ 0b5 Key 5
+ 0b6 Key 6
+ 0b7 Key 7
+ 0b8 Key 8
+ 0b9 Key 9
+ 0ba Key Star
+ 0bb Key Pound
+ 0bc Key A
+ 0bd Key B
+ 0be Key C
+ 0bf Key D
+HUT 0c Consumer
+ 000 Unassigned
+ 001 Consumer Control
+ 002 Numeric Key Pad
+ 003 Programmable Buttons
+ 020 +10
+ 021 +100
+ 022 AM/PM
+ 030 Power
+ 031 Reset
+ 032 Sleep
+ 033 Sleep After
+ 034 Sleep Mode
+ 035 Illumination
+ 036 Function Buttons
+ 040 Menu
+ 041 Menu Pick
+ 042 Menu Up
+ 043 Menu Down
+ 044 Menu Left
+ 045 Menu Right
+ 046 Menu Escape
+ 047 Menu Value Increase
+ 048 Menu Value Decrease
+ 060 Data on Screen
+ 061 Closed Caption
+ 062 Closed Caption Select
+ 063 VCR/TV
+ 064 Broadcast Mode
+ 065 Snapshot
+ 066 Still
+ 080 Selection
+ 081 Assign Selection
+ 082 Mode Step
+ 083 Recall Last
+ 084 Enter Channel
+ 085 Order Movie
+ 086 Channel
+ 087 Media Selection
+ 088 Media Select Computer
+ 089 Media Select TV
+ 08a Media Select WWW
+ 08b Media Select DVD
+ 08c Media Select Telephone
+ 08d Media Select Program Guide
+ 08e Media Select Video Phone
+ 08f Media Select Games
+ 090 Media Select Messages
+ 091 Media Select CD
+ 092 Media Select VCR
+ 093 Media Select Tuner
+ 094 Quit
+ 095 Help
+ 096 Media Select Tape
+ 097 Media Select Cable
+ 098 Media Select Satellite
+ 099 Media Select Security
+ 09a Media Select Home
+ 09b Media Select Call
+ 09c Channel Increment
+ 09d Channel Decrement
+ 09e Media Select SAP
+ 0a0 VCR Plus
+ 0a1 Once
+ 0a2 Daily
+ 0a3 Weekly
+ 0a4 Monthly
+ 0b0 Play
+ 0b1 Pause
+ 0b2 Record
+ 0b3 Fast Forward
+ 0b4 Rewind
+ 0b5 Scan Next Track
+ 0b6 Scan Previous Track
+ 0b7 Stop
+ 0b8 Eject
+ 0b9 Random Play
+ 0ba Select Disc
+ 0bb Enter Disc
+ 0bc Repeat
+ 0bd Tracking
+ 0be Track Normal
+ 0bf Slow Tracking
+ 0c0 Frame Forward
+ 0c1 Frame Back
+ 0c2 Mark
+ 0c3 Clear Mark
+ 0c4 Repeat from Mark
+ 0c5 Return to Mark
+ 0c6 Search Mark Forward
+ 0c7 Search Mark Backward
+ 0c8 Counter Reset
+ 0c9 Show Counter
+ 0ca Tracking Increment
+ 0cb Tracking Decrement
+ 0cc Stop/Eject
+ 0cd Play/Pause
+ 0ce Play/Skip
+ 0e0 Volume
+ 0e1 Balance
+ 0e2 Mute
+ 0e3 Bass
+ 0e4 Treble
+ 0e5 Bass Boost
+ 0e6 Surround Mode
+ 0e7 Loudness
+ 0e8 MPX
+ 0e9 Volume Increment
+ 0ea Volume Decrement
+ 0f0 Speed Select
+ 0f1 Playback Speed
+ 0f2 Standard Play
+ 0f3 Long Play
+ 0f4 Extended Play
+ 0f5 Slow
+ 100 Fan Enable
+ 101 Fan Speed
+ 102 Light Enable
+ 103 Light Illumination Level
+ 104 Climate Control Enable
+ 105 Room Temperature
+ 106 Security Enable
+ 107 Fire Alarm
+ 108 Police Alarm
+ 150 Balance Right
+ 151 Balance Left
+ 152 Bass Increment
+ 153 Bass Decrement
+ 154 Treble Increment
+ 155 Treble Decrement
+ 160 Speaker System
+ 161 Channel Left
+ 162 Channel Right
+ 163 Channel Center
+ 164 Channel Front
+ 165 Channel Center Front
+ 166 Channel Side
+ 167 Channel Surround
+ 168 Channel Low Frequency Enhancement
+ 169 Channel Top
+ 16a Channel Unknown
+ 170 Sub-Channel
+ 171 Sub-Channel Increment
+ 172 Sub-Channel Decrement
+ 173 Alternative Audio Increment
+ 174 Alternative Audio Decrement
+ 180 Application Launch Buttons
+ 181 AL Launch Button Configuration Tool
+ 182 AL Launch Button Configuration
+ 183 AL Consumer Control Configuration
+ 184 AL Word Processor
+ 185 AL Text Editor
+ 186 AL Spreadsheet
+ 187 AL Graphics Editor
+ 188 AL Presentation App
+ 189 AL Database App
+ 18a AL Email Reader
+ 18b AL Newsreader
+ 18c AL Voicemail
+ 18d AL Contacts/Address Book
+ 18e AL Calendar/Schedule
+ 18f AL Task/Project Manager
+ 190 AL Log/Jounal/Timecard
+ 191 AL Checkbook/Finance
+ 192 AL Calculator
+ 193 AL A/V Capture/Playback
+ 194 AL Local Machine Browser
+ 195 AL LAN/Wan Browser
+ 196 AL Internet Browser
+ 197 AL Remote Networking/ISP Connect
+ 198 AL Network Conference
+ 199 AL Network Chat
+ 19a AL Telephony/Dialer
+ 19b AL Logon
+ 19c AL Logoff
+ 19d AL Logon/Logoff
+ 19e AL Terminal Local/Screensaver
+ 19f AL Control Panel
+ 1a0 AL Command Line Processor/Run
+ 1a1 AL Process/Task Manager
+ 1a2 AL Select Task/Application
+ 1a3 AL Next Task/Application
+ 1a4 AL Previous Task/Application
+ 1a5 AL Preemptive Halt Task/Application
+ 200 Generic GUI Application Controls
+ 201 AC New
+ 202 AC Open
+ 203 AC CLose
+ 204 AC Exit
+ 205 AC Maximize
+ 206 AC Minimize
+ 207 AC Save
+ 208 AC Print
+ 209 AC Properties
+ 21a AC Undo
+ 21b AC Copy
+ 21c AC Cut
+ 21d AC Paste
+ 21e AC Select All
+ 21f AC Find
+ 220 AC Find and Replace
+ 221 AC Search
+ 222 AC Go To
+ 223 AC Home
+ 224 AC Back
+ 225 AC Forward
+ 226 AC Stop
+ 227 AC Refresh
+ 228 AC Previous Link
+ 229 AC Next Link
+ 22b AC History
+ 22c AC Subscriptions
+ 22d AC Zoom In
+ 22e AC Zoom Out
+ 22f AC Zoom
+ 230 AC Full Screen View
+ 231 AC Normal View
+ 232 AC View Toggle
+ 233 AC Scroll Up
+ 234 AC Scroll Down
+ 235 AC Scroll
+ 236 AC Pan Left
+ 237 AC Pan Right
+ 238 AC Pan
+ 239 AC New Window
+ 23a AC Tile Horizontally
+ 23b AC Tile Vertically
+ 23c AC Format
+HUT 0d Digitizer
+ 000 Undefined
+ 001 Digitizer
+ 002 Pen
+ 003 Light Pen
+ 004 Touch Screen
+ 005 Touch Pad
+ 006 White Board
+ 007 Coordinate Measuring Machine
+ 008 3D Digitizer
+ 009 Stereo Plotter
+ 00a Articulated Arm
+ 00b Armature
+ 00c Multiple Point Digitizer
+ 00d Free Space Wand
+ 020 Stylus
+ 021 Puck
+ 022 Finger
+ 030 Tip Pressure
+ 031 Barrel Pressure
+ 032 In Range
+ 033 Touch
+ 034 Untouch
+ 035 Tap
+ 036 Quality
+ 037 Data Valid
+ 038 Transducer Index
+ 039 Tablet Function Keys
+ 03a Program Change Keys
+ 03b Battery Strength
+ 03c Invert
+ 03d X Tilt
+ 03e Y Tilt
+ 03f Azimuth
+ 040 Altitude
+ 041 Twist
+ 042 Tip Switch
+ 043 Secondary Tip Switch
+ 044 Barrel Switch
+ 045 Eraser
+ 046 Tablet Pick
+ 047 Confidence
+ 048 Width
+ 049 Height
+ 051 Contact ID
+ 052 Input Mode
+ 053 Device Index
+ 054 Contact Count
+ 055 Maximum Contact Number
+HUT 0f PID Page
+ 000 Undefined
+ 001 Physical Interface Device
+ 020 Normal
+ 021 Set Effect Report
+ 022 Effect Block Index
+ 023 Parameter Block Offset
+ 024 ROM Flag
+ 025 Effect Type
+ 026 ET Constant Force
+ 027 ET Ramp
+ 028 ET Custom Force Data
+ 030 ET Square
+ 031 ET Sine
+ 032 ET Triangle
+ 033 ET Sawtooth Up
+ 034 ET Sawtooth Down
+ 040 ET Spring
+ 041 ET Damper
+ 042 ET Inertia
+ 043 ET Friction
+ 050 Duration
+ 051 Sample Period
+ 052 Gain
+ 053 Trigger Button
+ 054 Trigger Repeat Interval
+ 055 Axes Enable
+ 056 Direction Enable
+ 057 Direction
+ 058 Type Specific Block Offset
+ 059 Block Type
+ 05A Set Envelope Report
+ 05B Attack Level
+ 05C Attack Time
+ 05D Fade Level
+ 05E Fade Time
+ 05F Set Condition Report
+ 060 CP Offset
+ 061 Positive Coefficient
+ 062 Negative Coefficient
+ 063 Positive Saturation
+ 064 Negative Saturation
+ 065 Dead Band
+ 066 Download Force Sample
+ 067 Isoch Custom Force Enable
+ 068 Custom Force Data Report
+ 069 Custom Force Data
+ 06A Custom Force Vendor Defined Data
+ 06B Set Custom Force Report
+ 06C Custom Force Data Offset
+ 06D Sample Count
+ 06E Set Periodic Report
+ 06F Offset
+ 070 Magnitude
+ 071 Phase
+ 072 Period
+ 073 Set Constant Force Report
+ 074 Set Ramp Force Report
+ 075 Ramp Start
+ 076 Ramp End
+ 077 Effect Operation Report
+ 078 Effect Operation
+ 079 Op Effect Start
+ 07A Op Effect Start Solo
+ 07B Op Effect Stop
+ 07C Loop Count
+ 07D Device Gain Report
+ 07E Device Gain
+ 07F PID Pool Report
+ 080 RAM Pool Size
+ 081 ROM Pool Size
+ 082 ROM Effect Block Count
+ 083 Simultaneous Effects Max
+ 084 Pool Alignment
+ 085 PID Pool Move Report
+ 086 Move Source
+ 087 Move Destination
+ 088 Move Length
+ 089 PID Block Load Report
+ 08B Block Load Status
+ 08C Block Load Success
+ 08D Block Load Full
+ 08E Block Load Error
+ 08F Block Handle
+ 090 PID Block Free Report
+ 091 Type Specific Block Handle
+ 092 PID State Report
+ 094 Effect Playing
+ 095 PID Device Control Report
+ 096 PID Device Control
+ 097 DC Enable Actuators
+ 098 DC Disable Actuators
+ 099 DC Stop All Effects
+ 09A DC Device Reset
+ 09B DC Device Pause
+ 09C DC Device Continue
+ 09F Device Paused
+ 0A0 Actuators Enabled
+ 0A4 Safety Switch
+ 0A5 Actuator Override Switch
+ 0A6 Actuator Power
+ 0A7 Start Delay
+ 0A8 Parameter Block Size
+ 0A9 Device Managed Pool
+ 0AA Shared Parameter Blocks
+ 0AB Create New Effect Report
+ 0AC RAM Pool Available
+HUT 10 Unicode
+HUT 14 Alphanumeric Display
+ 000 Undefined
+ 001 Alphanumeric Display
+ 020 Display Attributes Report
+ 021 ASCII Character Set
+ 022 Data Read Back
+ 023 Font Read Back
+ 024 Display Control Report
+ 025 Clear Display
+ 026 Display Enable
+ 027 Screen Saver Delay
+ 028 Screen Saver Enable
+ 029 Vertical Scroll
+ 02a Horizontal Scroll
+ 02b Character Report
+ 02c Display Data
+ 02d Display Status
+ 02e Stat Not Ready
+ 02f Stat Ready
+ 030 Err Not a loadable Character
+ 031 Err Font Data Cannot Be Read
+ 032 Cursur Position Report
+ 033 Row
+ 034 Column
+ 035 Rows
+ 036 Columns
+ 037 Cursor Pixel Positioning
+ 038 Cursor Mode
+ 039 Cursor Enable
+ 03a Cursor Blink
+ 03b Font Report
+ 03c Font Data
+ 03d Character Width
+ 03e Character Height
+ 03f Character Spacing Horizontal
+ 040 Character Spacing Vertical
+ 041 Unicode Character Set
+HUT 80 USB Monitor
+ 001 Monitor Control
+ 002 EDID Information
+ 003 VDIF Information
+ 004 VESA Version
+HUT 81 USB Monitor Enumerated Values
+HUT 82 Monitor VESA Virtual Controls
+ 001 Degauss
+ 010 Brightness
+ 012 Contrast
+ 016 Red Video Gain
+ 018 Green Video Gain
+ 01a Blue Video Gain
+ 01c Focus
+ 020 Horizontal Position
+ 022 Horizontal Size
+ 024 Horizontal Pincushion
+ 026 Horizontal Pincushion Balance
+ 028 Horizontal Misconvergence
+ 02a Horizontal Linearity
+ 02c Horizontal Linearity Balance
+ 030 Vertical Position
+ 032 Vertical Size
+ 034 Vertical Pincushion
+ 036 Vertical Pincushion Balance
+ 038 Vertical Misconvergence
+ 03a Vertical Linearity
+ 03c Vertical Linearity Balance
+ 040 Parallelogram Balance (Key Distortion)
+ 042 Trapezoidal Distortion (Key)
+ 044 Tilt (Rotation)
+ 046 Top Corner Distortion Control
+ 048 Top Corner Distortion Balance
+ 04a Bottom Corner Distortion Control
+ 04c Bottom Corner Distortion Balance
+ 056 Horizontal Moire
+ 058 Vertical Moire
+ 05e Input Level Select
+ 060 Input Source Select
+ 06c Red Video Black Level
+ 06e Green Video Black Level
+ 070 Blue Video Black Level
+ 0a2 Auto Size Center
+ 0a4 Polarity Horizontal Sychronization
+ 0a6 Polarity Vertical Synchronization
+ 0aa Screen Orientation
+ 0ac Horizontal Frequency in Hz
+ 0ae Vertical Frequency in 0.1 Hz
+ 0b0 Settings
+ 0ca On Screen Display (OSD)
+ 0d4 Stereo Mode
+HUT 84 Power Device Page
+ 000 Undefined
+ 001 iName
+ 002 Present Status
+ 003 Changed Status
+ 004 UPS
+ 005 Power Supply
+ 010 Battery System
+ 011 Battery System ID
+ 012 Battery
+ 013 Battery ID
+ 014 Charger
+ 015 Charger ID
+ 016 Power Converter
+ 017 Power Converter ID
+ 018 Outlet System
+ 019 Outlet System ID
+ 01a Input
+ 01b Input ID
+ 01c Output
+ 01d Output ID
+ 01e Flow
+ 01f Flow ID
+ 020 Outlet
+ 021 Outlet ID
+ 022 Gang
+ 023 Gang ID
+ 024 Power Summary
+ 025 Power Summary ID
+ 030 Voltage
+ 031 Current
+ 032 Frequency
+ 033 Apparent Power
+ 034 Active Power
+ 035 Percent Load
+ 036 Temperature
+ 037 Humidity
+ 038 Bad Count
+ 040 Config Voltage
+ 041 Config Current
+ 042 Config Frequency
+ 043 Config Apparent Power
+ 044 Config Active Power
+ 045 Config Percent Load
+ 046 Config Temperature
+ 047 Config Humidity
+ 050 Switch On Control
+ 051 Switch Off Control
+ 052 Toggle Control
+ 053 Low Voltage Transfer
+ 054 High Voltage Transfer
+ 055 Delay Before Reboot
+ 056 Delay Before Startup
+ 057 Delay Before Shutdown
+ 058 Test
+ 059 Module Reset
+ 05a Audible Alarm Control
+ 060 Present
+ 061 Good
+ 062 Internal Failure
+ 063 Voltage out of range
+ 064 Frequency out of range
+ 065 Overload
+ 066 Over Charged
+ 067 Over Temperature
+ 068 Shutdown Requested
+ 069 Shutdown Imminent
+ 06a Reserved
+ 06b Switch On/Off
+ 06c Switchable
+ 06d Used
+ 06e Boost
+ 06f Buck
+ 070 Initialized
+ 071 Tested
+ 072 Awaiting Power
+ 073 Communication Lost
+ 0fd iManufacturer
+ 0fe iProduct
+ 0ff iSerialNumber
+HUT 85 Battery System Page
+ 000 Undefined
+ 001 SMB Battery Mode
+ 002 SMB Battery Status
+ 003 SMB Alarm Warning
+ 004 SMB Charger Mode
+ 005 SMB Charger Status
+ 006 SMB Charger Spec Info
+ 007 SMB Selector State
+ 008 SMB Selector Presets
+ 009 SMB Selector Info
+ 010 Optional Mfg. Function 1
+ 011 Optional Mfg. Function 2
+ 012 Optional Mfg. Function 3
+ 013 Optional Mfg. Function 4
+ 014 Optional Mfg. Function 5
+ 015 Connection to SMBus
+ 016 Output Connection
+ 017 Charger Connection
+ 018 Battery Insertion
+ 019 Use Next
+ 01a OK to use
+ 01b Battery Supported
+ 01c SelectorRevision
+ 01d Charging Indicator
+ 028 Manufacturer Access
+ 029 Remaining Capacity Limit
+ 02a Remaining Time Limit
+ 02b At Rate
+ 02c Capacity Mode
+ 02d Broadcast To Charger
+ 02e Primary Battery
+ 02f Charge Controller
+ 040 Terminate Charge
+ 041 Terminate Discharge
+ 042 Below Remaining Capacity Limit
+ 043 Remaining Time Limit Expired
+ 044 Charging
+ 045 Discharging
+ 046 Fully Charged
+ 047 Fully Discharged
+ 048 Conditioning Flag
+ 049 At Rate OK
+ 04a SMB Error Code
+ 04b Need Replacement
+ 060 At Rate Time To Full
+ 061 At Rate Time To Empty
+ 062 Average Current
+ 063 Max Error
+ 064 Relative State Of Charge
+ 065 Absolute State Of Charge
+ 066 Remaining Capacity
+ 067 Full Charge Capacity
+ 068 Run Time To Empty
+ 069 Average Time To Empty
+ 06a Average Time To Full
+ 06b Cycle Count
+ 080 Batt. Pack Model Level
+ 081 Internal Charge Controller
+ 082 Primary Battery Support
+ 083 Design Capacity
+ 084 Specification Info
+ 085 Manufacturer Date
+ 086 Serial Number
+ 087 iManufacturerName
+ 088 iDeviceName
+ 089 iDeviceChemistry
+ 08a Manufacturer Data
+ 08b Rechargeable
+ 08c Warning Capacity Limit
+ 08d Capacity Granularity 1
+ 08e Capacity Granularity 2
+ 08f iOEMInformation
+ 0c0 Inhibit Charge
+ 0c1 Enable Polling
+ 0c2 Reset To Zero
+ 0d0 AC Present
+ 0d1 Battery Present
+ 0d2 Power Fail
+ 0d3 Alarm Inhibited
+ 0d4 Thermistor Under Range
+ 0d5 Thermistor Hot
+ 0d6 Thermistor Cold
+ 0d7 Thermistor Over Range
+ 0d8 Voltage Out Of Range
+ 0d9 Current Out Of Range
+ 0da Current Not Regulated
+ 0db Voltage Not Regulated
+ 0dc Master Mode
+ 0f0 Charger Selector Support
+ 0f1 Charger Spec
+ 0f2 Level 2
+ 0f3 Level 3
+HUT 86 Power Pages
+HUT 87 Power Pages
+HUT 8c Bar Code Scanner Page (POS)
+HUT 8d Scale Page (POS)
+HUT 90 Camera Control Page
+HUT 91 Arcade Control Page
+HUT f0 Cash Device
+ 0f1 Cash Drawer
+ 0f2 Cash Drawer Number
+ 0f3 Cash Drawer Set
+ 0f4 Cash Drawer Status
+HUT ff Vendor Specific
+
+# List of Languages
+
+# Syntax:
+# L language_id language_name
+# dialect_id dialect_name
+
+L 0001 Arabic
+ 01 Saudi Arabia
+ 02 Iraq
+ 03 Egypt
+ 04 Libya
+ 05 Algeria
+ 06 Morocco
+ 07 Tunesia
+ 08 Oman
+ 09 Yemen
+ 0a Syria
+ 0b Jordan
+ 0c Lebanon
+ 0d Kuwait
+ 0e U.A.E
+ 0f Bahrain
+ 10 Qatar
+L 0002 Bulgarian
+L 0003 Catalan
+L 0004 Chinese
+ 01 Traditional
+ 02 Simplified
+ 03 Hongkong SAR, PRC
+ 04 Singapore
+ 05 Macau SAR
+L 0005 Czech
+L 0006 Danish
+L 0007 German
+ 01 German
+ 02 Swiss
+ 03 Austrian
+ 04 Luxembourg
+ 05 Liechtenstein
+L 0008 Greek
+L 0009 English
+ 01 US
+ 02 UK
+ 03 Australian
+ 04 Canadian
+ 05 New Zealand
+ 06 Ireland
+ 07 South Africa
+ 08 Jamaica
+ 09 Carribean
+ 0a Belize
+ 0b Trinidad
+ 0c Zimbabwe
+ 0d Philippines
+L 000a Spanish
+ 01 Castilian
+ 02 Mexican
+ 03 Modern
+ 04 Guatemala
+ 05 Costa Rica
+ 06 Panama
+ 07 Dominican Republic
+ 08 Venzuela
+ 09 Colombia
+ 0a Peru
+ 0b Argentina
+ 0c Ecuador
+ 0d Chile
+ 0e Uruguay
+ 0f Paraguay
+ 10 Bolivia
+ 11 El Salvador
+ 12 Honduras
+ 13 Nicaragua
+ 14 Puerto Rico
+L 000b Finnish
+L 000c French
+ 01 French
+ 02 Belgian
+ 03 Canadian
+ 04 Swiss
+ 05 Luxembourg
+ 06 Monaco
+L 000d Hebrew
+L 000e Hungarian
+L 000f Idelandic
+L 0010 Italian
+ 01 Italian
+ 02 Swiss
+L 0011 Japanese
+L 0012 Korean
+ 01 Korean
+L 0013 Dutch
+ 01 Dutch
+ 02 Belgian
+L 0014 Norwegian
+ 01 Bokmal
+ 02 Nynorsk
+L 0015 Polish
+L 0016 Portuguese
+ 01 Portuguese
+ 02 Brazilian
+L 0017 forgotten
+L 0018 Romanian
+L 0019 Russian
+L 001a Serbian
+ 01 Croatian
+ 02 Latin
+ 03 Cyrillic
+L 001b Slovak
+L 001c Albanian
+L 001d Swedish
+ 01 Swedish
+ 02 Finland
+L 001e Thai
+L 001f Turkish
+L 0020 Urdu
+ 01 Pakistan
+ 02 India
+L 0021 Indonesian
+L 0022 Ukrainian
+L 0023 Belarusian
+L 0024 Slovenian
+L 0025 Estonian
+L 0026 Latvian
+L 0027 Lithuanian
+ 01 Lithuanian
+L 0028 forgotten
+L 0029 Farsi
+L 002a Vietnamese
+L 002b Armenian
+L 002c Azeri
+ 01 Cyrillic
+ 02 Latin
+L 002d Basque
+L 002e forgotten
+L 002f Macedonian
+L 0036 Afrikaans
+L 0037 Georgian
+L 0038 Faeroese
+L 0039 Hindi
+L 003e Malay
+ 01 Malaysia
+ 02 Brunei Darassalam
+L 003f Kazak
+L 0041 Awahili
+L 0043 Uzbek
+ 01 Latin
+ 02 Cyrillic
+L 0044 Tatar
+L 0045 Bengali
+L 0046 Punjabi
+L 0047 Gujarati
+L 0048 Oriya
+L 0049 Tamil
+L 004a Telugu
+L 004b Kannada
+L 004c Malayalam
+L 004d Assamese
+L 004e Marathi
+L 004f Sanskrit
+L 0057 Konkani
+L 0058 Manipuri
+L 0059 Sindhi
+L 0060 Kashmiri
+ 02 India
+L 0061 Nepali
+ 02 India
+
+# HID Descriptor bCountryCode
+# HID Specification 1.11 (2001-06-27) page 23
+#
+# Syntax:
+# HCC country_code keymap_type
+
+HCC 00 Not supported
+HCC 01 Arabic
+HCC 02 Belgian
+HCC 03 Canadian-Bilingual
+HCC 04 Canadian-French
+HCC 05 Czech Republic
+HCC 06 Danish
+HCC 07 Finnish
+HCC 08 French
+HCC 09 German
+HCC 10 Greek
+HCC 11 Hebrew
+HCC 12 Hungary
+HCC 13 International (ISO)
+HCC 14 Italian
+HCC 15 Japan (Katakana)
+HCC 16 Korean
+HCC 17 Latin American
+HCC 18 Netherlands/Dutch
+HCC 19 Norwegian
+HCC 20 Persian (Farsi)
+HCC 21 Poland
+HCC 22 Portuguese
+HCC 23 Russia
+HCC 24 Slovakia
+HCC 25 Spanish
+HCC 26 Swedish
+HCC 27 Swiss/French
+HCC 28 Swiss/German
+HCC 29 Switzerland
+HCC 30 Taiwan
+HCC 31 Turkish-Q
+HCC 32 UK
+HCC 33 US
+HCC 34 Yugoslavia
+HCC 35 Turkish-F
+
+# List of Video Class Terminal Types
+
+# Syntax:
+# VT terminal_type terminal_type_name
+
+VT 0100 USB Vendor Specific
+VT 0101 USB Streaming
+VT 0200 Input Vendor Specific
+VT 0201 Camera Sensor
+VT 0202 Sequential Media
+VT 0300 Output Vendor Specific
+VT 0301 Generic Display
+VT 0302 Sequential Media
+VT 0400 External Vendor Specific
+VT 0401 Composite Video
+VT 0402 S-Video
+VT 0403 Component Video
diff --git a/src/VBox/Main/src-server/win/HostDnsServiceWin.cpp b/src/VBox/Main/src-server/win/HostDnsServiceWin.cpp
new file mode 100644
index 00000000..1e06c99f
--- /dev/null
+++ b/src/VBox/Main/src-server/win/HostDnsServiceWin.cpp
@@ -0,0 +1,488 @@
+/* $Id: HostDnsServiceWin.cpp $ */
+/** @file
+ * Host DNS listener for Windows.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * XXX: need <winsock2.h> to reveal IP_ADAPTER_ADDRESSES in
+ * <iptypes.h> and it must be included before <windows.h>, which is
+ * pulled in by IPRT headers.
+ */
+#include <iprt/win/winsock2.h>
+
+#include "../HostDnsService.h"
+
+#include <VBox/com/string.h>
+#include <VBox/com/ptr.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+
+#include <iprt/win/windows.h>
+#include <windns.h>
+#include <iptypes.h>
+#include <iprt/win/iphlpapi.h>
+
+#include <algorithm>
+#include <iprt/sanitized/sstream>
+#include <iprt/sanitized/string>
+#include <vector>
+
+
+DECLINLINE(int) registerNotification(const HKEY &hKey, HANDLE &hEvent)
+{
+ LONG lrc = RegNotifyChangeKeyValue(hKey,
+ TRUE,
+ REG_NOTIFY_CHANGE_LAST_SET,
+ hEvent,
+ TRUE);
+ AssertMsgReturn(lrc == ERROR_SUCCESS,
+ ("Failed to register event on the key. Please debug me!"),
+ VERR_INTERNAL_ERROR);
+
+ return VINF_SUCCESS;
+}
+
+static void appendTokenizedStrings(std::vector<std::string> &vecStrings, const std::string &strToAppend, char chDelim = ' ')
+{
+ if (strToAppend.empty())
+ return;
+
+ std::istringstream stream(strToAppend);
+ std::string substr;
+
+ while (std::getline(stream, substr, chDelim))
+ {
+ if (substr.empty())
+ continue;
+
+ if (std::find(vecStrings.cbegin(), vecStrings.cend(), substr) != vecStrings.cend())
+ continue;
+
+ vecStrings.push_back(substr);
+ }
+}
+
+
+struct HostDnsServiceWin::Data
+{
+ HKEY hKeyTcpipParameters;
+ bool fTimerArmed;
+
+#define DATA_SHUTDOWN_EVENT 0
+#define DATA_DNS_UPDATE_EVENT 1
+#define DATA_TIMER 2
+#define DATA_MAX_EVENT 3
+ HANDLE haDataEvent[DATA_MAX_EVENT];
+
+ Data()
+ {
+ hKeyTcpipParameters = NULL;
+ fTimerArmed = false;
+
+ for (size_t i = 0; i < DATA_MAX_EVENT; ++i)
+ haDataEvent[i] = NULL;
+ }
+
+ ~Data()
+ {
+ if (hKeyTcpipParameters != NULL)
+ RegCloseKey(hKeyTcpipParameters);
+
+ for (size_t i = 0; i < DATA_MAX_EVENT; ++i)
+ if (haDataEvent[i] != NULL)
+ CloseHandle(haDataEvent[i]);
+ }
+};
+
+
+HostDnsServiceWin::HostDnsServiceWin()
+ : HostDnsServiceBase(true)
+{
+ m = new Data();
+}
+
+HostDnsServiceWin::~HostDnsServiceWin()
+{
+ if (m != NULL)
+ delete m;
+}
+
+HRESULT HostDnsServiceWin::init(HostDnsMonitorProxy *pProxy)
+{
+ if (m == NULL)
+ return E_FAIL;
+
+ bool fRc = true;
+ LONG lRc = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters",
+ 0,
+ KEY_READ|KEY_NOTIFY,
+ &m->hKeyTcpipParameters);
+ if (lRc != ERROR_SUCCESS)
+ {
+ LogRel(("HostDnsServiceWin: failed to open key Tcpip\\Parameters (error %d)\n", lRc));
+ fRc = false;
+ }
+ else
+ {
+ for (size_t i = 0; i < DATA_MAX_EVENT; ++i)
+ {
+ HANDLE h;
+
+ if (i == DATA_TIMER)
+ h = CreateWaitableTimer(NULL, FALSE, NULL);
+ else
+ h = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (h == NULL)
+ {
+ LogRel(("HostDnsServiceWin: failed to create event (error %d)\n", GetLastError()));
+ fRc = false;
+ break;
+ }
+
+ m->haDataEvent[i] = h;
+ }
+ }
+
+ if (!fRc)
+ return E_FAIL;
+
+ HRESULT hrc = HostDnsServiceBase::init(pProxy);
+ if (FAILED(hrc))
+ return hrc;
+
+ return updateInfo();
+}
+
+void HostDnsServiceWin::uninit(void)
+{
+ HostDnsServiceBase::uninit();
+}
+
+int HostDnsServiceWin::monitorThreadShutdown(RTMSINTERVAL uTimeoutMs)
+{
+ RT_NOREF(uTimeoutMs);
+
+ AssertPtr(m);
+ SetEvent(m->haDataEvent[DATA_SHUTDOWN_EVENT]);
+ /** @todo r=andy Wait for thread? Check rc here. Timeouts? */
+
+ return VINF_SUCCESS;
+}
+
+int HostDnsServiceWin::monitorThreadProc(void)
+{
+ Assert(m != NULL);
+
+ registerNotification(m->hKeyTcpipParameters,
+ m->haDataEvent[DATA_DNS_UPDATE_EVENT]);
+
+ onMonitorThreadInitDone();
+
+ for (;;)
+ {
+ DWORD dwReady;
+
+ dwReady = WaitForMultipleObjects(DATA_MAX_EVENT, m->haDataEvent,
+ FALSE, INFINITE);
+
+ if (dwReady == WAIT_OBJECT_0 + DATA_SHUTDOWN_EVENT)
+ break;
+
+ if (dwReady == WAIT_OBJECT_0 + DATA_DNS_UPDATE_EVENT)
+ {
+ /*
+ * Registry updates for multiple values are not atomic, so
+ * wait a bit to avoid racing and reading partial update.
+ */
+ if (!m->fTimerArmed)
+ {
+ LARGE_INTEGER delay; /* in 100ns units */
+ delay.QuadPart = -2 * 1000 * 1000 * 10LL; /* relative: 2s */
+
+ BOOL ok = SetWaitableTimer(m->haDataEvent[DATA_TIMER], &delay,
+ 0, NULL, NULL, FALSE);
+ if (ok)
+ {
+ m->fTimerArmed = true;
+ }
+ else
+ {
+ LogRel(("HostDnsServiceWin: failed to arm timer (error %d)\n", GetLastError()));
+ updateInfo();
+ }
+ }
+
+ ResetEvent(m->haDataEvent[DATA_DNS_UPDATE_EVENT]);
+ registerNotification(m->hKeyTcpipParameters,
+ m->haDataEvent[DATA_DNS_UPDATE_EVENT]);
+ }
+ else if (dwReady == WAIT_OBJECT_0 + DATA_TIMER)
+ {
+ m->fTimerArmed = false;
+ updateInfo();
+ }
+ else if (dwReady == WAIT_FAILED)
+ {
+ LogRel(("HostDnsServiceWin: WaitForMultipleObjects failed: error %d\n", GetLastError()));
+ return VERR_INTERNAL_ERROR;
+ }
+ else
+ {
+ LogRel(("HostDnsServiceWin: WaitForMultipleObjects unexpected return value %d\n", dwReady));
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+HRESULT HostDnsServiceWin::updateInfo(void)
+{
+ HostDnsInformation info;
+
+ LONG lrc;
+ int rc;
+
+ std::string strDomain;
+ std::string strSearchList; /* NB: comma separated, no spaces */
+
+ /*
+ * We ignore "DhcpDomain" key here since it's not stable. If
+ * there are two active interfaces that use DHCP (in particular
+ * when host uses OpenVPN) then DHCP ACKs will take turns updating
+ * that key. Instead we call GetAdaptersAddresses() below (which
+ * is what ipconfig.exe seems to do).
+ */
+ for (DWORD regIndex = 0; /**/; ++regIndex) {
+ char keyName[256];
+ DWORD cbKeyName = sizeof(keyName);
+ DWORD keyType = 0;
+ char keyData[1024];
+ DWORD cbKeyData = sizeof(keyData);
+
+ lrc = RegEnumValueA(m->hKeyTcpipParameters, regIndex,
+ keyName, &cbKeyName, 0,
+ &keyType, (LPBYTE)keyData, &cbKeyData);
+
+ if (lrc == ERROR_NO_MORE_ITEMS)
+ break;
+
+ if (lrc == ERROR_MORE_DATA) /* buffer too small; handle? */
+ continue;
+
+ if (lrc != ERROR_SUCCESS)
+ {
+ LogRel2(("HostDnsServiceWin: RegEnumValue error %d\n", (int)lrc));
+ return E_FAIL;
+ }
+
+ if (keyType != REG_SZ)
+ continue;
+
+ if (cbKeyData > 0 && keyData[cbKeyData - 1] == '\0')
+ --cbKeyData; /* don't count trailing NUL if present */
+
+ if (RTStrICmp("Domain", keyName) == 0)
+ {
+ strDomain.assign(keyData, cbKeyData);
+ LogRel2(("HostDnsServiceWin: Domain=\"%s\"\n", strDomain.c_str()));
+ }
+ else if (RTStrICmp("DhcpDomain", keyName) == 0)
+ {
+ std::string strDhcpDomain(keyData, cbKeyData);
+ LogRel2(("HostDnsServiceWin: DhcpDomain=\"%s\"\n", strDhcpDomain.c_str()));
+ }
+ else if (RTStrICmp("SearchList", keyName) == 0)
+ {
+ strSearchList.assign(keyData, cbKeyData);
+ LogRel2(("HostDnsServiceWin: SearchList=\"%s\"\n", strSearchList.c_str()));
+ }
+ }
+
+ /* statically configured domain name */
+ if (!strDomain.empty())
+ {
+ info.domain = strDomain;
+ info.searchList.push_back(strDomain);
+ }
+
+ /* statically configured search list */
+ if (!strSearchList.empty())
+ appendTokenizedStrings(info.searchList, strSearchList, ',');
+
+ /*
+ * When name servers are configured statically it seems that the
+ * value of Tcpip\Parameters\NameServer is NOT set, inly interface
+ * specific NameServer value is (which triggers notification for
+ * us to pick up the change). Fortunately, DnsApi seems to do the
+ * right thing there.
+ */
+ DNS_STATUS status;
+ PIP4_ARRAY pIp4Array = NULL;
+
+ // NB: must be set on input it seems, despite docs' claim to the contrary.
+ DWORD cbBuffer = sizeof(&pIp4Array);
+
+ status = DnsQueryConfig(DnsConfigDnsServerList,
+ DNS_CONFIG_FLAG_ALLOC, NULL, NULL,
+ &pIp4Array, &cbBuffer);
+
+ if (status == NO_ERROR && pIp4Array != NULL)
+ {
+ for (DWORD i = 0; i < pIp4Array->AddrCount; ++i)
+ {
+ char szAddrStr[16] = "";
+ RTStrPrintf(szAddrStr, sizeof(szAddrStr), "%RTnaipv4", pIp4Array->AddrArray[i]);
+
+ LogRel2(("HostDnsServiceWin: server %d: %s\n", i+1, szAddrStr));
+ info.servers.push_back(szAddrStr);
+ }
+
+ LocalFree(pIp4Array);
+ }
+
+
+ /**
+ * DnsQueryConfig(DnsConfigSearchList, ...) is not implemented.
+ * Call GetAdaptersAddresses() that orders the returned list
+ * appropriately and collect IP_ADAPTER_ADDRESSES::DnsSuffix.
+ */
+ do {
+ PIP_ADAPTER_ADDRESSES pAddrBuf = NULL;
+ ULONG cbAddrBuf = 8 * 1024;
+ bool fReallocated = false;
+ ULONG err;
+
+ pAddrBuf = (PIP_ADAPTER_ADDRESSES) malloc(cbAddrBuf);
+ if (pAddrBuf == NULL)
+ {
+ LogRel2(("HostDnsServiceWin: failed to allocate %zu bytes"
+ " of GetAdaptersAddresses buffer\n",
+ (size_t)cbAddrBuf));
+ break;
+ }
+
+ while (pAddrBuf != NULL)
+ {
+ ULONG cbAddrBufProvided = cbAddrBuf;
+
+ err = GetAdaptersAddresses(AF_UNSPEC,
+ GAA_FLAG_SKIP_ANYCAST
+ | GAA_FLAG_SKIP_MULTICAST,
+ NULL,
+ pAddrBuf, &cbAddrBuf);
+ if (err == NO_ERROR)
+ {
+ break;
+ }
+ else if (err == ERROR_BUFFER_OVERFLOW)
+ {
+ LogRel2(("HostDnsServiceWin: provided GetAdaptersAddresses with %zu"
+ " but asked again for %zu bytes\n",
+ (size_t)cbAddrBufProvided, (size_t)cbAddrBuf));
+
+ if (RT_UNLIKELY(fReallocated)) /* what? again?! */
+ {
+ LogRel2(("HostDnsServiceWin: ... not going to realloc again\n"));
+ free(pAddrBuf);
+ pAddrBuf = NULL;
+ break;
+ }
+
+ PIP_ADAPTER_ADDRESSES pNewBuf = (PIP_ADAPTER_ADDRESSES) realloc(pAddrBuf, cbAddrBuf);
+ if (pNewBuf == NULL)
+ {
+ LogRel2(("HostDnsServiceWin: failed to reallocate %zu bytes\n", (size_t)cbAddrBuf));
+ free(pAddrBuf);
+ pAddrBuf = NULL;
+ break;
+ }
+
+ /* try again */
+ pAddrBuf = pNewBuf; /* cbAddrBuf already updated */
+ fReallocated = true;
+ }
+ else
+ {
+ LogRel2(("HostDnsServiceWin: GetAdaptersAddresses error %d\n", err));
+ free(pAddrBuf);
+ pAddrBuf = NULL;
+ break;
+ }
+ }
+
+ if (pAddrBuf == NULL)
+ break;
+
+ for (PIP_ADAPTER_ADDRESSES pAdp = pAddrBuf; pAdp != NULL; pAdp = pAdp->Next)
+ {
+ LogRel2(("HostDnsServiceWin: %ls (status %u) ...\n",
+ pAdp->FriendlyName ? pAdp->FriendlyName : L"(null)",
+ pAdp->OperStatus));
+
+ if (pAdp->OperStatus != IfOperStatusUp)
+ continue;
+
+ if (pAdp->DnsSuffix == NULL || *pAdp->DnsSuffix == L'\0')
+ continue;
+
+ char *pszDnsSuffix = NULL;
+ rc = RTUtf16ToUtf8Ex(pAdp->DnsSuffix, RTSTR_MAX,
+ &pszDnsSuffix, 0, /* allocate */
+ NULL);
+ if (RT_FAILURE(rc))
+ {
+ LogRel2(("HostDnsServiceWin: failed to convert DNS suffix \"%ls\": %Rrc\n",
+ pAdp->DnsSuffix, rc));
+ continue;
+ }
+
+ AssertContinue(pszDnsSuffix != NULL);
+ AssertContinue(*pszDnsSuffix != '\0');
+ LogRel2(("HostDnsServiceWin: ... suffix = \"%s\"\n", pszDnsSuffix));
+
+ appendTokenizedStrings(info.searchList, pszDnsSuffix);
+ RTStrFree(pszDnsSuffix);
+ }
+
+ free(pAddrBuf);
+ } while (0);
+
+
+ if (info.domain.empty() && !info.searchList.empty())
+ info.domain = info.searchList[0];
+
+ if (info.searchList.size() == 1)
+ info.searchList.clear();
+
+ HostDnsServiceBase::setInfo(info);
+
+ return S_OK;
+}
+
diff --git a/src/VBox/Main/src-server/win/HostPowerWin.cpp b/src/VBox/Main/src-server/win/HostPowerWin.cpp
new file mode 100644
index 00000000..5ed0253f
--- /dev/null
+++ b/src/VBox/Main/src-server/win/HostPowerWin.cpp
@@ -0,0 +1,238 @@
+/* $Id: HostPowerWin.cpp $ */
+/** @file
+ * VirtualBox interface to host's power notification service
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_HOST
+#include <iprt/win/windows.h>
+/* Some SDK versions lack the extern "C" and thus cause linking failures.
+ * This workaround isn't pretty, but there are not many options. */
+extern "C" {
+#include <PowrProf.h>
+}
+
+#include <VBox/com/ptr.h>
+#include <iprt/errcore.h>
+#include "HostPower.h"
+#include "LoggingNew.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static WCHAR gachWindowClassName[] = L"VBoxPowerNotifyClass";
+
+
+HostPowerServiceWin::HostPowerServiceWin(VirtualBox *aVirtualBox) : HostPowerService(aVirtualBox), mThread(NIL_RTTHREAD)
+{
+ mHwnd = 0;
+
+ int rc = RTThreadCreate(&mThread, HostPowerServiceWin::NotificationThread, this, 65536,
+ RTTHREADTYPE_GUI, RTTHREADFLAGS_WAITABLE, "MainPower");
+
+ if (RT_FAILURE(rc))
+ {
+ Log(("HostPowerServiceWin::HostPowerServiceWin: RTThreadCreate failed with %Rrc\n", rc));
+ return;
+ }
+}
+
+HostPowerServiceWin::~HostPowerServiceWin()
+{
+ if (mHwnd)
+ {
+ Log(("HostPowerServiceWin::!HostPowerServiceWin: destroy window %x\n", mHwnd));
+
+ /* Poke the thread out of the event loop and wait for it to clean up. */
+ PostMessage(mHwnd, WM_CLOSE, 0, 0);
+ RTThreadWait(mThread, 5000, NULL);
+ mThread = NIL_RTTHREAD;
+ }
+}
+
+
+
+DECLCALLBACK(int) HostPowerServiceWin::NotificationThread(RTTHREAD hThreadSelf, void *pInstance)
+{
+ RT_NOREF(hThreadSelf);
+ HostPowerServiceWin *pPowerObj = (HostPowerServiceWin *)pInstance;
+ HWND hwnd = 0;
+
+ /* Create a window and make it a power event notification handler. */
+ int rc = VINF_SUCCESS;
+
+ HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL);
+
+ /* Register the Window Class. */
+ WNDCLASS wc;
+
+ wc.style = CS_NOCLOSE;
+ wc.lpfnWndProc = HostPowerServiceWin::WndProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = sizeof(void *);
+ wc.hInstance = hInstance;
+ wc.hIcon = NULL;
+ wc.hCursor = NULL;
+ wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
+ wc.lpszMenuName = NULL;
+ wc.lpszClassName = gachWindowClassName;
+
+ ATOM atomWindowClass = RegisterClass(&wc);
+
+ if (atomWindowClass == 0)
+ {
+ rc = VERR_NOT_SUPPORTED;
+ Log(("HostPowerServiceWin::NotificationThread: RegisterClassA failed with %x\n", GetLastError()));
+ }
+ else
+ {
+ /* Create the window. */
+ hwnd = pPowerObj->mHwnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST,
+ gachWindowClassName, gachWindowClassName,
+ WS_POPUPWINDOW,
+ -200, -200, 100, 100, NULL, NULL, hInstance, NULL);
+
+ if (hwnd == NULL)
+ {
+ Log(("HostPowerServiceWin::NotificationThread: CreateWindowExA failed with %x\n", GetLastError()));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ {
+ SetWindowLongPtr(hwnd, 0, (LONG_PTR)pPowerObj);
+ SetWindowPos(hwnd, HWND_TOPMOST, -200, -200, 0, 0,
+ SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);
+
+ MSG msg;
+ BOOL fRet;
+ while ((fRet = GetMessage(&msg, NULL, 0, 0)) > 0)
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ /*
+ * Window procedure can return error,
+ * but this is exceptional situation
+ * that should be identified in testing
+ */
+ Assert(fRet >= 0);
+ }
+ }
+
+ Log(("HostPowerServiceWin::NotificationThread: exit thread\n"));
+
+ if (atomWindowClass != 0)
+ {
+ UnregisterClass(gachWindowClassName, hInstance);
+ atomWindowClass = 0;
+ }
+
+ return 0;
+}
+
+LRESULT CALLBACK HostPowerServiceWin::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_POWERBROADCAST:
+ {
+ HostPowerServiceWin *pPowerObj;
+
+ pPowerObj = (HostPowerServiceWin *)GetWindowLongPtr(hwnd, 0);
+ if (pPowerObj)
+ {
+ switch(wParam)
+ {
+ case PBT_APMSUSPEND:
+ pPowerObj->notify(Reason_HostSuspend);
+ break;
+
+ case PBT_APMRESUMEAUTOMATIC:
+ pPowerObj->notify(Reason_HostResume);
+ break;
+
+ case PBT_APMPOWERSTATUSCHANGE:
+ {
+ SYSTEM_POWER_STATUS SystemPowerStatus;
+
+ Log(("PBT_APMPOWERSTATUSCHANGE\n"));
+ if (GetSystemPowerStatus(&SystemPowerStatus) == TRUE)
+ {
+ Log(("PBT_APMPOWERSTATUSCHANGE ACLineStatus=%d BatteryFlag=%d\n", SystemPowerStatus.ACLineStatus,
+ SystemPowerStatus.BatteryFlag));
+
+ if (SystemPowerStatus.ACLineStatus == 0) /* offline */
+ {
+ if (SystemPowerStatus.BatteryFlag == 2 /* low > 33% */)
+ {
+ LONG rc;
+ SYSTEM_BATTERY_STATE BatteryState;
+
+ rc = CallNtPowerInformation(SystemBatteryState, NULL, 0, (PVOID)&BatteryState,
+ sizeof(BatteryState));
+#ifdef LOG_ENABLED
+ if (rc == 0 /* STATUS_SUCCESS */)
+ Log(("CallNtPowerInformation claims %d seconds of power left\n",
+ BatteryState.EstimatedTime));
+#endif
+ if ( rc == 0 /* STATUS_SUCCESS */
+ && BatteryState.EstimatedTime < 60*5)
+ {
+ pPowerObj->notify(Reason_HostBatteryLow);
+ }
+ }
+ /* If the machine has less than 5% battery left (and is not connected
+ * to the AC), then we should save the state. */
+ else if (SystemPowerStatus.BatteryFlag == 4 /* critical battery status; less than 5% */)
+ {
+ pPowerObj->notify(Reason_HostBatteryLow);
+ }
+ }
+ }
+ break;
+ }
+ default:
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+ }
+ }
+ return TRUE;
+ }
+
+ case WM_DESTROY:
+ {
+ /* moved here. it can't work across theads */
+ SetWindowLongPtr(hwnd, 0, 0);
+ PostQuitMessage(0);
+ return 0;
+ }
+
+ default:
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+ }
+}
diff --git a/src/VBox/Main/src-server/win/Makefile.kup b/src/VBox/Main/src-server/win/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-server/win/Makefile.kup
diff --git a/src/VBox/Main/src-server/win/NetIf-win.cpp b/src/VBox/Main/src-server/win/NetIf-win.cpp
new file mode 100644
index 00000000..8a48a133
--- /dev/null
+++ b/src/VBox/Main/src-server/win/NetIf-win.cpp
@@ -0,0 +1,2017 @@
+/* $Id: NetIf-win.cpp $ */
+/** @file
+ * Main - NetIfList, Windows implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_HOST
+
+#define NETIF_WITHOUT_NETCFG
+
+#include <iprt/errcore.h>
+#include <list>
+
+#define _WIN32_DCOM
+#include <iprt/win/winsock2.h>
+#include <iprt/win/ws2tcpip.h>
+#include <iprt/win/windows.h>
+#include <tchar.h>
+
+#ifdef VBOX_WITH_NETFLT
+# include "VBox/VBoxNetCfg-win.h"
+# include "devguid.h"
+#endif
+
+#include <iprt/win/iphlpapi.h>
+#include <iprt/win/ntddndis.h>
+
+#include "LoggingNew.h"
+#include "HostNetworkInterfaceImpl.h"
+#include "ProgressImpl.h"
+#include "VirtualBoxImpl.h"
+#include "VBoxNls.h"
+#include "Global.h"
+#include "netif.h"
+#include "ThreadTask.h"
+
+DECLARE_TRANSLATION_CONTEXT(NetIfWin);
+
+#ifdef VBOX_WITH_NETFLT
+# include <Wbemidl.h>
+
+# include "svchlp.h"
+
+# include <shellapi.h>
+# define INITGUID
+# include <guiddef.h>
+# include <devguid.h>
+# include <iprt/win/objbase.h>
+# include <iprt/win/setupapi.h>
+# include <iprt/win/shlobj.h>
+# include <cfgmgr32.h>
+
+# define VBOX_APP_NAME L"VirtualBox"
+
+static int getDefaultInterfaceIndex()
+{
+ PMIB_IPFORWARDTABLE pIpTable;
+ DWORD dwSize = sizeof(MIB_IPFORWARDTABLE) * 20;
+ DWORD dwRC = NO_ERROR;
+ int iIndex = -1;
+
+ pIpTable = (MIB_IPFORWARDTABLE *)RTMemAlloc(dwSize);
+ if (GetIpForwardTable(pIpTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER)
+ {
+ RTMemFree(pIpTable);
+ pIpTable = (MIB_IPFORWARDTABLE *)RTMemAlloc(dwSize);
+ if (!pIpTable)
+ return -1;
+ }
+ dwRC = GetIpForwardTable(pIpTable, &dwSize, 0);
+ if (dwRC == NO_ERROR)
+ {
+ for (unsigned int i = 0; i < pIpTable->dwNumEntries; i++)
+ if (pIpTable->table[i].dwForwardDest == 0)
+ {
+ iIndex = pIpTable->table[i].dwForwardIfIndex;
+ break;
+ }
+ }
+ RTMemFree(pIpTable);
+ return iIndex;
+}
+
+static int collectNetIfInfo(Bstr &strName, Guid &guid, PNETIFINFO pInfo, int iDefault)
+{
+ RT_NOREF(strName);
+
+ /*
+ * Most of the hosts probably have less than 10 adapters,
+ * so we'll mostly succeed from the first attempt.
+ */
+ ULONG uBufLen = sizeof(IP_ADAPTER_ADDRESSES) * 10;
+ PIP_ADAPTER_ADDRESSES pAddresses = (PIP_ADAPTER_ADDRESSES)RTMemAlloc(uBufLen);
+ if (!pAddresses)
+ return VERR_NO_MEMORY;
+ DWORD dwRc = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, pAddresses, &uBufLen);
+ if (dwRc == ERROR_BUFFER_OVERFLOW)
+ {
+ /* Impressive! More than 10 adapters! Get more memory and try again. */
+ RTMemFree(pAddresses);
+ pAddresses = (PIP_ADAPTER_ADDRESSES)RTMemAlloc(uBufLen);
+ if (!pAddresses)
+ return VERR_NO_MEMORY;
+ dwRc = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, pAddresses, &uBufLen);
+ }
+ if (dwRc == NO_ERROR)
+ {
+ PIP_ADAPTER_ADDRESSES pAdapter;
+ for (pAdapter = pAddresses; pAdapter; pAdapter = pAdapter->Next)
+ {
+ char *pszUuid = RTStrDup(pAdapter->AdapterName);
+ size_t len = strlen(pszUuid) - 1;
+ if (pszUuid[0] == '{' && pszUuid[len] == '}')
+ {
+ pszUuid[len] = 0;
+ if (!RTUuidCompareStr(&pInfo->Uuid, pszUuid + 1))
+ {
+ bool fIPFound, fIPv6Found;
+ PIP_ADAPTER_UNICAST_ADDRESS pAddr;
+ fIPFound = fIPv6Found = false;
+ for (pAddr = pAdapter->FirstUnicastAddress; pAddr; pAddr = pAddr->Next)
+ {
+ switch (pAddr->Address.lpSockaddr->sa_family)
+ {
+ case AF_INET:
+ if (!fIPFound)
+ {
+ fIPFound = true;
+ memcpy(&pInfo->IPAddress,
+ &((struct sockaddr_in *)pAddr->Address.lpSockaddr)->sin_addr.s_addr,
+ sizeof(pInfo->IPAddress));
+ }
+ break;
+ case AF_INET6:
+ if (!fIPv6Found)
+ {
+ fIPv6Found = true;
+ memcpy(&pInfo->IPv6Address,
+ ((struct sockaddr_in6 *)pAddr->Address.lpSockaddr)->sin6_addr.s6_addr,
+ sizeof(pInfo->IPv6Address));
+ }
+ break;
+ }
+ }
+ PIP_ADAPTER_PREFIX pPrefix;
+ fIPFound = fIPv6Found = false;
+ for (pPrefix = pAdapter->FirstPrefix; pPrefix; pPrefix = pPrefix->Next)
+ {
+ switch (pPrefix->Address.lpSockaddr->sa_family)
+ {
+ case AF_INET:
+ if (!fIPFound)
+ {
+ if (pPrefix->PrefixLength <= sizeof(pInfo->IPNetMask) * 8)
+ {
+ fIPFound = true;
+ RTNetPrefixToMaskIPv4(pPrefix->PrefixLength, &pInfo->IPNetMask);
+ }
+ else
+ LogFunc(("Unexpected IPv4 prefix length of %d\n",
+ pPrefix->PrefixLength));
+ }
+ break;
+ case AF_INET6:
+ if (!fIPv6Found)
+ {
+ if (pPrefix->PrefixLength <= sizeof(pInfo->IPv6NetMask) * 8)
+ {
+ fIPv6Found = true;
+ RTNetPrefixToMaskIPv6(pPrefix->PrefixLength, &pInfo->IPv6NetMask);
+ }
+ else
+ LogFunc(("Unexpected IPv6 prefix length of %d\n",
+ pPrefix->PrefixLength));
+ }
+ break;
+ }
+ }
+ if (sizeof(pInfo->MACAddress) != pAdapter->PhysicalAddressLength)
+ LogFunc(("Unexpected physical address length: %u\n", pAdapter->PhysicalAddressLength));
+ else
+ memcpy(pInfo->MACAddress.au8, pAdapter->PhysicalAddress, sizeof(pInfo->MACAddress));
+ pInfo->enmMediumType = NETIF_T_ETHERNET;
+ pInfo->enmStatus = pAdapter->OperStatus == IfOperStatusUp ? NETIF_S_UP : NETIF_S_DOWN;
+ pInfo->fIsDefault = (pAdapter->IfIndex == (DWORD)iDefault);
+ RTStrFree(pszUuid);
+ break;
+ }
+ }
+ RTStrFree(pszUuid);
+ }
+
+ ADAPTER_SETTINGS Settings;
+ HRESULT hr = VBoxNetCfgWinGetAdapterSettings((const GUID *)guid.raw(), &Settings);
+ if (hr == S_OK)
+ {
+ if (Settings.ip)
+ {
+ pInfo->IPAddress.u = Settings.ip;
+ pInfo->IPNetMask.u = Settings.mask;
+ }
+ pInfo->fDhcpEnabled = Settings.bDhcp;
+ }
+ else
+ {
+ pInfo->fDhcpEnabled = false;
+ }
+ }
+ RTMemFree(pAddresses);
+
+ return VINF_SUCCESS;
+}
+
+/* svc helper func */
+
+struct StaticIpConfig
+{
+ ULONG IPAddress;
+ ULONG IPNetMask;
+};
+
+struct StaticIpV6Config
+{
+ char * IPV6Address;
+ ULONG IPV6NetMaskLength;
+};
+
+class NetworkInterfaceHelperClientData : public ThreadVoidData
+{
+public:
+ NetworkInterfaceHelperClientData(){};
+ ~NetworkInterfaceHelperClientData()
+ {
+ if (msgCode == SVCHlpMsg::EnableStaticIpConfigV6 && u.StaticIPV6.IPV6Address)
+ {
+ RTStrFree(u.StaticIPV6.IPV6Address);
+ u.StaticIPV6.IPV6Address = NULL;
+ }
+ };
+
+ SVCHlpMsg::Code msgCode;
+ /* for SVCHlpMsg::CreateHostOnlyNetworkInterface */
+ Bstr name;
+ ComObjPtr<HostNetworkInterface> iface;
+ ComObjPtr<VirtualBox> ptrVBox;
+ /* for SVCHlpMsg::RemoveHostOnlyNetworkInterface */
+ Guid guid;
+
+ union
+ {
+ StaticIpConfig StaticIP;
+ StaticIpV6Config StaticIPV6;
+ } u;
+
+};
+
+static HRESULT netIfNetworkInterfaceHelperClient(SVCHlpClient *aClient,
+ Progress *aProgress,
+ void *aUser, int *aVrc)
+{
+ LogFlowFuncEnter();
+ LogFlowFunc(("aClient={%p}, aProgress={%p}, aUser={%p}\n",
+ aClient, aProgress, aUser));
+
+ AssertReturn( (aClient == NULL && aProgress == NULL && aVrc == NULL)
+ || (aClient != NULL && aProgress != NULL && aVrc != NULL),
+ E_POINTER);
+ AssertReturn(aUser, E_POINTER);
+
+ NetworkInterfaceHelperClientData* d = static_cast<NetworkInterfaceHelperClientData *>(aUser);
+
+ if (aClient == NULL)
+ {
+ /* "cleanup only" mode, just return (it will free aUser) */
+ return S_OK;
+ }
+
+ HRESULT rc = S_OK;
+ int vrc = VINF_SUCCESS;
+
+ switch (d->msgCode)
+ {
+ case SVCHlpMsg::CreateHostOnlyNetworkInterface:
+ {
+ LogFlowFunc(("CreateHostOnlyNetworkInterface:\n"));
+ LogFlowFunc(("Network connection name = '%ls'\n", d->name.raw()));
+
+ /* write message and parameters */
+ vrc = aClient->write(d->msgCode);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(Utf8Str(d->name));
+ if (RT_FAILURE(vrc)) break;
+
+ /* wait for a reply */
+ bool endLoop = false;
+ while (!endLoop)
+ {
+ SVCHlpMsg::Code reply = SVCHlpMsg::Null;
+
+ vrc = aClient->read(reply);
+ if (RT_FAILURE(vrc)) break;
+
+ switch (reply)
+ {
+ case SVCHlpMsg::CreateHostOnlyNetworkInterface_OK:
+ {
+ /* read the GUID */
+ Guid guid;
+ Utf8Str name;
+ vrc = aClient->read(name);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->read(guid);
+ if (RT_FAILURE(vrc)) break;
+
+ LogFlowFunc(("Network connection GUID = {%RTuuid}\n", guid.raw()));
+
+ /* initialize the object returned to the caller by
+ * CreateHostOnlyNetworkInterface() */
+ rc = d->iface->init(Bstr(name), Bstr(name), guid, HostNetworkInterfaceType_HostOnly);
+ if (SUCCEEDED(rc))
+ {
+ rc = d->iface->i_setVirtualBox(d->ptrVBox);
+ if (SUCCEEDED(rc))
+ {
+ rc = d->iface->updateConfig();
+ if (SUCCEEDED(rc))
+ rc = d->iface->i_updatePersistentConfig();
+ }
+ }
+ endLoop = true;
+ break;
+ }
+ case SVCHlpMsg::Error:
+ {
+ /* read the error message */
+ Utf8Str errMsg;
+ vrc = aClient->read(errMsg);
+ if (RT_FAILURE(vrc)) break;
+
+ rc = E_FAIL;
+ d->iface->setError(E_FAIL, errMsg.c_str());
+ endLoop = true;
+ break;
+ }
+ default:
+ {
+ endLoop = true;
+ rc = E_FAIL;/// @todo ComAssertMsgFailedBreak((
+ //"Invalid message code %d (%08lX)\n",
+ //reply, reply),
+ //rc = E_FAIL);
+ }
+ }
+ }
+
+ break;
+ }
+ case SVCHlpMsg::RemoveHostOnlyNetworkInterface:
+ {
+ LogFlowFunc(("RemoveHostOnlyNetworkInterface:\n"));
+ LogFlowFunc(("Network connection GUID = {%RTuuid}\n", d->guid.raw()));
+
+ /* write message and parameters */
+ vrc = aClient->write(d->msgCode);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(d->guid);
+ if (RT_FAILURE(vrc)) break;
+
+ /* wait for a reply */
+ bool endLoop = false;
+ while (!endLoop)
+ {
+ SVCHlpMsg::Code reply = SVCHlpMsg::Null;
+
+ vrc = aClient->read(reply);
+ if (RT_FAILURE(vrc)) break;
+
+ switch (reply)
+ {
+ case SVCHlpMsg::OK:
+ {
+ /* no parameters */
+ rc = S_OK;
+ endLoop = true;
+ break;
+ }
+ case SVCHlpMsg::Error:
+ {
+ /* read the error message */
+ Utf8Str errMsg;
+ vrc = aClient->read(errMsg);
+ if (RT_FAILURE(vrc)) break;
+
+ rc = E_FAIL;
+ d->iface->setError(E_FAIL, errMsg.c_str());
+ endLoop = true;
+ break;
+ }
+ default:
+ {
+ endLoop = true;
+ rc = E_FAIL; /// @todo ComAssertMsgFailedBreak((
+ //"Invalid message code %d (%08lX)\n",
+ //reply, reply),
+ //rc = E_FAIL);
+ }
+ }
+ }
+
+ break;
+ }
+ case SVCHlpMsg::EnableDynamicIpConfig: /* see usage in code */
+ {
+ LogFlowFunc(("EnableDynamicIpConfig:\n"));
+ LogFlowFunc(("Network connection name = '%ls'\n", d->name.raw()));
+
+ /* write message and parameters */
+ vrc = aClient->write(d->msgCode);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(d->guid);
+ if (RT_FAILURE(vrc)) break;
+
+ /* wait for a reply */
+ bool endLoop = false;
+ while (!endLoop)
+ {
+ SVCHlpMsg::Code reply = SVCHlpMsg::Null;
+
+ vrc = aClient->read(reply);
+ if (RT_FAILURE(vrc)) break;
+
+ switch (reply)
+ {
+ case SVCHlpMsg::OK:
+ {
+ /* no parameters */
+ rc = d->iface->updateConfig();
+ endLoop = true;
+ break;
+ }
+ case SVCHlpMsg::Error:
+ {
+ /* read the error message */
+ Utf8Str errMsg;
+ vrc = aClient->read(errMsg);
+ if (RT_FAILURE(vrc)) break;
+
+ rc = E_FAIL;
+ d->iface->setError(E_FAIL, errMsg.c_str());
+ endLoop = true;
+ break;
+ }
+ default:
+ {
+ endLoop = true;
+ rc = E_FAIL; /// @todo ComAssertMsgFailedBreak((
+ //"Invalid message code %d (%08lX)\n",
+ //reply, reply),
+ //rc = E_FAIL);
+ }
+ }
+ }
+
+ break;
+ }
+ case SVCHlpMsg::EnableStaticIpConfig: /* see usage in code */
+ {
+ LogFlowFunc(("EnableStaticIpConfig:\n"));
+ LogFlowFunc(("Network connection name = '%ls'\n", d->name.raw()));
+
+ /* write message and parameters */
+ vrc = aClient->write(d->msgCode);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(d->guid);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(d->u.StaticIP.IPAddress);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(d->u.StaticIP.IPNetMask);
+ if (RT_FAILURE(vrc)) break;
+
+ /* wait for a reply */
+ bool endLoop = false;
+ while (!endLoop)
+ {
+ SVCHlpMsg::Code reply = SVCHlpMsg::Null;
+
+ vrc = aClient->read(reply);
+ if (RT_FAILURE(vrc)) break;
+
+ switch (reply)
+ {
+ case SVCHlpMsg::OK:
+ {
+ /* no parameters */
+ rc = d->iface->updateConfig();
+ endLoop = true;
+ break;
+ }
+ case SVCHlpMsg::Error:
+ {
+ /* read the error message */
+ Utf8Str errMsg;
+ vrc = aClient->read(errMsg);
+ if (RT_FAILURE(vrc)) break;
+
+ rc = E_FAIL;
+ d->iface->setError(E_FAIL, errMsg.c_str());
+ endLoop = true;
+ break;
+ }
+ default:
+ {
+ endLoop = true;
+ rc = E_FAIL; /// @todo ComAssertMsgFailedBreak((
+ //"Invalid message code %d (%08lX)\n",
+ //reply, reply),
+ //rc = E_FAIL);
+ }
+ }
+ }
+
+ break;
+ }
+ case SVCHlpMsg::EnableStaticIpConfigV6: /* see usage in code */
+ {
+ LogFlowFunc(("EnableStaticIpConfigV6:\n"));
+ LogFlowFunc(("Network connection name = '%ls'\n", d->name.raw()));
+
+ /* write message and parameters */
+ vrc = aClient->write(d->msgCode);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(d->guid);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(d->u.StaticIPV6.IPV6Address);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(d->u.StaticIPV6.IPV6NetMaskLength);
+ if (RT_FAILURE(vrc)) break;
+
+ /* wait for a reply */
+ bool endLoop = false;
+ while (!endLoop)
+ {
+ SVCHlpMsg::Code reply = SVCHlpMsg::Null;
+
+ vrc = aClient->read(reply);
+ if (RT_FAILURE(vrc)) break;
+
+ switch (reply)
+ {
+ case SVCHlpMsg::OK:
+ {
+ /* no parameters */
+ rc = d->iface->updateConfig();
+ endLoop = true;
+ break;
+ }
+ case SVCHlpMsg::Error:
+ {
+ /* read the error message */
+ Utf8Str errMsg;
+ vrc = aClient->read(errMsg);
+ if (RT_FAILURE(vrc)) break;
+
+ rc = E_FAIL;
+ d->iface->setError(E_FAIL, errMsg.c_str());
+ endLoop = true;
+ break;
+ }
+ default:
+ {
+ endLoop = true;
+ rc = E_FAIL; /// @todo ComAssertMsgFailedBreak((
+ //"Invalid message code %d (%08lX)\n",
+ //reply, reply),
+ //rc = E_FAIL);
+ }
+ }
+ }
+
+ break;
+ }
+ case SVCHlpMsg::DhcpRediscover: /* see usage in code */
+ {
+ LogFlowFunc(("DhcpRediscover:\n"));
+ LogFlowFunc(("Network connection name = '%ls'\n", d->name.raw()));
+
+ /* write message and parameters */
+ vrc = aClient->write(d->msgCode);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(d->guid);
+ if (RT_FAILURE(vrc)) break;
+
+ /* wait for a reply */
+ bool endLoop = false;
+ while (!endLoop)
+ {
+ SVCHlpMsg::Code reply = SVCHlpMsg::Null;
+
+ vrc = aClient->read(reply);
+ if (RT_FAILURE(vrc)) break;
+
+ switch (reply)
+ {
+ case SVCHlpMsg::OK:
+ {
+ /* no parameters */
+ rc = d->iface->updateConfig();
+ endLoop = true;
+ break;
+ }
+ case SVCHlpMsg::Error:
+ {
+ /* read the error message */
+ Utf8Str errMsg;
+ vrc = aClient->read(errMsg);
+ if (RT_FAILURE(vrc)) break;
+
+ rc = E_FAIL;
+ d->iface->setError(E_FAIL, errMsg.c_str());
+ endLoop = true;
+ break;
+ }
+ default:
+ {
+ endLoop = true;
+ rc = E_FAIL; /// @todo ComAssertMsgFailedBreak((
+ //"Invalid message code %d (%08lX)\n",
+ //reply, reply),
+ //rc = E_FAIL);
+ }
+ }
+ }
+
+ break;
+ }
+ default:
+ rc = E_FAIL; /// @todo ComAssertMsgFailedBreak((
+// "Invalid message code %d (%08lX)\n",
+// d->msgCode, d->msgCode),
+// rc = E_FAIL);
+ }
+
+ if (aVrc)
+ *aVrc = vrc;
+
+ LogFlowFunc(("rc=0x%08X, vrc=%Rrc\n", rc, vrc));
+ LogFlowFuncLeave();
+ return rc;
+}
+
+
+int netIfNetworkInterfaceHelperServer(SVCHlpClient *aClient,
+ SVCHlpMsg::Code aMsgCode)
+{
+ LogFlowFuncEnter();
+ LogFlowFunc(("aClient={%p}, aMsgCode=%d\n", aClient, aMsgCode));
+
+ AssertReturn(aClient, VERR_INVALID_POINTER);
+
+ int vrc = VINF_SUCCESS;
+ HRESULT hrc;
+
+ switch (aMsgCode)
+ {
+ case SVCHlpMsg::CreateHostOnlyNetworkInterface:
+ {
+ LogFlowFunc(("CreateHostOnlyNetworkInterface:\n"));
+
+ Utf8Str desiredName;
+ vrc = aClient->read(desiredName);
+ if (RT_FAILURE(vrc)) break;
+
+ Guid guid;
+ Utf8Str errMsg;
+ Bstr name;
+ Bstr bstrErr;
+
+#ifdef VBOXNETCFG_DELAYEDRENAME
+ Bstr devId;
+ hrc = VBoxNetCfgWinCreateHostOnlyNetworkInterface(NULL, false, Bstr(desiredName).raw(), guid.asOutParam(), devId.asOutParam(),
+ bstrErr.asOutParam());
+#else /* !VBOXNETCFG_DELAYEDRENAME */
+ hrc = VBoxNetCfgWinCreateHostOnlyNetworkInterface(NULL, false, Bstr(desiredName).raw(), guid.asOutParam(), name.asOutParam(),
+ bstrErr.asOutParam());
+#endif /* !VBOXNETCFG_DELAYEDRENAME */
+
+ if (hrc == S_OK)
+ {
+ ULONG ip, mask;
+ hrc = VBoxNetCfgWinGenHostOnlyNetworkNetworkIp(&ip, &mask);
+ if (hrc == S_OK)
+ {
+ /* ip returned by VBoxNetCfgWinGenHostOnlyNetworkNetworkIp is a network ip,
+ * i.e. 192.168.xxx.0, assign 192.168.xxx.1 for the hostonly adapter */
+ ip = ip | (1 << 24);
+ hrc = VBoxNetCfgWinEnableStaticIpConfig((const GUID*)guid.raw(), ip, mask);
+ if (hrc != S_OK)
+ LogRel(("VBoxNetCfgWinEnableStaticIpConfig failed (0x%x)\n", hrc));
+ }
+ else
+ LogRel(("VBoxNetCfgWinGenHostOnlyNetworkNetworkIp failed (0x%x)\n", hrc));
+#ifdef VBOXNETCFG_DELAYEDRENAME
+ hrc = VBoxNetCfgWinRenameHostOnlyConnection((const GUID*)guid.raw(), devId.raw(), name.asOutParam());
+ if (hrc != S_OK)
+ LogRel(("VBoxNetCfgWinRenameHostOnlyConnection failed, error = 0x%x", hrc));
+#endif /* VBOXNETCFG_DELAYEDRENAME */
+ /* write success followed by GUID */
+ vrc = aClient->write(SVCHlpMsg::CreateHostOnlyNetworkInterface_OK);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(Utf8Str(name));
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(guid);
+ if (RT_FAILURE(vrc)) break;
+ }
+ else
+ {
+ vrc = VERR_GENERAL_FAILURE;
+ errMsg = Utf8Str(bstrErr);
+ /* write failure followed by error message */
+ if (errMsg.isEmpty())
+ errMsg = Utf8StrFmt("Unspecified error (%Rrc)", vrc);
+ vrc = aClient->write(SVCHlpMsg::Error);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(errMsg);
+ if (RT_FAILURE(vrc)) break;
+ }
+
+ break;
+ }
+ case SVCHlpMsg::RemoveHostOnlyNetworkInterface:
+ {
+ LogFlowFunc(("RemoveHostOnlyNetworkInterface:\n"));
+
+ Guid guid;
+ Bstr bstrErr;
+
+ vrc = aClient->read(guid);
+ if (RT_FAILURE(vrc)) break;
+
+ Utf8Str errMsg;
+ hrc = VBoxNetCfgWinRemoveHostOnlyNetworkInterface((const GUID*)guid.raw(), bstrErr.asOutParam());
+
+ if (hrc == S_OK)
+ {
+ /* write parameter-less success */
+ vrc = aClient->write(SVCHlpMsg::OK);
+ if (RT_FAILURE(vrc)) break;
+ }
+ else
+ {
+ vrc = VERR_GENERAL_FAILURE;
+ errMsg = Utf8Str(bstrErr);
+ /* write failure followed by error message */
+ if (errMsg.isEmpty())
+ errMsg = Utf8StrFmt("Unspecified error (%Rrc)", vrc);
+ vrc = aClient->write(SVCHlpMsg::Error);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(errMsg);
+ if (RT_FAILURE(vrc)) break;
+ }
+
+ break;
+ }
+ case SVCHlpMsg::EnableStaticIpConfigV6:
+ {
+ LogFlowFunc(("EnableStaticIpConfigV6:\n"));
+
+ Guid guid;
+ Utf8Str ipV6;
+ ULONG maskLengthV6;
+ vrc = aClient->read(guid);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->read(ipV6);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->read(maskLengthV6);
+ if (RT_FAILURE(vrc)) break;
+
+ Utf8Str errMsg;
+ vrc = VERR_NOT_IMPLEMENTED;
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* write success followed by GUID */
+ vrc = aClient->write(SVCHlpMsg::OK);
+ if (RT_FAILURE(vrc)) break;
+ }
+ else
+ {
+ /* write failure followed by error message */
+ if (errMsg.isEmpty())
+ errMsg = Utf8StrFmt("Unspecified error (%Rrc)", vrc);
+ vrc = aClient->write(SVCHlpMsg::Error);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(errMsg);
+ if (RT_FAILURE(vrc)) break;
+ }
+
+ break;
+ }
+ case SVCHlpMsg::EnableStaticIpConfig:
+ {
+ LogFlowFunc(("EnableStaticIpConfig:\n"));
+
+ Guid guid;
+ ULONG ip, mask;
+ vrc = aClient->read(guid);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->read(ip);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->read(mask);
+ if (RT_FAILURE(vrc)) break;
+
+ Utf8Str errMsg;
+ hrc = VBoxNetCfgWinEnableStaticIpConfig((const GUID *)guid.raw(), ip, mask);
+
+ if (hrc == S_OK)
+ {
+ /* write success followed by GUID */
+ vrc = aClient->write(SVCHlpMsg::OK);
+ if (RT_FAILURE(vrc)) break;
+ }
+ else
+ {
+ vrc = VERR_GENERAL_FAILURE;
+ /* write failure followed by error message */
+ if (errMsg.isEmpty())
+ errMsg = Utf8StrFmt("Unspecified error (%Rrc)", vrc);
+ vrc = aClient->write(SVCHlpMsg::Error);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(errMsg);
+ if (RT_FAILURE(vrc)) break;
+ }
+
+ break;
+ }
+ case SVCHlpMsg::EnableDynamicIpConfig:
+ {
+ LogFlowFunc(("EnableDynamicIpConfig:\n"));
+
+ Guid guid;
+ vrc = aClient->read(guid);
+ if (RT_FAILURE(vrc)) break;
+
+ Utf8Str errMsg;
+ hrc = VBoxNetCfgWinEnableDynamicIpConfig((const GUID *)guid.raw());
+
+ if (hrc == S_OK)
+ {
+ /* write success followed by GUID */
+ vrc = aClient->write(SVCHlpMsg::OK);
+ if (RT_FAILURE(vrc)) break;
+ }
+ else
+ {
+ vrc = VERR_GENERAL_FAILURE;
+ /* write failure followed by error message */
+ if (errMsg.isEmpty())
+ errMsg = Utf8StrFmt("Unspecified error (%Rrc)", vrc);
+ vrc = aClient->write(SVCHlpMsg::Error);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(errMsg);
+ if (RT_FAILURE(vrc)) break;
+ }
+
+ break;
+ }
+ case SVCHlpMsg::DhcpRediscover:
+ {
+ LogFlowFunc(("DhcpRediscover:\n"));
+
+ Guid guid;
+ vrc = aClient->read(guid);
+ if (RT_FAILURE(vrc)) break;
+
+ Utf8Str errMsg;
+ hrc = VBoxNetCfgWinDhcpRediscover((const GUID *)guid.raw());
+
+ if (hrc == S_OK)
+ {
+ /* write success followed by GUID */
+ vrc = aClient->write(SVCHlpMsg::OK);
+ if (RT_FAILURE(vrc)) break;
+ }
+ else
+ {
+ vrc = VERR_GENERAL_FAILURE;
+ /* write failure followed by error message */
+ if (errMsg.isEmpty())
+ errMsg = Utf8StrFmt("Unspecified error (%Rrc)", vrc);
+ vrc = aClient->write(SVCHlpMsg::Error);
+ if (RT_FAILURE(vrc)) break;
+ vrc = aClient->write(errMsg);
+ if (RT_FAILURE(vrc)) break;
+ }
+
+ break;
+ }
+ default:
+ AssertMsgFailedBreakStmt(
+ ("Invalid message code %d (%08lX)\n", aMsgCode, aMsgCode),
+ VERR_GENERAL_FAILURE);
+ }
+
+ LogFlowFunc(("vrc=%Rrc\n", vrc));
+ LogFlowFuncLeave();
+ return vrc;
+}
+
+/** @todo REMOVE. OBSOLETE NOW. */
+/**
+ * Returns TRUE if the Windows version is 6.0 or greater (i.e. it's Vista and
+ * later OSes) and it has the UAC (User Account Control) feature enabled.
+ */
+static BOOL IsUACEnabled()
+{
+ LONG rc = 0;
+
+ OSVERSIONINFOEX info;
+ ZeroMemory(&info, sizeof(OSVERSIONINFOEX));
+ info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ rc = GetVersionEx((OSVERSIONINFO *) &info);
+ AssertReturn(rc != 0, FALSE);
+
+ LogFlowFunc(("dwMajorVersion=%d, dwMinorVersion=%d\n",
+ info.dwMajorVersion, info.dwMinorVersion));
+
+ /* we are interested only in Vista (and newer versions...). In all
+ * earlier versions UAC is not present. */
+ if (info.dwMajorVersion < 6)
+ return FALSE;
+
+ /* the default EnableLUA value is 1 (Enabled) */
+ DWORD dwEnableLUA = 1;
+
+ HKEY hKey;
+ rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
+ "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System",
+ 0, KEY_QUERY_VALUE, &hKey);
+
+ Assert(rc == ERROR_SUCCESS || rc == ERROR_PATH_NOT_FOUND);
+ if (rc == ERROR_SUCCESS)
+ {
+
+ DWORD cbEnableLUA = sizeof(dwEnableLUA);
+ rc = RegQueryValueExA(hKey, "EnableLUA", NULL, NULL,
+ (LPBYTE) &dwEnableLUA, &cbEnableLUA);
+
+ RegCloseKey(hKey);
+
+ Assert(rc == ERROR_SUCCESS || rc == ERROR_FILE_NOT_FOUND);
+ }
+
+ LogFlowFunc(("rc=%d, dwEnableLUA=%d\n", rc, dwEnableLUA));
+
+ return dwEnableLUA == 1;
+}
+
+/* end */
+
+static int vboxNetWinAddComponent(std::list<ComObjPtr<HostNetworkInterface> > * pPist,
+ INetCfgComponent * pncc, HostNetworkInterfaceType enmType,
+ int iDefaultInterface)
+{
+ LPWSTR lpszName;
+ GUID IfGuid;
+ HRESULT hr;
+ int rc = VERR_GENERAL_FAILURE;
+
+ hr = pncc->GetDisplayName(&lpszName);
+ Assert(hr == S_OK);
+ if (hr == S_OK)
+ {
+ Bstr name(lpszName);
+
+ hr = pncc->GetInstanceGuid(&IfGuid);
+ Assert(hr == S_OK);
+ if (hr == S_OK)
+ {
+ Guid guidIfCopy(IfGuid);
+ NETIFINFO Info;
+ RT_ZERO(Info);
+ Info.Uuid = *guidIfCopy.raw();
+ rc = collectNetIfInfo(name, guidIfCopy, &Info, iDefaultInterface);
+ if (RT_FAILURE(rc))
+ LogRelFunc(("collectNetIfInfo() -> %Rrc\n", rc));
+ LogFunc(("adding %ls\n", lpszName));
+ /* create a new object and add it to the list */
+ ComObjPtr<HostNetworkInterface> iface;
+ iface.createObject();
+ /* remove the curly bracket at the end */
+ rc = iface->init(name, enmType, &Info);
+ if (SUCCEEDED(rc))
+ {
+ if (Info.fIsDefault)
+ pPist->push_front(iface);
+ else
+ pPist->push_back(iface);
+ }
+ else
+ {
+ LogRelFunc(("HostNetworkInterface::init() -> %Rrc\n", rc));
+ AssertComRC(rc);
+ }
+ }
+ else
+ LogRelFunc(("failed to get device instance GUID (0x%x)\n", hr));
+ CoTaskMemFree(lpszName);
+ }
+ else
+ LogRelFunc(("failed to get device display name (0x%x)\n", hr));
+
+ return rc;
+}
+
+#endif /* VBOX_WITH_NETFLT */
+
+
+static int netIfListHostAdapters(INetCfg *pNc, std::list<ComObjPtr<HostNetworkInterface> > &list)
+{
+#ifndef VBOX_WITH_NETFLT
+ /* VBoxNetAdp is available only when VBOX_WITH_NETFLT is enabled */
+ return VERR_NOT_IMPLEMENTED;
+#else /* # if defined VBOX_WITH_NETFLT */
+ INetCfgComponent *pMpNcc;
+ HRESULT hr;
+ IEnumNetCfgComponent *pEnumComponent;
+
+ hr = pNc->EnumComponents(&GUID_DEVCLASS_NET, &pEnumComponent);
+ if (hr == S_OK)
+ {
+ while ((hr = pEnumComponent->Next(1, &pMpNcc, NULL)) == S_OK)
+ {
+ LPWSTR pwszName;
+ ULONG uComponentStatus;
+ hr = pMpNcc->GetDisplayName(&pwszName);
+ if (hr == S_OK)
+ LogFunc(("%ls\n", pwszName));
+ else
+ LogRelFunc(("failed to get device display name (0x%x)\n", hr));
+ hr = pMpNcc->GetDeviceStatus(&uComponentStatus);
+ if (hr == S_OK)
+ {
+ if (uComponentStatus == 0)
+ {
+ LPWSTR pId;
+ hr = pMpNcc->GetId(&pId);
+ Assert(hr == S_OK);
+ if (hr == S_OK)
+ {
+ LogFunc(("id = %ls\n", pId));
+ if (!_wcsnicmp(pId, L"sun_VBoxNetAdp", sizeof(L"sun_VBoxNetAdp")/2))
+ {
+ vboxNetWinAddComponent(&list, pMpNcc, HostNetworkInterfaceType_HostOnly, -1);
+ }
+ CoTaskMemFree(pId);
+ }
+ else
+ LogRelFunc(("failed to get device id (0x%x)\n", hr));
+ }
+ }
+ else
+ LogRelFunc(("failed to get device status (0x%x)\n", hr));
+ pMpNcc->Release();
+ }
+ Assert(hr == S_OK || hr == S_FALSE);
+
+ pEnumComponent->Release();
+ }
+ else
+ LogRelFunc(("EnumComponents error (0x%x)\n", hr));
+#endif /* # if defined VBOX_WITH_NETFLT */
+ return VINF_SUCCESS;
+}
+
+int NetIfGetConfig(HostNetworkInterface * pIf, NETIFINFO *pInfo)
+{
+#ifndef VBOX_WITH_NETFLT
+ return VERR_NOT_IMPLEMENTED;
+#else
+ Bstr name;
+ HRESULT hr = pIf->COMGETTER(Name)(name.asOutParam());
+ if (hr == S_OK)
+ {
+ Bstr IfGuid;
+ hr = pIf->COMGETTER(Id)(IfGuid.asOutParam());
+ Assert(hr == S_OK);
+ if (hr == S_OK)
+ {
+ memset(pInfo, 0, sizeof(NETIFINFO));
+ Guid guid(IfGuid);
+ pInfo->Uuid = *(guid.raw());
+
+ return collectNetIfInfo(name, guid, pInfo, getDefaultInterfaceIndex());
+ }
+ }
+ return VERR_GENERAL_FAILURE;
+#endif
+}
+
+int NetIfGetConfigByName(PNETIFINFO)
+{
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * Obtain the current state of the interface.
+ *
+ * @returns VBox status code.
+ *
+ * @param pcszIfName Interface name.
+ * @param penmState Where to store the retrieved state.
+ */
+int NetIfGetState(const char *pcszIfName, NETIFSTATUS *penmState)
+{
+ RT_NOREF(pcszIfName, penmState);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * Retrieve the physical link speed in megabits per second. If the interface is
+ * not up or otherwise unavailable the zero speed is returned.
+ *
+ * @returns VBox status code.
+ *
+ * @param pcszIfName Interface name.
+ * @param puMbits Where to store the link speed.
+ */
+int NetIfGetLinkSpeed(const char *pcszIfName, uint32_t *puMbits)
+{
+ RT_NOREF(pcszIfName, puMbits);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int NetIfCreateHostOnlyNetworkInterface(VirtualBox *pVirtualBox,
+ IHostNetworkInterface **aHostNetworkInterface,
+ IProgress **aProgress,
+ IN_BSTR aName)
+{
+#ifndef VBOX_WITH_NETFLT
+ return VERR_NOT_IMPLEMENTED;
+#else
+ /* create a progress object */
+ ComObjPtr<Progress> progress;
+ HRESULT hrc = progress.createObject();
+ AssertComRCReturn(hrc, Global::vboxStatusCodeFromCOM(hrc));
+
+ ComPtr<IHost> host;
+ hrc = pVirtualBox->COMGETTER(Host)(host.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ hrc = progress->init(pVirtualBox, host,
+ Bstr(NetIfWin::tr("Creating host only network interface")).raw(),
+ FALSE /* aCancelable */);
+ if (SUCCEEDED(hrc))
+ {
+ progress.queryInterfaceTo(aProgress);
+
+ /* create a new uninitialized host interface object */
+ ComObjPtr<HostNetworkInterface> iface;
+ iface.createObject();
+ iface.queryInterfaceTo(aHostNetworkInterface);
+
+ /* create the networkInterfaceHelperClient() argument */
+ NetworkInterfaceHelperClientData* d = new NetworkInterfaceHelperClientData();
+
+ d->msgCode = SVCHlpMsg::CreateHostOnlyNetworkInterface;
+ d->name = aName;
+ d->iface = iface;
+ d->ptrVBox = pVirtualBox;
+
+ hrc = pVirtualBox->i_startSVCHelperClient(IsUACEnabled() == TRUE /* aPrivileged */,
+ netIfNetworkInterfaceHelperClient,
+ static_cast<void *>(d),
+ progress);
+ /* d is now owned by netIfNetworkInterfaceHelperClient(), no need to delete one here */
+
+ }
+ }
+
+ return Global::vboxStatusCodeFromCOM(hrc);
+#endif
+}
+
+int NetIfRemoveHostOnlyNetworkInterface(VirtualBox *pVirtualBox, const Guid &aId,
+ IProgress **aProgress)
+{
+#ifndef VBOX_WITH_NETFLT
+ return VERR_NOT_IMPLEMENTED;
+#else
+ /* create a progress object */
+ ComObjPtr<Progress> progress;
+ HRESULT hrc = progress.createObject();
+ AssertComRCReturn(hrc, Global::vboxStatusCodeFromCOM(hrc));
+
+ ComPtr<IHost> host;
+ hrc = pVirtualBox->COMGETTER(Host)(host.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ hrc = progress->init(pVirtualBox, host,
+ Bstr(NetIfWin::tr("Removing host network interface")).raw(),
+ FALSE /* aCancelable */);
+ if (SUCCEEDED(hrc))
+ {
+ progress.queryInterfaceTo(aProgress);
+
+ /* create the networkInterfaceHelperClient() argument */
+ NetworkInterfaceHelperClientData* d = new NetworkInterfaceHelperClientData();
+
+ d->msgCode = SVCHlpMsg::RemoveHostOnlyNetworkInterface;
+ d->guid = aId;
+
+ hrc = pVirtualBox->i_startSVCHelperClient(IsUACEnabled() == TRUE /* aPrivileged */,
+ netIfNetworkInterfaceHelperClient,
+ static_cast<void *>(d),
+ progress);
+ /* d is now owned by netIfNetworkInterfaceHelperClient(), no need to delete one here */
+
+ }
+ }
+
+ return Global::vboxStatusCodeFromCOM(hrc);
+#endif
+}
+
+int NetIfEnableStaticIpConfig(VirtualBox *pVBox, HostNetworkInterface * pIf, ULONG aOldIp, ULONG ip, ULONG mask)
+{
+ RT_NOREF(aOldIp);
+#ifndef VBOX_WITH_NETFLT
+ return VERR_NOT_IMPLEMENTED;
+#else
+ Bstr guid;
+ HRESULT rc = pIf->COMGETTER(Id)(guid.asOutParam());
+ if (SUCCEEDED(rc))
+ {
+// ComPtr<VirtualBox> pVBox;
+// rc = pIf->getVirtualBox(pVBox.asOutParam());
+// if (SUCCEEDED(rc))
+ {
+ /* create a progress object */
+ ComObjPtr<Progress> progress;
+ progress.createObject();
+// ComPtr<IHost> host;
+// HRESULT rc = pVBox->COMGETTER(Host)(host.asOutParam());
+// if (SUCCEEDED(rc))
+ {
+ rc = progress->init(pVBox, (IHostNetworkInterface*)pIf,
+ Bstr(NetIfWin::tr("Enabling Dynamic Ip Configuration")).raw(),
+ FALSE /* aCancelable */);
+ if (SUCCEEDED(rc))
+ {
+ if (FAILED(rc)) return rc;
+// progress.queryInterfaceTo(aProgress);
+
+ /* create the networkInterfaceHelperClient() argument */
+ NetworkInterfaceHelperClientData* d = new NetworkInterfaceHelperClientData();
+
+ d->msgCode = SVCHlpMsg::EnableStaticIpConfig;
+ d->guid = Guid(guid);
+ d->iface = pIf;
+ d->u.StaticIP.IPAddress = ip;
+ d->u.StaticIP.IPNetMask = mask;
+
+ rc = pVBox->i_startSVCHelperClient(IsUACEnabled() == TRUE /* aPrivileged */,
+ netIfNetworkInterfaceHelperClient,
+ static_cast<void *>(d),
+ progress);
+ /* d is now owned by netIfNetworkInterfaceHelperClient(), no need to delete one here */
+
+ if (SUCCEEDED(rc))
+ {
+ progress->WaitForCompletion(-1);
+ }
+ }
+ }
+ }
+ }
+
+ return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_GENERAL_FAILURE;
+#endif
+}
+
+int NetIfEnableStaticIpConfigV6(VirtualBox *pVBox, HostNetworkInterface * pIf, const Utf8Str &aOldIPV6Address,
+ const Utf8Str &aIPV6Address, ULONG aIPV6MaskPrefixLength)
+{
+ RT_NOREF(aOldIPV6Address);
+#ifndef VBOX_WITH_NETFLT
+ return VERR_NOT_IMPLEMENTED;
+#else
+ Bstr guid;
+ HRESULT rc = pIf->COMGETTER(Id)(guid.asOutParam());
+ if (SUCCEEDED(rc))
+ {
+// ComPtr<VirtualBox> pVBox;
+// rc = pIf->getVirtualBox(pVBox.asOutParam());
+// if (SUCCEEDED(rc))
+ {
+ /* create a progress object */
+ ComObjPtr<Progress> progress;
+ progress.createObject();
+// ComPtr<IHost> host;
+// HRESULT rc = pVBox->COMGETTER(Host)(host.asOutParam());
+// if (SUCCEEDED(rc))
+ {
+ rc = progress->init(pVBox, (IHostNetworkInterface*)pIf,
+ Bstr(NetIfWin::tr("Enabling Dynamic Ip Configuration")).raw(),
+ FALSE /* aCancelable */);
+ if (SUCCEEDED(rc))
+ {
+ if (FAILED(rc)) return rc;
+// progress.queryInterfaceTo(aProgress);
+
+ /* create the networkInterfaceHelperClient() argument */
+ NetworkInterfaceHelperClientData* d = new NetworkInterfaceHelperClientData();
+
+ d->msgCode = SVCHlpMsg::EnableStaticIpConfigV6;
+ d->guid = guid;
+ d->iface = pIf;
+ d->u.StaticIPV6.IPV6Address = RTStrDup(aIPV6Address.c_str());
+ d->u.StaticIPV6.IPV6NetMaskLength = aIPV6MaskPrefixLength;
+
+ rc = pVBox->i_startSVCHelperClient(IsUACEnabled() == TRUE /* aPrivileged */,
+ netIfNetworkInterfaceHelperClient,
+ static_cast<void *>(d),
+ progress);
+ /* d is now owned by netIfNetworkInterfaceHelperClient(), no need to delete one here */
+
+ if (SUCCEEDED(rc))
+ {
+ progress->WaitForCompletion(-1);
+ }
+ }
+ }
+ }
+ }
+
+ return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_GENERAL_FAILURE;
+#endif
+}
+
+int NetIfEnableDynamicIpConfig(VirtualBox *pVBox, HostNetworkInterface * pIf)
+{
+#ifndef VBOX_WITH_NETFLT
+ return VERR_NOT_IMPLEMENTED;
+#else
+ HRESULT rc;
+ Bstr guid;
+ rc = pIf->COMGETTER(Id)(guid.asOutParam());
+ if (SUCCEEDED(rc))
+ {
+// ComPtr<VirtualBox> pVBox;
+// rc = pIf->getVirtualBox(pVBox.asOutParam());
+// if (SUCCEEDED(rc))
+ {
+ /* create a progress object */
+ ComObjPtr<Progress> progress;
+ progress.createObject();
+// ComPtr<IHost> host;
+// HRESULT rc = pVBox->COMGETTER(Host)(host.asOutParam());
+// if (SUCCEEDED(rc))
+ {
+ rc = progress->init(pVBox, (IHostNetworkInterface*)pIf,
+ Bstr(NetIfWin::tr("Enabling Dynamic Ip Configuration")).raw(),
+ FALSE /* aCancelable */);
+ if (SUCCEEDED(rc))
+ {
+ if (FAILED(rc)) return rc;
+// progress.queryInterfaceTo(aProgress);
+
+ /* create the networkInterfaceHelperClient() argument */
+ NetworkInterfaceHelperClientData* d = new NetworkInterfaceHelperClientData();
+
+ d->msgCode = SVCHlpMsg::EnableDynamicIpConfig;
+ d->guid = guid;
+ d->iface = pIf;
+
+ rc = pVBox->i_startSVCHelperClient(IsUACEnabled() == TRUE /* aPrivileged */,
+ netIfNetworkInterfaceHelperClient,
+ static_cast<void *>(d),
+ progress);
+ /* d is now owned by netIfNetworkInterfaceHelperClient(), no need to delete one here */
+
+ if (SUCCEEDED(rc))
+ {
+ progress->WaitForCompletion(-1);
+ }
+ }
+ }
+ }
+ }
+
+ return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_GENERAL_FAILURE;
+#endif
+}
+
+int NetIfDhcpRediscover(VirtualBox *pVBox, HostNetworkInterface * pIf)
+{
+#ifndef VBOX_WITH_NETFLT
+ return VERR_NOT_IMPLEMENTED;
+#else
+ HRESULT rc;
+ Bstr guid;
+ rc = pIf->COMGETTER(Id)(guid.asOutParam());
+ if (SUCCEEDED(rc))
+ {
+// ComPtr<VirtualBox> pVBox;
+// rc = pIf->getVirtualBox(pVBox.asOutParam());
+// if (SUCCEEDED(rc))
+ {
+ /* create a progress object */
+ ComObjPtr<Progress> progress;
+ progress.createObject();
+// ComPtr<IHost> host;
+// HRESULT rc = pVBox->COMGETTER(Host)(host.asOutParam());
+// if (SUCCEEDED(rc))
+ {
+ rc = progress->init(pVBox, (IHostNetworkInterface*)pIf,
+ Bstr(NetIfWin::tr("Enabling Dynamic Ip Configuration")).raw(),
+ FALSE /* aCancelable */);
+ if (SUCCEEDED(rc))
+ {
+ if (FAILED(rc)) return rc;
+// progress.queryInterfaceTo(aProgress);
+
+ /* create the networkInterfaceHelperClient() argument */
+ NetworkInterfaceHelperClientData* d = new NetworkInterfaceHelperClientData();
+
+ d->msgCode = SVCHlpMsg::DhcpRediscover;
+ d->guid = guid;
+ d->iface = pIf;
+
+ rc = pVBox->i_startSVCHelperClient(IsUACEnabled() == TRUE /* aPrivileged */,
+ netIfNetworkInterfaceHelperClient,
+ static_cast<void *>(d),
+ progress);
+ /* d is now owned by netIfNetworkInterfaceHelperClient(), no need to delete one here */
+
+ if (SUCCEEDED(rc))
+ {
+ progress->WaitForCompletion(-1);
+ }
+ }
+ }
+ }
+ }
+
+ return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_GENERAL_FAILURE;
+#endif
+}
+
+
+#define netIfLog LogFunc
+
+struct BoundAdapter
+{
+ LPWSTR pName;
+ LPWSTR pHwId;
+ RTUUID guid;
+ PIP_ADAPTER_ADDRESSES pAdapter;
+ BOOL fWireless;
+};
+
+static int netIfGetUnboundHostOnlyAdapters(INetCfg *pNetCfg, std::list<BoundAdapter> &adapters)
+{
+ INetCfgComponent *pMiniport;
+ HRESULT hr;
+ IEnumNetCfgComponent *pEnumComponent;
+
+ if ((hr = pNetCfg->EnumComponents(&GUID_DEVCLASS_NET, &pEnumComponent)) != S_OK)
+ LogRelFunc(("failed to enumerate network adapter components (0x%x)\n", hr));
+ else
+ {
+ while ((hr = pEnumComponent->Next(1, &pMiniport, NULL)) == S_OK)
+ {
+ GUID guid;
+ ULONG uComponentStatus;
+ struct BoundAdapter adapter;
+ memset(&adapter, 0, sizeof(adapter));
+ if ((hr = pMiniport->GetDisplayName(&adapter.pName)) != S_OK)
+ LogRelFunc(("failed to get device display name (0x%x)\n", hr));
+ else if ((hr = pMiniport->GetDeviceStatus(&uComponentStatus)) != S_OK)
+ netIfLog(("failed to get device status (0x%x)\n", hr));
+ else if (uComponentStatus != 0)
+ netIfLog(("wrong device status (0x%x)\n", uComponentStatus));
+ else if ((hr = pMiniport->GetId(&adapter.pHwId)) != S_OK)
+ LogRelFunc(("failed to get device id (0x%x)\n", hr));
+ else if (_wcsnicmp(adapter.pHwId, L"sun_VBoxNetAdp", sizeof(L"sun_VBoxNetAdp")/2))
+ netIfLog(("not host-only id = %ls, ignored\n", adapter.pHwId));
+ else if ((hr = pMiniport->GetInstanceGuid(&guid)) != S_OK)
+ LogRelFunc(("failed to get instance id (0x%x)\n", hr));
+ else
+ {
+ adapter.guid = *(Guid(guid).raw());
+ netIfLog(("guid=%RTuuid, name=%ls id = %ls\n", &adapter.guid, adapter.pName, adapter.pHwId));
+ adapters.push_back(adapter);
+ adapter.pName = adapter.pHwId = NULL; /* do not free, will be done later */
+ }
+ if (adapter.pHwId)
+ CoTaskMemFree(adapter.pHwId);
+ if (adapter.pName)
+ CoTaskMemFree(adapter.pName);
+ pMiniport->Release();
+ }
+ Assert(hr == S_OK || hr == S_FALSE);
+
+ pEnumComponent->Release();
+ }
+ netIfLog(("return\n"));
+ return VINF_SUCCESS;
+}
+
+#define DEVNAME_PREFIX L"\\\\.\\"
+
+static BOOL netIfIsWireless(INetCfgComponent *pAdapter)
+{
+ bool fWireless = false;
+
+ /* Construct a device name. */
+ LPWSTR pwszBindName = NULL;
+ HRESULT hrc = pAdapter->GetBindName(&pwszBindName);
+ if (SUCCEEDED(hrc) && pwszBindName)
+ {
+ WCHAR wszFileName[MAX_PATH];
+ int vrc = RTUtf16Copy(wszFileName, MAX_PATH, DEVNAME_PREFIX);
+ if (RT_SUCCESS(vrc))
+ vrc = RTUtf16Cat(wszFileName, MAX_PATH, pwszBindName);
+ if (RT_SUCCESS(vrc))
+ {
+ /* open the device */
+ HANDLE hDevice = CreateFileW(wszFileName,
+ GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ if (hDevice != INVALID_HANDLE_VALUE)
+ {
+ /* now issue the OID_GEN_PHYSICAL_MEDIUM query */
+ DWORD Oid = OID_GEN_PHYSICAL_MEDIUM;
+ NDIS_PHYSICAL_MEDIUM PhMedium = NdisPhysicalMediumUnspecified;
+ DWORD cbResultIgn = 0;
+ if (DeviceIoControl(hDevice,
+ IOCTL_NDIS_QUERY_GLOBAL_STATS,
+ &Oid,
+ sizeof(Oid),
+ &PhMedium,
+ sizeof(PhMedium),
+ &cbResultIgn,
+ NULL))
+ {
+ /* that was simple, now examine PhMedium */
+ fWireless = PhMedium == NdisPhysicalMediumWirelessWan
+ || PhMedium == NdisPhysicalMediumWirelessLan
+ || PhMedium == NdisPhysicalMediumNative802_11
+ || PhMedium == NdisPhysicalMediumBluetooth;
+ }
+ else
+ {
+ DWORD rcWin = GetLastError();
+ LogRel(("netIfIsWireless: DeviceIoControl to '%ls' failed with rcWin=%u (%#x) - ignoring\n",
+ wszFileName, rcWin, rcWin));
+ Assert(rcWin == ERROR_INVALID_PARAMETER || rcWin == ERROR_NOT_SUPPORTED || rcWin == ERROR_BAD_COMMAND);
+ }
+ CloseHandle(hDevice);
+ }
+ else
+ {
+ DWORD rcWin = GetLastError();
+#if 0 /* bird: Triggers on each VBoxSVC startup so, disabled. Whoever want it, can enable using DEBUG_xxxx. */
+ AssertLogRelMsgFailed(("netIfIsWireless: CreateFile on '%ls' failed with rcWin=%u (%#x) - ignoring\n",
+ wszFileName, rcWin, rcWin));
+#else
+ LogRel(("netIfIsWireless: CreateFile on '%ls' failed with rcWin=%u (%#x) - ignoring\n",
+ wszFileName, rcWin, rcWin));
+#endif
+ }
+ }
+ CoTaskMemFree(pwszBindName);
+ }
+ else
+ LogRel(("netIfIsWireless: GetBindName failed hrc=%Rhrc\n", hrc));
+
+ return fWireless;
+}
+
+static HRESULT netIfGetBoundAdapters(std::list<BoundAdapter> &boundAdapters)
+{
+ INetCfg *pNetCfg = NULL;
+ INetCfgComponent *pFilter;
+ LPWSTR lpszApp;
+ HRESULT hr;
+
+ netIfLog(("building the list of interfaces\n"));
+ /* we are using the INetCfg API for getting the list of miniports */
+ hr = VBoxNetCfgWinQueryINetCfg(&pNetCfg, FALSE,
+ VBOX_APP_NAME,
+ 10000,
+ &lpszApp);
+ Assert(hr == S_OK);
+ if (hr != S_OK)
+ {
+ LogRelFunc(("failed to query INetCfg (0x%x)\n", hr));
+ return hr;
+ }
+
+ if ((hr = pNetCfg->FindComponent(L"oracle_VBoxNetLwf", &pFilter)) != S_OK
+ /* fall back to NDIS5 miniport lookup */
+ && (hr = pNetCfg->FindComponent(L"sun_VBoxNetFlt", &pFilter)))
+ LogRelFunc(("could not find either 'oracle_VBoxNetLwf' or 'sun_VBoxNetFlt' components (0x%x)\n", hr));
+ else
+ {
+ INetCfgComponentBindings *pFilterBindings;
+ if ((pFilter->QueryInterface(IID_INetCfgComponentBindings, (PVOID*)&pFilterBindings)) != S_OK)
+ LogRelFunc(("failed to query INetCfgComponentBindings (0x%x)\n", hr));
+ else
+ {
+ IEnumNetCfgBindingPath *pEnumBp;
+ INetCfgBindingPath *pBp;
+ if ((pFilterBindings->EnumBindingPaths(EBP_BELOW, &pEnumBp)) != S_OK)
+ LogRelFunc(("failed to enumerate binding paths (0x%x)\n", hr));
+ else
+ {
+ pEnumBp->Reset();
+ while ((hr = pEnumBp->Next(1, &pBp, NULL)) == S_OK)
+ {
+ IEnumNetCfgBindingInterface *pEnumBi;
+ INetCfgBindingInterface *pBi;
+ if (pBp->IsEnabled() != S_OK)
+ {
+ /** @todo some id of disabled path could be useful. */
+ netIfLog(("INetCfgBindingPath is disabled (0x%x)\n", hr));
+ pBp->Release();
+ continue;
+ }
+ if ((pBp->EnumBindingInterfaces(&pEnumBi)) != S_OK)
+ LogRelFunc(("failed to enumerate binding interfaces (0x%x)\n", hr));
+ else
+ {
+ hr = pEnumBi->Reset();
+ while ((hr = pEnumBi->Next(1, &pBi, NULL)) == S_OK)
+ {
+ INetCfgComponent *pAdapter;
+ if ((hr = pBi->GetLowerComponent(&pAdapter)) != S_OK)
+ LogRelFunc(("failed to get lower component (0x%x)\n", hr));
+ else
+ {
+ LPWSTR pwszName = NULL;
+ if ((hr = pAdapter->GetDisplayName(&pwszName)) != S_OK)
+ LogRelFunc(("failed to get display name (0x%x)\n", hr));
+ else
+ {
+ ULONG uStatus;
+ DWORD dwChars;
+ if ((hr = pAdapter->GetDeviceStatus(&uStatus)) != S_OK)
+ netIfLog(("%ls: failed to get device status (0x%x)\n",
+ pwszName, hr));
+ else if ((hr = pAdapter->GetCharacteristics(&dwChars)) != S_OK)
+ netIfLog(("%ls: failed to get device characteristics (0x%x)\n",
+ pwszName, hr));
+ else if (uStatus != 0)
+ netIfLog(("%ls: wrong status 0x%x\n",
+ pwszName, uStatus));
+ else if (dwChars & NCF_HIDDEN)
+ netIfLog(("%ls: wrong characteristics 0x%x\n",
+ pwszName, dwChars));
+ else
+ {
+ GUID guid;
+ LPWSTR pwszHwId = NULL;
+ if ((hr = pAdapter->GetId(&pwszHwId)) != S_OK)
+ LogRelFunc(("%ls: failed to get hardware id (0x%x)\n",
+ pwszName, hr));
+ else if (!_wcsnicmp(pwszHwId, L"sun_VBoxNetAdp", sizeof(L"sun_VBoxNetAdp")/2))
+ netIfLog(("host-only adapter %ls, ignored\n", pwszName));
+ else if ((hr = pAdapter->GetInstanceGuid(&guid)) != S_OK)
+ LogRelFunc(("%ls: failed to get instance GUID (0x%x)\n",
+ pwszName, hr));
+ else
+ {
+ struct BoundAdapter adapter;
+ adapter.pName = pwszName;
+ adapter.pHwId = pwszHwId;
+ adapter.guid = *(Guid(guid).raw());
+ adapter.pAdapter = NULL;
+ adapter.fWireless = netIfIsWireless(pAdapter);
+ netIfLog(("guid=%RTuuid, name=%ls, hwid=%ls, status=%x, chars=%x\n",
+ &adapter.guid, pwszName, pwszHwId, uStatus, dwChars));
+ boundAdapters.push_back(adapter);
+ pwszName = pwszHwId = NULL; /* do not free, will be done later */
+ }
+ if (pwszHwId)
+ CoTaskMemFree(pwszHwId);
+ }
+ if (pwszName)
+ CoTaskMemFree(pwszName);
+ }
+
+ pAdapter->Release();
+ }
+ pBi->Release();
+ }
+ pEnumBi->Release();
+ }
+ pBp->Release();
+ }
+ pEnumBp->Release();
+ }
+ pFilterBindings->Release();
+ }
+ pFilter->Release();
+ }
+ /* Host-only adapters are not necessarily bound, add them separately. */
+ netIfGetUnboundHostOnlyAdapters(pNetCfg, boundAdapters);
+ VBoxNetCfgWinReleaseINetCfg(pNetCfg, FALSE);
+
+ return S_OK;
+}
+
+#if 0
+static HRESULT netIfGetBoundAdaptersFallback(std::list<BoundAdapter> &boundAdapters)
+{
+ return CO_E_NOT_SUPPORTED;
+}
+#endif
+
+/**
+ * Walk through the list of adpater addresses and extract the required
+ * information. XP and older don't not have the OnLinkPrefixLength field.
+ */
+static void netIfFillInfoWithAddressesXp(PNETIFINFO pInfo, PIP_ADAPTER_ADDRESSES pAdapter)
+{
+ PIP_ADAPTER_UNICAST_ADDRESS pAddr;
+ bool fIPFound = false;
+ bool fIPv6Found = false;
+ for (pAddr = pAdapter->FirstUnicastAddress; pAddr; pAddr = pAddr->Next)
+ {
+ switch (pAddr->Address.lpSockaddr->sa_family)
+ {
+ case AF_INET:
+ if (!fIPFound)
+ {
+ fIPFound = true;
+ memcpy(&pInfo->IPAddress,
+ &((struct sockaddr_in *)pAddr->Address.lpSockaddr)->sin_addr.s_addr,
+ sizeof(pInfo->IPAddress));
+ }
+ break;
+ case AF_INET6:
+ if (!fIPv6Found)
+ {
+ fIPv6Found = true;
+ memcpy(&pInfo->IPv6Address,
+ ((struct sockaddr_in6 *)pAddr->Address.lpSockaddr)->sin6_addr.s6_addr,
+ sizeof(pInfo->IPv6Address));
+ }
+ break;
+ }
+ }
+ PIP_ADAPTER_PREFIX pPrefix;
+ ULONG uPrefixLenV4 = 0;
+ ULONG uPrefixLenV6 = 0;
+ for (pPrefix = pAdapter->FirstPrefix; pPrefix && !(uPrefixLenV4 && uPrefixLenV6); pPrefix = pPrefix->Next)
+ {
+ switch (pPrefix->Address.lpSockaddr->sa_family)
+ {
+ case AF_INET:
+ if (!uPrefixLenV4)
+ {
+ ULONG ip = ((PSOCKADDR_IN)(pPrefix->Address.lpSockaddr))->sin_addr.s_addr;
+ netIfLog(("prefix=%RTnaipv4 len=%u\n", ip, pPrefix->PrefixLength));
+ if ( pPrefix->PrefixLength < sizeof(pInfo->IPNetMask) * 8
+ && pPrefix->PrefixLength > 0
+ && (ip & 0xF0) < 224)
+ {
+ uPrefixLenV4 = pPrefix->PrefixLength;
+ RTNetPrefixToMaskIPv4(pPrefix->PrefixLength, &pInfo->IPNetMask);
+ }
+ else
+ netIfLog(("Unexpected IPv4 prefix length of %d\n",
+ pPrefix->PrefixLength));
+ }
+ break;
+ case AF_INET6:
+ if (!uPrefixLenV6)
+ {
+ PBYTE ipv6 = ((PSOCKADDR_IN6)(pPrefix->Address.lpSockaddr))->sin6_addr.s6_addr;
+ netIfLog(("prefix=%RTnaipv6 len=%u\n", ipv6, pPrefix->PrefixLength));
+ if ( pPrefix->PrefixLength < sizeof(pInfo->IPv6NetMask) * 8
+ && pPrefix->PrefixLength > 0
+ && ipv6[0] != 0xFF)
+ {
+ uPrefixLenV6 = pPrefix->PrefixLength;
+ RTNetPrefixToMaskIPv6(pPrefix->PrefixLength, &pInfo->IPv6NetMask);
+ }
+ else
+ netIfLog(("Unexpected IPv6 prefix length of %d\n", pPrefix->PrefixLength));
+ }
+ break;
+ }
+ }
+ netIfLog(("%RTnaipv4/%u\n", pInfo->IPAddress, uPrefixLenV4));
+ netIfLog(("%RTnaipv6/%u\n", &pInfo->IPv6Address, uPrefixLenV6));
+}
+
+/**
+ * Walk through the list of adpater addresses and extract the required
+ * information. XP and older don't not have the OnLinkPrefixLength field.
+ */
+static void netIfFillInfoWithAddressesVista(PNETIFINFO pInfo, PIP_ADAPTER_ADDRESSES pAdapter)
+{
+ PIP_ADAPTER_UNICAST_ADDRESS pAddr;
+
+ if (sizeof(pInfo->MACAddress) != pAdapter->PhysicalAddressLength)
+ netIfLog(("Unexpected physical address length: %u\n", pAdapter->PhysicalAddressLength));
+ else
+ memcpy(pInfo->MACAddress.au8, pAdapter->PhysicalAddress, sizeof(pInfo->MACAddress));
+
+ bool fIPFound = false;
+ bool fIPv6Found = false;
+ for (pAddr = pAdapter->FirstUnicastAddress; pAddr; pAddr = pAddr->Next)
+ {
+ PIP_ADAPTER_UNICAST_ADDRESS_LH pAddrLh = (PIP_ADAPTER_UNICAST_ADDRESS_LH)pAddr;
+ switch (pAddrLh->Address.lpSockaddr->sa_family)
+ {
+ case AF_INET:
+ if (!fIPFound)
+ {
+ fIPFound = true;
+ memcpy(&pInfo->IPAddress,
+ &((struct sockaddr_in *)pAddrLh->Address.lpSockaddr)->sin_addr.s_addr,
+ sizeof(pInfo->IPAddress));
+ if (pAddrLh->OnLinkPrefixLength > 32)
+ netIfLog(("Invalid IPv4 prefix length of %d\n", pAddrLh->OnLinkPrefixLength));
+ else
+ RTNetPrefixToMaskIPv4(pAddrLh->OnLinkPrefixLength, &pInfo->IPNetMask);
+ }
+ break;
+ case AF_INET6:
+ if (!fIPv6Found)
+ {
+ fIPv6Found = true;
+ memcpy(&pInfo->IPv6Address,
+ ((struct sockaddr_in6 *)pAddrLh->Address.lpSockaddr)->sin6_addr.s6_addr,
+ sizeof(pInfo->IPv6Address));
+ if (pAddrLh->OnLinkPrefixLength > 128)
+ netIfLog(("Invalid IPv6 prefix length of %d\n", pAddrLh->OnLinkPrefixLength));
+ else
+ RTNetPrefixToMaskIPv6(pAddrLh->OnLinkPrefixLength, &pInfo->IPv6NetMask);
+ }
+ break;
+ }
+ }
+
+ if (fIPFound)
+ {
+ int iPrefixIPv4 = -1;
+ RTNetMaskToPrefixIPv4(&pInfo->IPNetMask, &iPrefixIPv4);
+ netIfLog(("%RTnaipv4/%u\n", pInfo->IPAddress, iPrefixIPv4));
+ }
+ if (fIPv6Found)
+ {
+ int iPrefixIPv6 = -1;
+ RTNetMaskToPrefixIPv6(&pInfo->IPv6NetMask, &iPrefixIPv6);
+ netIfLog(("%RTnaipv6/%u\n", &pInfo->IPv6Address, iPrefixIPv6));
+ }
+}
+
+#if (NTDDI_VERSION >= NTDDI_VISTA)
+#define NETIF_GAA_FLAGS GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST
+#else /* (NTDDI_VERSION < NTDDI_VISTA) */
+#define NETIF_GAA_FLAGS GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST
+#endif /* (NTDDI_VERSION < NTDDI_VISTA) */
+
+int NetIfList(std::list<ComObjPtr<HostNetworkInterface> > &list)
+{
+ HRESULT hr = S_OK;
+ int iDefault = getDefaultInterfaceIndex();
+ /* MSDN recommends to pre-allocate a 15KB buffer. */
+ ULONG uBufLen = 15 * 1024;
+ PIP_ADAPTER_ADDRESSES pAddresses = (PIP_ADAPTER_ADDRESSES)RTMemAlloc(uBufLen);
+ if (!pAddresses)
+ return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
+ DWORD dwRc = GetAdaptersAddresses(AF_UNSPEC, NETIF_GAA_FLAGS, NULL, pAddresses, &uBufLen);
+ for (int tries = 0; tries < 3 && dwRc == ERROR_BUFFER_OVERFLOW; ++tries)
+ {
+ /* Get more memory and try again. */
+ RTMemFree(pAddresses);
+ pAddresses = (PIP_ADAPTER_ADDRESSES)RTMemAlloc(uBufLen);
+ if (!pAddresses)
+ return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
+ dwRc = GetAdaptersAddresses(AF_UNSPEC, NETIF_GAA_FLAGS, NULL, pAddresses, &uBufLen);
+ }
+ if (dwRc != NO_ERROR)
+ {
+ LogRelFunc(("GetAdaptersAddresses failed (0x%x)\n", dwRc));
+ hr = HRESULT_FROM_WIN32(dwRc);
+ }
+ else
+ {
+ std::list<BoundAdapter> boundAdapters;
+ hr = netIfGetBoundAdapters(boundAdapters);
+#if 0
+ if (hr != S_OK)
+ hr = netIfGetBoundAdaptersFallback(boundAdapters);
+#endif
+ if (hr != S_OK)
+ LogRelFunc(("netIfGetBoundAdapters failed (0x%x)\n", hr));
+ else
+ {
+ PIP_ADAPTER_ADDRESSES pAdapter;
+
+ for (pAdapter = pAddresses; pAdapter; pAdapter = pAdapter->Next)
+ {
+ char *pszUuid = RTStrDup(pAdapter->AdapterName);
+ if (!pszUuid)
+ {
+ LogRelFunc(("out of memory\n"));
+ break;
+ }
+ size_t len = strlen(pszUuid) - 1;
+ if (pszUuid[0] != '{' || pszUuid[len] != '}')
+ LogRelFunc(("ignoring invalid GUID %s\n", pAdapter->AdapterName));
+ else
+ {
+ std::list<BoundAdapter>::iterator it;
+ pszUuid[len] = 0;
+ for (it = boundAdapters.begin(); it != boundAdapters.end(); ++it)
+ {
+ if (!RTUuidCompareStr(&(*it).guid, pszUuid + 1))
+ {
+ (*it).pAdapter = pAdapter;
+ break;
+ }
+ }
+ }
+ RTStrFree(pszUuid);
+ }
+ std::list<BoundAdapter>::iterator it;
+ for (it = boundAdapters.begin(); it != boundAdapters.end(); ++it)
+ {
+ NETIFINFO info;
+ memset(&info, 0, sizeof(info));
+ info.Uuid = (*it).guid;
+ info.enmMediumType = NETIF_T_ETHERNET;
+ info.fWireless = (*it).fWireless;
+ pAdapter = (*it).pAdapter;
+ if (pAdapter)
+ {
+ info.enmStatus = pAdapter->OperStatus == IfOperStatusUp ? NETIF_S_UP : NETIF_S_DOWN;
+ info.fIsDefault = (pAdapter->IfIndex == (DWORD)iDefault);
+ info.fDhcpEnabled = pAdapter->Flags & IP_ADAPTER_DHCP_ENABLED;
+ OSVERSIONINFOEX OSInfoEx;
+ RT_ZERO(OSInfoEx);
+ OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ if ( GetVersionEx((LPOSVERSIONINFO)&OSInfoEx)
+ && OSInfoEx.dwMajorVersion < 6)
+ netIfFillInfoWithAddressesXp(&info, pAdapter);
+ else
+ netIfFillInfoWithAddressesVista(&info, pAdapter);
+ }
+ else
+ info.enmStatus = NETIF_S_DOWN;
+ /* create a new object and add it to the list */
+ ComObjPtr<HostNetworkInterface> iface;
+ iface.createObject();
+ HostNetworkInterfaceType enmType =
+ _wcsnicmp((*it).pHwId, L"sun_VBoxNetAdp", sizeof(L"sun_VBoxNetAdp")/2) ?
+ HostNetworkInterfaceType_Bridged : HostNetworkInterfaceType_HostOnly;
+ netIfLog(("Adding %ls as %s\n", (*it).pName,
+ enmType == HostNetworkInterfaceType_Bridged ? "bridged" :
+ enmType == HostNetworkInterfaceType_HostOnly ? "host-only" : "unknown"));
+ int rc = iface->init((*it).pName, enmType, &info);
+ if (FAILED(rc))
+ LogRelFunc(("HostNetworkInterface::init() -> %Rrc\n", rc));
+ else
+ {
+ if (info.fIsDefault)
+ list.push_front(iface);
+ else
+ list.push_back(iface);
+ }
+ if ((*it).pHwId)
+ CoTaskMemFree((*it).pHwId);
+ if ((*it).pName)
+ CoTaskMemFree((*it).pName);
+ }
+ }
+ }
+ RTMemFree(pAddresses);
+
+ return hr;
+}
diff --git a/src/VBox/Main/src-server/win/PerformanceWin.cpp b/src/VBox/Main/src-server/win/PerformanceWin.cpp
new file mode 100644
index 00000000..fdf1d8be
--- /dev/null
+++ b/src/VBox/Main/src-server/win/PerformanceWin.cpp
@@ -0,0 +1,357 @@
+/* $Id: PerformanceWin.cpp $ */
+/** @file
+ * VBox Windows-specific Performance Classes implementation.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_PERFORMANCECOLLECTOR
+#ifndef _WIN32_WINNT
+# define _WIN32_WINNT 0x0500
+#else /* !_WIN32_WINNT */
+# if (_WIN32_WINNT < 0x0500)
+# error Win XP or later required!
+# endif /* _WIN32_WINNT < 0x0500 */
+#endif /* !_WIN32_WINNT */
+
+#include <iprt/win/windows.h>
+#include <winternl.h>
+#include <psapi.h>
+extern "C" {
+#include <powrprof.h>
+}
+
+#include <iprt/errcore.h>
+#include <iprt/ldr.h>
+#include <iprt/mp.h>
+#include <iprt/mem.h>
+#include <iprt/system.h>
+
+#include <map>
+
+#include "LoggingNew.h"
+#include "Performance.h"
+
+#ifndef NT_ERROR
+#define NT_ERROR(Status) ((ULONG)(Status) >> 30 == 3)
+#endif
+
+namespace pm {
+
+class CollectorWin : public CollectorHAL
+{
+public:
+ CollectorWin();
+ virtual ~CollectorWin();
+ virtual int preCollect(const CollectorHints& hints, uint64_t /* iTick */);
+ virtual int getHostCpuLoad(ULONG *user, ULONG *kernel, ULONG *idle);
+ virtual int getHostCpuMHz(ULONG *mhz);
+ virtual int getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available);
+ virtual int getProcessCpuLoad(RTPROCESS process, ULONG *user, ULONG *kernel);
+ virtual int getProcessMemoryUsage(RTPROCESS process, ULONG *used);
+
+ virtual int getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle);
+ virtual int getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total);
+
+private:
+ struct VMProcessStats
+ {
+ uint64_t cpuUser;
+ uint64_t cpuKernel;
+ uint64_t cpuTotal;
+ uint64_t ramUsed;
+ };
+
+ typedef std::map<RTPROCESS, VMProcessStats> VMProcessMap;
+
+ VMProcessMap mProcessStats;
+
+ typedef BOOL (WINAPI *PFNGST)(LPFILETIME lpIdleTime,
+ LPFILETIME lpKernelTime,
+ LPFILETIME lpUserTime);
+ typedef NTSTATUS (WINAPI *PFNNQSI)(SYSTEM_INFORMATION_CLASS SystemInformationClass,
+ PVOID SystemInformation,
+ ULONG SystemInformationLength,
+ PULONG ReturnLength);
+
+ PFNGST mpfnGetSystemTimes;
+ PFNNQSI mpfnNtQuerySystemInformation;
+
+ ULONG totalRAM;
+};
+
+CollectorHAL *createHAL()
+{
+ return new CollectorWin();
+}
+
+CollectorWin::CollectorWin() : CollectorHAL(), mpfnNtQuerySystemInformation(NULL)
+{
+ /* Note! Both kernel32.dll and ntdll.dll can be assumed to always be present. */
+ mpfnGetSystemTimes = (PFNGST)RTLdrGetSystemSymbol("kernel32.dll", "GetSystemTimes");
+ if (!mpfnGetSystemTimes)
+ {
+ /* Fall back to deprecated NtQuerySystemInformation */
+ mpfnNtQuerySystemInformation = (PFNNQSI)RTLdrGetSystemSymbol("ntdll.dll", "NtQuerySystemInformation");
+ if (!mpfnNtQuerySystemInformation)
+ LogRel(("Warning! Neither GetSystemTimes() nor NtQuerySystemInformation() is not available.\n"
+ " CPU and VM metrics will not be collected! (lasterr %u)\n", GetLastError()));
+ }
+
+ uint64_t cb;
+ int rc = RTSystemQueryTotalRam(&cb);
+ if (RT_FAILURE(rc))
+ totalRAM = 0;
+ else
+ totalRAM = (ULONG)(cb / 1024);
+}
+
+CollectorWin::~CollectorWin()
+{
+}
+
+#define FILETTIME_TO_100NS(ft) (((uint64_t)ft.dwHighDateTime << 32) + ft.dwLowDateTime)
+
+int CollectorWin::preCollect(const CollectorHints& hints, uint64_t /* iTick */)
+{
+ LogFlowThisFuncEnter();
+
+ uint64_t user, kernel, idle, total;
+ int rc = getRawHostCpuLoad(&user, &kernel, &idle);
+ if (RT_FAILURE(rc))
+ return rc;
+ total = user + kernel + idle;
+
+ DWORD dwError;
+ const CollectorHints::ProcessList& processes = hints.getProcessFlags();
+ CollectorHints::ProcessList::const_iterator it;
+
+ mProcessStats.clear();
+
+ for (it = processes.begin(); it != processes.end() && RT_SUCCESS(rc); ++it)
+ {
+ RTPROCESS process = it->first;
+ HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
+ FALSE, process);
+
+ if (!h)
+ {
+ dwError = GetLastError();
+ Log (("OpenProcess() -> 0x%x\n", dwError));
+ rc = RTErrConvertFromWin32(dwError);
+ break;
+ }
+
+ VMProcessStats vmStats;
+ RT_ZERO(vmStats);
+ if ((it->second & COLLECT_CPU_LOAD) != 0)
+ {
+ FILETIME ftCreate, ftExit, ftKernel, ftUser;
+ if (!GetProcessTimes(h, &ftCreate, &ftExit, &ftKernel, &ftUser))
+ {
+ dwError = GetLastError();
+ Log (("GetProcessTimes() -> 0x%x\n", dwError));
+ rc = RTErrConvertFromWin32(dwError);
+ }
+ else
+ {
+ vmStats.cpuKernel = FILETTIME_TO_100NS(ftKernel);
+ vmStats.cpuUser = FILETTIME_TO_100NS(ftUser);
+ vmStats.cpuTotal = total;
+ }
+ }
+ if (RT_SUCCESS(rc) && (it->second & COLLECT_RAM_USAGE) != 0)
+ {
+ PROCESS_MEMORY_COUNTERS pmc;
+ if (!GetProcessMemoryInfo(h, &pmc, sizeof(pmc)))
+ {
+ dwError = GetLastError();
+ Log (("GetProcessMemoryInfo() -> 0x%x\n", dwError));
+ rc = RTErrConvertFromWin32(dwError);
+ }
+ else
+ vmStats.ramUsed = pmc.WorkingSetSize;
+ }
+ CloseHandle(h);
+ mProcessStats[process] = vmStats;
+ }
+
+ LogFlowThisFuncLeave();
+
+ return rc;
+}
+
+int CollectorWin::getHostCpuLoad(ULONG *user, ULONG *kernel, ULONG *idle)
+{
+ RT_NOREF(user, kernel, idle);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION
+{
+ LARGE_INTEGER IdleTime;
+ LARGE_INTEGER KernelTime;
+ LARGE_INTEGER UserTime;
+ LARGE_INTEGER Reserved1[2];
+ ULONG Reserved2;
+} SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION;
+
+int CollectorWin::getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle)
+{
+ LogFlowThisFuncEnter();
+
+ FILETIME ftIdle, ftKernel, ftUser;
+
+ if (mpfnGetSystemTimes)
+ {
+ if (!mpfnGetSystemTimes(&ftIdle, &ftKernel, &ftUser))
+ {
+ DWORD dwError = GetLastError();
+ Log (("GetSystemTimes() -> 0x%x\n", dwError));
+ return RTErrConvertFromWin32(dwError);
+ }
+
+ *user = FILETTIME_TO_100NS(ftUser);
+ *idle = FILETTIME_TO_100NS(ftIdle);
+ *kernel = FILETTIME_TO_100NS(ftKernel) - *idle;
+ }
+ else
+ {
+ /* GetSystemTimes is not available, fall back to NtQuerySystemInformation */
+ if (!mpfnNtQuerySystemInformation)
+ return VERR_NOT_IMPLEMENTED;
+
+ SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION sppi[MAXIMUM_PROCESSORS];
+ ULONG ulReturned;
+ NTSTATUS status = mpfnNtQuerySystemInformation(
+ SystemProcessorPerformanceInformation, &sppi, sizeof(sppi), &ulReturned);
+ if (NT_ERROR(status))
+ {
+ Log(("NtQuerySystemInformation() -> 0x%x\n", status));
+ return RTErrConvertFromNtStatus(status);
+ }
+ /* Sum up values across all processors */
+ *user = *kernel = *idle = 0;
+ for (unsigned i = 0; i < ulReturned / sizeof(sppi[0]); ++i)
+ {
+ *idle += sppi[i].IdleTime.QuadPart;
+ *kernel += sppi[i].KernelTime.QuadPart - sppi[i].IdleTime.QuadPart;
+ *user += sppi[i].UserTime.QuadPart;
+ }
+ }
+
+ LogFlowThisFunc(("user=%lu kernel=%lu idle=%lu\n", *user, *kernel, *idle));
+ LogFlowThisFuncLeave();
+
+ return VINF_SUCCESS;
+}
+
+typedef struct _PROCESSOR_POWER_INFORMATION {
+ ULONG Number;
+ ULONG MaxMhz;
+ ULONG CurrentMhz;
+ ULONG MhzLimit;
+ ULONG MaxIdleState;
+ ULONG CurrentIdleState;
+} PROCESSOR_POWER_INFORMATION , *PPROCESSOR_POWER_INFORMATION;
+
+int CollectorWin::getHostCpuMHz(ULONG *mhz)
+{
+ uint64_t uTotalMhz = 0;
+ RTCPUID nProcessors = RTMpGetCount();
+ PPROCESSOR_POWER_INFORMATION ppi = (PPROCESSOR_POWER_INFORMATION)
+ RTMemAllocZ(nProcessors * sizeof(PROCESSOR_POWER_INFORMATION));
+
+ if (!ppi)
+ return VERR_NO_MEMORY;
+
+ LONG ns = CallNtPowerInformation(ProcessorInformation, NULL, 0, ppi,
+ nProcessors * sizeof(PROCESSOR_POWER_INFORMATION));
+ if (ns)
+ {
+ Log(("CallNtPowerInformation() -> %x\n", ns));
+ RTMemFree(ppi);
+ return VERR_INTERNAL_ERROR;
+ }
+
+ /* Compute an average over all CPUs */
+ for (unsigned i = 0; i < nProcessors; i++)
+ uTotalMhz += ppi[i].CurrentMhz;
+ *mhz = (ULONG)(uTotalMhz / nProcessors);
+
+ RTMemFree(ppi);
+ LogFlowThisFunc(("mhz=%u\n", *mhz));
+ LogFlowThisFuncLeave();
+
+ return VINF_SUCCESS;
+}
+
+int CollectorWin::getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available)
+{
+ AssertReturn(totalRAM, VERR_INTERNAL_ERROR);
+ uint64_t cb;
+ int rc = RTSystemQueryAvailableRam(&cb);
+ if (RT_SUCCESS(rc))
+ {
+ *total = totalRAM;
+ *available = (ULONG)(cb / 1024);
+ *used = *total - *available;
+ }
+ return rc;
+}
+
+int CollectorWin::getProcessCpuLoad(RTPROCESS process, ULONG *user, ULONG *kernel)
+{
+ RT_NOREF(process, user, kernel);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+int CollectorWin::getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total)
+{
+ VMProcessMap::const_iterator it = mProcessStats.find(process);
+
+ if (it == mProcessStats.end())
+ {
+ Log (("No stats pre-collected for process %x\n", process));
+ return VERR_INTERNAL_ERROR;
+ }
+ *user = it->second.cpuUser;
+ *kernel = it->second.cpuKernel;
+ *total = it->second.cpuTotal;
+ return VINF_SUCCESS;
+}
+
+int CollectorWin::getProcessMemoryUsage(RTPROCESS process, ULONG *used)
+{
+ VMProcessMap::const_iterator it = mProcessStats.find(process);
+
+ if (it == mProcessStats.end())
+ {
+ Log (("No stats pre-collected for process %x\n", process));
+ return VERR_INTERNAL_ERROR;
+ }
+ *used = (ULONG)(it->second.ramUsed / 1024);
+ return VINF_SUCCESS;
+}
+
+}
diff --git a/src/VBox/Main/src-server/win/USBProxyBackendWindows.cpp b/src/VBox/Main/src-server/win/USBProxyBackendWindows.cpp
new file mode 100644
index 00000000..d20cfc85
--- /dev/null
+++ b/src/VBox/Main/src-server/win/USBProxyBackendWindows.cpp
@@ -0,0 +1,274 @@
+/* $Id: USBProxyBackendWindows.cpp $ */
+/** @file
+ * VirtualBox USB Proxy Service, Windows Specialization.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_USBPROXYBACKEND
+#include "USBProxyBackend.h"
+#include "LoggingNew.h"
+
+#include <VBox/usb.h>
+#include <iprt/errcore.h>
+
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/errcore.h>
+
+#include <VBox/usblib.h>
+
+
+/**
+ * Initialize data members.
+ */
+USBProxyBackendWindows::USBProxyBackendWindows()
+ : USBProxyBackend(), mhEventInterrupt(INVALID_HANDLE_VALUE)
+{
+ LogFlowThisFunc(("\n"));
+}
+
+USBProxyBackendWindows::~USBProxyBackendWindows()
+{
+}
+
+/**
+ * Initializes the object (called right after construction).
+ *
+ * @returns S_OK on success and non-fatal failures, some COM error otherwise.
+ */
+int USBProxyBackendWindows::init(USBProxyService *aUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings)
+{
+ USBProxyBackend::init(aUsbProxyService, strId, strAddress, fLoadingSettings);
+
+ unconst(m_strBackend) = Utf8Str("host");
+
+ /*
+ * Create the semaphore (considered fatal).
+ */
+ mhEventInterrupt = CreateEvent(NULL, FALSE, FALSE, NULL);
+ AssertReturn(mhEventInterrupt != INVALID_HANDLE_VALUE, VERR_OUT_OF_RESOURCES);
+
+ /*
+ * Initialize the USB lib and stuff.
+ */
+ int rc = USBLibInit();
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Start the poller thread.
+ */
+ rc = start();
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowThisFunc(("returns successfully\n"));
+ return VINF_SUCCESS;
+ }
+
+ USBLibTerm();
+ }
+
+ CloseHandle(mhEventInterrupt);
+ mhEventInterrupt = INVALID_HANDLE_VALUE;
+
+ LogFlowThisFunc(("returns failure!!! (rc=%Rrc)\n", rc));
+ return rc;
+}
+
+
+/**
+ * Stop all service threads and free the device chain.
+ */
+void USBProxyBackendWindows::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /*
+ * Stop the service.
+ */
+ if (isActive())
+ stop();
+
+ if (mhEventInterrupt != INVALID_HANDLE_VALUE)
+ CloseHandle(mhEventInterrupt);
+ mhEventInterrupt = INVALID_HANDLE_VALUE;
+
+ /*
+ * Terminate the library...
+ */
+ int rc = USBLibTerm();
+ AssertRC(rc);
+ USBProxyBackend::uninit();
+}
+
+
+void *USBProxyBackendWindows::insertFilter(PCUSBFILTER aFilter)
+{
+ AssertReturn(aFilter, NULL);
+
+ LogFlow(("USBProxyBackendWindows::insertFilter()\n"));
+
+ void *pvId = USBLibAddFilter(aFilter);
+
+ LogFlow(("USBProxyBackendWindows::insertFilter(): returning pvId=%p\n", pvId));
+
+ return pvId;
+}
+
+
+void USBProxyBackendWindows::removeFilter(void *aID)
+{
+ LogFlow(("USBProxyBackendWindows::removeFilter(): id=%p\n", aID));
+
+ AssertReturnVoid(aID);
+
+ USBLibRemoveFilter(aID);
+}
+
+
+int USBProxyBackendWindows::captureDevice(HostUSBDevice *aDevice)
+{
+ /*
+ * Check preconditions.
+ */
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_Capturing);
+
+ /*
+ * Create a one-shot ignore filter for the device
+ * and trigger a re-enumeration of it.
+ */
+ USBFILTER Filter;
+ USBFilterInit(&Filter, USBFILTERTYPE_ONESHOT_CAPTURE);
+ initFilterFromDevice(&Filter, aDevice);
+ Log(("USBFILTERIDX_PORT=%#x\n", USBFilterGetNum(&Filter, USBFILTERIDX_PORT)));
+ Log(("USBFILTERIDX_BUS=%#x\n", USBFilterGetNum(&Filter, USBFILTERIDX_BUS)));
+
+ void *pvId = USBLibAddFilter(&Filter);
+ if (!pvId)
+ {
+ AssertMsgFailed(("Add one-shot Filter failed\n"));
+ return VERR_GENERAL_FAILURE;
+ }
+
+ int rc = USBLibRunFilters();
+ if (!RT_SUCCESS(rc))
+ {
+ AssertMsgFailed(("Run Filters failed\n"));
+ USBLibRemoveFilter(pvId);
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+int USBProxyBackendWindows::releaseDevice(HostUSBDevice *aDevice)
+{
+ /*
+ * Check preconditions.
+ */
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_ReleasingToHost);
+
+ /*
+ * Create a one-shot ignore filter for the device
+ * and trigger a re-enumeration of it.
+ */
+ USBFILTER Filter;
+ USBFilterInit(&Filter, USBFILTERTYPE_ONESHOT_IGNORE);
+ initFilterFromDevice(&Filter, aDevice);
+ Log(("USBFILTERIDX_PORT=%#x\n", USBFilterGetNum(&Filter, USBFILTERIDX_PORT)));
+ Log(("USBFILTERIDX_BUS=%#x\n", USBFilterGetNum(&Filter, USBFILTERIDX_BUS)));
+
+ void *pvId = USBLibAddFilter(&Filter);
+ if (!pvId)
+ {
+ AssertMsgFailed(("Add one-shot Filter failed\n"));
+ return VERR_GENERAL_FAILURE;
+ }
+
+ int rc = USBLibRunFilters();
+ if (!RT_SUCCESS(rc))
+ {
+ AssertMsgFailed(("Run Filters failed\n"));
+ USBLibRemoveFilter(pvId);
+ return rc;
+ }
+
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Returns whether devices reported by this backend go through a de/re-attach
+ * and device re-enumeration cycle when they are captured or released.
+ */
+bool USBProxyBackendWindows::i_isDevReEnumerationRequired()
+{
+ return true;
+}
+
+
+int USBProxyBackendWindows::wait(unsigned aMillies)
+{
+ return USBLibWaitChange(aMillies);
+}
+
+
+int USBProxyBackendWindows::interruptWait(void)
+{
+ return USBLibInterruptWaitChange();
+}
+
+/**
+ * Gets a list of all devices the VM can grab
+ */
+PUSBDEVICE USBProxyBackendWindows::getDevices(void)
+{
+ PUSBDEVICE pDevices = NULL;
+ uint32_t cDevices = 0;
+
+ Log(("USBProxyBackendWindows::getDevices\n"));
+ USBLibGetDevices(&pDevices, &cDevices);
+ return pDevices;
+}
+
diff --git a/src/VBox/Main/src-server/win/VBoxSVC.rc b/src/VBox/Main/src-server/win/VBoxSVC.rc
new file mode 100644
index 00000000..9a5ff7d9
--- /dev/null
+++ b/src/VBox/Main/src-server/win/VBoxSVC.rc
@@ -0,0 +1,78 @@
+/* $Id: VBoxSVC.rc $ */
+/** @file
+ * VBoxSVC - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+#include "win/resource.h"
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4" // Lang=US English, CharSet=Windows Multilingual
+ BEGIN
+ VALUE "FileDescription", "VirtualBox Interface\0"
+ VALUE "InternalName", "VBoxSVC\0"
+ VALUE "OriginalFilename", "VBoxSVC.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+
+ VALUE "OLESelfRegister", "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+/* Creates the application icon. */
+#include "VBoxSVC-icon.rc"
+
+
+#ifndef VBOX_WITH_MIDL_PROXY_STUB
+/////////////////////////////////////////////////////////////////////////////
+//
+// REGISTRY
+//
+
+IDR_VIRTUALBOX REGISTRY "VBoxSVC.rgs"
+#endif
+
+1 TYPELIB "VirtualBox.tlb"
diff --git a/src/VBox/Main/src-server/win/precomp_vcc.h b/src/VBox/Main/src-server/win/precomp_vcc.h
new file mode 100644
index 00000000..b1853362
--- /dev/null
+++ b/src/VBox/Main/src-server/win/precomp_vcc.h
@@ -0,0 +1,48 @@
+/* $Id: precomp_vcc.h $ */
+/** @file
+ * VirtualBox COM - Visual C++ precompiled header for VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#include <iprt/cdefs.h>
+#include <iprt/win/winsock2.h>
+#include <iprt/win/windows.h>
+#include <VBox/cdefs.h>
+#include <iprt/types.h>
+#include <iprt/cpp/list.h>
+#include <iprt/cpp/meta.h>
+#include <iprt/cpp/ministring.h>
+#include <VBox/com/microatl.h>
+#include <VBox/com/com.h>
+#include <VBox/com/array.h>
+#include <VBox/com/Guid.h>
+#include <VBox/com/string.h>
+
+#include "VBox/com/VirtualBox.h"
+
+#if defined(Log) || defined(LogIsEnabled)
+# error "Log() from iprt/log.h cannot be defined in the precompiled header!"
+#endif
+
diff --git a/src/VBox/Main/src-server/win/svchlp.cpp b/src/VBox/Main/src-server/win/svchlp.cpp
new file mode 100644
index 00000000..549e775c
--- /dev/null
+++ b/src/VBox/Main/src-server/win/svchlp.cpp
@@ -0,0 +1,308 @@
+/* $Id: svchlp.cpp $ */
+/** @file
+ * Definition of SVC Helper Process control routines.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN
+#include "svchlp.h"
+
+//#include "HostImpl.h"
+#include "LoggingNew.h"
+
+#include <iprt/errcore.h>
+
+int netIfNetworkInterfaceHelperServer(SVCHlpClient *aClient, SVCHlpMsg::Code aMsgCode);
+
+using namespace com;
+
+enum { PipeBufSize = 1024 };
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * GetLastError() is known to return NO_ERROR even after the Win32 API
+ * function (i.e. Write() to a non-connected server end of a pipe) returns
+ * FALSE... This method ensures that at least VERR_GENERAL_FAILURE is returned
+ * in cases like that. Intended to be called immediately after a failed API
+ * call.
+ */
+static inline int rtErrConvertFromWin32OnFailure()
+{
+ DWORD err = GetLastError();
+ return err == NO_ERROR ? VERR_GENERAL_FAILURE
+ : RTErrConvertFromWin32 (err);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+SVCHlpClient::SVCHlpClient()
+ : mIsOpen (false), mIsServer (false)
+ , mReadEnd (NULL), mWriteEnd (NULL)
+{
+}
+
+SVCHlpClient::~SVCHlpClient()
+{
+ close();
+}
+
+int SVCHlpClient::create(const char *aName)
+{
+ AssertReturn(aName, VERR_INVALID_PARAMETER);
+
+ if (mIsOpen)
+ return VERR_WRONG_ORDER;
+
+ Bstr pipeName = Utf8StrFmt("\\\\.\\pipe\\%s", aName);
+
+ HANDLE pipe = CreateNamedPipe(pipeName.raw(),
+ PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
+ PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
+ 1, // PIPE_UNLIMITED_INSTANCES,
+ PipeBufSize, PipeBufSize,
+ NMPWAIT_USE_DEFAULT_WAIT,
+ NULL);
+
+ if (pipe == INVALID_HANDLE_VALUE)
+ rtErrConvertFromWin32OnFailure();
+
+ mIsOpen = true;
+ mIsServer = true;
+ mReadEnd = pipe;
+ mWriteEnd = pipe;
+ mName = aName;
+
+ return VINF_SUCCESS;
+}
+
+int SVCHlpClient::open(const char *aName)
+{
+ AssertReturn(aName, VERR_INVALID_PARAMETER);
+
+ if (mIsOpen)
+ return VERR_WRONG_ORDER;
+
+ Bstr pipeName = Utf8StrFmt("\\\\.\\pipe\\%s", aName);
+
+ HANDLE pipe = CreateFile(pipeName.raw(),
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL);
+
+ if (pipe == INVALID_HANDLE_VALUE)
+ rtErrConvertFromWin32OnFailure();
+
+ mIsOpen = true;
+ mIsServer = false;
+ mReadEnd = pipe;
+ mWriteEnd = pipe;
+ mName = aName;
+
+ return VINF_SUCCESS;
+}
+
+int SVCHlpClient::connect()
+{
+ if (!mIsOpen || !mIsServer)
+ return VERR_WRONG_ORDER;
+
+ BOOL ok = ConnectNamedPipe (mReadEnd, NULL);
+ if (!ok && GetLastError() != ERROR_PIPE_CONNECTED)
+ rtErrConvertFromWin32OnFailure();
+
+ return VINF_SUCCESS;
+}
+
+int SVCHlpClient::close()
+{
+ if (!mIsOpen)
+ return VERR_WRONG_ORDER;
+
+ if (mWriteEnd != NULL && mWriteEnd != mReadEnd)
+ {
+ if (!CloseHandle (mWriteEnd))
+ rtErrConvertFromWin32OnFailure();
+ mWriteEnd = NULL;
+ }
+
+ if (mReadEnd != NULL)
+ {
+ if (!CloseHandle (mReadEnd))
+ rtErrConvertFromWin32OnFailure();
+ mReadEnd = NULL;
+ }
+
+ mIsOpen = false;
+ mIsServer = false;
+ mName.setNull();
+
+ return VINF_SUCCESS;
+}
+
+int SVCHlpClient::write (const void *aVal, size_t aLen)
+{
+ AssertReturn(aVal != NULL, VERR_INVALID_PARAMETER);
+ AssertReturn(aLen != 0, VERR_INVALID_PARAMETER);
+
+ if (!mIsOpen)
+ return VERR_WRONG_ORDER;
+
+ DWORD written = 0;
+ BOOL ok = WriteFile (mWriteEnd, aVal, (ULONG)aLen, &written, NULL);
+ AssertReturn(!ok || written == aLen, VERR_GENERAL_FAILURE);
+ return ok ? VINF_SUCCESS : rtErrConvertFromWin32OnFailure();
+}
+
+int SVCHlpClient::write (const Utf8Str &aVal)
+{
+ if (!mIsOpen)
+ return VERR_WRONG_ORDER;
+
+ /* write -1 for NULL strings */
+ if (aVal.isEmpty())
+ return write ((size_t) ~0);
+
+ size_t len = aVal.length();
+
+ /* write string length */
+ int vrc = write (len);
+ if (RT_SUCCESS(vrc))
+ {
+ /* write string data */
+ vrc = write (aVal.c_str(), len);
+ }
+
+ return vrc;
+}
+
+int SVCHlpClient::write (const Guid &aGuid)
+{
+ Utf8Str guidStr = aGuid.toString();
+ return write (guidStr);
+}
+
+int SVCHlpClient::read (void *aVal, size_t aLen)
+{
+ AssertReturn(aVal != NULL, VERR_INVALID_PARAMETER);
+ AssertReturn(aLen != 0, VERR_INVALID_PARAMETER);
+
+ if (!mIsOpen)
+ return VERR_WRONG_ORDER;
+
+ DWORD read = 0;
+ BOOL ok = ReadFile (mReadEnd, aVal, (ULONG)aLen, &read, NULL);
+ AssertReturn(!ok || read == aLen, VERR_GENERAL_FAILURE);
+ return ok ? VINF_SUCCESS : rtErrConvertFromWin32OnFailure();
+}
+
+int SVCHlpClient::read (Utf8Str &aVal)
+{
+ if (!mIsOpen)
+ return VERR_WRONG_ORDER;
+
+ size_t len = 0;
+
+ /* read string length */
+ int vrc = read (len);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* length -1 means a NULL string */
+ if (len == (size_t) ~0)
+ {
+ aVal.setNull();
+ return VINF_SUCCESS;
+ }
+
+ aVal.reserve(len + 1);
+ aVal.mutableRaw()[len] = 0;
+
+ /* read string data */
+ vrc = read (aVal.mutableRaw(), len);
+
+ return vrc;
+}
+
+int SVCHlpClient::read (Guid &aGuid)
+{
+ Utf8Str guidStr;
+ int vrc = read (guidStr);
+ if (RT_SUCCESS(vrc))
+ aGuid = Guid (guidStr.c_str());
+ return vrc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+SVCHlpServer::SVCHlpServer ()
+{
+}
+
+int SVCHlpServer::run()
+{
+ int vrc = VINF_SUCCESS;
+ SVCHlpMsg::Code msgCode = SVCHlpMsg::Null;
+
+ do
+ {
+ vrc = read (msgCode);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* terminate request received */
+ if (msgCode == SVCHlpMsg::Null)
+ return VINF_SUCCESS;
+
+ switch (msgCode)
+ {
+ case SVCHlpMsg::CreateHostOnlyNetworkInterface:
+ case SVCHlpMsg::RemoveHostOnlyNetworkInterface:
+ case SVCHlpMsg::EnableDynamicIpConfig:
+ case SVCHlpMsg::EnableStaticIpConfig:
+ case SVCHlpMsg::EnableStaticIpConfigV6:
+ case SVCHlpMsg::DhcpRediscover:
+ {
+#ifdef VBOX_WITH_NETFLT
+ vrc = netIfNetworkInterfaceHelperServer(this, msgCode);
+#endif
+ break;
+ }
+ default:
+ AssertMsgFailedReturn(("Invalid message code %d (%08lX)\n", msgCode, msgCode),
+ VERR_GENERAL_FAILURE);
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+ while (1);
+
+ /* we never get here */
+ AssertFailed();
+ return VERR_GENERAL_FAILURE;
+}
diff --git a/src/VBox/Main/src-server/win/svchlp.h b/src/VBox/Main/src-server/win/svchlp.h
new file mode 100644
index 00000000..183bd524
--- /dev/null
+++ b/src/VBox/Main/src-server/win/svchlp.h
@@ -0,0 +1,107 @@
+/* $Id: svchlp.h $ */
+/** @file
+ * Declaration of SVC Helper Process control routines.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SRC_src_server_win_svchlp_h
+#define MAIN_INCLUDED_SRC_src_server_win_svchlp_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBox/com/string.h"
+#include "VBox/com/guid.h"
+
+#include <VBox/err.h>
+
+#include <iprt/win/windows.h>
+
+struct SVCHlpMsg
+{
+ enum Code
+ {
+ Null = 0, /* no parameters */
+ OK, /* no parameters */
+ Error, /* Utf8Str string (may be null but must present) */
+
+ CreateHostOnlyNetworkInterface = 100, /* see usage in code */
+ CreateHostOnlyNetworkInterface_OK, /* see usage in code */
+ RemoveHostOnlyNetworkInterface, /* see usage in code */
+ EnableDynamicIpConfig, /* see usage in code */
+ EnableStaticIpConfig, /* see usage in code */
+ EnableStaticIpConfigV6, /* see usage in code */
+ DhcpRediscover, /* see usage in code */
+ };
+};
+
+class SVCHlpClient
+{
+public:
+
+ SVCHlpClient();
+ virtual ~SVCHlpClient();
+
+ int create (const char *aName);
+ int connect();
+ int open (const char *aName);
+ int close();
+
+ bool isOpen() const { return mIsOpen; }
+ bool isServer() const { return mIsServer; }
+ const com::Utf8Str &name() const { return mName; }
+
+ int write (const void *aVal, size_t aLen);
+ template <typename Scalar>
+ int write (Scalar aVal) { return write (&aVal, sizeof (aVal)); }
+ int write (const com::Utf8Str &aVal);
+ int write (const com::Guid &aGuid);
+
+ int read (void *aVal, size_t aLen);
+ template <typename Scalar>
+ int read (Scalar &aVal) { return read (&aVal, sizeof (aVal)); }
+ int read (com::Utf8Str &aVal);
+ int read (com::Guid &aGuid);
+
+private:
+
+ bool mIsOpen : 1;
+ bool mIsServer : 1;
+
+ HANDLE mReadEnd;
+ HANDLE mWriteEnd;
+ com::Utf8Str mName;
+};
+
+class SVCHlpServer : public SVCHlpClient
+{
+public:
+
+ SVCHlpServer();
+
+ int run();
+};
+
+#endif /* !MAIN_INCLUDED_SRC_src_server_win_svchlp_h */
+
diff --git a/src/VBox/Main/src-server/win/svcmain.cpp b/src/VBox/Main/src-server/win/svcmain.cpp
new file mode 100644
index 00000000..311a3bd7
--- /dev/null
+++ b/src/VBox/Main/src-server/win/svcmain.cpp
@@ -0,0 +1,1212 @@
+/* $Id: svcmain.cpp $ */
+/** @file
+ * SVCMAIN - COM out-of-proc server main entry
+ */
+
+/*
+ * Copyright (C) 2004-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_VBOXSVC
+#include <iprt/win/windows.h>
+#ifdef DEBUG_bird
+# include <RpcAsync.h>
+#endif
+
+#include "VBox/com/defs.h"
+#include "VBox/com/com.h"
+#include "VBox/com/VirtualBox.h"
+
+#include "VirtualBoxImpl.h"
+#include "LoggingNew.h"
+
+#include "svchlp.h"
+
+#include <iprt/errcore.h>
+#include <iprt/buildconfig.h>
+#include <iprt/initterm.h>
+#include <iprt/string.h>
+#include <iprt/path.h>
+#include <iprt/getopt.h>
+#include <iprt/message.h>
+#include <iprt/asm.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define MAIN_WND_CLASS L"VirtualBox Interface"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+class CExeModule : public ATL::CComModule
+{
+public:
+ LONG Unlock() throw();
+ DWORD dwThreadID;
+ HANDLE hEventShutdown;
+ void MonitorShutdown();
+ bool StartMonitor();
+ bool HasActiveConnection();
+ bool bActivity;
+ static bool isIdleLockCount(LONG cLocks);
+};
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+BEGIN_OBJECT_MAP(ObjectMap)
+ OBJECT_ENTRY(CLSID_VirtualBox, VirtualBox)
+END_OBJECT_MAP()
+
+CExeModule *g_pModule = NULL;
+HWND g_hMainWindow = NULL;
+HINSTANCE g_hInstance = NULL;
+#ifdef VBOX_WITH_SDS
+/** This is set if we're connected to SDS.
+ *
+ * It means that we should discount a server lock that it is holding when
+ * deciding whether we're idle or not.
+ *
+ * Also, when set we deregister with SDS during class factory destruction. We
+ * exploit this to prevent attempts to deregister during or after COM shutdown.
+ */
+bool g_fRegisteredWithVBoxSDS = false;
+#endif
+
+/* Normal timeout usually used in Shutdown Monitor */
+const DWORD dwNormalTimeout = 5000;
+volatile uint32_t dwTimeOut = dwNormalTimeout; /* time for EXE to be idle before shutting down. Can be decreased at system shutdown phase. */
+
+
+
+/** Passed to CreateThread to monitor the shutdown event. */
+static DWORD WINAPI MonitorProc(void *pv) RT_NOTHROW_DEF
+{
+ CExeModule *p = (CExeModule *)pv;
+ p->MonitorShutdown();
+ return 0;
+}
+
+LONG CExeModule::Unlock() throw()
+{
+ LONG cLocks = ATL::CComModule::Unlock();
+ if (isIdleLockCount(cLocks))
+ {
+ bActivity = true;
+ SetEvent(hEventShutdown); /* tell monitor that we transitioned to zero */
+ }
+ return cLocks;
+}
+
+bool CExeModule::HasActiveConnection()
+{
+ return bActivity || !isIdleLockCount(GetLockCount());
+}
+
+/**
+ * Checks if @a cLocks signifies an IDLE server lock load.
+ *
+ * This takes VBoxSDS into account (i.e. ignores it).
+ */
+/*static*/ bool CExeModule::isIdleLockCount(LONG cLocks)
+{
+#ifdef VBOX_WITH_SDS
+ if (g_fRegisteredWithVBoxSDS)
+ return cLocks <= 1;
+#endif
+ return cLocks <= 0;
+}
+
+/* Monitors the shutdown event */
+void CExeModule::MonitorShutdown()
+{
+ while (1)
+ {
+ WaitForSingleObject(hEventShutdown, INFINITE);
+ DWORD dwWait;
+ do
+ {
+ bActivity = false;
+ dwWait = WaitForSingleObject(hEventShutdown, dwTimeOut);
+ } while (dwWait == WAIT_OBJECT_0);
+ /* timed out */
+ if (!HasActiveConnection()) /* if no activity let's really bail */
+ {
+ /* Disable log rotation at this point, worst case a log file
+ * becomes slightly bigger than it should. Avoids quirks with
+ * log rotation: there might be another API service process
+ * running at this point which would rotate the logs concurrently,
+ * creating a mess. */
+ PRTLOGGER pReleaseLogger = RTLogRelGetDefaultInstance();
+ if (pReleaseLogger)
+ {
+ char szDest[1024];
+ int rc = RTLogQueryDestinations(pReleaseLogger, szDest, sizeof(szDest));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrCat(szDest, sizeof(szDest), " nohistory");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLogDestinations(pReleaseLogger, szDest);
+ AssertRC(rc);
+ }
+ }
+ }
+#if _WIN32_WINNT >= 0x0400
+ CoSuspendClassObjects();
+ if (!HasActiveConnection())
+#endif
+ break;
+ }
+ }
+ CloseHandle(hEventShutdown);
+ PostThreadMessage(dwThreadID, WM_QUIT, 0, 0);
+}
+
+bool CExeModule::StartMonitor()
+{
+ hEventShutdown = CreateEvent(NULL, false, false, NULL);
+ if (hEventShutdown == NULL)
+ return false;
+ DWORD idThreadIgnored;
+ HANDLE h = CreateThread(NULL, 0, MonitorProc, this, 0, &idThreadIgnored);
+ return (h != NULL);
+}
+
+
+#ifdef VBOX_WITH_SDS
+
+class VBoxSVCRegistration;
+
+/**
+ * Custom class factory for the VirtualBox singleton.
+ *
+ * The implementation of CreateInstance is found in win/svcmain.cpp.
+ */
+class VirtualBoxClassFactory : public ATL::CComClassFactory
+{
+private:
+ /** Tri state: 0=uninitialized or initializing; 1=success; -1=failure.
+ * This will be updated after both m_hrcCreate and m_pObj have been set. */
+ volatile int32_t m_iState;
+ /** The result of the instantiation attempt. */
+ HRESULT m_hrcCreate;
+ /** The IUnknown of the VirtualBox object/interface we're working with. */
+ IUnknown *m_pObj;
+ /** Pointer to the IVBoxSVCRegistration implementation that VBoxSDS works with. */
+ VBoxSVCRegistration *m_pVBoxSVC;
+ /** The VBoxSDS interface. */
+ ComPtr<IVirtualBoxSDS> m_ptrVirtualBoxSDS;
+
+public:
+ VirtualBoxClassFactory() : m_iState(0), m_hrcCreate(S_OK), m_pObj(NULL), m_pVBoxSVC(NULL)
+ { }
+
+ virtual ~VirtualBoxClassFactory()
+ {
+ if (m_pObj)
+ {
+ m_pObj->Release();
+ m_pObj = NULL;
+ }
+
+ /* We usually get here during g_pModule->Term() via CoRevokeClassObjec, so COM
+ probably working well enough to talk to SDS when we get here. */
+ if (g_fRegisteredWithVBoxSDS)
+ i_deregisterWithSds();
+ }
+
+ // IClassFactory
+ STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid, void **ppvObj);
+
+ /** Worker for VBoxSVCRegistration::getVirtualBox. */
+ HRESULT i_getVirtualBox(IUnknown **ppResult);
+
+private:
+ HRESULT i_registerWithSds(IUnknown **ppOtherVirtualBox);
+ void i_deregisterWithSds(void);
+
+ friend VBoxSVCRegistration;
+};
+
+
+/**
+ * The VBoxSVC class is handed to VBoxSDS so it can call us back and ask for the
+ * VirtualBox object when the next VBoxSVC for this user registers itself.
+ */
+class VBoxSVCRegistration : public IVBoxSVCRegistration
+{
+private:
+ /** Number of references. */
+ uint32_t volatile m_cRefs;
+
+public:
+ /** Pointer to the factory. */
+ VirtualBoxClassFactory *m_pFactory;
+
+public:
+ VBoxSVCRegistration(VirtualBoxClassFactory *pFactory)
+ : m_cRefs(1), m_pFactory(pFactory)
+ { }
+ virtual ~VBoxSVCRegistration()
+ {
+ if (m_pFactory)
+ {
+ if (m_pFactory->m_pVBoxSVC)
+ m_pFactory->m_pVBoxSVC = NULL;
+ m_pFactory = NULL;
+ }
+ }
+ RTMEMEF_NEW_AND_DELETE_OPERATORS();
+
+ // IUnknown
+ STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject)
+ {
+ if (riid == __uuidof(IUnknown))
+ *ppvObject = (void *)(IUnknown *)this;
+ else if (riid == __uuidof(IVBoxSVCRegistration))
+ *ppvObject = (void *)(IVBoxSVCRegistration *)this;
+ else
+ {
+ return E_NOINTERFACE;
+ }
+ AddRef();
+ return S_OK;
+
+ }
+
+ STDMETHOD_(ULONG,AddRef)(void)
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&m_cRefs);
+ return cRefs;
+ }
+
+ STDMETHOD_(ULONG,Release)(void)
+ {
+ uint32_t cRefs = ASMAtomicDecU32(&m_cRefs);
+ if (cRefs == 0)
+ delete this;
+ return cRefs;
+ }
+
+ // IVBoxSVCRegistration
+ STDMETHOD(GetVirtualBox)(IUnknown **ppResult)
+ {
+ if (m_pFactory)
+ return m_pFactory->i_getVirtualBox(ppResult);
+ return E_FAIL;
+ }
+};
+
+
+HRESULT VirtualBoxClassFactory::i_registerWithSds(IUnknown **ppOtherVirtualBox)
+{
+# ifdef DEBUG_bird
+ RPC_CALL_ATTRIBUTES_V2_W CallAttribs = { RPC_CALL_ATTRIBUTES_VERSION, RPC_QUERY_CLIENT_PID | RPC_QUERY_IS_CLIENT_LOCAL };
+ RPC_STATUS rcRpc = RpcServerInqCallAttributesW(NULL, &CallAttribs);
+ LogRel(("i_registerWithSds: RpcServerInqCallAttributesW -> %#x ClientPID=%#x IsClientLocal=%d ProtocolSequence=%#x CallStatus=%#x CallType=%#x OpNum=%#x InterfaceUuid=%RTuuid\n",
+ rcRpc, CallAttribs.ClientPID, CallAttribs.IsClientLocal, CallAttribs.ProtocolSequence, CallAttribs.CallStatus,
+ CallAttribs.CallType, CallAttribs.OpNum, &CallAttribs.InterfaceUuid));
+# endif
+
+ /*
+ * Connect to VBoxSDS.
+ */
+ HRESULT hrc = CoCreateInstance(CLSID_VirtualBoxSDS, NULL, CLSCTX_LOCAL_SERVER, IID_IVirtualBoxSDS,
+ (void **)m_ptrVirtualBoxSDS.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ /* By default the RPC_C_IMP_LEVEL_IDENTIFY is used for impersonation the client. It allows
+ ACL checking but restricts an access to system objects e.g. files. Call to CoSetProxyBlanket
+ elevates the impersonation level up to RPC_C_IMP_LEVEL_IMPERSONATE allowing the VBoxSDS
+ service to access the files. */
+ hrc = CoSetProxyBlanket(m_ptrVirtualBoxSDS,
+ RPC_C_AUTHN_DEFAULT,
+ RPC_C_AUTHZ_DEFAULT,
+ COLE_DEFAULT_PRINCIPAL,
+ RPC_C_AUTHN_LEVEL_DEFAULT,
+ RPC_C_IMP_LEVEL_IMPERSONATE,
+ NULL,
+ EOAC_DEFAULT);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Create VBoxSVCRegistration object and hand that to VBoxSDS.
+ */
+ m_pVBoxSVC = new VBoxSVCRegistration(this);
+ hrc = E_PENDING;
+ /* we try to register IVirtualBox 10 times */
+ for (int regTimes = 0; hrc == E_PENDING && regTimes < 10; --regTimes)
+ {
+ hrc = m_ptrVirtualBoxSDS->RegisterVBoxSVC(m_pVBoxSVC, GetCurrentProcessId(), ppOtherVirtualBox);
+ if (SUCCEEDED(hrc))
+ {
+ g_fRegisteredWithVBoxSDS = !*ppOtherVirtualBox;
+ return hrc;
+ }
+ /* sleep to give a time for windows session 0 registration */
+ if (hrc == E_PENDING)
+ RTThreadSleep(1000);
+ }
+ m_pVBoxSVC->Release();
+ }
+ }
+ m_ptrVirtualBoxSDS.setNull();
+ m_pVBoxSVC = NULL;
+ *ppOtherVirtualBox = NULL;
+ return hrc;
+}
+
+
+void VirtualBoxClassFactory::i_deregisterWithSds(void)
+{
+ Log(("VirtualBoxClassFactory::i_deregisterWithSds\n"));
+
+ if (m_ptrVirtualBoxSDS.isNotNull())
+ {
+ if (m_pVBoxSVC)
+ {
+ HRESULT hrc = m_ptrVirtualBoxSDS->DeregisterVBoxSVC(m_pVBoxSVC, GetCurrentProcessId());
+ NOREF(hrc);
+ }
+ m_ptrVirtualBoxSDS.setNull();
+ g_fRegisteredWithVBoxSDS = false;
+ }
+ if (m_pVBoxSVC)
+ {
+ m_pVBoxSVC->m_pFactory = NULL;
+ m_pVBoxSVC->Release();
+ m_pVBoxSVC = NULL;
+ }
+}
+
+
+HRESULT VirtualBoxClassFactory::i_getVirtualBox(IUnknown **ppResult)
+{
+# ifdef DEBUG_bird
+ RPC_CALL_ATTRIBUTES_V2_W CallAttribs = { RPC_CALL_ATTRIBUTES_VERSION, RPC_QUERY_CLIENT_PID | RPC_QUERY_IS_CLIENT_LOCAL };
+ RPC_STATUS rcRpc = RpcServerInqCallAttributesW(NULL, &CallAttribs);
+ LogRel(("i_getVirtualBox: RpcServerInqCallAttributesW -> %#x ClientPID=%#x IsClientLocal=%d ProtocolSequence=%#x CallStatus=%#x CallType=%#x OpNum=%#x InterfaceUuid=%RTuuid\n",
+ rcRpc, CallAttribs.ClientPID, CallAttribs.IsClientLocal, CallAttribs.ProtocolSequence, CallAttribs.CallStatus,
+ CallAttribs.CallType, CallAttribs.OpNum, &CallAttribs.InterfaceUuid));
+# endif
+ IUnknown *pObj = m_pObj;
+ if (pObj)
+ {
+ /** @todo Do we need to do something regarding server locking? Hopefully COM
+ * deals with that........... */
+ pObj->AddRef();
+ *ppResult = pObj;
+ Log(("VirtualBoxClassFactory::GetVirtualBox: S_OK - %p\n", pObj));
+ return S_OK;
+ }
+ *ppResult = NULL;
+ Log(("VirtualBoxClassFactory::GetVirtualBox: E_FAIL\n"));
+ return E_FAIL;
+}
+
+
+/**
+ * Custom instantiation of CComObjectCached.
+ *
+ * This catches certain QueryInterface callers for the purpose of watching for
+ * abnormal client process termination (@bugref{3300}).
+ *
+ * @todo just merge this into class VirtualBox VirtualBoxImpl.h
+ */
+class VirtualBoxObjectCached : public VirtualBox
+{
+public:
+ VirtualBoxObjectCached(void * = NULL)
+ : VirtualBox()
+ {
+ }
+
+ virtual ~VirtualBoxObjectCached()
+ {
+ m_iRef = LONG_MIN / 2; /* Catch refcount screwups by setting refcount something insane. */
+ FinalRelease();
+ }
+
+ /** @name IUnknown implementation for VirtualBox
+ * @{ */
+
+ STDMETHOD_(ULONG, AddRef)() throw()
+ {
+ ULONG cRefs = InternalAddRef();
+ if (cRefs == 2)
+ {
+ AssertMsg(ATL::_pAtlModule, ("ATL: referring to ATL module without having one declared in this linking namespace\n"));
+ ATL::_pAtlModule->Lock();
+ }
+ return cRefs;
+ }
+
+ STDMETHOD_(ULONG, Release)() throw()
+ {
+ ULONG cRefs = InternalRelease();
+ if (cRefs == 0)
+ delete this;
+ else if (cRefs == 1)
+ {
+ AssertMsg(ATL::_pAtlModule, ("ATL: referring to ATL module without having one declared in this linking namespace\n"));
+ ATL::_pAtlModule->Unlock();
+ }
+ return cRefs;
+ }
+
+ STDMETHOD(QueryInterface)(REFIID iid, void **ppvObj) throw()
+ {
+ HRESULT hrc = _InternalQueryInterface(iid, ppvObj);
+#ifdef VBOXSVC_WITH_CLIENT_WATCHER
+ i_logCaller("QueryInterface %RTuuid -> %Rhrc %p", &iid, hrc, *ppvObj);
+#endif
+ return hrc;
+ }
+
+ /** @} */
+
+ static HRESULT WINAPI CreateInstance(VirtualBoxObjectCached **ppObj) throw()
+ {
+ AssertReturn(ppObj, E_POINTER);
+ *ppObj = NULL;
+
+ HRESULT hrc = E_OUTOFMEMORY;
+ VirtualBoxObjectCached *p = new (std::nothrow) VirtualBoxObjectCached();
+ if (p)
+ {
+ p->SetVoid(NULL);
+ p->InternalFinalConstructAddRef();
+ hrc = p->_AtlInitialConstruct();
+ if (SUCCEEDED(hrc))
+ hrc = p->FinalConstruct();
+ p->InternalFinalConstructRelease();
+ if (FAILED(hrc))
+ delete p;
+ else
+ *ppObj = p;
+ }
+ return hrc;
+ }
+};
+
+
+/**
+ * Custom class factory impl for the VirtualBox singleton.
+ *
+ * This will consult with VBoxSDS on whether this VBoxSVC instance should
+ * provide the actual VirtualBox instance or just forward the instance from
+ * some other SVC instance.
+ *
+ * @param pUnkOuter This must be NULL.
+ * @param riid Reference to the interface ID to provide.
+ * @param ppvObj Where to return the pointer to the riid instance.
+ *
+ * @return COM status code.
+ */
+STDMETHODIMP VirtualBoxClassFactory::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void **ppvObj)
+{
+# ifdef VBOXSVC_WITH_CLIENT_WATCHER
+ VirtualBox::i_logCaller("VirtualBoxClassFactory::CreateInstance: %RTuuid", riid);
+# endif
+ HRESULT hrc = E_POINTER;
+ if (ppvObj != NULL)
+ {
+ *ppvObj = NULL;
+ // no aggregation for singletons
+ AssertReturn(pUnkOuter == NULL, CLASS_E_NOAGGREGATION);
+
+ /*
+ * We must make sure there is only one instance around.
+ * So, we check without locking and then again after locking.
+ */
+ if (ASMAtomicReadS32(&m_iState) == 0)
+ {
+ Lock();
+ __try
+ {
+ if (ASMAtomicReadS32(&m_iState) == 0)
+ {
+ /*
+ * lock the module to indicate activity
+ * (necessary for the monitor shutdown thread to correctly
+ * terminate the module in case when CreateInstance() fails)
+ */
+ ATL::_pAtlModule->Lock();
+ __try
+ {
+ /*
+ * Now we need to connect to VBoxSDS to register ourselves.
+ */
+ IUnknown *pOtherVirtualBox = NULL;
+ m_hrcCreate = hrc = i_registerWithSds(&pOtherVirtualBox);
+ if (SUCCEEDED(hrc) && pOtherVirtualBox)
+ m_pObj = pOtherVirtualBox;
+ else if (SUCCEEDED(hrc))
+ {
+ ATL::_pAtlModule->Lock();
+ VirtualBoxObjectCached *p;
+ m_hrcCreate = hrc = VirtualBoxObjectCached::CreateInstance(&p);
+ if (SUCCEEDED(hrc))
+ {
+ m_hrcCreate = hrc = p->QueryInterface(IID_IUnknown, (void **)&m_pObj);
+ if (SUCCEEDED(hrc))
+ RTLogClearFileDelayFlag(RTLogRelGetDefaultInstance(), NULL);
+ else
+ {
+ delete p;
+ i_deregisterWithSds();
+ m_pObj = NULL;
+ }
+ }
+ }
+ ASMAtomicWriteS32(&m_iState, SUCCEEDED(hrc) ? 1 : -1);
+ }
+ __finally
+ {
+ ATL::_pAtlModule->Unlock();
+ }
+ }
+ }
+ __finally
+ {
+ if (ASMAtomicReadS32(&m_iState) == 0)
+ {
+ ASMAtomicWriteS32(&m_iState, -1);
+ if (SUCCEEDED(m_hrcCreate))
+ m_hrcCreate = E_FAIL;
+ }
+ Unlock();
+ }
+ }
+
+ /*
+ * Query the requested interface from the IUnknown one we're keeping around.
+ */
+ if (m_hrcCreate == S_OK)
+ hrc = m_pObj->QueryInterface(riid, ppvObj);
+ else
+ hrc = m_hrcCreate;
+ }
+ return hrc;
+}
+
+#endif // VBOX_WITH_SDS
+
+
+/*
+* Wrapper for Win API function ShutdownBlockReasonCreate
+* This function defined starting from Vista only.
+*/
+static BOOL ShutdownBlockReasonCreateAPI(HWND hWnd, LPCWSTR pwszReason)
+{
+ typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNSHUTDOWNBLOCKREASONCREATE,(HWND hWnd, LPCWSTR pwszReason));
+
+ PFNSHUTDOWNBLOCKREASONCREATE pfn
+ = (PFNSHUTDOWNBLOCKREASONCREATE)GetProcAddress(GetModuleHandle(L"User32.dll"), "ShutdownBlockReasonCreate");
+ AssertPtr(pfn);
+
+ BOOL fResult = FALSE;
+ if (pfn)
+ fResult = pfn(hWnd, pwszReason);
+ return fResult;
+}
+
+/*
+* Wrapper for Win API function ShutdownBlockReasonDestroy
+* This function defined starting from Vista only.
+*/
+static BOOL ShutdownBlockReasonDestroyAPI(HWND hWnd)
+{
+ typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNSHUTDOWNBLOCKREASONDESTROY,(HWND hWnd));
+ PFNSHUTDOWNBLOCKREASONDESTROY pfn
+ = (PFNSHUTDOWNBLOCKREASONDESTROY)GetProcAddress(GetModuleHandle(L"User32.dll"), "ShutdownBlockReasonDestroy");
+ AssertPtr(pfn);
+
+ BOOL fResult = FALSE;
+ if (pfn)
+ fResult = pfn(hWnd);
+ return fResult;
+}
+
+static LRESULT CALLBACK WinMainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ LRESULT lResult = 0;
+
+ switch (msg)
+ {
+ case WM_QUERYENDSESSION:
+ {
+ LogRel(("WM_QUERYENDSESSION:%s%s%s%s (0x%08lx)\n",
+ lParam == 0 ? " shutdown" : "",
+ lParam & ENDSESSION_CRITICAL ? " critical" : "",
+ lParam & ENDSESSION_LOGOFF ? " logoff" : "",
+ lParam & ENDSESSION_CLOSEAPP ? " close" : "",
+ (unsigned long)lParam));
+ if (g_pModule)
+ {
+ bool fActiveConnection = g_pModule->HasActiveConnection();
+ if (fActiveConnection)
+ {
+ lResult = FALSE;
+ LogRel(("VBoxSvc has active connections:"
+ " bActivity = %RTbool, lock count = %d\n",
+ g_pModule->bActivity, g_pModule->GetLockCount()));
+
+ /* place the VBoxSVC into system shutdown list */
+ ShutdownBlockReasonCreateAPI(hwnd, L"Has active connections.");
+ /* decrease a latency of MonitorShutdown loop */
+ ASMAtomicXchgU32(&dwTimeOut, 100);
+ Log(("VBoxSVCWinMain: WM_QUERYENDSESSION: VBoxSvc has active connections."
+ " bActivity = %d. Lock count = %d\n",
+ g_pModule->bActivity, g_pModule->GetLockCount()));
+ }
+ else
+ {
+ LogRel(("No active connections:"
+ " bActivity = %RTbool, lock count = %d\n",
+ g_pModule->bActivity, g_pModule->GetLockCount()));
+ lResult = TRUE;
+ }
+ }
+ else
+ AssertMsgFailed(("VBoxSVCWinMain: WM_QUERYENDSESSION: Error: g_pModule is NULL"));
+ break;
+ }
+ case WM_ENDSESSION:
+ {
+ LogRel(("WM_ENDSESSION:%s%s%s%s%s (%s/0x%08lx)\n",
+ lParam == 0 ? " shutdown" : "",
+ lParam & ENDSESSION_CRITICAL ? " critical" : "",
+ lParam & ENDSESSION_LOGOFF ? " logoff" : "",
+ lParam & ENDSESSION_CLOSEAPP ? " close" : "",
+ wParam == FALSE ? " cancelled" : "",
+ wParam ? "TRUE" : "FALSE",
+ (unsigned long)lParam));
+
+ /* Restore timeout of Monitor Shutdown if user canceled system shutdown */
+ if (wParam == FALSE)
+ {
+ Log(("VBoxSVCWinMain: user canceled system shutdown.\n"));
+ ASMAtomicXchgU32(&dwTimeOut, dwNormalTimeout);
+ ShutdownBlockReasonDestroyAPI(hwnd);
+ }
+ break;
+ }
+ case WM_DESTROY:
+ {
+ ShutdownBlockReasonDestroyAPI(hwnd);
+ PostQuitMessage(0);
+ break;
+ }
+
+ default:
+ {
+ lResult = DefWindowProc(hwnd, msg, wParam, lParam);
+ break;
+ }
+ }
+ return lResult;
+}
+
+static int CreateMainWindow()
+{
+ int rc = VINF_SUCCESS;
+ Assert(g_hMainWindow == NULL);
+
+ LogFlow(("CreateMainWindow\n"));
+
+ g_hInstance = (HINSTANCE)GetModuleHandle(NULL);
+
+ /* Register the Window Class. */
+ WNDCLASS wc;
+ RT_ZERO(wc);
+
+ wc.style = CS_NOCLOSE;
+ wc.lpfnWndProc = WinMainWndProc;
+ wc.hInstance = g_hInstance;
+ wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
+ wc.lpszClassName = MAIN_WND_CLASS;
+
+ ATOM atomWindowClass = RegisterClass(&wc);
+ if (atomWindowClass == 0)
+ {
+ LogRel(("Failed to register window class for session monitoring\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ {
+ /* Create the window. */
+ g_hMainWindow = CreateWindowEx(0, MAIN_WND_CLASS, MAIN_WND_CLASS, 0,
+ 0, 0, 1, 1, NULL, NULL, g_hInstance, NULL);
+ if (g_hMainWindow == NULL)
+ {
+ LogRel(("Failed to create window for session monitoring\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ return rc;
+}
+
+
+static void DestroyMainWindow()
+{
+ Assert(g_hMainWindow != NULL);
+ Log(("SVCMain: DestroyMainWindow \n"));
+ if (g_hMainWindow != NULL)
+ {
+ DestroyWindow(g_hMainWindow);
+ g_hMainWindow = NULL;
+ if (g_hInstance != NULL)
+ {
+ UnregisterClass(MAIN_WND_CLASS, g_hInstance);
+ g_hInstance = NULL;
+ }
+ }
+}
+
+
+static const char * const ctrl_event_names[] = {
+ "CTRL_C_EVENT",
+ "CTRL_BREAK_EVENT",
+ "CTRL_CLOSE_EVENT",
+ /* reserved, not used */
+ "<console control event 3>",
+ "<console control event 4>",
+ /* not sent to processes that load gdi32.dll or user32.dll */
+ "CTRL_LOGOFF_EVENT",
+ "CTRL_SHUTDOWN_EVENT",
+};
+
+/** @todo r=uwe placeholder */
+BOOL WINAPI
+ConsoleCtrlHandler(DWORD dwCtrlType) RT_NOTHROW_DEF
+{
+ const char *signame;
+ char namebuf[48];
+ // int rc;
+
+ if (dwCtrlType < RT_ELEMENTS(ctrl_event_names))
+ signame = ctrl_event_names[dwCtrlType];
+ else
+ {
+ /* should not happen, but be prepared */
+ RTStrPrintf(namebuf, sizeof(namebuf),
+ "<console control event %lu>", (unsigned long)dwCtrlType);
+ signame = namebuf;
+ }
+ LogRel(("Got %s\n", signame));
+
+ if (RT_UNLIKELY(g_pModule == NULL))
+ {
+ LogRel(("%s: g_pModule == NULL\n", __FUNCTION__));
+ return TRUE;
+ }
+
+ /* decrease latency of the MonitorShutdown loop */
+ ASMAtomicXchgU32(&dwTimeOut, 100);
+
+ bool fHasClients = g_pModule->HasActiveConnection();
+ if (!fHasClients)
+ {
+ LogRel(("No clients, closing the shop.\n"));
+ return TRUE;
+ }
+
+ LogRel(("VBoxSvc has clients: bActivity = %RTbool, lock count = %d\n",
+ g_pModule->bActivity, g_pModule->GetLockCount()));
+
+ /** @todo r=uwe wait for clients to disconnect */
+ return TRUE;
+}
+
+
+
+/** Special export that make VBoxProxyStub not register this process as one that
+ * VBoxSDS should be watching.
+ */
+extern "C" DECLEXPORT(void) VBOXCALL Is_VirtualBox_service_process_like_VBoxSDS_And_VBoxSDS(void)
+{
+ /* never called, just need to be here */
+}
+
+
+/* thread for registering the VBoxSVC started in session 0 */
+static DWORD WINAPI threadRegisterVirtualBox(LPVOID lpParam) throw()
+{
+ HANDLE hEvent = (HANDLE)lpParam;
+ HRESULT hrc = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ if (SUCCEEDED(hrc))
+ {
+ /* create IVirtualBox instance */
+ ComPtr<IVirtualBox> pVirtualBox;
+ hrc = CoCreateInstance(CLSID_VirtualBox, NULL, CLSCTX_INPROC_SERVER /*CLSCTX_LOCAL_SERVER */, IID_IVirtualBox,
+ (void **)pVirtualBox.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ /* wait a minute allowing clients to connect to the instance */
+ WaitForSingleObject(hEvent, 60 * 1000);
+ /* remove reference. If anybody connected to IVirtualBox it will stay alive. */
+ pVirtualBox.setNull();
+ }
+ CoUninitialize();
+ }
+ return 0L;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/)
+{
+ int argc = __argc;
+ char **argv = __argv;
+
+ /*
+ * Need to parse the command line before initializing the VBox runtime so we can
+ * change to the user home directory before logs are being created.
+ */
+ for (int i = 1; i < argc; i++)
+ if ( (argv[i][0] == '/' || argv[i][0] == '-')
+ && stricmp(&argv[i][1], "embedding") == 0) /* ANSI */
+ {
+ /* %HOMEDRIVE%%HOMEPATH% */
+ wchar_t wszHome[RTPATH_MAX];
+ DWORD cEnv = GetEnvironmentVariable(L"HOMEDRIVE", &wszHome[0], RTPATH_MAX);
+ if (cEnv && cEnv < RTPATH_MAX)
+ {
+ DWORD cwc = cEnv; /* doesn't include NUL */
+ cEnv = GetEnvironmentVariable(L"HOMEPATH", &wszHome[cEnv], RTPATH_MAX - cwc);
+ if (cEnv && cEnv < RTPATH_MAX - cwc)
+ {
+ /* If this fails there is nothing we can do. Ignore. */
+ SetCurrentDirectory(wszHome);
+ }
+ }
+ }
+
+ /*
+ * Initialize the VBox runtime without loading
+ * the support driver.
+ */
+ RTR3InitExe(argc, &argv, 0);
+
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--embedding", 'e', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "-embedding", 'e', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "/embedding", 'e', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "--unregserver", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "-unregserver", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "/unregserver", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "--regserver", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "-regserver", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "/regserver", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "--reregserver", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "-reregserver", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "/reregserver", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "--helper", 'H', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
+ { "-helper", 'H', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
+ { "/helper", 'H', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
+ { "--logfile", 'F', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
+ { "-logfile", 'F', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
+ { "/logfile", 'F', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
+ { "--logrotate", 'R', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "-logrotate", 'R', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "/logrotate", 'R', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "--logsize", 'S', RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_ICASE },
+ { "-logsize", 'S', RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_ICASE },
+ { "/logsize", 'S', RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_ICASE },
+ { "--loginterval", 'I', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "-loginterval", 'I', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "/loginterval", 'I', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "--registervbox", 'b', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "-registervbox", 'b', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "/registervbox", 'b', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ };
+
+ bool fRun = true;
+ bool fRegister = false;
+ bool fUnregister = false;
+ const char *pszPipeName = NULL;
+ const char *pszLogFile = NULL;
+ uint32_t cHistory = 10; // enable log rotation, 10 files
+ uint32_t uHistoryFileTime = RT_SEC_1DAY; // max 1 day per file
+ uint64_t uHistoryFileSize = 100 * _1M; // max 100MB per file
+ bool fRegisterVBox = false;
+
+ RTGETOPTSTATE GetOptState;
+ int vrc = RTGetOptInit(&GetOptState, argc, argv, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
+ AssertRC(vrc);
+
+ RTGETOPTUNION ValueUnion;
+ while ((vrc = RTGetOpt(&GetOptState, &ValueUnion)))
+ {
+ switch (vrc)
+ {
+ case 'e':
+ /* already handled above */
+ break;
+
+ case 'u':
+ fUnregister = true;
+ fRun = false;
+ break;
+
+ case 'r':
+ fRegister = true;
+ fRun = false;
+ break;
+
+ case 'f':
+ fUnregister = true;
+ fRegister = true;
+ fRun = false;
+ break;
+
+ case 'H':
+ pszPipeName = ValueUnion.psz;
+ if (!pszPipeName)
+ pszPipeName = "";
+ fRun = false;
+ break;
+
+ case 'F':
+ pszLogFile = ValueUnion.psz;
+ break;
+
+ case 'R':
+ cHistory = ValueUnion.u32;
+ break;
+
+ case 'S':
+ uHistoryFileSize = ValueUnion.u64;
+ break;
+
+ case 'I':
+ uHistoryFileTime = ValueUnion.u32;
+ break;
+
+ case 'h':
+ {
+ static const WCHAR s_wszText[] = L"Options:\n\n"
+ L"/RegServer:\tregister COM out-of-proc server\n"
+ L"/UnregServer:\tunregister COM out-of-proc server\n"
+ L"/ReregServer:\tunregister and register COM server\n"
+ L"no options:\trun the server";
+ static const WCHAR s_wszTitle[] = L"Usage";
+ fRun = false;
+ MessageBoxW(NULL, s_wszText, s_wszTitle, MB_OK);
+ return 0;
+ }
+
+ case 'V':
+ {
+ static const WCHAR s_wszTitle[] = L"Version";
+ char *pszText = NULL;
+ RTStrAPrintf(&pszText, "%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ PRTUTF16 pwszText = NULL;
+ RTStrToUtf16(pszText, &pwszText);
+ RTStrFree(pszText);
+ MessageBoxW(NULL, pwszText, s_wszTitle, MB_OK);
+ RTUtf16Free(pwszText);
+ fRun = false;
+ return 0;
+ }
+
+ case 'b':
+ fRegisterVBox = true;
+ break;
+
+ default:
+ /** @todo this assumes that stderr is visible, which is not
+ * true for standard Windows applications. */
+ /* continue on command line errors... */
+ RTGetOptPrintError(vrc, &ValueUnion);
+ }
+ }
+
+ /* Only create the log file when running VBoxSVC normally, but not when
+ * registering/unregistering or calling the helper functionality. */
+ if (fRun)
+ {
+ /** @todo Merge this code with server.cpp (use Logging.cpp?). */
+ char szLogFile[RTPATH_MAX];
+ if (!pszLogFile || !*pszLogFile)
+ {
+ vrc = com::GetVBoxUserHomeDirectory(szLogFile, sizeof(szLogFile));
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppend(szLogFile, sizeof(szLogFile), "VBoxSVC.log");
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to construct release log filename, rc=%Rrc", vrc);
+ pszLogFile = szLogFile;
+ }
+
+ RTERRINFOSTATIC ErrInfo;
+ vrc = com::VBoxLogRelCreate("COM Server", pszLogFile,
+ RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
+ VBOXSVC_LOG_DEFAULT, "VBOXSVC_RELEASE_LOG",
+#ifdef VBOX_WITH_SDS
+ RTLOGDEST_FILE | RTLOGDEST_F_DELAY_FILE,
+#else
+ RTLOGDEST_FILE,
+#endif
+ UINT32_MAX /* cMaxEntriesPerGroup */, cHistory, uHistoryFileTime, uHistoryFileSize,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, vrc);
+ }
+
+ /* Set up a build identifier so that it can be seen from core dumps what
+ * exact build was used to produce the core. Same as in Console::i_powerUpThread(). */
+ static char saBuildID[48];
+ RTStrPrintf(saBuildID, sizeof(saBuildID), "%s%s%s%s VirtualBox %s r%u %s%s%s%s",
+ "BU", "IL", "DI", "D", RTBldCfgVersion(), RTBldCfgRevision(), "BU", "IL", "DI", "D");
+
+ AssertCompile(VBOX_COM_INIT_F_DEFAULT == VBOX_COM_INIT_F_AUTO_REG_UPDATE);
+ HRESULT hRes = com::Initialize(fRun ? VBOX_COM_INIT_F_AUTO_REG_UPDATE : 0);
+ AssertLogRelMsg(SUCCEEDED(hRes), ("SVCMAIN: init failed: %Rhrc\n", hRes));
+
+ g_pModule = new CExeModule();
+ if(g_pModule == NULL)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "not enough memory to create ExeModule.");
+ g_pModule->Init(ObjectMap, hInstance, &LIBID_VirtualBox);
+ g_pModule->dwThreadID = GetCurrentThreadId();
+
+ int nRet = 0;
+ if (!fRun)
+ {
+#ifndef VBOX_WITH_MIDL_PROXY_STUB /* VBoxProxyStub.dll does all the registration work. */
+ if (fUnregister)
+ {
+ g_pModule->UpdateRegistryFromResource(IDR_VIRTUALBOX, FALSE);
+ nRet = g_pModule->UnregisterServer(TRUE);
+ }
+ if (fRegister)
+ {
+ g_pModule->UpdateRegistryFromResource(IDR_VIRTUALBOX, TRUE);
+ nRet = g_pModule->RegisterServer(TRUE);
+ }
+#endif
+ if (pszPipeName)
+ {
+ Log(("SVCMAIN: Processing Helper request (cmdline=\"%s\")...\n", pszPipeName));
+
+ if (!*pszPipeName)
+ vrc = VERR_INVALID_PARAMETER;
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* do the helper job */
+ SVCHlpServer server;
+ vrc = server.open(pszPipeName);
+ if (RT_SUCCESS(vrc))
+ vrc = server.run();
+ }
+ if (RT_FAILURE(vrc))
+ {
+ Log(("SVCMAIN: Failed to process Helper request (%Rrc).\n", vrc));
+ nRet = 1;
+ }
+ }
+ }
+ else
+ {
+
+ g_pModule->StartMonitor();
+#if _WIN32_WINNT >= 0x0400
+ hRes = g_pModule->RegisterClassObjects(CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED);
+ _ASSERTE(SUCCEEDED(hRes));
+ hRes = CoResumeClassObjects();
+#else
+ hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE);
+#endif
+ _ASSERTE(SUCCEEDED(hRes));
+
+ /*
+ * Register windows console signal handler to react to Ctrl-C,
+ * Ctrl-Break, Close; but more importantly - to get notified
+ * about shutdown when we are running in the context of the
+ * autostart service - we won't get WM_ENDSESSION in that
+ * case.
+ */
+ ::SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
+
+
+ if (RT_SUCCESS(CreateMainWindow()))
+ Log(("SVCMain: Main window succesfully created\n"));
+ else
+ Log(("SVCMain: Failed to create main window\n"));
+
+ /* create thread to register IVirtualBox in VBoxSDS
+ * It is used for starting the VBoxSVC in the windows
+ * session 0. */
+ HANDLE hWaitEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ HANDLE hRegisterVBoxThread = NULL;
+ if (fRegisterVBox)
+ {
+ DWORD dwThreadId = 0;
+ hRegisterVBoxThread = CreateThread(NULL, 0, threadRegisterVirtualBox, (LPVOID)hWaitEvent,
+ 0, &dwThreadId);
+ }
+
+ MSG msg;
+ while (GetMessage(&msg, 0, 0, 0) > 0)
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ DestroyMainWindow();
+
+ if (fRegisterVBox)
+ {
+ SetEvent(hWaitEvent);
+ WaitForSingleObject(hRegisterVBoxThread, INFINITE);
+ CloseHandle(hRegisterVBoxThread);
+ CloseHandle(hWaitEvent);
+ }
+
+ g_pModule->RevokeClassObjects();
+ }
+
+ g_pModule->Term();
+
+#ifdef VBOX_WITH_SDS
+ g_fRegisteredWithVBoxSDS = false; /* Don't trust COM LPC to work right from now on. */
+#endif
+ com::Shutdown();
+
+ if(g_pModule)
+ delete g_pModule;
+ g_pModule = NULL;
+
+ Log(("SVCMAIN: Returning, COM server process ends.\n"));
+ return nRet;
+}
diff --git a/src/VBox/Main/src-server/xpcom/Makefile.kup b/src/VBox/Main/src-server/xpcom/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-server/xpcom/Makefile.kup
diff --git a/src/VBox/Main/src-server/xpcom/precomp_gcc.h b/src/VBox/Main/src-server/xpcom/precomp_gcc.h
new file mode 100644
index 00000000..3bce15da
--- /dev/null
+++ b/src/VBox/Main/src-server/xpcom/precomp_gcc.h
@@ -0,0 +1,52 @@
+/* $Id: precomp_gcc.h $ */
+/** @file
+ * VirtualBox COM - GNU C++ precompiled header for VBoxSVC.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#include <iprt/cdefs.h>
+#include <VBox/cdefs.h>
+#include <iprt/types.h>
+#include <iprt/cpp/list.h>
+#include <iprt/cpp/meta.h>
+#include <iprt/cpp/ministring.h>
+#include <VBox/com/com.h>
+#include <VBox/com/array.h>
+#include <VBox/com/Guid.h>
+#include <VBox/com/string.h>
+#include <VBox/com/VirtualBox.h>
+
+#if 1
+# include "VirtualBoxBase.h"
+# include <list>
+# include <vector>
+# include <new>
+# include <iprt/time.h>
+#endif
+
+#if defined(Log) || defined(LogIsEnabled)
+# error "Log() from iprt/log.h cannot be defined in the precompiled header!"
+#endif
+
diff --git a/src/VBox/Main/src-server/xpcom/server.cpp b/src/VBox/Main/src-server/xpcom/server.cpp
new file mode 100644
index 00000000..2bc7b081
--- /dev/null
+++ b/src/VBox/Main/src-server/xpcom/server.cpp
@@ -0,0 +1,988 @@
+/* $Id: server.cpp $ */
+/** @file
+ * XPCOM server process (VBoxSVC) start point.
+ */
+
+/*
+ * Copyright (C) 2004-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_VBOXSVC
+#include <ipcIService.h>
+#include <ipcCID.h>
+
+#include <nsIComponentRegistrar.h>
+
+#include <nsGenericFactory.h>
+
+#include "prio.h"
+#include "prproces.h"
+
+#include "server.h"
+
+#include "LoggingNew.h"
+
+#include <VBox/param.h>
+#include <VBox/version.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/initterm.h>
+#include <iprt/critsect.h>
+#include <iprt/getopt.h>
+#include <iprt/message.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/path.h>
+#include <iprt/timer.h>
+#include <iprt/env.h>
+
+#include <signal.h> // for the signal handler
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/resource.h>
+
+/////////////////////////////////////////////////////////////////////////////
+// VirtualBox component instantiation
+/////////////////////////////////////////////////////////////////////////////
+
+#include <nsIGenericFactory.h>
+#include <VBox/com/VirtualBox.h>
+
+#include "VBox/com/NativeEventQueue.h"
+
+#include "ApplianceImpl.h"
+#include "AudioAdapterImpl.h"
+#include "BandwidthControlImpl.h"
+#include "BandwidthGroupImpl.h"
+#include "NetworkServiceRunner.h"
+#include "DHCPServerImpl.h"
+#include "GuestOSTypeImpl.h"
+#include "HostImpl.h"
+#include "HostNetworkInterfaceImpl.h"
+#include "MachineImpl.h"
+#include "MediumFormatImpl.h"
+#include "MediumImpl.h"
+#include "NATEngineImpl.h"
+#include "NetworkAdapterImpl.h"
+#include "ParallelPortImpl.h"
+#include "ProgressProxyImpl.h"
+#include "SerialPortImpl.h"
+#include "SharedFolderImpl.h"
+#include "SnapshotImpl.h"
+#include "StorageControllerImpl.h"
+#include "SystemPropertiesImpl.h"
+#include "USBControllerImpl.h"
+#include "USBDeviceFiltersImpl.h"
+#include "VFSExplorerImpl.h"
+#include "VirtualBoxImpl.h"
+#include "VRDEServerImpl.h"
+#ifdef VBOX_WITH_USB
+# include "HostUSBDeviceImpl.h"
+# include "USBDeviceFilterImpl.h"
+# include "USBDeviceImpl.h"
+#endif
+#ifdef VBOX_WITH_EXTPACK
+# include "ExtPackManagerImpl.h"
+#endif
+# include "NATNetworkImpl.h"
+
+// This needs to stay - it is needed by the service registration below, and
+// is defined in the automatically generated VirtualBoxWrap.cpp
+extern nsIClassInfo *NS_CLASSINFO_NAME(VirtualBoxWrap);
+NS_DECL_CI_INTERFACE_GETTER(VirtualBoxWrap)
+
+////////////////////////////////////////////////////////////////////////////////
+
+static bool gAutoShutdown = false;
+/** Delay before shutting down the VirtualBox server after the last
+ * VirtualBox instance is released, in ms */
+static uint32_t gShutdownDelayMs = 5000;
+
+static com::NativeEventQueue *gEventQ = NULL;
+static PRBool volatile gKeepRunning = PR_TRUE;
+static PRBool volatile gAllowSigUsrQuit = PR_TRUE;
+
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * VirtualBox class factory that destroys the created instance right after
+ * the last reference to it is released by the client, and recreates it again
+ * when necessary (so VirtualBox acts like a singleton object).
+ */
+class VirtualBoxClassFactory : public VirtualBox
+{
+public:
+
+ virtual ~VirtualBoxClassFactory()
+ {
+ LogFlowFunc(("Deleting VirtualBox...\n"));
+
+ FinalRelease();
+ sInstance = NULL;
+
+ LogFlowFunc(("VirtualBox object deleted.\n"));
+ RTPrintf("Informational: VirtualBox object deleted.\n");
+ }
+
+ NS_IMETHOD_(nsrefcnt) Release()
+ {
+ /* we overload Release() to guarantee the VirtualBox destructor is
+ * always called on the main thread */
+
+ nsrefcnt count = VirtualBox::Release();
+
+ if (count == 1)
+ {
+ /* the last reference held by clients is being released
+ * (see GetInstance()) */
+
+ bool onMainThread = RTThreadIsMain(RTThreadSelf());
+ PRBool timerStarted = PR_FALSE;
+
+ /* sTimer is null if this call originates from FactoryDestructor()*/
+ if (sTimer != NULL)
+ {
+ LogFlowFunc(("Last VirtualBox instance was released.\n"));
+ LogFlowFunc(("Scheduling server shutdown in %u ms...\n",
+ gShutdownDelayMs));
+
+ /* make sure the previous timer (if any) is stopped;
+ * otherwise RTTimerStart() will definitely fail. */
+ RTTimerLRStop(sTimer);
+
+ int vrc = RTTimerLRStart(sTimer, gShutdownDelayMs * RT_NS_1MS_64);
+ AssertRC(vrc);
+ timerStarted = RT_BOOL(RT_SUCCESS(vrc));
+ }
+ else
+ {
+ LogFlowFunc(("Last VirtualBox instance was released "
+ "on XPCOM shutdown.\n"));
+ Assert(onMainThread);
+ }
+
+ gAllowSigUsrQuit = PR_TRUE;
+
+ if (!timerStarted)
+ {
+ if (!onMainThread)
+ {
+ /* Failed to start the timer, post the shutdown event
+ * manually if not on the main thread already. */
+ ShutdownTimer(NULL, NULL, 0);
+ }
+ else
+ {
+ /* Here we come if:
+ *
+ * a) gEventQ is 0 which means either FactoryDestructor() is called
+ * or the IPC/DCONNECT shutdown sequence is initiated by the
+ * XPCOM shutdown routine (NS_ShutdownXPCOM()), which always
+ * happens on the main thread.
+ *
+ * b) gEventQ has reported we're on the main thread. This means
+ * that DestructEventHandler() has been called, but another
+ * client was faster and requested VirtualBox again.
+ *
+ * In either case, there is nothing to do.
+ *
+ * Note: case b) is actually no more valid since we don't
+ * call Release() from DestructEventHandler() in this case
+ * any more. Thus, we assert below.
+ */
+
+ Assert(!gEventQ);
+ }
+ }
+ }
+
+ return count;
+ }
+
+ class MaybeQuitEvent : public NativeEvent
+ {
+ public:
+ MaybeQuitEvent() :
+ m_fSignal(false)
+ {
+ }
+
+ MaybeQuitEvent(bool fSignal) :
+ m_fSignal(fSignal)
+ {
+ }
+
+ private:
+ /* called on the main thread */
+ void *handler()
+ {
+ LogFlowFuncEnter();
+
+ Assert(RTCritSectIsInitialized(&sLock));
+
+ /* stop accepting GetInstance() requests on other threads during
+ * possible destruction */
+ RTCritSectEnter(&sLock);
+
+ nsrefcnt count = 1;
+
+ /* sInstance is NULL here if it was deleted immediately after
+ * creation due to initialization error. See GetInstance(). */
+ if (sInstance != NULL)
+ {
+ /* Safe way to get current refcount is by first increasing and
+ * then decreasing. Keep in mind that the Release is overloaded
+ * (see VirtualBoxClassFactory::Release) and will start the
+ * timer again if the returned count is 1. It won't do harm,
+ * but also serves no purpose, so stop it ASAP. */
+ sInstance->AddRef();
+ count = sInstance->Release();
+ if (count == 1)
+ {
+ RTTimerLRStop(sTimer);
+ /* Release the guard reference added in GetInstance() */
+ sInstance->Release();
+ }
+ }
+
+ if (count == 1)
+ {
+ if (gAutoShutdown || m_fSignal)
+ {
+ Assert(sInstance == NULL);
+ LogFlowFunc(("Terminating the server process...\n"));
+ /* make it leave the event loop */
+ gKeepRunning = PR_FALSE;
+ }
+ else
+ LogFlowFunc(("No automatic shutdown.\n"));
+ }
+ else
+ {
+ /* This condition is quite rare: a new client happened to
+ * connect after this event has been posted to the main queue
+ * but before it started to process it. */
+ LogRel(("Destruction is canceled (refcnt=%d).\n", count));
+ }
+
+ RTCritSectLeave(&sLock);
+
+ LogFlowFuncLeave();
+ return NULL;
+ }
+
+ bool m_fSignal;
+ };
+
+ static DECLCALLBACK(void) ShutdownTimer(RTTIMERLR hTimerLR, void *pvUser, uint64_t /*iTick*/)
+ {
+ NOREF(hTimerLR);
+ NOREF(pvUser);
+
+ /* A "too late" event is theoretically possible if somebody
+ * manually ended the server after a destruction has been scheduled
+ * and this method was so lucky that it got a chance to run before
+ * the timer was killed. */
+ com::NativeEventQueue *q = gEventQ;
+ AssertReturnVoid(q);
+
+ /* post a quit event to the main queue */
+ MaybeQuitEvent *ev = new MaybeQuitEvent(false /* fSignal */);
+ if (!q->postEvent(ev))
+ delete ev;
+
+ /* A failure above means we've been already stopped (for example
+ * by Ctrl-C). FactoryDestructor() (NS_ShutdownXPCOM())
+ * will do the job. Nothing to do. */
+ }
+
+ static NS_IMETHODIMP FactoryConstructor()
+ {
+ LogFlowFunc(("\n"));
+
+ /* create a critsect to protect object construction */
+ if (RT_FAILURE(RTCritSectInit(&sLock)))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ int vrc = RTTimerLRCreateEx(&sTimer, 0, 0, ShutdownTimer, NULL);
+ if (RT_FAILURE(vrc))
+ {
+ LogFlowFunc(("Failed to create a timer! (vrc=%Rrc)\n", vrc));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ static NS_IMETHODIMP FactoryDestructor()
+ {
+ LogFlowFunc(("\n"));
+
+ RTTimerLRDestroy(sTimer);
+ sTimer = NULL;
+
+ if (sInstance != NULL)
+ {
+ /* Either posting a destruction event failed for some reason (most
+ * likely, the quit event has been received before the last release),
+ * or the client has terminated abnormally w/o releasing its
+ * VirtualBox instance (so NS_ShutdownXPCOM() is doing a cleanup).
+ * Release the guard reference we added in GetInstance(). */
+ sInstance->Release();
+ }
+
+ /* Destroy lock after releasing the VirtualBox instance, otherwise
+ * there are races with cleanup. */
+ RTCritSectDelete(&sLock);
+
+ return NS_OK;
+ }
+
+ static nsresult GetInstance(VirtualBox **inst)
+ {
+ LogFlowFunc(("Getting VirtualBox object...\n"));
+
+ RTCritSectEnter(&sLock);
+
+ if (!gKeepRunning)
+ {
+ LogFlowFunc(("Process termination requested first. Refusing.\n"));
+
+ RTCritSectLeave(&sLock);
+
+ /* this rv is what CreateInstance() on the client side returns
+ * when the server process stops accepting events. Do the same
+ * here. The client wrapper should attempt to start a new process in
+ * response to a failure from us. */
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv = NS_OK;
+
+ if (sInstance == NULL)
+ {
+ LogFlowFunc(("Creating new VirtualBox object...\n"));
+ sInstance = new VirtualBoxClassFactory();
+ if (sInstance != NULL)
+ {
+ /* make an extra AddRef to take the full control
+ * on the VirtualBox destruction (see FinalRelease()) */
+ sInstance->AddRef();
+
+ sInstance->AddRef(); /* protect FinalConstruct() */
+ rv = sInstance->FinalConstruct();
+ RTPrintf("Informational: VirtualBox object created (rc=%Rhrc).\n", rv);
+ if (NS_FAILED(rv))
+ {
+ /* On failure diring VirtualBox initialization, delete it
+ * immediately on the current thread by releasing all
+ * references in order to properly schedule the server
+ * shutdown. Since the object is fully deleted here, there
+ * is a chance to fix the error and request a new
+ * instantiation before the server terminates. However,
+ * the main reason to maintain the shutdown delay on
+ * failure is to let the front-end completely fetch error
+ * info from a server-side IVirtualBoxErrorInfo object. */
+ sInstance->Release();
+ sInstance->Release();
+ Assert(sInstance == NULL);
+ }
+ else
+ {
+ /* On success, make sure the previous timer is stopped to
+ * cancel a scheduled server termination (if any). */
+ gAllowSigUsrQuit = PR_FALSE;
+ RTTimerLRStop(sTimer);
+ }
+ }
+ else
+ {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ else
+ {
+ LogFlowFunc(("Using existing VirtualBox object...\n"));
+ nsrefcnt count = sInstance->AddRef();
+ Assert(count > 1);
+
+ if (count >= 2)
+ {
+ LogFlowFunc(("Another client has requested a reference to VirtualBox, canceling destruction...\n"));
+
+ /* make sure the previous timer is stopped */
+ gAllowSigUsrQuit = PR_FALSE;
+ RTTimerLRStop(sTimer);
+ }
+ }
+
+ *inst = sInstance;
+
+ RTCritSectLeave(&sLock);
+
+ return rv;
+ }
+
+private:
+
+ /* Don't be confused that sInstance is of the *ClassFactory type. This is
+ * actually a singleton instance (*ClassFactory inherits the singleton
+ * class; we combined them just for "simplicity" and used "static" for
+ * factory methods. *ClassFactory here is necessary for a couple of extra
+ * methods. */
+
+ static VirtualBoxClassFactory *sInstance;
+ static RTCRITSECT sLock;
+
+ static RTTIMERLR sTimer;
+};
+
+VirtualBoxClassFactory *VirtualBoxClassFactory::sInstance = NULL;
+RTCRITSECT VirtualBoxClassFactory::sLock;
+
+RTTIMERLR VirtualBoxClassFactory::sTimer = NIL_RTTIMERLR;
+
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR_WITH_RC(VirtualBox, VirtualBoxClassFactory::GetInstance)
+
+////////////////////////////////////////////////////////////////////////////////
+
+typedef NSFactoryDestructorProcPtr NSFactoryConstructorProcPtr;
+
+/**
+ * Enhanced module component information structure.
+ *
+ * nsModuleComponentInfo lacks the factory construction callback, here we add
+ * it. This callback is called straight after a nsGenericFactory instance is
+ * successfully created in RegisterSelfComponents.
+ */
+struct nsModuleComponentInfoPlusFactoryConstructor
+{
+ /** standard module component information */
+ const nsModuleComponentInfo *mpModuleComponentInfo;
+ /** (optional) Factory Construction Callback */
+ NSFactoryConstructorProcPtr mFactoryConstructor;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Helper function to register self components upon start-up
+ * of the out-of-proc server.
+ */
+static nsresult
+RegisterSelfComponents(nsIComponentRegistrar *registrar,
+ const nsModuleComponentInfoPlusFactoryConstructor *aComponents,
+ PRUint32 count)
+{
+ nsresult rc = NS_OK;
+ const nsModuleComponentInfoPlusFactoryConstructor *info = aComponents;
+ for (PRUint32 i = 0; i < count && NS_SUCCEEDED(rc); i++, info++)
+ {
+ /* skip components w/o a constructor */
+ if (!info->mpModuleComponentInfo->mConstructor)
+ continue;
+ /* create a new generic factory for a component and register it */
+ nsIGenericFactory *factory;
+ rc = NS_NewGenericFactory(&factory, info->mpModuleComponentInfo);
+ if (NS_SUCCEEDED(rc) && info->mFactoryConstructor)
+ {
+ rc = info->mFactoryConstructor();
+ if (NS_FAILED(rc))
+ NS_RELEASE(factory);
+ }
+ if (NS_SUCCEEDED(rc))
+ {
+ rc = registrar->RegisterFactory(info->mpModuleComponentInfo->mCID,
+ info->mpModuleComponentInfo->mDescription,
+ info->mpModuleComponentInfo->mContractID,
+ factory);
+ NS_RELEASE(factory);
+ }
+ }
+ return rc;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static ipcIService *gIpcServ = nsnull;
+static const char *g_pszPidFile = NULL;
+
+class ForceQuitEvent : public NativeEvent
+{
+ void *handler()
+ {
+ LogFlowFunc(("\n"));
+
+ gKeepRunning = PR_FALSE;
+
+ if (g_pszPidFile)
+ RTFileDelete(g_pszPidFile);
+
+ return NULL;
+ }
+};
+
+static void signal_handler(int sig)
+{
+ com::NativeEventQueue *q = gEventQ;
+ if (q && gKeepRunning)
+ {
+ if (sig == SIGUSR1)
+ {
+ if (gAllowSigUsrQuit)
+ {
+ /* terminate the server process if it is idle */
+ VirtualBoxClassFactory::MaybeQuitEvent *ev = new VirtualBoxClassFactory::MaybeQuitEvent(true /* fSignal */);
+ if (!q->postEvent(ev))
+ delete ev;
+ }
+ /* else do nothing */
+ }
+ else
+ {
+ /* post a force quit event to the queue */
+ ForceQuitEvent *ev = new ForceQuitEvent();
+ if (!q->postEvent(ev))
+ delete ev;
+ }
+ }
+}
+
+static nsresult vboxsvcSpawnDaemonByReExec(const char *pszPath, bool fAutoShutdown, const char *pszPidFile)
+{
+ PRFileDesc *readable = nsnull, *writable = nsnull;
+ PRProcessAttr *attr = nsnull;
+ nsresult rv = NS_ERROR_FAILURE;
+ PRFileDesc *devNull;
+ unsigned args_index = 0;
+ // The ugly casts are necessary because the PR_CreateProcessDetached has
+ // a const array of writable strings as a parameter. It won't write. */
+ char * args[1 + 1 + 2 + 1];
+ args[args_index++] = (char *)pszPath;
+ if (fAutoShutdown)
+ args[args_index++] = (char *)"--auto-shutdown";
+ if (pszPidFile)
+ {
+ args[args_index++] = (char *)"--pidfile";
+ args[args_index++] = (char *)pszPidFile;
+ }
+ args[args_index++] = 0;
+
+ // Use a pipe to determine when the daemon process is in the position
+ // to actually process requests. The daemon will write "READY" to the pipe.
+ if (PR_CreatePipe(&readable, &writable) != PR_SUCCESS)
+ goto end;
+ PR_SetFDInheritable(writable, PR_TRUE);
+
+ attr = PR_NewProcessAttr();
+ if (!attr)
+ goto end;
+
+ if (PR_ProcessAttrSetInheritableFD(attr, writable, VBOXSVC_STARTUP_PIPE_NAME) != PR_SUCCESS)
+ goto end;
+
+ devNull = PR_Open("/dev/null", PR_RDWR, 0);
+ if (!devNull)
+ goto end;
+
+ PR_ProcessAttrSetStdioRedirect(attr, PR_StandardInput, devNull);
+ PR_ProcessAttrSetStdioRedirect(attr, PR_StandardOutput, devNull);
+ PR_ProcessAttrSetStdioRedirect(attr, PR_StandardError, devNull);
+
+ if (PR_CreateProcessDetached(pszPath, (char * const *)args, nsnull, attr) != PR_SUCCESS)
+ goto end;
+
+ // Close /dev/null
+ PR_Close(devNull);
+ // Close the child end of the pipe to make it the only owner of the
+ // file descriptor, so that unexpected closing can be detected.
+ PR_Close(writable);
+ writable = nsnull;
+
+ char msg[10];
+ memset(msg, '\0', sizeof(msg));
+ if ( PR_Read(readable, msg, sizeof(msg)-1) != 5
+ || strcmp(msg, "READY"))
+ goto end;
+
+ rv = NS_OK;
+
+end:
+ if (readable)
+ PR_Close(readable);
+ if (writable)
+ PR_Close(writable);
+ if (attr)
+ PR_DestroyProcessAttr(attr);
+ return rv;
+}
+
+static void showUsage(const char *pcszFileName)
+{
+ RTPrintf(VBOX_PRODUCT " VBoxSVC "
+ VBOX_VERSION_STRING "\n"
+ "Copyright (C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
+ RTPrintf("By default the service will be started in the background.\n"
+ "\n");
+ RTPrintf("Usage:\n"
+ "\n");
+ RTPrintf(" %s\n", pcszFileName);
+ RTPrintf("\n");
+ RTPrintf("Options:\n");
+ RTPrintf(" -a, --automate Start XPCOM on demand and daemonize.\n");
+ RTPrintf(" -A, --auto-shutdown Shuts down service if no longer in use.\n");
+ RTPrintf(" -d, --daemonize Starts service in background.\n");
+ RTPrintf(" -D, --shutdown-delay <ms> Sets shutdown delay in ms.\n");
+ RTPrintf(" -h, --help Displays this help.\n");
+ RTPrintf(" -p, --pidfile <path> Uses a specific pidfile.\n");
+ RTPrintf(" -F, --logfile <path> Uses a specific logfile.\n");
+ RTPrintf(" -R, --logrotate <count> Number of old log files to keep.\n");
+ RTPrintf(" -S, --logsize <bytes> Maximum size of a log file before rotating.\n");
+ RTPrintf(" -I, --loginterval <s> Maximum amount of time to put in a log file.\n");
+
+ RTPrintf("\n");
+}
+
+int main(int argc, char **argv)
+{
+ /*
+ * Initialize the VBox runtime without loading
+ * the support driver
+ */
+ int vrc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(vrc))
+ return RTMsgInitFailure(vrc);
+
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--automate", 'a', RTGETOPT_REQ_NOTHING },
+ { "--auto-shutdown", 'A', RTGETOPT_REQ_NOTHING },
+ { "--daemonize", 'd', RTGETOPT_REQ_NOTHING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "--shutdown-delay", 'D', RTGETOPT_REQ_UINT32 },
+ { "--pidfile", 'p', RTGETOPT_REQ_STRING },
+ { "--logfile", 'F', RTGETOPT_REQ_STRING },
+ { "--logrotate", 'R', RTGETOPT_REQ_UINT32 },
+ { "--logsize", 'S', RTGETOPT_REQ_UINT64 },
+ { "--loginterval", 'I', RTGETOPT_REQ_UINT32 }
+ };
+
+ const char *pszLogFile = NULL;
+ uint32_t cHistory = 10; // enable log rotation, 10 files
+ uint32_t uHistoryFileTime = RT_SEC_1DAY; // max 1 day per file
+ uint64_t uHistoryFileSize = 100 * _1M; // max 100MB per file
+ bool fDaemonize = false;
+ PRFileDesc *daemon_pipe_wr = nsnull;
+
+ RTGETOPTSTATE GetOptState;
+ vrc = RTGetOptInit(&GetOptState, argc, argv, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
+ AssertRC(vrc);
+
+ RTGETOPTUNION ValueUnion;
+ while ((vrc = RTGetOpt(&GetOptState, &ValueUnion)))
+ {
+ switch (vrc)
+ {
+ case 'a':
+ /* --automate mode means we are started by XPCOM on
+ * demand. Daemonize ourselves and activate
+ * auto-shutdown. */
+ gAutoShutdown = true;
+ fDaemonize = true;
+ break;
+
+ case 'A':
+ /* --auto-shutdown mode means we're already daemonized. */
+ gAutoShutdown = true;
+ break;
+
+ case 'd':
+ fDaemonize = true;
+ break;
+
+ case 'D':
+ gShutdownDelayMs = ValueUnion.u32;
+ break;
+
+ case 'p':
+ g_pszPidFile = ValueUnion.psz;
+ break;
+
+ case 'F':
+ pszLogFile = ValueUnion.psz;
+ break;
+
+ case 'R':
+ cHistory = ValueUnion.u32;
+ break;
+
+ case 'S':
+ uHistoryFileSize = ValueUnion.u64;
+ break;
+
+ case 'I':
+ uHistoryFileTime = ValueUnion.u32;
+ break;
+
+ case 'h':
+ showUsage(argv[0]);
+ return RTEXITCODE_SYNTAX;
+
+ case 'V':
+ RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(vrc, &ValueUnion);
+ }
+ }
+
+ if (fDaemonize)
+ {
+ vboxsvcSpawnDaemonByReExec(argv[0], gAutoShutdown, g_pszPidFile);
+ exit(126);
+ }
+
+ nsresult rc;
+
+ /** @todo Merge this code with svcmain.cpp (use Logging.cpp?). */
+ char szLogFile[RTPATH_MAX];
+ if (!pszLogFile)
+ {
+ vrc = com::GetVBoxUserHomeDirectory(szLogFile, sizeof(szLogFile));
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppend(szLogFile, sizeof(szLogFile), "VBoxSVC.log");
+ }
+ else
+ {
+ if (!RTStrPrintf(szLogFile, sizeof(szLogFile), "%s", pszLogFile))
+ vrc = VERR_NO_MEMORY;
+ }
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to create logging file name, rc=%Rrc", vrc);
+
+ RTERRINFOSTATIC ErrInfo;
+ vrc = com::VBoxLogRelCreate("XPCOM Server", szLogFile,
+ RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
+ VBOXSVC_LOG_DEFAULT, "VBOXSVC_RELEASE_LOG",
+ RTLOGDEST_FILE, UINT32_MAX /* cMaxEntriesPerGroup */,
+ cHistory, uHistoryFileTime, uHistoryFileSize,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, vrc);
+
+ /* Set up a build identifier so that it can be seen from core dumps what
+ * exact build was used to produce the core. Same as in Console::i_powerUpThread(). */
+ static char saBuildID[48];
+ RTStrPrintf(saBuildID, sizeof(saBuildID), "%s%s%s%s VirtualBox %s r%u %s%s%s%s",
+ "BU", "IL", "DI", "D", RTBldCfgVersion(), RTBldCfgRevision(), "BU", "IL", "DI", "D");
+
+ daemon_pipe_wr = PR_GetInheritedFD(VBOXSVC_STARTUP_PIPE_NAME);
+ RTEnvUnset("NSPR_INHERIT_FDS");
+
+ const nsModuleComponentInfo VirtualBoxInfo = {
+ "VirtualBox component",
+ NS_VIRTUALBOX_CID,
+ NS_VIRTUALBOX_CONTRACTID,
+ VirtualBoxConstructor, // constructor function
+ NULL, // registration function
+ NULL, // deregistration function
+ VirtualBoxClassFactory::FactoryDestructor, // factory destructor function
+ NS_CI_INTERFACE_GETTER_NAME(VirtualBoxWrap),
+ NULL, // language helper
+ &NS_CLASSINFO_NAME(VirtualBoxWrap),
+ 0 // flags
+ };
+
+ const nsModuleComponentInfoPlusFactoryConstructor components[] = {
+ {
+ &VirtualBoxInfo,
+ VirtualBoxClassFactory::FactoryConstructor // factory constructor function
+ }
+ };
+
+ do /* goto avoidance only */
+ {
+ rc = com::Initialize();
+ if (NS_FAILED(rc))
+ {
+ RTMsgError("Failed to initialize XPCOM! (rc=%Rhrc)\n", rc);
+ break;
+ }
+
+ nsCOMPtr<nsIComponentRegistrar> registrar;
+ rc = NS_GetComponentRegistrar(getter_AddRefs(registrar));
+ if (NS_FAILED(rc))
+ {
+ RTMsgError("Failed to get component registrar! (rc=%Rhrc)", rc);
+ break;
+ }
+
+ registrar->AutoRegister(nsnull);
+ rc = RegisterSelfComponents(registrar, components,
+ NS_ARRAY_LENGTH(components));
+ if (NS_FAILED(rc))
+ {
+ RTMsgError("Failed to register server components! (rc=%Rhrc)", rc);
+ break;
+ }
+
+ nsCOMPtr<ipcIService> ipcServ(do_GetService(IPC_SERVICE_CONTRACTID, &rc));
+ if (NS_FAILED(rc))
+ {
+ RTMsgError("Failed to get IPC service! (rc=%Rhrc)", rc);
+ break;
+ }
+
+ NS_ADDREF(gIpcServ = ipcServ);
+
+ LogFlowFunc(("Will use \"%s\" as server name.\n", VBOXSVC_IPC_NAME));
+
+ rc = gIpcServ->AddName(VBOXSVC_IPC_NAME);
+ if (NS_FAILED(rc))
+ {
+ LogFlowFunc(("Failed to register the server name (rc=%Rhrc (%08X))!\n"
+ "Is another server already running?\n", rc, rc));
+
+ RTMsgError("Failed to register the server name \"%s\" (rc=%Rhrc)!\n"
+ "Is another server already running?\n",
+ VBOXSVC_IPC_NAME, rc);
+ NS_RELEASE(gIpcServ);
+ break;
+ }
+
+ {
+ /* setup signal handling to convert some signals to a quit event */
+ struct sigaction sa;
+ sa.sa_handler = signal_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGQUIT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+// XXX Temporary allow release assertions to terminate VBoxSVC
+// sigaction(SIGTRAP, &sa, NULL);
+ sigaction(SIGUSR1, &sa, NULL);
+ }
+
+ {
+ char szBuf[80];
+ size_t cSize;
+
+ cSize = RTStrPrintf(szBuf, sizeof(szBuf),
+ VBOX_PRODUCT" XPCOM Server Version "
+ VBOX_VERSION_STRING);
+ for (size_t i = cSize; i > 0; i--)
+ putchar('*');
+ RTPrintf("\n%s\n", szBuf);
+ RTPrintf("Copyright (C) 2004-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
+#ifdef DEBUG
+ RTPrintf("Debug version.\n");
+#endif
+ }
+
+ if (daemon_pipe_wr != nsnull)
+ {
+ RTPrintf("\nStarting event loop....\n[send TERM signal to quit]\n");
+ /* now we're ready, signal the parent process */
+ PR_Write(daemon_pipe_wr, RT_STR_TUPLE("READY"));
+ /* close writing end of the pipe, its job is done */
+ PR_Close(daemon_pipe_wr);
+ }
+ else
+ RTPrintf("\nStarting event loop....\n[press Ctrl-C to quit]\n");
+
+ if (g_pszPidFile)
+ {
+ RTFILE hPidFile = NIL_RTFILE;
+ vrc = RTFileOpen(&hPidFile, g_pszPidFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(vrc))
+ {
+ char szBuf[64];
+ size_t cchToWrite = RTStrPrintf(szBuf, sizeof(szBuf), "%ld\n", (long)getpid());
+ RTFileWrite(hPidFile, szBuf, cchToWrite, NULL);
+ RTFileClose(hPidFile);
+ }
+ }
+
+ // Increase the file table size to 10240 or as high as possible.
+ struct rlimit lim;
+ if (getrlimit(RLIMIT_NOFILE, &lim) == 0)
+ {
+ if ( lim.rlim_cur < 10240
+ && lim.rlim_cur < lim.rlim_max)
+ {
+ lim.rlim_cur = RT_MIN(lim.rlim_max, 10240);
+ if (setrlimit(RLIMIT_NOFILE, &lim) == -1)
+ RTPrintf("WARNING: failed to increase file descriptor limit. (%d)\n", errno);
+ }
+ }
+ else
+ RTPrintf("WARNING: failed to obtain per-process file-descriptor limit (%d).\n", errno);
+
+ /* get the main thread's event queue */
+ gEventQ = com::NativeEventQueue::getMainEventQueue();
+ if (!gEventQ)
+ {
+ RTMsgError("Failed to get the main event queue! (rc=%Rhrc)", rc);
+ break;
+ }
+
+ while (gKeepRunning)
+ {
+ vrc = gEventQ->processEventQueue(RT_INDEFINITE_WAIT);
+ if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
+ {
+ LogRel(("Failed to wait for events! (rc=%Rrc)", vrc));
+ break;
+ }
+ }
+
+ gEventQ = NULL;
+ RTPrintf("Terminated event loop.\n");
+
+ /* unregister ourselves. After this point, clients will start a new
+ * process because they won't be able to resolve the server name.*/
+ gIpcServ->RemoveName(VBOXSVC_IPC_NAME);
+ }
+ while (0); // this scopes the nsCOMPtrs
+
+ NS_IF_RELEASE(gIpcServ);
+
+ /* no nsCOMPtrs are allowed to be alive when you call com::Shutdown(). */
+
+ LogFlowFunc(("Calling com::Shutdown()...\n"));
+ rc = com::Shutdown();
+ LogFlowFunc(("Finished com::Shutdown() (rc=%Rhrc)\n", rc));
+
+ if (NS_FAILED(rc))
+ RTMsgError("Failed to shutdown XPCOM! (rc=%Rhrc)", rc);
+
+ RTPrintf("XPCOM server has shutdown.\n");
+
+ if (g_pszPidFile)
+ RTFileDelete(g_pszPidFile);
+
+ return RTEXITCODE_SUCCESS;
+}
diff --git a/src/VBox/Main/src-server/xpcom/server.h b/src/VBox/Main/src-server/xpcom/server.h
new file mode 100644
index 00000000..ffe5b9f3
--- /dev/null
+++ b/src/VBox/Main/src-server/xpcom/server.h
@@ -0,0 +1,50 @@
+/* $Id: server.h $ */
+/** @file
+ *
+ * Common header for XPCOM server and its module counterpart
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SRC_src_server_xpcom_server_h
+#define MAIN_INCLUDED_SRC_src_server_xpcom_server_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/com/com.h>
+
+#include <VBox/version.h>
+
+/**
+ * IPC name used to resolve the client ID of the server.
+ */
+#define VBOXSVC_IPC_NAME "VBoxSVC-" VBOX_VERSION_STRING
+
+
+/**
+ * Tag for the file descriptor passing for the daemonizing control.
+ */
+#define VBOXSVC_STARTUP_PIPE_NAME "vboxsvc:startup-pipe"
+
+#endif /* !MAIN_INCLUDED_SRC_src_server_xpcom_server_h */
diff --git a/src/VBox/Main/src-server/xpcom/server_module.cpp b/src/VBox/Main/src-server/xpcom/server_module.cpp
new file mode 100644
index 00000000..534eb70d
--- /dev/null
+++ b/src/VBox/Main/src-server/xpcom/server_module.cpp
@@ -0,0 +1,383 @@
+/* $Id: server_module.cpp $ */
+/** @file
+ * XPCOM server process helper module implementation functions
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_VBOXSVC
+#ifdef RT_OS_OS2
+# include <prproces.h>
+#endif
+
+#include <nsMemory.h>
+#include <nsString.h>
+#include <nsCOMPtr.h>
+#include <nsIFile.h>
+#include <nsIGenericFactory.h>
+#include <nsIServiceManagerUtils.h>
+#include <nsICategoryManager.h>
+#include <nsDirectoryServiceDefs.h>
+
+#include <ipcIService.h>
+#include <ipcIDConnectService.h>
+#include <ipcCID.h>
+#include <ipcdclient.h>
+
+#include "prio.h"
+#include "prproces.h"
+
+// official XPCOM headers don't define it yet
+#define IPC_DCONNECTSERVICE_CONTRACTID \
+ "@mozilla.org/ipc/dconnect-service;1"
+
+// generated file
+#include <VBox/com/VirtualBox.h>
+
+#include "server.h"
+#include "LoggingNew.h"
+
+#include <iprt/errcore.h>
+
+#include <iprt/assert.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/env.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+#if defined(RT_OS_SOLARIS)
+# include <sys/systeminfo.h>
+#endif
+
+/// @todo move this to RT headers (and use them in MachineImpl.cpp as well)
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+#define HOSTSUFF_EXE ".exe"
+#else /* !RT_OS_WINDOWS */
+#define HOSTSUFF_EXE ""
+#endif /* !RT_OS_WINDOWS */
+
+
+/** Name of the server executable. */
+const char VBoxSVC_exe[] = RTPATH_SLASH_STR "VBoxSVC" HOSTSUFF_EXE;
+
+enum
+{
+ /** Amount of time to wait for the server to establish a connection, ms */
+ VBoxSVC_Timeout = 30000,
+ /** How often to perform a connection check, ms */
+ VBoxSVC_WaitSlice = 100
+};
+
+/**
+ * Full path to the VBoxSVC executable.
+ */
+static char VBoxSVCPath[RTPATH_MAX];
+static bool IsVBoxSVCPathSet = false;
+
+/*
+ * The following macros define the method necessary to provide a list of
+ * interfaces implemented by the VirtualBox component. Note that this must be
+ * in sync with macros used for VirtualBox in server.cpp for the same purpose.
+ */
+
+NS_DECL_CLASSINFO(VirtualBoxWrap)
+NS_IMPL_CI_INTERFACE_GETTER1(VirtualBoxWrap, IVirtualBox)
+
+static nsresult vboxsvcSpawnDaemon(void)
+{
+ PRFileDesc *readable = nsnull, *writable = nsnull;
+ PRProcessAttr *attr = nsnull;
+ nsresult rv = NS_ERROR_FAILURE;
+ PRFileDesc *devNull;
+ // The ugly casts are necessary because the PR_CreateProcessDetached has
+ // a const array of writable strings as a parameter. It won't write. */
+ char * const args[] = { (char *)VBoxSVCPath, (char *)"--auto-shutdown", 0 };
+
+ // Use a pipe to determine when the daemon process is in the position
+ // to actually process requests. The daemon will write "READY" to the pipe.
+ if (PR_CreatePipe(&readable, &writable) != PR_SUCCESS)
+ goto end;
+ PR_SetFDInheritable(writable, PR_TRUE);
+
+ attr = PR_NewProcessAttr();
+ if (!attr)
+ goto end;
+
+ if (PR_ProcessAttrSetInheritableFD(attr, writable, VBOXSVC_STARTUP_PIPE_NAME) != PR_SUCCESS)
+ goto end;
+
+ devNull = PR_Open("/dev/null", PR_RDWR, 0);
+ if (!devNull)
+ goto end;
+
+ PR_ProcessAttrSetStdioRedirect(attr, PR_StandardInput, devNull);
+ PR_ProcessAttrSetStdioRedirect(attr, PR_StandardOutput, devNull);
+ PR_ProcessAttrSetStdioRedirect(attr, PR_StandardError, devNull);
+
+ if (PR_CreateProcessDetached(VBoxSVCPath, args, nsnull, attr) != PR_SUCCESS)
+ goto end;
+
+ // Close /dev/null
+ PR_Close(devNull);
+ // Close the child end of the pipe to make it the only owner of the
+ // file descriptor, so that unexpected closing can be detected.
+ PR_Close(writable);
+ writable = nsnull;
+
+ char msg[10];
+ RT_ZERO(msg);
+ if ( PR_Read(readable, msg, sizeof(msg)-1) != 5
+ || strcmp(msg, "READY"))
+ {
+ /* If several clients start VBoxSVC simultaneously only one can
+ * succeed. So treat this as success as well. */
+ rv = NS_OK;
+ goto end;
+ }
+
+ rv = NS_OK;
+
+end:
+ if (readable)
+ PR_Close(readable);
+ if (writable)
+ PR_Close(writable);
+ if (attr)
+ PR_DestroyProcessAttr(attr);
+ return rv;
+}
+
+
+/**
+ * VirtualBox component constructor.
+ *
+ * This constructor is responsible for starting the VirtualBox server
+ * process, connecting to it, and redirecting the constructor request to the
+ * VirtualBox component defined on the server.
+ */
+static NS_IMETHODIMP
+VirtualBoxConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ LogFlowFuncEnter();
+
+ nsresult rc = NS_OK;
+
+ do
+ {
+ *aResult = NULL;
+ if (NULL != aOuter)
+ {
+ rc = NS_ERROR_NO_AGGREGATION;
+ break;
+ }
+
+ if (!IsVBoxSVCPathSet)
+ {
+ /* Get the directory containing XPCOM components -- the VBoxSVC
+ * executable is expected in the parent directory. */
+ nsCOMPtr<nsIProperties> dirServ = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rc);
+ if (NS_SUCCEEDED(rc))
+ {
+ nsCOMPtr<nsIFile> componentDir;
+ rc = dirServ->Get(NS_XPCOM_COMPONENT_DIR,
+ NS_GET_IID(nsIFile), getter_AddRefs(componentDir));
+
+ if (NS_SUCCEEDED(rc))
+ {
+ nsCAutoString path;
+ componentDir->GetNativePath(path);
+
+ LogFlowFunc(("component directory = \"%s\"\n", path.get()));
+ AssertBreakStmt(path.Length() + strlen(VBoxSVC_exe) < RTPATH_MAX,
+ rc = NS_ERROR_FAILURE);
+
+#if defined(RT_OS_SOLARIS) && defined(VBOX_WITH_HARDENING)
+ char achKernArch[128];
+ int cbKernArch = sysinfo(SI_ARCHITECTURE_K, achKernArch, sizeof(achKernArch));
+ if (cbKernArch > 0)
+ {
+ sprintf(VBoxSVCPath, "/opt/VirtualBox/%s%s", achKernArch, VBoxSVC_exe);
+ IsVBoxSVCPathSet = true;
+ }
+ else
+ rc = NS_ERROR_UNEXPECTED;
+#else
+ strcpy(VBoxSVCPath, path.get());
+ RTPathStripFilename(VBoxSVCPath);
+ strcat(VBoxSVCPath, VBoxSVC_exe);
+
+ IsVBoxSVCPathSet = true;
+#endif
+ }
+ }
+ if (NS_FAILED(rc))
+ break;
+ }
+
+ nsCOMPtr<ipcIService> ipcServ = do_GetService(IPC_SERVICE_CONTRACTID, &rc);
+ if (NS_FAILED(rc))
+ break;
+
+ /* connect to the VBoxSVC server process */
+
+ bool startedOnce = false;
+ unsigned timeLeft = VBoxSVC_Timeout;
+
+ do
+ {
+ LogFlowFunc(("Resolving server name \"%s\"...\n", VBOXSVC_IPC_NAME));
+
+ PRUint32 serverID = 0;
+ rc = ipcServ->ResolveClientName(VBOXSVC_IPC_NAME, &serverID);
+ if (NS_FAILED(rc))
+ {
+ LogFlowFunc(("Starting server \"%s\"...\n", VBoxSVCPath));
+
+ startedOnce = true;
+
+ rc = vboxsvcSpawnDaemon();
+ if (NS_FAILED(rc))
+ break;
+
+ /* wait for the server process to establish a connection */
+ do
+ {
+ RTThreadSleep(VBoxSVC_WaitSlice);
+ rc = ipcServ->ResolveClientName(VBOXSVC_IPC_NAME, &serverID);
+ if (NS_SUCCEEDED(rc))
+ break;
+ if (timeLeft <= VBoxSVC_WaitSlice)
+ {
+ timeLeft = 0;
+ break;
+ }
+ timeLeft -= VBoxSVC_WaitSlice;
+ }
+ while (1);
+
+ if (!timeLeft)
+ {
+ rc = IPC_ERROR_WOULD_BLOCK;
+ break;
+ }
+ }
+
+ LogFlowFunc(("Connecting to server (ID=%d)...\n", serverID));
+
+ nsCOMPtr<ipcIDConnectService> dconServ =
+ do_GetService(IPC_DCONNECTSERVICE_CONTRACTID, &rc);
+ if (NS_FAILED(rc))
+ break;
+
+ rc = dconServ->CreateInstance(serverID,
+ CLSID_VirtualBox,
+ aIID, aResult);
+ if (NS_SUCCEEDED(rc))
+ break;
+
+ LogFlowFunc(("Failed to connect (rc=%Rhrc (%#08x))\n", rc, rc));
+
+ /* It's possible that the server gets shut down after we
+ * successfully resolve the server name but before it
+ * receives our CreateInstance() request. So, check for the
+ * name again, and restart the cycle if it fails. */
+ if (!startedOnce)
+ {
+ nsresult rc2 =
+ ipcServ->ResolveClientName(VBOXSVC_IPC_NAME, &serverID);
+ if (NS_SUCCEEDED(rc2))
+ break;
+
+ LogFlowFunc(("Server seems to have terminated before receiving our request. Will try again.\n"));
+ }
+ else
+ break;
+ }
+ while (1);
+ }
+ while (0);
+
+ LogFlowFunc(("rc=%Rhrc (%#08x)\n", rc, rc));
+ LogFlowFuncLeave();
+
+ return rc;
+}
+
+#if 0
+/// @todo not really necessary for the moment
+/**
+ *
+ * @param aCompMgr
+ * @param aPath
+ * @param aLoaderStr
+ * @param aType
+ * @param aInfo
+ *
+ * @return
+ */
+static NS_IMETHODIMP
+VirtualBoxRegistration(nsIComponentManager *aCompMgr,
+ nsIFile *aPath,
+ const char *aLoaderStr,
+ const char *aType,
+ const nsModuleComponentInfo *aInfo)
+{
+ nsCAutoString modulePath;
+ aPath->GetNativePath(modulePath);
+ nsCAutoString moduleTarget;
+ aPath->GetNativeTarget(moduleTarget);
+
+ LogFlowFunc(("aPath=%s, aTarget=%s, aLoaderStr=%s, aType=%s\n",
+ modulePath.get(), moduleTarget.get(), aLoaderStr, aType));
+
+ nsresult rc = NS_OK;
+
+ return rc;
+}
+#endif
+
+/**
+ * Component definition table.
+ * Lists all components defined in this module.
+ */
+static const nsModuleComponentInfo components[] =
+{
+ {
+ "VirtualBox component", // description
+ NS_VIRTUALBOX_CID, NS_VIRTUALBOX_CONTRACTID, // CID/ContractID
+ VirtualBoxConstructor, // constructor function
+ NULL, /* VirtualBoxRegistration, */ // registration function
+ NULL, // deregistration function
+ NULL, // destructor function
+ /// @todo
+ NS_CI_INTERFACE_GETTER_NAME(VirtualBoxWrap), // interfaces function
+ NULL, // language helper
+ /// @todo
+ &NS_CLASSINFO_NAME(VirtualBoxWrap) // global class info & flags
+ }
+};
+
+NS_IMPL_NSGETMODULE(VirtualBox_Server_Module, components)
diff --git a/src/VBox/Main/testcase/Makefile.kmk b/src/VBox/Main/testcase/Makefile.kmk
new file mode 100644
index 00000000..d2fb9a72
--- /dev/null
+++ b/src/VBox/Main/testcase/Makefile.kmk
@@ -0,0 +1,343 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VBox API testcases.
+#
+
+#
+# Copyright (C) 2004-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+
+#
+# Target and globals (small mess)
+#
+ifndef VBOX_ONLY_SDK
+ if defined(VBOX_WITH_TESTCASES)
+ PROGRAMS += \
+ tstAPI \
+ tstVBoxAPI \
+ tstVBoxAPIPerf \
+ tstVBoxMultipleVM \
+ $(if $(VBOX_OSE),,tstOVF) \
+ $(if $(VBOX_WITH_XPCOM),tstVBoxAPIXPCOM,tstVBoxAPIWin msiDarwinDescriptorDecoder) \
+ $(if $(VBOX_WITH_RESOURCE_USAGE_API),tstCollector,) \
+ $(if $(VBOX_WITH_GUEST_CONTROL),tstGuestCtrlContextID,) \
+ $(if $(VBOX_WITH_GUEST_CONTROL),tstGuestCtrlParseBuffer,) \
+ $(if $(VBOX_WITH_GUEST_CONTROL),tstGuestCtrlPaths,) \
+ tstMediumLock \
+ tstBstr \
+ tstGuid \
+ tstUnattendedScript \
+ tstVBoxCrypto
+ PROGRAMS.linux += \
+ $(if $(VBOX_WITH_USB),tstUSBProxyLinux,)
+ endif # !VBOX_WITH_TESTCASES
+endif # !VBOX_ONLY_SDK
+if defined(VBOX_ONLY_SDK) || !defined(VBOX_WITH_XPCOM)
+ INSTALLS += samplesMSCOM
+endif
+if defined(VBOX_ONLY_SDK) || defined(VBOX_WITH_XPCOM)
+ INSTALLS += samplesXPCOM
+endif
+
+
+#
+# The samples
+#
+samplesMSCOM_MODE = a+r,u+w
+samplesMSCOM_INST = $(INST_SDK)bindings/mscom/samples/
+samplesMSCOM_SOURCES = tstVBoxAPIWin.cpp makefile.tstVBoxAPIWin=>Makefile
+
+samplesXPCOM_MODE = a+r,u+w
+samplesXPCOM_INST = $(INST_SDK)bindings/xpcom/samples/
+samplesXPCOM_SOURCES = tstVBoxAPIXPCOM.cpp makefile.tstVBoxAPIXPCOM=>Makefile
+
+#
+# tstVBoxMultipleVM
+#
+tstVBoxMultipleVM_TEMPLATE = VBOXMAINCLIENTTSTEXE
+tstVBoxMultipleVM_SOURCES = tstVBoxMultipleVM.cpp
+
+#
+# tstAPI
+#
+tstAPI_TEMPLATE = VBOXMAINCLIENTTSTEXE
+#tstAPI_INST = $(INST_SDK)bindings/gluecom/samples/
+tstAPI_SOURCES = tstAPI.cpp
+
+#
+# tstVBoxAPI
+#
+tstVBoxAPI_TEMPLATE = VBOXMAINCLIENTTSTEXE
+tstVBoxAPI_SOURCES = \
+ tstVBoxAPI.cpp
+
+#
+# tstVBoxAPIPerf
+#
+tstVBoxAPIPerf_TEMPLATE = VBOXMAINCLIENTTSTEXE
+tstVBoxAPIPerf_SOURCES = \
+ tstVBoxAPIPerf.cpp
+
+#
+# tstOVF
+#
+tstOVF_TEMPLATE = VBOXMAINCLIENTTSTEXE
+#tstOVF_INST = $(INST_SDK)bindings/gluecom/samples/
+tstOVF_SOURCES = tstOVF.cpp
+
+ifndef VBOX_OSE
+#
+# OVF test data.
+#
+INSTALLS += ovf-testcases
+ovf-testcases_MODE = a+r,u+w
+ovf-testcases_INST = $(INST_TESTCASE)ovf-testcases/
+ovf-testcases_SOURCES = \
+ ovf-dummy.vmdk \
+ ovf-joomla-0.9/joomla-1.1.4-ovf.ovf=>ovf-joomla-0.9/joomla-1.1.4-ovf.ovf \
+ ovf-winhost-audio-nodisks/WinXP.ovf=>ovf-winhost-audio-nodisks/WinXP.ovf \
+ ovf-winxp-vbox-sharedfolders/winxp.ovf=>ovf-winxp-vbox-sharedfolders/winxp.ovf
+endif
+
+
+#
+# tstVBoxAPIXPCOM
+#
+# We only build the testcase here to make sure it builds.
+# It comes with a custom makefile which should be tested as well!
+#
+# Use very generic template to make the build environment similar
+# to the standalone case, to detect if IPRT or glue use sneaks in.
+#
+tstVBoxAPIXPCOM_TEMPLATE = VBOXR3EXE
+tstVBoxAPIXPCOM_INST = $(INST_TESTCASE)
+tstVBoxAPIXPCOM_SOURCES = tstVBoxAPIXPCOM.cpp
+tstVBoxAPIXPCOM_INCS = \
+ $(VBOX_PATH_SDK)/bindings/xpcom/include \
+ $(VBOX_PATH_SDK)/bindings/xpcom/include/nsprpub \
+ $(VBOX_PATH_SDK)/bindings/xpcom/include/string \
+ $(VBOX_PATH_SDK)/bindings/xpcom/include/xpcom \
+ $(VBOX_PATH_SDK)/bindings/xpcom/include/ipcd
+tstVBoxAPIXPCOM_LIBS = \
+ $(LIB_XPCOM) \
+ $(LIB_RUNTIME)
+tstVBoxAPIXPCOM_CXXFLAGS = -fshort-wchar
+ifdef VBOX_WITH_RUNPATH
+ tstVBoxAPIXPCOM_LDFLAGS = '$(VBOX_GCC_RPATH_OPT)$(VBOX_WITH_RUNPATH)' $(TEMPLATE_VBoxBldProg_LDFLAGS)
+else ifdef VBOX_WITH_RELATIVE_RUNPATH
+ tstVBoxAPIXPCOM_LDFLAGS = '$(VBOX_GCC_RPATH_OPT)$(VBOX_WITH_RELATIVE_RUNPATH)/..' $(TEMPLATE_VBoxBldProg_LDFLAGS)
+endif
+tstVBoxAPIXPCOM_INTERMEDIATES = \
+ $(VBOX_PATH_SDK)/bindings/xpcom/include/VirtualBox_XPCOM.h
+ifdef VBOX_WITH_XPCOM_NAMESPACE_CLEANUP
+ tstVBoxAPIXPCOM_DEFS += VBOX_WITH_XPCOM_NAMESPACE_CLEANUP
+endif
+
+
+#
+# tstVBoxAPIWin
+#
+# Use very generic template to make the build environment similar
+# to the standalone case, to detect if IPRT or glue use sneaks in.
+#
+tstVBoxAPIWin_TEMPLATE = VBOXR3EXE
+tstVBoxAPIWin_INST = $(INST_TESTCASE)
+tstVBoxAPIWin_SOURCES = \
+ tstVBoxAPIWin.cpp \
+ $(VBOX_PATH_SDK)/bindings/mscom/lib/VirtualBox_i.c
+tstVBoxAPIWin_INCS = \
+ $(VBOX_PATH_SDK)/bindings/mscom/include
+tstVBoxAPIWin_INTERMEDIATES = \
+ $(VBOX_PATH_SDK)/bindings/mscom/include/VirtualBox.h
+
+
+#
+# msiDarwinDescriptorDecoder
+#
+# Use very generic template to make the build environment similar
+# to the standalone case, to detect if IPRT or glue use sneaks in.
+#
+msiDarwinDescriptorDecoder_TEMPLATE = VBOXR3EXE
+msiDarwinDescriptorDecoder_INST = $(VBOX_INST_TOOLS)
+msiDarwinDescriptorDecoder_SOURCES = \
+ msiDarwinDescriptorDecoder.cpp
+
+
+#
+# tstCollector
+#
+# Note! VBOX_MAIN_APIWRAPPER_GEN_HDRS is only defined if kmk is executed a
+# parent directory. Since the rules for generating the files listed by
+# the variable lives in the parent makefile, this is not a problem.
+#
+tstCollector_TEMPLATE = VBOXMAINCLIENTTSTEXE
+tstCollector_SOURCES = \
+ tstCollector.cpp \
+ ../src-server/Performance.cpp
+tstCollector_INCS = \
+ ../include \
+ $(VBOX_MAIN_APIWRAPPER_INCS)
+tstCollector_INTERMEDIATES = $(VBOX_MAIN_APIWRAPPER_GEN_HDRS)
+tstCollector_DEFS = VBOX_COLLECTOR_TEST_CASE
+tstCollector_LDFLAGS.darwin = -lproc
+tstCollector_LDFLAGS.solaris = -lkstat -lnvpair
+tstCollector_LDFLAGS.win = psapi.lib powrprof.lib
+
+
+#
+# tstGuestCtrlContextID
+#
+tstGuestCtrlContextID_TEMPLATE = VBOXMAINCLIENTTSTEXE
+tstGuestCtrlContextID_INTERMEDIATES = $(VBOX_MAIN_APIWRAPPER_GEN_HDRS)
+tstGuestCtrlContextID_DEFS += VBOX_WITH_HGCM VBOX_WITH_GUEST_CONTROL VBOX_GUESTCTRL_TEST_CASE
+tstGuestCtrlContextID_SOURCES = \
+ tstGuestCtrlContextID.cpp \
+ ../src-client/GuestCtrlPrivate.cpp
+tstGuestCtrlContextID_INCS = ../include \
+ $(VBOX_MAIN_APIWRAPPER_INCS)
+
+
+#
+# tstGuestCtrlParseBuffer
+#
+tstGuestCtrlParseBuffer_TEMPLATE = VBOXMAINCLIENTTSTEXE
+tstGuestCtrlParseBuffer_INTERMEDIATES = $(VBOX_MAIN_APIWRAPPER_GEN_HDRS)
+tstGuestCtrlParseBuffer_DEFS += VBOX_WITH_HGCM VBOX_WITH_GUEST_CONTROL VBOX_GUESTCTRL_TEST_CASE
+tstGuestCtrlParseBuffer_SOURCES = \
+ tstGuestCtrlParseBuffer.cpp \
+ ../src-client/GuestCtrlPrivate.cpp
+tstGuestCtrlParseBuffer_INCS = ../include \
+ $(VBOX_MAIN_APIWRAPPER_INCS)
+
+
+#
+# tstGuestCtrlPaths
+#
+tstGuestCtrlPaths_TEMPLATE = VBOXMAINCLIENTTSTEXE
+tstGuestCtrlPaths_INTERMEDIATES = $(VBOX_MAIN_APIWRAPPER_GEN_HDRS)
+tstGuestCtrlPaths_DEFS += VBOX_WITH_HGCM VBOX_WITH_GUEST_CONTROL VBOX_GUESTCTRL_TEST_CASE
+tstGuestCtrlPaths_SOURCES = \
+ tstGuestCtrlPaths.cpp \
+ ../src-client/GuestCtrlPrivate.cpp
+tstGuestCtrlPaths_INCS = ../include \
+ $(VBOX_MAIN_APIWRAPPER_INCS)
+
+if 0 # Enable this if you want automatic runs after compilation.
+ $$(tstGuestCtrlPaths_0_OUTDIR)/tstGuestCtrlPaths.run: $$(tstGuestCtrlPaths_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstGuestCtrlPaths_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+ OTHERS += $(tstGuestCtrlPaths_0_OUTDIR)/tstGuestCtrlPaths.run
+endif
+
+
+#
+# tstUSBProxyLinux
+#
+tstUSBProxyLinux_TEMPLATE = VBOXMAINCLIENTTSTEXE
+tstUSBProxyLinux_INTERMEDIATES = $(VBOX_MAIN_APIWRAPPER_GEN_HDRS)
+tstUSBProxyLinux_SOURCES = \
+ tstUSBProxyLinux.cpp \
+ ../src-server/linux/USBGetDevices.cpp
+tstUSBProxyLinux_INCS = \
+ . \
+ ../include \
+ $(VBOX_PATH_SDK)/bindings/xpcom/include \
+ $(VBOX_PATH_SDK)/bindings/xpcom/include/nsprpub \
+ $(VBOX_PATH_SDK)/bindings/xpcom/include/xpcom \
+ $(VBOX_MAIN_APIWRAPPER_INCS)
+tstUSBProxyLinux_DEFS = \
+ UNIT_TEST \
+ VBOX_WITH_USB \
+ VBOX_USB_WITH_SYSFS \
+ VBOX_WITH_XPCOM
+tstUSBProxyLinux_DEPS = \
+ $(VBOX_PATH_SDK)/bindings/xpcom/include/VirtualBox_XPCOM.h
+tstUSBProxyLinux_LIBS += \
+ $(PATH_OUT)/lib/USBLib.a \
+ $(PATH_OUT)/lib/VBoxCOM.a
+
+
+#
+# tstMediumLock
+#
+tstMediumLock_TEMPLATE = VBOXMAINCLIENTTSTEXE
+tstMediumLock_SOURCES = tstMediumLock.cpp
+
+
+#
+# tstBstr
+#
+tstBstr_TEMPLATE = VBOXMAINCLIENTTSTEXE
+tstBstr_SOURCES = tstBstr.cpp
+
+
+#
+# tstGuid
+#
+tstGuid_TEMPLATE = VBOXMAINCLIENTTSTEXE
+tstGuid_SOURCES = tstGuid.cpp
+
+
+#
+# tstUnattendedScript
+#
+tstUnattendedScript_TEMPLATE = VBOXMAINCLIENTTSTEXE
+tstUnattendedScript_DEFS = VBOX_WITH_UNATTENDED IN_VBOXSVC IN_TST_UNATTENDED_SCRIPT
+tstUnattendedScript_INTERMEDIATES = \
+ $(VBOX_MAIN_APIWRAPPER_GEN_HDRS) \
+ $(VBOX_XML_SCHEMADEFS_H)
+tstUnattendedScript_INCS = \
+ ../include \
+ $(VBOX_MAIN_APIWRAPPER_INCS) \
+ $(dir $(VBOX_XML_SCHEMADEFS_H))
+tstUnattendedScript_SOURCES = \
+ tstUnattendedScript.cpp \
+ ../src-server/UnattendedScript.cpp \
+ ../src-all/TextScript.cpp \
+ ../src-all/VirtualBoxBase.cpp \
+ ../src-all/VirtualBoxErrorInfoImpl.cpp \
+ ../src-all/AutoCaller.cpp \
+ ../src-all/GlobalStatusConversion.cpp
+tstUnattendedScript_LIBS = \
+ $(PATH_STAGE_LIB)/VBoxAPIWrap$(VBOX_SUFF_LIB)
+
+INSTALLS += tstUnattendedScriptFiles
+tstUnattendedScriptFiles_TEMPLATE = VBOXMAINTSTEXE
+tstUnattendedScriptFiles_SOURCES = \
+ tstUnattendedScript-1.template \
+ tstUnattendedScript-1.expected
+
+
+#
+# tstVBoxCrypto
+#
+tstVBoxCrypto_TEMPLATE = VBOXMAINCLIENTTSTEXE
+tstVBoxCrypto_SOURCES = tstVBoxCrypto.cpp
+
+
+
+# generate rules.
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Main/testcase/VBoxVBTest/TestForm.frm b/src/VBox/Main/testcase/VBoxVBTest/TestForm.frm
new file mode 100644
index 00000000..3ba63c8f
--- /dev/null
+++ b/src/VBox/Main/testcase/VBoxVBTest/TestForm.frm
@@ -0,0 +1,137 @@
+VERSION 5.00
+Begin VB.Form TestForm
+ Caption = "VirtualBox Test"
+ ClientHeight = 4692
+ ClientLeft = 60
+ ClientTop = 348
+ ClientWidth = 6972
+ LinkTopic = "TestForm"
+ ScaleHeight = 4692
+ ScaleWidth = 6972
+ StartUpPosition = 3 'Windows Default
+ Begin VB.ListBox machineList
+ Height = 2352
+ ItemData = "TestForm.frx":0000
+ Left = 240
+ List = "TestForm.frx":0007
+ TabIndex = 4
+ Top = 2040
+ Width = 6492
+ End
+ Begin VB.CommandButton getMachieListCmd
+ Caption = "Get Machine List"
+ Height = 372
+ Left = 2640
+ TabIndex = 0
+ Top = 720
+ Width = 1692
+ End
+ Begin VB.Label Label3
+ AutoSize = -1 'True
+ Caption = "Registered Machines:"
+ Height = 192
+ Left = 240
+ TabIndex = 5
+ Top = 1680
+ Width = 1572
+ End
+ Begin VB.Label versionLabel
+ AutoSize = -1 'True
+ Caption = "<none>"
+ Height = 192
+ Left = 1680
+ TabIndex = 3
+ Top = 1320
+ Width = 528
+ End
+ Begin VB.Label Label2
+ AutoSize = -1 'True
+ Caption = "VirtualBox Version:"
+ Height = 252
+ Left = 240
+ TabIndex = 2
+ Top = 1320
+ Width = 1344
+ End
+ Begin VB.Label Label1
+ Alignment = 2 'Center
+ Caption = $"TestForm.frx":0013
+ Height = 372
+ Left = 240
+ TabIndex = 1
+ Top = 120
+ Width = 6492
+ WordWrap = -1 'True
+ End
+End
+Attribute VB_Name = "TestForm"
+Attribute VB_GlobalNameSpace = False
+Attribute VB_Creatable = False
+Attribute VB_PredeclaredId = True
+Attribute VB_Exposed = False
+
+Private Declare Function SetEnvironmentVariable Lib "kernel32" _
+ Alias "SetEnvironmentVariableA" (ByVal lpName As String, ByVal lpValue As String) As Long
+Private Declare Function GetEnvironmentVariable Lib "kernel32" _
+ Alias "GetEnvironmentVariableA" (ByVal lpName As String, ByVal lpValue As String, ByVal nSize As Long) As Long
+
+Private Sub Form_Load()
+
+ ' Set where to take VirtualBox configuration from
+
+ 'SetEnvironmentVariable "VBOX_USER_HOME", "E:\VirtualBoxHome\win"
+
+ ' Setup debug logging (available only in debug builds)
+
+ 'PATH_OUT_BASE = "D:/Coding/innotek/vbox/out"
+
+ 'SetEnvironmentVariable "VBOX_LOG", "main.e.l.f + gui.e.l.f"
+ 'SetEnvironmentVariable "VBOX_LOG_FLAGS", "time tid thread"
+ 'SetEnvironmentVariable "VBOX_LOG_DEST", "dir:" + PATH_OUT_BASE + "/logs"
+
+End Sub
+
+Private Sub getMachieListCmd_Click()
+
+ ' Clear the old list contents
+
+ machineList.Clear
+ machineList.Refresh
+
+ versionLabel.Caption = "<none>"
+
+ ' Disable the button and the list for the duration of the call
+
+ getMachieListCmd.Enabled = False
+ machineList.Enabled = False
+
+ ' Obtain the global VirtualBox object (this will start
+ ' the VirtualBox server if it is not already started)
+
+ Dim vbox As VirtualBox.VirtualBox
+ Set vbox = New VirtualBox.VirtualBox
+
+ ' Get the VirtualBox server version
+
+ versionLabel.Caption = vbox.Version
+
+ ' Obtain a list of registered machines
+
+ Dim machines() As VirtualBox.IMachine
+ machines = vbox.Machines2
+
+ If UBound(machines) < 0 Then
+ machineList.AddItem ("<none>")
+ Else
+ For i = 0 To UBound(machines)
+ Item = machines(i).Name + " (" + machines(i).OSTypeId() + ")"
+ machineList.AddItem (Item)
+ Next i
+ End If
+
+ ' Reenable the button and the list
+
+ getMachieListCmd.Enabled = True
+ machineList.Enabled = True
+
+End Sub
diff --git a/src/VBox/Main/testcase/VBoxVBTest/TestForm.frx b/src/VBox/Main/testcase/VBoxVBTest/TestForm.frx
new file mode 100644
index 00000000..bd27fb45
--- /dev/null
+++ b/src/VBox/Main/testcase/VBoxVBTest/TestForm.frx
Binary files differ
diff --git a/src/VBox/Main/testcase/VBoxVBTest/VBoxVBTest.vbp b/src/VBox/Main/testcase/VBoxVBTest/VBoxVBTest.vbp
new file mode 100644
index 00000000..cdc19c8a
--- /dev/null
+++ b/src/VBox/Main/testcase/VBoxVBTest/VBoxVBTest.vbp
@@ -0,0 +1,34 @@
+Type=Exe
+Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\WINDOWS\system32\stdole2.tlb#OLE Automation
+Reference=*\G{D7569351-1750-46F0-936E-BD127D5BC264}#1.3#0#VBoxC.dll#InnoTek VirtualBox Machine Type Library
+Form=TestForm.frm
+Startup="TestForm"
+ExeName32="VBoxVBTest.exe"
+Command32=""
+Name="VBoxVBTest"
+HelpContextID="0"
+CompatibleMode="0"
+MajorVer=1
+MinorVer=0
+RevisionVer=0
+AutoIncrementVer=0
+ServerSupportFiles=0
+VersionCompanyName="Something"
+CompilationType=0
+OptimizationType=0
+FavorPentiumPro(tm)=0
+CodeViewDebugInfo=0
+NoAliasing=0
+BoundsCheck=0
+OverflowCheck=0
+FlPointCheck=0
+FDIVCheck=0
+UnroundedFP=0
+StartMode=0
+Unattended=0
+Retained=0
+ThreadPerObject=0
+MaxNumberOfThreads=1
+
+[MS Transaction Server]
+AutoRefresh=1
diff --git a/src/VBox/Main/testcase/makefile.tstVBoxAPIWin b/src/VBox/Main/testcase/makefile.tstVBoxAPIWin
new file mode 100644
index 00000000..af8299d3
--- /dev/null
+++ b/src/VBox/Main/testcase/makefile.tstVBoxAPIWin
@@ -0,0 +1,100 @@
+# $Id: makefile.tstVBoxAPIWin $
+## @file
+# tstVBoxAPILinux makefile
+#
+
+#
+# Copyright (C) 2006-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+#
+# Several assumptions and propositions:
+# - Visual Studio has already installed on machine or you already have nmake.exe, cl.exe, link.exe
+# - Windows SDK has already installed on machine or you already have Uuid.Lib Ole32.Lib OleAut32.Lib OleDlg.Lib
+# - VirtualBox SDK was downloaded and was placed into folder where VirtualBox had been installed.
+# - nmake is a default tool that builds projects based on commands contained in this description file
+# - cl is cl.exe - Windows compiler
+# - link is link.exe - Windows linker
+# - all needed paths have been set in working environment. It means that when you type "cl" from the console,
+# Windows shall find cl.exe by using enviroment variable PATH or something similar.
+#
+# The best way to accomplish it is to run a script vcvars32.bat located in the Visual studio "bin" directory.
+# This script installs needed paths in your working environment.
+# Important!!!
+# Script vcvars32.bat sets up needed paths only for local console session.
+# For permanent using, needed paths must be added globally.
+#
+# Several possible examples of paths:
+# VS_INSTALL_PATH = "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\"
+# VS_INCLUDE_PATH = "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include"
+# WIN_SDK_INCLUDE_PATH = "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include"
+# WIN_SDK_LIB_PATH = "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib\x64\"
+# VB_INSTALL_PATH = "C:\Program Files\Oracle\VirtualBox"
+#
+
+
+CXX = cl
+LINK = link
+PATH_MSCOM = ../../../bindings/mscom
+INCS_MSCOM = $(PATH_MSCOM)/include
+LIBS_MSCOM = $(PATH_MSCOM)/lib
+
+LIBS_DEPS = "Uuid.Lib" "Ole32.Lib" "OleAut32.Lib" "OleDlg.Lib"
+
+tstVBoxAPIWin_SOURCES = $(LIBS_MSCOM)/VirtualBox_i.c
+tstVBoxAPIWin_DEPS = $(INCS_MSCOM)
+
+COMPILER_CMDLINE = /Zi /nologo /W3 /WX- /Od /Oy- /Gm /EHsc /RTC1 /GS /fp:precise /Gd /analyze- /errorReport:queue
+
+LINKER_CMDLINE = /INCREMENTAL /DEBUG /SUBSYSTEM:CONSOLE
+
+# default linking
+tstVBoxAPIWin.exe: tstVBoxAPIWin.obj VirtualBox_i.obj
+ $(LINK) /out:tstVBoxAPIWin.exe $** $(LIBS_DEPS)
+
+# default compilation
+tstVBoxAPIWin.obj:
+ $(CXX) /c /I$(INCS_MSCOM) tstVBoxAPIWin.cpp
+
+# default compilation
+VirtualBox_i.obj:
+ $(CXX) /c /I$(INCS_MSCOM) $(tstVBoxAPIWin_SOURCES)
+
+# linking with defined linker's options
+#tstVBoxAPIWin.exe: tstVBoxAPIWin.obj VirtualBox_i.obj
+# $(LINK) $(LINKER_CMDLINE) /out:tstVBoxAPIWin.exe $** $(LIBS_DEPS)
+
+# compile with pre-defined compiler's options and locally defined paths
+#tstVBoxAPIWin.obj:
+# $(CXX) /c $(COMPILER_CMDLINE) /I$(INCS_MSCOM) /I$(WIN_SDK_INCLUDE_PATH) /I$(VS_INCLUDE_PATH) tstVBoxAPIWin.cpp
+
+# compile with locally defined paths
+#tstVBoxAPIWin.obj:
+# $(CXX) /c /I$(INCS_MSCOM) /I$(WIN_SDK_INCLUDE_PATH) /I$(VS_INCLUDE_PATH) tstVBoxAPIWin.cpp
+
+# compile with pre-defined compiler's options and locally defined paths
+#VirtualBox_i.obj:
+# $(CXX) /c $(COMPILER_CMDLINE) /I$(INCS_MSCOM) /I$(WIN_SDK_INCLUDE_PATH) /I$(VS_INCLUDE_PATH) $(tstVBoxAPIWin_SOURCES)
+
+# compile with locally defined paths
+#VirtualBox_i.obj:
+# $(CXX) /c /I$(INCS_MSCOM) /I$(WIN_SDK_INCLUDE_PATH) /I$(VS_INCLUDE_PATH) $(tstVBoxAPIWin_SOURCES)
+
diff --git a/src/VBox/Main/testcase/makefile.tstVBoxAPIXPCOM b/src/VBox/Main/testcase/makefile.tstVBoxAPIXPCOM
new file mode 100644
index 00000000..c91f5149
--- /dev/null
+++ b/src/VBox/Main/testcase/makefile.tstVBoxAPIXPCOM
@@ -0,0 +1,65 @@
+# $Id: makefile.tstVBoxAPIXPCOM $
+## @file
+# tstVBoxAPIXPCOM makefile
+#
+
+#
+# Copyright (C) 2006-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+PATH_XPCOM = ..
+PATH_BIN = ../../../../
+
+# This setting must be the same as used when building VBoxXPCOM.so.
+# If you get a lot of unresolved symbols, try commenting it out.
+VBOX_WITH_XPCOM_NAMESPACE_CLEANUP=1
+
+PATH_XPCOM_IDL = $(PATH_XPCOM)/idl
+INCS_XPCOM = $(PATH_XPCOM)/include \
+ $(PATH_XPCOM)/include/nsprpub \
+ $(PATH_XPCOM)/include/string \
+ $(PATH_XPCOM)/include/xpcom \
+ $(PATH_XPCOM)/include/ipcd
+
+ifdef VBOX_WITH_XPCOM_NAMESPACE_CLEANUP
+ DEFS_XPCOM += VBOX_WITH_XPCOM_NAMESPACE_CLEANUP
+endif
+
+# Adjust this to match your platform, pick from RT_OS_LINUX, RT_OS_WINDOWS,
+# RT_OS_DARWIN, RT_OS_SOLARIS...
+DEFS_XPCOM += RT_OS_LINUX
+
+
+#
+# Link with the public XPCOM libraries
+#
+tstVBoxAPIXPCOM: tstVBoxAPIXPCOM.o
+ g++ -g -o $@ $^ \
+ $(PATH_BIN)/VBoxXPCOM.so \
+ -Wl,-rpath $(PATH_BIN)/ \
+ -ldl -lpthread
+
+tstVBoxAPIXPCOM.o: tstVBoxAPIXPCOM.cpp
+ g++ -c -g -fshort-wchar $(addprefix -I, $(INCS_XPCOM)) $(addprefix -D, $(DEFS_XPCOM)) -o $@ tstVBoxAPIXPCOM.cpp
+
+clean:
+ rm -f tstVBoxAPIXPCOM tstVBoxAPIXPCOM.o
+
diff --git a/src/VBox/Main/testcase/msiDarwinDescriptorDecoder.cpp b/src/VBox/Main/testcase/msiDarwinDescriptorDecoder.cpp
new file mode 100644
index 00000000..f81aaab6
--- /dev/null
+++ b/src/VBox/Main/testcase/msiDarwinDescriptorDecoder.cpp
@@ -0,0 +1,81 @@
+/* $Id: msiDarwinDescriptorDecoder.cpp $ */
+/** @file
+ * msiDarwinDescriptorDecoder
+ */
+
+/*
+ * Copyright (C) 2016-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#include <stdio.h>
+#include <iprt/win/windows.h> /* Avoid -Wall warnings. */
+
+
+
+typedef DWORD (WINAPI *PFNMSIDECOMPOSEDESCRIPTORW)(PCWSTR pwszDescriptor,
+ LPWSTR pwszProductCode /*[40]*/,
+ LPWSTR pwszFeatureId /*[40]*/,
+ LPWSTR pwszComponentCode /*[40]*/,
+ DWORD *poffArguments);
+
+int wmain(int cArgs, wchar_t **papwszArgs)
+{
+ HMODULE hmodMsi = LoadLibrary("msi.dll");
+ PFNMSIDECOMPOSEDESCRIPTORW pfnMsiDecomposeDescriptorW;
+ pfnMsiDecomposeDescriptorW = (PFNMSIDECOMPOSEDESCRIPTORW)GetProcAddress(hmodMsi, "MsiDecomposeDescriptorW");
+ if (!pfnMsiDecomposeDescriptorW)
+ {
+ fprintf(stderr, "Failed to load msi.dll or resolve 'MsiDecomposeDescriptorW'\n");
+ return 1;
+ }
+
+ int rcExit = 0;
+ for (int iArg = 1; iArg < cArgs; iArg++)
+ {
+ wchar_t wszProductCode[40] = { 0 };
+ wchar_t wszFeatureId[40] = { 0 };
+ wchar_t wszComponentCode[40] = { 0 };
+ DWORD offArguments = ~(DWORD)0;
+ DWORD dwErr = pfnMsiDecomposeDescriptorW(papwszArgs[iArg], wszProductCode, wszFeatureId, wszComponentCode, &offArguments);
+ if (dwErr == 0)
+ {
+ fprintf(stderr,
+ "#%u: '%ls'\n"
+ " -> Product=%ls\n"
+ " -> FeatureId=%ls\n"
+ " -> ComponentCode=%ls\n"
+ " -> offArguments=%#lx (%ld)\n"
+ , iArg, papwszArgs[iArg], wszProductCode, wszFeatureId, wszComponentCode, offArguments, offArguments);
+ }
+ else
+ {
+ fprintf(stderr,
+ "#%u: '%ls'\n"
+ " -> error %lu (%#lx)\n"
+ , iArg, papwszArgs[iArg], dwErr, dwErr);
+ rcExit = 1;
+ }
+ }
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Main/testcase/ovf-joomla-0.9/joomla-1.1.4-ovf.ovf b/src/VBox/Main/testcase/ovf-joomla-0.9/joomla-1.1.4-ovf.ovf
new file mode 100644
index 00000000..924379a8
--- /dev/null
+++ b/src/VBox/Main/testcase/ovf-joomla-0.9/joomla-1.1.4-ovf.ovf
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<ovf:Envelope xmlns:ovf="http://www.vmware.com/schema/ovf/1/envelope" xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ovf:version="0.9">
+<References>
+<File ovf:href="joomla-1.1.4-ovf-0.vmdk" ovf:id="file1" ovf:size="199414784"/>
+<File ovf:href="joomla-1.1.4-ovf-1.vmdk" ovf:id="file2" ovf:size="393216"/>
+</References>
+<Section xsi:type="ovf:NetworkSection_Type">
+<Info>List of networks</Info>
+<Network ovf:name="Network 1">
+<Description>The "Network 1" network</Description>
+</Network>
+</Section>
+<Section xsi:type="ovf:DiskSection_Type">
+<Info>List of Virtual Disks</Info>
+<Disk ovf:capacity="1610612736" ovf:diskId="disk1" ovf:fileRef="file1" ovf:format="http://www.vmware.com/specifications/vmdk.html#sparse" ovf:populatedSize="639434752"/>
+<Disk ovf:capacity="10737418240" ovf:diskId="disk2" ovf:fileRef="file2" ovf:format="http://www.vmware.com/specifications/vmdk.html#sparse" ovf:populatedSize="7536640"/>
+</Section>
+<Content ovf:id="joomla-1.1.4-ovf" xsi:type="ovf:VirtualSystem_Type">
+<Info>A virtual machine</Info>
+<Section ovf:required="false" xsi:type="ovf:AnnotationSection_Type">
+<Info>An annotation</Info>
+<Annotation>This is a JumpBox for joomla. For more information, please visit http://www.jumpbox.com/</Annotation>
+<!-- <Annotation>This is a JumpBox for joomla. For more information, please visit http://www.jumpbox.com/</Annotation> -->
+</Section>
+<Section ovf:id="101" ovf:required="false" xsi:type="ovf:OperatingSystemSection_Type">
+<Info>Guest Operating System</Info>
+<Description>Ubuntu</Description>
+</Section>
+<Section xsi:type="ovf:VirtualHardwareSection_Type">
+<Info>1 CPU, 256 memory</Info>
+<System>
+<vssd:VirtualSystemType>vmx-04</vssd:VirtualSystemType>
+</System>
+<Item>
+<rasd:Caption>1 virtual CPUs</rasd:Caption>
+<rasd:Description>Number of virtual CPUs</rasd:Description>
+<rasd:InstanceId>1</rasd:InstanceId>
+<rasd:ResourceType>3</rasd:ResourceType>
+<rasd:VirtualQuantity>1</rasd:VirtualQuantity>
+</Item>
+<Item>
+<rasd:Caption>256 MB of memory</rasd:Caption>
+<rasd:Description>Memory Size</rasd:Description>
+<rasd:InstanceId>2</rasd:InstanceId>
+<rasd:ResourceType>4</rasd:ResourceType>
+<rasd:AllocationUnits>MegaBytes</rasd:AllocationUnits>
+<rasd:VirtualQuantity>256</rasd:VirtualQuantity>
+</Item>
+<Item>
+<rasd:Caption>Harddisk 0</rasd:Caption>
+<rasd:InstanceId>9</rasd:InstanceId>
+<rasd:ResourceType>17</rasd:ResourceType>
+<rasd:HostResource>/disk/disk1</rasd:HostResource>
+<rasd:Parent>8</rasd:Parent>
+<rasd:AddressOnParent>0</rasd:AddressOnParent>
+</Item>
+<Item>
+<rasd:Caption>Harddisk 1</rasd:Caption>
+<rasd:InstanceId>10</rasd:InstanceId>
+<rasd:ResourceType>17</rasd:ResourceType>
+<rasd:HostResource>/disk/disk2</rasd:HostResource>
+<rasd:Parent>8</rasd:Parent>
+<rasd:AddressOnParent>1</rasd:AddressOnParent>
+</Item>
+<Item>
+<rasd:Caption>SCSI Controller 0</rasd:Caption>
+<rasd:InstanceId>8</rasd:InstanceId>
+<rasd:ResourceType>6</rasd:ResourceType>
+<rasd:ResourceSubType>lsilogic</rasd:ResourceSubType>
+<rasd:BusNumber>0</rasd:BusNumber>
+</Item>
+<Item>
+<rasd:Caption>Ethernet adapter on "Network 1"</rasd:Caption>
+<rasd:InstanceId>7</rasd:InstanceId>
+<rasd:ResourceType>10</rasd:ResourceType>
+<rasd:ResourceSubType>PCNet32</rasd:ResourceSubType>
+<rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+<rasd:Connection>Network 1</rasd:Connection>
+<rasd:AddressOnParent>1</rasd:AddressOnParent>
+</Item>
+</Section>
+</Content>
+</ovf:Envelope>
diff --git a/src/VBox/Main/testcase/ovf-winhost-audio-nodisks/WinXP.ovf b/src/VBox/Main/testcase/ovf-winhost-audio-nodisks/WinXP.ovf
new file mode 100644
index 00000000..ea46077e
--- /dev/null
+++ b/src/VBox/Main/testcase/ovf-winhost-audio-nodisks/WinXP.ovf
@@ -0,0 +1,267 @@
+<?xml version="1.0"?>
+<Envelope ovf:version="1.0" xml:lang="en-US" xmlns="http://schemas.dmtf.org/ovf/envelope/1" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1" xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:vbox="http://www.virtualbox.org/ovf/machine">
+ <References/>
+ <DiskSection>
+ <Info>List of the virtual disks used in the package</Info>
+ </DiskSection>
+ <NetworkSection>
+ <Info>Logical networks used in the package</Info>
+ <Network ovf:name="NAT">
+ <Description>Logical network used by this appliance.</Description>
+ </Network>
+ </NetworkSection>
+ <VirtualSystem ovf:id="WinXP">
+ <Info>A virtual machine</Info>
+ <OperatingSystemSection ovf:id="67">
+ <Info>The kind of installed guest operating system</Info>
+ <Description>WindowsXP</Description>
+ </OperatingSystemSection>
+ <VirtualHardwareSection>
+ <Info>Virtual hardware requirements for a virtual machine</Info>
+ <System>
+ <vssd:ElementName>Virtual Hardware Family</vssd:ElementName>
+ <vssd:InstanceID>0</vssd:InstanceID>
+ <vssd:VirtualSystemIdentifier>WinXP</vssd:VirtualSystemIdentifier>
+ <vssd:VirtualSystemType>virtualbox-2.2</vssd:VirtualSystemType>
+ </System>
+ <Item>
+ <rasd:Caption>1 virtual CPU</rasd:Caption>
+ <rasd:Description>Number of virtual CPUs</rasd:Description>
+ <rasd:ElementName>1 virtual CPU</rasd:ElementName>
+ <rasd:InstanceID>1</rasd:InstanceID>
+ <rasd:ResourceType>3</rasd:ResourceType>
+ <rasd:VirtualQuantity>1</rasd:VirtualQuantity>
+ </Item>
+ <Item>
+ <rasd:AllocationUnits>MegaBytes</rasd:AllocationUnits>
+ <rasd:Caption>512 MB of memory</rasd:Caption>
+ <rasd:Description>Memory Size</rasd:Description>
+ <rasd:ElementName>512 MB of memory</rasd:ElementName>
+ <rasd:InstanceID>2</rasd:InstanceID>
+ <rasd:ResourceType>4</rasd:ResourceType>
+ <rasd:VirtualQuantity>512</rasd:VirtualQuantity>
+ </Item>
+ <Item>
+ <rasd:Address>0</rasd:Address>
+ <rasd:Caption>ideController0</rasd:Caption>
+ <rasd:Description>IDE Controller</rasd:Description>
+ <rasd:ElementName>ideController0</rasd:ElementName>
+ <rasd:InstanceID>3</rasd:InstanceID>
+ <rasd:ResourceSubType>PIIX3</rasd:ResourceSubType>
+ <rasd:ResourceType>5</rasd:ResourceType>
+ </Item>
+ <Item>
+ <rasd:Address>1</rasd:Address>
+ <rasd:Caption>ideController1</rasd:Caption>
+ <rasd:Description>IDE Controller</rasd:Description>
+ <rasd:ElementName>ideController1</rasd:ElementName>
+ <rasd:InstanceID>4</rasd:InstanceID>
+ <rasd:ResourceSubType>PIIX3</rasd:ResourceSubType>
+ <rasd:ResourceType>5</rasd:ResourceType>
+ </Item>
+ <Item>
+ <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+ <rasd:Caption>Ethernet adapter on 'NAT'</rasd:Caption>
+ <rasd:Connection>NAT</rasd:Connection>
+ <rasd:ElementName>Ethernet adapter on 'NAT'</rasd:ElementName>
+ <rasd:InstanceID>5</rasd:InstanceID>
+ <rasd:ResourceSubType>PCNet32</rasd:ResourceSubType>
+ <rasd:ResourceType>10</rasd:ResourceType>
+ </Item>
+ <Item>
+ <rasd:Address>0</rasd:Address>
+ <rasd:Caption>usb</rasd:Caption>
+ <rasd:Description>USB Controller</rasd:Description>
+ <rasd:ElementName>usb</rasd:ElementName>
+ <rasd:InstanceID>6</rasd:InstanceID>
+ <rasd:ResourceType>23</rasd:ResourceType>
+ </Item>
+ <Item>
+ <rasd:AddressOnParent>3</rasd:AddressOnParent>
+ <rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
+ <rasd:Caption>sound</rasd:Caption>
+ <rasd:Description>Sound Card</rasd:Description>
+ <rasd:ElementName>sound</rasd:ElementName>
+ <rasd:InstanceID>7</rasd:InstanceID>
+ <rasd:ResourceSubType>ensoniq1371</rasd:ResourceSubType>
+ <rasd:ResourceType>35</rasd:ResourceType>
+ </Item>
+ <Item>
+ <rasd:AddressOnParent>0</rasd:AddressOnParent>
+ <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+ <rasd:Caption>cdrom1</rasd:Caption>
+ <rasd:Description>CD-ROM Drive</rasd:Description>
+ <rasd:ElementName>cdrom1</rasd:ElementName>
+ <rasd:InstanceID>8</rasd:InstanceID>
+ <rasd:Parent>4</rasd:Parent>
+ <rasd:ResourceType>15</rasd:ResourceType>
+ </Item>
+ </VirtualHardwareSection>
+ <vbox:Machine ovf:required="false" version="1.10-windows" uuid="{fc4b0fa2-61a7-4974-939a-d25bdab59971}" name="WinXP" OSType="WindowsXP" lastStateChange="2010-07-20T14:23:26Z">
+ <ovf:Info>Complete VirtualBox machine configuration in VirtualBox format</ovf:Info>
+ <ExtraData>
+ <ExtraDataItem name="GUI/LastCloseAction" value="powerOff"/>
+ <ExtraDataItem name="GUI/LastGuestSizeHint" value="640,480"/>
+ <ExtraDataItem name="GUI/LastNormalWindowPosition" value="520,243,640,523"/>
+ <ExtraDataItem name="GUI/LastScaleWindowPosition" value="520,265,640,480"/>
+ <ExtraDataItem name="GUI/MiniToolBarAlignment" value="bottom"/>
+ <ExtraDataItem name="GUI/SaveMountedAtRuntime" value="yes"/>
+ <ExtraDataItem name="GUI/ShowMiniToolBar" value="yes"/>
+ </ExtraData>
+ <Hardware version="2">
+ <CPU count="1" hotplug="false">
+ <HardwareVirtEx enabled="true" exclusive="false"/>
+ <HardwareVirtExNestedPaging enabled="true"/>
+ <HardwareVirtExVPID enabled="true"/>
+ <PAE enabled="false"/>
+ </CPU>
+ <Memory RAMSize="512" PageFusion="false"/>
+ <HID Pointing="PS2Mouse" Keyboard="PS2Keyboard"/>
+ <HPET enabled="false"/>
+ <Boot>
+ <Order position="1" device="HardDisk"/>
+ <Order position="2" device="None"/>
+ <Order position="3" device="None"/>
+ <Order position="4" device="None"/>
+ </Boot>
+ <Display VRAMSize="20" monitorCount="1" accelerate3D="false" accelerate2DVideo="false"/>
+ <RemoteDisplay enabled="false" port="3389" authType="Null" authTimeout="5000">
+ <VideoChannel enabled="false" quality="75"/>
+ </RemoteDisplay>
+ <BIOS>
+ <ACPI enabled="true"/>
+ <IOAPIC enabled="false"/>
+ <Logo fadeIn="true" fadeOut="true" displayTime="0"/>
+ <BootMenu mode="MessageAndMenu"/>
+ <TimeOffset value="0"/>
+ <PXEDebug enabled="false"/>
+ </BIOS>
+ <USBController enabled="true" enabledEhci="true"/>
+ <Network>
+ <Adapter slot="0" enabled="true" MACAddress="080027FDEC62" cable="true" speed="0" type="Am79C973">
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </Adapter>
+ <Adapter slot="1" enabled="false" MACAddress="08002767B875" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ <Adapter slot="2" enabled="false" MACAddress="080027CE5CC2" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ <Adapter slot="3" enabled="false" MACAddress="0800277CCAFC" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ <Adapter slot="4" enabled="false" MACAddress="080027D1AE28" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ <Adapter slot="5" enabled="false" MACAddress="080027A2AA62" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ <Adapter slot="6" enabled="false" MACAddress="080027BFC29B" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ <Adapter slot="7" enabled="false" MACAddress="080027A2A78A" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ </Network>
+ <UART>
+ <Port slot="0" enabled="false" IOBase="0x3f8" IRQ="4" hostMode="Disconnected"/>
+ <Port slot="1" enabled="false" IOBase="0x3f8" IRQ="4" hostMode="Disconnected"/>
+ </UART>
+ <LPT>
+ <Port slot="0" enabled="false" IOBase="0x378" IRQ="4"/>
+ <Port slot="1" enabled="false" IOBase="0x378" IRQ="4"/>
+ </LPT>
+ <AudioAdapter controller="AC97" driver="DirectSound" enabled="true"/>
+ <RTC localOrUTC="local"/>
+ <SharedFolders/>
+ <Clipboard mode="Bidirectional"/>
+ <IO>
+ <IoCache enabled="true" size="5"/>
+ <IoBandwidth max="0"/>
+ </IO>
+ <Guest memoryBalloonSize="0"/>
+ <GuestProperties>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/Product" value="Windows XP Professional" timestamp="1279634733194581900" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/Release" value="5.1.2600" timestamp="1279634733206582500" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/Version" value="" timestamp="1279634733209582700" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/ServicePack" value="2" timestamp="1279634733215583100" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Version" value="3.1.51" timestamp="1279634733216583100" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Revision" value="60359" timestamp="1279634733217583200" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/InstallDir" value="C:/Program Files/Oracle/VirtualBox Guest Additions" timestamp="1279634733219583300" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxControl.exe" value="3.1.51r60359" timestamp="1279634733223583500" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxHook.dll" value="3.1.51r60359" timestamp="1279634733227583700" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxDisp.dll" value="3.1.51r60359" timestamp="1279634733230583900" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxMRXNP.dll" value="3.1.51r60359" timestamp="1279634733235584200" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxService.exe" value="3.1.51r60359" timestamp="1279634733236584300" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxTray.exe" value="3.1.51r60359" timestamp="1279634733243584700" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxGINA.dll" value="-" timestamp="1279634733274586400" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxCredProv.dll" value="-" timestamp="1279634733292587500" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGLarrayspu.dll" value="3.1.51r60359" timestamp="1279634733294587600" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGLcrutil.dll" value="3.1.51r60359" timestamp="1279634733296587700" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGLerrorspu.dll" value="3.1.51r60359" timestamp="1279634733298587800" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGLpackspu.dll" value="3.1.51r60359" timestamp="1279634733300587900" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGLpassthroughspu.dll" value="3.1.51r60359" timestamp="1279634733303588100" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGLfeedbackspu.dll" value="3.1.51r60359" timestamp="1279634733305588200" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGL.dll" value="3.1.51r60359" timestamp="1279634733310588500" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxGuest.sys" value="3.1.51r60359" timestamp="1279634733316588800" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxMouse.sys" value="3.1.51r60359" timestamp="1279634733321589100" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxSF.sys" value="3.1.51r60359" timestamp="1279634733325589400" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxVideo.sys" value="3.1.51r60359" timestamp="1279634733331589700" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/NoLoggedInUsers" value="false" timestamp="1279634743366163600" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/HostVerLastChecked" value="3.2.51" timestamp="1279634759329076700" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/LoggedInUsersList" value="Dsen" timestamp="1279634773413882300" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/LoggedInUsers" value="1" timestamp="1279634773414882300" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/Net/Count" value="1" timestamp="1279634773416882400" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/Net/0/V4/IP" value="10.0.2.15" timestamp="1279634773417882500" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/Net/0/V4/Broadcast" value="255.255.255.255" timestamp="1279634773418882600" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/Net/0/V4/Netmask" value="255.255.255.0" timestamp="1279634773418882601" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/Net/0/Status" value="Up" timestamp="1279634773419882600" flags=""/>
+ <GuestProperty name="/VirtualBox/HostInfo/GUI/LanguageID" value="C" timestamp="1279636075749371600" flags=""/>
+ </GuestProperties>
+ </Hardware>
+ <StorageControllers>
+ <StorageController name="IDE Controller" type="PIIX3" PortCount="2" useHostIOCache="true">
+ <AttachedDevice passthrough="false" type="DVD" port="1" device="0"/>
+ </StorageController>
+ </StorageControllers>
+ </vbox:Machine>
+ </VirtualSystem>
+</Envelope>
diff --git a/src/VBox/Main/testcase/ovf-winxp-vbox-sharedfolders/winxp.ovf b/src/VBox/Main/testcase/ovf-winxp-vbox-sharedfolders/winxp.ovf
new file mode 100644
index 00000000..9d6ce61c
--- /dev/null
+++ b/src/VBox/Main/testcase/ovf-winxp-vbox-sharedfolders/winxp.ovf
@@ -0,0 +1,315 @@
+<?xml version="1.0"?>
+<Envelope ovf:version="1.0" xml:lang="en-US" xmlns="http://schemas.dmtf.org/ovf/envelope/1" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1" xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:vbox="http://www.virtualbox.org/ovf/machine">
+ <References>
+ <File ovf:href="Windows 5.1 XP 1 merged.vmdk" ovf:id="file1" ovf:size="4948965888"/>
+ <File ovf:href="smallvdi.vmdk" ovf:id="file2" ovf:size="1265152"/>
+ </References>
+ <DiskSection>
+ <Info>List of the virtual disks used in the package</Info>
+ <Disk ovf:capacity="10485760000" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" vbox:uuid="1fc3c37e-079b-477b-a6d7-84c0e8a717ac"/>
+ <Disk ovf:capacity="2147483648" ovf:diskId="vmdisk2" ovf:fileRef="file2" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" vbox:uuid="cf2b9350-4d5f-42a2-be70-b85a00c2ec9f"/>
+ </DiskSection>
+ <NetworkSection>
+ <Info>Logical networks used in the package</Info>
+ <Network ovf:name="NAT">
+ <Description>Logical network used by this appliance.</Description>
+ </Network>
+ </NetworkSection>
+ <VirtualSystem ovf:id="Windows 5.1 XP 1">
+ <Info>A virtual machine</Info>
+ <OperatingSystemSection ovf:id="67">
+ <Info>The kind of installed guest operating system</Info>
+ <Description>WindowsXP</Description>
+ </OperatingSystemSection>
+ <VirtualHardwareSection>
+ <Info>Virtual hardware requirements for a virtual machine</Info>
+ <System>
+ <vssd:ElementName>Virtual Hardware Family</vssd:ElementName>
+ <vssd:InstanceID>0</vssd:InstanceID>
+ <vssd:VirtualSystemIdentifier>Windows 5.1 XP 1</vssd:VirtualSystemIdentifier>
+ <vssd:VirtualSystemType>virtualbox-2.2</vssd:VirtualSystemType>
+ </System>
+ <Item>
+ <rasd:Caption>1 virtual CPU</rasd:Caption>
+ <rasd:Description>Number of virtual CPUs</rasd:Description>
+ <rasd:ElementName>1 virtual CPU</rasd:ElementName>
+ <rasd:InstanceID>1</rasd:InstanceID>
+ <rasd:ResourceType>3</rasd:ResourceType>
+ <rasd:VirtualQuantity>1</rasd:VirtualQuantity>
+ </Item>
+ <Item>
+ <rasd:AllocationUnits>MegaBytes</rasd:AllocationUnits>
+ <rasd:Caption>895 MB of memory</rasd:Caption>
+ <rasd:Description>Memory Size</rasd:Description>
+ <rasd:ElementName>895 MB of memory</rasd:ElementName>
+ <rasd:InstanceID>2</rasd:InstanceID>
+ <rasd:ResourceType>4</rasd:ResourceType>
+ <rasd:VirtualQuantity>895</rasd:VirtualQuantity>
+ </Item>
+ <Item>
+ <rasd:Address>0</rasd:Address>
+ <rasd:Caption>ideController0</rasd:Caption>
+ <rasd:Description>IDE Controller</rasd:Description>
+ <rasd:ElementName>ideController0</rasd:ElementName>
+ <rasd:InstanceID>3</rasd:InstanceID>
+ <rasd:ResourceSubType>PIIX3</rasd:ResourceSubType>
+ <rasd:ResourceType>5</rasd:ResourceType>
+ </Item>
+ <Item>
+ <rasd:Address>1</rasd:Address>
+ <rasd:Caption>ideController1</rasd:Caption>
+ <rasd:Description>IDE Controller</rasd:Description>
+ <rasd:ElementName>ideController1</rasd:ElementName>
+ <rasd:InstanceID>4</rasd:InstanceID>
+ <rasd:ResourceSubType>PIIX3</rasd:ResourceSubType>
+ <rasd:ResourceType>5</rasd:ResourceType>
+ </Item>
+ <Item>
+ <rasd:AddressOnParent>0</rasd:AddressOnParent>
+ <rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
+ <rasd:Caption>floppy0</rasd:Caption>
+ <rasd:Description>Floppy Drive</rasd:Description>
+ <rasd:ElementName>floppy0</rasd:ElementName>
+ <rasd:InstanceID>5</rasd:InstanceID>
+ <rasd:ResourceType>14</rasd:ResourceType>
+ </Item>
+ <Item>
+ <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+ <rasd:Caption>Ethernet adapter on 'NAT'</rasd:Caption>
+ <rasd:Connection>NAT</rasd:Connection>
+ <rasd:ElementName>Ethernet adapter on 'NAT'</rasd:ElementName>
+ <rasd:InstanceID>6</rasd:InstanceID>
+ <rasd:ResourceSubType>PCNet32</rasd:ResourceSubType>
+ <rasd:ResourceType>10</rasd:ResourceType>
+ </Item>
+ <Item>
+ <rasd:Address>0</rasd:Address>
+ <rasd:Caption>usb</rasd:Caption>
+ <rasd:Description>USB Controller</rasd:Description>
+ <rasd:ElementName>usb</rasd:ElementName>
+ <rasd:InstanceID>7</rasd:InstanceID>
+ <rasd:ResourceType>23</rasd:ResourceType>
+ </Item>
+ <Item>
+ <rasd:AddressOnParent>3</rasd:AddressOnParent>
+ <rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
+ <rasd:Caption>sound</rasd:Caption>
+ <rasd:Description>Sound Card</rasd:Description>
+ <rasd:ElementName>sound</rasd:ElementName>
+ <rasd:InstanceID>8</rasd:InstanceID>
+ <rasd:ResourceSubType>ensoniq1371</rasd:ResourceSubType>
+ <rasd:ResourceType>35</rasd:ResourceType>
+ </Item>
+ <Item>
+ <rasd:AddressOnParent>0</rasd:AddressOnParent>
+ <rasd:Caption>disk1</rasd:Caption>
+ <rasd:Description>Disk Image</rasd:Description>
+ <rasd:ElementName>disk1</rasd:ElementName>
+ <rasd:HostResource>/disk/vmdisk1</rasd:HostResource>
+ <rasd:InstanceID>9</rasd:InstanceID>
+ <rasd:Parent>3</rasd:Parent>
+ <rasd:ResourceType>17</rasd:ResourceType>
+ </Item>
+ <Item>
+ <rasd:AddressOnParent>1</rasd:AddressOnParent>
+ <rasd:Caption>disk2</rasd:Caption>
+ <rasd:Description>Disk Image</rasd:Description>
+ <rasd:ElementName>disk2</rasd:ElementName>
+ <rasd:HostResource>/disk/vmdisk2</rasd:HostResource>
+ <rasd:InstanceID>10</rasd:InstanceID>
+ <rasd:Parent>3</rasd:Parent>
+ <rasd:ResourceType>17</rasd:ResourceType>
+ </Item>
+ <Item>
+ <rasd:AddressOnParent>0</rasd:AddressOnParent>
+ <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+ <rasd:Caption>cdrom1</rasd:Caption>
+ <rasd:Description>CD-ROM Drive</rasd:Description>
+ <rasd:ElementName>cdrom1</rasd:ElementName>
+ <rasd:InstanceID>11</rasd:InstanceID>
+ <rasd:Parent>4</rasd:Parent>
+ <rasd:ResourceType>15</rasd:ResourceType>
+ </Item>
+ </VirtualHardwareSection>
+ <vbox:Machine ovf:required="false" version="1.10-linux" uuid="{5f102a55-a51b-48e3-b45a-b28d33469488}" name="Windows 5.1 XP 1" OSType="WindowsXP" snapshotFolder="Snapshots" lastStateChange="2010-06-15T15:33:58Z">
+ <ovf:Info>Complete VirtualBox machine configuration in VirtualBox format</ovf:Info>
+ <ExtraData>
+ <ExtraDataItem name="GUI/InfoDlgState" value="380,662,normal"/>
+ <ExtraDataItem name="GUI/LastCloseAction" value="save"/>
+ <ExtraDataItem name="GUI/LastGuestSizeHint" value="1136,933"/>
+ <ExtraDataItem name="GUI/LastWindowPostion" value="188,53,1136,977"/>
+ <ExtraDataItem name="GUI/MiniToolBarAlignment" value="bottom"/>
+ <ExtraDataItem name="GUI/SaveMountedAtRuntime" value="yes"/>
+ <ExtraDataItem name="GUI/ShowMiniToolBar" value="yes"/>
+ <ExtraDataItem name="VBoxInternal2/VRDPBindPort" value="3389"/>
+ </ExtraData>
+ <Hardware version="2">
+ <CPU count="1" hotplug="false">
+ <HardwareVirtEx enabled="true" exclusive="true"/>
+ <HardwareVirtExNestedPaging enabled="true"/>
+ <HardwareVirtExVPID enabled="false"/>
+ <PAE enabled="false"/>
+ </CPU>
+ <Memory RAMSize="895" PageFusion="false"/>
+ <HID Pointing="PS2Mouse" Keyboard="PS2Keyboard"/>
+ <HPET enabled="false"/>
+ <Boot>
+ <Order position="1" device="Floppy"/>
+ <Order position="2" device="DVD"/>
+ <Order position="3" device="HardDisk"/>
+ <Order position="4" device="None"/>
+ </Boot>
+ <Display VRAMSize="24" monitorCount="1" accelerate3D="true" accelerate2DVideo="false"/>
+ <RemoteDisplay enabled="true" port="3389" authType="Null" authTimeout="5000">
+ <VideoChannel enabled="false" quality="75"/>
+ </RemoteDisplay>
+ <BIOS>
+ <ACPI enabled="true"/>
+ <IOAPIC enabled="false"/>
+ <Logo fadeIn="true" fadeOut="false" displayTime="0"/>
+ <BootMenu mode="MessageAndMenu"/>
+ <TimeOffset value="0"/>
+ <PXEDebug enabled="false"/>
+ </BIOS>
+ <USBController enabled="true" enabledEhci="false">
+ <DeviceFilter name="SanDisk U3 Cruzer Micro [0200]" active="true" vendorId="0781" productId="5406" revision="0200" manufacturer="SanDisk" product="U3 Cruzer Micro" serialNumber="0877201B1A11B7A6" remote="no"/>
+ </USBController>
+ <Network>
+ <Adapter slot="0" enabled="true" MACAddress="080027F1086C" cable="true" speed="0" type="Am79C973">
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </Adapter>
+ <Adapter slot="1" enabled="false" MACAddress="0800273B18E3" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ <Adapter slot="2" enabled="false" MACAddress="080027C9B8C9" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ <Adapter slot="3" enabled="false" MACAddress="08002729FB57" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ <Adapter slot="4" enabled="false" MACAddress="080027C6BF50" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ <Adapter slot="5" enabled="false" MACAddress="080027DF7499" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ <Adapter slot="6" enabled="false" MACAddress="080027FB8C2B" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ <Adapter slot="7" enabled="false" MACAddress="080027614C1B" cable="true" speed="0" type="Am79C973">
+ <DisabledModes>
+ <NAT>
+ <DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
+ <Alias logging="false" proxy-only="false" use-same-ports="false"/>
+ </NAT>
+ </DisabledModes>
+ </Adapter>
+ </Network>
+ <UART>
+ <Port slot="0" enabled="false" IOBase="0x3f8" IRQ="4" hostMode="Disconnected"/>
+ <Port slot="1" enabled="false" IOBase="0x3f8" IRQ="4" hostMode="Disconnected"/>
+ </UART>
+ <LPT>
+ <Port slot="0" enabled="false" IOBase="0x378" IRQ="4"/>
+ <Port slot="1" enabled="false" IOBase="0x378" IRQ="4"/>
+ </LPT>
+ <AudioAdapter controller="AC97" driver="ALSA" enabled="true"/>
+ <RTC localOrUTC="local"/>
+ <SharedFolders>
+ <SharedFolder name="t" hostPath="/path/does/not/exist" writable="true"/>
+ </SharedFolders>
+ <Clipboard mode="Bidirectional"/>
+ <IO>
+ <IoCache enabled="true" size="5"/>
+ <IoBandwidth max="0"/>
+ </IO>
+ <Guest memoryBalloonSize="0"/>
+ <GuestProperties>
+ <GuestProperty name="/VirtualBox/HostInfo/GUI/LanguageID" value="C" timestamp="1276615475026659000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/Product" value="Windows XP Professional" timestamp="1276615535415199000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/Release" value="5.1.2600" timestamp="1276615535423377000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/Version" value="" timestamp="1276615535424390000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/ServicePack" value="3" timestamp="1276615535425834000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Version" value="3.1.7" timestamp="1276615535426567000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Revision" value="60877" timestamp="1276615535428273000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/InstallDir" value="C:/Program Files/Sun/VirtualBox Guest Additions" timestamp="1276615535428823000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxControl.exe" value="3.1.7r60877" timestamp="1276615535452159000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxHook.dll" value="3.1.7r60877" timestamp="1276615535453735000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxDisp.dll" value="3.1.7r60877" timestamp="1276615535455440000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxMRXNP.dll" value="3.1.7r60877" timestamp="1276615535456268000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxService.exe" value="3.1.7r60877" timestamp="1276615535457312000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxTray.exe" value="3.1.7r60877" timestamp="1276615535458094000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxGINA.dll" value="3.0.0r49275" timestamp="1276615535458828000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxCredProv.dll" value="-" timestamp="1276615535467867000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGLarrayspu.dll" value="3.1.7r60877" timestamp="1276615535468601000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGLcrutil.dll" value="3.1.7r60877" timestamp="1276615535469299000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGLerrorspu.dll" value="3.1.7r60877" timestamp="1276615535469853000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGLpackspu.dll" value="3.1.7r60877" timestamp="1276615535491615000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGLpassthroughspu.dll" value="3.1.7r60877" timestamp="1276615535492438000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGLfeedbackspu.dll" value="3.1.7r60877" timestamp="1276615535493364000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxOGL.dll" value="3.1.7r60877" timestamp="1276615535494186000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxGuest.sys" value="3.1.7r60877" timestamp="1276615535495047000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxMouse.sys" value="3.1.7r60877" timestamp="1276615535495946000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxSF.sys" value="3.1.7r60877" timestamp="1276615535496631000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/Components/VBoxVideo.sys" value="3.1.7r60877" timestamp="1276615535497318000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestAdd/HostVerLastChecked" value="3.2.51" timestamp="1276615561761565000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/LoggedInUsersList" value="" timestamp="1276616026260647000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/LoggedInUsers" value="0" timestamp="1276616026262352000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/OS/NoLoggedInUsers" value="true" timestamp="1276616026262920000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/Net/0/V4/IP" value="" timestamp="1276616026263814000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/Net/0/V4/Broadcast" value="" timestamp="1276616026264275000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/Net/0/V4/Netmask" value="" timestamp="1276616026264727000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/Net/0/Status" value="" timestamp="1276616026265170000" flags=""/>
+ <GuestProperty name="/VirtualBox/GuestInfo/Net/Count" value="0" timestamp="1276616026266071000" flags=""/>
+ </GuestProperties>
+ </Hardware>
+ <StorageControllers>
+ <StorageController name="IDE Controller" type="PIIX3" PortCount="2" useHostIOCache="true">
+ <AttachedDevice type="HardDisk" port="0" device="0">
+ <Image uuid="{1fc3c37e-079b-477b-a6d7-84c0e8a717ac}"/>
+ </AttachedDevice>
+ <AttachedDevice type="HardDisk" port="0" device="1">
+ <Image uuid="{cf2b9350-4d5f-42a2-be70-b85a00c2ec9f}"/>
+ </AttachedDevice>
+ <AttachedDevice passthrough="false" type="DVD" port="1" device="0"/>
+ </StorageController>
+ <StorageController name="Floppy Controller" type="I82078" PortCount="1" useHostIOCache="true">
+ <AttachedDevice type="Floppy" port="0" device="0"/>
+ </StorageController>
+ </StorageControllers>
+ </vbox:Machine>
+ </VirtualSystem>
+</Envelope>
diff --git a/src/VBox/Main/testcase/tstAPI.cpp b/src/VBox/Main/testcase/tstAPI.cpp
new file mode 100644
index 00000000..df0a8905
--- /dev/null
+++ b/src/VBox/Main/testcase/tstAPI.cpp
@@ -0,0 +1,1706 @@
+/* $Id: tstAPI.cpp $ */
+/** @file
+ * tstAPI - test program for our COM/XPCOM interface
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <VBox/com/com.h>
+#include <VBox/com/string.h>
+#include <VBox/com/Guid.h>
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/com/errorprint.h>
+
+#include <VBox/com/VirtualBox.h>
+
+using namespace com;
+
+#define LOG_ENABLED
+#define LOG_GROUP LOG_GROUP_MAIN
+#include <VBox/log.h>
+
+#include <iprt/initterm.h>
+#include <iprt/path.h>
+#include <iprt/param.h>
+#include <iprt/stream.h>
+#include <iprt/thread.h>
+
+
+// forward declarations
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+static Bstr getObjectName(ComPtr<IVirtualBox> aVirtualBox, ComPtr<IUnknown> aObject);
+static void queryMetrics(ComPtr<IVirtualBox> aVirtualBox,
+ ComPtr<IPerformanceCollector> collector,
+ ComSafeArrayIn(IUnknown *, objects));
+static void listAffectedMetrics(ComPtr<IVirtualBox> aVirtualBox,
+ ComSafeArrayIn(IPerformanceMetric*, aMetrics));
+#endif
+
+// funcs
+///////////////////////////////////////////////////////////////////////////////
+
+HRESULT readAndChangeMachineSettings(IMachine *machine, IMachine *readonlyMachine = 0)
+{
+ HRESULT hrc = S_OK;
+
+ Bstr name;
+ RTPrintf("Getting machine name...\n");
+ CHECK_ERROR_RET(machine, COMGETTER(Name)(name.asOutParam()), hrc);
+ RTPrintf("Name: {%ls}\n", name.raw());
+
+ RTPrintf("Getting machine GUID...\n");
+ Bstr guid;
+ CHECK_ERROR(machine, COMGETTER(Id)(guid.asOutParam()));
+ if (SUCCEEDED(hrc) && !guid.isEmpty()) {
+ RTPrintf("Guid::toString(): {%s}\n", Utf8Str(guid).c_str());
+ } else {
+ RTPrintf("WARNING: there's no GUID!");
+ }
+
+ ULONG memorySize;
+ RTPrintf("Getting memory size...\n");
+ CHECK_ERROR_RET(machine, COMGETTER(MemorySize)(&memorySize), hrc);
+ RTPrintf("Memory size: %d\n", memorySize);
+
+ MachineState_T machineState;
+ RTPrintf("Getting machine state...\n");
+ CHECK_ERROR_RET(machine, COMGETTER(State)(&machineState), hrc);
+ RTPrintf("Machine state: %d\n", machineState);
+
+ BOOL modified;
+ RTPrintf("Are any settings modified?...\n");
+ CHECK_ERROR(machine, COMGETTER(SettingsModified)(&modified));
+ if (SUCCEEDED(hrc))
+ RTPrintf("%s\n", modified ? "yes" : "no");
+
+ ULONG memorySizeBig = memorySize * 10;
+ RTPrintf("Changing memory size to %d...\n", memorySizeBig);
+ CHECK_ERROR(machine, COMSETTER(MemorySize)(memorySizeBig));
+
+ if (SUCCEEDED(hrc))
+ {
+ RTPrintf("Are any settings modified now?...\n");
+ CHECK_ERROR_RET(machine, COMGETTER(SettingsModified)(&modified), hrc);
+ RTPrintf("%s\n", modified ? "yes" : "no");
+ ASSERT_RET(modified, 0);
+
+ ULONG memorySizeGot;
+ RTPrintf("Getting memory size again...\n");
+ CHECK_ERROR_RET(machine, COMGETTER(MemorySize)(&memorySizeGot), hrc);
+ RTPrintf("Memory size: %d\n", memorySizeGot);
+ ASSERT_RET(memorySizeGot == memorySizeBig, 0);
+
+ if (readonlyMachine)
+ {
+ RTPrintf("Getting memory size of the counterpart readonly machine...\n");
+ ULONG memorySizeRO;
+ readonlyMachine->COMGETTER(MemorySize)(&memorySizeRO);
+ RTPrintf("Memory size: %d\n", memorySizeRO);
+ ASSERT_RET(memorySizeRO != memorySizeGot, 0);
+ }
+
+ RTPrintf("Discarding recent changes...\n");
+ CHECK_ERROR_RET(machine, DiscardSettings(), hrc);
+ RTPrintf("Are any settings modified after discarding?...\n");
+ CHECK_ERROR_RET(machine, COMGETTER(SettingsModified)(&modified), hrc);
+ RTPrintf("%s\n", modified ? "yes" : "no");
+ ASSERT_RET(!modified, 0);
+
+ RTPrintf("Getting memory size once more...\n");
+ CHECK_ERROR_RET(machine, COMGETTER(MemorySize)(&memorySizeGot), hrc);
+ RTPrintf("Memory size: %d\n", memorySizeGot);
+ ASSERT_RET(memorySizeGot == memorySize, 0);
+
+ memorySize = memorySize > 128 ? memorySize / 2 : memorySize * 2;
+ RTPrintf("Changing memory size to %d...\n", memorySize);
+ CHECK_ERROR_RET(machine, COMSETTER(MemorySize)(memorySize), hrc);
+ }
+
+ Bstr desc;
+ RTPrintf("Getting description...\n");
+ CHECK_ERROR_RET(machine, COMGETTER(Description)(desc.asOutParam()), hrc);
+ RTPrintf("Description is: \"%ls\"\n", desc.raw());
+
+ desc = L"This is an exemplary description (changed).";
+ RTPrintf("Setting description to \"%ls\"...\n", desc.raw());
+ CHECK_ERROR_RET(machine, COMSETTER(Description)(desc.raw()), hrc);
+
+ RTPrintf("Saving machine settings...\n");
+ CHECK_ERROR(machine, SaveSettings());
+ if (SUCCEEDED(hrc))
+ {
+ RTPrintf("Are any settings modified after saving?...\n");
+ CHECK_ERROR_RET(machine, COMGETTER(SettingsModified)(&modified), hrc);
+ RTPrintf("%s\n", modified ? "yes" : "no");
+ ASSERT_RET(!modified, 0);
+
+ if (readonlyMachine) {
+ RTPrintf("Getting memory size of the counterpart readonly machine...\n");
+ ULONG memorySizeRO;
+ readonlyMachine->COMGETTER(MemorySize)(&memorySizeRO);
+ RTPrintf("Memory size: %d\n", memorySizeRO);
+ ASSERT_RET(memorySizeRO == memorySize, 0);
+ }
+ }
+
+ Bstr extraDataKey = L"Blafasel";
+ Bstr extraData;
+ RTPrintf("Getting extra data key {%ls}...\n", extraDataKey.raw());
+ CHECK_ERROR_RET(machine, GetExtraData(extraDataKey.raw(), extraData.asOutParam()), hrc);
+ if (!extraData.isEmpty()) {
+ RTPrintf("Extra data value: {%ls}\n", extraData.raw());
+ } else {
+ RTPrintf("No extra data exists\n");
+ }
+
+ if (extraData.isEmpty())
+ extraData = L"Das ist die Berliner Luft, Luft, Luft...";
+ else
+ extraData.setNull();
+ RTPrintf("Setting extra data key {%ls} to {%ls}...\n",
+ extraDataKey.raw(), extraData.raw());
+ CHECK_ERROR(machine, SetExtraData(extraDataKey.raw(), extraData.raw()));
+
+ if (SUCCEEDED(hrc)) {
+ RTPrintf("Getting extra data key {%ls} again...\n", extraDataKey.raw());
+ CHECK_ERROR_RET(machine, GetExtraData(extraDataKey.raw(), extraData.asOutParam()), hrc);
+ if (!extraData.isEmpty()) {
+ RTPrintf("Extra data value: {%ls}\n", extraData.raw());
+ } else {
+ RTPrintf("No extra data exists\n");
+ }
+ }
+
+ return hrc;
+}
+
+// main
+///////////////////////////////////////////////////////////////////////////////
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Initialize the VBox runtime without loading
+ * the support driver.
+ */
+ RTR3InitExe(argc, &argv, 0);
+
+ HRESULT hrc;
+
+ {
+ char homeDir[RTPATH_MAX];
+ GetVBoxUserHomeDirectory(homeDir, sizeof(homeDir));
+ RTPrintf("VirtualBox Home Directory = '%s'\n", homeDir);
+ }
+
+ RTPrintf("Initializing COM...\n");
+
+ hrc = com::Initialize();
+ if (FAILED(hrc))
+ {
+ RTPrintf("ERROR: failed to initialize COM!\n");
+ return hrc;
+ }
+
+ do
+ {
+ // scopes all the stuff till shutdown
+ ////////////////////////////////////////////////////////////////////////////
+
+ ComPtr<IVirtualBoxClient> virtualBoxClient;
+ ComPtr<IVirtualBox> virtualBox;
+ ComPtr<ISession> session;
+
+#if 0
+ // Utf8Str test
+ ////////////////////////////////////////////////////////////////////////////
+
+ Utf8Str nullUtf8Str;
+ RTPrintf("nullUtf8Str='%s'\n", nullUtf8Str.raw());
+
+ Utf8Str simpleUtf8Str = "simpleUtf8Str";
+ RTPrintf("simpleUtf8Str='%s'\n", simpleUtf8Str.raw());
+
+ Utf8Str utf8StrFmt = Utf8StrFmt("[0=%d]%s[1=%d]", 0, "utf8StrFmt", 1);
+ RTPrintf("utf8StrFmt='%s'\n", utf8StrFmt.raw());
+
+#endif
+
+ RTPrintf("Creating VirtualBox object...\n");
+ hrc = virtualBoxClient.createInprocObject(CLSID_VirtualBoxClient);
+ if (SUCCEEDED(hrc))
+ hrc = virtualBoxClient->COMGETTER(VirtualBox)(virtualBox.asOutParam());
+ if (FAILED(hrc))
+ RTPrintf("ERROR: failed to create the VirtualBox object!\n");
+ else
+ {
+ hrc = session.createInprocObject(CLSID_Session);
+ if (FAILED(hrc))
+ RTPrintf("ERROR: failed to create a session object!\n");
+ }
+
+ if (FAILED(hrc))
+ {
+ com::ErrorInfo info;
+ if (!info.isFullAvailable() && !info.isBasicAvailable())
+ {
+ com::GluePrintRCMessage(hrc);
+ RTPrintf("Most likely, the VirtualBox COM server is not running or failed to start.\n");
+ }
+ else
+ com::GluePrintErrorInfo(info);
+ break;
+ }
+
+#if 0
+ // Testing VirtualBox::COMGETTER(ProgressOperations).
+ // This is designed to be tested while running
+ // "./VBoxManage clonehd src.vdi clone.vdi" in parallel.
+ // It will then display the progress every 2 seconds.
+ ////////////////////////////////////////////////////////////////////////////
+ {
+ RTPrintf("Testing VirtualBox::COMGETTER(ProgressOperations)...\n");
+
+ for (;;) {
+ com::SafeIfaceArray<IProgress> operations;
+
+ CHECK_ERROR_BREAK(virtualBox,
+ COMGETTER(ProgressOperations)(ComSafeArrayAsOutParam(operations)));
+
+ RTPrintf("operations: %d\n", operations.size());
+ if (operations.size() == 0)
+ break; // No more operations left.
+
+ for (size_t i = 0; i < operations.size(); ++i) {
+ PRInt32 percent;
+
+ operations[i]->COMGETTER(Percent)(&percent);
+ RTPrintf("operations[%u]: %ld\n", (unsigned)i, (long)percent);
+ }
+ RTThreadSleep(2000); // msec
+ }
+ }
+#endif
+
+#if 0
+ // IUnknown identity test
+ ////////////////////////////////////////////////////////////////////////////
+ {
+ {
+ ComPtr<IVirtualBox> virtualBox2;
+
+ RTPrintf("Creating one more VirtualBox object...\n");
+ CHECK_RC(virtualBoxClient->COMGETTER(virtualBox2.asOutParam()));
+ if (FAILED(rc))
+ {
+ CHECK_ERROR_NOCALL();
+ break;
+ }
+
+ RTPrintf("IVirtualBox(virtualBox)=%p IVirtualBox(virtualBox2)=%p\n",
+ (IVirtualBox *)virtualBox, (IVirtualBox *)virtualBox2);
+ Assert((IVirtualBox *)virtualBox == (IVirtualBox *)virtualBox2);
+
+ ComPtr<IUnknown> unk(virtualBox);
+ ComPtr<IUnknown> unk2;
+ unk2 = virtualBox2;
+
+ RTPrintf("IUnknown(virtualBox)=%p IUnknown(virtualBox2)=%p\n",
+ (IUnknown *)unk, (IUnknown *)unk2);
+ Assert((IUnknown *)unk == (IUnknown *)unk2);
+
+ ComPtr<IVirtualBox> vb = unk;
+ ComPtr<IVirtualBox> vb2 = unk;
+
+ RTPrintf("IVirtualBox(IUnknown(virtualBox))=%p IVirtualBox(IUnknown(virtualBox2))=%p\n",
+ (IVirtualBox *)vb, (IVirtualBox *)vb2);
+ Assert((IVirtualBox *)vb == (IVirtualBox *)vb2);
+ }
+
+ {
+ ComPtr<IHost> host;
+ CHECK_ERROR_BREAK(virtualBox, COMGETTER(Host)(host.asOutParam()));
+ RTPrintf(" IHost(host)=%p\n", (IHost *)host);
+ ComPtr<IUnknown> unk = host;
+ RTPrintf(" IUnknown(host)=%p\n", (IUnknown *)unk);
+ ComPtr<IHost> host_copy = unk;
+ RTPrintf(" IHost(host_copy)=%p\n", (IHost *)host_copy);
+ ComPtr<IUnknown> unk_copy = host_copy;
+ RTPrintf(" IUnknown(host_copy)=%p\n", (IUnknown *)unk_copy);
+ Assert((IUnknown *)unk == (IUnknown *)unk_copy);
+
+ /* query IUnknown on IUnknown */
+ ComPtr<IUnknown> unk_copy_copy;
+ unk_copy.queryInterfaceTo(unk_copy_copy.asOutParam());
+ RTPrintf(" IUnknown(unk_copy)=%p\n", (IUnknown *)unk_copy_copy);
+ Assert((IUnknown *)unk_copy == (IUnknown *)unk_copy_copy);
+ /* query IUnknown on IUnknown in the opposite direction */
+ unk_copy_copy.queryInterfaceTo(unk_copy.asOutParam());
+ RTPrintf(" IUnknown(unk_copy_copy)=%p\n", (IUnknown *)unk_copy);
+ Assert((IUnknown *)unk_copy == (IUnknown *)unk_copy_copy);
+
+ /* query IUnknown again after releasing all previous IUnknown instances
+ * but keeping IHost -- it should remain the same (Identity Rule) */
+ IUnknown *oldUnk = unk;
+ unk.setNull();
+ unk_copy.setNull();
+ unk_copy_copy.setNull();
+ unk = host;
+ RTPrintf(" IUnknown(host)=%p\n", (IUnknown *)unk);
+ Assert(oldUnk == (IUnknown *)unk);
+ }
+
+// RTPrintf("Will be now released (press Enter)...");
+// getchar();
+ }
+#endif
+
+#if 0
+ // the simplest COM API test
+ ////////////////////////////////////////////////////////////////////////////
+ {
+ Bstr version;
+ CHECK_ERROR_BREAK(virtualBox, COMGETTER(Version)(version.asOutParam()));
+ RTPrintf("VirtualBox version = %ls\n", version.raw());
+ }
+#endif
+
+#if 0
+ // Array test
+ ////////////////////////////////////////////////////////////////////////////
+ {
+ RTPrintf("Calling IVirtualBox::Machines...\n");
+
+ com::SafeIfaceArray<IMachine> machines;
+ CHECK_ERROR_BREAK(virtualBox,
+ COMGETTER(Machines)(ComSafeArrayAsOutParam(machines)));
+
+ RTPrintf("%u machines registered (machines.isNull()=%d).\n",
+ machines.size(), machines.isNull());
+
+ for (size_t i = 0; i < machines.size(); ++ i)
+ {
+ Bstr name;
+ CHECK_ERROR_BREAK(machines[i], COMGETTER(Name)(name.asOutParam()));
+ RTPrintf("machines[%u]='%s'\n", i, Utf8Str(name).raw());
+ }
+
+#if 0
+ {
+ RTPrintf("Testing [out] arrays...\n");
+ com::SafeGUIDArray uuids;
+ CHECK_ERROR_BREAK(virtualBox,
+ COMGETTER(Uuids)(ComSafeArrayAsOutParam(uuids)));
+
+ for (size_t i = 0; i < uuids.size(); ++ i)
+ RTPrintf("uuids[%u]=%RTuuid\n", i, &uuids[i]);
+ }
+
+ {
+ RTPrintf("Testing [in] arrays...\n");
+ com::SafeGUIDArray uuids(5);
+ for (size_t i = 0; i < uuids.size(); ++ i)
+ {
+ Guid id;
+ id.create();
+ uuids[i] = id;
+ RTPrintf("uuids[%u]=%RTuuid\n", i, &uuids[i]);
+ }
+
+ CHECK_ERROR_BREAK(virtualBox,
+ SetUuids(ComSafeArrayAsInParam(uuids)));
+ }
+#endif
+
+ }
+#endif
+
+#if 0
+ // some outdated stuff
+ ////////////////////////////////////////////////////////////////////////////
+
+ RTPrintf("Getting IHost interface...\n");
+ IHost *host;
+ rc = virtualBox->GetHost(&host);
+ if (SUCCEEDED(rc))
+ {
+ IHostDVDDriveCollection *dvdColl;
+ rc = host->GetHostDVDDrives(&dvdColl);
+ if (SUCCEEDED(rc))
+ {
+ IHostDVDDrive *dvdDrive = NULL;
+ dvdColl->GetNextHostDVDDrive(dvdDrive, &dvdDrive);
+ while (dvdDrive)
+ {
+ BSTR driveName;
+ char *driveNameUtf8;
+ dvdDrive->GetDriveName(&driveName);
+ RTUtf16ToUtf8((PCRTUTF16)driveName, &driveNameUtf8);
+ RTPrintf("Host DVD drive name: %s\n", driveNameUtf8);
+ RTStrFree(driveNameUtf8);
+ SysFreeString(driveName);
+ IHostDVDDrive *dvdDriveTemp = dvdDrive;
+ dvdColl->GetNextHostDVDDrive(dvdDriveTemp, &dvdDrive);
+ dvdDriveTemp->Release();
+ }
+ dvdColl->Release();
+ } else
+ {
+ RTPrintf("Could not get host DVD drive collection\n");
+ }
+
+ IHostFloppyDriveCollection *floppyColl;
+ rc = host->GetHostFloppyDrives(&floppyColl);
+ if (SUCCEEDED(rc))
+ {
+ IHostFloppyDrive *floppyDrive = NULL;
+ floppyColl->GetNextHostFloppyDrive(floppyDrive, &floppyDrive);
+ while (floppyDrive)
+ {
+ BSTR driveName;
+ char *driveNameUtf8;
+ floppyDrive->GetDriveName(&driveName);
+ RTUtf16ToUtf8((PCRTUTF16)driveName, &driveNameUtf8);
+ RTPrintf("Host floppy drive name: %s\n", driveNameUtf8);
+ RTStrFree(driveNameUtf8);
+ SysFreeString(driveName);
+ IHostFloppyDrive *floppyDriveTemp = floppyDrive;
+ floppyColl->GetNextHostFloppyDrive(floppyDriveTemp, &floppyDrive);
+ floppyDriveTemp->Release();
+ }
+ floppyColl->Release();
+ } else
+ {
+ RTPrintf("Could not get host floppy drive collection\n");
+ }
+ host->Release();
+ } else
+ {
+ RTPrintf("Call failed\n");
+ }
+ RTPrintf("\n");
+#endif
+
+#if 0
+ // IVirtualBoxErrorInfo test
+ ////////////////////////////////////////////////////////////////////////////
+ {
+ // RPC calls
+
+ // call a method that will definitely fail
+ Guid uuid;
+ ComPtr<IHardDisk> hardDisk;
+ rc = virtualBox->GetHardDisk(uuid, hardDisk.asOutParam());
+ RTPrintf("virtualBox->GetHardDisk(null-uuid)=%08X\n", rc);
+
+// {
+// com::ErrorInfo info(virtualBox);
+// PRINT_ERROR_INFO(info);
+// }
+
+ // call a method that will definitely succeed
+ Bstr version;
+ rc = virtualBox->COMGETTER(Version)(version.asOutParam());
+ RTPrintf("virtualBox->COMGETTER(Version)=%08X\n", rc);
+
+ {
+ com::ErrorInfo info(virtualBox);
+ PRINT_ERROR_INFO(info);
+ }
+
+ // Local calls
+
+ // call a method that will definitely fail
+ ComPtr<IMachine> machine;
+ rc = session->COMGETTER(Machine)(machine.asOutParam());
+ RTPrintf("session->COMGETTER(Machine)=%08X\n", rc);
+
+// {
+// com::ErrorInfo info(virtualBox);
+// PRINT_ERROR_INFO(info);
+// }
+
+ // call a method that will definitely succeed
+ SessionState_T state;
+ rc = session->COMGETTER(State)(&state);
+ RTPrintf("session->COMGETTER(State)=%08X\n", rc);
+
+ {
+ com::ErrorInfo info(virtualBox);
+ PRINT_ERROR_INFO(info);
+ }
+ }
+#endif
+
+#if 0
+ // register the existing hard disk image
+ ///////////////////////////////////////////////////////////////////////////
+ do
+ {
+ ComPtr<IHardDisk> hd;
+ Bstr src = L"E:\\develop\\innotek\\images\\NewHardDisk.vdi";
+ RTPrintf("Opening the existing hard disk '%ls'...\n", src.raw());
+ CHECK_ERROR_BREAK(virtualBox, OpenHardDisk(src, AccessMode_ReadWrite, hd.asOutParam()));
+ RTPrintf("Enter to continue...\n");
+ getchar();
+ RTPrintf("Registering the existing hard disk '%ls'...\n", src.raw());
+ CHECK_ERROR_BREAK(virtualBox, RegisterHardDisk(hd));
+ RTPrintf("Enter to continue...\n");
+ getchar();
+ }
+ while (FALSE);
+ RTPrintf("\n");
+#endif
+
+#if 0
+ // find and unregister the existing hard disk image
+ ///////////////////////////////////////////////////////////////////////////
+ do
+ {
+ ComPtr<IVirtualDiskImage> vdi;
+ Bstr src = L"CreatorTest.vdi";
+ RTPrintf("Unregistering the hard disk '%ls'...\n", src.raw());
+ CHECK_ERROR_BREAK(virtualBox, FindVirtualDiskImage(src, vdi.asOutParam()));
+ ComPtr<IHardDisk> hd = vdi;
+ Guid id;
+ CHECK_ERROR_BREAK(hd, COMGETTER(Id)(id.asOutParam()));
+ CHECK_ERROR_BREAK(virtualBox, UnregisterHardDisk(id, hd.asOutParam()));
+ }
+ while (FALSE);
+ RTPrintf("\n");
+#endif
+
+#if 0
+ // clone the registered hard disk
+ ///////////////////////////////////////////////////////////////////////////
+ do
+ {
+#if defined RT_OS_LINUX
+ Bstr src = L"/mnt/hugaida/common/develop/innotek/images/freedos-linux.vdi";
+#else
+ Bstr src = L"E:/develop/innotek/images/freedos.vdi";
+#endif
+ Bstr dst = L"./clone.vdi";
+ RTPrintf("Cloning '%ls' to '%ls'...\n", src.raw(), dst.raw());
+ ComPtr<IVirtualDiskImage> vdi;
+ CHECK_ERROR_BREAK(virtualBox, FindVirtualDiskImage(src, vdi.asOutParam()));
+ ComPtr<IHardDisk> hd = vdi;
+ ComPtr<IProgress> progress;
+ CHECK_ERROR_BREAK(hd, CloneToImage(dst, vdi.asOutParam(), progress.asOutParam()));
+ RTPrintf("Waiting for completion...\n");
+ CHECK_ERROR_BREAK(progress, WaitForCompletion(-1));
+ ProgressErrorInfo ei(progress);
+ if (FAILED(ei.getResultCode()))
+ {
+ PRINT_ERROR_INFO(ei);
+ }
+ else
+ {
+ vdi->COMGETTER(FilePath)(dst.asOutParam());
+ RTPrintf("Actual clone path is '%ls'\n", dst.raw());
+ }
+ }
+ while (FALSE);
+ RTPrintf("\n");
+#endif
+
+#if 0
+ // find a registered hard disk by location and get properties
+ ///////////////////////////////////////////////////////////////////////////
+ do
+ {
+ ComPtr<IHardDisk> hd;
+ static const wchar_t *Names[] =
+ {
+#ifndef RT_OS_LINUX
+ L"freedos.vdi",
+ L"MS-DOS.vmdk",
+ L"iscsi",
+ L"some/path/and/disk.vdi",
+#else
+ L"xp.vdi",
+ L"Xp.vDI",
+#endif
+ };
+
+ RTPrintf("\n");
+
+ for (size_t i = 0; i < RT_ELEMENTS(Names); ++ i)
+ {
+ Bstr src = Names[i];
+ RTPrintf("Searching for hard disk '%ls'...\n", src.raw());
+ rc = virtualBox->FindHardDisk(src, hd.asOutParam());
+ if (SUCCEEDED(rc))
+ {
+ Guid id;
+ Bstr location;
+ CHECK_ERROR_BREAK(hd, COMGETTER(Id)(id.asOutParam()));
+ CHECK_ERROR_BREAK(hd, COMGETTER(Location)(location.asOutParam()));
+ RTPrintf("Found, UUID={%RTuuid}, location='%ls'.\n",
+ id.raw(), location.raw());
+
+ com::SafeArray<BSTR> names;
+ com::SafeArray<BSTR> values;
+
+ CHECK_ERROR_BREAK(hd, GetProperties(NULL,
+ ComSafeArrayAsOutParam(names),
+ ComSafeArrayAsOutParam(values)));
+
+ RTPrintf("Properties:\n");
+ for (size_t i = 0; i < names.size(); ++ i)
+ RTPrintf(" %ls = %ls\n", names[i], values[i]);
+
+ if (names.size() == 0)
+ RTPrintf(" <none>\n");
+
+#if 0
+ Bstr name("TargetAddress");
+ Bstr value = Utf8StrFmt("lalala (%llu)", RTTimeMilliTS());
+
+ RTPrintf("Settings property %ls to %ls...\n", name.raw(), value.raw());
+ CHECK_ERROR(hd, SetProperty(name, value));
+#endif
+ }
+ else
+ {
+ com::ErrorInfo info(virtualBox);
+ PRINT_ERROR_INFO(info);
+ }
+ RTPrintf("\n");
+ }
+ }
+ while (FALSE);
+ RTPrintf("\n");
+#endif
+
+#if 0
+ // access the machine in read-only mode
+ ///////////////////////////////////////////////////////////////////////////
+ do
+ {
+ ComPtr<IMachine> machine;
+ Bstr name = argc > 1 ? argv[1] : "dos";
+ RTPrintf("Getting a machine object named '%ls'...\n", name.raw());
+ CHECK_ERROR_BREAK(virtualBox, FindMachine(name, machine.asOutParam()));
+ RTPrintf("Accessing the machine in read-only mode:\n");
+ readAndChangeMachineSettings(machine);
+#if 0
+ if (argc != 2)
+ {
+ RTPrintf("Error: a string has to be supplied!\n");
+ }
+ else
+ {
+ Bstr secureLabel = argv[1];
+ machine->COMSETTER(ExtraData)(L"VBoxSDL/SecureLabel", secureLabel);
+ }
+#endif
+ }
+ while (0);
+ RTPrintf("\n");
+#endif
+
+#if 0
+ // create a new machine (w/o registering it)
+ ///////////////////////////////////////////////////////////////////////////
+ do
+ {
+ ComPtr<IMachine> machine;
+#if defined(RT_OS_LINUX)
+ Bstr baseDir = L"/tmp/vbox";
+#else
+ Bstr baseDir = L"C:\\vbox";
+#endif
+ Bstr name = L"machina";
+
+ RTPrintf("Creating a new machine object(base dir '%ls', name '%ls')...\n",
+ baseDir.raw(), name.raw());
+ CHECK_ERROR_BREAK(virtualBox, CreateMachine(name, L"", baseDir, L"",
+ false,
+ machine.asOutParam()));
+
+ RTPrintf("Getting name...\n");
+ CHECK_ERROR_BREAK(machine, COMGETTER(Name)(name.asOutParam()));
+ RTPrintf("Name: {%ls}\n", name.raw());
+
+ BOOL modified = FALSE;
+ RTPrintf("Are any settings modified?...\n");
+ CHECK_ERROR_BREAK(machine, COMGETTER(SettingsModified)(&modified));
+ RTPrintf("%s\n", modified ? "yes" : "no");
+
+ ASSERT_BREAK(modified == TRUE);
+
+ name = L"Kakaya prekrasnaya virtual'naya mashina!";
+ RTPrintf("Setting new name ({%ls})...\n", name.raw());
+ CHECK_ERROR_BREAK(machine, COMSETTER(Name)(name));
+
+ RTPrintf("Setting memory size to 111...\n");
+ CHECK_ERROR_BREAK(machine, COMSETTER(MemorySize)(111));
+
+ Bstr desc = L"This is an exemplary description.";
+ RTPrintf("Setting description to \"%ls\"...\n", desc.raw());
+ CHECK_ERROR_BREAK(machine, COMSETTER(Description)(desc));
+
+ ComPtr<IGuestOSType> guestOSType;
+ Bstr type = L"os2warp45";
+ CHECK_ERROR_BREAK(virtualBox, GetGuestOSType(type, guestOSType.asOutParam()));
+
+ RTPrintf("Saving new machine settings...\n");
+ CHECK_ERROR_BREAK(machine, SaveSettings());
+
+ RTPrintf("Accessing the newly created machine:\n");
+ readAndChangeMachineSettings(machine);
+ }
+ while (FALSE);
+ RTPrintf("\n");
+#endif
+
+#if 0
+ // enumerate host DVD drives
+ ///////////////////////////////////////////////////////////////////////////
+ do
+ {
+ ComPtr<IHost> host;
+ CHECK_RC_BREAK(virtualBox->COMGETTER(Host)(host.asOutParam()));
+
+ {
+ ComPtr<IHostDVDDriveCollection> coll;
+ CHECK_RC_BREAK(host->COMGETTER(DVDDrives)(coll.asOutParam()));
+ ComPtr<IHostDVDDriveEnumerator> enumerator;
+ CHECK_RC_BREAK(coll->Enumerate(enumerator.asOutParam()));
+ BOOL hasmore;
+ while (SUCCEEDED(enumerator->HasMore(&hasmore)) && hasmore)
+ {
+ ComPtr<IHostDVDDrive> drive;
+ CHECK_RC_BREAK(enumerator->GetNext(drive.asOutParam()));
+ Bstr name;
+ CHECK_RC_BREAK(drive->COMGETTER(Name)(name.asOutParam()));
+ RTPrintf("Host DVD drive: name={%ls}\n", name.raw());
+ }
+ CHECK_RC_BREAK(rc);
+
+ ComPtr<IHostDVDDrive> drive;
+ CHECK_ERROR(enumerator, GetNext(drive.asOutParam()));
+ CHECK_ERROR(coll, GetItemAt(1000, drive.asOutParam()));
+ CHECK_ERROR(coll, FindByName(Bstr("R:"), drive.asOutParam()));
+ if (SUCCEEDED(rc))
+ {
+ Bstr name;
+ CHECK_RC_BREAK(drive->COMGETTER(Name)(name.asOutParam()));
+ RTPrintf("Found by name: name={%ls}\n", name.raw());
+ }
+ }
+ }
+ while (FALSE);
+ RTPrintf("\n");
+#endif
+
+#if 0
+ // check for available hd backends
+ ///////////////////////////////////////////////////////////////////////////
+ {
+ RTPrintf("Supported hard disk backends: --------------------------\n");
+ ComPtr<ISystemProperties> systemProperties;
+ CHECK_ERROR_BREAK(virtualBox,
+ COMGETTER(SystemProperties)(systemProperties.asOutParam()));
+ com::SafeIfaceArray<IHardDiskFormat> hardDiskFormats;
+ CHECK_ERROR_BREAK(systemProperties,
+ COMGETTER(HardDiskFormats)(ComSafeArrayAsOutParam(hardDiskFormats)));
+
+ for (size_t i = 0; i < hardDiskFormats.size(); ++ i)
+ {
+ /* General information */
+ Bstr id;
+ CHECK_ERROR_BREAK(hardDiskFormats[i],
+ COMGETTER(Id)(id.asOutParam()));
+
+ Bstr description;
+ CHECK_ERROR_BREAK(hardDiskFormats[i],
+ COMGETTER(Id)(description.asOutParam()));
+
+ ULONG caps;
+ CHECK_ERROR_BREAK(hardDiskFormats[i],
+ COMGETTER(Capabilities)(&caps));
+
+ RTPrintf("Backend %u: id='%ls' description='%ls' capabilities=%#06x extensions='",
+ i, id.raw(), description.raw(), caps);
+
+ /* File extensions */
+ com::SafeArray<BSTR> fileExtensions;
+ CHECK_ERROR_BREAK(hardDiskFormats[i],
+ COMGETTER(FileExtensions)(ComSafeArrayAsOutParam(fileExtensions)));
+ for (size_t a = 0; a < fileExtensions.size(); ++ a)
+ {
+ RTPrintf("%ls", Bstr(fileExtensions[a]).raw());
+ if (a != fileExtensions.size()-1)
+ RTPrintf(",");
+ }
+ RTPrintf("'");
+
+ /* Configuration keys */
+ com::SafeArray<BSTR> propertyNames;
+ com::SafeArray<BSTR> propertyDescriptions;
+ com::SafeArray<ULONG> propertyTypes;
+ com::SafeArray<ULONG> propertyFlags;
+ com::SafeArray<BSTR> propertyDefaults;
+ CHECK_ERROR_BREAK(hardDiskFormats[i],
+ DescribeProperties(ComSafeArrayAsOutParam(propertyNames),
+ ComSafeArrayAsOutParam(propertyDescriptions),
+ ComSafeArrayAsOutParam(propertyTypes),
+ ComSafeArrayAsOutParam(propertyFlags),
+ ComSafeArrayAsOutParam(propertyDefaults)));
+
+ RTPrintf(" config=(");
+ if (propertyNames.size() > 0)
+ {
+ for (size_t a = 0; a < propertyNames.size(); ++ a)
+ {
+ RTPrintf("key='%ls' desc='%ls' type=", Bstr(propertyNames[a]).raw(), Bstr(propertyDescriptions[a]).raw());
+ switch (propertyTypes[a])
+ {
+ case DataType_Int32Type: RTPrintf("int"); break;
+ case DataType_Int8Type: RTPrintf("byte"); break;
+ case DataType_StringType: RTPrintf("string"); break;
+ }
+ RTPrintf(" flags=%#04x", propertyFlags[a]);
+ RTPrintf(" default='%ls'", Bstr(propertyDefaults[a]).raw());
+ if (a != propertyNames.size()-1)
+ RTPrintf(",");
+ }
+ }
+ RTPrintf(")\n");
+ }
+ RTPrintf("-------------------------------------------------------\n");
+ }
+#endif
+
+#if 0
+ // enumerate hard disks & dvd images
+ ///////////////////////////////////////////////////////////////////////////
+ do
+ {
+ {
+ com::SafeIfaceArray<IHardDisk> disks;
+ CHECK_ERROR_BREAK(virtualBox,
+ COMGETTER(HardDisks)(ComSafeArrayAsOutParam(disks)));
+
+ RTPrintf("%u base hard disks registered (disks.isNull()=%d).\n",
+ disks.size(), disks.isNull());
+
+ for (size_t i = 0; i < disks.size(); ++ i)
+ {
+ Bstr loc;
+ CHECK_ERROR_BREAK(disks[i], COMGETTER(Location)(loc.asOutParam()));
+ Guid id;
+ CHECK_ERROR_BREAK(disks[i], COMGETTER(Id)(id.asOutParam()));
+ MediaState_T state;
+ CHECK_ERROR_BREAK(disks[i], COMGETTER(State)(&state));
+ Bstr format;
+ CHECK_ERROR_BREAK(disks[i], COMGETTER(Format)(format.asOutParam()));
+
+ RTPrintf(" disks[%u]: '%ls'\n"
+ " UUID: {%RTuuid}\n"
+ " State: %s\n"
+ " Format: %ls\n",
+ i, loc.raw(), id.raw(),
+ state == MediaState_NotCreated ? "Not Created" :
+ state == MediaState_Created ? "Created" :
+ state == MediaState_Inaccessible ? "Inaccessible" :
+ state == MediaState_LockedRead ? "Locked Read" :
+ state == MediaState_LockedWrite ? "Locked Write" :
+ "???",
+ format.raw());
+
+ if (state == MediaState_Inaccessible)
+ {
+ Bstr error;
+ CHECK_ERROR_BREAK(disks[i],
+ COMGETTER(LastAccessError)(error.asOutParam()));
+ RTPrintf(" Access Error: %ls\n", error.raw());
+ }
+
+ /* get usage */
+
+ RTPrintf(" Used by VMs:\n");
+
+ com::SafeGUIDArray ids;
+ CHECK_ERROR_BREAK(disks[i],
+ COMGETTER(MachineIds)(ComSafeArrayAsOutParam(ids)));
+ if (ids.size() == 0)
+ {
+ RTPrintf(" <not used>\n");
+ }
+ else
+ {
+ for (size_t j = 0; j < ids.size(); ++ j)
+ {
+ RTPrintf(" {%RTuuid}\n", &ids[j]);
+ }
+ }
+ }
+ }
+ {
+ com::SafeIfaceArray<IDVDImage> images;
+ CHECK_ERROR_BREAK(virtualBox,
+ COMGETTER(DVDImages)(ComSafeArrayAsOutParam(images)));
+
+ RTPrintf("%u DVD images registered (images.isNull()=%d).\n",
+ images.size(), images.isNull());
+
+ for (size_t i = 0; i < images.size(); ++ i)
+ {
+ Bstr loc;
+ CHECK_ERROR_BREAK(images[i], COMGETTER(Location)(loc.asOutParam()));
+ Guid id;
+ CHECK_ERROR_BREAK(images[i], COMGETTER(Id)(id.asOutParam()));
+ MediaState_T state;
+ CHECK_ERROR_BREAK(images[i], COMGETTER(State)(&state));
+
+ RTPrintf(" images[%u]: '%ls'\n"
+ " UUID: {%RTuuid}\n"
+ " State: %s\n",
+ i, loc.raw(), id.raw(),
+ state == MediaState_NotCreated ? "Not Created" :
+ state == MediaState_Created ? "Created" :
+ state == MediaState_Inaccessible ? "Inaccessible" :
+ state == MediaState_LockedRead ? "Locked Read" :
+ state == MediaState_LockedWrite ? "Locked Write" :
+ "???");
+
+ if (state == MediaState_Inaccessible)
+ {
+ Bstr error;
+ CHECK_ERROR_BREAK(images[i],
+ COMGETTER(LastAccessError)(error.asOutParam()));
+ RTPrintf(" Access Error: %ls\n", error.raw());
+ }
+
+ /* get usage */
+
+ RTPrintf(" Used by VMs:\n");
+
+ com::SafeGUIDArray ids;
+ CHECK_ERROR_BREAK(images[i],
+ COMGETTER(MachineIds)(ComSafeArrayAsOutParam(ids)));
+ if (ids.size() == 0)
+ {
+ RTPrintf(" <not used>\n");
+ }
+ else
+ {
+ for (size_t j = 0; j < ids.size(); ++ j)
+ {
+ RTPrintf(" {%RTuuid}\n", &ids[j]);
+ }
+ }
+ }
+ }
+ }
+ while (FALSE);
+ RTPrintf("\n");
+#endif
+
+#if 0
+ // open a (direct) session
+ ///////////////////////////////////////////////////////////////////////////
+ do
+ {
+ ComPtr<IMachine> machine;
+ Bstr name = argc > 1 ? argv[1] : "dos";
+ RTPrintf("Getting a machine object named '%ls'...\n", name.raw());
+ CHECK_ERROR_BREAK(virtualBox, FindMachine(name, machine.asOutParam()));
+ Guid guid;
+ CHECK_RC_BREAK(machine->COMGETTER(Id)(guid.asOutParam()));
+ RTPrintf("Opening a session for this machine...\n");
+ CHECK_RC_BREAK(virtualBox->OpenSession(session, guid));
+#if 1
+ ComPtr<IMachine> sessionMachine;
+ RTPrintf("Getting machine session object...\n");
+ CHECK_RC_BREAK(session->COMGETTER(Machine)(sessionMachine.asOutParam()));
+ RTPrintf("Accessing the machine within the session:\n");
+ readAndChangeMachineSettings(sessionMachine, machine);
+#if 0
+ RTPrintf("\n");
+ RTPrintf("Enabling the VRDE server (must succeed even if the VM is saved):\n");
+ ComPtr<IVRDEServer> vrdeServer;
+ CHECK_ERROR_BREAK(sessionMachine, COMGETTER(VRDEServer)(vrdeServer.asOutParam()));
+ if (FAILED(vrdeServer->COMSETTER(Enabled)(TRUE)))
+ {
+ PRINT_ERROR_INFO(com::ErrorInfo(vrdeServer));
+ }
+ else
+ {
+ BOOL enabled = FALSE;
+ CHECK_ERROR_BREAK(vrdeServer, COMGETTER(Enabled)(&enabled));
+ RTPrintf("VRDE server is %s\n", enabled ? "enabled" : "disabled");
+ }
+#endif
+#endif
+#if 0
+ ComPtr<IConsole> console;
+ RTPrintf("Getting the console object...\n");
+ CHECK_RC_BREAK(session->COMGETTER(Console)(console.asOutParam()));
+ RTPrintf("Discarding the current machine state...\n");
+ ComPtr<IProgress> progress;
+ CHECK_ERROR_BREAK(console, DiscardCurrentState(progress.asOutParam()));
+ RTPrintf("Waiting for completion...\n");
+ CHECK_ERROR_BREAK(progress, WaitForCompletion(-1));
+ ProgressErrorInfo ei(progress);
+ if (FAILED(ei.getResultCode()))
+ {
+ PRINT_ERROR_INFO(ei);
+
+ ComPtr<IUnknown> initiator;
+ CHECK_ERROR_BREAK(progress, COMGETTER(Initiator)(initiator.asOutParam()));
+
+ RTPrintf("initiator(unk) = %p\n", (IUnknown *)initiator);
+ RTPrintf("console(unk) = %p\n", (IUnknown *)ComPtr<IUnknown>((IConsole *)console));
+ RTPrintf("console = %p\n", (IConsole *)console);
+ }
+#endif
+ RTPrintf("Press enter to close session...");
+ getchar();
+ session->Close();
+ }
+ while (FALSE);
+ RTPrintf("\n");
+#endif
+
+#if 0
+ // open a remote session
+ ///////////////////////////////////////////////////////////////////////////
+ do
+ {
+ ComPtr<IMachine> machine;
+ Bstr name = L"dos";
+ RTPrintf("Getting a machine object named '%ls'...\n", name.raw());
+ CHECK_RC_BREAK(virtualBox->FindMachine(name, machine.asOutParam()));
+ Guid guid;
+ CHECK_RC_BREAK(machine->COMGETTER(Id)(guid.asOutParam()));
+ RTPrintf("Opening a remote session for this machine...\n");
+ ComPtr<IProgress> progress;
+ CHECK_RC_BREAK(virtualBox->OpenRemoteSession(session, guid, Bstr("gui"),
+ NULL, progress.asOutParam()));
+ RTPrintf("Waiting for the session to open...\n");
+ CHECK_RC_BREAK(progress->WaitForCompletion(-1));
+ ComPtr<IMachine> sessionMachine;
+ RTPrintf("Getting machine session object...\n");
+ CHECK_RC_BREAK(session->COMGETTER(Machine)(sessionMachine.asOutParam()));
+ ComPtr<IConsole> console;
+ RTPrintf("Getting console object...\n");
+ CHECK_RC_BREAK(session->COMGETTER(Console)(console.asOutParam()));
+ RTPrintf("Press enter to pause the VM execution in the remote session...");
+ getchar();
+ CHECK_RC(console->Pause());
+ RTPrintf("Press enter to close this session...");
+ getchar();
+ session->Close();
+ }
+ while (FALSE);
+ RTPrintf("\n");
+#endif
+
+#if 0
+ // open an existing remote session
+ ///////////////////////////////////////////////////////////////////////////
+ do
+ {
+ ComPtr<IMachine> machine;
+ Bstr name = "dos";
+ RTPrintf("Getting a machine object named '%ls'...\n", name.raw());
+ CHECK_RC_BREAK(virtualBox->FindMachine(name, machine.asOutParam()));
+ Guid guid;
+ CHECK_RC_BREAK(machine->COMGETTER(Id)(guid.asOutParam()));
+ RTPrintf("Opening an existing remote session for this machine...\n");
+ CHECK_RC_BREAK(virtualBox->OpenExistingSession(session, guid));
+ ComPtr<IMachine> sessionMachine;
+ RTPrintf("Getting machine session object...\n");
+ CHECK_RC_BREAK(session->COMGETTER(Machine)(sessionMachine.asOutParam()));
+
+#if 0
+ Bstr extraDataKey = "VBoxSDL/SecureLabel";
+ Bstr extraData = "Das kommt jetzt noch viel krasser vom total konkreten API!";
+ CHECK_RC(sessionMachine->SetExtraData(extraDataKey, extraData));
+#endif
+#if 0
+ ComPtr<IConsole> console;
+ RTPrintf("Getting console object...\n");
+ CHECK_RC_BREAK(session->COMGETTER(Console)(console.asOutParam()));
+ RTPrintf("Press enter to pause the VM execution in the remote session...");
+ getchar();
+ CHECK_RC(console->Pause());
+ RTPrintf("Press enter to close this session...");
+ getchar();
+#endif
+ session->Close();
+ }
+ while (FALSE);
+ RTPrintf("\n");
+#endif
+
+#if 1
+ do {
+ // Get host
+ ComPtr<IHost> host;
+ CHECK_ERROR_BREAK(virtualBox, COMGETTER(Host)(host.asOutParam()));
+
+ ULONG uMemSize, uMemAvail;
+ CHECK_ERROR_BREAK(host, COMGETTER(MemorySize)(&uMemSize));
+ RTPrintf("Total memory (MB): %u\n", uMemSize);
+ CHECK_ERROR_BREAK(host, COMGETTER(MemoryAvailable)(&uMemAvail));
+ RTPrintf("Free memory (MB): %u\n", uMemAvail);
+ } while (0);
+#endif
+
+#if 0
+ do {
+ // Get host
+ ComPtr<IHost> host;
+ CHECK_ERROR_BREAK(virtualBox, COMGETTER(Host)(host.asOutParam()));
+
+ com::SafeIfaceArray<IHostNetworkInterface> hostNetworkInterfaces;
+ CHECK_ERROR_BREAK(host,
+ COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(hostNetworkInterfaces)));
+ if (hostNetworkInterfaces.size() > 0)
+ {
+ ComPtr<IHostNetworkInterface> networkInterface = hostNetworkInterfaces[0];
+ Bstr interfaceName;
+ networkInterface->COMGETTER(Name)(interfaceName.asOutParam());
+ RTPrintf("Found %d network interfaces, testing with %ls...\n", hostNetworkInterfaces.size(), interfaceName.raw());
+ Bstr interfaceGuid;
+ networkInterface->COMGETTER(Id)(interfaceGuid.asOutParam());
+ // Find the interface by its name
+ networkInterface.setNull();
+ CHECK_ERROR_BREAK(host,
+ FindHostNetworkInterfaceByName(interfaceName.raw(), networkInterface.asOutParam()));
+ Bstr interfaceGuid2;
+ networkInterface->COMGETTER(Id)(interfaceGuid2.asOutParam());
+ if (interfaceGuid2 != interfaceGuid)
+ RTPrintf("Failed to retrieve an interface by name %ls.\n", interfaceName.raw());
+ // Find the interface by its guid
+ networkInterface.setNull();
+ CHECK_ERROR_BREAK(host,
+ FindHostNetworkInterfaceById(interfaceGuid.raw(), networkInterface.asOutParam()));
+ Bstr interfaceName2;
+ networkInterface->COMGETTER(Name)(interfaceName2.asOutParam());
+ if (interfaceName != interfaceName2)
+ RTPrintf("Failed to retrieve an interface by GUID %ls.\n", interfaceGuid.raw());
+ }
+ else
+ {
+ RTPrintf("No network interfaces found!\n");
+ }
+ } while (0);
+#endif
+
+#if 0
+ // DNS & Co.
+ ///////////////////////////////////////////////////////////////////////////
+ /* XXX: Note it's endless loop */
+ do
+ {
+ ComPtr<IHost> host;
+ CHECK_ERROR_BREAK(virtualBox, COMGETTER(Host)(host.asOutParam()));
+
+ {
+ Bstr domainName;
+ CHECK_ERROR_BREAK(host,COMGETTER(DomainName)(domainName.asOutParam()));
+ RTPrintf("Domain name: %ls\n", domainName.raw());
+ }
+
+ com::SafeArray<BSTR> strs;
+ CHECK_ERROR_BREAK(host, COMGETTER(NameServers)(ComSafeArrayAsOutParam(strs)));
+
+ unsigned int i;
+ for (i = 0; i < strs.size(); ++i)
+ RTPrintf("Name server[%d]:%s\n", i, com::Utf8Str(strs[i]).c_str());
+
+ RTThreadSleep(1000);
+ }
+ while (1);
+ RTPrintf("\n");
+#endif
+
+
+#if 0 && defined(VBOX_WITH_RESOURCE_USAGE_API)
+ do {
+ // Get collector
+ ComPtr<IPerformanceCollector> collector;
+ CHECK_ERROR_BREAK(virtualBox,
+ COMGETTER(PerformanceCollector)(collector.asOutParam()));
+
+
+ // Fill base metrics array
+ Bstr baseMetricNames[] = { L"Net/eth0/Load" };
+ com::SafeArray<BSTR> baseMetrics(1);
+ baseMetricNames[0].cloneTo(&baseMetrics[0]);
+
+ // Get host
+ ComPtr<IHost> host;
+ CHECK_ERROR_BREAK(virtualBox, COMGETTER(Host)(host.asOutParam()));
+
+ // Get host network interfaces
+ // com::SafeIfaceArray<IHostNetworkInterface> hostNetworkInterfaces;
+ // CHECK_ERROR_BREAK(host,
+ // COMGETTER(NetworkInterfaces)(ComSafeArrayAsOutParam(hostNetworkInterfaces)));
+
+ // Setup base metrics
+ // Note that one needs to set up metrics after a session is open for a machine.
+ com::SafeIfaceArray<IPerformanceMetric> affectedMetrics;
+ com::SafeIfaceArray<IUnknown> objects(1);
+ host.queryInterfaceTo(&objects[0]);
+ CHECK_ERROR_BREAK(collector, SetupMetrics(ComSafeArrayAsInParam(baseMetrics),
+ ComSafeArrayAsInParam(objects), 1u, 10u,
+ ComSafeArrayAsOutParam(affectedMetrics)));
+ listAffectedMetrics(virtualBox,
+ ComSafeArrayAsInParam(affectedMetrics));
+ affectedMetrics.setNull();
+
+ RTPrintf("Sleeping for 5 seconds...\n");
+ RTThreadSleep(5000); // Sleep for 5 seconds
+
+ RTPrintf("\nMetrics collected: --------------------\n");
+ queryMetrics(virtualBox, collector, ComSafeArrayAsInParam(objects));
+ } while (false);
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+#if 0 && defined(VBOX_WITH_RESOURCE_USAGE_API)
+ do {
+ // Get collector
+ ComPtr<IPerformanceCollector> collector;
+ CHECK_ERROR_BREAK(virtualBox,
+ COMGETTER(PerformanceCollector)(collector.asOutParam()));
+
+
+ // Fill base metrics array
+ //Bstr baseMetricNames[] = { L"CPU/Load,RAM/Usage" };
+ Bstr baseMetricNames[] = { L"RAM/VMM" };
+ com::SafeArray<BSTR> baseMetrics(1);
+ baseMetricNames[0].cloneTo(&baseMetrics[0]);
+
+ // Get host
+ ComPtr<IHost> host;
+ CHECK_ERROR_BREAK(virtualBox, COMGETTER(Host)(host.asOutParam()));
+
+ // Get machine
+ ComPtr<IMachine> machine;
+ Bstr name = argc > 1 ? argv[1] : "dsl";
+ Bstr sessionType = argc > 2 ? argv[2] : "headless";
+ RTPrintf("Getting a machine object named '%ls'...\n", name.raw());
+ CHECK_ERROR_BREAK(virtualBox, FindMachine(name.raw(), machine.asOutParam()));
+
+ // Open session
+ ComPtr<IProgress> progress;
+ RTPrintf("Launching VM process...\n");
+ CHECK_ERROR_BREAK(machine, LaunchVMProcess(session, sessionType.raw(),
+ ComSafeArrayNullInParam(), progress.asOutParam()));
+ RTPrintf("Waiting for the VM to power on...\n");
+ CHECK_ERROR_BREAK(progress, WaitForCompletion(-1));
+
+ // ComPtr<IMachine> sessionMachine;
+ // RTPrintf("Getting machine session object...\n");
+ // CHECK_ERROR_BREAK(session, COMGETTER(Machine)(sessionMachine.asOutParam()));
+
+ // Setup base metrics
+ // Note that one needs to set up metrics after a session is open for a machine.
+ com::SafeIfaceArray<IPerformanceMetric> affectedMetrics;
+ com::SafeIfaceArray<IUnknown> objects(1);
+ host.queryInterfaceTo(&objects[0]);
+ //machine.queryInterfaceTo(&objects[1]);
+ CHECK_ERROR_BREAK(collector, SetupMetrics(ComSafeArrayAsInParam(baseMetrics),
+ ComSafeArrayAsInParam(objects), 1u, 10u,
+ ComSafeArrayAsOutParam(affectedMetrics)));
+ listAffectedMetrics(virtualBox,
+ ComSafeArrayAsInParam(affectedMetrics));
+ affectedMetrics.setNull();
+
+ // Get console
+ ComPtr<IConsole> console;
+ RTPrintf("Getting console object...\n");
+ CHECK_ERROR_BREAK(session, COMGETTER(Console)(console.asOutParam()));
+
+ RTThreadSleep(5000); // Sleep for 5 seconds
+
+ RTPrintf("\nMetrics collected with VM running: --------------------\n");
+ queryMetrics(virtualBox, collector, ComSafeArrayAsInParam(objects));
+
+ // Pause
+ //RTPrintf("Press enter to pause the VM execution in the remote session...");
+ //getchar();
+ CHECK_ERROR_BREAK(console, Pause());
+
+ RTThreadSleep(5000); // Sleep for 5 seconds
+
+ RTPrintf("\nMetrics collected with VM paused: ---------------------\n");
+ queryMetrics(virtualBox, collector, ComSafeArrayAsInParam(objects));
+
+ RTPrintf("\nDrop collected metrics: ----------------------------------------\n");
+ CHECK_ERROR_BREAK(collector,
+ SetupMetrics(ComSafeArrayAsInParam(baseMetrics),
+ ComSafeArrayAsInParam(objects),
+ 1u, 5u, ComSafeArrayAsOutParam(affectedMetrics)));
+ listAffectedMetrics(virtualBox,
+ ComSafeArrayAsInParam(affectedMetrics));
+ affectedMetrics.setNull();
+ queryMetrics(virtualBox, collector, ComSafeArrayAsInParam(objects));
+
+ com::SafeIfaceArray<IUnknown> vmObject(1);
+ machine.queryInterfaceTo(&vmObject[0]);
+
+ RTPrintf("\nDisable collection of VM metrics: ------------------------------\n");
+ CHECK_ERROR_BREAK(collector,
+ DisableMetrics(ComSafeArrayAsInParam(baseMetrics),
+ ComSafeArrayAsInParam(vmObject),
+ ComSafeArrayAsOutParam(affectedMetrics)));
+ listAffectedMetrics(virtualBox,
+ ComSafeArrayAsInParam(affectedMetrics));
+ affectedMetrics.setNull();
+ RTThreadSleep(5000); // Sleep for 5 seconds
+ queryMetrics(virtualBox, collector, ComSafeArrayAsInParam(objects));
+
+ RTPrintf("\nRe-enable collection of all metrics: ---------------------------\n");
+ CHECK_ERROR_BREAK(collector,
+ EnableMetrics(ComSafeArrayAsInParam(baseMetrics),
+ ComSafeArrayAsInParam(objects),
+ ComSafeArrayAsOutParam(affectedMetrics)));
+ listAffectedMetrics(virtualBox,
+ ComSafeArrayAsInParam(affectedMetrics));
+ affectedMetrics.setNull();
+ RTThreadSleep(5000); // Sleep for 5 seconds
+ queryMetrics(virtualBox, collector, ComSafeArrayAsInParam(objects));
+
+ // Power off
+ RTPrintf("Press enter to power off VM...");
+ getchar();
+ CHECK_ERROR_BREAK(console, PowerDown(progress.asOutParam()));
+ RTPrintf("Waiting for the VM to power down...\n");
+ CHECK_ERROR_BREAK(progress, WaitForCompletion(-1));
+ RTPrintf("Press enter to close this session...");
+ getchar();
+ session->UnlockMachine();
+ } while (false);
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+#if 0
+ // check of OVF appliance handling
+ ///////////////////////////////////////////////////////////////////////////
+ do
+ {
+ Bstr ovf = argc > 1 ? argv[1] : "someOVF.ovf";
+ RTPrintf("Try to open %ls ...\n", ovf.raw());
+
+ ComPtr<IAppliance> appliance;
+ CHECK_ERROR_BREAK(virtualBox,
+ CreateAppliance(appliance.asOutParam()));
+ CHECK_ERROR_BREAK(appliance, Read(ovf));
+ Bstr path;
+ CHECK_ERROR_BREAK(appliance, COMGETTER(Path)(path.asOutParam()));
+ RTPrintf("Successfully opened %ls.\n", path.raw());
+ CHECK_ERROR_BREAK(appliance, Interpret());
+ RTPrintf("Successfully interpreted %ls.\n", path.raw());
+ RTPrintf("Appliance:\n");
+ // Fetch all disks
+ com::SafeArray<BSTR> retDisks;
+ CHECK_ERROR_BREAK(appliance,
+ COMGETTER(Disks)(ComSafeArrayAsOutParam(retDisks)));
+ if (retDisks.size() > 0)
+ {
+ RTPrintf("Disks:");
+ for (unsigned i = 0; i < retDisks.size(); i++)
+ RTPrintf(" %ls", Bstr(retDisks[i]).raw());
+ RTPrintf("\n");
+ }
+ /* Fetch all virtual system descriptions */
+ com::SafeIfaceArray<IVirtualSystemDescription> retVSD;
+ CHECK_ERROR_BREAK(appliance,
+ COMGETTER(VirtualSystemDescriptions)(ComSafeArrayAsOutParam(retVSD)));
+ if (retVSD.size() > 0)
+ {
+ for (unsigned i = 0; i < retVSD.size(); ++i)
+ {
+ com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
+ com::SafeArray<BSTR> retRefValues;
+ com::SafeArray<BSTR> retOrigValues;
+ com::SafeArray<BSTR> retAutoValues;
+ com::SafeArray<BSTR> retConfiguration;
+ CHECK_ERROR_BREAK(retVSD[i],
+ GetDescription(ComSafeArrayAsOutParam(retTypes),
+ ComSafeArrayAsOutParam(retRefValues),
+ ComSafeArrayAsOutParam(retOrigValues),
+ ComSafeArrayAsOutParam(retAutoValues),
+ ComSafeArrayAsOutParam(retConfiguration)));
+
+ RTPrintf("VirtualSystemDescription:\n");
+ for (unsigned a = 0; a < retTypes.size(); ++a)
+ {
+ RTPrintf(" %d %ls %ls %ls\n",
+ retTypes[a],
+ Bstr(retOrigValues[a]).raw(),
+ Bstr(retAutoValues[a]).raw(),
+ Bstr(retConfiguration[a]).raw());
+ }
+ /* Show warnings from interpret */
+ com::SafeArray<BSTR> retWarnings;
+ CHECK_ERROR_BREAK(retVSD[i],
+ GetWarnings(ComSafeArrayAsOutParam(retWarnings)));
+ if (retWarnings.size() > 0)
+ {
+ RTPrintf("The following warnings occurs on interpret:\n");
+ for (unsigned r = 0; r < retWarnings.size(); ++r)
+ RTPrintf("%ls\n", Bstr(retWarnings[r]).raw());
+ RTPrintf("\n");
+ }
+ }
+ RTPrintf("\n");
+ }
+ RTPrintf("Try to import the appliance ...\n");
+ ComPtr<IProgress> progress;
+ CHECK_ERROR_BREAK(appliance,
+ ImportMachines(progress.asOutParam()));
+ CHECK_ERROR(progress, WaitForCompletion(-1));
+ if (SUCCEEDED(rc))
+ {
+ /* Check if the import was successfully */
+ progress->COMGETTER(ResultCode)(&rc);
+ if (FAILED(rc))
+ {
+ com::ProgressErrorInfo info(progress);
+ if (info.isBasicAvailable())
+ RTPrintf("Error: failed to import appliance. Error message: %ls\n", info.getText().raw());
+ else
+ RTPrintf("Error: failed to import appliance. No error message available!\n");
+ }
+ else
+ RTPrintf("Successfully imported the appliance.\n");
+ }
+
+ }
+ while (FALSE);
+ RTPrintf("\n");
+#endif
+#if 0
+ // check of network bandwidth control
+ ///////////////////////////////////////////////////////////////////////////
+ do
+ {
+ Bstr name = argc > 1 ? argv[1] : "ubuntu";
+ Bstr sessionType = argc > 2 ? argv[2] : "headless";
+ Bstr grpName = "tstAPI";
+ {
+ // Get machine
+ ComPtr<IMachine> machine;
+ ComPtr<IBandwidthControl> bwCtrl;
+ ComPtr<IBandwidthGroup> bwGroup;
+ ComPtr<INetworkAdapter> nic;
+ RTPrintf("Getting a machine object named '%ls'...\n", name.raw());
+ CHECK_ERROR_BREAK(virtualBox, FindMachine(name.raw(), machine.asOutParam()));
+ /* open a session for the VM (new or shared) */
+ CHECK_ERROR_BREAK(machine, LockMachine(session, LockType_Shared));
+ SessionType_T st;
+ CHECK_ERROR_BREAK(session, COMGETTER(Type)(&st));
+ bool fRunTime = (st == SessionType_Shared);
+ if (fRunTime)
+ {
+ RTPrintf("Machine %ls must not be running!\n");
+ break;
+ }
+ /* get the mutable session machine */
+ session->COMGETTER(Machine)(machine.asOutParam());
+ CHECK_ERROR_BREAK(machine, COMGETTER(BandwidthControl)(bwCtrl.asOutParam()));
+
+ RTPrintf("Creating bandwidth group named '%ls'...\n", grpName.raw());
+ CHECK_ERROR_BREAK(bwCtrl, CreateBandwidthGroup(grpName.raw(), BandwidthGroupType_Network, 123));
+
+
+ CHECK_ERROR_BREAK(bwCtrl, GetBandwidthGroup(grpName.raw(), bwGroup.asOutParam()));
+ CHECK_ERROR_BREAK(machine, GetNetworkAdapter(0, nic.asOutParam()));
+ RTPrintf("Assigning the group to the first network adapter...\n");
+ CHECK_ERROR_BREAK(nic, COMSETTER(BandwidthGroup)(bwGroup));
+ CHECK_ERROR_BREAK(machine, SaveSettings());
+ RTPrintf("Press enter to close this session...");
+ getchar();
+ session->UnlockMachine();
+ }
+ {
+ // Get machine
+ ComPtr<IMachine> machine;
+ ComPtr<IBandwidthControl> bwCtrl;
+ ComPtr<IBandwidthGroup> bwGroup;
+ Bstr grpNameReadFromNic;
+ ComPtr<INetworkAdapter> nic;
+ RTPrintf("Getting a machine object named '%ls'...\n", name.raw());
+ CHECK_ERROR_BREAK(virtualBox, FindMachine(name.raw(), machine.asOutParam()));
+ /* open a session for the VM (new or shared) */
+ CHECK_ERROR_BREAK(machine, LockMachine(session, LockType_Shared));
+ /* get the mutable session machine */
+ session->COMGETTER(Machine)(machine.asOutParam());
+ CHECK_ERROR_BREAK(machine, COMGETTER(BandwidthControl)(bwCtrl.asOutParam()));
+ CHECK_ERROR_BREAK(machine, GetNetworkAdapter(0, nic.asOutParam()));
+
+ RTPrintf("Reading the group back from the first network adapter...\n");
+ CHECK_ERROR_BREAK(nic, COMGETTER(BandwidthGroup)(bwGroup.asOutParam()));
+ if (bwGroup.isNull())
+ RTPrintf("Error: Bandwidth group is null at the first network adapter!\n");
+ else
+ {
+ CHECK_ERROR_BREAK(bwGroup, COMGETTER(Name)(grpNameReadFromNic.asOutParam()));
+ if (grpName != grpNameReadFromNic)
+ RTPrintf("Error: Bandwidth group names do not match (%ls != %ls)!\n", grpName.raw(), grpNameReadFromNic.raw());
+ else
+ RTPrintf("Successfully retrieved bandwidth group attribute from NIC (name=%ls)\n", grpNameReadFromNic.raw());
+ ComPtr<IBandwidthGroup> bwGroupEmpty;
+ RTPrintf("Assigning an empty group to the first network adapter...\n");
+ CHECK_ERROR_BREAK(nic, COMSETTER(BandwidthGroup)(bwGroupEmpty));
+ }
+ RTPrintf("Removing bandwidth group named '%ls'...\n", grpName.raw());
+ CHECK_ERROR_BREAK(bwCtrl, DeleteBandwidthGroup(grpName.raw()));
+ CHECK_ERROR_BREAK(machine, SaveSettings());
+ RTPrintf("Press enter to close this session...");
+ getchar();
+ session->UnlockMachine();
+ }
+ } while (FALSE);
+ RTPrintf("\n");
+#endif
+
+ RTPrintf("Press enter to release Session and VirtualBox instances...");
+ getchar();
+
+ // end "all-stuff" scope
+ ////////////////////////////////////////////////////////////////////////////
+ }
+ while (0);
+
+ RTPrintf("Press enter to shutdown COM...");
+ getchar();
+
+ com::Shutdown();
+
+ RTPrintf("tstAPI FINISHED.\n");
+
+ return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+#ifdef VBOX_WITH_RESOURCE_USAGE_API
+
+static void queryMetrics(ComPtr<IVirtualBox> aVirtualBox,
+ ComPtr<IPerformanceCollector> collector,
+ ComSafeArrayIn(IUnknown *, objects))
+{
+ HRESULT rc;
+
+ //Bstr metricNames[] = { L"CPU/Load/User:avg,CPU/Load/System:avg,CPU/Load/Idle:avg,RAM/Usage/Total,RAM/Usage/Used:avg" };
+ Bstr metricNames[] = { L"*" };
+ com::SafeArray<BSTR> metrics(1);
+ metricNames[0].cloneTo(&metrics[0]);
+ com::SafeArray<BSTR> retNames;
+ com::SafeIfaceArray<IUnknown> retObjects;
+ com::SafeArray<BSTR> retUnits;
+ com::SafeArray<ULONG> retScales;
+ com::SafeArray<ULONG> retSequenceNumbers;
+ com::SafeArray<ULONG> retIndices;
+ com::SafeArray<ULONG> retLengths;
+ com::SafeArray<LONG> retData;
+ CHECK_ERROR(collector, QueryMetricsData(ComSafeArrayAsInParam(metrics),
+ ComSafeArrayInArg(objects),
+ ComSafeArrayAsOutParam(retNames),
+ ComSafeArrayAsOutParam(retObjects),
+ ComSafeArrayAsOutParam(retUnits),
+ ComSafeArrayAsOutParam(retScales),
+ ComSafeArrayAsOutParam(retSequenceNumbers),
+ ComSafeArrayAsOutParam(retIndices),
+ ComSafeArrayAsOutParam(retLengths),
+ ComSafeArrayAsOutParam(retData)));
+ RTPrintf("Object Metric Values\n"
+ "---------- -------------------- --------------------------------------------\n");
+ for (unsigned i = 0; i < retNames.size(); i++)
+ {
+ Bstr metricUnit(retUnits[i]);
+ Bstr metricName(retNames[i]);
+ RTPrintf("%-10ls %-20ls ", getObjectName(aVirtualBox, retObjects[i]).raw(), metricName.raw());
+ const char *separator = "";
+ for (unsigned j = 0; j < retLengths[i]; j++)
+ {
+ if (retScales[i] == 1)
+ RTPrintf("%s%d %ls", separator, retData[retIndices[i] + j], metricUnit.raw());
+ else
+ RTPrintf("%s%d.%02d%ls", separator, retData[retIndices[i] + j] / retScales[i],
+ (retData[retIndices[i] + j] * 100 / retScales[i]) % 100, metricUnit.raw());
+ separator = ", ";
+ }
+ RTPrintf("\n");
+ }
+}
+
+static Bstr getObjectName(ComPtr<IVirtualBox> aVirtualBox,
+ ComPtr<IUnknown> aObject)
+{
+ HRESULT rc;
+
+ ComPtr<IHost> host = aObject;
+ if (!host.isNull())
+ return Bstr("host");
+
+ ComPtr<IMachine> machine = aObject;
+ if (!machine.isNull())
+ {
+ Bstr name;
+ CHECK_ERROR(machine, COMGETTER(Name)(name.asOutParam()));
+ if (SUCCEEDED(rc))
+ return name;
+ }
+ return Bstr("unknown");
+}
+
+static void listAffectedMetrics(ComPtr<IVirtualBox> aVirtualBox,
+ ComSafeArrayIn(IPerformanceMetric*, aMetrics))
+{
+ HRESULT rc;
+ com::SafeIfaceArray<IPerformanceMetric> metrics(ComSafeArrayInArg(aMetrics));
+ if (metrics.size())
+ {
+ ComPtr<IUnknown> object;
+ Bstr metricName;
+ RTPrintf("The following metrics were modified:\n\n"
+ "Object Metric\n"
+ "---------- --------------------\n");
+ for (size_t i = 0; i < metrics.size(); i++)
+ {
+ CHECK_ERROR(metrics[i], COMGETTER(Object)(object.asOutParam()));
+ CHECK_ERROR(metrics[i], COMGETTER(MetricName)(metricName.asOutParam()));
+ RTPrintf("%-10ls %-20ls\n",
+ getObjectName(aVirtualBox, object).raw(), metricName.raw());
+ }
+ RTPrintf("\n");
+ }
+ else
+ {
+ RTPrintf("No metrics match the specified filter!\n");
+ }
+}
+
+#endif /* VBOX_WITH_RESOURCE_USAGE_API */
+/* vim: set shiftwidth=4 tabstop=4 expandtab: */
diff --git a/src/VBox/Main/testcase/tstBstr.cpp b/src/VBox/Main/testcase/tstBstr.cpp
new file mode 100644
index 00000000..1b702758
--- /dev/null
+++ b/src/VBox/Main/testcase/tstBstr.cpp
@@ -0,0 +1,295 @@
+/* $Id: tstBstr.cpp $ */
+/** @file
+ * API Glue Testcase - Bstr.
+ */
+
+/*
+ * Copyright (C) 2019-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/com/string.h>
+
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+#include <iprt/uni.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define CHECK_BSTR(a_Expr, a_bstr, a_szExpected) \
+ do { \
+ a_Expr; \
+ size_t cchExpect = RTStrCalcUtf16Len(a_szExpected); \
+ if ((a_bstr).length() != cchExpect) \
+ RTTestFailed(hTest, "line %u: length() -> %zu, expected %zu (%ls vs %s)", \
+ __LINE__, (a_bstr).length(), cchExpect, (a_bstr).raw(), a_szExpected); \
+ else \
+ { \
+ int iDiff = (a_bstr).compareUtf8(a_szExpected); \
+ if (iDiff) \
+ RTTestFailed(hTest, "line %u: compareUtf8() -> %d: %ls vs %s", \
+ __LINE__, iDiff, (a_bstr).raw(), a_szExpected); \
+ } \
+ } while (0)
+
+
+
+static void testBstrPrintf(RTTEST hTest)
+{
+ RTTestSub(hTest, "Bstr::printf");
+
+ com::Bstr bstr1;
+ CHECK_BSTR(bstr1.printf(""), bstr1, "");
+ CHECK_BSTR(bstr1.printf("1234098694"), bstr1, "1234098694");
+ CHECK_BSTR(bstr1.printf("%u-%u-%u-%s-%u", 42, 999, 42, "asdkfhjasldfk0", 42),
+ bstr1, "42-999-42-asdkfhjasldfk0-42");
+
+ com::Bstr bstr2;
+ CHECK_BSTR(bstr2.printf("%ls-%ls-%ls-%ls-%ls-%ls-%ls-%ls-%ls-%ls::%ls-%ls-%ls-%ls-%ls-%ls-%ls-%ls-%ls-%ls::%ls-%ls-%ls-%ls-%ls-%ls-%ls-%ls-%ls-%ls",
+ bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(),
+ bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(),
+ bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw(), bstr1.raw()),
+ bstr2, "42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42::42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42::42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42");
+ CHECK_BSTR(bstr2.appendPrintf("-9999998888888777776666655554443322110!"),
+ bstr2, "42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42::42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42::42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-9999998888888777776666655554443322110!");
+ CHECK_BSTR(bstr2.appendPrintf("!"),
+ bstr2, "42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42::42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42::42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-42-999-42-asdkfhjasldfk0-42-9999998888888777776666655554443322110!!");
+}
+
+static void testBstrAppend(RTTEST hTest)
+{
+ RTTestSub(hTest, "Bstr::append");
+
+ /* C-string source: */
+ com::Bstr bstr1;
+ CHECK_BSTR(bstr1.append("1234"), bstr1, "1234");
+ CHECK_BSTR(bstr1.append("56"), bstr1, "123456");
+ CHECK_BSTR(bstr1.append("7"), bstr1, "1234567");
+ CHECK_BSTR(bstr1.append("89abcdefghijklmnopqrstuvwxyz"), bstr1, "123456789abcdefghijklmnopqrstuvwxyz");
+ CHECK_BSTR(bstr1.append("ABCDEFGHIJKLMNOPQRSTUVWXYZ"), bstr1, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ CHECK_BSTR(bstr1.append("123456789abcdefghijklmnopqrstuvwxyz"), bstr1, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz");
+ CHECK_BSTR(bstr1.append("_"), bstr1, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz_");
+ CHECK_BSTR(bstr1.append(""), bstr1, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz_");
+
+ com::Bstr bstr2;
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow(""), VINF_SUCCESS), bstr2, "");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow("1234"), VINF_SUCCESS), bstr2, "1234");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow("56"), VINF_SUCCESS), bstr2, "123456");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow("7"), VINF_SUCCESS), bstr2, "1234567");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow("89abcdefghijklmnopqrstuvwxyz"), VINF_SUCCESS), bstr2, "123456789abcdefghijklmnopqrstuvwxyz");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow("ABCDEFGHIJKLMNOPQRSTUVWXYZ"), VINF_SUCCESS), bstr2, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow("123456789abcdefghijklmnopqrstuvwxyz"), VINF_SUCCESS), bstr2, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow("_"), VINF_SUCCESS), bstr2, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz_");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow(""), VINF_SUCCESS), bstr2, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz_");
+
+ /* Bstr source: */
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow(bstr1), VINF_SUCCESS), bstr2, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz_123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz_");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr1.appendNoThrow(bstr2), VINF_SUCCESS), bstr1, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz_123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz_123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz_");
+
+ com::Bstr const bstrWord1("word");
+ CHECK_BSTR(bstr1.setNull(), bstr1, "");
+ CHECK_BSTR(bstr1.append(bstr2, 5, 10), bstr1, "6789abcdef");
+ CHECK_BSTR(bstr1.append(bstr2, 4096, 10), bstr1, "6789abcdef");
+ CHECK_BSTR(bstr1.append(bstrWord1, 1), bstr1, "6789abcdeford");
+ CHECK_BSTR(bstr1.append(bstrWord1, 1,1), bstr1, "6789abcdefordo");
+ CHECK_BSTR(bstr1.append(bstrWord1, 1,2), bstr1, "6789abcdefordoor");
+ CHECK_BSTR(bstr1.append(bstrWord1, 1,3), bstr1, "6789abcdefordoorord");
+ CHECK_BSTR(bstr1.append(bstrWord1, 1,4), bstr1, "6789abcdefordoorordord");
+ CHECK_BSTR(bstr1.append(bstrWord1, 3,1), bstr1, "6789abcdefordoorordordd");
+ CHECK_BSTR(bstr1.append(bstrWord1, 3,2), bstr1, "6789abcdefordoorordorddd");
+ CHECK_BSTR(bstr1.append(bstrWord1, 3), bstr1, "6789abcdefordoorordordddd");
+
+ com::Bstr bstr3;
+ CHECK_BSTR(bstr3.setNull(), bstr3, "");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(bstr2, 5, 10), VINF_SUCCESS), bstr3, "6789abcdef");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(bstr2, 4096, 10),VINF_SUCCESS), bstr3, "6789abcdef");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(bstrWord1, 1), VINF_SUCCESS), bstr3, "6789abcdeford");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(bstrWord1, 1,1), VINF_SUCCESS), bstr3, "6789abcdefordo");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(bstrWord1, 1,2), VINF_SUCCESS), bstr3, "6789abcdefordoor");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(bstrWord1, 1,3), VINF_SUCCESS), bstr3, "6789abcdefordoorord");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(bstrWord1, 1,4), VINF_SUCCESS), bstr3, "6789abcdefordoorordord");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(bstrWord1, 3,1), VINF_SUCCESS), bstr3, "6789abcdefordoorordordd");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(bstrWord1, 3,2), VINF_SUCCESS), bstr3, "6789abcdefordoorordorddd");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(bstrWord1, 3), VINF_SUCCESS), bstr3, "6789abcdefordoorordordddd");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(bstrWord1, 3), VINF_SUCCESS), bstr3, "6789abcdefordoorordorddddd");
+ com::Bstr const bstrWord2("-SEP-");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(bstrWord2, 0), VINF_SUCCESS), bstr3, "6789abcdefordoorordorddddd-SEP-");
+
+ /* CBSTR source: */
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(bstr1.raw()), VINF_SUCCESS), bstr3, "6789abcdefordoorordorddddd-SEP-6789abcdefordoorordordddd");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr1.appendNoThrow(bstr3.raw()), VINF_SUCCESS), bstr1, "6789abcdefordoorordordddd6789abcdefordoorordorddddd-SEP-6789abcdefordoorordordddd");
+
+ CBSTR const pwsz2 = bstr2.raw();
+ CBSTR const pwszWord1 = bstrWord1.raw();
+ CHECK_BSTR(bstr1.setNull(), bstr1, "");
+ CHECK_BSTR(bstr1.append(pwsz2 + 5, 10), bstr1, "6789abcdef");
+ CHECK_BSTR(bstr1.append(pwszWord1 + 1), bstr1, "6789abcdeford");
+ CHECK_BSTR(bstr1.append(pwszWord1 + 1, 1), bstr1, "6789abcdefordo");
+ CHECK_BSTR(bstr1.append(pwszWord1 + 1, 2), bstr1, "6789abcdefordoor");
+ CHECK_BSTR(bstr1.append(pwszWord1 + 1, 3), bstr1, "6789abcdefordoorord");
+ CHECK_BSTR(bstr1.append(pwszWord1 + 1, 4), bstr1, "6789abcdefordoorordord");
+ CHECK_BSTR(bstr1.append(pwszWord1 + 3, 1), bstr1, "6789abcdefordoorordordd");
+ CHECK_BSTR(bstr1.append(pwszWord1 + 3, 2), bstr1, "6789abcdefordoorordorddd");
+ CHECK_BSTR(bstr1.append(pwszWord1 + 3), bstr1, "6789abcdefordoorordordddd");
+
+ CHECK_BSTR(bstr3.setNull(), bstr3, "");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(pwsz2 + 5, 10), VINF_SUCCESS), bstr3, "6789abcdef");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(pwszWord1 + 1), VINF_SUCCESS), bstr3, "6789abcdeford");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(pwszWord1 + 1, 1), VINF_SUCCESS), bstr3, "6789abcdefordo");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(pwszWord1 + 1, 2), VINF_SUCCESS), bstr3, "6789abcdefordoor");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(pwszWord1 + 1, 3), VINF_SUCCESS), bstr3, "6789abcdefordoorord");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(pwszWord1 + 1, 4), VINF_SUCCESS), bstr3, "6789abcdefordoorordord");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(pwszWord1 + 3, 1), VINF_SUCCESS), bstr3, "6789abcdefordoorordordd");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(pwszWord1 + 3, 2), VINF_SUCCESS), bstr3, "6789abcdefordoorordorddd");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(pwszWord1 + 3), VINF_SUCCESS), bstr3, "6789abcdefordoorordordddd");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(pwszWord1 + 3), VINF_SUCCESS), bstr3, "6789abcdefordoorordorddddd");
+
+ /* RTCString source: */
+ bstr1.setNull();
+ CHECK_BSTR(bstr1.append(RTCString("1234")), bstr1, "1234");
+ CHECK_BSTR(bstr1.append(RTCString("56")), bstr1, "123456");
+ CHECK_BSTR(bstr1.append(RTCString("7")), bstr1, "1234567");
+ CHECK_BSTR(bstr1.append(RTCString("89abcdefghijklmnopqrstuvwxyz")), bstr1, "123456789abcdefghijklmnopqrstuvwxyz");
+ CHECK_BSTR(bstr1.append(RTCString("ABCDEFGHIJKLMNOPQRSTUVWXYZ")), bstr1, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ CHECK_BSTR(bstr1.append(RTCString("123456789abcdefghijklmnopqrstuvwxyz")), bstr1, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz");
+ CHECK_BSTR(bstr1.append(RTCString("_")), bstr1, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz_");
+ CHECK_BSTR(bstr1.append(RTCString()), bstr1, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz_");
+
+ bstr2.setNull();
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow(RTCString("")), VINF_SUCCESS), bstr2, "");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow(RTCString("1234")), VINF_SUCCESS), bstr2, "1234");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow(RTCString("56")), VINF_SUCCESS), bstr2, "123456");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow(RTCString("7")), VINF_SUCCESS), bstr2, "1234567");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow(RTCString("89abcdefghijklmnopqrstuvwxyz")), VINF_SUCCESS), bstr2, "123456789abcdefghijklmnopqrstuvwxyz");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow(RTCString("ABCDEFGHIJKLMNOPQRSTUVWXYZ")), VINF_SUCCESS), bstr2, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow(RTCString("123456789abcdefghijklmnopqrstuvwxyz")), VINF_SUCCESS), bstr2, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow(RTCString("_")), VINF_SUCCESS), bstr2, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz_");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr2.appendNoThrow(RTCString("")), VINF_SUCCESS), bstr2, "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz_");
+
+ RTCString const strWord1 = "word";
+ CHECK_BSTR(bstr1.setNull(), bstr1, "");
+ CHECK_BSTR(bstr1.append(com::Utf8Str(bstr2), 5, 10), bstr1, "6789abcdef");
+ CHECK_BSTR(bstr1.append(com::Utf8Str(bstr2), 4096, 10), bstr1, "6789abcdef");
+ CHECK_BSTR(bstr1.append(strWord1, 1), bstr1, "6789abcdeford");
+ CHECK_BSTR(bstr1.append(strWord1, 1,1), bstr1, "6789abcdefordo");
+ CHECK_BSTR(bstr1.append(strWord1, 1,2), bstr1, "6789abcdefordoor");
+ CHECK_BSTR(bstr1.append(strWord1, 1,3), bstr1, "6789abcdefordoorord");
+ CHECK_BSTR(bstr1.append(strWord1, 1,4), bstr1, "6789abcdefordoorordord");
+ CHECK_BSTR(bstr1.append(strWord1, 3,1), bstr1, "6789abcdefordoorordordd");
+ CHECK_BSTR(bstr1.append(strWord1, 3,2), bstr1, "6789abcdefordoorordorddd");
+ CHECK_BSTR(bstr1.append(strWord1, 3), bstr1, "6789abcdefordoorordordddd");
+
+ CHECK_BSTR(bstr3.setNull(), bstr3, "");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(com::Utf8Str(bstr2), 5, 10), VINF_SUCCESS), bstr3, "6789abcdef");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(com::Utf8Str(bstr2), 4096, 10),VINF_SUCCESS), bstr3, "6789abcdef");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(strWord1, 1), VINF_SUCCESS), bstr3, "6789abcdeford");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(strWord1, 1,1), VINF_SUCCESS), bstr3, "6789abcdefordo");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(strWord1, 1,2), VINF_SUCCESS), bstr3, "6789abcdefordoor");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(strWord1, 1,3), VINF_SUCCESS), bstr3, "6789abcdefordoorord");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(strWord1, 1,4), VINF_SUCCESS), bstr3, "6789abcdefordoorordord");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(strWord1, 3,1), VINF_SUCCESS), bstr3, "6789abcdefordoorordordd");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(strWord1, 3,2), VINF_SUCCESS), bstr3, "6789abcdefordoorordorddd");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(strWord1, 3), VINF_SUCCESS), bstr3, "6789abcdefordoorordordddd");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow(strWord1, 3), VINF_SUCCESS), bstr3, "6789abcdefordoorordorddddd");
+
+ /* char: */
+ CHECK_BSTR(bstr1.setNull(), bstr1, "");
+ CHECK_BSTR(bstr1.append('-'), bstr1, "-");
+ CHECK_BSTR(bstr1.append('a'), bstr1, "-a");
+ CHECK_BSTR(bstr1.append('b'), bstr1, "-ab");
+ CHECK_BSTR(bstr1.append('Z'), bstr1, "-abZ");
+ CHECK_BSTR(bstr1.append('-'), bstr1, "-abZ-");
+
+ CHECK_BSTR(bstr3.setNull(), bstr3, "");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow('-'), VINF_SUCCESS), bstr3, "-");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow('a'), VINF_SUCCESS), bstr3, "-a");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow('b'), VINF_SUCCESS), bstr3, "-ab");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow('Z'), VINF_SUCCESS), bstr3, "-abZ");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendNoThrow('-'), VINF_SUCCESS), bstr3, "-abZ-");
+
+ /* unicode codepoint: */
+ CHECK_BSTR(bstr1.setNull(), bstr1, "");
+ CHECK_BSTR(bstr1.appendCodePoint('-'), bstr1, "-");
+ CHECK_BSTR(bstr1.appendCodePoint('a'), bstr1, "-a");
+ CHECK_BSTR(bstr1.appendCodePoint('b'), bstr1, "-ab");
+ CHECK_BSTR(bstr1.appendCodePoint('Z'), bstr1, "-abZ");
+ CHECK_BSTR(bstr1.appendCodePoint('-'), bstr1, "-abZ-");
+ CHECK_BSTR(bstr1.appendCodePoint(0x39f), bstr1, "-abZ-\xce\x9f");
+ CHECK_BSTR(bstr1.appendCodePoint(0x1f50), bstr1, "-abZ-\xce\x9f\xe1\xbd\x90");
+ CHECK_BSTR(bstr1.appendCodePoint(0x3c7), bstr1, "-abZ-\xce\x9f\xe1\xbd\x90\xcf\x87");
+ CHECK_BSTR(bstr1.appendCodePoint(0x1f76), bstr1, "-abZ-\xce\x9f\xe1\xbd\x90\xcf\x87\xe1\xbd\xb6");
+
+ CHECK_BSTR(bstr3.setNull(), bstr3, "");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendCodePointNoThrow('-'), VINF_SUCCESS), bstr3, "-");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendCodePointNoThrow('a'), VINF_SUCCESS), bstr3, "-a");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendCodePointNoThrow('b'), VINF_SUCCESS), bstr3, "-ab");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendCodePointNoThrow('Z'), VINF_SUCCESS), bstr3, "-abZ");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendCodePointNoThrow('-'), VINF_SUCCESS), bstr3, "-abZ-");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendCodePointNoThrow(0x39f), VINF_SUCCESS), bstr3, "-abZ-\xce\x9f");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendCodePointNoThrow(0x1f50), VINF_SUCCESS), bstr3, "-abZ-\xce\x9f\xe1\xbd\x90");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendCodePointNoThrow(0x3c7), VINF_SUCCESS), bstr3, "-abZ-\xce\x9f\xe1\xbd\x90\xcf\x87");
+ CHECK_BSTR(RTTESTI_CHECK_RC(bstr3.appendCodePointNoThrow(0x1f76), VINF_SUCCESS), bstr3, "-abZ-\xce\x9f\xe1\xbd\x90\xcf\x87\xe1\xbd\xb6");
+}
+
+
+static void testBstrErase(RTTEST hTest)
+{
+ RTTestSub(hTest, "Bstr::erase");
+
+ com::Bstr bstr1;
+ CHECK_BSTR(bstr1.erase(), bstr1, "");
+ CHECK_BSTR(bstr1.erase(99), bstr1, "");
+ CHECK_BSTR(bstr1.erase(99,999), bstr1, "");
+
+ CHECK_BSTR(bstr1 = "asdlfjhasldfjhaldfhjalhjsdf", bstr1, "asdlfjhasldfjhaldfhjalhjsdf");
+ CHECK_BSTR(bstr1.erase(8, 9), bstr1, "asdlfjhafhjalhjsdf");
+ CHECK_BSTR(bstr1.erase(17, 20), bstr1, "asdlfjhafhjalhjsd");
+ CHECK_BSTR(bstr1.erase(16, 1), bstr1, "asdlfjhafhjalhjs");
+ CHECK_BSTR(bstr1.erase(15, 0), bstr1, "asdlfjhafhjalhjs");
+ CHECK_BSTR(bstr1.erase(13, 3), bstr1, "asdlfjhafhjal");
+ CHECK_BSTR(bstr1.erase(3, 3), bstr1, "asdhafhjal");
+ CHECK_BSTR(bstr1.erase(), bstr1, "");
+}
+
+
+int main()
+{
+ RTTEST hTest;
+ RTEXITCODE rcExit = RTTestInitAndCreate("tstBstr", &hTest);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ RTTestBanner(hTest);
+
+ testBstrPrintf(hTest);
+ testBstrAppend(hTest);
+ testBstrErase(hTest);
+
+ rcExit = RTTestSummaryAndDestroy(hTest);
+ }
+ return rcExit;
+}
+
diff --git a/src/VBox/Main/testcase/tstCollector.cpp b/src/VBox/Main/testcase/tstCollector.cpp
new file mode 100644
index 00000000..4f42ec01
--- /dev/null
+++ b/src/VBox/Main/testcase/tstCollector.cpp
@@ -0,0 +1,588 @@
+/* $Id: tstCollector.cpp $ */
+/** @file
+ * VirtualBox Main - Performance collector classes test cases.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifdef RT_OS_DARWIN
+# include "../src-server/darwin/PerformanceDarwin.cpp"
+#endif
+#ifdef RT_OS_FREEBSD
+# include "../src-server/freebsd/PerformanceFreeBSD.cpp"
+#endif
+#ifdef RT_OS_LINUX
+# include "../src-server/linux/PerformanceLinux.cpp"
+#endif
+#ifdef RT_OS_OS2
+# include "../src-server/os2/PerformanceOS2.cpp"
+#endif
+#ifdef RT_OS_SOLARIS
+# include "../src-server/solaris/PerformanceSolaris.cpp"
+#endif
+#ifdef RT_OS_WINDOWS
+# define _WIN32_DCOM
+# include <iprt/win/objidl.h>
+# include <iprt/win/objbase.h>
+# include "../src-server/win/PerformanceWin.cpp"
+#endif
+
+#include <iprt/initterm.h>
+#include <iprt/stream.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/process.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#define RUN_TIME_MS 1000
+
+#define N_CALLS(n, fn) \
+ do {\
+ for (int call = 0; call < n; ++call) \
+ rc = collector->fn; \
+ if (RT_FAILURE(rc)) \
+ RTPrintf("tstCollector: "#fn" -> %Rrc\n", rc); \
+ } while (0)
+
+#define CALLS_PER_SECOND(fn, args) \
+ do { \
+ nCalls = 0; \
+ start = RTTimeMilliTS(); \
+ do { \
+ rc = collector->fn args; \
+ if (RT_FAILURE(rc)) \
+ break; \
+ ++nCalls; \
+ } while (RTTimeMilliTS() - start < RUN_TIME_MS); \
+ if (RT_FAILURE(rc)) \
+ RTPrintf("tstCollector: "#fn" -> %Rrc\n", rc); \
+ else \
+ RTPrintf("%70s -- %u calls per second\n", #fn, nCalls); \
+ } while (0)
+
+void shutdownProcessList(std::vector<RTPROCESS> const &rProcesses)
+{
+ for (size_t i = 0; i < rProcesses.size(); i++)
+ RTProcTerminate(rProcesses[i]);
+}
+
+void measurePerformance(pm::CollectorHAL *collector, const char *pszName, int cVMs)
+{
+
+ const char * const args[] = { pszName, "-child", NULL };
+ pm::CollectorHints hints;
+ std::vector<RTPROCESS> processes;
+
+ hints.collectHostCpuLoad();
+ hints.collectHostRamUsage();
+ /* Start fake VMs */
+ for (int i = 0; i < cVMs; ++i)
+ {
+ RTPROCESS pid;
+ int rc = RTProcCreate(pszName, args, RTENV_DEFAULT, 0, &pid);
+ if (RT_FAILURE(rc))
+ {
+ hints.getProcesses(processes);
+ shutdownProcessList(processes);
+
+ RTPrintf("tstCollector: RTProcCreate() -> %Rrc\n", rc);
+ return;
+ }
+ hints.collectProcessCpuLoad(pid);
+ hints.collectProcessRamUsage(pid);
+ }
+
+ hints.getProcesses(processes);
+ RTThreadSleep(30000); // Let children settle for half a minute
+
+ int rc;
+ ULONG tmp;
+ uint64_t tmp64;
+ uint64_t start;
+ unsigned int nCalls;
+ /* Pre-collect */
+ CALLS_PER_SECOND(preCollect, (hints, 0));
+ /* Host CPU load */
+ CALLS_PER_SECOND(getRawHostCpuLoad, (&tmp64, &tmp64, &tmp64));
+ /* Process CPU load */
+ CALLS_PER_SECOND(getRawProcessCpuLoad, (processes[nCalls % cVMs], &tmp64, &tmp64, &tmp64));
+ /* Host CPU speed */
+ CALLS_PER_SECOND(getHostCpuMHz, (&tmp));
+ /* Host RAM usage */
+ CALLS_PER_SECOND(getHostMemoryUsage, (&tmp, &tmp, &tmp));
+ /* Process RAM usage */
+ CALLS_PER_SECOND(getProcessMemoryUsage, (processes[nCalls % cVMs], &tmp));
+
+ start = RTTimeNanoTS();
+
+ int times;
+ for (times = 0; times < 100; times++)
+ {
+ /* Pre-collect */
+ N_CALLS(1, preCollect(hints, 0));
+ /* Host CPU load */
+ N_CALLS(1, getRawHostCpuLoad(&tmp64, &tmp64, &tmp64));
+ /* Host CPU speed */
+ N_CALLS(1, getHostCpuMHz(&tmp));
+ /* Host RAM usage */
+ N_CALLS(1, getHostMemoryUsage(&tmp, &tmp, &tmp));
+ /* Process CPU load */
+ N_CALLS(cVMs, getRawProcessCpuLoad(processes[call], &tmp64, &tmp64, &tmp64));
+ /* Process RAM usage */
+ N_CALLS(cVMs, getProcessMemoryUsage(processes[call], &tmp));
+ }
+ RTPrintf("\n%d VMs -- %u%% of CPU time\n", cVMs, (unsigned)((double)(RTTimeNanoTS() - start) / 10000000.0 / times));
+
+ /* Shut down fake VMs */
+ shutdownProcessList(processes);
+}
+
+#ifdef RT_OS_SOLARIS
+#define NETIFNAME "net0"
+#else
+#define NETIFNAME "eth0"
+#endif
+int testNetwork(pm::CollectorHAL *collector)
+{
+ pm::CollectorHints hints;
+ uint64_t hostRxStart, hostTxStart;
+ uint64_t hostRxStop, hostTxStop, speed = 125000000; /* Assume 1Gbit/s */
+
+ RTPrintf("tstCollector: TESTING - Network load, sleeping for 5 s...\n");
+
+ hostRxStart = hostTxStart = 0;
+ int rc = collector->preCollect(hints, 0);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: preCollect() -> %Rrc\n", rc);
+ return 1;
+ }
+ rc = collector->getRawHostNetworkLoad(NETIFNAME, &hostRxStart, &hostTxStart);
+ if (rc == VERR_NOT_IMPLEMENTED)
+ RTPrintf("tstCollector: getRawHostNetworkLoad() not implemented, skipping\n");
+ else
+ {
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getRawHostNetworkLoad() -> %Rrc\n", rc);
+ return 1;
+ }
+
+ RTThreadSleep(5000); // Sleep for five seconds
+
+ rc = collector->preCollect(hints, 0);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: preCollect() -> %Rrc\n", rc);
+ return 1;
+ }
+ hostRxStop = hostRxStart;
+ hostTxStop = hostTxStart;
+ rc = collector->getRawHostNetworkLoad(NETIFNAME, &hostRxStop, &hostTxStop);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getRawHostNetworkLoad() -> %Rrc\n", rc);
+ return 1;
+ }
+ RTPrintf("tstCollector: host network speed = %llu bytes/sec (%llu mbit/sec)\n",
+ speed, speed/(1000000/8));
+ RTPrintf("tstCollector: host network rx = %llu bytes/sec (%llu mbit/sec, %u.%u %%)\n",
+ (hostRxStop - hostRxStart)/5, (hostRxStop - hostRxStart)/(5000000/8),
+ (hostRxStop - hostRxStart) * 100 / (speed * 5),
+ (hostRxStop - hostRxStart) * 10000 / (speed * 5) % 100);
+ RTPrintf("tstCollector: host network tx = %llu bytes/sec (%llu mbit/sec, %u.%u %%)\n\n",
+ (hostTxStop - hostTxStart)/5, (hostTxStop - hostTxStart)/(5000000/8),
+ (hostTxStop - hostTxStart) * 100 / (speed * 5),
+ (hostTxStop - hostTxStart) * 10000 / (speed * 5) % 100);
+ }
+
+ return 0;
+}
+
+#define FSNAME "/"
+int testFsUsage(pm::CollectorHAL *collector)
+{
+ RTPrintf("tstCollector: TESTING - File system usage\n");
+
+ ULONG total, used, available;
+
+ int rc = collector->getHostFilesystemUsage(FSNAME, &total, &used, &available);
+ if (rc == VERR_NOT_IMPLEMENTED)
+ RTPrintf("tstCollector: getHostFilesystemUsage() not implemented, skipping\n");
+ else
+ {
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getHostFilesystemUsage() -> %Rrc\n", rc);
+ return 1;
+ }
+ RTPrintf("tstCollector: host root fs total = %lu MB\n", total);
+ RTPrintf("tstCollector: host root fs used = %lu MB\n", used);
+ RTPrintf("tstCollector: host root fs available = %lu MB\n\n", available);
+ }
+ return 0;
+}
+
+int testDisk(pm::CollectorHAL *collector)
+{
+ pm::CollectorHints hints;
+ uint64_t diskMsStart, totalMsStart;
+ uint64_t diskMsStop, totalMsStop;
+
+ pm::DiskList disksUsage, disksLoad;
+ int rc = collector->getDiskListByFs(FSNAME, disksUsage, disksLoad);
+ if (rc == VERR_NOT_IMPLEMENTED)
+ RTPrintf("tstCollector: getDiskListByFs() not implemented, skipping\n");
+ else
+ {
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getDiskListByFs(%s) -> %Rrc\n", FSNAME, rc);
+ return 1;
+ }
+ if (disksUsage.empty())
+ {
+ RTPrintf("tstCollector: getDiskListByFs(%s) returned empty usage list\n", FSNAME);
+ return 0;
+ }
+ if (disksLoad.empty())
+ {
+ RTPrintf("tstCollector: getDiskListByFs(%s) returned empty usage list\n", FSNAME);
+ return 0;
+ }
+
+ pm::DiskList::iterator it;
+ for (it = disksUsage.begin(); it != disksUsage.end(); ++it)
+ {
+ uint64_t diskSize = 0;
+ rc = collector->getHostDiskSize(it->c_str(), &diskSize);
+ RTPrintf("tstCollector: TESTING - Disk size (%s) = %llu\n", it->c_str(), diskSize);
+ if (rc == VERR_FILE_NOT_FOUND)
+ RTPrintf("tstCollector: getHostDiskSize(%s) returned VERR_FILE_NOT_FOUND\n", it->c_str());
+ else if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getHostDiskSize() -> %Rrc\n", rc);
+ return 1;
+ }
+ }
+
+ for (it = disksLoad.begin(); it != disksLoad.end(); ++it)
+ {
+ RTPrintf("tstCollector: TESTING - Disk utilization (%s), sleeping for 5 s...\n", it->c_str());
+
+ hints.collectHostCpuLoad();
+ rc = collector->preCollect(hints, 0);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: preCollect() -> %Rrc\n", rc);
+ return 1;
+ }
+ rc = collector->getRawHostDiskLoad(it->c_str(), &diskMsStart, &totalMsStart);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getRawHostDiskLoad() -> %Rrc\n", rc);
+ return 1;
+ }
+
+ RTThreadSleep(5000); // Sleep for five seconds
+
+ rc = collector->preCollect(hints, 0);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: preCollect() -> %Rrc\n", rc);
+ return 1;
+ }
+ rc = collector->getRawHostDiskLoad(it->c_str(), &diskMsStop, &totalMsStop);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getRawHostDiskLoad() -> %Rrc\n", rc);
+ return 1;
+ }
+ RTPrintf("tstCollector: host disk util = %llu msec (%u.%u %%), total = %llu msec\n\n",
+ (diskMsStop - diskMsStart),
+ (unsigned)((diskMsStop - diskMsStart) * 100 / (totalMsStop - totalMsStart)),
+ (unsigned)((diskMsStop - diskMsStart) * 10000 / (totalMsStop - totalMsStart) % 100),
+ totalMsStop - totalMsStart);
+ }
+ }
+
+ return 0;
+}
+
+
+
+int main(int argc, char *argv[])
+{
+ bool cpuTest, ramTest, netTest, diskTest, fsTest, perfTest;
+ cpuTest = ramTest = netTest = diskTest = fsTest = perfTest = false;
+ /*
+ * Initialize the VBox runtime without loading
+ * the support driver.
+ */
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: RTR3InitExe() -> %d\n", rc);
+ return 1;
+ }
+ if (argc > 1)
+ {
+ if (!strcmp(argv[1], "-child"))
+ {
+ /* We have spawned ourselves as a child process -- scratch the leg */
+ RTThreadSleep(1000000);
+ return 1;
+ }
+ for (int i = 1; i < argc; i++)
+ {
+ if (!strcmp(argv[i], "-cpu"))
+ cpuTest = true;
+ else if (!strcmp(argv[i], "-ram"))
+ ramTest = true;
+ else if (!strcmp(argv[i], "-net"))
+ netTest = true;
+ else if (!strcmp(argv[i], "-disk"))
+ diskTest = true;
+ else if (!strcmp(argv[i], "-fs"))
+ fsTest = true;
+ else if (!strcmp(argv[i], "-perf"))
+ perfTest = true;
+ else
+ {
+ RTPrintf("tstCollector: Unknown option: %s\n", argv[i]);
+ return 2;
+ }
+ }
+ }
+ else
+ cpuTest = ramTest = netTest = diskTest = fsTest = perfTest = true;
+
+#ifdef RT_OS_WINDOWS
+ HRESULT hRes = CoInitialize(NULL);
+ /*
+ * Need to initialize security to access performance enumerators.
+ */
+ hRes = CoInitializeSecurity(
+ NULL,
+ -1,
+ NULL,
+ NULL,
+ RPC_C_AUTHN_LEVEL_NONE,
+ RPC_C_IMP_LEVEL_IMPERSONATE,
+ NULL, EOAC_NONE, 0);
+#endif
+
+ pm::CollectorHAL *collector = pm::createHAL();
+ if (!collector)
+ {
+ RTPrintf("tstCollector: createMetricFactory() failed\n");
+ return 1;
+ }
+
+ pm::CollectorHints hints;
+ if (cpuTest)
+ {
+ hints.collectHostCpuLoad();
+ hints.collectProcessCpuLoad(RTProcSelf());
+ }
+ if (ramTest)
+ {
+ hints.collectHostRamUsage();
+ hints.collectProcessRamUsage(RTProcSelf());
+ }
+
+ uint64_t start;
+
+ uint64_t hostUserStart, hostKernelStart, hostIdleStart;
+ uint64_t hostUserStop, hostKernelStop, hostIdleStop, hostTotal;
+
+ uint64_t processUserStart, processKernelStart, processTotalStart;
+ uint64_t processUserStop, processKernelStop, processTotalStop;
+
+ rc = collector->preCollect(hints, 0);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: preCollect() -> %Rrc\n", rc);
+ return 1;
+ }
+ if (cpuTest)
+ {
+ RTPrintf("tstCollector: TESTING - CPU load, sleeping for 5 s...\n");
+
+ rc = collector->getRawHostCpuLoad(&hostUserStart, &hostKernelStart, &hostIdleStart);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getRawHostCpuLoad() -> %Rrc\n", rc);
+ return 1;
+ }
+ rc = collector->getRawProcessCpuLoad(RTProcSelf(), &processUserStart, &processKernelStart, &processTotalStart);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getRawProcessCpuLoad() -> %Rrc\n", rc);
+ return 1;
+ }
+
+ RTThreadSleep(5000); // Sleep for 5 seconds
+
+ rc = collector->preCollect(hints, 0);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: preCollect() -> %Rrc\n", rc);
+ return 1;
+ }
+ rc = collector->getRawHostCpuLoad(&hostUserStop, &hostKernelStop, &hostIdleStop);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getRawHostCpuLoad() -> %Rrc\n", rc);
+ return 1;
+ }
+ rc = collector->getRawProcessCpuLoad(RTProcSelf(), &processUserStop, &processKernelStop, &processTotalStop);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getRawProcessCpuLoad() -> %Rrc\n", rc);
+ return 1;
+ }
+ hostTotal = hostUserStop - hostUserStart
+ + hostKernelStop - hostKernelStart
+ + hostIdleStop - hostIdleStart;
+ RTPrintf("tstCollector: host cpu user = %u.%u %%\n",
+ (unsigned)((hostUserStop - hostUserStart) * 100 / hostTotal),
+ (unsigned)((hostUserStop - hostUserStart) * 10000 / hostTotal % 100));
+ RTPrintf("tstCollector: host cpu kernel = %u.%u %%\n",
+ (unsigned)((hostKernelStop - hostKernelStart) * 100 / hostTotal),
+ (unsigned)((hostKernelStop - hostKernelStart) * 10000 / hostTotal % 100));
+ RTPrintf("tstCollector: host cpu idle = %u.%u %%\n",
+ (unsigned)((hostIdleStop - hostIdleStart) * 100 / hostTotal),
+ (unsigned)((hostIdleStop - hostIdleStart) * 10000 / hostTotal % 100));
+ RTPrintf("tstCollector: process cpu user = %u.%u %%\n",
+ (unsigned)((processUserStop - processUserStart) * 100 / (processTotalStop - processTotalStart)),
+ (unsigned)((processUserStop - processUserStart) * 10000 / (processTotalStop - processTotalStart) % 100));
+ RTPrintf("tstCollector: process cpu kernel = %u.%u %%\n\n",
+ (unsigned)((processKernelStop - processKernelStart) * 100 / (processTotalStop - processTotalStart)),
+ (unsigned)((processKernelStop - processKernelStart) * 10000 / (processTotalStop - processTotalStart) % 100));
+
+ RTPrintf("tstCollector: TESTING - CPU load, looping for 5 s...\n");
+ rc = collector->preCollect(hints, 0);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: preCollect() -> %Rrc\n", rc);
+ return 1;
+ }
+ rc = collector->getRawHostCpuLoad(&hostUserStart, &hostKernelStart, &hostIdleStart);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getRawHostCpuLoad() -> %Rrc\n", rc);
+ return 1;
+ }
+ rc = collector->getRawProcessCpuLoad(RTProcSelf(), &processUserStart, &processKernelStart, &processTotalStart);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getRawProcessCpuLoad() -> %Rrc\n", rc);
+ return 1;
+ }
+ start = RTTimeMilliTS();
+ while (RTTimeMilliTS() - start < 5000)
+ ; // Loop for 5 seconds
+ rc = collector->preCollect(hints, 0);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: preCollect() -> %Rrc\n", rc);
+ return 1;
+ }
+ rc = collector->getRawHostCpuLoad(&hostUserStop, &hostKernelStop, &hostIdleStop);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getRawHostCpuLoad() -> %Rrc\n", rc);
+ return 1;
+ }
+ rc = collector->getRawProcessCpuLoad(RTProcSelf(), &processUserStop, &processKernelStop, &processTotalStop);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getRawProcessCpuLoad() -> %Rrc\n", rc);
+ return 1;
+ }
+ hostTotal = hostUserStop - hostUserStart
+ + hostKernelStop - hostKernelStart
+ + hostIdleStop - hostIdleStart;
+ RTPrintf("tstCollector: host cpu user = %u.%u %%\n",
+ (unsigned)((hostUserStop - hostUserStart) * 100 / hostTotal),
+ (unsigned)((hostUserStop - hostUserStart) * 10000 / hostTotal % 100));
+ RTPrintf("tstCollector: host cpu kernel = %u.%u %%\n",
+ (unsigned)((hostKernelStop - hostKernelStart) * 100 / hostTotal),
+ (unsigned)((hostKernelStop - hostKernelStart) * 10000 / hostTotal % 100));
+ RTPrintf("tstCollector: host cpu idle = %u.%u %%\n",
+ (unsigned)((hostIdleStop - hostIdleStart) * 100 / hostTotal),
+ (unsigned)((hostIdleStop - hostIdleStart) * 10000 / hostTotal % 100));
+ RTPrintf("tstCollector: process cpu user = %u.%u %%\n",
+ (unsigned)((processUserStop - processUserStart) * 100 / (processTotalStop - processTotalStart)),
+ (unsigned)((processUserStop - processUserStart) * 10000 / (processTotalStop - processTotalStart) % 100));
+ RTPrintf("tstCollector: process cpu kernel = %u.%u %%\n\n",
+ (unsigned)((processKernelStop - processKernelStart) * 100 / (processTotalStop - processTotalStart)),
+ (unsigned)((processKernelStop - processKernelStart) * 10000 / (processTotalStop - processTotalStart) % 100));
+ }
+
+ if (ramTest)
+ {
+ RTPrintf("tstCollector: TESTING - Memory usage\n");
+
+ ULONG total, used, available, processUsed;
+
+ rc = collector->getHostMemoryUsage(&total, &used, &available);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getHostMemoryUsage() -> %Rrc\n", rc);
+ return 1;
+ }
+ rc = collector->getProcessMemoryUsage(RTProcSelf(), &processUsed);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("tstCollector: getProcessMemoryUsage() -> %Rrc\n", rc);
+ return 1;
+ }
+ RTPrintf("tstCollector: host mem total = %lu kB\n", total);
+ RTPrintf("tstCollector: host mem used = %lu kB\n", used);
+ RTPrintf("tstCollector: host mem available = %lu kB\n", available);
+ RTPrintf("tstCollector: process mem used = %lu kB\n\n", processUsed);
+ }
+
+ if (netTest)
+ rc = testNetwork(collector);
+ if (fsTest)
+ rc = testFsUsage(collector);
+ if (diskTest)
+ rc = testDisk(collector);
+ if (perfTest)
+ {
+ RTPrintf("tstCollector: TESTING - Performance\n\n");
+
+ measurePerformance(collector, argv[0], 100);
+ }
+
+ delete collector;
+
+ RTPrintf("\ntstCollector FINISHED.\n");
+
+ return RTEXITCODE_SUCCESS;
+}
+
diff --git a/src/VBox/Main/testcase/tstGuestCtrlContextID.cpp b/src/VBox/Main/testcase/tstGuestCtrlContextID.cpp
new file mode 100644
index 00000000..f53e08f7
--- /dev/null
+++ b/src/VBox/Main/testcase/tstGuestCtrlContextID.cpp
@@ -0,0 +1,125 @@
+/* $Id: tstGuestCtrlContextID.cpp $ */
+/** @file
+ * Context ID makeup/extraction test cases.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_ENABLED
+#define LOG_GROUP LOG_GROUP_MAIN
+#include <VBox/log.h>
+
+#include "../include/GuestCtrlImplPrivate.h"
+
+using namespace com;
+
+#include <iprt/env.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/test.h>
+
+int main()
+{
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstGuestCtrlContextID", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+
+ RTTestIPrintf(RTTESTLVL_DEBUG, "Initializing COM...\n");
+ HRESULT hrc = com::Initialize();
+ if (FAILED(hrc))
+ {
+ RTTestFailed(hTest, "Failed to initialize COM (%Rhrc)!\n", hrc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ /* Don't let the assertions trigger here
+ * -- we rely on the return values in the test(s) below. */
+ RTAssertSetQuiet(true);
+
+#if 0
+ for (int t = 0; t < 4 && !RTTestErrorCount(hTest); t++)
+ {
+ uint32_t uSession = RTRandU32Ex(0, VBOX_GUESTCTRL_MAX_SESSIONS - 1);
+ uint32_t uFilter = VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(uSession);
+ RTTestIPrintf(RTTESTLVL_INFO, "Session: %RU32, Filter: %x\n", uSession, uFilter);
+
+ uint32_t uSession2 = RTRandU32Ex(0, VBOX_GUESTCTRL_MAX_SESSIONS - 1);
+ uint32_t uCID = VBOX_GUESTCTRL_CONTEXTID_MAKE(uSession2,
+ RTRandU32Ex(0, VBOX_GUESTCTRL_MAX_OBJECTS - 1),
+ RTRandU32Ex(0, VBOX_GUESTCTRL_MAX_CONTEXTS - 1));
+ RTTestIPrintf(RTTESTLVL_INFO, "CID: %x (Session: %d), Masked: %x\n",
+ uCID, VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(uCID), uCID & uFilter);
+ if ((uCID & uFilter) == uCID)
+ {
+ RTTestIPrintf(RTTESTLVL_INFO, "=========== Masking works: %x vs. %x\n",
+ uCID & uFilter, uFilter);
+ }
+ }
+#endif
+
+ uint32_t uContextMax = UINT32_MAX;
+ RTTestIPrintf(RTTESTLVL_DEBUG, "Max context is: %RU32\n", uContextMax);
+
+ /* Do 4048 tests total. */
+ for (int t = 0; t < 4048 && !RTTestErrorCount(hTest); t++)
+ {
+ /* VBOX_GUESTCTRL_MAX_* includes 0 as an object, so subtract one. */
+ uint32_t s = RTRandU32Ex(0, VBOX_GUESTCTRL_MAX_SESSIONS - 1);
+ uint32_t p = RTRandU32Ex(0, VBOX_GUESTCTRL_MAX_OBJECTS - 1);
+ uint32_t c = RTRandU32Ex(0, VBOX_GUESTCTRL_MAX_CONTEXTS - 1);
+
+ uint64_t uContextID = VBOX_GUESTCTRL_CONTEXTID_MAKE(s, p, c);
+ RTTestIPrintf(RTTESTLVL_DEBUG, "ContextID (%d,%d,%d) = %RU32\n", s, p, c, uContextID);
+ if (s != VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(uContextID))
+ {
+ RTTestFailed(hTest, "%d,%d,%d: Session is %d, expected %d -> %RU64\n",
+ s, p, c, VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(uContextID), s, uContextID);
+ }
+ else if (p != VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(uContextID))
+ {
+ RTTestFailed(hTest, "%d,%d,%d: Object is %d, expected %d -> %RU64\n",
+ s, p, c, VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(uContextID), p, uContextID);
+ }
+ if (c != VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(uContextID))
+ {
+ RTTestFailed(hTest, "%d,%d,%d: Count is %d, expected %d -> %RU64\n",
+ s, p, c, VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(uContextID), c, uContextID);
+ }
+ if (uContextID > UINT32_MAX)
+ {
+ RTTestFailed(hTest, "%d,%d,%d: Value overflow; does not fit anymore: %RU64\n",
+ s, p, c, uContextID);
+ }
+ }
+
+ RTTestIPrintf(RTTESTLVL_DEBUG, "Shutting down COM...\n");
+ com::Shutdown();
+
+ /*
+ * Summary.
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}
+
diff --git a/src/VBox/Main/testcase/tstGuestCtrlParseBuffer.cpp b/src/VBox/Main/testcase/tstGuestCtrlParseBuffer.cpp
new file mode 100644
index 00000000..1f4a50ce
--- /dev/null
+++ b/src/VBox/Main/testcase/tstGuestCtrlParseBuffer.cpp
@@ -0,0 +1,297 @@
+/* $Id: tstGuestCtrlParseBuffer.cpp $ */
+/** @file
+ * Output stream parsing test cases.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include "../include/GuestCtrlImplPrivate.h"
+
+using namespace com;
+
+#include <iprt/env.h>
+#include <iprt/test.h>
+#include <iprt/stream.h>
+
+#ifndef BYTE
+# define BYTE uint8_t
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define STR_SIZE(a_sz) RT_STR_TUPLE(a_sz)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct VBOXGUESTCTRL_BUFFER_VALUE
+{
+ char *pszValue;
+} VBOXGUESTCTRL_BUFFER_VALUE, *PVBOXGUESTCTRL_BUFFER_VALUE;
+typedef std::map< RTCString, VBOXGUESTCTRL_BUFFER_VALUE > GuestBufferMap;
+typedef std::map< RTCString, VBOXGUESTCTRL_BUFFER_VALUE >::iterator GuestBufferMapIter;
+typedef std::map< RTCString, VBOXGUESTCTRL_BUFFER_VALUE >::const_iterator GuestBufferMapIterConst;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+char szUnterm1[] = { 'a', 's', 'd', 'f' };
+char szUnterm2[] = { 'f', 'o', 'o', '3', '=', 'b', 'a', 'r', '3' };
+
+static struct
+{
+ const char *pbData;
+ size_t cbData;
+ uint32_t offStart;
+ uint32_t offAfter;
+ uint32_t cMapElements;
+ int iResult;
+} g_aTestBlocks[] =
+{
+ /*
+ * Single object parsing.
+ * An object is represented by one or multiple key=value pairs which are
+ * separated by a single "\0". If this termination is missing it will be assumed
+ * that we need to collect more data to do a successful parsing.
+ */
+ /* Invalid stuff. */
+ { NULL, 0, 0, 0, 0, VERR_INVALID_POINTER },
+ { NULL, 512, 0, 0, 0, VERR_INVALID_POINTER },
+ { "", 0, 0, 0, 0, VERR_INVALID_PARAMETER },
+ { "", 0, 0, 0, 0, VERR_INVALID_PARAMETER },
+ { "foo=bar1", 0, 0, 0, 0, VERR_INVALID_PARAMETER },
+ { "foo=bar2", 0, 50, 50, 0, VERR_INVALID_PARAMETER },
+ /* Empty buffers. */
+ { "", 1, 0, 1, 0, VINF_SUCCESS },
+ { "\0", 1, 0, 1, 0, VINF_SUCCESS },
+ /* Unterminated values (missing "\0"). */
+ { STR_SIZE("test1"), 0, 0, 0, VERR_MORE_DATA },
+ { STR_SIZE("test2="), 0, 0, 0, VERR_MORE_DATA },
+ { STR_SIZE("test3=test3"), 0, 0, 0, VERR_MORE_DATA },
+ { STR_SIZE("test4=test4\0t41"), 0, sizeof("test4=test4\0") - 1, 1, VERR_MORE_DATA },
+ { STR_SIZE("test5=test5\0t51=t51"), 0, sizeof("test5=test5\0") - 1, 1, VERR_MORE_DATA },
+ /* Next block unterminated. */
+ { STR_SIZE("t51=t51\0t52=t52\0\0t53=t53"), 0, sizeof("t51=t51\0t52=t52\0") - 1, 2, VINF_SUCCESS },
+ { STR_SIZE("test6=test6\0\0t61=t61"), 0, sizeof("test6=test6\0") - 1, 1, VINF_SUCCESS },
+ /* Good stuff. */
+ { STR_SIZE("test61=\0test611=test611\0"), 0, sizeof("test61=\0test611=test611\0") - 1, 2, VINF_SUCCESS },
+ { STR_SIZE("test7=test7\0\0"), 0, sizeof("test7=test7\0") - 1, 1, VINF_SUCCESS },
+ { STR_SIZE("test8=test8\0t81=t81\0\0"), 0, sizeof("test8=test8\0t81=t81\0") - 1, 2, VINF_SUCCESS },
+ /* Good stuff, but with a second block -- should be *not* taken into account since
+ * we're only interested in parsing/handling the first object. */
+ { STR_SIZE("t9=t9\0t91=t91\0\0t92=t92\0\0"), 0, sizeof("t9=t9\0t91=t91\0") - 1, 2, VINF_SUCCESS },
+ /* Nasty stuff. */
+ /* iso 8859-1 encoding (?) of 'aou' all with diaeresis '=f' and 'ao' with diaeresis. */
+ { STR_SIZE("\xe4\xf6\xfc=\x66\xe4\xf6\0\0"), 0, sizeof("\xe4\xf6\xfc=\x66\xe4\xf6\0") - 1, 1, VINF_SUCCESS },
+ /* Like above, but after the first '\0' it adds 'ooo=aaa' all letters with diaeresis. */
+ { STR_SIZE("\xe4\xf6\xfc=\x66\xe4\xf6\0\xf6\xf6\xf6=\xe4\xe4\xe4"),
+ 0, sizeof("\xe4\xf6\xfc=\x66\xe4\xf6\0") - 1, 1, VERR_MORE_DATA },
+ /* Some "real world" examples. */
+ { STR_SIZE("hdr_id=vbt_stat\0hdr_ver=1\0name=foo.txt\0\0"), 0, sizeof("hdr_id=vbt_stat\0hdr_ver=1\0name=foo.txt\0") - 1,
+ 3, VINF_SUCCESS }
+};
+
+static struct
+{
+ const char *pbData;
+ size_t cbData;
+ /** Number of data blocks retrieved. These are separated by "\0\0". */
+ uint32_t cBlocks;
+ /** Overall result when done parsing. */
+ int iResult;
+} const g_aTestStream[] =
+{
+ /* No blocks. */
+ { "\0\0\0\0", sizeof("\0\0\0\0"), 0, VERR_NO_DATA },
+ /* Good stuff. */
+ { "\0b1=b1\0\0", sizeof("\0b1=b1\0\0"), 1, VERR_NO_DATA },
+ { "b1=b1\0\0", sizeof("b1=b1\0\0"), 1, VERR_NO_DATA },
+ { "b1=b1\0b2=b2\0\0", sizeof("b1=b1\0b2=b2\0\0"), 1, VERR_NO_DATA },
+ { "b1=b1\0b2=b2\0\0\0", sizeof("b1=b1\0b2=b2\0\0\0"), 1, VERR_NO_DATA }
+};
+
+int manualTest(void)
+{
+ int rc = VINF_SUCCESS;
+ static struct
+ {
+ const char *pbData;
+ size_t cbData;
+ uint32_t offStart;
+ uint32_t offAfter;
+ uint32_t cMapElements;
+ int iResult;
+ } const s_aTest[] =
+ {
+ { "test5=test5\0t51=t51", sizeof("test5=test5\0t51=t51"), 0, sizeof("test5=test5\0") - 1, 1, VERR_MORE_DATA },
+ { "\0\0test5=test5\0t51=t51", sizeof("\0\0test5=test5\0t51=t51"), 0, sizeof("\0\0test5=test5\0") - 1, 1, VERR_MORE_DATA },
+ };
+
+ for (unsigned iTest = 0; iTest < RT_ELEMENTS(s_aTest); iTest++)
+ {
+ RTTestIPrintf(RTTESTLVL_DEBUG, "Manual test #%d\n", iTest);
+
+ GuestProcessStream stream;
+ rc = stream.AddData((BYTE *)s_aTest[iTest].pbData, s_aTest[iTest].cbData);
+
+ for (;;)
+ {
+ GuestProcessStreamBlock block;
+ rc = stream.ParseBlock(block);
+ RTTestIPrintf(RTTESTLVL_DEBUG, "\tReturned with rc=%Rrc, numItems=%ld\n",
+ rc, block.GetCount());
+
+ if (block.GetCount())
+ break;
+ }
+ }
+
+ return rc;
+}
+
+int main()
+{
+ RTTEST hTest;
+ RTEXITCODE rcExit = RTTestInitAndCreate("tstParseBuffer", &hTest);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ RTTestBanner(hTest);
+
+ RTTestIPrintf(RTTESTLVL_DEBUG, "Initializing COM...\n");
+ HRESULT hrc = com::Initialize();
+ if (FAILED(hrc))
+ {
+ RTTestFailed(hTest, "Failed to initialize COM (%Rhrc)!\n", hrc);
+ return RTEXITCODE_FAILURE;
+ }
+
+#ifdef DEBUG_andy
+ int rc = manualTest();
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+#endif
+
+ AssertCompile(sizeof("sizecheck") == 10);
+ AssertCompile(sizeof("off=rab") == 8);
+ AssertCompile(sizeof("off=rab\0\0") == 10);
+
+ RTTestSub(hTest, "Lines");
+ for (unsigned iTest = 0; iTest < RT_ELEMENTS(g_aTestBlocks); iTest++)
+ {
+ RTTestIPrintf(RTTESTLVL_DEBUG, "=> Test #%u\n", iTest);
+
+ GuestProcessStream stream;
+ if (RT_FAILURE(g_aTestBlocks[iTest].iResult))
+ RTTestDisableAssertions(hTest);
+ int iResult = stream.AddData((BYTE *)g_aTestBlocks[iTest].pbData, g_aTestBlocks[iTest].cbData);
+ if (RT_FAILURE(g_aTestBlocks[iTest].iResult))
+ RTTestRestoreAssertions(hTest);
+ if (RT_SUCCESS(iResult))
+ {
+ GuestProcessStreamBlock curBlock;
+ iResult = stream.ParseBlock(curBlock);
+ if (iResult != g_aTestBlocks[iTest].iResult)
+ RTTestFailed(hTest, "Block #%u: Returned %Rrc, expected %Rrc", iTest, iResult, g_aTestBlocks[iTest].iResult);
+ else if (stream.GetOffset() != g_aTestBlocks[iTest].offAfter)
+ RTTestFailed(hTest, "Block #%uOffset %zu wrong, expected %u\n",
+ iTest, stream.GetOffset(), g_aTestBlocks[iTest].offAfter);
+ else if (iResult == VERR_MORE_DATA)
+ RTTestIPrintf(RTTESTLVL_DEBUG, "\tMore data (Offset: %zu)\n", stream.GetOffset());
+
+ if (RT_SUCCESS(iResult) || iResult == VERR_MORE_DATA)
+ if (curBlock.GetCount() != g_aTestBlocks[iTest].cMapElements)
+ RTTestFailed(hTest, "Block #%u: Map has %u elements, expected %u\n",
+ iTest, curBlock.GetCount(), g_aTestBlocks[iTest].cMapElements);
+
+ /* There is remaining data left in the buffer (which needs to be merged
+ * with a following buffer) -- print it. */
+ size_t off = stream.GetOffset();
+ size_t cbToWrite = g_aTestBlocks[iTest].cbData - off;
+ if (cbToWrite)
+ {
+ RTTestIPrintf(RTTESTLVL_DEBUG, "\tRemaining (%u):\n", cbToWrite);
+
+ /* How to properly get the current RTTESTLVL (aka IPRT_TEST_MAX_LEVEL) here?
+ * Hack alert: Using RTEnvGet for now. */
+ if (!RTStrICmp(RTEnvGet("IPRT_TEST_MAX_LEVEL"), "debug"))
+ RTStrmWriteEx(g_pStdOut, &g_aTestBlocks[iTest].pbData[off], cbToWrite - 1, NULL);
+ }
+ }
+ }
+
+ RTTestSub(hTest, "Blocks");
+ for (unsigned iTest = 0; iTest < RT_ELEMENTS(g_aTestStream); iTest++)
+ {
+ RTTestIPrintf(RTTESTLVL_DEBUG, "=> Block test #%u\n", iTest);
+
+ GuestProcessStream stream;
+ int iResult = stream.AddData((BYTE*)g_aTestStream[iTest].pbData, g_aTestStream[iTest].cbData);
+ if (RT_SUCCESS(iResult))
+ {
+ uint32_t cBlocks = 0;
+ uint8_t uSafeCouunter = 0;
+ do
+ {
+ GuestProcessStreamBlock curBlock;
+ iResult = stream.ParseBlock(curBlock);
+ RTTestIPrintf(RTTESTLVL_DEBUG, "Block #%u: Returned with %Rrc", iTest, iResult);
+ if (RT_SUCCESS(iResult))
+ {
+ /* Only count block which have at least one pair. */
+ if (curBlock.GetCount())
+ cBlocks++;
+ }
+ if (uSafeCouunter++ > 32)
+ break;
+ } while (RT_SUCCESS(iResult));
+
+ if (iResult != g_aTestStream[iTest].iResult)
+ RTTestFailed(hTest, "Block #%uReturned %Rrc, expected %Rrc", iTest, iResult, g_aTestStream[iTest].iResult);
+ else if (cBlocks != g_aTestStream[iTest].cBlocks)
+ RTTestFailed(hTest, "Block #%uReturned %u blocks, expected %u", iTest, cBlocks, g_aTestStream[iTest].cBlocks);
+ }
+ else
+ RTTestFailed(hTest, "Block #%u: Adding data failed with %Rrc", iTest, iResult);
+ }
+
+ RTTestIPrintf(RTTESTLVL_DEBUG, "Shutting down COM...\n");
+ com::Shutdown();
+
+ /*
+ * Summary.
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}
+
diff --git a/src/VBox/Main/testcase/tstGuestCtrlPaths.cpp b/src/VBox/Main/testcase/tstGuestCtrlPaths.cpp
new file mode 100644
index 00000000..6cec555a
--- /dev/null
+++ b/src/VBox/Main/testcase/tstGuestCtrlPaths.cpp
@@ -0,0 +1,167 @@
+/* $Id */
+/** @file
+ * Guest Control path test cases.
+ */
+
+/*
+ * Copyright (C) 2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_ENABLED
+#define LOG_GROUP LOG_GROUP_MAIN
+#include <VBox/log.h>
+
+#include "../include/GuestCtrlImplPrivate.h"
+
+using namespace com;
+
+#include <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/test.h>
+
+
+DECLINLINE(void) tstPathBuildDestination(const Utf8Str &strSrcPath, PathStyle_T enmSrcPathStyle,
+ const Utf8Str &strDstPath, PathStyle_T enmDstPathStyle,
+ int rcExp, Utf8Str strPathExp)
+{
+ Utf8Str strDstPath2 = strDstPath;
+ int vrc = GuestPath::BuildDestinationPath(strSrcPath, enmSrcPathStyle, strDstPath2, enmDstPathStyle);
+ RTTESTI_CHECK_MSG_RETV(vrc == rcExp, ("Expected %Rrc, got %Rrc for '%s'\n", rcExp, vrc, strDstPath.c_str()));
+ RTTESTI_CHECK_MSG_RETV(strDstPath2 == strPathExp, ("Expected '%s', got '%s'\n", strPathExp.c_str(), strDstPath2.c_str()));
+}
+
+DECLINLINE(void) tstPathTranslate(Utf8Str strPath, PathStyle_T enmSrcPathStyle, PathStyle_T enmDstPathStyle, int rcExp, Utf8Str strPathExp)
+{
+ Utf8Str strPath2 = strPath;
+ int vrc = GuestPath::Translate(strPath2, enmSrcPathStyle, enmDstPathStyle);
+ RTTESTI_CHECK_MSG_RETV(vrc == rcExp, ("Expected %Rrc, got %Rrc for '%s'\n", rcExp, vrc, strPath.c_str()));
+ RTTESTI_CHECK_MSG_RETV(strPath2 == strPathExp, ("Expected '%s', got '%s'\n", strPathExp.c_str(), strPath2.c_str()));
+}
+
+int main()
+{
+ RTTEST hTest;
+ int vrc = RTTestInitAndCreate("tstGuestCtrlPaths", &hTest);
+ if (vrc)
+ return vrc;
+ RTTestBanner(hTest);
+
+ RTTestIPrintf(RTTESTLVL_DEBUG, "Initializing COM...\n");
+ HRESULT hrc = com::Initialize();
+ if (FAILED(hrc))
+ {
+ RTTestFailed(hTest, "Failed to initialize COM (%Rhrc)!\n", hrc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ /* Don't let the assertions trigger here
+ * -- we rely on the return values in the test(s) below. */
+ RTAssertSetQuiet(true);
+
+ /*
+ * Path translation testing.
+ */
+ tstPathTranslate("", PathStyle_DOS, PathStyle_DOS, VINF_SUCCESS, "");
+
+ tstPathTranslate("foo", PathStyle_DOS, PathStyle_DOS, VINF_SUCCESS, "foo");
+ tstPathTranslate("foo", PathStyle_UNIX, PathStyle_UNIX, VINF_SUCCESS, "foo");
+ tstPathTranslate("foo", PathStyle_DOS, PathStyle_UNIX, VINF_SUCCESS, "foo");
+ tstPathTranslate("foo", PathStyle_UNIX, PathStyle_UNIX, VINF_SUCCESS, "foo");
+
+ tstPathTranslate("foo\\bar", PathStyle_DOS, PathStyle_DOS, VINF_SUCCESS, "foo\\bar");
+ tstPathTranslate("foo/bar", PathStyle_UNIX, PathStyle_UNIX, VINF_SUCCESS, "foo/bar");
+
+ tstPathTranslate("foo\\bar\\", PathStyle_DOS, PathStyle_DOS, VINF_SUCCESS, "foo\\bar\\");
+ tstPathTranslate("foo/bar/", PathStyle_UNIX, PathStyle_UNIX, VINF_SUCCESS, "foo/bar/");
+ /* Actually also allowed on Windows. */
+ tstPathTranslate("foo/bar/", PathStyle_DOS, PathStyle_UNIX, VINF_SUCCESS, "foo/bar/");
+
+ tstPathTranslate("foo\\bar\\BAZ", PathStyle_DOS, PathStyle_DOS, VINF_SUCCESS, "foo\\bar\\BAZ");
+ tstPathTranslate("foo/bar/BAZ", PathStyle_UNIX, PathStyle_UNIX, VINF_SUCCESS, "foo/bar/BAZ");
+
+ tstPathTranslate("foo\\bar\\dir with space\\", PathStyle_DOS, PathStyle_UNIX, VINF_SUCCESS, "foo/bar/dir with space/");
+ tstPathTranslate("foo/bar/dir with space/", PathStyle_UNIX, PathStyle_UNIX, VINF_SUCCESS, "foo/bar/dir with space/");
+
+#if 0
+ /** Do a mapping of "\", which marks an escape sequence for paths on UNIX-y OSes to DOS-based OSes (like Windows),
+ * however, on DOS "\" is a path separator. See @bugref{21095} */
+ tstPathTranslate("foo/bar/dir_with_escape_sequence\\ space", PathStyle_UNIX, PathStyle_UNIX, VINF_SUCCESS, "foo/bar/dir_with_escape_sequence\\ space");
+ tstPathTranslate("foo/bar/dir_with_escape_sequence\\ space", PathStyle_UNIX, PathStyle_DOS, VINF_SUCCESS, "foo\\bar\\dir_with_escape_sequence space");
+ tstPathTranslate("foo/bar/1_dir_with_escape_sequence/the\\ space", PathStyle_UNIX, PathStyle_DOS, VINF_SUCCESS, "foo\\bar\\1_dir_with_escape_sequence\\the space");
+ tstPathTranslate("foo/bar/2_dir_with_escape_sequence/the\\ \\ space", PathStyle_UNIX, PathStyle_DOS, VINF_SUCCESS, "foo\\bar\\2_dir_with_escape_sequence\\the space");
+ tstPathTranslate("foo/bar/dir_with_escape_sequence/spaces at end\\ \\ ", PathStyle_UNIX, PathStyle_DOS, VINF_SUCCESS, "foo\\bar\\dir_with_escape_sequence\\spaces at end ");
+#endif
+
+ /* Filter out double slashes (cosmetic only). */
+ tstPathTranslate("\\\\", PathStyle_DOS, PathStyle_DOS, VINF_SUCCESS, "\\");
+ tstPathTranslate("foo\\\\bar\\", PathStyle_DOS, PathStyle_DOS, VINF_SUCCESS, "foo\\bar\\");
+
+ /* Mixed slashes. */
+ tstPathTranslate("\\\\foo/bar\\\\baz", PathStyle_UNIX, PathStyle_UNIX, VINF_SUCCESS, "\\\\foo/bar\\\\baz");
+#if 0 /** @todo Not clear what to expect here. */
+ tstPathTranslate("with spaces\\ foo/\\ bar", PathStyle_UNIX, PathStyle_DOS, VINF_SUCCESS, "with spaces foo\\ bar");
+#endif
+
+ /*
+ * Destination path building testing.
+ */
+ bool fQuiet = RTAssertSetQuiet(true);
+ bool fMayPanic = RTAssertSetMayPanic(false);
+ tstPathBuildDestination("", PathStyle_UNIX, "", PathStyle_UNIX, VERR_PATH_ZERO_LENGTH, "");
+ tstPathBuildDestination(".", PathStyle_UNIX, ".", PathStyle_UNIX, VINF_SUCCESS, ".");
+ tstPathBuildDestination("..", PathStyle_UNIX, "..", PathStyle_UNIX, VERR_INVALID_PARAMETER, "..");
+ tstPathBuildDestination("/tmp/", PathStyle_UNIX, "/root/../foo", PathStyle_UNIX, VERR_INVALID_PARAMETER, "/root/../foo");
+ /* ".." in actual file names are allowed. */
+ tstPathBuildDestination("/tmp/", PathStyle_UNIX, "/root/foo..bar", PathStyle_UNIX, VINF_SUCCESS, "/root/foo..bar");
+ /* Ditto for path names which consist of more than just "..". */
+ tstPathBuildDestination("/tmp/", PathStyle_UNIX, "/root/foo..bar/baz", PathStyle_UNIX, VINF_SUCCESS, "/root/foo..bar/baz");
+ tstPathBuildDestination("...", PathStyle_UNIX, "...", PathStyle_UNIX, VINF_SUCCESS, "...");
+ tstPathBuildDestination("foo", PathStyle_UNIX, "bar", PathStyle_UNIX, VINF_SUCCESS, "bar");
+ tstPathBuildDestination("foo/", PathStyle_UNIX, "bar/", PathStyle_UNIX, VINF_SUCCESS, "bar/");
+ tstPathBuildDestination("foo/", PathStyle_UNIX, "bar/baz", PathStyle_UNIX, VINF_SUCCESS, "bar/baz");
+ tstPathBuildDestination("foo/baz", PathStyle_UNIX, "bar/", PathStyle_UNIX, VINF_SUCCESS, "bar/baz");
+ tstPathBuildDestination("foo/baz", PathStyle_UNIX, "bar\\", PathStyle_DOS, VINF_SUCCESS, "bar\\baz");
+
+ tstPathBuildDestination("c:\\temp\\", PathStyle_DOS, "/tmp/", PathStyle_UNIX, VINF_SUCCESS, "/tmp/");
+ tstPathBuildDestination("c:\\TEMP\\", PathStyle_DOS, "/TmP/", PathStyle_UNIX, VINF_SUCCESS, "/TmP/");
+ tstPathBuildDestination("c:\\temp\\foo.txt", PathStyle_DOS, "/tmp/foo.txt", PathStyle_UNIX, VINF_SUCCESS, "/tmp/foo.txt");
+ tstPathBuildDestination("c:\\temp\\bar\\foo.txt", PathStyle_DOS, "/tmp/foo2.txt", PathStyle_UNIX, VINF_SUCCESS, "/tmp/foo2.txt");
+ tstPathBuildDestination("c:\\temp\\bar\\foo3.txt", PathStyle_DOS, "/tmp/", PathStyle_UNIX, VINF_SUCCESS, "/tmp/foo3.txt");
+
+ tstPathBuildDestination("/tmp/bar/", PathStyle_UNIX, "c:\\temp\\", PathStyle_DOS, VINF_SUCCESS, "c:\\temp\\");
+ tstPathBuildDestination("/tmp/BaR/", PathStyle_UNIX, "c:\\tEmP\\", PathStyle_DOS, VINF_SUCCESS, "c:\\tEmP\\");
+ tstPathBuildDestination("/tmp/foo.txt", PathStyle_UNIX, "c:\\temp\\foo.txt", PathStyle_DOS, VINF_SUCCESS, "c:\\temp\\foo.txt");
+ tstPathBuildDestination("/tmp/bar/foo.txt", PathStyle_UNIX, "c:\\temp\\foo2.txt", PathStyle_DOS, VINF_SUCCESS, "c:\\temp\\foo2.txt");
+ tstPathBuildDestination("/tmp/bar/foo3.txt", PathStyle_UNIX, "c:\\temp\\", PathStyle_DOS, VINF_SUCCESS, "c:\\temp\\foo3.txt");
+ RTAssertSetMayPanic(fMayPanic);
+ RTAssertSetQuiet(fQuiet);
+
+ RTTestIPrintf(RTTESTLVL_DEBUG, "Shutting down COM...\n");
+ com::Shutdown();
+
+ /*
+ * Summary.
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}
+
diff --git a/src/VBox/Main/testcase/tstGuid.cpp b/src/VBox/Main/testcase/tstGuid.cpp
new file mode 100644
index 00000000..4d662ce4
--- /dev/null
+++ b/src/VBox/Main/testcase/tstGuid.cpp
@@ -0,0 +1,110 @@
+/* $Id: tstGuid.cpp $ */
+/** @file
+ * API Glue Testcase - Guid.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/com/Guid.h>
+
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+#include <iprt/uni.h>
+
+
+static void test1(RTTEST hTest)
+{
+ RTTestSub(hTest, "Basics");
+
+#define CHECK(expr) RTTESTI_CHECK(expr)
+#define CHECK_DUMP(expr, value) \
+ do { \
+ if (!(expr)) \
+ RTTestFailed(hTest, "%d: FAILED %s, got \"%s\"", __LINE__, #expr, value); \
+ } while (0)
+
+#define CHECK_DUMP_I(expr) \
+ do { \
+ if (!(expr)) \
+ RTTestFailed(hTest, "%d: FAILED %s, got \"%d\"", __LINE__, #expr, expr); \
+ } while (0)
+#define CHECK_EQUAL(Str, szExpect) \
+ do { \
+ if (!(Str).equals(szExpect)) \
+ RTTestIFailed("line %u: expected \"%s\" got \"%s\"", __LINE__, szExpect, (Str).c_str()); \
+ } while (0)
+#define CHECK_EQUAL_I(iRes, iExpect) \
+ do { \
+ if (iRes != iExpect) \
+ RTTestIFailed("line %u: expected \"%zd\" got \"%zd\"", __LINE__, iExpect, iRes); \
+ } while (0)
+
+ com::Guid zero;
+ CHECK(zero.isZero());
+
+ com::Guid copyZero(zero);
+ CHECK(copyZero.isZero());
+
+ com::Guid assignZero(zero);
+ CHECK(assignZero.isZero());
+
+ com::Guid random;
+ random.create();
+ CHECK(!random.isZero());
+
+ com::Guid copyRandom(random);
+ CHECK(!copyRandom.isZero());
+
+ com::Guid assignRandom(random);
+ CHECK(!assignRandom.isZero());
+
+ /** @todo extend this a lot, it needs to cover many more cases */
+
+#undef CHECK
+#undef CHECK_DUMP
+#undef CHECK_DUMP_I
+#undef CHECK_EQUAL
+}
+
+
+int main()
+{
+ RTTEST hTest;
+ RTEXITCODE rcExit = RTTestInitAndCreate("tstGuid", &hTest);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ RTTestBanner(hTest);
+
+ test1(hTest);
+
+ rcExit = RTTestSummaryAndDestroy(hTest);
+ }
+ return rcExit;
+}
+
diff --git a/src/VBox/Main/testcase/tstMediumLock.cpp b/src/VBox/Main/testcase/tstMediumLock.cpp
new file mode 100644
index 00000000..57629d53
--- /dev/null
+++ b/src/VBox/Main/testcase/tstMediumLock.cpp
@@ -0,0 +1,309 @@
+/* $Id: tstMediumLock.cpp $ */
+/** @file
+ * Medium lock test cases.
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_ENABLED
+#define LOG_GROUP LOG_GROUP_MAIN
+#include <VBox/log.h>
+
+#include <VBox/com/com.h>
+#include <VBox/com/ptr.h>
+#include <VBox/com/defs.h>
+#include <VBox/com/array.h>
+#include <VBox/com/string.h>
+#include <VBox/com/VirtualBox.h>
+
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/initterm.h>
+#include <iprt/test.h>
+
+using namespace com;
+
+
+#define TEST_RT_SUCCESS(x,y,z) \
+ do \
+ { \
+ int rc = (y); \
+ if (RT_FAILURE(rc)) \
+ RTTestFailed((x), "%s %Rrc", (z), rc); \
+ } while (0)
+
+#define TEST_COM_SUCCESS(x,y,z) \
+ do \
+ { \
+ HRESULT hrc = (y); \
+ if (FAILED(hrc)) \
+ RTTestFailed((x), "%s %Rhrc", (z), hrc); \
+ } while (0)
+
+#define TEST_COM_FAILURE(x,y,z) \
+ do \
+ { \
+ HRESULT hrc = (y); \
+ if (SUCCEEDED(hrc)) \
+ RTTestFailed((x), "%s", (z)); \
+ } while (0)
+
+int main(int argc, char *argv[])
+{
+ /* Init the runtime without loading the support driver. */
+ RTR3InitExe(argc, &argv, 0);
+
+ RTTEST hTest;
+ RTEXITCODE rcExit = RTTestInitAndCreate("tstMediumLock", &hTest);
+ if (rcExit)
+ return rcExit;
+ RTTestBanner(hTest);
+
+ bool fComInit = false;
+ ComPtr<IVirtualBoxClient> pVirtualBoxClient;
+ ComPtr<IVirtualBox> pVirtualBox;
+ char szPathTemp[RTPATH_MAX] = "";
+ ComPtr<IMedium> pMedium;
+
+ if (!RTTestSubErrorCount(hTest))
+ {
+ RTTestSub(hTest, "Constructing temp image name");
+ TEST_RT_SUCCESS(hTest, RTPathTemp(szPathTemp, sizeof(szPathTemp)), "temp directory");
+ RTUUID uuid;
+ RTUuidCreate(&uuid);
+ char szFile[50];
+ RTStrPrintf(szFile, sizeof(szFile), "%RTuuid.vdi", &uuid);
+ TEST_RT_SUCCESS(hTest, RTPathAppend(szPathTemp, sizeof(szPathTemp), szFile), "concatenate image name");
+ }
+
+ if (!RTTestSubErrorCount(hTest))
+ {
+ RTTestSub(hTest, "Initializing COM");
+ TEST_COM_SUCCESS(hTest, Initialize(), "init");
+ }
+
+ if (!RTTestSubErrorCount(hTest))
+ {
+ fComInit = true;
+
+ RTTestSub(hTest, "Getting VirtualBox reference");
+ TEST_COM_SUCCESS(hTest, pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient), "vboxclient reference");
+ TEST_COM_SUCCESS(hTest, pVirtualBoxClient->COMGETTER(VirtualBox)(pVirtualBox.asOutParam()), "vbox reference");
+ }
+
+ if (!RTTestSubErrorCount(hTest))
+ {
+ RTTestSub(hTest, "Creating temp hard disk medium");
+ TEST_COM_SUCCESS(hTest, pVirtualBox->CreateMedium(Bstr("VDI").raw(), Bstr(szPathTemp).raw(), AccessMode_ReadWrite, DeviceType_HardDisk, pMedium.asOutParam()), "create medium");
+ if (!pMedium.isNull())
+ {
+ ComPtr<IProgress> pProgress;
+ SafeArray<MediumVariant_T> variant;
+ variant.push_back(MediumVariant_Standard);
+ TEST_COM_SUCCESS(hTest, pMedium->CreateBaseStorage(_1M, ComSafeArrayAsInParam(variant), pProgress.asOutParam()), "create base storage");
+ if (!pProgress.isNull())
+ TEST_COM_SUCCESS(hTest, pProgress->WaitForCompletion(30000), "waiting for completion of create");
+ }
+ }
+
+ if (!RTTestSubErrorCount(hTest))
+ {
+ RTTestSub(hTest, "Write locks");
+ ComPtr<IToken> pToken1, pToken2;
+
+ MediumState_T mediumState = MediumState_NotCreated;
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting state");
+ if (mediumState != MediumState_Created)
+ RTTestFailed(hTest, "wrong medium state %d", mediumState);
+
+ TEST_COM_SUCCESS(hTest, pMedium->LockWrite(pToken1.asOutParam()), "write lock");
+
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting lock write state");
+ if (mediumState != MediumState_LockedWrite)
+ RTTestFailed(hTest, "wrong lock write medium state %d", mediumState);
+
+ TEST_COM_FAILURE(hTest, pMedium->LockWrite(pToken2.asOutParam()), "nested write lock succeeded");
+ if (!pToken2.isNull())
+ RTTestFailed(hTest, "pToken2 is not null");
+
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting after nested lock write state");
+ if (mediumState != MediumState_LockedWrite)
+ RTTestFailed(hTest, "wrong after nested lock write medium state %d", mediumState);
+
+ if (!pToken1.isNull())
+ TEST_COM_SUCCESS(hTest, pToken1->Abandon(), "write unlock");
+ else
+ RTTestFailed(hTest, "pToken1 is null");
+
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting unlock write state");
+ if (mediumState != MediumState_Created)
+ RTTestFailed(hTest, "wrong unlock write medium state %d", mediumState);
+ }
+
+ if (!RTTestSubErrorCount(hTest))
+ {
+ RTTestSub(hTest, "Read locks");
+ ComPtr<IToken> pToken1, pToken2;
+
+ MediumState_T mediumState = MediumState_NotCreated;
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting state");
+ if (mediumState != MediumState_Created)
+ RTTestFailed(hTest, "wrong medium state %d", mediumState);
+
+ TEST_COM_SUCCESS(hTest, pMedium->LockRead(pToken1.asOutParam()), "read lock");
+
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting lock read state");
+ if (mediumState != MediumState_LockedRead)
+ RTTestFailed(hTest, "wrong lock read medium state %d", mediumState);
+
+ TEST_COM_SUCCESS(hTest, pMedium->LockRead(pToken2.asOutParam()), "nested read lock failed");
+
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting after nested lock read state");
+ if (mediumState != MediumState_LockedRead)
+ RTTestFailed(hTest, "wrong after nested lock read medium state %d", mediumState);
+
+ if (!pToken2.isNull())
+ TEST_COM_SUCCESS(hTest, pToken2->Abandon(), "read nested unlock");
+ else
+ RTTestFailed(hTest, "pToken2 is null");
+
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting after nested lock read state");
+ if (mediumState != MediumState_LockedRead)
+ RTTestFailed(hTest, "wrong after nested lock read medium state %d", mediumState);
+
+ if (!pToken1.isNull())
+ TEST_COM_SUCCESS(hTest, pToken1->Abandon(), "read nested unlock");
+ else
+ RTTestFailed(hTest, "pToken1 is null");
+
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting unlock read state");
+ if (mediumState != MediumState_Created)
+ RTTestFailed(hTest, "wrong unlock read medium state %d", mediumState);
+ }
+
+ if (!RTTestSubErrorCount(hTest))
+ {
+ RTTestSub(hTest, "Mixing write and read locks");
+ ComPtr<IToken> pToken1, pToken2;
+
+ MediumState_T mediumState = MediumState_NotCreated;
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting state");
+ if (mediumState != MediumState_Created)
+ RTTestFailed(hTest, "wrong medium state %d", mediumState);
+
+ TEST_COM_SUCCESS(hTest, pMedium->LockWrite(pToken1.asOutParam()), "write lock");
+
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting lock write state");
+ if (mediumState != MediumState_LockedWrite)
+ RTTestFailed(hTest, "wrong lock write medium state %d", mediumState);
+
+ TEST_COM_FAILURE(hTest, pMedium->LockRead(pToken2.asOutParam()), "write+read lock succeeded");
+ if (!pToken2.isNull())
+ RTTestFailed(hTest, "pToken2 is not null");
+
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting after nested lock write state");
+ if (mediumState != MediumState_LockedWrite)
+ RTTestFailed(hTest, "wrong after nested lock write medium state %d", mediumState);
+
+ if (!pToken1.isNull())
+ TEST_COM_SUCCESS(hTest, pToken1->Abandon(), "write unlock");
+ else
+ RTTestFailed(hTest, "pToken1 is null");
+
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting unlock write state");
+ if (mediumState != MediumState_Created)
+ RTTestFailed(hTest, "wrong unlock write medium state %d", mediumState);
+ }
+
+ if (!RTTestSubErrorCount(hTest))
+ {
+ RTTestSub(hTest, "Mixing read and write locks");
+ ComPtr<IToken> pToken1, pToken2;
+
+ MediumState_T mediumState = MediumState_NotCreated;
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting state");
+ if (mediumState != MediumState_Created)
+ RTTestFailed(hTest, "wrong medium state %d", mediumState);
+
+ TEST_COM_SUCCESS(hTest, pMedium->LockRead(pToken1.asOutParam()), "read lock");
+
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting lock read state");
+ if (mediumState != MediumState_LockedRead)
+ RTTestFailed(hTest, "wrong lock read medium state %d", mediumState);
+
+ TEST_COM_FAILURE(hTest, pMedium->LockWrite(pToken2.asOutParam()), "read+write lock succeeded");
+ if (!pToken2.isNull())
+ RTTestFailed(hTest, "pToken2 is not null");
+
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting after nested lock read state");
+ if (mediumState != MediumState_LockedRead)
+ RTTestFailed(hTest, "wrong after nested lock read medium state %d", mediumState);
+
+ if (!pToken1.isNull())
+ TEST_COM_SUCCESS(hTest, pToken1->Abandon(), "read unlock");
+ else
+ RTTestFailed(hTest, "pToken1 is null");
+
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting unlock read state");
+ if (mediumState != MediumState_Created)
+ RTTestFailed(hTest, "wrong unlock read medium state %d", mediumState);
+ }
+
+ /* Cleanup, also part of the testcase */
+
+ if (!pMedium.isNull())
+ {
+ RTTestSub(hTest, "Closing medium");
+ MediumState_T mediumState = MediumState_NotCreated;
+ TEST_COM_SUCCESS(hTest, pMedium->COMGETTER(State)(&mediumState), "getting state");
+ if (mediumState == MediumState_Created)
+ {
+ ComPtr<IProgress> pProgress;
+ TEST_COM_SUCCESS(hTest, pMedium->DeleteStorage(pProgress.asOutParam()), "deleting storage");
+ if (!pProgress.isNull())
+ TEST_COM_SUCCESS(hTest, pProgress->WaitForCompletion(30000), "waiting for completion of delete");
+ }
+ TEST_COM_SUCCESS(hTest, pMedium->Close(), "closing");
+ pMedium.setNull();
+ }
+
+ pVirtualBox.setNull();
+ pVirtualBoxClient.setNull();
+
+ /* Make sure that there are no object references alive here, XPCOM does
+ * a very bad job at cleaning up such leftovers, spitting out warning
+ * messages in a debug build. */
+
+ if (fComInit)
+ {
+ RTTestIPrintf(RTTESTLVL_DEBUG, "Shutting down COM...\n");
+ Shutdown();
+ }
+
+ /*
+ * Summary.
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}
diff --git a/src/VBox/Main/testcase/tstOVF.cpp b/src/VBox/Main/testcase/tstOVF.cpp
new file mode 100644
index 00000000..90b62174
--- /dev/null
+++ b/src/VBox/Main/testcase/tstOVF.cpp
@@ -0,0 +1,431 @@
+/* $Id: tstOVF.cpp $ */
+/** @file
+ *
+ * tstOVF - testcases for OVF import and export
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/com/VirtualBox.h>
+
+#include <VBox/com/com.h>
+#include <VBox/com/array.h>
+#include <VBox/com/string.h>
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/com/errorprint.h>
+
+#include <iprt/initterm.h>
+#include <iprt/stream.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/param.h>
+
+#include <list>
+
+using namespace com;
+
+// main
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Quick hack exception structure.
+ *
+ */
+struct MyError
+{
+ MyError(HRESULT rc,
+ const char *pcsz,
+ IProgress *pProgress = NULL)
+ : m_rc(rc)
+ {
+ m_str = "ERROR: ";
+ m_str += pcsz;
+
+ if (pProgress)
+ {
+ com::ProgressErrorInfo info(pProgress);
+ com::GluePrintErrorInfo(info);
+ }
+ else if (rc != S_OK)
+ {
+ com::ErrorInfo info;
+ if (!info.isFullAvailable() && !info.isBasicAvailable())
+ com::GluePrintRCMessage(rc);
+ else
+ com::GluePrintErrorInfo(info);
+ }
+ }
+
+ Utf8Str m_str;
+ HRESULT m_rc;
+};
+
+/**
+ * Imports the given OVF file, with all bells and whistles.
+ * Throws MyError on errors.
+ * @param pcszPrefix Descriptive short prefix string for console output.
+ * @param pVirtualBox VirtualBox instance.
+ * @param pcszOVF0 File to import.
+ * @param llMachinesCreated out: UUIDs of machines that were created so that caller can clean up.
+ */
+void importOVF(const char *pcszPrefix,
+ ComPtr<IVirtualBox> &pVirtualBox,
+ const char *pcszOVF0,
+ std::list<Guid> &llMachinesCreated)
+{
+ char szAbsOVF[RTPATH_MAX];
+ RTPathExecDir(szAbsOVF, sizeof(szAbsOVF));
+ RTPathAppend(szAbsOVF, sizeof(szAbsOVF), pcszOVF0);
+
+ RTPrintf("%s: reading appliance \"%s\"...\n", pcszPrefix, szAbsOVF);
+ ComPtr<IAppliance> pAppl;
+ HRESULT rc = pVirtualBox->CreateAppliance(pAppl.asOutParam());
+ if (FAILED(rc)) throw MyError(rc, "failed to create appliance\n");
+
+ ComPtr<IProgress> pProgress;
+ rc = pAppl->Read(Bstr(szAbsOVF).raw(), pProgress.asOutParam());
+ if (FAILED(rc)) throw MyError(rc, "Appliance::Read() failed\n");
+ rc = pProgress->WaitForCompletion(-1);
+ if (FAILED(rc)) throw MyError(rc, "Progress::WaitForCompletion() failed\n");
+ LONG rc2;
+ pProgress->COMGETTER(ResultCode)(&rc2);
+ if (FAILED(rc2)) throw MyError(rc2, "Appliance::Read() failed\n", pProgress);
+
+ RTPrintf("%s: interpreting appliance \"%s\"...\n", pcszPrefix, szAbsOVF);
+ rc = pAppl->Interpret();
+ if (FAILED(rc)) throw MyError(rc, "Appliance::Interpret() failed\n");
+
+ com::SafeIfaceArray<IVirtualSystemDescription> aDescriptions;
+ rc = pAppl->COMGETTER(VirtualSystemDescriptions)(ComSafeArrayAsOutParam(aDescriptions));
+ for (uint32_t u = 0;
+ u < aDescriptions.size();
+ ++u)
+ {
+ ComPtr<IVirtualSystemDescription> pVSys = aDescriptions[u];
+
+ com::SafeArray<VirtualSystemDescriptionType_T> aTypes;
+ com::SafeArray<BSTR> aRefs;
+ com::SafeArray<BSTR> aOvfValues;
+ com::SafeArray<BSTR> aVBoxValues;
+ com::SafeArray<BSTR> aExtraConfigValues;
+ rc = pVSys->GetDescription(ComSafeArrayAsOutParam(aTypes),
+ ComSafeArrayAsOutParam(aRefs),
+ ComSafeArrayAsOutParam(aOvfValues),
+ ComSafeArrayAsOutParam(aVBoxValues),
+ ComSafeArrayAsOutParam(aExtraConfigValues));
+ if (FAILED(rc)) throw MyError(rc, "VirtualSystemDescription::GetDescription() failed\n");
+
+ for (uint32_t u2 = 0;
+ u2 < aTypes.size();
+ ++u2)
+ {
+ const char *pcszType;
+
+ VirtualSystemDescriptionType_T t = aTypes[u2];
+ switch (t)
+ {
+ case VirtualSystemDescriptionType_OS:
+ pcszType = "ostype";
+ break;
+
+ case VirtualSystemDescriptionType_Name:
+ pcszType = "name";
+ break;
+
+ case VirtualSystemDescriptionType_Product:
+ pcszType = "product";
+ break;
+
+ case VirtualSystemDescriptionType_ProductUrl:
+ pcszType = "producturl";
+ break;
+
+ case VirtualSystemDescriptionType_Vendor:
+ pcszType = "vendor";
+ break;
+
+ case VirtualSystemDescriptionType_VendorUrl:
+ pcszType = "vendorurl";
+ break;
+
+ case VirtualSystemDescriptionType_Version:
+ pcszType = "version";
+ break;
+
+ case VirtualSystemDescriptionType_Description:
+ pcszType = "description";
+ break;
+
+ case VirtualSystemDescriptionType_License:
+ pcszType = "license";
+ break;
+
+ case VirtualSystemDescriptionType_CPU:
+ pcszType = "cpu";
+ break;
+
+ case VirtualSystemDescriptionType_Memory:
+ pcszType = "memory";
+ break;
+
+ case VirtualSystemDescriptionType_HardDiskControllerIDE:
+ pcszType = "ide";
+ break;
+
+ case VirtualSystemDescriptionType_HardDiskControllerSATA:
+ pcszType = "sata";
+ break;
+
+ case VirtualSystemDescriptionType_HardDiskControllerSAS:
+ pcszType = "sas";
+ break;
+
+ case VirtualSystemDescriptionType_HardDiskControllerSCSI:
+ pcszType = "scsi";
+ break;
+
+ case VirtualSystemDescriptionType_HardDiskControllerVirtioSCSI:
+ pcszType = "virtio-scsi";
+ break;
+
+ case VirtualSystemDescriptionType_HardDiskImage:
+ pcszType = "hd";
+ break;
+
+ case VirtualSystemDescriptionType_CDROM:
+ pcszType = "cdrom";
+ break;
+
+ case VirtualSystemDescriptionType_Floppy:
+ pcszType = "floppy";
+ break;
+
+ case VirtualSystemDescriptionType_NetworkAdapter:
+ pcszType = "net";
+ break;
+
+ case VirtualSystemDescriptionType_USBController:
+ pcszType = "usb";
+ break;
+
+ case VirtualSystemDescriptionType_SoundCard:
+ pcszType = "sound";
+ break;
+
+ case VirtualSystemDescriptionType_SettingsFile:
+ pcszType = "settings";
+ break;
+
+ case VirtualSystemDescriptionType_BaseFolder:
+ pcszType = "basefolder";
+ break;
+
+ case VirtualSystemDescriptionType_PrimaryGroup:
+ pcszType = "primarygroup";
+ break;
+
+ default:
+ throw MyError(E_UNEXPECTED, "Invalid VirtualSystemDescriptionType (enum)\n");
+ break;
+ }
+
+ RTPrintf(" vsys %2u item %2u: type %2d (%s), ovf: \"%ls\", vbox: \"%ls\", extra: \"%ls\"\n",
+ u, u2, t, pcszType,
+ aOvfValues[u2],
+ aVBoxValues[u2],
+ aExtraConfigValues[u2]);
+ }
+ }
+
+ RTPrintf("%s: importing %d machine(s)...\n", pcszPrefix, aDescriptions.size());
+ SafeArray<ImportOptions_T> sfaOptions;
+ rc = pAppl->ImportMachines(ComSafeArrayAsInParam(sfaOptions), pProgress.asOutParam());
+ if (FAILED(rc)) throw MyError(rc, "Appliance::ImportMachines() failed\n");
+ rc = pProgress->WaitForCompletion(-1);
+ if (FAILED(rc)) throw MyError(rc, "Progress::WaitForCompletion() failed\n");
+ pProgress->COMGETTER(ResultCode)(&rc2);
+ if (FAILED(rc2)) throw MyError(rc2, "Appliance::ImportMachines() failed\n", pProgress);
+
+ com::SafeArray<BSTR> aMachineUUIDs;
+ rc = pAppl->COMGETTER(Machines)(ComSafeArrayAsOutParam(aMachineUUIDs));
+ if (FAILED(rc)) throw MyError(rc, "Appliance::GetMachines() failed\n");
+
+ for (size_t u = 0;
+ u < aMachineUUIDs.size();
+ ++u)
+ {
+ RTPrintf("%s: created machine %u: %ls\n", pcszPrefix, u, aMachineUUIDs[u]);
+ llMachinesCreated.push_back(Guid(Bstr(aMachineUUIDs[u])));
+ }
+
+ RTPrintf("%s: success!\n", pcszPrefix);
+}
+
+/**
+ * Copies ovf-testcases/ovf-dummy.vmdk to the given target and appends that
+ * target as a string to the given list so that the caller can delete it
+ * again later.
+ * @param llFiles2Delete List of strings to append the target file path to.
+ * @param pcszDest Target for dummy VMDK.
+ */
+void copyDummyDiskImage(const char *pcszPrefix,
+ std::list<Utf8Str> &llFiles2Delete,
+ const char *pcszDest)
+{
+ char szSrc[RTPATH_MAX];
+ RTPathExecDir(szSrc, sizeof(szSrc));
+ RTPathAppend(szSrc, sizeof(szSrc), "ovf-testcases/ovf-dummy.vmdk");
+
+ char szDst[RTPATH_MAX];
+ RTPathExecDir(szDst, sizeof(szDst));
+ RTPathAppend(szDst, sizeof(szDst), pcszDest);
+ RTPrintf("%s: copying ovf-dummy.vmdk to \"%s\"...\n", pcszPrefix, pcszDest);
+
+ /* Delete the destination file if it exists or RTFileCopy will fail. */
+ if (RTFileExists(szDst))
+ {
+ RTPrintf("Deleting file %s...\n", szDst);
+ RTFileDelete(szDst);
+ }
+
+ int vrc = RTFileCopy(szSrc, szDst);
+ if (RT_FAILURE(vrc)) throw MyError(0, Utf8StrFmt("Cannot copy ovf-dummy.vmdk to %s: %Rra\n", pcszDest, vrc).c_str());
+ llFiles2Delete.push_back(szDst);
+}
+
+/**
+ *
+ * @param argc
+ * @param argv[]
+ * @return
+ */
+int main(int argc, char *argv[])
+{
+ RTR3InitExe(argc, &argv, 0);
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ HRESULT rc = S_OK;
+
+ std::list<Utf8Str> llFiles2Delete;
+ std::list<Guid> llMachinesCreated;
+
+ ComPtr<IVirtualBoxClient> pVirtualBoxClient;
+ ComPtr<IVirtualBox> pVirtualBox;
+
+ try
+ {
+ RTPrintf("Initializing COM...\n");
+ rc = com::Initialize();
+ if (FAILED(rc)) throw MyError(rc, "failed to initialize COM!\n");
+
+ ComPtr<ISession> pSession;
+
+ RTPrintf("Creating VirtualBox object...\n");
+ rc = pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient);
+ if (SUCCEEDED(rc))
+ rc = pVirtualBoxClient->COMGETTER(VirtualBox)(pVirtualBox.asOutParam());
+ if (FAILED(rc)) throw MyError(rc, "failed to create the VirtualBox object!\n");
+
+ rc = pSession.createInprocObject(CLSID_Session);
+ if (FAILED(rc)) throw MyError(rc, "failed to create a session object!\n");
+
+ // for each testcase, we will copy the dummy VMDK image to the subdirectory with the OVF testcase
+ // so that the import will find the disks it expects; this is just for testing the import since
+ // the imported machines will obviously not be usable.
+ // llFiles2Delete receives the paths of all the files that we need to clean up later.
+
+ // testcase 1: import ovf-joomla-0.9/joomla-1.1.4-ovf.ovf
+ copyDummyDiskImage("joomla-0.9", llFiles2Delete, "ovf-testcases/ovf-joomla-0.9/joomla-1.1.4-ovf-0.vmdk");
+ copyDummyDiskImage("joomla-0.9", llFiles2Delete, "ovf-testcases/ovf-joomla-0.9/joomla-1.1.4-ovf-1.vmdk");
+ importOVF("joomla-0.9", pVirtualBox, "ovf-testcases/ovf-joomla-0.9/joomla-1.1.4-ovf.ovf", llMachinesCreated);
+
+ // testcase 2: import ovf-winxp-vbox-sharedfolders/winxp.ovf
+ copyDummyDiskImage("winxp-vbox-sharedfolders", llFiles2Delete, "ovf-testcases/ovf-winxp-vbox-sharedfolders/Windows 5.1 XP 1 merged.vmdk");
+ copyDummyDiskImage("winxp-vbox-sharedfolders", llFiles2Delete, "ovf-testcases/ovf-winxp-vbox-sharedfolders/smallvdi.vmdk");
+ importOVF("winxp-vbox-sharedfolders", pVirtualBox, "ovf-testcases/ovf-winxp-vbox-sharedfolders/winxp.ovf", llMachinesCreated);
+
+ // testcase 3: import ovf-winxp-vbox-sharedfolders/winxp.ovf
+ importOVF("winhost-audio-nodisks", pVirtualBox, "ovf-testcases/ovf-winhost-audio-nodisks/WinXP.ovf", llMachinesCreated);
+
+ RTPrintf("Machine imports done, no errors. Cleaning up...\n");
+ }
+ catch (MyError &e)
+ {
+ rc = e.m_rc;
+ RTPrintf("%s", e.m_str.c_str());
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ try
+ {
+ // clean up the machines created
+ for (std::list<Guid>::const_iterator it = llMachinesCreated.begin();
+ it != llMachinesCreated.end();
+ ++it)
+ {
+ const Guid &uuid = *it;
+ Bstr bstrUUID(uuid.toUtf16());
+ ComPtr<IMachine> pMachine;
+ rc = pVirtualBox->FindMachine(bstrUUID.raw(), pMachine.asOutParam());
+ if (FAILED(rc)) throw MyError(rc, "VirtualBox::FindMachine() failed\n");
+
+ RTPrintf(" Deleting machine %ls...\n", bstrUUID.raw());
+ SafeIfaceArray<IMedium> sfaMedia;
+ rc = pMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly,
+ ComSafeArrayAsOutParam(sfaMedia));
+ if (FAILED(rc)) throw MyError(rc, "Machine::Unregister() failed\n");
+
+ ComPtr<IProgress> pProgress;
+ rc = pMachine->DeleteConfig(ComSafeArrayAsInParam(sfaMedia), pProgress.asOutParam());
+ if (FAILED(rc)) throw MyError(rc, "Machine::DeleteSettings() failed\n");
+ rc = pProgress->WaitForCompletion(-1);
+ if (FAILED(rc)) throw MyError(rc, "Progress::WaitForCompletion() failed\n");
+ }
+ }
+ catch (MyError &e)
+ {
+ rc = e.m_rc;
+ RTPrintf("%s", e.m_str.c_str());
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ // clean up the VMDK copies that we made in copyDummyDiskImage()
+ for (std::list<Utf8Str>::const_iterator it = llFiles2Delete.begin();
+ it != llFiles2Delete.end();
+ ++it)
+ {
+ const Utf8Str &strFile = *it;
+ RTPrintf("Deleting file %s...\n", strFile.c_str());
+ RTFileDelete(strFile.c_str());
+ }
+
+ pVirtualBox.setNull();
+ pVirtualBoxClient.setNull();
+
+ RTPrintf("Shutting down COM...\n");
+ com::Shutdown();
+ RTPrintf("tstOVF all done: %s\n", rcExit ? "ERROR" : "SUCCESS");
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Main/testcase/tstUSBLinux.h b/src/VBox/Main/testcase/tstUSBLinux.h
new file mode 100644
index 00000000..b112db96
--- /dev/null
+++ b/src/VBox/Main/testcase/tstUSBLinux.h
@@ -0,0 +1,79 @@
+/* $Id: tstUSBLinux.h $ */
+/** @file
+ * VirtualBox USB Proxy Service class, test version for Linux hosts.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SRC_testcase_tstUSBLinux_h
+#define MAIN_INCLUDED_SRC_testcase_tstUSBLinux_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+typedef int HRESULT;
+enum { S_OK = 0, E_NOTIMPL = 1 };
+
+#include <VBox/usb.h>
+#include <VBox/usbfilter.h>
+
+#include <VBox/err.h>
+
+#ifdef VBOX_USB_WITH_SYSFS
+# include <libhal.h>
+#endif
+
+#include <stdio.h>
+/**
+ * The Linux hosted USB Proxy Service.
+ */
+class USBProxyServiceLinux
+{
+public:
+ USBProxyServiceLinux()
+ : mLastError(VINF_SUCCESS)
+ {}
+
+ HRESULT initSysfs(void);
+ PUSBDEVICE getDevicesFromSysfs(void);
+ int getLastError(void)
+ {
+ return mLastError;
+ }
+
+private:
+ int start(void) { return VINF_SUCCESS; }
+ static void freeDevice(PUSBDEVICE) {} /* We don't care about leaks in a test. */
+ int usbProbeInterfacesFromLibhal(const char *pszHalUuid, PUSBDEVICE pDev);
+ int mLastError;
+# ifdef VBOX_USB_WITH_SYSFS
+ /** Our connection to DBus for getting information from hal. This will be
+ * NULL if the initialisation failed. */
+ DBusConnection *mDBusConnection;
+ /** Handle to libhal. */
+ LibHalContext *mLibHalContext;
+# endif
+};
+
+#endif /* !MAIN_INCLUDED_SRC_testcase_tstUSBLinux_h */
+
diff --git a/src/VBox/Main/testcase/tstUSBProxyLinux.cpp b/src/VBox/Main/testcase/tstUSBProxyLinux.cpp
new file mode 100644
index 00000000..db199da3
--- /dev/null
+++ b/src/VBox/Main/testcase/tstUSBProxyLinux.cpp
@@ -0,0 +1,195 @@
+/* $Id: tstUSBProxyLinux.cpp $ */
+/** @file
+ * USBProxyBackendLinux test case.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+
+#include "USBGetDevices.h"
+
+#include <VBox/err.h>
+#include <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+
+/*** BEGIN STUBS ***/
+
+static struct
+{
+ const char *pcszEnvUsb;
+ const char *pcszEnvUsbRoot;
+ const char *pcszDevicesRoot;
+ bool fDevicesAccessible;
+ const char *pcszUsbfsRoot;
+ bool fUsbfsAccessible;
+ int rcMethodInit;
+ const char *pcszDevicesRootExpected;
+ bool fUsingUsbfsExpected;
+ int rcExpected;
+} s_testEnvironment[] =
+{
+ /* "sysfs" and valid root in the environment */
+ { "sysfs", "/dev/bus/usb", "/dev/bus/usb", true, NULL, false, VINF_SUCCESS, "/dev/bus/usb", false, VINF_SUCCESS },
+ /* "sysfs" and bad root in the environment */
+ { "sysfs", "/dev/bus/usb", "/dev/vboxusb", false, "/proc/usb/bus", false, VINF_SUCCESS, "", true, VERR_NOT_FOUND },
+ /* "sysfs" and no root in the environment */
+ { "sysfs", NULL, "/dev/vboxusb", true, NULL, false, VINF_SUCCESS, "/dev/vboxusb", false, VINF_SUCCESS },
+ /* "usbfs" and valid root in the environment */
+ { "usbfs", "/dev/bus/usb", NULL, false, "/dev/bus/usb", true, VINF_SUCCESS, "/dev/bus/usb", true, VINF_SUCCESS },
+ /* "usbfs" and bad root in the environment */
+ { "usbfs", "/dev/bus/usb", "/dev/vboxusb", false, "/proc/usb/bus", false, VINF_SUCCESS, "", true, VERR_NOT_FOUND },
+ /* "usbfs" and no root in the environment */
+ { "usbfs", NULL, NULL, false, "/proc/bus/usb", true, VINF_SUCCESS, "/proc/bus/usb", true, VINF_SUCCESS },
+ /* invalid method in the environment, sysfs available */
+ { "invalid", "/dev/bus/usb", "/dev/vboxusb", true, NULL, false, VINF_SUCCESS, "/dev/vboxusb", false, VINF_SUCCESS },
+ /* invalid method in the environment, usbfs available */
+ { "invalid", "/dev/bus/usb", NULL, true, "/proc/bus/usb", true, VINF_SUCCESS, "/proc/bus/usb", true, VINF_SUCCESS },
+ /* invalid method in the environment, sysfs inaccessible */
+ { "invalid", "/dev/bus/usb", "/dev/vboxusb", false, NULL, false, VINF_SUCCESS, "", true, VERR_VUSB_USB_DEVICE_PERMISSION },
+ /* invalid method in the environment, usbfs inaccessible */
+ { "invalid", "/dev/bus/usb", NULL, false, "/proc/bus/usb", false, VINF_SUCCESS, "", true, VERR_VUSB_USBFS_PERMISSION },
+ /* No environment, sysfs and usbfs available but without access permissions. */
+ { NULL, NULL, "/dev/vboxusb", false, "/proc/bus/usb", false, VERR_NO_MEMORY, "", true, VERR_VUSB_USB_DEVICE_PERMISSION },
+ /* No environment, sysfs and usbfs available, access permissions for sysfs. */
+ { NULL, NULL, "/dev/vboxusb", true, "/proc/bus/usb", false, VINF_SUCCESS, "/dev/vboxusb", false, VINF_SUCCESS },
+ /* No environment, sysfs and usbfs available, access permissions for usbfs. */
+ { NULL, NULL, "/dev/vboxusb", false, "/proc/bus/usb", true, VINF_SUCCESS, "/proc/bus/usb", true, VINF_SUCCESS },
+ /* No environment, sysfs available but without access permissions. */
+ { NULL, NULL, "/dev/vboxusb", false, NULL, false, VERR_NO_MEMORY, "", true, VERR_VUSB_USB_DEVICE_PERMISSION },
+ /* No environment, usbfs available but without access permissions. */
+ { NULL, NULL, NULL, false, "/proc/bus/usb", false, VERR_NO_MEMORY, "", true, VERR_VUSB_USBFS_PERMISSION },
+};
+
+static void testInit(RTTEST hTest)
+{
+ RTTestSub(hTest, "Testing USBProxyLinuxChooseMethod");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_testEnvironment); ++i)
+ {
+ bool fUsingUsbfs = true;
+ const char *pcszDevicesRoot = "";
+
+ TestUSBSetEnv(s_testEnvironment[i].pcszEnvUsb,
+ s_testEnvironment[i].pcszEnvUsbRoot);
+ TestUSBSetupInit(s_testEnvironment[i].pcszUsbfsRoot,
+ s_testEnvironment[i].fUsbfsAccessible,
+ s_testEnvironment[i].pcszDevicesRoot,
+ s_testEnvironment[i].fDevicesAccessible,
+ s_testEnvironment[i].rcMethodInit);
+ int rc = USBProxyLinuxChooseMethod(&fUsingUsbfs, &pcszDevicesRoot);
+ RTTESTI_CHECK_MSG(rc == s_testEnvironment[i].rcExpected,
+ ("rc=%Rrc (test index %i) instead of %Rrc!\n",
+ rc, i, s_testEnvironment[i].rcExpected));
+ RTTESTI_CHECK_MSG(!RTStrCmp(pcszDevicesRoot,
+ s_testEnvironment[i].pcszDevicesRootExpected),
+ ("testGetDevicesRoot() returned %s (test index %i) instead of %s!\n",
+ pcszDevicesRoot, i,
+ s_testEnvironment[i].pcszDevicesRootExpected));
+ RTTESTI_CHECK_MSG( fUsingUsbfs
+ == s_testEnvironment[i].fUsingUsbfsExpected,
+ ("testGetUsingUsbfs() returned %RTbool (test index %i) instead of %RTbool!\n",
+ fUsingUsbfs, i,
+ s_testEnvironment[i].fUsingUsbfsExpected));
+ }
+}
+
+static struct
+{
+ const char *pacszDeviceAddresses[16];
+ const char *pacszAccessibleFiles[16];
+ const char *pcszRoot;
+ bool fIsDeviceNodes;
+ bool fAvailableExpected;
+} s_testCheckDeviceRoot[] =
+{
+ /* /dev/vboxusb accessible -> device nodes method available */
+ { { NULL }, { "/dev/vboxusb" }, "/dev/vboxusb", true, true },
+ /* /dev/vboxusb present but not accessible -> device nodes method not
+ * available */
+ { { NULL }, { NULL }, "/dev/vboxusb", true, false },
+ /* /proc/bus/usb available but empty -> usbfs method available (we can't
+ * really check in this case) */
+ { { NULL }, { "/proc/bus/usb" }, "/proc/bus/usb", false, true },
+ /* /proc/bus/usb not available or not accessible -> usbfs method not available */
+ { { NULL }, { NULL }, "/proc/bus/usb", false, false },
+ /* /proc/bus/usb available, one inaccessible device -> usbfs method not
+ * available */
+ { { "/proc/bus/usb/001/001" }, { "/proc/bus/usb" }, "/proc/bus/usb", false, false },
+ /* /proc/bus/usb available, one device of two inaccessible -> usbfs method
+ * not available */
+ { { "/proc/bus/usb/001/001", "/proc/bus/usb/002/002" },
+ { "/proc/bus/usb", "/proc/bus/usb/001/001" }, "/proc/bus/usb", false, false },
+ /* /proc/bus/usb available, two accessible devices -> usbfs method
+ * available */
+ { { "/proc/bus/usb/001/001", "/proc/bus/usb/002/002" },
+ { "/proc/bus/usb", "/proc/bus/usb/001/001", "/proc/bus/usb/002/002" },
+ "/proc/bus/usb", false, true }
+};
+
+static void testCheckDeviceRoot(RTTEST hTest)
+{
+ RTTestSub(hTest, "Testing the USBProxyLinuxCheckDeviceRoot API");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_testCheckDeviceRoot); ++i)
+ {
+ TestUSBSetAvailableUsbfsDevices(s_testCheckDeviceRoot[i]
+ .pacszDeviceAddresses);
+ TestUSBSetAccessibleFiles(s_testCheckDeviceRoot[i]
+ .pacszAccessibleFiles);
+ bool fAvailable = USBProxyLinuxCheckDeviceRoot
+ (s_testCheckDeviceRoot[i].pcszRoot,
+ s_testCheckDeviceRoot[i].fIsDeviceNodes);
+ RTTESTI_CHECK_MSG( fAvailable
+ == s_testCheckDeviceRoot[i].fAvailableExpected,
+ ("USBProxyLinuxCheckDeviceRoot() returned %RTbool (test index %i) instead of %RTbool!\n",
+ fAvailable, i,
+ s_testCheckDeviceRoot[i].fAvailableExpected));
+ }
+}
+
+int main(void)
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ RTTEST hTest;
+ RTEXITCODE rcExit = RTTestInitAndCreate("tstUSBProxyLinux", &hTest);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ RTTestBanner(hTest);
+
+ /*
+ * Run the tests.
+ */
+ testInit(hTest);
+ testCheckDeviceRoot(hTest);
+
+ /*
+ * Summary
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}
diff --git a/src/VBox/Main/testcase/tstUnattendedScript-1.expected b/src/VBox/Main/testcase/tstUnattendedScript-1.expected
new file mode 100644
index 00000000..1457a2e7
--- /dev/null
+++ b/src/VBox/Main/testcase/tstUnattendedScript-1.expected
@@ -0,0 +1,384 @@
+/*
+ * Regular conditions.
+ */
+01=true
+02=false
+03=true
+04=true
+05=true
+06=false
+07=true
+08=false
+09=true
+
+/*
+ * Expression conditions.
+ */
+01=true
+02=false
+03=true
+04=true
+05=true
+06=false
+07=true
+08=false
+09=true
+
+
+/*
+ * Regular inserts.
+ */
+/* variables */
+01=vboxuser
+02=changeme
+03=changeme
+04=VBox & VBox;
+05=911
+06=/bin/post-install-command arg1 arg2 --amp=& --lt=< --gt=> --dq-word="word" --sq-word='word'
+07=/aux/install/dir
+08=42
+09=x86
+10=x86
+11=i386
+12=i486
+13=i686
+14=3.4.2
+15=3
+16=cet
+17=cet
+18=cet
+19=dk_DK
+20=dk-DK
+21=dk
+22=DK
+23=my-extra-long-name.hostname.com
+24=my-extra-long-name
+25=my-extra-long-n
+26=hostname.com
+27=http://proxy.intranet.com:443
+
+/* indicators */
+01=1
+02=0
+03=1
+04=1
+05=1
+06=0
+07=1
+08=0
+09=1
+
+
+/*
+ * Expression inserts
+ */
+/* variables */
+01=vboxuser
+02=changeme
+03=changeme
+04=VBox & VBox;
+05=911
+06=/bin/post-install-command arg1 arg2 --amp=& --lt=< --gt=> --dq-word="word" --sq-word='word'
+07=/aux/install/dir
+08=42
+09=x86
+10=x86
+11=i386
+12=i486
+13=i686
+14=3.4.2
+15=3
+16=cet
+17=cet
+18=cet
+19=dk_DK
+20=dk-DK
+21=dk
+22=DK
+23=my-extra-long-name.hostname.com
+24=my-extra-long-name
+25=my-extra-long-n
+26=hostname.com
+27=http://proxy.intranet.com:443
+
+/* indicators */
+01=1
+02=0
+03=1
+04=1
+05=1
+06=0
+07=1
+08=0
+09=1
+
+
+/*
+ * Regular inserts with shell quoting.
+ */
+/* variables */
+01=vboxuser
+02=changeme
+03=changeme
+04='VBox & VBox;'
+05=911
+06='/bin/post-install-command arg1 arg2 --amp=& --lt=< --gt=> --dq-word="word" --sq-word='"'"'word'"'"''
+07=/aux/install/dir
+08=42
+09=x86
+10=x86
+11=i386
+12=i486
+13=i686
+14=3.4.2
+15=3
+16=cet
+17=cet
+18=cet
+19=dk_DK
+20=dk-DK
+21=dk
+22=DK
+23=my-extra-long-name.hostname.com
+24=my-extra-long-name
+25=my-extra-long-n
+26=hostname.com
+27=http://proxy.intranet.com:443
+
+/* indicators */
+01=1
+02=0
+03=1
+04=1
+05=1
+06=0
+07=1
+08=0
+09=1
+
+
+/*
+ * Expression inserts with shell quoting.
+ */
+/* variables */
+01=vboxuser
+02=changeme
+03=changeme
+04='VBox & VBox;'
+05=911
+06='/bin/post-install-command arg1 arg2 --amp=& --lt=< --gt=> --dq-word="word" --sq-word='"'"'word'"'"''
+07=/aux/install/dir
+08=42
+09=x86
+10=x86
+11=i386
+12=i486
+13=i686
+14=3.4.2
+15=3
+16=cet
+17=cet
+18=cet
+19=dk_DK
+20=dk-DK
+21=dk
+22=DK
+23=my-extra-long-name.hostname.com
+24=my-extra-long-name
+25=my-extra-long-n
+26=hostname.com
+27=http://proxy.intranet.com:443
+
+/* indicators */
+01=1
+02=0
+03=1
+04=1
+05=1
+06=0
+07=1
+08=0
+09=1
+
+
+/*
+ * Regular inserts escaped for use in XML element.
+ */
+/* variables */
+01=vboxuser
+02=changeme
+03=changeme
+04=VBox &amp; VBox;
+05=911
+06=/bin/post-install-command arg1 arg2 --amp=&amp; --lt=&lt; --gt=&gt; --dq-word=&quot;word&quot; --sq-word=&apos;word&apos;
+07=/aux/install/dir
+08=42
+09=x86
+10=x86
+11=i386
+12=i486
+13=i686
+14=3.4.2
+15=3
+16=cet
+17=cet
+18=cet
+19=dk_DK
+20=dk-DK
+21=dk
+22=DK
+23=my-extra-long-name.hostname.com
+24=my-extra-long-name
+25=my-extra-long-n
+26=hostname.com
+27=http://proxy.intranet.com:443
+
+/* indicators */
+01=1
+02=0
+03=1
+04=1
+05=1
+06=0
+07=1
+08=0
+09=1
+
+
+/*
+ * Expression inserts escaped for use in XML element.
+ */
+/* variables */
+01=vboxuser
+02=changeme
+03=changeme
+04=VBox &amp; VBox;
+05=911
+06=/bin/post-install-command arg1 arg2 --amp=&amp; --lt=&lt; --gt=&gt; --dq-word=&quot;word&quot; --sq-word=&apos;word&apos;
+07=/aux/install/dir
+08=42
+09=x86
+10=x86
+11=i386
+12=i486
+13=i686
+14=3.4.2
+15=3
+16=cet
+17=cet
+18=cet
+19=dk_DK
+20=dk-DK
+21=dk
+22=DK
+23=my-extra-long-name.hostname.com
+24=my-extra-long-name
+25=my-extra-long-n
+26=hostname.com
+27=http://proxy.intranet.com:443
+
+/* indicators */
+01=1
+02=0
+03=1
+04=1
+05=1
+06=0
+07=1
+08=0
+09=1
+
+
+/*
+ * Regular inserts escaped for use in double quoted attributes.
+ */
+/* variables */
+01=vboxuser
+02=changeme
+03=changeme
+04=VBox &amp; VBox;
+05=911
+06=/bin/post-install-command arg1 arg2 --amp=&amp; --lt=&lt; --gt=&gt; --dq-word=&quot;word&quot; --sq-word='word'
+07=/aux/install/dir
+08=42
+09=x86
+10=x86
+11=i386
+12=i486
+13=i686
+14=3.4.2
+15=3
+16=cet
+17=cet
+18=cet
+19=dk_DK
+20=dk-DK
+21=dk
+22=DK
+23=my-extra-long-name.hostname.com
+24=my-extra-long-name
+25=my-extra-long-n
+26=hostname.com
+27=http://proxy.intranet.com:443
+
+/* indicators */
+01=1
+02=0
+03=1
+04=1
+05=1
+06=0
+07=1
+08=0
+09=1
+
+
+/*
+ * Expression inserts escaped for use in double quoted attributes.
+ */
+/* variables */
+01=vboxuser
+02=changeme
+03=changeme
+04=VBox &amp; VBox;
+05=911
+06=/bin/post-install-command arg1 arg2 --amp=&amp; --lt=&lt; --gt=&gt; --dq-word=&quot;word&quot; --sq-word='word'
+07=/aux/install/dir
+08=42
+09=x86
+10=x86
+11=i386
+12=i486
+13=i686
+14=3.4.2
+15=3
+16=cet
+17=cet
+18=cet
+19=dk_DK
+20=dk-DK
+21=dk
+22=DK
+23=my-extra-long-name.hostname.com
+24=my-extra-long-name
+25=my-extra-long-n
+26=hostname.com
+27=http://proxy.intranet.com:443
+
+/* indicators */
+01=1
+02=0
+03=1
+04=1
+05=1
+06=0
+07=1
+08=0
+09=1
+
+
+/*
+ * Some typical expression conditions.
+ */
+01: GUEST_OS_VERSION >= 1.2.3
+02: GUEST_OS_VERSION <= 3.4.2
diff --git a/src/VBox/Main/testcase/tstUnattendedScript-1.template b/src/VBox/Main/testcase/tstUnattendedScript-1.template
new file mode 100644
index 00000000..07762c7d
--- /dev/null
+++ b/src/VBox/Main/testcase/tstUnattendedScript-1.template
@@ -0,0 +1,384 @@
+/*
+ * Regular conditions.
+ */
+01=@@VBOX_COND_IS_INSTALLING_ADDITIONS@@true@@VBOX_COND_ELSE@@ false @@VBOX_COND_END@@
+02=@@VBOX_COND_IS_USER_LOGIN_ADMINISTRATOR@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+03=@@VBOX_COND_IS_INSTALLING_TEST_EXEC_SERVICE@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+04=@@VBOX_COND_HAS_POST_INSTALL_COMMAND@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+05=@@VBOX_COND_HAS_PRODUCT_KEY@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+06=@@VBOX_COND_IS_MINIMAL_INSTALLATION@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+07=@@VBOX_COND_IS_FIRMWARE_UEFI@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+08=@@VBOX_COND_IS_RTC_USING_UTC@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+09=@@VBOX_COND_HAS_PROXY@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+
+/*
+ * Expression conditions.
+ */
+01=@@VBOX_COND[${IS_INSTALLING_ADDITIONS}]@@true@@VBOX_COND_ELSE@@ false @@VBOX_COND_END@@
+02=@@VBOX_COND[${IS_USER_LOGIN_ADMINISTRATOR}]@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+03=@@VBOX_COND[${IS_INSTALLING_TEST_EXEC_SERVICE}]@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+04=@@VBOX_COND[${HAS_POST_INSTALL_COMMAND}]@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+05=@@VBOX_COND[${HAS_PRODUCT_KEY}]@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+06=@@VBOX_COND[${IS_MINIMAL_INSTALLATION}]@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+07=@@VBOX_COND[${IS_FIRMWARE_UEFI}]@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+08=@@VBOX_COND[${IS_RTC_USING_UTC}]@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+09=@@VBOX_COND[${HAS_PROXY}]@@true@@VBOX_COND_ELSE@@false@@VBOX_COND_END@@
+
+
+/*
+ * Regular inserts.
+ */
+/* variables */
+01=@@VBOX_INSERT_USER_LOGIN@@
+02=@@VBOX_INSERT_USER_PASSWORD@@
+03=@@VBOX_INSERT_ROOT_PASSWORD@@
+04=@@VBOX_INSERT_USER_FULL_NAME@@
+05=@@VBOX_INSERT_PRODUCT_KEY@@
+06=@@VBOX_INSERT_POST_INSTALL_COMMAND@@
+07=@@VBOX_INSERT_AUXILIARY_INSTALL_DIR@@
+08=@@VBOX_INSERT_IMAGE_INDEX@@
+09=@@VBOX_INSERT_OS_ARCH@@
+10=@@VBOX_INSERT_OS_ARCH2@@
+11=@@VBOX_INSERT_OS_ARCH3@@
+12=@@VBOX_INSERT_OS_ARCH4@@
+13=@@VBOX_INSERT_OS_ARCH6@@
+14=@@VBOX_INSERT_GUEST_OS_VERSION@@
+15=@@VBOX_INSERT_GUEST_OS_MAJOR_VERSION@@
+16=@@VBOX_INSERT_TIME_ZONE_UX@@
+17=@@VBOX_INSERT_TIME_ZONE_WIN_NAME@@
+18=@@VBOX_INSERT_TIME_ZONE_WIN_INDEX@@
+19=@@VBOX_INSERT_LOCALE@@
+20=@@VBOX_INSERT_DASH_LOCALE@@
+21=@@VBOX_INSERT_LANGUAGE@@
+22=@@VBOX_INSERT_COUNTRY@@
+23=@@VBOX_INSERT_HOSTNAME_FQDN@@
+24=@@VBOX_INSERT_HOSTNAME_WITHOUT_DOMAIN@@
+25=@@VBOX_INSERT_HOSTNAME_WITHOUT_DOMAIN_MAX_15@@
+26=@@VBOX_INSERT_HOSTNAME_DOMAIN@@
+27=@@VBOX_INSERT_PROXY@@
+
+/* indicators */
+01=@@VBOX_INSERT_IS_INSTALLING_ADDITIONS@@
+02=@@VBOX_INSERT_IS_USER_LOGIN_ADMINISTRATOR@@
+03=@@VBOX_INSERT_IS_INSTALLING_TEST_EXEC_SERVICE@@
+04=@@VBOX_INSERT_HAS_POST_INSTALL_COMMAND@@
+05=@@VBOX_INSERT_HAS_PRODUCT_KEY@@
+06=@@VBOX_INSERT_IS_MINIMAL_INSTALLATION@@
+07=@@VBOX_INSERT_IS_FIRMWARE_UEFI@@
+08=@@VBOX_INSERT_IS_RTC_USING_UTC@@
+09=@@VBOX_INSERT_HAS_PROXY@@
+
+
+/*
+ * Expression inserts
+ */
+/* variables */
+01=@@VBOX_INSERT[${USER_LOGIN}]@@
+02=@@VBOX_INSERT[${USER_PASSWORD}]@@
+03=@@VBOX_INSERT[${ROOT_PASSWORD}]@@
+04=@@VBOX_INSERT[${USER_FULL_NAME}]@@
+05=@@VBOX_INSERT[${PRODUCT_KEY}]@@
+06=@@VBOX_INSERT[${POST_INSTALL_COMMAND}]@@
+07=@@VBOX_INSERT[${AUXILIARY_INSTALL_DIR}]@@
+08=@@VBOX_INSERT[${IMAGE_INDEX}]@@
+09=@@VBOX_INSERT[${OS_ARCH}]@@
+10=@@VBOX_INSERT[${OS_ARCH2}]@@
+11=@@VBOX_INSERT[${OS_ARCH3}]@@
+12=@@VBOX_INSERT[${OS_ARCH4}]@@
+13=@@VBOX_INSERT[${OS_ARCH6}]@@
+14=@@VBOX_INSERT[${GUEST_OS_VERSION}]@@
+15=@@VBOX_INSERT[${GUEST_OS_MAJOR_VERSION}]@@
+16=@@VBOX_INSERT[${TIME_ZONE_UX}]@@
+17=@@VBOX_INSERT[${TIME_ZONE_WIN_NAME}]@@
+18=@@VBOX_INSERT[${TIME_ZONE_WIN_INDEX}]@@
+19=@@VBOX_INSERT[${LOCALE}]@@
+20=@@VBOX_INSERT[${DASH_LOCALE}]@@
+21=@@VBOX_INSERT[${LANGUAGE}]@@
+22=@@VBOX_INSERT[${COUNTRY}]@@
+23=@@VBOX_INSERT[${HOSTNAME_FQDN}]@@
+24=@@VBOX_INSERT[${HOSTNAME_WITHOUT_DOMAIN}]@@
+25=@@VBOX_INSERT[${HOSTNAME_WITHOUT_DOMAIN_MAX_15}]@@
+26=@@VBOX_INSERT[${HOSTNAME_DOMAIN}]@@
+27=@@VBOX_INSERT[${PROXY}]@@
+
+/* indicators */
+01=@@VBOX_INSERT[${IS_INSTALLING_ADDITIONS}]@@
+02=@@VBOX_INSERT[${IS_USER_LOGIN_ADMINISTRATOR}]@@
+03=@@VBOX_INSERT[${IS_INSTALLING_TEST_EXEC_SERVICE}]@@
+04=@@VBOX_INSERT[${HAS_POST_INSTALL_COMMAND}]@@
+05=@@VBOX_INSERT[${HAS_PRODUCT_KEY}]@@
+06=@@VBOX_INSERT[${IS_MINIMAL_INSTALLATION}]@@
+07=@@VBOX_INSERT[${IS_FIRMWARE_UEFI}]@@
+08=@@VBOX_INSERT[${IS_RTC_USING_UTC}]@@
+09=@@VBOX_INSERT[${HAS_PROXY}]@@
+
+
+/*
+ * Regular inserts with shell quoting.
+ */
+/* variables */
+01=@@VBOX_INSERT_USER_LOGIN_SH@@
+02=@@VBOX_INSERT_USER_PASSWORD_SH@@
+03=@@VBOX_INSERT_ROOT_PASSWORD_SH@@
+04=@@VBOX_INSERT_USER_FULL_NAME_SH@@
+05=@@VBOX_INSERT_PRODUCT_KEY_SH@@
+06=@@VBOX_INSERT_POST_INSTALL_COMMAND_SH@@
+07=@@VBOX_INSERT_AUXILIARY_INSTALL_DIR_SH@@
+08=@@VBOX_INSERT_IMAGE_INDEX_SH@@
+09=@@VBOX_INSERT_OS_ARCH_SH@@
+10=@@VBOX_INSERT_OS_ARCH2_SH@@
+11=@@VBOX_INSERT_OS_ARCH3_SH@@
+12=@@VBOX_INSERT_OS_ARCH4_SH@@
+13=@@VBOX_INSERT_OS_ARCH6_SH@@
+14=@@VBOX_INSERT_GUEST_OS_VERSION_SH@@
+15=@@VBOX_INSERT_GUEST_OS_MAJOR_VERSION_SH@@
+16=@@VBOX_INSERT_TIME_ZONE_UX_SH@@
+17=@@VBOX_INSERT_TIME_ZONE_WIN_NAME_SH@@
+18=@@VBOX_INSERT_TIME_ZONE_WIN_INDEX_SH@@
+19=@@VBOX_INSERT_LOCALE_SH@@
+20=@@VBOX_INSERT_DASH_LOCALE_SH@@
+21=@@VBOX_INSERT_LANGUAGE_SH@@
+22=@@VBOX_INSERT_COUNTRY_SH@@
+23=@@VBOX_INSERT_HOSTNAME_FQDN_SH@@
+24=@@VBOX_INSERT_HOSTNAME_WITHOUT_DOMAIN_SH@@
+25=@@VBOX_INSERT_HOSTNAME_WITHOUT_DOMAIN_MAX_15_SH@@
+26=@@VBOX_INSERT_HOSTNAME_DOMAIN_SH@@
+27=@@VBOX_INSERT_PROXY_SH@@
+
+/* indicators */
+01=@@VBOX_INSERT_IS_INSTALLING_ADDITIONS_SH@@
+02=@@VBOX_INSERT_IS_USER_LOGIN_ADMINISTRATOR_SH@@
+03=@@VBOX_INSERT_IS_INSTALLING_TEST_EXEC_SERVICE_SH@@
+04=@@VBOX_INSERT_HAS_POST_INSTALL_COMMAND_SH@@
+05=@@VBOX_INSERT_HAS_PRODUCT_KEY_SH@@
+06=@@VBOX_INSERT_IS_MINIMAL_INSTALLATION_SH@@
+07=@@VBOX_INSERT_IS_FIRMWARE_UEFI_SH@@
+08=@@VBOX_INSERT_IS_RTC_USING_UTC_SH@@
+09=@@VBOX_INSERT_HAS_PROXY_SH@@
+
+
+/*
+ * Expression inserts with shell quoting.
+ */
+/* variables */
+01=@@VBOX_INSERT[${USER_LOGIN}]SH@@
+02=@@VBOX_INSERT[${USER_PASSWORD}]SH@@
+03=@@VBOX_INSERT[${ROOT_PASSWORD}]SH@@
+04=@@VBOX_INSERT[${USER_FULL_NAME}]SH@@
+05=@@VBOX_INSERT[${PRODUCT_KEY}]SH@@
+06=@@VBOX_INSERT[${POST_INSTALL_COMMAND}]SH@@
+07=@@VBOX_INSERT[${AUXILIARY_INSTALL_DIR}]SH@@
+08=@@VBOX_INSERT[${IMAGE_INDEX}]SH@@
+09=@@VBOX_INSERT[${OS_ARCH}]SH@@
+10=@@VBOX_INSERT[${OS_ARCH2}]SH@@
+11=@@VBOX_INSERT[${OS_ARCH3}]SH@@
+12=@@VBOX_INSERT[${OS_ARCH4}]SH@@
+13=@@VBOX_INSERT[${OS_ARCH6}]SH@@
+14=@@VBOX_INSERT[${GUEST_OS_VERSION}]SH@@
+15=@@VBOX_INSERT[${GUEST_OS_MAJOR_VERSION}]SH@@
+16=@@VBOX_INSERT[${TIME_ZONE_UX}]SH@@
+17=@@VBOX_INSERT[${TIME_ZONE_WIN_NAME}]SH@@
+18=@@VBOX_INSERT[${TIME_ZONE_WIN_INDEX}]SH@@
+19=@@VBOX_INSERT[${LOCALE}]SH@@
+20=@@VBOX_INSERT[${DASH_LOCALE}]SH@@
+21=@@VBOX_INSERT[${LANGUAGE}]SH@@
+22=@@VBOX_INSERT[${COUNTRY}]SH@@
+23=@@VBOX_INSERT[${HOSTNAME_FQDN}]SH@@
+24=@@VBOX_INSERT[${HOSTNAME_WITHOUT_DOMAIN}]SH@@
+25=@@VBOX_INSERT[${HOSTNAME_WITHOUT_DOMAIN_MAX_15}]SH@@
+26=@@VBOX_INSERT[${HOSTNAME_DOMAIN}]SH@@
+27=@@VBOX_INSERT[${PROXY}]SH@@
+
+/* indicators */
+01=@@VBOX_INSERT[${IS_INSTALLING_ADDITIONS}]SH@@
+02=@@VBOX_INSERT[${IS_USER_LOGIN_ADMINISTRATOR}]SH@@
+03=@@VBOX_INSERT[${IS_INSTALLING_TEST_EXEC_SERVICE}]SH@@
+04=@@VBOX_INSERT[${HAS_POST_INSTALL_COMMAND}]SH@@
+05=@@VBOX_INSERT[${HAS_PRODUCT_KEY}]SH@@
+06=@@VBOX_INSERT[${IS_MINIMAL_INSTALLATION}]SH@@
+07=@@VBOX_INSERT[${IS_FIRMWARE_UEFI}]SH@@
+08=@@VBOX_INSERT[${IS_RTC_USING_UTC}]SH@@
+09=@@VBOX_INSERT[${HAS_PROXY}]SH@@
+
+
+/*
+ * Regular inserts escaped for use in XML element.
+ */
+/* variables */
+01=@@VBOX_INSERT_USER_LOGIN_ELEMENT@@
+02=@@VBOX_INSERT_USER_PASSWORD_ELEMENT@@
+03=@@VBOX_INSERT_ROOT_PASSWORD_ELEMENT@@
+04=@@VBOX_INSERT_USER_FULL_NAME_ELEMENT@@
+05=@@VBOX_INSERT_PRODUCT_KEY_ELEMENT@@
+06=@@VBOX_INSERT_POST_INSTALL_COMMAND_ELEMENT@@
+07=@@VBOX_INSERT_AUXILIARY_INSTALL_DIR_ELEMENT@@
+08=@@VBOX_INSERT_IMAGE_INDEX_ELEMENT@@
+09=@@VBOX_INSERT_OS_ARCH_ELEMENT@@
+10=@@VBOX_INSERT_OS_ARCH2_ELEMENT@@
+11=@@VBOX_INSERT_OS_ARCH3_ELEMENT@@
+12=@@VBOX_INSERT_OS_ARCH4_ELEMENT@@
+13=@@VBOX_INSERT_OS_ARCH6_ELEMENT@@
+14=@@VBOX_INSERT_GUEST_OS_VERSION_ELEMENT@@
+15=@@VBOX_INSERT_GUEST_OS_MAJOR_VERSION_ELEMENT@@
+16=@@VBOX_INSERT_TIME_ZONE_UX_ELEMENT@@
+17=@@VBOX_INSERT_TIME_ZONE_WIN_NAME_ELEMENT@@
+18=@@VBOX_INSERT_TIME_ZONE_WIN_INDEX_ELEMENT@@
+19=@@VBOX_INSERT_LOCALE_ELEMENT@@
+20=@@VBOX_INSERT_DASH_LOCALE_ELEMENT@@
+21=@@VBOX_INSERT_LANGUAGE_ELEMENT@@
+22=@@VBOX_INSERT_COUNTRY_ELEMENT@@
+23=@@VBOX_INSERT_HOSTNAME_FQDN_ELEMENT@@
+24=@@VBOX_INSERT_HOSTNAME_WITHOUT_DOMAIN_ELEMENT@@
+25=@@VBOX_INSERT_HOSTNAME_WITHOUT_DOMAIN_MAX_15_ELEMENT@@
+26=@@VBOX_INSERT_HOSTNAME_DOMAIN_ELEMENT@@
+27=@@VBOX_INSERT_PROXY_ELEMENT@@
+
+/* indicators */
+01=@@VBOX_INSERT_IS_INSTALLING_ADDITIONS_ELEMENT@@
+02=@@VBOX_INSERT_IS_USER_LOGIN_ADMINISTRATOR_ELEMENT@@
+03=@@VBOX_INSERT_IS_INSTALLING_TEST_EXEC_SERVICE_ELEMENT@@
+04=@@VBOX_INSERT_HAS_POST_INSTALL_COMMAND_ELEMENT@@
+05=@@VBOX_INSERT_HAS_PRODUCT_KEY_ELEMENT@@
+06=@@VBOX_INSERT_IS_MINIMAL_INSTALLATION_ELEMENT@@
+07=@@VBOX_INSERT_IS_FIRMWARE_UEFI_ELEMENT@@
+08=@@VBOX_INSERT_IS_RTC_USING_UTC_ELEMENT@@
+09=@@VBOX_INSERT_HAS_PROXY_ELEMENT@@
+
+
+/*
+ * Expression inserts escaped for use in XML element.
+ */
+/* variables */
+01=@@VBOX_INSERT[${USER_LOGIN}]ELEMENT@@
+02=@@VBOX_INSERT[${USER_PASSWORD}]ELEMENT@@
+03=@@VBOX_INSERT[${ROOT_PASSWORD}]ELEMENT@@
+04=@@VBOX_INSERT[${USER_FULL_NAME}]ELEMENT@@
+05=@@VBOX_INSERT[${PRODUCT_KEY}]ELEMENT@@
+06=@@VBOX_INSERT[${POST_INSTALL_COMMAND}]ELEMENT@@
+07=@@VBOX_INSERT[${AUXILIARY_INSTALL_DIR}]ELEMENT@@
+08=@@VBOX_INSERT[${IMAGE_INDEX}]ELEMENT@@
+09=@@VBOX_INSERT[${OS_ARCH}]ELEMENT@@
+10=@@VBOX_INSERT[${OS_ARCH2}]ELEMENT@@
+11=@@VBOX_INSERT[${OS_ARCH3}]ELEMENT@@
+12=@@VBOX_INSERT[${OS_ARCH4}]ELEMENT@@
+13=@@VBOX_INSERT[${OS_ARCH6}]ELEMENT@@
+14=@@VBOX_INSERT[${GUEST_OS_VERSION}]ELEMENT@@
+15=@@VBOX_INSERT[${GUEST_OS_MAJOR_VERSION}]ELEMENT@@
+16=@@VBOX_INSERT[${TIME_ZONE_UX}]ELEMENT@@
+17=@@VBOX_INSERT[${TIME_ZONE_WIN_NAME}]ELEMENT@@
+18=@@VBOX_INSERT[${TIME_ZONE_WIN_INDEX}]ELEMENT@@
+19=@@VBOX_INSERT[${LOCALE}]ELEMENT@@
+20=@@VBOX_INSERT[${DASH_LOCALE}]ELEMENT@@
+21=@@VBOX_INSERT[${LANGUAGE}]ELEMENT@@
+22=@@VBOX_INSERT[${COUNTRY}]ELEMENT@@
+23=@@VBOX_INSERT[${HOSTNAME_FQDN}]ELEMENT@@
+24=@@VBOX_INSERT[${HOSTNAME_WITHOUT_DOMAIN}]ELEMENT@@
+25=@@VBOX_INSERT[${HOSTNAME_WITHOUT_DOMAIN_MAX_15}]ELEMENT@@
+26=@@VBOX_INSERT[${HOSTNAME_DOMAIN}]ELEMENT@@
+27=@@VBOX_INSERT[${PROXY}]ELEMENT@@
+
+/* indicators */
+01=@@VBOX_INSERT[${IS_INSTALLING_ADDITIONS}]ELEMENT@@
+02=@@VBOX_INSERT[${IS_USER_LOGIN_ADMINISTRATOR}]ELEMENT@@
+03=@@VBOX_INSERT[${IS_INSTALLING_TEST_EXEC_SERVICE}]ELEMENT@@
+04=@@VBOX_INSERT[${HAS_POST_INSTALL_COMMAND}]ELEMENT@@
+05=@@VBOX_INSERT[${HAS_PRODUCT_KEY}]ELEMENT@@
+06=@@VBOX_INSERT[${IS_MINIMAL_INSTALLATION}]ELEMENT@@
+07=@@VBOX_INSERT[${IS_FIRMWARE_UEFI}]ELEMENT@@
+08=@@VBOX_INSERT[${IS_RTC_USING_UTC}]ELEMENT@@
+09=@@VBOX_INSERT[${HAS_PROXY}]ELEMENT@@
+
+
+/*
+ * Regular inserts escaped for use in double quoted attributes.
+ */
+/* variables */
+01=@@VBOX_INSERT_USER_LOGIN_ATTRIB_DQ@@
+02=@@VBOX_INSERT_USER_PASSWORD_ATTRIB_DQ@@
+03=@@VBOX_INSERT_ROOT_PASSWORD_ATTRIB_DQ@@
+04=@@VBOX_INSERT_USER_FULL_NAME_ATTRIB_DQ@@
+05=@@VBOX_INSERT_PRODUCT_KEY_ATTRIB_DQ@@
+06=@@VBOX_INSERT_POST_INSTALL_COMMAND_ATTRIB_DQ@@
+07=@@VBOX_INSERT_AUXILIARY_INSTALL_DIR_ATTRIB_DQ@@
+08=@@VBOX_INSERT_IMAGE_INDEX_ATTRIB_DQ@@
+09=@@VBOX_INSERT_OS_ARCH_ATTRIB_DQ@@
+10=@@VBOX_INSERT_OS_ARCH2_ATTRIB_DQ@@
+11=@@VBOX_INSERT_OS_ARCH3_ATTRIB_DQ@@
+12=@@VBOX_INSERT_OS_ARCH4_ATTRIB_DQ@@
+13=@@VBOX_INSERT_OS_ARCH6_ATTRIB_DQ@@
+14=@@VBOX_INSERT_GUEST_OS_VERSION_ATTRIB_DQ@@
+15=@@VBOX_INSERT_GUEST_OS_MAJOR_VERSION_ATTRIB_DQ@@
+16=@@VBOX_INSERT_TIME_ZONE_UX_ATTRIB_DQ@@
+17=@@VBOX_INSERT_TIME_ZONE_WIN_NAME_ATTRIB_DQ@@
+18=@@VBOX_INSERT_TIME_ZONE_WIN_INDEX_ATTRIB_DQ@@
+19=@@VBOX_INSERT_LOCALE_ATTRIB_DQ@@
+20=@@VBOX_INSERT_DASH_LOCALE_ATTRIB_DQ@@
+21=@@VBOX_INSERT_LANGUAGE_ATTRIB_DQ@@
+22=@@VBOX_INSERT_COUNTRY_ATTRIB_DQ@@
+23=@@VBOX_INSERT_HOSTNAME_FQDN_ATTRIB_DQ@@
+24=@@VBOX_INSERT_HOSTNAME_WITHOUT_DOMAIN_ATTRIB_DQ@@
+25=@@VBOX_INSERT_HOSTNAME_WITHOUT_DOMAIN_MAX_15_ATTRIB_DQ@@
+26=@@VBOX_INSERT_HOSTNAME_DOMAIN_ATTRIB_DQ@@
+27=@@VBOX_INSERT_PROXY_ATTRIB_DQ@@
+
+/* indicators */
+01=@@VBOX_INSERT_IS_INSTALLING_ADDITIONS_ATTRIB_DQ@@
+02=@@VBOX_INSERT_IS_USER_LOGIN_ADMINISTRATOR_ATTRIB_DQ@@
+03=@@VBOX_INSERT_IS_INSTALLING_TEST_EXEC_SERVICE_ATTRIB_DQ@@
+04=@@VBOX_INSERT_HAS_POST_INSTALL_COMMAND_ATTRIB_DQ@@
+05=@@VBOX_INSERT_HAS_PRODUCT_KEY_ATTRIB_DQ@@
+06=@@VBOX_INSERT_IS_MINIMAL_INSTALLATION_ATTRIB_DQ@@
+07=@@VBOX_INSERT_IS_FIRMWARE_UEFI_ATTRIB_DQ@@
+08=@@VBOX_INSERT_IS_RTC_USING_UTC_ATTRIB_DQ@@
+09=@@VBOX_INSERT_HAS_PROXY_ATTRIB_DQ@@
+
+
+/*
+ * Expression inserts escaped for use in double quoted attributes.
+ */
+/* variables */
+01=@@VBOX_INSERT[${USER_LOGIN}]ATTRIB_DQ@@
+02=@@VBOX_INSERT[${USER_PASSWORD}]ATTRIB_DQ@@
+03=@@VBOX_INSERT[${ROOT_PASSWORD}]ATTRIB_DQ@@
+04=@@VBOX_INSERT[${USER_FULL_NAME}]ATTRIB_DQ@@
+05=@@VBOX_INSERT[${PRODUCT_KEY}]ATTRIB_DQ@@
+06=@@VBOX_INSERT[${POST_INSTALL_COMMAND}]ATTRIB_DQ@@
+07=@@VBOX_INSERT[${AUXILIARY_INSTALL_DIR}]ATTRIB_DQ@@
+08=@@VBOX_INSERT[${IMAGE_INDEX}]ATTRIB_DQ@@
+09=@@VBOX_INSERT[${OS_ARCH}]ATTRIB_DQ@@
+10=@@VBOX_INSERT[${OS_ARCH2}]ATTRIB_DQ@@
+11=@@VBOX_INSERT[${OS_ARCH3}]ATTRIB_DQ@@
+12=@@VBOX_INSERT[${OS_ARCH4}]ATTRIB_DQ@@
+13=@@VBOX_INSERT[${OS_ARCH6}]ATTRIB_DQ@@
+14=@@VBOX_INSERT[${GUEST_OS_VERSION}]ATTRIB_DQ@@
+15=@@VBOX_INSERT[${GUEST_OS_MAJOR_VERSION}]ATTRIB_DQ@@
+16=@@VBOX_INSERT[${TIME_ZONE_UX}]ATTRIB_DQ@@
+17=@@VBOX_INSERT[${TIME_ZONE_WIN_NAME}]ATTRIB_DQ@@
+18=@@VBOX_INSERT[${TIME_ZONE_WIN_INDEX}]ATTRIB_DQ@@
+19=@@VBOX_INSERT[${LOCALE}]ATTRIB_DQ@@
+20=@@VBOX_INSERT[${DASH_LOCALE}]ATTRIB_DQ@@
+21=@@VBOX_INSERT[${LANGUAGE}]ATTRIB_DQ@@
+22=@@VBOX_INSERT[${COUNTRY}]ATTRIB_DQ@@
+23=@@VBOX_INSERT[${HOSTNAME_FQDN}]ATTRIB_DQ@@
+24=@@VBOX_INSERT[${HOSTNAME_WITHOUT_DOMAIN}]ATTRIB_DQ@@
+25=@@VBOX_INSERT[${HOSTNAME_WITHOUT_DOMAIN_MAX_15}]ATTRIB_DQ@@
+26=@@VBOX_INSERT[${HOSTNAME_DOMAIN}]ATTRIB_DQ@@
+27=@@VBOX_INSERT[${PROXY}]ATTRIB_DQ@@
+
+/* indicators */
+01=@@VBOX_INSERT[${IS_INSTALLING_ADDITIONS}]ATTRIB_DQ@@
+02=@@VBOX_INSERT[${IS_USER_LOGIN_ADMINISTRATOR}]ATTRIB_DQ@@
+03=@@VBOX_INSERT[${IS_INSTALLING_TEST_EXEC_SERVICE}]ATTRIB_DQ@@
+04=@@VBOX_INSERT[${HAS_POST_INSTALL_COMMAND}]ATTRIB_DQ@@
+05=@@VBOX_INSERT[${HAS_PRODUCT_KEY}]ATTRIB_DQ@@
+06=@@VBOX_INSERT[${IS_MINIMAL_INSTALLATION}]ATTRIB_DQ@@
+07=@@VBOX_INSERT[${IS_FIRMWARE_UEFI}]ATTRIB_DQ@@
+08=@@VBOX_INSERT[${IS_RTC_USING_UTC}]ATTRIB_DQ@@
+09=@@VBOX_INSERT[${HAS_PROXY}]ATTRIB_DQ@@
+
+
+/*
+ * Some typical expression conditions.
+ */
+01: @@VBOX_COND[${GUEST_OS_VERSION} vge 1.2.3]@@ GUEST_OS_VERSION >= 1.2.3@@VBOX_COND_ELSE@@ failed@@VBOX_COND_END@@
+02: @@VBOX_COND[${GUEST_OS_VERSION} vle 3.4.2]@@ GUEST_OS_VERSION <= 3.4.2@@VBOX_COND_ELSE@@ failed@@VBOX_COND_END@@
diff --git a/src/VBox/Main/testcase/tstUnattendedScript.cpp b/src/VBox/Main/testcase/tstUnattendedScript.cpp
new file mode 100644
index 00000000..09d3f37a
--- /dev/null
+++ b/src/VBox/Main/testcase/tstUnattendedScript.cpp
@@ -0,0 +1,731 @@
+/* $Id: tstUnattendedScript.cpp $ */
+/** @file
+ * tstUnattendedScript - testcases for UnattendedScript.
+ */
+
+/*
+ * Copyright (C) 2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "UnattendedScript.h"
+
+#include <VBox/com/VirtualBox.h>
+#include <VBox/com/errorprint.h>
+
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/test.h>
+#include <iprt/stream.h>
+
+#include "VirtualBoxBase.h"
+#include "UnattendedImpl.h"
+#include "UnattendedScript.h"
+#include "VirtualBoxImpl.h"
+#include "MachineImpl.h"
+
+using namespace std;
+
+
+/*********************************************************************************************************************************
+* Unattended Stub Implementation *
+*********************************************************************************************************************************/
+Unattended::Unattended()
+ : mhThreadReconfigureVM(NIL_RTNATIVETHREAD), mfRtcUseUtc(false), mfGuestOs64Bit(false)
+ , mpInstaller(NULL), mpTimeZoneInfo(NULL), mfIsDefaultAuxiliaryBasePath(true), mfDoneDetectIsoOS(false)
+{
+ mStrUser = "vboxuser";
+ mStrPassword = "changeme";
+ mStrFullUserName = "VBox & VBox;";
+ mStrProductKey = "911";
+ mStrIsoPath = "/iso/path/file.iso";
+ mStrAdditionsIsoPath = "/iso/path/addition.iso";
+ mfInstallGuestAdditions = true;
+ mfInstallTestExecService = true;
+ mStrValidationKitIsoPath = "/iso/path/valkit.iso";
+ mStrTimeZone = "cet";
+ mpTimeZoneInfo = NULL;
+ mStrLocale = "dk_DK";
+ mStrLanguage = "dk";
+ mStrCountry = "DK";
+ //mPackageSelectionAdjustments = "minimal";
+ mStrHostname = "my-extra-long-name.hostname.com";
+ mStrAuxiliaryBasePath = "/aux/path/pfx-";
+ mfIsDefaultAuxiliaryBasePath = false;
+ midxImage = 42;
+ mStrScriptTemplatePath = "/path/to/script-template.file";
+ mStrPostInstallScriptTemplatePath = "/path/to/post-install-template.file";
+ mStrPostInstallCommand = "/bin/post-install-command arg1 arg2 --amp=& --lt=< --gt=> --dq-word=\"word\" --sq-word='word'";
+ mStrExtraInstallKernelParameters = "extra=kernel parameters quiet amp=& lt=< gt=>";
+ mStrProxy = "http://proxy.intranet.com:443";
+
+ mfDoneDetectIsoOS = true;
+ mStrDetectedOSTypeId = "MyOSTypeId";
+ mStrDetectedOSVersion = "3.4.2";
+ mStrDetectedOSFlavor = "server";
+ //mDetectedOSLanguages = "en_UK"
+ mStrDetectedOSHints = "nudge nudge wink wink";
+}
+
+Unattended::~Unattended()
+{
+}
+
+HRESULT Unattended::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void Unattended::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+void Unattended::uninit()
+{
+}
+
+HRESULT Unattended::initUnattended(VirtualBox *aParent)
+{
+ unconst(mParent) = aParent;
+ return S_OK;
+}
+
+HRESULT Unattended::detectIsoOS()
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT Unattended::prepare()
+{
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::constructMedia()
+{
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::reconfigureVM()
+{
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::done()
+{
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getIsoPath(com::Utf8Str &isoPath)
+{
+ RT_NOREF(isoPath);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setIsoPath(const com::Utf8Str &isoPath)
+{
+ RT_NOREF(isoPath);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getUser(com::Utf8Str &user)
+{
+ RT_NOREF(user);
+ return E_NOTIMPL;
+}
+
+
+HRESULT Unattended::setUser(const com::Utf8Str &user)
+{
+ RT_NOREF(user);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getPassword(com::Utf8Str &password)
+{
+ RT_NOREF(password);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setPassword(const com::Utf8Str &password)
+{
+ RT_NOREF(password);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getFullUserName(com::Utf8Str &fullUserName)
+{
+ RT_NOREF(fullUserName);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setFullUserName(const com::Utf8Str &fullUserName)
+{
+ RT_NOREF(fullUserName);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getProductKey(com::Utf8Str &productKey)
+{
+ RT_NOREF(productKey);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setProductKey(const com::Utf8Str &productKey)
+{
+ RT_NOREF(productKey);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getAdditionsIsoPath(com::Utf8Str &additionsIsoPath)
+{
+ RT_NOREF(additionsIsoPath);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setAdditionsIsoPath(const com::Utf8Str &additionsIsoPath)
+{
+ RT_NOREF(additionsIsoPath);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getInstallGuestAdditions(BOOL *installGuestAdditions)
+{
+ RT_NOREF(installGuestAdditions);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setInstallGuestAdditions(BOOL installGuestAdditions)
+{
+ RT_NOREF(installGuestAdditions);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getValidationKitIsoPath(com::Utf8Str &aValidationKitIsoPath)
+{
+ RT_NOREF(aValidationKitIsoPath);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setValidationKitIsoPath(const com::Utf8Str &aValidationKitIsoPath)
+{
+ RT_NOREF(aValidationKitIsoPath);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getInstallTestExecService(BOOL *aInstallTestExecService)
+{
+ RT_NOREF(aInstallTestExecService);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setInstallTestExecService(BOOL aInstallTestExecService)
+{
+ RT_NOREF(aInstallTestExecService);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getTimeZone(com::Utf8Str &aTimeZone)
+{
+ RT_NOREF(aTimeZone);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setTimeZone(const com::Utf8Str &aTimezone)
+{
+ RT_NOREF(aTimezone);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getLocale(com::Utf8Str &aLocale)
+{
+ RT_NOREF(aLocale);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setLocale(const com::Utf8Str &aLocale)
+{
+ RT_NOREF(aLocale);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getLanguage(com::Utf8Str &aLanguage)
+{
+ RT_NOREF(aLanguage);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setLanguage(const com::Utf8Str &aLanguage)
+{
+ RT_NOREF(aLanguage);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getCountry(com::Utf8Str &aCountry)
+{
+ RT_NOREF(aCountry);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setCountry(const com::Utf8Str &aCountry)
+{
+ RT_NOREF(aCountry);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getProxy(com::Utf8Str &aProxy)
+{
+ RT_NOREF(aProxy);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setProxy(const com::Utf8Str &aProxy)
+{
+ RT_NOREF(aProxy);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getPackageSelectionAdjustments(com::Utf8Str &aPackageSelectionAdjustments)
+{
+ RT_NOREF(aPackageSelectionAdjustments);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setPackageSelectionAdjustments(const com::Utf8Str &aPackageSelectionAdjustments)
+{
+ RT_NOREF(aPackageSelectionAdjustments);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getHostname(com::Utf8Str &aHostname)
+{
+ RT_NOREF(aHostname);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setHostname(const com::Utf8Str &aHostname)
+{
+ RT_NOREF(aHostname);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getAuxiliaryBasePath(com::Utf8Str &aAuxiliaryBasePath)
+{
+ RT_NOREF(aAuxiliaryBasePath);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setAuxiliaryBasePath(const com::Utf8Str &aAuxiliaryBasePath)
+{
+ RT_NOREF(aAuxiliaryBasePath);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getImageIndex(ULONG *index)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *index = midxImage;
+ return S_OK;
+}
+
+HRESULT Unattended::setImageIndex(ULONG index)
+{
+ RT_NOREF(index);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getMachine(ComPtr<IMachine> &aMachine)
+{
+ RT_NOREF(aMachine);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setMachine(const ComPtr<IMachine> &aMachine)
+{
+ RT_NOREF(aMachine);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getScriptTemplatePath(com::Utf8Str &aScriptTemplatePath)
+{
+ RT_NOREF(aScriptTemplatePath);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setScriptTemplatePath(const com::Utf8Str &aScriptTemplatePath)
+{
+ RT_NOREF(aScriptTemplatePath);
+ return E_NOTIMPL;
+
+}
+
+HRESULT Unattended::getPostInstallScriptTemplatePath(com::Utf8Str &aPostInstallScriptTemplatePath)
+{
+ RT_NOREF(aPostInstallScriptTemplatePath);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setPostInstallScriptTemplatePath(const com::Utf8Str &aPostInstallScriptTemplatePath)
+{
+ RT_NOREF(aPostInstallScriptTemplatePath);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getPostInstallCommand(com::Utf8Str &aPostInstallCommand)
+{
+ RT_NOREF(aPostInstallCommand);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setPostInstallCommand(const com::Utf8Str &aPostInstallCommand)
+{
+ RT_NOREF(aPostInstallCommand);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getExtraInstallKernelParameters(com::Utf8Str &aExtraInstallKernelParameters)
+{
+ RT_NOREF(aExtraInstallKernelParameters);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setExtraInstallKernelParameters(const com::Utf8Str &aExtraInstallKernelParameters)
+{
+ RT_NOREF(aExtraInstallKernelParameters);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getDetectedOSTypeId(com::Utf8Str &aDetectedOSTypeId)
+{
+ RT_NOREF(aDetectedOSTypeId);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getDetectedOSVersion(com::Utf8Str &aDetectedOSVersion)
+{
+ RT_NOREF(aDetectedOSVersion);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getDetectedOSFlavor(com::Utf8Str &aDetectedOSFlavor)
+{
+ RT_NOREF(aDetectedOSFlavor);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getDetectedOSLanguages(com::Utf8Str &aDetectedOSLanguages)
+{
+ RT_NOREF(aDetectedOSLanguages);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getDetectedOSHints(com::Utf8Str &aDetectedOSHints)
+{
+ RT_NOREF(aDetectedOSHints);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getDetectedImageNames(std::vector<com::Utf8Str> &aDetectedImageNames)
+{
+ RT_NOREF(aDetectedImageNames);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getDetectedImageIndices(std::vector<ULONG> &aDetectedImageIndices)
+{
+ RT_NOREF(aDetectedImageIndices);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getIsUnattendedInstallSupported(BOOL *aIsUnattendedInstallSupported)
+{
+ RT_NOREF(aIsUnattendedInstallSupported);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::getAvoidUpdatesOverNetwork(BOOL *aAvoidUpdatesOverNetwork)
+{
+ RT_NOREF(aAvoidUpdatesOverNetwork);
+ return E_NOTIMPL;
+}
+
+HRESULT Unattended::setAvoidUpdatesOverNetwork(BOOL aAvoidUpdatesOverNetwork)
+{
+ RT_NOREF(aAvoidUpdatesOverNetwork);
+ return E_NOTIMPL;
+}
+
+
+/*
+ * Getters that the installer and script classes can use.
+ */
+Utf8Str const &Unattended::i_getIsoPath() const
+{
+ return mStrIsoPath;
+}
+
+Utf8Str const &Unattended::i_getUser() const
+{
+ return mStrUser;
+}
+
+Utf8Str const &Unattended::i_getPassword() const
+{
+ return mStrPassword;
+}
+
+Utf8Str const &Unattended::i_getFullUserName() const
+{
+ return mStrFullUserName.isNotEmpty() ? mStrFullUserName : mStrUser;
+}
+
+Utf8Str const &Unattended::i_getProductKey() const
+{
+ return mStrProductKey;
+}
+
+Utf8Str const &Unattended::i_getProxy() const
+{
+ return mStrProxy;
+}
+
+Utf8Str const &Unattended::i_getAdditionsIsoPath() const
+{
+ return mStrAdditionsIsoPath;
+}
+
+bool Unattended::i_getInstallGuestAdditions() const
+{
+ return mfInstallGuestAdditions;
+}
+
+Utf8Str const &Unattended::i_getValidationKitIsoPath() const
+{
+ return mStrValidationKitIsoPath;
+}
+
+bool Unattended::i_getInstallTestExecService() const
+{
+ return mfInstallTestExecService;
+}
+
+Utf8Str const &Unattended::i_getTimeZone() const
+{
+ return mStrTimeZone;
+}
+
+PCRTTIMEZONEINFO Unattended::i_getTimeZoneInfo() const
+{
+ return mpTimeZoneInfo;
+}
+
+Utf8Str const &Unattended::i_getLocale() const
+{
+ return mStrLocale;
+}
+
+Utf8Str const &Unattended::i_getLanguage() const
+{
+ return mStrLanguage;
+}
+
+Utf8Str const &Unattended::i_getCountry() const
+{
+ return mStrCountry;
+}
+
+bool Unattended::i_isMinimalInstallation() const
+{
+ size_t i = mPackageSelectionAdjustments.size();
+ while (i-- > 0)
+ if (mPackageSelectionAdjustments[i].equals("minimal"))
+ return true;
+ return false;
+}
+
+Utf8Str const &Unattended::i_getHostname() const
+{
+ return mStrHostname;
+}
+
+Utf8Str const &Unattended::i_getAuxiliaryBasePath() const
+{
+ return mStrAuxiliaryBasePath;
+}
+
+ULONG Unattended::i_getImageIndex() const
+{
+ return midxImage;
+}
+
+Utf8Str const &Unattended::i_getScriptTemplatePath() const
+{
+ return mStrScriptTemplatePath;
+}
+
+Utf8Str const &Unattended::i_getPostInstallScriptTemplatePath() const
+{
+ return mStrPostInstallScriptTemplatePath;
+}
+
+Utf8Str const &Unattended::i_getPostInstallCommand() const
+{
+ return mStrPostInstallCommand;
+}
+
+Utf8Str const &Unattended::i_getAuxiliaryInstallDir() const
+{
+ static Utf8Str s_strAuxInstallDir("/aux/install/dir");
+ return s_strAuxInstallDir;
+}
+
+Utf8Str const &Unattended::i_getExtraInstallKernelParameters() const
+{
+ return mStrExtraInstallKernelParameters;
+}
+
+bool Unattended::i_isRtcUsingUtc() const
+{
+ return mfRtcUseUtc;
+}
+
+bool Unattended::i_isGuestOs64Bit() const
+{
+ return mfGuestOs64Bit;
+}
+
+bool Unattended::i_isFirmwareEFI() const
+{
+ return menmFirmwareType != FirmwareType_BIOS;
+}
+
+Utf8Str const &Unattended::i_getDetectedOSVersion()
+{
+ return mStrDetectedOSVersion;
+}
+
+bool Unattended::i_getAvoidUpdatesOverNetwork() const
+{
+ return mfAvoidUpdatesOverNetwork;
+}
+
+
+/*********************************************************************************************************************************
+* The Testcase *
+*********************************************************************************************************************************/
+
+static bool loadFileAsString(const char *pszFilename, Utf8Str &rstrContent)
+{
+ rstrContent.setNull();
+
+ char szPath[RTPATH_MAX];
+ RTTESTI_CHECK_RC_RET(RTPathExecDir(szPath, sizeof(szPath)), VINF_SUCCESS, false);
+ RTTESTI_CHECK_RC_RET(RTPathAppend(szPath, sizeof(szPath), pszFilename), VINF_SUCCESS, false);
+
+ RTFILE hFile;
+ RTTESTI_CHECK_RC_RET(RTFileOpen(&hFile, szPath, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE), VINF_SUCCESS, false);
+
+ uint64_t cbFile = 0;
+ RTTESTI_CHECK_RC_RET(RTFileQuerySize(hFile, &cbFile), VINF_SUCCESS, false);
+
+ rstrContent.reserve((size_t)cbFile + 1);
+ RTTESTI_CHECK_RC_RET(RTFileRead(hFile, rstrContent.mutableRaw(), (size_t)cbFile, NULL), VINF_SUCCESS, false);
+ rstrContent.mutableRaw()[cbFile] = '\0';
+ rstrContent.jolt();
+
+ RTTESTI_CHECK_RC_RET(RTFileClose(hFile), VINF_SUCCESS, false);
+
+ return true;
+}
+
+static void doTest1()
+{
+ RTTestISub("tstUnattendedScript-1.template");
+
+ /* Create the parent class instance: */
+ ComObjPtr<Unattended> ptrParent;
+ HRESULT hrc = ptrParent.createObject();
+ RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("hrc=%Rhrc\n", hrc));
+
+ /* Instantiate the script editor. */
+ UnattendedScriptTemplate Tmpl(ptrParent, "template.ext", "file.ext");
+#define CHECK_HRESULT(a_Expr) do { \
+ HRESULT hrcThis = a_Expr; \
+ if (SUCCEEDED(hrcThis)) break; \
+ RTTestIFailed("line %d: %s -> %Rhrc", __LINE__, #a_Expr, hrcThis); \
+ GlueHandleComError(ptrParent, NULL, hrcThis, NULL, __LINE__); \
+ } while (0)
+
+ /* Load the exercise script. */
+ char szPath[RTPATH_MAX];
+ RTTESTI_CHECK_RC_RETV(RTPathExecDir(szPath, sizeof(szPath)), VINF_SUCCESS);
+ RTTESTI_CHECK_RC_RETV(RTPathAppend(szPath, sizeof(szPath), "tstUnattendedScript-1.template"), VINF_SUCCESS);
+ CHECK_HRESULT(Tmpl.read(szPath));
+
+ /* Save the template to string. */
+ Utf8Str strActual;
+ CHECK_HRESULT(Tmpl.saveToString(strActual));
+
+ /* Load the expected result. */
+ Utf8Str strExpected;
+ RTTESTI_CHECK_RETV(loadFileAsString("tstUnattendedScript-1.expected", strExpected));
+
+ /* Compare the two. */
+ if (strExpected != strActual)
+ {
+ RTTestIFailed("Output does not match tstUnattendedScript-1.expect!");
+ RTTestIFailureDetails("------ BEGIN OUTPUT ------\n");
+ RTStrmWrite(g_pStdErr, strActual.c_str(), strActual.length());
+ RTTestIFailureDetails("------- END OUTPUT -------\n");
+
+ RTCList<RTCString, RTCString *> const lstActual = strActual.split("\n");
+ RTCList<RTCString, RTCString *> const lstExpected = strExpected.split("\n");
+ size_t const cLines = RT_MIN(lstActual.size(), lstExpected.size());
+ for (size_t i = 0; i < cLines; i++)
+ if (lstActual[i] != lstExpected[i])
+ {
+ RTTestIFailureDetails("First difference on line %u:\n%s\nexpected:\n%s\n",
+ i + 1, lstActual[i].c_str(), lstExpected[i].c_str());
+ break;
+ }
+ }
+}
+
+int main()
+{
+ RTTEST hTest;
+ RTEXITCODE rcExit = RTTestInitAndCreate("tstUnattendedScript", &hTest);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+#ifdef RT_OS_WINDOWS
+ /*ATL::CComModule *g_pAtlComModule = */ new(ATL::CComModule);
+#endif
+
+ doTest1();
+
+ return RTTestSummaryAndDestroy(hTest);
+}
diff --git a/src/VBox/Main/testcase/tstVBoxAPI.cpp b/src/VBox/Main/testcase/tstVBoxAPI.cpp
new file mode 100644
index 00000000..c94b9eb3
--- /dev/null
+++ b/src/VBox/Main/testcase/tstVBoxAPI.cpp
@@ -0,0 +1,417 @@
+/* $Id: tstVBoxAPI.cpp $ */
+/** @file
+ * tstVBoxAPI - Checks VirtualBox API.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/com/com.h>
+#include <VBox/com/string.h>
+#include <VBox/com/array.h>
+#include <VBox/com/Guid.h>
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/com/errorprint.h>
+#include <VBox/com/VirtualBox.h>
+#include <VBox/sup.h>
+
+#include <iprt/test.h>
+#include <iprt/time.h>
+
+using namespace com;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static RTTEST g_hTest;
+static Bstr tstMachineName = "tstVBoxAPI test VM";
+
+
+/** Worker for TST_COM_EXPR(). */
+static HRESULT tstComExpr(HRESULT hrc, const char *pszOperation, int iLine)
+{
+ if (FAILED(hrc))
+ RTTestFailed(g_hTest, "%s failed on line %u with hrc=%Rhrc", pszOperation, iLine, hrc);
+ return hrc;
+}
+
+/** Macro that executes the given expression and report any failure.
+ * The expression must return a HRESULT. */
+#define TST_COM_EXPR(expr) tstComExpr(expr, #expr, __LINE__)
+
+
+static BOOL tstApiIVirtualBox(IVirtualBox *pVBox)
+{
+ HRESULT hrc;
+ Bstr bstrTmp;
+ ULONG ulTmp;
+
+ RTTestSub(g_hTest, "IVirtualBox::version");
+ CHECK_ERROR(pVBox, COMGETTER(Version)(bstrTmp.asOutParam()));
+ if (SUCCEEDED(hrc))
+ RTTestPassed(g_hTest, "IVirtualBox::version");
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::version failed", __LINE__);
+
+ RTTestSub(g_hTest, "IVirtualBox::versionNormalized");
+ CHECK_ERROR(pVBox, COMGETTER(VersionNormalized)(bstrTmp.asOutParam()));
+ if (SUCCEEDED(hrc))
+ RTTestPassed(g_hTest, "IVirtualBox::versionNormalized");
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::versionNormalized failed", __LINE__);
+
+ RTTestSub(g_hTest, "IVirtualBox::revision");
+ CHECK_ERROR(pVBox, COMGETTER(Revision)(&ulTmp));
+ if (SUCCEEDED(hrc))
+ RTTestPassed(g_hTest, "IVirtualBox::revision");
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::revision failed", __LINE__);
+
+ RTTestSub(g_hTest, "IVirtualBox::packageType");
+ CHECK_ERROR(pVBox, COMGETTER(PackageType)(bstrTmp.asOutParam()));
+ if (SUCCEEDED(hrc))
+ RTTestPassed(g_hTest, "IVirtualBox::packageType");
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::packageType failed", __LINE__);
+
+ RTTestSub(g_hTest, "IVirtualBox::APIVersion");
+ CHECK_ERROR(pVBox, COMGETTER(APIVersion)(bstrTmp.asOutParam()));
+ if (SUCCEEDED(hrc))
+ RTTestPassed(g_hTest, "IVirtualBox::APIVersion");
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::APIVersion failed", __LINE__);
+
+ RTTestSub(g_hTest, "IVirtualBox::homeFolder");
+ CHECK_ERROR(pVBox, COMGETTER(HomeFolder)(bstrTmp.asOutParam()));
+ if (SUCCEEDED(hrc))
+ RTTestPassed(g_hTest, "IVirtualBox::homeFolder");
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::homeFolder failed", __LINE__);
+
+ RTTestSub(g_hTest, "IVirtualBox::settingsFilePath");
+ CHECK_ERROR(pVBox, COMGETTER(SettingsFilePath)(bstrTmp.asOutParam()));
+ if (SUCCEEDED(hrc))
+ RTTestPassed(g_hTest, "IVirtualBox::settingsFilePath");
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::settingsFilePath failed", __LINE__);
+
+ com::SafeIfaceArray<IGuestOSType> guestOSTypes;
+ RTTestSub(g_hTest, "IVirtualBox::guestOSTypes");
+ CHECK_ERROR(pVBox, COMGETTER(GuestOSTypes)(ComSafeArrayAsOutParam(guestOSTypes)));
+ if (SUCCEEDED(hrc))
+ RTTestPassed(g_hTest, "IVirtualBox::guestOSTypes");
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::guestOSTypes failed", __LINE__);
+
+ /** Create VM */
+ RTTestSub(g_hTest, "IVirtualBox::CreateMachine");
+ ComPtr<IMachine> ptrMachine;
+ com::SafeArray<BSTR> groups;
+ /** Default VM settings */
+ CHECK_ERROR(pVBox, CreateMachine(NULL, /** Settings */
+ tstMachineName.raw(), /** Name */
+ ComSafeArrayAsInParam(groups), /** Groups */
+ NULL, /** OS Type */
+ NULL, /** Create flags */
+ NULL, /** Cipher */
+ NULL, /** Password id */
+ NULL, /** Password */
+ ptrMachine.asOutParam())); /** Machine */
+ if (SUCCEEDED(hrc))
+ RTTestPassed(g_hTest, "IVirtualBox::CreateMachine");
+ else
+ {
+ RTTestFailed(g_hTest, "%d: IVirtualBox::CreateMachine failed", __LINE__);
+ return FALSE;
+ }
+
+ RTTestSub(g_hTest, "IVirtualBox::RegisterMachine");
+ CHECK_ERROR(pVBox, RegisterMachine(ptrMachine));
+ if (SUCCEEDED(hrc))
+ RTTestPassed(g_hTest, "IVirtualBox::RegisterMachine");
+ else
+ {
+ RTTestFailed(g_hTest, "%d: IVirtualBox::RegisterMachine failed", __LINE__);
+ return FALSE;
+ }
+
+ ComPtr<IHost> host;
+ RTTestSub(g_hTest, "IVirtualBox::host");
+ CHECK_ERROR(pVBox, COMGETTER(Host)(host.asOutParam()));
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo Add IHost testing here. */
+ RTTestPassed(g_hTest, "IVirtualBox::host");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::host failed", __LINE__);
+
+ ComPtr<ISystemProperties> sysprop;
+ RTTestSub(g_hTest, "IVirtualBox::systemProperties");
+ CHECK_ERROR(pVBox, COMGETTER(SystemProperties)(sysprop.asOutParam()));
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo Add ISystemProperties testing here. */
+ RTTestPassed(g_hTest, "IVirtualBox::systemProperties");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::systemProperties failed", __LINE__);
+
+ com::SafeIfaceArray<IMachine> machines;
+ RTTestSub(g_hTest, "IVirtualBox::machines");
+ CHECK_ERROR(pVBox, COMGETTER(Machines)(ComSafeArrayAsOutParam(machines)));
+ if (SUCCEEDED(hrc))
+ {
+ bool bFound = FALSE;
+ for (size_t i = 0; i < machines.size(); ++i)
+ {
+ if (machines[i])
+ {
+ Bstr tmpName;
+ hrc = machines[i]->COMGETTER(Name)(tmpName.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ if (tmpName == tstMachineName)
+ {
+ bFound = TRUE;
+ break;
+ }
+ }
+ }
+ }
+
+ if (bFound)
+ RTTestPassed(g_hTest, "IVirtualBox::machines");
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::machines failed. No created machine found", __LINE__);
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::machines failed", __LINE__);
+
+#if 0 /** Not yet implemented */
+ com::SafeIfaceArray<ISharedFolder> sharedFolders;
+ RTTestSub(g_hTest, "IVirtualBox::sharedFolders");
+ CHECK_ERROR(pVBox, COMGETTER(SharedFolders)(ComSafeArrayAsOutParam(sharedFolders)));
+ if (SUCCEEDED(rc))
+ {
+ /** @todo Add ISharedFolders testing here. */
+ RTTestPassed(g_hTest, "IVirtualBox::sharedFolders");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::sharedFolders failed", __LINE__);
+#endif
+
+ com::SafeIfaceArray<IMedium> hardDisks;
+ RTTestSub(g_hTest, "IVirtualBox::hardDisks");
+ CHECK_ERROR(pVBox, COMGETTER(HardDisks)(ComSafeArrayAsOutParam(hardDisks)));
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo Add hardDisks testing here. */
+ RTTestPassed(g_hTest, "IVirtualBox::hardDisks");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::hardDisks failed", __LINE__);
+
+ com::SafeIfaceArray<IMedium> DVDImages;
+ RTTestSub(g_hTest, "IVirtualBox::DVDImages");
+ CHECK_ERROR(pVBox, COMGETTER(DVDImages)(ComSafeArrayAsOutParam(DVDImages)));
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo Add DVDImages testing here. */
+ RTTestPassed(g_hTest, "IVirtualBox::DVDImages");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::DVDImages failed", __LINE__);
+
+ com::SafeIfaceArray<IMedium> floppyImages;
+ RTTestSub(g_hTest, "IVirtualBox::floppyImages");
+ CHECK_ERROR(pVBox, COMGETTER(FloppyImages)(ComSafeArrayAsOutParam(floppyImages)));
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo Add floppyImages testing here. */
+ RTTestPassed(g_hTest, "IVirtualBox::floppyImages");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::floppyImages failed", __LINE__);
+
+ com::SafeIfaceArray<IProgress> progressOperations;
+ RTTestSub(g_hTest, "IVirtualBox::progressOperations");
+ CHECK_ERROR(pVBox, COMGETTER(ProgressOperations)(ComSafeArrayAsOutParam(progressOperations)));
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo Add IProgress testing here. */
+ RTTestPassed(g_hTest, "IVirtualBox::progressOperations");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::progressOperations failed", __LINE__);
+
+ ComPtr<IPerformanceCollector> performanceCollector;
+ RTTestSub(g_hTest, "IVirtualBox::performanceCollector");
+ CHECK_ERROR(pVBox, COMGETTER(PerformanceCollector)(performanceCollector.asOutParam()));
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo Add IPerformanceCollector testing here. */
+ RTTestPassed(g_hTest, "IVirtualBox::performanceCollector");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::performanceCollector failed", __LINE__);
+
+ com::SafeIfaceArray<IDHCPServer> DHCPServers;
+ RTTestSub(g_hTest, "IVirtualBox::DHCPServers");
+ CHECK_ERROR(pVBox, COMGETTER(DHCPServers)(ComSafeArrayAsOutParam(DHCPServers)));
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo Add IDHCPServers testing here. */
+ RTTestPassed(g_hTest, "IVirtualBox::DHCPServers");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::DHCPServers failed", __LINE__);
+
+ com::SafeIfaceArray<INATNetwork> NATNetworks;
+ RTTestSub(g_hTest, "IVirtualBox::NATNetworks");
+ CHECK_ERROR(pVBox, COMGETTER(NATNetworks)(ComSafeArrayAsOutParam(NATNetworks)));
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo Add INATNetworks testing here. */
+ RTTestPassed(g_hTest, "IVirtualBox::NATNetworks");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::NATNetworks failed", __LINE__);
+
+ ComPtr<IEventSource> eventSource;
+ RTTestSub(g_hTest, "IVirtualBox::eventSource");
+ CHECK_ERROR(pVBox, COMGETTER(EventSource)(eventSource.asOutParam()));
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo Add IEventSource testing here. */
+ RTTestPassed(g_hTest, "IVirtualBox::eventSource");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::eventSource failed", __LINE__);
+
+ ComPtr<IExtPackManager> extensionPackManager;
+ RTTestSub(g_hTest, "IVirtualBox::extensionPackManager");
+ CHECK_ERROR(pVBox, COMGETTER(ExtensionPackManager)(extensionPackManager.asOutParam()));
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo Add IExtPackManager testing here. */
+ RTTestPassed(g_hTest, "IVirtualBox::extensionPackManager");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::extensionPackManager failed", __LINE__);
+
+ com::SafeArray<BSTR> internalNetworks;
+ RTTestSub(g_hTest, "IVirtualBox::internalNetworks");
+ CHECK_ERROR(pVBox, COMGETTER(InternalNetworks)(ComSafeArrayAsOutParam(internalNetworks)));
+ if (SUCCEEDED(hrc))
+ {
+ RTTestPassed(g_hTest, "IVirtualBox::internalNetworks");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::internalNetworks failed", __LINE__);
+
+ com::SafeArray<BSTR> genericNetworkDrivers;
+ RTTestSub(g_hTest, "IVirtualBox::genericNetworkDrivers");
+ CHECK_ERROR(pVBox, COMGETTER(GenericNetworkDrivers)(ComSafeArrayAsOutParam(genericNetworkDrivers)));
+ if (SUCCEEDED(hrc))
+ {
+ RTTestPassed(g_hTest, "IVirtualBox::genericNetworkDrivers");
+ }
+ else
+ RTTestFailed(g_hTest, "%d: IVirtualBox::genericNetworkDrivers failed", __LINE__);
+
+ return TRUE;
+}
+
+
+static BOOL tstApiClean(IVirtualBox *pVBox)
+{
+ HRESULT hrc;
+
+ /** Delete created VM and its files */
+ ComPtr<IMachine> machine;
+ CHECK_ERROR_RET(pVBox, FindMachine(Bstr(tstMachineName).raw(), machine.asOutParam()), FALSE);
+ SafeIfaceArray<IMedium> media;
+ CHECK_ERROR_RET(machine, Unregister(CleanupMode_DetachAllReturnHardDisksOnly,
+ ComSafeArrayAsOutParam(media)), FALSE);
+ ComPtr<IProgress> progress;
+ CHECK_ERROR_RET(machine, DeleteConfig(ComSafeArrayAsInParam(media), progress.asOutParam()), FALSE);
+ CHECK_ERROR_RET(progress, WaitForCompletion(-1), FALSE);
+
+ return TRUE;
+}
+
+
+int main()
+{
+ /*
+ * Initialization.
+ */
+ RTEXITCODE rcExit = RTTestInitAndCreate("tstVBoxAPI", &g_hTest);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ SUPR3Init(NULL); /* Better time support. */
+ RTTestBanner(g_hTest);
+
+ RTTestSub(g_hTest, "Initializing COM and singletons");
+ HRESULT hrc = com::Initialize();
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<IVirtualBoxClient> ptrVBoxClient;
+ ComPtr<IVirtualBox> ptrVBox;
+ hrc = TST_COM_EXPR(ptrVBoxClient.createInprocObject(CLSID_VirtualBoxClient));
+ if (SUCCEEDED(hrc))
+ hrc = TST_COM_EXPR(ptrVBoxClient->COMGETTER(VirtualBox)(ptrVBox.asOutParam()));
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<ISession> ptrSession;
+ hrc = TST_COM_EXPR(ptrSession.createInprocObject(CLSID_Session));
+ if (SUCCEEDED(hrc))
+ {
+ RTTestSubDone(g_hTest);
+
+ /*
+ * Call test functions.
+ */
+
+ /** Test IVirtualBox interface */
+ tstApiIVirtualBox(ptrVBox);
+
+
+ /** Clean files/configs */
+ tstApiClean(ptrVBox);
+ }
+ }
+
+ ptrVBox.setNull();
+ ptrVBoxClient.setNull();
+ com::Shutdown();
+ }
+ else
+ RTTestIFailed("com::Initialize failed with hrc=%Rhrc", hrc);
+ return RTTestSummaryAndDestroy(g_hTest);
+}
diff --git a/src/VBox/Main/testcase/tstVBoxAPIPerf.cpp b/src/VBox/Main/testcase/tstVBoxAPIPerf.cpp
new file mode 100644
index 00000000..00c2a97c
--- /dev/null
+++ b/src/VBox/Main/testcase/tstVBoxAPIPerf.cpp
@@ -0,0 +1,257 @@
+/* $Id: tstVBoxAPIPerf.cpp $ */
+/** @file
+ * tstVBoxAPIPerf - Checks the performance of the COM / XPOM API.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/com/com.h>
+#include <VBox/com/string.h>
+#include <VBox/com/array.h>
+#include <VBox/com/Guid.h>
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/com/VirtualBox.h>
+#include <VBox/sup.h>
+
+#include <iprt/test.h>
+#include <iprt/time.h>
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static RTTEST g_hTest;
+
+
+/** Worker fro TST_COM_EXPR(). */
+static HRESULT tstComExpr(HRESULT hrc, const char *pszOperation, int iLine)
+{
+ if (FAILED(hrc))
+ RTTestFailed(g_hTest, "%s failed on line %u with hrc=%Rhrc", pszOperation, iLine, hrc);
+ return hrc;
+}
+
+/** Macro that executes the given expression and report any failure.
+ * The expression must return a HRESULT. */
+#define TST_COM_EXPR(expr) tstComExpr(expr, #expr, __LINE__)
+
+
+
+static void tstApiPrf1(IVirtualBox *pVBox)
+{
+ RTTestSub(g_hTest, "IVirtualBox::Revision performance");
+
+ uint32_t const cCalls = 65536;
+ uint32_t cLeft = cCalls;
+ uint64_t uStartTS = RTTimeNanoTS();
+ while (cLeft-- > 0)
+ {
+ ULONG uRev;
+ HRESULT hrc = pVBox->COMGETTER(Revision)(&uRev);
+ if (FAILED(hrc))
+ {
+ tstComExpr(hrc, "IVirtualBox::Revision", __LINE__);
+ return;
+ }
+ }
+ uint64_t uElapsed = RTTimeNanoTS() - uStartTS;
+ RTTestValue(g_hTest, "IVirtualBox::Revision average", uElapsed / cCalls, RTTESTUNIT_NS_PER_CALL);
+ RTTestSubDone(g_hTest);
+}
+
+
+static void tstApiPrf2(IVirtualBox *pVBox)
+{
+ RTTestSub(g_hTest, "IVirtualBox::Version performance");
+
+ uint32_t const cCalls = 65536;
+ uint32_t cLeft = cCalls;
+ uint64_t uStartTS = RTTimeNanoTS();
+ while (cLeft-- > 0)
+ {
+ com::Bstr bstrVersion;
+ HRESULT hrc = pVBox->COMGETTER(Version)(bstrVersion.asOutParam());
+ if (FAILED(hrc))
+ {
+ tstComExpr(hrc, "IVirtualBox::Version", __LINE__);
+ return;
+ }
+ }
+ uint64_t uElapsed = RTTimeNanoTS() - uStartTS;
+ RTTestValue(g_hTest, "IVirtualBox::Version average", uElapsed / cCalls, RTTESTUNIT_NS_PER_CALL);
+ RTTestSubDone(g_hTest);
+}
+
+
+static void tstApiPrf3(IVirtualBox *pVBox)
+{
+ RTTestSub(g_hTest, "IVirtualBox::Host performance");
+
+ /* The first call. */
+ uint64_t uStartTS = RTTimeNanoTS();
+ IHost *pHost = NULL;
+ HRESULT hrc = pVBox->COMGETTER(Host)(&pHost);
+ if (FAILED(hrc))
+ {
+ tstComExpr(hrc, "IVirtualBox::Host", __LINE__);
+ return;
+ }
+ pHost->Release();
+ uint64_t uElapsed = RTTimeNanoTS() - uStartTS;
+ RTTestValue(g_hTest, "IVirtualBox::Host first", uElapsed, RTTESTUNIT_NS);
+
+ /* Subsequent calls. */
+ uint32_t const cCalls1 = 4096;
+ uint32_t cLeft = cCalls1;
+ uStartTS = RTTimeNanoTS();
+ while (cLeft-- > 0)
+ {
+ IHost *pHost2 = NULL;
+ hrc = pVBox->COMGETTER(Host)(&pHost2);
+ if (FAILED(hrc))
+ {
+ tstComExpr(hrc, "IVirtualBox::Host", __LINE__);
+ return;
+ }
+ pHost2->Release();
+ }
+ uElapsed = RTTimeNanoTS() - uStartTS;
+ RTTestValue(g_hTest, "IVirtualBox::Host average", uElapsed / cCalls1, RTTESTUNIT_NS_PER_CALL);
+
+ /* Keep a reference around and see how that changes things.
+ Note! VBoxSVC is not creating and destroying Host(). */
+ pHost = NULL;
+ hrc = pVBox->COMGETTER(Host)(&pHost);
+
+ uint32_t const cCalls2 = 16384;
+ cLeft = cCalls2;
+ uStartTS = RTTimeNanoTS();
+ while (cLeft-- > 0)
+ {
+ IHost *pHost2 = NULL;
+ hrc = pVBox->COMGETTER(Host)(&pHost2);
+ if (FAILED(hrc))
+ {
+ tstComExpr(hrc, "IVirtualBox::Host", __LINE__);
+ pHost->Release();
+ return;
+ }
+ pHost2->Release();
+ }
+ uElapsed = RTTimeNanoTS() - uStartTS;
+ RTTestValue(g_hTest, "IVirtualBox::Host 2nd ref", uElapsed / cCalls2, RTTESTUNIT_NS_PER_CALL);
+ pHost->Release();
+
+ RTTestSubDone(g_hTest);
+}
+
+
+static void tstApiPrf4(IVirtualBox *pVBox)
+{
+ RTTestSub(g_hTest, "IHost::GetProcessorFeature performance");
+
+ IHost *pHost = NULL;
+ HRESULT hrc = pVBox->COMGETTER(Host)(&pHost);
+ if (FAILED(hrc))
+ {
+ tstComExpr(hrc, "IVirtualBox::Host", __LINE__);
+ return;
+ }
+
+ uint32_t const cCalls = 65536;
+ uint32_t cLeft = cCalls;
+ uint64_t uStartTS = RTTimeNanoTS();
+ while (cLeft-- > 0)
+ {
+ BOOL fSupported;
+ hrc = pHost->GetProcessorFeature(ProcessorFeature_PAE, &fSupported);
+ if (FAILED(hrc))
+ {
+ tstComExpr(hrc, "IHost::GetProcessorFeature", __LINE__);
+ pHost->Release();
+ return;
+ }
+ }
+ uint64_t uElapsed = RTTimeNanoTS() - uStartTS;
+ RTTestValue(g_hTest, "IHost::GetProcessorFeature average", uElapsed / cCalls, RTTESTUNIT_NS_PER_CALL);
+ pHost->Release();
+ RTTestSubDone(g_hTest);
+}
+
+
+
+int main()
+{
+ /*
+ * Initialization.
+ */
+ RTEXITCODE rcExit = RTTestInitAndCreate("tstVBoxAPIPerf", &g_hTest);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ SUPR3Init(NULL); /* Better time support. */
+ RTTestBanner(g_hTest);
+
+ RTTestSub(g_hTest, "Initializing COM and singletons");
+ HRESULT hrc = com::Initialize();
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<IVirtualBoxClient> ptrVBoxClient;
+ ComPtr<IVirtualBox> ptrVBox;
+ hrc = TST_COM_EXPR(ptrVBoxClient.createInprocObject(CLSID_VirtualBoxClient));
+ if (SUCCEEDED(hrc))
+ hrc = TST_COM_EXPR(ptrVBoxClient->COMGETTER(VirtualBox)(ptrVBox.asOutParam()));
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<ISession> ptrSession;
+ hrc = TST_COM_EXPR(ptrSession.createInprocObject(CLSID_Session));
+ if (SUCCEEDED(hrc))
+ {
+ RTTestSubDone(g_hTest);
+
+ /*
+ * Call test functions.
+ */
+ tstApiPrf1(ptrVBox);
+ tstApiPrf2(ptrVBox);
+ tstApiPrf3(ptrVBox);
+
+ /** @todo Find something that returns a 2nd instance of an interface and see
+ * how if wrapper stuff is reused in any way. */
+ tstApiPrf4(ptrVBox);
+ }
+ }
+
+ ptrVBox.setNull();
+ ptrVBoxClient.setNull();
+ com::Shutdown();
+ }
+ else
+ RTTestIFailed("com::Initialize failed with hrc=%Rhrc", hrc);
+ return RTTestSummaryAndDestroy(g_hTest);
+}
+
diff --git a/src/VBox/Main/testcase/tstVBoxAPIWin.cpp b/src/VBox/Main/testcase/tstVBoxAPIWin.cpp
new file mode 100644
index 00000000..444fa4d5
--- /dev/null
+++ b/src/VBox/Main/testcase/tstVBoxAPIWin.cpp
@@ -0,0 +1,305 @@
+/* $Id: tstVBoxAPIWin.cpp $ */
+/** @file
+ *
+ * tstVBoxAPIWin - sample program to illustrate the VirtualBox
+ * COM API for machine management on Windows.
+ It only uses standard C/C++ and COM semantics,
+ * no additional VBox classes/macros/helpers. To
+ * make things even easier to follow, only the
+ * standard Win32 API has been used. Typically,
+ * C++ developers would make use of Microsoft's
+ * ATL to ease development.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * PURPOSE OF THIS SAMPLE PROGRAM
+ * ------------------------------
+ *
+ * This sample program is intended to demonstrate the minimal code necessary
+ * to use VirtualBox COM API for learning puroses only. The program uses pure
+ * Win32 API and doesn't have any extra dependencies to let you better
+ * understand what is going on when a client talks to the VirtualBox core
+ * using the COM framework.
+ *
+ * However, if you want to write a real application, it is highly recommended
+ * to use our MS COM XPCOM Glue library and helper C++ classes. This way, you
+ * will get at least the following benefits:
+ *
+ * a) better portability: both the MS COM (used on Windows) and XPCOM (used
+ * everywhere else) VirtualBox client application from the same source code
+ * (including common smart C++ templates for automatic interface pointer
+ * reference counter and string data management);
+ * b) simpler XPCOM initialization and shutdown (only a single method call
+ * that does everything right).
+ *
+ * Currently, there is no separate sample program that uses the VirtualBox MS
+ * COM XPCOM Glue library. Please refer to the sources of stock VirtualBox
+ * applications such as the VirtualBox GUI frontend or the VBoxManage command
+ * line frontend.
+ */
+
+
+#include <stdio.h>
+#include <iprt/win/windows.h> /* Avoid -Wall warnings. */
+#include "VirtualBox.h"
+
+#define SAFE_RELEASE(x) \
+ if (x) { \
+ x->Release(); \
+ x = NULL; \
+ }
+
+int listVMs(IVirtualBox *virtualBox)
+{
+ HRESULT rc;
+
+ /*
+ * First we have to get a list of all registered VMs
+ */
+ SAFEARRAY *machinesArray = NULL;
+
+ rc = virtualBox->get_Machines(&machinesArray);
+ if (SUCCEEDED(rc))
+ {
+ IMachine **machines;
+ rc = SafeArrayAccessData(machinesArray, (void **) &machines);
+ if (SUCCEEDED(rc))
+ {
+ for (ULONG i = 0; i < machinesArray->rgsabound[0].cElements; ++i)
+ {
+ BSTR str;
+
+ rc = machines[i]->get_Name(&str);
+ if (SUCCEEDED(rc))
+ {
+ printf("Name: %S\n", str);
+ SysFreeString(str);
+ }
+ }
+
+ SafeArrayUnaccessData(machinesArray);
+ }
+
+ SafeArrayDestroy(machinesArray);
+ }
+
+ return 0;
+}
+
+
+int testErrorInfo(IVirtualBox *virtualBox)
+{
+ HRESULT rc;
+
+ /* Try to find a machine that doesn't exist */
+ IMachine *machine = NULL;
+ BSTR machineName = SysAllocString(L"Foobar");
+
+ rc = virtualBox->FindMachine(machineName, &machine);
+
+ if (FAILED(rc))
+ {
+ IErrorInfo *errorInfo;
+
+ rc = GetErrorInfo(0, &errorInfo);
+
+ if (FAILED(rc))
+ printf("Error getting error info! rc=%#lx\n", rc);
+ else
+ {
+ BSTR errorDescription = NULL;
+
+ rc = errorInfo->GetDescription(&errorDescription);
+
+ if (FAILED(rc) || !errorDescription)
+ printf("Error getting error description! rc=%#lx\n", rc);
+ else
+ {
+ printf("Successfully retrieved error description: %S\n", errorDescription);
+
+ SysFreeString(errorDescription);
+ }
+
+ errorInfo->Release();
+ }
+ }
+
+ SAFE_RELEASE(machine);
+ SysFreeString(machineName);
+
+ return 0;
+}
+
+
+int testStartVM(IVirtualBox *virtualBox)
+{
+ HRESULT rc;
+
+ /* Try to start a VM called "WinXP SP2". */
+ IMachine *machine = NULL;
+ BSTR machineName = SysAllocString(L"WinXP SP2");
+
+ rc = virtualBox->FindMachine(machineName, &machine);
+
+ if (FAILED(rc))
+ {
+ IErrorInfo *errorInfo;
+
+ rc = GetErrorInfo(0, &errorInfo);
+
+ if (FAILED(rc))
+ printf("Error getting error info! rc=%#lx\n", rc);
+ else
+ {
+ BSTR errorDescription = NULL;
+
+ rc = errorInfo->GetDescription(&errorDescription);
+
+ if (FAILED(rc) || !errorDescription)
+ printf("Error getting error description! rc=%#lx\n", rc);
+ else
+ {
+ printf("Successfully retrieved error description: %S\n", errorDescription);
+
+ SysFreeString(errorDescription);
+ }
+
+ SAFE_RELEASE(errorInfo);
+ }
+ }
+ else
+ {
+ ISession *session = NULL;
+ IConsole *console = NULL;
+ IProgress *progress = NULL;
+ BSTR sessiontype = SysAllocString(L"gui");
+ BSTR guid;
+
+ do
+ {
+ rc = machine->get_Id(&guid); /* Get the GUID of the machine. */
+ if (!SUCCEEDED(rc))
+ {
+ printf("Error retrieving machine ID! rc=%#lx\n", rc);
+ break;
+ }
+
+ /* Create the session object. */
+ rc = CoCreateInstance(CLSID_Session, /* the VirtualBox base object */
+ NULL, /* no aggregation */
+ CLSCTX_INPROC_SERVER, /* the object lives in the current process */
+ IID_ISession, /* IID of the interface */
+ (void**)&session);
+ if (!SUCCEEDED(rc))
+ {
+ printf("Error creating Session instance! rc=%#lx\n", rc);
+ break;
+ }
+
+ /* Start a VM session using the delivered VBox GUI. */
+ rc = machine->LaunchVMProcess(session, sessiontype,
+ NULL, &progress);
+ if (!SUCCEEDED(rc))
+ {
+ printf("Could not open remote session! rc=%#lx\n", rc);
+ break;
+ }
+
+ /* Wait until VM is running. */
+ printf("Starting VM, please wait ...\n");
+ rc = progress->WaitForCompletion(-1);
+
+ /* Get console object. */
+ session->get_Console(&console);
+
+ /* Bring console window to front. */
+ machine->ShowConsoleWindow(0);
+
+ printf("Press enter to power off VM and close the session...\n");
+ getchar();
+
+ /* Power down the machine. */
+ rc = console->PowerDown(&progress);
+
+ /* Wait until VM is powered down. */
+ printf("Powering off VM, please wait ...\n");
+ rc = progress->WaitForCompletion(-1);
+
+ /* Close the session. */
+ rc = session->UnlockMachine();
+
+ } while (0);
+
+ SAFE_RELEASE(console);
+ SAFE_RELEASE(progress);
+ SAFE_RELEASE(session);
+ SysFreeString(guid);
+ SysFreeString(sessiontype);
+ SAFE_RELEASE(machine);
+ }
+
+ SysFreeString(machineName);
+
+ return 0;
+}
+
+
+int main()
+{
+ /* Initialize the COM subsystem. */
+ CoInitialize(NULL);
+
+ /* Instantiate the VirtualBox root object. */
+ IVirtualBoxClient *virtualBoxClient;
+ HRESULT rc = CoCreateInstance(CLSID_VirtualBoxClient, /* the VirtualBoxClient object */
+ NULL, /* no aggregation */
+ CLSCTX_INPROC_SERVER, /* the object lives in the current process */
+ IID_IVirtualBoxClient, /* IID of the interface */
+ (void**)&virtualBoxClient);
+ if (SUCCEEDED(rc))
+ {
+ IVirtualBox *virtualBox;
+ rc = virtualBoxClient->get_VirtualBox(&virtualBox);
+ if (SUCCEEDED(rc))
+ {
+ listVMs(virtualBox);
+
+ testErrorInfo(virtualBox);
+
+ /* Enable the following line to get a VM started. */
+ //testStartVM(virtualBox);
+
+ /* Release the VirtualBox object. */
+ virtualBox->Release();
+ virtualBoxClient->Release();
+ }
+ else
+ printf("Error creating VirtualBox instance! rc=%#lx\n", rc);
+ }
+
+ CoUninitialize();
+ return 0;
+}
+
diff --git a/src/VBox/Main/testcase/tstVBoxAPIXPCOM.cpp b/src/VBox/Main/testcase/tstVBoxAPIXPCOM.cpp
new file mode 100644
index 00000000..12908a15
--- /dev/null
+++ b/src/VBox/Main/testcase/tstVBoxAPIXPCOM.cpp
@@ -0,0 +1,684 @@
+/* $Id: tstVBoxAPIXPCOM.cpp $ */
+/** @file
+ *
+ * tstVBoxAPIXPCOM - sample program to illustrate the VirtualBox
+ * XPCOM API for machine management.
+ * It only uses standard C/C++ and XPCOM semantics,
+ * no additional VBox classes/macros/helpers.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * PURPOSE OF THIS SAMPLE PROGRAM
+ * ------------------------------
+ *
+ * This sample program is intended to demonstrate the minimal code necessary
+ * to use VirtualBox XPCOM API for learning puroses only. The program uses
+ * pure XPCOM and doesn't have any extra dependencies to let you better
+ * understand what is going on when a client talks to the VirtualBox core
+ * using the XPCOM framework.
+ *
+ * However, if you want to write a real application, it is highly recommended
+ * to use our MS COM XPCOM Glue library and helper C++ classes. This way, you
+ * will get at least the following benefits:
+ *
+ * a) better portability: both the MS COM (used on Windows) and XPCOM (used
+ * everywhere else) VirtualBox client application from the same source code
+ * (including common smart C++ templates for automatic interface pointer
+ * reference counter and string data management);
+ * b) simpler XPCOM initialization and shutdown (only a single method call
+ * that does everything right).
+ *
+ * Currently, there is no separate sample program that uses the VirtualBox MS
+ * COM XPCOM Glue library. Please refer to the sources of stock VirtualBox
+ * applications such as the VirtualBox GUI frontend or the VBoxManage command
+ * line frontend.
+ *
+ *
+ * RUNNING THIS SAMPLE PROGRAM
+ * ---------------------------
+ *
+ * This sample program needs to know where the VirtualBox core files reside
+ * and where to search for VirtualBox shared libraries. Therefore, you need to
+ * use the following (or similar) command to execute it:
+ *
+ * $ env VBOX_XPCOM_HOME=../../.. LD_LIBRARY_PATH=../../.. ./tstVBoxAPIXPCOM
+ *
+ * The above command assumes that VBoxRT.so, VBoxXPCOM.so and others reside in
+ * the directory ../../..
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <iconv.h>
+
+/*
+ * Include the XPCOM headers
+ */
+#include <nsMemory.h>
+#include <nsString.h>
+#include <nsIServiceManager.h>
+#include <nsEventQueueUtils.h>
+
+#include <nsIExceptionService.h>
+
+/*
+ * VirtualBox XPCOM interface. This header is generated
+ * from IDL which in turn is generated from a custom XML format.
+ */
+#include "VirtualBox_XPCOM.h"
+
+/*
+ * Prototypes
+ */
+
+char *nsIDToString(nsID *guid);
+void printErrorInfo();
+
+
+/**
+ * Display all registered VMs on the screen with some information about each
+ *
+ * @param virtualBox VirtualBox instance object.
+ */
+void listVMs(IVirtualBox *virtualBox)
+{
+ nsresult rc;
+
+ printf("----------------------------------------------------\n");
+ printf("VM List:\n\n");
+
+ /*
+ * Get the list of all registered VMs
+ */
+ IMachine **machines = NULL;
+ PRUint32 cMachines = 0;
+
+ rc = virtualBox->GetMachines(&cMachines, &machines);
+ if (NS_SUCCEEDED(rc))
+ {
+ /*
+ * Iterate through the collection
+ */
+ for (PRUint32 i = 0; i < cMachines; ++ i)
+ {
+ IMachine *machine = machines[i];
+ if (machine)
+ {
+ PRBool isAccessible = PR_FALSE;
+ machine->GetAccessible(&isAccessible);
+
+ if (isAccessible)
+ {
+ nsXPIDLString machineName;
+ machine->GetName(getter_Copies(machineName));
+ char *machineNameAscii = ToNewCString(machineName);
+ printf("\tName: %s\n", machineNameAscii);
+ free(machineNameAscii);
+ }
+ else
+ {
+ printf("\tName: <inaccessible>\n");
+ }
+
+ nsXPIDLString iid;
+ machine->GetId(getter_Copies(iid));
+ const char *uuidString = ToNewCString(iid);
+ printf("\tUUID: %s\n", uuidString);
+ free((void*)uuidString);
+
+ if (isAccessible)
+ {
+ nsXPIDLString configFile;
+ machine->GetSettingsFilePath(getter_Copies(configFile));
+ char *configFileAscii = ToNewCString(configFile);
+ printf("\tConfig file: %s\n", configFileAscii);
+ free(configFileAscii);
+
+ PRUint32 memorySize;
+ machine->GetMemorySize(&memorySize);
+ printf("\tMemory size: %uMB\n", memorySize);
+
+ nsXPIDLString typeId;
+ machine->GetOSTypeId(getter_Copies(typeId));
+ IGuestOSType *osType = nsnull;
+ virtualBox->GetGuestOSType(typeId.get(), &osType);
+ nsXPIDLString osName;
+ osType->GetDescription(getter_Copies(osName));
+ char *osNameAscii = ToNewCString(osName);
+ printf("\tGuest OS: %s\n\n", osNameAscii);
+ free(osNameAscii);
+ osType->Release();
+ }
+
+ /* don't forget to release the objects in the array... */
+ machine->Release();
+ }
+ }
+ nsMemory::Free(machines);
+ }
+ printf("----------------------------------------------------\n\n");
+}
+
+/**
+ * Create a sample VM
+ *
+ * @param virtualBox VirtualBox instance object.
+ */
+void createVM(IVirtualBox *virtualBox)
+{
+ nsresult rc;
+ /*
+ * First create a unnamed new VM. It will be unconfigured and not be saved
+ * in the configuration until we explicitely choose to do so.
+ */
+ nsCOMPtr<IMachine> machine;
+ rc = virtualBox->CreateMachine(NULL, /* settings file */
+ NS_LITERAL_STRING("A brand new name").get(),
+ 0, nsnull, /* groups (safearray)*/
+ nsnull, /* ostype */
+ nsnull, /* create flags */
+ nsnull, /* cipher */
+ nsnull, /* password id */
+ nsnull, /* password */
+ getter_AddRefs(machine));
+ if (NS_FAILED(rc))
+ {
+ printf("Error: could not create machine! rc=%#x\n", rc);
+ return;
+ }
+
+ /*
+ * Set some properties
+ */
+ /* alternative to illustrate the use of string classes */
+ rc = machine->SetName(NS_ConvertUTF8toUTF16("A new name").get());
+ rc = machine->SetMemorySize(128);
+
+ /*
+ * Now a more advanced property -- the guest OS type. This is
+ * an object by itself which has to be found first. Note that we
+ * use the ID of the guest OS type here which is an internal
+ * representation (you can find that by configuring the OS type of
+ * a machine in the GUI and then looking at the <Guest ostype=""/>
+ * setting in the XML file. It is also possible to get the OS type from
+ * its description (win2k would be "Windows 2000") by getting the
+ * guest OS type collection and enumerating it.
+ */
+ nsCOMPtr<IGuestOSType> osType;
+ rc = virtualBox->GetGuestOSType(NS_LITERAL_STRING("Windows2000").get(),
+ getter_AddRefs(osType));
+ if (NS_FAILED(rc))
+ {
+ printf("Error: could not find guest OS type! rc=%#x\n", rc);
+ }
+ else
+ {
+ machine->SetOSTypeId(NS_LITERAL_STRING("Windows2000").get());
+ }
+
+ /*
+ * Register the VM. Note that this call also saves the VM config
+ * to disk. It is also possible to save the VM settings but not
+ * register the VM.
+ *
+ * Also note that due to current VirtualBox limitations, the machine
+ * must be registered *before* we can attach hard disks to it.
+ */
+ rc = virtualBox->RegisterMachine(machine);
+ if (NS_FAILED(rc))
+ {
+ printf("Error: could not register machine! rc=%#x\n", rc);
+ printErrorInfo();
+ return;
+ }
+
+ nsCOMPtr<IMachine> origMachine = machine;
+
+ /*
+ * In order to manipulate the registered machine, we must open a session
+ * for that machine. Do it now.
+ */
+ nsCOMPtr<ISession> session;
+ nsCOMPtr<IMachine> sessionMachine;
+ {
+ nsCOMPtr<nsIComponentManager> manager;
+ rc = NS_GetComponentManager(getter_AddRefs(manager));
+ if (NS_FAILED(rc))
+ {
+ printf("Error: could not get component manager! rc=%#x\n", rc);
+ return;
+ }
+ rc = manager->CreateInstanceByContractID(NS_SESSION_CONTRACTID,
+ nsnull,
+ NS_GET_IID(ISession),
+ getter_AddRefs(session));
+ if (NS_FAILED(rc))
+ {
+ printf("Error, could not instantiate session object! rc=%#x\n", rc);
+ return;
+ }
+
+ rc = machine->LockMachine(session, LockType_Write);
+ if (NS_FAILED(rc))
+ {
+ printf("Error, could not lock the machine for the session! rc=%#x\n", rc);
+ return;
+ }
+
+ /*
+ * After the machine is registered, the initial machine object becomes
+ * immutable. In order to get a mutable machine object, we must query
+ * it from the opened session object.
+ */
+ rc = session->GetMachine(getter_AddRefs(sessionMachine));
+ if (NS_FAILED(rc))
+ {
+ printf("Error, could not get machine session! rc=%#x\n", rc);
+ return;
+ }
+ }
+
+ /*
+ * Create a virtual harddisk
+ */
+ nsCOMPtr<IMedium> hardDisk = 0;
+ rc = virtualBox->CreateMedium(NS_LITERAL_STRING("VDI").get(),
+ NS_LITERAL_STRING("/tmp/TestHardDisk.vdi").get(),
+ AccessMode_ReadWrite, DeviceType_HardDisk,
+ getter_AddRefs(hardDisk));
+ if (NS_FAILED(rc))
+ {
+ printf("Failed creating a hard disk object! rc=%#x\n", rc);
+ }
+ else
+ {
+ /*
+ * We have only created an object so far. No on disk representation exists
+ * because none of its properties has been set so far. Let's continue creating
+ * a dynamically expanding image.
+ */
+ nsCOMPtr<IProgress> progress;
+ MediumVariant_T mediumVariants[] =
+ { MediumVariant_Standard };
+ rc = hardDisk->CreateBaseStorage(100 * 1024 * 1024, // size in bytes
+ sizeof(mediumVariants) / sizeof(mediumVariants[0]), mediumVariants,
+ getter_AddRefs(progress)); // optional progress object
+ if (NS_FAILED(rc))
+ {
+ printf("Failed creating hard disk image! rc=%#x\n", rc);
+ }
+ else
+ {
+ /*
+ * Creating the image is done in the background because it can take quite
+ * some time (at least fixed size images). We have to wait for its completion.
+ * Here we wait forever (timeout -1) which is potentially dangerous.
+ */
+ rc = progress->WaitForCompletion(-1);
+ PRInt32 resultCode;
+ progress->GetResultCode(&resultCode);
+ if (NS_FAILED(rc) || NS_FAILED(resultCode))
+ {
+ printf("Error: could not create hard disk! rc=%#x\n",
+ NS_FAILED(rc) ? rc : resultCode);
+ }
+ else
+ {
+ /*
+ * Now that it's created, we can assign it to the VM.
+ */
+ rc = sessionMachine->AttachDevice(
+ NS_LITERAL_STRING("IDE Controller").get(), // controller identifier
+ 0, // channel number on the controller
+ 0, // device number on the controller
+ DeviceType_HardDisk,
+ hardDisk);
+ if (NS_FAILED(rc))
+ {
+ printf("Error: could not attach hard disk! rc=%#x\n", rc);
+ }
+ }
+ }
+ }
+
+ /*
+ * It's got a hard disk but that one is new and thus not bootable. Make it
+ * boot from an ISO file. This requires some processing. First the ISO file
+ * has to be registered and then mounted to the VM's DVD drive and selected
+ * as the boot device.
+ */
+ nsCOMPtr<IMedium> dvdImage;
+ rc = virtualBox->OpenMedium(NS_LITERAL_STRING("/home/vbox/isos/winnt4ger.iso").get(),
+ DeviceType_DVD,
+ AccessMode_ReadOnly,
+ false /* fForceNewUuid */,
+ getter_AddRefs(dvdImage));
+ if (NS_FAILED(rc))
+ printf("Error: could not open CD image! rc=%#x\n", rc);
+ else
+ {
+ /*
+ * Now assign it to our VM
+ */
+ rc = sessionMachine->MountMedium(
+ NS_LITERAL_STRING("IDE Controller").get(), // controller identifier
+ 2, // channel number on the controller
+ 0, // device number on the controller
+ dvdImage,
+ PR_FALSE); // aForce
+ if (NS_FAILED(rc))
+ {
+ printf("Error: could not mount ISO image! rc=%#x\n", rc);
+ }
+ else
+ {
+ /*
+ * Last step: tell the VM to boot from the CD.
+ */
+ rc = sessionMachine->SetBootOrder(1, DeviceType::DVD);
+ if (NS_FAILED(rc))
+ {
+ printf("Could not set boot device! rc=%#x\n", rc);
+ }
+ }
+ }
+
+ /*
+ * Save all changes we've just made.
+ */
+ rc = sessionMachine->SaveSettings();
+ if (NS_FAILED(rc))
+ printf("Could not save machine settings! rc=%#x\n", rc);
+
+ /*
+ * It is always important to close the open session when it becomes not
+ * necessary any more.
+ */
+ session->UnlockMachine();
+
+ IMedium **aMedia;
+ PRUint32 cMedia;
+ rc = machine->Unregister((CleanupMode_T)CleanupMode_DetachAllReturnHardDisksOnly,
+ &cMedia, &aMedia);
+ if (NS_FAILED(rc))
+ printf("Unregistering the machine failed! rc=%#x\n", rc);
+ else
+ {
+ nsCOMPtr<IProgress> pProgress;
+ rc = machine->DeleteConfig(cMedia, aMedia, getter_AddRefs(pProgress));
+ if (NS_FAILED(rc))
+ printf("Deleting of machine failed! rc=%#x\n", rc);
+ else
+ {
+ rc = pProgress->WaitForCompletion(-1);
+ PRInt32 resultCode;
+ pProgress->GetResultCode(&resultCode);
+ if (NS_FAILED(rc) || NS_FAILED(resultCode))
+ printf("Failed to delete the machine! rc=%#x\n",
+ NS_FAILED(rc) ? rc : resultCode);
+ }
+
+ /* Release the media array: */
+ for (PRUint32 i = 0; i < cMedia; i++)
+ if (aMedia[i])
+ aMedia[i]->Release();
+ nsMemory::Free(aMedia);
+ }
+}
+
+// main
+///////////////////////////////////////////////////////////////////////////////
+
+int main(int argc, char **argv)
+{
+ /*
+ * Check that PRUnichar is equal in size to what compiler composes L""
+ * strings from; otherwise NS_LITERAL_STRING macros won't work correctly
+ * and we will get a meaningless SIGSEGV. This, of course, must be checked
+ * at compile time in xpcom/string/nsTDependentString.h, but XPCOM lacks
+ * compile-time assert macros and I'm not going to add them now.
+ */
+ if (sizeof(PRUnichar) != sizeof(wchar_t))
+ {
+ printf("Error: sizeof(PRUnichar) {%lu} != sizeof(wchar_t) {%lu}!\n"
+ "Probably, you forgot the -fshort-wchar compiler option.\n",
+ (unsigned long) sizeof(PRUnichar),
+ (unsigned long) sizeof(wchar_t));
+ return -1;
+ }
+
+#if 1 /* Please ignore this! It is very very crude. */
+# ifdef RTPATH_APP_PRIVATE_ARCH
+ if (!getenv("VBOX_XPCOM_HOME"))
+ setenv("VBOX_XPCOM_HOME", RTPATH_APP_PRIVATE_ARCH, 1);
+# else
+ char szTmp[8192];
+ if (!getenv("VBOX_XPCOM_HOME"))
+ {
+ strcpy(szTmp, argv[0]);
+ *strrchr(szTmp, '/') = '\0';
+ strcat(szTmp, "/..");
+ fprintf(stderr, "tstVBoxAPIXPCOM: VBOX_XPCOM_HOME is not set, using '%s' instead\n", szTmp);
+ setenv("VBOX_XPCOM_HOME", szTmp, 1);
+ }
+# endif
+#endif
+ (void)argc; (void)argv;
+
+ nsresult rc;
+
+ /*
+ * This is the standard XPCOM init procedure.
+ * What we do is just follow the required steps to get an instance
+ * of our main interface, which is IVirtualBox.
+ *
+ * Note that we scope all nsCOMPtr variables in order to have all XPCOM
+ * objects automatically released before we call NS_ShutdownXPCOM at the
+ * end. This is an XPCOM requirement.
+ */
+ {
+ nsCOMPtr<nsIServiceManager> serviceManager;
+ rc = NS_InitXPCOM2(getter_AddRefs(serviceManager), nsnull, nsnull);
+ if (NS_FAILED(rc))
+ {
+ printf("Error: XPCOM could not be initialized! rc=%#x\n", rc);
+ return -1;
+ }
+
+#if 0
+ /*
+ * Register our components. This step is only necessary if this executable
+ * implements XPCOM components itself which is not the case for this
+ * simple example.
+ */
+ nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(serviceManager);
+ if (!registrar)
+ {
+ printf("Error: could not query nsIComponentRegistrar interface!\n");
+ return -1;
+ }
+ registrar->AutoRegister(nsnull);
+#endif
+
+ /*
+ * Make sure the main event queue is created. This event queue is
+ * responsible for dispatching incoming XPCOM IPC messages. The main
+ * thread should run this event queue's loop during lengthy non-XPCOM
+ * operations to ensure messages from the VirtualBox server and other
+ * XPCOM IPC clients are processed. This use case doesn't perform such
+ * operations so it doesn't run the event loop.
+ */
+ nsCOMPtr<nsIEventQueue> eventQ;
+ rc = NS_GetMainEventQ(getter_AddRefs(eventQ));
+ if (NS_FAILED(rc))
+ {
+ printf("Error: could not get main event queue! rc=%#x\n", rc);
+ return -1;
+ }
+
+ /*
+ * Now XPCOM is ready and we can start to do real work.
+ * IVirtualBox is the root interface of VirtualBox and will be
+ * retrieved from the XPCOM component manager. We use the
+ * XPCOM provided smart pointer nsCOMPtr for all objects because
+ * that's very convenient and removes the need deal with reference
+ * counting and freeing.
+ */
+ nsCOMPtr<nsIComponentManager> manager;
+ rc = NS_GetComponentManager(getter_AddRefs(manager));
+ if (NS_FAILED(rc))
+ {
+ printf("Error: could not get component manager! rc=%#x\n", rc);
+ return -1;
+ }
+
+ nsCOMPtr<IVirtualBox> virtualBox;
+ rc = manager->CreateInstanceByContractID(NS_VIRTUALBOX_CONTRACTID,
+ nsnull,
+ NS_GET_IID(IVirtualBox),
+ getter_AddRefs(virtualBox));
+ if (NS_FAILED(rc))
+ {
+ printf("Error, could not instantiate VirtualBox object! rc=%#x\n", rc);
+ return -1;
+ }
+ printf("VirtualBox object created\n");
+
+ ////////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////////
+
+
+ listVMs(virtualBox);
+
+ createVM(virtualBox);
+
+
+ ////////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////////
+
+ /* this is enough to free the IVirtualBox instance -- smart pointers rule! */
+ virtualBox = nsnull;
+
+ /*
+ * Process events that might have queued up in the XPCOM event
+ * queue. If we don't process them, the server might hang.
+ */
+ eventQ->ProcessPendingEvents();
+ }
+
+ /*
+ * Perform the standard XPCOM shutdown procedure.
+ */
+ NS_ShutdownXPCOM(nsnull);
+ printf("Done!\n");
+ return 0;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+//// Helpers
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Helper function to convert an nsID into a human readable string
+ *
+ * @returns result string, allocated. Has to be freed using free()
+ * @param guid Pointer to nsID that will be converted.
+ */
+char *nsIDToString(nsID *guid)
+{
+ char *res = (char*)malloc(39);
+
+ if (res != NULL)
+ {
+ snprintf(res, 39, "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
+ guid->m0, (PRUint32)guid->m1, (PRUint32)guid->m2,
+ (PRUint32)guid->m3[0], (PRUint32)guid->m3[1], (PRUint32)guid->m3[2],
+ (PRUint32)guid->m3[3], (PRUint32)guid->m3[4], (PRUint32)guid->m3[5],
+ (PRUint32)guid->m3[6], (PRUint32)guid->m3[7]);
+ }
+ return res;
+}
+
+/**
+ * Helper function to print XPCOM exception information set on the current
+ * thread after a failed XPCOM method call. This function will also print
+ * extended VirtualBox error info if it is available.
+ */
+void printErrorInfo()
+{
+ nsresult rc;
+
+ nsCOMPtr<nsIExceptionService> es;
+ es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &rc);
+ if (NS_SUCCEEDED(rc))
+ {
+ nsCOMPtr<nsIExceptionManager> em;
+ rc = es->GetCurrentExceptionManager(getter_AddRefs(em));
+ if (NS_SUCCEEDED(rc))
+ {
+ nsCOMPtr<nsIException> ex;
+ rc = em->GetCurrentException(getter_AddRefs(ex));
+ if (NS_SUCCEEDED(rc) && ex)
+ {
+ nsCOMPtr<IVirtualBoxErrorInfo> info;
+ info = do_QueryInterface(ex, &rc);
+ if (NS_SUCCEEDED(rc) && info)
+ {
+ /* got extended error info */
+ printf("Extended error info (IVirtualBoxErrorInfo):\n");
+ PRInt32 resultCode = NS_OK;
+ info->GetResultCode(&resultCode);
+ printf(" resultCode=%08X\n", resultCode);
+ nsXPIDLString component;
+ info->GetComponent(getter_Copies(component));
+ printf(" component=%s\n", NS_ConvertUTF16toUTF8(component).get());
+ nsXPIDLString text;
+ info->GetText(getter_Copies(text));
+ printf(" text=%s\n", NS_ConvertUTF16toUTF8(text).get());
+ }
+ else
+ {
+ /* got basic error info */
+ printf("Basic error info (nsIException):\n");
+ nsresult resultCode = NS_OK;
+ ex->GetResult(&resultCode);
+ printf(" resultCode=%08X\n", resultCode);
+ nsXPIDLCString message;
+ ex->GetMessage(getter_Copies(message));
+ printf(" message=%s\n", message.get());
+ }
+
+ /* reset the exception to NULL to indicate we've processed it */
+ em->SetCurrentException(NULL);
+
+ rc = NS_OK;
+ }
+ }
+ }
+}
diff --git a/src/VBox/Main/testcase/tstVBoxCrypto.cpp b/src/VBox/Main/testcase/tstVBoxCrypto.cpp
new file mode 100644
index 00000000..0f3fbcf6
--- /dev/null
+++ b/src/VBox/Main/testcase/tstVBoxCrypto.cpp
@@ -0,0 +1,431 @@
+/* $Id: tstVBoxCrypto.cpp $ */
+/** @file
+ * tstVBoxCrypto - Testcase for the cryptographic support module.
+ */
+
+/*
+ * Copyright (C) 2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/VBoxCryptoIf.h>
+#include <VBox/err.h>
+
+#include <iprt/file.h>
+#include <iprt/test.h>
+#include <iprt/ldr.h>
+#include <iprt/mem.h>
+#include <iprt/memsafer.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static RTTEST g_hTest;
+static const uint8_t g_abDek[64] = { 0x42 };
+static const char g_szPassword[] = "testtesttest";
+static const char g_szPasswordWrong[] = "testtest";
+
+static const char *g_aCiphers[] =
+{
+ "AES-XTS128-PLAIN64",
+ "AES-GCM128",
+ "AES-CTR128",
+
+ "AES-XTS256-PLAIN64",
+ "AES-GCM256",
+ "AES-CTR256"
+};
+
+#define CHECK_STR(str1, str2) do { if (strcmp(str1, str2)) { RTTestIFailed("line %u: '%s' != '%s' (*)", __LINE__, str1, str2); } } while (0)
+#define CHECK_BYTES(bytes1, bytes2, size) do { if (memcmp(bytes1, bytes2, size)) { RTTestIFailed("line %u: '%s' != '%s' (*)", __LINE__, #bytes1, bytes2); } } while (0)
+
+
+/**
+ * Creates a new cryptographic context and returns the encoded string version on success.
+ *
+ * @returns VBox status code.
+ * @param pCryptoIf Pointer to the cryptographic interface.
+ * @param pszCipher The cipher to use.
+ * @param pszPassword The password to use.
+ * @param ppszCtx Where to store the pointer to the context on success.
+ */
+static int tstCryptoCtxCreate(PCVBOXCRYPTOIF pCryptoIf, const char *pszCipher, const char *pszPassword, char **ppszCtx)
+{
+ VBOXCRYPTOCTX hCryptoCtx;
+
+ int rc = pCryptoIf->pfnCryptoCtxCreate(pszCipher, pszPassword, &hCryptoCtx);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pCryptoIf->pfnCryptoCtxSave(hCryptoCtx, ppszCtx);
+ int rc2 = pCryptoIf->pfnCryptoCtxDestroy(hCryptoCtx);
+ AssertReleaseRC(rc2);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Writes data to the given file until the given size is reached.
+ *
+ * @returns VBox status code.
+ * @param hVfsFile The file handle to write to.
+ * @param cbWrite Number of bytes to write.
+ */
+static int tstCryptoVfsWrite(RTVFSFILE hVfsFile, size_t cbWrite)
+{
+ RTTestISub("Writing to encrypted file");
+
+ int rc = VINF_SUCCESS;
+ size_t cbBufLeft = _128K;
+ void *pv = RTMemTmpAllocZ(cbBufLeft);
+ if (pv)
+ {
+ size_t cbLeft = cbWrite;
+ uint32_t cCounter = 0;
+ uint8_t *pb = (uint8_t *)pv;
+
+ /* Fill the counter buffer. */
+ uint32_t *pu32 = (uint32_t *)pv;
+ for (uint32_t i = 0; i < cbBufLeft / sizeof(uint32_t); i++)
+ *pu32++ = cCounter++;
+
+
+ for (;;)
+ {
+ size_t cbThisWrite = RTRandU64Ex(1, RT_MIN(cbBufLeft, cbLeft));
+ rc = RTVfsFileWrite(hVfsFile, pb, cbThisWrite, NULL /*pcbWritten*/);
+ if (RT_FAILURE(rc))
+ {
+ RTTestIFailed("Writing to file failed with %Rrc (cbLeft=%zu, cbBufLeft=%zu, cbThisWrite=%zu)",
+ rc, cbLeft, cbBufLeft, cbThisWrite);
+ break;
+ }
+
+ cbLeft -= cbThisWrite;
+ cbBufLeft -= cbThisWrite;
+ pb += cbThisWrite;
+
+ if (!cbBufLeft)
+ {
+ /* Fill the counter buffer again. */
+ pu32 = (uint32_t *)pv;
+ pb = (uint8_t *)pv;
+ cbBufLeft = _128K;
+ for (uint32_t i = 0; i < cbBufLeft / sizeof(uint32_t); i++)
+ *pu32++ = cCounter++;
+ }
+
+ if (!cbLeft)
+ break;
+ }
+
+ RTMemTmpFree(pv);
+ }
+ else
+ {
+ RTTestIFailed("Allocating write buffer failed - out of memory");
+ rc = VERR_NO_MEMORY;
+ }
+
+ RTTestISubDone();
+ return rc;
+}
+
+
+/**
+ * Writes data to the given file until the given size is reached.
+ *
+ * @returns VBox status code.
+ * @param hVfsFile The file handle to write to.
+ * @param cbFile Size of the file payload in bytes.
+ */
+static int tstCryptoVfsReadAndVerify(RTVFSFILE hVfsFile, size_t cbFile)
+{
+ RTTestISub("Reading from encrypted file and verifying data");
+
+ int rc = VINF_SUCCESS;
+ void *pv = RTMemTmpAllocZ(_128K);
+ if (pv)
+ {
+ size_t cbLeft = cbFile;
+ uint32_t cCounter = 0;
+
+ for (;;)
+ {
+ /* Read the data in multiple calls. */
+ size_t cbBufLeft = RT_MIN(cbLeft, _128K);
+ uint8_t *pb = (uint8_t *)pv;
+
+ while (cbBufLeft)
+ {
+ size_t cbThisRead = RTRandU64Ex(1, RT_MIN(cbBufLeft, cbLeft));
+ rc = RTVfsFileRead(hVfsFile, pb, cbThisRead, NULL /*pcbWritten*/);
+ if (RT_FAILURE(rc))
+ {
+ RTTestIFailed("Reading from file failed with %Rrc (cbLeft=%zu, cbBufLeft=%zu, cbThisRead=%zu)",
+ rc, cbLeft, cbBufLeft, cbThisRead);
+ break;
+ }
+
+ cbBufLeft -= cbThisRead;
+ pb += cbThisRead;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Verify the read data. */
+ size_t cbInBuffer = RT_MIN(cbLeft, _128K);
+ Assert(!(cbInBuffer % sizeof(uint32_t)));
+ uint32_t *pu32 = (uint32_t *)pv;
+
+ for (uint32_t i = 0; i < cbInBuffer / sizeof(uint32_t); i++)
+ {
+ if (*pu32 != cCounter)
+ {
+ RTTestIFailed("Reading from file resulted in corrupted data (expected '%#x' got '%#x')",
+ cCounter, *pu32);
+ break;
+ }
+
+ pu32++;
+ cCounter++;
+ }
+
+ cbLeft -= RT_MIN(cbLeft, _128K);
+ if (!cbLeft)
+ break;
+ }
+
+ RTMemTmpFree(pv);
+ }
+ else
+ {
+ RTTestIFailed("Allocating read buffer failed - out of memory");
+ rc = VERR_NO_MEMORY;
+ }
+
+ RTTestISubDone();
+ return rc;
+}
+
+
+/**
+ * Testing some basics of the encrypted file VFS code.
+ *
+ * @returns nothing.
+ * @param pCryptoIf Pointer to the callback table.
+ */
+static void tstCryptoVfsBasics(PCVBOXCRYPTOIF pCryptoIf)
+{
+ RTTestISub("Encrypted file - Basics");
+
+ RTTestDisableAssertions(g_hTest);
+
+ char *pszCtx = NULL;
+ int rc = tstCryptoCtxCreate(pCryptoIf, g_aCiphers[4], g_szPassword, &pszCtx);
+ if (RT_SUCCESS(rc))
+ {
+ /* Create the memory file to write to. */
+ RTVFSFILE hVfsFile;
+ rc = RTVfsMemFileCreate(NIL_RTVFSIOSTREAM, 0 /*cbEstimate*/, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsFileEnc;
+
+ RTTestISub("Creating encrypted file");
+
+ rc = pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFile, pszCtx, g_szPassword, &hVfsFileEnc);
+ if (RT_SUCCESS(rc))
+ {
+ RTTestISubDone();
+
+ size_t cbFile = RT_ALIGN_Z(RTRandU32Ex(_1K, 10 * _1M), sizeof(uint32_t)); /* Align to full counter field size. */
+ rc = tstCryptoVfsWrite(hVfsFileEnc, cbFile);
+ RTVfsFileRelease(hVfsFileEnc); /* Close file. */
+ if (RT_SUCCESS(rc))
+ {
+ /* Reopen for reading. */
+ RTTestISub("Open encrypted file");
+
+ /* Reset the memory file offset. */
+ RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+
+ rc = pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFile, pszCtx, g_szPassword, &hVfsFileEnc);
+ if (RT_SUCCESS(rc))
+ {
+ RTTestISubDone();
+
+ RTTestISub("Query encrypted file size");
+ uint64_t cbFileRd;
+ rc = RTVfsFileQuerySize(hVfsFileEnc, &cbFileRd);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbFile != cbFileRd)
+ RTTestIFailed("Unexpected file size, got %#llx expected %#zx", cbFileRd, cbFile);
+
+ RTTestISubDone();
+ tstCryptoVfsReadAndVerify(hVfsFileEnc, cbFile);
+ }
+ else
+ RTTestIFailed("Querying encrypted file size failed %Rrc", rc);
+
+ RTVfsFileRelease(hVfsFileEnc); /* Close file. */
+ }
+ else
+ RTTestIFailed("Opening encrypted file for reading failed with %Rrc", rc);
+
+ }
+ /* Error set on failure. */
+ }
+ else
+ RTTestIFailed("Creating encrypted file handle failed with %Rrc", rc);
+
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ RTTestIFailed("Creating a new encrypted file failed with %Rrc", rc);
+
+ RTMemFree(pszCtx);
+ }
+ else
+ RTTestIFailed("Creating a new encrypted context failed with %Rrc", rc);
+
+ RTTestRestoreAssertions(g_hTest);
+ RTTestISubDone();
+}
+
+
+/**
+ * Testing some basics of the crypto keystore code.
+ *
+ * @returns nothing.
+ * @param pCryptoIf Pointer to the callback table.
+ */
+static void tstCryptoKeyStoreBasics(PCVBOXCRYPTOIF pCryptoIf)
+{
+ RTTestISub("Crypto Keystore - Basics");
+
+ RTTestDisableAssertions(g_hTest);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aCiphers); i++)
+ {
+ RTTestISubF("Creating a new keystore for cipher '%s'", g_aCiphers[i]);
+
+ char *pszKeystoreEnc = NULL; /**< The encoded keystore. */
+ int rc = pCryptoIf->pfnCryptoKeyStoreCreate(g_szPassword, &g_abDek[0], sizeof(g_abDek),
+ g_aCiphers[i], &pszKeystoreEnc);
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t *pbKey = NULL;
+ size_t cbKey = 0;
+ char *pszCipher = NULL;
+
+ RTTestSub(g_hTest, "Trying to unlock DEK with wrong password");
+ rc = pCryptoIf->pfnCryptoKeyStoreGetDekFromEncoded(pszKeystoreEnc, g_szPasswordWrong,
+ &pbKey, &cbKey, &pszCipher);
+ RTTESTI_CHECK_RC(rc, VERR_VD_PASSWORD_INCORRECT);
+
+ RTTestSub(g_hTest, "Trying to unlock DEK with correct password");
+ rc = pCryptoIf->pfnCryptoKeyStoreGetDekFromEncoded(pszKeystoreEnc, g_szPassword,
+ &pbKey, &cbKey, &pszCipher);
+ RTTESTI_CHECK_RC_OK(rc);
+ if (RT_SUCCESS(rc))
+ {
+ RTTESTI_CHECK(cbKey == sizeof(g_abDek));
+ CHECK_STR(pszCipher, g_aCiphers[i]);
+ CHECK_BYTES(pbKey, &g_abDek[0], sizeof(g_abDek));
+
+ RTMemSaferFree(pbKey, cbKey);
+ }
+
+ RTMemFree(pszKeystoreEnc);
+ }
+ else
+ RTTestIFailed("Creating a new keystore failed with %Rrc", rc);
+ }
+
+ RTTestRestoreAssertions(g_hTest);
+}
+
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Initialization.
+ */
+ RTEXITCODE rcExit = RTTestInitAndCreate("tstVBoxCrypto", &g_hTest);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ RTTestBanner(g_hTest);
+
+ RTTestSub(g_hTest, "Loading the cryptographic support module");
+ const char *pszModCrypto = NULL;
+ if (argc == 2)
+ {
+ /* The module to load is given on the command line. */
+ pszModCrypto = argv[1];
+ }
+ else
+ {
+ /* Try find it in the extension pack. */
+ /** @todo */
+ RTTestSkipped(g_hTest, "Getting the module from the extension pack is not implemented yet, skipping testcase");
+ }
+
+ if (pszModCrypto)
+ {
+ RTLDRMOD hLdrModCrypto = NIL_RTLDRMOD;
+ int rc = RTLdrLoad(pszModCrypto, &hLdrModCrypto);
+ if (RT_SUCCESS(rc))
+ {
+ PFNVBOXCRYPTOENTRY pfnCryptoEntry = NULL;
+ rc = RTLdrGetSymbol(hLdrModCrypto, VBOX_CRYPTO_MOD_ENTRY_POINT, (void **)&pfnCryptoEntry);
+ if (RT_SUCCESS(rc))
+ {
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ rc = pfnCryptoEntry(&pCryptoIf);
+ if (RT_SUCCESS(rc))
+ {
+ /* Loading succeeded, now we can start real testing. */
+ tstCryptoKeyStoreBasics(pCryptoIf);
+ tstCryptoVfsBasics(pCryptoIf);
+ }
+ else
+ RTTestIFailed("Calling '%s' failed with %Rrc", VBOX_CRYPTO_MOD_ENTRY_POINT, rc);
+ }
+ else
+ RTTestIFailed("Failed to resolve entry point '%s' with %Rrc", VBOX_CRYPTO_MOD_ENTRY_POINT, rc);
+ }
+ else
+ RTTestIFailed("Failed to load the crypto module '%s' with %Rrc", pszModCrypto, rc);
+ }
+
+ return RTTestSummaryAndDestroy(g_hTest);
+}
diff --git a/src/VBox/Main/testcase/tstVBoxMultipleVM.cpp b/src/VBox/Main/testcase/tstVBoxMultipleVM.cpp
new file mode 100644
index 00000000..fc12ab80
--- /dev/null
+++ b/src/VBox/Main/testcase/tstVBoxMultipleVM.cpp
@@ -0,0 +1,617 @@
+/** @file
+ * tstVBoxMultipleVM - load test for ClientWatcher.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/com/com.h>
+#include <VBox/com/string.h>
+#include <VBox/com/array.h>
+#include <VBox/com/Guid.h>
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/com/errorprint.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <VBox/com/VirtualBox.h>
+#include <iprt/stream.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <VBox/sup.h>
+
+#include <vector>
+#include <algorithm>
+
+#include <iprt/test.h>
+#include <iprt/time.h>
+#include <iprt/rand.h>
+#include <iprt/getopt.h>
+
+using namespace com;
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/* Arguments of test thread */
+struct TestThreadArgs
+{
+ /** number of machines that should be run simultaneousely */
+ uint32_t machinesPackSize;
+ /** percents of VM Stop operation what should be called
+ * without session unlocking */
+ uint32_t percentsUnlok;
+ /** How much time in milliseconds test will be executed */
+ uint64_t cMsExecutionTime;
+ /** How much machines create for the test */
+ uint32_t numberMachines;
+};
+
+
+/*********************************************************************************************************************************
+* Global Variables & defs *
+*********************************************************************************************************************************/
+static RTTEST g_hTest;
+#ifdef RT_ARCH_AMD64
+typedef std::vector<Bstr> TMachinesList;
+static volatile bool g_RunTest = true;
+static RTSEMEVENT g_PingEevent;
+static volatile uint64_t g_Counter = 0;
+static TestThreadArgs g_Args;
+
+
+/** Worker for TST_COM_EXPR(). */
+static HRESULT tstComExpr(HRESULT hrc, const char *pszOperation, int iLine)
+{
+ if (FAILED(hrc))
+ {
+ RTTestFailed(g_hTest, "%s failed on line %u with hrc=%Rhrc\n", pszOperation, iLine, hrc);
+ }
+ return hrc;
+}
+
+
+#define CHECK_ERROR_L(iface, method) \
+ do { \
+ hrc = iface->method; \
+ if (FAILED(hrc)) \
+ RTPrintf("warning: %s->%s failed on line %u with hrc=%Rhrc\n", #iface, #method, __LINE__, hrc);\
+ } while (0)
+
+
+/** Macro that executes the given expression and report any failure.
+ * The expression must return a HRESULT. */
+#define TST_COM_EXPR(expr) tstComExpr(expr, #expr, __LINE__)
+
+
+static int tstStartVM(IVirtualBox *pVBox, ISession *pSession, Bstr machineID, bool fSkipUnlock)
+{
+ HRESULT hrc;
+ ComPtr<IProgress> progress;
+ ComPtr<IMachine> machine;
+ Bstr machineName;
+
+ hrc = TST_COM_EXPR(pVBox->FindMachine(machineID.raw(), machine.asOutParam()));
+ if(SUCCEEDED(hrc))
+ hrc = TST_COM_EXPR(machine->COMGETTER(Name)(machineName.asOutParam()));
+ if(SUCCEEDED(hrc))
+ {
+ hrc = machine->LaunchVMProcess(pSession, Bstr("headless").raw(),
+ ComSafeArrayNullInParam(), progress.asOutParam());
+ }
+ if (SUCCEEDED(hrc) && !progress.isNull())
+ {
+ CHECK_ERROR_L(progress, WaitForCompletion(-1));
+ if (SUCCEEDED(hrc))
+ {
+ BOOL completed = true;
+ CHECK_ERROR_L(progress, COMGETTER(Completed)(&completed));
+ if (SUCCEEDED(hrc))
+ {
+ Assert(completed);
+ LONG iRc;
+ CHECK_ERROR_L(progress, COMGETTER(ResultCode)(&iRc));
+ if (SUCCEEDED(hrc))
+ {
+ if (FAILED(iRc))
+ {
+ ProgressErrorInfo info(progress);
+ RTPrintf("Start VM '%ls' failed. Warning: %ls.\n", machineName.raw(), info.getText().raw());
+ }
+ else
+ RTPrintf("VM '%ls' started.\n", machineName.raw());
+ }
+ }
+ }
+ if (!fSkipUnlock)
+ pSession->UnlockMachine();
+ else
+ RTPrintf("Session unlock skipped.\n");
+ }
+ return hrc;
+}
+
+
+static int tstStopVM(IVirtualBox* pVBox, ISession* pSession, Bstr machineID, bool fSkipUnlock)
+{
+ ComPtr<IMachine> machine;
+ HRESULT hrc = TST_COM_EXPR(pVBox->FindMachine(machineID.raw(), machine.asOutParam()));
+ if (SUCCEEDED(hrc))
+ {
+ Bstr machineName;
+ hrc = TST_COM_EXPR(machine->COMGETTER(Name)(machineName.asOutParam()));
+ if (SUCCEEDED(hrc))
+ {
+ MachineState_T machineState;
+ hrc = TST_COM_EXPR(machine->COMGETTER(State)(&machineState));
+ // check that machine is in running state
+ if ( SUCCEEDED(hrc)
+ && ( machineState == MachineState_Running
+ || machineState == MachineState_Paused))
+ {
+ ComPtr<IConsole> console;
+ ComPtr<IProgress> progress;
+
+ hrc = TST_COM_EXPR(machine->LockMachine(pSession, LockType_Shared));
+ if(SUCCEEDED(hrc))
+ TST_COM_EXPR(pSession->COMGETTER(Console)(console.asOutParam()));
+ if(SUCCEEDED(hrc))
+ hrc = console->PowerDown(progress.asOutParam());
+ if (SUCCEEDED(hrc) && !progress.isNull())
+ {
+ //RTPrintf("Stopping VM %ls...\n", machineName.raw());
+ CHECK_ERROR_L(progress, WaitForCompletion(-1));
+ if (SUCCEEDED(hrc))
+ {
+ BOOL completed = true;
+ CHECK_ERROR_L(progress, COMGETTER(Completed)(&completed));
+ if (SUCCEEDED(hrc))
+ {
+ //ASSERT(completed);
+ LONG iRc;
+ CHECK_ERROR_L(progress, COMGETTER(ResultCode)(&iRc));
+ if (SUCCEEDED(hrc))
+ {
+ if (FAILED(iRc))
+ {
+ ProgressErrorInfo info(progress);
+ RTPrintf("Stop VM %ls failed. Warning: %ls.\n", machineName.raw(), info.getText().raw());
+ hrc = iRc;
+ }
+ else
+ {
+ RTPrintf("VM '%ls' stopped.\n", machineName.raw());
+ }
+ }
+ }
+ }
+ if (!fSkipUnlock)
+ pSession->UnlockMachine();
+ else
+ RTPrintf("Session unlock skipped.\n");
+ }
+ }
+ }
+ }
+ return hrc;
+}
+
+
+/**
+ * Get random @a maxCount machines from list of existing VMs.
+ *
+ * @note Can return less then maxCount machines.
+ */
+static int tstGetMachinesList(IVirtualBox *pVBox, uint32_t maxCount, TMachinesList &listToFill)
+{
+ com::SafeIfaceArray<IMachine> machines;
+ HRESULT hrc = TST_COM_EXPR(pVBox->COMGETTER(Machines)(ComSafeArrayAsOutParam(machines)));
+ if (SUCCEEDED(hrc))
+ {
+
+ size_t cMachines = RT_MIN(machines.size(), maxCount);
+ for (size_t i = 0; i < cMachines; ++i)
+ {
+ // choose random index of machine
+ uint32_t idx = RTRandU32Ex(0, (uint32_t)machines.size() - 1);
+ if (machines[idx])
+ {
+ Bstr bstrId;
+ Bstr machineName;
+ CHECK_ERROR_L(machines[idx], COMGETTER(Id)(bstrId.asOutParam()));
+ if (SUCCEEDED(hrc))
+ CHECK_ERROR_L(machines[idx], COMGETTER(Name)(machineName.asOutParam()));
+ if (SUCCEEDED(hrc))
+ {
+ if (Utf8Str(machineName).startsWith("umtvm"))
+ listToFill.push_back(bstrId);
+ }
+ }
+ }
+
+ // remove duplicates from the vector
+ std::sort(listToFill.begin(), listToFill.end());
+ listToFill.erase(std::unique(listToFill.begin(), listToFill.end()), listToFill.end());
+ RTPrintf("Filled pack of %d from %d machines.\n", listToFill.size(), machines.size());
+ }
+
+ return hrc;
+}
+
+
+static int tstMachinesPack(IVirtualBox *pVBox, uint32_t maxPackSize, uint32_t percentage)
+{
+ HRESULT hrc = S_OK;
+ TMachinesList machinesList;
+ bool alwaysUnlock = false;
+ uint64_t percN = 0;
+
+ // choose and fill pack of machines for test
+ tstGetMachinesList(pVBox, maxPackSize, machinesList);
+
+ RTPrintf("Start test.\n");
+ // screw up counter
+ g_Counter = UINT64_MAX - machinesList.size() <= g_Counter ? 0 : g_Counter;
+ if (percentage > 0)
+ percN = 100 / percentage;
+ else
+ alwaysUnlock = true;
+
+ // start all machines in pack
+ for (TMachinesList::iterator it = machinesList.begin();
+ it != machinesList.end() && g_RunTest;
+ ++it)
+ {
+ ComPtr<ISession> session;
+ hrc = session.createInprocObject(CLSID_Session);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = tstStartVM(pVBox, session, *it, !(alwaysUnlock || g_Counter++ % percN));
+ }
+ RTSemEventSignal(g_PingEevent);
+ RTThreadSleep(100);
+ }
+ // stop all machines in the pack
+ for (TMachinesList::iterator it = machinesList.begin();
+ it != machinesList.end() && g_RunTest;
+ ++it)
+ {
+ ComPtr<ISession> session;
+ hrc = session.createInprocObject(CLSID_Session);
+ if (SUCCEEDED(hrc))
+ {
+ // stop machines, skip session unlock of given % of machines
+ hrc = tstStopVM(pVBox, session, *it, !(alwaysUnlock || g_Counter++ % percN));
+ }
+ RTSemEventSignal(g_PingEevent);
+ RTThreadSleep(100);
+ }
+ return hrc;
+}
+
+
+static Bstr tstMakeMachineName(int i)
+{
+ char szMachineName[32];
+ RTStrPrintf(szMachineName, sizeof(szMachineName), "umtvm%d", i);
+ return Bstr(szMachineName);
+}
+
+
+static int tstCreateMachines(IVirtualBox *pVBox)
+{
+ HRESULT hrc = S_OK;
+ // create machines for the test
+ for (uint32_t i = 0; i < g_Args.numberMachines; i++)
+ {
+ ComPtr<IMachine> ptrMachine;
+ com::SafeArray<BSTR> groups;
+
+ Bstr machineName(tstMakeMachineName(i));
+ /* Default VM settings */
+ CHECK_ERROR_L(pVBox, CreateMachine(NULL, /* Settings */
+ machineName.raw(), /* Name */
+ ComSafeArrayAsInParam(groups), /* Groups */
+ NULL, /* OS Type */
+ NULL, /** Cipher */
+ NULL, /** Password id */
+ NULL, /** Password */
+ NULL, /* Create flags */
+ ptrMachine.asOutParam()));
+ if (SUCCEEDED(hrc))
+ {
+ CHECK_ERROR_L(pVBox, RegisterMachine(ptrMachine));
+ RTPrintf("Machine '%ls' created\n", machineName.raw());
+ }
+
+ RTSemEventSignal(g_PingEevent);
+ RTThreadSleep(100);
+ }
+ return hrc;
+}
+
+
+static int tstClean(IVirtualBox *pVBox, IVirtualBoxClient *pClient)
+{
+ RT_NOREF(pClient);
+ HRESULT hrc = S_OK;
+
+ // stop all machines created for the test
+ for (uint32_t i = 0; i < g_Args.numberMachines; i++)
+ {
+ ComPtr<IMachine> machine;
+ ComPtr<IProgress> progress;
+ ComPtr<ISession> session;
+ SafeIfaceArray<IMedium> media;
+
+ Bstr machineName(tstMakeMachineName(i));
+
+ /* Delete created VM and its files */
+ CHECK_ERROR_L(pVBox, FindMachine(machineName.raw(), machine.asOutParam()));
+
+ // try to stop it again if it was not stopped
+ if (SUCCEEDED(hrc))
+ {
+ MachineState_T machineState;
+ CHECK_ERROR_L(machine, COMGETTER(State)(&machineState));
+ if ( SUCCEEDED(hrc)
+ && ( machineState == MachineState_Running
+ || machineState == MachineState_Paused) )
+ {
+ hrc = session.createInprocObject(CLSID_Session);
+ if (SUCCEEDED(hrc))
+ tstStopVM(pVBox, session, machineName, FALSE);
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ CHECK_ERROR_L(machine, Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(media)));
+ if (SUCCEEDED(hrc))
+ CHECK_ERROR_L(machine, DeleteConfig(ComSafeArrayAsInParam(media), progress.asOutParam()));
+ if (SUCCEEDED(hrc))
+ CHECK_ERROR_L(progress, WaitForCompletion(-1));
+ if (SUCCEEDED(hrc))
+ RTPrintf("Machine '%ls' deleted.\n", machineName.raw());
+ }
+ return hrc;
+}
+
+
+static DECLCALLBACK(int) tstThreadRun(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+ TestThreadArgs* args = (TestThreadArgs*)pvUser;
+ Assert(args != NULL);
+ uint32_t maxPackSize = args->machinesPackSize;
+ uint32_t percentage = args->percentsUnlok;
+
+ HRESULT hrc = com::Initialize();
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<IVirtualBoxClient> ptrVBoxClient;
+ ComPtr<IVirtualBox> ptrVBox;
+
+ hrc = TST_COM_EXPR(ptrVBoxClient.createInprocObject(CLSID_VirtualBoxClient));
+ if (SUCCEEDED(hrc))
+ hrc = TST_COM_EXPR(ptrVBoxClient->COMGETTER(VirtualBox)(ptrVBox.asOutParam()));
+ if (SUCCEEDED(hrc))
+ {
+ RTPrintf("Creating machines...\n");
+ tstCreateMachines(ptrVBox);
+
+ while (g_RunTest)
+ {
+ hrc = tstMachinesPack(ptrVBox, maxPackSize, percentage);
+ }
+
+ RTPrintf("Deleting machines...\n");
+ tstClean(ptrVBox, ptrVBoxClient);
+ }
+
+ g_RunTest = false;
+ RTSemEventSignal(g_PingEevent);
+ RTThreadSleep(100);
+
+ ptrVBox = NULL;
+ ptrVBoxClient = NULL;
+ com::Shutdown();
+ }
+ return hrc;
+}
+
+
+static int ParseArguments(int argc, char **argv, TestThreadArgs *pArgs)
+{
+ RTGETOPTSTATE GetState;
+ RTGETOPTUNION ValueUnion;
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--packsize", 'p', RTGETOPT_REQ_UINT32 }, // number of machines to start together
+ { "--lock", 's', RTGETOPT_REQ_UINT32 }, // percentage of VM sessions closed without Unlok
+ { "--time", 't', RTGETOPT_REQ_UINT64 }, // required time of load test execution, in seconds
+ { "--machines" , 'u', RTGETOPT_REQ_UINT32 }
+ };
+ int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
+ AssertRCReturn(rc, rc);
+ AssertPtr(pArgs);
+
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ switch (rc)
+ {
+ case 'p':
+ if (ValueUnion.u32 == 0)
+ {
+ RTPrintf("--packsize should be more then zero\n");
+ return VERR_INVALID_PARAMETER;
+ }
+ if (ValueUnion.u32 > 16000)
+ {
+ RTPrintf("maximum --packsize value is 16000.\n"
+ "That means can use no more then 16000 machines for the test.\n");
+ return VERR_INVALID_PARAMETER;
+ }
+ pArgs->machinesPackSize = ValueUnion.u32;
+ break;
+
+ case 's':
+ if (ValueUnion.u32 > 100)
+ {
+ RTPrintf("maximum --lock value is 100.\n"
+ "That means 100 percent of sessions should be closed without unlock.\n");
+ return VERR_INVALID_PARAMETER;
+ }
+ pArgs->percentsUnlok = ValueUnion.u32;
+ break;
+
+ case 't':
+ pArgs->cMsExecutionTime = ValueUnion.u64 * 1000;
+ break;
+
+ case 'u':
+ if (ValueUnion.u32 > 16000)
+ {
+ RTPrintf("maximum --machines value is 16000.\n"
+ "That means can make no more then 16000 machines for the test.\n");
+ return VERR_INVALID_PARAMETER;
+ }
+ if (ValueUnion.u32 < pArgs->machinesPackSize)
+ {
+ RTPrintf("--machines value should be larger then --packsize value.\n");
+ return VERR_INVALID_PARAMETER;
+ }
+ pArgs->numberMachines = ValueUnion.u32;
+ break;
+
+ default:
+ RTGetOptPrintError(rc, &ValueUnion);
+ return rc;
+ }
+ }
+ return rc;
+}
+
+#endif /* RT_ARCH_AMD64 */
+
+
+/**
+ *
+ * Examples:
+ * - tstVBoxClientWatcherLoad --packsize 500 --lock 10 --time 14400 --machines 4000
+ * It will create 4000 VMs with names "utmvm0"..."utmvm3999". It will start
+ * 500 random VMs together, stop them, without closing their session with
+ * probability 10%, will repeat this over 4 hours. After test it will
+ * delete all "utmvm..." machines.
+ *
+ * - tstVBoxClientWatcherLoad --packsize 1 --lock 30 --time 3600 --machines 1000
+ * It will create 1000 VMs with names "utmvm0"..."utmvm999". It will start
+ * random VM - stop them, without closing their session with probability
+ * 30%, will repeat this over 30 minutes. After test it will delete all
+ * "utmvm..." machines.
+ */
+int main(int argc, char **argv)
+{
+ RT_NOREF(argc, argv);
+ RTEXITCODE rcExit = RTTestInitAndCreate("tstVBoxMultipleVM", &g_hTest);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ SUPR3Init(NULL);
+ com::Initialize();
+ RTTestBanner(g_hTest);
+
+#ifndef RT_ARCH_AMD64
+ /*
+ * Linux OOM killer when running many VMs on a 32-bit host.
+ */
+ return RTTestSkipAndDestroy(g_hTest, "The test can only run reliably on 64-bit hosts.");
+#else /* RT_ARCH_AMD64 */
+
+ RTPrintf("Initializing ...\n");
+ int rc = RTSemEventCreate(&g_PingEevent);
+ AssertRC(rc);
+
+ g_Args.machinesPackSize = 100;
+ g_Args.percentsUnlok = 10;
+ g_Args.cMsExecutionTime = 3*RT_MS_1MIN;
+ g_Args.numberMachines = 200;
+
+ /*
+ * Skip this test for the time being. Saw crashes on several test boxes but no time
+ * to debug.
+ */
+ if (argc == 1)
+ return RTTestSkipAndDestroy(g_hTest, "Test crashes sometimes.\n");
+
+ rc = ParseArguments(argc, argv, &g_Args);
+ if (RT_FAILURE(rc))
+ return RTTestSkipAndDestroy(g_hTest, "Invalid arguments.\n");
+
+ RTPrintf("Arguments packSize = %d, percentUnlok = %d, time = %lld.\n",
+ g_Args.machinesPackSize, g_Args.percentsUnlok, g_Args.cMsExecutionTime);
+
+ RTTHREAD hThread;
+ rc = RTThreadCreate(&hThread, tstThreadRun, (void *)&g_Args,
+ 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "tstThreadRun");
+ if (RT_SUCCESS(rc))
+ {
+ AssertRC(rc);
+
+ uint64_t msStart = RTTimeMilliTS();
+ while (RTTimeMilliTS() - msStart < g_Args.cMsExecutionTime && g_RunTest)
+ {
+ // check that test thread didn't hang and call us periodically
+ // allowed 30 seconds for operation - msStart or stop VM
+ rc = RTSemEventWait(g_PingEevent, 3 * 60 * 1000);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_TIMEOUT)
+ {
+ RTTestFailed(g_hTest, "Timeout. Deadlock?\n");
+ com::Shutdown();
+ return RTTestSummaryAndDestroy(g_hTest);
+ }
+ AssertRC(rc);
+ }
+ }
+
+ RTPrintf("Finishing...\n");
+
+ // finish test thread
+ g_RunTest = false;
+ // wait it for finish
+ RTThreadWait(hThread, RT_INDEFINITE_WAIT, &rc);
+ }
+ RTSemEventDestroy(g_PingEevent);
+
+ com::Shutdown();
+ if (RT_FAILURE(rc))
+ RTTestFailed(g_hTest, "Test failed.\n");
+ else
+ RTTestPassed(g_hTest, "Test finished.\n");
+ return RTTestSummaryAndDestroy(g_hTest);
+#endif /* RT_ARCH_AMD64 */
+}
+
diff --git a/src/VBox/Main/webservice/MANIFEST.MF.in b/src/VBox/Main/webservice/MANIFEST.MF.in
new file mode 100644
index 00000000..3d04734c
--- /dev/null
+++ b/src/VBox/Main/webservice/MANIFEST.MF.in
@@ -0,0 +1,28 @@
+Manifest-Version: 1.0
+Bundle-Symbolicname: org.virtualbox@VBOX_API_SUFFIX@
+Bundle-Name: vboxjws
+Bundle-Vendor: VirtualBox
+Bundle-Version: @VBOX_VERSION_STRING@
+Bundle-License: http://glassfish.java.net/nonav/public/CDDL+GPL.html
+Bundle-ManifestVersion: 2
+Tool: vi+sed
+Export-Package: org.virtualbox@VBOX_API_SUFFIX@;uses="javax.jws.soap,
+ javax.xml,javax.xml.bind,javax.xml.bind.annotation,
+ javax.xml.bind.attachment,javax.xml.bind.helpers,javax.xml.bind.util,
+ javax,xml.namespace,javax.xml.parsers,javax.xml.stream,
+ javax.xml.transform,javax.xml.transform.dom,
+ javax.xml.transform.stream,javax.xml.ws,javax.xml.ws.handler,
+ javax.xml.ws.http,javax.xml.ws.soap,javax.xml.ws.spi,
+ javax.xml.ws.wsaddressing,org.w3c.dom,org.xml.sax";
+ version="@VBOX_VERSION_STRING@"
+Import-Package: javax.validation.constraints;version="1.0",
+ javax.jws,com.sun.xml.ws.developer,
+ com.sun.xml.ws,com.sun.xml.ws.api.message,javax.jws.soap,
+ javax.xml.namespace,javax.xml.ws,javax.xml.ws.spi,
+ javax.xml.ws.soap,javax.xml.ws.handler,javax.xml.ws.http,
+ javax.xml.ws.wsaddressing,javax.xml.parsers,javax.xml.stream,
+ javax.xml.transform,javax.xml.transform.dom,
+ javax.xml.transform.stream,org.w3c.dom,org.xml.sax,javax.xml.bind,
+ javax.xml.bind.annotation,javax.xml.bind.attachment,
+ javax.xml.bind.helpers,javax.xml.bind.util,
+ org.virtualbox@VBOX_API_SUFFIX@;version="@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@"
diff --git a/src/VBox/Main/webservice/Makefile.kmk b/src/VBox/Main/webservice/Makefile.kmk
new file mode 100644
index 00000000..072d066e
--- /dev/null
+++ b/src/VBox/Main/webservice/Makefile.kmk
@@ -0,0 +1,960 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VBox web service.
+#
+# Warning! This is a seriously complicated makefile!
+#
+
+#
+# Copyright (C) 2007-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# Define VBOX_GSOAP_INSTALLED to something if you have gSOAP's
+# "wsdl2h" and "soapcpp2" executables on your PATH somewhere.
+
+#
+# Here's an overview how all this works. It's complicated. Essentially,
+# lots of files get generated automatically from our XML XIDL file that
+# describes the VirtualBox API (../idl/VirtualBox.xidl); as a result,
+# no manual coding is necessary when the API changes. All generated
+# web service code gets adjusted automatically.
+#
+# In more detail:
+#
+# 1) We use xsltproc and websrv-wsdl.xsl to generate a WSDL file from
+# our XML IDL file. WSDL (Web Service Description language) is an XML
+# industry standard that publicly describes a web service.
+# So, the WSDL generated here describes the VirtualBox web
+# service to third-party clients; for example, one can feed it to
+# Java or Perl or some other toolkit that understands WSDL and thus
+# easily write a short script that connects to the web service properly.
+# This WSDL file ends up in $(VBOXWEB_OUT_DIR)/vboxweb.wsdl.
+#
+# 2) We use xsltproc and websrv-wsdl2gsoapH.xsl to generate a so-called
+# "gSoap header file": $(VBOXWEB_OUT_DIR)/gsoapH_from_xslt.h from
+# the WSDL previously generated.
+# This file looks like a C header file, but really isn't meant
+# to be included by a C compiler. Instead, it just happens to be the
+# format that gSOAP uses to specify SOAP interfaces instead of WSDL.
+# (The reason for this appears to be that gSOAP predates WSDL and
+# thus needed some format to describe the syntax of a web service.)
+#
+# Note that gSOAP now also comes with its own WSDL-to-gsoap.h converter,
+# but the readme mentions some funny license restrictions, so instead we
+# have our own converter in XSLT.
+#
+# 3) We then feed that pseudo-header file to gSOAP's soapcpp2 compiler,
+# which generates a ton of files in $(VBOXWEB_OUT_DIR), most importantly:
+#
+# SOAP_CLIENT_H = $(VBOXWEB_OUT_DIR)/soapStub.h (header file for webservice clients)
+# SOAP_SERVER_H = $(VBOXWEB_OUT_DIR)/soapH.h (header file for webservice servers)
+#
+# These are "real" header files that one can use to program a) a webservice client
+# and b) a webservice server. Of course to build b) one will have to write method
+# implementations with useful code that does something. This is where more
+# code generation via XSLT comes in:
+#
+# 4) We use xsltproc to generate tons of C++ code directly from the XIDL that
+# maps each SOAP method to our COM methods. This large C++ file is
+# $(VBOXWEB_OUT_DIR)/methodmaps.cpp. The actual webservice executable (vboxwebsrv,
+# which acts as an HTTP server) is composed of this file, plus hard-coded
+# method implementations in vboxweb.cpp, plus gSOAP library code for the HTTP
+# server.
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Find the gSOAP toolkit.
+#
+# Note! We're not using the gSOAP toolkit correctly. The main issue is that
+# compiling soapcpp2.cpp instead of using the library. So, in order
+# to make this work with a locally installed gSOAP toolkit there are
+# some hoops to jump thru to say the least... Shipping soapcpp2.cpp/h
+# is out of the question without also including the two soap tools.
+#
+# Some observations on distros for OSE / configure:
+# The proposed gentoo ebuild screws up several things in the install phase
+# and thus fails to ship stdsoap2.cpp and relatives.
+#
+# debian (2.7.9l-0.2) stuffs stdsoap2.cpp and a handful of the import files
+# into /usr/include/gsoap.
+#
+# fedora (2.7.12-fc10.x86_64) uses the default install layout and does not
+# ship stdsoap2.cpp and friends.
+#
+ifeq ($(VBOX_GSOAP_INSTALLED),)
+ VBOX_GSOAP_INSTALLED = 1
+ VBOX_PATH_GSOAP := $(firstfile $(rversortfiles $(qwildcard ,$(KBUILD_DEVTOOLS)/common/gsoap/*)))
+ ifeq ($(VBOX_PATH_GSOAP),)
+ VBOX_PATH_GSOAP := $(firstfile $(rversortfiles $(qwildcard ,$(KBUILD_DEVTOOLS_HST)/gsoap/*)))
+ endif
+ if "$(VBOX_PATH_GSOAP)" == "" && defined(KBUILD_DEVTOOLS_HST)
+ VBOX_PATH_GSOAP := $(firstfile $(rversortfiles $(qwildcard ,$(KBUILD_DEVTOOLS_HST_ALT)/gsoap/*)))
+ endif
+ ifeq ($(VBOX_PATH_GSOAP),)
+ $(warning VBOX_PATH_GSOAP not found...)
+ VBOX_GSOAP_INSTALLED =
+ endif
+else
+ VBOX_PATH_GSOAP := $(VBOX_PATH_GSOAP)
+endif
+VBOX_PATH_GSOAP_BIN := $(strip $(VBOX_PATH_GSOAP_BIN))
+if "$(VBOX_PATH_GSOAP_BIN)" == ""
+ VBOX_PATH_GSOAP_BIN := $(VBOX_PATH_GSOAP)/bin
+ if "$(KBUILD_HOST)" == "darwin"
+ VBOX_PATH_GSOAP_BIN := $(VBOX_PATH_GSOAP_BIN)/macosx
+ else if "$(KBUILD_HOST)" == "win"
+ VBOX_PATH_GSOAP_BIN := $(VBOX_PATH_GSOAP_BIN)/win32
+ else
+ VBOX_PATH_GSOAP_BIN := $(VBOX_PATH_GSOAP_BIN)/$(KBUILD_HOST).x86
+ endif
+ if !exists($(VBOX_PATH_GSOAP_BIN))
+ VBOX_PATH_GSOAP_BIN := $(VBOX_PATH_GSOAP)/bin
+ endif
+endif
+VBOX_SOAPCPP2 := $(VBOX_PATH_GSOAP_BIN)/soapcpp2$(HOSTSUFF_EXE)
+VBOX_WSDL2H := $(VBOX_PATH_GSOAP_BIN)/wsdl2h$(HOSTSUFF_EXE)
+VBOX_STUBMAKER = $(firstword $(which stubmaker stubmaker.pl) stubmaker_not_found)
+# Keep in mind that Python ZSI only exists for Python 2.x, so this hardcodes
+# some things. If you want to build using Python 3.x only you need to disable
+# the creation of the Python webservice bindings anyway.
+if "$(KBUILD_HOST)" != "win"
+VBOX_WSDL2PY = $(firstword $(which wsdl2py) wsdl2py_not_found)
+else
+VBOX_WSDL2PY = $(firstword $(wildcard C:/Python27/Scripts/wsdl2py.exe) wsdl2py_not_found)
+endif
+
+VBOX_PATH_GSOAP_IMPORT := $(strip $(if $(VBOX_PATH_GSOAP_IMPORT),$(VBOX_PATH_GSOAP_IMPORT),$(VBOX_PATH_GSOAP)/import))
+VBOX_GSOAP_INCS := $(strip $(if $(VBOX_GSOAP_INCS),$(VBOX_GSOAP_INCS),$(VBOX_PATH_GSOAP) $(VBOX_PATH_GSOAP_IMPORT) ))
+# note: $(if $(defined FOO)) does not work here!
+VBOX_GSOAP_CXX_SOURCES := $(strip $(if-expr "$(origin VBOX_GSOAP_CXX_SOURCES)" != "undefined",$(VBOX_GSOAP_CXX_SOURCES),$(VBOX_PATH_GSOAP)/stdsoap2.cpp))
+VBOX_GSOAP_CXX_LIBS := $(strip $(if-expr "$(origin VBOX_GSOAP_CXX_LIBS)" != "undefined",$(VBOX_GSOAP_CXX_LIBS),))
+
+
+#
+# Globals
+#
+VBOXWEB_OUT_DIR := $(PATH_TARGET)/webservice
+BLDDIRS += $(VBOXWEB_OUT_DIR)
+
+# The webservice location
+VBOX_PATH_WEBSERVICE := $(PATH_SUB_CURRENT)
+
+# The IDL subdirectory (contains some XSLT files)
+VBOX_PATH_IDL := $(abspath $(PATH_SUB_CURRENT)/../idl)
+
+# If this is set, all webservice files are considered out-of-date every time
+# this makefile is touched. Otherwise, set this to empty.
+RECOMPILE_ON_MAKEFILE_CURRENT := $(MAKEFILE_CURRENT)
+
+PATH_TARGET_SOAPDEMOXML := $(VBOXWEB_OUT_DIR)/demo_soapxml
+PATH_TARGET_SOAPDEMOHEADERS := $(VBOXWEB_OUT_DIR)/demo_headers
+PATH_TARGET_SOAPDEMONSMAPS := $(VBOXWEB_OUT_DIR)/demo_namespacemaps
+PATH_TARGET_WEBTEST := $(VBOXWEB_OUT_DIR)/webtest
+
+# the original XIDL file (has to include documentation as we need it):
+VBOXWEB_IDL_SRC_ORIG := $(VBOX_XIDL_FILE_SRC)
+# the original XIDL file without documentation
+VBOXWEB_IDL_SRC_STRIPPED := $(VBOX_XIDL_FILE)
+# platform-specific XIDL file generated from $(VBOXWEB_IDL_SRC_STRIPPED):
+VBOXWEB_IDL_SRC := $(VBOXWEB_OUT_DIR)/VirtualBox.xidl
+
+VBOXWEB_WSDL = $(VBOXWEB_OUT_DIR)/vboxweb.wsdl
+VBOXWEBSERVICE_WSDL = $(VBOXWEB_OUT_DIR)/vboxwebService.wsdl
+
+VBOXWEB_TYPEMAP := $(VBOXWEB_OUT_DIR)/typemap.dat
+
+VBOXWEB_GSOAPH_FROM_XSLT := $(VBOXWEB_OUT_DIR)/gsoapH_from_xslt.h
+ifdef VBOX_GSOAP_INSTALLED
+ VBOXWEB_GSOAPH_FROM_GSOAP := $(VBOXWEB_OUT_DIR)/gsoapH_from_gsoap.h
+else
+ VBOXWEB_GSOAPH_FROM_GSOAP :=
+endif
+VBOXWEB_SOAP_CLIENT_H := $(VBOXWEB_OUT_DIR)/soapStub.h
+VBOXWEB_SOAP_SERVER_H := $(VBOXWEB_OUT_DIR)/soapH.h
+
+
+ifdef VBOX_GSOAP_VERBOSE
+ VBOXWEB_XSLTPROC_VERBOSE = --stringparam G_argDebug '1'
+ VBOXWEB_WSDL_VERBOSE = -v
+else
+ VBOXWEB_SOAPCPP2_SKIP_FILES = -x
+endif
+
+
+## @todo VBOXWEB_GSOAPH_FROM_XSLT should probably be a indirect dep of something.
+VBOXWEB_OTHERS += \
+ $(VBOXWEB_GSOAPH_FROM_XSLT)
+
+
+# GCC 9.2 doesn't cope ver well with the split soapC.cpp files (internal error).
+# Seemingly, the issue goes away when we strip inline functions from soapH.h.
+ifdef VBOX_WITHOUT_NOINLINE_SOAPH
+ if !defined(VBOX_WITHOUT_SPLIT_SOAPC) && !defined(VBOX_WITH_SPLIT_SOAPC) && $(VBOX_GCC_VERSION_CXX) >= 90200
+ $(info Triggering VBOX_WITHOUT_SPLIT_SOAPC because of VBOX_GCC_VERSION_CXX=$(VBOX_GCC_VERSION_CXX) and VBOX_WITHOUT_NOINLINE_SOAPH)
+ VBOX_WITHOUT_SPLIT_SOAPC := 1
+ endif
+endif
+
+# disable -fvisibility=hidden as the SOAP stuff does not properly set the visibility attributes
+TEMPLATE_VBOXWEBR3EXE = Webservices without -fvisibility
+TEMPLATE_VBOXWEBR3EXE_EXTENDS = VBOXR3EXE
+TEMPLATE_VBOXWEBR3EXE_DEFS.win += WIN32_LEAN_AND_MEAN $(TEMPLATE_VBOXR3EXE_DEFS.win) # Makes the redefinition warnings go away.
+TEMPLATE_VBOXWEBR3EXE_CXXFLAGS = $(filter-out $(VBOX_GCC_fvisibility-hidden) $(VBOX_GCC_fvisibility-inlines-hidden),\
+ $(TEMPLATE_VBOXR3EXE_CXXFLAGS))
+ifn1of ($(KBUILD_TARGET), win)
+TEMPLATE_VBOXWEBR3EXE_CXXFLAGS += $(VBOX_GCC_Wno-misleading-indentation)
+endif
+if "$(VBOX_VCC_TOOL_STEM)" >= "VCC140"
+ # -wd4774: string(532): warning C4774: 'sprintf_s' : format string expected in argument 3 is not a string literal
+ # -wd4458: stdsoap2.h(2644): warning C4458: declaration of 'type' hides class member
+ # -wd5039: x509v3.h(883): warning C5039: 'OPENSSL_sk_set_cmp_func': pointer or reference to potentially throwing function passed to 'extern "C"' function under -EHc. Undefined behavior may occur if this function throws an exception.
+ # soapc-1.cpp(182) : warning C4883: 'soap_getelement': function size suppresses optimizations
+ TEMPLATE_VBOXWEBR3EXE_CXXFLAGS.win = $(TEMPLATE_VBOXR3EXE_CXXFLAGS.win) -wd4774 -wd4458 -wd4883 -wd5039
+endif
+
+ifdef VBOX_GSOAP_INSTALLED
+ ifndef VBOX_ONLY_SDK
+ #
+ # soapC and soapH-noinline file splitter.
+ #
+ BLDPROGS += split-soapC
+ split-soapC_TEMPLATE = VBoxBldProg
+ split-soapC_SOURCES = split-soapC.cpp
+
+ #
+ # vboxsoap - Library used by both the programs (save build time).
+ #
+ LIBRARIES += vboxsoap
+ vboxsoap_TEMPLATE = VBOXWEBR3EXE
+ ifndef VBOX_WITHOUT_PRECOMPILED_HEADERS
+ ifeq ($(KBUILD_TARGET),win)
+ vboxsoap_USES = vccprecomp
+ vboxsoap_PCH_HDR := $(VBOXWEB_SOAP_SERVER_H)
+ vboxsoap_CXXFLAGS += -Zm127
+ endif
+ endif
+ vboxsoap_CXXFLAGS += $(VBOX_C_CXX_FLAGS_NO_UNUSED_PARAMETERS)
+ vboxsoap_CXXFLAGS.win += -bigobj
+ vboxsoap_CXXFLAGS.win += -wd4702 # soapc-4.cpp(16) : warning C4702: unreachable code
+ ifn1of ($(KBUILD_TARGET), win)
+ vboxsoap_CXXFLAGS += -Wno-shadow -Wno-parentheses $(VBOX_GCC_Wno-literal-suffix) \
+ $(VBOX_GCC_Wno-stringop-overflow) $(VBOX_GCC_Wno-stringop-truncation) \
+ $(VBOX_GCC_Wno-vla) -Wno-format -Wno-deprecated-declarations $(VBOX_GCC_fno-printf-return-value)
+ ifn1of ($(KBUILD_TYPE), debug) # Save time+memory by using -O0 instead of -O2.
+ vboxsoap_CXXFLAGS += -O0 ## @todo this could be interesting for g++ (not clang++): -fcprop-registers
+ endif
+ endif
+ vboxsoap_INCS := \
+ $(VBOX_GSOAP_INCS) \
+ $(VBOXWEB_OUT_DIR) \
+ $(PATH_SUB_CURRENT)
+ ifdef VBOX_WITH_WEBSERVICES_SSL
+ vboxsoap_DEFS += WITH_OPENSSL
+ vboxsoap_SDKS += VBOX_OPENSSL2
+ endif
+ ifdef VBOX_WITHOUT_SPLIT_SOAPC
+ vboxsoap_SOURCES = \
+ $(VBOXWEB_OUT_DIR)/soapC.cpp
+ else
+ vboxsoap_SOURCES = \
+ $(VBOXWEB_OUT_DIR)/soapC-1.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-2.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-3.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-4.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-5.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-6.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-7.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-8.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-9.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-10.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-11.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-12.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-13.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-14.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-15.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-16.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-17.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-18.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-19.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-20.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-21.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-22.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-23.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-24.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-25.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-26.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-27.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-28.cpp \
+ $(VBOXWEB_OUT_DIR)/soapC-29.cpp
+ endif
+ ifndef VBOX_WITHOUT_NOINLINE_SOAPH
+ vboxsoap_SOURCES += \
+ $(VBOXWEB_OUT_DIR)/soapH-noinline-1.cpp \
+ $(VBOXWEB_OUT_DIR)/soapH-noinline-2.cpp \
+ $(VBOXWEB_OUT_DIR)/soapH-noinline-3.cpp \
+ $(VBOXWEB_OUT_DIR)/soapH-noinline-4.cpp \
+ $(VBOXWEB_OUT_DIR)/soapH-noinline-5.cpp \
+ $(VBOXWEB_OUT_DIR)/soapH-noinline-6.cpp \
+ $(VBOXWEB_OUT_DIR)/soapH-noinline-7.cpp \
+ $(VBOXWEB_OUT_DIR)/soapH-noinline-8.cpp \
+ $(VBOXWEB_OUT_DIR)/soapH-noinline-9.cpp \
+ $(VBOXWEB_OUT_DIR)/soapH-noinline-10.cpp \
+ $(VBOXWEB_OUT_DIR)/soapH-noinline-11.cpp \
+ $(VBOXWEB_OUT_DIR)/soapH-noinline-12.cpp
+ endif
+ vboxsoap_CLEAN := $(vboxsoap_SOURCES) # lazy bird
+ vboxsoap_SOURCES <= \
+ $(VBOX_GSOAP_CXX_SOURCES)
+ vboxsoap_ORDERDEPS = \
+ $(VBOXWEB_IDL_SRC) \
+ $(VBOXWEB_OUT_DIR)/gsoap_copy_all_ts
+ ifn1of ($(KBUILD_TARGET), win)
+ $(VBOX_GSOAP_CXX_SOURCES)_CXXFLAGS = \
+ -Wno-format \
+ $(VBOX_GCC_Wno-int-in-bool-context) \
+ $(VBOX_GCC_Wno-logical-op)
+ # currently necessary when compiling against OpenSSL 1.0 due to a missing
+ # typecase from 'const v3_ext_method*' to 'aka v3_ext_method*'.
+ $(VBOX_GSOAP_CXX_SOURCES)_CXXFLAGS += -fpermissive
+ # Necessary with gcc 9.2.1 for some reason:
+ $(VBOX_GSOAP_CXX_SOURCES)_CXXFLAGS += -Wno-deprecated-declarations
+ endif
+
+ if "$(KBUILD_TARGET)" == "win" && "$(VBOX_GSOAP_CXX_SOURCES)" != ""
+ $(VBOX_GSOAP_CXX_SOURCES)_CXXFLAGS.win += -wd4668 # preprocessor / windows.h
+ $(VBOX_GSOAP_CXX_SOURCES)_CXXFLAGS.win += -wd4211 # nonstandard extension used: redefined extern to static
+ $(VBOX_GSOAP_CXX_SOURCES)_CXXFLAGS.win += -wd4310 # cast truncates constant value
+ if1of ($(VBOX_VCC_TOOL_STEM), VCC120)
+ $(VBOX_GSOAP_CXX_SOURCES)_CXXFLAGS.win += -wd4056 # v2.8.36/stdsoap2.cpp(14008) : warning C4056: overflow in floating-point constant arithmetic
+ $(VBOX_GSOAP_CXX_SOURCES)_CXXFLAGS.win += -wd4756 # v2.8.36/stdsoap2.cpp(14008) : warning C4756: overflow in constant arithmetic
+ endif
+ if "$(VBOX_VCC_TOOL_STEM)" >= "VCC140"
+ $(VBOX_GSOAP_CXX_SOURCES)_CXXFLAGS.win += -wd4456 # stdsoap2.cpp(3127): warning C4456: declaration of 'i' hides previous local declaration
+ endif
+ endif
+
+ ifdef VBOX_SOAP_PRECOMPILED_HEADER
+ # This'll save a few seconds, but the compiler invocation currently makes it impracticable. This will
+ # be addressed in a future kBuild version, by adding PCH support or/and by adding some helpers to
+ # gather the required data (DEFS,INCS,CXXTOOL,CXXFLAGS).
+ vboxsoap_INTERMEDIATES += $(VBOXWEB_OUT_DIR)/soapH.h.gch
+ vboxsoap_CXXFLAGS += -Winvalid-pch -H
+ vboxsoap_CLEAN += $(VBOXWEB_OUT_DIR)/soapH.h.gch
+
+ $(VBOXWEB_OUT_DIR)/soapH.h.gch: $(VBOXWEB_OUT_DIR)/soapH.h
+ g++ -x c++-header -g -g -Wall -pedantic -Wno-long-long -Wno-trigraphs -Wno-variadic-macros -pipe -O0 -fno-omit-frame-pointer \
+ -fno-strict-aliasing -fvisibility-inlines-hidden -fvisibility=hidden -DVBOX_HAVE_VISIBILITY_HIDDEN \
+ -mmacosx-version-min=10.4 -isysroot /Developer/SDKs/MacOSX10.4u.sdk -m32 \
+ -I/Volumes/ScratchHFS/bird/vbox/svn/trunk/src/VBox/Main/webservice/gsoap \
+ -I/Volumes/ScratchHFS/bird/vbox/svn/trunk/out/darwin.x86/debug/obj/src/VBox/Main \
+ -I/Volumes/ScratchHFS/bird/vbox/svn/trunk/src/VBox/Main/webservice \
+ -I/Volumes/ScratchHFS/bird/vbox/svn/trunk/include -I/Volumes/ScratchHFS/bird/vbox/svn/trunk/out/darwin.x86/debug
+ \-DVBOX -DVBOX_WITH_DEBUGGER -DVBOX_WITH_DEBUGGER_GUI -DDEBUG -DDEBUG_bird -DDEBUG_USERNAME=bird -DRT_OS_DARWIN \
+ -D__DARWIN__ -DRT_ARCH_X86 -D__X86__ -DVBOX_WITH_HYBRID_32BIT_KERNEL -DIN_RING3 -DHC_ARCH_BITS=32 -DGC_ARCH_BITS=32 \
+ -DMAC_OS_X_VERSION_MIN_REQUIRED=1040 -DMAC_OS_X_VERSION_MAX_ALLOWED=1040 \
+ $< -o $@
+ endif
+
+ # Tweak for systems with too many CPUs compared to memory.
+ ifdef VBOX_WITH_SOAP_NOT_PARALLEL
+ .NOTPARALLEL: $$(vboxsoap_2_OBJS)
+ endif
+ endif # !VBOX_ONLY_SDK
+
+
+ ifndef VBOX_ONLY_SDK
+ #
+ # vboxwebsrv - webservice server process
+ #
+ PROGRAMS += vboxwebsrv
+ vboxwebsrv_TEMPLATE = VBOXMAINCLIENTEXE
+ vboxwebsrv_DEFS += SOCKET_CLOSE_ON_EXEC
+ vboxwebsrv_DEFS.win += WIN32_LEAN_AND_MEAN
+ vboxwebsrv_INCS = \
+ $(VBOX_GSOAP_INCS) \
+ $(VBOXWEB_OUT_DIR) \
+ .
+ vboxwebsrv_CXXFLAGS.win += -bigobj
+ if "$(VBOX_VCC_TOOL_STEM)" >= "VCC140"
+ vboxwebsrv_CXXFLAGS.win += -wd4774 # string(532): warning C4774: 'sprintf_s' : format string expected in argument 3 is not a string literal
+ vboxwebsrv_CXXFLAGS.win += -wd4458 # stdsoap2.h(2644): warning C4458: declaration of 'type' hides class member
+ vboxwebsrv_CXXFLAGS.win += -wd5039 # x509v3.h(883): warning C5039: 'OPENSSL_sk_set_cmp_func': pointer or reference to potentially throwing function passed to 'extern "C"' function under -EHc. Undefined behavior may occur if this function throws an exception.
+ endif
+ ifn1of ($(KBUILD_TARGET), win)
+ vboxwebsrv_CXXFLAGS += -Wno-shadow $(VBOX_GCC_Wno-literal-suffix) $(VBOX_GCC_Wno-misleading-indentation)
+ ifn1of ($(KBUILD_TYPE), debug) # Save time+memory by using -O1 instead of -O2.
+ vboxwebsrv_CXXFLAGS += -O0
+ endif
+ endif
+ vboxwebsrv_LIBS += \
+ $(PATH_STAGE_LIB)/vboxsoap$(VBOX_SUFF_LIB) \
+ $(VBOX_GSOAP_CXX_LIBS) \
+ $(LIB_RUNTIME)
+ vboxwebsrv_LIBS.solaris += socket nsl
+ ifdef VBOX_WITH_WEBSERVICES_SSL
+ vboxwebsrv_DEFS += WITH_OPENSSL
+ vboxwebsrv_SDKS += VBOX_OPENSSL2
+ endif
+ vboxwebsrv_SOURCES = \
+ vboxweb.cpp \
+ $(VBOXWEB_OUT_DIR)/methodmaps.cpp \
+ $(VBOXWEB_OUT_DIR)/soapServer.cpp \
+ $(VBOXWEB_OUT_DIR)/vboxweb-wsdl.c
+ vboxwebsrv_SOURCES.win = \
+ VBoxWebSrv.rc
+ vboxwebsrv_CLEAN = \
+ $(VBOXWEB_OUT_DIR)/methodmaps.cpp \
+ $(VBOXWEB_OUT_DIR)/soapServer.cpp \
+ $(VBOXWEB_OUT_DIR)/vboxweb-wsdl.c
+ vboxwebsrv_ORDERDEPS = $(VBOXWEB_OUT_DIR)/gsoap_copy_all_ts
+ endif # !VBOX_ONLY_SDK
+
+ ifdef VBOX_WITH_JWS
+INSTALLS += VBoxJWs-inst-jar
+
+#
+# Java glue JAR files
+#
+VBOX_JWS_JAR = $(VBoxJWs-inst-jar_0_OUTDIR)/vboxjws.jar
+VBOX_JWSDOC_JAR = $(VBoxJWs-inst-jar_0_OUTDIR)/vboxjws-doc.jar
+VBOX_JWSSRC_JAR = $(VBoxJWs-inst-jar_0_OUTDIR)/vboxjws-src.jar
+VBOX_JWS_TARGET := $(PATH_TARGET)/vboxjws-gen
+VBOX_JWS_GEN = $(VBOX_JWS_TARGET)/jwsgen
+VBOX_JWS_GEN_RAWSRC = $(VBOX_JWS_GEN)/merged.file
+VBOX_JWS_JDEST := $(VBOX_JWS_TARGET)/jdest
+VBOX_JWSDOC_JDEST := $(VBOX_JWS_TARGET)/jdest-doc
+VBOX_GLUE_XSLT_DIR := $(PATH_ROOT)/src/VBox/Main/glue
+VBOX_JAXLIB_DIR := $(PATH_ROOT)/src/VBox/Main/webservice/jaxlibs
+
+VBoxJWs-inst-jar_INST = $(INST_SDK)bindings/webservice/java/jax-ws/
+VBoxJWs-inst-jar_MODE = a+r,u+w
+VBoxJWs-inst-jar_SOURCES = \
+ $(VBOX_JWS_JAR) \
+ $(VBOX_JWSDOC_JAR) \
+ $(VBOX_JWSSRC_JAR)
+VBoxJWs-inst-jar_CLEAN = \
+ $(VBOX_JWS_JAR) \
+ $(VBOX_JWSDOC_JAR) \
+ $(VBOX_JWSSRC_JAR) \
+ $(VBOX_JWS_GEN)/jwsglue.list \
+ $(VBOX_JWSDOC_JDEST)/package-list \
+ $(wildcard \
+ $(VBOX_JWS_GEN)/java/*/*/*.java \
+ $(VBOX_JWS_GEN)/java/*/*/*/*.java \
+ $(VBOX_JWS_JDEST)/*.class \
+ $(VBOX_JWS_JDEST)/*/*.class \
+ $(VBOX_JWS_JDEST)/*/*/*.class \
+ $(VBOX_JWS_JDEST)/*/*/*/*.class \
+ $(VBOX_JWSDOC_JDEST)/*.html \
+ $(VBOX_JWSDOC_JDEST)/*.css \
+ $(VBOX_JWSDOC_JDEST)/*/*.gif \
+ $(VBOX_JWSDOC_JDEST)/*/*/*.html \
+ $(VBOX_JWSDOC_JDEST)/*/*/*/*.html \
+ )
+VBoxJWs-inst-jar_BLDDIRS += $(VBOX_JWS_GEN)/java
+VBoxJWs-inst-jar_GENERATEDSOURCES = $(addprefix $(VBoxJWs-inst-jar_BLDDIRS)/,$(VBoxJWS_VBOX_JWSGLUEFILES))
+
+VBoxJWSGlue_KMK = $(PATH_OUT)/vboxjwsglue.kmk
+include $(VBoxJWSGlue_KMK)
+
+$(VBoxJWSGlue_KMK).ts +| $(VBoxJWSGlue_KMK): $(VBOXWEB_IDL_SRC_ORIG) $(VBOX_GLUE_XSLT_DIR)/glue-java.xsl $(VBOX_VERSION_STAMP)
+ $(call MSG_GENERATE,,$(VBoxJWSGlue_KMK))
+ $(QUIET)$(RM) -f $@
+ $(QUIET)$(MKDIR) -p $(@D)
+ $(QUIET)$(VBOX_XSLTPROC) \
+ --stringparam filelistonly VBoxJWS_VBOX_JWSGLUEFILES \
+ --stringparam G_vboxApiSuffix $(VBOX_API_SUFFIX) \
+ --stringparam G_vboxGlueStyle jaxws \
+ --stringparam G_vboxDirPrefix org/virtualbox$(VBOX_API_SUFFIX)/ \
+ -o $@ $(VBOX_GLUE_XSLT_DIR)/glue-java.xsl $<
+ $(QUIET)$(CP) --changed -fv $@ $(VBoxJWSGlue_KMK)
+
+$(VBOX_JWS_GEN_RAWSRC) \
++| $(VBoxJWs-inst-jar_GENERATEDSOURCES): \
+ $(VBOXWEB_IDL_SRC_ORIG) \
+ $(VBOX_GLUE_XSLT_DIR)/glue-java.xsl \
+ $(VBOX_FILESPLIT) \
+ $(VBOX_VERSION_STAMP)
+ $(call MSG_L1,Generating JAX-WS Java glue files from XIDL)
+ $(QUIET)$(RM) -f $(filter-out $(VBoxJWs-inst-jar_GENERATEDSOURCES),$(wildcard $(VBOX_JWS_GEN)/java/*/*/*.java))
+ $(QUIET)$(MKDIR) -p $(@D)
+ $(QUIET)$(VBOX_XSLTPROC) \
+ --stringparam filelistonly "" \
+ --stringparam G_vboxApiSuffix $(VBOX_API_SUFFIX) \
+ --stringparam G_vboxGlueStyle jaxws \
+ --stringparam G_vboxDirPrefix org/virtualbox$(VBOX_API_SUFFIX)/ \
+ -o $(VBOX_JWS_GEN_RAWSRC) $(VBOX_GLUE_XSLT_DIR)/glue-java.xsl $<
+ $(QUIET)$(MKDIR) -p $(VBOX_JWS_GEN)/java/org/virtualbox$(VBOX_API_SUFFIX)
+ $(QUIET)$(VBOX_FILESPLIT) $(VBOX_JWS_GEN_RAWSRC) $(VBOX_JWS_GEN)/java
+
+## @todo somehow also find out the authoritative list of files generated by
+# wsimport (before running it), then we could rely on proper dependencies
+# instead of creating jwsglue.list. Would allow avoiding a lot of unnecessary
+# compilation with incremental builds, when almost nothing changed in the IDL
+# file. Currently everything is recompiled if only one file is changed.
+$(VBOX_JWS_GEN)/jwsglue.list.ts +| $(VBOX_JWS_GEN)/jwsglue.list: \
+ $(VBOXWEB_IDL_SRC) \
+ $(VBOX_FILESPLIT) \
+ $(VBOXWEBSERVICE_WSDL) \
+ $(VBOXWEB_WSDL) \
+ $(VBoxJWs-inst-jar_GENERATEDSOURCES) \
+ | $(VBOX_JWS_GEN)/java/
+ $(QUIET)$(RM) -f -- $(wildcard $(VBOX_JWS_GEN)/java/*/*/*/*.java)
+ $(call MSG_GENERATE,,$(VBOX_JWS_GEN)/jwsglue.list,JAX-WS for Java 1.6 bindings using $(VBOXWEBSERVICE_WSDL))
+ $(VBOX_WSIMPORT) -Xnocompile -p $(VBOX_JAVA_PACKAGE).jaxws -d $(VBOX_JWS_GEN)/java $(VBOXWEBSERVICE_WSDL)
+ $(QUIET)echo $(VBoxJWs-inst-jar_GENERATEDSOURCES) > $@
+ $(QUIET)echo $(VBOX_JWS_GEN)/java/*/*/*/*.java >> $@
+ $(QUIET)$(CP) --changed -fv $@ $(VBOX_JWS_GEN)/jwsglue.list
+
+$$(VBOX_JWS_JAR): $(VBOX_JWS_GEN)/jwsglue.list $(VBOXWEB_WSDL) $(VBOXWEBSERVICE_WSDL) $(VBOX_JWS_GEN)/MANIFEST.MF | $$(dir $$@)
+ $(call MSG_TOOL,javac,$(notdir $@),jwsgen.list,)
+ $(QUIET)$(RM) -Rf $(VBOX_JWS_JDEST)
+ $(QUIET)$(MKDIR) -p $(VBOX_JWS_JDEST)
+ $(call MSG_L1,Compiling bridge code)
+ $(VBOX_JAVAC) $(VBOX_JAVAC_OPTS) $(VBOX_JAVA_WS_OPTS) \
+ @$(VBOX_JWS_GEN)/jwsglue.list \
+ -d $(VBOX_JWS_JDEST) -classpath $(VBOX_JWS_JDEST)$(VBOX_SEP)$(VBOX_JAVA_WS_EXTRA_JARS)
+ $(QUIET)$(SED) -e "s/vboxweb.wsdl/vboxweb$(VBOX_API_SUFFIX).wsdl/" < $(VBOXWEBSERVICE_WSDL) > $(VBOX_JWS_JDEST)/vboxwebService$(VBOX_API_SUFFIX).wsdl
+ $(QUIET)$(CP) -f $(VBOXWEB_WSDL) $(VBOX_JWS_JDEST)/vboxweb$(VBOX_API_SUFFIX).wsdl
+ $(call MSG_LINK,$(notdir $@),$@)
+ $(VBOX_JAR) cfm $@ $(VBOX_JWS_GEN)/MANIFEST.MF -C $(VBOX_JWS_JDEST) .
+
+$(VBOX_JWS_GEN)/MANIFEST.MF: $(VBOX_PATH_WEBSERVICE)/MANIFEST.MF.in
+ $(QUIET)$(RM) -f -- $@
+ $(QUIET)$(MKDIR) -p $(VBOX_JWS_GEN)
+ $(QUIET)$(SED) \
+ -e 's/@VBOX_VERSION_STRING@/$(VBOX_VERSION_STRING)/' \
+ -e 's/@VBOX_VERSION_MAJOR@/$(VBOX_VERSION_MAJOR)/' \
+ -e 's/@VBOX_VERSION_MINOR@/$(VBOX_VERSION_MINOR)/' \
+ -e 's/@VBOX_API_SUFFIX@/$(VBOX_API_SUFFIX)/' \
+ < $< > $@
+
+$$(VBOX_JWSDOC_JAR): $(VBOX_JWS_GEN)/jwsglue.list $$(VBoxJWs-inst-jar_GENERATEDSOURCES) $(VBOXWEB_WSDL) $(VBOXWEBSERVICE_WSDL) $$(VBOX_JWS_JAR) | $$(dir $$@)
+ $(call MSG_TOOL,javadoc,$(notdir $@),jwsgen.list,)
+ $(QUIET)$(RM) -Rf $(VBOX_JWSDOC_JDEST)
+ $(QUIET)$(MKDIR) -p $(VBOX_JWSDOC_JDEST)
+ $(call MSG_L1,Generating javadoc html documentation)
+ $(VBOX_JAVADOC) $(VBOX_JAVADOC_OPTS) $(VBOX_JAVA_WS_OPTS) -quiet \
+ -sourcepath $(VBOX_JWS_GEN)/java org.virtualbox$(VBOX_API_SUFFIX) \
+ -d $(VBOX_JWSDOC_JDEST) -classpath $(VBOX_SEP)$(VBOX_JAVA_WS_EXTRA_JARS)
+ $(call MSG_LINK,$(notdir $@),$@)
+ $(VBOX_JAR) cf $@ -C $(VBOX_JWSDOC_JDEST) .
+
+$$(VBOX_JWSSRC_JAR): $$(VBOX_JWS_JAR) | $$(dir $$@)
+ $(call MSG_LINK,$(notdir $@),$@)
+ $(VBOX_JAR) cf $@ -C $(VBOX_JWS_GEN)/java .
+
+## @todo compile ../glue/tests/TestVBox.java to have sanity checking
+
+ endif # VBOX_WITH_JWS
+
+ ifndef VBOX_ONLY_SDK
+ #
+ # webtest - webservice sample client in C++
+ #
+ PROGRAMS += webtest
+ webtest_TEMPLATE = VBOXWEBR3EXE
+ webtest_CXXFLAGS.win += -bigobj
+ ifn1of ($(KBUILD_TARGET), win)
+ webtest_CXXFLAGS += -Wno-shadow
+ endif
+ webtest_INCS := \
+ $(VBOX_GSOAP_INCS) \
+ $(VBOXWEB_OUT_DIR) \
+ .
+ webtest_LIBS += \
+ $(PATH_STAGE_LIB)/vboxsoap$(VBOX_SUFF_LIB) \
+ $(VBOX_GSOAP_CXX_LIBS) \
+ $(LIB_RUNTIME)
+ webtest_LIBS.solaris += nsl
+ ifdef VBOX_WITH_WEBSERVICES_SSL
+ webtest_DEFS += WITH_OPENSSL
+ webtest_SDKS += VBOX_OPENSSL2
+ endif
+ webtest_SOURCES = \
+ webtest.cpp \
+ $(VBOXWEB_OUT_DIR)/soapClient.cpp
+ webtest_CLEAN = \
+ $(VBOXWEB_OUT_DIR)/soapClient.cpp
+
+ webtest_ORDERDEPS = $(VBOXWEB_OUT_DIR)/gsoap_copy_all_ts
+ endif # !VBOX_ONLY_SDK
+
+
+ #
+ # Additional mess to cleanup (applies to both webtest and vboxwebsrv).
+ #
+ ## @todo figure out whether the SDK really needs this or not...
+ OTHER_CLEAN += \
+ $(wildcard $(VBOXWEB_OUT_DIR)/soap*.h) \
+ $(wildcard $(VBOXWEB_OUT_DIR)/soap*.cpp) \
+ $(wildcard $(VBOXWEB_OUT_DIR)/*.nsmap) \
+ $(VBOXWEB_GSOAPH_FROM_XSLT) \
+ $(VBOXWEB_GSOAPH_FROM_GSOAP) \
+ $(VBOXWEB_SOAP_CLIENT_H) \
+ $(VBOXWEB_SOAP_SERVER_H) \
+ $(VBOXWEB_OUT_DIR)/gsoap_generate_all_ts \
+ $(VBOXWEB_OUT_DIR)/gsoap_copy_all_ts \
+ $(wildcard $(PATH_TARGET_SOAPDEMOXML)/*) \
+ $(PATH_TARGET_SOAPDEMOXML)/dummy_file \
+ $(wildcard $(PATH_TARGET_SOAPDEMOHEADERS)/*) \
+ $(PATH_TARGET_SOAPDEMOHEADERS)/dummy_file \
+ $(wildcard $(PATH_TARGET_SOAPDEMONSMAPS)/*) \
+ $(PATH_TARGET_SOAPDEMONSMAPS)/dummy_file
+
+endif # VBOX_GSOAP_INSTALLED
+
+
+if defined(VBOX_ONLY_SDK) && ("$(KBUILD_TARGET)" != "win" || defined(VBOX_FORCE_SDK))
+ #
+ # Globals relevant to the SDK.
+ #
+ VBOXWEB_GLUE_PYTHON = $(VBOX_PATH_SDK)/bindings/webservice/python/lib/VirtualBox_wrappers.py
+ # The following 3 files are generated by Python ZSI 2.1_a1 (alpha version
+ # shipped by many Linux distributions). Only the first two are actually used.
+ # The 4th and 5th file is created by ZSI 2.0 (released in 2007), and gets
+ # renamed to the 1st/2nd file for simplicity reasons.
+ # ZSI 1.x used different file names. Not worth supporting any more. If you're
+ # curious, check the VirtualBox 4.3 sources.
+ VBOXWEB_WS_PYTHON = $(VBOX_PATH_SDK)/bindings/webservice/python/lib/VirtualBox_client.py
+ VBOXWEB_WS_PYTHON_TYPES = $(VBOX_PATH_SDK)/bindings/webservice/python/lib/VirtualBox_types.py
+ VBOXWEB_WS_PYTHON_SERVER = $(VBOX_PATH_SDK)/bindings/webservice/python/lib/VirtualBox_server.py
+ VBOXWEB_WS_PYTHON_ALTERNATIVE = $(VBOX_PATH_SDK)/bindings/webservice/python/lib/VirtualBox_services.py
+ VBOXWEB_WS_PERL = $(VBOX_PATH_SDK)/bindings/webservice/perl/lib/vboxService.pm
+ VBOXWEB_WS_PHP = $(VBOX_PATH_SDK)/bindings/webservice/php/lib/vboxServiceWrappers.php
+ VBOXWEB_SAMPLES_JAXWS_DIR = $(VBOX_PATH_SDK)/bindings/webservice/java/jax-ws/samples
+ VBOXWEB_JAXWSSAMPLE = $(VBOXWEB_SAMPLES_JAXWS_DIR)/clienttest.java
+ VBOXWEB_METRICSAMPLE = $(VBOXWEB_SAMPLES_JAXWS_DIR)/metrictest.java
+
+ define find_java_files
+ $(shell find $(1) -name \*.java)
+ endef
+
+ VBOXWEB_OTHERS += \
+ $(if $(VBOX_WITH_PYTHON),$(VBOXWEB_GLUE_PYTHON),) \
+ $(if $(VBOX_WITH_PYTHON),$(VBOXWEB_WS_PYTHON),) \
+ $(if $(VBOX_WITH_PYTHON),$(VBOXWEB_WS_PYTHON_TYPES),) \
+ $(if $(VBOX_WITH_PERL),$(VBOXWEB_WS_PERL),) \
+ $(if $(VBOX_WITH_PHP),$(VBOXWEB_WS_PHP),)
+
+
+ #
+ # Install sample code.
+ #
+ INSTALLS += vboxwebinst
+ vboxwebinst_INST = $(INST_SDK)bindings/webservice/
+ vboxwebinst_MODE = a+rx,u+w
+ vboxwebinst_SOURCES = \
+ $(if $(VBOX_WITH_PERL),samples/perl/clienttest.pl=>perl/samples/clienttest.pl,) \
+ $(if $(VBOX_WITH_PHP),samples/php/clienttest.php=>php/samples/clienttest.php,) \
+ $(if $(VBOX_WITH_PYTHON),samples/python/clienttest.py=>python/samples/clienttest.py,)
+
+ INSTALLS += vboxwebinst_nox
+ vboxwebinst_nox_INST = $(INST_SDK)bindings/webservice/
+ vboxwebinst_nox_MODE = a+r,u+w
+ vboxwebinst_nox_SOURCES = \
+ $(if $(VBOX_WITH_PYTHON),samples/python/Makefile=>python/samples/Makefile,) \
+ $(if $(VBOX_WITH_PYTHON),samples/python/Makefile.glue=>python/lib/Makefile,) \
+ $(if ($VBOX_WITH_JWS),$(PATH_ROOT)/COPYING.LIB=>java/jax-ws/COPYING.LIB,)
+
+ INSTALLS += vboxwebinst_wsdl
+ vboxwebinst_wsdl_INST = $(INST_SDK)bindings/webservice/
+ vboxwebinst_wsdl_MODE = a+r,u+w
+ vboxwebinst_wsdl_SOURCES = \
+ $(VBOXWEB_WSDL)=>vboxweb.wsdl \
+ $(VBOXWEBSERVICE_WSDL)=>vboxwebService.wsdl
+
+ INSTALLS += vboxwebinst_webtest
+ vboxwebinst_webtest_INST = $(INST_SDK)bindings/webservice/
+ vboxwebinst_webtest_MODE = a+r,u+w
+ vboxwebinst_webtest_SOURCES = \
+ $(VBOX_PATH_WEBSERVICE)/websrv-wsdl2gsoapH.xsl=>xsl/websrv-wsdl2gsoapH.xsl \
+ $(VBOX_PATH_WEBSERVICE)/websrv-nsmap.xsl=>xsl/websrv-nsmap.xsl \
+ $(VBOX_PATH_IDL)/typemap-shared.inc.xsl=>idl/typemap-shared.inc.xsl \
+ $(VBOX_PATH_WEBSERVICE)/split-soapC.cpp=>tools/split-soapC.cpp \
+ $(VBOX_PATH_WEBSERVICE)/webtest.cpp=>cpp/samples/webtest/webtest.cpp \
+ $(VBOX_PATH_WEBSERVICE)/Makefile.webtest=>cpp/samples/webtest/Makefile
+
+endif # VBOX_ONLY_SDK
+
+#
+# Update the OTHERS and OTHER_CLEAN lists with VBOXWEB_OTHERS and some more stuff.
+#
+# We can't just built up OTHERS and append it to OTHER_CLEAN because we're sharing
+# OTHERS with all the other VBox makefiles, thus VBOXWEB_OTHERS.
+#
+OTHERS += $(VBOXWEB_OTHERS)
+OTHER_CLEAN += \
+ $(VBOXWEB_OTHERS) \
+ $(if $(VBOX_WITH_PYTHON),$(VBOXWEB_WS_PYTHON_SERVER),) \
+ $(VBOXWEB_WSDL) \
+ $(VBOXWEBSERVICE_WSDL) \
+ $(VBOXWEB_TYPEMAP) \
+ $(VBOXWEB_IDL_SRC)
+
+# generate platform-specific XIDL file from original XIDL file
+$(VBOXWEB_IDL_SRC): $(VBOXWEB_IDL_SRC_STRIPPED) $(VBOX_PATH_WEBSERVICE)/platform-xidl.xsl | $$(dir $$@)
+ $(call MSG_GENERATE,,$@,$(VBOXWEB_IDL_SRC) using platform-xidl.xsl)
+ $(QUIET)$(RM) -f -- $@
+ $(QUIET)$(VBOX_XSLTPROC) $(VBOXWEB_XSLTPROC_VERBOSE) -o $@ $(VBOX_PATH_WEBSERVICE)/platform-xidl.xsl $<
+
+# generate WSDL from main XIDL file
+$(VBOXWEB_WSDL): $(VBOXWEB_IDL_SRC) $(VBOX_PATH_WEBSERVICE)/websrv-wsdl.xsl $(VBOX_PATH_IDL)/typemap-shared.inc.xsl $(RECOMPILE_ON_MAKEFILE_CURRENT) | $$(dir $$@)
+ $(call MSG_GENERATE,,$@,$(VBOXWEB_IDL_SRC) using websrv-wsdl.xsl)
+ $(QUIET)$(RM) -f -- $@
+ $(QUIET)$(VBOX_XSLTPROC) $(VBOXWEB_XSLTPROC_VERBOSE) -o $@ $(VBOX_PATH_WEBSERVICE)/websrv-wsdl.xsl $<
+
+$(VBOXWEBSERVICE_WSDL): $(VBOXWEB_IDL_SRC) $(VBOX_PATH_WEBSERVICE)/websrv-wsdl-service.xsl $(VBOX_PATH_IDL)/typemap-shared.inc.xsl $(RECOMPILE_ON_MAKEFILE_CURRENT) | $$(dir $$@)
+ $(call MSG_GENERATE,,$@,$(VBOXWEB_IDL_SRC) using websrv-wsdl-service.xsl)
+ $(QUIET)$(RM) -f -- $@
+ $(QUIET)$(VBOX_XSLTPROC) $(VBOXWEB_XSLTPROC_VERBOSE) -o $@ $(VBOX_PATH_WEBSERVICE)/websrv-wsdl-service.xsl $<
+
+ifdef VBOX_ONLY_SDK
+
+$(VBOXWEB_GLUE_PYTHON): $(VBOXWEB_IDL_SRC) $(VBOXWEB_WSDL) $(VBOXWEBSERVICE_WSDL) $(VBOX_PATH_WEBSERVICE)/websrv-python.xsl
+ $(call MSG_GENERATE,,$@,$(VBOXWEB_IDL_SRC) using websrv-python.xsl)
+ $(QUIET)$(RM) -f -- $@
+ $(QUIET)$(MKDIR) -p $(@D)
+ $(QUIET)$(VBOX_XSLTPROC) $(VBOXWEB_XSLTPROC_VERBOSE) -o $@ $(VBOX_PATH_WEBSERVICE)/websrv-python.xsl $<
+
+$(VBOXWEB_WS_PYTHON) \
++ $(VBOXWEB_WS_PYTHON_TYPES): $(VBOXWEB_WSDL) $(VBOXWEBSERVICE_WSDL)
+ $(call MSG_GENERATE,,$@, WS Python bindings)
+ $(QUIET)$(RM) -f -- $@
+ $(QUIET)$(MKDIR) -p $(@D)
+# Change directory to the "source", as otherwise ZSI 2.0 has trouble finding
+# the 2nd WSDL file included in the main one. ZSI 2.1 is smarter, but some
+# versions floating around (especially on Linux) lack the --file option.
+if "$(KBUILD_HOST)" != "win"
+ $(QUIET)$(REDIRECT) -C $(dir $(VBOXWEBSERVICE_WSDL)) -- $(SHELL) -c "$(VBOX_WSDL2PY) -b --output-dir $(@D) $(VBOXWEBSERVICE_WSDL) || $(VBOX_WSDL2PY) -b --file $(VBOXWEBSERVICE_WSDL) --output-dir $(@D)"
+else
+ $(QUIET)$(REDIRECT) -C $(dir $(VBOXWEBSERVICE_WSDL)) -- $(VBOX_WSDL2PY) -b --file $(subst /,\\\\,$(VBOXWEBSERVICE_WSDL)) --output-dir $(@D)
+endif
+# Hack: some wsdl2py versions generate VirtualBox_client.py, some generate
+# VirtualBox_services.py. Standardize on the former.
+ -$(QUIET)$(MV) -f $(VBOXWEB_WS_PYTHON_ALTERNATIVE) $(VBOXWEB_WS_PYTHON)
+# We do not ever need the VirtualBox_server.py file. Delete it immediately
+# so that it will not get packaged in the SDK.
+ $(QUIET)$(RM) -f -- $(VBOXWEB_WS_PYTHON_SERVER)
+ $(QUIET)$(APPEND) $@ ''
+
+$(VBOXWEB_WS_PERL): $(VBOXWEB_WSDL) $(VBOXWEBSERVICE_WSDL)
+ $(call MSG_GENERATE,,$@, WS Perl bindings)
+ $(QUIET)$(MKDIR) -p $(@D)
+ $(QUIET)$(REDIRECT) -C $(@D) -- $(VBOX_STUBMAKER) file://$(VBOXWEBSERVICE_WSDL)
+# Ugly, ugly, ugly, make me right once
+ $(QUIET)$(SED) -e "s+http://www.virtualbox.org/Service+http://www.virtualbox.org/+" --output $(VBOXWEB_WS_PERL).tmp $(VBOXWEB_WS_PERL)
+ $(QUIET)$(MV) $(VBOXWEB_WS_PERL).tmp $(VBOXWEB_WS_PERL)
+ $(QUIET)$(APPEND) $@ ''
+
+$(VBOXWEB_WS_PHP): $(VBOXWEB_IDL_SRC) $(VBOX_PATH_WEBSERVICE)/websrv-php.xsl
+ $(call MSG_GENERATE,,$@,$(VBOXWEB_IDL_SRC) using websrv-php.xsl)
+ $(QUIET)$(RM) -f -- $@
+ $(QUIET)$(MKDIR) -p $(@D)
+ $(QUIET)$(VBOX_XSLTPROC) $(VBOXWEB_XSLTPROC_VERBOSE) -o $@ $(VBOX_PATH_WEBSERVICE)/websrv-php.xsl $<
+
+endif # VBOX_ONLY_SDK
+
+# generate typemap.dat (used by wsdl2h) from main XIDL file
+$(VBOXWEB_TYPEMAP): $(VBOXWEB_IDL_SRC) $(VBOX_PATH_WEBSERVICE)/websrv-typemap.xsl $(VBOX_PATH_IDL)/typemap-shared.inc.xsl $(RECOMPILE_ON_MAKEFILE_CURRENT) | $$(dir $$@)
+ $(call MSG_GENERATE,,$@,$(VBOXWEB_IDL_SRC) using websrv-typemap.xsl)
+ $(QUIET)$(RM) -f -- $@
+ $(QUIET)$(VBOX_XSLTPROC) $(VBOXWEB_XSLTPROC_VERBOSE) -o $@ $(VBOX_PATH_WEBSERVICE)/websrv-typemap.xsl $<
+
+# generate gsoap pseudo-C header file from that WSDL; once via XSLT...
+$(VBOXWEB_GSOAPH_FROM_XSLT): $(VBOXWEB_WSDL) $(VBOX_PATH_WEBSERVICE)/websrv-wsdl2gsoapH.xsl $(VBOX_PATH_IDL)/typemap-shared.inc.xsl $(RECOMPILE_ON_MAKEFILE_CURRENT) | $$(dir $$@)
+ $(call MSG_GENERATE,,$@,$(VBOXWEB_WSDL) using websrv-wsdl2gsoapH.xsl)
+ $(QUIET)$(RM) -f -- $@
+ $(QUIET)$(VBOX_XSLTPROC) $(VBOXWEB_XSLTPROC_VERBOSE) -o $@ $(VBOX_PATH_WEBSERVICE)/websrv-wsdl2gsoapH.xsl $<
+
+VBOX_NSMAP = $(VBOXWEB_OUT_DIR)/vboxwebsrv.nsmap
+$(VBOX_NSMAP): $(VBOXWEB_IDL_SRC) $(VBOX_PATH_WEBSERVICE)/websrv-nsmap.xsl $(VBOX_PATH_IDL)/typemap-shared.inc.xsl $(RECOMPILE_ON_MAKEFILE_CURRENT) | $$(dir $$@)
+ $(call MSG_GENERATE,,$@,$(VBOXWEB_IDL_SRC) using websrv-nsmap.xsl)
+ $(QUIET)$(RM) -f -- $@
+ $(QUIET)$(VBOX_XSLTPROC) $(VBOXWEB_XSLTPROC_VERBOSE) -o $@ $(VBOX_PATH_WEBSERVICE)/websrv-nsmap.xsl $<
+
+ifdef VBOX_GSOAP_INSTALLED
+# ... and once with the gSOAP tool (just for comparison, we don't use it for licensing reasons)
+$(VBOXWEB_GSOAPH_FROM_GSOAP): $(VBOXWEB_WSDL) $(VBOXWEB_TYPEMAP) | $$(dir $$@)
+ $(call MSG_GENERATE,,$@,)
+ $(QUIET)$(RM) -f -- $@
+ $(VBOX_WSDL2H) $(VBOXWEB_WSDL_VERBOSE) -t$(VBOXWEB_TYPEMAP) -nvbox -o $@ $<
+
+# this sets the gsoap header that we use for further compilation; if stuff works, then the
+# one we generate via XSLT produces the same end result as the one from the gSOAP tool;
+# with this variable we can swap for testing, but shipped code must use VBOXWEB_GSOAPH_FROM_XSLT
+GSOAPH_RELEVANT = $(VBOXWEB_GSOAPH_FROM_XSLT)
+
+# wsdl2h -v: verbose
+# wsdl2h -e: don't qualify enum names
+# wsdl2h -n<prefix>: namespace header prefix
+
+## @todo change this to state explicitly what will be generated?
+
+#
+# Generate server and client code from gsoap pseudo-C header file.
+#
+# Options for soapcpp2:
+# -2: generate SOAP 1.2 calls
+# -L: don't generate soapClientLib/soapServerLib
+# -w: don't generate WSDL and schema files
+# -x: don't generate sample XML files (VBOXWEB_SOAPCPP2_SKIP_FILES).
+#
+$(VBOXWEB_OUT_DIR)/gsoap_generate_all_ts \
++ $(VBOXWEB_OUT_DIR)/soapH.h \
+$(if-expr !defined(VBOX_WITHOUT_NOINLINE_SOAPH),\
++ $(VBOXWEB_OUT_DIR)/soapH-noinline-1.cpp \
++ $(VBOXWEB_OUT_DIR)/soapH-noinline-2.cpp \
++ $(VBOXWEB_OUT_DIR)/soapH-noinline-3.cpp \
++ $(VBOXWEB_OUT_DIR)/soapH-noinline-4.cpp \
++ $(VBOXWEB_OUT_DIR)/soapH-noinline-5.cpp \
++ $(VBOXWEB_OUT_DIR)/soapH-noinline-6.cpp \
++ $(VBOXWEB_OUT_DIR)/soapH-noinline-7.cpp \
++ $(VBOXWEB_OUT_DIR)/soapH-noinline-8.cpp \
++ $(VBOXWEB_OUT_DIR)/soapH-noinline-9.cpp \
++ $(VBOXWEB_OUT_DIR)/soapH-noinline-10.cpp \
++ $(VBOXWEB_OUT_DIR)/soapH-noinline-11.cpp \
++ $(VBOXWEB_OUT_DIR)/soapH-noinline-12.cpp,) \
++ $(VBOXWEB_SOAP_CLIENT_H) \
++ $(VBOXWEB_OUT_DIR)/soapC.cpp \
++ $(VBOXWEB_OUT_DIR)/soapClient.cpp \
++ $(VBOXWEB_OUT_DIR)/soapServer.cpp \
+: $(VBOXWEB_GSOAPH_FROM_GSOAP) $(VBOXWEB_GSOAPH_FROM_XSLT) $(VBOX_NSMAP) \
+ $(VBOX_PATH_WEBSERVICE)/stdsoap2.sed \
+ $(VBOX_PATH_WEBSERVICE)/soap-header-to-inline-source-file.sed \
+ $(VBOX_PATH_WEBSERVICE)/soap-header-strip-inline.sed \
+ $$(split-soapC_1_TARGET) \
+ $(RECOMPILE_ON_MAKEFILE_CURRENT) | $$(dir $$@)
+ $(call MSG_GENERATE,,lots of files,$(GSOAPH_RELEVANT))
+ $(RM) -f $@
+ $(REDIRECT) -C $(VBOXWEB_OUT_DIR) -- $(VBOX_SOAPCPP2) $(VBOXWEB_SOAPCPP2_SKIP_FILES) -L -2 -w -I$(VBOX_PATH_GSOAP_IMPORT) $(GSOAPH_RELEVANT)
+ifeq ($(KBUILD_TARGET),win) # MSC -Wall workaround.
+ $(CP) -f "$(VBOXWEB_SOAP_CLIENT_H)" "$(VBOXWEB_SOAP_CLIENT_H).tmp"
+ $(SED) -f $(VBOX_PATH_WEBSERVICE)/stdsoap2.sed --output "$(VBOXWEB_SOAP_CLIENT_H)" "$(VBOXWEB_SOAP_CLIENT_H).tmp"
+ $(RM) -f "$(VBOXWEB_SOAP_CLIENT_H).tmp"
+endif
+ifndef VBOX_WITHOUT_NOINLINE_SOAPH
+ $(MV) -f -- "$(VBOXWEB_OUT_DIR)/soapH.h" "$(VBOXWEB_OUT_DIR)/soapH.h.tmp"
+ $(SED) -f $(VBOX_PATH_WEBSERVICE)/soap-header-to-inline-source-file.sed \
+ --output "$(VBOXWEB_OUT_DIR)/soapH-noinline.cpp" \
+ "$(VBOXWEB_OUT_DIR)/soapH.h.tmp"
+ $(split-soapC_1_TARGET) $(VBOXWEB_OUT_DIR)/soapH-noinline.cpp $(VBOXWEB_OUT_DIR)/soapH-noinline- 12
+ $(SED) -f $(VBOX_PATH_WEBSERVICE)/soap-header-strip-inline.sed \
+ --output "$(VBOXWEB_OUT_DIR)/soapH.h" \
+ "$(VBOXWEB_OUT_DIR)/soapH.h.tmp"
+ $(RM) -f -- "$(VBOXWEB_OUT_DIR)/soapH.h.tmp" "$(VBOXWEB_OUT_DIR)/soapH-noinline.cpp"
+endif
+ $(APPEND) $@ done
+
+# Copy the generated headers and stuff. This was split into a separate rule
+# way back because we thought we could use $(wildcard ) and avoid the shell,
+# however we cannot as it is subject to caching. Let the shell do the globbing.
+# GSOAP versions 2.8 and later do not generate the unneeded soapvbox*.h files
+# any more. Ignoring the exit code is the simple solution, accepting the error.
+$(VBOXWEB_OUT_DIR)/gsoap_copy_all_ts: $(VBOXWEB_OUT_DIR)/gsoap_generate_all_ts | $$(dir $$@)
+ $(RM) -f $@
+ $(MKDIR) -p $(PATH_TARGET_SOAPDEMOXML) $(PATH_TARGET_SOAPDEMOHEADERS) $(PATH_TARGET_SOAPDEMONSMAPS)
+ifdef VBOX_GSOAP_VERBOSE
+ $(MV_EXT) -f -- $(VBOXWEB_OUT_DIR)/*.req.xml $(VBOXWEB_OUT_DIR)/*.res.xml $(PATH_TARGET_SOAPDEMOXML)/
+endif
+ -$(MV_EXT) -f -- $(VBOXWEB_OUT_DIR)/soapvbox*.h $(PATH_TARGET_SOAPDEMOHEADERS)/
+ $(MV_EXT) -f -- $(VBOXWEB_OUT_DIR)/vboxBinding.nsmap $(PATH_TARGET_SOAPDEMONSMAPS)/
+ $(APPEND) $@ done
+
+$(PATH_TARGET_SOAPDEMONSMAPS) \
+$(PATH_TARGET_SOAPDEMOHEADERS)/soapvboxBindingProxy.h \
+$(PATH_TARGET_SOAPDEMOHEADERS)/soapvboxBindingObject.h: $(VBOXWEB_OUT_DIR)/gsoap_copy_all_ts
+
+hack: $(VBOXWEB_OUT_DIR)/gsoap_copy_all_ts
+
+ifndef VBOX_WITHOUT_SPLIT_SOAPC
+#
+# Split up the soapC.cpp monster into manageable bits that can be
+# built in parallel and without exhausting all available memory.
+#
+$(VBOXWEB_OUT_DIR)/soapC-1.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-2.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-3.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-4.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-5.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-6.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-7.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-8.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-9.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-10.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-11.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-12.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-13.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-14.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-15.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-16.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-17.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-18.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-19.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-20.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-21.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-22.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-23.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-24.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-25.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-26.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-27.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-28.cpp \
++ $(VBOXWEB_OUT_DIR)/soapC-29.cpp \
+: $(VBOXWEB_OUT_DIR)/soapC.cpp $$(split-soapC_1_TARGET) | $$(dir $$@)
+ $(RM) -f -- $(wildcard $(VBOXWEB_OUT_DIR)/soapC-?.cpp $(VBOXWEB_OUT_DIR)/soapC-??.cpp)
+ $(split-soapC_1_TARGET) $(VBOXWEB_OUT_DIR)/soapC.cpp $(VBOXWEB_OUT_DIR)/soapC- 29
+endif # !VBOX_WITHOUT_SPLIT_SOAPC
+
+endif # VBOX_GSOAP_INSTALLED
+
+
+
+# generate method maps in server: map wsdl operations to com/xpcom method calls
+$(VBOXWEB_OUT_DIR)/methodmaps.cpp: $(VBOXWEB_IDL_SRC) $(VBOX_PATH_WEBSERVICE)/websrv-cpp.xsl $(VBOX_PATH_IDL)/typemap-shared.inc.xsl $(RECOMPILE_ON_MAKEFILE_CURRENT) | $$(dir $$@)
+ $(call MSG_GENERATE,,$@,$(VBOXWEB_IDL_SRC) using websrv-cpp.xsl)
+ $(QUIET)$(VBOX_XSLTPROC) -o $@ $(VBOX_PATH_WEBSERVICE)/websrv-cpp.xsl $<
+
+# generate C file which contains vboxweb.wsdl
+$$(VBOXWEB_OUT_DIR)/vboxweb-wsdl.c: $(VBOXWEB_WSDL) $(VBOX_BIN2C)
+ $(call MSG_TOOL,bin2c,vboxweb-wsdl,$<,$@)
+ $(QUIET)$(VBOX_BIN2C) -ascii VBoxWebWSDL $< $@
+
+
+ifdef VBOX_ONLY_SDK
+
+$(VBOXWEB_JAXWSSAMPLE): $(VBOX_PATH_WEBSERVICE)/samples/java/jax-ws/clienttest.java
+ $(QUIET)$(RM) -f -- $@
+ $(QUIET)$(MKDIR) -p $(VBOXWEB_SAMPLES_JAXWS_DIR)
+ $(QUIET)$(SED) -e 's/{VBOX_API_SUFFIX}/$(VBOX_API_SUFFIX)/' < $< > $@
+
+$(VBOXWEB_METRICSAMPLE): $(VBOX_PATH_WEBSERVICE)/samples/java/jax-ws/metrictest.java
+ $(QUIET)$(RM) -f -- $@
+ $(QUIET)$(MKDIR) -p $(VBOXWEB_SAMPLES_JAXWS_DIR)
+ $(QUIET)$(SED) -e 's/{VBOX_API_SUFFIX}/$(VBOX_API_SUFFIX)/' < $< > $@
+
+endif # VBOX_ONLY_SDK
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Main/webservice/Makefile.webtest b/src/VBox/Main/webservice/Makefile.webtest
new file mode 100644
index 00000000..d506b14d
--- /dev/null
+++ b/src/VBox/Main/webservice/Makefile.webtest
@@ -0,0 +1,94 @@
+# $Id: Makefile.webtest $
+## @files
+# ???
+#
+
+#
+# Copyright (C) 2016-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+XSLTPROC = xsltproc
+ifeq ($(PATH_GSOAP),)
+ PATH_GSOAP = $(lastword $(sort $(wildcard $(KBUILD_DEVTOOLS)/common/gsoap/*)))
+endif
+PATH_GSOAP_BIN := $(strip $(PATH_GSOAP))
+ifeq ($(PATH_GSOAP_BIN),)
+ PATH_GSOAP_BIN = /usr/bin
+endif
+SOAPCPP2 = $(PATH_GSOAP_BIN)/soapcpp2
+
+ifneq ($(MAKECMDGOALS),clean)
+ ifeq ($(wildcard $(PATH_GSOAP)/stdsoap2.cpp),)
+ $(error Fix PATH_GSOAP!)
+ endif
+endif
+
+WEBSRVWSDL2GSOAPH = ../../../xsl/websrv-wsdl2gsoapH.xsl
+WEBSRVNSMAPXSL = ../../../xsl/websrv-nsmap.xsl
+VBOXWEBIDLSRC = ../../../../VirtualBox.xidl
+VBOXWEBWSDL = ../../../vboxweb.wsdl
+SPLITSOAPCCPP = ../../../tools/split-soapC.cpp
+SOAPCCPP = $(foreach num,1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20,soapC-$(num).cpp)
+SOAPCO = $(patsubst %.cpp,%.o,$(SOAPCCPP))
+
+webtest: webtest.o soapClient.o $(SOAPCO) stdsoap2.o
+ $(CXX) -O2 -o $@ $^ -lssl -lcrypto
+
+webtest.o: webtest.cpp soapC.cpp vboxwebsrv.nsmap
+ $(CXX) -O2 -DWITH_OPENSSL -c -o $@ $< -I$(PATH_GSOAP)
+
+soapClient.o: soapC.cpp
+ $(CXX) -O2 -c -o $@ soapClient.cpp -I$(PATH_GSOAP)
+
+$(SOAPCO): soapC-%.o: soapC-%.cpp
+ $(CXX) -O0 -c -o $@ $< -I$(PATH_GSOAP)
+
+soapC.cpp: gsoapH_from_xslt.h
+ $(SOAPCPP2) -x -L -2 -w -I$(PATH_GSOAP)/import $^
+
+stdsoap2.o: $(PATH_GSOAP)/stdsoap2.cpp
+ $(CXX) -O2 -DWITH_OPENSSL -c -o $@ $<
+
+gsoapH_from_xslt.h:
+ $(XSLTPROC) -o $@ $(WEBSRVWSDL2GSOAPH) $(VBOXWEBWSDL)
+
+vboxwebsrv.nsmap:
+ $(XSLTPROC) -o $@ $(WEBSRVNSMAPXSL) $(VBOXWEBIDLSRC)
+
+$(subst soapC,%,$(SOAPCCPP)): split-soapC %.cpp
+ ./split-soapC soapC.cpp . 20
+
+split-soapC: $(SPLITSOAPCCPP)
+ $(CXX) -O2 -o $@ $<
+
+.PHONY: clean
+clean:
+ rm -f gsoapH_from_xslt.h
+ rm -f soapStub.h soapServer.cpp soapC.cpp soapClient.cpp
+ rm -f soapH.h soapvboxBindingObject.h soapvboxBindingProxy.h
+ rm -f vboxBinding.nsmap
+ rm -f vboxwebsrv.nsmap
+ rm -f split-soapC
+ rm -f $(SOAPCCPP) $(SOAPCO)
+ rm -f soapClient.o stdsoap2.o
+ rm -f webtest.o webtest
+ rm -f soapC-split-done
+
diff --git a/src/VBox/Main/webservice/VBoxWebSrv.rc b/src/VBox/Main/webservice/VBoxWebSrv.rc
new file mode 100644
index 00000000..764a52c7
--- /dev/null
+++ b/src/VBox/Main/webservice/VBoxWebSrv.rc
@@ -0,0 +1,61 @@
+/* $Id: VBoxWebSrv.rc $ */
+/** @file
+ * VBoxWebSrv - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2015-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox Web Service\0"
+ VALUE "InternalName", "VBoxWebSrv\0"
+ VALUE "OriginalFilename", "VBoxWebSrv.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Main/webservice/platform-xidl.xsl b/src/VBox/Main/webservice/platform-xidl.xsl
new file mode 100644
index 00000000..42915887
--- /dev/null
+++ b/src/VBox/Main/webservice/platform-xidl.xsl
@@ -0,0 +1,96 @@
+<?xml version="1.0"?>
+
+<!--
+ platform-xidl.xsl:
+ XSLT stylesheet that generates a platform-specific
+ VirtualBox.xidl from ../idl/VirtualBox.xidl, which
+ is identical to the original except that all <if...>
+ sections are resolved (for easier processing).
+-->
+<!--
+ Copyright (C) 2006-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+<xsl:output
+ method="xml"
+ version="1.0"
+ encoding="utf-8"
+ indent="yes"/>
+
+<xsl:strip-space
+ elements="*" />
+
+<!--
+ template for "idl" match; this emits the header of the target file
+ and recurses into the libraries with interfaces (which are matched below)
+ -->
+<xsl:template match="/idl">
+ <xsl:comment>
+ DO NOT EDIT! This is a generated file.
+ Generated from: src/VBox/Main/idl/VirtualBox.xidl (VirtualBox's interface definitions in XML)
+ Generator: src/VBox/Main/webservice/platform-xidl.xsl
+</xsl:comment>
+
+ <idl>
+ <xsl:apply-templates />
+ </idl>
+
+</xsl:template>
+
+<!--
+ template for "if" match: ignore all ifs except those for wsdl
+ -->
+<xsl:template match="if">
+ <xsl:if test="@target='wsdl'">
+ <xsl:apply-templates/>
+ </xsl:if>
+</xsl:template>
+
+<!--
+ ignore everything we don't need
+ -->
+<xsl:template match="cpp|class|enumerator">
+</xsl:template>
+
+<!--
+ and keep the rest intact (including all attributes)
+
+ NOTE: this drops class and everything in it, which I left unchanged
+ since the other xslt scripts blow up badly.
+ -->
+<xsl:template match="library|module|enum|const|interface|attribute|collection|method|param|result">
+ <xsl:copy><xsl:copy-of select="@*"/><xsl:apply-templates/></xsl:copy>
+</xsl:template>
+
+<!--
+ keep those completely unchanged, including child nodes (including all
+ attributes)
+ -->
+<xsl:template match="descGroup|desc|note">
+ <xsl:copy-of select="."/>
+</xsl:template>
+
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/webservice/samples/java/axis/clienttest.java b/src/VBox/Main/webservice/samples/java/axis/clienttest.java
new file mode 100644
index 00000000..31043d3e
--- /dev/null
+++ b/src/VBox/Main/webservice/samples/java/axis/clienttest.java
@@ -0,0 +1,310 @@
+/* $Id: clienttest.java $ */
+/*!file
+ * Sample client for the VirtualBox web service, written in Java (raw web service variant).
+ *
+ * Run the VirtualBox web service server first; see the VirtualBox
+ * SDK reference for details.
+ *
+ * The following license applies to this file only:
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import org.virtualbox.www.Service.VboxService;
+import org.virtualbox.www.Service.VboxServiceLocator;
+import org.virtualbox.www.VboxPortType;
+
+public class clienttest
+{
+ private VboxService _service;
+ private VboxPortType _port;
+ private String _oVbox;
+
+ public clienttest()
+ {
+ try
+ {
+ // instantiate the webservice in instance data; the classes
+ // VboxServiceLocator and VboxPortType have been created
+ // by the WSDL2Java helper that you should have run prior
+ // to compiling this example, as described in the User Manual.
+ _service = new VboxServiceLocator();
+ _port = _service.getvboxServicePort();
+
+ // From now on, we can call any method in the webservice by
+ // prefixing it with "port."
+
+ // First step is always to log on to the webservice. This
+ // returns a managed object reference to the webservice's
+ // global instance of IVirtualBox, which in turn contains
+ // the most important methods provided by the Main API.
+ _oVbox = _port.IWebsessionManager_logon("", "");
+
+ // Call IVirtualBox::getVersion and print out the result
+ String version = _port.IVirtualBox_getVersion(_oVbox);
+ System.out.println("Initialized connection to VirtualBox version " + version);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void showVMs()
+ {
+ try
+ {
+ // Call IVirtualBox::getMachines, which yields an array
+ // of managed object references to all machines which have
+ // been registered:
+ String[] aMachines = _port.IVirtualBox_getMachines2(_oVbox);
+ // Walk through this array and, for each machine, call
+ // IMachine::getName (accessor method to the "name" attribute)
+ for (int i = 0; i < aMachines.length; i++)
+ {
+ String oMachine = aMachines[i];
+ String machinename = _port.IMachine_getName(oMachine);
+ System.out.println("Machine " + i + ": " + oMachine + " - " + machinename);
+
+ // release managed object reference
+ _port.IManagedObjectRef_release(oMachine);
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void listHostInfo()
+ {
+ try
+ {
+ String oHost = _port.IVirtualBox_getHost(_oVbox);
+
+ org.apache.axis.types.UnsignedInt uProcCount = _port.IHost_getProcessorCount(oHost);
+ System.out.println("Processor count: " + uProcCount);
+
+ String oCollector = _port.IVirtualBox_getPerformanceCollector(_oVbox);
+
+ String aobj[] = {oHost};
+ String astrMetrics[] = {"*"};
+ String aMetrics[] = {};
+ aMetrics = _port.IPerformanceCollector_getMetrics(oCollector,
+ astrMetrics,
+ aobj);
+
+// String astrMetricNames[] = { "*" };
+// String aObjects[];
+// String aRetNames[];
+// int aRetIndices[];
+// int aRetLengths[];
+// int aRetData[];
+// int rc = _port.ICollector_queryMetricsData(oCollector,
+// aObjects,
+// aRetNames,
+// aRetObjects,
+// aRetIndices,
+// aRetLengths,
+// aRetData);
+//
+/*
+ Bstr metricNames[] = { L"*" };
+ com::SafeArray<BSTR> metrics (1);
+ metricNames[0].cloneTo (&metrics [0]);
+ com::SafeArray<BSTR> retNames;
+ com::SafeIfaceArray<IUnknown> retObjects;
+ com::SafeArray<ULONG> retIndices;
+ com::SafeArray<ULONG> retLengths;
+ com::SafeArray<LONG> retData;
+ CHECK_ERROR (collector, QueryMetricsData(ComSafeArrayAsInParam(metrics),
+ ComSafeArrayInArg(objects),
+ ComSafeArrayAsOutParam(retNames),
+ ComSafeArrayAsOutParam(retObjects),
+ ComSafeArrayAsOutParam(retIndices),
+ ComSafeArrayAsOutParam(retLengths),
+ ComSafeArrayAsOutParam(retData)) );
+*/
+
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void startVM(String strVM)
+ {
+ String oSession = "";
+ Boolean fSessionOpen = false;
+
+ try
+ {
+ // this is pretty much what VBoxManage does to start a VM
+ String oMachine = "";
+ Boolean fOK = false;
+
+ oSession = _port.IWebsessionManager_getSessionObject(_oVbox);
+
+ // first assume we were given a UUID
+ try
+ {
+ oMachine = _port.IVirtualBox_getMachine(_oVbox, strVM);
+ fOK = true;
+ }
+ catch (Exception e)
+ {
+ }
+
+ if (!fOK)
+ {
+ try
+ {
+ // or try by name
+ oMachine = _port.IVirtualBox_findMachine(_oVbox, strVM);
+ fOK = true;
+ }
+ catch (Exception e)
+ {
+ }
+ }
+
+ if (!fOK)
+ {
+ System.out.println("Error: can't find VM \"" + strVM + "\"");
+ }
+ else
+ {
+ String uuid = _port.IMachine_getId(oMachine);
+ String sessionType = "gui";
+ String env = "DISPLAY=:0.0";
+ String oProgress = _port.IVirtualBox_openRemoteSession(_oVbox, oSession, uuid, sessionType, env);
+ fSessionOpen = true;
+
+ System.out.println("Session for VM " + uuid + " is opening...");
+ _port.IProgress_waitForCompletion(oProgress, 10000);
+
+ int rc = _port.IProgress_getResultCode(oProgress).intValue();
+ if (rc != 0)
+ {
+ System.out.println("Session failed!");
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ if (fSessionOpen)
+ {
+ try
+ {
+ _port.ISession_close(oSession);
+ }
+ catch (Exception e)
+ {
+ }
+ }
+ }
+
+ public void cleanup()
+ {
+ try
+ {
+ if (_oVbox.length() > 0)
+ {
+ // log off
+ _port.IWebsessionManager_logoff(_oVbox);
+ _oVbox = null;
+ System.out.println("Logged off.");
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public static void printArgs()
+ {
+ System.out.println( "Usage: java clienttest <mode> ..." +
+ "\nwith <mode> being:" +
+ "\n show vms list installed virtual machines" +
+ "\n list hostinfo list host info" +
+ "\n startvm <vmname|uuid> start the given virtual machine");
+ }
+
+ public static void main(String[] args)
+ {
+ if (args.length < 1)
+ {
+ System.out.println("Error: Must specify at least one argument.");
+ printArgs();
+ }
+ else
+ {
+ clienttest c = new clienttest();
+ if (args[0].equals("show"))
+ {
+ if (args.length == 2)
+ {
+ if (args[1].equals("vms"))
+ c.showVMs();
+ else
+ System.out.println("Error: Unknown argument to \"show\": \"" + args[1] + "\".");
+ }
+ else
+ System.out.println("Error: Missing argument to \"show\" command");
+ }
+ else if (args[0].equals("list"))
+ {
+ if (args.length == 2)
+ {
+ if (args[1].equals("hostinfo"))
+ c.listHostInfo();
+ else
+ System.out.println("Error: Unknown argument to \"show\": \"" + args[1] + "\".");
+ }
+ else
+ System.out.println("Error: Missing argument to \"show\" command");
+ }
+ else if (args[0].equals("startvm"))
+ {
+ if (args.length == 2)
+ {
+ c.startVM(args[1]);
+ }
+ else
+ System.out.println("Error: Missing argument to \"startvm\" command");
+ }
+ else
+ System.out.println("Error: Unknown command: \"" + args[0] + "\".");
+
+ c.cleanup();
+ }
+ }
+}
diff --git a/src/VBox/Main/webservice/samples/java/jax-ws/Makefile b/src/VBox/Main/webservice/samples/java/jax-ws/Makefile
new file mode 100644
index 00000000..7bd53a61
--- /dev/null
+++ b/src/VBox/Main/webservice/samples/java/jax-ws/Makefile
@@ -0,0 +1,83 @@
+# $Id: Makefile $
+## @file
+# Makefile for java samples.
+#
+
+
+#
+# Copyright (C) 2008-2022 Oracle and/or its affiliates.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+
+JAVA16=java
+JAVA15=/usr/lib/jvm/java-1.5.0-sun/bin/java
+JAVAC = javac
+JAVAC15 = javac -target 1.5
+JAVAC16 = javac -target 1.6
+MKDIR = mkdir
+RM = rm
+
+DEST16 = ./gen16
+DEST15 = ./gen15
+
+VBOXWS15 = ../lib/vboxws_java15.jar
+VBOXWS16 = ../lib/vboxws_java16.jar
+
+JAXWS=./jaxws-ri
+JAXWSLIBS=$(JAXWS)/lib/jaxws-api.jar:$(JAXWS)/lib/jaxb-api.jar:$(JAXWS)/lib/jsr181-api.jar:$(JAXWS)/lib/jaxws-rt.jar
+
+all: run16
+
+metric: metric16
+
+$(DEST16)/clienttest.class: clienttest.java
+ $(MKDIR) -p $(DEST16)
+ $(JAVAC16) -d $(DEST16) -cp $(VBOXWS16) $<
+
+$(DEST15)/clienttest.class: clienttest.java
+ $(MKDIR) -p $(DEST15)
+ $(JAVAC15) -d $(DEST15) -cp $(VBOXWS15):$(JAXWSLIBS) $<
+
+run16: $(DEST16)/clienttest.class
+ $(JAVA16) -cp $(VBOXWS16):$(DEST16) clienttest show vms
+
+run15: $(DEST15)/clienttest.class
+ $(JAVA15) -cp $(VBOXWS15):$(JAXWSLIBS):$(DEST15) clienttest show vms
+
+$(DEST16)/metrictest.class: metrictest.java
+ $(MKDIR) -p $(DEST16)
+ $(JAVAC16) -d $(DEST16) -cp $(VBOXWS16) $<
+
+$(DEST15)/metrictest.class: metrictest.java
+ $(MKDIR) -p $(DEST15)
+ $(JAVAC15) -d $(DEST15) -cp $(VBOXWS15):$(JAXWSLIBS) $<
+
+metric16: $(DEST16)/metrictest.class
+ -$(JAVA16) -cp $(VBOXWS16):$(DEST16) metrictest
+
+metric15: $(DEST15)/metrictest.class
+ -$(JAVA15) -cp $(VBOXWS15):$(JAXWSLIBS):$(DEST15) metrictest
+
+clean:
+ $(RM) -rf $(DEST15) $(DEST16)
+
diff --git a/src/VBox/Main/webservice/samples/java/jax-ws/Makefile.glue b/src/VBox/Main/webservice/samples/java/jax-ws/Makefile.glue
new file mode 100644
index 00000000..295ea77c
--- /dev/null
+++ b/src/VBox/Main/webservice/samples/java/jax-ws/Makefile.glue
@@ -0,0 +1,72 @@
+# $Id: Makefile.glue $
+## @file
+# Makefile for java samples.
+#
+
+#
+# Copyright (C) 2008-2022 Oracle and/or its affiliates.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+
+JAXWS=./jaxws-ri
+JAXWSLIBS=$(JAXWS)/lib/jaxws-api.jar:$(JAXWS)/lib/jaxb-api.jar:$(JAXWS)/lib/jsr181-api.jar:$(JAXWS)/lib/jaxws-rt.jar
+
+
+JAVA16=java
+JAVA15=/usr/lib/jvm/java-1.5.0-sun/bin/java
+JAVAC15 = javac -target 1.5
+JAVAC16 = javac -target 1.6
+WSIMPORT15 = $(JAVA15) -cp $(JAXWS)/lib/jaxws-tools.jar com.sun.tools.ws.WsImport
+WSIMPORT16 = wsimport
+JAR = jar
+CP = cp
+MKDIR = mkdir
+RM = rm
+
+DEST16 = ./gen16
+DEST15 = ./gen15
+
+VBOXWS15 = ../lib/vboxws_java15.jar
+VBOXWS16 = ../lib/vboxws_java16.jar
+
+all: $(VBOXWS15) $(VBOXWS16)
+
+$(VBOXWS15): ../../../vboxwebService.wsdl ../../../vboxweb.wsdl *.java
+ $(RM) -rf $(DEST15)
+ $(MKDIR) -p $(DEST15)
+ $(WSIMPORT15) -d $(DEST15) $<
+ $(JAVAC15) -cp $(DEST15) *.java -d $(DEST15)
+ $(CP) ../../../vboxwebService.wsdl ../../../vboxweb.wsdl $(DEST15)
+ $(JAR) cvf $(VBOXWS15) -C $(DEST15) . > /dev/null
+
+$(VBOXWS16): ../../../vboxwebService.wsdl ../../../vboxweb.wsdl *.java
+ $(RM) -rf $(DEST16)
+ $(MKDIR) -p $(DEST16)
+ $(WSIMPORT16) -d $(DEST16) $<
+ $(JAVAC16) -cp $(DEST16) *.java -d $(DEST16)
+ $(CP) ../../../vboxwebService.wsdl ../../../vboxweb.wsdl $(DEST16)
+ $(JAR) cvf $(VBOXWS16) -C $(DEST16) . > /dev/null
+
+clean:
+ $(RM) -rf $(DEST)
+
diff --git a/src/VBox/Main/webservice/samples/java/jax-ws/clienttest.java b/src/VBox/Main/webservice/samples/java/jax-ws/clienttest.java
new file mode 100644
index 00000000..02d67ab0
--- /dev/null
+++ b/src/VBox/Main/webservice/samples/java/jax-ws/clienttest.java
@@ -0,0 +1,339 @@
+/* $Id: clienttest.java $ */
+/*!file
+ * Sample client for the VirtualBox web service, written in Java (object-oriented bindings).
+ *
+ * Run the VirtualBox web service server first; see the VirtualBox
+ * SDK reference for details.
+ *
+ * The following license applies to this file only:
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/* Somewhat ugly way to support versioning */
+import com.sun.xml.ws.commons.virtualbox{VBOX_API_SUFFIX}.*;
+
+import java.util.*;
+import javax.xml.ws.Holder;
+
+public class clienttest
+{
+ IWebsessionManager mgr;
+ IVirtualBox vbox;
+
+ public clienttest()
+ {
+ mgr = new IWebsessionManager("http://localhost:18083/");
+ vbox = mgr.logon("test", "test");
+ System.out.println("Initialized connection to VirtualBox version " + vbox.getVersion());
+ }
+
+ public void disconnect()
+ {
+ mgr.disconnect(vbox);
+ }
+
+ class Desktop
+ {
+ String name;
+ String uuid;
+
+ Desktop(int n)
+ {
+ name = "Mach"+n;
+ uuid = UUID.randomUUID().toString();
+ }
+ String getName()
+ {
+ return name;
+ }
+ String getId()
+ {
+ return uuid;
+ }
+ }
+
+ public void test()
+ {
+ for (int i=0; i<100; i++)
+ {
+ String baseFolder =
+ vbox.getSystemProperties().getDefaultMachineFolder();
+ Desktop desktop = new Desktop(i);
+ IMachine machine = vbox.createMachine(baseFolder,
+ "linux",
+ desktop.getName(),
+ desktop.getId(),
+ true);
+ machine.saveSettings();
+ mgr.cleanupUnused();
+ }
+ }
+
+ public void test2()
+ {
+ ISession session = mgr.getSessionObject(vbox);
+ String id = "bc8b6219-2775-42c4-f1b2-b48b3c177294";
+ vbox.openSession(session, id);
+ IMachine mach = session.getMachine();
+ IBIOSSettings bios = mach.getBIOSSettings();
+ bios.setIOAPICEnabled(true);
+ mach.saveSettings();
+ session.close();
+ }
+
+
+ public void test3()
+ {
+
+ IWebsessionManager mgr1 = new IWebsessionManager("http://localhost:18082/");
+ IWebsessionManager mgr2 = new IWebsessionManager("http://localhost:18083/");
+ IVirtualBox vbox1 = mgr1.logon("test", "test");
+ IVirtualBox vbox2 = mgr2.logon("test", "test");
+
+
+ System.out.println("connection 1 to VirtualBox version " + vbox1.getVersion());
+ System.out.println("connection 2 to VirtualBox version " + vbox2.getVersion());
+ mgr1.disconnect(vbox1);
+ mgr2.disconnect(vbox2);
+
+ mgr1 = new IWebsessionManager("http://localhost:18082/");
+ mgr2 = new IWebsessionManager("http://localhost:18083/");
+ vbox1 = mgr1.logon("test", "test");
+ vbox2 = mgr2.logon("test", "test");
+
+ System.out.println("second connection 1 to VirtualBox version " + vbox1.getVersion());
+ System.out.println("second connection 2 to VirtualBox version " + vbox2.getVersion());
+
+ mgr1.disconnect(vbox1);
+ mgr2.disconnect(vbox2);
+ }
+
+ public void showVMs()
+ {
+ try
+ {
+ int i = 0;
+ for (IMachine m : vbox.getMachines())
+ {
+ System.out.println("Machine " + (i++) + ": " + " [" + m.getId() + "]" + " - " + m.getName());
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void listHostInfo()
+ {
+ try
+ {
+ IHost host = vbox.getHost();
+ long uProcCount = host.getProcessorCount();
+ System.out.println("Processor count: " + uProcCount);
+
+ for (long i=0; i<uProcCount; i++)
+ {
+ System.out.println("Processor #" + i + " speed: " + host.getProcessorSpeed(i) + "MHz");
+ }
+
+ IPerformanceCollector oCollector = vbox.getPerformanceCollector();
+
+ List<IPerformanceMetric> aMetrics =
+ oCollector.getMetrics(Arrays.asList(new String[]{"*"}),
+ Arrays.asList(new IUnknown[]{host}));
+
+ for (IPerformanceMetric m : aMetrics)
+ {
+ System.out.println("known metric = "+m.getMetricName());
+ }
+
+ Holder<List<String>> names = new Holder<List<String>> ();
+ Holder<List<IUnknown>> objects = new Holder<List<IUnknown>>() ;
+ Holder<List<String>> units = new Holder<List<String>>();
+ Holder<List<Long>> scales = new Holder<List<Long>>();
+ Holder<List<Long>> sequenceNumbers = new Holder<List<Long>>();
+ Holder<List<Long>> indices = new Holder<List<Long>>();
+ Holder<List<Long>> lengths = new Holder<List<Long>>();
+
+ List<Integer> vals =
+ oCollector.queryMetricsData(Arrays.asList(new String[]{"*"}),
+ Arrays.asList(new IUnknown[]{host}),
+ names, objects, units, scales,
+ sequenceNumbers, indices, lengths);
+
+ for (int i=0; i < names.value.size(); i++)
+ System.out.println("name: "+names.value.get(i));
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void startVM(String strVM)
+ {
+ ISession oSession = null;
+ IMachine oMachine = null;
+
+ try
+ {
+ oSession = mgr.getSessionObject(vbox);
+
+ // first assume we were given a UUID
+ try
+ {
+ oMachine = vbox.getMachine(strVM);
+ }
+ catch (Exception e)
+ {
+ try
+ {
+ oMachine = vbox.findMachine(strVM);
+ }
+ catch (Exception e1)
+ {
+ }
+ }
+
+ if (oMachine == null)
+ {
+ System.out.println("Error: can't find VM \"" + strVM + "\"");
+ }
+ else
+ {
+ String uuid = oMachine.getId();
+ String sessionType = "gui";
+ ArrayList<String> env = new ArrayList<String>();
+ env.add("DISPLAY=:0.0");
+ IProgress oProgress =
+ oMachine.launchVMProcess(oSession,
+ sessionType,
+ env);
+ System.out.println("Session for VM " + uuid + " is opening...");
+ oProgress.waitForCompletion(10000);
+
+ long rc = oProgress.getResultCode();
+ if (rc != 0)
+ System.out.println("Session failed!");
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ if (oSession != null)
+ {
+ oSession.close();
+ }
+ }
+ }
+
+ public void cleanup()
+ {
+ try
+ {
+ if (vbox != null)
+ {
+ disconnect();
+ vbox = null;
+ System.out.println("Logged off.");
+ }
+ mgr.cleanupUnused();
+ mgr = null;
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public static void printArgs()
+ {
+ System.out.println( "Usage: java clienttest <mode> ..." +
+ "\nwith <mode> being:" +
+ "\n show vms list installed virtual machines" +
+ "\n list hostinfo list host info" +
+ "\n startvm <vmname|uuid> start the given virtual machine");
+ }
+
+ public static void main(String[] args)
+ {
+ if (args.length < 1)
+ {
+ System.out.println("Error: Must specify at least one argument.");
+ printArgs();
+ }
+ else
+ {
+ clienttest c = new clienttest();
+ if (args[0].equals("show"))
+ {
+ if (args.length == 2)
+ {
+ if (args[1].equals("vms"))
+ c.showVMs();
+ else
+ System.out.println("Error: Unknown argument to \"show\": \"" + args[1] + "\".");
+ }
+ else
+ System.out.println("Error: Missing argument to \"show\" command");
+ }
+ else if (args[0].equals("list"))
+ {
+ if (args.length == 2)
+ {
+ if (args[1].equals("hostinfo"))
+ c.listHostInfo();
+ else
+ System.out.println("Error: Unknown argument to \"show\": \"" + args[1] + "\".");
+ }
+ else
+ System.out.println("Error: Missing argument to \"list\" command");
+ }
+ else if (args[0].equals("startvm"))
+ {
+ if (args.length == 2)
+ {
+ c.startVM(args[1]);
+ }
+ else
+ System.out.println("Error: Missing argument to \"startvm\" command");
+ }
+ else if (args[0].equals("test"))
+ {
+ c.test3();
+ }
+ else
+ System.out.println("Error: Unknown command: \"" + args[0] + "\".");
+
+ c.cleanup();
+ }
+ }
+}
diff --git a/src/VBox/Main/webservice/samples/java/jax-ws/metrictest.java b/src/VBox/Main/webservice/samples/java/jax-ws/metrictest.java
new file mode 100644
index 00000000..05cfabba
--- /dev/null
+++ b/src/VBox/Main/webservice/samples/java/jax-ws/metrictest.java
@@ -0,0 +1,231 @@
+/* $Id: metrictest.java $ */
+/*!file
+ * Sample of performance API usage, written in Java.
+ *
+ * Don't forget to run VBOX webserver
+ * with 'vboxwebsrv -t 1000' command, to calm down watchdog thread.
+ *
+ * The following license applies to this file only:
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import com.sun.xml.ws.commons.virtualbox{VBOX_API_SUFFIX}.*;
+
+import java.util.*;
+import javax.xml.ws.Holder;
+
+class PerformanceData
+{
+ public String name;
+ public IUnknown object;
+ public String unit;
+ public Long scale;
+ public Long sequenceNumber;
+ public List<Long> samples;
+
+ public String getFormattedSamples()
+ {
+ String out = "[";
+ String separator = "";
+
+ if (scale != 1)
+ {
+ for (Long sample : samples)
+ {
+ out += separator + (sample.doubleValue() / scale) + " " + unit;
+ separator = ", ";
+ }
+ }
+ else
+ {
+ for (Long sample : samples)
+ {
+ out += separator + sample.toString() + " " + unit;
+ separator = ", ";
+ }
+ }
+ out += "]";
+ return out;
+ }
+}
+
+class PerformanceCollector
+{
+ private IVirtualBox _vbox;
+ private IPerformanceCollector _collector;
+
+ public PerformanceCollector(IVirtualBox vbox)
+ {
+ _vbox = vbox;
+ _collector = vbox.getPerformanceCollector();
+ }
+
+ public void cleanup()
+ {
+ _collector.releaseRemote();
+ }
+
+ public List<IPerformanceMetric> setup(List<String> metricNames, List<IUnknown> objects, Long period, Long samples)
+ {
+ return _collector.setupMetrics(metricNames, objects, period, samples);
+ }
+
+ public List<IPerformanceMetric> enable(List<String> metricNames, List<IUnknown> objects)
+ {
+ return _collector.enableMetrics(metricNames, objects);
+ }
+
+ public List<IPerformanceMetric> disable(List<String> metricNames, List<IUnknown> objects)
+ {
+ return _collector.disableMetrics(metricNames, objects);
+ }
+
+ public List<PerformanceData> query(List<String> filterMetrics, List<IUnknown> filterObjects)
+ {
+ Holder<List<String>> names = new Holder<List<String>>();
+ Holder<List<IUnknown>> objects = new Holder<List<IUnknown>>();
+ Holder<List<String>> units = new Holder<List<String>>();
+ Holder<List<Long>> scales = new Holder<List<Long>>();
+ Holder<List<Long>> sequenceNumbers = new Holder<List<Long>>();
+ Holder<List<Long>> indices = new Holder<List<Long>>();
+ Holder<List<Long>> lengths = new Holder<List<Long>>();
+ List<Integer> values =
+ _collector.queryMetricsData(filterMetrics, filterObjects,
+ names, objects, units, scales, sequenceNumbers, indices, lengths);
+ List<PerformanceData> data = new ArrayList<PerformanceData>(names.value.size());
+ for (int i = 0; i < names.value.size(); i++)
+ {
+ PerformanceData singleMetricData = new PerformanceData();
+ singleMetricData.name = names.value.get(i);
+ singleMetricData.object = objects.value.get(i);
+ singleMetricData.unit = units.value.get(i);
+ singleMetricData.scale = scales.value.get(i);
+ singleMetricData.sequenceNumber = sequenceNumbers.value.get(i);
+ List<Long> samples = new ArrayList<Long>(lengths.value.get(i).intValue());
+ for (int j = 0; j < lengths.value.get(i); j++)
+ {
+ samples.add(values.get(indices.value.get(i).intValue() + j).longValue());
+ }
+ singleMetricData.samples = samples;
+ data.add(singleMetricData);
+ }
+
+ return data;
+ }
+}
+
+public class metrictest implements Runnable
+{
+ IVirtualBox vbox;
+ IWebsessionManager mgr;
+ PerformanceCollector perf;
+
+ public metrictest()
+ {
+ mgr = new IWebsessionManager("http://localhost:18083/");
+ vbox = mgr.logon("test", "test");
+ System.out.println("Initialized connection to VirtualBox version " + vbox.getVersion());
+ perf = new PerformanceCollector(vbox);
+ }
+
+ private String getObjectName(IUnknown object)
+ {
+ try
+ {
+ String machineName = object.getRemoteWSPort().iMachineGetName(object.getRef());
+ return machineName;
+ } catch (Exception e)
+ {
+ }
+ return new String("host");
+ }
+
+ public void setup()
+ {
+ perf.setup(Arrays.asList(new String[]{"*"}),
+ new ArrayList<IUnknown>(),
+ new Long(1), new Long(5));
+ }
+
+ public void collect()
+ {
+ try
+ {
+ List<IUnknown> allObjects = new ArrayList<IUnknown>();
+ List<PerformanceData> metricData = perf.query(Arrays.asList(new String[]{"*"}),
+ allObjects);
+ for (PerformanceData md : metricData)
+ {
+ System.out.println("(" + getObjectName(md.object) + ") " +
+ md.name + " " + md.getFormattedSamples());
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void run()
+ {
+ // Clean up
+ try
+ {
+ if (perf != null)
+ {
+ perf.cleanup();
+ perf = null;
+ }
+ if (vbox != null)
+ {
+ mgr.logoff(vbox);
+ vbox = null;
+ mgr = null;
+ System.out.println("Logged off.");
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public static void main(String[] args) throws InterruptedException
+ {
+ metrictest c = new metrictest();
+ // Add a shutdown handle to clean up
+ Runtime.getRuntime().addShutdownHook(new Thread(c));
+ // Start metric collection
+ c.setup();
+ // Obtain and print out stats continuously until ctrl-C is pressed
+ while (true)
+ {
+ Thread.sleep(1000); // Sleep for a second
+ c.collect();
+ }
+ }
+}
diff --git a/src/VBox/Main/webservice/samples/perl/clienttest.pl b/src/VBox/Main/webservice/samples/perl/clienttest.pl
new file mode 100755
index 00000000..d7ef31a8
--- /dev/null
+++ b/src/VBox/Main/webservice/samples/perl/clienttest.pl
@@ -0,0 +1,232 @@
+#!/usr/bin/perl
+# $Id: clienttest.pl $
+## @file
+# This little perl program attempts to connect to a running VirtualBox
+# webservice and calls various methods on it. Please refer to the SDK
+# programming reference (SDKRef.pdf) for how to use this sample.
+#
+# Note! The following license applies to this file only
+#
+
+#
+# Copyright (C) 2008-2022 Oracle and/or its affiliates.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+
+use strict;
+use SOAP::Lite;
+use vboxService; # generated by stubmaker, see SDKRef.pdf
+use Data::Dumper;
+
+my $cmd = 'clienttest';
+my $optMode;
+my $vmname;
+my $disk;
+
+while (my $this = shift(@ARGV))
+{
+ if (($this =~ /^-h/) || ($this =~ /^--help/))
+ {
+ print "$cmd: test the VirtualBox web service.\n".
+ "Usage:\n".
+ " $cmd <mode>\n".
+ "with <mode> being one of 'version', 'list', 'start'; default is 'list'.\n".
+ " $cmd version: print version of VirtualBox web service.\n".
+ " $cmd list: list installed virtual machines.\n".
+ " $cmd startvm <vm>: start the virtual machine named <vm>.\n".
+ " $cmd acpipowerbutton <vm>: shutdown of the irtual machine named <vm>.\n";
+ " $cmd openhd <disk>: open disk image <disk>.\n";
+ exit 0;
+ }
+ elsif ( ($this eq 'version')
+ || ($this eq 'list')
+ )
+ {
+ $optMode = $this;
+ }
+ elsif ( ($this eq 'startvm')
+ || ($this eq 'acpipowerbutton')
+ )
+ {
+ $optMode = $this;
+
+ if (!($vmname = shift(@ARGV)))
+ {
+ die "[$cmd] Missing parameter: You must specify the name of the VM to start.\nStopped";
+ }
+ }
+ elsif ($this eq 'openhd')
+ {
+ $optMode = $this;
+
+ if (!($disk = shift(@ARGV)))
+ {
+ die "[$cmd] Missing parameter: You must specify the name of the disk to open.\nStopped";
+ }
+ }
+ else
+ {
+ die "[$cmd] Unknown option \"$this\"; stopped";
+ }
+}
+
+$optMode = "list"
+ if (!$optMode);
+
+# SOAP::Lite hacking to make it serialize the enum types we use correctly.
+# In the long run, this needs to be done either by stubmaker.pl or something
+# else, because the WSDL clearly says they're restricted strings. Quite silly
+# that the default behavior is to ignore the parameter and just let the server
+# use the default value for the type.
+
+sub SOAP::Serializer::as_LockType
+{
+ my ($self, $value, $name, $type, $attr) = @_;
+ die "String value expected instead of @{[ref $value]} reference\n"
+ if ref $value;
+ return [
+ $name,
+ {'xsi:type' => 'vbox:LockType', %$attr},
+ SOAP::Utils::encode_data($value)
+ ];
+}
+
+sub SOAP::Serializer::as_DeviceType
+{
+ my ($self, $value, $name, $type, $attr) = @_;
+ die "String value expected instead of @{[ref $value]} reference\n"
+ if ref $value;
+ return [
+ $name,
+ {'xsi:type' => 'vbox:DeviceType', %$attr},
+ SOAP::Utils::encode_data($value)
+ ];
+}
+
+sub SOAP::Serializer::as_AccessMode
+{
+ my ($self, $value, $name, $type, $attr) = @_;
+ die "String value expected instead of @{[ref $value]} reference\n"
+ if ref $value;
+ return [
+ $name,
+ {'xsi:type' => 'vbox:AccessMode', %$attr},
+ SOAP::Utils::encode_data($value)
+ ];
+}
+
+## @todo needs much more error handling, e.g. openhd never complains
+
+my $vbox = vboxService->IWebsessionManager_logon("test", "test");
+
+if (!$vbox)
+{
+ die "[$cmd] Logon to session manager with user \"test\" and password \"test\" failed.\nStopped";
+}
+
+if ($optMode eq "version")
+{
+ my $v = vboxService->IVirtualBox_getVersion($vbox);
+ print "[$cmd] Version number of running VirtualBox web service: $v\n";
+}
+elsif ($optMode eq "list")
+{
+ print "[$cmd] Listing machines:\n";
+ my @result = vboxService->IVirtualBox_getMachines($vbox);
+ foreach my $idMachine (@result)
+ {
+ my $if = vboxService->IManagedObjectRef_getInterfaceName($idMachine);
+ my $name = vboxService->IMachine_getName($idMachine);
+
+ print "machine $if $idMachine: $name\n";
+ }
+}
+elsif ($optMode eq "startvm")
+{
+ my $machine = vboxService->IVirtualBox_findMachine($vbox, $vmname);
+
+ die "[$cmd] Cannot find VM \"$vmname\"; stopped"
+ if (!$machine);
+
+ my $session = vboxService->IWebsessionManager_getSessionObject($vbox);
+ die "[$cmd] Cannot get session object; stopped"
+ if (!$session);
+
+ my $uuid = vboxService->IMachine_getId($machine);
+ die "[$cmd] Cannot get uuid for machine; stopped"
+ if (!$uuid);
+
+ print "[$cmd] UUID: $uuid\n";
+
+ my @env = ();
+ my $progress = vboxService->IMachine_launchVMProcess($machine,
+ $session,
+ "headless",
+ @env);
+ die "[$cmd] Cannot launch VM; stopped"
+ if (!$progress);
+
+ print("[$cmd] Waiting for the VM to start...\n");
+ vboxService->IProgress_waitForCompletion($progress, -1);
+
+ my $fCompleted;
+ $fCompleted = vboxService->IProgress_getCompleted($progress);
+ print("[$cmd] Completed: $fCompleted\n");
+
+ my $resultCode;
+ $resultCode = vboxService->IProgress_getResultCode($progress);
+
+ print("[$cmd] Result: $resultCode\n");
+
+ vboxService->ISession_unlockMachine($session);
+
+ vboxService->IWebsessionManager_logoff($vbox);
+}
+elsif ($optMode eq "acpipowerbutton")
+{
+ my $machine = vboxService->IVirtualBox_findMachine($vbox, $vmname);
+
+ die "[$cmd] Cannot find VM \"$vmname\"; stopped"
+ if (!$machine);
+
+ my $session = vboxService->IWebsessionManager_getSessionObject($vbox);
+ die "[$cmd] Cannot get session object; stopped"
+ if (!$session);
+
+ vboxService->IMachine_lockMachine($machine, $session, 'Shared');
+
+ my $console = vboxService->ISession_getConsole($session);
+
+ vboxService->IConsole_powerButton($console);
+
+ vboxService->ISession_unlockMachine($session);
+
+ vboxService->IWebsessionManager_logoff($vbox);
+}
+elsif ($optMode eq "openhd")
+{
+ my $medium = vboxService->IVirtualBox_openMedium($vbox, $disk,
+ 'HardDisk',
+ 'ReadWrite',
+ 0);
+}
diff --git a/src/VBox/Main/webservice/samples/php/clienttest.php b/src/VBox/Main/webservice/samples/php/clienttest.php
new file mode 100644
index 00000000..39552d2a
--- /dev/null
+++ b/src/VBox/Main/webservice/samples/php/clienttest.php
@@ -0,0 +1,108 @@
+<?php
+/* $Id: clienttest.php $ */
+/*!file
+ * Sample client for the VirtualBox webservice, written in PHP.
+ *
+ * Run the VirtualBox web service server first; see the VirtualBox
+ * SDK reference for details.
+ *
+ * The following license applies to this file only:
+ */
+
+/*
+ * Contributed by James Lucas (mjlucas at eng.uts.edu.au).
+ *
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+require_once('./vboxServiceWrappers.php');
+
+//Connect to webservice
+$connection = new SoapClient("vboxwebService.wsdl", array('location' => "http://localhost:18083/"));
+
+//Logon to webservice
+$websessionManager = new IWebsessionManager($connection);
+// Dummy username and password (change to appropriate values or set authentication method to null)
+$virtualbox = $websessionManager->logon("username","password");
+
+//Get a list of registered machines
+$machines = $virtualbox->machines;
+
+//Take a screenshot of the first vm we find that is running
+foreach ($machines as $machine)
+{
+ if ( 'Running' == $machine->state )
+ {
+ $session = $websessionManager->getSessionObject($virtualbox->handle);
+ $uuid = $machine->id;
+ $machine->lockMachine($session->handle, "Shared");
+ try
+ {
+ $console = $session->console;
+ $display = $console->display;
+ list($screenWidth, $screenHeight, $screenBpp, $screenX, $screenY, $screenStatus) = $display->getScreenResolution(0 /* First screen */);
+
+ $imageraw = $display->takeScreenShotToArray(0 /* First screen */, $screenWidth, $screenHeight, "RGBA");
+ echo "Screenshot size: " . sizeof($imageraw) . "\n";
+
+ $filename = 'screenshot.png';
+ echo "Saving screenshot of " . $machine->name . " (${screenWidth}x${screenHeight}, ${screenBpp}BPP) to $filename\n";
+ $image = imagecreatetruecolor($screenWidth, $screenHeight);
+
+ for ($height = 0; $height < $screenHeight; $height++)
+ {
+ for ($width = 0; $width < $screenWidth; $width++)
+ {
+ $start = ($height*$screenWidth + $width)*4;
+ $red = $imageraw[$start];
+ $green = $imageraw[($start+1)];
+ $blue = $imageraw[($start+2)];
+ //$alpha = $image[$start+3];
+
+ $colour = imagecolorallocate($image, $red, $green, $blue);
+
+ imagesetpixel($image, $width, $height, $colour);
+ }
+ }
+
+ imagepng($image, $filename);
+ }
+ catch (Exception $ex)
+ {
+ echo $ex->getMessage();
+ }
+
+ $session->unlockMachine();
+
+ $machine->releaseRemote();
+ $session->releaseRemote();
+
+ break;
+ }
+}
+
+$websessionManager->logoff($virtualbox->handle);
+
+?>
+
diff --git a/src/VBox/Main/webservice/samples/python/Makefile b/src/VBox/Main/webservice/samples/python/Makefile
new file mode 100644
index 00000000..2430639b
--- /dev/null
+++ b/src/VBox/Main/webservice/samples/python/Makefile
@@ -0,0 +1,39 @@
+# $Id: Makefile $
+## @file
+# Makefile for java samples.
+#
+
+#
+# Copyright (C) 2008-2022 Oracle and/or its affiliates.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+
+all: run
+
+run:
+ @echo !!!! Don\'t forget to start webserver with \"vboxwebsrv -t 10000\" !!!
+ PYTHONPATH=../lib python ../../../glue/python/sample/vboxshell.py -w
+
+server:
+ nohup vboxwebsrv -t 10000 &
+
diff --git a/src/VBox/Main/webservice/samples/python/Makefile.glue b/src/VBox/Main/webservice/samples/python/Makefile.glue
new file mode 100644
index 00000000..768bc242
--- /dev/null
+++ b/src/VBox/Main/webservice/samples/python/Makefile.glue
@@ -0,0 +1,35 @@
+# $Id: Makefile.glue $
+## @file
+# Makefile for java samples.
+#
+
+#
+# Copyright (C) 2008-2022 Oracle and/or its affiliates.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+
+all: wrappers
+
+wrappers: ../../vboxwebService.wsdl ../../vboxweb.wsdl
+ wsdl2py -b --file $<
+
diff --git a/src/VBox/Main/webservice/samples/python/clienttest.py b/src/VBox/Main/webservice/samples/python/clienttest.py
new file mode 100755
index 00000000..2c4f3097
--- /dev/null
+++ b/src/VBox/Main/webservice/samples/python/clienttest.py
@@ -0,0 +1,132 @@
+#!/usr/bin/python
+
+__copyright__ = \
+"""
+Copyright (C) 2012-2022 Oracle and/or its affiliates.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+"""
+
+
+# Things needed to be set up before running this sample:
+# - Install Python and verify it works (2.7.2 will do, 3.x is untested yet)
+# - On Windows: Install the PyWin32 extensions for your Python version
+# (see http://sourceforge.net/projects/pywin32/)
+# - If not already done, set the environment variable "VBOX_INSTALL_PATH"
+# to point to your VirtualBox installation directory (which in turn must have
+# the "sdk" subfolder")
+# - Install the VirtualBox Python bindings by doing a
+# "[python] vboxapisetup.py install"
+# - Run this sample with "[python] clienttest.py"
+
+import os,sys
+import traceback
+
+#
+# Converts an enumeration to a printable string.
+#
+def enumToString(constants, enum, elem):
+ all = constants.all_values(enum)
+ for e in all.keys():
+ if str(elem) == str(all[e]):
+ return e
+ return "<unknown>"
+
+def main(argv):
+
+ from vboxapi import VirtualBoxManager
+ # This is a VirtualBox COM/XPCOM API client, no data needed.
+ mgr = VirtualBoxManager(None, None)
+
+ # Get the global VirtualBox object
+ vbox = mgr.getVirtualBox()
+
+ print "Running VirtualBox version %s" %(vbox.version)
+
+ # Get all constants through the Python manager code
+ vboxConstants = mgr.constants
+
+ # Enumerate all defined machines
+ for mach in mgr.getArray(vbox, 'machines'):
+
+ try:
+ # Be prepared for failures - the VM can be inaccessible
+ vmname = '<inaccessible>'
+ try:
+ vmname = mach.name
+ except Exception, e:
+ None
+ vmid = '';
+ try:
+ vmid = mach.id
+ except Exception, e:
+ None
+
+ # Print some basic VM information even if there were errors
+ print "Machine name: %s [%s]" %(vmname,vmid)
+ if vmname == '<inaccessible>' or vmid == '':
+ continue
+
+ # Print some basic VM information
+ print " State: %s" %(enumToString(vboxConstants, "MachineState", mach.state))
+ print " Session state: %s" %(enumToString(vboxConstants, "SessionState", mach.sessionState))
+
+ # Do some stuff which requires a running VM
+ if mach.state == vboxConstants.MachineState_Running:
+
+ # Get the session object
+ session = mgr.getSessionObject()
+
+ # Lock the current machine (shared mode, since we won't modify the machine)
+ mach.lockMachine(session, vboxConstants.LockType_Shared)
+
+ # Acquire the VM's console and guest object
+ console = session.console
+ guest = console.guest
+
+ # Retrieve the current Guest Additions runlevel and print
+ # the installed Guest Additions version
+ addRunLevel = guest.additionsRunLevel
+ print " Additions State: %s" %(enumToString(vboxConstants, "AdditionsRunLevelType", addRunLevel))
+ if addRunLevel != vboxConstants.AdditionsRunLevelType_None:
+ print " Additions Ver: %s" %(guest.additionsVersion)
+
+ # Get the VM's display object
+ display = console.display
+
+ # Get the VM's current display resolution + bit depth + position
+ screenNum = 0 # From first screen
+ (screenW, screenH, screenBPP, screenX, screenY, _) = display.getScreenResolution(screenNum)
+ print " Display (%d): %dx%d, %d BPP at %d,%d" %(screenNum, screenW, screenH, screenBPP, screenX, screenY)
+
+ # We're done -- don't forget to unlock the machine!
+ session.unlockMachine()
+
+ except Exception, e:
+ print "Errror [%s]: %s" %(mach.name, str(e))
+ traceback.print_exc()
+
+ # Call destructor and delete manager
+ del mgr
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/src/VBox/Main/webservice/soap-header-strip-inline.sed b/src/VBox/Main/webservice/soap-header-strip-inline.sed
new file mode 100644
index 00000000..be8a02bd
--- /dev/null
+++ b/src/VBox/Main/webservice/soap-header-strip-inline.sed
@@ -0,0 +1,35 @@
+# $Id: soap-header-strip-inline.sed $
+## @file
+# WebService - SED script for stripping inlined bodies from soapH.h.
+#
+
+#
+# Copyright (C) 2020-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+/^inline /,/^}/ {
+ /^inline/,/^{/ {
+ s/^inline/\/\*noinline\*\//
+ s/^{.*/;/
+ p
+ }
+ d
+}
diff --git a/src/VBox/Main/webservice/soap-header-to-inline-source-file.sed b/src/VBox/Main/webservice/soap-header-to-inline-source-file.sed
new file mode 100644
index 00000000..3e540848
--- /dev/null
+++ b/src/VBox/Main/webservice/soap-header-to-inline-source-file.sed
@@ -0,0 +1,38 @@
+# $Id: soap-header-to-inline-source-file.sed $
+## @file
+# WebService - SED script for extracting inline functions from soapH.h
+# for putting them in a C++ source file.
+#
+
+#
+# Copyright (C) 2020-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+/^inline /,/^}/ {
+ /^inline /,/^{/ {
+ s/^inline/\n\/\*noinline\*\//
+ s/ *= *\([^,)]*\)\([),]\)/ \/\*=\1\*\/\2/
+ }
+ p
+}
+1 c #include "soapH.h"
+d
+
diff --git a/src/VBox/Main/webservice/split-soapC.cpp b/src/VBox/Main/webservice/split-soapC.cpp
new file mode 100644
index 00000000..1a8b3493
--- /dev/null
+++ b/src/VBox/Main/webservice/split-soapC.cpp
@@ -0,0 +1,236 @@
+/* $Id: split-soapC.cpp $ */
+/** @file
+ * Splits soapC.cpp and soapH-noinline.cpp into more manageable portions.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/types.h>
+#include <iprt/path.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+
+
+static char *readfileIntoBuffer(const char *pszFile, size_t *pcbFile)
+{
+ FILE *pFileIn = fopen(pszFile, "rb");
+ if (pFileIn)
+ {
+ int rc2 = fseek(pFileIn, 0, SEEK_END);
+ long cbFileIn = ftell(pFileIn);
+ int rc3 = fseek(pFileIn, 0, SEEK_SET);
+ if (rc3 != -1 && rc2 != -1 && cbFileIn >= 0)
+ {
+ char *pBuffer = (char *)malloc(cbFileIn + 1);
+ if (pBuffer)
+ {
+ size_t cbRead = fread(pBuffer, 1, cbFileIn, pFileIn);
+ if (cbRead == (size_t)cbFileIn)
+ {
+ pBuffer[cbFileIn] = '\0';
+ fclose(pFileIn);
+ *pcbFile = (size_t)cbFileIn;
+ return pBuffer;
+ }
+
+ fprintf(stderr, "split-soapC: Failed to read %ld bytes from input file.\n", cbFileIn);
+ free(pBuffer);
+ }
+ else
+ fprintf(stderr, "split-soapC: Failed to allocate %ld bytes.\n", cbFileIn);
+ }
+ else
+ fprintf(stderr, "split-soapC: Seek failure.\n");
+ fclose(pFileIn);
+ }
+ else
+ fprintf(stderr, "split-soapC: Cannot open file \"%s\" for reading.\n", pszFile);
+ return NULL;
+}
+
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Check argument count.
+ */
+ if (argc != 4)
+ {
+ fprintf(stderr, "split-soapC: Must be started with exactly four arguments,\n"
+ "1) the input file, 2) the output filename prefix and\n"
+ "3) the number chunks to create.");
+ return RTEXITCODE_SYNTAX;
+ }
+
+ /*
+ * Number of chunks (argv[3]).
+ */
+ char *pszEnd = NULL;
+ unsigned long cChunks = strtoul(argv[3], &pszEnd, 0);
+ if (cChunks == ULONG_MAX || cChunks == 0 || !argv[3] || *pszEnd)
+ {
+ fprintf(stderr, "split-soapC: Given argument \"%s\" is not a valid chunk count.\n", argv[3]);
+ return RTEXITCODE_SYNTAX;
+ }
+
+ /*
+ * Read the input file into a zero terminated memory buffer.
+ */
+ size_t cbFileIn;
+ char *pszBuffer = readfileIntoBuffer(argv[1], &cbFileIn);
+ if (!pszBuffer)
+ return RTEXITCODE_FAILURE;
+
+ /*
+ * Split the file.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ FILE *pFileOut = NULL;
+ const char *pszLine = pszBuffer;
+ size_t cbChunk = cbFileIn / cChunks;
+ unsigned long cFiles = 0;
+ size_t cbLimit = 0;
+ size_t cbWritten = 0;
+ unsigned long cIfNesting = 0;
+ unsigned long cWarningNesting = 0;
+ unsigned long cBraceNesting = 0;
+ unsigned long cLinesSinceStaticMap = ~0UL / 2;
+ bool fJustZero = false;
+
+ do
+ {
+ if (!pFileOut)
+ {
+ /* construct output filename */
+ char szFilename[1024];
+ sprintf(szFilename, "%s%lu.cpp", argv[2], ++cFiles);
+ szFilename[sizeof(szFilename)-1] = '\0';
+
+ size_t offName = strlen(szFilename);
+ while (offName > 0 && !RTPATH_IS_SEP(szFilename[offName - 1]))
+ offName -= 1;
+ printf("info: %s\n", &szFilename[offName]);
+
+ /* create output file */
+ pFileOut = fopen(szFilename, "wb");
+ if (!pFileOut)
+ {
+ fprintf(stderr, "split-soapC: Failed to open file \"%s\" for writing\n", szFilename);
+ rcExit = RTEXITCODE_FAILURE;
+ break;
+ }
+ if (cFiles > 1)
+ fprintf(pFileOut, "#include \"soapH.h\"%s\n",
+#ifdef RT_OS_WINDOWS
+ "\r"
+#else
+ ""
+#endif
+ );
+ cbLimit += cbChunk;
+ cLinesSinceStaticMap = ~0UL / 2;
+ }
+
+ /* find begin of next line and print current line */
+ const char *pszNextLine = strchr(pszLine, '\n');
+ size_t cbLine;
+ if (pszNextLine)
+ {
+ pszNextLine++;
+ cbLine = pszNextLine - pszLine;
+ }
+ else
+ cbLine = strlen(pszLine);
+ if (fwrite(pszLine, 1, cbLine, pFileOut) != cbLine)
+ {
+ fprintf(stderr, "split-soapC: Failed to write to output file\n");
+ rcExit = RTEXITCODE_FAILURE;
+ break;
+ }
+ cbWritten += cbLine;
+
+ /* process nesting depth information */
+ if (!strncmp(pszLine, "#if", 3))
+ cIfNesting++;
+ else if (!strncmp(pszLine, "#endif", 6))
+ {
+ cIfNesting--;
+ if (!cBraceNesting && !cIfNesting)
+ fJustZero = true;
+ }
+ else if (!strncmp(pszLine, RT_STR_TUPLE("#pragma warning(push)")))
+ cWarningNesting++;
+ else if (!strncmp(pszLine, RT_STR_TUPLE("#pragma warning(pop)")))
+ cWarningNesting--;
+ else
+ {
+ for (const char *p = pszLine; p < pszLine + cbLine; p++)
+ {
+ if (*p == '{')
+ cBraceNesting++;
+ else if (*p == '}')
+ {
+ cBraceNesting--;
+ if (!cBraceNesting && !cIfNesting)
+ fJustZero = true;
+ }
+ }
+ }
+
+ /* look for static variables used for enum conversion. */
+ if (!strncmp(pszLine, "static const struct soap_code_map", sizeof("static const struct soap_code_map") - 1))
+ cLinesSinceStaticMap = 0;
+ else
+ cLinesSinceStaticMap++;
+
+ /* start a new output file if necessary and possible */
+ if ( cbWritten >= cbLimit
+ && cIfNesting == 0
+ && cWarningNesting == 0
+ && fJustZero
+ && cFiles < cChunks
+ && cLinesSinceStaticMap > 150 /*hack!*/)
+ {
+ fclose(pFileOut);
+ pFileOut = NULL;
+ }
+
+ fJustZero = false;
+ pszLine = pszNextLine;
+ } while (pszLine);
+
+ printf("split-soapC: Created %lu files.\n", (unsigned long)cFiles);
+
+ free(pszBuffer);
+ if (pFileOut)
+ fclose(pFileOut);
+
+ return rcExit;
+}
diff --git a/src/VBox/Main/webservice/stdsoap2.sed b/src/VBox/Main/webservice/stdsoap2.sed
new file mode 100644
index 00000000..6bb0f9d5
--- /dev/null
+++ b/src/VBox/Main/webservice/stdsoap2.sed
@@ -0,0 +1,31 @@
+# $Id: stdsoap2.sed $
+## @file
+# WebService - SED script for inserting a iprt/win/windows.h include
+# before stdsoap2.h in soapStub.h. This prevents hacking
+# client and server code to do the same when using -Wall.
+#
+
+#
+# Copyright (C) 2016-2022 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+s/\(#include "stdsoap2\.h"\)/#ifdef RT_OS_WINDOWS\n# include <iprt\/win\/windows.h>\n#endif\n\1/
+
diff --git a/src/VBox/Main/webservice/types.txt b/src/VBox/Main/webservice/types.txt
new file mode 100644
index 00000000..c976d172
--- /dev/null
+++ b/src/VBox/Main/webservice/types.txt
@@ -0,0 +1,30 @@
+
+ XIDL IPRT COM XPCOM WSDL gSOAP default JAX-WS PHP
+
+ boolean BOOL PRBool xsd:boolean bool Boolean boolean
+
+ octet uint8_t BYTE PRUint8 xsd:unsignedByte Short integer
+
+ short int16_t SHORT PRInt16 xsd:short short Short (2) integer
+ unsigned short uint16_t USHORT PRUint16 xsd:unsignedShort unsigned short Integer (2) integer
+ long int32_t LONG PRInt32 xsd:int int Integer (2) integer
+ unsigned long uint32_t ULONG PRUint32 xsd:unsignedInt unsigned int Long (2) float (3)
+ long long int64_t LONG64 PRInt64 xsd:long long long Long (2) float (3)
+ unsigned long long uint64_t ULONG64 PRUint64 xsd:unsignedLong unsigned long long BigInteger (2) float (3)
+
+ double xsd:double double Double float
+ float xsd:float float Float float
+
+ wstring BSTR PRUnichar* xsd:string std::string String string
+
+ result xsd:unsignedInt(1) (undefined)
+
+ uuid xsd:string(1) (undefined)
+
+
+(1) my definition
+(2) Java "Short" is signed 16-bit integer; since Java has no support for unsigned types, we need to use the
+ next bigger class, which is Integer, for IPRT uint16_t. Similarly for the other integer types.
+(3) PHP does not support unsigned integers; Size of integer is platform-dependent, usual value of at least 32-bits signed. Use float for numbers greeted that signed 32-bit int
+
+
diff --git a/src/VBox/Main/webservice/vboxweb.cpp b/src/VBox/Main/webservice/vboxweb.cpp
new file mode 100644
index 00000000..a419c354
--- /dev/null
+++ b/src/VBox/Main/webservice/vboxweb.cpp
@@ -0,0 +1,2518 @@
+/* $Id: vboxweb.cpp $ */
+/** @file
+ * vboxweb.cpp:
+ * hand-coded parts of the webservice server. This is linked with the
+ * generated code in out/.../src/VBox/Main/webservice/methodmaps.cpp
+ * (plus static gSOAP server code) to implement the actual webservice
+ * server, to which clients can connect.
+ */
+
+/*
+ * Copyright (C) 2007-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+// shared webservice header
+#include "vboxweb.h"
+
+// vbox headers
+#include <VBox/com/com.h>
+#include <VBox/com/array.h>
+#include <VBox/com/string.h>
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/com/errorprint.h>
+#include <VBox/com/listeners.h>
+#include <VBox/com/NativeEventQueue.h>
+#include <VBox/VBoxAuth.h>
+#include <VBox/version.h>
+#include <VBox/log.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/ldr.h>
+#include <iprt/message.h>
+#include <iprt/process.h>
+#include <iprt/rand.h>
+#include <iprt/semaphore.h>
+#include <iprt/critsect.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+#include <iprt/path.h>
+#include <iprt/system.h>
+#include <iprt/base64.h>
+#include <iprt/stream.h>
+#include <iprt/asm.h>
+
+#ifdef WITH_OPENSSL
+# include <openssl/opensslv.h>
+#endif
+
+#ifndef RT_OS_WINDOWS
+# include <signal.h>
+#endif
+
+// workaround for compile problems on gcc 4.1
+#ifdef __GNUC__
+#pragma GCC visibility push(default)
+#endif
+
+// gSOAP headers (must come after vbox includes because it checks for conflicting defs)
+#include "soapH.h"
+
+// standard headers
+#include <map>
+#include <list>
+
+#ifdef __GNUC__
+#pragma GCC visibility pop
+#endif
+
+// include generated namespaces table
+#include "vboxwebsrv.nsmap"
+
+RT_C_DECLS_BEGIN
+
+// declarations for the generated WSDL text
+extern const unsigned char g_abVBoxWebWSDL[];
+extern const unsigned g_cbVBoxWebWSDL;
+
+RT_C_DECLS_END
+
+static void WebLogSoapError(struct soap *soap);
+
+/****************************************************************************
+ *
+ * private typedefs
+ *
+ ****************************************************************************/
+
+typedef std::map<uint64_t, ManagedObjectRef*> ManagedObjectsMapById;
+typedef ManagedObjectsMapById::iterator ManagedObjectsIteratorById;
+typedef std::map<uintptr_t, ManagedObjectRef*> ManagedObjectsMapByPtr;
+typedef ManagedObjectsMapByPtr::iterator ManagedObjectsIteratorByPtr;
+
+typedef std::map<uint64_t, WebServiceSession*> WebsessionsMap;
+typedef WebsessionsMap::iterator WebsessionsMapIterator;
+
+typedef std::map<RTTHREAD, com::Utf8Str> ThreadsMap;
+
+static DECLCALLBACK(int) fntWatchdog(RTTHREAD ThreadSelf, void *pvUser);
+
+/****************************************************************************
+ *
+ * Read-only global variables
+ *
+ ****************************************************************************/
+
+static ComPtr<IVirtualBoxClient> g_pVirtualBoxClient = NULL;
+
+// generated strings in methodmaps.cpp
+extern const char *g_pcszISession,
+ *g_pcszIVirtualBox,
+ *g_pcszIVirtualBoxErrorInfo;
+
+// globals for vboxweb command-line arguments
+#define DEFAULT_TIMEOUT_SECS 300
+#define DEFAULT_TIMEOUT_SECS_STRING "300"
+static int g_iWatchdogTimeoutSecs = DEFAULT_TIMEOUT_SECS;
+static int g_iWatchdogCheckInterval = 5;
+
+static const char *g_pcszBindToHost = NULL; // host; NULL = localhost
+static unsigned int g_uBindToPort = 18083; // port
+static unsigned int g_uBacklog = 100; // backlog = max queue size for requests
+
+#ifdef WITH_OPENSSL
+static bool g_fSSL = false; // if SSL is enabled
+static const char *g_pcszKeyFile = NULL; // server key file
+static const char *g_pcszPassword = NULL; // password for server key
+static const char *g_pcszCACert = NULL; // file with trusted CA certificates
+static const char *g_pcszCAPath = NULL; // directory with trusted CA certificates
+static const char *g_pcszDHFile = NULL; // DH file name or DH key length in bits, NULL=use RSA
+static const char *g_pcszRandFile = NULL; // file with random data seed
+static const char *g_pcszSID = "vboxwebsrv"; // server ID for SSL session cache
+#endif /* WITH_OPENSSL */
+
+static unsigned int g_cMaxWorkerThreads = 100; // max. no. of worker threads
+static unsigned int g_cMaxKeepAlive = 100; // maximum number of soap requests in one connection
+
+static const char *g_pcszAuthentication = NULL; // web service authentication
+
+static uint32_t g_cHistory = 10; // enable log rotation, 10 files
+static uint32_t g_uHistoryFileTime = RT_SEC_1DAY; // max 1 day per file
+static uint64_t g_uHistoryFileSize = 100 * _1M; // max 100MB per file
+bool g_fVerbose = false; // be verbose
+
+static bool g_fDaemonize = false; // run in background.
+static volatile bool g_fKeepRunning = true; // controlling the exit
+
+const WSDLT_ID g_EmptyWSDLID; // for NULL MORs
+
+/****************************************************************************
+ *
+ * Writeable global variables
+ *
+ ****************************************************************************/
+
+// The one global SOAP queue created by main().
+class SoapQ;
+static SoapQ *g_pSoapQ = NULL;
+
+// this mutex protects the auth lib and authentication
+static util::WriteLockHandle *g_pAuthLibLockHandle;
+
+// this mutex protects the global VirtualBox reference below
+static util::RWLockHandle *g_pVirtualBoxLockHandle;
+
+static ComPtr<IVirtualBox> g_pVirtualBox = NULL;
+
+// this mutex protects all of the below
+util::WriteLockHandle *g_pWebsessionsLockHandle;
+
+static WebsessionsMap g_mapWebsessions;
+static ULONG64 g_cManagedObjects = 0;
+
+// this mutex protects g_mapThreads
+static util::RWLockHandle *g_pThreadsLockHandle;
+
+// Threads map, so we can quickly map an RTTHREAD struct to a logger prefix
+static ThreadsMap g_mapThreads;
+
+/****************************************************************************
+ *
+ * Command line help
+ *
+ ****************************************************************************/
+
+static const RTGETOPTDEF g_aOptions[]
+ = {
+ { "--help", 'h', RTGETOPT_REQ_NOTHING }, /* for DisplayHelp() */
+#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
+ { "--background", 'b', RTGETOPT_REQ_NOTHING },
+#endif
+ { "--host", 'H', RTGETOPT_REQ_STRING },
+ { "--port", 'p', RTGETOPT_REQ_UINT32 },
+#ifdef WITH_OPENSSL
+ { "--ssl", 's', RTGETOPT_REQ_NOTHING },
+ { "--keyfile", 'K', RTGETOPT_REQ_STRING },
+ { "--passwordfile", 'a', RTGETOPT_REQ_STRING },
+ { "--cacert", 'c', RTGETOPT_REQ_STRING },
+ { "--capath", 'C', RTGETOPT_REQ_STRING },
+ { "--dhfile", 'D', RTGETOPT_REQ_STRING },
+ { "--randfile", 'r', RTGETOPT_REQ_STRING },
+#endif /* WITH_OPENSSL */
+ { "--timeout", 't', RTGETOPT_REQ_UINT32 },
+ { "--check-interval", 'i', RTGETOPT_REQ_UINT32 },
+ { "--threads", 'T', RTGETOPT_REQ_UINT32 },
+ { "--keepalive", 'k', RTGETOPT_REQ_UINT32 },
+ { "--authentication", 'A', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--pidfile", 'P', RTGETOPT_REQ_STRING },
+ { "--logfile", 'F', RTGETOPT_REQ_STRING },
+ { "--logrotate", 'R', RTGETOPT_REQ_UINT32 },
+ { "--logsize", 'S', RTGETOPT_REQ_UINT64 },
+ { "--loginterval", 'I', RTGETOPT_REQ_UINT32 }
+ };
+
+static void DisplayHelp()
+{
+ RTStrmPrintf(g_pStdErr, "\nUsage: vboxwebsrv [options]\n\nSupported options (default values in brackets):\n");
+ for (unsigned i = 0;
+ i < RT_ELEMENTS(g_aOptions);
+ ++i)
+ {
+ std::string str(g_aOptions[i].pszLong);
+ str += ", -";
+ str += g_aOptions[i].iShort;
+ str += ":";
+
+ const char *pcszDescr = "";
+
+ switch (g_aOptions[i].iShort)
+ {
+ case 'h':
+ pcszDescr = "Print this help message and exit.";
+ break;
+
+#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
+ case 'b':
+ pcszDescr = "Run in background (daemon mode).";
+ break;
+#endif
+
+ case 'H':
+ pcszDescr = "The host to bind to (localhost).";
+ break;
+
+ case 'p':
+ pcszDescr = "The port to bind to (18083).";
+ break;
+
+#ifdef WITH_OPENSSL
+ case 's':
+ pcszDescr = "Enable SSL/TLS encryption.";
+ break;
+
+ case 'K':
+ pcszDescr = "Server key and certificate file, PEM format (\"\").";
+ break;
+
+ case 'a':
+ pcszDescr = "File name for password to server key (\"\").";
+ break;
+
+ case 'c':
+ pcszDescr = "CA certificate file, PEM format (\"\").";
+ break;
+
+ case 'C':
+ pcszDescr = "CA certificate path (\"\").";
+ break;
+
+ case 'D':
+ pcszDescr = "DH file name or DH key length in bits (\"\").";
+ break;
+
+ case 'r':
+ pcszDescr = "File containing seed for random number generator (\"\").";
+ break;
+#endif /* WITH_OPENSSL */
+
+ case 't':
+ pcszDescr = "Session timeout in seconds; 0 = disable timeouts (" DEFAULT_TIMEOUT_SECS_STRING ").";
+ break;
+
+ case 'T':
+ pcszDescr = "Maximum number of worker threads to run in parallel (100).";
+ break;
+
+ case 'k':
+ pcszDescr = "Maximum number of requests before a socket will be closed (100).";
+ break;
+
+ case 'A':
+ pcszDescr = "Authentication method for the webservice (\"\").";
+ break;
+
+ case 'i':
+ pcszDescr = "Frequency of timeout checks in seconds (5).";
+ break;
+
+ case 'v':
+ pcszDescr = "Be verbose.";
+ break;
+
+ case 'P':
+ pcszDescr = "Name of the PID file which is created when the daemon was started.";
+ break;
+
+ case 'F':
+ pcszDescr = "Name of file to write log to (no file).";
+ break;
+
+ case 'R':
+ pcszDescr = "Number of log files (0 disables log rotation).";
+ break;
+
+ case 'S':
+ pcszDescr = "Maximum size of a log file to trigger rotation (bytes).";
+ break;
+
+ case 'I':
+ pcszDescr = "Maximum time interval to trigger log rotation (seconds).";
+ break;
+ }
+
+ RTStrmPrintf(g_pStdErr, "%-23s%s\n", str.c_str(), pcszDescr);
+ }
+}
+
+/****************************************************************************
+ *
+ * SoapQ, SoapThread (multithreading)
+ *
+ ****************************************************************************/
+
+class SoapQ;
+
+class SoapThread
+{
+public:
+ /**
+ * Constructor. Creates the new thread and makes it call process() for processing the queue.
+ * @param u Thread number. (So we can count from 1 and be readable.)
+ * @param q SoapQ instance which has the queue to process.
+ * @param soap struct soap instance from main() which we copy here.
+ */
+ SoapThread(size_t u,
+ SoapQ &q,
+ const struct soap *soap)
+ : m_u(u),
+ m_strThread(com::Utf8StrFmt("SQW%02d", m_u)),
+ m_pQ(&q)
+ {
+ // make a copy of the soap struct for the new thread
+ m_soap = soap_copy(soap);
+ m_soap->fget = fnHttpGet;
+
+ /* The soap.max_keep_alive value can be set to the maximum keep-alive calls allowed,
+ * which is important to avoid a client from holding a thread indefinitely.
+ * http://www.cs.fsu.edu/~engelen/soapdoc2.html#sec:keepalive
+ *
+ * Strings with 8-bit content can hold ASCII (default) or UTF8. The latter is
+ * possible by enabling the SOAP_C_UTFSTRING flag.
+ */
+ soap_set_omode(m_soap, SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING);
+ soap_set_imode(m_soap, SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING);
+ m_soap->max_keep_alive = g_cMaxKeepAlive;
+
+ int rc = RTThreadCreate(&m_pThread,
+ fntWrapper,
+ this, // pvUser
+ 0, // cbStack
+ RTTHREADTYPE_MAIN_HEAVY_WORKER,
+ 0,
+ m_strThread.c_str());
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("Cannot start worker thread %d: %Rrc\n", u, rc);
+ exit(1);
+ }
+ }
+
+ void process();
+
+ static int fnHttpGet(struct soap *soap)
+ {
+ char *s = strchr(soap->path, '?');
+ if (!s || strcmp(s, "?wsdl"))
+ return SOAP_GET_METHOD;
+ soap_response(soap, SOAP_HTML);
+ soap_send_raw(soap, (const char *)g_abVBoxWebWSDL, g_cbVBoxWebWSDL);
+ soap_end_send(soap);
+ return SOAP_OK;
+ }
+
+ /**
+ * Static function that can be passed to RTThreadCreate and that calls
+ * process() on the SoapThread instance passed as the thread parameter.
+ *
+ * @param hThreadSelf
+ * @param pvThread
+ * @return
+ */
+ static DECLCALLBACK(int) fntWrapper(RTTHREAD hThreadSelf, void *pvThread)
+ {
+ RT_NOREF(hThreadSelf);
+ SoapThread *pst = (SoapThread*)pvThread;
+ pst->process();
+ return VINF_SUCCESS;
+ }
+
+ size_t m_u; // thread number
+ com::Utf8Str m_strThread; // thread name ("SoapQWrkXX")
+ SoapQ *m_pQ; // the single SOAP queue that all the threads service
+ struct soap *m_soap; // copy of the soap structure for this thread (from soap_copy())
+ RTTHREAD m_pThread; // IPRT thread struct for this thread
+};
+
+/**
+ * SOAP queue encapsulation. There is only one instance of this, to
+ * which add() adds a queue item (called on the main thread),
+ * and from which get() fetch items, called from each queue thread.
+ */
+class SoapQ
+{
+public:
+
+ /**
+ * Constructor. Creates the soap queue.
+ * @param pSoap
+ */
+ SoapQ(const struct soap *pSoap)
+ : m_soap(pSoap),
+ m_mutex(util::LOCKCLASS_OBJECTSTATE), // lowest lock order, no other may be held while this is held
+ m_cIdleThreads(0)
+ {
+ RTSemEventMultiCreate(&m_event);
+ }
+
+ ~SoapQ()
+ {
+ /* Tell the threads to terminate. */
+ RTSemEventMultiSignal(m_event);
+ {
+ util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
+ int i = 0;
+ while (m_llAllThreads.size() && i++ <= 30)
+ {
+ qlock.release();
+ RTThreadSleep(1000);
+ RTSemEventMultiSignal(m_event);
+ qlock.acquire();
+ }
+ LogRel(("ending queue processing (%d out of %d threads idle)\n", m_cIdleThreads, m_llAllThreads.size()));
+ }
+
+ RTSemEventMultiDestroy(m_event);
+ }
+
+ /**
+ * Adds the given socket to the SOAP queue and posts the
+ * member event sem to wake up the workers. Called on the main thread
+ * whenever a socket has work to do. Creates a new SOAP thread on the
+ * first call or when all existing threads are busy.
+ * @param s Socket from soap_accept() which has work to do.
+ */
+ size_t add(SOAP_SOCKET s)
+ {
+ size_t cItems;
+ util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
+
+ // if no threads have yet been created, or if all threads are busy,
+ // create a new SOAP thread
+ if ( !m_cIdleThreads
+ // but only if we're not exceeding the global maximum (default is 100)
+ && (m_llAllThreads.size() < g_cMaxWorkerThreads)
+ )
+ {
+ SoapThread *pst = new SoapThread(m_llAllThreads.size() + 1,
+ *this,
+ m_soap);
+ m_llAllThreads.push_back(pst);
+ util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
+ g_mapThreads[pst->m_pThread] = com::Utf8StrFmt("[%3u]", pst->m_u);
+ ++m_cIdleThreads;
+ }
+
+ // enqueue the socket of this connection and post eventsem so that
+ // one of the threads (possibly the one just created) can pick it up
+ m_llSocketsQ.push_back(s);
+ cItems = m_llSocketsQ.size();
+ qlock.release();
+
+ // unblock one of the worker threads
+ RTSemEventMultiSignal(m_event);
+
+ return cItems;
+ }
+
+ /**
+ * Blocks the current thread until work comes in; then returns
+ * the SOAP socket which has work to do. This reduces m_cIdleThreads
+ * by one, and the caller MUST call done() when it's done processing.
+ * Called from the worker threads.
+ * @param cIdleThreads out: no. of threads which are currently idle (not counting the caller)
+ * @param cThreads out: total no. of SOAP threads running
+ * @return
+ */
+ SOAP_SOCKET get(size_t &cIdleThreads, size_t &cThreads)
+ {
+ while (g_fKeepRunning)
+ {
+ // wait for something to happen
+ RTSemEventMultiWait(m_event, RT_INDEFINITE_WAIT);
+
+ if (!g_fKeepRunning)
+ break;
+
+ util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
+ if (!m_llSocketsQ.empty())
+ {
+ SOAP_SOCKET socket = m_llSocketsQ.front();
+ m_llSocketsQ.pop_front();
+ cIdleThreads = --m_cIdleThreads;
+ cThreads = m_llAllThreads.size();
+
+ // reset the multi event only if the queue is now empty; otherwise
+ // another thread will also wake up when we release the mutex and
+ // process another one
+ if (m_llSocketsQ.empty())
+ RTSemEventMultiReset(m_event);
+
+ qlock.release();
+
+ return socket;
+ }
+
+ // nothing to do: keep looping
+ }
+ return SOAP_INVALID_SOCKET;
+ }
+
+ /**
+ * To be called by a worker thread after fetching an item from the
+ * queue via get() and having finished its lengthy processing.
+ */
+ void done()
+ {
+ util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
+ ++m_cIdleThreads;
+ }
+
+ /**
+ * To be called by a worker thread when signing off, i.e. no longer
+ * willing to process requests.
+ */
+ void signoff(SoapThread *th)
+ {
+ {
+ util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
+ size_t c = g_mapThreads.erase(th->m_pThread);
+ AssertReturnVoid(c == 1);
+ }
+ {
+ util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
+ m_llAllThreads.remove(th);
+ --m_cIdleThreads;
+ }
+ }
+
+ const struct soap *m_soap; // soap structure created by main(), passed to constructor
+
+ util::WriteLockHandle m_mutex;
+ RTSEMEVENTMULTI m_event; // posted by add(), blocked on by get()
+
+ std::list<SoapThread*> m_llAllThreads; // all the threads created by the constructor
+ size_t m_cIdleThreads; // threads which are currently idle (statistics)
+
+ // A std::list abused as a queue; this contains the actual jobs to do,
+ // each int being a socket from soap_accept()
+ std::list<SOAP_SOCKET> m_llSocketsQ;
+};
+
+/**
+ * Thread function for each of the SOAP queue worker threads. This keeps
+ * running, blocks on the event semaphore in SoapThread.SoapQ and picks
+ * up a socket from the queue therein, which has been put there by
+ * beginProcessing().
+ */
+void SoapThread::process()
+{
+ LogRel(("New SOAP thread started\n"));
+
+ while (g_fKeepRunning)
+ {
+ // wait for a socket to arrive on the queue
+ size_t cIdleThreads = 0, cThreads = 0;
+ m_soap->socket = m_pQ->get(cIdleThreads, cThreads);
+
+ if (!soap_valid_socket(m_soap->socket))
+ continue;
+
+ LogRel(("Processing connection from IP=%RTnaipv4 socket=%d (%d out of %d threads idle)\n",
+ RT_H2N_U32(m_soap->ip), m_soap->socket, cIdleThreads, cThreads));
+
+ // Ensure that we don't get stuck indefinitely for connections using
+ // keepalive, otherwise stale connections tie up worker threads.
+ m_soap->send_timeout = 60;
+ m_soap->recv_timeout = 60;
+ // Limit the maximum SOAP request size to a generous amount, just to
+ // be on the safe side (SOAP is quite wordy when representing arrays,
+ // and some API uses need to deal with large arrays). Good that binary
+ // data is no longer represented by byte arrays...
+ m_soap->recv_maxlength = _16M;
+ // process the request; this goes into the COM code in methodmaps.cpp
+ do {
+#ifdef WITH_OPENSSL
+ if (g_fSSL && soap_ssl_accept(m_soap))
+ {
+ WebLogSoapError(m_soap);
+ break;
+ }
+#endif /* WITH_OPENSSL */
+ soap_serve(m_soap);
+ } while (0);
+
+ soap_destroy(m_soap); // clean up class instances
+ soap_end(m_soap); // clean up everything and close socket
+
+ // tell the queue we're idle again
+ m_pQ->done();
+ }
+ m_pQ->signoff(this);
+}
+
+/****************************************************************************
+ *
+ * VirtualBoxClient event listener
+ *
+ ****************************************************************************/
+
+class VirtualBoxClientEventListener
+{
+public:
+ VirtualBoxClientEventListener()
+ {
+ }
+
+ virtual ~VirtualBoxClientEventListener()
+ {
+ }
+
+ HRESULT init()
+ {
+ return S_OK;
+ }
+
+ void uninit()
+ {
+ }
+
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
+ {
+ switch (aType)
+ {
+ case VBoxEventType_OnVBoxSVCAvailabilityChanged:
+ {
+ ComPtr<IVBoxSVCAvailabilityChangedEvent> pVSACEv = aEvent;
+ Assert(pVSACEv);
+ BOOL fAvailable = FALSE;
+ pVSACEv->COMGETTER(Available)(&fAvailable);
+ if (!fAvailable)
+ {
+ LogRel(("VBoxSVC became unavailable\n"));
+ {
+ util::AutoWriteLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
+ g_pVirtualBox.setNull();
+ }
+ {
+ // we're messing with websessions, so lock them
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+ WEBDEBUG(("SVC unavailable: deleting %d websessions\n", g_mapWebsessions.size()));
+
+ WebsessionsMapIterator it = g_mapWebsessions.begin(),
+ itEnd = g_mapWebsessions.end();
+ while (it != itEnd)
+ {
+ WebServiceSession *pWebsession = it->second;
+ WEBDEBUG(("SVC unavailable: websession %#llx stale, deleting\n", pWebsession->getID()));
+ delete pWebsession;
+ it = g_mapWebsessions.begin();
+ }
+ }
+ }
+ else
+ {
+ LogRel(("VBoxSVC became available\n"));
+ util::AutoWriteLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = g_pVirtualBoxClient->COMGETTER(VirtualBox)(g_pVirtualBox.asOutParam());
+ AssertComRC(hrc);
+ }
+ break;
+ }
+ default:
+ AssertFailed();
+ }
+
+ return S_OK;
+ }
+
+private:
+};
+
+typedef ListenerImpl<VirtualBoxClientEventListener> VirtualBoxClientEventListenerImpl;
+
+VBOX_LISTENER_DECLARE(VirtualBoxClientEventListenerImpl)
+
+/**
+ * Helper for printing SOAP error messages.
+ * @param soap
+ */
+/*static*/
+void WebLogSoapError(struct soap *soap)
+{
+ if (soap_check_state(soap))
+ {
+ LogRel(("Error: soap struct not initialized\n"));
+ return;
+ }
+
+ const char *pcszFaultString = *soap_faultstring(soap);
+ const char **ppcszDetail = soap_faultcode(soap);
+ LogRel(("#### SOAP FAULT: %s [%s]\n",
+ pcszFaultString ? pcszFaultString : "[no fault string available]",
+ (ppcszDetail && *ppcszDetail) ? *ppcszDetail : "no details available"));
+}
+
+/**
+ * Helper for decoding AuthResult.
+ * @param result AuthResult
+ */
+static const char * decodeAuthResult(AuthResult result)
+{
+ switch (result)
+ {
+ case AuthResultAccessDenied: return "access DENIED";
+ case AuthResultAccessGranted: return "access granted";
+ case AuthResultDelegateToGuest: return "delegated to guest";
+ default: return "unknown AuthResult";
+ }
+}
+
+#if defined(WITH_OPENSSL) && (OPENSSL_VERSION_NUMBER < 0x10100000 || defined(LIBRESSL_VERSION_NUMBER))
+/****************************************************************************
+ *
+ * OpenSSL convenience functions for multithread support.
+ * Not required for OpenSSL 1.1+
+ *
+ ****************************************************************************/
+
+static RTCRITSECT *g_pSSLMutexes = NULL;
+
+struct CRYPTO_dynlock_value
+{
+ RTCRITSECT mutex;
+};
+
+static unsigned long CRYPTO_id_function()
+{
+ return (unsigned long)RTThreadNativeSelf();
+}
+
+static void CRYPTO_locking_function(int mode, int n, const char * /*file*/, int /*line*/)
+{
+ if (mode & CRYPTO_LOCK)
+ RTCritSectEnter(&g_pSSLMutexes[n]);
+ else
+ RTCritSectLeave(&g_pSSLMutexes[n]);
+}
+
+static struct CRYPTO_dynlock_value *CRYPTO_dyn_create_function(const char * /*file*/, int /*line*/)
+{
+ static uint32_t s_iCritSectDynlock = 0;
+ struct CRYPTO_dynlock_value *value = (struct CRYPTO_dynlock_value *)RTMemAlloc(sizeof(struct CRYPTO_dynlock_value));
+ if (value)
+ RTCritSectInitEx(&value->mutex, RTCRITSECT_FLAGS_NO_LOCK_VAL,
+ NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE,
+ "openssl-dyn-%u", ASMAtomicIncU32(&s_iCritSectDynlock) - 1);
+
+ return value;
+}
+
+static void CRYPTO_dyn_lock_function(int mode, struct CRYPTO_dynlock_value *value, const char * /*file*/, int /*line*/)
+{
+ if (mode & CRYPTO_LOCK)
+ RTCritSectEnter(&value->mutex);
+ else
+ RTCritSectLeave(&value->mutex);
+}
+
+static void CRYPTO_dyn_destroy_function(struct CRYPTO_dynlock_value *value, const char * /*file*/, int /*line*/)
+{
+ if (value)
+ {
+ RTCritSectDelete(&value->mutex);
+ free(value);
+ }
+}
+
+static int CRYPTO_thread_setup()
+{
+ int num_locks = CRYPTO_num_locks();
+ g_pSSLMutexes = (RTCRITSECT *)RTMemAlloc(num_locks * sizeof(RTCRITSECT));
+ if (!g_pSSLMutexes)
+ return SOAP_EOM;
+
+ for (int i = 0; i < num_locks; i++)
+ {
+ int rc = RTCritSectInitEx(&g_pSSLMutexes[i], RTCRITSECT_FLAGS_NO_LOCK_VAL,
+ NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE,
+ "openssl-%d", i);
+ if (RT_FAILURE(rc))
+ {
+ for ( ; i >= 0; i--)
+ RTCritSectDelete(&g_pSSLMutexes[i]);
+ RTMemFree(g_pSSLMutexes);
+ g_pSSLMutexes = NULL;
+ return SOAP_EOM;
+ }
+ }
+
+ CRYPTO_set_id_callback(CRYPTO_id_function);
+ CRYPTO_set_locking_callback(CRYPTO_locking_function);
+ CRYPTO_set_dynlock_create_callback(CRYPTO_dyn_create_function);
+ CRYPTO_set_dynlock_lock_callback(CRYPTO_dyn_lock_function);
+ CRYPTO_set_dynlock_destroy_callback(CRYPTO_dyn_destroy_function);
+
+ return SOAP_OK;
+}
+
+static void CRYPTO_thread_cleanup()
+{
+ if (!g_pSSLMutexes)
+ return;
+
+ CRYPTO_set_id_callback(NULL);
+ CRYPTO_set_locking_callback(NULL);
+ CRYPTO_set_dynlock_create_callback(NULL);
+ CRYPTO_set_dynlock_lock_callback(NULL);
+ CRYPTO_set_dynlock_destroy_callback(NULL);
+
+ int num_locks = CRYPTO_num_locks();
+ for (int i = 0; i < num_locks; i++)
+ RTCritSectDelete(&g_pSSLMutexes[i]);
+
+ RTMemFree(g_pSSLMutexes);
+ g_pSSLMutexes = NULL;
+}
+#endif /* WITH_OPENSSL && (OPENSSL_VERSION_NUMBER < 0x10100000 || defined(LIBRESSL_VERSION_NUMBER)) */
+
+/****************************************************************************
+ *
+ * SOAP queue pumper thread
+ *
+ ****************************************************************************/
+
+static void doQueuesLoop()
+{
+#if defined(WITH_OPENSSL) && (OPENSSL_VERSION_NUMBER < 0x10100000 || defined(LIBRESSL_VERSION_NUMBER))
+ if (g_fSSL && CRYPTO_thread_setup())
+ {
+ LogRel(("Failed to set up OpenSSL thread mutex!"));
+ exit(RTEXITCODE_FAILURE);
+ }
+#endif
+
+ // set up gSOAP
+ struct soap soap;
+ soap_init(&soap);
+
+#ifdef WITH_OPENSSL
+ if (g_fSSL && soap_ssl_server_context(&soap, SOAP_SSL_REQUIRE_SERVER_AUTHENTICATION | SOAP_TLSv1, g_pcszKeyFile,
+ g_pcszPassword, g_pcszCACert, g_pcszCAPath,
+ g_pcszDHFile, g_pcszRandFile, g_pcszSID))
+ {
+ WebLogSoapError(&soap);
+ exit(RTEXITCODE_FAILURE);
+ }
+#endif /* WITH_OPENSSL */
+
+ soap.bind_flags |= SO_REUSEADDR;
+ // avoid EADDRINUSE on bind()
+
+ SOAP_SOCKET m, s; // master and slave sockets
+ m = soap_bind(&soap,
+ g_pcszBindToHost ? g_pcszBindToHost : "localhost", // safe default host
+ g_uBindToPort, // port
+ g_uBacklog); // backlog = max queue size for requests
+ if (m == SOAP_INVALID_SOCKET)
+ WebLogSoapError(&soap);
+ else
+ {
+#ifdef WITH_OPENSSL
+ const char *pszSsl = g_fSSL ? "SSL, " : "";
+#else /* !WITH_OPENSSL */
+ const char *pszSsl = "";
+#endif /*!WITH_OPENSSL */
+ LogRel(("Socket connection successful: host = %s, port = %u, %smaster socket = %d\n",
+ (g_pcszBindToHost) ? g_pcszBindToHost : "default (localhost)",
+ g_uBindToPort, pszSsl, m));
+
+ // initialize thread queue, mutex and eventsem
+ g_pSoapQ = new SoapQ(&soap);
+
+ uint64_t cAccepted = 1;
+ while (g_fKeepRunning)
+ {
+ struct timeval timeout;
+ fd_set ReadFds, WriteFds, XcptFds;
+ int rv;
+ for (;;)
+ {
+ timeout.tv_sec = 60;
+ timeout.tv_usec = 0;
+ FD_ZERO(&ReadFds);
+ FD_SET(soap.master, &ReadFds);
+ FD_ZERO(&WriteFds);
+ FD_SET(soap.master, &WriteFds);
+ FD_ZERO(&XcptFds);
+ FD_SET(soap.master, &XcptFds);
+ rv = select((int)soap.master + 1, &ReadFds, &WriteFds, &XcptFds, &timeout);
+ if (rv > 0)
+ break; // work is waiting
+ if (rv == 0)
+ continue; // timeout, not necessary to bother gsoap
+ // r < 0, errno
+#if GSOAP_VERSION >= 208103
+ if (soap_socket_errno == SOAP_EINTR)
+#else
+ if (soap_socket_errno(soap.master) == SOAP_EINTR)
+#endif
+ rv = 0; // re-check if we should terminate
+ break;
+ }
+ if (rv == 0)
+ continue;
+
+ // call gSOAP to handle incoming SOAP connection
+ soap.accept_timeout = -1; // 1usec timeout, actual waiting is above
+ s = soap_accept(&soap);
+ if (!soap_valid_socket(s))
+ {
+ if (soap.errnum)
+ WebLogSoapError(&soap);
+ continue;
+ }
+
+ // add the socket to the queue and tell worker threads to
+ // pick up the job
+ size_t cItemsOnQ = g_pSoapQ->add(s);
+ LogRel(("Request %llu on socket %d queued for processing (%d items on Q)\n", cAccepted, s, cItemsOnQ));
+ cAccepted++;
+ }
+
+ delete g_pSoapQ;
+ g_pSoapQ = NULL;
+
+ LogRel(("ending SOAP request handling\n"));
+
+ delete g_pSoapQ;
+ g_pSoapQ = NULL;
+
+ }
+ soap_done(&soap); // close master socket and detach environment
+
+#if defined(WITH_OPENSSL) && (OPENSSL_VERSION_NUMBER < 0x10100000 || defined(LIBRESSL_VERSION_NUMBER))
+ if (g_fSSL)
+ CRYPTO_thread_cleanup();
+#endif
+}
+
+/**
+ * Thread function for the "queue pumper" thread started from main(). This implements
+ * the loop that takes SOAP calls from HTTP and serves them by handing sockets to the
+ * SOAP queue worker threads.
+ */
+static DECLCALLBACK(int) fntQPumper(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf, pvUser);
+
+ // store a log prefix for this thread
+ util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
+ g_mapThreads[RTThreadSelf()] = "[ P ]";
+ thrLock.release();
+
+ doQueuesLoop();
+
+ thrLock.acquire();
+ g_mapThreads.erase(RTThreadSelf());
+ return VINF_SUCCESS;
+}
+
+#ifdef RT_OS_WINDOWS
+/**
+ * "Signal" handler for cleanly terminating the event loop.
+ */
+static BOOL WINAPI websrvSignalHandler(DWORD dwCtrlType)
+{
+ bool fEventHandled = FALSE;
+ switch (dwCtrlType)
+ {
+ /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
+ * via GenerateConsoleCtrlEvent(). */
+ case CTRL_BREAK_EVENT:
+ case CTRL_CLOSE_EVENT:
+ case CTRL_C_EVENT:
+ case CTRL_LOGOFF_EVENT:
+ case CTRL_SHUTDOWN_EVENT:
+ {
+ ASMAtomicWriteBool(&g_fKeepRunning, false);
+ com::NativeEventQueue *pQ = com::NativeEventQueue::getMainEventQueue();
+ pQ->interruptEventQueueProcessing();
+ fEventHandled = TRUE;
+ break;
+ }
+ default:
+ break;
+ }
+ return fEventHandled;
+}
+#else
+/**
+ * Signal handler for cleanly terminating the event loop.
+ */
+static void websrvSignalHandler(int iSignal)
+{
+ NOREF(iSignal);
+ ASMAtomicWriteBool(&g_fKeepRunning, false);
+ com::NativeEventQueue *pQ = com::NativeEventQueue::getMainEventQueue();
+ pQ->interruptEventQueueProcessing();
+}
+#endif
+
+
+/**
+ * Start up the webservice server. This keeps running and waits
+ * for incoming SOAP connections; for each request that comes in,
+ * it calls method implementation code, most of it in the generated
+ * code in methodmaps.cpp.
+ *
+ * @param argc
+ * @param argv[]
+ * @return
+ */
+int main(int argc, char *argv[])
+{
+ // initialize runtime
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+#ifdef RT_OS_WINDOWS
+ ATL::CComModule _Module; /* Required internally by ATL (constructor records instance in global variable). */
+#endif
+
+ // store a log prefix for this thread
+ g_mapThreads[RTThreadSelf()] = "[M ]";
+
+ RTStrmPrintf(g_pStdErr, VBOX_PRODUCT " web service Version " VBOX_VERSION_STRING "\n"
+ "Copyright (C) 2007-" VBOX_C_YEAR " " VBOX_VENDOR "\n");
+
+ int c;
+ const char *pszLogFile = NULL;
+ const char *pszPidFile = NULL;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, g_aOptions, RT_ELEMENTS(g_aOptions), 1, 0 /*fFlags*/);
+ while ((c = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (c)
+ {
+ case 'H':
+ if (!ValueUnion.psz || !*ValueUnion.psz)
+ {
+ /* Normalize NULL/empty string to NULL, which will be
+ * interpreted as "localhost" below. */
+ g_pcszBindToHost = NULL;
+ }
+ else
+ g_pcszBindToHost = ValueUnion.psz;
+ break;
+
+ case 'p':
+ g_uBindToPort = ValueUnion.u32;
+ break;
+
+#ifdef WITH_OPENSSL
+ case 's':
+ g_fSSL = true;
+ break;
+
+ case 'K':
+ g_pcszKeyFile = ValueUnion.psz;
+ break;
+
+ case 'a':
+ if (ValueUnion.psz[0] == '\0')
+ g_pcszPassword = NULL;
+ else
+ {
+ PRTSTREAM StrmIn;
+ if (!strcmp(ValueUnion.psz, "-"))
+ StrmIn = g_pStdIn;
+ else
+ {
+ int vrc = RTStrmOpen(ValueUnion.psz, "r", &StrmIn);
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open password file (%s, %Rrc)", ValueUnion.psz, vrc);
+ }
+ char szPasswd[512];
+ int vrc = RTStrmGetLine(StrmIn, szPasswd, sizeof(szPasswd));
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to read password (%s, %Rrc)", ValueUnion.psz, vrc);
+ g_pcszPassword = RTStrDup(szPasswd);
+ memset(szPasswd, '\0', sizeof(szPasswd));
+ if (StrmIn != g_pStdIn)
+ RTStrmClose(StrmIn);
+ }
+ break;
+
+ case 'c':
+ g_pcszCACert = ValueUnion.psz;
+ break;
+
+ case 'C':
+ g_pcszCAPath = ValueUnion.psz;
+ break;
+
+ case 'D':
+ g_pcszDHFile = ValueUnion.psz;
+ break;
+
+ case 'r':
+ g_pcszRandFile = ValueUnion.psz;
+ break;
+#endif /* WITH_OPENSSL */
+
+ case 't':
+ g_iWatchdogTimeoutSecs = ValueUnion.u32;
+ break;
+
+ case 'i':
+ g_iWatchdogCheckInterval = ValueUnion.u32;
+ break;
+
+ case 'F':
+ pszLogFile = ValueUnion.psz;
+ break;
+
+ case 'R':
+ g_cHistory = ValueUnion.u32;
+ break;
+
+ case 'S':
+ g_uHistoryFileSize = ValueUnion.u64;
+ break;
+
+ case 'I':
+ g_uHistoryFileTime = ValueUnion.u32;
+ break;
+
+ case 'P':
+ pszPidFile = ValueUnion.psz;
+ break;
+
+ case 'T':
+ g_cMaxWorkerThreads = ValueUnion.u32;
+ break;
+
+ case 'k':
+ g_cMaxKeepAlive = ValueUnion.u32;
+ break;
+
+ case 'A':
+ g_pcszAuthentication = ValueUnion.psz;
+ break;
+
+ case 'h':
+ DisplayHelp();
+ return 0;
+
+ case 'v':
+ g_fVerbose = true;
+ break;
+
+#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
+ case 'b':
+ g_fDaemonize = true;
+ break;
+#endif
+ case 'V':
+ RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ return 0;
+
+ default:
+ rc = RTGetOptPrintError(c, &ValueUnion);
+ return rc;
+ }
+ }
+
+ /* create release logger, to stdout */
+ RTERRINFOSTATIC ErrInfo;
+ rc = com::VBoxLogRelCreate("web service", g_fDaemonize ? NULL : pszLogFile,
+ RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
+ "all", "VBOXWEBSRV_RELEASE_LOG",
+ RTLOGDEST_STDOUT, UINT32_MAX /* cMaxEntriesPerGroup */,
+ g_cHistory, g_uHistoryFileTime, g_uHistoryFileSize,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, rc);
+
+#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
+ if (g_fDaemonize)
+ {
+ /* prepare release logging */
+ char szLogFile[RTPATH_MAX];
+
+ if (!pszLogFile || !*pszLogFile)
+ {
+ rc = com::GetVBoxUserHomeDirectory(szLogFile, sizeof(szLogFile));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not get base directory for logging: %Rrc", rc);
+ rc = RTPathAppend(szLogFile, sizeof(szLogFile), "vboxwebsrv.log");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not construct logging path: %Rrc", rc);
+ pszLogFile = szLogFile;
+ }
+
+ rc = RTProcDaemonizeUsingFork(false /* fNoChDir */, false /* fNoClose */, pszPidFile);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to daemonize, rc=%Rrc. exiting.", rc);
+
+ /* create release logger, to file */
+ rc = com::VBoxLogRelCreate("web service", pszLogFile,
+ RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
+ "all", "VBOXWEBSRV_RELEASE_LOG",
+ RTLOGDEST_FILE, UINT32_MAX /* cMaxEntriesPerGroup */,
+ g_cHistory, g_uHistoryFileTime, g_uHistoryFileSize,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, rc);
+ }
+#endif
+
+ // initialize SOAP SSL support if enabled
+#ifdef WITH_OPENSSL
+ if (g_fSSL)
+ soap_ssl_init();
+#endif /* WITH_OPENSSL */
+
+ // initialize COM/XPCOM
+ HRESULT hrc = com::Initialize();
+#ifdef VBOX_WITH_XPCOM
+ if (hrc == NS_ERROR_FILE_ACCESS_DENIED)
+ {
+ char szHome[RTPATH_MAX] = "";
+ com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome));
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "Failed to initialize COM because the global settings directory '%s' is not accessible!", szHome);
+ }
+#endif
+ if (FAILED(hrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to initialize COM! hrc=%Rhrc\n", hrc);
+
+ hrc = g_pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient);
+ if (FAILED(hrc))
+ {
+ RTMsgError("failed to create the VirtualBoxClient object!");
+ com::ErrorInfo info;
+ if (!info.isFullAvailable() && !info.isBasicAvailable())
+ {
+ com::GluePrintRCMessage(hrc);
+ RTMsgError("Most likely, the VirtualBox COM server is not running or failed to start.");
+ }
+ else
+ com::GluePrintErrorInfo(info);
+ return RTEXITCODE_FAILURE;
+ }
+
+ hrc = g_pVirtualBoxClient->COMGETTER(VirtualBox)(g_pVirtualBox.asOutParam());
+ if (FAILED(hrc))
+ {
+ RTMsgError("Failed to get VirtualBox object (rc=%Rhrc)!", hrc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ // set the authentication method if requested
+ if (g_pVirtualBox && g_pcszAuthentication && g_pcszAuthentication[0])
+ {
+ ComPtr<ISystemProperties> pSystemProperties;
+ g_pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ if (pSystemProperties)
+ pSystemProperties->COMSETTER(WebServiceAuthLibrary)(com::Bstr(g_pcszAuthentication).raw());
+ }
+
+ /* VirtualBoxClient events registration. */
+ ComPtr<IEventListener> vboxClientListener;
+ {
+ ComPtr<IEventSource> pES;
+ CHECK_ERROR(g_pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
+ ComObjPtr<VirtualBoxClientEventListenerImpl> clientListener;
+ clientListener.createObject();
+ clientListener->init(new VirtualBoxClientEventListener());
+ vboxClientListener = clientListener;
+ com::SafeArray<VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnVBoxSVCAvailabilityChanged);
+ CHECK_ERROR(pES, RegisterListener(vboxClientListener, ComSafeArrayAsInParam(eventTypes), true));
+ }
+
+ // create the global mutexes
+ g_pAuthLibLockHandle = new util::WriteLockHandle(util::LOCKCLASS_WEBSERVICE);
+ g_pVirtualBoxLockHandle = new util::RWLockHandle(util::LOCKCLASS_WEBSERVICE);
+ g_pWebsessionsLockHandle = new util::WriteLockHandle(util::LOCKCLASS_WEBSERVICE);
+ g_pThreadsLockHandle = new util::RWLockHandle(util::LOCKCLASS_OBJECTSTATE);
+
+ // SOAP queue pumper thread
+ RTTHREAD threadQPumper;
+ rc = RTThreadCreate(&threadQPumper,
+ fntQPumper,
+ NULL, // pvUser
+ 0, // cbStack (default)
+ RTTHREADTYPE_MAIN_WORKER,
+ RTTHREADFLAGS_WAITABLE,
+ "SQPmp");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot start SOAP queue pumper thread: %Rrc", rc);
+
+ // watchdog thread
+ RTTHREAD threadWatchdog = NIL_RTTHREAD;
+ if (g_iWatchdogTimeoutSecs > 0)
+ {
+ // start our watchdog thread
+ rc = RTThreadCreate(&threadWatchdog,
+ fntWatchdog,
+ NULL,
+ 0,
+ RTTHREADTYPE_MAIN_WORKER,
+ RTTHREADFLAGS_WAITABLE,
+ "Watchdog");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot start watchdog thread: %Rrc", rc);
+ }
+
+#ifdef RT_OS_WINDOWS
+ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)websrvSignalHandler, TRUE /* Add handler */))
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ RTMsgError("Unable to install console control handler, rc=%Rrc\n", rc);
+ }
+#else
+ signal(SIGINT, websrvSignalHandler);
+ signal(SIGTERM, websrvSignalHandler);
+# ifdef SIGBREAK
+ signal(SIGBREAK, websrvSignalHandler);
+# endif
+#endif
+
+ com::NativeEventQueue *pQ = com::NativeEventQueue::getMainEventQueue();
+ while (g_fKeepRunning)
+ {
+ // we have to process main event queue
+ WEBDEBUG(("Pumping COM event queue\n"));
+ rc = pQ->processEventQueue(RT_INDEFINITE_WAIT);
+ if (RT_FAILURE(rc))
+ RTMsgError("processEventQueue -> %Rrc", rc);
+ }
+
+ LogRel(("requested termination, cleaning up\n"));
+
+#ifdef RT_OS_WINDOWS
+ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)websrvSignalHandler, FALSE /* Remove handler */))
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ RTMsgError("Unable to remove console control handler, rc=%Rrc\n", rc);
+ }
+#else
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+# ifdef SIGBREAK
+ signal(SIGBREAK, SIG_DFL);
+# endif
+#endif
+
+#ifndef RT_OS_WINDOWS
+ RTThreadPoke(threadQPumper);
+#endif
+ RTThreadWait(threadQPumper, 30000, NULL);
+ if (threadWatchdog != NIL_RTTHREAD)
+ {
+#ifndef RT_OS_WINDOWS
+ RTThreadPoke(threadWatchdog);
+#endif
+ RTThreadWait(threadWatchdog, g_iWatchdogCheckInterval * 1000 + 10000, NULL);
+ }
+
+ /* VirtualBoxClient events unregistration. */
+ if (vboxClientListener)
+ {
+ ComPtr<IEventSource> pES;
+ CHECK_ERROR(g_pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
+ if (!pES.isNull())
+ CHECK_ERROR(pES, UnregisterListener(vboxClientListener));
+ vboxClientListener.setNull();
+ }
+
+ {
+ util::AutoWriteLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
+ g_pVirtualBox.setNull();
+ }
+ {
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+ WebsessionsMapIterator it = g_mapWebsessions.begin(),
+ itEnd = g_mapWebsessions.end();
+ while (it != itEnd)
+ {
+ WebServiceSession *pWebsession = it->second;
+ WEBDEBUG(("SVC unavailable: websession %#llx stale, deleting\n", pWebsession->getID()));
+ delete pWebsession;
+ it = g_mapWebsessions.begin();
+ }
+ }
+ g_pVirtualBoxClient.setNull();
+
+ com::Shutdown();
+
+ return 0;
+}
+
+/****************************************************************************
+ *
+ * Watchdog thread
+ *
+ ****************************************************************************/
+
+/**
+ * Watchdog thread, runs in the background while the webservice is alive.
+ *
+ * This gets started by main() and runs in the background to check all websessions
+ * for whether they have been no requests in a configurable timeout period. In
+ * that case, the websession is automatically logged off.
+ */
+static DECLCALLBACK(int) fntWatchdog(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf, pvUser);
+
+ // store a log prefix for this thread
+ util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
+ g_mapThreads[RTThreadSelf()] = "[W ]";
+ thrLock.release();
+
+ WEBDEBUG(("Watchdog thread started\n"));
+
+ uint32_t tNextStat = 0;
+
+ while (g_fKeepRunning)
+ {
+ WEBDEBUG(("Watchdog: sleeping %d seconds\n", g_iWatchdogCheckInterval));
+ RTThreadSleep(g_iWatchdogCheckInterval * 1000);
+
+ uint32_t tNow = RTTimeProgramSecTS();
+
+ // we're messing with websessions, so lock them
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+ WEBDEBUG(("Watchdog: checking %d websessions\n", g_mapWebsessions.size()));
+
+ WebsessionsMapIterator it = g_mapWebsessions.begin(),
+ itEnd = g_mapWebsessions.end();
+ while (it != itEnd)
+ {
+ WebServiceSession *pWebsession = it->second;
+ WEBDEBUG(("Watchdog: tNow: %d, websession timestamp: %d\n", tNow, pWebsession->getLastObjectLookup()));
+ if (tNow > pWebsession->getLastObjectLookup() + g_iWatchdogTimeoutSecs)
+ {
+ WEBDEBUG(("Watchdog: websession %#llx timed out, deleting\n", pWebsession->getID()));
+ delete pWebsession;
+ it = g_mapWebsessions.begin();
+ }
+ else
+ ++it;
+ }
+
+ // re-set the authentication method in case it has been changed
+ if (g_pVirtualBox && g_pcszAuthentication && g_pcszAuthentication[0])
+ {
+ ComPtr<ISystemProperties> pSystemProperties;
+ g_pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ if (pSystemProperties)
+ pSystemProperties->COMSETTER(WebServiceAuthLibrary)(com::Bstr(g_pcszAuthentication).raw());
+ }
+
+ // Log some MOR usage statistics every 5 minutes, but only if there's
+ // something worth logging (at least one reference or a transition to
+ // zero references). Avoids useless log spamming in idle webservice.
+ if (tNow >= tNextStat)
+ {
+ size_t cMOR = 0;
+ it = g_mapWebsessions.begin();
+ itEnd = g_mapWebsessions.end();
+ while (it != itEnd)
+ {
+ cMOR += it->second->CountRefs();
+ ++it;
+ }
+ static bool fLastZero = false;
+ if (cMOR || !fLastZero)
+ LogRel(("Statistics: %zu websessions, %zu references\n",
+ g_mapWebsessions.size(), cMOR));
+ fLastZero = (cMOR == 0);
+ while (tNextStat <= tNow)
+ tNextStat += 5 * 60; /* 5 minutes */
+ }
+ }
+
+ thrLock.acquire();
+ g_mapThreads.erase(RTThreadSelf());
+
+ LogRel(("ending Watchdog thread\n"));
+ return 0;
+}
+
+/****************************************************************************
+ *
+ * SOAP exceptions
+ *
+ ****************************************************************************/
+
+/**
+ * Helper function to raise a SOAP fault. Called by the other helper
+ * functions, which raise specific SOAP faults.
+ *
+ * @param soap
+ * @param pcsz
+ * @param extype
+ * @param ex
+ */
+static void RaiseSoapFault(struct soap *soap,
+ const char *pcsz,
+ int extype,
+ void *ex)
+{
+ // raise the fault
+ soap_sender_fault(soap, pcsz, NULL);
+
+ struct SOAP_ENV__Detail *pDetail = (struct SOAP_ENV__Detail*)soap_malloc(soap, sizeof(struct SOAP_ENV__Detail));
+
+ // without the following, gSOAP crashes miserably when sending out the
+ // data because it will try to serialize all fields (stupid documentation)
+ memset(pDetail, 0, sizeof(struct SOAP_ENV__Detail));
+
+ // fill extended info depending on SOAP version
+ if (soap->version == 2) // SOAP 1.2 is used
+ {
+ soap->fault->SOAP_ENV__Detail = pDetail;
+ soap->fault->SOAP_ENV__Detail->__type = extype;
+ soap->fault->SOAP_ENV__Detail->fault = ex;
+ soap->fault->SOAP_ENV__Detail->__any = NULL; // no other XML data
+ }
+ else
+ {
+ soap->fault->detail = pDetail;
+ soap->fault->detail->__type = extype;
+ soap->fault->detail->fault = ex;
+ soap->fault->detail->__any = NULL; // no other XML data
+ }
+}
+
+/**
+ * Raises a SOAP fault that signals that an invalid object was passed.
+ *
+ * @param soap
+ * @param obj
+ */
+void RaiseSoapInvalidObjectFault(struct soap *soap,
+ WSDLT_ID obj)
+{
+ _vbox__InvalidObjectFault *ex = soap_new__vbox__InvalidObjectFault(soap, 1);
+ ex->badObjectID = obj;
+
+ std::string str("VirtualBox error: ");
+ str += "Invalid managed object reference \"" + obj + "\"";
+
+ RaiseSoapFault(soap,
+ str.c_str(),
+ SOAP_TYPE__vbox__InvalidObjectFault,
+ ex);
+}
+
+/**
+ * Return a safe C++ string from the given COM string,
+ * without crashing if the COM string is empty.
+ * @param bstr
+ * @return
+ */
+std::string ConvertComString(const com::Bstr &bstr)
+{
+ com::Utf8Str ustr(bstr);
+ return ustr.c_str(); /// @todo r=dj since the length is known, we can probably use a better std::string allocator
+}
+
+/**
+ * Return a safe C++ string from the given COM UUID,
+ * without crashing if the UUID is empty.
+ * @param uuid
+ * @return
+ */
+std::string ConvertComString(const com::Guid &uuid)
+{
+ com::Utf8Str ustr(uuid.toString());
+ return ustr.c_str(); /// @todo r=dj since the length is known, we can probably use a better std::string allocator
+}
+
+/** Code to handle string <-> byte arrays base64 conversion. */
+std::string Base64EncodeByteArray(ComSafeArrayIn(BYTE, aData))
+{
+
+ com::SafeArray<BYTE> sfaData(ComSafeArrayInArg(aData));
+ ssize_t cbData = sfaData.size();
+
+ if (cbData == 0)
+ return "";
+
+ ssize_t cchOut = RTBase64EncodedLength(cbData);
+
+ RTCString aStr;
+
+ aStr.reserve(cchOut+1);
+ int rc = RTBase64Encode(sfaData.raw(), cbData,
+ aStr.mutableRaw(), aStr.capacity(),
+ NULL);
+ AssertRC(rc);
+ aStr.jolt();
+
+ return aStr.c_str();
+}
+
+#define DECODE_STR_MAX _1M
+void Base64DecodeByteArray(struct soap *soap, const std::string& aStr, ComSafeArrayOut(BYTE, aData), const WSDLT_ID &idThis, const char *pszMethodName, IUnknown *pObj, const com::Guid &iid)
+{
+ const char* pszStr = aStr.c_str();
+ ssize_t cbOut = RTBase64DecodedSize(pszStr, NULL);
+
+ if (cbOut > DECODE_STR_MAX)
+ {
+ LogRel(("Decode string too long.\n"));
+ RaiseSoapRuntimeFault(soap, idThis, pszMethodName, E_INVALIDARG, pObj, iid);
+ }
+
+ com::SafeArray<BYTE> result(cbOut);
+ int rc = RTBase64Decode(pszStr, result.raw(), cbOut, NULL, NULL);
+ if (FAILED(rc))
+ {
+ LogRel(("String Decoding Failed. Error code: %Rrc\n", rc));
+ RaiseSoapRuntimeFault(soap, idThis, pszMethodName, E_INVALIDARG, pObj, iid);
+ }
+
+ result.detachTo(ComSafeArrayOutArg(aData));
+}
+
+/**
+ * Raises a SOAP runtime fault.
+ *
+ * @param soap
+ * @param idThis
+ * @param pcszMethodName
+ * @param apirc
+ * @param pObj
+ * @param iid
+ */
+void RaiseSoapRuntimeFault(struct soap *soap,
+ const WSDLT_ID &idThis,
+ const char *pcszMethodName,
+ HRESULT apirc,
+ IUnknown *pObj,
+ const com::Guid &iid)
+{
+ com::ErrorInfo info(pObj, iid.ref());
+
+ WEBDEBUG((" error, raising SOAP exception\n"));
+
+ LogRel(("API method name: %s\n", pcszMethodName));
+ LogRel(("API return code: %#10lx (%Rhrc)\n", apirc, apirc));
+ if (info.isFullAvailable() || info.isBasicAvailable())
+ {
+ const com::ErrorInfo *pInfo = &info;
+ do
+ {
+ LogRel(("COM error info result code: %#10lx (%Rhrc)\n", pInfo->getResultCode(), pInfo->getResultCode()));
+ LogRel(("COM error info text: %ls\n", pInfo->getText().raw()));
+
+ pInfo = pInfo->getNext();
+ }
+ while (pInfo);
+ }
+
+ // compose descriptive message
+ com::Utf8Str str = com::Utf8StrFmt("VirtualBox error: rc=%#lx", apirc);
+ if (info.isFullAvailable() || info.isBasicAvailable())
+ {
+ const com::ErrorInfo *pInfo = &info;
+ do
+ {
+ str += com::Utf8StrFmt(" %ls (%#lx)", pInfo->getText().raw(), pInfo->getResultCode());
+ pInfo = pInfo->getNext();
+ }
+ while (pInfo);
+ }
+
+ // allocate our own soap fault struct
+ _vbox__RuntimeFault *ex = soap_new__vbox__RuntimeFault(soap, 1);
+ ComPtr<IVirtualBoxErrorInfo> pVirtualBoxErrorInfo;
+ info.getVirtualBoxErrorInfo(pVirtualBoxErrorInfo);
+ ex->resultCode = apirc;
+ ex->returnval = createOrFindRefFromComPtr(idThis, g_pcszIVirtualBoxErrorInfo, pVirtualBoxErrorInfo);
+
+ RaiseSoapFault(soap,
+ str.c_str(),
+ SOAP_TYPE__vbox__RuntimeFault,
+ ex);
+}
+
+/****************************************************************************
+ *
+ * splitting and merging of object IDs
+ *
+ ****************************************************************************/
+
+/**
+ * Splits a managed object reference (in string form, as passed in from a SOAP
+ * method call) into two integers for websession and object IDs, respectively.
+ *
+ * @param id
+ * @param pWebsessId
+ * @param pObjId
+ * @return
+ */
+static bool SplitManagedObjectRef(const WSDLT_ID &id,
+ uint64_t *pWebsessId,
+ uint64_t *pObjId)
+{
+ // 64-bit numbers in hex have 16 digits; hence
+ // the object-ref string must have 16 + "-" + 16 characters
+ if ( id.length() == 33
+ && id[16] == '-'
+ )
+ {
+ char psz[34];
+ memcpy(psz, id.c_str(), 34);
+ psz[16] = '\0';
+ if (pWebsessId)
+ RTStrToUInt64Full(psz, 16, pWebsessId);
+ if (pObjId)
+ RTStrToUInt64Full(psz + 17, 16, pObjId);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Creates a managed object reference (in string form) from
+ * two integers representing a websession and object ID, respectively.
+ *
+ * @param sz Buffer with at least 34 bytes space to receive MOR string.
+ * @param websessId
+ * @param objId
+ * @return
+ */
+static void MakeManagedObjectRef(char *sz,
+ uint64_t websessId,
+ uint64_t objId)
+{
+ RTStrFormatNumber(sz, websessId, 16, 16, 0, RTSTR_F_64BIT | RTSTR_F_ZEROPAD);
+ sz[16] = '-';
+ RTStrFormatNumber(sz + 17, objId, 16, 16, 0, RTSTR_F_64BIT | RTSTR_F_ZEROPAD);
+}
+
+/****************************************************************************
+ *
+ * class WebServiceSession
+ *
+ ****************************************************************************/
+
+class WebServiceSessionPrivate
+{
+ public:
+ ManagedObjectsMapById _mapManagedObjectsById;
+ ManagedObjectsMapByPtr _mapManagedObjectsByPtr;
+};
+
+/**
+ * Constructor for the websession object.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
+ */
+WebServiceSession::WebServiceSession()
+ : _uNextObjectID(1), // avoid 0 for no real reason
+ _fDestructing(false),
+ _tLastObjectLookup(0)
+{
+ _pp = new WebServiceSessionPrivate;
+ _uWebsessionID = RTRandU64();
+
+ // register this websession globally
+ Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
+ g_mapWebsessions[_uWebsessionID] = this;
+}
+
+/**
+ * Destructor. Cleans up and destroys all contained managed object references on the way.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
+ */
+WebServiceSession::~WebServiceSession()
+{
+ // delete us from global map first so we can't be found
+ // any more while we're cleaning up
+ Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
+ g_mapWebsessions.erase(_uWebsessionID);
+
+ // notify ManagedObjectRef destructor so it won't
+ // remove itself from the maps; this avoids rebalancing
+ // the map's tree on every delete as well
+ _fDestructing = true;
+
+ ManagedObjectsIteratorById it,
+ end = _pp->_mapManagedObjectsById.end();
+ for (it = _pp->_mapManagedObjectsById.begin();
+ it != end;
+ ++it)
+ {
+ ManagedObjectRef *pRef = it->second;
+ delete pRef; // this frees the contained ComPtr as well
+ }
+
+ delete _pp;
+}
+
+/**
+ * Authenticate the username and password against an authentication authority.
+ *
+ * @return 0 if the user was successfully authenticated, or an error code
+ * otherwise.
+ */
+int WebServiceSession::authenticate(const char *pcszUsername,
+ const char *pcszPassword,
+ IVirtualBox **ppVirtualBox)
+{
+ int rc = VERR_WEB_NOT_AUTHENTICATED;
+ ComPtr<IVirtualBox> pVirtualBox;
+ {
+ util::AutoReadLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
+ pVirtualBox = g_pVirtualBox;
+ }
+ if (pVirtualBox.isNull())
+ return rc;
+ pVirtualBox.queryInterfaceTo(ppVirtualBox);
+
+ util::AutoReadLock lock(g_pAuthLibLockHandle COMMA_LOCKVAL_SRC_POS);
+
+ static bool fAuthLibLoaded = false;
+ static PAUTHENTRY pfnAuthEntry = NULL;
+ static PAUTHENTRY2 pfnAuthEntry2 = NULL;
+ static PAUTHENTRY3 pfnAuthEntry3 = NULL;
+
+ if (!fAuthLibLoaded)
+ {
+ // retrieve authentication library from system properties
+ ComPtr<ISystemProperties> systemProperties;
+ pVirtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam());
+
+ com::Bstr authLibrary;
+ systemProperties->COMGETTER(WebServiceAuthLibrary)(authLibrary.asOutParam());
+ com::Utf8Str filename = authLibrary;
+
+ LogRel(("External authentication library is '%ls'\n", authLibrary.raw()));
+
+ if (filename == "null")
+ // authentication disabled, let everyone in:
+ fAuthLibLoaded = true;
+ else
+ {
+ RTLDRMOD hlibAuth = 0;
+ do
+ {
+ if (RTPathHavePath(filename.c_str()))
+ rc = RTLdrLoad(filename.c_str(), &hlibAuth);
+ else
+ rc = RTLdrLoadAppPriv(filename.c_str(), &hlibAuth);
+
+ if (RT_FAILURE(rc))
+ {
+ WEBDEBUG(("%s() Failed to load external authentication library '%s'. Error code: %Rrc\n",
+ __FUNCTION__, filename.c_str(), rc));
+ break;
+ }
+
+ if (RT_FAILURE(rc = RTLdrGetSymbol(hlibAuth, AUTHENTRY3_NAME, (void**)&pfnAuthEntry3)))
+ {
+ WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n",
+ __FUNCTION__, AUTHENTRY3_NAME, rc));
+
+ if (RT_FAILURE(rc = RTLdrGetSymbol(hlibAuth, AUTHENTRY2_NAME, (void**)&pfnAuthEntry2)))
+ {
+ WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n",
+ __FUNCTION__, AUTHENTRY2_NAME, rc));
+
+ if (RT_FAILURE(rc = RTLdrGetSymbol(hlibAuth, AUTHENTRY_NAME, (void**)&pfnAuthEntry)))
+ WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n",
+ __FUNCTION__, AUTHENTRY_NAME, rc));
+ }
+ }
+
+ if (pfnAuthEntry || pfnAuthEntry2 || pfnAuthEntry3)
+ fAuthLibLoaded = true;
+
+ } while (0);
+ }
+ }
+
+ if (strlen(pcszUsername) >= _1K)
+ {
+ LogRel(("Access denied, excessive username length: %zu\n", strlen(pcszUsername)));
+ rc = VERR_WEB_NOT_AUTHENTICATED;
+ }
+ else if (strlen(pcszPassword) >= _1K)
+ {
+ LogRel(("Access denied, excessive password length: %zu\n", strlen(pcszPassword)));
+ rc = VERR_WEB_NOT_AUTHENTICATED;
+ }
+ else if (pfnAuthEntry3 || pfnAuthEntry2 || pfnAuthEntry)
+ {
+ const char *pszFn;
+ AuthResult result;
+ if (pfnAuthEntry3)
+ {
+ result = pfnAuthEntry3("webservice", NULL, AuthGuestNotAsked, pcszUsername, pcszPassword, NULL, true, 0);
+ pszFn = AUTHENTRY3_NAME;
+ }
+ else if (pfnAuthEntry2)
+ {
+ result = pfnAuthEntry2(NULL, AuthGuestNotAsked, pcszUsername, pcszPassword, NULL, true, 0);
+ pszFn = AUTHENTRY2_NAME;
+ }
+ else
+ {
+ result = pfnAuthEntry(NULL, AuthGuestNotAsked, pcszUsername, pcszPassword, NULL);
+ pszFn = AUTHENTRY_NAME;
+ }
+ WEBDEBUG(("%s(): result of %s('%s', [%d]): %d (%s)\n",
+ __FUNCTION__, pszFn, pcszUsername, strlen(pcszPassword), result, decodeAuthResult(result)));
+ if (result == AuthResultAccessGranted)
+ {
+ LogRel(("Access for user '%s' granted\n", pcszUsername));
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ if (result == AuthResultAccessDenied)
+ LogRel(("Access for user '%s' denied\n", pcszUsername));
+ rc = VERR_WEB_NOT_AUTHENTICATED;
+ }
+ }
+ else if (fAuthLibLoaded)
+ {
+ // fAuthLibLoaded = true but all pointers are NULL:
+ // The authlib was "null" and auth was disabled
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ WEBDEBUG(("Could not resolve AuthEntry, VRDPAuth2 or VRDPAuth entry point"));
+ rc = VERR_WEB_NOT_AUTHENTICATED;
+ }
+
+ lock.release();
+
+ return rc;
+}
+
+/**
+ * Look up, in this websession, whether a ManagedObjectRef has already been
+ * created for the given COM pointer.
+ *
+ * Note how we require that a ComPtr<IUnknown> is passed, which causes a
+ * queryInterface call when the caller passes in a different type, since
+ * a ComPtr<IUnknown> will point to something different than a
+ * ComPtr<IVirtualBox>, for example. As we store the ComPtr<IUnknown> in
+ * our private hash table, we must search for one too.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
+ *
+ * @param pObject pointer to a COM object.
+ * @return The existing ManagedObjectRef that represents the COM object, or NULL if there's none yet.
+ */
+ManagedObjectRef* WebServiceSession::findRefFromPtr(const IUnknown *pObject)
+{
+ Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
+
+ uintptr_t ulp = (uintptr_t)pObject;
+ // WEBDEBUG((" %s: looking up %#lx\n", __FUNCTION__, ulp));
+ ManagedObjectsIteratorByPtr it = _pp->_mapManagedObjectsByPtr.find(ulp);
+ if (it != _pp->_mapManagedObjectsByPtr.end())
+ {
+ ManagedObjectRef *pRef = it->second;
+ WEBDEBUG((" %s: found existing ref %s (%s) for COM obj %#lx\n", __FUNCTION__, pRef->getWSDLID().c_str(), pRef->getInterfaceName(), ulp));
+ return pRef;
+ }
+
+ return NULL;
+}
+
+/**
+ * Static method which attempts to find the websession for which the given
+ * managed object reference was created, by splitting the reference into the
+ * websession and object IDs and then looking up the websession object.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle in read mode.
+ *
+ * @param id Managed object reference (with combined websession and object IDs).
+ * @return
+ */
+WebServiceSession *WebServiceSession::findWebsessionFromRef(const WSDLT_ID &id)
+{
+ Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
+
+ WebServiceSession *pWebsession = NULL;
+ uint64_t websessId;
+ if (SplitManagedObjectRef(id,
+ &websessId,
+ NULL))
+ {
+ WebsessionsMapIterator it = g_mapWebsessions.find(websessId);
+ if (it != g_mapWebsessions.end())
+ pWebsession = it->second;
+ }
+ return pWebsession;
+}
+
+/**
+ * Touches the websession to prevent it from timing out.
+ *
+ * Each websession has an internal timestamp that records the last request made
+ * to it from the client that started it. If no request was made within a
+ * configurable timeframe, then the client is logged off automatically,
+ * by calling IWebsessionManager::logoff()
+ */
+void WebServiceSession::touch()
+{
+ _tLastObjectLookup = RTTimeProgramSecTS();
+}
+
+/**
+ * Counts the number of managed object references in this websession.
+ */
+size_t WebServiceSession::CountRefs()
+{
+ return _pp->_mapManagedObjectsById.size();
+}
+
+
+/****************************************************************************
+ *
+ * class ManagedObjectRef
+ *
+ ****************************************************************************/
+
+/**
+ * Constructor, which assigns a unique ID to this managed object
+ * reference and stores it in two hashes (living in the associated
+ * WebServiceSession object):
+ *
+ * a) _mapManagedObjectsById, which maps ManagedObjectID's to
+ * instances of this class; this hash is then used by the
+ * findObjectFromRef() template function in vboxweb.h
+ * to quickly retrieve the COM object from its managed
+ * object ID (mostly in the context of the method mappers
+ * in methodmaps.cpp, when a web service client passes in
+ * a managed object ID);
+ *
+ * b) _mapManagedObjectsByPtr, which maps COM pointers to
+ * instances of this class; this hash is used by
+ * createRefFromObject() to quickly figure out whether an
+ * instance already exists for a given COM pointer.
+ *
+ * This constructor calls AddRef() on the given COM object, and
+ * the destructor will call Release(). We require two input pointers
+ * for that COM object, one generic IUnknown* pointer which is used
+ * as the map key, and a specific interface pointer (e.g. IMachine*)
+ * which must support the interface given in guidInterface. All
+ * three values are returned by getPtr(), which gives future callers
+ * a chance to reuse the specific interface pointer without having
+ * to call QueryInterface, which can be expensive.
+ *
+ * This does _not_ check whether another instance already
+ * exists in the hash. This gets called only from the
+ * createOrFindRefFromComPtr() template function in vboxweb.h, which
+ * does perform that check.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
+ *
+ * @param websession Websession to which the MOR will be added.
+ * @param pobjUnknown Pointer to IUnknown* interface for the COM object; this will be used in the hashes.
+ * @param pobjInterface Pointer to a specific interface for the COM object, described by guidInterface.
+ * @param guidInterface Interface which pobjInterface points to.
+ * @param pcszInterface String representation of that interface (e.g. "IMachine") for readability and logging.
+ */
+ManagedObjectRef::ManagedObjectRef(WebServiceSession &websession,
+ IUnknown *pobjUnknown,
+ void *pobjInterface,
+ const com::Guid &guidInterface,
+ const char *pcszInterface)
+ : _websession(websession),
+ _pobjUnknown(pobjUnknown),
+ _pobjInterface(pobjInterface),
+ _guidInterface(guidInterface),
+ _pcszInterface(pcszInterface)
+{
+ Assert(pobjUnknown);
+ Assert(pobjInterface);
+
+ // keep both stubs alive while this MOR exists (matching Release() calls are in destructor)
+ uint32_t cRefs1 = pobjUnknown->AddRef();
+ uint32_t cRefs2 = ((IUnknown*)pobjInterface)->AddRef();
+ _ulp = (uintptr_t)pobjUnknown;
+
+ Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
+ _id = websession.createObjectID();
+ // and count globally
+ ULONG64 cTotal = ++g_cManagedObjects; // raise global count and make a copy for the debug message below
+
+ char sz[34];
+ MakeManagedObjectRef(sz, websession._uWebsessionID, _id);
+ _strID = sz;
+
+ websession._pp->_mapManagedObjectsById[_id] = this;
+ websession._pp->_mapManagedObjectsByPtr[_ulp] = this;
+
+ websession.touch();
+
+ WEBDEBUG((" * %s: MOR created for %s*=%#p (IUnknown*=%#p; COM refcount now %RI32/%RI32), new ID is %#llx; now %lld objects total\n",
+ __FUNCTION__,
+ pcszInterface,
+ pobjInterface,
+ pobjUnknown,
+ cRefs1,
+ cRefs2,
+ _id,
+ cTotal));
+}
+
+/**
+ * Destructor; removes the instance from the global hash of
+ * managed objects. Calls Release() on the contained COM object.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
+ */
+ManagedObjectRef::~ManagedObjectRef()
+{
+ Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
+ ULONG64 cTotal = --g_cManagedObjects;
+
+ Assert(_pobjUnknown);
+ Assert(_pobjInterface);
+
+ // we called AddRef() on both interfaces, so call Release() on
+ // both as well, but in reverse order
+ uint32_t cRefs2 = ((IUnknown*)_pobjInterface)->Release();
+ uint32_t cRefs1 = _pobjUnknown->Release();
+ WEBDEBUG((" * %s: deleting MOR for ID %#llx (%s; COM refcount now %RI32/%RI32); now %lld objects total\n", __FUNCTION__, _id, _pcszInterface, cRefs1, cRefs2, cTotal));
+
+ // if we're being destroyed from the websession's destructor,
+ // then that destructor is iterating over the maps, so
+ // don't remove us there! (data integrity + speed)
+ if (!_websession._fDestructing)
+ {
+ WEBDEBUG((" * %s: removing from websession maps\n", __FUNCTION__));
+ _websession._pp->_mapManagedObjectsById.erase(_id);
+ if (_websession._pp->_mapManagedObjectsByPtr.erase(_ulp) != 1)
+ WEBDEBUG((" WARNING: could not find %#llx in _mapManagedObjectsByPtr\n", _ulp));
+ }
+}
+
+/**
+ * Static helper method for findObjectFromRef() template that actually
+ * looks up the object from a given integer ID.
+ *
+ * This has been extracted into this non-template function to reduce
+ * code bloat as we have the actual STL map lookup only in this function.
+ *
+ * This also "touches" the timestamp in the websession whose ID is encoded
+ * in the given integer ID, in order to prevent the websession from timing
+ * out.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
+ *
+ * @param id
+ * @param pRef
+ * @param fNullAllowed
+ * @return
+ */
+int ManagedObjectRef::findRefFromId(const WSDLT_ID &id,
+ ManagedObjectRef **pRef,
+ bool fNullAllowed)
+{
+ int rc = 0;
+
+ do
+ {
+ // allow NULL (== empty string) input reference, which should return a NULL pointer
+ if (!id.length() && fNullAllowed)
+ {
+ *pRef = NULL;
+ return 0;
+ }
+
+ uint64_t websessId;
+ uint64_t objId;
+ WEBDEBUG((" %s(): looking up objref %s\n", __FUNCTION__, id.c_str()));
+ if (!SplitManagedObjectRef(id,
+ &websessId,
+ &objId))
+ {
+ rc = VERR_WEB_INVALID_MANAGED_OBJECT_REFERENCE;
+ break;
+ }
+
+ WebsessionsMapIterator it = g_mapWebsessions.find(websessId);
+ if (it == g_mapWebsessions.end())
+ {
+ WEBDEBUG((" %s: cannot find websession for objref %s\n", __FUNCTION__, id.c_str()));
+ rc = VERR_WEB_INVALID_SESSION_ID;
+ break;
+ }
+
+ WebServiceSession *pWebsession = it->second;
+ // "touch" websession to prevent it from timing out
+ pWebsession->touch();
+
+ ManagedObjectsIteratorById iter = pWebsession->_pp->_mapManagedObjectsById.find(objId);
+ if (iter == pWebsession->_pp->_mapManagedObjectsById.end())
+ {
+ WEBDEBUG((" %s: cannot find comobj for objref %s\n", __FUNCTION__, id.c_str()));
+ rc = VERR_WEB_INVALID_OBJECT_ID;
+ break;
+ }
+
+ *pRef = iter->second;
+
+ } while (0);
+
+ return rc;
+}
+
+/****************************************************************************
+ *
+ * interface IManagedObjectRef
+ *
+ ****************************************************************************/
+
+/**
+ * This is the hard-coded implementation for the IManagedObjectRef::getInterfaceName()
+ * that our WSDL promises to our web service clients. This method returns a
+ * string describing the interface that this managed object reference
+ * supports, e.g. "IMachine".
+ *
+ * @param soap
+ * @param req
+ * @param resp
+ * @return
+ */
+int __vbox__IManagedObjectRef_USCOREgetInterfaceName(
+ struct soap *soap,
+ _vbox__IManagedObjectRef_USCOREgetInterfaceName *req,
+ _vbox__IManagedObjectRef_USCOREgetInterfaceNameResponse *resp)
+{
+ RT_NOREF(soap);
+ HRESULT rc = S_OK;
+ WEBDEBUG(("-- entering %s\n", __FUNCTION__));
+
+ do
+ {
+ // findRefFromId require the lock
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+
+ ManagedObjectRef *pRef;
+ if (!ManagedObjectRef::findRefFromId(req->_USCOREthis, &pRef, false))
+ resp->returnval = pRef->getInterfaceName();
+
+ } while (0);
+
+ WEBDEBUG(("-- leaving %s, rc: %#lx\n", __FUNCTION__, rc));
+ if (FAILED(rc))
+ return SOAP_FAULT;
+ return SOAP_OK;
+}
+
+/**
+ * This is the hard-coded implementation for the IManagedObjectRef::release()
+ * that our WSDL promises to our web service clients. This method releases
+ * a managed object reference and removes it from our stacks.
+ *
+ * @param soap
+ * @param req
+ * @param resp
+ * @return
+ */
+int __vbox__IManagedObjectRef_USCORErelease(
+ struct soap *soap,
+ _vbox__IManagedObjectRef_USCORErelease *req,
+ _vbox__IManagedObjectRef_USCOREreleaseResponse *resp)
+{
+ RT_NOREF(resp);
+ HRESULT rc;
+ WEBDEBUG(("-- entering %s\n", __FUNCTION__));
+
+ {
+ // findRefFromId and the delete call below require the lock
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+
+ ManagedObjectRef *pRef;
+ rc = ManagedObjectRef::findRefFromId(req->_USCOREthis, &pRef, false);
+ if (rc == S_OK)
+ {
+ WEBDEBUG((" found reference; deleting!\n"));
+ // this removes the object from all stacks; since
+ // there's a ComPtr<> hidden inside the reference,
+ // this should also invoke Release() on the COM
+ // object
+ delete pRef;
+ }
+ else
+ RaiseSoapInvalidObjectFault(soap, req->_USCOREthis);
+ }
+
+ WEBDEBUG(("-- leaving %s, rc: %#lx\n", __FUNCTION__, rc));
+ if (FAILED(rc))
+ return SOAP_FAULT;
+ return SOAP_OK;
+}
+
+/****************************************************************************
+ *
+ * interface IWebsessionManager
+ *
+ ****************************************************************************/
+
+/**
+ * Hard-coded implementation for IWebsessionManager::logon. As opposed to the underlying
+ * COM API, this is the first method that a webservice client must call before the
+ * webservice will do anything useful.
+ *
+ * This returns a managed object reference to the global IVirtualBox object; into this
+ * reference a websession ID is encoded which remains constant with all managed object
+ * references returned by other methods.
+ *
+ * When the webservice client is done, it should call IWebsessionManager::logoff. This
+ * will clean up internally (destroy all remaining managed object references and
+ * related COM objects used internally).
+ *
+ * After logon, an internal timeout ensures that if the webservice client does not
+ * call any methods, after a configurable number of seconds, the webservice will log
+ * off the client automatically. This is to ensure that the webservice does not
+ * drown in managed object references and eventually deny service. Still, it is
+ * a much better solution, both for performance and cleanliness, for the webservice
+ * client to clean up itself.
+ *
+ * @param soap
+ * @param req
+ * @param resp
+ * @return
+ */
+int __vbox__IWebsessionManager_USCORElogon(
+ struct soap *soap,
+ _vbox__IWebsessionManager_USCORElogon *req,
+ _vbox__IWebsessionManager_USCORElogonResponse *resp)
+{
+ RT_NOREF(soap);
+ HRESULT rc = S_OK;
+ WEBDEBUG(("-- entering %s\n", __FUNCTION__));
+
+ do
+ {
+ // WebServiceSession constructor tinkers with global MOR map and requires a write lock
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+
+ // create new websession; the constructor stores the new websession
+ // in the global map automatically
+ WebServiceSession *pWebsession = new WebServiceSession();
+ ComPtr<IVirtualBox> pVirtualBox;
+
+ // authenticate the user
+ if (!(pWebsession->authenticate(req->username.c_str(),
+ req->password.c_str(),
+ pVirtualBox.asOutParam())))
+ {
+ // fake up a "root" MOR for this websession
+ char sz[34];
+ MakeManagedObjectRef(sz, pWebsession->getID(), 0ULL);
+ WSDLT_ID id = sz;
+
+ // in the new websession, create a managed object reference (MOR) for the
+ // global VirtualBox object; this encodes the websession ID in the MOR so
+ // that it will be implicitly be included in all future requests of this
+ // webservice client
+ resp->returnval = createOrFindRefFromComPtr(id, g_pcszIVirtualBox, pVirtualBox);
+ WEBDEBUG(("VirtualBox object ref is %s\n", resp->returnval.c_str()));
+ }
+ else
+ rc = E_FAIL;
+ } while (0);
+
+ WEBDEBUG(("-- leaving %s, rc: %#lx\n", __FUNCTION__, rc));
+ if (FAILED(rc))
+ return SOAP_FAULT;
+ return SOAP_OK;
+}
+
+/**
+ * Returns a new ISession object every time.
+ *
+ * No longer connected in any way to logons, one websession can easily
+ * handle multiple sessions.
+ */
+int __vbox__IWebsessionManager_USCOREgetSessionObject(
+ struct soap*,
+ _vbox__IWebsessionManager_USCOREgetSessionObject *req,
+ _vbox__IWebsessionManager_USCOREgetSessionObjectResponse *resp)
+{
+ HRESULT rc = S_OK;
+ WEBDEBUG(("-- entering %s\n", __FUNCTION__));
+
+ do
+ {
+ // create a new ISession object
+ ComPtr<ISession> pSession;
+ rc = g_pVirtualBoxClient->COMGETTER(Session)(pSession.asOutParam());
+ if (FAILED(rc))
+ {
+ WEBDEBUG(("ERROR: cannot create session object!"));
+ break;
+ }
+
+ // return its MOR
+ resp->returnval = createOrFindRefFromComPtr(req->refIVirtualBox, g_pcszISession, pSession);
+ WEBDEBUG(("Session object ref is %s\n", resp->returnval.c_str()));
+ } while (0);
+
+ WEBDEBUG(("-- leaving %s, rc: %#lx\n", __FUNCTION__, rc));
+ if (FAILED(rc))
+ return SOAP_FAULT;
+ return SOAP_OK;
+}
+
+/**
+ * hard-coded implementation for IWebsessionManager::logoff.
+ *
+ * @param req
+ * @param resp
+ * @return
+ */
+int __vbox__IWebsessionManager_USCORElogoff(
+ struct soap*,
+ _vbox__IWebsessionManager_USCORElogoff *req,
+ _vbox__IWebsessionManager_USCORElogoffResponse *resp)
+{
+ RT_NOREF(resp);
+ HRESULT rc = S_OK;
+ WEBDEBUG(("-- entering %s\n", __FUNCTION__));
+
+ {
+ // findWebsessionFromRef and the websession destructor require the lock
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+
+ WebServiceSession *pWebsession = WebServiceSession::findWebsessionFromRef(req->refIVirtualBox);
+ if (pWebsession)
+ {
+ WEBDEBUG(("websession logoff, deleting websession %#llx\n", pWebsession->getID()));
+ delete pWebsession;
+ // destructor cleans up
+
+ WEBDEBUG(("websession destroyed, %d websessions left open\n", g_mapWebsessions.size()));
+ }
+ }
+
+ WEBDEBUG(("-- leaving %s, rc: %#lx\n", __FUNCTION__, rc));
+ if (FAILED(rc))
+ return SOAP_FAULT;
+ return SOAP_OK;
+}
diff --git a/src/VBox/Main/webservice/vboxweb.h b/src/VBox/Main/webservice/vboxweb.h
new file mode 100644
index 00000000..00408490
--- /dev/null
+++ b/src/VBox/Main/webservice/vboxweb.h
@@ -0,0 +1,376 @@
+/* $Id: vboxweb.h $ */
+/** @file
+ * vboxweb.h - header file for "real" web server code.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef MAIN_INCLUDED_SRC_webservice_vboxweb_h
+#define MAIN_INCLUDED_SRC_webservice_vboxweb_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define LOG_GROUP LOG_GROUP_WEBSERVICE
+#include <VBox/log.h>
+#include <VBox/err.h>
+
+#include <VBox/com/VirtualBox.h>
+#include <VBox/com/Guid.h>
+#include <VBox/com/AutoLock.h>
+
+#include <iprt/asm.h>
+
+#include <iprt/sanitized/string>
+
+/****************************************************************************
+ *
+ * debug macro
+ *
+ ****************************************************************************/
+
+#define WEBDEBUG(a) do { if (g_fVerbose) { LogRel(a); } } while (0)
+
+/****************************************************************************
+ *
+ * typedefs
+ *
+ ****************************************************************************/
+
+// type used by gSOAP-generated code
+typedef std::string WSDLT_ID; // combined managed object ref (websession ID plus object ID)
+typedef std::string vbox__uuid;
+
+/****************************************************************************
+ *
+ * global variables
+ *
+ ****************************************************************************/
+
+extern bool g_fVerbose;
+
+extern util::WriteLockHandle *g_pWebsessionsLockHandle;
+
+extern const WSDLT_ID g_EmptyWSDLID;
+
+/****************************************************************************
+ *
+ * SOAP exceptions
+ *
+ ****************************************************************************/
+
+extern void RaiseSoapInvalidObjectFault(struct soap *soap, WSDLT_ID obj);
+
+extern void RaiseSoapRuntimeFault(struct soap *soap, const WSDLT_ID &idThis, const char *pcszMethodName, HRESULT apirc, IUnknown *pObj, const com::Guid &iid);
+
+/****************************************************************************
+ *
+ * conversion helpers
+ *
+ ****************************************************************************/
+
+extern std::string ConvertComString(const com::Bstr &bstr);
+
+extern std::string ConvertComString(const com::Guid &bstr);
+
+extern std::string Base64EncodeByteArray(ComSafeArrayIn(BYTE, aData));
+
+extern void Base64DecodeByteArray(struct soap *soap, const std::string& aStr, ComSafeArrayOut(BYTE, aData), const WSDLT_ID &idThis, const char *pszMethodName, IUnknown *pObj, const com::Guid &iid);
+
+/****************************************************************************
+ *
+ * managed object reference classes
+ *
+ ****************************************************************************/
+
+class WebServiceSessionPrivate;
+class ManagedObjectRef;
+
+/**
+ * An instance of this gets created for every client that logs onto the
+ * webservice (via the special IWebsessionManager::logon() SOAP API) and
+ * maintains the managed object references for that websession.
+ */
+class WebServiceSession
+{
+ friend class ManagedObjectRef;
+
+ private:
+ uint64_t _uWebsessionID;
+ uint64_t _uNextObjectID;
+ WebServiceSessionPrivate *_pp; // opaque data struct (defined in vboxweb.cpp)
+ bool _fDestructing;
+
+ uint32_t _tLastObjectLookup;
+
+ // hide the copy constructor because we're not copyable
+ WebServiceSession(const WebServiceSession &copyFrom);
+
+ public:
+ WebServiceSession();
+
+ ~WebServiceSession();
+
+ int authenticate(const char *pcszUsername,
+ const char *pcszPassword,
+ IVirtualBox **ppVirtualBox);
+
+ ManagedObjectRef* findRefFromPtr(const IUnknown *pObject);
+
+ uint64_t getID() const
+ {
+ return _uWebsessionID;
+ }
+
+ uint64_t createObjectID()
+ {
+ uint64_t id = ASMAtomicIncU64(&_uNextObjectID);
+ return id - 1;
+ }
+
+ void touch();
+
+ uint32_t getLastObjectLookup() const
+ {
+ return _tLastObjectLookup;
+ }
+
+ static WebServiceSession* findWebsessionFromRef(const WSDLT_ID &id);
+
+ size_t CountRefs();
+};
+
+/**
+ * ManagedObjectRef is used to map COM pointers to object IDs
+ * within a websession. Such object IDs are 64-bit integers.
+ *
+ * When a webservice method call is invoked on an object, it
+ * has an opaque string called a "managed object reference". Such
+ * a string consists of a websession ID combined with an object ID.
+ *
+ */
+class ManagedObjectRef
+{
+ protected:
+ // owning websession:
+ WebServiceSession &_websession;
+
+
+ IUnknown *_pobjUnknown; // pointer to IUnknown interface for this MOR
+
+ void *_pobjInterface; // pointer to COM interface represented by _guidInterface, for which this MOR
+ // was created; this may be an IUnknown or something more specific
+ com::Guid _guidInterface; // the interface which _pvObj represents
+
+ const char *_pcszInterface; // string representation of that interface (e.g. "IMachine")
+
+ // keys:
+ uint64_t _id;
+ uintptr_t _ulp;
+
+ // long ID as string
+ WSDLT_ID _strID;
+
+ public:
+ ManagedObjectRef(WebServiceSession &websession,
+ IUnknown *pobjUnknown,
+ void *pobjInterface,
+ const com::Guid &guidInterface,
+ const char *pcszInterface);
+ ~ManagedObjectRef();
+
+ uint64_t getID()
+ {
+ return _id;
+ }
+
+ /**
+ * Returns the contained COM pointer and the UUID of the COM interface
+ * which it supports.
+ * @param ppobjInterface
+ * @param ppobjUnknown
+ * @return
+ */
+ const com::Guid& getPtr(void **ppobjInterface,
+ IUnknown **ppobjUnknown)
+ {
+ *ppobjInterface = _pobjInterface;
+ *ppobjUnknown = _pobjUnknown;
+ return _guidInterface;
+ }
+
+ /**
+ * Returns the ID of this managed object reference to string
+ * form, for returning with SOAP data or similar.
+ *
+ * @return The ID in string form.
+ */
+ const WSDLT_ID& getWSDLID() const
+ {
+ return _strID;
+ }
+
+ const char* getInterfaceName() const
+ {
+ return _pcszInterface;
+ }
+
+ static int findRefFromId(const WSDLT_ID &id,
+ ManagedObjectRef **pRef,
+ bool fNullAllowed);
+};
+
+/**
+ * Template function that resolves a managed object reference to a COM pointer
+ * of the template class T.
+ *
+ * This gets called only from tons of generated code in methodmaps.cpp to
+ * resolve objects in *input* parameters to COM methods (i.e. translate
+ * MOR strings to COM objects which should exist already).
+ *
+ * This is a template function so that we can support ComPtr's for arbitrary
+ * interfaces and automatically verify that the managed object reference on
+ * the internal stack actually is of the expected interface. We also now avoid
+ * calling QueryInterface for the case that the interface desired by the caller
+ * is the same as the interface for which the MOR was originally created. In
+ * that case, the lookup is very fast.
+ *
+ * @param soap
+ * @param id in: integer managed object reference, as passed in by web service client
+ * @param pComPtr out: reference to COM pointer object that receives the com pointer,
+ * if SOAP_OK is returned
+ * @param fNullAllowed in: if true, then this func returns a NULL COM pointer if an
+ * empty MOR is passed in (i.e. NULL pointers are allowed). If false,
+ * then this fails; this will be false when called for the "this"
+ * argument of method calls, which really shouldn't be NULL.
+ * @return error code or SOAP_OK if no error
+ */
+template <class T>
+int findComPtrFromId(struct soap *soap,
+ const WSDLT_ID &id,
+ ComPtr<T> &pComPtr,
+ bool fNullAllowed)
+{
+ // findRefFromId requires thelock
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+
+ int rc;
+ ManagedObjectRef *pRef;
+ if ((rc = ManagedObjectRef::findRefFromId(id, &pRef, fNullAllowed)))
+ // error:
+ RaiseSoapInvalidObjectFault(soap, id);
+ else
+ {
+ if (fNullAllowed && pRef == NULL)
+ {
+ WEBDEBUG((" %s(): returning NULL object as permitted\n", __FUNCTION__));
+ pComPtr.setNull();
+ return 0;
+ }
+
+ const com::Guid &guidCaller = COM_IIDOF(T);
+
+ // pRef->getPtr returns both a void* for its specific interface pointer as well as a generic IUnknown*
+ void *pobjInterface;
+ IUnknown *pobjUnknown;
+ const com::Guid &guidInterface = pRef->getPtr(&pobjInterface, &pobjUnknown);
+
+ if (guidInterface == guidCaller)
+ {
+ // same interface: then no QueryInterface needed
+ WEBDEBUG((" %s(): returning original %s*=0x%lX (IUnknown*=0x%lX)\n", __FUNCTION__, pRef->getInterfaceName(), pobjInterface, pobjUnknown));
+ pComPtr = (T*)pobjInterface; // this calls AddRef() once
+ return 0;
+ }
+
+ // QueryInterface tests whether p actually supports the templated T interface desired by caller
+ T *pT;
+ pobjUnknown->QueryInterface(guidCaller.ref(), (void**)&pT); // this adds a reference count
+ if (pT)
+ {
+ // assign to caller's ComPtr<T>; use asOutParam() to avoid adding another reference, QueryInterface() already added one
+ WEBDEBUG((" %s(): returning pointer 0x%lX for queried interface %RTuuid (IUnknown*=0x%lX)\n", __FUNCTION__, pT, guidCaller.raw(), pobjUnknown));
+ *(pComPtr.asOutParam()) = pT;
+ return 0;
+ }
+
+ WEBDEBUG((" Interface not supported for object reference %s, which is of class %s\n", id.c_str(), pRef->getInterfaceName()));
+ rc = VERR_WEB_UNSUPPORTED_INTERFACE;
+ RaiseSoapInvalidObjectFault(soap, id); // @todo better message
+ }
+
+ return rc;
+}
+
+/**
+ * Creates a new managed object reference for the given COM pointer. If one
+ * already exists for the given pointer, then that reference's ID is returned.
+ *
+ * This gets called from tons of generated code in methodmaps.cpp to resolve
+ * objects *returned* from COM methods (i.e. create MOR strings from COM
+ * objects which might have been newly created).
+ *
+ * @param idParent managed object reference of calling object; used to extract
+ * websession ID
+ * @param pcszInterface
+ * @param pc COM object for which to create a reference
+ * @return existing or new managed object reference
+ */
+template <class T>
+const WSDLT_ID& createOrFindRefFromComPtr(const WSDLT_ID &idParent,
+ const char *pcszInterface,
+ const ComPtr<T> &pc)
+{
+ // NULL comptr should return NULL MOR
+ if (pc.isNull())
+ {
+ WEBDEBUG((" createOrFindRefFromComPtr(): returning empty MOR for NULL COM pointer\n"));
+ return g_EmptyWSDLID;
+ }
+
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+ WebServiceSession *pWebsession;
+ if ((pWebsession = WebServiceSession::findWebsessionFromRef(idParent)))
+ {
+ ManagedObjectRef *pRef;
+
+ // we need an IUnknown pointer for the MOR
+ ComPtr<IUnknown> pobjUnknown = pc;
+
+ if ( ((pRef = pWebsession->findRefFromPtr(pobjUnknown)))
+ || ((pRef = new ManagedObjectRef(*pWebsession,
+ pobjUnknown, // IUnknown *pobjUnknown
+ pc, // void *pobjInterface
+ COM_IIDOF(T),
+ pcszInterface)))
+ )
+ return pRef->getWSDLID();
+ }
+
+ // websession has expired, return an empty MOR instead of allocating a
+ // new reference which couldn't be used anyway.
+ return g_EmptyWSDLID;
+}
+
+#endif /* !MAIN_INCLUDED_SRC_webservice_vboxweb_h */
+
diff --git a/src/VBox/Main/webservice/websrv-cpp.xsl b/src/VBox/Main/webservice/websrv-cpp.xsl
new file mode 100644
index 00000000..f86dbf1a
--- /dev/null
+++ b/src/VBox/Main/webservice/websrv-cpp.xsl
@@ -0,0 +1,1507 @@
+<?xml version="1.0"?>
+
+<!--
+ websrv-cpp.xsl:
+ XSLT stylesheet that generates methodmaps.cpp from
+ VirtualBox.xidl. This generated C++ code contains
+ all the service implementations that one would
+ normally have to implement manually to create a
+ web service; our generated code automatically maps
+ all SOAP calls into COM/XPCOM method calls.
+ See webservice/Makefile.kmk for an overview of all the things
+ generated for the webservice.
+-->
+<!--
+ Copyright (C) 2007-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:exsl="http://exslt.org/common"
+ extension-element-prefixes="exsl">
+
+ <xsl:output method="text"/>
+
+ <xsl:strip-space elements="*"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ global XSLT variables
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:variable name="G_xsltFilename" select="'websrv-cpp.xsl'" />
+
+<xsl:include href="../idl/typemap-shared.inc.xsl" />
+
+<!-- collect all interfaces with "wsmap='suppress'" in a global variable for
+ quick lookup -->
+<xsl:variable name="G_setSuppressedInterfaces"
+ select="//interface[@wsmap='suppress']" />
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ Keys for more efficiently looking up of types.
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:key name="G_keyEnumsByName" match="//enum[@name]" use="@name"/>
+<xsl:key name="G_keyInterfacesByName" match="//interface[@name]" use="@name"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ root match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="/idl">
+ <xsl:text><![CDATA[
+/* DO NOT EDIT! This is a generated file.
+ * Generated from: src/VBox/Main/idl/VirtualBox.xidl (VirtualBox's interface definitions in XML)
+ * Generator: src/VBox/Main/webservice/websrv-cpp.xsl
+ */
+
+// shared webservice header
+#include "vboxweb.h"
+
+// vbox headers
+#include <VBox/com/com.h>
+#include <VBox/com/array.h>
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/com/errorprint.h>
+#include <VBox/VBoxAuth.h>
+
+#include <iprt/assert.h>
+#include <iprt/initterm.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+// gSOAP headers (must come after vbox includes because it checks for conflicting defs)
+#include "soapH.h"
+
+// standard headers
+#include <map>
+#include <iprt/sanitized/sstream>
+
+// shared strings for debug output
+const char *g_pcszCallingComMethod = " calling COM method %s\n";
+const char *g_pcszDoneCallingComMethod = " done calling COM method\n";
+const char *g_pcszConvertComOutputBack = " convert COM output \"%s\" back to caller format\n";
+const char *g_pcszDoneConvertingComOutputBack = " done converting COM output \"%s\" back to caller format\n";
+const char *g_pcszEntering = "-- entering %s\n";
+const char *g_pcszLeaving = "-- leaving %s, rc: %#lx (%d)\n";
+
+// generated string constants for all interface names
+const char *g_pcszIUnknown = "IUnknown";
+]]></xsl:text>
+
+ <xsl:for-each select="//interface">
+ <xsl:variable name="ifname" select="@name" />
+ <xsl:value-of select="concat('const char *g_pcsz', $ifname, ' = &quot;', $ifname, '&quot;;')" />
+ <xsl:call-template name="emitNewline" />
+ </xsl:for-each>
+ <xsl:apply-templates />
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ if
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<!--
+ * ignore all |if|s except those for WSDL target
+-->
+<xsl:template match="if">
+ <xsl:if test="@target='wsdl'">
+ <xsl:apply-templates/>
+ </xsl:if>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ cpp
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="cpp">
+<!-- ignore this -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ library
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="library">
+ <xsl:text>
+/****************************************************************************
+ *
+ * types: enum converter helper functions
+ *
+ ****************************************************************************/
+ </xsl:text>
+ <!--
+ enum converter functions at top of file
+ -->
+ <xsl:for-each select="//enum">
+ <xsl:variable name="enumname" select="@name" />
+ <!-- generate enum converter for COM-to-SOAP -->
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="concat('vbox__', $enumname, ' ', $G_funcPrefixOutputEnumConverter, $enumname, '(', $enumname, '_T e)')" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:text>{</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="concat(' vbox__', $enumname, ' v;')" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:text> switch(e)</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:text> {</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:for-each select="const[not(@wsmap='suppress')]">
+ <xsl:variable name="enumconst" select="@name" />
+ <xsl:value-of select="concat(' case ', $enumname, '_', $enumconst, ':')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' v = vbox__', $enumname, '__')" />
+ <!-- escape all "_" in $enumconst -->
+ <xsl:call-template name="escapeUnderscores">
+ <xsl:with-param name="string" select="$enumconst" />
+ </xsl:call-template>
+ <xsl:value-of select="';'" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text>break;</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ </xsl:for-each>
+ <!-- Add a default case so gcc gives us a rest, esp. on darwin. -->
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text>default:</xsl:text>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text> AssertMsgFailed(("e=%d\n", (int)e));</xsl:text>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' v = (vbox__', $enumname, ')0x7fffdead;')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text>break; </xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:text> }</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:text> return v;</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:text>}</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <!-- generate enum converter for SOAP-to-COM -->
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="concat($enumname, '_T ', $G_funcPrefixInputEnumConverter, $enumname, '(vbox__', $enumname, ' v)')" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:text>{</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="concat(' ', $enumname, '_T e;')" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:text> switch(v)</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:text> {</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:for-each select="const[not(@wsmap='suppress')]">
+ <xsl:variable name="enumconst" select="@name" />
+ <xsl:value-of select="concat(' case vbox__', $enumname, '__')" />
+ <!-- escape all "_" in $enumconst -->
+ <xsl:call-template name="escapeUnderscores">
+ <xsl:with-param name="string" select="$enumconst" />
+ </xsl:call-template>
+ <xsl:value-of select="':'" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' e = ', $enumname, '_', $enumconst, ';')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text>break;</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ </xsl:for-each>
+ <!-- Insert a default case so gcc gives us a rest, esp. on darwin. -->
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text>default:</xsl:text>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text> AssertMsgFailed(("v=%d\n", (int)v));</xsl:text>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' e = (', $enumname, '_T)0x7fffbeef;')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text>break; </xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:text> }</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:text> return e;</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:text>}</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ </xsl:for-each>
+
+ <xsl:text>
+/****************************************************************************
+ *
+ * types: struct converter helper functions
+ *
+ ****************************************************************************/
+ </xsl:text>
+
+ <xsl:for-each select="//interface[@wsmap='struct']">
+ <xsl:variable name="structname" select="@name" />
+
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="concat('// ', $structname, ' converter: called from method mappers to convert data from')" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="concat('// COM interface ', $structname, ', which has wsmap=&quot;struct&quot;, to SOAP structures')" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="concat('vbox__', $structname, '* ', $G_funcPrefixOutputStructConverter, $structname, '(')" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="' struct soap *soap,'" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="' const WSDLT_ID &amp;idThis,'" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="' HRESULT &amp;rc,'" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="concat(' ComPtr&lt;', $structname, '&gt; &amp;in)')" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:text>{</xsl:text>
+ <xsl:call-template name="emitNewline" />
+
+ <xsl:value-of select="concat(' vbox__', $structname, ' *resp = NULL;')" />
+ <xsl:call-template name="emitNewline" />
+
+ <xsl:call-template name="emitPrologue"><xsl:with-param name="fSkipHRESULT" select="'1'"/></xsl:call-template>
+
+ <xsl:value-of select="concat(' resp = soap_new_vbox__', $structname, '(soap, -1);')" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:text> if (!in)&#10;</xsl:text>
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> // @todo ambiguous. Problem is the MOR for the object converted to struct&#10;</xsl:text>
+ <xsl:text> RaiseSoapInvalidObjectFault(soap, "");&#10;</xsl:text>
+ <xsl:text> break;&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:call-template name="emitNewline" />
+
+ <xsl:for-each select="key('G_keyInterfacesByName', $structname)/attribute">
+ <xsl:if test="not(@wsmap = 'suppress')">
+ <xsl:value-of select="concat(' // -- ', $structname, '.', @name)" />
+ <xsl:call-template name="emitNewline" />
+ <!-- recurse! -->
+ <xsl:call-template name="emitGetAttributeComCall">
+ <xsl:with-param name="ifname" select="$structname" />
+ <xsl:with-param name="object" select="'in'" />
+ <xsl:with-param name="attrname" select="@name" />
+ <xsl:with-param name="attrtype" select="@type" />
+ <xsl:with-param name="callerprefix" select="concat('out', '.')" />
+ </xsl:call-template>
+ <xsl:call-template name="emitNewline" />
+ </xsl:if>
+ </xsl:for-each>
+
+ <xsl:call-template name="emitEpilogue"><xsl:with-param name="fSkipHRESULT" select="'1'"/></xsl:call-template>
+
+ </xsl:for-each>
+
+ <xsl:apply-templates />
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ class
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="module/class">
+<!-- TODO swallow for now -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ enum
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="enum">
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ const
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<!--
+<xsl:template match="const">
+ <xsl:apply-templates />
+</xsl:template>
+-->
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ desc
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="desc">
+<!-- TODO swallow for now -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ note
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="note">
+<!-- TODO -->
+ <xsl:apply-templates />
+</xsl:template>
+
+<!--
+ emitBeginOfFunctionHeader:
+-->
+
+<xsl:template name="emitBeginOfFunctionHeader">
+ <xsl:param name="ifname" />
+ <xsl:param name="method" />
+
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="concat('int __vbox__', $ifname, '_USCORE', $method, '(')" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:text> struct soap *soap</xsl:text>
+</xsl:template>
+
+<!--
+ emitCppTypeForIDLType:
+ emits the C++ type that corresponds to the given WSDL type in $type.
+ -->
+<xsl:template name="emitCppTypeForIDLType">
+ <xsl:param name="method" />
+ <xsl:param name="type" />
+ <xsl:param name="safearray" />
+ <xsl:param name="varprefix" /> <!-- only with nested get-attribute calls -->
+ <xsl:param name="inptr" /> <!-- whether to add INPTR to BSTR (Dmitry template magic) -->
+
+ <!-- look up C++ glue type from IDL type from table array in typemap-shared.inc.xsl -->
+ <xsl:variable name="gluetypefield" select="exsl:node-set($G_aSharedTypes)/type[@idlname=$type]/@gluename" />
+
+ <xsl:choose>
+ <xsl:when test="$type='wstring' or $type='uuid'">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:choose>
+ <xsl:when test="$inptr='yes'">
+ <xsl:value-of select="'com::SafeArray&lt;IN_BSTR&gt;'" /> <!-- input string arrays must use IN_BSTR (see com/array.h) -->
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="'com::SafeArray&lt;BSTR&gt;'" /> <!-- output string arrays use raw BSTR -->
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="'com::Bstr'" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <!-- if above lookup in table succeeded, use that type -->
+ <xsl:when test="string-length($gluetypefield)">
+ <xsl:call-template name="emitTypeOrArray">
+ <xsl:with-param name="type" select="$gluetypefield"/>
+ <xsl:with-param name="safearray" select="$safearray"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyEnumsByName', $type)) > 0">
+ <xsl:call-template name="emitTypeOrArray">
+ <xsl:with-param name="type" select="concat($type, '_T ')"/>
+ <xsl:with-param name="safearray" select="$safearray"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="$type='$unknown'">
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="'com::SafeIfaceArray&lt;IUnknown&gt;'" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="'ComPtr&lt;IUnknown&gt;'" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:variable name="thatif" select="key('G_keyInterfacesByName', $type)" />
+ <xsl:variable name="thatifname" select="$thatif/@name" />
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('com::SafeIfaceArray&lt;', $thatifname, '&gt;')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('ComPtr&lt;', $thatifname, '&gt;')" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('emitCppTypeForIDLType: Type &quot;', $type, '&quot; in method &quot;', $method, '&quot; is not supported.')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+
+<!--
+ emitDocumentStyleArgStructs:
+ with WSDL "document" style only, emits those lengthy structs for
+ the input and output argument in the function header.
+-->
+<xsl:template name="emitDocumentStyleArgStructs">
+ <xsl:param name="ifname" />
+ <xsl:param name="methodname" />
+ <xsl:param name="fOutputs" /> <!-- if 1, emit output struct as well -->
+
+ <xsl:text>,</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="concat(' _vbox__', $ifname, '_USCORE', $methodname, $G_requestMessageElementSuffix, ' *', $G_requestElementVarName)" />
+ <xsl:if test="$fOutputs">
+ <xsl:text>,</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="concat(' _vbox__', $ifname, '_USCORE', $methodname, $G_responseMessageElementSuffix, ' *', $G_responseElementVarName)" />
+ <!-- <xsl:value-of select="concat(' struct ', $ifname, '__', $methodname, 'Response &amp;', $G_result)" /> -->
+ </xsl:if>
+
+</xsl:template>
+
+<!--
+ emitPrologue:
+ emits the closing ")" for the parameter list and the beginning
+ of the function body.
+ -->
+<xsl:template name="emitPrologue">
+ <xsl:text> WEBDEBUG((g_pcszEntering, __FUNCTION__));
+
+ do {</xsl:text>
+ <xsl:call-template name="emitNewline" />
+</xsl:template>
+
+<!--
+ emitEpilogue
+ -->
+<xsl:template name="emitEpilogue">
+ <xsl:param name="fSkipHRESULT" />
+
+ <xsl:text> } while (0);</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:text> WEBDEBUG((g_pcszLeaving, __FUNCTION__, rc, rc));</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:if test="not($fSkipHRESULT)">
+ <xsl:text>
+ if (FAILED(rc))
+ return SOAP_FAULT;
+ return SOAP_OK;</xsl:text>
+ </xsl:if>
+ <xsl:if test="$fSkipHRESULT">
+ <xsl:text> return resp;</xsl:text>
+ </xsl:if>
+ <xsl:call-template name="emitNewline" />
+ <xsl:text>}</xsl:text>
+ <xsl:call-template name="emitNewline" />
+</xsl:template>
+
+<!--
+ emitObjForMethod:
+ after the function prologue, emit a "pObj" object that
+ specifies the object upon which the method should be invoked.
+-->
+<xsl:template name="emitObjForMethod">
+ <xsl:param name="ifname" />
+ <xsl:param name="wsmap" />
+ <xsl:param name="structprefix" /> <!-- with WSDL document style: req element prefix, like "vbox__IVirtualBox_USCOREcreateMachineRequestElement->" -->
+
+ <xsl:choose>
+ <xsl:when test="$wsmap='global'">
+ <xsl:choose>
+ <xsl:when test="$ifname='IVirtualBox'">
+ <xsl:text> // invoke method on global IVirtualBox instance</xsl:text>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text>ComPtr&lt;IVirtualBox&gt; pObj = G_pVirtualBox;</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('emitObjForMethod: Unknown interface &quot;', $ifname, '&quot; with wsmap=global in XIDL.')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="($wsmap='managed')">
+ <xsl:text> // look up managed object reference for method call&#10;</xsl:text>
+ <xsl:value-of select="concat(' ComPtr&lt;', $ifname, '&gt; pObj;&#10;')" />
+ <xsl:value-of select="concat(' if (!', $G_requestElementVarName, ')&#10;')" />
+ <xsl:text> {&#10;</xsl:text>
+ <xsl:text> RaiseSoapInvalidObjectFault(soap, "");&#10;</xsl:text>
+ <xsl:text> break;&#10;</xsl:text>
+ <xsl:text> }&#10;</xsl:text>
+ <xsl:value-of select="concat(' const WSDLT_ID &amp;idThis = ', $structprefix, $G_nameObjectRefEncoded, ';&#10;')" />
+ <xsl:value-of select="' if ((rc = findComPtrFromId(soap, idThis, pObj, false)) != S_OK)&#10;'" />
+ <xsl:text> break;&#10;</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+ emitInputArgConverter:
+ another type converter (from wsdl type to COM types),
+ that generates temporary variables on the stack with
+ the WSDL input parameters converted to the COM types,
+ so we can then pass them to the actual COM method call.
+-->
+<xsl:template name="emitInputArgConverter">
+ <xsl:param name="ifname" />
+ <xsl:param name="object" /> <!-- normally "pObj" -->
+ <xsl:param name="method" />
+ <xsl:param name="methodname" />
+ <xsl:param name="structprefix" /> <!-- with WSDL document style: req element prefix, like "vbox__IVirtualBox_USCOREcreateMachineRequestElement->" -->
+ <xsl:param name="name" />
+ <xsl:param name="type" />
+ <xsl:param name="safearray" />
+
+ <xsl:value-of select="concat(' // convert input arg ', $name, '(safearray: ', $safearray, ')')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+
+ <xsl:choose>
+ <xsl:when test="$safearray='yes' and $type='octet'">
+ <xsl:value-of select="concat('com::SafeArray&lt;BYTE&gt; comcall_',$name, ';')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat('Base64DecodeByteArray(soap, ',$structprefix,$name,', ComSafeArrayAsOutParam(comcall_',$name, '), idThis, &quot;', $ifname, '::', $methodname, '&quot;, ', $object, ', COM_IIDOF(', $ifname, '));')" />
+ </xsl:when>
+
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('size_t c', $name, ' = ', $structprefix, $name, '.size();')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:call-template name="emitCppTypeForIDLType">
+ <xsl:with-param name="method" select="$method"/>
+ <xsl:with-param name="type" select="$type"/>
+ <xsl:with-param name="safearray" select="$safearray"/>
+ <xsl:with-param name="inptr" select="'yes'"/>
+ </xsl:call-template>
+ <xsl:value-of select="concat(' comcall_', $name, '(c', $name, ');')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat('for (size_t i = 0; i &lt; c', $name, '; ++i)')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="'{'" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:choose>
+ <xsl:when test="$type='$unknown'">
+ <xsl:value-of select="' ComPtr&lt;IUnknown&gt; tmpObject;'" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' if ((rc = findComPtrFromId(soap, ', $structprefix, $name, '[i], tmpObject, true)) != S_OK)')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text> break;</xsl:text>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' IUnknown *tmpObject2(tmpObject); tmpObject2->AddRef(); comcall_', $name, '[i] = tmpObject;')" />
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:value-of select="concat(' ComPtr&lt;', $type, '&gt; tmpObject;')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' if ((rc = findComPtrFromId(soap, ', $structprefix, $name, '[i], tmpObject, true)) != S_OK)')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text> break;</xsl:text>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' ', $type, ' *tmpObject2(tmpObject); tmpObject2->AddRef(); comcall_', $name, '[i] = tmpObject;')" />
+ </xsl:when>
+ <xsl:when test="$type='wstring'">
+ <xsl:value-of select="concat(' com::Bstr tmpObject(', $structprefix, $name, '[i].c_str());')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="' BSTR tmpObjectB;'" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="' tmpObject.detachTo(&amp;tmpObjectB);'" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' comcall_', $name, '[i] = tmpObjectB;')" />
+ </xsl:when>
+ <xsl:when test="$type='long'">
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' comcall_', $name, '[i] = ', $structprefix, $name, '[i];')" />
+ </xsl:when>
+ <xsl:when test="$type='long long'">
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' comcall_', $name, '[i] = ', $structprefix, $name, '[i];')" />
+ </xsl:when>
+ <xsl:when test="$type='boolean'">
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' comcall_', $name, '[i] = ', $structprefix, $name, '[i];')" />
+ </xsl:when>
+ <xsl:when test="count(key('G_keyEnumsByName', $type)) > 0">
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' comcall_', $name, '[i] = ', $G_funcPrefixInputEnumConverter, $type, '(', $structprefix, $name, '[i]);')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('emitInputArgConverter Type &quot;', $type, '&quot; in arg &quot;', $name, '&quot; of method &quot;', $method, '&quot; is not yet supported in safearrays.')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="'}'" />
+ <xsl:call-template name="emitNewline" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="emitCppTypeForIDLType">
+ <xsl:with-param name="method" select="$method"/>
+ <xsl:with-param name="type" select="$type"/>
+ <xsl:with-param name="safearray" select="$safearray"/>
+ <xsl:with-param name="inptr" select="'yes'"/>
+ </xsl:call-template>
+ <xsl:choose>
+ <xsl:when test="$type='wstring' or $type='uuid'">
+ <xsl:value-of select="concat(' comcall_', $name, '(', $structprefix, $name, '.c_str())')" />
+ </xsl:when>
+ <xsl:when test="count(key('G_keyEnumsByName', $type)) > 0">
+ <xsl:value-of select="concat(' comcall_', $name, ' = ', $G_funcPrefixInputEnumConverter, $type, '(', $structprefix, $name, ')')" />
+ </xsl:when>
+ <xsl:when test="$type='$unknown'">
+ <xsl:value-of select="concat(' comcall_', $name, ';')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat('if ((rc = findComPtrFromId(soap, ', $structprefix, $name, ', comcall_', $name,', true)) != S_OK)')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text> break</xsl:text>
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <!-- the type is one of our own interfaces: then it must have a wsmap attr -->
+ <xsl:variable name="thatif" select="key('G_keyInterfacesByName', $type)" />
+ <xsl:variable name="wsmap" select="$thatif/@wsmap" />
+ <xsl:variable name="thatifname" select="$thatif/@name" />
+ <xsl:choose>
+ <xsl:when test="not($wsmap)">
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('emitInputArgConverter: Type &quot;', $type, '&quot; in arg &quot;', $name, '&quot; of method &quot;', $method, '&quot; lacks wsmap attribute in XIDL.')" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="($wsmap='managed')">
+ <xsl:value-of select="concat(' comcall_', $name, ';')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat('if ((rc = findComPtrFromId(soap, ', $structprefix, $name, ', comcall_', $name,', true)) != S_OK)')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text> break</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('emitInputArgConverter: Type &quot;', $type, '&quot; in arg &quot;', $name, '&quot; of method &quot;', $method, '&quot; has unsupported wsmap attribute value &quot;', $wsmap, '&quot; in XIDL.')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat(' comcall_', $name, ' = ', $structprefix, $name)" />
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>;
+</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+
+</xsl:template>
+
+<!--
+ emitTypeOrArray
+-->
+
+<xsl:template name="emitTypeOrArray">
+ <xsl:param name="type" />
+ <xsl:param name="safearray" />
+
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('com::SafeArray&lt;', $type, '&gt;')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$type" />
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+ emitOutputArgBuffer:
+ another type converter (from wsdl type to COM types)
+ that generates a buffer variable which receives the
+ data from 'out' and 'return' parameters of the COM method call.
+-->
+<xsl:template name="emitOutputArgBuffer">
+ <xsl:param name="method" />
+ <xsl:param name="name" />
+ <xsl:param name="type" />
+ <xsl:param name="safearray" />
+ <xsl:param name="varprefix" /> <!-- only with nested get-attribute calls -->
+
+ <xsl:text> // com output arg for </xsl:text><xsl:value-of select="concat($name, ' (safearray: ', $safearray, ')')" /><xsl:text>
+ </xsl:text>
+ <xsl:call-template name="emitCppTypeForIDLType">
+ <xsl:with-param name="method" select="$method" />
+ <xsl:with-param name="type" select="$type" />
+ <xsl:with-param name="safearray" select="$safearray" />
+ </xsl:call-template>
+ <xsl:value-of select="concat(' comcall_', $varprefix, $name, ';')" />
+ <xsl:call-template name="emitNewline" />
+</xsl:template>
+
+<!--
+ emitInParam:
+-->
+<xsl:template name="emitInParam">
+ <xsl:param name="name" />
+ <xsl:param name="type" />
+ <xsl:param name="safearray" />
+ <xsl:param name="varprefix" /> <!-- only with nested set-attribute calls -->
+
+ <xsl:variable name="varname" select="concat('comcall_', $varprefix, $name)" />
+
+ <xsl:choose>
+ <xsl:when test="@safearray='yes'">
+ <xsl:value-of select="concat('ComSafeArrayAsInParam(', $varname, ')')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$varname" />
+ <xsl:if test="@type='wstring' or @type='uuid'">
+ <xsl:text>.raw()</xsl:text>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+ emitOutParam:
+-->
+<xsl:template name="emitOutParam">
+ <xsl:param name="name" />
+ <xsl:param name="type" />
+ <xsl:param name="safearray" />
+ <xsl:param name="varprefix" /> <!-- only with nested get-attribute calls -->
+
+ <xsl:variable name="varname" select="concat('comcall_', $varprefix, $name)" />
+
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('ComSafeArrayAsOutParam(', $varname, ')')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test=" ($type='boolean')
+ or ($type='short')
+ or ($type='unsigned short')
+ or ($type='long')
+ or ($type='unsigned long')
+ or ($type='long long')
+ or ($type='unsigned long long')
+ or ($type='result')
+ or (count(key('G_keyEnumsByName', $type)) > 0)">
+ <xsl:text>&amp;</xsl:text><xsl:value-of select="$varname" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$varname" /><xsl:text>.asOutParam()</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+ emitComCall:
+ emits the actual method call with the arguments.
+-->
+<xsl:template name="emitComCall">
+ <xsl:param name="ifname" />
+ <xsl:param name="object" /> <!-- normally "pObj" -->
+ <xsl:param name="methodname" />
+ <xsl:param name="attrname" /> <!-- with attributes only -->
+ <xsl:param name="attrtype" /> <!-- with attributes only -->
+ <xsl:param name="attrsafearray" /> <!-- with attributes only -->
+ <xsl:param name="attrdir" /> <!-- with attributes only: "in" or "return" -->
+ <xsl:param name="varprefix" /> <!-- only with nested get-attribute calls -->
+
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat('WEBDEBUG((g_pcszCallingComMethod, &quot;', $methodname, '&quot;));')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat('rc = ', $object, '-&gt;', $methodname, '(')" />
+ <xsl:if test="$attrtype">
+ <xsl:choose>
+ <xsl:when test="$attrdir='in'">
+ <xsl:call-template name="emitInParam">
+ <xsl:with-param name="name" select="$attrname" />
+ <xsl:with-param name="type" select="$attrtype" />
+ <xsl:with-param name="safearray" select="$attrsafearray" />
+ <xsl:with-param name="varprefix" select="$varprefix" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="$attrdir='return'">
+ <xsl:call-template name="emitOutParam">
+ <xsl:with-param name="name" select="$attrname" />
+ <xsl:with-param name="type" select="$attrtype" />
+ <xsl:with-param name="safearray" select="$attrsafearray" />
+ <xsl:with-param name="varprefix" select="$varprefix" />
+ </xsl:call-template>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:if>
+ <xsl:for-each select="param">
+ <xsl:if test="position()=1">
+ <xsl:call-template name="emitNewline" />
+ </xsl:if>
+ <xsl:if test="position() > 1">
+ <xsl:text>,</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ </xsl:if>
+ <xsl:text> </xsl:text>
+ <xsl:choose>
+ <xsl:when test="@dir='in'">
+ <xsl:call-template name="emitInParam">
+ <xsl:with-param name="name" select="@name" />
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="varprefix" select="$varprefix" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="@dir='out'">
+ <xsl:call-template name="emitOutParam">
+ <xsl:with-param name="name" select="@name" />
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="varprefix" select="$varprefix" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="@dir='return'">
+ <xsl:call-template name="emitOutParam">
+ <xsl:with-param name="name" select="$G_result" />
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="varprefix" select="$varprefix" />
+ </xsl:call-template>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:for-each>
+ <xsl:text>);</xsl:text>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text>if (FAILED(rc))</xsl:text>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text>{</xsl:text>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' RaiseSoapRuntimeFault(soap, idThis, &quot;', $ifname, '::', $methodname,'&quot;, rc, ', $object, ', COM_IIDOF(', $ifname, '));')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text> break;</xsl:text>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text>}</xsl:text>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:text>WEBDEBUG((g_pcszDoneCallingComMethod));</xsl:text>
+ <xsl:call-template name="emitNewline" />
+</xsl:template>
+
+<!--
+ emitOutputArgBackConverter2: implementation details of emitOutputArgBackConverter.
+ -->
+
+<xsl:template name="emitOutputArgBackConverter2">
+ <xsl:param name="name" />
+ <xsl:param name="varname" />
+ <xsl:param name="type" />
+ <xsl:param name="callerprefix" />
+
+ <xsl:choose>
+ <xsl:when test="$type='wstring' or $type='uuid'">
+ <xsl:value-of select="concat('ConvertComString(', $varname, ')')" />
+ </xsl:when>
+ <xsl:when test="$type='boolean'">
+ <!-- the "!!" avoids a microsoft compiler warning -->
+ <xsl:value-of select="concat('!!', $varname)" />
+ </xsl:when>
+ <xsl:when test=" ($type='octet')
+ or ($type='short')
+ or ($type='unsigned short')
+ or ($type='long')
+ or ($type='unsigned long')
+ or ($type='long long')
+ or ($type='unsigned long long')
+ or ($type='result')">
+ <xsl:value-of select="$varname" />
+ </xsl:when>
+ <xsl:when test="count(key('G_keyEnumsByName', $type)) > 0">
+ <xsl:value-of select="concat($G_funcPrefixOutputEnumConverter, $type, '(', $varname, ')')" />
+ </xsl:when>
+ <xsl:when test="$type='$unknown'">
+ <xsl:value-of select="concat('createOrFindRefFromComPtr(idThis, g_pcszIUnknown, ', $varname, ')')" />
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <!-- the type is one of our own interfaces: then it must have a wsmap attr -->
+ <xsl:variable name="thatif" select="key('G_keyInterfacesByName', $type)" />
+ <xsl:variable name="wsmap" select="$thatif/@wsmap" />
+ <xsl:variable name="thatifname" select="$thatif/@name" />
+ <xsl:choose>
+ <xsl:when test=" ($wsmap='managed') or ($wsmap='global')">
+ <xsl:value-of select="concat('createOrFindRefFromComPtr(idThis, g_pcsz', $thatifname, ', ', $varname, ')')" />
+ </xsl:when>
+ <xsl:when test="$wsmap='struct'">
+ <!-- prevent infinite recursion -->
+ <!-- <xsl:call-template name="fatalError"><xsl:with-param name="msg" select="concat('emitOutputArgBackConverter2: attempted infinite recursion for type &quot;', $type, '&quot; in arg &quot;', $name, '&quot; of method &quot;', $ifname, '::', $method)" /></xsl:call-template> -->
+ <xsl:if test="not($callerprefix)">
+ <xsl:value-of select="concat('/* convert COM interface to struct */ ', $G_funcPrefixOutputStructConverter, $type, '(soap, idThis, rc, ', $varname, ')')" />
+ </xsl:if>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('emitOutputArgBackConverter2: Type &quot;', $type, '&quot; in arg &quot;', $name, '&quot; of method &quot;', $thatifname, '::', $method, '&quot; has invalid wsmap attribute value &quot;', $wsmap, '&quot; in XIDL.')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('emitOutputArgBackConverter2: Type &quot;', $type, '&quot; in arg &quot;', $name, '&quot; of method &quot;', $method, '&quot; is not supported.')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+</xsl:template>
+
+<!--
+ emitOutputArgBackConverter:
+ another type converter (from COM type back to WSDL)
+ which converts the output argument from the COM
+ method call back to the WSDL type passed in by the
+ caller.
+-->
+<xsl:template name="emitOutputArgBackConverter">
+ <xsl:param name="ifname" />
+ <xsl:param name="method" />
+ <xsl:param name="name" />
+ <xsl:param name="type" />
+ <xsl:param name="safearray" />
+ <xsl:param name="varprefix" /> <!-- only when called recursively from emitGetAttributeComCall -->
+ <xsl:param name="callerprefix" /> <!-- only for out params or when called recursively from emitGetAttributeComCall -->
+
+ <xsl:variable name="topname" select="$name" />
+ <xsl:variable name="varname" select="concat('comcall_', $varprefix, $name)" />
+
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat('WEBDEBUG((g_pcszConvertComOutputBack, &quot;', $name, '&quot;));')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+
+ <xsl:variable name="receiverVariable">
+ <xsl:choose>
+ <xsl:when test="(not($varprefix))">
+ <xsl:choose>
+ <xsl:when test="$callerprefix"> <!-- callerprefix set but varprefix not: then this is an out parameter :-) -->
+ <xsl:value-of select="concat($G_responseElementVarName, '-&gt;', $name)" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat($G_responseElementVarName, '-&gt;', $G_result)" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat($callerprefix, $G_result, '-&gt;', $name)" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$safearray='yes' and $type='octet'">
+ <xsl:value-of select="concat($receiverVariable, ' = Base64EncodeByteArray(ComSafeArrayAsInParam(', $varname,'));')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ </xsl:when>
+
+ <xsl:when test="$safearray='yes'">
+ <xsl:value-of select="concat('for (size_t i = 0; i &lt; ', $varname, '.size(); ++i)')" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="'{'" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <!-- look up C++ glue type from IDL type from table array in typemap-shared.inc.xsl -->
+ <xsl:variable name="gluetypefield" select="exsl:node-set($G_aSharedTypes)/type[@idlname=$type]/@gluename" />
+ <xsl:choose>
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <xsl:value-of select="concat(' ComPtr&lt;', $type, '&gt; tmpObject(', $varname, '[i]);')" />
+ </xsl:when>
+ <xsl:when test="count(key('G_keyEnumsByName', $type)) > 0">
+ <xsl:value-of select="concat(' ', $type, '_T tmpObject(', $varname, '[i]);')" />
+ </xsl:when>
+ <xsl:when test="$type='$unknown'">
+ <xsl:value-of select="concat(' ComPtr&lt;IUnknown&gt; tmpObject(', $varname, '[i]);')" />
+ </xsl:when>
+ <xsl:when test="$type='wstring' or $type='uuid'">
+ <xsl:value-of select="concat(' com::Bstr tmpObject(', $varname, '[i]);')" />
+ </xsl:when>
+ <xsl:when test="$gluetypefield">
+ <xsl:value-of select="concat(' ', $gluetypefield, ' tmpObject(', $varname, '[i]);')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('emitOutputArgBackConverter (1): Type &quot;', $type, '&quot; in arg &quot;', $name, '&quot; of method &quot;', $method, '&quot; is not yet supported in safearrays.')" />
+ </xsl:call-template>
+
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="concat(' ', $receiverVariable, '.push_back(')" />
+ <xsl:call-template name="emitOutputArgBackConverter2">
+ <xsl:with-param name="name" select="$name"/>
+ <xsl:with-param name="varname" select="'tmpObject'"/>
+ <xsl:with-param name="type" select="$type"/>
+ <xsl:with-param name="callerprefix" select="$callerprefix"/>
+ </xsl:call-template>
+ <xsl:value-of select="');'" />
+ <xsl:call-template name="emitNewlineIndent8" />
+ <xsl:value-of select="'}'" />
+ <xsl:call-template name="emitNewline" />
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- emit variable name: "resp->retval = " -->
+ <xsl:value-of select="$receiverVariable" />
+
+ <xsl:value-of select="' = '" />
+ <xsl:call-template name="emitOutputArgBackConverter2">
+ <xsl:with-param name="name" select="$name"/>
+ <xsl:with-param name="varname" select="$varname"/>
+ <xsl:with-param name="type" select="$type"/>
+ <xsl:with-param name="callerprefix" select="$callerprefix"/>
+ </xsl:call-template>
+ <xsl:value-of select="';'" />
+ <xsl:call-template name="emitNewline" />
+
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:value-of select="concat(' WEBDEBUG((g_pcszDoneConvertingComOutputBack, &quot;', $name, '&quot;));')" />
+ <xsl:call-template name="emitNewline" />
+</xsl:template>
+
+<!--
+ emitGetAttributeComCall
+ -->
+<xsl:template name="emitGetAttributeComCall">
+ <xsl:param name="ifname" />
+ <xsl:param name="object" /> <!-- normally "pObj->" -->
+ <xsl:param name="attrname" />
+ <xsl:param name="attrtype" />
+ <xsl:param name="attrsafearray" />
+ <xsl:param name="varprefix" /> <!-- only when called recursively from emitOutputArgBackConverter-->
+ <xsl:param name="callerprefix" /> <!-- only when called recursively from emitOutputArgBackConverter-->
+
+ <xsl:variable name="gettername"><xsl:call-template name="makeGetterName"><xsl:with-param name="attrname" select="$attrname" /></xsl:call-template></xsl:variable>
+ <xsl:call-template name="emitOutputArgBuffer">
+ <xsl:with-param name="ifname"><xsl:value-of select="$ifname" /></xsl:with-param>
+ <xsl:with-param name="method"><xsl:value-of select="$gettername" /></xsl:with-param>
+ <xsl:with-param name="name" select="$attrname" />
+ <xsl:with-param name="type" select="$attrtype" />
+ <xsl:with-param name="safearray" select="$attrsafearray" />
+ <xsl:with-param name="varprefix" select="$varprefix" />
+ </xsl:call-template>
+ <xsl:variable name="upperattrname"><xsl:call-template name="capitalize"><xsl:with-param name="str" select="$attrname" /></xsl:call-template></xsl:variable>
+ <!-- actual COM method call -->
+ <xsl:call-template name="emitComCall">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="concat('COMGETTER(', $upperattrname, ')')" />
+ <xsl:with-param name="object" select="$object" />
+ <xsl:with-param name="attrname" select="$attrname" />
+ <xsl:with-param name="attrtype" select="$attrtype" />
+ <xsl:with-param name="attrsafearray" select="$attrsafearray" />
+ <xsl:with-param name="attrdir" select="'return'" />
+ <xsl:with-param name="varprefix" select="$varprefix" />
+ </xsl:call-template>
+ <!-- convert back the output data -->
+ <xsl:call-template name="emitOutputArgBackConverter">
+ <xsl:with-param name="ifname"><xsl:value-of select="$ifname" /></xsl:with-param>
+ <xsl:with-param name="method"><xsl:value-of select="$gettername" /></xsl:with-param>
+ <xsl:with-param name="name" select="$attrname" />
+ <xsl:with-param name="type" select="$attrtype" />
+ <xsl:with-param name="safearray" select="$attrsafearray" />
+ <xsl:with-param name="varprefix" select="$varprefix" />
+ <xsl:with-param name="callerprefix" select="$callerprefix" />
+ </xsl:call-template>
+</xsl:template>
+
+<!--
+ emitSetAttributeComCall
+ -->
+<xsl:template name="emitSetAttributeComCall">
+ <xsl:param name="ifname" />
+ <xsl:param name="object" /> <!-- normally "pObj->" -->
+ <xsl:param name="attrname" />
+ <xsl:param name="attrtype" />
+ <xsl:param name="attrsafearray" />
+ <xsl:param name="callerprefix" /> <!-- only when called recursively from emitOutputArgBackConverter-->
+
+ <xsl:variable name="settername"><xsl:call-template name="makeSetterName"><xsl:with-param name="attrname" select="$attrname" /></xsl:call-template></xsl:variable>
+ <xsl:variable name="upperattrname"><xsl:call-template name="capitalize"><xsl:with-param name="str" select="$attrname" /></xsl:call-template></xsl:variable>
+
+ <xsl:call-template name="emitInputArgConverter">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="method" select="concat($ifname, '::', $settername)" />
+ <xsl:with-param name="methodname" select="concat('COMSETTER(', $upperattrname, ')')" />
+ <xsl:with-param name="object" select="$object" />
+ <xsl:with-param name="name" select="$attrname" />
+ <xsl:with-param name="structprefix" select="concat($G_requestElementVarName, '-&gt;')" />
+ <xsl:with-param name="type" select="$attrtype" />
+ <xsl:with-param name="safearray" select="$attrsafearray" />
+ </xsl:call-template>
+ <xsl:call-template name="emitComCall">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="concat('COMSETTER(', $upperattrname, ')')" />
+ <xsl:with-param name="object" select="$object" />
+ <xsl:with-param name="attrname" select="$attrname" />
+ <xsl:with-param name="attrtype" select="$attrtype" />
+ <xsl:with-param name="attrsafearray" select="$attrsafearray" />
+ <xsl:with-param name="attrdir" select="'in'" />
+ </xsl:call-template>
+</xsl:template>
+
+<!--
+ emitGetAttributeMapper
+ -->
+<xsl:template name="emitGetAttributeMapper">
+ <xsl:param name="ifname" />
+ <xsl:param name="wsmap" />
+ <xsl:param name="attrname" />
+ <xsl:param name="attrtype" />
+ <xsl:param name="attrreadonly" />
+ <xsl:param name="attrsafearray" />
+
+ <xsl:variable name="gettername"><xsl:call-template name="makeGetterName"><xsl:with-param name="attrname" select="$attrname" /></xsl:call-template></xsl:variable>
+
+ <xsl:call-template name="emitBeginOfFunctionHeader">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="method" select="$gettername" />
+ </xsl:call-template>
+
+ <xsl:call-template name="emitDocumentStyleArgStructs">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="$gettername" />
+ <xsl:with-param name="fOutputs" select="$attrtype" />
+ </xsl:call-template>
+
+ <xsl:text>)</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:text>{</xsl:text>
+ <xsl:call-template name="emitNewline" />
+
+ <xsl:value-of select="' HRESULT rc = S_OK;'" />
+ <xsl:call-template name="emitNewline" />
+
+ <xsl:call-template name="emitPrologue" />
+
+ <!-- actual COM method call -->
+ <!-- <xsl:choose>
+ array attributes/parameters are not supported yet...
+ <xsl:when test="@array or @safearray='yes'">
+ <xsl:call-template name="warning"><xsl:with-param name="msg" select="concat('emitComCall: SKIPPING ATTRIBUTE IMPLEMENTATION for &quot;', $attrname, '&quot; because it has array type. THIS SOAP METHOD WILL NOT DO ANYTHING!')" /></xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise> -->
+ <xsl:call-template name="emitObjForMethod">
+ <xsl:with-param name="ifname"><xsl:value-of select="$ifname" /></xsl:with-param>
+ <xsl:with-param name="wsmap"><xsl:value-of select="$wsmap" /></xsl:with-param>
+ <xsl:with-param name="structprefix" select="concat($G_requestElementVarName, '-&gt;')" />
+ </xsl:call-template>
+
+ <xsl:call-template name="emitGetAttributeComCall">
+ <xsl:with-param name="ifname"><xsl:value-of select="$ifname" /></xsl:with-param>
+ <xsl:with-param name="object" select='"pObj"' />
+ <xsl:with-param name="attrname"><xsl:value-of select="$attrname" /></xsl:with-param>
+ <xsl:with-param name="attrtype"><xsl:value-of select="$attrtype" /></xsl:with-param>
+ <xsl:with-param name="attrsafearray"><xsl:value-of select="$attrsafearray" /></xsl:with-param>
+ </xsl:call-template>
+ <!-- </xsl:otherwise>
+ </xsl:choose> -->
+
+ <xsl:call-template name="emitEpilogue" />
+</xsl:template>
+
+<!--
+ emitSetAttributeMapper:
+ -->
+<xsl:template name="emitSetAttributeMapper">
+ <xsl:param name="ifname" select="$ifname" />
+ <xsl:param name="wsmap" select="$wsmap" />
+ <xsl:param name="attrname" select="$attrname" />
+ <xsl:param name="attrtype" select="$attrtype" />
+ <xsl:param name="attrreadonly" select="$attrreadonly" />
+ <xsl:param name="attrsafearray" select="$attrsafearray" />
+
+ <xsl:variable name="settername"><xsl:call-template name="makeSetterName"><xsl:with-param name="attrname" select="$attrname" /></xsl:call-template></xsl:variable>
+
+ <xsl:call-template name="emitBeginOfFunctionHeader">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="method" select="$settername" />
+ </xsl:call-template>
+
+ <xsl:call-template name="emitDocumentStyleArgStructs">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="$settername" />
+ <xsl:with-param name="fOutputs" select="1" />
+ </xsl:call-template>
+
+ <xsl:text>)</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:text>{</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="' HRESULT rc = S_OK;'" />
+ <xsl:value-of select="concat(concat(' NOREF(', $G_responseElementVarName),');')" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:call-template name="emitPrologue" />
+
+ <!-- actual COM method call -->
+ <!-- <xsl:choose>
+ array attributes/parameters are not supported yet...
+ <xsl:when test="@array or @safearray='yes'">
+ <xsl:call-template name="warning"><xsl:with-param name="msg" select="concat('emitComCall: SKIPPING ATTRIBUTE IMPLEMENTATION for &quot;', $attrname, '&quot; because it has array type. THIS SOAP METHOD WILL NOT DO ANYTHING!')" /></xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise> -->
+ <xsl:call-template name="emitObjForMethod">
+ <xsl:with-param name="ifname"><xsl:value-of select="$ifname" /></xsl:with-param>
+ <xsl:with-param name="wsmap"><xsl:value-of select="$wsmap" /></xsl:with-param>
+ <xsl:with-param name="structprefix" select="concat($G_requestElementVarName, '-&gt;')" />
+ </xsl:call-template>
+ <xsl:call-template name="emitSetAttributeComCall">
+ <xsl:with-param name="ifname"><xsl:value-of select="$ifname" /></xsl:with-param>
+ <xsl:with-param name="object" select='"pObj"' />
+ <xsl:with-param name="attrname"><xsl:value-of select="$attrname" /></xsl:with-param>
+ <xsl:with-param name="attrtype"><xsl:value-of select="$attrtype" /></xsl:with-param>
+ <xsl:with-param name="attrsafearray"><xsl:value-of select="$attrsafearray" /></xsl:with-param>
+ </xsl:call-template>
+ <!-- </xsl:otherwise>
+ </xsl:choose> -->
+
+ <xsl:call-template name="emitEpilogue" />
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ interface
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="interface">
+ <!-- remember the interface name in local variables -->
+ <xsl:variable name="ifname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="wsmap"><xsl:value-of select="@wsmap" /></xsl:variable>
+ <xsl:variable name="wscpp"><xsl:value-of select="@wscpp" /></xsl:variable>
+
+ <!-- we can save ourselves verifying the interface here as it's already
+ done in the WSDL converter -->
+
+ <xsl:if test='not( ($wsmap="suppress") or ($wsmap="struct") or ($wscpp="hardcoded") )'>
+ <xsl:text>
+/****************************************************************************
+ *
+ * interface </xsl:text>
+<xsl:copy-of select="$ifname" />
+<xsl:text>
+ *
+ ****************************************************************************/</xsl:text>
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+
+ <!--
+ here come the attributes
+ -->
+ <xsl:for-each select="attribute">
+ <xsl:variable name="attrname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="attrtype"><xsl:value-of select="@type" /></xsl:variable>
+ <xsl:variable name="attrreadonly"><xsl:value-of select="@readonly" /></xsl:variable>
+ <xsl:variable name="attrsafearray"><xsl:value-of select="@safearray" /></xsl:variable>
+ <xsl:call-template name="emitNewline" />
+ <!-- skip this attribute if it has parameters of a type that has wsmap="suppress" -->
+ <xsl:choose>
+ <xsl:when test="( $attrtype=($G_setSuppressedInterfaces/@name) )">
+ <xsl:value-of select="concat('// Skipping attribute ', $attrname, ' for it is of suppressed type ', $attrtype)" />
+ </xsl:when>
+ <xsl:when test="@wsmap = 'suppress'">
+ <xsl:value-of select="concat('// Skipping attribute ', $attrname, ' for it is suppressed')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="@readonly='yes'">
+ <xsl:value-of select="concat('// read-only attribute ', $ifname, '::', $attrname, ' of type ', $attrtype)" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('// read/write attribute ', $ifname, '::', $attrname, ' of type ', $attrtype)" />
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:value-of select="concat(' (safearray: ', $attrsafearray, ')')" />
+ <!-- emit getter method -->
+ <xsl:call-template name="emitGetAttributeMapper">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="wsmap" select="$wsmap" />
+ <xsl:with-param name="attrname" select="$attrname" />
+ <xsl:with-param name="attrtype" select="$attrtype" />
+ <xsl:with-param name="attrreadonly" select="$attrreadonly" />
+ <xsl:with-param name="attrsafearray" select="$attrsafearray" />
+ </xsl:call-template>
+ <!-- for read-write attributes, emit setter method -->
+ <xsl:if test="not(@readonly='yes')">
+ <xsl:call-template name="emitSetAttributeMapper">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="wsmap" select="$wsmap" />
+ <xsl:with-param name="attrname" select="$attrname" />
+ <xsl:with-param name="attrtype" select="$attrtype" />
+ <xsl:with-param name="attrreadonly" select="$attrreadonly" />
+ <xsl:with-param name="attrsafearray" select="$attrsafearray" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise> <!-- not wsmap=suppress -->
+ </xsl:choose>
+ </xsl:for-each>
+
+ <!--
+ here come the real methods
+ -->
+
+ <xsl:for-each select="method">
+ <xsl:variable name="methodname"><xsl:value-of select="@name" /></xsl:variable>
+ <!-- method header: return value "int", method name, soap arguments -->
+ <!-- skip this method if it has parameters of a type that has wsmap="suppress" -->
+ <xsl:choose>
+ <xsl:when test=" (param[@type=($G_setSuppressedInterfaces/@name)])
+ or (param[@mod='ptr'])" >
+ <xsl:comment><xsl:value-of select="concat('Skipping method ', $methodname, ' for it has parameters with suppressed types')" /></xsl:comment>
+ </xsl:when>
+ <xsl:when test="@wsmap = 'suppress'">
+ <xsl:comment><xsl:value-of select="concat('Skipping method ', $methodname, ' for it is suppressed')" /></xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:variable name="fHasReturnParms" select="param[@dir='return']" />
+ <xsl:variable name="fHasOutParms" select="param[@dir='out']" />
+
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="concat('/* method ', $ifname, '::', $methodname, '(')" />
+ <xsl:for-each select="param">
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="concat(' [', @dir, '] ', @type, ' ', @name)" />
+ <xsl:if test="@safearray='yes'">
+ <xsl:text>[]</xsl:text>
+ </xsl:if>
+ <xsl:if test="not(position()=last())">
+ <xsl:text>,</xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>)</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:text> */</xsl:text>
+
+ <xsl:call-template name="emitBeginOfFunctionHeader">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="method" select="$methodname" />
+ </xsl:call-template>
+
+ <xsl:call-template name="emitDocumentStyleArgStructs">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="$methodname" />
+ <xsl:with-param name="fOutputs" select="1" />
+ </xsl:call-template>
+ <xsl:text>)</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:text>{</xsl:text>
+ <xsl:call-template name="emitNewline" />
+ <xsl:value-of select="' HRESULT rc = S_OK;'" />
+ <xsl:value-of select="concat(concat(' NOREF(', $G_responseElementVarName),');')" />
+ <xsl:call-template name="emitNewline" />
+ <xsl:call-template name="emitPrologue" />
+
+ <xsl:choose>
+ <xsl:when test="param[@array]">
+ <xsl:call-template name="warning"><xsl:with-param name="msg" select="concat('emitComCall: SKIPPING METHOD IMPLEMENTATION for &quot;', $methodname, '&quot; because it has arguments with &quot;array&quot; types. THIS SOAP METHOD WILL NOT DO ANYTHING!')" /></xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- emit the object upon which to invoke the method -->
+ <xsl:call-template name="emitObjForMethod">
+ <xsl:with-param name="ifname"><xsl:value-of select="$ifname" /></xsl:with-param>
+ <xsl:with-param name="wsmap"><xsl:value-of select="$wsmap" /></xsl:with-param>
+ <xsl:with-param name="structprefix" select="concat($G_requestElementVarName, '-&gt;')" />
+ </xsl:call-template>
+ <!-- next, emit storage variables to convert the SOAP/C++ arguments to COM types -->
+ <xsl:for-each select="param">
+ <xsl:variable name="dir" select="@dir" />
+ <xsl:choose>
+ <xsl:when test="$dir='in'">
+ <xsl:call-template name="emitInputArgConverter">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="method" select="concat($ifname, '::', $methodname)" />
+ <xsl:with-param name="methodname">
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="$methodname" />
+ </xsl:call-template>
+ </xsl:with-param>
+ <xsl:with-param name="object" select='"pObj"' />
+ <xsl:with-param name="structprefix" select="concat($G_requestElementVarName, '-&gt;')" />
+ <xsl:with-param name="name" select="@name" />
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="$dir='out'">
+ <xsl:call-template name="emitOutputArgBuffer">
+ <xsl:with-param name="method" select="concat($ifname, '::', $methodname)" />
+ <xsl:with-param name="name" select="@name" />
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="$dir='return'">
+ <xsl:call-template name="emitOutputArgBuffer">
+ <xsl:with-param name="method" select="concat($ifname, '::', $methodname)" />
+ <xsl:with-param name="name" select="$G_result" />
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ </xsl:call-template>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:for-each>
+ <!-- actual COM method call -->
+ <xsl:call-template name="emitComCall">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="object" select='"pObj"' />
+ <xsl:with-param name="methodname">
+ <xsl:call-template name="capitalize">
+ <xsl:with-param name="str" select="$methodname" />
+ </xsl:call-template>
+ </xsl:with-param>
+ </xsl:call-template>
+ <!-- convert back the output data -->
+ <xsl:for-each select="param">
+ <xsl:variable name="dir" select="@dir" />
+ <xsl:if test="$dir='out'">
+ <xsl:call-template name="emitOutputArgBackConverter">
+ <xsl:with-param name="ifname"><xsl:value-of select="$ifname" /></xsl:with-param>
+ <xsl:with-param name="method" select="$methodname" />
+ <xsl:with-param name="name"><xsl:value-of select="@name" /></xsl:with-param>
+ <xsl:with-param name="type"><xsl:value-of select="@type" /></xsl:with-param>
+ <xsl:with-param name="safearray"><xsl:value-of select="@safearray" /></xsl:with-param>
+ <xsl:with-param name="callerprefix" select="'outparms.'"/>
+ </xsl:call-template>
+ </xsl:if>
+ <xsl:if test="$dir='return'">
+ <!-- return values _normally_ should convert to the input arg from the function prototype,
+ except when there are both return and out params; in that case gsoap squeezes them all
+ into the output args structure and the return thing is called "retval" -->
+ <xsl:choose>
+ <xsl:when test="$fHasOutParms">
+ <xsl:call-template name="emitOutputArgBackConverter">
+ <xsl:with-param name="ifname"><xsl:value-of select="$ifname" /></xsl:with-param>
+ <xsl:with-param name="method" select="$methodname" />
+ <xsl:with-param name="name"><xsl:value-of select="$G_result" /></xsl:with-param>
+ <xsl:with-param name="type"><xsl:value-of select="@type" /></xsl:with-param>
+ <xsl:with-param name="safearray"><xsl:value-of select="@safearray" /></xsl:with-param>
+ <xsl:with-param name="callerprefix" select="'outparms.'"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="emitOutputArgBackConverter">
+ <xsl:with-param name="ifname"><xsl:value-of select="$ifname" /></xsl:with-param>
+ <xsl:with-param name="method" select="$methodname" />
+ <xsl:with-param name="name"><xsl:value-of select="$G_result" /></xsl:with-param>
+ <xsl:with-param name="type"><xsl:value-of select="@type" /></xsl:with-param>
+ <xsl:with-param name="safearray"><xsl:value-of select="@safearray" /></xsl:with-param>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:if>
+ </xsl:for-each>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:call-template name="emitEpilogue" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </xsl:if>
+
+</xsl:template>
+
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/webservice/websrv-nsmap.xsl b/src/VBox/Main/webservice/websrv-nsmap.xsl
new file mode 100644
index 00000000..ddf349e4
--- /dev/null
+++ b/src/VBox/Main/webservice/websrv-nsmap.xsl
@@ -0,0 +1,148 @@
+<?xml version="1.0"?>
+
+<!--
+ websrv-nsmap.xsl:
+ XSLT stylesheet that generates a vboxweb.nsmap file from
+ VirtualBox.xidl, which gets included from C++ client and
+ server code.
+ See webservice/Makefile.kmk for an overview of all the things
+ generated for the webservice.
+-->
+<!--
+ Copyright (C) 2006-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+
+ <xsl:output method="text"/>
+
+ <xsl:strip-space elements="*"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ global XSLT variables
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:variable name="G_xsltFilename" select="'websrv-typemap.xsl'" />
+
+<xsl:include href="../idl/typemap-shared.inc.xsl" />
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ root match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="/idl">
+ <xsl:text><![CDATA[
+/* DO NOT EDIT! This is a generated file.
+ * Generated from: src/VBox/Main/idl/VirtualBox.xidl (VirtualBox's interface definitions in XML)
+ * Generator: src/VBox/Main/webservice/websrv-nsmap.xsl */
+
+#include "soapH.h"
+SOAP_NMAC struct Namespace namespaces[] =
+{
+ {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/", "http://www.w3.org/*/soap-envelope", NULL},
+ {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/", "http://www.w3.org/*/soap-encoding", NULL},
+ {"xsi", "http://www.w3.org/2001/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance", NULL},
+ {"xsd", "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/*/XMLSchema", NULL},
+]]></xsl:text>
+
+ <xsl:value-of select="concat(' {&quot;vbox&quot;, &quot;', $G_targetNamespace, $G_targetNamespaceSeparator, '&quot;, NULL, NULL},')" />
+ <xsl:call-template name="emitNewline" />
+
+ <xsl:text><![CDATA[
+ {NULL, NULL, NULL, NULL}
+};
+
+]]></xsl:text>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ if
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<!--
+ * ignore all |if|s except those for WSDL target
+-->
+<xsl:template match="if">
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ cpp
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="cpp">
+<!-- ignore this -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ library
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="library">
+ <xsl:apply-templates />
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ class
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="module/class">
+<!-- TODO swallow for now -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ enum
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="enum">
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ const
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<!--
+<xsl:template match="const">
+ <xsl:apply-templates />
+</xsl:template>
+-->
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ desc
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="desc">
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ note
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="note">
+ <xsl:apply-templates />
+</xsl:template>
+
+<xsl:template match="interface | collection">
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/webservice/websrv-php.xsl b/src/VBox/Main/webservice/websrv-php.xsl
new file mode 100644
index 00000000..a6c503a6
--- /dev/null
+++ b/src/VBox/Main/webservice/websrv-php.xsl
@@ -0,0 +1,647 @@
+<xsl:stylesheet version = '1.0'
+ xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
+ xmlns:vbox="http://www.virtualbox.org/">
+
+<!--
+ websrv-php.xsl:
+ XSLT stylesheet that generates vboxServiceWrappers.php from
+ VirtualBox.xidl. This PHP file represents our
+ web service API. Depends on WSDL file for actual SOAP bindings.
+
+ Contributed by James Lucas (mjlucas at eng.uts.edu.au).
+-->
+<!--
+ Copyright (C) 2008-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+
+<xsl:output
+ method="text"
+ version="1.0"
+ encoding="utf-8"
+ indent="no"/>
+
+<xsl:include href="../idl/typemap-shared.inc.xsl" />
+
+<xsl:variable name="G_setSuppressedInterfaces"
+ select="//interface[@wsmap='suppress']" />
+
+<xsl:key name="G_keyInterfacesByName" match="//interface[@name]" use="@name"/>
+
+<xsl:template name="emitOutParam">
+ <xsl:param name="type" />
+ <xsl:param name="value" />
+ <xsl:param name="safearray" />
+
+ <xsl:choose>
+ <xsl:when test="$type='wstring' or $type='uuid'">
+ <xsl:call-template name="emitPrimitive">
+ <xsl:with-param name="type">string</xsl:with-param>
+ <xsl:with-param name="value" select="$value" />
+ <xsl:with-param name="safearray" select="$safearray"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="$type='boolean'">
+ <xsl:call-template name="emitPrimitive">
+ <xsl:with-param name="type">bool</xsl:with-param>
+ <xsl:with-param name="value" select="$value" />
+ <xsl:with-param name="safearray" select="$safearray"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="$type='short' or $type='unsigned short' or $type='long' or $type='octet'">
+ <xsl:call-template name="emitPrimitive">
+ <xsl:with-param name="type">int</xsl:with-param>
+ <xsl:with-param name="value" select="$value" />
+ <xsl:with-param name="safearray" select="$safearray"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="$type='double' or $type='float' or $type='unsigned long' or $type='long long' or $type='unsigned long long'">
+ <xsl:call-template name="emitPrimitive">
+ <xsl:with-param name="type">float</xsl:with-param>
+ <xsl:with-param name="value" select="$value" />
+ <xsl:with-param name="safearray" select="$safearray"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:when test="$type='$unknown'">
+ <xsl:call-template name="emitObject">
+ <xsl:with-param name="type">VBox_ManagedObject</xsl:with-param>
+ <xsl:with-param name="value" select="$value" />
+ <xsl:with-param name="safearray" select="$safearray"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="emitObject">
+ <xsl:with-param name="type" select="$type" />
+ <xsl:with-param name="value" select="$value" />
+ <xsl:with-param name="safearray" select="$safearray"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="emitObject">
+ <xsl:param name="type" />
+ <xsl:param name="value" />
+ <xsl:param name="safearray" />
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:text>new </xsl:text><xsl:value-of select="$type" />Collection ($this->connection, (array)<xsl:value-of select="$value"/><xsl:text>)</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>new </xsl:text><xsl:value-of select="$type" /> ($this->connection, <xsl:value-of select="$value"/><xsl:text>)</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="emitPrimitive">
+ <xsl:param name="type" />
+ <xsl:param name="value" />
+ <xsl:param name="safearray" />
+ <xsl:choose>
+ <xsl:when test="$safearray='yes'">
+ <xsl:text>(array)</xsl:text><xsl:value-of select="$value"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>(</xsl:text><xsl:value-of select="$type" /><xsl:text>)</xsl:text><xsl:value-of select="$value"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="emitGetAttribute">
+ <xsl:param name="ifname" />
+ <xsl:param name="attrname" />
+ <xsl:param name="attrtype" />
+ <xsl:param name="attrsafearray" />
+ <xsl:variable name="fname"><xsl:call-template name="makeGetterName"><xsl:with-param name="attrname" select="$attrname"/></xsl:call-template> </xsl:variable>
+ public function <xsl:value-of select="$fname"/>()
+ {
+ $request = new stdClass();
+ $request->_this = $this->handle;
+ $response = $this->connection->__soapCall('<xsl:value-of select="$ifname"/>_<xsl:value-of select="$fname"/>', array((array)$request));
+ <xsl:text>return </xsl:text>
+ <xsl:call-template name="emitOutParam">
+ <xsl:with-param name="type" select="$attrtype" />
+ <xsl:with-param name="value" select="concat('$response->','returnval')" />
+ <xsl:with-param name="safearray" select="@safearray"/>
+ </xsl:call-template><xsl:text>;</xsl:text>
+ }
+</xsl:template>
+
+<xsl:template name="emitSetAttribute">
+ <xsl:param name="ifname" />
+ <xsl:param name="attrname" />
+ <xsl:param name="attrtype" />
+ <xsl:param name="attrsafearray" />
+ <xsl:variable name="fname"><xsl:call-template name="makeSetterName"><xsl:with-param name="attrname" select="$attrname"/></xsl:call-template></xsl:variable>
+ public function <xsl:value-of select="$fname"/>($value)
+ {
+ $request = new stdClass();
+ $request->_this = $this->handle;
+<xsl:choose>
+<xsl:when test="$attrsafearray='yes'"> if (is_array($value) || is_null($value) || is_scalar($value))</xsl:when>
+<xsl:otherwise> if (is_null($value) || is_scalar($value))</xsl:otherwise>
+</xsl:choose>
+ {
+ $request-><xsl:value-of select="$attrname"/> = $value;
+ }
+ else
+ {
+ $request-><xsl:value-of select="$attrname"/> = $value->handle;
+ }
+ $this->connection->__soapCall('<xsl:value-of select="$ifname"/>_<xsl:value-of select="$fname"/>', array((array)$request));
+ }
+</xsl:template>
+
+<xsl:template name="interface">
+ <xsl:variable name="ifname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="wsmap"><xsl:value-of select="@wsmap" /></xsl:variable>
+ <xsl:variable name="extends"><xsl:value-of select="@extends" /></xsl:variable>
+ <xsl:text>
+/**
+ * Generated VBoxWebService Interface Wrapper
+ */
+</xsl:text>
+ <xsl:choose>
+ <xsl:when test="($extends = '$unknown') or ($extends = '$errorinfo')">
+ <xsl:value-of select="concat('class ', $ifname, ' extends VBox_ManagedObject&#10;{&#10;')" />
+ </xsl:when>
+ <xsl:when test="count(key('G_keyInterfacesByName', $extends)) > 0">
+ <xsl:value-of select="concat('class ', $ifname, ' extends ', $extends, '&#10;{&#10;')" />
+ </xsl:when>
+ </xsl:choose>
+ <xsl:for-each select="method">
+ <xsl:if test="not((param[@type=($G_setSuppressedInterfaces/@name)])
+ or (param[@mod='ptr']))" >
+ <xsl:call-template name="method">
+ <xsl:with-param name="wsmap" select="$wsmap" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:for-each select="attribute">
+ <xsl:variable name="attrname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="attrtype"><xsl:value-of select="@type" /></xsl:variable>
+ <xsl:variable name="attrreadonly"><xsl:value-of select="@readonly" /></xsl:variable>
+ <xsl:variable name="attrsafearray"><xsl:value-of select="@safearray" /></xsl:variable>
+ <!-- skip this attribute if it has parameters of a type that has wsmap="suppress" -->
+ <xsl:choose>
+ <xsl:when test="( $attrtype=($G_setSuppressedInterfaces/@name) )">
+ <xsl:comment><xsl:value-of select="concat('skipping attribute ', $attrtype, ' for it is of a suppressed type')" /></xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="@readonly='yes'">
+ <xsl:comment> readonly attribute <xsl:copy-of select="$ifname" />::<xsl:copy-of select="$attrname" /> </xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:comment> read/write attribute <xsl:copy-of select="$ifname" />::<xsl:copy-of select="$attrname" /> </xsl:comment>
+ </xsl:otherwise>
+ </xsl:choose>
+ <!-- aa) get method: emit request and result -->
+ <xsl:call-template name="emitGetAttribute">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="attrname" select="$attrname" />
+ <xsl:with-param name="attrtype" select="$attrtype" />
+ <xsl:with-param name="attrsafearray" select="$attrsafearray" />
+ </xsl:call-template>
+ <!-- bb) emit a set method if the attribute is read/write -->
+ <xsl:if test="not($attrreadonly='yes')">
+ <xsl:call-template name="emitSetAttribute">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="attrname" select="$attrname" />
+ <xsl:with-param name="attrtype" select="$attrtype" />
+ <xsl:with-param name="attrsafearray" select="$attrsafearray" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ <xsl:text>}
+</xsl:text>
+</xsl:template>
+
+<xsl:template name="collection">
+ <xsl:variable name="ifname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:text>
+/**
+ * Generated VBoxWebService Managed Object Collection
+ */</xsl:text>
+class <xsl:value-of select="$ifname"/>Collection extends VBox_ManagedObjectCollection
+{
+ protected $_interfaceName = "<xsl:value-of select="$ifname"/>";
+}
+</xsl:template>
+
+<xsl:template name="interfacestruct">
+ <xsl:variable name="ifname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:text>
+/**
+ * Generated VBoxWebService Struct
+ */</xsl:text>
+class <xsl:value-of select="$ifname"/> extends VBox_Struct
+{
+<xsl:for-each select="attribute"> protected $<xsl:value-of select="@name"/>;
+</xsl:for-each>
+ public function __construct($connection, $values)
+ {
+ $this->connection = $connection;
+<xsl:for-each select="attribute"> $this-><xsl:value-of select="@name"/> = $values-><xsl:value-of select="@name"/>;
+</xsl:for-each> }
+
+<xsl:for-each select="attribute"> public function <xsl:call-template name="makeGetterName"><xsl:with-param name="attrname" select="@name"/></xsl:call-template>()
+ {
+ <xsl:text>return </xsl:text>
+ <xsl:call-template name="emitOutParam">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="value" select="concat('$this->',@name)" />
+ <xsl:with-param name="safearray" select="@safearray"/>
+ </xsl:call-template>;
+ }
+</xsl:for-each>}
+</xsl:template>
+
+<xsl:template name="structcollection">
+ <xsl:variable name="ifname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:text>
+/**
+ * Generated VBoxWebService Struct Collection
+ */</xsl:text>
+class <xsl:value-of select="$ifname"/>Collection extends VBox_StructCollection
+{
+ protected $_interfaceName = "<xsl:value-of select="$ifname"/>";
+}
+</xsl:template>
+
+<xsl:template name="genreq">
+ <xsl:param name="wsmap" />
+ <xsl:text> $request = new stdClass();
+</xsl:text>
+ <xsl:if test="$wsmap='managed'"> $request->_this = $this->handle;</xsl:if>
+ <xsl:for-each select="param[@dir='in']">
+ $request-><xsl:value-of select="@name" /> = $arg_<xsl:value-of select="@name" /><xsl:text>;</xsl:text>
+ </xsl:for-each>
+ $response = $this->connection->__soapCall('<xsl:value-of select="../@name"/>_<xsl:value-of select="@name"/>', array((array)$request));
+ return <xsl:if test="param[@dir='out']">
+ <xsl:text>array(</xsl:text>
+ </xsl:if>
+ <xsl:for-each select="param[@dir='return']">
+ <xsl:call-template name="emitOutParam">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="value" select="concat('$response->','returnval')" />
+ <xsl:with-param name="safearray" select="@safearray"/>
+ </xsl:call-template>
+ <xsl:if test="../param[@dir='out']">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:for-each select="param[@dir='out']">
+ <xsl:if test="not(position()=1)">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+ <xsl:call-template name="emitOutParam">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="value" select="concat('$response->',@name)" />
+ <xsl:with-param name="safearray" select="@safearray"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ <xsl:if test="param[@dir='out']">
+ <xsl:text>)</xsl:text>
+ </xsl:if>
+ <xsl:text>;&#10;</xsl:text>
+</xsl:template>
+
+<xsl:template name="method" >
+ <xsl:param name="wsmap" />
+ public function <xsl:value-of select="@name"/><xsl:text>(</xsl:text>
+ <xsl:for-each select="param[@dir='in']">
+ <xsl:if test="not(position()=1)">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+ <xsl:value-of select="concat('$arg_',@name)"/>
+ </xsl:for-each> <xsl:text>)&#10; {&#10;</xsl:text>
+ <xsl:call-template name="genreq"><xsl:with-param name="wsmap" select="$wsmap" /></xsl:call-template>
+ <xsl:text> }&#10;</xsl:text>
+</xsl:template>
+
+<xsl:template name="enum">
+ <xsl:text>
+/**
+ * Generated VBoxWebService ENUM
+ */</xsl:text>
+class <xsl:value-of select="@name"/> extends VBox_Enum
+{
+ public $NameMap = array(<xsl:for-each select="const"><xsl:if test="not(@wsmap='suppress')"><xsl:value-of select="@value"/> => '<xsl:value-of select="@name"/>'<xsl:if test="not(position()=last())">, </xsl:if></xsl:if></xsl:for-each>);
+ public $ValueMap = array(<xsl:for-each select="const"><xsl:if test="not(@wsmap='suppress')">'<xsl:value-of select="@name"/>' => <xsl:value-of select="@value"/><xsl:if test="not(position()=last())">, </xsl:if></xsl:if></xsl:for-each>);
+}
+</xsl:template>
+
+<xsl:template name="enumcollection">
+ <xsl:variable name="ifname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:text>
+/**
+ * Generated VBoxWebService Enum Collection
+ */</xsl:text>
+class <xsl:value-of select="$ifname"/>Collection extends VBox_EnumCollection
+{
+ protected $_interfaceName = "<xsl:value-of select="$ifname"/>";
+}
+</xsl:template>
+
+<xsl:template name="comResultCodes">
+ const <xsl:value-of select="@name"/> = <xsl:value-of select="@value"/>;
+</xsl:template>
+
+<xsl:template match="/">
+<xsl:text>&lt;?php
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of a free software library; you can redistribute
+ * it and/or modify it under the terms of the GNU Lesser General
+ * Public License version 2.1 as published by the Free Software
+ * Foundation and shipped in the "COPYING.LIB" file with this library.
+ * The library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY of any kind.
+ *
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if
+ * any license choice other than GPL or LGPL is available it will
+ * apply instead, Oracle elects to use only the Lesser General Public
+ * License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the
+ * language indicating that LGPLv2 or any later version may be used,
+ * or where a choice of which version of the LGPL is applied is
+ * otherwise unspecified.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+/*
+ * This file is autogenerated from VirtualBox.xidl, DO NOT EDIT!
+ */
+
+class VBox_ManagedObject
+{
+ protected $connection;
+ protected $handle;
+
+ public function __construct($soap, $handle = null)
+ {
+ $this->connection = $soap;
+ $this->handle = $handle;
+ }
+
+ public function __toString()
+ {
+ return (string)$this->handle;
+ }
+
+ public function __set($attr, $value)
+ {
+ $methodName = "set" . $attr;
+ if (method_exists($this, $methodName))
+ $this->$methodName($value);
+ else
+ throw new Exception("Attribute does not exist");
+ }
+
+ public function __get($attr)
+ {
+ $methodName = "get" . $attr;
+ if (method_exists($this, $methodName))
+ return $this->$methodName();
+ else
+ throw new Exception("Attribute does not exist");
+ }
+
+ public function getHandle()
+ {
+ return $this->handle;
+ }
+
+ public function cast($class)
+ {
+ if (is_subclass_of($class, 'VBox_ManagedObject'))
+ {
+ return new $class($this->connection, $this->handle);
+ }
+ throw new Exception('Cannot cast VBox_ManagedObject to non-child class VBox_ManagedObject');
+ }
+
+ public function releaseRemote()
+ {
+ try
+ {
+ $request = new stdClass();
+ $request->_this = $this->handle;
+ $this->connection->__soapCall('IManagedObjectRef_release', array((array)$request));
+ }
+ catch (Exception $ex)
+ {
+ }
+ }
+}
+
+abstract class VBox_Collection implements ArrayAccess, Iterator, Countable
+{
+ protected $_connection;
+ protected $_values;
+ protected $_objects;
+ protected $_interfaceName;
+
+ public function __construct($soap, array $values = array())
+ {
+ $this->_connection = $soap;
+ $this->_values = $values;
+ $this->_soapToObject();
+ }
+
+ protected function _soapToObject()
+ {
+ $this->_objects = array();
+ foreach($this->_values as $value)
+ {
+ $this->_objects[] = new $this->_interfaceName($this->_connection, $value);
+ }
+ }
+
+ /** ArrayAccess Functions **/
+ public function offsetSet($offset, $value)
+ {
+ if ($value instanceof $this->_interfaceName)
+ {
+ if ($offset)
+ {
+ $this->_objects[$offset] = $value;
+ }
+ else
+ {
+ $this->_objects[] = $value;
+ }
+ }
+ else
+ {
+ throw new Exception("Value must be a instance of " . $this->_interfaceName);
+ }
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->_objects[$offset]);
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->_objects[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return isset($this->_objects[$offset]) ? $this->_objects[$offset] : null;
+ }
+
+ /** Iterator Functions **/
+ public function rewind()
+ {
+ reset($this->_objects);
+ }
+
+ public function current()
+ {
+ return current($this->_objects);
+ }
+
+ public function key()
+ {
+ return key($this->_objects);
+ }
+
+ public function next()
+ {
+ return next($this->_objects);
+ }
+
+ public function valid()
+ {
+ return ($this->current() !== false);
+ }
+
+ /** Countable Functions **/
+ public function count()
+ {
+ return count($this->_objects);
+ }
+}
+
+class VBox_ManagedObjectCollection extends VBox_Collection
+{
+ protected $_interfaceName = 'VBox_ManagedObject';
+
+ // Result is undefined if this is called AFTER any call to VBox_Collection::offsetSet or VBox_Collection::offsetUnset
+ public function setInterfaceName($interface)
+ {
+ if (!is_subclass_of($interface, 'VBox_ManagedObject'))
+ {
+ throw new Exception('Cannot set collection interface to non-child class of VBox_ManagedObject');
+ }
+ $this->_interfaceName = $interface;
+ $this->_soapToObject();
+ }
+}
+
+abstract class VBox_Struct
+{
+ protected $connection;
+
+ public function __get($attr)
+ {
+ $methodName = "get" . $attr;
+ if (method_exists($this, $methodName))
+ return $this->$methodName();
+ else
+ throw new Exception("Attribute does not exist");
+ }
+}
+
+abstract class VBox_StructCollection extends VBox_Collection
+{
+
+ public function __construct($soap, array $values = array())
+ {
+ if (!(array_values($values) === $values))
+ {
+ $values = array((object)$values); //Fix for when struct return value only contains one list item (e.g. one medium attachment)
+ }
+ parent::__construct($soap, $values);
+ }
+}
+
+abstract class VBox_Enum
+{
+ protected $_handle;
+
+ public function __construct($connection, $handle)
+ {
+ if (is_string($handle))
+ $this->_handle = $this->ValueMap[$handle];
+ else
+ $this->_handle = $handle;
+ }
+
+ public function __toString()
+ {
+ return (string)$this->NameMap[$this->_handle];
+ }
+}
+
+abstract class VBox_EnumCollection extends VBox_Collection
+{
+}
+
+</xsl:text>
+
+<xsl:text>
+/**
+ * VirtualBox COM result codes
+ */
+class VirtualBox_COM_result_codes
+{
+</xsl:text>
+ <xsl:for-each select="/idl/library/result">
+ <xsl:call-template name="comResultCodes"/>
+ </xsl:for-each>
+<xsl:text>
+}
+</xsl:text>
+ <xsl:for-each select="//interface[@wsmap='managed' or @wsmap='global']">
+ <xsl:call-template name="interface"/>
+ <xsl:call-template name="collection"/>
+ </xsl:for-each>
+ <xsl:for-each select="//interface[@wsmap='struct']">
+ <xsl:call-template name="interfacestruct"/>
+ <xsl:call-template name="structcollection"/>
+ </xsl:for-each>
+ <xsl:for-each select="//enum">
+ <xsl:call-template name="enum"/>
+ <xsl:call-template name="enumcollection"/>
+ </xsl:for-each>
+
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/webservice/websrv-python.xsl b/src/VBox/Main/webservice/websrv-python.xsl
new file mode 100644
index 00000000..7a32cb9d
--- /dev/null
+++ b/src/VBox/Main/webservice/websrv-python.xsl
@@ -0,0 +1,923 @@
+<xsl:stylesheet version = '1.0'
+ xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
+ xmlns:vbox="http://www.virtualbox.org/">
+
+<!--
+ websrv-python.xsl:
+ XSLT stylesheet that generates VirtualBox_services.py from
+ VirtualBox.xidl. This Python file represents our
+ web service API. Depends on WSDL file for actual SOAP bindings.
+-->
+<!--
+ Copyright (C) 2008-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+
+<xsl:output
+ method="text"
+ version="1.0"
+ encoding="utf-8"
+ indent="no"/>
+
+<xsl:include href="../idl/typemap-shared.inc.xsl" />
+
+<xsl:variable name="G_setSuppressedInterfaces"
+ select="//interface[@wsmap='suppress']" />
+
+<xsl:template name="emitConvertedType">
+ <xsl:param name="ifname" />
+ <xsl:param name="methodname" />
+ <xsl:param name="type" />
+ <xsl:choose>
+ <xsl:when test="$type='wstring'">String</xsl:when>
+ <xsl:when test="$type='uuid'">String</xsl:when>
+ <xsl:when test="$type='boolean'">Boolean</xsl:when>
+ <xsl:when test="$type='unsigned long'">UnsignedInt</xsl:when>
+ <xsl:when test="$type='double'">Double</xsl:when>
+ <xsl:when test="$type='float'">Float</xsl:when>
+ <xsl:when test="$type='long'">Int</xsl:when>
+ <xsl:when test="$type='long long'">Long</xsl:when>
+ <xsl:when test="$type='short'">Short</xsl:when>
+ <xsl:when test="$type='unsigned short'">UnsignedShort</xsl:when>
+ <xsl:when test="$type='unsigned long long'">UnsignedLong</xsl:when>
+ <xsl:when test="$type='result'">UnsignedInt</xsl:when>
+ <xsl:when test="$type='octet'">Octet</xsl:when>
+ <xsl:when test="$type='$unknown'">IUnknown</xsl:when>
+ <xsl:otherwise><xsl:value-of select="$type" /></xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="emitOutParam">
+ <xsl:param name="ifname" />
+ <xsl:param name="methodname" />
+ <xsl:param name="type" />
+ <xsl:param name="value" />
+ <xsl:param name="safearray" />
+
+ <xsl:choose>
+ <xsl:when test="$type='octet' and $safearray">
+ <xsl:value-of select="concat('self.mgr.decodebase64(',$value,')')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="emitConvertedType">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="$methodname" />
+ <xsl:with-param name="type" select="$type" />
+ </xsl:call-template>
+ <xsl:text>(</xsl:text>
+ <xsl:text>self.mgr,</xsl:text>
+ <xsl:value-of select="$value"/>
+ <xsl:if test="$safearray='yes'">
+ <xsl:value-of select="', True'"/>
+ </xsl:if>
+ <xsl:text>)</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+
+<xsl:template name="emitGetAttribute">
+ <xsl:param name="ifname" />
+ <xsl:param name="attrname" />
+ <xsl:param name="attrtype" />
+ <xsl:param name="attrsafearray" />
+ <xsl:variable name="fname"><xsl:call-template name="makeGetterName"><xsl:with-param name="attrname" select="$attrname"/></xsl:call-template> </xsl:variable>
+ def <xsl:value-of select="$fname"/>(self):
+ req=<xsl:value-of select="$ifname"/>_<xsl:value-of select="$fname"/>RequestMsg()
+ req._this=self.handle
+ val=self.mgr.getPort().<xsl:value-of select="$ifname"/>_<xsl:value-of select="$fname"/>(req)
+ <xsl:text>return </xsl:text>
+ <xsl:call-template name="emitOutParam">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="@name" />
+ <xsl:with-param name="type" select="$attrtype" />
+ <xsl:with-param name="value" select="concat('val.','_returnval')" />
+ <xsl:with-param name="safearray" select="$attrsafearray"/>
+ </xsl:call-template>
+</xsl:template>
+
+<xsl:template name="emitSetAttribute">
+ <xsl:param name="ifname" />
+ <xsl:param name="attrname" />
+ <xsl:param name="attrtype" />
+ <xsl:param name="attrsafearray" />
+ <xsl:variable name="fname"><xsl:call-template name="makeSetterName"><xsl:with-param name="attrname" select="$attrname"/></xsl:call-template> </xsl:variable>
+ def <xsl:value-of select="$fname"/>(self, value):
+ req=<xsl:value-of select="$ifname"/>_<xsl:value-of select="$fname"/>RequestMsg()
+ req._this=self.handle
+ if type(value) in [int, bool, basestring, str<xsl:if test="$attrsafearray='yes'">, tuple, list</xsl:if>]:
+ req._<xsl:value-of select="$attrname"/> = value
+ else:
+ req._<xsl:value-of select="$attrname"/> = value.handle
+ self.mgr.getPort().<xsl:value-of select="$ifname"/>_<xsl:value-of select="$fname"/>(req)
+</xsl:template>
+
+<xsl:template name="collection">
+ <xsl:variable name="cname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="ename"><xsl:value-of select="@type" /></xsl:variable>
+class <xsl:value-of select="$cname"/>:
+ def __init__(self, mgr, array):
+ self.array = array
+ self.mgr = mgr
+
+ def __next(self):
+ return self.array.__next()
+
+ def __size(self):
+ return self.array._array.__size()
+
+ def __len__(self):
+ return self.array._array.__len__()
+
+ def __getitem__(self, index):
+ return <xsl:value-of select="$ename"/>(self.mgr, self.array._array[index])
+
+</xsl:template>
+
+
+<xsl:template name="computeExtends">
+ <xsl:param name="base" />
+
+ <xsl:choose>
+ <xsl:when test="($base = '$unknown')">
+ <xsl:value-of select="'IUnknown'"/>
+ </xsl:when>
+ <xsl:when test="($base = '$errorinfo') ">
+ <xsl:value-of select="'IUnknown'"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$base"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="interface">
+ <xsl:variable name="base">
+ <xsl:call-template name="computeExtends">
+ <xsl:with-param name="base" select="@extends" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="ifname"><xsl:value-of select="@name" /></xsl:variable>
+
+class <xsl:value-of select="$ifname"/>(<xsl:value-of select="$base" />):
+ def __init__(self, mgr, handle, isarray = False):
+ self.mgr = mgr
+ if handle is None:
+ raise Exception("bad handle: "+str(handle))
+ self.handle = handle
+ self.isarray = isarray
+ if self.isarray:
+ for strHnd in handle:
+ mgr.register(strHnd)
+ else:
+ mgr.register(self.handle)
+
+ def __del__(self):
+ self.releaseRemote()
+
+ def releaseRemote(self):
+ try:
+ if self.handle is not None:
+ if self.isarray:
+ for strHnd in self.handle:
+ self.mgr.unregister(strHnd)
+ else:
+ self.mgr.unregister(self.handle)
+ self.handle = None;
+ except:
+ pass
+
+ def __next(self):
+ if self.isarray:
+ return self.handle.__next()
+ raise TypeError("iteration over non-sequence")
+
+ def __size(self):
+ if self.isarray:
+ return self.handle.__size()
+ raise TypeError("iteration over non-sequence")
+
+ def __len__(self):
+ if self.isarray:
+ return self.handle.__len__()
+ raise TypeError("iteration over non-sequence")
+
+ def __getitem__(self, index):
+ if self.isarray:
+ return <xsl:value-of select="$ifname" />(self.mgr, self.handle[index])
+ raise TypeError("iteration over non-sequence")
+
+ def __str__(self):
+ if self.isarray:
+ return str(self.handle)
+ else:
+ return self.handle
+
+ def isValid(self):
+ return self.handle != None and self.handle != ''
+
+ def __getattr__(self,name):
+ hndl = <xsl:value-of select="$ifname" />._Attrs_.get(name, None)
+ if hndl != None:
+ if hndl[0] != None:
+ return hndl[0](self)
+ else:
+ raise AttributeError
+ else:
+ return <xsl:value-of select="$base" />.__getattr__(self, name)
+
+ def __setattr__(self, name, val):
+ hndl = <xsl:value-of select="$ifname" />._Attrs_.get(name, None)
+ if (hndl != None and hndl[1] != None):
+ hndl[1](self,val)
+ else:
+ self.__dict__[name] = val
+
+ <xsl:for-each select="method">
+ <xsl:call-template name="method"/>
+ </xsl:for-each>
+
+ <xsl:for-each select="attribute">
+ <xsl:variable name="attrname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="attrtype"><xsl:value-of select="@type" /></xsl:variable>
+ <xsl:variable name="attrreadonly"><xsl:value-of select="@readonly" /></xsl:variable>
+ <xsl:variable name="attrsafearray"><xsl:value-of select="@safearray" /></xsl:variable>
+ <!-- skip this attribute if it has parameters of a type that has wsmap="suppress" -->
+ <xsl:choose>
+ <xsl:when test="( $attrtype=($G_setSuppressedInterfaces/@name) )">
+ <xsl:comment><xsl:value-of select="concat('skipping attribute ', $attrtype, ' for it is of a suppressed type')" /></xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="@readonly='yes'">
+ <xsl:comment> readonly attribute <xsl:copy-of select="$ifname" />::<xsl:copy-of select="$attrname" /> </xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:comment> read/write attribute <xsl:copy-of select="$ifname" />::<xsl:copy-of select="$attrname" /> </xsl:comment>
+ </xsl:otherwise>
+ </xsl:choose>
+ <!-- aa) get method: emit request and result -->
+ <xsl:call-template name="emitGetAttribute">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="attrname" select="$attrname" />
+ <xsl:with-param name="attrtype" select="$attrtype" />
+ <xsl:with-param name="attrsafearray" select="$attrsafearray" />
+ </xsl:call-template>
+ <!-- bb) emit a set method if the attribute is read/write -->
+ <xsl:if test="not($attrreadonly='yes')">
+ <xsl:call-template name="emitSetAttribute">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="attrname" select="$attrname" />
+ <xsl:with-param name="attrtype" select="$attrtype" />
+ <xsl:with-param name="attrsafearray" select="$attrsafearray" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+
+
+ _Attrs_=<xsl:text>{</xsl:text>
+ <xsl:for-each select="attribute">
+ <xsl:if test="not( @type=($G_setSuppressedInterfaces/@name) )">
+ <xsl:text> </xsl:text>'<xsl:value-of select="@name"/>'<xsl:text>:[</xsl:text>
+ <xsl:call-template name="makeGetterName">
+ <xsl:with-param name="attrname" select="@name"/>
+ </xsl:call-template>
+ <xsl:text>,</xsl:text>
+ <xsl:choose>
+ <xsl:when test="@readonly='yes'">
+ <xsl:text>None</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="makeSetterName">
+ <xsl:with-param name="attrname" select="@name"/>
+ </xsl:call-template>,
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>]</xsl:text>
+ <xsl:if test="not(position()=last())"><xsl:text>,&#10;</xsl:text></xsl:if>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>}</xsl:text>
+</xsl:template>
+
+<xsl:template name="interfacestruct">
+ <xsl:variable name="ifname"><xsl:value-of select="@name" /></xsl:variable>
+class <xsl:value-of select="$ifname"/>:
+ def __init__(self, mgr, handle, isarray = False):
+ self.mgr = mgr
+ self.isarray = isarray
+ if isarray:
+ self.handle = handle
+ else:
+<xsl:for-each select="attribute">
+ self.<xsl:value-of select="@name"/> = <xsl:call-template name="emitConvertedType">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="''" />
+ <xsl:with-param name="type" select="@type" />
+ </xsl:call-template>(self.mgr, handle._<xsl:value-of select="@name"/>)
+ </xsl:for-each>
+ pass
+
+ <!-- also do getters/setters -->
+ <xsl:for-each select="attribute">
+ def <xsl:call-template name="makeGetterName"><xsl:with-param name="attrname" select="@name"/></xsl:call-template>(self):
+ return self.<xsl:value-of select="@name"/>
+
+ def <xsl:call-template name="makeSetterName"><xsl:with-param name="attrname" select="@name"/></xsl:call-template>(self):
+ raise Error('setters not supported')
+ </xsl:for-each>
+
+ def __next(self):
+ if self.isarray:
+ return self.handle.__next()
+ raise TypeError("iteration over non-sequence")
+
+ def __size(self):
+ if self.isarray:
+ return self.handle.__size()
+ raise TypeError("iteration over non-sequence")
+
+ def __len__(self):
+ if self.isarray:
+ return self.handle.__len__()
+ raise TypeError("iteration over non-sequence")
+
+ def __getitem__(self, index):
+ if self.isarray:
+ return <xsl:value-of select="$ifname" />(self.mgr, self.handle[index])
+ raise TypeError("iteration over non-sequence")
+<xsl:call-template name="xsltprocNewlineOutputHack"/>
+</xsl:template>
+
+<xsl:template name="convertInParam">
+ <xsl:param name="type" />
+ <xsl:param name="safearray" />
+ <xsl:param name="arg" />
+
+ <xsl:choose>
+ <xsl:when test="$type='octet' and $safearray">
+ <xsl:value-of select="concat('self.mgr.encodebase64(',$arg,')')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$arg" />
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="genreq">
+ <xsl:text>req=</xsl:text><xsl:value-of select="../@name"/>_<xsl:value-of select="@name"/>RequestMsg()
+ req._this=self.handle
+ <xsl:for-each select="param[@dir='in']">
+ req._<xsl:value-of select="@name" />=<xsl:call-template name="convertInParam">
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="arg" select="concat('_arg_', @name)" />
+ </xsl:call-template>
+ </xsl:for-each>
+ val=self.mgr.getPort().<xsl:value-of select="../@name"/>_<xsl:value-of select="@name"/>(req)
+ <!-- return needs to be the first one -->
+ return <xsl:for-each select="param[@dir='return']">
+ <xsl:call-template name="emitOutParam">
+ <xsl:with-param name="ifname" select="../@name" />
+ <xsl:with-param name="methodname" select="@name" />
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="value" select="concat('val.','_returnval')" />
+ <xsl:with-param name="safearray" select="@safearray"/>
+ </xsl:call-template>
+ <xsl:if test="../param[@dir='out']">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:for-each select="param[@dir='out']">
+ <xsl:if test="not(position()=1)">
+ <xsl:text>, </xsl:text>
+ </xsl:if>
+ <xsl:call-template name="emitOutParam">
+ <xsl:with-param name="ifname" select="../@name" />
+ <xsl:with-param name="methodname" select="@name" />
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="value" select="concat('val._',@name)" />
+ <xsl:with-param name="safearray" select="@safearray"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ <xsl:text>&#10;&#10;</xsl:text>
+</xsl:template>
+
+<xsl:template name="method" >
+ def <xsl:value-of select="@name"/><xsl:text>(self</xsl:text>
+ <xsl:for-each select="param[@dir='in']">
+ <xsl:text>, </xsl:text>
+ <xsl:value-of select="concat('_arg_',@name)"/>
+ </xsl:for-each><xsl:text>):&#10; </xsl:text>
+ <xsl:call-template name="genreq"/>
+</xsl:template>
+
+<xsl:template name="makeConstantName" >
+ <xsl:choose>
+ <!-- special case for reserved word, maybe will need more in the future -->
+ <xsl:when test="@name='None'">
+ <xsl:text>_None</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="@name"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template name="enum">
+class <xsl:value-of select="@name"/>:
+ def __init__(self,mgr,handle):
+ self.mgr=mgr
+ if isinstance(handle,basestring):
+ self.handle=<xsl:value-of select="@name"/>._ValueMap[handle]
+ else:
+ self.handle=handle
+
+ def __eq__(self,other):
+ if isinstance(other,<xsl:value-of select="@name"/>):
+ return self.handle == other.handle
+ if isinstance(other,int):
+ return self.handle == other
+ if isinstance(other,basestring):
+ return str(self) == other
+ return False
+
+ def __ne__(self,other):
+ if isinstance(other,<xsl:value-of select="@name"/>):
+ return self.handle != other.handle
+ if isinstance(other,int):
+ return self.handle != other
+ if isinstance(other,basestring):
+ return str(self) != other
+ return True
+
+ def __str__(self):
+ return <xsl:value-of select="@name"/>._NameMap[self.handle]
+
+ def __int__(self):
+ return self.handle
+
+ _NameMap={<xsl:for-each select="const">
+ <xsl:value-of select="@value"/>:'<xsl:value-of select="@name"/>'<xsl:if test="not(position()=last())">,</xsl:if>
+ </xsl:for-each>}
+ _ValueMap={<xsl:for-each select="const">
+ '<xsl:value-of select="@name"/>':<xsl:value-of select="@value"/><xsl:if test="not(position()=last())">,</xsl:if>
+ </xsl:for-each>}
+
+<xsl:for-each select="const"><xsl:text> </xsl:text><xsl:call-template name="makeConstantName"><xsl:with-param name="name" select="@name"/></xsl:call-template>=<xsl:value-of select="@value"/><xsl:text>&#xa;</xsl:text>
+</xsl:for-each>
+</xsl:template>
+
+<xsl:template match="/">
+<xsl:text># Copyright (C) 2008-2022 Oracle and/or its affiliates.
+#
+# This file is part of a free software library; you can redistribute
+# it and/or modify it under the terms of the GNU Lesser General
+# Public License version 2.1 as published by the Free Software
+# Foundation and shipped in the "COPYING.LIB" file with this library.
+# The library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY of any kind.
+#
+# Oracle LGPL Disclaimer: For the avoidance of doubt, except that if
+# any license choice other than GPL or LGPL is available it will
+# apply instead, Oracle elects to use only the Lesser General Public
+# License version 2.1 (LGPLv2) at this time for any software where
+# a choice of LGPL license versions is made available with the
+# language indicating that LGPLv2 or any later version may be used,
+# or where a choice of which version of the LGPL is applied is
+# otherwise unspecified.
+#
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+
+#
+# This file is autogenerated from VirtualBox.xidl, DO NOT EDIT!
+#
+
+# Works only with ZSI 2.0 generated stubs (part of the VirtualBox SDK).
+from VirtualBox_client import *
+
+class ObjectRefManager:
+ def __init__(self, sessionmgr):
+ self.map = {}
+ self.sessionmgr = sessionmgr
+
+ def register(self, handle):
+ if handle == None:
+ return
+ c = self.map.get(handle,0)
+ c = c + 1
+ self.map[handle]=c
+
+ def unregister(self, handle):
+ if handle == None:
+ return
+ c = self.map.get(handle,-1)
+ if c == -1:
+ raise Error('wrong refcount')
+ c = c - 1
+ if c == 0:
+ try:
+ req=IManagedObjectRef_releaseRequestMsg()
+ req._this=handle
+ self.sessionmgr.getPort().IManagedObjectRef_release(req)
+ except:
+ pass
+ del self.map[handle]
+ else:
+ self.map[handle] = c
+
+class String:
+ def __init__(self, mgr, handle, isarray = False):
+ self.handle = handle
+ self.mgr = mgr
+ self.isarray = isarray
+
+ def __next(self):
+ if self.isarray:
+ return self.handle.__next()
+ raise TypeError("iteration over non-sequence")
+
+ def __size(self):
+ if self.isarray:
+ return self.handle.__size()
+ raise TypeError("iteration over non-sequence")
+
+ def __len__(self):
+ if self.isarray:
+ return self.handle.__len__()
+ raise TypeError("iteration over non-sequence")
+
+ def __getitem__(self, index):
+ if self.isarray:
+ return String(self.mgr, self.handle[index])
+ raise TypeError("iteration over non-sequence")
+
+ def __str__(self):
+ return str(self.handle)
+
+ def __eq__(self,other):
+ if self.isarray:
+ return isinstance(other,String) and self.handle == other.handle
+ if isinstance(other,String):
+ return self.handle == other.handle
+ if isinstance(other,basestring):
+ return self.handle == other
+ return False
+
+ def __ne__(self,other):
+ if self.isarray:
+ return not isinstance(other,String) or self.handle != other.handle
+ if isinstance(other,String):
+ return self.handle != other.handle
+ if isinstance(other,basestring):
+ return self.handle != other
+ return True
+
+ def __add__(self,other):
+ return str(self.handle)+str(other)
+
+
+class Boolean:
+ def __init__(self, mgr, handle, isarray = False):
+ self.handle = handle
+ if self.handle == "false":
+ self.handle = None
+ self.mgr = mgr
+ self.isarray = isarray
+
+ def __str__(self):
+ if self.handle:
+ return "true"
+ else:
+ return "false"
+
+ def __eq__(self,other):
+ if isinstance(other,Bool):
+ return self.handle == other.value
+ if isinstance(other,bool):
+ return self.handle == other
+ return False
+
+ def __ne__(self,other):
+ if isinstance(other,Bool):
+ return self.handle != other.handle
+ if isinstance(other,bool):
+ return self.handle != other
+ return True
+
+ def __int__(self):
+ if self.handle:
+ return 1
+ else:
+ return 0
+
+ def __long__(self):
+ if self.handle:
+ return 1
+ else:
+ return 0
+
+ def __nonzero__(self):
+ if self.handle:
+ return True
+ else:
+ return False
+
+ def __next(self):
+ if self.isarray:
+ return self.handle.__next()
+ raise TypeError("iteration over non-sequence")
+
+ def __size(self):
+ if self.isarray:
+ return self.handle.__size()
+ raise TypeError("iteration over non-sequence")
+
+ def __len__(self):
+ if self.isarray:
+ return self.handle.__len__()
+ raise TypeError("iteration over non-sequence")
+
+ def __getitem__(self, index):
+ if self.isarray:
+ return Boolean(self.mgr, self.handle[index])
+ raise TypeError("iteration over non-sequence")
+
+class Number:
+ def __init__(self, mgr, handle, isarray = False):
+ self.handle = handle
+ self.mgr = mgr
+ self.isarray = isarray
+
+ def __next(self):
+ if self.isarray:
+ return self.handle.__next()
+ raise TypeError("iteration over non-sequence")
+
+ def __size(self):
+ if self.isarray:
+ return self.handle.__size()
+ raise TypeError("iteration over non-sequence")
+
+ def __len__(self):
+ if self.isarray:
+ return self.handle.__len__()
+ raise TypeError("iteration over non-sequence")
+
+ def __str__(self):
+ return str(self.handle)
+
+ def __int__(self):
+ return int(self.handle)
+
+ def __long__(self):
+ return long(self.handle)
+
+ def __float__(self):
+ return float(self.handle)
+
+ def __lt__(self, other):
+ if self.isarray:
+ return NotImplemented
+ else:
+ return self.handle &lt; other
+
+ def __le__(self, other):
+ if self.isarray:
+ return NotImplemented
+ else:
+ return self.handle &lt;= other
+
+ def __eq__(self, other):
+ return self.handle == other
+
+ def __ne__(self, other):
+ return self.handle != other
+
+ def __gt__(self, other):
+ if self.isarray:
+ return NotImplemented
+ else:
+ return self.handle &gt; other
+
+ def __ge__(self, other):
+ if self.isarray:
+ return NotImplemented
+ else:
+ return self.handle &gt;= other
+
+class Octet:
+ def __init__(self, mgr, handle, isarray = False):
+ self.mgr = mgr
+ self.isarray = isarray
+ if isarray:
+ self.handle = mgr.decodebase64(handle)
+ else:
+ raise TypeError("only octet arrays")
+
+ def __getitem__(self, index):
+ return self.handle[index]
+
+ def __str__(self):
+ return str(self.handle)
+
+ def __len__(self):
+ return self.handle.__len__()
+
+class UnsignedInt(Number):
+ def __init__(self, mgr, handle, isarray = False):
+ self.handle = handle
+ self.mgr = mgr
+ self.isarray = isarray
+
+ def __getitem__(self, index):
+ if self.isarray:
+ return UnsignedInt(self.mgr, self.handle[index])
+ raise TypeError("iteration over non-sequence")
+
+
+class Int(Number):
+ def __init__(self, mgr, handle, isarray = False):
+ self.handle = handle
+ self.mgr = mgr
+ self.isarray = isarray
+
+ def __getitem__(self, index):
+ if self.isarray:
+ return Int(self.mgr, self.handle[index])
+ raise TypeError("iteration over non-sequence")
+
+class UnsignedShort(Number):
+ def __init__(self, mgr, handle, isarray = False):
+ self.handle = handle
+ self.mgr = mgr
+ self.isarray = isarray
+
+ def __getitem__(self, index):
+ if self.isarray:
+ return UnsignedShort(self.mgr, self.handle[index])
+ raise TypeError("iteration over non-sequence")
+
+class Short(Number):
+ def __init__(self, mgr, handle, isarray = False):
+ self.handle = handle
+ self.mgr = mgr
+ self.isarray = isarray
+
+ def __getitem__(self, index):
+ if self.isarray:
+ return Short(self.mgr, self.handle[index])
+ raise TypeError("iteration over non-sequence")
+
+class UnsignedLong(Number):
+ def __init__(self, mgr, handle, isarray = False):
+ self.handle = handle
+ self.mgr = mgr
+ self.isarray = isarray
+
+ def __getitem__(self, index):
+ if self.isarray:
+ return UnsignedLong(self.mgr, self.handle[index])
+ raise TypeError("iteration over non-sequence")
+
+class Long(Number):
+ def __init__(self, mgr, handle, isarray = False):
+ self.handle = handle
+ self.mgr = mgr
+ self.isarray = isarray
+
+ def __getitem__(self, index):
+ if self.isarray:
+ return Long(self.mgr, self.handle[index])
+ raise TypeError("iteration over non-sequence")
+
+class Double(Number):
+ def __init__(self, mgr, handle, isarray = False):
+ self.handle = handle
+ self.mgr = mgr
+ self.isarray = isarray
+
+ def __getitem__(self, index):
+ if self.isarray:
+ return Double(self.mgr, self.handle[index])
+ raise TypeError("iteration over non-sequence")
+
+class Float(Number):
+ def __init__(self, mgr, handle, isarray = False):
+ self.handle = handle
+ self.mgr = mgr
+ self.isarray = isarray
+
+ def __getitem__(self, index):
+ if self.isarray:
+ return Float(self.mgr, self.handle[index])
+ raise TypeError("iteration over non-sequence")
+
+class IUnknown:
+ def __init__(self, mgr, handle, isarray = False):
+ self.handle = handle
+ self.mgr = mgr
+ self.isarray = isarray
+
+ def __nonzero__(self):
+ if self.handle != "":
+ return True
+ else:
+ return False
+
+ def __next(self):
+ if self.isarray:
+ return self.handle.__next()
+ raise TypeError("iteration over non-sequence")
+
+ def __size(self):
+ if self.isarray:
+ return self.handle.__size()
+ raise TypeError("iteration over non-sequence")
+
+ def __len__(self):
+ if self.isarray:
+ return self.handle.__len__()
+ raise TypeError("iteration over non-sequence")
+
+ def __getitem__(self, index):
+ if self.isarray:
+ return IUnknown(self.mgr, self.handle[index])
+ raise TypeError("iteration over non-sequence")
+
+ def __str__(self):
+ return str(self.handle)
+
+ def __eq__(self, other):
+ return self.handle == other
+
+ def __ne__(self, other):
+ return self.handle != other
+
+ def __getattr__(self,attr):
+ if self.__class__.__dict__.get(attr) != None:
+ return self.__class__.__dict__.get(attr)
+ if self.__dict__.get(attr) != None:
+ return self.__dict__.get(attr)
+ raise AttributeError
+
+</xsl:text>
+ <xsl:for-each select="//interface[@wsmap='managed' or @wsmap='global']">
+ <xsl:call-template name="interface"/>
+ </xsl:for-each>
+ <xsl:for-each select="//interface[@wsmap='struct']">
+ <xsl:call-template name="interfacestruct"/>
+ </xsl:for-each>
+ <xsl:for-each select="//enum">
+ <xsl:call-template name="enum"/>
+ </xsl:for-each>
+ <xsl:text>
+
+import base64
+
+class IWebsessionManager2(IWebsessionManager, ObjectRefManager):
+ def __init__(self, url):
+ self.url = url
+ self.port = None
+ self.handle = None
+ self.mgr = self
+ ObjectRefManager.__init__(self, self.mgr)
+
+ def getPort(self):
+ if self.port is None:
+ try:
+ self.port = vboxServiceLocator().getvboxPortType(self.url)
+ except:
+ self.port = vboxServiceLocator().getvboxServicePort(self.url)
+ return self.port
+
+ def decodebase64(self, str):
+ return base64.decodestring(str)
+
+ def encodebase64(self, str):
+ return base64.encodestring(str)
+</xsl:text>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/webservice/websrv-typemap.xsl b/src/VBox/Main/webservice/websrv-typemap.xsl
new file mode 100644
index 00000000..d24e6bce
--- /dev/null
+++ b/src/VBox/Main/webservice/websrv-typemap.xsl
@@ -0,0 +1,165 @@
+<?xml version="1.0"?>
+
+<!--
+ websrv-typemap.xsl:
+ XSLT stylesheet that generates a typemap file from
+ VirtualBox.xidl for use with the gSOAP compilers.
+ See webservice/Makefile.kmk for an overview of all the things
+ generated for the webservice.
+-->
+<!--
+ Copyright (C) 2006-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+
+ <xsl:output method="text"/>
+
+ <xsl:strip-space elements="*"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ global XSLT variables
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:variable name="G_xsltFilename" select="'websrv-typemap.xsl'" />
+
+<xsl:include href="../idl/typemap-shared.inc.xsl" />
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ root match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="/idl">
+ <xsl:text><![CDATA[
+# DO NOT EDIT! This is a generated file.
+# Generated from: src/VBox/Main/idl/VirtualBox.xidl (VirtualBox's interface definitions in XML)
+# Generator: src/VBox/Main/webservice/websrv-typemap.xsl
+
+# forces typedefs:
+xsd__int = | long
+xsd__unsignedInt = | unsigned long
+
+# xsd__short =| int16_t
+# xsd__unsignedShort =| uint16_t
+# xsd__int =| int32_t
+# xsd__unsignedInt =| uint32_t
+# xsd__long =| int64_t
+# xsd__unsignedLong =| uint64_t
+
+# Main namespace (which is mapped to vbox__ prefixes):
+]]></xsl:text>
+ <xsl:value-of select="concat('vbox = &quot;', $G_targetNamespace, '&quot;')" />
+ <xsl:text>
+
+# Namespaces for the interfaces in xidl that need to be mapped according to their wsmap attribs:
+</xsl:text>
+ <xsl:apply-templates />
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ if
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<!--
+ * ignore all |if|s except those for WSDL target
+-->
+<xsl:template match="if">
+ <xsl:if test="@target='wsdl'">
+ <xsl:apply-templates/>
+ </xsl:if>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ cpp
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="cpp">
+<!-- ignore this -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ library
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="library">
+ <xsl:apply-templates />
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ class
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="module/class">
+<!-- TODO swallow for now -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ enum
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="enum">
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ const
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<!--
+<xsl:template match="const">
+ <xsl:apply-templates />
+</xsl:template>
+-->
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ desc
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="desc">
+<!-- TODO swallow for now -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ note
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="note">
+<!-- TODO -->
+ <xsl:apply-templates />
+</xsl:template>
+
+<xsl:template match="interface | collection">
+ <!-- remember the interface name in local variables -->
+ <xsl:variable name="ifname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="wsmap"><xsl:value-of select="@wsmap" /></xsl:variable>
+ <xsl:choose>
+ <xsl:when test="$wsmap='struct'" />
+ <xsl:when test="$wsmap='suppress'" />
+ <xsl:otherwise>
+ <xsl:value-of select="concat($ifname, ' = ', $G_targetNamespace, $G_targetNamespaceSeparator,
+ $ifname, $G_bindingSuffix, $G_sNewLine)" />
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/webservice/websrv-wsdl-service.xsl b/src/VBox/Main/webservice/websrv-wsdl-service.xsl
new file mode 100644
index 00000000..fefbb816
--- /dev/null
+++ b/src/VBox/Main/webservice/websrv-wsdl-service.xsl
@@ -0,0 +1,204 @@
+<?xml version="1.0"?>
+
+<!--
+ websrv-wsdl.xsl:
+ XSLT stylesheet that generates vboxwebService.wsdl from
+ VirtualBox.xidl. That extra WSDL file includes the big
+ vboxweb.wsdl file and adds a "service" section.
+ See webservice/Makefile.kmk for an overview of all the things
+ generated for the webservice.
+-->
+<!--
+ Copyright (C) 2006-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet
+ version="1.0"
+ targetNamespace="http://schemas.xmlsoap.org/wsdl/"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
+
+<xsl:param name="G_argDebug" />
+
+<xsl:output
+ method="xml"
+ version="1.0"
+ encoding="utf-8"
+ indent="yes"/>
+
+<xsl:strip-space
+ elements="*" />
+
+<!--**********************************************************************
+ *
+ * global XSLT variables
+ *
+ **********************************************************************-->
+
+<xsl:variable name="G_xsltFilename" select="'websrv-wsdl-service.xsl'" />
+
+<xsl:include href="../idl/typemap-shared.inc.xsl" />
+
+<!-- collect all interfaces with "wsmap='suppress'" in a global variable for
+ quick lookup -->
+<xsl:variable name="G_setSuppressedInterfaces"
+ select="//interface[@wsmap='suppress']" />
+
+<!--**********************************************************************
+ *
+ * shared helpers
+ *
+ **********************************************************************-->
+
+
+<!--**********************************************************************
+ *
+ * matches
+ *
+ **********************************************************************-->
+
+<!--
+A WSDL document describes a web service using these major elements:
+Element Defines
+<portType> The operations performed by the web service. A portType can be thought
+ of as a class.
+<message> The messages used by the web service. A message is a function call
+ and with it come "parts", which are the parameters.
+<types> The data types used by the web service, described in XML Schema
+ syntax.
+<binding> The communication protocols used by the web service.
+
+The root tag is <definitions>.
+
+-->
+
+<xsl:template match="/idl">
+ <xsl:comment>
+ DO NOT EDIT! This is a generated file.
+ Generated from: src/VBox/Main/idl/VirtualBox.xidl (VirtualBox's generic pseudo-IDL file)
+ Generator: src/VBox/Main/webservice/websrv-wsdl-service.xsl
+</xsl:comment>
+ <xsl:apply-templates />
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ if
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<!--
+ * ignore all |if|s except those for WSDL target
+-->
+<xsl:template match="if">
+ <xsl:if test="@target='wsdl'">
+ <xsl:apply-templates/>
+ </xsl:if>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ cpp
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="cpp">
+<!-- ignore this -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ library
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<!--
+ "library" match: we use this to emit most of the WSDL <types> section.
+ With WSDL "document" style, this requires us to go through all interfaces
+ and emit complexTypes for all method arguments and return values.
+-->
+<xsl:template match="library">
+ <definitions xmlns:interface="urn:vbox"
+ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+ xmlns:vbox="http://www.virtualbox.org/"
+ name="VirtualBox"
+ xmlns="http://schemas.xmlsoap.org/wsdl/">
+ <xsl:attribute name="targetNamespace"><xsl:value-of select="concat($G_targetNamespace, 'Service')" /></xsl:attribute>
+
+ <import location="vboxweb.wsdl" namespace="urn:vbox">
+ <xsl:attribute name="namespace"><xsl:value-of select="$G_targetNamespace" /></xsl:attribute>
+ </import>
+
+ <service name="vboxService">
+ <port>
+ <xsl:attribute name="binding"><xsl:value-of select="concat('vbox:vbox', $G_bindingSuffix)" /></xsl:attribute>
+ <xsl:attribute name="name"><xsl:value-of select="concat('vbox', 'ServicePort')" /></xsl:attribute>
+ <soap:address location="http://localhost:18083/"/>
+ </port>
+ </service>
+
+ </definitions>
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ class
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="module/class">
+ <!-- swallow -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ enum
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="enum">
+ <!-- swallow -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ const
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<!--
+<xsl:template match="const">
+ <xsl:apply-templates />
+</xsl:template>
+-->
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ desc
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="desc">
+ <!-- swallow -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ note
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="note">
+ <!-- swallow -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ interface
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="interface">
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/webservice/websrv-wsdl.xsl b/src/VBox/Main/webservice/websrv-wsdl.xsl
new file mode 100644
index 00000000..86146361
--- /dev/null
+++ b/src/VBox/Main/webservice/websrv-wsdl.xsl
@@ -0,0 +1,1308 @@
+<?xml version="1.0"?>
+
+<!--
+ websrv-wsdl.xsl:
+ XSLT stylesheet that generates vboxweb.wsdl from
+ VirtualBox.xidl. This WSDL file represents our
+ web service API..
+ See webservice/Makefile.kmk for an overview of all the things
+ generated for the webservice.
+-->
+<!--
+ Copyright (C) 2006-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<!--
+ A WSDL document describes a web service using these major elements:
+ Element Defines
+ <types> The data types used by the web service, described in XML Schema
+ syntax.
+ <message> The messages used by the web service. A message is a function call
+ and with it come "parts", which are the parameters.
+ <portType> The operations performed by the web service. A portType can be thought
+ of as a class or, in COM terms, as an interface.
+ <binding> The communication protocols used by the web service.
+
+ The root tag is <definitions>.
+
+ Representing COM interfaces is tricky in WSDL 1.1, which doesn't really have them.
+ WSDL only knows about "port types", which are an abstract representation
+ of a group of functions. So for each "interface", we need to emit
+ a "port type"; in the port type, we declare each "interface method"
+ as one "operation". Each operation in turn consists of at least one
+ message for the method invocation, which contains all the "in" and
+ "inout" arguments. An optional second message for the response contains
+ the return value, if one is present in the IDL (called "_return" to
+ avoid name clashes), together with all the "out" and "inout" arguments.
+ Each of these messages, however, need to be independently declared
+ using the "message" element outside of the "port type" declaration.
+
+ As an example: To create this XPCOM IDL:
+
+ void createMachine (
+ in wstring baseFolder,
+ in wstring name,
+ [retval] out IMachine machine
+ );
+
+ the following exists in the XIDL:
+
+ <interface name="ifname">
+ <method name="createMachine">
+ <param name="baseFolder" type="wstring" dir="in" />
+ <param name="name" type="wstring" dir="in" />
+ <param name="machine" type="IMachine" dir="return" />
+ </method>
+ </interface>
+
+ So, we have two "in" parameters, and one "out" parameter. The
+ operation therefore requires two messages (one for the request,
+ with the two "in" parameters, and one for the result with the
+ return value). With RPC/encoded style, we end up with this:
+
+ <message name="ifname.methodname_Request">
+ <part name="baseFolder" type="xsd:string" />
+ <part name="name" type="xsd:string" />
+ </message>
+ <message name="ifname.methodname_Result">
+ <part name="_return" type="IMachine" />
+ </message>
+ <portType name="ifname">
+ <operation name="methodname"
+ <input message="ifname.methodname_Request" />
+ <output message="ifname.methodname_Result" />
+ </operation>
+ </portType>
+
+ With document/literal style, things get even more verbose, as
+ instead of listing the arguments and return values in the messages,
+ we declare a struct-like complexType in the <types> section
+ instead and then reference that type in the messages.
+-->
+
+<xsl:stylesheet
+ version="1.0"
+ targetNamespace="http://schemas.xmlsoap.org/wsdl/"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+ xmlns:vbox="http://www.virtualbox.org/"
+ xmlns:exsl="http://exslt.org/common"
+ extension-element-prefixes="exsl">
+
+<xsl:param name="G_argDebug" />
+
+<xsl:output
+ method="xml"
+ version="1.0"
+ encoding="utf-8"
+ indent="yes"/>
+
+<xsl:strip-space
+ elements="*" />
+
+<!--**********************************************************************
+ *
+ * global XSLT variables
+ *
+ **********************************************************************-->
+
+<xsl:variable name="G_xsltFilename" select="'websrv-wsdl.xsl'" />
+
+<xsl:include href="../idl/typemap-shared.inc.xsl" />
+
+<!-- collect all interfaces with "wsmap='suppress'" in a global variable for
+ quick lookup -->
+<xsl:variable name="G_setSuppressedInterfaces"
+ select="//interface[@wsmap='suppress']" />
+
+<!-- this marker is used with WSDL document style to mark that a message
+ should have an automatic type that matches a complexType definition;
+ use a string that cannot possibly appear in an XIDL interface name -->
+<xsl:variable name="G_typeIsGlobalRequestElementMarker"
+ select="'&lt;&lt;&lt;&lt;Request'" />
+<xsl:variable name="G_typeIsGlobalResponseElementMarker"
+ select="'&lt;&lt;&lt;&lt;Response'" />
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ Keys for more efficiently looking up of types.
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:key name="G_keyEnumsByName" match="//enum[@name]" use="@name"/>
+<xsl:key name="G_keyInterfacesByName" match="//interface[@name]" use="@name"/>
+
+
+<!--**********************************************************************
+ *
+ * shared helpers
+ *
+ **********************************************************************-->
+
+<!--
+ function emitConvertedType
+ -->
+<xsl:template name="emitConvertedType">
+ <xsl:param name="ifname" />
+ <xsl:param name="methodname" />
+ <xsl:param name="type" />
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('......emitConvertedType: type=&quot;', $type, '&quot;')" /></xsl:call-template>
+ <!-- look up XML Schema type from IDL type from table array in typemap-shared.inc.xsl -->
+ <xsl:variable name="xmltypefield" select="exsl:node-set($G_aSharedTypes)/type[@idlname=$type]/@xmlname" />
+ <xsl:choose>
+ <xsl:when test="$type=$G_typeIsGlobalRequestElementMarker"><xsl:value-of select="concat('vbox:', $ifname, $G_classSeparator, $methodname, $G_requestMessageElementSuffix)" /></xsl:when>
+ <xsl:when test="$type=$G_typeIsGlobalResponseElementMarker"><xsl:value-of select="concat('vbox:', $ifname, $G_classSeparator, $methodname, $G_responseMessageElementSuffix)" /></xsl:when>
+ <!-- if above lookup in table succeeded, use that type -->
+ <xsl:when test="string-length($xmltypefield)"><xsl:value-of select="concat('xsd:', $xmltypefield)" /></xsl:when>
+ <xsl:when test="$type='$unknown'"><xsl:value-of select="$G_typeObjectRef" /></xsl:when>
+ <xsl:when test="$type='global'"><xsl:value-of select="$G_typeObjectRef" /></xsl:when>
+ <xsl:when test="$type='managed'"><xsl:value-of select="$G_typeObjectRef" /></xsl:when>
+ <xsl:when test="$type='explicit'"><xsl:value-of select="$G_typeObjectRef" /></xsl:when>
+ <!-- enums are easy, these are defined in schema at the top of the wsdl -->
+ <xsl:when test="count(key('G_keyEnumsByName', $type)) > 0"><xsl:value-of select="concat('vbox:', $type)" /></xsl:when>
+ <!-- otherwise test for an interface with this name -->
+ <xsl:when test="count(key('G_keyInterfacesByName', $type)) > 0">
+ <!-- the type is one of our own interfaces: then it must have a wsmap attr -->
+ <xsl:variable name="wsmap" select="key('G_keyInterfacesByName', $type)/@wsmap" />
+ <xsl:choose>
+ <xsl:when test="$wsmap='struct'"><xsl:value-of select="concat('vbox:', $type)" /></xsl:when>
+ <xsl:when test="$wsmap='global'"><xsl:value-of select="$G_typeObjectRef" /></xsl:when>
+ <xsl:when test="$wsmap='managed'"><xsl:value-of select="$G_typeObjectRef" /></xsl:when>
+ <xsl:when test="$wsmap='explicit'"><xsl:value-of select="$G_typeObjectRef" /></xsl:when>
+ <xsl:when test="$wsmap='suppress'">
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('emitConvertedType: Type &quot;', $type, '&quot; in method &quot;', $ifname, '::', $methodname, '&quot; has wsmap=&quot;suppress&quot; attribute in XIDL. This function should have been suppressed as well.')" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('emitConvertedType: Type &quot;', $type, '&quot; used in method &quot;', $ifname, '::', $methodname, '&quot; has unsupported wsmap attribute value &quot;', $wsmap, '&quot;')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('emitConvertedType: Unknown type &quot;', $type, '&quot; used in method &quot;', $ifname, '::', $methodname, '&quot;.')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+ function convertTypeAndEmitPartOrElement
+ -->
+<xsl:template name="convertTypeAndEmitPartOrElement">
+ <xsl:param name="ifname" />
+ <xsl:param name="methodname" />
+ <xsl:param name="name" />
+ <xsl:param name="type" />
+ <xsl:param name="safearray" /> <!-- "yes" if XIDL has safearray=yes -->
+ <xsl:param name="elname" /> <!-- "part" or "element" -->
+ <xsl:param name="attrname" /> <!-- attrib of part or element: <part type=...> or <part element=...> or <element type=...> -->
+
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('....convertTypeAndEmitPartOrElement: arg name: ', $name)" /></xsl:call-template>
+ <xsl:choose>
+ <xsl:when test="$safearray='yes' and $type='octet'">
+ <!-- we pass octet arrays as Base64-encoded strings. -->
+ <xsl:element name="{$elname}">
+ <xsl:attribute name="name"><xsl:value-of select="$name" /></xsl:attribute>
+ <xsl:attribute name="type"><xsl:value-of select="'xsd:string'" /></xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+
+ <xsl:when test="$safearray='yes'">
+ <xsl:element name="{$elname}"> <!-- <part> or <element> -->
+ <xsl:attribute name="name"><xsl:value-of select="$name" /></xsl:attribute>
+ <xsl:attribute name="minOccurs"><xsl:value-of select="'0'" /></xsl:attribute>
+ <xsl:attribute name="maxOccurs"><xsl:value-of select="'unbounded'" /></xsl:attribute>
+ <xsl:attribute name="{$attrname}">
+ <xsl:call-template name="emitConvertedType">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="$methodname" />
+ <xsl:with-param name="type" select="$type" />
+ </xsl:call-template>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:element name="{$elname}"> <!-- <part> or <element> -->
+ <xsl:attribute name="name"><xsl:value-of select="$name" /></xsl:attribute>
+ <xsl:attribute name="{$attrname}">
+ <xsl:call-template name="emitConvertedType">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="methodname" select="$methodname" />
+ <xsl:with-param name="type" select="$type" />
+ </xsl:call-template>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+ function emitRequestArgs
+ -->
+<xsl:template name="emitRequestArgs">
+ <xsl:param name="_ifname" /> <!-- interface name -->
+ <xsl:param name="_wsmap" /> <!-- interface's wsmap attribute -->
+ <xsl:param name="_methodname" />
+ <xsl:param name="_params" />
+ <xsl:param name="_valuetype" /> <!-- optional, for attribute setter messages -->
+ <xsl:param name="_valuesafearray" /> <!-- optional, 'yes' if attribute of setter has safearray=yes -->
+ <xsl:param name="_elname" /> <!-- "part" or "xsd:element" -->
+ <xsl:param name="_attrname" /> <!-- attrib of part of element: <part type=...> or <part element=...> or <element type=...> -->
+
+ <!-- first parameter will be object on which method is called, depending on wsmap attribute -->
+ <xsl:choose>
+ <xsl:when test="($_wsmap='managed') or ($_wsmap='explicit')">
+ <xsl:call-template name="convertTypeAndEmitPartOrElement">
+ <xsl:with-param name="ifname" select="$_ifname" />
+ <xsl:with-param name="methodname" select="$_methodname" />
+ <xsl:with-param name="name" select="$G_nameObjectRef" />
+ <xsl:with-param name="type" select="$_wsmap" />
+ <xsl:with-param name="safearray" select="'no'" />
+ <xsl:with-param name="elname" select="$_elname" /> <!-- "part" or "element" -->
+ <xsl:with-param name="attrname" select="$_attrname" /> <!-- attrib of part of element: <part type=...> or <part element=...> or <element type=...> -->
+ </xsl:call-template>
+ </xsl:when>
+ </xsl:choose>
+ <!-- now for the real parameters, if any -->
+ <xsl:for-each select="$_params">
+ <!-- emit only parts for "in" parameters -->
+ <xsl:if test="@dir='in'">
+ <xsl:call-template name="convertTypeAndEmitPartOrElement">
+ <xsl:with-param name="ifname" select="$_ifname" />
+ <xsl:with-param name="methodname" select="$_methodname" />
+ <xsl:with-param name="name" select="@name" />
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="elname" select="$_elname" /> <!-- "part" or "element" -->
+ <xsl:with-param name="attrname" select="$_attrname" /> <!-- attrib of part of element: <part type=...> or <part element=...> or <element type=...> -->
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:if test="$_valuetype">
+ <!-- <part>
+ <xsl:attribute name="name">value</xsl:attribute>
+ <xsl:attribute name="type"><xsl:value-of select='string($_valuetype)' /></xsl:attribute>
+ </part> -->
+ <xsl:call-template name="convertTypeAndEmitPartOrElement">
+ <xsl:with-param name="ifname" select="$_ifname" />
+ <xsl:with-param name="methodname" select="$_methodname" />
+ <xsl:with-param name="name" select="@name" />
+ <xsl:with-param name="type" select="@type" />
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="elname" select="$_elname" /> <!-- "part" or "element" -->
+ <xsl:with-param name="attrname" select="$_attrname" /> <!-- attrib of part of element: <part type=...> or <part element=...> or <element type=...> -->
+ </xsl:call-template>
+ </xsl:if>
+</xsl:template>
+
+<!--
+ function emitResultArgs
+ -->
+<xsl:template name="emitResultArgs">
+ <xsl:param name="_ifname" />
+ <xsl:param name="_methodname" />
+ <xsl:param name="_params" /> <!-- set of parameter elements -->
+ <xsl:param name="_resulttype" /> <!-- for attribute getter methods only -->
+ <xsl:param name="_resultsafearray" /> <!-- for attribute getter methods only -->
+ <xsl:param name="_elname" /> <!-- "part" or "xsd:element" -->
+ <xsl:param name="_attrname" /> <!-- attrib of part of element: <part type=...> or <part element=...> or <element type=...> -->
+
+ <xsl:choose>
+ <xsl:when test="$_resulttype">
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('..', $_ifname, '::', $_methodname, ': ', 'resultmsg for attr of type ', $_resulttype)" /></xsl:call-template>
+ <xsl:call-template name="convertTypeAndEmitPartOrElement">
+ <xsl:with-param name="ifname" select="$_ifname" />
+ <xsl:with-param name="methodname" select="$_methodname" />
+ <xsl:with-param name="name" select="$G_result" />
+ <xsl:with-param name="type" select="$_resulttype" />
+ <xsl:with-param name="safearray" select="$_resultsafearray" />
+ <xsl:with-param name="elname" select="$_elname" /> <!-- "part" or "element" -->
+ <xsl:with-param name="attrname" select="$_attrname" /> <!-- attrib of part of element: <part type=...> or <part element=...> or <element type=...> -->
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('..', 'resultmsg for method: ', $_ifname, '::', $_methodname)" /></xsl:call-template>
+ <xsl:for-each select="$_params">
+ <!-- emit only parts for "out" parameters -->
+ <xsl:if test="@dir='out'">
+ <xsl:call-template name="convertTypeAndEmitPartOrElement">
+ <xsl:with-param name="ifname" select="$_ifname" />
+ <xsl:with-param name="methodname" select="$_methodname" />
+ <xsl:with-param name="name"><xsl:value-of select="@name" /></xsl:with-param>
+ <xsl:with-param name="type"><xsl:value-of select="@type" /></xsl:with-param>
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="elname" select="$_elname" /> <!-- "part" or "element" -->
+ <xsl:with-param name="attrname" select="$_attrname" /> <!-- attrib of part of element: <part type=...> or <part element=...> or <element type=...> -->
+ </xsl:call-template>
+ </xsl:if>
+ <xsl:if test="@dir='return'">
+ <xsl:call-template name="convertTypeAndEmitPartOrElement">
+ <xsl:with-param name="ifname" select="$_ifname" />
+ <xsl:with-param name="methodname" select="$_methodname" />
+ <xsl:with-param name="name"><xsl:value-of select="$G_result" /></xsl:with-param>
+ <xsl:with-param name="type"><xsl:value-of select="@type" /></xsl:with-param>
+ <xsl:with-param name="safearray" select="@safearray" />
+ <xsl:with-param name="elname" select="$_elname" /> <!-- "part" or "element" -->
+ <xsl:with-param name="attrname" select="$_attrname" /> <!-- attrib of part of element: <part type=...> or <part element=...> or <element type=...> -->
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:for-each>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+ function emitRequestElements:
+ for "in" parameters
+ -->
+<xsl:template name="emitRequestElements">
+ <xsl:param name="_ifname" /> <!-- interface name -->
+ <xsl:param name="_wsmap" /> <!-- interface's wsmap attribute -->
+ <xsl:param name="_methodname" />
+ <xsl:param name="_params" />
+ <xsl:param name="_valuetype" /> <!-- optional, for attribute setter messages -->
+ <xsl:param name="_valuesafearray" /> <!-- optional, 'yes' if attribute of setter has safearray=yes -->
+
+ <xsd:element>
+ <xsl:attribute name="name"><xsl:value-of select="concat($_ifname, $G_classSeparator, $_methodname, $G_requestMessageElementSuffix)" /></xsl:attribute>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsl:call-template name="emitRequestArgs">
+ <xsl:with-param name="_ifname" select="$_ifname" /> <!-- interface name -->
+ <xsl:with-param name="_wsmap" select="$_wsmap" /> <!-- interface's wsmap attribute -->
+ <xsl:with-param name="_methodname" select="$_methodname" />
+ <xsl:with-param name="_params" select="$_params" />
+ <xsl:with-param name="_valuetype" select="$_valuetype" /> <!-- optional, for attribute setter messages -->
+ <xsl:with-param name="_valuesafearray" select="$_valuesafearray" /> <!-- optional, for attribute setter messages -->
+ <xsl:with-param name="_elname" select="'xsd:element'" /> <!-- "part" or "xsd:element" -->
+ <xsl:with-param name="_attrname" select="'type'" /> <!-- attrib of part of element: <part type=...> or <part element=...> or <element type=...> -->
+ </xsl:call-template>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+</xsl:template>
+
+<!--
+ function emitResultElements:
+ for "out" and "return" parameters
+ -->
+<xsl:template name="emitResultElements">
+ <xsl:param name="_ifname" />
+ <xsl:param name="_methodname" />
+ <xsl:param name="_params" /> <!-- set of parameter elements -->
+ <xsl:param name="_resulttype" /> <!-- optional, for attribute getter methods only -->
+ <xsl:param name="_resultsafearray" /> <!-- optional, 'yes' if attribute of getter has safearray=yes -->
+
+ <xsd:element>
+ <xsl:attribute name="name"><xsl:value-of select="concat($_ifname, $G_classSeparator, $_methodname, $G_responseMessageElementSuffix)" /></xsl:attribute>
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsl:call-template name="emitResultArgs">
+ <xsl:with-param name="_ifname" select="$_ifname" />
+ <xsl:with-param name="_methodname" select="$_methodname" />
+ <xsl:with-param name="_params" select="$_params" /> <!-- set of parameter elements -->
+ <xsl:with-param name="_resulttype" select="$_resulttype" /> <!-- for attribute getter methods only -->
+ <xsl:with-param name="_resultsafearray" select="$_resultsafearray" /> <!-- for attribute getter methods only -->
+ <xsl:with-param name="_elname" select="'xsd:element'" /> <!-- "part" or "xsd:element" -->
+ <xsl:with-param name="_attrname" select="'type'" /> <!-- attrib of part of element: <part type=...> or <part element=...> or <element type=...> -->
+ </xsl:call-template>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+</xsl:template>
+
+<!--
+ function emitGetAttributeElements
+ -->
+<xsl:template name="emitGetAttributeElements">
+ <xsl:param name="ifname" />
+ <xsl:param name="wsmap" />
+ <xsl:param name="attrname" />
+ <xsl:param name="attrtype" />
+ <xsl:param name="attrsafearray" />
+
+ <xsl:variable name="attrGetter"><xsl:call-template name="makeGetterName"><xsl:with-param name="attrname" select="$attrname" /></xsl:call-template></xsl:variable>
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('..', $ifname, '::', $attrGetter)" /></xsl:call-template>
+ <xsl:call-template name="emitRequestElements">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_wsmap" select="$wsmap" />
+ <xsl:with-param name="_methodname" select="$attrGetter" />
+ <xsl:with-param name="_params" select="/.." /> <!-- empty set -->
+ </xsl:call-template>
+ <xsl:call-template name="emitResultElements">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_methodname" select="$attrGetter" />
+ <xsl:with-param name="_params" select="/.." /> <!-- empty set -->
+ <xsl:with-param name="_resulttype" select='$attrtype' />
+ <xsl:with-param name="_resultsafearray" select='$attrsafearray' />
+ </xsl:call-template>
+</xsl:template>
+
+<!--
+ function: emitRequestMessage
+ for "in" parameters
+-->
+<xsl:template name="emitRequestMessage">
+ <xsl:param name="_ifname" /> <!-- interface name -->
+ <xsl:param name="_wsmap" /> <!-- interface's wsmap attribute -->
+ <xsl:param name="_methodname" />
+ <xsl:param name="_params" />
+ <xsl:param name="_valuetype" /> <!-- optional, for attribute setter messages -->
+
+ <wsdl:message>
+ <xsl:attribute name="name"><xsl:value-of select="concat($_ifname, $G_classSeparator, $_methodname, $G_methodRequest)" /></xsl:attribute>
+
+ <xsl:call-template name="convertTypeAndEmitPartOrElement">
+ <xsl:with-param name="ifname" select="$_ifname" />
+ <xsl:with-param name="methodname" select="$_methodname" />
+ <xsl:with-param name="name" select="'parameters'" />
+ <xsl:with-param name="type" select="$G_typeIsGlobalRequestElementMarker" />
+ <xsl:with-param name="safearray" select="'no'" />
+ <xsl:with-param name="elname" select="'wsdl:part'" /> <!-- "part" or "element" -->
+ <xsl:with-param name="attrname" select="'element'" /> <!-- attrib of part of element: <part type=...> or <part element=...> or <element type=...> -->
+ </xsl:call-template>
+ </wsdl:message>
+</xsl:template>
+
+<!--
+ function: emitResultMessage
+ for "out" and "return" parameters
+-->
+<xsl:template name="emitResultMessage">
+ <xsl:param name="_ifname" />
+ <xsl:param name="_methodname" />
+ <xsl:param name="_params" /> <!-- set of parameter elements -->
+ <xsl:param name="_resulttype" /> <!-- for attribute getter methods only -->
+
+ <wsdl:message>
+ <xsl:attribute name="name"><xsl:copy-of select="$_ifname" /><xsl:value-of select="$G_classSeparator" /><xsl:value-of select="$_methodname" /><xsl:copy-of select="$G_methodResponse" /></xsl:attribute>
+
+ <!-- <xsl:variable name="cOutParams" select="count($_params[@dir='out']) + count($_params[@dir='return'])" /> -->
+ <xsl:call-template name="convertTypeAndEmitPartOrElement">
+ <xsl:with-param name="ifname" select="$_ifname" />
+ <xsl:with-param name="methodname" select="$_methodname" />
+ <xsl:with-param name="name" select="'parameters'" />
+ <xsl:with-param name="type" select="$G_typeIsGlobalResponseElementMarker" />
+ <xsl:with-param name="safearray" select="'no'" />
+ <xsl:with-param name="elname" select="'wsdl:part'" /> <!-- "part" or "element" -->
+ <xsl:with-param name="attrname" select="'element'" /> <!-- attrib of part of element: <part type=...> or <part element=...> or <element type=...> -->
+ </xsl:call-template>
+ </wsdl:message>
+</xsl:template>
+
+<!--
+ function emitGetAttributeMessages:
+-->
+<xsl:template name="emitGetAttributeMessages">
+ <xsl:param name="ifname" />
+ <xsl:param name="wsmap" />
+ <xsl:param name="attrname" />
+ <xsl:param name="attrtype" />
+
+ <xsl:variable name="attrGetter"><xsl:call-template name="makeGetterName"><xsl:with-param name="attrname" select="$attrname" /></xsl:call-template></xsl:variable>
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('..', $ifname, '::', $attrGetter)" /></xsl:call-template>
+ <xsl:call-template name="emitRequestMessage">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_wsmap" select="$wsmap" />
+ <xsl:with-param name="_methodname" select="$attrGetter" />
+ <xsl:with-param name="_params" select="/.." /> <!-- empty set -->
+ </xsl:call-template>
+ <xsl:call-template name="emitResultMessage">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_methodname" select="$attrGetter" />
+ <xsl:with-param name="_params" select="/.." /> <!-- empty set -->
+ <xsl:with-param name="_resulttype" select='$attrtype' />
+ </xsl:call-template>
+</xsl:template>
+
+<!--
+ function emitSetAttributeMessages
+ -->
+<xsl:template name="emitSetAttributeMessages">
+ <xsl:param name="ifname" select="$ifname" />
+ <xsl:param name="wsmap" select="$wsmap" />
+ <xsl:param name="attrname" select="$attrname" />
+ <xsl:param name="attrtype" select="$attrtype" />
+
+ <xsl:variable name="attrSetter"><xsl:call-template name="makeSetterName"><xsl:with-param name="attrname" select="$attrname" /></xsl:call-template></xsl:variable>
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('..', $ifname, '::', $attrSetter)" /></xsl:call-template>
+ <xsl:call-template name="emitRequestMessage">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_wsmap" select="$wsmap" />
+ <xsl:with-param name="_methodname" select="$attrSetter" />
+ <xsl:with-param name="_params" select="/.." /> <!-- empty set -->
+ <xsl:with-param name="_valuetype" select="$attrtype" />
+ <xsl:with-param name="elname" select="'wsdl:part'" /> <!-- "part" or "element" -->
+ </xsl:call-template>
+ <xsl:call-template name="emitResultMessage">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_methodname" select="$attrSetter" />
+ <xsl:with-param name="_params" select="/.." /> <!-- empty set -->
+ <xsl:with-param name="elname" select="'wsdl:part'" /> <!-- "part" or "element" -->
+ </xsl:call-template>
+</xsl:template>
+
+<!--
+ function emitInOutOperation:
+ referencing the messages that must have been emitted previously
+-->
+<xsl:template name="emitInOutOperation">
+ <xsl:param name="_ifname" /> <!-- interface name -->
+ <xsl:param name="_methodname" /> <!-- method name -->
+ <xsl:param name="_params" />
+ <xsl:param name="_resulttype" /> <!-- for attribute getter methods only -->
+ <xsl:param name="_fSoap" />
+
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('....emitInOutOperation ', $_ifname, '::', $_methodname)" /></xsl:call-template>
+
+ <wsdl:operation>
+ <xsl:attribute name="name">
+ <xsl:value-of select="concat($_ifname, '_', $_methodname)" />
+ </xsl:attribute>
+ <xsl:if test="$_fSoap">
+ <soap:operation>
+ <!-- VMware has an empty attribute like this as well -->
+ <xsl:attribute name="soapAction"><xsl:value-of select="''" /></xsl:attribute>
+ <xsl:attribute name="style"><xsl:value-of select="$G_basefmt" /></xsl:attribute>
+ </soap:operation>
+ </xsl:if>
+ <wsdl:input>
+ <xsl:choose>
+ <xsl:when test="$_fSoap">
+ <soap:body>
+ <xsl:attribute name="use"><xsl:value-of select="$G_parmfmt" /></xsl:attribute>
+ <!-- avoid jax-ws warning: <xsl:attribute name="namespace"><xsl:value-of select="concat($G_targetNamespace, $G_targetNamespaceSeparator)" /></xsl:attribute>-->
+ </soap:body>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:attribute name="message">vbox:<xsl:copy-of select="$_ifname" /><xsl:value-of select="$G_classSeparator" /><xsl:value-of select="$_methodname" /><xsl:copy-of select="$G_methodRequest" /></xsl:attribute>
+ </xsl:otherwise>
+ </xsl:choose>
+ </wsdl:input>
+ <xsl:choose>
+ <xsl:when test="$_resulttype">
+ <wsdl:output>
+ <xsl:choose>
+ <xsl:when test="$_fSoap">
+ <soap:body>
+ <xsl:attribute name="use"><xsl:value-of select="$G_parmfmt" /></xsl:attribute>
+ <!-- avoid jax-ws warning: <xsl:attribute name="namespace"><xsl:value-of select="concat($G_targetNamespace, $G_targetNamespaceSeparator)" /></xsl:attribute> -->
+ </soap:body>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:attribute name="message">vbox:<xsl:copy-of select="$_ifname" /><xsl:value-of select="$G_classSeparator" /><xsl:value-of select="$_methodname" /><xsl:copy-of select="$G_methodResponse" /></xsl:attribute>
+ </xsl:otherwise>
+ </xsl:choose>
+ </wsdl:output>
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- <xsl:if test="count($_params[@dir='out'] | $_params[@dir='return']) > 0"> -->
+ <wsdl:output>
+ <xsl:choose>
+ <xsl:when test="$_fSoap">
+ <soap:body>
+ <xsl:attribute name="use"><xsl:value-of select="$G_parmfmt" /></xsl:attribute>
+ <!-- avoid jax-ws warning: <xsl:attribute name="namespace"><xsl:value-of select="concat($G_targetNamespace, $G_targetNamespaceSeparator)" /></xsl:attribute> -->
+ </soap:body>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:attribute name="message">vbox:<xsl:copy-of select="$_ifname" /><xsl:value-of select="$G_classSeparator" /><xsl:value-of select="$_methodname" /><xsl:copy-of select="$G_methodResponse" /></xsl:attribute>
+ </xsl:otherwise>
+ </xsl:choose>
+ </wsdl:output>
+ <!-- </xsl:if> -->
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:choose>
+ <xsl:when test="not($_fSoap)">
+ <wsdl:fault name="InvalidObjectFault" message="vbox:InvalidObjectFaultMsg" />
+ <wsdl:fault name="RuntimeFault" message="vbox:RuntimeFaultMsg" />
+ </xsl:when>
+ <xsl:otherwise>
+ <wsdl:fault name="InvalidObjectFault">
+ <soap:fault name="InvalidObjectFault">
+ <xsl:attribute name="use"><xsl:value-of select="$G_parmfmt" /></xsl:attribute>
+ </soap:fault>
+ </wsdl:fault>
+ <wsdl:fault name="RuntimeFault">
+ <soap:fault name="RuntimeFault">
+ <xsl:attribute name="use"><xsl:value-of select="$G_parmfmt" /></xsl:attribute>
+ </soap:fault>
+ </wsdl:fault>
+ </xsl:otherwise>
+ </xsl:choose>
+ </wsdl:operation>
+</xsl:template>
+
+<!--
+ function verifyInterface
+-->
+<xsl:template name="verifyInterface">
+ <xsl:param name="ifname" />
+ <xsl:param name="wsmap" />
+
+ <xsl:choose>
+ <xsl:when test="$wsmap='global'" />
+ <xsl:when test="$wsmap='managed'" />
+ <xsl:when test="$wsmap='explicit'" />
+ <xsl:when test="$wsmap='struct'" />
+ <xsl:when test="$wsmap='suppress'" />
+ <xsl:otherwise>
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat(local-name(), ' template: Interface &quot;', $ifname, '&quot; has invalid wsmap attribute &quot;', $wsmap, '&quot; in XIDL.')" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <!-- now make sure we have each interface only once -->
+ <xsl:if test="(count(//library/interface[@name=$ifname]) > 1)">
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat(local-name(), ' template: There is more than one interface with a name=&quot;', $ifname, '&quot; attribute.')" />
+ </xsl:call-template>
+ </xsl:if>
+</xsl:template>
+
+<!--
+ function emitMessagesForInterface
+-->
+<xsl:template name="emitMessagesForInterface">
+ <xsl:param name="ifname" />
+ <xsl:param name="wsmap" />
+
+ <!-- 1) outside the portType, here come the in/out methods for all the "operations" we declare below;
+ a) for attributes (get/set methods)
+ b) for "real" methods
+ -->
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('************* messages for interface &quot;', $ifname, '&quot;')" /></xsl:call-template>
+ <!-- a) attributes first -->
+ <xsl:for-each select="attribute">
+ <xsl:variable name="attrname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="attrtype"><xsl:value-of select="@type" /></xsl:variable>
+ <xsl:variable name="attrreadonly"><xsl:value-of select="@readonly" /></xsl:variable>
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('messages for ', $ifname, '::', $attrname, ': attribute of type &quot;', $attrtype, '&quot;, readonly: ', $attrreadonly)" /></xsl:call-template>
+ <!-- skip this attribute if it has parameters of a type that has wsmap="suppress" -->
+ <xsl:choose>
+ <xsl:when test="( $attrtype=($G_setSuppressedInterfaces/@name) )">
+ <xsl:comment><xsl:value-of select="concat('skipping attribute ', $attrname, ' for it is of a suppressed type')" /></xsl:comment>
+ </xsl:when>
+ <xsl:when test="@wsmap = 'suppress'">
+ <xsl:comment><xsl:value-of select="concat('skipping attribute ', $attrname, ' for it is suppressed')" /></xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="@readonly='yes'">
+ <xsl:comment> readonly attribute <xsl:copy-of select="$ifname" />::<xsl:copy-of select="$attrname" /> </xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:comment> read/write attribute <xsl:copy-of select="$ifname" />::<xsl:copy-of select="$attrname" /> </xsl:comment>
+ </xsl:otherwise>
+ </xsl:choose>
+ <!-- aa) get method: emit request and result -->
+ <xsl:call-template name="emitGetAttributeMessages">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="wsmap" select="$wsmap" />
+ <xsl:with-param name="attrname" select="$attrname" />
+ <xsl:with-param name="attrtype" select="$attrtype" />
+ </xsl:call-template>
+ <!-- bb) emit a set method if the attribute is read/write -->
+ <xsl:if test="not($attrreadonly='yes')">
+ <xsl:call-template name="emitSetAttributeMessages">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="wsmap" select="$wsmap" />
+ <xsl:with-param name="attrname" select="$attrname" />
+ <xsl:with-param name="attrtype" select="$attrtype" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each> <!-- select="attribute" -->
+ <!-- b) "real" methods after the attributes -->
+ <xsl:for-each select="method">
+ <xsl:variable name="methodname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('messages for ', $ifname, '::', $methodname, ': method')" /></xsl:call-template>
+ <xsl:comment> method <xsl:copy-of select="$ifname" />::<xsl:copy-of select="$methodname" /> </xsl:comment>
+ <!-- skip this method if it has parameters of a type that has wsmap="suppress" -->
+ <xsl:choose>
+ <xsl:when test=" (param[@type=($G_setSuppressedInterfaces/@name)])
+ or (param[@mod='ptr'])" >
+ <xsl:comment><xsl:value-of select="concat('skipping method ', $methodname, ' for it has parameters with suppressed types')" /></xsl:comment>
+ </xsl:when>
+ <xsl:when test="@wsmap = 'suppress'">
+ <xsl:comment><xsl:value-of select="concat('skipping method ', $methodname, ' for it is suppressed')" /></xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- always emit a request message -->
+ <xsl:call-template name="emitRequestMessage">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_wsmap" select="$wsmap" />
+ <xsl:with-param name="_methodname" select="$methodname" />
+ <xsl:with-param name="_params" select="param" />
+ <xsl:with-param name="elname" select="'wsdl:part'" /> <!-- "part" or "element" -->
+ </xsl:call-template>
+ <!-- emit a second "result" message only if the method has "out" arguments or a return value -->
+ <!-- <xsl:if test="(count(param[@dir='out'] | param[@dir='return']) > 0)"> -->
+ <xsl:call-template name="emitResultMessage">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_wsmap" select="$wsmap" />
+ <xsl:with-param name="_methodname" select="@name" />
+ <xsl:with-param name="_params" select="param" />
+ <xsl:with-param name="elname" select="'wsdl:part'" /> <!-- "part" or "element" -->
+ </xsl:call-template>
+ <!-- </xsl:if> -->
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+</xsl:template>
+
+<!--
+ function emitOperationsForInterface
+ -->
+<xsl:template name="emitOperationsInPortTypeForInterface">
+ <xsl:param name="ifname" />
+ <xsl:param name="wsmap" />
+
+ <!-- a) again, first for the attributes whose messages we produced above -->
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('************* portType for interface &quot;', $ifname, '&quot;')" /></xsl:call-template>
+ <xsl:for-each select="attribute">
+ <xsl:variable name="attrname" select="@name" />
+ <xsl:variable name="attrtype" select="@type" />
+ <xsl:variable name="attrreadonly" select="@readonly" />
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('operations for ', $ifname, '::', $attrname, ': attribute of type &quot;', $attrtype, '&quot;, readonly: ', $attrreadonly)" /></xsl:call-template>
+ <xsl:choose>
+ <!-- skip this attribute if it has parameters of a type that has wsmap="suppress" -->
+ <xsl:when test="( $attrtype=($G_setSuppressedInterfaces/@name) )">
+ <xsl:comment><xsl:value-of select="concat('skipping attribute ', $attrname, ' for it is of a suppressed type')" /></xsl:comment>
+ </xsl:when>
+ <xsl:when test="@wsmap = 'suppress'">
+ <xsl:comment><xsl:value-of select="concat('skipping attribute ', $attrname, ' for it is suppressed')" /></xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:variable name="attrGetter"><xsl:call-template name="makeGetterName"><xsl:with-param name="attrname" select="$attrname" /></xsl:call-template></xsl:variable>
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('..', $G_attributeGetPrefix, $attrname)" /></xsl:call-template>
+ <xsl:call-template name="emitInOutOperation">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_methodname" select="$attrGetter" />
+ <xsl:with-param name="_params" select="/.." />
+ <xsl:with-param name="_resulttype" select='$attrtype' />
+ </xsl:call-template>
+ <xsl:if test="not($attrreadonly='yes')">
+ <xsl:variable name="attrSetter"><xsl:call-template name="makeSetterName"><xsl:with-param name="attrname" select="$attrname" /></xsl:call-template></xsl:variable>
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('..', $attrSetter)" /></xsl:call-template>
+ <xsl:call-template name="emitInOutOperation">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_methodname" select="$attrSetter" />
+ <xsl:with-param name="_params" select="/.." />
+ <xsl:with-param name="_resulttype" select='$attrtype' />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ <!-- b) then for the "real" methods whose messages we produced above -->
+ <xsl:for-each select="method">
+ <xsl:variable name="methodname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('operations for ', $ifname, '::', $methodname, ': method')" /></xsl:call-template>
+ <!-- skip this method if it has parameters of a type that has wsmap="suppress" -->
+ <xsl:choose>
+ <xsl:when test=" (param[@type=($G_setSuppressedInterfaces/@name)])
+ or (param[@mod='ptr'])" >
+ <xsl:comment><xsl:value-of select="concat('skipping method ', $methodname, ' for it has parameters with suppressed types')" /></xsl:comment>
+ </xsl:when>
+ <xsl:when test="@wsmap = 'suppress'">
+ <xsl:comment><xsl:value-of select="concat('skipping method ', $methodname, ' for it is suppressed')" /></xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="emitInOutOperation">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_methodname" select="$methodname" />
+ <xsl:with-param name="_params" select="param" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+</xsl:template>
+
+<!--
+ function emitOperationsInBindingForInterface
+ -->
+<xsl:template name="emitOperationsInBindingForInterface">
+ <xsl:param name="ifname" />
+ <xsl:param name="wsmap" />
+
+ <!-- a) again, first for the attributes whose messages we produced above -->
+ <xsl:for-each select="attribute">
+ <xsl:variable name="attrname" select="@name" />
+ <xsl:variable name="attrtype" select="@type" />
+ <xsl:variable name="attrreadonly" select="@readonly" />
+ <!-- skip this attribute if it has parameters of a type that has wsmap="suppress" -->
+ <xsl:choose>
+ <xsl:when test="( $attrtype=($G_setSuppressedInterfaces/@name) )">
+ <xsl:comment><xsl:value-of select="concat('skipping attribute ', $attrname, ' for it is of a suppressed type')" /></xsl:comment>
+ </xsl:when>
+ <xsl:when test="@wsmap = 'suppress'">
+ <xsl:comment><xsl:value-of select="concat('skipping attribute ', $attrname, ' for it is suppressed')" /></xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:variable name="attrGetter"><xsl:call-template name="makeGetterName"><xsl:with-param name="attrname" select="$attrname" /></xsl:call-template></xsl:variable>
+ <xsl:call-template name="emitInOutOperation">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_methodname" select="$attrGetter" />
+ <xsl:with-param name="_params" select="/.." />
+ <xsl:with-param name="_resulttype" select='$attrtype' />
+ <xsl:with-param name="_fSoap" select="1" />
+ </xsl:call-template>
+ <xsl:if test="not($attrreadonly='yes')">
+ <xsl:variable name="attrSetter"><xsl:call-template name="makeSetterName"><xsl:with-param name="attrname" select="$attrname" /></xsl:call-template></xsl:variable>
+ <xsl:call-template name="emitInOutOperation">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_methodname" select="$attrSetter" />
+ <xsl:with-param name="_params" select="/.." />
+ <xsl:with-param name="_resulttype" select='$attrtype' />
+ <xsl:with-param name="_fSoap" select="1" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ <!-- b) then for the "real" methods whose messages we produced above -->
+ <xsl:for-each select="method">
+ <xsl:variable name="methodname"><xsl:value-of select="@name" /></xsl:variable>
+ <!-- skip this method if it has parameters of a type that has wsmap="suppress" -->
+ <xsl:choose>
+ <xsl:when test=" (param[@type=($G_setSuppressedInterfaces/@name)])
+ or (param[@mod='ptr'])" >
+ <xsl:comment><xsl:value-of select="concat('skipping method ', $methodname, ' for it has parameters with suppressed types')" /></xsl:comment>
+ </xsl:when>
+ <xsl:when test="@wsmap = 'suppress'">
+ <xsl:comment><xsl:value-of select="concat('skipping method ', $methodname, ' for it is suppressed')" /></xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="emitInOutOperation">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_methodname" select="$methodname" />
+ <xsl:with-param name="_params" select="param" />
+ <xsl:with-param name="_fSoap" select="1" />
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+</xsl:template>
+
+<!--**********************************************************************
+ *
+ * matches
+ *
+ **********************************************************************-->
+
+<!--
+ template for "idl" match; this emits the header of the target file
+ and recurses into the libraries with interfaces (which are matched below)
+ -->
+<xsl:template match="/idl">
+ <xsl:comment>
+ DO NOT EDIT! This is a generated file.
+ Generated from: src/VBox/Main/idl/VirtualBox.xidl (VirtualBox's interface definitions in XML)
+ Generator: src/VBox/Main/webservice/websrv-wsdl.xsl
+</xsl:comment>
+
+ <xsl:apply-templates />
+
+</xsl:template>
+
+<!--
+ template for "if" match: ignore all ifs except those for wsdl
+ -->
+<xsl:template match="if">
+ <xsl:if test="@target='wsdl'">
+ <xsl:apply-templates/>
+ </xsl:if>
+</xsl:template>
+
+<!--
+ template for "cpp": ignore
+ -->
+<xsl:template match="cpp">
+<!-- ignore this -->
+</xsl:template>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ class
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="module/class">
+<!-- swallow -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ enum
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="enum">
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ desc
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="desc">
+<!-- swallow -->
+</xsl:template>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ note
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="note">
+ <xsl:apply-templates />
+</xsl:template>
+
+<!--
+ "library" match: we use this to emit most of the WSDL <types> section.
+ With WSDL "document" style, this requires us to go through all interfaces
+ and emit complexTypes for all method arguments and return values.
+-->
+<xsl:template match="library">
+ <wsdl:definitions
+ name="VirtualBox"
+ xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
+ <xsl:attribute name="targetNamespace"><xsl:value-of select="$G_targetNamespace" /></xsl:attribute>
+ <!-- at top of WSDL file, dump a <types> section with user-defined types -->
+ <xsl:comment>
+ ******************************************************
+ *
+ * WSDL type definitions in XML Schema
+ *
+ ******************************************************
+</xsl:comment>
+ <wsdl:types>
+ <xsd:schema>
+ <xsl:attribute name="targetNamespace"><xsl:value-of select='$G_targetNamespace' /></xsl:attribute>
+
+ <!-- type-define all enums -->
+ <xsl:comment>
+ ******************************************************
+ * enumerations
+ ******************************************************
+</xsl:comment>
+ <xsl:for-each select="//enum">
+ <xsl:comment> enum: <xsl:value-of select="@name" /> -
+ <xsl:for-each select="const">
+ <xsl:value-of select="@name" />: <xsl:value-of select="@value" /> -
+ </xsl:for-each>
+</xsl:comment>
+ <xsd:simpleType>
+ <xsl:attribute name="name"><xsl:value-of select="@name" /></xsl:attribute>
+ <xsd:restriction base="xsd:string">
+ <!-- XML Schema does not seem to have a C-like mapping between identifiers and numbers;
+ instead, it treats enumerations like strings that can have only specific values. -->
+ <xsl:for-each select="const">
+ <xsd:enumeration>
+ <xsl:attribute name="value"><xsl:value-of select="@name" /></xsl:attribute>
+ </xsd:enumeration>
+ </xsl:for-each>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsl:for-each>
+
+ <!-- type-define all interfaces that have wsmap=struct as structs (complexTypes) -->
+ <xsl:comment>
+ ******************************************************
+ * structs
+ ******************************************************
+</xsl:comment>
+ <xsl:for-each select="//interface[@wsmap='struct']">
+ <xsl:comment> interface <xsl:value-of select="@name" /> as struct: </xsl:comment>
+ <xsd:complexType>
+ <xsl:attribute name="name"><xsl:value-of select="@name" /></xsl:attribute>
+ <xsd:sequence>
+ <xsl:for-each select="attribute">
+ <xsd:element>
+ <xsl:attribute name="name"><xsl:value-of select="@name" /></xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:call-template name="emitConvertedType">
+ <xsl:with-param name="type" select="@type" />
+ </xsl:call-template>
+ </xsl:attribute>
+ </xsd:element>
+ </xsl:for-each>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsl:for-each>
+
+ <!-- for WSDL 'document' style, we need to emit elements since we can't
+ refer to types in message parts as with RPC style -->
+ <xsl:if test="$G_basefmt='document'">
+ <xsl:comment>
+ ******************************************************
+ * elements for message arguments (parts); generated for WSDL 'document' style
+ ******************************************************
+</xsl:comment>
+
+ <xsl:for-each select="//interface">
+ <xsl:variable name="ifname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="wsmap"><xsl:value-of select="@wsmap" /></xsl:variable>
+
+ <xsl:if test='not( ($wsmap="suppress") or ($wsmap="struct") )'>
+ <xsl:comment>Interface <xsl:copy-of select="$ifname" /></xsl:comment>
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('************* types: elements for interface &quot;', $ifname, '&quot;')" /></xsl:call-template>
+ <!-- a) attributes first -->
+ <xsl:for-each select="attribute">
+ <xsl:variable name="attrname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="attrtype"><xsl:value-of select="@type" /></xsl:variable>
+ <xsl:variable name="attrsafearray"><xsl:value-of select="@safearray" /></xsl:variable>
+ <xsl:variable name="attrreadonly"><xsl:value-of select="@readonly" /></xsl:variable>
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('elements for ', $ifname, '::', $attrname, ': attribute of type &quot;', $attrtype, '&quot;, readonly: ', $attrreadonly)" /></xsl:call-template>
+ <!-- skip this attribute if it has parameters of a type that has wsmap="suppress" -->
+ <xsl:choose>
+ <xsl:when test="( $attrtype=($G_setSuppressedInterfaces/@name) )">
+ <xsl:comment><xsl:value-of select="concat('skipping attribute ', $attrtype, ' for it is of a suppressed type')" /></xsl:comment>
+ </xsl:when>
+ <xsl:when test="@wsmap = 'suppress'">
+ <xsl:comment><xsl:value-of select="concat('skipping attribute ', $attrname, ' for it is suppressed')" /></xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="@readonly='yes'">
+ <xsl:comment> readonly attribute <xsl:copy-of select="$ifname" />::<xsl:copy-of select="$attrname" /> </xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:comment> read/write attribute <xsl:copy-of select="$ifname" />::<xsl:copy-of select="$attrname" /> </xsl:comment>
+ </xsl:otherwise>
+ </xsl:choose>
+ <!-- aa) get method: emit request and result -->
+ <xsl:call-template name="emitGetAttributeElements">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="wsmap" select="$wsmap" />
+ <xsl:with-param name="attrname" select="$attrname" />
+ <xsl:with-param name="attrtype" select="$attrtype" />
+ <xsl:with-param name="attrsafearray" select="$attrsafearray" />
+ </xsl:call-template>
+ <!-- bb) emit a set method if the attribute is read/write -->
+ <xsl:if test="not($attrreadonly='yes')">
+ <xsl:variable name="attrSetter"><xsl:call-template name="makeSetterName"><xsl:with-param name="attrname" select="$attrname" /></xsl:call-template></xsl:variable>
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('..', $ifname, '::', $attrSetter)" /></xsl:call-template>
+ <xsl:call-template name="emitRequestElements">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_wsmap" select="$wsmap" />
+ <xsl:with-param name="_methodname" select="$attrSetter" />
+ <xsl:with-param name="_params" select="/.." />
+ <xsl:with-param name="_valuetype" select="$attrtype" />
+ <xsl:with-param name="_valuesafearray" select="$attrsafearray" />
+ </xsl:call-template>
+ <xsl:call-template name="emitResultElements">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_methodname" select="$attrSetter" />
+ <xsl:with-param name="_params" select="/.." />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each> <!-- select="attribute" -->
+ <!-- b) "real" methods after the attributes -->
+ <xsl:for-each select="method">
+ <xsl:variable name="methodname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:call-template name="debugMsg"><xsl:with-param name="msg" select="concat('messages for ', $ifname, '::', $methodname, ': method')" /></xsl:call-template>
+ <xsl:comment> method <xsl:copy-of select="$ifname" />::<xsl:copy-of select="$methodname" /> </xsl:comment>
+ <!-- skip this method if it has parameters of a type that has wsmap="suppress" -->
+ <xsl:choose>
+ <xsl:when test=" (param[@type=($G_setSuppressedInterfaces/@name)])
+ or (param[@mod='ptr'])" >
+ <xsl:comment><xsl:value-of select="concat('skipping method ', $methodname, ' for it has parameters with suppressed types')" /></xsl:comment>
+ </xsl:when>
+ <xsl:when test="@wsmap = 'suppress'">
+ <xsl:comment><xsl:value-of select="concat('skipping method ', $methodname, ' for it is suppressed')" /></xsl:comment>
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- always emit a request message -->
+ <xsl:call-template name="emitRequestElements">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_wsmap" select="$wsmap" />
+ <xsl:with-param name="_methodname" select="$methodname" />
+ <xsl:with-param name="_params" select="param" />
+ </xsl:call-template>
+ <!-- emit a second "result" message only if the method has "out" arguments or a return value -->
+ <!-- <xsl:if test="(count(param[@dir='out'] | param[@dir='return']) > 0)"> -->
+ <xsl:call-template name="emitResultElements">
+ <xsl:with-param name="_ifname" select="$ifname" />
+ <xsl:with-param name="_wsmap" select="$wsmap" />
+ <xsl:with-param name="_methodname" select="$methodname" />
+ <xsl:with-param name="_params" select="param" />
+ </xsl:call-template>
+ <!-- </xsl:if> -->
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </xsl:if> <!-- <xsl:if test='not( ($wsmap="suppress") or ($wsmap="struct") )'> -->
+ </xsl:for-each>
+
+ </xsl:if> <!-- <xsl:if test="$G_basefmt='document'"> -->
+
+ <xsl:comment>
+ ******************************************************
+ * faults
+ ******************************************************
+</xsl:comment>
+
+ <xsd:element name="InvalidObjectFault">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="badObjectID">
+ <xsl:attribute name="type">
+ <xsl:value-of select="$G_typeObjectRef" />
+ </xsl:attribute>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:element name="RuntimeFault">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="resultCode" type="xsd:int" />
+ <xsd:element name="returnval">
+ <xsl:attribute name="type">
+ <xsl:value-of select="$G_typeObjectRef" />
+ </xsl:attribute>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <!-- done! -->
+ </xsd:schema>
+
+
+ </wsdl:types>
+
+ <wsdl:message name="InvalidObjectFaultMsg">
+ <wsdl:part name="fault" element="vbox:InvalidObjectFault" />
+ </wsdl:message>
+ <wsdl:message name="RuntimeFaultMsg">
+ <wsdl:part name="fault" element="vbox:RuntimeFault" />
+ </wsdl:message>
+
+ <xsl:comment>
+ ******************************************************
+ *
+ * messages for all interfaces
+ *
+ ******************************************************
+</xsl:comment>
+
+ <xsl:for-each select="//interface">
+ <xsl:variable name="ifname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="wsmap"><xsl:value-of select="@wsmap" /></xsl:variable>
+
+ <xsl:call-template name="verifyInterface">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="wsmap" select="$wsmap" />
+ </xsl:call-template>
+
+ <xsl:comment>
+ *************************************
+ messages for interface <xsl:copy-of select="$ifname" />
+ *************************************
+ </xsl:comment>
+
+ <xsl:if test='not( ($wsmap="suppress") or ($wsmap="struct") )'>
+ <xsl:call-template name="emitMessagesForInterface">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="wsmap" select="$wsmap" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:for-each>
+
+ <xsl:comment>
+ ******************************************************
+ *
+ * one portType for all interfaces
+ *
+ ******************************************************
+ </xsl:comment>
+
+ <wsdl:portType>
+ <xsl:attribute name="name"><xsl:copy-of select="'vbox'" /><xsl:value-of select="$G_portTypeSuffix" /></xsl:attribute>
+
+ <xsl:for-each select="//interface">
+ <xsl:variable name="ifname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="wsmap"><xsl:value-of select="@wsmap" /></xsl:variable>
+
+ <xsl:comment>
+ *************************************
+ operations in portType for interface <xsl:copy-of select="$ifname" />
+ *************************************
+ </xsl:comment>
+
+ <xsl:if test='not( ($wsmap="suppress") or ($wsmap="struct") )'>
+ <xsl:call-template name="emitOperationsInPortTypeForInterface">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="wsmap" select="$wsmap" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:for-each>
+ </wsdl:portType>
+
+ <xsl:comment>
+ ******************************************************
+ *
+ * one binding for all interfaces
+ *
+ ******************************************************
+ </xsl:comment>
+
+ <wsdl:binding>
+ <xsl:attribute name="name"><xsl:value-of select="concat('vbox', $G_bindingSuffix)" /></xsl:attribute>
+ <xsl:attribute name="type"><xsl:value-of select="concat('vbox:vbox', $G_portTypeSuffix)" /></xsl:attribute>
+
+ <soap:binding>
+ <xsl:attribute name="style"><xsl:value-of select="$G_basefmt" /></xsl:attribute>
+ <xsl:attribute name="transport">http://schemas.xmlsoap.org/soap/http</xsl:attribute>
+ </soap:binding>
+
+ <xsl:for-each select="//interface">
+ <xsl:variable name="ifname"><xsl:value-of select="@name" /></xsl:variable>
+ <xsl:variable name="wsmap"><xsl:value-of select="@wsmap" /></xsl:variable>
+
+ <xsl:comment>
+ *************************************
+ operations in portType for interface <xsl:copy-of select="$ifname" />
+ *************************************
+ </xsl:comment>
+
+ <xsl:if test='not( ($wsmap="suppress") or ($wsmap="struct") )'>
+ <xsl:call-template name="emitOperationsInBindingForInterface">
+ <xsl:with-param name="ifname" select="$ifname" />
+ <xsl:with-param name="wsmap" select="$wsmap" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:for-each>
+ </wsdl:binding>
+
+ </wsdl:definitions>
+</xsl:template>
+
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/webservice/websrv-wsdl2gsoapH.xsl b/src/VBox/Main/webservice/websrv-wsdl2gsoapH.xsl
new file mode 100644
index 00000000..4303e537
--- /dev/null
+++ b/src/VBox/Main/webservice/websrv-wsdl2gsoapH.xsl
@@ -0,0 +1,308 @@
+<?xml version="1.0"?>
+
+<!--
+ websrv-gsoapH.xsl:
+ XSLT stylesheet that generates a gSOAP pseudo-header
+ file from VirtualBox.xidl. Such a pseudo-header files
+ can be fed into gSOAP's soapcpp2 to create web service
+ client headers and server stubs.
+ See webservice/Makefile.kmk for an overview of all the things
+ generated for the webservice.
+-->
+<!--
+ Copyright (C) 2006-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+ xmlns:vbox="http://www.virtualbox.org/"
+ xmlns:exsl="http://exslt.org/common"
+ extension-element-prefixes="exsl"
+ >
+
+ <xsl:param name="G_argDebug" />
+
+ <xsl:output method="text"/>
+
+ <xsl:strip-space elements="*"/>
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ global XSLT variables
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:variable name="G_xsltFilename" select="'websrv-wsdl2gsoapH.xsl'" />
+
+<xsl:include href="../idl/typemap-shared.inc.xsl" />
+
+<!-- collect all interfaces with "wsmap='suppress'" in a global variable for
+ quick lookup -->
+<xsl:variable name="G_setSuppressedInterfaces"
+ select="//interface[@wsmap='suppress']" />
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ Keys for more efficiently looking up of stuff
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:key name="G_keyMessagesByName" match="//wsdl:message[@name]" use="@name"/>
+<xsl:key name="G_keySimpleTypesByName" match="//xsd:simpleType[@name]" use="@name"/>
+<xsl:key name="G_keyComplexTypesByName" match="//xsd:complexType[@name]" use="@name"/>
+
+
+<!-- - - - - - - - - - - - - - - - - - - - - - -
+ root match
+ - - - - - - - - - - - - - - - - - - - - - - -->
+
+<xsl:template match="/wsdl:definitions">
+ <xsl:text><![CDATA[
+/* DO NOT EDIT! This is a generated file.
+ * Generated from: vboxweb.wsdl (generated WSDL file)
+ * Generator: src/VBox/Main/webservice/websrv-gsoapH.xsl
+ *
+ * Note: This is not a real C/C++ header file. Instead, gSOAP uses files like this
+ * one -- with a pseudo-C-header syntax -- to describe a web service API.
+ */
+
+// STL vector containers
+#import "stlvector.h"
+
+]]></xsl:text>
+
+ <xsl:value-of select="concat('//gsoap vbox schema namespace: ', $G_targetNamespace)" />
+ <xsl:value-of select="concat('//gsoap vbox schema form: unqualified', '')" />
+
+ <xsl:text>
+/****************************************************************************
+ *
+ * declarations
+ *
+ ****************************************************************************/
+
+// forward declarations
+class _vbox__InvalidObjectFault;
+class _vbox__RuntimeFault;
+
+struct SOAP_ENV__Detail
+{
+ _vbox__InvalidObjectFault *vbox__InvalidObjectFault;
+ _vbox__RuntimeFault *vbox__RuntimeFault;
+ int __type;
+ void *fault;
+ _XML __any;
+};
+</xsl:text>
+
+ <xsl:apply-templates />
+</xsl:template>
+
+<xsl:template name="convertSequence">
+ <xsl:param name="xmltype" />
+ <xsl:param name="ctype" />
+
+ <xsl:value-of select="concat('class ', $ctype, $G_sNewLine)" />
+ <xsl:text>{
+ public:
+</xsl:text>
+ <xsl:for-each select="xsd:element">
+ <xsl:variable name="typefield" select="@type" />
+ <xsl:variable name="xmltypefield" select="substring($typefield, 5)" /><!-- remove "xsd:" prefix-->
+ <xsl:variable name="withoutvboxtypefield" select="substring($typefield, 6)" /><!-- remove "vbox:" prefix-->
+ <xsl:variable name="ctypefield" select="exsl:node-set($G_aSharedTypes)/type[@xmlname=$xmltypefield]/@cname" />
+ <xsl:text> </xsl:text>
+ <xsl:choose>
+ <xsl:when test="$ctypefield">
+ <!-- array or simple type: depends on whether maxOccurs="unbounded" is in WSDL -->
+ <xsl:choose>
+ <xsl:when test="@maxOccurs='unbounded'">
+ <xsl:value-of select="concat('std::vector&lt;', $ctypefield, '&gt;')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$ctypefield" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <!-- is there an enum of this type? (look up in simple types) -->
+ <xsl:when test="count(key('G_keySimpleTypesByName', $withoutvboxtypefield)) > 0">
+ <xsl:variable name="enumname">
+ <xsl:value-of select="concat('enum vbox__', $withoutvboxtypefield)" />
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="@maxOccurs='unbounded'">
+ <xsl:value-of select="concat('std::vector&lt;', $enumname, '&gt;')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$enumname" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <!-- is this one of the vbox types? (look up in complex types) -->
+ <xsl:when test="count(key('G_keyComplexTypesByName', $withoutvboxtypefield)) > 0">
+ <!-- array or simple type: depends on whether maxOccurs="unbounded" is in WSDL -->
+ <xsl:choose>
+ <xsl:when test="@maxOccurs='unbounded'">
+ <xsl:value-of select="concat('std::vector&lt;vbox__', $withoutvboxtypefield, '*&gt;')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('vbox__', $withoutvboxtypefield, '*')" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('std::string', '')" />
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:variable name="underscoredname">
+ <xsl:call-template name="escapeUnderscores">
+ <xsl:with-param name="string" select="@name" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' ', $underscoredname, ' 1;', $G_sNewLine)" />
+ </xsl:for-each>
+ <xsl:text> struct soap *soap;
+};
+</xsl:text>
+<xsl:call-template name="xsltprocNewlineOutputHack"/>
+
+</xsl:template>
+
+<xsl:template match="wsdl:types/xsd:schema">
+
+ <!-- enums are represented as simple types -->
+ <xsl:for-each select="xsd:simpleType">
+ <xsl:variable name="ctype" select="concat('vbox__', @name)" />
+ <xsl:for-each select="xsd:restriction">
+ <xsl:value-of select="concat('enum ', $ctype)" />
+ <xsl:text>
+{
+</xsl:text>
+ <xsl:for-each select="xsd:enumeration">
+ <xsl:variable name="underscoredname">
+ <xsl:call-template name="escapeUnderscores">
+ <xsl:with-param name="string" select="@value" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' ', $ctype, '__', $underscoredname)" />
+ <xsl:if test = "not(position()=last())" >
+ <xsl:text >,</xsl:text>
+ </xsl:if>
+ <xsl:text>
+</xsl:text>
+ </xsl:for-each>
+ <xsl:text>};
+
+</xsl:text>
+ </xsl:for-each>
+ </xsl:for-each>
+
+ <!-- structs and arrays are represented as complex types -->
+ <xsl:for-each select="xsd:complexType">
+ <xsl:variable name="xmltype" select="@name" />
+ <xsl:variable name="ctype" select="concat('vbox__', $xmltype)" />
+ <xsl:for-each select="xsd:sequence">
+ <xsl:call-template name="convertSequence">
+ <xsl:with-param name="xmltype" select="$xmltype" />
+ <xsl:with-param name="ctype" select="$ctype" />
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:for-each>
+
+ <!-- individual message elements are represented with xsd:element -> xsd:complexType -> xsdSequence -->
+ <xsl:for-each select="xsd:element">
+ <xsl:variable name="xmltype" select="@name" />
+ <xsl:variable name="underscoredname">
+ <xsl:call-template name="escapeUnderscores">
+ <xsl:with-param name="string" select="$xmltype" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="ctype" select="concat('_vbox__', $underscoredname)" />
+ <xsl:for-each select="xsd:complexType">
+ <xsl:for-each select="xsd:sequence">
+ <xsl:call-template name="convertSequence">
+ <xsl:with-param name="xmltype" select="$xmltype" />
+ <xsl:with-param name="ctype" select="$ctype" />
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:for-each>
+ </xsl:for-each>
+</xsl:template>
+
+<xsl:template match="wsdl:portType">
+
+ <xsl:value-of select="concat('//gsoap vbox service name: vbox', $G_bindingSuffix, $G_sNewLine)" />
+ <xsl:value-of select="concat('//gsoap vbox service type: vbox', $G_portTypeSuffix, $G_sNewLine)" />
+ <xsl:value-of select="concat('//gsoap vbox service namespace: ', $G_targetNamespace, $G_targetNamespaceSeparator, $G_sNewLine)" />
+ <xsl:value-of select="concat('//gsoap vbox service transport: ', 'http://schemas.xmlsoap.org/soap/http', $G_sNewLine)" />
+
+ <xsl:for-each select="wsdl:operation">
+ <xsl:variable name="methodname" select="@name" />
+ <xsl:variable name="cmethodname">
+ <xsl:call-template name="escapeUnderscores">
+ <xsl:with-param name="string" select="$methodname" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="requestmsg" select="concat($methodname, $G_methodRequest)" />
+ <xsl:variable name="responsemsg" select="concat($methodname, $G_methodResponse)" />
+
+ <xsl:value-of select="concat($G_sNewLine, '//gsoap vbox service method-style: ', $cmethodname, ' ', $G_basefmt)" />
+ <xsl:value-of select="concat($G_sNewLine, '//gsoap vbox service method-encoding: ', $cmethodname, ' ', $G_parmfmt)" />
+ <xsl:value-of select="concat($G_sNewLine, '//gsoap vbox service method-action: ', $cmethodname, ' &quot;&quot;')" />
+ <xsl:value-of select="concat($G_sNewLine, '//gsoap vbox service method-fault: ', $cmethodname, ' vbox__InvalidObjectFault')" />
+ <xsl:value-of select="concat($G_sNewLine, '//gsoap vbox service method-fault: ', $cmethodname, ' vbox__RuntimeFault')" />
+ <xsl:value-of select="concat($G_sNewLine, 'int __vbox__', $cmethodname, '(', $G_sNewLine)" />
+
+ <!-- request element -->
+ <xsl:variable name="reqtype" select="key('G_keyMessagesByName', $requestmsg)/wsdl:part/@element" />
+ <xsl:if test="not($reqtype)">
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('wsdl:portType match: Cannot find message with &quot;name&quot;=&quot;', $requestmsg, '&quot;.')" />
+ </xsl:call-template>
+ </xsl:if>
+ <xsl:variable name="creqtype">
+ <xsl:call-template name="escapeUnderscores">
+ <xsl:with-param name="string" select="substring($reqtype, 6)" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' _vbox__', $creqtype, '* vbox__', $creqtype, ',', $G_sNewLine)"/>
+ <!-- response element -->
+ <xsl:variable name="resptype" select="key('G_keyMessagesByName', $responsemsg)/wsdl:part/@element" />
+ <xsl:if test="not($resptype)">
+ <xsl:call-template name="fatalError">
+ <xsl:with-param name="msg" select="concat('wsdl:portType match: Cannot find message with &quot;name&quot;=&quot;', $responsemsg, '&quot;.')" />
+ </xsl:call-template>
+ </xsl:if>
+ <xsl:variable name="cresptype">
+ <xsl:call-template name="escapeUnderscores">
+ <xsl:with-param name="string" select="substring($resptype, 6)" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:value-of select="concat(' _vbox__', $cresptype, '* vbox__', $cresptype, $G_sNewLine)"/>
+
+ <xsl:text>);</xsl:text>
+ <xsl:call-template name="xsltprocNewlineOutputHack"/>
+
+ </xsl:for-each>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/webservice/webtest.cpp b/src/VBox/Main/webservice/webtest.cpp
new file mode 100644
index 00000000..166ebcce
--- /dev/null
+++ b/src/VBox/Main/webservice/webtest.cpp
@@ -0,0 +1,572 @@
+/* $Id: webtest.cpp $ */
+/** @file
+ * webtest.cpp:
+ * demo webservice client in C++. This mimics some of the
+ * functionality of VBoxManage for testing purposes.
+ */
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+// gSOAP headers (must come after vbox includes because it checks for conflicting defs)
+#include "soapStub.h"
+
+// include generated namespaces table
+#include "vboxwebsrv.nsmap"
+
+#include <iostream>
+#include <iprt/sanitized/sstream>
+#include <iprt/sanitized/string>
+
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/errcore.h>
+
+
+static void usage(int exitcode)
+{
+ std::cout <<
+ "webtest: VirtualBox webservice testcase.\n"
+ "\nUsage: webtest [options] [command]...\n"
+ "\nSupported options:\n"
+ " -h: print this help message and exit.\n"
+ " -c URL: specify the webservice server URL (default http://localhost:18083/).\n"
+ "\nSupported commands:\n"
+ " - IWebsessionManager:\n"
+ " - webtest logon <user> <pass>: IWebsessionManager::logon().\n"
+ " - webtest getsession <vboxref>: IWebsessionManager::getSessionObject().\n"
+ " - webtest logoff <vboxref>: IWebsessionManager::logoff().\n"
+ " - IVirtualBox:\n"
+ " - webtest version <vboxref>: IVirtualBox::getVersion().\n"
+ " - webtest gethost <vboxref>: IVirtualBox::getHost().\n"
+ " - webtest getpc <vboxref>: IVirtualBox::getPerformanceCollector().\n"
+ " - webtest getmachines <vboxref>: IVirtualBox::getMachines().\n"
+ " - webtest createmachine <vboxref> <settingsPath> <name>: IVirtualBox::createMachine().\n"
+ " - webtest registermachine <vboxref> <machineref>: IVirtualBox::registerMachine().\n"
+ " - IHost:\n"
+ " - webtest getdvddrives <hostref>: IHost::getDVDDrives.\n"
+ " - IHostDVDDrive:\n"
+ " - webtest getdvdname <dvdref>: IHostDVDDrive::getname.\n"
+ " - IMachine:\n"
+ " - webtest getname <machineref>: IMachine::getName().\n"
+ " - webtest getid <machineref>: IMachine::getId().\n"
+ " - webtest getostype <machineref>: IMachine::getGuestOSType().\n"
+ " - webtest savesettings <machineref>: IMachine::saveSettings().\n"
+ " - IPerformanceCollector:\n"
+ " - webtest setupmetrics <pcref>: IPerformanceCollector::setupMetrics()\n"
+ " - webtest querymetricsdata <pcref>: IPerformanceCollector::QueryMetricsData()\n"
+ " - IVirtualBoxErrorInfo:\n"
+ " - webtest errorinfo <eiref>: various IVirtualBoxErrorInfo getters\n"
+ " - All managed object references:\n"
+ " - webtest getif <ref>: report interface of object.\n"
+ " - webtest release <ref>: IUnknown::Release().\n";
+ exit(exitcode);
+}
+
+/**
+ *
+ * @param argc
+ * @param argv[]
+ * @return
+ */
+int main(int argc, char* argv[])
+{
+ bool fSSL = false;
+ const char *pcszArgEndpoint = "http://localhost:18083/";
+
+ /* SSL callbacks drag in IPRT sem/thread use, so make sure it is ready. */
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ int ap;
+ for (ap = 1; ap < argc; ap++)
+ {
+ if (argv[ap][0] == '-')
+ {
+ if (!strcmp(argv[ap], "-h"))
+ usage(0);
+ else if (!strcmp(argv[ap], "-c"))
+ {
+ ap++;
+ if (ap >= argc)
+ usage(1);
+ pcszArgEndpoint = argv[ap];
+ fSSL = !strncmp(pcszArgEndpoint, "https://", 8);
+ }
+ else
+ usage(1);
+ }
+ else
+ break;
+ }
+
+ if (argc < 1 + ap)
+ usage(1);
+
+#ifdef WITH_OPENSSL
+ if (fSSL)
+ soap_ssl_init();
+#endif /* WITH_OPENSSL */
+
+ struct soap soap; // gSOAP runtime environment
+ soap_init(&soap); // initialize runtime environment (only once)
+#ifdef WITH_OPENSSL
+ // Use SOAP_SSL_NO_AUTHENTICATION here to accept broken server configs.
+ // In a real world setup please use at least SOAP_SSL_DEFAULT and provide
+ // the necessary CA certificate for validating the server's certificate.
+ if (fSSL && soap_ssl_client_context(&soap, SOAP_SSL_NO_AUTHENTICATION | SOAP_TLSv1,
+ NULL /*clientkey*/, NULL /*password*/,
+ NULL /*cacert*/, NULL /*capath*/,
+ NULL /*randfile*/))
+ {
+ soap_print_fault(&soap, stderr);
+ exit(1);
+ }
+#endif /* WITH_OPENSSL */
+
+ const char *pcszMode = argv[ap];
+ int soaprc = SOAP_SVR_FAULT;
+
+ if (!strcmp(pcszMode, "logon"))
+ {
+ if (argc < 3 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IWebsessionManager_USCORElogon req;
+ req.username = argv[ap + 1];
+ req.password = argv[ap + 2];
+ _vbox__IWebsessionManager_USCORElogonResponse resp;
+
+ if (!(soaprc = soap_call___vbox__IWebsessionManager_USCORElogon(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ std::cout << "VirtualBox objref: \"" << resp.returnval << "\"\n";
+ }
+ }
+ else if (!strcmp(pcszMode, "getsession"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IWebsessionManager_USCOREgetSessionObject req;
+ req.refIVirtualBox = argv[ap + 1];
+ _vbox__IWebsessionManager_USCOREgetSessionObjectResponse resp;
+
+ if (!(soaprc = soap_call___vbox__IWebsessionManager_USCOREgetSessionObject(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ std::cout << "session: \"" << resp.returnval << "\"\n";
+ }
+ }
+ else if (!strcmp(pcszMode, "logoff"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IWebsessionManager_USCORElogoff req;
+ req.refIVirtualBox = argv[ap + 1];
+ _vbox__IWebsessionManager_USCORElogoffResponse resp;
+
+ if (!(soaprc = soap_call___vbox__IWebsessionManager_USCORElogoff(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ {
+ ;
+ }
+ }
+ }
+ else if (!strcmp(pcszMode, "version"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IVirtualBox_USCOREgetVersion req;
+ req._USCOREthis = argv[ap + 1];
+ _vbox__IVirtualBox_USCOREgetVersionResponse resp;
+
+ if (!(soaprc = soap_call___vbox__IVirtualBox_USCOREgetVersion(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ std::cout << "version: \"" << resp.returnval << "\"\n";
+ }
+ }
+ else if (!strcmp(pcszMode, "gethost"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IVirtualBox_USCOREgetHost req;
+ req._USCOREthis = argv[ap + 1];
+ _vbox__IVirtualBox_USCOREgetHostResponse resp;
+
+ if (!(soaprc = soap_call___vbox__IVirtualBox_USCOREgetHost(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ {
+ std::cout << "Host objref " << resp.returnval << "\n";
+ }
+ }
+ }
+ else if (!strcmp(pcszMode, "getpc"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IVirtualBox_USCOREgetPerformanceCollector req;
+ req._USCOREthis = argv[ap + 1];
+ _vbox__IVirtualBox_USCOREgetPerformanceCollectorResponse resp;
+
+ if (!(soaprc = soap_call___vbox__IVirtualBox_USCOREgetPerformanceCollector(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ {
+ std::cout << "Performance collector objref " << resp.returnval << "\n";
+ }
+ }
+ }
+ else if (!strcmp(pcszMode, "getmachines"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IVirtualBox_USCOREgetMachines req;
+ req._USCOREthis = argv[ap + 1];
+ _vbox__IVirtualBox_USCOREgetMachinesResponse resp;
+
+ if (!(soaprc = soap_call___vbox__IVirtualBox_USCOREgetMachines(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ {
+ size_t c = resp.returnval.size();
+ for (size_t i = 0;
+ i < c;
+ ++i)
+ {
+ std::cout << "Machine " << i << ": objref " << resp.returnval[i] << "\n";
+ }
+ }
+ }
+ }
+ else if (!strcmp(pcszMode, "createmachine"))
+ {
+ if (argc < 4 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IVirtualBox_USCOREcreateMachine req;
+ req._USCOREthis = argv[ap + 1];
+ req.settingsFile = argv[ap + 2];
+ req.name = argv[ap + 3];
+ std::cout << "createmachine: settingsFile = \"" << req.settingsFile << "\", name = \"" << req.name << "\"\n";
+ _vbox__IVirtualBox_USCOREcreateMachineResponse resp;
+
+ if (!(soaprc = soap_call___vbox__IVirtualBox_USCOREcreateMachine(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ std::cout << "Machine created: managed object reference ID is " << resp.returnval << "\n";
+ }
+ }
+ else if (!strcmp(pcszMode, "registermachine"))
+ {
+ if (argc < 3 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IVirtualBox_USCOREregisterMachine req;
+ req._USCOREthis = argv[ap + 1];
+ req.machine = argv[ap + 2];
+ _vbox__IVirtualBox_USCOREregisterMachineResponse resp;
+ if (!(soaprc = soap_call___vbox__IVirtualBox_USCOREregisterMachine(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ std::cout << "Machine registered.\n";
+ }
+ }
+ else if (!strcmp(pcszMode, "getdvddrives"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IHost_USCOREgetDVDDrives req;
+ req._USCOREthis = argv[ap + 1];
+ _vbox__IHost_USCOREgetDVDDrivesResponse resp;
+ if (!(soaprc = soap_call___vbox__IHost_USCOREgetDVDDrives(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ {
+ size_t c = resp.returnval.size();
+ for (size_t i = 0;
+ i < c;
+ ++i)
+ {
+ std::cout << "DVD drive " << i << ": objref " << resp.returnval[i] << "\n";
+ }
+ }
+ }
+ }
+ else if (!strcmp(pcszMode, "getname"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IMachine_USCOREgetName req;
+ req._USCOREthis = argv[ap + 1];
+ _vbox__IMachine_USCOREgetNameResponse resp;
+ if (!(soaprc = soap_call___vbox__IMachine_USCOREgetName(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ printf("Name is: %s\n", resp.returnval.c_str());
+ }
+ }
+ else if (!strcmp(pcszMode, "getid"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IMachine_USCOREgetId req;
+ req._USCOREthis = argv[ap + 1];
+ _vbox__IMachine_USCOREgetIdResponse resp;
+ if (!(soaprc = soap_call___vbox__IMachine_USCOREgetId(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ std::cout << "UUID is: " << resp.returnval << "\n";;
+ }
+ }
+ else if (!strcmp(pcszMode, "getostypeid"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IMachine_USCOREgetOSTypeId req;
+ req._USCOREthis = argv[ap + 1];
+ _vbox__IMachine_USCOREgetOSTypeIdResponse resp;
+ if (!(soaprc = soap_call___vbox__IMachine_USCOREgetOSTypeId(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ std::cout << "Guest OS type is: " << resp.returnval << "\n";
+ }
+ }
+ else if (!strcmp(pcszMode, "savesettings"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IMachine_USCOREsaveSettings req;
+ req._USCOREthis = argv[ap + 1];
+ _vbox__IMachine_USCOREsaveSettingsResponse resp;
+ if (!(soaprc = soap_call___vbox__IMachine_USCOREsaveSettings(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ std::cout << "Settings saved\n";
+ }
+ }
+ else if (!strcmp(pcszMode, "setupmetrics"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IPerformanceCollector_USCOREsetupMetrics req;
+ req._USCOREthis = argv[ap + 1];
+// req.metricNames[0] = "*";
+// req.objects
+ req.period = 1; // seconds
+ req.count = 100;
+ _vbox__IPerformanceCollector_USCOREsetupMetricsResponse resp;
+ if (!(soaprc = soap_call___vbox__IPerformanceCollector_USCOREsetupMetrics(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ {
+ size_t c = resp.returnval.size();
+ for (size_t i = 0;
+ i < c;
+ ++i)
+ {
+ std::cout << "Metric " << i << ": objref " << resp.returnval[i] << "\n";
+ }
+ }
+ }
+ }
+ else if (!strcmp(pcszMode, "querymetricsdata"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IPerformanceCollector_USCOREqueryMetricsData req;
+ req._USCOREthis = argv[ap + 1];
+// req.metricNames[0] = "*";
+// req.objects
+ _vbox__IPerformanceCollector_USCOREqueryMetricsDataResponse resp;
+ if (!(soaprc = soap_call___vbox__IPerformanceCollector_USCOREqueryMetricsData(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ {
+ size_t c = resp.returnval.size();
+ for (size_t i = 0;
+ i < c;
+ ++i)
+ {
+ std::cout << "long " << i << ": " << resp.returnval[i] << "\n";
+ }
+ }
+ }
+ }
+ else if (!strcmp(pcszMode, "errorinfo"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IVirtualBoxErrorInfo_USCOREgetResultCode req;
+ req._USCOREthis = argv[ap + 1];
+ _vbox__IVirtualBoxErrorInfo_USCOREgetResultCodeResponse resp;
+ if (!(soaprc = soap_call___vbox__IVirtualBoxErrorInfo_USCOREgetResultCode(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ {
+ std::cout << "ErrorInfo ResultCode: " << std::hex << resp.returnval << "\n";
+
+ _vbox__IVirtualBoxErrorInfo_USCOREgetText req2;
+ req2._USCOREthis = argv[ap + 1];
+ _vbox__IVirtualBoxErrorInfo_USCOREgetTextResponse resp2;
+ if (!(soaprc = soap_call___vbox__IVirtualBoxErrorInfo_USCOREgetText(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req2,
+ &resp2)))
+ {
+ std::cout << "ErrorInfo Text: " << resp2.returnval << "\n";
+
+ _vbox__IVirtualBoxErrorInfo_USCOREgetNext req3;
+ req3._USCOREthis = argv[ap + 1];
+ _vbox__IVirtualBoxErrorInfo_USCOREgetNextResponse resp3;
+ if (!(soaprc = soap_call___vbox__IVirtualBoxErrorInfo_USCOREgetNext(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req3,
+ &resp3)))
+ std::cout << "Next ErrorInfo: " << resp3.returnval << "\n";
+ }
+ }
+ }
+ }
+ else if (!strcmp(pcszMode, "release"))
+ {
+ if (argc < 2 + ap)
+ std::cout << "Not enough arguments for \"" << pcszMode << "\" mode.\n";
+ else
+ {
+ _vbox__IManagedObjectRef_USCORErelease req;
+ req._USCOREthis = argv[ap + 1];
+ _vbox__IManagedObjectRef_USCOREreleaseResponse resp;
+ if (!(soaprc = soap_call___vbox__IManagedObjectRef_USCORErelease(&soap,
+ pcszArgEndpoint,
+ NULL,
+ &req,
+ &resp)))
+ std::cout << "Managed object reference " << req._USCOREthis << " released.\n";
+ }
+ }
+ else
+ std::cout << "Unknown mode parameter \"" << pcszMode << "\".\n";
+
+ if (soaprc)
+ {
+ if ( (soap.fault)
+ && (soap.fault->detail)
+ )
+ {
+ // generic fault message whether the fault is known or not
+ std::cerr << "Generic fault message:\n";
+ soap_print_fault(&soap, stderr); // display the SOAP fault message on the stderr stream
+
+ if (soap.fault->detail->vbox__InvalidObjectFault)
+ {
+ std::cerr << "Bad object ID: " << soap.fault->detail->vbox__InvalidObjectFault->badObjectID << "\n";
+ }
+ else if (soap.fault->detail->vbox__RuntimeFault)
+ {
+ std::cerr << "Result code: 0x" << std::hex << soap.fault->detail->vbox__RuntimeFault->resultCode << "\n";
+ std::cerr << "ErrorInfo: " << soap.fault->detail->vbox__RuntimeFault->returnval << "\n";
+ }
+ }
+ else
+ {
+ std::cerr << "Invalid fault data, fault message:\n";
+ soap_print_fault(&soap, stderr); // display the SOAP fault message on the stderr stream
+ }
+ }
+
+ soap_destroy(&soap); // delete deserialized class instances (for C++ only)
+ soap_end(&soap); // remove deserialized data and clean up
+ soap_done(&soap); // detach the gSOAP environment
+
+ return soaprc;
+}
+
diff --git a/src/VBox/Main/xml/Makefile.kup b/src/VBox/Main/xml/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/xml/Makefile.kup
diff --git a/src/VBox/Main/xml/SchemaDefs.xsl b/src/VBox/Main/xml/SchemaDefs.xsl
new file mode 100644
index 00000000..abf80a20
--- /dev/null
+++ b/src/VBox/Main/xml/SchemaDefs.xsl
@@ -0,0 +1,246 @@
+<?xml version="1.0"?>
+
+<!--
+ * A template to generate a header that will contain some important constraints
+ * extracted from the VirtualBox XML Schema (VirtualBox-settings-*.xsd).
+ * The output file name must be SchemaDefs.h.
+ *
+ * This template depends on XML Schema structure (type names and constraints)
+ * and should be reviewed on every Schema change.
+-->
+<!--
+ Copyright (C) 2006-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+>
+<xsl:output method="text"/>
+
+<xsl:strip-space elements="*"/>
+
+<xsl:param name="mode" expr=''/>
+
+<!--
+// helpers
+////////////////////////////////////////////////////////////////////////////////
+-->
+
+<!--
+ * Extract the specified value and assign it to an enum member with the given
+ * name
+-->
+<xsl:template name="defineEnumMember">
+ <xsl:param name="member"/>
+ <xsl:param name="select"/>
+ <xsl:if test="$select">
+ <xsl:value-of select="concat(' ', $member, ' = ', $select, ',&#x0A;')"/>
+ </xsl:if>
+</xsl:template>
+
+<!--
+// templates
+////////////////////////////////////////////////////////////////////////////////
+-->
+
+<!--
+ * shut down all implicit templates
+-->
+<xsl:template match="*"/>
+<xsl:template match="*" mode="declare"/>
+<xsl:template match="*" mode="declare.enum"/>
+<xsl:template match="*" mode="define"/>
+
+<xsl:template match="/">
+ <xsl:choose>
+ <xsl:when test="$mode='declare'">
+ <xsl:apply-templates select="/" mode="declare"/>
+ </xsl:when>
+ <xsl:when test="$mode='define'">
+ <xsl:apply-templates select="/" mode="define"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+Value '<xsl:value-of select="$mode"/>' of parameter 'mode' is invalid!
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!--
+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ * declare mode (C++ header file)
+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+-->
+
+<xsl:template match="/" mode="declare">
+<xsl:text>
+/*
+ * DO NOT EDIT.
+ *
+ * This header is automatically generated from the VirtualBox XML Settings
+ * Schema and contains selected schema constraints declared in C++.
+ */
+
+#ifndef ____H_SCHEMADEFS
+#define ____H_SCHEMADEFS
+
+namespace SchemaDefs
+{
+ enum
+ {
+</xsl:text>
+
+ <xsl:apply-templates select="xsd:schema" mode="declare.enum"/>
+
+<xsl:text> DummyTerminator
+ };
+</xsl:text>
+
+<xsl:apply-templates select="xsd:schema" mode="declare"/>
+
+<xsl:text>}
+
+#endif // !____H_SCHEMADEFS
+</xsl:text>
+</xsl:template>
+
+<!--
+ * enumeration values
+-->
+<xsl:template match="xsd:schema" mode="declare.enum">
+
+ <!-- process include statements -->
+ <xsl:for-each select="xsd:include">
+ <xsl:apply-templates select="document(@schemaLocation)/xsd:schema" mode="declare.enum"/>
+ </xsl:for-each>
+
+ <xsl:call-template name="defineEnumMember">
+ <xsl:with-param name="member" select="'MinGuestRAM'"/>
+ <xsl:with-param name="select" select="
+ xsd:complexType[@name='TMemory']/xsd:attribute[@name='RAMSize']//xsd:minInclusive/@value
+ "/>
+ </xsl:call-template>
+ <xsl:call-template name="defineEnumMember">
+ <xsl:with-param name="member" select="'MaxGuestRAM'"/>
+ <xsl:with-param name="select" select="
+ xsd:complexType[@name='TMemory']/xsd:attribute[@name='RAMSize']//xsd:maxInclusive/@value
+ "/>
+ </xsl:call-template>
+
+ <xsl:call-template name="defineEnumMember">
+ <xsl:with-param name="member" select="'MinGuestVRAM'"/>
+ <xsl:with-param name="select" select="
+ xsd:complexType[@name='TDisplay']/xsd:attribute[@name='VRAMSize']//xsd:minInclusive/@value
+ "/>
+ </xsl:call-template>
+ <xsl:call-template name="defineEnumMember">
+ <xsl:with-param name="member" select="'MaxGuestVRAM'"/>
+ <xsl:with-param name="select" select="
+ xsd:complexType[@name='TDisplay']/xsd:attribute[@name='VRAMSize']//xsd:maxInclusive/@value
+ "/>
+ </xsl:call-template>
+
+ <xsl:call-template name="defineEnumMember">
+ <xsl:with-param name="member" select="'MinCPUCount'"/>
+ <xsl:with-param name="select" select="
+ xsd:simpleType[@name='TCPUCount']//xsd:minInclusive/@value
+ "/>
+ </xsl:call-template>
+ <xsl:call-template name="defineEnumMember">
+ <xsl:with-param name="member" select="'MaxCPUCount'"/>
+ <xsl:with-param name="select" select="
+ xsd:simpleType[@name='TCPUCount']//xsd:maxInclusive/@value
+ "/>
+ </xsl:call-template>
+
+ <xsl:call-template name="defineEnumMember">
+ <xsl:with-param name="member" select="'MaxGuestMonitors'"/>
+ <xsl:with-param name="select" select="
+ xsd:simpleType[@name='TMonitorCount']//xsd:maxInclusive/@value
+ "/>
+ </xsl:call-template>
+
+ <xsl:call-template name="defineEnumMember">
+ <xsl:with-param name="member" select="'SerialPortCount'"/>
+ <xsl:with-param name="select" select="
+ xsd:complexType[@name='TUARTPort']/xsd:attribute[@name='slot']//xsd:maxExclusive/@value
+ "/>
+ </xsl:call-template>
+
+ <xsl:call-template name="defineEnumMember">
+ <xsl:with-param name="member" select="'ParallelPortCount'"/>
+ <xsl:with-param name="select" select="
+ xsd:complexType[@name='TLPTPort']/xsd:attribute[@name='slot']//xsd:maxExclusive/@value
+ "/>
+ </xsl:call-template>
+
+ <xsl:call-template name="defineEnumMember">
+ <xsl:with-param name="member" select="'MaxBootPosition'"/>
+ <xsl:with-param name="select" select="
+ xsd:complexType[@name='TBoot']//xsd:element[@name='Order']//xsd:attribute[@name='position']//xsd:maxInclusive/@value
+ "/>
+ </xsl:call-template>
+
+ <xsl:call-template name="defineEnumMember">
+ <xsl:with-param name="member" select="'DefaultHardwareVersion'"/>
+ <xsl:with-param name="select" select="
+ xsd:complexType[@name='THardware']/xsd:attribute[@name='version']/@default
+ "/>
+ </xsl:call-template>
+
+</xsl:template>
+
+<!--
+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ * define mode (C++ source file)
+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+-->
+
+<xsl:template match="/" mode="define">
+<xsl:text>
+/*
+ * DO NOT EDIT.
+ *
+ * This source is automatically generated from the VirtualBox XML Settings
+ * Schema and contains selected schema constraints defined in C++.
+ */
+
+#include "SchemaDefs.h"
+
+namespace SchemaDefs
+{
+</xsl:text>
+
+<xsl:apply-templates select="xsd:schema" mode="define"/>
+
+<xsl:text>}
+</xsl:text>
+</xsl:template>
+
+<!--
+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ * END
+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+-->
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/xml/Settings.cpp b/src/VBox/Main/xml/Settings.cpp
new file mode 100644
index 00000000..046ae0d7
--- /dev/null
+++ b/src/VBox/Main/xml/Settings.cpp
@@ -0,0 +1,9719 @@
+/* $Id: Settings.cpp $ */
+/** @file
+ * Settings File Manipulation API.
+ *
+ * Two classes, MainConfigFile and MachineConfigFile, represent the VirtualBox.xml and
+ * machine XML files. They share a common ancestor class, ConfigFileBase, which shares
+ * functionality such as talking to the XML back-end classes and settings version management.
+ *
+ * The code can read all VirtualBox settings files version 1.3 and higher. That version was
+ * written by VirtualBox 2.0. It can write settings version 1.7 (used by VirtualBox 2.2 and
+ * 3.0) and 1.9 (used by VirtualBox 3.1) and newer ones obviously.
+ *
+ * The settings versions enum is defined in src/VBox/Main/idl/VirtualBox.xidl. To introduce
+ * a new settings version (should be necessary at most once per VirtualBox major release,
+ * if at all), add a new SettingsVersion value to that enum and grep for the previously
+ * highest value to see which code in here needs adjusting.
+ *
+ * Certainly ConfigFileBase::ConfigFileBase() will. Change VBOX_XML_VERSION below as well.
+ * VBOX_XML_VERSION does not have to be changed if the settings for a default VM do not
+ * touch newly introduced attributes or tags. It has the benefit that older VirtualBox
+ * versions do not trigger their "newer" code path.
+ *
+ * Once a new settings version has been added, these are the rules for introducing a new
+ * setting: If an XML element or attribute or value is introduced that was not present in
+ * previous versions, then settings version checks need to be introduced. See the
+ * SettingsVersion enumeration in src/VBox/Main/idl/VirtualBox.xidl for details about which
+ * version was used when.
+ *
+ * The settings versions checks are necessary because since version 3.1, VirtualBox no longer
+ * automatically converts XML settings files but only if necessary, that is, if settings are
+ * present that the old format does not support. If we write an element or attribute to a
+ * settings file of an older version, then an old VirtualBox (before 3.1) will attempt to
+ * validate it with XML schema, and that will certainly fail.
+ *
+ * So, to introduce a new setting:
+ *
+ * 1) Make sure the constructor of corresponding settings structure has a proper default.
+ *
+ * 2) In the settings reader method, try to read the setting; if it's there, great, if not,
+ * the default value will have been set by the constructor. The rule is to be tolerant
+ * here.
+ *
+ * 3) In MachineConfigFile::bumpSettingsVersionIfNeeded(), check if the new setting has
+ * a non-default value (i.e. that differs from the constructor). If so, bump the
+ * settings version to the current version so the settings writer (4) can write out
+ * the non-default value properly.
+ *
+ * So far a corresponding method for MainConfigFile has not been necessary since there
+ * have been no incompatible changes yet.
+ *
+ * 4) In the settings writer method, write the setting _only_ if the current settings
+ * version (stored in m->sv) is high enough. That is, for VirtualBox 4.0, write it
+ * only if (m->sv >= SettingsVersion_v1_11).
+ *
+ * 5) You _must_ update xml/VirtualBox-settings.xsd to contain the new tags and attributes.
+ * Check that settings file from before and after your change are validating properly.
+ * Use "kmk testvalidsettings", it should not find any files which don't validate.
+ */
+
+/*
+ * Copyright (C) 2007-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN
+#include "VBox/com/string.h"
+#include "VBox/settings.h"
+#include <iprt/base64.h>
+#include <iprt/cpp/lock.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/cpp/xml.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/ldr.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#ifdef RT_OS_WINDOWS
+# include <iprt/system.h> /* For RTSystemGetNtVersion() / RTSYSTEM_MAKE_NT_VERSION. */
+#endif
+#include <iprt/uri.h>
+
+// Guest Properties validation.
+#include "VBox/HostServices/GuestPropertySvc.h"
+
+// generated header
+#include "SchemaDefs.h"
+
+#include "HashedPw.h"
+#include "LoggingNew.h"
+
+using namespace com;
+using namespace settings;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Defines
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/** VirtualBox XML settings namespace */
+#define VBOX_XML_NAMESPACE "http://www.virtualbox.org/"
+
+/** VirtualBox XML schema location (relative URI) */
+#define VBOX_XML_SCHEMA "VirtualBox-settings.xsd"
+
+/** VirtualBox XML settings version number substring ("x.y") */
+#define VBOX_XML_VERSION "1.12"
+
+/** VirtualBox OVF settings import default version number substring ("x.y").
+ *
+ * Think twice before changing this, as all VirtualBox versions before 5.1
+ * wrote the settings version when exporting, but totally ignored it on
+ * importing (while it should have been a mandatory attribute), so 3rd party
+ * software out there creates OVF files with the VirtualBox specific settings
+ * but lacking the version attribute. This shouldn't happen any more, but
+ * breaking existing OVF files isn't nice. */
+#define VBOX_XML_IMPORT_VERSION "1.15"
+
+/** VirtualBox XML settings version platform substring */
+#if defined (RT_OS_DARWIN)
+# define VBOX_XML_PLATFORM "macosx"
+#elif defined (RT_OS_FREEBSD)
+# define VBOX_XML_PLATFORM "freebsd"
+#elif defined (RT_OS_LINUX)
+# define VBOX_XML_PLATFORM "linux"
+#elif defined (RT_OS_NETBSD)
+# define VBOX_XML_PLATFORM "netbsd"
+#elif defined (RT_OS_OPENBSD)
+# define VBOX_XML_PLATFORM "openbsd"
+#elif defined (RT_OS_OS2)
+# define VBOX_XML_PLATFORM "os2"
+#elif defined (RT_OS_SOLARIS)
+# define VBOX_XML_PLATFORM "solaris"
+#elif defined (RT_OS_WINDOWS)
+# define VBOX_XML_PLATFORM "windows"
+#else
+# error Unsupported platform!
+#endif
+
+/** VirtualBox XML settings full version string ("x.y-platform") */
+#define VBOX_XML_VERSION_FULL VBOX_XML_VERSION "-" VBOX_XML_PLATFORM
+
+/** VirtualBox OVF import default settings full version string ("x.y-platform") */
+#define VBOX_XML_IMPORT_VERSION_FULL VBOX_XML_IMPORT_VERSION "-" VBOX_XML_PLATFORM
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Internal data
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Opaque data structore for ConfigFileBase (only declared
+ * in header, defined only here).
+ */
+
+struct ConfigFileBase::Data
+{
+ Data()
+ : pDoc(NULL),
+ pelmRoot(NULL),
+ sv(SettingsVersion_Null),
+ svRead(SettingsVersion_Null)
+ {}
+
+ ~Data()
+ {
+ cleanup();
+ }
+
+ RTCString strFilename;
+ bool fFileExists;
+
+ xml::Document *pDoc;
+ xml::ElementNode *pelmRoot;
+
+ com::Utf8Str strSettingsVersionFull; // e.g. "1.7-linux"
+ SettingsVersion_T sv; // e.g. SettingsVersion_v1_7
+
+ SettingsVersion_T svRead; // settings version that the original file had when it was read,
+ // or SettingsVersion_Null if none
+
+ void copyFrom(const Data &d)
+ {
+ strFilename = d.strFilename;
+ fFileExists = d.fFileExists;
+ strSettingsVersionFull = d.strSettingsVersionFull;
+ sv = d.sv;
+ svRead = d.svRead;
+ }
+
+ void cleanup()
+ {
+ if (pDoc)
+ {
+ delete pDoc;
+ pDoc = NULL;
+ pelmRoot = NULL;
+ }
+ }
+};
+
+/**
+ * Private exception class (not in the header file) that makes
+ * throwing xml::LogicError instances easier. That class is public
+ * and should be caught by client code.
+ */
+class settings::ConfigFileError : public xml::LogicError
+{
+public:
+ ConfigFileError(const ConfigFileBase *file,
+ const xml::Node *pNode,
+ const char *pcszFormat, ...)
+ : xml::LogicError()
+ {
+ va_list args;
+ va_start(args, pcszFormat);
+ Utf8Str strWhat(pcszFormat, args);
+ va_end(args);
+
+ Utf8Str strLine;
+ if (pNode)
+ strLine = Utf8StrFmt(" (line %RU32)", pNode->getLineNumber());
+
+ const char *pcsz = strLine.c_str();
+ Utf8StrFmt str(N_("Error in %s%s -- %s"),
+ file->m->strFilename.c_str(),
+ (pcsz) ? pcsz : "",
+ strWhat.c_str());
+
+ setWhat(str.c_str());
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ConfigFileBase
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Constructor. Allocates the XML internals, parses the XML file if
+ * pstrFilename is != NULL and reads the settings version from it.
+ * @param pstrFilename
+ */
+ConfigFileBase::ConfigFileBase(const com::Utf8Str *pstrFilename)
+ : m(new Data)
+{
+ m->fFileExists = false;
+
+ if (pstrFilename)
+ {
+ try
+ {
+ // reading existing settings file:
+ m->strFilename = *pstrFilename;
+
+ xml::XmlFileParser parser;
+ m->pDoc = new xml::Document;
+ parser.read(*pstrFilename,
+ *m->pDoc);
+
+ m->fFileExists = true;
+
+ m->pelmRoot = m->pDoc->getRootElement();
+ if (!m->pelmRoot || !m->pelmRoot->nameEquals("VirtualBox"))
+ throw ConfigFileError(this, m->pelmRoot, N_("Root element in VirtualBox settings files must be \"VirtualBox\""));
+
+ if (!(m->pelmRoot->getAttributeValue("version", m->strSettingsVersionFull)))
+ throw ConfigFileError(this, m->pelmRoot, N_("Required VirtualBox/@version attribute is missing"));
+
+ LogRel(("Loading settings file \"%s\" with version \"%s\"\n", m->strFilename.c_str(), m->strSettingsVersionFull.c_str()));
+
+ m->sv = parseVersion(m->strSettingsVersionFull, m->pelmRoot);
+
+ // remember the settings version we read in case it gets upgraded later,
+ // so we know when to make backups
+ m->svRead = m->sv;
+ }
+ catch(...)
+ {
+ /*
+ * The destructor is not called when an exception is thrown in the constructor,
+ * so we have to do the cleanup here.
+ */
+ delete m;
+ m = NULL;
+ throw;
+ }
+ }
+ else
+ {
+ // creating new settings file:
+ m->strSettingsVersionFull = VBOX_XML_VERSION_FULL;
+ m->sv = SettingsVersion_v1_12;
+ }
+}
+
+ConfigFileBase::ConfigFileBase(const ConfigFileBase &other)
+ : m(new Data)
+{
+ copyBaseFrom(other);
+ m->strFilename = "";
+ m->fFileExists = false;
+}
+
+/**
+ * Clean up.
+ */
+ConfigFileBase::~ConfigFileBase()
+{
+ if (m)
+ {
+ delete m;
+ m = NULL;
+ }
+}
+
+/**
+ * Helper function to convert a MediaType enum value into string from.
+ * @param t
+ */
+/*static*/
+const char *ConfigFileBase::stringifyMediaType(MediaType t)
+{
+ switch (t)
+ {
+ case HardDisk:
+ return "hard disk";
+ case DVDImage:
+ return "DVD";
+ case FloppyImage:
+ return "floppy";
+ default:
+ AssertMsgFailed(("media type %d\n", t));
+ return "UNKNOWN";
+ }
+}
+
+/**
+ * Helper function that parses a full version number.
+ *
+ * Allow future versions but fail if file is older than 1.6. Throws on errors.
+ * @returns settings version
+ * @param strVersion
+ * @param pElm
+ */
+SettingsVersion_T ConfigFileBase::parseVersion(const Utf8Str &strVersion, const xml::ElementNode *pElm)
+{
+ SettingsVersion_T sv = SettingsVersion_Null;
+ if (strVersion.length() > 3)
+ {
+ const char *pcsz = strVersion.c_str();
+
+ uint32_t uMajor = 0;
+ char ch;
+ while ( (ch = *pcsz)
+ && RT_C_IS_DIGIT(ch) )
+ {
+ uMajor *= 10;
+ uMajor += (uint32_t)(ch - '0');
+ ++pcsz;
+ }
+
+ uint32_t uMinor = 0;
+ if (ch == '.')
+ {
+ pcsz++;
+ while ( (ch = *pcsz)
+ && RT_C_IS_DIGIT(ch))
+ {
+ uMinor *= 10;
+ uMinor += (ULONG)(ch - '0');
+ ++pcsz;
+ }
+ }
+
+ if (uMajor == 1)
+ {
+ if (uMinor == 3)
+ sv = SettingsVersion_v1_3;
+ else if (uMinor == 4)
+ sv = SettingsVersion_v1_4;
+ else if (uMinor == 5)
+ sv = SettingsVersion_v1_5;
+ else if (uMinor == 6)
+ sv = SettingsVersion_v1_6;
+ else if (uMinor == 7)
+ sv = SettingsVersion_v1_7;
+ else if (uMinor == 8)
+ sv = SettingsVersion_v1_8;
+ else if (uMinor == 9)
+ sv = SettingsVersion_v1_9;
+ else if (uMinor == 10)
+ sv = SettingsVersion_v1_10;
+ else if (uMinor == 11)
+ sv = SettingsVersion_v1_11;
+ else if (uMinor == 12)
+ sv = SettingsVersion_v1_12;
+ else if (uMinor == 13)
+ sv = SettingsVersion_v1_13;
+ else if (uMinor == 14)
+ sv = SettingsVersion_v1_14;
+ else if (uMinor == 15)
+ sv = SettingsVersion_v1_15;
+ else if (uMinor == 16)
+ sv = SettingsVersion_v1_16;
+ else if (uMinor == 17)
+ sv = SettingsVersion_v1_17;
+ else if (uMinor == 18)
+ sv = SettingsVersion_v1_18;
+ else if (uMinor == 19)
+ sv = SettingsVersion_v1_19;
+ else if (uMinor > 19)
+ sv = SettingsVersion_Future;
+ }
+ else if (uMajor > 1)
+ sv = SettingsVersion_Future;
+
+ Log(("Parsed settings version %d.%d to enum value %d\n", uMajor, uMinor, sv));
+ }
+
+ if (sv == SettingsVersion_Null)
+ throw ConfigFileError(this, pElm, N_("Cannot handle settings version '%s'"), strVersion.c_str());
+
+ return sv;
+}
+
+/**
+ * Helper function that parses a UUID in string form into
+ * a com::Guid item. Accepts UUIDs both with and without
+ * "{}" brackets. Throws on errors.
+ * @param guid
+ * @param strUUID
+ * @param pElm
+ */
+void ConfigFileBase::parseUUID(Guid &guid,
+ const Utf8Str &strUUID,
+ const xml::ElementNode *pElm) const
+{
+ guid = strUUID.c_str();
+ if (guid.isZero())
+ throw ConfigFileError(this, pElm, N_("UUID \"%s\" has zero format"), strUUID.c_str());
+ else if (!guid.isValid())
+ throw ConfigFileError(this, pElm, N_("UUID \"%s\" has invalid format"), strUUID.c_str());
+}
+
+/**
+ * Parses the given string in str and attempts to treat it as an ISO
+ * date/time stamp to put into timestamp. Throws on errors.
+ * @param timestamp
+ * @param str
+ * @param pElm
+ */
+void ConfigFileBase::parseTimestamp(RTTIMESPEC &timestamp,
+ const com::Utf8Str &str,
+ const xml::ElementNode *pElm) const
+{
+ const char *pcsz = str.c_str();
+ // yyyy-mm-ddThh:mm:ss
+ // "2009-07-10T11:54:03Z"
+ // 01234567890123456789
+ // 1
+ if (str.length() > 19)
+ {
+ // timezone must either be unspecified or 'Z' for UTC
+ if ( (pcsz[19])
+ && (pcsz[19] != 'Z')
+ )
+ throw ConfigFileError(this, pElm, N_("Cannot handle ISO timestamp '%s': is not UTC date"), str.c_str());
+
+ int32_t yyyy;
+ uint32_t mm, dd, hh, min, secs;
+ if ( (pcsz[4] == '-')
+ && (pcsz[7] == '-')
+ && (pcsz[10] == 'T')
+ && (pcsz[13] == ':')
+ && (pcsz[16] == ':')
+ )
+ {
+ int rc;
+ if ( (RT_SUCCESS(rc = RTStrToInt32Ex(pcsz, NULL, 0, &yyyy)))
+ // could theoretically be negative but let's assume that nobody
+ // created virtual machines before the Christian era
+ && (RT_SUCCESS(rc = RTStrToUInt32Ex(pcsz + 5, NULL, 0, &mm)))
+ && (RT_SUCCESS(rc = RTStrToUInt32Ex(pcsz + 8, NULL, 0, &dd)))
+ && (RT_SUCCESS(rc = RTStrToUInt32Ex(pcsz + 11, NULL, 0, &hh)))
+ && (RT_SUCCESS(rc = RTStrToUInt32Ex(pcsz + 14, NULL, 0, &min)))
+ && (RT_SUCCESS(rc = RTStrToUInt32Ex(pcsz + 17, NULL, 0, &secs)))
+ )
+ {
+ RTTIME time =
+ {
+ yyyy,
+ (uint8_t)mm,
+ 0,
+ 0,
+ (uint8_t)dd,
+ (uint8_t)hh,
+ (uint8_t)min,
+ (uint8_t)secs,
+ 0,
+ RTTIME_FLAGS_TYPE_UTC,
+ 0
+ };
+ if (RTTimeNormalize(&time))
+ if (RTTimeImplode(&timestamp, &time))
+ return;
+ }
+
+ throw ConfigFileError(this, pElm, N_("Cannot parse ISO timestamp '%s': runtime error, %Rra"), str.c_str(), rc);
+ }
+
+ throw ConfigFileError(this, pElm, N_("Cannot parse ISO timestamp '%s': invalid format"), str.c_str());
+ }
+}
+
+/**
+ * Helper function that parses a Base64 formatted string into a binary blob.
+ * @param binary
+ * @param str
+ * @param pElm
+ */
+void ConfigFileBase::parseBase64(IconBlob &binary,
+ const Utf8Str &str,
+ const xml::ElementNode *pElm) const
+{
+#define DECODE_STR_MAX _1M
+ const char* psz = str.c_str();
+ ssize_t cbOut = RTBase64DecodedSize(psz, NULL);
+ if (cbOut > DECODE_STR_MAX)
+ throw ConfigFileError(this, pElm, N_("Base64 encoded data too long (%d > %d)"), cbOut, DECODE_STR_MAX);
+ else if (cbOut < 0)
+ throw ConfigFileError(this, pElm, N_("Base64 encoded data '%s' invalid"), psz);
+ binary.resize((size_t)cbOut);
+ int vrc = VINF_SUCCESS;
+ if (cbOut)
+ vrc = RTBase64Decode(psz, &binary.front(), (size_t)cbOut, NULL, NULL);
+ if (RT_FAILURE(vrc))
+ {
+ binary.resize(0);
+ throw ConfigFileError(this, pElm, N_("Base64 encoded data could not be decoded (%Rrc)"), vrc);
+ }
+}
+
+/**
+ * Helper to create a string for a RTTIMESPEC for writing out ISO timestamps.
+ * @param stamp
+ * @return
+ */
+com::Utf8Str ConfigFileBase::stringifyTimestamp(const RTTIMESPEC &stamp) const
+{
+ RTTIME time;
+ if (!RTTimeExplode(&time, &stamp))
+ throw ConfigFileError(this, NULL, N_("Timespec %lld ms is invalid"), RTTimeSpecGetMilli(&stamp));
+
+ return Utf8StrFmt("%04u-%02u-%02uT%02u:%02u:%02uZ",
+ time.i32Year, time.u8Month, time.u8MonthDay,
+ time.u8Hour, time.u8Minute, time.u8Second);
+}
+
+/**
+ * Helper to create a base64 encoded string out of a binary blob.
+ * @param str
+ * @param binary
+ * @throws std::bad_alloc and ConfigFileError
+ */
+void ConfigFileBase::toBase64(com::Utf8Str &str, const IconBlob &binary) const
+{
+ size_t cb = binary.size();
+ if (cb > 0)
+ {
+ size_t cchOut = RTBase64EncodedLength(cb);
+ str.reserve(cchOut + 1);
+ int vrc = RTBase64Encode(&binary.front(), cb, str.mutableRaw(), str.capacity(), NULL);
+ if (RT_FAILURE(vrc))
+ throw ConfigFileError(this, NULL, N_("Failed to convert binary data to base64 format (%Rrc)"), vrc);
+ str.jolt();
+ }
+}
+
+/**
+ * Helper method to read in an ExtraData subtree and stores its contents
+ * in the given map of extradata items. Used for both main and machine
+ * extradata (MainConfigFile and MachineConfigFile).
+ * @param elmExtraData
+ * @param map
+ */
+void ConfigFileBase::readExtraData(const xml::ElementNode &elmExtraData,
+ StringsMap &map)
+{
+ xml::NodesLoop nlLevel4(elmExtraData);
+ const xml::ElementNode *pelmExtraDataItem;
+ while ((pelmExtraDataItem = nlLevel4.forAllNodes()))
+ {
+ if (pelmExtraDataItem->nameEquals("ExtraDataItem"))
+ {
+ // <ExtraDataItem name="GUI/LastWindowPostion" value="97,88,981,858"/>
+ Utf8Str strName, strValue;
+ if ( pelmExtraDataItem->getAttributeValue("name", strName)
+ && pelmExtraDataItem->getAttributeValue("value", strValue) )
+ map[strName] = strValue;
+ else
+ throw ConfigFileError(this, pelmExtraDataItem, N_("Required ExtraDataItem/@name or @value attribute is missing"));
+ }
+ }
+}
+
+/**
+ * Reads \<USBDeviceFilter\> entries from under the given elmDeviceFilters node and
+ * stores them in the given linklist. This is in ConfigFileBase because it's used
+ * from both MainConfigFile (for host filters) and MachineConfigFile (for machine
+ * filters).
+ * @param elmDeviceFilters
+ * @param ll
+ */
+void ConfigFileBase::readUSBDeviceFilters(const xml::ElementNode &elmDeviceFilters,
+ USBDeviceFiltersList &ll)
+{
+ xml::NodesLoop nl1(elmDeviceFilters, "DeviceFilter");
+ const xml::ElementNode *pelmLevel4Child;
+ while ((pelmLevel4Child = nl1.forAllNodes()))
+ {
+ USBDeviceFilter flt;
+ flt.action = USBDeviceFilterAction_Ignore;
+ Utf8Str strAction;
+ if ( pelmLevel4Child->getAttributeValue("name", flt.strName)
+ && pelmLevel4Child->getAttributeValue("active", flt.fActive))
+ {
+ if (!pelmLevel4Child->getAttributeValue("vendorId", flt.strVendorId))
+ pelmLevel4Child->getAttributeValue("vendorid", flt.strVendorId); // used before 1.3
+ if (!pelmLevel4Child->getAttributeValue("productId", flt.strProductId))
+ pelmLevel4Child->getAttributeValue("productid", flt.strProductId); // used before 1.3
+ pelmLevel4Child->getAttributeValue("revision", flt.strRevision);
+ pelmLevel4Child->getAttributeValue("manufacturer", flt.strManufacturer);
+ pelmLevel4Child->getAttributeValue("product", flt.strProduct);
+ if (!pelmLevel4Child->getAttributeValue("serialNumber", flt.strSerialNumber))
+ pelmLevel4Child->getAttributeValue("serialnumber", flt.strSerialNumber); // used before 1.3
+ pelmLevel4Child->getAttributeValue("port", flt.strPort);
+
+ // the next 2 are irrelevant for host USB objects
+ pelmLevel4Child->getAttributeValue("remote", flt.strRemote);
+ pelmLevel4Child->getAttributeValue("maskedInterfaces", flt.ulMaskedInterfaces);
+
+ // action is only used with host USB objects
+ if (pelmLevel4Child->getAttributeValue("action", strAction))
+ {
+ if (strAction == "Ignore")
+ flt.action = USBDeviceFilterAction_Ignore;
+ else if (strAction == "Hold")
+ flt.action = USBDeviceFilterAction_Hold;
+ else
+ throw ConfigFileError(this, pelmLevel4Child, N_("Invalid value '%s' in DeviceFilter/@action attribute"), strAction.c_str());
+ }
+
+ ll.push_back(flt);
+ }
+ }
+}
+
+/**
+ * Reads a media registry entry from the main VirtualBox.xml file.
+ *
+ * Whereas the current media registry code is fairly straightforward, it was quite a mess
+ * with settings format before 1.4 (VirtualBox 2.0 used settings format 1.3). The elements
+ * in the media registry were much more inconsistent, and different elements were used
+ * depending on the type of device and image.
+ *
+ * @param t
+ * @param elmMedium
+ * @param med
+ */
+void ConfigFileBase::readMediumOne(MediaType t,
+ const xml::ElementNode &elmMedium,
+ Medium &med)
+{
+ // <HardDisk uuid="{5471ecdb-1ddb-4012-a801-6d98e226868b}" location="/mnt/innotek-unix/vdis/Windows XP.vdi" format="VDI" type="Normal">
+
+ Utf8Str strUUID;
+ if (!elmMedium.getAttributeValue("uuid", strUUID))
+ throw ConfigFileError(this, &elmMedium, N_("Required %s/@uuid attribute is missing"), elmMedium.getName());
+
+ parseUUID(med.uuid, strUUID, &elmMedium);
+
+ bool fNeedsLocation = true;
+
+ if (t == HardDisk)
+ {
+ if (m->sv < SettingsVersion_v1_4)
+ {
+ // here the system is:
+ // <HardDisk uuid="{....}" type="normal">
+ // <VirtualDiskImage filePath="/path/to/xxx.vdi"/>
+ // </HardDisk>
+
+ fNeedsLocation = false;
+ bool fNeedsFilePath = true;
+ const xml::ElementNode *pelmImage;
+ if ((pelmImage = elmMedium.findChildElement("VirtualDiskImage")))
+ med.strFormat = "VDI";
+ else if ((pelmImage = elmMedium.findChildElement("VMDKImage")))
+ med.strFormat = "VMDK";
+ else if ((pelmImage = elmMedium.findChildElement("VHDImage")))
+ med.strFormat = "VHD";
+ else if ((pelmImage = elmMedium.findChildElement("ISCSIHardDisk")))
+ {
+ med.strFormat = "iSCSI";
+
+ fNeedsFilePath = false;
+ // location is special here: current settings specify an "iscsi://user@server:port/target/lun"
+ // string for the location and also have several disk properties for these, whereas this used
+ // to be hidden in several sub-elements before 1.4, so compose a location string and set up
+ // the properties:
+ med.strLocation = "iscsi://";
+ Utf8Str strUser, strServer, strPort, strTarget, strLun;
+ if (pelmImage->getAttributeValue("userName", strUser))
+ {
+ med.strLocation.append(strUser);
+ med.strLocation.append("@");
+ }
+ Utf8Str strServerAndPort;
+ if (pelmImage->getAttributeValue("server", strServer))
+ {
+ strServerAndPort = strServer;
+ }
+ if (pelmImage->getAttributeValue("port", strPort))
+ {
+ if (strServerAndPort.length())
+ strServerAndPort.append(":");
+ strServerAndPort.append(strPort);
+ }
+ med.strLocation.append(strServerAndPort);
+ if (pelmImage->getAttributeValue("target", strTarget))
+ {
+ med.strLocation.append("/");
+ med.strLocation.append(strTarget);
+ }
+ if (pelmImage->getAttributeValue("lun", strLun))
+ {
+ med.strLocation.append("/");
+ med.strLocation.append(strLun);
+ }
+
+ if (strServer.length() && strPort.length())
+ med.properties["TargetAddress"] = strServerAndPort;
+ if (strTarget.length())
+ med.properties["TargetName"] = strTarget;
+ if (strUser.length())
+ med.properties["InitiatorUsername"] = strUser;
+ Utf8Str strPassword;
+ if (pelmImage->getAttributeValue("password", strPassword))
+ med.properties["InitiatorSecret"] = strPassword;
+ if (strLun.length())
+ med.properties["LUN"] = strLun;
+ }
+ else if ((pelmImage = elmMedium.findChildElement("CustomHardDisk")))
+ {
+ fNeedsFilePath = false;
+ fNeedsLocation = true;
+ // also requires @format attribute, which will be queried below
+ }
+ else
+ throw ConfigFileError(this, &elmMedium, N_("Required %s/VirtualDiskImage element is missing"), elmMedium.getName());
+
+ if (fNeedsFilePath)
+ {
+ if (!(pelmImage->getAttributeValuePath("filePath", med.strLocation)))
+ throw ConfigFileError(this, &elmMedium, N_("Required %s/@filePath attribute is missing"), elmMedium.getName());
+ }
+ }
+
+ if (med.strFormat.isEmpty()) // not set with 1.4 format above, or 1.4 Custom format?
+ if (!elmMedium.getAttributeValue("format", med.strFormat))
+ throw ConfigFileError(this, &elmMedium, N_("Required %s/@format attribute is missing"), elmMedium.getName());
+
+ if (!elmMedium.getAttributeValue("autoReset", med.fAutoReset))
+ med.fAutoReset = false;
+
+ Utf8Str strType;
+ if (elmMedium.getAttributeValue("type", strType))
+ {
+ // pre-1.4 used lower case, so make this case-insensitive
+ strType.toUpper();
+ if (strType == "NORMAL")
+ med.hdType = MediumType_Normal;
+ else if (strType == "IMMUTABLE")
+ med.hdType = MediumType_Immutable;
+ else if (strType == "WRITETHROUGH")
+ med.hdType = MediumType_Writethrough;
+ else if (strType == "SHAREABLE")
+ med.hdType = MediumType_Shareable;
+ else if (strType == "READONLY")
+ med.hdType = MediumType_Readonly;
+ else if (strType == "MULTIATTACH")
+ med.hdType = MediumType_MultiAttach;
+ else
+ throw ConfigFileError(this, &elmMedium, N_("HardDisk/@type attribute must be one of Normal, Immutable, Writethrough, Shareable, Readonly or MultiAttach"));
+ }
+ }
+ else
+ {
+ if (m->sv < SettingsVersion_v1_4)
+ {
+ // DVD and floppy images before 1.4 had "src" attribute instead of "location"
+ if (!elmMedium.getAttributeValue("src", med.strLocation))
+ throw ConfigFileError(this, &elmMedium, N_("Required %s/@src attribute is missing"), elmMedium.getName());
+
+ fNeedsLocation = false;
+ }
+
+ if (!elmMedium.getAttributeValue("format", med.strFormat))
+ {
+ // DVD and floppy images before 1.11 had no format attribute. assign the default.
+ med.strFormat = "RAW";
+ }
+
+ if (t == DVDImage)
+ med.hdType = MediumType_Readonly;
+ else if (t == FloppyImage)
+ med.hdType = MediumType_Writethrough;
+ }
+
+ if (fNeedsLocation)
+ // current files and 1.4 CustomHardDisk elements must have a location attribute
+ if (!elmMedium.getAttributeValue("location", med.strLocation))
+ throw ConfigFileError(this, &elmMedium, N_("Required %s/@location attribute is missing"), elmMedium.getName());
+
+ // 3.2 builds added Description as an attribute, read it silently
+ // and write it back as an element starting with 5.1.26
+ elmMedium.getAttributeValue("Description", med.strDescription);
+
+ xml::NodesLoop nlMediumChildren(elmMedium);
+ const xml::ElementNode *pelmMediumChild;
+ while ((pelmMediumChild = nlMediumChildren.forAllNodes()))
+ {
+ if (pelmMediumChild->nameEquals("Description"))
+ med.strDescription = pelmMediumChild->getValue();
+ else if (pelmMediumChild->nameEquals("Property"))
+ {
+ // handle medium properties
+ Utf8Str strPropName, strPropValue;
+ if ( pelmMediumChild->getAttributeValue("name", strPropName)
+ && pelmMediumChild->getAttributeValue("value", strPropValue) )
+ med.properties[strPropName] = strPropValue;
+ else
+ throw ConfigFileError(this, pelmMediumChild, N_("Required HardDisk/Property/@name or @value attribute is missing"));
+ }
+ }
+}
+
+/**
+ * Reads a media registry entry from the main VirtualBox.xml file and
+ * likewise for all children where applicable.
+ *
+ * @param t
+ * @param elmMedium
+ * @param med
+ */
+void ConfigFileBase::readMedium(MediaType t,
+ const xml::ElementNode &elmMedium,
+ Medium &med)
+{
+ std::list<const xml::ElementNode *> llElementsTodo;
+ llElementsTodo.push_back(&elmMedium);
+ std::list<Medium *> llSettingsTodo;
+ llSettingsTodo.push_back(&med);
+ std::list<uint32_t> llDepthsTodo;
+ llDepthsTodo.push_back(1);
+
+ while (llElementsTodo.size() > 0)
+ {
+ const xml::ElementNode *pElement = llElementsTodo.front();
+ llElementsTodo.pop_front();
+ Medium *pMed = llSettingsTodo.front();
+ llSettingsTodo.pop_front();
+ uint32_t depth = llDepthsTodo.front();
+ llDepthsTodo.pop_front();
+
+ if (depth > SETTINGS_MEDIUM_DEPTH_MAX)
+ throw ConfigFileError(this, pElement, N_("Maximum medium tree depth of %u exceeded"), SETTINGS_MEDIUM_DEPTH_MAX);
+
+ readMediumOne(t, *pElement, *pMed);
+
+ if (t != HardDisk)
+ return;
+
+ // load all children
+ xml::NodesLoop nl2(*pElement, m->sv >= SettingsVersion_v1_4 ? "HardDisk" : "DiffHardDisk");
+ const xml::ElementNode *pelmHDChild;
+ while ((pelmHDChild = nl2.forAllNodes()))
+ {
+ llElementsTodo.push_back(pelmHDChild);
+ pMed->llChildren.push_back(Medium::Empty);
+ llSettingsTodo.push_back(&pMed->llChildren.back());
+ llDepthsTodo.push_back(depth + 1);
+ }
+ }
+}
+
+/**
+ * Reads in the entire \<MediaRegistry\> chunk and stores its media in the lists
+ * of the given MediaRegistry structure.
+ *
+ * This is used in both MainConfigFile and MachineConfigFile since starting with
+ * VirtualBox 4.0, we can have media registries in both.
+ *
+ * For pre-1.4 files, this gets called with the \<DiskRegistry\> chunk instead.
+ *
+ * @param elmMediaRegistry
+ * @param mr
+ */
+void ConfigFileBase::readMediaRegistry(const xml::ElementNode &elmMediaRegistry,
+ MediaRegistry &mr)
+{
+ xml::NodesLoop nl1(elmMediaRegistry);
+ const xml::ElementNode *pelmChild1;
+ while ((pelmChild1 = nl1.forAllNodes()))
+ {
+ MediaType t = Error;
+ if (pelmChild1->nameEquals("HardDisks"))
+ t = HardDisk;
+ else if (pelmChild1->nameEquals("DVDImages"))
+ t = DVDImage;
+ else if (pelmChild1->nameEquals("FloppyImages"))
+ t = FloppyImage;
+ else
+ continue;
+
+ xml::NodesLoop nl2(*pelmChild1);
+ const xml::ElementNode *pelmMedium;
+ while ((pelmMedium = nl2.forAllNodes()))
+ {
+ if ( t == HardDisk
+ && (pelmMedium->nameEquals("HardDisk")))
+ {
+ mr.llHardDisks.push_back(Medium::Empty);
+ readMedium(t, *pelmMedium, mr.llHardDisks.back());
+ }
+ else if ( t == DVDImage
+ && (pelmMedium->nameEquals("Image")))
+ {
+ mr.llDvdImages.push_back(Medium::Empty);
+ readMedium(t, *pelmMedium, mr.llDvdImages.back());
+ }
+ else if ( t == FloppyImage
+ && (pelmMedium->nameEquals("Image")))
+ {
+ mr.llFloppyImages.push_back(Medium::Empty);
+ readMedium(t, *pelmMedium, mr.llFloppyImages.back());
+ }
+ }
+ }
+}
+
+/**
+ * This is common version for reading NAT port forward rule in per-_machine's_adapter_ and
+ * per-network approaches.
+ * Note: this function doesn't in fill given list from xml::ElementNodesList, because there is conflicting
+ * declaration in ovmfreader.h.
+ */
+void ConfigFileBase::readNATForwardRulesMap(const xml::ElementNode &elmParent, NATRulesMap &mapRules)
+{
+ xml::ElementNodesList plstRules;
+ elmParent.getChildElements(plstRules, "Forwarding");
+ for (xml::ElementNodesList::iterator pf = plstRules.begin(); pf != plstRules.end(); ++pf)
+ {
+ NATRule rule;
+ uint32_t port = 0;
+ (*pf)->getAttributeValue("name", rule.strName);
+ (*pf)->getAttributeValue("proto", (uint32_t&)rule.proto);
+ (*pf)->getAttributeValue("hostip", rule.strHostIP);
+ (*pf)->getAttributeValue("hostport", port);
+ rule.u16HostPort = (uint16_t)port;
+ (*pf)->getAttributeValue("guestip", rule.strGuestIP);
+ (*pf)->getAttributeValue("guestport", port);
+ rule.u16GuestPort = (uint16_t)port;
+ mapRules.insert(std::make_pair(rule.strName, rule));
+ }
+}
+
+void ConfigFileBase::readNATLoopbacks(const xml::ElementNode &elmParent, NATLoopbackOffsetList &llLoopbacks)
+{
+ xml::ElementNodesList plstLoopbacks;
+ elmParent.getChildElements(plstLoopbacks, "Loopback4");
+ for (xml::ElementNodesList::iterator lo = plstLoopbacks.begin();
+ lo != plstLoopbacks.end(); ++lo)
+ {
+ NATHostLoopbackOffset loopback;
+ (*lo)->getAttributeValue("address", loopback.strLoopbackHostAddress);
+ (*lo)->getAttributeValue("offset", (uint32_t&)loopback.u32Offset);
+ llLoopbacks.push_back(loopback);
+ }
+}
+
+
+/**
+ * Adds a "version" attribute to the given XML element with the
+ * VirtualBox settings version (e.g. "1.10-linux"). Used by
+ * the XML format for the root element and by the OVF export
+ * for the vbox:Machine element.
+ * @param elm
+ */
+void ConfigFileBase::setVersionAttribute(xml::ElementNode &elm)
+{
+ const char *pcszVersion = NULL;
+ switch (m->sv)
+ {
+ case SettingsVersion_v1_8:
+ pcszVersion = "1.8";
+ break;
+
+ case SettingsVersion_v1_9:
+ pcszVersion = "1.9";
+ break;
+
+ case SettingsVersion_v1_10:
+ pcszVersion = "1.10";
+ break;
+
+ case SettingsVersion_v1_11:
+ pcszVersion = "1.11";
+ break;
+
+ case SettingsVersion_v1_12:
+ pcszVersion = "1.12";
+ break;
+
+ case SettingsVersion_v1_13:
+ pcszVersion = "1.13";
+ break;
+
+ case SettingsVersion_v1_14:
+ pcszVersion = "1.14";
+ break;
+
+ case SettingsVersion_v1_15:
+ pcszVersion = "1.15";
+ break;
+
+ case SettingsVersion_v1_16:
+ pcszVersion = "1.16";
+ break;
+
+ case SettingsVersion_v1_17:
+ pcszVersion = "1.17";
+ break;
+
+ case SettingsVersion_v1_18:
+ pcszVersion = "1.18";
+ break;
+
+ case SettingsVersion_v1_19:
+ pcszVersion = "1.19";
+ break;
+
+ default:
+ // catch human error: the assertion below will trigger in debug
+ // or dbgopt builds, so hopefully this will get noticed sooner in
+ // the future, because it's easy to forget top update something.
+ AssertMsg(m->sv <= SettingsVersion_v1_7, ("Settings.cpp: unexpected settings version %d, unhandled future version?\n", m->sv));
+ // silently upgrade if this is less than 1.7 because that's the oldest we can write
+ if (m->sv <= SettingsVersion_v1_7)
+ {
+ pcszVersion = "1.7";
+ m->sv = SettingsVersion_v1_7;
+ }
+ else
+ {
+ // This is reached for SettingsVersion_Future and forgotten
+ // settings version after SettingsVersion_v1_7, which should
+ // not happen (see assertion above). Set the version to the
+ // latest known version, to minimize loss of information, but
+ // as we can't predict the future we have to use some format
+ // we know, and latest should be the best choice. Note that
+ // for "forgotten settings" this may not be the best choice,
+ // but as it's an omission of someone who changed this file
+ // it's the only generic possibility.
+ pcszVersion = "1.19";
+ m->sv = SettingsVersion_v1_19;
+ }
+ break;
+ }
+
+ m->strSettingsVersionFull = Utf8StrFmt("%s-%s",
+ pcszVersion,
+ VBOX_XML_PLATFORM); // e.g. "linux"
+ elm.setAttribute("version", m->strSettingsVersionFull);
+}
+
+
+/**
+ * Creates a special backup file in case there is a version
+ * bump, so that it is possible to go back to the previous
+ * state. This is done only once (not for every settings
+ * version bump), when the settings version is newer than
+ * the version read from the config file. Must be called
+ * before ConfigFileBase::createStubDocument, because that
+ * method may alter information which this method needs.
+ */
+void ConfigFileBase::specialBackupIfFirstBump()
+{
+ // Since this gets called before the XML document is actually written out,
+ // this is where we must check whether we're upgrading the settings version
+ // and need to make a backup, so the user can go back to an earlier
+ // VirtualBox version and recover his old settings files.
+ if ( (m->svRead != SettingsVersion_Null) // old file exists?
+ && (m->svRead < m->sv) // we're upgrading?
+ )
+ {
+ // compose new filename: strip off trailing ".xml"/".vbox"
+ Utf8Str strFilenameNew;
+ Utf8Str strExt = ".xml";
+ if (m->strFilename.endsWith(".xml"))
+ strFilenameNew = m->strFilename.substr(0, m->strFilename.length() - 4);
+ else if (m->strFilename.endsWith(".vbox"))
+ {
+ strFilenameNew = m->strFilename.substr(0, m->strFilename.length() - 5);
+ strExt = ".vbox";
+ }
+
+ // and append something like "-1.3-linux.xml"
+ strFilenameNew.append("-");
+ strFilenameNew.append(m->strSettingsVersionFull); // e.g. "1.3-linux"
+ strFilenameNew.append(strExt); // .xml for main config, .vbox for machine config
+
+ // Copying the file cannot be avoided, as doing tricks with renaming
+ // causes trouble on OS X with aliases (which follow the rename), and
+ // on all platforms there is a risk of "losing" the VM config when
+ // running out of space, as a rename here couldn't be rolled back.
+ // Ignoring all errors besides running out of space is intentional, as
+ // we don't want to do anything if the file already exists.
+ int vrc = RTFileCopy(m->strFilename.c_str(), strFilenameNew.c_str());
+ if (RT_UNLIKELY(vrc == VERR_DISK_FULL))
+ throw ConfigFileError(this, NULL, N_("Cannot create settings backup file when upgrading to a newer settings format"));
+
+ // do this only once
+ m->svRead = SettingsVersion_Null;
+ }
+}
+
+/**
+ * Creates a new stub xml::Document in the m->pDoc member with the
+ * root "VirtualBox" element set up. This is used by both
+ * MainConfigFile and MachineConfigFile at the beginning of writing
+ * out their XML.
+ *
+ * Before calling this, it is the responsibility of the caller to
+ * set the "sv" member to the required settings version that is to
+ * be written. For newly created files, the settings version will be
+ * recent (1.12 or later if necessary); for files read in from disk
+ * earlier, it will be the settings version indicated in the file.
+ * However, this method will silently make sure that the settings
+ * version is always at least 1.7 and change it if necessary, since
+ * there is no write support for earlier settings versions.
+ */
+void ConfigFileBase::createStubDocument()
+{
+ Assert(m->pDoc == NULL);
+ m->pDoc = new xml::Document;
+
+ m->pelmRoot = m->pDoc->createRootElement("VirtualBox",
+ "\n"
+ "** DO NOT EDIT THIS FILE.\n"
+ "** If you make changes to this file while any VirtualBox related application\n"
+ "** is running, your changes will be overwritten later, without taking effect.\n"
+ "** Use VBoxManage or the VirtualBox Manager GUI to make changes.\n"
+);
+ m->pelmRoot->setAttribute("xmlns", VBOX_XML_NAMESPACE);
+ // Have the code for producing a proper schema reference. Not used by most
+ // tools, so don't bother doing it. The schema is not on the server anyway.
+#ifdef VBOX_WITH_SETTINGS_SCHEMA
+ m->pelmRoot->setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+ m->pelmRoot->setAttribute("xsi:schemaLocation", VBOX_XML_NAMESPACE " " VBOX_XML_SCHEMA);
+#endif
+
+ // add settings version attribute to root element, update m->strSettingsVersionFull
+ setVersionAttribute(*m->pelmRoot);
+
+ LogRel(("Saving settings file \"%s\" with version \"%s\"\n", m->strFilename.c_str(), m->strSettingsVersionFull.c_str()));
+}
+
+/**
+ * Creates an \<ExtraData\> node under the given parent element with
+ * \<ExtraDataItem\> childern according to the contents of the given
+ * map.
+ *
+ * This is in ConfigFileBase because it's used in both MainConfigFile
+ * and MachineConfigFile, which both can have extradata.
+ *
+ * @param elmParent
+ * @param me
+ */
+void ConfigFileBase::buildExtraData(xml::ElementNode &elmParent,
+ const StringsMap &me)
+{
+ if (me.size())
+ {
+ xml::ElementNode *pelmExtraData = elmParent.createChild("ExtraData");
+ for (StringsMap::const_iterator it = me.begin();
+ it != me.end();
+ ++it)
+ {
+ const Utf8Str &strName = it->first;
+ const Utf8Str &strValue = it->second;
+ xml::ElementNode *pelmThis = pelmExtraData->createChild("ExtraDataItem");
+ pelmThis->setAttribute("name", strName);
+ pelmThis->setAttribute("value", strValue);
+ }
+ }
+}
+
+/**
+ * Creates \<DeviceFilter\> nodes under the given parent element according to
+ * the contents of the given USBDeviceFiltersList. This is in ConfigFileBase
+ * because it's used in both MainConfigFile (for host filters) and
+ * MachineConfigFile (for machine filters).
+ *
+ * If fHostMode is true, this means that we're supposed to write filters
+ * for the IHost interface (respect "action", omit "strRemote" and
+ * "ulMaskedInterfaces" in struct USBDeviceFilter).
+ *
+ * @param elmParent
+ * @param ll
+ * @param fHostMode
+ */
+void ConfigFileBase::buildUSBDeviceFilters(xml::ElementNode &elmParent,
+ const USBDeviceFiltersList &ll,
+ bool fHostMode)
+{
+ for (USBDeviceFiltersList::const_iterator it = ll.begin();
+ it != ll.end();
+ ++it)
+ {
+ const USBDeviceFilter &flt = *it;
+ xml::ElementNode *pelmFilter = elmParent.createChild("DeviceFilter");
+ pelmFilter->setAttribute("name", flt.strName);
+ pelmFilter->setAttribute("active", flt.fActive);
+ if (flt.strVendorId.length())
+ pelmFilter->setAttribute("vendorId", flt.strVendorId);
+ if (flt.strProductId.length())
+ pelmFilter->setAttribute("productId", flt.strProductId);
+ if (flt.strRevision.length())
+ pelmFilter->setAttribute("revision", flt.strRevision);
+ if (flt.strManufacturer.length())
+ pelmFilter->setAttribute("manufacturer", flt.strManufacturer);
+ if (flt.strProduct.length())
+ pelmFilter->setAttribute("product", flt.strProduct);
+ if (flt.strSerialNumber.length())
+ pelmFilter->setAttribute("serialNumber", flt.strSerialNumber);
+ if (flt.strPort.length())
+ pelmFilter->setAttribute("port", flt.strPort);
+
+ if (fHostMode)
+ {
+ const char *pcsz =
+ (flt.action == USBDeviceFilterAction_Ignore) ? "Ignore"
+ : /*(flt.action == USBDeviceFilterAction_Hold) ?*/ "Hold";
+ pelmFilter->setAttribute("action", pcsz);
+ }
+ else
+ {
+ if (flt.strRemote.length())
+ pelmFilter->setAttribute("remote", flt.strRemote);
+ if (flt.ulMaskedInterfaces)
+ pelmFilter->setAttribute("maskedInterfaces", flt.ulMaskedInterfaces);
+ }
+ }
+}
+
+/**
+ * Creates a single \<HardDisk\> element for the given Medium structure
+ * and all child hard disks underneath. Called from MainConfigFile::write().
+ *
+ * @param t
+ * @param elmMedium
+ * @param med
+ */
+void ConfigFileBase::buildMedium(MediaType t,
+ xml::ElementNode &elmMedium,
+ const Medium &med)
+{
+ std::list<const Medium *> llSettingsTodo;
+ llSettingsTodo.push_back(&med);
+ std::list<xml::ElementNode *> llElementsTodo;
+ llElementsTodo.push_back(&elmMedium);
+ std::list<uint32_t> llDepthsTodo;
+ llDepthsTodo.push_back(1);
+
+ while (llSettingsTodo.size() > 0)
+ {
+ const Medium *pMed = llSettingsTodo.front();
+ llSettingsTodo.pop_front();
+ xml::ElementNode *pElement = llElementsTodo.front();
+ llElementsTodo.pop_front();
+ uint32_t depth = llDepthsTodo.front();
+ llDepthsTodo.pop_front();
+
+ if (depth > SETTINGS_MEDIUM_DEPTH_MAX)
+ throw ConfigFileError(this, pElement, N_("Maximum medium tree depth of %u exceeded"), SETTINGS_MEDIUM_DEPTH_MAX);
+
+ xml::ElementNode *pelmMedium;
+
+ if (t == HardDisk)
+ pelmMedium = pElement->createChild("HardDisk");
+ else
+ pelmMedium = pElement->createChild("Image");
+
+ pelmMedium->setAttribute("uuid", pMed->uuid.toStringCurly());
+
+ pelmMedium->setAttributePath("location", pMed->strLocation);
+
+ if (t == HardDisk || RTStrICmp(pMed->strFormat.c_str(), "RAW"))
+ pelmMedium->setAttribute("format", pMed->strFormat);
+ if ( t == HardDisk
+ && pMed->fAutoReset)
+ pelmMedium->setAttribute("autoReset", pMed->fAutoReset);
+ if (pMed->strDescription.length())
+ pelmMedium->createChild("Description")->addContent(pMed->strDescription);
+
+ for (StringsMap::const_iterator it = pMed->properties.begin();
+ it != pMed->properties.end();
+ ++it)
+ {
+ xml::ElementNode *pelmProp = pelmMedium->createChild("Property");
+ pelmProp->setAttribute("name", it->first);
+ pelmProp->setAttribute("value", it->second);
+ }
+
+ // only for base hard disks, save the type
+ if (depth == 1)
+ {
+ // no need to save the usual DVD/floppy medium types
+ if ( ( t != DVDImage
+ || ( pMed->hdType != MediumType_Writethrough // shouldn't happen
+ && pMed->hdType != MediumType_Readonly))
+ && ( t != FloppyImage
+ || pMed->hdType != MediumType_Writethrough))
+ {
+ const char *pcszType =
+ pMed->hdType == MediumType_Normal ? "Normal" :
+ pMed->hdType == MediumType_Immutable ? "Immutable" :
+ pMed->hdType == MediumType_Writethrough ? "Writethrough" :
+ pMed->hdType == MediumType_Shareable ? "Shareable" :
+ pMed->hdType == MediumType_Readonly ? "Readonly" :
+ pMed->hdType == MediumType_MultiAttach ? "MultiAttach" :
+ "INVALID";
+ pelmMedium->setAttribute("type", pcszType);
+ }
+ }
+
+ /* save all children */
+ MediaList::const_iterator itBegin = pMed->llChildren.begin();
+ MediaList::const_iterator itEnd = pMed->llChildren.end();
+ for (MediaList::const_iterator it = itBegin; it != itEnd; ++it)
+ {
+ llSettingsTodo.push_back(&*it);
+ llElementsTodo.push_back(pelmMedium);
+ llDepthsTodo.push_back(depth + 1);
+ }
+ }
+}
+
+/**
+ * Creates a \<MediaRegistry\> node under the given parent and writes out all
+ * hard disks and DVD and floppy images from the lists in the given MediaRegistry
+ * structure under it.
+ *
+ * This is used in both MainConfigFile and MachineConfigFile since starting with
+ * VirtualBox 4.0, we can have media registries in both.
+ *
+ * @param elmParent
+ * @param mr
+ */
+void ConfigFileBase::buildMediaRegistry(xml::ElementNode &elmParent,
+ const MediaRegistry &mr)
+{
+ if (mr.llHardDisks.size() == 0 && mr.llDvdImages.size() == 0 && mr.llFloppyImages.size() == 0)
+ return;
+
+ xml::ElementNode *pelmMediaRegistry = elmParent.createChild("MediaRegistry");
+
+ if (mr.llHardDisks.size())
+ {
+ xml::ElementNode *pelmHardDisks = pelmMediaRegistry->createChild("HardDisks");
+ for (MediaList::const_iterator it = mr.llHardDisks.begin();
+ it != mr.llHardDisks.end();
+ ++it)
+ {
+ buildMedium(HardDisk, *pelmHardDisks, *it);
+ }
+ }
+
+ if (mr.llDvdImages.size())
+ {
+ xml::ElementNode *pelmDVDImages = pelmMediaRegistry->createChild("DVDImages");
+ for (MediaList::const_iterator it = mr.llDvdImages.begin();
+ it != mr.llDvdImages.end();
+ ++it)
+ {
+ buildMedium(DVDImage, *pelmDVDImages, *it);
+ }
+ }
+
+ if (mr.llFloppyImages.size())
+ {
+ xml::ElementNode *pelmFloppyImages = pelmMediaRegistry->createChild("FloppyImages");
+ for (MediaList::const_iterator it = mr.llFloppyImages.begin();
+ it != mr.llFloppyImages.end();
+ ++it)
+ {
+ buildMedium(FloppyImage, *pelmFloppyImages, *it);
+ }
+ }
+}
+
+/**
+ * Serialize NAT port-forwarding rules in parent container.
+ * Note: it's responsibility of caller to create parent of the list tag.
+ * because this method used for serializing per-_mahine's_adapter_ and per-network approaches.
+ */
+void ConfigFileBase::buildNATForwardRulesMap(xml::ElementNode &elmParent, const NATRulesMap &mapRules)
+{
+ for (NATRulesMap::const_iterator r = mapRules.begin();
+ r != mapRules.end(); ++r)
+ {
+ xml::ElementNode *pelmPF;
+ pelmPF = elmParent.createChild("Forwarding");
+ const NATRule &nr = r->second;
+ if (nr.strName.length())
+ pelmPF->setAttribute("name", nr.strName);
+ pelmPF->setAttribute("proto", nr.proto);
+ if (nr.strHostIP.length())
+ pelmPF->setAttribute("hostip", nr.strHostIP);
+ if (nr.u16HostPort)
+ pelmPF->setAttribute("hostport", nr.u16HostPort);
+ if (nr.strGuestIP.length())
+ pelmPF->setAttribute("guestip", nr.strGuestIP);
+ if (nr.u16GuestPort)
+ pelmPF->setAttribute("guestport", nr.u16GuestPort);
+ }
+}
+
+
+void ConfigFileBase::buildNATLoopbacks(xml::ElementNode &elmParent, const NATLoopbackOffsetList &natLoopbackOffsetList)
+{
+ for (NATLoopbackOffsetList::const_iterator lo = natLoopbackOffsetList.begin();
+ lo != natLoopbackOffsetList.end(); ++lo)
+ {
+ xml::ElementNode *pelmLo;
+ pelmLo = elmParent.createChild("Loopback4");
+ pelmLo->setAttribute("address", (*lo).strLoopbackHostAddress);
+ pelmLo->setAttribute("offset", (*lo).u32Offset);
+ }
+}
+
+/**
+ * Cleans up memory allocated by the internal XML parser. To be called by
+ * descendant classes when they're done analyzing the DOM tree to discard it.
+ */
+void ConfigFileBase::clearDocument()
+{
+ m->cleanup();
+}
+
+/**
+ * Returns true only if the underlying config file exists on disk;
+ * either because the file has been loaded from disk, or it's been written
+ * to disk, or both.
+ * @return
+ */
+bool ConfigFileBase::fileExists()
+{
+ return m->fFileExists;
+}
+
+/**
+ * Returns the settings file version
+ *
+ * @returns Settings file version enum.
+ */
+SettingsVersion_T ConfigFileBase::getSettingsVersion()
+{
+ return m->sv;
+}
+
+
+/**
+ * Copies the base variables from another instance. Used by Machine::saveSettings
+ * so that the settings version does not get lost when a copy of the Machine settings
+ * file is made to see if settings have actually changed.
+ * @param b
+ */
+void ConfigFileBase::copyBaseFrom(const ConfigFileBase &b)
+{
+ m->copyFrom(*b.m);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Structures shared between Machine XML and VirtualBox.xml
+//
+////////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+USBDeviceFilter::USBDeviceFilter() :
+ fActive(false),
+ action(USBDeviceFilterAction_Null),
+ ulMaskedInterfaces(0)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool USBDeviceFilter::operator==(const USBDeviceFilter &u) const
+{
+ return (this == &u)
+ || ( strName == u.strName
+ && fActive == u.fActive
+ && strVendorId == u.strVendorId
+ && strProductId == u.strProductId
+ && strRevision == u.strRevision
+ && strManufacturer == u.strManufacturer
+ && strProduct == u.strProduct
+ && strSerialNumber == u.strSerialNumber
+ && strPort == u.strPort
+ && action == u.action
+ && strRemote == u.strRemote
+ && ulMaskedInterfaces == u.ulMaskedInterfaces);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+settings::Medium::Medium() :
+ fAutoReset(false),
+ hdType(MediumType_Normal)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool settings::Medium::operator==(const settings::Medium &m) const
+{
+ return (this == &m)
+ || ( uuid == m.uuid
+ && strLocation == m.strLocation
+ && strDescription == m.strDescription
+ && strFormat == m.strFormat
+ && fAutoReset == m.fAutoReset
+ && properties == m.properties
+ && hdType == m.hdType
+ && llChildren == m.llChildren); // this is deep and recurses
+}
+
+const struct settings::Medium settings::Medium::Empty; /* default ctor is OK */
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool MediaRegistry::operator==(const MediaRegistry &m) const
+{
+ return (this == &m)
+ || ( llHardDisks == m.llHardDisks
+ && llDvdImages == m.llDvdImages
+ && llFloppyImages == m.llFloppyImages);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+NATRule::NATRule() :
+ proto(NATProtocol_TCP),
+ u16HostPort(0),
+ u16GuestPort(0)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool NATRule::operator==(const NATRule &r) const
+{
+ return (this == &r)
+ || ( strName == r.strName
+ && proto == r.proto
+ && u16HostPort == r.u16HostPort
+ && strHostIP == r.strHostIP
+ && u16GuestPort == r.u16GuestPort
+ && strGuestIP == r.strGuestIP);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+NATHostLoopbackOffset::NATHostLoopbackOffset() :
+ u32Offset(0)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool NATHostLoopbackOffset::operator==(const NATHostLoopbackOffset &o) const
+{
+ return (this == &o)
+ || ( strLoopbackHostAddress == o.strLoopbackHostAddress
+ && u32Offset == o.u32Offset);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// VirtualBox.xml structures
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+SystemProperties::SystemProperties()
+ : uProxyMode(ProxyMode_System)
+ , uLogHistoryCount(3)
+ , fExclusiveHwVirt(true)
+{
+#if defined(RT_OS_DARWIN) || defined(RT_OS_WINDOWS) || defined(RT_OS_SOLARIS)
+ fExclusiveHwVirt = false;
+#endif
+}
+
+#ifdef VBOX_WITH_UPDATE_AGENT
+UpdateAgent::UpdateAgent()
+ : fEnabled(false)
+ , enmChannel(UpdateChannel_Stable)
+ , uCheckFreqSeconds(RT_SEC_1DAY)
+ , uCheckCount(0)
+{
+}
+#endif /* VBOX_WITH_UPDATE_AGENT */
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+DhcpOptValue::DhcpOptValue()
+ : strValue()
+ , enmEncoding(DHCPOptionEncoding_Normal)
+{
+}
+
+/**
+ * Non-standard constructor.
+ */
+DhcpOptValue::DhcpOptValue(const com::Utf8Str &aText, DHCPOptionEncoding_T aEncoding)
+ : strValue(aText)
+ , enmEncoding(aEncoding)
+{
+}
+
+/**
+ * Default constructor.
+ */
+DHCPGroupCondition::DHCPGroupCondition()
+ : fInclusive(true)
+ , enmType(DHCPGroupConditionType_MAC)
+ , strValue()
+{
+}
+
+/**
+ * Default constructor.
+ */
+DHCPConfig::DHCPConfig()
+ : mapOptions()
+ , secMinLeaseTime(0)
+ , secDefaultLeaseTime(0)
+ , secMaxLeaseTime(0)
+{
+}
+
+/**
+ * Default constructor.
+ */
+DHCPGroupConfig::DHCPGroupConfig()
+ : DHCPConfig()
+ , strName()
+ , vecConditions()
+{
+}
+
+/**
+ * Default constructor.
+ */
+DHCPIndividualConfig::DHCPIndividualConfig()
+ : DHCPConfig()
+ , strMACAddress()
+ , strVMName()
+ , uSlot(0)
+{
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+DHCPServer::DHCPServer()
+ : fEnabled(false)
+{
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+NATNetwork::NATNetwork() :
+ fEnabled(true),
+ fIPv6Enabled(false),
+ fAdvertiseDefaultIPv6Route(false),
+ fNeedDhcpServer(true),
+ u32HostLoopback6Offset(0)
+{
+}
+
+#ifdef VBOX_WITH_VMNET
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+HostOnlyNetwork::HostOnlyNetwork() :
+ strNetworkMask("255.255.255.0"),
+ strIPLower("192.168.56.1"),
+ strIPUpper("192.168.56.199"),
+ fEnabled(true)
+{
+ uuid.create();
+}
+#endif /* VBOX_WITH_VMNET */
+
+#ifdef VBOX_WITH_CLOUD_NET
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+CloudNetwork::CloudNetwork() :
+ strProviderShortName("OCI"),
+ strProfileName("Default"),
+ fEnabled(true)
+{
+}
+#endif /* VBOX_WITH_CLOUD_NET */
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MainConfigFile
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Reads one \<MachineEntry\> from the main VirtualBox.xml file.
+ * @param elmMachineRegistry
+ */
+void MainConfigFile::readMachineRegistry(const xml::ElementNode &elmMachineRegistry)
+{
+ // <MachineEntry uuid="{ xxx }" src=" xxx "/>
+ xml::NodesLoop nl1(elmMachineRegistry);
+ const xml::ElementNode *pelmChild1;
+ while ((pelmChild1 = nl1.forAllNodes()))
+ {
+ if (pelmChild1->nameEquals("MachineEntry"))
+ {
+ MachineRegistryEntry mre;
+ Utf8Str strUUID;
+ if ( pelmChild1->getAttributeValue("uuid", strUUID)
+ && pelmChild1->getAttributeValue("src", mre.strSettingsFile) )
+ {
+ parseUUID(mre.uuid, strUUID, pelmChild1);
+ llMachines.push_back(mre);
+ }
+ else
+ throw ConfigFileError(this, pelmChild1, N_("Required MachineEntry/@uuid or @src attribute is missing"));
+ }
+ }
+}
+
+/**
+ * Builds the XML tree for the DHCP servers.
+ */
+void MainConfigFile::buildDHCPServers(xml::ElementNode &elmDHCPServers, DHCPServersList const &ll)
+{
+ for (DHCPServersList::const_iterator it = ll.begin(); it != ll.end(); ++it)
+ {
+ const DHCPServer &srv = *it;
+ xml::ElementNode *pElmThis = elmDHCPServers.createChild("DHCPServer");
+
+ pElmThis->setAttribute("networkName", srv.strNetworkName);
+ pElmThis->setAttribute("IPAddress", srv.strIPAddress);
+ DhcpOptConstIterator itOpt = srv.globalConfig.mapOptions.find(DHCPOption_SubnetMask);
+ if (itOpt != srv.globalConfig.mapOptions.end())
+ pElmThis->setAttribute("networkMask", itOpt->second.strValue);
+ pElmThis->setAttribute("lowerIP", srv.strIPLower);
+ pElmThis->setAttribute("upperIP", srv.strIPUpper);
+ pElmThis->setAttribute("enabled", (srv.fEnabled) ? 1 : 0); // too bad we chose 1 vs. 0 here
+
+ /* We don't want duplicate validation check of networkMask here*/
+ if (srv.globalConfig.mapOptions.size() > (itOpt != srv.globalConfig.mapOptions.end() ? 1U : 0U))
+ {
+ xml::ElementNode *pElmOptions = pElmThis->createChild("Options");
+ buildDHCPOptions(*pElmOptions, srv.globalConfig, true);
+ }
+
+ for (DHCPGroupConfigVec::const_iterator itGroup = srv.vecGroupConfigs.begin();
+ itGroup != srv.vecGroupConfigs.end(); ++itGroup)
+ {
+ DHCPGroupConfig const &rGroupConfig = *itGroup;
+
+ xml::ElementNode *pElmGroup = pElmThis->createChild("Group");
+ pElmGroup->setAttribute("name", rGroupConfig.strName);
+ buildDHCPOptions(*pElmGroup, rGroupConfig, false);
+
+ for (DHCPGroupConditionVec::const_iterator itCond = rGroupConfig.vecConditions.begin();
+ itCond != rGroupConfig.vecConditions.end(); ++itCond)
+ {
+ xml::ElementNode *pElmCondition = pElmGroup->createChild("Condition");
+ pElmCondition->setAttribute("inclusive", itCond->fInclusive);
+ pElmCondition->setAttribute("type", (int32_t)itCond->enmType);
+ pElmCondition->setAttribute("value", itCond->strValue);
+ }
+ }
+
+ for (DHCPIndividualConfigMap::const_iterator itHost = srv.mapIndividualConfigs.begin();
+ itHost != srv.mapIndividualConfigs.end(); ++itHost)
+ {
+ DHCPIndividualConfig const &rIndividualConfig = itHost->second;
+
+ xml::ElementNode *pElmConfig = pElmThis->createChild("Config");
+ if (rIndividualConfig.strMACAddress.isNotEmpty())
+ pElmConfig->setAttribute("MACAddress", rIndividualConfig.strMACAddress);
+ if (rIndividualConfig.strVMName.isNotEmpty())
+ pElmConfig->setAttribute("vm-name", rIndividualConfig.strVMName);
+ if (rIndividualConfig.uSlot != 0 || rIndividualConfig.strVMName.isNotEmpty())
+ pElmConfig->setAttribute("slot", rIndividualConfig.uSlot);
+ if (rIndividualConfig.strFixedAddress.isNotEmpty())
+ pElmConfig->setAttribute("fixedAddress", rIndividualConfig.strFixedAddress);
+ buildDHCPOptions(*pElmConfig, rIndividualConfig, false);
+ }
+ }
+}
+
+/**
+ * Worker for buildDHCPServers() that builds Options or Config element trees.
+ */
+void MainConfigFile::buildDHCPOptions(xml::ElementNode &elmOptions, DHCPConfig const &rConfig, bool fSkipSubnetMask)
+{
+ /* Generic (and optional) attributes on the Options or Config element: */
+ if (rConfig.secMinLeaseTime > 0)
+ elmOptions.setAttribute("secMinLeaseTime", rConfig.secMinLeaseTime);
+ if (rConfig.secDefaultLeaseTime > 0)
+ elmOptions.setAttribute("secDefaultLeaseTime", rConfig.secDefaultLeaseTime);
+ if (rConfig.secMaxLeaseTime > 0)
+ elmOptions.setAttribute("secMaxLeaseTime", rConfig.secMaxLeaseTime);
+ if (rConfig.strForcedOptions.isNotEmpty())
+ elmOptions.setAttribute("forcedOptions", rConfig.strForcedOptions);
+ if (rConfig.strSuppressedOptions.isNotEmpty())
+ elmOptions.setAttribute("suppressedOptions", rConfig.strSuppressedOptions);
+
+ /* The DHCP options are <Option> child elements: */
+ for (DhcpOptConstIterator it = rConfig.mapOptions.begin(); it != rConfig.mapOptions.end(); ++it)
+ if (it->first != DHCPOption_SubnetMask || !fSkipSubnetMask)
+ {
+ xml::ElementNode *pElmOption = elmOptions.createChild("Option");
+ pElmOption->setAttribute("name", it->first);
+ pElmOption->setAttribute("value", it->second.strValue);
+ if (it->second.enmEncoding != DHCPOptionEncoding_Normal)
+ pElmOption->setAttribute("encoding", (int32_t)it->second.enmEncoding);
+ }
+}
+
+/**
+ * Reads in the \<DHCPServers\> chunk.
+ * @param elmDHCPServers
+ */
+void MainConfigFile::readDHCPServers(const xml::ElementNode &elmDHCPServers)
+{
+ xml::NodesLoop nl1(elmDHCPServers);
+ const xml::ElementNode *pelmServer;
+ while ((pelmServer = nl1.forAllNodes()))
+ {
+ if (pelmServer->nameEquals("DHCPServer"))
+ {
+ DHCPServer srv;
+ if ( pelmServer->getAttributeValue("networkName", srv.strNetworkName)
+ && pelmServer->getAttributeValue("IPAddress", srv.strIPAddress)
+ && pelmServer->getAttributeValue("networkMask", srv.globalConfig.mapOptions[DHCPOption_SubnetMask].strValue)
+ && pelmServer->getAttributeValue("lowerIP", srv.strIPLower)
+ && pelmServer->getAttributeValue("upperIP", srv.strIPUpper)
+ && pelmServer->getAttributeValue("enabled", srv.fEnabled) )
+ {
+ /* Global options: */
+ const xml::ElementNode *pElmOptions;
+ xml::NodesLoop nlOptions(*pelmServer, "Options");
+ while ((pElmOptions = nlOptions.forAllNodes()) != NULL) /** @todo this loop makes no sense, there can only be one \<Options\> child. */
+ readDHCPOptions(srv.globalConfig, *pElmOptions, true /*fIgnoreSubnetMask*/);
+
+ /* Group configurations: */
+ xml::NodesLoop nlGroup(*pelmServer, "Group");
+ const xml::ElementNode *pElmGroup;
+ size_t i = 0;
+ while ((pElmGroup = nlGroup.forAllNodes()) != NULL)
+ {
+ srv.vecGroupConfigs.push_back(DHCPGroupConfig());
+ DHCPGroupConfig &rGroupConfig = srv.vecGroupConfigs.back();
+
+ if (!pElmGroup->getAttributeValue("name", rGroupConfig.strName))
+ rGroupConfig.strName.printf("Unamed Group #%u", ++i);
+
+ readDHCPOptions(rGroupConfig, *pElmGroup, false /*fIgnoreSubnetMask*/);
+
+ xml::NodesLoop nlCondition(*pElmGroup, "Condition");
+ const xml::ElementNode *pElmCondition;
+ while ((pElmCondition = nlCondition.forAllNodes()) != NULL)
+ {
+ rGroupConfig.vecConditions.push_back(DHCPGroupCondition());
+ DHCPGroupCondition &rGroupCondition = rGroupConfig.vecConditions.back();
+
+ if (!pElmCondition->getAttributeValue("inclusive", rGroupCondition.fInclusive))
+ rGroupCondition.fInclusive = true;
+
+ int32_t iType;
+ if (!pElmCondition->getAttributeValue("type", iType))
+ iType = DHCPGroupConditionType_MAC;
+ rGroupCondition.enmType = (DHCPGroupConditionType_T)iType;
+
+ pElmCondition->getAttributeValue("value", rGroupCondition.strValue);
+ }
+ }
+
+ /* host specific configuration: */
+ xml::NodesLoop nlConfig(*pelmServer, "Config");
+ const xml::ElementNode *pElmConfig;
+ while ((pElmConfig = nlConfig.forAllNodes()) != NULL)
+ {
+ com::Utf8Str strMACAddress;
+ if (!pElmConfig->getAttributeValue("MACAddress", strMACAddress))
+ strMACAddress.setNull();
+
+ com::Utf8Str strVMName;
+ if (!pElmConfig->getAttributeValue("vm-name", strVMName))
+ strVMName.setNull();
+
+ uint32_t uSlot;
+ if (!pElmConfig->getAttributeValue("slot", uSlot))
+ uSlot = 0;
+
+ com::Utf8Str strKey;
+ if (strVMName.isNotEmpty())
+ strKey.printf("%s/%u", strVMName.c_str(), uSlot);
+ else
+ strKey.printf("%s/%u", strMACAddress.c_str(), uSlot);
+
+ DHCPIndividualConfig &rIndividualConfig = srv.mapIndividualConfigs[strKey];
+ rIndividualConfig.strMACAddress = strMACAddress;
+ rIndividualConfig.strVMName = strVMName;
+ rIndividualConfig.uSlot = uSlot;
+ pElmConfig->getAttributeValue("fixedAddress", rIndividualConfig.strFixedAddress);
+
+ readDHCPOptions(rIndividualConfig, *pElmConfig, false /*fIgnoreSubnetMask*/);
+ }
+
+ llDhcpServers.push_back(srv);
+ }
+ else
+ throw ConfigFileError(this, pelmServer, N_("Required DHCPServer/@networkName, @IPAddress, @networkMask, @lowerIP, @upperIP or @enabled attribute is missing"));
+ }
+ }
+}
+
+/**
+ * Worker for readDHCPServers that reads a configuration, either global,
+ * group or host (VM+NIC) specific.
+ */
+void MainConfigFile::readDHCPOptions(DHCPConfig &rConfig, const xml::ElementNode &elmConfig, bool fIgnoreSubnetMask)
+{
+ /* Generic (and optional) attributes on the Options or Config element: */
+ if (!elmConfig.getAttributeValue("secMinLeaseTime", rConfig.secMinLeaseTime))
+ rConfig.secMinLeaseTime = 0;
+ if (!elmConfig.getAttributeValue("secDefaultLeaseTime", rConfig.secDefaultLeaseTime))
+ rConfig.secDefaultLeaseTime = 0;
+ if (!elmConfig.getAttributeValue("secMaxLeaseTime", rConfig.secMaxLeaseTime))
+ rConfig.secMaxLeaseTime = 0;
+ if (!elmConfig.getAttributeValue("forcedOptions", rConfig.strForcedOptions))
+ rConfig.strSuppressedOptions.setNull();
+ if (!elmConfig.getAttributeValue("suppressedOptions", rConfig.strSuppressedOptions))
+ rConfig.strSuppressedOptions.setNull();
+
+ /* The DHCP options are <Option> child elements: */
+ xml::NodesLoop nl2(elmConfig, "Option");
+ const xml::ElementNode *pElmOption;
+ while ((pElmOption = nl2.forAllNodes()) != NULL)
+ {
+ int32_t iOptName;
+ if (!pElmOption->getAttributeValue("name", iOptName))
+ continue;
+ DHCPOption_T OptName = (DHCPOption_T)iOptName;
+ if (OptName == DHCPOption_SubnetMask && fIgnoreSubnetMask)
+ continue;
+
+ com::Utf8Str strValue;
+ pElmOption->getAttributeValue("value", strValue);
+
+ int32_t iOptEnc;
+ if (!pElmOption->getAttributeValue("encoding", iOptEnc))
+ iOptEnc = DHCPOptionEncoding_Normal;
+
+ rConfig.mapOptions[OptName] = DhcpOptValue(strValue, (DHCPOptionEncoding_T)iOptEnc);
+ } /* end of forall("Option") */
+
+}
+
+/**
+ * Reads in the \<NATNetworks\> chunk.
+ * @param elmNATNetworks
+ */
+void MainConfigFile::readNATNetworks(const xml::ElementNode &elmNATNetworks)
+{
+ xml::NodesLoop nl1(elmNATNetworks);
+ const xml::ElementNode *pelmNet;
+ while ((pelmNet = nl1.forAllNodes()))
+ {
+ if (pelmNet->nameEquals("NATNetwork"))
+ {
+ NATNetwork net;
+ if ( pelmNet->getAttributeValue("networkName", net.strNetworkName)
+ && pelmNet->getAttributeValue("enabled", net.fEnabled)
+ && pelmNet->getAttributeValue("network", net.strIPv4NetworkCidr)
+ && pelmNet->getAttributeValue("ipv6", net.fIPv6Enabled)
+ && pelmNet->getAttributeValue("ipv6prefix", net.strIPv6Prefix)
+ && pelmNet->getAttributeValue("advertiseDefaultIPv6Route", net.fAdvertiseDefaultIPv6Route)
+ && pelmNet->getAttributeValue("needDhcp", net.fNeedDhcpServer) )
+ {
+ pelmNet->getAttributeValue("loopback6", net.u32HostLoopback6Offset);
+ const xml::ElementNode *pelmMappings;
+ if ((pelmMappings = pelmNet->findChildElement("Mappings")))
+ readNATLoopbacks(*pelmMappings, net.llHostLoopbackOffsetList);
+
+ const xml::ElementNode *pelmPortForwardRules4;
+ if ((pelmPortForwardRules4 = pelmNet->findChildElement("PortForwarding4")))
+ readNATForwardRulesMap(*pelmPortForwardRules4,
+ net.mapPortForwardRules4);
+
+ const xml::ElementNode *pelmPortForwardRules6;
+ if ((pelmPortForwardRules6 = pelmNet->findChildElement("PortForwarding6")))
+ readNATForwardRulesMap(*pelmPortForwardRules6,
+ net.mapPortForwardRules6);
+
+ llNATNetworks.push_back(net);
+ }
+ else
+ throw ConfigFileError(this, pelmNet, N_("Required NATNetwork/@networkName, @gateway, @network,@advertiseDefaultIpv6Route , @needDhcp or @enabled attribute is missing"));
+ }
+ }
+}
+
+#ifdef VBOX_WITH_VMNET
+/**
+ * Reads in the \<HostOnlyNetworks\> chunk.
+ * @param elmHostOnlyNetworks
+ */
+void MainConfigFile::readHostOnlyNetworks(const xml::ElementNode &elmHostOnlyNetworks)
+{
+ xml::NodesLoop nl1(elmHostOnlyNetworks);
+ const xml::ElementNode *pelmNet;
+ while ((pelmNet = nl1.forAllNodes()))
+ {
+ if (pelmNet->nameEquals("HostOnlyNetwork"))
+ {
+ HostOnlyNetwork net;
+ Utf8Str strID;
+ if ( pelmNet->getAttributeValue("name", net.strNetworkName)
+ && pelmNet->getAttributeValue("mask", net.strNetworkMask)
+ && pelmNet->getAttributeValue("ipLower", net.strIPLower)
+ && pelmNet->getAttributeValue("ipUpper", net.strIPUpper)
+ && pelmNet->getAttributeValue("id", strID)
+ && pelmNet->getAttributeValue("enabled", net.fEnabled) )
+ {
+ parseUUID(net.uuid, strID, pelmNet);
+ llHostOnlyNetworks.push_back(net);
+ }
+ else
+ throw ConfigFileError(this, pelmNet, N_("Required HostOnlyNetwork/@name, @mask, @ipLower, @ipUpper, @id or @enabled attribute is missing"));
+ }
+ }
+}
+#endif /* VBOX_WITH_VMNET */
+
+#ifdef VBOX_WITH_CLOUD_NET
+/**
+ * Reads in the \<CloudNetworks\> chunk.
+ * @param elmCloudNetworks
+ */
+void MainConfigFile::readCloudNetworks(const xml::ElementNode &elmCloudNetworks)
+{
+ xml::NodesLoop nl1(elmCloudNetworks);
+ const xml::ElementNode *pelmNet;
+ while ((pelmNet = nl1.forAllNodes()))
+ {
+ if (pelmNet->nameEquals("CloudNetwork"))
+ {
+ CloudNetwork net;
+ if ( pelmNet->getAttributeValue("name", net.strNetworkName)
+ && pelmNet->getAttributeValue("provider", net.strProviderShortName)
+ && pelmNet->getAttributeValue("profile", net.strProfileName)
+ && pelmNet->getAttributeValue("id", net.strNetworkId)
+ && pelmNet->getAttributeValue("enabled", net.fEnabled) )
+ {
+ llCloudNetworks.push_back(net);
+ }
+ else
+ throw ConfigFileError(this, pelmNet, N_("Required CloudNetwork/@name, @provider, @profile, @id or @enabled attribute is missing"));
+ }
+ }
+}
+#endif /* VBOX_WITH_CLOUD_NET */
+
+/**
+ * Creates \<USBDeviceSource\> nodes under the given parent element according to
+ * the contents of the given USBDeviceSourcesList.
+ *
+ * @param elmParent
+ * @param ll
+ */
+void MainConfigFile::buildUSBDeviceSources(xml::ElementNode &elmParent,
+ const USBDeviceSourcesList &ll)
+{
+ for (USBDeviceSourcesList::const_iterator it = ll.begin();
+ it != ll.end();
+ ++it)
+ {
+ const USBDeviceSource &src = *it;
+ xml::ElementNode *pelmSource = elmParent.createChild("USBDeviceSource");
+ pelmSource->setAttribute("name", src.strName);
+ pelmSource->setAttribute("backend", src.strBackend);
+ pelmSource->setAttribute("address", src.strAddress);
+
+ /* Write the properties. */
+ for (StringsMap::const_iterator itProp = src.properties.begin();
+ itProp != src.properties.end();
+ ++itProp)
+ {
+ xml::ElementNode *pelmProp = pelmSource->createChild("Property");
+ pelmProp->setAttribute("name", itProp->first);
+ pelmProp->setAttribute("value", itProp->second);
+ }
+ }
+}
+
+/**
+ * Reads \<USBDeviceFilter\> entries from under the given elmDeviceFilters node and
+ * stores them in the given linklist. This is in ConfigFileBase because it's used
+ * from both MainConfigFile (for host filters) and MachineConfigFile (for machine
+ * filters).
+ * @param elmDeviceSources
+ * @param ll
+ */
+void MainConfigFile::readUSBDeviceSources(const xml::ElementNode &elmDeviceSources,
+ USBDeviceSourcesList &ll)
+{
+ xml::NodesLoop nl1(elmDeviceSources, "USBDeviceSource");
+ const xml::ElementNode *pelmChild;
+ while ((pelmChild = nl1.forAllNodes()))
+ {
+ USBDeviceSource src;
+
+ if ( pelmChild->getAttributeValue("name", src.strName)
+ && pelmChild->getAttributeValue("backend", src.strBackend)
+ && pelmChild->getAttributeValue("address", src.strAddress))
+ {
+ // handle medium properties
+ xml::NodesLoop nl2(*pelmChild, "Property");
+ const xml::ElementNode *pelmSrcChild;
+ while ((pelmSrcChild = nl2.forAllNodes()))
+ {
+ Utf8Str strPropName, strPropValue;
+ if ( pelmSrcChild->getAttributeValue("name", strPropName)
+ && pelmSrcChild->getAttributeValue("value", strPropValue) )
+ src.properties[strPropName] = strPropValue;
+ else
+ throw ConfigFileError(this, pelmSrcChild, N_("Required USBDeviceSource/Property/@name or @value attribute is missing"));
+ }
+
+ ll.push_back(src);
+ }
+ }
+}
+
+/**
+ * Converts old style Proxy settings from ExtraData/UI section.
+ *
+ * Saves proxy settings directly to systemProperties structure.
+ *
+ * @returns true if conversion was successfull, false if not.
+ * @param strUIProxySettings The GUI settings string to convert.
+ */
+bool MainConfigFile::convertGuiProxySettings(const com::Utf8Str &strUIProxySettings)
+{
+ /*
+ * Possible variants:
+ * - "ProxyAuto,proxyserver.url,1080,authDisabled,,"
+ * - "ProxyDisabled,proxyserver.url,1080,authDisabled,,"
+ * - "ProxyEnabled,proxyserver.url,1080,authDisabled,,"
+ *
+ * Note! We only need to bother with the first three fields as the last
+ * three was never really used or ever actually passed to the HTTP
+ * client code.
+ */
+ /* First field: The proxy mode. */
+ const char *psz = RTStrStripL(strUIProxySettings.c_str());
+ static const struct { const char *psz; size_t cch; ProxyMode_T enmMode; } s_aModes[] =
+ {
+ { RT_STR_TUPLE("ProxyAuto"), ProxyMode_System },
+ { RT_STR_TUPLE("ProxyDisabled"), ProxyMode_NoProxy },
+ { RT_STR_TUPLE("ProxyEnabled"), ProxyMode_Manual },
+ };
+ for (size_t i = 0; i < RT_ELEMENTS(s_aModes); i++)
+ if (RTStrNICmpAscii(psz, s_aModes[i].psz, s_aModes[i].cch) == 0)
+ {
+ systemProperties.uProxyMode = s_aModes[i].enmMode;
+ psz = RTStrStripL(psz + s_aModes[i].cch);
+ if (*psz == ',')
+ {
+ /* Second field: The proxy host, possibly fully fledged proxy URL. */
+ psz = RTStrStripL(psz + 1);
+ if (*psz != '\0' && *psz != ',')
+ {
+ const char *pszEnd = strchr(psz, ',');
+ size_t cchHost = pszEnd ? (size_t)(pszEnd - psz) : strlen(psz);
+ while (cchHost > 0 && RT_C_IS_SPACE(psz[cchHost - 1]))
+ cchHost--;
+ systemProperties.strProxyUrl.assign(psz, cchHost);
+ if (systemProperties.strProxyUrl.find("://") == RTCString::npos)
+ systemProperties.strProxyUrl.replace(0, 0, "http://");
+
+ /* Third field: The proxy port. Defaulted to 1080 for all proxies.
+ The new settings has type specific default ports. */
+ uint16_t uPort = 1080;
+ if (pszEnd)
+ {
+ int rc = RTStrToUInt16Ex(RTStrStripL(pszEnd + 1), NULL, 10, &uPort);
+ if (RT_FAILURE(rc))
+ uPort = 1080;
+ }
+ RTURIPARSED Parsed;
+ int rc = RTUriParse(systemProperties.strProxyUrl.c_str(), &Parsed);
+ if (RT_SUCCESS(rc))
+ {
+ if (Parsed.uAuthorityPort == UINT32_MAX)
+ systemProperties.strProxyUrl.appendPrintf(systemProperties.strProxyUrl.endsWith(":")
+ ? "%u" : ":%u", uPort);
+ }
+ else
+ {
+ LogRelFunc(("Dropping invalid proxy URL for %u: %s\n",
+ systemProperties.uProxyMode, systemProperties.strProxyUrl.c_str()));
+ systemProperties.strProxyUrl.setNull();
+ }
+ }
+ /* else: don't bother with the rest if we haven't got a host. */
+ }
+ if ( systemProperties.strProxyUrl.isEmpty()
+ && systemProperties.uProxyMode == ProxyMode_Manual)
+ {
+ systemProperties.uProxyMode = ProxyMode_System;
+ return false;
+ }
+ return true;
+ }
+ LogRelFunc(("Unknown proxy type: %s\n", psz));
+ return false;
+}
+
+/**
+ * Constructor.
+ *
+ * If pstrFilename is != NULL, this reads the given settings file into the member
+ * variables and various substructures and lists. Otherwise, the member variables
+ * are initialized with default values.
+ *
+ * Throws variants of xml::Error for I/O, XML and logical content errors, which
+ * the caller should catch; if this constructor does not throw, then the member
+ * variables contain meaningful values (either from the file or defaults).
+ *
+ * @param pstrFilename
+ */
+MainConfigFile::MainConfigFile(const Utf8Str *pstrFilename)
+ : ConfigFileBase(pstrFilename)
+{
+ if (pstrFilename)
+ {
+ // the ConfigFileBase constructor has loaded the XML file, so now
+ // we need only analyze what is in there
+ xml::NodesLoop nlRootChildren(*m->pelmRoot);
+ const xml::ElementNode *pelmRootChild;
+ bool fCopyProxySettingsFromExtraData = false;
+ while ((pelmRootChild = nlRootChildren.forAllNodes()))
+ {
+ if (pelmRootChild->nameEquals("Global"))
+ {
+ xml::NodesLoop nlGlobalChildren(*pelmRootChild);
+ const xml::ElementNode *pelmGlobalChild;
+ while ((pelmGlobalChild = nlGlobalChildren.forAllNodes()))
+ {
+ if (pelmGlobalChild->nameEquals("SystemProperties"))
+ {
+ pelmGlobalChild->getAttributeValue("defaultMachineFolder", systemProperties.strDefaultMachineFolder);
+ pelmGlobalChild->getAttributeValue("LoggingLevel", systemProperties.strLoggingLevel);
+ pelmGlobalChild->getAttributeValue("defaultHardDiskFormat", systemProperties.strDefaultHardDiskFormat);
+ if (!pelmGlobalChild->getAttributeValue("VRDEAuthLibrary", systemProperties.strVRDEAuthLibrary))
+ // pre-1.11 used @remoteDisplayAuthLibrary instead
+ pelmGlobalChild->getAttributeValue("remoteDisplayAuthLibrary", systemProperties.strVRDEAuthLibrary);
+ pelmGlobalChild->getAttributeValue("webServiceAuthLibrary", systemProperties.strWebServiceAuthLibrary);
+ pelmGlobalChild->getAttributeValue("defaultVRDEExtPack", systemProperties.strDefaultVRDEExtPack);
+ pelmGlobalChild->getAttributeValue("defaultCryptoExtPack", systemProperties.strDefaultCryptoExtPack);
+ pelmGlobalChild->getAttributeValue("LogHistoryCount", systemProperties.uLogHistoryCount);
+ pelmGlobalChild->getAttributeValue("autostartDatabasePath", systemProperties.strAutostartDatabasePath);
+ pelmGlobalChild->getAttributeValue("defaultFrontend", systemProperties.strDefaultFrontend);
+ pelmGlobalChild->getAttributeValue("exclusiveHwVirt", systemProperties.fExclusiveHwVirt);
+ if (!pelmGlobalChild->getAttributeValue("proxyMode", systemProperties.uProxyMode))
+ fCopyProxySettingsFromExtraData = true;
+ pelmGlobalChild->getAttributeValue("proxyUrl", systemProperties.strProxyUrl);
+ pelmGlobalChild->getAttributeValue("LanguageId", systemProperties.strLanguageId);
+ }
+#ifdef VBOX_WITH_UPDATE_AGENT
+ else if (pelmGlobalChild->nameEquals("Updates"))
+ {
+ /* We keep the updates configuration as part of the host for now, as the API exposes the IHost::updateHost attribute,
+ * but use an own "Updates" branch in the XML for better structurizing stuff in the future. */
+ UpdateAgent &updateHost = host.updateHost;
+
+ xml::NodesLoop nlLevel4(*pelmGlobalChild);
+ const xml::ElementNode *pelmLevel4Child;
+ while ((pelmLevel4Child = nlLevel4.forAllNodes()))
+ {
+ if (pelmLevel4Child->nameEquals("Host"))
+ {
+ pelmLevel4Child->getAttributeValue("enabled", updateHost.fEnabled);
+ pelmLevel4Child->getAttributeValue("channel", (uint32_t&)updateHost.enmChannel);
+ pelmLevel4Child->getAttributeValue("checkFreqSec", updateHost.uCheckFreqSeconds);
+ pelmLevel4Child->getAttributeValue("repoUrl", updateHost.strRepoUrl);
+ pelmLevel4Child->getAttributeValue("lastCheckDate", updateHost.strLastCheckDate);
+ pelmLevel4Child->getAttributeValue("checkCount", updateHost.uCheckCount);
+ }
+ /** @todo Add update settings for ExtPack and Guest Additions here later. See @bugref{7983}. */
+ }
+
+ /* Global enabled switch for updates. Currently bound to host updates, as this is the only update we have so far. */
+ pelmGlobalChild->getAttributeValue("enabled", updateHost.fEnabled);
+ }
+#endif
+ else if (pelmGlobalChild->nameEquals("ExtraData"))
+ readExtraData(*pelmGlobalChild, mapExtraDataItems);
+ else if (pelmGlobalChild->nameEquals("MachineRegistry"))
+ readMachineRegistry(*pelmGlobalChild);
+ else if ( (pelmGlobalChild->nameEquals("MediaRegistry"))
+ || ( (m->sv < SettingsVersion_v1_4)
+ && (pelmGlobalChild->nameEquals("DiskRegistry"))
+ )
+ )
+ readMediaRegistry(*pelmGlobalChild, mediaRegistry);
+ else if (pelmGlobalChild->nameEquals("NetserviceRegistry"))
+ {
+ xml::NodesLoop nlLevel4(*pelmGlobalChild);
+ const xml::ElementNode *pelmLevel4Child;
+ while ((pelmLevel4Child = nlLevel4.forAllNodes()))
+ {
+ if (pelmLevel4Child->nameEquals("DHCPServers"))
+ readDHCPServers(*pelmLevel4Child);
+ if (pelmLevel4Child->nameEquals("NATNetworks"))
+ readNATNetworks(*pelmLevel4Child);
+#ifdef VBOX_WITH_VMNET
+ if (pelmLevel4Child->nameEquals("HostOnlyNetworks"))
+ readHostOnlyNetworks(*pelmLevel4Child);
+#endif /* VBOX_WITH_VMNET */
+#ifdef VBOX_WITH_CLOUD_NET
+ if (pelmLevel4Child->nameEquals("CloudNetworks"))
+ readCloudNetworks(*pelmLevel4Child);
+#endif /* VBOX_WITH_CLOUD_NET */
+ }
+ }
+ else if (pelmGlobalChild->nameEquals("USBDeviceFilters"))
+ readUSBDeviceFilters(*pelmGlobalChild, host.llUSBDeviceFilters);
+ else if (pelmGlobalChild->nameEquals("USBDeviceSources"))
+ readUSBDeviceSources(*pelmGlobalChild, host.llUSBDeviceSources);
+ }
+ } // end if (pelmRootChild->nameEquals("Global"))
+ }
+
+ if (fCopyProxySettingsFromExtraData)
+ for (StringsMap::const_iterator it = mapExtraDataItems.begin(); it != mapExtraDataItems.end(); ++it)
+ if (it->first.equals("GUI/ProxySettings"))
+ {
+ convertGuiProxySettings(it->second);
+ break;
+ }
+
+ clearDocument();
+ }
+
+ // DHCP servers were introduced with settings version 1.7; if we're loading
+ // from an older version OR this is a fresh install, then add one DHCP server
+ // with default settings
+ if ( (!llDhcpServers.size())
+ && ( (!pstrFilename) // empty VirtualBox.xml file
+ || (m->sv < SettingsVersion_v1_7) // upgrading from before 1.7
+ )
+ )
+ {
+ DHCPServer srv;
+#ifdef RT_OS_WINDOWS
+ srv.strNetworkName = "HostInterfaceNetworking-VirtualBox Host-Only Ethernet Adapter";
+#else
+ srv.strNetworkName = "HostInterfaceNetworking-vboxnet0";
+#endif
+ srv.strIPAddress = "192.168.56.100";
+ srv.globalConfig.mapOptions[DHCPOption_SubnetMask] = DhcpOptValue("255.255.255.0");
+ srv.strIPLower = "192.168.56.101";
+ srv.strIPUpper = "192.168.56.254";
+ srv.fEnabled = true;
+ llDhcpServers.push_back(srv);
+ }
+}
+
+void MainConfigFile::bumpSettingsVersionIfNeeded()
+{
+#ifdef VBOX_WITH_VMNET
+ if (m->sv < SettingsVersion_v1_19)
+ {
+ // VirtualBox 7.0 adds support for host-only networks.
+ if (!llHostOnlyNetworks.empty())
+ m->sv = SettingsVersion_v1_19;
+ }
+#endif /* VBOX_WITH_VMNET */
+#ifdef VBOX_WITH_CLOUD_NET
+ if (m->sv < SettingsVersion_v1_18)
+ {
+ // VirtualBox 6.1 adds support for cloud networks.
+ if (!llCloudNetworks.empty())
+ m->sv = SettingsVersion_v1_18;
+ }
+#endif /* VBOX_WITH_CLOUD_NET */
+
+ if (m->sv < SettingsVersion_v1_16)
+ {
+ // VirtualBox 5.1 add support for additional USB device sources.
+ if (!host.llUSBDeviceSources.empty())
+ m->sv = SettingsVersion_v1_16;
+ }
+
+ if (m->sv < SettingsVersion_v1_14)
+ {
+ // VirtualBox 4.3 adds NAT networks.
+ if ( !llNATNetworks.empty())
+ m->sv = SettingsVersion_v1_14;
+ }
+}
+
+
+/**
+ * Called from the IVirtualBox interface to write out VirtualBox.xml. This
+ * builds an XML DOM tree and writes it out to disk.
+ */
+void MainConfigFile::write(const com::Utf8Str strFilename)
+{
+ bumpSettingsVersionIfNeeded();
+
+ m->strFilename = strFilename;
+ specialBackupIfFirstBump();
+ createStubDocument();
+
+ xml::ElementNode *pelmGlobal = m->pelmRoot->createChild("Global");
+
+ buildExtraData(*pelmGlobal, mapExtraDataItems);
+
+ xml::ElementNode *pelmMachineRegistry = pelmGlobal->createChild("MachineRegistry");
+ for (MachinesRegistry::const_iterator it = llMachines.begin();
+ it != llMachines.end();
+ ++it)
+ {
+ // <MachineEntry uuid="{5f102a55-a51b-48e3-b45a-b28d33469488}" src="/mnt/innotek-unix/vbox-machines/Windows 5.1 XP 1 (Office 2003)/Windows 5.1 XP 1 (Office 2003).xml"/>
+ const MachineRegistryEntry &mre = *it;
+ xml::ElementNode *pelmMachineEntry = pelmMachineRegistry->createChild("MachineEntry");
+ pelmMachineEntry->setAttribute("uuid", mre.uuid.toStringCurly());
+ pelmMachineEntry->setAttribute("src", mre.strSettingsFile);
+ }
+
+ buildMediaRegistry(*pelmGlobal, mediaRegistry);
+
+ xml::ElementNode *pelmNetServiceRegistry = pelmGlobal->createChild("NetserviceRegistry"); /** @todo r=bird: wrong capitalization of NetServiceRegistry. sigh. */
+ buildDHCPServers(*pelmNetServiceRegistry->createChild("DHCPServers"), llDhcpServers);
+
+ xml::ElementNode *pelmNATNetworks;
+ /* don't create entry if no NAT networks are registered. */
+ if (!llNATNetworks.empty())
+ {
+ pelmNATNetworks = pelmNetServiceRegistry->createChild("NATNetworks");
+ for (NATNetworksList::const_iterator it = llNATNetworks.begin();
+ it != llNATNetworks.end();
+ ++it)
+ {
+ const NATNetwork &n = *it;
+ xml::ElementNode *pelmThis = pelmNATNetworks->createChild("NATNetwork");
+ pelmThis->setAttribute("networkName", n.strNetworkName);
+ pelmThis->setAttribute("network", n.strIPv4NetworkCidr);
+ pelmThis->setAttribute("ipv6", n.fIPv6Enabled ? 1 : 0);
+ pelmThis->setAttribute("ipv6prefix", n.strIPv6Prefix);
+ pelmThis->setAttribute("advertiseDefaultIPv6Route", (n.fAdvertiseDefaultIPv6Route)? 1 : 0);
+ pelmThis->setAttribute("needDhcp", (n.fNeedDhcpServer) ? 1 : 0);
+ pelmThis->setAttribute("enabled", (n.fEnabled) ? 1 : 0); // too bad we chose 1 vs. 0 here
+ if (n.mapPortForwardRules4.size())
+ {
+ xml::ElementNode *pelmPf4 = pelmThis->createChild("PortForwarding4");
+ buildNATForwardRulesMap(*pelmPf4, n.mapPortForwardRules4);
+ }
+ if (n.mapPortForwardRules6.size())
+ {
+ xml::ElementNode *pelmPf6 = pelmThis->createChild("PortForwarding6");
+ buildNATForwardRulesMap(*pelmPf6, n.mapPortForwardRules6);
+ }
+
+ if (n.llHostLoopbackOffsetList.size())
+ {
+ xml::ElementNode *pelmMappings = pelmThis->createChild("Mappings");
+ buildNATLoopbacks(*pelmMappings, n.llHostLoopbackOffsetList);
+
+ }
+ }
+ }
+
+#ifdef VBOX_WITH_VMNET
+ xml::ElementNode *pelmHostOnlyNetworks;
+ /* don't create entry if no HostOnly networks are registered. */
+ if (!llHostOnlyNetworks.empty())
+ {
+ pelmHostOnlyNetworks = pelmNetServiceRegistry->createChild("HostOnlyNetworks");
+ for (HostOnlyNetworksList::const_iterator it = llHostOnlyNetworks.begin();
+ it != llHostOnlyNetworks.end();
+ ++it)
+ {
+ const HostOnlyNetwork &n = *it;
+ xml::ElementNode *pelmThis = pelmHostOnlyNetworks->createChild("HostOnlyNetwork");
+ pelmThis->setAttribute("name", n.strNetworkName);
+ pelmThis->setAttribute("mask", n.strNetworkMask);
+ pelmThis->setAttribute("ipLower", n.strIPLower);
+ pelmThis->setAttribute("ipUpper", n.strIPUpper);
+ pelmThis->setAttribute("id", n.uuid.toStringCurly());
+ pelmThis->setAttribute("enabled", (n.fEnabled) ? 1 : 0); // too bad we chose 1 vs. 0 here
+ }
+ }
+#endif /* VBOX_WITH_VMNET */
+#ifdef VBOX_WITH_CLOUD_NET
+ xml::ElementNode *pelmCloudNetworks;
+ /* don't create entry if no cloud networks are registered. */
+ if (!llCloudNetworks.empty())
+ {
+ pelmCloudNetworks = pelmNetServiceRegistry->createChild("CloudNetworks");
+ for (CloudNetworksList::const_iterator it = llCloudNetworks.begin();
+ it != llCloudNetworks.end();
+ ++it)
+ {
+ const CloudNetwork &n = *it;
+ xml::ElementNode *pelmThis = pelmCloudNetworks->createChild("CloudNetwork");
+ pelmThis->setAttribute("name", n.strNetworkName);
+ pelmThis->setAttribute("provider", n.strProviderShortName);
+ pelmThis->setAttribute("profile", n.strProfileName);
+ pelmThis->setAttribute("id", n.strNetworkId);
+ pelmThis->setAttribute("enabled", (n.fEnabled) ? 1 : 0); // too bad we chose 1 vs. 0 here
+ }
+ }
+#endif /* VBOX_WITH_CLOUD_NET */
+
+#ifdef VBOX_WITH_UPDATE_AGENT
+ /* We keep the updates configuration as part of the host for now, as the API exposes the IHost::updateHost attribute,
+ * but use an own "Updates" branch in the XML for better structurizing stuff in the future. */
+ UpdateAgent &updateHost = host.updateHost;
+
+ xml::ElementNode *pelmUpdates = pelmGlobal->createChild("Updates");
+ /* Global enabled switch for updates. Currently bound to host updates, as this is the only update we have so far. */
+ pelmUpdates->setAttribute("enabled", updateHost.fEnabled);
+
+ xml::ElementNode *pelmUpdateHost = pelmUpdates->createChild("Host");
+ pelmUpdateHost->setAttribute("enabled", updateHost.fEnabled);
+ pelmUpdateHost->setAttribute("channel", (int32_t)updateHost.enmChannel);
+ pelmUpdateHost->setAttribute("checkFreqSec", updateHost.uCheckFreqSeconds);
+ if (updateHost.strRepoUrl.length())
+ pelmUpdateHost->setAttribute("repoUrl", updateHost.strRepoUrl);
+ if (updateHost.strLastCheckDate.length())
+ pelmUpdateHost->setAttribute("lastCheckDate", updateHost.strLastCheckDate);
+ pelmUpdateHost->setAttribute("checkCount", updateHost.uCheckCount);
+ /** @todo Add update settings for ExtPack and Guest Additions here later. See @bugref{7983}. */
+#endif
+
+ xml::ElementNode *pelmSysProps = pelmGlobal->createChild("SystemProperties");
+ if (systemProperties.strDefaultMachineFolder.length())
+ pelmSysProps->setAttribute("defaultMachineFolder", systemProperties.strDefaultMachineFolder);
+ if (systemProperties.strLoggingLevel.length())
+ pelmSysProps->setAttribute("LoggingLevel", systemProperties.strLoggingLevel);
+ if (systemProperties.strDefaultHardDiskFormat.length())
+ pelmSysProps->setAttribute("defaultHardDiskFormat", systemProperties.strDefaultHardDiskFormat);
+ if (systemProperties.strVRDEAuthLibrary.length())
+ pelmSysProps->setAttribute("VRDEAuthLibrary", systemProperties.strVRDEAuthLibrary);
+ if (systemProperties.strWebServiceAuthLibrary.length())
+ pelmSysProps->setAttribute("webServiceAuthLibrary", systemProperties.strWebServiceAuthLibrary);
+ if (systemProperties.strDefaultVRDEExtPack.length())
+ pelmSysProps->setAttribute("defaultVRDEExtPack", systemProperties.strDefaultVRDEExtPack);
+ if (systemProperties.strDefaultCryptoExtPack.length())
+ pelmSysProps->setAttribute("defaultCryptoExtPack", systemProperties.strDefaultCryptoExtPack);
+ pelmSysProps->setAttribute("LogHistoryCount", systemProperties.uLogHistoryCount);
+ if (systemProperties.strAutostartDatabasePath.length())
+ pelmSysProps->setAttribute("autostartDatabasePath", systemProperties.strAutostartDatabasePath);
+ if (systemProperties.strDefaultFrontend.length())
+ pelmSysProps->setAttribute("defaultFrontend", systemProperties.strDefaultFrontend);
+ if (systemProperties.strProxyUrl.length())
+ pelmSysProps->setAttribute("proxyUrl", systemProperties.strProxyUrl);
+ pelmSysProps->setAttribute("proxyMode", systemProperties.uProxyMode);
+ pelmSysProps->setAttribute("exclusiveHwVirt", systemProperties.fExclusiveHwVirt);
+ if (systemProperties.strLanguageId.isNotEmpty())
+ pelmSysProps->setAttribute("LanguageId", systemProperties.strLanguageId);
+
+ buildUSBDeviceFilters(*pelmGlobal->createChild("USBDeviceFilters"),
+ host.llUSBDeviceFilters,
+ true); // fHostMode
+
+ if (!host.llUSBDeviceSources.empty())
+ buildUSBDeviceSources(*pelmGlobal->createChild("USBDeviceSources"),
+ host.llUSBDeviceSources);
+
+ // now go write the XML
+ xml::XmlFileWriter writer(*m->pDoc);
+ writer.write(m->strFilename.c_str(), true /*fSafe*/);
+
+ m->fFileExists = true;
+
+ clearDocument();
+ LogRel(("Finished saving settings file \"%s\"\n", m->strFilename.c_str()));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Machine XML structures
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+VRDESettings::VRDESettings() :
+ fEnabled(true), // default for old VMs, for new ones it's false
+ authType(AuthType_Null),
+ ulAuthTimeout(5000),
+ fAllowMultiConnection(false),
+ fReuseSingleConnection(false)
+{
+}
+
+/**
+ * Check if all settings have default values.
+ */
+bool VRDESettings::areDefaultSettings(SettingsVersion_T sv) const
+{
+ return (sv < SettingsVersion_v1_16 ? fEnabled : !fEnabled)
+ && authType == AuthType_Null
+ && (ulAuthTimeout == 5000 || ulAuthTimeout == 0)
+ && strAuthLibrary.isEmpty()
+ && !fAllowMultiConnection
+ && !fReuseSingleConnection
+ && strVrdeExtPack.isEmpty()
+ && mapProperties.size() == 0;
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool VRDESettings::operator==(const VRDESettings& v) const
+{
+ return (this == &v)
+ || ( fEnabled == v.fEnabled
+ && authType == v.authType
+ && ulAuthTimeout == v.ulAuthTimeout
+ && strAuthLibrary == v.strAuthLibrary
+ && fAllowMultiConnection == v.fAllowMultiConnection
+ && fReuseSingleConnection == v.fReuseSingleConnection
+ && strVrdeExtPack == v.strVrdeExtPack
+ && mapProperties == v.mapProperties);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+BIOSSettings::BIOSSettings() :
+ fACPIEnabled(true),
+ fIOAPICEnabled(false),
+ fLogoFadeIn(true),
+ fLogoFadeOut(true),
+ fPXEDebugEnabled(false),
+ fSmbiosUuidLittleEndian(true),
+ ulLogoDisplayTime(0),
+ biosBootMenuMode(BIOSBootMenuMode_MessageAndMenu),
+ apicMode(APICMode_APIC),
+ llTimeOffset(0)
+{
+}
+
+/**
+ * Check if all settings have default values.
+ */
+bool BIOSSettings::areDefaultSettings() const
+{
+ return fACPIEnabled
+ && !fIOAPICEnabled
+ && fLogoFadeIn
+ && fLogoFadeOut
+ && !fPXEDebugEnabled
+ && !fSmbiosUuidLittleEndian
+ && ulLogoDisplayTime == 0
+ && biosBootMenuMode == BIOSBootMenuMode_MessageAndMenu
+ && apicMode == APICMode_APIC
+ && llTimeOffset == 0
+ && strLogoImagePath.isEmpty();
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool BIOSSettings::operator==(const BIOSSettings &d) const
+{
+ return (this == &d)
+ || ( fACPIEnabled == d.fACPIEnabled
+ && fIOAPICEnabled == d.fIOAPICEnabled
+ && fLogoFadeIn == d.fLogoFadeIn
+ && fLogoFadeOut == d.fLogoFadeOut
+ && fPXEDebugEnabled == d.fPXEDebugEnabled
+ && fSmbiosUuidLittleEndian == d.fSmbiosUuidLittleEndian
+ && ulLogoDisplayTime == d.ulLogoDisplayTime
+ && biosBootMenuMode == d.biosBootMenuMode
+ && apicMode == d.apicMode
+ && llTimeOffset == d.llTimeOffset
+ && strLogoImagePath == d.strLogoImagePath);
+}
+
+RecordingScreenSettings::RecordingScreenSettings(uint32_t a_idScreen /* = UINT32_MAX */)
+ : idScreen(a_idScreen)
+{
+ applyDefaults();
+}
+
+RecordingScreenSettings::~RecordingScreenSettings()
+{
+
+}
+
+/**
+ * Returns the default options string for screen recording settings.
+ *
+ * @returns Default options string for a given screen.
+ */
+/* static */
+const char *RecordingScreenSettings::getDefaultOptions(void)
+{
+ /* Note: Needs to be kept in sync with FE/Qt's UIMachineSettingsDisplay::putToCache()! */
+ return "vc_enabled=true,ac_enabled=false,ac_profile=med";
+}
+
+/**
+ * Returns a recording settings feature map from a given string.
+ *
+ * @returns VBox status code.
+ * @param strFeatures String of features to convert.
+ * @param featureMap Where to return the converted features on success.
+ */
+/* static */
+int RecordingScreenSettings::featuresFromString(const com::Utf8Str &strFeatures, RecordingFeatureMap &featureMap)
+{
+ featureMap.clear();
+
+ RTCList<RTCString> lstFeatures = strFeatures.split(" ");
+ for (size_t i = 0; i < lstFeatures.size(); i++)
+ {
+ if (lstFeatures.at(i).compare("video", RTCString::CaseInsensitive) == 0)
+ featureMap[RecordingFeature_Video] = true;
+ else if (lstFeatures.at(i).compare("audio", RTCString::CaseInsensitive) == 0)
+ featureMap[RecordingFeature_Audio] = true;
+ /* ignore everything else */
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Converts a feature map to a serializable string.
+ *
+ * @param featureMap Feature map to convert.
+ * @param strFeatures Where to return the features converted as a string.
+ */
+/* static */
+void RecordingScreenSettings::featuresToString(const RecordingFeatureMap &featureMap, com::Utf8Str &strFeatures)
+{
+ strFeatures = "";
+
+ RecordingFeatureMap::const_iterator itFeature = featureMap.begin();
+ while (itFeature != featureMap.end())
+ {
+ if (itFeature->first == RecordingFeature_Video && itFeature->second)
+ strFeatures += "video ";
+ if (itFeature->first == RecordingFeature_Audio && itFeature->second)
+ strFeatures += "audio ";
+ ++itFeature;
+ }
+ strFeatures.strip();
+}
+
+/**
+ * Returns a recording settings audio codec from a given string.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NOT_SUPPORTED if audio codec is invalid or not supported.
+ * @param strCodec String that contains the codec name.
+ * @param enmCodec Where to return the audio codec on success.
+ *
+ * @note An empty string will return "none" (no codec).
+ */
+/* static */
+int RecordingScreenSettings::audioCodecFromString(const com::Utf8Str &strCodec, RecordingAudioCodec_T &enmCodec)
+{
+ if ( RTStrIStr(strCodec.c_str(), "none")
+ || strCodec.isEmpty())
+ {
+ enmCodec = RecordingAudioCodec_None;
+ return VINF_SUCCESS;
+ }
+ else if (RTStrIStr(strCodec.c_str(), "wav"))
+ {
+ enmCodec = RecordingAudioCodec_WavPCM;
+ return VINF_SUCCESS;
+ }
+ else if (RTStrIStr(strCodec.c_str(), "mp3"))
+ {
+ enmCodec = RecordingAudioCodec_MP3;
+ return VINF_SUCCESS;
+ }
+ else if (RTStrIStr(strCodec.c_str(), "opus"))
+ {
+ enmCodec = RecordingAudioCodec_Opus;
+ return VINF_SUCCESS;
+ }
+ else if (RTStrIStr(strCodec.c_str(), "vorbis"))
+ {
+ enmCodec = RecordingAudioCodec_OggVorbis;
+ return VINF_SUCCESS;
+ }
+
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+/**
+ * Converts an audio codec to a serializable string.
+ *
+ * @param enmCodec Codec to convert to a string.
+ * @param strCodec Where to return the audio codec converted as a string.
+ */
+/* static */
+void RecordingScreenSettings::audioCodecToString(const RecordingAudioCodec_T &enmCodec, com::Utf8Str &strCodec)
+{
+ switch (enmCodec)
+ {
+ case RecordingAudioCodec_None: strCodec = "none"; return;
+ case RecordingAudioCodec_WavPCM: strCodec = "wav"; return;
+ case RecordingAudioCodec_MP3: strCodec = "mp3"; return;
+ case RecordingAudioCodec_Opus: strCodec = "opus"; return;
+ case RecordingAudioCodec_OggVorbis: strCodec = "vorbis"; return;
+ default: AssertFailedReturnVoid();
+ }
+}
+
+/**
+ * Returns a recording settings video codec from a given string.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NOT_SUPPORTED if video codec is invalid or not supported.
+ * @param strCodec String that contains the codec name.
+ * @param enmCodec Where to return the video codec on success.
+ *
+ * @note An empty string will return "none" (no codec).
+ */
+/* static */
+int RecordingScreenSettings::videoCodecFromString(const com::Utf8Str &strCodec, RecordingVideoCodec_T &enmCodec)
+{
+ if ( RTStrIStr(strCodec.c_str(), "none")
+ || strCodec.isEmpty())
+ {
+ enmCodec = RecordingVideoCodec_None;
+ return VINF_SUCCESS;
+ }
+ else if (RTStrIStr(strCodec.c_str(), "MJPEG"))
+ {
+ enmCodec = RecordingVideoCodec_MJPEG;
+ return VINF_SUCCESS;
+ }
+ else if (RTStrIStr(strCodec.c_str(), "H262"))
+ {
+ enmCodec = RecordingVideoCodec_H262;
+ return VINF_SUCCESS;
+ }
+ else if (RTStrIStr(strCodec.c_str(), "H264"))
+ {
+ enmCodec = RecordingVideoCodec_H264;
+ return VINF_SUCCESS;
+ }
+ else if (RTStrIStr(strCodec.c_str(), "H265"))
+ {
+ enmCodec = RecordingVideoCodec_H265;
+ return VINF_SUCCESS;
+ }
+ else if (RTStrIStr(strCodec.c_str(), "H266"))
+ {
+ enmCodec = RecordingVideoCodec_H266;
+ return VINF_SUCCESS;
+ }
+ else if (RTStrIStr(strCodec.c_str(), "VP8"))
+ {
+ enmCodec = RecordingVideoCodec_VP8;
+ return VINF_SUCCESS;
+ }
+ else if (RTStrIStr(strCodec.c_str(), "VP9"))
+ {
+ enmCodec = RecordingVideoCodec_VP9;
+ return VINF_SUCCESS;
+ }
+ else if (RTStrIStr(strCodec.c_str(), "AV1"))
+ {
+ enmCodec = RecordingVideoCodec_AV1;
+ return VINF_SUCCESS;
+ }
+ else if (RTStrIStr(strCodec.c_str(), "other"))
+ {
+ enmCodec = RecordingVideoCodec_Other;
+ return VINF_SUCCESS;
+ }
+
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+/**
+ * Converts a video codec to a serializable string.
+ *
+ * @param enmCodec Codec to convert to a string.
+ * @param strCodec Where to return the video codec converted as a string.
+ */
+/* static */
+void RecordingScreenSettings::videoCodecToString(const RecordingVideoCodec_T &enmCodec, com::Utf8Str &strCodec)
+{
+ switch (enmCodec)
+ {
+ case RecordingVideoCodec_None: strCodec = "none"; return;
+ case RecordingVideoCodec_MJPEG: strCodec = "MJPEG"; return;
+ case RecordingVideoCodec_H262: strCodec = "H262"; return;
+ case RecordingVideoCodec_H264: strCodec = "H264"; return;
+ case RecordingVideoCodec_H265: strCodec = "H265"; return;
+ case RecordingVideoCodec_H266: strCodec = "H266"; return;
+ case RecordingVideoCodec_VP8: strCodec = "VP8"; return;
+ case RecordingVideoCodec_VP9: strCodec = "VP9"; return;
+ case RecordingVideoCodec_AV1: strCodec = "AV1"; return;
+ case RecordingVideoCodec_Other: strCodec = "other"; return;
+ default: AssertFailedReturnVoid();
+ }
+}
+
+/**
+ * Applies the default settings.
+ */
+void RecordingScreenSettings::applyDefaults(void)
+{
+ /*
+ * Set sensible defaults.
+ */
+
+ /*
+ * Enable screen 0 by default.
+ * Otherwise enabling recording without any screen enabled at all makes no sense.
+ *
+ * Note: When tweaking this, make sure to also alter RecordingScreenSettings::areDefaultSettings().
+ */
+ fEnabled = idScreen == 0 ? true : false;;
+ enmDest = RecordingDestination_File;
+ ulMaxTimeS = 0;
+ strOptions = RecordingScreenSettings::getDefaultOptions();
+ File.ulMaxSizeMB = 0;
+ File.strName = "";
+ Video.enmCodec = RecordingVideoCodec_VP8;
+ Video.enmDeadline = RecordingCodecDeadline_Default;
+ Video.enmRateCtlMode = RecordingRateControlMode_VBR;
+ Video.enmScalingMode = RecordingVideoScalingMode_None;
+ Video.ulWidth = 1024;
+ Video.ulHeight = 768;
+ Video.ulRate = 512;
+ Video.ulFPS = 25;
+#ifdef VBOX_WITH_AUDIO_RECORDING
+# if defined(VBOX_WITH_LIBVORBIS)
+ Audio.enmCodec = RecordingAudioCodec_OggVorbis;
+# else
+ Audio.enmCodec = RecordingAudioCodec_None;
+# endif
+#else
+ Audio.enmCodec = RecordingAudioCodec_None;
+#endif /* VBOX_WITH_RECORDING */
+ Audio.enmDeadline = RecordingCodecDeadline_Default;
+ Audio.enmRateCtlMode = RecordingRateControlMode_VBR;
+ Audio.cBits = 16;
+ Audio.cChannels = 2;
+ Audio.uHz = 22050;
+
+ featureMap[RecordingFeature_Video] = true;
+ featureMap[RecordingFeature_Audio] = false; /** @todo Audio is not yet enabled by default. */
+}
+
+/**
+ * Check if all settings have default values.
+ *
+ * @returns @c true if default, @c false if not.
+ */
+bool RecordingScreenSettings::areDefaultSettings(void) const
+{
+ return ( fEnabled == false
+ /* Screen 0 is special: There we ALWAYS enable recording by default. */
+ || ( idScreen == 0
+ && fEnabled == true)
+ )
+ && enmDest == RecordingDestination_File
+ && ulMaxTimeS == 0
+ && strOptions == RecordingScreenSettings::getDefaultOptions()
+ && File.ulMaxSizeMB == 0
+ && File.strName == ""
+ && Video.enmCodec == RecordingVideoCodec_VP8
+ && Video.enmDeadline == RecordingCodecDeadline_Default
+ && Video.enmRateCtlMode == RecordingRateControlMode_VBR
+ && Video.enmScalingMode == RecordingVideoScalingMode_None
+ && Video.ulWidth == 1024
+ && Video.ulHeight == 768
+ && Video.ulRate == 512
+ && Video.ulFPS == 25
+#ifdef VBOX_WITH_AUDIO_RECORDING
+# if defined(VBOX_WITH_LIBVORBIS)
+ && Audio.enmCodec == RecordingAudioCodec_OggVorbis
+# else
+ && Audio.enmCodec == RecordingAudioCodec_None
+# endif
+#else
+ && Audio.enmCodec == RecordingAudioCodec_None
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+ && Audio.enmDeadline == RecordingCodecDeadline_Default
+ && Audio.enmRateCtlMode == RecordingRateControlMode_VBR
+ && Audio.cBits == 16
+ && Audio.cChannels == 2
+ && Audio.uHz == 22050
+ && featureMap.find(RecordingFeature_Video)->second == true
+ && featureMap.find(RecordingFeature_Audio)->second == false;
+}
+
+/**
+ * Returns if a certain recording feature is enabled or not.
+ *
+ * @returns @c true if the feature is enabled, @c false if not.
+ * @param enmFeature Feature to check.
+ */
+bool RecordingScreenSettings::isFeatureEnabled(RecordingFeature_T enmFeature) const
+{
+ RecordingFeatureMap::const_iterator itFeature = featureMap.find(enmFeature);
+ if (itFeature != featureMap.end())
+ return itFeature->second;
+
+ return false;
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool RecordingScreenSettings::operator==(const RecordingScreenSettings &d) const
+{
+ return fEnabled == d.fEnabled
+ && enmDest == d.enmDest
+ && featureMap == d.featureMap
+ && ulMaxTimeS == d.ulMaxTimeS
+ && strOptions == d.strOptions
+ && File.strName == d.File.strName
+ && File.ulMaxSizeMB == d.File.ulMaxSizeMB
+ && Video.enmCodec == d.Video.enmCodec
+ && Video.enmDeadline == d.Video.enmDeadline
+ && Video.enmRateCtlMode == d.Video.enmRateCtlMode
+ && Video.enmScalingMode == d.Video.enmScalingMode
+ && Video.ulWidth == d.Video.ulWidth
+ && Video.ulHeight == d.Video.ulHeight
+ && Video.ulRate == d.Video.ulRate
+ && Video.ulFPS == d.Video.ulFPS
+ && Audio.enmCodec == d.Audio.enmCodec
+ && Audio.enmDeadline == d.Audio.enmDeadline
+ && Audio.enmRateCtlMode == d.Audio.enmRateCtlMode
+ && Audio.cBits == d.Audio.cBits
+ && Audio.cChannels == d.Audio.cChannels
+ && Audio.uHz == d.Audio.uHz
+ && featureMap == d.featureMap;
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+RecordingCommonSettings::RecordingCommonSettings()
+{
+ applyDefaults();
+}
+
+/**
+ * Applies the default settings.
+ */
+void RecordingCommonSettings::applyDefaults(void)
+{
+ fEnabled = false;
+}
+
+/**
+ * Check if all settings have default values.
+ */
+bool RecordingCommonSettings::areDefaultSettings(void) const
+{
+ return fEnabled == false;
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool RecordingCommonSettings::operator==(const RecordingCommonSettings &d) const
+{
+ if (this == &d)
+ return true;
+
+ return fEnabled == d.fEnabled;
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+RecordingSettings::RecordingSettings()
+{
+ applyDefaults();
+}
+
+/**
+ * Applies the default settings.
+ */
+void RecordingSettings::applyDefaults(void)
+{
+ common.applyDefaults();
+
+ mapScreens.clear();
+
+ try
+ {
+ /* Always add screen 0 to the default configuration. */
+ RecordingScreenSettings screenSettings(0 /* Screen ID */);
+
+ mapScreens[0 /* Screen ID */] = screenSettings;
+ }
+ catch (std::bad_alloc &)
+ {
+ AssertFailed();
+ }
+}
+
+/**
+ * Check if all settings have default values.
+ */
+bool RecordingSettings::areDefaultSettings(void) const
+{
+ AssertReturn(mapScreens.size() >= 1, false); /* The first screen always must be present. */
+
+ if (!common.areDefaultSettings())
+ return false;
+
+ RecordingScreenSettingsMap::const_iterator itScreen = mapScreens.begin();
+ while (itScreen != mapScreens.end())
+ {
+ if (!itScreen->second.areDefaultSettings())
+ return false;
+ ++itScreen;
+ }
+
+ return true;
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool RecordingSettings::operator==(const RecordingSettings &that) const
+{
+ if (this == &that) /* If pointers match, take a shortcut. */
+ return true;
+
+ if (common == that.common)
+ {
+ /* Too lazy for a != operator. */
+ }
+ else
+ return false;
+
+ if (mapScreens.size() != that.mapScreens.size())
+ return false;
+
+ RecordingScreenSettingsMap::const_iterator itScreen = mapScreens.begin();
+ RecordingScreenSettingsMap::const_iterator itScreenThat = that.mapScreens.begin();
+ while ( itScreen != mapScreens.end()
+ && itScreenThat != that.mapScreens.end())
+ {
+ if (itScreen->second == itScreenThat->second)
+ {
+ /* Nothing to do in here (yet). */
+ }
+ else
+ return false;
+
+ ++itScreen;
+ ++itScreenThat;
+ }
+
+ return true;
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+GraphicsAdapter::GraphicsAdapter() :
+ graphicsControllerType(GraphicsControllerType_VBoxVGA),
+ ulVRAMSizeMB(8),
+ cMonitors(1),
+ fAccelerate3D(false),
+ fAccelerate2DVideo(false)
+{
+}
+
+/**
+ * Check if all settings have default values.
+ */
+bool GraphicsAdapter::areDefaultSettings() const
+{
+ return graphicsControllerType == GraphicsControllerType_VBoxVGA
+ && ulVRAMSizeMB == 8
+ && cMonitors <= 1
+ && !fAccelerate3D
+ && !fAccelerate2DVideo;
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool GraphicsAdapter::operator==(const GraphicsAdapter &g) const
+{
+ return (this == &g)
+ || ( graphicsControllerType == g.graphicsControllerType
+ && ulVRAMSizeMB == g.ulVRAMSizeMB
+ && cMonitors == g.cMonitors
+ && fAccelerate3D == g.fAccelerate3D
+ && fAccelerate2DVideo == g.fAccelerate2DVideo);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+TpmSettings::TpmSettings() :
+ tpmType(TpmType_None)
+{
+}
+
+/**
+ * Check if all settings have default values.
+ */
+bool TpmSettings::areDefaultSettings() const
+{
+ return tpmType == TpmType_None
+ && strLocation.isEmpty();
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool TpmSettings::operator==(const TpmSettings &g) const
+{
+ return (this == &g)
+ || ( tpmType == g.tpmType
+ && strLocation == g.strLocation);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+NvramSettings::NvramSettings()
+{
+}
+
+/**
+ * Check if all settings have default values.
+ */
+bool NvramSettings::areDefaultSettings() const
+{
+ return strNvramPath.isEmpty()
+ && strKeyId.isEmpty()
+ && strKeyStore.isEmpty();
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool NvramSettings::operator==(const NvramSettings &g) const
+{
+ return (this == &g)
+ || (strNvramPath == g.strNvramPath)
+ || (strKeyId == g.strKeyId)
+ || (strKeyStore == g.strKeyStore);
+}
+
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+USBController::USBController() :
+ enmType(USBControllerType_Null)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool USBController::operator==(const USBController &u) const
+{
+ return (this == &u)
+ || ( strName == u.strName
+ && enmType == u.enmType);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+USB::USB()
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool USB::operator==(const USB &u) const
+{
+ return (this == &u)
+ || ( llUSBControllers == u.llUSBControllers
+ && llDeviceFilters == u.llDeviceFilters);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+NAT::NAT() :
+ u32Mtu(0),
+ u32SockRcv(0),
+ u32SockSnd(0),
+ u32TcpRcv(0),
+ u32TcpSnd(0),
+ fDNSPassDomain(true), /* historically this value is true */
+ fDNSProxy(false),
+ fDNSUseHostResolver(false),
+ fAliasLog(false),
+ fAliasProxyOnly(false),
+ fAliasUseSamePorts(false),
+ fLocalhostReachable(true) /* Historically this value is true. */
+{
+}
+
+/**
+ * Check if all DNS settings have default values.
+ */
+bool NAT::areDNSDefaultSettings() const
+{
+ return fDNSPassDomain && !fDNSProxy && !fDNSUseHostResolver;
+}
+
+/**
+ * Check if all Alias settings have default values.
+ */
+bool NAT::areAliasDefaultSettings() const
+{
+ return !fAliasLog && !fAliasProxyOnly && !fAliasUseSamePorts;
+}
+
+/**
+ * Check if all TFTP settings have default values.
+ */
+bool NAT::areTFTPDefaultSettings() const
+{
+ return strTFTPPrefix.isEmpty()
+ && strTFTPBootFile.isEmpty()
+ && strTFTPNextServer.isEmpty();
+}
+
+/**
+ * Check whether the localhost-reachable setting is the default for the given settings version.
+ */
+bool NAT::areLocalhostReachableDefaultSettings(SettingsVersion_T sv) const
+{
+ return ( fLocalhostReachable
+ && sv < SettingsVersion_v1_19)
+ || ( !fLocalhostReachable
+ && sv >= SettingsVersion_v1_19);
+}
+
+/**
+ * Check if all settings have default values.
+ */
+bool NAT::areDefaultSettings(SettingsVersion_T sv) const
+{
+ /*
+ * Before settings version 1.19 localhost was reachable by default
+ * when using NAT which was changed with version 1.19+, see @bugref{9896}
+ * for more information.
+ */
+ return strNetwork.isEmpty()
+ && strBindIP.isEmpty()
+ && u32Mtu == 0
+ && u32SockRcv == 0
+ && u32SockSnd == 0
+ && u32TcpRcv == 0
+ && u32TcpSnd == 0
+ && areDNSDefaultSettings()
+ && areAliasDefaultSettings()
+ && areTFTPDefaultSettings()
+ && mapRules.size() == 0
+ && areLocalhostReachableDefaultSettings(sv);
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool NAT::operator==(const NAT &n) const
+{
+ return (this == &n)
+ || ( strNetwork == n.strNetwork
+ && strBindIP == n.strBindIP
+ && u32Mtu == n.u32Mtu
+ && u32SockRcv == n.u32SockRcv
+ && u32SockSnd == n.u32SockSnd
+ && u32TcpSnd == n.u32TcpSnd
+ && u32TcpRcv == n.u32TcpRcv
+ && strTFTPPrefix == n.strTFTPPrefix
+ && strTFTPBootFile == n.strTFTPBootFile
+ && strTFTPNextServer == n.strTFTPNextServer
+ && fDNSPassDomain == n.fDNSPassDomain
+ && fDNSProxy == n.fDNSProxy
+ && fDNSUseHostResolver == n.fDNSUseHostResolver
+ && fAliasLog == n.fAliasLog
+ && fAliasProxyOnly == n.fAliasProxyOnly
+ && fAliasUseSamePorts == n.fAliasUseSamePorts
+ && fLocalhostReachable == n.fLocalhostReachable
+ && mapRules == n.mapRules);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+NetworkAdapter::NetworkAdapter() :
+ ulSlot(0),
+ type(NetworkAdapterType_Am79C970A), // default for old VMs, for new ones it's Am79C973
+ fEnabled(false),
+ fCableConnected(false), // default for old VMs, for new ones it's true
+ ulLineSpeed(0),
+ enmPromiscModePolicy(NetworkAdapterPromiscModePolicy_Deny),
+ fTraceEnabled(false),
+ mode(NetworkAttachmentType_Null),
+ ulBootPriority(0)
+{
+}
+
+/**
+ * Check if all Generic Driver settings have default values.
+ */
+bool NetworkAdapter::areGenericDriverDefaultSettings() const
+{
+ return strGenericDriver.isEmpty()
+ && genericProperties.size() == 0;
+}
+
+/**
+ * Check if all settings have default values.
+ */
+bool NetworkAdapter::areDefaultSettings(SettingsVersion_T sv) const
+{
+ // 5.0 and earlier had a default of fCableConnected=false, which doesn't
+ // make a lot of sense (but it's a fact). Later versions don't save the
+ // setting if it's at the default value and thus must get it right.
+ return !fEnabled
+ && strMACAddress.isEmpty()
+ && ( (sv >= SettingsVersion_v1_16 && fCableConnected && type == NetworkAdapterType_Am79C973)
+ || (sv < SettingsVersion_v1_16 && !fCableConnected && type == NetworkAdapterType_Am79C970A))
+ && ulLineSpeed == 0
+ && enmPromiscModePolicy == NetworkAdapterPromiscModePolicy_Deny
+ && mode == NetworkAttachmentType_Null
+ && nat.areDefaultSettings(sv)
+ && strBridgedName.isEmpty()
+ && strInternalNetworkName.isEmpty()
+#ifdef VBOX_WITH_VMNET
+ && strHostOnlyNetworkName.isEmpty()
+#endif /* VBOX_WITH_VMNET */
+#ifdef VBOX_WITH_CLOUD_NET
+ && strCloudNetworkName.isEmpty()
+#endif /* VBOX_WITH_CLOUD_NET */
+ && strHostOnlyName.isEmpty()
+ && areGenericDriverDefaultSettings()
+ && strNATNetworkName.isEmpty();
+}
+
+/**
+ * Special check if settings of the non-current attachment type have default values.
+ */
+bool NetworkAdapter::areDisabledDefaultSettings(SettingsVersion_T sv) const
+{
+ return (mode != NetworkAttachmentType_NAT ? nat.areDefaultSettings(sv) : true)
+ && (mode != NetworkAttachmentType_Bridged ? strBridgedName.isEmpty() : true)
+ && (mode != NetworkAttachmentType_Internal ? strInternalNetworkName.isEmpty() : true)
+#ifdef VBOX_WITH_VMNET
+ && (mode != NetworkAttachmentType_HostOnlyNetwork ? strHostOnlyNetworkName.isEmpty() : true)
+#endif /* VBOX_WITH_VMNET */
+#ifdef VBOX_WITH_CLOUD_NET
+ && (mode != NetworkAttachmentType_Cloud ? strCloudNetworkName.isEmpty() : true)
+#endif /* VBOX_WITH_CLOUD_NET */
+ && (mode != NetworkAttachmentType_HostOnly ? strHostOnlyName.isEmpty() : true)
+ && (mode != NetworkAttachmentType_Generic ? areGenericDriverDefaultSettings() : true)
+ && (mode != NetworkAttachmentType_NATNetwork ? strNATNetworkName.isEmpty() : true);
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool NetworkAdapter::operator==(const NetworkAdapter &n) const
+{
+ return (this == &n)
+ || ( ulSlot == n.ulSlot
+ && type == n.type
+ && fEnabled == n.fEnabled
+ && strMACAddress == n.strMACAddress
+ && fCableConnected == n.fCableConnected
+ && ulLineSpeed == n.ulLineSpeed
+ && enmPromiscModePolicy == n.enmPromiscModePolicy
+ && fTraceEnabled == n.fTraceEnabled
+ && strTraceFile == n.strTraceFile
+ && mode == n.mode
+ && nat == n.nat
+ && strBridgedName == n.strBridgedName
+ && strHostOnlyName == n.strHostOnlyName
+#ifdef VBOX_WITH_VMNET
+ && strHostOnlyNetworkName == n.strHostOnlyNetworkName
+#endif /* VBOX_WITH_VMNET */
+ && strInternalNetworkName == n.strInternalNetworkName
+#ifdef VBOX_WITH_CLOUD_NET
+ && strCloudNetworkName == n.strCloudNetworkName
+#endif /* VBOX_WITH_CLOUD_NET */
+ && strGenericDriver == n.strGenericDriver
+ && genericProperties == n.genericProperties
+ && ulBootPriority == n.ulBootPriority
+ && strBandwidthGroup == n.strBandwidthGroup);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+SerialPort::SerialPort() :
+ ulSlot(0),
+ fEnabled(false),
+ ulIOBase(0x3f8),
+ ulIRQ(4),
+ portMode(PortMode_Disconnected),
+ fServer(false),
+ uartType(UartType_U16550A)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool SerialPort::operator==(const SerialPort &s) const
+{
+ return (this == &s)
+ || ( ulSlot == s.ulSlot
+ && fEnabled == s.fEnabled
+ && ulIOBase == s.ulIOBase
+ && ulIRQ == s.ulIRQ
+ && portMode == s.portMode
+ && strPath == s.strPath
+ && fServer == s.fServer
+ && uartType == s.uartType);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+ParallelPort::ParallelPort() :
+ ulSlot(0),
+ fEnabled(false),
+ ulIOBase(0x378),
+ ulIRQ(7)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool ParallelPort::operator==(const ParallelPort &s) const
+{
+ return (this == &s)
+ || ( ulSlot == s.ulSlot
+ && fEnabled == s.fEnabled
+ && ulIOBase == s.ulIOBase
+ && ulIRQ == s.ulIRQ
+ && strPath == s.strPath);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+AudioAdapter::AudioAdapter() :
+ fEnabled(true), // default for old VMs, for new ones it's false
+ fEnabledIn(true), // default for old VMs, for new ones it's false
+ fEnabledOut(true), // default for old VMs, for new ones it's false
+ controllerType(AudioControllerType_AC97),
+ codecType(AudioCodecType_STAC9700),
+ driverType(AudioDriverType_Null)
+{
+}
+
+/**
+ * Check if all settings have default values.
+ */
+bool AudioAdapter::areDefaultSettings(SettingsVersion_T sv) const
+{
+ return (sv < SettingsVersion_v1_16 ? false : !fEnabled)
+ && (sv <= SettingsVersion_v1_16 ? fEnabledIn : !fEnabledIn)
+ && (sv <= SettingsVersion_v1_16 ? fEnabledOut : !fEnabledOut)
+ && fEnabledOut == true
+ && controllerType == AudioControllerType_AC97
+ && codecType == AudioCodecType_STAC9700
+ && properties.size() == 0;
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool AudioAdapter::operator==(const AudioAdapter &a) const
+{
+ return (this == &a)
+ || ( fEnabled == a.fEnabled
+ && fEnabledIn == a.fEnabledIn
+ && fEnabledOut == a.fEnabledOut
+ && controllerType == a.controllerType
+ && codecType == a.codecType
+ && driverType == a.driverType
+ && properties == a.properties);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+SharedFolder::SharedFolder() :
+ fWritable(false),
+ fAutoMount(false)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool SharedFolder::operator==(const SharedFolder &g) const
+{
+ return (this == &g)
+ || ( strName == g.strName
+ && strHostPath == g.strHostPath
+ && fWritable == g.fWritable
+ && fAutoMount == g.fAutoMount
+ && strAutoMountPoint == g.strAutoMountPoint);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+GuestProperty::GuestProperty() :
+ timestamp(0)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool GuestProperty::operator==(const GuestProperty &g) const
+{
+ return (this == &g)
+ || ( strName == g.strName
+ && strValue == g.strValue
+ && timestamp == g.timestamp
+ && strFlags == g.strFlags);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+CpuIdLeaf::CpuIdLeaf() :
+ idx(UINT32_MAX),
+ idxSub(0),
+ uEax(0),
+ uEbx(0),
+ uEcx(0),
+ uEdx(0)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool CpuIdLeaf::operator==(const CpuIdLeaf &c) const
+{
+ return (this == &c)
+ || ( idx == c.idx
+ && idxSub == c.idxSub
+ && uEax == c.uEax
+ && uEbx == c.uEbx
+ && uEcx == c.uEcx
+ && uEdx == c.uEdx);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+Cpu::Cpu() :
+ ulId(UINT32_MAX)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool Cpu::operator==(const Cpu &c) const
+{
+ return (this == &c)
+ || (ulId == c.ulId);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+BandwidthGroup::BandwidthGroup() :
+ cMaxBytesPerSec(0),
+ enmType(BandwidthGroupType_Null)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool BandwidthGroup::operator==(const BandwidthGroup &i) const
+{
+ return (this == &i)
+ || ( strName == i.strName
+ && cMaxBytesPerSec == i.cMaxBytesPerSec
+ && enmType == i.enmType);
+}
+
+/**
+ * IOSettings constructor.
+ */
+IOSettings::IOSettings() :
+ fIOCacheEnabled(true),
+ ulIOCacheSize(5)
+{
+}
+
+/**
+ * Check if all IO Cache settings have default values.
+ */
+bool IOSettings::areIOCacheDefaultSettings() const
+{
+ return fIOCacheEnabled
+ && ulIOCacheSize == 5;
+}
+
+/**
+ * Check if all settings have default values.
+ */
+bool IOSettings::areDefaultSettings() const
+{
+ return areIOCacheDefaultSettings()
+ && llBandwidthGroups.size() == 0;
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool IOSettings::operator==(const IOSettings &i) const
+{
+ return (this == &i)
+ || ( fIOCacheEnabled == i.fIOCacheEnabled
+ && ulIOCacheSize == i.ulIOCacheSize
+ && llBandwidthGroups == i.llBandwidthGroups);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+HostPCIDeviceAttachment::HostPCIDeviceAttachment() :
+ uHostAddress(0),
+ uGuestAddress(0)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool HostPCIDeviceAttachment::operator==(const HostPCIDeviceAttachment &a) const
+{
+ return (this == &a)
+ || ( uHostAddress == a.uHostAddress
+ && uGuestAddress == a.uGuestAddress
+ && strDeviceName == a.strDeviceName);
+}
+
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+Hardware::Hardware() :
+ strVersion("1"),
+ fHardwareVirt(true),
+ fNestedPaging(true),
+ fVPID(true),
+ fUnrestrictedExecution(true),
+ fHardwareVirtForce(false),
+ fUseNativeApi(false),
+ fTripleFaultReset(false),
+ fPAE(false),
+ fAPIC(true),
+ fX2APIC(false),
+ fIBPBOnVMExit(false),
+ fIBPBOnVMEntry(false),
+ fSpecCtrl(false),
+ fSpecCtrlByHost(false),
+ fL1DFlushOnSched(true),
+ fL1DFlushOnVMEntry(false),
+ fMDSClearOnSched(true),
+ fMDSClearOnVMEntry(false),
+ fNestedHWVirt(false),
+ fVirtVmsaveVmload(true),
+ enmLongMode(HC_ARCH_BITS == 64 ? Hardware::LongMode_Enabled : Hardware::LongMode_Disabled),
+ cCPUs(1),
+ fCpuHotPlug(false),
+ fHPETEnabled(false),
+ ulCpuExecutionCap(100),
+ uCpuIdPortabilityLevel(0),
+ strCpuProfile("host"),
+ ulMemorySizeMB((uint32_t)-1),
+ firmwareType(FirmwareType_BIOS),
+ pointingHIDType(PointingHIDType_PS2Mouse),
+ keyboardHIDType(KeyboardHIDType_PS2Keyboard),
+ chipsetType(ChipsetType_PIIX3),
+ iommuType(IommuType_None),
+ paravirtProvider(ParavirtProvider_Legacy), // default for old VMs, for new ones it's ParavirtProvider_Default
+ strParavirtDebug(""),
+ fEmulatedUSBCardReader(false),
+ clipboardMode(ClipboardMode_Disabled),
+ fClipboardFileTransfersEnabled(false),
+ dndMode(DnDMode_Disabled),
+ ulMemoryBalloonSize(0),
+ fPageFusionEnabled(false)
+{
+ mapBootOrder[0] = DeviceType_Floppy;
+ mapBootOrder[1] = DeviceType_DVD;
+ mapBootOrder[2] = DeviceType_HardDisk;
+
+ /* The default value for PAE depends on the host:
+ * - 64 bits host -> always true
+ * - 32 bits host -> true for Windows & Darwin (masked off if the host cpu doesn't support it anyway)
+ */
+#if HC_ARCH_BITS == 64 || defined(RT_OS_WINDOWS) || defined(RT_OS_DARWIN)
+ fPAE = true;
+#endif
+
+ /* The default value of large page supports depends on the host:
+ * - 64 bits host -> true, unless it's Linux (pending further prediction work due to excessively expensive large page allocations)
+ * - 32 bits host -> false
+ */
+#if HC_ARCH_BITS == 64 && !defined(RT_OS_LINUX)
+ fLargePages = true;
+#else
+ /* Not supported on 32 bits hosts. */
+ fLargePages = false;
+#endif
+}
+
+/**
+ * Check if all Paravirt settings have default values.
+ */
+bool Hardware::areParavirtDefaultSettings(SettingsVersion_T sv) const
+{
+ // 5.0 didn't save the paravirt settings if it is ParavirtProvider_Legacy,
+ // so this default must be kept. Later versions don't save the setting if
+ // it's at the default value.
+ return ( (sv >= SettingsVersion_v1_16 && paravirtProvider == ParavirtProvider_Default)
+ || (sv < SettingsVersion_v1_16 && paravirtProvider == ParavirtProvider_Legacy))
+ && strParavirtDebug.isEmpty();
+}
+
+/**
+ * Check if all Boot Order settings have default values.
+ */
+bool Hardware::areBootOrderDefaultSettings() const
+{
+ BootOrderMap::const_iterator it0 = mapBootOrder.find(0);
+ BootOrderMap::const_iterator it1 = mapBootOrder.find(1);
+ BootOrderMap::const_iterator it2 = mapBootOrder.find(2);
+ BootOrderMap::const_iterator it3 = mapBootOrder.find(3);
+ return ( mapBootOrder.size() == 3
+ || ( mapBootOrder.size() == 4
+ && (it3 != mapBootOrder.end() && it3->second == DeviceType_Null)))
+ && (it0 != mapBootOrder.end() && it0->second == DeviceType_Floppy)
+ && (it1 != mapBootOrder.end() && it1->second == DeviceType_DVD)
+ && (it2 != mapBootOrder.end() && it2->second == DeviceType_HardDisk);
+}
+
+/**
+ * Check if all Network Adapter settings have default values.
+ */
+bool Hardware::areAllNetworkAdaptersDefaultSettings(SettingsVersion_T sv) const
+{
+ for (NetworkAdaptersList::const_iterator it = llNetworkAdapters.begin();
+ it != llNetworkAdapters.end();
+ ++it)
+ {
+ if (!it->areDefaultSettings(sv))
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool Hardware::operator==(const Hardware& h) const
+{
+ return (this == &h)
+ || ( strVersion == h.strVersion
+ && uuid == h.uuid
+ && fHardwareVirt == h.fHardwareVirt
+ && fNestedPaging == h.fNestedPaging
+ && fLargePages == h.fLargePages
+ && fVPID == h.fVPID
+ && fUnrestrictedExecution == h.fUnrestrictedExecution
+ && fHardwareVirtForce == h.fHardwareVirtForce
+ && fUseNativeApi == h.fUseNativeApi
+ && fPAE == h.fPAE
+ && enmLongMode == h.enmLongMode
+ && fTripleFaultReset == h.fTripleFaultReset
+ && fAPIC == h.fAPIC
+ && fX2APIC == h.fX2APIC
+ && fIBPBOnVMExit == h.fIBPBOnVMExit
+ && fIBPBOnVMEntry == h.fIBPBOnVMEntry
+ && fSpecCtrl == h.fSpecCtrl
+ && fSpecCtrlByHost == h.fSpecCtrlByHost
+ && fL1DFlushOnSched == h.fL1DFlushOnSched
+ && fL1DFlushOnVMEntry == h.fL1DFlushOnVMEntry
+ && fMDSClearOnSched == h.fMDSClearOnSched
+ && fMDSClearOnVMEntry == h.fMDSClearOnVMEntry
+ && fNestedHWVirt == h.fNestedHWVirt
+ && fVirtVmsaveVmload == h.fVirtVmsaveVmload
+ && cCPUs == h.cCPUs
+ && fCpuHotPlug == h.fCpuHotPlug
+ && ulCpuExecutionCap == h.ulCpuExecutionCap
+ && uCpuIdPortabilityLevel == h.uCpuIdPortabilityLevel
+ && strCpuProfile == h.strCpuProfile
+ && fHPETEnabled == h.fHPETEnabled
+ && llCpus == h.llCpus
+ && llCpuIdLeafs == h.llCpuIdLeafs
+ && ulMemorySizeMB == h.ulMemorySizeMB
+ && mapBootOrder == h.mapBootOrder
+ && firmwareType == h.firmwareType
+ && pointingHIDType == h.pointingHIDType
+ && keyboardHIDType == h.keyboardHIDType
+ && chipsetType == h.chipsetType
+ && iommuType == h.iommuType
+ && paravirtProvider == h.paravirtProvider
+ && strParavirtDebug == h.strParavirtDebug
+ && fEmulatedUSBCardReader == h.fEmulatedUSBCardReader
+ && vrdeSettings == h.vrdeSettings
+ && biosSettings == h.biosSettings
+ && nvramSettings == h.nvramSettings
+ && graphicsAdapter == h.graphicsAdapter
+ && usbSettings == h.usbSettings
+ && tpmSettings == h.tpmSettings
+ && llNetworkAdapters == h.llNetworkAdapters
+ && llSerialPorts == h.llSerialPorts
+ && llParallelPorts == h.llParallelPorts
+ && audioAdapter == h.audioAdapter
+ && storage == h.storage
+ && llSharedFolders == h.llSharedFolders
+ && clipboardMode == h.clipboardMode
+ && fClipboardFileTransfersEnabled == h.fClipboardFileTransfersEnabled
+ && dndMode == h.dndMode
+ && ulMemoryBalloonSize == h.ulMemoryBalloonSize
+ && fPageFusionEnabled == h.fPageFusionEnabled
+ && llGuestProperties == h.llGuestProperties
+ && ioSettings == h.ioSettings
+ && pciAttachments == h.pciAttachments
+ && strDefaultFrontend == h.strDefaultFrontend);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+AttachedDevice::AttachedDevice() :
+ deviceType(DeviceType_Null),
+ fPassThrough(false),
+ fTempEject(false),
+ fNonRotational(false),
+ fDiscard(false),
+ fHotPluggable(false),
+ lPort(0),
+ lDevice(0)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool AttachedDevice::operator==(const AttachedDevice &a) const
+{
+ return (this == &a)
+ || ( deviceType == a.deviceType
+ && fPassThrough == a.fPassThrough
+ && fTempEject == a.fTempEject
+ && fNonRotational == a.fNonRotational
+ && fDiscard == a.fDiscard
+ && fHotPluggable == a.fHotPluggable
+ && lPort == a.lPort
+ && lDevice == a.lDevice
+ && uuid == a.uuid
+ && strHostDriveSrc == a.strHostDriveSrc
+ && strBwGroup == a.strBwGroup);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+StorageController::StorageController() :
+ storageBus(StorageBus_IDE),
+ controllerType(StorageControllerType_PIIX3),
+ ulPortCount(2),
+ ulInstance(0),
+ fUseHostIOCache(true),
+ fBootable(true)
+{
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool StorageController::operator==(const StorageController &s) const
+{
+ return (this == &s)
+ || ( strName == s.strName
+ && storageBus == s.storageBus
+ && controllerType == s.controllerType
+ && ulPortCount == s.ulPortCount
+ && ulInstance == s.ulInstance
+ && fUseHostIOCache == s.fUseHostIOCache
+ && llAttachedDevices == s.llAttachedDevices);
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool Storage::operator==(const Storage &s) const
+{
+ return (this == &s)
+ || (llStorageControllers == s.llStorageControllers); // deep compare
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+Debugging::Debugging() :
+ fTracingEnabled(false),
+ fAllowTracingToAccessVM(false),
+ strTracingConfig(),
+ enmDbgProvider(GuestDebugProvider_None),
+ enmIoProvider(GuestDebugIoProvider_None),
+ strAddress(),
+ ulPort(0)
+{
+}
+
+/**
+ * Check if all settings have default values.
+ */
+bool Debugging::areDefaultSettings() const
+{
+ return !fTracingEnabled
+ && !fAllowTracingToAccessVM
+ && strTracingConfig.isEmpty()
+ && enmDbgProvider == GuestDebugProvider_None
+ && enmIoProvider == GuestDebugIoProvider_None
+ && strAddress.isEmpty()
+ && ulPort == 0;
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool Debugging::operator==(const Debugging &d) const
+{
+ return (this == &d)
+ || ( fTracingEnabled == d.fTracingEnabled
+ && fAllowTracingToAccessVM == d.fAllowTracingToAccessVM
+ && strTracingConfig == d.strTracingConfig
+ && enmDbgProvider == d.enmDbgProvider
+ && enmIoProvider == d.enmIoProvider
+ && strAddress == d.strAddress
+ && ulPort == d.ulPort);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+Autostart::Autostart() :
+ fAutostartEnabled(false),
+ uAutostartDelay(0),
+ enmAutostopType(AutostopType_Disabled)
+{
+}
+
+/**
+ * Check if all settings have default values.
+ */
+bool Autostart::areDefaultSettings() const
+{
+ return !fAutostartEnabled
+ && !uAutostartDelay
+ && enmAutostopType == AutostopType_Disabled;
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool Autostart::operator==(const Autostart &a) const
+{
+ return (this == &a)
+ || ( fAutostartEnabled == a.fAutostartEnabled
+ && uAutostartDelay == a.uAutostartDelay
+ && enmAutostopType == a.enmAutostopType);
+}
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+Snapshot::Snapshot()
+{
+ RTTimeSpecSetNano(&timestamp, 0);
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool Snapshot::operator==(const Snapshot &s) const
+{
+ return (this == &s)
+ || ( uuid == s.uuid
+ && strName == s.strName
+ && strDescription == s.strDescription
+ && RTTimeSpecIsEqual(&timestamp, &s.timestamp)
+ && strStateFile == s.strStateFile
+ && hardware == s.hardware // deep compare
+ && recordingSettings == s.recordingSettings // deep compare
+ && llChildSnapshots == s.llChildSnapshots // deep compare
+ && debugging == s.debugging
+ && autostart == s.autostart);
+}
+
+const struct Snapshot settings::Snapshot::Empty; /* default ctor is OK */
+
+/**
+ * Constructor. Needs to set sane defaults which stand the test of time.
+ */
+MachineUserData::MachineUserData() :
+ fDirectoryIncludesUUID(false),
+ fNameSync(true),
+ fTeleporterEnabled(false),
+ uTeleporterPort(0),
+ fRTCUseUTC(false),
+ enmVMPriority(VMProcPriority_Default)
+{
+ llGroups.push_back("/");
+}
+
+/**
+ * Comparison operator. This gets called from MachineConfigFile::operator==,
+ * which in turn gets called from Machine::saveSettings to figure out whether
+ * machine settings have really changed and thus need to be written out to disk.
+ */
+bool MachineUserData::operator==(const MachineUserData &c) const
+{
+ return (this == &c)
+ || ( strName == c.strName
+ && fDirectoryIncludesUUID == c.fDirectoryIncludesUUID
+ && fNameSync == c.fNameSync
+ && strDescription == c.strDescription
+ && llGroups == c.llGroups
+ && strOsType == c.strOsType
+ && strSnapshotFolder == c.strSnapshotFolder
+ && fTeleporterEnabled == c.fTeleporterEnabled
+ && uTeleporterPort == c.uTeleporterPort
+ && strTeleporterAddress == c.strTeleporterAddress
+ && strTeleporterPassword == c.strTeleporterPassword
+ && fRTCUseUTC == c.fRTCUseUTC
+ && ovIcon == c.ovIcon
+ && enmVMPriority == c.enmVMPriority);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MachineConfigFile
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Constructor.
+ *
+ * If pstrFilename is != NULL, this reads the given settings file into the member
+ * variables and various substructures and lists. Otherwise, the member variables
+ * are initialized with default values.
+ *
+ * Throws variants of xml::Error for I/O, XML and logical content errors, which
+ * the caller should catch; if this constructor does not throw, then the member
+ * variables contain meaningful values (either from the file or defaults).
+ *
+ * @param pstrFilename
+ * @param pCryptoIf Pointer to the cryptographic interface, required for an encrypted machine config.
+ * @param pszPassword The password to use for an encrypted machine config.
+ */
+MachineConfigFile::MachineConfigFile(const Utf8Str *pstrFilename, PCVBOXCRYPTOIF pCryptoIf, const char *pszPassword)
+ : ConfigFileBase(pstrFilename),
+ enmParseState(ParseState_NotParsed),
+ fCurrentStateModified(true),
+ fAborted(false)
+{
+ RTTimeNow(&timeLastStateChange);
+
+ if (pstrFilename)
+ {
+ // the ConfigFileBase constructor has loaded the XML file, so now
+ // we need only analyze what is in there
+
+ xml::NodesLoop nlRootChildren(*m->pelmRoot);
+ const xml::ElementNode *pelmRootChild;
+ while ((pelmRootChild = nlRootChildren.forAllNodes()))
+ {
+ if (pelmRootChild->nameEquals("MachineEncrypted"))
+ readMachineEncrypted(*pelmRootChild, pCryptoIf, pszPassword);
+ if (pelmRootChild->nameEquals("Machine"))
+ readMachine(*pelmRootChild);
+ }
+
+ // clean up memory allocated by XML engine
+ clearDocument();
+
+ if (enmParseState == ParseState_NotParsed)
+ enmParseState = ParseState_Parsed;
+ }
+}
+
+/**
+ * Public routine which returns true if this machine config file can have its
+ * own media registry (which is true for settings version v1.11 and higher,
+ * i.e. files created by VirtualBox 4.0 and higher).
+ * @return
+ */
+bool MachineConfigFile::canHaveOwnMediaRegistry() const
+{
+ return (m->sv >= SettingsVersion_v1_11);
+}
+
+/**
+ * Public routine which copies encryption settings. Used by Machine::saveSettings
+ * so that the encryption settings do not get lost when a copy of the Machine settings
+ * file is made to see if settings have actually changed.
+ * @param other
+ */
+void MachineConfigFile::copyEncryptionSettingsFrom(const MachineConfigFile &other)
+{
+ strKeyId = other.strKeyId;
+ strKeyStore = other.strKeyStore;
+}
+
+/**
+ * Public routine which allows for importing machine XML from an external DOM tree.
+ * Use this after having called the constructor with a NULL argument.
+ *
+ * This is used by the OVF code if a <vbox:Machine> element has been encountered
+ * in an OVF VirtualSystem element.
+ *
+ * @param elmMachine
+ */
+void MachineConfigFile::importMachineXML(const xml::ElementNode &elmMachine)
+{
+ // Ideally the version should be mandatory, but since VirtualBox didn't
+ // care about it until 5.1 came with different defaults, there are OVF
+ // files created by magicians (not using VirtualBox, which always wrote it)
+ // which lack this information. Let's hope that they learn to add the
+ // version when they switch to the newer settings style/defaults of 5.1.
+ if (!(elmMachine.getAttributeValue("version", m->strSettingsVersionFull)))
+ m->strSettingsVersionFull = VBOX_XML_IMPORT_VERSION_FULL;
+
+ LogRel(("Import settings with version \"%s\"\n", m->strSettingsVersionFull.c_str()));
+
+ m->sv = parseVersion(m->strSettingsVersionFull, &elmMachine);
+
+ // remember the settings version we read in case it gets upgraded later,
+ // so we know when to make backups
+ m->svRead = m->sv;
+
+ readMachine(elmMachine);
+}
+
+/**
+ * Comparison operator. This gets called from Machine::saveSettings to figure out
+ * whether machine settings have really changed and thus need to be written out to disk.
+ *
+ * Even though this is called operator==, this does NOT compare all fields; the "equals"
+ * should be understood as "has the same machine config as". The following fields are
+ * NOT compared:
+ * -- settings versions and file names inherited from ConfigFileBase;
+ * -- fCurrentStateModified because that is considered separately in Machine::saveSettings!!
+ *
+ * The "deep" comparisons marked below will invoke the operator== functions of the
+ * structs defined in this file, which may in turn go into comparing lists of
+ * other structures. As a result, invoking this can be expensive, but it's
+ * less expensive than writing out XML to disk.
+ */
+bool MachineConfigFile::operator==(const MachineConfigFile &c) const
+{
+ return (this == &c)
+ || ( uuid == c.uuid
+ && machineUserData == c.machineUserData
+ && strStateFile == c.strStateFile
+ && uuidCurrentSnapshot == c.uuidCurrentSnapshot
+ // skip fCurrentStateModified!
+ && RTTimeSpecIsEqual(&timeLastStateChange, &c.timeLastStateChange)
+ && fAborted == c.fAborted
+ && hardwareMachine == c.hardwareMachine // this one's deep
+ && mediaRegistry == c.mediaRegistry // this one's deep
+ // skip mapExtraDataItems! there is no old state available as it's always forced
+ && llFirstSnapshot == c.llFirstSnapshot // this one's deep
+ && recordingSettings == c.recordingSettings // this one's deep
+ && strKeyId == c.strKeyId
+ && strKeyStore == c.strKeyStore
+ && strStateKeyId == c.strStateKeyId
+ && strStateKeyStore == c.strStateKeyStore
+ && strLogKeyId == c.strLogKeyId
+ && strLogKeyStore == c.strLogKeyStore);
+}
+
+/**
+ * Called from MachineConfigFile::readHardware() to read cpu information.
+ * @param elmCpu
+ * @param ll
+ */
+void MachineConfigFile::readCpuTree(const xml::ElementNode &elmCpu,
+ CpuList &ll)
+{
+ xml::NodesLoop nl1(elmCpu, "Cpu");
+ const xml::ElementNode *pelmCpu;
+ while ((pelmCpu = nl1.forAllNodes()))
+ {
+ Cpu cpu;
+
+ if (!pelmCpu->getAttributeValue("id", cpu.ulId))
+ throw ConfigFileError(this, pelmCpu, N_("Required Cpu/@id attribute is missing"));
+
+ ll.push_back(cpu);
+ }
+}
+
+/**
+ * Called from MachineConfigFile::readHardware() to cpuid information.
+ * @param elmCpuid
+ * @param ll
+ */
+void MachineConfigFile::readCpuIdTree(const xml::ElementNode &elmCpuid,
+ CpuIdLeafsList &ll)
+{
+ xml::NodesLoop nl1(elmCpuid, "CpuIdLeaf");
+ const xml::ElementNode *pelmCpuIdLeaf;
+ while ((pelmCpuIdLeaf = nl1.forAllNodes()))
+ {
+ CpuIdLeaf leaf;
+
+ if (!pelmCpuIdLeaf->getAttributeValue("id", leaf.idx))
+ throw ConfigFileError(this, pelmCpuIdLeaf, N_("Required CpuId/@id attribute is missing"));
+
+ if (!pelmCpuIdLeaf->getAttributeValue("subleaf", leaf.idxSub))
+ leaf.idxSub = 0;
+ pelmCpuIdLeaf->getAttributeValue("eax", leaf.uEax);
+ pelmCpuIdLeaf->getAttributeValue("ebx", leaf.uEbx);
+ pelmCpuIdLeaf->getAttributeValue("ecx", leaf.uEcx);
+ pelmCpuIdLeaf->getAttributeValue("edx", leaf.uEdx);
+
+ ll.push_back(leaf);
+ }
+}
+
+/**
+ * Called from MachineConfigFile::readHardware() to network information.
+ * @param elmNetwork
+ * @param ll
+ */
+void MachineConfigFile::readNetworkAdapters(const xml::ElementNode &elmNetwork,
+ NetworkAdaptersList &ll)
+{
+ xml::NodesLoop nl1(elmNetwork, "Adapter");
+ const xml::ElementNode *pelmAdapter;
+ while ((pelmAdapter = nl1.forAllNodes()))
+ {
+ NetworkAdapter nic;
+
+ if (m->sv >= SettingsVersion_v1_16)
+ {
+ /* Starting with VirtualBox 5.1 the default is cable connected and
+ * PCnet-FAST III. Needs to match NetworkAdapter.areDefaultSettings(). */
+ nic.fCableConnected = true;
+ nic.type = NetworkAdapterType_Am79C973;
+ }
+
+ if (!pelmAdapter->getAttributeValue("slot", nic.ulSlot))
+ throw ConfigFileError(this, pelmAdapter, N_("Required Adapter/@slot attribute is missing"));
+
+ Utf8Str strTemp;
+ if (pelmAdapter->getAttributeValue("type", strTemp))
+ {
+ if (strTemp == "Am79C970A")
+ nic.type = NetworkAdapterType_Am79C970A;
+ else if (strTemp == "Am79C973")
+ nic.type = NetworkAdapterType_Am79C973;
+ else if (strTemp == "Am79C960")
+ nic.type = NetworkAdapterType_Am79C960;
+ else if (strTemp == "82540EM")
+ nic.type = NetworkAdapterType_I82540EM;
+ else if (strTemp == "82543GC")
+ nic.type = NetworkAdapterType_I82543GC;
+ else if (strTemp == "82545EM")
+ nic.type = NetworkAdapterType_I82545EM;
+ else if (strTemp == "virtio")
+ nic.type = NetworkAdapterType_Virtio;
+ else if (strTemp == "NE1000")
+ nic.type = NetworkAdapterType_NE1000;
+ else if (strTemp == "NE2000")
+ nic.type = NetworkAdapterType_NE2000;
+ else if (strTemp == "WD8003")
+ nic.type = NetworkAdapterType_WD8003;
+ else if (strTemp == "WD8013")
+ nic.type = NetworkAdapterType_WD8013;
+ else if (strTemp == "3C503")
+ nic.type = NetworkAdapterType_ELNK2;
+ else if (strTemp == "3C501")
+ nic.type = NetworkAdapterType_ELNK1;
+ else
+ throw ConfigFileError(this, pelmAdapter, N_("Invalid value '%s' in Adapter/@type attribute"), strTemp.c_str());
+ }
+
+ pelmAdapter->getAttributeValue("enabled", nic.fEnabled);
+ pelmAdapter->getAttributeValue("MACAddress", nic.strMACAddress);
+ pelmAdapter->getAttributeValue("cable", nic.fCableConnected);
+ pelmAdapter->getAttributeValue("speed", nic.ulLineSpeed);
+
+ if (pelmAdapter->getAttributeValue("promiscuousModePolicy", strTemp))
+ {
+ if (strTemp == "Deny")
+ nic.enmPromiscModePolicy = NetworkAdapterPromiscModePolicy_Deny;
+ else if (strTemp == "AllowNetwork")
+ nic.enmPromiscModePolicy = NetworkAdapterPromiscModePolicy_AllowNetwork;
+ else if (strTemp == "AllowAll")
+ nic.enmPromiscModePolicy = NetworkAdapterPromiscModePolicy_AllowAll;
+ else
+ throw ConfigFileError(this, pelmAdapter,
+ N_("Invalid value '%s' in Adapter/@promiscuousModePolicy attribute"), strTemp.c_str());
+ }
+
+ pelmAdapter->getAttributeValue("trace", nic.fTraceEnabled);
+ pelmAdapter->getAttributeValue("tracefile", nic.strTraceFile);
+ pelmAdapter->getAttributeValue("bootPriority", nic.ulBootPriority);
+ pelmAdapter->getAttributeValue("bandwidthGroup", nic.strBandwidthGroup);
+
+ xml::ElementNodesList llNetworkModes;
+ pelmAdapter->getChildElements(llNetworkModes);
+ xml::ElementNodesList::iterator it;
+ /* We should have only active mode descriptor and disabled modes set */
+ if (llNetworkModes.size() > 2)
+ {
+ throw ConfigFileError(this, pelmAdapter, N_("Invalid number of modes ('%d') attached to Adapter attribute"), llNetworkModes.size());
+ }
+ for (it = llNetworkModes.begin(); it != llNetworkModes.end(); ++it)
+ {
+ const xml::ElementNode *pelmNode = *it;
+ if (pelmNode->nameEquals("DisabledModes"))
+ {
+ xml::ElementNodesList llDisabledNetworkModes;
+ xml::ElementNodesList::iterator itDisabled;
+ pelmNode->getChildElements(llDisabledNetworkModes);
+ /* run over disabled list and load settings */
+ for (itDisabled = llDisabledNetworkModes.begin();
+ itDisabled != llDisabledNetworkModes.end(); ++itDisabled)
+ {
+ const xml::ElementNode *pelmDisabledNode = *itDisabled;
+ readAttachedNetworkMode(*pelmDisabledNode, false, nic);
+ }
+ }
+ else
+ readAttachedNetworkMode(*pelmNode, true, nic);
+ }
+ // else: default is NetworkAttachmentType_Null
+
+ ll.push_back(nic);
+ }
+}
+
+void MachineConfigFile::readAttachedNetworkMode(const xml::ElementNode &elmMode, bool fEnabled, NetworkAdapter &nic)
+{
+ NetworkAttachmentType_T enmAttachmentType = NetworkAttachmentType_Null;
+
+ if (elmMode.nameEquals("NAT"))
+ {
+ enmAttachmentType = NetworkAttachmentType_NAT;
+
+ elmMode.getAttributeValue("network", nic.nat.strNetwork);
+ elmMode.getAttributeValue("hostip", nic.nat.strBindIP);
+ elmMode.getAttributeValue("mtu", nic.nat.u32Mtu);
+ elmMode.getAttributeValue("sockrcv", nic.nat.u32SockRcv);
+ elmMode.getAttributeValue("socksnd", nic.nat.u32SockSnd);
+ elmMode.getAttributeValue("tcprcv", nic.nat.u32TcpRcv);
+ elmMode.getAttributeValue("tcpsnd", nic.nat.u32TcpSnd);
+ elmMode.getAttributeValue("localhost-reachable", nic.nat.fLocalhostReachable);
+ const xml::ElementNode *pelmDNS;
+ if ((pelmDNS = elmMode.findChildElement("DNS")))
+ {
+ pelmDNS->getAttributeValue("pass-domain", nic.nat.fDNSPassDomain);
+ pelmDNS->getAttributeValue("use-proxy", nic.nat.fDNSProxy);
+ pelmDNS->getAttributeValue("use-host-resolver", nic.nat.fDNSUseHostResolver);
+ }
+ const xml::ElementNode *pelmAlias;
+ if ((pelmAlias = elmMode.findChildElement("Alias")))
+ {
+ pelmAlias->getAttributeValue("logging", nic.nat.fAliasLog);
+ pelmAlias->getAttributeValue("proxy-only", nic.nat.fAliasProxyOnly);
+ pelmAlias->getAttributeValue("use-same-ports", nic.nat.fAliasUseSamePorts);
+ }
+ const xml::ElementNode *pelmTFTP;
+ if ((pelmTFTP = elmMode.findChildElement("TFTP")))
+ {
+ pelmTFTP->getAttributeValue("prefix", nic.nat.strTFTPPrefix);
+ pelmTFTP->getAttributeValue("boot-file", nic.nat.strTFTPBootFile);
+ pelmTFTP->getAttributeValue("next-server", nic.nat.strTFTPNextServer);
+ }
+
+ readNATForwardRulesMap(elmMode, nic.nat.mapRules);
+ }
+ else if ( elmMode.nameEquals("HostInterface")
+ || elmMode.nameEquals("BridgedInterface"))
+ {
+ enmAttachmentType = NetworkAttachmentType_Bridged;
+
+ // optional network name, cannot be required or we have trouble with
+ // settings which are saved before configuring the network name
+ elmMode.getAttributeValue("name", nic.strBridgedName);
+ }
+ else if (elmMode.nameEquals("InternalNetwork"))
+ {
+ enmAttachmentType = NetworkAttachmentType_Internal;
+
+ // optional network name, cannot be required or we have trouble with
+ // settings which are saved before configuring the network name
+ elmMode.getAttributeValue("name", nic.strInternalNetworkName);
+ }
+ else if (elmMode.nameEquals("HostOnlyInterface"))
+ {
+ enmAttachmentType = NetworkAttachmentType_HostOnly;
+
+ // optional network name, cannot be required or we have trouble with
+ // settings which are saved before configuring the network name
+ elmMode.getAttributeValue("name", nic.strHostOnlyName);
+ }
+#ifdef VBOX_WITH_VMNET
+ else if (elmMode.nameEquals("HostOnlyNetwork"))
+ {
+ enmAttachmentType = NetworkAttachmentType_HostOnlyNetwork;
+
+ // optional network name, cannot be required or we have trouble with
+ // settings which are saved before configuring the network name
+ elmMode.getAttributeValue("name", nic.strHostOnlyNetworkName);
+ }
+#endif /* VBOX_WITH_VMNET */
+ else if (elmMode.nameEquals("GenericInterface"))
+ {
+ enmAttachmentType = NetworkAttachmentType_Generic;
+
+ elmMode.getAttributeValue("driver", nic.strGenericDriver); // optional network attachment driver
+
+ // get all properties
+ xml::NodesLoop nl(elmMode);
+ const xml::ElementNode *pelmModeChild;
+ while ((pelmModeChild = nl.forAllNodes()))
+ {
+ if (pelmModeChild->nameEquals("Property"))
+ {
+ Utf8Str strPropName, strPropValue;
+ if ( pelmModeChild->getAttributeValue("name", strPropName)
+ && pelmModeChild->getAttributeValue("value", strPropValue) )
+ nic.genericProperties[strPropName] = strPropValue;
+ else
+ throw ConfigFileError(this, pelmModeChild, N_("Required GenericInterface/Property/@name or @value attribute is missing"));
+ }
+ }
+ }
+ else if (elmMode.nameEquals("NATNetwork"))
+ {
+ enmAttachmentType = NetworkAttachmentType_NATNetwork;
+
+ // optional network name, cannot be required or we have trouble with
+ // settings which are saved before configuring the network name
+ elmMode.getAttributeValue("name", nic.strNATNetworkName);
+ }
+ else if (elmMode.nameEquals("VDE"))
+ {
+ // inofficial hack (VDE networking was never part of the official
+ // settings, so it's not mentioned in VirtualBox-settings.xsd)
+ enmAttachmentType = NetworkAttachmentType_Generic;
+
+ com::Utf8Str strVDEName;
+ elmMode.getAttributeValue("network", strVDEName); // optional network name
+ nic.strGenericDriver = "VDE";
+ nic.genericProperties["network"] = strVDEName;
+ }
+#ifdef VBOX_WITH_VMNET
+ else if (elmMode.nameEquals("HostOnlyNetwork"))
+ {
+ enmAttachmentType = NetworkAttachmentType_HostOnly;
+
+ // optional network name, cannot be required or we have trouble with
+ // settings which are saved before configuring the network name
+ elmMode.getAttributeValue("name", nic.strHostOnlyNetworkName);
+ }
+#endif /* VBOX_WITH_VMNET */
+#ifdef VBOX_WITH_CLOUD_NET
+ else if (elmMode.nameEquals("CloudNetwork"))
+ {
+ enmAttachmentType = NetworkAttachmentType_Cloud;
+
+ // optional network name, cannot be required or we have trouble with
+ // settings which are saved before configuring the network name
+ elmMode.getAttributeValue("name", nic.strCloudNetworkName);
+ }
+#endif /* VBOX_WITH_CLOUD_NET */
+
+ if (fEnabled && enmAttachmentType != NetworkAttachmentType_Null)
+ nic.mode = enmAttachmentType;
+}
+
+/**
+ * Called from MachineConfigFile::readHardware() to read serial port information.
+ * @param elmUART
+ * @param ll
+ */
+void MachineConfigFile::readSerialPorts(const xml::ElementNode &elmUART,
+ SerialPortsList &ll)
+{
+ xml::NodesLoop nl1(elmUART, "Port");
+ const xml::ElementNode *pelmPort;
+ while ((pelmPort = nl1.forAllNodes()))
+ {
+ SerialPort port;
+ if (!pelmPort->getAttributeValue("slot", port.ulSlot))
+ throw ConfigFileError(this, pelmPort, N_("Required UART/Port/@slot attribute is missing"));
+
+ // slot must be unique
+ for (SerialPortsList::const_iterator it = ll.begin();
+ it != ll.end();
+ ++it)
+ if ((*it).ulSlot == port.ulSlot)
+ throw ConfigFileError(this, pelmPort, N_("Invalid value %RU32 in UART/Port/@slot attribute: value is not unique"), port.ulSlot);
+
+ if (!pelmPort->getAttributeValue("enabled", port.fEnabled))
+ throw ConfigFileError(this, pelmPort, N_("Required UART/Port/@enabled attribute is missing"));
+ if (!pelmPort->getAttributeValue("IOBase", port.ulIOBase))
+ throw ConfigFileError(this, pelmPort, N_("Required UART/Port/@IOBase attribute is missing"));
+ if (!pelmPort->getAttributeValue("IRQ", port.ulIRQ))
+ throw ConfigFileError(this, pelmPort, N_("Required UART/Port/@IRQ attribute is missing"));
+
+ Utf8Str strPortMode;
+ if (!pelmPort->getAttributeValue("hostMode", strPortMode))
+ throw ConfigFileError(this, pelmPort, N_("Required UART/Port/@hostMode attribute is missing"));
+ if (strPortMode == "RawFile")
+ port.portMode = PortMode_RawFile;
+ else if (strPortMode == "HostPipe")
+ port.portMode = PortMode_HostPipe;
+ else if (strPortMode == "HostDevice")
+ port.portMode = PortMode_HostDevice;
+ else if (strPortMode == "Disconnected")
+ port.portMode = PortMode_Disconnected;
+ else if (strPortMode == "TCP")
+ port.portMode = PortMode_TCP;
+ else
+ throw ConfigFileError(this, pelmPort, N_("Invalid value '%s' in UART/Port/@hostMode attribute"), strPortMode.c_str());
+
+ pelmPort->getAttributeValue("path", port.strPath);
+ pelmPort->getAttributeValue("server", port.fServer);
+
+ Utf8Str strUartType;
+ if (pelmPort->getAttributeValue("uartType", strUartType))
+ {
+ if (strUartType == "16450")
+ port.uartType = UartType_U16450;
+ else if (strUartType == "16550A")
+ port.uartType = UartType_U16550A;
+ else if (strUartType == "16750")
+ port.uartType = UartType_U16750;
+ else
+ throw ConfigFileError(this, pelmPort, N_("Invalid value '%s' in UART/Port/@uartType attribute"), strUartType.c_str());
+ }
+
+ ll.push_back(port);
+ }
+}
+
+/**
+ * Called from MachineConfigFile::readHardware() to read parallel port information.
+ * @param elmLPT
+ * @param ll
+ */
+void MachineConfigFile::readParallelPorts(const xml::ElementNode &elmLPT,
+ ParallelPortsList &ll)
+{
+ xml::NodesLoop nl1(elmLPT, "Port");
+ const xml::ElementNode *pelmPort;
+ while ((pelmPort = nl1.forAllNodes()))
+ {
+ ParallelPort port;
+ if (!pelmPort->getAttributeValue("slot", port.ulSlot))
+ throw ConfigFileError(this, pelmPort, N_("Required LPT/Port/@slot attribute is missing"));
+
+ // slot must be unique
+ for (ParallelPortsList::const_iterator it = ll.begin();
+ it != ll.end();
+ ++it)
+ if ((*it).ulSlot == port.ulSlot)
+ throw ConfigFileError(this, pelmPort, N_("Invalid value %RU32 in LPT/Port/@slot attribute: value is not unique"), port.ulSlot);
+
+ if (!pelmPort->getAttributeValue("enabled", port.fEnabled))
+ throw ConfigFileError(this, pelmPort, N_("Required LPT/Port/@enabled attribute is missing"));
+ if (!pelmPort->getAttributeValue("IOBase", port.ulIOBase))
+ throw ConfigFileError(this, pelmPort, N_("Required LPT/Port/@IOBase attribute is missing"));
+ if (!pelmPort->getAttributeValue("IRQ", port.ulIRQ))
+ throw ConfigFileError(this, pelmPort, N_("Required LPT/Port/@IRQ attribute is missing"));
+
+ pelmPort->getAttributeValue("path", port.strPath);
+
+ ll.push_back(port);
+ }
+}
+
+/**
+ * Called from MachineConfigFile::readHardware() to read audio adapter information
+ * and maybe fix driver information depending on the current host hardware.
+ *
+ * @param elmAudioAdapter "AudioAdapter" XML element.
+ * @param aa
+ */
+void MachineConfigFile::readAudioAdapter(const xml::ElementNode &elmAudioAdapter,
+ AudioAdapter &aa)
+{
+ if (m->sv >= SettingsVersion_v1_15)
+ {
+ // get all properties
+ xml::NodesLoop nl1(elmAudioAdapter, "Property");
+ const xml::ElementNode *pelmModeChild;
+ while ((pelmModeChild = nl1.forAllNodes()))
+ {
+ Utf8Str strPropName, strPropValue;
+ if ( pelmModeChild->getAttributeValue("name", strPropName)
+ && pelmModeChild->getAttributeValue("value", strPropValue) )
+ aa.properties[strPropName] = strPropValue;
+ else
+ throw ConfigFileError(this, pelmModeChild, N_("Required AudioAdapter/Property/@name or @value attribute "
+ "is missing"));
+ }
+ }
+
+ elmAudioAdapter.getAttributeValue("enabled", aa.fEnabled);
+ elmAudioAdapter.getAttributeValue("enabledIn", aa.fEnabledIn);
+ elmAudioAdapter.getAttributeValue("enabledOut", aa.fEnabledOut);
+
+ Utf8Str strTemp;
+ if (elmAudioAdapter.getAttributeValue("controller", strTemp))
+ {
+ if (strTemp == "SB16")
+ aa.controllerType = AudioControllerType_SB16;
+ else if (strTemp == "AC97")
+ aa.controllerType = AudioControllerType_AC97;
+ else if (strTemp == "HDA")
+ aa.controllerType = AudioControllerType_HDA;
+ else
+ throw ConfigFileError(this, &elmAudioAdapter, N_("Invalid value '%s' in AudioAdapter/@controller attribute"), strTemp.c_str());
+ }
+
+ if (elmAudioAdapter.getAttributeValue("codec", strTemp))
+ {
+ if (strTemp == "SB16")
+ aa.codecType = AudioCodecType_SB16;
+ else if (strTemp == "STAC9700")
+ aa.codecType = AudioCodecType_STAC9700;
+ else if (strTemp == "AD1980")
+ aa.codecType = AudioCodecType_AD1980;
+ else if (strTemp == "STAC9221")
+ aa.codecType = AudioCodecType_STAC9221;
+ else
+ throw ConfigFileError(this, &elmAudioAdapter, N_("Invalid value '%s' in AudioAdapter/@codec attribute"), strTemp.c_str());
+ }
+ else
+ {
+ /* No codec attribute provided; use defaults. */
+ switch (aa.controllerType)
+ {
+ case AudioControllerType_AC97:
+ aa.codecType = AudioCodecType_STAC9700;
+ break;
+ case AudioControllerType_SB16:
+ aa.codecType = AudioCodecType_SB16;
+ break;
+ case AudioControllerType_HDA:
+ aa.codecType = AudioCodecType_STAC9221;
+ break;
+ default:
+ Assert(false); /* We just checked the controller type above. */
+ }
+ }
+
+ if (elmAudioAdapter.getAttributeValue("driver", strTemp))
+ {
+ // settings before 1.3 used lower case so make sure this is case-insensitive
+ strTemp.toUpper();
+ if (strTemp == "DEFAULT") /* Keep this to be backwards compatible for settings < r152556. */
+ aa.driverType = AudioDriverType_Default;
+ else if (strTemp == "NULL")
+ aa.driverType = AudioDriverType_Null;
+ else if (strTemp == "WAS")
+ aa.driverType = AudioDriverType_WAS;
+ else if (strTemp == "WINMM")
+ aa.driverType = AudioDriverType_WinMM;
+ else if ( (strTemp == "DIRECTSOUND") || (strTemp == "DSOUND") )
+ aa.driverType = AudioDriverType_DirectSound;
+ else if (strTemp == "SOLAUDIO") /* Deprecated -- Solaris will use OSS by default now. */
+ aa.driverType = AudioDriverType_SolAudio;
+ else if (strTemp == "ALSA")
+ aa.driverType = AudioDriverType_ALSA;
+ else if (strTemp == "PULSE")
+ aa.driverType = AudioDriverType_Pulse;
+ else if (strTemp == "OSS")
+ aa.driverType = AudioDriverType_OSS;
+ else if (strTemp == "COREAUDIO")
+ aa.driverType = AudioDriverType_CoreAudio;
+ else if (strTemp == "MMPM") /* Deprecated; only kept for backwards compatibility. */
+ aa.driverType = AudioDriverType_MMPM;
+ else
+ throw ConfigFileError(this, &elmAudioAdapter, N_("Invalid value '%s' in AudioAdapter/@driver attribute"), strTemp.c_str());
+
+ /* When loading settings >= 1.19 (VBox 7.0), the attribute "useDefault" will determine if the VM should use
+ * the OS' default audio driver or not. This additional attribute is necessary in order to be backwards compatible
+ * with older VBox versions. */
+ bool fUseDefault = false;
+ if ( elmAudioAdapter.getAttributeValue("useDefault", &fUseDefault) /* Overrides "driver" above (if set). */
+ && fUseDefault)
+ aa.driverType = AudioDriverType_Default;
+
+ // now check if this is actually supported on the current host platform;
+ // people might be opening a file created on a Windows host, and that
+ // VM should still start on a Linux host
+ if (!isAudioDriverAllowedOnThisHost(aa.driverType))
+ aa.driverType = getHostDefaultAudioDriver();
+ }
+}
+
+/**
+ * Called from MachineConfigFile::readHardware() to read guest property information.
+ * @param elmGuestProperties
+ * @param hw
+ */
+void MachineConfigFile::readGuestProperties(const xml::ElementNode &elmGuestProperties,
+ Hardware &hw)
+{
+ xml::NodesLoop nl1(elmGuestProperties, "GuestProperty");
+ const xml::ElementNode *pelmProp;
+ while ((pelmProp = nl1.forAllNodes()))
+ {
+ GuestProperty prop;
+
+ pelmProp->getAttributeValue("name", prop.strName);
+ pelmProp->getAttributeValue("value", prop.strValue);
+
+ pelmProp->getAttributeValue("timestamp", prop.timestamp);
+ pelmProp->getAttributeValue("flags", prop.strFlags);
+
+ /* Check guest property 'name' and 'value' for correctness before
+ * placing it to local cache. */
+
+ int rc = GuestPropValidateName(prop.strName.c_str(), prop.strName.length() + 1 /* '\0' */);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("WARNING: Guest property with invalid name (%s) present in VM configuration file. Guest property will be dropped.\n",
+ prop.strName.c_str()));
+ continue;
+ }
+
+ rc = GuestPropValidateValue(prop.strValue.c_str(), prop.strValue.length() + 1 /* '\0' */);
+ if (rc == VERR_TOO_MUCH_DATA)
+ {
+ LogRel(("WARNING: Guest property '%s' present in VM configuration file and has too long value. Guest property value will be truncated.\n",
+ prop.strName.c_str()));
+
+ /* In order to pass validation, guest property value length (including '\0') in bytes
+ * should be less than GUEST_PROP_MAX_VALUE_LEN. Chop it down to an appropriate length. */
+ prop.strValue.truncate(GUEST_PROP_MAX_VALUE_LEN - 1 /*terminator*/);
+ }
+ else if (RT_FAILURE(rc))
+ {
+ LogRel(("WARNING: Guest property '%s' present in VM configuration file and has invalid value. Guest property will be dropped.\n",
+ prop.strName.c_str()));
+ continue;
+ }
+
+ hw.llGuestProperties.push_back(prop);
+ }
+}
+
+/**
+ * Helper function to read attributes that are common to \<SATAController\> (pre-1.7)
+ * and \<StorageController\>.
+ * @param elmStorageController
+ * @param sctl
+ */
+void MachineConfigFile::readStorageControllerAttributes(const xml::ElementNode &elmStorageController,
+ StorageController &sctl)
+{
+ elmStorageController.getAttributeValue("PortCount", sctl.ulPortCount);
+ elmStorageController.getAttributeValue("useHostIOCache", sctl.fUseHostIOCache);
+}
+
+/**
+ * Reads in a \<Hardware\> block and stores it in the given structure. Used
+ * both directly from readMachine and from readSnapshot, since snapshots
+ * have their own hardware sections.
+ *
+ * For legacy pre-1.7 settings we also need a storage structure because
+ * the IDE and SATA controllers used to be defined under \<Hardware\>.
+ *
+ * @param elmHardware Hardware node to read from.
+ * @param hw Where to store the hardware settings.
+ */
+void MachineConfigFile::readHardware(const xml::ElementNode &elmHardware,
+ Hardware &hw)
+{
+ if (m->sv >= SettingsVersion_v1_16)
+ {
+ /* Starting with VirtualBox 5.1 the default is Default, before it was
+ * Legacy. This needs to matched by areParavirtDefaultSettings(). */
+ hw.paravirtProvider = ParavirtProvider_Default;
+ /* The new default is disabled, before it was enabled by default. */
+ hw.vrdeSettings.fEnabled = false;
+ /* The new default is disabled, before it was enabled by default. */
+ hw.audioAdapter.fEnabled = false;
+ }
+
+ if (m->sv >= SettingsVersion_v1_17)
+ {
+ /* Starting with VirtualBox 5.2 the default is disabled, before it was
+ * enabled. This needs to matched by AudioAdapter::areDefaultSettings(). */
+ hw.audioAdapter.fEnabledIn = false;
+ /* The new default is disabled, before it was enabled by default. */
+ hw.audioAdapter.fEnabledOut = false;
+ }
+
+ if (!elmHardware.getAttributeValue("version", hw.strVersion))
+ {
+ /* KLUDGE ALERT! For a while during the 3.1 development this was not
+ written because it was thought to have a default value of "2". For
+ sv <= 1.3 it defaults to "1" because the attribute didn't exist,
+ while for 1.4+ it is sort of mandatory. Now, the buggy XML writer
+ code only wrote 1.7 and later. So, if it's a 1.7+ XML file and it's
+ missing the hardware version, then it probably should be "2" instead
+ of "1". */
+ if (m->sv < SettingsVersion_v1_7)
+ hw.strVersion = "1";
+ else
+ hw.strVersion = "2";
+ }
+ Utf8Str strUUID;
+ if (elmHardware.getAttributeValue("uuid", strUUID))
+ parseUUID(hw.uuid, strUUID, &elmHardware);
+
+ xml::NodesLoop nl1(elmHardware);
+ const xml::ElementNode *pelmHwChild;
+ while ((pelmHwChild = nl1.forAllNodes()))
+ {
+ if (pelmHwChild->nameEquals("CPU"))
+ {
+ if (!pelmHwChild->getAttributeValue("count", hw.cCPUs))
+ {
+ // pre-1.5 variant; not sure if this actually exists in the wild anywhere
+ const xml::ElementNode *pelmCPUChild;
+ if ((pelmCPUChild = pelmHwChild->findChildElement("CPUCount")))
+ pelmCPUChild->getAttributeValue("count", hw.cCPUs);
+ }
+
+ pelmHwChild->getAttributeValue("hotplug", hw.fCpuHotPlug);
+ pelmHwChild->getAttributeValue("executionCap", hw.ulCpuExecutionCap);
+
+ const xml::ElementNode *pelmCPUChild;
+ if (hw.fCpuHotPlug)
+ {
+ if ((pelmCPUChild = pelmHwChild->findChildElement("CpuTree")))
+ readCpuTree(*pelmCPUChild, hw.llCpus);
+ }
+
+ if ((pelmCPUChild = pelmHwChild->findChildElement("HardwareVirtEx")))
+ {
+ pelmCPUChild->getAttributeValue("enabled", hw.fHardwareVirt);
+ }
+ if ((pelmCPUChild = pelmHwChild->findChildElement("HardwareVirtExNestedPaging")))
+ pelmCPUChild->getAttributeValue("enabled", hw.fNestedPaging);
+ if ((pelmCPUChild = pelmHwChild->findChildElement("HardwareVirtExLargePages")))
+ pelmCPUChild->getAttributeValue("enabled", hw.fLargePages);
+ if ((pelmCPUChild = pelmHwChild->findChildElement("HardwareVirtExVPID")))
+ pelmCPUChild->getAttributeValue("enabled", hw.fVPID);
+ if ((pelmCPUChild = pelmHwChild->findChildElement("HardwareVirtExUX")))
+ pelmCPUChild->getAttributeValue("enabled", hw.fUnrestrictedExecution);
+ if ((pelmCPUChild = pelmHwChild->findChildElement("HardwareVirtForce")))
+ pelmCPUChild->getAttributeValue("enabled", hw.fHardwareVirtForce);
+ if ((pelmCPUChild = pelmHwChild->findChildElement("HardwareVirtExUseNativeApi")))
+ pelmCPUChild->getAttributeValue("enabled", hw.fUseNativeApi);
+ if ((pelmCPUChild = pelmHwChild->findChildElement("HardwareVirtExVirtVmsaveVmload")))
+ pelmCPUChild->getAttributeValue("enabled", hw.fVirtVmsaveVmload);
+
+ if (!(pelmCPUChild = pelmHwChild->findChildElement("PAE")))
+ {
+ /* The default for pre 3.1 was false, so we must respect that. */
+ if (m->sv < SettingsVersion_v1_9)
+ hw.fPAE = false;
+ }
+ else
+ pelmCPUChild->getAttributeValue("enabled", hw.fPAE);
+
+ bool fLongMode;
+ if ( (pelmCPUChild = pelmHwChild->findChildElement("LongMode"))
+ && pelmCPUChild->getAttributeValue("enabled", fLongMode) )
+ hw.enmLongMode = fLongMode ? Hardware::LongMode_Enabled : Hardware::LongMode_Disabled;
+ else
+ hw.enmLongMode = Hardware::LongMode_Legacy;
+
+ if ((pelmCPUChild = pelmHwChild->findChildElement("SyntheticCpu")))
+ {
+ bool fSyntheticCpu = false;
+ pelmCPUChild->getAttributeValue("enabled", fSyntheticCpu);
+ hw.uCpuIdPortabilityLevel = fSyntheticCpu ? 1 : 0;
+ }
+ pelmHwChild->getAttributeValue("CpuIdPortabilityLevel", hw.uCpuIdPortabilityLevel);
+ pelmHwChild->getAttributeValue("CpuProfile", hw.strCpuProfile);
+
+ if ((pelmCPUChild = pelmHwChild->findChildElement("TripleFaultReset")))
+ pelmCPUChild->getAttributeValue("enabled", hw.fTripleFaultReset);
+
+ if ((pelmCPUChild = pelmHwChild->findChildElement("APIC")))
+ pelmCPUChild->getAttributeValue("enabled", hw.fAPIC);
+ if ((pelmCPUChild = pelmHwChild->findChildElement("X2APIC")))
+ pelmCPUChild->getAttributeValue("enabled", hw.fX2APIC);
+ if (hw.fX2APIC)
+ hw.fAPIC = true;
+ pelmCPUChild = pelmHwChild->findChildElement("IBPBOn");
+ if (pelmCPUChild)
+ {
+ pelmCPUChild->getAttributeValue("vmexit", hw.fIBPBOnVMExit);
+ pelmCPUChild->getAttributeValue("vmentry", hw.fIBPBOnVMEntry);
+ }
+ pelmCPUChild = pelmHwChild->findChildElement("SpecCtrl");
+ if (pelmCPUChild)
+ pelmCPUChild->getAttributeValue("enabled", hw.fSpecCtrl);
+ pelmCPUChild = pelmHwChild->findChildElement("SpecCtrlByHost");
+ if (pelmCPUChild)
+ pelmCPUChild->getAttributeValue("enabled", hw.fSpecCtrlByHost);
+ pelmCPUChild = pelmHwChild->findChildElement("L1DFlushOn");
+ if (pelmCPUChild)
+ {
+ pelmCPUChild->getAttributeValue("scheduling", hw.fL1DFlushOnSched);
+ pelmCPUChild->getAttributeValue("vmentry", hw.fL1DFlushOnVMEntry);
+ }
+ pelmCPUChild = pelmHwChild->findChildElement("MDSClearOn");
+ if (pelmCPUChild)
+ {
+ pelmCPUChild->getAttributeValue("scheduling", hw.fMDSClearOnSched);
+ pelmCPUChild->getAttributeValue("vmentry", hw.fMDSClearOnVMEntry);
+ }
+ pelmCPUChild = pelmHwChild->findChildElement("NestedHWVirt");
+ if (pelmCPUChild)
+ pelmCPUChild->getAttributeValue("enabled", hw.fNestedHWVirt);
+
+ if ((pelmCPUChild = pelmHwChild->findChildElement("CpuIdTree")))
+ readCpuIdTree(*pelmCPUChild, hw.llCpuIdLeafs);
+ }
+ else if (pelmHwChild->nameEquals("Memory"))
+ {
+ pelmHwChild->getAttributeValue("RAMSize", hw.ulMemorySizeMB);
+ pelmHwChild->getAttributeValue("PageFusion", hw.fPageFusionEnabled);
+ }
+ else if (pelmHwChild->nameEquals("Firmware"))
+ {
+ Utf8Str strFirmwareType;
+ if (pelmHwChild->getAttributeValue("type", strFirmwareType))
+ {
+ if ( (strFirmwareType == "BIOS")
+ || (strFirmwareType == "1") // some trunk builds used the number here
+ )
+ hw.firmwareType = FirmwareType_BIOS;
+ else if ( (strFirmwareType == "EFI")
+ || (strFirmwareType == "2") // some trunk builds used the number here
+ )
+ hw.firmwareType = FirmwareType_EFI;
+ else if ( strFirmwareType == "EFI32")
+ hw.firmwareType = FirmwareType_EFI32;
+ else if ( strFirmwareType == "EFI64")
+ hw.firmwareType = FirmwareType_EFI64;
+ else if ( strFirmwareType == "EFIDUAL")
+ hw.firmwareType = FirmwareType_EFIDUAL;
+ else
+ throw ConfigFileError(this,
+ pelmHwChild,
+ N_("Invalid value '%s' in Firmware/@type"),
+ strFirmwareType.c_str());
+ }
+ }
+ else if (pelmHwChild->nameEquals("HID"))
+ {
+ Utf8Str strHIDType;
+ if (pelmHwChild->getAttributeValue("Keyboard", strHIDType))
+ {
+ if (strHIDType == "None")
+ hw.keyboardHIDType = KeyboardHIDType_None;
+ else if (strHIDType == "USBKeyboard")
+ hw.keyboardHIDType = KeyboardHIDType_USBKeyboard;
+ else if (strHIDType == "PS2Keyboard")
+ hw.keyboardHIDType = KeyboardHIDType_PS2Keyboard;
+ else if (strHIDType == "ComboKeyboard")
+ hw.keyboardHIDType = KeyboardHIDType_ComboKeyboard;
+ else
+ throw ConfigFileError(this,
+ pelmHwChild,
+ N_("Invalid value '%s' in HID/Keyboard/@type"),
+ strHIDType.c_str());
+ }
+ if (pelmHwChild->getAttributeValue("Pointing", strHIDType))
+ {
+ if (strHIDType == "None")
+ hw.pointingHIDType = PointingHIDType_None;
+ else if (strHIDType == "USBMouse")
+ hw.pointingHIDType = PointingHIDType_USBMouse;
+ else if (strHIDType == "USBTablet")
+ hw.pointingHIDType = PointingHIDType_USBTablet;
+ else if (strHIDType == "PS2Mouse")
+ hw.pointingHIDType = PointingHIDType_PS2Mouse;
+ else if (strHIDType == "ComboMouse")
+ hw.pointingHIDType = PointingHIDType_ComboMouse;
+ else if (strHIDType == "USBMultiTouch")
+ hw.pointingHIDType = PointingHIDType_USBMultiTouch;
+ else if (strHIDType == "USBMTScreenPlusPad")
+ hw.pointingHIDType = PointingHIDType_USBMultiTouchScreenPlusPad;
+ else
+ throw ConfigFileError(this,
+ pelmHwChild,
+ N_("Invalid value '%s' in HID/Pointing/@type"),
+ strHIDType.c_str());
+ }
+ }
+ else if (pelmHwChild->nameEquals("Chipset"))
+ {
+ Utf8Str strChipsetType;
+ if (pelmHwChild->getAttributeValue("type", strChipsetType))
+ {
+ if (strChipsetType == "PIIX3")
+ hw.chipsetType = ChipsetType_PIIX3;
+ else if (strChipsetType == "ICH9")
+ hw.chipsetType = ChipsetType_ICH9;
+ else
+ throw ConfigFileError(this,
+ pelmHwChild,
+ N_("Invalid value '%s' in Chipset/@type"),
+ strChipsetType.c_str());
+ }
+ }
+ else if (pelmHwChild->nameEquals("Iommu"))
+ {
+ Utf8Str strIommuType;
+ if (pelmHwChild->getAttributeValue("type", strIommuType))
+ {
+ if (strIommuType == "None")
+ hw.iommuType = IommuType_None;
+ else if (strIommuType == "Automatic")
+ hw.iommuType = IommuType_Automatic;
+ else if (strIommuType == "AMD")
+ hw.iommuType = IommuType_AMD;
+ else if (strIommuType == "Intel")
+ hw.iommuType = IommuType_Intel;
+ else
+ throw ConfigFileError(this,
+ pelmHwChild,
+ N_("Invalid value '%s' in Iommu/@type"),
+ strIommuType.c_str());
+ }
+ }
+ else if (pelmHwChild->nameEquals("Paravirt"))
+ {
+ Utf8Str strProvider;
+ if (pelmHwChild->getAttributeValue("provider", strProvider))
+ {
+ if (strProvider == "None")
+ hw.paravirtProvider = ParavirtProvider_None;
+ else if (strProvider == "Default")
+ hw.paravirtProvider = ParavirtProvider_Default;
+ else if (strProvider == "Legacy")
+ hw.paravirtProvider = ParavirtProvider_Legacy;
+ else if (strProvider == "Minimal")
+ hw.paravirtProvider = ParavirtProvider_Minimal;
+ else if (strProvider == "HyperV")
+ hw.paravirtProvider = ParavirtProvider_HyperV;
+ else if (strProvider == "KVM")
+ hw.paravirtProvider = ParavirtProvider_KVM;
+ else
+ throw ConfigFileError(this,
+ pelmHwChild,
+ N_("Invalid value '%s' in Paravirt/@provider attribute"),
+ strProvider.c_str());
+ }
+
+ pelmHwChild->getAttributeValue("debug", hw.strParavirtDebug);
+ }
+ else if (pelmHwChild->nameEquals("HPET"))
+ {
+ pelmHwChild->getAttributeValue("enabled", hw.fHPETEnabled);
+ }
+ else if (pelmHwChild->nameEquals("Boot"))
+ {
+ hw.mapBootOrder.clear();
+
+ xml::NodesLoop nl2(*pelmHwChild, "Order");
+ const xml::ElementNode *pelmOrder;
+ while ((pelmOrder = nl2.forAllNodes()))
+ {
+ uint32_t ulPos;
+ Utf8Str strDevice;
+ if (!pelmOrder->getAttributeValue("position", ulPos))
+ throw ConfigFileError(this, pelmOrder, N_("Required Boot/Order/@position attribute is missing"));
+
+ if ( ulPos < 1
+ || ulPos > SchemaDefs::MaxBootPosition
+ )
+ throw ConfigFileError(this,
+ pelmOrder,
+ N_("Invalid value '%RU32' in Boot/Order/@position: must be greater than 0 and less than %RU32"),
+ ulPos,
+ SchemaDefs::MaxBootPosition + 1);
+ // XML is 1-based but internal data is 0-based
+ --ulPos;
+
+ if (hw.mapBootOrder.find(ulPos) != hw.mapBootOrder.end())
+ throw ConfigFileError(this, pelmOrder, N_("Invalid value '%RU32' in Boot/Order/@position: value is not unique"), ulPos);
+
+ if (!pelmOrder->getAttributeValue("device", strDevice))
+ throw ConfigFileError(this, pelmOrder, N_("Required Boot/Order/@device attribute is missing"));
+
+ DeviceType_T type;
+ if (strDevice == "None")
+ type = DeviceType_Null;
+ else if (strDevice == "Floppy")
+ type = DeviceType_Floppy;
+ else if (strDevice == "DVD")
+ type = DeviceType_DVD;
+ else if (strDevice == "HardDisk")
+ type = DeviceType_HardDisk;
+ else if (strDevice == "Network")
+ type = DeviceType_Network;
+ else
+ throw ConfigFileError(this, pelmOrder, N_("Invalid value '%s' in Boot/Order/@device attribute"), strDevice.c_str());
+ hw.mapBootOrder[ulPos] = type;
+ }
+ }
+ else if (pelmHwChild->nameEquals("Display"))
+ {
+ Utf8Str strGraphicsControllerType;
+ if (!pelmHwChild->getAttributeValue("controller", strGraphicsControllerType))
+ hw.graphicsAdapter.graphicsControllerType = GraphicsControllerType_VBoxVGA;
+ else
+ {
+ strGraphicsControllerType.toUpper();
+ GraphicsControllerType_T type;
+ if (strGraphicsControllerType == "VBOXVGA")
+ type = GraphicsControllerType_VBoxVGA;
+ else if (strGraphicsControllerType == "VMSVGA")
+ type = GraphicsControllerType_VMSVGA;
+ else if (strGraphicsControllerType == "VBOXSVGA")
+ type = GraphicsControllerType_VBoxSVGA;
+ else if (strGraphicsControllerType == "NONE")
+ type = GraphicsControllerType_Null;
+ else
+ throw ConfigFileError(this, pelmHwChild, N_("Invalid value '%s' in Display/@controller attribute"), strGraphicsControllerType.c_str());
+ hw.graphicsAdapter.graphicsControllerType = type;
+ }
+ pelmHwChild->getAttributeValue("VRAMSize", hw.graphicsAdapter.ulVRAMSizeMB);
+ if (!pelmHwChild->getAttributeValue("monitorCount", hw.graphicsAdapter.cMonitors))
+ pelmHwChild->getAttributeValue("MonitorCount", hw.graphicsAdapter.cMonitors); // pre-v1.5 variant
+ if (!pelmHwChild->getAttributeValue("accelerate3D", hw.graphicsAdapter.fAccelerate3D))
+ pelmHwChild->getAttributeValue("Accelerate3D", hw.graphicsAdapter.fAccelerate3D); // pre-v1.5 variant
+ pelmHwChild->getAttributeValue("accelerate2DVideo", hw.graphicsAdapter.fAccelerate2DVideo);
+ }
+ else if (pelmHwChild->nameEquals("RemoteDisplay"))
+ {
+ pelmHwChild->getAttributeValue("enabled", hw.vrdeSettings.fEnabled);
+
+ Utf8Str str;
+ if (pelmHwChild->getAttributeValue("port", str))
+ hw.vrdeSettings.mapProperties["TCP/Ports"] = str;
+ if (pelmHwChild->getAttributeValue("netAddress", str))
+ hw.vrdeSettings.mapProperties["TCP/Address"] = str;
+
+ Utf8Str strAuthType;
+ if (pelmHwChild->getAttributeValue("authType", strAuthType))
+ {
+ // settings before 1.3 used lower case so make sure this is case-insensitive
+ strAuthType.toUpper();
+ if (strAuthType == "NULL")
+ hw.vrdeSettings.authType = AuthType_Null;
+ else if (strAuthType == "GUEST")
+ hw.vrdeSettings.authType = AuthType_Guest;
+ else if (strAuthType == "EXTERNAL")
+ hw.vrdeSettings.authType = AuthType_External;
+ else
+ throw ConfigFileError(this, pelmHwChild, N_("Invalid value '%s' in RemoteDisplay/@authType attribute"), strAuthType.c_str());
+ }
+
+ pelmHwChild->getAttributeValue("authLibrary", hw.vrdeSettings.strAuthLibrary);
+ pelmHwChild->getAttributeValue("authTimeout", hw.vrdeSettings.ulAuthTimeout);
+ pelmHwChild->getAttributeValue("allowMultiConnection", hw.vrdeSettings.fAllowMultiConnection);
+ pelmHwChild->getAttributeValue("reuseSingleConnection", hw.vrdeSettings.fReuseSingleConnection);
+
+ /* 3.2 and 4.0 betas, 4.0 has this information in VRDEProperties. */
+ const xml::ElementNode *pelmVideoChannel;
+ if ((pelmVideoChannel = pelmHwChild->findChildElement("VideoChannel")))
+ {
+ bool fVideoChannel = false;
+ pelmVideoChannel->getAttributeValue("enabled", fVideoChannel);
+ hw.vrdeSettings.mapProperties["VideoChannel/Enabled"] = fVideoChannel? "true": "false";
+
+ uint32_t ulVideoChannelQuality = 75;
+ pelmVideoChannel->getAttributeValue("quality", ulVideoChannelQuality);
+ ulVideoChannelQuality = RT_CLAMP(ulVideoChannelQuality, 10, 100);
+ char *pszBuffer = NULL;
+ if (RTStrAPrintf(&pszBuffer, "%d", ulVideoChannelQuality) >= 0)
+ {
+ hw.vrdeSettings.mapProperties["VideoChannel/Quality"] = pszBuffer;
+ RTStrFree(pszBuffer);
+ }
+ else
+ hw.vrdeSettings.mapProperties["VideoChannel/Quality"] = "75";
+ }
+ pelmHwChild->getAttributeValue("VRDEExtPack", hw.vrdeSettings.strVrdeExtPack);
+
+ const xml::ElementNode *pelmProperties = pelmHwChild->findChildElement("VRDEProperties");
+ if (pelmProperties != NULL)
+ {
+ xml::NodesLoop nl(*pelmProperties);
+ const xml::ElementNode *pelmProperty;
+ while ((pelmProperty = nl.forAllNodes()))
+ {
+ if (pelmProperty->nameEquals("Property"))
+ {
+ /* <Property name="TCP/Ports" value="3000-3002"/> */
+ Utf8Str strName, strValue;
+ if ( pelmProperty->getAttributeValue("name", strName)
+ && pelmProperty->getAttributeValue("value", strValue))
+ hw.vrdeSettings.mapProperties[strName] = strValue;
+ else
+ throw ConfigFileError(this, pelmProperty, N_("Required VRDE Property/@name or @value attribute is missing"));
+ }
+ }
+ }
+ }
+ else if (pelmHwChild->nameEquals("BIOS"))
+ {
+ const xml::ElementNode *pelmBIOSChild;
+ if ((pelmBIOSChild = pelmHwChild->findChildElement("ACPI")))
+ pelmBIOSChild->getAttributeValue("enabled", hw.biosSettings.fACPIEnabled);
+ if ((pelmBIOSChild = pelmHwChild->findChildElement("IOAPIC")))
+ pelmBIOSChild->getAttributeValue("enabled", hw.biosSettings.fIOAPICEnabled);
+ if ((pelmBIOSChild = pelmHwChild->findChildElement("APIC")))
+ {
+ Utf8Str strAPIC;
+ if (pelmBIOSChild->getAttributeValue("mode", strAPIC))
+ {
+ strAPIC.toUpper();
+ if (strAPIC == "DISABLED")
+ hw.biosSettings.apicMode = APICMode_Disabled;
+ else if (strAPIC == "APIC")
+ hw.biosSettings.apicMode = APICMode_APIC;
+ else if (strAPIC == "X2APIC")
+ hw.biosSettings.apicMode = APICMode_X2APIC;
+ else
+ throw ConfigFileError(this, pelmBIOSChild, N_("Invalid value '%s' in APIC/@mode attribute"), strAPIC.c_str());
+ }
+ }
+ if ((pelmBIOSChild = pelmHwChild->findChildElement("Logo")))
+ {
+ pelmBIOSChild->getAttributeValue("fadeIn", hw.biosSettings.fLogoFadeIn);
+ pelmBIOSChild->getAttributeValue("fadeOut", hw.biosSettings.fLogoFadeOut);
+ pelmBIOSChild->getAttributeValue("displayTime", hw.biosSettings.ulLogoDisplayTime);
+ pelmBIOSChild->getAttributeValue("imagePath", hw.biosSettings.strLogoImagePath);
+ }
+ if ((pelmBIOSChild = pelmHwChild->findChildElement("BootMenu")))
+ {
+ Utf8Str strBootMenuMode;
+ if (pelmBIOSChild->getAttributeValue("mode", strBootMenuMode))
+ {
+ // settings before 1.3 used lower case so make sure this is case-insensitive
+ strBootMenuMode.toUpper();
+ if (strBootMenuMode == "DISABLED")
+ hw.biosSettings.biosBootMenuMode = BIOSBootMenuMode_Disabled;
+ else if (strBootMenuMode == "MENUONLY")
+ hw.biosSettings.biosBootMenuMode = BIOSBootMenuMode_MenuOnly;
+ else if (strBootMenuMode == "MESSAGEANDMENU")
+ hw.biosSettings.biosBootMenuMode = BIOSBootMenuMode_MessageAndMenu;
+ else
+ throw ConfigFileError(this, pelmBIOSChild, N_("Invalid value '%s' in BootMenu/@mode attribute"), strBootMenuMode.c_str());
+ }
+ }
+ if ((pelmBIOSChild = pelmHwChild->findChildElement("PXEDebug")))
+ pelmBIOSChild->getAttributeValue("enabled", hw.biosSettings.fPXEDebugEnabled);
+ if ((pelmBIOSChild = pelmHwChild->findChildElement("TimeOffset")))
+ pelmBIOSChild->getAttributeValue("value", hw.biosSettings.llTimeOffset);
+ if ((pelmBIOSChild = pelmHwChild->findChildElement("NVRAM")))
+ {
+ pelmBIOSChild->getAttributeValue("path", hw.nvramSettings.strNvramPath);
+ if (m->sv >= SettingsVersion_v1_19)
+ {
+ pelmBIOSChild->getAttributeValue("keyId", hw.nvramSettings.strKeyId);
+ pelmBIOSChild->getAttributeValue("keyStore", hw.nvramSettings.strKeyStore);
+ }
+ }
+ if ((pelmBIOSChild = pelmHwChild->findChildElement("SmbiosUuidLittleEndian")))
+ pelmBIOSChild->getAttributeValue("enabled", hw.biosSettings.fSmbiosUuidLittleEndian);
+ else
+ hw.biosSettings.fSmbiosUuidLittleEndian = false; /* Default for existing VMs. */
+
+ // legacy BIOS/IDEController (pre 1.7)
+ if ( (m->sv < SettingsVersion_v1_7)
+ && (pelmBIOSChild = pelmHwChild->findChildElement("IDEController"))
+ )
+ {
+ StorageController sctl;
+ sctl.strName = "IDE Controller";
+ sctl.storageBus = StorageBus_IDE;
+
+ Utf8Str strType;
+ if (pelmBIOSChild->getAttributeValue("type", strType))
+ {
+ if (strType == "PIIX3")
+ sctl.controllerType = StorageControllerType_PIIX3;
+ else if (strType == "PIIX4")
+ sctl.controllerType = StorageControllerType_PIIX4;
+ else if (strType == "ICH6")
+ sctl.controllerType = StorageControllerType_ICH6;
+ else
+ throw ConfigFileError(this, pelmBIOSChild, N_("Invalid value '%s' for IDEController/@type attribute"), strType.c_str());
+ }
+ sctl.ulPortCount = 2;
+ hw.storage.llStorageControllers.push_back(sctl);
+ }
+ }
+ else if (pelmHwChild->nameEquals("TrustedPlatformModule"))
+ {
+ Utf8Str strTpmType;
+ if (pelmHwChild->getAttributeValue("type", strTpmType))
+ {
+ if (strTpmType == "None")
+ hw.tpmSettings.tpmType = TpmType_None;
+ else if (strTpmType == "v1_2")
+ hw.tpmSettings.tpmType = TpmType_v1_2;
+ else if (strTpmType == "v2_0")
+ hw.tpmSettings.tpmType = TpmType_v2_0;
+ else if (strTpmType == "Host")
+ hw.tpmSettings.tpmType = TpmType_Host;
+ else if (strTpmType == "Swtpm")
+ hw.tpmSettings.tpmType = TpmType_Swtpm;
+ else
+ throw ConfigFileError(this,
+ pelmHwChild,
+ N_("Invalid value '%s' in TrustedPlatformModule/@type"),
+ strTpmType.c_str());
+ }
+
+ pelmHwChild->getAttributeValue("location", hw.tpmSettings.strLocation);
+ }
+ else if ( (m->sv <= SettingsVersion_v1_14)
+ && pelmHwChild->nameEquals("USBController"))
+ {
+ bool fEnabled = false;
+
+ pelmHwChild->getAttributeValue("enabled", fEnabled);
+ if (fEnabled)
+ {
+ /* Create OHCI controller with default name. */
+ USBController ctrl;
+
+ ctrl.strName = "OHCI";
+ ctrl.enmType = USBControllerType_OHCI;
+ hw.usbSettings.llUSBControllers.push_back(ctrl);
+ }
+
+ pelmHwChild->getAttributeValue("enabledEhci", fEnabled);
+ if (fEnabled)
+ {
+ /* Create OHCI controller with default name. */
+ USBController ctrl;
+
+ ctrl.strName = "EHCI";
+ ctrl.enmType = USBControllerType_EHCI;
+ hw.usbSettings.llUSBControllers.push_back(ctrl);
+ }
+
+ readUSBDeviceFilters(*pelmHwChild,
+ hw.usbSettings.llDeviceFilters);
+ }
+ else if (pelmHwChild->nameEquals("USB"))
+ {
+ const xml::ElementNode *pelmUSBChild;
+
+ if ((pelmUSBChild = pelmHwChild->findChildElement("Controllers")))
+ {
+ xml::NodesLoop nl2(*pelmUSBChild, "Controller");
+ const xml::ElementNode *pelmCtrl;
+
+ while ((pelmCtrl = nl2.forAllNodes()))
+ {
+ USBController ctrl;
+ com::Utf8Str strCtrlType;
+
+ pelmCtrl->getAttributeValue("name", ctrl.strName);
+
+ if (pelmCtrl->getAttributeValue("type", strCtrlType))
+ {
+ if (strCtrlType == "OHCI")
+ ctrl.enmType = USBControllerType_OHCI;
+ else if (strCtrlType == "EHCI")
+ ctrl.enmType = USBControllerType_EHCI;
+ else if (strCtrlType == "XHCI")
+ ctrl.enmType = USBControllerType_XHCI;
+ else
+ throw ConfigFileError(this, pelmCtrl, N_("Invalid value '%s' for Controller/@type attribute"), strCtrlType.c_str());
+ }
+
+ hw.usbSettings.llUSBControllers.push_back(ctrl);
+ }
+ }
+
+ if ((pelmUSBChild = pelmHwChild->findChildElement("DeviceFilters")))
+ readUSBDeviceFilters(*pelmUSBChild, hw.usbSettings.llDeviceFilters);
+ }
+ else if ( m->sv < SettingsVersion_v1_7
+ && pelmHwChild->nameEquals("SATAController"))
+ {
+ bool f;
+ if ( pelmHwChild->getAttributeValue("enabled", f)
+ && f)
+ {
+ StorageController sctl;
+ sctl.strName = "SATA Controller";
+ sctl.storageBus = StorageBus_SATA;
+ sctl.controllerType = StorageControllerType_IntelAhci;
+
+ readStorageControllerAttributes(*pelmHwChild, sctl);
+
+ hw.storage.llStorageControllers.push_back(sctl);
+ }
+ }
+ else if (pelmHwChild->nameEquals("Network"))
+ readNetworkAdapters(*pelmHwChild, hw.llNetworkAdapters);
+ else if (pelmHwChild->nameEquals("RTC"))
+ {
+ Utf8Str strLocalOrUTC;
+ machineUserData.fRTCUseUTC = pelmHwChild->getAttributeValue("localOrUTC", strLocalOrUTC)
+ && strLocalOrUTC == "UTC";
+ }
+ else if ( pelmHwChild->nameEquals("UART")
+ || pelmHwChild->nameEquals("Uart") // used before 1.3
+ )
+ readSerialPorts(*pelmHwChild, hw.llSerialPorts);
+ else if ( pelmHwChild->nameEquals("LPT")
+ || pelmHwChild->nameEquals("Lpt") // used before 1.3
+ )
+ readParallelPorts(*pelmHwChild, hw.llParallelPorts);
+ else if (pelmHwChild->nameEquals("AudioAdapter"))
+ readAudioAdapter(*pelmHwChild, hw.audioAdapter);
+ else if (pelmHwChild->nameEquals("SharedFolders"))
+ {
+ xml::NodesLoop nl2(*pelmHwChild, "SharedFolder");
+ const xml::ElementNode *pelmFolder;
+ while ((pelmFolder = nl2.forAllNodes()))
+ {
+ SharedFolder sf;
+ pelmFolder->getAttributeValue("name", sf.strName);
+ pelmFolder->getAttributeValue("hostPath", sf.strHostPath);
+ pelmFolder->getAttributeValue("writable", sf.fWritable);
+ pelmFolder->getAttributeValue("autoMount", sf.fAutoMount);
+ pelmFolder->getAttributeValue("autoMountPoint", sf.strAutoMountPoint);
+ hw.llSharedFolders.push_back(sf);
+ }
+ }
+ else if (pelmHwChild->nameEquals("Clipboard"))
+ {
+ Utf8Str strTemp;
+ if (pelmHwChild->getAttributeValue("mode", strTemp))
+ {
+ if (strTemp == "Disabled")
+ hw.clipboardMode = ClipboardMode_Disabled;
+ else if (strTemp == "HostToGuest")
+ hw.clipboardMode = ClipboardMode_HostToGuest;
+ else if (strTemp == "GuestToHost")
+ hw.clipboardMode = ClipboardMode_GuestToHost;
+ else if (strTemp == "Bidirectional")
+ hw.clipboardMode = ClipboardMode_Bidirectional;
+ else
+ throw ConfigFileError(this, pelmHwChild, N_("Invalid value '%s' in Clipboard/@mode attribute"), strTemp.c_str());
+ }
+
+ pelmHwChild->getAttributeValue("fileTransfersEnabled", hw.fClipboardFileTransfersEnabled);
+ }
+ else if (pelmHwChild->nameEquals("DragAndDrop"))
+ {
+ Utf8Str strTemp;
+ if (pelmHwChild->getAttributeValue("mode", strTemp))
+ {
+ if (strTemp == "Disabled")
+ hw.dndMode = DnDMode_Disabled;
+ else if (strTemp == "HostToGuest")
+ hw.dndMode = DnDMode_HostToGuest;
+ else if (strTemp == "GuestToHost")
+ hw.dndMode = DnDMode_GuestToHost;
+ else if (strTemp == "Bidirectional")
+ hw.dndMode = DnDMode_Bidirectional;
+ else
+ throw ConfigFileError(this, pelmHwChild, N_("Invalid value '%s' in DragAndDrop/@mode attribute"), strTemp.c_str());
+ }
+ }
+ else if (pelmHwChild->nameEquals("Guest"))
+ {
+ if (!pelmHwChild->getAttributeValue("memoryBalloonSize", hw.ulMemoryBalloonSize))
+ pelmHwChild->getAttributeValue("MemoryBalloonSize", hw.ulMemoryBalloonSize); // used before 1.3
+ }
+ else if (pelmHwChild->nameEquals("GuestProperties"))
+ readGuestProperties(*pelmHwChild, hw);
+ else if (pelmHwChild->nameEquals("IO"))
+ {
+ const xml::ElementNode *pelmBwGroups;
+ const xml::ElementNode *pelmIOChild;
+
+ if ((pelmIOChild = pelmHwChild->findChildElement("IoCache")))
+ {
+ pelmIOChild->getAttributeValue("enabled", hw.ioSettings.fIOCacheEnabled);
+ pelmIOChild->getAttributeValue("size", hw.ioSettings.ulIOCacheSize);
+ }
+
+ if ((pelmBwGroups = pelmHwChild->findChildElement("BandwidthGroups")))
+ {
+ xml::NodesLoop nl2(*pelmBwGroups, "BandwidthGroup");
+ const xml::ElementNode *pelmBandwidthGroup;
+ while ((pelmBandwidthGroup = nl2.forAllNodes()))
+ {
+ BandwidthGroup gr;
+ Utf8Str strTemp;
+
+ pelmBandwidthGroup->getAttributeValue("name", gr.strName);
+
+ if (pelmBandwidthGroup->getAttributeValue("type", strTemp))
+ {
+ if (strTemp == "Disk")
+ gr.enmType = BandwidthGroupType_Disk;
+ else if (strTemp == "Network")
+ gr.enmType = BandwidthGroupType_Network;
+ else
+ throw ConfigFileError(this, pelmBandwidthGroup, N_("Invalid value '%s' in BandwidthGroup/@type attribute"), strTemp.c_str());
+ }
+ else
+ throw ConfigFileError(this, pelmBandwidthGroup, N_("Missing BandwidthGroup/@type attribute"));
+
+ if (!pelmBandwidthGroup->getAttributeValue("maxBytesPerSec", gr.cMaxBytesPerSec))
+ {
+ pelmBandwidthGroup->getAttributeValue("maxMbPerSec", gr.cMaxBytesPerSec);
+ gr.cMaxBytesPerSec *= _1M;
+ }
+ hw.ioSettings.llBandwidthGroups.push_back(gr);
+ }
+ }
+ }
+ else if (pelmHwChild->nameEquals("HostPci"))
+ {
+ const xml::ElementNode *pelmDevices;
+
+ if ((pelmDevices = pelmHwChild->findChildElement("Devices")))
+ {
+ xml::NodesLoop nl2(*pelmDevices, "Device");
+ const xml::ElementNode *pelmDevice;
+ while ((pelmDevice = nl2.forAllNodes()))
+ {
+ HostPCIDeviceAttachment hpda;
+
+ if (!pelmDevice->getAttributeValue("host", hpda.uHostAddress))
+ throw ConfigFileError(this, pelmDevice, N_("Missing Device/@host attribute"));
+
+ if (!pelmDevice->getAttributeValue("guest", hpda.uGuestAddress))
+ throw ConfigFileError(this, pelmDevice, N_("Missing Device/@guest attribute"));
+
+ /* name is optional */
+ pelmDevice->getAttributeValue("name", hpda.strDeviceName);
+
+ hw.pciAttachments.push_back(hpda);
+ }
+ }
+ }
+ else if (pelmHwChild->nameEquals("EmulatedUSB"))
+ {
+ const xml::ElementNode *pelmCardReader;
+
+ if ((pelmCardReader = pelmHwChild->findChildElement("CardReader")))
+ {
+ pelmCardReader->getAttributeValue("enabled", hw.fEmulatedUSBCardReader);
+ }
+ }
+ else if (pelmHwChild->nameEquals("Frontend"))
+ {
+ const xml::ElementNode *pelmDefault;
+
+ if ((pelmDefault = pelmHwChild->findChildElement("Default")))
+ {
+ pelmDefault->getAttributeValue("type", hw.strDefaultFrontend);
+ }
+ }
+ else if (pelmHwChild->nameEquals("StorageControllers"))
+ readStorageControllers(*pelmHwChild, hw.storage);
+ }
+
+ if (hw.ulMemorySizeMB == (uint32_t)-1)
+ throw ConfigFileError(this, &elmHardware, N_("Required Memory/@RAMSize element/attribute is missing"));
+}
+
+/**
+ * This gets called instead of readStorageControllers() for legacy pre-1.7 settings
+ * files which have a \<HardDiskAttachments\> node and storage controller settings
+ * hidden in the \<Hardware\> settings. We set the StorageControllers fields just the
+ * same, just from different sources.
+ * @param elmHardDiskAttachments \<HardDiskAttachments\> XML node.
+ * @param strg
+ */
+void MachineConfigFile::readHardDiskAttachments_pre1_7(const xml::ElementNode &elmHardDiskAttachments,
+ Storage &strg)
+{
+ StorageController *pIDEController = NULL;
+ StorageController *pSATAController = NULL;
+
+ for (StorageControllersList::iterator it = strg.llStorageControllers.begin();
+ it != strg.llStorageControllers.end();
+ ++it)
+ {
+ StorageController &s = *it;
+ if (s.storageBus == StorageBus_IDE)
+ pIDEController = &s;
+ else if (s.storageBus == StorageBus_SATA)
+ pSATAController = &s;
+ }
+
+ xml::NodesLoop nl1(elmHardDiskAttachments, "HardDiskAttachment");
+ const xml::ElementNode *pelmAttachment;
+ while ((pelmAttachment = nl1.forAllNodes()))
+ {
+ AttachedDevice att;
+ Utf8Str strUUID, strBus;
+
+ if (!pelmAttachment->getAttributeValue("hardDisk", strUUID))
+ throw ConfigFileError(this, pelmAttachment, N_("Required HardDiskAttachment/@hardDisk attribute is missing"));
+ parseUUID(att.uuid, strUUID, pelmAttachment);
+
+ if (!pelmAttachment->getAttributeValue("bus", strBus))
+ throw ConfigFileError(this, pelmAttachment, N_("Required HardDiskAttachment/@bus attribute is missing"));
+ // pre-1.7 'channel' is now port
+ if (!pelmAttachment->getAttributeValue("channel", att.lPort))
+ throw ConfigFileError(this, pelmAttachment, N_("Required HardDiskAttachment/@channel attribute is missing"));
+ // pre-1.7 'device' is still device
+ if (!pelmAttachment->getAttributeValue("device", att.lDevice))
+ throw ConfigFileError(this, pelmAttachment, N_("Required HardDiskAttachment/@device attribute is missing"));
+
+ att.deviceType = DeviceType_HardDisk;
+
+ if (strBus == "IDE")
+ {
+ if (!pIDEController)
+ throw ConfigFileError(this, pelmAttachment, N_("HardDiskAttachment/@bus is 'IDE' but cannot find IDE controller"));
+ pIDEController->llAttachedDevices.push_back(att);
+ }
+ else if (strBus == "SATA")
+ {
+ if (!pSATAController)
+ throw ConfigFileError(this, pelmAttachment, N_("HardDiskAttachment/@bus is 'SATA' but cannot find SATA controller"));
+ pSATAController->llAttachedDevices.push_back(att);
+ }
+ else
+ throw ConfigFileError(this, pelmAttachment, N_("HardDiskAttachment/@bus attribute has illegal value '%s'"), strBus.c_str());
+ }
+}
+
+/**
+ * Reads in a \<StorageControllers\> block and stores it in the given Storage structure.
+ * Used both directly from readMachine and from readSnapshot, since snapshots
+ * have their own storage controllers sections.
+ *
+ * This is only called for settings version 1.7 and above; see readHardDiskAttachments_pre1_7()
+ * for earlier versions.
+ *
+ * @param elmStorageControllers
+ * @param strg
+ */
+void MachineConfigFile::readStorageControllers(const xml::ElementNode &elmStorageControllers,
+ Storage &strg)
+{
+ xml::NodesLoop nlStorageControllers(elmStorageControllers, "StorageController");
+ const xml::ElementNode *pelmController;
+ while ((pelmController = nlStorageControllers.forAllNodes()))
+ {
+ StorageController sctl;
+
+ if (!pelmController->getAttributeValue("name", sctl.strName))
+ throw ConfigFileError(this, pelmController, N_("Required StorageController/@name attribute is missing"));
+ // canonicalize storage controller names for configs in the switchover
+ // period.
+ if (m->sv < SettingsVersion_v1_9)
+ {
+ if (sctl.strName == "IDE")
+ sctl.strName = "IDE Controller";
+ else if (sctl.strName == "SATA")
+ sctl.strName = "SATA Controller";
+ else if (sctl.strName == "SCSI")
+ sctl.strName = "SCSI Controller";
+ }
+
+ pelmController->getAttributeValue("Instance", sctl.ulInstance);
+ // default from constructor is 0
+
+ pelmController->getAttributeValue("Bootable", sctl.fBootable);
+ // default from constructor is true which is true
+ // for settings below version 1.11 because they allowed only
+ // one controller per type.
+
+ Utf8Str strType;
+ if (!pelmController->getAttributeValue("type", strType))
+ throw ConfigFileError(this, pelmController, N_("Required StorageController/@type attribute is missing"));
+
+ if (strType == "AHCI")
+ {
+ sctl.storageBus = StorageBus_SATA;
+ sctl.controllerType = StorageControllerType_IntelAhci;
+ }
+ else if (strType == "LsiLogic")
+ {
+ sctl.storageBus = StorageBus_SCSI;
+ sctl.controllerType = StorageControllerType_LsiLogic;
+ }
+ else if (strType == "BusLogic")
+ {
+ sctl.storageBus = StorageBus_SCSI;
+ sctl.controllerType = StorageControllerType_BusLogic;
+ }
+ else if (strType == "PIIX3")
+ {
+ sctl.storageBus = StorageBus_IDE;
+ sctl.controllerType = StorageControllerType_PIIX3;
+ }
+ else if (strType == "PIIX4")
+ {
+ sctl.storageBus = StorageBus_IDE;
+ sctl.controllerType = StorageControllerType_PIIX4;
+ }
+ else if (strType == "ICH6")
+ {
+ sctl.storageBus = StorageBus_IDE;
+ sctl.controllerType = StorageControllerType_ICH6;
+ }
+ else if ( (m->sv >= SettingsVersion_v1_9)
+ && (strType == "I82078")
+ )
+ {
+ sctl.storageBus = StorageBus_Floppy;
+ sctl.controllerType = StorageControllerType_I82078;
+ }
+ else if (strType == "LsiLogicSas")
+ {
+ sctl.storageBus = StorageBus_SAS;
+ sctl.controllerType = StorageControllerType_LsiLogicSas;
+ }
+ else if (strType == "USB")
+ {
+ sctl.storageBus = StorageBus_USB;
+ sctl.controllerType = StorageControllerType_USB;
+ }
+ else if (strType == "NVMe")
+ {
+ sctl.storageBus = StorageBus_PCIe;
+ sctl.controllerType = StorageControllerType_NVMe;
+ }
+ else if (strType == "VirtioSCSI")
+ {
+ sctl.storageBus = StorageBus_VirtioSCSI;
+ sctl.controllerType = StorageControllerType_VirtioSCSI;
+ }
+ else
+ throw ConfigFileError(this, pelmController, N_("Invalid value '%s' for StorageController/@type attribute"), strType.c_str());
+
+ readStorageControllerAttributes(*pelmController, sctl);
+
+ xml::NodesLoop nlAttached(*pelmController, "AttachedDevice");
+ const xml::ElementNode *pelmAttached;
+ while ((pelmAttached = nlAttached.forAllNodes()))
+ {
+ AttachedDevice att;
+ Utf8Str strTemp;
+ pelmAttached->getAttributeValue("type", strTemp);
+
+ att.fDiscard = false;
+ att.fNonRotational = false;
+ att.fHotPluggable = false;
+ att.fPassThrough = false;
+
+ if (strTemp == "HardDisk")
+ {
+ att.deviceType = DeviceType_HardDisk;
+ pelmAttached->getAttributeValue("nonrotational", att.fNonRotational);
+ pelmAttached->getAttributeValue("discard", att.fDiscard);
+ }
+ else if (m->sv >= SettingsVersion_v1_9)
+ {
+ // starting with 1.9 we list DVD and floppy drive info + attachments under <StorageControllers>
+ if (strTemp == "DVD")
+ {
+ att.deviceType = DeviceType_DVD;
+ pelmAttached->getAttributeValue("passthrough", att.fPassThrough);
+ pelmAttached->getAttributeValue("tempeject", att.fTempEject);
+ }
+ else if (strTemp == "Floppy")
+ att.deviceType = DeviceType_Floppy;
+ }
+
+ if (att.deviceType != DeviceType_Null)
+ {
+ const xml::ElementNode *pelmImage;
+ // all types can have images attached, but for HardDisk it's required
+ if (!(pelmImage = pelmAttached->findChildElement("Image")))
+ {
+ if (att.deviceType == DeviceType_HardDisk)
+ throw ConfigFileError(this, pelmImage, N_("Required AttachedDevice/Image element is missing"));
+ else
+ {
+ // DVDs and floppies can also have <HostDrive> instead of <Image>
+ const xml::ElementNode *pelmHostDrive;
+ if ((pelmHostDrive = pelmAttached->findChildElement("HostDrive")))
+ if (!pelmHostDrive->getAttributeValue("src", att.strHostDriveSrc))
+ throw ConfigFileError(this, pelmHostDrive, N_("Required AttachedDevice/HostDrive/@src attribute is missing"));
+ }
+ }
+ else
+ {
+ if (!pelmImage->getAttributeValue("uuid", strTemp))
+ throw ConfigFileError(this, pelmImage, N_("Required AttachedDevice/Image/@uuid attribute is missing"));
+ parseUUID(att.uuid, strTemp, pelmImage);
+ }
+
+ if (!pelmAttached->getAttributeValue("port", att.lPort))
+ throw ConfigFileError(this, pelmImage, N_("Required AttachedDevice/@port attribute is missing"));
+ if (!pelmAttached->getAttributeValue("device", att.lDevice))
+ throw ConfigFileError(this, pelmImage, N_("Required AttachedDevice/@device attribute is missing"));
+
+ /* AHCI controller ports are hotpluggable by default, keep compatibility with existing settings. */
+ if (m->sv >= SettingsVersion_v1_15)
+ pelmAttached->getAttributeValue("hotpluggable", att.fHotPluggable);
+ else if (sctl.controllerType == StorageControllerType_IntelAhci)
+ att.fHotPluggable = true;
+
+ pelmAttached->getAttributeValue("bandwidthGroup", att.strBwGroup);
+ sctl.llAttachedDevices.push_back(att);
+ }
+ }
+
+ strg.llStorageControllers.push_back(sctl);
+ }
+}
+
+/**
+ * This gets called for legacy pre-1.9 settings files after having parsed the
+ * \<Hardware\> and \<StorageControllers\> sections to parse \<Hardware\> once more
+ * for the \<DVDDrive\> and \<FloppyDrive\> sections.
+ *
+ * Before settings version 1.9, DVD and floppy drives were specified separately
+ * under \<Hardware\>; we then need this extra loop to make sure the storage
+ * controller structs are already set up so we can add stuff to them.
+ *
+ * @param elmHardware
+ * @param strg
+ */
+void MachineConfigFile::readDVDAndFloppies_pre1_9(const xml::ElementNode &elmHardware,
+ Storage &strg)
+{
+ xml::NodesLoop nl1(elmHardware);
+ const xml::ElementNode *pelmHwChild;
+ while ((pelmHwChild = nl1.forAllNodes()))
+ {
+ if (pelmHwChild->nameEquals("DVDDrive"))
+ {
+ // create a DVD "attached device" and attach it to the existing IDE controller
+ AttachedDevice att;
+ att.deviceType = DeviceType_DVD;
+ // legacy DVD drive is always secondary master (port 1, device 0)
+ att.lPort = 1;
+ att.lDevice = 0;
+ pelmHwChild->getAttributeValue("passthrough", att.fPassThrough);
+ pelmHwChild->getAttributeValue("tempeject", att.fTempEject);
+
+ const xml::ElementNode *pDriveChild;
+ Utf8Str strTmp;
+ if ( (pDriveChild = pelmHwChild->findChildElement("Image")) != NULL
+ && pDriveChild->getAttributeValue("uuid", strTmp))
+ parseUUID(att.uuid, strTmp, pDriveChild);
+ else if ((pDriveChild = pelmHwChild->findChildElement("HostDrive")))
+ pDriveChild->getAttributeValue("src", att.strHostDriveSrc);
+
+ // find the IDE controller and attach the DVD drive
+ bool fFound = false;
+ for (StorageControllersList::iterator it = strg.llStorageControllers.begin();
+ it != strg.llStorageControllers.end();
+ ++it)
+ {
+ StorageController &sctl = *it;
+ if (sctl.storageBus == StorageBus_IDE)
+ {
+ sctl.llAttachedDevices.push_back(att);
+ fFound = true;
+ break;
+ }
+ }
+
+ if (!fFound)
+ throw ConfigFileError(this, pelmHwChild, N_("Internal error: found DVD drive but IDE controller does not exist"));
+ // shouldn't happen because pre-1.9 settings files always had at least one IDE controller in the settings
+ // which should have gotten parsed in <StorageControllers> before this got called
+ }
+ else if (pelmHwChild->nameEquals("FloppyDrive"))
+ {
+ bool fEnabled;
+ if ( pelmHwChild->getAttributeValue("enabled", fEnabled)
+ && fEnabled)
+ {
+ // create a new floppy controller and attach a floppy "attached device"
+ StorageController sctl;
+ sctl.strName = "Floppy Controller";
+ sctl.storageBus = StorageBus_Floppy;
+ sctl.controllerType = StorageControllerType_I82078;
+ sctl.ulPortCount = 1;
+
+ AttachedDevice att;
+ att.deviceType = DeviceType_Floppy;
+ att.lPort = 0;
+ att.lDevice = 0;
+
+ const xml::ElementNode *pDriveChild;
+ Utf8Str strTmp;
+ if ( (pDriveChild = pelmHwChild->findChildElement("Image"))
+ && pDriveChild->getAttributeValue("uuid", strTmp) )
+ parseUUID(att.uuid, strTmp, pDriveChild);
+ else if ((pDriveChild = pelmHwChild->findChildElement("HostDrive")))
+ pDriveChild->getAttributeValue("src", att.strHostDriveSrc);
+
+ // store attachment with controller
+ sctl.llAttachedDevices.push_back(att);
+ // store controller with storage
+ strg.llStorageControllers.push_back(sctl);
+ }
+ }
+ }
+}
+
+/**
+ * Called for reading the \<Teleporter\> element under \<Machine\>.
+ */
+void MachineConfigFile::readTeleporter(const xml::ElementNode &elmTeleporter,
+ MachineUserData &userData)
+{
+ elmTeleporter.getAttributeValue("enabled", userData.fTeleporterEnabled);
+ elmTeleporter.getAttributeValue("port", userData.uTeleporterPort);
+ elmTeleporter.getAttributeValue("address", userData.strTeleporterAddress);
+ elmTeleporter.getAttributeValue("password", userData.strTeleporterPassword);
+
+ if ( userData.strTeleporterPassword.isNotEmpty()
+ && !VBoxIsPasswordHashed(&userData.strTeleporterPassword))
+ VBoxHashPassword(&userData.strTeleporterPassword);
+}
+
+/**
+ * Called for reading the \<Debugging\> element under \<Machine\> or \<Snapshot\>.
+ */
+void MachineConfigFile::readDebugging(const xml::ElementNode &elmDebugging, Debugging &dbg)
+{
+ if (m->sv < SettingsVersion_v1_13)
+ return;
+
+ const xml::ElementNode *pelmTracing = elmDebugging.findChildElement("Tracing");
+ if (pelmTracing)
+ {
+ pelmTracing->getAttributeValue("enabled", dbg.fTracingEnabled);
+ pelmTracing->getAttributeValue("allowTracingToAccessVM", dbg.fAllowTracingToAccessVM);
+ pelmTracing->getAttributeValue("config", dbg.strTracingConfig);
+ }
+
+ const xml::ElementNode *pelmGuestDebug = elmDebugging.findChildElement("GuestDebug");
+ if (pelmGuestDebug)
+ {
+ Utf8Str strTmp;
+ pelmGuestDebug->getAttributeValue("provider", strTmp);
+ if (strTmp == "None")
+ dbg.enmDbgProvider = GuestDebugProvider_None;
+ else if (strTmp == "GDB")
+ dbg.enmDbgProvider = GuestDebugProvider_GDB;
+ else if (strTmp == "KD")
+ dbg.enmDbgProvider = GuestDebugProvider_KD;
+ else
+ throw ConfigFileError(this, pelmGuestDebug, N_("Invalid value '%s' for GuestDebug/@provider attribute"), strTmp.c_str());
+
+ pelmGuestDebug->getAttributeValue("io", strTmp);
+ if (strTmp == "None")
+ dbg.enmIoProvider = GuestDebugIoProvider_None;
+ else if (strTmp == "TCP")
+ dbg.enmIoProvider = GuestDebugIoProvider_TCP;
+ else if (strTmp == "UDP")
+ dbg.enmIoProvider = GuestDebugIoProvider_UDP;
+ else if (strTmp == "IPC")
+ dbg.enmIoProvider = GuestDebugIoProvider_IPC;
+ else
+ throw ConfigFileError(this, pelmGuestDebug, N_("Invalid value '%s' for GuestDebug/@io attribute"), strTmp.c_str());
+
+ pelmGuestDebug->getAttributeValue("address", dbg.strAddress);
+ pelmGuestDebug->getAttributeValue("port", dbg.ulPort);
+ }
+}
+
+/**
+ * Called for reading the \<Autostart\> element under \<Machine\> or \<Snapshot\>.
+ */
+void MachineConfigFile::readAutostart(const xml::ElementNode &elmAutostart, Autostart &autostrt)
+{
+ Utf8Str strAutostop;
+
+ if (m->sv < SettingsVersion_v1_13)
+ return;
+
+ elmAutostart.getAttributeValue("enabled", autostrt.fAutostartEnabled);
+ elmAutostart.getAttributeValue("delay", autostrt.uAutostartDelay);
+ elmAutostart.getAttributeValue("autostop", strAutostop);
+ if (strAutostop == "Disabled")
+ autostrt.enmAutostopType = AutostopType_Disabled;
+ else if (strAutostop == "SaveState")
+ autostrt.enmAutostopType = AutostopType_SaveState;
+ else if (strAutostop == "PowerOff")
+ autostrt.enmAutostopType = AutostopType_PowerOff;
+ else if (strAutostop == "AcpiShutdown")
+ autostrt.enmAutostopType = AutostopType_AcpiShutdown;
+ else
+ throw ConfigFileError(this, &elmAutostart, N_("Invalid value '%s' for Autostart/@autostop attribute"), strAutostop.c_str());
+}
+
+/**
+ * Called for reading the \<VideoCapture\> element under \<Machine|Hardware\>,
+ * or \<Recording\> under \<Machine\>,
+ */
+void MachineConfigFile::readRecordingSettings(const xml::ElementNode &elmRecording, uint32_t cMonitors, RecordingSettings &recording)
+{
+ if (cMonitors > 64)
+ throw ConfigFileError(this, &elmRecording, N_("Invalid monitor count given"));
+
+ elmRecording.getAttributeValue("enabled", recording.common.fEnabled);
+
+ /* Note: Since settings 1.19 the recording settings have a dedicated XML branch "Recording" outside of "Hardware". */
+ if (m->sv >= SettingsVersion_v1_19 /* VBox >= 7.0 */)
+ {
+ uint32_t cScreens = 0;
+ elmRecording.getAttributeValue("screens", cScreens);
+
+ xml::ElementNodesList plstScreens;
+ elmRecording.getChildElements(plstScreens, "Screen");
+
+ /* Sanity checks. */
+ if (cScreens != plstScreens.size())
+ throw ConfigFileError(this, &elmRecording, N_("Recording/@screens attribute does not match stored screen objects"));
+ if (cScreens > 64)
+ throw ConfigFileError(this, &elmRecording, N_("Recording/@screens attribute is invalid"));
+
+ for (xml::ElementNodesList::iterator itScreen = plstScreens.begin();
+ itScreen != plstScreens.end();
+ ++itScreen)
+ {
+ /* The screen's stored ID is the monitor ID and also the key for the map. */
+ uint32_t idxScreen;
+ (*itScreen)->getAttributeValue("id", idxScreen);
+
+ RecordingScreenSettings &screenSettings = recording.mapScreens[idxScreen];
+
+ (*itScreen)->getAttributeValue("enabled", screenSettings.fEnabled);
+ Utf8Str strTemp;
+ (*itScreen)->getAttributeValue("featuresEnabled", strTemp);
+ RecordingScreenSettings::featuresFromString(strTemp, screenSettings.featureMap);
+ (*itScreen)->getAttributeValue("maxTimeS", screenSettings.ulMaxTimeS);
+ (*itScreen)->getAttributeValue("options", screenSettings.strOptions);
+ (*itScreen)->getAttributeValue("dest", (uint32_t &)screenSettings.enmDest);
+ if (screenSettings.enmDest == RecordingDestination_File)
+ (*itScreen)->getAttributeValuePath("file", screenSettings.File.strName);
+ else
+ throw ConfigFileError(this, (*itScreen),
+ N_("Not supported Recording/@dest attribute '%#x'"), screenSettings.enmDest);
+ (*itScreen)->getAttributeValue("maxSizeMB", screenSettings.File.ulMaxSizeMB);
+ if ((*itScreen)->getAttributeValue("videoCodec", strTemp)) /* Stick with default if not set. */
+ RecordingScreenSettings::videoCodecFromString(strTemp, screenSettings.Video.enmCodec);
+ (*itScreen)->getAttributeValue("videoDeadline", (uint32_t &)screenSettings.Video.enmDeadline);
+ (*itScreen)->getAttributeValue("videoRateCtlMode", (uint32_t &)screenSettings.Video.enmRateCtlMode);
+ (*itScreen)->getAttributeValue("videoScalingMode", (uint32_t &)screenSettings.Video.enmScalingMode);
+ (*itScreen)->getAttributeValue("horzRes", screenSettings.Video.ulWidth);
+ (*itScreen)->getAttributeValue("vertRes", screenSettings.Video.ulHeight);
+ (*itScreen)->getAttributeValue("rateKbps", screenSettings.Video.ulRate);
+ (*itScreen)->getAttributeValue("fps", screenSettings.Video.ulFPS);
+
+ if ((*itScreen)->getAttributeValue("audioCodec", strTemp)) /* Stick with default if not set. */
+ RecordingScreenSettings::audioCodecFromString(strTemp, screenSettings.Audio.enmCodec);
+ (*itScreen)->getAttributeValue("audioDeadline", (uint32_t &)screenSettings.Audio.enmDeadline);
+ (*itScreen)->getAttributeValue("audioRateCtlMode", (uint32_t &)screenSettings.Audio.enmRateCtlMode);
+ (*itScreen)->getAttributeValue("audioHz", (uint32_t &)screenSettings.Audio.uHz);
+ (*itScreen)->getAttributeValue("audioBits", (uint32_t &)screenSettings.Audio.cBits);
+ (*itScreen)->getAttributeValue("audioChannels", (uint32_t &)screenSettings.Audio.cChannels);
+ }
+ }
+ else if ( m->sv >= SettingsVersion_v1_14
+ && m->sv < SettingsVersion_v1_19 /* VBox < 7.0 */)
+ {
+ /* For settings < 1.19 (< VBox 7.0) we only support one recording configuration, that is,
+ * all screens have the same configuration. So load/save to/from screen 0. */
+ RecordingScreenSettings &screen0 = recording.mapScreens[0];
+
+ elmRecording.getAttributeValue("maxTime", screen0.ulMaxTimeS);
+ elmRecording.getAttributeValue("options", screen0.strOptions);
+ elmRecording.getAttributeValuePath("file", screen0.File.strName);
+ elmRecording.getAttributeValue("maxSize", screen0.File.ulMaxSizeMB);
+ elmRecording.getAttributeValue("horzRes", screen0.Video.ulWidth);
+ elmRecording.getAttributeValue("vertRes", screen0.Video.ulHeight);
+ elmRecording.getAttributeValue("rate", screen0.Video.ulRate);
+ elmRecording.getAttributeValue("fps", screen0.Video.ulFPS);
+
+ /* Convert the enabled screens to the former uint64_t bit array and vice versa. */
+ uint64_t uScreensBitmap = 0;
+ elmRecording.getAttributeValue("screens", uScreensBitmap);
+
+ /* Note: For settings < 1.19 the "screens" attribute is a bit field for all screens
+ * which are ENABLED for recording. The settings for recording are for all the same though. */
+ for (unsigned i = 0; i < cMonitors; i++)
+ {
+ /* Apply settings of screen 0 to screen i and enable it. */
+ recording.mapScreens[i] = screen0;
+
+ /* Screen i enabled? */
+ recording.mapScreens[i].idScreen = i;
+ recording.mapScreens[i].fEnabled = RT_BOOL(uScreensBitmap & RT_BIT_64(i));
+ }
+ }
+}
+
+/**
+ * Called for reading the \<Groups\> element under \<Machine\>.
+ */
+void MachineConfigFile::readGroups(const xml::ElementNode &elmGroups, StringsList &llGroups)
+{
+ llGroups.clear();
+ if (m->sv < SettingsVersion_v1_13)
+ {
+ llGroups.push_back("/");
+ return;
+ }
+
+ xml::NodesLoop nlGroups(elmGroups);
+ const xml::ElementNode *pelmGroup;
+ while ((pelmGroup = nlGroups.forAllNodes()))
+ {
+ if (pelmGroup->nameEquals("Group"))
+ {
+ Utf8Str strGroup;
+ if (!pelmGroup->getAttributeValue("name", strGroup))
+ throw ConfigFileError(this, pelmGroup, N_("Required Group/@name attribute is missing"));
+ llGroups.push_back(strGroup);
+ }
+ }
+}
+
+/**
+ * Called initially for the \<Snapshot\> element under \<Machine\>, if present,
+ * to store the snapshot's data into the given Snapshot structure (which is
+ * then the one in the Machine struct). This might process further elements
+ * of the snapshot tree if a \<Snapshots\> (plural) element is found in the
+ * snapshot, which should contain a list of child snapshots; such lists are
+ * maintained in the Snapshot structure.
+ *
+ * @param curSnapshotUuid
+ * @param elmSnapshot
+ * @param snap
+ * @returns true if curSnapshotUuid is in this snapshot subtree, otherwise false
+ */
+bool MachineConfigFile::readSnapshot(const Guid &curSnapshotUuid,
+ const xml::ElementNode &elmSnapshot,
+ Snapshot &snap)
+{
+ std::list<const xml::ElementNode *> llElementsTodo;
+ llElementsTodo.push_back(&elmSnapshot);
+ std::list<Snapshot *> llSettingsTodo;
+ llSettingsTodo.push_back(&snap);
+ std::list<uint32_t> llDepthsTodo;
+ llDepthsTodo.push_back(1);
+
+ bool foundCurrentSnapshot = false;
+
+ while (llElementsTodo.size() > 0)
+ {
+ const xml::ElementNode *pElement = llElementsTodo.front();
+ llElementsTodo.pop_front();
+ Snapshot *pSnap = llSettingsTodo.front();
+ llSettingsTodo.pop_front();
+ uint32_t depth = llDepthsTodo.front();
+ llDepthsTodo.pop_front();
+
+ if (depth > SETTINGS_SNAPSHOT_DEPTH_MAX)
+ throw ConfigFileError(this, pElement, N_("Maximum snapshot tree depth of %u exceeded"), SETTINGS_SNAPSHOT_DEPTH_MAX);
+
+ Utf8Str strTemp;
+ if (!pElement->getAttributeValue("uuid", strTemp))
+ throw ConfigFileError(this, pElement, N_("Required Snapshot/@uuid attribute is missing"));
+ parseUUID(pSnap->uuid, strTemp, pElement);
+ foundCurrentSnapshot |= (pSnap->uuid == curSnapshotUuid);
+
+ if (!pElement->getAttributeValue("name", pSnap->strName))
+ throw ConfigFileError(this, pElement, N_("Required Snapshot/@name attribute is missing"));
+
+ // 3.1 dev builds added Description as an attribute, read it silently
+ // and write it back as an element
+ pElement->getAttributeValue("Description", pSnap->strDescription);
+
+ if (!pElement->getAttributeValue("timeStamp", strTemp))
+ throw ConfigFileError(this, pElement, N_("Required Snapshot/@timeStamp attribute is missing"));
+ parseTimestamp(pSnap->timestamp, strTemp, pElement);
+
+ pElement->getAttributeValuePath("stateFile", pSnap->strStateFile); // online snapshots only
+
+ // parse Hardware before the other elements because other things depend on it
+ const xml::ElementNode *pelmHardware;
+ if (!(pelmHardware = pElement->findChildElement("Hardware")))
+ throw ConfigFileError(this, pElement, N_("Required Snapshot/@Hardware element is missing"));
+ readHardware(*pelmHardware, pSnap->hardware);
+
+ const xml::ElementNode *pelmSnapshots = NULL;
+
+ xml::NodesLoop nlSnapshotChildren(*pElement);
+ const xml::ElementNode *pelmSnapshotChild;
+ while ((pelmSnapshotChild = nlSnapshotChildren.forAllNodes()))
+ {
+ if (pelmSnapshotChild->nameEquals("Description"))
+ pSnap->strDescription = pelmSnapshotChild->getValue();
+ else if ( m->sv < SettingsVersion_v1_7
+ && pelmSnapshotChild->nameEquals("HardDiskAttachments"))
+ readHardDiskAttachments_pre1_7(*pelmSnapshotChild, pSnap->hardware.storage);
+ else if ( m->sv >= SettingsVersion_v1_7
+ && pelmSnapshotChild->nameEquals("StorageControllers"))
+ readStorageControllers(*pelmSnapshotChild, pSnap->hardware.storage);
+ else if (pelmSnapshotChild->nameEquals("Snapshots"))
+ {
+ if (pelmSnapshots)
+ throw ConfigFileError(this, pelmSnapshotChild, N_("Just a single Snapshots element is allowed"));
+ pelmSnapshots = pelmSnapshotChild;
+ }
+ }
+
+ if (m->sv < SettingsVersion_v1_9)
+ // go through Hardware once more to repair the settings controller structures
+ // with data from old DVDDrive and FloppyDrive elements
+ readDVDAndFloppies_pre1_9(*pelmHardware, pSnap->hardware.storage);
+
+ const xml::ElementNode *pelmDebugging = elmSnapshot.findChildElement("Debugging"); /** @todo r=andy Shouldn't this be pElement instead of elmSnapshot? Re-visit this! */
+ if (pelmDebugging)
+ readDebugging(*pelmDebugging, pSnap->debugging);
+ const xml::ElementNode *pelmAutostart = elmSnapshot.findChildElement("Autostart"); /** @todo r=andy Ditto. */
+ if (pelmAutostart)
+ readAutostart(*pelmAutostart, pSnap->autostart);
+ if (m->sv < SettingsVersion_v1_19)
+ {
+ const xml::ElementNode *pelmVideoCapture = pElement->findChildElement("VideoCapture");
+ if (pelmVideoCapture)
+ readRecordingSettings(*pelmVideoCapture, pSnap->hardware.graphicsAdapter.cMonitors, pSnap->recordingSettings);
+ }
+ else /* >= VBox 7.0 */
+ {
+ const xml::ElementNode *pelmRecording = pElement->findChildElement("Recording");
+ if (pelmRecording)
+ readRecordingSettings(*pelmRecording, pSnap->hardware.graphicsAdapter.cMonitors, pSnap->recordingSettings);
+ }
+ // note: Groups exist only for Machine, not for Snapshot
+
+ // process all child snapshots
+ if (pelmSnapshots)
+ {
+ xml::NodesLoop nlChildSnapshots(*pelmSnapshots);
+ const xml::ElementNode *pelmChildSnapshot;
+ while ((pelmChildSnapshot = nlChildSnapshots.forAllNodes()))
+ {
+ if (pelmChildSnapshot->nameEquals("Snapshot"))
+ {
+ llElementsTodo.push_back(pelmChildSnapshot);
+ pSnap->llChildSnapshots.push_back(Snapshot::Empty);
+ llSettingsTodo.push_back(&pSnap->llChildSnapshots.back());
+ llDepthsTodo.push_back(depth + 1);
+ }
+ }
+ }
+ }
+
+ return foundCurrentSnapshot;
+}
+
+const struct {
+ const char *pcszOld;
+ const char *pcszNew;
+} aConvertOSTypes[] =
+{
+ { "unknown", "Other" },
+ { "dos", "DOS" },
+ { "win31", "Windows31" },
+ { "win95", "Windows95" },
+ { "win98", "Windows98" },
+ { "winme", "WindowsMe" },
+ { "winnt4", "WindowsNT4" },
+ { "win2k", "Windows2000" },
+ { "winxp", "WindowsXP" },
+ { "win2k3", "Windows2003" },
+ { "winvista", "WindowsVista" },
+ { "win2k8", "Windows2008" },
+ { "os2warp3", "OS2Warp3" },
+ { "os2warp4", "OS2Warp4" },
+ { "os2warp45", "OS2Warp45" },
+ { "ecs", "OS2eCS" },
+ { "linux22", "Linux22" },
+ { "linux24", "Linux24" },
+ { "linux26", "Linux26" },
+ { "archlinux", "ArchLinux" },
+ { "debian", "Debian" },
+ { "opensuse", "OpenSUSE" },
+ { "fedoracore", "Fedora" },
+ { "gentoo", "Gentoo" },
+ { "mandriva", "Mandriva" },
+ { "redhat", "RedHat" },
+ { "ubuntu", "Ubuntu" },
+ { "xandros", "Xandros" },
+ { "freebsd", "FreeBSD" },
+ { "openbsd", "OpenBSD" },
+ { "netbsd", "NetBSD" },
+ { "netware", "Netware" },
+ { "solaris", "Solaris" },
+ { "opensolaris", "OpenSolaris" },
+ { "l4", "L4" }
+};
+
+void MachineConfigFile::convertOldOSType_pre1_5(Utf8Str &str)
+{
+ for (unsigned u = 0;
+ u < RT_ELEMENTS(aConvertOSTypes);
+ ++u)
+ {
+ if (str == aConvertOSTypes[u].pcszOld)
+ {
+ str = aConvertOSTypes[u].pcszNew;
+ break;
+ }
+ }
+}
+
+/**
+ * Called from the constructor to actually read in the \<Machine\> element
+ * of a machine config file.
+ * @param elmMachine
+ */
+void MachineConfigFile::readMachine(const xml::ElementNode &elmMachine)
+{
+ Utf8Str strUUID;
+ if ( elmMachine.getAttributeValue("uuid", strUUID)
+ && elmMachine.getAttributeValue("name", machineUserData.strName))
+ {
+ parseUUID(uuid, strUUID, &elmMachine);
+
+ elmMachine.getAttributeValue("directoryIncludesUUID", machineUserData.fDirectoryIncludesUUID);
+ elmMachine.getAttributeValue("nameSync", machineUserData.fNameSync);
+
+ Utf8Str str;
+ elmMachine.getAttributeValue("Description", machineUserData.strDescription);
+ elmMachine.getAttributeValue("OSType", machineUserData.strOsType);
+ if (m->sv < SettingsVersion_v1_5)
+ convertOldOSType_pre1_5(machineUserData.strOsType);
+
+ elmMachine.getAttributeValue("stateKeyId", strStateKeyId);
+ elmMachine.getAttributeValue("stateKeyStore", strStateKeyStore);
+ elmMachine.getAttributeValuePath("stateFile", strStateFile);
+
+ elmMachine.getAttributeValue("logKeyId", strLogKeyId);
+ elmMachine.getAttributeValue("logKeyStore", strLogKeyStore);
+
+ if (elmMachine.getAttributeValue("currentSnapshot", str))
+ parseUUID(uuidCurrentSnapshot, str, &elmMachine);
+
+ elmMachine.getAttributeValuePath("snapshotFolder", machineUserData.strSnapshotFolder);
+
+ if (!elmMachine.getAttributeValue("currentStateModified", fCurrentStateModified))
+ fCurrentStateModified = true;
+ if (elmMachine.getAttributeValue("lastStateChange", str))
+ parseTimestamp(timeLastStateChange, str, &elmMachine);
+ // constructor has called RTTimeNow(&timeLastStateChange) before
+ if (elmMachine.getAttributeValue("aborted", fAborted))
+ fAborted = true;
+
+ {
+ Utf8Str strVMPriority;
+ if (elmMachine.getAttributeValue("processPriority", strVMPriority))
+ {
+ if (strVMPriority == "Flat")
+ machineUserData.enmVMPriority = VMProcPriority_Flat;
+ else if (strVMPriority == "Low")
+ machineUserData.enmVMPriority = VMProcPriority_Low;
+ else if (strVMPriority == "Normal")
+ machineUserData.enmVMPriority = VMProcPriority_Normal;
+ else if (strVMPriority == "High")
+ machineUserData.enmVMPriority = VMProcPriority_High;
+ else
+ machineUserData.enmVMPriority = VMProcPriority_Default;
+ }
+ }
+
+ str.setNull();
+ elmMachine.getAttributeValue("icon", str);
+ parseBase64(machineUserData.ovIcon, str, &elmMachine);
+
+ // parse Hardware before the other elements because other things depend on it
+ const xml::ElementNode *pelmHardware;
+ if (!(pelmHardware = elmMachine.findChildElement("Hardware")))
+ throw ConfigFileError(this, &elmMachine, N_("Required Machine/Hardware element is missing"));
+ readHardware(*pelmHardware, hardwareMachine);
+
+ xml::NodesLoop nlRootChildren(elmMachine);
+ const xml::ElementNode *pelmMachineChild;
+ while ((pelmMachineChild = nlRootChildren.forAllNodes()))
+ {
+ if (pelmMachineChild->nameEquals("ExtraData"))
+ readExtraData(*pelmMachineChild,
+ mapExtraDataItems);
+ else if ( (m->sv < SettingsVersion_v1_7)
+ && (pelmMachineChild->nameEquals("HardDiskAttachments"))
+ )
+ readHardDiskAttachments_pre1_7(*pelmMachineChild, hardwareMachine.storage);
+ else if ( (m->sv >= SettingsVersion_v1_7)
+ && (pelmMachineChild->nameEquals("StorageControllers"))
+ )
+ readStorageControllers(*pelmMachineChild, hardwareMachine.storage);
+ else if (pelmMachineChild->nameEquals("Snapshot"))
+ {
+ if (uuidCurrentSnapshot.isZero())
+ throw ConfigFileError(this, &elmMachine, N_("Snapshots present but required Machine/@currentSnapshot attribute is missing"));
+ bool foundCurrentSnapshot = false;
+ // Work directly with the target list, because otherwise
+ // the entire snapshot settings tree will need to be copied,
+ // and the usual STL implementation needs a lot of stack space.
+ llFirstSnapshot.push_back(Snapshot::Empty);
+ // this will also read all child snapshots
+ foundCurrentSnapshot = readSnapshot(uuidCurrentSnapshot, *pelmMachineChild, llFirstSnapshot.back());
+ if (!foundCurrentSnapshot)
+ throw ConfigFileError(this, &elmMachine, N_("Snapshots present but none matches the UUID in the Machine/@currentSnapshot attribute"));
+ }
+ else if (pelmMachineChild->nameEquals("Description"))
+ machineUserData.strDescription = pelmMachineChild->getValue();
+ else if (pelmMachineChild->nameEquals("Teleporter"))
+ readTeleporter(*pelmMachineChild, machineUserData);
+ else if (pelmMachineChild->nameEquals("MediaRegistry"))
+ readMediaRegistry(*pelmMachineChild, mediaRegistry);
+ else if (pelmMachineChild->nameEquals("Debugging"))
+ readDebugging(*pelmMachineChild, debugging);
+ else if (pelmMachineChild->nameEquals("Autostart"))
+ readAutostart(*pelmMachineChild, autostart);
+ else if (pelmMachineChild->nameEquals("Groups"))
+ readGroups(*pelmMachineChild, machineUserData.llGroups);
+
+ if ( m->sv >= SettingsVersion_v1_14
+ && m->sv < SettingsVersion_v1_19
+ && pelmMachineChild->nameEquals("VideoCapture")) /* For settings >= 1.14 (< VBox 7.0). */
+ readRecordingSettings(*pelmMachineChild, hardwareMachine.graphicsAdapter.cMonitors, recordingSettings);
+ else if ( m->sv >= SettingsVersion_v1_19
+ && pelmMachineChild->nameEquals("Recording")) /* Only exists for settings >= 1.19 (VBox 7.0). */
+ readRecordingSettings(*pelmMachineChild, hardwareMachine.graphicsAdapter.cMonitors, recordingSettings);
+ }
+
+ if (m->sv < SettingsVersion_v1_9)
+ // go through Hardware once more to repair the settings controller structures
+ // with data from old DVDDrive and FloppyDrive elements
+ readDVDAndFloppies_pre1_9(*pelmHardware, hardwareMachine.storage);
+ }
+ else
+ throw ConfigFileError(this, &elmMachine, N_("Required Machine/@uuid or @name attributes is missing"));
+}
+
+/**
+ * Called from the constructor to decrypt the machine config and read
+ * data from it.
+ * @param elmMachine
+ * @param pCryptoIf Pointer to the cryptographic interface.
+ * @param pszPassword The password to decrypt the config with.
+ */
+void MachineConfigFile::readMachineEncrypted(const xml::ElementNode &elmMachine,
+ PCVBOXCRYPTOIF pCryptoIf = NULL,
+ const char *pszPassword = NULL)
+{
+ Utf8Str strUUID;
+ if (elmMachine.getAttributeValue("uuid", strUUID))
+ {
+ parseUUID(uuid, strUUID, &elmMachine);
+ if (!elmMachine.getAttributeValue("keyId", strKeyId))
+ throw ConfigFileError(this, &elmMachine, N_("Required MachineEncrypted/@keyId attribute is missing"));
+ if (!elmMachine.getAttributeValue("keyStore", strKeyStore))
+ throw ConfigFileError(this, &elmMachine, N_("Required MachineEncrypted/@keyStore attribute is missing"));
+
+ if (!pszPassword)
+ {
+ enmParseState = ParseState_PasswordError;
+ return;
+ }
+
+ VBOXCRYPTOCTX hCryptoCtx = NULL;
+ int rc = pCryptoIf->pfnCryptoCtxLoad(strKeyStore.c_str(), pszPassword, &hCryptoCtx);
+ if (RT_SUCCESS(rc))
+ {
+ com::Utf8Str str = elmMachine.getValue();
+ IconBlob abEncrypted; /** @todo Rename IconBlob because this is not about icons. */
+ /** @todo This is not nice. */
+ try
+ {
+ parseBase64(abEncrypted, str, &elmMachine);
+ }
+ catch(...)
+ {
+ int rc2 = pCryptoIf->pfnCryptoCtxDestroy(hCryptoCtx);
+ AssertRC(rc2);
+ throw;
+ }
+
+ IconBlob abDecrypted(abEncrypted.size());
+ size_t cbDecrypted = 0;
+ rc = pCryptoIf->pfnCryptoCtxDecrypt(hCryptoCtx, false /*fPartial*/,
+ &abEncrypted[0], abEncrypted.size(),
+ uuid.raw(), sizeof(RTUUID),
+ &abDecrypted[0], abDecrypted.size(), &cbDecrypted);
+ int rc2 = pCryptoIf->pfnCryptoCtxDestroy(hCryptoCtx);
+ AssertRC(rc2);
+
+ if (RT_SUCCESS(rc))
+ {
+ abDecrypted.resize(cbDecrypted);
+ xml::XmlMemParser parser;
+ xml::Document *pDoc = new xml::Document;
+ parser.read(&abDecrypted[0], abDecrypted.size(), m->strFilename, *pDoc);
+ xml::ElementNode *pelmRoot = pDoc->getRootElement();
+ if (!pelmRoot || !pelmRoot->nameEquals("Machine"))
+ throw ConfigFileError(this, pelmRoot, N_("Root element in Machine settings encrypted block must be \"Machine\""));
+ readMachine(*pelmRoot);
+ delete pDoc;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_ACCESS_DENIED)
+ enmParseState = ParseState_PasswordError;
+ else
+ throw ConfigFileError(this, &elmMachine, N_("Parsing config failed. (%Rrc)"), rc);
+ }
+ }
+ else
+ throw ConfigFileError(this, &elmMachine, N_("Required MachineEncrypted/@uuid attribute is missing"));
+}
+
+/**
+ * Creates a \<Hardware\> node under elmParent and then writes out the XML
+ * keys under that. Called for both the \<Machine\> node and for snapshots.
+ * @param elmParent
+ * @param hw
+ * @param fl
+ * @param pllElementsWithUuidAttributes
+ */
+void MachineConfigFile::buildHardwareXML(xml::ElementNode &elmParent,
+ const Hardware &hw,
+ uint32_t fl,
+ std::list<xml::ElementNode*> *pllElementsWithUuidAttributes)
+{
+ xml::ElementNode *pelmHardware = elmParent.createChild("Hardware");
+
+ if ( m->sv >= SettingsVersion_v1_4
+ && (m->sv < SettingsVersion_v1_7 ? hw.strVersion != "1" : hw.strVersion != "2"))
+ pelmHardware->setAttribute("version", hw.strVersion);
+
+ if ((m->sv >= SettingsVersion_v1_9)
+ && !hw.uuid.isZero()
+ && hw.uuid.isValid()
+ )
+ pelmHardware->setAttribute("uuid", hw.uuid.toStringCurly());
+
+ xml::ElementNode *pelmCPU = pelmHardware->createChild("CPU");
+
+ if (!hw.fHardwareVirt)
+ pelmCPU->createChild("HardwareVirtEx")->setAttribute("enabled", hw.fHardwareVirt);
+ if (!hw.fNestedPaging)
+ pelmCPU->createChild("HardwareVirtExNestedPaging")->setAttribute("enabled", hw.fNestedPaging);
+ if (!hw.fVPID)
+ pelmCPU->createChild("HardwareVirtExVPID")->setAttribute("enabled", hw.fVPID);
+ if (!hw.fUnrestrictedExecution)
+ pelmCPU->createChild("HardwareVirtExUX")->setAttribute("enabled", hw.fUnrestrictedExecution);
+ // PAE has too crazy default handling, must always save this setting.
+ pelmCPU->createChild("PAE")->setAttribute("enabled", hw.fPAE);
+ if (m->sv >= SettingsVersion_v1_16)
+ {
+ if (hw.fIBPBOnVMEntry || hw.fIBPBOnVMExit)
+ {
+ xml::ElementNode *pelmChild = pelmCPU->createChild("IBPBOn");
+ if (hw.fIBPBOnVMExit)
+ pelmChild->setAttribute("vmexit", hw.fIBPBOnVMExit);
+ if (hw.fIBPBOnVMEntry)
+ pelmChild->setAttribute("vmentry", hw.fIBPBOnVMEntry);
+ }
+ if (hw.fSpecCtrl)
+ pelmCPU->createChild("SpecCtrl")->setAttribute("enabled", hw.fSpecCtrl);
+ if (hw.fSpecCtrlByHost)
+ pelmCPU->createChild("SpecCtrlByHost")->setAttribute("enabled", hw.fSpecCtrlByHost);
+ if (!hw.fL1DFlushOnSched || hw.fL1DFlushOnVMEntry)
+ {
+ xml::ElementNode *pelmChild = pelmCPU->createChild("L1DFlushOn");
+ if (!hw.fL1DFlushOnSched)
+ pelmChild->setAttribute("scheduling", hw.fL1DFlushOnSched);
+ if (hw.fL1DFlushOnVMEntry)
+ pelmChild->setAttribute("vmentry", hw.fL1DFlushOnVMEntry);
+ }
+ if (!hw.fMDSClearOnSched || hw.fMDSClearOnVMEntry)
+ {
+ xml::ElementNode *pelmChild = pelmCPU->createChild("MDSClearOn");
+ if (!hw.fMDSClearOnSched)
+ pelmChild->setAttribute("scheduling", hw.fMDSClearOnSched);
+ if (hw.fMDSClearOnVMEntry)
+ pelmChild->setAttribute("vmentry", hw.fMDSClearOnVMEntry);
+ }
+ }
+ if (m->sv >= SettingsVersion_v1_17 && hw.fNestedHWVirt)
+ pelmCPU->createChild("NestedHWVirt")->setAttribute("enabled", hw.fNestedHWVirt);
+
+ if (m->sv >= SettingsVersion_v1_18 && !hw.fVirtVmsaveVmload)
+ pelmCPU->createChild("HardwareVirtExVirtVmsaveVmload")->setAttribute("enabled", hw.fVirtVmsaveVmload);
+
+ if (m->sv >= SettingsVersion_v1_14 && hw.enmLongMode != Hardware::LongMode_Legacy)
+ {
+ // LongMode has too crazy default handling, must always save this setting.
+ pelmCPU->createChild("LongMode")->setAttribute("enabled", hw.enmLongMode == Hardware::LongMode_Enabled);
+ }
+
+ if (hw.fTripleFaultReset)
+ pelmCPU->createChild("TripleFaultReset")->setAttribute("enabled", hw.fTripleFaultReset);
+ if (m->sv >= SettingsVersion_v1_14)
+ {
+ if (hw.fX2APIC)
+ pelmCPU->createChild("X2APIC")->setAttribute("enabled", hw.fX2APIC);
+ else if (!hw.fAPIC)
+ pelmCPU->createChild("APIC")->setAttribute("enabled", hw.fAPIC);
+ }
+ if (hw.cCPUs > 1)
+ pelmCPU->setAttribute("count", hw.cCPUs);
+ if (hw.ulCpuExecutionCap != 100)
+ pelmCPU->setAttribute("executionCap", hw.ulCpuExecutionCap);
+ if (hw.uCpuIdPortabilityLevel != 0)
+ pelmCPU->setAttribute("CpuIdPortabilityLevel", hw.uCpuIdPortabilityLevel);
+ if (!hw.strCpuProfile.equals("host") && hw.strCpuProfile.isNotEmpty())
+ pelmCPU->setAttribute("CpuProfile", hw.strCpuProfile);
+
+ // HardwareVirtExLargePages has too crazy default handling, must always save this setting.
+ pelmCPU->createChild("HardwareVirtExLargePages")->setAttribute("enabled", hw.fLargePages);
+
+ if (m->sv >= SettingsVersion_v1_9)
+ {
+ if (hw.fHardwareVirtForce)
+ pelmCPU->createChild("HardwareVirtForce")->setAttribute("enabled", hw.fHardwareVirtForce);
+ }
+
+ if (m->sv >= SettingsVersion_v1_9 && hw.fUseNativeApi)
+ pelmCPU->createChild("HardwareVirtExUseNativeApi")->setAttribute("enabled", hw.fUseNativeApi);
+
+ if (m->sv >= SettingsVersion_v1_10)
+ {
+ if (hw.fCpuHotPlug)
+ pelmCPU->setAttribute("hotplug", hw.fCpuHotPlug);
+
+ xml::ElementNode *pelmCpuTree = NULL;
+ for (CpuList::const_iterator it = hw.llCpus.begin();
+ it != hw.llCpus.end();
+ ++it)
+ {
+ const Cpu &cpu = *it;
+
+ if (pelmCpuTree == NULL)
+ pelmCpuTree = pelmCPU->createChild("CpuTree");
+
+ xml::ElementNode *pelmCpu = pelmCpuTree->createChild("Cpu");
+ pelmCpu->setAttribute("id", cpu.ulId);
+ }
+ }
+
+ xml::ElementNode *pelmCpuIdTree = NULL;
+ for (CpuIdLeafsList::const_iterator it = hw.llCpuIdLeafs.begin();
+ it != hw.llCpuIdLeafs.end();
+ ++it)
+ {
+ const CpuIdLeaf &leaf = *it;
+
+ if (pelmCpuIdTree == NULL)
+ pelmCpuIdTree = pelmCPU->createChild("CpuIdTree");
+
+ xml::ElementNode *pelmCpuIdLeaf = pelmCpuIdTree->createChild("CpuIdLeaf");
+ pelmCpuIdLeaf->setAttribute("id", leaf.idx);
+ if (leaf.idxSub != 0)
+ pelmCpuIdLeaf->setAttribute("subleaf", leaf.idxSub);
+ pelmCpuIdLeaf->setAttribute("eax", leaf.uEax);
+ pelmCpuIdLeaf->setAttribute("ebx", leaf.uEbx);
+ pelmCpuIdLeaf->setAttribute("ecx", leaf.uEcx);
+ pelmCpuIdLeaf->setAttribute("edx", leaf.uEdx);
+ }
+
+ xml::ElementNode *pelmMemory = pelmHardware->createChild("Memory");
+ pelmMemory->setAttribute("RAMSize", hw.ulMemorySizeMB);
+ if (m->sv >= SettingsVersion_v1_10)
+ {
+ if (hw.fPageFusionEnabled)
+ pelmMemory->setAttribute("PageFusion", hw.fPageFusionEnabled);
+ }
+
+ if ( (m->sv >= SettingsVersion_v1_9)
+ && (hw.firmwareType >= FirmwareType_EFI)
+ )
+ {
+ xml::ElementNode *pelmFirmware = pelmHardware->createChild("Firmware");
+ const char *pcszFirmware;
+
+ switch (hw.firmwareType)
+ {
+ case FirmwareType_EFI: pcszFirmware = "EFI"; break;
+ case FirmwareType_EFI32: pcszFirmware = "EFI32"; break;
+ case FirmwareType_EFI64: pcszFirmware = "EFI64"; break;
+ case FirmwareType_EFIDUAL: pcszFirmware = "EFIDUAL"; break;
+ default: pcszFirmware = "None"; break;
+ }
+ pelmFirmware->setAttribute("type", pcszFirmware);
+ }
+
+ if ( m->sv >= SettingsVersion_v1_10
+ && ( hw.pointingHIDType != PointingHIDType_PS2Mouse
+ || hw.keyboardHIDType != KeyboardHIDType_PS2Keyboard))
+ {
+ xml::ElementNode *pelmHID = pelmHardware->createChild("HID");
+ const char *pcszHID;
+
+ if (hw.pointingHIDType != PointingHIDType_PS2Mouse)
+ {
+ switch (hw.pointingHIDType)
+ {
+ case PointingHIDType_USBMouse: pcszHID = "USBMouse"; break;
+ case PointingHIDType_USBTablet: pcszHID = "USBTablet"; break;
+ case PointingHIDType_PS2Mouse: pcszHID = "PS2Mouse"; break;
+ case PointingHIDType_ComboMouse: pcszHID = "ComboMouse"; break;
+ case PointingHIDType_USBMultiTouch: pcszHID = "USBMultiTouch";break;
+ case PointingHIDType_USBMultiTouchScreenPlusPad: pcszHID = "USBMTScreenPlusPad";break;
+ case PointingHIDType_None: pcszHID = "None"; break;
+ default: Assert(false); pcszHID = "PS2Mouse"; break;
+ }
+ pelmHID->setAttribute("Pointing", pcszHID);
+ }
+
+ if (hw.keyboardHIDType != KeyboardHIDType_PS2Keyboard)
+ {
+ switch (hw.keyboardHIDType)
+ {
+ case KeyboardHIDType_USBKeyboard: pcszHID = "USBKeyboard"; break;
+ case KeyboardHIDType_PS2Keyboard: pcszHID = "PS2Keyboard"; break;
+ case KeyboardHIDType_ComboKeyboard: pcszHID = "ComboKeyboard"; break;
+ case KeyboardHIDType_None: pcszHID = "None"; break;
+ default: Assert(false); pcszHID = "PS2Keyboard"; break;
+ }
+ pelmHID->setAttribute("Keyboard", pcszHID);
+ }
+ }
+
+ if ( (m->sv >= SettingsVersion_v1_10)
+ && hw.fHPETEnabled
+ )
+ {
+ xml::ElementNode *pelmHPET = pelmHardware->createChild("HPET");
+ pelmHPET->setAttribute("enabled", hw.fHPETEnabled);
+ }
+
+ if ( (m->sv >= SettingsVersion_v1_11)
+ )
+ {
+ if (hw.chipsetType != ChipsetType_PIIX3)
+ {
+ xml::ElementNode *pelmChipset = pelmHardware->createChild("Chipset");
+ const char *pcszChipset;
+
+ switch (hw.chipsetType)
+ {
+ case ChipsetType_PIIX3: pcszChipset = "PIIX3"; break;
+ case ChipsetType_ICH9: pcszChipset = "ICH9"; break;
+ default: Assert(false); pcszChipset = "PIIX3"; break;
+ }
+ pelmChipset->setAttribute("type", pcszChipset);
+ }
+ }
+
+ if ( (m->sv >= SettingsVersion_v1_15)
+ && !hw.areParavirtDefaultSettings(m->sv)
+ )
+ {
+ const char *pcszParavirtProvider;
+ switch (hw.paravirtProvider)
+ {
+ case ParavirtProvider_None: pcszParavirtProvider = "None"; break;
+ case ParavirtProvider_Default: pcszParavirtProvider = "Default"; break;
+ case ParavirtProvider_Legacy: pcszParavirtProvider = "Legacy"; break;
+ case ParavirtProvider_Minimal: pcszParavirtProvider = "Minimal"; break;
+ case ParavirtProvider_HyperV: pcszParavirtProvider = "HyperV"; break;
+ case ParavirtProvider_KVM: pcszParavirtProvider = "KVM"; break;
+ default: Assert(false); pcszParavirtProvider = "None"; break;
+ }
+
+ xml::ElementNode *pelmParavirt = pelmHardware->createChild("Paravirt");
+ pelmParavirt->setAttribute("provider", pcszParavirtProvider);
+
+ if ( m->sv >= SettingsVersion_v1_16
+ && hw.strParavirtDebug.isNotEmpty())
+ pelmParavirt->setAttribute("debug", hw.strParavirtDebug);
+ }
+
+ if ( m->sv >= SettingsVersion_v1_19
+ && hw.iommuType != IommuType_None)
+ {
+ const char *pcszIommuType;
+ switch (hw.iommuType)
+ {
+ case IommuType_None: pcszIommuType = "None"; break;
+ case IommuType_Automatic: pcszIommuType = "Automatic"; break;
+ case IommuType_AMD: pcszIommuType = "AMD"; break;
+ case IommuType_Intel: pcszIommuType = "Intel"; break;
+ default: Assert(false); pcszIommuType = "None"; break;
+ }
+
+ xml::ElementNode *pelmIommu = pelmHardware->createChild("Iommu");
+ pelmIommu->setAttribute("type", pcszIommuType);
+ }
+
+ if (!hw.areBootOrderDefaultSettings())
+ {
+ xml::ElementNode *pelmBoot = pelmHardware->createChild("Boot");
+ for (BootOrderMap::const_iterator it = hw.mapBootOrder.begin();
+ it != hw.mapBootOrder.end();
+ ++it)
+ {
+ uint32_t i = it->first;
+ DeviceType_T type = it->second;
+ const char *pcszDevice;
+
+ switch (type)
+ {
+ case DeviceType_Floppy: pcszDevice = "Floppy"; break;
+ case DeviceType_DVD: pcszDevice = "DVD"; break;
+ case DeviceType_HardDisk: pcszDevice = "HardDisk"; break;
+ case DeviceType_Network: pcszDevice = "Network"; break;
+ default: /*case DeviceType_Null:*/ pcszDevice = "None"; break;
+ }
+
+ xml::ElementNode *pelmOrder = pelmBoot->createChild("Order");
+ pelmOrder->setAttribute("position",
+ i + 1); // XML is 1-based but internal data is 0-based
+ pelmOrder->setAttribute("device", pcszDevice);
+ }
+ }
+
+ if (!hw.graphicsAdapter.areDefaultSettings())
+ {
+ xml::ElementNode *pelmDisplay = pelmHardware->createChild("Display");
+ if (hw.graphicsAdapter.graphicsControllerType != GraphicsControllerType_VBoxVGA)
+ {
+ const char *pcszGraphics;
+ switch (hw.graphicsAdapter.graphicsControllerType)
+ {
+ case GraphicsControllerType_VBoxVGA: pcszGraphics = "VBoxVGA"; break;
+ case GraphicsControllerType_VMSVGA: pcszGraphics = "VMSVGA"; break;
+ case GraphicsControllerType_VBoxSVGA: pcszGraphics = "VBoxSVGA"; break;
+ default: /*case GraphicsControllerType_Null:*/ pcszGraphics = "None"; break;
+ }
+ pelmDisplay->setAttribute("controller", pcszGraphics);
+ }
+ if (hw.graphicsAdapter.ulVRAMSizeMB != 8)
+ pelmDisplay->setAttribute("VRAMSize", hw.graphicsAdapter.ulVRAMSizeMB);
+ if (hw.graphicsAdapter.cMonitors > 1)
+ pelmDisplay->setAttribute("monitorCount", hw.graphicsAdapter.cMonitors);
+ if (hw.graphicsAdapter.fAccelerate3D)
+ pelmDisplay->setAttribute("accelerate3D", hw.graphicsAdapter.fAccelerate3D);
+
+ if (m->sv >= SettingsVersion_v1_8)
+ {
+ if (hw.graphicsAdapter.fAccelerate2DVideo)
+ pelmDisplay->setAttribute("accelerate2DVideo", hw.graphicsAdapter.fAccelerate2DVideo);
+ }
+ }
+
+ if (!hw.vrdeSettings.areDefaultSettings(m->sv))
+ {
+ xml::ElementNode *pelmVRDE = pelmHardware->createChild("RemoteDisplay");
+ if (m->sv < SettingsVersion_v1_16 ? !hw.vrdeSettings.fEnabled : hw.vrdeSettings.fEnabled)
+ pelmVRDE->setAttribute("enabled", hw.vrdeSettings.fEnabled);
+ if (m->sv < SettingsVersion_v1_11)
+ {
+ /* In VBox 4.0 these attributes are replaced with "Properties". */
+ Utf8Str strPort;
+ StringsMap::const_iterator it = hw.vrdeSettings.mapProperties.find("TCP/Ports");
+ if (it != hw.vrdeSettings.mapProperties.end())
+ strPort = it->second;
+ if (!strPort.length())
+ strPort = "3389";
+ pelmVRDE->setAttribute("port", strPort);
+
+ Utf8Str strAddress;
+ it = hw.vrdeSettings.mapProperties.find("TCP/Address");
+ if (it != hw.vrdeSettings.mapProperties.end())
+ strAddress = it->second;
+ if (strAddress.length())
+ pelmVRDE->setAttribute("netAddress", strAddress);
+ }
+ if (hw.vrdeSettings.authType != AuthType_Null)
+ {
+ const char *pcszAuthType;
+ switch (hw.vrdeSettings.authType)
+ {
+ case AuthType_Guest: pcszAuthType = "Guest"; break;
+ case AuthType_External: pcszAuthType = "External"; break;
+ default: /*case AuthType_Null:*/ pcszAuthType = "Null"; break;
+ }
+ pelmVRDE->setAttribute("authType", pcszAuthType);
+ }
+
+ if (hw.vrdeSettings.ulAuthTimeout != 0 && hw.vrdeSettings.ulAuthTimeout != 5000)
+ pelmVRDE->setAttribute("authTimeout", hw.vrdeSettings.ulAuthTimeout);
+ if (hw.vrdeSettings.fAllowMultiConnection)
+ pelmVRDE->setAttribute("allowMultiConnection", hw.vrdeSettings.fAllowMultiConnection);
+ if (hw.vrdeSettings.fReuseSingleConnection)
+ pelmVRDE->setAttribute("reuseSingleConnection", hw.vrdeSettings.fReuseSingleConnection);
+
+ if (m->sv == SettingsVersion_v1_10)
+ {
+ xml::ElementNode *pelmVideoChannel = pelmVRDE->createChild("VideoChannel");
+
+ /* In 4.0 videochannel settings were replaced with properties, so look at properties. */
+ Utf8Str str;
+ StringsMap::const_iterator it = hw.vrdeSettings.mapProperties.find("VideoChannel/Enabled");
+ if (it != hw.vrdeSettings.mapProperties.end())
+ str = it->second;
+ bool fVideoChannel = RTStrICmp(str.c_str(), "true") == 0
+ || RTStrCmp(str.c_str(), "1") == 0;
+ pelmVideoChannel->setAttribute("enabled", fVideoChannel);
+
+ it = hw.vrdeSettings.mapProperties.find("VideoChannel/Quality");
+ if (it != hw.vrdeSettings.mapProperties.end())
+ str = it->second;
+ uint32_t ulVideoChannelQuality = RTStrToUInt32(str.c_str()); /* This returns 0 on invalid string which is ok. */
+ if (ulVideoChannelQuality == 0)
+ ulVideoChannelQuality = 75;
+ else
+ ulVideoChannelQuality = RT_CLAMP(ulVideoChannelQuality, 10, 100);
+ pelmVideoChannel->setAttribute("quality", ulVideoChannelQuality);
+ }
+ if (m->sv >= SettingsVersion_v1_11)
+ {
+ if (hw.vrdeSettings.strAuthLibrary.length())
+ pelmVRDE->setAttribute("authLibrary", hw.vrdeSettings.strAuthLibrary);
+ if (hw.vrdeSettings.strVrdeExtPack.isNotEmpty())
+ pelmVRDE->setAttribute("VRDEExtPack", hw.vrdeSettings.strVrdeExtPack);
+ if (hw.vrdeSettings.mapProperties.size() > 0)
+ {
+ xml::ElementNode *pelmProperties = pelmVRDE->createChild("VRDEProperties");
+ for (StringsMap::const_iterator it = hw.vrdeSettings.mapProperties.begin();
+ it != hw.vrdeSettings.mapProperties.end();
+ ++it)
+ {
+ const Utf8Str &strName = it->first;
+ const Utf8Str &strValue = it->second;
+ xml::ElementNode *pelm = pelmProperties->createChild("Property");
+ pelm->setAttribute("name", strName);
+ pelm->setAttribute("value", strValue);
+ }
+ }
+ }
+ }
+
+ if (!hw.biosSettings.areDefaultSettings() || !hw.nvramSettings.areDefaultSettings())
+ {
+ xml::ElementNode *pelmBIOS = pelmHardware->createChild("BIOS");
+ if (!hw.biosSettings.fACPIEnabled)
+ pelmBIOS->createChild("ACPI")->setAttribute("enabled", hw.biosSettings.fACPIEnabled);
+ if (hw.biosSettings.fIOAPICEnabled)
+ pelmBIOS->createChild("IOAPIC")->setAttribute("enabled", hw.biosSettings.fIOAPICEnabled);
+ if (hw.biosSettings.apicMode != APICMode_APIC)
+ {
+ const char *pcszAPIC;
+ switch (hw.biosSettings.apicMode)
+ {
+ case APICMode_Disabled:
+ pcszAPIC = "Disabled";
+ break;
+ case APICMode_APIC:
+ default:
+ pcszAPIC = "APIC";
+ break;
+ case APICMode_X2APIC:
+ pcszAPIC = "X2APIC";
+ break;
+ }
+ pelmBIOS->createChild("APIC")->setAttribute("mode", pcszAPIC);
+ }
+
+ if ( !hw.biosSettings.fLogoFadeIn
+ || !hw.biosSettings.fLogoFadeOut
+ || hw.biosSettings.ulLogoDisplayTime
+ || !hw.biosSettings.strLogoImagePath.isEmpty())
+ {
+ xml::ElementNode *pelmLogo = pelmBIOS->createChild("Logo");
+ pelmLogo->setAttribute("fadeIn", hw.biosSettings.fLogoFadeIn);
+ pelmLogo->setAttribute("fadeOut", hw.biosSettings.fLogoFadeOut);
+ pelmLogo->setAttribute("displayTime", hw.biosSettings.ulLogoDisplayTime);
+ if (!hw.biosSettings.strLogoImagePath.isEmpty())
+ pelmLogo->setAttribute("imagePath", hw.biosSettings.strLogoImagePath);
+ }
+
+ if (hw.biosSettings.biosBootMenuMode != BIOSBootMenuMode_MessageAndMenu)
+ {
+ const char *pcszBootMenu;
+ switch (hw.biosSettings.biosBootMenuMode)
+ {
+ case BIOSBootMenuMode_Disabled: pcszBootMenu = "Disabled"; break;
+ case BIOSBootMenuMode_MenuOnly: pcszBootMenu = "MenuOnly"; break;
+ default: /*BIOSBootMenuMode_MessageAndMenu*/ pcszBootMenu = "MessageAndMenu"; break;
+ }
+ pelmBIOS->createChild("BootMenu")->setAttribute("mode", pcszBootMenu);
+ }
+ if (hw.biosSettings.llTimeOffset)
+ pelmBIOS->createChild("TimeOffset")->setAttribute("value", hw.biosSettings.llTimeOffset);
+ if (hw.biosSettings.fPXEDebugEnabled)
+ pelmBIOS->createChild("PXEDebug")->setAttribute("enabled", hw.biosSettings.fPXEDebugEnabled);
+ if (!hw.nvramSettings.areDefaultSettings())
+ {
+ xml::ElementNode *pelmNvram = pelmBIOS->createChild("NVRAM");
+ if (!hw.nvramSettings.strNvramPath.isEmpty())
+ pelmNvram->setAttribute("path", hw.nvramSettings.strNvramPath);
+ if (m->sv >= SettingsVersion_v1_9)
+ {
+ if (hw.nvramSettings.strKeyId.isNotEmpty())
+ pelmNvram->setAttribute("keyId", hw.nvramSettings.strKeyId);
+ if (hw.nvramSettings.strKeyStore.isNotEmpty())
+ pelmNvram->setAttribute("keyStore", hw.nvramSettings.strKeyStore);
+ }
+ }
+ if (hw.biosSettings.fSmbiosUuidLittleEndian)
+ pelmBIOS->createChild("SmbiosUuidLittleEndian")->setAttribute("enabled", hw.biosSettings.fSmbiosUuidLittleEndian);
+ }
+
+ if (!hw.tpmSettings.areDefaultSettings())
+ {
+ xml::ElementNode *pelmTpm = pelmHardware->createChild("TrustedPlatformModule");
+
+ const char *pcszTpm;
+ switch (hw.tpmSettings.tpmType)
+ {
+ default:
+ case TpmType_None:
+ pcszTpm = "None";
+ break;
+ case TpmType_v1_2:
+ pcszTpm = "v1_2";
+ break;
+ case TpmType_v2_0:
+ pcszTpm = "v2_0";
+ break;
+ case TpmType_Host:
+ pcszTpm = "Host";
+ break;
+ case TpmType_Swtpm:
+ pcszTpm = "Swtpm";
+ break;
+ }
+ pelmTpm->setAttribute("type", pcszTpm);
+ pelmTpm->setAttribute("location", hw.tpmSettings.strLocation);
+ }
+
+ if (m->sv < SettingsVersion_v1_9)
+ {
+ // settings formats before 1.9 had separate DVDDrive and FloppyDrive items under Hardware;
+ // run thru the storage controllers to see if we have a DVD or floppy drives
+ size_t cDVDs = 0;
+ size_t cFloppies = 0;
+
+ xml::ElementNode *pelmDVD = pelmHardware->createChild("DVDDrive");
+ xml::ElementNode *pelmFloppy = pelmHardware->createChild("FloppyDrive");
+
+ for (StorageControllersList::const_iterator it = hw.storage.llStorageControllers.begin();
+ it != hw.storage.llStorageControllers.end();
+ ++it)
+ {
+ const StorageController &sctl = *it;
+ // in old settings format, the DVD drive could only have been under the IDE controller
+ if (sctl.storageBus == StorageBus_IDE)
+ {
+ for (AttachedDevicesList::const_iterator it2 = sctl.llAttachedDevices.begin();
+ it2 != sctl.llAttachedDevices.end();
+ ++it2)
+ {
+ const AttachedDevice &att = *it2;
+ if (att.deviceType == DeviceType_DVD)
+ {
+ if (cDVDs > 0)
+ throw ConfigFileError(this, NULL, N_("Internal error: cannot save more than one DVD drive with old settings format"));
+
+ ++cDVDs;
+
+ pelmDVD->setAttribute("passthrough", att.fPassThrough);
+ if (att.fTempEject)
+ pelmDVD->setAttribute("tempeject", att.fTempEject);
+
+ if (!att.uuid.isZero() && att.uuid.isValid())
+ pelmDVD->createChild("Image")->setAttribute("uuid", att.uuid.toStringCurly());
+ else if (att.strHostDriveSrc.length())
+ pelmDVD->createChild("HostDrive")->setAttribute("src", att.strHostDriveSrc);
+ }
+ }
+ }
+ else if (sctl.storageBus == StorageBus_Floppy)
+ {
+ size_t cFloppiesHere = sctl.llAttachedDevices.size();
+ if (cFloppiesHere > 1)
+ throw ConfigFileError(this, NULL, N_("Internal error: floppy controller cannot have more than one device attachment"));
+ if (cFloppiesHere)
+ {
+ const AttachedDevice &att = sctl.llAttachedDevices.front();
+ pelmFloppy->setAttribute("enabled", true);
+
+ if (!att.uuid.isZero() && att.uuid.isValid())
+ pelmFloppy->createChild("Image")->setAttribute("uuid", att.uuid.toStringCurly());
+ else if (att.strHostDriveSrc.length())
+ pelmFloppy->createChild("HostDrive")->setAttribute("src", att.strHostDriveSrc);
+ }
+
+ cFloppies += cFloppiesHere;
+ }
+ }
+
+ if (cFloppies == 0)
+ pelmFloppy->setAttribute("enabled", false);
+ else if (cFloppies > 1)
+ throw ConfigFileError(this, NULL, N_("Internal error: cannot save more than one floppy drive with old settings format"));
+ }
+
+ if (m->sv < SettingsVersion_v1_14)
+ {
+ bool fOhciEnabled = false;
+ bool fEhciEnabled = false;
+ xml::ElementNode *pelmUSB = pelmHardware->createChild("USBController");
+
+ for (USBControllerList::const_iterator it = hw.usbSettings.llUSBControllers.begin();
+ it != hw.usbSettings.llUSBControllers.end();
+ ++it)
+ {
+ const USBController &ctrl = *it;
+
+ switch (ctrl.enmType)
+ {
+ case USBControllerType_OHCI:
+ fOhciEnabled = true;
+ break;
+ case USBControllerType_EHCI:
+ fEhciEnabled = true;
+ break;
+ default:
+ AssertMsgFailed(("Unknown USB controller type %d\n", ctrl.enmType));
+ }
+ }
+
+ pelmUSB->setAttribute("enabled", fOhciEnabled);
+ pelmUSB->setAttribute("enabledEhci", fEhciEnabled);
+
+ buildUSBDeviceFilters(*pelmUSB, hw.usbSettings.llDeviceFilters, false /* fHostMode */);
+ }
+ else
+ {
+ if ( hw.usbSettings.llUSBControllers.size()
+ || hw.usbSettings.llDeviceFilters.size())
+ {
+ xml::ElementNode *pelmUSB = pelmHardware->createChild("USB");
+ if (hw.usbSettings.llUSBControllers.size())
+ {
+ xml::ElementNode *pelmCtrls = pelmUSB->createChild("Controllers");
+
+ for (USBControllerList::const_iterator it = hw.usbSettings.llUSBControllers.begin();
+ it != hw.usbSettings.llUSBControllers.end();
+ ++it)
+ {
+ const USBController &ctrl = *it;
+ com::Utf8Str strType;
+ xml::ElementNode *pelmCtrl = pelmCtrls->createChild("Controller");
+
+ switch (ctrl.enmType)
+ {
+ case USBControllerType_OHCI:
+ strType = "OHCI";
+ break;
+ case USBControllerType_EHCI:
+ strType = "EHCI";
+ break;
+ case USBControllerType_XHCI:
+ strType = "XHCI";
+ break;
+ default:
+ AssertMsgFailed(("Unknown USB controller type %d\n", ctrl.enmType));
+ }
+
+ pelmCtrl->setAttribute("name", ctrl.strName);
+ pelmCtrl->setAttribute("type", strType);
+ }
+ }
+
+ if (hw.usbSettings.llDeviceFilters.size())
+ {
+ xml::ElementNode *pelmFilters = pelmUSB->createChild("DeviceFilters");
+ buildUSBDeviceFilters(*pelmFilters, hw.usbSettings.llDeviceFilters, false /* fHostMode */);
+ }
+ }
+ }
+
+ if ( hw.llNetworkAdapters.size()
+ && !hw.areAllNetworkAdaptersDefaultSettings(m->sv))
+ {
+ xml::ElementNode *pelmNetwork = pelmHardware->createChild("Network");
+ for (NetworkAdaptersList::const_iterator it = hw.llNetworkAdapters.begin();
+ it != hw.llNetworkAdapters.end();
+ ++it)
+ {
+ const NetworkAdapter &nic = *it;
+
+ if (!nic.areDefaultSettings(m->sv))
+ {
+ xml::ElementNode *pelmAdapter = pelmNetwork->createChild("Adapter");
+ pelmAdapter->setAttribute("slot", nic.ulSlot);
+ if (nic.fEnabled)
+ pelmAdapter->setAttribute("enabled", nic.fEnabled);
+ if (!nic.strMACAddress.isEmpty())
+ pelmAdapter->setAttribute("MACAddress", nic.strMACAddress);
+ if ( (m->sv >= SettingsVersion_v1_16 && !nic.fCableConnected)
+ || (m->sv < SettingsVersion_v1_16 && nic.fCableConnected))
+ pelmAdapter->setAttribute("cable", nic.fCableConnected);
+ if (nic.ulLineSpeed)
+ pelmAdapter->setAttribute("speed", nic.ulLineSpeed);
+ if (nic.ulBootPriority != 0)
+ pelmAdapter->setAttribute("bootPriority", nic.ulBootPriority);
+ if (nic.fTraceEnabled)
+ {
+ pelmAdapter->setAttribute("trace", nic.fTraceEnabled);
+ pelmAdapter->setAttribute("tracefile", nic.strTraceFile);
+ }
+ if (nic.strBandwidthGroup.isNotEmpty())
+ pelmAdapter->setAttribute("bandwidthGroup", nic.strBandwidthGroup);
+
+ const char *pszPolicy;
+ switch (nic.enmPromiscModePolicy)
+ {
+ case NetworkAdapterPromiscModePolicy_Deny: pszPolicy = NULL; break;
+ case NetworkAdapterPromiscModePolicy_AllowNetwork: pszPolicy = "AllowNetwork"; break;
+ case NetworkAdapterPromiscModePolicy_AllowAll: pszPolicy = "AllowAll"; break;
+ default: pszPolicy = NULL; AssertFailed(); break;
+ }
+ if (pszPolicy)
+ pelmAdapter->setAttribute("promiscuousModePolicy", pszPolicy);
+
+ if ( (m->sv >= SettingsVersion_v1_16 && nic.type != NetworkAdapterType_Am79C973)
+ || (m->sv < SettingsVersion_v1_16 && nic.type != NetworkAdapterType_Am79C970A))
+ {
+ const char *pcszType;
+ switch (nic.type)
+ {
+ case NetworkAdapterType_Am79C973: pcszType = "Am79C973"; break;
+ case NetworkAdapterType_Am79C960: pcszType = "Am79C960"; break;
+ case NetworkAdapterType_I82540EM: pcszType = "82540EM"; break;
+ case NetworkAdapterType_I82543GC: pcszType = "82543GC"; break;
+ case NetworkAdapterType_I82545EM: pcszType = "82545EM"; break;
+ case NetworkAdapterType_Virtio: pcszType = "virtio"; break;
+ case NetworkAdapterType_NE1000: pcszType = "NE1000"; break;
+ case NetworkAdapterType_NE2000: pcszType = "NE2000"; break;
+ case NetworkAdapterType_WD8003: pcszType = "WD8003"; break;
+ case NetworkAdapterType_WD8013: pcszType = "WD8013"; break;
+ case NetworkAdapterType_ELNK2: pcszType = "3C503"; break;
+ case NetworkAdapterType_ELNK1: pcszType = "3C501"; break;
+ default: /*case NetworkAdapterType_Am79C970A:*/ pcszType = "Am79C970A"; break;
+ }
+ pelmAdapter->setAttribute("type", pcszType);
+ }
+
+ xml::ElementNode *pelmNAT;
+ if (m->sv < SettingsVersion_v1_10)
+ {
+ switch (nic.mode)
+ {
+ case NetworkAttachmentType_NAT:
+ pelmNAT = pelmAdapter->createChild("NAT");
+ if (nic.nat.strNetwork.length())
+ pelmNAT->setAttribute("network", nic.nat.strNetwork);
+ break;
+
+ case NetworkAttachmentType_Bridged:
+ pelmAdapter->createChild("BridgedInterface")->setAttribute("name", nic.strBridgedName);
+ break;
+
+ case NetworkAttachmentType_Internal:
+ pelmAdapter->createChild("InternalNetwork")->setAttribute("name", nic.strInternalNetworkName);
+ break;
+
+ case NetworkAttachmentType_HostOnly:
+ pelmAdapter->createChild("HostOnlyInterface")->setAttribute("name", nic.strHostOnlyName);
+ break;
+
+ default: /*case NetworkAttachmentType_Null:*/
+ break;
+ }
+ }
+ else
+ {
+ /* m->sv >= SettingsVersion_v1_10 */
+ if (!nic.areDisabledDefaultSettings(m->sv))
+ {
+ xml::ElementNode *pelmDisabledNode = pelmAdapter->createChild("DisabledModes");
+ if (nic.mode != NetworkAttachmentType_NAT)
+ buildNetworkXML(NetworkAttachmentType_NAT, false, *pelmDisabledNode, nic);
+ if (nic.mode != NetworkAttachmentType_Bridged)
+ buildNetworkXML(NetworkAttachmentType_Bridged, false, *pelmDisabledNode, nic);
+ if (nic.mode != NetworkAttachmentType_Internal)
+ buildNetworkXML(NetworkAttachmentType_Internal, false, *pelmDisabledNode, nic);
+ if (nic.mode != NetworkAttachmentType_HostOnly)
+ buildNetworkXML(NetworkAttachmentType_HostOnly, false, *pelmDisabledNode, nic);
+ if (nic.mode != NetworkAttachmentType_Generic)
+ buildNetworkXML(NetworkAttachmentType_Generic, false, *pelmDisabledNode, nic);
+ if (nic.mode != NetworkAttachmentType_NATNetwork)
+ buildNetworkXML(NetworkAttachmentType_NATNetwork, false, *pelmDisabledNode, nic);
+#ifdef VBOX_WITH_CLOUD_NET
+ /// @todo Bump settings version!
+ if (nic.mode != NetworkAttachmentType_Cloud)
+ buildNetworkXML(NetworkAttachmentType_Cloud, false, *pelmDisabledNode, nic);
+#endif /* VBOX_WITH_CLOUD_NET */
+#ifdef VBOX_WITH_VMNET
+ if (nic.mode != NetworkAttachmentType_HostOnlyNetwork)
+ buildNetworkXML(NetworkAttachmentType_HostOnlyNetwork, false, *pelmDisabledNode, nic);
+#endif /* VBOX_WITH_VMNET */
+ }
+ buildNetworkXML(nic.mode, true, *pelmAdapter, nic);
+ }
+ }
+ }
+ }
+
+ if (hw.llSerialPorts.size())
+ {
+ xml::ElementNode *pelmPorts = pelmHardware->createChild("UART");
+ for (SerialPortsList::const_iterator it = hw.llSerialPorts.begin();
+ it != hw.llSerialPorts.end();
+ ++it)
+ {
+ const SerialPort &port = *it;
+ xml::ElementNode *pelmPort = pelmPorts->createChild("Port");
+ pelmPort->setAttribute("slot", port.ulSlot);
+ pelmPort->setAttribute("enabled", port.fEnabled);
+ pelmPort->setAttributeHex("IOBase", port.ulIOBase);
+ pelmPort->setAttribute("IRQ", port.ulIRQ);
+
+ const char *pcszHostMode;
+ switch (port.portMode)
+ {
+ case PortMode_HostPipe: pcszHostMode = "HostPipe"; break;
+ case PortMode_HostDevice: pcszHostMode = "HostDevice"; break;
+ case PortMode_TCP: pcszHostMode = "TCP"; break;
+ case PortMode_RawFile: pcszHostMode = "RawFile"; break;
+ default: /*case PortMode_Disconnected:*/ pcszHostMode = "Disconnected"; break;
+ }
+ switch (port.portMode)
+ {
+ case PortMode_TCP:
+ case PortMode_HostPipe:
+ pelmPort->setAttribute("server", port.fServer);
+ RT_FALL_THRU();
+ case PortMode_HostDevice:
+ case PortMode_RawFile:
+ pelmPort->setAttribute("path", port.strPath);
+ break;
+
+ default:
+ break;
+ }
+ pelmPort->setAttribute("hostMode", pcszHostMode);
+
+ if ( m->sv >= SettingsVersion_v1_17
+ && port.uartType != UartType_U16550A)
+ {
+ const char *pcszUartType;
+
+ switch (port.uartType)
+ {
+ case UartType_U16450: pcszUartType = "16450"; break;
+ case UartType_U16550A: pcszUartType = "16550A"; break;
+ case UartType_U16750: pcszUartType = "16750"; break;
+ default: pcszUartType = "16550A"; break;
+ }
+ pelmPort->setAttribute("uartType", pcszUartType);
+ }
+ }
+ }
+
+ if (hw.llParallelPorts.size())
+ {
+ xml::ElementNode *pelmPorts = pelmHardware->createChild("LPT");
+ for (ParallelPortsList::const_iterator it = hw.llParallelPorts.begin();
+ it != hw.llParallelPorts.end();
+ ++it)
+ {
+ const ParallelPort &port = *it;
+ xml::ElementNode *pelmPort = pelmPorts->createChild("Port");
+ pelmPort->setAttribute("slot", port.ulSlot);
+ pelmPort->setAttribute("enabled", port.fEnabled);
+ pelmPort->setAttributeHex("IOBase", port.ulIOBase);
+ pelmPort->setAttribute("IRQ", port.ulIRQ);
+ if (port.strPath.length())
+ pelmPort->setAttribute("path", port.strPath);
+ }
+ }
+
+ /* Always write the AudioAdapter config, intentionally not checking if
+ * the settings are at the default, because that would be problematic
+ * for the configured host driver type, which would automatically change
+ * if the default host driver is detected differently. */
+ {
+ xml::ElementNode *pelmAudio = pelmHardware->createChild("AudioAdapter");
+
+ const char *pcszController;
+ switch (hw.audioAdapter.controllerType)
+ {
+ case AudioControllerType_SB16:
+ pcszController = "SB16";
+ break;
+ case AudioControllerType_HDA:
+ if (m->sv >= SettingsVersion_v1_11)
+ {
+ pcszController = "HDA";
+ break;
+ }
+ RT_FALL_THRU();
+ case AudioControllerType_AC97:
+ default:
+ pcszController = NULL;
+ break;
+ }
+ if (pcszController)
+ pelmAudio->setAttribute("controller", pcszController);
+
+ const char *pcszCodec;
+ switch (hw.audioAdapter.codecType)
+ {
+ /* Only write out the setting for non-default AC'97 codec
+ * and leave the rest alone.
+ */
+#if 0
+ case AudioCodecType_SB16:
+ pcszCodec = "SB16";
+ break;
+ case AudioCodecType_STAC9221:
+ pcszCodec = "STAC9221";
+ break;
+ case AudioCodecType_STAC9700:
+ pcszCodec = "STAC9700";
+ break;
+#endif
+ case AudioCodecType_AD1980:
+ pcszCodec = "AD1980";
+ break;
+ default:
+ /* Don't write out anything if unknown. */
+ pcszCodec = NULL;
+ }
+ if (pcszCodec)
+ pelmAudio->setAttribute("codec", pcszCodec);
+
+ /*
+ * Keep settings >= 1.19 compatible with older VBox versions (on a best effort basis, of course).
+ * So use a dedicated attribute for the new "Default" audio driver type, which did not exist prior
+ * settings 1.19 (VBox 7.0) and explicitly set the driver type to something older VBox versions
+ * know about.
+ */
+ AudioDriverType_T driverType = hw.audioAdapter.driverType;
+
+ if (driverType == AudioDriverType_Default)
+ {
+ /* Only recognized by VBox >= 7.0. */
+ pelmAudio->setAttribute("useDefault", true);
+
+ /* Make sure to set the actual driver type to the OS' default driver type.
+ * This is required for VBox < 7.0. */
+ driverType = getHostDefaultAudioDriver();
+ }
+
+ const char *pcszDriver = NULL;
+ switch (driverType)
+ {
+ case AudioDriverType_Default: /* Handled above. */ break;
+ case AudioDriverType_WinMM: pcszDriver = "WinMM"; break;
+ case AudioDriverType_DirectSound: pcszDriver = "DirectSound"; break;
+ case AudioDriverType_WAS: pcszDriver = "WAS"; break;
+ case AudioDriverType_ALSA: pcszDriver = "ALSA"; break;
+ case AudioDriverType_OSS: pcszDriver = "OSS"; break;
+ case AudioDriverType_Pulse: pcszDriver = "Pulse"; break;
+ case AudioDriverType_CoreAudio: pcszDriver = "CoreAudio"; break;
+ case AudioDriverType_SolAudio: pcszDriver = "SolAudio"; break;
+ case AudioDriverType_MMPM: pcszDriver = "MMPM"; break;
+ default: /*case AudioDriverType_Null:*/ pcszDriver = "Null"; break;
+ }
+
+ /* Deliberately have the audio driver explicitly in the config file,
+ * otherwise an unwritten default driver triggers auto-detection. */
+ AssertStmt(pcszDriver != NULL, pcszDriver = "Null");
+ pelmAudio->setAttribute("driver", pcszDriver);
+
+ if (hw.audioAdapter.fEnabled || m->sv < SettingsVersion_v1_16)
+ pelmAudio->setAttribute("enabled", hw.audioAdapter.fEnabled);
+
+ if ( (m->sv <= SettingsVersion_v1_16 && !hw.audioAdapter.fEnabledIn)
+ || (m->sv > SettingsVersion_v1_16 && hw.audioAdapter.fEnabledIn))
+ pelmAudio->setAttribute("enabledIn", hw.audioAdapter.fEnabledIn);
+
+ if ( (m->sv <= SettingsVersion_v1_16 && !hw.audioAdapter.fEnabledOut)
+ || (m->sv > SettingsVersion_v1_16 && hw.audioAdapter.fEnabledOut))
+ pelmAudio->setAttribute("enabledOut", hw.audioAdapter.fEnabledOut);
+
+ if (m->sv >= SettingsVersion_v1_15 && hw.audioAdapter.properties.size() > 0)
+ {
+ for (StringsMap::const_iterator it = hw.audioAdapter.properties.begin();
+ it != hw.audioAdapter.properties.end();
+ ++it)
+ {
+ const Utf8Str &strName = it->first;
+ const Utf8Str &strValue = it->second;
+ xml::ElementNode *pelm = pelmAudio->createChild("Property");
+ pelm->setAttribute("name", strName);
+ pelm->setAttribute("value", strValue);
+ }
+ }
+ }
+
+ if (m->sv >= SettingsVersion_v1_10 && machineUserData.fRTCUseUTC)
+ {
+ xml::ElementNode *pelmRTC = pelmHardware->createChild("RTC");
+ pelmRTC->setAttribute("localOrUTC", machineUserData.fRTCUseUTC ? "UTC" : "local");
+ }
+
+ if (hw.llSharedFolders.size())
+ {
+ xml::ElementNode *pelmSharedFolders = pelmHardware->createChild("SharedFolders");
+ for (SharedFoldersList::const_iterator it = hw.llSharedFolders.begin();
+ it != hw.llSharedFolders.end();
+ ++it)
+ {
+ const SharedFolder &sf = *it;
+ xml::ElementNode *pelmThis = pelmSharedFolders->createChild("SharedFolder");
+ pelmThis->setAttribute("name", sf.strName);
+ pelmThis->setAttribute("hostPath", sf.strHostPath);
+ pelmThis->setAttribute("writable", sf.fWritable);
+ pelmThis->setAttribute("autoMount", sf.fAutoMount);
+ if (sf.strAutoMountPoint.isNotEmpty())
+ pelmThis->setAttribute("autoMountPoint", sf.strAutoMountPoint);
+ }
+ }
+
+ xml::ElementNode *pelmClip = pelmHardware->createChild("Clipboard");
+ if (pelmClip)
+ {
+ if (hw.clipboardMode != ClipboardMode_Disabled)
+ {
+ const char *pcszClip;
+ switch (hw.clipboardMode)
+ {
+ default: /*case ClipboardMode_Disabled:*/ pcszClip = "Disabled"; break;
+ case ClipboardMode_HostToGuest: pcszClip = "HostToGuest"; break;
+ case ClipboardMode_GuestToHost: pcszClip = "GuestToHost"; break;
+ case ClipboardMode_Bidirectional: pcszClip = "Bidirectional"; break;
+ }
+ pelmClip->setAttribute("mode", pcszClip);
+ }
+
+ if (hw.fClipboardFileTransfersEnabled)
+ pelmClip->setAttribute("fileTransfersEnabled", hw.fClipboardFileTransfersEnabled);
+ }
+
+ if (hw.dndMode != DnDMode_Disabled)
+ {
+ xml::ElementNode *pelmDragAndDrop = pelmHardware->createChild("DragAndDrop");
+ const char *pcszDragAndDrop;
+ switch (hw.dndMode)
+ {
+ default: /*case DnDMode_Disabled:*/ pcszDragAndDrop = "Disabled"; break;
+ case DnDMode_HostToGuest: pcszDragAndDrop = "HostToGuest"; break;
+ case DnDMode_GuestToHost: pcszDragAndDrop = "GuestToHost"; break;
+ case DnDMode_Bidirectional: pcszDragAndDrop = "Bidirectional"; break;
+ }
+ pelmDragAndDrop->setAttribute("mode", pcszDragAndDrop);
+ }
+
+ if ( m->sv >= SettingsVersion_v1_10
+ && !hw.ioSettings.areDefaultSettings())
+ {
+ xml::ElementNode *pelmIO = pelmHardware->createChild("IO");
+ xml::ElementNode *pelmIOCache;
+
+ if (!hw.ioSettings.areDefaultSettings())
+ {
+ pelmIOCache = pelmIO->createChild("IoCache");
+ if (!hw.ioSettings.fIOCacheEnabled)
+ pelmIOCache->setAttribute("enabled", hw.ioSettings.fIOCacheEnabled);
+ if (hw.ioSettings.ulIOCacheSize != 5)
+ pelmIOCache->setAttribute("size", hw.ioSettings.ulIOCacheSize);
+ }
+
+ if ( m->sv >= SettingsVersion_v1_11
+ && hw.ioSettings.llBandwidthGroups.size())
+ {
+ xml::ElementNode *pelmBandwidthGroups = pelmIO->createChild("BandwidthGroups");
+ for (BandwidthGroupList::const_iterator it = hw.ioSettings.llBandwidthGroups.begin();
+ it != hw.ioSettings.llBandwidthGroups.end();
+ ++it)
+ {
+ const BandwidthGroup &gr = *it;
+ const char *pcszType;
+ xml::ElementNode *pelmThis = pelmBandwidthGroups->createChild("BandwidthGroup");
+ pelmThis->setAttribute("name", gr.strName);
+ switch (gr.enmType)
+ {
+ case BandwidthGroupType_Network: pcszType = "Network"; break;
+ default: /* BandwidthGrouptype_Disk */ pcszType = "Disk"; break;
+ }
+ pelmThis->setAttribute("type", pcszType);
+ if (m->sv >= SettingsVersion_v1_13)
+ pelmThis->setAttribute("maxBytesPerSec", gr.cMaxBytesPerSec);
+ else
+ pelmThis->setAttribute("maxMbPerSec", gr.cMaxBytesPerSec / _1M);
+ }
+ }
+ }
+
+ if ( m->sv >= SettingsVersion_v1_12
+ && hw.pciAttachments.size())
+ {
+ xml::ElementNode *pelmPCI = pelmHardware->createChild("HostPci");
+ xml::ElementNode *pelmPCIDevices = pelmPCI->createChild("Devices");
+
+ for (HostPCIDeviceAttachmentList::const_iterator it = hw.pciAttachments.begin();
+ it != hw.pciAttachments.end();
+ ++it)
+ {
+ const HostPCIDeviceAttachment &hpda = *it;
+
+ xml::ElementNode *pelmThis = pelmPCIDevices->createChild("Device");
+
+ pelmThis->setAttribute("host", hpda.uHostAddress);
+ pelmThis->setAttribute("guest", hpda.uGuestAddress);
+ pelmThis->setAttribute("name", hpda.strDeviceName);
+ }
+ }
+
+ if ( m->sv >= SettingsVersion_v1_12
+ && hw.fEmulatedUSBCardReader)
+ {
+ xml::ElementNode *pelmEmulatedUSB = pelmHardware->createChild("EmulatedUSB");
+
+ xml::ElementNode *pelmCardReader = pelmEmulatedUSB->createChild("CardReader");
+ pelmCardReader->setAttribute("enabled", hw.fEmulatedUSBCardReader);
+ }
+
+ if ( m->sv >= SettingsVersion_v1_14
+ && !hw.strDefaultFrontend.isEmpty())
+ {
+ xml::ElementNode *pelmFrontend = pelmHardware->createChild("Frontend");
+ xml::ElementNode *pelmDefault = pelmFrontend->createChild("Default");
+ pelmDefault->setAttribute("type", hw.strDefaultFrontend);
+ }
+
+ if (hw.ulMemoryBalloonSize)
+ {
+ xml::ElementNode *pelmGuest = pelmHardware->createChild("Guest");
+ pelmGuest->setAttribute("memoryBalloonSize", hw.ulMemoryBalloonSize);
+ }
+
+ if (hw.llGuestProperties.size())
+ {
+ xml::ElementNode *pelmGuestProps = pelmHardware->createChild("GuestProperties");
+ for (GuestPropertiesList::const_iterator it = hw.llGuestProperties.begin();
+ it != hw.llGuestProperties.end();
+ ++it)
+ {
+ const GuestProperty &prop = *it;
+ xml::ElementNode *pelmProp = pelmGuestProps->createChild("GuestProperty");
+ pelmProp->setAttribute("name", prop.strName);
+ pelmProp->setAttribute("value", prop.strValue);
+ pelmProp->setAttribute("timestamp", prop.timestamp);
+ pelmProp->setAttribute("flags", prop.strFlags);
+ }
+ }
+
+ /* Starting with settings version of 6.0 (and only 6.1 and later does this, while
+ * 5.2 and 6.0 understand it), place storage controller settings under hardware,
+ * where it always should've been. */
+ xml::ElementNode &elmStorageParent = (m->sv >= SettingsVersion_v1_17) ? *pelmHardware : elmParent;
+ buildStorageControllersXML(elmStorageParent,
+ hw.storage,
+ !!(fl & BuildMachineXML_SkipRemovableMedia),
+ pllElementsWithUuidAttributes);
+}
+
+/**
+ * Fill a \<Network\> node. Only relevant for XML version >= v1_10.
+ * @param mode
+ * @param fEnabled
+ * @param elmParent
+ * @param nic
+ */
+void MachineConfigFile::buildNetworkXML(NetworkAttachmentType_T mode,
+ bool fEnabled,
+ xml::ElementNode &elmParent,
+ const NetworkAdapter &nic)
+{
+ switch (mode)
+ {
+ case NetworkAttachmentType_NAT:
+ // For the currently active network attachment type we have to
+ // generate the tag, otherwise the attachment type is lost.
+ if (fEnabled || !nic.nat.areDefaultSettings(m->sv))
+ {
+ xml::ElementNode *pelmNAT = elmParent.createChild("NAT");
+
+ if (!nic.nat.areDefaultSettings(m->sv))
+ {
+ if (nic.nat.strNetwork.length())
+ pelmNAT->setAttribute("network", nic.nat.strNetwork);
+ if (nic.nat.strBindIP.length())
+ pelmNAT->setAttribute("hostip", nic.nat.strBindIP);
+ if (nic.nat.u32Mtu)
+ pelmNAT->setAttribute("mtu", nic.nat.u32Mtu);
+ if (nic.nat.u32SockRcv)
+ pelmNAT->setAttribute("sockrcv", nic.nat.u32SockRcv);
+ if (nic.nat.u32SockSnd)
+ pelmNAT->setAttribute("socksnd", nic.nat.u32SockSnd);
+ if (nic.nat.u32TcpRcv)
+ pelmNAT->setAttribute("tcprcv", nic.nat.u32TcpRcv);
+ if (nic.nat.u32TcpSnd)
+ pelmNAT->setAttribute("tcpsnd", nic.nat.u32TcpSnd);
+ if (!nic.nat.areLocalhostReachableDefaultSettings(m->sv))
+ pelmNAT->setAttribute("localhost-reachable", nic.nat.fLocalhostReachable);
+ if (!nic.nat.areDNSDefaultSettings())
+ {
+ xml::ElementNode *pelmDNS = pelmNAT->createChild("DNS");
+ if (!nic.nat.fDNSPassDomain)
+ pelmDNS->setAttribute("pass-domain", nic.nat.fDNSPassDomain);
+ if (nic.nat.fDNSProxy)
+ pelmDNS->setAttribute("use-proxy", nic.nat.fDNSProxy);
+ if (nic.nat.fDNSUseHostResolver)
+ pelmDNS->setAttribute("use-host-resolver", nic.nat.fDNSUseHostResolver);
+ }
+
+ if (!nic.nat.areAliasDefaultSettings())
+ {
+ xml::ElementNode *pelmAlias = pelmNAT->createChild("Alias");
+ if (nic.nat.fAliasLog)
+ pelmAlias->setAttribute("logging", nic.nat.fAliasLog);
+ if (nic.nat.fAliasProxyOnly)
+ pelmAlias->setAttribute("proxy-only", nic.nat.fAliasProxyOnly);
+ if (nic.nat.fAliasUseSamePorts)
+ pelmAlias->setAttribute("use-same-ports", nic.nat.fAliasUseSamePorts);
+ }
+
+ if (!nic.nat.areTFTPDefaultSettings())
+ {
+ xml::ElementNode *pelmTFTP;
+ pelmTFTP = pelmNAT->createChild("TFTP");
+ if (nic.nat.strTFTPPrefix.length())
+ pelmTFTP->setAttribute("prefix", nic.nat.strTFTPPrefix);
+ if (nic.nat.strTFTPBootFile.length())
+ pelmTFTP->setAttribute("boot-file", nic.nat.strTFTPBootFile);
+ if (nic.nat.strTFTPNextServer.length())
+ pelmTFTP->setAttribute("next-server", nic.nat.strTFTPNextServer);
+ }
+ buildNATForwardRulesMap(*pelmNAT, nic.nat.mapRules);
+ }
+ }
+ break;
+
+ case NetworkAttachmentType_Bridged:
+ // For the currently active network attachment type we have to
+ // generate the tag, otherwise the attachment type is lost.
+ if (fEnabled || !nic.strBridgedName.isEmpty())
+ {
+ xml::ElementNode *pelmMode = elmParent.createChild("BridgedInterface");
+ if (!nic.strBridgedName.isEmpty())
+ pelmMode->setAttribute("name", nic.strBridgedName);
+ }
+ break;
+
+ case NetworkAttachmentType_Internal:
+ // For the currently active network attachment type we have to
+ // generate the tag, otherwise the attachment type is lost.
+ if (fEnabled || !nic.strInternalNetworkName.isEmpty())
+ {
+ xml::ElementNode *pelmMode = elmParent.createChild("InternalNetwork");
+ if (!nic.strInternalNetworkName.isEmpty())
+ pelmMode->setAttribute("name", nic.strInternalNetworkName);
+ }
+ break;
+
+ case NetworkAttachmentType_HostOnly:
+ // For the currently active network attachment type we have to
+ // generate the tag, otherwise the attachment type is lost.
+ if (fEnabled || !nic.strHostOnlyName.isEmpty())
+ {
+ xml::ElementNode *pelmMode = elmParent.createChild("HostOnlyInterface");
+ if (!nic.strHostOnlyName.isEmpty())
+ pelmMode->setAttribute("name", nic.strHostOnlyName);
+ }
+ break;
+
+#ifdef VBOX_WITH_VMNET
+ case NetworkAttachmentType_HostOnlyNetwork:
+ // For the currently active network attachment type we have to
+ // generate the tag, otherwise the attachment type is lost.
+ if (fEnabled || !nic.strHostOnlyNetworkName.isEmpty())
+ {
+ xml::ElementNode *pelmMode = elmParent.createChild("HostOnlyNetwork");
+ if (!nic.strHostOnlyNetworkName.isEmpty())
+ pelmMode->setAttribute("name", nic.strHostOnlyNetworkName);
+ }
+ break;
+#endif /* VBOX_WITH_VMNET */
+
+ case NetworkAttachmentType_Generic:
+ // For the currently active network attachment type we have to
+ // generate the tag, otherwise the attachment type is lost.
+ if (fEnabled || !nic.areGenericDriverDefaultSettings())
+ {
+ xml::ElementNode *pelmMode = elmParent.createChild("GenericInterface");
+ if (!nic.areGenericDriverDefaultSettings())
+ {
+ pelmMode->setAttribute("driver", nic.strGenericDriver);
+ for (StringsMap::const_iterator it = nic.genericProperties.begin();
+ it != nic.genericProperties.end();
+ ++it)
+ {
+ xml::ElementNode *pelmProp = pelmMode->createChild("Property");
+ pelmProp->setAttribute("name", it->first);
+ pelmProp->setAttribute("value", it->second);
+ }
+ }
+ }
+ break;
+
+ case NetworkAttachmentType_NATNetwork:
+ // For the currently active network attachment type we have to
+ // generate the tag, otherwise the attachment type is lost.
+ if (fEnabled || !nic.strNATNetworkName.isEmpty())
+ {
+ xml::ElementNode *pelmMode = elmParent.createChild("NATNetwork");
+ if (!nic.strNATNetworkName.isEmpty())
+ pelmMode->setAttribute("name", nic.strNATNetworkName);
+ }
+ break;
+
+#ifdef VBOX_WITH_CLOUD_NET
+ case NetworkAttachmentType_Cloud:
+ // For the currently active network attachment type we have to
+ // generate the tag, otherwise the attachment type is lost.
+ if (fEnabled || !nic.strCloudNetworkName.isEmpty())
+ {
+ xml::ElementNode *pelmMode = elmParent.createChild("CloudNetwork");
+ if (!nic.strCloudNetworkName.isEmpty())
+ pelmMode->setAttribute("name", nic.strCloudNetworkName);
+ }
+ break;
+#endif /* VBOX_WITH_CLOUD_NET */
+
+ default: /*case NetworkAttachmentType_Null:*/
+ break;
+ }
+}
+
+/**
+ * Creates a \<StorageControllers\> node under elmParent and then writes out the XML
+ * keys under that. Called for both the \<Machine\> node and for snapshots.
+ * @param elmParent
+ * @param st
+ * @param fSkipRemovableMedia If true, DVD and floppy attachments are skipped and
+ * an empty drive is always written instead. This is for the OVF export case.
+ * This parameter is ignored unless the settings version is at least v1.9, which
+ * is always the case when this gets called for OVF export.
+ * @param pllElementsWithUuidAttributes If not NULL, must point to a list of element node
+ * pointers to which we will append all elements that we created here that contain
+ * UUID attributes. This allows the OVF export code to quickly replace the internal
+ * media UUIDs with the UUIDs of the media that were exported.
+ */
+void MachineConfigFile::buildStorageControllersXML(xml::ElementNode &elmParent,
+ const Storage &st,
+ bool fSkipRemovableMedia,
+ std::list<xml::ElementNode*> *pllElementsWithUuidAttributes)
+{
+ if (!st.llStorageControllers.size())
+ return;
+ xml::ElementNode *pelmStorageControllers = elmParent.createChild("StorageControllers");
+
+ for (StorageControllersList::const_iterator it = st.llStorageControllers.begin();
+ it != st.llStorageControllers.end();
+ ++it)
+ {
+ const StorageController &sc = *it;
+
+ if ( (m->sv < SettingsVersion_v1_9)
+ && (sc.controllerType == StorageControllerType_I82078)
+ )
+ // floppy controller already got written into <Hardware>/<FloppyController> in buildHardwareXML()
+ // for pre-1.9 settings
+ continue;
+
+ xml::ElementNode *pelmController = pelmStorageControllers->createChild("StorageController");
+ com::Utf8Str name = sc.strName;
+ if (m->sv < SettingsVersion_v1_8)
+ {
+ // pre-1.8 settings use shorter controller names, they are
+ // expanded when reading the settings
+ if (name == "IDE Controller")
+ name = "IDE";
+ else if (name == "SATA Controller")
+ name = "SATA";
+ else if (name == "SCSI Controller")
+ name = "SCSI";
+ }
+ pelmController->setAttribute("name", sc.strName);
+
+ const char *pcszType;
+ switch (sc.controllerType)
+ {
+ case StorageControllerType_IntelAhci: pcszType = "AHCI"; break;
+ case StorageControllerType_LsiLogic: pcszType = "LsiLogic"; break;
+ case StorageControllerType_BusLogic: pcszType = "BusLogic"; break;
+ case StorageControllerType_PIIX4: pcszType = "PIIX4"; break;
+ case StorageControllerType_ICH6: pcszType = "ICH6"; break;
+ case StorageControllerType_I82078: pcszType = "I82078"; break;
+ case StorageControllerType_LsiLogicSas: pcszType = "LsiLogicSas"; break;
+ case StorageControllerType_USB: pcszType = "USB"; break;
+ case StorageControllerType_NVMe: pcszType = "NVMe"; break;
+ case StorageControllerType_VirtioSCSI: pcszType = "VirtioSCSI"; break;
+ default: /*case StorageControllerType_PIIX3:*/ pcszType = "PIIX3"; break;
+ }
+ pelmController->setAttribute("type", pcszType);
+
+ pelmController->setAttribute("PortCount", sc.ulPortCount);
+
+ if (m->sv >= SettingsVersion_v1_9)
+ if (sc.ulInstance)
+ pelmController->setAttribute("Instance", sc.ulInstance);
+
+ if (m->sv >= SettingsVersion_v1_10)
+ pelmController->setAttribute("useHostIOCache", sc.fUseHostIOCache);
+
+ if (m->sv >= SettingsVersion_v1_11)
+ pelmController->setAttribute("Bootable", sc.fBootable);
+
+ if (sc.controllerType == StorageControllerType_IntelAhci)
+ {
+ pelmController->setAttribute("IDE0MasterEmulationPort", 0);
+ pelmController->setAttribute("IDE0SlaveEmulationPort", 1);
+ pelmController->setAttribute("IDE1MasterEmulationPort", 2);
+ pelmController->setAttribute("IDE1SlaveEmulationPort", 3);
+ }
+
+ for (AttachedDevicesList::const_iterator it2 = sc.llAttachedDevices.begin();
+ it2 != sc.llAttachedDevices.end();
+ ++it2)
+ {
+ const AttachedDevice &att = *it2;
+
+ // For settings version before 1.9, DVDs and floppies are in hardware, not storage controllers,
+ // so we shouldn't write them here; we only get here for DVDs though because we ruled out
+ // the floppy controller at the top of the loop
+ if ( att.deviceType == DeviceType_DVD
+ && m->sv < SettingsVersion_v1_9
+ )
+ continue;
+
+ xml::ElementNode *pelmDevice = pelmController->createChild("AttachedDevice");
+
+ pcszType = NULL;
+
+ switch (att.deviceType)
+ {
+ case DeviceType_HardDisk:
+ pcszType = "HardDisk";
+ if (att.fNonRotational)
+ pelmDevice->setAttribute("nonrotational", att.fNonRotational);
+ if (att.fDiscard)
+ pelmDevice->setAttribute("discard", att.fDiscard);
+ break;
+
+ case DeviceType_DVD:
+ pcszType = "DVD";
+ pelmDevice->setAttribute("passthrough", att.fPassThrough);
+ if (att.fTempEject)
+ pelmDevice->setAttribute("tempeject", att.fTempEject);
+ break;
+
+ case DeviceType_Floppy:
+ pcszType = "Floppy";
+ break;
+
+ default: break; /* Shut up MSC. */
+ }
+
+ pelmDevice->setAttribute("type", pcszType);
+
+ if (m->sv >= SettingsVersion_v1_15)
+ pelmDevice->setAttribute("hotpluggable", att.fHotPluggable);
+
+ pelmDevice->setAttribute("port", att.lPort);
+ pelmDevice->setAttribute("device", att.lDevice);
+
+ if (att.strBwGroup.length())
+ pelmDevice->setAttribute("bandwidthGroup", att.strBwGroup);
+
+ // attached image, if any
+ if (!att.uuid.isZero()
+ && att.uuid.isValid()
+ && (att.deviceType == DeviceType_HardDisk
+ || !fSkipRemovableMedia
+ )
+ )
+ {
+ xml::ElementNode *pelmImage = pelmDevice->createChild("Image");
+ pelmImage->setAttribute("uuid", att.uuid.toStringCurly());
+
+ // if caller wants a list of UUID elements, give it to them
+ if (pllElementsWithUuidAttributes)
+ pllElementsWithUuidAttributes->push_back(pelmImage);
+ }
+ else if ( (m->sv >= SettingsVersion_v1_9)
+ && (att.strHostDriveSrc.length())
+ )
+ pelmDevice->createChild("HostDrive")->setAttribute("src", att.strHostDriveSrc);
+ }
+ }
+}
+
+/**
+ * Creates a \<Debugging\> node under elmParent and then writes out the XML
+ * keys under that. Called for both the \<Machine\> node and for snapshots.
+ *
+ * @param elmParent Parent element.
+ * @param dbg Debugging settings.
+ */
+void MachineConfigFile::buildDebuggingXML(xml::ElementNode &elmParent, const Debugging &dbg)
+{
+ if (m->sv < SettingsVersion_v1_13 || dbg.areDefaultSettings())
+ return;
+
+ xml::ElementNode *pElmDebugging = elmParent.createChild("Debugging");
+ xml::ElementNode *pElmTracing = pElmDebugging->createChild("Tracing");
+ pElmTracing->setAttribute("enabled", dbg.fTracingEnabled);
+ pElmTracing->setAttribute("allowTracingToAccessVM", dbg.fAllowTracingToAccessVM);
+ pElmTracing->setAttribute("config", dbg.strTracingConfig);
+
+ xml::ElementNode *pElmGuestDebug = pElmDebugging->createChild("GuestDebug");
+ const char *pcszDebugProvider = NULL;
+ const char *pcszIoProvider = NULL;
+
+ switch (dbg.enmDbgProvider)
+ {
+ case GuestDebugProvider_None: pcszDebugProvider = "None"; break;
+ case GuestDebugProvider_GDB: pcszDebugProvider = "GDB"; break;
+ case GuestDebugProvider_KD: pcszDebugProvider = "KD"; break;
+ default: AssertFailed(); pcszDebugProvider = "None"; break;
+ }
+
+ switch (dbg.enmIoProvider)
+ {
+ case GuestDebugIoProvider_None: pcszIoProvider = "None"; break;
+ case GuestDebugIoProvider_TCP: pcszIoProvider = "TCP"; break;
+ case GuestDebugIoProvider_UDP: pcszIoProvider = "UDP"; break;
+ case GuestDebugIoProvider_IPC: pcszIoProvider = "IPC"; break;
+ default: AssertFailed(); pcszIoProvider = "None"; break;
+ }
+
+ pElmGuestDebug->setAttribute("provider", pcszDebugProvider);
+ pElmGuestDebug->setAttribute("io", pcszIoProvider);
+ pElmGuestDebug->setAttribute("address", dbg.strAddress);
+ pElmGuestDebug->setAttribute("port", dbg.ulPort);
+}
+
+/**
+ * Creates a \<Autostart\> node under elmParent and then writes out the XML
+ * keys under that. Called for both the \<Machine\> node and for snapshots.
+ *
+ * @param elmParent Parent element.
+ * @param autostrt Autostart settings.
+ */
+void MachineConfigFile::buildAutostartXML(xml::ElementNode &elmParent, const Autostart &autostrt)
+{
+ const char *pcszAutostop = NULL;
+
+ if (m->sv < SettingsVersion_v1_13 || autostrt.areDefaultSettings())
+ return;
+
+ xml::ElementNode *pElmAutostart = elmParent.createChild("Autostart");
+ pElmAutostart->setAttribute("enabled", autostrt.fAutostartEnabled);
+ pElmAutostart->setAttribute("delay", autostrt.uAutostartDelay);
+
+ switch (autostrt.enmAutostopType)
+ {
+ case AutostopType_Disabled: pcszAutostop = "Disabled"; break;
+ case AutostopType_SaveState: pcszAutostop = "SaveState"; break;
+ case AutostopType_PowerOff: pcszAutostop = "PowerOff"; break;
+ case AutostopType_AcpiShutdown: pcszAutostop = "AcpiShutdown"; break;
+ default: Assert(false); pcszAutostop = "Disabled"; break;
+ }
+ pElmAutostart->setAttribute("autostop", pcszAutostop);
+}
+
+void MachineConfigFile::buildRecordingXML(xml::ElementNode &elmParent, const RecordingSettings &recording)
+{
+ if (recording.areDefaultSettings()) /* Omit branch if we still have the default settings (i.e. nothing to save). */
+ return;
+
+ AssertReturnVoid(recording.mapScreens.size() <= 64); /* Make sure we never exceed the bitmap of 64 monitors. */
+
+ /* Note: Since settings 1.19 the recording settings have a dedicated XML branch outside of Hardware. */
+ if (m->sv >= SettingsVersion_v1_19 /* VBox >= 7.0 */)
+ {
+ /* Note: elmParent is Machine or Snapshot. */
+ xml::ElementNode *pelmRecording = elmParent.createChild("Recording");
+
+ if (!recordingSettings.common.areDefaultSettings())
+ {
+ pelmRecording->setAttribute("enabled", recording.common.fEnabled);
+ }
+
+ /* Only serialize screens which have non-default settings. */
+ uint32_t cScreensToWrite = 0;
+
+ RecordingScreenSettingsMap::const_iterator itScreen = recording.mapScreens.begin();
+ while (itScreen != recording.mapScreens.end())
+ {
+ if (!itScreen->second.areDefaultSettings())
+ cScreensToWrite++;
+ ++itScreen;
+ }
+
+ if (cScreensToWrite)
+ pelmRecording->setAttribute("screens", cScreensToWrite);
+
+ itScreen = recording.mapScreens.begin();
+ while (itScreen != recording.mapScreens.end())
+ {
+ if (!itScreen->second.areDefaultSettings()) /* Skip serializing screen settings which have default settings. */
+ {
+ xml::ElementNode *pelmScreen = pelmRecording->createChild("Screen");
+
+ pelmScreen->setAttribute("id", itScreen->first); /* The key equals the monitor ID. */
+ pelmScreen->setAttribute("enabled", itScreen->second.fEnabled);
+ com::Utf8Str strTemp;
+ RecordingScreenSettings::featuresToString(itScreen->second.featureMap, strTemp);
+ pelmScreen->setAttribute("featuresEnabled", strTemp);
+ if (itScreen->second.ulMaxTimeS)
+ pelmScreen->setAttribute("maxTimeS", itScreen->second.ulMaxTimeS);
+ if (itScreen->second.strOptions.isNotEmpty())
+ pelmScreen->setAttributePath("options", itScreen->second.strOptions);
+ pelmScreen->setAttribute("dest", itScreen->second.enmDest);
+ if (!itScreen->second.File.strName.isEmpty())
+ pelmScreen->setAttributePath("file", itScreen->second.File.strName);
+ if (itScreen->second.File.ulMaxSizeMB)
+ pelmScreen->setAttribute("maxSizeMB", itScreen->second.File.ulMaxSizeMB);
+
+ RecordingScreenSettings::videoCodecToString(itScreen->second.Video.enmCodec, strTemp);
+ pelmScreen->setAttribute("videoCodec", strTemp);
+ if (itScreen->second.Video.enmDeadline != RecordingCodecDeadline_Default)
+ pelmScreen->setAttribute("videoDeadline", itScreen->second.Video.enmDeadline);
+ if (itScreen->second.Video.enmRateCtlMode != RecordingRateControlMode_VBR) /* Is default. */
+ pelmScreen->setAttribute("videoRateCtlMode", itScreen->second.Video.enmRateCtlMode);
+ if (itScreen->second.Video.enmScalingMode != RecordingVideoScalingMode_None)
+ pelmScreen->setAttribute("videoScalingMode",itScreen->second.Video.enmScalingMode);
+ if ( itScreen->second.Video.ulWidth != 1024
+ || itScreen->second.Video.ulHeight != 768)
+ {
+ pelmScreen->setAttribute("horzRes", itScreen->second.Video.ulWidth);
+ pelmScreen->setAttribute("vertRes", itScreen->second.Video.ulHeight);
+ }
+ if (itScreen->second.Video.ulRate != 512)
+ pelmScreen->setAttribute("rateKbps", itScreen->second.Video.ulRate);
+ if (itScreen->second.Video.ulFPS)
+ pelmScreen->setAttribute("fps", itScreen->second.Video.ulFPS);
+
+ RecordingScreenSettings::audioCodecToString(itScreen->second.Audio.enmCodec, strTemp);
+ pelmScreen->setAttribute("audioCodec", strTemp);
+ if (itScreen->second.Audio.enmDeadline != RecordingCodecDeadline_Default)
+ pelmScreen->setAttribute("audioDeadline", itScreen->second.Audio.enmDeadline);
+ if (itScreen->second.Audio.enmRateCtlMode != RecordingRateControlMode_VBR) /* Is default. */
+ pelmScreen->setAttribute("audioRateCtlMode", itScreen->second.Audio.enmRateCtlMode);
+ if (itScreen->second.Audio.uHz != 22050)
+ pelmScreen->setAttribute("audioHz", itScreen->second.Audio.uHz);
+ if (itScreen->second.Audio.cBits != 16)
+ pelmScreen->setAttribute("audioBits", itScreen->second.Audio.cBits);
+ if (itScreen->second.Audio.cChannels != 2)
+ pelmScreen->setAttribute("audioChannels", itScreen->second.Audio.cChannels);
+ }
+ ++itScreen;
+ }
+ }
+ else if ( m->sv >= SettingsVersion_v1_14
+ && m->sv < SettingsVersion_v1_19 /* VBox < 7.0 */)
+ {
+ /* Note: elmParent is Hardware or Snapshot. */
+ xml::ElementNode *pelmVideoCapture = elmParent.createChild("VideoCapture");
+
+ if (!recordingSettings.common.areDefaultSettings())
+ {
+ pelmVideoCapture->setAttribute("enabled", recording.common.fEnabled);
+ }
+
+ /* Convert the enabled screens to the former uint64_t bit array and vice versa. */
+ uint64_t uScreensBitmap = 0;
+ RecordingScreenSettingsMap::const_iterator itScreen = recording.mapScreens.begin();
+ while (itScreen != recording.mapScreens.end())
+ {
+ if (itScreen->second.fEnabled)
+ uScreensBitmap |= RT_BIT_64(itScreen->first);
+ ++itScreen;
+ }
+
+ if (uScreensBitmap)
+ pelmVideoCapture->setAttribute("screens", uScreensBitmap);
+
+ Assert(recording.mapScreens.size());
+ const RecordingScreenSettingsMap::const_iterator itScreen0Settings = recording.mapScreens.find(0);
+ Assert(itScreen0Settings != recording.mapScreens.end());
+
+ if (itScreen0Settings->second.ulMaxTimeS)
+ pelmVideoCapture->setAttribute("maxTime", itScreen0Settings->second.ulMaxTimeS);
+ if (itScreen0Settings->second.strOptions.isNotEmpty())
+ pelmVideoCapture->setAttributePath("options", itScreen0Settings->second.strOptions);
+
+ if (!itScreen0Settings->second.File.strName.isEmpty())
+ pelmVideoCapture->setAttributePath("file", itScreen0Settings->second.File.strName);
+ if (itScreen0Settings->second.File.ulMaxSizeMB)
+ pelmVideoCapture->setAttribute("maxSize", itScreen0Settings->second.File.ulMaxSizeMB);
+
+ if ( itScreen0Settings->second.Video.ulWidth != 1024
+ || itScreen0Settings->second.Video.ulHeight != 768)
+ {
+ pelmVideoCapture->setAttribute("horzRes", itScreen0Settings->second.Video.ulWidth);
+ pelmVideoCapture->setAttribute("vertRes", itScreen0Settings->second.Video.ulHeight);
+ }
+ if (itScreen0Settings->second.Video.ulRate != 512)
+ pelmVideoCapture->setAttribute("rate", itScreen0Settings->second.Video.ulRate);
+ if (itScreen0Settings->second.Video.ulFPS)
+ pelmVideoCapture->setAttribute("fps", itScreen0Settings->second.Video.ulFPS);
+ }
+}
+
+/**
+ * Creates a \<Groups\> node under elmParent and then writes out the XML
+ * keys under that. Called for the \<Machine\> node only.
+ *
+ * @param elmParent Parent element.
+ * @param llGroups Groups list.
+ */
+void MachineConfigFile::buildGroupsXML(xml::ElementNode &elmParent, const StringsList &llGroups)
+{
+ if ( m->sv < SettingsVersion_v1_13 || llGroups.size() == 0
+ || (llGroups.size() == 1 && llGroups.front() == "/"))
+ return;
+
+ xml::ElementNode *pElmGroups = elmParent.createChild("Groups");
+ for (StringsList::const_iterator it = llGroups.begin();
+ it != llGroups.end();
+ ++it)
+ {
+ const Utf8Str &group = *it;
+ xml::ElementNode *pElmGroup = pElmGroups->createChild("Group");
+ pElmGroup->setAttribute("name", group);
+ }
+}
+
+/**
+ * Writes a single snapshot into the DOM tree. Initially this gets called from
+ * MachineConfigFile::write() for the root snapshot of a machine, if present;
+ * elmParent then points to the \<Snapshots\> node under the \<Machine\> node
+ * to which \<Snapshot\> must be added. This may then continue processing the
+ * child snapshots.
+ *
+ * @param elmParent
+ * @param snap
+ */
+void MachineConfigFile::buildSnapshotXML(xml::ElementNode &elmParent,
+ const Snapshot &snap)
+{
+ std::list<const Snapshot *> llSettingsTodo;
+ llSettingsTodo.push_back(&snap);
+ std::list<xml::ElementNode *> llElementsTodo;
+ llElementsTodo.push_back(&elmParent);
+ std::list<uint32_t> llDepthsTodo;
+ llDepthsTodo.push_back(1);
+
+ while (llSettingsTodo.size() > 0)
+ {
+ const Snapshot *pSnap = llSettingsTodo.front();
+ llSettingsTodo.pop_front();
+ xml::ElementNode *pElement = llElementsTodo.front();
+ llElementsTodo.pop_front();
+ uint32_t depth = llDepthsTodo.front();
+ llDepthsTodo.pop_front();
+
+ if (depth > SETTINGS_SNAPSHOT_DEPTH_MAX)
+ throw ConfigFileError(this, NULL, N_("Maximum snapshot tree depth of %u exceeded"), SETTINGS_SNAPSHOT_DEPTH_MAX);
+
+ xml::ElementNode *pelmSnapshot = pElement->createChild("Snapshot");
+
+ pelmSnapshot->setAttribute("uuid", pSnap->uuid.toStringCurly());
+ pelmSnapshot->setAttribute("name", pSnap->strName);
+ pelmSnapshot->setAttribute("timeStamp", stringifyTimestamp(pSnap->timestamp));
+
+ if (pSnap->strStateFile.length())
+ pelmSnapshot->setAttributePath("stateFile", pSnap->strStateFile);
+
+ if (pSnap->strDescription.length())
+ pelmSnapshot->createChild("Description")->addContent(pSnap->strDescription);
+
+ // We only skip removable media for OVF, but OVF never includes snapshots.
+ buildHardwareXML(*pelmSnapshot, pSnap->hardware, 0 /* fl */, NULL /* pllElementsWithUuidAttributes */);
+ buildDebuggingXML(*pelmSnapshot, pSnap->debugging);
+ buildAutostartXML(*pelmSnapshot, pSnap->autostart);
+ buildRecordingXML(*pelmSnapshot, pSnap->recordingSettings);
+ // note: Groups exist only for Machine, not for Snapshot
+
+ if (pSnap->llChildSnapshots.size())
+ {
+ xml::ElementNode *pelmChildren = pelmSnapshot->createChild("Snapshots");
+ for (SnapshotsList::const_iterator it = pSnap->llChildSnapshots.begin();
+ it != pSnap->llChildSnapshots.end();
+ ++it)
+ {
+ llSettingsTodo.push_back(&*it);
+ llElementsTodo.push_back(pelmChildren);
+ llDepthsTodo.push_back(depth + 1);
+ }
+ }
+ }
+}
+
+/**
+ * Builds the XML DOM tree for the machine config under the given XML element.
+ *
+ * This has been separated out from write() so it can be called from elsewhere,
+ * such as the OVF code, to build machine XML in an existing XML tree.
+ *
+ * As a result, this gets called from two locations:
+ *
+ * -- MachineConfigFile::write();
+ *
+ * -- Appliance::buildXMLForOneVirtualSystem()
+ *
+ * In fl, the following flag bits are recognized:
+ *
+ * -- BuildMachineXML_MediaRegistry: If set, the machine's media registry will
+ * be written, if present. This is not set when called from OVF because OVF
+ * has its own variant of a media registry. This flag is ignored unless the
+ * settings version is at least v1.11 (VirtualBox 4.0).
+ *
+ * -- BuildMachineXML_IncludeSnapshots: If set, descend into the snapshots tree
+ * of the machine and write out \<Snapshot\> and possibly more snapshots under
+ * that, if snapshots are present. Otherwise all snapshots are suppressed
+ * (when called from OVF).
+ *
+ * -- BuildMachineXML_WriteVBoxVersionAttribute: If set, add a settingsVersion
+ * attribute to the machine tag with the vbox settings version. This is for
+ * the OVF export case in which we don't have the settings version set in
+ * the root element.
+ *
+ * -- BuildMachineXML_SkipRemovableMedia: If set, removable media attachments
+ * (DVDs, floppies) are silently skipped. This is for the OVF export case
+ * until we support copying ISO and RAW media as well. This flag is ignored
+ * unless the settings version is at least v1.9, which is always the case
+ * when this gets called for OVF export.
+ *
+ * -- BuildMachineXML_SuppressSavedState: If set, the Machine/stateFile
+ * attribute is never set. This is also for the OVF export case because we
+ * cannot save states with OVF.
+ *
+ * @param elmMachine XML \<Machine\> element to add attributes and elements to.
+ * @param fl Flags.
+ * @param pllElementsWithUuidAttributes pointer to list that should receive UUID elements or NULL;
+ * see buildStorageControllersXML() for details.
+ */
+void MachineConfigFile::buildMachineXML(xml::ElementNode &elmMachine,
+ uint32_t fl,
+ std::list<xml::ElementNode*> *pllElementsWithUuidAttributes)
+{
+ if (fl & BuildMachineXML_WriteVBoxVersionAttribute)
+ {
+ // add settings version attribute to machine element
+ setVersionAttribute(elmMachine);
+ LogRel(("Exporting settings file \"%s\" with version \"%s\"\n", m->strFilename.c_str(), m->strSettingsVersionFull.c_str()));
+ }
+
+ elmMachine.setAttribute("uuid", uuid.toStringCurly());
+ elmMachine.setAttribute("name", machineUserData.strName);
+ if (machineUserData.fDirectoryIncludesUUID)
+ elmMachine.setAttribute("directoryIncludesUUID", machineUserData.fDirectoryIncludesUUID);
+ if (!machineUserData.fNameSync)
+ elmMachine.setAttribute("nameSync", machineUserData.fNameSync);
+ if (machineUserData.strDescription.length())
+ elmMachine.createChild("Description")->addContent(machineUserData.strDescription);
+ elmMachine.setAttribute("OSType", machineUserData.strOsType);
+
+
+ if (m->sv >= SettingsVersion_v1_19)
+ {
+ if (strStateKeyId.length())
+ elmMachine.setAttribute("stateKeyId", strStateKeyId);
+ if (strStateKeyStore.length())
+ elmMachine.setAttribute("stateKeyStore", strStateKeyStore);
+ if (strLogKeyId.length())
+ elmMachine.setAttribute("logKeyId", strLogKeyId);
+ if (strLogKeyStore.length())
+ elmMachine.setAttribute("logKeyStore", strLogKeyStore);
+ }
+ if ( strStateFile.length()
+ && !(fl & BuildMachineXML_SuppressSavedState)
+ )
+ elmMachine.setAttributePath("stateFile", strStateFile);
+
+ if ((fl & BuildMachineXML_IncludeSnapshots)
+ && !uuidCurrentSnapshot.isZero()
+ && uuidCurrentSnapshot.isValid())
+ elmMachine.setAttribute("currentSnapshot", uuidCurrentSnapshot.toStringCurly());
+
+ if (machineUserData.strSnapshotFolder.length())
+ elmMachine.setAttributePath("snapshotFolder", machineUserData.strSnapshotFolder);
+ if (!fCurrentStateModified)
+ elmMachine.setAttribute("currentStateModified", fCurrentStateModified);
+ elmMachine.setAttribute("lastStateChange", stringifyTimestamp(timeLastStateChange));
+ if (fAborted)
+ elmMachine.setAttribute("aborted", fAborted);
+
+ switch (machineUserData.enmVMPriority)
+ {
+ case VMProcPriority_Flat:
+ elmMachine.setAttribute("processPriority", "Flat");
+ break;
+ case VMProcPriority_Low:
+ elmMachine.setAttribute("processPriority", "Low");
+ break;
+ case VMProcPriority_Normal:
+ elmMachine.setAttribute("processPriority", "Normal");
+ break;
+ case VMProcPriority_High:
+ elmMachine.setAttribute("processPriority", "High");
+ break;
+ default:
+ break;
+ }
+ // Please keep the icon last so that one doesn't have to check if there
+ // is anything in the line after this very long attribute in the XML.
+ if (machineUserData.ovIcon.size())
+ {
+ Utf8Str strIcon;
+ toBase64(strIcon, machineUserData.ovIcon);
+ elmMachine.setAttribute("icon", strIcon);
+ }
+ if ( m->sv >= SettingsVersion_v1_9
+ && ( machineUserData.fTeleporterEnabled
+ || machineUserData.uTeleporterPort
+ || !machineUserData.strTeleporterAddress.isEmpty()
+ || !machineUserData.strTeleporterPassword.isEmpty()
+ )
+ )
+ {
+ xml::ElementNode *pelmTeleporter = elmMachine.createChild("Teleporter");
+ pelmTeleporter->setAttribute("enabled", machineUserData.fTeleporterEnabled);
+ pelmTeleporter->setAttribute("port", machineUserData.uTeleporterPort);
+ pelmTeleporter->setAttribute("address", machineUserData.strTeleporterAddress);
+ pelmTeleporter->setAttribute("password", machineUserData.strTeleporterPassword);
+ }
+
+ if ( (fl & BuildMachineXML_MediaRegistry)
+ && (m->sv >= SettingsVersion_v1_11)
+ )
+ buildMediaRegistry(elmMachine, mediaRegistry);
+
+ buildExtraData(elmMachine, mapExtraDataItems);
+
+ if ( (fl & BuildMachineXML_IncludeSnapshots)
+ && llFirstSnapshot.size())
+ buildSnapshotXML(elmMachine, llFirstSnapshot.front());
+
+ buildHardwareXML(elmMachine, hardwareMachine, fl, pllElementsWithUuidAttributes);
+ buildDebuggingXML(elmMachine, debugging);
+ buildAutostartXML(elmMachine, autostart);
+
+ /* Note: Must come *after* buildHardwareXML(), as the "Hardware" branch is needed. */
+ if ( m->sv >= SettingsVersion_v1_14
+ && m->sv < SettingsVersion_v1_19) /* < VBox 7.0. */
+ {
+ xml::ElementNode *pelHardware = unconst(elmMachine.findChildElement("Hardware"));
+ if (pelHardware)
+ buildRecordingXML(*pelHardware, recordingSettings);
+ }
+ else if (m->sv >= SettingsVersion_v1_19) /* Now lives outside of "Hardware", in "Machine". */
+ buildRecordingXML(elmMachine, recordingSettings);
+
+ buildGroupsXML(elmMachine, machineUserData.llGroups);
+}
+
+ /**
+ * Builds encrypted config.
+ *
+ * @sa MachineConfigFile::buildMachineXML
+ */
+void MachineConfigFile::buildMachineEncryptedXML(xml::ElementNode &elmMachine,
+ uint32_t fl,
+ std::list<xml::ElementNode*> *pllElementsWithUuidAttributes,
+ PCVBOXCRYPTOIF pCryptoIf,
+ const char *pszPassword = NULL)
+{
+ if ( !pszPassword
+ || !pCryptoIf)
+ throw ConfigFileError(this, &elmMachine, N_("Password is required"));
+
+ xml::Document *pDoc = new xml::Document;
+ xml::ElementNode *pelmRoot = pDoc->createRootElement("Machine");
+ pelmRoot->setAttribute("xmlns", VBOX_XML_NAMESPACE);
+ // Have the code for producing a proper schema reference. Not used by most
+ // tools, so don't bother doing it. The schema is not on the server anyway.
+#ifdef VBOX_WITH_SETTINGS_SCHEMA
+ pelmRoot->setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+ pelmRoot->setAttribute("xsi:schemaLocation", VBOX_XML_NAMESPACE " " VBOX_XML_SCHEMA);
+#endif
+
+ buildMachineXML(*pelmRoot, fl, pllElementsWithUuidAttributes);
+ xml::XmlStringWriter writer;
+ com::Utf8Str strMachineXml;
+ int rc = writer.write(*pDoc, &strMachineXml);
+ delete pDoc;
+ if (RT_SUCCESS(rc))
+ {
+ VBOXCRYPTOCTX hCryptoCtx;
+ if (strKeyStore.isEmpty())
+ {
+ rc = pCryptoIf->pfnCryptoCtxCreate("AES-GCM256", pszPassword, &hCryptoCtx);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszNewKeyStore;
+ rc = pCryptoIf->pfnCryptoCtxSave(hCryptoCtx, &pszNewKeyStore);
+ if (RT_SUCCESS(rc))
+ {
+ strKeyStore = pszNewKeyStore;
+ RTStrFree(pszNewKeyStore);
+ }
+ else
+ pCryptoIf->pfnCryptoCtxDestroy(hCryptoCtx);
+ }
+ }
+ else
+ rc = pCryptoIf->pfnCryptoCtxLoad(strKeyStore.c_str(), pszPassword, &hCryptoCtx);
+ if (RT_SUCCESS(rc))
+ {
+ IconBlob abEncrypted;
+ size_t cbEncrypted = 0;
+ rc = pCryptoIf->pfnCryptoCtxQueryEncryptedSize(hCryptoCtx, strMachineXml.length(), &cbEncrypted);
+ if (RT_SUCCESS(rc))
+ {
+ abEncrypted.resize(cbEncrypted);
+ rc = pCryptoIf->pfnCryptoCtxEncrypt(hCryptoCtx, false /*fPartial*/, NULL /*pvIV*/, 0 /*cbIV*/,
+ strMachineXml.c_str(), strMachineXml.length(),
+ uuid.raw(), sizeof(RTUUID),
+ &abEncrypted[0], abEncrypted.size(), &cbEncrypted);
+ int rc2 = pCryptoIf->pfnCryptoCtxDestroy(hCryptoCtx);
+ AssertRC(rc2);
+ if (RT_SUCCESS(rc))
+ {
+ abEncrypted.resize(cbEncrypted);
+ toBase64(strMachineXml, abEncrypted);
+ elmMachine.setAttribute("uuid", uuid.toStringCurly());
+ elmMachine.setAttribute("keyId", strKeyId);
+ elmMachine.setAttribute("keyStore", strKeyStore);
+ elmMachine.setContent(strMachineXml.c_str());
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ throw ConfigFileError(this, &elmMachine, N_("Creating machine encrypted xml failed. (%Rrc)"), rc);
+ }
+ else
+ throw ConfigFileError(this, &elmMachine, N_("Creating machine xml failed. (%Rrc)"), rc);
+}
+
+/**
+ * Returns true only if the given AudioDriverType is supported on
+ * the current host platform. For example, this would return false
+ * for AudioDriverType_DirectSound when compiled on a Linux host.
+ *
+* @return @c true if the current host supports the driver, @c false if not.
+ * @param enmDrvType AudioDriverType_* enum to test.
+ */
+/*static*/
+bool MachineConfigFile::isAudioDriverAllowedOnThisHost(AudioDriverType_T enmDrvType)
+{
+ switch (enmDrvType)
+ {
+ case AudioDriverType_Default:
+ RT_FALL_THROUGH();
+ case AudioDriverType_Null:
+ return true; /* Default and Null audio are always allowed. */
+#ifdef RT_OS_WINDOWS
+ case AudioDriverType_WAS:
+ /* We only support WAS on systems we tested so far (Vista+). */
+ if (RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(6,1,0))
+ break;
+ RT_FALL_THROUGH();
+ case AudioDriverType_DirectSound:
+#endif
+#ifdef VBOX_WITH_AUDIO_OSS
+ case AudioDriverType_OSS:
+#endif
+#ifdef VBOX_WITH_AUDIO_ALSA
+ case AudioDriverType_ALSA:
+#endif
+#ifdef VBOX_WITH_AUDIO_PULSE
+ case AudioDriverType_Pulse:
+#endif
+#ifdef RT_OS_DARWIN
+ case AudioDriverType_CoreAudio:
+#endif
+#ifdef RT_OS_OS2
+ case AudioDriverType_MMPM:
+#endif
+ return true;
+ default: break; /* Shut up MSC. */
+ }
+
+ return false;
+}
+
+/**
+ * Returns the AudioDriverType_* which should be used by default on this
+ * host platform. On Linux, this will check at runtime whether PulseAudio
+ * or ALSA are actually supported on the first call.
+ *
+ * When more than one supported audio stack is available, choose the most suited
+ * (probably newest in most cases) one.
+ *
+ * @return Default audio driver type for this host platform.
+ */
+/*static*/
+AudioDriverType_T MachineConfigFile::getHostDefaultAudioDriver()
+{
+#if defined(RT_OS_WINDOWS)
+ if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6,1,0))
+ return AudioDriverType_WAS;
+ return AudioDriverType_DirectSound;
+
+#elif defined(RT_OS_LINUX)
+ /* On Linux, we need to check at runtime what's actually supported. */
+ static RTCLockMtx s_mtx;
+ static AudioDriverType_T s_enmLinuxDriver = AudioDriverType_Null;
+ RTCLock lock(s_mtx);
+ if (s_enmLinuxDriver == AudioDriverType_Null)
+ {
+# ifdef VBOX_WITH_AUDIO_PULSE
+ /* Check for the pulse library & that the pulse audio daemon is running. */
+ if (RTProcIsRunningByName("pulseaudio") &&
+ RTLdrIsLoadable("libpulse.so.0"))
+ s_enmLinuxDriver = AudioDriverType_Pulse;
+ else
+# endif /* VBOX_WITH_AUDIO_PULSE */
+# ifdef VBOX_WITH_AUDIO_ALSA
+ /* Check if we can load the ALSA library */
+ if (RTLdrIsLoadable("libasound.so.2"))
+ s_enmLinuxDriver = AudioDriverType_ALSA;
+# endif /* VBOX_WITH_AUDIO_ALSA */
+# ifdef VBOX_WITH_AUDIO_OSS
+ else
+ s_enmLinuxDriver = AudioDriverType_OSS;
+# endif /* VBOX_WITH_AUDIO_OSS */
+ }
+ return s_enmLinuxDriver;
+
+#elif defined(RT_OS_DARWIN)
+ return AudioDriverType_CoreAudio;
+
+#elif defined(RT_OS_OS2)
+ return AudioDriverType_MMPM;
+
+#else /* All other platforms. */
+# ifdef VBOX_WITH_AUDIO_OSS
+ return AudioDriverType_OSS;
+# else
+ /* Return NULL driver as a fallback if nothing of the above is available. */
+ return AudioDriverType_Null;
+# endif
+#endif
+}
+
+/**
+ * Called from write() before calling ConfigFileBase::createStubDocument().
+ * This adjusts the settings version in m->sv if incompatible settings require
+ * a settings bump, whereas otherwise we try to preserve the settings version
+ * to avoid breaking compatibility with older versions.
+ *
+ * We do the checks in here in reverse order: newest first, oldest last, so
+ * that we avoid unnecessary checks since some of these are expensive.
+ */
+void MachineConfigFile::bumpSettingsVersionIfNeeded()
+{
+ if (m->sv < SettingsVersion_v1_19)
+ {
+ // VirtualBox 7.0 adds iommu device and full VM encryption.
+ if ( hardwareMachine.iommuType != IommuType_None
+ || strKeyId.isNotEmpty()
+ || strKeyStore.isNotEmpty()
+ || strStateKeyId.isNotEmpty()
+ || strStateKeyStore.isNotEmpty()
+ || hardwareMachine.nvramSettings.strKeyId.isNotEmpty()
+ || hardwareMachine.nvramSettings.strKeyStore.isNotEmpty()
+ /* Default for newly created VMs in VBox 7.0.
+ * Older VMs might have a specific audio driver set (also for VMs created with < VBox 7.0). */
+ || hardwareMachine.audioAdapter.driverType == AudioDriverType_Default
+ || recordingSettings.areDefaultSettings() == false
+ || strLogKeyId.isNotEmpty()
+ || strLogKeyStore.isEmpty())
+ {
+ m->sv = SettingsVersion_v1_19;
+ return;
+ }
+
+ // VirtualBox 7.0 adds a Trusted Platform Module.
+ if ( hardwareMachine.tpmSettings.tpmType != TpmType_None
+ || hardwareMachine.tpmSettings.strLocation.isNotEmpty())
+ {
+ m->sv = SettingsVersion_v1_19;
+ return;
+ }
+
+ NetworkAdaptersList::const_iterator netit;
+ for (netit = hardwareMachine.llNetworkAdapters.begin();
+ netit != hardwareMachine.llNetworkAdapters.end();
+ ++netit)
+ {
+ // VirtualBox 7.0 adds a flag if NAT can reach localhost.
+ if ( netit->fEnabled
+ && netit->mode == NetworkAttachmentType_NAT
+ && !netit->nat.fLocalhostReachable)
+ {
+ m->sv = SettingsVersion_v1_19;
+ break;
+ }
+
+#ifdef VBOX_WITH_VMNET
+ // VirtualBox 7.0 adds a host-only network attachment.
+ if (netit->mode == NetworkAttachmentType_HostOnlyNetwork)
+ {
+ m->sv = SettingsVersion_v1_19;
+ break;
+ }
+#endif /* VBOX_WITH_VMNET */
+ }
+
+ // VirtualBox 7.0 adds guest debug settings.
+ if ( debugging.enmDbgProvider != GuestDebugProvider_None
+ || debugging.enmIoProvider != GuestDebugIoProvider_None
+ || debugging.strAddress.isNotEmpty()
+ || debugging.ulPort != 0)
+ {
+ m->sv = SettingsVersion_v1_19;
+ return;
+ }
+ }
+
+ if (m->sv < SettingsVersion_v1_18)
+ {
+ if (!hardwareMachine.nvramSettings.strNvramPath.isEmpty())
+ {
+ m->sv = SettingsVersion_v1_18;
+ return;
+ }
+
+ // VirtualBox 6.1 adds AMD-V virtualized VMSAVE/VMLOAD setting.
+ if (hardwareMachine.fVirtVmsaveVmload == false)
+ {
+ m->sv = SettingsVersion_v1_18;
+ return;
+ }
+
+ // VirtualBox 6.1 adds a virtio-scsi storage controller.
+ for (StorageControllersList::const_iterator it = hardwareMachine.storage.llStorageControllers.begin();
+ it != hardwareMachine.storage.llStorageControllers.end();
+ ++it)
+ {
+ const StorageController &sctl = *it;
+
+ if (sctl.controllerType == StorageControllerType_VirtioSCSI)
+ {
+ m->sv = SettingsVersion_v1_18;
+ return;
+ }
+ }
+
+#ifdef VBOX_WITH_CLOUD_NET
+ NetworkAdaptersList::const_iterator netit;
+ for (netit = hardwareMachine.llNetworkAdapters.begin();
+ netit != hardwareMachine.llNetworkAdapters.end();
+ ++netit)
+ {
+ // VirtualBox 6.1 adds support for cloud networks.
+ if ( netit->fEnabled
+ && netit->mode == NetworkAttachmentType_Cloud)
+ {
+ m->sv = SettingsVersion_v1_18;
+ break;
+ }
+
+ }
+#endif /* VBOX_WITH_CLOUD_NET */
+ }
+
+ if (m->sv < SettingsVersion_v1_17)
+ {
+ if (machineUserData.enmVMPriority != VMProcPriority_Default)
+ {
+ m->sv = SettingsVersion_v1_17;
+ return;
+ }
+
+ // VirtualBox 6.0 adds nested hardware virtualization, using native API (NEM).
+ if ( hardwareMachine.fNestedHWVirt
+ || hardwareMachine.fUseNativeApi)
+ {
+ m->sv = SettingsVersion_v1_17;
+ return;
+ }
+ if (hardwareMachine.llSharedFolders.size())
+ for (SharedFoldersList::const_iterator it = hardwareMachine.llSharedFolders.begin();
+ it != hardwareMachine.llSharedFolders.end();
+ ++it)
+ if (it->strAutoMountPoint.isNotEmpty())
+ {
+ m->sv = SettingsVersion_v1_17;
+ return;
+ }
+
+ /*
+ * Check if any serial port uses a non 16550A serial port.
+ */
+ for (SerialPortsList::const_iterator it = hardwareMachine.llSerialPorts.begin();
+ it != hardwareMachine.llSerialPorts.end();
+ ++it)
+ {
+ const SerialPort &port = *it;
+ if (port.uartType != UartType_U16550A)
+ {
+ m->sv = SettingsVersion_v1_17;
+ return;
+ }
+ }
+ }
+
+ if (m->sv < SettingsVersion_v1_16)
+ {
+ // VirtualBox 5.1 adds a NVMe storage controller, paravirt debug
+ // options, cpu profile, APIC settings (CPU capability and BIOS).
+
+ if ( hardwareMachine.strParavirtDebug.isNotEmpty()
+ || (!hardwareMachine.strCpuProfile.equals("host") && hardwareMachine.strCpuProfile.isNotEmpty())
+ || hardwareMachine.biosSettings.apicMode != APICMode_APIC
+ || !hardwareMachine.fAPIC
+ || hardwareMachine.fX2APIC
+ || hardwareMachine.fIBPBOnVMExit
+ || hardwareMachine.fIBPBOnVMEntry
+ || hardwareMachine.fSpecCtrl
+ || hardwareMachine.fSpecCtrlByHost
+ || !hardwareMachine.fL1DFlushOnSched
+ || hardwareMachine.fL1DFlushOnVMEntry
+ || !hardwareMachine.fMDSClearOnSched
+ || hardwareMachine.fMDSClearOnVMEntry)
+ {
+ m->sv = SettingsVersion_v1_16;
+ return;
+ }
+
+ for (StorageControllersList::const_iterator it = hardwareMachine.storage.llStorageControllers.begin();
+ it != hardwareMachine.storage.llStorageControllers.end();
+ ++it)
+ {
+ const StorageController &sctl = *it;
+
+ if (sctl.controllerType == StorageControllerType_NVMe)
+ {
+ m->sv = SettingsVersion_v1_16;
+ return;
+ }
+ }
+
+ for (CpuIdLeafsList::const_iterator it = hardwareMachine.llCpuIdLeafs.begin();
+ it != hardwareMachine.llCpuIdLeafs.end();
+ ++it)
+ if (it->idxSub != 0)
+ {
+ m->sv = SettingsVersion_v1_16;
+ return;
+ }
+ }
+
+ if (m->sv < SettingsVersion_v1_15)
+ {
+ // VirtualBox 5.0 adds paravirt providers, explicit AHCI port hotplug
+ // setting, USB storage controller, xHCI, serial port TCP backend
+ // and VM process priority.
+
+ /*
+ * Check simple configuration bits first, loopy stuff afterwards.
+ */
+ if ( hardwareMachine.paravirtProvider != ParavirtProvider_Legacy
+ || hardwareMachine.uCpuIdPortabilityLevel != 0)
+ {
+ m->sv = SettingsVersion_v1_15;
+ return;
+ }
+
+ /*
+ * Check whether the hotpluggable flag of all storage devices differs
+ * from the default for old settings.
+ * AHCI ports are hotpluggable by default every other device is not.
+ * Also check if there are USB storage controllers.
+ */
+ for (StorageControllersList::const_iterator it = hardwareMachine.storage.llStorageControllers.begin();
+ it != hardwareMachine.storage.llStorageControllers.end();
+ ++it)
+ {
+ const StorageController &sctl = *it;
+
+ if (sctl.controllerType == StorageControllerType_USB)
+ {
+ m->sv = SettingsVersion_v1_15;
+ return;
+ }
+
+ for (AttachedDevicesList::const_iterator it2 = sctl.llAttachedDevices.begin();
+ it2 != sctl.llAttachedDevices.end();
+ ++it2)
+ {
+ const AttachedDevice &att = *it2;
+
+ if ( ( att.fHotPluggable
+ && sctl.controllerType != StorageControllerType_IntelAhci)
+ || ( !att.fHotPluggable
+ && sctl.controllerType == StorageControllerType_IntelAhci))
+ {
+ m->sv = SettingsVersion_v1_15;
+ return;
+ }
+ }
+ }
+
+ /*
+ * Check if there is an xHCI (USB3) USB controller.
+ */
+ for (USBControllerList::const_iterator it = hardwareMachine.usbSettings.llUSBControllers.begin();
+ it != hardwareMachine.usbSettings.llUSBControllers.end();
+ ++it)
+ {
+ const USBController &ctrl = *it;
+ if (ctrl.enmType == USBControllerType_XHCI)
+ {
+ m->sv = SettingsVersion_v1_15;
+ return;
+ }
+ }
+
+ /*
+ * Check if any serial port uses the TCP backend.
+ */
+ for (SerialPortsList::const_iterator it = hardwareMachine.llSerialPorts.begin();
+ it != hardwareMachine.llSerialPorts.end();
+ ++it)
+ {
+ const SerialPort &port = *it;
+ if (port.portMode == PortMode_TCP)
+ {
+ m->sv = SettingsVersion_v1_15;
+ return;
+ }
+ }
+ }
+
+ if (m->sv < SettingsVersion_v1_14)
+ {
+ // VirtualBox 4.3 adds default frontend setting, graphics controller
+ // setting, explicit long mode setting, (video) capturing and NAT networking.
+ if ( !hardwareMachine.strDefaultFrontend.isEmpty()
+ || hardwareMachine.graphicsAdapter.graphicsControllerType != GraphicsControllerType_VBoxVGA
+ || hardwareMachine.enmLongMode != Hardware::LongMode_Legacy
+ || machineUserData.ovIcon.size() > 0
+ || recordingSettings.common.fEnabled)
+ {
+ m->sv = SettingsVersion_v1_14;
+ return;
+ }
+ NetworkAdaptersList::const_iterator netit;
+ for (netit = hardwareMachine.llNetworkAdapters.begin();
+ netit != hardwareMachine.llNetworkAdapters.end();
+ ++netit)
+ {
+ if (netit->mode == NetworkAttachmentType_NATNetwork)
+ {
+ m->sv = SettingsVersion_v1_14;
+ break;
+ }
+ }
+ }
+
+ if (m->sv < SettingsVersion_v1_14)
+ {
+ unsigned cOhciCtrls = 0;
+ unsigned cEhciCtrls = 0;
+ bool fNonStdName = false;
+
+ for (USBControllerList::const_iterator it = hardwareMachine.usbSettings.llUSBControllers.begin();
+ it != hardwareMachine.usbSettings.llUSBControllers.end();
+ ++it)
+ {
+ const USBController &ctrl = *it;
+
+ switch (ctrl.enmType)
+ {
+ case USBControllerType_OHCI:
+ cOhciCtrls++;
+ if (ctrl.strName != "OHCI")
+ fNonStdName = true;
+ break;
+ case USBControllerType_EHCI:
+ cEhciCtrls++;
+ if (ctrl.strName != "EHCI")
+ fNonStdName = true;
+ break;
+ default:
+ /* Anything unknown forces a bump. */
+ fNonStdName = true;
+ }
+
+ /* Skip checking other controllers if the settings bump is necessary. */
+ if (cOhciCtrls > 1 || cEhciCtrls > 1 || fNonStdName)
+ {
+ m->sv = SettingsVersion_v1_14;
+ break;
+ }
+ }
+ }
+
+ if (m->sv < SettingsVersion_v1_13)
+ {
+ // VirtualBox 4.2 adds tracing, autostart, UUID in directory and groups.
+ if ( !debugging.areDefaultSettings()
+ || !autostart.areDefaultSettings()
+ || machineUserData.fDirectoryIncludesUUID
+ || machineUserData.llGroups.size() > 1
+ || machineUserData.llGroups.front() != "/")
+ m->sv = SettingsVersion_v1_13;
+ }
+
+ if (m->sv < SettingsVersion_v1_13)
+ {
+ // VirtualBox 4.2 changes the units for bandwidth group limits.
+ for (BandwidthGroupList::const_iterator it = hardwareMachine.ioSettings.llBandwidthGroups.begin();
+ it != hardwareMachine.ioSettings.llBandwidthGroups.end();
+ ++it)
+ {
+ const BandwidthGroup &gr = *it;
+ if (gr.cMaxBytesPerSec % _1M)
+ {
+ // Bump version if a limit cannot be expressed in megabytes
+ m->sv = SettingsVersion_v1_13;
+ break;
+ }
+ }
+ }
+
+ if (m->sv < SettingsVersion_v1_12)
+ {
+ // VirtualBox 4.1 adds PCI passthrough and emulated USB Smart Card reader
+ if ( hardwareMachine.pciAttachments.size()
+ || hardwareMachine.fEmulatedUSBCardReader)
+ m->sv = SettingsVersion_v1_12;
+ }
+
+ if (m->sv < SettingsVersion_v1_12)
+ {
+ // VirtualBox 4.1 adds a promiscuous mode policy to the network
+ // adapters and a generic network driver transport.
+ NetworkAdaptersList::const_iterator netit;
+ for (netit = hardwareMachine.llNetworkAdapters.begin();
+ netit != hardwareMachine.llNetworkAdapters.end();
+ ++netit)
+ {
+ if ( netit->enmPromiscModePolicy != NetworkAdapterPromiscModePolicy_Deny
+ || netit->mode == NetworkAttachmentType_Generic
+ || !netit->areGenericDriverDefaultSettings()
+ )
+ {
+ m->sv = SettingsVersion_v1_12;
+ break;
+ }
+ }
+ }
+
+ if (m->sv < SettingsVersion_v1_11)
+ {
+ // VirtualBox 4.0 adds HD audio, CPU priorities, ~fault tolerance~,
+ // per-machine media registries, VRDE, JRockitVE, bandwidth groups,
+ // ICH9 chipset
+ if ( hardwareMachine.audioAdapter.controllerType == AudioControllerType_HDA
+ || hardwareMachine.ulCpuExecutionCap != 100
+ || mediaRegistry.llHardDisks.size()
+ || mediaRegistry.llDvdImages.size()
+ || mediaRegistry.llFloppyImages.size()
+ || !hardwareMachine.vrdeSettings.strVrdeExtPack.isEmpty()
+ || !hardwareMachine.vrdeSettings.strAuthLibrary.isEmpty()
+ || machineUserData.strOsType == "JRockitVE"
+ || hardwareMachine.ioSettings.llBandwidthGroups.size()
+ || hardwareMachine.chipsetType == ChipsetType_ICH9
+ )
+ m->sv = SettingsVersion_v1_11;
+ }
+
+ if (m->sv < SettingsVersion_v1_10)
+ {
+ /* If the properties contain elements other than "TCP/Ports" and "TCP/Address",
+ * then increase the version to at least VBox 3.2, which can have video channel properties.
+ */
+ unsigned cOldProperties = 0;
+
+ StringsMap::const_iterator it = hardwareMachine.vrdeSettings.mapProperties.find("TCP/Ports");
+ if (it != hardwareMachine.vrdeSettings.mapProperties.end())
+ cOldProperties++;
+ it = hardwareMachine.vrdeSettings.mapProperties.find("TCP/Address");
+ if (it != hardwareMachine.vrdeSettings.mapProperties.end())
+ cOldProperties++;
+
+ if (hardwareMachine.vrdeSettings.mapProperties.size() != cOldProperties)
+ m->sv = SettingsVersion_v1_10;
+ }
+
+ if (m->sv < SettingsVersion_v1_11)
+ {
+ /* If the properties contain elements other than "TCP/Ports", "TCP/Address",
+ * "VideoChannel/Enabled" and "VideoChannel/Quality" then increase the version to VBox 4.0.
+ */
+ unsigned cOldProperties = 0;
+
+ StringsMap::const_iterator it = hardwareMachine.vrdeSettings.mapProperties.find("TCP/Ports");
+ if (it != hardwareMachine.vrdeSettings.mapProperties.end())
+ cOldProperties++;
+ it = hardwareMachine.vrdeSettings.mapProperties.find("TCP/Address");
+ if (it != hardwareMachine.vrdeSettings.mapProperties.end())
+ cOldProperties++;
+ it = hardwareMachine.vrdeSettings.mapProperties.find("VideoChannel/Enabled");
+ if (it != hardwareMachine.vrdeSettings.mapProperties.end())
+ cOldProperties++;
+ it = hardwareMachine.vrdeSettings.mapProperties.find("VideoChannel/Quality");
+ if (it != hardwareMachine.vrdeSettings.mapProperties.end())
+ cOldProperties++;
+
+ if (hardwareMachine.vrdeSettings.mapProperties.size() != cOldProperties)
+ m->sv = SettingsVersion_v1_11;
+ }
+
+ // settings version 1.9 is required if there is not exactly one DVD
+ // or more than one floppy drive present or the DVD is not at the secondary
+ // master; this check is a bit more complicated
+ //
+ // settings version 1.10 is required if the host cache should be disabled
+ //
+ // settings version 1.11 is required for bandwidth limits and if more than
+ // one controller of each type is present.
+ if (m->sv < SettingsVersion_v1_11)
+ {
+ // count attached DVDs and floppies (only if < v1.9)
+ size_t cDVDs = 0;
+ size_t cFloppies = 0;
+
+ // count storage controllers (if < v1.11)
+ size_t cSata = 0;
+ size_t cScsiLsi = 0;
+ size_t cScsiBuslogic = 0;
+ size_t cSas = 0;
+ size_t cIde = 0;
+ size_t cFloppy = 0;
+
+ // need to run thru all the storage controllers and attached devices to figure this out
+ for (StorageControllersList::const_iterator it = hardwareMachine.storage.llStorageControllers.begin();
+ it != hardwareMachine.storage.llStorageControllers.end();
+ ++it)
+ {
+ const StorageController &sctl = *it;
+
+ // count storage controllers of each type; 1.11 is required if more than one
+ // controller of one type is present
+ switch (sctl.storageBus)
+ {
+ case StorageBus_IDE:
+ cIde++;
+ break;
+ case StorageBus_SATA:
+ cSata++;
+ break;
+ case StorageBus_SAS:
+ cSas++;
+ break;
+ case StorageBus_SCSI:
+ if (sctl.controllerType == StorageControllerType_LsiLogic)
+ cScsiLsi++;
+ else
+ cScsiBuslogic++;
+ break;
+ case StorageBus_Floppy:
+ cFloppy++;
+ break;
+ default:
+ // Do nothing
+ break;
+ }
+
+ if ( cSata > 1
+ || cScsiLsi > 1
+ || cScsiBuslogic > 1
+ || cSas > 1
+ || cIde > 1
+ || cFloppy > 1)
+ {
+ m->sv = SettingsVersion_v1_11;
+ break; // abort the loop -- we will not raise the version further
+ }
+
+ for (AttachedDevicesList::const_iterator it2 = sctl.llAttachedDevices.begin();
+ it2 != sctl.llAttachedDevices.end();
+ ++it2)
+ {
+ const AttachedDevice &att = *it2;
+
+ // Bandwidth limitations are new in VirtualBox 4.0 (1.11)
+ if (m->sv < SettingsVersion_v1_11)
+ {
+ if (att.strBwGroup.length() != 0)
+ {
+ m->sv = SettingsVersion_v1_11;
+ break; // abort the loop -- we will not raise the version further
+ }
+ }
+
+ // disabling the host IO cache requires settings version 1.10
+ if ( (m->sv < SettingsVersion_v1_10)
+ && (!sctl.fUseHostIOCache)
+ )
+ m->sv = SettingsVersion_v1_10;
+
+ // we can only write the StorageController/@Instance attribute with v1.9
+ if ( (m->sv < SettingsVersion_v1_9)
+ && (sctl.ulInstance != 0)
+ )
+ m->sv = SettingsVersion_v1_9;
+
+ if (m->sv < SettingsVersion_v1_9)
+ {
+ if (att.deviceType == DeviceType_DVD)
+ {
+ if ( (sctl.storageBus != StorageBus_IDE) // DVD at bus other than DVD?
+ || (att.lPort != 1) // DVDs not at secondary master?
+ || (att.lDevice != 0)
+ )
+ m->sv = SettingsVersion_v1_9;
+
+ ++cDVDs;
+ }
+ else if (att.deviceType == DeviceType_Floppy)
+ ++cFloppies;
+ }
+ }
+
+ if (m->sv >= SettingsVersion_v1_11)
+ break; // abort the loop -- we will not raise the version further
+ }
+
+ // VirtualBox before 3.1 had zero or one floppy and exactly one DVD,
+ // so any deviation from that will require settings version 1.9
+ if ( (m->sv < SettingsVersion_v1_9)
+ && ( (cDVDs != 1)
+ || (cFloppies > 1)
+ )
+ )
+ m->sv = SettingsVersion_v1_9;
+ }
+
+ // VirtualBox 3.2: Check for non default I/O settings
+ if (m->sv < SettingsVersion_v1_10)
+ {
+ if ( (hardwareMachine.ioSettings.fIOCacheEnabled != true)
+ || (hardwareMachine.ioSettings.ulIOCacheSize != 5)
+ // and page fusion
+ || (hardwareMachine.fPageFusionEnabled)
+ // and CPU hotplug, RTC timezone control, HID type and HPET
+ || machineUserData.fRTCUseUTC
+ || hardwareMachine.fCpuHotPlug
+ || hardwareMachine.pointingHIDType != PointingHIDType_PS2Mouse
+ || hardwareMachine.keyboardHIDType != KeyboardHIDType_PS2Keyboard
+ || hardwareMachine.fHPETEnabled
+ )
+ m->sv = SettingsVersion_v1_10;
+ }
+
+ // VirtualBox 3.2 adds NAT and boot priority to the NIC config in Main
+ // VirtualBox 4.0 adds network bandwitdth
+ if (m->sv < SettingsVersion_v1_11)
+ {
+ NetworkAdaptersList::const_iterator netit;
+ for (netit = hardwareMachine.llNetworkAdapters.begin();
+ netit != hardwareMachine.llNetworkAdapters.end();
+ ++netit)
+ {
+ if ( (m->sv < SettingsVersion_v1_12)
+ && (netit->strBandwidthGroup.isNotEmpty())
+ )
+ {
+ /* New in VirtualBox 4.1 */
+ m->sv = SettingsVersion_v1_12;
+ break;
+ }
+ else if ( (m->sv < SettingsVersion_v1_10)
+ && (netit->fEnabled)
+ && (netit->mode == NetworkAttachmentType_NAT)
+ && ( netit->nat.u32Mtu != 0
+ || netit->nat.u32SockRcv != 0
+ || netit->nat.u32SockSnd != 0
+ || netit->nat.u32TcpRcv != 0
+ || netit->nat.u32TcpSnd != 0
+ || !netit->nat.fDNSPassDomain
+ || netit->nat.fDNSProxy
+ || netit->nat.fDNSUseHostResolver
+ || netit->nat.fAliasLog
+ || netit->nat.fAliasProxyOnly
+ || netit->nat.fAliasUseSamePorts
+ || netit->nat.strTFTPPrefix.length()
+ || netit->nat.strTFTPBootFile.length()
+ || netit->nat.strTFTPNextServer.length()
+ || netit->nat.mapRules.size()
+ )
+ )
+ {
+ m->sv = SettingsVersion_v1_10;
+ // no break because we still might need v1.11 above
+ }
+ else if ( (m->sv < SettingsVersion_v1_10)
+ && (netit->fEnabled)
+ && (netit->ulBootPriority != 0)
+ )
+ {
+ m->sv = SettingsVersion_v1_10;
+ // no break because we still might need v1.11 above
+ }
+ }
+ }
+
+ // all the following require settings version 1.9
+ if ( (m->sv < SettingsVersion_v1_9)
+ && ( (hardwareMachine.firmwareType >= FirmwareType_EFI)
+ || machineUserData.fTeleporterEnabled
+ || machineUserData.uTeleporterPort
+ || !machineUserData.strTeleporterAddress.isEmpty()
+ || !machineUserData.strTeleporterPassword.isEmpty()
+ || (!hardwareMachine.uuid.isZero() && hardwareMachine.uuid.isValid())
+ )
+ )
+ m->sv = SettingsVersion_v1_9;
+
+ // "accelerate 2d video" requires settings version 1.8
+ if ( (m->sv < SettingsVersion_v1_8)
+ && (hardwareMachine.graphicsAdapter.fAccelerate2DVideo)
+ )
+ m->sv = SettingsVersion_v1_8;
+
+ // The hardware versions other than "1" requires settings version 1.4 (2.1+).
+ if ( m->sv < SettingsVersion_v1_4
+ && hardwareMachine.strVersion != "1"
+ )
+ m->sv = SettingsVersion_v1_4;
+}
+
+/**
+ * Called from Main code to write a machine config file to disk. This builds a DOM tree from
+ * the member variables and then writes the XML file; it throws xml::Error instances on errors,
+ * in particular if the file cannot be written.
+ */
+void MachineConfigFile::write(const com::Utf8Str &strFilename, PCVBOXCRYPTOIF pCryptoIf, const char *pszPassword)
+{
+ try
+ {
+ // createStubDocument() sets the settings version to at least 1.7; however,
+ // we might need to enfore a later settings version if incompatible settings
+ // are present:
+ bumpSettingsVersionIfNeeded();
+
+ m->strFilename = strFilename;
+ /*
+ * Only create a backup if it is not encrypted.
+ * Otherwise we get an unencrypted copy of the settings.
+ */
+ if (strKeyId.isEmpty() && strKeyStore.isEmpty())
+ specialBackupIfFirstBump();
+ createStubDocument();
+
+ if (strKeyStore.isNotEmpty())
+ {
+ xml::ElementNode *pelmMachine = m->pelmRoot->createChild("MachineEncrypted");
+ buildMachineEncryptedXML(*pelmMachine,
+ MachineConfigFile::BuildMachineXML_IncludeSnapshots
+ | MachineConfigFile::BuildMachineXML_MediaRegistry,
+ // but not BuildMachineXML_WriteVBoxVersionAttribute
+ NULL, /* pllElementsWithUuidAttributes */
+ pCryptoIf,
+ pszPassword);
+ }
+ else
+ {
+ xml::ElementNode *pelmMachine = m->pelmRoot->createChild("Machine");
+ buildMachineXML(*pelmMachine,
+ MachineConfigFile::BuildMachineXML_IncludeSnapshots
+ | MachineConfigFile::BuildMachineXML_MediaRegistry,
+ // but not BuildMachineXML_WriteVBoxVersionAttribute
+ NULL); /* pllElementsWithUuidAttributes */
+ }
+
+ // now go write the XML
+ xml::XmlFileWriter writer(*m->pDoc);
+ writer.write(m->strFilename.c_str(), true /*fSafe*/);
+
+ m->fFileExists = true;
+ clearDocument();
+ LogRel(("Finished saving settings file \"%s\"\n", m->strFilename.c_str()));
+ }
+ catch (...)
+ {
+ clearDocument();
+ LogRel(("Finished saving settings file \"%s\" with failure\n", m->strFilename.c_str()));
+ throw;
+ }
+}
diff --git a/src/VBox/Main/xml/VirtualBox-settings.xsd b/src/VBox/Main/xml/VirtualBox-settings.xsd
new file mode 100644
index 00000000..eb7f98d6
--- /dev/null
+++ b/src/VBox/Main/xml/VirtualBox-settings.xsd
@@ -0,0 +1,1514 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * :tabSize=2:indentSize=2:noTabs=true:
+ * :folding=explicit:collapseFolds=1:
+ *
+ * Oracle VM VirtualBox Settings Schema
+ * Common definitions
+-->
+<!--
+ Copyright (C) 2004-2022 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+
+<xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns="http://www.virtualbox.org/"
+ xmlns:vb="http://www.virtualbox.org/"
+ targetNamespace="http://www.virtualbox.org/"
+ elementFormDefault="qualified"
+>
+
+<xsd:annotation>
+ <xsd:documentation xml:lang="en">
+ Oracle VM VirtualBox Settings Schema (common definitions).
+ Copyright (C) 2004-2022 Oracle and/or its affiliates.
+ </xsd:documentation>
+</xsd:annotation>
+
+<!--
+// Simple types
+/////////////////////////////////////////////////////////////////////////
+-->
+
+<xsd:simpleType name="TUUID">
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="\{[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TNonNullUUID">
+ <xsd:restriction base="TUUID">
+ <xsd:pattern value=".*[1-9A-Fa-f]+.*"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TUInt8">
+ <xsd:union>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:unsignedByte">
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="0[xX][A-Fa-f0-9]{1,2}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:union>
+</xsd:simpleType>
+
+<xsd:simpleType name="TUInt16">
+ <xsd:union>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:unsignedShort">
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="0[xX][A-Fa-f0-9]{1,4}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:union>
+</xsd:simpleType>
+
+<xsd:simpleType name="TUInt16Hex">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="0x[A-Fa-f0-9]{1,4}"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TUInt16HexNoBase">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="[A-Fa-f0-9]{1,4}"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TUInt32Hex">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="0x[A-Fa-f0-9]{1,8}"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TUInt64Hex">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="0x[A-Fa-f0-9]{1,16}"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TLocalFile">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value=".+"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TDeviceType">
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="None"/>
+ <xsd:enumeration value="Floppy"/>
+ <xsd:enumeration value="DVD"/>
+ <xsd:enumeration value="HardDisk"/>
+ <xsd:enumeration value="Network"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TMediumDeviceType">
+ <xsd:restriction base="TDeviceType">
+ <xsd:enumeration value="Floppy"/>
+ <xsd:enumeration value="DVD"/>
+ <xsd:enumeration value="HardDisk"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TMediumType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="Normal"/>
+ <xsd:enumeration value="Immutable"/>
+ <xsd:enumeration value="Writethrough"/>
+ <xsd:enumeration value="Shareable"/>
+ <xsd:enumeration value="Readonly"/>
+ <xsd:enumeration value="MultiAttach"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TVMProcPriority">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="Invalid"/>
+ <xsd:enumeration value="Default"/>
+ <xsd:enumeration value="Flat"/>
+ <xsd:enumeration value="Low"/>
+ <xsd:enumeration value="Normal"/>
+ <xsd:enumeration value="High"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TMonitorCount">
+ <xsd:restriction base="xsd:unsignedInt">
+ <xsd:minInclusive value="1"/>
+ <!-- This should be in sync with VBOX_VIDEO_MAX_SCREENS in VBoxVideo.h -->
+ <xsd:maxInclusive value="64"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TUSBDeviceFilterAction">
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="Ignore"/>
+ <xsd:enumeration value="Hold"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TNonEmptyString">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value=".+"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TPresentDateTimeUTC">
+ <xsd:restriction base="xsd:dateTime">
+ <xsd:minInclusive value="1900-01-01T00:00:00Z"/>
+ <xsd:maxInclusive value="199999999-12-31T23:59:59Z"/>
+ <xsd:pattern value=".+-.+-.+T.+:.+:[0-9]{2}Z"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+
+<xsd:simpleType name="TAuthType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="null"/> <!-- deprecated -->
+ <xsd:enumeration value="Null"/>
+ <xsd:enumeration value="Guest"/>
+ <xsd:enumeration value="External"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TNetworkAdapterType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="Am79C970A"/>
+ <xsd:enumeration value="Am79C973"/>
+ <xsd:enumeration value="Am79C960"/>
+ <xsd:enumeration value="82540EM"/>
+ <xsd:enumeration value="82543GC"/>
+ <xsd:enumeration value="82545EM"/>
+ <xsd:enumeration value="virtio"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TTriStateBoolType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="false"/>
+ <xsd:enumeration value="true"/>
+ <xsd:enumeration value="default"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TBIOSBootMenuModeType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="Disabled"/>
+ <xsd:enumeration value="MenuOnly"/>
+ <xsd:enumeration value="MessageAndMenu"/>
+ <xsd:enumeration value="messageandmenu"/> <!-- deprecated -->
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TLocalOrUTC">
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="local"/>
+ <xsd:enumeration value="UTC"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TClipboardMode">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="Disabled"/>
+ <xsd:enumeration value="HostToGuest"/>
+ <xsd:enumeration value="GuestToHost"/>
+ <xsd:enumeration value="Bidirectional"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TDragAndDropMode">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="Disabled"/>
+ <xsd:enumeration value="HostToGuest"/>
+ <xsd:enumeration value="GuestToHost"/>
+ <xsd:enumeration value="Bidirectional"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TBandwidthGroupType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="Disk"/>
+ <xsd:enumeration value="Network"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TPortMode">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="Disconnected"/>
+ <xsd:enumeration value="RawFile"/>
+ <xsd:enumeration value="HostPipe"/>
+ <xsd:enumeration value="HostDevice"/>
+ <xsd:enumeration value="TCP"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TStorageControllerType">
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="AHCI"/>
+ <xsd:enumeration value="LsiLogic"/>
+ <xsd:enumeration value="BusLogic"/>
+ <xsd:enumeration value="PIIX3"/>
+ <xsd:enumeration value="PIIX4"/>
+ <xsd:enumeration value="ICH6"/>
+ <xsd:enumeration value="LsiLogicSas"/>
+ <xsd:enumeration value="I82078"/>
+ <xsd:enumeration value="USB"/>
+ <xsd:enumeration value="NVMe"/>
+ <xsd:enumeration value="VirtioSCSI"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="TDisplayControllerType">
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="VBoxVGA"/>
+ <xsd:enumeration value="VMSVGA"/>
+ <xsd:enumeration value="VBoxSVGA"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<!--
+// Complex types
+/////////////////////////////////////////////////////////////////////////
+-->
+<xsd:complexType name="TDHCPServer">
+ <xsd:sequence>
+ <xsd:element name="Options" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Option" type="TDHCPOption" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="networkName" type="xsd:string" use="required"/>
+ <xsd:attribute name="lowerIP" type="xsd:string" use="required"/>
+ <xsd:attribute name="upperIP" type="xsd:string" use="required"/>
+ <xsd:attribute name="IPAddress" type="xsd:string" use="required"/>
+ <xsd:attribute name="networkMask" type="xsd:string" use="required"/>
+ <xsd:attribute name="enabled" type="xsd:boolean" use="required"/>
+</xsd:complexType>
+
+<xsd:complexType name="TDHCPOption">
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="value" type="xsd:string" use="required"/>
+ <xsd:attribute name="encoding" type="xsd:integer" default="0"/>
+</xsd:complexType>
+
+<xsd:complexType name="TNATNetwork">
+ <xsd:sequence>
+ <xsd:element name="PortForwarding4" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Forwarding" type="TNATPortForwarding" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="PortForwarding6" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Forwarding" type="TNATPortForwarding" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="Mappings" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Loopback4" type="TNATLoopback4" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="networkName" type="xsd:string" use="required"/>
+ <xsd:attribute name="enabled" type="xsd:boolean" use="required"/>
+ <xsd:attribute name="network" type="xsd:string" use="required"/>
+ <xsd:attribute name="ipv6" type="xsd:boolean" use="required"/>
+ <xsd:attribute name="ipv6prefix" type="xsd:string" use="required"/>
+ <xsd:attribute name="advertiseDefaultIPv6Route" type="xsd:boolean" use="required"/>
+ <xsd:attribute name="needDhcp" type="xsd:boolean" use="required"/>
+ <xsd:attribute name="loopback6" type="xsd:integer" default="0"/>
+</xsd:complexType>
+
+<xsd:complexType name="TNATLoopback4">
+ <xsd:attribute name="address" type="xsd:string" use="required"/>
+ <xsd:attribute name="offset" type="xsd:integer" use="required"/>
+</xsd:complexType>
+
+<xsd:complexType name="TNATPortForwarding">
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="proto" type="xsd:integer" use="required"/>
+ <xsd:attribute name="hostip" type="xsd:string" default=""/>
+ <xsd:attribute name="hostport" type="xsd:integer" use="required"/>
+ <xsd:attribute name="guestip" type="xsd:string" use="required"/>
+ <xsd:attribute name="guestport" type="xsd:integer" use="required"/>
+</xsd:complexType>
+
+<xsd:complexType name="TProperty">
+ <xsd:attribute name="name" type="xsd:token" use="required"/>
+ <xsd:attribute name="value" type="xsd:string" use="required"/>
+</xsd:complexType>
+
+<xsd:complexType name="THardDiskBase">
+ <xsd:sequence>
+ <xsd:element name="Description" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="Property" type="TProperty" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="HardDisk" type="TDiffHardDisk" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="uuid" type="TNonNullUUID" use="required"/>
+ <xsd:attribute name="location" type="TLocalFile" use="required"/>
+ <xsd:attribute name="format" type="TNonEmptyString" use="required"/>
+</xsd:complexType>
+
+<xsd:complexType name="TDiffHardDisk">
+ <xsd:complexContent>
+ <xsd:extension base="THardDiskBase">
+ <xsd:attribute name="autoReset" type="xsd:boolean" default="false"/>
+ </xsd:extension>
+ </xsd:complexContent>
+</xsd:complexType>
+
+<xsd:complexType name="THardDisk">
+ <xsd:complexContent>
+ <xsd:extension base="THardDiskBase">
+ <xsd:attribute name="type" type="TMediumType" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+</xsd:complexType>
+
+<xsd:complexType name="TImage2">
+ <xsd:sequence>
+ <xsd:element name="Description" type="xsd:string" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="uuid" type="TNonNullUUID" use="required"/>
+ <xsd:attribute name="location" type="TLocalFile" use="required"/>
+ <xsd:attribute name="type" type="TMediumType"/>
+</xsd:complexType>
+
+<xsd:complexType name="TImageRef">
+ <xsd:attribute name="uuid" type="TNonNullUUID" use="required"/>
+</xsd:complexType>
+
+<xsd:complexType name="THostDrive">
+ <xsd:attribute name="src" type="TLocalFile" use="required"/>
+</xsd:complexType>
+
+<xsd:complexType name="TUSBDeviceFilter">
+ <xsd:attribute name="name" type="TNonEmptyString" use="required"/>
+ <xsd:attribute name="active" type="xsd:boolean" use="required"/>
+ <xsd:attribute name="vendorId" type="xsd:token"/>
+ <xsd:attribute name="productId" type="xsd:token"/>
+ <xsd:attribute name="revision" type="xsd:token"/>
+ <xsd:attribute name="manufacturer" type="xsd:token"/>
+ <xsd:attribute name="product" type="xsd:token"/>
+ <xsd:attribute name="serialNumber" type="xsd:token"/>
+ <xsd:attribute name="port" type="xsd:token"/>
+ <xsd:attribute name="remote" type="xsd:token"/>
+ <xsd:attribute name="maskedInterfaces" type="xsd:unsignedInt" default="0"/>
+</xsd:complexType>
+
+<xsd:complexType name="TUSBDeviceFilters">
+ <xsd:sequence>
+ <xsd:element name="DeviceFilter" type="TUSBDeviceFilter" minOccurs="0"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="THostUSBDeviceFilter">
+ <xsd:complexContent>
+ <xsd:extension base="TUSBDeviceFilter">
+ <xsd:attribute name="action" type="TUSBDeviceFilterAction" use="required"/>
+ </xsd:extension>
+ </xsd:complexContent>
+</xsd:complexType>
+
+<xsd:complexType name="TSystemProperties">
+ <xsd:attribute name="defaultMachineFolder" type="TLocalFile"/>
+ <xsd:attribute name="defaultHardDiskFolder" type="TLocalFile"/>
+ <xsd:attribute name="defaultHardDiskFormat" type="TNonEmptyString"/>
+ <xsd:attribute name="VRDEAuthLibrary" type="TLocalFile"/>
+ <xsd:attribute name="webServiceAuthLibrary" type="TLocalFile"/>
+ <xsd:attribute name="defaultVRDELibrary" type="TLocalFile"/>
+ <xsd:attribute name="HWVirtExEnabled" type="xsd:boolean"/>
+ <xsd:attribute name="LogHistoryCount" type="xsd:unsignedInt" default="3"/>
+ <xsd:attribute name="defaultVRDEExtPack" type="xsd:string"/>
+ <xsd:attribute name="exclusiveHwVirt" type="xsd:boolean"/>
+ <xsd:attribute name="proxyMode" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="TExtraData">
+ <xsd:sequence>
+ <xsd:element name="ExtraDataItem" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:attribute name="name" type="xsd:token" use="required"/>
+ <xsd:attribute name="value" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TMediaRegistry">
+ <xsd:all>
+ <xsd:element name="HardDisks" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="HardDisk" type="THardDisk" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="DVDImages" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Image" type="TImage2" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="FloppyImages" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Image" type="TImage2" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:all>
+</xsd:complexType>
+
+<xsd:complexType name="TGlobal">
+ <xsd:all>
+ <xsd:element name="MachineRegistry">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="MachineEntry" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:attribute name="src" type="TLocalFile" use="required"/>
+ <xsd:attribute name="uuid" type="TNonNullUUID" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="MediaRegistry" type="TMediaRegistry" minOccurs="0"/>
+ <xsd:element name="NetserviceRegistry" minOccurs="0">
+ <xsd:complexType>
+ <xsd:all>
+ <xsd:element name="DHCPServers" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="DHCPServer" type="TDHCPServer" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="NATNetworks" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="NATNetwork" type="TNATNetwork" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:all>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="USBDeviceFilters">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="DeviceFilter" type="THostUSBDeviceFilter"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="SystemProperties" type="TSystemProperties"/>
+ <xsd:element name="ExtraData" type="TExtraData" minOccurs="0"/>
+ </xsd:all>
+</xsd:complexType>
+
+<xsd:complexType name="THWVirtExType">
+ <xsd:attribute name="enabled" type="TTriStateBoolType"/>
+ <xsd:attribute name="exclusive" type="xsd:boolean"/>
+</xsd:complexType>
+
+<xsd:complexType name="THWVirtExNestedPagingType">
+ <xsd:attribute name="enabled" type="xsd:boolean"/>
+</xsd:complexType>
+
+<xsd:complexType name="THWVirtExVPIDType">
+ <xsd:attribute name="enabled" type="xsd:boolean"/>
+</xsd:complexType>
+
+<xsd:complexType name="THWVirtExUXType">
+ <xsd:attribute name="enabled" type="xsd:boolean"/>
+</xsd:complexType>
+
+<xsd:complexType name="TSyntheticCpuType">
+ <xsd:attribute name="enabled" type="xsd:boolean"/>
+</xsd:complexType>
+
+<xsd:complexType name="TPAEType">
+ <xsd:attribute name="enabled" type="xsd:boolean"/>
+</xsd:complexType>
+
+<xsd:complexType name="TLongModeType">
+ <xsd:attribute name="enabled" type="xsd:boolean"/>
+</xsd:complexType>
+
+<xsd:complexType name="THardwareVirtExLargePages">
+ <xsd:attribute name="enabled" type="xsd:boolean"/>
+</xsd:complexType>
+
+<xsd:complexType name="THardwareVirtForce">
+ <xsd:attribute name="enabled" type="xsd:boolean"/>
+</xsd:complexType>
+
+<xsd:simpleType name="TCPUCount">
+ <xsd:restriction base="xsd:unsignedInt">
+ <xsd:minInclusive value="1"/>
+ <xsd:maxInclusive value="64"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:complexType name="TCpuIdLeaf">
+ <xsd:attribute name="id" type="xsd:unsignedInt" use="required"/>
+ <xsd:attribute name="eax" type="xsd:unsignedInt" use="required"/>
+ <xsd:attribute name="ebx" type="xsd:unsignedInt" use="required"/>
+ <xsd:attribute name="ecx" type="xsd:unsignedInt" use="required"/>
+ <xsd:attribute name="edx" type="xsd:unsignedInt" use="required"/>
+</xsd:complexType>
+
+<xsd:complexType name="TCpuIdTree">
+ <xsd:sequence>
+ <xsd:element name="CpuIdLeaf" type="TCpuIdLeaf"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TCPU">
+ <xsd:sequence>
+ <xsd:element name="HardwareVirtEx" type="THWVirtExType" minOccurs="0"/>
+ <xsd:element name="HardwareVirtExNestedPaging" type="THWVirtExNestedPagingType" minOccurs="0"/>
+ <xsd:element name="HardwareVirtExVPID" type="THWVirtExVPIDType" minOccurs="0"/>
+ <xsd:element name="HardwareVirtExUX" type="THWVirtExUXType" minOccurs="0"/>
+ <xsd:element name="PAE" type="TPAEType" minOccurs="0"/>
+ <xsd:element name="LongMode" type="TLongModeType" minOccurs="0"/>
+ <xsd:element name="HardwareVirtExLargePages" type="THardwareVirtExLargePages" minOccurs="0"/>
+ <xsd:element name="HardwareVirtForce" type="THardwareVirtForce" minOccurs="0"/>
+ <xsd:element name="SyntheticCpu" type="TSyntheticCpuType" minOccurs="0"/>
+ <xsd:element name="CpuIdTree" type="TCpuIdTree" minOccurs="0">
+ <xsd:unique name="TCPU-CpuIdTree-CpuIdLeaf">
+ <xsd:selector xpath="vb:CpuIdLeaf"/>
+ <xsd:field xpath="@id"/>
+ </xsd:unique>
+ </xsd:element>
+ </xsd:sequence>
+ <xsd:attribute name="count" type="TCPUCount" default="1"/>
+ <xsd:attribute name="hotplug" type="xsd:boolean" default="false"/>
+</xsd:complexType>
+
+<xsd:complexType name="TBoot">
+ <xsd:sequence>
+ <xsd:element name="Order" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:attribute name="position" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:unsignedInt">
+ <xsd:minInclusive value="1"/>
+ <xsd:maxInclusive value="4"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="device" type="TDeviceType" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TDisplay">
+ <xsd:attribute name="VRAMSize" default="8">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:unsignedInt">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxInclusive value="256"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="monitorCount" type="TMonitorCount" default="1"/>
+ <xsd:attribute name="MonitorCount" type="TMonitorCount"/> <!-- deprecated -->
+ <xsd:attribute name="accelerate3D" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="accelerate2DVideo" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="controller" type="TDisplayControllerType" default="VBoxSVGA"/>
+</xsd:complexType>
+
+<xsd:complexType name="TVideoRecording">
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="file" type="xsd:string"/>
+ <xsd:attribute name="horzRes">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:unsignedInt">
+ <xsd:minInclusive value="4"/>
+ <xsd:maxInclusive value="2097152"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="vertRes" type="xsd:unsignedInt"/>
+</xsd:complexType>
+
+<xsd:complexType name="TVideoCapture">
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="file" type="xsd:string"/>
+ <xsd:attribute name="screens" type="xsd:unsignedLong"/> <!-- todo: fix writing of settings (writes -1) -->
+ <xsd:attribute name="horzRes">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:unsignedInt">
+ <xsd:minInclusive value="4"/>
+ <xsd:maxInclusive value="2097152"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="vertRes" type="xsd:unsignedInt"/>
+ <xsd:attribute name="rate" type="xsd:unsignedInt"/>
+ <xsd:attribute name="fps" type="xsd:unsignedInt"/>
+ <xsd:attribute name="maxTime" type="xsd:unsignedInt"/>
+ <xsd:attribute name="maxSize" type="xsd:unsignedInt"/>
+ <xsd:attribute name="options" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="TVRDEProperties">
+ <xsd:sequence>
+ <xsd:element name="Property" type="TProperty" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TVideoChannel">
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="quality" type="xsd:unsignedByte" default="75"/>
+</xsd:complexType>
+
+<xsd:complexType name="TRemoteDisplay">
+ <xsd:sequence>
+ <xsd:element name="VideoChannel" type="TVideoChannel" minOccurs="0"/> <!-- deprecated -->
+ <xsd:choice minOccurs="0">
+ <xsd:element name="VRDEProperties" type="TVRDEProperties"/>
+ </xsd:choice>
+ </xsd:sequence>
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="port" type="xsd:string" default="3389"/> <!-- deprecated -->
+ <xsd:attribute name="authType" type="TAuthType" default="Null"/>
+ <xsd:attribute name="authTimeout" type="xsd:unsignedInt" default="5000"/>
+ <xsd:attribute name="allowMultiConnection" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="reuseSingleConnection" type="xsd:boolean" default="false"/>
+</xsd:complexType>
+
+<xsd:complexType name="TBIOS">
+ <xsd:all>
+ <xsd:element name="ACPI" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="enabled" type="xsd:boolean" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="IOAPIC" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="Logo" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="fadeIn" type="xsd:boolean" default="true"/>
+ <xsd:attribute name="fadeOut" type="xsd:boolean" default="true"/>
+ <xsd:attribute name="displayTime" type="xsd:unsignedInt" default="0"/>
+ <xsd:attribute name="imagePath" type="TLocalFile"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="BootMenu" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="mode" type="TBIOSBootMenuModeType" default="MessageAndMenu"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="TimeOffset" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="value" type="xsd:integer" default="0"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="PXEDebug" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="NVRAM" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="path" type="xsd:string"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="SmbiosUuidLittleEndian" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="IDEController" minOccurs="0"> <!-- deprecated -->
+ <xsd:complexType>
+ <xsd:attribute name="type">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="PIIX3"/>
+ <xsd:enumeration value="PIIX4"/>
+ <xsd:enumeration value="ICH6"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:all>
+</xsd:complexType>
+
+<xsd:complexType name="TStorageControllerDevice">
+ <xsd:choice minOccurs="0">
+ <xsd:element name="Image" type="TImageRef"/>
+ <xsd:element name="HostDrive" type="THostDrive"/>
+ </xsd:choice>
+ <xsd:attribute name="type" type="TMediumDeviceType"/>
+ <xsd:attribute name="port" type="xsd:unsignedInt" default="0"/>
+ <xsd:attribute name="device" type="xsd:unsignedInt" default="0"/>
+ <xsd:attribute name="passthrough" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="tempeject" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="nonrotational" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="discard" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="hotpluggable" type="xsd:boolean" default="false"/>
+</xsd:complexType>
+
+<xsd:complexType name="TStorageController">
+ <xsd:sequence>
+ <xsd:element name="AttachedDevice" type="TStorageControllerDevice"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="type" type="TStorageControllerType" use="required"/>
+ <xsd:attribute name="PortCount" type="xsd:unsignedInt" use="required"/>
+ <xsd:attribute name="useHostIOCache" type="xsd:boolean" use="optional" default="true"/>
+ <xsd:attribute name="Bootable" type="xsd:boolean" use="optional"/>
+ <xsd:attribute name="PCIBus" type="xsd:unsignedInt" use="optional"/>
+ <xsd:attribute name="PCIDevice" type="xsd:unsignedInt" use="optional"/>
+ <xsd:attribute name="PCIFunction" type="xsd:unsignedInt" use="optional"/>
+ <xsd:attribute name="IDE0MasterEmulationPort" type="xsd:unsignedInt" use="optional"/>
+ <xsd:attribute name="IDE0SlaveEmulationPort" type="xsd:unsignedInt" use="optional"/>
+ <xsd:attribute name="IDE1MasterEmulationPort" type="xsd:unsignedInt" use="optional"/>
+ <xsd:attribute name="IDE1SlaveEmulationPort" type="xsd:unsignedInt" use="optional"/>
+</xsd:complexType>
+
+<xsd:complexType name="TSATAController"> <!-- deprecated -->
+ <xsd:sequence>
+ <xsd:element name="AttachedDevice" type="TStorageControllerDevice"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="PortCount" type="xsd:unsignedInt" use="required"/>
+ <xsd:attribute name="IDE0MasterEmulationPort" type="xsd:unsignedInt" use="optional"/>
+ <xsd:attribute name="IDE0SlaveEmulationPort" type="xsd:unsignedInt" use="optional"/>
+ <xsd:attribute name="IDE1MasterEmulationPort" type="xsd:unsignedInt" use="optional"/>
+ <xsd:attribute name="IDE1SlaveEmulationPort" type="xsd:unsignedInt" use="optional"/>
+</xsd:complexType>
+
+<xsd:complexType name="TStorageControllers">
+ <xsd:sequence>
+ <xsd:element name="StorageController" type="TStorageController"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="THardDiskAttachment">
+ <xsd:attribute name="hardDisk" type="TNonNullUUID"/>
+ <xsd:attribute name="bus" default="IDE">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="IDE"/>
+ <xsd:enumeration value="SATA"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="channel" type="xsd:unsignedInt" default="0"/>
+ <xsd:attribute name="device" type="xsd:unsignedInt" default="0"/>
+</xsd:complexType>
+
+<xsd:complexType name="THardDiskAttachments"> <!-- deprecated -->
+ <xsd:sequence>
+ <xsd:element name="HardDiskAttachment" type="THardDiskAttachment"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TDVDDrive">
+ <xsd:choice minOccurs="0">
+ <xsd:element name="Image" type="TImageRef"/>
+ <xsd:element name="HostDrive" type="THostDrive"/>
+ </xsd:choice>
+ <xsd:attribute name="passthrough" type="xsd:boolean" default="false"/>
+</xsd:complexType>
+
+<xsd:complexType name="TFloppyDrive">
+ <xsd:choice minOccurs="0">
+ <xsd:element name="Image" type="TImageRef"/>
+ <xsd:element name="HostDrive" type="THostDrive"/>
+ </xsd:choice>
+ <xsd:attribute name="enabled" type="xsd:boolean" default="true"/>
+</xsd:complexType>
+
+<xsd:complexType name="TUSBController">
+ <xsd:sequence>
+ <xsd:element name="DeviceFilter" type="TUSBDeviceFilter"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="enabled" type="xsd:boolean" use="required"/>
+ <xsd:attribute name="enabledEhci" type="xsd:boolean" default="false"/>
+</xsd:complexType>
+
+<xsd:complexType name="TUSBController2">
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="type" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="OHCI"/>
+ <xsd:enumeration value="EHCI"/>
+ <xsd:enumeration value="XHCI"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+</xsd:complexType>
+
+<xsd:complexType name="TUSBControllers">
+ <xsd:sequence>
+ <xsd:element name="Controller" type="TUSBController2"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TUSB">
+ <xsd:sequence>
+ <xsd:element name="Controllers" type="TUSBControllers" minOccurs="0"/>
+ <xsd:element name="DeviceFilters" type="TUSBDeviceFilters" minOccurs="0"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+
+<xsd:complexType name="TAudioAdapter">
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="enabledIn" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="enabledOut" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="controller" default="AC97">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="AC97"/>
+ <xsd:enumeration value="SB16"/>
+ <xsd:enumeration value="HDA"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="driver" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="null"/> <!-- deprecated -->
+ <xsd:enumeration value="Null"/> <!-- all platforms -->
+ <xsd:enumeration value="OSS"/> <!-- Linux, Solaris, FreeBSD -->
+ <xsd:enumeration value="ALSA"/> <!-- Linux, FreeBSD -->
+ <xsd:enumeration value="Pulse"/> <!-- Linux -->
+ <xsd:enumeration value="CoreAudio"/> <!-- Mac OS X -->
+ <xsd:enumeration value="MMPM"/> <!-- OS/2 -->
+ <xsd:enumeration value="SolAudio"/> <!-- Solaris -->
+ <xsd:enumeration value="WinMM"/> <!-- Windows -->
+ <xsd:enumeration value="DirectSound"/> <!-- Windows -->
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+</xsd:complexType>
+
+<xsd:complexType name="TNetNAT">
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="DNS" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="pass-domain" type="xsd:boolean" default="true"/>
+ <xsd:attribute name="use-proxy" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="use-host-resolver" type="xsd:boolean" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="Alias" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="logging" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="proxy-only" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="use-same-ports" type="xsd:boolean" default="false"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="TFTP" minOccurs="0">
+ <xsd:complexType>
+ <xsd:attribute name="prefix" type="xsd:string"/>
+ <xsd:attribute name="boot-file" type="xsd:string"/>
+ <xsd:attribute name="next-server" type="xsd:string"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="Forwarding" minOccurs="0" maxOccurs="unbounded">
+ <xsd:complexType>
+ <xsd:attribute name="name" type="xsd:string"/>
+ <xsd:attribute name="proto" type="xsd:unsignedInt"/>
+ <xsd:attribute name="hostip" type="xsd:string"/>
+ <xsd:attribute name="hostport" type="xsd:unsignedInt"/>
+ <xsd:attribute name="guestip" type="xsd:string"/>
+ <xsd:attribute name="guestport" type="xsd:unsignedInt"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:attribute name="network" type="xsd:string"/>
+ <xsd:attribute name="hostip" type="xsd:string"/>
+ <xsd:attribute name="mtu" type="xsd:unsignedInt"/>
+ <xsd:attribute name="sockrcv" type="xsd:unsignedInt"/>
+ <xsd:attribute name="socksnd" type="xsd:unsignedInt"/>
+ <xsd:attribute name="tcprcv" type="xsd:unsignedInt"/>
+ <xsd:attribute name="tcpsnd" type="xsd:unsignedInt"/>
+</xsd:complexType>
+
+<xsd:complexType name="TNetNATNetwork">
+ <xsd:attribute name="name" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="TNetBridged">
+ <xsd:attribute name="name" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="TNetInternal">
+ <xsd:attribute name="name" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="TNetHostOnly">
+ <xsd:attribute name="name" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="TNetGeneric">
+ <xsd:sequence>
+ <xsd:element name="Property" type="TProperty" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="driver" type="xsd:string" use="required"/>
+</xsd:complexType>
+
+<xsd:complexType name="TNetworkConfig">
+ <xsd:choice maxOccurs="2">
+ <xsd:choice minOccurs="0">
+ <xsd:element name="NAT" type="TNetNAT"/>
+ <xsd:element name="NATNetwork" type="TNetNATNetwork"/>
+ <xsd:element name="HostInterface" type="TNetBridged"/>
+ <xsd:element name="BridgedInterface" type="TNetBridged"/>
+ <xsd:element name="InternalNetwork" type="TNetInternal"/>
+ <xsd:element name="HostOnlyInterface" type="TNetHostOnly"/>
+ <xsd:element name="GenericInterface" type="TNetGeneric"/>
+ </xsd:choice>
+ <xsd:element name="DisabledModes">
+ <xsd:complexType>
+ <xsd:all>
+ <xsd:element name="NAT" type="TNetNAT" minOccurs="0"/>
+ <xsd:element name="NATNetwork" type="TNetNATNetwork" minOccurs="0"/>
+ <xsd:element name="HostInterface" type="TNetBridged" minOccurs="0"/>
+ <xsd:element name="BridgedInterface" type="TNetBridged" minOccurs="0"/>
+ <xsd:element name="InternalNetwork" type="TNetInternal" minOccurs="0"/>
+ <xsd:element name="HostOnlyInterface" type="TNetHostOnly" minOccurs="0"/>
+ <xsd:element name="GenericInterface" type="TNetGeneric" minOccurs="0"/>
+ </xsd:all>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+</xsd:complexType>
+
+<xsd:complexType name="TNetworkAdapter">
+ <xsd:complexContent>
+ <xsd:extension base="TNetworkConfig">
+ <xsd:attribute name="type" type="TNetworkAdapterType" default="Am79C973"/>
+ <xsd:attribute name="slot" type="xsd:unsignedInt" use="required"/>
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="MACAddress">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:hexBinary">
+ <xsd:pattern value="[0-9A-Fa-f][02468ACEace][0-9A-Fa-f]{10}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="cable" type="xsd:boolean" default="true"/>
+ <xsd:attribute name="speed" type="xsd:unsignedInt" default="1000000"/>
+ <xsd:attribute name="bootPriority" type="xsd:unsignedInt"/>
+ <xsd:attribute name="trace" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="tracefile" type="xsd:string"/>
+ <xsd:attribute name="bandwidthGroup" type="xsd:string"/>
+ <xsd:attribute name="promiscuousModePolicy" default="Deny">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="Deny"/>
+ <xsd:enumeration value="AllowNetwork"/>
+ <xsd:enumeration value="AllowAll"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+</xsd:complexType>
+
+<xsd:complexType name="TNetwork">
+ <xsd:sequence>
+ <xsd:element name="Adapter" type="TNetworkAdapter"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TUARTPort">
+ <xsd:attribute name="slot" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:unsignedInt">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxExclusive value="4"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="enabled" type="xsd:boolean" use="required"/>
+ <xsd:attribute name="IRQ" type="TUInt8" use="required"/>
+ <xsd:attribute name="IOBase" type="TUInt16" use="required"/>
+ <xsd:attribute name="hostMode" type="TPortMode" use="required"/>
+ <xsd:attribute name="path" type="TLocalFile"/>
+ <xsd:attribute name="server" type="xsd:boolean" default="false"/>
+</xsd:complexType>
+
+<xsd:complexType name="TUART">
+ <xsd:sequence>
+ <xsd:element name="Port" type="TUARTPort"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TUartPort"> <!-- deprecated -->
+ <xsd:attribute name="slot" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:unsignedInt">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxExclusive value="4"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="enabled" type="xsd:boolean" use="required"/>
+ <xsd:attribute name="IRQ" type="TUInt8" use="required"/>
+ <xsd:attribute name="IOBase" type="TUInt16HexNoBase" use="required"/>
+ <xsd:attribute name="hostMode" type="TPortMode" use="required"/>
+ <xsd:attribute name="path" type="TLocalFile"/>
+ <xsd:attribute name="server" type="xsd:boolean" default="false"/>
+</xsd:complexType>
+
+<xsd:complexType name="TUart"> <!-- deprecated -->
+ <xsd:sequence>
+ <xsd:element name="Port" type="TUartPort"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TLPTPort">
+ <xsd:attribute name="slot" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:unsignedInt">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxExclusive value="2"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="enabled" type="xsd:boolean" use="required"/>
+ <xsd:attribute name="IRQ" type="TUInt8" use="required"/>
+ <xsd:attribute name="IOBase" type="TUInt16" use="required"/>
+ <xsd:attribute name="path" type="TLocalFile"/>
+</xsd:complexType>
+
+<xsd:complexType name="TLPT">
+ <xsd:sequence>
+ <xsd:element name="Port" type="TLPTPort"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TLptPort"> <!-- deprecated -->
+ <xsd:attribute name="slot" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:unsignedInt">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxExclusive value="2"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="enabled" type="xsd:boolean" use="required"/>
+ <xsd:attribute name="IRQ" type="TUInt8" use="required"/>
+ <xsd:attribute name="IOBase" type="TUInt16HexNoBase" use="required"/>
+ <xsd:attribute name="path" type="TLocalFile"/>
+</xsd:complexType>
+
+<xsd:complexType name="TLpt">
+ <xsd:sequence>
+ <xsd:element name="Port" type="TLptPort"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TRTC">
+ <xsd:attribute name="localOrUTC" type="TLocalOrUTC" use="required"/>
+</xsd:complexType>
+
+<xsd:complexType name="TSharedFolder">
+ <xsd:attribute name="name" type="TNonEmptyString" use="required"/>
+ <xsd:attribute name="hostPath" type="TLocalFile" use="required"/>
+ <xsd:attribute name="writable" type="xsd:boolean" default="true"/>
+ <xsd:attribute name="autoMount" type="xsd:boolean" default="false"/>
+</xsd:complexType>
+
+<xsd:complexType name="TSharedFolders">
+ <xsd:sequence>
+ <xsd:element name="SharedFolder" type="TSharedFolder" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TClipboard">
+ <xsd:attribute name="mode" type="TClipboardMode" default="Disabled"/>
+</xsd:complexType>
+
+<xsd:complexType name="TDragAndDrop">
+ <xsd:attribute name="mode" type="TDragAndDropMode" use="required"/>
+</xsd:complexType>
+
+<xsd:complexType name="TIoCache">
+ <xsd:attribute name="enabled" type="xsd:boolean" default="true"/>
+ <xsd:attribute name="size" type="xsd:unsignedLong"/>
+</xsd:complexType>
+
+<xsd:complexType name="TBandwidthGroup">
+ <xsd:attribute name="name" type="xsd:token" use="required"/>
+ <xsd:attribute name="type" type="TBandwidthGroupType" use="required"/>
+ <xsd:attribute name="maxBytesPerSec" type="xsd:unsignedLong"/>
+ <xsd:attribute name="maxMbPerSec" type="xsd:unsignedLong"/>
+</xsd:complexType>
+
+<xsd:complexType name="TBandwidthGroups">
+ <xsd:sequence>
+ <xsd:element name="BandwidthGroup" type="TBandwidthGroup" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TIO">
+ <xsd:sequence>
+ <xsd:element name="IoCache" type="TIoCache" minOccurs="0"/>
+ <xsd:element name="BandwidthGroups" type="TBandwidthGroups" minOccurs="0"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="THostPciDevice">
+ <xsd:attribute name="host" type="xsd:unsignedInt" use="required"/>
+ <xsd:attribute name="guest" type="xsd:unsignedInt" use="required"/>
+ <xsd:attribute name="name" type="xsd:token"/>
+</xsd:complexType>
+
+<xsd:complexType name="THostPciDevices">
+ <xsd:sequence>
+ <xsd:element name="Device" type="THostPciDevice" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="THostPci">
+ <xsd:sequence>
+ <xsd:element name="Devices" type="THostPciDevices" minOccurs="0"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TCardReader">
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+</xsd:complexType>
+
+<xsd:complexType name="TWebcam">
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+</xsd:complexType>
+
+<xsd:complexType name="TEmulatedUSB">
+ <xsd:sequence>
+ <xsd:element name="CardReader" type="TCardReader" minOccurs="0"/>
+ <xsd:element name="Webcam" type="TWebcam" minOccurs="0"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TGuest">
+ <xsd:attribute name="memoryBalloonSize" type="xsd:unsignedInt" default="0"/>
+ <xsd:attribute name="MemoryBalloonSize" type="xsd:unsignedInt" default="0"/> <!-- deprecated -->
+ <xsd:attribute name="statisticsUpdateInterval" type="xsd:unsignedInt" default="0"/>
+ <xsd:attribute name="StatisticsUpdateInterval" type="xsd:unsignedInt" default="0"/> <!-- deprecated -->
+</xsd:complexType>
+
+<xsd:complexType name="TGuestProperty">
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ <xsd:attribute name="value" type="xsd:string" use="required"/>
+ <xsd:attribute name="timestamp" type="xsd:unsignedLong" default="0"/>
+ <xsd:attribute name="flags" type="xsd:string" default=""/>
+</xsd:complexType>
+
+<xsd:complexType name="TGuestProperties">
+ <xsd:sequence>
+ <xsd:element name="GuestProperty" type="TGuestProperty" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="notificationPatterns" type="xsd:string" default=""/>
+</xsd:complexType>
+
+<xsd:complexType name="TMemory">
+ <xsd:attribute name="RAMSize" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:unsignedInt">
+ <xsd:minInclusive value="4"/>
+ <xsd:maxInclusive value="2097152"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="PageFusion" type="xsd:boolean" default="false"/>
+</xsd:complexType>
+
+<xsd:complexType name="TFirmware">
+ <xsd:attribute name="type" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="BIOS"/>
+ <xsd:enumeration value="EFI"/>
+ <xsd:enumeration value="EFI32"/>
+ <xsd:enumeration value="EFI64"/>
+ <xsd:enumeration value="EFIDUAL"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+</xsd:complexType>
+
+<xsd:complexType name="THID">
+ <xsd:attribute name="Pointing" default="PS2Mouse">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="USBMouse"/>
+ <xsd:enumeration value="USBTablet"/>
+ <xsd:enumeration value="PS2Mouse"/>
+ <xsd:enumeration value="ComboMouse"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="Keyboard" default="PS2Keyboard">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="USBKeyboard"/>
+ <xsd:enumeration value="PS2Keyboard"/>
+ <xsd:enumeration value="ComboKeyboard"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+</xsd:complexType>
+
+<xsd:complexType name="THPET">
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+</xsd:complexType>
+
+<xsd:complexType name="TChipset">
+ <xsd:attribute name="type" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="PIIX3"/>
+ <xsd:enumeration value="ICH9"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+</xsd:complexType>
+
+<xsd:complexType name="TParavirt">
+ <xsd:attribute name="provider" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="None"/>
+ <xsd:enumeration value="Default"/>
+ <xsd:enumeration value="Legacy"/>
+ <xsd:enumeration value="Minimal"/>
+ <xsd:enumeration value="HyperV"/>
+ <xsd:enumeration value="KVM"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="debug" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="TTeleporter">
+ <xsd:attribute name="enabled" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="port" type="xsd:unsignedShort"/>
+ <xsd:attribute name="address" type="xsd:string"/>
+ <xsd:attribute name="password" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="THardware">
+ <xsd:all>
+ <xsd:element name="CPU" type="TCPU" minOccurs="0"/>
+ <xsd:element name="Memory" type="TMemory"/>
+ <xsd:element name="Firmware" type="TFirmware" minOccurs="0"/>
+ <xsd:element name="HID" type="THID" minOccurs="0"/>
+ <xsd:element name="HPET" type="THPET" minOccurs="0"/>
+ <xsd:element name="Chipset" type="TChipset" minOccurs="0"/>
+ <xsd:element name="Paravirt" type="TParavirt" minOccurs="0"/>
+ <xsd:element name="Boot" type="TBoot" minOccurs="0">
+ <xsd:unique name="THardware-Boot-Order">
+ <xsd:selector xpath="vb:Order"/>
+ <xsd:field xpath="@position"/>
+ </xsd:unique>
+ </xsd:element>
+ <xsd:element name="Display" type="TDisplay" minOccurs="0"/>
+ <xsd:element name="VideoRecording" type="TVideoRecording" minOccurs="0"/>
+ <xsd:element name="VideoCapture" type="TVideoCapture" minOccurs="0"/>
+ <xsd:element name="RemoteDisplay" type="TRemoteDisplay" minOccurs="0"/>
+ <xsd:element name="BIOS" type="TBIOS" minOccurs="0"/>
+ <xsd:element name="DVDDrive" type="TDVDDrive" minOccurs="0"/>
+ <xsd:element name="FloppyDrive" type="TFloppyDrive" minOccurs="0"/>
+ <xsd:element name="USBController" type="TUSBController" minOccurs="0"/>
+ <xsd:element name="USB" type="TUSB" minOccurs="0"/>
+ <xsd:element name="SATAController" type="TSATAController" minOccurs="0"/> <!-- deprecated -->
+ <xsd:element name="Network" type="TNetwork" minOccurs="0">
+ <xsd:unique name="THardware-Network-Adapter">
+ <xsd:selector xpath="vb:Adapter"/>
+ <xsd:field xpath="@slot"/>
+ </xsd:unique>
+ </xsd:element>
+ <xsd:element name="UART" type="TUART" minOccurs="0">
+ <xsd:unique name="THardware-UART-Port">
+ <xsd:selector xpath="vb:Port"/>
+ <xsd:field xpath="@slot"/>
+ </xsd:unique>
+ </xsd:element>
+ <xsd:element name="Uart" type="TUart" minOccurs="0"> <!-- deprecated -->
+ <xsd:unique name="THardware-Uart-Port">
+ <xsd:selector xpath="vb:Port"/>
+ <xsd:field xpath="@slot"/>
+ </xsd:unique>
+ </xsd:element>
+ <xsd:element name="LPT" type="TLPT" minOccurs="0">
+ <xsd:unique name="THardware-LPT-Port">
+ <xsd:selector xpath="vb:Port"/>
+ <xsd:field xpath="@slot"/>
+ </xsd:unique>
+ </xsd:element>
+ <xsd:element name="Lpt" type="TLpt" minOccurs="0"> <!-- deprecated -->
+ <xsd:unique name="THardware-Lpt-Port">
+ <xsd:selector xpath="vb:Port"/>
+ <xsd:field xpath="@slot"/>
+ </xsd:unique>
+ </xsd:element>
+ <xsd:element name="AudioAdapter" type="TAudioAdapter" minOccurs="0"/>
+ <xsd:element name="RTC" type="TRTC" minOccurs="0"/>
+ <xsd:element name="SharedFolders" type="TSharedFolders" minOccurs="0">
+ <xsd:unique name="THardware-SharedFolders-SharedFolder">
+ <xsd:selector xpath="vb:SharedFolder"/>
+ <xsd:field xpath="@name"/>
+ </xsd:unique>
+ </xsd:element>
+ <xsd:element name="Clipboard" type="TClipboard" minOccurs="0"/>
+ <xsd:element name="DragAndDrop" type="TDragAndDrop" minOccurs="0"/>
+ <xsd:element name="IO" type="TIO" minOccurs="0"/>
+ <xsd:element name="HostPci" type="THostPci" minOccurs="0"/>
+ <xsd:element name="EmulatedUSB" type="TEmulatedUSB" minOccurs="0"/>
+ <xsd:element name="Guest" type="TGuest" minOccurs="0"/>
+ <xsd:element name="GuestProperties" type="TGuestProperties" minOccurs="0">
+ <xsd:unique name="THardware-GuestProperties-GuestProperty">
+ <xsd:selector xpath="vb:GuestProperty"/>
+ <xsd:field xpath="@name"/>
+ </xsd:unique>
+ </xsd:element>
+ <xsd:element name="StorageControllers" type="TStorageControllers" minOccurs="0"/>
+ </xsd:all>
+ <xsd:attribute name="version" type="xsd:string" default="2"/>
+</xsd:complexType>
+
+<xsd:complexType name="TGroup">
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+</xsd:complexType>
+
+<xsd:complexType name="TGroups">
+ <xsd:sequence>
+ <xsd:element name="Group" type="TGroup" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="TMachine">
+ <xsd:all>
+ <xsd:element name="MediaRegistry" type="TMediaRegistry" minOccurs="0"/>
+ <xsd:element name="Description" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="Teleporter" type="TTeleporter" minOccurs="0"/>
+ <xsd:element name="Hardware" type="THardware"/>
+ <xsd:element name="StorageControllers" type="TStorageControllers" minOccurs="0"/>
+ <xsd:element name="HardDiskAttachments" type="THardDiskAttachments" minOccurs="0"/> <!-- deprecated -->
+ <xsd:element name="Groups" type="TGroups" minOccurs="0"/>
+ <xsd:element name="ExtraData" type="TExtraData" minOccurs="0"/>
+ <xsd:element name="Snapshot" type="TSnapshot" minOccurs="0"/>
+ </xsd:all>
+ <xsd:attribute name="name" type="TNonEmptyString" use="required"/>
+ <xsd:attribute name="nameSync" type="xsd:boolean" default="true"/>
+ <xsd:attribute name="directoryIncludesUUID" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="OSType" type="TNonEmptyString" use="required"/>
+ <xsd:attribute name="uuid" type="TNonNullUUID" use="required"/>
+ <xsd:attribute name="stateFile" type="TLocalFile"/>
+ <xsd:attribute name="currentSnapshot" type="TNonNullUUID"/>
+ <xsd:attribute name="snapshotFolder" type="TLocalFile"/>
+ <xsd:attribute name="lastStateChange" type="TPresentDateTimeUTC"/>
+ <xsd:attribute name="aborted" type="xsd:boolean" default="false"/>
+ <xsd:attribute name="currentStateModified" type="xsd:boolean" default="true"/>
+ <xsd:attribute name="version" type="xsd:string" default="1.15"/> <!-- Used for OVF files only, must not be present in normal settings files. The default corresponds to settings created by 5.0, which covers many older versions but not newer ones. -->
+ <xsd:attribute name="VMProcessPriority" type="TVMProcPriority"/>
+</xsd:complexType>
+
+<xsd:complexType name="TSnapshot">
+ <xsd:all>
+ <xsd:element name="Description" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="Hardware" type="THardware"/>
+ <xsd:element name="StorageControllers" type="TStorageControllers" minOccurs="0"/>
+ <xsd:element name="Snapshots" minOccurs="0">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="Snapshot" type="TSnapshot" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:all>
+ <xsd:attribute name="name" type="xsd:token" use="required"/>
+ <xsd:attribute name="uuid" type="TNonNullUUID" use="required"/>
+ <xsd:attribute name="timeStamp" type="TPresentDateTimeUTC" use="required"/>
+ <xsd:attribute name="stateFile" type="TLocalFile"/>
+</xsd:complexType>
+
+<xsd:complexType name="TVirtualBox">
+ <xsd:choice>
+ <xsd:element name="Global" type="TGlobal"/>
+ <xsd:element name="Machine" type="TMachine">
+ <!-- @currentSnapshot must refer to an existing Snapshot/@uuid -->
+ <xsd:key name="snapshot">
+ <xsd:selector xpath=".//vb:Snapshot"/>
+ <xsd:field xpath="@uuid"/>
+ </xsd:key>
+ <xsd:keyref name="currentSnapshot" refer="vb:snapshot">
+ <xsd:selector xpath="."/>
+ <xsd:field xpath="@currentSnapshot"/>
+ </xsd:keyref>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:attribute name="version" type="xsd:string" use="required"/>
+</xsd:complexType>
+
+<!-- Root element for all VirtualBox config files -->
+<xsd:element name="VirtualBox" type="TVirtualBox"/>
+
+</xsd:schema>
diff --git a/src/VBox/Main/xml/ovfreader.cpp b/src/VBox/Main/xml/ovfreader.cpp
new file mode 100644
index 00000000..7d13d7be
--- /dev/null
+++ b/src/VBox/Main/xml/ovfreader.cpp
@@ -0,0 +1,1078 @@
+/* $Id: ovfreader.cpp $ */
+/** @file
+ * OVF reader declarations.
+ *
+ * Depends only on IPRT, including the RTCString and IPRT XML classes.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_APPLIANCE
+#include "ovfreader.h"
+#include <VBox/log.h>
+#include <vector>
+
+using namespace std;
+using namespace ovf;
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// OVF reader implementation
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Default Constructor.
+ * Should be used if you don't have an OVF file, but want to fill the data
+ * m_mapDisks, m_llVirtualSystems manually
+ */
+OVFReader::OVFReader()
+{
+}
+
+/**
+ * Constructor. This parses the given XML file out of the memory. Throws lots of exceptions
+ * on XML or OVF invalidity.
+ * @param pvBuf the memory buffer to parse
+ * @param cbSize the size of the memory buffer
+ * @param path path to a filename for error messages.
+ */
+OVFReader::OVFReader(const void *pvBuf, size_t cbSize, const RTCString &path)
+ : m_strPath(path)
+{
+ xml::XmlMemParser parser;
+ parser.read(pvBuf, cbSize,
+ m_strPath,
+ m_doc);
+ /* Start the parsing */
+ parse();
+}
+
+/**
+ * Constructor. This opens the given XML file and parses it. Throws lots of exceptions
+ * on XML or OVF invalidity.
+ * @param path
+ */
+OVFReader::OVFReader(const RTCString &path)
+ : m_strPath(path)
+{
+ xml::XmlFileParser parser;
+ parser.read(m_strPath,
+ m_doc);
+ /* Start the parsing */
+ parse();
+}
+
+void OVFReader::parse()
+{
+ const xml::ElementNode *pRootElem = m_doc.getRootElement();
+ const xml::AttributeNode *pTypeAttr;
+ const char *pcszTypeAttr = "";
+ RTCString pcszNamespaceURI;
+
+ if (!pRootElem || strcmp(pRootElem->getName(), "Envelope"))
+ throw OVFLogicError(N_("Root element in OVF file must be 'Envelope'."));
+
+ pcszNamespaceURI = pRootElem->getNamespaceURI();
+ if (pcszNamespaceURI.isEmpty())
+ {
+ throw OVFLogicError(N_("Error reading namespace URI in 'Envelope' element, line %d"), pRootElem->getLineNumber());
+ }
+
+ if (strncmp(ovf::OVF20_URI_string, pcszNamespaceURI.c_str(), pcszNamespaceURI.length()) == 0)
+ {
+ m_envelopeData.setOVFVersion(ovf::OVFVersion_2_0);
+ }
+ else if (strncmp(OVF10_URI_string, pcszNamespaceURI.c_str(), pcszNamespaceURI.length()) == 0)
+ {
+ m_envelopeData.setOVFVersion(ovf::OVFVersion_1_0);
+ }
+ else
+ {
+ m_envelopeData.setOVFVersion(ovf::OVFVersion_0_9);
+ }
+
+ if ((pTypeAttr = pRootElem->findAttribute("lang", "xml")))
+ {
+ pcszTypeAttr = pTypeAttr->getValueN(RT_XML_ATTR_TINY);
+ m_envelopeData.lang = pcszTypeAttr;
+ }
+
+ // OVF has the following rough layout:
+ /*
+ -- <References> .... files referenced from other parts of the file, such as VMDK images
+ -- Metadata, comprised of several section commands
+ -- virtual machines, either a single <VirtualSystem>, or a <VirtualSystemCollection>
+ -- optionally <Strings> for localization
+ */
+
+ // get all "File" child elements of "References" section so we can look up files easily;
+ // first find the "References" sections so we can look up files
+ xml::ElementNodesList listFileElements; // receives all /Envelope/References/File nodes
+ const xml::ElementNode *pReferencesElem;
+ if ((pReferencesElem = pRootElem->findChildElement("References")))
+ pReferencesElem->getChildElements(listFileElements, "File");
+
+ // now go though the sections
+ LoopThruSections(pReferencesElem, pRootElem);
+}
+
+/**
+ * Private helper method that goes thru the elements of the given "current" element in the OVF XML
+ * and handles the contained child elements (which can be "Section" or "Content" elements).
+ *
+ * @param pReferencesElem "References" element from OVF, for looking up file specifications;
+ * can be NULL if no such element is present.
+ * @param pCurElem Element whose children are to be analyzed here.
+ * @return
+ */
+void OVFReader::LoopThruSections(const xml::ElementNode *pReferencesElem,
+ const xml::ElementNode *pCurElem)
+{
+ xml::NodesLoop loopChildren(*pCurElem);
+ const xml::ElementNode *pElem;
+ while ((pElem = loopChildren.forAllNodes()))
+ {
+ const char *pcszElemName = pElem->getName();
+ const xml::AttributeNode *pTypeAttr = pElem->findAttribute("type");
+ const char *pcszTypeAttr = pTypeAttr ? pTypeAttr->getValueN(RT_XML_ATTR_TINY) : "";
+
+ if ( !strcmp(pcszElemName, "DiskSection")
+ || ( !strcmp(pcszElemName, "Section")
+ && !strcmp(pcszTypeAttr, "ovf:DiskSection_Type")
+ )
+ )
+ {
+ HandleDiskSection(pReferencesElem, pElem);
+ }
+ else if ( !strcmp(pcszElemName, "NetworkSection")
+ || ( !strcmp(pcszElemName, "Section")
+ && !strcmp(pcszTypeAttr, "ovf:NetworkSection_Type")
+ )
+ )
+ {
+ HandleNetworkSection(pElem);
+ }
+ else if ( !strcmp(pcszElemName, "DeploymentOptionSection"))
+ {
+ /// @todo
+ }
+ else if ( !strcmp(pcszElemName, "Info"))
+ {
+ // child of VirtualSystemCollection -- TODO
+ }
+ else if ( !strcmp(pcszElemName, "ResourceAllocationSection"))
+ {
+ // child of VirtualSystemCollection -- TODO
+ }
+ else if ( !strcmp(pcszElemName, "StartupSection"))
+ {
+ // child of VirtualSystemCollection -- TODO
+ }
+ else if ( !strcmp(pcszElemName, "VirtualSystem")
+ || ( !strcmp(pcszElemName, "Content")
+ && !strcmp(pcszTypeAttr, "ovf:VirtualSystem_Type")
+ )
+ )
+ {
+ HandleVirtualSystemContent(pElem);
+ }
+ else if ( !strcmp(pcszElemName, "VirtualSystemCollection")
+ || ( !strcmp(pcszElemName, "Content")
+ && !strcmp(pcszTypeAttr, "ovf:VirtualSystemCollection_Type")
+ )
+ )
+ {
+ /// @todo ResourceAllocationSection
+
+ // recurse for this, since it has VirtualSystem elements as children
+ LoopThruSections(pReferencesElem, pElem);
+ }
+ }
+}
+
+/**
+ * Private helper method that handles disk sections in the OVF XML.
+ *
+ * Gets called indirectly from IAppliance::read().
+ *
+ * @param pReferencesElem "References" element from OVF, for looking up file
+ * specifications; can be NULL if no such element is
+ * present.
+ * @param pSectionElem Section element for which this helper is getting called.
+ */
+void OVFReader::HandleDiskSection(const xml::ElementNode *pReferencesElem,
+ const xml::ElementNode *pSectionElem)
+{
+ // contains "Disk" child elements
+ xml::NodesLoop loopDisks(*pSectionElem, "Disk");
+ const xml::ElementNode *pelmDisk;
+ while ((pelmDisk = loopDisks.forAllNodes()))
+ {
+ DiskImage d;
+ const char *pcszBad = NULL;
+ const char *pcszDiskId;
+ const char *pcszFormat;
+ if (!pelmDisk->getAttributeValueN("diskId", pcszDiskId, RT_XML_ATTR_TINY))
+ pcszBad = "diskId";
+ else if (!pelmDisk->getAttributeValueN("format", pcszFormat, RT_XML_ATTR_SMALL))
+ pcszBad = "format";
+ else if (!pelmDisk->getAttributeValue("capacity", d.iCapacity))
+ pcszBad = "capacity";
+ else
+ {
+ d.strDiskId = pcszDiskId;
+ d.strFormat = pcszFormat;
+
+ if (!pelmDisk->getAttributeValue("populatedSize", d.iPopulatedSize))
+ // optional
+ d.iPopulatedSize = -1;
+
+ // optional vbox:uuid attribute (if OVF was exported by VirtualBox != 3.2)
+ pelmDisk->getAttributeValueN("uuid", d.uuidVBox, RT_XML_ATTR_TINY, "vbox");
+
+ const char *pcszFileRef;
+ if (pelmDisk->getAttributeValueN("fileRef", pcszFileRef, RT_XML_ATTR_SMALL)) // optional
+ {
+ // look up corresponding /References/File nodes (list built above)
+ const xml::ElementNode *pFileElem;
+ if ( pReferencesElem
+ && (pFileElem = pReferencesElem->findChildElementFromId(pcszFileRef)) != NULL
+ )
+ {
+
+ // copy remaining values from file node then
+ const char *pcszBadInFile = NULL;
+ const char *pcszHref;
+ if (!pFileElem->getAttributeValueN("href", pcszHref, RT_XML_ATTR_SMALL))
+ pcszBadInFile = "href";
+ else if (!pFileElem->getAttributeValue("size", d.iSize))
+ d.iSize = -1; // optional
+
+ d.strHref = pcszHref;
+
+ // if (!(pFileElem->getAttributeValue("size", d.iChunkSize))) TODO
+ d.iChunkSize = -1; // optional
+ const char *pcszCompression;
+ if (pFileElem->getAttributeValueN("compression", pcszCompression, RT_XML_ATTR_TINY))
+ d.strCompression = pcszCompression;
+
+ if (pcszBadInFile)
+ throw OVFLogicError(N_("Error reading \"%s\": missing or invalid attribute '%s' in 'File' element, line %d"),
+ m_strPath.c_str(),
+ pcszBadInFile,
+ pFileElem->getLineNumber());
+ }
+ else
+ throw OVFLogicError(N_("Error reading \"%s\": cannot find References/File element for ID \"%s\" referenced by 'Disk' element, line %d"),
+ m_strPath.c_str(),
+ pcszFileRef,
+ pelmDisk->getLineNumber());
+ }
+ }
+
+ if (pcszBad)
+ throw OVFLogicError(N_("Error reading \"%s\": missing or invalid attribute '%s' in 'DiskSection' element, line %d"),
+ m_strPath.c_str(),
+ pcszBad,
+ pelmDisk->getLineNumber());
+
+ // suggest a size in megabytes to help callers with progress reports
+ d.ulSuggestedSizeMB = 0;
+ if (d.iCapacity != -1)
+ d.ulSuggestedSizeMB = (uint32_t)(d.iCapacity / _1M);
+ else if (d.iPopulatedSize != -1)
+ d.ulSuggestedSizeMB = (uint32_t)(d.iPopulatedSize / _1M);
+ else if (d.iSize != -1)
+ d.ulSuggestedSizeMB = (uint32_t)(d.iSize / _1M);
+ if (d.ulSuggestedSizeMB == 0)
+ d.ulSuggestedSizeMB = 10000; // assume 10 GB, this is for the progress bar only anyway
+
+ m_mapDisks[d.strDiskId] = d;
+ }
+}
+
+/**
+ * Private helper method that handles network sections in the OVF XML.
+ * Gets called indirectly from IAppliance::read().
+ *
+ * @return
+ */
+void OVFReader::HandleNetworkSection(const xml::ElementNode * /* pSectionElem */)
+{
+ // we ignore network sections for now
+
+// xml::NodesLoop loopNetworks(*pSectionElem, "Network");
+// const xml::Node *pelmNetwork;
+// while ((pelmNetwork = loopNetworks.forAllNodes()))
+// {
+// Network n;
+// if (!(pelmNetwork->getAttributeValue("name", n.strNetworkName)))
+// return setError(VBOX_E_FILE_ERROR,
+// tr("Error reading \"%s\": missing 'name' attribute in 'Network', line %d"),
+// pcszPath,
+// pelmNetwork->getLineNumber());
+//
+// m->mapNetworks[n.strNetworkName] = n;
+// }
+}
+
+/**
+ * Private helper method that handles a "VirtualSystem" element in the OVF XML.
+ * Gets called indirectly from IAppliance::read().
+ *
+ * @param pelmVirtualSystem
+ * @return
+ */
+void OVFReader::HandleVirtualSystemContent(const xml::ElementNode *pelmVirtualSystem)
+{
+ /* Create a new virtual system and work directly on the list copy. */
+ m_llVirtualSystems.push_back(VirtualSystem());
+ VirtualSystem &vsys = m_llVirtualSystems.back();
+
+ // peek under the <VirtualSystem> node whether we have a <vbox:Machine> node;
+ // that case case, the caller can completely ignore the OVF but only load the VBox machine XML
+ vsys.pelmVBoxMachine = pelmVirtualSystem->findChildElementNS("vbox", "Machine");
+
+ // now look for real OVF
+ const xml::AttributeNode *pIdAttr = pelmVirtualSystem->findAttribute("id");
+ if (pIdAttr)
+ vsys.strName = pIdAttr->getValueN(RT_XML_ATTR_SMALL);
+
+ xml::NodesLoop loop(*pelmVirtualSystem); // all child elements
+ const xml::ElementNode *pelmThis;
+ while ((pelmThis = loop.forAllNodes()))
+ {
+ const char *pcszElemName = pelmThis->getName();
+ const char *pcszTypeAttr = "";
+ if (!strcmp(pcszElemName, "Section")) // OVF 0.9 used "Section" element always with a varying "type" attribute
+ {
+ const xml::AttributeNode *pTypeAttr = pelmThis->findAttribute("type");
+ if (pTypeAttr)
+ pcszTypeAttr = pTypeAttr->getValueN(RT_XML_ATTR_TINY);
+ else
+ throw OVFLogicError(N_("Error reading \"%s\": element 'Section' has no 'type' attribute, line %d"),
+ m_strPath.c_str(),
+ pelmThis->getLineNumber());
+ }
+
+ if ( !strcmp(pcszElemName, "EulaSection")
+ || !strcmp(pcszTypeAttr, "ovf:EulaSection_Type")
+ )
+ {
+ /* <EulaSection>
+ <Info ovf:msgid="6">License agreement for the Virtual System.</Info>
+ <License ovf:msgid="1">License terms can go in here.</License>
+ </EulaSection> */
+
+ const xml::ElementNode *pelmLicense;
+ if ((pelmLicense = pelmThis->findChildElement("License")))
+ vsys.strLicenseText = pelmLicense->getValueN(RT_XML_CONTENT_LARGE);
+ }
+ if ( !strcmp(pcszElemName, "ProductSection")
+ || !strcmp(pcszTypeAttr, "ovf:ProductSection_Type")
+ )
+ {
+ /* <Section ovf:required="false" xsi:type="ovf:ProductSection_Type">
+ <Info>Meta-information about the installed software</Info>
+ <Product>VAtest</Product>
+ <Vendor>SUN Microsystems</Vendor>
+ <Version>10.0</Version>
+ <ProductUrl>http://blogs.sun.com/VirtualGuru</ProductUrl>
+ <VendorUrl>http://www.sun.com</VendorUrl>
+ </Section> */
+ const xml::ElementNode *pelmProduct;
+ if ((pelmProduct = pelmThis->findChildElement("Product")))
+ vsys.strProduct = pelmProduct->getValueN(RT_XML_CONTENT_SMALL);
+ const xml::ElementNode *pelmVendor;
+ if ((pelmVendor = pelmThis->findChildElement("Vendor")))
+ vsys.strVendor = pelmVendor->getValueN(RT_XML_CONTENT_SMALL);
+ const xml::ElementNode *pelmVersion;
+ if ((pelmVersion = pelmThis->findChildElement("Version")))
+ vsys.strVersion = pelmVersion->getValueN(RT_XML_CONTENT_SMALL);
+ const xml::ElementNode *pelmProductUrl;
+ if ((pelmProductUrl = pelmThis->findChildElement("ProductUrl")))
+ vsys.strProductUrl = pelmProductUrl->getValueN(RT_XML_CONTENT_SMALL);
+ const xml::ElementNode *pelmVendorUrl;
+ if ((pelmVendorUrl = pelmThis->findChildElement("VendorUrl")))
+ vsys.strVendorUrl = pelmVendorUrl->getValueN(RT_XML_CONTENT_SMALL);
+ }
+ else if ( !strcmp(pcszElemName, "VirtualHardwareSection")
+ || !strcmp(pcszTypeAttr, "ovf:VirtualHardwareSection_Type")
+ )
+ {
+ const xml::ElementNode *pelmSystem, *pelmVirtualSystemType;
+ if ((pelmSystem = pelmThis->findChildElement("System")))
+ {
+ /* <System>
+ <vssd:Description>Description of the virtual hardware section.</vssd:Description>
+ <vssd:ElementName>vmware</vssd:ElementName>
+ <vssd:InstanceID>1</vssd:InstanceID>
+ <vssd:VirtualSystemIdentifier>MyLampService</vssd:VirtualSystemIdentifier>
+ <vssd:VirtualSystemType>vmx-4</vssd:VirtualSystemType>
+ </System>*/
+ if ((pelmVirtualSystemType = pelmSystem->findChildElement("VirtualSystemType")))
+ vsys.strVirtualSystemType = pelmVirtualSystemType->getValueN(RT_XML_CONTENT_SMALL);
+ }
+
+ /* Parse the items into the hardware item vector. */
+ {
+ std::map<RTCString, const VirtualHardwareItem *> mapHardwareItems;
+ xml::NodesLoop childrenIterator(*pelmThis);
+ const xml::ElementNode *pelmItem;
+ while ((pelmItem = childrenIterator.forAllNodes()) != NULL)
+ {
+ /* Parse according to type. */
+ VirtualHardwareItem *pItem;
+ const char *pszName = pelmItem->getName();
+ if (RTStrCmp(pszName, "Item") == 0)
+ pItem = new VirtualHardwareItem();
+ else if (RTStrCmp(pszName, "StorageItem") == 0)
+ pItem = new StorageItem();
+ else if (RTStrCmp(pszName, "EthernetPortItem") == 0)
+ pItem = new EthernetPortItem();
+ else
+ continue;
+ vsys.vecHardwareItems.push_back(pItem);
+ pItem->m_iLineNumber = pelmItem->getLineNumber();
+ pItem->fillItem(pelmItem);
+
+ /* validate */
+ try
+ {
+ pItem->checkConsistencyAndCompliance();
+ }
+ catch (OVFLogicError &e)
+ {
+ throw OVFLogicError(N_("Error reading \"%s\": \"%s\""), m_strPath.c_str(), e.what());
+ }
+
+ /* Add to mapping vector (for parent ID lookups) if it has a valid instance ID. */
+ if (!pItem->strInstanceID.isEmpty())
+ {
+ std::map<RTCString, const VirtualHardwareItem *>::const_iterator itDup;
+ itDup = mapHardwareItems.find(pItem->strInstanceID);
+ if (itDup == mapHardwareItems.end())
+ mapHardwareItems[pItem->strInstanceID] = pItem;
+ else
+#if 1
+ LogRel(("OVFREADER: Warning reading \"%s\": Duplicate InstanceID \"%s\" on line %d, previous at %d!\n",
+ m_strPath.c_str(), pItem->strInstanceID.c_str(), pItem->m_iLineNumber, itDup->second->m_iLineNumber));
+#else
+ throw OVFLogicError(N_("Error reading \"%s\": Duplicate InstanceID \"%s\" on line %d, previous at %d"),
+ m_strPath.c_str(), pItem->strInstanceID.c_str(),
+ pItem->m_iLineNumber, itDup->second->m_iLineNumber);
+#endif
+ }
+ }
+ }
+
+ HardDiskController *pPrimaryIDEController = NULL;// will be set once found
+
+ // now go thru all hardware items and handle them according to their type;
+ // in this first loop we handle all items _except_ hard disk images,
+ // which we'll handle in a second loop below
+ HardwareItemVector::const_iterator itH;
+ for (itH = vsys.vecHardwareItems.begin(); itH != vsys.vecHardwareItems.end(); ++itH)
+ {
+ const VirtualHardwareItem &i = **itH;
+
+ // do some analysis
+ switch (i.resourceType)
+ {
+ case ResourceType_Processor: // 3
+ /* <rasd:Caption>1 virtual CPU</rasd:Caption>
+ <rasd:Description>Number of virtual CPUs</rasd:Description>
+ <rasd:ElementName>virtual CPU</rasd:ElementName>
+ <rasd:InstanceID>1</rasd:InstanceID>
+ <rasd:ResourceType>3</rasd:ResourceType>
+ <rasd:VirtualQuantity>1</rasd:VirtualQuantity>*/
+ if (i.ullVirtualQuantity < UINT16_MAX)
+ vsys.cCPUs = (uint16_t)i.ullVirtualQuantity;
+ else
+ throw OVFLogicError(N_("Error reading \"%s\": CPU count %RI64 is larger than %d, line %d"),
+ m_strPath.c_str(),
+ i.ullVirtualQuantity,
+ UINT16_MAX,
+ i.m_iLineNumber);
+ break;
+
+ case ResourceType_Memory: // 4
+ if ( i.strAllocationUnits == "MegaBytes" // found in OVF created by OVF toolkit
+ || i.strAllocationUnits == "MB" // found in MS docs
+ || i.strAllocationUnits == "byte * 2^20" // suggested by OVF spec DSP0243 page 21
+ )
+ vsys.ullMemorySize = i.ullVirtualQuantity * _1M;
+ else
+ throw OVFLogicError(N_("Error reading \"%s\": Invalid allocation unit \"%s\" specified with memory size item, line %d"),
+ m_strPath.c_str(),
+ i.strAllocationUnits.c_str(),
+ i.m_iLineNumber);
+ break;
+
+ case ResourceType_IDEController: // 5
+ {
+ /* <Item>
+ <rasd:Caption>ideController0</rasd:Caption>
+ <rasd:Description>IDE Controller</rasd:Description>
+ <rasd:InstanceId>5</rasd:InstanceId>
+ <rasd:ResourceType>5</rasd:ResourceType>
+ <rasd:Address>0</rasd:Address>
+ <rasd:BusNumber>0</rasd:BusNumber>
+ </Item> */
+ HardDiskController hdc;
+ hdc.system = HardDiskController::IDE;
+ hdc.strIdController = i.strInstanceID;
+ hdc.strControllerType = i.strResourceSubType;
+
+ hdc.lAddress = i.lAddress;
+
+ if (!pPrimaryIDEController)
+ // this is the first IDE controller found: then mark it as "primary"
+ hdc.fPrimary = true;
+ else
+ {
+ // this is the second IDE controller found: If VMware exports two
+ // IDE controllers, it seems that they are given an "Address" of 0
+ // an 1, respectively, so assume address=0 means primary controller
+ if ( pPrimaryIDEController->lAddress == 0
+ && hdc.lAddress == 1
+ )
+ {
+ pPrimaryIDEController->fPrimary = true;
+ hdc.fPrimary = false;
+ }
+ else if ( pPrimaryIDEController->lAddress == 1
+ && hdc.lAddress == 0
+ )
+ {
+ pPrimaryIDEController->fPrimary = false;
+ hdc.fPrimary = false;
+ }
+ else
+ // then we really can't tell, just hope for the best
+ hdc.fPrimary = false;
+ }
+
+ vsys.mapControllers[i.strInstanceID] = hdc;
+ if (!pPrimaryIDEController)
+ pPrimaryIDEController = &vsys.mapControllers[i.strInstanceID];
+ break;
+ }
+
+ case ResourceType_ParallelSCSIHBA: // 6 SCSI controller
+ {
+ /* <Item>
+ <rasd:Caption>SCSI Controller 0 - LSI Logic</rasd:Caption>
+ <rasd:Description>SCI Controller</rasd:Description>
+ <rasd:ElementName>SCSI controller</rasd:ElementName>
+ <rasd:InstanceID>4</rasd:InstanceID>
+ <rasd:ResourceSubType>LsiLogic</rasd:ResourceSubType>
+ <rasd:ResourceType>6</rasd:ResourceType>
+ </Item> */
+ HardDiskController hdc;
+ hdc.system = HardDiskController::SCSI;
+ hdc.strIdController = i.strInstanceID;
+ hdc.strControllerType = i.strResourceSubType;
+
+ vsys.mapControllers[i.strInstanceID] = hdc;
+ break;
+ }
+
+ case ResourceType_EthernetAdapter: // 10
+ {
+ /* <Item>
+ <rasd:Caption>Ethernet adapter on 'Bridged'</rasd:Caption>
+ <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+ <rasd:Connection>Bridged</rasd:Connection>
+ <rasd:InstanceID>6</rasd:InstanceID>
+ <rasd:ResourceType>10</rasd:ResourceType>
+ <rasd:ResourceSubType>E1000</rasd:ResourceSubType>
+ </Item>
+
+ OVF spec DSP 0243 page 21:
+ "For an Ethernet adapter, this specifies the abstract network connection name
+ for the virtual machine. All Ethernet adapters that specify the same abstract
+ network connection name within an OVF package shall be deployed on the same
+ network. The abstract network connection name shall be listed in the NetworkSection
+ at the outermost envelope level." */
+
+ // only store the name
+ EthernetAdapter ea;
+ ea.strAdapterType = i.strResourceSubType;
+ ea.strNetworkName = i.strConnection;
+ vsys.llEthernetAdapters.push_back(ea);
+ break;
+ }
+
+ case ResourceType_FloppyDrive: // 14
+ vsys.fHasFloppyDrive = true; // we have no additional information
+ break;
+
+ case ResourceType_CDDrive: // 15
+ /* <Item ovf:required="false">
+ <rasd:Caption>cdrom1</rasd:Caption>
+ <rasd:InstanceId>7</rasd:InstanceId>
+ <rasd:ResourceType>15</rasd:ResourceType>
+ <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+ <rasd:Parent>5</rasd:Parent>
+ <rasd:AddressOnParent>0</rasd:AddressOnParent>
+ </Item> */
+ // I tried to see what happens if I set an ISO for the CD-ROM in VMware Workstation,
+ // but then the ovftool dies with "Device backing not supported". So I guess if
+ // VMware can't export ISOs, then we don't need to be able to import them right now.
+ vsys.fHasCdromDrive = true; // we have no additional information
+ break;
+
+ case ResourceType_HardDisk: // 17
+ // handled separately in second loop below
+ break;
+
+ case ResourceType_OtherStorageDevice: // 20 SATA controller
+ {
+ /* <Item>
+ <rasd:Description>SATA Controller</rasd:Description>
+ <rasd:Caption>sataController0</rasd:Caption>
+ <rasd:InstanceID>4</rasd:InstanceID>
+ <rasd:ResourceType>20</rasd:ResourceType>
+ <rasd:ResourceSubType>AHCI</rasd:ResourceSubType>
+ <rasd:Address>0</rasd:Address>
+ <rasd:BusNumber>0</rasd:BusNumber>
+ </Item> */
+ if ( i.strResourceSubType.compare("AHCI", RTCString::CaseInsensitive) == 0
+ || i.strResourceSubType.compare("vmware.sata.ahci", RTCString::CaseInsensitive) == 0)
+ {
+ HardDiskController hdc;
+ hdc.system = HardDiskController::SATA;
+ hdc.strIdController = i.strInstanceID;
+ hdc.strControllerType = i.strResourceSubType;
+
+ vsys.mapControllers[i.strInstanceID] = hdc;
+ }
+ else if ( i.strResourceSubType.compare("VirtioSCSI", RTCString::CaseInsensitive) == 0
+ || i.strResourceSubType.compare("virtio-scsi", RTCString::CaseInsensitive) == 0 )
+ {
+ HardDiskController hdc;
+ hdc.system = HardDiskController::VIRTIOSCSI; /**< r=klaus: GUI needs to learn about this in the import dialog, currently shown as "Unknown Hardware Item". */
+ hdc.strIdController = i.strInstanceID;
+ //<rasd:ResourceSubType>VirtioSCSI</rasd:ResourceSubType>
+ hdc.strControllerType = i.strResourceSubType;
+ vsys.mapControllers[i.strInstanceID] = hdc;
+ }
+ else
+ throw OVFLogicError(N_("Error reading \"%s\": Host resource of type \"Other Storage Device (%d)\" is supported with SATA AHCI or virtio-scsi controllers only, line %d (subtype:%s)"),
+ m_strPath.c_str(),
+ ResourceType_OtherStorageDevice,
+ i.m_iLineNumber, i.strResourceSubType.c_str() );
+ break;
+ }
+
+ case ResourceType_USBController: // 23
+ /* <Item ovf:required="false">
+ <rasd:Caption>usb</rasd:Caption>
+ <rasd:Description>USB Controller</rasd:Description>
+ <rasd:InstanceId>3</rasd:InstanceId>
+ <rasd:ResourceType>23</rasd:ResourceType>
+ <rasd:Address>0</rasd:Address>
+ <rasd:BusNumber>0</rasd:BusNumber>
+ </Item> */
+ vsys.fHasUsbController = true; // we have no additional information
+ break;
+
+ case ResourceType_SoundCard: // 35
+ /* <Item ovf:required="false">
+ <rasd:Caption>sound</rasd:Caption>
+ <rasd:Description>Sound Card</rasd:Description>
+ <rasd:InstanceId>10</rasd:InstanceId>
+ <rasd:ResourceType>35</rasd:ResourceType>
+ <rasd:ResourceSubType>ensoniq1371</rasd:ResourceSubType>
+ <rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
+ <rasd:AddressOnParent>3</rasd:AddressOnParent>
+ </Item> */
+ vsys.strSoundCardType = i.strResourceSubType;
+ break;
+
+ default:
+ {
+ /* If this unknown resource type isn't required, we simply skip it. */
+ if (i.fResourceRequired)
+ {
+ throw OVFLogicError(N_("Error reading \"%s\": Unknown resource type %d in hardware item, line %d"),
+ m_strPath.c_str(),
+ i.resourceType,
+ i.m_iLineNumber);
+ }
+ }
+ } // end switch
+ }
+
+ // now run through the items for a second time, but handle only
+ // hard disk images; otherwise the code would fail if a hard
+ // disk image appears in the OVF before its hard disk controller
+ for (itH = vsys.vecHardwareItems.begin(); itH != vsys.vecHardwareItems.end(); ++itH)
+ {
+ const VirtualHardwareItem &i = **itH;
+
+ // do some analysis
+ switch (i.resourceType)
+ {
+ case ResourceType_CDDrive: // 15
+ /* <Item ovf:required="false">
+ <rasd:Caption>cdrom1</rasd:Caption>
+ <rasd:InstanceId>7</rasd:InstanceId>
+ <rasd:ResourceType>15</rasd:ResourceType>
+ <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+ <rasd:Parent>5</rasd:Parent>
+ <rasd:AddressOnParent>0</rasd:AddressOnParent>
+ </Item> */
+ case ResourceType_HardDisk: // 17
+ {
+ /* <Item>
+ <rasd:Caption>Harddisk 1</rasd:Caption>
+ <rasd:Description>HD</rasd:Description>
+ <rasd:ElementName>Hard Disk</rasd:ElementName>
+ <rasd:HostResource>ovf://disk/lamp</rasd:HostResource>
+ <rasd:InstanceID>5</rasd:InstanceID>
+ <rasd:Parent>4</rasd:Parent>
+ <rasd:ResourceType>17</rasd:ResourceType>
+ </Item> */
+
+ // look up the hard disk controller element whose InstanceID equals our Parent;
+ // this is how the connection is specified in OVF
+ ControllersMap::const_iterator it = vsys.mapControllers.find(i.strParent);
+ if (it == vsys.mapControllers.end())
+ throw OVFLogicError(N_("Error reading \"%s\": Disk item with instance ID \"%s\" specifies invalid parent \"%s\", line %d"),
+ m_strPath.c_str(),
+ i.strInstanceID.c_str(),
+ i.strParent.c_str(),
+ i.m_iLineNumber);
+
+ VirtualDisk vd;
+ vd.strIdController = i.strParent;
+ i.strAddressOnParent.toInt(vd.ulAddressOnParent);
+ // ovf://disk/lamp
+ // 123456789012345
+ if (i.strHostResource.startsWith("ovf://disk/"))
+ vd.strDiskId = i.strHostResource.substr(11);
+ else if (i.strHostResource.startsWith("ovf:/disk/"))
+ vd.strDiskId = i.strHostResource.substr(10);
+ else if (i.strHostResource.startsWith("/disk/"))
+ vd.strDiskId = i.strHostResource.substr(6);
+
+ //the error may be missed for CD, because CD can be empty
+ if ((vd.strDiskId.isEmpty() || (m_mapDisks.find(vd.strDiskId) == m_mapDisks.end()))
+ && i.resourceType == ResourceType_HardDisk)
+ throw OVFLogicError(N_("Error reading \"%s\": Disk item with instance ID \"%s\" specifies invalid host resource \"%s\", line %d"),
+ m_strPath.c_str(),
+ i.strInstanceID.c_str(),
+ i.strHostResource.c_str(),
+ i.m_iLineNumber);
+
+ vsys.mapVirtualDisks[vd.strDiskId] = vd;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ else if ( !strcmp(pcszElemName, "OperatingSystemSection")
+ || !strcmp(pcszTypeAttr, "ovf:OperatingSystemSection_Type")
+ )
+ {
+ uint64_t cimos64;
+ if (!(pelmThis->getAttributeValue("id", cimos64)))
+ throw OVFLogicError(N_("Error reading \"%s\": missing or invalid 'ovf:id' attribute in operating system section element, line %d"),
+ m_strPath.c_str(),
+ pelmThis->getLineNumber());
+
+ vsys.cimos = (CIMOSType_T)cimos64;
+ const xml::ElementNode *pelmCIMOSDescription;
+ if ((pelmCIMOSDescription = pelmThis->findChildElement("Description")))
+ vsys.strCimosDesc = pelmCIMOSDescription->getValueN(RT_XML_CONTENT_SMALL);
+
+ const xml::ElementNode *pelmVBoxOSType;
+ if ((pelmVBoxOSType = pelmThis->findChildElementNS("vbox", // namespace
+ "OSType"))) // element name
+ vsys.strTypeVBox = pelmVBoxOSType->getValueN(RT_XML_CONTENT_SMALL);
+ }
+ else if ( (!strcmp(pcszElemName, "AnnotationSection"))
+ || (!strcmp(pcszTypeAttr, "ovf:AnnotationSection_Type"))
+ )
+ {
+ const xml::ElementNode *pelmAnnotation;
+ if ((pelmAnnotation = pelmThis->findChildElement("Annotation")))
+ vsys.strDescription = pelmAnnotation->getValueN(RT_XML_CONTENT_SMALL);
+ }
+ }
+}
+
+void VirtualHardwareItem::fillItem(const xml::ElementNode *item)
+{
+ xml::NodesLoop loopItemChildren(*item);// all child elements
+ const xml::ElementNode *pelmItemChild;
+ while ((pelmItemChild = loopItemChildren.forAllNodes()))
+ {
+ const char *pcszItemChildName = pelmItemChild->getName();
+ if (!strcmp(pcszItemChildName, "Description"))
+ strDescription = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "Caption"))
+ strCaption = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "ElementName"))
+ strElementName = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if ( !strcmp(pcszItemChildName, "InstanceID")
+ || !strcmp(pcszItemChildName, "InstanceId") )
+ strInstanceID = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "HostResource"))
+ strHostResource = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "ResourceType"))
+ {
+ uint32_t ulType;
+ pelmItemChild->copyValue(ulType);
+ if (ulType > 0xffff)
+ ulType = 0xffff;
+ resourceType = (ResourceType_T)ulType;
+ fResourceRequired = true;
+ const char *pcszAttValue;
+ if (item->getAttributeValueN("required", pcszAttValue, RT_XML_ATTR_TINY))
+ {
+ if (!strcmp(pcszAttValue, "false"))
+ fResourceRequired = false;
+ }
+ }
+ else if (!strcmp(pcszItemChildName, "OtherResourceType"))
+ strOtherResourceType = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "ResourceSubType"))
+ strResourceSubType = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "AutomaticAllocation"))
+ fAutomaticAllocation = (!strcmp(pelmItemChild->getValueN(RT_XML_CONTENT_SMALL), "true")) ? true : false;
+ else if (!strcmp(pcszItemChildName, "AutomaticDeallocation"))
+ fAutomaticDeallocation = (!strcmp(pelmItemChild->getValueN(RT_XML_CONTENT_SMALL), "true")) ? true : false;
+ else if (!strcmp(pcszItemChildName, "Parent"))
+ strParent = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "Connection"))
+ strConnection = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "Address"))
+ {
+ strAddress = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ pelmItemChild->copyValue(lAddress);
+ }
+ else if (!strcmp(pcszItemChildName, "AddressOnParent"))
+ strAddressOnParent = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "AllocationUnits"))
+ strAllocationUnits = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "VirtualQuantity"))
+ pelmItemChild->copyValue(ullVirtualQuantity);
+ else if (!strcmp(pcszItemChildName, "Reservation"))
+ pelmItemChild->copyValue(ullReservation);
+ else if (!strcmp(pcszItemChildName, "Limit"))
+ pelmItemChild->copyValue(ullLimit);
+ else if (!strcmp(pcszItemChildName, "Weight"))
+ pelmItemChild->copyValue(ullWeight);
+ else if (!strcmp(pcszItemChildName, "ConsumerVisibility"))
+ strConsumerVisibility = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "MappingBehavior"))
+ strMappingBehavior = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "PoolID"))
+ strPoolID = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "BusNumber"))
+ pelmItemChild->copyValue(ulBusNumber);
+// else if (pelmItemChild->getPrefix() == NULL
+// || strcmp(pelmItemChild->getPrefix(), "vmw"))
+// throw OVFLogicError(N_("Unknown element '%s' under Item element, line %d"),
+// pcszItemChildName,
+// m_iLineNumber);
+ }
+}
+
+void VirtualHardwareItem::_checkConsistencyAndCompliance() RT_THROW(OVFLogicError)
+{
+ RTCString name = getItemName();
+ if (resourceType == 0)
+ throw OVFLogicError(N_("Empty element ResourceType under %s element, line %d. see DMTF Schema Documentation %s"),
+ name.c_str(), m_iLineNumber, DTMF_SPECS_URI);
+
+ /* Don't be too uptight about the strInstanceID value. There are OVAs out
+ there which have InstanceID="%iid%" for memory for instance, which is
+ no good reason for not being able to process them. bugref:8997 */
+ if (strInstanceID.isEmpty())
+ {
+ if ( resourceType == ResourceType_IDEController
+ || resourceType == ResourceType_OtherStorageDevice
+ || resourceType == ResourceType_ParallelSCSIHBA
+ || resourceType == ResourceType_iSCSIHBA //??
+ || resourceType == ResourceType_IBHCA ) //??
+ throw OVFLogicError(N_("Element InstanceID is absent under %s element, line %d. see DMTF Schema Documentation %s"),
+ name.c_str(), m_iLineNumber, DTMF_SPECS_URI);
+ else
+ LogRel(("OVFREADER: Warning: Ignoring missing or invalid InstanceID under element %s, line %u\n",
+ name.c_str(), m_iLineNumber));
+ }
+}
+
+void StorageItem::fillItem(const xml::ElementNode *item)
+{
+ VirtualHardwareItem::fillItem(item);
+
+ xml::NodesLoop loopItemChildren(*item);// all child elements
+ const xml::ElementNode *pelmItemChild;
+ while ((pelmItemChild = loopItemChildren.forAllNodes()))
+ {
+ const char *pcszItemChildName = pelmItemChild->getName();
+ if (!strcmp(pcszItemChildName, "HostExtentName"))
+ strHostExtentName = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "OtherHostExtentNameFormat"))
+ strOtherHostExtentNameFormat = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "OtherHostExtentNameNamespace"))
+ strOtherHostExtentNameNamespace = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "VirtualQuantityUnits"))
+ strVirtualQuantityUnits = pelmItemChild->getValueN(RT_XML_CONTENT_SMALL);
+ else if (!strcmp(pcszItemChildName, "Access"))
+ {
+ uint32_t temp;
+ pelmItemChild->copyValue(temp);
+ accessType = (StorageAccessType_T)temp;
+ }
+ else if (!strcmp(pcszItemChildName, "HostExtentNameFormat"))
+ {
+ }
+ else if (!strcmp(pcszItemChildName, "HostExtentNameNamespace"))
+ {
+ }
+ else if (!strcmp(pcszItemChildName, "HostExtentStartingAddress"))
+ {
+ }
+ else if (!strcmp(pcszItemChildName, "HostResourceBlockSize"))
+ {
+ int64_t temp;
+ pelmItemChild->copyValue(temp);
+ hostResourceBlockSize = temp;
+ }
+ else if (!strcmp(pcszItemChildName, "Limit"))
+ {
+ int64_t temp;
+ pelmItemChild->copyValue(temp);
+ limit = temp;
+ }
+ else if (!strcmp(pcszItemChildName, "Reservation"))
+ {
+ int64_t temp;
+ pelmItemChild->copyValue(temp);
+ reservation = temp;
+ }
+ else if (!strcmp(pcszItemChildName, "VirtualQuantity"))
+ {
+ int64_t temp;
+ pelmItemChild->copyValue(temp);
+ virtualQuantity = temp;
+ }
+ else if (!strcmp(pcszItemChildName, "VirtualResourceBlockSize"))
+ {
+ int64_t temp;
+ pelmItemChild->copyValue(temp);
+ virtualResourceBlockSize = temp;
+ }
+ }
+}
+
+
+void StorageItem::_checkConsistencyAndCompliance() RT_THROW(OVFLogicError)
+{
+ VirtualHardwareItem::_checkConsistencyAndCompliance();
+
+ RTCString name = getItemName();
+
+ if (accessType == StorageAccessType_Unknown)
+ {
+ //throw OVFLogicError(N_("Access type is unknown under %s element, line %d"),
+ // name.c_str(), m_iLineNumber);
+ }
+
+ if (hostResourceBlockSize <= 0 && reservation > 0)
+ {
+ throw OVFLogicError(N_("Element HostResourceBlockSize is absent under %s element, line %d. "
+ "see DMTF Schema Documentation %s"),
+ name.c_str(), m_iLineNumber, DTMF_SPECS_URI);
+ }
+
+ if (virtualResourceBlockSize <= 0 && virtualQuantity > 0)
+ {
+ throw OVFLogicError(N_("Element VirtualResourceBlockSize is absent under %s element, line %d. "
+ "see DMTF Schema Documentation %s"),
+ name.c_str(), m_iLineNumber, DTMF_SPECS_URI);
+ }
+
+ if (virtualQuantity > 0 && strVirtualQuantityUnits.isEmpty())
+ {
+ throw OVFLogicError(N_("Element VirtualQuantityUnits is absent under %s element, line %d. "
+ "see DMTF Schema Documentation %s"),
+ name.c_str(), m_iLineNumber, DTMF_SPECS_URI);
+ }
+
+ if (virtualResourceBlockSize <= 1 &&
+ strVirtualQuantityUnits.compare(RTCString("count"), RTCString::CaseInsensitive) == 0
+ )
+ {
+ throw OVFLogicError(N_("Element VirtualQuantityUnits is set to \"count\" "
+ "while VirtualResourceBlockSize is set to 1. "
+ "under %s element, line %d. "
+ "It's needed to change on \"byte\". "
+ "see DMTF Schema Documentation %s"),
+ name.c_str(), m_iLineNumber, DTMF_SPECS_URI);
+ }
+}
+
+void EthernetPortItem::fillItem(const xml::ElementNode *item)
+{
+ VirtualHardwareItem::fillItem(item);
+
+ xml::NodesLoop loopItemChildren(*item);// all child elements
+ const xml::ElementNode *pelmItemChild;
+ while ((pelmItemChild = loopItemChildren.forAllNodes()))
+ {
+ }
+}
+
+void EthernetPortItem::_checkConsistencyAndCompliance() RT_THROW(OVFLogicError)
+{
+ VirtualHardwareItem::_checkConsistencyAndCompliance();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Errors
+//
+////////////////////////////////////////////////////////////////////////////////
+
+OVFLogicError::OVFLogicError(const char *aFormat, ...)
+{
+ char *pszNewMsg;
+ va_list args;
+ va_start(args, aFormat);
+ RTStrAPrintfV(&pszNewMsg, aFormat, args);
+ setWhat(pszNewMsg);
+ RTStrFree(pszNewMsg);
+ va_end(args);
+}
diff --git a/src/VBox/Main/xml/samples/VirtualBox-global.xml b/src/VBox/Main/xml/samples/VirtualBox-global.xml
new file mode 100644
index 00000000..139a47ce
--- /dev/null
+++ b/src/VBox/Main/xml/samples/VirtualBox-global.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
+<!-- Oracle VM VirtualBox Sample Global Configuration -->
+<!--
+ * :tabSize=2:indentSize=2:noTabs=true:
+ * :folding=explicit:collapseFolds=1:
+-->
+
+<VirtualBox
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.innotek.de/VirtualBox-settings ../VirtualBox-settings-windows.xsd"
+ xmlns="http://www.innotek.de/VirtualBox-settings"
+ version="1.2-windows"
+>
+
+ <Global>
+ <ExtraData>
+ <ExtraDataItem name="GUI/Input/HostKey" value="165"/>
+ <ExtraDataItem name="GUI/Input/AutoCapture" value="true"/>
+ <ExtraDataItem name="GUI/SuppressMessages" value="remindAboutAutoCapture,remindAboutMouseIntegrationOn"/>
+ </ExtraData>
+ <MachineRegistry>
+ <MachineEntry src="Machines\{1a4d3cff-e693-4ae5-afaf-a1ccc6882183}.xml" uuid="{1a4d3cff-e693-4ae5-afaf-a1ccc6882183}"/>
+ <MachineEntry src="Machines\{aa08aecf-e67d-46b9-a325-948691660689}.xml" uuid="{aa08aecf-e67d-46b9-a325-948691660689}"/>
+ <MachineEntry src="Machines/%7B81ae80b4-2507-4fe8-bed8-33f14920d301%7D.xml" uuid="{81ae80b4-2507-4fe8-bed8-33f14920d301}"/>
+ </MachineRegistry>
+ <DiskRegistry>
+ <HardDisks>
+ <HardDisk uuid="{23dbf22e-9f27-49da-bdae-f4c43f968057}" type="normal">
+ <VirtualDiskImage filePath="E:/develop/innotek/images/nt4ger.vdi"/>
+ <DiffHardDisk uuid="{23dbf22e-9f27-49da-bdae-f4c43f968057}">
+ <VirtualDiskImage filePath="E:/develop/innotek/images/diff1.vdi"/>
+ </DiffHardDisk>
+ <DiffHardDisk uuid="{23dbf22e-9f27-49da-bdae-f4c43f968057}">
+ <VirtualDiskImage filePath="E:/develop/innotek/images/diff2.vdi"/>
+ <DiffHardDisk uuid="{23dbf22e-9f27-49da-bdae-f4c43f968057}">
+ <VirtualDiskImage filePath="E:/develop/innotek/images/diff2.vdi"/>
+ </DiffHardDisk>
+ </DiffHardDisk>
+ </HardDisk>
+ <HardDisk uuid="{23dbf22e-9f27-49da-bdae-f4c43f968058}" type="immutable">
+ <ISCSIHardDisk server="abc.com" target="drive"/>
+ </HardDisk>
+ <HardDisk uuid="{23dbf22e-9f27-49da-bdae-f4c43f968059}" type="writethrough">
+ <PhysicalVolume path="D:"/>
+ </HardDisk>
+ </HardDisks>
+ <DVDImages>
+ <Image src="e:/Develop/innotek/images/fdbootcd.iso" uuid="{8c307b77-f331-45fd-b8b4-cbc5281bf0FF}"/>
+ </DVDImages>
+ <FloppyImages>
+ </FloppyImages>
+ </DiskRegistry>
+ <SystemProperties defaultVDIFolder="somefolder"/>
+ <USBDeviceFilters>
+ <DeviceFilter name="Test1" active="false" action="Ignore"/>
+ <DeviceFilter name="Test2" active="false" vendorid="MegaCorpLTD" action="Ignore"/>
+ </USBDeviceFilters>
+ </Global>
+
+</VirtualBox>
+
+
diff --git a/src/VBox/Main/xml/samples/VirtualBox-machine-linux.xml b/src/VBox/Main/xml/samples/VirtualBox-machine-linux.xml
new file mode 100644
index 00000000..8b67af38
--- /dev/null
+++ b/src/VBox/Main/xml/samples/VirtualBox-machine-linux.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
+<!-- Oracle VM VirtualBox Sample Machine Configuration (for a linux host) -->
+<!--
+ * :tabSize=2:indentSize=2:noTabs=true:
+ * :folding=explicit:collapseFolds=1:
+-->
+
+<VirtualBox
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.innotek.de/VirtualBox-settings ../VirtualBox-settings-linux.xsd"
+ xmlns="http://www.innotek.de/VirtualBox-settings"
+ version="1.2-linux"
+>
+
+ <Machine
+ name="dos" OSType="dos" uuid="{1a4d3cff-e693-4ae5-afaf-a1ccc6882183}"
+ stateFile="aaa.sav"
+ >
+
+ <HardDiskAttachments>
+ <HardDiskAttachment hardDisk="{508b474f-62ce-4872-9a42-3af09037a277}"
+ bus="ide0" device="master"/>
+ </HardDiskAttachments>
+
+ <Hardware>
+ <Memory RAMSize="128"/>
+ <Boot>
+ <Order device="None" position="4"/>
+ <Order device="None" position="2"/>
+ <Order device="HardDisk" position="1"/>
+ <Order device="Floppy" position="3"/>
+ </Boot>
+ <Display VRAMSize="4"/>
+ <RemoteDisplay enabled="false"/>
+ <BIOS>
+ <ACPI enabled="false"/>
+ <Logo displayTime="0" fadeIn="true" fadeOut="true"/>
+ </BIOS>
+ <DVDDrive>
+ <Image uuid="{00000000-0000-0000-0100-000000000000}"/>
+ </DVDDrive>
+ <FloppyDrive>
+ <HostDrive src="aaa"/>
+ </FloppyDrive>
+ <USBController enabled="false"/>
+ <Network>
+ <Adapter slot="0" enabled="true" cable="true" MACAddress="08002751DD5E">
+ <NAT/>
+ </Adapter>
+ <Adapter slot="1" enabled="true" cable="false" MACAddress="08002751DD5E">
+ <HostInterface/>
+ </Adapter>
+ <Adapter slot="2" enabled="true" cable="false" MACAddress="08002751DD5E"/>
+ <Adapter slot="3" enabled="true" cable="false" MACAddress="08002751DD5E"/>
+ </Network>
+ <AudioAdapter driver="alsa" enabled="true"/>
+ <SharedFolders>
+ <SharedFolder name="aaa" hostPath="/bbb/ccc"/>
+ <SharedFolder name="aaab" hostPath="/bbb/ccc"/>
+ </SharedFolders>
+ </Hardware>
+
+ </Machine>
+
+</VirtualBox>
diff --git a/src/VBox/Main/xml/samples/VirtualBox-machine-windows.xml b/src/VBox/Main/xml/samples/VirtualBox-machine-windows.xml
new file mode 100644
index 00000000..729a9357
--- /dev/null
+++ b/src/VBox/Main/xml/samples/VirtualBox-machine-windows.xml
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
+<!-- Oracle VM VirtualBox Sample Machine Configuration (for a win32 host) -->
+<!--
+ * :tabSize=2:indentSize=2:noTabs=true:
+ * :folding=explicit:collapseFolds=1:
+-->
+
+<VirtualBox
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.innotek.de/VirtualBox-settings ../VirtualBox-settings-windows.xsd"
+ xmlns="http://www.innotek.de/VirtualBox-settings"
+ version="1.2-windows"
+>
+
+ <Machine
+ name="dos" OSType="dos" uuid="{00000000-0000-0000-0100-000000000000}"
+ currentSnapshot="{00000000-0000-0000-0111-000000000000}"
+ snapshotFolder="."
+ lastStateChange="2005-01-01T10:22:22Z"
+ aborted="true"
+ >
+ <Description>
+ Something is here.
+ </Description>
+ <Hardware>
+ <Memory RAMSize="128"/>
+ <Boot>
+ <Order device="None" position="1"/>
+ <Order device="HardDisk" position="2"/>
+ </Boot>
+ <Display VRAMSize="4"/>
+ <RemoteDisplay enabled="false"/>
+ <BIOS>
+ <ACPI enabled="false"/>
+ <Logo displayTime="0" fadeIn="true" fadeOut="true"/>
+ </BIOS>
+ <DVDDrive>
+ <Image uuid="{00000000-0000-0000-0100-000000000000}"/>
+ </DVDDrive>
+ <FloppyDrive>
+ <HostDrive src="aaa"/>
+ </FloppyDrive>
+ <USBController enabled="false">
+ <DeviceFilter name="Test1" active="false"/>
+ </USBController>
+ <Network>
+ <Adapter slot="0" enabled="false" cable="false" MACAddress="08002751DD5E"/>
+ <Adapter slot="3" enabled="false" cable="false" MACAddress="08002751DD5E">
+ <NAT/>
+ </Adapter>
+ <Adapter slot="2" enabled="false" cable="false" MACAddress="08002751DD5E">
+ <HostInterface name="Home LAN"/>
+ </Adapter>
+ </Network>
+ <AudioAdapter driver="winmm" enabled="true"/>
+ </Hardware>
+
+ <HardDiskAttachments>
+ <HardDiskAttachment hardDisk="{508b474f-62ce-4872-9a42-3af09037a277}"
+ bus="ide0" device="master"/>
+ </HardDiskAttachments>
+
+ <ExtraData>
+ <ExtraDataItem name="GUI/LastCloseAction" value="powerOff"/>
+ </ExtraData>
+
+ <Snapshot
+ name="Snapshot 1" uuid="{00000000-0000-0000-0111-000000000000}"
+ timeStamp="2006-01-01T00:00:00Z"
+ >
+ <Hardware>
+ <Memory RAMSize="128"/>
+ <Boot>
+ <Order device="None" position="1"/>
+ <Order device="HardDisk" position="2"/>
+ </Boot>
+ <Display VRAMSize="4"/>
+ <RemoteDisplay enabled="false"/>
+ <BIOS>
+ <ACPI enabled="false"/>
+ <Logo displayTime="0" fadeIn="true" fadeOut="true"/>
+ </BIOS>
+ <DVDDrive>
+ <Image uuid="{00000000-0000-0000-0100-000000000000}"/>
+ </DVDDrive>
+ <FloppyDrive>
+ <HostDrive src="aaa"/>
+ </FloppyDrive>
+ <USBController enabled="false">
+ <DeviceFilter name="Test1" active="false"/>
+ </USBController>
+ <Network>
+ <Adapter slot="0" enabled="false" cable="false" MACAddress="08002751DD5E"/>
+ <Adapter slot="3" enabled="false" cable="false" MACAddress="08002751DD5E">
+ <NAT/>
+ </Adapter>
+ <Adapter slot="2" enabled="false" cable="false" MACAddress="08002751DD5E">
+ <HostInterface name="Home LAN"/>
+ </Adapter>
+ </Network>
+ <AudioAdapter driver="winmm" enabled="true"/>
+ <Clipboard mode="Disabled"/>
+ </Hardware>
+ <HardDiskAttachments>
+ <HardDiskAttachment hardDisk="{508b474f-62ce-4872-9a42-3af09037a277}"
+ bus="ide0" device="master"/>
+ </HardDiskAttachments>
+
+ <Snapshots>
+
+ <Snapshot
+ name="Snapshot 2" uuid="{00000000-0000-0000-0100-000000000000}"
+ timeStamp="2006-01-01T00:00:00Z"
+ stateFile="D:\aaa\bbb.sav"
+ >
+ <Description>ewfqwef</Description>
+ <Hardware>
+ <Memory RAMSize="128"/>
+ <Boot>
+ <Order device="None" position="1"/>
+ <Order device="HardDisk" position="2"/>
+ </Boot>
+ <Display VRAMSize="4"/>
+ <RemoteDisplay enabled="false"/>
+ <BIOS>
+ <ACPI enabled="false"/>
+ <Logo displayTime="0" fadeIn="true" fadeOut="true"/>
+ </BIOS>
+ <DVDDrive>
+ <Image uuid="{00000000-0000-0000-0100-000000000000}"/>
+ </DVDDrive>
+ <FloppyDrive>
+ <HostDrive src="aaa"/>
+ </FloppyDrive>
+ <USBController enabled="false">
+ <DeviceFilter name="Test1" active="false"/>
+ </USBController>
+ <Network>
+ <Adapter slot="0" enabled="false" cable="false" MACAddress="08002751DD5E"/>
+ <Adapter slot="3" enabled="false" cable="false" MACAddress="08002751DD5E">
+ <NAT/>
+ </Adapter>
+ <Adapter slot="2" enabled="false" cable="false" MACAddress="08002751DD5E">
+ <HostInterface name="Home LAN"/>
+ </Adapter>
+ </Network>
+ <AudioAdapter driver="winmm" enabled="true"/>
+ </Hardware>
+ <HardDiskAttachments>
+ <HardDiskAttachment hardDisk="{508b474f-62ce-4872-9a42-3af09037a277}"
+ bus="ide0" device="master"/>
+ </HardDiskAttachments>
+ <Snapshots/>
+ </Snapshot>
+
+ <Snapshot
+ name="Snapshot 3" uuid="{00000000-0000-0000-0112-000000000000}"
+ timeStamp="2006-01-01T00:00:00Z"
+ >
+ <Hardware>
+ <Memory RAMSize="128"/>
+ <Boot>
+ <Order device="None" position="1"/>
+ <Order device="HardDisk" position="2"/>
+ </Boot>
+ <Display VRAMSize="4"/>
+ <RemoteDisplay enabled="false"/>
+ <BIOS>
+ <ACPI enabled="false"/>
+ <Logo displayTime="0" fadeIn="true" fadeOut="true"/>
+ </BIOS>
+ <DVDDrive>
+ <Image uuid="{00000000-0000-0000-0100-000000000000}"/>
+ </DVDDrive>
+ <FloppyDrive>
+ <HostDrive src="aaa"/>
+ </FloppyDrive>
+ <USBController enabled="false">
+ <DeviceFilter name="Test1" active="false"/>
+ </USBController>
+ <Network>
+ <Adapter slot="0" enabled="false" cable="false" MACAddress="08002751DD5E"/>
+ <Adapter slot="3" enabled="false" cable="false" MACAddress="08002751DD5E">
+ <NAT/>
+ </Adapter>
+ <Adapter slot="2" enabled="false" cable="false" MACAddress="08002751DD5E">
+ <HostInterface name="Home LAN"/>
+ </Adapter>
+ </Network>
+ <AudioAdapter driver="winmm" enabled="true"/>
+ </Hardware>
+ <HardDiskAttachments>
+ <HardDiskAttachment hardDisk="{508b474f-62ce-4872-9a42-3af09037a277}"
+ bus="ide0" device="master"/>
+ </HardDiskAttachments>
+ <Snapshots/>
+ </Snapshot>
+
+ </Snapshots>
+ </Snapshot>
+ </Machine>
+
+</VirtualBox>